@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
|
@@ -0,0 +1,227 @@
|
|
|
1
|
+
import { describe, test, expect, beforeEach, afterEach } from "bun:test";
|
|
2
|
+
import { mkdtempSync, writeFileSync, rmSync, mkdirSync } from "fs";
|
|
3
|
+
import { join } from "path";
|
|
4
|
+
import { tmpdir } from "os";
|
|
5
|
+
import {
|
|
6
|
+
resolveChannelEntries,
|
|
7
|
+
instantiateTransport,
|
|
8
|
+
loadRegistry,
|
|
9
|
+
defaultStateDir,
|
|
10
|
+
type ChannelEntry,
|
|
11
|
+
} from "./registry.ts";
|
|
12
|
+
|
|
13
|
+
let dir: string;
|
|
14
|
+
const savedToken = process.env.TELEGRAM_BOT_TOKEN;
|
|
15
|
+
|
|
16
|
+
beforeEach(() => {
|
|
17
|
+
dir = mkdtempSync(join(tmpdir(), "channel-registry-"));
|
|
18
|
+
delete process.env.TELEGRAM_BOT_TOKEN;
|
|
19
|
+
});
|
|
20
|
+
|
|
21
|
+
afterEach(() => {
|
|
22
|
+
rmSync(dir, { recursive: true, force: true });
|
|
23
|
+
if (savedToken === undefined) delete process.env.TELEGRAM_BOT_TOKEN;
|
|
24
|
+
else process.env.TELEGRAM_BOT_TOKEN = savedToken;
|
|
25
|
+
});
|
|
26
|
+
|
|
27
|
+
describe("no env synthesis — channels are always explicit", () => {
|
|
28
|
+
test("no channels.json + TELEGRAM_BOT_TOKEN set → [] (env is NOT a token source)", () => {
|
|
29
|
+
process.env.TELEGRAM_BOT_TOKEN = "dummy-token";
|
|
30
|
+
const entries = resolveChannelEntries({ stateDir: dir, loadEnv: false });
|
|
31
|
+
expect(entries).toEqual([]);
|
|
32
|
+
});
|
|
33
|
+
|
|
34
|
+
test("no channels.json + token only in state-dir .env → still [] after loadEnv", () => {
|
|
35
|
+
// .env is still loaded (generic vars may live there), but a TELEGRAM_BOT_TOKEN
|
|
36
|
+
// there no longer synthesizes a channel — per-channel config is required.
|
|
37
|
+
writeFileSync(join(dir, ".env"), "TELEGRAM_BOT_TOKEN=from-env-file\n");
|
|
38
|
+
const entries = resolveChannelEntries({ stateDir: dir, loadEnv: true });
|
|
39
|
+
expect(entries).toEqual([]);
|
|
40
|
+
});
|
|
41
|
+
|
|
42
|
+
test("no channels.json + no token → empty list (caller decides to error)", () => {
|
|
43
|
+
const entries = resolveChannelEntries({ stateDir: dir, loadEnv: false });
|
|
44
|
+
expect(entries).toEqual([]);
|
|
45
|
+
});
|
|
46
|
+
});
|
|
47
|
+
|
|
48
|
+
describe("explicit channels.json parsing", () => {
|
|
49
|
+
test("parses multiple named channels", () => {
|
|
50
|
+
const file = {
|
|
51
|
+
channels: [
|
|
52
|
+
{ name: "tele-aaron", transport: "telegram", config: { token: "t1" } },
|
|
53
|
+
{ name: "ops", transport: "telegram", config: { token: "t2" } },
|
|
54
|
+
],
|
|
55
|
+
};
|
|
56
|
+
writeFileSync(join(dir, "channels.json"), JSON.stringify(file));
|
|
57
|
+
const entries = resolveChannelEntries({ stateDir: dir, loadEnv: false });
|
|
58
|
+
expect(entries).toHaveLength(2);
|
|
59
|
+
expect(entries[0]!.name).toBe("tele-aaron");
|
|
60
|
+
expect(entries[1]!.transport).toBe("telegram");
|
|
61
|
+
});
|
|
62
|
+
|
|
63
|
+
test("channels.json takes precedence over the token fallback", () => {
|
|
64
|
+
process.env.TELEGRAM_BOT_TOKEN = "dummy";
|
|
65
|
+
writeFileSync(
|
|
66
|
+
join(dir, "channels.json"),
|
|
67
|
+
JSON.stringify({ channels: [{ name: "explicit", transport: "telegram", config: { token: "x" } }] }),
|
|
68
|
+
);
|
|
69
|
+
const entries = resolveChannelEntries({ stateDir: dir, loadEnv: false });
|
|
70
|
+
expect(entries).toEqual([{ name: "explicit", transport: "telegram", config: { token: "x" } }]);
|
|
71
|
+
});
|
|
72
|
+
|
|
73
|
+
test("missing 'channels' array throws clearly", () => {
|
|
74
|
+
writeFileSync(join(dir, "channels.json"), JSON.stringify({ nope: true }));
|
|
75
|
+
expect(() => resolveChannelEntries({ stateDir: dir, loadEnv: false })).toThrow(/channels/);
|
|
76
|
+
});
|
|
77
|
+
|
|
78
|
+
test("entry missing name or transport throws clearly", () => {
|
|
79
|
+
writeFileSync(join(dir, "channels.json"), JSON.stringify({ channels: [{ name: "x" }] }));
|
|
80
|
+
expect(() => resolveChannelEntries({ stateDir: dir, loadEnv: false })).toThrow(/transport/);
|
|
81
|
+
});
|
|
82
|
+
});
|
|
83
|
+
|
|
84
|
+
describe("instantiateTransport", () => {
|
|
85
|
+
test("telegram entry yields a telegram transport", () => {
|
|
86
|
+
const entry: ChannelEntry = { name: "t", transport: "telegram", config: { token: "tok" } };
|
|
87
|
+
const t = instantiateTransport(entry);
|
|
88
|
+
expect(t.kind).toBe("telegram");
|
|
89
|
+
});
|
|
90
|
+
|
|
91
|
+
test("unknown transport kind errors clearly", () => {
|
|
92
|
+
const entry: ChannelEntry = { name: "weird", transport: "carrier-pigeon" };
|
|
93
|
+
expect(() => instantiateTransport(entry)).toThrow(/unknown transport kind "carrier-pigeon"/);
|
|
94
|
+
});
|
|
95
|
+
|
|
96
|
+
test("telegram entry with no token errors clearly (names the channel)", () => {
|
|
97
|
+
const entry: ChannelEntry = { name: "t", transport: "telegram" };
|
|
98
|
+
expect(() => instantiateTransport(entry)).toThrow(
|
|
99
|
+
/telegram channel t requires a per-channel bot token/,
|
|
100
|
+
);
|
|
101
|
+
});
|
|
102
|
+
});
|
|
103
|
+
|
|
104
|
+
describe("loadRegistry", () => {
|
|
105
|
+
test("builds a live channel map keyed by name", () => {
|
|
106
|
+
writeFileSync(
|
|
107
|
+
join(dir, "channels.json"),
|
|
108
|
+
JSON.stringify({
|
|
109
|
+
channels: [
|
|
110
|
+
{ name: "a", transport: "telegram", config: { token: "t1", stateDir: dir } },
|
|
111
|
+
{ name: "b", transport: "telegram", config: { token: "t2", stateDir: dir } },
|
|
112
|
+
],
|
|
113
|
+
}),
|
|
114
|
+
);
|
|
115
|
+
const reg = loadRegistry({ stateDir: dir, loadEnv: false });
|
|
116
|
+
expect([...reg.keys()].sort()).toEqual(["a", "b"]);
|
|
117
|
+
expect(reg.get("a")!.transport.kind).toBe("telegram");
|
|
118
|
+
});
|
|
119
|
+
|
|
120
|
+
test("duplicate channel names throw", () => {
|
|
121
|
+
writeFileSync(
|
|
122
|
+
join(dir, "channels.json"),
|
|
123
|
+
JSON.stringify({
|
|
124
|
+
channels: [
|
|
125
|
+
{ name: "dup", transport: "telegram", config: { token: "t1", stateDir: dir } },
|
|
126
|
+
{ name: "dup", transport: "telegram", config: { token: "t2", stateDir: dir } },
|
|
127
|
+
],
|
|
128
|
+
}),
|
|
129
|
+
);
|
|
130
|
+
expect(() => loadRegistry({ stateDir: dir, loadEnv: false })).toThrow(/duplicate channel name/);
|
|
131
|
+
});
|
|
132
|
+
|
|
133
|
+
test("unknown transport kind surfaces from loadRegistry", () => {
|
|
134
|
+
writeFileSync(
|
|
135
|
+
join(dir, "channels.json"),
|
|
136
|
+
JSON.stringify({ channels: [{ name: "x", transport: "smoke-signal" }] }),
|
|
137
|
+
);
|
|
138
|
+
expect(() => loadRegistry({ stateDir: dir, loadEnv: false })).toThrow(/unknown transport kind/);
|
|
139
|
+
});
|
|
140
|
+
});
|
|
141
|
+
|
|
142
|
+
// ===========================================================================
|
|
143
|
+
// defaultStateDir — channel→agent rename + back-compat resolution.
|
|
144
|
+
//
|
|
145
|
+
// Resolution order (registry.ts):
|
|
146
|
+
// 1. PARACHUTE_AGENT_STATE_DIR env → legacy PARACHUTE_CHANNEL_STATE_DIR (via
|
|
147
|
+
// env-compat.agentEnv) — explicit override.
|
|
148
|
+
// 2. ~/.parachute/agent (the NEW default) if it exists.
|
|
149
|
+
// 3. Back-compat: ~/.parachute/channel (legacy) if it exists and agent does not.
|
|
150
|
+
// 4. ~/.parachute/agent (the new default) when neither exists.
|
|
151
|
+
//
|
|
152
|
+
// `defaultStateDir(home?)` takes an injectable home so we exercise the
|
|
153
|
+
// home-dir-default + legacy-fallback branches against a throwaway dir WITHOUT a
|
|
154
|
+
// process-wide `mock.module("os")` — which would leak a faked `homedir()` into
|
|
155
|
+
// other test files (it broke the live-Seatbelt path-confinement tests when an
|
|
156
|
+
// earlier version did exactly that).
|
|
157
|
+
// ===========================================================================
|
|
158
|
+
|
|
159
|
+
describe("defaultStateDir — env override (new + legacy)", () => {
|
|
160
|
+
const savedAgent = process.env.PARACHUTE_AGENT_STATE_DIR;
|
|
161
|
+
const savedChannel = process.env.PARACHUTE_CHANNEL_STATE_DIR;
|
|
162
|
+
afterEach(() => {
|
|
163
|
+
if (savedAgent === undefined) delete process.env.PARACHUTE_AGENT_STATE_DIR;
|
|
164
|
+
else process.env.PARACHUTE_AGENT_STATE_DIR = savedAgent;
|
|
165
|
+
if (savedChannel === undefined) delete process.env.PARACHUTE_CHANNEL_STATE_DIR;
|
|
166
|
+
else process.env.PARACHUTE_CHANNEL_STATE_DIR = savedChannel;
|
|
167
|
+
});
|
|
168
|
+
|
|
169
|
+
test("PARACHUTE_AGENT_STATE_DIR (the new var) wins", () => {
|
|
170
|
+
delete process.env.PARACHUTE_CHANNEL_STATE_DIR;
|
|
171
|
+
process.env.PARACHUTE_AGENT_STATE_DIR = "/tmp/explicit-agent-state";
|
|
172
|
+
expect(defaultStateDir()).toBe("/tmp/explicit-agent-state");
|
|
173
|
+
});
|
|
174
|
+
|
|
175
|
+
test("back-compat: legacy PARACHUTE_CHANNEL_STATE_DIR is still honored when the new var is unset", () => {
|
|
176
|
+
delete process.env.PARACHUTE_AGENT_STATE_DIR;
|
|
177
|
+
process.env.PARACHUTE_CHANNEL_STATE_DIR = "/tmp/legacy-channel-state";
|
|
178
|
+
expect(defaultStateDir()).toBe("/tmp/legacy-channel-state");
|
|
179
|
+
});
|
|
180
|
+
|
|
181
|
+
test("the new PARACHUTE_AGENT_STATE_DIR takes precedence over the legacy one", () => {
|
|
182
|
+
process.env.PARACHUTE_AGENT_STATE_DIR = "/tmp/new-wins";
|
|
183
|
+
process.env.PARACHUTE_CHANNEL_STATE_DIR = "/tmp/legacy-loses";
|
|
184
|
+
expect(defaultStateDir()).toBe("/tmp/new-wins");
|
|
185
|
+
});
|
|
186
|
+
});
|
|
187
|
+
|
|
188
|
+
describe("defaultStateDir — home-dir default + legacy-dir back-compat", () => {
|
|
189
|
+
let home: string;
|
|
190
|
+
const savedAgent = process.env.PARACHUTE_AGENT_STATE_DIR;
|
|
191
|
+
const savedChannel = process.env.PARACHUTE_CHANNEL_STATE_DIR;
|
|
192
|
+
|
|
193
|
+
beforeEach(() => {
|
|
194
|
+
// A throwaway HOME passed straight to defaultStateDir(home); no env override so
|
|
195
|
+
// the existence checks drive resolution. No os mock — see the note above.
|
|
196
|
+
home = mkdtempSync(join(tmpdir(), "agent-home-"));
|
|
197
|
+
delete process.env.PARACHUTE_AGENT_STATE_DIR;
|
|
198
|
+
delete process.env.PARACHUTE_CHANNEL_STATE_DIR;
|
|
199
|
+
});
|
|
200
|
+
afterEach(() => {
|
|
201
|
+
rmSync(home, { recursive: true, force: true });
|
|
202
|
+
if (savedAgent === undefined) delete process.env.PARACHUTE_AGENT_STATE_DIR;
|
|
203
|
+
else process.env.PARACHUTE_AGENT_STATE_DIR = savedAgent;
|
|
204
|
+
if (savedChannel === undefined) delete process.env.PARACHUTE_CHANNEL_STATE_DIR;
|
|
205
|
+
else process.env.PARACHUTE_CHANNEL_STATE_DIR = savedChannel;
|
|
206
|
+
});
|
|
207
|
+
|
|
208
|
+
test("the new default is ~/.parachute/agent when neither dir exists", () => {
|
|
209
|
+
expect(defaultStateDir(home)).toBe(join(home, ".parachute", "agent"));
|
|
210
|
+
});
|
|
211
|
+
|
|
212
|
+
test("uses the new ~/.parachute/agent when it exists", () => {
|
|
213
|
+
mkdirSync(join(home, ".parachute", "agent"), { recursive: true });
|
|
214
|
+
expect(defaultStateDir(home)).toBe(join(home, ".parachute", "agent"));
|
|
215
|
+
});
|
|
216
|
+
|
|
217
|
+
test("back-compat: uses legacy ~/.parachute/channel when ~/.parachute/agent is absent", () => {
|
|
218
|
+
mkdirSync(join(home, ".parachute", "channel"), { recursive: true });
|
|
219
|
+
expect(defaultStateDir(home)).toBe(join(home, ".parachute", "channel"));
|
|
220
|
+
});
|
|
221
|
+
|
|
222
|
+
test("the new agent dir wins even when the legacy channel dir also exists", () => {
|
|
223
|
+
mkdirSync(join(home, ".parachute", "agent"), { recursive: true });
|
|
224
|
+
mkdirSync(join(home, ".parachute", "channel"), { recursive: true });
|
|
225
|
+
expect(defaultStateDir(home)).toBe(join(home, ".parachute", "agent"));
|
|
226
|
+
});
|
|
227
|
+
});
|
package/src/registry.ts
ADDED
|
@@ -0,0 +1,228 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Channel registry.
|
|
3
|
+
*
|
|
4
|
+
* Loads named channels from `channels.json` in the channel state dir and
|
|
5
|
+
* instantiates a Transport per entry. A channel is a name bound to a transport
|
|
6
|
+
* kind plus optional transport config.
|
|
7
|
+
*
|
|
8
|
+
* Every channel is explicit: with no `channels.json` the daemon resolves to
|
|
9
|
+
* zero channels. There is NO daemon-global TELEGRAM_BOT_TOKEN synthesis — each
|
|
10
|
+
* telegram channel carries its own per-channel bot token in its config.
|
|
11
|
+
*/
|
|
12
|
+
|
|
13
|
+
import { readFileSync, writeFileSync, existsSync, chmodSync, mkdirSync } from "fs";
|
|
14
|
+
import { join } from "path";
|
|
15
|
+
import { homedir } from "os";
|
|
16
|
+
import { agentEnv } from "./env-compat.ts";
|
|
17
|
+
import type { Transport } from "./transport.ts";
|
|
18
|
+
import { TelegramTransport, type TelegramTransportConfig } from "./transports/telegram.ts";
|
|
19
|
+
import { HttpUiTransport, type HttpUiTransportConfig } from "./transports/http-ui.ts";
|
|
20
|
+
import { VaultTransport, type VaultTransportConfig } from "./transports/vault.ts";
|
|
21
|
+
|
|
22
|
+
export interface ChannelEntry {
|
|
23
|
+
name: string;
|
|
24
|
+
transport: string;
|
|
25
|
+
config?: Record<string, unknown>;
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
export interface ChannelsFile {
|
|
29
|
+
channels: ChannelEntry[];
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
/** A live channel: its config entry plus the instantiated transport. */
|
|
33
|
+
export interface Channel {
|
|
34
|
+
name: string;
|
|
35
|
+
transport: Transport;
|
|
36
|
+
entry: ChannelEntry;
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
/**
|
|
40
|
+
* The agent module's state dir. Resolution, in order:
|
|
41
|
+
*
|
|
42
|
+
* 1. `PARACHUTE_AGENT_STATE_DIR` env → its legacy alias
|
|
43
|
+
* `PARACHUTE_CHANNEL_STATE_DIR` (via {@link agentEnv}) — the explicit override.
|
|
44
|
+
* 2. The new default `~/.parachute/agent`.
|
|
45
|
+
* 3. **Back-compat:** if `~/.parachute/agent` does NOT exist but the legacy
|
|
46
|
+
* `~/.parachute/channel` DOES, use the legacy dir — so an operator's existing
|
|
47
|
+
* config/credentials/sessions keep working with no manual move across the
|
|
48
|
+
* channel→agent rename. Once `~/.parachute/agent` exists (a fresh install, or
|
|
49
|
+
* after a manual move) it wins and the legacy dir is ignored.
|
|
50
|
+
*
|
|
51
|
+
* `home` is injectable for tests (default `homedir()`) so the home-dir-default +
|
|
52
|
+
* legacy-fallback branches can be exercised against a throwaway dir WITHOUT a
|
|
53
|
+
* process-wide `mock.module("os")` (which leaks `homedir()` into other test files
|
|
54
|
+
* — it broke the live-Seatbelt path-confinement tests when it did).
|
|
55
|
+
*
|
|
56
|
+
* (Migration `parachute-patterns/migrations/2026-06-17-channel-to-agent.md`.)
|
|
57
|
+
*/
|
|
58
|
+
export function defaultStateDir(home: string = homedir()): string {
|
|
59
|
+
const explicit = agentEnv("STATE_DIR");
|
|
60
|
+
if (explicit) return explicit;
|
|
61
|
+
const agentDir = join(home, ".parachute", "agent");
|
|
62
|
+
if (existsSync(agentDir)) return agentDir;
|
|
63
|
+
const legacyDir = join(home, ".parachute", "channel");
|
|
64
|
+
if (existsSync(legacyDir)) return legacyDir;
|
|
65
|
+
return agentDir;
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
/**
|
|
69
|
+
* Load the state dir's `.env` into process.env if not already set. Generic —
|
|
70
|
+
* an operator may keep arbitrary vars there (e.g. PARACHUTE_HUB_ORIGIN). The
|
|
71
|
+
* daemon no longer consumes TELEGRAM_BOT_TOKEN; telegram tokens live per-channel
|
|
72
|
+
* in channels.json. Mirrors the original daemon's `.env` loading behavior.
|
|
73
|
+
*/
|
|
74
|
+
export function loadEnvFile(stateDir: string): void {
|
|
75
|
+
const envFile = join(stateDir, ".env");
|
|
76
|
+
try {
|
|
77
|
+
if (existsSync(envFile)) {
|
|
78
|
+
chmodSync(envFile, 0o600);
|
|
79
|
+
for (const line of readFileSync(envFile, "utf8").split("\n")) {
|
|
80
|
+
const m = line.match(/^(\w+)=(.*)$/);
|
|
81
|
+
if (m && process.env[m[1]!] === undefined) process.env[m[1]!] = m[2]!;
|
|
82
|
+
}
|
|
83
|
+
}
|
|
84
|
+
} catch {}
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
/** Instantiate the Transport for a single channel entry. */
|
|
88
|
+
export function instantiateTransport(entry: ChannelEntry): Transport {
|
|
89
|
+
switch (entry.transport) {
|
|
90
|
+
case "telegram":
|
|
91
|
+
// Thread the channel name in so a missing-token error names the channel.
|
|
92
|
+
return new TelegramTransport({
|
|
93
|
+
...(entry.config ?? {}),
|
|
94
|
+
name: entry.name,
|
|
95
|
+
} as TelegramTransportConfig);
|
|
96
|
+
case "http-ui":
|
|
97
|
+
// http-ui needs no secret — just a channel name (taken from ctx at start).
|
|
98
|
+
return new HttpUiTransport((entry.config ?? {}) as HttpUiTransportConfig);
|
|
99
|
+
case "vault":
|
|
100
|
+
// vault needs vault name + a write token + the inbound-webhook secret.
|
|
101
|
+
return new VaultTransport((entry.config ?? {}) as unknown as VaultTransportConfig);
|
|
102
|
+
default:
|
|
103
|
+
throw new Error(
|
|
104
|
+
`registry: unknown transport kind "${entry.transport}" for channel "${entry.name}" ` +
|
|
105
|
+
`(known: telegram, http-ui, vault)`,
|
|
106
|
+
);
|
|
107
|
+
}
|
|
108
|
+
}
|
|
109
|
+
|
|
110
|
+
/**
|
|
111
|
+
* Resolve the channel entries for this install. Reads channels.json if present;
|
|
112
|
+
* otherwise resolves to `[]` (no channels) — explicit per-channel config is
|
|
113
|
+
* required, there is no env-token synthesis.
|
|
114
|
+
*
|
|
115
|
+
* `loadEnv` (default true) still loads the state-dir `.env` for any generic vars
|
|
116
|
+
* an operator keeps there. Tests pass `loadEnv: false` to stay hermetic.
|
|
117
|
+
*/
|
|
118
|
+
export function resolveChannelEntries(opts: {
|
|
119
|
+
stateDir?: string;
|
|
120
|
+
loadEnv?: boolean;
|
|
121
|
+
} = {}): ChannelEntry[] {
|
|
122
|
+
const stateDir = opts.stateDir ?? defaultStateDir();
|
|
123
|
+
if (opts.loadEnv !== false) loadEnvFile(stateDir);
|
|
124
|
+
|
|
125
|
+
const channelsFile = join(stateDir, "channels.json");
|
|
126
|
+
if (existsSync(channelsFile)) {
|
|
127
|
+
const parsed = JSON.parse(readFileSync(channelsFile, "utf8")) as ChannelsFile;
|
|
128
|
+
if (!parsed || !Array.isArray(parsed.channels)) {
|
|
129
|
+
throw new Error(`registry: ${channelsFile} must have a "channels" array`);
|
|
130
|
+
}
|
|
131
|
+
for (const entry of parsed.channels) {
|
|
132
|
+
if (!entry.name || !entry.transport) {
|
|
133
|
+
throw new Error(
|
|
134
|
+
`registry: each channel needs "name" and "transport" (got ${JSON.stringify(entry)})`,
|
|
135
|
+
);
|
|
136
|
+
}
|
|
137
|
+
}
|
|
138
|
+
return parsed.channels;
|
|
139
|
+
}
|
|
140
|
+
|
|
141
|
+
// No channels.json → no channels. Explicit per-channel config is now
|
|
142
|
+
// required; there is no daemon-global TELEGRAM_BOT_TOKEN synthesis. Each
|
|
143
|
+
// telegram channel carries its OWN bot token in channels.json (created via
|
|
144
|
+
// the admin UI), so the daemon never depends on a global env token.
|
|
145
|
+
return [];
|
|
146
|
+
}
|
|
147
|
+
|
|
148
|
+
/** Absolute path to the channels.json registry file in a state dir. */
|
|
149
|
+
export function channelsFilePath(stateDir?: string): string {
|
|
150
|
+
return join(stateDir ?? defaultStateDir(), "channels.json");
|
|
151
|
+
}
|
|
152
|
+
|
|
153
|
+
/**
|
|
154
|
+
* Read the channels.json registry as a plain `ChannelsFile`, WITHOUT
|
|
155
|
+
* instantiating transports or synthesizing the telegram fallback. Returns an
|
|
156
|
+
* empty `{ channels: [] }` if the file is absent. Used by the config-management
|
|
157
|
+
* API to read-modify-write the file while the daemon holds the live channels.
|
|
158
|
+
*/
|
|
159
|
+
export function readChannelsFile(stateDir?: string): ChannelsFile {
|
|
160
|
+
const file = channelsFilePath(stateDir);
|
|
161
|
+
if (!existsSync(file)) return { channels: [] };
|
|
162
|
+
const parsed = JSON.parse(readFileSync(file, "utf8")) as ChannelsFile;
|
|
163
|
+
if (!parsed || !Array.isArray(parsed.channels)) {
|
|
164
|
+
throw new Error(`registry: ${file} must have a "channels" array`);
|
|
165
|
+
}
|
|
166
|
+
return parsed;
|
|
167
|
+
}
|
|
168
|
+
|
|
169
|
+
/**
|
|
170
|
+
* Upsert a channel entry into channels.json (preserving every other entry) and
|
|
171
|
+
* write it back with 0600 perms — the file holds transport tokens/secrets. If an
|
|
172
|
+
* entry with the same name exists it's REPLACED in place (same position);
|
|
173
|
+
* otherwise the new entry is appended. Creates the state dir if needed. Returns
|
|
174
|
+
* the persisted file contents.
|
|
175
|
+
*/
|
|
176
|
+
export function upsertChannelEntry(entry: ChannelEntry, stateDir?: string): ChannelsFile {
|
|
177
|
+
const dir = stateDir ?? defaultStateDir();
|
|
178
|
+
mkdirSync(dir, { recursive: true });
|
|
179
|
+
const file = channelsFilePath(dir);
|
|
180
|
+
const current = readChannelsFile(dir);
|
|
181
|
+
const idx = current.channels.findIndex((c) => c.name === entry.name);
|
|
182
|
+
if (idx >= 0) current.channels[idx] = entry;
|
|
183
|
+
else current.channels.push(entry);
|
|
184
|
+
writeFileSync(file, JSON.stringify(current, null, 2) + "\n", { mode: 0o600 });
|
|
185
|
+
// writeFileSync's mode only applies on CREATE; chmod unconditionally so an
|
|
186
|
+
// existing file (created before this code, or with a looser umask) is tightened.
|
|
187
|
+
chmodSync(file, 0o600);
|
|
188
|
+
return current;
|
|
189
|
+
}
|
|
190
|
+
|
|
191
|
+
/**
|
|
192
|
+
* Remove a channel entry from channels.json by name, preserving the rest. Returns
|
|
193
|
+
* the persisted file contents, or null if the file didn't exist. A no-op (name
|
|
194
|
+
* absent) still rewrites the file (idempotent) when the file exists.
|
|
195
|
+
*/
|
|
196
|
+
export function removeChannelEntry(name: string, stateDir?: string): ChannelsFile | null {
|
|
197
|
+
const dir = stateDir ?? defaultStateDir();
|
|
198
|
+
const file = channelsFilePath(dir);
|
|
199
|
+
if (!existsSync(file)) return null;
|
|
200
|
+
const current = readChannelsFile(dir);
|
|
201
|
+
current.channels = current.channels.filter((c) => c.name !== name);
|
|
202
|
+
writeFileSync(file, JSON.stringify(current, null, 2) + "\n", { mode: 0o600 });
|
|
203
|
+
chmodSync(file, 0o600);
|
|
204
|
+
return current;
|
|
205
|
+
}
|
|
206
|
+
|
|
207
|
+
/**
|
|
208
|
+
* Build the live channel map: resolve entries, instantiate each transport.
|
|
209
|
+
* Throws if an entry names an unknown transport kind.
|
|
210
|
+
*/
|
|
211
|
+
export function loadRegistry(opts: {
|
|
212
|
+
stateDir?: string;
|
|
213
|
+
loadEnv?: boolean;
|
|
214
|
+
} = {}): Map<string, Channel> {
|
|
215
|
+
const entries = resolveChannelEntries(opts);
|
|
216
|
+
const channels = new Map<string, Channel>();
|
|
217
|
+
for (const entry of entries) {
|
|
218
|
+
if (channels.has(entry.name)) {
|
|
219
|
+
throw new Error(`registry: duplicate channel name "${entry.name}"`);
|
|
220
|
+
}
|
|
221
|
+
channels.set(entry.name, {
|
|
222
|
+
name: entry.name,
|
|
223
|
+
transport: instantiateTransport(entry),
|
|
224
|
+
entry,
|
|
225
|
+
});
|
|
226
|
+
}
|
|
227
|
+
return channels;
|
|
228
|
+
}
|
|
@@ -0,0 +1,64 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Port-resolution tests (channel#41).
|
|
3
|
+
*
|
|
4
|
+
* The hub supervisor injects `PORT` from the module's services.json `entry.port`
|
|
5
|
+
* and PROBES that same port for readiness (and reverse-proxies `/agent/*` to
|
|
6
|
+
* it). Pre-#41 the daemon read only `PARACHUTE_AGENT_PORT` (default 1941), so
|
|
7
|
+
* it ignored the supervisor's `PORT` and could bind a different port than the
|
|
8
|
+
* supervisor probed — the supervisor then reported `started_but_unbound` and the
|
|
9
|
+
* proxy routed to a dead port. `resolvePort` now honors `PORT` first so the bound
|
|
10
|
+
* port (which is also the self-registered port) matches what the supervisor
|
|
11
|
+
* assigned.
|
|
12
|
+
*
|
|
13
|
+
* Resolution order: `PORT > PARACHUTE_AGENT_PORT > PARACHUTE_CHANNEL_PORT > 1941`.
|
|
14
|
+
* The legacy `PARACHUTE_CHANNEL_PORT` stays a recognized fallback (back-compat
|
|
15
|
+
* for pre-rename operator setups) but the new `PARACHUTE_AGENT_PORT` wins over it.
|
|
16
|
+
*/
|
|
17
|
+
import { describe, test, expect } from "bun:test";
|
|
18
|
+
import { resolvePort } from "./daemon.ts";
|
|
19
|
+
|
|
20
|
+
describe("resolvePort — PORT > PARACHUTE_AGENT_PORT > PARACHUTE_CHANNEL_PORT > 1941", () => {
|
|
21
|
+
test("honors the supervisor-injected PORT first", () => {
|
|
22
|
+
expect(resolvePort({ PORT: "19415" })).toBe(19415);
|
|
23
|
+
});
|
|
24
|
+
|
|
25
|
+
test("PORT wins even when PARACHUTE_AGENT_PORT is also set", () => {
|
|
26
|
+
expect(resolvePort({ PORT: "1941", PARACHUTE_AGENT_PORT: "19415" })).toBe(1941);
|
|
27
|
+
});
|
|
28
|
+
|
|
29
|
+
test("falls back to PARACHUTE_AGENT_PORT when PORT is unset", () => {
|
|
30
|
+
expect(resolvePort({ PARACHUTE_AGENT_PORT: "2025" })).toBe(2025);
|
|
31
|
+
});
|
|
32
|
+
|
|
33
|
+
test("the legacy PARACHUTE_CHANNEL_PORT STILL works as a fallback (back-compat)", () => {
|
|
34
|
+
// Pre-rename operator setups exported PARACHUTE_CHANNEL_PORT; it stays a
|
|
35
|
+
// recognized tier so those installs keep binding the intended port.
|
|
36
|
+
expect(resolvePort({ PARACHUTE_CHANNEL_PORT: "2030" })).toBe(2030);
|
|
37
|
+
});
|
|
38
|
+
|
|
39
|
+
test("PARACHUTE_AGENT_PORT wins over the legacy PARACHUTE_CHANNEL_PORT", () => {
|
|
40
|
+
// Both present → the NEW var takes precedence; the legacy var is only a
|
|
41
|
+
// fallback for when the new one is absent.
|
|
42
|
+
expect(resolvePort({ PARACHUTE_AGENT_PORT: "2040", PARACHUTE_CHANNEL_PORT: "2050" })).toBe(2040);
|
|
43
|
+
});
|
|
44
|
+
|
|
45
|
+
test("falls back to the canonical 1941 default when none is set", () => {
|
|
46
|
+
expect(resolvePort({})).toBe(1941);
|
|
47
|
+
});
|
|
48
|
+
|
|
49
|
+
test("an EMPTY PORT='' falls through to PARACHUTE_AGENT_PORT (|| not ??)", () => {
|
|
50
|
+
// With `??` an empty string is "defined" → parseInt("") = NaN → bind port 0.
|
|
51
|
+
// `||` skips the empty string to the next tier.
|
|
52
|
+
expect(resolvePort({ PORT: "", PARACHUTE_AGENT_PORT: "2000" })).toBe(2000);
|
|
53
|
+
});
|
|
54
|
+
|
|
55
|
+
test("an EMPTY PARACHUTE_AGENT_PORT='' falls through to the legacy PARACHUTE_CHANNEL_PORT", () => {
|
|
56
|
+
expect(resolvePort({ PARACHUTE_AGENT_PORT: "", PARACHUTE_CHANNEL_PORT: "2060" })).toBe(2060);
|
|
57
|
+
});
|
|
58
|
+
|
|
59
|
+
test("a non-numeric PORT='abc' falls through to the canonical default", () => {
|
|
60
|
+
// parseInt("abc") = NaN → falsy → falls through past PARACHUTE_AGENT_PORT +
|
|
61
|
+
// PARACHUTE_CHANNEL_PORT (also unset here) to 1941. No garbage port.
|
|
62
|
+
expect(resolvePort({ PORT: "abc" })).toBe(1941);
|
|
63
|
+
});
|
|
64
|
+
});
|