@openparachute/agent 0.1.2 → 0.2.2
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 +35 -42
- 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/dist/assets/index-C-iWdFFV.css +1 -0
- package/web/ui/dist/assets/index-VFETBk0a.js +60 -0
- package/web/ui/dist/index.html +15 -0
- 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 -263
- 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 -172
- package/scripts/init-first-agent.ts +0 -378
- 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 -80
- 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/api-translator.test.ts +0 -306
- package/src/channels/api-translator.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 -128
- 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 -269
- package/src/container-runtime.ts +0 -167
- 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 -176
- package/src/db/connection.ts +0 -259
- 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 -232
- 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.test.ts +0 -126
- package/src/mcp/tools/channels.ts +0 -134
- 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/expand-path.test.ts +0 -82
- package/src/modules/mount-security/index.ts +0 -459
- 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 -641
- 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 -461
- package/src/secrets/master-key.ts +0 -70
- package/src/secrets/secrets.test.ts +0 -651
- package/src/session-manager.attachments.test.ts +0 -171
- package/src/session-manager.dup-skip.test.ts +0 -173
- package/src/session-manager.migrate.test.ts +0 -59
- package/src/session-manager.ts +0 -451
- 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 -233
- 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 -472
- package/src/web/routes/channels.ts +0 -311
- package/src/web/routes/oauth-providers.ts +0 -42
- package/src/web/routes/secrets.test.ts +0 -220
- package/src/web/routes/secrets.ts +0 -317
- 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 -1024
- package/src/web/services-manifest.test.ts +0 -148
- package/src/web/services-manifest.ts +0 -66
- 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 -1248
- package/web/ui/src/lib/auth.test.ts +0 -352
- package/web/ui/src/lib/auth.ts +0 -405
- 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.test.tsx +0 -206
- package/web/ui/src/routes/GroupDetail.tsx +0 -880
- 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 -942
- 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 -363
- 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
package/src/secrets/index.ts
DELETED
|
@@ -1,461 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* Public API for paraclaw's secret store. Values are AES-256-GCM encrypted
|
|
3
|
-
* in-process before landing in the central DB; decrypted only when injected
|
|
4
|
-
* into per-session containers (`src/container-runner.ts`).
|
|
5
|
-
*
|
|
6
|
-
* Naming: a secret is keyed by `(name, agent_group_id)`. A NULL agent_group_id
|
|
7
|
-
* is global; a non-NULL agent_group_id scopes the secret to that group only.
|
|
8
|
-
*
|
|
9
|
-
* Resolution preference at injection time: agent-scoped secret with that
|
|
10
|
-
* name beats the global one. The host walks both rows and the scoped wins.
|
|
11
|
-
*
|
|
12
|
-
* Injection policy lives on the recipient `agent_groups.secret_mode` row
|
|
13
|
-
* (migration 023): `all` injects every in-scope secret; `selective` injects
|
|
14
|
-
* only those with an explicit `secret_assignments` row pointing to the group.
|
|
15
|
-
*/
|
|
16
|
-
import crypto from 'crypto';
|
|
17
|
-
|
|
18
|
-
import { getDb } from '../db/connection.js';
|
|
19
|
-
import type { Database } from '../db/connection.js';
|
|
20
|
-
import { decryptSecret, deriveKey, encryptSecret } from './crypto.js';
|
|
21
|
-
import { loadOrCreateMasterKey } from './master-key.js';
|
|
22
|
-
|
|
23
|
-
// Domain tag for HKDF-derived secrets-store key. Bumping the version (v2…)
|
|
24
|
-
// would force re-encryption of every row in this table. See crypto.ts.
|
|
25
|
-
//
|
|
26
|
-
// ⚠ The `paraclaw.` prefix is a cryptographic domain separator and must
|
|
27
|
-
// stay frozen across the paraclaw → parachute-agent rename. Renaming it
|
|
28
|
-
// changes the derived key and renders every existing ciphertext row
|
|
29
|
-
// undecryptable. The brand-sweep documentation lives in commit messages
|
|
30
|
-
// and CHANGELOG; the bytes here do not change.
|
|
31
|
-
const SECRETS_INFO = 'paraclaw.secrets.v1';
|
|
32
|
-
|
|
33
|
-
function secretsKey(): Buffer {
|
|
34
|
-
return deriveKey(loadOrCreateMasterKey(), SECRETS_INFO);
|
|
35
|
-
}
|
|
36
|
-
|
|
37
|
-
export type SecretKind = 'channel-token' | 'api-key' | 'generic';
|
|
38
|
-
export type AssignedMode = 'all' | 'selective';
|
|
39
|
-
|
|
40
|
-
export interface SecretRow {
|
|
41
|
-
id: string;
|
|
42
|
-
name: string;
|
|
43
|
-
kind: SecretKind;
|
|
44
|
-
agent_group_id: string | null;
|
|
45
|
-
created_at: string;
|
|
46
|
-
updated_at: string;
|
|
47
|
-
}
|
|
48
|
-
|
|
49
|
-
export interface PutSecretOpts {
|
|
50
|
-
kind?: SecretKind;
|
|
51
|
-
agent_group_id?: string | null;
|
|
52
|
-
}
|
|
53
|
-
|
|
54
|
-
interface RawRow extends SecretRow {
|
|
55
|
-
value_encrypted: string;
|
|
56
|
-
}
|
|
57
|
-
|
|
58
|
-
function db(): Database {
|
|
59
|
-
return getDb();
|
|
60
|
-
}
|
|
61
|
-
|
|
62
|
-
function nowIso(): string {
|
|
63
|
-
return new Date().toISOString();
|
|
64
|
-
}
|
|
65
|
-
|
|
66
|
-
/** Insert or update a secret. Returns the row's id. */
|
|
67
|
-
export function putSecret(name: string, value: string, opts: PutSecretOpts = {}): string {
|
|
68
|
-
const key = secretsKey();
|
|
69
|
-
const ct = encryptSecret(value, key);
|
|
70
|
-
const agentGroupId = opts.agent_group_id ?? null;
|
|
71
|
-
const kind = opts.kind ?? 'generic';
|
|
72
|
-
|
|
73
|
-
const existing = db()
|
|
74
|
-
.prepare<{ id: string }>(`SELECT id FROM secrets WHERE name = @name AND agent_group_id IS @agent_group_id`)
|
|
75
|
-
.get({ name, agent_group_id: agentGroupId });
|
|
76
|
-
|
|
77
|
-
const now = nowIso();
|
|
78
|
-
if (existing) {
|
|
79
|
-
db()
|
|
80
|
-
.prepare(
|
|
81
|
-
`UPDATE secrets
|
|
82
|
-
SET value_encrypted = @value_encrypted,
|
|
83
|
-
kind = @kind,
|
|
84
|
-
updated_at = @updated_at
|
|
85
|
-
WHERE id = @id`,
|
|
86
|
-
)
|
|
87
|
-
.run({
|
|
88
|
-
id: existing.id,
|
|
89
|
-
value_encrypted: ct,
|
|
90
|
-
kind,
|
|
91
|
-
updated_at: now,
|
|
92
|
-
});
|
|
93
|
-
return existing.id;
|
|
94
|
-
}
|
|
95
|
-
|
|
96
|
-
const id = crypto.randomUUID();
|
|
97
|
-
// Auto-seed the matching `secret_assignments` row when the secret is
|
|
98
|
-
// scoped to a group (paraclaw#127). For a scoped secret the only valid
|
|
99
|
-
// assignment-row pair is (id, owning_group); the resolver only injects
|
|
100
|
-
// when `s.agent_group_id = g.id OR s.agent_group_id IS NULL`, so an
|
|
101
|
-
// assignment elsewhere is meaningless. Without this seed, scoped
|
|
102
|
-
// creates land orphaned under the default `selective` group mode and
|
|
103
|
-
// are silently invisible to `resolveInjectableSecrets`. INSERT path
|
|
104
|
-
// only — UPDATE/rotate leaves the existing assignment set alone.
|
|
105
|
-
// ON CONFLICT DO NOTHING for idempotency-on-replay (the constraint
|
|
106
|
-
// already exists in `replaceAssignments` for the same reason).
|
|
107
|
-
db().transaction(() => {
|
|
108
|
-
db()
|
|
109
|
-
.prepare(
|
|
110
|
-
`INSERT INTO secrets
|
|
111
|
-
(id, name, value_encrypted, kind, agent_group_id, created_at, updated_at)
|
|
112
|
-
VALUES
|
|
113
|
-
(@id, @name, @value_encrypted, @kind, @agent_group_id, @created_at, @updated_at)`,
|
|
114
|
-
)
|
|
115
|
-
.run({
|
|
116
|
-
id,
|
|
117
|
-
name,
|
|
118
|
-
value_encrypted: ct,
|
|
119
|
-
kind,
|
|
120
|
-
agent_group_id: agentGroupId,
|
|
121
|
-
created_at: now,
|
|
122
|
-
updated_at: now,
|
|
123
|
-
});
|
|
124
|
-
if (agentGroupId !== null) {
|
|
125
|
-
db()
|
|
126
|
-
.prepare(
|
|
127
|
-
`INSERT INTO secret_assignments (secret_id, agent_group_id, created_at)
|
|
128
|
-
VALUES (@secret_id, @agent_group_id, @created_at)
|
|
129
|
-
ON CONFLICT (secret_id, agent_group_id) DO NOTHING`,
|
|
130
|
-
)
|
|
131
|
-
.run({ secret_id: id, agent_group_id: agentGroupId, created_at: now });
|
|
132
|
-
}
|
|
133
|
-
})();
|
|
134
|
-
return id;
|
|
135
|
-
}
|
|
136
|
-
|
|
137
|
-
/**
|
|
138
|
-
* Decrypt and return a secret's plaintext value. Returns undefined if the
|
|
139
|
-
* named secret does not exist for the given scope. Resolution: an
|
|
140
|
-
* agent-scoped secret beats a global one with the same name.
|
|
141
|
-
*/
|
|
142
|
-
export function getSecret(name: string, agentGroupId?: string | null): string | undefined {
|
|
143
|
-
const key = secretsKey();
|
|
144
|
-
const scoped = agentGroupId
|
|
145
|
-
? db()
|
|
146
|
-
.prepare<RawRow>(`SELECT * FROM secrets WHERE name = @name AND agent_group_id = @agent_group_id`)
|
|
147
|
-
.get({ name, agent_group_id: agentGroupId })
|
|
148
|
-
: undefined;
|
|
149
|
-
const row =
|
|
150
|
-
scoped ?? db().prepare<RawRow>(`SELECT * FROM secrets WHERE name = @name AND agent_group_id IS NULL`).get({ name });
|
|
151
|
-
if (!row) return undefined;
|
|
152
|
-
return decryptSecret(row.value_encrypted, key);
|
|
153
|
-
}
|
|
154
|
-
|
|
155
|
-
/** Names + metadata only — never decrypts. */
|
|
156
|
-
export function listSecrets(agentGroupId?: string | null): SecretRow[] {
|
|
157
|
-
if (agentGroupId === undefined) {
|
|
158
|
-
return db()
|
|
159
|
-
.prepare<SecretRow>(
|
|
160
|
-
`SELECT id, name, kind, agent_group_id, created_at, updated_at
|
|
161
|
-
FROM secrets ORDER BY name`,
|
|
162
|
-
)
|
|
163
|
-
.all();
|
|
164
|
-
}
|
|
165
|
-
if (agentGroupId === null) {
|
|
166
|
-
return db()
|
|
167
|
-
.prepare<SecretRow>(
|
|
168
|
-
`SELECT id, name, kind, agent_group_id, created_at, updated_at
|
|
169
|
-
FROM secrets WHERE agent_group_id IS NULL ORDER BY name`,
|
|
170
|
-
)
|
|
171
|
-
.all();
|
|
172
|
-
}
|
|
173
|
-
return db()
|
|
174
|
-
.prepare<SecretRow>(
|
|
175
|
-
`SELECT id, name, kind, agent_group_id, created_at, updated_at
|
|
176
|
-
FROM secrets
|
|
177
|
-
WHERE agent_group_id = @agent_group_id OR agent_group_id IS NULL
|
|
178
|
-
ORDER BY name`,
|
|
179
|
-
)
|
|
180
|
-
.all({ agent_group_id: agentGroupId });
|
|
181
|
-
}
|
|
182
|
-
|
|
183
|
-
export function deleteSecret(id: string): boolean {
|
|
184
|
-
const r = db().prepare(`DELETE FROM secrets WHERE id = @id`).run({ id });
|
|
185
|
-
return r.changes > 0;
|
|
186
|
-
}
|
|
187
|
-
|
|
188
|
-
/**
|
|
189
|
-
* Resolve the secrets that should be injected into a session for the given
|
|
190
|
-
* agent group. Returns plaintext values; callers are expected to inject as
|
|
191
|
-
* env vars and never log. Agent-scoped wins over global on name collision.
|
|
192
|
-
*
|
|
193
|
-
* Mode lives on the recipient `agent_groups.secret_mode`:
|
|
194
|
-
* - `all` — inject every in-scope secret (scoped + globals).
|
|
195
|
-
* - `selective` — inject only those with an explicit assignment row
|
|
196
|
-
* pointing to this group. Lets operators stage credentials
|
|
197
|
-
* in the store before any agent gets them and revoke per
|
|
198
|
-
* agent without rotating the value.
|
|
199
|
-
*
|
|
200
|
-
* Unknown agent_group_id is treated as `selective` (the safe default) — the
|
|
201
|
-
* group-level row is the source of truth, so a missing row means we err on
|
|
202
|
-
* the side of withholding.
|
|
203
|
-
*/
|
|
204
|
-
export function resolveInjectableSecrets(agentGroupId: string): Map<string, string> {
|
|
205
|
-
const key = secretsKey();
|
|
206
|
-
const rows = db()
|
|
207
|
-
.prepare<RawRow>(
|
|
208
|
-
`SELECT s.*
|
|
209
|
-
FROM secrets s
|
|
210
|
-
LEFT JOIN secret_assignments a
|
|
211
|
-
ON a.secret_id = s.id
|
|
212
|
-
AND a.agent_group_id = @agent_group_id
|
|
213
|
-
LEFT JOIN agent_groups g
|
|
214
|
-
ON g.id = @agent_group_id
|
|
215
|
-
WHERE (s.agent_group_id = @agent_group_id OR s.agent_group_id IS NULL)
|
|
216
|
-
AND (g.secret_mode = 'all' OR a.secret_id IS NOT NULL)
|
|
217
|
-
ORDER BY s.agent_group_id IS NULL`,
|
|
218
|
-
)
|
|
219
|
-
.all({ agent_group_id: agentGroupId });
|
|
220
|
-
|
|
221
|
-
const out = new Map<string, string>();
|
|
222
|
-
for (const row of rows) {
|
|
223
|
-
if (out.has(row.name)) continue;
|
|
224
|
-
out.set(row.name, decryptSecret(row.value_encrypted, key));
|
|
225
|
-
}
|
|
226
|
-
return out;
|
|
227
|
-
}
|
|
228
|
-
|
|
229
|
-
// ── Assignments ──
|
|
230
|
-
|
|
231
|
-
export interface SecretAssignment {
|
|
232
|
-
secret_id: string;
|
|
233
|
-
agent_group_id: string;
|
|
234
|
-
created_at: string;
|
|
235
|
-
}
|
|
236
|
-
|
|
237
|
-
/** All agent_group_ids assigned to this secret (selective-mode allowlist). */
|
|
238
|
-
export function listAssignments(secretId: string): string[] {
|
|
239
|
-
const rows = db()
|
|
240
|
-
.prepare<{ agent_group_id: string }>(
|
|
241
|
-
`SELECT agent_group_id FROM secret_assignments
|
|
242
|
-
WHERE secret_id = @secret_id
|
|
243
|
-
ORDER BY agent_group_id`,
|
|
244
|
-
)
|
|
245
|
-
.all({ secret_id: secretId });
|
|
246
|
-
return rows.map((r) => r.agent_group_id);
|
|
247
|
-
}
|
|
248
|
-
|
|
249
|
-
/**
|
|
250
|
-
* Replace the assignment set atomically. Empty array = revoke everything.
|
|
251
|
-
* Throws if the secret doesn't exist; FK ON DELETE CASCADE handles agent
|
|
252
|
-
* groups that vanish. The whole replace runs inside one transaction so
|
|
253
|
-
* the UI's "save" button is all-or-nothing.
|
|
254
|
-
*/
|
|
255
|
-
export function replaceAssignments(secretId: string, agentGroupIds: string[]): void {
|
|
256
|
-
const exists = db().prepare<{ id: string }>(`SELECT id FROM secrets WHERE id = @id`).get({ id: secretId });
|
|
257
|
-
if (!exists) throw new Error(`secret not found: ${secretId}`);
|
|
258
|
-
const now = nowIso();
|
|
259
|
-
db().transaction(() => {
|
|
260
|
-
db().prepare(`DELETE FROM secret_assignments WHERE secret_id = @secret_id`).run({ secret_id: secretId });
|
|
261
|
-
const insert = db().prepare(
|
|
262
|
-
`INSERT INTO secret_assignments (secret_id, agent_group_id, created_at)
|
|
263
|
-
VALUES (@secret_id, @agent_group_id, @created_at)`,
|
|
264
|
-
);
|
|
265
|
-
for (const gid of agentGroupIds) {
|
|
266
|
-
insert.run({ secret_id: secretId, agent_group_id: gid, created_at: now });
|
|
267
|
-
}
|
|
268
|
-
})();
|
|
269
|
-
}
|
|
270
|
-
|
|
271
|
-
/** Idempotent — re-adding an existing assignment is a no-op (composite PK). */
|
|
272
|
-
export function addAssignment(secretId: string, agentGroupId: string): boolean {
|
|
273
|
-
const r = db()
|
|
274
|
-
.prepare(
|
|
275
|
-
`INSERT INTO secret_assignments (secret_id, agent_group_id, created_at)
|
|
276
|
-
VALUES (@secret_id, @agent_group_id, @created_at)
|
|
277
|
-
ON CONFLICT (secret_id, agent_group_id) DO NOTHING`,
|
|
278
|
-
)
|
|
279
|
-
.run({ secret_id: secretId, agent_group_id: agentGroupId, created_at: nowIso() });
|
|
280
|
-
return r.changes > 0;
|
|
281
|
-
}
|
|
282
|
-
|
|
283
|
-
export function removeAssignment(secretId: string, agentGroupId: string): boolean {
|
|
284
|
-
const r = db()
|
|
285
|
-
.prepare(`DELETE FROM secret_assignments WHERE secret_id = @secret_id AND agent_group_id = @agent_group_id`)
|
|
286
|
-
.run({ secret_id: secretId, agent_group_id: agentGroupId });
|
|
287
|
-
return r.changes > 0;
|
|
288
|
-
}
|
|
289
|
-
|
|
290
|
-
// ── Staleness detection (Bug B) ──
|
|
291
|
-
|
|
292
|
-
export interface StaleSession {
|
|
293
|
-
sessionId: string;
|
|
294
|
-
agentGroupId: string;
|
|
295
|
-
agentGroupName: string;
|
|
296
|
-
agentGroupFolder: string;
|
|
297
|
-
sessionCreatedAt: string;
|
|
298
|
-
secretUpdatedAt: string;
|
|
299
|
-
}
|
|
300
|
-
|
|
301
|
-
/**
|
|
302
|
-
* Sessions whose container was spawned BEFORE this secret was last updated
|
|
303
|
-
* AND whose agent group would inject the secret. The injection predicate
|
|
304
|
-
* mirrors `resolveInjectableSecrets` for the configurations the UI can
|
|
305
|
-
* actually create:
|
|
306
|
-
* - scoped secret → matches its parent group (`s.agent_group_id = g.id`)
|
|
307
|
-
* - global secret → matches any group with `secret_mode='all'` OR an
|
|
308
|
-
* explicit `secret_assignments` row
|
|
309
|
-
*
|
|
310
|
-
* Note on a subtle asymmetry: `resolveInjectableSecrets` additionally gates
|
|
311
|
-
* scoped secrets through `(secret_mode='all' OR assignment row exists)` on
|
|
312
|
-
* the recipient group. The SQL here accepts the scoped match unconditionally.
|
|
313
|
-
* The asymmetry is benign — the only config where it would diverge (a scoped
|
|
314
|
-
* secret in a `selective`-mode group with no assignment row) is structurally
|
|
315
|
-
* unreachable from `putSecret`: paraclaw#127 made the INSERT path auto-seed
|
|
316
|
-
* the (id, owning_group) assignment row in the same transaction. If a future
|
|
317
|
-
* code path bypasses `putSecret` and writes the orphan state directly, tighten
|
|
318
|
-
* the SQL to add the same gate.
|
|
319
|
-
*
|
|
320
|
-
* The host injects env vars at spawn time only — there is no in-process
|
|
321
|
-
* update path. This helper powers the post-save banner that prompts the
|
|
322
|
-
* operator to restart the specific sessions that need to see the change.
|
|
323
|
-
*
|
|
324
|
-
* Returns empty when the secret doesn't exist (caller handles 404).
|
|
325
|
-
*/
|
|
326
|
-
export function findStaleSessionsForSecret(secretId: string): StaleSession[] {
|
|
327
|
-
const rows = db()
|
|
328
|
-
.prepare<{
|
|
329
|
-
session_id: string;
|
|
330
|
-
agent_group_id: string;
|
|
331
|
-
agent_group_name: string;
|
|
332
|
-
agent_group_folder: string;
|
|
333
|
-
session_created_at: string;
|
|
334
|
-
secret_updated_at: string;
|
|
335
|
-
}>(
|
|
336
|
-
`SELECT
|
|
337
|
-
sess.id AS session_id,
|
|
338
|
-
g.id AS agent_group_id,
|
|
339
|
-
g.name AS agent_group_name,
|
|
340
|
-
g.folder AS agent_group_folder,
|
|
341
|
-
sess.created_at AS session_created_at,
|
|
342
|
-
s.updated_at AS secret_updated_at
|
|
343
|
-
FROM secrets s
|
|
344
|
-
JOIN agent_groups g
|
|
345
|
-
ON s.agent_group_id = g.id
|
|
346
|
-
OR s.agent_group_id IS NULL
|
|
347
|
-
LEFT JOIN secret_assignments a
|
|
348
|
-
ON a.secret_id = s.id AND a.agent_group_id = g.id
|
|
349
|
-
JOIN sessions sess
|
|
350
|
-
ON sess.agent_group_id = g.id
|
|
351
|
-
WHERE s.id = @secret_id
|
|
352
|
-
AND sess.container_status = 'running'
|
|
353
|
-
AND sess.created_at < s.updated_at
|
|
354
|
-
AND (
|
|
355
|
-
s.agent_group_id = g.id
|
|
356
|
-
OR (s.agent_group_id IS NULL
|
|
357
|
-
AND (g.secret_mode = 'all' OR a.secret_id IS NOT NULL))
|
|
358
|
-
)
|
|
359
|
-
ORDER BY sess.created_at DESC`,
|
|
360
|
-
)
|
|
361
|
-
.all({ secret_id: secretId });
|
|
362
|
-
return rows.map((r) => ({
|
|
363
|
-
sessionId: r.session_id,
|
|
364
|
-
agentGroupId: r.agent_group_id,
|
|
365
|
-
agentGroupName: r.agent_group_name,
|
|
366
|
-
agentGroupFolder: r.agent_group_folder,
|
|
367
|
-
sessionCreatedAt: r.session_created_at,
|
|
368
|
-
secretUpdatedAt: r.secret_updated_at,
|
|
369
|
-
}));
|
|
370
|
-
}
|
|
371
|
-
|
|
372
|
-
/** Metadata-only single-row read by id. Returns undefined if missing. */
|
|
373
|
-
export function getSecretById(id: string): SecretRow | undefined {
|
|
374
|
-
return db()
|
|
375
|
-
.prepare<SecretRow>(`SELECT id, name, kind, agent_group_id, created_at, updated_at FROM secrets WHERE id = ?`)
|
|
376
|
-
.get(id);
|
|
377
|
-
}
|
|
378
|
-
|
|
379
|
-
/**
|
|
380
|
-
* Why a secret lands in a particular agent group's injectable set:
|
|
381
|
-
* - `scoped` — secret is owned by this group (`s.agent_group_id = g.id`).
|
|
382
|
-
* - `assigned` — global secret with an explicit `secret_assignments` row
|
|
383
|
-
* pointing at this group.
|
|
384
|
-
* - `global` — global secret with no assignment row, included only because
|
|
385
|
-
* the recipient group is in `secret_mode='all'`.
|
|
386
|
-
*
|
|
387
|
-
* When a global has BOTH an assignment row AND `secret_mode='all'`, we report
|
|
388
|
-
* `assigned` — the explicit row reflects deliberate operator intent, while
|
|
389
|
-
* mode='all' is a blanket setting; surfacing the more-specific reason makes
|
|
390
|
-
* the GroupDetail page actionable ("revoke this assignment" vs "flip to
|
|
391
|
-
* selective"). See paraclaw#104.
|
|
392
|
-
*/
|
|
393
|
-
export type SecretInclusionScope = 'global' | 'scoped' | 'assigned';
|
|
394
|
-
|
|
395
|
-
export interface InjectableSecretView extends SecretRow {
|
|
396
|
-
scope: SecretInclusionScope;
|
|
397
|
-
}
|
|
398
|
-
|
|
399
|
-
/**
|
|
400
|
-
* Metadata-only mirror of `resolveInjectableSecrets` for the GroupDetail
|
|
401
|
-
* "Secrets" panel. Returns the same row set (subject to the same SQL gate)
|
|
402
|
-
* tagged with the inclusion reason — never decrypts. Caller is the read-only
|
|
403
|
-
* `GET /api/groups/:folder/secrets` route.
|
|
404
|
-
*
|
|
405
|
-
* The SQL mirrors `resolveInjectableSecrets` (the `(s.agent_group_id = g.id
|
|
406
|
-
* OR s.agent_group_id IS NULL)` row predicate gated by `(secret_mode='all'
|
|
407
|
-
* OR assignment exists)`) so the panel cannot disagree with what the
|
|
408
|
-
* container will actually receive at spawn time. Drift here would defeat
|
|
409
|
-
* the entire point of #104 — keep them in lockstep. If you change either,
|
|
410
|
-
* change both.
|
|
411
|
-
*
|
|
412
|
-
* `ORDER BY s.agent_group_id IS NULL` puts scoped rows first so the
|
|
413
|
-
* dedupe-by-name loop honors the "scoped wins on collision" rule
|
|
414
|
-
* `resolveInjectableSecrets` enforces.
|
|
415
|
-
*/
|
|
416
|
-
export function listInjectableSecretsForGroup(agentGroupId: string): InjectableSecretView[] {
|
|
417
|
-
const rows = db()
|
|
418
|
-
.prepare<{
|
|
419
|
-
id: string;
|
|
420
|
-
name: string;
|
|
421
|
-
kind: SecretKind;
|
|
422
|
-
agent_group_id: string | null;
|
|
423
|
-
created_at: string;
|
|
424
|
-
updated_at: string;
|
|
425
|
-
assignment_present: number;
|
|
426
|
-
}>(
|
|
427
|
-
`SELECT s.id, s.name, s.kind, s.agent_group_id, s.created_at, s.updated_at,
|
|
428
|
-
CASE WHEN a.secret_id IS NULL THEN 0 ELSE 1 END AS assignment_present
|
|
429
|
-
FROM secrets s
|
|
430
|
-
LEFT JOIN secret_assignments a
|
|
431
|
-
ON a.secret_id = s.id
|
|
432
|
-
AND a.agent_group_id = @agent_group_id
|
|
433
|
-
LEFT JOIN agent_groups g
|
|
434
|
-
ON g.id = @agent_group_id
|
|
435
|
-
WHERE (s.agent_group_id = @agent_group_id OR s.agent_group_id IS NULL)
|
|
436
|
-
AND (g.secret_mode = 'all' OR a.secret_id IS NOT NULL)
|
|
437
|
-
ORDER BY s.agent_group_id IS NULL, s.name`,
|
|
438
|
-
)
|
|
439
|
-
.all({ agent_group_id: agentGroupId });
|
|
440
|
-
|
|
441
|
-
const out: InjectableSecretView[] = [];
|
|
442
|
-
const seen = new Set<string>();
|
|
443
|
-
for (const row of rows) {
|
|
444
|
-
if (seen.has(row.name)) continue;
|
|
445
|
-
seen.add(row.name);
|
|
446
|
-
let scope: SecretInclusionScope;
|
|
447
|
-
if (row.agent_group_id === agentGroupId) scope = 'scoped';
|
|
448
|
-
else if (row.assignment_present === 1) scope = 'assigned';
|
|
449
|
-
else scope = 'global';
|
|
450
|
-
out.push({
|
|
451
|
-
id: row.id,
|
|
452
|
-
name: row.name,
|
|
453
|
-
kind: row.kind,
|
|
454
|
-
agent_group_id: row.agent_group_id,
|
|
455
|
-
created_at: row.created_at,
|
|
456
|
-
updated_at: row.updated_at,
|
|
457
|
-
scope,
|
|
458
|
-
});
|
|
459
|
-
}
|
|
460
|
-
return out;
|
|
461
|
-
}
|
|
@@ -1,70 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* Master key bootstrap. Stores a 32-byte (256-bit) random key at
|
|
3
|
-
* `~/.parachute/agent/master.key` with mode 0600. Generated on first start;
|
|
4
|
-
* loaded from disk on subsequent starts.
|
|
5
|
-
*
|
|
6
|
-
* The key is never written to logs, never sent over the wire, never put in
|
|
7
|
-
* env vars. Loss of the file = loss of every encrypted secret (no recovery
|
|
8
|
-
* path); rotation requires re-encrypting every row.
|
|
9
|
-
*/
|
|
10
|
-
import crypto from 'crypto';
|
|
11
|
-
import fs from 'fs';
|
|
12
|
-
import path from 'path';
|
|
13
|
-
|
|
14
|
-
import { CENTRAL_DB_DIR } from '../config.js';
|
|
15
|
-
|
|
16
|
-
const KEY_LEN = 32;
|
|
17
|
-
// `master.key` lives next to the central DB so a single backup of
|
|
18
|
-
// `<PARACHUTE_DIR>/agent/` captures both crypto material and DB state, and
|
|
19
|
-
// so `PARACHUTE_HOME` overrides reroute both atoms together — sandboxes
|
|
20
|
-
// that override the home dir get a fresh DB AND a fresh master.key.
|
|
21
|
-
const KEY_DIR = CENTRAL_DB_DIR;
|
|
22
|
-
const KEY_PATH = path.join(KEY_DIR, 'master.key');
|
|
23
|
-
|
|
24
|
-
let cached: Buffer | null = null;
|
|
25
|
-
|
|
26
|
-
export function getMasterKeyPath(): string {
|
|
27
|
-
return KEY_PATH;
|
|
28
|
-
}
|
|
29
|
-
|
|
30
|
-
export function loadOrCreateMasterKey(): Buffer {
|
|
31
|
-
if (cached) return cached;
|
|
32
|
-
|
|
33
|
-
if (fs.existsSync(KEY_PATH)) {
|
|
34
|
-
// Refuse to load a key file that's group/world readable. The file was
|
|
35
|
-
// created with mode 0600; if something has loosened it (chmod, restore
|
|
36
|
-
// from a backup tarball, etc.) we'd rather fail loud than silently keep
|
|
37
|
-
// serving secrets out of a file anyone on the box can read.
|
|
38
|
-
const stat = fs.statSync(KEY_PATH);
|
|
39
|
-
const perm = stat.mode & 0o777;
|
|
40
|
-
if ((stat.mode & 0o077) !== 0) {
|
|
41
|
-
throw new Error(
|
|
42
|
-
`Master key at ${KEY_PATH} has permissive mode 0${perm.toString(8).padStart(3, '0')}; ` +
|
|
43
|
-
`expected 0600. Run: chmod 600 ${KEY_PATH}`,
|
|
44
|
-
);
|
|
45
|
-
}
|
|
46
|
-
const buf = fs.readFileSync(KEY_PATH);
|
|
47
|
-
if (buf.length !== KEY_LEN) {
|
|
48
|
-
throw new Error(`Master key at ${KEY_PATH} is ${buf.length} bytes; expected ${KEY_LEN}`);
|
|
49
|
-
}
|
|
50
|
-
cached = buf;
|
|
51
|
-
return buf;
|
|
52
|
-
}
|
|
53
|
-
|
|
54
|
-
fs.mkdirSync(KEY_DIR, { recursive: true, mode: 0o700 });
|
|
55
|
-
const key = crypto.randomBytes(KEY_LEN);
|
|
56
|
-
fs.writeFileSync(KEY_PATH, key, { mode: 0o600 });
|
|
57
|
-
cached = key;
|
|
58
|
-
return key;
|
|
59
|
-
}
|
|
60
|
-
|
|
61
|
-
/** Test-only: clear the cached key so a different one can be loaded. */
|
|
62
|
-
export function _resetMasterKeyCache(): void {
|
|
63
|
-
cached = null;
|
|
64
|
-
}
|
|
65
|
-
|
|
66
|
-
/** Test-only: install a key without touching disk. */
|
|
67
|
-
export function _setMasterKeyForTest(key: Buffer): void {
|
|
68
|
-
if (key.length !== KEY_LEN) throw new Error(`test key must be ${KEY_LEN} bytes`);
|
|
69
|
-
cached = key;
|
|
70
|
-
}
|