ima2-gen 3.21.0 → 3.22.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 (43) hide show
  1. package/bin/commands/grok.js +8 -1
  2. package/docs/API.md +4 -1
  3. package/docs/migration/runtime-test-inventory.md +164 -159
  4. package/lib/grokSizeMapper.js +2 -0
  5. package/lib/mcp/adapters/higgsfield.js +17 -10
  6. package/lib/mcp/adapters/higgsfieldUpload.js +77 -0
  7. package/lib/mcp/connectionManager.js +32 -0
  8. package/lib/mcp/modelsCatalog.js +12 -1
  9. package/lib/xaiAuth.js +35 -3
  10. package/lib/xaiDeviceLogin.js +30 -4
  11. package/package.json +2 -2
  12. package/routes/auth.js +13 -2
  13. package/routes/mcpMedia.js +21 -7
  14. package/routes/mcpMultishot.js +11 -7
  15. package/routes/mcpRecover.js +6 -2
  16. package/routes/quota.js +50 -14
  17. package/ui/dist/.vite/manifest.json +64 -64
  18. package/ui/dist/assets/{AgentWorkspace-CBOIYDSC.js → AgentWorkspace-CRJwrf5D.js} +1 -1
  19. package/ui/dist/assets/App-B7t5h5M_.js +9 -0
  20. package/ui/dist/assets/{AssetGenWorkspace-kjhZeUE8.js → AssetGenWorkspace-CR3k3lgk.js} +2 -2
  21. package/ui/dist/assets/{AssetMediaLightbox-B6YoVErG.js → AssetMediaLightbox-DOP-l7is.js} +1 -1
  22. package/ui/dist/assets/{AssetsWorkspace-D3yHm95L.js → AssetsWorkspace-i9IE-OMD.js} +1 -1
  23. package/ui/dist/assets/{CardNewsWorkspace-BVmfGpNh.js → CardNewsWorkspace-B238fPNf.js} +1 -1
  24. package/ui/dist/assets/{GenerationRequestLogPanel-lNQlY7ax.js → GenerationRequestLogPanel-Bznhoi1c.js} +1 -1
  25. package/ui/dist/assets/{HomeWorkspace-DoJRK1Oc.js → HomeWorkspace-DmQcpD4W.js} +1 -1
  26. package/ui/dist/assets/{KeyingPanel-CzXXD18n.js → KeyingPanel-C-9CZfhI.js} +1 -1
  27. package/ui/dist/assets/{NodeCanvas-Di9xPPrm.js → NodeCanvas-CtSwikbj.js} +1 -1
  28. package/ui/dist/assets/{PromptBuilderPanel-BRC8Or5u.js → PromptBuilderPanel-CWOtvCbq.js} +1 -1
  29. package/ui/dist/assets/{PromptImportDialog-DftvHziZ.js → PromptImportDialog-ski4y5rq.js} +2 -2
  30. package/ui/dist/assets/{PromptImportDiscoverySection-BRVea9Rt.js → PromptImportDiscoverySection-BxPnvWSF.js} +1 -1
  31. package/ui/dist/assets/{PromptImportFolderSection-y9o9rPoQ.js → PromptImportFolderSection-rt8TMgTU.js} +1 -1
  32. package/ui/dist/assets/{PromptLibraryPanel-CpseDORF.js → PromptLibraryPanel-evfChzKq.js} +2 -2
  33. package/ui/dist/assets/SettingsWorkspace-PzprnYiR.js +1 -0
  34. package/ui/dist/assets/{SpriteRecipeWorkspace-Bt9UmStQ.js → SpriteRecipeWorkspace-BidCI5hK.js} +1 -1
  35. package/ui/dist/assets/{index-DbV9f53B.js → index-CaFoTJlR.js} +8 -8
  36. package/ui/dist/assets/{index-C9gJqtx4.js → index-GbV8-z5Y.js} +4 -4
  37. package/ui/dist/assets/{pptxgen.es-CCODGbBd.js → pptxgen.es-BkpMsCHc.js} +1 -1
  38. package/ui/dist/assets/{promptBuilderStore-CF1EPSCL.js → promptBuilderStore-B7xCm5Ga.js} +1 -1
  39. package/ui/dist/assets/useAgentDialogFocus-C80ZVSWi.js +1 -0
  40. package/ui/dist/index.html +1 -1
  41. package/ui/dist/assets/App-BLBE-fTl.js +0 -9
  42. package/ui/dist/assets/SettingsWorkspace-BnvKUZsF.js +0 -1
  43. package/ui/dist/assets/useAgentDialogFocus-CNcAdwxO.js +0 -1
