@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
|
@@ -0,0 +1,154 @@
|
|
|
1
|
+
import { describe, test, expect } from "bun:test";
|
|
2
|
+
import {
|
|
3
|
+
composeFilesystemView,
|
|
4
|
+
homeTreeDenyRoot,
|
|
5
|
+
sharedMounts,
|
|
6
|
+
} from "./mounts.ts";
|
|
7
|
+
import type { AgentMount, BaseBinds } from "./types.ts";
|
|
8
|
+
|
|
9
|
+
const BASE: BaseBinds = {
|
|
10
|
+
workspace: "/state/sessions/arm",
|
|
11
|
+
runtimeReadOnly: ["/home/op/.claude"],
|
|
12
|
+
};
|
|
13
|
+
|
|
14
|
+
describe("homeTreeDenyRoot — platform nuance", () => {
|
|
15
|
+
test("macOS denies /Users", () => {
|
|
16
|
+
expect(homeTreeDenyRoot("darwin")).toBe("/Users");
|
|
17
|
+
});
|
|
18
|
+
test("Linux denies /home", () => {
|
|
19
|
+
expect(homeTreeDenyRoot("linux")).toBe("/home");
|
|
20
|
+
});
|
|
21
|
+
});
|
|
22
|
+
|
|
23
|
+
describe("composeFilesystemView — scoped reads + write confinement", () => {
|
|
24
|
+
test("with no mounts: reads = workspace + runtime, writes = workspace only", () => {
|
|
25
|
+
const fs = composeFilesystemView(BASE, undefined, "darwin");
|
|
26
|
+
expect(fs.allowRead).toContain("/state/sessions/arm");
|
|
27
|
+
expect(fs.allowRead).toContain("/home/op/.claude");
|
|
28
|
+
expect(fs.allowWrite).toEqual(["/state/sessions/arm"]);
|
|
29
|
+
});
|
|
30
|
+
|
|
31
|
+
test("the home tree is denied for reads (scoped-read policy, §4.5)", () => {
|
|
32
|
+
const fs = composeFilesystemView(BASE, undefined, "darwin");
|
|
33
|
+
expect(fs.denyRead).toContain("/Users");
|
|
34
|
+
});
|
|
35
|
+
|
|
36
|
+
test("the home tree denied is platform-correct on Linux", () => {
|
|
37
|
+
const fs = composeFilesystemView(BASE, undefined, "linux");
|
|
38
|
+
expect(fs.denyRead).toContain("/home");
|
|
39
|
+
});
|
|
40
|
+
|
|
41
|
+
test("an ro mount is readable but NOT writable", () => {
|
|
42
|
+
const mounts: AgentMount[] = [{ hostPath: "/refs/tree", mountPath: "/ref", mode: "ro" }];
|
|
43
|
+
const fs = composeFilesystemView(BASE, mounts, "darwin");
|
|
44
|
+
expect(fs.allowRead).toContain("/refs/tree");
|
|
45
|
+
expect(fs.allowWrite).not.toContain("/refs/tree");
|
|
46
|
+
});
|
|
47
|
+
|
|
48
|
+
test("an rw mount is BOTH readable and writable", () => {
|
|
49
|
+
const mounts: AgentMount[] = [{ hostPath: "/proj/foo", mountPath: "/work/foo", mode: "rw" }];
|
|
50
|
+
const fs = composeFilesystemView(BASE, mounts, "darwin");
|
|
51
|
+
expect(fs.allowRead).toContain("/proj/foo");
|
|
52
|
+
expect(fs.allowWrite).toContain("/proj/foo");
|
|
53
|
+
});
|
|
54
|
+
|
|
55
|
+
test("SECURITY: a path OUTSIDE all binds is not in the read surface (scoped, not broad)", () => {
|
|
56
|
+
const mounts: AgentMount[] = [{ hostPath: "/proj/foo", mountPath: "/work/foo", mode: "rw" }];
|
|
57
|
+
const fs = composeFilesystemView(BASE, mounts, "darwin");
|
|
58
|
+
// The operator's SSH dir / other vaults are NOT re-allowed within the denied home tree.
|
|
59
|
+
expect(fs.allowRead).not.toContain("/Users/op/.ssh");
|
|
60
|
+
expect(fs.allowRead).not.toContain("/Users/op/other-vault");
|
|
61
|
+
// And nothing widened the deny away.
|
|
62
|
+
expect(fs.denyRead).toEqual(["/Users"]);
|
|
63
|
+
});
|
|
64
|
+
|
|
65
|
+
test("SECURITY: writes are confined — only workspace + rw mounts, never an ro mount or arbitrary path", () => {
|
|
66
|
+
const mounts: AgentMount[] = [
|
|
67
|
+
{ hostPath: "/refs/tree", mountPath: "/ref", mode: "ro" },
|
|
68
|
+
{ hostPath: "/proj/foo", mountPath: "/work/foo", mode: "rw" },
|
|
69
|
+
];
|
|
70
|
+
const fs = composeFilesystemView(BASE, mounts, "darwin");
|
|
71
|
+
expect(new Set(fs.allowWrite)).toEqual(new Set(["/state/sessions/arm", "/proj/foo"]));
|
|
72
|
+
});
|
|
73
|
+
|
|
74
|
+
test("the base binds are always present even if the spec declares none", () => {
|
|
75
|
+
const fs = composeFilesystemView(BASE, [], "linux");
|
|
76
|
+
expect(fs.allowRead).toContain("/state/sessions/arm");
|
|
77
|
+
expect(fs.allowRead).toContain("/home/op/.claude");
|
|
78
|
+
expect(fs.allowWrite).toContain("/state/sessions/arm");
|
|
79
|
+
});
|
|
80
|
+
|
|
81
|
+
test("dedupes a mount equal to the workspace", () => {
|
|
82
|
+
const mounts: AgentMount[] = [
|
|
83
|
+
{ hostPath: "/state/sessions/arm", mountPath: "/work", mode: "rw" },
|
|
84
|
+
];
|
|
85
|
+
const fs = composeFilesystemView(BASE, mounts, "darwin");
|
|
86
|
+
expect(fs.allowWrite.filter((p) => p === "/state/sessions/arm")).toHaveLength(1);
|
|
87
|
+
});
|
|
88
|
+
|
|
89
|
+
// The working-directory axis (design 2026-06-16-agent-filesystem-and-sharing.md):
|
|
90
|
+
// the spec's `workspace` (a shared real dir) is bound rw — readable + writable —
|
|
91
|
+
// decoupled from the private home (which stays the per-agent session dir).
|
|
92
|
+
describe("the working dir (workspace) as an rw working-root", () => {
|
|
93
|
+
test("a working dir is BOTH readable and writable (an rw working-root)", () => {
|
|
94
|
+
const fs = composeFilesystemView(BASE, undefined, "darwin", true, "/Users/op/Code/repo");
|
|
95
|
+
expect(fs.allowRead).toContain("/Users/op/Code/repo");
|
|
96
|
+
expect(fs.allowWrite).toContain("/Users/op/Code/repo");
|
|
97
|
+
});
|
|
98
|
+
|
|
99
|
+
test("the PRIVATE home is ALSO writable alongside the working dir (decoupled, both rw)", () => {
|
|
100
|
+
const fs = composeFilesystemView(BASE, undefined, "darwin", true, "/Users/op/Code/repo");
|
|
101
|
+
// The private session dir stays in the write surface (it holds .mcp.json/home/tmp).
|
|
102
|
+
expect(fs.allowWrite).toContain("/state/sessions/arm");
|
|
103
|
+
expect(fs.allowWrite).toContain("/Users/op/Code/repo");
|
|
104
|
+
});
|
|
105
|
+
|
|
106
|
+
test("unset working dir → only the private home is writable (today's behavior)", () => {
|
|
107
|
+
const fs = composeFilesystemView(BASE, undefined, "darwin", true, undefined);
|
|
108
|
+
expect(fs.allowWrite).toEqual(["/state/sessions/arm"]);
|
|
109
|
+
});
|
|
110
|
+
|
|
111
|
+
test("a blank working dir is ignored (treated as unset)", () => {
|
|
112
|
+
const fs = composeFilesystemView(BASE, undefined, "darwin", true, "");
|
|
113
|
+
expect(fs.allowWrite).toEqual(["/state/sessions/arm"]);
|
|
114
|
+
});
|
|
115
|
+
|
|
116
|
+
test("under filesystem 'full' (broad reads) the working dir is still in the write surface", () => {
|
|
117
|
+
const fs = composeFilesystemView(BASE, undefined, "darwin", false, "/Users/op/Code/repo");
|
|
118
|
+
expect(fs.denyRead).toEqual([]); // broad reads — no home-tree deny
|
|
119
|
+
expect(fs.allowWrite).toContain("/Users/op/Code/repo");
|
|
120
|
+
expect(fs.allowWrite).toContain("/state/sessions/arm");
|
|
121
|
+
});
|
|
122
|
+
|
|
123
|
+
test("a working dir equal to a declared mount dedupes in the write surface", () => {
|
|
124
|
+
const mounts: AgentMount[] = [{ hostPath: "/Users/op/Code/repo", mountPath: "/repo", mode: "rw" }];
|
|
125
|
+
const fs = composeFilesystemView(BASE, mounts, "darwin", true, "/Users/op/Code/repo");
|
|
126
|
+
expect(fs.allowWrite.filter((p) => p === "/Users/op/Code/repo")).toHaveLength(1);
|
|
127
|
+
});
|
|
128
|
+
});
|
|
129
|
+
});
|
|
130
|
+
|
|
131
|
+
describe("sharedMounts — the named cross-session relaxation", () => {
|
|
132
|
+
test("surfaces only mounts carrying a non-empty `shared` tag", () => {
|
|
133
|
+
const mounts: AgentMount[] = [
|
|
134
|
+
{ hostPath: "/a", mountPath: "/a", mode: "ro" },
|
|
135
|
+
{ hostPath: "/cache", mountPath: "/cache", mode: "ro", shared: "build-cache" },
|
|
136
|
+
{ hostPath: "/b", mountPath: "/b", mode: "rw", shared: "" },
|
|
137
|
+
];
|
|
138
|
+
const shared = sharedMounts(mounts);
|
|
139
|
+
expect(shared).toHaveLength(1);
|
|
140
|
+
expect(shared[0]!.shared).toBe("build-cache");
|
|
141
|
+
});
|
|
142
|
+
|
|
143
|
+
test("a shared mount is still bound like any other (honored in the fs view)", () => {
|
|
144
|
+
const mounts: AgentMount[] = [
|
|
145
|
+
{ hostPath: "/cache", mountPath: "/cache", mode: "ro", shared: "build-cache" },
|
|
146
|
+
];
|
|
147
|
+
const fs = composeFilesystemView(BASE, mounts, "darwin");
|
|
148
|
+
expect(fs.allowRead).toContain("/cache");
|
|
149
|
+
});
|
|
150
|
+
|
|
151
|
+
test("empty input → no shared mounts", () => {
|
|
152
|
+
expect(sharedMounts(undefined)).toEqual([]);
|
|
153
|
+
});
|
|
154
|
+
});
|
|
@@ -0,0 +1,133 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Filesystem-view composition: scoped reads + write confinement (design §3.1
|
|
3
|
+
* item 2, §4.5).
|
|
4
|
+
*
|
|
5
|
+
* The contract:
|
|
6
|
+
*
|
|
7
|
+
* - WRITES are confined to the private per-session workspace plus any `rw`
|
|
8
|
+
* mount the spec declares. The sandbox-runtime's write model is allow-only:
|
|
9
|
+
* `allowWrite: [...binds]`, empty = no writes.
|
|
10
|
+
*
|
|
11
|
+
* - READS are scoped to declared binds — a DELIBERATE divergence from
|
|
12
|
+
* Anthropic's broad-read default (design §4.5). The runtime's read model is
|
|
13
|
+
* deny-then-allow: by default everything is readable. To confine reads we
|
|
14
|
+
* DENY the home tree (`/Users` on macOS, `/home` on Linux) and then RE-ALLOW
|
|
15
|
+
* exactly the binds (workspace + runtime/config + declared mounts). System
|
|
16
|
+
* paths (`/usr`, `/lib`, …) stay readable so `claude` + its toolchain run.
|
|
17
|
+
* `allowRead` overrides `denyRead` in the runtime, so the re-allow wins.
|
|
18
|
+
*
|
|
19
|
+
* Platform nuance (verified against the runtime README "Path Syntax"):
|
|
20
|
+
* - macOS: paths support git-style globs.
|
|
21
|
+
* - Linux: literal paths only — no glob matching.
|
|
22
|
+
* We never synthesize globs; we bind the literal host paths the caller passes,
|
|
23
|
+
* which is correct on both platforms. The `platform` arg only selects the
|
|
24
|
+
* home-tree deny root.
|
|
25
|
+
*/
|
|
26
|
+
|
|
27
|
+
import type { AgentMount, BaseBinds, SandboxPlatform } from "./types.ts";
|
|
28
|
+
|
|
29
|
+
/** The home-tree root denied for scoped reads, per platform. */
|
|
30
|
+
export function homeTreeDenyRoot(platform: SandboxPlatform): string {
|
|
31
|
+
return platform === "darwin" ? "/Users" : "/home";
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
export interface FilesystemView {
|
|
35
|
+
/** Paths to deny reads under (the home tree) — scoped-read enforcement. */
|
|
36
|
+
denyRead: string[];
|
|
37
|
+
/** Paths to re-allow reads within the denied region (the binds). */
|
|
38
|
+
allowRead: string[];
|
|
39
|
+
/** Paths writes are confined to (workspace + rw mounts). */
|
|
40
|
+
allowWrite: string[];
|
|
41
|
+
/** Paths to deny writes within allowed regions. Empty in v1. */
|
|
42
|
+
denyWrite: string[];
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
/**
|
|
46
|
+
* Compose the filesystem view for an arm from the always-present base binds and
|
|
47
|
+
* the spec's additive mounts.
|
|
48
|
+
*
|
|
49
|
+
* Read surface = private home (rw) + the working dir (rw, when set) + runtime/
|
|
50
|
+
* config (ro) + every declared mount (ro and rw alike — you can
|
|
51
|
+
* read what you can write).
|
|
52
|
+
* Write surface = private home (rw) + the working dir (rw, when set) + every
|
|
53
|
+
* `rw` mount.
|
|
54
|
+
*
|
|
55
|
+
* `base.workspace` is the agent's PRIVATE per-session home (always rw — it holds
|
|
56
|
+
* `.mcp.json`/`tmp`/seeded `CLAUDE_CONFIG_DIR`). `workingRoot` is the OPTIONAL
|
|
57
|
+
* shared real dir the agent works from (the `workspace` spec field, design
|
|
58
|
+
* 2026-06-16-agent-filesystem-and-sharing.md): when set it's bound rw + readable,
|
|
59
|
+
* so writes are confined to (private home + working dir + rw mounts). Both are
|
|
60
|
+
* decoupled — the private home is never the shared dir, so `.mcp.json`'s secrets
|
|
61
|
+
* stay per-agent even when the working dir is shared.
|
|
62
|
+
*
|
|
63
|
+
* The home tree is denied and the read surface re-allowed within it, giving the
|
|
64
|
+
* scoped-read policy (§4.5). The base binds are always included regardless of
|
|
65
|
+
* the spec, so a spec cannot strip its own private home or the runtime/config.
|
|
66
|
+
*/
|
|
67
|
+
export function composeFilesystemView(
|
|
68
|
+
base: BaseBinds,
|
|
69
|
+
mounts: readonly AgentMount[] | undefined,
|
|
70
|
+
platform: SandboxPlatform,
|
|
71
|
+
scopedReads = true,
|
|
72
|
+
workingRoot?: string,
|
|
73
|
+
): FilesystemView {
|
|
74
|
+
const declared = mounts ?? [];
|
|
75
|
+
|
|
76
|
+
// The private home + the (optional) working dir are readable AND writable;
|
|
77
|
+
// every other bind is readable, and only rw mounts add to the write surface.
|
|
78
|
+
const readPaths: string[] = [base.workspace, ...base.runtimeReadOnly];
|
|
79
|
+
const writePaths: string[] = [base.workspace];
|
|
80
|
+
if (workingRoot && workingRoot.length > 0) {
|
|
81
|
+
// The shared working dir is bound rw — the agent's cwd lives here AND it can
|
|
82
|
+
// write to it (it's effectively an rw mount that is also the cwd). dedupe
|
|
83
|
+
// below handles the case where it coincides with the private home / a mount.
|
|
84
|
+
readPaths.push(workingRoot);
|
|
85
|
+
writePaths.push(workingRoot);
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
for (const m of declared) {
|
|
89
|
+
readPaths.push(m.hostPath);
|
|
90
|
+
if (m.mode === "rw") writePaths.push(m.hostPath);
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
// WRITES are confined in BOTH cases (workspace + rw mounts) — the agent never
|
|
94
|
+
// escapes its workspace or corrupts the operator's files, regardless of read
|
|
95
|
+
// scope. READS depend on `filesystem`:
|
|
96
|
+
// - scopedReads (filesystem "workspace", the DEFAULT): deny the home tree,
|
|
97
|
+
// re-allow the read surface within it (workspace + runtime/config + mounts).
|
|
98
|
+
// Keeps the operator's secrets (e.g. ~/.parachute/operator.token) unreadable.
|
|
99
|
+
// - broad (filesystem "full", explicit opt-in): no deny — claude reads the
|
|
100
|
+
// whole disk. (System paths are readable in both.)
|
|
101
|
+
if (!scopedReads) {
|
|
102
|
+
return { denyRead: [], allowRead: [], allowWrite: dedupePreserveOrder(writePaths), denyWrite: [] };
|
|
103
|
+
}
|
|
104
|
+
return {
|
|
105
|
+
denyRead: [homeTreeDenyRoot(platform)],
|
|
106
|
+
allowRead: dedupePreserveOrder(readPaths),
|
|
107
|
+
allowWrite: dedupePreserveOrder(writePaths),
|
|
108
|
+
denyWrite: [],
|
|
109
|
+
};
|
|
110
|
+
}
|
|
111
|
+
|
|
112
|
+
/**
|
|
113
|
+
* The cross-session `shared` mounts declared by a spec (design §4.5). v1 honors
|
|
114
|
+
* them by binding them like any other mount (composeFilesystemView already does);
|
|
115
|
+
* this helper surfaces them by name so a caller can log/audit the deliberate
|
|
116
|
+
* cross-session channel. The trust caveat (prefer shared-`ro` from the producer,
|
|
117
|
+
* never shared-`rw` across a trust boundary) is doc-level for v1.
|
|
118
|
+
*/
|
|
119
|
+
export function sharedMounts(mounts: readonly AgentMount[] | undefined): AgentMount[] {
|
|
120
|
+
return (mounts ?? []).filter((m) => typeof m.shared === "string" && m.shared.length > 0);
|
|
121
|
+
}
|
|
122
|
+
|
|
123
|
+
function dedupePreserveOrder(xs: readonly string[]): string[] {
|
|
124
|
+
const seen = new Set<string>();
|
|
125
|
+
const out: string[] = [];
|
|
126
|
+
for (const x of xs) {
|
|
127
|
+
if (!seen.has(x)) {
|
|
128
|
+
seen.add(x);
|
|
129
|
+
out.push(x);
|
|
130
|
+
}
|
|
131
|
+
}
|
|
132
|
+
return out;
|
|
133
|
+
}
|
|
@@ -0,0 +1,168 @@
|
|
|
1
|
+
import { describe, test, expect } from "bun:test";
|
|
2
|
+
import { Sandbox, configForSpec, type SandboxEngine } from "./index.ts";
|
|
3
|
+
import type { SandboxRuntimeConfig } from "@anthropic-ai/sandbox-runtime";
|
|
4
|
+
import type { AgentSpec, BaseBinds } from "./types.ts";
|
|
5
|
+
import type { EgressBaseInput } from "./egress.ts";
|
|
6
|
+
|
|
7
|
+
const BASE_BINDS: BaseBinds = { workspace: "/state/sessions/arm", runtimeReadOnly: ["/cfg"] };
|
|
8
|
+
const EGRESS_BASE: EgressBaseInput = { hubOrigin: "https://hub.example.com" };
|
|
9
|
+
|
|
10
|
+
/** A fake engine that records what it was initialized with + what it wrapped. */
|
|
11
|
+
function fakeEngine(): SandboxEngine & {
|
|
12
|
+
initializedWith: SandboxRuntimeConfig | null;
|
|
13
|
+
wrappedCommands: string[];
|
|
14
|
+
resets: number;
|
|
15
|
+
calls: string[];
|
|
16
|
+
} {
|
|
17
|
+
const rec = {
|
|
18
|
+
initializedWith: null as SandboxRuntimeConfig | null,
|
|
19
|
+
wrappedCommands: [] as string[],
|
|
20
|
+
resets: 0,
|
|
21
|
+
calls: [] as string[],
|
|
22
|
+
isSupportedPlatform: () => true,
|
|
23
|
+
isSandboxingEnabled: () => true,
|
|
24
|
+
async initialize(cfg: SandboxRuntimeConfig) {
|
|
25
|
+
rec.initializedWith = cfg;
|
|
26
|
+
rec.calls.push("initialize");
|
|
27
|
+
},
|
|
28
|
+
async wrapWithSandboxArgv(command: string) {
|
|
29
|
+
rec.wrappedCommands.push(command);
|
|
30
|
+
rec.calls.push("wrap");
|
|
31
|
+
return {
|
|
32
|
+
argv: ["/bin/bash", "-c", `SANDBOXED ${command}`],
|
|
33
|
+
env: { SANDBOX_RUNTIME: "1", HTTP_PROXY: "http://localhost:9999" },
|
|
34
|
+
};
|
|
35
|
+
},
|
|
36
|
+
async reset() {
|
|
37
|
+
rec.resets += 1;
|
|
38
|
+
rec.calls.push("reset");
|
|
39
|
+
},
|
|
40
|
+
};
|
|
41
|
+
return rec;
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
// network "restricted" so the allowedDomains-floor assertion applies; the /Users
|
|
45
|
+
// read-deny applies via the DEFAULT filesystem "workspace" (scoped reads). The
|
|
46
|
+
// open-network default is covered in config.test.ts.
|
|
47
|
+
const SPEC: AgentSpec = {
|
|
48
|
+
name: "arm",
|
|
49
|
+
channels: ["ch"],
|
|
50
|
+
network: "restricted",
|
|
51
|
+
egress: ["registry.npmjs.org"],
|
|
52
|
+
mounts: [{ hostPath: "/proj", mountPath: "/work", mode: "rw" }],
|
|
53
|
+
};
|
|
54
|
+
|
|
55
|
+
describe("Sandbox adapter", () => {
|
|
56
|
+
test("initializes the engine with the spec-derived config, then wraps the command", async () => {
|
|
57
|
+
const engine = fakeEngine();
|
|
58
|
+
const sandbox = new Sandbox(engine);
|
|
59
|
+
const wrapped = await sandbox.wrap({
|
|
60
|
+
spec: SPEC,
|
|
61
|
+
baseBinds: BASE_BINDS,
|
|
62
|
+
egressBase: EGRESS_BASE,
|
|
63
|
+
command: "claude --strict-mcp-config",
|
|
64
|
+
platform: "darwin",
|
|
65
|
+
});
|
|
66
|
+
|
|
67
|
+
// It initialized with the right config (egress floor + scoped reads).
|
|
68
|
+
expect(engine.initializedWith).not.toBeNull();
|
|
69
|
+
expect(engine.initializedWith!.network.allowedDomains).toContain("api.anthropic.com");
|
|
70
|
+
expect(engine.initializedWith!.network.allowedDomains).toContain("registry.npmjs.org");
|
|
71
|
+
expect(engine.initializedWith!.filesystem.denyRead).toContain("/Users");
|
|
72
|
+
expect(engine.initializedWith!.filesystem.allowWrite).toContain("/proj");
|
|
73
|
+
|
|
74
|
+
// It wrapped exactly the command we passed.
|
|
75
|
+
expect(engine.wrappedCommands).toEqual(["claude --strict-mcp-config"]);
|
|
76
|
+
|
|
77
|
+
// It returns argv + env + the config used.
|
|
78
|
+
expect(wrapped.argv[0]).toBe("/bin/bash");
|
|
79
|
+
expect(wrapped.argv[2]).toContain("SANDBOXED claude");
|
|
80
|
+
expect(wrapped.env.SANDBOX_RUNTIME).toBe("1");
|
|
81
|
+
expect(wrapped.config).toBe(engine.initializedWith!);
|
|
82
|
+
});
|
|
83
|
+
|
|
84
|
+
test("wrap() RESETS the singleton before initializing (no stale proxy/config leak across spawns)", async () => {
|
|
85
|
+
const engine = fakeEngine();
|
|
86
|
+
const sandbox = new Sandbox(engine);
|
|
87
|
+
await sandbox.wrap({
|
|
88
|
+
spec: SPEC,
|
|
89
|
+
baseBinds: BASE_BINDS,
|
|
90
|
+
egressBase: EGRESS_BASE,
|
|
91
|
+
command: "claude",
|
|
92
|
+
});
|
|
93
|
+
// The order matters: reset → initialize → wrap. Without the leading reset, a
|
|
94
|
+
// prior spawn's network config (e.g. HTTP_PROXY from a restricted session)
|
|
95
|
+
// leaks into this wrap and an open session routes to a dead proxy → dies.
|
|
96
|
+
expect(engine.calls).toEqual(["reset", "initialize", "wrap"]);
|
|
97
|
+
});
|
|
98
|
+
|
|
99
|
+
test("a RESTRICTED-network spawn's proxy env does NOT leak into a later OPEN spawn (the real-bug scenario)", async () => {
|
|
100
|
+
// A leak-MODELING engine, mirroring the real singleton: `initialize` turns the
|
|
101
|
+
// proxy on iff the config has an allowlist (restricted network); `wrap` emits
|
|
102
|
+
// HTTP_PROXY iff the proxy is on; `reset` turns it off. Without the
|
|
103
|
+
// reset-before-init in Sandbox.wrap, the proxy would still be on from the
|
|
104
|
+
// restricted spawn and leak into the open spawn's env — exactly the live failure.
|
|
105
|
+
let proxyOn = false;
|
|
106
|
+
const engine: SandboxEngine = {
|
|
107
|
+
isSupportedPlatform: () => true,
|
|
108
|
+
isSandboxingEnabled: () => true,
|
|
109
|
+
async initialize(cfg: SandboxRuntimeConfig) {
|
|
110
|
+
if ((cfg.network as { allowedDomains?: string[] }).allowedDomains) proxyOn = true;
|
|
111
|
+
},
|
|
112
|
+
async wrapWithSandboxArgv(command: string) {
|
|
113
|
+
return { argv: ["/bin/bash", "-c", command], env: proxyOn ? { HTTP_PROXY: "http://localhost:1" } : {} };
|
|
114
|
+
},
|
|
115
|
+
async reset() {
|
|
116
|
+
proxyOn = false;
|
|
117
|
+
},
|
|
118
|
+
};
|
|
119
|
+
const sandbox = new Sandbox(engine);
|
|
120
|
+
// 1) restricted-network spawn → allowlist present → proxy on, HTTP_PROXY present.
|
|
121
|
+
const restricted = await sandbox.wrap({ spec: { name: "r", channels: ["c"], network: "restricted" }, baseBinds: BASE_BINDS, egressBase: EGRESS_BASE, command: "claude" });
|
|
122
|
+
expect(restricted.env.HTTP_PROXY).toBeDefined();
|
|
123
|
+
// 2) open-network spawn right after (the default) → the per-wrap reset clears the
|
|
124
|
+
// proxy, so NO stale HTTP_PROXY leaks in (the bug: claude would route to the
|
|
125
|
+
// dead proxy + die).
|
|
126
|
+
const open = await sandbox.wrap({ spec: { name: "o", channels: ["c"] }, baseBinds: BASE_BINDS, egressBase: EGRESS_BASE, command: "claude" });
|
|
127
|
+
expect(open.env.HTTP_PROXY).toBeUndefined();
|
|
128
|
+
});
|
|
129
|
+
|
|
130
|
+
test("reset() tears the engine down", async () => {
|
|
131
|
+
const engine = fakeEngine();
|
|
132
|
+
const sandbox = new Sandbox(engine);
|
|
133
|
+
await sandbox.reset();
|
|
134
|
+
expect(engine.resets).toBe(1);
|
|
135
|
+
});
|
|
136
|
+
|
|
137
|
+
test("isSupportedPlatform delegates to the engine", () => {
|
|
138
|
+
const engine = fakeEngine();
|
|
139
|
+
expect(new Sandbox(engine).isSupportedPlatform()).toBe(true);
|
|
140
|
+
});
|
|
141
|
+
|
|
142
|
+
test("SECURITY: a restricted-network spec omitting egress still gets the base floor in the engine config", async () => {
|
|
143
|
+
const engine = fakeEngine();
|
|
144
|
+
const sandbox = new Sandbox(engine);
|
|
145
|
+
await sandbox.wrap({
|
|
146
|
+
spec: { name: "x", channels: ["c"], network: "restricted" }, // restricted, no egress declared
|
|
147
|
+
baseBinds: BASE_BINDS,
|
|
148
|
+
egressBase: EGRESS_BASE,
|
|
149
|
+
command: "claude",
|
|
150
|
+
platform: "darwin",
|
|
151
|
+
});
|
|
152
|
+
expect(engine.initializedWith!.network.allowedDomains).toContain("api.anthropic.com");
|
|
153
|
+
expect(engine.initializedWith!.network.allowedDomains).toContain("hub.example.com");
|
|
154
|
+
});
|
|
155
|
+
});
|
|
156
|
+
|
|
157
|
+
describe("configForSpec helper", () => {
|
|
158
|
+
test("builds the config without touching an engine", () => {
|
|
159
|
+
const cfg = configForSpec({
|
|
160
|
+
spec: SPEC,
|
|
161
|
+
baseBinds: BASE_BINDS,
|
|
162
|
+
egressBase: EGRESS_BASE,
|
|
163
|
+
platform: "darwin",
|
|
164
|
+
});
|
|
165
|
+
expect(cfg.network.allowedDomains).toContain("registry.npmjs.org");
|
|
166
|
+
expect(cfg.filesystem.allowWrite).toContain("/proj");
|
|
167
|
+
});
|
|
168
|
+
});
|