@1agh/maude 1.0.9 → 1.2.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 (506) hide show
  1. package/apps/studio/acp/bridge.ts +18 -13
  2. package/apps/studio/acp/probe.ts +268 -16
  3. package/apps/studio/acp/transcript-io.ts +394 -0
  4. package/apps/studio/acp/transcript.ts +103 -38
  5. package/apps/studio/api.ts +139 -26
  6. package/apps/studio/bin/_import-figma.mjs +57 -11
  7. package/apps/studio/bin/_seed-fixture.mjs +163 -0
  8. package/apps/studio/canvas-imports.ts +89 -0
  9. package/apps/studio/client/app.jsx +257 -7
  10. package/apps/studio/client/export-center.jsx +15 -7
  11. package/apps/studio/client/panels/ReadinessList.jsx +7 -1
  12. package/apps/studio/client/panels/SyncPanel.jsx +125 -4
  13. package/apps/studio/client/report-bug.jsx +19 -1
  14. package/apps/studio/client/styles/3-shell-maude.css +34 -0
  15. package/apps/studio/client/styles/4-components.css +26 -0
  16. package/apps/studio/collab/persistence.ts +132 -16
  17. package/apps/studio/collab/registry.ts +10 -18
  18. package/apps/studio/debug-bundle.ts +13 -0
  19. package/apps/studio/dist/client.bundle.js +1128 -1128
  20. package/apps/studio/dist/styles.css +1 -1
  21. package/apps/studio/export-dialog.tsx +54 -0
  22. package/apps/studio/exporters/degraded.ts +19 -3
  23. package/apps/studio/exporters/ghostscript.ts +194 -0
  24. package/apps/studio/exporters/pdf-fonts.ts +261 -0
  25. package/apps/studio/exporters/pdf.ts +152 -3
  26. package/apps/studio/exporters/remote.ts +36 -1
  27. package/apps/studio/fs-watch.ts +45 -1
  28. package/apps/studio/http.ts +7 -1
  29. package/apps/studio/mem.ts +45 -9
  30. package/apps/studio/readiness.ts +42 -7
  31. package/apps/studio/server.ts +11 -4
  32. package/apps/studio/sync/agent.ts +136 -29
  33. package/apps/studio/sync/asset-push.ts +1 -47
  34. package/apps/studio/sync/codec.ts +122 -31
  35. package/apps/studio/sync/cold-start-apply.ts +7 -0
  36. package/apps/studio/sync/cold-start.ts +118 -22
  37. package/apps/studio/sync/comment-identity.ts +68 -0
  38. package/apps/studio/sync/connection-state.ts +15 -1
  39. package/apps/studio/sync/file-ledger.ts +117 -0
  40. package/apps/studio/sync/file-plane.ts +623 -27
  41. package/apps/studio/sync/index.ts +711 -20
  42. package/apps/studio/sync/journal-client.ts +13 -1
  43. package/apps/studio/sync/migrate-seed.ts +40 -3
  44. package/apps/studio/sync/presentation.ts +26 -3
  45. package/apps/studio/sync/retry-after.ts +169 -0
  46. package/apps/studio/sync/seed-progress.ts +218 -0
  47. package/apps/studio/sync/status.ts +122 -5
  48. package/apps/studio/sync/transport-error.ts +111 -0
  49. package/apps/studio/whats-new.json +91 -0
  50. package/cli/bin/maude.mjs +2 -0
  51. package/cli/commands/codex.mjs +191 -0
  52. package/cli/commands/harness.mjs +1051 -0
  53. package/cli/commands/help.mjs +13 -0
  54. package/cli/lib/design-link.mjs +190 -2
  55. package/cli/lib/harness/capabilities.mjs +302 -0
  56. package/cli/lib/harness/codex-runtime.mjs +889 -0
  57. package/cli/lib/harness/compatibility.mjs +27 -0
  58. package/cli/lib/harness/discover-claude.mjs +1393 -0
  59. package/cli/lib/harness/managed-state.mjs +1296 -0
  60. package/cli/lib/harness/model.mjs +86 -0
  61. package/cli/lib/harness/secrets.mjs +390 -0
  62. package/cli/lib/harness/targets/codex.mjs +1141 -0
  63. package/cli/lib/harness/targets/opencode.mjs +807 -0
  64. package/cli/lib/harness/transaction.mjs +1099 -0
  65. package/cli/lib/hubs-config.mjs +35 -8
  66. package/cli/templates/harness/opencode/maude-projector.ts +58 -0
  67. package/docs/harness-capability-matrix.md +125 -0
  68. package/docs/harness-environment-projection.md +95 -0
  69. package/package.json +32 -9
  70. package/plugins/flow/.claude-plugin/config.schema.json +8 -0
  71. package/apps/studio/.ai/cache/_stats.json +0 -7
  72. package/apps/studio/bin/_agent-browser-safe.test.mjs +0 -165
  73. package/apps/studio/bin/_curl-local.test.mjs +0 -280
  74. package/apps/studio/bin/_pw-launch.test.mjs +0 -90
  75. package/apps/studio/bin/_smart-frames.test.mjs +0 -195
  76. package/apps/studio/bin/_transcribe.test.mjs +0 -80
  77. package/apps/studio/bun.lock +0 -928
  78. package/apps/studio/draw/test/animate.test.ts +0 -172
  79. package/apps/studio/draw/test/brush.test.ts +0 -213
  80. package/apps/studio/draw/test/composition.test.ts +0 -141
  81. package/apps/studio/draw/test/geometry.test.ts +0 -199
  82. package/apps/studio/draw/test/gradient.test.ts +0 -167
  83. package/apps/studio/draw/test/layout.test.ts +0 -120
  84. package/apps/studio/draw/test/morph.test.ts +0 -107
  85. package/apps/studio/draw/test/optimize.test.ts +0 -40
  86. package/apps/studio/draw/test/palette.test.ts +0 -123
  87. package/apps/studio/draw/test/primitives.test.ts +0 -129
  88. package/apps/studio/draw/test/serialize-animate.test.ts +0 -142
  89. package/apps/studio/draw/test/serialize.test.ts +0 -105
  90. package/apps/studio/figma/assets.test.ts +0 -464
  91. package/apps/studio/figma/client.test.ts +0 -395
  92. package/apps/studio/figma/codegen-client.test.ts +0 -276
  93. package/apps/studio/figma/codegen-fonts.test.ts +0 -103
  94. package/apps/studio/figma/codegen-values.test.ts +0 -179
  95. package/apps/studio/figma/comments-to-strokes.test.ts +0 -194
  96. package/apps/studio/figma/fig-decode.test.ts +0 -788
  97. package/apps/studio/figma/fig-differential.test.ts +0 -182
  98. package/apps/studio/figma/fig-translator.test.ts +0 -192
  99. package/apps/studio/figma/fig-vector.test.ts +0 -113
  100. package/apps/studio/figma/from-codegen.test.ts +0 -408
  101. package/apps/studio/figma/sanitize.test.ts +0 -325
  102. package/apps/studio/figma/tailwind-map.test.ts +0 -142
  103. package/apps/studio/figma/to-artboard.test.ts +0 -808
  104. package/apps/studio/figma/to-render.test.ts +0 -180
  105. package/apps/studio/figma/to-strokes-roundtrip.test.ts +0 -152
  106. package/apps/studio/figma/to-strokes.test.ts +0 -705
  107. package/apps/studio/figma/to-tokens.test.ts +0 -321
  108. package/apps/studio/figma/url.test.ts +0 -167
  109. package/apps/studio/footage/schema.test.ts +0 -287
  110. package/apps/studio/generation/adapters/groq.test.ts +0 -80
  111. package/apps/studio/generation/audio-library.test.ts +0 -95
  112. package/apps/studio/generation/captions.test.ts +0 -78
  113. package/apps/studio/generation/elevenlabs.test.ts +0 -223
  114. package/apps/studio/generation/gemini.test.ts +0 -350
  115. package/apps/studio/generation/jobs.test.ts +0 -128
  116. package/apps/studio/generation/keys.test.ts +0 -75
  117. package/apps/studio/generation/prefs.test.ts +0 -95
  118. package/apps/studio/generation/types.test.ts +0 -123
  119. package/apps/studio/generation/whisper-models.test.ts +0 -179
  120. package/apps/studio/test/_helpers.ts +0 -108
  121. package/apps/studio/test/acp-activity-endpoint.test.ts +0 -183
  122. package/apps/studio/test/acp-activity.test.ts +0 -154
  123. package/apps/studio/test/acp-ai-activity.test.ts +0 -182
  124. package/apps/studio/test/acp-attachment-serve.test.ts +0 -74
  125. package/apps/studio/test/acp-bootstrap-brief.test.ts +0 -178
  126. package/apps/studio/test/acp-branch-guard.test.ts +0 -123
  127. package/apps/studio/test/acp-bridge-lifetime.test.ts +0 -475
  128. package/apps/studio/test/acp-bridge.test.ts +0 -301
  129. package/apps/studio/test/acp-capabilities.test.ts +0 -123
  130. package/apps/studio/test/acp-caps-bridge.test.ts +0 -279
  131. package/apps/studio/test/acp-commands.test.ts +0 -119
  132. package/apps/studio/test/acp-elicitation-bridge.test.ts +0 -480
  133. package/apps/studio/test/acp-elicitation.test.ts +0 -251
  134. package/apps/studio/test/acp-env.test.ts +0 -95
  135. package/apps/studio/test/acp-mode-banner.test.ts +0 -45
  136. package/apps/studio/test/acp-origin-gate.test.ts +0 -160
  137. package/apps/studio/test/acp-permission-prompt.test.ts +0 -251
  138. package/apps/studio/test/acp-permission.test.ts +0 -278
  139. package/apps/studio/test/acp-plugin-bootstrap.test.ts +0 -89
  140. package/apps/studio/test/acp-session-allowed-tools.test.ts +0 -235
  141. package/apps/studio/test/acp-session-plugins.test.ts +0 -209
  142. package/apps/studio/test/acp-toolgroup.test.ts +0 -76
  143. package/apps/studio/test/acp-transcript-view.test.ts +0 -71
  144. package/apps/studio/test/acp-transcript.test.ts +0 -238
  145. package/apps/studio/test/acp-usage-bridge.test.ts +0 -141
  146. package/apps/studio/test/acp-usage.test.ts +0 -143
  147. package/apps/studio/test/acp-write-gate.test.ts +0 -323
  148. package/apps/studio/test/acp-write-scope.test.ts +0 -533
  149. package/apps/studio/test/active-state.test.ts +0 -186
  150. package/apps/studio/test/activity.test.ts +0 -189
  151. package/apps/studio/test/ai-activity.test.ts +0 -113
  152. package/apps/studio/test/annotate-write.test.ts +0 -1074
  153. package/apps/studio/test/annotation-strokes-command.test.ts +0 -166
  154. package/apps/studio/test/annotations-align.test.ts +0 -88
  155. package/apps/studio/test/annotations-api.test.ts +0 -145
  156. package/apps/studio/test/annotations-bindings.test.ts +0 -262
  157. package/apps/studio/test/annotations-draw-modifiers.test.ts +0 -206
  158. package/apps/studio/test/annotations-groups.test.ts +0 -231
  159. package/apps/studio/test/annotations-layer.test.ts +0 -1011
  160. package/apps/studio/test/annotations-roundtrip.test.ts +0 -663
  161. package/apps/studio/test/annotations-snap.test.ts +0 -135
  162. package/apps/studio/test/annotations-text-format.test.ts +0 -396
  163. package/apps/studio/test/artboard-activity-overlay.test.tsx +0 -56
  164. package/apps/studio/test/artboard-guides-overlay.test.tsx +0 -152
  165. package/apps/studio/test/artboard-hug-commit.test.ts +0 -36
  166. package/apps/studio/test/artboard-kinds.test.tsx +0 -83
  167. package/apps/studio/test/artboard-selection-attrs.test.ts +0 -69
  168. package/apps/studio/test/asset-api.test.ts +0 -149
  169. package/apps/studio/test/assets-s3.test.ts +0 -249
  170. package/apps/studio/test/binary-smoke.test.ts +0 -47
  171. package/apps/studio/test/boot-self-heal.test.ts +0 -87
  172. package/apps/studio/test/browse-posture.test.tsx +0 -107
  173. package/apps/studio/test/bundle-smoke.test.ts +0 -46
  174. package/apps/studio/test/camera-reveal.test.tsx +0 -173
  175. package/apps/studio/test/canvas-artifacts.test.ts +0 -125
  176. package/apps/studio/test/canvas-build-sandbox.test.ts +0 -69
  177. package/apps/studio/test/canvas-build.test.ts +0 -141
  178. package/apps/studio/test/canvas-create-api.test.ts +0 -676
  179. package/apps/studio/test/canvas-cursors.test.ts +0 -164
  180. package/apps/studio/test/canvas-edit.test.ts +0 -470
  181. package/apps/studio/test/canvas-freshness-deps.test.ts +0 -64
  182. package/apps/studio/test/canvas-header.test.ts +0 -127
  183. package/apps/studio/test/canvas-hide-chrome.test.ts +0 -88
  184. package/apps/studio/test/canvas-hmr-runtime.test.tsx +0 -114
  185. package/apps/studio/test/canvas-lib-inline.test.ts +0 -146
  186. package/apps/studio/test/canvas-lib-motion.test.ts +0 -131
  187. package/apps/studio/test/canvas-lib-resolver.test.ts +0 -111
  188. package/apps/studio/test/canvas-list-watch.test.ts +0 -388
  189. package/apps/studio/test/canvas-media-drop.test.ts +0 -187
  190. package/apps/studio/test/canvas-meta-api.test.ts +0 -707
  191. package/apps/studio/test/canvas-move-api.test.ts +0 -587
  192. package/apps/studio/test/canvas-origin-gate.test.ts +0 -417
  193. package/apps/studio/test/canvas-pipeline.test.ts +0 -180
  194. package/apps/studio/test/canvas-rects.test.ts +0 -198
  195. package/apps/studio/test/canvas-reorder.test.ts +0 -211
  196. package/apps/studio/test/canvas-route.test.ts +0 -179
  197. package/apps/studio/test/canvas-shell-base.test.ts +0 -107
  198. package/apps/studio/test/canvas-shell-build-error.test.ts +0 -52
  199. package/apps/studio/test/canvas-url.test.ts +0 -138
  200. package/apps/studio/test/canvas-wheel-delta.test.ts +0 -42
  201. package/apps/studio/test/canvas-zoom-floor.test.ts +0 -73
  202. package/apps/studio/test/capture-core.test.ts +0 -32
  203. package/apps/studio/test/capture-determinism-shape.test.ts +0 -135
  204. package/apps/studio/test/chat-attachments.test.ts +0 -117
  205. package/apps/studio/test/chat-context.test.ts +0 -129
  206. package/apps/studio/test/chat-markdown.test.tsx +0 -58
  207. package/apps/studio/test/clip-addressing.test.ts +0 -738
  208. package/apps/studio/test/clip-ops.test.ts +0 -922
  209. package/apps/studio/test/cloud-build.test.ts +0 -95
  210. package/apps/studio/test/cloud-connect-note.test.ts +0 -135
  211. package/apps/studio/test/cloud-endpoints.test.ts +0 -590
  212. package/apps/studio/test/cloud-history-hardening.test.ts +0 -165
  213. package/apps/studio/test/cloud-history-posture.test.ts +0 -230
  214. package/apps/studio/test/cloud-managed-save-surfaces.test.ts +0 -90
  215. package/apps/studio/test/cloud-renew.test.ts +0 -205
  216. package/apps/studio/test/cloud-session-role.test.ts +0 -161
  217. package/apps/studio/test/cloud-shell-surfaces.test.ts +0 -170
  218. package/apps/studio/test/cold-start-apply.test.ts +0 -303
  219. package/apps/studio/test/collab-annotations-bridge.test.ts +0 -55
  220. package/apps/studio/test/collab-awareness-bridge.test.ts +0 -223
  221. package/apps/studio/test/collab-bridge.test.ts +0 -79
  222. package/apps/studio/test/collab-loopback.test.ts +0 -63
  223. package/apps/studio/test/collab-origin-gate.test.ts +0 -323
  224. package/apps/studio/test/collab-protocol.test.ts +0 -146
  225. package/apps/studio/test/collab-readonly-gate.test.ts +0 -194
  226. package/apps/studio/test/collab-reseed-guard.test.ts +0 -78
  227. package/apps/studio/test/collab-room.test.ts +0 -188
  228. package/apps/studio/test/collab-session-survival.test.tsx +0 -176
  229. package/apps/studio/test/collab-stress.test.ts +0 -129
  230. package/apps/studio/test/comment-mount.test.ts +0 -97
  231. package/apps/studio/test/comment-relay-origin-gate.test.ts +0 -117
  232. package/apps/studio/test/comments-api.test.ts +0 -277
  233. package/apps/studio/test/comments-fs-rebroadcast.test.ts +0 -155
  234. package/apps/studio/test/comments-overlay.test.ts +0 -117
  235. package/apps/studio/test/compile-entry.test.ts +0 -133
  236. package/apps/studio/test/component-map.test.ts +0 -48
  237. package/apps/studio/test/config-projection.test.ts +0 -104
  238. package/apps/studio/test/config-reload.test.ts +0 -230
  239. package/apps/studio/test/config-version.test.ts +0 -88
  240. package/apps/studio/test/context-resolve-tokens.test.ts +0 -97
  241. package/apps/studio/test/convert-to-absolute.test.ts +0 -333
  242. package/apps/studio/test/credential-grammar.test.ts +0 -107
  243. package/apps/studio/test/csp-canvas-shell.test.ts +0 -62
  244. package/apps/studio/test/csrf-write-guard.test.ts +0 -141
  245. package/apps/studio/test/debug-bundle.test.ts +0 -105
  246. package/apps/studio/test/design-setup-readiness.test.ts +0 -155
  247. package/apps/studio/test/detach-component.test.ts +0 -94
  248. package/apps/studio/test/dns-rebinding-guard.test.ts +0 -101
  249. package/apps/studio/test/dom-selection.test.ts +0 -198
  250. package/apps/studio/test/dynamic-text-edit.test.ts +0 -162
  251. package/apps/studio/test/edit-css-occurrence.test.ts +0 -81
  252. package/apps/studio/test/edit-persistence.test.ts +0 -91
  253. package/apps/studio/test/edit-scope-api.test.ts +0 -119
  254. package/apps/studio/test/edit-source-command.test.ts +0 -142
  255. package/apps/studio/test/edit-suppress.test.ts +0 -170
  256. package/apps/studio/test/editing-presence.test.ts +0 -103
  257. package/apps/studio/test/element-resize.test.ts +0 -136
  258. package/apps/studio/test/element-structural-api.test.ts +0 -434
  259. package/apps/studio/test/element-structural-edit.test.ts +0 -750
  260. package/apps/studio/test/ensure-browser.test.ts +0 -92
  261. package/apps/studio/test/equal-spacing-detector.test.ts +0 -257
  262. package/apps/studio/test/export-assemble.test.ts +0 -53
  263. package/apps/studio/test/export-browser-lane.test.ts +0 -126
  264. package/apps/studio/test/export-capture-fidelity.test.ts +0 -146
  265. package/apps/studio/test/export-capture-hygiene.test.ts +0 -675
  266. package/apps/studio/test/export-center.test.tsx +0 -287
  267. package/apps/studio/test/export-e2e-lanes.test.ts +0 -857
  268. package/apps/studio/test/export-format-scope-coherence.test.ts +0 -89
  269. package/apps/studio/test/export-lane.test.ts +0 -245
  270. package/apps/studio/test/export-shim-multi-capture.test.ts +0 -83
  271. package/apps/studio/test/exporters/canva.test.ts +0 -63
  272. package/apps/studio/test/exporters/degraded-propagation.test.ts +0 -123
  273. package/apps/studio/test/exporters/endpoint.test.ts +0 -121
  274. package/apps/studio/test/exporters/history.test.ts +0 -107
  275. package/apps/studio/test/exporters/html.test.ts +0 -26
  276. package/apps/studio/test/exporters/jobs.test.ts +0 -342
  277. package/apps/studio/test/exporters/pdf.test.ts +0 -85
  278. package/apps/studio/test/exporters/png.test.ts +0 -103
  279. package/apps/studio/test/exporters/pptx-deck.test.ts +0 -112
  280. package/apps/studio/test/exporters/pptx.test.ts +0 -31
  281. package/apps/studio/test/exporters/pw-launch.test.ts +0 -49
  282. package/apps/studio/test/exporters/runtime.test.ts +0 -59
  283. package/apps/studio/test/exporters/scope.test.ts +0 -292
  284. package/apps/studio/test/exporters/svg.test.ts +0 -29
  285. package/apps/studio/test/exporters/unsupported-media.test.ts +0 -123
  286. package/apps/studio/test/exporters/zip.test.ts +0 -104
  287. package/apps/studio/test/fetch-asset-gate.test.ts +0 -189
  288. package/apps/studio/test/figjam-v3-model.test.ts +0 -342
  289. package/apps/studio/test/figma-explode.test.ts +0 -438
  290. package/apps/studio/test/figma-provenance.test.ts +0 -108
  291. package/apps/studio/test/figma-routes.test.ts +0 -294
  292. package/apps/studio/test/file-lock.test.ts +0 -84
  293. package/apps/studio/test/fixtures/fake-claude-auth.mjs +0 -13
  294. package/apps/studio/test/fixtures/figjam-v3-groups-bindings.svg +0 -1
  295. package/apps/studio/test/fixtures/mock-acp-agent-caps.mjs +0 -158
  296. package/apps/studio/test/fixtures/mock-acp-agent-commands.mjs +0 -44
  297. package/apps/studio/test/fixtures/mock-acp-agent-elicit-flood.mjs +0 -46
  298. package/apps/studio/test/fixtures/mock-acp-agent-elicit-url.mjs +0 -42
  299. package/apps/studio/test/fixtures/mock-acp-agent-elicit.mjs +0 -63
  300. package/apps/studio/test/fixtures/mock-acp-agent-permission-flood.mjs +0 -51
  301. package/apps/studio/test/fixtures/mock-acp-agent-permission.mjs +0 -50
  302. package/apps/studio/test/fixtures/mock-acp-agent-slow.mjs +0 -45
  303. package/apps/studio/test/fixtures/mock-acp-agent-usage.mjs +0 -56
  304. package/apps/studio/test/fixtures/mock-acp-agent-wedged.mjs +0 -37
  305. package/apps/studio/test/fixtures/mock-acp-agent-write.mjs +0 -118
  306. package/apps/studio/test/fixtures/mock-acp-agent.mjs +0 -69
  307. package/apps/studio/test/fixtures/perf-canvas.mjs +0 -201
  308. package/apps/studio/test/fixtures/phase-20-annotations.svg +0 -1
  309. package/apps/studio/test/fixtures/phase-21-annotations.svg +0 -1
  310. package/apps/studio/test/fixtures/tiny-subset.woff2 +0 -0
  311. package/apps/studio/test/fixtures/video-comp-fixture.tsx +0 -82
  312. package/apps/studio/test/footage-store.test.ts +0 -100
  313. package/apps/studio/test/fs-mkdir-api.test.ts +0 -248
  314. package/apps/studio/test/fs-watch.test.ts +0 -41
  315. package/apps/studio/test/gemma-models.test.ts +0 -245
  316. package/apps/studio/test/generate-route.test.ts +0 -106
  317. package/apps/studio/test/git-api.test.ts +0 -0
  318. package/apps/studio/test/git-branches.test.ts +0 -341
  319. package/apps/studio/test/git-cloud-posture.test.ts +0 -59
  320. package/apps/studio/test/git-grouping.test.ts +0 -106
  321. package/apps/studio/test/git-lifecycle.test.ts +0 -100
  322. package/apps/studio/test/git-log-format.test.ts +0 -95
  323. package/apps/studio/test/git-safe-rel.test.ts +0 -132
  324. package/apps/studio/test/git-system-engine.test.ts +0 -135
  325. package/apps/studio/test/git-watch.test.ts +0 -97
  326. package/apps/studio/test/github-api.test.ts +0 -577
  327. package/apps/studio/test/grid-track-handles.test.ts +0 -160
  328. package/apps/studio/test/handoff-static-frames.test.ts +0 -215
  329. package/apps/studio/test/handoff.test.ts +0 -329
  330. package/apps/studio/test/history-rollback.test.ts +0 -88
  331. package/apps/studio/test/hmr-broadcast.test.ts +0 -236
  332. package/apps/studio/test/hmr-classify.test.ts +0 -87
  333. package/apps/studio/test/hub-link.test.ts +0 -80
  334. package/apps/studio/test/identity-cache.test.ts +0 -83
  335. package/apps/studio/test/import-asset-browser.test.ts +0 -64
  336. package/apps/studio/test/import-asset-route.test.ts +0 -71
  337. package/apps/studio/test/import-asset.test.ts +0 -427
  338. package/apps/studio/test/import-brand-browser.test.ts +0 -88
  339. package/apps/studio/test/import-brand-route.test.ts +0 -120
  340. package/apps/studio/test/import-brand.test.ts +0 -206
  341. package/apps/studio/test/import-figma.test.ts +0 -667
  342. package/apps/studio/test/import-tokens.test.ts +0 -722
  343. package/apps/studio/test/index-data-canvas-ds.test.ts +0 -97
  344. package/apps/studio/test/input-router.test.ts +0 -757
  345. package/apps/studio/test/inspect-script-syntax.test.ts +0 -53
  346. package/apps/studio/test/inspect-selections.test.ts +0 -219
  347. package/apps/studio/test/knob-props-authored.test.ts +0 -87
  348. package/apps/studio/test/layers-synthetic-groups.test.ts +0 -96
  349. package/apps/studio/test/locator.test.ts +0 -214
  350. package/apps/studio/test/marquee-overlay.test.ts +0 -94
  351. package/apps/studio/test/media-commit-chain.test.ts +0 -210
  352. package/apps/studio/test/mode-toggle.test.tsx +0 -226
  353. package/apps/studio/test/motion-sample.test.ts +0 -35
  354. package/apps/studio/test/move-artboards-command.test.ts +0 -108
  355. package/apps/studio/test/participants-chrome.test.ts +0 -65
  356. package/apps/studio/test/paths.test.ts +0 -88
  357. package/apps/studio/test/pdf-print-boxes.test.ts +0 -359
  358. package/apps/studio/test/peer-selection-follows-camera.test.tsx +0 -131
  359. package/apps/studio/test/perf-harness.ts +0 -193
  360. package/apps/studio/test/phase-3.6-smoke.test.ts +0 -79
  361. package/apps/studio/test/photo-bg-remove-validation.test.ts +0 -71
  362. package/apps/studio/test/photo-canvas-bundle.test.ts +0 -79
  363. package/apps/studio/test/photo-edit-api.test.ts +0 -201
  364. package/apps/studio/test/photo-filters.test.ts +0 -149
  365. package/apps/studio/test/photo-pipeline.test.ts +0 -106
  366. package/apps/studio/test/photo-taxonomy.test.ts +0 -96
  367. package/apps/studio/test/print-marks.test.ts +0 -113
  368. package/apps/studio/test/print-units.test.ts +0 -173
  369. package/apps/studio/test/read-annotations.test.ts +0 -727
  370. package/apps/studio/test/read-only-gate.test.ts +0 -188
  371. package/apps/studio/test/read-only-role.test.ts +0 -95
  372. package/apps/studio/test/readiness.test.ts +0 -226
  373. package/apps/studio/test/remote-ahead-behind-cache.test.ts +0 -89
  374. package/apps/studio/test/reorder-api.test.ts +0 -210
  375. package/apps/studio/test/repo-concurrency.test.ts +0 -146
  376. package/apps/studio/test/repo-lock.test.ts +0 -350
  377. package/apps/studio/test/report-proxy.test.ts +0 -125
  378. package/apps/studio/test/ripple.test.ts +0 -197
  379. package/apps/studio/test/runtime-bundle-error-mapping.test.ts +0 -43
  380. package/apps/studio/test/runtime-bundle.test.ts +0 -69
  381. package/apps/studio/test/sanitize-annotation-svg.test.ts +0 -238
  382. package/apps/studio/test/scaffold-design.test.ts +0 -122
  383. package/apps/studio/test/server-lifecycle.test.ts +0 -38
  384. package/apps/studio/test/session-runtime-state.test.ts +0 -226
  385. package/apps/studio/test/session-scope.test.ts +0 -100
  386. package/apps/studio/test/shared-doc-cell-pairing.test.ts +0 -642
  387. package/apps/studio/test/shared-doc-convergence.test.ts +0 -265
  388. package/apps/studio/test/shared-doc-foundation.test.ts +0 -63
  389. package/apps/studio/test/shared-doc-migrate.test.ts +0 -369
  390. package/apps/studio/test/shared-doc-projection.test.ts +0 -238
  391. package/apps/studio/test/shell-importmap.test.ts +0 -56
  392. package/apps/studio/test/sizing-mode.test.ts +0 -102
  393. package/apps/studio/test/slash-commands.test.ts +0 -117
  394. package/apps/studio/test/snap-distance-pill.test.ts +0 -68
  395. package/apps/studio/test/spacing-handles.test.ts +0 -138
  396. package/apps/studio/test/specimen-select.test.ts +0 -88
  397. package/apps/studio/test/sync-agent.test.ts +0 -660
  398. package/apps/studio/test/sync-annotations-cold-start.test.ts +0 -302
  399. package/apps/studio/test/sync-asset-push.test.ts +0 -931
  400. package/apps/studio/test/sync-atomic-write.test.ts +0 -62
  401. package/apps/studio/test/sync-attach-incremental.test.ts +0 -527
  402. package/apps/studio/test/sync-autocommit.test.ts +0 -537
  403. package/apps/studio/test/sync-canvas-path.test.ts +0 -200
  404. package/apps/studio/test/sync-cell-write-nudge.test.ts +0 -346
  405. package/apps/studio/test/sync-codec.test.ts +0 -230
  406. package/apps/studio/test/sync-cold-seed-dedup.test.ts +0 -187
  407. package/apps/studio/test/sync-cold-start.test.ts +0 -304
  408. package/apps/studio/test/sync-connect-honesty.test.ts +0 -114
  409. package/apps/studio/test/sync-connection-state.test.ts +0 -337
  410. package/apps/studio/test/sync-ctl-channel.test.ts +0 -508
  411. package/apps/studio/test/sync-decide-file.test.ts +0 -420
  412. package/apps/studio/test/sync-doc-name.test.ts +0 -281
  413. package/apps/studio/test/sync-echo-guard.test.ts +0 -96
  414. package/apps/studio/test/sync-file-ledger.test.ts +0 -334
  415. package/apps/studio/test/sync-file-membership.test.ts +0 -347
  416. package/apps/studio/test/sync-file-plane.test.ts +0 -976
  417. package/apps/studio/test/sync-file-pull.test.ts +0 -333
  418. package/apps/studio/test/sync-fresh-link-parity.test.ts +0 -267
  419. package/apps/studio/test/sync-fs-mirror.test.ts +0 -181
  420. package/apps/studio/test/sync-hardening.test.ts +0 -586
  421. package/apps/studio/test/sync-hub-listing.test.ts +0 -46
  422. package/apps/studio/test/sync-hubs-config.test.ts +0 -133
  423. package/apps/studio/test/sync-incident-replay.test.ts +0 -211
  424. package/apps/studio/test/sync-journal.test.ts +0 -176
  425. package/apps/studio/test/sync-meta-codec.test.ts +0 -199
  426. package/apps/studio/test/sync-migrate-flat-fallback.test.ts +0 -98
  427. package/apps/studio/test/sync-move-retirement.test.ts +0 -231
  428. package/apps/studio/test/sync-panel-surface.test.ts +0 -153
  429. package/apps/studio/test/sync-path-pull.test.ts +0 -531
  430. package/apps/studio/test/sync-presentation.test.ts +0 -262
  431. package/apps/studio/test/sync-pull-budget.test.ts +0 -169
  432. package/apps/studio/test/sync-remote-docs.test.ts +0 -333
  433. package/apps/studio/test/sync-resync-routes.test.ts +0 -168
  434. package/apps/studio/test/sync-runtime.test.ts +0 -1675
  435. package/apps/studio/test/sync-seed-defers-to-hub.test.ts +0 -83
  436. package/apps/studio/test/sync-settings-routes.test.ts +0 -195
  437. package/apps/studio/test/sync-settings.test.ts +0 -151
  438. package/apps/studio/test/sync-status.test.ts +0 -261
  439. package/apps/studio/test/sync-supervisor.test.ts +0 -262
  440. package/apps/studio/test/sync-tombstone-apply.test.ts +0 -111
  441. package/apps/studio/test/sync-trash.test.ts +0 -132
  442. package/apps/studio/test/sync-two-peer-discovery.test.ts +0 -343
  443. package/apps/studio/test/sync-untrusted.test.ts +0 -103
  444. package/apps/studio/test/system-endpoint.test.ts +0 -144
  445. package/apps/studio/test/text-editability-stamp.test.ts +0 -131
  446. package/apps/studio/test/timeline-comp-target.test.ts +0 -139
  447. package/apps/studio/test/timeline-media-cache.test.ts +0 -41
  448. package/apps/studio/test/timeline-parse.test.ts +0 -184
  449. package/apps/studio/test/timeline-scale.test.ts +0 -65
  450. package/apps/studio/test/timeline-snap.test.ts +0 -85
  451. package/apps/studio/test/tool-palette-insert-anchor.test.ts +0 -86
  452. package/apps/studio/test/tool-palette.test.tsx +0 -55
  453. package/apps/studio/test/tour-overlay.test.tsx +0 -90
  454. package/apps/studio/test/trusted-request-host.test.ts +0 -66
  455. package/apps/studio/test/undo-sequence-byte-compare.test.ts +0 -211
  456. package/apps/studio/test/undo-stack.test.ts +0 -244
  457. package/apps/studio/test/use-agent-presence.test.tsx +0 -117
  458. package/apps/studio/test/use-annotation-resize.test.ts +0 -402
  459. package/apps/studio/test/use-annotation-selection.test.tsx +0 -77
  460. package/apps/studio/test/use-artboard-drag.test.ts +0 -324
  461. package/apps/studio/test/use-canvas-activity.test.tsx +0 -206
  462. package/apps/studio/test/use-collab.test.ts +0 -0
  463. package/apps/studio/test/use-cursor-modifiers.test.ts +0 -59
  464. package/apps/studio/test/use-keyboard-discipline.test.ts +0 -27
  465. package/apps/studio/test/use-selection-set.test.tsx +0 -166
  466. package/apps/studio/test/use-snap-guides.test.ts +0 -271
  467. package/apps/studio/test/use-tool-mode.test.tsx +0 -186
  468. package/apps/studio/test/use-undo-stack.test.tsx +0 -223
  469. package/apps/studio/test/video-asset.test.ts +0 -163
  470. package/apps/studio/test/video-comp-fixture.test.ts +0 -50
  471. package/apps/studio/test/video-comp.test.ts +0 -270
  472. package/apps/studio/test/video-encode-lib.test.ts +0 -63
  473. package/apps/studio/test/video-render-bridge.test.ts +0 -171
  474. package/apps/studio/test/whats-new.test.ts +0 -113
  475. package/apps/studio/test/whisper-setup.test.ts +0 -97
  476. package/apps/studio/test/workspace-containment.test.ts +0 -382
  477. package/apps/studio/test/workspace-signin.test.ts +0 -292
  478. package/apps/studio/test/ws-handshake.test.ts +0 -33
  479. package/cli/commands/cache.test.mjs +0 -166
  480. package/cli/commands/design-link.test.mjs +0 -419
  481. package/cli/commands/design.test.mjs +0 -240
  482. package/cli/commands/doctor.test.mjs +0 -237
  483. package/cli/commands/hub-workspace.test.mjs +0 -275
  484. package/cli/commands/hub.test.mjs +0 -225
  485. package/cli/commands/kg.test.mjs +0 -142
  486. package/cli/commands/preflight.test.mjs +0 -46
  487. package/cli/lib/argv.test.mjs +0 -46
  488. package/cli/lib/cache.test.mjs +0 -303
  489. package/cli/lib/cell-plan.test.mjs +0 -225
  490. package/cli/lib/config-lint.test.mjs +0 -117
  491. package/cli/lib/ddr-to-kgai.test.mjs +0 -118
  492. package/cli/lib/design-ownership.test.mjs +0 -329
  493. package/cli/lib/fetch-asset.test.mjs +0 -213
  494. package/cli/lib/figma-codegen-reachability.test.mjs +0 -104
  495. package/cli/lib/figma-import-controls.test.mjs +0 -70
  496. package/cli/lib/flow-design-integration.test.mjs +0 -165
  497. package/cli/lib/gitignore-block.test.mjs +0 -136
  498. package/cli/lib/gitignore-drift.test.mjs +0 -156
  499. package/cli/lib/hubs-config.test.mjs +0 -165
  500. package/cli/lib/pkg-root.test.mjs +0 -111
  501. package/cli/lib/plugin-cli-reachability.test.mjs +0 -57
  502. package/cli/lib/plugin-name-namespace.test.mjs +0 -71
  503. package/cli/lib/reconstruct-toolset.test.mjs +0 -194
  504. package/cli/lib/stack-detect.test.mjs +0 -121
  505. package/cli/lib/update-check.test.mjs +0 -32
  506. package/cli/lib/workspace-plan.test.mjs +0 -699
