@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,74 @@
|
|
|
1
|
+
import type { VaultScope } from "../lib/api.ts";
|
|
2
|
+
|
|
3
|
+
const READ_TOOLS = ["query-notes", "list-tags", "find-path", "vault-info"] as const;
|
|
4
|
+
const WRITE_TOOLS = ["create-note", "update-note", "delete-note", "update-tag", "delete-tag"] as const;
|
|
5
|
+
|
|
6
|
+
export const SCOPE_OPTIONS: { value: VaultScope; label: string }[] = [
|
|
7
|
+
{ value: "vault:read", label: "vault:read — query, can't modify" },
|
|
8
|
+
{ value: "vault:write", label: "vault:write — capture and update notes" },
|
|
9
|
+
{ value: "vault:admin", label: "vault:admin — full access (use sparingly)" },
|
|
10
|
+
];
|
|
11
|
+
|
|
12
|
+
export function scopeGrants(scope: VaultScope): {
|
|
13
|
+
summary: string;
|
|
14
|
+
granted: readonly string[];
|
|
15
|
+
withheld: readonly string[];
|
|
16
|
+
adminNote?: string;
|
|
17
|
+
} {
|
|
18
|
+
if (scope === "vault:read") {
|
|
19
|
+
return {
|
|
20
|
+
summary: "Read-only access. The agent can query the vault but cannot create, modify, or delete anything.",
|
|
21
|
+
granted: READ_TOOLS,
|
|
22
|
+
withheld: WRITE_TOOLS,
|
|
23
|
+
};
|
|
24
|
+
}
|
|
25
|
+
if (scope === "vault:write") {
|
|
26
|
+
return {
|
|
27
|
+
summary: "Read + write. The agent can capture and update notes and tags, but cannot manage vault config or tokens.",
|
|
28
|
+
granted: [...READ_TOOLS, ...WRITE_TOOLS],
|
|
29
|
+
withheld: [],
|
|
30
|
+
};
|
|
31
|
+
}
|
|
32
|
+
return {
|
|
33
|
+
summary: "Full access including vault config (/.parachute/config*) and token management. Use sparingly.",
|
|
34
|
+
granted: [...READ_TOOLS, ...WRITE_TOOLS],
|
|
35
|
+
withheld: [],
|
|
36
|
+
adminNote: "Admin tokens can read and write any path including vault config — only grant when the agent genuinely needs it.",
|
|
37
|
+
};
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
export function ScopeGrants({ scope }: { scope: VaultScope }) {
|
|
41
|
+
const grants = scopeGrants(scope);
|
|
42
|
+
return (
|
|
43
|
+
<div className="scope-grants">
|
|
44
|
+
<p className="scope-grants-summary">{grants.summary}</p>
|
|
45
|
+
<div className="scope-grants-tools">
|
|
46
|
+
<div>
|
|
47
|
+
<span className="scope-grants-label">Allows</span>
|
|
48
|
+
<ul>
|
|
49
|
+
{grants.granted.map((t) => (
|
|
50
|
+
<li key={t}>
|
|
51
|
+
<code>{t}</code>
|
|
52
|
+
</li>
|
|
53
|
+
))}
|
|
54
|
+
</ul>
|
|
55
|
+
</div>
|
|
56
|
+
{grants.withheld.length > 0 && (
|
|
57
|
+
<div>
|
|
58
|
+
<span className="scope-grants-label">Blocks</span>
|
|
59
|
+
<ul>
|
|
60
|
+
{grants.withheld.map((t) => (
|
|
61
|
+
<li key={t} className="dim">
|
|
62
|
+
<code>{t}</code>
|
|
63
|
+
</li>
|
|
64
|
+
))}
|
|
65
|
+
</ul>
|
|
66
|
+
</div>
|
|
67
|
+
)}
|
|
68
|
+
</div>
|
|
69
|
+
{grants.adminNote && (
|
|
70
|
+
<p className="scope-grants-warn">{grants.adminNote}</p>
|
|
71
|
+
)}
|
|
72
|
+
</div>
|
|
73
|
+
);
|
|
74
|
+
}
|
|
@@ -0,0 +1,43 @@
|
|
|
1
|
+
import type { GroupStatus } from "../lib/api.ts";
|
|
2
|
+
|
|
3
|
+
export function StatusDot({ status }: { status: GroupStatus | null }) {
|
|
4
|
+
if (!status) {
|
|
5
|
+
return <span className="status-dot status-dot-unknown" title="status unavailable" aria-label="status unavailable" />;
|
|
6
|
+
}
|
|
7
|
+
if (status.containerRunning) {
|
|
8
|
+
const lastActive = status.lastHeartbeatAt
|
|
9
|
+
? `last heartbeat ${formatRelative(status.lastHeartbeatAt)}`
|
|
10
|
+
: "alive";
|
|
11
|
+
return (
|
|
12
|
+
<span
|
|
13
|
+
className="status-dot status-dot-alive"
|
|
14
|
+
title={`${status.activeSessionCount} session${status.activeSessionCount === 1 ? "" : "s"} alive — ${lastActive}`}
|
|
15
|
+
aria-label="alive"
|
|
16
|
+
/>
|
|
17
|
+
);
|
|
18
|
+
}
|
|
19
|
+
const idleTitle = status.lastHeartbeatAt
|
|
20
|
+
? `idle — last heartbeat ${formatRelative(status.lastHeartbeatAt)}`
|
|
21
|
+
: "idle — never started";
|
|
22
|
+
return (
|
|
23
|
+
<span
|
|
24
|
+
className="status-dot status-dot-idle"
|
|
25
|
+
title={idleTitle}
|
|
26
|
+
aria-label="idle"
|
|
27
|
+
/>
|
|
28
|
+
);
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
export function formatRelative(iso: string, nowMs: number = Date.now()): string {
|
|
32
|
+
const t = Date.parse(iso);
|
|
33
|
+
if (Number.isNaN(t)) return iso;
|
|
34
|
+
const diffSec = Math.max(0, Math.floor((nowMs - t) / 1000));
|
|
35
|
+
if (diffSec < 5) return "just now";
|
|
36
|
+
if (diffSec < 60) return `${diffSec}s ago`;
|
|
37
|
+
const diffMin = Math.floor(diffSec / 60);
|
|
38
|
+
if (diffMin < 60) return `${diffMin}m ago`;
|
|
39
|
+
const diffHr = Math.floor(diffMin / 60);
|
|
40
|
+
if (diffHr < 24) return `${diffHr}h ago`;
|
|
41
|
+
const diffDay = Math.floor(diffHr / 24);
|
|
42
|
+
return `${diffDay}d ago`;
|
|
43
|
+
}
|
|
@@ -0,0 +1,127 @@
|
|
|
1
|
+
import { useEffect, useRef, useState } from 'react';
|
|
2
|
+
import { listVaults, type VaultListing } from '../lib/api.ts';
|
|
3
|
+
|
|
4
|
+
interface VaultPickerProps {
|
|
5
|
+
value: string;
|
|
6
|
+
onChange: (next: string) => void;
|
|
7
|
+
disabled?: boolean;
|
|
8
|
+
inputId?: string;
|
|
9
|
+
/** Fires with the picked vault's display name (or null when "Other" is selected / no match). */
|
|
10
|
+
onPickedName?: (name: string | null) => void;
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
const OTHER_SENTINEL = '__other__';
|
|
14
|
+
|
|
15
|
+
export function VaultPicker({ value, onChange, disabled, inputId, onPickedName }: VaultPickerProps) {
|
|
16
|
+
const [vaults, setVaults] = useState<VaultListing[] | null>(null);
|
|
17
|
+
const [loadError, setLoadError] = useState<string | null>(null);
|
|
18
|
+
const [useCustomUrl, setUseCustomUrl] = useState(false);
|
|
19
|
+
const initializedRef = useRef(false);
|
|
20
|
+
|
|
21
|
+
useEffect(() => {
|
|
22
|
+
let cancelled = false;
|
|
23
|
+
listVaults()
|
|
24
|
+
.then((vs) => {
|
|
25
|
+
if (cancelled) return;
|
|
26
|
+
setVaults(vs);
|
|
27
|
+
if (initializedRef.current) return;
|
|
28
|
+
initializedRef.current = true;
|
|
29
|
+
if (vs.length === 0) {
|
|
30
|
+
setUseCustomUrl(true);
|
|
31
|
+
onPickedName?.(null);
|
|
32
|
+
return;
|
|
33
|
+
}
|
|
34
|
+
const match = vs.find((v) => v.url === value);
|
|
35
|
+
if (!match) onChange(vs[0].url);
|
|
36
|
+
})
|
|
37
|
+
.catch((e) => {
|
|
38
|
+
if (cancelled) return;
|
|
39
|
+
setLoadError(e instanceof Error ? e.message : String(e));
|
|
40
|
+
setUseCustomUrl(true);
|
|
41
|
+
initializedRef.current = true;
|
|
42
|
+
onPickedName?.(null);
|
|
43
|
+
});
|
|
44
|
+
return () => {
|
|
45
|
+
cancelled = true;
|
|
46
|
+
};
|
|
47
|
+
// eslint-disable-next-line react-hooks/exhaustive-deps
|
|
48
|
+
}, []);
|
|
49
|
+
|
|
50
|
+
// Notify parent when the picked-name changes (selection or value flip).
|
|
51
|
+
useEffect(() => {
|
|
52
|
+
if (!onPickedName) return;
|
|
53
|
+
if (useCustomUrl) {
|
|
54
|
+
onPickedName(null);
|
|
55
|
+
return;
|
|
56
|
+
}
|
|
57
|
+
if (!vaults) return;
|
|
58
|
+
const match = vaults.find((v) => v.url === value);
|
|
59
|
+
onPickedName(match ? match.name : null);
|
|
60
|
+
}, [vaults, value, useCustomUrl, onPickedName]);
|
|
61
|
+
|
|
62
|
+
const trimmed = value.replace(/\/+$/, '');
|
|
63
|
+
const reachLine = (
|
|
64
|
+
<p className="dim">
|
|
65
|
+
The agent will reach this at <code>{trimmed || '…'}/mcp</code>.
|
|
66
|
+
</p>
|
|
67
|
+
);
|
|
68
|
+
|
|
69
|
+
if (vaults === null && !loadError) {
|
|
70
|
+
return <p className="dim">Loading vaults…</p>;
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
const customInput = (
|
|
74
|
+
<input
|
|
75
|
+
id={vaults && vaults.length > 0 ? undefined : inputId}
|
|
76
|
+
type="text"
|
|
77
|
+
value={value}
|
|
78
|
+
onChange={(e) => onChange(e.target.value)}
|
|
79
|
+
disabled={disabled}
|
|
80
|
+
placeholder="https://parachute.example/vault/default"
|
|
81
|
+
style={vaults && vaults.length > 0 ? { marginTop: '0.5rem' } : undefined}
|
|
82
|
+
/>
|
|
83
|
+
);
|
|
84
|
+
|
|
85
|
+
if (!vaults || vaults.length === 0) {
|
|
86
|
+
return (
|
|
87
|
+
<>
|
|
88
|
+
{customInput}
|
|
89
|
+
{reachLine}
|
|
90
|
+
{loadError && <p className="dim">(could not list registered vaults: {loadError})</p>}
|
|
91
|
+
</>
|
|
92
|
+
);
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
return (
|
|
96
|
+
<>
|
|
97
|
+
<select
|
|
98
|
+
id={inputId}
|
|
99
|
+
value={useCustomUrl ? OTHER_SENTINEL : value}
|
|
100
|
+
onChange={(e) => {
|
|
101
|
+
if (e.target.value === OTHER_SENTINEL) {
|
|
102
|
+
setUseCustomUrl(true);
|
|
103
|
+
} else {
|
|
104
|
+
setUseCustomUrl(false);
|
|
105
|
+
onChange(e.target.value);
|
|
106
|
+
}
|
|
107
|
+
}}
|
|
108
|
+
disabled={disabled}
|
|
109
|
+
>
|
|
110
|
+
{vaults.map((v) => (
|
|
111
|
+
<option key={v.url} value={v.url}>
|
|
112
|
+
{v.name} — {v.url}
|
|
113
|
+
</option>
|
|
114
|
+
))}
|
|
115
|
+
<option value={OTHER_SENTINEL}>Other (paste URL)…</option>
|
|
116
|
+
</select>
|
|
117
|
+
{useCustomUrl && customInput}
|
|
118
|
+
{reachLine}
|
|
119
|
+
</>
|
|
120
|
+
);
|
|
121
|
+
}
|
|
122
|
+
|
|
123
|
+
export function vaultLabelForUrl(vaults: VaultListing[] | null, url: string): string | null {
|
|
124
|
+
if (!vaults) return null;
|
|
125
|
+
const match = vaults.find((v) => v.url === url.replace(/\/+$/, ''));
|
|
126
|
+
return match ? match.name : null;
|
|
127
|
+
}
|
|
@@ -0,0 +1,178 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Step 4 — Adapter install.
|
|
3
|
+
*
|
|
4
|
+
* Triggers POST /setup/install-channel with the chosen adapter, then polls
|
|
5
|
+
* GET /tasks/:id every second until the task lands in a terminal state.
|
|
6
|
+
* Renders the per-step checklist live as the orchestrator advances.
|
|
7
|
+
*
|
|
8
|
+
* Per-channel step counts: discord = 5, telegram = 6 (extra
|
|
9
|
+
* register-setup-step that wires the pair-telegram setup script).
|
|
10
|
+
*
|
|
11
|
+
* Idempotency: if the chosen adapter is already installed (status check
|
|
12
|
+
* before dispatch shows channels.<adapter>.installed=true), we skip
|
|
13
|
+
* kicking off the task and let the user advance immediately. The
|
|
14
|
+
* dirty-tree 409 surfaces as an inline error with the offending file
|
|
15
|
+
* list — operator commits/stashes and re-clicks.
|
|
16
|
+
*/
|
|
17
|
+
import { useCallback, useEffect, useRef, useState } from 'react';
|
|
18
|
+
import {
|
|
19
|
+
getSetupStatus,
|
|
20
|
+
getTask,
|
|
21
|
+
startInstallChannel,
|
|
22
|
+
type TaskRecord,
|
|
23
|
+
type SetupStatus,
|
|
24
|
+
} from '../../lib/api.ts';
|
|
25
|
+
import { ADAPTER_LABELS, type StepProps } from './types.ts';
|
|
26
|
+
|
|
27
|
+
const POLL_MS = 1000;
|
|
28
|
+
|
|
29
|
+
export function AdapterInstallStep({ state, patchState, next, back }: StepProps) {
|
|
30
|
+
const [status, setStatus] = useState<SetupStatus | null>(null);
|
|
31
|
+
const [task, setTask] = useState<TaskRecord | null>(null);
|
|
32
|
+
const [starting, setStarting] = useState(false);
|
|
33
|
+
const [error, setError] = useState<string | null>(null);
|
|
34
|
+
const [dirtyFiles, setDirtyFiles] = useState<string[] | null>(null);
|
|
35
|
+
const pollTimer = useRef<ReturnType<typeof setInterval> | null>(null);
|
|
36
|
+
|
|
37
|
+
const adapter = state.adapter;
|
|
38
|
+
|
|
39
|
+
useEffect(() => {
|
|
40
|
+
getSetupStatus()
|
|
41
|
+
.then(setStatus)
|
|
42
|
+
.catch((err) => setError(err instanceof Error ? err.message : String(err)));
|
|
43
|
+
}, []);
|
|
44
|
+
|
|
45
|
+
const startPolling = useCallback((taskId: string) => {
|
|
46
|
+
if (pollTimer.current) clearInterval(pollTimer.current);
|
|
47
|
+
pollTimer.current = setInterval(() => {
|
|
48
|
+
getTask(taskId)
|
|
49
|
+
.then((t) => {
|
|
50
|
+
setTask(t);
|
|
51
|
+
if (t.status === 'completed' || t.status === 'failed') {
|
|
52
|
+
if (pollTimer.current) clearInterval(pollTimer.current);
|
|
53
|
+
pollTimer.current = null;
|
|
54
|
+
}
|
|
55
|
+
})
|
|
56
|
+
.catch((err) => {
|
|
57
|
+
setError(err instanceof Error ? err.message : String(err));
|
|
58
|
+
if (pollTimer.current) clearInterval(pollTimer.current);
|
|
59
|
+
pollTimer.current = null;
|
|
60
|
+
});
|
|
61
|
+
}, POLL_MS);
|
|
62
|
+
}, []);
|
|
63
|
+
|
|
64
|
+
useEffect(() => {
|
|
65
|
+
if (state.installTaskId && !task) {
|
|
66
|
+
getTask(state.installTaskId).then(setTask).catch(() => {
|
|
67
|
+
patchState({ installTaskId: null });
|
|
68
|
+
});
|
|
69
|
+
startPolling(state.installTaskId);
|
|
70
|
+
}
|
|
71
|
+
return () => {
|
|
72
|
+
if (pollTimer.current) clearInterval(pollTimer.current);
|
|
73
|
+
};
|
|
74
|
+
}, [state.installTaskId, task, startPolling, patchState]);
|
|
75
|
+
|
|
76
|
+
if (!adapter) {
|
|
77
|
+
return (
|
|
78
|
+
<>
|
|
79
|
+
<h3>Install adapter</h3>
|
|
80
|
+
<div className="error-banner">No channel selected — go back to step 2.</div>
|
|
81
|
+
<div className="actions" style={{ marginTop: '1rem' }}>
|
|
82
|
+
<button className="secondary" onClick={back}>Back</button>
|
|
83
|
+
</div>
|
|
84
|
+
</>
|
|
85
|
+
);
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
const onStart = async () => {
|
|
89
|
+
setStarting(true);
|
|
90
|
+
setError(null);
|
|
91
|
+
setDirtyFiles(null);
|
|
92
|
+
try {
|
|
93
|
+
const res = await startInstallChannel(adapter);
|
|
94
|
+
patchState({ installTaskId: res.taskId });
|
|
95
|
+
startPolling(res.taskId);
|
|
96
|
+
} catch (err) {
|
|
97
|
+
const msg = err instanceof Error ? err.message : String(err);
|
|
98
|
+
if (msg.includes('uncommitted changes')) {
|
|
99
|
+
setDirtyFiles([msg]);
|
|
100
|
+
}
|
|
101
|
+
setError(msg);
|
|
102
|
+
} finally {
|
|
103
|
+
setStarting(false);
|
|
104
|
+
}
|
|
105
|
+
};
|
|
106
|
+
|
|
107
|
+
const installed = status?.channels[adapter]?.installed ?? false;
|
|
108
|
+
const taskTerminal = task?.status === 'completed' || task?.status === 'failed';
|
|
109
|
+
const adapterLabel = ADAPTER_LABELS[adapter];
|
|
110
|
+
|
|
111
|
+
return (
|
|
112
|
+
<>
|
|
113
|
+
<h3>Install {adapterLabel} adapter</h3>
|
|
114
|
+
{installed && !task && (
|
|
115
|
+
<div className="empty empty-rich" style={{ marginTop: '0.5rem' }}>
|
|
116
|
+
<p className="empty-headline" style={{ margin: 0 }}>{adapterLabel} adapter is already installed.</p>
|
|
117
|
+
<p className="muted" style={{ marginTop: '0.4rem' }}>
|
|
118
|
+
Move on to test the connection.
|
|
119
|
+
</p>
|
|
120
|
+
</div>
|
|
121
|
+
)}
|
|
122
|
+
|
|
123
|
+
{!installed && !task && (
|
|
124
|
+
<p className="muted">
|
|
125
|
+
{adapter === 'discord' ? (
|
|
126
|
+
<>
|
|
127
|
+
We'll fetch the adapter from the <code>channels</code> branch, copy it into <code>src/channels/discord.ts</code>,
|
|
128
|
+
register the import, run <code>pnpm install @chat-adapter/discord</code>, and rebuild. Takes ~1–3 min depending on cache.
|
|
129
|
+
</>
|
|
130
|
+
) : (
|
|
131
|
+
<>
|
|
132
|
+
We'll fetch the adapter from the <code>channels</code> branch, copy 6 files into <code>src/channels/</code> + <code>setup/</code>,
|
|
133
|
+
register the import + setup step, run <code>pnpm install @chat-adapter/telegram</code>, and rebuild.
|
|
134
|
+
Takes ~1–3 min depending on cache.
|
|
135
|
+
</>
|
|
136
|
+
)}
|
|
137
|
+
</p>
|
|
138
|
+
)}
|
|
139
|
+
|
|
140
|
+
{task && (
|
|
141
|
+
<ol style={{ listStyle: 'none', padding: 0, marginTop: '0.5rem' }}>
|
|
142
|
+
{task.steps.map((s) => (
|
|
143
|
+
<li key={s.name} style={{ padding: '0.4rem 0', display: 'flex', gap: '0.5rem' }}>
|
|
144
|
+
<span aria-hidden style={{ width: '1.2rem' }}>
|
|
145
|
+
{s.status === 'completed' ? '✓' : s.status === 'running' ? '…' : s.status === 'failed' ? '✗' : '○'}
|
|
146
|
+
</span>
|
|
147
|
+
<code>{s.name}</code>
|
|
148
|
+
{s.error && <span className="dim" style={{ color: 'var(--error)' }}>— {s.error}</span>}
|
|
149
|
+
</li>
|
|
150
|
+
))}
|
|
151
|
+
</ol>
|
|
152
|
+
)}
|
|
153
|
+
|
|
154
|
+
{error && (
|
|
155
|
+
<div className="error-banner" style={{ marginTop: '0.75rem' }}>
|
|
156
|
+
{error}
|
|
157
|
+
{dirtyFiles && (
|
|
158
|
+
<p style={{ marginTop: '0.4rem' }}>
|
|
159
|
+
Commit or stash the listed files, then click <strong>Retry</strong>.
|
|
160
|
+
</p>
|
|
161
|
+
)}
|
|
162
|
+
</div>
|
|
163
|
+
)}
|
|
164
|
+
|
|
165
|
+
<div className="actions" style={{ marginTop: '1rem' }}>
|
|
166
|
+
<button className="secondary" onClick={back}>Back</button>
|
|
167
|
+
{!installed && (
|
|
168
|
+
<button onClick={onStart} disabled={starting || (!!task && !taskTerminal)}>
|
|
169
|
+
{starting ? 'Starting…' : task && !taskTerminal ? 'Installing…' : task?.status === 'failed' ? 'Retry install' : 'Start install'}
|
|
170
|
+
</button>
|
|
171
|
+
)}
|
|
172
|
+
<button onClick={next} disabled={!installed && task?.status !== 'completed'}>
|
|
173
|
+
Next: test connection
|
|
174
|
+
</button>
|
|
175
|
+
</div>
|
|
176
|
+
</>
|
|
177
|
+
);
|
|
178
|
+
}
|
|
@@ -0,0 +1,43 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Step 6 — Agent group.
|
|
3
|
+
*
|
|
4
|
+
* Two paths:
|
|
5
|
+
*
|
|
6
|
+
* (a) Pick an existing group. If the operator already has groups, list
|
|
7
|
+
* them and let them select one — the wire-channel step works the
|
|
8
|
+
* same way regardless of how the group was created.
|
|
9
|
+
*
|
|
10
|
+
* (b) Create a new group inline. Vault attach is offered on the group
|
|
11
|
+
* detail page after setup, not here, to keep the wizard fast.
|
|
12
|
+
*
|
|
13
|
+
* The picker UI is shared with /channels/new — both are thin wrappers
|
|
14
|
+
* around <AgentGroupPicker />. Once a group is picked / created, we
|
|
15
|
+
* stamp `agentGroupFolder` and `agentGroupName` on the wizard state and
|
|
16
|
+
* advance.
|
|
17
|
+
*/
|
|
18
|
+
import { AgentGroupPicker, type PickedGroup } from '../AgentGroupPicker.tsx';
|
|
19
|
+
import type { StepProps } from './types.ts';
|
|
20
|
+
|
|
21
|
+
export function AgentGroupStep({ patchState, next, back }: StepProps) {
|
|
22
|
+
const onPicked = (g: PickedGroup) => {
|
|
23
|
+
patchState({ agentGroupFolder: g.folder, agentGroupName: g.name });
|
|
24
|
+
next();
|
|
25
|
+
};
|
|
26
|
+
|
|
27
|
+
return (
|
|
28
|
+
<>
|
|
29
|
+
<h3>Agent group</h3>
|
|
30
|
+
<p className="muted">
|
|
31
|
+
The agent group is the entity your bot represents. Pick an existing one or create a new one.
|
|
32
|
+
</p>
|
|
33
|
+
|
|
34
|
+
<AgentGroupPicker onPicked={onPicked} />
|
|
35
|
+
|
|
36
|
+
<div className="actions" style={{ marginTop: '1rem' }}>
|
|
37
|
+
<button className="secondary" onClick={back}>
|
|
38
|
+
Back
|
|
39
|
+
</button>
|
|
40
|
+
</div>
|
|
41
|
+
</>
|
|
42
|
+
);
|
|
43
|
+
}
|
|
@@ -0,0 +1,74 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Step 2 — Channel pick.
|
|
3
|
+
*
|
|
4
|
+
* Phase 1 ships Discord + Telegram. Slack/WhatsApp/Teams render disabled
|
|
5
|
+
* with a "coming soon" badge so the operator sees the trajectory — when
|
|
6
|
+
* those phases land, the UI shape doesn't change, only the disabled flag
|
|
7
|
+
* flips.
|
|
8
|
+
*/
|
|
9
|
+
import { useEffect } from 'react';
|
|
10
|
+
import type { ChannelAdapter, StepProps } from './types.ts';
|
|
11
|
+
|
|
12
|
+
interface AdapterCard {
|
|
13
|
+
key: ChannelAdapter | 'slack' | 'whatsapp';
|
|
14
|
+
name: string;
|
|
15
|
+
blurb: string;
|
|
16
|
+
available: boolean;
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
const ADAPTERS: AdapterCard[] = [
|
|
20
|
+
{ key: 'telegram', name: 'Telegram', blurb: 'Easiest first run — BotFather + @userinfobot, ~1 minute.', available: true },
|
|
21
|
+
{ key: 'discord', name: 'Discord', blurb: 'DM your bot or @-mention it in a server.', available: true },
|
|
22
|
+
{ key: 'slack', name: 'Slack', blurb: 'Workspace bot.', available: false },
|
|
23
|
+
{ key: 'whatsapp', name: 'WhatsApp', blurb: 'Cloud API.', available: false },
|
|
24
|
+
];
|
|
25
|
+
|
|
26
|
+
export function ChannelPickStep({ state, patchState, next, back }: StepProps) {
|
|
27
|
+
// If the user re-enters this step without an adapter set, default to telegram
|
|
28
|
+
// (the lower-friction onboarding path).
|
|
29
|
+
useEffect(() => {
|
|
30
|
+
if (!state.adapter) patchState({ adapter: 'telegram' });
|
|
31
|
+
}, [state.adapter, patchState]);
|
|
32
|
+
|
|
33
|
+
return (
|
|
34
|
+
<>
|
|
35
|
+
<h3>Pick a channel</h3>
|
|
36
|
+
<p className="muted">Telegram is the lowest-friction setup; Discord is the production target. Slack + WhatsApp arrive in Phase 3.</p>
|
|
37
|
+
<div style={{ display: 'grid', gridTemplateColumns: 'repeat(auto-fit, minmax(220px, 1fr))', gap: '0.75rem', marginTop: '0.75rem' }}>
|
|
38
|
+
{ADAPTERS.map((a) => {
|
|
39
|
+
const selected = state.adapter === a.key;
|
|
40
|
+
return (
|
|
41
|
+
<button
|
|
42
|
+
key={a.key}
|
|
43
|
+
type="button"
|
|
44
|
+
disabled={!a.available}
|
|
45
|
+
onClick={() => a.available && patchState({ adapter: a.key as ChannelAdapter })}
|
|
46
|
+
className="secondary"
|
|
47
|
+
style={{
|
|
48
|
+
textAlign: 'left',
|
|
49
|
+
padding: '1rem',
|
|
50
|
+
border: selected ? '2px solid var(--accent)' : '1px solid var(--border)',
|
|
51
|
+
background: selected ? 'var(--accent-soft)' : 'white',
|
|
52
|
+
opacity: a.available ? 1 : 0.5,
|
|
53
|
+
cursor: a.available ? 'pointer' : 'not-allowed',
|
|
54
|
+
}}
|
|
55
|
+
>
|
|
56
|
+
<div style={{ display: 'flex', alignItems: 'center', justifyContent: 'space-between' }}>
|
|
57
|
+
<strong>{a.name}</strong>
|
|
58
|
+
{!a.available && <span className="tag muted">coming soon</span>}
|
|
59
|
+
</div>
|
|
60
|
+
<p className="dim" style={{ margin: '0.4rem 0 0', fontSize: '0.85rem' }}>{a.blurb}</p>
|
|
61
|
+
</button>
|
|
62
|
+
);
|
|
63
|
+
})}
|
|
64
|
+
</div>
|
|
65
|
+
|
|
66
|
+
<div className="actions" style={{ marginTop: '1rem' }}>
|
|
67
|
+
<button className="secondary" onClick={back}>Back</button>
|
|
68
|
+
<button onClick={next} disabled={state.adapter !== 'discord' && state.adapter !== 'telegram'}>
|
|
69
|
+
Next: install adapter
|
|
70
|
+
</button>
|
|
71
|
+
</div>
|
|
72
|
+
</>
|
|
73
|
+
);
|
|
74
|
+
}
|
|
@@ -0,0 +1,49 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Step 9 — Done.
|
|
3
|
+
*
|
|
4
|
+
* Terminal state. Links to the agent group's detail page so the operator
|
|
5
|
+
* can see the live session and inspect status. Does NOT clear the
|
|
6
|
+
* localStorage state — re-opening /setup just lands here, which is the
|
|
7
|
+
* desired behavior (you can revisit any step via the indicator).
|
|
8
|
+
*/
|
|
9
|
+
import { Link } from 'react-router-dom';
|
|
10
|
+
import { ADAPTER_LABELS, SETUP_STORAGE_KEY, type StepProps } from './types.ts';
|
|
11
|
+
|
|
12
|
+
export function DoneStep({ state }: StepProps) {
|
|
13
|
+
const folder = state.agentGroupFolder;
|
|
14
|
+
const channel = state.adapter ? ADAPTER_LABELS[state.adapter] : 'your channel';
|
|
15
|
+
return (
|
|
16
|
+
<>
|
|
17
|
+
<h3>Done.</h3>
|
|
18
|
+
<p>
|
|
19
|
+
<code>{state.agentGroupName ?? folder}</code> is wired to {channel} and your first inbound has round-tripped.
|
|
20
|
+
</p>
|
|
21
|
+
|
|
22
|
+
<div className="actions" style={{ marginTop: '1rem' }}>
|
|
23
|
+
{folder && (
|
|
24
|
+
<Link to={`/groups/${encodeURIComponent(folder)}`}>
|
|
25
|
+
<button>Open group</button>
|
|
26
|
+
</Link>
|
|
27
|
+
)}
|
|
28
|
+
<Link to="/">
|
|
29
|
+
<button className="secondary">All groups</button>
|
|
30
|
+
</Link>
|
|
31
|
+
<button
|
|
32
|
+
className="secondary"
|
|
33
|
+
onClick={() => {
|
|
34
|
+
if (
|
|
35
|
+
confirm(
|
|
36
|
+
'Clear local wizard state? Returning to /agent/setup later will restart from step 1 (your installed adapters / agent groups / wired channels are unaffected).',
|
|
37
|
+
)
|
|
38
|
+
) {
|
|
39
|
+
localStorage.removeItem(SETUP_STORAGE_KEY);
|
|
40
|
+
window.location.href = '/';
|
|
41
|
+
}
|
|
42
|
+
}}
|
|
43
|
+
>
|
|
44
|
+
Clear wizard state
|
|
45
|
+
</button>
|
|
46
|
+
</div>
|
|
47
|
+
</>
|
|
48
|
+
);
|
|
49
|
+
}
|