@full-self-developing/fsd 1.0.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (1191) hide show
  1. package/.engine/engine-config.json +27 -0
  2. package/CODEBASE_CONTEXT.md +152 -0
  3. package/README.md +111 -0
  4. package/README_zh.md +111 -0
  5. package/UI_SPEC.md +57 -0
  6. package/agents/api-proxy.js +542 -0
  7. package/agents/base.js +280 -0
  8. package/agents/branch-manager.js +135 -0
  9. package/agents/cli-models.json +48 -0
  10. package/agents/coder.js +128 -0
  11. package/agents/core-request.js +174 -0
  12. package/agents/dispatcher.js +491 -0
  13. package/agents/drivers/.atomcode/graph.bin +0 -0
  14. package/agents/drivers/atomcode.js +143 -0
  15. package/agents/drivers/gemini-cli.js +195 -0
  16. package/agents/drivers/index.js +65 -0
  17. package/agents/drivers/openrouter.js +390 -0
  18. package/agents/engine-config.js +444 -0
  19. package/agents/log-fixer.js +72 -0
  20. package/agents/mcp-client-manager.js +159 -0
  21. package/agents/optimizer.js +54 -0
  22. package/agents/path-validator.js +43 -0
  23. package/agents/planner.js +81 -0
  24. package/agents/prompt-manager.js +170 -0
  25. package/agents/skeptic.js +79 -0
  26. package/agents/skills-manager.js +130 -0
  27. package/agents/summarizer.js +34 -0
  28. package/agents/test-runner.js +85 -0
  29. package/bin/cli.js +166 -0
  30. package/client/eslint.config.js +21 -0
  31. package/client/index.html +12 -0
  32. package/client/package-lock.json +3339 -0
  33. package/client/package.json +35 -0
  34. package/client/src/App.jsx +745 -0
  35. package/client/src/api.js +78 -0
  36. package/client/src/components/ChatPanel.jsx +277 -0
  37. package/client/src/components/ConfirmationModal.jsx +61 -0
  38. package/client/src/components/ErrorBoundary.jsx +66 -0
  39. package/client/src/components/FolderPicker.jsx +200 -0
  40. package/client/src/components/LoopPanel.jsx +863 -0
  41. package/client/src/components/NotFound.jsx +52 -0
  42. package/client/src/components/SettingsPanel.jsx +966 -0
  43. package/client/src/components/Sidebar.jsx +318 -0
  44. package/client/src/context/SettingsContext.jsx +353 -0
  45. package/client/src/i18n.js +462 -0
  46. package/client/src/index.css +31 -0
  47. package/client/src/main.jsx +17 -0
  48. package/client/vite.config.js +19 -0
  49. package/design.md +875 -0
  50. package/extensions/alibaba/index.ts +11 -0
  51. package/extensions/alibaba/openclaw.plugin.json +34 -0
  52. package/extensions/alibaba/package.json +15 -0
  53. package/extensions/alibaba/plugin-registration.contract.test.ts +7 -0
  54. package/extensions/alibaba/tsconfig.json +16 -0
  55. package/extensions/alibaba/video-generation-provider.test.ts +92 -0
  56. package/extensions/alibaba/video-generation-provider.ts +83 -0
  57. package/extensions/amazon-bedrock/api.ts +6 -0
  58. package/extensions/amazon-bedrock/aws-credential-refresh.ts +42 -0
  59. package/extensions/amazon-bedrock/config-api.ts +4 -0
  60. package/extensions/amazon-bedrock/config-compat.test.ts +81 -0
  61. package/extensions/amazon-bedrock/config-compat.ts +107 -0
  62. package/extensions/amazon-bedrock/discovery-shared.ts +28 -0
  63. package/extensions/amazon-bedrock/discovery.test.ts +608 -0
  64. package/extensions/amazon-bedrock/discovery.ts +616 -0
  65. package/extensions/amazon-bedrock/embedding-provider.test.ts +109 -0
  66. package/extensions/amazon-bedrock/embedding-provider.ts +470 -0
  67. package/extensions/amazon-bedrock/index.test.ts +1249 -0
  68. package/extensions/amazon-bedrock/index.ts +11 -0
  69. package/extensions/amazon-bedrock/lazy-import.test.ts +56 -0
  70. package/extensions/amazon-bedrock/memory-embedding-adapter.test.ts +105 -0
  71. package/extensions/amazon-bedrock/memory-embedding-adapter.ts +47 -0
  72. package/extensions/amazon-bedrock/npm-shrinkwrap.json +1241 -0
  73. package/extensions/amazon-bedrock/openclaw.plugin.json +80 -0
  74. package/extensions/amazon-bedrock/package.json +41 -0
  75. package/extensions/amazon-bedrock/provider-policy-api.test.ts +46 -0
  76. package/extensions/amazon-bedrock/provider-policy-api.ts +9 -0
  77. package/extensions/amazon-bedrock/register.sync.runtime.ts +659 -0
  78. package/extensions/amazon-bedrock/setup-api.ts +18 -0
  79. package/extensions/amazon-bedrock/thinking-policy.ts +32 -0
  80. package/extensions/amazon-bedrock/tsconfig.json +16 -0
  81. package/extensions/anthropic/api.ts +11 -0
  82. package/extensions/anthropic/claude-model-refs.ts +104 -0
  83. package/extensions/anthropic/cli-auth-seam.ts +13 -0
  84. package/extensions/anthropic/cli-backend-api.ts +6 -0
  85. package/extensions/anthropic/cli-backend.ts +83 -0
  86. package/extensions/anthropic/cli-catalog.ts +42 -0
  87. package/extensions/anthropic/cli-constants.ts +41 -0
  88. package/extensions/anthropic/cli-migration.test.ts +487 -0
  89. package/extensions/anthropic/cli-migration.ts +266 -0
  90. package/extensions/anthropic/cli-shared.test.ts +300 -0
  91. package/extensions/anthropic/cli-shared.ts +248 -0
  92. package/extensions/anthropic/config-defaults.ts +428 -0
  93. package/extensions/anthropic/contract-api.ts +9 -0
  94. package/extensions/anthropic/doctor-contract-api.ts +14 -0
  95. package/extensions/anthropic/index.test.ts +663 -0
  96. package/extensions/anthropic/index.ts +11 -0
  97. package/extensions/anthropic/media-understanding-provider.ts +15 -0
  98. package/extensions/anthropic/openclaw.plugin.json +112 -0
  99. package/extensions/anthropic/package.json +18 -0
  100. package/extensions/anthropic/provider-contract-api.ts +59 -0
  101. package/extensions/anthropic/provider-discovery.ts +35 -0
  102. package/extensions/anthropic/provider-policy-api.test.ts +135 -0
  103. package/extensions/anthropic/provider-policy-api.ts +24 -0
  104. package/extensions/anthropic/provider-runtime.contract.test.ts +3 -0
  105. package/extensions/anthropic/register.runtime.ts +668 -0
  106. package/extensions/anthropic/replay-policy.ts +9 -0
  107. package/extensions/anthropic/setup-api.ts +11 -0
  108. package/extensions/anthropic/stream-wrappers.test.ts +233 -0
  109. package/extensions/anthropic/stream-wrappers.ts +228 -0
  110. package/extensions/anthropic/test-api.ts +3 -0
  111. package/extensions/anthropic/tsconfig.json +16 -0
  112. package/extensions/arcee/api.ts +8 -0
  113. package/extensions/arcee/index.test.ts +195 -0
  114. package/extensions/arcee/index.ts +142 -0
  115. package/extensions/arcee/models.ts +68 -0
  116. package/extensions/arcee/onboard.ts +43 -0
  117. package/extensions/arcee/openclaw.plugin.json +46 -0
  118. package/extensions/arcee/package.json +15 -0
  119. package/extensions/arcee/provider-catalog.ts +54 -0
  120. package/extensions/arcee/tsconfig.json +16 -0
  121. package/extensions/azure-speech/azure-speech.live.test.ts +92 -0
  122. package/extensions/azure-speech/index.ts +11 -0
  123. package/extensions/azure-speech/openclaw.plugin.json +66 -0
  124. package/extensions/azure-speech/package.json +15 -0
  125. package/extensions/azure-speech/speech-provider.test.ts +242 -0
  126. package/extensions/azure-speech/speech-provider.ts +306 -0
  127. package/extensions/azure-speech/tsconfig.json +16 -0
  128. package/extensions/azure-speech/tts.test.ts +127 -0
  129. package/extensions/azure-speech/tts.ts +209 -0
  130. package/extensions/byteplus/api.ts +8 -0
  131. package/extensions/byteplus/index.test.ts +60 -0
  132. package/extensions/byteplus/index.ts +84 -0
  133. package/extensions/byteplus/live.test.ts +60 -0
  134. package/extensions/byteplus/models.ts +35 -0
  135. package/extensions/byteplus/openclaw.plugin.json +165 -0
  136. package/extensions/byteplus/package.json +15 -0
  137. package/extensions/byteplus/plugin-registration.contract.test.ts +8 -0
  138. package/extensions/byteplus/provider-catalog.ts +17 -0
  139. package/extensions/byteplus/provider-discovery.ts +31 -0
  140. package/extensions/byteplus/tsconfig.json +16 -0
  141. package/extensions/byteplus/video-generation-provider.test.ts +223 -0
  142. package/extensions/byteplus/video-generation-provider.ts +389 -0
  143. package/extensions/cerebras/api.ts +7 -0
  144. package/extensions/cerebras/index.ts +41 -0
  145. package/extensions/cerebras/models.ts +25 -0
  146. package/extensions/cerebras/onboard.ts +26 -0
  147. package/extensions/cerebras/openclaw.plugin.json +111 -0
  148. package/extensions/cerebras/package.json +15 -0
  149. package/extensions/cerebras/provider-catalog.ts +10 -0
  150. package/extensions/cerebras/tsconfig.json +16 -0
  151. package/extensions/chutes/api.ts +14 -0
  152. package/extensions/chutes/implicit-provider.test.ts +107 -0
  153. package/extensions/chutes/index.ts +194 -0
  154. package/extensions/chutes/model-discovery-env.ts +5 -0
  155. package/extensions/chutes/models.test.ts +289 -0
  156. package/extensions/chutes/models.ts +632 -0
  157. package/extensions/chutes/oauth.ts +235 -0
  158. package/extensions/chutes/onboard.ts +63 -0
  159. package/extensions/chutes/openclaw.plugin.json +726 -0
  160. package/extensions/chutes/package.json +15 -0
  161. package/extensions/chutes/provider-catalog.ts +29 -0
  162. package/extensions/chutes/tsconfig.json +16 -0
  163. package/extensions/cloudflare-ai-gateway/api.ts +14 -0
  164. package/extensions/cloudflare-ai-gateway/catalog-provider.ts +73 -0
  165. package/extensions/cloudflare-ai-gateway/index.test.ts +60 -0
  166. package/extensions/cloudflare-ai-gateway/index.ts +233 -0
  167. package/extensions/cloudflare-ai-gateway/models.ts +44 -0
  168. package/extensions/cloudflare-ai-gateway/onboard.ts +91 -0
  169. package/extensions/cloudflare-ai-gateway/openclaw.plugin.json +44 -0
  170. package/extensions/cloudflare-ai-gateway/package.json +15 -0
  171. package/extensions/cloudflare-ai-gateway/provider-discovery.contract.test.ts +3 -0
  172. package/extensions/cloudflare-ai-gateway/stream-wrappers.test.ts +160 -0
  173. package/extensions/cloudflare-ai-gateway/stream-wrappers.ts +32 -0
  174. package/extensions/cloudflare-ai-gateway/tsconfig.json +16 -0
  175. package/extensions/codex/doctor-contract-api.test.ts +44 -0
  176. package/extensions/codex/doctor-contract-api.ts +68 -0
  177. package/extensions/codex/harness.ts +85 -0
  178. package/extensions/codex/index.test.ts +230 -0
  179. package/extensions/codex/index.ts +125 -0
  180. package/extensions/codex/media-understanding-provider.test.ts +496 -0
  181. package/extensions/codex/media-understanding-provider.ts +524 -0
  182. package/extensions/codex/npm-shrinkwrap.json +1949 -0
  183. package/extensions/codex/openclaw.plugin.json +403 -0
  184. package/extensions/codex/package.json +41 -0
  185. package/extensions/codex/prompt-overlay-runtime-contract.test.ts +48 -0
  186. package/extensions/codex/prompt-overlay.ts +21 -0
  187. package/extensions/codex/provider-catalog.ts +83 -0
  188. package/extensions/codex/provider-discovery.ts +45 -0
  189. package/extensions/codex/provider.test.ts +384 -0
  190. package/extensions/codex/provider.ts +243 -0
  191. package/extensions/codex/src/app-server/app-inventory-cache.test.ts +176 -0
  192. package/extensions/codex/src/app-server/app-inventory-cache.ts +324 -0
  193. package/extensions/codex/src/app-server/approval-bridge.test.ts +1472 -0
  194. package/extensions/codex/src/app-server/approval-bridge.ts +1211 -0
  195. package/extensions/codex/src/app-server/auth-bridge.test.ts +1449 -0
  196. package/extensions/codex/src/app-server/auth-bridge.ts +614 -0
  197. package/extensions/codex/src/app-server/auth-profile-runtime-contract.test.ts +242 -0
  198. package/extensions/codex/src/app-server/capabilities.ts +27 -0
  199. package/extensions/codex/src/app-server/client-factory.ts +24 -0
  200. package/extensions/codex/src/app-server/client.test.ts +563 -0
  201. package/extensions/codex/src/app-server/client.ts +721 -0
  202. package/extensions/codex/src/app-server/compact.test.ts +1029 -0
  203. package/extensions/codex/src/app-server/compact.ts +662 -0
  204. package/extensions/codex/src/app-server/computer-use.test.ts +788 -0
  205. package/extensions/codex/src/app-server/computer-use.ts +683 -0
  206. package/extensions/codex/src/app-server/config.test.ts +948 -0
  207. package/extensions/codex/src/app-server/config.ts +1093 -0
  208. package/extensions/codex/src/app-server/context-engine-projection.test.ts +252 -0
  209. package/extensions/codex/src/app-server/context-engine-projection.ts +403 -0
  210. package/extensions/codex/src/app-server/delivery-no-reply-runtime-contract.test.ts +80 -0
  211. package/extensions/codex/src/app-server/dynamic-tool-diagnostics.ts +73 -0
  212. package/extensions/codex/src/app-server/dynamic-tool-profile.ts +70 -0
  213. package/extensions/codex/src/app-server/dynamic-tools.test.ts +1357 -0
  214. package/extensions/codex/src/app-server/dynamic-tools.ts +646 -0
  215. package/extensions/codex/src/app-server/elicitation-bridge.test.ts +1281 -0
  216. package/extensions/codex/src/app-server/elicitation-bridge.ts +828 -0
  217. package/extensions/codex/src/app-server/event-projector.test.ts +2885 -0
  218. package/extensions/codex/src/app-server/event-projector.ts +2047 -0
  219. package/extensions/codex/src/app-server/image-payload-sanitizer.test.ts +49 -0
  220. package/extensions/codex/src/app-server/image-payload-sanitizer.ts +195 -0
  221. package/extensions/codex/src/app-server/local-runtime-attribution.ts +39 -0
  222. package/extensions/codex/src/app-server/managed-binary.test.ts +141 -0
  223. package/extensions/codex/src/app-server/managed-binary.ts +193 -0
  224. package/extensions/codex/src/app-server/models.test.ts +246 -0
  225. package/extensions/codex/src/app-server/models.ts +172 -0
  226. package/extensions/codex/src/app-server/native-hook-relay.test.ts +274 -0
  227. package/extensions/codex/src/app-server/native-hook-relay.ts +150 -0
  228. package/extensions/codex/src/app-server/native-subagent-monitor.test.ts +1125 -0
  229. package/extensions/codex/src/app-server/native-subagent-monitor.ts +1061 -0
  230. package/extensions/codex/src/app-server/native-subagent-notification.test.ts +176 -0
  231. package/extensions/codex/src/app-server/native-subagent-notification.ts +222 -0
  232. package/extensions/codex/src/app-server/native-subagent-task-ids.ts +3 -0
  233. package/extensions/codex/src/app-server/native-subagent-task-mirror.test.ts +625 -0
  234. package/extensions/codex/src/app-server/native-subagent-task-mirror.ts +460 -0
  235. package/extensions/codex/src/app-server/notification-correlation.ts +91 -0
  236. package/extensions/codex/src/app-server/openclaw-owned-tool-runtime-contract.test.ts +456 -0
  237. package/extensions/codex/src/app-server/outcome-fallback-runtime-contract.test.ts +404 -0
  238. package/extensions/codex/src/app-server/plugin-activation.test.ts +336 -0
  239. package/extensions/codex/src/app-server/plugin-activation.ts +283 -0
  240. package/extensions/codex/src/app-server/plugin-app-cache-key.ts +74 -0
  241. package/extensions/codex/src/app-server/plugin-approval-roundtrip.ts +122 -0
  242. package/extensions/codex/src/app-server/plugin-inventory.test.ts +355 -0
  243. package/extensions/codex/src/app-server/plugin-inventory.ts +357 -0
  244. package/extensions/codex/src/app-server/plugin-thread-config.test.ts +865 -0
  245. package/extensions/codex/src/app-server/plugin-thread-config.ts +455 -0
  246. package/extensions/codex/src/app-server/protocol-generated/json/DynamicToolCallParams.json +33 -0
  247. package/extensions/codex/src/app-server/protocol-generated/json/v2/ErrorNotification.json +199 -0
  248. package/extensions/codex/src/app-server/protocol-generated/json/v2/GetAccountResponse.json +102 -0
  249. package/extensions/codex/src/app-server/protocol-generated/json/v2/ModelListResponse.json +227 -0
  250. package/extensions/codex/src/app-server/protocol-generated/json/v2/ThreadResumeResponse.json +2630 -0
  251. package/extensions/codex/src/app-server/protocol-generated/json/v2/ThreadStartResponse.json +2630 -0
  252. package/extensions/codex/src/app-server/protocol-generated/json/v2/TurnCompletedNotification.json +1659 -0
  253. package/extensions/codex/src/app-server/protocol-generated/json/v2/TurnStartResponse.json +1655 -0
  254. package/extensions/codex/src/app-server/protocol-validators.test.ts +75 -0
  255. package/extensions/codex/src/app-server/protocol-validators.ts +203 -0
  256. package/extensions/codex/src/app-server/protocol.ts +537 -0
  257. package/extensions/codex/src/app-server/rate-limit-cache.ts +48 -0
  258. package/extensions/codex/src/app-server/rate-limits.test.ts +202 -0
  259. package/extensions/codex/src/app-server/rate-limits.ts +583 -0
  260. package/extensions/codex/src/app-server/request.test.ts +68 -0
  261. package/extensions/codex/src/app-server/request.ts +90 -0
  262. package/extensions/codex/src/app-server/run-attempt-thread-cleanup.test.ts +197 -0
  263. package/extensions/codex/src/app-server/run-attempt.context-engine.test.ts +1246 -0
  264. package/extensions/codex/src/app-server/run-attempt.test.ts +10799 -0
  265. package/extensions/codex/src/app-server/run-attempt.ts +5264 -0
  266. package/extensions/codex/src/app-server/run-attempt.vision-tools.test.ts +35 -0
  267. package/extensions/codex/src/app-server/sandbox-exec-server/filesystem.ts +261 -0
  268. package/extensions/codex/src/app-server/sandbox-exec-server/fs-policy.ts +346 -0
  269. package/extensions/codex/src/app-server/sandbox-exec-server/http.ts +312 -0
  270. package/extensions/codex/src/app-server/sandbox-exec-server/json-rpc.ts +93 -0
  271. package/extensions/codex/src/app-server/sandbox-exec-server/processes.ts +411 -0
  272. package/extensions/codex/src/app-server/sandbox-exec-server/runtime.ts +22 -0
  273. package/extensions/codex/src/app-server/sandbox-exec-server/types.ts +80 -0
  274. package/extensions/codex/src/app-server/sandbox-exec-server.fs.test.ts +527 -0
  275. package/extensions/codex/src/app-server/sandbox-exec-server.http.test.ts +210 -0
  276. package/extensions/codex/src/app-server/sandbox-exec-server.test-helpers.ts +236 -0
  277. package/extensions/codex/src/app-server/sandbox-exec-server.test.ts +460 -0
  278. package/extensions/codex/src/app-server/sandbox-exec-server.ts +355 -0
  279. package/extensions/codex/src/app-server/sandbox-guard.ts +153 -0
  280. package/extensions/codex/src/app-server/schema-normalization-runtime-contract.test.ts +206 -0
  281. package/extensions/codex/src/app-server/session-binding.test.ts +303 -0
  282. package/extensions/codex/src/app-server/session-binding.ts +407 -0
  283. package/extensions/codex/src/app-server/session-history.ts +44 -0
  284. package/extensions/codex/src/app-server/shared-client.test.ts +591 -0
  285. package/extensions/codex/src/app-server/shared-client.ts +289 -0
  286. package/extensions/codex/src/app-server/side-question.test.ts +1243 -0
  287. package/extensions/codex/src/app-server/side-question.ts +1019 -0
  288. package/extensions/codex/src/app-server/test-support.ts +48 -0
  289. package/extensions/codex/src/app-server/thread-lifecycle.test.ts +447 -0
  290. package/extensions/codex/src/app-server/thread-lifecycle.ts +1004 -0
  291. package/extensions/codex/src/app-server/thread-lifecycle.user-mcp-servers.test.ts +442 -0
  292. package/extensions/codex/src/app-server/timeout.ts +9 -0
  293. package/extensions/codex/src/app-server/tool-progress-normalization.ts +77 -0
  294. package/extensions/codex/src/app-server/trajectory.test.ts +205 -0
  295. package/extensions/codex/src/app-server/trajectory.ts +368 -0
  296. package/extensions/codex/src/app-server/transcript-mirror.test.ts +527 -0
  297. package/extensions/codex/src/app-server/transcript-mirror.ts +208 -0
  298. package/extensions/codex/src/app-server/transcript-repair-runtime-contract.test.ts +44 -0
  299. package/extensions/codex/src/app-server/transport-stdio.test.ts +184 -0
  300. package/extensions/codex/src/app-server/transport-stdio.ts +107 -0
  301. package/extensions/codex/src/app-server/transport-websocket.test.ts +71 -0
  302. package/extensions/codex/src/app-server/transport-websocket.ts +90 -0
  303. package/extensions/codex/src/app-server/transport.ts +117 -0
  304. package/extensions/codex/src/app-server/user-input-bridge.test.ts +249 -0
  305. package/extensions/codex/src/app-server/user-input-bridge.ts +316 -0
  306. package/extensions/codex/src/app-server/version.ts +5 -0
  307. package/extensions/codex/src/app-server/vision-tools.ts +12 -0
  308. package/extensions/codex/src/command-account.ts +589 -0
  309. package/extensions/codex/src/command-formatters.ts +426 -0
  310. package/extensions/codex/src/command-handlers.ts +2092 -0
  311. package/extensions/codex/src/command-plugins-management.test.ts +172 -0
  312. package/extensions/codex/src/command-plugins-management.ts +137 -0
  313. package/extensions/codex/src/command-rpc.test.ts +16 -0
  314. package/extensions/codex/src/command-rpc.ts +146 -0
  315. package/extensions/codex/src/commands.test.ts +3737 -0
  316. package/extensions/codex/src/commands.ts +65 -0
  317. package/extensions/codex/src/conversation-binding-data.ts +124 -0
  318. package/extensions/codex/src/conversation-binding.test.ts +697 -0
  319. package/extensions/codex/src/conversation-binding.ts +575 -0
  320. package/extensions/codex/src/conversation-control.test.ts +126 -0
  321. package/extensions/codex/src/conversation-control.ts +303 -0
  322. package/extensions/codex/src/conversation-turn-collector.test.ts +191 -0
  323. package/extensions/codex/src/conversation-turn-collector.ts +190 -0
  324. package/extensions/codex/src/conversation-turn-input.test.ts +141 -0
  325. package/extensions/codex/src/conversation-turn-input.ts +106 -0
  326. package/extensions/codex/src/manifest.test.ts +20 -0
  327. package/extensions/codex/src/migration/apply.ts +501 -0
  328. package/extensions/codex/src/migration/helpers.ts +55 -0
  329. package/extensions/codex/src/migration/plan.ts +461 -0
  330. package/extensions/codex/src/migration/provider.test.ts +1741 -0
  331. package/extensions/codex/src/migration/provider.ts +41 -0
  332. package/extensions/codex/src/migration/source.ts +643 -0
  333. package/extensions/codex/src/migration/targets.ts +25 -0
  334. package/extensions/codex/src/node-cli-sessions.test.ts +180 -0
  335. package/extensions/codex/src/node-cli-sessions.ts +711 -0
  336. package/extensions/codex/test-api.ts +95 -0
  337. package/extensions/codex/tsconfig.json +16 -0
  338. package/extensions/comfy/comfy.live.test.ts +128 -0
  339. package/extensions/comfy/image-generation-provider.test.ts +457 -0
  340. package/extensions/comfy/image-generation-provider.ts +79 -0
  341. package/extensions/comfy/index.test.ts +51 -0
  342. package/extensions/comfy/index.ts +45 -0
  343. package/extensions/comfy/music-generation-provider.test.ts +101 -0
  344. package/extensions/comfy/music-generation-provider.ts +88 -0
  345. package/extensions/comfy/openclaw.plugin.json +268 -0
  346. package/extensions/comfy/package.json +15 -0
  347. package/extensions/comfy/plugin-registration.contract.test.ts +11 -0
  348. package/extensions/comfy/test-helpers.ts +113 -0
  349. package/extensions/comfy/tsconfig.json +16 -0
  350. package/extensions/comfy/video-generation-provider.test.ts +184 -0
  351. package/extensions/comfy/video-generation-provider.ts +104 -0
  352. package/extensions/comfy/workflow-runtime.ts +827 -0
  353. package/extensions/deepgram/audio.live.test.ts +75 -0
  354. package/extensions/deepgram/audio.test.ts +146 -0
  355. package/extensions/deepgram/audio.ts +109 -0
  356. package/extensions/deepgram/index.ts +13 -0
  357. package/extensions/deepgram/media-understanding-provider.ts +10 -0
  358. package/extensions/deepgram/openclaw.plugin.json +30 -0
  359. package/extensions/deepgram/package.json +15 -0
  360. package/extensions/deepgram/realtime-transcription-provider.test.ts +69 -0
  361. package/extensions/deepgram/realtime-transcription-provider.ts +283 -0
  362. package/extensions/deepgram/test-api.ts +2 -0
  363. package/extensions/deepgram/tsconfig.json +16 -0
  364. package/extensions/deepinfra/api.ts +8 -0
  365. package/extensions/deepinfra/embedding-provider.ts +33 -0
  366. package/extensions/deepinfra/image-generation-provider.test.ts +224 -0
  367. package/extensions/deepinfra/image-generation-provider.ts +89 -0
  368. package/extensions/deepinfra/index.test.ts +113 -0
  369. package/extensions/deepinfra/index.ts +84 -0
  370. package/extensions/deepinfra/media-models.ts +50 -0
  371. package/extensions/deepinfra/media-understanding-provider.test.ts +73 -0
  372. package/extensions/deepinfra/media-understanding-provider.ts +37 -0
  373. package/extensions/deepinfra/memory-embedding-adapter.test.ts +31 -0
  374. package/extensions/deepinfra/memory-embedding-adapter.ts +35 -0
  375. package/extensions/deepinfra/onboard.test.ts +172 -0
  376. package/extensions/deepinfra/onboard.ts +36 -0
  377. package/extensions/deepinfra/openclaw.plugin.json +203 -0
  378. package/extensions/deepinfra/package.json +15 -0
  379. package/extensions/deepinfra/provider-catalog.ts +24 -0
  380. package/extensions/deepinfra/provider-models.test.ts +217 -0
  381. package/extensions/deepinfra/provider-models.ts +167 -0
  382. package/extensions/deepinfra/provider-policy-api.test.ts +41 -0
  383. package/extensions/deepinfra/provider-policy-api.ts +21 -0
  384. package/extensions/deepinfra/provider.contract.test.ts +3 -0
  385. package/extensions/deepinfra/speech-provider.test.ts +169 -0
  386. package/extensions/deepinfra/speech-provider.ts +41 -0
  387. package/extensions/deepinfra/tsconfig.json +16 -0
  388. package/extensions/deepinfra/video-generation-provider.test.ts +194 -0
  389. package/extensions/deepinfra/video-generation-provider.ts +262 -0
  390. package/extensions/deepseek/api.ts +7 -0
  391. package/extensions/deepseek/deepseek.live.test.ts +232 -0
  392. package/extensions/deepseek/index.test.ts +488 -0
  393. package/extensions/deepseek/index.ts +58 -0
  394. package/extensions/deepseek/models.ts +33 -0
  395. package/extensions/deepseek/onboard.ts +31 -0
  396. package/extensions/deepseek/openclaw.plugin.json +132 -0
  397. package/extensions/deepseek/package.json +15 -0
  398. package/extensions/deepseek/provider-catalog.ts +14 -0
  399. package/extensions/deepseek/provider-discovery.ts +17 -0
  400. package/extensions/deepseek/provider-policy-api.test.ts +264 -0
  401. package/extensions/deepseek/provider-policy-api.ts +104 -0
  402. package/extensions/deepseek/stream.ts +14 -0
  403. package/extensions/deepseek/thinking.ts +19 -0
  404. package/extensions/deepseek/tsconfig.json +16 -0
  405. package/extensions/elevenlabs/config-api.ts +8 -0
  406. package/extensions/elevenlabs/config-compat.test.ts +75 -0
  407. package/extensions/elevenlabs/config-compat.ts +181 -0
  408. package/extensions/elevenlabs/contract-api.ts +8 -0
  409. package/extensions/elevenlabs/doctor-contract.ts +34 -0
  410. package/extensions/elevenlabs/elevenlabs.live.test.ts +91 -0
  411. package/extensions/elevenlabs/index.ts +15 -0
  412. package/extensions/elevenlabs/media-understanding-provider.test.ts +95 -0
  413. package/extensions/elevenlabs/media-understanding-provider.ts +85 -0
  414. package/extensions/elevenlabs/openclaw.plugin.json +40 -0
  415. package/extensions/elevenlabs/package.json +15 -0
  416. package/extensions/elevenlabs/realtime-transcription-provider.test.ts +60 -0
  417. package/extensions/elevenlabs/realtime-transcription-provider.ts +284 -0
  418. package/extensions/elevenlabs/setup-api.ts +11 -0
  419. package/extensions/elevenlabs/shared.ts +10 -0
  420. package/extensions/elevenlabs/speech-provider.test.ts +124 -0
  421. package/extensions/elevenlabs/speech-provider.ts +594 -0
  422. package/extensions/elevenlabs/test-api.ts +6 -0
  423. package/extensions/elevenlabs/tsconfig.json +16 -0
  424. package/extensions/elevenlabs/tts.test.ts +212 -0
  425. package/extensions/elevenlabs/tts.ts +198 -0
  426. package/extensions/fal/image-generation-provider.test.ts +710 -0
  427. package/extensions/fal/image-generation-provider.ts +463 -0
  428. package/extensions/fal/index.ts +19 -0
  429. package/extensions/fal/music-generation-provider.test.ts +200 -0
  430. package/extensions/fal/music-generation-provider.ts +219 -0
  431. package/extensions/fal/onboard.ts +21 -0
  432. package/extensions/fal/openclaw.plugin.json +42 -0
  433. package/extensions/fal/package.json +15 -0
  434. package/extensions/fal/plugin-registration.contract.test.ts +11 -0
  435. package/extensions/fal/provider-contract-api.ts +31 -0
  436. package/extensions/fal/provider-registration.ts +38 -0
  437. package/extensions/fal/test-api.ts +3 -0
  438. package/extensions/fal/tsconfig.json +16 -0
  439. package/extensions/fal/video-generation-provider.test.ts +566 -0
  440. package/extensions/fal/video-generation-provider.ts +648 -0
  441. package/extensions/fireworks/index.test.ts +181 -0
  442. package/extensions/fireworks/index.ts +85 -0
  443. package/extensions/fireworks/model-id.ts +5 -0
  444. package/extensions/fireworks/onboard.ts +30 -0
  445. package/extensions/fireworks/openclaw.plugin.json +73 -0
  446. package/extensions/fireworks/package.json +18 -0
  447. package/extensions/fireworks/provider-catalog.ts +50 -0
  448. package/extensions/fireworks/provider-policy-api.ts +8 -0
  449. package/extensions/fireworks/stream.test.ts +184 -0
  450. package/extensions/fireworks/stream.ts +39 -0
  451. package/extensions/fireworks/thinking-policy.ts +17 -0
  452. package/extensions/fireworks/tsconfig.json +16 -0
  453. package/extensions/github-copilot/api.ts +1 -0
  454. package/extensions/github-copilot/auth.test.ts +109 -0
  455. package/extensions/github-copilot/auth.ts +65 -0
  456. package/extensions/github-copilot/connection-bound-ids.live.test.ts +231 -0
  457. package/extensions/github-copilot/connection-bound-ids.test.ts +96 -0
  458. package/extensions/github-copilot/connection-bound-ids.ts +81 -0
  459. package/extensions/github-copilot/embeddings.test.ts +287 -0
  460. package/extensions/github-copilot/embeddings.ts +342 -0
  461. package/extensions/github-copilot/index.test.ts +660 -0
  462. package/extensions/github-copilot/index.ts +492 -0
  463. package/extensions/github-copilot/login.ts +323 -0
  464. package/extensions/github-copilot/model-metadata.ts +51 -0
  465. package/extensions/github-copilot/models-defaults.ts +61 -0
  466. package/extensions/github-copilot/models.test.ts +695 -0
  467. package/extensions/github-copilot/models.ts +274 -0
  468. package/extensions/github-copilot/openclaw.plugin.json +270 -0
  469. package/extensions/github-copilot/package.json +19 -0
  470. package/extensions/github-copilot/provider-auth.contract.test.ts +3 -0
  471. package/extensions/github-copilot/provider-discovery.contract.test.ts +7 -0
  472. package/extensions/github-copilot/provider-runtime.contract.test.ts +3 -0
  473. package/extensions/github-copilot/register.runtime.ts +24 -0
  474. package/extensions/github-copilot/replay-policy.ts +9 -0
  475. package/extensions/github-copilot/stream.test.ts +282 -0
  476. package/extensions/github-copilot/stream.ts +157 -0
  477. package/extensions/github-copilot/token.ts +6 -0
  478. package/extensions/github-copilot/tsconfig.json +16 -0
  479. package/extensions/github-copilot/usage.ts +68 -0
  480. package/extensions/google/api.test.ts +249 -0
  481. package/extensions/google/api.ts +91 -0
  482. package/extensions/google/cli-backend.ts +58 -0
  483. package/extensions/google/default-model.test.ts +115 -0
  484. package/extensions/google/doctor-contract-api.ts +18 -0
  485. package/extensions/google/embedding-batch.ts +379 -0
  486. package/extensions/google/embedding-provider.test.ts +264 -0
  487. package/extensions/google/embedding-provider.ts +441 -0
  488. package/extensions/google/gemini-auth.ts +20 -0
  489. package/extensions/google/gemini-cli-provider.ts +145 -0
  490. package/extensions/google/generation-provider-metadata.ts +121 -0
  491. package/extensions/google/google-genai-runtime.ts +8 -0
  492. package/extensions/google/google-shared.test-helpers.ts +99 -0
  493. package/extensions/google/google-shared.test.ts +380 -0
  494. package/extensions/google/google.live.test.ts +179 -0
  495. package/extensions/google/image-generation-provider.test.ts +503 -0
  496. package/extensions/google/image-generation-provider.ts +272 -0
  497. package/extensions/google/index.test.ts +310 -0
  498. package/extensions/google/index.ts +354 -0
  499. package/extensions/google/manifest.test.ts +104 -0
  500. package/extensions/google/media-understanding-provider.ts +164 -0
  501. package/extensions/google/media-understanding-provider.video.test.ts +158 -0
  502. package/extensions/google/memory-embedding-adapter.ts +79 -0
  503. package/extensions/google/model-id.test.ts +42 -0
  504. package/extensions/google/model-id.ts +35 -0
  505. package/extensions/google/music-generation-provider.test.ts +278 -0
  506. package/extensions/google/music-generation-provider.ts +176 -0
  507. package/extensions/google/oauth-token-shared.test.ts +39 -0
  508. package/extensions/google/oauth-token-shared.ts +42 -0
  509. package/extensions/google/oauth.credentials.ts +273 -0
  510. package/extensions/google/oauth.flow.ts +61 -0
  511. package/extensions/google/oauth.http.ts +24 -0
  512. package/extensions/google/oauth.project.ts +232 -0
  513. package/extensions/google/oauth.runtime.ts +1 -0
  514. package/extensions/google/oauth.settings.ts +72 -0
  515. package/extensions/google/oauth.shared.ts +44 -0
  516. package/extensions/google/oauth.test.ts +922 -0
  517. package/extensions/google/oauth.token.ts +138 -0
  518. package/extensions/google/oauth.ts +104 -0
  519. package/extensions/google/onboard.ts +78 -0
  520. package/extensions/google/openclaw.plugin.json +706 -0
  521. package/extensions/google/package.json +19 -0
  522. package/extensions/google/plugin-registration.contract.test.ts +12 -0
  523. package/extensions/google/provider-contract-api.ts +77 -0
  524. package/extensions/google/provider-hooks.ts +18 -0
  525. package/extensions/google/provider-models.test.ts +513 -0
  526. package/extensions/google/provider-models.ts +237 -0
  527. package/extensions/google/provider-policy-api.test.ts +201 -0
  528. package/extensions/google/provider-policy-api.ts +11 -0
  529. package/extensions/google/provider-policy.ts +208 -0
  530. package/extensions/google/provider-registration.ts +72 -0
  531. package/extensions/google/provider-runtime.contract.test.ts +3 -0
  532. package/extensions/google/realtime-voice-provider.test.ts +857 -0
  533. package/extensions/google/realtime-voice-provider.ts +952 -0
  534. package/extensions/google/runtime-api.ts +19 -0
  535. package/extensions/google/setup-api.test.ts +23 -0
  536. package/extensions/google/setup-api.ts +13 -0
  537. package/extensions/google/speech-provider.test.ts +682 -0
  538. package/extensions/google/speech-provider.ts +683 -0
  539. package/extensions/google/src/gemini-web-search-provider.runtime.ts +367 -0
  540. package/extensions/google/src/gemini-web-search-provider.shared.ts +45 -0
  541. package/extensions/google/src/gemini-web-search-provider.ts +151 -0
  542. package/extensions/google/test-api.ts +6 -0
  543. package/extensions/google/thinking-api.ts +14 -0
  544. package/extensions/google/thinking.test.ts +153 -0
  545. package/extensions/google/thinking.ts +14 -0
  546. package/extensions/google/transport-stream.test.ts +1726 -0
  547. package/extensions/google/transport-stream.ts +1396 -0
  548. package/extensions/google/tsconfig.json +16 -0
  549. package/extensions/google/vertex-adc.ts +188 -0
  550. package/extensions/google/video-generation-provider.test.ts +573 -0
  551. package/extensions/google/video-generation-provider.ts +591 -0
  552. package/extensions/google/web-search-contract-api.ts +1 -0
  553. package/extensions/google/web-search-provider.test.ts +548 -0
  554. package/extensions/google/web-search-provider.ts +1 -0
  555. package/extensions/groq/api.ts +60 -0
  556. package/extensions/groq/index.test.ts +90 -0
  557. package/extensions/groq/index.ts +21 -0
  558. package/extensions/groq/media-understanding-provider.ts +21 -0
  559. package/extensions/groq/openclaw.plugin.json +314 -0
  560. package/extensions/groq/package.json +15 -0
  561. package/extensions/groq/test-api.ts +1 -0
  562. package/extensions/groq/tsconfig.json +16 -0
  563. package/extensions/huggingface/api.ts +10 -0
  564. package/extensions/huggingface/index.test.ts +81 -0
  565. package/extensions/huggingface/index.ts +60 -0
  566. package/extensions/huggingface/model-discovery-env.ts +5 -0
  567. package/extensions/huggingface/models.test.ts +98 -0
  568. package/extensions/huggingface/models.ts +218 -0
  569. package/extensions/huggingface/onboard.ts +26 -0
  570. package/extensions/huggingface/openclaw.plugin.json +57 -0
  571. package/extensions/huggingface/package.json +15 -0
  572. package/extensions/huggingface/provider-catalog.ts +22 -0
  573. package/extensions/huggingface/tsconfig.json +16 -0
  574. package/extensions/image-generation-core/api.ts +30 -0
  575. package/extensions/image-generation-core/package.json +10 -0
  576. package/extensions/image-generation-core/runtime-api.ts +6 -0
  577. package/extensions/image-generation-core/src/runtime.test.ts +29 -0
  578. package/extensions/image-generation-core/src/runtime.ts +6 -0
  579. package/extensions/image-generation-core/tsconfig.json +16 -0
  580. package/extensions/kimi-coding/api.ts +8 -0
  581. package/extensions/kimi-coding/implicit-provider.test.ts +116 -0
  582. package/extensions/kimi-coding/index.test.ts +45 -0
  583. package/extensions/kimi-coding/index.ts +113 -0
  584. package/extensions/kimi-coding/onboard.test.ts +44 -0
  585. package/extensions/kimi-coding/onboard.ts +42 -0
  586. package/extensions/kimi-coding/openclaw.plugin.json +64 -0
  587. package/extensions/kimi-coding/package.json +18 -0
  588. package/extensions/kimi-coding/provider-catalog.test.ts +23 -0
  589. package/extensions/kimi-coding/provider-catalog.ts +58 -0
  590. package/extensions/kimi-coding/replay-policy.test.ts +10 -0
  591. package/extensions/kimi-coding/replay-policy.ts +3 -0
  592. package/extensions/kimi-coding/stream.test.ts +603 -0
  593. package/extensions/kimi-coding/stream.ts +399 -0
  594. package/extensions/kimi-coding/tsconfig.json +16 -0
  595. package/extensions/litellm/api.ts +8 -0
  596. package/extensions/litellm/image-generation-provider.test.ts +348 -0
  597. package/extensions/litellm/image-generation-provider.ts +142 -0
  598. package/extensions/litellm/index.test.ts +107 -0
  599. package/extensions/litellm/index.ts +108 -0
  600. package/extensions/litellm/onboard.test.ts +21 -0
  601. package/extensions/litellm/onboard.ts +55 -0
  602. package/extensions/litellm/openclaw.plugin.json +35 -0
  603. package/extensions/litellm/package.json +15 -0
  604. package/extensions/litellm/provider-catalog.ts +10 -0
  605. package/extensions/litellm/tsconfig.json +16 -0
  606. package/extensions/lmstudio/README.md +3 -0
  607. package/extensions/lmstudio/api.ts +36 -0
  608. package/extensions/lmstudio/index.test.ts +207 -0
  609. package/extensions/lmstudio/index.ts +137 -0
  610. package/extensions/lmstudio/memory-embedding-adapter.ts +36 -0
  611. package/extensions/lmstudio/openclaw.plugin.json +53 -0
  612. package/extensions/lmstudio/package.json +15 -0
  613. package/extensions/lmstudio/plugin-registration.contract.test.ts +6 -0
  614. package/extensions/lmstudio/runtime-api.ts +35 -0
  615. package/extensions/lmstudio/src/api.ts +42 -0
  616. package/extensions/lmstudio/src/defaults.ts +14 -0
  617. package/extensions/lmstudio/src/embedding-provider.ts +147 -0
  618. package/extensions/lmstudio/src/models.fetch.ts +277 -0
  619. package/extensions/lmstudio/src/models.test.ts +491 -0
  620. package/extensions/lmstudio/src/models.ts +536 -0
  621. package/extensions/lmstudio/src/plain-text-tool-calls.ts +24 -0
  622. package/extensions/lmstudio/src/provider-auth.ts +59 -0
  623. package/extensions/lmstudio/src/runtime.test.ts +357 -0
  624. package/extensions/lmstudio/src/runtime.ts +276 -0
  625. package/extensions/lmstudio/src/setup.test.ts +1543 -0
  626. package/extensions/lmstudio/src/setup.ts +878 -0
  627. package/extensions/lmstudio/src/stream.test.ts +658 -0
  628. package/extensions/lmstudio/src/stream.ts +493 -0
  629. package/extensions/media-understanding-core/image-ops.ts +137 -0
  630. package/extensions/media-understanding-core/package.json +14 -0
  631. package/extensions/media-understanding-core/runtime-api.ts +9 -0
  632. package/extensions/media-understanding-core/src/runtime.ts +9 -0
  633. package/extensions/media-understanding-core/tsconfig.json +16 -0
  634. package/extensions/microsoft/index.ts +11 -0
  635. package/extensions/microsoft/microsoft.live.test.ts +14 -0
  636. package/extensions/microsoft/openclaw.plugin.json +15 -0
  637. package/extensions/microsoft/package.json +18 -0
  638. package/extensions/microsoft/speech-provider.test.ts +298 -0
  639. package/extensions/microsoft/speech-provider.ts +295 -0
  640. package/extensions/microsoft/test-api.ts +1 -0
  641. package/extensions/microsoft/tsconfig.json +16 -0
  642. package/extensions/microsoft/tts.test.ts +193 -0
  643. package/extensions/microsoft/tts.ts +137 -0
  644. package/extensions/minimax/README.md +37 -0
  645. package/extensions/minimax/api.ts +27 -0
  646. package/extensions/minimax/image-generation-provider.test.ts +313 -0
  647. package/extensions/minimax/image-generation-provider.ts +216 -0
  648. package/extensions/minimax/index.test.ts +408 -0
  649. package/extensions/minimax/index.ts +39 -0
  650. package/extensions/minimax/media-understanding-provider.ts +23 -0
  651. package/extensions/minimax/minimax.live.test.ts +115 -0
  652. package/extensions/minimax/model-definitions.test.ts +101 -0
  653. package/extensions/minimax/model-definitions.ts +91 -0
  654. package/extensions/minimax/music-generation-provider.test.ts +198 -0
  655. package/extensions/minimax/music-generation-provider.ts +259 -0
  656. package/extensions/minimax/oauth.runtime.ts +1 -0
  657. package/extensions/minimax/oauth.ts +233 -0
  658. package/extensions/minimax/onboard.test.ts +126 -0
  659. package/extensions/minimax/onboard.ts +104 -0
  660. package/extensions/minimax/openclaw.plugin.json +133 -0
  661. package/extensions/minimax/package.json +15 -0
  662. package/extensions/minimax/plugin-registration.contract.test.ts +15 -0
  663. package/extensions/minimax/provider-catalog.ts +86 -0
  664. package/extensions/minimax/provider-contract-api.ts +84 -0
  665. package/extensions/minimax/provider-discovery.contract.test.ts +3 -0
  666. package/extensions/minimax/provider-http.test-helpers.ts +142 -0
  667. package/extensions/minimax/provider-models.ts +21 -0
  668. package/extensions/minimax/provider-registration.ts +285 -0
  669. package/extensions/minimax/speech-provider.test.ts +576 -0
  670. package/extensions/minimax/speech-provider.ts +312 -0
  671. package/extensions/minimax/src/minimax-web-search-provider.runtime.ts +270 -0
  672. package/extensions/minimax/src/minimax-web-search-provider.test.ts +177 -0
  673. package/extensions/minimax/src/minimax-web-search-provider.ts +64 -0
  674. package/extensions/minimax/test-api.ts +11 -0
  675. package/extensions/minimax/tsconfig.json +16 -0
  676. package/extensions/minimax/tts.ts +116 -0
  677. package/extensions/minimax/video-generation-provider.test.ts +214 -0
  678. package/extensions/minimax/video-generation-provider.ts +456 -0
  679. package/extensions/minimax/web-search-contract-api.ts +35 -0
  680. package/extensions/minimax/web-search-provider.ts +1 -0
  681. package/extensions/mistral/api.test.ts +195 -0
  682. package/extensions/mistral/api.ts +81 -0
  683. package/extensions/mistral/embedding-provider.ts +52 -0
  684. package/extensions/mistral/index.ts +61 -0
  685. package/extensions/mistral/media-understanding-provider.test.ts +46 -0
  686. package/extensions/mistral/media-understanding-provider.ts +21 -0
  687. package/extensions/mistral/memory-embedding-adapter.ts +35 -0
  688. package/extensions/mistral/mistral.live.test.ts +62 -0
  689. package/extensions/mistral/model-definitions.test.ts +65 -0
  690. package/extensions/mistral/model-definitions.ts +37 -0
  691. package/extensions/mistral/onboard.test.ts +54 -0
  692. package/extensions/mistral/onboard.ts +31 -0
  693. package/extensions/mistral/openclaw.plugin.json +180 -0
  694. package/extensions/mistral/package.json +15 -0
  695. package/extensions/mistral/provider-catalog.ts +10 -0
  696. package/extensions/mistral/provider-compat.ts +62 -0
  697. package/extensions/mistral/realtime-transcription-provider.test.ts +61 -0
  698. package/extensions/mistral/realtime-transcription-provider.ts +280 -0
  699. package/extensions/mistral/test-api.ts +2 -0
  700. package/extensions/mistral/tsconfig.json +16 -0
  701. package/extensions/moonshot/api.ts +9 -0
  702. package/extensions/moonshot/index.test.ts +73 -0
  703. package/extensions/moonshot/index.ts +81 -0
  704. package/extensions/moonshot/media-understanding-provider.test.ts +92 -0
  705. package/extensions/moonshot/media-understanding-provider.ts +85 -0
  706. package/extensions/moonshot/moonshot.live.test.ts +56 -0
  707. package/extensions/moonshot/onboard.ts +38 -0
  708. package/extensions/moonshot/openclaw.plugin.json +209 -0
  709. package/extensions/moonshot/package.json +15 -0
  710. package/extensions/moonshot/provider-catalog.test.ts +84 -0
  711. package/extensions/moonshot/provider-catalog.ts +34 -0
  712. package/extensions/moonshot/provider-contract-api.ts +33 -0
  713. package/extensions/moonshot/provider-discovery.ts +17 -0
  714. package/extensions/moonshot/src/kimi-web-search-provider.runtime.ts +513 -0
  715. package/extensions/moonshot/src/kimi-web-search-provider.test.ts +297 -0
  716. package/extensions/moonshot/src/kimi-web-search-provider.ts +71 -0
  717. package/extensions/moonshot/test-api.ts +2 -0
  718. package/extensions/moonshot/tsconfig.json +16 -0
  719. package/extensions/moonshot/web-search-contract-api.ts +28 -0
  720. package/extensions/moonshot/web-search-provider.ts +1 -0
  721. package/extensions/nvidia/api.ts +6 -0
  722. package/extensions/nvidia/index.test.ts +180 -0
  723. package/extensions/nvidia/index.ts +64 -0
  724. package/extensions/nvidia/onboard.test.ts +49 -0
  725. package/extensions/nvidia/onboard.ts +30 -0
  726. package/extensions/nvidia/openclaw.plugin.json +122 -0
  727. package/extensions/nvidia/package.json +15 -0
  728. package/extensions/nvidia/plugin-registration.contract.test.ts +14 -0
  729. package/extensions/nvidia/provider-catalog.test.ts +21 -0
  730. package/extensions/nvidia/provider-catalog.ts +15 -0
  731. package/extensions/nvidia/tsconfig.json +16 -0
  732. package/extensions/ollama/README.md +3 -0
  733. package/extensions/ollama/api.ts +34 -0
  734. package/extensions/ollama/index.test.ts +979 -0
  735. package/extensions/ollama/index.ts +336 -0
  736. package/extensions/ollama/ollama.live.test.ts +287 -0
  737. package/extensions/ollama/openclaw.plugin.json +67 -0
  738. package/extensions/ollama/package.json +19 -0
  739. package/extensions/ollama/plugin-registration.contract.test.ts +7 -0
  740. package/extensions/ollama/provider-discovery.import-guard.test.ts +29 -0
  741. package/extensions/ollama/provider-discovery.test.ts +657 -0
  742. package/extensions/ollama/provider-discovery.ts +69 -0
  743. package/extensions/ollama/provider-policy-api.test.ts +72 -0
  744. package/extensions/ollama/provider-policy-api.ts +59 -0
  745. package/extensions/ollama/runtime-api.ts +22 -0
  746. package/extensions/ollama/src/defaults.ts +14 -0
  747. package/extensions/ollama/src/discovery-shared.test.ts +41 -0
  748. package/extensions/ollama/src/discovery-shared.ts +322 -0
  749. package/extensions/ollama/src/embedding-provider.test.ts +557 -0
  750. package/extensions/ollama/src/embedding-provider.ts +393 -0
  751. package/extensions/ollama/src/media-understanding-provider.ts +18 -0
  752. package/extensions/ollama/src/memory-embedding-adapter.ts +30 -0
  753. package/extensions/ollama/src/model-id.ts +24 -0
  754. package/extensions/ollama/src/ollama-json.ts +143 -0
  755. package/extensions/ollama/src/provider-base-url.test.ts +44 -0
  756. package/extensions/ollama/src/provider-base-url.ts +23 -0
  757. package/extensions/ollama/src/provider-models.ssrf.test.ts +41 -0
  758. package/extensions/ollama/src/provider-models.test.ts +312 -0
  759. package/extensions/ollama/src/provider-models.ts +327 -0
  760. package/extensions/ollama/src/setup.test.ts +771 -0
  761. package/extensions/ollama/src/setup.ts +743 -0
  762. package/extensions/ollama/src/stream-runtime.test.ts +2218 -0
  763. package/extensions/ollama/src/stream.test.ts +252 -0
  764. package/extensions/ollama/src/stream.ts +1347 -0
  765. package/extensions/ollama/src/web-search-provider.test.ts +488 -0
  766. package/extensions/ollama/src/web-search-provider.ts +350 -0
  767. package/extensions/ollama/src/wsl2-crash-loop-check.test.ts +157 -0
  768. package/extensions/ollama/src/wsl2-crash-loop-check.ts +84 -0
  769. package/extensions/ollama/tsconfig.json +16 -0
  770. package/extensions/ollama/web-search-contract-api.ts +26 -0
  771. package/extensions/ollama/web-search-provider.ts +1 -0
  772. package/extensions/openai/api.ts +16 -0
  773. package/extensions/openai/auth-choice-copy.ts +33 -0
  774. package/extensions/openai/base-url.test.ts +60 -0
  775. package/extensions/openai/base-url.ts +23 -0
  776. package/extensions/openai/default-models.test.ts +36 -0
  777. package/extensions/openai/default-models.ts +40 -0
  778. package/extensions/openai/embedding-batch.test.ts +10 -0
  779. package/extensions/openai/embedding-batch.ts +274 -0
  780. package/extensions/openai/embedding-provider.test.ts +102 -0
  781. package/extensions/openai/embedding-provider.ts +110 -0
  782. package/extensions/openai/image-generation-provider.test.ts +1624 -0
  783. package/extensions/openai/image-generation-provider.ts +903 -0
  784. package/extensions/openai/index.test.ts +630 -0
  785. package/extensions/openai/index.ts +58 -0
  786. package/extensions/openai/media-understanding-provider.test.ts +119 -0
  787. package/extensions/openai/media-understanding-provider.ts +51 -0
  788. package/extensions/openai/memory-embedding-adapter.test.ts +82 -0
  789. package/extensions/openai/memory-embedding-adapter.ts +68 -0
  790. package/extensions/openai/native-web-search.ts +103 -0
  791. package/extensions/openai/openai-codex-auth-identity.test.ts +77 -0
  792. package/extensions/openai/openai-codex-auth-identity.ts +100 -0
  793. package/extensions/openai/openai-codex-catalog.ts +12 -0
  794. package/extensions/openai/openai-codex-device-code.test.ts +248 -0
  795. package/extensions/openai/openai-codex-device-code.ts +309 -0
  796. package/extensions/openai/openai-codex-oauth.runtime.ts +348 -0
  797. package/extensions/openai/openai-codex-provider.runtime.ts +45 -0
  798. package/extensions/openai/openai-codex-provider.test.ts +883 -0
  799. package/extensions/openai/openai-codex-provider.ts +636 -0
  800. package/extensions/openai/openai-codex-shared.ts +3 -0
  801. package/extensions/openai/openai-provider.live.test.ts +196 -0
  802. package/extensions/openai/openai-provider.test.ts +929 -0
  803. package/extensions/openai/openai-provider.ts +325 -0
  804. package/extensions/openai/openai-tts.live.test.ts +44 -0
  805. package/extensions/openai/openai.live.test.ts +493 -0
  806. package/extensions/openai/openclaw.plugin.json +897 -0
  807. package/extensions/openai/openclaw.plugin.test.ts +181 -0
  808. package/extensions/openai/package.json +19 -0
  809. package/extensions/openai/plugin-registration.contract.test.ts +9 -0
  810. package/extensions/openai/prompt-overlay.ts +51 -0
  811. package/extensions/openai/provider-auth.contract.test.ts +12 -0
  812. package/extensions/openai/provider-catalog.contract.test.ts +3 -0
  813. package/extensions/openai/provider-contract-api.ts +83 -0
  814. package/extensions/openai/provider-policy-api.ts +20 -0
  815. package/extensions/openai/provider-runtime.contract.test.ts +3 -0
  816. package/extensions/openai/realtime-provider-shared.ts +168 -0
  817. package/extensions/openai/realtime-transcription-provider.test.ts +356 -0
  818. package/extensions/openai/realtime-transcription-provider.ts +307 -0
  819. package/extensions/openai/realtime-voice-provider.test.ts +1924 -0
  820. package/extensions/openai/realtime-voice-provider.ts +1315 -0
  821. package/extensions/openai/register.runtime.ts +15 -0
  822. package/extensions/openai/replay-policy.ts +32 -0
  823. package/extensions/openai/setup-api.test.ts +29 -0
  824. package/extensions/openai/setup-api.ts +166 -0
  825. package/extensions/openai/shared.ts +131 -0
  826. package/extensions/openai/speech-provider.test.ts +324 -0
  827. package/extensions/openai/speech-provider.ts +347 -0
  828. package/extensions/openai/test-api.ts +9 -0
  829. package/extensions/openai/test-support/provider-catalog.contract-test-support.ts +134 -0
  830. package/extensions/openai/thinking-policy.ts +55 -0
  831. package/extensions/openai/transport-policy.test.ts +128 -0
  832. package/extensions/openai/transport-policy.ts +111 -0
  833. package/extensions/openai/tsconfig.json +16 -0
  834. package/extensions/openai/tts.test.ts +444 -0
  835. package/extensions/openai/tts.ts +184 -0
  836. package/extensions/openai/video-generation-provider.test.ts +254 -0
  837. package/extensions/openai/video-generation-provider.ts +382 -0
  838. package/extensions/opencode/api.ts +9 -0
  839. package/extensions/opencode/index.test.ts +84 -0
  840. package/extensions/opencode/index.ts +74 -0
  841. package/extensions/opencode/media-understanding-provider.test.ts +44 -0
  842. package/extensions/opencode/media-understanding-provider.ts +42 -0
  843. package/extensions/opencode/onboard.test.ts +25 -0
  844. package/extensions/opencode/onboard.ts +29 -0
  845. package/extensions/opencode/openclaw.plugin.json +55 -0
  846. package/extensions/opencode/package.json +15 -0
  847. package/extensions/opencode/plugin-registration.contract.test.ts +8 -0
  848. package/extensions/opencode/provider-policy-api.test.ts +44 -0
  849. package/extensions/opencode/provider-policy-api.ts +5 -0
  850. package/extensions/opencode/tsconfig.json +16 -0
  851. package/extensions/opencode-go/api.ts +27 -0
  852. package/extensions/opencode-go/index.test.ts +305 -0
  853. package/extensions/opencode-go/index.ts +101 -0
  854. package/extensions/opencode-go/media-understanding-provider.test.ts +12 -0
  855. package/extensions/opencode-go/media-understanding-provider.ts +15 -0
  856. package/extensions/opencode-go/onboard.test.ts +28 -0
  857. package/extensions/opencode-go/onboard.ts +17 -0
  858. package/extensions/opencode-go/openclaw.plugin.json +106 -0
  859. package/extensions/opencode-go/package.json +15 -0
  860. package/extensions/opencode-go/plugin-registration.contract.test.ts +8 -0
  861. package/extensions/opencode-go/provider-catalog.ts +135 -0
  862. package/extensions/opencode-go/stream.ts +51 -0
  863. package/extensions/opencode-go/tsconfig.json +16 -0
  864. package/extensions/openrouter/api.ts +12 -0
  865. package/extensions/openrouter/image-generation-provider.test.ts +361 -0
  866. package/extensions/openrouter/image-generation-provider.ts +345 -0
  867. package/extensions/openrouter/index.test.ts +650 -0
  868. package/extensions/openrouter/index.ts +184 -0
  869. package/extensions/openrouter/media-understanding-provider.test.ts +260 -0
  870. package/extensions/openrouter/media-understanding-provider.ts +176 -0
  871. package/extensions/openrouter/models.ts +18 -0
  872. package/extensions/openrouter/music-generation-provider.test.ts +226 -0
  873. package/extensions/openrouter/music-generation-provider.ts +344 -0
  874. package/extensions/openrouter/onboard.test.ts +27 -0
  875. package/extensions/openrouter/onboard.ts +32 -0
  876. package/extensions/openrouter/openclaw.plugin.json +81 -0
  877. package/extensions/openrouter/openrouter.live.test.ts +118 -0
  878. package/extensions/openrouter/package.json +15 -0
  879. package/extensions/openrouter/provider-catalog.ts +88 -0
  880. package/extensions/openrouter/provider-contract-api.ts +27 -0
  881. package/extensions/openrouter/provider-policy-api.ts +5 -0
  882. package/extensions/openrouter/provider-routing.ts +87 -0
  883. package/extensions/openrouter/provider-runtime.contract.test.ts +3 -0
  884. package/extensions/openrouter/speech-provider.test.ts +218 -0
  885. package/extensions/openrouter/speech-provider.ts +46 -0
  886. package/extensions/openrouter/stream.ts +247 -0
  887. package/extensions/openrouter/test-api.ts +4 -0
  888. package/extensions/openrouter/thinking-policy.ts +34 -0
  889. package/extensions/openrouter/tsconfig.json +16 -0
  890. package/extensions/openrouter/video-generation-provider.test.ts +722 -0
  891. package/extensions/openrouter/video-generation-provider.ts +530 -0
  892. package/extensions/openrouter/video-http.ts +48 -0
  893. package/extensions/openrouter/video-model-catalog.ts +299 -0
  894. package/extensions/openshell/index.ts +28 -0
  895. package/extensions/openshell/npm-shrinkwrap.json +24 -0
  896. package/extensions/openshell/openclaw.plugin.json +118 -0
  897. package/extensions/openshell/package.json +37 -0
  898. package/extensions/openshell/src/backend.e2e.test.ts +595 -0
  899. package/extensions/openshell/src/backend.test.ts +40 -0
  900. package/extensions/openshell/src/backend.ts +512 -0
  901. package/extensions/openshell/src/backend.types.ts +11 -0
  902. package/extensions/openshell/src/cli.ts +85 -0
  903. package/extensions/openshell/src/config.test.ts +80 -0
  904. package/extensions/openshell/src/config.ts +194 -0
  905. package/extensions/openshell/src/fs-bridge.ts +370 -0
  906. package/extensions/openshell/src/mirror.test.ts +194 -0
  907. package/extensions/openshell/src/mirror.ts +141 -0
  908. package/extensions/openshell/src/openshell-core.test.ts +529 -0
  909. package/extensions/openshell/tsconfig.json +16 -0
  910. package/extensions/perplexity/index.ts +11 -0
  911. package/extensions/perplexity/openclaw.plugin.json +52 -0
  912. package/extensions/perplexity/package.json +15 -0
  913. package/extensions/perplexity/src/perplexity-web-search-provider.runtime.ts +551 -0
  914. package/extensions/perplexity/src/perplexity-web-search-provider.shared.ts +124 -0
  915. package/extensions/perplexity/src/perplexity-web-search-provider.test.ts +151 -0
  916. package/extensions/perplexity/src/perplexity-web-search-provider.ts +127 -0
  917. package/extensions/perplexity/test-api.ts +1 -0
  918. package/extensions/perplexity/tsconfig.json +16 -0
  919. package/extensions/perplexity/web-search-contract-api.ts +13 -0
  920. package/extensions/perplexity/web-search-provider.ts +1 -0
  921. package/extensions/qianfan/api.ts +6 -0
  922. package/extensions/qianfan/index.test.ts +133 -0
  923. package/extensions/qianfan/index.ts +31 -0
  924. package/extensions/qianfan/onboard.ts +61 -0
  925. package/extensions/qianfan/openclaw.plugin.json +78 -0
  926. package/extensions/qianfan/package.json +15 -0
  927. package/extensions/qianfan/provider-catalog.ts +13 -0
  928. package/extensions/qianfan/tsconfig.json +16 -0
  929. package/extensions/qwen/api.ts +34 -0
  930. package/extensions/qwen/index.test.ts +31 -0
  931. package/extensions/qwen/index.ts +181 -0
  932. package/extensions/qwen/media-understanding-provider.test.ts +76 -0
  933. package/extensions/qwen/media-understanding-provider.ts +88 -0
  934. package/extensions/qwen/model-definitions.ts +20 -0
  935. package/extensions/qwen/models.ts +202 -0
  936. package/extensions/qwen/onboard.ts +73 -0
  937. package/extensions/qwen/openclaw.plugin.json +143 -0
  938. package/extensions/qwen/package.json +15 -0
  939. package/extensions/qwen/plugin-registration.contract.test.ts +10 -0
  940. package/extensions/qwen/provider-catalog.test.ts +62 -0
  941. package/extensions/qwen/provider-catalog.ts +13 -0
  942. package/extensions/qwen/provider-discovery.contract.test.ts +3 -0
  943. package/extensions/qwen/stream.test.ts +171 -0
  944. package/extensions/qwen/stream.ts +87 -0
  945. package/extensions/qwen/test-api.ts +2 -0
  946. package/extensions/qwen/tsconfig.json +16 -0
  947. package/extensions/qwen/video-generation-provider.test.ts +155 -0
  948. package/extensions/qwen/video-generation-provider.ts +111 -0
  949. package/extensions/runway/index.ts +11 -0
  950. package/extensions/runway/openclaw.plugin.json +34 -0
  951. package/extensions/runway/package.json +15 -0
  952. package/extensions/runway/plugin-registration.contract.test.ts +7 -0
  953. package/extensions/runway/tsconfig.json +16 -0
  954. package/extensions/runway/video-generation-provider.test.ts +248 -0
  955. package/extensions/runway/video-generation-provider.ts +462 -0
  956. package/extensions/senseaudio/index.ts +11 -0
  957. package/extensions/senseaudio/media-understanding-provider.test.ts +136 -0
  958. package/extensions/senseaudio/media-understanding-provider.ts +25 -0
  959. package/extensions/senseaudio/openclaw.plugin.json +18 -0
  960. package/extensions/senseaudio/package.json +15 -0
  961. package/extensions/senseaudio/test-api.ts +1 -0
  962. package/extensions/sglang/README.md +3 -0
  963. package/extensions/sglang/api.ts +7 -0
  964. package/extensions/sglang/defaults.ts +4 -0
  965. package/extensions/sglang/index.test.ts +34 -0
  966. package/extensions/sglang/index.ts +95 -0
  967. package/extensions/sglang/models.ts +23 -0
  968. package/extensions/sglang/openclaw.plugin.json +45 -0
  969. package/extensions/sglang/package.json +15 -0
  970. package/extensions/sglang/provider-discovery.contract.test.ts +7 -0
  971. package/extensions/sglang/tsconfig.json +16 -0
  972. package/extensions/skill-workshop/api.ts +3 -0
  973. package/extensions/skill-workshop/index.test.ts +990 -0
  974. package/extensions/skill-workshop/index.ts +170 -0
  975. package/extensions/skill-workshop/openclaw.plugin.json +83 -0
  976. package/extensions/skill-workshop/package.json +18 -0
  977. package/extensions/skill-workshop/src/config.ts +50 -0
  978. package/extensions/skill-workshop/src/prompt.ts +18 -0
  979. package/extensions/skill-workshop/src/reviewer.ts +290 -0
  980. package/extensions/skill-workshop/src/scanner.ts +69 -0
  981. package/extensions/skill-workshop/src/signals.ts +95 -0
  982. package/extensions/skill-workshop/src/skills.ts +186 -0
  983. package/extensions/skill-workshop/src/store.ts +184 -0
  984. package/extensions/skill-workshop/src/text.ts +59 -0
  985. package/extensions/skill-workshop/src/tool.ts +200 -0
  986. package/extensions/skill-workshop/src/types.ts +42 -0
  987. package/extensions/skill-workshop/src/workshop.ts +85 -0
  988. package/extensions/speech-core/api.ts +54 -0
  989. package/extensions/speech-core/package.json +10 -0
  990. package/extensions/speech-core/runtime-api.ts +42 -0
  991. package/extensions/speech-core/src/tts.test.ts +1025 -0
  992. package/extensions/speech-core/src/tts.ts +1929 -0
  993. package/extensions/speech-core/tsconfig.json +16 -0
  994. package/extensions/stepfun/index.ts +252 -0
  995. package/extensions/stepfun/onboard.ts +73 -0
  996. package/extensions/stepfun/openclaw.plugin.json +148 -0
  997. package/extensions/stepfun/package.json +15 -0
  998. package/extensions/stepfun/provider-catalog.ts +40 -0
  999. package/extensions/stepfun/tsconfig.json +16 -0
  1000. package/extensions/tencent/api.ts +7 -0
  1001. package/extensions/tencent/index.ts +64 -0
  1002. package/extensions/tencent/models.ts +25 -0
  1003. package/extensions/tencent/onboard.ts +38 -0
  1004. package/extensions/tencent/openclaw.plugin.json +86 -0
  1005. package/extensions/tencent/package.json +15 -0
  1006. package/extensions/tencent/provider-catalog.ts +14 -0
  1007. package/extensions/tencent/provider-discovery.ts +17 -0
  1008. package/extensions/tencent/tsconfig.json +16 -0
  1009. package/extensions/together/api.ts +7 -0
  1010. package/extensions/together/index.ts +42 -0
  1011. package/extensions/together/models.ts +23 -0
  1012. package/extensions/together/onboard.ts +26 -0
  1013. package/extensions/together/openclaw.plugin.json +160 -0
  1014. package/extensions/together/package.json +15 -0
  1015. package/extensions/together/plugin-registration.contract.test.ts +8 -0
  1016. package/extensions/together/provider-catalog.ts +10 -0
  1017. package/extensions/together/tsconfig.json +16 -0
  1018. package/extensions/together/video-generation-provider.test.ts +130 -0
  1019. package/extensions/together/video-generation-provider.ts +281 -0
  1020. package/extensions/tts-local-cli/index.ts +11 -0
  1021. package/extensions/tts-local-cli/openclaw.plugin.json +15 -0
  1022. package/extensions/tts-local-cli/package.json +15 -0
  1023. package/extensions/tts-local-cli/speech-provider.test.ts +307 -0
  1024. package/extensions/tts-local-cli/speech-provider.ts +455 -0
  1025. package/extensions/venice/api.ts +8 -0
  1026. package/extensions/venice/index.test.ts +109 -0
  1027. package/extensions/venice/index.ts +70 -0
  1028. package/extensions/venice/models.test.ts +291 -0
  1029. package/extensions/venice/models.ts +302 -0
  1030. package/extensions/venice/onboard.ts +27 -0
  1031. package/extensions/venice/openclaw.plugin.json +504 -0
  1032. package/extensions/venice/package.json +15 -0
  1033. package/extensions/venice/provider-catalog.ts +11 -0
  1034. package/extensions/venice/provider-runtime.contract.test.ts +3 -0
  1035. package/extensions/venice/stream.ts +37 -0
  1036. package/extensions/venice/tsconfig.json +16 -0
  1037. package/extensions/vercel-ai-gateway/api.ts +12 -0
  1038. package/extensions/vercel-ai-gateway/index.ts +41 -0
  1039. package/extensions/vercel-ai-gateway/models.ts +226 -0
  1040. package/extensions/vercel-ai-gateway/onboard.ts +32 -0
  1041. package/extensions/vercel-ai-gateway/openclaw.plugin.json +61 -0
  1042. package/extensions/vercel-ai-gateway/package.json +15 -0
  1043. package/extensions/vercel-ai-gateway/provider-catalog.test.ts +96 -0
  1044. package/extensions/vercel-ai-gateway/provider-catalog.ts +22 -0
  1045. package/extensions/vercel-ai-gateway/thinking.test.ts +100 -0
  1046. package/extensions/vercel-ai-gateway/thinking.ts +77 -0
  1047. package/extensions/vercel-ai-gateway/tsconfig.json +16 -0
  1048. package/extensions/vllm/README.md +3 -0
  1049. package/extensions/vllm/api.ts +8 -0
  1050. package/extensions/vllm/defaults.ts +4 -0
  1051. package/extensions/vllm/index.ts +96 -0
  1052. package/extensions/vllm/models.ts +23 -0
  1053. package/extensions/vllm/openclaw.plugin.json +45 -0
  1054. package/extensions/vllm/package.json +15 -0
  1055. package/extensions/vllm/provider-discovery.contract.test.ts +7 -0
  1056. package/extensions/vllm/register.runtime.ts +7 -0
  1057. package/extensions/vllm/stream.test.ts +282 -0
  1058. package/extensions/vllm/stream.ts +164 -0
  1059. package/extensions/vllm/tsconfig.json +16 -0
  1060. package/extensions/volcengine/api.ts +56 -0
  1061. package/extensions/volcengine/index.test.ts +92 -0
  1062. package/extensions/volcengine/index.ts +87 -0
  1063. package/extensions/volcengine/models.ts +28 -0
  1064. package/extensions/volcengine/openclaw.plugin.json +221 -0
  1065. package/extensions/volcengine/package.json +15 -0
  1066. package/extensions/volcengine/provider-catalog.ts +17 -0
  1067. package/extensions/volcengine/provider-discovery.ts +31 -0
  1068. package/extensions/volcengine/speech-provider.ts +229 -0
  1069. package/extensions/volcengine/tsconfig.json +16 -0
  1070. package/extensions/volcengine/tts.live.test.ts +30 -0
  1071. package/extensions/volcengine/tts.test.ts +279 -0
  1072. package/extensions/volcengine/tts.ts +266 -0
  1073. package/extensions/voyage/embedding-batch.ts +315 -0
  1074. package/extensions/voyage/embedding-provider.ts +90 -0
  1075. package/extensions/voyage/index.ts +11 -0
  1076. package/extensions/voyage/memory-embedding-adapter.ts +56 -0
  1077. package/extensions/voyage/openclaw.plugin.json +18 -0
  1078. package/extensions/voyage/package.json +15 -0
  1079. package/extensions/xai/.boundary-stubs/anthropic-vertex-api.d.ts +2 -0
  1080. package/extensions/xai/.boundary-stubs/ollama-api.d.ts +1 -0
  1081. package/extensions/xai/.boundary-stubs/ollama-runtime-api.d.ts +16 -0
  1082. package/extensions/xai/.boundary-stubs/speech-core-runtime-api.d.ts +33 -0
  1083. package/extensions/xai/api.test.ts +51 -0
  1084. package/extensions/xai/api.ts +119 -0
  1085. package/extensions/xai/code-execution.test.ts +262 -0
  1086. package/extensions/xai/code-execution.ts +146 -0
  1087. package/extensions/xai/image-generation-provider.test.ts +293 -0
  1088. package/extensions/xai/image-generation-provider.ts +124 -0
  1089. package/extensions/xai/index.test.ts +263 -0
  1090. package/extensions/xai/index.ts +233 -0
  1091. package/extensions/xai/model-compat.ts +34 -0
  1092. package/extensions/xai/model-definitions.ts +346 -0
  1093. package/extensions/xai/model-id.test.ts +32 -0
  1094. package/extensions/xai/model-id.ts +24 -0
  1095. package/extensions/xai/onboard.test.ts +91 -0
  1096. package/extensions/xai/onboard.ts +56 -0
  1097. package/extensions/xai/openclaw.plugin.json +274 -0
  1098. package/extensions/xai/package.json +20 -0
  1099. package/extensions/xai/plugin-registration.contract.test.ts +11 -0
  1100. package/extensions/xai/provider-catalog.ts +12 -0
  1101. package/extensions/xai/provider-contract-api.ts +22 -0
  1102. package/extensions/xai/provider-discovery.ts +27 -0
  1103. package/extensions/xai/provider-models.ts +45 -0
  1104. package/extensions/xai/provider-policy-api.test.ts +37 -0
  1105. package/extensions/xai/provider-policy-api.ts +18 -0
  1106. package/extensions/xai/realtime-transcription-provider.test.ts +273 -0
  1107. package/extensions/xai/realtime-transcription-provider.ts +306 -0
  1108. package/extensions/xai/runtime-model-compat.test.ts +60 -0
  1109. package/extensions/xai/runtime-model-compat.ts +72 -0
  1110. package/extensions/xai/setup-api.ts +22 -0
  1111. package/extensions/xai/speech-provider.test.ts +184 -0
  1112. package/extensions/xai/speech-provider.ts +275 -0
  1113. package/extensions/xai/src/code-execution-shared.ts +110 -0
  1114. package/extensions/xai/src/responses-tool-shared.test.ts +107 -0
  1115. package/extensions/xai/src/responses-tool-shared.ts +163 -0
  1116. package/extensions/xai/src/tool-auth-shared.test.ts +326 -0
  1117. package/extensions/xai/src/tool-auth-shared.ts +219 -0
  1118. package/extensions/xai/src/tool-config-shared.test.ts +36 -0
  1119. package/extensions/xai/src/tool-config-shared.ts +32 -0
  1120. package/extensions/xai/src/web-search-provider.runtime.ts +429 -0
  1121. package/extensions/xai/src/web-search-response.types.ts +25 -0
  1122. package/extensions/xai/src/web-search-shared.ts +124 -0
  1123. package/extensions/xai/src/x-search-config.ts +78 -0
  1124. package/extensions/xai/src/x-search-shared.ts +146 -0
  1125. package/extensions/xai/src/xai-user-agent.test.ts +59 -0
  1126. package/extensions/xai/src/xai-user-agent.ts +52 -0
  1127. package/extensions/xai/stream.test.ts +410 -0
  1128. package/extensions/xai/stream.ts +359 -0
  1129. package/extensions/xai/stt.test.ts +106 -0
  1130. package/extensions/xai/stt.ts +91 -0
  1131. package/extensions/xai/test-api.ts +1 -0
  1132. package/extensions/xai/test-helpers.ts +73 -0
  1133. package/extensions/xai/tsconfig.json +64 -0
  1134. package/extensions/xai/tts.test.ts +125 -0
  1135. package/extensions/xai/tts.ts +97 -0
  1136. package/extensions/xai/video-generation-provider.test.ts +443 -0
  1137. package/extensions/xai/video-generation-provider.ts +499 -0
  1138. package/extensions/xai/web-search-contract-api.ts +29 -0
  1139. package/extensions/xai/web-search.test.ts +1242 -0
  1140. package/extensions/xai/web-search.ts +68 -0
  1141. package/extensions/xai/x-search-tool-shared.ts +48 -0
  1142. package/extensions/xai/x-search.live.test.ts +76 -0
  1143. package/extensions/xai/x-search.test.ts +484 -0
  1144. package/extensions/xai/x-search.ts +230 -0
  1145. package/extensions/xai/xai-oauth.test.ts +387 -0
  1146. package/extensions/xai/xai-oauth.ts +752 -0
  1147. package/extensions/xai/xai.live.test.ts +323 -0
  1148. package/launcher.js +97 -0
  1149. package/logger.js +87 -0
  1150. package/package.json +21 -0
  1151. package/server.js +1800 -0
  1152. package/skills/ai-error-prevention/SKILL.md +105 -0
  1153. package/skills/api-design/SKILL.md +523 -0
  1154. package/skills/architecture-decision-records/SKILL.md +179 -0
  1155. package/skills/autonomous-loops/SKILL.md +610 -0
  1156. package/skills/backend-patterns/SKILL.md +598 -0
  1157. package/skills/codebase-onboarding/SKILL.md +233 -0
  1158. package/skills/coding-standards/SKILL.md +530 -0
  1159. package/skills/database-migrations/SKILL.md +429 -0
  1160. package/skills/deep-research/SKILL.md +155 -0
  1161. package/skills/error-prevention/SKILL.md +61 -0
  1162. package/skills/exa-search/SKILL.md +103 -0
  1163. package/skills/frontend-slides/SKILL.md +184 -0
  1164. package/skills/frontend-slides/STYLE_PRESETS.md +330 -0
  1165. package/skills/git-workflow/SKILL.md +715 -0
  1166. package/skills/iterative-retrieval/SKILL.md +211 -0
  1167. package/skills/php-security/SKILL.md +70 -0
  1168. package/skills/php-security/rules/thinkphp-security.rules +23 -0
  1169. package/skills/requirement-ears/SKILL.md +31 -0
  1170. package/skills/rules-distill/SKILL.md +264 -0
  1171. package/skills/rules-distill/scripts/scan-rules.sh +58 -0
  1172. package/skills/rules-distill/scripts/scan-skills.sh +129 -0
  1173. package/skills/search-first/SKILL.md +161 -0
  1174. package/skills/security-review/SKILL.md +495 -0
  1175. package/skills/security-review/cloud-infrastructure-security.md +361 -0
  1176. package/skills/security-scan/SKILL.md +68 -0
  1177. package/skills/security-scan/scripts/scan-config.ps1 +31 -0
  1178. package/skills/security-scan/scripts/scan-sqli.ps1 +21 -0
  1179. package/skills/skill-stocktake/SKILL.md +193 -0
  1180. package/skills/skill-stocktake/scripts/quick-diff.sh +87 -0
  1181. package/skills/skill-stocktake/scripts/save-results.sh +56 -0
  1182. package/skills/skill-stocktake/scripts/scan.sh +170 -0
  1183. package/skills/strategic-compact/SKILL.md +131 -0
  1184. package/skills/strategic-compact/suggest-compact.sh +54 -0
  1185. package/skills/tdd-workflow/SKILL.md +90 -0
  1186. package/skills/tdd-workflow/examples/IntegrationTestExample.php +35 -0
  1187. package/skills/tdd-workflow/examples/UnitTestExample.php +39 -0
  1188. package/skills/ui-spec-guider/ui-spec-guider/SKILL.md +37 -0
  1189. package/skills/ui-spec-guider/ui-spec-guider.skill +0 -0
  1190. package/skills/verification-loop/SKILL.md +126 -0
  1191. package/start.bat +29 -0
