@1agh/maude 1.0.11 → 1.3.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 (567) hide show
  1. package/README.md +3 -1
  2. package/apps/studio/acp/bridge.ts +18 -13
  3. package/apps/studio/acp/index.ts +43 -7
  4. package/apps/studio/acp/transcript-io.ts +394 -0
  5. package/apps/studio/acp/transcript.ts +103 -38
  6. package/apps/studio/annotations-layer.tsx +191 -116
  7. package/apps/studio/annotations-model.ts +39 -0
  8. package/apps/studio/annotations-sync.ts +50 -0
  9. package/apps/studio/api.ts +821 -55
  10. package/apps/studio/bin/_import-figma.mjs +57 -11
  11. package/apps/studio/bin/_seed-fixture.mjs +163 -0
  12. package/apps/studio/bin/annotate.mjs +3 -1
  13. package/apps/studio/bin/server-up.sh +17 -2
  14. package/apps/studio/canvas-edit.ts +391 -7
  15. package/apps/studio/canvas-imports.ts +89 -0
  16. package/apps/studio/canvas-lib.tsx +82 -18
  17. package/apps/studio/canvas-notice-message.ts +16 -0
  18. package/apps/studio/canvas-notifications.tsx +19 -0
  19. package/apps/studio/canvas-shell.tsx +57 -6
  20. package/apps/studio/client/app.jsx +943 -248
  21. package/apps/studio/client/apply-edit-request.ts +55 -0
  22. package/apps/studio/client/export-center.jsx +125 -67
  23. package/apps/studio/client/github.js +38 -1
  24. package/apps/studio/client/index-loader.ts +76 -0
  25. package/apps/studio/client/panels/CloudBar.jsx +52 -6
  26. package/apps/studio/client/panels/GitPanel.jsx +103 -0
  27. package/apps/studio/client/panels/OnboardingWizard.jsx +36 -7
  28. package/apps/studio/client/panels/RepoBranchSwitcher.jsx +92 -27
  29. package/apps/studio/client/panels/SourceConflictPanel.jsx +166 -0
  30. package/apps/studio/client/panels/SyncPanel.jsx +238 -4
  31. package/apps/studio/client/panels/TeamProjects.jsx +376 -0
  32. package/apps/studio/client/panels/file-deep-link-dialog.jsx +54 -0
  33. package/apps/studio/client/photo-knobs.jsx +3 -0
  34. package/apps/studio/client/report-bug.jsx +19 -1
  35. package/apps/studio/client/share-dialog.jsx +91 -0
  36. package/apps/studio/client/share-link.js +94 -0
  37. package/apps/studio/client/styles/3-shell-maude.css +69 -18
  38. package/apps/studio/client/styles/4-components.css +44 -1
  39. package/apps/studio/client/tree-row-menu.jsx +48 -3
  40. package/apps/studio/client/whats-new.jsx +26 -32
  41. package/apps/studio/cloud/endpoints.ts +132 -0
  42. package/apps/studio/collab/index.ts +4 -0
  43. package/apps/studio/collab/persistence.ts +137 -17
  44. package/apps/studio/collab/registry.ts +19 -21
  45. package/apps/studio/collab/room.ts +13 -2
  46. package/apps/studio/context.ts +59 -0
  47. package/apps/studio/debug-bundle.ts +13 -0
  48. package/apps/studio/dist/client.bundle.js +1911 -1879
  49. package/apps/studio/dist/runtime/.min-sizes.json +1 -0
  50. package/apps/studio/dist/runtime/sonner.js +1 -0
  51. package/apps/studio/dist/styles.css +1 -1
  52. package/apps/studio/export-dialog.tsx +54 -0
  53. package/apps/studio/exporters/degraded.ts +19 -3
  54. package/apps/studio/exporters/ghostscript.ts +194 -0
  55. package/apps/studio/exporters/pdf-fonts.ts +261 -0
  56. package/apps/studio/exporters/pdf.ts +152 -3
  57. package/apps/studio/exporters/remote.ts +36 -1
  58. package/apps/studio/fs-watch.ts +45 -1
  59. package/apps/studio/http.ts +296 -19
  60. package/apps/studio/inspect.ts +19 -0
  61. package/apps/studio/managed-projects.ts +150 -0
  62. package/apps/studio/mem.ts +45 -9
  63. package/apps/studio/notifications.tsx +292 -0
  64. package/apps/studio/runtime-bundle.ts +2 -0
  65. package/apps/studio/server.ts +69 -6
  66. package/apps/studio/sync/accepted-cold-start.ts +225 -0
  67. package/apps/studio/sync/accepted-link.ts +320 -0
  68. package/apps/studio/sync/action-stage.ts +343 -0
  69. package/apps/studio/sync/agent.ts +104 -45
  70. package/apps/studio/sync/asset-push.ts +1 -47
  71. package/apps/studio/sync/cell-file-events.ts +3 -0
  72. package/apps/studio/sync/codec.ts +192 -34
  73. package/apps/studio/sync/cold-start-apply.ts +7 -0
  74. package/apps/studio/sync/cold-start.ts +118 -22
  75. package/apps/studio/sync/comment-identity.ts +68 -0
  76. package/apps/studio/sync/connection-state.ts +15 -1
  77. package/apps/studio/sync/ctl-provider.ts +15 -2
  78. package/apps/studio/sync/document-discovery.ts +81 -0
  79. package/apps/studio/sync/file-ledger.ts +117 -0
  80. package/apps/studio/sync/file-membership.ts +10 -2
  81. package/apps/studio/sync/file-plane.ts +926 -47
  82. package/apps/studio/sync/index.ts +1786 -36
  83. package/apps/studio/sync/journal-client.ts +13 -1
  84. package/apps/studio/sync/migrate-seed.ts +150 -22
  85. package/apps/studio/sync/poke.ts +4 -2
  86. package/apps/studio/sync/presentation.ts +311 -5
  87. package/apps/studio/sync/projection.ts +820 -23
  88. package/apps/studio/sync/repeated-module.ts +73 -0
  89. package/apps/studio/sync/retry-after.ts +169 -0
  90. package/apps/studio/sync/revision-barrier.ts +129 -0
  91. package/apps/studio/sync/seed-progress.ts +218 -0
  92. package/apps/studio/sync/seed-repair.ts +46 -0
  93. package/apps/studio/sync/source-merge.ts +100 -0
  94. package/apps/studio/sync/source-ops.ts +289 -0
  95. package/apps/studio/sync/source-recovery.ts +70 -0
  96. package/apps/studio/sync/source-validation.ts +56 -0
  97. package/apps/studio/sync/status.ts +201 -6
  98. package/apps/studio/sync/transaction-client.ts +460 -0
  99. package/apps/studio/sync/transport-error.ts +111 -0
  100. package/apps/studio/sync/writer-registry.ts +236 -0
  101. package/apps/studio/text-caret.ts +35 -0
  102. package/apps/studio/undo-hud.tsx +9 -87
  103. package/apps/studio/use-canvas-media-drop.tsx +4 -39
  104. package/apps/studio/whats-new.json +173 -0
  105. package/cli/bin/maude.mjs +2 -0
  106. package/cli/commands/codex.mjs +191 -0
  107. package/cli/commands/harness.mjs +1051 -0
  108. package/cli/commands/help.mjs +13 -0
  109. package/cli/lib/design-link.mjs +190 -2
  110. package/cli/lib/harness/capabilities.mjs +302 -0
  111. package/cli/lib/harness/codex-runtime.mjs +892 -0
  112. package/cli/lib/harness/compatibility.mjs +27 -0
  113. package/cli/lib/harness/discover-claude.mjs +1393 -0
  114. package/cli/lib/harness/managed-state.mjs +1296 -0
  115. package/cli/lib/harness/model.mjs +86 -0
  116. package/cli/lib/harness/secrets.mjs +390 -0
  117. package/cli/lib/harness/targets/codex.mjs +1141 -0
  118. package/cli/lib/harness/targets/opencode.mjs +807 -0
  119. package/cli/lib/harness/transaction.mjs +1099 -0
  120. package/cli/lib/hubs-config.mjs +35 -8
  121. package/cli/templates/harness/opencode/maude-projector.ts +58 -0
  122. package/docs/harness-capability-matrix.md +125 -0
  123. package/docs/harness-environment-projection.md +95 -0
  124. package/package.json +33 -9
  125. package/plugins/design/dependencies.json +3 -3
  126. package/plugins/design/templates/_shell.html +47 -0
  127. package/plugins/flow/.claude-plugin/config.schema.json +8 -0
  128. package/plugins/flow/dependencies.json +3 -3
  129. package/apps/studio/.ai/cache/_stats.json +0 -7
  130. package/apps/studio/bin/_agent-browser-safe.test.mjs +0 -165
  131. package/apps/studio/bin/_curl-local.test.mjs +0 -280
  132. package/apps/studio/bin/_pw-launch.test.mjs +0 -90
  133. package/apps/studio/bin/_smart-frames.test.mjs +0 -195
  134. package/apps/studio/bin/_transcribe.test.mjs +0 -80
  135. package/apps/studio/bun.lock +0 -928
  136. package/apps/studio/draw/test/animate.test.ts +0 -172
  137. package/apps/studio/draw/test/brush.test.ts +0 -213
  138. package/apps/studio/draw/test/composition.test.ts +0 -141
  139. package/apps/studio/draw/test/geometry.test.ts +0 -199
  140. package/apps/studio/draw/test/gradient.test.ts +0 -167
  141. package/apps/studio/draw/test/layout.test.ts +0 -120
  142. package/apps/studio/draw/test/morph.test.ts +0 -107
  143. package/apps/studio/draw/test/optimize.test.ts +0 -40
  144. package/apps/studio/draw/test/palette.test.ts +0 -123
  145. package/apps/studio/draw/test/primitives.test.ts +0 -129
  146. package/apps/studio/draw/test/serialize-animate.test.ts +0 -142
  147. package/apps/studio/draw/test/serialize.test.ts +0 -105
  148. package/apps/studio/figma/assets.test.ts +0 -464
  149. package/apps/studio/figma/client.test.ts +0 -395
  150. package/apps/studio/figma/codegen-client.test.ts +0 -276
  151. package/apps/studio/figma/codegen-fonts.test.ts +0 -103
  152. package/apps/studio/figma/codegen-values.test.ts +0 -179
  153. package/apps/studio/figma/comments-to-strokes.test.ts +0 -194
  154. package/apps/studio/figma/fig-decode.test.ts +0 -788
  155. package/apps/studio/figma/fig-differential.test.ts +0 -182
  156. package/apps/studio/figma/fig-translator.test.ts +0 -192
  157. package/apps/studio/figma/fig-vector.test.ts +0 -113
  158. package/apps/studio/figma/from-codegen.test.ts +0 -408
  159. package/apps/studio/figma/sanitize.test.ts +0 -325
  160. package/apps/studio/figma/tailwind-map.test.ts +0 -142
  161. package/apps/studio/figma/to-artboard.test.ts +0 -808
  162. package/apps/studio/figma/to-render.test.ts +0 -180
  163. package/apps/studio/figma/to-strokes-roundtrip.test.ts +0 -152
  164. package/apps/studio/figma/to-strokes.test.ts +0 -705
  165. package/apps/studio/figma/to-tokens.test.ts +0 -321
  166. package/apps/studio/figma/url.test.ts +0 -167
  167. package/apps/studio/footage/schema.test.ts +0 -287
  168. package/apps/studio/generation/adapters/groq.test.ts +0 -80
  169. package/apps/studio/generation/audio-library.test.ts +0 -95
  170. package/apps/studio/generation/captions.test.ts +0 -78
  171. package/apps/studio/generation/elevenlabs.test.ts +0 -223
  172. package/apps/studio/generation/gemini.test.ts +0 -350
  173. package/apps/studio/generation/jobs.test.ts +0 -128
  174. package/apps/studio/generation/keys.test.ts +0 -75
  175. package/apps/studio/generation/prefs.test.ts +0 -95
  176. package/apps/studio/generation/types.test.ts +0 -123
  177. package/apps/studio/generation/whisper-models.test.ts +0 -179
  178. package/apps/studio/test/_helpers.ts +0 -108
  179. package/apps/studio/test/acp-activity-endpoint.test.ts +0 -183
  180. package/apps/studio/test/acp-activity.test.ts +0 -154
  181. package/apps/studio/test/acp-ai-activity.test.ts +0 -182
  182. package/apps/studio/test/acp-attachment-serve.test.ts +0 -74
  183. package/apps/studio/test/acp-bootstrap-brief.test.ts +0 -178
  184. package/apps/studio/test/acp-branch-guard.test.ts +0 -123
  185. package/apps/studio/test/acp-bridge-lifetime.test.ts +0 -475
  186. package/apps/studio/test/acp-bridge.test.ts +0 -545
  187. package/apps/studio/test/acp-capabilities.test.ts +0 -123
  188. package/apps/studio/test/acp-caps-bridge.test.ts +0 -279
  189. package/apps/studio/test/acp-commands.test.ts +0 -119
  190. package/apps/studio/test/acp-elicitation-bridge.test.ts +0 -480
  191. package/apps/studio/test/acp-elicitation.test.ts +0 -251
  192. package/apps/studio/test/acp-env.test.ts +0 -95
  193. package/apps/studio/test/acp-mode-banner.test.ts +0 -45
  194. package/apps/studio/test/acp-origin-gate.test.ts +0 -160
  195. package/apps/studio/test/acp-permission-prompt.test.ts +0 -251
  196. package/apps/studio/test/acp-permission.test.ts +0 -278
  197. package/apps/studio/test/acp-plugin-bootstrap.test.ts +0 -89
  198. package/apps/studio/test/acp-session-allowed-tools.test.ts +0 -235
  199. package/apps/studio/test/acp-session-plugins.test.ts +0 -209
  200. package/apps/studio/test/acp-toolgroup.test.ts +0 -76
  201. package/apps/studio/test/acp-transcript-view.test.ts +0 -71
  202. package/apps/studio/test/acp-transcript.test.ts +0 -238
  203. package/apps/studio/test/acp-usage-bridge.test.ts +0 -141
  204. package/apps/studio/test/acp-usage.test.ts +0 -143
  205. package/apps/studio/test/acp-write-gate.test.ts +0 -323
  206. package/apps/studio/test/acp-write-scope.test.ts +0 -533
  207. package/apps/studio/test/active-state.test.ts +0 -186
  208. package/apps/studio/test/activity.test.ts +0 -189
  209. package/apps/studio/test/ai-activity.test.ts +0 -113
  210. package/apps/studio/test/annotate-write.test.ts +0 -1074
  211. package/apps/studio/test/annotation-strokes-command.test.ts +0 -166
  212. package/apps/studio/test/annotations-align.test.ts +0 -88
  213. package/apps/studio/test/annotations-api.test.ts +0 -145
  214. package/apps/studio/test/annotations-bindings.test.ts +0 -262
  215. package/apps/studio/test/annotations-draw-modifiers.test.ts +0 -206
  216. package/apps/studio/test/annotations-groups.test.ts +0 -231
  217. package/apps/studio/test/annotations-layer.test.ts +0 -1011
  218. package/apps/studio/test/annotations-roundtrip.test.ts +0 -663
  219. package/apps/studio/test/annotations-snap.test.ts +0 -135
  220. package/apps/studio/test/annotations-text-format.test.ts +0 -396
  221. package/apps/studio/test/artboard-activity-overlay.test.tsx +0 -56
  222. package/apps/studio/test/artboard-guides-overlay.test.tsx +0 -152
  223. package/apps/studio/test/artboard-hug-commit.test.ts +0 -36
  224. package/apps/studio/test/artboard-kinds.test.tsx +0 -83
  225. package/apps/studio/test/artboard-selection-attrs.test.ts +0 -69
  226. package/apps/studio/test/asset-api.test.ts +0 -149
  227. package/apps/studio/test/assets-s3.test.ts +0 -249
  228. package/apps/studio/test/binary-smoke.test.ts +0 -47
  229. package/apps/studio/test/boot-self-heal.test.ts +0 -87
  230. package/apps/studio/test/browse-posture.test.tsx +0 -107
  231. package/apps/studio/test/bundle-smoke.test.ts +0 -46
  232. package/apps/studio/test/camera-reveal.test.tsx +0 -173
  233. package/apps/studio/test/canvas-artifacts.test.ts +0 -125
  234. package/apps/studio/test/canvas-build-sandbox.test.ts +0 -69
  235. package/apps/studio/test/canvas-build.test.ts +0 -141
  236. package/apps/studio/test/canvas-create-api.test.ts +0 -676
  237. package/apps/studio/test/canvas-cursors.test.ts +0 -164
  238. package/apps/studio/test/canvas-edit.test.ts +0 -470
  239. package/apps/studio/test/canvas-freshness-deps.test.ts +0 -64
  240. package/apps/studio/test/canvas-header.test.ts +0 -127
  241. package/apps/studio/test/canvas-hide-chrome.test.ts +0 -88
  242. package/apps/studio/test/canvas-hmr-runtime.test.tsx +0 -114
  243. package/apps/studio/test/canvas-lib-inline.test.ts +0 -146
  244. package/apps/studio/test/canvas-lib-motion.test.ts +0 -131
  245. package/apps/studio/test/canvas-lib-resolver.test.ts +0 -111
  246. package/apps/studio/test/canvas-list-watch.test.ts +0 -388
  247. package/apps/studio/test/canvas-media-drop.test.ts +0 -187
  248. package/apps/studio/test/canvas-meta-api.test.ts +0 -707
  249. package/apps/studio/test/canvas-move-api.test.ts +0 -587
  250. package/apps/studio/test/canvas-origin-gate.test.ts +0 -417
  251. package/apps/studio/test/canvas-pipeline.test.ts +0 -180
  252. package/apps/studio/test/canvas-rects.test.ts +0 -198
  253. package/apps/studio/test/canvas-reorder.test.ts +0 -211
  254. package/apps/studio/test/canvas-route.test.ts +0 -179
  255. package/apps/studio/test/canvas-shell-base.test.ts +0 -107
  256. package/apps/studio/test/canvas-shell-build-error.test.ts +0 -52
  257. package/apps/studio/test/canvas-url.test.ts +0 -138
  258. package/apps/studio/test/canvas-wheel-delta.test.ts +0 -42
  259. package/apps/studio/test/canvas-zoom-floor.test.ts +0 -73
  260. package/apps/studio/test/capture-core.test.ts +0 -32
  261. package/apps/studio/test/capture-determinism-shape.test.ts +0 -135
  262. package/apps/studio/test/chat-attachments.test.ts +0 -117
  263. package/apps/studio/test/chat-context.test.ts +0 -129
  264. package/apps/studio/test/chat-markdown.test.tsx +0 -58
  265. package/apps/studio/test/clip-addressing.test.ts +0 -738
  266. package/apps/studio/test/clip-ops.test.ts +0 -922
  267. package/apps/studio/test/cloud-build.test.ts +0 -95
  268. package/apps/studio/test/cloud-connect-note.test.ts +0 -135
  269. package/apps/studio/test/cloud-endpoints.test.ts +0 -590
  270. package/apps/studio/test/cloud-history-hardening.test.ts +0 -165
  271. package/apps/studio/test/cloud-history-posture.test.ts +0 -230
  272. package/apps/studio/test/cloud-managed-save-surfaces.test.ts +0 -90
  273. package/apps/studio/test/cloud-renew.test.ts +0 -205
  274. package/apps/studio/test/cloud-session-role.test.ts +0 -161
  275. package/apps/studio/test/cloud-shell-surfaces.test.ts +0 -170
  276. package/apps/studio/test/cold-start-apply.test.ts +0 -303
  277. package/apps/studio/test/collab-annotations-bridge.test.ts +0 -55
  278. package/apps/studio/test/collab-awareness-bridge.test.ts +0 -223
  279. package/apps/studio/test/collab-bridge.test.ts +0 -79
  280. package/apps/studio/test/collab-loopback.test.ts +0 -63
  281. package/apps/studio/test/collab-origin-gate.test.ts +0 -323
  282. package/apps/studio/test/collab-protocol.test.ts +0 -146
  283. package/apps/studio/test/collab-readonly-gate.test.ts +0 -194
  284. package/apps/studio/test/collab-reseed-guard.test.ts +0 -78
  285. package/apps/studio/test/collab-room.test.ts +0 -188
  286. package/apps/studio/test/collab-session-survival.test.tsx +0 -176
  287. package/apps/studio/test/collab-stress.test.ts +0 -129
  288. package/apps/studio/test/comment-mount.test.ts +0 -97
  289. package/apps/studio/test/comment-relay-origin-gate.test.ts +0 -117
  290. package/apps/studio/test/comments-api.test.ts +0 -277
  291. package/apps/studio/test/comments-fs-rebroadcast.test.ts +0 -155
  292. package/apps/studio/test/comments-overlay.test.ts +0 -117
  293. package/apps/studio/test/compile-entry.test.ts +0 -133
  294. package/apps/studio/test/component-map.test.ts +0 -48
  295. package/apps/studio/test/config-projection.test.ts +0 -104
  296. package/apps/studio/test/config-reload.test.ts +0 -230
  297. package/apps/studio/test/config-version.test.ts +0 -88
  298. package/apps/studio/test/context-resolve-tokens.test.ts +0 -97
  299. package/apps/studio/test/convert-to-absolute.test.ts +0 -333
  300. package/apps/studio/test/credential-grammar.test.ts +0 -107
  301. package/apps/studio/test/csp-canvas-shell.test.ts +0 -62
  302. package/apps/studio/test/csrf-write-guard.test.ts +0 -141
  303. package/apps/studio/test/debug-bundle.test.ts +0 -105
  304. package/apps/studio/test/design-setup-readiness.test.ts +0 -155
  305. package/apps/studio/test/detach-component.test.ts +0 -94
  306. package/apps/studio/test/dns-rebinding-guard.test.ts +0 -101
  307. package/apps/studio/test/dom-selection.test.ts +0 -198
  308. package/apps/studio/test/dynamic-text-edit.test.ts +0 -162
  309. package/apps/studio/test/edit-css-occurrence.test.ts +0 -81
  310. package/apps/studio/test/edit-persistence.test.ts +0 -91
  311. package/apps/studio/test/edit-scope-api.test.ts +0 -119
  312. package/apps/studio/test/edit-source-command.test.ts +0 -142
  313. package/apps/studio/test/edit-suppress.test.ts +0 -170
  314. package/apps/studio/test/editing-presence.test.ts +0 -103
  315. package/apps/studio/test/element-resize.test.ts +0 -136
  316. package/apps/studio/test/element-structural-api.test.ts +0 -434
  317. package/apps/studio/test/element-structural-edit.test.ts +0 -750
  318. package/apps/studio/test/ensure-browser.test.ts +0 -92
  319. package/apps/studio/test/equal-spacing-detector.test.ts +0 -257
  320. package/apps/studio/test/export-assemble.test.ts +0 -53
  321. package/apps/studio/test/export-browser-lane.test.ts +0 -126
  322. package/apps/studio/test/export-capture-fidelity.test.ts +0 -146
  323. package/apps/studio/test/export-capture-hygiene.test.ts +0 -675
  324. package/apps/studio/test/export-center.test.tsx +0 -287
  325. package/apps/studio/test/export-e2e-lanes.test.ts +0 -857
  326. package/apps/studio/test/export-format-scope-coherence.test.ts +0 -89
  327. package/apps/studio/test/export-lane.test.ts +0 -245
  328. package/apps/studio/test/export-shim-multi-capture.test.ts +0 -83
  329. package/apps/studio/test/exporters/canva.test.ts +0 -63
  330. package/apps/studio/test/exporters/degraded-propagation.test.ts +0 -123
  331. package/apps/studio/test/exporters/endpoint.test.ts +0 -121
  332. package/apps/studio/test/exporters/history.test.ts +0 -107
  333. package/apps/studio/test/exporters/html.test.ts +0 -26
  334. package/apps/studio/test/exporters/jobs.test.ts +0 -342
  335. package/apps/studio/test/exporters/pdf.test.ts +0 -85
  336. package/apps/studio/test/exporters/png.test.ts +0 -103
  337. package/apps/studio/test/exporters/pptx-deck.test.ts +0 -112
  338. package/apps/studio/test/exporters/pptx.test.ts +0 -31
  339. package/apps/studio/test/exporters/pw-launch.test.ts +0 -49
  340. package/apps/studio/test/exporters/runtime.test.ts +0 -59
  341. package/apps/studio/test/exporters/scope.test.ts +0 -292
  342. package/apps/studio/test/exporters/svg.test.ts +0 -29
  343. package/apps/studio/test/exporters/unsupported-media.test.ts +0 -123
  344. package/apps/studio/test/exporters/zip.test.ts +0 -104
  345. package/apps/studio/test/fetch-asset-gate.test.ts +0 -189
  346. package/apps/studio/test/figjam-v3-model.test.ts +0 -342
  347. package/apps/studio/test/figma-explode.test.ts +0 -438
  348. package/apps/studio/test/figma-provenance.test.ts +0 -108
  349. package/apps/studio/test/figma-routes.test.ts +0 -294
  350. package/apps/studio/test/file-lock.test.ts +0 -84
  351. package/apps/studio/test/fixtures/backgrounding-claude-auth.sh +0 -12
  352. package/apps/studio/test/fixtures/fake-claude-auth.mjs +0 -23
  353. package/apps/studio/test/fixtures/figjam-v3-groups-bindings.svg +0 -1
  354. package/apps/studio/test/fixtures/mock-acp-agent-caps.mjs +0 -158
  355. package/apps/studio/test/fixtures/mock-acp-agent-commands.mjs +0 -44
  356. package/apps/studio/test/fixtures/mock-acp-agent-elicit-flood.mjs +0 -46
  357. package/apps/studio/test/fixtures/mock-acp-agent-elicit-url.mjs +0 -42
  358. package/apps/studio/test/fixtures/mock-acp-agent-elicit.mjs +0 -63
  359. package/apps/studio/test/fixtures/mock-acp-agent-permission-flood.mjs +0 -51
  360. package/apps/studio/test/fixtures/mock-acp-agent-permission.mjs +0 -50
  361. package/apps/studio/test/fixtures/mock-acp-agent-slow.mjs +0 -45
  362. package/apps/studio/test/fixtures/mock-acp-agent-usage.mjs +0 -56
  363. package/apps/studio/test/fixtures/mock-acp-agent-wedged.mjs +0 -37
  364. package/apps/studio/test/fixtures/mock-acp-agent-write.mjs +0 -118
  365. package/apps/studio/test/fixtures/mock-acp-agent.mjs +0 -69
  366. package/apps/studio/test/fixtures/noisy-claude-auth.mjs +0 -27
  367. package/apps/studio/test/fixtures/perf-canvas.mjs +0 -201
  368. package/apps/studio/test/fixtures/phase-20-annotations.svg +0 -1
  369. package/apps/studio/test/fixtures/phase-21-annotations.svg +0 -1
  370. package/apps/studio/test/fixtures/tiny-subset.woff2 +0 -0
  371. package/apps/studio/test/fixtures/video-comp-fixture.tsx +0 -82
  372. package/apps/studio/test/fixtures/wedged-claude-auth.sh +0 -11
  373. package/apps/studio/test/footage-store.test.ts +0 -100
  374. package/apps/studio/test/fs-mkdir-api.test.ts +0 -248
  375. package/apps/studio/test/fs-watch.test.ts +0 -41
  376. package/apps/studio/test/gemma-models.test.ts +0 -245
  377. package/apps/studio/test/generate-route.test.ts +0 -106
  378. package/apps/studio/test/git-api.test.ts +0 -0
  379. package/apps/studio/test/git-branches.test.ts +0 -341
  380. package/apps/studio/test/git-cloud-posture.test.ts +0 -59
  381. package/apps/studio/test/git-grouping.test.ts +0 -106
  382. package/apps/studio/test/git-lifecycle.test.ts +0 -100
  383. package/apps/studio/test/git-log-format.test.ts +0 -95
  384. package/apps/studio/test/git-safe-rel.test.ts +0 -132
  385. package/apps/studio/test/git-system-engine.test.ts +0 -135
  386. package/apps/studio/test/git-watch.test.ts +0 -97
  387. package/apps/studio/test/github-api.test.ts +0 -577
  388. package/apps/studio/test/grid-track-handles.test.ts +0 -160
  389. package/apps/studio/test/handoff-static-frames.test.ts +0 -215
  390. package/apps/studio/test/handoff.test.ts +0 -329
  391. package/apps/studio/test/history-rollback.test.ts +0 -88
  392. package/apps/studio/test/hmr-broadcast.test.ts +0 -236
  393. package/apps/studio/test/hmr-classify.test.ts +0 -87
  394. package/apps/studio/test/hub-link.test.ts +0 -80
  395. package/apps/studio/test/identity-cache.test.ts +0 -83
  396. package/apps/studio/test/import-asset-browser.test.ts +0 -64
  397. package/apps/studio/test/import-asset-route.test.ts +0 -71
  398. package/apps/studio/test/import-asset.test.ts +0 -427
  399. package/apps/studio/test/import-brand-browser.test.ts +0 -88
  400. package/apps/studio/test/import-brand-route.test.ts +0 -120
  401. package/apps/studio/test/import-brand.test.ts +0 -206
  402. package/apps/studio/test/import-figma.test.ts +0 -667
  403. package/apps/studio/test/import-tokens.test.ts +0 -722
  404. package/apps/studio/test/index-data-canvas-ds.test.ts +0 -97
  405. package/apps/studio/test/input-router.test.ts +0 -757
  406. package/apps/studio/test/inspect-script-syntax.test.ts +0 -53
  407. package/apps/studio/test/inspect-selections.test.ts +0 -219
  408. package/apps/studio/test/knob-props-authored.test.ts +0 -87
  409. package/apps/studio/test/layers-synthetic-groups.test.ts +0 -96
  410. package/apps/studio/test/locator.test.ts +0 -214
  411. package/apps/studio/test/marquee-overlay.test.ts +0 -94
  412. package/apps/studio/test/media-commit-chain.test.ts +0 -210
  413. package/apps/studio/test/mode-toggle.test.tsx +0 -226
  414. package/apps/studio/test/motion-sample.test.ts +0 -35
  415. package/apps/studio/test/move-artboards-command.test.ts +0 -108
  416. package/apps/studio/test/participants-chrome.test.ts +0 -65
  417. package/apps/studio/test/paths.test.ts +0 -88
  418. package/apps/studio/test/pdf-print-boxes.test.ts +0 -359
  419. package/apps/studio/test/peer-selection-follows-camera.test.tsx +0 -131
  420. package/apps/studio/test/perf-harness.ts +0 -193
  421. package/apps/studio/test/phase-3.6-smoke.test.ts +0 -79
  422. package/apps/studio/test/photo-bg-remove-validation.test.ts +0 -71
  423. package/apps/studio/test/photo-canvas-bundle.test.ts +0 -79
  424. package/apps/studio/test/photo-edit-api.test.ts +0 -201
  425. package/apps/studio/test/photo-filters.test.ts +0 -149
  426. package/apps/studio/test/photo-pipeline.test.ts +0 -106
  427. package/apps/studio/test/photo-taxonomy.test.ts +0 -96
  428. package/apps/studio/test/print-marks.test.ts +0 -113
  429. package/apps/studio/test/print-units.test.ts +0 -173
  430. package/apps/studio/test/read-annotations.test.ts +0 -727
  431. package/apps/studio/test/read-only-gate.test.ts +0 -188
  432. package/apps/studio/test/read-only-role.test.ts +0 -95
  433. package/apps/studio/test/readiness.test.ts +0 -337
  434. package/apps/studio/test/remote-ahead-behind-cache.test.ts +0 -89
  435. package/apps/studio/test/reorder-api.test.ts +0 -210
  436. package/apps/studio/test/repo-concurrency.test.ts +0 -146
  437. package/apps/studio/test/repo-lock.test.ts +0 -350
  438. package/apps/studio/test/report-proxy.test.ts +0 -125
  439. package/apps/studio/test/ripple.test.ts +0 -197
  440. package/apps/studio/test/runtime-bundle-error-mapping.test.ts +0 -43
  441. package/apps/studio/test/runtime-bundle.test.ts +0 -69
  442. package/apps/studio/test/sanitize-annotation-svg.test.ts +0 -238
  443. package/apps/studio/test/scaffold-design.test.ts +0 -122
  444. package/apps/studio/test/server-lifecycle.test.ts +0 -38
  445. package/apps/studio/test/session-runtime-state.test.ts +0 -226
  446. package/apps/studio/test/session-scope.test.ts +0 -100
  447. package/apps/studio/test/shared-doc-cell-pairing.test.ts +0 -642
  448. package/apps/studio/test/shared-doc-convergence.test.ts +0 -265
  449. package/apps/studio/test/shared-doc-foundation.test.ts +0 -63
  450. package/apps/studio/test/shared-doc-migrate.test.ts +0 -369
  451. package/apps/studio/test/shared-doc-projection.test.ts +0 -238
  452. package/apps/studio/test/shell-importmap.test.ts +0 -56
  453. package/apps/studio/test/sizing-mode.test.ts +0 -102
  454. package/apps/studio/test/slash-commands.test.ts +0 -117
  455. package/apps/studio/test/snap-distance-pill.test.ts +0 -68
  456. package/apps/studio/test/spacing-handles.test.ts +0 -138
  457. package/apps/studio/test/specimen-select.test.ts +0 -88
  458. package/apps/studio/test/sync-agent.test.ts +0 -660
  459. package/apps/studio/test/sync-annotations-cold-start.test.ts +0 -302
  460. package/apps/studio/test/sync-asset-push.test.ts +0 -931
  461. package/apps/studio/test/sync-atomic-write.test.ts +0 -62
  462. package/apps/studio/test/sync-attach-incremental.test.ts +0 -527
  463. package/apps/studio/test/sync-autocommit.test.ts +0 -537
  464. package/apps/studio/test/sync-canvas-path.test.ts +0 -200
  465. package/apps/studio/test/sync-cell-write-nudge.test.ts +0 -346
  466. package/apps/studio/test/sync-codec.test.ts +0 -230
  467. package/apps/studio/test/sync-cold-seed-dedup.test.ts +0 -187
  468. package/apps/studio/test/sync-cold-start.test.ts +0 -304
  469. package/apps/studio/test/sync-connect-honesty.test.ts +0 -114
  470. package/apps/studio/test/sync-connection-state.test.ts +0 -337
  471. package/apps/studio/test/sync-ctl-channel.test.ts +0 -508
  472. package/apps/studio/test/sync-decide-file.test.ts +0 -420
  473. package/apps/studio/test/sync-doc-name.test.ts +0 -281
  474. package/apps/studio/test/sync-echo-guard.test.ts +0 -96
  475. package/apps/studio/test/sync-file-ledger.test.ts +0 -334
  476. package/apps/studio/test/sync-file-membership.test.ts +0 -347
  477. package/apps/studio/test/sync-file-plane.test.ts +0 -976
  478. package/apps/studio/test/sync-file-pull.test.ts +0 -333
  479. package/apps/studio/test/sync-fresh-link-parity.test.ts +0 -267
  480. package/apps/studio/test/sync-fs-mirror.test.ts +0 -181
  481. package/apps/studio/test/sync-hardening.test.ts +0 -586
  482. package/apps/studio/test/sync-hub-listing.test.ts +0 -46
  483. package/apps/studio/test/sync-hubs-config.test.ts +0 -133
  484. package/apps/studio/test/sync-incident-replay.test.ts +0 -211
  485. package/apps/studio/test/sync-journal.test.ts +0 -176
  486. package/apps/studio/test/sync-meta-codec.test.ts +0 -199
  487. package/apps/studio/test/sync-migrate-flat-fallback.test.ts +0 -98
  488. package/apps/studio/test/sync-move-retirement.test.ts +0 -231
  489. package/apps/studio/test/sync-panel-surface.test.ts +0 -153
  490. package/apps/studio/test/sync-path-pull.test.ts +0 -531
  491. package/apps/studio/test/sync-presentation.test.ts +0 -262
  492. package/apps/studio/test/sync-pull-budget.test.ts +0 -169
  493. package/apps/studio/test/sync-remote-docs.test.ts +0 -333
  494. package/apps/studio/test/sync-resync-routes.test.ts +0 -168
  495. package/apps/studio/test/sync-runtime.test.ts +0 -1675
  496. package/apps/studio/test/sync-seed-defers-to-hub.test.ts +0 -83
  497. package/apps/studio/test/sync-settings-routes.test.ts +0 -195
  498. package/apps/studio/test/sync-settings.test.ts +0 -151
  499. package/apps/studio/test/sync-status.test.ts +0 -261
  500. package/apps/studio/test/sync-supervisor.test.ts +0 -262
  501. package/apps/studio/test/sync-tombstone-apply.test.ts +0 -111
  502. package/apps/studio/test/sync-trash.test.ts +0 -132
  503. package/apps/studio/test/sync-two-peer-discovery.test.ts +0 -343
  504. package/apps/studio/test/sync-untrusted.test.ts +0 -103
  505. package/apps/studio/test/system-endpoint.test.ts +0 -144
  506. package/apps/studio/test/text-editability-stamp.test.ts +0 -131
  507. package/apps/studio/test/timeline-comp-target.test.ts +0 -139
  508. package/apps/studio/test/timeline-media-cache.test.ts +0 -41
  509. package/apps/studio/test/timeline-parse.test.ts +0 -184
  510. package/apps/studio/test/timeline-scale.test.ts +0 -65
  511. package/apps/studio/test/timeline-snap.test.ts +0 -85
  512. package/apps/studio/test/tool-palette-insert-anchor.test.ts +0 -86
  513. package/apps/studio/test/tool-palette.test.tsx +0 -55
  514. package/apps/studio/test/tour-overlay.test.tsx +0 -90
  515. package/apps/studio/test/trusted-request-host.test.ts +0 -66
  516. package/apps/studio/test/undo-sequence-byte-compare.test.ts +0 -211
  517. package/apps/studio/test/undo-stack.test.ts +0 -244
  518. package/apps/studio/test/use-agent-presence.test.tsx +0 -117
  519. package/apps/studio/test/use-annotation-resize.test.ts +0 -402
  520. package/apps/studio/test/use-annotation-selection.test.tsx +0 -77
  521. package/apps/studio/test/use-artboard-drag.test.ts +0 -324
  522. package/apps/studio/test/use-canvas-activity.test.tsx +0 -206
  523. package/apps/studio/test/use-collab.test.ts +0 -0
  524. package/apps/studio/test/use-cursor-modifiers.test.ts +0 -59
  525. package/apps/studio/test/use-keyboard-discipline.test.ts +0 -27
  526. package/apps/studio/test/use-selection-set.test.tsx +0 -166
  527. package/apps/studio/test/use-snap-guides.test.ts +0 -271
  528. package/apps/studio/test/use-tool-mode.test.tsx +0 -186
  529. package/apps/studio/test/use-undo-stack.test.tsx +0 -223
  530. package/apps/studio/test/video-asset.test.ts +0 -163
  531. package/apps/studio/test/video-comp-fixture.test.ts +0 -50
  532. package/apps/studio/test/video-comp.test.ts +0 -270
  533. package/apps/studio/test/video-encode-lib.test.ts +0 -63
  534. package/apps/studio/test/video-render-bridge.test.ts +0 -171
  535. package/apps/studio/test/whats-new.test.ts +0 -113
  536. package/apps/studio/test/whisper-setup.test.ts +0 -97
  537. package/apps/studio/test/workspace-containment.test.ts +0 -382
  538. package/apps/studio/test/workspace-signin.test.ts +0 -292
  539. package/apps/studio/test/ws-handshake.test.ts +0 -33
  540. package/cli/commands/cache.test.mjs +0 -166
  541. package/cli/commands/design-link.test.mjs +0 -419
  542. package/cli/commands/design.test.mjs +0 -240
  543. package/cli/commands/doctor.test.mjs +0 -237
  544. package/cli/commands/hub-workspace.test.mjs +0 -275
  545. package/cli/commands/hub.test.mjs +0 -225
  546. package/cli/commands/kg.test.mjs +0 -142
  547. package/cli/commands/preflight.test.mjs +0 -46
  548. package/cli/lib/argv.test.mjs +0 -46
  549. package/cli/lib/cache.test.mjs +0 -303
  550. package/cli/lib/cell-plan.test.mjs +0 -225
  551. package/cli/lib/config-lint.test.mjs +0 -117
  552. package/cli/lib/ddr-to-kgai.test.mjs +0 -118
  553. package/cli/lib/design-ownership.test.mjs +0 -329
  554. package/cli/lib/fetch-asset.test.mjs +0 -213
  555. package/cli/lib/figma-codegen-reachability.test.mjs +0 -104
  556. package/cli/lib/figma-import-controls.test.mjs +0 -70
  557. package/cli/lib/flow-design-integration.test.mjs +0 -165
  558. package/cli/lib/gitignore-block.test.mjs +0 -136
  559. package/cli/lib/gitignore-drift.test.mjs +0 -156
  560. package/cli/lib/hubs-config.test.mjs +0 -165
  561. package/cli/lib/pkg-root.test.mjs +0 -111
  562. package/cli/lib/plugin-cli-reachability.test.mjs +0 -57
  563. package/cli/lib/plugin-name-namespace.test.mjs +0 -71
  564. package/cli/lib/reconstruct-toolset.test.mjs +0 -194
  565. package/cli/lib/stack-detect.test.mjs +0 -121
  566. package/cli/lib/update-check.test.mjs +0 -32
  567. package/cli/lib/workspace-plan.test.mjs +0 -699
