@engenty/wizards-runtime 0.0.0-stage → 0.7.5
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/LICENSE +110 -0
- package/README.md +214 -2
- package/apps/runtime/dist/agents/architect.js +444 -0
- package/apps/runtime/dist/agents/studio-assistant.js +883 -0
- package/apps/runtime/dist/agents/subscription.js +207 -0
- package/apps/runtime/dist/agents/thought.js +21 -0
- package/apps/runtime/dist/agents/website.js +480 -0
- package/apps/runtime/dist/app.js +359 -0
- package/apps/runtime/dist/apple/wizards-apple +0 -0
- package/apps/runtime/dist/assistant/browser.js +26 -0
- package/apps/runtime/dist/assistant/docs.js +158 -0
- package/apps/runtime/dist/assistant/knows.js +353 -0
- package/apps/runtime/dist/assistant/primer.js +26 -0
- package/apps/runtime/dist/assistant/runs.js +24 -0
- package/apps/runtime/dist/auth/account.js +175 -0
- package/apps/runtime/dist/auth/device.js +112 -0
- package/apps/runtime/dist/auth/index.js +358 -0
- package/apps/runtime/dist/auth/keys.js +55 -0
- package/apps/runtime/dist/auth/local-ticket.js +26 -0
- package/apps/runtime/dist/authoring/example.js +74 -0
- package/apps/runtime/dist/authoring/guide.js +433 -0
- package/apps/runtime/dist/authoring/ops.js +117 -0
- package/apps/runtime/dist/browser/fast/action-space.js +160 -0
- package/apps/runtime/dist/browser/fast/classifier.js +91 -0
- package/apps/runtime/dist/browser/fast/decide.js +171 -0
- package/apps/runtime/dist/browser/fast/driver.js +141 -0
- package/apps/runtime/dist/browser/fast/instructions.js +33 -0
- package/apps/runtime/dist/browser/fast/run.js +249 -0
- package/apps/runtime/dist/browser/fast/span-picker.js +153 -0
- package/apps/runtime/dist/browser/fast/text-helper.js +178 -0
- package/apps/runtime/dist/browser/snapshot.js +335 -0
- package/apps/runtime/dist/cli/account.js +385 -0
- package/apps/runtime/dist/cli/autostart.js +182 -0
- package/apps/runtime/dist/cli/autoupdate.js +221 -0
- package/apps/runtime/dist/cli/clients.js +44 -0
- package/apps/runtime/dist/cli/connect.js +540 -0
- package/apps/runtime/dist/cli/environment.js +93 -0
- package/apps/runtime/dist/cli/exec.js +45 -0
- package/apps/runtime/dist/cli/features.js +50 -0
- package/apps/runtime/dist/cli/home.js +88 -0
- package/apps/runtime/dist/cli/install-env.js +33 -0
- package/apps/runtime/dist/cli/machine.js +107 -0
- package/apps/runtime/dist/cli/main.js +222 -0
- package/apps/runtime/dist/cli/mcp.js +136 -0
- package/apps/runtime/dist/cli/menu.js +202 -0
- package/apps/runtime/dist/cli/processes.js +72 -0
- package/apps/runtime/dist/cli/report.js +66 -0
- package/apps/runtime/dist/cli/setup.js +335 -0
- package/apps/runtime/dist/cli/start.js +276 -0
- package/apps/runtime/dist/cli/status.js +203 -0
- package/apps/runtime/dist/cli/ui.js +59 -0
- package/apps/runtime/dist/cli/uninstall-main.js +20 -0
- package/apps/runtime/dist/cli/uninstall.js +231 -0
- package/apps/runtime/dist/cli/update.js +86 -0
- package/apps/runtime/dist/cli/validate.js +75 -0
- package/apps/runtime/dist/cli/versions.js +123 -0
- package/apps/runtime/dist/connectors/builtin.js +86 -0
- package/apps/runtime/dist/connectors/external.js +409 -0
- package/apps/runtime/dist/connectors/gmail.js +107 -0
- package/apps/runtime/dist/connectors/imap.js +261 -0
- package/apps/runtime/dist/connectors/index.js +325 -0
- package/apps/runtime/dist/connectors/mail.js +72 -0
- package/apps/runtime/dist/connectors/outlook.js +90 -0
- package/apps/runtime/dist/connectors/plugin-connections.js +343 -0
- package/apps/runtime/dist/connectors/via-account.js +65 -0
- package/apps/runtime/dist/credits/credits.js +100 -0
- package/apps/runtime/dist/credits/estimate.js +152 -0
- package/apps/runtime/dist/db/adopt.js +149 -0
- package/apps/runtime/dist/db/client.js +173 -0
- package/apps/runtime/dist/db/control-schema.js +133 -0
- package/apps/runtime/dist/db/migrations/control/0000_last_absorbing_man.sql +47 -0
- package/apps/runtime/dist/db/migrations/control/0001_gorgeous_nicolaos.sql +41 -0
- package/apps/runtime/dist/db/migrations/control/0002_secret_penance.sql +1 -0
- package/apps/runtime/dist/db/migrations/control/0003_nasty_mordo.sql +3 -0
- package/apps/runtime/dist/db/migrations/control/0004_breezy_eddie_brock.sql +6 -0
- package/apps/runtime/dist/db/migrations/control/0005_chilly_oracle.sql +1 -0
- package/apps/runtime/dist/db/migrations/control/0006_sleepy_mojo.sql +1 -0
- package/apps/runtime/dist/db/migrations/control/0007_common_the_hand.sql +15 -0
- package/apps/runtime/dist/db/migrations/control/0008_runner.sql +8 -0
- package/apps/runtime/dist/db/migrations/control/0009_plugin_claim.sql +9 -0
- package/apps/runtime/dist/db/migrations/control/0010_tan_snowbird.sql +6 -0
- package/apps/runtime/dist/db/migrations/control/meta/0000_snapshot.json +311 -0
- package/apps/runtime/dist/db/migrations/control/meta/0001_snapshot.json +599 -0
- package/apps/runtime/dist/db/migrations/control/meta/0002_snapshot.json +607 -0
- package/apps/runtime/dist/db/migrations/control/meta/0003_snapshot.json +311 -0
- package/apps/runtime/dist/db/migrations/control/meta/0004_snapshot.json +350 -0
- package/apps/runtime/dist/db/migrations/control/meta/0005_snapshot.json +358 -0
- package/apps/runtime/dist/db/migrations/control/meta/0006_snapshot.json +365 -0
- package/apps/runtime/dist/db/migrations/control/meta/0007_snapshot.json +460 -0
- package/apps/runtime/dist/db/migrations/control/meta/0008_snapshot.json +505 -0
- package/apps/runtime/dist/db/migrations/control/meta/0009_snapshot.json +561 -0
- package/apps/runtime/dist/db/migrations/control/meta/0010_snapshot.json +600 -0
- package/apps/runtime/dist/db/migrations/control/meta/_journal.json +83 -0
- package/apps/runtime/dist/db/migrations/tenant/0000_eminent_freak.sql +188 -0
- package/apps/runtime/dist/db/migrations/tenant/0001_salty_master_chief.sql +1 -0
- package/apps/runtime/dist/db/migrations/tenant/0002_cloudy_firebird.sql +1 -0
- package/apps/runtime/dist/db/migrations/tenant/0003_brown_warbound.sql +52 -0
- package/apps/runtime/dist/db/migrations/tenant/0004_romantic_nocturne.sql +6 -0
- package/apps/runtime/dist/db/migrations/tenant/0005_reflective_forgotten_one.sql +2 -0
- package/apps/runtime/dist/db/migrations/tenant/0006_hard_war_machine.sql +30 -0
- package/apps/runtime/dist/db/migrations/tenant/0007_space_data.sql +43 -0
- package/apps/runtime/dist/db/migrations/tenant/0008_run_event_note.sql +1 -0
- package/apps/runtime/dist/db/migrations/tenant/0009_knowledge.sql +104 -0
- package/apps/runtime/dist/db/migrations/tenant/0010_plugin_connection.sql +15 -0
- package/apps/runtime/dist/db/migrations/tenant/0011_runners.sql +2 -0
- package/apps/runtime/dist/db/migrations/tenant/0012_assistant_chat.sql +40 -0
- package/apps/runtime/dist/db/migrations/tenant/0013_run_origin.sql +1 -0
- package/apps/runtime/dist/db/migrations/tenant/0014_run_answer.sql +2 -0
- package/apps/runtime/dist/db/migrations/tenant/0015_device_calls.sql +13 -0
- package/apps/runtime/dist/db/migrations/tenant/0016_even_trish_tilby.sql +12 -0
- package/apps/runtime/dist/db/migrations/tenant/meta/0000_snapshot.json +1323 -0
- package/apps/runtime/dist/db/migrations/tenant/meta/0001_snapshot.json +1330 -0
- package/apps/runtime/dist/db/migrations/tenant/meta/0002_snapshot.json +1337 -0
- package/apps/runtime/dist/db/migrations/tenant/meta/0003_snapshot.json +1605 -0
- package/apps/runtime/dist/db/migrations/tenant/meta/0004_snapshot.json +1645 -0
- package/apps/runtime/dist/db/migrations/tenant/meta/0005_snapshot.json +1659 -0
- package/apps/runtime/dist/db/migrations/tenant/meta/0006_snapshot.json +1696 -0
- package/apps/runtime/dist/db/migrations/tenant/meta/0007_snapshot.json +2012 -0
- package/apps/runtime/dist/db/migrations/tenant/meta/0008_snapshot.json +2019 -0
- package/apps/runtime/dist/db/migrations/tenant/meta/0009_snapshot.json +2837 -0
- package/apps/runtime/dist/db/migrations/tenant/meta/0010_snapshot.json +2942 -0
- package/apps/runtime/dist/db/migrations/tenant/meta/0011_snapshot.json +2956 -0
- package/apps/runtime/dist/db/migrations/tenant/meta/0012_snapshot.json +3132 -0
- package/apps/runtime/dist/db/migrations/tenant/meta/0013_snapshot.json +3139 -0
- package/apps/runtime/dist/db/migrations/tenant/meta/0014_snapshot.json +3154 -0
- package/apps/runtime/dist/db/migrations/tenant/meta/0015_snapshot.json +3243 -0
- package/apps/runtime/dist/db/migrations/tenant/meta/0016_snapshot.json +3326 -0
- package/apps/runtime/dist/db/migrations/tenant/meta/_journal.json +125 -0
- package/apps/runtime/dist/db/schema.js +602 -0
- package/apps/runtime/dist/debug.js +196 -0
- package/apps/runtime/dist/docs/dev/index.md +41 -0
- package/apps/runtime/dist/docs/dev/meta.json +7 -0
- package/apps/runtime/dist/docs/dev/plugins/connections.md +74 -0
- package/apps/runtime/dist/docs/dev/plugins/events.md +89 -0
- package/apps/runtime/dist/docs/dev/plugins/examples.md +101 -0
- package/apps/runtime/dist/docs/dev/plugins/index.md +78 -0
- package/apps/runtime/dist/docs/dev/plugins/layout.md +90 -0
- package/apps/runtime/dist/docs/dev/plugins/meta.json +28 -0
- package/apps/runtime/dist/docs/dev/plugins/quick-start.md +94 -0
- package/apps/runtime/dist/docs/dev/plugins/reference.md +348 -0
- package/apps/runtime/dist/docs/dev/plugins/reloading.md +76 -0
- package/apps/runtime/dist/docs/dev/plugins/routes.md +185 -0
- package/apps/runtime/dist/docs/dev/plugins/runners.md +230 -0
- package/apps/runtime/dist/docs/dev/plugins/server.md +281 -0
- package/apps/runtime/dist/docs/dev/plugins/shipping.md +79 -0
- package/apps/runtime/dist/docs/dev/plugins/space.md +297 -0
- package/apps/runtime/dist/docs/dev/plugins/starters.md +70 -0
- package/apps/runtime/dist/docs/dev/plugins/studio.md +363 -0
- package/apps/runtime/dist/docs/dev/plugins/styling.md +129 -0
- package/apps/runtime/dist/docs/dev/plugins/tables.md +104 -0
- package/apps/runtime/dist/docs/dev/plugins/tools.md +139 -0
- package/apps/runtime/dist/docs/dev/wizard-format.md +125 -0
- package/apps/runtime/dist/docs/meta.json +3 -0
- package/apps/runtime/dist/docs/user/ai-apps.md +112 -0
- package/apps/runtime/dist/docs/user/assistant.md +77 -0
- package/apps/runtime/dist/docs/user/build-a-wizard.md +116 -0
- package/apps/runtime/dist/docs/user/command-line.md +83 -0
- package/apps/runtime/dist/docs/user/connectors.md +90 -0
- package/apps/runtime/dist/docs/user/create-account.md +52 -0
- package/apps/runtime/dist/docs/user/data.md +66 -0
- package/apps/runtime/dist/docs/user/first-start.md +60 -0
- package/apps/runtime/dist/docs/user/index.md +49 -0
- package/apps/runtime/dist/docs/user/install.md +123 -0
- package/apps/runtime/dist/docs/user/local-only.md +42 -0
- package/apps/runtime/dist/docs/user/meta.json +32 -0
- package/apps/runtime/dist/docs/user/models.md +156 -0
- package/apps/runtime/dist/docs/user/plugins.md +71 -0
- package/apps/runtime/dist/docs/user/run.md +98 -0
- package/apps/runtime/dist/docs/user/server.md +52 -0
- package/apps/runtime/dist/docs/user/space.md +126 -0
- package/apps/runtime/dist/docs/user/steps.md +220 -0
- package/apps/runtime/dist/docs/user/test-and-share.md +224 -0
- package/apps/runtime/dist/docs/user/troubleshooting.md +67 -0
- package/apps/runtime/dist/docs/user/wordpress.md +121 -0
- package/apps/runtime/dist/documents/parse.js +246 -0
- package/apps/runtime/dist/engenty/connections-external/build-connector.js +193 -0
- package/apps/runtime/dist/engenty/connections-external/errors.js +11 -0
- package/apps/runtime/dist/engenty/connections-external/import-service.js +270 -0
- package/apps/runtime/dist/engenty/connections-external/importer/classify.js +34 -0
- package/apps/runtime/dist/engenty/connections-external/importer/map-auth.js +185 -0
- package/apps/runtime/dist/engenty/connections-external/importer/normalize-mcp.js +63 -0
- package/apps/runtime/dist/engenty/connections-external/importer/normalize-openapi.js +130 -0
- package/apps/runtime/dist/engenty/connections-external/invoke/http-invoker.js +143 -0
- package/apps/runtime/dist/engenty/connections-external/invoke/mcp-client.js +143 -0
- package/apps/runtime/dist/engenty/connections-external/net/guarded-fetch.js +119 -0
- package/apps/runtime/dist/engenty/connections-external/oauth-dcr.js +88 -0
- package/apps/runtime/dist/engenty/connections-external/registry-client.js +336 -0
- package/apps/runtime/dist/engenty/connections-external/registry-source.js +82 -0
- package/apps/runtime/dist/engenty/connections-external/types.js +7 -0
- package/apps/runtime/dist/engenty/connections-github/connector.js +209 -0
- package/apps/runtime/dist/engenty/connections-google/connectors/calendar.js +381 -0
- package/apps/runtime/dist/engenty/connections-google/connectors/contacts.js +94 -0
- package/apps/runtime/dist/engenty/connections-google/connectors/drive.js +290 -0
- package/apps/runtime/dist/engenty/connections-google/connectors/gmail.js +567 -0
- package/apps/runtime/dist/engenty/connections-google/definitions.js +11 -0
- package/apps/runtime/dist/engenty/connections-google/shared.js +73 -0
- package/apps/runtime/dist/engenty/connections-hubspot/connector.js +140 -0
- package/apps/runtime/dist/engenty/connections-microsoft/action.js +17 -0
- package/apps/runtime/dist/engenty/connections-microsoft/graph.js +114 -0
- package/apps/runtime/dist/engenty/connections-microsoft/onedrive.js +286 -0
- package/apps/runtime/dist/engenty/connections-microsoft/outlook.js +492 -0
- package/apps/runtime/dist/engenty/connections-s3/s3.js +328 -0
- package/apps/runtime/dist/engenty/connections-sdk/files-capability.js +86 -0
- package/apps/runtime/dist/engenty/connections-sdk/oauth2.js +150 -0
- package/apps/runtime/dist/engenty/connections-sdk/registry.js +89 -0
- package/apps/runtime/dist/engenty/connections-sdk/storage-capability.js +84 -0
- package/apps/runtime/dist/engenty/connections-sdk/types.js +30 -0
- package/apps/runtime/dist/engenty/connections-slack/connector.js +360 -0
- package/apps/runtime/dist/engenty/csv-import/csv-matrix.js +178 -0
- package/apps/runtime/dist/engenty/csv-import/import-sources.js +87 -0
- package/apps/runtime/dist/engenty/csv-import/parse-csv.js +129 -0
- package/apps/runtime/dist/engenty/csv-import/serialize-csv.js +20 -0
- package/apps/runtime/dist/engenty/csv-import/types.js +1 -0
- package/apps/runtime/dist/engenty/csv-import/xlsx-workbook.js +159 -0
- package/apps/runtime/dist/engenty/doc-converter/interface.js +9 -0
- package/apps/runtime/dist/engenty/doc-converter/page-break.js +92 -0
- package/apps/runtime/dist/engenty/doc-converter/providers/local/index.js +72 -0
- package/apps/runtime/dist/engenty/document-scanner/schemas/classifier.js +30 -0
- package/apps/runtime/dist/engenty/document-scanner/schemas/index.js +4 -0
- package/apps/runtime/dist/engenty/document-scanner/schemas/invoice.js +73 -0
- package/apps/runtime/dist/engenty/document-scanner/schemas/receipt.js +63 -0
- package/apps/runtime/dist/engenty/document-scanner/schemas/shared.js +40 -0
- package/apps/runtime/dist/engenty/search-index/chunks.js +164 -0
- package/apps/runtime/dist/engenty/search-index/contracts.js +9 -0
- package/apps/runtime/dist/engenty/shims/connections-sdk.js +39 -0
- package/apps/runtime/dist/engenty/shims/plugin-sdk.js +1 -0
- package/apps/runtime/dist/engenty/shims/web-ingest.js +19 -0
- package/apps/runtime/dist/engenty/web-ingest/ssrf.js +277 -0
- package/apps/runtime/dist/engine/asks.js +82 -0
- package/apps/runtime/dist/engine/decide.js +82 -0
- package/apps/runtime/dist/engine/device.js +194 -0
- package/apps/runtime/dist/engine/events.js +128 -0
- package/apps/runtime/dist/engine/input.js +208 -0
- package/apps/runtime/dist/engine/owner.js +32 -0
- package/apps/runtime/dist/engine/prepare.js +90 -0
- package/apps/runtime/dist/engine/push.js +58 -0
- package/apps/runtime/dist/engine/requirements.js +355 -0
- package/apps/runtime/dist/engine/resources.js +198 -0
- package/apps/runtime/dist/engine/runner.js +970 -0
- package/apps/runtime/dist/engine/steps.js +1000 -0
- package/apps/runtime/dist/engine/surface.js +91 -0
- package/apps/runtime/dist/engine/template.js +240 -0
- package/apps/runtime/dist/engine/types.js +4 -0
- package/apps/runtime/dist/env.js +295 -0
- package/apps/runtime/dist/features.js +123 -0
- package/apps/runtime/dist/files/blobs.js +22 -0
- package/apps/runtime/dist/files/objects.js +86 -0
- package/apps/runtime/dist/files/storage.js +120 -0
- package/apps/runtime/dist/files/web-images.js +101 -0
- package/apps/runtime/dist/film/project.js +257 -0
- package/apps/runtime/dist/film/skills.js +91 -0
- package/apps/runtime/dist/film/sounds.js +122 -0
- package/apps/runtime/dist/film/step.js +289 -0
- package/apps/runtime/dist/film/tools.js +99 -0
- package/apps/runtime/dist/geocode.js +58 -0
- package/apps/runtime/dist/harness/antigravity.js +129 -0
- package/apps/runtime/dist/harness/apple.js +270 -0
- package/apps/runtime/dist/harness/claude.js +147 -0
- package/apps/runtime/dist/harness/codex.js +229 -0
- package/apps/runtime/dist/harness/cursor.js +93 -0
- package/apps/runtime/dist/harness/env.js +124 -0
- package/apps/runtime/dist/harness/index.js +95 -0
- package/apps/runtime/dist/harness/model.js +279 -0
- package/apps/runtime/dist/harness/prompt.js +121 -0
- package/apps/runtime/dist/harness/terminal.js +194 -0
- package/apps/runtime/dist/harness/types.js +7 -0
- package/apps/runtime/dist/index.js +113 -0
- package/apps/runtime/dist/limits.js +81 -0
- package/apps/runtime/dist/link-preview.js +60 -0
- package/apps/runtime/dist/log.js +106 -0
- package/apps/runtime/dist/manage.js +249 -0
- package/apps/runtime/dist/mcp/auth.js +161 -0
- package/apps/runtime/dist/mcp/bridge.js +59 -0
- package/apps/runtime/dist/mcp/flow-app.js +97 -0
- package/apps/runtime/dist/mcp/handler.js +54 -0
- package/apps/runtime/dist/mcp/scopes.js +14 -0
- package/apps/runtime/dist/mcp/seen.js +65 -0
- package/apps/runtime/dist/mcp/tools.js +572 -0
- package/apps/runtime/dist/media/conversation.js +167 -0
- package/apps/runtime/dist/media/ffmpeg.js +121 -0
- package/apps/runtime/dist/media/generate.js +120 -0
- package/apps/runtime/dist/media/marking.js +121 -0
- package/apps/runtime/dist/media/transcribe.js +198 -0
- package/apps/runtime/dist/model-errors.js +42 -0
- package/apps/runtime/dist/models.js +1164 -0
- package/apps/runtime/dist/plugin.js +61 -0
- package/apps/runtime/dist/plugins/channels.js +78 -0
- package/apps/runtime/dist/plugins/claims.js +56 -0
- package/apps/runtime/dist/plugins/discovery.js +155 -0
- package/apps/runtime/dist/plugins/events.js +63 -0
- package/apps/runtime/dist/plugins/jobs.js +64 -0
- package/apps/runtime/dist/plugins/loader.js +508 -0
- package/apps/runtime/dist/plugins/migrations.js +69 -0
- package/apps/runtime/dist/plugins/pro-keys.js +34 -0
- package/apps/runtime/dist/plugins/pro-state.js +83 -0
- package/apps/runtime/dist/plugins/pro.js +310 -0
- package/apps/runtime/dist/plugins/public.js +44 -0
- package/apps/runtime/dist/plugins/registry.js +122 -0
- package/apps/runtime/dist/plugins/runs.js +210 -0
- package/apps/runtime/dist/render/browser-path.js +54 -0
- package/apps/runtime/dist/render/chromium.js +98 -0
- package/apps/runtime/dist/render/convert.js +158 -0
- package/apps/runtime/dist/render/downloads.js +299 -0
- package/apps/runtime/dist/render/guard.js +25 -0
- package/apps/runtime/dist/render/shaped.js +137 -0
- package/apps/runtime/dist/routes/api.js +440 -0
- package/apps/runtime/dist/routes/debug.js +42 -0
- package/apps/runtime/dist/routes/delivery.js +90 -0
- package/apps/runtime/dist/routes/internal.js +81 -0
- package/apps/runtime/dist/routes/lang.js +25 -0
- package/apps/runtime/dist/routes/marketplace.js +43 -0
- package/apps/runtime/dist/routes/plugin-public.js +113 -0
- package/apps/runtime/dist/routes/plugins.js +212 -0
- package/apps/runtime/dist/routes/runs.js +1422 -0
- package/apps/runtime/dist/routes/studio.js +1629 -0
- package/apps/runtime/dist/routes/wizard-stream.js +30 -0
- package/apps/runtime/dist/running.js +39 -0
- package/apps/runtime/dist/sandbox/docker.js +75 -0
- package/apps/runtime/dist/sandbox/index.js +23 -0
- package/apps/runtime/dist/sandbox/types.js +1 -0
- package/apps/runtime/dist/secrets/crypto.js +28 -0
- package/apps/runtime/dist/secrets/signing.js +52 -0
- package/apps/runtime/dist/secrets/vault.js +59 -0
- package/apps/runtime/dist/services/access.js +88 -0
- package/apps/runtime/dist/services/api.js +248 -0
- package/apps/runtime/dist/services/architect.js +95 -0
- package/apps/runtime/dist/services/assistant-chats.js +311 -0
- package/apps/runtime/dist/services/brand.js +85 -0
- package/apps/runtime/dist/services/cloud.js +456 -0
- package/apps/runtime/dist/services/draft-events.js +27 -0
- package/apps/runtime/dist/services/engenty-shapes.js +71 -0
- package/apps/runtime/dist/services/errors.js +29 -0
- package/apps/runtime/dist/services/files.js +178 -0
- package/apps/runtime/dist/services/knowledge-model.js +559 -0
- package/apps/runtime/dist/services/knowledge.js +721 -0
- package/apps/runtime/dist/services/links.js +16 -0
- package/apps/runtime/dist/services/marketplace.js +492 -0
- package/apps/runtime/dist/services/onboarding.js +152 -0
- package/apps/runtime/dist/services/package-file.js +82 -0
- package/apps/runtime/dist/services/package.js +170 -0
- package/apps/runtime/dist/services/profile.js +51 -0
- package/apps/runtime/dist/services/project-files.js +439 -0
- package/apps/runtime/dist/services/project-index.js +707 -0
- package/apps/runtime/dist/services/projects.js +387 -0
- package/apps/runtime/dist/services/relevance.js +58 -0
- package/apps/runtime/dist/services/runners.js +178 -0
- package/apps/runtime/dist/services/runs.js +524 -0
- package/apps/runtime/dist/services/shares.js +130 -0
- package/apps/runtime/dist/services/site.js +270 -0
- package/apps/runtime/dist/services/space-categories.js +861 -0
- package/apps/runtime/dist/services/space-data.js +792 -0
- package/apps/runtime/dist/services/space-origin.js +207 -0
- package/apps/runtime/dist/services/spaces.js +656 -0
- package/apps/runtime/dist/services/translations.js +306 -0
- package/apps/runtime/dist/services/widgets.js +34 -0
- package/apps/runtime/dist/services/wizard-icon.js +157 -0
- package/apps/runtime/dist/services/wizards.js +420 -0
- package/apps/runtime/dist/settings.js +16 -0
- package/apps/runtime/dist/store/errors.js +4 -0
- package/apps/runtime/dist/store/index.js +299 -0
- package/apps/runtime/dist/store/shared-lists.js +123 -0
- package/apps/runtime/dist/tenants/control.js +238 -0
- package/apps/runtime/dist/tenants/tenant.js +26 -0
- package/apps/runtime/dist/texts.js +207 -0
- package/apps/runtime/dist/tools/browser.js +703 -0
- package/apps/runtime/dist/tools/connector.js +204 -0
- package/apps/runtime/dist/tools/index.js +291 -0
- package/apps/runtime/dist/tools/mail.js +104 -0
- package/apps/runtime/dist/tools/net-guard.js +170 -0
- package/apps/runtime/dist/tools/pages.js +81 -0
- package/apps/runtime/dist/tools/plugin.js +180 -0
- package/apps/runtime/dist/tools/project.js +154 -0
- package/apps/runtime/dist/tools/shared.js +69 -0
- package/apps/runtime/dist/tools/store.js +238 -0
- package/apps/runtime/dist/update.js +113 -0
- package/apps/runtime/dist/widgets/bundle.js +99 -0
- package/apps/runtime/dist/widgets/render.js +329 -0
- package/apps/runtime/dist/widgets/runtime.js +108 -0
- package/apps/runtime/dist/widgets/script.js +30 -0
- package/apps/runtime/dist/widgets/step.js +223 -0
- package/apps/web/dist/ai-labels/ai-black.svg +20 -0
- package/apps/web/dist/ai-labels/ai-generated-black.svg +31 -0
- package/apps/web/dist/ai-labels/ai-generated-white.svg +32 -0
- package/apps/web/dist/ai-labels/ai-modified-black.svg +30 -0
- package/apps/web/dist/ai-labels/ai-modified-white.svg +31 -0
- package/apps/web/dist/ai-labels/ai-white.svg +21 -0
- package/apps/web/dist/assets/AboutDialog-D4mvc-zu.js +1 -0
- package/apps/web/dist/assets/ChangelogDialog-DLqpJsF6.js +1 -0
- package/apps/web/dist/assets/ChatView-3laypRcE.js +10 -0
- package/apps/web/dist/assets/EditorPage-BalA5U2Y.js +3 -0
- package/apps/web/dist/assets/FlowPreview-CusP7ci9.js +1 -0
- package/apps/web/dist/assets/Harness-BrP-ENHg.css +1 -0
- package/apps/web/dist/assets/Harness-iYLtOLaK.js +16 -0
- package/apps/web/dist/assets/NewWizardChannels-CbwJ9A56.js +1 -0
- package/apps/web/dist/assets/NewWizardPage-BOBYiB9i.js +7 -0
- package/apps/web/dist/assets/OnboardingPage-CUwCiVIr.js +1 -0
- package/apps/web/dist/assets/ProjectPage-Bb16MdYG.js +1 -0
- package/apps/web/dist/assets/PublicRunner-BCt7Q6C-.js +1 -0
- package/apps/web/dist/assets/ReadOnly-kIHX2TEy.js +1 -0
- package/apps/web/dist/assets/RunDrawer-hZ9oeSXu.js +4 -0
- package/apps/web/dist/assets/RunnerView-DUS9fPmx.js +1 -0
- package/apps/web/dist/assets/SettingsPage-BzwR50fi.js +1 -0
- package/apps/web/dist/assets/SetupPage-gAp7rPxp.js +1 -0
- package/apps/web/dist/assets/SharePage-DqZWHva_.js +1 -0
- package/apps/web/dist/assets/changelog-DugmCNSf.js +1 -0
- package/apps/web/dist/assets/engenty-eXZ-EAwd.js +1 -0
- package/apps/web/dist/assets/esm-DXeMu8Lb.js +7 -0
- package/apps/web/dist/assets/fluffy-engenty-ClRou7yu.js +456 -0
- package/apps/web/dist/assets/index-AfUa8xKD.css +1 -0
- package/apps/web/dist/assets/index-C5Sm7tTR.js +14 -0
- package/apps/web/dist/assets/jsx-runtime-BNakU3Ej.js +1 -0
- package/apps/web/dist/assets/lock-R_CfU0Xo.js +1 -0
- package/apps/web/dist/assets/oss-credits-cZ9NUaLZ.js +1 -0
- package/apps/web/dist/assets/pdfmake-Cx9scTbD.js +228 -0
- package/apps/web/dist/assets/phone-CgrIS4_-.js +1 -0
- package/apps/web/dist/assets/preload-helper-CNEI7N0N.js +1 -0
- package/apps/web/dist/assets/qr-scanner-worker.min-CJaXo3yu.js +98 -0
- package/apps/web/dist/assets/qr-scanner.min-C_UI0gH-.js +1 -0
- package/apps/web/dist/assets/rolldown-runtime-hePW80VL.js +1 -0
- package/apps/web/dist/assets/settings-menu-2IMOOlLl.js +1 -0
- package/apps/web/dist/assets/ui-dzGvc3xk.js +137 -0
- package/apps/web/dist/assets/upload-BSuTilFF.js +1 -0
- package/apps/web/dist/assets/vfs_fonts-DllDSYt1.js +1 -0
- package/apps/web/dist/embed.js +266 -0
- package/apps/web/dist/favicon.svg +1 -0
- package/apps/web/dist/icons/apple-touch-icon.png +0 -0
- package/apps/web/dist/icons/icon-192.png +0 -0
- package/apps/web/dist/icons/icon-512.png +0 -0
- package/apps/web/dist/icons/icon-maskable-512.png +0 -0
- package/apps/web/dist/index.html +48 -0
- package/apps/web/dist/install.sh +577 -0
- package/apps/web/dist/manifest.webmanifest +21 -0
- package/apps/web/dist/mcp/flow.html +980 -0
- package/apps/web/dist/sw.js +15 -0
- package/bin/wizards.mjs +288 -0
- package/modules/README.md +15 -0
- package/node_modules/@engenty/wizards/dist/plugin/index.d.ts +1150 -0
- package/node_modules/@engenty/wizards/dist/plugin/index.js +4 -0
- package/node_modules/@engenty/wizards/dist/plugin/studio.d.ts +322 -0
- package/node_modules/@engenty/wizards/dist/plugin/studio.js +4 -0
- package/node_modules/@engenty/wizards/dist/plugin/thread.d.ts +174 -0
- package/node_modules/@engenty/wizards/dist/plugin/thread.js +821 -0
- package/node_modules/@engenty/wizards/package.json +11 -0
- package/node_modules/@engenty-wizards/shared/dist/assistant.d.ts +174 -0
- package/node_modules/@engenty-wizards/shared/dist/assistant.js +65 -0
- package/node_modules/@engenty-wizards/shared/dist/changes.d.ts +22 -0
- package/node_modules/@engenty-wizards/shared/dist/changes.js +61 -0
- package/node_modules/@engenty-wizards/shared/dist/connectors.d.ts +77 -0
- package/node_modules/@engenty-wizards/shared/dist/connectors.js +2 -0
- package/node_modules/@engenty-wizards/shared/dist/decision.d.ts +76 -0
- package/node_modules/@engenty-wizards/shared/dist/decision.js +147 -0
- package/node_modules/@engenty-wizards/shared/dist/definition.d.ts +2922 -0
- package/node_modules/@engenty-wizards/shared/dist/definition.js +1920 -0
- package/node_modules/@engenty-wizards/shared/dist/engenty/data-tables/columns-format.d.ts +5 -0
- package/node_modules/@engenty-wizards/shared/dist/engenty/data-tables/columns-format.js +132 -0
- package/node_modules/@engenty-wizards/shared/dist/engenty/data-tables/columns-value.d.ts +11 -0
- package/node_modules/@engenty-wizards/shared/dist/engenty/data-tables/columns-value.js +209 -0
- package/node_modules/@engenty-wizards/shared/dist/engenty/data-tables/columns.d.ts +532 -0
- package/node_modules/@engenty-wizards/shared/dist/engenty/data-tables/columns.js +217 -0
- package/node_modules/@engenty-wizards/shared/dist/engenty/data-tables/index.d.ts +3 -0
- package/node_modules/@engenty-wizards/shared/dist/engenty/data-tables/index.js +3 -0
- package/node_modules/@engenty-wizards/shared/dist/engenty/search-index/catalog-lexical.d.ts +14 -0
- package/node_modules/@engenty-wizards/shared/dist/engenty/search-index/catalog-lexical.js +166 -0
- package/node_modules/@engenty-wizards/shared/dist/engenty/search-index/catalog-rank.d.ts +33 -0
- package/node_modules/@engenty-wizards/shared/dist/engenty/search-index/catalog-rank.js +99 -0
- package/node_modules/@engenty-wizards/shared/dist/feature-flags.d.ts +78 -0
- package/node_modules/@engenty-wizards/shared/dist/feature-flags.js +70 -0
- package/node_modules/@engenty-wizards/shared/dist/features.d.ts +41 -0
- package/node_modules/@engenty-wizards/shared/dist/features.js +89 -0
- package/node_modules/@engenty-wizards/shared/dist/fit.d.ts +71 -0
- package/node_modules/@engenty-wizards/shared/dist/fit.js +382 -0
- package/node_modules/@engenty-wizards/shared/dist/json-schema.d.ts +14 -0
- package/node_modules/@engenty-wizards/shared/dist/json-schema.js +66 -0
- package/node_modules/@engenty-wizards/shared/dist/knowledge.d.ts +199 -0
- package/node_modules/@engenty-wizards/shared/dist/knowledge.js +68 -0
- package/node_modules/@engenty-wizards/shared/dist/marketplace-entry.d.ts +35 -0
- package/node_modules/@engenty-wizards/shared/dist/marketplace-entry.js +107 -0
- package/node_modules/@engenty-wizards/shared/dist/marketplace-search.d.ts +20 -0
- package/node_modules/@engenty-wizards/shared/dist/marketplace-search.js +136 -0
- package/node_modules/@engenty-wizards/shared/dist/marketplace.d.ts +227 -0
- package/node_modules/@engenty-wizards/shared/dist/marketplace.js +354 -0
- package/node_modules/@engenty-wizards/shared/dist/merge.d.ts +7 -0
- package/node_modules/@engenty-wizards/shared/dist/merge.js +40 -0
- package/node_modules/@engenty-wizards/shared/dist/numbers.d.ts +24 -0
- package/node_modules/@engenty-wizards/shared/dist/numbers.js +54 -0
- package/node_modules/@engenty-wizards/shared/dist/projects.d.ts +224 -0
- package/node_modules/@engenty-wizards/shared/dist/projects.js +319 -0
- package/node_modules/@engenty-wizards/shared/dist/providers.d.ts +101 -0
- package/node_modules/@engenty-wizards/shared/dist/providers.js +390 -0
- package/node_modules/@engenty-wizards/shared/dist/run.d.ts +680 -0
- package/node_modules/@engenty-wizards/shared/dist/run.js +620 -0
- package/node_modules/@engenty-wizards/shared/dist/runner-texts.d.ts +546 -0
- package/node_modules/@engenty-wizards/shared/dist/runner-texts.js +523 -0
- package/node_modules/@engenty-wizards/shared/dist/runners.d.ts +93 -0
- package/node_modules/@engenty-wizards/shared/dist/runners.js +146 -0
- package/node_modules/@engenty-wizards/shared/dist/shape.d.ts +102 -0
- package/node_modules/@engenty-wizards/shared/dist/shape.js +318 -0
- package/node_modules/@engenty-wizards/shared/dist/space-data.d.ts +89 -0
- package/node_modules/@engenty-wizards/shared/dist/space-data.js +47 -0
- package/node_modules/@engenty-wizards/shared/dist/store.d.ts +117 -0
- package/node_modules/@engenty-wizards/shared/dist/store.js +81 -0
- package/node_modules/@engenty-wizards/shared/dist/surface.d.ts +115 -0
- package/node_modules/@engenty-wizards/shared/dist/surface.js +442 -0
- package/node_modules/@engenty-wizards/shared/dist/texts.d.ts +45 -0
- package/node_modules/@engenty-wizards/shared/dist/texts.js +54 -0
- package/node_modules/@engenty-wizards/shared/dist/translation.d.ts +23 -0
- package/node_modules/@engenty-wizards/shared/dist/translation.js +154 -0
- package/node_modules/@engenty-wizards/shared/dist/wizard-package.d.ts +48 -0
- package/node_modules/@engenty-wizards/shared/dist/wizard-package.js +49 -0
- package/node_modules/@engenty-wizards/shared/dist/workspace.d.ts +16 -0
- package/node_modules/@engenty-wizards/shared/dist/workspace.js +51 -0
- package/node_modules/@engenty-wizards/shared/package.json +10 -0
- package/package.json +77 -4
- package/plugin/.claude-plugin/plugin.json +9 -0
- package/plugin/.mcp.json +8 -0
- package/plugin/commands/wizard.md +8 -0
- package/plugin/skills/build-wizard/SKILL.md +27 -0
- package/plugin/skills/use-wizard/SKILL.md +18 -0
|
@@ -0,0 +1,1422 @@
|
|
|
1
|
+
import { FORMATS, isLang, LANG_CODE, LANGS, LIST_FORMATS, TEXT_CLASSES, wizardLang, } from "@engenty-wizards/shared/definition";
|
|
2
|
+
import { TableColumnValueError } from "@engenty-wizards/shared/engenty/data-tables";
|
|
3
|
+
import { noteText } from "@engenty-wizards/shared/run";
|
|
4
|
+
import { machineTranslated, translateDefinition, } from "@engenty-wizards/shared/translation";
|
|
5
|
+
import { getConnInfo } from "@hono/node-server/conninfo";
|
|
6
|
+
import { and, eq } from "drizzle-orm";
|
|
7
|
+
import { Hono } from "hono";
|
|
8
|
+
import { getCookie, setCookie } from "hono/cookie";
|
|
9
|
+
import { streamSSE } from "hono/streaming";
|
|
10
|
+
import { nanoid } from "nanoid";
|
|
11
|
+
import { z } from "zod";
|
|
12
|
+
import { principalOf } from "../auth/index.js";
|
|
13
|
+
import { MARK_FOCUSED_PERSONAL } from "../browser/snapshot.js";
|
|
14
|
+
import { ConnectError, connectionViews, connectWithCredentials, disconnect, finishOAuth, oauthStateLang, oauthStateRun, startOAuth, } from "../connectors/index.js";
|
|
15
|
+
import { finishPluginOAuth, pluginStateTenant } from "../connectors/plugin-connections.js";
|
|
16
|
+
import { db, schema, withTenant } from "../db/client.js";
|
|
17
|
+
import { answerAsk, pendingAsk } from "../engine/asks.js";
|
|
18
|
+
import { answerDevice, offerDevice, openDeviceCalls } from "../engine/device.js";
|
|
19
|
+
import { emitEvent, signalChanged, watchRun } from "../engine/events.js";
|
|
20
|
+
import { fieldErrorsIn } from "../engine/input.js";
|
|
21
|
+
import { fromOwner } from "../engine/owner.js";
|
|
22
|
+
import { pushAvailable, setPushDevice } from "../engine/push.js";
|
|
23
|
+
import { liveResources } from "../engine/resources.js";
|
|
24
|
+
import { cancel, createRun, editOutput, goBack, NoCreditsError, projectIdOf, RunConflict, RunInputError, retry, reviewStep, runView, submitPage, talkAllowed, } from "../engine/runner.js";
|
|
25
|
+
import { basePath, env } from "../env.js";
|
|
26
|
+
import { loadAsset, saveAsset } from "../files/storage.js";
|
|
27
|
+
import { reverseGeocode } from "../geocode.js";
|
|
28
|
+
import { allowed, hashIp, verifyTurnstile, wizardUnavailable } from "../limits.js";
|
|
29
|
+
import { mintConversation } from "../media/conversation.js";
|
|
30
|
+
import { transcribeAudio } from "../media/transcribe.js";
|
|
31
|
+
import { errorText, ModelUnavailableError, noteOf } from "../model-errors.js";
|
|
32
|
+
import { listDownload, renderDownload, stepHtml } from "../render/downloads.js";
|
|
33
|
+
import { HTML_RESPONSE_CSP } from "../render/guard.js";
|
|
34
|
+
import { runTicketValid, verifySignedUrl } from "../secrets/signing.js";
|
|
35
|
+
import { brandView } from "../services/brand.js";
|
|
36
|
+
import { ServiceError } from "../services/errors.js";
|
|
37
|
+
import { publicRunners, servePageRunner } from "../services/runners.js";
|
|
38
|
+
import { sharedRun, shareImage, shareRun, shareView, unshareRun } from "../services/shares.js";
|
|
39
|
+
import { isLangCode, storedTranslation, translateResult, translationFor, uiTexts, } from "../services/translations.js";
|
|
40
|
+
import { isIconFile, wizardAvatarSvg, wizardIcon } from "../services/wizard-icon.js";
|
|
41
|
+
import { clearStore, deleteRows, listRows, listSecrets, readStoreFile, StoreError, saveRows, scopeOf, storeFiles, updateRow, } from "../store/index.js";
|
|
42
|
+
import { tenantOfLink, tenantOfRun, tenantStatus, tokenOfCode, visitorOverLimit, } from "../tenants/control.js";
|
|
43
|
+
import { bareMime, byteRange, inlineSafe, uploadLimit, uploadMime, wizardManifest, } from "./delivery.js";
|
|
44
|
+
import { notFoundAnswer, requestLang, say } from "./lang.js";
|
|
45
|
+
const VISITOR_COOKIE = "wz_vid";
|
|
46
|
+
/** Kept files a review shows inline. */
|
|
47
|
+
const INLINE_TYPES = new Set(["application/pdf", "image/png", "image/jpeg", "image/webp"]);
|
|
48
|
+
/** Assets whose bytes and type someone outside the wizard chose: uploads and collected files. */
|
|
49
|
+
const FOREIGN_KINDS = new Set(["upload", "upload-image", "file"]);
|
|
50
|
+
export function clientIp(c) {
|
|
51
|
+
const fwd = c.req.header("x-forwarded-for")?.split(",")[0]?.trim();
|
|
52
|
+
if (fwd) {
|
|
53
|
+
return fwd;
|
|
54
|
+
}
|
|
55
|
+
try {
|
|
56
|
+
return getConnInfo(c).remote.address ?? null;
|
|
57
|
+
}
|
|
58
|
+
catch {
|
|
59
|
+
return null;
|
|
60
|
+
}
|
|
61
|
+
}
|
|
62
|
+
/** A refusal the visitor reads, in the language their request asks for. */
|
|
63
|
+
function refuse(c, note, status) {
|
|
64
|
+
return c.json({ error: noteText(note, requestLang(c)) }, status);
|
|
65
|
+
}
|
|
66
|
+
/**
|
|
67
|
+
* The mobile app's own requests (a run's view, downloads) name the visitor in a header: iOS
|
|
68
|
+
* merges a Cookie header the app sets with its own cookie store (`wz_vid=a,wz_vid=a`). The header
|
|
69
|
+
* comes first; a browser never sends it across sites without the runtime's say (CORS), so it is
|
|
70
|
+
* as good as the cookie.
|
|
71
|
+
*/
|
|
72
|
+
const VISITOR_HEADER = "x-wizards-visitor";
|
|
73
|
+
/** An http(s) address without credentials or fragment, or null for anything else. */
|
|
74
|
+
function webAddress(raw) {
|
|
75
|
+
if (!raw) {
|
|
76
|
+
return null;
|
|
77
|
+
}
|
|
78
|
+
try {
|
|
79
|
+
const url = new URL(raw);
|
|
80
|
+
if (!(url.protocol === "http:" || url.protocol === "https:") || url.username || url.password) {
|
|
81
|
+
return null;
|
|
82
|
+
}
|
|
83
|
+
url.hash = "";
|
|
84
|
+
return url.toString();
|
|
85
|
+
}
|
|
86
|
+
catch {
|
|
87
|
+
return null;
|
|
88
|
+
}
|
|
89
|
+
}
|
|
90
|
+
function visitorId(c, create, embedded = false) {
|
|
91
|
+
let vid = c.req.header(VISITOR_HEADER) || getCookie(c, VISITOR_COOKIE) || null;
|
|
92
|
+
if (!vid && create) {
|
|
93
|
+
vid = nanoid(24);
|
|
94
|
+
const secure = env.appUrl.startsWith("https");
|
|
95
|
+
setCookie(c, VISITOR_COOKIE, vid, {
|
|
96
|
+
httpOnly: true,
|
|
97
|
+
path: basePath || "/",
|
|
98
|
+
maxAge: 60 * 60 * 24 * 365,
|
|
99
|
+
// In a frame on another website the wizard is a third party: a browser keeps its cookie
|
|
100
|
+
// only when the cookie says so, and then apart for each website (CHIPS).
|
|
101
|
+
...(embedded && secure
|
|
102
|
+
? { sameSite: "None", secure: true, partitioned: true }
|
|
103
|
+
: { sameSite: "Lax", secure }),
|
|
104
|
+
});
|
|
105
|
+
}
|
|
106
|
+
return vid;
|
|
107
|
+
}
|
|
108
|
+
/**
|
|
109
|
+
* The run, if the caller may see it: its visitor, the admin who owns the wizard, the widget of
|
|
110
|
+
* the owner's AI app with its run ticket, or — on the read-only file routes — a signed link
|
|
111
|
+
* handed out to the owner's MCP client.
|
|
112
|
+
*/
|
|
113
|
+
/** A view's kept state stays small: ticks and choices, not data. */
|
|
114
|
+
const MAX_VIEW_STATE = 32_000;
|
|
115
|
+
async function accessibleRun(c, runId, opts = {}) {
|
|
116
|
+
const run = await db.query.run.findFirst({ where: eq(schema.run.id, runId) });
|
|
117
|
+
if (!run) {
|
|
118
|
+
return null;
|
|
119
|
+
}
|
|
120
|
+
if (opts.signed && verifySignedUrl(c.req.url)) {
|
|
121
|
+
return run;
|
|
122
|
+
}
|
|
123
|
+
if (runTicketValid(run.id, c.req.query("rt"))) {
|
|
124
|
+
return run;
|
|
125
|
+
}
|
|
126
|
+
const vid = visitorId(c, false);
|
|
127
|
+
if (vid && run.visitorId === vid) {
|
|
128
|
+
return run;
|
|
129
|
+
}
|
|
130
|
+
const user = await principalOf(c);
|
|
131
|
+
if (user && user.tenantId === run.tenantId) {
|
|
132
|
+
return run;
|
|
133
|
+
}
|
|
134
|
+
return null;
|
|
135
|
+
}
|
|
136
|
+
async function viewOf(run) {
|
|
137
|
+
const w = await db.query.wizard.findFirst({ where: eq(schema.wizard.id, run.wizardId) });
|
|
138
|
+
return runView(run, await brandView(w?.projectId ?? ""));
|
|
139
|
+
}
|
|
140
|
+
function commandError(c, err) {
|
|
141
|
+
if (err instanceof RunInputError) {
|
|
142
|
+
return c.json({ error: errorText(err, requestLang(c)), fields: fieldErrorsIn(err.errors, requestLang(c)) }, 422);
|
|
143
|
+
}
|
|
144
|
+
if (err instanceof RunConflict) {
|
|
145
|
+
return c.json({ error: errorText(err, requestLang(c)) }, 409);
|
|
146
|
+
}
|
|
147
|
+
throw err;
|
|
148
|
+
}
|
|
149
|
+
/**
|
|
150
|
+
* These routes arrive without a session. The tenant comes from the public token or the run id
|
|
151
|
+
* in the path — looked up in the control database, never taken from the request.
|
|
152
|
+
*/
|
|
153
|
+
function tenantFrom(find) {
|
|
154
|
+
return async (c, next) => {
|
|
155
|
+
const tenant = await find(c);
|
|
156
|
+
if (!tenant) {
|
|
157
|
+
return c.json(notFoundAnswer(c), 404);
|
|
158
|
+
}
|
|
159
|
+
if ((await tenantStatus(tenant)) === "suspended") {
|
|
160
|
+
return refuse(c, { code: "wizardUnavailable" }, 403);
|
|
161
|
+
}
|
|
162
|
+
return withTenant(tenant, next);
|
|
163
|
+
};
|
|
164
|
+
}
|
|
165
|
+
const wizardLink = tenantFrom((c) => tenantOfLink(c.req.param("token") ?? "", "wizard"));
|
|
166
|
+
const resultLink = tenantFrom((c) => tenantOfLink(c.req.param("token") ?? "", "result"));
|
|
167
|
+
const logoLink = tenantFrom((c) => tenantOfLink(c.req.param("id") ?? "", "logo"));
|
|
168
|
+
const runTenant = tenantFrom((c) => tenantOfRun(c.req.param("id") ?? ""));
|
|
169
|
+
/** Wrong guesses at a wizard's ID, per address and hour: 32^8 IDs stay out of reach. */
|
|
170
|
+
const codeMisses = new Map();
|
|
171
|
+
const CODE_MISSES_PER_HOUR = 30;
|
|
172
|
+
function codeLookupBlocked(ip) {
|
|
173
|
+
const key = hashIp(ip) ?? "unknown";
|
|
174
|
+
const entry = codeMisses.get(key);
|
|
175
|
+
if (entry && Date.now() - entry.since > 3600_000) {
|
|
176
|
+
codeMisses.delete(key);
|
|
177
|
+
return false;
|
|
178
|
+
}
|
|
179
|
+
return (entry?.n ?? 0) >= CODE_MISSES_PER_HOUR;
|
|
180
|
+
}
|
|
181
|
+
function codeMissed(ip) {
|
|
182
|
+
const key = hashIp(ip) ?? "unknown";
|
|
183
|
+
const entry = codeMisses.get(key) ?? { n: 0, since: Date.now() };
|
|
184
|
+
entry.n += 1;
|
|
185
|
+
codeMisses.set(key, entry);
|
|
186
|
+
if (codeMisses.size > 10_000) {
|
|
187
|
+
codeMisses.delete(codeMisses.keys().next().value);
|
|
188
|
+
}
|
|
189
|
+
}
|
|
190
|
+
/** A plugin's page runner behind a wizard's address, or 404 where the wizard does not offer it. */
|
|
191
|
+
async function pageRunner(c, rest) {
|
|
192
|
+
const w = await db.query.wizard.findFirst({
|
|
193
|
+
where: eq(schema.wizard.shareToken, c.req.param("token") ?? ""),
|
|
194
|
+
});
|
|
195
|
+
if (!w || w.publishedVersion === null) {
|
|
196
|
+
return c.json(notFoundAnswer(c), 404);
|
|
197
|
+
}
|
|
198
|
+
const version = await db.query.wizardVersion.findFirst({
|
|
199
|
+
where: and(eq(schema.wizardVersion.wizardId, w.id), eq(schema.wizardVersion.version, w.publishedVersion)),
|
|
200
|
+
});
|
|
201
|
+
const served = version
|
|
202
|
+
? await servePageRunner(w, version.definition, c.req.param("runner") ?? "", rest, c.req.raw)
|
|
203
|
+
: null;
|
|
204
|
+
return served ?? c.json(notFoundAnswer(c), 404);
|
|
205
|
+
}
|
|
206
|
+
/** A wizard's link with its published version, or null where it has none. */
|
|
207
|
+
async function publishedWizard(token) {
|
|
208
|
+
const w = await db.query.wizard.findFirst({
|
|
209
|
+
where: eq(schema.wizard.shareToken, token ?? ""),
|
|
210
|
+
});
|
|
211
|
+
if (!w || w.publishedVersion === null) {
|
|
212
|
+
return null;
|
|
213
|
+
}
|
|
214
|
+
const version = await db.query.wizardVersion.findFirst({
|
|
215
|
+
where: and(eq(schema.wizardVersion.wizardId, w.id), eq(schema.wizardVersion.version, w.publishedVersion)),
|
|
216
|
+
});
|
|
217
|
+
return version ? { w, def: version.definition, version: w.publishedVersion } : null;
|
|
218
|
+
}
|
|
219
|
+
/** The author offers the wizard translated, and the language is not the one it is written in. */
|
|
220
|
+
function wantsTranslation(w, def, lang) {
|
|
221
|
+
return w.translate && lang !== wizardLang(def);
|
|
222
|
+
}
|
|
223
|
+
/** What a wizard's page shows before a run: in the viewer's language where `as` is given. */
|
|
224
|
+
async function publicBody(c, w, original, as) {
|
|
225
|
+
const translated = as ? translateDefinition(original, as.memory) : null;
|
|
226
|
+
const def = translated?.def ?? original;
|
|
227
|
+
const reason = await wizardUnavailable(w);
|
|
228
|
+
return {
|
|
229
|
+
token: w.shareToken,
|
|
230
|
+
title: def.title,
|
|
231
|
+
description: def.description,
|
|
232
|
+
avatar: def.avatar,
|
|
233
|
+
intro: def.intro ?? null,
|
|
234
|
+
brand: await brandView(w.projectId),
|
|
235
|
+
turnstileSiteKey: env.turnstile.siteKey || null,
|
|
236
|
+
available: !reason,
|
|
237
|
+
unavailableReason: reason && noteText(reason, requestLang(c)),
|
|
238
|
+
runners: await publicRunners(w, original),
|
|
239
|
+
lang: wizardLang(original),
|
|
240
|
+
translatable: w.translate,
|
|
241
|
+
translation: as && translated
|
|
242
|
+
? {
|
|
243
|
+
lang: as.lang,
|
|
244
|
+
labels: translated.labels,
|
|
245
|
+
machine: machineTranslated(as.memory, original),
|
|
246
|
+
}
|
|
247
|
+
: null,
|
|
248
|
+
};
|
|
249
|
+
}
|
|
250
|
+
export const publicRoutes = new Hono()
|
|
251
|
+
// A wizard's ID, typed into the mobile app: answers its share token.
|
|
252
|
+
.get("/codes/:code", async (c) => {
|
|
253
|
+
const ip = clientIp(c);
|
|
254
|
+
if (codeLookupBlocked(ip)) {
|
|
255
|
+
return c.json({
|
|
256
|
+
error: say(c, "tooManyTriesWait"),
|
|
257
|
+
code: "too_many_tries",
|
|
258
|
+
}, 429);
|
|
259
|
+
}
|
|
260
|
+
const found = await tokenOfCode(c.req.param("code"));
|
|
261
|
+
if (!found || (await tenantStatus(found.tenantId)) === "suspended") {
|
|
262
|
+
codeMissed(ip);
|
|
263
|
+
return c.json(notFoundAnswer(c), 404);
|
|
264
|
+
}
|
|
265
|
+
return c.json({ token: found.token, url: `${env.publicUrl}/w/${found.token}` });
|
|
266
|
+
})
|
|
267
|
+
.use("/wizards/:token", wizardLink)
|
|
268
|
+
.use("/wizards/:token/*", wizardLink)
|
|
269
|
+
.use("/logos/:id", logoLink)
|
|
270
|
+
.get("/wizards/:token", async (c) => {
|
|
271
|
+
const found = await publishedWizard(c.req.param("token"));
|
|
272
|
+
if (!found) {
|
|
273
|
+
return c.json(notFoundAnswer(c), 404);
|
|
274
|
+
}
|
|
275
|
+
// The page in the viewer's language where it is kept; making it is the POST below.
|
|
276
|
+
const lang = c.req.query("lang");
|
|
277
|
+
const memory = isLangCode(lang) && wantsTranslation(found.w, found.def, lang)
|
|
278
|
+
? await storedTranslation(found.w.id, lang)
|
|
279
|
+
: null;
|
|
280
|
+
return c.json(await publicBody(c, found.w, found.def, lang && memory ? { lang, memory } : null));
|
|
281
|
+
})
|
|
282
|
+
// The wizard in the viewer's language: translated now where it is not kept yet.
|
|
283
|
+
.post("/wizards/:token/translation", async (c) => {
|
|
284
|
+
const found = await publishedWizard(c.req.param("token"));
|
|
285
|
+
if (!found) {
|
|
286
|
+
return c.json(notFoundAnswer(c), 404);
|
|
287
|
+
}
|
|
288
|
+
const { lang } = z
|
|
289
|
+
.object({ lang: z.string().regex(LANG_CODE) })
|
|
290
|
+
.parse(await c.req.json().catch(() => ({})));
|
|
291
|
+
if (!wantsTranslation(found.w, found.def, lang)) {
|
|
292
|
+
return refuse(c, { code: "translationOff" }, 403);
|
|
293
|
+
}
|
|
294
|
+
try {
|
|
295
|
+
const memory = await translationFor(found.w.id, found.def, lang, found.version);
|
|
296
|
+
return c.json(await publicBody(c, found.w, found.def, { lang, memory }));
|
|
297
|
+
}
|
|
298
|
+
catch (err) {
|
|
299
|
+
if (err instanceof ServiceError || err instanceof ModelUnavailableError) {
|
|
300
|
+
return refuse(c, { code: "translationFailed" }, 502);
|
|
301
|
+
}
|
|
302
|
+
throw err;
|
|
303
|
+
}
|
|
304
|
+
})
|
|
305
|
+
// The runner's own words in a language outside LANGS (the wizard's own or a viewer's), booked
|
|
306
|
+
// to this wizard's owner when new.
|
|
307
|
+
.get("/wizards/:token/texts/:lang", async (c) => {
|
|
308
|
+
const found = await publishedWizard(c.req.param("token"));
|
|
309
|
+
const lang = c.req.param("lang");
|
|
310
|
+
if (!found || !isLangCode(lang) || isLang(lang)) {
|
|
311
|
+
return c.json(notFoundAnswer(c), 404);
|
|
312
|
+
}
|
|
313
|
+
// The wizard's own language needs the runner's words too, translation offered or not.
|
|
314
|
+
if (lang !== wizardLang(found.def) && !wantsTranslation(found.w, found.def, lang)) {
|
|
315
|
+
return refuse(c, { code: "translationOff" }, 403);
|
|
316
|
+
}
|
|
317
|
+
try {
|
|
318
|
+
return c.json(await uiTexts(lang));
|
|
319
|
+
}
|
|
320
|
+
catch (err) {
|
|
321
|
+
if (err instanceof ServiceError || err instanceof ModelUnavailableError) {
|
|
322
|
+
return refuse(c, { code: "translationFailed" }, 502);
|
|
323
|
+
}
|
|
324
|
+
throw err;
|
|
325
|
+
}
|
|
326
|
+
})
|
|
327
|
+
.post("/wizards/:token/runs", async (c) => {
|
|
328
|
+
const w = await db.query.wizard.findFirst({
|
|
329
|
+
where: eq(schema.wizard.shareToken, c.req.param("token")),
|
|
330
|
+
});
|
|
331
|
+
if (!w || w.publishedVersion === null) {
|
|
332
|
+
return c.json(notFoundAnswer(c), 404);
|
|
333
|
+
}
|
|
334
|
+
const reason = await wizardUnavailable(w);
|
|
335
|
+
if (reason) {
|
|
336
|
+
return refuse(c, reason, 403);
|
|
337
|
+
}
|
|
338
|
+
const body = z
|
|
339
|
+
.object({
|
|
340
|
+
turnstileToken: z.string().optional(),
|
|
341
|
+
embedded: z.boolean().optional(),
|
|
342
|
+
// The website page that embeds the wizard: shown with the result.
|
|
343
|
+
origin: z.string().max(2000).optional(),
|
|
344
|
+
// The language the viewer reads the wizard in, translated on request.
|
|
345
|
+
lang: z.string().regex(LANG_CODE).optional(),
|
|
346
|
+
})
|
|
347
|
+
.parse(await c.req.json().catch(() => ({})));
|
|
348
|
+
const ip = clientIp(c);
|
|
349
|
+
if (!(await verifyTurnstile(body.turnstileToken, ip))) {
|
|
350
|
+
return refuse(c, { code: "confirmHuman" }, 403);
|
|
351
|
+
}
|
|
352
|
+
const vid = visitorId(c, true, body.embedded);
|
|
353
|
+
const ipHash = hashIp(ip);
|
|
354
|
+
if (await visitorOverLimit(vid, ipHash)) {
|
|
355
|
+
return refuse(c, { code: "tooManyRuns" }, 429);
|
|
356
|
+
}
|
|
357
|
+
const version = await db.query.wizardVersion.findFirst({
|
|
358
|
+
where: and(eq(schema.wizardVersion.wizardId, w.id), eq(schema.wizardVersion.version, w.publishedVersion)),
|
|
359
|
+
});
|
|
360
|
+
try {
|
|
361
|
+
const runId = await createRun({
|
|
362
|
+
wizardId: w.id,
|
|
363
|
+
definition: version.definition,
|
|
364
|
+
files: version.files,
|
|
365
|
+
version: w.publishedVersion,
|
|
366
|
+
mode: "live",
|
|
367
|
+
visitorId: vid,
|
|
368
|
+
ipHash,
|
|
369
|
+
origin: body.embedded ? webAddress(body.origin) : null,
|
|
370
|
+
lang: body.lang &&
|
|
371
|
+
wantsTranslation(w, version.definition, body.lang) &&
|
|
372
|
+
(await storedTranslation(w.id, body.lang))
|
|
373
|
+
? body.lang
|
|
374
|
+
: null,
|
|
375
|
+
});
|
|
376
|
+
return c.json({ runId });
|
|
377
|
+
}
|
|
378
|
+
catch (err) {
|
|
379
|
+
if (err instanceof NoCreditsError || err instanceof ModelUnavailableError) {
|
|
380
|
+
return refuse(c, { code: "wizardUnavailable" }, 403);
|
|
381
|
+
}
|
|
382
|
+
throw err;
|
|
383
|
+
}
|
|
384
|
+
})
|
|
385
|
+
// A plugin's page runner for this wizard: what `/w/<token>/<runner>` serves built, reached here from source.
|
|
386
|
+
.all("/wizards/:token/runners/:runner", (c) => pageRunner(c, ""))
|
|
387
|
+
.all("/wizards/:token/runners/:runner/*", (c) => pageRunner(c, c.req.path.slice(c.req.path.indexOf(`/runners/${c.req.param("runner")}`) +
|
|
388
|
+
`/runners/${c.req.param("runner")}`.length)))
|
|
389
|
+
// A published wizard as an app of its own: added to a phone's home screen it opens on its link.
|
|
390
|
+
.get("/wizards/:token/manifest.webmanifest", async (c) => {
|
|
391
|
+
const w = await db.query.wizard.findFirst({
|
|
392
|
+
where: eq(schema.wizard.shareToken, c.req.param("token")),
|
|
393
|
+
});
|
|
394
|
+
if (!w || w.publishedVersion === null) {
|
|
395
|
+
return c.notFound();
|
|
396
|
+
}
|
|
397
|
+
const version = await db.query.wizardVersion.findFirst({
|
|
398
|
+
where: and(eq(schema.wizardVersion.wizardId, w.id), eq(schema.wizardVersion.version, w.publishedVersion)),
|
|
399
|
+
});
|
|
400
|
+
const def = version.definition;
|
|
401
|
+
const manifest = wizardManifest(w.shareToken, def.title, def.description, w.publishedVersion);
|
|
402
|
+
return c.body(JSON.stringify(manifest), 200, {
|
|
403
|
+
"content-type": "application/manifest+json; charset=utf-8",
|
|
404
|
+
"cache-control": "public, max-age=300",
|
|
405
|
+
});
|
|
406
|
+
})
|
|
407
|
+
// Its engenty as the chat button a website shows it behind (`embed.js`).
|
|
408
|
+
.get("/wizards/:token/avatar.svg", async (c) => {
|
|
409
|
+
const w = await db.query.wizard.findFirst({
|
|
410
|
+
where: eq(schema.wizard.shareToken, c.req.param("token")),
|
|
411
|
+
});
|
|
412
|
+
if (!w || w.publishedVersion === null) {
|
|
413
|
+
return c.notFound();
|
|
414
|
+
}
|
|
415
|
+
const version = await db.query.wizardVersion.findFirst({
|
|
416
|
+
where: and(eq(schema.wizardVersion.wizardId, w.id), eq(schema.wizardVersion.version, w.publishedVersion)),
|
|
417
|
+
});
|
|
418
|
+
return c.body(wizardAvatarSvg(version.definition.avatar), 200, {
|
|
419
|
+
"content-type": "image/svg+xml; charset=utf-8",
|
|
420
|
+
"cache-control": "public, max-age=3600",
|
|
421
|
+
});
|
|
422
|
+
})
|
|
423
|
+
// Its icon: the wizard's engenty on its hue with its name; the studio's where none can be drawn.
|
|
424
|
+
.get("/wizards/:token/icons/:file", async (c) => {
|
|
425
|
+
const file = c.req.param("file");
|
|
426
|
+
if (!isIconFile(file)) {
|
|
427
|
+
return c.notFound();
|
|
428
|
+
}
|
|
429
|
+
const w = await db.query.wizard.findFirst({
|
|
430
|
+
where: eq(schema.wizard.shareToken, c.req.param("token")),
|
|
431
|
+
});
|
|
432
|
+
if (!w || w.publishedVersion === null) {
|
|
433
|
+
return c.notFound();
|
|
434
|
+
}
|
|
435
|
+
const version = await db.query.wizardVersion.findFirst({
|
|
436
|
+
where: and(eq(schema.wizardVersion.wizardId, w.id), eq(schema.wizardVersion.version, w.publishedVersion)),
|
|
437
|
+
});
|
|
438
|
+
const def = version.definition;
|
|
439
|
+
const png = await wizardIcon(file, def.avatar, def.title);
|
|
440
|
+
if (!png) {
|
|
441
|
+
return c.redirect(`${basePath}/studio/${file === "favicon.png" ? "favicon.svg" : `icons/${file}`}`);
|
|
442
|
+
}
|
|
443
|
+
return c.body(png, 200, {
|
|
444
|
+
"content-type": "image/png",
|
|
445
|
+
"cache-control": "public, max-age=3600",
|
|
446
|
+
});
|
|
447
|
+
})
|
|
448
|
+
.get("/logos/:id", async (c) => {
|
|
449
|
+
const found = await loadAsset(c.req.param("id"));
|
|
450
|
+
if (found?.row.kind !== "logo") {
|
|
451
|
+
return c.notFound();
|
|
452
|
+
}
|
|
453
|
+
return c.body(new Uint8Array(found.data), 200, {
|
|
454
|
+
"content-type": found.row.mime,
|
|
455
|
+
"cache-control": "public, max-age=86400",
|
|
456
|
+
// An SVG logo is shown, never run.
|
|
457
|
+
"content-security-policy": "sandbox",
|
|
458
|
+
});
|
|
459
|
+
});
|
|
460
|
+
export const runRoutes = new Hono()
|
|
461
|
+
.use("/:id", runTenant)
|
|
462
|
+
.use("/:id/*", runTenant)
|
|
463
|
+
.get("/:id", async (c) => {
|
|
464
|
+
const run = await accessibleRun(c, c.req.param("id"));
|
|
465
|
+
if (!run) {
|
|
466
|
+
return c.json(notFoundAnswer(c), 404);
|
|
467
|
+
}
|
|
468
|
+
return c.json(await viewOf(run));
|
|
469
|
+
})
|
|
470
|
+
.get("/:id/stream", async (c) => {
|
|
471
|
+
const id = c.req.param("id");
|
|
472
|
+
const first = await accessibleRun(c, id);
|
|
473
|
+
if (!first) {
|
|
474
|
+
return c.json(notFoundAnswer(c), 404);
|
|
475
|
+
}
|
|
476
|
+
return streamSSE(c, async (stream) => {
|
|
477
|
+
let pending = false;
|
|
478
|
+
let closed = false;
|
|
479
|
+
const push = async () => {
|
|
480
|
+
if (pending || closed) {
|
|
481
|
+
return;
|
|
482
|
+
}
|
|
483
|
+
pending = true;
|
|
484
|
+
await new Promise((r) => setTimeout(r, 120));
|
|
485
|
+
pending = false;
|
|
486
|
+
const run = await db.query.run.findFirst({ where: eq(schema.run.id, id) });
|
|
487
|
+
if (run && !closed) {
|
|
488
|
+
await stream.writeSSE({ event: "view", data: JSON.stringify(await viewOf(run)) });
|
|
489
|
+
}
|
|
490
|
+
};
|
|
491
|
+
// Calls the runner put to the device the run is watched from, each sent once per stream.
|
|
492
|
+
const sent = new Set();
|
|
493
|
+
const sendDeviceCalls = async () => {
|
|
494
|
+
for (const call of await openDeviceCalls(id)) {
|
|
495
|
+
if (!(closed || sent.has(call.id))) {
|
|
496
|
+
sent.add(call.id);
|
|
497
|
+
await stream.writeSSE({ event: "device", data: JSON.stringify(call) });
|
|
498
|
+
}
|
|
499
|
+
}
|
|
500
|
+
};
|
|
501
|
+
const unsubscribe = watchRun(id, (signal) => {
|
|
502
|
+
if (!signal.event) {
|
|
503
|
+
void sendDeviceCalls().catch(() => undefined);
|
|
504
|
+
}
|
|
505
|
+
void push();
|
|
506
|
+
});
|
|
507
|
+
stream.onAbort(() => {
|
|
508
|
+
closed = true;
|
|
509
|
+
unsubscribe();
|
|
510
|
+
});
|
|
511
|
+
await stream.writeSSE({ event: "view", data: JSON.stringify(await viewOf(first)) });
|
|
512
|
+
await sendDeviceCalls();
|
|
513
|
+
while (!closed) {
|
|
514
|
+
await stream.sleep(20_000);
|
|
515
|
+
if (!closed) {
|
|
516
|
+
await stream.writeSSE({ event: "ping", data: "" });
|
|
517
|
+
}
|
|
518
|
+
}
|
|
519
|
+
});
|
|
520
|
+
})
|
|
521
|
+
// The runner says what the device around it offers the run (the app's Apple Intelligence):
|
|
522
|
+
// from then on short text calls go there first. Renewed on every connect; see engine/device.ts.
|
|
523
|
+
.post("/:id/device", async (c) => {
|
|
524
|
+
const run = await accessibleRun(c, c.req.param("id"));
|
|
525
|
+
if (!run) {
|
|
526
|
+
return c.json(notFoundAnswer(c), 404);
|
|
527
|
+
}
|
|
528
|
+
const offer = z
|
|
529
|
+
.object({
|
|
530
|
+
think: z
|
|
531
|
+
.object({
|
|
532
|
+
classes: z.array(z.enum(TEXT_CLASSES)).max(TEXT_CLASSES.length),
|
|
533
|
+
maxChars: z.number().int().min(500).max(50_000),
|
|
534
|
+
})
|
|
535
|
+
.optional(),
|
|
536
|
+
})
|
|
537
|
+
.parse(await c.req.json().catch(() => ({})));
|
|
538
|
+
await offerDevice(run.id, offer);
|
|
539
|
+
return c.json({ ok: true });
|
|
540
|
+
})
|
|
541
|
+
// The device's answer to one call the stream carried as a `device` event.
|
|
542
|
+
.post("/:id/device/:reqId", async (c) => {
|
|
543
|
+
const run = await accessibleRun(c, c.req.param("id"));
|
|
544
|
+
if (!run) {
|
|
545
|
+
return c.json(notFoundAnswer(c), 404);
|
|
546
|
+
}
|
|
547
|
+
const answer = z
|
|
548
|
+
.object({ text: z.string().max(200_000).optional(), error: z.string().max(500).optional() })
|
|
549
|
+
.parse(await c.req.json().catch(() => ({})));
|
|
550
|
+
return c.json({ ok: await answerDevice(run.id, c.req.param("reqId"), answer) });
|
|
551
|
+
})
|
|
552
|
+
.post("/:id/pages/:stepId", async (c) => {
|
|
553
|
+
const run = await accessibleRun(c, c.req.param("id"));
|
|
554
|
+
if (!run) {
|
|
555
|
+
return c.json(notFoundAnswer(c), 404);
|
|
556
|
+
}
|
|
557
|
+
const { values } = z
|
|
558
|
+
.object({ values: z.record(z.string(), z.unknown()) })
|
|
559
|
+
.parse(await c.req.json());
|
|
560
|
+
try {
|
|
561
|
+
await submitPage(run.id, c.req.param("stepId"), values);
|
|
562
|
+
return c.json({ ok: true });
|
|
563
|
+
}
|
|
564
|
+
catch (err) {
|
|
565
|
+
return commandError(c, err);
|
|
566
|
+
}
|
|
567
|
+
})
|
|
568
|
+
// What the person did in a step's view — a checklist's ticks, a widget's state — kept with the
|
|
569
|
+
// run, so the next visit shows it. Only while the run waits or is done: a running step owns
|
|
570
|
+
// the state then.
|
|
571
|
+
.put("/:id/views/:stepId", async (c) => {
|
|
572
|
+
const run = await accessibleRun(c, c.req.param("id"));
|
|
573
|
+
const stepId = c.req.param("stepId");
|
|
574
|
+
const output = run?.state.outputs[stepId];
|
|
575
|
+
if (!run || !output) {
|
|
576
|
+
return c.json(notFoundAnswer(c), 404);
|
|
577
|
+
}
|
|
578
|
+
if (run.status !== "waiting_input" && run.status !== "done") {
|
|
579
|
+
return c.json({ error: "busy" }, 409);
|
|
580
|
+
}
|
|
581
|
+
const { state } = z
|
|
582
|
+
.object({ state: z.record(z.string(), z.unknown()) })
|
|
583
|
+
.parse(await c.req.json());
|
|
584
|
+
if (JSON.stringify(state).length > MAX_VIEW_STATE) {
|
|
585
|
+
return c.json({ error: "too large" }, 413);
|
|
586
|
+
}
|
|
587
|
+
await db
|
|
588
|
+
.update(schema.run)
|
|
589
|
+
.set({
|
|
590
|
+
state: {
|
|
591
|
+
...run.state,
|
|
592
|
+
outputs: { ...run.state.outputs, [stepId]: { ...output, view: state } },
|
|
593
|
+
},
|
|
594
|
+
})
|
|
595
|
+
.where(eq(schema.run.id, run.id));
|
|
596
|
+
return c.json({ ok: true });
|
|
597
|
+
})
|
|
598
|
+
// A json step's data, edited in the studio by someone of the team the run belongs to.
|
|
599
|
+
.put("/:id/outputs/:stepId", async (c) => {
|
|
600
|
+
const run = await accessibleRun(c, c.req.param("id"));
|
|
601
|
+
const user = await principalOf(c);
|
|
602
|
+
if (!run || !user || user.tenantId !== run.tenantId) {
|
|
603
|
+
return c.json(notFoundAnswer(c), 404);
|
|
604
|
+
}
|
|
605
|
+
const { json } = z
|
|
606
|
+
.object({ json: z.record(z.string(), z.unknown()) })
|
|
607
|
+
.parse(await c.req.json());
|
|
608
|
+
try {
|
|
609
|
+
await editOutput(run.id, c.req.param("stepId"), json);
|
|
610
|
+
return c.json({ ok: true });
|
|
611
|
+
}
|
|
612
|
+
catch (err) {
|
|
613
|
+
return commandError(c, err);
|
|
614
|
+
}
|
|
615
|
+
})
|
|
616
|
+
.post("/:id/reviews/:stepId", async (c) => {
|
|
617
|
+
const run = await accessibleRun(c, c.req.param("id"));
|
|
618
|
+
if (!run) {
|
|
619
|
+
return c.json(notFoundAnswer(c), 404);
|
|
620
|
+
}
|
|
621
|
+
const action = z
|
|
622
|
+
.discriminatedUnion("type", [
|
|
623
|
+
z.object({
|
|
624
|
+
type: z.literal("accept"),
|
|
625
|
+
// A text, or the data of a json step (checked against its schema).
|
|
626
|
+
edits: z
|
|
627
|
+
.record(z.string(), z.union([z.string(), z.record(z.string(), z.unknown())]))
|
|
628
|
+
.optional(),
|
|
629
|
+
}),
|
|
630
|
+
z.object({
|
|
631
|
+
type: z.literal("regenerate"),
|
|
632
|
+
target: z.string(),
|
|
633
|
+
note: z.string().max(2000),
|
|
634
|
+
items: z.array(z.number().int().min(0).max(50)).max(20).optional(),
|
|
635
|
+
}),
|
|
636
|
+
])
|
|
637
|
+
.parse(await c.req.json());
|
|
638
|
+
try {
|
|
639
|
+
await reviewStep(run.id, c.req.param("stepId"), action);
|
|
640
|
+
return c.json({ ok: true });
|
|
641
|
+
}
|
|
642
|
+
catch (err) {
|
|
643
|
+
return commandError(c, err);
|
|
644
|
+
}
|
|
645
|
+
})
|
|
646
|
+
.post("/:id/back", async (c) => {
|
|
647
|
+
const run = await accessibleRun(c, c.req.param("id"));
|
|
648
|
+
if (!run) {
|
|
649
|
+
return c.json(notFoundAnswer(c), 404);
|
|
650
|
+
}
|
|
651
|
+
try {
|
|
652
|
+
await goBack(run.id);
|
|
653
|
+
return c.json({ ok: true });
|
|
654
|
+
}
|
|
655
|
+
catch (err) {
|
|
656
|
+
return commandError(c, err);
|
|
657
|
+
}
|
|
658
|
+
})
|
|
659
|
+
.post("/:id/retry", async (c) => {
|
|
660
|
+
const run = await accessibleRun(c, c.req.param("id"));
|
|
661
|
+
if (!run) {
|
|
662
|
+
return c.json(notFoundAnswer(c), 404);
|
|
663
|
+
}
|
|
664
|
+
try {
|
|
665
|
+
await retry(run.id);
|
|
666
|
+
return c.json({ ok: true });
|
|
667
|
+
}
|
|
668
|
+
catch (err) {
|
|
669
|
+
return commandError(c, err);
|
|
670
|
+
}
|
|
671
|
+
})
|
|
672
|
+
.post("/:id/cancel", async (c) => {
|
|
673
|
+
const run = await accessibleRun(c, c.req.param("id"));
|
|
674
|
+
if (!run) {
|
|
675
|
+
return c.json(notFoundAnswer(c), 404);
|
|
676
|
+
}
|
|
677
|
+
await cancel(run.id);
|
|
678
|
+
return c.json({ ok: true });
|
|
679
|
+
})
|
|
680
|
+
// The viewer reads the run in another language from now on: null for the wizard's own.
|
|
681
|
+
.post("/:id/lang", async (c) => {
|
|
682
|
+
const run = await accessibleRun(c, c.req.param("id"));
|
|
683
|
+
if (!run) {
|
|
684
|
+
return c.json(notFoundAnswer(c), 404);
|
|
685
|
+
}
|
|
686
|
+
const { lang } = z
|
|
687
|
+
.object({ lang: z.string().regex(LANG_CODE).nullable() })
|
|
688
|
+
.parse(await c.req.json().catch(() => ({})));
|
|
689
|
+
const w = await db.query.wizard.findFirst({ where: eq(schema.wizard.id, run.wizardId) });
|
|
690
|
+
const own = !lang || lang === wizardLang(run.definition);
|
|
691
|
+
if (!(own || (w && wantsTranslation(w, run.definition, lang)))) {
|
|
692
|
+
return refuse(c, { code: "translationOff" }, 403);
|
|
693
|
+
}
|
|
694
|
+
if (!own) {
|
|
695
|
+
try {
|
|
696
|
+
await translationFor(run.wizardId, run.definition, lang, run.version);
|
|
697
|
+
}
|
|
698
|
+
catch (err) {
|
|
699
|
+
if (err instanceof ServiceError || err instanceof ModelUnavailableError) {
|
|
700
|
+
return refuse(c, { code: "translationFailed" }, 502);
|
|
701
|
+
}
|
|
702
|
+
throw err;
|
|
703
|
+
}
|
|
704
|
+
}
|
|
705
|
+
await db
|
|
706
|
+
.update(schema.run)
|
|
707
|
+
.set({ lang: own ? null : lang })
|
|
708
|
+
.where(eq(schema.run.id, run.id));
|
|
709
|
+
signalChanged(run.id);
|
|
710
|
+
return c.json({ ok: true });
|
|
711
|
+
})
|
|
712
|
+
// A finished result's text in the language the viewer reads the run in, kept on the run.
|
|
713
|
+
.post("/:id/translate-result", async (c) => {
|
|
714
|
+
const run = await accessibleRun(c, c.req.param("id"));
|
|
715
|
+
if (!run) {
|
|
716
|
+
return c.json(notFoundAnswer(c), 404);
|
|
717
|
+
}
|
|
718
|
+
const { stepId } = z.object({ stepId: z.string() }).parse(await c.req.json().catch(() => ({})));
|
|
719
|
+
const text = run.state.outputs[stepId]?.text;
|
|
720
|
+
if (!(run.lang && text)) {
|
|
721
|
+
return c.json(notFoundAnswer(c), 404);
|
|
722
|
+
}
|
|
723
|
+
if (!run.state.translated?.[run.lang]?.[stepId]) {
|
|
724
|
+
let translated;
|
|
725
|
+
try {
|
|
726
|
+
translated = await translateResult(text, run.lang, wizardLang(run.definition));
|
|
727
|
+
}
|
|
728
|
+
catch (err) {
|
|
729
|
+
if (err instanceof ServiceError || err instanceof ModelUnavailableError) {
|
|
730
|
+
return refuse(c, { code: "translationFailed" }, 502);
|
|
731
|
+
}
|
|
732
|
+
throw err;
|
|
733
|
+
}
|
|
734
|
+
const fresh = await db.query.run.findFirst({ where: eq(schema.run.id, run.id) });
|
|
735
|
+
const state = fresh?.state ?? run.state;
|
|
736
|
+
state.translated = {
|
|
737
|
+
...state.translated,
|
|
738
|
+
[run.lang]: { ...state.translated?.[run.lang], [stepId]: translated },
|
|
739
|
+
};
|
|
740
|
+
await db.update(schema.run).set({ state }).where(eq(schema.run.id, run.id));
|
|
741
|
+
signalChanged(run.id);
|
|
742
|
+
}
|
|
743
|
+
return c.json({ ok: true });
|
|
744
|
+
})
|
|
745
|
+
// A live conversation on the run: the secret the browser opens the session with.
|
|
746
|
+
.post("/:id/talk", async (c) => {
|
|
747
|
+
const run = await accessibleRun(c, c.req.param("id"));
|
|
748
|
+
if (!run) {
|
|
749
|
+
return c.json(notFoundAnswer(c), 404);
|
|
750
|
+
}
|
|
751
|
+
if (run.status === "done" || run.status === "cancelled" || run.status === "failed") {
|
|
752
|
+
return refuse(c, { code: "runOver" }, 409);
|
|
753
|
+
}
|
|
754
|
+
if (!(await talkAllowed(run))) {
|
|
755
|
+
return refuse(c, { code: "talkOff" }, 403);
|
|
756
|
+
}
|
|
757
|
+
if (!allowed(`talk:${run.id}`, 20, 3600_000)) {
|
|
758
|
+
return refuse(c, { code: "tryLater" }, 429);
|
|
759
|
+
}
|
|
760
|
+
try {
|
|
761
|
+
const view = await viewOf(run);
|
|
762
|
+
return c.json(await mintConversation(view));
|
|
763
|
+
}
|
|
764
|
+
catch (err) {
|
|
765
|
+
if (err instanceof ModelUnavailableError) {
|
|
766
|
+
return c.json({ error: errorText(err, requestLang(c)) }, 403);
|
|
767
|
+
}
|
|
768
|
+
console.error("[talk]", err);
|
|
769
|
+
return c.json({ error: errorText(err, requestLang(c)) }, 502);
|
|
770
|
+
}
|
|
771
|
+
})
|
|
772
|
+
// What the person says into the chat's composer, written down — where the browser cannot do it itself.
|
|
773
|
+
.post("/:id/transcribe", async (c) => {
|
|
774
|
+
const run = await accessibleRun(c, c.req.param("id"));
|
|
775
|
+
if (!run) {
|
|
776
|
+
return c.json(notFoundAnswer(c), 404);
|
|
777
|
+
}
|
|
778
|
+
if (run.status === "done" || run.status === "cancelled" || run.status === "failed") {
|
|
779
|
+
return refuse(c, { code: "runOver" }, 409);
|
|
780
|
+
}
|
|
781
|
+
if (!allowed(`transcribe:${run.id}`, 60, 3600_000)) {
|
|
782
|
+
return refuse(c, { code: "tryLater" }, 429);
|
|
783
|
+
}
|
|
784
|
+
const file = (await c.req.formData()).get("file");
|
|
785
|
+
if (!(file instanceof File) || !file.size || file.size > 10_000_000) {
|
|
786
|
+
return refuse(c, { code: "recordingTooLarge" }, 400);
|
|
787
|
+
}
|
|
788
|
+
try {
|
|
789
|
+
const { text } = await transcribeAudio({
|
|
790
|
+
bytes: new Uint8Array(await file.arrayBuffer()),
|
|
791
|
+
mediaType: file.type.split(";")[0].trim() || "audio/webm",
|
|
792
|
+
call: { runId: run.id },
|
|
793
|
+
});
|
|
794
|
+
return c.json({ text });
|
|
795
|
+
}
|
|
796
|
+
catch (err) {
|
|
797
|
+
if (err instanceof ModelUnavailableError) {
|
|
798
|
+
return c.json({ error: errorText(err, requestLang(c)) }, 403);
|
|
799
|
+
}
|
|
800
|
+
// Apple Intelligence on this Mac says why in a note (harness/apple.ts).
|
|
801
|
+
if (noteOf(err)) {
|
|
802
|
+
return c.json({ error: errorText(err, requestLang(c)) }, 502);
|
|
803
|
+
}
|
|
804
|
+
throw err;
|
|
805
|
+
}
|
|
806
|
+
})
|
|
807
|
+
.post("/:id/uploads", async (c) => {
|
|
808
|
+
const run = await accessibleRun(c, c.req.param("id"));
|
|
809
|
+
if (!run) {
|
|
810
|
+
return c.json(notFoundAnswer(c), 404);
|
|
811
|
+
}
|
|
812
|
+
const form = await c.req.formData();
|
|
813
|
+
const file = form.get("file");
|
|
814
|
+
// A recorder names its type with codecs ("audio/webm;codecs=opus"); the bare type is kept,
|
|
815
|
+
// and only when it is one a wizard takes. The browser's word is the sender's: anything else
|
|
816
|
+
// is kept as plain bytes, so it can never come back as a page on this origin.
|
|
817
|
+
const mime = uploadMime(file instanceof File ? file.type : "");
|
|
818
|
+
const limit = uploadLimit(mime);
|
|
819
|
+
if (!(file instanceof File) || file.size > limit) {
|
|
820
|
+
return refuse(c, { code: "fileTooLarge", params: { mb: Math.round(limit / 1_000_000) } }, 400);
|
|
821
|
+
}
|
|
822
|
+
const ref = await saveAsset({
|
|
823
|
+
runId: run.id,
|
|
824
|
+
kind: mime.startsWith("image/") ? "upload-image" : "upload",
|
|
825
|
+
mime,
|
|
826
|
+
name: file.name.slice(0, 120),
|
|
827
|
+
data: new Uint8Array(await file.arrayBuffer()),
|
|
828
|
+
});
|
|
829
|
+
return c.json(ref);
|
|
830
|
+
})
|
|
831
|
+
// The address of where the person stands, for a location field — only when a geocoder is set up.
|
|
832
|
+
.post("/:id/geocode", async (c) => {
|
|
833
|
+
const run = await accessibleRun(c, c.req.param("id"));
|
|
834
|
+
if (!run) {
|
|
835
|
+
return c.json(notFoundAnswer(c), 404);
|
|
836
|
+
}
|
|
837
|
+
const { lat, lng, language } = z
|
|
838
|
+
.object({
|
|
839
|
+
lat: z.number().min(-90).max(90),
|
|
840
|
+
lng: z.number().min(-180).max(180),
|
|
841
|
+
language: z.enum(LANGS).default("en"),
|
|
842
|
+
})
|
|
843
|
+
.parse(await c.req.json());
|
|
844
|
+
return c.json({ label: await reverseGeocode(lat, lng, language) });
|
|
845
|
+
})
|
|
846
|
+
.get("/:id/assets/:assetId", async (c) => {
|
|
847
|
+
const run = await accessibleRun(c, c.req.param("id"), { signed: true });
|
|
848
|
+
if (!run) {
|
|
849
|
+
return c.notFound();
|
|
850
|
+
}
|
|
851
|
+
return serveAsset(c, run, c.req.param("assetId"));
|
|
852
|
+
})
|
|
853
|
+
.get("/:id/steps/:stepId/download", async (c) => {
|
|
854
|
+
const run = await accessibleRun(c, c.req.param("id"), { signed: true });
|
|
855
|
+
if (!run) {
|
|
856
|
+
return c.notFound();
|
|
857
|
+
}
|
|
858
|
+
return serveDownload(c, run, c.req.param("stepId"), c.req.query("format"));
|
|
859
|
+
})
|
|
860
|
+
// --- a running step waits for the person ------------------------------------
|
|
861
|
+
.post("/:id/ask/:askId", async (c) => {
|
|
862
|
+
const run = await accessibleRun(c, c.req.param("id"));
|
|
863
|
+
if (!run) {
|
|
864
|
+
return c.json(notFoundAnswer(c), 404);
|
|
865
|
+
}
|
|
866
|
+
const answer = z
|
|
867
|
+
.discriminatedUnion("type", [
|
|
868
|
+
z.object({
|
|
869
|
+
type: z.literal("fill"),
|
|
870
|
+
values: z.record(z.string(), z.string().max(500)),
|
|
871
|
+
remember: z.boolean().optional(),
|
|
872
|
+
}),
|
|
873
|
+
z.object({ type: z.literal("done"), remember: z.boolean().optional() }),
|
|
874
|
+
z.object({ type: z.literal("skip") }),
|
|
875
|
+
])
|
|
876
|
+
.parse(await c.req.json());
|
|
877
|
+
if (!(await answerAsk(run.id, c.req.param("askId"), answer))) {
|
|
878
|
+
return refuse(c, { code: "askClosed" }, 409);
|
|
879
|
+
}
|
|
880
|
+
return c.json({ ok: true });
|
|
881
|
+
})
|
|
882
|
+
// Who drives the wizard's browser: the wizard, or the person who took it over.
|
|
883
|
+
.get("/:id/browser/seat", async (c) => {
|
|
884
|
+
const run = await accessibleRun(c, c.req.param("id"));
|
|
885
|
+
const resources = run?.status === "running" ? liveResources(run.id) : null;
|
|
886
|
+
if (!resources?.openPage()) {
|
|
887
|
+
// The run may work in another process: its runner answers.
|
|
888
|
+
return (run && (await fromOwner(c, run.id))) || c.json(notFoundAnswer(c), 404);
|
|
889
|
+
}
|
|
890
|
+
return c.json({ seat: resources.seatHolder() });
|
|
891
|
+
})
|
|
892
|
+
// The person takes the wizard's browser over, or hands it back. While they hold it, the
|
|
893
|
+
// wizard's browser steps wait.
|
|
894
|
+
.post("/:id/browser/seat", async (c) => {
|
|
895
|
+
const run = await accessibleRun(c, c.req.param("id"));
|
|
896
|
+
const resources = run?.status === "running" ? liveResources(run.id) : null;
|
|
897
|
+
if (!(run && resources?.openPage())) {
|
|
898
|
+
return (run && (await fromOwner(c, run.id))) || refuse(c, { code: "noBrowser" }, 409);
|
|
899
|
+
}
|
|
900
|
+
const { to } = z.object({ to: z.enum(["person", "agent"]) }).parse(await c.req.json());
|
|
901
|
+
if (resources.seatHolder() !== to) {
|
|
902
|
+
resources.setSeat(to);
|
|
903
|
+
await emitEvent(run.id, run.cursor, "info", {
|
|
904
|
+
code: to === "person" ? "personDrives" : "agentDrives",
|
|
905
|
+
}).catch(() => undefined);
|
|
906
|
+
}
|
|
907
|
+
return c.json({ seat: to });
|
|
908
|
+
})
|
|
909
|
+
// The wizard's browser as it works, as a picture that keeps changing (MJPEG, for an <img>):
|
|
910
|
+
// a new frame whenever the page repaints, at most about five a second. Ends with the run's
|
|
911
|
+
// browser.
|
|
912
|
+
.get("/:id/browser/live", async (c) => {
|
|
913
|
+
const run = await accessibleRun(c, c.req.param("id"));
|
|
914
|
+
const resources = run?.status === "running" ? liveResources(run.id) : null;
|
|
915
|
+
if (!resources?.openPage()) {
|
|
916
|
+
return (run && (await fromOwner(c, run.id))) || c.notFound();
|
|
917
|
+
}
|
|
918
|
+
const boundary = "frame";
|
|
919
|
+
const encoder = new TextEncoder();
|
|
920
|
+
let stop = () => undefined;
|
|
921
|
+
const body = new ReadableStream({
|
|
922
|
+
start(controller) {
|
|
923
|
+
let latest = null;
|
|
924
|
+
let sentAt = 0;
|
|
925
|
+
let timer = null;
|
|
926
|
+
const send = () => {
|
|
927
|
+
timer = null;
|
|
928
|
+
if (!latest) {
|
|
929
|
+
return;
|
|
930
|
+
}
|
|
931
|
+
sentAt = Date.now();
|
|
932
|
+
const head = `--${boundary}\r\nContent-Type: image/jpeg\r\nContent-Length: ${latest.length}\r\n\r\n`;
|
|
933
|
+
try {
|
|
934
|
+
controller.enqueue(encoder.encode(head));
|
|
935
|
+
controller.enqueue(new Uint8Array(latest));
|
|
936
|
+
controller.enqueue(encoder.encode("\r\n"));
|
|
937
|
+
}
|
|
938
|
+
catch {
|
|
939
|
+
stop();
|
|
940
|
+
}
|
|
941
|
+
latest = null;
|
|
942
|
+
};
|
|
943
|
+
const unwatch = resources.watchBrowser((frame) => {
|
|
944
|
+
if (!frame) {
|
|
945
|
+
stop();
|
|
946
|
+
try {
|
|
947
|
+
controller.close();
|
|
948
|
+
}
|
|
949
|
+
catch {
|
|
950
|
+
// already closed by the viewer
|
|
951
|
+
}
|
|
952
|
+
return;
|
|
953
|
+
}
|
|
954
|
+
latest = frame;
|
|
955
|
+
if (!timer) {
|
|
956
|
+
timer = setTimeout(send, Math.max(0, 200 - (Date.now() - sentAt)));
|
|
957
|
+
}
|
|
958
|
+
});
|
|
959
|
+
stop = () => {
|
|
960
|
+
if (timer) {
|
|
961
|
+
clearTimeout(timer);
|
|
962
|
+
}
|
|
963
|
+
unwatch();
|
|
964
|
+
};
|
|
965
|
+
},
|
|
966
|
+
cancel() {
|
|
967
|
+
stop();
|
|
968
|
+
},
|
|
969
|
+
});
|
|
970
|
+
c.req.raw.signal.addEventListener("abort", () => stop());
|
|
971
|
+
return new Response(body, {
|
|
972
|
+
headers: {
|
|
973
|
+
"content-type": `multipart/x-mixed-replace; boundary=${boundary}`,
|
|
974
|
+
"cache-control": "no-store",
|
|
975
|
+
"x-accel-buffering": "no",
|
|
976
|
+
},
|
|
977
|
+
});
|
|
978
|
+
})
|
|
979
|
+
// While the wizard waits for a sign-in, or the person took its browser over, they use its
|
|
980
|
+
// page: click, type, scroll. What they type is theirs: no reading of the page gives it to a
|
|
981
|
+
// model.
|
|
982
|
+
.post("/:id/browser/act", async (c) => {
|
|
983
|
+
const run = await accessibleRun(c, c.req.param("id"));
|
|
984
|
+
const resources = run ? liveResources(run.id) : null;
|
|
985
|
+
if (run?.status === "running" && !resources) {
|
|
986
|
+
// The run works in another process: its runner answers.
|
|
987
|
+
const answered = await fromOwner(c, run.id);
|
|
988
|
+
if (answered) {
|
|
989
|
+
return answered;
|
|
990
|
+
}
|
|
991
|
+
}
|
|
992
|
+
const theirs = run && ((await pendingAsk(run.id)) || resources?.seatHolder() === "person");
|
|
993
|
+
const page = theirs ? resources?.openPage() : null;
|
|
994
|
+
if (!(run && resources && page)) {
|
|
995
|
+
return refuse(c, { code: "notWaiting" }, 409);
|
|
996
|
+
}
|
|
997
|
+
resources.touchSeat();
|
|
998
|
+
const act = z
|
|
999
|
+
.discriminatedUnion("type", [
|
|
1000
|
+
z.object({
|
|
1001
|
+
type: z.literal("click"),
|
|
1002
|
+
fx: z.number().min(0).max(1),
|
|
1003
|
+
fy: z.number().min(0).max(1),
|
|
1004
|
+
}),
|
|
1005
|
+
z.object({ type: z.literal("type"), text: z.string().max(500) }),
|
|
1006
|
+
z.object({ type: z.literal("key"), key: z.enum(["Enter", "Tab", "Backspace", "Escape"]) }),
|
|
1007
|
+
z.object({ type: z.literal("scroll"), dy: z.number().min(-2000).max(2000) }),
|
|
1008
|
+
])
|
|
1009
|
+
.parse(await c.req.json());
|
|
1010
|
+
try {
|
|
1011
|
+
if (act.type === "click") {
|
|
1012
|
+
// A click may open the sign-in in a new tab; the wizard then goes on there.
|
|
1013
|
+
const popup = page
|
|
1014
|
+
.context()
|
|
1015
|
+
.waitForEvent("page", { timeout: 1500 })
|
|
1016
|
+
.catch(() => null);
|
|
1017
|
+
const size = page.viewportSize() ?? { width: 1280, height: 900 };
|
|
1018
|
+
await page.mouse.click(act.fx * size.width, act.fy * size.height);
|
|
1019
|
+
const opened = await popup;
|
|
1020
|
+
if (opened) {
|
|
1021
|
+
resources.adopt(opened);
|
|
1022
|
+
}
|
|
1023
|
+
}
|
|
1024
|
+
else if (act.type === "type") {
|
|
1025
|
+
await page.evaluate(MARK_FOCUSED_PERSONAL).catch(() => undefined);
|
|
1026
|
+
await page.keyboard.type(act.text);
|
|
1027
|
+
}
|
|
1028
|
+
else if (act.type === "key") {
|
|
1029
|
+
await page.keyboard.press(act.key);
|
|
1030
|
+
}
|
|
1031
|
+
else {
|
|
1032
|
+
await page.mouse.wheel(0, act.dy);
|
|
1033
|
+
}
|
|
1034
|
+
await page.waitForLoadState("domcontentloaded", { timeout: 3000 }).catch(() => undefined);
|
|
1035
|
+
}
|
|
1036
|
+
catch {
|
|
1037
|
+
return refuse(c, { code: "pageActFailed" }, 422);
|
|
1038
|
+
}
|
|
1039
|
+
return c.json({ ok: true });
|
|
1040
|
+
})
|
|
1041
|
+
// --- the person's accounts ----------------------------------------------------
|
|
1042
|
+
.post("/:id/connections/:connectionId/oauth/:connectorId", async (c) => {
|
|
1043
|
+
const run = await accessibleRun(c, c.req.param("id"));
|
|
1044
|
+
const connection = run?.definition.connections?.find((x) => x.id === c.req.param("connectionId"));
|
|
1045
|
+
if (!run || !connection) {
|
|
1046
|
+
return c.json(notFoundAnswer(c), 404);
|
|
1047
|
+
}
|
|
1048
|
+
try {
|
|
1049
|
+
const url = await startOAuth(scopeOf(run), await projectIdOf(run), run.id, connection, c.req.param("connectorId"), requestLang(c));
|
|
1050
|
+
return c.json({ url });
|
|
1051
|
+
}
|
|
1052
|
+
catch (err) {
|
|
1053
|
+
if (err instanceof ConnectError) {
|
|
1054
|
+
return c.json({ error: errorText(err, requestLang(c)) }, 422);
|
|
1055
|
+
}
|
|
1056
|
+
throw err;
|
|
1057
|
+
}
|
|
1058
|
+
})
|
|
1059
|
+
.post("/:id/connections/:connectionId/credentials/:connectorId", async (c) => {
|
|
1060
|
+
const run = await accessibleRun(c, c.req.param("id"));
|
|
1061
|
+
const connection = run?.definition.connections?.find((x) => x.id === c.req.param("connectionId"));
|
|
1062
|
+
if (!run || !connection) {
|
|
1063
|
+
return c.json(notFoundAnswer(c), 404);
|
|
1064
|
+
}
|
|
1065
|
+
const { values } = z
|
|
1066
|
+
.object({ values: z.record(z.string(), z.string().max(500)) })
|
|
1067
|
+
.parse(await c.req.json());
|
|
1068
|
+
try {
|
|
1069
|
+
const label = await connectWithCredentials(scopeOf(run), await projectIdOf(run), connection, c.req.param("connectorId"), values);
|
|
1070
|
+
signalChanged(run.id);
|
|
1071
|
+
return c.json({ ok: true, label });
|
|
1072
|
+
}
|
|
1073
|
+
catch (err) {
|
|
1074
|
+
if (err instanceof ConnectError) {
|
|
1075
|
+
return c.json({ error: errorText(err, requestLang(c)) }, 422);
|
|
1076
|
+
}
|
|
1077
|
+
throw err;
|
|
1078
|
+
}
|
|
1079
|
+
})
|
|
1080
|
+
.delete("/:id/connections/:connectionId", async (c) => {
|
|
1081
|
+
const run = await accessibleRun(c, c.req.param("id"));
|
|
1082
|
+
if (!run) {
|
|
1083
|
+
return c.json(notFoundAnswer(c), 404);
|
|
1084
|
+
}
|
|
1085
|
+
await disconnect(scopeOf(run), c.req.param("connectionId"));
|
|
1086
|
+
signalChanged(run.id);
|
|
1087
|
+
return c.json({ ok: true });
|
|
1088
|
+
})
|
|
1089
|
+
// --- the wizard's lists and files for this person -------------------------------
|
|
1090
|
+
.post("/:id/lists/:list/rows", async (c) => {
|
|
1091
|
+
const run = await accessibleRun(c, c.req.param("id"));
|
|
1092
|
+
// A shared list is every run's: the person never sees it.
|
|
1093
|
+
const def = run?.definition.lists?.find((l) => l.id === c.req.param("list") && !l.shared);
|
|
1094
|
+
if (!run || !def) {
|
|
1095
|
+
return c.json(notFoundAnswer(c), 404);
|
|
1096
|
+
}
|
|
1097
|
+
const { cells } = z
|
|
1098
|
+
.object({ cells: z.record(z.string(), z.unknown()) })
|
|
1099
|
+
.parse(await c.req.json());
|
|
1100
|
+
try {
|
|
1101
|
+
await saveRows(scopeOf(run), def, [cells]);
|
|
1102
|
+
}
|
|
1103
|
+
catch (err) {
|
|
1104
|
+
return listError(c, err);
|
|
1105
|
+
}
|
|
1106
|
+
signalChanged(run.id);
|
|
1107
|
+
return c.json({ ok: true });
|
|
1108
|
+
})
|
|
1109
|
+
.patch("/:id/lists/:list/rows/:rowId", async (c) => {
|
|
1110
|
+
const run = await accessibleRun(c, c.req.param("id"));
|
|
1111
|
+
// A shared list is every run's: the person never sees it.
|
|
1112
|
+
const def = run?.definition.lists?.find((l) => l.id === c.req.param("list") && !l.shared);
|
|
1113
|
+
if (!run || !def) {
|
|
1114
|
+
return c.json(notFoundAnswer(c), 404);
|
|
1115
|
+
}
|
|
1116
|
+
const { cells } = z
|
|
1117
|
+
.object({ cells: z.record(z.string(), z.unknown()) })
|
|
1118
|
+
.parse(await c.req.json());
|
|
1119
|
+
try {
|
|
1120
|
+
if (!(await updateRow(scopeOf(run), def, c.req.param("rowId"), cells))) {
|
|
1121
|
+
return c.json(notFoundAnswer(c), 404);
|
|
1122
|
+
}
|
|
1123
|
+
}
|
|
1124
|
+
catch (err) {
|
|
1125
|
+
return listError(c, err);
|
|
1126
|
+
}
|
|
1127
|
+
signalChanged(run.id);
|
|
1128
|
+
return c.json({ ok: true });
|
|
1129
|
+
})
|
|
1130
|
+
.delete("/:id/lists/:list/rows/:rowId", async (c) => {
|
|
1131
|
+
const run = await accessibleRun(c, c.req.param("id"));
|
|
1132
|
+
// A shared list is every run's: the person never sees it.
|
|
1133
|
+
const def = run?.definition.lists?.find((l) => l.id === c.req.param("list") && !l.shared);
|
|
1134
|
+
if (!run || !def) {
|
|
1135
|
+
return c.json(notFoundAnswer(c), 404);
|
|
1136
|
+
}
|
|
1137
|
+
await deleteRows(scopeOf(run), def, [c.req.param("rowId")]);
|
|
1138
|
+
signalChanged(run.id);
|
|
1139
|
+
return c.json({ ok: true });
|
|
1140
|
+
})
|
|
1141
|
+
.get("/:id/lists/:list/download", async (c) => {
|
|
1142
|
+
const run = await accessibleRun(c, c.req.param("id"));
|
|
1143
|
+
// A shared list is every run's: the person never sees it.
|
|
1144
|
+
const def = run?.definition.lists?.find((l) => l.id === c.req.param("list") && !l.shared);
|
|
1145
|
+
const format = z.enum(FORMATS).safeParse(c.req.query("format"));
|
|
1146
|
+
if (!run || !def || !format.success || !LIST_FORMATS.includes(format.data)) {
|
|
1147
|
+
return c.notFound();
|
|
1148
|
+
}
|
|
1149
|
+
const scope = scopeOf(run);
|
|
1150
|
+
const download = await listDownload(def, await listRows(scope, def), format.data, `${run.definition.title} ${def.title}`, async (path) => {
|
|
1151
|
+
const found = await readStoreFile(scope, path).catch(() => null);
|
|
1152
|
+
return found ? new Uint8Array(found.data) : null;
|
|
1153
|
+
});
|
|
1154
|
+
return download ? sendDownload(c, download) : c.notFound();
|
|
1155
|
+
})
|
|
1156
|
+
.get("/:id/store", async (c) => {
|
|
1157
|
+
const run = await accessibleRun(c, c.req.param("id"));
|
|
1158
|
+
if (!run) {
|
|
1159
|
+
return c.json(notFoundAnswer(c), 404);
|
|
1160
|
+
}
|
|
1161
|
+
const scope = scopeOf(run);
|
|
1162
|
+
const lists = await Promise.all((run.definition.lists ?? [])
|
|
1163
|
+
.filter((def) => !def.shared)
|
|
1164
|
+
.map(async (def) => ({
|
|
1165
|
+
id: def.id,
|
|
1166
|
+
title: def.title,
|
|
1167
|
+
rows: (await listRows(scope, def)).length,
|
|
1168
|
+
})));
|
|
1169
|
+
const secrets = await listSecrets(scope);
|
|
1170
|
+
return c.json({
|
|
1171
|
+
lists,
|
|
1172
|
+
files: await storeFiles(scope),
|
|
1173
|
+
connections: (await connectionViews(run.definition.connections ?? [], scope, await projectIdOf(run), requestLang(c))).filter((x) => x.account),
|
|
1174
|
+
keepsSignIns: secrets.some((s) => s.slot === "browser"),
|
|
1175
|
+
});
|
|
1176
|
+
})
|
|
1177
|
+
.get("/:id/store/files/:path{.+}", async (c) => {
|
|
1178
|
+
const run = await accessibleRun(c, c.req.param("id"));
|
|
1179
|
+
const found = run ? await readStoreFile(scopeOf(run), c.req.param("path")) : null;
|
|
1180
|
+
if (!found) {
|
|
1181
|
+
return c.notFound();
|
|
1182
|
+
}
|
|
1183
|
+
const name = (found.file.path.split("/").pop() ?? "file").replace(/"/g, "");
|
|
1184
|
+
// Shown beside its row in a review: only what a browser draws without running anything of
|
|
1185
|
+
// the file's — PDFs and photos. Everything else stays a download.
|
|
1186
|
+
if (c.req.query("inline") && INLINE_TYPES.has(found.file.mime)) {
|
|
1187
|
+
return c.body(new Uint8Array(found.data), 200, {
|
|
1188
|
+
"content-type": found.file.mime,
|
|
1189
|
+
"content-disposition": `inline; filename="${name}"`,
|
|
1190
|
+
"x-content-type-options": "nosniff",
|
|
1191
|
+
"cache-control": "private, max-age=300",
|
|
1192
|
+
});
|
|
1193
|
+
}
|
|
1194
|
+
return c.body(new Uint8Array(found.data), 200, {
|
|
1195
|
+
// Stored files come from mail and the web: always a download, never a page of ours.
|
|
1196
|
+
"content-type": "application/octet-stream",
|
|
1197
|
+
"content-disposition": `attachment; filename="${name}"`,
|
|
1198
|
+
"x-content-type-options": "nosniff",
|
|
1199
|
+
});
|
|
1200
|
+
})
|
|
1201
|
+
.delete("/:id/store", async (c) => {
|
|
1202
|
+
const run = await accessibleRun(c, c.req.param("id"));
|
|
1203
|
+
if (!run) {
|
|
1204
|
+
return c.json(notFoundAnswer(c), 404);
|
|
1205
|
+
}
|
|
1206
|
+
await clearStore(scopeOf(run));
|
|
1207
|
+
signalChanged(run.id);
|
|
1208
|
+
return c.json({ ok: true });
|
|
1209
|
+
})
|
|
1210
|
+
// The mobile app's device for this run: told when it is done, failed or waits for the person.
|
|
1211
|
+
.post("/:id/notify", async (c) => {
|
|
1212
|
+
const run = await accessibleRun(c, c.req.param("id"));
|
|
1213
|
+
if (!run) {
|
|
1214
|
+
return c.json(notFoundAnswer(c), 404);
|
|
1215
|
+
}
|
|
1216
|
+
const body = z
|
|
1217
|
+
.object({
|
|
1218
|
+
token: z.string().min(8).max(512),
|
|
1219
|
+
platform: z.enum(["ios", "android"]),
|
|
1220
|
+
lang: z.enum(LANGS).default("en"),
|
|
1221
|
+
})
|
|
1222
|
+
.parse(await c.req.json());
|
|
1223
|
+
await setPushDevice(run.id, body);
|
|
1224
|
+
return c.json({ push: pushAvailable() });
|
|
1225
|
+
})
|
|
1226
|
+
.delete("/:id/notify", async (c) => {
|
|
1227
|
+
const run = await accessibleRun(c, c.req.param("id"));
|
|
1228
|
+
if (!run) {
|
|
1229
|
+
return c.json(notFoundAnswer(c), 404);
|
|
1230
|
+
}
|
|
1231
|
+
await setPushDevice(run.id, null);
|
|
1232
|
+
return c.json({ ok: true });
|
|
1233
|
+
})
|
|
1234
|
+
.post("/:id/share", async (c) => {
|
|
1235
|
+
const run = await accessibleRun(c, c.req.param("id"));
|
|
1236
|
+
if (!run) {
|
|
1237
|
+
return c.json(notFoundAnswer(c), 404);
|
|
1238
|
+
}
|
|
1239
|
+
try {
|
|
1240
|
+
return c.json(await shareRun(run));
|
|
1241
|
+
}
|
|
1242
|
+
catch (err) {
|
|
1243
|
+
if (err instanceof ServiceError) {
|
|
1244
|
+
return c.json(err.toJSON(requestLang(c)), err.status);
|
|
1245
|
+
}
|
|
1246
|
+
throw err;
|
|
1247
|
+
}
|
|
1248
|
+
})
|
|
1249
|
+
.delete("/:id/share", async (c) => {
|
|
1250
|
+
const run = await accessibleRun(c, c.req.param("id"));
|
|
1251
|
+
if (!run) {
|
|
1252
|
+
return c.json(notFoundAnswer(c), 404);
|
|
1253
|
+
}
|
|
1254
|
+
await unshareRun(run);
|
|
1255
|
+
return c.json({ ok: true });
|
|
1256
|
+
});
|
|
1257
|
+
function listError(c, err) {
|
|
1258
|
+
if (err instanceof TableColumnValueError || err instanceof StoreError) {
|
|
1259
|
+
return c.json({ error: errorText(err, requestLang(c)) }, 422);
|
|
1260
|
+
}
|
|
1261
|
+
throw err;
|
|
1262
|
+
}
|
|
1263
|
+
function sendDownload(c, download) {
|
|
1264
|
+
const data = new Uint8Array(typeof download.data === "string" ? new TextEncoder().encode(download.data) : download.data);
|
|
1265
|
+
return c.body(data, 200, {
|
|
1266
|
+
"content-type": download.mime.startsWith("text/")
|
|
1267
|
+
? `${download.mime}; charset=utf-8`
|
|
1268
|
+
: download.mime,
|
|
1269
|
+
"content-disposition": `attachment; filename="${download.filename}"`,
|
|
1270
|
+
});
|
|
1271
|
+
}
|
|
1272
|
+
/**
|
|
1273
|
+
* Where an OAuth provider sends the person back after connecting an account. The page tells the
|
|
1274
|
+
* wizard's window and closes itself.
|
|
1275
|
+
*/
|
|
1276
|
+
export const connectCallback = new Hono().get("/callback", async (c) => {
|
|
1277
|
+
const code = c.req.query("code");
|
|
1278
|
+
// Connected through the account's Manage-App: a ticket instead of a code.
|
|
1279
|
+
const ticket = c.req.query("ticket");
|
|
1280
|
+
const state = c.req.query("state");
|
|
1281
|
+
let ok = false;
|
|
1282
|
+
let said = c.req.query("error") === "exchange_failed"
|
|
1283
|
+
? { code: "connectFailed" }
|
|
1284
|
+
: { code: "connectCancelled" };
|
|
1285
|
+
// A plugin's sign-in (server.connections) names its tenant; a wizard's names its run.
|
|
1286
|
+
const pluginTenant = state ? pluginStateTenant(state) : null;
|
|
1287
|
+
const tenant = state ? (pluginTenant ?? (await tenantOfRun(oauthStateRun(state) ?? ""))) : null;
|
|
1288
|
+
const answer = ticket ? { ticket } : code ? { code } : null;
|
|
1289
|
+
if (answer && state && tenant) {
|
|
1290
|
+
try {
|
|
1291
|
+
if (pluginTenant) {
|
|
1292
|
+
await withTenant(tenant, () => finishPluginOAuth(answer, state));
|
|
1293
|
+
}
|
|
1294
|
+
else {
|
|
1295
|
+
const done = await withTenant(tenant, () => finishOAuth(answer, state));
|
|
1296
|
+
signalChanged(done.runId);
|
|
1297
|
+
}
|
|
1298
|
+
ok = true;
|
|
1299
|
+
said = { code: "connected" };
|
|
1300
|
+
}
|
|
1301
|
+
catch (err) {
|
|
1302
|
+
console.error("[connect]", err);
|
|
1303
|
+
said = { code: "connectFailed" };
|
|
1304
|
+
}
|
|
1305
|
+
}
|
|
1306
|
+
const message = noteText(said, (state && oauthStateLang(state)) || requestLang(c));
|
|
1307
|
+
return c.html(`<!doctype html><meta charset="utf-8"><title>engenty wizards</title>
|
|
1308
|
+
<body style="font:16px system-ui;display:grid;place-items:center;height:100vh;margin:0;color:#333">
|
|
1309
|
+
<p>${message}</p>
|
|
1310
|
+
<script>
|
|
1311
|
+
// The opener is the studio or a wizard's page: each origin gets the word, the wrong one drops it.
|
|
1312
|
+
for (const origin of ${JSON.stringify([...new Set([env.appUrl, env.publicUrl])])}) {
|
|
1313
|
+
try { window.opener && window.opener.postMessage({ type: "wizards:connected", ok: ${ok} }, origin); } catch (e) {}
|
|
1314
|
+
}
|
|
1315
|
+
setTimeout(function () { window.close(); }, ${ok ? 300 : 4000});
|
|
1316
|
+
</script>`);
|
|
1317
|
+
});
|
|
1318
|
+
async function serveAsset(c, run, assetId) {
|
|
1319
|
+
const found = await loadAsset(assetId);
|
|
1320
|
+
if (!found || found.row.runId !== run.id) {
|
|
1321
|
+
return c.notFound();
|
|
1322
|
+
}
|
|
1323
|
+
// The stored type is sent only as a bare `type/subtype`: "text/html,x" reads as HTML to a browser.
|
|
1324
|
+
const mime = bareMime(found.row.mime) ?? "application/octet-stream";
|
|
1325
|
+
// What a person or a plugin uploaded, and files a step collected from mail and the web.
|
|
1326
|
+
const foreign = FOREIGN_KINDS.has(found.row.kind);
|
|
1327
|
+
const headers = {
|
|
1328
|
+
"content-type": mime,
|
|
1329
|
+
"cache-control": "private, max-age=3600",
|
|
1330
|
+
"x-content-type-options": "nosniff",
|
|
1331
|
+
// Nothing opened on its own runs scripts with this origin (an SVG, an XML document, a page).
|
|
1332
|
+
// A PDF is left to the browser's own viewer, which a sandbox would keep from starting.
|
|
1333
|
+
...(mime === "application/pdf" ? {} : { "content-security-policy": "sandbox" }),
|
|
1334
|
+
};
|
|
1335
|
+
// Foreign files are downloads, never pages of ours: only photos, sound, film and PDFs show in place.
|
|
1336
|
+
if (foreign && !inlineSafe(mime)) {
|
|
1337
|
+
headers["content-disposition"] =
|
|
1338
|
+
`attachment; filename="${found.row.name.replace(/["\\\r\n]/g, "")}"`;
|
|
1339
|
+
}
|
|
1340
|
+
// Generated HTML runs its scripts in an opaque origin and can reach nothing.
|
|
1341
|
+
if (mime === "text/html" && !foreign) {
|
|
1342
|
+
const html = await stepHtml({
|
|
1343
|
+
assets: [{ id: assetId, kind: found.row.kind, mime: found.row.mime, name: "" }],
|
|
1344
|
+
at: "",
|
|
1345
|
+
});
|
|
1346
|
+
headers["content-type"] = "text/html; charset=utf-8";
|
|
1347
|
+
headers["content-security-policy"] = HTML_RESPONSE_CSP;
|
|
1348
|
+
return c.body(new TextEncoder().encode(html ?? ""), 200, headers);
|
|
1349
|
+
}
|
|
1350
|
+
if (/^(audio|video)\//.test(mime)) {
|
|
1351
|
+
headers["accept-ranges"] = "bytes";
|
|
1352
|
+
const size = found.data.byteLength;
|
|
1353
|
+
const range = byteRange(c.req.header("range"), size);
|
|
1354
|
+
if (range === "unsatisfiable") {
|
|
1355
|
+
return c.body(null, 416, { "content-range": `bytes */${size}` });
|
|
1356
|
+
}
|
|
1357
|
+
if (range) {
|
|
1358
|
+
headers["content-range"] = `bytes ${range.start}-${range.end}/${size}`;
|
|
1359
|
+
return c.body(new Uint8Array(found.data.subarray(range.start, range.end + 1)), 206, headers);
|
|
1360
|
+
}
|
|
1361
|
+
}
|
|
1362
|
+
return c.body(new Uint8Array(found.data), 200, headers);
|
|
1363
|
+
}
|
|
1364
|
+
async function serveDownload(c, run, stepId, rawFormat) {
|
|
1365
|
+
const format = z.enum(FORMATS).safeParse(rawFormat);
|
|
1366
|
+
const step = run.definition.steps.find((s) => s.id === stepId);
|
|
1367
|
+
const output = step ? run.state.outputs[step.id] : undefined;
|
|
1368
|
+
if (!format.success || !step || !output) {
|
|
1369
|
+
return c.notFound();
|
|
1370
|
+
}
|
|
1371
|
+
const label = run.definition.steps
|
|
1372
|
+
.flatMap((s) => (s.type === "result" ? s.deliverables : []))
|
|
1373
|
+
.find((d) => d.from === step.id)?.label;
|
|
1374
|
+
const download = await renderDownload(step, output, format.data, `${run.definition.title} ${label ?? step.title}`);
|
|
1375
|
+
if (!download) {
|
|
1376
|
+
return c.notFound();
|
|
1377
|
+
}
|
|
1378
|
+
return sendDownload(c, download);
|
|
1379
|
+
}
|
|
1380
|
+
/** Steps a shared link shows: the result's deliverables — never uploads or in-between steps. */
|
|
1381
|
+
function sharedStepIds(run) {
|
|
1382
|
+
return new Set(run.definition.steps.flatMap((s) => s.type === "result" ? s.deliverables.map((d) => d.from) : []));
|
|
1383
|
+
}
|
|
1384
|
+
/** `/api/shares/:token` — a shared result, readable by anyone holding the link until it expires. */
|
|
1385
|
+
export const shareRoutes = new Hono()
|
|
1386
|
+
.use("/:token", resultLink)
|
|
1387
|
+
.use("/:token/*", resultLink)
|
|
1388
|
+
.get("/:token", async (c) => {
|
|
1389
|
+
const run = await sharedRun(c.req.param("token"));
|
|
1390
|
+
if (!run) {
|
|
1391
|
+
return refuse(c, { code: "linkGone" }, 404);
|
|
1392
|
+
}
|
|
1393
|
+
return c.json(await shareView(run));
|
|
1394
|
+
})
|
|
1395
|
+
.get("/:token/image", async (c) => {
|
|
1396
|
+
const run = await sharedRun(c.req.param("token"));
|
|
1397
|
+
const image = run ? await shareImage(run) : null;
|
|
1398
|
+
if (!image) {
|
|
1399
|
+
return c.notFound();
|
|
1400
|
+
}
|
|
1401
|
+
return c.body(image.data, 200, {
|
|
1402
|
+
"content-type": image.mime,
|
|
1403
|
+
"cache-control": "public, max-age=3600",
|
|
1404
|
+
});
|
|
1405
|
+
})
|
|
1406
|
+
.get("/:token/assets/:assetId", async (c) => {
|
|
1407
|
+
const run = await sharedRun(c.req.param("token"));
|
|
1408
|
+
if (!run) {
|
|
1409
|
+
return c.notFound();
|
|
1410
|
+
}
|
|
1411
|
+
const assetId = c.req.param("assetId");
|
|
1412
|
+
const allowed = [...sharedStepIds(run)].some((id) => run.state.outputs[id]?.assets?.some((a) => a.id === assetId));
|
|
1413
|
+
return allowed ? serveAsset(c, run, assetId) : c.notFound();
|
|
1414
|
+
})
|
|
1415
|
+
.get("/:token/steps/:stepId/download", async (c) => {
|
|
1416
|
+
const run = await sharedRun(c.req.param("token"));
|
|
1417
|
+
const stepId = c.req.param("stepId");
|
|
1418
|
+
if (!run || !sharedStepIds(run).has(stepId)) {
|
|
1419
|
+
return c.notFound();
|
|
1420
|
+
}
|
|
1421
|
+
return serveDownload(c, run, stepId, c.req.query("format"));
|
|
1422
|
+
});
|