@openparachute/agent 0.1.2 → 0.2.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/.parachute/module.json +124 -8
- package/LICENSE +2 -16
- package/README.md +118 -166
- package/package.json +32 -43
- package/scripts/spawn-agent.ts +371 -0
- package/src/_parked/interactive-spawn.test.ts +324 -0
- package/src/_parked/interactive-spawn.ts +701 -0
- package/src/agent-defs.test.ts +1504 -0
- package/src/agent-defs.ts +1702 -0
- package/src/agent-mcp-config.test.ts +115 -0
- package/src/agent-mcp-config.ts +115 -0
- package/src/agents.test.ts +360 -0
- package/src/agents.ts +379 -0
- package/src/auth.test.ts +46 -0
- package/src/auth.ts +140 -0
- package/src/backends/attached-queue.test.ts +376 -0
- package/src/backends/attached-queue.ts +372 -0
- package/src/backends/programmatic.test.ts +1715 -0
- package/src/backends/programmatic.ts +927 -0
- package/src/backends/registry.test.ts +1494 -0
- package/src/backends/registry.ts +1202 -0
- package/src/backends/stream-json.test.ts +570 -0
- package/src/backends/stream-json.ts +392 -0
- package/src/backends/types.ts +223 -0
- package/src/bridge.ts +417 -0
- package/src/channel-backend-wiring.test.ts +237 -0
- package/src/credentials.test.ts +274 -0
- package/src/credentials.ts +380 -0
- package/src/cron.test.ts +342 -0
- package/src/cron.ts +380 -0
- package/src/daemon-agent-def-api.test.ts +166 -0
- package/src/daemon-agent-defs-api.test.ts +953 -0
- package/src/daemon-agent-env-api.test.ts +338 -0
- package/src/daemon-attached-queue-store.test.ts +65 -0
- package/src/daemon-config-api.test.ts +962 -0
- package/src/daemon-jobs-api.test.ts +271 -0
- package/src/daemon-vault-chat.test.ts +250 -0
- package/src/daemon.test.ts +746 -0
- package/src/daemon.ts +3314 -0
- package/src/def-vaults.test.ts +136 -0
- package/src/def-vaults.ts +165 -0
- package/src/delivery-state.test.ts +110 -0
- package/src/delivery-state.ts +154 -0
- package/src/effective-env.test.ts +114 -0
- package/src/effective-env.ts +184 -0
- package/src/env-compat.ts +39 -0
- package/src/grants.test.ts +638 -0
- package/src/grants.ts +675 -0
- package/src/hub-jwt.test.ts +161 -0
- package/src/hub-jwt.ts +182 -0
- package/src/jobs.test.ts +245 -0
- package/src/jobs.ts +266 -0
- package/src/mcp-http.test.ts +265 -0
- package/src/mcp-http.ts +771 -0
- package/src/mint-token.test.ts +152 -0
- package/src/mint-token.ts +139 -0
- package/src/module-manifest.test.ts +158 -0
- package/src/oauth-discovery.ts +134 -0
- package/src/programmatic-wiring.test.ts +838 -0
- package/src/registry.test.ts +227 -0
- package/src/registry.ts +228 -0
- package/src/resolve-port.test.ts +64 -0
- package/src/routing.test.ts +184 -0
- package/src/routing.ts +76 -0
- package/src/runner.test.ts +506 -0
- package/src/runner.ts +255 -0
- package/src/sandbox/config.test.ts +150 -0
- package/src/sandbox/config.ts +102 -0
- package/src/sandbox/egress.test.ts +113 -0
- package/src/sandbox/egress.ts +123 -0
- package/src/sandbox/index.ts +180 -0
- package/src/sandbox/live-seatbelt.test.ts +277 -0
- package/src/sandbox/mounts.test.ts +154 -0
- package/src/sandbox/mounts.ts +133 -0
- package/src/sandbox/sandbox.test.ts +168 -0
- package/src/sandbox/types.ts +382 -0
- package/src/services-manifest.test.ts +106 -0
- package/src/services-manifest.ts +95 -0
- package/src/spa-serve.test.ts +116 -0
- package/src/spa-serve.ts +116 -0
- package/src/spawn-agent-cli.test.ts +172 -0
- package/src/spawn-agent.test.ts +1218 -0
- package/src/spawn-agent.ts +569 -0
- package/src/spawn-deps.test.ts +54 -0
- package/src/spawn-deps.ts +166 -0
- package/src/telegram/api.ts +153 -0
- package/src/terminal-assets.test.ts +50 -0
- package/src/terminal-assets.ts +79 -0
- package/src/terminal-ui.ts +305 -0
- package/src/terminal.test.ts +530 -0
- package/src/terminal.ts +458 -0
- package/src/transport.ts +270 -0
- package/src/transports/http-ui.test.ts +455 -0
- package/src/transports/http-ui.ts +201 -0
- package/src/transports/telegram.test.ts +174 -0
- package/src/transports/telegram.ts +426 -0
- package/src/transports/vault.test.ts +2011 -0
- package/src/transports/vault.ts +1790 -0
- package/src/ui-kit.test.ts +178 -0
- package/src/ui-kit.ts +402 -0
- package/tsconfig.json +8 -14
- package/web/ui/tsconfig.json +2 -1
- package/.claude/scheduled_tasks.lock +0 -1
- package/.claude/settings.json +0 -5
- package/.claude/skills/add-atomic-chat-tool/SKILL.md +0 -243
- package/.claude/skills/add-atomic-chat-tool/atomic-chat-mcp-stdio.ts +0 -229
- package/.claude/skills/add-codex/SKILL.md +0 -161
- package/.claude/skills/add-dashboard/SKILL.md +0 -138
- package/.claude/skills/add-dashboard/resources/dashboard-pusher.ts +0 -495
- package/.claude/skills/add-emacs/SKILL.md +0 -296
- package/.claude/skills/add-gcal-tool/SKILL.md +0 -210
- package/.claude/skills/add-gchat/REMOVE.md +0 -6
- package/.claude/skills/add-gchat/SKILL.md +0 -92
- package/.claude/skills/add-gchat/VERIFY.md +0 -3
- package/.claude/skills/add-github/REMOVE.md +0 -6
- package/.claude/skills/add-github/SKILL.md +0 -148
- package/.claude/skills/add-github/VERIFY.md +0 -3
- package/.claude/skills/add-gmail-tool/SKILL.md +0 -229
- package/.claude/skills/add-imessage/REMOVE.md +0 -6
- package/.claude/skills/add-imessage/SKILL.md +0 -113
- package/.claude/skills/add-imessage/VERIFY.md +0 -3
- package/.claude/skills/add-karpathy-llm-wiki/SKILL.md +0 -110
- package/.claude/skills/add-karpathy-llm-wiki/llm-wiki.md +0 -75
- package/.claude/skills/add-linear/REMOVE.md +0 -6
- package/.claude/skills/add-linear/SKILL.md +0 -168
- package/.claude/skills/add-linear/VERIFY.md +0 -3
- package/.claude/skills/add-macos-statusbar/SKILL.md +0 -133
- package/.claude/skills/add-macos-statusbar/add/src/statusbar.swift +0 -147
- package/.claude/skills/add-matrix/REMOVE.md +0 -6
- package/.claude/skills/add-matrix/SKILL.md +0 -148
- package/.claude/skills/add-matrix/VERIFY.md +0 -3
- package/.claude/skills/add-ollama-provider/SKILL.md +0 -179
- package/.claude/skills/add-ollama-tool/SKILL.md +0 -193
- package/.claude/skills/add-opencode/SKILL.md +0 -229
- package/.claude/skills/add-parallel/SKILL.md +0 -290
- package/.claude/skills/add-resend/REMOVE.md +0 -6
- package/.claude/skills/add-resend/SKILL.md +0 -93
- package/.claude/skills/add-resend/VERIFY.md +0 -3
- package/.claude/skills/add-signal/REMOVE.md +0 -13
- package/.claude/skills/add-signal/SKILL.md +0 -318
- package/.claude/skills/add-signal/VERIFY.md +0 -5
- package/.claude/skills/add-slack/REMOVE.md +0 -6
- package/.claude/skills/add-slack/SKILL.md +0 -112
- package/.claude/skills/add-slack/VERIFY.md +0 -3
- package/.claude/skills/add-teams/REMOVE.md +0 -6
- package/.claude/skills/add-teams/SKILL.md +0 -207
- package/.claude/skills/add-teams/VERIFY.md +0 -3
- package/.claude/skills/add-vercel/SKILL.md +0 -147
- package/.claude/skills/add-vercel/container-skills/vercel-cli/SKILL.md +0 -103
- package/.claude/skills/add-webex/REMOVE.md +0 -6
- package/.claude/skills/add-webex/SKILL.md +0 -88
- package/.claude/skills/add-webex/VERIFY.md +0 -3
- package/.claude/skills/add-wechat/REMOVE.md +0 -49
- package/.claude/skills/add-wechat/SKILL.md +0 -170
- package/.claude/skills/add-wechat/scripts/wire-dm.ts +0 -172
- package/.claude/skills/add-whatsapp/SKILL.md +0 -264
- package/.claude/skills/add-whatsapp-cloud/REMOVE.md +0 -6
- package/.claude/skills/add-whatsapp-cloud/SKILL.md +0 -95
- package/.claude/skills/add-whatsapp-cloud/VERIFY.md +0 -3
- package/.claude/skills/claw/SKILL.md +0 -131
- package/.claude/skills/claw/scripts/claw +0 -374
- package/.claude/skills/convert-to-apple-container/SKILL.md +0 -212
- package/.claude/skills/customize/SKILL.md +0 -110
- package/.claude/skills/debug/SKILL.md +0 -349
- package/.claude/skills/get-qodo-rules/SKILL.md +0 -122
- package/.claude/skills/get-qodo-rules/references/output-format.md +0 -41
- package/.claude/skills/get-qodo-rules/references/pagination.md +0 -33
- package/.claude/skills/get-qodo-rules/references/repository-scope.md +0 -26
- package/.claude/skills/init-first-agent/SKILL.md +0 -120
- package/.claude/skills/init-onecli/SKILL.md +0 -270
- package/.claude/skills/manage-channels/SKILL.md +0 -87
- package/.claude/skills/manage-mounts/SKILL.md +0 -47
- package/.claude/skills/migrate-from-openclaw/MIGRATE_CRONS.md +0 -100
- package/.claude/skills/migrate-from-openclaw/SKILL.md +0 -447
- package/.claude/skills/migrate-from-openclaw/scripts/discover-openclaw.ts +0 -734
- package/.claude/skills/migrate-from-openclaw/scripts/extract-channel-credentials.ts +0 -476
- package/.claude/skills/migrate-nanoclaw/SKILL.md +0 -484
- package/.claude/skills/migrate-nanoclaw/diagnostics.md +0 -51
- package/.claude/skills/qodo-pr-resolver/SKILL.md +0 -326
- package/.claude/skills/qodo-pr-resolver/resources/providers.md +0 -329
- package/.claude/skills/update-nanoclaw/SKILL.md +0 -243
- package/.claude/skills/update-nanoclaw/diagnostics.md +0 -48
- package/.claude/skills/update-skills/SKILL.md +0 -130
- package/.claude/skills/use-native-credential-proxy/SKILL.md +0 -167
- package/.claude/skills/x-integration/SKILL.md +0 -417
- package/.claude/skills/x-integration/agent.ts +0 -243
- package/.claude/skills/x-integration/host.ts +0 -155
- package/.claude/skills/x-integration/lib/browser.ts +0 -148
- package/.claude/skills/x-integration/lib/config.ts +0 -62
- package/.claude/skills/x-integration/scripts/like.ts +0 -56
- package/.claude/skills/x-integration/scripts/post.ts +0 -66
- package/.claude/skills/x-integration/scripts/quote.ts +0 -80
- package/.claude/skills/x-integration/scripts/reply.ts +0 -74
- package/.claude/skills/x-integration/scripts/retweet.ts +0 -62
- package/.claude/skills/x-integration/scripts/setup.ts +0 -87
- package/.github/CODEOWNERS +0 -10
- package/.github/PULL_REQUEST_TEMPLATE.md +0 -18
- package/.github/workflows/bump-version.yml +0 -35
- package/.github/workflows/ci.yml +0 -39
- package/.github/workflows/label-pr.yml +0 -40
- package/.github/workflows/update-tokens.yml +0 -43
- package/.husky/pre-commit +0 -1
- package/.mcp.json +0 -3
- package/.nvmrc +0 -1
- package/.prettierrc +0 -4
- package/CHANGELOG.md +0 -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
|
@@ -1,269 +0,0 @@
|
|
|
1
|
-
import { describe, it, expect, vi, beforeEach } from 'vitest';
|
|
2
|
-
|
|
3
|
-
// Mock log
|
|
4
|
-
vi.mock('./log.js', () => ({
|
|
5
|
-
log: {
|
|
6
|
-
debug: vi.fn(),
|
|
7
|
-
info: vi.fn(),
|
|
8
|
-
warn: vi.fn(),
|
|
9
|
-
error: vi.fn(),
|
|
10
|
-
fatal: vi.fn(),
|
|
11
|
-
},
|
|
12
|
-
}));
|
|
13
|
-
|
|
14
|
-
// Mock child_process — store the mock fn so tests can configure it
|
|
15
|
-
const mockExecSync = vi.fn();
|
|
16
|
-
vi.mock('child_process', () => ({
|
|
17
|
-
execSync: (...args: unknown[]) => mockExecSync(...args),
|
|
18
|
-
}));
|
|
19
|
-
|
|
20
|
-
import {
|
|
21
|
-
CONTAINER_RUNTIME_BIN,
|
|
22
|
-
readonlyMountArgs,
|
|
23
|
-
stopContainer,
|
|
24
|
-
ensureContainerRuntimeRunning,
|
|
25
|
-
ensureContainerImage,
|
|
26
|
-
cleanupOrphans,
|
|
27
|
-
} from './container-runtime.js';
|
|
28
|
-
import { CONTAINER_IMAGE, CONTAINER_INSTALL_LABEL, LEGACY_PARACLAW_INSTALL_LABEL } from './config.js';
|
|
29
|
-
import { log } from './log.js';
|
|
30
|
-
|
|
31
|
-
beforeEach(() => {
|
|
32
|
-
vi.clearAllMocks();
|
|
33
|
-
});
|
|
34
|
-
|
|
35
|
-
// --- Pure functions ---
|
|
36
|
-
|
|
37
|
-
describe('readonlyMountArgs', () => {
|
|
38
|
-
it('returns -v flag with :ro suffix', () => {
|
|
39
|
-
const args = readonlyMountArgs('/host/path', '/container/path');
|
|
40
|
-
expect(args).toEqual(['-v', '/host/path:/container/path:ro']);
|
|
41
|
-
});
|
|
42
|
-
});
|
|
43
|
-
|
|
44
|
-
describe('stopContainer', () => {
|
|
45
|
-
it('calls docker stop for valid container names', () => {
|
|
46
|
-
stopContainer('parachute-agent-test-123');
|
|
47
|
-
expect(mockExecSync).toHaveBeenCalledWith(`${CONTAINER_RUNTIME_BIN} stop -t 1 parachute-agent-test-123`, {
|
|
48
|
-
stdio: 'pipe',
|
|
49
|
-
});
|
|
50
|
-
});
|
|
51
|
-
|
|
52
|
-
it('rejects names with shell metacharacters', () => {
|
|
53
|
-
expect(() => stopContainer('foo; rm -rf /')).toThrow('Invalid container name');
|
|
54
|
-
expect(() => stopContainer('foo$(whoami)')).toThrow('Invalid container name');
|
|
55
|
-
expect(() => stopContainer('foo`id`')).toThrow('Invalid container name');
|
|
56
|
-
expect(mockExecSync).not.toHaveBeenCalled();
|
|
57
|
-
});
|
|
58
|
-
});
|
|
59
|
-
|
|
60
|
-
// --- ensureContainerRuntimeRunning ---
|
|
61
|
-
|
|
62
|
-
describe('ensureContainerRuntimeRunning', () => {
|
|
63
|
-
it('does nothing when runtime is already running', () => {
|
|
64
|
-
mockExecSync.mockReturnValueOnce('');
|
|
65
|
-
|
|
66
|
-
ensureContainerRuntimeRunning();
|
|
67
|
-
|
|
68
|
-
expect(mockExecSync).toHaveBeenCalledTimes(1);
|
|
69
|
-
expect(mockExecSync).toHaveBeenCalledWith(`${CONTAINER_RUNTIME_BIN} info`, {
|
|
70
|
-
stdio: 'pipe',
|
|
71
|
-
timeout: 10000,
|
|
72
|
-
});
|
|
73
|
-
expect(log.debug).toHaveBeenCalledWith('Container runtime already running');
|
|
74
|
-
});
|
|
75
|
-
|
|
76
|
-
it('throws when docker info fails', () => {
|
|
77
|
-
mockExecSync.mockImplementationOnce(() => {
|
|
78
|
-
throw new Error('Cannot connect to the Docker daemon');
|
|
79
|
-
});
|
|
80
|
-
|
|
81
|
-
expect(() => ensureContainerRuntimeRunning()).toThrow('Container runtime is required but failed to start');
|
|
82
|
-
expect(log.error).toHaveBeenCalled();
|
|
83
|
-
});
|
|
84
|
-
});
|
|
85
|
-
|
|
86
|
-
// --- ensureContainerImage ---
|
|
87
|
-
|
|
88
|
-
describe('ensureContainerImage', () => {
|
|
89
|
-
// Each test enumerates its own queue rather than going through a helper —
|
|
90
|
-
// throwing scenarios skip the retag call, so a generic helper would queue
|
|
91
|
-
// a `mockReturnValueOnce` that never gets consumed and leaks into the next
|
|
92
|
-
// test (vi.clearAllMocks doesn't drain the response queue).
|
|
93
|
-
const inspectFailed = () => {
|
|
94
|
-
mockExecSync.mockImplementationOnce(() => {
|
|
95
|
-
throw new Error('No such image');
|
|
96
|
-
});
|
|
97
|
-
};
|
|
98
|
-
|
|
99
|
-
it('no-ops when the expected image is already present', () => {
|
|
100
|
-
mockExecSync.mockReturnValueOnce('{}'); // inspect — image exists
|
|
101
|
-
|
|
102
|
-
ensureContainerImage();
|
|
103
|
-
|
|
104
|
-
expect(mockExecSync).toHaveBeenCalledTimes(1);
|
|
105
|
-
expect(mockExecSync).toHaveBeenCalledWith(
|
|
106
|
-
`${CONTAINER_RUNTIME_BIN} image inspect ${CONTAINER_IMAGE}`,
|
|
107
|
-
expect.objectContaining({ stdio: 'pipe' }),
|
|
108
|
-
);
|
|
109
|
-
expect(log.warn).not.toHaveBeenCalled();
|
|
110
|
-
});
|
|
111
|
-
|
|
112
|
-
it('retags from a current-prefix peer when the expected image is missing', () => {
|
|
113
|
-
// Operator dir-rename case: the previously-built image carries the old
|
|
114
|
-
// INSTALL_SLUG (cafef00d); the daemon now boots under a new slug.
|
|
115
|
-
const peer = 'parachute-agent-image-cafef00d:latest';
|
|
116
|
-
inspectFailed();
|
|
117
|
-
mockExecSync.mockReturnValueOnce(`${peer}\nnode:24-bookworm-slim\n`); // list
|
|
118
|
-
mockExecSync.mockReturnValueOnce(''); // tag
|
|
119
|
-
|
|
120
|
-
ensureContainerImage();
|
|
121
|
-
|
|
122
|
-
expect(mockExecSync).toHaveBeenCalledTimes(3);
|
|
123
|
-
expect(mockExecSync).toHaveBeenNthCalledWith(
|
|
124
|
-
3,
|
|
125
|
-
`${CONTAINER_RUNTIME_BIN} tag ${peer} ${CONTAINER_IMAGE}`,
|
|
126
|
-
expect.objectContaining({ stdio: 'pipe' }),
|
|
127
|
-
);
|
|
128
|
-
expect(log.warn).toHaveBeenCalledWith(
|
|
129
|
-
'Container image missing for current install slug — retagging from peer',
|
|
130
|
-
expect.objectContaining({ expected: CONTAINER_IMAGE, peer }),
|
|
131
|
-
);
|
|
132
|
-
});
|
|
133
|
-
|
|
134
|
-
it('retags from a pre-0.1.0 paraclaw-agent peer (one cycle of back-compat)', () => {
|
|
135
|
-
// Operator upgrades a pre-0.1.0 install: their on-disk image is named
|
|
136
|
-
// `paraclaw-agent-<slug>:latest`. Auto-retag rather than forcing a
|
|
137
|
-
// 5-minute rebuild they didn't ask for.
|
|
138
|
-
const legacyPeer = 'paraclaw-agent-cafef00d:latest';
|
|
139
|
-
inspectFailed();
|
|
140
|
-
mockExecSync.mockReturnValueOnce(legacyPeer);
|
|
141
|
-
mockExecSync.mockReturnValueOnce('');
|
|
142
|
-
|
|
143
|
-
ensureContainerImage();
|
|
144
|
-
|
|
145
|
-
expect(mockExecSync).toHaveBeenNthCalledWith(
|
|
146
|
-
3,
|
|
147
|
-
`${CONTAINER_RUNTIME_BIN} tag ${legacyPeer} ${CONTAINER_IMAGE}`,
|
|
148
|
-
expect.objectContaining({ stdio: 'pipe' }),
|
|
149
|
-
);
|
|
150
|
-
});
|
|
151
|
-
|
|
152
|
-
it('throws an actionable error when no peer exists (fresh install, no build yet)', () => {
|
|
153
|
-
// No matching prefix on disk: only unrelated base images. Better to fail
|
|
154
|
-
// visibly at startup than crashloop code=125 on every container spawn.
|
|
155
|
-
inspectFailed();
|
|
156
|
-
mockExecSync.mockReturnValueOnce('node:24-bookworm-slim\nubuntu:22.04\n');
|
|
157
|
-
|
|
158
|
-
expect(() => ensureContainerImage()).toThrow(/build\.sh/);
|
|
159
|
-
expect(mockExecSync).toHaveBeenCalledTimes(2); // inspect + list, no tag
|
|
160
|
-
});
|
|
161
|
-
|
|
162
|
-
it('skips the (missing) expected name when picking a peer from the listing', () => {
|
|
163
|
-
// Belt-and-suspenders: the inspect already confirmed the expected name
|
|
164
|
-
// is absent, but the peer-search guard against picking it back up keeps
|
|
165
|
-
// the function safe if a future caller pre-checks a different way.
|
|
166
|
-
inspectFailed();
|
|
167
|
-
mockExecSync.mockReturnValueOnce(`${CONTAINER_IMAGE}\nparachute-agent-image-cafef00d:latest\n`);
|
|
168
|
-
mockExecSync.mockReturnValueOnce('');
|
|
169
|
-
|
|
170
|
-
ensureContainerImage();
|
|
171
|
-
|
|
172
|
-
const tagCall = mockExecSync.mock.calls.find((call) => String(call[0]).startsWith(`${CONTAINER_RUNTIME_BIN} tag`));
|
|
173
|
-
expect(tagCall).toBeDefined();
|
|
174
|
-
expect(String(tagCall![0])).toContain('parachute-agent-image-cafef00d:latest');
|
|
175
|
-
});
|
|
176
|
-
|
|
177
|
-
it('ignores arbitrary non-matching tags (e.g. base images, unrelated projects)', () => {
|
|
178
|
-
inspectFailed();
|
|
179
|
-
mockExecSync.mockReturnValueOnce('node:24-bookworm-slim\nparachute-vault:latest\nrandomthing:v2\n');
|
|
180
|
-
|
|
181
|
-
expect(() => ensureContainerImage()).toThrow(/build\.sh/);
|
|
182
|
-
});
|
|
183
|
-
});
|
|
184
|
-
|
|
185
|
-
// --- cleanupOrphans ---
|
|
186
|
-
|
|
187
|
-
describe('cleanupOrphans', () => {
|
|
188
|
-
it('filters ps by both the new and legacy install labels so peers are not reaped', () => {
|
|
189
|
-
mockExecSync.mockReturnValue('');
|
|
190
|
-
|
|
191
|
-
cleanupOrphans();
|
|
192
|
-
|
|
193
|
-
expect(mockExecSync).toHaveBeenNthCalledWith(
|
|
194
|
-
1,
|
|
195
|
-
`${CONTAINER_RUNTIME_BIN} ps --filter label=${CONTAINER_INSTALL_LABEL} --format '{{.Names}}'`,
|
|
196
|
-
expect.any(Object),
|
|
197
|
-
);
|
|
198
|
-
expect(mockExecSync).toHaveBeenNthCalledWith(
|
|
199
|
-
2,
|
|
200
|
-
`${CONTAINER_RUNTIME_BIN} ps --filter label=${LEGACY_PARACLAW_INSTALL_LABEL} --format '{{.Names}}'`,
|
|
201
|
-
expect.any(Object),
|
|
202
|
-
);
|
|
203
|
-
});
|
|
204
|
-
|
|
205
|
-
it('stops orphaned containers from both labels and de-dupes', () => {
|
|
206
|
-
// First ps (new label) returns one container; second ps (legacy label) returns two —
|
|
207
|
-
// one duplicates the first, simulating a container that carries both labels during
|
|
208
|
-
// upgrade.
|
|
209
|
-
mockExecSync.mockReturnValueOnce('parachute-agent-group1-111\n');
|
|
210
|
-
mockExecSync.mockReturnValueOnce('parachute-agent-group1-111\nparaclaw-group2-222\n');
|
|
211
|
-
mockExecSync.mockReturnValue('');
|
|
212
|
-
|
|
213
|
-
cleanupOrphans();
|
|
214
|
-
|
|
215
|
-
// 2 ps + 2 unique stop calls
|
|
216
|
-
expect(mockExecSync).toHaveBeenCalledTimes(4);
|
|
217
|
-
expect(mockExecSync).toHaveBeenNthCalledWith(3, `${CONTAINER_RUNTIME_BIN} stop -t 1 parachute-agent-group1-111`, {
|
|
218
|
-
stdio: 'pipe',
|
|
219
|
-
});
|
|
220
|
-
expect(mockExecSync).toHaveBeenNthCalledWith(4, `${CONTAINER_RUNTIME_BIN} stop -t 1 paraclaw-group2-222`, {
|
|
221
|
-
stdio: 'pipe',
|
|
222
|
-
});
|
|
223
|
-
expect(log.info).toHaveBeenCalledWith('Stopped orphaned containers', {
|
|
224
|
-
count: 2,
|
|
225
|
-
names: ['parachute-agent-group1-111', 'paraclaw-group2-222'],
|
|
226
|
-
});
|
|
227
|
-
});
|
|
228
|
-
|
|
229
|
-
it('does nothing when no orphans exist on either label', () => {
|
|
230
|
-
mockExecSync.mockReturnValue('');
|
|
231
|
-
|
|
232
|
-
cleanupOrphans();
|
|
233
|
-
|
|
234
|
-
expect(mockExecSync).toHaveBeenCalledTimes(2); // both label queries
|
|
235
|
-
expect(log.info).not.toHaveBeenCalled();
|
|
236
|
-
});
|
|
237
|
-
|
|
238
|
-
it('warns and continues when ps fails', () => {
|
|
239
|
-
mockExecSync.mockImplementationOnce(() => {
|
|
240
|
-
throw new Error('docker not available');
|
|
241
|
-
});
|
|
242
|
-
|
|
243
|
-
cleanupOrphans(); // should not throw
|
|
244
|
-
|
|
245
|
-
expect(log.warn).toHaveBeenCalledWith(
|
|
246
|
-
'Failed to clean up orphaned containers',
|
|
247
|
-
expect.objectContaining({ err: expect.any(Error) }),
|
|
248
|
-
);
|
|
249
|
-
});
|
|
250
|
-
|
|
251
|
-
it('continues stopping remaining containers when one stop fails', () => {
|
|
252
|
-
mockExecSync.mockReturnValueOnce('parachute-agent-a-1\nparachute-agent-b-2\n');
|
|
253
|
-
mockExecSync.mockReturnValueOnce(''); // legacy label query empty
|
|
254
|
-
// First stop fails
|
|
255
|
-
mockExecSync.mockImplementationOnce(() => {
|
|
256
|
-
throw new Error('already stopped');
|
|
257
|
-
});
|
|
258
|
-
// Second stop succeeds
|
|
259
|
-
mockExecSync.mockReturnValueOnce('');
|
|
260
|
-
|
|
261
|
-
cleanupOrphans(); // should not throw
|
|
262
|
-
|
|
263
|
-
expect(mockExecSync).toHaveBeenCalledTimes(4);
|
|
264
|
-
expect(log.info).toHaveBeenCalledWith('Stopped orphaned containers', {
|
|
265
|
-
count: 2,
|
|
266
|
-
names: ['parachute-agent-a-1', 'parachute-agent-b-2'],
|
|
267
|
-
});
|
|
268
|
-
});
|
|
269
|
-
});
|
package/src/container-runtime.ts
DELETED
|
@@ -1,167 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* Container runtime abstraction for parachute-agent.
|
|
3
|
-
* All runtime-specific logic lives here so swapping runtimes means changing one file.
|
|
4
|
-
*/
|
|
5
|
-
import { execSync } from 'child_process';
|
|
6
|
-
import os from 'os';
|
|
7
|
-
|
|
8
|
-
import { CONTAINER_IMAGE, CONTAINER_INSTALL_LABEL, LEGACY_PARACLAW_INSTALL_LABEL } from './config.js';
|
|
9
|
-
import { log } from './log.js';
|
|
10
|
-
|
|
11
|
-
// Per-install image tag schemas:
|
|
12
|
-
// - 0.1.0+: `parachute-agent-image-<8-hex-slug>:latest`
|
|
13
|
-
// - pre-0.1.0: `paraclaw-agent-<8-hex-slug>:latest` (kept for one cycle of
|
|
14
|
-
// back-compat so an operator who upgrades into a 0.1.x checkout
|
|
15
|
-
// without rebuilding the image still gets a working spawn;
|
|
16
|
-
// drop in 0.2.0 — same lifecycle as LEGACY_PARACLAW_INSTALL_LABEL).
|
|
17
|
-
// Both prefixes are stable + content-equivalent — Dockerfile baseline
|
|
18
|
-
// matches across slugs — so a `docker tag` of any peer is safe.
|
|
19
|
-
const PEER_IMAGE_PATTERN = /^(parachute-agent-image|paraclaw-agent)-[0-9a-f]{8}:latest$/;
|
|
20
|
-
|
|
21
|
-
/** The container runtime binary name. */
|
|
22
|
-
export const CONTAINER_RUNTIME_BIN = 'docker';
|
|
23
|
-
|
|
24
|
-
/** CLI args needed for the container to resolve the host gateway. */
|
|
25
|
-
export function hostGatewayArgs(): string[] {
|
|
26
|
-
// On Linux, host.docker.internal isn't built-in — add it explicitly
|
|
27
|
-
if (os.platform() === 'linux') {
|
|
28
|
-
return ['--add-host=host.docker.internal:host-gateway'];
|
|
29
|
-
}
|
|
30
|
-
return [];
|
|
31
|
-
}
|
|
32
|
-
|
|
33
|
-
/** Returns CLI args for a readonly bind mount. */
|
|
34
|
-
export function readonlyMountArgs(hostPath: string, containerPath: string): string[] {
|
|
35
|
-
return ['-v', `${hostPath}:${containerPath}:ro`];
|
|
36
|
-
}
|
|
37
|
-
|
|
38
|
-
/** Stop a container by name. Uses execFileSync to avoid shell injection. */
|
|
39
|
-
export function stopContainer(name: string): void {
|
|
40
|
-
if (!/^[a-zA-Z0-9][a-zA-Z0-9_.-]*$/.test(name)) {
|
|
41
|
-
throw new Error(`Invalid container name: ${name}`);
|
|
42
|
-
}
|
|
43
|
-
execSync(`${CONTAINER_RUNTIME_BIN} stop -t 1 ${name}`, { stdio: 'pipe' });
|
|
44
|
-
}
|
|
45
|
-
|
|
46
|
-
/** Ensure the container runtime is running, starting it if needed. */
|
|
47
|
-
export function ensureContainerRuntimeRunning(): void {
|
|
48
|
-
try {
|
|
49
|
-
execSync(`${CONTAINER_RUNTIME_BIN} info`, {
|
|
50
|
-
stdio: 'pipe',
|
|
51
|
-
timeout: 10000,
|
|
52
|
-
});
|
|
53
|
-
log.debug('Container runtime already running');
|
|
54
|
-
} catch (err) {
|
|
55
|
-
log.error('Failed to reach container runtime', { err });
|
|
56
|
-
console.error('\n╔════════════════════════════════════════════════════════════════╗');
|
|
57
|
-
console.error('║ FATAL: Container runtime failed to start ║');
|
|
58
|
-
console.error('║ ║');
|
|
59
|
-
console.error('║ Agents cannot run without a container runtime. To fix: ║');
|
|
60
|
-
console.error('║ 1. Ensure Docker is installed and running ║');
|
|
61
|
-
console.error('║ 2. Run: docker info ║');
|
|
62
|
-
console.error('║ 3. Restart parachute-agent ║');
|
|
63
|
-
console.error('╚════════════════════════════════════════════════════════════════╝\n');
|
|
64
|
-
throw new Error('Container runtime is required but failed to start', {
|
|
65
|
-
cause: err,
|
|
66
|
-
});
|
|
67
|
-
}
|
|
68
|
-
}
|
|
69
|
-
|
|
70
|
-
/**
|
|
71
|
-
* Ensure the per-install container image is reachable before we start
|
|
72
|
-
* spawning sessions.
|
|
73
|
-
*
|
|
74
|
-
* INSTALL_SLUG = sha1(process.cwd())[:8], so an operator dir-rename
|
|
75
|
-
* (paraclaw#114) flips the slug. The previously-built image carries the
|
|
76
|
-
* OLD slug; the daemon goes to spawn against the NEW slug; `docker run`
|
|
77
|
-
* returns code=125 ("image not found") and every container spawn
|
|
78
|
-
* crashloops silently.
|
|
79
|
-
*
|
|
80
|
-
* Resolution path, ordered fail-fast → cheap → loud:
|
|
81
|
-
* 1. Expected tag present → no-op.
|
|
82
|
-
* 2. Any peer image (`parachute-agent-image-*` or pre-0.1.0
|
|
83
|
-
* `paraclaw-agent-*`) present → `docker tag` it to the expected
|
|
84
|
-
* name. Safe because the Dockerfile baseline doesn't fork per slug.
|
|
85
|
-
* 3. No peer found → throw with an actionable hint. The daemon was
|
|
86
|
-
* going to crashloop anyway; failing visibly at startup is strictly
|
|
87
|
-
* better than silent code=125 on every Telegram message.
|
|
88
|
-
*/
|
|
89
|
-
export function ensureContainerImage(): void {
|
|
90
|
-
if (imageExists(CONTAINER_IMAGE)) {
|
|
91
|
-
log.debug('Container image present', { image: CONTAINER_IMAGE });
|
|
92
|
-
return;
|
|
93
|
-
}
|
|
94
|
-
const peer = findPeerImage(CONTAINER_IMAGE);
|
|
95
|
-
if (peer) {
|
|
96
|
-
// The dir-rename / upgrade case. Loud-warn so the operator can see in
|
|
97
|
-
// the log what happened — silent retags become folklore.
|
|
98
|
-
log.warn('Container image missing for current install slug — retagging from peer', {
|
|
99
|
-
expected: CONTAINER_IMAGE,
|
|
100
|
-
peer,
|
|
101
|
-
hint: 'Operator dir-rename or upgrade likely changed INSTALL_SLUG. Auto-retagging is safe; rebuild via ./container/build.sh next time you want to refresh dependencies.',
|
|
102
|
-
});
|
|
103
|
-
execSync(`${CONTAINER_RUNTIME_BIN} tag ${peer} ${CONTAINER_IMAGE}`, { stdio: 'pipe' });
|
|
104
|
-
return;
|
|
105
|
-
}
|
|
106
|
-
throw new Error(
|
|
107
|
-
`No parachute-agent container image found. Build one with: ./container/build.sh\n` +
|
|
108
|
-
`Expected image: ${CONTAINER_IMAGE}`,
|
|
109
|
-
);
|
|
110
|
-
}
|
|
111
|
-
|
|
112
|
-
function imageExists(ref: string): boolean {
|
|
113
|
-
try {
|
|
114
|
-
execSync(`${CONTAINER_RUNTIME_BIN} image inspect ${ref}`, { stdio: 'pipe' });
|
|
115
|
-
return true;
|
|
116
|
-
} catch {
|
|
117
|
-
return false;
|
|
118
|
-
}
|
|
119
|
-
}
|
|
120
|
-
|
|
121
|
-
function findPeerImage(exclude: string): string | null {
|
|
122
|
-
const output = execSync(`${CONTAINER_RUNTIME_BIN} images --format '{{.Repository}}:{{.Tag}}'`, {
|
|
123
|
-
stdio: ['pipe', 'pipe', 'pipe'],
|
|
124
|
-
encoding: 'utf-8',
|
|
125
|
-
});
|
|
126
|
-
// `docker images` lists newest-created first by default. Take the first
|
|
127
|
-
// matching peer so a recent rebuild wins over a stale legacy tag.
|
|
128
|
-
for (const ref of output.trim().split('\n').filter(Boolean)) {
|
|
129
|
-
if (ref === exclude) continue;
|
|
130
|
-
if (PEER_IMAGE_PATTERN.test(ref)) return ref;
|
|
131
|
-
}
|
|
132
|
-
return null;
|
|
133
|
-
}
|
|
134
|
-
|
|
135
|
-
/**
|
|
136
|
-
* Kill orphaned parachute-agent containers from THIS install's previous runs.
|
|
137
|
-
*
|
|
138
|
-
* Scoped by the `parachute-agent-install=<slug>` label (and the pre-0.1.0
|
|
139
|
-
* `paraclaw-install=<slug>` label for one upgrade cycle) so a crash-looping
|
|
140
|
-
* peer install cannot reap our containers, and we cannot reap theirs. The
|
|
141
|
-
* label is stamped onto every container at spawn time — see
|
|
142
|
-
* container-runner.ts. Old-label compat reap is queued to drop in 0.2.0.
|
|
143
|
-
*/
|
|
144
|
-
export function cleanupOrphans(): void {
|
|
145
|
-
try {
|
|
146
|
-
const namesByLabel = [CONTAINER_INSTALL_LABEL, LEGACY_PARACLAW_INSTALL_LABEL].flatMap((label) => {
|
|
147
|
-
const output = execSync(`${CONTAINER_RUNTIME_BIN} ps --filter label=${label} --format '{{.Names}}'`, {
|
|
148
|
-
stdio: ['pipe', 'pipe', 'pipe'],
|
|
149
|
-
encoding: 'utf-8',
|
|
150
|
-
});
|
|
151
|
-
return output.trim().split('\n').filter(Boolean);
|
|
152
|
-
});
|
|
153
|
-
const orphans = Array.from(new Set(namesByLabel));
|
|
154
|
-
for (const name of orphans) {
|
|
155
|
-
try {
|
|
156
|
-
stopContainer(name);
|
|
157
|
-
} catch {
|
|
158
|
-
/* already stopped */
|
|
159
|
-
}
|
|
160
|
-
}
|
|
161
|
-
if (orphans.length > 0) {
|
|
162
|
-
log.info('Stopped orphaned containers', { count: orphans.length, names: orphans });
|
|
163
|
-
}
|
|
164
|
-
} catch (err) {
|
|
165
|
-
log.warn('Failed to clean up orphaned containers', { err });
|
|
166
|
-
}
|
|
167
|
-
}
|
|
@@ -1,88 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* Test-only shim that re-exports the `bun:sqlite` surface backed by
|
|
3
|
-
* `better-sqlite3`. Wired in via vitest's `resolve.alias` so the host code
|
|
4
|
-
* (which imports `bun:sqlite`) can be exercised under Node + vitest without
|
|
5
|
-
* a real bun runtime.
|
|
6
|
-
*
|
|
7
|
-
* Production code runs under Bun and sees the real `bun:sqlite` module —
|
|
8
|
-
* never this shim. Symmetric inverse of the wrapper in connection.ts:
|
|
9
|
-
* connection.ts prefixes object keys with `@` for bun's binder; this shim
|
|
10
|
-
* strips that prefix so better-sqlite3's binder is happy.
|
|
11
|
-
*/
|
|
12
|
-
import BetterSqlite3 from 'better-sqlite3';
|
|
13
|
-
|
|
14
|
-
type Bindable = unknown;
|
|
15
|
-
|
|
16
|
-
function stripPrefix(key: string): string {
|
|
17
|
-
return key.startsWith('@') || key.startsWith('$') || key.startsWith(':') ? key.slice(1) : key;
|
|
18
|
-
}
|
|
19
|
-
|
|
20
|
-
function adaptArgs(args: Bindable[]): Bindable[] {
|
|
21
|
-
return args.map((a) => {
|
|
22
|
-
if (a == null) return a;
|
|
23
|
-
if (Array.isArray(a)) return a;
|
|
24
|
-
if (typeof a !== 'object') return a;
|
|
25
|
-
const out: Record<string, unknown> = {};
|
|
26
|
-
for (const [k, v] of Object.entries(a as Record<string, unknown>)) {
|
|
27
|
-
out[stripPrefix(k)] = v;
|
|
28
|
-
}
|
|
29
|
-
return out;
|
|
30
|
-
});
|
|
31
|
-
}
|
|
32
|
-
|
|
33
|
-
class ShimStatement {
|
|
34
|
-
constructor(private readonly stmt: BetterSqlite3.Statement) {}
|
|
35
|
-
run(...args: Bindable[]): { changes: number; lastInsertRowid: number | bigint } {
|
|
36
|
-
const r = this.stmt.run(...(adaptArgs(args) as never[]));
|
|
37
|
-
return { changes: r.changes, lastInsertRowid: r.lastInsertRowid };
|
|
38
|
-
}
|
|
39
|
-
get<T = unknown>(...args: Bindable[]): T | null {
|
|
40
|
-
const r = this.stmt.get(...(adaptArgs(args) as never[]));
|
|
41
|
-
return (r ?? null) as T | null;
|
|
42
|
-
}
|
|
43
|
-
all<T = unknown>(...args: Bindable[]): T[] {
|
|
44
|
-
return this.stmt.all(...(adaptArgs(args) as never[])) as T[];
|
|
45
|
-
}
|
|
46
|
-
values(...args: Bindable[]): unknown[][] {
|
|
47
|
-
return this.stmt.raw().all(...(adaptArgs(args) as never[])) as unknown[][];
|
|
48
|
-
}
|
|
49
|
-
iterate<T = unknown>(...args: Bindable[]): IterableIterator<T> {
|
|
50
|
-
return this.stmt.iterate(...(adaptArgs(args) as never[])) as IterableIterator<T>;
|
|
51
|
-
}
|
|
52
|
-
finalize(): void {
|
|
53
|
-
/* no-op — better-sqlite3 finalizes on GC */
|
|
54
|
-
}
|
|
55
|
-
toString(): string {
|
|
56
|
-
return this.stmt.source;
|
|
57
|
-
}
|
|
58
|
-
}
|
|
59
|
-
|
|
60
|
-
export class Database {
|
|
61
|
-
public readonly raw: BetterSqlite3.Database;
|
|
62
|
-
constructor(path: string, opts?: { readonly?: boolean }) {
|
|
63
|
-
this.raw = new BetterSqlite3(path, opts);
|
|
64
|
-
}
|
|
65
|
-
prepare(sql: string): ShimStatement {
|
|
66
|
-
return new ShimStatement(this.raw.prepare(sql));
|
|
67
|
-
}
|
|
68
|
-
exec(sql: string): void {
|
|
69
|
-
this.raw.exec(sql);
|
|
70
|
-
}
|
|
71
|
-
query(sql: string): ShimStatement {
|
|
72
|
-
return this.prepare(sql);
|
|
73
|
-
}
|
|
74
|
-
run(sql: string): void {
|
|
75
|
-
this.raw.exec(sql);
|
|
76
|
-
}
|
|
77
|
-
transaction<F extends (...a: never[]) => unknown>(fn: F): F {
|
|
78
|
-
return this.raw.transaction(fn) as unknown as F;
|
|
79
|
-
}
|
|
80
|
-
close(): void {
|
|
81
|
-
this.raw.close();
|
|
82
|
-
}
|
|
83
|
-
get filename(): string {
|
|
84
|
-
return this.raw.name;
|
|
85
|
-
}
|
|
86
|
-
}
|
|
87
|
-
|
|
88
|
-
export type Statement = ShimStatement;
|
|
@@ -1,155 +0,0 @@
|
|
|
1
|
-
import { afterEach, beforeEach, describe, expect, it } from 'vitest';
|
|
2
|
-
|
|
3
|
-
import { createAgentGroup, createSession, closeDb, getDb, initTestDb, runMigrations } from './index.js';
|
|
4
|
-
import {
|
|
5
|
-
getActivitySyncedSeq,
|
|
6
|
-
listActivityByAgentGroup,
|
|
7
|
-
listActivityBySession,
|
|
8
|
-
mergeActivityBatch,
|
|
9
|
-
} from './agent-activity.js';
|
|
10
|
-
import type { OutboundActivityRow } from './session-db.js';
|
|
11
|
-
|
|
12
|
-
function now(): string {
|
|
13
|
-
return new Date().toISOString();
|
|
14
|
-
}
|
|
15
|
-
|
|
16
|
-
function seedAgentAndSession(): { agentGroupId: string; sessionId: string } {
|
|
17
|
-
createAgentGroup({
|
|
18
|
-
id: 'ag-1',
|
|
19
|
-
name: 'Test Agent',
|
|
20
|
-
folder: 'test-agent',
|
|
21
|
-
agent_provider: null,
|
|
22
|
-
created_at: now(),
|
|
23
|
-
});
|
|
24
|
-
createSession({
|
|
25
|
-
id: 'sess-1',
|
|
26
|
-
agent_group_id: 'ag-1',
|
|
27
|
-
messaging_group_id: null,
|
|
28
|
-
thread_id: null,
|
|
29
|
-
agent_provider: null,
|
|
30
|
-
status: 'active',
|
|
31
|
-
container_status: 'running',
|
|
32
|
-
last_active: now(),
|
|
33
|
-
created_at: now(),
|
|
34
|
-
});
|
|
35
|
-
return { agentGroupId: 'ag-1', sessionId: 'sess-1' };
|
|
36
|
-
}
|
|
37
|
-
|
|
38
|
-
function row(seq: number, kind: string, target: string | null, summary: string | null = null): OutboundActivityRow {
|
|
39
|
-
return { seq, ts: new Date(2026, 0, seq).toISOString(), kind, target, summary };
|
|
40
|
-
}
|
|
41
|
-
|
|
42
|
-
beforeEach(() => {
|
|
43
|
-
const db = initTestDb();
|
|
44
|
-
runMigrations(db);
|
|
45
|
-
});
|
|
46
|
-
|
|
47
|
-
afterEach(() => {
|
|
48
|
-
closeDb();
|
|
49
|
-
});
|
|
50
|
-
|
|
51
|
-
describe('agent_activity merge', () => {
|
|
52
|
-
it('initial cursor is 0 and merging a batch advances it', () => {
|
|
53
|
-
const { agentGroupId, sessionId } = seedAgentAndSession();
|
|
54
|
-
expect(getActivitySyncedSeq(sessionId)).toBe(0);
|
|
55
|
-
|
|
56
|
-
const newCursor = mergeActivityBatch(agentGroupId, sessionId, [
|
|
57
|
-
row(1, 'tool_call', 'Read'),
|
|
58
|
-
row(2, 'cmd_exec', 'Bash'),
|
|
59
|
-
row(3, 'mcp_call', 'mcp__parachute_agent__schedule_task'),
|
|
60
|
-
]);
|
|
61
|
-
expect(newCursor).toBe(3);
|
|
62
|
-
expect(getActivitySyncedSeq(sessionId)).toBe(3);
|
|
63
|
-
|
|
64
|
-
const rows = listActivityBySession(sessionId);
|
|
65
|
-
expect(rows).toHaveLength(3);
|
|
66
|
-
expect(rows.map((r) => r.kind).sort()).toEqual(['cmd_exec', 'mcp_call', 'tool_call']);
|
|
67
|
-
});
|
|
68
|
-
|
|
69
|
-
it('empty batch is a no-op and returns the existing cursor', () => {
|
|
70
|
-
const { agentGroupId, sessionId } = seedAgentAndSession();
|
|
71
|
-
mergeActivityBatch(agentGroupId, sessionId, [row(1, 'tool_call', 'Read')]);
|
|
72
|
-
expect(getActivitySyncedSeq(sessionId)).toBe(1);
|
|
73
|
-
|
|
74
|
-
const c = mergeActivityBatch(agentGroupId, sessionId, []);
|
|
75
|
-
expect(c).toBe(1);
|
|
76
|
-
expect(listActivityBySession(sessionId)).toHaveLength(1);
|
|
77
|
-
});
|
|
78
|
-
|
|
79
|
-
it('cursor advance is monotonic — re-merging an older batch leaves it unchanged', () => {
|
|
80
|
-
const { agentGroupId, sessionId } = seedAgentAndSession();
|
|
81
|
-
mergeActivityBatch(agentGroupId, sessionId, [row(5, 'tool_call', 'Read'), row(6, 'tool_call', 'Glob')]);
|
|
82
|
-
expect(getActivitySyncedSeq(sessionId)).toBe(6);
|
|
83
|
-
|
|
84
|
-
// The delivery loop guards against this with `seq > cursor`, but if a
|
|
85
|
-
// caller passes older rows, the cursor should NOT regress.
|
|
86
|
-
mergeActivityBatch(agentGroupId, sessionId, [row(2, 'tool_call', 'Read')]);
|
|
87
|
-
expect(getActivitySyncedSeq(sessionId)).toBe(6);
|
|
88
|
-
});
|
|
89
|
-
|
|
90
|
-
it('listActivityByAgentGroup returns rows for a single group, newest first', () => {
|
|
91
|
-
const { agentGroupId, sessionId } = seedAgentAndSession();
|
|
92
|
-
createAgentGroup({
|
|
93
|
-
id: 'ag-2',
|
|
94
|
-
name: 'Other',
|
|
95
|
-
folder: 'other',
|
|
96
|
-
agent_provider: null,
|
|
97
|
-
created_at: now(),
|
|
98
|
-
});
|
|
99
|
-
createSession({
|
|
100
|
-
id: 'sess-2',
|
|
101
|
-
agent_group_id: 'ag-2',
|
|
102
|
-
messaging_group_id: null,
|
|
103
|
-
thread_id: null,
|
|
104
|
-
agent_provider: null,
|
|
105
|
-
status: 'active',
|
|
106
|
-
container_status: 'running',
|
|
107
|
-
last_active: now(),
|
|
108
|
-
created_at: now(),
|
|
109
|
-
});
|
|
110
|
-
|
|
111
|
-
mergeActivityBatch(agentGroupId, sessionId, [row(1, 'tool_call', 'Read'), row(2, 'cmd_exec', 'Bash')]);
|
|
112
|
-
mergeActivityBatch('ag-2', 'sess-2', [row(1, 'tool_call', 'Glob')]);
|
|
113
|
-
|
|
114
|
-
const ag1 = listActivityByAgentGroup(agentGroupId);
|
|
115
|
-
expect(ag1).toHaveLength(2);
|
|
116
|
-
expect(ag1.every((r) => r.agent_group_id === agentGroupId)).toBe(true);
|
|
117
|
-
// DESC by created_at — row(2) was minted later (Jan 2 > Jan 1).
|
|
118
|
-
expect(ag1[0].target).toBe('Bash');
|
|
119
|
-
expect(ag1[1].target).toBe('Read');
|
|
120
|
-
|
|
121
|
-
const ag2 = listActivityByAgentGroup('ag-2');
|
|
122
|
-
expect(ag2).toHaveLength(1);
|
|
123
|
-
expect(ag2[0].target).toBe('Glob');
|
|
124
|
-
});
|
|
125
|
-
|
|
126
|
-
it('honors `since` and `limit`', () => {
|
|
127
|
-
const { agentGroupId, sessionId } = seedAgentAndSession();
|
|
128
|
-
mergeActivityBatch(agentGroupId, sessionId, [
|
|
129
|
-
row(1, 'tool_call', 'A'),
|
|
130
|
-
row(2, 'tool_call', 'B'),
|
|
131
|
-
row(3, 'tool_call', 'C'),
|
|
132
|
-
row(4, 'tool_call', 'D'),
|
|
133
|
-
]);
|
|
134
|
-
|
|
135
|
-
const limited = listActivityBySession(sessionId, { limit: 2 });
|
|
136
|
-
expect(limited).toHaveLength(2);
|
|
137
|
-
expect(limited[0].target).toBe('D'); // newest first
|
|
138
|
-
expect(limited[1].target).toBe('C');
|
|
139
|
-
|
|
140
|
-
// since = ts of seq=2 (Jan 2). Should include C (Jan 3) and D (Jan 4).
|
|
141
|
-
const since = new Date(2026, 0, 2).toISOString();
|
|
142
|
-
const sinceRows = listActivityBySession(sessionId, { since });
|
|
143
|
-
expect(sinceRows.map((r) => r.target)).toEqual(['D', 'C']);
|
|
144
|
-
});
|
|
145
|
-
|
|
146
|
-
it('cascades on session delete', () => {
|
|
147
|
-
const { agentGroupId, sessionId } = seedAgentAndSession();
|
|
148
|
-
mergeActivityBatch(agentGroupId, sessionId, [row(1, 'tool_call', 'Read')]);
|
|
149
|
-
expect(listActivityBySession(sessionId)).toHaveLength(1);
|
|
150
|
-
|
|
151
|
-
getDb().prepare('DELETE FROM sessions WHERE id = ?').run(sessionId);
|
|
152
|
-
expect(listActivityBySession(sessionId)).toHaveLength(0);
|
|
153
|
-
expect(listActivityByAgentGroup(agentGroupId)).toHaveLength(0);
|
|
154
|
-
});
|
|
155
|
-
});
|