@@ -8,7 +8,6 @@
8
8
  // (env.ts): the child inherits the environment MINUS `ANTHROPIC_API_KEY`, so
9
9
  // auth precedence falls through to the user's Pro/Max subscription.
10
10
 
11
- import { readFileSync } from 'node:fs';
12
11
  import { appendFile, mkdir, readFile, writeFile } from 'node:fs/promises';
13
12
  import { dirname } from 'node:path';
14
13
 
@@ -32,6 +31,7 @@ import {
32
31
  import { scrubAgentEnv } from './env.ts';
33
32
  import type { SdkPluginConfig } from './plugin-bootstrap.ts';
34
33
  import { resolveAdapterEntry, resolveAgentRuntime, resolveClaudePath } from './probe.ts';
34
+ import { countTranscriptLinesAt, stripInlineBlobs } from './transcript-io.ts';
35
35
  import {
36
36
  isWriteToolName,
37
37
  looksLikeWriteToolCall,
@@ -207,18 +207,16 @@ type Spawned = ReturnType<typeof Bun.spawn>;
207
207
  const VALID_SESSION_ID = /^[A-Za-z0-9_-]{1,128}$/;
208
208
 
209
209
  /** Raw non-empty line count of a transcript file — the re-attach seam's seed.
210
- * Deliberately duplicated from `transcript.ts`'s `chatTranscriptSeq` rather
211
- * than imported: importing would pull the transcript READER (and its
212
- * designRoot/chatId path convention) into the bridge, which knows only an
213
- * absolute file path. The two MUST count identically — raw non-empty lines,
214
- * never parsed lines, since a corrupt line would otherwise shift every later
215
- * seq and permanently desync the seam. */
210
+ * Delegates to the ONE shared implementation in `transcript-io.ts`, which
211
+ * `transcript.ts`'s `chatTranscriptSeq` also calls. These two counters MUST
212
+ * agree — a disagreement shifts every later seq and permanently desyncs the
213
+ * seam — and until #119 they were two hand-copied bodies kept in step only by
214
+ * a comment. `transcript-io.ts` takes an absolute path and nothing else, so
215
+ * sharing it does NOT drag the reader's designRoot/chatId path convention into
216
+ * the bridge (the original reason for the duplication). It also counts in
217
+ * bounded chunks rather than materializing a multi-hundred-MB file. */
216
218
  function countTranscriptLines(path: string): number {
217
- try {
218
- return readFileSync(path, 'utf8').split('\n').filter(Boolean).length;
219
- } catch {
220
- return 0; // no transcript yet — first turn of this chat
221
- }
219
+ return countTranscriptLinesAt(path);
222
220
  }
223
221
 
224
222
  // `loadSession`'s replay can, in principle, never settle if the underlying
@@ -1760,7 +1758,14 @@ export class AcpBridge {
1760
1758
  if (claimedSeq === undefined) this.transcriptLines += 1;
1761
1759
  try {
1762
1760
  await mkdir(dirname(path), { recursive: true });
1763
- await appendFile(path, `${JSON.stringify({ ts: Date.now(), ...entry })}\n`);
1761
+ // #119 — elide inline binary payloads (base64 image/audio `data`, `data:`
1762
+ // URIs) before they reach disk. A tool result carrying a screenshot was
1763
+ // persisted verbatim at up to 1.36 MB PER LINE, and nothing ever read it
1764
+ // back: `readChatMessages` projects a tool call down to its name and
1765
+ // done-flag. In a screenshot-heavy design project that was 66–76% of the
1766
+ // transcript's bytes. Prose, tool arguments and file contents are NOT
1767
+ // touched — those are the audit record of what steered the agent.
1768
+ await appendFile(path, `${JSON.stringify(stripInlineBlobs({ ts: Date.now(), ...entry }))}\n`);
1764
1769
  } catch {
1765
1770
  /* transcript is best-effort; never block the chat on disk errors */
1766
1771
  }
@@ -222,39 +222,284 @@ export interface ClaudeAuthStatus {
222
222
  subscriptionType?: string;
223
223
  }
224
224
 
225
+ // A `claude` on PATH is not always the real binary. A version-manager shim
226
+ // (mise, asdf, volta) or a corporate `bin` wrapper routinely fronts it, and
227
+ // several of those print a banner to STDOUT before `exec`ing the real thing —
228
+ // `stderr: 'ignore'` doesn't help, because the noise isn't on stderr.
229
+ //
230
+ // Issue #107: `~/.local/bin/claude` was a mise shim whose `mise use -g claude`
231
+ // confirmation line landed ahead of the JSON. `JSON.parse(wholeStdout)` threw,
232
+ // the blanket catch returned null, and the readiness gate told a signed-in user
233
+ // they were signed OUT — then sent them to a Sign-in button that could only ever
234
+ // end in "Sign-in timed out", because the 2 s poll re-ran the same deterministic
235
+ // parse failure for 120 s.
236
+ //
237
+ // So: extract the JSON OBJECT from the blob rather than parsing the blob. This
238
+ // widens nothing about what is TRUSTED — the binary was already resolved and
239
+ // spawned by us, and the caller still narrows to three fields.
240
+ //
241
+ // The scan runs BACKWARDS from the end, and every giving-up path returns null
242
+ // rather than a best guess. Both properties are security-review findings against
243
+ // the first cut of this function (defender M1, attacker F1/F2), and they are the
244
+ // same lesson twice: a parser that fails SOFT re-creates #107 through a new door.
245
+ // • Forwards + a candidate budget meant noise was scanned first and the real
246
+ // payload could fall outside the window — 99 bare `{}` in a 242-byte prefix
247
+ // pushed a genuine `loggedIn:false` out of scan range and let a forged
248
+ // `loggedIn:true` win, turning a fail-CLOSED `JSON.parse` throw into
249
+ // fail-OPEN. Backwards makes the budget bite on the noise side, where it
250
+ // belongs, and matches the actual invariant: a wrapper's banner is written
251
+ // BEFORE it execs, so the real payload is the LAST thing on stdout.
252
+ // • Truncation now keeps the TAIL, for the same reason. Slicing the head threw
253
+ // away the answer and kept the noise.
254
+ // • Returning "the last object that parsed at all" meant an NDJSON-logging
255
+ // wrapper's `{"level":"info",...}` became `loggedIn: !!undefined` → a
256
+ // confident false "not signed in", with the dead-end Sign-in button armed.
257
+ // An object with no `loggedIn` is not a status document; null is.
258
+
259
+ /** Enough for any plausible banner + payload; a shim writes a line, not a stream. */
260
+ const AUTH_STATUS_SCAN_LIMIT = 1 << 20;
261
+ /** Bounds the candidate scan so a pathological blob of `{` can't become a CPU sink. */
262
+ const AUTH_STATUS_MAX_CANDIDATES = 100;
263
+ /** Wall clock for the whole read. The probe MUST resolve — see `readBounded`. */
264
+ const AUTH_STATUS_TIMEOUT_MS = 5000;
265
+
266
+ /** Index of the `}` closing the `{` at `from`, or -1. String- and escape-aware, so a brace inside a JSON string never miscounts. */
267
+ function matchingBrace(text: string, from: number): number {
268
+ let depth = 0;
269
+ let inString = false;
270
+ let escaped = false;
271
+ for (let i = from; i < text.length; i++) {
272
+ const ch = text[i];
273
+ if (inString) {
274
+ if (escaped) escaped = false;
275
+ else if (ch === '\\') escaped = true;
276
+ else if (ch === '"') inString = false;
277
+ continue;
278
+ }
279
+ if (ch === '"') inString = true;
280
+ else if (ch === '{') depth++;
281
+ else if (ch === '}' && --depth === 0) return i;
282
+ }
283
+ return -1;
284
+ }
285
+
286
+ /**
287
+ * The last `{…}` in `out` that parses to an object carrying `loggedIn`, found by
288
+ * scanning `{` positions backwards from the end. Returns null when no such object
289
+ * is in range — including when the candidate budget runs out, which is a scan that
290
+ * did not finish, not a status that says "signed out". The caller must render null
291
+ * as "couldn't read", never as a claim about the user.
292
+ */
293
+ function extractAuthStatusObject(out: string): Record<string, unknown> | null {
294
+ // Keep the TAIL on truncation: the payload trails the noise, so the end is the
295
+ // half worth having.
296
+ const text = out.length > AUTH_STATUS_SCAN_LIMIT ? out.slice(-AUTH_STATUS_SCAN_LIMIT) : out;
297
+ let tried = 0;
298
+ let start = text.lastIndexOf('{');
299
+ while (start !== -1 && ++tried <= AUTH_STATUS_MAX_CANDIDATES) {
300
+ const end = matchingBrace(text, start);
301
+ if (end !== -1) {
302
+ try {
303
+ const parsed: unknown = JSON.parse(text.slice(start, end + 1));
304
+ if (
305
+ typeof parsed === 'object' &&
306
+ parsed !== null &&
307
+ !Array.isArray(parsed) &&
308
+ 'loggedIn' in (parsed as Record<string, unknown>)
309
+ ) {
310
+ return parsed as Record<string, unknown>;
311
+ }
312
+ } catch {
313
+ /* noise that merely looked like an object — keep walking backwards */
314
+ }
315
+ }
316
+ // `lastIndexOf(needle, -1)` clamps to 0 and would re-test index 0 for the
317
+ // whole remaining budget (round-2 defender L7) — stop instead.
318
+ if (start === 0) break;
319
+ start = text.lastIndexOf('{', start - 1);
320
+ }
321
+ return null;
322
+ }
323
+
324
+ /**
325
+ * Read `stream` to EOF, bounded by `timeoutMs` and by a `maxBytes` sliding TAIL
326
+ * window. Always returns whatever bytes it managed to collect — never null.
327
+ *
328
+ * Security-review finding (defender M2 / attacker F3): the previous
329
+ * `await new Response(proc.stdout).text()` resolves on pipe EOF, and `proc.kill()`
330
+ * signals only the DIRECT child. #107's proven root cause was a self-recursive
331
+ * wrapper, i.e. exactly the shape that leaves descendants holding fd 1 after the
332
+ * kill — so the read never completed, `probeReadiness()` never resolved, and
333
+ * `GET /_api/preflight` hung. The honest-unknown path this whole change exists to
334
+ * render was unreachable in its own headline scenario.
335
+ *
336
+ * Round-2 finding (defender L6): the first cut of this function returned null on
337
+ * BOTH bounds, throwing away a payload it was already holding. A signed-in user
338
+ * whose wrapper backgrounds anything at all — a daemon, an update check, a
339
+ * corporate agent — printed a perfectly good status, then lost AI editing to a
340
+ * deadline that fired on the still-open pipe. Honest, but the answer was in the
341
+ * buffer. Returning it cannot fabricate a status: `extractAuthStatusObject` still
342
+ * demands a parsed `loggedIn`-bearing object, so a partial or garbled tail is
343
+ * still null.
344
+ *
345
+ * The cap is a sliding window for the same reason the scan runs backwards — the
346
+ * payload trails the noise, so when we must drop bytes we drop the OLDEST. The
347
+ * drop is byte-exact, not chunk-granular (attacker R3-2).
348
+ *
349
+ * ACCEPTED TRADE, stated plainly because the round-2 docstring overclaimed
350
+ * (attacker R3-1): **the deadline path is no longer fail-closed.** This function
351
+ * cannot distinguish "this is all of stdout" from "this is the first 5 s of
352
+ * stdout", so a deadline landing mid-stream answers from whatever complete object
353
+ * precedes the cut — a stale status where round-2 code returned null. That is
354
+ * accepted, not overlooked: the alternative cost a signed-in user AI editing for
355
+ * the far more common backgrounding-wrapper shape, and forcing the stale answer
356
+ * requires controlling the writer's timing, which is the owner position — from
357
+ * there the binary can simply return whatever status it likes.
358
+ */
359
+ async function readBounded(
360
+ stream: ReadableStream<Uint8Array>,
361
+ maxBytes: number,
362
+ timeoutMs: number
363
+ ): Promise<string> {
364
+ const reader = stream.getReader();
365
+ const chunks: Uint8Array[] = [];
366
+ let total = 0;
367
+ const deadline = Date.now() + timeoutMs;
368
+ try {
369
+ for (;;) {
370
+ const left = deadline - Date.now();
371
+ if (left <= 0) break;
372
+ let timer: ReturnType<typeof setTimeout> | undefined;
373
+ const expired = Symbol('expired');
374
+ const next = await Promise.race([
375
+ reader.read(),
376
+ new Promise<typeof expired>((resolve) => {
377
+ timer = setTimeout(() => resolve(expired), left);
378
+ }),
379
+ ]);
380
+ if (timer !== undefined) clearTimeout(timer);
381
+ if (next === expired) break;
382
+ if (next.done) break;
383
+ if (next.value?.byteLength) {
384
+ chunks.push(next.value);
385
+ total += next.value.byteLength;
386
+ // Bound memory DURING the read, not by materialising the whole blob and
387
+ // slicing after — a fast writer has no practical ceiling inside the
388
+ // timeout window otherwise.
389
+ // Byte-exact, one branch (attacker R3-2). Shifting whole chunks
390
+ // overshot by up to `sizeof(oldest chunk) - 1` — measured 262 144 B
391
+ // over-dropped for a 150-byte overflow against a real Bun pipe, so the
392
+ // effective window was as little as 75 % of the advertised cap, while
393
+ // the sibling single-chunk branch four lines away was byte-exact. Same
394
+ // window, two granularities, and the doc claimed the byte one.
395
+ while (total > maxBytes) {
396
+ const first = chunks[0];
397
+ if (!first) break;
398
+ const over = total - maxBytes;
399
+ if (first.byteLength <= over) {
400
+ chunks.shift();
401
+ total -= first.byteLength;
402
+ } else {
403
+ chunks[0] = first.subarray(over);
404
+ total -= over;
405
+ }
406
+ }
407
+ }
408
+ }
409
+ } finally {
410
+ try {
411
+ await reader.cancel();
412
+ } catch {
413
+ /* already closed */
414
+ }
415
+ }
416
+ const joined = new Uint8Array(total);
417
+ let at = 0;
418
+ for (const c of chunks) {
419
+ joined.set(c, at);
420
+ at += c.byteLength;
421
+ }
422
+ // A dropped leading chunk can split a UTF-8 sequence; the decoder emits U+FFFD
423
+ // there, which the `{`-scan simply walks past.
424
+ return new TextDecoder().decode(joined);
425
+ }
426
+
427
+ /**
428
+ * Narrow a status tag for DISPLAY. Attacker F5: `apiProvider` is interpolated
429
+ * verbatim into the readiness row, so an unbounded, unsanitized value is
430
+ * arbitrary text inside a trusted system-status line. Anything that isn't a
431
+ * short identifier becomes the literal `unrecognized` — deliberately NOT
432
+ * `undefined`, which would read as "no provider" and silently suppress the
433
+ * metered-billing warning (`offSubscription` in readiness.ts tests `!== 'firstParty'`).
434
+ */
435
+ function narrowStatusTag(value: unknown): string | undefined {
436
+ if (typeof value !== 'string') return undefined;
437
+ return /^[A-Za-z0-9_-]{1,32}$/.test(value) ? value : 'unrecognized';
438
+ }
439
+
225
440
  /**
226
441
  * Shells `claude auth status --json` and narrows the result to only the fields
227
442
  * ever trusted/exposed here. `email`/`orgId`/`orgName` are deliberately dropped
228
443
  * on read — never forwarded to a caller, never logged (security review finding:
229
444
  * raw-stdout-to-log is a real habit elsewhere in this codebase; the fix is to
230
445
  * never let the raw payload exist past this function).
446
+ *
447
+ * Returns null for BOTH "no CLI" and "couldn't read a status out of it" — see
448
+ * `readiness.ts`, which must not render the latter as a positive "not signed
449
+ * in" claim (issue #107).
231
450
  */
232
451
  export async function getClaudeAuthStatus(): Promise<ClaudeAuthStatus | null> {
233
452
  const bin = resolveClaudePath();
234
453
  if (!bin) return null;
235
454
  try {
236
- const proc = Bun.spawn([bin, 'auth', 'status', '--json'], {
237
- env: scrubAgentEnv(),
238
- stdout: 'pipe',
239
- stderr: 'ignore',
240
- });
241
- const timeout = setTimeout(() => proc.kill(), 5000);
242
- const out = await new Response(proc.stdout).text();
243
- await proc.exited;
244
- clearTimeout(timeout);
245
- const parsed: unknown = JSON.parse(out);
246
- if (typeof parsed !== 'object' || parsed === null) return null;
247
- const p = parsed as Record<string, unknown>;
455
+ // Whatever bytes arrived inside the deadline — possibly empty, possibly a
456
+ // truncated tail. `extractAuthStatusObject` is the only thing that decides
457
+ // whether that is a status document.
458
+ const p = extractAuthStatusObject(await readAuthStatusStdout(bin));
459
+ if (!p) return null;
248
460
  return {
249
- loggedIn: !!p.loggedIn,
250
- apiProvider: typeof p.apiProvider === 'string' ? p.apiProvider : undefined,
251
- subscriptionType: typeof p.subscriptionType === 'string' ? p.subscriptionType : undefined,
461
+ // Strict `=== true`, not `!!` — a wrapper's `"loggedIn":"no"` or `:1` must
462
+ // not truthy-coerce into a sign-in (attacker F5's neighbour).
463
+ loggedIn: p.loggedIn === true,
464
+ apiProvider: narrowStatusTag(p.apiProvider),
465
+ subscriptionType: narrowStatusTag(p.subscriptionType),
252
466
  };
253
467
  } catch {
254
468
  return null;
255
469
  }
256
470
  }
257
471
 
472
+ /**
473
+ * Spawn + bounded read, kept in its own function so the `Bun.spawn` call stays
474
+ * inline with its literal options — that is what gives `proc.stdout` its precise
475
+ * `ReadableStream` type instead of the widened `number | ReadableStream | undefined`.
476
+ *
477
+ * Deliberately does NOT await `proc.exited`: a self-recursive wrapper's
478
+ * descendants can outlive the kill, and this function's contract is that it
479
+ * always resolves. The bounded read already has whatever bytes exist.
480
+ */
481
+ async function readAuthStatusStdout(bin: string): Promise<string> {
482
+ const proc = Bun.spawn([bin, 'auth', 'status', '--json'], {
483
+ env: scrubAgentEnv(),
484
+ // A wrapper that prompts would otherwise block forever on a tty-less read.
485
+ stdin: 'ignore',
486
+ stdout: 'pipe',
487
+ stderr: 'ignore',
488
+ });
489
+ try {
490
+ return await readBounded(proc.stdout, AUTH_STATUS_SCAN_LIMIT, AUTH_STATUS_TIMEOUT_MS);
491
+ } finally {
492
+ // Best-effort reap on every path, including the deadline one. This kills the
493
+ // DIRECT child only — an orphaned descendant tree is a known residual, but it
494
+ // can no longer wedge the probe (see `readBounded`).
495
+ try {
496
+ proc.kill();
497
+ } catch {
498
+ /* already exited */
499
+ }
500
+ }
501
+ }
502
+
258
503
  /**
259
504
  * The fuller availability check backing `GET /_api/acp/status` — the ONE
260
505
  * signal the ChatPanel UI's connected/not-connected gate actually watches
@@ -280,7 +525,14 @@ export async function probeAcpAvailabilityAuthed(): Promise<AcpAvailability> {
280
525
  return {
281
526
  ...base,
282
527
  available: false,
283
- reason: 'Claude Code is installed but not signed in — sign in to connect.',
528
+ // `base.available` is true here, so the CLI DID resolve — a null status
529
+ // therefore means "we couldn't read it", not "signed out". Say so
530
+ // (issue #107): telling a signed-in user they aren't is a claim they
531
+ // have no way to act on, and it hides the real cause.
532
+ reason:
533
+ authStatus === null
534
+ ? "Claude Code is installed, but Maude couldn't read its sign-in state — check Help ▸ Check AI editing readiness."
535
+ : 'Claude Code is installed but not signed in — sign in to connect.',
284
536
  };
285
537
  }
286
538
  return base;