@stage-labs/metro 0.1.0-beta.13 → 0.1.0-beta.131
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/dist/api.js +14 -0
- package/dist/claude-settings.js +40 -0
- package/dist/claude.js +176 -0
- package/dist/cli.js +101 -0
- package/dist/control.js +74 -0
- package/dist/hold.js +211 -0
- package/dist/local.js +93 -0
- package/dist/mcp-config.js +25 -0
- package/dist/node-name.js +71 -0
- package/dist/onboarding.js +43 -0
- package/dist/plugin.js +120 -0
- package/dist/provider-flags.js +9 -0
- package/dist/route.js +38 -0
- package/dist/runtime-install.js +97 -0
- package/dist/runtime.js +74 -0
- package/dist/serve.js +150 -0
- package/dist/service.js +242 -0
- package/dist/tail.js +119 -0
- package/dist/update.js +45 -0
- package/dist/version.js +97 -0
- package/package.json +14 -27
- package/runtime/marketplace/.claude-plugin/marketplace.json +12 -0
- package/runtime/marketplace/plugin/.claude-plugin/plugin.json +18 -0
- package/runtime/marketplace/plugin/.mcp.json +1 -0
- package/runtime/marketplace/plugin/bin/guard.mjs +55 -0
- package/runtime/marketplace/plugin/bin/metro-plugin.mjs +122 -0
- package/runtime/marketplace/plugin/bin/session-start.mjs +31 -0
- package/runtime/marketplace/plugin/hooks/hooks.json +25 -0
- package/runtime/marketplace/plugin/orchestrator.md +16 -0
- package/runtime/marketplace/plugin/skills/refresh/SKILL.md +15 -0
- package/runtime/node_modules/@metro-labs/core/package.json +43 -0
- package/runtime/node_modules/@metro-labs/core/src/address.ts +6 -0
- package/runtime/node_modules/@metro-labs/core/src/endpoints.ts +7 -0
- package/runtime/node_modules/@metro-labs/core/src/events.ts +120 -0
- package/runtime/node_modules/@metro-labs/core/src/identity.ts +7 -0
- package/runtime/node_modules/@metro-labs/core/src/ids.ts +21 -0
- package/runtime/node_modules/@metro-labs/core/src/is-record.ts +2 -0
- package/runtime/node_modules/@metro-labs/core/src/lines.ts +87 -0
- package/runtime/node_modules/@metro-labs/core/src/log.ts +42 -0
- package/runtime/node_modules/@metro-labs/core/src/protocol.ts +226 -0
- package/runtime/node_modules/@metro-labs/core/src/secure-fs.ts +57 -0
- package/runtime/node_modules/@metro-labs/core/src/station-names.ts +13 -0
- package/runtime/node_modules/@metro-labs/core/src/stations/account-store.ts +60 -0
- package/runtime/node_modules/@metro-labs/core/src/stations/attachments.ts +196 -0
- package/runtime/node_modules/@metro-labs/core/src/stations/messaging-normalize.ts +88 -0
- package/runtime/node_modules/@metro-labs/core/src/stations/station-runtime.ts +64 -0
- package/runtime/node_modules/@metro-labs/core/src/stations/types.ts +108 -0
- package/runtime/node_modules/@metro-labs/core/src/str.ts +5 -0
- package/runtime/node_modules/@metro-labs/core/src/train-error.ts +53 -0
- package/runtime/node_modules/@metro-labs/core/src/version.ts +2 -0
- package/runtime/node_modules/@metro-labs/daemon/package.json +48 -0
- package/runtime/node_modules/@metro-labs/daemon/src/agents/account-attach.ts +19 -0
- package/runtime/node_modules/@metro-labs/daemon/src/agents/account-routes.ts +70 -0
- package/runtime/node_modules/@metro-labs/daemon/src/agents/accounts-api.ts +365 -0
- package/runtime/node_modules/@metro-labs/daemon/src/agents/admin.ts +43 -0
- package/runtime/node_modules/@metro-labs/daemon/src/agents/allowlist.ts +37 -0
- package/runtime/node_modules/@metro-labs/daemon/src/agents/api.ts +300 -0
- package/runtime/node_modules/@metro-labs/daemon/src/agents/bundle.ts +122 -0
- package/runtime/node_modules/@metro-labs/daemon/src/agents/file-admin.ts +371 -0
- package/runtime/node_modules/@metro-labs/daemon/src/agents/files.ts +155 -0
- package/runtime/node_modules/@metro-labs/daemon/src/agents/keys.ts +45 -0
- package/runtime/node_modules/@metro-labs/daemon/src/agents/map.ts +95 -0
- package/runtime/node_modules/@metro-labs/daemon/src/agents/scope.ts +64 -0
- package/runtime/node_modules/@metro-labs/daemon/src/agents/seed.ts +33 -0
- package/runtime/node_modules/@metro-labs/daemon/src/agents/senders.ts +38 -0
- package/runtime/node_modules/@metro-labs/daemon/src/boot/boot.ts +209 -0
- package/runtime/node_modules/@metro-labs/daemon/src/boot/connect-hint.ts +39 -0
- package/runtime/node_modules/@metro-labs/daemon/src/boot/crash-guard.ts +49 -0
- package/runtime/node_modules/@metro-labs/daemon/src/boot/local-owner.ts +14 -0
- package/runtime/node_modules/@metro-labs/daemon/src/boot/paths.ts +106 -0
- package/runtime/node_modules/@metro-labs/daemon/src/channels/addressed.ts +25 -0
- package/runtime/node_modules/@metro-labs/daemon/src/channels/dedupe.ts +24 -0
- package/runtime/node_modules/@metro-labs/daemon/src/channels/inbound.ts +377 -0
- package/runtime/node_modules/@metro-labs/daemon/src/channels/media-note.ts +85 -0
- package/runtime/node_modules/@metro-labs/daemon/src/channels/pending.ts +75 -0
- package/runtime/node_modules/@metro-labs/daemon/src/channels/relay.ts +129 -0
- package/runtime/node_modules/@metro-labs/daemon/src/channels/webhook-note.ts +51 -0
- package/runtime/node_modules/@metro-labs/daemon/src/claude/api.ts +309 -0
- package/runtime/node_modules/@metro-labs/daemon/src/claude/files.ts +358 -0
- package/runtime/node_modules/@metro-labs/daemon/src/claude/login.ts +169 -0
- package/runtime/node_modules/@metro-labs/daemon/src/claude/onboarding.ts +56 -0
- package/runtime/node_modules/@metro-labs/daemon/src/claude/plugin-install.ts +140 -0
- package/runtime/node_modules/@metro-labs/daemon/src/claude/session-files.ts +90 -0
- package/runtime/node_modules/@metro-labs/daemon/src/claude/session.ts +253 -0
- package/runtime/node_modules/@metro-labs/daemon/src/claude/settings.ts +117 -0
- package/runtime/node_modules/@metro-labs/daemon/src/claude/setup.ts +239 -0
- package/runtime/node_modules/@metro-labs/daemon/src/claude/skills.ts +154 -0
- package/runtime/node_modules/@metro-labs/daemon/src/claude/version.ts +109 -0
- package/runtime/node_modules/@metro-labs/daemon/src/connectors/api.ts +310 -0
- package/runtime/node_modules/@metro-labs/daemon/src/connectors/cli-api.ts +63 -0
- package/runtime/node_modules/@metro-labs/daemon/src/connectors/config.ts +175 -0
- package/runtime/node_modules/@metro-labs/daemon/src/connectors/json.ts +21 -0
- package/runtime/node_modules/@metro-labs/daemon/src/connectors/model.ts +77 -0
- package/runtime/node_modules/@metro-labs/daemon/src/connectors/oauth-client.ts +166 -0
- package/runtime/node_modules/@metro-labs/daemon/src/connectors/oauth-discovery.ts +139 -0
- package/runtime/node_modules/@metro-labs/daemon/src/connectors/oauth-pending.ts +65 -0
- package/runtime/node_modules/@metro-labs/daemon/src/connectors/oauth-routes.ts +171 -0
- package/runtime/node_modules/@metro-labs/daemon/src/connectors/oauth.ts +155 -0
- package/runtime/node_modules/@metro-labs/daemon/src/connectors/plugin-sync.ts +122 -0
- package/runtime/node_modules/@metro-labs/daemon/src/connectors/reach.ts +27 -0
- package/runtime/node_modules/@metro-labs/daemon/src/connectors/relay-target.ts +39 -0
- package/runtime/node_modules/@metro-labs/daemon/src/connectors/relay.ts +296 -0
- package/runtime/node_modules/@metro-labs/daemon/src/connectors/store.ts +286 -0
- package/runtime/node_modules/@metro-labs/daemon/src/connectors/tools.ts +98 -0
- package/runtime/node_modules/@metro-labs/daemon/src/connectors/url.ts +70 -0
- package/runtime/node_modules/@metro-labs/daemon/src/connectors/verify.ts +332 -0
- package/runtime/node_modules/@metro-labs/daemon/src/connectors/watch.ts +54 -0
- package/runtime/node_modules/@metro-labs/daemon/src/files/attach-grant.ts +99 -0
- package/runtime/node_modules/@metro-labs/daemon/src/files/attach-owner.ts +32 -0
- package/runtime/node_modules/@metro-labs/daemon/src/files/attach-serve.ts +121 -0
- package/runtime/node_modules/@metro-labs/daemon/src/files/upload-api.ts +294 -0
- package/runtime/node_modules/@metro-labs/daemon/src/files/upload-store.ts +181 -0
- package/runtime/node_modules/@metro-labs/daemon/src/gateway/bedrock.ts +259 -0
- package/runtime/node_modules/@metro-labs/daemon/src/gateway/codex-auth.ts +231 -0
- package/runtime/node_modules/@metro-labs/daemon/src/gateway/codex-device.ts +91 -0
- package/runtime/node_modules/@metro-labs/daemon/src/gateway/codex-stream.ts +288 -0
- package/runtime/node_modules/@metro-labs/daemon/src/gateway/codex-translate.ts +203 -0
- package/runtime/node_modules/@metro-labs/daemon/src/gateway/codex.ts +231 -0
- package/runtime/node_modules/@metro-labs/daemon/src/gateway/eventstream.ts +123 -0
- package/runtime/node_modules/@metro-labs/daemon/src/gateway/forward.ts +187 -0
- package/runtime/node_modules/@metro-labs/daemon/src/gateway/gateway.ts +229 -0
- package/runtime/node_modules/@metro-labs/daemon/src/gateway/model-api.ts +269 -0
- package/runtime/node_modules/@metro-labs/daemon/src/gateway/model-config.ts +207 -0
- package/runtime/node_modules/@metro-labs/daemon/src/gateway/openrouter.ts +90 -0
- package/runtime/node_modules/@metro-labs/daemon/src/gateway/provider-models.ts +65 -0
- package/runtime/node_modules/@metro-labs/daemon/src/gateway/served.ts +17 -0
- package/runtime/node_modules/@metro-labs/daemon/src/gateway/usage.ts +229 -0
- package/runtime/node_modules/@metro-labs/daemon/src/mcp/accounts.ts +151 -0
- package/runtime/node_modules/@metro-labs/daemon/src/mcp/call-tools.ts +216 -0
- package/runtime/node_modules/@metro-labs/daemon/src/mcp/channel-owner.ts +29 -0
- package/runtime/node_modules/@metro-labs/daemon/src/mcp/ctx.ts +62 -0
- package/runtime/node_modules/@metro-labs/daemon/src/mcp/event-store.ts +91 -0
- package/runtime/node_modules/@metro-labs/daemon/src/mcp/frame-scope.ts +21 -0
- package/runtime/node_modules/@metro-labs/daemon/src/mcp/group-tools.ts +129 -0
- package/runtime/node_modules/@metro-labs/daemon/src/mcp/index.ts +191 -0
- package/runtime/node_modules/@metro-labs/daemon/src/mcp/member-tools.ts +72 -0
- package/runtime/node_modules/@metro-labs/daemon/src/mcp/permission-relay.ts +66 -0
- package/runtime/node_modules/@metro-labs/daemon/src/mcp/raw-get-stream.ts +207 -0
- package/runtime/node_modules/@metro-labs/daemon/src/mcp/request-identity.ts +47 -0
- package/runtime/node_modules/@metro-labs/daemon/src/mcp/session-registry.ts +172 -0
- package/runtime/node_modules/@metro-labs/daemon/src/mcp/session-route.ts +43 -0
- package/runtime/node_modules/@metro-labs/daemon/src/mcp/session.ts +215 -0
- package/runtime/node_modules/@metro-labs/daemon/src/mcp/tool-dispatch.ts +215 -0
- package/runtime/node_modules/@metro-labs/daemon/src/mcp/tool-schemas.ts +400 -0
- package/runtime/node_modules/@metro-labs/daemon/src/mcp/upload-tool.ts +66 -0
- package/runtime/node_modules/@metro-labs/daemon/src/monitor/api.ts +237 -0
- package/runtime/node_modules/@metro-labs/daemon/src/net/tunnel.ts +389 -0
- package/runtime/node_modules/@metro-labs/daemon/src/routes/body.ts +24 -0
- package/runtime/node_modules/@metro-labs/daemon/src/routes/http.ts +372 -0
- package/runtime/node_modules/@metro-labs/daemon/src/routes/identity.ts +54 -0
- package/runtime/node_modules/@metro-labs/daemon/src/routes/local-mode.ts +223 -0
- package/runtime/node_modules/@metro-labs/daemon/src/routes/session-apis.ts +60 -0
- package/runtime/node_modules/@metro-labs/daemon/src/routes/session.ts +29 -0
- package/runtime/node_modules/@metro-labs/daemon/src/routes/threema-callback.ts +103 -0
- package/runtime/node_modules/@metro-labs/daemon/src/server/control.ts +64 -0
- package/runtime/node_modules/@metro-labs/daemon/src/server/machine.ts +61 -0
- package/runtime/node_modules/@metro-labs/daemon/src/server/update.ts +116 -0
- package/runtime/node_modules/@metro-labs/daemon/src/server.ts +2 -0
- package/runtime/node_modules/@metro-labs/daemon/src/stations/attach-inline.ts +100 -0
- package/runtime/node_modules/@metro-labs/daemon/src/stations/attach-interactive.ts +161 -0
- package/runtime/node_modules/@metro-labs/daemon/src/stations/attach-resolve.ts +202 -0
- package/runtime/node_modules/@metro-labs/daemon/src/stations/attach-session.ts +280 -0
- package/runtime/node_modules/@metro-labs/daemon/src/stations/attach-xmtp.ts +111 -0
- package/runtime/node_modules/@metro-labs/daemon/src/stations/attach.ts +248 -0
- package/runtime/node_modules/@metro-labs/daemon/src/stations/materialize.ts +261 -0
- package/runtime/node_modules/@metro-labs/daemon/src/stations/registry.ts +37 -0
- package/runtime/node_modules/@metro-labs/daemon/src/stations/runtime-deps.ts +82 -0
- package/runtime/node_modules/@metro-labs/daemon/src/stations/supervisor-io.ts +97 -0
- package/runtime/node_modules/@metro-labs/daemon/src/stations/supervisor.ts +320 -0
- package/runtime/node_modules/@metro-labs/daemon/src/stations/threema-callbacks.ts +32 -0
- package/runtime/node_modules/@metro-labs/daemon/src/stations/train-call.ts +22 -0
- package/runtime/node_modules/@metro-labs/daemon/src/terminal/api.ts +96 -0
- package/runtime/node_modules/@metro-labs/daemon/src/terminal/socket.ts +123 -0
- package/runtime/node_modules/@metro-labs/daemon/src/terminal/tickets.ts +40 -0
- package/runtime/node_modules/@metro-labs/discord-bot/package.json +28 -0
- package/runtime/node_modules/@metro-labs/discord-bot/src/accounts.ts +118 -0
- package/runtime/node_modules/@metro-labs/discord-bot/src/actions.ts +388 -0
- package/runtime/node_modules/@metro-labs/discord-bot/src/api-base.ts +1 -0
- package/runtime/node_modules/@metro-labs/discord-bot/src/attachments.ts +29 -0
- package/runtime/node_modules/@metro-labs/discord-bot/src/format.ts +220 -0
- package/runtime/node_modules/@metro-labs/discord-bot/src/group-actions.ts +50 -0
- package/runtime/node_modules/@metro-labs/discord-bot/src/group.ts +115 -0
- package/runtime/node_modules/@metro-labs/discord-bot/src/index.ts +127 -0
- package/runtime/node_modules/@metro-labs/discord-bot/src/members.ts +215 -0
- package/runtime/node_modules/@metro-labs/discord-bot/src/send-files.ts +43 -0
- package/runtime/node_modules/@metro-labs/discord-bot/src/station.ts +19 -0
- package/runtime/node_modules/@metro-labs/discord-bot/src/verify.ts +82 -0
- package/runtime/node_modules/@metro-labs/discord-bot/src/voice-speak.ts +195 -0
- package/runtime/node_modules/@metro-labs/discord-bot/src/voice-transcribe.ts +292 -0
- package/runtime/node_modules/@metro-labs/discord-bot/src/voice.ts +197 -0
- package/runtime/node_modules/@metro-labs/discord-bot/src/wire.ts +5 -0
- package/runtime/node_modules/@metro-labs/http/package.json +33 -0
- package/runtime/node_modules/@metro-labs/http/src/api-error.ts +8 -0
- package/runtime/node_modules/@metro-labs/http/src/api-http.ts +95 -0
- package/runtime/node_modules/@metro-labs/http/src/cors.ts +29 -0
- package/runtime/node_modules/@metro-labs/http/src/identity-key.ts +33 -0
- package/runtime/node_modules/@metro-labs/http/src/identity-registry.ts +13 -0
- package/runtime/node_modules/@metro-labs/http/src/mode-api.ts +32 -0
- package/runtime/node_modules/@metro-labs/http/src/return-to.ts +21 -0
- package/runtime/node_modules/@metro-labs/http/src/signed-identity.ts +36 -0
- package/runtime/node_modules/@metro-labs/telegram/package.json +26 -0
- package/runtime/node_modules/@metro-labs/telegram/src/accounts.ts +80 -0
- package/runtime/node_modules/@metro-labs/telegram/src/actions.ts +254 -0
- package/runtime/node_modules/@metro-labs/telegram/src/attachments.ts +87 -0
- package/runtime/node_modules/@metro-labs/telegram/src/client.ts +77 -0
- package/runtime/node_modules/@metro-labs/telegram/src/format.ts +156 -0
- package/runtime/node_modules/@metro-labs/telegram/src/group.ts +261 -0
- package/runtime/node_modules/@metro-labs/telegram/src/history.ts +52 -0
- package/runtime/node_modules/@metro-labs/telegram/src/inbound.ts +78 -0
- package/runtime/node_modules/@metro-labs/telegram/src/index.ts +46 -0
- package/runtime/node_modules/@metro-labs/telegram/src/login.ts +166 -0
- package/runtime/node_modules/@metro-labs/telegram/src/media-actions.ts +80 -0
- package/runtime/node_modules/@metro-labs/telegram/src/members.ts +75 -0
- package/runtime/node_modules/@metro-labs/telegram/src/normalize.ts +22 -0
- package/runtime/node_modules/@metro-labs/telegram/src/reactions.ts +167 -0
- package/runtime/node_modules/@metro-labs/telegram/src/station.ts +24 -0
- package/runtime/node_modules/@metro-labs/telegram/src/types.ts +7 -0
- package/runtime/node_modules/@metro-labs/telegram/src/wire.ts +1 -0
- package/runtime/node_modules/@metro-labs/telegram-bot/package.json +25 -0
- package/runtime/node_modules/@metro-labs/telegram-bot/src/accounts.ts +120 -0
- package/runtime/node_modules/@metro-labs/telegram-bot/src/actions.ts +295 -0
- package/runtime/node_modules/@metro-labs/telegram-bot/src/attachments.ts +128 -0
- package/runtime/node_modules/@metro-labs/telegram-bot/src/format.ts +201 -0
- package/runtime/node_modules/@metro-labs/telegram-bot/src/index.ts +109 -0
- package/runtime/node_modules/@metro-labs/telegram-bot/src/media-actions.ts +166 -0
- package/runtime/node_modules/@metro-labs/telegram-bot/src/members.ts +47 -0
- package/runtime/node_modules/@metro-labs/telegram-bot/src/station.ts +17 -0
- package/runtime/node_modules/@metro-labs/telegram-bot/src/types.ts +58 -0
- package/runtime/node_modules/@metro-labs/telegram-bot/src/verify.ts +38 -0
- package/runtime/node_modules/@metro-labs/telegram-bot/src/wire.ts +3 -0
- package/runtime/node_modules/@metro-labs/threema/package.json +26 -0
- package/runtime/node_modules/@metro-labs/threema/src/accounts.ts +100 -0
- package/runtime/node_modules/@metro-labs/threema/src/actions.ts +219 -0
- package/runtime/node_modules/@metro-labs/threema/src/api.ts +130 -0
- package/runtime/node_modules/@metro-labs/threema/src/crypto.ts +132 -0
- package/runtime/node_modules/@metro-labs/threema/src/format.ts +123 -0
- package/runtime/node_modules/@metro-labs/threema/src/ids.ts +21 -0
- package/runtime/node_modules/@metro-labs/threema/src/index.ts +29 -0
- package/runtime/node_modules/@metro-labs/threema/src/station.ts +10 -0
- package/runtime/node_modules/@metro-labs/threema/src/verify.ts +117 -0
- package/runtime/node_modules/@metro-labs/webhook/package.json +23 -0
- package/runtime/node_modules/@metro-labs/webhook/src/station.ts +37 -0
- package/runtime/node_modules/@metro-labs/whatsapp/package.json +27 -0
- package/runtime/node_modules/@metro-labs/whatsapp/src/accounts.ts +66 -0
- package/runtime/node_modules/@metro-labs/whatsapp/src/ack.ts +101 -0
- package/runtime/node_modules/@metro-labs/whatsapp/src/actions.ts +190 -0
- package/runtime/node_modules/@metro-labs/whatsapp/src/attachments.ts +72 -0
- package/runtime/node_modules/@metro-labs/whatsapp/src/auth-state.ts +129 -0
- package/runtime/node_modules/@metro-labs/whatsapp/src/client.ts +347 -0
- package/runtime/node_modules/@metro-labs/whatsapp/src/delivery.ts +49 -0
- package/runtime/node_modules/@metro-labs/whatsapp/src/format.ts +138 -0
- package/runtime/node_modules/@metro-labs/whatsapp/src/inbound.ts +57 -0
- package/runtime/node_modules/@metro-labs/whatsapp/src/index.ts +40 -0
- package/runtime/node_modules/@metro-labs/whatsapp/src/keys.ts +59 -0
- package/runtime/node_modules/@metro-labs/whatsapp/src/logger.ts +60 -0
- package/runtime/node_modules/@metro-labs/whatsapp/src/login.ts +176 -0
- package/runtime/node_modules/@metro-labs/whatsapp/src/media.ts +84 -0
- package/runtime/node_modules/@metro-labs/whatsapp/src/normalize.ts +22 -0
- package/runtime/node_modules/@metro-labs/whatsapp/src/outbox.ts +38 -0
- package/runtime/node_modules/@metro-labs/whatsapp/src/parse.ts +185 -0
- package/runtime/node_modules/@metro-labs/whatsapp/src/resolve.ts +52 -0
- package/runtime/node_modules/@metro-labs/whatsapp/src/station.ts +18 -0
- package/runtime/node_modules/@metro-labs/whatsapp/src/token-store.ts +98 -0
- package/runtime/node_modules/@metro-labs/whatsapp/src/types.ts +6 -0
- package/runtime/node_modules/@metro-labs/whatsapp/src/wire.ts +5 -0
- package/runtime/node_modules/@metro-labs/xmtp/package.json +33 -0
- package/runtime/node_modules/@metro-labs/xmtp/src/accounts.ts +112 -0
- package/runtime/node_modules/@metro-labs/xmtp/src/actions-close.ts +110 -0
- package/runtime/node_modules/@metro-labs/xmtp/src/actions-conv.ts +299 -0
- package/runtime/node_modules/@metro-labs/xmtp/src/actions-meta.ts +157 -0
- package/runtime/node_modules/@metro-labs/xmtp/src/actions-push.ts +87 -0
- package/runtime/node_modules/@metro-labs/xmtp/src/actions.ts +375 -0
- package/runtime/node_modules/@metro-labs/xmtp/src/attachments.ts +107 -0
- package/runtime/node_modules/@metro-labs/xmtp/src/codecs.ts +171 -0
- package/runtime/node_modules/@metro-labs/xmtp/src/conv-helpers.ts +239 -0
- package/runtime/node_modules/@metro-labs/xmtp/src/emit-core.ts +55 -0
- package/runtime/node_modules/@metro-labs/xmtp/src/emit-payloads.ts +335 -0
- package/runtime/node_modules/@metro-labs/xmtp/src/emit.ts +80 -0
- package/runtime/node_modules/@metro-labs/xmtp/src/group-members.ts +34 -0
- package/runtime/node_modules/@metro-labs/xmtp/src/group.ts +56 -0
- package/runtime/node_modules/@metro-labs/xmtp/src/identity.ts +33 -0
- package/runtime/node_modules/@metro-labs/xmtp/src/index.ts +125 -0
- package/runtime/node_modules/@metro-labs/xmtp/src/labels.ts +157 -0
- package/runtime/node_modules/@metro-labs/xmtp/src/member-args.ts +40 -0
- package/runtime/node_modules/@metro-labs/xmtp/src/push.ts +349 -0
- package/runtime/node_modules/@metro-labs/xmtp/src/station.ts +13 -0
- package/runtime/node_modules/@metro-labs/xmtp/src/tools-handlers.ts +126 -0
- package/runtime/node_modules/@metro-labs/xmtp/src/tools.ts +212 -0
- package/runtime/node_modules/@metro-labs/xmtp/src/transcribe.ts +90 -0
- package/runtime/node_modules/@metro-labs/xmtp/src/verify.ts +84 -0
- package/runtime/node_modules/@metro-labs/xmtp/src/wire.ts +53 -0
- package/runtime/runtime.json +3 -0
- package/runtime/server.ts +1 -0
- package/runtime/stations.json +39 -0
- package/runtime/trains/.keep +0 -0
- package/LICENSE +0 -21
- package/README.md +0 -252
- package/dist/broker.js +0 -202
- package/dist/cache.js +0 -69
- package/dist/cli/actions.js +0 -206
- package/dist/cli/config.js +0 -185
- package/dist/cli/index.js +0 -188
- package/dist/cli/skill.js +0 -62
- package/dist/cli/tail.js +0 -164
- package/dist/cli/util.js +0 -72
- package/dist/cli/webhook.js +0 -81
- package/dist/codex-rc.js +0 -245
- package/dist/dispatcher.js +0 -128
- package/dist/history.js +0 -121
- package/dist/ipc.js +0 -72
- package/dist/log.js +0 -15
- package/dist/monitor.js +0 -194
- package/dist/paths.js +0 -68
- package/dist/registry.js +0 -48
- package/dist/stations/claude.js +0 -45
- package/dist/stations/codex.js +0 -68
- package/dist/stations/discord.js +0 -216
- package/dist/stations/index.js +0 -129
- package/dist/stations/telegram-md.js +0 -34
- package/dist/stations/telegram-upload.js +0 -113
- package/dist/stations/telegram.js +0 -234
- package/dist/stations/webhook.js +0 -103
- package/dist/tunnel.js +0 -64
- package/dist/webhooks.js +0 -41
- package/docs/broker.md +0 -214
- package/docs/monitor.md +0 -163
- package/docs/uri-scheme.md +0 -124
- package/docs/users.md +0 -226
- package/skills/metro/SKILL.md +0 -256
|
@@ -0,0 +1,161 @@
|
|
|
1
|
+
import { StationAttachError } from './attach.js';
|
|
2
|
+
|
|
3
|
+
export const INTERACTIVE_STATIONS = ['telegram', 'whatsapp'] as const;
|
|
4
|
+
|
|
5
|
+
export type InteractiveStation = (typeof INTERACTIVE_STATIONS)[number];
|
|
6
|
+
|
|
7
|
+
export type AttachStep = 'code' | 'password' | 'scan' | 'pair';
|
|
8
|
+
|
|
9
|
+
export interface AttachPrompt {
|
|
10
|
+
step: AttachStep;
|
|
11
|
+
prompt: string;
|
|
12
|
+
qr?: string;
|
|
13
|
+
pairingCode?: string;
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
export interface AttachOutcome {
|
|
17
|
+
config: Record<string, unknown>;
|
|
18
|
+
identity: Record<string, string>;
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
export interface DriverHooks {
|
|
22
|
+
prompt: (p: AttachPrompt) => void;
|
|
23
|
+
done: (o: AttachOutcome) => void;
|
|
24
|
+
fail: (message: string) => void;
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
export interface AttachDriver {
|
|
28
|
+
submit: (input: { code?: unknown; password?: unknown }) => Promise<void>;
|
|
29
|
+
cancel: () => Promise<void>;
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
export interface StartedAttach {
|
|
33
|
+
driver: AttachDriver;
|
|
34
|
+
prompt: AttachPrompt;
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
const CODE_PROMPT =
|
|
38
|
+
'Telegram sent a login code to that number. Enter it here.';
|
|
39
|
+
const PASSWORD_PROMPT =
|
|
40
|
+
'This account has two-step verification. Enter its password.';
|
|
41
|
+
const SCAN_PROMPT =
|
|
42
|
+
'Open WhatsApp on your phone, go to Settings, Linked Devices, Link a Device, and scan this code.';
|
|
43
|
+
const PAIR_PROMPT =
|
|
44
|
+
'Open WhatsApp on your phone, go to Settings, Linked Devices, Link a Device, Link with phone number, and type this code.';
|
|
45
|
+
|
|
46
|
+
export function isInteractiveStation(raw: unknown): raw is InteractiveStation {
|
|
47
|
+
return (
|
|
48
|
+
typeof raw === 'string' &&
|
|
49
|
+
(INTERACTIVE_STATIONS as readonly string[]).includes(raw)
|
|
50
|
+
);
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
function refuse(err: unknown, fallback: string): never {
|
|
54
|
+
throw new StationAttachError(
|
|
55
|
+
err instanceof Error && err.message !== '' ? err.message : fallback,
|
|
56
|
+
400,
|
|
57
|
+
);
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
async function startTelegramUser(
|
|
61
|
+
input: Record<string, unknown>,
|
|
62
|
+
hooks: DriverHooks,
|
|
63
|
+
): Promise<StartedAttach> {
|
|
64
|
+
const { TelegramLogin, TelegramLoginError, validateCredentials } =
|
|
65
|
+
await import('@metro-labs/telegram/login');
|
|
66
|
+
const isOwn = (e: unknown): boolean => e instanceof TelegramLoginError;
|
|
67
|
+
let login;
|
|
68
|
+
try {
|
|
69
|
+
login = new TelegramLogin(
|
|
70
|
+
validateCredentials({
|
|
71
|
+
apiId: input.apiId,
|
|
72
|
+
apiHash: input.apiHash,
|
|
73
|
+
phone: input.phone,
|
|
74
|
+
}),
|
|
75
|
+
);
|
|
76
|
+
await login.requestCode();
|
|
77
|
+
} catch (err) {
|
|
78
|
+
await login?.close();
|
|
79
|
+
refuse(isOwn(err) ? err : null, 'Telegram refused to start the sign-in');
|
|
80
|
+
}
|
|
81
|
+
const settled = login;
|
|
82
|
+
return {
|
|
83
|
+
prompt: { step: 'code', prompt: CODE_PROMPT },
|
|
84
|
+
driver: {
|
|
85
|
+
cancel: () => settled.close(),
|
|
86
|
+
submit: async (values) => {
|
|
87
|
+
try {
|
|
88
|
+
if (typeof values.password === 'string') {
|
|
89
|
+
hooks.done(await settled.submitPassword(values.password));
|
|
90
|
+
return;
|
|
91
|
+
}
|
|
92
|
+
if (typeof values.code !== 'string')
|
|
93
|
+
throw new StationAttachError('enter the login code', 400);
|
|
94
|
+
const result = await settled.submitCode(values.code);
|
|
95
|
+
if (result === 'password') {
|
|
96
|
+
hooks.prompt({ step: 'password', prompt: PASSWORD_PROMPT });
|
|
97
|
+
return;
|
|
98
|
+
}
|
|
99
|
+
hooks.done(result);
|
|
100
|
+
} catch (err) {
|
|
101
|
+
if (err instanceof StationAttachError) throw err;
|
|
102
|
+
refuse(isOwn(err) ? err : null, 'Telegram refused the sign-in');
|
|
103
|
+
}
|
|
104
|
+
},
|
|
105
|
+
},
|
|
106
|
+
};
|
|
107
|
+
}
|
|
108
|
+
|
|
109
|
+
async function startWhatsapp(
|
|
110
|
+
input: Record<string, unknown>,
|
|
111
|
+
hooks: DriverHooks,
|
|
112
|
+
): Promise<StartedAttach> {
|
|
113
|
+
const { WhatsappLogin, WhatsappLoginError, normalizePhone } = await import(
|
|
114
|
+
'@metro-labs/whatsapp/login'
|
|
115
|
+
);
|
|
116
|
+
const isOwn = (e: unknown): boolean => e instanceof WhatsappLoginError;
|
|
117
|
+
let login;
|
|
118
|
+
try {
|
|
119
|
+
login = new WhatsappLogin(normalizePhone(input.phone), {
|
|
120
|
+
onQr: (qr) => {
|
|
121
|
+
hooks.prompt({ step: 'scan', prompt: SCAN_PROMPT, qr });
|
|
122
|
+
},
|
|
123
|
+
onPairingCode: (pairingCode) => {
|
|
124
|
+
hooks.prompt({ step: 'pair', prompt: PAIR_PROMPT, pairingCode });
|
|
125
|
+
},
|
|
126
|
+
onPaired: hooks.done,
|
|
127
|
+
onFailed: hooks.fail,
|
|
128
|
+
});
|
|
129
|
+
await login.start();
|
|
130
|
+
} catch (err) {
|
|
131
|
+
await login?.cancel();
|
|
132
|
+
refuse(isOwn(err) ? err : null, 'WhatsApp refused to start the pairing');
|
|
133
|
+
}
|
|
134
|
+
const settled = login;
|
|
135
|
+
const qrMode = settled.mode === 'qr';
|
|
136
|
+
return {
|
|
137
|
+
prompt: qrMode
|
|
138
|
+
? { step: 'scan', prompt: SCAN_PROMPT }
|
|
139
|
+
: { step: 'pair', prompt: PAIR_PROMPT },
|
|
140
|
+
driver: {
|
|
141
|
+
cancel: () => settled.cancel(),
|
|
142
|
+
submit: () =>
|
|
143
|
+
Promise.reject(
|
|
144
|
+
new StationAttachError(
|
|
145
|
+
'this pairing finishes on your phone, there is nothing to submit here',
|
|
146
|
+
409,
|
|
147
|
+
),
|
|
148
|
+
),
|
|
149
|
+
},
|
|
150
|
+
};
|
|
151
|
+
}
|
|
152
|
+
|
|
153
|
+
export async function startInteractiveAttach(
|
|
154
|
+
station: InteractiveStation,
|
|
155
|
+
input: Record<string, unknown>,
|
|
156
|
+
hooks: DriverHooks,
|
|
157
|
+
): Promise<StartedAttach> {
|
|
158
|
+
return station === 'telegram'
|
|
159
|
+
? startTelegramUser(input, hooks)
|
|
160
|
+
: startWhatsapp(input, hooks);
|
|
161
|
+
}
|
|
@@ -0,0 +1,202 @@
|
|
|
1
|
+
import {
|
|
2
|
+
assertAttachmentSize,
|
|
3
|
+
assertContentLength,
|
|
4
|
+
guessMime,
|
|
5
|
+
saveBufferToCache,
|
|
6
|
+
} from '@metro-labs/core/stations/attachments';
|
|
7
|
+
import {
|
|
8
|
+
assertInlineTotal,
|
|
9
|
+
decodeInline,
|
|
10
|
+
removeInlineTemp,
|
|
11
|
+
splitInlineData,
|
|
12
|
+
writeInlineTemp,
|
|
13
|
+
} from './attach-inline.js';
|
|
14
|
+
import { realpathSync } from 'node:fs';
|
|
15
|
+
import { log } from '@metro-labs/core/log';
|
|
16
|
+
import { readUpload, UPLOAD_TTL_MS } from '../files/upload-store.js';
|
|
17
|
+
import type { CanonicalAttachment } from '@metro-labs/core/stations/types';
|
|
18
|
+
|
|
19
|
+
export interface ResolvedAttachment {
|
|
20
|
+
path: string;
|
|
21
|
+
mime: string;
|
|
22
|
+
name: string;
|
|
23
|
+
bytes: number;
|
|
24
|
+
temp?: string;
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
export interface ResolveOptions {
|
|
28
|
+
allowed?: Set<string>;
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
interface InlineBudget {
|
|
32
|
+
used: number;
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
const FETCH_TIMEOUT_MS = 60_000;
|
|
36
|
+
|
|
37
|
+
const basenameOf = (src: string): string =>
|
|
38
|
+
src.split('?')[0]?.split('#')[0]?.split('/').filter(Boolean).pop() ?? '';
|
|
39
|
+
|
|
40
|
+
const isHttpUrl = (src: string): boolean => /^https?:\/\//i.test(src);
|
|
41
|
+
|
|
42
|
+
const mintId = (): string => Math.random().toString(36).slice(2, 12);
|
|
43
|
+
|
|
44
|
+
const HIDDEN_SEGMENT = /(^|\/)\.[^/]/;
|
|
45
|
+
|
|
46
|
+
function assertReadablePath(path: string): void {
|
|
47
|
+
let real: string;
|
|
48
|
+
try {
|
|
49
|
+
real = realpathSync(path);
|
|
50
|
+
} catch {
|
|
51
|
+
return;
|
|
52
|
+
}
|
|
53
|
+
if (HIDDEN_SEGMENT.test(real))
|
|
54
|
+
throw new Error(
|
|
55
|
+
`attachment path '${path}' resolves into a hidden directory ` +
|
|
56
|
+
`('${real}') and will not be read. \`path\` is read on the daemon ` +
|
|
57
|
+
'machine; put the file somewhere non-hidden or pass `upload` instead.',
|
|
58
|
+
);
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
async function fromPath(
|
|
62
|
+
a: CanonicalAttachment,
|
|
63
|
+
path: string,
|
|
64
|
+
): Promise<ResolvedAttachment> {
|
|
65
|
+
assertReadablePath(path);
|
|
66
|
+
const file = Bun.file(path);
|
|
67
|
+
if (!(await file.exists()))
|
|
68
|
+
throw new Error(
|
|
69
|
+
`attachment path '${path}' does not exist on the metro host. ` +
|
|
70
|
+
'`path` is read on the daemon machine, not on the caller\'s machine; ' +
|
|
71
|
+
'pass `url` instead if the file is not already on the daemon.',
|
|
72
|
+
);
|
|
73
|
+
assertAttachmentSize(file.size);
|
|
74
|
+
return {
|
|
75
|
+
path,
|
|
76
|
+
mime: a.mime ?? guessMime(path),
|
|
77
|
+
name: a.name ?? basenameOf(path) ?? 'attachment',
|
|
78
|
+
bytes: file.size,
|
|
79
|
+
};
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
async function fromUrl(
|
|
83
|
+
a: CanonicalAttachment,
|
|
84
|
+
url: string,
|
|
85
|
+
): Promise<ResolvedAttachment> {
|
|
86
|
+
if (!isHttpUrl(url))
|
|
87
|
+
throw new Error(
|
|
88
|
+
`attachment url '${url}' is not an http(s) url; use \`path\` for a local file`,
|
|
89
|
+
);
|
|
90
|
+
const res = await fetch(url, {
|
|
91
|
+
signal: AbortSignal.timeout(FETCH_TIMEOUT_MS),
|
|
92
|
+
});
|
|
93
|
+
if (!res.ok)
|
|
94
|
+
throw new Error(`attachment fetch failed ${res.status} for '${url}'`);
|
|
95
|
+
assertContentLength(res.headers.get('content-length'));
|
|
96
|
+
const data = new Uint8Array(await res.arrayBuffer());
|
|
97
|
+
const mime =
|
|
98
|
+
a.mime ?? res.headers.get('content-type')?.split(';')[0] ?? guessMime(url);
|
|
99
|
+
const name = a.name ?? basenameOf(url) ?? 'attachment';
|
|
100
|
+
const saved = await saveBufferToCache(data, `out${mintId()}`, 0, {
|
|
101
|
+
mime,
|
|
102
|
+
name,
|
|
103
|
+
});
|
|
104
|
+
return { path: saved.path, mime, name, bytes: data.length };
|
|
105
|
+
}
|
|
106
|
+
|
|
107
|
+
async function fromData(
|
|
108
|
+
a: CanonicalAttachment,
|
|
109
|
+
data: string,
|
|
110
|
+
budget: InlineBudget,
|
|
111
|
+
): Promise<ResolvedAttachment> {
|
|
112
|
+
const name = a.name ?? 'attachment';
|
|
113
|
+
const source = splitInlineData(data);
|
|
114
|
+
const bytes = decodeInline(source.data, `'${name}'`);
|
|
115
|
+
budget.used += bytes.length;
|
|
116
|
+
assertInlineTotal(budget.used);
|
|
117
|
+
const { dir, path } = await writeInlineTemp(bytes, a.name);
|
|
118
|
+
return {
|
|
119
|
+
path,
|
|
120
|
+
mime: a.mime ?? source.mime ?? guessMime(name),
|
|
121
|
+
name,
|
|
122
|
+
bytes: bytes.length,
|
|
123
|
+
temp: dir,
|
|
124
|
+
};
|
|
125
|
+
}
|
|
126
|
+
|
|
127
|
+
const uploadMissing = (id: string): Error =>
|
|
128
|
+
new Error(
|
|
129
|
+
`upload '${id}' is not a live upload of yours. Either it was never created, or its ` +
|
|
130
|
+
'bytes were never pushed to `POST /api/uploads`, or it belongs to another agent, or ' +
|
|
131
|
+
`it expired (an upload lives ${Math.round(UPLOAD_TTL_MS / 60_000)} minutes). ` +
|
|
132
|
+
'Create a new one with `create_upload` and push the file again.',
|
|
133
|
+
);
|
|
134
|
+
|
|
135
|
+
function fromUpload(
|
|
136
|
+
a: CanonicalAttachment,
|
|
137
|
+
id: string,
|
|
138
|
+
allowed: Set<string> | undefined,
|
|
139
|
+
): ResolvedAttachment {
|
|
140
|
+
const rec = allowed === undefined ? undefined : readUpload(id);
|
|
141
|
+
if (rec === undefined || !(allowed?.has(rec.agentId) ?? false))
|
|
142
|
+
throw uploadMissing(id);
|
|
143
|
+
const name = a.name ?? rec.name;
|
|
144
|
+
return { path: rec.path, mime: a.mime ?? rec.mime, name, bytes: rec.bytes };
|
|
145
|
+
}
|
|
146
|
+
|
|
147
|
+
export const SOURCE_KEYS = ['path', 'url', 'data', 'upload'] as const;
|
|
148
|
+
|
|
149
|
+
const sourcesOf = (a: CanonicalAttachment): string[] =>
|
|
150
|
+
SOURCE_KEYS.filter((k) => {
|
|
151
|
+
const v = a[k];
|
|
152
|
+
return typeof v === 'string' && v.length > 0;
|
|
153
|
+
});
|
|
154
|
+
|
|
155
|
+
async function resolveAttachment(
|
|
156
|
+
a: CanonicalAttachment,
|
|
157
|
+
budget: InlineBudget = { used: 0 },
|
|
158
|
+
opts: ResolveOptions = {},
|
|
159
|
+
): Promise<ResolvedAttachment> {
|
|
160
|
+
const sources = sourcesOf(a);
|
|
161
|
+
if (sources.length === 0)
|
|
162
|
+
throw new Error(
|
|
163
|
+
'attachment requires exactly one of `upload`, `data`, `url` or `path`',
|
|
164
|
+
);
|
|
165
|
+
if (sources.length > 1)
|
|
166
|
+
throw new Error(
|
|
167
|
+
`attachment names ${sources.length} sources (${sources
|
|
168
|
+
.map((s) => `\`${s}\``)
|
|
169
|
+
.join(', ')}); pass exactly one of \`upload\`, \`data\`, \`url\` or \`path\``,
|
|
170
|
+
);
|
|
171
|
+
log.info({ source: sources[0] }, 'send: attachment source');
|
|
172
|
+
if (a.upload) return fromUpload(a, a.upload, opts.allowed);
|
|
173
|
+
if (a.data) return fromData(a, a.data, budget);
|
|
174
|
+
if (a.path) return fromPath(a, a.path);
|
|
175
|
+
return fromUrl(a, a.url ?? '');
|
|
176
|
+
}
|
|
177
|
+
|
|
178
|
+
export async function cleanupAttachments(
|
|
179
|
+
atts: ResolvedAttachment[],
|
|
180
|
+
): Promise<void> {
|
|
181
|
+
for (const a of atts) if (a.temp) await removeInlineTemp(a.temp);
|
|
182
|
+
}
|
|
183
|
+
|
|
184
|
+
export async function resolveAttachments(
|
|
185
|
+
atts: CanonicalAttachment[],
|
|
186
|
+
opts: ResolveOptions = {},
|
|
187
|
+
): Promise<ResolvedAttachment[]> {
|
|
188
|
+
const out: ResolvedAttachment[] = [];
|
|
189
|
+
const budget: InlineBudget = { used: 0 };
|
|
190
|
+
for (let i = 0; i < atts.length; i++) {
|
|
191
|
+
const a = atts[i];
|
|
192
|
+
if (!a) continue;
|
|
193
|
+
try {
|
|
194
|
+
out.push(await resolveAttachment(a, budget, opts));
|
|
195
|
+
} catch (e) {
|
|
196
|
+
await cleanupAttachments(out);
|
|
197
|
+
const why = e instanceof Error ? e.message : String(e);
|
|
198
|
+
throw new Error(`attachment ${i + 1} of ${atts.length}: ${why}`);
|
|
199
|
+
}
|
|
200
|
+
}
|
|
201
|
+
return out;
|
|
202
|
+
}
|
|
@@ -0,0 +1,280 @@
|
|
|
1
|
+
import { randomBytes } from 'node:crypto';
|
|
2
|
+
import { ApiError } from '@metro-labs/http/api-error';
|
|
3
|
+
import { errMsg, log } from '@metro-labs/core/log';
|
|
4
|
+
import type { StationName } from '@metro-labs/core/station-names';
|
|
5
|
+
import { ensureStationDeps } from './runtime-deps.js';
|
|
6
|
+
import {
|
|
7
|
+
startInteractiveAttach,
|
|
8
|
+
type AttachDriver,
|
|
9
|
+
type AttachOutcome,
|
|
10
|
+
type AttachPrompt,
|
|
11
|
+
type AttachStep,
|
|
12
|
+
type DriverHooks,
|
|
13
|
+
type InteractiveStation,
|
|
14
|
+
type StartedAttach,
|
|
15
|
+
} from './attach-interactive.js';
|
|
16
|
+
|
|
17
|
+
export type StartAttach = (
|
|
18
|
+
station: InteractiveStation,
|
|
19
|
+
input: Record<string, unknown>,
|
|
20
|
+
hooks: DriverHooks,
|
|
21
|
+
) => Promise<StartedAttach>;
|
|
22
|
+
|
|
23
|
+
export const ATTACH_ID_RE = /^as_[A-Za-z0-9_-]{22}$/;
|
|
24
|
+
|
|
25
|
+
const PENDING_TTL_MS = 5 * 60_000;
|
|
26
|
+
const SETTLED_TTL_MS = 60_000;
|
|
27
|
+
const SWEEP_MS = 15_000;
|
|
28
|
+
const MAX_PER_AGENT = 2;
|
|
29
|
+
const MAX_TOTAL = 40;
|
|
30
|
+
|
|
31
|
+
export interface AttachOwner {
|
|
32
|
+
subject: string;
|
|
33
|
+
agentId: string;
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
export interface AttachView {
|
|
37
|
+
attachId: string;
|
|
38
|
+
station: string;
|
|
39
|
+
status: 'pending' | 'done' | 'failed';
|
|
40
|
+
step: AttachStep | null;
|
|
41
|
+
prompt: string;
|
|
42
|
+
qr: string | null;
|
|
43
|
+
pairingCode: string | null;
|
|
44
|
+
accountId: string | null;
|
|
45
|
+
identity: Record<string, string>;
|
|
46
|
+
activated: boolean;
|
|
47
|
+
error: string | null;
|
|
48
|
+
expiresAt: number;
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
export type CompleteAttach = (
|
|
52
|
+
owner: AttachOwner,
|
|
53
|
+
station: StationName,
|
|
54
|
+
config: Record<string, unknown>,
|
|
55
|
+
) => Promise<{ accountId: string; activated: boolean }>;
|
|
56
|
+
|
|
57
|
+
export type AuthorizeAttach = (owner: AttachOwner) => Promise<void>;
|
|
58
|
+
|
|
59
|
+
export interface AttachSessionDeps {
|
|
60
|
+
authorize: AuthorizeAttach;
|
|
61
|
+
complete: CompleteAttach;
|
|
62
|
+
start?: StartAttach;
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
interface Session {
|
|
66
|
+
owner: AttachOwner;
|
|
67
|
+
driver: AttachDriver | null;
|
|
68
|
+
view: AttachView;
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
const missing = (): ApiError => new ApiError('no such attach session', 404);
|
|
72
|
+
|
|
73
|
+
function newAttachId(): string {
|
|
74
|
+
return `as_${randomBytes(16).toString('base64url')}`;
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
function blankView(attachId: string, station: string): AttachView {
|
|
78
|
+
return {
|
|
79
|
+
attachId,
|
|
80
|
+
station,
|
|
81
|
+
status: 'pending',
|
|
82
|
+
step: null,
|
|
83
|
+
prompt: '',
|
|
84
|
+
qr: null,
|
|
85
|
+
pairingCode: null,
|
|
86
|
+
accountId: null,
|
|
87
|
+
identity: {},
|
|
88
|
+
activated: false,
|
|
89
|
+
error: null,
|
|
90
|
+
expiresAt: Date.now() + PENDING_TTL_MS,
|
|
91
|
+
};
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
export class AttachSessions {
|
|
95
|
+
private sessions = new Map<string, Session>();
|
|
96
|
+
private sweeper: ReturnType<typeof setInterval> | null = null;
|
|
97
|
+
|
|
98
|
+
constructor(private deps: AttachSessionDeps) {}
|
|
99
|
+
|
|
100
|
+
private ensureSweeper(): void {
|
|
101
|
+
if (this.sweeper) return;
|
|
102
|
+
this.sweeper = setInterval(() => {
|
|
103
|
+
this.sweep().catch((err: unknown) => {
|
|
104
|
+
log.warn({ err: errMsg(err) }, 'attach-session: sweep failed');
|
|
105
|
+
});
|
|
106
|
+
}, SWEEP_MS);
|
|
107
|
+
this.sweeper.unref();
|
|
108
|
+
}
|
|
109
|
+
|
|
110
|
+
private own(owner: AttachOwner, attachId: string): Session {
|
|
111
|
+
const session = this.sessions.get(attachId);
|
|
112
|
+
if (
|
|
113
|
+
session?.owner.subject !== owner.subject ||
|
|
114
|
+
session.owner.agentId !== owner.agentId
|
|
115
|
+
)
|
|
116
|
+
throw missing();
|
|
117
|
+
return session;
|
|
118
|
+
}
|
|
119
|
+
|
|
120
|
+
private admit(owner: AttachOwner): void {
|
|
121
|
+
if (this.sessions.size >= MAX_TOTAL)
|
|
122
|
+
throw new ApiError(
|
|
123
|
+
'too many sign-ins are in flight on this daemon, try again shortly',
|
|
124
|
+
429,
|
|
125
|
+
);
|
|
126
|
+
const mine = [...this.sessions.values()].filter(
|
|
127
|
+
(s) => s.owner.agentId === owner.agentId && s.view.status === 'pending',
|
|
128
|
+
);
|
|
129
|
+
if (mine.length >= MAX_PER_AGENT)
|
|
130
|
+
throw new ApiError(
|
|
131
|
+
'this agent already has a sign-in in progress, finish or cancel it first',
|
|
132
|
+
409,
|
|
133
|
+
);
|
|
134
|
+
}
|
|
135
|
+
|
|
136
|
+
private settle(session: Session, patch: Partial<AttachView>): void {
|
|
137
|
+
Object.assign(session.view, patch, {
|
|
138
|
+
qr: null,
|
|
139
|
+
pairingCode: null,
|
|
140
|
+
step: null,
|
|
141
|
+
expiresAt: Date.now() + SETTLED_TTL_MS,
|
|
142
|
+
});
|
|
143
|
+
session.driver = null;
|
|
144
|
+
}
|
|
145
|
+
|
|
146
|
+
private async finish(
|
|
147
|
+
attachId: string,
|
|
148
|
+
station: StationName,
|
|
149
|
+
outcome: AttachOutcome,
|
|
150
|
+
): Promise<void> {
|
|
151
|
+
const session = this.sessions.get(attachId);
|
|
152
|
+
if (session?.view.status !== 'pending') return;
|
|
153
|
+
try {
|
|
154
|
+
const landed = await this.deps.complete(
|
|
155
|
+
session.owner,
|
|
156
|
+
station,
|
|
157
|
+
outcome.config,
|
|
158
|
+
);
|
|
159
|
+
this.settle(session, {
|
|
160
|
+
status: 'done',
|
|
161
|
+
prompt: '',
|
|
162
|
+
identity: outcome.identity,
|
|
163
|
+
accountId: landed.accountId,
|
|
164
|
+
activated: landed.activated,
|
|
165
|
+
});
|
|
166
|
+
log.info(
|
|
167
|
+
{ agentId: session.owner.agentId, station, account: landed.accountId },
|
|
168
|
+
'attach-session: signed in and attached a station account',
|
|
169
|
+
);
|
|
170
|
+
} catch (err) {
|
|
171
|
+
const reason =
|
|
172
|
+
err instanceof ApiError ? err.message : 'could not store the account';
|
|
173
|
+
log.warn({ station, err: errMsg(err) }, 'attach-session: store failed');
|
|
174
|
+
this.settle(session, { status: 'failed', prompt: '', error: reason });
|
|
175
|
+
}
|
|
176
|
+
}
|
|
177
|
+
|
|
178
|
+
private hooksFor(attachId: string, station: StationName) {
|
|
179
|
+
return {
|
|
180
|
+
prompt: (p: AttachPrompt): void => {
|
|
181
|
+
const session = this.sessions.get(attachId);
|
|
182
|
+
if (session?.view.status !== 'pending') return;
|
|
183
|
+
session.view.step = p.step;
|
|
184
|
+
session.view.prompt = p.prompt;
|
|
185
|
+
session.view.qr = p.qr ?? null;
|
|
186
|
+
session.view.pairingCode = p.pairingCode ?? null;
|
|
187
|
+
},
|
|
188
|
+
done: (o: AttachOutcome): void => {
|
|
189
|
+
this.finish(attachId, station, o).catch((err: unknown) => {
|
|
190
|
+
log.warn(
|
|
191
|
+
{ attachId, station, err: errMsg(err) },
|
|
192
|
+
'attach-session: completion failed',
|
|
193
|
+
);
|
|
194
|
+
});
|
|
195
|
+
},
|
|
196
|
+
fail: (message: string): void => {
|
|
197
|
+
const session = this.sessions.get(attachId);
|
|
198
|
+
if (session?.view.status !== 'pending') return;
|
|
199
|
+
this.settle(session, { status: 'failed', prompt: '', error: message });
|
|
200
|
+
},
|
|
201
|
+
};
|
|
202
|
+
}
|
|
203
|
+
|
|
204
|
+
async start(
|
|
205
|
+
owner: AttachOwner,
|
|
206
|
+
station: InteractiveStation,
|
|
207
|
+
input: Record<string, unknown>,
|
|
208
|
+
): Promise<AttachView> {
|
|
209
|
+
await this.deps.authorize(owner);
|
|
210
|
+
this.admit(owner);
|
|
211
|
+
this.ensureSweeper();
|
|
212
|
+
const attachId = newAttachId();
|
|
213
|
+
const session: Session = {
|
|
214
|
+
owner,
|
|
215
|
+
driver: null,
|
|
216
|
+
view: blankView(attachId, station),
|
|
217
|
+
};
|
|
218
|
+
this.sessions.set(attachId, session);
|
|
219
|
+
try {
|
|
220
|
+
ensureStationDeps(station);
|
|
221
|
+
const started = await (this.deps.start ?? startInteractiveAttach)(
|
|
222
|
+
station,
|
|
223
|
+
input,
|
|
224
|
+
this.hooksFor(attachId, station),
|
|
225
|
+
);
|
|
226
|
+
session.driver = started.driver;
|
|
227
|
+
this.hooksFor(attachId, station).prompt(started.prompt);
|
|
228
|
+
} catch (err) {
|
|
229
|
+
this.sessions.delete(attachId);
|
|
230
|
+
throw err;
|
|
231
|
+
}
|
|
232
|
+
log.info(
|
|
233
|
+
{ agentId: owner.agentId, station, attachId },
|
|
234
|
+
'attach-session: started',
|
|
235
|
+
);
|
|
236
|
+
return { ...session.view };
|
|
237
|
+
}
|
|
238
|
+
|
|
239
|
+
view(owner: AttachOwner, attachId: string): AttachView {
|
|
240
|
+
return { ...this.own(owner, attachId).view };
|
|
241
|
+
}
|
|
242
|
+
|
|
243
|
+
async submit(
|
|
244
|
+
owner: AttachOwner,
|
|
245
|
+
attachId: string,
|
|
246
|
+
input: { code?: unknown; password?: unknown },
|
|
247
|
+
): Promise<AttachView> {
|
|
248
|
+
const session = this.own(owner, attachId);
|
|
249
|
+
const driver = session.driver;
|
|
250
|
+
if (session.view.status !== 'pending' || !driver)
|
|
251
|
+
throw new ApiError('this sign-in is already finished', 409);
|
|
252
|
+
await driver.submit(input);
|
|
253
|
+
return { ...session.view };
|
|
254
|
+
}
|
|
255
|
+
|
|
256
|
+
async cancel(owner: AttachOwner, attachId: string): Promise<void> {
|
|
257
|
+
const session = this.own(owner, attachId);
|
|
258
|
+
this.sessions.delete(attachId);
|
|
259
|
+
await session.driver?.cancel().catch(() => undefined);
|
|
260
|
+
log.info({ attachId }, 'attach-session: cancelled');
|
|
261
|
+
}
|
|
262
|
+
|
|
263
|
+
async sweep(now = Date.now()): Promise<void> {
|
|
264
|
+
for (const [attachId, session] of [...this.sessions]) {
|
|
265
|
+
if (session.view.expiresAt > now) continue;
|
|
266
|
+
this.sessions.delete(attachId);
|
|
267
|
+
const driver = session.driver;
|
|
268
|
+
session.driver = null;
|
|
269
|
+
if (!driver) continue;
|
|
270
|
+
log.info({ attachId }, 'attach-session: expired, credentials discarded');
|
|
271
|
+
await driver.cancel().catch(() => undefined);
|
|
272
|
+
}
|
|
273
|
+
}
|
|
274
|
+
|
|
275
|
+
async stop(): Promise<void> {
|
|
276
|
+
if (this.sweeper) clearInterval(this.sweeper);
|
|
277
|
+
this.sweeper = null;
|
|
278
|
+
await this.sweep(Number.MAX_SAFE_INTEGER);
|
|
279
|
+
}
|
|
280
|
+
}
|