@openparachute/agent 0.1.1 → 0.2.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/.parachute/module.json +124 -8
- package/LICENSE +2 -16
- package/README.md +118 -166
- package/package.json +32 -43
- package/scripts/spawn-agent.ts +371 -0
- package/src/_parked/interactive-spawn.test.ts +324 -0
- package/src/_parked/interactive-spawn.ts +701 -0
- package/src/agent-defs.test.ts +1504 -0
- package/src/agent-defs.ts +1702 -0
- package/src/agent-mcp-config.test.ts +115 -0
- package/src/agent-mcp-config.ts +115 -0
- package/src/agents.test.ts +360 -0
- package/src/agents.ts +379 -0
- package/src/auth.test.ts +46 -0
- package/src/auth.ts +140 -0
- package/src/backends/attached-queue.test.ts +376 -0
- package/src/backends/attached-queue.ts +372 -0
- package/src/backends/programmatic.test.ts +1715 -0
- package/src/backends/programmatic.ts +927 -0
- package/src/backends/registry.test.ts +1494 -0
- package/src/backends/registry.ts +1202 -0
- package/src/backends/stream-json.test.ts +570 -0
- package/src/backends/stream-json.ts +392 -0
- package/src/backends/types.ts +223 -0
- package/src/bridge.ts +417 -0
- package/src/channel-backend-wiring.test.ts +237 -0
- package/src/credentials.test.ts +274 -0
- package/src/credentials.ts +380 -0
- package/src/cron.test.ts +342 -0
- package/src/cron.ts +380 -0
- package/src/daemon-agent-def-api.test.ts +166 -0
- package/src/daemon-agent-defs-api.test.ts +953 -0
- package/src/daemon-agent-env-api.test.ts +338 -0
- package/src/daemon-attached-queue-store.test.ts +65 -0
- package/src/daemon-config-api.test.ts +962 -0
- package/src/daemon-jobs-api.test.ts +271 -0
- package/src/daemon-vault-chat.test.ts +250 -0
- package/src/daemon.test.ts +746 -0
- package/src/daemon.ts +3314 -0
- package/src/def-vaults.test.ts +136 -0
- package/src/def-vaults.ts +165 -0
- package/src/delivery-state.test.ts +110 -0
- package/src/delivery-state.ts +154 -0
- package/src/effective-env.test.ts +114 -0
- package/src/effective-env.ts +184 -0
- package/src/env-compat.ts +39 -0
- package/src/grants.test.ts +638 -0
- package/src/grants.ts +675 -0
- package/src/hub-jwt.test.ts +161 -0
- package/src/hub-jwt.ts +182 -0
- package/src/jobs.test.ts +245 -0
- package/src/jobs.ts +266 -0
- package/src/mcp-http.test.ts +265 -0
- package/src/mcp-http.ts +771 -0
- package/src/mint-token.test.ts +152 -0
- package/src/mint-token.ts +139 -0
- package/src/module-manifest.test.ts +158 -0
- package/src/oauth-discovery.ts +134 -0
- package/src/programmatic-wiring.test.ts +838 -0
- package/src/registry.test.ts +227 -0
- package/src/registry.ts +228 -0
- package/src/resolve-port.test.ts +64 -0
- package/src/routing.test.ts +184 -0
- package/src/routing.ts +76 -0
- package/src/runner.test.ts +506 -0
- package/src/runner.ts +255 -0
- package/src/sandbox/config.test.ts +150 -0
- package/src/sandbox/config.ts +102 -0
- package/src/sandbox/egress.test.ts +113 -0
- package/src/sandbox/egress.ts +123 -0
- package/src/sandbox/index.ts +180 -0
- package/src/sandbox/live-seatbelt.test.ts +277 -0
- package/src/sandbox/mounts.test.ts +154 -0
- package/src/sandbox/mounts.ts +133 -0
- package/src/sandbox/sandbox.test.ts +168 -0
- package/src/sandbox/types.ts +382 -0
- package/src/services-manifest.test.ts +106 -0
- package/src/services-manifest.ts +95 -0
- package/src/spa-serve.test.ts +116 -0
- package/src/spa-serve.ts +116 -0
- package/src/spawn-agent-cli.test.ts +172 -0
- package/src/spawn-agent.test.ts +1218 -0
- package/src/spawn-agent.ts +569 -0
- package/src/spawn-deps.test.ts +54 -0
- package/src/spawn-deps.ts +166 -0
- package/src/telegram/api.ts +153 -0
- package/src/terminal-assets.test.ts +50 -0
- package/src/terminal-assets.ts +79 -0
- package/src/terminal-ui.ts +305 -0
- package/src/terminal.test.ts +530 -0
- package/src/terminal.ts +458 -0
- package/src/transport.ts +270 -0
- package/src/transports/http-ui.test.ts +455 -0
- package/src/transports/http-ui.ts +201 -0
- package/src/transports/telegram.test.ts +174 -0
- package/src/transports/telegram.ts +426 -0
- package/src/transports/vault.test.ts +2011 -0
- package/src/transports/vault.ts +1790 -0
- package/src/ui-kit.test.ts +178 -0
- package/src/ui-kit.ts +402 -0
- package/tsconfig.json +8 -14
- package/web/ui/tsconfig.json +2 -1
- package/.claude/scheduled_tasks.lock +0 -1
- package/.claude/settings.json +0 -5
- package/.claude/skills/add-atomic-chat-tool/SKILL.md +0 -243
- package/.claude/skills/add-atomic-chat-tool/atomic-chat-mcp-stdio.ts +0 -229
- package/.claude/skills/add-codex/SKILL.md +0 -161
- package/.claude/skills/add-dashboard/SKILL.md +0 -138
- package/.claude/skills/add-dashboard/resources/dashboard-pusher.ts +0 -495
- package/.claude/skills/add-emacs/SKILL.md +0 -296
- package/.claude/skills/add-gcal-tool/SKILL.md +0 -210
- package/.claude/skills/add-gchat/REMOVE.md +0 -6
- package/.claude/skills/add-gchat/SKILL.md +0 -92
- package/.claude/skills/add-gchat/VERIFY.md +0 -3
- package/.claude/skills/add-github/REMOVE.md +0 -6
- package/.claude/skills/add-github/SKILL.md +0 -148
- package/.claude/skills/add-github/VERIFY.md +0 -3
- package/.claude/skills/add-gmail-tool/SKILL.md +0 -229
- package/.claude/skills/add-imessage/REMOVE.md +0 -6
- package/.claude/skills/add-imessage/SKILL.md +0 -113
- package/.claude/skills/add-imessage/VERIFY.md +0 -3
- package/.claude/skills/add-karpathy-llm-wiki/SKILL.md +0 -110
- package/.claude/skills/add-karpathy-llm-wiki/llm-wiki.md +0 -75
- package/.claude/skills/add-linear/REMOVE.md +0 -6
- package/.claude/skills/add-linear/SKILL.md +0 -168
- package/.claude/skills/add-linear/VERIFY.md +0 -3
- package/.claude/skills/add-macos-statusbar/SKILL.md +0 -133
- package/.claude/skills/add-macos-statusbar/add/src/statusbar.swift +0 -147
- package/.claude/skills/add-matrix/REMOVE.md +0 -6
- package/.claude/skills/add-matrix/SKILL.md +0 -148
- package/.claude/skills/add-matrix/VERIFY.md +0 -3
- package/.claude/skills/add-ollama-provider/SKILL.md +0 -179
- package/.claude/skills/add-ollama-tool/SKILL.md +0 -193
- package/.claude/skills/add-opencode/SKILL.md +0 -229
- package/.claude/skills/add-parallel/SKILL.md +0 -290
- package/.claude/skills/add-resend/REMOVE.md +0 -6
- package/.claude/skills/add-resend/SKILL.md +0 -93
- package/.claude/skills/add-resend/VERIFY.md +0 -3
- package/.claude/skills/add-signal/REMOVE.md +0 -13
- package/.claude/skills/add-signal/SKILL.md +0 -318
- package/.claude/skills/add-signal/VERIFY.md +0 -5
- package/.claude/skills/add-slack/REMOVE.md +0 -6
- package/.claude/skills/add-slack/SKILL.md +0 -112
- package/.claude/skills/add-slack/VERIFY.md +0 -3
- package/.claude/skills/add-teams/REMOVE.md +0 -6
- package/.claude/skills/add-teams/SKILL.md +0 -207
- package/.claude/skills/add-teams/VERIFY.md +0 -3
- package/.claude/skills/add-vercel/SKILL.md +0 -147
- package/.claude/skills/add-vercel/container-skills/vercel-cli/SKILL.md +0 -103
- package/.claude/skills/add-webex/REMOVE.md +0 -6
- package/.claude/skills/add-webex/SKILL.md +0 -88
- package/.claude/skills/add-webex/VERIFY.md +0 -3
- package/.claude/skills/add-wechat/REMOVE.md +0 -49
- package/.claude/skills/add-wechat/SKILL.md +0 -170
- package/.claude/skills/add-wechat/scripts/wire-dm.ts +0 -172
- package/.claude/skills/add-whatsapp/SKILL.md +0 -264
- package/.claude/skills/add-whatsapp-cloud/REMOVE.md +0 -6
- package/.claude/skills/add-whatsapp-cloud/SKILL.md +0 -95
- package/.claude/skills/add-whatsapp-cloud/VERIFY.md +0 -3
- package/.claude/skills/claw/SKILL.md +0 -131
- package/.claude/skills/claw/scripts/claw +0 -374
- package/.claude/skills/convert-to-apple-container/SKILL.md +0 -212
- package/.claude/skills/customize/SKILL.md +0 -110
- package/.claude/skills/debug/SKILL.md +0 -349
- package/.claude/skills/get-qodo-rules/SKILL.md +0 -122
- package/.claude/skills/get-qodo-rules/references/output-format.md +0 -41
- package/.claude/skills/get-qodo-rules/references/pagination.md +0 -33
- package/.claude/skills/get-qodo-rules/references/repository-scope.md +0 -26
- package/.claude/skills/init-first-agent/SKILL.md +0 -120
- package/.claude/skills/init-onecli/SKILL.md +0 -270
- package/.claude/skills/manage-channels/SKILL.md +0 -87
- package/.claude/skills/manage-mounts/SKILL.md +0 -47
- package/.claude/skills/migrate-from-openclaw/MIGRATE_CRONS.md +0 -100
- package/.claude/skills/migrate-from-openclaw/SKILL.md +0 -447
- package/.claude/skills/migrate-from-openclaw/scripts/discover-openclaw.ts +0 -734
- package/.claude/skills/migrate-from-openclaw/scripts/extract-channel-credentials.ts +0 -476
- package/.claude/skills/migrate-nanoclaw/SKILL.md +0 -484
- package/.claude/skills/migrate-nanoclaw/diagnostics.md +0 -51
- package/.claude/skills/qodo-pr-resolver/SKILL.md +0 -326
- package/.claude/skills/qodo-pr-resolver/resources/providers.md +0 -329
- package/.claude/skills/update-nanoclaw/SKILL.md +0 -243
- package/.claude/skills/update-nanoclaw/diagnostics.md +0 -48
- package/.claude/skills/update-skills/SKILL.md +0 -130
- package/.claude/skills/use-native-credential-proxy/SKILL.md +0 -167
- package/.claude/skills/x-integration/SKILL.md +0 -417
- package/.claude/skills/x-integration/agent.ts +0 -243
- package/.claude/skills/x-integration/host.ts +0 -155
- package/.claude/skills/x-integration/lib/browser.ts +0 -148
- package/.claude/skills/x-integration/lib/config.ts +0 -62
- package/.claude/skills/x-integration/scripts/like.ts +0 -56
- package/.claude/skills/x-integration/scripts/post.ts +0 -66
- package/.claude/skills/x-integration/scripts/quote.ts +0 -80
- package/.claude/skills/x-integration/scripts/reply.ts +0 -74
- package/.claude/skills/x-integration/scripts/retweet.ts +0 -62
- package/.claude/skills/x-integration/scripts/setup.ts +0 -87
- package/.github/CODEOWNERS +0 -10
- package/.github/PULL_REQUEST_TEMPLATE.md +0 -18
- package/.github/workflows/bump-version.yml +0 -35
- package/.github/workflows/ci.yml +0 -39
- package/.github/workflows/label-pr.yml +0 -40
- package/.github/workflows/update-tokens.yml +0 -43
- package/.husky/pre-commit +0 -1
- package/.mcp.json +0 -3
- package/.nvmrc +0 -1
- package/.prettierrc +0 -4
- package/CHANGELOG.md +0 -221
- package/CLAUDE.md +0 -307
- package/CODE_OF_CONDUCT.md +0 -128
- package/CONTRIBUTING.md +0 -159
- package/CONTRIBUTORS.md +0 -26
- package/LICENSE-NANOCLAW-MIT +0 -21
- package/README_ja.md +0 -194
- package/README_zh.md +0 -194
- 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 +0 -25
- package/container/.dockerignore +0 -2
- package/container/CLAUDE.md +0 -21
- package/container/Dockerfile +0 -121
- package/container/agent-runner/bun.lock +0 -243
- package/container/agent-runner/package.json +0 -22
- package/container/agent-runner/scripts/sdk-signal-probe.ts +0 -169
- package/container/agent-runner/src/config.ts +0 -55
- package/container/agent-runner/src/db/connection.ts +0 -267
- package/container/agent-runner/src/db/index.ts +0 -20
- package/container/agent-runner/src/db/messages-in.ts +0 -138
- package/container/agent-runner/src/db/messages-out.ts +0 -143
- package/container/agent-runner/src/db/session-routing.ts +0 -30
- package/container/agent-runner/src/db/session-state.test.ts +0 -100
- package/container/agent-runner/src/db/session-state.ts +0 -79
- package/container/agent-runner/src/destinations.ts +0 -135
- package/container/agent-runner/src/formatter.test.ts +0 -167
- package/container/agent-runner/src/formatter.ts +0 -260
- package/container/agent-runner/src/index.ts +0 -110
- package/container/agent-runner/src/integration.test.ts +0 -121
- package/container/agent-runner/src/mcp-tools/agents.instructions.md +0 -26
- package/container/agent-runner/src/mcp-tools/agents.ts +0 -66
- package/container/agent-runner/src/mcp-tools/core.instructions.md +0 -27
- package/container/agent-runner/src/mcp-tools/core.ts +0 -262
- package/container/agent-runner/src/mcp-tools/index.ts +0 -22
- package/container/agent-runner/src/mcp-tools/interactive.instructions.md +0 -22
- package/container/agent-runner/src/mcp-tools/interactive.ts +0 -169
- package/container/agent-runner/src/mcp-tools/scheduling.instructions.md +0 -40
- package/container/agent-runner/src/mcp-tools/scheduling.ts +0 -299
- package/container/agent-runner/src/mcp-tools/self-mod.instructions.md +0 -25
- package/container/agent-runner/src/mcp-tools/self-mod.ts +0 -120
- package/container/agent-runner/src/mcp-tools/server.ts +0 -54
- package/container/agent-runner/src/mcp-tools/types.ts +0 -6
- package/container/agent-runner/src/poll-loop.test.ts +0 -248
- package/container/agent-runner/src/poll-loop.ts +0 -437
- package/container/agent-runner/src/providers/claude.ts +0 -379
- package/container/agent-runner/src/providers/factory.test.ts +0 -19
- package/container/agent-runner/src/providers/factory.ts +0 -13
- package/container/agent-runner/src/providers/index.ts +0 -6
- package/container/agent-runner/src/providers/mock.ts +0 -77
- package/container/agent-runner/src/providers/provider-registry.ts +0 -33
- package/container/agent-runner/src/providers/types.ts +0 -82
- package/container/agent-runner/src/scheduling/task-script.ts +0 -121
- package/container/agent-runner/src/timezone.test.ts +0 -93
- package/container/agent-runner/src/timezone.ts +0 -107
- package/container/agent-runner/tsconfig.json +0 -14
- package/container/build.sh +0 -48
- package/container/entrypoint.sh +0 -16
- package/container/skills/agent-browser/SKILL.md +0 -159
- package/container/skills/frontend-engineer/SKILL.md +0 -157
- package/container/skills/self-customize/SKILL.md +0 -87
- package/container/skills/slack-formatting/SKILL.md +0 -94
- package/container/skills/vercel-cli/SKILL.md +0 -111
- package/container/skills/welcome/SKILL.md +0 -85
- package/docs/APPLE-CONTAINER-NETWORKING.md +0 -90
- package/docs/BRANCH-FORK-MAINTENANCE.md +0 -81
- package/docs/README.md +0 -25
- package/docs/SDK_DEEP_DIVE.md +0 -643
- package/docs/SECURITY.md +0 -162
- package/docs/agent-runner-details.md +0 -749
- package/docs/api-details.md +0 -365
- package/docs/architecture-diagram.html +0 -422
- package/docs/architecture-diagram.md +0 -215
- package/docs/architecture.md +0 -751
- package/docs/audit/2026-04-30-channel-endpoint-audit.md +0 -36
- package/docs/build-and-runtime.md +0 -80
- package/docs/cross-mount-stress/README.md +0 -112
- package/docs/cross-mount-stress/container-writer-retry.mjs +0 -55
- package/docs/cross-mount-stress/container-writer-slow.mjs +0 -42
- package/docs/cross-mount-stress/container-writer.mjs +0 -47
- package/docs/cross-mount-stress/host-writer-retry.mjs +0 -55
- package/docs/cross-mount-stress/host-writer-slow.mjs +0 -43
- package/docs/cross-mount-stress/host-writer.mjs +0 -47
- package/docs/db-central.md +0 -316
- package/docs/db-session.md +0 -183
- package/docs/db.md +0 -119
- package/docs/design/2026-04-29-vault-management-ui.md +0 -231
- package/docs/design/2026-04-30-channel-wiring-rework.md +0 -234
- package/docs/design/2026-05-01-channel-wiring-approvals-deep-dive.md +0 -272
- package/docs/design/2026-05-02-channel-policy-and-approval-routing.md +0 -250
- package/docs/docker-sandboxes.md +0 -359
- package/docs/isolation-model.md +0 -88
- package/docs/ollama.md +0 -79
- package/docs/parachute-integration.md +0 -109
- package/docs/post-night-rebirth-reflections.md +0 -151
- package/eslint.config.js +0 -32
- package/pnpm-workspace.yaml +0 -8
- package/repo-tokens/README.md +0 -113
- package/repo-tokens/action.yml +0 -186
- package/repo-tokens/badge.svg +0 -23
- package/repo-tokens/examples/green.svg +0 -14
- package/repo-tokens/examples/red.svg +0 -14
- package/repo-tokens/examples/yellow-green.svg +0 -14
- package/repo-tokens/examples/yellow.svg +0 -14
- package/scripts/chat.ts +0 -101
- package/scripts/cleanup-sessions.sh +0 -150
- package/scripts/init-cli-agent.ts +0 -171
- package/scripts/init-first-agent.ts +0 -377
- package/scripts/parachute.ts +0 -158
- package/scripts/run-migrations.ts +0 -105
- package/scripts/sanity-live-poll.ts +0 -95
- package/scripts/seed-discord.ts +0 -79
- package/scripts/test-v2-agent.ts +0 -106
- package/scripts/test-v2-channel-e2e.ts +0 -265
- package/scripts/test-v2-host.ts +0 -184
- package/src/channels/adapter.ts +0 -214
- package/src/channels/ask-question.ts +0 -46
- package/src/channels/channel-registry.test.ts +0 -421
- package/src/channels/channel-registry.ts +0 -313
- package/src/channels/chat-sdk-bridge.test.ts +0 -84
- package/src/channels/chat-sdk-bridge.ts +0 -652
- package/src/channels/cli.ts +0 -276
- package/src/channels/discord.ts +0 -90
- package/src/channels/index.ts +0 -17
- package/src/channels/telegram-markdown-sanitize.test.ts +0 -78
- package/src/channels/telegram-markdown-sanitize.ts +0 -55
- package/src/channels/telegram-pairing.test.ts +0 -254
- package/src/channels/telegram-pairing.ts +0 -339
- package/src/channels/telegram.ts +0 -279
- package/src/channels/trust-hint.test.ts +0 -48
- package/src/channels/trust-hint.ts +0 -75
- package/src/claude-md-compose.migrate.test.ts +0 -64
- package/src/claude-md-compose.ts +0 -205
- package/src/command-gate.ts +0 -63
- package/src/config.test.ts +0 -93
- package/src/config.ts +0 -108
- package/src/container-config.ts +0 -167
- package/src/container-runner.test.ts +0 -32
- package/src/container-runner.ts +0 -576
- package/src/container-runtime.test.ts +0 -169
- package/src/container-runtime.ts +0 -92
- package/src/db/_bun-sqlite-shim.ts +0 -88
- package/src/db/agent-activity.test.ts +0 -155
- package/src/db/agent-activity.ts +0 -121
- package/src/db/agent-groups.ts +0 -77
- package/src/db/connection.migrate.test.ts +0 -143
- package/src/db/connection.ts +0 -224
- package/src/db/db-v2.test.ts +0 -440
- package/src/db/dropped-messages.ts +0 -44
- package/src/db/index.ts +0 -40
- package/src/db/messaging-groups.ts +0 -252
- package/src/db/migrations/001-initial.ts +0 -112
- package/src/db/migrations/002-chat-sdk-state.ts +0 -36
- package/src/db/migrations/008-dropped-messages.ts +0 -27
- package/src/db/migrations/009-drop-pending-credentials.ts +0 -13
- package/src/db/migrations/010-engage-modes.ts +0 -103
- package/src/db/migrations/011-pending-sender-approvals.ts +0 -40
- package/src/db/migrations/012-channel-registration.ts +0 -48
- package/src/db/migrations/013-approval-render-metadata.ts +0 -27
- package/src/db/migrations/014-secrets.ts +0 -44
- package/src/db/migrations/015-secrets-drop-host-pattern.ts +0 -18
- package/src/db/migrations/016-secret-assignments.ts +0 -30
- package/src/db/migrations/017-agent-activity.ts +0 -40
- package/src/db/migrations/018-oauth-app-configs.ts +0 -34
- package/src/db/migrations/019-oauth-app-connections.ts +0 -48
- package/src/db/migrations/020-agent-app-connections.ts +0 -28
- package/src/db/migrations/021-pending-oauth-states.ts +0 -35
- package/src/db/migrations/022-app-connections-provider.ts +0 -25
- package/src/db/migrations/023-agent-group-secret-mode.test.ts +0 -124
- package/src/db/migrations/023-agent-group-secret-mode.ts +0 -65
- package/src/db/migrations/024-collapse-approvals.test.ts +0 -249
- package/src/db/migrations/024-collapse-approvals.ts +0 -182
- package/src/db/migrations/025-secret-mode-check.test.ts +0 -155
- package/src/db/migrations/025-secret-mode-check.ts +0 -49
- package/src/db/migrations/026-user-dms-bot-id.test.ts +0 -116
- package/src/db/migrations/026-user-dms-bot-id.ts +0 -54
- package/src/db/migrations/027-provider-credentials.ts +0 -41
- package/src/db/migrations/_test-helpers.ts +0 -41
- package/src/db/migrations/index.ts +0 -127
- package/src/db/migrations/module-agent-to-agent-destinations.ts +0 -84
- package/src/db/migrations/module-approvals-pending-approvals.ts +0 -42
- package/src/db/migrations/module-approvals-title-options.ts +0 -40
- package/src/db/schema.ts +0 -258
- package/src/db/session-db.test.ts +0 -93
- package/src/db/session-db.ts +0 -325
- package/src/db/sessions.ts +0 -241
- package/src/delivery.test.ts +0 -148
- package/src/delivery.ts +0 -445
- package/src/env.ts +0 -74
- package/src/group-folder.test.ts +0 -35
- package/src/group-folder.ts +0 -44
- package/src/group-init.ts +0 -92
- package/src/host-core.test.ts +0 -456
- package/src/host-sweep.test.ts +0 -146
- package/src/host-sweep.ts +0 -287
- package/src/index.ts +0 -227
- package/src/install-slug.ts +0 -33
- package/src/log.test.ts +0 -81
- package/src/log.ts +0 -117
- package/src/mcp/http.ts +0 -72
- package/src/mcp/server.ts +0 -92
- package/src/mcp/stdio.ts +0 -51
- package/src/mcp/tools/activity.ts +0 -88
- package/src/mcp/tools/agent-groups.ts +0 -183
- package/src/mcp/tools/approvals.ts +0 -122
- package/src/mcp/tools/channels.ts +0 -199
- package/src/mcp/tools/index.ts +0 -27
- package/src/mcp/tools/oauth.ts +0 -48
- package/src/mcp/tools/secrets.ts +0 -169
- package/src/mcp/tools/sessions.ts +0 -135
- package/src/mcp/types.ts +0 -51
- package/src/modules/agent-to-agent/agent-route.test.ts +0 -46
- package/src/modules/agent-to-agent/agent-route.ts +0 -223
- package/src/modules/agent-to-agent/create-agent.ts +0 -127
- package/src/modules/agent-to-agent/db/agent-destinations.ts +0 -135
- package/src/modules/agent-to-agent/index.ts +0 -22
- package/src/modules/agent-to-agent/write-destinations.ts +0 -59
- package/src/modules/approvals/agent.md +0 -45
- package/src/modules/approvals/index.ts +0 -21
- package/src/modules/approvals/picks.test.ts +0 -291
- package/src/modules/approvals/primitive.ts +0 -279
- package/src/modules/approvals/project.md +0 -27
- package/src/modules/approvals/response-handler.ts +0 -87
- package/src/modules/index.ts +0 -24
- package/src/modules/interactive/agent.md +0 -21
- package/src/modules/interactive/index.ts +0 -69
- package/src/modules/interactive/project.md +0 -12
- package/src/modules/mount-security/index.ts +0 -448
- package/src/modules/mount-security/migrate.test.ts +0 -91
- package/src/modules/permissions/access.ts +0 -28
- package/src/modules/permissions/channel-approval.test.ts +0 -389
- package/src/modules/permissions/channel-approval.ts +0 -188
- package/src/modules/permissions/db/agent-group-members.ts +0 -44
- package/src/modules/permissions/db/pending-channel-approvals.test.ts +0 -86
- package/src/modules/permissions/db/pending-channel-approvals.ts +0 -66
- package/src/modules/permissions/db/pending-sender-approvals.ts +0 -60
- package/src/modules/permissions/db/user-dms.ts +0 -58
- package/src/modules/permissions/db/user-roles.ts +0 -85
- package/src/modules/permissions/db/users.ts +0 -38
- package/src/modules/permissions/index.ts +0 -421
- package/src/modules/permissions/permissions.test.ts +0 -358
- package/src/modules/permissions/sender-approval.test.ts +0 -470
- package/src/modules/permissions/sender-approval.ts +0 -165
- package/src/modules/permissions/user-dm.ts +0 -200
- package/src/modules/provider-credentials/db.ts +0 -121
- package/src/modules/provider-credentials/index.ts +0 -12
- package/src/modules/provider-credentials/spawn.test.ts +0 -206
- package/src/modules/provider-credentials/spawn.ts +0 -114
- package/src/modules/scheduling/actions.ts +0 -113
- package/src/modules/scheduling/db.test.ts +0 -282
- package/src/modules/scheduling/db.ts +0 -148
- package/src/modules/scheduling/index.ts +0 -34
- package/src/modules/scheduling/recurrence.test.ts +0 -98
- package/src/modules/scheduling/recurrence.ts +0 -54
- package/src/modules/self-mod/agent.md +0 -30
- package/src/modules/self-mod/apply.ts +0 -85
- package/src/modules/self-mod/index.ts +0 -30
- package/src/modules/self-mod/project.md +0 -39
- package/src/modules/self-mod/request.ts +0 -91
- package/src/modules/typing/index.ts +0 -165
- package/src/oauth/agent-app-connections.ts +0 -103
- package/src/oauth/app-configs.test.ts +0 -64
- package/src/oauth/app-configs.ts +0 -114
- package/src/oauth/app-connections.test.ts +0 -109
- package/src/oauth/app-connections.ts +0 -178
- package/src/oauth/crypto.ts +0 -56
- package/src/oauth/flow.ts +0 -104
- package/src/oauth/providers/google.test.ts +0 -38
- package/src/oauth/providers/google.ts +0 -46
- package/src/oauth/providers/index.ts +0 -48
- package/src/oauth/state-store.test.ts +0 -54
- package/src/oauth/state-store.ts +0 -93
- package/src/parachute/README.md +0 -27
- package/src/parachute/create-agent.test.ts +0 -83
- package/src/parachute/create-agent.ts +0 -122
- package/src/parachute/group-status.test.ts +0 -165
- package/src/parachute/group-status.ts +0 -136
- package/src/parachute/types.ts +0 -41
- package/src/parachute/vault-mcp.test.ts +0 -251
- package/src/parachute/vault-mcp.ts +0 -232
- package/src/platform-id.test.ts +0 -104
- package/src/platform-id.ts +0 -109
- package/src/providers/index.ts +0 -6
- package/src/providers/provider-container-registry.ts +0 -58
- package/src/response-registry.ts +0 -45
- package/src/router.ts +0 -530
- package/src/secrets/crypto.test.ts +0 -45
- package/src/secrets/crypto.ts +0 -55
- package/src/secrets/index.ts +0 -355
- package/src/secrets/master-key.ts +0 -70
- package/src/secrets/secrets.test.ts +0 -354
- package/src/session-manager.migrate.test.ts +0 -59
- package/src/session-manager.ts +0 -433
- package/src/startup-bootstrap.test.ts +0 -226
- package/src/startup-bootstrap.ts +0 -207
- package/src/state-sqlite.ts +0 -182
- package/src/timezone.test.ts +0 -64
- package/src/timezone.ts +0 -37
- package/src/types.ts +0 -230
- package/src/web/auth.test.ts +0 -335
- package/src/web/auth.ts +0 -214
- package/src/web/discord-validate.test.ts +0 -77
- package/src/web/discord-validate.ts +0 -88
- package/src/web/hub-discovery.test.ts +0 -98
- package/src/web/hub-discovery.ts +0 -69
- package/src/web/routes/activity.ts +0 -106
- package/src/web/routes/agent-provider.test.ts +0 -282
- package/src/web/routes/agent-provider.ts +0 -309
- package/src/web/routes/approvals.ts +0 -185
- package/src/web/routes/apps.ts +0 -434
- package/src/web/routes/channels-mg-detail.test.ts +0 -324
- package/src/web/routes/channels-mga-detail.test.ts +0 -425
- package/src/web/routes/channels.ts +0 -489
- package/src/web/routes/oauth-providers.ts +0 -42
- package/src/web/routes/secrets.test.ts +0 -175
- package/src/web/routes/secrets.ts +0 -282
- package/src/web/routes/sessions.ts +0 -123
- package/src/web/routes/settings.test.ts +0 -106
- package/src/web/routes/settings.ts +0 -247
- package/src/web/routes/setup-status.ts +0 -205
- package/src/web/routes/vaults.test.ts +0 -389
- package/src/web/routes/vaults.ts +0 -225
- package/src/web/server-version.test.ts +0 -16
- package/src/web/server.ts +0 -1003
- package/src/web/services-manifest.test.ts +0 -120
- package/src/web/services-manifest.ts +0 -61
- package/src/web/static-serve.test.ts +0 -255
- package/src/web/static-serve.ts +0 -104
- package/src/web/telegram-validate.test.ts +0 -116
- package/src/web/telegram-validate.ts +0 -107
- package/src/web/vault-proxy.test.ts +0 -214
- package/src/web/vault-proxy.ts +0 -120
- package/src/web/wire-channel.ts +0 -181
- package/src/webhook-server.ts +0 -134
- package/vitest.config.ts +0 -18
- package/web/README.md +0 -63
- package/web/ui/index.html +0 -13
- package/web/ui/package.json +0 -35
- package/web/ui/pnpm-lock.yaml +0 -2164
- package/web/ui/scripts/verify-base.mjs +0 -31
- package/web/ui/src/App.tsx +0 -88
- package/web/ui/src/components/ActivityFeed.tsx +0 -444
- package/web/ui/src/components/AgentGroupPicker.tsx +0 -263
- package/web/ui/src/components/AgentProviderCards.tsx +0 -220
- package/web/ui/src/components/CredentialForm.tsx +0 -214
- package/web/ui/src/components/ScopeGrants.tsx +0 -74
- package/web/ui/src/components/StatusDot.tsx +0 -43
- package/web/ui/src/components/VaultPicker.tsx +0 -127
- package/web/ui/src/components/setup/AdapterInstallStep.tsx +0 -178
- package/web/ui/src/components/setup/AgentGroupStep.tsx +0 -43
- package/web/ui/src/components/setup/ChannelPickStep.tsx +0 -74
- package/web/ui/src/components/setup/DoneStep.tsx +0 -49
- package/web/ui/src/components/setup/PrereqStep.tsx +0 -129
- package/web/ui/src/components/setup/TestConnectionStep.tsx +0 -108
- package/web/ui/src/components/setup/TestMessageStep.tsx +0 -104
- package/web/ui/src/components/setup/WireChannelStep.tsx +0 -166
- package/web/ui/src/components/setup/types.ts +0 -105
- package/web/ui/src/lib/api.test.ts +0 -410
- package/web/ui/src/lib/api.ts +0 -1210
- package/web/ui/src/lib/auth.test.ts +0 -139
- package/web/ui/src/lib/auth.ts +0 -348
- package/web/ui/src/lib/channel-adapters.ts +0 -136
- package/web/ui/src/main.tsx +0 -19
- package/web/ui/src/routes/ApprovalsList.tsx +0 -294
- package/web/ui/src/routes/Apps.tsx +0 -613
- package/web/ui/src/routes/ChannelWireDetail.test.tsx +0 -233
- package/web/ui/src/routes/ChannelWireDetail.tsx +0 -403
- package/web/ui/src/routes/ChannelsList.tsx +0 -158
- package/web/ui/src/routes/GroupDetail.tsx +0 -755
- package/web/ui/src/routes/GroupList.tsx +0 -187
- package/web/ui/src/routes/MessagingGroupDetail.test.tsx +0 -233
- package/web/ui/src/routes/MessagingGroupDetail.tsx +0 -306
- package/web/ui/src/routes/NewGroupWizard.tsx +0 -390
- package/web/ui/src/routes/OAuthCallback.tsx +0 -56
- package/web/ui/src/routes/SecretsList.tsx +0 -921
- package/web/ui/src/routes/SessionsList.tsx +0 -220
- package/web/ui/src/routes/SettingsAgentProvider.tsx +0 -109
- package/web/ui/src/routes/SettingsApprovals.tsx +0 -234
- package/web/ui/src/routes/SetupWizard.tsx +0 -219
- package/web/ui/src/routes/VaultDetail.test.tsx +0 -361
- package/web/ui/src/routes/VaultDetail.tsx +0 -960
- package/web/ui/src/routes/VaultsList.tsx +0 -295
- package/web/ui/src/routes/WireChannelPage.tsx +0 -413
- package/web/ui/src/styles.css +0 -608
- package/web/ui/src/test/setup.ts +0 -23
- package/web/ui/src/vite-env.d.ts +0 -10
- package/web/ui/vite.config.ts +0 -34
- package/web/ui/vitest.config.ts +0 -25
|
@@ -1,107 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* Validate a Telegram bot token by calling Telegram's `/getMe`.
|
|
3
|
-
*
|
|
4
|
-
* The setup wizard captures a bot token from the operator and needs to verify
|
|
5
|
-
* three things before persisting it: the token authenticates, the account is
|
|
6
|
-
* a bot (`is_bot=true`), and we know the bot's @username for the wizard's
|
|
7
|
-
* "now go DM @<username>" instruction in the test-message step.
|
|
8
|
-
*
|
|
9
|
-
* Telegram returns `{ ok: true, result: { id, username, first_name, is_bot, ... } }`
|
|
10
|
-
* on success; on auth failure the body is `{ ok: false, error_code: 401, description }`.
|
|
11
|
-
* Network failures bubble up as 502 with a clear unreachable message — same
|
|
12
|
-
* shape as discord-validate.ts so the wizard can render either with one
|
|
13
|
-
* surface.
|
|
14
|
-
*
|
|
15
|
-
* We also enforce the BotFather token shape (`<digits>:<35+ chars>`) before
|
|
16
|
-
* calling out: a clearly malformed token wastes a network round-trip and
|
|
17
|
-
* Telegram's 401 response is less useful than a local "token format invalid".
|
|
18
|
-
*
|
|
19
|
-
* The fetch is injectable for tests; in production it shells through the
|
|
20
|
-
* default global `fetch`.
|
|
21
|
-
*/
|
|
22
|
-
const TELEGRAM_API = 'https://api.telegram.org';
|
|
23
|
-
const TOKEN_SHAPE = /^[0-9]+:[A-Za-z0-9_-]{35,}$/;
|
|
24
|
-
|
|
25
|
-
export interface TelegramIdentity {
|
|
26
|
-
id: number;
|
|
27
|
-
username: string;
|
|
28
|
-
firstName: string;
|
|
29
|
-
isBot: boolean;
|
|
30
|
-
}
|
|
31
|
-
|
|
32
|
-
export type TelegramValidateResult =
|
|
33
|
-
| { ok: true; identity: TelegramIdentity }
|
|
34
|
-
| { ok: false; status: number; error: string };
|
|
35
|
-
|
|
36
|
-
interface TelegramGetMeBody {
|
|
37
|
-
ok?: unknown;
|
|
38
|
-
result?: {
|
|
39
|
-
id?: unknown;
|
|
40
|
-
username?: unknown;
|
|
41
|
-
first_name?: unknown;
|
|
42
|
-
is_bot?: unknown;
|
|
43
|
-
};
|
|
44
|
-
description?: unknown;
|
|
45
|
-
}
|
|
46
|
-
|
|
47
|
-
export async function validateTelegramBotToken(
|
|
48
|
-
token: string,
|
|
49
|
-
fetchImpl: typeof fetch = fetch,
|
|
50
|
-
): Promise<TelegramValidateResult> {
|
|
51
|
-
const trimmed = token.trim();
|
|
52
|
-
if (!trimmed) return { ok: false, status: 400, error: 'token is empty' };
|
|
53
|
-
if (!TOKEN_SHAPE.test(trimmed)) {
|
|
54
|
-
return {
|
|
55
|
-
ok: false,
|
|
56
|
-
status: 400,
|
|
57
|
-
error: 'token format invalid (expected <digits>:<35+ chars>, e.g. 123456:ABCdef…)',
|
|
58
|
-
};
|
|
59
|
-
}
|
|
60
|
-
|
|
61
|
-
let res: Response;
|
|
62
|
-
try {
|
|
63
|
-
res = await fetchImpl(`${TELEGRAM_API}/bot${trimmed}/getMe`, {
|
|
64
|
-
headers: { Accept: 'application/json' },
|
|
65
|
-
});
|
|
66
|
-
} catch (err) {
|
|
67
|
-
return {
|
|
68
|
-
ok: false,
|
|
69
|
-
status: 502,
|
|
70
|
-
error: `telegram unreachable: ${err instanceof Error ? err.message : String(err)}`,
|
|
71
|
-
};
|
|
72
|
-
}
|
|
73
|
-
|
|
74
|
-
// Telegram returns 401/404 for bad tokens but always with a JSON body.
|
|
75
|
-
// Parse first, then decide.
|
|
76
|
-
let body: TelegramGetMeBody;
|
|
77
|
-
try {
|
|
78
|
-
body = (await res.json()) as TelegramGetMeBody;
|
|
79
|
-
} catch {
|
|
80
|
-
return { ok: false, status: 502, error: `telegram returned non-JSON body (HTTP ${res.status})` };
|
|
81
|
-
}
|
|
82
|
-
|
|
83
|
-
if (body.ok !== true) {
|
|
84
|
-
const desc = typeof body.description === 'string' ? body.description : `HTTP ${res.status}`;
|
|
85
|
-
// 401 is the canonical "bad token" — surface as 401 for the wizard step.
|
|
86
|
-
const status = res.status === 401 || res.status === 404 ? 401 : res.status || 400;
|
|
87
|
-
return { ok: false, status, error: `telegram rejected token: ${desc}` };
|
|
88
|
-
}
|
|
89
|
-
|
|
90
|
-
const r = body.result;
|
|
91
|
-
if (!r || typeof r.id !== 'number' || typeof r.username !== 'string' || typeof r.first_name !== 'string') {
|
|
92
|
-
return { ok: false, status: 502, error: 'telegram returned malformed getMe body' };
|
|
93
|
-
}
|
|
94
|
-
if (r.is_bot !== true) {
|
|
95
|
-
return { ok: false, status: 400, error: 'token is not a bot token (result.is_bot=false)' };
|
|
96
|
-
}
|
|
97
|
-
|
|
98
|
-
return {
|
|
99
|
-
ok: true,
|
|
100
|
-
identity: {
|
|
101
|
-
id: r.id,
|
|
102
|
-
username: r.username,
|
|
103
|
-
firstName: r.first_name,
|
|
104
|
-
isBot: true,
|
|
105
|
-
},
|
|
106
|
-
};
|
|
107
|
-
}
|
|
@@ -1,214 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* Coverage for the JWT-forwarding helper that paraclaw uses to call vault
|
|
3
|
-
* REST endpoints with the operator's hub-issued session JWT
|
|
4
|
-
* (`docs/design/2026-04-29-vault-management-ui.md` § Admin auth model).
|
|
5
|
-
*
|
|
6
|
-
* The contract under test: pass through the Authorization header verbatim,
|
|
7
|
-
* surface vault status codes verbatim (401/403 must reach the browser so it
|
|
8
|
-
* can trigger an OAuth consent flow), parse JSON bodies, and turn network
|
|
9
|
-
* failures into a 502 — never a thrown exception, because the route handler
|
|
10
|
-
* mirrors `result.status` to the browser unmodified.
|
|
11
|
-
*/
|
|
12
|
-
import { afterEach, beforeEach, describe, expect, it, vi } from 'vitest';
|
|
13
|
-
|
|
14
|
-
import { clearHubDiscoveryCache } from './hub-discovery.js';
|
|
15
|
-
import { forwardToVault, mintVaultTokenHttp, resolveVaultBaseUrl } from './vault-proxy.js';
|
|
16
|
-
|
|
17
|
-
let prevHub: string | undefined;
|
|
18
|
-
|
|
19
|
-
beforeEach(() => {
|
|
20
|
-
prevHub = process.env.PARACHUTE_HUB_ORIGIN;
|
|
21
|
-
delete process.env.PARACHUTE_AGENT_HUB_ORIGIN;
|
|
22
|
-
delete process.env.PARACLAW_HUB_ORIGIN;
|
|
23
|
-
process.env.PARACHUTE_HUB_ORIGIN = 'https://parachute.example';
|
|
24
|
-
clearHubDiscoveryCache();
|
|
25
|
-
});
|
|
26
|
-
|
|
27
|
-
afterEach(() => {
|
|
28
|
-
if (prevHub === undefined) delete process.env.PARACHUTE_HUB_ORIGIN;
|
|
29
|
-
else process.env.PARACHUTE_HUB_ORIGIN = prevHub;
|
|
30
|
-
clearHubDiscoveryCache();
|
|
31
|
-
});
|
|
32
|
-
|
|
33
|
-
function jsonResponse(status: number, body: unknown): Response {
|
|
34
|
-
const text = body === undefined ? '' : JSON.stringify(body);
|
|
35
|
-
return new Response(text, { status, headers: { 'content-type': 'application/json' } });
|
|
36
|
-
}
|
|
37
|
-
|
|
38
|
-
describe('forwardToVault', () => {
|
|
39
|
-
it('GETs the right URL with the operator JWT', async () => {
|
|
40
|
-
const fetchImpl = vi.fn().mockResolvedValue(jsonResponse(200, { tokens: [] }));
|
|
41
|
-
const result = await forwardToVault({
|
|
42
|
-
method: 'GET',
|
|
43
|
-
vaultBaseUrl: 'https://h/vault/work',
|
|
44
|
-
subpath: '/tokens',
|
|
45
|
-
authHeader: 'Bearer the-jwt',
|
|
46
|
-
fetchImpl,
|
|
47
|
-
});
|
|
48
|
-
expect(result.status).toBe(200);
|
|
49
|
-
expect(result.body).toEqual({ tokens: [] });
|
|
50
|
-
expect(fetchImpl).toHaveBeenCalledWith(
|
|
51
|
-
'https://h/vault/work/tokens',
|
|
52
|
-
expect.objectContaining({
|
|
53
|
-
method: 'GET',
|
|
54
|
-
headers: expect.objectContaining({
|
|
55
|
-
Authorization: 'Bearer the-jwt',
|
|
56
|
-
Accept: 'application/json',
|
|
57
|
-
}),
|
|
58
|
-
}),
|
|
59
|
-
);
|
|
60
|
-
});
|
|
61
|
-
|
|
62
|
-
it('POSTs JSON body with content-type header', async () => {
|
|
63
|
-
const fetchImpl = vi.fn().mockResolvedValue(jsonResponse(201, { id: 't_abc', token: 'pvt_x' }));
|
|
64
|
-
await forwardToVault({
|
|
65
|
-
method: 'POST',
|
|
66
|
-
vaultBaseUrl: 'https://h/vault/work',
|
|
67
|
-
subpath: '/tokens',
|
|
68
|
-
authHeader: 'Bearer x',
|
|
69
|
-
body: { label: 'test', scopes: ['vault:read'] },
|
|
70
|
-
fetchImpl,
|
|
71
|
-
});
|
|
72
|
-
expect(fetchImpl).toHaveBeenCalledWith(
|
|
73
|
-
'https://h/vault/work/tokens',
|
|
74
|
-
expect.objectContaining({
|
|
75
|
-
method: 'POST',
|
|
76
|
-
body: JSON.stringify({ label: 'test', scopes: ['vault:read'] }),
|
|
77
|
-
headers: expect.objectContaining({
|
|
78
|
-
'Content-Type': 'application/json',
|
|
79
|
-
}),
|
|
80
|
-
}),
|
|
81
|
-
);
|
|
82
|
-
});
|
|
83
|
-
|
|
84
|
-
it('strips a trailing slash off vaultBaseUrl so subpath joins cleanly', async () => {
|
|
85
|
-
const fetchImpl = vi.fn().mockResolvedValue(jsonResponse(200, {}));
|
|
86
|
-
await forwardToVault({
|
|
87
|
-
method: 'GET',
|
|
88
|
-
vaultBaseUrl: 'https://h/vault/work/',
|
|
89
|
-
subpath: '/tokens',
|
|
90
|
-
authHeader: 'Bearer x',
|
|
91
|
-
fetchImpl,
|
|
92
|
-
});
|
|
93
|
-
expect(fetchImpl).toHaveBeenCalledWith('https://h/vault/work/tokens', expect.anything());
|
|
94
|
-
});
|
|
95
|
-
|
|
96
|
-
it('mirrors a 401 from the vault — caller surfaces it for consent prompt', async () => {
|
|
97
|
-
const fetchImpl = vi.fn().mockResolvedValue(jsonResponse(401, { error: 'missing vault:work:admin' }));
|
|
98
|
-
const result = await forwardToVault({
|
|
99
|
-
method: 'GET',
|
|
100
|
-
vaultBaseUrl: 'https://h/vault/work',
|
|
101
|
-
subpath: '/tokens',
|
|
102
|
-
authHeader: 'Bearer x',
|
|
103
|
-
fetchImpl,
|
|
104
|
-
});
|
|
105
|
-
expect(result.status).toBe(401);
|
|
106
|
-
expect(result.body).toEqual({ error: 'missing vault:work:admin' });
|
|
107
|
-
});
|
|
108
|
-
|
|
109
|
-
it('returns a 502 with structured body when fetch throws', async () => {
|
|
110
|
-
const fetchImpl = vi.fn().mockRejectedValue(new Error('ECONNREFUSED'));
|
|
111
|
-
const result = await forwardToVault({
|
|
112
|
-
method: 'GET',
|
|
113
|
-
vaultBaseUrl: 'https://h/vault/work',
|
|
114
|
-
subpath: '/tokens',
|
|
115
|
-
authHeader: 'Bearer x',
|
|
116
|
-
fetchImpl,
|
|
117
|
-
});
|
|
118
|
-
expect(result.status).toBe(502);
|
|
119
|
-
expect(result.body).toMatchObject({ error: expect.stringContaining('ECONNREFUSED') });
|
|
120
|
-
});
|
|
121
|
-
|
|
122
|
-
it('handles a non-JSON body without throwing', async () => {
|
|
123
|
-
const fetchImpl = vi.fn().mockResolvedValue(new Response('<!DOCTYPE html>504 from edge', { status: 504 }));
|
|
124
|
-
const result = await forwardToVault({
|
|
125
|
-
method: 'GET',
|
|
126
|
-
vaultBaseUrl: 'https://h/vault/work',
|
|
127
|
-
subpath: '/tokens',
|
|
128
|
-
authHeader: 'Bearer x',
|
|
129
|
-
fetchImpl,
|
|
130
|
-
});
|
|
131
|
-
expect(result.status).toBe(504);
|
|
132
|
-
expect(result.body).toMatchObject({ error: expect.stringContaining('non-JSON') });
|
|
133
|
-
});
|
|
134
|
-
|
|
135
|
-
it('handles an empty body by returning null', async () => {
|
|
136
|
-
const fetchImpl = vi.fn().mockResolvedValue(new Response('', { status: 200 }));
|
|
137
|
-
const result = await forwardToVault({
|
|
138
|
-
method: 'DELETE',
|
|
139
|
-
vaultBaseUrl: 'https://h/vault/work',
|
|
140
|
-
subpath: '/tokens/t_abc',
|
|
141
|
-
authHeader: 'Bearer x',
|
|
142
|
-
fetchImpl,
|
|
143
|
-
});
|
|
144
|
-
expect(result.status).toBe(200);
|
|
145
|
-
expect(result.body).toBeNull();
|
|
146
|
-
});
|
|
147
|
-
});
|
|
148
|
-
|
|
149
|
-
describe('resolveVaultBaseUrl', () => {
|
|
150
|
-
it('returns the hub-published URL when the name matches', async () => {
|
|
151
|
-
const fetchImpl = vi.fn().mockResolvedValue(
|
|
152
|
-
jsonResponse(200, {
|
|
153
|
-
vaults: [
|
|
154
|
-
{ name: 'work', url: 'https://h/vault/work', version: '0.4.7' },
|
|
155
|
-
{ name: 'personal', url: 'https://h/vault/personal/', version: '0.4.7' },
|
|
156
|
-
],
|
|
157
|
-
}),
|
|
158
|
-
);
|
|
159
|
-
expect(await resolveVaultBaseUrl('work', fetchImpl)).toBe('https://h/vault/work');
|
|
160
|
-
});
|
|
161
|
-
|
|
162
|
-
it('strips trailing slash from the hub-published URL', async () => {
|
|
163
|
-
const fetchImpl = vi.fn().mockResolvedValue(
|
|
164
|
-
jsonResponse(200, {
|
|
165
|
-
vaults: [{ name: 'personal', url: 'https://h/vault/personal/', version: '0.4.7' }],
|
|
166
|
-
}),
|
|
167
|
-
);
|
|
168
|
-
expect(await resolveVaultBaseUrl('personal', fetchImpl)).toBe('https://h/vault/personal');
|
|
169
|
-
});
|
|
170
|
-
|
|
171
|
-
it('returns null when the vault name is unknown', async () => {
|
|
172
|
-
const fetchImpl = vi.fn().mockResolvedValue(jsonResponse(200, { vaults: [] }));
|
|
173
|
-
expect(await resolveVaultBaseUrl('ghost', fetchImpl)).toBeNull();
|
|
174
|
-
});
|
|
175
|
-
});
|
|
176
|
-
|
|
177
|
-
describe('mintVaultTokenHttp', () => {
|
|
178
|
-
it('POSTs to /tokens with label + scopes + null expires_at', async () => {
|
|
179
|
-
const fetchImpl = vi.fn().mockResolvedValue(jsonResponse(201, { id: 't_x', token: 'pvt_x' }));
|
|
180
|
-
const result = await mintVaultTokenHttp({
|
|
181
|
-
vaultBaseUrl: 'https://h/vault/work',
|
|
182
|
-
authHeader: 'Bearer x',
|
|
183
|
-
label: 'claw-personal',
|
|
184
|
-
scopes: ['vault:read', 'vault:write'],
|
|
185
|
-
fetchImpl,
|
|
186
|
-
});
|
|
187
|
-
expect(result.status).toBe(201);
|
|
188
|
-
expect(fetchImpl).toHaveBeenCalledWith(
|
|
189
|
-
'https://h/vault/work/tokens',
|
|
190
|
-
expect.objectContaining({
|
|
191
|
-
method: 'POST',
|
|
192
|
-
body: JSON.stringify({
|
|
193
|
-
label: 'claw-personal',
|
|
194
|
-
scopes: ['vault:read', 'vault:write'],
|
|
195
|
-
expires_at: null,
|
|
196
|
-
}),
|
|
197
|
-
}),
|
|
198
|
-
);
|
|
199
|
-
});
|
|
200
|
-
|
|
201
|
-
it('passes expires_at through when provided', async () => {
|
|
202
|
-
const fetchImpl = vi.fn().mockResolvedValue(jsonResponse(201, { id: 't_x', token: 'pvt_x' }));
|
|
203
|
-
await mintVaultTokenHttp({
|
|
204
|
-
vaultBaseUrl: 'https://h/vault/work',
|
|
205
|
-
authHeader: 'Bearer x',
|
|
206
|
-
label: 'short-lived',
|
|
207
|
-
scopes: ['vault:read'],
|
|
208
|
-
expiresAt: '2026-12-31T00:00:00Z',
|
|
209
|
-
fetchImpl,
|
|
210
|
-
});
|
|
211
|
-
const callArgs = fetchImpl.mock.calls[0]![1] as { body: string };
|
|
212
|
-
expect(JSON.parse(callArgs.body)).toMatchObject({ expires_at: '2026-12-31T00:00:00Z' });
|
|
213
|
-
});
|
|
214
|
-
});
|
package/src/web/vault-proxy.ts
DELETED
|
@@ -1,120 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* JWT-forwarding helper for paraclaw → vault HTTP calls.
|
|
3
|
-
*
|
|
4
|
-
* Per the chosen v1 admin auth model (Option C in
|
|
5
|
-
* `docs/design/2026-04-29-vault-management-ui.md` § Admin auth model):
|
|
6
|
-
* paraclaw forwards the *operator's* hub-issued session JWT to the vault
|
|
7
|
-
* unmodified. The vault validates `vault:<name>:admin` against the hub's
|
|
8
|
-
* JWKS — same path it uses for any hub-issued JWT — so paraclaw doesn't
|
|
9
|
-
* downgrade or re-issue.
|
|
10
|
-
*
|
|
11
|
-
* The helper is intentionally thin: name → URL resolution lives in the
|
|
12
|
-
* caller (route handlers do that via `fetchHubVaults()`); this just wraps
|
|
13
|
-
* fetch with the right headers and surfaces vault errors as structured
|
|
14
|
-
* results so the route handler can propagate the status verbatim.
|
|
15
|
-
*/
|
|
16
|
-
import { fetchHubVaults, type VaultListing, type FetchLike } from './hub-discovery.js';
|
|
17
|
-
|
|
18
|
-
export interface VaultProxyOpts {
|
|
19
|
-
method: 'GET' | 'POST' | 'DELETE';
|
|
20
|
-
/** Vault base URL, no trailing slash, no `/tokens` suffix. */
|
|
21
|
-
vaultBaseUrl: string;
|
|
22
|
-
/** Sub-path, must start with `/` (e.g. `/tokens`, `/tokens/t_abc123def456`). */
|
|
23
|
-
subpath: string;
|
|
24
|
-
/** Operator's `Authorization: Bearer <jwt>` header value, forwarded as-is. */
|
|
25
|
-
authHeader: string;
|
|
26
|
-
/** Optional JSON body — POST only. */
|
|
27
|
-
body?: unknown;
|
|
28
|
-
/** Test seam. */
|
|
29
|
-
fetchImpl?: FetchLike;
|
|
30
|
-
}
|
|
31
|
-
|
|
32
|
-
export interface VaultProxyResult {
|
|
33
|
-
status: number;
|
|
34
|
-
body: unknown;
|
|
35
|
-
}
|
|
36
|
-
|
|
37
|
-
/**
|
|
38
|
-
* Forward a request to a vault and return the parsed result. Network errors
|
|
39
|
-
* surface as 502; HTTP errors come back with the vault's status so the route
|
|
40
|
-
* handler can mirror it (a 401 from vault means the caller's JWT is missing
|
|
41
|
-
* `vault:<name>:admin` — surface that to the browser to trigger consent).
|
|
42
|
-
*/
|
|
43
|
-
export async function forwardToVault(opts: VaultProxyOpts): Promise<VaultProxyResult> {
|
|
44
|
-
const fetchImpl = opts.fetchImpl ?? fetch;
|
|
45
|
-
const url = `${opts.vaultBaseUrl.replace(/\/+$/, '')}${opts.subpath}`;
|
|
46
|
-
const headers: Record<string, string> = {
|
|
47
|
-
Authorization: opts.authHeader,
|
|
48
|
-
Accept: 'application/json',
|
|
49
|
-
};
|
|
50
|
-
const init: RequestInit = { method: opts.method, headers };
|
|
51
|
-
if (opts.body !== undefined) {
|
|
52
|
-
headers['Content-Type'] = 'application/json';
|
|
53
|
-
init.body = JSON.stringify(opts.body);
|
|
54
|
-
}
|
|
55
|
-
let res: Response;
|
|
56
|
-
try {
|
|
57
|
-
res = await fetchImpl(url, init);
|
|
58
|
-
} catch (err) {
|
|
59
|
-
return {
|
|
60
|
-
status: 502,
|
|
61
|
-
body: { error: `vault unreachable: ${err instanceof Error ? err.message : String(err)}` },
|
|
62
|
-
};
|
|
63
|
-
}
|
|
64
|
-
let body: unknown = null;
|
|
65
|
-
const text = await res.text();
|
|
66
|
-
if (text.length > 0) {
|
|
67
|
-
try {
|
|
68
|
-
body = JSON.parse(text);
|
|
69
|
-
} catch {
|
|
70
|
-
body = { error: `vault returned non-JSON: ${text.slice(0, 200)}` };
|
|
71
|
-
}
|
|
72
|
-
}
|
|
73
|
-
return { status: res.status, body };
|
|
74
|
-
}
|
|
75
|
-
|
|
76
|
-
/**
|
|
77
|
-
* Resolve a vault name to its hub-published base URL. Returns null if the
|
|
78
|
-
* name doesn't match any registered vault — caller responds 404.
|
|
79
|
-
*
|
|
80
|
-
* The base URL is the well-known doc's `vault.url`, which is the
|
|
81
|
-
* public-routable form (e.g. `https://hub.tail.../vault/<name>`). Strip
|
|
82
|
-
* any trailing slash before returning so callers can append `/tokens`
|
|
83
|
-
* etc. without double-slashing.
|
|
84
|
-
*/
|
|
85
|
-
export async function resolveVaultBaseUrl(name: string, fetchImpl?: FetchLike): Promise<string | null> {
|
|
86
|
-
const vaults: VaultListing[] = await fetchHubVaults(fetchImpl);
|
|
87
|
-
const hit = vaults.find((v) => v.name === name);
|
|
88
|
-
if (!hit) return null;
|
|
89
|
-
return hit.url.replace(/\/+$/, '');
|
|
90
|
-
}
|
|
91
|
-
|
|
92
|
-
/**
|
|
93
|
-
* Mint a vault token via HTTP. Used both by the public `POST /api/vaults/
|
|
94
|
-
* /:name/tokens` endpoint and by the legacy implicit-mint paths in
|
|
95
|
-
* `/attach-vault` / `POST /api/groups` so the shell-out can go.
|
|
96
|
-
*
|
|
97
|
-
* Returns the vault's response verbatim (status + body) — the route handler
|
|
98
|
-
* decides what to surface to the browser.
|
|
99
|
-
*/
|
|
100
|
-
export async function mintVaultTokenHttp(opts: {
|
|
101
|
-
vaultBaseUrl: string;
|
|
102
|
-
authHeader: string;
|
|
103
|
-
label: string;
|
|
104
|
-
scopes: string[];
|
|
105
|
-
expiresAt?: string | null;
|
|
106
|
-
fetchImpl?: FetchLike;
|
|
107
|
-
}): Promise<VaultProxyResult> {
|
|
108
|
-
return forwardToVault({
|
|
109
|
-
method: 'POST',
|
|
110
|
-
vaultBaseUrl: opts.vaultBaseUrl,
|
|
111
|
-
subpath: '/tokens',
|
|
112
|
-
authHeader: opts.authHeader,
|
|
113
|
-
body: {
|
|
114
|
-
label: opts.label,
|
|
115
|
-
scopes: opts.scopes,
|
|
116
|
-
expires_at: opts.expiresAt ?? null,
|
|
117
|
-
},
|
|
118
|
-
fetchImpl: opts.fetchImpl,
|
|
119
|
-
});
|
|
120
|
-
}
|
package/src/web/wire-channel.ts
DELETED
|
@@ -1,181 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* Proactively wire a DM channel (Discord OR Telegram) to an agent group BEFORE
|
|
3
|
-
* the first inbound message arrives.
|
|
4
|
-
*
|
|
5
|
-
* ## Why proactive
|
|
6
|
-
*
|
|
7
|
-
* The reactive flow (`src/modules/permissions/channel-approval.ts`) escalates
|
|
8
|
-
* any unwired-channel mention to the owner via DM card. Two problems for a
|
|
9
|
-
* fresh-install setup wizard:
|
|
10
|
-
*
|
|
11
|
-
* 1. `messaging_groups` rows do not exist until the first inbound arrives,
|
|
12
|
-
* so an operator finishing setup has no DM to send to themselves.
|
|
13
|
-
* 2. `channel-approval.ts:73-77` silently drops if `getAllAgentGroups()
|
|
14
|
-
* .length === 0`. Even after the wizard creates the agent group, the
|
|
15
|
-
* first message would be dropped because the wiring path never runs
|
|
16
|
-
* until an unwired mention reaches the router.
|
|
17
|
-
*
|
|
18
|
-
* The Discord fix mirrors `scripts/init-first-agent.ts`'s `wireIfMissing`
|
|
19
|
-
* exactly (lines 155-166): synthesize the DM platform_id, INSERT
|
|
20
|
-
* messaging_groups + messaging_group_agents with the same defaults
|
|
21
|
-
* init-first-agent.ts uses. Any deviation from those defaults silently
|
|
22
|
-
* changes engage/sender behavior — keep behavior parity, do not "improve"
|
|
23
|
-
* them here.
|
|
24
|
-
*
|
|
25
|
-
* Telegram uses the same canonical defaults; only the platform_id shape
|
|
26
|
-
* differs:
|
|
27
|
-
*
|
|
28
|
-
* - Discord: `discord:@me:<botUserId>` — addressee-routed; ANY user DM
|
|
29
|
-
* to the bot lands on the bot's own @me channel. The wizard wires the
|
|
30
|
-
* bot's user id once and every operator who DMs the bot is matched by
|
|
31
|
-
* the same MGA's `sender_scope: 'all'`.
|
|
32
|
-
* - Telegram: `telegram:<userId>` — chat-id-routed; the chat_id of a DM
|
|
33
|
-
* between the bot and a user equals the user's Telegram user id
|
|
34
|
-
* (positive int; group/channel ids are negative). The wizard wires the
|
|
35
|
-
* OPERATOR's user id, so only that specific user's DMs match.
|
|
36
|
-
*
|
|
37
|
-
* The architectural fix is documented in PR #31 (paraclaw#27 phase 1) and a
|
|
38
|
-
* follow-up issue against the channel-approval silent-drop bug (NanoClaw
|
|
39
|
-
* upstream).
|
|
40
|
-
*/
|
|
41
|
-
import {
|
|
42
|
-
createMessagingGroup,
|
|
43
|
-
createMessagingGroupAgent,
|
|
44
|
-
getMessagingGroupAgentByPair,
|
|
45
|
-
getMessagingGroupByPlatform,
|
|
46
|
-
} from '../db/messaging-groups.js';
|
|
47
|
-
import { encodePlatformId } from '../platform-id.js';
|
|
48
|
-
import type { AgentGroup, MessagingGroup } from '../types.js';
|
|
49
|
-
|
|
50
|
-
export type ChannelKind = 'discord' | 'telegram';
|
|
51
|
-
|
|
52
|
-
function generateId(prefix: string): string {
|
|
53
|
-
return `${prefix}-${Date.now()}-${Math.random().toString(36).slice(2, 8)}`;
|
|
54
|
-
}
|
|
55
|
-
|
|
56
|
-
export interface WireDmInput {
|
|
57
|
-
channelType: ChannelKind;
|
|
58
|
-
agentGroup: AgentGroup;
|
|
59
|
-
/**
|
|
60
|
-
* Bot identity used as the second segment of the v2 platform_id
|
|
61
|
-
* (`<channel>:<botId>:<native>`). For Discord this is the bot
|
|
62
|
-
* application id (DISCORD_APPLICATION_ID). For Telegram this is the
|
|
63
|
-
* `id` returned by `getMe`. Resolved by the caller from the active
|
|
64
|
-
* adapter at wire time.
|
|
65
|
-
*/
|
|
66
|
-
botId: string;
|
|
67
|
-
/**
|
|
68
|
-
* For Discord: the BOT's own user id (snowflake) — forms the native
|
|
69
|
-
* segment `@me:<botUserId>` of `discord:<botId>:@me:<botUserId>`. For most
|
|
70
|
-
* Discord bots this equals `botId`, but it is kept distinct because
|
|
71
|
-
* application-id and user-id can diverge for legacy bot accounts.
|
|
72
|
-
*
|
|
73
|
-
* For Telegram: the OPERATOR's user id (positive int as string). The
|
|
74
|
-
* native segment is the chat_id, and Telegram DM chat_id == user_id, so
|
|
75
|
-
* this is what we wire as the third segment of `telegram:<botId>:<id>`.
|
|
76
|
-
*/
|
|
77
|
-
botUserId: string;
|
|
78
|
-
/** Optional display name for the messaging_groups row. */
|
|
79
|
-
displayName?: string;
|
|
80
|
-
}
|
|
81
|
-
|
|
82
|
-
export interface WireDmResult {
|
|
83
|
-
channelType: ChannelKind;
|
|
84
|
-
messagingGroupId: string;
|
|
85
|
-
messagingGroupAgentId: string;
|
|
86
|
-
platformId: string;
|
|
87
|
-
created: { messagingGroup: boolean; wiring: boolean };
|
|
88
|
-
}
|
|
89
|
-
|
|
90
|
-
function platformIdFor(channelType: ChannelKind, botId: string, userId: string): string {
|
|
91
|
-
switch (channelType) {
|
|
92
|
-
case 'discord':
|
|
93
|
-
// v2 Discord DM platform_id: `discord:<botId>:@me:<botUserId>`. The
|
|
94
|
-
// native segment (`@me:<botUserId>`) is what the Chat SDK adapter
|
|
95
|
-
// produces from `channelIdFromThreadId` for a bot DM channel; the
|
|
96
|
-
// bridge prepends `<botId>` so messaging_groups keys per-bot.
|
|
97
|
-
return encodePlatformId('discord', botId, `@me:${userId}`);
|
|
98
|
-
case 'telegram':
|
|
99
|
-
// v2 Telegram DM platform_id: `telegram:<botId>:<chatId>` where the
|
|
100
|
-
// chat_id of a bot-↔-user DM equals the user's Telegram user id.
|
|
101
|
-
// Encoding the bot id as the second segment is what makes two
|
|
102
|
-
// Telegram bots' identical DM chat_ids resolve to distinct
|
|
103
|
-
// messaging_groups rows (see src/platform-id.ts module comment).
|
|
104
|
-
return encodePlatformId('telegram', botId, userId);
|
|
105
|
-
}
|
|
106
|
-
}
|
|
107
|
-
|
|
108
|
-
/**
|
|
109
|
-
* Wire a DM channel to an agent group. Idempotent — safe to re-run; if the
|
|
110
|
-
* messaging_groups row OR the messaging_group_agents pair already exists,
|
|
111
|
-
* we leave it alone and report `created: false`.
|
|
112
|
-
*/
|
|
113
|
-
export function wireDmToAgent(input: WireDmInput): WireDmResult {
|
|
114
|
-
const { channelType, agentGroup, botId, botUserId, displayName } = input;
|
|
115
|
-
if (!botUserId.trim()) {
|
|
116
|
-
throw new Error('botUserId is required');
|
|
117
|
-
}
|
|
118
|
-
if (!botId.trim()) {
|
|
119
|
-
throw new Error('botId is required');
|
|
120
|
-
}
|
|
121
|
-
|
|
122
|
-
const userId = botUserId.trim();
|
|
123
|
-
const platformId = platformIdFor(channelType, botId.trim(), userId);
|
|
124
|
-
const now = new Date().toISOString();
|
|
125
|
-
|
|
126
|
-
let mg: MessagingGroup | undefined = getMessagingGroupByPlatform(channelType, platformId);
|
|
127
|
-
let createdMg = false;
|
|
128
|
-
if (!mg) {
|
|
129
|
-
const mgId = generateId('mg');
|
|
130
|
-
createMessagingGroup({
|
|
131
|
-
id: mgId,
|
|
132
|
-
channel_type: channelType,
|
|
133
|
-
platform_id: platformId,
|
|
134
|
-
name: displayName ?? null,
|
|
135
|
-
is_group: 0,
|
|
136
|
-
unknown_sender_policy: 'strict',
|
|
137
|
-
created_at: now,
|
|
138
|
-
});
|
|
139
|
-
mg = getMessagingGroupByPlatform(channelType, platformId);
|
|
140
|
-
if (!mg) {
|
|
141
|
-
throw new Error(`failed to read back messaging_groups row after insert (platform_id=${platformId})`);
|
|
142
|
-
}
|
|
143
|
-
createdMg = true;
|
|
144
|
-
}
|
|
145
|
-
|
|
146
|
-
const existing = getMessagingGroupAgentByPair(mg.id, agentGroup.id);
|
|
147
|
-
if (existing) {
|
|
148
|
-
return {
|
|
149
|
-
channelType,
|
|
150
|
-
messagingGroupId: mg.id,
|
|
151
|
-
messagingGroupAgentId: existing.id,
|
|
152
|
-
platformId,
|
|
153
|
-
created: { messagingGroup: createdMg, wiring: false },
|
|
154
|
-
};
|
|
155
|
-
}
|
|
156
|
-
|
|
157
|
-
const mgaId = generateId('mga');
|
|
158
|
-
createMessagingGroupAgent({
|
|
159
|
-
id: mgaId,
|
|
160
|
-
messaging_group_id: mg.id,
|
|
161
|
-
agent_group_id: agentGroup.id,
|
|
162
|
-
// Defaults below MUST match scripts/init-first-agent.ts:155-166. DM rows
|
|
163
|
-
// (is_group=0) are always 'pattern' + '.' so the agent responds to every
|
|
164
|
-
// message; group rows would be 'mention'. We only create DM rows here.
|
|
165
|
-
engage_mode: 'pattern',
|
|
166
|
-
engage_pattern: '.',
|
|
167
|
-
sender_scope: 'all',
|
|
168
|
-
ignored_message_policy: 'drop',
|
|
169
|
-
session_mode: 'shared',
|
|
170
|
-
priority: 0,
|
|
171
|
-
created_at: now,
|
|
172
|
-
});
|
|
173
|
-
|
|
174
|
-
return {
|
|
175
|
-
channelType,
|
|
176
|
-
messagingGroupId: mg.id,
|
|
177
|
-
messagingGroupAgentId: mgaId,
|
|
178
|
-
platformId,
|
|
179
|
-
created: { messagingGroup: createdMg, wiring: true },
|
|
180
|
-
};
|
|
181
|
-
}
|