@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
package/src/container-runner.ts
DELETED
|
@@ -1,576 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* Container Runner v2
|
|
3
|
-
* Spawns agent containers with session folder + agent group folder mounts.
|
|
4
|
-
* The container runs the v2 agent-runner which polls the session DB.
|
|
5
|
-
*/
|
|
6
|
-
import { ChildProcess, execSync, spawn } from 'child_process';
|
|
7
|
-
import fs from 'fs';
|
|
8
|
-
import path from 'path';
|
|
9
|
-
|
|
10
|
-
import {
|
|
11
|
-
CONTAINER_IMAGE,
|
|
12
|
-
CONTAINER_IMAGE_BASE,
|
|
13
|
-
CONTAINER_INSTALL_LABEL,
|
|
14
|
-
DATA_DIR,
|
|
15
|
-
GROUPS_DIR,
|
|
16
|
-
TIMEZONE,
|
|
17
|
-
} from './config.js';
|
|
18
|
-
import { readContainerConfig, writeContainerConfig } from './container-config.js';
|
|
19
|
-
import { CONTAINER_RUNTIME_BIN, hostGatewayArgs, readonlyMountArgs, stopContainer } from './container-runtime.js';
|
|
20
|
-
import { rewriteMcpUrlsForContainer } from './parachute/vault-mcp.js';
|
|
21
|
-
import { composeGroupClaudeMd } from './claude-md-compose.js';
|
|
22
|
-
import { getAgentGroup } from './db/agent-groups.js';
|
|
23
|
-
import { getDb, hasTable } from './db/connection.js';
|
|
24
|
-
import { initGroupFilesystem } from './group-init.js';
|
|
25
|
-
import { stopTypingRefresh } from './modules/typing/index.js';
|
|
26
|
-
import { log } from './log.js';
|
|
27
|
-
import { resolveInjectableSecrets } from './secrets/index.js';
|
|
28
|
-
import { validateAdditionalMounts } from './modules/mount-security/index.js';
|
|
29
|
-
// Provider host-side config barrel — each provider that needs host-side
|
|
30
|
-
// container setup self-registers on import.
|
|
31
|
-
import './providers/index.js';
|
|
32
|
-
import {
|
|
33
|
-
getProviderContainerConfig,
|
|
34
|
-
type ProviderContainerContribution,
|
|
35
|
-
type VolumeMount,
|
|
36
|
-
} from './providers/provider-container-registry.js';
|
|
37
|
-
import { getProviderCredentialsForSpawn, type ProviderSpawnEnvelope } from './modules/provider-credentials/index.js';
|
|
38
|
-
import {
|
|
39
|
-
heartbeatPath,
|
|
40
|
-
markContainerRunning,
|
|
41
|
-
markContainerStopped,
|
|
42
|
-
sessionDir,
|
|
43
|
-
writeSessionRouting,
|
|
44
|
-
} from './session-manager.js';
|
|
45
|
-
import type { AgentGroup, Session } from './types.js';
|
|
46
|
-
|
|
47
|
-
/** Active containers tracked by session ID. */
|
|
48
|
-
const activeContainers = new Map<string, { process: ChildProcess; containerName: string }>();
|
|
49
|
-
|
|
50
|
-
/**
|
|
51
|
-
* In-flight wake promises, keyed by session id. Deduplicates concurrent
|
|
52
|
-
* `wakeContainer` calls while the first spawn is still mid-setup (async
|
|
53
|
-
* buildContainerArgs, secret resolution, etc.) — otherwise a second wake
|
|
54
|
-
* in that window passes the `activeContainers.has` check and spawns a
|
|
55
|
-
* duplicate container against the same session directory, producing racy
|
|
56
|
-
* double-replies.
|
|
57
|
-
*/
|
|
58
|
-
const wakePromises = new Map<string, Promise<void>>();
|
|
59
|
-
|
|
60
|
-
export function getActiveContainerCount(): number {
|
|
61
|
-
return activeContainers.size;
|
|
62
|
-
}
|
|
63
|
-
|
|
64
|
-
export function isContainerRunning(sessionId: string): boolean {
|
|
65
|
-
return activeContainers.has(sessionId);
|
|
66
|
-
}
|
|
67
|
-
|
|
68
|
-
/**
|
|
69
|
-
* Wake up a container for a session. If already running or mid-spawn, no-op
|
|
70
|
-
* (the in-flight wake promise is reused).
|
|
71
|
-
*
|
|
72
|
-
* The container runs the v2 agent-runner which polls the session DB.
|
|
73
|
-
*/
|
|
74
|
-
export function wakeContainer(session: Session): Promise<void> {
|
|
75
|
-
if (activeContainers.has(session.id)) {
|
|
76
|
-
log.debug('Container already running', { sessionId: session.id });
|
|
77
|
-
return Promise.resolve();
|
|
78
|
-
}
|
|
79
|
-
const existing = wakePromises.get(session.id);
|
|
80
|
-
if (existing) {
|
|
81
|
-
log.debug('Container wake already in-flight — joining existing promise', { sessionId: session.id });
|
|
82
|
-
return existing;
|
|
83
|
-
}
|
|
84
|
-
const promise = spawnContainer(session).finally(() => {
|
|
85
|
-
wakePromises.delete(session.id);
|
|
86
|
-
});
|
|
87
|
-
wakePromises.set(session.id, promise);
|
|
88
|
-
return promise;
|
|
89
|
-
}
|
|
90
|
-
|
|
91
|
-
async function spawnContainer(session: Session): Promise<void> {
|
|
92
|
-
const agentGroup = getAgentGroup(session.agent_group_id);
|
|
93
|
-
if (!agentGroup) {
|
|
94
|
-
log.error('Agent group not found', { agentGroupId: session.agent_group_id });
|
|
95
|
-
return;
|
|
96
|
-
}
|
|
97
|
-
|
|
98
|
-
// Refresh the destination map and default reply routing so any admin
|
|
99
|
-
// changes take effect on wake. Destinations come from the agent-to-agent
|
|
100
|
-
// module — skip when the module isn't installed (table absent).
|
|
101
|
-
if (hasTable(getDb(), 'agent_destinations')) {
|
|
102
|
-
const { writeDestinations } = await import('./modules/agent-to-agent/write-destinations.js');
|
|
103
|
-
writeDestinations(agentGroup.id, session.id);
|
|
104
|
-
}
|
|
105
|
-
writeSessionRouting(agentGroup.id, session.id);
|
|
106
|
-
|
|
107
|
-
// Read container config once — threaded through provider resolution,
|
|
108
|
-
// buildMounts, and buildContainerArgs so we don't re-read the file.
|
|
109
|
-
const containerConfig = readContainerConfig(agentGroup.folder);
|
|
110
|
-
|
|
111
|
-
// Ensure container.json has the agent group identity fields the runner needs.
|
|
112
|
-
// Written at spawn time so the runner can read them from the RO mount.
|
|
113
|
-
ensureRuntimeFields(containerConfig, agentGroup);
|
|
114
|
-
|
|
115
|
-
// Resolve the effective provider + any host-side contribution it declares
|
|
116
|
-
// (extra mounts, env passthrough). Computed once and threaded through both
|
|
117
|
-
// buildMounts and buildContainerArgs so side effects (mkdir, etc.) fire once.
|
|
118
|
-
const { provider, contribution } = resolveProviderContribution(session, agentGroup, containerConfig);
|
|
119
|
-
|
|
120
|
-
// Resolve the chosen agent-provider credential source for this spawn
|
|
121
|
-
// (paraclaw#78). Two-tier lookup (paraclaw#86): per-agent-group row
|
|
122
|
-
// wins over the install-wide default sentinel. Side effects (host file
|
|
123
|
-
// re-read, fallback warnings) happen here once per spawn.
|
|
124
|
-
const credentialsEnvelope = getProviderCredentialsForSpawn(agentGroup.id);
|
|
125
|
-
|
|
126
|
-
const mounts = buildMounts(agentGroup, session, containerConfig, contribution, credentialsEnvelope);
|
|
127
|
-
const containerName = `parachute-agent-${agentGroup.folder}-${Date.now()}`;
|
|
128
|
-
const args = await buildContainerArgs(
|
|
129
|
-
mounts,
|
|
130
|
-
containerName,
|
|
131
|
-
agentGroup,
|
|
132
|
-
containerConfig,
|
|
133
|
-
provider,
|
|
134
|
-
contribution,
|
|
135
|
-
credentialsEnvelope,
|
|
136
|
-
);
|
|
137
|
-
|
|
138
|
-
log.info('Spawning container', { sessionId: session.id, agentGroup: agentGroup.name, containerName });
|
|
139
|
-
|
|
140
|
-
// Clear any orphan heartbeat from a previous container instance — the
|
|
141
|
-
// sweep's ceiling check treats a missing file as "fresh spawn, give grace"
|
|
142
|
-
// (host-sweep.ts line 87). Without this, the stale mtime can trigger an
|
|
143
|
-
// immediate kill before the new container touches the file itself.
|
|
144
|
-
fs.rmSync(heartbeatPath(agentGroup.id, session.id), { force: true });
|
|
145
|
-
|
|
146
|
-
const container = spawn(CONTAINER_RUNTIME_BIN, args, { stdio: ['ignore', 'pipe', 'pipe'] });
|
|
147
|
-
|
|
148
|
-
activeContainers.set(session.id, { process: container, containerName });
|
|
149
|
-
markContainerRunning(session.id);
|
|
150
|
-
|
|
151
|
-
// Log stderr
|
|
152
|
-
container.stderr?.on('data', (data) => {
|
|
153
|
-
for (const line of data.toString().trim().split('\n')) {
|
|
154
|
-
if (line) log.debug(line, { container: agentGroup.folder });
|
|
155
|
-
}
|
|
156
|
-
});
|
|
157
|
-
|
|
158
|
-
// stdout is unused in v2 (all IO is via session DB)
|
|
159
|
-
container.stdout?.on('data', () => {});
|
|
160
|
-
|
|
161
|
-
// No host-side idle timeout. Stale/stuck detection is driven by the host
|
|
162
|
-
// sweep reading heartbeat mtime + processing_ack claim age + container_state
|
|
163
|
-
// (see src/host-sweep.ts). This avoids killing long-running legitimate work
|
|
164
|
-
// on a wall-clock timer.
|
|
165
|
-
|
|
166
|
-
container.on('close', (code) => {
|
|
167
|
-
activeContainers.delete(session.id);
|
|
168
|
-
markContainerStopped(session.id);
|
|
169
|
-
stopTypingRefresh(session.id);
|
|
170
|
-
log.info('Container exited', { sessionId: session.id, code, containerName });
|
|
171
|
-
});
|
|
172
|
-
|
|
173
|
-
container.on('error', (err) => {
|
|
174
|
-
activeContainers.delete(session.id);
|
|
175
|
-
markContainerStopped(session.id);
|
|
176
|
-
stopTypingRefresh(session.id);
|
|
177
|
-
log.error('Container spawn error', { sessionId: session.id, err });
|
|
178
|
-
});
|
|
179
|
-
}
|
|
180
|
-
|
|
181
|
-
/** Kill a container for a session. */
|
|
182
|
-
export function killContainer(sessionId: string, reason: string): void {
|
|
183
|
-
const entry = activeContainers.get(sessionId);
|
|
184
|
-
if (!entry) return;
|
|
185
|
-
|
|
186
|
-
log.info('Killing container', { sessionId, reason, containerName: entry.containerName });
|
|
187
|
-
try {
|
|
188
|
-
stopContainer(entry.containerName);
|
|
189
|
-
} catch {
|
|
190
|
-
entry.process.kill('SIGKILL');
|
|
191
|
-
}
|
|
192
|
-
}
|
|
193
|
-
|
|
194
|
-
/**
|
|
195
|
-
* Resolve the provider name for a session using the precedence documented in
|
|
196
|
-
* the provider-install skills:
|
|
197
|
-
*
|
|
198
|
-
* sessions.agent_provider
|
|
199
|
-
* → agent_groups.agent_provider
|
|
200
|
-
* → container.json `provider`
|
|
201
|
-
* → 'claude'
|
|
202
|
-
*
|
|
203
|
-
* Pure so the precedence can be unit-tested without a DB or filesystem.
|
|
204
|
-
*/
|
|
205
|
-
export function resolveProviderName(
|
|
206
|
-
sessionProvider: string | null | undefined,
|
|
207
|
-
agentGroupProvider: string | null | undefined,
|
|
208
|
-
containerConfigProvider: string | null | undefined,
|
|
209
|
-
): string {
|
|
210
|
-
return (sessionProvider || agentGroupProvider || containerConfigProvider || 'claude').toLowerCase();
|
|
211
|
-
}
|
|
212
|
-
|
|
213
|
-
function resolveProviderContribution(
|
|
214
|
-
session: Session,
|
|
215
|
-
agentGroup: AgentGroup,
|
|
216
|
-
containerConfig: import('./container-config.js').ContainerConfig,
|
|
217
|
-
): { provider: string; contribution: ProviderContainerContribution } {
|
|
218
|
-
const provider = resolveProviderName(session.agent_provider, agentGroup.agent_provider, containerConfig.provider);
|
|
219
|
-
const fn = getProviderContainerConfig(provider);
|
|
220
|
-
const contribution = fn
|
|
221
|
-
? fn({
|
|
222
|
-
sessionDir: sessionDir(agentGroup.id, session.id),
|
|
223
|
-
agentGroupId: agentGroup.id,
|
|
224
|
-
hostEnv: process.env,
|
|
225
|
-
})
|
|
226
|
-
: {};
|
|
227
|
-
return { provider, contribution };
|
|
228
|
-
}
|
|
229
|
-
|
|
230
|
-
function buildMounts(
|
|
231
|
-
agentGroup: AgentGroup,
|
|
232
|
-
session: Session,
|
|
233
|
-
containerConfig: import('./container-config.js').ContainerConfig,
|
|
234
|
-
providerContribution: ProviderContainerContribution,
|
|
235
|
-
credentialsEnvelope: ProviderSpawnEnvelope,
|
|
236
|
-
): VolumeMount[] {
|
|
237
|
-
const projectRoot = process.cwd();
|
|
238
|
-
|
|
239
|
-
// Per-group filesystem state lives forever after first creation. Init is
|
|
240
|
-
// idempotent: it only writes paths that don't already exist, so this call
|
|
241
|
-
// is a no-op for groups that have spawned before.
|
|
242
|
-
initGroupFilesystem(agentGroup);
|
|
243
|
-
|
|
244
|
-
// Sync skill symlinks based on container.json selection before mounting.
|
|
245
|
-
const claudeDir = path.join(DATA_DIR, 'sessions', agentGroup.id, '.claude-shared');
|
|
246
|
-
syncSkillSymlinks(claudeDir, containerConfig);
|
|
247
|
-
|
|
248
|
-
// Compose CLAUDE.md fresh every spawn from the shared base, enabled skill
|
|
249
|
-
// fragments, and MCP server instructions. See `claude-md-compose.ts`.
|
|
250
|
-
composeGroupClaudeMd(agentGroup);
|
|
251
|
-
|
|
252
|
-
const mounts: VolumeMount[] = [];
|
|
253
|
-
const sessDir = sessionDir(agentGroup.id, session.id);
|
|
254
|
-
const groupDir = path.resolve(GROUPS_DIR, agentGroup.folder);
|
|
255
|
-
|
|
256
|
-
// Session folder at /workspace (contains inbound.db, outbound.db, outbox/, .claude/)
|
|
257
|
-
mounts.push({ hostPath: sessDir, containerPath: '/workspace', readonly: false });
|
|
258
|
-
|
|
259
|
-
// Agent group folder at /workspace/agent (RW for working files + CLAUDE.local.md)
|
|
260
|
-
mounts.push({ hostPath: groupDir, containerPath: '/workspace/agent', readonly: false });
|
|
261
|
-
|
|
262
|
-
// container.json — nested RO mount on top of RW group dir so the agent
|
|
263
|
-
// can read its config but cannot modify it.
|
|
264
|
-
//
|
|
265
|
-
// We don't mount the on-disk file directly: we write a per-spawn copy
|
|
266
|
-
// to the session dir with HTTP MCP URLs translated from loopback
|
|
267
|
-
// (`127.0.0.1` / `localhost`) to `host.docker.internal`. The on-disk
|
|
268
|
-
// file keeps the operator-facing URL (what the UI displays, what they
|
|
269
|
-
// typed); the container sees a derived copy that's actually reachable
|
|
270
|
-
// from inside Docker. See `rewriteMcpUrlsForContainer` in
|
|
271
|
-
// src/parachute/vault-mcp.ts for the rewrite rules.
|
|
272
|
-
const containerJsonPath = path.join(groupDir, 'container.json');
|
|
273
|
-
if (fs.existsSync(containerJsonPath)) {
|
|
274
|
-
const spawnConfig = rewriteMcpUrlsForContainer(containerConfig);
|
|
275
|
-
const spawnConfigPath = path.join(sessDir, '.spawn-container.json');
|
|
276
|
-
fs.writeFileSync(spawnConfigPath, JSON.stringify(spawnConfig, null, 2) + '\n');
|
|
277
|
-
mounts.push({ hostPath: spawnConfigPath, containerPath: '/workspace/agent/container.json', readonly: true });
|
|
278
|
-
}
|
|
279
|
-
|
|
280
|
-
// Composer-managed CLAUDE.md artifacts — nested RO mounts. These are
|
|
281
|
-
// regenerated from the shared base + fragments on every spawn; any
|
|
282
|
-
// agent-side writes would be clobbered, so enforce read-only. Only
|
|
283
|
-
// CLAUDE.local.md (per-group memory) remains RW via the group-dir mount.
|
|
284
|
-
// `.claude-shared.md` is a symlink whose target (`/app/CLAUDE.md`) is
|
|
285
|
-
// already RO-mounted, so writes through it fail regardless — no need for
|
|
286
|
-
// a nested mount there.
|
|
287
|
-
const composedClaudeMd = path.join(groupDir, 'CLAUDE.md');
|
|
288
|
-
if (fs.existsSync(composedClaudeMd)) {
|
|
289
|
-
mounts.push({ hostPath: composedClaudeMd, containerPath: '/workspace/agent/CLAUDE.md', readonly: true });
|
|
290
|
-
}
|
|
291
|
-
const fragmentsDir = path.join(groupDir, '.claude-fragments');
|
|
292
|
-
if (fs.existsSync(fragmentsDir)) {
|
|
293
|
-
mounts.push({ hostPath: fragmentsDir, containerPath: '/workspace/agent/.claude-fragments', readonly: true });
|
|
294
|
-
}
|
|
295
|
-
|
|
296
|
-
// Global memory directory — always read-only.
|
|
297
|
-
const globalDir = path.join(GROUPS_DIR, 'global');
|
|
298
|
-
if (fs.existsSync(globalDir)) {
|
|
299
|
-
mounts.push({ hostPath: globalDir, containerPath: '/workspace/global', readonly: true });
|
|
300
|
-
}
|
|
301
|
-
|
|
302
|
-
// Shared CLAUDE.md — read-only, imported by the composed entry point via
|
|
303
|
-
// the `.claude-shared.md` symlink inside the group dir.
|
|
304
|
-
const sharedClaudeMd = path.join(process.cwd(), 'container', 'CLAUDE.md');
|
|
305
|
-
if (fs.existsSync(sharedClaudeMd)) {
|
|
306
|
-
mounts.push({ hostPath: sharedClaudeMd, containerPath: '/app/CLAUDE.md', readonly: true });
|
|
307
|
-
}
|
|
308
|
-
|
|
309
|
-
// Lay down provider-credentials files (paraclaw#78). All current sources
|
|
310
|
-
// (claude_setup_token, anthropic_api_key, external_server) inject via env
|
|
311
|
-
// vars — `files` is empty in practice today and reserved for future
|
|
312
|
-
// sources that need on-disk credential material. Files land under
|
|
313
|
-
// `claudeDir` (= /home/node/.claude inside the container). Write before
|
|
314
|
-
// the claudeDir mount is registered so the file is visible on first read.
|
|
315
|
-
for (const [filename, contents] of Object.entries(credentialsEnvelope.files)) {
|
|
316
|
-
if (!fs.existsSync(claudeDir)) fs.mkdirSync(claudeDir, { recursive: true });
|
|
317
|
-
const target = path.join(claudeDir, filename);
|
|
318
|
-
fs.writeFileSync(target, contents, { mode: 0o600 });
|
|
319
|
-
}
|
|
320
|
-
|
|
321
|
-
// Per-group .claude-shared at /home/node/.claude (Claude state, settings,
|
|
322
|
-
// skill symlinks)
|
|
323
|
-
mounts.push({ hostPath: claudeDir, containerPath: '/home/node/.claude', readonly: false });
|
|
324
|
-
|
|
325
|
-
// Shared agent-runner source — read-only, same code for all groups.
|
|
326
|
-
const agentRunnerSrc = path.join(projectRoot, 'container', 'agent-runner', 'src');
|
|
327
|
-
mounts.push({ hostPath: agentRunnerSrc, containerPath: '/app/src', readonly: true });
|
|
328
|
-
|
|
329
|
-
// Shared skills — read-only, symlinks in .claude-shared/skills/ point here.
|
|
330
|
-
const skillsSrc = path.join(projectRoot, 'container', 'skills');
|
|
331
|
-
if (fs.existsSync(skillsSrc)) {
|
|
332
|
-
mounts.push({ hostPath: skillsSrc, containerPath: '/app/skills', readonly: true });
|
|
333
|
-
}
|
|
334
|
-
|
|
335
|
-
// Additional mounts from container config
|
|
336
|
-
if (containerConfig.additionalMounts && containerConfig.additionalMounts.length > 0) {
|
|
337
|
-
const validated = validateAdditionalMounts(containerConfig.additionalMounts, agentGroup.name);
|
|
338
|
-
mounts.push(...validated);
|
|
339
|
-
}
|
|
340
|
-
|
|
341
|
-
// Provider-contributed mounts (e.g. opencode-xdg)
|
|
342
|
-
if (providerContribution.mounts) {
|
|
343
|
-
mounts.push(...providerContribution.mounts);
|
|
344
|
-
}
|
|
345
|
-
|
|
346
|
-
return mounts;
|
|
347
|
-
}
|
|
348
|
-
|
|
349
|
-
/**
|
|
350
|
-
* Sync skill symlinks in .claude-shared/skills/ to match the container.json
|
|
351
|
-
* selection. Each symlink points to a container path (/app/skills/<name>)
|
|
352
|
-
* so it's dangling on the host but valid inside the container.
|
|
353
|
-
*/
|
|
354
|
-
function syncSkillSymlinks(claudeDir: string, containerConfig: import('./container-config.js').ContainerConfig): void {
|
|
355
|
-
const skillsDir = path.join(claudeDir, 'skills');
|
|
356
|
-
if (!fs.existsSync(skillsDir)) {
|
|
357
|
-
fs.mkdirSync(skillsDir, { recursive: true });
|
|
358
|
-
}
|
|
359
|
-
|
|
360
|
-
// Determine desired skill set
|
|
361
|
-
const projectRoot = process.cwd();
|
|
362
|
-
const sharedSkillsDir = path.join(projectRoot, 'container', 'skills');
|
|
363
|
-
let desired: string[];
|
|
364
|
-
if (containerConfig.skills === 'all') {
|
|
365
|
-
// Recompute from shared dir — newly-added upstream skills appear automatically
|
|
366
|
-
desired = fs.existsSync(sharedSkillsDir)
|
|
367
|
-
? fs.readdirSync(sharedSkillsDir).filter((e) => {
|
|
368
|
-
try {
|
|
369
|
-
return fs.statSync(path.join(sharedSkillsDir, e)).isDirectory();
|
|
370
|
-
} catch {
|
|
371
|
-
return false;
|
|
372
|
-
}
|
|
373
|
-
})
|
|
374
|
-
: [];
|
|
375
|
-
} else {
|
|
376
|
-
desired = containerConfig.skills;
|
|
377
|
-
}
|
|
378
|
-
|
|
379
|
-
const desiredSet = new Set(desired);
|
|
380
|
-
|
|
381
|
-
// Remove symlinks not in the desired set
|
|
382
|
-
for (const entry of fs.readdirSync(skillsDir)) {
|
|
383
|
-
const entryPath = path.join(skillsDir, entry);
|
|
384
|
-
let isSymlink = false;
|
|
385
|
-
try {
|
|
386
|
-
isSymlink = fs.lstatSync(entryPath).isSymbolicLink();
|
|
387
|
-
} catch {
|
|
388
|
-
continue;
|
|
389
|
-
}
|
|
390
|
-
if (isSymlink && !desiredSet.has(entry)) {
|
|
391
|
-
fs.unlinkSync(entryPath);
|
|
392
|
-
}
|
|
393
|
-
}
|
|
394
|
-
|
|
395
|
-
// Create symlinks for desired skills (container path targets)
|
|
396
|
-
for (const skill of desired) {
|
|
397
|
-
const linkPath = path.join(skillsDir, skill);
|
|
398
|
-
let exists = false;
|
|
399
|
-
try {
|
|
400
|
-
fs.lstatSync(linkPath);
|
|
401
|
-
exists = true;
|
|
402
|
-
} catch {
|
|
403
|
-
/* missing */
|
|
404
|
-
}
|
|
405
|
-
if (!exists) {
|
|
406
|
-
fs.symlinkSync(`/app/skills/${skill}`, linkPath);
|
|
407
|
-
}
|
|
408
|
-
}
|
|
409
|
-
}
|
|
410
|
-
|
|
411
|
-
/**
|
|
412
|
-
* Ensure container.json has the runtime identity fields the runner needs.
|
|
413
|
-
* Written at spawn time so they're always current even if the DB values
|
|
414
|
-
* change (e.g. group rename). Only writes if values differ to avoid
|
|
415
|
-
* unnecessary file churn.
|
|
416
|
-
*/
|
|
417
|
-
function ensureRuntimeFields(
|
|
418
|
-
containerConfig: import('./container-config.js').ContainerConfig,
|
|
419
|
-
agentGroup: AgentGroup,
|
|
420
|
-
): void {
|
|
421
|
-
let dirty = false;
|
|
422
|
-
if (containerConfig.agentGroupId !== agentGroup.id) {
|
|
423
|
-
containerConfig.agentGroupId = agentGroup.id;
|
|
424
|
-
dirty = true;
|
|
425
|
-
}
|
|
426
|
-
if (containerConfig.groupName !== agentGroup.name) {
|
|
427
|
-
containerConfig.groupName = agentGroup.name;
|
|
428
|
-
dirty = true;
|
|
429
|
-
}
|
|
430
|
-
if (containerConfig.assistantName !== agentGroup.name) {
|
|
431
|
-
containerConfig.assistantName = agentGroup.name;
|
|
432
|
-
dirty = true;
|
|
433
|
-
}
|
|
434
|
-
if (dirty) {
|
|
435
|
-
writeContainerConfig(agentGroup.folder, containerConfig);
|
|
436
|
-
}
|
|
437
|
-
}
|
|
438
|
-
|
|
439
|
-
async function buildContainerArgs(
|
|
440
|
-
mounts: VolumeMount[],
|
|
441
|
-
containerName: string,
|
|
442
|
-
agentGroup: AgentGroup,
|
|
443
|
-
containerConfig: import('./container-config.js').ContainerConfig,
|
|
444
|
-
provider: string,
|
|
445
|
-
providerContribution: ProviderContainerContribution,
|
|
446
|
-
credentialsEnvelope: ProviderSpawnEnvelope,
|
|
447
|
-
): Promise<string[]> {
|
|
448
|
-
const args: string[] = ['run', '--rm', '--name', containerName, '--label', CONTAINER_INSTALL_LABEL];
|
|
449
|
-
|
|
450
|
-
// Environment — only vars read by code we don't own.
|
|
451
|
-
// Everything host-specific is in container.json (read by runner at startup).
|
|
452
|
-
args.push('-e', `TZ=${TIMEZONE}`);
|
|
453
|
-
|
|
454
|
-
// Provider-contributed env vars (e.g. XDG_DATA_HOME, OPENCODE_*, NO_PROXY).
|
|
455
|
-
if (providerContribution.env) {
|
|
456
|
-
for (const [key, value] of Object.entries(providerContribution.env)) {
|
|
457
|
-
args.push('-e', `${key}=${value}`);
|
|
458
|
-
}
|
|
459
|
-
}
|
|
460
|
-
|
|
461
|
-
// Paraclaw secret injection. Mode is per-recipient agent group
|
|
462
|
-
// (`agent_groups.secret_mode`): `all` injects every in-scope secret,
|
|
463
|
-
// `selective` only those with an explicit assignment row. Agent-scoped
|
|
464
|
-
// beats global on name collision (handled inside resolveInjectableSecrets).
|
|
465
|
-
// Plaintext lives in container env only; never logged here, never
|
|
466
|
-
// written to chat context.
|
|
467
|
-
//
|
|
468
|
-
// suppressSecretEnvKeys (paraclaw#78): when the operator's chosen
|
|
469
|
-
// agent-provider source is claude_setup_token, ANTHROPIC_API_KEY /
|
|
470
|
-
// ANTHROPIC_AUTH_TOKEN secrets get dropped — Claude Code SDK prefers
|
|
471
|
-
// those over CLAUDE_CODE_OAUTH_TOKEN, so leaving them in would silently
|
|
472
|
-
// override the chosen source.
|
|
473
|
-
try {
|
|
474
|
-
const secrets = resolveInjectableSecrets(agentGroup.id);
|
|
475
|
-
let suppressed = 0;
|
|
476
|
-
for (const [name, value] of secrets) {
|
|
477
|
-
if (credentialsEnvelope.suppressSecretEnvKeys.has(name)) {
|
|
478
|
-
suppressed++;
|
|
479
|
-
continue;
|
|
480
|
-
}
|
|
481
|
-
args.push('-e', `${name}=${value}`);
|
|
482
|
-
}
|
|
483
|
-
log.info('host secrets injected', { containerName, count: secrets.size - suppressed, suppressed });
|
|
484
|
-
} catch (err) {
|
|
485
|
-
log.warn('host secret injection failed — container will have no credentials', { containerName, err });
|
|
486
|
-
}
|
|
487
|
-
|
|
488
|
-
// Provider-credentials env (paraclaw#78). Pushed AFTER the secrets bag
|
|
489
|
-
// so an explicit operator choice is the last `-e` Docker sees on a
|
|
490
|
-
// given key.
|
|
491
|
-
for (const [name, value] of Object.entries(credentialsEnvelope.env)) {
|
|
492
|
-
args.push('-e', `${name}=${value}`);
|
|
493
|
-
}
|
|
494
|
-
|
|
495
|
-
// Host gateway
|
|
496
|
-
args.push(...hostGatewayArgs());
|
|
497
|
-
|
|
498
|
-
// User mapping
|
|
499
|
-
const hostUid = process.getuid?.();
|
|
500
|
-
const hostGid = process.getgid?.();
|
|
501
|
-
if (hostUid != null && hostUid !== 0 && hostUid !== 1000) {
|
|
502
|
-
args.push('--user', `${hostUid}:${hostGid}`);
|
|
503
|
-
args.push('-e', 'HOME=/home/node');
|
|
504
|
-
}
|
|
505
|
-
|
|
506
|
-
// Volume mounts
|
|
507
|
-
for (const mount of mounts) {
|
|
508
|
-
if (mount.readonly) {
|
|
509
|
-
args.push(...readonlyMountArgs(mount.hostPath, mount.containerPath));
|
|
510
|
-
} else {
|
|
511
|
-
args.push('-v', `${mount.hostPath}:${mount.containerPath}`);
|
|
512
|
-
}
|
|
513
|
-
}
|
|
514
|
-
|
|
515
|
-
// Override entrypoint: run v2 entry point directly via Bun (no tsc, no stdin).
|
|
516
|
-
args.push('--entrypoint', 'bash');
|
|
517
|
-
|
|
518
|
-
// Use per-agent-group image if one has been built, otherwise base image
|
|
519
|
-
const imageTag = containerConfig.imageTag || CONTAINER_IMAGE;
|
|
520
|
-
args.push(imageTag);
|
|
521
|
-
|
|
522
|
-
args.push('-c', 'exec bun run /app/src/index.ts');
|
|
523
|
-
|
|
524
|
-
return args;
|
|
525
|
-
}
|
|
526
|
-
|
|
527
|
-
/** Build a per-agent-group Docker image with custom packages. */
|
|
528
|
-
export async function buildAgentGroupImage(agentGroupId: string): Promise<void> {
|
|
529
|
-
const agentGroup = getAgentGroup(agentGroupId);
|
|
530
|
-
if (!agentGroup) throw new Error('Agent group not found');
|
|
531
|
-
|
|
532
|
-
const containerConfig = readContainerConfig(agentGroup.folder);
|
|
533
|
-
const aptPackages = containerConfig.packages.apt;
|
|
534
|
-
const npmPackages = containerConfig.packages.npm;
|
|
535
|
-
|
|
536
|
-
if (aptPackages.length === 0 && npmPackages.length === 0) {
|
|
537
|
-
throw new Error('No packages to install. Use install_packages first.');
|
|
538
|
-
}
|
|
539
|
-
|
|
540
|
-
let dockerfile = `FROM ${CONTAINER_IMAGE}\nUSER root\n`;
|
|
541
|
-
if (aptPackages.length > 0) {
|
|
542
|
-
dockerfile += `RUN apt-get update && apt-get install -y ${aptPackages.join(' ')} && rm -rf /var/lib/apt/lists/*\n`;
|
|
543
|
-
}
|
|
544
|
-
if (npmPackages.length > 0) {
|
|
545
|
-
// pnpm skips build scripts unless packages are allowlisted. Append each
|
|
546
|
-
// to /root/.npmrc (base image sets it up for agent-browser) so packages
|
|
547
|
-
// with postinstall — e.g. playwright, puppeteer, native addons — don't
|
|
548
|
-
// install silently broken.
|
|
549
|
-
const allowlist = npmPackages.map((p) => `echo 'only-built-dependencies[]=${p}' >> /root/.npmrc`).join(' && ');
|
|
550
|
-
dockerfile += `RUN ${allowlist} && pnpm install -g ${npmPackages.join(' ')}\n`;
|
|
551
|
-
}
|
|
552
|
-
dockerfile += 'USER node\n';
|
|
553
|
-
|
|
554
|
-
const imageTag = `${CONTAINER_IMAGE_BASE}:${agentGroupId}`;
|
|
555
|
-
|
|
556
|
-
log.info('Building per-agent-group image', { agentGroupId, imageTag, apt: aptPackages, npm: npmPackages });
|
|
557
|
-
|
|
558
|
-
// Write Dockerfile to temp file and build
|
|
559
|
-
const tmpDockerfile = path.join(DATA_DIR, `Dockerfile.${agentGroupId}`);
|
|
560
|
-
fs.writeFileSync(tmpDockerfile, dockerfile);
|
|
561
|
-
try {
|
|
562
|
-
execSync(`${CONTAINER_RUNTIME_BIN} build -t ${imageTag} -f ${tmpDockerfile} .`, {
|
|
563
|
-
cwd: DATA_DIR,
|
|
564
|
-
stdio: 'pipe',
|
|
565
|
-
timeout: 300_000,
|
|
566
|
-
});
|
|
567
|
-
} finally {
|
|
568
|
-
fs.unlinkSync(tmpDockerfile);
|
|
569
|
-
}
|
|
570
|
-
|
|
571
|
-
// Store the image tag in groups/<folder>/container.json
|
|
572
|
-
containerConfig.imageTag = imageTag;
|
|
573
|
-
writeContainerConfig(agentGroup.folder, containerConfig);
|
|
574
|
-
|
|
575
|
-
log.info('Per-agent-group image built', { agentGroupId, imageTag });
|
|
576
|
-
}
|