@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
|
@@ -0,0 +1,372 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The ATTACHED-backend queue registry (design 2026-06-18-channel-backend.md, phase 1).
|
|
3
|
+
* (The backend VALUE was named `"channel"` before the rename; the ROUTING KEY `channel`
|
|
4
|
+
* — the agent's address / the `/mcp/<channel>` segment — is a separate concept and KEEPS
|
|
5
|
+
* its name, so `channel` still appears throughout as the routing-key identifier.)
|
|
6
|
+
*
|
|
7
|
+
* The PARALLEL to {@link ProgrammaticAgentRegistry}, NOT a reuse of it. A
|
|
8
|
+
* `backend: "attached"` agent runs NO `claude -p` and has NO drain worker: the turn
|
|
9
|
+
* is handled by a Claude Code session the OPERATOR runs and connects ("attaches") to the
|
|
10
|
+
* channel's MCP endpoint. The inbound `#agent/message/inbound` notes themselves ARE the
|
|
11
|
+
* queue (the vault is the queue + the source of truth), and their claim `status`
|
|
12
|
+
* (`pending | in-flight | handled`) lives on the note, so a claim survives a daemon
|
|
13
|
+
* restart and a handled message is never re-presented.
|
|
14
|
+
*
|
|
15
|
+
* ── Why a separate registry (the daemon routing fork) ────────────────────────────
|
|
16
|
+
* The programmatic registry's drain worker reads `deliver()`'s `reply` synchronously
|
|
17
|
+
* and OWNS the outbound write. An attached agent has no synchronous turn and its
|
|
18
|
+
* outbound is written by the MCP `reply` tool — reusing that worker would double-write
|
|
19
|
+
* (worker + tool) or drop the reply (worker sees an empty `deliver`). So the fork is
|
|
20
|
+
* at the daemon ROUTER: inbound for an `attached` agent routes HERE and is NOT enqueued
|
|
21
|
+
* to the programmatic worker. This registry exposes only queue operations the MCP
|
|
22
|
+
* surface calls — there is no in-process `deliver`-produces-reply.
|
|
23
|
+
*
|
|
24
|
+
* ── The queue operations (called by the channel MCP surface, phase 2) ────────────
|
|
25
|
+
* - `pending(channel)` → count + a peek (ids/previews) of `status:pending` inbound.
|
|
26
|
+
* - `claimNext(channel)` → oldest `pending` → set `in-flight` + `claimedAt`; returns
|
|
27
|
+
* { id, text, inReplyTo, systemPrompt }. Single-claim (two
|
|
28
|
+
* sessions don't double-handle). null when none pending.
|
|
29
|
+
* - `reply(channel, …)` → write the outbound note via the SAME vault-transport
|
|
30
|
+
* `reply()` the programmatic worker uses (durable, threads,
|
|
31
|
+
* shows in chat UI, tagged outbound so it can't re-trigger
|
|
32
|
+
* the inbound webhook), THEN set the inbound `handled`.
|
|
33
|
+
* - `release(channel,id)`→ `in-flight` → `pending` (the session is giving up).
|
|
34
|
+
* - `sweepExpired(now)` → `in-flight` notes claimed > TTL ago → `pending` (so a
|
|
35
|
+
* crashed session can't strand the queue). Wired into the
|
|
36
|
+
* daemon's periodic tick.
|
|
37
|
+
*
|
|
38
|
+
* CARDINALITY: one channel : one agent (the channel IS the agent's conduit). The
|
|
39
|
+
* surface deliberately doesn't bake "channel == agent" so deep that adding an optional
|
|
40
|
+
* agent filter to `claimNext` later would be a breaking change — the operations key on
|
|
41
|
+
* the channel name only, and the per-channel record carries the agent's spec.
|
|
42
|
+
*/
|
|
43
|
+
|
|
44
|
+
import type { AgentSpec } from "../sandbox/types.ts";
|
|
45
|
+
import { InboundClaimConflictError } from "../transports/vault.ts";
|
|
46
|
+
import type { InboundQueueNote, InboundStatus } from "../transports/vault.ts";
|
|
47
|
+
|
|
48
|
+
/**
|
|
49
|
+
* The storage seam the registry operates a channel's queue through — the durable
|
|
50
|
+
* inbound-note store (the daemon wires this to the channel's VaultTransport; tests
|
|
51
|
+
* inject a fake). Mirrors the vault-transport methods 1:1 so the seam is thin.
|
|
52
|
+
*/
|
|
53
|
+
export interface AttachedQueueStore {
|
|
54
|
+
/** List this channel's inbound queue notes, ascending by ts (oldest first). */
|
|
55
|
+
listInboundQueue(opts?: { limit?: number }): Promise<InboundQueueNote[]>;
|
|
56
|
+
/**
|
|
57
|
+
* Set an inbound note's claim status (+ optionally claimedAt; `null` clears it).
|
|
58
|
+
* When `ifUpdatedAt` is given, the write is a COMPARE-AND-SWAP (the claim only lands
|
|
59
|
+
* if the note hasn't changed since it was read) and throws {@link
|
|
60
|
+
* InboundClaimConflictError} when the race is lost (agent#101); omitting it is the
|
|
61
|
+
* prior last-write-wins behavior (release / handled / sweep).
|
|
62
|
+
*/
|
|
63
|
+
setInboundStatus(
|
|
64
|
+
id: string,
|
|
65
|
+
status: InboundStatus,
|
|
66
|
+
claimedAt?: string | null,
|
|
67
|
+
ifUpdatedAt?: string,
|
|
68
|
+
): Promise<void>;
|
|
69
|
+
/** Write an outbound reply (the SAME `#agent/message/outbound` path the worker uses). */
|
|
70
|
+
reply(args: { text: string; inReplyTo?: string }): Promise<{ sent: string[] }>;
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
/** Bound on the CAS re-list retries in {@link AttachedQueueRegistry.claimNext} — a
|
|
74
|
+
* safety net against a pathological all-contended queue (each pass claims/eliminates
|
|
75
|
+
* one note, so the loop is naturally bounded by the pending count anyway). */
|
|
76
|
+
const MAX_CLAIM_ATTEMPTS = 25;
|
|
77
|
+
|
|
78
|
+
/** The default in-flight claim TTL (design: 15 min comfortably covers an operator turn). */
|
|
79
|
+
export const DEFAULT_CLAIM_TTL_MS = 15 * 60 * 1000;
|
|
80
|
+
|
|
81
|
+
/** A peek at the pending queue — count + a bounded preview of the waiting items. */
|
|
82
|
+
export interface PendingView {
|
|
83
|
+
/** How many inbound messages are `pending` (unclaimed) on this channel. */
|
|
84
|
+
count: number;
|
|
85
|
+
/** A bounded preview (oldest-first) for "you have N messages waiting" affordances. */
|
|
86
|
+
items: Array<{ id: string; preview: string }>;
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
/** The claimed message `claimNext` returns — everything a session needs to be the agent. */
|
|
90
|
+
export interface ClaimedMessage {
|
|
91
|
+
/** The inbound note id — pass it back as `inReplyTo` on `reply`. */
|
|
92
|
+
id: string;
|
|
93
|
+
/** The message text to work on. */
|
|
94
|
+
text: string;
|
|
95
|
+
/** The note id this turn threads to (== `id`, surfaced explicitly for the reply call). */
|
|
96
|
+
inReplyTo: string;
|
|
97
|
+
/**
|
|
98
|
+
* The agent's system prompt (the `#agent/definition` body) — the session adopts the
|
|
99
|
+
* persona by treating this as its instructions for the reply. Adopting it is the
|
|
100
|
+
* SESSION's responsibility (MCP can't force a system prompt on the caller); the MCP
|
|
101
|
+
* server INSTRUCTIONS reinforce the convention. Empty string when the def has none.
|
|
102
|
+
*/
|
|
103
|
+
systemPrompt: string;
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
/** One registered attached-backend agent: its spec + the store its queue lives in. */
|
|
107
|
+
interface AttachedRecord {
|
|
108
|
+
/** The agent slug (the spec name) == the wake channel (agent ≡ channel). */
|
|
109
|
+
name: string;
|
|
110
|
+
/** The channel the queue + MCP surface key on. */
|
|
111
|
+
channel: string;
|
|
112
|
+
/** The spec — carries the systemPrompt the session adopts on `next-message`. */
|
|
113
|
+
spec: AgentSpec;
|
|
114
|
+
/** The durable inbound-note store (the channel's VaultTransport, in production). */
|
|
115
|
+
store: AttachedQueueStore;
|
|
116
|
+
}
|
|
117
|
+
|
|
118
|
+
/** How many pending items the `pending` peek returns at most (a nudge, not a dump). */
|
|
119
|
+
const PENDING_PEEK_CAP = 20;
|
|
120
|
+
/** How long a pending preview snippet is (characters). */
|
|
121
|
+
const PREVIEW_LEN = 120;
|
|
122
|
+
|
|
123
|
+
/**
|
|
124
|
+
* The daemon's registry of ATTACHED-backend agents + their durable queues. Keyed by
|
|
125
|
+
* CHANNEL (the inbound-routing index + the MCP-surface lookup are both O(1)). One
|
|
126
|
+
* instance per daemon, constructed at boot; the store is injected per-agent so tests
|
|
127
|
+
* drive it with a fake store, no real vault.
|
|
128
|
+
*/
|
|
129
|
+
export class AttachedQueueRegistry {
|
|
130
|
+
/** channel → record. */
|
|
131
|
+
private readonly byChannel = new Map<string, AttachedRecord>();
|
|
132
|
+
/** name → channel (the lifecycle index; an agent has exactly one channel). */
|
|
133
|
+
private readonly nameToChannel = new Map<string, string>();
|
|
134
|
+
private readonly claimTtlMs: number;
|
|
135
|
+
|
|
136
|
+
constructor(opts?: { claimTtlMs?: number }) {
|
|
137
|
+
this.claimTtlMs = opts?.claimTtlMs ?? DEFAULT_CLAIM_TTL_MS;
|
|
138
|
+
}
|
|
139
|
+
|
|
140
|
+
/**
|
|
141
|
+
* Register (or replace) a attached-backend agent. Lightweight: index the record by
|
|
142
|
+
* channel + name. Idempotent-replace by name (a reload / boot re-register swaps the
|
|
143
|
+
* spec + store in place). Throws if the spec declares no channel.
|
|
144
|
+
*/
|
|
145
|
+
register(spec: AgentSpec, store: AttachedQueueStore): void {
|
|
146
|
+
if (spec.channels.length === 0) {
|
|
147
|
+
throw new Error(`attached-queue registry: spec "${spec.name}" declares no channels`);
|
|
148
|
+
}
|
|
149
|
+
const channel = channelOf(spec);
|
|
150
|
+
// If the name moved channels (rare), drop the stale channel index.
|
|
151
|
+
const priorChannel = this.nameToChannel.get(spec.name);
|
|
152
|
+
if (priorChannel !== undefined && priorChannel !== channel) {
|
|
153
|
+
this.byChannel.delete(priorChannel);
|
|
154
|
+
}
|
|
155
|
+
this.byChannel.set(channel, { name: spec.name, channel, spec, store });
|
|
156
|
+
this.nameToChannel.set(spec.name, channel);
|
|
157
|
+
}
|
|
158
|
+
|
|
159
|
+
/** Deregister a attached-backend agent by NAME — drop its indexes. The durable queue
|
|
160
|
+
* notes stay in the vault (deregistering an agent doesn't delete its history). */
|
|
161
|
+
deregister(name: string): boolean {
|
|
162
|
+
const channel = this.nameToChannel.get(name);
|
|
163
|
+
if (channel === undefined) return false;
|
|
164
|
+
this.byChannel.delete(channel);
|
|
165
|
+
this.nameToChannel.delete(name);
|
|
166
|
+
return true;
|
|
167
|
+
}
|
|
168
|
+
|
|
169
|
+
/** Is a attached-backend agent registered for this channel? (the routing-fork check) */
|
|
170
|
+
hasChannel(channel: string): boolean {
|
|
171
|
+
return this.byChannel.has(channel);
|
|
172
|
+
}
|
|
173
|
+
|
|
174
|
+
/** Is a attached-backend agent registered under this name? (the mutual-exclusion check) */
|
|
175
|
+
hasName(name: string): boolean {
|
|
176
|
+
return this.nameToChannel.has(name);
|
|
177
|
+
}
|
|
178
|
+
|
|
179
|
+
/** All registered channels (for /health + the sweep + tests). */
|
|
180
|
+
channels(): string[] {
|
|
181
|
+
return [...this.byChannel.keys()];
|
|
182
|
+
}
|
|
183
|
+
|
|
184
|
+
/**
|
|
185
|
+
* The registered attached-backend agents as plain records (name + channel + the
|
|
186
|
+
* spec's surfaceable, non-secret fields). The `GET /api/agents` list (#102) maps
|
|
187
|
+
* these into {@link AgentInfo}; tests assert the shape. Sorted by name for a stable
|
|
188
|
+
* list. NEVER carries a token/secret — the spec's vault binding is a name + access
|
|
189
|
+
* verb only. The live `pending` count is fetched separately (it's an async vault
|
|
190
|
+
* read; see {@link pending}) so this accessor stays synchronous + cheap.
|
|
191
|
+
*/
|
|
192
|
+
list(): Array<{ name: string; channel: string; vault?: string; systemPrompt?: string }> {
|
|
193
|
+
return [...this.byChannel.values()]
|
|
194
|
+
.map((rec) => ({
|
|
195
|
+
name: rec.name,
|
|
196
|
+
channel: rec.channel,
|
|
197
|
+
...(rec.spec.vault?.name ? { vault: rec.spec.vault.name } : {}),
|
|
198
|
+
...(rec.spec.systemPrompt ? { systemPrompt: rec.spec.systemPrompt } : {}),
|
|
199
|
+
}))
|
|
200
|
+
.sort((a, b) => a.name.localeCompare(b.name));
|
|
201
|
+
}
|
|
202
|
+
|
|
203
|
+
/**
|
|
204
|
+
* Peek the pending queue for a channel: the count of `status:pending` inbound + a
|
|
205
|
+
* bounded oldest-first preview. A no-op shape `{ count: 0, items: [] }` for an
|
|
206
|
+
* unregistered (non-channel) channel — the MCP surface gates cleanly on a non-channel
|
|
207
|
+
* channel rather than erroring.
|
|
208
|
+
*/
|
|
209
|
+
async pending(channel: string): Promise<PendingView> {
|
|
210
|
+
const rec = this.byChannel.get(channel);
|
|
211
|
+
if (!rec) return { count: 0, items: [] };
|
|
212
|
+
const notes = await rec.store.listInboundQueue();
|
|
213
|
+
const pendingNotes = notes.filter((n) => n.status === "pending");
|
|
214
|
+
const items = pendingNotes
|
|
215
|
+
.slice(0, PENDING_PEEK_CAP)
|
|
216
|
+
.map((n) => ({ id: n.id, preview: preview(n.text) }));
|
|
217
|
+
return { count: pendingNotes.length, items };
|
|
218
|
+
}
|
|
219
|
+
|
|
220
|
+
/**
|
|
221
|
+
* Claim the OLDEST `pending` inbound for a channel: set it `in-flight` + stamp
|
|
222
|
+
* `claimedAt = now`, then return it (+ the agent's system prompt). The status flip to
|
|
223
|
+
* `in-flight` (the vault is the source of truth) is what makes a SEQUENTIAL second
|
|
224
|
+
* `claimNext` skip it: each call re-reads the live queue, so once the first claim's
|
|
225
|
+
* PATCH lands the note is no longer `pending`. Returns null when none pending (or for
|
|
226
|
+
* an unregistered channel).
|
|
227
|
+
*
|
|
228
|
+
* SINGLE-CLAIM via COMPARE-AND-SWAP (agent#101). The claim PATCH carries
|
|
229
|
+
* `if_updated_at` (the note's last-seen `updated_at`), so it only lands if the note
|
|
230
|
+
* hasn't changed since this call read it. Two truly-concurrent `claimNext` calls read
|
|
231
|
+
* the SAME `updated_at`; the first claim advances it, so the second's precondition
|
|
232
|
+
* FAILS (the store throws {@link InboundClaimConflictError}) — we then RE-LIST and try
|
|
233
|
+
* the NEXT pending message, never double-claiming. The loop is bounded by the pending
|
|
234
|
+
* count (each pass either claims one or eliminates a now-contended one) plus a hard
|
|
235
|
+
* {@link MAX_CLAIM_ATTEMPTS} backstop. A note with no `updatedAt` (a vault that omitted
|
|
236
|
+
* it) falls back to the prior last-write-wins claim — the precondition is simply
|
|
237
|
+
* absent, so the narrow double-claim window remains only for that degenerate case.
|
|
238
|
+
* Returns null when none pending (or for an unregistered channel).
|
|
239
|
+
*/
|
|
240
|
+
async claimNext(channel: string, now: () => Date = () => new Date()): Promise<ClaimedMessage | null> {
|
|
241
|
+
const rec = this.byChannel.get(channel);
|
|
242
|
+
if (!rec) return null;
|
|
243
|
+
for (let attempt = 0; attempt < MAX_CLAIM_ATTEMPTS; attempt++) {
|
|
244
|
+
// RE-LIST each attempt so a lost CAS race sees the queue as the winner left it
|
|
245
|
+
// (the contended note is now in-flight, so `find` skips past it to the next pending).
|
|
246
|
+
const notes = await rec.store.listInboundQueue();
|
|
247
|
+
const oldest = notes.find((n) => n.status === "pending"); // ascending by ts.
|
|
248
|
+
if (!oldest) return null;
|
|
249
|
+
try {
|
|
250
|
+
// Commit the claim (status → in-flight + claimedAt) BEFORE returning, guarded by
|
|
251
|
+
// the note's `updated_at` so a concurrent claimer can't also win it. A non-conflict
|
|
252
|
+
// PATCH failure propagates (the caller gets the error; the note stays pending).
|
|
253
|
+
await rec.store.setInboundStatus(
|
|
254
|
+
oldest.id,
|
|
255
|
+
"in-flight",
|
|
256
|
+
now().toISOString(),
|
|
257
|
+
oldest.updatedAt, // CAS precondition; undefined → store falls back to force.
|
|
258
|
+
);
|
|
259
|
+
} catch (err) {
|
|
260
|
+
if (err instanceof InboundClaimConflictError) {
|
|
261
|
+
// Another session claimed this note between our list and PATCH — re-list and
|
|
262
|
+
// try the next pending one (no double-claim).
|
|
263
|
+
continue;
|
|
264
|
+
}
|
|
265
|
+
throw err;
|
|
266
|
+
}
|
|
267
|
+
return {
|
|
268
|
+
id: oldest.id,
|
|
269
|
+
text: oldest.text,
|
|
270
|
+
inReplyTo: oldest.id,
|
|
271
|
+
systemPrompt: rec.spec.systemPrompt ?? "",
|
|
272
|
+
};
|
|
273
|
+
}
|
|
274
|
+
// Exhausted the retry budget under sustained contention — treat as "none claimable
|
|
275
|
+
// right now" (a connected session retries `next-message`). Non-corrupting.
|
|
276
|
+
return null;
|
|
277
|
+
}
|
|
278
|
+
|
|
279
|
+
/**
|
|
280
|
+
* Reply to an inbound on a channel: write the outbound `#agent/message/outbound`
|
|
281
|
+
* note via the SAME vault-transport `reply()` the programmatic worker uses (durable,
|
|
282
|
+
* threads `inReplyTo`, renders in the chat UI, tagged outbound so it can NEVER
|
|
283
|
+
* re-trigger the inbound webhook — loop-safe), THEN mark the inbound `handled` and
|
|
284
|
+
* clear its `claimedAt`. Order matters: the outbound is written FIRST so a failure to
|
|
285
|
+
* persist the reply leaves the inbound un-handled (the session can retry) rather than
|
|
286
|
+
* marking it done with no reply. Returns the outbound note id(s). Throws for an
|
|
287
|
+
* unregistered channel (the MCP surface maps it to a tool error).
|
|
288
|
+
*/
|
|
289
|
+
async reply(channel: string, args: { inReplyTo?: string; text: string }): Promise<{ sent: string[] }> {
|
|
290
|
+
const rec = this.byChannel.get(channel);
|
|
291
|
+
if (!rec) throw new Error(`attached-queue registry: no attached-backend agent for "${channel}"`);
|
|
292
|
+
const sent = await rec.store.reply({
|
|
293
|
+
text: args.text,
|
|
294
|
+
...(args.inReplyTo ? { inReplyTo: args.inReplyTo } : {}),
|
|
295
|
+
});
|
|
296
|
+
// Mark handled only AFTER the outbound is durably written. Clear claimedAt so a
|
|
297
|
+
// handled note doesn't linger with a stale claim timestamp.
|
|
298
|
+
if (args.inReplyTo) {
|
|
299
|
+
await rec.store.setInboundStatus(args.inReplyTo, "handled", null);
|
|
300
|
+
}
|
|
301
|
+
return sent;
|
|
302
|
+
}
|
|
303
|
+
|
|
304
|
+
/**
|
|
305
|
+
* Release an in-flight claim back to `pending` (the session is giving up). Clears
|
|
306
|
+
* `claimedAt`. Idempotent at the vault level (re-setting pending is harmless). Throws
|
|
307
|
+
* for an unregistered channel.
|
|
308
|
+
*/
|
|
309
|
+
async release(channel: string, id: string): Promise<void> {
|
|
310
|
+
const rec = this.byChannel.get(channel);
|
|
311
|
+
if (!rec) throw new Error(`attached-queue registry: no attached-backend agent for "${channel}"`);
|
|
312
|
+
await rec.store.setInboundStatus(id, "pending", null);
|
|
313
|
+
}
|
|
314
|
+
|
|
315
|
+
/**
|
|
316
|
+
* TTL auto-release: across EVERY registered channel, reset to `pending` any
|
|
317
|
+
* `in-flight` note whose `claimedAt` is older than the claim TTL — so a crashed /
|
|
318
|
+
* abandoned session can't strand the queue. Best-effort + per-channel-isolated: one
|
|
319
|
+
* channel's store error is logged and never aborts the others. Returns the number of
|
|
320
|
+
* notes released. Wired into the daemon's periodic tick.
|
|
321
|
+
*
|
|
322
|
+
* An `in-flight` note with NO usable `claimedAt` is left alone (we can't judge its
|
|
323
|
+
* age) — defensive; in practice `claimNext` always stamps one.
|
|
324
|
+
*/
|
|
325
|
+
async sweepExpired(now: Date = new Date()): Promise<number> {
|
|
326
|
+
let released = 0;
|
|
327
|
+
const cutoff = now.getTime() - this.claimTtlMs;
|
|
328
|
+
for (const rec of this.byChannel.values()) {
|
|
329
|
+
let notes: InboundQueueNote[];
|
|
330
|
+
try {
|
|
331
|
+
notes = await rec.store.listInboundQueue();
|
|
332
|
+
} catch (err) {
|
|
333
|
+
console.warn(
|
|
334
|
+
`attached-queue: sweep list for "${rec.channel}" failed (continuing): ${(err as Error).message}`,
|
|
335
|
+
);
|
|
336
|
+
continue;
|
|
337
|
+
}
|
|
338
|
+
for (const n of notes) {
|
|
339
|
+
if (n.status !== "in-flight") continue;
|
|
340
|
+
if (!n.claimedAt) continue; // can't judge age — leave it.
|
|
341
|
+
const claimedMs = new Date(n.claimedAt).getTime();
|
|
342
|
+
if (Number.isNaN(claimedMs) || claimedMs > cutoff) continue; // fresh enough.
|
|
343
|
+
try {
|
|
344
|
+
await rec.store.setInboundStatus(n.id, "pending", null);
|
|
345
|
+
released++;
|
|
346
|
+
console.log(
|
|
347
|
+
`attached-queue: auto-released stale in-flight note ${n.id} on "${rec.channel}" ` +
|
|
348
|
+
`(claimed ${n.claimedAt}, TTL ${this.claimTtlMs}ms).`,
|
|
349
|
+
);
|
|
350
|
+
} catch (err) {
|
|
351
|
+
console.warn(
|
|
352
|
+
`attached-queue: sweep release of ${n.id} on "${rec.channel}" failed (continuing): ` +
|
|
353
|
+
`${(err as Error).message}`,
|
|
354
|
+
);
|
|
355
|
+
}
|
|
356
|
+
}
|
|
357
|
+
}
|
|
358
|
+
return released;
|
|
359
|
+
}
|
|
360
|
+
}
|
|
361
|
+
|
|
362
|
+
/** The wake channel for a spec (its first channel) — agent ≡ channel for a channel def. */
|
|
363
|
+
function channelOf(spec: AgentSpec): string {
|
|
364
|
+
const first = spec.channels[0]!;
|
|
365
|
+
return typeof first === "string" ? first : first.name;
|
|
366
|
+
}
|
|
367
|
+
|
|
368
|
+
/** A bounded single-line preview of a message body (for the pending peek). */
|
|
369
|
+
function preview(text: string): string {
|
|
370
|
+
const oneLine = text.replace(/\s+/g, " ").trim();
|
|
371
|
+
return oneLine.length > PREVIEW_LEN ? `${oneLine.slice(0, PREVIEW_LEN - 1)}…` : oneLine;
|
|
372
|
+
}
|