@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,382 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Agent-spec + Sandbox contract types.
|
|
3
|
+
*
|
|
4
|
+
* The **agent spec** (design §4.1) is the single declaration of everything an
|
|
5
|
+
* arm may reach: its MCP surface (channels + vault), its network egress, and its
|
|
6
|
+
* filesystem view. Scope and isolation are both read off this one object, so
|
|
7
|
+
* there is exactly one place that says "what is this arm allowed to touch."
|
|
8
|
+
*
|
|
9
|
+
* design/2026-06-14-sandboxed-agent-sessions.md §4.1, §4.4, §4.5
|
|
10
|
+
*
|
|
11
|
+
* The Sandbox **contract** (design §3.1) is held constant; the **mechanism**
|
|
12
|
+
* varies by platform (Seatbelt on macOS, bubblewrap on Linux) behind it. v1
|
|
13
|
+
* ships one backend (Anthropic's sandbox-runtime); the escalation rung (§3.4 —
|
|
14
|
+
* gVisor / full VM) is a second backend added later without touching callers.
|
|
15
|
+
*/
|
|
16
|
+
|
|
17
|
+
/** Read/write mode for a declared mount. */
|
|
18
|
+
export type MountMode = "ro" | "rw";
|
|
19
|
+
|
|
20
|
+
/**
|
|
21
|
+
* A filesystem bind beyond the implicit workspace + runtime/config. Each entry
|
|
22
|
+
* binds a host path to a mount path at `ro` or `rw` (design §4.5).
|
|
23
|
+
*
|
|
24
|
+
* On macOS the sandbox profile is glob-capable, so `hostPath` may contain glob
|
|
25
|
+
* patterns; on Linux it must be a literal path (the runtime does not glob there).
|
|
26
|
+
* For v1 we bind the host path directly (`mountPath` is recorded for the future
|
|
27
|
+
* bubblewrap bind-remap and for the spec→mandate seam, §4.6 — it does not change
|
|
28
|
+
* the v1 Seatbelt path, which has no path-remapping layer).
|
|
29
|
+
*/
|
|
30
|
+
export interface AgentMount {
|
|
31
|
+
/** Path on the host to expose into the session. */
|
|
32
|
+
hostPath: string;
|
|
33
|
+
/** Path the session sees it at. Recorded for the future bind-remap; see note above. */
|
|
34
|
+
mountPath: string;
|
|
35
|
+
/** Read-only or read-write. */
|
|
36
|
+
mode: MountMode;
|
|
37
|
+
/**
|
|
38
|
+
* Opt-in cross-session share by name (design §4.5). A deliberate hole in
|
|
39
|
+
* session-to-session isolation — honored here, but the trust caveat (prefer
|
|
40
|
+
* shared-`ro` from the producer, never shared-`rw` across a trust boundary) is
|
|
41
|
+
* doc-level for v1. Plumbed through so a future use is a considered decision.
|
|
42
|
+
*/
|
|
43
|
+
shared?: string;
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
/** The vault binding for an arm: which vault, what access, optionally tag-scoped. */
|
|
47
|
+
export interface AgentVaultSpec {
|
|
48
|
+
/** Vault instance name (e.g. "default"). */
|
|
49
|
+
name: string;
|
|
50
|
+
/** Access verb minted into the vault token. */
|
|
51
|
+
access: "read" | "write" | "admin";
|
|
52
|
+
/**
|
|
53
|
+
* Optional tag scope — narrows the minted vault token to these tags via the
|
|
54
|
+
* `permissions.scoped_tags` claim (e.g. `["#agent/message"]`). Omitted = the
|
|
55
|
+
* verb's full scope across the vault.
|
|
56
|
+
*/
|
|
57
|
+
tags?: string[];
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
/** An additional MCP server to wire in, by URL (design §4.1 `otherMcps`). */
|
|
61
|
+
export interface OtherMcpSpec {
|
|
62
|
+
/** Entry key in the generated `mcpServers` object. */
|
|
63
|
+
name: string;
|
|
64
|
+
/** Streamable-HTTP MCP URL. */
|
|
65
|
+
url: string;
|
|
66
|
+
/**
|
|
67
|
+
* Scope to mint a token for, if this MCP is hub-gated. Omitted = no token
|
|
68
|
+
* (an unauthenticated / externally-authenticated MCP).
|
|
69
|
+
*/
|
|
70
|
+
scope?: string;
|
|
71
|
+
/** Audience to mint the token under. Defaults to inferred from scope by the hub. */
|
|
72
|
+
audience?: string;
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
/**
|
|
76
|
+
* An agent spec — the complete least-privilege envelope for one launched arm
|
|
77
|
+
* (design §4.1). `egress` and `mounts` are additive to a non-removable base; the
|
|
78
|
+
* spec only ever *adds*.
|
|
79
|
+
*/
|
|
80
|
+
/**
|
|
81
|
+
* A channel binding for an arm. A bare string is shorthand for `{ name, access:
|
|
82
|
+
* "write" }` (back-compat — the common read+write single-threaded session); the object
|
|
83
|
+
* form scopes a channel read-only so an arm that only *watches* a channel mints
|
|
84
|
+
* `agent:read` and never `agent:write` (the "scope an arm to channel X
|
|
85
|
+
* read-only" use case).
|
|
86
|
+
*/
|
|
87
|
+
export interface AgentChannelSpec {
|
|
88
|
+
/** Channel name (the `/mcp/<channel>` segment). */
|
|
89
|
+
name: string;
|
|
90
|
+
/**
|
|
91
|
+
* Channel access. `"write"` (default) mints `agent:read agent:write`;
|
|
92
|
+
* `"read"` mints `agent:read` only — the arm can be woken + read the channel
|
|
93
|
+
* but cannot reply.
|
|
94
|
+
*/
|
|
95
|
+
access?: "read" | "write";
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
/** A channel entry: a bare name (= write access) or the scoped object form. */
|
|
99
|
+
export type AgentChannel = string | AgentChannelSpec;
|
|
100
|
+
|
|
101
|
+
/**
|
|
102
|
+
* Which backend drives the agent (design 2026-06-16-pluggable-agent-backend.md +
|
|
103
|
+
* 2026-06-18-channel-backend.md). There are exactly TWO — the `interactive` (tmux)
|
|
104
|
+
* backend was RETIRED 2026-06-19 (design 2026-06-19-retire-interactive-backend.md);
|
|
105
|
+
* `channel` is what it was reaching for, done right:
|
|
106
|
+
*
|
|
107
|
+
* - `"programmatic"` (the DEFAULT): NO resident process. An inbound message becomes
|
|
108
|
+
* one on-demand `claude -p --resume <sid>` turn ({@link AgentBackend}); the reply
|
|
109
|
+
* is posted back as an outbound `#agent/message/outbound` note. No idle session →
|
|
110
|
+
* nothing to go deaf, no reconnect, no replay, no consent gate. The reliable
|
|
111
|
+
* primary path; best for clean per-message "do a task, report back" turns.
|
|
112
|
+
* - `"attached"` (design 2026-06-18-channel-backend.md; the backend value was named
|
|
113
|
+
* `"channel"` before this rename — see the BACK-COMPAT note below): the turn is
|
|
114
|
+
* delivered over a channel to a Claude Code session the OPERATOR runs themselves
|
|
115
|
+
* (their machine, their env/creds, unsandboxed) and has connected (is "attached") to
|
|
116
|
+
* the channel's MCP endpoint. The daemon runs NO `claude -p`; the inbound
|
|
117
|
+
* `#agent/message/inbound` notes accumulate as a durable queue (the vault IS the
|
|
118
|
+
* queue). The connected session PULLs the next message (an MCP tool), works, and
|
|
119
|
+
* REPLYs (an MCP tool) — the daemon writes the outbound note + marks the inbound
|
|
120
|
+
* handled. Routed to the {@link AttachedQueueRegistry}, entirely bypassing the
|
|
121
|
+
* programmatic serial worker (the daemon routing fork). Claim state lives on the
|
|
122
|
+
* inbound note's `status` (`pending | in-flight | handled`), so it's restart-safe.
|
|
123
|
+
*
|
|
124
|
+
* BACK-COMPAT (backend VALUE rename `"channel"` → `"attached"`): an already-persisted
|
|
125
|
+
* def (or `spec.json`) whose `backend` is the legacy `"channel"` is DUAL-READ —
|
|
126
|
+
* normalized to `"attached"` on read (`parseAgentDef` in agent-defs.ts; the daemon
|
|
127
|
+
* routing fork also accepts a legacy un-normalized value defensively). Only the new
|
|
128
|
+
* `"attached"` value is ever WRITTEN. (Note: the ROUTING KEY `channel` — the agent's
|
|
129
|
+
* address, the `/mcp/<channel>` URL segment, `metadata.channel` on notes — is a
|
|
130
|
+
* SEPARATE concept and is deliberately unchanged.)
|
|
131
|
+
*
|
|
132
|
+
* BACK-COMPAT (retired `interactive`): a persisted `spec.json` that carries the retired
|
|
133
|
+
* `backend:"interactive"` value (or omits `backend` entirely — pre-field specs were
|
|
134
|
+
* interactive) is no longer re-registered on boot (the boot re-register reads
|
|
135
|
+
* `spec.backend === "programmatic"` exactly; anything else is skipped — see daemon.ts),
|
|
136
|
+
* so a stale interactive spec on disk is inert, never migrated and never launched.
|
|
137
|
+
*/
|
|
138
|
+
export type AgentBackendKind = "programmatic" | "attached";
|
|
139
|
+
|
|
140
|
+
/**
|
|
141
|
+
* How a channel's {@link AgentSpec.systemPrompt} composes with Claude Code's own
|
|
142
|
+
* default system prompt (design 2026-06-16-channel-system-prompt.md):
|
|
143
|
+
*
|
|
144
|
+
* - `"append"` (DEFAULT): KEEP Claude Code's capable default system prompt
|
|
145
|
+
* (~2.4k tokens of tool/agentic instruction) and ADD the channel's role on top
|
|
146
|
+
* — `claude -p --append-system-prompt(-file) <X>`. The right default: the
|
|
147
|
+
* channel gets strong specificity without losing CC's base competence.
|
|
148
|
+
* - `"replace"`: REPLACE the default entirely with the channel's prompt —
|
|
149
|
+
* `claude -p --system-prompt(-file) <X>`. A fully-custom persona for an
|
|
150
|
+
* operator who wants total control of the system layer (and leanness on the
|
|
151
|
+
* subscription — fewer base tokens per turn).
|
|
152
|
+
*
|
|
153
|
+
* Either mode is orthogonal to CLAUDE.md, which is a SEPARATE context layer
|
|
154
|
+
* unaffected by both flags (only `--bare` would drop it — and `--bare` is
|
|
155
|
+
* deliberately NOT implemented; see the design note: it is API-key-only by design
|
|
156
|
+
* and would force metered billing off the subscription).
|
|
157
|
+
*/
|
|
158
|
+
export type SystemPromptMode = "append" | "replace";
|
|
159
|
+
|
|
160
|
+
/**
|
|
161
|
+
* The agent's EXECUTION-LIFECYCLE mode — how a turn relates to the agent's
|
|
162
|
+
* conversation thread (the architecture synthesis, Phase 3 prerequisite). The UNIFIED
|
|
163
|
+
* model is `definition -> thread -> message`: EVERYTHING is a thread, and BOTH modes
|
|
164
|
+
* materialize a `#agent/thread` note (the structural unification — a "run" was always a
|
|
165
|
+
* thread with one turn). An agent is either SINGLE-THREADED or MULTI-THREADED; the
|
|
166
|
+
* distinction is defined entirely by `claude -p` session-id semantics + the thread's
|
|
167
|
+
* identity:
|
|
168
|
+
*
|
|
169
|
+
* - `"single-threaded"` (DEFAULT; = today's behavior): ONE persistent session id
|
|
170
|
+
* per channel. Each turn `--resume`s the stored id and persists the returned id
|
|
171
|
+
* after — the channel transcript IS the thread. It materializes exactly ONE
|
|
172
|
+
* `#agent/thread` note per channel, named after the definition, UPSERTED in place
|
|
173
|
+
* each turn; the note body holds a rolling SUMMARY of the conversation (turn_count +
|
|
174
|
+
* cumulative usage roll up). A scheduled runner job for a single-threaded def is a
|
|
175
|
+
* synthetic inbound that RESUMES that one thread (continuing the chat). This is
|
|
176
|
+
* exactly what every agent does today (plus the now-materialized thread note).
|
|
177
|
+
*
|
|
178
|
+
* - `"multi-threaded"`: turns are THREAD-KEYED. TODAY — because no inbound carries
|
|
179
|
+
* a thread id yet — every fire mints a FRESH thread: do NOT read the prior session
|
|
180
|
+
* id (no `--resume`) and do NOT persist the returned id to the channel store, so
|
|
181
|
+
* each fire is a clean, independent invocation with no conversation continuity. It
|
|
182
|
+
* materializes ONE `#agent/thread` note per FIRE (the per-fire record: input + reply
|
|
183
|
+
* + status + timing). This is what an operator reaches for when a scheduled job
|
|
184
|
+
* should be a clean task run, NOT a silent continuation of the chat thread.
|
|
185
|
+
*
|
|
186
|
+
* ("one-shot" was the prior name for this mode — it was only ever the DEGENERATE
|
|
187
|
+
* FIRST-TURN of a multi-threaded agent, so the term retires. Continuation-by-
|
|
188
|
+
* thread-id — resuming a SPECIFIC prior thread — is a DEFERRED increment: it needs
|
|
189
|
+
* thread-id routing on the inbound, a thread-keyed session store, per-thread drain
|
|
190
|
+
* serialization, and recording the minted session/thread id into the thread note so
|
|
191
|
+
* a thread becomes resumable. When it lands, the SAME mode simply gains continuation
|
|
192
|
+
* with NO operator-facing change and NO migration; the fresh-per-fire shape that ships
|
|
193
|
+
* now is its degenerate case.)
|
|
194
|
+
*/
|
|
195
|
+
export type AgentMode = "single-threaded" | "multi-threaded";
|
|
196
|
+
|
|
197
|
+
export interface AgentSpec {
|
|
198
|
+
/** Human-readable arm name; used as the tmux session + workspace slug. */
|
|
199
|
+
name: string;
|
|
200
|
+
/**
|
|
201
|
+
* Channels to attach (one MCP entry each). Each entry is a bare name (read+write,
|
|
202
|
+
* back-compat) or `{ name, access: "read" }` to scope a channel read-only.
|
|
203
|
+
*/
|
|
204
|
+
channels: AgentChannel[];
|
|
205
|
+
/**
|
|
206
|
+
* WORKING DIRECTORY — a real host path the agent operates from (design
|
|
207
|
+
* 2026-06-16-agent-filesystem-and-sharing.md, the working-directory axis). When
|
|
208
|
+
* set, this absolute path becomes the agent's CWD and an rw working-root in the
|
|
209
|
+
* sandbox (it's bound rw + readable, exactly like an `rw` mount that is also the
|
|
210
|
+
* cwd). It is SHAREABLE — two agents (or a runner job, or a plain script) can
|
|
211
|
+
* point at the same dir (shared-rw concurrency is a known, deferred caveat; the
|
|
212
|
+
* agents step on each other like humans in a repo without git discipline).
|
|
213
|
+
*
|
|
214
|
+
* CRITICAL — the working dir is DECOUPLED from the agent's PRIVATE RUNTIME HOME.
|
|
215
|
+
* The seeded `CLAUDE_CONFIG_DIR` (`seedAgentHome`), `tmp`, `spec.json`,
|
|
216
|
+
* `system-prompt.txt`, and ESPECIALLY `.mcp.json` (which inlines the scoped
|
|
217
|
+
* vault/channel tokens — secrets) STAY in the per-agent private `sessions/<name>/`
|
|
218
|
+
* dir, 0600, NEVER written into this shared `workspace`. The governing principle
|
|
219
|
+
* (the design note's "line"): capability/credential/state is per-agent private;
|
|
220
|
+
* the working dir is shareable. `--mcp-config` / `--system-prompt-file` point at
|
|
221
|
+
* the private dir by ABSOLUTE path, so they're unaffected by the cwd change.
|
|
222
|
+
*
|
|
223
|
+
* Unset → today's behavior EXACTLY: the cwd is the private `sessions/<name>` dir
|
|
224
|
+
* (which is also the synthetic workspace).
|
|
225
|
+
*/
|
|
226
|
+
workspace?: string;
|
|
227
|
+
/** Optional vault binding. */
|
|
228
|
+
vault?: AgentVaultSpec;
|
|
229
|
+
/** Additional MCP servers, by URL. */
|
|
230
|
+
otherMcps?: OtherMcpSpec[];
|
|
231
|
+
/**
|
|
232
|
+
* Filesystem READ scope — ONE of Anthropic's two containment boundaries
|
|
233
|
+
* (https://www.anthropic.com/engineering/claude-code-sandboxing). Orthogonal to
|
|
234
|
+
* {@link network} — the agent's reach into the local disk and its reach onto the
|
|
235
|
+
* network are independent controls, deliberately NOT bundled.
|
|
236
|
+
*
|
|
237
|
+
* - `"workspace"` (DEFAULT): SCOPED reads. The home tree (`/Users` on macOS,
|
|
238
|
+
* `/home` on Linux) is DENIED, then re-allowed ONLY for the per-session
|
|
239
|
+
* workspace + the claude runtime + declared mounts. The agent literally
|
|
240
|
+
* cannot read the operator's secrets — `~/.parachute/operator.token` (the
|
|
241
|
+
* hub bearer), SSH keys, other projects — even though the network is open.
|
|
242
|
+
* This is the security-correct default: scoped disk, exfiltration surface
|
|
243
|
+
* removed at the source.
|
|
244
|
+
*
|
|
245
|
+
* - `"full"`: BROAD reads (the runtime default — the whole filesystem is
|
|
246
|
+
* readable). An explicit, deliberate escape hatch for the rare agent that
|
|
247
|
+
* genuinely needs to read across the operator's disk AND is trusted not to
|
|
248
|
+
* leak it. Combined with `network: "open"` this is the maximum-reach posture
|
|
249
|
+
* — only choose it knowingly.
|
|
250
|
+
*
|
|
251
|
+
* WRITES are confined to the per-session workspace + rw mounts in BOTH cases
|
|
252
|
+
* (the agent can never corrupt the operator's files or escape its workspace),
|
|
253
|
+
* exactly per Anthropic's "read/write the cwd, block outside" model.
|
|
254
|
+
*/
|
|
255
|
+
filesystem?: "workspace" | "full";
|
|
256
|
+
/**
|
|
257
|
+
* Network egress — the SECOND containment boundary, orthogonal to
|
|
258
|
+
* {@link filesystem}:
|
|
259
|
+
*
|
|
260
|
+
* - `"open"` (DEFAULT): full internet, no restriction. The right default for
|
|
261
|
+
* an owner-operated agent on a trusted box (claude needs the network to be
|
|
262
|
+
* useful) — SAFE because the `"workspace"` filesystem default already keeps
|
|
263
|
+
* local secrets unreadable, so "open network" can't exfiltrate what the
|
|
264
|
+
* agent can't see.
|
|
265
|
+
*
|
|
266
|
+
* - `"restricted"`: egress confined to a non-removable base
|
|
267
|
+
* (`{ Anthropic API, hub/vault }`) UNIONed with {@link egress}. For an agent
|
|
268
|
+
* fed FOREIGN/untrusted input, where you want to bound where it can reach
|
|
269
|
+
* even for data it legitimately holds.
|
|
270
|
+
*/
|
|
271
|
+
network?: "open" | "restricted";
|
|
272
|
+
/**
|
|
273
|
+
* Network egress hosts ADDITIVE to the non-removable base — only meaningful
|
|
274
|
+
* under `network: "restricted"` (when the network is `"open"` this is ignored).
|
|
275
|
+
* A restricted code-building agent opens exactly the package/source hosts it
|
|
276
|
+
* needs here.
|
|
277
|
+
*/
|
|
278
|
+
egress?: string[];
|
|
279
|
+
/**
|
|
280
|
+
* Filesystem mounts — ADDITIVE to the default private per-session workspace
|
|
281
|
+
* (rw) + the implicit runtime/claude-config (ro). Under `filesystem:
|
|
282
|
+
* "workspace"` (the default) these are the ONLY host paths re-allowed for
|
|
283
|
+
* reads beyond the workspace — mount the project you want the agent to work on.
|
|
284
|
+
*/
|
|
285
|
+
mounts?: AgentMount[];
|
|
286
|
+
/**
|
|
287
|
+
* Which stored Claude credential to inject (design §6). Default = "operator".
|
|
288
|
+
* This stream injects the credential as a passed-in param/placeholder; Stream 3
|
|
289
|
+
* builds the real per-channel secret store.
|
|
290
|
+
*/
|
|
291
|
+
credentialRef?: string;
|
|
292
|
+
/**
|
|
293
|
+
* Which backend drives the agent (design 2026-06-16-pluggable-agent-backend.md +
|
|
294
|
+
* 2026-06-18-channel-backend.md) — `"programmatic"` (the default; on-demand
|
|
295
|
+
* `claude -p` turns, no resident process) or `"attached"` (handled by a Claude Code
|
|
296
|
+
* session the operator connects — "attaches" — to the channel's MCP endpoint; the
|
|
297
|
+
* value was named `"channel"` before the rename, dual-read on load). The `interactive`
|
|
298
|
+
* (tmux) backend was retired 2026-06-19. See {@link AgentBackendKind}. Persisted in
|
|
299
|
+
* spec.json so a daemon restart re-registers a programmatic agent on boot.
|
|
300
|
+
*/
|
|
301
|
+
backend?: AgentBackendKind;
|
|
302
|
+
/**
|
|
303
|
+
* Which model the PROGRAMMATIC backend runs the turn on — passed verbatim to
|
|
304
|
+
* `claude -p --model <value>`. Accepts a Claude Code alias (`opus` / `sonnet` /
|
|
305
|
+
* `haiku`) or a full model id (e.g. `claude-opus-4-8`). Unset → no `--model`
|
|
306
|
+
* flag, so the turn inherits Claude Code's own default (Sonnet today). Only the
|
|
307
|
+
* programmatic backend reads this (a `channel`-backend turn runs in the
|
|
308
|
+
* operator's own session, whose model the operator controls). Set from the def's
|
|
309
|
+
* `metadata.model`; persisted in spec.json. NOT shell-interpolated — it's a
|
|
310
|
+
* discrete argv element, so an arbitrary string can't inject.
|
|
311
|
+
*/
|
|
312
|
+
model?: string;
|
|
313
|
+
/**
|
|
314
|
+
* Per-channel system prompt — the operator gives the channel a specific role,
|
|
315
|
+
* backend-visible, so the agent gets strong specificity decoupled from the
|
|
316
|
+
* workspace + CLAUDE.md (design 2026-06-16-channel-system-prompt.md). Set when
|
|
317
|
+
* creating/configuring the channel; written to a per-session file and passed on
|
|
318
|
+
* EVERY `claude -p` turn (the flags are per-invocation, not persistent — a
|
|
319
|
+
* `--resume` turn re-passes it too). Unset → today's behavior (CC's default
|
|
320
|
+
* prompt, untouched). Persisted in spec.json.
|
|
321
|
+
*/
|
|
322
|
+
systemPrompt?: string;
|
|
323
|
+
/**
|
|
324
|
+
* How {@link systemPrompt} composes with Claude Code's default system prompt —
|
|
325
|
+
* `"append"` (DEFAULT, keep CC's base + add the role) or `"replace"` (full
|
|
326
|
+
* custom persona). Only meaningful when `systemPrompt` is set. See
|
|
327
|
+
* {@link SystemPromptMode}.
|
|
328
|
+
*/
|
|
329
|
+
systemPromptMode?: SystemPromptMode;
|
|
330
|
+
/**
|
|
331
|
+
* The execution-lifecycle mode (the Phase-3 prerequisite). `"single-threaded"`
|
|
332
|
+
* (DEFAULT, = today): one persistent session per channel, `--resume`d + persisted
|
|
333
|
+
* each turn, the channel transcript is the thread. `"multi-threaded"`: thread-keyed —
|
|
334
|
+
* today (no inbound thread id yet) every fire mints a fresh thread (no `--resume`, the
|
|
335
|
+
* returned session id is NOT persisted to the channel store). BOTH modes now materialize
|
|
336
|
+
* an `#agent/thread` note (the unified model `definition -> thread -> message`): a
|
|
337
|
+
* single-threaded agent upserts ONE thread note per channel (named after the def, rolling
|
|
338
|
+
* summary); a multi-threaded agent writes one thread note per fire. Read at the
|
|
339
|
+
* session-handling chokepoint (the programmatic backend's `deliver` resume block) +
|
|
340
|
+
* governs the thread note's identity (one-per-channel upsert vs one-per-fire). Persisted
|
|
341
|
+
* in spec.json (set from the def's `metadata.mode`). See {@link AgentMode}.
|
|
342
|
+
*/
|
|
343
|
+
mode?: AgentMode;
|
|
344
|
+
/**
|
|
345
|
+
* The `#agent/definition` note id this agent was instantiated from — the
|
|
346
|
+
* provenance carried into the `#agent/thread` note a turn materializes (so a thread
|
|
347
|
+
* record links back to its def; BOTH modes). A plain id STRING for now (interim — typed
|
|
348
|
+
* link fields are a future vault feature). Set by {@link parseAgentDef} from the note
|
|
349
|
+
* id; unset for a spec not sourced from a def note (then the thread note carries no
|
|
350
|
+
* definition link).
|
|
351
|
+
*/
|
|
352
|
+
definition?: string;
|
|
353
|
+
}
|
|
354
|
+
|
|
355
|
+
/**
|
|
356
|
+
* The default workspace + runtime binds the contract always grants, independent
|
|
357
|
+
* of any spec. The caller supplies the concrete paths (resolved against the
|
|
358
|
+
* session's state dir + the runtime/config location) — keeping this module
|
|
359
|
+
* free of filesystem-layout assumptions and test-sandboxable.
|
|
360
|
+
*/
|
|
361
|
+
export interface BaseBinds {
|
|
362
|
+
/** Private per-session workspace (rw). */
|
|
363
|
+
workspace: string;
|
|
364
|
+
/**
|
|
365
|
+
* Read-only runtime/claude-config paths the session needs to run `claude`
|
|
366
|
+
* (e.g. the claude config dir). Always bound `ro`.
|
|
367
|
+
*/
|
|
368
|
+
runtimeReadOnly: string[];
|
|
369
|
+
}
|
|
370
|
+
|
|
371
|
+
/** Platform the Sandbox config is being built for. */
|
|
372
|
+
export type SandboxPlatform = "darwin" | "linux";
|
|
373
|
+
|
|
374
|
+
/**
|
|
375
|
+
* Normalize a channel entry (bare name or object) to `{ name, access }`. A bare
|
|
376
|
+
* string defaults to `write` (back-compat); the object form defaults to `write`
|
|
377
|
+
* when `access` is omitted.
|
|
378
|
+
*/
|
|
379
|
+
export function normalizeChannel(ch: AgentChannel): { name: string; access: "read" | "write" } {
|
|
380
|
+
if (typeof ch === "string") return { name: ch, access: "write" };
|
|
381
|
+
return { name: ch.name, access: ch.access ?? "write" };
|
|
382
|
+
}
|
|
@@ -0,0 +1,106 @@
|
|
|
1
|
+
import { describe, test, expect } from "bun:test";
|
|
2
|
+
import { mkdtempSync, readFileSync, writeFileSync, existsSync } from "fs";
|
|
3
|
+
import { tmpdir } from "os";
|
|
4
|
+
import { join } from "path";
|
|
5
|
+
import { resolveManifestPath, upsertService, listVaultNames, type ServiceEntry } from "./services-manifest.ts";
|
|
6
|
+
|
|
7
|
+
function tmp(): string {
|
|
8
|
+
return join(mkdtempSync(join(tmpdir(), "pc-manifest-")), "services.json");
|
|
9
|
+
}
|
|
10
|
+
|
|
11
|
+
const AGENT: ServiceEntry = {
|
|
12
|
+
name: "parachute-agent",
|
|
13
|
+
port: 1941,
|
|
14
|
+
paths: ["/agent"],
|
|
15
|
+
health: "/health",
|
|
16
|
+
version: "0.1.0",
|
|
17
|
+
displayName: "Agent",
|
|
18
|
+
stripPrefix: true,
|
|
19
|
+
};
|
|
20
|
+
|
|
21
|
+
describe("resolveManifestPath", () => {
|
|
22
|
+
test("honors PARACHUTE_HOME (sandbox for tests/e2e)", () => {
|
|
23
|
+
expect(resolveManifestPath({ PARACHUTE_HOME: "/tmp/sandbox" })).toBe("/tmp/sandbox/services.json");
|
|
24
|
+
});
|
|
25
|
+
test("falls back to HOME/.parachute", () => {
|
|
26
|
+
expect(resolveManifestPath({ HOME: "/home/x" })).toBe("/home/x/.parachute/services.json");
|
|
27
|
+
});
|
|
28
|
+
});
|
|
29
|
+
|
|
30
|
+
describe("upsertService", () => {
|
|
31
|
+
test("creates the manifest with the entry on first write", () => {
|
|
32
|
+
const path = tmp();
|
|
33
|
+
upsertService(AGENT, path);
|
|
34
|
+
const m = JSON.parse(readFileSync(path, "utf8"));
|
|
35
|
+
expect(m.services).toHaveLength(1);
|
|
36
|
+
expect(m.services[0].name).toBe("parachute-agent");
|
|
37
|
+
expect(m.services[0].paths).toEqual(["/agent"]);
|
|
38
|
+
expect(m.services[0].stripPrefix).toBe(true);
|
|
39
|
+
});
|
|
40
|
+
|
|
41
|
+
test("carries startCmd so the hub supervisor can start/restart/adopt the module (agent#34)", () => {
|
|
42
|
+
const path = tmp();
|
|
43
|
+
upsertService({ ...AGENT, startCmd: ["parachute-agent"] }, path);
|
|
44
|
+
const m = JSON.parse(readFileSync(path, "utf8"));
|
|
45
|
+
expect(m.services[0].startCmd).toEqual(["parachute-agent"]);
|
|
46
|
+
});
|
|
47
|
+
|
|
48
|
+
test("is idempotent — re-registering the same name does not duplicate", () => {
|
|
49
|
+
const path = tmp();
|
|
50
|
+
upsertService(AGENT, path);
|
|
51
|
+
upsertService({ ...AGENT, version: "0.1.1" }, path);
|
|
52
|
+
const m = JSON.parse(readFileSync(path, "utf8"));
|
|
53
|
+
expect(m.services).toHaveLength(1);
|
|
54
|
+
expect(m.services[0].version).toBe("0.1.1"); // module wins for fields it owns
|
|
55
|
+
});
|
|
56
|
+
|
|
57
|
+
test("merges — preserves hub-stamped fields the module doesn't author", () => {
|
|
58
|
+
const path = tmp();
|
|
59
|
+
// hub stamped installDir onto the row; module re-registers without it.
|
|
60
|
+
writeFileSync(path, JSON.stringify({ services: [{ ...AGENT, installDir: "/hub/stamped" }] }));
|
|
61
|
+
upsertService(AGENT, path);
|
|
62
|
+
const m = JSON.parse(readFileSync(path, "utf8"));
|
|
63
|
+
expect(m.services[0].installDir).toBe("/hub/stamped");
|
|
64
|
+
});
|
|
65
|
+
|
|
66
|
+
test("preserves other modules' entries", () => {
|
|
67
|
+
const path = tmp();
|
|
68
|
+
writeFileSync(path, JSON.stringify({ services: [{ name: "parachute-vault", port: 1940, paths: ["/vault/default"], health: "/vault/default/health", version: "0.5.2" }] }));
|
|
69
|
+
upsertService(AGENT, path);
|
|
70
|
+
const m = JSON.parse(readFileSync(path, "utf8"));
|
|
71
|
+
expect(m.services).toHaveLength(2);
|
|
72
|
+
expect(m.services.map((s: ServiceEntry) => s.name).sort()).toEqual(["parachute-agent", "parachute-vault"]);
|
|
73
|
+
});
|
|
74
|
+
|
|
75
|
+
test("throws on a malformed manifest rather than clobbering it", () => {
|
|
76
|
+
const path = tmp();
|
|
77
|
+
writeFileSync(path, JSON.stringify({ not_services: true }));
|
|
78
|
+
expect(() => upsertService(AGENT, path)).toThrow(/malformed/);
|
|
79
|
+
expect(existsSync(path)).toBe(true); // original left intact
|
|
80
|
+
expect(JSON.parse(readFileSync(path, "utf8"))).toEqual({ not_services: true }); // content untouched
|
|
81
|
+
});
|
|
82
|
+
});
|
|
83
|
+
|
|
84
|
+
describe("listVaultNames", () => {
|
|
85
|
+
test("extracts vault names from the vault module's /vault/<name> paths, default first", () => {
|
|
86
|
+
const path = tmp();
|
|
87
|
+
writeFileSync(path, JSON.stringify({ services: [
|
|
88
|
+
{ name: "parachute-vault", port: 1940, paths: ["/vault/boulder", "/vault/default", "/vault/techne"], health: "x", version: "1" },
|
|
89
|
+
{ name: "parachute-agent", port: 1941, paths: ["/agent"], health: "x", version: "1" },
|
|
90
|
+
] }));
|
|
91
|
+
expect(listVaultNames(path)).toEqual(["default", "boulder", "techne"]);
|
|
92
|
+
});
|
|
93
|
+
|
|
94
|
+
test("dedupes across services and ignores non-vault paths", () => {
|
|
95
|
+
const path = tmp();
|
|
96
|
+
writeFileSync(path, JSON.stringify({ services: [
|
|
97
|
+
{ name: "a", port: 1, paths: ["/vault/x", "/other"], health: "x", version: "1" },
|
|
98
|
+
{ name: "b", port: 2, paths: ["/vault/x", "/vault/y"], health: "x", version: "1" },
|
|
99
|
+
] }));
|
|
100
|
+
expect(listVaultNames(path).sort()).toEqual(["x", "y"]);
|
|
101
|
+
});
|
|
102
|
+
|
|
103
|
+
test("returns [] when the manifest is absent or unreadable", () => {
|
|
104
|
+
expect(listVaultNames(join(tmpdir(), "does-not-exist-" + Math.floor(performance.now()), "services.json"))).toEqual([]);
|
|
105
|
+
});
|
|
106
|
+
});
|
|
@@ -0,0 +1,95 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Self-registration into `~/.parachute/services.json` on daemon boot.
|
|
3
|
+
*
|
|
4
|
+
* Mirrors `parachute-scribe/src/services-manifest.ts` deliberately — the file
|
|
5
|
+
* shape is the contract between every Parachute module and the hub
|
|
6
|
+
* (`parachute-hub/src/services-manifest.ts` is the canonical reader). Hub reads
|
|
7
|
+
* this to know the module's port, the paths it should reverse-proxy
|
|
8
|
+
* (`/agent/*` over the expose → this daemon on loopback), and the version.
|
|
9
|
+
*
|
|
10
|
+
* Best-effort: any write error is logged + swallowed by the caller. The daemon
|
|
11
|
+
* still serves locally even if registration fails. Honors `PARACHUTE_HOME` so
|
|
12
|
+
* sandboxed/test/e2e daemons never touch the operator's real services.json.
|
|
13
|
+
*/
|
|
14
|
+
import { existsSync, mkdirSync, readFileSync, renameSync, writeFileSync } from "node:fs";
|
|
15
|
+
import { homedir } from "node:os";
|
|
16
|
+
import { dirname, join } from "node:path";
|
|
17
|
+
|
|
18
|
+
export interface ServiceEntry {
|
|
19
|
+
name: string;
|
|
20
|
+
port: number;
|
|
21
|
+
paths: string[];
|
|
22
|
+
health: string;
|
|
23
|
+
version: string;
|
|
24
|
+
displayName?: string;
|
|
25
|
+
tagline?: string;
|
|
26
|
+
installDir?: string;
|
|
27
|
+
stripPrefix?: boolean;
|
|
28
|
+
/** Hub-stamped fields (e.g. installDir) ride along; the upsert merges. */
|
|
29
|
+
[key: string]: unknown;
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
interface ServicesManifest {
|
|
33
|
+
services: ServiceEntry[];
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
/** Canonical services.json path. Honors PARACHUTE_HOME for sandbox/test runs. */
|
|
37
|
+
export function resolveManifestPath(env: Record<string, string | undefined> = process.env): string {
|
|
38
|
+
const base = env.PARACHUTE_HOME ?? join(env.HOME ?? homedir(), ".parachute");
|
|
39
|
+
return join(base, "services.json");
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
function readManifest(path: string): ServicesManifest {
|
|
43
|
+
if (!existsSync(path)) return { services: [] };
|
|
44
|
+
const raw = JSON.parse(readFileSync(path, "utf8"));
|
|
45
|
+
if (!raw || typeof raw !== "object" || !Array.isArray((raw as { services?: unknown }).services)) {
|
|
46
|
+
throw new Error(`services manifest at ${path} is malformed (missing "services" array)`);
|
|
47
|
+
}
|
|
48
|
+
return raw as ServicesManifest;
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
/**
|
|
52
|
+
* List the vault instance names installed on this host, from the vault module's
|
|
53
|
+
* registered `paths` (`/vault/<name>` → `<name>`). Used by the agents page's vault
|
|
54
|
+
* picker so an operator chooses from real vaults instead of typing a name blind.
|
|
55
|
+
* Best-effort: returns `[]` if the manifest is absent/unreadable (the picker then
|
|
56
|
+
* falls back to free text). Deduped + sorted; `default` floated first if present.
|
|
57
|
+
*/
|
|
58
|
+
export function listVaultNames(path: string = resolveManifestPath()): string[] {
|
|
59
|
+
let manifest: ServicesManifest;
|
|
60
|
+
try {
|
|
61
|
+
manifest = readManifest(path);
|
|
62
|
+
} catch {
|
|
63
|
+
return [];
|
|
64
|
+
}
|
|
65
|
+
const names = new Set<string>();
|
|
66
|
+
for (const svc of manifest.services) {
|
|
67
|
+
for (const p of svc.paths ?? []) {
|
|
68
|
+
// `paths` are operator-registered route prefixes, not URLs — take the literal
|
|
69
|
+
// segment (no decodeURIComponent: a stray %2F could synthesize a slash-bearing
|
|
70
|
+
// vault name, and real vault names are plain slugs).
|
|
71
|
+
const m = /^\/vault\/([^/]+)/.exec(p);
|
|
72
|
+
if (m && m[1]) names.add(m[1]);
|
|
73
|
+
}
|
|
74
|
+
}
|
|
75
|
+
const sorted = [...names].sort((a, b) => a.localeCompare(b));
|
|
76
|
+
// Float "default" to the front — it's the conventional primary vault.
|
|
77
|
+
return sorted.sort((a, b) => (a === "default" ? -1 : b === "default" ? 1 : 0));
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
/**
|
|
81
|
+
* Idempotent upsert of a service entry. Merges into any existing row rather
|
|
82
|
+
* than replacing it — preserves hub-stamped fields the module doesn't own.
|
|
83
|
+
* Atomic write: stages to a tmp file, then renames over the target so a crash
|
|
84
|
+
* mid-write leaves the prior file intact.
|
|
85
|
+
*/
|
|
86
|
+
export function upsertService(entry: ServiceEntry, path: string = resolveManifestPath()): void {
|
|
87
|
+
mkdirSync(dirname(path), { recursive: true });
|
|
88
|
+
const manifest = readManifest(path);
|
|
89
|
+
const idx = manifest.services.findIndex((s) => s.name === entry.name);
|
|
90
|
+
if (idx >= 0) manifest.services[idx] = { ...manifest.services[idx], ...entry };
|
|
91
|
+
else manifest.services.push(entry);
|
|
92
|
+
const tmp = `${path}.tmp-${process.pid}-${Date.now()}`;
|
|
93
|
+
writeFileSync(tmp, `${JSON.stringify(manifest, null, 2)}\n`);
|
|
94
|
+
renameSync(tmp, path);
|
|
95
|
+
}
|