@@ -0,0 +1,2885 @@
1
+ import fs from "node:fs/promises";
2
+ import os from "node:os";
3
+ import path from "node:path";
4
+ import { SessionManager } from "@earendil-works/pi-coding-agent";
5
+ import type { EmbeddedRunAttemptParams } from "openclaw/plugin-sdk/agent-harness";
6
+ import { resetAgentEventsForTest } from "openclaw/plugin-sdk/agent-harness-runtime";
7
+ import {
8
+ onInternalDiagnosticEvent,
9
+ resetDiagnosticEventsForTest,
10
+ type DiagnosticEventPayload,
11
+ } from "openclaw/plugin-sdk/diagnostic-runtime";
12
+ import {
13
+ initializeGlobalHookRunner,
14
+ resetGlobalHookRunner,
15
+ } from "openclaw/plugin-sdk/hook-runtime";
16
+ import { createMockPluginRegistry } from "openclaw/plugin-sdk/plugin-test-runtime";
17
+ import { afterEach, beforeEach, describe, expect, it, vi } from "vitest";
18
+ import {
19
+ CodexAppServerEventProjector,
20
+ type CodexAppServerEventProjectorOptions,
21
+ type CodexAppServerToolTelemetry,
22
+ } from "./event-projector.js";
23
+ import { rememberCodexRateLimits, resetCodexRateLimitCacheForTests } from "./rate-limit-cache.js";
24
+ import { createCodexTestModel } from "./test-support.js";
25
+
26
+ const THREAD_ID = "thread-1";
27
+ const TURN_ID = "turn-1";
28
+ const tempDirs = new Set<string>();
29
+
30
+ type ProjectorNotification = Parameters<CodexAppServerEventProjector["handleNotification"]>[0];
31
+
32
+ function flushDiagnosticEvents() {
33
+ return new Promise<void>((resolve) => setImmediate(resolve));
34
+ }
35
+
36
+ function assistantMessage(text: string, timestamp: number) {
37
+ return {
38
+ role: "assistant" as const,
39
+ content: [{ type: "text" as const, text }],
40
+ api: "openai-codex-responses",
41
+ provider: "openai-codex",
42
+ model: "gpt-5.4-codex",
43
+ usage: {
44
+ input: 0,
45
+ output: 0,
46
+ cacheRead: 0,
47
+ cacheWrite: 0,
48
+ totalTokens: 0,
49
+ cost: { input: 0, output: 0, cacheRead: 0, cacheWrite: 0, total: 0 },
50
+ },
51
+ stopReason: "stop" as const,
52
+ timestamp,
53
+ };
54
+ }
55
+
56
+ async function createParams(): Promise<EmbeddedRunAttemptParams> {
57
+ const tempDir = await fs.mkdtemp(path.join(os.tmpdir(), "openclaw-codex-projector-"));
58
+ tempDirs.add(tempDir);
59
+ const sessionFile = path.join(tempDir, "session.jsonl");
60
+ SessionManager.open(sessionFile).appendMessage(assistantMessage("history", Date.now()));
61
+ return {
62
+ prompt: "hello",
63
+ sessionId: "session-1",
64
+ sessionFile,
65
+ workspaceDir: tempDir,
66
+ runId: "run-1",
67
+ provider: "openai-codex",
68
+ modelId: "gpt-5.4-codex",
69
+ model: createCodexTestModel(),
70
+ thinkLevel: "medium",
71
+ } as EmbeddedRunAttemptParams;
72
+ }
73
+
74
+ async function createProjector(
75
+ params?: EmbeddedRunAttemptParams,
76
+ options?: CodexAppServerEventProjectorOptions,
77
+ ): Promise<CodexAppServerEventProjector> {
78
+ const resolvedParams = params ?? (await createParams());
79
+ return new CodexAppServerEventProjector(resolvedParams, THREAD_ID, TURN_ID, options);
80
+ }
81
+
82
+ async function createProjectorWithAssistantHooks() {
83
+ const onAssistantMessageStart = vi.fn();
84
+ const onPartialReply = vi.fn();
85
+ const params = await createParams();
86
+ const projector = await createProjector({
87
+ ...params,
88
+ onAssistantMessageStart,
89
+ onPartialReply,
90
+ });
91
+ return { onAssistantMessageStart, onPartialReply, projector };
92
+ }
93
+
94
+ beforeEach(() => {
95
+ resetAgentEventsForTest();
96
+ resetDiagnosticEventsForTest();
97
+ });
98
+
99
+ afterEach(async () => {
100
+ resetAgentEventsForTest();
101
+ resetDiagnosticEventsForTest();
102
+ resetGlobalHookRunner();
103
+ resetCodexRateLimitCacheForTests();
104
+ vi.restoreAllMocks();
105
+ for (const tempDir of tempDirs) {
106
+ await fs.rm(tempDir, { recursive: true, force: true });
107
+ }
108
+ tempDirs.clear();
109
+ });
110
+
111
+ async function createProjectorWithHooks() {
112
+ const beforeCompaction = vi.fn();
113
+ const afterCompaction = vi.fn();
114
+ initializeGlobalHookRunner(
115
+ createMockPluginRegistry([
116
+ { hookName: "before_compaction", handler: beforeCompaction },
117
+ { hookName: "after_compaction", handler: afterCompaction },
118
+ ]),
119
+ );
120
+ const projector = await createProjector();
121
+ return { projector, beforeCompaction, afterCompaction };
122
+ }
123
+
124
+ function buildEmptyToolTelemetry(): CodexAppServerToolTelemetry {
125
+ return {
126
+ didSendViaMessagingTool: false,
127
+ messagingToolSentTexts: [],
128
+ messagingToolSentMediaUrls: [],
129
+ messagingToolSentTargets: [],
130
+ };
131
+ }
132
+
133
+ function requireRecord(value: unknown, label: string): Record<string, unknown> {
134
+ if (!value || typeof value !== "object" || Array.isArray(value)) {
135
+ throw new Error(`Expected ${label}`);
136
+ }
137
+ return value as Record<string, unknown>;
138
+ }
139
+
140
+ function requireArray(value: unknown, label: string): unknown[] {
141
+ if (!Array.isArray(value)) {
142
+ throw new Error(`Expected ${label}`);
143
+ }
144
+ return value;
145
+ }
146
+
147
+ function expectUsageFields(
148
+ usage: unknown,
149
+ expected: { input: number; output: number; cacheRead: number; total: number },
150
+ ) {
151
+ const record = requireRecord(usage, "usage");
152
+ expect(record.input).toBe(expected.input);
153
+ expect(record.output).toBe(expected.output);
154
+ expect(record.cacheRead).toBe(expected.cacheRead);
155
+ expect(record.total ?? record.totalTokens).toBe(expected.total);
156
+ }
157
+
158
+ function mockCallArg(mock: unknown, callIndex: number, argIndex: number, label: string) {
159
+ const calls = (mock as { mock?: { calls?: unknown[][] } }).mock?.calls;
160
+ if (!Array.isArray(calls)) {
161
+ throw new Error(`Expected ${label} mock calls`);
162
+ }
163
+ const call = calls[callIndex];
164
+ if (!call) {
165
+ throw new Error(`Expected ${label} call ${callIndex + 1}`);
166
+ }
167
+ return call[argIndex];
168
+ }
169
+
170
+ function findAgentEvent(
171
+ mock: unknown,
172
+ params: { stream: string; phase?: string; itemId?: string; name?: string },
173
+ ) {
174
+ const calls = (mock as { mock?: { calls?: unknown[][] } }).mock?.calls;
175
+ if (!Array.isArray(calls)) {
176
+ throw new Error("Expected onAgentEvent mock calls");
177
+ }
178
+ for (const call of calls) {
179
+ const event = requireRecord(call[0], "agent event");
180
+ const data = requireRecord(event.data, "agent event data");
181
+ if (
182
+ event.stream === params.stream &&
183
+ (!params.phase || data.phase === params.phase) &&
184
+ (!params.itemId || data.itemId === params.itemId) &&
185
+ (!params.name || data.name === params.name)
186
+ ) {
187
+ return { event, data };
188
+ }
189
+ }
190
+ throw new Error(`Expected agent event ${params.stream}`);
191
+ }
192
+
193
+ function findPlanEventWithSteps(mock: unknown, steps: string[]) {
194
+ const calls = (mock as { mock?: { calls?: unknown[][] } }).mock?.calls;
195
+ if (!Array.isArray(calls)) {
196
+ throw new Error("Expected onAgentEvent mock calls");
197
+ }
198
+ for (const call of calls) {
199
+ const event = requireRecord(call[0], "agent event");
200
+ if (event.stream !== "plan") {
201
+ continue;
202
+ }
203
+ const data = requireRecord(event.data, "plan event data");
204
+ if (JSON.stringify(data.steps) === JSON.stringify(steps)) {
205
+ return data;
206
+ }
207
+ }
208
+ throw new Error(`Expected plan event ${steps.join(", ")}`);
209
+ }
210
+
211
+ function forCurrentTurn(
212
+ method: ProjectorNotification["method"],
213
+ params: Record<string, unknown>,
214
+ ): ProjectorNotification {
215
+ return {
216
+ method,
217
+ params: { threadId: THREAD_ID, turnId: TURN_ID, ...params },
218
+ } as ProjectorNotification;
219
+ }
220
+
221
+ function agentMessageDelta(delta: string, itemId = "msg-1"): ProjectorNotification {
222
+ return forCurrentTurn("item/agentMessage/delta", { itemId, delta });
223
+ }
224
+
225
+ function appServerError(params: { message: string; willRetry: boolean }): ProjectorNotification {
226
+ return forCurrentTurn("error", {
227
+ error: {
228
+ message: params.message,
229
+ codexErrorInfo: null,
230
+ additionalDetails: null,
231
+ },
232
+ willRetry: params.willRetry,
233
+ });
234
+ }
235
+
236
+ function rateLimitsUpdated(resetsAt: number): ProjectorNotification {
237
+ return {
238
+ method: "account/rateLimits/updated",
239
+ params: {
240
+ rateLimits: {
241
+ limitId: "codex",
242
+ limitName: "Codex",
243
+ primary: { usedPercent: 100, windowDurationMins: 300, resetsAt },
244
+ secondary: null,
245
+ credits: null,
246
+ planType: "plus",
247
+ rateLimitReachedType: "rate_limit_reached",
248
+ },
249
+ },
250
+ } as ProjectorNotification;
251
+ }
252
+
253
+ function turnCompleted(items: unknown[] = []): ProjectorNotification {
254
+ return turnWithStatus("completed", items);
255
+ }
256
+
257
+ function turnWithStatus(status: string, items: unknown[] = []): ProjectorNotification {
258
+ return {
259
+ method: "turn/completed",
260
+ params: {
261
+ threadId: THREAD_ID,
262
+ turn: { id: TURN_ID, status, items },
263
+ },
264
+ } as ProjectorNotification;
265
+ }
266
+
267
+ describe("CodexAppServerEventProjector", () => {
268
+ it("projects assistant deltas and usage into embedded attempt results", async () => {
269
+ const { onAssistantMessageStart, onPartialReply, projector } =
270
+ await createProjectorWithAssistantHooks();
271
+
272
+ await projector.handleNotification(agentMessageDelta("hel"));
273
+ await projector.handleNotification(agentMessageDelta("lo"));
274
+ await projector.handleNotification(
275
+ forCurrentTurn("thread/tokenUsage/updated", {
276
+ tokenUsage: {
277
+ total: {
278
+ totalTokens: 900_000,
279
+ inputTokens: 700_000,
280
+ cachedInputTokens: 100_000,
281
+ outputTokens: 100_000,
282
+ },
283
+ last: {
284
+ totalTokens: 12,
285
+ inputTokens: 5,
286
+ cachedInputTokens: 2,
287
+ outputTokens: 7,
288
+ },
289
+ },
290
+ }),
291
+ );
292
+ await projector.handleNotification(
293
+ turnCompleted([{ type: "agentMessage", id: "msg-1", text: "hello" }]),
294
+ );
295
+
296
+ const result = projector.buildResult(buildEmptyToolTelemetry());
297
+
298
+ expect(onAssistantMessageStart).toHaveBeenCalledTimes(1);
299
+ expect(onPartialReply).not.toHaveBeenCalled();
300
+ expect(result.assistantTexts).toEqual(["hello"]);
301
+ expect(result.messagesSnapshot.map((message) => message.role)).toEqual(["user", "assistant"]);
302
+ expect(result.lastAssistant?.content).toEqual([{ type: "text", text: "hello" }]);
303
+ expectUsageFields(result.attemptUsage, { input: 3, output: 7, cacheRead: 2, total: 12 });
304
+ expectUsageFields(result.lastAssistant?.usage, {
305
+ input: 3,
306
+ output: 7,
307
+ cacheRead: 2,
308
+ total: 12,
309
+ });
310
+ expect(result.replayMetadata.replaySafe).toBe(true);
311
+ });
312
+
313
+ it("suppresses mirrored user prompt when the inbound message was already persisted", async () => {
314
+ const params = await createParams();
315
+ const projector = await createProjector({
316
+ ...params,
317
+ suppressNextUserMessagePersistence: true,
318
+ });
319
+ await projector.handleNotification(
320
+ turnCompleted([{ type: "agentMessage", id: "msg-1", text: "retry result" }]),
321
+ );
322
+
323
+ const result = projector.buildResult(buildEmptyToolTelemetry());
324
+
325
+ expect(result.messagesSnapshot.map((message) => message.role)).toEqual(["assistant"]);
326
+ expect(JSON.stringify(result.messagesSnapshot)).not.toContain(params.prompt);
327
+ });
328
+
329
+ it("records canonical OpenAI Codex app-server turns with Codex local attribution", async () => {
330
+ const params = await createParams();
331
+ const projector = await createProjector({
332
+ ...params,
333
+ provider: "openai",
334
+ modelId: "gpt-5.5",
335
+ model: {
336
+ ...createCodexTestModel("openai"),
337
+ id: "gpt-5.5",
338
+ name: "gpt-5.5",
339
+ api: "openai-responses",
340
+ } as EmbeddedRunAttemptParams["model"],
341
+ runtimePlan: {
342
+ auth: {},
343
+ observability: {
344
+ resolvedRef: "openai/gpt-5.5",
345
+ provider: "openai",
346
+ modelId: "gpt-5.5",
347
+ harnessId: "codex",
348
+ },
349
+ prompt: {
350
+ resolveSystemPromptContribution: () => undefined,
351
+ },
352
+ tools: {
353
+ normalize: (tools: unknown[]) => tools,
354
+ logDiagnostics: () => undefined,
355
+ },
356
+ } as unknown as EmbeddedRunAttemptParams["runtimePlan"],
357
+ });
358
+
359
+ await projector.handleNotification(
360
+ turnCompleted([{ type: "agentMessage", id: "msg-1", text: "done" }]),
361
+ );
362
+
363
+ const result = projector.buildResult(buildEmptyToolTelemetry());
364
+
365
+ expect(result.lastAssistant?.provider).toBe("openai-codex");
366
+ expect(result.lastAssistant?.api).toBe("openai-codex-responses");
367
+ expect(result.lastAssistant?.model).toBe("gpt-5.5");
368
+ });
369
+
370
+ it("preserves OpenAI attribution for Codex app-server OpenAI API-key fallback profiles", async () => {
371
+ const params = await createParams();
372
+ const projector = await createProjector({
373
+ ...params,
374
+ provider: "openai",
375
+ authProfileId: "openai:work",
376
+ modelId: "gpt-5.5",
377
+ model: {
378
+ ...createCodexTestModel("openai"),
379
+ id: "gpt-5.5",
380
+ name: "gpt-5.5",
381
+ api: "openai-responses",
382
+ } as EmbeddedRunAttemptParams["model"],
383
+ runtimePlan: {
384
+ auth: {
385
+ providerForAuth: "openai",
386
+ authProfileProviderForAuth: "openai",
387
+ harnessAuthProvider: "openai-codex",
388
+ forwardedAuthProfileId: "openai:work",
389
+ },
390
+ observability: {
391
+ resolvedRef: "openai/gpt-5.5",
392
+ provider: "openai",
393
+ modelId: "gpt-5.5",
394
+ harnessId: "codex",
395
+ },
396
+ prompt: {
397
+ resolveSystemPromptContribution: () => undefined,
398
+ },
399
+ tools: {
400
+ normalize: (tools: unknown[]) => tools,
401
+ logDiagnostics: () => undefined,
402
+ },
403
+ } as unknown as EmbeddedRunAttemptParams["runtimePlan"],
404
+ });
405
+
406
+ await projector.handleNotification(
407
+ turnCompleted([{ type: "agentMessage", id: "msg-1", text: "done" }]),
408
+ );
409
+
410
+ const result = projector.buildResult(buildEmptyToolTelemetry());
411
+
412
+ expect(result.lastAssistant?.provider).toBe("openai");
413
+ expect(result.lastAssistant?.api).toBe("openai-responses");
414
+ expect(result.lastAssistant?.model).toBe("gpt-5.5");
415
+ });
416
+
417
+ it("preserves inbound sender metadata on the mirrored user prompt", async () => {
418
+ const params = await createParams();
419
+ const projector = await createProjector({
420
+ ...params,
421
+ messageChannel: "discord",
422
+ messageProvider: "discord-voice",
423
+ senderId: "user-123",
424
+ senderName: "Test User",
425
+ senderUsername: "testuser",
426
+ inputProvenance: {
427
+ kind: "external_user",
428
+ sourceChannel: "discord",
429
+ },
430
+ });
431
+
432
+ const result = projector.buildResult(buildEmptyToolTelemetry());
433
+
434
+ const userMessage = requireRecord(result.messagesSnapshot[0], "user message");
435
+ expect(userMessage.role).toBe("user");
436
+ expect(userMessage.content).toBe("hello");
437
+ expect(userMessage.sourceChannel).toBe("discord");
438
+ expect(userMessage.senderId).toBe("user-123");
439
+ expect(userMessage.senderName).toBe("Test User");
440
+ expect(userMessage.senderUsername).toBe("testuser");
441
+ expect(userMessage.senderLabel).toBe("Test User (user-123)");
442
+ expect(userMessage.provenance).toEqual({
443
+ kind: "external_user",
444
+ sourceChannel: "discord",
445
+ });
446
+ });
447
+
448
+ it("does not treat cumulative-only token usage as fresh context usage", async () => {
449
+ const projector = await createProjector();
450
+
451
+ await projector.handleNotification(agentMessageDelta("done"));
452
+ await projector.handleNotification(
453
+ forCurrentTurn("thread/tokenUsage/updated", {
454
+ tokenUsage: {
455
+ total: {
456
+ totalTokens: 1_000_000,
457
+ inputTokens: 999_000,
458
+ cachedInputTokens: 500,
459
+ outputTokens: 500,
460
+ },
461
+ },
462
+ }),
463
+ );
464
+
465
+ const result = projector.buildResult(buildEmptyToolTelemetry());
466
+
467
+ expect(result.assistantTexts).toEqual(["done"]);
468
+ expect(result.attemptUsage).toBeUndefined();
469
+ expectUsageFields(result.lastAssistant?.usage, {
470
+ input: 0,
471
+ output: 0,
472
+ cacheRead: 0,
473
+ total: 0,
474
+ });
475
+ });
476
+
477
+ it("uses raw assistant response items when turn completion omits items", async () => {
478
+ const projector = await createProjector();
479
+
480
+ await projector.handleNotification(
481
+ forCurrentTurn("rawResponseItem/completed", {
482
+ item: {
483
+ type: "message",
484
+ id: "raw-1",
485
+ role: "assistant",
486
+ content: [{ type: "output_text", text: "OK from raw" }],
487
+ },
488
+ }),
489
+ );
490
+ await projector.handleNotification(turnCompleted());
491
+
492
+ const result = projector.buildResult(buildEmptyToolTelemetry());
493
+
494
+ expect(result.assistantTexts).toEqual(["OK from raw"]);
495
+ expect(result.lastAssistant?.content).toEqual([{ type: "text", text: "OK from raw" }]);
496
+ });
497
+
498
+ it("attaches native Codex image-generation saved paths as reply media", async () => {
499
+ const projector = await createProjector();
500
+ const savedPath = "/tmp/codex-home/generated_images/session-1/ig_123.png";
501
+
502
+ await projector.handleNotification(
503
+ turnCompleted([
504
+ {
505
+ type: "imageGeneration",
506
+ id: "ig_123",
507
+ status: "completed",
508
+ revisedPrompt: "A tiny blue square",
509
+ result: "Zm9v",
510
+ savedPath,
511
+ },
512
+ ]),
513
+ );
514
+
515
+ const result = projector.buildResult(buildEmptyToolTelemetry());
516
+
517
+ expect(result.assistantTexts).toStrictEqual([]);
518
+ expect(result.toolMediaUrls).toEqual([savedPath]);
519
+ });
520
+
521
+ it("does not append native Codex image-generation media after explicit media delivery", async () => {
522
+ const projector = await createProjector();
523
+ const savedPath = "/tmp/codex-home/generated_images/session-1/ig_123.png";
524
+
525
+ await projector.handleNotification(
526
+ turnCompleted([
527
+ {
528
+ type: "imageGeneration",
529
+ id: "ig_123",
530
+ status: "completed",
531
+ revisedPrompt: null,
532
+ result: "Zm9v",
533
+ savedPath,
534
+ },
535
+ ]),
536
+ );
537
+
538
+ const result = projector.buildResult({
539
+ ...buildEmptyToolTelemetry(),
540
+ messagingToolSentMediaUrls: [savedPath],
541
+ toolMediaUrls: [],
542
+ });
543
+
544
+ expect(result.toolMediaUrls).toStrictEqual([]);
545
+ });
546
+
547
+ it("does not promote repeated tool progress text to the final assistant reply", async () => {
548
+ const onToolResult = vi.fn();
549
+ const projector = await createProjector({
550
+ ...(await createParams()),
551
+ verboseLevel: "on",
552
+ onToolResult,
553
+ });
554
+
555
+ await projector.handleNotification(
556
+ forCurrentTurn("item/started", {
557
+ item: {
558
+ type: "commandExecution",
559
+ id: "cmd-1",
560
+ command: "pnpm test extensions/codex",
561
+ cwd: "/workspace",
562
+ processId: null,
563
+ source: "agent",
564
+ status: "inProgress",
565
+ commandActions: [],
566
+ aggregatedOutput: null,
567
+ exitCode: null,
568
+ durationMs: null,
569
+ },
570
+ }),
571
+ );
572
+ const toolProgressText = (mockCallArg(onToolResult, 0, 0, "onToolResult") as { text?: string })
573
+ .text;
574
+ expect(toolProgressText).toBe("🛠️ `run tests (workspace)`");
575
+
576
+ await projector.handleNotification(
577
+ forCurrentTurn("rawResponseItem/completed", {
578
+ item: {
579
+ type: "message",
580
+ id: "raw-tool-progress",
581
+ role: "assistant",
582
+ content: [{ type: "output_text", text: toolProgressText }],
583
+ },
584
+ }),
585
+ );
586
+ await projector.handleNotification(turnCompleted());
587
+
588
+ const result = projector.buildResult(buildEmptyToolTelemetry());
589
+
590
+ expect(result.assistantTexts).toEqual([]);
591
+ expect(result.lastAssistant).toBeUndefined();
592
+ });
593
+
594
+ it("does not treat app-server interrupted status as a user cancellation by itself", async () => {
595
+ const projector = await createProjector();
596
+
597
+ await projector.handleNotification(turnWithStatus("interrupted"));
598
+
599
+ const result = projector.buildResult(buildEmptyToolTelemetry());
600
+
601
+ expect(result.aborted).toBe(false);
602
+ expect(result.externalAbort).toBe(false);
603
+ expect(result.timedOut).toBe(false);
604
+ expect(result.promptError).toBeNull();
605
+ expect(result.assistantTexts).toEqual([]);
606
+ expect(result.lastAssistant).toBeUndefined();
607
+ });
608
+
609
+ it("keeps sparse successful bash output eligible for the no-visible-answer guard", async () => {
610
+ const projector = await createProjector();
611
+
612
+ await projector.handleNotification(
613
+ turnWithStatus("interrupted", [
614
+ {
615
+ type: "commandExecution",
616
+ id: "cmd-empty-output",
617
+ command:
618
+ "ps -eo pid,ppid,stat,cmd | rg 'venv-roadmap|pytest|run_security_contract_validation|validate_public_install|git push|apply_patch' || true",
619
+ cwd: "/workspace",
620
+ processId: null,
621
+ source: "agent",
622
+ status: "completed",
623
+ commandActions: [],
624
+ aggregatedOutput: "",
625
+ exitCode: 0,
626
+ durationMs: 42,
627
+ },
628
+ ]),
629
+ );
630
+
631
+ const result = projector.buildResult(buildEmptyToolTelemetry());
632
+
633
+ expect(result.aborted).toBe(false);
634
+ expect(result.assistantTexts).toEqual([]);
635
+ expect(result.toolMetas).toEqual([
636
+ expect.objectContaining({ toolName: "bash", meta: expect.stringContaining("workspace") }),
637
+ ]);
638
+ });
639
+
640
+ it("keeps explicit cancellation marked aborted for interrupted tool-only turns", async () => {
641
+ const projector = await createProjector();
642
+ projector.markAborted();
643
+
644
+ await projector.handleNotification(
645
+ turnWithStatus("interrupted", [
646
+ {
647
+ type: "commandExecution",
648
+ id: "cmd-cancelled",
649
+ command: "/bin/bash -lc true",
650
+ cwd: "/workspace",
651
+ processId: null,
652
+ source: "agent",
653
+ status: "completed",
654
+ commandActions: [],
655
+ aggregatedOutput: "",
656
+ exitCode: 0,
657
+ durationMs: 12,
658
+ },
659
+ ]),
660
+ );
661
+
662
+ const result = projector.buildResult(buildEmptyToolTelemetry());
663
+ expect(result.aborted).toBe(true);
664
+ expect(result.assistantTexts).toEqual([]);
665
+ });
666
+
667
+ it("does not fail a completed reply after a retryable app-server error notification", async () => {
668
+ const projector = await createProjector();
669
+
670
+ await projector.handleNotification(agentMessageDelta("still working"));
671
+ await projector.handleNotification(
672
+ appServerError({ message: "stream disconnected", willRetry: true }),
673
+ );
674
+ await projector.handleNotification(
675
+ turnCompleted([{ type: "agentMessage", id: "msg-1", text: "final answer" }]),
676
+ );
677
+
678
+ const result = projector.buildResult(buildEmptyToolTelemetry());
679
+
680
+ expect(result.assistantTexts).toEqual(["final answer"]);
681
+ expect(result.promptError).toBeNull();
682
+ expect(result.promptErrorSource).toBeNull();
683
+ expect(result.lastAssistant?.stopReason).toBe("stop");
684
+ expect(result.lastAssistant?.errorMessage).toBeUndefined();
685
+ });
686
+
687
+ it("uses nested app-server error messages for terminal errors", async () => {
688
+ const projector = await createProjector();
689
+
690
+ await projector.handleNotification(
691
+ appServerError({ message: "stream failed permanently", willRetry: false }),
692
+ );
693
+
694
+ const result = projector.buildResult(buildEmptyToolTelemetry());
695
+
696
+ expect(result.promptError).toBe("stream failed permanently");
697
+ expect(result.promptErrorSource).toBe("prompt");
698
+ expect(result.lastAssistant).toBeUndefined();
699
+ });
700
+
701
+ it("uses Codex rate-limit resets for usage-limit app-server errors", async () => {
702
+ const projector = await createProjector();
703
+ const resetsAt = Math.ceil(Date.now() / 1000) + 120;
704
+
705
+ await projector.handleNotification(rateLimitsUpdated(resetsAt));
706
+ await projector.handleNotification(
707
+ forCurrentTurn("error", {
708
+ error: {
709
+ message: "You've reached your usage limit.",
710
+ codexErrorInfo: "usageLimitExceeded",
711
+ additionalDetails: null,
712
+ },
713
+ willRetry: false,
714
+ }),
715
+ );
716
+
717
+ const result = projector.buildResult(buildEmptyToolTelemetry());
718
+
719
+ expect(result.promptError).toContain("You've reached your Codex subscription usage limit.");
720
+ expect(result.promptError).toContain("Next reset in");
721
+ expect(result.promptError).toContain("Run /codex account");
722
+ expect(result.promptErrorSource).toBe("prompt");
723
+ });
724
+
725
+ it("uses Codex rate-limit resets for failed turns", async () => {
726
+ const projector = await createProjector();
727
+ const resetsAt = Math.ceil(Date.now() / 1000) + 120;
728
+
729
+ await projector.handleNotification(rateLimitsUpdated(resetsAt));
730
+ await projector.handleNotification(
731
+ forCurrentTurn("turn/completed", {
732
+ turn: {
733
+ id: TURN_ID,
734
+ status: "failed",
735
+ error: {
736
+ message: "You've reached your usage limit.",
737
+ codexErrorInfo: "usageLimitExceeded",
738
+ additionalDetails: null,
739
+ },
740
+ items: [],
741
+ },
742
+ }),
743
+ );
744
+
745
+ const result = projector.buildResult(buildEmptyToolTelemetry());
746
+
747
+ expect(result.promptError).toContain("You've reached your Codex subscription usage limit.");
748
+ expect(result.promptError).toContain("Next reset in");
749
+ expect(result.promptErrorSource).toBe("prompt");
750
+ });
751
+
752
+ it("uses a recent Codex rate-limit snapshot when failed turns omit reset details", async () => {
753
+ const projector = await createProjector();
754
+ const resetsAt = Math.ceil(Date.now() / 1000) + 120;
755
+ rememberCodexRateLimits({
756
+ rateLimits: {
757
+ limitId: "codex",
758
+ limitName: "Codex",
759
+ primary: { usedPercent: 100, windowDurationMins: 300, resetsAt },
760
+ secondary: null,
761
+ credits: null,
762
+ planType: "plus",
763
+ rateLimitReachedType: "rate_limit_reached",
764
+ },
765
+ rateLimitsByLimitId: null,
766
+ });
767
+
768
+ await projector.handleNotification(
769
+ forCurrentTurn("turn/completed", {
770
+ turn: {
771
+ id: TURN_ID,
772
+ status: "failed",
773
+ error: {
774
+ message: "You've reached your usage limit.",
775
+ codexErrorInfo: "usageLimitExceeded",
776
+ additionalDetails: null,
777
+ },
778
+ items: [],
779
+ },
780
+ }),
781
+ );
782
+
783
+ const result = projector.buildResult(buildEmptyToolTelemetry());
784
+
785
+ expect(result.promptError).toContain("You've reached your Codex subscription usage limit.");
786
+ expect(result.promptError).toContain("Next reset in");
787
+ expect(result.promptErrorSource).toBe("prompt");
788
+ });
789
+
790
+ it("preserves Codex retry hints when failed turns omit structured reset details", async () => {
791
+ const projector = await createProjector();
792
+
793
+ await projector.handleNotification(
794
+ forCurrentTurn("turn/completed", {
795
+ turn: {
796
+ id: TURN_ID,
797
+ status: "failed",
798
+ error: {
799
+ message:
800
+ "You've hit your usage limit. Visit https://chatgpt.com/codex/settings/usage to purchase more credits or try again at May 11th, 2026 9:00 AM.",
801
+ codexErrorInfo: "usageLimitExceeded",
802
+ additionalDetails: null,
803
+ },
804
+ items: [],
805
+ },
806
+ }),
807
+ );
808
+
809
+ const result = projector.buildResult(buildEmptyToolTelemetry());
810
+
811
+ expect(result.promptError).toContain("You've reached your Codex subscription usage limit.");
812
+ expect(result.promptError).toContain("Codex says to try again at May 11th, 2026 9:00 AM.");
813
+ expect(result.promptError).not.toContain("Codex did not return a reset time");
814
+ expect(result.promptErrorSource).toBe("prompt");
815
+ });
816
+
817
+ it("normalizes snake_case current token usage fields", async () => {
818
+ const projector = await createProjector();
819
+
820
+ await projector.handleNotification(agentMessageDelta("done"));
821
+ await projector.handleNotification(
822
+ forCurrentTurn("thread/tokenUsage/updated", {
823
+ tokenUsage: {
824
+ total: { total_tokens: 1_000_000 },
825
+ last_token_usage: {
826
+ total_tokens: 17,
827
+ input_tokens: 8,
828
+ cached_input_tokens: 3,
829
+ output_tokens: 9,
830
+ },
831
+ },
832
+ }),
833
+ );
834
+
835
+ const result = projector.buildResult(buildEmptyToolTelemetry());
836
+
837
+ expectUsageFields(result.attemptUsage, { input: 5, output: 9, cacheRead: 3, total: 17 });
838
+ expectUsageFields(result.lastAssistant?.usage, {
839
+ input: 5,
840
+ output: 9,
841
+ cacheRead: 3,
842
+ total: 17,
843
+ });
844
+ });
845
+
846
+ it("keeps intermediate agentMessage items out of the final visible reply", async () => {
847
+ const { onAssistantMessageStart, onPartialReply, projector } =
848
+ await createProjectorWithAssistantHooks();
849
+
850
+ await projector.handleNotification(
851
+ agentMessageDelta(
852
+ "checking thread context; then post a tight progress reply here.",
853
+ "msg-commentary",
854
+ ),
855
+ );
856
+ await projector.handleNotification(
857
+ agentMessageDelta(
858
+ "release fixes first. please drop affected PRs, failing checks, and blockers here.",
859
+ "msg-final",
860
+ ),
861
+ );
862
+ await projector.handleNotification(
863
+ turnCompleted([
864
+ {
865
+ type: "agentMessage",
866
+ id: "msg-commentary",
867
+ text: "checking thread context; then post a tight progress reply here.",
868
+ },
869
+ {
870
+ type: "agentMessage",
871
+ id: "msg-final",
872
+ text: "release fixes first. please drop affected PRs, failing checks, and blockers here.",
873
+ },
874
+ ]),
875
+ );
876
+
877
+ const result = projector.buildResult(buildEmptyToolTelemetry());
878
+
879
+ expect(onAssistantMessageStart).toHaveBeenCalledTimes(1);
880
+ expect(onPartialReply).not.toHaveBeenCalled();
881
+ expect(result.assistantTexts).toEqual([
882
+ "release fixes first. please drop affected PRs, failing checks, and blockers here.",
883
+ ]);
884
+ expect(result.lastAssistant?.content).toEqual([
885
+ {
886
+ type: "text",
887
+ text: "release fixes first. please drop affected PRs, failing checks, and blockers here.",
888
+ },
889
+ ]);
890
+ expect(JSON.stringify(result.messagesSnapshot)).not.toContain("checking thread context");
891
+ });
892
+
893
+ it("streams commentary agent messages as keyed progress events", async () => {
894
+ const onAgentEvent = vi.fn();
895
+ const onPartialReply = vi.fn();
896
+ const projector = await createProjector({
897
+ ...(await createParams()),
898
+ onAgentEvent,
899
+ onPartialReply,
900
+ });
901
+
902
+ await projector.handleNotification(
903
+ forCurrentTurn("item/started", {
904
+ item: {
905
+ type: "agentMessage",
906
+ id: "msg-commentary",
907
+ phase: "commentary",
908
+ text: "",
909
+ },
910
+ }),
911
+ );
912
+ await projector.handleNotification(agentMessageDelta("Checking", "msg-commentary"));
913
+ await projector.handleNotification(
914
+ agentMessageDelta(" the app-server stream", "msg-commentary"),
915
+ );
916
+ await projector.handleNotification(
917
+ turnCompleted([
918
+ {
919
+ type: "agentMessage",
920
+ id: "msg-commentary",
921
+ phase: "commentary",
922
+ text: "Checking the app-server stream",
923
+ },
924
+ {
925
+ type: "agentMessage",
926
+ id: "msg-final",
927
+ phase: "final_answer",
928
+ text: "final answer",
929
+ },
930
+ ]),
931
+ );
932
+
933
+ const progressEvents = onAgentEvent.mock.calls
934
+ .map((call) => call[0])
935
+ .filter((event) => event.stream === "item" && event.data.kind === "preamble");
936
+
937
+ expect(onPartialReply).not.toHaveBeenCalled();
938
+ expect(progressEvents.map((event) => event.data)).toEqual([
939
+ {
940
+ itemId: "msg-commentary",
941
+ kind: "preamble",
942
+ title: "Preamble",
943
+ phase: "update",
944
+ progressText: "Checking",
945
+ source: "codex-app-server",
946
+ },
947
+ {
948
+ itemId: "msg-commentary",
949
+ kind: "preamble",
950
+ title: "Preamble",
951
+ phase: "update",
952
+ progressText: "Checking the app-server stream",
953
+ source: "codex-app-server",
954
+ },
955
+ ]);
956
+
957
+ const result = projector.buildResult(buildEmptyToolTelemetry());
958
+ expect(result.assistantTexts).toEqual(["final answer"]);
959
+ });
960
+
961
+ it("does not resolve commentary-phase assistant text as the final reply", async () => {
962
+ const projector = await createProjector();
963
+
964
+ await projector.handleNotification(
965
+ turnCompleted([
966
+ {
967
+ type: "agentMessage",
968
+ id: "msg-final",
969
+ phase: "final_answer",
970
+ text: "final answer",
971
+ },
972
+ {
973
+ type: "agentMessage",
974
+ id: "msg-commentary",
975
+ phase: "commentary",
976
+ text: "I am checking one more thing.",
977
+ },
978
+ ]),
979
+ );
980
+
981
+ const result = projector.buildResult(buildEmptyToolTelemetry());
982
+
983
+ expect(result.assistantTexts).toEqual(["final answer"]);
984
+ });
985
+
986
+ it("ignores notifications for other turns", async () => {
987
+ const projector = await createProjector();
988
+
989
+ await projector.handleNotification({
990
+ method: "item/agentMessage/delta",
991
+ params: { threadId: THREAD_ID, turnId: "turn-2", itemId: "msg-1", delta: "wrong" },
992
+ });
993
+
994
+ const result = projector.buildResult(buildEmptyToolTelemetry());
995
+ expect(result.assistantTexts).toStrictEqual([]);
996
+ });
997
+
998
+ it("ignores notifications that omit top-level thread and turn ids", async () => {
999
+ const projector = await createProjector();
1000
+
1001
+ await projector.handleNotification({
1002
+ method: "turn/completed",
1003
+ params: {
1004
+ turn: {
1005
+ id: TURN_ID,
1006
+ status: "completed",
1007
+ items: [{ type: "agentMessage", id: "msg-1", text: "wrong turn" }],
1008
+ },
1009
+ },
1010
+ });
1011
+
1012
+ const result = projector.buildResult(buildEmptyToolTelemetry());
1013
+ expect(result.assistantTexts).toStrictEqual([]);
1014
+ expect(result.lastAssistant).toBeUndefined();
1015
+ });
1016
+
1017
+ it("preserves sessions_yield detection in attempt results", () => {
1018
+ const projector = new CodexAppServerEventProjector(
1019
+ {
1020
+ prompt: "hello",
1021
+ sessionId: "session-1",
1022
+ sessionFile: "/tmp/session.jsonl",
1023
+ workspaceDir: "/tmp",
1024
+ runId: "run-1",
1025
+ provider: "openai-codex",
1026
+ modelId: "gpt-5.4-codex",
1027
+ model: createCodexTestModel(),
1028
+ thinkLevel: "medium",
1029
+ } as EmbeddedRunAttemptParams,
1030
+ THREAD_ID,
1031
+ TURN_ID,
1032
+ );
1033
+
1034
+ const result = projector.buildResult(buildEmptyToolTelemetry(), { yieldDetected: true });
1035
+
1036
+ expect(result.yieldDetected).toBe(true);
1037
+ });
1038
+
1039
+ it("projects guardian review lifecycle details into agent events", async () => {
1040
+ const onAgentEvent = vi.fn();
1041
+ const projector = await createProjector({ ...(await createParams()), onAgentEvent });
1042
+
1043
+ await projector.handleNotification(
1044
+ forCurrentTurn("item/autoApprovalReview/started", {
1045
+ reviewId: "review-1",
1046
+ targetItemId: "cmd-1",
1047
+ review: { status: "inProgress" },
1048
+ action: {
1049
+ type: "execve",
1050
+ source: "shell",
1051
+ program: "/bin/printf",
1052
+ argv: ["printf", "hello"],
1053
+ cwd: "/tmp",
1054
+ },
1055
+ }),
1056
+ );
1057
+ await projector.handleNotification(
1058
+ forCurrentTurn("item/autoApprovalReview/completed", {
1059
+ reviewId: "review-1",
1060
+ targetItemId: "cmd-1",
1061
+ decisionSource: "agent",
1062
+ review: {
1063
+ status: "approved",
1064
+ riskLevel: "low",
1065
+ userAuthorization: "high",
1066
+ rationale: "Benign local probe.",
1067
+ },
1068
+ action: {
1069
+ type: "execve",
1070
+ source: "shell",
1071
+ program: "/bin/printf",
1072
+ argv: ["printf", "hello"],
1073
+ cwd: "/tmp",
1074
+ },
1075
+ }),
1076
+ );
1077
+
1078
+ const started = findAgentEvent(onAgentEvent, {
1079
+ stream: "codex_app_server.guardian",
1080
+ phase: "started",
1081
+ }).data;
1082
+ expect(started.reviewId).toBe("review-1");
1083
+ expect(started.targetItemId).toBe("cmd-1");
1084
+ expect(started.status).toBe("inProgress");
1085
+ expect(started.actionType).toBe("execve");
1086
+ const completed = findAgentEvent(onAgentEvent, {
1087
+ stream: "codex_app_server.guardian",
1088
+ phase: "completed",
1089
+ }).data;
1090
+ expect(completed.reviewId).toBe("review-1");
1091
+ expect(completed.targetItemId).toBe("cmd-1");
1092
+ expect(completed.decisionSource).toBe("agent");
1093
+ expect(completed.status).toBe("approved");
1094
+ expect(completed.riskLevel).toBe("low");
1095
+ expect(completed.userAuthorization).toBe("high");
1096
+ expect(completed.rationale).toBe("Benign local probe.");
1097
+ expect(completed.actionType).toBe("execve");
1098
+ expect(
1099
+ projector.buildResult(buildEmptyToolTelemetry()).didSendDeterministicApprovalPrompt,
1100
+ ).toBe(false);
1101
+ });
1102
+
1103
+ it("projects reasoning end, plan updates, compaction state, and tool metadata", async () => {
1104
+ const onReasoningStream = vi.fn();
1105
+ const onReasoningEnd = vi.fn();
1106
+ const onAgentEvent = vi.fn();
1107
+ const params = {
1108
+ ...(await createParams()),
1109
+ onReasoningStream,
1110
+ onReasoningEnd,
1111
+ onAgentEvent,
1112
+ };
1113
+ const projector = await createProjector(params);
1114
+
1115
+ await projector.handleNotification(
1116
+ forCurrentTurn("item/reasoning/textDelta", { itemId: "reason-1", delta: "thinking" }),
1117
+ );
1118
+ await projector.handleNotification(
1119
+ forCurrentTurn("item/plan/delta", { itemId: "plan-1", delta: "- inspect\n" }),
1120
+ );
1121
+ await projector.handleNotification(
1122
+ forCurrentTurn("turn/plan/updated", {
1123
+ explanation: "next",
1124
+ plan: [{ step: "patch", status: "in_progress" }],
1125
+ }),
1126
+ );
1127
+ await projector.handleNotification(
1128
+ forCurrentTurn("item/started", {
1129
+ item: { type: "contextCompaction", id: "compact-1" },
1130
+ }),
1131
+ );
1132
+ expect(projector.isCompacting()).toBe(true);
1133
+ await projector.handleNotification(
1134
+ forCurrentTurn("item/completed", {
1135
+ item: { type: "contextCompaction", id: "compact-1" },
1136
+ }),
1137
+ );
1138
+ expect(projector.isCompacting()).toBe(false);
1139
+ await projector.handleNotification(
1140
+ forCurrentTurn("item/completed", {
1141
+ item: {
1142
+ type: "dynamicToolCall",
1143
+ id: "tool-1",
1144
+ tool: "sessions_send",
1145
+ status: "completed",
1146
+ },
1147
+ }),
1148
+ );
1149
+ await projector.handleNotification(turnCompleted());
1150
+
1151
+ const result = projector.buildResult(buildEmptyToolTelemetry());
1152
+
1153
+ expect(onReasoningStream).toHaveBeenCalledWith({ text: "thinking" });
1154
+ expect(onReasoningEnd).toHaveBeenCalledTimes(1);
1155
+ expect(findPlanEventWithSteps(onAgentEvent, ["patch (in_progress)"]).steps).toEqual([
1156
+ "patch (in_progress)",
1157
+ ]);
1158
+ expect(findAgentEvent(onAgentEvent, { stream: "compaction", phase: "start" }).data.itemId).toBe(
1159
+ "compact-1",
1160
+ );
1161
+ expect(findAgentEvent(onAgentEvent, { stream: "compaction", phase: "end" }).data).toMatchObject(
1162
+ {
1163
+ itemId: "compact-1",
1164
+ completed: true,
1165
+ },
1166
+ );
1167
+ expect(result.toolMetas).toEqual([{ toolName: "sessions_send" }]);
1168
+ expect(result.messagesSnapshot.map((message) => message.role)).toEqual([
1169
+ "user",
1170
+ "assistant",
1171
+ "assistant",
1172
+ ]);
1173
+ expect(JSON.stringify(result.messagesSnapshot[1])).toContain("Codex reasoning");
1174
+ expect(JSON.stringify(result.messagesSnapshot[2])).toContain("Codex plan");
1175
+ expect(requireRecord(result.itemLifecycle, "item lifecycle").compactionCount).toBe(1);
1176
+ });
1177
+
1178
+ it("synthesizes normalized tool progress for Codex-native tool items", async () => {
1179
+ const onAgentEvent = vi.fn();
1180
+ const projector = await createProjector({ ...(await createParams()), onAgentEvent });
1181
+ const diagnosticEvents: DiagnosticEventPayload[] = [];
1182
+ const unsubscribe = onInternalDiagnosticEvent((event) => diagnosticEvents.push(event));
1183
+
1184
+ try {
1185
+ await projector.handleNotification(
1186
+ forCurrentTurn("item/started", {
1187
+ item: {
1188
+ type: "commandExecution",
1189
+ id: "cmd-1",
1190
+ command: "pnpm test extensions/codex",
1191
+ cwd: "/workspace",
1192
+ processId: null,
1193
+ source: "agent",
1194
+ status: "inProgress",
1195
+ commandActions: [],
1196
+ aggregatedOutput: null,
1197
+ exitCode: null,
1198
+ durationMs: null,
1199
+ },
1200
+ }),
1201
+ );
1202
+ await projector.handleNotification(
1203
+ forCurrentTurn("item/completed", {
1204
+ item: {
1205
+ type: "commandExecution",
1206
+ id: "cmd-1",
1207
+ command: "pnpm test extensions/codex",
1208
+ cwd: "/workspace",
1209
+ processId: null,
1210
+ source: "agent",
1211
+ status: "completed",
1212
+ commandActions: [],
1213
+ aggregatedOutput: "ok",
1214
+ exitCode: 0,
1215
+ durationMs: 42,
1216
+ },
1217
+ }),
1218
+ );
1219
+ await flushDiagnosticEvents();
1220
+ } finally {
1221
+ unsubscribe();
1222
+ }
1223
+
1224
+ const itemStart = findAgentEvent(onAgentEvent, {
1225
+ stream: "item",
1226
+ phase: "start",
1227
+ itemId: "cmd-1",
1228
+ }).data;
1229
+ expect(itemStart.kind).toBe("command");
1230
+ expect(itemStart.name).toBe("bash");
1231
+ expect(itemStart.suppressChannelProgress).toBe(true);
1232
+ const toolStart = findAgentEvent(onAgentEvent, {
1233
+ stream: "tool",
1234
+ phase: "start",
1235
+ itemId: "cmd-1",
1236
+ name: "bash",
1237
+ }).data;
1238
+ expect(toolStart.toolCallId).toBe("cmd-1");
1239
+ expect(toolStart.args).toEqual({ command: "pnpm test extensions/codex", cwd: "/workspace" });
1240
+ const toolResult = findAgentEvent(onAgentEvent, {
1241
+ stream: "tool",
1242
+ phase: "result",
1243
+ itemId: "cmd-1",
1244
+ name: "bash",
1245
+ }).data;
1246
+ expect(toolResult.toolCallId).toBe("cmd-1");
1247
+ expect(toolResult.status).toBe("completed");
1248
+ expect(toolResult.isError).toBe(false);
1249
+ const toolResultPayload = requireRecord(toolResult.result, "tool result payload");
1250
+ expect(toolResultPayload.exitCode).toBe(0);
1251
+ expect(toolResultPayload.durationMs).toBe(42);
1252
+ const toolDiagnosticEvents = diagnosticEvents.filter(
1253
+ (
1254
+ event,
1255
+ ): event is Extract<
1256
+ DiagnosticEventPayload,
1257
+ {
1258
+ type:
1259
+ | "tool.execution.started"
1260
+ | "tool.execution.completed"
1261
+ | "tool.execution.error"
1262
+ | "tool.execution.blocked";
1263
+ }
1264
+ > => event.type.startsWith("tool.execution."),
1265
+ );
1266
+ expect(
1267
+ toolDiagnosticEvents.map((event) => ({
1268
+ type: event.type,
1269
+ toolName: event.toolName,
1270
+ toolCallId: event.toolCallId,
1271
+ durationMs: "durationMs" in event ? event.durationMs : undefined,
1272
+ })),
1273
+ ).toEqual([
1274
+ {
1275
+ type: "tool.execution.started",
1276
+ toolName: "bash",
1277
+ toolCallId: "cmd-1",
1278
+ durationMs: undefined,
1279
+ },
1280
+ {
1281
+ type: "tool.execution.completed",
1282
+ toolName: "bash",
1283
+ toolCallId: "cmd-1",
1284
+ durationMs: 42,
1285
+ },
1286
+ ]);
1287
+ const result = projector.buildResult(buildEmptyToolTelemetry());
1288
+ expect(result.messagesSnapshot.map((message) => message.role)).toEqual([
1289
+ "user",
1290
+ "assistant",
1291
+ "toolResult",
1292
+ ]);
1293
+ const assistant = requireRecord(result.messagesSnapshot[1], "assistant tool call message");
1294
+ expect(assistant.role).toBe("assistant");
1295
+ const assistantContent = requireArray(assistant.content, "assistant content");
1296
+ expect(assistantContent[0]).toEqual({
1297
+ type: "toolCall",
1298
+ id: "cmd-1",
1299
+ name: "bash",
1300
+ arguments: { command: "pnpm test extensions/codex", cwd: "/workspace" },
1301
+ input: { command: "pnpm test extensions/codex", cwd: "/workspace" },
1302
+ });
1303
+ const toolResultMessage = requireRecord(result.messagesSnapshot[2], "tool result message");
1304
+ expect(toolResultMessage.role).toBe("toolResult");
1305
+ expect(toolResultMessage.toolCallId).toBe("cmd-1");
1306
+ expect(toolResultMessage.toolName).toBe("bash");
1307
+ expect(toolResultMessage.isError).toBe(false);
1308
+ const toolResultContent = requireArray(toolResultMessage.content, "tool result content");
1309
+ const toolResultContentItem = requireRecord(toolResultContent[0], "tool result content item");
1310
+ expect(toolResultContentItem.type).toBe("toolResult");
1311
+ expect(toolResultContentItem.id).toBe("cmd-1");
1312
+ expect(toolResultContentItem.name).toBe("bash");
1313
+ expect(toolResultContentItem.toolName).toBe("bash");
1314
+ expect(toolResultContentItem.toolCallId).toBe("cmd-1");
1315
+ expect(toolResultContentItem.content).toBe("ok");
1316
+ });
1317
+
1318
+ it("synthesizes native tool progress from turn completion snapshots", async () => {
1319
+ const onAgentEvent = vi.fn();
1320
+ const onToolResult = vi.fn();
1321
+ const trajectoryRecorder = {
1322
+ filePath: "trajectory.jsonl",
1323
+ recordEvent: vi.fn(),
1324
+ flush: vi.fn(async () => undefined),
1325
+ };
1326
+ const projector = await createProjector(
1327
+ {
1328
+ ...(await createParams()),
1329
+ verboseLevel: "on",
1330
+ onAgentEvent,
1331
+ onToolResult,
1332
+ },
1333
+ {
1334
+ trajectoryRecorder,
1335
+ },
1336
+ );
1337
+
1338
+ await projector.handleNotification(
1339
+ turnCompleted([
1340
+ {
1341
+ type: "commandExecution",
1342
+ id: "cmd-snapshot",
1343
+ command: "pnpm test extensions/codex",
1344
+ cwd: "/workspace",
1345
+ processId: null,
1346
+ source: "agent",
1347
+ status: "completed",
1348
+ commandActions: [],
1349
+ aggregatedOutput: "ok",
1350
+ exitCode: 0,
1351
+ durationMs: 42,
1352
+ },
1353
+ ]),
1354
+ );
1355
+
1356
+ const itemStart = findAgentEvent(onAgentEvent, {
1357
+ stream: "item",
1358
+ phase: "start",
1359
+ itemId: "cmd-snapshot",
1360
+ }).data;
1361
+ expect(itemStart.kind).toBe("command");
1362
+ expect(itemStart.name).toBe("bash");
1363
+ expect(itemStart.suppressChannelProgress).toBe(true);
1364
+ const toolStart = findAgentEvent(onAgentEvent, {
1365
+ stream: "tool",
1366
+ phase: "start",
1367
+ itemId: "cmd-snapshot",
1368
+ name: "bash",
1369
+ }).data;
1370
+ expect(toolStart.args).toEqual({ command: "pnpm test extensions/codex", cwd: "/workspace" });
1371
+ const toolResult = findAgentEvent(onAgentEvent, {
1372
+ stream: "tool",
1373
+ phase: "result",
1374
+ itemId: "cmd-snapshot",
1375
+ name: "bash",
1376
+ }).data;
1377
+ expect(toolResult.status).toBe("completed");
1378
+ expect(toolResult.isError).toBe(false);
1379
+ expect(onToolResult).toHaveBeenCalledWith({
1380
+ text: "🛠️ `run tests (workspace)`",
1381
+ });
1382
+ expect(trajectoryRecorder.recordEvent).toHaveBeenCalledWith("tool.call", {
1383
+ threadId: THREAD_ID,
1384
+ turnId: TURN_ID,
1385
+ itemId: "cmd-snapshot",
1386
+ toolCallId: "cmd-snapshot",
1387
+ name: "bash",
1388
+ arguments: { command: "pnpm test extensions/codex", cwd: "/workspace" },
1389
+ });
1390
+ expect(trajectoryRecorder.recordEvent).toHaveBeenCalledWith("tool.result", {
1391
+ threadId: THREAD_ID,
1392
+ turnId: TURN_ID,
1393
+ itemId: "cmd-snapshot",
1394
+ toolCallId: "cmd-snapshot",
1395
+ name: "bash",
1396
+ status: "completed",
1397
+ isError: false,
1398
+ result: { status: "completed", exitCode: 0, durationMs: 42 },
1399
+ output: "ok",
1400
+ });
1401
+ });
1402
+
1403
+ it("uses streamed command output when final command snapshots omit aggregated output", async () => {
1404
+ const onAgentEvent = vi.fn();
1405
+ const trajectoryRecorder = {
1406
+ filePath: "trajectory.jsonl",
1407
+ recordEvent: vi.fn(),
1408
+ flush: vi.fn(async () => undefined),
1409
+ };
1410
+ const projector = await createProjector(
1411
+ {
1412
+ ...(await createParams()),
1413
+ onAgentEvent,
1414
+ },
1415
+ {
1416
+ trajectoryRecorder,
1417
+ },
1418
+ );
1419
+
1420
+ await projector.handleNotification(
1421
+ forCurrentTurn("item/commandExecution/outputDelta", {
1422
+ itemId: "cmd-1",
1423
+ delta: "status passed\n",
1424
+ }),
1425
+ );
1426
+ await projector.handleNotification(
1427
+ forCurrentTurn("item/commandExecution/outputDelta", {
1428
+ itemId: "cmd-1",
1429
+ delta: "json /tmp/scenario.json\n",
1430
+ }),
1431
+ );
1432
+ await projector.handleNotification(
1433
+ turnCompleted([
1434
+ {
1435
+ type: "commandExecution",
1436
+ id: "cmd-1",
1437
+ command: "python scripts/run_demo_scenario.py",
1438
+ cwd: "/workspace",
1439
+ processId: null,
1440
+ source: "agent",
1441
+ status: "completed",
1442
+ commandActions: [],
1443
+ aggregatedOutput: null,
1444
+ exitCode: 0,
1445
+ durationMs: 42,
1446
+ },
1447
+ ]),
1448
+ );
1449
+
1450
+ const result = projector.buildResult(buildEmptyToolTelemetry());
1451
+ const toolResultMessage = requireRecord(result.messagesSnapshot[2], "tool result message");
1452
+ const toolResultContent = requireArray(toolResultMessage.content, "tool result content");
1453
+ const toolResultContentItem = requireRecord(toolResultContent[0], "tool result content item");
1454
+ expect(toolResultContentItem.content).toBe("status passed\njson /tmp/scenario.json");
1455
+ expect(trajectoryRecorder.recordEvent).toHaveBeenCalledWith(
1456
+ "tool.result",
1457
+ expect.objectContaining({
1458
+ itemId: "cmd-1",
1459
+ output: "status passed\njson /tmp/scenario.json",
1460
+ }),
1461
+ );
1462
+ const toolResult = findAgentEvent(onAgentEvent, {
1463
+ stream: "tool",
1464
+ phase: "result",
1465
+ itemId: "cmd-1",
1466
+ name: "bash",
1467
+ }).data;
1468
+ expect(toolResult.result).toEqual({ status: "completed", exitCode: 0, durationMs: 42 });
1469
+ });
1470
+
1471
+ it("uses streamed command output for failed native tool errors", async () => {
1472
+ const projector = await createProjector();
1473
+
1474
+ await projector.handleNotification(
1475
+ forCurrentTurn("item/commandExecution/outputDelta", {
1476
+ itemId: "cmd-streamed-failure",
1477
+ delta: "fatal: missing fixture\n",
1478
+ }),
1479
+ );
1480
+ await projector.handleNotification(
1481
+ turnCompleted([
1482
+ {
1483
+ type: "commandExecution",
1484
+ id: "cmd-streamed-failure",
1485
+ command: "pnpm test extensions/codex",
1486
+ cwd: "/workspace",
1487
+ processId: null,
1488
+ source: "agent",
1489
+ status: "failed",
1490
+ commandActions: [],
1491
+ aggregatedOutput: null,
1492
+ exitCode: 1,
1493
+ durationMs: 42,
1494
+ },
1495
+ ]),
1496
+ );
1497
+
1498
+ expect(projector.buildResult(buildEmptyToolTelemetry()).lastToolError).toEqual({
1499
+ toolName: "bash",
1500
+ meta: "run tests (workspace)",
1501
+ error: "fatal: missing fixture",
1502
+ mutatingAction: true,
1503
+ actionFingerprint: JSON.stringify({
1504
+ type: "commandExecution",
1505
+ command: "pnpm test extensions/codex",
1506
+ cwd: "/workspace",
1507
+ }),
1508
+ });
1509
+ });
1510
+
1511
+ it("does not duplicate native tool starts when the snapshot completes a started item", async () => {
1512
+ const onAgentEvent = vi.fn();
1513
+ const trajectoryRecorder = {
1514
+ filePath: "trajectory.jsonl",
1515
+ recordEvent: vi.fn(),
1516
+ flush: vi.fn(async () => undefined),
1517
+ };
1518
+ const projector = await createProjector(
1519
+ { ...(await createParams()), onAgentEvent },
1520
+ { trajectoryRecorder },
1521
+ );
1522
+ const commandItem = {
1523
+ type: "commandExecution",
1524
+ id: "cmd-started",
1525
+ command: "pnpm test extensions/codex",
1526
+ cwd: "/workspace",
1527
+ processId: null,
1528
+ source: "agent",
1529
+ status: "completed",
1530
+ commandActions: [],
1531
+ aggregatedOutput: "ok",
1532
+ exitCode: 0,
1533
+ durationMs: 42,
1534
+ };
1535
+
1536
+ await projector.handleNotification(
1537
+ forCurrentTurn("item/started", {
1538
+ item: { ...commandItem, status: "inProgress", aggregatedOutput: null, exitCode: null },
1539
+ }),
1540
+ );
1541
+ await projector.handleNotification(turnCompleted([commandItem]));
1542
+
1543
+ const toolEvents = onAgentEvent.mock.calls
1544
+ .map((call) => requireRecord(call[0], "agent event"))
1545
+ .filter((event) => event.stream === "tool")
1546
+ .map((event) => requireRecord(event.data, "agent event data"));
1547
+ expect(
1548
+ toolEvents.filter((event) => event.phase === "start" && event.itemId === "cmd-started"),
1549
+ ).toHaveLength(1);
1550
+ expect(
1551
+ toolEvents.filter((event) => event.phase === "result" && event.itemId === "cmd-started"),
1552
+ ).toHaveLength(1);
1553
+ expect(
1554
+ trajectoryRecorder.recordEvent.mock.calls.filter(([type]) => type === "tool.call"),
1555
+ ).toHaveLength(1);
1556
+ expect(
1557
+ trajectoryRecorder.recordEvent.mock.calls.filter(([type]) => type === "tool.result"),
1558
+ ).toHaveLength(1);
1559
+ });
1560
+
1561
+ it("does not synthesize completed progress for running turn completion snapshots", async () => {
1562
+ const onAgentEvent = vi.fn();
1563
+ const projector = await createProjector({ ...(await createParams()), onAgentEvent });
1564
+
1565
+ await projector.handleNotification(
1566
+ turnCompleted([
1567
+ {
1568
+ type: "commandExecution",
1569
+ id: "cmd-running-snapshot",
1570
+ command: "pnpm test extensions/codex",
1571
+ cwd: "/workspace",
1572
+ processId: null,
1573
+ source: "agent",
1574
+ status: "inProgress",
1575
+ commandActions: [],
1576
+ aggregatedOutput: null,
1577
+ exitCode: null,
1578
+ durationMs: null,
1579
+ },
1580
+ ]),
1581
+ );
1582
+
1583
+ const toolEvents = onAgentEvent.mock.calls
1584
+ .map((call) => requireRecord(call[0], "agent event"))
1585
+ .filter((event) => event.stream === "tool")
1586
+ .map((event) => requireRecord(event.data, "agent event data"));
1587
+ expect(toolEvents).toEqual([]);
1588
+ });
1589
+
1590
+ it("does not synthesize progress for stale prior-turn snapshot items", async () => {
1591
+ const onAgentEvent = vi.fn();
1592
+ const projector = await createProjector({ ...(await createParams()), onAgentEvent });
1593
+
1594
+ await projector.handleNotification(
1595
+ turnCompleted([
1596
+ {
1597
+ type: "commandExecution",
1598
+ id: "cmd-prior-turn",
1599
+ turnId: "turn-old",
1600
+ command: "pnpm test extensions/codex",
1601
+ cwd: "/workspace",
1602
+ processId: null,
1603
+ source: "agent",
1604
+ status: "completed",
1605
+ commandActions: [],
1606
+ aggregatedOutput: "ok",
1607
+ exitCode: 0,
1608
+ durationMs: 42,
1609
+ },
1610
+ {
1611
+ type: "commandExecution",
1612
+ id: "cmd-current-turn",
1613
+ turnId: TURN_ID,
1614
+ command: "pnpm test extensions/codex",
1615
+ cwd: "/workspace",
1616
+ processId: null,
1617
+ source: "agent",
1618
+ status: "completed",
1619
+ commandActions: [],
1620
+ aggregatedOutput: "ok",
1621
+ exitCode: 0,
1622
+ durationMs: 42,
1623
+ },
1624
+ ]),
1625
+ );
1626
+
1627
+ const toolEvents = onAgentEvent.mock.calls
1628
+ .map((call) => requireRecord(call[0], "agent event"))
1629
+ .filter((event) => event.stream === "tool")
1630
+ .map((event) => requireRecord(event.data, "agent event data"));
1631
+ expect(toolEvents.map((event) => event.itemId)).toEqual([
1632
+ "cmd-current-turn",
1633
+ "cmd-current-turn",
1634
+ ]);
1635
+ });
1636
+
1637
+ it("orders declined native tool diagnostics after their start event", async () => {
1638
+ const projector = await createProjector();
1639
+ const diagnosticEvents: DiagnosticEventPayload[] = [];
1640
+ const unsubscribe = onInternalDiagnosticEvent((event) => diagnosticEvents.push(event));
1641
+
1642
+ try {
1643
+ await projector.handleNotification(
1644
+ forCurrentTurn("item/started", {
1645
+ item: {
1646
+ type: "commandExecution",
1647
+ id: "cmd-declined",
1648
+ command: "pnpm test extensions/codex",
1649
+ cwd: "/workspace",
1650
+ processId: null,
1651
+ source: "agent",
1652
+ status: "inProgress",
1653
+ commandActions: [],
1654
+ aggregatedOutput: null,
1655
+ exitCode: null,
1656
+ durationMs: null,
1657
+ },
1658
+ }),
1659
+ );
1660
+ await projector.handleNotification(
1661
+ forCurrentTurn("item/completed", {
1662
+ item: {
1663
+ type: "commandExecution",
1664
+ id: "cmd-declined",
1665
+ command: "pnpm test extensions/codex",
1666
+ cwd: "/workspace",
1667
+ processId: null,
1668
+ source: "agent",
1669
+ status: "declined",
1670
+ commandActions: [],
1671
+ aggregatedOutput: null,
1672
+ exitCode: null,
1673
+ durationMs: 1,
1674
+ },
1675
+ }),
1676
+ );
1677
+ await flushDiagnosticEvents();
1678
+ } finally {
1679
+ unsubscribe();
1680
+ }
1681
+
1682
+ const toolDiagnosticEvents = diagnosticEvents.filter(
1683
+ (
1684
+ event,
1685
+ ): event is Extract<
1686
+ DiagnosticEventPayload,
1687
+ {
1688
+ type:
1689
+ | "tool.execution.started"
1690
+ | "tool.execution.completed"
1691
+ | "tool.execution.error"
1692
+ | "tool.execution.blocked";
1693
+ }
1694
+ > => event.type.startsWith("tool.execution."),
1695
+ );
1696
+ expect(
1697
+ toolDiagnosticEvents.map((event) => ({
1698
+ type: event.type,
1699
+ toolName: event.toolName,
1700
+ toolCallId: event.toolCallId,
1701
+ })),
1702
+ ).toEqual([
1703
+ {
1704
+ type: "tool.execution.started",
1705
+ toolName: "bash",
1706
+ toolCallId: "cmd-declined",
1707
+ },
1708
+ {
1709
+ type: "tool.execution.blocked",
1710
+ toolName: "bash",
1711
+ toolCallId: "cmd-declined",
1712
+ },
1713
+ ]);
1714
+ expect(projector.buildResult(buildEmptyToolTelemetry()).lastToolError).toEqual({
1715
+ toolName: "bash",
1716
+ meta: "run tests (workspace)",
1717
+ error: "codex native tool blocked",
1718
+ mutatingAction: true,
1719
+ actionFingerprint: JSON.stringify({
1720
+ type: "commandExecution",
1721
+ command: "pnpm test extensions/codex",
1722
+ cwd: "/workspace",
1723
+ }),
1724
+ });
1725
+ });
1726
+
1727
+ it("clears a recovered declined native tool error", async () => {
1728
+ const projector = await createProjector();
1729
+
1730
+ await projector.handleNotification(
1731
+ forCurrentTurn("item/completed", {
1732
+ item: {
1733
+ type: "commandExecution",
1734
+ id: "cmd-declined",
1735
+ command: "pnpm test extensions/codex",
1736
+ cwd: "/workspace",
1737
+ processId: null,
1738
+ source: "agent",
1739
+ status: "declined",
1740
+ commandActions: [],
1741
+ aggregatedOutput: null,
1742
+ exitCode: null,
1743
+ durationMs: 1,
1744
+ },
1745
+ }),
1746
+ );
1747
+ expect(projector.buildResult(buildEmptyToolTelemetry()).lastToolError).toEqual({
1748
+ toolName: "bash",
1749
+ meta: "run tests (workspace)",
1750
+ error: "codex native tool blocked",
1751
+ mutatingAction: true,
1752
+ actionFingerprint: JSON.stringify({
1753
+ type: "commandExecution",
1754
+ command: "pnpm test extensions/codex",
1755
+ cwd: "/workspace",
1756
+ }),
1757
+ });
1758
+
1759
+ await projector.handleNotification(
1760
+ forCurrentTurn("item/completed", {
1761
+ item: {
1762
+ type: "commandExecution",
1763
+ id: "cmd-recovered",
1764
+ command: "pnpm test extensions/codex",
1765
+ cwd: "/workspace",
1766
+ processId: null,
1767
+ source: "agent",
1768
+ status: "completed",
1769
+ commandActions: [],
1770
+ aggregatedOutput: "ok",
1771
+ exitCode: 0,
1772
+ durationMs: 42,
1773
+ },
1774
+ }),
1775
+ );
1776
+
1777
+ expect(projector.buildResult(buildEmptyToolTelemetry()).lastToolError).toBeUndefined();
1778
+ });
1779
+
1780
+ it("does not clear a declined native tool error with a different action", async () => {
1781
+ const projector = await createProjector();
1782
+
1783
+ await projector.handleNotification(
1784
+ forCurrentTurn("item/completed", {
1785
+ item: {
1786
+ type: "commandExecution",
1787
+ id: "cmd-declined",
1788
+ command: "pnpm test extensions/codex",
1789
+ cwd: "/workspace",
1790
+ processId: null,
1791
+ source: "agent",
1792
+ status: "declined",
1793
+ commandActions: [],
1794
+ aggregatedOutput: null,
1795
+ exitCode: null,
1796
+ durationMs: 1,
1797
+ },
1798
+ }),
1799
+ );
1800
+ await projector.handleNotification(
1801
+ forCurrentTurn("item/completed", {
1802
+ item: {
1803
+ type: "commandExecution",
1804
+ id: "cmd-unrelated-success",
1805
+ command: "pnpm test src/foo.test.ts",
1806
+ cwd: "/workspace",
1807
+ processId: null,
1808
+ source: "agent",
1809
+ status: "completed",
1810
+ commandActions: [],
1811
+ aggregatedOutput: "ok",
1812
+ exitCode: 0,
1813
+ durationMs: 42,
1814
+ },
1815
+ }),
1816
+ );
1817
+
1818
+ expect(projector.buildResult(buildEmptyToolTelemetry()).lastToolError).toEqual({
1819
+ toolName: "bash",
1820
+ meta: "run tests (workspace)",
1821
+ error: "codex native tool blocked",
1822
+ mutatingAction: true,
1823
+ actionFingerprint: JSON.stringify({
1824
+ type: "commandExecution",
1825
+ command: "pnpm test extensions/codex",
1826
+ cwd: "/workspace",
1827
+ }),
1828
+ });
1829
+ });
1830
+
1831
+ it("emits after_tool_call observations for Codex-native tool item completions", async () => {
1832
+ const afterToolCall = vi.fn();
1833
+ initializeGlobalHookRunner(
1834
+ createMockPluginRegistry([{ hookName: "after_tool_call", handler: afterToolCall }]),
1835
+ );
1836
+ const projector = await createProjector({
1837
+ ...(await createParams()),
1838
+ agentId: "main",
1839
+ sessionKey: "agent:main:session-1",
1840
+ });
1841
+
1842
+ await projector.handleNotification(
1843
+ forCurrentTurn("item/started", {
1844
+ item: {
1845
+ type: "commandExecution",
1846
+ id: "cmd-observed",
1847
+ command: "pnpm test extensions/codex",
1848
+ cwd: "/workspace",
1849
+ processId: null,
1850
+ source: "agent",
1851
+ status: "inProgress",
1852
+ commandActions: [],
1853
+ aggregatedOutput: null,
1854
+ exitCode: null,
1855
+ durationMs: null,
1856
+ },
1857
+ }),
1858
+ );
1859
+ await projector.handleNotification(
1860
+ forCurrentTurn("item/completed", {
1861
+ item: {
1862
+ type: "commandExecution",
1863
+ id: "cmd-observed",
1864
+ command: "pnpm test extensions/codex",
1865
+ cwd: "/workspace",
1866
+ processId: null,
1867
+ source: "agent",
1868
+ status: "completed",
1869
+ commandActions: [],
1870
+ aggregatedOutput: "ok",
1871
+ exitCode: 0,
1872
+ durationMs: 42,
1873
+ },
1874
+ }),
1875
+ );
1876
+
1877
+ await vi.waitFor(() => expect(afterToolCall).toHaveBeenCalledTimes(1));
1878
+ const event = requireRecord(
1879
+ mockCallArg(afterToolCall, 0, 0, "after_tool_call event"),
1880
+ "after_tool_call event",
1881
+ );
1882
+ expect(event.toolName).toBe("bash");
1883
+ expect(event.params).toEqual({ command: "pnpm test extensions/codex", cwd: "/workspace" });
1884
+ expect(event.runId).toBe("run-1");
1885
+ expect(event.toolCallId).toBe("cmd-observed");
1886
+ expect(event.result).toEqual({ status: "completed", exitCode: 0, durationMs: 42 });
1887
+ expect(event.durationMs).toBeGreaterThanOrEqual(42);
1888
+ const context = requireRecord(
1889
+ mockCallArg(afterToolCall, 0, 1, "after_tool_call context"),
1890
+ "after_tool_call context",
1891
+ );
1892
+ expect(context.agentId).toBe("main");
1893
+ expect(context.sessionId).toBe("session-1");
1894
+ expect(context.sessionKey).toBe("agent:main:session-1");
1895
+ expect(context.runId).toBe("run-1");
1896
+ expect(context.toolName).toBe("bash");
1897
+ expect(context.toolCallId).toBe("cmd-observed");
1898
+ });
1899
+
1900
+ it("does not duplicate native items already covered by PostToolUse relay", async () => {
1901
+ const afterToolCall = vi.fn();
1902
+ initializeGlobalHookRunner(
1903
+ createMockPluginRegistry([{ hookName: "after_tool_call", handler: afterToolCall }]),
1904
+ );
1905
+ const projector = await createProjector(
1906
+ { ...(await createParams()), sessionKey: "agent:main:session-1" },
1907
+ { nativePostToolUseRelayEnabled: true },
1908
+ );
1909
+
1910
+ await projector.handleNotification(
1911
+ forCurrentTurn("item/completed", {
1912
+ item: {
1913
+ type: "commandExecution",
1914
+ id: "cmd-relayed",
1915
+ command: "pnpm test extensions/codex",
1916
+ cwd: "/workspace",
1917
+ processId: null,
1918
+ source: "agent",
1919
+ status: "completed",
1920
+ commandActions: [],
1921
+ aggregatedOutput: "ok",
1922
+ exitCode: 0,
1923
+ durationMs: 42,
1924
+ },
1925
+ }),
1926
+ );
1927
+ expect(afterToolCall).not.toHaveBeenCalled();
1928
+
1929
+ await projector.handleNotification(
1930
+ forCurrentTurn("item/completed", {
1931
+ item: {
1932
+ type: "webSearch",
1933
+ id: "search-observed",
1934
+ query: "native tool observability",
1935
+ status: "completed",
1936
+ durationMs: 5,
1937
+ },
1938
+ }),
1939
+ );
1940
+
1941
+ await vi.waitFor(() => expect(afterToolCall).toHaveBeenCalledTimes(1));
1942
+ const event = requireRecord(
1943
+ mockCallArg(afterToolCall, 0, 0, "after_tool_call event"),
1944
+ "after_tool_call event",
1945
+ );
1946
+ expect(event.toolName).toBe("web_search");
1947
+ expect(event.params).toEqual({ query: "native tool observability" });
1948
+ expect(event.runId).toBe("run-1");
1949
+ expect(event.toolCallId).toBe("search-observed");
1950
+ expect(event.result).toEqual({ status: "completed" });
1951
+ });
1952
+
1953
+ it("records dynamic OpenClaw tool calls in mirrored transcript snapshots", async () => {
1954
+ const projector = await createProjector();
1955
+
1956
+ projector.recordDynamicToolCall({
1957
+ callId: "call-browser-1",
1958
+ tool: "browser",
1959
+ arguments: { action: "open", url: "http://127.0.0.1:3000" },
1960
+ });
1961
+ projector.recordDynamicToolResult({
1962
+ callId: "call-browser-1",
1963
+ tool: "browser",
1964
+ success: true,
1965
+ contentItems: [{ type: "inputText", text: "opened" }],
1966
+ });
1967
+ await projector.handleNotification(agentMessageDelta("done"));
1968
+
1969
+ const result = projector.buildResult(buildEmptyToolTelemetry());
1970
+
1971
+ expect(result.messagesSnapshot.map((message) => message.role)).toEqual([
1972
+ "user",
1973
+ "assistant",
1974
+ "toolResult",
1975
+ "assistant",
1976
+ ]);
1977
+ const assistant = requireRecord(result.messagesSnapshot[1], "assistant tool call message");
1978
+ expect(assistant.role).toBe("assistant");
1979
+ expect(requireArray(assistant.content, "assistant content")[0]).toEqual({
1980
+ type: "toolCall",
1981
+ id: "call-browser-1",
1982
+ name: "browser",
1983
+ arguments: { action: "open", url: "http://127.0.0.1:3000" },
1984
+ input: { action: "open", url: "http://127.0.0.1:3000" },
1985
+ });
1986
+ const toolResultMessage = requireRecord(result.messagesSnapshot[2], "tool result message");
1987
+ expect(toolResultMessage.role).toBe("toolResult");
1988
+ expect(toolResultMessage.toolCallId).toBe("call-browser-1");
1989
+ expect(toolResultMessage.toolName).toBe("browser");
1990
+ expect(toolResultMessage.isError).toBe(false);
1991
+ const toolResultContent = requireRecord(
1992
+ requireArray(toolResultMessage.content, "tool result content")[0],
1993
+ "tool result content item",
1994
+ );
1995
+ expect(toolResultContent.type).toBe("toolResult");
1996
+ expect(toolResultContent.id).toBe("call-browser-1");
1997
+ expect(toolResultContent.name).toBe("browser");
1998
+ expect(toolResultContent.toolName).toBe("browser");
1999
+ expect(toolResultContent.toolCallId).toBe("call-browser-1");
2000
+ expect(toolResultContent.content).toBe("opened");
2001
+ });
2002
+
2003
+ it("does not mirror Codex-native web searches into transcript snapshots", async () => {
2004
+ const projector = await createProjector();
2005
+
2006
+ await projector.handleNotification(
2007
+ forCurrentTurn("item/completed", {
2008
+ item: {
2009
+ type: "webSearch",
2010
+ id: "search-observed",
2011
+ status: "completed",
2012
+ durationMs: 5,
2013
+ },
2014
+ }),
2015
+ );
2016
+
2017
+ const result = projector.buildResult(buildEmptyToolTelemetry());
2018
+
2019
+ expect(
2020
+ result.messagesSnapshot.some((message) => {
2021
+ const record = message as unknown as Record<string, unknown>;
2022
+ if (record.role === "toolResult") {
2023
+ return true;
2024
+ }
2025
+ const content = Array.isArray(record.content) ? record.content : [];
2026
+ return content.some((entry) => {
2027
+ return (
2028
+ typeof entry === "object" &&
2029
+ entry !== null &&
2030
+ (entry as Record<string, unknown>).type === "toolCall"
2031
+ );
2032
+ });
2033
+ }),
2034
+ ).toBe(false);
2035
+ });
2036
+
2037
+ it("emits verbose summaries for transcript-recorded dynamic tool calls", async () => {
2038
+ const onAgentEvent = vi.fn();
2039
+ const onToolResult = vi.fn();
2040
+ const projector = await createProjector({
2041
+ ...(await createParams()),
2042
+ verboseLevel: "on",
2043
+ onAgentEvent,
2044
+ onToolResult,
2045
+ });
2046
+
2047
+ projector.recordDynamicToolCall({
2048
+ callId: "call-browser-1",
2049
+ tool: "browser",
2050
+ arguments: { action: "open", url: "http://127.0.0.1:3000" },
2051
+ });
2052
+
2053
+ const toolEvents = onAgentEvent.mock.calls.filter(([event]) => {
2054
+ const record = requireRecord(event, "agent event");
2055
+ return record.stream === "tool";
2056
+ });
2057
+ expect(toolEvents).toHaveLength(0);
2058
+ expect(onToolResult).toHaveBeenCalledTimes(1);
2059
+ const payload = mockCallArg(onToolResult, 0, 0, "onToolResult") as { text?: string };
2060
+ expect(payload.text).toContain("Browser");
2061
+ });
2062
+
2063
+ it("does not replay transcript summaries when only tool output is enabled", async () => {
2064
+ const onToolResult = vi.fn();
2065
+ const projector = await createProjector({
2066
+ ...(await createParams()),
2067
+ onToolResult,
2068
+ shouldEmitToolResult: () => false,
2069
+ shouldEmitToolOutput: () => true,
2070
+ });
2071
+
2072
+ projector.recordDynamicToolCall({
2073
+ callId: "call-browser-1",
2074
+ tool: "browser",
2075
+ arguments: { action: "open", url: "http://127.0.0.1:3000" },
2076
+ });
2077
+ projector.recordDynamicToolResult({
2078
+ callId: "call-browser-1",
2079
+ tool: "browser",
2080
+ success: true,
2081
+ contentItems: [{ type: "inputText", text: "opened" }],
2082
+ });
2083
+
2084
+ expect(onToolResult).toHaveBeenCalledTimes(1);
2085
+ const payload = mockCallArg(onToolResult, 0, 0, "onToolResult") as { text?: string };
2086
+ expect(payload.text).toContain("opened");
2087
+ expect(payload.text).toContain("```txt\nopened\n```");
2088
+ });
2089
+
2090
+ it("keeps side-effect evidence for dynamic tools that error after execution", async () => {
2091
+ const projector = await createProjector();
2092
+
2093
+ projector.recordDynamicToolCall({
2094
+ callId: "call-process-kill",
2095
+ tool: "process",
2096
+ arguments: { action: "kill", sessionId: "session-1" },
2097
+ });
2098
+ projector.recordDynamicToolResult({
2099
+ callId: "call-process-kill",
2100
+ tool: "process",
2101
+ success: false,
2102
+ terminalType: "error",
2103
+ sideEffectEvidence: true,
2104
+ contentItems: [{ type: "inputText", text: "process exited" }],
2105
+ });
2106
+
2107
+ const result = projector.buildResult(buildEmptyToolTelemetry());
2108
+
2109
+ expect(result.replayMetadata).toEqual({ hadPotentialSideEffects: true, replaySafe: false });
2110
+ });
2111
+
2112
+ it("does not keep side-effect evidence for pre-execution dynamic tool errors", async () => {
2113
+ const projector = await createProjector();
2114
+
2115
+ projector.recordDynamicToolCall({
2116
+ callId: "call-unknown-message",
2117
+ tool: "message",
2118
+ arguments: { action: "send", text: "hello" },
2119
+ });
2120
+ projector.recordDynamicToolResult({
2121
+ callId: "call-unknown-message",
2122
+ tool: "message",
2123
+ success: false,
2124
+ terminalType: "error",
2125
+ contentItems: [{ type: "inputText", text: "Unknown OpenClaw tool: message" }],
2126
+ });
2127
+
2128
+ const result = projector.buildResult(buildEmptyToolTelemetry());
2129
+
2130
+ expect(result.replayMetadata).toEqual({ hadPotentialSideEffects: false, replaySafe: true });
2131
+ });
2132
+
2133
+ it("does not mark blocked dynamic tools as side-effecting", async () => {
2134
+ const projector = await createProjector();
2135
+
2136
+ projector.recordDynamicToolCall({
2137
+ callId: "call-bash-blocked",
2138
+ tool: "bash",
2139
+ arguments: { command: "touch blocked.txt" },
2140
+ });
2141
+ projector.recordDynamicToolResult({
2142
+ callId: "call-bash-blocked",
2143
+ tool: "bash",
2144
+ success: false,
2145
+ terminalType: "blocked",
2146
+ sideEffectEvidence: true,
2147
+ contentItems: [{ type: "inputText", text: "blocked" }],
2148
+ });
2149
+
2150
+ const result = projector.buildResult(buildEmptyToolTelemetry());
2151
+
2152
+ expect(result.replayMetadata).toEqual({ hadPotentialSideEffects: false, replaySafe: true });
2153
+ });
2154
+
2155
+ it("treats completed native MCP tool calls as side-effect evidence", async () => {
2156
+ const projector = await createProjector();
2157
+
2158
+ await projector.handleNotification({
2159
+ method: "item/completed",
2160
+ params: {
2161
+ threadId: "thread-1",
2162
+ turnId: "turn-1",
2163
+ item: {
2164
+ id: "mcp-1",
2165
+ type: "mcpToolCall",
2166
+ server: "github",
2167
+ tool: "create_issue",
2168
+ status: "completed",
2169
+ arguments: { title: "check replay safety" },
2170
+ },
2171
+ },
2172
+ });
2173
+
2174
+ const result = projector.buildResult(buildEmptyToolTelemetry());
2175
+
2176
+ expect(result.replayMetadata).toEqual({ hadPotentialSideEffects: true, replaySafe: false });
2177
+ });
2178
+
2179
+ it("suppresses transcript progress for message-like tools", async () => {
2180
+ const onAgentEvent = vi.fn();
2181
+ const onToolResult = vi.fn();
2182
+ const projector = await createProjector({
2183
+ ...(await createParams()),
2184
+ verboseLevel: "on",
2185
+ onAgentEvent,
2186
+ onToolResult,
2187
+ });
2188
+
2189
+ projector.recordDynamicToolCall({
2190
+ callId: "call-message-1",
2191
+ tool: "message",
2192
+ arguments: { action: "send", text: "hello" },
2193
+ });
2194
+ projector.recordDynamicToolResult({
2195
+ callId: "call-message-1",
2196
+ tool: "message",
2197
+ success: true,
2198
+ contentItems: [{ type: "inputText", text: "sent" }],
2199
+ });
2200
+
2201
+ const toolEvents = onAgentEvent.mock.calls.filter(([event]) => {
2202
+ const record = requireRecord(event, "agent event");
2203
+ return record.stream === "tool";
2204
+ });
2205
+ expect(toolEvents).toHaveLength(0);
2206
+ expect(onToolResult).not.toHaveBeenCalled();
2207
+ });
2208
+
2209
+ it("does not parse shell command text to suppress transcript progress", async () => {
2210
+ const onAgentEvent = vi.fn();
2211
+ const onToolResult = vi.fn();
2212
+ const projector = await createProjector({
2213
+ ...(await createParams()),
2214
+ verboseLevel: "on",
2215
+ onAgentEvent,
2216
+ onToolResult,
2217
+ });
2218
+
2219
+ projector.recordDynamicToolCall({
2220
+ callId: "call-log-activity-1",
2221
+ tool: "bash",
2222
+ arguments: {
2223
+ command:
2224
+ '/bin/bash -lc \'/home/openclaw/.openclaw/workspace/bin/log_activity.sh "web_search" "Grilled salmon research"\'',
2225
+ cwd: "/workspace",
2226
+ },
2227
+ });
2228
+ projector.recordDynamicToolResult({
2229
+ callId: "call-log-activity-1",
2230
+ tool: "bash",
2231
+ success: true,
2232
+ contentItems: [{ type: "inputText", text: "Logged: [web_search] Grilled salmon research" }],
2233
+ });
2234
+
2235
+ expect(onAgentEvent).not.toHaveBeenCalled();
2236
+ const toolProgressText = onToolResult.mock.calls
2237
+ .map(([payload]) => (payload as { text?: string }).text ?? "")
2238
+ .join("\n");
2239
+ expect(toolProgressText).toContain("log_activity.sh");
2240
+
2241
+ const result = projector.buildResult(buildEmptyToolTelemetry());
2242
+ expect(result.messagesSnapshot.some((message) => message.role === "toolResult")).toBe(true);
2243
+ });
2244
+
2245
+ it("keeps diagnostics for exact message-like native tool items while suppressing progress", async () => {
2246
+ const onAgentEvent = vi.fn();
2247
+ const onToolResult = vi.fn();
2248
+ const projector = await createProjector({
2249
+ ...(await createParams()),
2250
+ verboseLevel: "on",
2251
+ onAgentEvent,
2252
+ onToolResult,
2253
+ });
2254
+ const diagnosticEvents: DiagnosticEventPayload[] = [];
2255
+ const unsubscribe = onInternalDiagnosticEvent((event) => diagnosticEvents.push(event));
2256
+
2257
+ try {
2258
+ await projector.handleNotification(
2259
+ forCurrentTurn("item/started", {
2260
+ item: {
2261
+ type: "mcpToolCall",
2262
+ id: "mcp-message-1",
2263
+ server: null,
2264
+ tool: "message",
2265
+ arguments: { text: "hello" },
2266
+ status: "inProgress",
2267
+ result: null,
2268
+ error: null,
2269
+ durationMs: null,
2270
+ },
2271
+ }),
2272
+ );
2273
+ await projector.handleNotification(
2274
+ forCurrentTurn("item/completed", {
2275
+ item: {
2276
+ type: "mcpToolCall",
2277
+ id: "mcp-message-1",
2278
+ server: null,
2279
+ tool: "message",
2280
+ arguments: { text: "hello" },
2281
+ status: "completed",
2282
+ result: { ok: true },
2283
+ error: null,
2284
+ durationMs: 7,
2285
+ },
2286
+ }),
2287
+ );
2288
+ await flushDiagnosticEvents();
2289
+ } finally {
2290
+ unsubscribe();
2291
+ }
2292
+
2293
+ const toolEvents = onAgentEvent.mock.calls.filter(([event]) => {
2294
+ const record = requireRecord(event, "agent event");
2295
+ return record.stream === "tool";
2296
+ });
2297
+ expect(toolEvents).toHaveLength(0);
2298
+ expect(onToolResult).not.toHaveBeenCalled();
2299
+
2300
+ const toolDiagnosticEvents = diagnosticEvents.filter(
2301
+ (
2302
+ event,
2303
+ ): event is Extract<
2304
+ DiagnosticEventPayload,
2305
+ {
2306
+ type:
2307
+ | "tool.execution.started"
2308
+ | "tool.execution.completed"
2309
+ | "tool.execution.error"
2310
+ | "tool.execution.blocked";
2311
+ }
2312
+ > => event.type.startsWith("tool.execution."),
2313
+ );
2314
+ expect(
2315
+ toolDiagnosticEvents.map((event) => ({
2316
+ type: event.type,
2317
+ toolName: event.toolName,
2318
+ toolCallId: event.toolCallId,
2319
+ durationMs: "durationMs" in event ? event.durationMs : undefined,
2320
+ })),
2321
+ ).toEqual([
2322
+ {
2323
+ type: "tool.execution.started",
2324
+ toolName: "message",
2325
+ toolCallId: "mcp-message-1",
2326
+ durationMs: undefined,
2327
+ },
2328
+ {
2329
+ type: "tool.execution.completed",
2330
+ toolName: "message",
2331
+ toolCallId: "mcp-message-1",
2332
+ durationMs: 7,
2333
+ },
2334
+ ]);
2335
+ });
2336
+
2337
+ it("does not suppress qualified external tools that end with message-like names", async () => {
2338
+ const onAgentEvent = vi.fn();
2339
+ const onToolResult = vi.fn();
2340
+ const projector = await createProjector({
2341
+ ...(await createParams()),
2342
+ verboseLevel: "on",
2343
+ onAgentEvent,
2344
+ onToolResult,
2345
+ });
2346
+
2347
+ await projector.handleNotification(
2348
+ forCurrentTurn("item/started", {
2349
+ item: {
2350
+ type: "mcpToolCall",
2351
+ id: "mcp-email-send-1",
2352
+ server: "email",
2353
+ tool: "send",
2354
+ arguments: { to: "user@example.com" },
2355
+ status: "inProgress",
2356
+ result: null,
2357
+ error: null,
2358
+ durationMs: null,
2359
+ },
2360
+ }),
2361
+ );
2362
+
2363
+ const toolStart = findAgentEvent(onAgentEvent, {
2364
+ stream: "tool",
2365
+ phase: "start",
2366
+ itemId: "mcp-email-send-1",
2367
+ name: "email.send",
2368
+ }).data;
2369
+ expect(toolStart.toolCallId).toBe("mcp-email-send-1");
2370
+ expect(onToolResult).toHaveBeenCalledWith({
2371
+ text: "🧩 Email.send: `user@example.com`",
2372
+ });
2373
+ });
2374
+
2375
+ it("marks declined Codex-native tool results as non-success", async () => {
2376
+ const onAgentEvent = vi.fn();
2377
+ const projector = await createProjector({ ...(await createParams()), onAgentEvent });
2378
+
2379
+ await projector.handleNotification(
2380
+ forCurrentTurn("item/completed", {
2381
+ item: {
2382
+ type: "commandExecution",
2383
+ id: "cmd-declined",
2384
+ command: "pnpm test extensions/codex",
2385
+ cwd: "/workspace",
2386
+ processId: null,
2387
+ source: "agent",
2388
+ status: "declined",
2389
+ commandActions: [],
2390
+ aggregatedOutput: null,
2391
+ exitCode: null,
2392
+ durationMs: null,
2393
+ },
2394
+ }),
2395
+ );
2396
+
2397
+ const itemEnd = findAgentEvent(onAgentEvent, {
2398
+ stream: "item",
2399
+ phase: "end",
2400
+ itemId: "cmd-declined",
2401
+ }).data;
2402
+ expect(itemEnd.kind).toBe("command");
2403
+ expect(itemEnd.name).toBe("bash");
2404
+ expect(itemEnd.status).toBe("blocked");
2405
+ expect(itemEnd.suppressChannelProgress).toBe(true);
2406
+ const toolResult = findAgentEvent(onAgentEvent, {
2407
+ stream: "tool",
2408
+ phase: "result",
2409
+ itemId: "cmd-declined",
2410
+ name: "bash",
2411
+ }).data;
2412
+ expect(toolResult.toolCallId).toBe("cmd-declined");
2413
+ expect(toolResult.status).toBe("blocked");
2414
+ expect(toolResult.isError).toBe(true);
2415
+ });
2416
+
2417
+ it("leaves Codex dynamic tool item progress to item/tool/call normalization", async () => {
2418
+ const onAgentEvent = vi.fn();
2419
+ const projector = await createProjector({ ...(await createParams()), onAgentEvent });
2420
+
2421
+ await projector.handleNotification(
2422
+ forCurrentTurn("item/started", {
2423
+ item: {
2424
+ type: "dynamicToolCall",
2425
+ id: "call-1",
2426
+ namespace: null,
2427
+ tool: "message",
2428
+ arguments: { action: "send" },
2429
+ status: "inProgress",
2430
+ contentItems: null,
2431
+ success: null,
2432
+ durationMs: null,
2433
+ },
2434
+ }),
2435
+ );
2436
+
2437
+ const itemStart = findAgentEvent(onAgentEvent, {
2438
+ stream: "item",
2439
+ phase: "start",
2440
+ name: "message",
2441
+ }).data;
2442
+ expect(itemStart.kind).toBe("tool");
2443
+ expect(itemStart.suppressChannelProgress).toBe(true);
2444
+ const calls = (onAgentEvent as { mock: { calls: unknown[][] } }).mock.calls;
2445
+ const toolStart = calls.some((call) => {
2446
+ const event = requireRecord(call[0], "agent event");
2447
+ if (event.stream !== "tool") {
2448
+ return false;
2449
+ }
2450
+ const data = requireRecord(event.data, "agent event data");
2451
+ return data.phase === "start" && data.name === "message";
2452
+ });
2453
+ expect(toolStart).toBe(false);
2454
+ });
2455
+
2456
+ it("emits verbose tool summaries through onToolResult", async () => {
2457
+ const onToolResult = vi.fn();
2458
+ const projector = await createProjector({
2459
+ ...(await createParams()),
2460
+ verboseLevel: "on",
2461
+ onToolResult,
2462
+ });
2463
+
2464
+ await projector.handleNotification(
2465
+ forCurrentTurn("item/started", {
2466
+ item: {
2467
+ type: "commandExecution",
2468
+ id: "cmd-1",
2469
+ command: "pnpm test extensions/codex",
2470
+ cwd: "/workspace",
2471
+ processId: null,
2472
+ source: "agent",
2473
+ status: "inProgress",
2474
+ commandActions: [],
2475
+ aggregatedOutput: null,
2476
+ exitCode: null,
2477
+ durationMs: null,
2478
+ },
2479
+ }),
2480
+ );
2481
+
2482
+ expect(onToolResult).toHaveBeenCalledTimes(1);
2483
+ expect(onToolResult).toHaveBeenCalledWith({
2484
+ text: "🛠️ `run tests (workspace)`",
2485
+ });
2486
+ });
2487
+
2488
+ it("can emit raw verbose tool summaries through onToolResult", async () => {
2489
+ const onToolResult = vi.fn();
2490
+ const projector = await createProjector({
2491
+ ...(await createParams()),
2492
+ verboseLevel: "on",
2493
+ toolProgressDetail: "raw",
2494
+ onToolResult,
2495
+ });
2496
+
2497
+ await projector.handleNotification(
2498
+ forCurrentTurn("item/started", {
2499
+ item: {
2500
+ type: "commandExecution",
2501
+ id: "cmd-1",
2502
+ command: "pnpm test extensions/codex",
2503
+ cwd: "/workspace",
2504
+ processId: null,
2505
+ source: "agent",
2506
+ status: "inProgress",
2507
+ commandActions: [],
2508
+ aggregatedOutput: null,
2509
+ exitCode: null,
2510
+ durationMs: null,
2511
+ },
2512
+ }),
2513
+ );
2514
+
2515
+ expect(onToolResult).toHaveBeenCalledWith({
2516
+ text: "🛠️ `` run tests (workspace), `pnpm test extensions/codex` ``",
2517
+ });
2518
+ });
2519
+
2520
+ it("redacts secrets in verbose command summaries", async () => {
2521
+ const onToolResult = vi.fn();
2522
+ const projector = await createProjector({
2523
+ ...(await createParams()),
2524
+ verboseLevel: "on",
2525
+ toolProgressDetail: "raw",
2526
+ onToolResult,
2527
+ });
2528
+
2529
+ await projector.handleNotification(
2530
+ forCurrentTurn("item/started", {
2531
+ item: {
2532
+ type: "commandExecution",
2533
+ id: "cmd-1",
2534
+ command: "OPENAI_API_KEY=sk-1234567890abcdefZZZZ pnpm test",
2535
+ cwd: "/workspace",
2536
+ processId: null,
2537
+ source: "agent",
2538
+ status: "inProgress",
2539
+ commandActions: [],
2540
+ aggregatedOutput: null,
2541
+ exitCode: null,
2542
+ durationMs: null,
2543
+ },
2544
+ }),
2545
+ );
2546
+
2547
+ const text = (mockCallArg(onToolResult, 0, 0, "onToolResult") as { text?: string }).text;
2548
+ expect(text).toContain("OPENAI_API_KEY=*** pnpm test");
2549
+ expect(text).not.toContain("sk-1234567890abcdefZZZZ");
2550
+ });
2551
+
2552
+ it("uses argument details instead of lifecycle status in verbose tool summaries", async () => {
2553
+ const onToolResult = vi.fn();
2554
+ const projector = await createProjector({
2555
+ ...(await createParams()),
2556
+ verboseLevel: "on",
2557
+ onToolResult,
2558
+ });
2559
+
2560
+ await projector.handleNotification(
2561
+ forCurrentTurn("item/started", {
2562
+ item: {
2563
+ type: "dynamicToolCall",
2564
+ id: "tool-1",
2565
+ namespace: null,
2566
+ tool: "lcm_grep",
2567
+ arguments: { query: "inProgress text" },
2568
+ status: "inProgress",
2569
+ contentItems: null,
2570
+ success: null,
2571
+ durationMs: null,
2572
+ },
2573
+ }),
2574
+ );
2575
+
2576
+ expect(onToolResult).toHaveBeenCalledTimes(1);
2577
+ expect(onToolResult).toHaveBeenCalledWith({
2578
+ text: "🧩 Lcm Grep: `inProgress text`",
2579
+ });
2580
+ });
2581
+
2582
+ it("emits completed tool output only when verbose full is enabled", async () => {
2583
+ const onToolResult = vi.fn();
2584
+ const projector = await createProjector({
2585
+ ...(await createParams()),
2586
+ verboseLevel: "full",
2587
+ onToolResult,
2588
+ });
2589
+
2590
+ await projector.handleNotification(
2591
+ turnCompleted([
2592
+ {
2593
+ type: "dynamicToolCall",
2594
+ id: "tool-1",
2595
+ namespace: null,
2596
+ tool: "read",
2597
+ arguments: { path: "README.md" },
2598
+ status: "completed",
2599
+ contentItems: [{ type: "inputText", text: "file contents" }],
2600
+ success: true,
2601
+ durationMs: 12,
2602
+ },
2603
+ ]),
2604
+ );
2605
+
2606
+ expect(onToolResult).toHaveBeenCalledTimes(2);
2607
+ expect(onToolResult).toHaveBeenNthCalledWith(1, {
2608
+ text: "📖 Read: `from README.md`",
2609
+ });
2610
+ expect(onToolResult).toHaveBeenNthCalledWith(2, {
2611
+ text: "📖 Read: `from README.md`\n```txt\nfile contents\n```",
2612
+ });
2613
+ });
2614
+
2615
+ it("marks failed completed tool output as error progress", async () => {
2616
+ const onToolResult = vi.fn();
2617
+ const projector = await createProjector({
2618
+ ...(await createParams()),
2619
+ verboseLevel: "full",
2620
+ onToolResult,
2621
+ });
2622
+
2623
+ await projector.handleNotification(
2624
+ turnCompleted([
2625
+ {
2626
+ type: "dynamicToolCall",
2627
+ id: "tool-1",
2628
+ namespace: null,
2629
+ tool: "bash",
2630
+ arguments: { command: "ls /tmp/missing" },
2631
+ status: "failed",
2632
+ contentItems: [{ type: "inputText", text: "No such file or directory" }],
2633
+ success: false,
2634
+ durationMs: 12,
2635
+ },
2636
+ ]),
2637
+ );
2638
+
2639
+ expect(onToolResult).toHaveBeenNthCalledWith(2, {
2640
+ text: "🛠️ `list files in /tmp/missing`\n```txt\nNo such file or directory\n```",
2641
+ isError: true,
2642
+ });
2643
+ });
2644
+
2645
+ it("uses a safe markdown fence for verbose tool output", async () => {
2646
+ const onToolResult = vi.fn();
2647
+ const projector = await createProjector({
2648
+ ...(await createParams()),
2649
+ verboseLevel: "full",
2650
+ onToolResult,
2651
+ });
2652
+
2653
+ await projector.handleNotification(
2654
+ turnCompleted([
2655
+ {
2656
+ type: "dynamicToolCall",
2657
+ id: "tool-1",
2658
+ namespace: null,
2659
+ tool: "read",
2660
+ arguments: { path: "README.md" },
2661
+ status: "completed",
2662
+ contentItems: [{ type: "inputText", text: "line\n```\nMEDIA:/tmp/secret.png" }],
2663
+ success: true,
2664
+ durationMs: 12,
2665
+ },
2666
+ ]),
2667
+ );
2668
+
2669
+ expect(onToolResult).toHaveBeenNthCalledWith(2, {
2670
+ text: "📖 Read: `from README.md`\n````txt\nline\n```\nMEDIA:/tmp/secret.png\n````",
2671
+ });
2672
+ });
2673
+
2674
+ it("bounds streamed verbose tool output", async () => {
2675
+ const onToolResult = vi.fn();
2676
+ const projector = await createProjector({
2677
+ ...(await createParams()),
2678
+ verboseLevel: "full",
2679
+ onToolResult,
2680
+ });
2681
+
2682
+ for (let i = 0; i < 25; i += 1) {
2683
+ await projector.handleNotification(
2684
+ forCurrentTurn("item/commandExecution/outputDelta", {
2685
+ itemId: "cmd-1",
2686
+ delta: `line ${i}\n`,
2687
+ }),
2688
+ );
2689
+ }
2690
+ await projector.handleNotification(
2691
+ turnCompleted([
2692
+ {
2693
+ type: "commandExecution",
2694
+ id: "cmd-1",
2695
+ command: "pnpm test",
2696
+ cwd: "/workspace",
2697
+ processId: null,
2698
+ source: "agent",
2699
+ status: "completed",
2700
+ commandActions: [],
2701
+ aggregatedOutput: "final output should not duplicate streamed output",
2702
+ exitCode: 0,
2703
+ durationMs: 12,
2704
+ },
2705
+ ]),
2706
+ );
2707
+
2708
+ expect(onToolResult).toHaveBeenCalledTimes(21);
2709
+ const truncatedOutput = mockCallArg(onToolResult, 19, 0, "onToolResult") as {
2710
+ text?: string;
2711
+ };
2712
+ expect(truncatedOutput.text).toContain("...(truncated)...");
2713
+ expect(JSON.stringify(onToolResult.mock.calls)).not.toContain(
2714
+ "final output should not duplicate",
2715
+ );
2716
+ });
2717
+
2718
+ it("continues projecting turn completion when an event consumer throws", async () => {
2719
+ const onAgentEvent = vi.fn(() => {
2720
+ throw new Error("consumer failed");
2721
+ });
2722
+ const projector = await createProjector({
2723
+ ...(await createParams()),
2724
+ onAgentEvent,
2725
+ });
2726
+
2727
+ await expect(
2728
+ projector.handleNotification(
2729
+ turnCompleted([
2730
+ { type: "plan", id: "plan-1", text: "step one\nstep two" },
2731
+ { type: "agentMessage", id: "msg-1", text: "final answer" },
2732
+ ]),
2733
+ ),
2734
+ ).resolves.toBeUndefined();
2735
+
2736
+ const result = projector.buildResult(buildEmptyToolTelemetry());
2737
+
2738
+ expect(findAgentEvent(onAgentEvent, { stream: "plan" }).data.steps).toEqual([
2739
+ "step one",
2740
+ "step two",
2741
+ ]);
2742
+ expect(result.assistantTexts).toEqual(["final answer"]);
2743
+ expect(JSON.stringify(result.messagesSnapshot)).toContain("Codex plan");
2744
+ });
2745
+
2746
+ it("fires before_compaction and after_compaction hooks for codex compaction items", async () => {
2747
+ const { projector, beforeCompaction, afterCompaction } = await createProjectorWithHooks();
2748
+ const openSpy = vi.spyOn(SessionManager, "open");
2749
+
2750
+ await projector.handleNotification(
2751
+ forCurrentTurn("item/started", {
2752
+ item: { type: "contextCompaction", id: "compact-1" },
2753
+ }),
2754
+ );
2755
+ await projector.handleNotification(
2756
+ forCurrentTurn("item/completed", {
2757
+ item: { type: "contextCompaction", id: "compact-1" },
2758
+ }),
2759
+ );
2760
+ expect(openSpy).not.toHaveBeenCalled();
2761
+
2762
+ const beforePayload = requireRecord(
2763
+ mockCallArg(beforeCompaction, 0, 0, "beforeCompaction"),
2764
+ "before payload",
2765
+ );
2766
+ expect(beforePayload.messageCount).toBe(1);
2767
+ expect(String(beforePayload.sessionFile)).toContain("session.jsonl");
2768
+ const beforeMessages = requireArray(beforePayload.messages, "before messages");
2769
+ expect(requireRecord(beforeMessages[0], "before message").role).toBe("assistant");
2770
+ const beforeContext = requireRecord(
2771
+ mockCallArg(beforeCompaction, 0, 1, "beforeCompaction"),
2772
+ "before context",
2773
+ );
2774
+ expect(beforeContext.runId).toBe("run-1");
2775
+ expect(beforeContext.sessionId).toBe("session-1");
2776
+ const afterPayload = requireRecord(
2777
+ mockCallArg(afterCompaction, 0, 0, "afterCompaction"),
2778
+ "after payload",
2779
+ );
2780
+ expect(afterPayload.messageCount).toBe(1);
2781
+ expect(afterPayload.compactedCount).toBe(-1);
2782
+ expect(String(afterPayload.sessionFile)).toContain("session.jsonl");
2783
+ const afterContext = requireRecord(
2784
+ mockCallArg(afterCompaction, 0, 1, "afterCompaction"),
2785
+ "after context",
2786
+ );
2787
+ expect(afterContext.runId).toBe("run-1");
2788
+ expect(afterContext.sessionId).toBe("session-1");
2789
+ });
2790
+
2791
+ it("projects codex hook started and completed notifications into agent events", async () => {
2792
+ const onAgentEvent = vi.fn();
2793
+ const params = await createParams();
2794
+ const projector = await createProjector({ ...params, onAgentEvent });
2795
+
2796
+ await projector.handleNotification(
2797
+ forCurrentTurn("hook/started", {
2798
+ run: {
2799
+ id: "hook-1",
2800
+ eventName: "preToolUse",
2801
+ handlerType: "command",
2802
+ executionMode: "sync",
2803
+ scope: "turn",
2804
+ source: "project",
2805
+ sourcePath: "/repo/.codex/hooks.json",
2806
+ status: "running",
2807
+ statusMessage: null,
2808
+ entries: [],
2809
+ },
2810
+ }),
2811
+ );
2812
+ await projector.handleNotification(
2813
+ forCurrentTurn("hook/completed", {
2814
+ run: {
2815
+ id: "hook-1",
2816
+ eventName: "preToolUse",
2817
+ handlerType: "command",
2818
+ executionMode: "sync",
2819
+ scope: "turn",
2820
+ source: "project",
2821
+ sourcePath: "/repo/.codex/hooks.json",
2822
+ status: "blocked",
2823
+ statusMessage: "blocked by hook",
2824
+ durationMs: 42,
2825
+ entries: [{ kind: "stderr", text: "blocked" }],
2826
+ },
2827
+ }),
2828
+ );
2829
+
2830
+ const started = findAgentEvent(onAgentEvent, {
2831
+ stream: "codex_app_server.hook",
2832
+ phase: "started",
2833
+ }).data;
2834
+ expect(started.threadId).toBe(THREAD_ID);
2835
+ expect(started.turnId).toBe(TURN_ID);
2836
+ expect(started.hookRunId).toBe("hook-1");
2837
+ expect(started.eventName).toBe("preToolUse");
2838
+ expect(started.status).toBe("running");
2839
+ const completed = findAgentEvent(onAgentEvent, {
2840
+ stream: "codex_app_server.hook",
2841
+ phase: "completed",
2842
+ }).data;
2843
+ expect(completed.hookRunId).toBe("hook-1");
2844
+ expect(completed.status).toBe("blocked");
2845
+ expect(completed.statusMessage).toBe("blocked by hook");
2846
+ expect(completed.durationMs).toBe(42);
2847
+ expect(completed.entries).toEqual([{ kind: "stderr", text: "blocked" }]);
2848
+ });
2849
+
2850
+ it("projects thread-scoped codex hook notifications that omit a turn id", async () => {
2851
+ const onAgentEvent = vi.fn();
2852
+ const params = await createParams();
2853
+ const projector = await createProjector({ ...params, onAgentEvent });
2854
+
2855
+ await projector.handleNotification({
2856
+ method: "hook/started",
2857
+ params: {
2858
+ threadId: THREAD_ID,
2859
+ turnId: null,
2860
+ run: {
2861
+ id: "hook-thread-1",
2862
+ eventName: "sessionStart",
2863
+ handlerType: "command",
2864
+ executionMode: "sync",
2865
+ scope: "thread",
2866
+ source: "project",
2867
+ sourcePath: "/repo/.codex/hooks.json",
2868
+ status: "running",
2869
+ statusMessage: null,
2870
+ entries: [],
2871
+ },
2872
+ },
2873
+ });
2874
+
2875
+ const started = findAgentEvent(onAgentEvent, {
2876
+ stream: "codex_app_server.hook",
2877
+ phase: "started",
2878
+ }).data;
2879
+ expect(started.threadId).toBe(THREAD_ID);
2880
+ expect(started.turnId).toBeNull();
2881
+ expect(started.hookRunId).toBe("hook-thread-1");
2882
+ expect(started.eventName).toBe("sessionStart");
2883
+ expect(started.scope).toBe("thread");
2884
+ });
2885
+ });