@@ -113,7 +113,14 @@ async function loginViaServer(base) {
113
113
  const deadline = Date.now() + start.expiresIn * 1000;
114
114
  while (Date.now() < deadline) {
115
115
  await sleep(POLL_INTERVAL_MS);
116
- const poll = await request(base, `/api/auth/switch/${encodeURIComponent(start.sessionId)}`);
116
+ // A 404 means the session is gone (server restarted or reaped it): report it as the
117
+ // expired login it is, not as a bare HTTP status the user cannot act on.
118
+ const poll = await request(base, `/api/auth/switch/${encodeURIComponent(start.sessionId)}`)
119
+ .catch((error) => {
120
+ if (error.status === 404)
121
+ return { status: "expired" };
122
+ throw error;
123
+ });
117
124
  if (poll.status === "complete")
118
125
  return;
119
126
  if (poll.status === "error")
package/docs/API.md CHANGED
@@ -1266,13 +1266,16 @@ Async: `202 { requestId, provider }`; lifecycle events on `/api/events`.
1266
1266
  Results commit with `workflow: "video.multishot"` and `mcpParameters`.
1267
1267
 
1268
1268
  Re-download a remote-succeeded MCP task into the generated library. Body:
1269
- `{ provider?: "runway", kind?: "video"|"image" }`. Use after a generation's
1269
+ `{ provider?: "runway", kind?: "video"|"image", requestId? }`. Use after a generation's
1270
1270
  download/commit step failed transiently — provider assets stay fetchable for
1271
1271
  ~24-48h. Re-polls `get_task`, requires `SUCCEEDED` with an output URL
1272
1272
  (`error` SSE event with `MCP_TASK_NOT_SUCCEEDED` otherwise), then runs the same
1273
1273
  download (with retry + IPv4 fallback) → single-persistence commit path as a
1274
1274
  normal generation. Async: `202 { requestId, taskId }`; `done` carries
1275
1275
  `recovered: true`.
1276
+ `requestId` dedupes only while the job is still running — a retry with the
1277
+ same id returns `409 REQUEST_ID_IN_USE`, and once the job settles a new
1278
+ recover may reuse it (same scope as `/api/mcp/generate`).
1276
1279
  Catalog-only providers (e.g. Higgsfield on a free plan) return
1277
1280
  `409 MCP_EXECUTION_LOCKED`, same as `/api/mcp/generate`.
1278
1281
 
@@ -4,7 +4,7 @@ Generated by `npm run test:inventory` (script: `scripts/classify-tests.mjs`).
4
4
 
5
5
  _Tests considered "runtime-importing" if they import from `../lib/`, `../routes/`, `../bin/`, `../server`, or `../config`._
6
6
 
7
- Total: 513 (runtime: 233, contract: 280)
7
+ Total: 518 (runtime: 234, contract: 284)
8
8
 
9
9
  ## Runtime-importing tests
10
10
  - `tests/advertise-payload-contract.test.ts`
@@ -185,8 +185,9 @@ Total: 513 (runtime: 233, contract: 280)
185
185
  - `tests/provider-registry-parity.test.ts`
186
186
  - `tests/provider-surface-boundary.test.ts`
187
187
  - `tests/provider-surface-support.test.ts`
188
+ - `tests/quota-codex-session.test.ts`
188
189
  - `tests/reference-image-compress.test.ts`
189
- - `tests/reference-limits-node-recovery.test.mjs`
190
+ - `tests/reference-limits-node-recovery.test.ts`
190
191
  - `tests/refs-size.test.ts`
191
192
  - `tests/request-logging.test.ts`
192
193
  - `tests/responses-adapter-safety.test.ts`
@@ -245,102 +246,105 @@ Total: 513 (runtime: 233, contract: 280)
245
246
  - `tests/a11y-modal-contract.test.ts`
246
247
  - `tests/a11y-touch-target-contract.test.ts`
247
248
  - `tests/action-pin-contract.test.ts`
248
- - `tests/agent-mode-frontend-contract.test.js`
249
- - `tests/agent-mode-layout-contract.test.js`
250
- - `tests/agent-mode-right-sidebar-contract.test.js`
249
+ - `tests/agent-mode-frontend-contract.test.ts`
250
+ - `tests/agent-mode-layout-contract.test.ts`
251
+ - `tests/agent-mode-right-sidebar-contract.test.ts`
251
252
  - `tests/agent-mode-run-progress-contract.test.ts`
252
- - `tests/agent-mode-streaming-a11y-contract.test.js`
253
- - `tests/agent-mode-tool-call-rendering-contract.test.js`
254
- - `tests/agent-mode-tool-folding-contract.test.js`
253
+ - `tests/agent-mode-streaming-a11y-contract.test.ts`
254
+ - `tests/agent-mode-tool-call-rendering-contract.test.ts`
255
+ - `tests/agent-mode-tool-folding-contract.test.ts`
255
256
  - `tests/agy-artifact-check-contract.test.ts`
256
257
  - `tests/agy-artifact-read-bounds.test.ts`
257
- - `tests/api-docs-contract.test.js`
258
- - `tests/app-weight-splitting-contract.test.js`
258
+ - `tests/api-docs-contract.test.ts`
259
+ - `tests/app-weight-splitting-contract.test.ts`
259
260
  - `tests/asset-element-toggle-contract.test.ts`
260
- - `tests/asset-gen-keying-preview-contract.test.js`
261
- - `tests/asset-gen-media-lightbox-contract.test.js`
261
+ - `tests/asset-gen-keying-preview-contract.test.ts`
262
+ - `tests/asset-gen-media-lightbox-contract.test.ts`
262
263
  - `tests/asset-preview-element-fallback.test.ts`
263
264
  - `tests/assets-element-library-contract.test.ts`
264
265
  - `tests/assets-star-rename-contract.test.ts`
265
266
  - `tests/assets-ui-store-contract.test.ts`
266
267
  - `tests/assets-workspace-polish-contract.test.ts`
267
268
  - `tests/async-capacity-retry-behavior.test.ts`
268
- - `tests/async-capacity-retry-contract.test.js`
269
- - `tests/async-stream-subscribe-order.test.js`
269
+ - `tests/async-capacity-retry-contract.test.ts`
270
+ - `tests/async-stream-subscribe-order.test.ts`
270
271
  - `tests/audit-gate-contract.test.ts`
271
- - `tests/backend-hardening-contract.test.js`
272
- - `tests/background-cleanup-brush-rasterize.test.js`
273
- - `tests/background-cleanup-mask-compose.test.js`
274
- - `tests/bin.test.js`
275
- - `tests/blank-canvas-size-contract.test.js`
276
- - `tests/browser-attention-badge-contract.test.js`
277
- - `tests/canvas-alpha-controls-contract.test.js`
278
- - `tests/canvas-annotation-contract.test.js`
279
- - `tests/canvas-annotation-revert-contract.test.js`
280
- - `tests/canvas-apply-merged-contract.test.js`
281
- - `tests/canvas-background-cleanup-2x2-contract.test.js`
282
- - `tests/canvas-background-cleanup-contract.test.js`
283
- - `tests/canvas-edit-mask-flow-contract.test.js`
284
- - `tests/canvas-eraser-contract.test.js`
285
- - `tests/canvas-export-contract.test.js`
286
- - `tests/canvas-history-contract.test.js`
287
- - `tests/canvas-hit-test-contract.test.js`
288
- - `tests/canvas-mask-contract.test.js`
289
- - `tests/canvas-memo-contract.test.js`
290
- - `tests/canvas-memo-prompt-contract.test.js`
291
- - `tests/canvas-mode-contract.test.js`
292
- - `tests/canvas-object-key-contract.test.js`
293
- - `tests/canvas-persistence-contract.test.js`
272
+ - `tests/backend-hardening-contract.test.ts`
273
+ - `tests/background-cleanup-brush-rasterize.test.ts`
274
+ - `tests/background-cleanup-mask-compose.test.ts`
275
+ - `tests/bin.test.ts`
276
+ - `tests/blank-canvas-size-contract.test.ts`
277
+ - `tests/browser-attention-badge-contract.test.ts`
278
+ - `tests/canvas-alpha-controls-contract.test.ts`
279
+ - `tests/canvas-annotation-contract.test.ts`
280
+ - `tests/canvas-annotation-revert-contract.test.ts`
281
+ - `tests/canvas-apply-merged-contract.test.ts`
282
+ - `tests/canvas-background-cleanup-2x2-contract.test.ts`
283
+ - `tests/canvas-background-cleanup-contract.test.ts`
284
+ - `tests/canvas-edit-mask-flow-contract.test.ts`
285
+ - `tests/canvas-eraser-contract.test.ts`
286
+ - `tests/canvas-export-contract.test.ts`
287
+ - `tests/canvas-history-contract.test.ts`
288
+ - `tests/canvas-hit-test-contract.test.ts`
289
+ - `tests/canvas-mask-contract.test.ts`
290
+ - `tests/canvas-memo-contract.test.ts`
291
+ - `tests/canvas-memo-prompt-contract.test.ts`
292
+ - `tests/canvas-mode-contract.test.ts`
293
+ - `tests/canvas-object-key-contract.test.ts`
294
+ - `tests/canvas-persistence-contract.test.ts`
294
295
  - `tests/canvas-pptx-export-contract.test.ts`
295
- - `tests/canvas-selection-contract.test.js`
296
- - `tests/canvas-style-contract.test.js`
296
+ - `tests/canvas-selection-contract.test.ts`
297
+ - `tests/canvas-style-contract.test.ts`
297
298
  - `tests/canvas-svg-export-contract.test.ts`
298
299
  - `tests/canvas-vectorize-entry-contract.test.ts`
299
- - `tests/canvas-version-api.test.js`
300
- - `tests/canvas-version-contract.test.js`
301
- - `tests/canvas-viewport-pan-contract.test.js`
302
- - `tests/card-news-42-43-contract.test.js`
303
- - `tests/card-news-frontend-contract.test.js`
304
- - `tests/card-news-smoke.test.js`
300
+ - `tests/canvas-version-api.test.ts`
301
+ - `tests/canvas-version-contract.test.ts`
302
+ - `tests/canvas-viewport-pan-contract.test.ts`
303
+ - `tests/card-news-42-43-contract.test.ts`
304
+ - `tests/card-news-frontend-contract.test.ts`
305
+ - `tests/card-news-smoke.test.ts`
305
306
  - `tests/card-news-visible-text-language-contract.test.ts`
306
307
  - `tests/character-binding-ui-contract.test.ts`
307
308
  - `tests/ci-windows-candidate.test.ts`
308
- - `tests/cli-capabilities-contract.test.js`
309
- - `tests/cli-character-contract.test.js`
310
- - `tests/cli-commands.test.js`
311
- - `tests/cli-config-keys-contract.test.js`
312
- - `tests/cli-default-output-dir-contract.test.js`
313
- - `tests/cli-defaults-command-contract.test.js`
314
- - `tests/cli-destructive-safety-contract.test.js`
315
- - `tests/cli-doctor-status-contract.test.js`
316
- - `tests/cli-feature-parity-contract.test.js`
317
- - `tests/cli-help-safety-contract.test.js`
318
- - `tests/cli-output-recovery-contract.test.js`
319
- - `tests/cli-prompt-builder-contract.test.js`
320
- - `tests/cli-prompt-import-contract.test.js`
309
+ - `tests/cli-capabilities-contract.test.ts`
310
+ - `tests/cli-character-contract.test.ts`
311
+ - `tests/cli-commands.test.ts`
312
+ - `tests/cli-config-keys-contract.test.ts`
313
+ - `tests/cli-default-output-dir-contract.test.ts`
314
+ - `tests/cli-defaults-command-contract.test.ts`
315
+ - `tests/cli-destructive-safety-contract.test.ts`
316
+ - `tests/cli-doctor-status-contract.test.ts`
317
+ - `tests/cli-feature-parity-contract.test.ts`
318
+ - `tests/cli-grok-login.test.ts`
319
+ - `tests/cli-help-safety-contract.test.ts`
320
+ - `tests/cli-output-recovery-contract.test.ts`
321
+ - `tests/cli-prompt-builder-contract.test.ts`
322
+ - `tests/cli-prompt-import-contract.test.ts`
321
323
  - `tests/cli-session-graph.test.ts`
322
- - `tests/cli-skill-command-contract.test.js`
324
+ - `tests/cli-skill-command-contract.test.ts`
323
325
  - `tests/cli-unknown-options.test.ts`
324
- - `tests/cli-upscale-contract.test.js`
325
- - `tests/cli-video-command-contract.test.js`
326
+ - `tests/cli-upscale-contract.test.ts`
327
+ - `tests/cli-video-command-contract.test.ts`
326
328
  - `tests/color-key.test.ts`
327
329
  - `tests/comfy-cli-contract.test.ts`
328
330
  - `tests/comfy-display.test.ts`
329
- - `tests/comfy-export-ui-contract.test.js`
331
+ - `tests/comfy-export-ui-contract.test.ts`
330
332
  - `tests/comfy-ui-contract.test.ts`
331
- - `tests/comfyui-custom-node-contract.test.js`
332
- - `tests/composer-feedback-contract.test.js`
333
- - `tests/composer-mention-parity-contract.test.js`
334
- - `tests/composer-tray-ui-contract.test.js`
335
- - `tests/config.test.js`
333
+ - `tests/comfyui-custom-node-contract.test.ts`
334
+ - `tests/composer-feedback-contract.test.ts`
335
+ - `tests/composer-mention-parity-contract.test.ts`
336
+ - `tests/composer-tray-ui-contract.test.ts`
337
+ - `tests/config.test.ts`
336
338
  - `tests/contract-docs-projection.test.ts`
337
339
  - `tests/core-generation-mode.test.ts`
338
340
  - `tests/core-selection-actions.test.ts`
339
341
  - `tests/core-selection-memory.test.ts`
340
342
  - `tests/core-selection-reconcile.test.ts`
341
- - `tests/core-selection-transport.test.mjs`
342
- - `tests/current-image-actions-readiness-contract.test.js`
343
+ - `tests/core-selection-transport.test.ts`
344
+ - `tests/current-image-actions-readiness-contract.test.ts`
343
345
  - `tests/desktop-app-lifecycle.test.ts`
346
+ - `tests/desktop-context-menu.test.ts`
347
+ - `tests/desktop-edit-menu.test.ts`
344
348
  - `tests/desktop-mac-verification.test.ts`
345
349
  - `tests/desktop-release-assets.test.ts`
346
350
  - `tests/desktop-release-contract.test.ts`
@@ -349,159 +353,160 @@ Total: 513 (runtime: 233, contract: 280)
349
353
  - `tests/desktop-signing-policy.test.ts`
350
354
  - `tests/desktop-tray.test.ts`
351
355
  - `tests/desktop-updater.test.ts`
352
- - `tests/direct-mode-visual-contract.test.js`
356
+ - `tests/desktop-window-open.test.ts`
357
+ - `tests/direct-mode-visual-contract.test.ts`
353
358
  - `tests/dropped-media-sorting-contract.test.ts`
354
- - `tests/duration-slider-contract.test.js`
359
+ - `tests/duration-slider-contract.test.ts`
355
360
  - `tests/e2e-app-environment.test.ts`
356
- - `tests/e2e-runtime-build.test.mjs`
357
- - `tests/e2e-runtime-guards.test.mjs`
358
- - `tests/edit-mask-api-contract.test.js`
359
- - `tests/element-mention-ui-contract.test.js`
361
+ - `tests/e2e-runtime-build.test.ts`
362
+ - `tests/e2e-runtime-guards.test.ts`
363
+ - `tests/edit-mask-api-contract.test.ts`
364
+ - `tests/element-mention-ui-contract.test.ts`
360
365
  - `tests/error-ui-consumption.test.ts`
361
366
  - `tests/event-channel-replay-gap.test.ts`
362
367
  - `tests/execution-network-isolation.test.ts`
363
368
  - `tests/execution-process-isolation.test.ts`
364
369
  - `tests/frame-extraction-fallback.test.ts`
365
370
  - `tests/frontend-connection-state-contract.test.ts`
366
- - `tests/frontend-sse-risk-contract.test.js`
371
+ - `tests/frontend-sse-risk-contract.test.ts`
367
372
  - `tests/gallery-hang-regression-contract.test.ts`
368
- - `tests/gallery-load-older-contract.test.js`
369
- - `tests/gallery-navigation-ux-contract.test.js`
370
- - `tests/gallery-selection-during-generation-contract.test.js`
371
- - `tests/gallery-session-scope-contract.test.js`
372
- - `tests/gallery-shortcuts-behavior.test.js`
373
- - `tests/gallery-shortcuts-visible-domain-contract.test.js`
374
- - `tests/gallery-viewer-navigation-contract.test.js`
373
+ - `tests/gallery-load-older-contract.test.ts`
374
+ - `tests/gallery-navigation-ux-contract.test.ts`
375
+ - `tests/gallery-selection-during-generation-contract.test.ts`
376
+ - `tests/gallery-session-scope-contract.test.ts`
377
+ - `tests/gallery-shortcuts-behavior.test.ts`
378
+ - `tests/gallery-shortcuts-visible-domain-contract.test.ts`
379
+ - `tests/gallery-viewer-navigation-contract.test.ts`
375
380
  - `tests/gemini-api-wire-contract.test.ts`
376
381
  - `tests/gemini-transport-parity.test.ts`
377
- - `tests/gen-output-path-contract.test.js`
378
- - `tests/generation-controls-ux-contract.test.js`
379
- - `tests/generation-limit-unlock-contract.test.js`
382
+ - `tests/gen-output-path-contract.test.ts`
383
+ - `tests/generation-controls-ux-contract.test.ts`
384
+ - `tests/generation-limit-unlock-contract.test.ts`
380
385
  - `tests/governance-files-contract.test.ts`
381
- - `tests/health.test.js`
382
- - `tests/history-index-contract.test.js`
383
- - `tests/history-permanent-delete.test.js`
386
+ - `tests/health.test.ts`
387
+ - `tests/history-index-contract.test.ts`
388
+ - `tests/history-permanent-delete.test.ts`
384
389
  - `tests/history-strip-duplicate-contract.test.ts`
385
390
  - `tests/i18n-coverage-contract.test.ts`
386
391
  - `tests/i18n-dictionary-contract.test.ts`
387
- - `tests/image-metadata-ui-contract.test.js`
388
- - `tests/inflight-badge-popup-contract.test.js`
389
- - `tests/inflight-cancel-contract.test.js`
390
- - `tests/inflight-list-tooltip-contract.test.js`
391
- - `tests/inflight-popup-polish-contract.test.js`
392
+ - `tests/image-metadata-ui-contract.test.ts`
393
+ - `tests/inflight-badge-popup-contract.test.ts`
394
+ - `tests/inflight-cancel-contract.test.ts`
395
+ - `tests/inflight-list-tooltip-contract.test.ts`
396
+ - `tests/inflight-popup-polish-contract.test.ts`
392
397
  - `tests/inflight-reconciliation-behavior.test.ts`
393
398
  - `tests/inflight-reload-race.test.ts`
394
399
  - `tests/inflight-reload-reconcile-contract.test.ts`
395
400
  - `tests/install-runtime-contract.test.ts`
396
- - `tests/install-windows-contract.test.js`
397
- - `tests/issue75-prompt-studio-state-contract.test.js`
398
- - `tests/issue77-long-prompt-preview-contract.test.js`
399
- - `tests/j6-isolation-preflight.test.mjs`
401
+ - `tests/install-windows-contract.test.ts`
402
+ - `tests/issue75-prompt-studio-state-contract.test.ts`
403
+ - `tests/issue77-long-prompt-preview-contract.test.ts`
404
+ - `tests/j6-isolation-preflight.test.ts`
400
405
  - `tests/job-tracking-ci-contract.test.ts`
401
406
  - `tests/job-tracking-timeout-ui.test.ts`
402
407
  - `tests/lan-config.test.ts`
403
408
  - `tests/lane-catalog.test.ts`
404
409
  - `tests/mcp-media-kind-behavior.test.ts`
405
410
  - `tests/mcp-provider-smoke.test.ts`
406
- - `tests/mcp-provider-ui-contract.test.js`
411
+ - `tests/mcp-provider-ui-contract.test.ts`
407
412
  - `tests/mcp-readiness-observation.test.ts`
408
413
  - `tests/mcp-readiness.test.ts`
409
414
  - `tests/mcp-schema-spike-contract.test.ts`
410
415
  - `tests/mcp-selection-helpers.test.ts`
411
416
  - `tests/mcp-settings-states-contract.test.ts`
412
417
  - `tests/minimax-ui-registration-contract.test.ts`
413
- - `tests/mobile-compose-sheet-accessibility-contract.test.js`
414
- - `tests/mobile-composer-tray-contract.test.js`
415
- - `tests/mobile-generate-entry-contract.test.js`
418
+ - `tests/mobile-compose-sheet-accessibility-contract.test.ts`
419
+ - `tests/mobile-composer-tray-contract.test.ts`
420
+ - `tests/mobile-generate-entry-contract.test.ts`
416
421
  - `tests/model-select-lane-gating.test.ts`
417
- - `tests/multimode-backend-contract.test.js`
418
- - `tests/multimode-concurrent-store-contract.test.js`
419
- - `tests/multimode-ui-contract.test.js`
422
+ - `tests/multimode-backend-contract.test.ts`
423
+ - `tests/multimode-concurrent-store-contract.test.ts`
424
+ - `tests/multimode-ui-contract.test.ts`
420
425
  - `tests/nai-cli-built-smoke.test.ts`
421
426
  - `tests/nai-dual-prompt-contract.test.ts`
422
427
  - `tests/nai-ui-registration-contract.test.ts`
423
428
  - `tests/navrail-hover-label-contract.test.ts`
424
- - `tests/node-async-eventbus-contract.test.js`
425
- - `tests/node-batch-contract.test.js`
429
+ - `tests/node-async-eventbus-contract.test.ts`
430
+ - `tests/node-batch-contract.test.ts`
426
431
  - `tests/node-batch-partial-contract.test.ts`
427
- - `tests/node-child-refs-contract.test.js`
428
- - `tests/node-child-refs-payload.test.js`
432
+ - `tests/node-child-refs-contract.test.ts`
433
+ - `tests/node-child-refs-payload.test.ts`
429
434
  - `tests/node-compatibility.test.ts`
430
- - `tests/node-context-policy.test.js`
435
+ - `tests/node-context-policy.test.ts`
431
436
  - `tests/node-cycle-contract.test.ts`
432
- - `tests/node-diagnostics-contract.test.js`
433
- - `tests/node-edge-disconnect-contract.test.js`
437
+ - `tests/node-diagnostics-contract.test.ts`
438
+ - `tests/node-edge-disconnect-contract.test.ts`
434
439
  - `tests/node-error-info-contract.test.ts`
435
- - `tests/node-footer-compact-contract.test.js`
436
- - `tests/node-gallery-drop-contract.test.js`
437
- - `tests/node-generation-lock-contract.test.js`
440
+ - `tests/node-footer-compact-contract.test.ts`
441
+ - `tests/node-gallery-drop-contract.test.ts`
442
+ - `tests/node-generation-lock-contract.test.ts`
438
443
  - `tests/node-history-contract.test.ts`
439
444
  - `tests/node-image-preview-contract.test.ts`
440
- - `tests/node-layout-contract.test.js`
445
+ - `tests/node-layout-contract.test.ts`
441
446
  - `tests/node-multi-parent-ui.test.ts`
442
- - `tests/node-pending-recovery-contract.test.js`
443
- - `tests/node-regen-actions-contract.test.js`
444
- - `tests/node-session-evaporation-contract.test.js`
445
- - `tests/node-studio-ui-contract.test.js`
446
- - `tests/node-ui-contract.test.js`
447
+ - `tests/node-pending-recovery-contract.test.ts`
448
+ - `tests/node-regen-actions-contract.test.ts`
449
+ - `tests/node-session-evaporation-contract.test.ts`
450
+ - `tests/node-studio-ui-contract.test.ts`
451
+ - `tests/node-ui-contract.test.ts`
447
452
  - `tests/oauth-image-lane-contract.test.ts`
448
- - `tests/oauth-masked-edit-contract.test.js`
449
- - `tests/oauth-proxy-edit-mask-contract.test.js`
453
+ - `tests/oauth-masked-edit-contract.test.ts`
454
+ - `tests/oauth-proxy-edit-mask-contract.test.ts`
450
455
  - `tests/package-global-update-smoke-contract.test.ts`
451
- - `tests/package-smoke.test.js`
456
+ - `tests/package-smoke.test.ts`
452
457
  - `tests/pages-publication-contract.test.ts`
453
458
  - `tests/platform-open-url.test.ts`
454
- - `tests/png-info-contract.test.js`
459
+ - `tests/png-info-contract.test.ts`
455
460
  - `tests/portal-dropdown-scroll-dismiss-contract.test.ts`
456
- - `tests/pr-fast-history.test.mjs`
461
+ - `tests/pr-fast-history.test.ts`
457
462
  - `tests/pr-screenshot-gate.test.ts`
458
- - `tests/prompt-curated-search-contract.test.js`
459
- - `tests/prompt-discovery-ui-contract.test.js`
460
- - `tests/prompt-import-dialog-ui-contract.test.js`
461
- - `tests/prompt-import-folder-ui-contract.test.js`
462
- - `tests/prompt-import-search-ux-contract.test.js`
463
- - `tests/prompt-library-ui-contract.test.js`
464
- - `tests/prompt-studio-docs-contract.test.js`
465
- - `tests/prompt-studio-ui-contract.test.js`
463
+ - `tests/prompt-curated-search-contract.test.ts`
464
+ - `tests/prompt-discovery-ui-contract.test.ts`
465
+ - `tests/prompt-import-dialog-ui-contract.test.ts`
466
+ - `tests/prompt-import-folder-ui-contract.test.ts`
467
+ - `tests/prompt-import-search-ux-contract.test.ts`
468
+ - `tests/prompt-library-ui-contract.test.ts`
469
+ - `tests/prompt-studio-docs-contract.test.ts`
470
+ - `tests/prompt-studio-ui-contract.test.ts`
466
471
  - `tests/provenance-chip-contract.test.ts`
467
472
  - `tests/provider-canary-live-contract.test.ts`
468
473
  - `tests/provider-canary-parity.test.ts`
469
474
  - `tests/provider-execution-edit.test.ts`
470
475
  - `tests/provider-execution-harness.test.ts`
471
476
  - `tests/provider-execution-imports.test.ts`
472
- - `tests/provider-ui-polish-contract.test.js`
477
+ - `tests/provider-ui-polish-contract.test.ts`
473
478
  - `tests/reference-limits.test.ts`
474
479
  - `tests/reference-tray-model.test.ts`
475
480
  - `tests/release-pipeline-contract.test.ts`
476
481
  - `tests/runtime-install-projection.test.ts`
477
482
  - `tests/selector-comfy-admission.test.ts`
478
- - `tests/server-fallback-contract.test.js`
479
- - `tests/server.test.js`
480
- - `tests/session-conflict.test.js`
483
+ - `tests/server-fallback-contract.test.ts`
484
+ - `tests/server.test.ts`
485
+ - `tests/session-conflict.test.ts`
481
486
  - `tests/settings-i18n-state-contract.test.ts`
482
- - `tests/settings-persistence-contract.test.js`
483
- - `tests/settings-workspace-layout-contract.test.js`
484
- - `tests/size-custom-input-contract.test.js`
485
- - `tests/size-presets.test.js`
487
+ - `tests/settings-persistence-contract.test.ts`
488
+ - `tests/settings-workspace-layout-contract.test.ts`
489
+ - `tests/size-custom-input-contract.test.ts`
490
+ - `tests/size-presets.test.ts`
486
491
  - `tests/slash-command-menu-contract.test.ts`
487
492
  - `tests/soft-unmix.test.ts`
488
- - `tests/sprite-curator-ui-contract.test.js`
489
- - `tests/sprite-recipe-ui-contract.test.js`
493
+ - `tests/sprite-curator-ui-contract.test.ts`
494
+ - `tests/sprite-recipe-ui-contract.test.ts`
490
495
  - `tests/sprite-transform-contract.test.ts`
491
496
  - `tests/star-prompt-ui-contract.test.ts`
492
497
  - `tests/star-surface-controls-contract.test.ts`
493
- - `tests/storage-open-generated-dir.test.js`
494
- - `tests/structure-line-counts-contract.test.js`
498
+ - `tests/storage-open-generated-dir.test.ts`
499
+ - `tests/structure-line-counts-contract.test.ts`
495
500
  - `tests/studio-surface-docs-contract.test.ts`
496
- - `tests/style-feature-removal-contract.test.js`
501
+ - `tests/style-feature-removal-contract.test.ts`
497
502
  - `tests/subprocess-deadline-contract.test.ts`
498
503
  - `tests/test-runner-invocation.test.ts`
499
- - `tests/toast-stack-contract.test.js`
504
+ - `tests/toast-stack-contract.test.ts`
500
505
  - `tests/tools-cli-contract.test.ts`
501
- - `tests/transparent-background-ui-contract.test.js`
502
- - `tests/ui-build-receipt-inventory.test.mjs`
503
- - `tests/ui-build-receipt-transaction.test.mjs`
504
- - `tests/ui-build-receipt.test.mjs`
506
+ - `tests/transparent-background-ui-contract.test.ts`
507
+ - `tests/ui-build-receipt-inventory.test.ts`
508
+ - `tests/ui-build-receipt-transaction.test.ts`
509
+ - `tests/ui-build-receipt.test.ts`
505
510
  - `tests/ui-color-token-contract.test.ts`
506
511
  - `tests/ui-error-code-contract.test.ts`
507
512
  - `tests/ui-glyph-policy.test.ts`
@@ -511,14 +516,14 @@ Total: 513 (runtime: 233, contract: 280)
511
516
  - `tests/ui-typography-rules-contract.test.ts`
512
517
  - `tests/vectorize-cli-contract.test.ts`
513
518
  - `tests/vectorize-panel-contract.test.ts`
514
- - `tests/video-continuity-ui-contract.test.js`
515
- - `tests/video-defaults-persistence-contract.test.js`
519
+ - `tests/video-continuity-ui-contract.test.ts`
520
+ - `tests/video-defaults-persistence-contract.test.ts`
516
521
  - `tests/video-edit-client-contract.test.ts`
517
- - `tests/video-extend-ui-contract.test.js`
518
- - `tests/video-frame-local-file.test.js`
522
+ - `tests/video-extend-ui-contract.test.ts`
523
+ - `tests/video-frame-local-file.test.ts`
519
524
  - `tests/video-gallery-refresh-contract.test.ts`
520
- - `tests/video-inflight-kind-contract.test.js`
525
+ - `tests/video-inflight-kind-contract.test.ts`
521
526
  - `tests/video-ui-source-count.test.ts`
522
- - `tests/vite-dev-port-contract.test.js`
527
+ - `tests/vite-dev-port-contract.test.ts`
523
528
  - `tests/wand-erase.test.ts`
524
- - `tests/web-search-toggle-contract.test.js`
529
+ - `tests/web-search-toggle-contract.test.ts`
@@ -12,6 +12,8 @@ const SUPPORTED_ASPECTS = [
12
12
  "9:19.5",
13
13
  "20:9",
14
14
  "9:20",
15
+ "21:9",
16
+ "5:2",
15
17
  ];
16
18
  const PRESET_MAP = {
17
19
  "1024x1024": { aspect_ratio: "1:1", resolution: "1k" },
@@ -6,7 +6,9 @@
6
6
  // - Poll tool is `job_status({ jobId })` (uuid), NOT `get_task`.
7
7
  // - `job_status` supports `sync: true` (~25s server-side wait) to reduce churn.
8
8
  // - `generate_video`/`generate_image` take `medias[].value` as media_id/job_id
9
- // via `media_import_url` / `media_upload_widget`, never raw URLs.
9
+ // via `media_upload` + `media_confirm` / `media_import_url`, never raw URLs,
10
+ // and `medias[].role` names the provider-declared role (image|start_image|
11
+ // end_image per models_explore — not the runway-style canonical names).
10
12
  // - Status enum: pending|waiting|queued|in_progress|ip_detect|completed|failed|canceled|nsfw|ip_detected
11
13
  // - Output URL lives at `generation.results.rawUrl`.
12
14
  //
@@ -58,6 +60,7 @@ export const HIGGSFIELD_VIDEO_MODELS = [
58
60
  ];
59
61
  const DEFAULT_MODEL = { image: "soul_2", video: "cinematic_studio_3_0" };
60
62
  const TASK_ID_PATTERN = /\b[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}\b/i;
63
+ const FORWARDED_PARAM_KEYS = new Set(["resolution", "quality", "count", "use_unlim"]);
61
64
  function buildGenerateCall(request) {
62
65
  const model = request.model ?? DEFAULT_MODEL[request.kind];
63
66
  // 260723: the web app sends `use_unlim: true` on every generation (captured
@@ -75,27 +78,31 @@ function buildGenerateCall(request) {
75
78
  }
76
79
  // Provider-declared scalar knobs (models catalog): resolution (nano_banana),
77
80
  // quality (soul), count. Only whitelisted keys are forwarded.
78
- const FORWARDED_PARAM_KEYS = new Set(["resolution", "quality", "count", "use_unlim"]);
79
81
  for (const [key, value] of Object.entries(request.parameters ?? {})) {
80
82
  if (FORWARDED_PARAM_KEYS.has(key))
81
83
  params[key] = value;
82
84
  }
83
- // Reference images: Higgsfield requires media_id (from media_import_url),
84
- // not raw URLs. The MCP media pipeline handles upload before calling here,
85
- // so referenceImages[].url should already be a provider-hosted media_id or
86
- // a public HTTPS URL that the server can auto-import.
85
+ // medias[].value must be a provider media_id (local uploads go through
86
+ // media_upload + media_confirm; https inputs through media_import_url).
87
+ // medias[].role must be a provider-declared role name — start_image /
88
+ // end_image / image. Every media slot in models_explore is type:"image";
89
+ // no higgsfield model declares a video-input role.
87
90
  const medias = [];
88
91
  if (request.startFrameUrl) {
89
- medias.push({ value: request.startFrameUrl, role: "start_frame" });
92
+ medias.push({ value: request.startFrameUrl, role: "start_image" });
90
93
  }
91
94
  if (request.endFrameUrl) {
92
- medias.push({ value: request.endFrameUrl, role: "end_frame" });
95
+ if (!request.startFrameUrl)
96
+ throw new Error(`MCP_END_FRAME_REQUIRES_START:${model}`);
97
+ medias.push({ value: request.endFrameUrl, role: "end_image" });
93
98
  }
94
99
  for (const ref of request.referenceImages ?? []) {
95
- medias.push({ value: ref.url, role: ref.tag || "reference" });
100
+ medias.push({ value: ref.url, role: "image" });
96
101
  }
97
102
  if (request.referenceVideoUrl) {
98
- medias.push({ value: request.referenceVideoUrl, role: "video_reference" });
103
+ // Undeclared roles would either be dropped silently or fail the whole job
104
+ // provider-side; forward a video role once models_explore declares one.
105
+ throw new Error(`MCP_INPUT_ROLE_UNSUPPORTED:${model}:video_references`);
99
106
  }
100
107
  if (medias.length > 0)
101
108
  params.medias = medias;
@@ -0,0 +1,77 @@
1
+ // Higgsfield media inputs (260925 audit): local files go media_upload ->
2
+ // presigned PUT -> media_confirm; public https inputs go media_import_url.
3
+ // Both produce the media_id that generate_*'s medias[].value requires —
4
+ // higgsfield never accepts raw URLs or runway-hosted assets.
5
+ import { readFile } from "node:fs/promises";
6
+ import { assertPublicHttps } from "../downloadMediaResult.js";
7
+ function structuredOf(result) {
8
+ const value = result.structuredContent;
9
+ return value && typeof value === "object" ? value : {};
10
+ }
11
+ function mediaTypeFor(mimeType) {
12
+ if (mimeType.startsWith("video/"))
13
+ return "video";
14
+ if (mimeType.startsWith("audio/"))
15
+ return "audio";
16
+ return "image";
17
+ }
18
+ function providerError(result) {
19
+ const error = structuredOf(result).error;
20
+ return typeof error === "string" && error ? error.slice(0, 120) : null;
21
+ }
22
+ /** Upload a validated local file; returns the higgsfield media_id for medias[].value. */
23
+ export async function uploadLocalMediaToHiggsfield(manager, filePath, options) {
24
+ const bytes = await readFile(filePath);
25
+ if (bytes.byteLength === 0)
26
+ throw new Error("MCP_UPLOAD_EMPTY");
27
+ if (bytes.byteLength > (options.maxBytes ?? 100 * 1024 * 1024))
28
+ throw new Error("MCP_UPLOAD_TOO_LARGE");
29
+ const type = mediaTypeFor(options.mimeType);
30
+ const init = await manager.callTool("higgsfield", "media_upload", {
31
+ filename: options.fileName,
32
+ content_type: options.mimeType,
33
+ });
34
+ const uploads = structuredOf(init).uploads;
35
+ const slot = Array.isArray(uploads) ? uploads[0] : undefined;
36
+ const uploadUrl = typeof slot?.upload_url === "string" ? slot.upload_url : null;
37
+ const mediaId = typeof slot?.media_id === "string" ? slot.media_id : null;
38
+ if (!uploadUrl || !mediaId) {
39
+ const detail = providerError(init);
40
+ throw new Error(`MCP_UPLOAD_INIT_INVALID${detail ? `:${detail}` : ""}`);
41
+ }
42
+ // Same public-HTTPS/IP validation as downloads — hostile MCP output must not
43
+ // redirect local media elsewhere.
44
+ const target = new URL(uploadUrl);
45
+ await assertPublicHttps(target);
46
+ const response = await fetch(target, {
47
+ method: "PUT",
48
+ redirect: "error",
49
+ body: bytes,
50
+ headers: { "content-type": options.mimeType, "content-length": String(bytes.byteLength) },
51
+ signal: AbortSignal.timeout(180_000),
52
+ });
53
+ if (!response.ok)
54
+ throw new Error(`MCP_UPLOAD_PUT_FAILED:${response.status}`);
55
+ const confirmed = await manager.callTool("higgsfield", "media_confirm", { type, media_id: mediaId });
56
+ const confirmError = providerError(confirmed);
57
+ if (confirmError)
58
+ throw new Error(`MCP_UPLOAD_CONFIRM_FAILED:${confirmError}`);
59
+ const results = structuredOf(confirmed).results;
60
+ const confirmedId = Array.isArray(results)
61
+ ? results[0]?.media_id
62
+ : undefined;
63
+ if (typeof confirmedId !== "string" || !confirmedId)
64
+ throw new Error("MCP_UPLOAD_CONFIRM_INVALID");
65
+ return confirmedId;
66
+ }
67
+ /** Import a public https URL into a higgsfield media_id via media_import_url. */
68
+ export async function importUrlToHiggsfield(manager, url, type) {
69
+ const result = await manager.callTool("higgsfield", "media_import_url", { url, type });
70
+ const detail = providerError(result);
71
+ if (detail)
72
+ throw new Error(`MCP_IMPORT_URL_FAILED:${detail}`);
73
+ const mediaId = structuredOf(result).media_id;
74
+ if (typeof mediaId !== "string" || !mediaId)
75
+ throw new Error("MCP_IMPORT_URL_INVALID");
76
+ return mediaId;
77
+ }