@openparachute/agent 0.1.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/.claude/scheduled_tasks.lock +1 -0
- package/.claude/settings.json +5 -0
- package/.claude/skills/add-atomic-chat-tool/SKILL.md +243 -0
- package/.claude/skills/add-atomic-chat-tool/atomic-chat-mcp-stdio.ts +229 -0
- package/.claude/skills/add-codex/SKILL.md +161 -0
- package/.claude/skills/add-dashboard/SKILL.md +138 -0
- package/.claude/skills/add-dashboard/resources/dashboard-pusher.ts +495 -0
- package/.claude/skills/add-emacs/SKILL.md +296 -0
- package/.claude/skills/add-gcal-tool/SKILL.md +210 -0
- package/.claude/skills/add-gchat/REMOVE.md +6 -0
- package/.claude/skills/add-gchat/SKILL.md +92 -0
- package/.claude/skills/add-gchat/VERIFY.md +3 -0
- package/.claude/skills/add-github/REMOVE.md +6 -0
- package/.claude/skills/add-github/SKILL.md +148 -0
- package/.claude/skills/add-github/VERIFY.md +3 -0
- package/.claude/skills/add-gmail-tool/SKILL.md +229 -0
- package/.claude/skills/add-imessage/REMOVE.md +6 -0
- package/.claude/skills/add-imessage/SKILL.md +113 -0
- package/.claude/skills/add-imessage/VERIFY.md +3 -0
- package/.claude/skills/add-karpathy-llm-wiki/SKILL.md +110 -0
- package/.claude/skills/add-karpathy-llm-wiki/llm-wiki.md +75 -0
- package/.claude/skills/add-linear/REMOVE.md +6 -0
- package/.claude/skills/add-linear/SKILL.md +168 -0
- package/.claude/skills/add-linear/VERIFY.md +3 -0
- package/.claude/skills/add-macos-statusbar/SKILL.md +133 -0
- package/.claude/skills/add-macos-statusbar/add/src/statusbar.swift +147 -0
- package/.claude/skills/add-matrix/REMOVE.md +6 -0
- package/.claude/skills/add-matrix/SKILL.md +148 -0
- package/.claude/skills/add-matrix/VERIFY.md +3 -0
- package/.claude/skills/add-ollama-provider/SKILL.md +179 -0
- package/.claude/skills/add-ollama-tool/SKILL.md +193 -0
- package/.claude/skills/add-opencode/SKILL.md +229 -0
- package/.claude/skills/add-parallel/SKILL.md +290 -0
- package/.claude/skills/add-resend/REMOVE.md +6 -0
- package/.claude/skills/add-resend/SKILL.md +93 -0
- package/.claude/skills/add-resend/VERIFY.md +3 -0
- package/.claude/skills/add-signal/REMOVE.md +13 -0
- package/.claude/skills/add-signal/SKILL.md +318 -0
- package/.claude/skills/add-signal/VERIFY.md +5 -0
- package/.claude/skills/add-slack/REMOVE.md +6 -0
- package/.claude/skills/add-slack/SKILL.md +112 -0
- package/.claude/skills/add-slack/VERIFY.md +3 -0
- package/.claude/skills/add-teams/REMOVE.md +6 -0
- package/.claude/skills/add-teams/SKILL.md +207 -0
- package/.claude/skills/add-teams/VERIFY.md +3 -0
- package/.claude/skills/add-vercel/SKILL.md +147 -0
- package/.claude/skills/add-vercel/container-skills/vercel-cli/SKILL.md +103 -0
- package/.claude/skills/add-webex/REMOVE.md +6 -0
- package/.claude/skills/add-webex/SKILL.md +88 -0
- package/.claude/skills/add-webex/VERIFY.md +3 -0
- package/.claude/skills/add-wechat/REMOVE.md +49 -0
- package/.claude/skills/add-wechat/SKILL.md +170 -0
- package/.claude/skills/add-wechat/scripts/wire-dm.ts +172 -0
- package/.claude/skills/add-whatsapp/SKILL.md +264 -0
- package/.claude/skills/add-whatsapp-cloud/REMOVE.md +6 -0
- package/.claude/skills/add-whatsapp-cloud/SKILL.md +95 -0
- package/.claude/skills/add-whatsapp-cloud/VERIFY.md +3 -0
- package/.claude/skills/claw/SKILL.md +131 -0
- package/.claude/skills/claw/scripts/claw +374 -0
- package/.claude/skills/convert-to-apple-container/SKILL.md +212 -0
- package/.claude/skills/customize/SKILL.md +110 -0
- package/.claude/skills/debug/SKILL.md +349 -0
- package/.claude/skills/get-qodo-rules/SKILL.md +122 -0
- package/.claude/skills/get-qodo-rules/references/output-format.md +41 -0
- package/.claude/skills/get-qodo-rules/references/pagination.md +33 -0
- package/.claude/skills/get-qodo-rules/references/repository-scope.md +26 -0
- package/.claude/skills/init-first-agent/SKILL.md +120 -0
- package/.claude/skills/init-onecli/SKILL.md +270 -0
- package/.claude/skills/manage-channels/SKILL.md +87 -0
- package/.claude/skills/manage-mounts/SKILL.md +47 -0
- package/.claude/skills/migrate-from-openclaw/MIGRATE_CRONS.md +100 -0
- package/.claude/skills/migrate-from-openclaw/SKILL.md +447 -0
- package/.claude/skills/migrate-from-openclaw/scripts/discover-openclaw.ts +734 -0
- package/.claude/skills/migrate-from-openclaw/scripts/extract-channel-credentials.ts +476 -0
- package/.claude/skills/migrate-nanoclaw/SKILL.md +484 -0
- package/.claude/skills/migrate-nanoclaw/diagnostics.md +51 -0
- package/.claude/skills/qodo-pr-resolver/SKILL.md +326 -0
- package/.claude/skills/qodo-pr-resolver/resources/providers.md +329 -0
- package/.claude/skills/update-nanoclaw/SKILL.md +243 -0
- package/.claude/skills/update-nanoclaw/diagnostics.md +48 -0
- package/.claude/skills/update-skills/SKILL.md +130 -0
- package/.claude/skills/use-native-credential-proxy/SKILL.md +167 -0
- package/.claude/skills/x-integration/SKILL.md +417 -0
- package/.claude/skills/x-integration/agent.ts +243 -0
- package/.claude/skills/x-integration/host.ts +155 -0
- package/.claude/skills/x-integration/lib/browser.ts +148 -0
- package/.claude/skills/x-integration/lib/config.ts +62 -0
- package/.claude/skills/x-integration/scripts/like.ts +56 -0
- package/.claude/skills/x-integration/scripts/post.ts +66 -0
- package/.claude/skills/x-integration/scripts/quote.ts +80 -0
- package/.claude/skills/x-integration/scripts/reply.ts +74 -0
- package/.claude/skills/x-integration/scripts/retweet.ts +62 -0
- package/.claude/skills/x-integration/scripts/setup.ts +87 -0
- package/.github/CODEOWNERS +10 -0
- package/.github/PULL_REQUEST_TEMPLATE.md +18 -0
- package/.github/workflows/bump-version.yml +35 -0
- package/.github/workflows/ci.yml +39 -0
- package/.github/workflows/label-pr.yml +40 -0
- package/.github/workflows/update-tokens.yml +43 -0
- package/.husky/pre-commit +1 -0
- package/.mcp.json +3 -0
- package/.nvmrc +1 -0
- package/.parachute/module.json +14 -0
- package/.prettierrc +4 -0
- package/CHANGELOG.md +215 -0
- package/CLAUDE.md +307 -0
- package/CODE_OF_CONDUCT.md +128 -0
- package/CONTRIBUTING.md +159 -0
- package/CONTRIBUTORS.md +26 -0
- package/LICENSE +21 -0
- package/README.md +190 -0
- package/README_ja.md +194 -0
- package/README_zh.md +194 -0
- package/assets/nanoclaw-favicon.png +0 -0
- package/assets/nanoclaw-icon.png +0 -0
- package/assets/nanoclaw-logo-dark.png +0 -0
- package/assets/nanoclaw-logo.png +0 -0
- package/assets/nanoclaw-profile.jpeg +0 -0
- package/assets/nanoclaw-sales.png +0 -0
- package/assets/social-preview.jpg +0 -0
- package/config-examples/mount-allowlist.json +25 -0
- package/container/.dockerignore +2 -0
- package/container/CLAUDE.md +21 -0
- package/container/Dockerfile +121 -0
- package/container/agent-runner/bun.lock +243 -0
- package/container/agent-runner/package.json +22 -0
- package/container/agent-runner/scripts/sdk-signal-probe.ts +169 -0
- package/container/agent-runner/src/config.ts +55 -0
- package/container/agent-runner/src/db/connection.ts +267 -0
- package/container/agent-runner/src/db/index.ts +20 -0
- package/container/agent-runner/src/db/messages-in.ts +138 -0
- package/container/agent-runner/src/db/messages-out.ts +143 -0
- package/container/agent-runner/src/db/session-routing.ts +30 -0
- package/container/agent-runner/src/db/session-state.test.ts +100 -0
- package/container/agent-runner/src/db/session-state.ts +79 -0
- package/container/agent-runner/src/destinations.ts +135 -0
- package/container/agent-runner/src/formatter.test.ts +167 -0
- package/container/agent-runner/src/formatter.ts +260 -0
- package/container/agent-runner/src/index.ts +110 -0
- package/container/agent-runner/src/integration.test.ts +121 -0
- package/container/agent-runner/src/mcp-tools/agents.instructions.md +26 -0
- package/container/agent-runner/src/mcp-tools/agents.ts +66 -0
- package/container/agent-runner/src/mcp-tools/core.instructions.md +27 -0
- package/container/agent-runner/src/mcp-tools/core.ts +262 -0
- package/container/agent-runner/src/mcp-tools/index.ts +22 -0
- package/container/agent-runner/src/mcp-tools/interactive.instructions.md +22 -0
- package/container/agent-runner/src/mcp-tools/interactive.ts +169 -0
- package/container/agent-runner/src/mcp-tools/scheduling.instructions.md +40 -0
- package/container/agent-runner/src/mcp-tools/scheduling.ts +299 -0
- package/container/agent-runner/src/mcp-tools/self-mod.instructions.md +25 -0
- package/container/agent-runner/src/mcp-tools/self-mod.ts +120 -0
- package/container/agent-runner/src/mcp-tools/server.ts +54 -0
- package/container/agent-runner/src/mcp-tools/types.ts +6 -0
- package/container/agent-runner/src/poll-loop.test.ts +248 -0
- package/container/agent-runner/src/poll-loop.ts +437 -0
- package/container/agent-runner/src/providers/claude.ts +379 -0
- package/container/agent-runner/src/providers/factory.test.ts +19 -0
- package/container/agent-runner/src/providers/factory.ts +13 -0
- package/container/agent-runner/src/providers/index.ts +6 -0
- package/container/agent-runner/src/providers/mock.ts +77 -0
- package/container/agent-runner/src/providers/provider-registry.ts +33 -0
- package/container/agent-runner/src/providers/types.ts +82 -0
- package/container/agent-runner/src/scheduling/task-script.ts +121 -0
- package/container/agent-runner/src/timezone.test.ts +93 -0
- package/container/agent-runner/src/timezone.ts +107 -0
- package/container/agent-runner/tsconfig.json +14 -0
- package/container/build.sh +48 -0
- package/container/entrypoint.sh +16 -0
- package/container/skills/agent-browser/SKILL.md +159 -0
- package/container/skills/frontend-engineer/SKILL.md +157 -0
- package/container/skills/self-customize/SKILL.md +87 -0
- package/container/skills/slack-formatting/SKILL.md +94 -0
- package/container/skills/vercel-cli/SKILL.md +111 -0
- package/container/skills/welcome/SKILL.md +85 -0
- package/docs/APPLE-CONTAINER-NETWORKING.md +90 -0
- package/docs/BRANCH-FORK-MAINTENANCE.md +81 -0
- package/docs/README.md +25 -0
- package/docs/SDK_DEEP_DIVE.md +643 -0
- package/docs/SECURITY.md +162 -0
- package/docs/agent-runner-details.md +749 -0
- package/docs/api-details.md +365 -0
- package/docs/architecture-diagram.html +422 -0
- package/docs/architecture-diagram.md +215 -0
- package/docs/architecture.md +751 -0
- package/docs/audit/2026-04-30-channel-endpoint-audit.md +36 -0
- package/docs/build-and-runtime.md +80 -0
- package/docs/cross-mount-stress/README.md +112 -0
- package/docs/cross-mount-stress/container-writer-retry.mjs +55 -0
- package/docs/cross-mount-stress/container-writer-slow.mjs +42 -0
- package/docs/cross-mount-stress/container-writer.mjs +47 -0
- package/docs/cross-mount-stress/host-writer-retry.mjs +55 -0
- package/docs/cross-mount-stress/host-writer-slow.mjs +43 -0
- package/docs/cross-mount-stress/host-writer.mjs +47 -0
- package/docs/db-central.md +316 -0
- package/docs/db-session.md +183 -0
- package/docs/db.md +119 -0
- package/docs/design/2026-04-29-vault-management-ui.md +231 -0
- package/docs/design/2026-04-30-channel-wiring-rework.md +234 -0
- package/docs/design/2026-05-01-channel-wiring-approvals-deep-dive.md +272 -0
- package/docs/design/2026-05-02-channel-policy-and-approval-routing.md +250 -0
- package/docs/docker-sandboxes.md +359 -0
- package/docs/isolation-model.md +88 -0
- package/docs/ollama.md +79 -0
- package/docs/parachute-integration.md +109 -0
- package/docs/post-night-rebirth-reflections.md +151 -0
- package/eslint.config.js +32 -0
- package/package.json +54 -0
- package/pnpm-workspace.yaml +8 -0
- package/repo-tokens/README.md +113 -0
- package/repo-tokens/action.yml +186 -0
- package/repo-tokens/badge.svg +23 -0
- package/repo-tokens/examples/green.svg +14 -0
- package/repo-tokens/examples/red.svg +14 -0
- package/repo-tokens/examples/yellow-green.svg +14 -0
- package/repo-tokens/examples/yellow.svg +14 -0
- package/scripts/chat.ts +101 -0
- package/scripts/cleanup-sessions.sh +150 -0
- package/scripts/init-cli-agent.ts +171 -0
- package/scripts/init-first-agent.ts +377 -0
- package/scripts/parachute.ts +158 -0
- package/scripts/run-migrations.ts +105 -0
- package/scripts/sanity-live-poll.ts +95 -0
- package/scripts/seed-discord.ts +79 -0
- package/scripts/test-v2-agent.ts +106 -0
- package/scripts/test-v2-channel-e2e.ts +265 -0
- package/scripts/test-v2-host.ts +184 -0
- package/src/channels/adapter.ts +214 -0
- package/src/channels/ask-question.ts +46 -0
- package/src/channels/channel-registry.test.ts +421 -0
- package/src/channels/channel-registry.ts +313 -0
- package/src/channels/chat-sdk-bridge.test.ts +84 -0
- package/src/channels/chat-sdk-bridge.ts +652 -0
- package/src/channels/cli.ts +276 -0
- package/src/channels/discord.ts +90 -0
- package/src/channels/index.ts +17 -0
- package/src/channels/telegram-markdown-sanitize.test.ts +78 -0
- package/src/channels/telegram-markdown-sanitize.ts +55 -0
- package/src/channels/telegram-pairing.test.ts +254 -0
- package/src/channels/telegram-pairing.ts +339 -0
- package/src/channels/telegram.ts +279 -0
- package/src/channels/trust-hint.test.ts +48 -0
- package/src/channels/trust-hint.ts +75 -0
- package/src/claude-md-compose.migrate.test.ts +64 -0
- package/src/claude-md-compose.ts +205 -0
- package/src/command-gate.ts +63 -0
- package/src/config.test.ts +93 -0
- package/src/config.ts +108 -0
- package/src/container-config.ts +167 -0
- package/src/container-runner.test.ts +32 -0
- package/src/container-runner.ts +576 -0
- package/src/container-runtime.test.ts +169 -0
- package/src/container-runtime.ts +92 -0
- package/src/db/_bun-sqlite-shim.ts +88 -0
- package/src/db/agent-activity.test.ts +155 -0
- package/src/db/agent-activity.ts +121 -0
- package/src/db/agent-groups.ts +77 -0
- package/src/db/connection.migrate.test.ts +143 -0
- package/src/db/connection.ts +224 -0
- package/src/db/db-v2.test.ts +440 -0
- package/src/db/dropped-messages.ts +44 -0
- package/src/db/index.ts +40 -0
- package/src/db/messaging-groups.ts +252 -0
- package/src/db/migrations/001-initial.ts +112 -0
- package/src/db/migrations/002-chat-sdk-state.ts +36 -0
- package/src/db/migrations/008-dropped-messages.ts +27 -0
- package/src/db/migrations/009-drop-pending-credentials.ts +13 -0
- package/src/db/migrations/010-engage-modes.ts +103 -0
- package/src/db/migrations/011-pending-sender-approvals.ts +40 -0
- package/src/db/migrations/012-channel-registration.ts +48 -0
- package/src/db/migrations/013-approval-render-metadata.ts +27 -0
- package/src/db/migrations/014-secrets.ts +44 -0
- package/src/db/migrations/015-secrets-drop-host-pattern.ts +18 -0
- package/src/db/migrations/016-secret-assignments.ts +30 -0
- package/src/db/migrations/017-agent-activity.ts +40 -0
- package/src/db/migrations/018-oauth-app-configs.ts +34 -0
- package/src/db/migrations/019-oauth-app-connections.ts +48 -0
- package/src/db/migrations/020-agent-app-connections.ts +28 -0
- package/src/db/migrations/021-pending-oauth-states.ts +35 -0
- package/src/db/migrations/022-app-connections-provider.ts +25 -0
- package/src/db/migrations/023-agent-group-secret-mode.test.ts +124 -0
- package/src/db/migrations/023-agent-group-secret-mode.ts +65 -0
- package/src/db/migrations/024-collapse-approvals.test.ts +249 -0
- package/src/db/migrations/024-collapse-approvals.ts +182 -0
- package/src/db/migrations/025-secret-mode-check.test.ts +155 -0
- package/src/db/migrations/025-secret-mode-check.ts +49 -0
- package/src/db/migrations/026-user-dms-bot-id.test.ts +116 -0
- package/src/db/migrations/026-user-dms-bot-id.ts +54 -0
- package/src/db/migrations/027-provider-credentials.ts +41 -0
- package/src/db/migrations/_test-helpers.ts +41 -0
- package/src/db/migrations/index.ts +127 -0
- package/src/db/migrations/module-agent-to-agent-destinations.ts +84 -0
- package/src/db/migrations/module-approvals-pending-approvals.ts +42 -0
- package/src/db/migrations/module-approvals-title-options.ts +40 -0
- package/src/db/schema.ts +258 -0
- package/src/db/session-db.test.ts +93 -0
- package/src/db/session-db.ts +325 -0
- package/src/db/sessions.ts +241 -0
- package/src/delivery.test.ts +148 -0
- package/src/delivery.ts +445 -0
- package/src/env.ts +74 -0
- package/src/group-folder.test.ts +35 -0
- package/src/group-folder.ts +44 -0
- package/src/group-init.ts +92 -0
- package/src/host-core.test.ts +456 -0
- package/src/host-sweep.test.ts +146 -0
- package/src/host-sweep.ts +287 -0
- package/src/index.ts +227 -0
- package/src/install-slug.ts +33 -0
- package/src/log.test.ts +81 -0
- package/src/log.ts +117 -0
- package/src/mcp/http.ts +72 -0
- package/src/mcp/server.ts +92 -0
- package/src/mcp/stdio.ts +51 -0
- package/src/mcp/tools/activity.ts +88 -0
- package/src/mcp/tools/agent-groups.ts +183 -0
- package/src/mcp/tools/approvals.ts +122 -0
- package/src/mcp/tools/channels.ts +199 -0
- package/src/mcp/tools/index.ts +27 -0
- package/src/mcp/tools/oauth.ts +48 -0
- package/src/mcp/tools/secrets.ts +169 -0
- package/src/mcp/tools/sessions.ts +135 -0
- package/src/mcp/types.ts +51 -0
- package/src/modules/agent-to-agent/agent-route.test.ts +46 -0
- package/src/modules/agent-to-agent/agent-route.ts +223 -0
- package/src/modules/agent-to-agent/create-agent.ts +127 -0
- package/src/modules/agent-to-agent/db/agent-destinations.ts +135 -0
- package/src/modules/agent-to-agent/index.ts +22 -0
- package/src/modules/agent-to-agent/write-destinations.ts +59 -0
- package/src/modules/approvals/agent.md +45 -0
- package/src/modules/approvals/index.ts +21 -0
- package/src/modules/approvals/picks.test.ts +291 -0
- package/src/modules/approvals/primitive.ts +279 -0
- package/src/modules/approvals/project.md +27 -0
- package/src/modules/approvals/response-handler.ts +87 -0
- package/src/modules/index.ts +24 -0
- package/src/modules/interactive/agent.md +21 -0
- package/src/modules/interactive/index.ts +69 -0
- package/src/modules/interactive/project.md +12 -0
- package/src/modules/mount-security/index.ts +448 -0
- package/src/modules/mount-security/migrate.test.ts +91 -0
- package/src/modules/permissions/access.ts +28 -0
- package/src/modules/permissions/channel-approval.test.ts +389 -0
- package/src/modules/permissions/channel-approval.ts +188 -0
- package/src/modules/permissions/db/agent-group-members.ts +44 -0
- package/src/modules/permissions/db/pending-channel-approvals.test.ts +86 -0
- package/src/modules/permissions/db/pending-channel-approvals.ts +66 -0
- package/src/modules/permissions/db/pending-sender-approvals.ts +60 -0
- package/src/modules/permissions/db/user-dms.ts +58 -0
- package/src/modules/permissions/db/user-roles.ts +85 -0
- package/src/modules/permissions/db/users.ts +38 -0
- package/src/modules/permissions/index.ts +421 -0
- package/src/modules/permissions/permissions.test.ts +358 -0
- package/src/modules/permissions/sender-approval.test.ts +470 -0
- package/src/modules/permissions/sender-approval.ts +165 -0
- package/src/modules/permissions/user-dm.ts +200 -0
- package/src/modules/provider-credentials/db.ts +121 -0
- package/src/modules/provider-credentials/index.ts +12 -0
- package/src/modules/provider-credentials/spawn.test.ts +206 -0
- package/src/modules/provider-credentials/spawn.ts +114 -0
- package/src/modules/scheduling/actions.ts +113 -0
- package/src/modules/scheduling/db.test.ts +282 -0
- package/src/modules/scheduling/db.ts +148 -0
- package/src/modules/scheduling/index.ts +34 -0
- package/src/modules/scheduling/recurrence.test.ts +98 -0
- package/src/modules/scheduling/recurrence.ts +54 -0
- package/src/modules/self-mod/agent.md +30 -0
- package/src/modules/self-mod/apply.ts +85 -0
- package/src/modules/self-mod/index.ts +30 -0
- package/src/modules/self-mod/project.md +39 -0
- package/src/modules/self-mod/request.ts +91 -0
- package/src/modules/typing/index.ts +165 -0
- package/src/oauth/agent-app-connections.ts +103 -0
- package/src/oauth/app-configs.test.ts +64 -0
- package/src/oauth/app-configs.ts +114 -0
- package/src/oauth/app-connections.test.ts +109 -0
- package/src/oauth/app-connections.ts +178 -0
- package/src/oauth/crypto.ts +56 -0
- package/src/oauth/flow.ts +104 -0
- package/src/oauth/providers/google.test.ts +38 -0
- package/src/oauth/providers/google.ts +46 -0
- package/src/oauth/providers/index.ts +48 -0
- package/src/oauth/state-store.test.ts +54 -0
- package/src/oauth/state-store.ts +93 -0
- package/src/parachute/README.md +27 -0
- package/src/parachute/create-agent.test.ts +83 -0
- package/src/parachute/create-agent.ts +122 -0
- package/src/parachute/group-status.test.ts +165 -0
- package/src/parachute/group-status.ts +136 -0
- package/src/parachute/types.ts +41 -0
- package/src/parachute/vault-mcp.test.ts +251 -0
- package/src/parachute/vault-mcp.ts +232 -0
- package/src/platform-id.test.ts +104 -0
- package/src/platform-id.ts +109 -0
- package/src/providers/index.ts +6 -0
- package/src/providers/provider-container-registry.ts +58 -0
- package/src/response-registry.ts +45 -0
- package/src/router.ts +530 -0
- package/src/secrets/crypto.test.ts +45 -0
- package/src/secrets/crypto.ts +55 -0
- package/src/secrets/index.ts +355 -0
- package/src/secrets/master-key.ts +70 -0
- package/src/secrets/secrets.test.ts +354 -0
- package/src/session-manager.migrate.test.ts +59 -0
- package/src/session-manager.ts +433 -0
- package/src/startup-bootstrap.test.ts +226 -0
- package/src/startup-bootstrap.ts +207 -0
- package/src/state-sqlite.ts +182 -0
- package/src/timezone.test.ts +64 -0
- package/src/timezone.ts +37 -0
- package/src/types.ts +230 -0
- package/src/web/auth.test.ts +335 -0
- package/src/web/auth.ts +214 -0
- package/src/web/discord-validate.test.ts +77 -0
- package/src/web/discord-validate.ts +88 -0
- package/src/web/hub-discovery.test.ts +98 -0
- package/src/web/hub-discovery.ts +69 -0
- package/src/web/routes/activity.ts +106 -0
- package/src/web/routes/agent-provider.test.ts +282 -0
- package/src/web/routes/agent-provider.ts +309 -0
- package/src/web/routes/approvals.ts +185 -0
- package/src/web/routes/apps.ts +434 -0
- package/src/web/routes/channels-mg-detail.test.ts +324 -0
- package/src/web/routes/channels-mga-detail.test.ts +425 -0
- package/src/web/routes/channels.ts +489 -0
- package/src/web/routes/oauth-providers.ts +42 -0
- package/src/web/routes/secrets.test.ts +175 -0
- package/src/web/routes/secrets.ts +282 -0
- package/src/web/routes/sessions.ts +123 -0
- package/src/web/routes/settings.test.ts +106 -0
- package/src/web/routes/settings.ts +247 -0
- package/src/web/routes/setup-status.ts +205 -0
- package/src/web/routes/vaults.test.ts +389 -0
- package/src/web/routes/vaults.ts +225 -0
- package/src/web/server-version.test.ts +16 -0
- package/src/web/server.ts +1003 -0
- package/src/web/services-manifest.test.ts +120 -0
- package/src/web/services-manifest.ts +61 -0
- package/src/web/static-serve.test.ts +255 -0
- package/src/web/static-serve.ts +104 -0
- package/src/web/telegram-validate.test.ts +116 -0
- package/src/web/telegram-validate.ts +107 -0
- package/src/web/vault-proxy.test.ts +214 -0
- package/src/web/vault-proxy.ts +120 -0
- package/src/web/wire-channel.ts +181 -0
- package/src/webhook-server.ts +134 -0
- package/tsconfig.json +21 -0
- package/vitest.config.ts +18 -0
- package/web/README.md +63 -0
- package/web/ui/index.html +13 -0
- package/web/ui/package.json +35 -0
- package/web/ui/pnpm-lock.yaml +2164 -0
- package/web/ui/scripts/verify-base.mjs +31 -0
- package/web/ui/src/App.tsx +88 -0
- package/web/ui/src/components/ActivityFeed.tsx +444 -0
- package/web/ui/src/components/AgentGroupPicker.tsx +263 -0
- package/web/ui/src/components/AgentProviderCards.tsx +220 -0
- package/web/ui/src/components/CredentialForm.tsx +214 -0
- package/web/ui/src/components/ScopeGrants.tsx +74 -0
- package/web/ui/src/components/StatusDot.tsx +43 -0
- package/web/ui/src/components/VaultPicker.tsx +127 -0
- package/web/ui/src/components/setup/AdapterInstallStep.tsx +178 -0
- package/web/ui/src/components/setup/AgentGroupStep.tsx +43 -0
- package/web/ui/src/components/setup/ChannelPickStep.tsx +74 -0
- package/web/ui/src/components/setup/DoneStep.tsx +49 -0
- package/web/ui/src/components/setup/PrereqStep.tsx +129 -0
- package/web/ui/src/components/setup/TestConnectionStep.tsx +108 -0
- package/web/ui/src/components/setup/TestMessageStep.tsx +104 -0
- package/web/ui/src/components/setup/WireChannelStep.tsx +166 -0
- package/web/ui/src/components/setup/types.ts +105 -0
- package/web/ui/src/lib/api.test.ts +410 -0
- package/web/ui/src/lib/api.ts +1210 -0
- package/web/ui/src/lib/auth.test.ts +139 -0
- package/web/ui/src/lib/auth.ts +348 -0
- package/web/ui/src/lib/channel-adapters.ts +136 -0
- package/web/ui/src/main.tsx +19 -0
- package/web/ui/src/routes/ApprovalsList.tsx +294 -0
- package/web/ui/src/routes/Apps.tsx +613 -0
- package/web/ui/src/routes/ChannelWireDetail.test.tsx +233 -0
- package/web/ui/src/routes/ChannelWireDetail.tsx +403 -0
- package/web/ui/src/routes/ChannelsList.tsx +158 -0
- package/web/ui/src/routes/GroupDetail.tsx +755 -0
- package/web/ui/src/routes/GroupList.tsx +187 -0
- package/web/ui/src/routes/MessagingGroupDetail.test.tsx +233 -0
- package/web/ui/src/routes/MessagingGroupDetail.tsx +306 -0
- package/web/ui/src/routes/NewGroupWizard.tsx +390 -0
- package/web/ui/src/routes/OAuthCallback.tsx +56 -0
- package/web/ui/src/routes/SecretsList.tsx +921 -0
- package/web/ui/src/routes/SessionsList.tsx +220 -0
- package/web/ui/src/routes/SettingsAgentProvider.tsx +109 -0
- package/web/ui/src/routes/SettingsApprovals.tsx +234 -0
- package/web/ui/src/routes/SetupWizard.tsx +219 -0
- package/web/ui/src/routes/VaultDetail.test.tsx +361 -0
- package/web/ui/src/routes/VaultDetail.tsx +960 -0
- package/web/ui/src/routes/VaultsList.tsx +295 -0
- package/web/ui/src/routes/WireChannelPage.tsx +413 -0
- package/web/ui/src/styles.css +608 -0
- package/web/ui/src/test/setup.ts +23 -0
- package/web/ui/src/vite-env.d.ts +10 -0
- package/web/ui/tsconfig.json +20 -0
- package/web/ui/vite.config.ts +34 -0
- package/web/ui/vitest.config.ts +25 -0
|
@@ -0,0 +1,313 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Channel adapter registry.
|
|
3
|
+
*
|
|
4
|
+
* Channels self-register on import. The host calls initChannelAdapters() at startup
|
|
5
|
+
* to instantiate and set up all registered adapters.
|
|
6
|
+
*/
|
|
7
|
+
import type { ChannelAdapter, ChannelRegistration, ChannelSetup } from './adapter.js';
|
|
8
|
+
import { getDb } from '../db/connection.js';
|
|
9
|
+
import { log } from '../log.js';
|
|
10
|
+
import { decodePlatformIdAs } from '../platform-id.js';
|
|
11
|
+
import { listSecrets, getSecret } from '../secrets/index.js';
|
|
12
|
+
|
|
13
|
+
const SETUP_RETRY_DELAYS_MS = [2000, 5000, 10000];
|
|
14
|
+
|
|
15
|
+
/** Duck-type check — adapters that throw an Error with `name === 'NetworkError'`
|
|
16
|
+
* (Chat SDK's `@chat-adapter/shared.NetworkError` and similar) get a retry on
|
|
17
|
+
* setup. Avoids depending on `@chat-adapter/shared` at trunk level. */
|
|
18
|
+
function isNetworkError(err: unknown): err is Error {
|
|
19
|
+
return err instanceof Error && err.name === 'NetworkError';
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
const sleep = (ms: number) => new Promise<void>((resolve) => setTimeout(resolve, ms));
|
|
23
|
+
|
|
24
|
+
const registry = new Map<string, ChannelRegistration>();
|
|
25
|
+
/**
|
|
26
|
+
* Active adapters keyed by `<channelType>\0<botId>` so two adapters on the
|
|
27
|
+
* same channel type but different bots (e.g. two Telegram bots) can coexist.
|
|
28
|
+
* Adapters without a bot dimension (CLI admin transport) key under empty
|
|
29
|
+
* botId.
|
|
30
|
+
*/
|
|
31
|
+
const activeAdapters = new Map<string, ChannelAdapter>();
|
|
32
|
+
|
|
33
|
+
/**
|
|
34
|
+
* Cached host-callbacks builder, set by {@link initChannelAdapters} the
|
|
35
|
+
* first time it runs. Reused by {@link spawnSecretsBackedBots} (boot scan)
|
|
36
|
+
* and {@link registerBotAdapter} (dynamic per-bot adds via the HTTP
|
|
37
|
+
* register-bot endpoint) so every adapter — primary or dynamic — wires
|
|
38
|
+
* inbound through the same router callbacks.
|
|
39
|
+
*/
|
|
40
|
+
let cachedSetupFn: ((adapter: ChannelAdapter) => ChannelSetup) | null = null;
|
|
41
|
+
|
|
42
|
+
function adapterKey(channelType: string, botId: string | null | undefined): string {
|
|
43
|
+
return `${channelType}\0${botId ?? ''}`;
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
/** Register a channel adapter factory. Called by channel modules on import. */
|
|
47
|
+
export function registerChannelAdapter(name: string, registration: ChannelRegistration): void {
|
|
48
|
+
registry.set(name, registration);
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
/**
|
|
52
|
+
* Get a live adapter by channel type. Returns the first adapter registered
|
|
53
|
+
* under that type — meaningful only in single-bot-per-platform installs
|
|
54
|
+
* (the current state through PR A). Multi-bot callers must use
|
|
55
|
+
* {@link getChannelAdapterForPlatformId} so the right bot's adapter is
|
|
56
|
+
* selected by the v2 platform_id's bot dimension.
|
|
57
|
+
*/
|
|
58
|
+
export function getChannelAdapter(channelType: string): ChannelAdapter | undefined {
|
|
59
|
+
for (const [key, adapter] of activeAdapters) {
|
|
60
|
+
if (key.startsWith(`${channelType}\0`)) return adapter;
|
|
61
|
+
}
|
|
62
|
+
return undefined;
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
/** Resolve a live adapter by exact `(channelType, botId)`. Returns undefined if no adapter for that bot is active. */
|
|
66
|
+
export function getChannelAdapterByBotId(channelType: string, botId: string): ChannelAdapter | undefined {
|
|
67
|
+
return activeAdapters.get(adapterKey(channelType, botId));
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
/**
|
|
71
|
+
* Resolve the adapter responsible for a given v2 platform_id by decoding
|
|
72
|
+
* its bot segment. Falls back to the channel-type-only lookup for legacy
|
|
73
|
+
* v1 ids (botId === null) so deliveries against not-yet-backfilled rows
|
|
74
|
+
* still go somewhere sensible during the rollout window.
|
|
75
|
+
*/
|
|
76
|
+
export function getChannelAdapterForPlatformId(channelType: string, platformId: string): ChannelAdapter | undefined {
|
|
77
|
+
const decoded = decodePlatformIdAs(platformId, 'v2');
|
|
78
|
+
if (decoded.botId !== null) {
|
|
79
|
+
const exact = activeAdapters.get(adapterKey(channelType, decoded.botId));
|
|
80
|
+
if (exact) return exact;
|
|
81
|
+
}
|
|
82
|
+
return getChannelAdapter(channelType);
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
/** Get all active adapters. */
|
|
86
|
+
export function getActiveAdapters(): ChannelAdapter[] {
|
|
87
|
+
return [...activeAdapters.values()];
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
/** Get all registered channel names. */
|
|
91
|
+
export function getRegisteredChannelNames(): string[] {
|
|
92
|
+
return [...registry.keys()];
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
/** Get container config for a channel (used by container-runner for additional mounts/env). */
|
|
96
|
+
export function getChannelContainerConfig(name: string): ChannelRegistration['containerConfig'] {
|
|
97
|
+
return registry.get(name)?.containerConfig;
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
/**
|
|
101
|
+
* Run an adapter's setup with NetworkError retry — the same retry loop both
|
|
102
|
+
* {@link initChannelAdapters} and the dynamic register helpers share so
|
|
103
|
+
* boot-time and runtime adds get identical resilience semantics.
|
|
104
|
+
*/
|
|
105
|
+
async function setupAdapterWithRetry(adapter: ChannelAdapter, setup: ChannelSetup, label: string): Promise<void> {
|
|
106
|
+
let attempt = 0;
|
|
107
|
+
while (true) {
|
|
108
|
+
try {
|
|
109
|
+
await adapter.setup(setup);
|
|
110
|
+
return;
|
|
111
|
+
} catch (err) {
|
|
112
|
+
if (isNetworkError(err) && attempt < SETUP_RETRY_DELAYS_MS.length) {
|
|
113
|
+
const delay = SETUP_RETRY_DELAYS_MS[attempt]!;
|
|
114
|
+
log.warn('Channel adapter setup failed with network error, retrying', {
|
|
115
|
+
channel: label,
|
|
116
|
+
attempt: attempt + 1,
|
|
117
|
+
delayMs: delay,
|
|
118
|
+
err: err.message,
|
|
119
|
+
});
|
|
120
|
+
await sleep(delay);
|
|
121
|
+
attempt += 1;
|
|
122
|
+
continue;
|
|
123
|
+
}
|
|
124
|
+
throw err;
|
|
125
|
+
}
|
|
126
|
+
}
|
|
127
|
+
}
|
|
128
|
+
|
|
129
|
+
/**
|
|
130
|
+
* Instantiate and set up all registered channel adapters.
|
|
131
|
+
* Skips adapters that return null (missing credentials).
|
|
132
|
+
*/
|
|
133
|
+
export async function initChannelAdapters(setupFn: (adapter: ChannelAdapter) => ChannelSetup): Promise<void> {
|
|
134
|
+
cachedSetupFn = setupFn;
|
|
135
|
+
for (const [name, registration] of registry) {
|
|
136
|
+
try {
|
|
137
|
+
const adapter = await registration.factory();
|
|
138
|
+
if (!adapter) {
|
|
139
|
+
log.warn('Channel credentials missing, skipping', { channel: name });
|
|
140
|
+
continue;
|
|
141
|
+
}
|
|
142
|
+
|
|
143
|
+
// Transient network failures during adapter init (e.g. Telegram deleteWebhook
|
|
144
|
+
// hitting a DNS hiccup at boot) would otherwise leave the channel permanently
|
|
145
|
+
// dead until manual restart. Retry only on NetworkError so misconfigs (bad
|
|
146
|
+
// tokens, etc.) still fail fast.
|
|
147
|
+
await setupAdapterWithRetry(adapter, setupFn(adapter), name);
|
|
148
|
+
activeAdapters.set(adapterKey(adapter.channelType, adapter.botId), adapter);
|
|
149
|
+
log.info('Channel adapter started', {
|
|
150
|
+
channel: name,
|
|
151
|
+
type: adapter.channelType,
|
|
152
|
+
botId: adapter.botId ?? null,
|
|
153
|
+
});
|
|
154
|
+
} catch (err) {
|
|
155
|
+
log.error('Failed to start channel adapter', { channel: name, err });
|
|
156
|
+
}
|
|
157
|
+
}
|
|
158
|
+
}
|
|
159
|
+
|
|
160
|
+
/**
|
|
161
|
+
* Register an adapter for a specific bot at runtime — used by the
|
|
162
|
+
* `POST /api/channels/{channel}/register-bot` endpoint after the operator
|
|
163
|
+
* pastes a token via the wire-channel UI.
|
|
164
|
+
*
|
|
165
|
+
* Resolves the channel's `spawnFromSecret` hook to build the adapter, runs
|
|
166
|
+
* setup with the same callbacks the primary adapter uses, and slots it into
|
|
167
|
+
* `activeAdapters` keyed by `(channelType, botId)`. Idempotent: if an
|
|
168
|
+
* adapter is already active for this `(channelType, botId)` it returns the
|
|
169
|
+
* existing one instead of double-registering.
|
|
170
|
+
*
|
|
171
|
+
* Throws if the channel doesn't expose `spawnFromSecret` (single-bot
|
|
172
|
+
* channel) or if `initChannelAdapters` hasn't run yet (no cached setup
|
|
173
|
+
* callbacks). Returns null if the spawn hook itself returns null (token
|
|
174
|
+
* rejected at the platform).
|
|
175
|
+
*/
|
|
176
|
+
export async function registerBotAdapter(
|
|
177
|
+
channelType: string,
|
|
178
|
+
secretName: string,
|
|
179
|
+
secretValue: string,
|
|
180
|
+
): Promise<ChannelAdapter | null> {
|
|
181
|
+
const registration = registry.get(channelType);
|
|
182
|
+
if (!registration) throw new Error(`unknown channel: ${channelType}`);
|
|
183
|
+
if (!registration.spawnFromSecret) {
|
|
184
|
+
throw new Error(`channel does not support multi-bot operation: ${channelType}`);
|
|
185
|
+
}
|
|
186
|
+
if (!cachedSetupFn) {
|
|
187
|
+
throw new Error('initChannelAdapters has not run yet — cannot register dynamic bot');
|
|
188
|
+
}
|
|
189
|
+
const adapter = await registration.spawnFromSecret(secretName, secretValue);
|
|
190
|
+
if (!adapter) return null;
|
|
191
|
+
const key = adapterKey(adapter.channelType, adapter.botId);
|
|
192
|
+
const existing = activeAdapters.get(key);
|
|
193
|
+
if (existing) {
|
|
194
|
+
log.info('Channel adapter already active for bot, skipping re-setup', {
|
|
195
|
+
channel: channelType,
|
|
196
|
+
botId: adapter.botId ?? null,
|
|
197
|
+
});
|
|
198
|
+
return existing;
|
|
199
|
+
}
|
|
200
|
+
await setupAdapterWithRetry(adapter, cachedSetupFn(adapter), channelType);
|
|
201
|
+
activeAdapters.set(key, adapter);
|
|
202
|
+
log.info('Channel adapter registered dynamically', {
|
|
203
|
+
channel: channelType,
|
|
204
|
+
botId: adapter.botId ?? null,
|
|
205
|
+
});
|
|
206
|
+
return adapter;
|
|
207
|
+
}
|
|
208
|
+
|
|
209
|
+
/**
|
|
210
|
+
* Boot-time scan that brings up adapters for every persisted
|
|
211
|
+
* `CHANNEL_BOT_TOKEN:<channel>:<botId>` secret that (a) has at least one
|
|
212
|
+
* wired messaging_group_agents row and (b) isn't already covered by the
|
|
213
|
+
* `.env`-seeded primary adapter. Run AFTER `initChannelAdapters` and
|
|
214
|
+
* AFTER `runStartupBootstrap`.
|
|
215
|
+
*
|
|
216
|
+
* Orphan rule (paraclaw#67 Proposal A): a secret with no MGA wiring is
|
|
217
|
+
* "registered but inert" — the operator validated a token but never
|
|
218
|
+
* committed it to a group. We deliberately don't spawn its adapter at
|
|
219
|
+
* boot, because doing so would re-introduce the validate-then-poll race
|
|
220
|
+
* that pre-A behavior had: an adapter polling without a wire feeds
|
|
221
|
+
* inbounds straight into the unwired-channel approval cascade. Operators
|
|
222
|
+
* recover by completing the wire from `/agent/channels/new` (the wire
|
|
223
|
+
* endpoint spawns the adapter atomically with the MGA insert).
|
|
224
|
+
*
|
|
225
|
+
* Skips channels with no `spawnFromSecret` hook. Logs but doesn't throw
|
|
226
|
+
* on individual spawn failures — one bad token shouldn't keep the rest
|
|
227
|
+
* offline.
|
|
228
|
+
*/
|
|
229
|
+
export async function spawnSecretsBackedBots(): Promise<void> {
|
|
230
|
+
const tokens = listSecrets(null).filter((s) => s.kind === 'channel-token' && s.name.startsWith('CHANNEL_BOT_TOKEN:'));
|
|
231
|
+
for (const row of tokens) {
|
|
232
|
+
const parts = row.name.split(':');
|
|
233
|
+
if (parts.length < 3) continue;
|
|
234
|
+
const channelType = parts[1]!;
|
|
235
|
+
const botId = parts.slice(2).join(':');
|
|
236
|
+
if (!channelType || !botId) continue;
|
|
237
|
+
const registration = registry.get(channelType);
|
|
238
|
+
if (!registration?.spawnFromSecret) continue;
|
|
239
|
+
if (activeAdapters.has(adapterKey(channelType, botId))) continue;
|
|
240
|
+
if (!hasWiringForBot(channelType, botId)) {
|
|
241
|
+
log.info('Skipping orphan channel bot secret (no wiring)', { channel: channelType, botId });
|
|
242
|
+
continue;
|
|
243
|
+
}
|
|
244
|
+
const value = getSecret(row.name);
|
|
245
|
+
if (!value) {
|
|
246
|
+
log.warn('Channel bot secret has no value, skipping', { secret: row.name });
|
|
247
|
+
continue;
|
|
248
|
+
}
|
|
249
|
+
try {
|
|
250
|
+
const adapter = await registerBotAdapter(channelType, row.name, value);
|
|
251
|
+
if (!adapter) {
|
|
252
|
+
log.warn('Secrets-backed bot spawn returned null', { channel: channelType, botId });
|
|
253
|
+
}
|
|
254
|
+
} catch (err) {
|
|
255
|
+
log.error('Failed to spawn secrets-backed bot', { channel: channelType, botId, err });
|
|
256
|
+
}
|
|
257
|
+
}
|
|
258
|
+
}
|
|
259
|
+
|
|
260
|
+
/**
|
|
261
|
+
* Returns true iff at least one messaging_group_agents row exists wired
|
|
262
|
+
* through a messaging_groups row whose platform_id encodes the given
|
|
263
|
+
* `(channelType, botId)` pair (v2 format `<channel>:<botId>:<native>`).
|
|
264
|
+
*
|
|
265
|
+
* v1 rows (no bot dimension) for the same channel match nothing here —
|
|
266
|
+
* the secrets-backed scan only runs for bots that have a botId in their
|
|
267
|
+
* secret name, and v1 wires don't carry one. That's the correct
|
|
268
|
+
* conservative answer: a v1 wire could belong to *any* bot on that
|
|
269
|
+
* channel, so we can't safely auto-attribute it to this secret.
|
|
270
|
+
*/
|
|
271
|
+
function hasWiringForBot(channelType: string, botId: string): boolean {
|
|
272
|
+
const row = getDb()
|
|
273
|
+
.prepare(
|
|
274
|
+
`SELECT 1
|
|
275
|
+
FROM messaging_groups mg
|
|
276
|
+
JOIN messaging_group_agents mga ON mga.messaging_group_id = mg.id
|
|
277
|
+
WHERE mg.channel_type = ?
|
|
278
|
+
AND mg.platform_id LIKE ? ESCAPE '\\'
|
|
279
|
+
LIMIT 1`,
|
|
280
|
+
)
|
|
281
|
+
.get(channelType, `${channelType}:${escapeLike(botId)}:%`);
|
|
282
|
+
return row !== undefined;
|
|
283
|
+
}
|
|
284
|
+
|
|
285
|
+
function escapeLike(s: string): string {
|
|
286
|
+
return s.replace(/\\/g, '\\\\').replace(/%/g, '\\%').replace(/_/g, '\\_');
|
|
287
|
+
}
|
|
288
|
+
|
|
289
|
+
/**
|
|
290
|
+
* Test-only: inject a fake active adapter so functions that read the
|
|
291
|
+
* registry (e.g. startup-bootstrap) can run without a full setup. Caller
|
|
292
|
+
* is responsible for calling {@link _resetActiveAdaptersForTest} after.
|
|
293
|
+
*/
|
|
294
|
+
export function _setActiveAdapterForTest(adapter: ChannelAdapter): void {
|
|
295
|
+
activeAdapters.set(adapterKey(adapter.channelType, adapter.botId), adapter);
|
|
296
|
+
}
|
|
297
|
+
|
|
298
|
+
export function _resetActiveAdaptersForTest(): void {
|
|
299
|
+
activeAdapters.clear();
|
|
300
|
+
}
|
|
301
|
+
|
|
302
|
+
/** Tear down all active adapters. */
|
|
303
|
+
export async function teardownChannelAdapters(): Promise<void> {
|
|
304
|
+
for (const [key, adapter] of activeAdapters) {
|
|
305
|
+
try {
|
|
306
|
+
await adapter.teardown();
|
|
307
|
+
log.info('Channel adapter stopped', { key, type: adapter.channelType });
|
|
308
|
+
} catch (err) {
|
|
309
|
+
log.error('Failed to stop channel adapter', { key, type: adapter.channelType, err });
|
|
310
|
+
}
|
|
311
|
+
}
|
|
312
|
+
activeAdapters.clear();
|
|
313
|
+
}
|
|
@@ -0,0 +1,84 @@
|
|
|
1
|
+
import { describe, expect, it } from 'vitest';
|
|
2
|
+
|
|
3
|
+
import type { Adapter } from 'chat';
|
|
4
|
+
|
|
5
|
+
import { createChatSdkBridge, splitForLimit } from './chat-sdk-bridge.js';
|
|
6
|
+
|
|
7
|
+
function stubAdapter(partial: Partial<Adapter>): Adapter {
|
|
8
|
+
return { name: 'stub', ...partial } as unknown as Adapter;
|
|
9
|
+
}
|
|
10
|
+
|
|
11
|
+
describe('splitForLimit', () => {
|
|
12
|
+
it('returns a single chunk when text fits', () => {
|
|
13
|
+
expect(splitForLimit('short text', 100)).toEqual(['short text']);
|
|
14
|
+
});
|
|
15
|
+
|
|
16
|
+
it('splits on paragraph boundaries when available', () => {
|
|
17
|
+
const text = 'para one line one\npara one line two\n\npara two line one\npara two line two';
|
|
18
|
+
const chunks = splitForLimit(text, 40);
|
|
19
|
+
expect(chunks.length).toBeGreaterThan(1);
|
|
20
|
+
for (const c of chunks) expect(c.length).toBeLessThanOrEqual(40);
|
|
21
|
+
});
|
|
22
|
+
|
|
23
|
+
it('falls back to line boundaries when no paragraph fits', () => {
|
|
24
|
+
const text = 'alpha\nbravo\ncharlie\ndelta\necho\nfoxtrot';
|
|
25
|
+
const chunks = splitForLimit(text, 15);
|
|
26
|
+
expect(chunks.length).toBeGreaterThan(1);
|
|
27
|
+
for (const c of chunks) expect(c.length).toBeLessThanOrEqual(15);
|
|
28
|
+
});
|
|
29
|
+
|
|
30
|
+
it('hard-cuts when no whitespace is available', () => {
|
|
31
|
+
const text = 'a'.repeat(100);
|
|
32
|
+
const chunks = splitForLimit(text, 30);
|
|
33
|
+
expect(chunks.length).toBe(Math.ceil(100 / 30));
|
|
34
|
+
for (const c of chunks) expect(c.length).toBeLessThanOrEqual(30);
|
|
35
|
+
expect(chunks.join('')).toBe(text);
|
|
36
|
+
});
|
|
37
|
+
});
|
|
38
|
+
|
|
39
|
+
describe('createChatSdkBridge', () => {
|
|
40
|
+
// The bridge is now transport-only: forward inbound events, relay outbound
|
|
41
|
+
// ops. All per-wiring engage / accumulate / drop / subscribe decisions live
|
|
42
|
+
// in the router (src/router.ts routeInbound / evaluateEngage) and are
|
|
43
|
+
// exercised by host-core.test.ts end-to-end. These tests only cover the
|
|
44
|
+
// bridge's narrow, platform-adjacent surface.
|
|
45
|
+
|
|
46
|
+
it('omits openDM when the underlying Chat SDK adapter has none', () => {
|
|
47
|
+
const bridge = createChatSdkBridge({
|
|
48
|
+
adapter: stubAdapter({}),
|
|
49
|
+
botId: 'bot-1',
|
|
50
|
+
supportsThreads: false,
|
|
51
|
+
});
|
|
52
|
+
expect(bridge.openDM).toBeUndefined();
|
|
53
|
+
});
|
|
54
|
+
|
|
55
|
+
it('exposes openDM when the underlying adapter has one, and encodes the bot dim', async () => {
|
|
56
|
+
const openDMCalls: string[] = [];
|
|
57
|
+
const bridge = createChatSdkBridge({
|
|
58
|
+
adapter: stubAdapter({
|
|
59
|
+
openDM: async (userId: string) => {
|
|
60
|
+
openDMCalls.push(userId);
|
|
61
|
+
return `thread::${userId}`;
|
|
62
|
+
},
|
|
63
|
+
channelIdFromThreadId: (threadId: string) => `stub:${threadId.replace(/^thread::/, '')}`,
|
|
64
|
+
}),
|
|
65
|
+
botId: 'bot-1',
|
|
66
|
+
supportsThreads: false,
|
|
67
|
+
});
|
|
68
|
+
expect(bridge.openDM).toBeDefined();
|
|
69
|
+
const platformId = await bridge.openDM!('user-42');
|
|
70
|
+
// Delegation: adapter.openDM → adapter.channelIdFromThreadId, then bridge
|
|
71
|
+
// wraps with the bot id so messaging_groups gets the v2 form.
|
|
72
|
+
expect(openDMCalls).toEqual(['user-42']);
|
|
73
|
+
expect(platformId).toBe('stub:bot-1:user-42');
|
|
74
|
+
});
|
|
75
|
+
|
|
76
|
+
it('exposes subscribe (lets the router initiate thread subscription on mention-sticky engage)', () => {
|
|
77
|
+
const bridge = createChatSdkBridge({
|
|
78
|
+
adapter: stubAdapter({}),
|
|
79
|
+
botId: 'bot-1',
|
|
80
|
+
supportsThreads: true,
|
|
81
|
+
});
|
|
82
|
+
expect(typeof bridge.subscribe).toBe('function');
|
|
83
|
+
});
|
|
84
|
+
});
|