@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.
- package/apps/studio/acp/bridge.ts +18 -13
- package/apps/studio/acp/probe.ts +268 -16
- package/apps/studio/acp/transcript-io.ts +394 -0
- package/apps/studio/acp/transcript.ts +103 -38
- package/apps/studio/api.ts +139 -26
- package/apps/studio/bin/_import-figma.mjs +57 -11
- package/apps/studio/bin/_seed-fixture.mjs +163 -0
- package/apps/studio/canvas-imports.ts +89 -0
- package/apps/studio/client/app.jsx +257 -7
- package/apps/studio/client/export-center.jsx +15 -7
- package/apps/studio/client/panels/ReadinessList.jsx +7 -1
- package/apps/studio/client/panels/SyncPanel.jsx +125 -4
- package/apps/studio/client/report-bug.jsx +19 -1
- package/apps/studio/client/styles/3-shell-maude.css +34 -0
- package/apps/studio/client/styles/4-components.css +26 -0
- package/apps/studio/collab/persistence.ts +132 -16
- package/apps/studio/collab/registry.ts +10 -18
- package/apps/studio/debug-bundle.ts +13 -0
- package/apps/studio/dist/client.bundle.js +1128 -1128
- package/apps/studio/dist/styles.css +1 -1
- package/apps/studio/export-dialog.tsx +54 -0
- package/apps/studio/exporters/degraded.ts +19 -3
- package/apps/studio/exporters/ghostscript.ts +194 -0
- package/apps/studio/exporters/pdf-fonts.ts +261 -0
- package/apps/studio/exporters/pdf.ts +152 -3
- package/apps/studio/exporters/remote.ts +36 -1
- package/apps/studio/fs-watch.ts +45 -1
- package/apps/studio/http.ts +7 -1
- package/apps/studio/mem.ts +45 -9
- package/apps/studio/readiness.ts +42 -7
- package/apps/studio/server.ts +11 -4
- package/apps/studio/sync/agent.ts +136 -29
- package/apps/studio/sync/asset-push.ts +1 -47
- package/apps/studio/sync/codec.ts +122 -31
- package/apps/studio/sync/cold-start-apply.ts +7 -0
- package/apps/studio/sync/cold-start.ts +118 -22
- package/apps/studio/sync/comment-identity.ts +68 -0
- package/apps/studio/sync/connection-state.ts +15 -1
- package/apps/studio/sync/file-ledger.ts +117 -0
- package/apps/studio/sync/file-plane.ts +623 -27
- package/apps/studio/sync/index.ts +711 -20
- package/apps/studio/sync/journal-client.ts +13 -1
- package/apps/studio/sync/migrate-seed.ts +40 -3
- package/apps/studio/sync/presentation.ts +26 -3
- package/apps/studio/sync/retry-after.ts +169 -0
- package/apps/studio/sync/seed-progress.ts +218 -0
- package/apps/studio/sync/status.ts +122 -5
- package/apps/studio/sync/transport-error.ts +111 -0
- package/apps/studio/whats-new.json +91 -0
- package/cli/bin/maude.mjs +2 -0
- package/cli/commands/codex.mjs +191 -0
- package/cli/commands/harness.mjs +1051 -0
- package/cli/commands/help.mjs +13 -0
- package/cli/lib/design-link.mjs +190 -2
- package/cli/lib/harness/capabilities.mjs +302 -0
- package/cli/lib/harness/codex-runtime.mjs +889 -0
- package/cli/lib/harness/compatibility.mjs +27 -0
- package/cli/lib/harness/discover-claude.mjs +1393 -0
- package/cli/lib/harness/managed-state.mjs +1296 -0
- package/cli/lib/harness/model.mjs +86 -0
- package/cli/lib/harness/secrets.mjs +390 -0
- package/cli/lib/harness/targets/codex.mjs +1141 -0
- package/cli/lib/harness/targets/opencode.mjs +807 -0
- package/cli/lib/harness/transaction.mjs +1099 -0
- package/cli/lib/hubs-config.mjs +35 -8
- package/cli/templates/harness/opencode/maude-projector.ts +58 -0
- package/docs/harness-capability-matrix.md +125 -0
- package/docs/harness-environment-projection.md +95 -0
- package/package.json +32 -9
- package/plugins/flow/.claude-plugin/config.schema.json +8 -0
- package/apps/studio/.ai/cache/_stats.json +0 -7
- package/apps/studio/bin/_agent-browser-safe.test.mjs +0 -165
- package/apps/studio/bin/_curl-local.test.mjs +0 -280
- package/apps/studio/bin/_pw-launch.test.mjs +0 -90
- package/apps/studio/bin/_smart-frames.test.mjs +0 -195
- package/apps/studio/bin/_transcribe.test.mjs +0 -80
- package/apps/studio/bun.lock +0 -928
- package/apps/studio/draw/test/animate.test.ts +0 -172
- package/apps/studio/draw/test/brush.test.ts +0 -213
- package/apps/studio/draw/test/composition.test.ts +0 -141
- package/apps/studio/draw/test/geometry.test.ts +0 -199
- package/apps/studio/draw/test/gradient.test.ts +0 -167
- package/apps/studio/draw/test/layout.test.ts +0 -120
- package/apps/studio/draw/test/morph.test.ts +0 -107
- package/apps/studio/draw/test/optimize.test.ts +0 -40
- package/apps/studio/draw/test/palette.test.ts +0 -123
- package/apps/studio/draw/test/primitives.test.ts +0 -129
- package/apps/studio/draw/test/serialize-animate.test.ts +0 -142
- package/apps/studio/draw/test/serialize.test.ts +0 -105
- package/apps/studio/figma/assets.test.ts +0 -464
- package/apps/studio/figma/client.test.ts +0 -395
- package/apps/studio/figma/codegen-client.test.ts +0 -276
- package/apps/studio/figma/codegen-fonts.test.ts +0 -103
- package/apps/studio/figma/codegen-values.test.ts +0 -179
- package/apps/studio/figma/comments-to-strokes.test.ts +0 -194
- package/apps/studio/figma/fig-decode.test.ts +0 -788
- package/apps/studio/figma/fig-differential.test.ts +0 -182
- package/apps/studio/figma/fig-translator.test.ts +0 -192
- package/apps/studio/figma/fig-vector.test.ts +0 -113
- package/apps/studio/figma/from-codegen.test.ts +0 -408
- package/apps/studio/figma/sanitize.test.ts +0 -325
- package/apps/studio/figma/tailwind-map.test.ts +0 -142
- package/apps/studio/figma/to-artboard.test.ts +0 -808
- package/apps/studio/figma/to-render.test.ts +0 -180
- package/apps/studio/figma/to-strokes-roundtrip.test.ts +0 -152
- package/apps/studio/figma/to-strokes.test.ts +0 -705
- package/apps/studio/figma/to-tokens.test.ts +0 -321
- package/apps/studio/figma/url.test.ts +0 -167
- package/apps/studio/footage/schema.test.ts +0 -287
- package/apps/studio/generation/adapters/groq.test.ts +0 -80
- package/apps/studio/generation/audio-library.test.ts +0 -95
- package/apps/studio/generation/captions.test.ts +0 -78
- package/apps/studio/generation/elevenlabs.test.ts +0 -223
- package/apps/studio/generation/gemini.test.ts +0 -350
- package/apps/studio/generation/jobs.test.ts +0 -128
- package/apps/studio/generation/keys.test.ts +0 -75
- package/apps/studio/generation/prefs.test.ts +0 -95
- package/apps/studio/generation/types.test.ts +0 -123
- package/apps/studio/generation/whisper-models.test.ts +0 -179
- package/apps/studio/test/_helpers.ts +0 -108
- package/apps/studio/test/acp-activity-endpoint.test.ts +0 -183
- package/apps/studio/test/acp-activity.test.ts +0 -154
- package/apps/studio/test/acp-ai-activity.test.ts +0 -182
- package/apps/studio/test/acp-attachment-serve.test.ts +0 -74
- package/apps/studio/test/acp-bootstrap-brief.test.ts +0 -178
- package/apps/studio/test/acp-branch-guard.test.ts +0 -123
- package/apps/studio/test/acp-bridge-lifetime.test.ts +0 -475
- package/apps/studio/test/acp-bridge.test.ts +0 -301
- package/apps/studio/test/acp-capabilities.test.ts +0 -123
- package/apps/studio/test/acp-caps-bridge.test.ts +0 -279
- package/apps/studio/test/acp-commands.test.ts +0 -119
- package/apps/studio/test/acp-elicitation-bridge.test.ts +0 -480
- package/apps/studio/test/acp-elicitation.test.ts +0 -251
- package/apps/studio/test/acp-env.test.ts +0 -95
- package/apps/studio/test/acp-mode-banner.test.ts +0 -45
- package/apps/studio/test/acp-origin-gate.test.ts +0 -160
- package/apps/studio/test/acp-permission-prompt.test.ts +0 -251
- package/apps/studio/test/acp-permission.test.ts +0 -278
- package/apps/studio/test/acp-plugin-bootstrap.test.ts +0 -89
- package/apps/studio/test/acp-session-allowed-tools.test.ts +0 -235
- package/apps/studio/test/acp-session-plugins.test.ts +0 -209
- package/apps/studio/test/acp-toolgroup.test.ts +0 -76
- package/apps/studio/test/acp-transcript-view.test.ts +0 -71
- package/apps/studio/test/acp-transcript.test.ts +0 -238
- package/apps/studio/test/acp-usage-bridge.test.ts +0 -141
- package/apps/studio/test/acp-usage.test.ts +0 -143
- package/apps/studio/test/acp-write-gate.test.ts +0 -323
- package/apps/studio/test/acp-write-scope.test.ts +0 -533
- package/apps/studio/test/active-state.test.ts +0 -186
- package/apps/studio/test/activity.test.ts +0 -189
- package/apps/studio/test/ai-activity.test.ts +0 -113
- package/apps/studio/test/annotate-write.test.ts +0 -1074
- package/apps/studio/test/annotation-strokes-command.test.ts +0 -166
- package/apps/studio/test/annotations-align.test.ts +0 -88
- package/apps/studio/test/annotations-api.test.ts +0 -145
- package/apps/studio/test/annotations-bindings.test.ts +0 -262
- package/apps/studio/test/annotations-draw-modifiers.test.ts +0 -206
- package/apps/studio/test/annotations-groups.test.ts +0 -231
- package/apps/studio/test/annotations-layer.test.ts +0 -1011
- package/apps/studio/test/annotations-roundtrip.test.ts +0 -663
- package/apps/studio/test/annotations-snap.test.ts +0 -135
- package/apps/studio/test/annotations-text-format.test.ts +0 -396
- package/apps/studio/test/artboard-activity-overlay.test.tsx +0 -56
- package/apps/studio/test/artboard-guides-overlay.test.tsx +0 -152
- package/apps/studio/test/artboard-hug-commit.test.ts +0 -36
- package/apps/studio/test/artboard-kinds.test.tsx +0 -83
- package/apps/studio/test/artboard-selection-attrs.test.ts +0 -69
- package/apps/studio/test/asset-api.test.ts +0 -149
- package/apps/studio/test/assets-s3.test.ts +0 -249
- package/apps/studio/test/binary-smoke.test.ts +0 -47
- package/apps/studio/test/boot-self-heal.test.ts +0 -87
- package/apps/studio/test/browse-posture.test.tsx +0 -107
- package/apps/studio/test/bundle-smoke.test.ts +0 -46
- package/apps/studio/test/camera-reveal.test.tsx +0 -173
- package/apps/studio/test/canvas-artifacts.test.ts +0 -125
- package/apps/studio/test/canvas-build-sandbox.test.ts +0 -69
- package/apps/studio/test/canvas-build.test.ts +0 -141
- package/apps/studio/test/canvas-create-api.test.ts +0 -676
- package/apps/studio/test/canvas-cursors.test.ts +0 -164
- package/apps/studio/test/canvas-edit.test.ts +0 -470
- package/apps/studio/test/canvas-freshness-deps.test.ts +0 -64
- package/apps/studio/test/canvas-header.test.ts +0 -127
- package/apps/studio/test/canvas-hide-chrome.test.ts +0 -88
- package/apps/studio/test/canvas-hmr-runtime.test.tsx +0 -114
- package/apps/studio/test/canvas-lib-inline.test.ts +0 -146
- package/apps/studio/test/canvas-lib-motion.test.ts +0 -131
- package/apps/studio/test/canvas-lib-resolver.test.ts +0 -111
- package/apps/studio/test/canvas-list-watch.test.ts +0 -388
- package/apps/studio/test/canvas-media-drop.test.ts +0 -187
- package/apps/studio/test/canvas-meta-api.test.ts +0 -707
- package/apps/studio/test/canvas-move-api.test.ts +0 -587
- package/apps/studio/test/canvas-origin-gate.test.ts +0 -417
- package/apps/studio/test/canvas-pipeline.test.ts +0 -180
- package/apps/studio/test/canvas-rects.test.ts +0 -198
- package/apps/studio/test/canvas-reorder.test.ts +0 -211
- package/apps/studio/test/canvas-route.test.ts +0 -179
- package/apps/studio/test/canvas-shell-base.test.ts +0 -107
- package/apps/studio/test/canvas-shell-build-error.test.ts +0 -52
- package/apps/studio/test/canvas-url.test.ts +0 -138
- package/apps/studio/test/canvas-wheel-delta.test.ts +0 -42
- package/apps/studio/test/canvas-zoom-floor.test.ts +0 -73
- package/apps/studio/test/capture-core.test.ts +0 -32
- package/apps/studio/test/capture-determinism-shape.test.ts +0 -135
- package/apps/studio/test/chat-attachments.test.ts +0 -117
- package/apps/studio/test/chat-context.test.ts +0 -129
- package/apps/studio/test/chat-markdown.test.tsx +0 -58
- package/apps/studio/test/clip-addressing.test.ts +0 -738
- package/apps/studio/test/clip-ops.test.ts +0 -922
- package/apps/studio/test/cloud-build.test.ts +0 -95
- package/apps/studio/test/cloud-connect-note.test.ts +0 -135
- package/apps/studio/test/cloud-endpoints.test.ts +0 -590
- package/apps/studio/test/cloud-history-hardening.test.ts +0 -165
- package/apps/studio/test/cloud-history-posture.test.ts +0 -230
- package/apps/studio/test/cloud-managed-save-surfaces.test.ts +0 -90
- package/apps/studio/test/cloud-renew.test.ts +0 -205
- package/apps/studio/test/cloud-session-role.test.ts +0 -161
- package/apps/studio/test/cloud-shell-surfaces.test.ts +0 -170
- package/apps/studio/test/cold-start-apply.test.ts +0 -303
- package/apps/studio/test/collab-annotations-bridge.test.ts +0 -55
- package/apps/studio/test/collab-awareness-bridge.test.ts +0 -223
- package/apps/studio/test/collab-bridge.test.ts +0 -79
- package/apps/studio/test/collab-loopback.test.ts +0 -63
- package/apps/studio/test/collab-origin-gate.test.ts +0 -323
- package/apps/studio/test/collab-protocol.test.ts +0 -146
- package/apps/studio/test/collab-readonly-gate.test.ts +0 -194
- package/apps/studio/test/collab-reseed-guard.test.ts +0 -78
- package/apps/studio/test/collab-room.test.ts +0 -188
- package/apps/studio/test/collab-session-survival.test.tsx +0 -176
- package/apps/studio/test/collab-stress.test.ts +0 -129
- package/apps/studio/test/comment-mount.test.ts +0 -97
- package/apps/studio/test/comment-relay-origin-gate.test.ts +0 -117
- package/apps/studio/test/comments-api.test.ts +0 -277
- package/apps/studio/test/comments-fs-rebroadcast.test.ts +0 -155
- package/apps/studio/test/comments-overlay.test.ts +0 -117
- package/apps/studio/test/compile-entry.test.ts +0 -133
- package/apps/studio/test/component-map.test.ts +0 -48
- package/apps/studio/test/config-projection.test.ts +0 -104
- package/apps/studio/test/config-reload.test.ts +0 -230
- package/apps/studio/test/config-version.test.ts +0 -88
- package/apps/studio/test/context-resolve-tokens.test.ts +0 -97
- package/apps/studio/test/convert-to-absolute.test.ts +0 -333
- package/apps/studio/test/credential-grammar.test.ts +0 -107
- package/apps/studio/test/csp-canvas-shell.test.ts +0 -62
- package/apps/studio/test/csrf-write-guard.test.ts +0 -141
- package/apps/studio/test/debug-bundle.test.ts +0 -105
- package/apps/studio/test/design-setup-readiness.test.ts +0 -155
- package/apps/studio/test/detach-component.test.ts +0 -94
- package/apps/studio/test/dns-rebinding-guard.test.ts +0 -101
- package/apps/studio/test/dom-selection.test.ts +0 -198
- package/apps/studio/test/dynamic-text-edit.test.ts +0 -162
- package/apps/studio/test/edit-css-occurrence.test.ts +0 -81
- package/apps/studio/test/edit-persistence.test.ts +0 -91
- package/apps/studio/test/edit-scope-api.test.ts +0 -119
- package/apps/studio/test/edit-source-command.test.ts +0 -142
- package/apps/studio/test/edit-suppress.test.ts +0 -170
- package/apps/studio/test/editing-presence.test.ts +0 -103
- package/apps/studio/test/element-resize.test.ts +0 -136
- package/apps/studio/test/element-structural-api.test.ts +0 -434
- package/apps/studio/test/element-structural-edit.test.ts +0 -750
- package/apps/studio/test/ensure-browser.test.ts +0 -92
- package/apps/studio/test/equal-spacing-detector.test.ts +0 -257
- package/apps/studio/test/export-assemble.test.ts +0 -53
- package/apps/studio/test/export-browser-lane.test.ts +0 -126
- package/apps/studio/test/export-capture-fidelity.test.ts +0 -146
- package/apps/studio/test/export-capture-hygiene.test.ts +0 -675
- package/apps/studio/test/export-center.test.tsx +0 -287
- package/apps/studio/test/export-e2e-lanes.test.ts +0 -857
- package/apps/studio/test/export-format-scope-coherence.test.ts +0 -89
- package/apps/studio/test/export-lane.test.ts +0 -245
- package/apps/studio/test/export-shim-multi-capture.test.ts +0 -83
- package/apps/studio/test/exporters/canva.test.ts +0 -63
- package/apps/studio/test/exporters/degraded-propagation.test.ts +0 -123
- package/apps/studio/test/exporters/endpoint.test.ts +0 -121
- package/apps/studio/test/exporters/history.test.ts +0 -107
- package/apps/studio/test/exporters/html.test.ts +0 -26
- package/apps/studio/test/exporters/jobs.test.ts +0 -342
- package/apps/studio/test/exporters/pdf.test.ts +0 -85
- package/apps/studio/test/exporters/png.test.ts +0 -103
- package/apps/studio/test/exporters/pptx-deck.test.ts +0 -112
- package/apps/studio/test/exporters/pptx.test.ts +0 -31
- package/apps/studio/test/exporters/pw-launch.test.ts +0 -49
- package/apps/studio/test/exporters/runtime.test.ts +0 -59
- package/apps/studio/test/exporters/scope.test.ts +0 -292
- package/apps/studio/test/exporters/svg.test.ts +0 -29
- package/apps/studio/test/exporters/unsupported-media.test.ts +0 -123
- package/apps/studio/test/exporters/zip.test.ts +0 -104
- package/apps/studio/test/fetch-asset-gate.test.ts +0 -189
- package/apps/studio/test/figjam-v3-model.test.ts +0 -342
- package/apps/studio/test/figma-explode.test.ts +0 -438
- package/apps/studio/test/figma-provenance.test.ts +0 -108
- package/apps/studio/test/figma-routes.test.ts +0 -294
- package/apps/studio/test/file-lock.test.ts +0 -84
- package/apps/studio/test/fixtures/fake-claude-auth.mjs +0 -13
- package/apps/studio/test/fixtures/figjam-v3-groups-bindings.svg +0 -1
- package/apps/studio/test/fixtures/mock-acp-agent-caps.mjs +0 -158
- package/apps/studio/test/fixtures/mock-acp-agent-commands.mjs +0 -44
- package/apps/studio/test/fixtures/mock-acp-agent-elicit-flood.mjs +0 -46
- package/apps/studio/test/fixtures/mock-acp-agent-elicit-url.mjs +0 -42
- package/apps/studio/test/fixtures/mock-acp-agent-elicit.mjs +0 -63
- package/apps/studio/test/fixtures/mock-acp-agent-permission-flood.mjs +0 -51
- package/apps/studio/test/fixtures/mock-acp-agent-permission.mjs +0 -50
- package/apps/studio/test/fixtures/mock-acp-agent-slow.mjs +0 -45
- package/apps/studio/test/fixtures/mock-acp-agent-usage.mjs +0 -56
- package/apps/studio/test/fixtures/mock-acp-agent-wedged.mjs +0 -37
- package/apps/studio/test/fixtures/mock-acp-agent-write.mjs +0 -118
- package/apps/studio/test/fixtures/mock-acp-agent.mjs +0 -69
- package/apps/studio/test/fixtures/perf-canvas.mjs +0 -201
- package/apps/studio/test/fixtures/phase-20-annotations.svg +0 -1
- package/apps/studio/test/fixtures/phase-21-annotations.svg +0 -1
- package/apps/studio/test/fixtures/tiny-subset.woff2 +0 -0
- package/apps/studio/test/fixtures/video-comp-fixture.tsx +0 -82
- package/apps/studio/test/footage-store.test.ts +0 -100
- package/apps/studio/test/fs-mkdir-api.test.ts +0 -248
- package/apps/studio/test/fs-watch.test.ts +0 -41
- package/apps/studio/test/gemma-models.test.ts +0 -245
- package/apps/studio/test/generate-route.test.ts +0 -106
- package/apps/studio/test/git-api.test.ts +0 -0
- package/apps/studio/test/git-branches.test.ts +0 -341
- package/apps/studio/test/git-cloud-posture.test.ts +0 -59
- package/apps/studio/test/git-grouping.test.ts +0 -106
- package/apps/studio/test/git-lifecycle.test.ts +0 -100
- package/apps/studio/test/git-log-format.test.ts +0 -95
- package/apps/studio/test/git-safe-rel.test.ts +0 -132
- package/apps/studio/test/git-system-engine.test.ts +0 -135
- package/apps/studio/test/git-watch.test.ts +0 -97
- package/apps/studio/test/github-api.test.ts +0 -577
- package/apps/studio/test/grid-track-handles.test.ts +0 -160
- package/apps/studio/test/handoff-static-frames.test.ts +0 -215
- package/apps/studio/test/handoff.test.ts +0 -329
- package/apps/studio/test/history-rollback.test.ts +0 -88
- package/apps/studio/test/hmr-broadcast.test.ts +0 -236
- package/apps/studio/test/hmr-classify.test.ts +0 -87
- package/apps/studio/test/hub-link.test.ts +0 -80
- package/apps/studio/test/identity-cache.test.ts +0 -83
- package/apps/studio/test/import-asset-browser.test.ts +0 -64
- package/apps/studio/test/import-asset-route.test.ts +0 -71
- package/apps/studio/test/import-asset.test.ts +0 -427
- package/apps/studio/test/import-brand-browser.test.ts +0 -88
- package/apps/studio/test/import-brand-route.test.ts +0 -120
- package/apps/studio/test/import-brand.test.ts +0 -206
- package/apps/studio/test/import-figma.test.ts +0 -667
- package/apps/studio/test/import-tokens.test.ts +0 -722
- package/apps/studio/test/index-data-canvas-ds.test.ts +0 -97
- package/apps/studio/test/input-router.test.ts +0 -757
- package/apps/studio/test/inspect-script-syntax.test.ts +0 -53
- package/apps/studio/test/inspect-selections.test.ts +0 -219
- package/apps/studio/test/knob-props-authored.test.ts +0 -87
- package/apps/studio/test/layers-synthetic-groups.test.ts +0 -96
- package/apps/studio/test/locator.test.ts +0 -214
- package/apps/studio/test/marquee-overlay.test.ts +0 -94
- package/apps/studio/test/media-commit-chain.test.ts +0 -210
- package/apps/studio/test/mode-toggle.test.tsx +0 -226
- package/apps/studio/test/motion-sample.test.ts +0 -35
- package/apps/studio/test/move-artboards-command.test.ts +0 -108
- package/apps/studio/test/participants-chrome.test.ts +0 -65
- package/apps/studio/test/paths.test.ts +0 -88
- package/apps/studio/test/pdf-print-boxes.test.ts +0 -359
- package/apps/studio/test/peer-selection-follows-camera.test.tsx +0 -131
- package/apps/studio/test/perf-harness.ts +0 -193
- package/apps/studio/test/phase-3.6-smoke.test.ts +0 -79
- package/apps/studio/test/photo-bg-remove-validation.test.ts +0 -71
- package/apps/studio/test/photo-canvas-bundle.test.ts +0 -79
- package/apps/studio/test/photo-edit-api.test.ts +0 -201
- package/apps/studio/test/photo-filters.test.ts +0 -149
- package/apps/studio/test/photo-pipeline.test.ts +0 -106
- package/apps/studio/test/photo-taxonomy.test.ts +0 -96
- package/apps/studio/test/print-marks.test.ts +0 -113
- package/apps/studio/test/print-units.test.ts +0 -173
- package/apps/studio/test/read-annotations.test.ts +0 -727
- package/apps/studio/test/read-only-gate.test.ts +0 -188
- package/apps/studio/test/read-only-role.test.ts +0 -95
- package/apps/studio/test/readiness.test.ts +0 -226
- package/apps/studio/test/remote-ahead-behind-cache.test.ts +0 -89
- package/apps/studio/test/reorder-api.test.ts +0 -210
- package/apps/studio/test/repo-concurrency.test.ts +0 -146
- package/apps/studio/test/repo-lock.test.ts +0 -350
- package/apps/studio/test/report-proxy.test.ts +0 -125
- package/apps/studio/test/ripple.test.ts +0 -197
- package/apps/studio/test/runtime-bundle-error-mapping.test.ts +0 -43
- package/apps/studio/test/runtime-bundle.test.ts +0 -69
- package/apps/studio/test/sanitize-annotation-svg.test.ts +0 -238
- package/apps/studio/test/scaffold-design.test.ts +0 -122
- package/apps/studio/test/server-lifecycle.test.ts +0 -38
- package/apps/studio/test/session-runtime-state.test.ts +0 -226
- package/apps/studio/test/session-scope.test.ts +0 -100
- package/apps/studio/test/shared-doc-cell-pairing.test.ts +0 -642
- package/apps/studio/test/shared-doc-convergence.test.ts +0 -265
- package/apps/studio/test/shared-doc-foundation.test.ts +0 -63
- package/apps/studio/test/shared-doc-migrate.test.ts +0 -369
- package/apps/studio/test/shared-doc-projection.test.ts +0 -238
- package/apps/studio/test/shell-importmap.test.ts +0 -56
- package/apps/studio/test/sizing-mode.test.ts +0 -102
- package/apps/studio/test/slash-commands.test.ts +0 -117
- package/apps/studio/test/snap-distance-pill.test.ts +0 -68
- package/apps/studio/test/spacing-handles.test.ts +0 -138
- package/apps/studio/test/specimen-select.test.ts +0 -88
- package/apps/studio/test/sync-agent.test.ts +0 -660
- package/apps/studio/test/sync-annotations-cold-start.test.ts +0 -302
- package/apps/studio/test/sync-asset-push.test.ts +0 -931
- package/apps/studio/test/sync-atomic-write.test.ts +0 -62
- package/apps/studio/test/sync-attach-incremental.test.ts +0 -527
- package/apps/studio/test/sync-autocommit.test.ts +0 -537
- package/apps/studio/test/sync-canvas-path.test.ts +0 -200
- package/apps/studio/test/sync-cell-write-nudge.test.ts +0 -346
- package/apps/studio/test/sync-codec.test.ts +0 -230
- package/apps/studio/test/sync-cold-seed-dedup.test.ts +0 -187
- package/apps/studio/test/sync-cold-start.test.ts +0 -304
- package/apps/studio/test/sync-connect-honesty.test.ts +0 -114
- package/apps/studio/test/sync-connection-state.test.ts +0 -337
- package/apps/studio/test/sync-ctl-channel.test.ts +0 -508
- package/apps/studio/test/sync-decide-file.test.ts +0 -420
- package/apps/studio/test/sync-doc-name.test.ts +0 -281
- package/apps/studio/test/sync-echo-guard.test.ts +0 -96
- package/apps/studio/test/sync-file-ledger.test.ts +0 -334
- package/apps/studio/test/sync-file-membership.test.ts +0 -347
- package/apps/studio/test/sync-file-plane.test.ts +0 -976
- package/apps/studio/test/sync-file-pull.test.ts +0 -333
- package/apps/studio/test/sync-fresh-link-parity.test.ts +0 -267
- package/apps/studio/test/sync-fs-mirror.test.ts +0 -181
- package/apps/studio/test/sync-hardening.test.ts +0 -586
- package/apps/studio/test/sync-hub-listing.test.ts +0 -46
- package/apps/studio/test/sync-hubs-config.test.ts +0 -133
- package/apps/studio/test/sync-incident-replay.test.ts +0 -211
- package/apps/studio/test/sync-journal.test.ts +0 -176
- package/apps/studio/test/sync-meta-codec.test.ts +0 -199
- package/apps/studio/test/sync-migrate-flat-fallback.test.ts +0 -98
- package/apps/studio/test/sync-move-retirement.test.ts +0 -231
- package/apps/studio/test/sync-panel-surface.test.ts +0 -153
- package/apps/studio/test/sync-path-pull.test.ts +0 -531
- package/apps/studio/test/sync-presentation.test.ts +0 -262
- package/apps/studio/test/sync-pull-budget.test.ts +0 -169
- package/apps/studio/test/sync-remote-docs.test.ts +0 -333
- package/apps/studio/test/sync-resync-routes.test.ts +0 -168
- package/apps/studio/test/sync-runtime.test.ts +0 -1675
- package/apps/studio/test/sync-seed-defers-to-hub.test.ts +0 -83
- package/apps/studio/test/sync-settings-routes.test.ts +0 -195
- package/apps/studio/test/sync-settings.test.ts +0 -151
- package/apps/studio/test/sync-status.test.ts +0 -261
- package/apps/studio/test/sync-supervisor.test.ts +0 -262
- package/apps/studio/test/sync-tombstone-apply.test.ts +0 -111
- package/apps/studio/test/sync-trash.test.ts +0 -132
- package/apps/studio/test/sync-two-peer-discovery.test.ts +0 -343
- package/apps/studio/test/sync-untrusted.test.ts +0 -103
- package/apps/studio/test/system-endpoint.test.ts +0 -144
- package/apps/studio/test/text-editability-stamp.test.ts +0 -131
- package/apps/studio/test/timeline-comp-target.test.ts +0 -139
- package/apps/studio/test/timeline-media-cache.test.ts +0 -41
- package/apps/studio/test/timeline-parse.test.ts +0 -184
- package/apps/studio/test/timeline-scale.test.ts +0 -65
- package/apps/studio/test/timeline-snap.test.ts +0 -85
- package/apps/studio/test/tool-palette-insert-anchor.test.ts +0 -86
- package/apps/studio/test/tool-palette.test.tsx +0 -55
- package/apps/studio/test/tour-overlay.test.tsx +0 -90
- package/apps/studio/test/trusted-request-host.test.ts +0 -66
- package/apps/studio/test/undo-sequence-byte-compare.test.ts +0 -211
- package/apps/studio/test/undo-stack.test.ts +0 -244
- package/apps/studio/test/use-agent-presence.test.tsx +0 -117
- package/apps/studio/test/use-annotation-resize.test.ts +0 -402
- package/apps/studio/test/use-annotation-selection.test.tsx +0 -77
- package/apps/studio/test/use-artboard-drag.test.ts +0 -324
- package/apps/studio/test/use-canvas-activity.test.tsx +0 -206
- package/apps/studio/test/use-collab.test.ts +0 -0
- package/apps/studio/test/use-cursor-modifiers.test.ts +0 -59
- package/apps/studio/test/use-keyboard-discipline.test.ts +0 -27
- package/apps/studio/test/use-selection-set.test.tsx +0 -166
- package/apps/studio/test/use-snap-guides.test.ts +0 -271
- package/apps/studio/test/use-tool-mode.test.tsx +0 -186
- package/apps/studio/test/use-undo-stack.test.tsx +0 -223
- package/apps/studio/test/video-asset.test.ts +0 -163
- package/apps/studio/test/video-comp-fixture.test.ts +0 -50
- package/apps/studio/test/video-comp.test.ts +0 -270
- package/apps/studio/test/video-encode-lib.test.ts +0 -63
- package/apps/studio/test/video-render-bridge.test.ts +0 -171
- package/apps/studio/test/whats-new.test.ts +0 -113
- package/apps/studio/test/whisper-setup.test.ts +0 -97
- package/apps/studio/test/workspace-containment.test.ts +0 -382
- package/apps/studio/test/workspace-signin.test.ts +0 -292
- package/apps/studio/test/ws-handshake.test.ts +0 -33
- package/cli/commands/cache.test.mjs +0 -166
- package/cli/commands/design-link.test.mjs +0 -419
- package/cli/commands/design.test.mjs +0 -240
- package/cli/commands/doctor.test.mjs +0 -237
- package/cli/commands/hub-workspace.test.mjs +0 -275
- package/cli/commands/hub.test.mjs +0 -225
- package/cli/commands/kg.test.mjs +0 -142
- package/cli/commands/preflight.test.mjs +0 -46
- package/cli/lib/argv.test.mjs +0 -46
- package/cli/lib/cache.test.mjs +0 -303
- package/cli/lib/cell-plan.test.mjs +0 -225
- package/cli/lib/config-lint.test.mjs +0 -117
- package/cli/lib/ddr-to-kgai.test.mjs +0 -118
- package/cli/lib/design-ownership.test.mjs +0 -329
- package/cli/lib/fetch-asset.test.mjs +0 -213
- package/cli/lib/figma-codegen-reachability.test.mjs +0 -104
- package/cli/lib/figma-import-controls.test.mjs +0 -70
- package/cli/lib/flow-design-integration.test.mjs +0 -165
- package/cli/lib/gitignore-block.test.mjs +0 -136
- package/cli/lib/gitignore-drift.test.mjs +0 -156
- package/cli/lib/hubs-config.test.mjs +0 -165
- package/cli/lib/pkg-root.test.mjs +0 -111
- package/cli/lib/plugin-cli-reachability.test.mjs +0 -57
- package/cli/lib/plugin-name-namespace.test.mjs +0 -71
- package/cli/lib/reconstruct-toolset.test.mjs +0 -194
- package/cli/lib/stack-detect.test.mjs +0 -121
- package/cli/lib/update-check.test.mjs +0 -32
- package/cli/lib/workspace-plan.test.mjs +0 -699
|
@@ -1,223 +0,0 @@
|
|
|
1
|
-
// generation/elevenlabs.test.ts — the ElevenLabs adapter proven per-capability
|
|
2
|
-
// against stubbed HTTP (no real key, no network). Audio verbs yield audio bytes;
|
|
3
|
-
// Scribe yields an SRT built from word timings.
|
|
4
|
-
|
|
5
|
-
import { afterEach, describe, expect, test } from 'bun:test';
|
|
6
|
-
|
|
7
|
-
import { createElevenLabsAdapter, parseHistory } from './adapters/elevenlabs.ts';
|
|
8
|
-
import { createAdapter, providersForModality } from './registry.ts';
|
|
9
|
-
import type { AdapterContext, GenRequest } from './types.ts';
|
|
10
|
-
|
|
11
|
-
const realFetch = globalThis.fetch;
|
|
12
|
-
afterEach(() => {
|
|
13
|
-
globalThis.fetch = realFetch;
|
|
14
|
-
});
|
|
15
|
-
|
|
16
|
-
function ctxWith(overrides: Partial<AdapterContext> = {}): AdapterContext {
|
|
17
|
-
return {
|
|
18
|
-
apiKey: 'sk-eleven-test',
|
|
19
|
-
localize: async () => 'assets/stub.mp3',
|
|
20
|
-
...overrides,
|
|
21
|
-
};
|
|
22
|
-
}
|
|
23
|
-
|
|
24
|
-
/** Capture the last request + return a canned response. */
|
|
25
|
-
function stub(makeRes: () => Response): { calls: Array<{ url: string; init: RequestInit }> } {
|
|
26
|
-
const calls: Array<{ url: string; init: RequestInit }> = [];
|
|
27
|
-
globalThis.fetch = (async (url: string, init: RequestInit) => {
|
|
28
|
-
calls.push({ url: String(url), init });
|
|
29
|
-
return makeRes();
|
|
30
|
-
}) as typeof fetch;
|
|
31
|
-
return { calls };
|
|
32
|
-
}
|
|
33
|
-
|
|
34
|
-
const MP3 = new Uint8Array([0xff, 0xfb, 0x90, 0x00, 1, 2, 3, 4]); // MP3 frame-sync-ish
|
|
35
|
-
function audioRes(): Response {
|
|
36
|
-
return new Response(MP3, { status: 200, headers: { 'content-type': 'audio/mpeg' } });
|
|
37
|
-
}
|
|
38
|
-
|
|
39
|
-
async function firstAsset(adapter: ReturnType<typeof createElevenLabsAdapter>, req: GenRequest) {
|
|
40
|
-
const job = await adapter.submit(req);
|
|
41
|
-
const res = await job.result();
|
|
42
|
-
return { job, res };
|
|
43
|
-
}
|
|
44
|
-
|
|
45
|
-
describe('elevenlabs audio', () => {
|
|
46
|
-
test('music POSTs /v1/music and yields audio bytes', async () => {
|
|
47
|
-
const { calls } = stub(audioRes);
|
|
48
|
-
const { job, res } = await firstAsset(createElevenLabsAdapter(ctxWith()), {
|
|
49
|
-
modality: 'audio',
|
|
50
|
-
provider: 'elevenlabs',
|
|
51
|
-
prompt: 'a warm lo-fi loop',
|
|
52
|
-
});
|
|
53
|
-
expect(job.status()).toBe('done');
|
|
54
|
-
expect(calls[0].url).toContain('/v1/music');
|
|
55
|
-
expect(res.assets[0].kind).toBe('audio');
|
|
56
|
-
expect(res.assets[0].bytes?.byteLength).toBe(MP3.byteLength);
|
|
57
|
-
// The key rides the header only, never the URL.
|
|
58
|
-
expect(String(calls[0].init.headers['xi-api-key'])).toBe('sk-eleven-test');
|
|
59
|
-
expect(calls[0].url).not.toContain('sk-eleven-test');
|
|
60
|
-
});
|
|
61
|
-
|
|
62
|
-
test('sfx POSTs /v1/sound-generation with a duration', async () => {
|
|
63
|
-
const { calls } = stub(audioRes);
|
|
64
|
-
await firstAsset(createElevenLabsAdapter(ctxWith()), {
|
|
65
|
-
modality: 'audio',
|
|
66
|
-
provider: 'elevenlabs',
|
|
67
|
-
prompt: 'glass shatter',
|
|
68
|
-
params: { audioKind: 'sfx', durationSeconds: 2 },
|
|
69
|
-
});
|
|
70
|
-
expect(calls[0].url).toContain('/v1/sound-generation');
|
|
71
|
-
expect(JSON.parse(String(calls[0].init.body)).duration_seconds).toBe(2);
|
|
72
|
-
});
|
|
73
|
-
|
|
74
|
-
test('tts POSTs /v1/text-to-speech/{voice} and requires a voice_id', async () => {
|
|
75
|
-
const { calls } = stub(audioRes);
|
|
76
|
-
await firstAsset(createElevenLabsAdapter(ctxWith()), {
|
|
77
|
-
modality: 'audio',
|
|
78
|
-
provider: 'elevenlabs',
|
|
79
|
-
prompt: 'Welcome to Maude',
|
|
80
|
-
params: { audioKind: 'tts', voice_id: 'Rachel' },
|
|
81
|
-
});
|
|
82
|
-
expect(calls[0].url).toContain('/v1/text-to-speech/Rachel');
|
|
83
|
-
|
|
84
|
-
const noVoice = createElevenLabsAdapter(ctxWith());
|
|
85
|
-
const job = await noVoice.submit({
|
|
86
|
-
modality: 'audio',
|
|
87
|
-
provider: 'elevenlabs',
|
|
88
|
-
prompt: 'x',
|
|
89
|
-
params: { audioKind: 'tts' },
|
|
90
|
-
});
|
|
91
|
-
await expect(job.result()).rejects.toThrow(/voice_id/);
|
|
92
|
-
});
|
|
93
|
-
|
|
94
|
-
test('a missing key fails fast without a network call', async () => {
|
|
95
|
-
let called = false;
|
|
96
|
-
globalThis.fetch = (async () => {
|
|
97
|
-
called = true;
|
|
98
|
-
return audioRes();
|
|
99
|
-
}) as typeof fetch;
|
|
100
|
-
const job = await createElevenLabsAdapter(ctxWith({ apiKey: null })).submit({
|
|
101
|
-
modality: 'audio',
|
|
102
|
-
provider: 'elevenlabs',
|
|
103
|
-
prompt: 'x',
|
|
104
|
-
});
|
|
105
|
-
await expect(job.result()).rejects.toThrow(/no ElevenLabs key/);
|
|
106
|
-
expect(called).toBe(false);
|
|
107
|
-
});
|
|
108
|
-
|
|
109
|
-
test('an HTTP error surfaces the provider detail, never the key', async () => {
|
|
110
|
-
stub(
|
|
111
|
-
() =>
|
|
112
|
-
new Response(JSON.stringify({ detail: { message: 'quota exceeded' } }), {
|
|
113
|
-
status: 429,
|
|
114
|
-
headers: { 'content-type': 'application/json' },
|
|
115
|
-
})
|
|
116
|
-
);
|
|
117
|
-
const job = await createElevenLabsAdapter(ctxWith()).submit({
|
|
118
|
-
modality: 'audio',
|
|
119
|
-
provider: 'elevenlabs',
|
|
120
|
-
prompt: 'x',
|
|
121
|
-
});
|
|
122
|
-
await expect(job.result()).rejects.toThrow(/quota exceeded/);
|
|
123
|
-
});
|
|
124
|
-
});
|
|
125
|
-
|
|
126
|
-
describe('elevenlabs scribe (transcription)', () => {
|
|
127
|
-
test('word timings become an SRT text asset', async () => {
|
|
128
|
-
stub(
|
|
129
|
-
() =>
|
|
130
|
-
new Response(
|
|
131
|
-
JSON.stringify({
|
|
132
|
-
text: 'Hello there.',
|
|
133
|
-
words: [
|
|
134
|
-
{ text: 'Hello', start: 0, end: 0.4, type: 'word' },
|
|
135
|
-
{ text: ' ', start: 0.4, end: 0.4, type: 'spacing' },
|
|
136
|
-
{ text: 'there.', start: 0.4, end: 0.8, type: 'word' },
|
|
137
|
-
],
|
|
138
|
-
}),
|
|
139
|
-
{ status: 200, headers: { 'content-type': 'application/json' } }
|
|
140
|
-
)
|
|
141
|
-
);
|
|
142
|
-
const adapter = createElevenLabsAdapter(
|
|
143
|
-
ctxWith({
|
|
144
|
-
readSourceAsset: async () => ({ bytes: new Uint8Array([1, 2, 3]), mime: 'audio/mpeg' }),
|
|
145
|
-
})
|
|
146
|
-
);
|
|
147
|
-
const { res } = await firstAsset(adapter, {
|
|
148
|
-
modality: 'transcription',
|
|
149
|
-
provider: 'elevenlabs',
|
|
150
|
-
sourceAsset: 'assets/deadbeef.mp3',
|
|
151
|
-
});
|
|
152
|
-
const asset = res.assets[0];
|
|
153
|
-
expect(asset.kind).toBe('transcription');
|
|
154
|
-
expect(asset.text).toContain('00:00:00,000 --> 00:00:00,800');
|
|
155
|
-
expect(asset.text).toContain('Hello there.');
|
|
156
|
-
// The spacing token is dropped, not emitted as a word.
|
|
157
|
-
expect(asset.text).not.toContain('Hello there');
|
|
158
|
-
});
|
|
159
|
-
|
|
160
|
-
test('transcription without a source asset fails', async () => {
|
|
161
|
-
const job = await createElevenLabsAdapter(ctxWith()).submit({
|
|
162
|
-
modality: 'transcription',
|
|
163
|
-
provider: 'elevenlabs',
|
|
164
|
-
sourceAsset: 'assets/deadbeef.mp3',
|
|
165
|
-
});
|
|
166
|
-
// no readSourceAsset wired → hard error
|
|
167
|
-
await expect(job.result()).rejects.toThrow(/source-asset access/);
|
|
168
|
-
});
|
|
169
|
-
});
|
|
170
|
-
|
|
171
|
-
describe('registry', () => {
|
|
172
|
-
test('elevenlabs is registered for audio + transcription', () => {
|
|
173
|
-
expect(providersForModality('audio').some((p) => p.id === 'elevenlabs')).toBe(true);
|
|
174
|
-
expect(providersForModality('transcription').some((p) => p.id === 'elevenlabs')).toBe(true);
|
|
175
|
-
const adapter = createAdapter('elevenlabs', ctxWith());
|
|
176
|
-
expect(adapter.descriptor.id).toBe('elevenlabs');
|
|
177
|
-
});
|
|
178
|
-
});
|
|
179
|
-
|
|
180
|
-
describe('elevenlabs history (Task 2.5 — reuse-before-you-pay)', () => {
|
|
181
|
-
test('parseHistory maps items, drops id-less, converts date_unix → ISO', () => {
|
|
182
|
-
const items = parseHistory({
|
|
183
|
-
history: [
|
|
184
|
-
{ history_item_id: 'h1', text: 'warm lofi loop', voice_id: 'v1', date_unix: 1_700_000_000 },
|
|
185
|
-
{ text: 'no id — dropped' },
|
|
186
|
-
{ history_item_id: 'h2', text: 'sfx whoosh' },
|
|
187
|
-
],
|
|
188
|
-
});
|
|
189
|
-
expect(items.map((i) => i.id)).toEqual(['h1', 'h2']);
|
|
190
|
-
expect(items[0].text).toBe('warm lofi loop');
|
|
191
|
-
expect(items[0].voiceId).toBe('v1');
|
|
192
|
-
expect(items[0].at).toBe(new Date(1_700_000_000 * 1000).toISOString());
|
|
193
|
-
expect(items[1].at).toBeUndefined();
|
|
194
|
-
});
|
|
195
|
-
|
|
196
|
-
test('parseHistory tolerates an empty / missing history', () => {
|
|
197
|
-
expect(parseHistory({})).toEqual([]);
|
|
198
|
-
expect(parseHistory({ history: [] })).toEqual([]);
|
|
199
|
-
});
|
|
200
|
-
|
|
201
|
-
test('listHistory GETs /v1/history with the key in the header only', async () => {
|
|
202
|
-
const { calls } = stub(
|
|
203
|
-
() =>
|
|
204
|
-
new Response(JSON.stringify({ history: [{ history_item_id: 'h1', text: 'x' }] }), {
|
|
205
|
-
status: 200,
|
|
206
|
-
headers: { 'content-type': 'application/json' },
|
|
207
|
-
})
|
|
208
|
-
);
|
|
209
|
-
const items = await createElevenLabsAdapter(ctxWith()).listHistory?.();
|
|
210
|
-
expect(items?.[0].id).toBe('h1');
|
|
211
|
-
expect(calls[0].url).toContain('/v1/history');
|
|
212
|
-
expect(String(calls[0].init.headers['xi-api-key'])).toBe('sk-eleven-test');
|
|
213
|
-
expect(calls[0].url).not.toContain('sk-eleven-test');
|
|
214
|
-
});
|
|
215
|
-
|
|
216
|
-
test('fetchHistoryAudio GETs the item audio and yields audio bytes', async () => {
|
|
217
|
-
const { calls } = stub(audioRes);
|
|
218
|
-
const asset = await createElevenLabsAdapter(ctxWith()).fetchHistoryAudio?.('h1');
|
|
219
|
-
expect(asset?.kind).toBe('audio');
|
|
220
|
-
expect(asset?.bytes?.byteLength).toBe(MP3.byteLength);
|
|
221
|
-
expect(calls[0].url).toContain('/v1/history/h1/audio');
|
|
222
|
-
});
|
|
223
|
-
});
|
|
@@ -1,350 +0,0 @@
|
|
|
1
|
-
// generation/gemini.test.ts — the Gemini adapter + download localizer proven
|
|
2
|
-
// against a stubbed HTTP response (no real key, no network). A 1x1 PNG's base64
|
|
3
|
-
// round-trips: adapter → GenResult.bytes → localizeGenAsset → a saveAsset call
|
|
4
|
-
// that lands `assets/<sha8>.png`.
|
|
5
|
-
|
|
6
|
-
import { afterEach, describe, expect, test } from 'bun:test';
|
|
7
|
-
|
|
8
|
-
import { createGeminiAdapter, extractOperationName, extractVideoUri } from './adapters/gemini.ts';
|
|
9
|
-
import { localizeGenAsset } from './download.ts';
|
|
10
|
-
import { createAdapter, providersForModality } from './registry.ts';
|
|
11
|
-
import type { AdapterContext } from './types.ts';
|
|
12
|
-
|
|
13
|
-
// A real 1x1 transparent PNG (so a magic-byte sniff downstream would accept it).
|
|
14
|
-
const PNG_1x1_B64 =
|
|
15
|
-
'iVBORw0KGgoAAAANSUhEUgAAAAEAAAABCAQAAAC1HAwCAAAAC0lEQVR42mNkYPhfDwAChwGA60e6kgAAAABJRU5ErkJggg==';
|
|
16
|
-
|
|
17
|
-
const realFetch = globalThis.fetch;
|
|
18
|
-
afterEach(() => {
|
|
19
|
-
globalThis.fetch = realFetch;
|
|
20
|
-
});
|
|
21
|
-
|
|
22
|
-
function stubFetch(body: unknown, status = 200): void {
|
|
23
|
-
globalThis.fetch = (async () =>
|
|
24
|
-
new Response(JSON.stringify(body), {
|
|
25
|
-
status,
|
|
26
|
-
headers: { 'content-type': 'application/json' },
|
|
27
|
-
})) as typeof fetch;
|
|
28
|
-
}
|
|
29
|
-
|
|
30
|
-
function ctxWith(overrides: Partial<AdapterContext> = {}): AdapterContext {
|
|
31
|
-
return {
|
|
32
|
-
apiKey: 'AIza-test-key',
|
|
33
|
-
localize: async () => 'assets/stub.png',
|
|
34
|
-
...overrides,
|
|
35
|
-
};
|
|
36
|
-
}
|
|
37
|
-
|
|
38
|
-
describe('gemini adapter', () => {
|
|
39
|
-
test('a successful generateContent yields an already-done Job with PNG bytes', async () => {
|
|
40
|
-
stubFetch({
|
|
41
|
-
candidates: [
|
|
42
|
-
{ content: { parts: [{ inlineData: { mimeType: 'image/png', data: PNG_1x1_B64 } }] } },
|
|
43
|
-
],
|
|
44
|
-
});
|
|
45
|
-
const adapter = createGeminiAdapter(ctxWith());
|
|
46
|
-
const job = await adapter.submit({
|
|
47
|
-
modality: 'image',
|
|
48
|
-
provider: 'gemini',
|
|
49
|
-
prompt: 'a red circle',
|
|
50
|
-
});
|
|
51
|
-
const res = await job.result();
|
|
52
|
-
expect(job.status()).toBe('done');
|
|
53
|
-
expect(res.assets).toHaveLength(1);
|
|
54
|
-
expect(res.assets[0].kind).toBe('image');
|
|
55
|
-
expect(res.assets[0].bytes?.byteLength).toBeGreaterThan(0);
|
|
56
|
-
});
|
|
57
|
-
|
|
58
|
-
test('a missing key fails fast without a network call', async () => {
|
|
59
|
-
let called = false;
|
|
60
|
-
globalThis.fetch = (async () => {
|
|
61
|
-
called = true;
|
|
62
|
-
return new Response('{}');
|
|
63
|
-
}) as typeof fetch;
|
|
64
|
-
const adapter = createGeminiAdapter(ctxWith({ apiKey: null }));
|
|
65
|
-
const job = await adapter.submit({ modality: 'image', provider: 'gemini', prompt: 'x' });
|
|
66
|
-
await expect(job.result()).rejects.toThrow(/no Google Gemini key/);
|
|
67
|
-
expect(called).toBe(false);
|
|
68
|
-
});
|
|
69
|
-
|
|
70
|
-
test('an HTTP error surfaces the provider message, never the key', async () => {
|
|
71
|
-
stubFetch({ error: { message: 'quota exceeded' } }, 429);
|
|
72
|
-
const adapter = createGeminiAdapter(ctxWith());
|
|
73
|
-
const job = await adapter.submit({ modality: 'image', provider: 'gemini', prompt: 'x' });
|
|
74
|
-
await expect(job.result()).rejects.toThrow(/quota exceeded/);
|
|
75
|
-
});
|
|
76
|
-
|
|
77
|
-
test('a response with no image part is an error', async () => {
|
|
78
|
-
stubFetch({ candidates: [{ content: { parts: [{ text: 'I cannot do that' }] } }] });
|
|
79
|
-
const adapter = createGeminiAdapter(ctxWith());
|
|
80
|
-
const job = await adapter.submit({ modality: 'image', provider: 'gemini', prompt: 'x' });
|
|
81
|
-
await expect(job.result()).rejects.toThrow(/no image/);
|
|
82
|
-
});
|
|
83
|
-
|
|
84
|
-
// Task 1.2 — the maskless-edit flow reads the source asset into an inlineData
|
|
85
|
-
// part alongside the prompt (via the host-provided readSourceAsset).
|
|
86
|
-
test('an edit request sends the source image as an inlineData part', async () => {
|
|
87
|
-
let sentBody: {
|
|
88
|
-
contents?: Array<{
|
|
89
|
-
parts?: Array<{ text?: string; inlineData?: { mimeType?: string; data?: string } }>;
|
|
90
|
-
}>;
|
|
91
|
-
} | null = null;
|
|
92
|
-
globalThis.fetch = (async (_url: string, init: RequestInit) => {
|
|
93
|
-
sentBody = JSON.parse(String(init.body));
|
|
94
|
-
return new Response(
|
|
95
|
-
JSON.stringify({
|
|
96
|
-
candidates: [
|
|
97
|
-
{ content: { parts: [{ inlineData: { mimeType: 'image/png', data: PNG_1x1_B64 } }] } },
|
|
98
|
-
],
|
|
99
|
-
}),
|
|
100
|
-
{ status: 200, headers: { 'content-type': 'application/json' } }
|
|
101
|
-
);
|
|
102
|
-
}) as typeof fetch;
|
|
103
|
-
const adapter = createGeminiAdapter(
|
|
104
|
-
ctxWith({
|
|
105
|
-
readSourceAsset: async () => ({
|
|
106
|
-
bytes: new Uint8Array([137, 80, 78, 71]),
|
|
107
|
-
mime: 'image/png',
|
|
108
|
-
}),
|
|
109
|
-
})
|
|
110
|
-
);
|
|
111
|
-
const job = await adapter.submit({
|
|
112
|
-
modality: 'image',
|
|
113
|
-
provider: 'gemini',
|
|
114
|
-
prompt: 'make the sky purple',
|
|
115
|
-
sourceAsset: 'assets/deadbeef.png',
|
|
116
|
-
});
|
|
117
|
-
await job.result();
|
|
118
|
-
const parts = sentBody?.contents?.[0]?.parts ?? [];
|
|
119
|
-
// Source image FIRST, then the instruction text (Gemini's edit order).
|
|
120
|
-
expect(parts[0]?.inlineData?.mimeType).toBe('image/png');
|
|
121
|
-
expect(typeof parts[0]?.inlineData?.data).toBe('string');
|
|
122
|
-
expect(parts[1]?.text).toBe('make the sky purple');
|
|
123
|
-
});
|
|
124
|
-
|
|
125
|
-
test('an edit request with no source-asset reader fails fast', async () => {
|
|
126
|
-
let called = false;
|
|
127
|
-
globalThis.fetch = (async () => {
|
|
128
|
-
called = true;
|
|
129
|
-
return new Response('{}');
|
|
130
|
-
}) as typeof fetch;
|
|
131
|
-
const adapter = createGeminiAdapter(ctxWith()); // no readSourceAsset
|
|
132
|
-
const job = await adapter.submit({
|
|
133
|
-
modality: 'image',
|
|
134
|
-
provider: 'gemini',
|
|
135
|
-
prompt: 'edit',
|
|
136
|
-
sourceAsset: 'assets/deadbeef.png',
|
|
137
|
-
});
|
|
138
|
-
await expect(job.result()).rejects.toThrow(/source-asset access/);
|
|
139
|
-
expect(called).toBe(false);
|
|
140
|
-
});
|
|
141
|
-
|
|
142
|
-
test('an edit whose source is not an image is rejected', async () => {
|
|
143
|
-
const adapter = createGeminiAdapter(
|
|
144
|
-
ctxWith({
|
|
145
|
-
readSourceAsset: async () => ({ bytes: new Uint8Array([0, 0, 0]), mime: 'video/mp4' }),
|
|
146
|
-
})
|
|
147
|
-
);
|
|
148
|
-
const job = await adapter.submit({
|
|
149
|
-
modality: 'image',
|
|
150
|
-
provider: 'gemini',
|
|
151
|
-
prompt: 'edit',
|
|
152
|
-
sourceAsset: 'assets/deadbeef.mp4',
|
|
153
|
-
});
|
|
154
|
-
await expect(job.result()).rejects.toThrow(/not an image/);
|
|
155
|
-
});
|
|
156
|
-
|
|
157
|
-
// security fan-out F2 — an env-poisoned non-default base is refused before the
|
|
158
|
-
// key-bearing request (unless MAUDE_GEN_ALLOW_CUSTOM_BASE is opted in).
|
|
159
|
-
test('a non-allowlisted MAUDE_GEMINI_API_BASE is refused without touching the network', async () => {
|
|
160
|
-
const prevBase = process.env.MAUDE_GEMINI_API_BASE;
|
|
161
|
-
const prevAllow = process.env.MAUDE_GEN_ALLOW_CUSTOM_BASE;
|
|
162
|
-
process.env.MAUDE_GEMINI_API_BASE = 'https://evil.example.com/v1beta';
|
|
163
|
-
delete process.env.MAUDE_GEN_ALLOW_CUSTOM_BASE;
|
|
164
|
-
// Re-import the module so the module-level base constant re-reads the env.
|
|
165
|
-
const mod = await import(`./adapters/gemini.ts?evilbase=${Date.now()}`);
|
|
166
|
-
let called = false;
|
|
167
|
-
globalThis.fetch = (async () => {
|
|
168
|
-
called = true;
|
|
169
|
-
return new Response('{}');
|
|
170
|
-
}) as typeof fetch;
|
|
171
|
-
try {
|
|
172
|
-
const adapter = mod.createGeminiAdapter(ctxWith());
|
|
173
|
-
const job = await adapter.submit({ modality: 'image', provider: 'gemini', prompt: 'x' });
|
|
174
|
-
await expect(job.result()).rejects.toThrow(/not allowlisted/);
|
|
175
|
-
expect(called).toBe(false);
|
|
176
|
-
} finally {
|
|
177
|
-
if (prevBase === undefined) delete process.env.MAUDE_GEMINI_API_BASE;
|
|
178
|
-
else process.env.MAUDE_GEMINI_API_BASE = prevBase;
|
|
179
|
-
if (prevAllow === undefined) delete process.env.MAUDE_GEN_ALLOW_CUSTOM_BASE;
|
|
180
|
-
else process.env.MAUDE_GEN_ALLOW_CUSTOM_BASE = prevAllow;
|
|
181
|
-
}
|
|
182
|
-
});
|
|
183
|
-
|
|
184
|
-
// security fan-out F1 — an oversized provider response is rejected before it
|
|
185
|
-
// is fully materialized + base64-decoded in RAM (OOM guard).
|
|
186
|
-
test('an over-cap response body is rejected (RAM DoS guard)', async () => {
|
|
187
|
-
const prev = process.env.MAUDE_GEMINI_MAX_RESPONSE_BYTES;
|
|
188
|
-
// The cap has a 1 MB floor, so drive the test with a >1 MB body at the floor.
|
|
189
|
-
process.env.MAUDE_GEMINI_MAX_RESPONSE_BYTES = String(1024 * 1024);
|
|
190
|
-
const mod = await import(`./adapters/gemini.ts?cap=${Date.now()}`);
|
|
191
|
-
globalThis.fetch = (async () =>
|
|
192
|
-
new Response('x'.repeat(2 * 1024 * 1024), {
|
|
193
|
-
status: 200,
|
|
194
|
-
headers: { 'content-type': 'application/json' },
|
|
195
|
-
})) as typeof fetch;
|
|
196
|
-
try {
|
|
197
|
-
const adapter = mod.createGeminiAdapter(ctxWith());
|
|
198
|
-
const job = await adapter.submit({ modality: 'image', provider: 'gemini', prompt: 'x' });
|
|
199
|
-
await expect(job.result()).rejects.toThrow(/too large|exceeded/);
|
|
200
|
-
} finally {
|
|
201
|
-
if (prev === undefined) delete process.env.MAUDE_GEMINI_MAX_RESPONSE_BYTES;
|
|
202
|
-
else process.env.MAUDE_GEMINI_MAX_RESPONSE_BYTES = prev;
|
|
203
|
-
}
|
|
204
|
-
});
|
|
205
|
-
});
|
|
206
|
-
|
|
207
|
-
describe('download localizer', () => {
|
|
208
|
-
test('inline bytes land via saveAsset and return the rel path', async () => {
|
|
209
|
-
const saved: Uint8Array[] = [];
|
|
210
|
-
const rel = await localizeGenAsset(
|
|
211
|
-
{ kind: 'image', mime: 'image/png', bytes: new Uint8Array([137, 80, 78, 71]) },
|
|
212
|
-
{
|
|
213
|
-
saveAsset: async (bytes) => {
|
|
214
|
-
saved.push(bytes);
|
|
215
|
-
return { ok: true, path: 'assets/deadbeef.png' };
|
|
216
|
-
},
|
|
217
|
-
}
|
|
218
|
-
);
|
|
219
|
-
expect(rel).toBe('assets/deadbeef.png');
|
|
220
|
-
expect(saved).toHaveLength(1);
|
|
221
|
-
});
|
|
222
|
-
|
|
223
|
-
test('a rejected save throws', async () => {
|
|
224
|
-
await expect(
|
|
225
|
-
localizeGenAsset(
|
|
226
|
-
{ kind: 'image', mime: 'image/png', bytes: new Uint8Array([1]) },
|
|
227
|
-
{ saveAsset: async () => ({ ok: false, error: 'not an image' }) }
|
|
228
|
-
)
|
|
229
|
-
).rejects.toThrow(/rejected on save/);
|
|
230
|
-
});
|
|
231
|
-
|
|
232
|
-
test('a URL result fails loud in Phase 0 (no unhardened egress)', async () => {
|
|
233
|
-
await expect(
|
|
234
|
-
localizeGenAsset(
|
|
235
|
-
{ kind: 'image', mime: 'image/png', url: 'https://cdn.example/x.png' },
|
|
236
|
-
{ saveAsset: async () => ({ ok: true, path: 'assets/x.png' }) }
|
|
237
|
-
)
|
|
238
|
-
).rejects.toThrow(/Phase 1/);
|
|
239
|
-
});
|
|
240
|
-
});
|
|
241
|
-
|
|
242
|
-
describe('registry', () => {
|
|
243
|
-
test('gemini is registered for image and instantiable', () => {
|
|
244
|
-
expect(providersForModality('image').some((p) => p.id === 'gemini')).toBe(true);
|
|
245
|
-
const adapter = createAdapter('gemini', ctxWith());
|
|
246
|
-
expect(adapter.descriptor.id).toBe('gemini');
|
|
247
|
-
});
|
|
248
|
-
|
|
249
|
-
test('an unknown provider throws', () => {
|
|
250
|
-
expect(() => createAdapter('nope', ctxWith())).toThrow(/unknown provider/);
|
|
251
|
-
});
|
|
252
|
-
});
|
|
253
|
-
|
|
254
|
-
describe('veo video (Task 3.1 — async operation lifecycle)', () => {
|
|
255
|
-
// A minimal MP4 (ftyp box) so a magic-byte sniff downstream would accept it.
|
|
256
|
-
const MP4 = new Uint8Array([
|
|
257
|
-
0x00, 0x00, 0x00, 0x18, 0x66, 0x74, 0x79, 0x70, 0x6d, 0x70, 0x34, 0x32, 0, 0, 0, 0, 0x6d, 0x70,
|
|
258
|
-
0x34, 0x32, 0x69, 0x73, 0x6f, 0x6d,
|
|
259
|
-
]);
|
|
260
|
-
|
|
261
|
-
test('extractOperationName reads the start-response name', () => {
|
|
262
|
-
expect(extractOperationName({ name: 'operations/abc' })).toBe('operations/abc');
|
|
263
|
-
expect(extractOperationName({})).toBeNull();
|
|
264
|
-
});
|
|
265
|
-
|
|
266
|
-
test('extractVideoUri walks nested shapes for the first https uri', () => {
|
|
267
|
-
expect(
|
|
268
|
-
extractVideoUri({
|
|
269
|
-
response: {
|
|
270
|
-
generateVideoResponse: {
|
|
271
|
-
generatedSamples: [{ video: { uri: 'https://generativelanguage.googleapis.com/v/1' } }],
|
|
272
|
-
},
|
|
273
|
-
},
|
|
274
|
-
})
|
|
275
|
-
).toBe('https://generativelanguage.googleapis.com/v/1');
|
|
276
|
-
expect(
|
|
277
|
-
extractVideoUri({
|
|
278
|
-
response: {
|
|
279
|
-
generatedVideos: [
|
|
280
|
-
{ video: { fileUri: 'https://generativelanguage.googleapis.com/v/2' } },
|
|
281
|
-
],
|
|
282
|
-
},
|
|
283
|
-
})
|
|
284
|
-
).toBe('https://generativelanguage.googleapis.com/v/2');
|
|
285
|
-
expect(extractVideoUri({ response: {} })).toBeNull();
|
|
286
|
-
});
|
|
287
|
-
|
|
288
|
-
test('submit(video) drives start → poll(done) → download and yields MP4 bytes', async () => {
|
|
289
|
-
process.env.MAUDE_VEO_POLL_INTERVAL_MS = '1';
|
|
290
|
-
const calls: string[] = [];
|
|
291
|
-
globalThis.fetch = (async (url: string) => {
|
|
292
|
-
calls.push(String(url));
|
|
293
|
-
if (String(url).includes(':predictLongRunning'))
|
|
294
|
-
return new Response(JSON.stringify({ name: 'operations/xyz' }), {
|
|
295
|
-
status: 200,
|
|
296
|
-
headers: { 'content-type': 'application/json' },
|
|
297
|
-
});
|
|
298
|
-
if (String(url).includes('operations/xyz'))
|
|
299
|
-
return new Response(
|
|
300
|
-
JSON.stringify({
|
|
301
|
-
done: true,
|
|
302
|
-
response: {
|
|
303
|
-
generatedSamples: [
|
|
304
|
-
{
|
|
305
|
-
video: {
|
|
306
|
-
uri: 'https://generativelanguage.googleapis.com/v1beta/files/x:download',
|
|
307
|
-
},
|
|
308
|
-
},
|
|
309
|
-
],
|
|
310
|
-
},
|
|
311
|
-
}),
|
|
312
|
-
{ status: 200, headers: { 'content-type': 'application/json' } }
|
|
313
|
-
);
|
|
314
|
-
// the video download
|
|
315
|
-
return new Response(MP4, { status: 200, headers: { 'content-type': 'video/mp4' } });
|
|
316
|
-
}) as typeof fetch;
|
|
317
|
-
|
|
318
|
-
const adapter = createGeminiAdapter(ctxWith());
|
|
319
|
-
const job = await adapter.submit({ modality: 'video', provider: 'gemini', prompt: 'a wave' });
|
|
320
|
-
const res = await job.result();
|
|
321
|
-
delete process.env.MAUDE_VEO_POLL_INTERVAL_MS;
|
|
322
|
-
expect(res.assets[0].kind).toBe('video');
|
|
323
|
-
expect(res.assets[0].bytes?.byteLength).toBe(MP4.byteLength);
|
|
324
|
-
expect(calls.some((u) => u.includes(':predictLongRunning'))).toBe(true);
|
|
325
|
-
expect(calls.some((u) => u.includes('operations/xyz'))).toBe(true);
|
|
326
|
-
});
|
|
327
|
-
|
|
328
|
-
test('a video URI off the Google host is rejected (no key exfil)', async () => {
|
|
329
|
-
process.env.MAUDE_VEO_POLL_INTERVAL_MS = '1';
|
|
330
|
-
globalThis.fetch = (async (url: string) => {
|
|
331
|
-
if (String(url).includes(':predictLongRunning'))
|
|
332
|
-
return new Response(JSON.stringify({ name: 'operations/xyz' }), { status: 200 });
|
|
333
|
-
return new Response(
|
|
334
|
-
JSON.stringify({ done: true, response: { video: { uri: 'https://evil.example/v.mp4' } } }),
|
|
335
|
-
{ status: 200 }
|
|
336
|
-
);
|
|
337
|
-
}) as typeof fetch;
|
|
338
|
-
const job = await createGeminiAdapter(ctxWith()).submit({
|
|
339
|
-
modality: 'video',
|
|
340
|
-
provider: 'gemini',
|
|
341
|
-
prompt: 'x',
|
|
342
|
-
});
|
|
343
|
-
await expect(job.result()).rejects.toThrow(/host not allowlisted/);
|
|
344
|
-
delete process.env.MAUDE_VEO_POLL_INTERVAL_MS;
|
|
345
|
-
});
|
|
346
|
-
|
|
347
|
-
test('gemini is now registered for the video modality', () => {
|
|
348
|
-
expect(providersForModality('video').some((p) => p.id === 'gemini')).toBe(true);
|
|
349
|
-
});
|
|
350
|
-
});
|