@@ -1,545 +0,0 @@
1
- // End-to-end bridge test against a mock ACP agent (test/fixtures/mock-acp-agent.mjs)
2
- // so no real `claude` install is needed. Proves: spawn + handshake + streamed
3
- // update + turn completion, AND that ANTHROPIC_API_KEY never reaches the child
4
- // (DDR-123 guardrail #1, verified end-to-end — the mock echoes its own env).
5
-
6
- import { afterEach, describe, expect, test } from 'bun:test';
7
- import { mkdtemp, readFile, rm } from 'node:fs/promises';
8
- import { tmpdir } from 'node:os';
9
- import { join } from 'node:path';
10
-
11
- import { AcpBridge } from '../acp/bridge.ts';
12
- import {
13
- getClaudeAuthStatus,
14
- probeAcpAvailability,
15
- probeAcpAvailabilityAuthed,
16
- } from '../acp/probe.ts';
17
-
18
- const FIXTURE = join(import.meta.dir, 'fixtures', 'mock-acp-agent.mjs');
19
- const TEST_ENV_KEYS = [
20
- 'ANTHROPIC_API_KEY',
21
- 'MAUDE_ACP_ADAPTER_ENTRY',
22
- 'MAUDE_ACP_RUNTIME',
23
- 'MAUDE_CLAUDE_BIN',
24
- ];
25
-
26
- afterEach(() => {
27
- for (const key of TEST_ENV_KEYS) delete process.env[key];
28
- });
29
-
30
- describe('AcpBridge — round-trip + subscription guardrail', () => {
31
- test('connects, streams an update, completes a turn, and scrubs ANTHROPIC_API_KEY', async () => {
32
- process.env.ANTHROPIC_API_KEY = 'sk-must-be-scrubbed';
33
- process.env.MAUDE_ACP_ADAPTER_ENTRY = FIXTURE;
34
- process.env.MAUDE_ACP_RUNTIME = process.execPath; // run the mock under this bun
35
- process.env.MAUDE_CLAUDE_BIN = process.execPath; // satisfy the claude-present check
36
-
37
- const updates: unknown[] = [];
38
- const bridge = new AcpBridge({ repoRoot: process.cwd(), onUpdate: (u) => updates.push(u) });
39
- try {
40
- const result = await bridge.prompt('hello', 'c1');
41
- expect(bridge.connected).toBe(true);
42
- expect(bridge.sessionId).toBe('mock-session-1');
43
- expect(result.stopReason).toBe('end_turn');
44
-
45
- // The mock streamed its own ANTHROPIC_API_KEY — it must read `<unset>`,
46
- // proving scrubAgentEnv stripped it from the child env before spawn.
47
- const streamed = JSON.stringify(updates);
48
- expect(streamed).toContain('apiKey=<unset>');
49
- expect(streamed).not.toContain('sk-must-be-scrubbed');
50
- // DDR-123 guardrail #2 — the bridge pins the adapter to the user's own
51
- // `claude` (here MAUDE_CLAUDE_BIN = this bun) via CLAUDE_CODE_EXECUTABLE, so
52
- // it never falls back to the unshipped ~210 MB native binary.
53
- expect(streamed).toContain(`claudeExe=${process.execPath}`);
54
- } finally {
55
- await bridge.stop();
56
- }
57
- }, 15000);
58
-
59
- test('model/effort are no longer env-at-spawn — a config change never respawns the running adapter', async () => {
60
- process.env.MAUDE_ACP_ADAPTER_ENTRY = FIXTURE;
61
- process.env.MAUDE_ACP_RUNTIME = process.execPath;
62
- process.env.MAUDE_CLAUDE_BIN = process.execPath;
63
-
64
- const updates: unknown[] = [];
65
- const bridge = new AcpBridge({ repoRoot: process.cwd(), onUpdate: (u) => updates.push(u) });
66
- try {
67
- // A persisted pick this mock doesn't advertise (it has no configOptions at
68
- // all) is skipped best-effort, never forwarded to ANTHROPIC_MODEL/env.
69
- bridge.setConfig('opus', 'thorough');
70
- await bridge.prompt('hi', 'c1');
71
- const sid = bridge.sessionId;
72
- const first = JSON.stringify(updates);
73
- expect(first).toContain('model=<unset>');
74
- expect(first).toContain('thinking=<unset>');
75
-
76
- // Changing the config again must NOT tear down / respawn the live process
77
- // (Task A3 — the old configChanged()-triggered stop() is gone).
78
- updates.length = 0;
79
- bridge.setConfig(null, 'fast');
80
- await bridge.prompt('again', 'c1');
81
- expect(bridge.sessionId).toBe(sid); // same session — no respawn happened
82
- const second = JSON.stringify(updates);
83
- expect(second).toContain('model=<unset>');
84
- expect(second).toContain('thinking=<unset>');
85
- } finally {
86
- await bridge.stop();
87
- }
88
- }, 15000);
89
-
90
- test('a second prompt reuses the same live session', async () => {
91
- process.env.MAUDE_ACP_ADAPTER_ENTRY = FIXTURE;
92
- process.env.MAUDE_ACP_RUNTIME = process.execPath;
93
- process.env.MAUDE_CLAUDE_BIN = process.execPath;
94
-
95
- const bridge = new AcpBridge({ repoRoot: process.cwd(), onUpdate: () => {} });
96
- try {
97
- await bridge.prompt('first', 'c1');
98
- const sid = bridge.sessionId;
99
- await bridge.prompt('second', 'c1');
100
- expect(bridge.sessionId).toBe(sid);
101
- } finally {
102
- await bridge.stop();
103
- }
104
- }, 15000);
105
-
106
- test('different chat ids get separate claude sessions; same id reuses its own', async () => {
107
- process.env.MAUDE_ACP_ADAPTER_ENTRY = FIXTURE;
108
- process.env.MAUDE_ACP_RUNTIME = process.execPath;
109
- process.env.MAUDE_CLAUDE_BIN = process.execPath;
110
-
111
- const bridge = new AcpBridge({ repoRoot: process.cwd(), onUpdate: () => {} });
112
- try {
113
- await bridge.prompt('a', 'chatA');
114
- const sa = bridge.sessionId;
115
- await bridge.prompt('b', 'chatB');
116
- const sb = bridge.sessionId;
117
- expect(sa).not.toBe(sb); // separate per-chat contexts
118
- await bridge.prompt('a again', 'chatA');
119
- expect(bridge.sessionId).toBe(sa); // chatA reuses its own session
120
- } finally {
121
- await bridge.stop();
122
- }
123
- }, 15000);
124
- });
125
-
126
- describe('AcpBridge — cross-restart session resume (DDR-125 gap)', () => {
127
- let dir: string;
128
-
129
- afterEach(async () => {
130
- if (dir) await rm(dir, { recursive: true, force: true });
131
- });
132
-
133
- test('a persisted sessionId resumes via loadSession instead of spawning a new session', async () => {
134
- process.env.MAUDE_ACP_ADAPTER_ENTRY = FIXTURE;
135
- process.env.MAUDE_ACP_RUNTIME = process.execPath;
136
- process.env.MAUDE_CLAUDE_BIN = process.execPath;
137
- dir = await mkdtemp(join(tmpdir(), 'acp-bridge-test-'));
138
- const storePath = join(dir, 'c1.session.json');
139
- await Bun.write(storePath, JSON.stringify({ sessionId: 'persisted-session-abc' }));
140
-
141
- const updates: unknown[] = [];
142
- const bridge = new AcpBridge({ repoRoot: process.cwd(), onUpdate: (u) => updates.push(u) });
143
- try {
144
- bridge.setSessionStorePath(storePath);
145
- await bridge.prompt('pokracuj', 'c1');
146
- // Resumed the persisted id verbatim — never fell through to session/new
147
- // (which would have minted a fresh `mock-session-N`).
148
- expect(bridge.sessionId).toBe('persisted-session-abc');
149
- // The mock's replay notification must NOT reach the UI sink.
150
- expect(JSON.stringify(updates)).not.toContain('REPLAYED-HISTORY-SHOULD-NOT-SURFACE');
151
- } finally {
152
- await bridge.stop();
153
- }
154
- }, 15000);
155
-
156
- test('a resume that fails (pruned session) falls back to newSession and re-persists the new id', async () => {
157
- process.env.MAUDE_ACP_ADAPTER_ENTRY = FIXTURE;
158
- process.env.MAUDE_ACP_RUNTIME = process.execPath;
159
- process.env.MAUDE_CLAUDE_BIN = process.execPath;
160
- dir = await mkdtemp(join(tmpdir(), 'acp-bridge-test-'));
161
- const storePath = join(dir, 'c1.session.json');
162
- await Bun.write(storePath, JSON.stringify({ sessionId: 'not-found' }));
163
-
164
- const bridge = new AcpBridge({ repoRoot: process.cwd(), onUpdate: () => {} });
165
- try {
166
- bridge.setSessionStorePath(storePath);
167
- await bridge.prompt('pokracuj', 'c1');
168
- expect(bridge.sessionId).toBe('mock-session-1'); // fresh session, not the stale id
169
- const stored = JSON.parse(await readFile(storePath, 'utf8'));
170
- expect(stored.sessionId).toBe('mock-session-1'); // re-persisted so the NEXT restart resumes it
171
- } finally {
172
- await bridge.stop();
173
- }
174
- }, 15000);
175
-
176
- test('no prior chat for this sidecar behaves exactly as before — a brand new session is created and persisted', async () => {
177
- process.env.MAUDE_ACP_ADAPTER_ENTRY = FIXTURE;
178
- process.env.MAUDE_ACP_RUNTIME = process.execPath;
179
- process.env.MAUDE_CLAUDE_BIN = process.execPath;
180
- dir = await mkdtemp(join(tmpdir(), 'acp-bridge-test-'));
181
- const storePath = join(dir, 'brand-new.session.json');
182
-
183
- const bridge = new AcpBridge({ repoRoot: process.cwd(), onUpdate: () => {} });
184
- try {
185
- bridge.setSessionStorePath(storePath);
186
- await bridge.prompt('hi', 'c1');
187
- expect(bridge.sessionId).toBe('mock-session-1');
188
- const stored = JSON.parse(await readFile(storePath, 'utf8'));
189
- expect(stored.sessionId).toBe('mock-session-1');
190
- } finally {
191
- await bridge.stop();
192
- }
193
- }, 15000);
194
-
195
- test('no sidecar wired at all (e.g. a warm-up before any prompt) never touches disk and behaves exactly as before', async () => {
196
- process.env.MAUDE_ACP_ADAPTER_ENTRY = FIXTURE;
197
- process.env.MAUDE_ACP_RUNTIME = process.execPath;
198
- process.env.MAUDE_CLAUDE_BIN = process.execPath;
199
-
200
- const bridge = new AcpBridge({ repoRoot: process.cwd(), onUpdate: () => {} });
201
- try {
202
- // setSessionStorePath is never called — sessionStorePath stays null.
203
- await bridge.prompt('hi', 'c1');
204
- expect(bridge.sessionId).toBe('mock-session-1');
205
- } finally {
206
- await bridge.stop();
207
- }
208
- }, 15000);
209
-
210
- test('a malformed persisted sessionId (not a plausible shape) is rejected — falls back to newSession', async () => {
211
- process.env.MAUDE_ACP_ADAPTER_ENTRY = FIXTURE;
212
- process.env.MAUDE_ACP_RUNTIME = process.execPath;
213
- process.env.MAUDE_CLAUDE_BIN = process.execPath;
214
- dir = await mkdtemp(join(tmpdir(), 'acp-bridge-test-'));
215
- const storePath = join(dir, 'c1.session.json');
216
- // Not a plausible sessionId shape (embedded newline + non-UUID charset) —
217
- // must never reach the wire as a `loadSession` sessionId.
218
- await Bun.write(storePath, JSON.stringify({ sessionId: 'evil\nsessionId; rm -rf /' }));
219
-
220
- const bridge = new AcpBridge({ repoRoot: process.cwd(), onUpdate: () => {} });
221
- try {
222
- bridge.setSessionStorePath(storePath);
223
- await bridge.prompt('hi', 'c1');
224
- expect(bridge.sessionId).toBe('mock-session-1'); // rejected — fresh session instead
225
- } finally {
226
- await bridge.stop();
227
- }
228
- }, 15000);
229
-
230
- test('warm and prompt racing for the same chat share one resume attempt (no replaying race)', async () => {
231
- process.env.MAUDE_ACP_ADAPTER_ENTRY = FIXTURE;
232
- process.env.MAUDE_ACP_RUNTIME = process.execPath;
233
- process.env.MAUDE_CLAUDE_BIN = process.execPath;
234
- dir = await mkdtemp(join(tmpdir(), 'acp-bridge-test-'));
235
- const storePath = join(dir, 'c1.session.json');
236
- await Bun.write(storePath, JSON.stringify({ sessionId: 'persisted-race-session' }));
237
-
238
- const updates: unknown[] = [];
239
- const bridge = new AcpBridge({ repoRoot: process.cwd(), onUpdate: (u) => updates.push(u) });
240
- try {
241
- bridge.setSessionStorePath(storePath);
242
- // Fire warmUp and prompt concurrently for the same chatId — both call
243
- // sessionFor('c1') before either has resolved.
244
- const [, result] = await Promise.all([bridge.warmUp('c1'), bridge.prompt('pokracuj', 'c1')]);
245
- expect(bridge.sessionId).toBe('persisted-race-session');
246
- expect(result.stopReason).toBe('end_turn');
247
- // Only ONE resume attempt happened — the mock's replay text never leaked,
248
- // and the prompt's own turn still streamed normally.
249
- expect(JSON.stringify(updates)).not.toContain('REPLAYED-HISTORY-SHOULD-NOT-SURFACE');
250
- } finally {
251
- await bridge.stop();
252
- }
253
- }, 15000);
254
- });
255
-
256
- describe('probeAcpAvailability — not-connected detection', () => {
257
- test('reports not-available with a Claude-Code reason when the CLI is absent', () => {
258
- process.env.MAUDE_ACP_ADAPTER_ENTRY = FIXTURE;
259
- process.env.MAUDE_CLAUDE_BIN = join(import.meta.dir, 'no-such-claude-bin');
260
- const probe = probeAcpAvailability();
261
- expect(probe.available).toBe(false);
262
- expect(probe.reason ?? '').toMatch(/Claude Code/i);
263
- });
264
-
265
- test('reports available when both the adapter and a claude binary resolve', () => {
266
- process.env.MAUDE_ACP_ADAPTER_ENTRY = FIXTURE;
267
- process.env.MAUDE_CLAUDE_BIN = process.execPath;
268
- const probe = probeAcpAvailability();
269
- expect(probe.available).toBe(true);
270
- expect(probe.adapterEntry).toBe(FIXTURE);
271
- });
272
- });
273
-
274
- describe('probeAcpAvailabilityAuthed — DDR-166 T0d, logged-out must not read as available', () => {
275
- const AUTH_FIXTURE = join(import.meta.dir, 'fixtures', 'fake-claude-auth.mjs');
276
-
277
- afterEach(() => {
278
- delete process.env.FAKE_CLAUDE_LOGGED_IN;
279
- delete process.env.FAKE_CLAUDE_STDOUT_NOISE;
280
- });
281
-
282
- test('installed but NOT signed in reports available:false (the bug this closes)', async () => {
283
- process.env.MAUDE_ACP_ADAPTER_ENTRY = FIXTURE;
284
- process.env.MAUDE_CLAUDE_BIN = AUTH_FIXTURE;
285
- process.env.FAKE_CLAUDE_LOGGED_IN = '0';
286
- const probe = await probeAcpAvailabilityAuthed();
287
- expect(probe.available).toBe(false);
288
- expect(probe.reason ?? '').toMatch(/signed in/i);
289
- });
290
-
291
- test('installed AND signed in reports available:true', async () => {
292
- process.env.MAUDE_ACP_ADAPTER_ENTRY = FIXTURE;
293
- process.env.MAUDE_CLAUDE_BIN = AUTH_FIXTURE;
294
- process.env.FAKE_CLAUDE_LOGGED_IN = '1';
295
- const probe = await probeAcpAvailabilityAuthed();
296
- expect(probe.available).toBe(true);
297
- });
298
-
299
- test('not installed still reports the plain not-installed reason (no auth spawn attempted)', async () => {
300
- process.env.MAUDE_ACP_ADAPTER_ENTRY = FIXTURE;
301
- process.env.MAUDE_CLAUDE_BIN = join(import.meta.dir, 'no-such-claude-bin');
302
- const probe = await probeAcpAvailabilityAuthed();
303
- expect(probe.available).toBe(false);
304
- expect(probe.reason ?? '').toMatch(/Claude Code/i);
305
- });
306
-
307
- // Issue #107 — the reported `claude` was a mise shim printing a banner to
308
- // STDOUT before the JSON. Parsing the whole blob threw, the catch returned
309
- // null, and a signed-in user was told they were signed out.
310
- test('signed in through a stdout-noisy shim still reports available:true (#107)', async () => {
311
- process.env.MAUDE_ACP_ADAPTER_ENTRY = FIXTURE;
312
- process.env.MAUDE_CLAUDE_BIN = AUTH_FIXTURE;
313
- process.env.FAKE_CLAUDE_LOGGED_IN = '1';
314
- process.env.FAKE_CLAUDE_STDOUT_NOISE = '1';
315
- const probe = await probeAcpAvailabilityAuthed();
316
- expect(probe.available).toBe(true);
317
- });
318
-
319
- test('an UNREADABLE status says so — it does not accuse the user of being signed out (#107)', async () => {
320
- process.env.MAUDE_ACP_ADAPTER_ENTRY = FIXTURE;
321
- process.env.MAUDE_CLAUDE_BIN = join(import.meta.dir, 'fixtures', 'noisy-claude-auth.mjs');
322
- process.env.FAKE_CLAUDE_OUT = 'mise: something went sideways\nno json here\n';
323
- try {
324
- const probe = await probeAcpAvailabilityAuthed();
325
- expect(probe.available).toBe(false);
326
- expect(probe.reason ?? '').toContain("couldn't read");
327
- expect(probe.reason ?? '').not.toMatch(/not signed in/i);
328
- } finally {
329
- delete process.env.FAKE_CLAUDE_OUT;
330
- }
331
- });
332
-
333
- test('signed OUT through a stdout-noisy shim is still reported as signed out (no false positive)', async () => {
334
- process.env.MAUDE_ACP_ADAPTER_ENTRY = FIXTURE;
335
- process.env.MAUDE_CLAUDE_BIN = AUTH_FIXTURE;
336
- process.env.FAKE_CLAUDE_LOGGED_IN = '0';
337
- process.env.FAKE_CLAUDE_STDOUT_NOISE = '1';
338
- const probe = await probeAcpAvailabilityAuthed();
339
- expect(probe.available).toBe(false);
340
- expect(probe.reason ?? '').toMatch(/signed in/i);
341
- });
342
- });
343
-
344
- // Issue #107 — direct coverage of the stdout shape tolerated by the auth probe.
345
- // A wrapper on PATH (mise/asdf/volta, corporate `bin` shims) can prepend noise
346
- // to stdout; the fast path (clean JSON) and the honest failure (nothing
347
- // parseable → null) both have to keep working alongside it.
348
- describe('getClaudeAuthStatus — wrapper-stdout tolerance', () => {
349
- const NOISY = join(import.meta.dir, 'fixtures', 'noisy-claude-auth.mjs');
350
-
351
- afterEach(() => {
352
- delete process.env.FAKE_CLAUDE_OUT;
353
- delete process.env.FAKE_CLAUDE_HEAD;
354
- delete process.env.FAKE_CLAUDE_PAD_BYTES;
355
- delete process.env.FAKE_CLAUDE_TAIL;
356
- });
357
-
358
- const statusFor = async (stdout: string) => {
359
- process.env.MAUDE_CLAUDE_BIN = NOISY;
360
- process.env.FAKE_CLAUDE_OUT = stdout;
361
- return getClaudeAuthStatus();
362
- };
363
-
364
- const PAYLOAD = '{"loggedIn":true,"apiProvider":"firstParty","subscriptionType":"max"}';
365
-
366
- test('clean JSON — the unchanged fast path', async () => {
367
- expect(await statusFor(PAYLOAD)).toEqual({
368
- loggedIn: true,
369
- apiProvider: 'firstParty',
370
- subscriptionType: 'max',
371
- });
372
- });
373
-
374
- test('a banner line ahead of the JSON (the reported mise shim)', async () => {
375
- const out = `mise ~/.config/mise/config.toml tools: claude@2.1.246\n${PAYLOAD}`;
376
- expect((await statusFor(out))?.loggedIn).toBe(true);
377
- });
378
-
379
- test('noise carrying braces of its own does not outrank the real payload', async () => {
380
- const out = `warning: {"loggedIn":false} is not a real status\nresolving {claude}\n${PAYLOAD}`;
381
- expect((await statusFor(out))?.subscriptionType).toBe('max');
382
- });
383
-
384
- test('trailing noise after the JSON', async () => {
385
- expect((await statusFor(`${PAYLOAD}\nmise: done in 12ms\n`))?.loggedIn).toBe(true);
386
- });
387
-
388
- test('a brace inside a string value never miscounts the object bounds', async () => {
389
- const out = `noise\n{"orgName":"a } brace \\" in a string","loggedIn":true}\n`;
390
- expect((await statusFor(out))?.loggedIn).toBe(true);
391
- });
392
-
393
- test('nothing parseable still returns null — an unreadable status is not a signed-in one', async () => {
394
- expect(await statusFor('mise: command failed\nno json here at all\n')).toBeNull();
395
- });
396
-
397
- test('a signed-out payload behind noise stays signed out', async () => {
398
- expect((await statusFor(`mise banner\n{"loggedIn":false}`))?.loggedIn).toBe(false);
399
- });
400
-
401
- // ── Security-review regressions (defender M1, attacker F1/F2/F5) ──
402
- // Every one of these is a way the FIRST cut of this parser failed SOFT and
403
- // re-created #107 — a confident false claim — through a new door.
404
-
405
- test('an object with no `loggedIn` is not a status document → null, never a false "signed out" (F2)', async () => {
406
- // A plain NDJSON-logging wrapper. The first cut returned this as the
407
- // "fallback", and `!!undefined` rendered it as "Installed, but not signed
408
- // in." with the dead-end Sign-in button armed.
409
- const out =
410
- '{"level":"info","msg":"resolving claude"}\n{"level":"error","msg":"exec failed"}\n';
411
- expect(await statusFor(out)).toBeNull();
412
- });
413
-
414
- test('a JSON error document is not a status document either (F2)', async () => {
415
- expect(await statusFor('{"error":"ENOENT","code":127}')).toBeNull();
416
- });
417
-
418
- test('a signed-in user behind 150 JSON log lines is still read as signed in (M1)', async () => {
419
- // Forwards + a 100-candidate budget put the real payload outside the scan
420
- // window and demoted a signed-in user. Backwards spends the budget on noise.
421
- const noise = Array.from({ length: 150 }, (_, i) => `{"level":"debug","msg":"tool ${i}"}`).join(
422
- '\n'
423
- );
424
- expect((await statusFor(`${noise}\n${PAYLOAD}`))?.loggedIn).toBe(true);
425
- });
426
-
427
- test('a flood prefix cannot push the real payload out of range and forge loggedIn (F1)', async () => {
428
- // The verbatim exploit: a 242-byte prefix — one forged object plus 99 bare
429
- // `{}` fillers — burned the whole candidate budget so the genuine
430
- // signed-OUT payload never got looked at. Fail-closed became fail-open.
431
- const forged = '{"loggedIn":true,"apiProvider":"firstParty","subscriptionType":"max"}';
432
- const out = forged + '{}'.repeat(99) + '{"loggedIn":false,"apiProvider":"firstParty"}';
433
- const status = await statusFor(out);
434
- expect(status?.loggedIn).toBe(false);
435
- expect(status?.subscriptionType).toBeUndefined();
436
- });
437
-
438
- test('padding past the read cap drops the OLDEST bytes, so the forged head loses (F1, variant 2)', async () => {
439
- // The exploit padded 1 MiB after a forged object so the real payload fell
440
- // outside the scan window. The read cap is a sliding TAIL window now, so the
441
- // bytes that get dropped are the attacker's, and the trailing real payload
442
- // is what survives.
443
- //
444
- // The padding is generated by the fixture, not passed through the
445
- // environment — a multi-MiB env var exceeds the exec limit and the spawn
446
- // fails with E2BIG, which reads as a null status and would make this test
447
- // pass for entirely the wrong reason.
448
- process.env.MAUDE_CLAUDE_BIN = NOISY;
449
- process.env.FAKE_CLAUDE_HEAD =
450
- '{"loggedIn":true,"apiProvider":"firstParty","subscriptionType":"max"}';
451
- process.env.FAKE_CLAUDE_PAD_BYTES = String((1 << 20) + 64);
452
- process.env.FAKE_CLAUDE_TAIL = '{"loggedIn":false}';
453
- const status = await getClaudeAuthStatus();
454
- expect(status?.loggedIn).toBe(false); // the trailing real payload
455
- expect(status?.subscriptionType).toBeUndefined(); // the forged head was dropped
456
- });
457
-
458
- test('a large but sub-cap blob still reads the trailing payload', async () => {
459
- const out = `${'noise\n'.repeat(20000)}${PAYLOAD}`; // ~120 kB, well under the cap
460
- expect((await statusFor(out))?.subscriptionType).toBe('max');
461
- });
462
-
463
- test('a `{` at index 0 that does not parse costs one candidate, not the whole budget (R2-1)', async () => {
464
- // `lastIndexOf(needle, -1)` clamps to 0, so index 0 was re-tested for the
465
- // rest of the budget — a 116x cost amplification measured at ~2.2 s of
466
- // blocked event loop per probe, at a 1.5-2 s poll cadence. The payload must
467
- // still be found, and fast.
468
- const out = `{unparseable at index zero\n${PAYLOAD}`;
469
- const started = Date.now();
470
- expect((await statusFor(out))?.subscriptionType).toBe('max');
471
- expect(Date.now() - started).toBeLessThan(5000);
472
- });
473
-
474
- test('a payload whose `{` IS index 0 is still found (the R2-1 fix must not skip it)', async () => {
475
- expect((await statusFor(PAYLOAD))?.subscriptionType).toBe('max');
476
- });
477
-
478
- test('the read cap drops the exact overflow, not a whole chunk (R3-2)', async () => {
479
- // Chunk-granular shifting over-dropped up to `sizeof(oldest chunk) - 1` —
480
- // 262 144 B measured for a 150-byte overflow against a real Bun pipe. Here
481
- // the payload sits 50 bytes into the stream and the stream is 10 bytes over
482
- // the cap: byte-exact drops 10 bytes of leading junk and keeps it, while a
483
- // whole-chunk drop takes the payload with it.
484
- // The assertion has to depend on the HEAD surviving — asserting on the tail
485
- // passes under both granularities and proves nothing (verified: the first
486
- // cut of this test was green against the chunk-granular implementation it
487
- // was written to reject). So the tail carries NO `loggedIn`, and the only
488
- // status document in the stream is the one 50 bytes in.
489
- const OVERFLOW = 10;
490
- const head = `${'z'.repeat(50)}${PAYLOAD}`;
491
- const tail = '\nmise: done\n';
492
- process.env.MAUDE_CLAUDE_BIN = NOISY;
493
- process.env.FAKE_CLAUDE_HEAD = head;
494
- process.env.FAKE_CLAUDE_PAD_BYTES = String((1 << 20) + OVERFLOW - head.length - tail.length);
495
- process.env.FAKE_CLAUDE_TAIL = tail;
496
- expect((await getClaudeAuthStatus())?.subscriptionType).toBe('max');
497
- });
498
-
499
- test('a hostile apiProvider is bounded for display but still trips the billing warning (F5)', async () => {
500
- // Unbounded attacker text used to render verbatim inside a trusted status
501
- // row. It must be neutralised WITHOUT becoming undefined — that would read
502
- // as "no provider" and silently suppress the metered-billing warning.
503
- const evil = `{"loggedIn":true,"apiProvider":"expired — re-authorize at https://evil.example/${'A'.repeat(300)}"}`;
504
- const status = await statusFor(evil);
505
- expect(status?.apiProvider).toBe('unrecognized');
506
- expect(status?.apiProvider).not.toBe('firstParty'); // → offSubscription warning still fires
507
- });
508
-
509
- test('a non-string loggedIn is not truthy-coerced into a sign-in', async () => {
510
- expect((await statusFor('{"loggedIn":"yes"}'))?.loggedIn).toBe(false);
511
- expect((await statusFor('{"loggedIn":1}'))?.loggedIn).toBe(false);
512
- });
513
- });
514
-
515
- // Defender M2 / attacker F3 — the probe MUST always resolve. `.text()` waits for
516
- // pipe EOF and `proc.kill()` signals only the direct child, so a wrapper whose
517
- // descendants keep fd 1 open (exactly #107's self-recursive shape) wedged the
518
- // read forever: probeReadiness() never resolved, /_api/preflight never answered,
519
- // and the honest "couldn't read" row was unreachable in its own headline case.
520
- describe('getClaudeAuthStatus — a wedged CLI must not wedge the probe', () => {
521
- test('resolves to null within the deadline when a descendant holds stdout open', async () => {
522
- process.env.MAUDE_CLAUDE_BIN = join(import.meta.dir, 'fixtures', 'wedged-claude-auth.sh');
523
- const started = Date.now();
524
- const status = await getClaudeAuthStatus();
525
- const elapsed = Date.now() - started;
526
- expect(status).toBeNull(); // unreadable — NOT a "signed out" claim
527
- expect(elapsed).toBeLessThan(15000); // bounded by AUTH_STATUS_TIMEOUT_MS (5 s) + slack
528
- }, 30000);
529
-
530
- // Round-2 defender L6 — the deadline must not throw away an answer already
531
- // held. A signed-in user whose wrapper backgrounds anything at all printed a
532
- // perfectly good status and still lost AI editing.
533
- test('a status printed before a descendant wedges the pipe is still read', async () => {
534
- process.env.MAUDE_CLAUDE_BIN = join(
535
- import.meta.dir,
536
- 'fixtures',
537
- 'backgrounding-claude-auth.sh'
538
- );
539
- const started = Date.now();
540
- const status = await getClaudeAuthStatus();
541
- expect(status?.loggedIn).toBe(true);
542
- expect(status?.subscriptionType).toBe('max');
543
- expect(Date.now() - started).toBeLessThan(15000);
544
- }, 30000);
545
- });
@@ -1,123 +0,0 @@
1
- // Pure unit tests for client/panels/acp-capabilities.js — the parsing +
2
- // persisted-pick-resolution helpers the dynamic CapabilityBar renders from.
3
- // No hardcoded model/effort/mode list is under test here BY DESIGN: these
4
- // functions must work for whatever the session actually advertises.
5
-
6
- import { describe, expect, test } from 'bun:test';
7
-
8
- import {
9
- flattenSelectOptions,
10
- parseConfigOptions,
11
- parseModes,
12
- resolvePersistedPick,
13
- } from '../client/panels/acp-capabilities.js';
14
-
15
- describe('parseConfigOptions', () => {
16
- test('identifies model/effort/fast by id, excludes the mode-mirror entry, and buckets everything else into others', () => {
17
- const configOptions = [
18
- { id: 'model', category: 'model', currentValue: 'sonnet', options: [] },
19
- { id: 'effort', category: 'thought_level', currentValue: 'default', options: [] },
20
- { id: 'mode', category: 'mode', currentValue: 'default', options: [] },
21
- { id: 'fast', category: 'model_config', currentValue: 'off', options: [] },
22
- { id: 'agent', currentValue: 'default', options: [] }, // no category — the documented gap
23
- { id: 'some-future-option', category: '_custom', currentValue: 'x', options: [] },
24
- ];
25
- const { model, effort, fast, others } = parseConfigOptions(configOptions);
26
- expect(model?.id).toBe('model');
27
- expect(effort?.id).toBe('effort');
28
- expect(fast?.id).toBe('fast');
29
- expect(others.map((o) => o.id)).toEqual(['agent', 'some-future-option']);
30
- });
31
-
32
- test('tolerates missing/empty/malformed input', () => {
33
- expect(parseConfigOptions(undefined)).toEqual({
34
- model: null,
35
- effort: null,
36
- fast: null,
37
- others: [],
38
- });
39
- expect(parseConfigOptions(null)).toEqual({ model: null, effort: null, fast: null, others: [] });
40
- expect(parseConfigOptions([])).toEqual({ model: null, effort: null, fast: null, others: [] });
41
- // A non-object entry must not throw.
42
- expect(parseConfigOptions([null, 'x', 42]).others).toEqual([]);
43
- });
44
-
45
- test('a session with only a subset of options omits the rest as null, not a stale default', () => {
46
- const { model, effort, fast } = parseConfigOptions([{ id: 'model', options: [] }]);
47
- expect(model).not.toBeNull();
48
- expect(effort).toBeNull();
49
- expect(fast).toBeNull();
50
- });
51
- });
52
-
53
- describe('parseModes', () => {
54
- test('returns current + available from a real SessionModeState', () => {
55
- const modes = {
56
- currentModeId: 'plan',
57
- availableModes: [
58
- { id: 'default', name: 'Manual' },
59
- { id: 'plan', name: 'Plan Mode' },
60
- ],
61
- };
62
- expect(parseModes(modes)).toEqual({
63
- current: 'plan',
64
- available: modes.availableModes,
65
- });
66
- });
67
-
68
- test('an agent that never advertises modes yields an empty, non-throwing shape', () => {
69
- expect(parseModes(null)).toEqual({ current: null, available: [] });
70
- expect(parseModes(undefined)).toEqual({ current: null, available: [] });
71
- expect(parseModes({})).toEqual({ current: null, available: [] });
72
- });
73
- });
74
-
75
- describe('flattenSelectOptions', () => {
76
- test('passes through a flat option list unchanged', () => {
77
- const options = [
78
- { value: 'a', name: 'A' },
79
- { value: 'b', name: 'B' },
80
- ];
81
- expect(flattenSelectOptions(options)).toEqual(options);
82
- });
83
-
84
- test('flattens grouped SessionConfigSelectGroup[] into one leaf list', () => {
85
- const options = [
86
- { group: 'g1', name: 'Group 1', options: [{ value: 'a', name: 'A' }] },
87
- {
88
- group: 'g2',
89
- name: 'Group 2',
90
- options: [
91
- { value: 'b', name: 'B' },
92
- { value: 'c', name: 'C' },
93
- ],
94
- },
95
- ];
96
- expect(flattenSelectOptions(options).map((o) => o.value)).toEqual(['a', 'b', 'c']);
97
- });
98
-
99
- test('tolerates missing input', () => {
100
- expect(flattenSelectOptions(undefined)).toEqual([]);
101
- expect(flattenSelectOptions(null)).toEqual([]);
102
- });
103
- });
104
-
105
- describe('resolvePersistedPick', () => {
106
- test('prefers the saved pick when the session still offers it', () => {
107
- expect(resolvePersistedPick(['opus', 'sonnet'], 'opus', 'sonnet')).toBe('opus');
108
- });
109
-
110
- test('falls back to the session default when the saved pick is no longer offered', () => {
111
- expect(resolvePersistedPick(['sonnet', 'haiku'], 'opus', 'sonnet')).toBe('sonnet');
112
- });
113
-
114
- test('falls back to null when there is neither a valid saved pick nor a default', () => {
115
- expect(resolvePersistedPick([], 'opus', undefined)).toBeNull();
116
- expect(resolvePersistedPick([], null, null)).toBeNull();
117
- });
118
-
119
- test('a null/undefined saved value always defers to the default', () => {
120
- expect(resolvePersistedPick(['a', 'b'], null, 'b')).toBe('b');
121
- expect(resolvePersistedPick(['a', 'b'], undefined, 'a')).toBe('a');
122
- });
123
- });