@desplega.ai/agent-swarm 1.20.0 → 1.51.2
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +271 -169
- package/openapi.json +5015 -0
- package/package.json +40 -7
- package/plugin/commands/close-issue.md +7 -3
- package/plugin/commands/create-pr.md +18 -12
- package/plugin/commands/implement-issue.md +7 -3
- package/plugin/commands/respond-github.md +8 -4
- package/plugin/commands/review-pr.md +44 -10
- package/plugin/commands/start-leader.md +1 -3
- package/plugin/commands/start-worker.md +1 -3
- package/plugin/commands/work-on-task.md +22 -3
- package/plugin/pi-skills/close-issue/SKILL.md +90 -0
- package/plugin/pi-skills/create-pr/SKILL.md +99 -0
- package/plugin/pi-skills/implement-issue/SKILL.md +135 -0
- package/plugin/pi-skills/investigate-sentry-issue/SKILL.md +138 -0
- package/plugin/pi-skills/respond-github/SKILL.md +98 -0
- package/plugin/pi-skills/review-offered-task/SKILL.md +45 -0
- package/plugin/pi-skills/review-pr/SKILL.md +261 -0
- package/plugin/pi-skills/start-leader/SKILL.md +121 -0
- package/plugin/pi-skills/start-worker/SKILL.md +60 -0
- package/plugin/pi-skills/swarm-chat/SKILL.md +82 -0
- package/plugin/pi-skills/todos/SKILL.md +66 -0
- package/plugin/pi-skills/work-on-task/SKILL.md +65 -0
- package/plugin/skills/artifacts/examples/approval-flow.ts +34 -0
- package/plugin/skills/artifacts/examples/hono-dashboard.ts +31 -0
- package/plugin/skills/artifacts/examples/multi-artifact.ts +20 -0
- package/plugin/skills/artifacts/examples/static-report.sh +17 -0
- package/plugin/skills/artifacts/skill.md +71 -0
- package/src/agentmail/app.ts +65 -0
- package/src/agentmail/handlers.ts +262 -0
- package/src/agentmail/index.ts +9 -0
- package/src/agentmail/templates.ts +111 -0
- package/src/agentmail/types.ts +51 -0
- package/src/artifact-sdk/browser-sdk.ts +30 -0
- package/src/artifact-sdk/index.ts +2 -0
- package/src/artifact-sdk/localtunnel.d.ts +20 -0
- package/src/artifact-sdk/port.ts +12 -0
- package/src/artifact-sdk/server.ts +156 -0
- package/src/artifact-sdk/tunnel.ts +19 -0
- package/src/be/chunking.ts +193 -0
- package/src/be/db-queries/oauth.ts +90 -0
- package/src/be/db-queries/tracker.ts +182 -0
- package/src/be/db.ts +3327 -784
- package/src/be/embedding.ts +80 -0
- package/src/be/migrations/001_initial.sql +409 -0
- package/src/be/migrations/002_one_time_schedules.sql +59 -0
- package/src/be/migrations/003_workflows.sql +51 -0
- package/src/be/migrations/004_workflow_source.sql +81 -0
- package/src/be/migrations/005_epic_next_steps.sql +2 -0
- package/src/be/migrations/006_vcs_provider.sql +94 -0
- package/src/be/migrations/007_task_dir.sql +2 -0
- package/src/be/migrations/008_workflow_redesign.sql +85 -0
- package/src/be/migrations/009_tracker_integration.sql +144 -0
- package/src/be/migrations/010_step_diagnostics.sql +1 -0
- package/src/be/migrations/011_step_next_port.sql +1 -0
- package/src/be/migrations/012_trigger_schema.sql +1 -0
- package/src/be/migrations/013_task_output_schema.sql +2 -0
- package/src/be/migrations/014_prompt_templates.sql +33 -0
- package/src/be/migrations/015_workflow_workspace.sql +3 -0
- package/src/be/migrations/016_active_session_runner_session.sql +4 -0
- package/src/be/migrations/017_channel_activity_cursors.sql +6 -0
- package/src/be/migrations/018_fix_seed_double_version.sql +30 -0
- package/src/be/migrations/runner.ts +188 -0
- package/src/be/seed.ts +62 -0
- package/src/cli.tsx +231 -299
- package/src/commands/artifact.ts +241 -0
- package/src/commands/onboard/compose-generator.ts +169 -0
- package/src/commands/onboard/env-generator.ts +79 -0
- package/src/commands/onboard/manifest.ts +37 -0
- package/src/commands/onboard/presets.ts +85 -0
- package/src/commands/onboard/service-names.ts +47 -0
- package/src/commands/onboard/steps/core-credentials.tsx +111 -0
- package/src/commands/onboard/steps/custom-templates.tsx +168 -0
- package/src/commands/onboard/steps/generate.tsx +154 -0
- package/src/commands/onboard/steps/harness-credentials.tsx +195 -0
- package/src/commands/onboard/steps/harness.tsx +21 -0
- package/src/commands/onboard/steps/health-check.tsx +171 -0
- package/src/commands/onboard/steps/integration-github.tsx +105 -0
- package/src/commands/onboard/steps/integration-gitlab.tsx +79 -0
- package/src/commands/onboard/steps/integration-menu.tsx +58 -0
- package/src/commands/onboard/steps/integration-sentry.tsx +79 -0
- package/src/commands/onboard/steps/integration-slack.tsx +165 -0
- package/src/commands/onboard/steps/post-connect.tsx +145 -0
- package/src/commands/onboard/steps/post-dashboard.tsx +34 -0
- package/src/commands/onboard/steps/post-task.tsx +103 -0
- package/src/commands/onboard/steps/prereq-check.tsx +178 -0
- package/src/commands/onboard/steps/review.tsx +82 -0
- package/src/commands/onboard/steps/start.tsx +97 -0
- package/src/commands/onboard/templates.ts +34 -0
- package/src/commands/onboard/types.ts +259 -0
- package/src/commands/onboard.tsx +425 -0
- package/src/commands/runner.ts +1540 -630
- package/src/commands/setup.tsx +23 -38
- package/src/commands/shared/client-config.ts +41 -0
- package/src/commands/templates.ts +172 -0
- package/src/github/app.ts +8 -0
- package/src/github/handlers.ts +384 -151
- package/src/github/index.ts +1 -0
- package/src/github/mentions-aliases.test.ts +73 -0
- package/src/github/mentions.test.ts +3 -3
- package/src/github/mentions.ts +32 -6
- package/src/github/templates.ts +398 -0
- package/src/github/types.ts +1 -0
- package/src/gitlab/auth.ts +63 -0
- package/src/gitlab/handlers.ts +368 -0
- package/src/gitlab/index.ts +19 -0
- package/src/gitlab/reactions.ts +104 -0
- package/src/gitlab/templates.ts +140 -0
- package/src/gitlab/types.ts +130 -0
- package/src/heartbeat/heartbeat.ts +434 -0
- package/src/heartbeat/index.ts +1 -0
- package/src/heartbeat/templates.ts +30 -0
- package/src/hooks/hook.ts +555 -4
- package/src/hooks/tool-loop-detection.test.ts +158 -0
- package/src/hooks/tool-loop-detection.ts +167 -0
- package/src/http/active-sessions.ts +199 -0
- package/src/http/agents.ts +328 -0
- package/src/http/config.ts +191 -0
- package/src/http/core.ts +309 -0
- package/src/http/db-query.ts +91 -0
- package/src/http/ecosystem.ts +63 -0
- package/src/http/epics.ts +460 -0
- package/src/http/index.ts +216 -0
- package/src/http/mcp.ts +77 -0
- package/src/http/memory.ts +168 -0
- package/src/http/openapi.ts +109 -0
- package/src/http/poll.ts +299 -0
- package/src/http/prompt-templates.ts +412 -0
- package/src/http/repos.ts +195 -0
- package/src/http/route-def.ts +123 -0
- package/src/http/schedules.ts +426 -0
- package/src/http/session-data.ts +241 -0
- package/src/http/stats.ts +174 -0
- package/src/http/tasks.ts +468 -0
- package/src/http/trackers/index.ts +10 -0
- package/src/http/trackers/linear.ts +187 -0
- package/src/http/types.ts +12 -0
- package/src/http/utils.ts +87 -0
- package/src/http/webhooks.ts +432 -0
- package/src/http/workflows.ts +530 -0
- package/src/http.ts +1 -1890
- package/src/linear/README.md +65 -0
- package/src/linear/app.ts +48 -0
- package/src/linear/client.ts +18 -0
- package/src/linear/index.ts +1 -0
- package/src/linear/oauth.ts +35 -0
- package/src/linear/outbound.ts +212 -0
- package/src/linear/sync.ts +567 -0
- package/src/linear/templates.ts +47 -0
- package/src/linear/types.ts +7 -0
- package/src/linear/webhook.ts +104 -0
- package/src/oauth/README.md +66 -0
- package/src/oauth/index.ts +6 -0
- package/src/oauth/wrapper.ts +204 -0
- package/src/prompts/base-prompt.ts +150 -265
- package/src/prompts/defaults.ts +196 -0
- package/src/prompts/registry.ts +57 -0
- package/src/prompts/resolver.ts +296 -0
- package/src/prompts/session-templates.ts +604 -0
- package/src/providers/claude-adapter.ts +442 -0
- package/src/providers/index.ts +24 -0
- package/src/providers/pi-mono-adapter.ts +442 -0
- package/src/providers/pi-mono-extension.ts +624 -0
- package/src/providers/pi-mono-mcp-client.ts +124 -0
- package/src/providers/types.ts +75 -0
- package/src/scheduler/scheduler.test.ts +2 -0
- package/src/scheduler/scheduler.ts +231 -40
- package/src/server.ts +97 -6
- package/src/slack/HEURISTICS.md +105 -0
- package/src/slack/actions.ts +133 -0
- package/src/slack/app.ts +7 -0
- package/src/slack/assistant.ts +118 -0
- package/src/slack/blocks.ts +233 -0
- package/src/slack/channel-activity.ts +177 -0
- package/src/slack/commands.ts +31 -17
- package/src/slack/files.ts +1 -1
- package/src/slack/handlers.test.ts +114 -1
- package/src/slack/handlers.ts +230 -55
- package/src/slack/responses.ts +120 -67
- package/src/slack/router.ts +17 -99
- package/src/slack/templates.ts +55 -0
- package/src/slack/thread-buffer.ts +213 -0
- package/src/slack/watcher.ts +119 -4
- package/src/tests/agent-activity.test.ts +247 -0
- package/src/tests/agentmail-filters.test.ts +97 -0
- package/src/tests/artifact-sdk.test.ts +800 -0
- package/src/tests/base-prompt.test.ts +264 -0
- package/src/tests/build-pi-skills.test.ts +127 -0
- package/src/tests/channel-activity.test.ts +363 -0
- package/src/tests/claude-adapter.test.ts +126 -0
- package/src/tests/context-versioning.test.ts +425 -0
- package/src/tests/db-queries-oauth.test.ts +197 -0
- package/src/tests/db-queries-tracker.test.ts +230 -0
- package/src/tests/epics.test.ts +3 -3
- package/src/tests/error-tracker.test.ts +368 -0
- package/src/tests/fetch-resolved-env.test.ts +167 -0
- package/src/tests/generate-default-claude-md.test.ts +9 -1
- package/src/tests/generate-identity-templates.test.ts +124 -0
- package/src/tests/gitlab-auth.test.ts +109 -0
- package/src/tests/gitlab-handlers.test.ts +691 -0
- package/src/tests/gitlab-vcs-db.test.ts +177 -0
- package/src/tests/heartbeat.test.ts +364 -0
- package/src/tests/http-api-integration.test.ts +1698 -0
- package/src/tests/linear-outbound-sync.test.ts +200 -0
- package/src/tests/linear-webhook.test.ts +406 -0
- package/src/tests/match-route.test.ts +187 -0
- package/src/tests/memory.test.ts +737 -0
- package/src/tests/migration-runner-regressions.test.ts +86 -0
- package/src/tests/model-control.test.ts +338 -0
- package/src/tests/oauth-wrapper.test.ts +147 -0
- package/src/tests/onboard-compose.test.ts +138 -0
- package/src/tests/onboard-env.test.ts +174 -0
- package/src/tests/onboard-manifest.test.ts +137 -0
- package/src/tests/pi-mono-adapter.test.ts +234 -0
- package/src/tests/pool-session-logs.test.ts +199 -0
- package/src/tests/progress-dedup.test.ts +98 -0
- package/src/tests/prompt-template-github.test.ts +682 -0
- package/src/tests/prompt-template-remaining.test.ts +504 -0
- package/src/tests/prompt-template-resolver.test.ts +621 -0
- package/src/tests/prompt-template-session.test.ts +363 -0
- package/src/tests/prompt-templates-db.test.ts +616 -0
- package/src/tests/provider-adapter.test.ts +122 -0
- package/src/tests/provider-command-format.test.ts +98 -0
- package/src/tests/reload-config.test.ts +170 -0
- package/src/tests/runner-polling-api.test.ts +25 -20
- package/src/tests/scheduled-tasks.test.ts +104 -0
- package/src/tests/scheduler-backoff.test.ts +166 -0
- package/src/tests/self-improvement.test.ts +541 -0
- package/src/tests/session-attach.test.ts +536 -0
- package/src/tests/session-costs.test.ts +267 -1
- package/src/tests/slack-actions.test.ts +133 -0
- package/src/tests/slack-assistant.test.ts +136 -0
- package/src/tests/slack-blocks.test.ts +246 -0
- package/src/tests/slack-metadata-inheritance.test.ts +243 -0
- package/src/tests/slack-queue-offline.test.ts +174 -0
- package/src/tests/slack-router.test.ts +181 -0
- package/src/tests/slack-thread-buffer.test.ts +305 -0
- package/src/tests/slack-thread-followups.test.ts +298 -0
- package/src/tests/slack-watcher.test.ts +101 -0
- package/src/tests/structured-output.test.ts +307 -0
- package/src/tests/swarm-repos.test.ts +198 -0
- package/src/tests/task-cancellation.test.ts +6 -4
- package/src/tests/task-working-dir.test.ts +176 -0
- package/src/tests/template-fetch.test.ts +490 -0
- package/src/tests/tool-annotations.test.ts +371 -0
- package/src/tests/tracker-tools.test.ts +184 -0
- package/src/tests/update-profile-agentid.test.ts +248 -0
- package/src/tests/update-profile-api.test.ts +143 -3
- package/src/tests/update-profile-auth.test.ts +195 -0
- package/src/tests/validation-adapters.test.ts +86 -0
- package/src/tests/vcs-provider.test.ts +27 -0
- package/src/tests/workflow-agent-task.test.ts +196 -0
- package/src/tests/workflow-async-v2.test.ts +508 -0
- package/src/tests/workflow-convergence.test.ts +541 -0
- package/src/tests/workflow-definition-validation.test.ts +366 -0
- package/src/tests/workflow-engine-v2.test.ts +691 -0
- package/src/tests/workflow-executors.test.ts +736 -0
- package/src/tests/workflow-http-v2.test.ts +599 -0
- package/src/tests/workflow-integration-io.test.ts +902 -0
- package/src/tests/workflow-io-schemas.test.ts +624 -0
- package/src/tests/workflow-registry.test.ts +592 -0
- package/src/tests/workflow-retry-v2.test.ts +401 -0
- package/src/tests/workflow-retry-validation.test.ts +282 -0
- package/src/tests/workflow-schedule-trigger.test.ts +104 -0
- package/src/tests/workflow-template.test.ts +288 -0
- package/src/tests/workflow-trigger-schema.test.ts +359 -0
- package/src/tests/workflow-triggers-v2.test.ts +264 -0
- package/src/tests/workflow-versions.test.ts +208 -0
- package/src/tests/workflow-workspace.test.ts +272 -0
- package/src/tests/x402-client.test.ts +117 -0
- package/src/tests/x402-config.test.ts +182 -0
- package/src/tests/x402-spending-tracker.test.ts +185 -0
- package/src/tools/cancel-task.ts +2 -0
- package/src/tools/context-diff.ts +171 -0
- package/src/tools/context-history.ts +138 -0
- package/src/tools/create-channel.ts +1 -0
- package/src/tools/db-query.ts +78 -0
- package/src/tools/delete-channel.ts +132 -0
- package/src/tools/epics/assign-task-to-epic.ts +1 -0
- package/src/tools/epics/create-epic.ts +3 -2
- package/src/tools/epics/delete-epic.ts +2 -0
- package/src/tools/epics/get-epic-details.ts +2 -0
- package/src/tools/epics/list-epics.ts +2 -0
- package/src/tools/epics/unassign-task-from-epic.ts +1 -0
- package/src/tools/epics/update-epic.ts +7 -4
- package/src/tools/get-swarm.ts +2 -0
- package/src/tools/get-task-details.ts +2 -0
- package/src/tools/get-tasks.ts +27 -1
- package/src/tools/inject-learning.ts +106 -0
- package/src/tools/join-swarm.ts +17 -7
- package/src/tools/list-channels.ts +2 -0
- package/src/tools/list-services.ts +2 -0
- package/src/tools/memory-get.ts +56 -0
- package/src/tools/memory-search.ts +131 -0
- package/src/tools/my-agent-info.ts +2 -0
- package/src/tools/poll-task.ts +2 -20
- package/src/tools/post-message.ts +1 -0
- package/src/tools/prompt-templates/delete.ts +86 -0
- package/src/tools/prompt-templates/get.ts +89 -0
- package/src/tools/prompt-templates/index.ts +5 -0
- package/src/tools/prompt-templates/list.ts +95 -0
- package/src/tools/prompt-templates/preview.ts +84 -0
- package/src/tools/prompt-templates/set.ts +117 -0
- package/src/tools/read-messages.ts +2 -0
- package/src/tools/register-agentmail-inbox.ts +166 -0
- package/src/tools/register-service.ts +2 -0
- package/src/tools/schedules/create-schedule.ts +134 -24
- package/src/tools/schedules/delete-schedule.ts +2 -0
- package/src/tools/schedules/list-schedules.ts +20 -4
- package/src/tools/schedules/run-schedule-now.ts +1 -0
- package/src/tools/schedules/update-schedule.ts +49 -17
- package/src/tools/send-task.ts +132 -10
- package/src/tools/slack-download-file.ts +4 -2
- package/src/tools/slack-list-channels.ts +2 -0
- package/src/tools/slack-post.ts +2 -0
- package/src/tools/slack-read.ts +2 -0
- package/src/tools/slack-reply.ts +2 -0
- package/src/tools/slack-upload-file.ts +2 -0
- package/src/tools/store-progress.ts +205 -4
- package/src/tools/swarm-config/delete-config.ts +87 -0
- package/src/tools/swarm-config/get-config.ts +108 -0
- package/src/tools/swarm-config/index.ts +4 -0
- package/src/tools/swarm-config/list-config.ts +99 -0
- package/src/tools/swarm-config/set-config.ts +118 -0
- package/src/tools/task-action.ts +50 -5
- package/src/tools/task-dedup.ts +97 -0
- package/src/tools/templates.ts +53 -0
- package/src/tools/tool-config.ts +124 -0
- package/src/tools/tracker/index.ts +6 -0
- package/src/tools/tracker/tracker-link-epic.ts +64 -0
- package/src/tools/tracker/tracker-link-task.ts +64 -0
- package/src/tools/tracker/tracker-map-agent.ts +57 -0
- package/src/tools/tracker/tracker-status.ts +56 -0
- package/src/tools/tracker/tracker-sync-status.ts +42 -0
- package/src/tools/tracker/tracker-unlink.ts +41 -0
- package/src/tools/unregister-service.ts +2 -0
- package/src/tools/update-profile.ts +172 -17
- package/src/tools/update-service-status.ts +2 -0
- package/src/tools/utils.ts +10 -1
- package/src/tools/workflows/create-workflow.ts +129 -0
- package/src/tools/workflows/delete-workflow.ts +42 -0
- package/src/tools/workflows/get-workflow-run.ts +59 -0
- package/src/tools/workflows/get-workflow.ts +53 -0
- package/src/tools/workflows/index.ts +9 -0
- package/src/tools/workflows/list-workflow-runs.ts +48 -0
- package/src/tools/workflows/list-workflows.ts +42 -0
- package/src/tools/workflows/retry-workflow-run.ts +40 -0
- package/src/tools/workflows/trigger-workflow.ts +96 -0
- package/src/tools/workflows/update-workflow.ts +133 -0
- package/src/tracker/types.ts +51 -0
- package/src/types.ts +530 -14
- package/src/utils/credentials.test.ts +156 -0
- package/src/utils/credentials.ts +50 -0
- package/src/utils/error-tracker.ts +190 -0
- package/src/vcs/index.ts +15 -0
- package/src/vcs/types.ts +5 -0
- package/src/workflows/checkpoint.ts +121 -0
- package/src/workflows/cooldown.ts +28 -0
- package/src/workflows/definition.ts +235 -0
- package/src/workflows/engine.ts +580 -0
- package/src/workflows/event-bus.ts +29 -0
- package/src/workflows/executors/agent-task.ts +103 -0
- package/src/workflows/executors/base.ts +86 -0
- package/src/workflows/executors/code-match.ts +88 -0
- package/src/workflows/executors/index.ts +16 -0
- package/src/workflows/executors/notify.ts +93 -0
- package/src/workflows/executors/property-match.ts +104 -0
- package/src/workflows/executors/raw-llm.ts +83 -0
- package/src/workflows/executors/registry.ts +76 -0
- package/src/workflows/executors/script.ts +103 -0
- package/src/workflows/executors/validate.ts +215 -0
- package/src/workflows/executors/vcs.ts +58 -0
- package/src/workflows/index.ts +61 -0
- package/src/workflows/input.ts +46 -0
- package/src/workflows/json-schema-validator.ts +118 -0
- package/src/workflows/recovery.ts +139 -0
- package/src/workflows/resume.ts +229 -0
- package/src/workflows/retry-poller.ts +216 -0
- package/src/workflows/template.ts +74 -0
- package/src/workflows/templates.ts +86 -0
- package/src/workflows/triggers.ts +124 -0
- package/src/workflows/validation.ts +104 -0
- package/src/workflows/version.ts +44 -0
- package/src/x402/cli.ts +140 -0
- package/src/x402/client.ts +192 -0
- package/src/x402/config.ts +131 -0
- package/src/x402/index.ts +37 -0
- package/src/x402/openfort-signer.ts +83 -0
- package/src/x402/spending-tracker.ts +109 -0
- package/templates/official/coder/CLAUDE.md +49 -0
- package/templates/official/coder/IDENTITY.md +28 -0
- package/templates/official/coder/SOUL.md +43 -0
- package/templates/official/coder/TOOLS.md +40 -0
- package/templates/official/coder/config.json +23 -0
- package/templates/official/coder/start-up.sh +23 -0
- package/templates/official/content-reviewer/CLAUDE.md +68 -0
- package/templates/official/content-reviewer/IDENTITY.md +28 -0
- package/templates/official/content-reviewer/SOUL.md +44 -0
- package/templates/official/content-reviewer/TOOLS.md +37 -0
- package/templates/official/content-reviewer/config.json +23 -0
- package/templates/official/content-reviewer/start-up.sh +23 -0
- package/templates/official/content-strategist/CLAUDE.md +63 -0
- package/templates/official/content-strategist/IDENTITY.md +33 -0
- package/templates/official/content-strategist/SOUL.md +48 -0
- package/templates/official/content-strategist/TOOLS.md +47 -0
- package/templates/official/content-strategist/config.json +23 -0
- package/templates/official/content-strategist/start-up.sh +23 -0
- package/templates/official/content-writer/CLAUDE.md +72 -0
- package/templates/official/content-writer/IDENTITY.md +30 -0
- package/templates/official/content-writer/SOUL.md +46 -0
- package/templates/official/content-writer/TOOLS.md +44 -0
- package/templates/official/content-writer/config.json +23 -0
- package/templates/official/content-writer/start-up.sh +23 -0
- package/templates/official/forward-deployed-engineer/CLAUDE.md +54 -0
- package/templates/official/forward-deployed-engineer/IDENTITY.md +37 -0
- package/templates/official/forward-deployed-engineer/SOUL.md +55 -0
- package/templates/official/forward-deployed-engineer/config.json +21 -0
- package/templates/official/lead/CLAUDE.md +33 -0
- package/templates/official/lead/IDENTITY.md +36 -0
- package/templates/official/lead/SOUL.md +51 -0
- package/templates/official/lead/config.json +22 -0
- package/templates/official/researcher/CLAUDE.md +46 -0
- package/templates/official/researcher/IDENTITY.md +28 -0
- package/templates/official/researcher/SOUL.md +43 -0
- package/templates/official/researcher/config.json +21 -0
- package/templates/official/reviewer/CLAUDE.md +63 -0
- package/templates/official/reviewer/IDENTITY.md +28 -0
- package/templates/official/reviewer/SOUL.md +45 -0
- package/templates/official/reviewer/config.json +21 -0
- package/templates/official/tester/CLAUDE.md +53 -0
- package/templates/official/tester/IDENTITY.md +28 -0
- package/templates/official/tester/SOUL.md +55 -0
- package/templates/official/tester/config.json +21 -0
- package/templates/schema.ts +35 -0
- package/.claude/settings.local.json +0 -115
- package/.dockerignore +0 -61
- package/.editorconfig +0 -15
- package/.env.docker.example +0 -39
- package/.env.example +0 -40
- package/.github/workflows/ci.yml +0 -76
- package/.github/workflows/docker-and-deploy.yml +0 -117
- package/.wts-config.json +0 -4
- package/.wts-setup.ts +0 -102
- package/CLAUDE.md +0 -104
- package/CONTRIBUTING.md +0 -270
- package/DEPLOYMENT.md +0 -605
- package/Dockerfile +0 -57
- package/Dockerfile.worker +0 -157
- package/FAQ.md +0 -19
- package/MCP.md +0 -406
- package/UI.md +0 -40
- package/assets/agent-swarm-logo-orange.png +0 -0
- package/assets/agent-swarm-logo.png +0 -0
- package/assets/agent-swarm.mp4 +0 -0
- package/assets/agent-swarm.png +0 -0
- package/biome.json +0 -39
- package/deploy/DEPLOY.md +0 -60
- package/deploy/agent-swarm.service +0 -17
- package/deploy/docker-push.ts +0 -30
- package/deploy/install.ts +0 -85
- package/deploy/prod-db.ts +0 -42
- package/deploy/uninstall.ts +0 -12
- package/deploy/update.ts +0 -21
- package/docker-compose.example.yml +0 -159
- package/docker-entrypoint.sh +0 -352
- package/ecosystem.config.cjs +0 -66
- package/plugin/README.md +0 -1
- package/plugin/hooks/hooks.json +0 -71
- package/pyproject.toml +0 -9
- package/scripts/generate-mcp-docs.ts +0 -415
- package/slack-manifest.json +0 -71
- package/src/tests/get-inbox-message.test.ts +0 -145
- package/src/tools/get-inbox-message.ts +0 -89
- package/src/tools/inbox-delegate.ts +0 -113
- package/thoughts/shared/plans/2025-12-18-slack-integration.md +0 -1195
- package/thoughts/shared/plans/2025-12-19-agent-log-streaming.md +0 -732
- package/thoughts/shared/plans/2025-12-19-role-based-swarm-plugin.md +0 -361
- package/thoughts/shared/plans/2025-12-20-mobile-responsive-ui.md +0 -501
- package/thoughts/shared/plans/2025-12-20-startup-team-swarm.md +0 -560
- package/thoughts/shared/plans/2025-12-23-runner-level-polling.md +0 -934
- package/thoughts/shared/plans/2025-12-23-runner-session-logs.md +0 -1000
- package/thoughts/shared/plans/2025-12-23-worker-lead-spawn-triggers.md +0 -568
- package/thoughts/shared/plans/2026-01-09-inverse-teleport.md +0 -1516
- package/thoughts/shared/plans/2026-01-12-agent-rename-pm2-control.md +0 -1133
- package/thoughts/shared/plans/2026-01-12-github-app-integration.md +0 -380
- package/thoughts/shared/plans/2026-01-12-lead-inbox-model.md +0 -876
- package/thoughts/shared/plans/2026-01-12-ralph-wiggum-integration.md +0 -463
- package/thoughts/shared/plans/2026-01-13-agent-concurrency.md +0 -691
- package/thoughts/shared/plans/2026-01-13-github-assignment-handling.md +0 -690
- package/thoughts/shared/plans/2026-01-13-prevent-duplicate-trigger-processing.md +0 -1071
- package/thoughts/shared/plans/2026-01-14-fix-slack-thread-context.md +0 -507
- package/thoughts/shared/plans/2026-01-15-scheduled-tasks-implementation.md +0 -565
- package/thoughts/shared/plans/2026-01-15-usage-cost-tracking-ui.md +0 -1479
- package/thoughts/shared/plans/2026-01-16-epics-feature-implementation.md +0 -1230
- package/thoughts/shared/research/.gitkeep +0 -0
- package/thoughts/shared/research/2025-01-09-inverse-teleport-plan-review.md +0 -420
- package/thoughts/shared/research/2025-12-18-slack-integration.md +0 -442
- package/thoughts/shared/research/2025-12-19-agent-log-streaming.md +0 -339
- package/thoughts/shared/research/2025-12-19-agent-secrets-cli-research.md +0 -390
- package/thoughts/shared/research/2025-12-21-gemini-cli-integration.md +0 -376
- package/thoughts/shared/research/2025-12-22-runner-loop-architecture.md +0 -582
- package/thoughts/shared/research/2025-12-22-setup-experience-improvements.md +0 -264
- package/thoughts/shared/research/2026-01-13-lead-duplicate-trigger-processing.md +0 -223
- package/thoughts/shared/research/2026-01-14-lead-slack-thread-context.md +0 -277
- package/thoughts/shared/research/2026-01-15-ai-tracker-agent-swarm-integration.md +0 -376
- package/thoughts/shared/research/2026-01-15-auto-starting-processes-in-worker-containers.md +0 -787
- package/thoughts/shared/research/2026-01-15-scheduled-tasks.md +0 -390
- package/thoughts/shared/research/2026-01-16-epics-feature-research.md +0 -437
- package/thoughts/taras/plans/2026-01-22-agent-swarm-schemas.md +0 -98
- package/thoughts/taras/plans/2026-01-28-per-worker-claude-md.md +0 -617
- package/thoughts/taras/plans/2026-01-28-sentry-cli-integration.md +0 -214
- package/thoughts/taras/research/2026-01-22-vercel-cli-integration.md +0 -287
- package/thoughts/taras/research/2026-01-27-excessive-polling-issue.md +0 -311
- package/thoughts/taras/research/2026-01-28-per-worker-claude-md.md +0 -383
- package/thoughts/taras/research/2026-01-28-sentry-cli-integration.md +0 -240
- package/tsconfig.json +0 -37
- package/ui/CLAUDE.md +0 -49
- package/ui/bun.lock +0 -771
- package/ui/index.html +0 -22
- package/ui/package-lock.json +0 -5290
- package/ui/package.json +0 -33
- package/ui/pnpm-lock.yaml +0 -3341
- package/ui/postcss.config.js +0 -6
- package/ui/public/logo.png +0 -0
- package/ui/src/App.tsx +0 -63
- package/ui/src/components/ActivityFeed.tsx +0 -440
- package/ui/src/components/AgentDetailPanel.tsx +0 -733
- package/ui/src/components/AgentsPanel.tsx +0 -815
- package/ui/src/components/ChatPanel.tsx +0 -1920
- package/ui/src/components/ConfigModal.tsx +0 -253
- package/ui/src/components/Dashboard.tsx +0 -832
- package/ui/src/components/EditAgentProfileModal.tsx +0 -433
- package/ui/src/components/EpicDetailPage.tsx +0 -741
- package/ui/src/components/EpicsPanel.tsx +0 -566
- package/ui/src/components/Header.tsx +0 -160
- package/ui/src/components/JsonViewer.tsx +0 -171
- package/ui/src/components/ScheduledTaskDetailPanel.tsx +0 -517
- package/ui/src/components/ScheduledTasksPanel.tsx +0 -639
- package/ui/src/components/ServicesPanel.tsx +0 -622
- package/ui/src/components/SessionLogPanel.tsx +0 -1219
- package/ui/src/components/StatsBar.tsx +0 -321
- package/ui/src/components/StatusBadge.tsx +0 -168
- package/ui/src/components/TaskDetailPanel.tsx +0 -903
- package/ui/src/components/TasksPanel.tsx +0 -614
- package/ui/src/components/UsageCharts.tsx +0 -216
- package/ui/src/components/UsageTab.tsx +0 -394
- package/ui/src/hooks/queries.ts +0 -353
- package/ui/src/hooks/useAutoScroll.ts +0 -83
- package/ui/src/index.css +0 -257
- package/ui/src/lib/api.ts +0 -268
- package/ui/src/lib/config.ts +0 -35
- package/ui/src/lib/contentPreview.ts +0 -208
- package/ui/src/lib/theme.ts +0 -214
- package/ui/src/lib/utils.ts +0 -88
- package/ui/src/main.tsx +0 -28
- package/ui/src/types/api.ts +0 -323
- package/ui/src/vite-env.d.ts +0 -1
- package/ui/tailwind.config.js +0 -37
- package/ui/tsconfig.json +0 -31
- package/ui/vite.config.ts +0 -35
- /package/{thoughts/shared/plans → templates/community}/.gitkeep +0 -0
|
@@ -0,0 +1,248 @@
|
|
|
1
|
+
import { afterAll, beforeAll, describe, expect, test } from "bun:test";
|
|
2
|
+
import { unlink } from "node:fs/promises";
|
|
3
|
+
import {
|
|
4
|
+
closeDb,
|
|
5
|
+
createAgent,
|
|
6
|
+
getAgentById,
|
|
7
|
+
getLatestContextVersion,
|
|
8
|
+
initDb,
|
|
9
|
+
updateAgentName,
|
|
10
|
+
updateAgentProfile,
|
|
11
|
+
} from "../be/db";
|
|
12
|
+
|
|
13
|
+
const TEST_DB_PATH = "./test-update-profile-agentid.sqlite";
|
|
14
|
+
|
|
15
|
+
describe("update-profile agentId authorization", () => {
|
|
16
|
+
const leadId = "aaaa0000-0000-4000-8000-000000000001";
|
|
17
|
+
const workerId = "bbbb0000-0000-4000-8000-000000000002";
|
|
18
|
+
const otherWorkerId = "cccc0000-0000-4000-8000-000000000003";
|
|
19
|
+
const nonExistentId = "dddd0000-0000-4000-8000-000000000099";
|
|
20
|
+
|
|
21
|
+
beforeAll(async () => {
|
|
22
|
+
for (const suffix of ["", "-wal", "-shm"]) {
|
|
23
|
+
try {
|
|
24
|
+
await unlink(TEST_DB_PATH + suffix);
|
|
25
|
+
} catch {
|
|
26
|
+
// File doesn't exist
|
|
27
|
+
}
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
closeDb();
|
|
31
|
+
initDb(TEST_DB_PATH);
|
|
32
|
+
|
|
33
|
+
createAgent({ id: leadId, name: "Test Lead", isLead: true, status: "idle" });
|
|
34
|
+
createAgent({ id: workerId, name: "Test Worker", isLead: false, status: "idle" });
|
|
35
|
+
createAgent({ id: otherWorkerId, name: "Other Worker", isLead: false, status: "idle" });
|
|
36
|
+
});
|
|
37
|
+
|
|
38
|
+
afterAll(async () => {
|
|
39
|
+
closeDb();
|
|
40
|
+
for (const suffix of ["", "-wal", "-shm"]) {
|
|
41
|
+
try {
|
|
42
|
+
await unlink(TEST_DB_PATH + suffix);
|
|
43
|
+
} catch {
|
|
44
|
+
// File doesn't exist
|
|
45
|
+
}
|
|
46
|
+
}
|
|
47
|
+
});
|
|
48
|
+
|
|
49
|
+
// ==========================================================================
|
|
50
|
+
// Authorization logic (simulates update-profile tool handler checks)
|
|
51
|
+
// ==========================================================================
|
|
52
|
+
|
|
53
|
+
describe("lead agent can update another agent's profile", () => {
|
|
54
|
+
test("lead updates worker description", () => {
|
|
55
|
+
const callingAgent = getAgentById(leadId);
|
|
56
|
+
expect(callingAgent).not.toBeNull();
|
|
57
|
+
expect(callingAgent!.isLead).toBe(true);
|
|
58
|
+
|
|
59
|
+
// Lead updating another agent — this is the happy path
|
|
60
|
+
const updated = updateAgentProfile(
|
|
61
|
+
workerId,
|
|
62
|
+
{ description: "Updated by lead" },
|
|
63
|
+
{ changeSource: "lead_coaching", changedByAgentId: leadId },
|
|
64
|
+
);
|
|
65
|
+
expect(updated).not.toBeNull();
|
|
66
|
+
expect(updated!.description).toBe("Updated by lead");
|
|
67
|
+
});
|
|
68
|
+
|
|
69
|
+
test("lead updates worker soulMd with lead_coaching changeSource", () => {
|
|
70
|
+
const updated = updateAgentProfile(
|
|
71
|
+
workerId,
|
|
72
|
+
{ soulMd: "# Soul updated by lead" },
|
|
73
|
+
{ changeSource: "lead_coaching", changedByAgentId: leadId },
|
|
74
|
+
);
|
|
75
|
+
expect(updated).not.toBeNull();
|
|
76
|
+
expect(updated!.soulMd).toBe("# Soul updated by lead");
|
|
77
|
+
|
|
78
|
+
// Verify the context version records lead_coaching as changeSource
|
|
79
|
+
const version = getLatestContextVersion(workerId, "soulMd");
|
|
80
|
+
expect(version).not.toBeNull();
|
|
81
|
+
expect(version!.changeSource).toBe("lead_coaching");
|
|
82
|
+
expect(version!.changedByAgentId).toBe(leadId);
|
|
83
|
+
});
|
|
84
|
+
|
|
85
|
+
test("lead updates worker name", () => {
|
|
86
|
+
const updated = updateAgentName(workerId, "Renamed Worker");
|
|
87
|
+
expect(updated).not.toBeNull();
|
|
88
|
+
expect(updated!.name).toBe("Renamed Worker");
|
|
89
|
+
|
|
90
|
+
// Rename back for other tests
|
|
91
|
+
updateAgentName(workerId, "Test Worker");
|
|
92
|
+
});
|
|
93
|
+
});
|
|
94
|
+
|
|
95
|
+
describe("non-lead is rejected when providing agentId", () => {
|
|
96
|
+
test("non-lead agent cannot update another agent's profile", () => {
|
|
97
|
+
const callingAgent = getAgentById(workerId);
|
|
98
|
+
expect(callingAgent).not.toBeNull();
|
|
99
|
+
expect(callingAgent!.isLead).toBe(false);
|
|
100
|
+
|
|
101
|
+
// The tool handler checks isLead before calling DB functions.
|
|
102
|
+
// Simulate the authorization check the tool performs:
|
|
103
|
+
const isUpdatingSelf = otherWorkerId === workerId; // false — different agent
|
|
104
|
+
expect(isUpdatingSelf).toBe(false);
|
|
105
|
+
|
|
106
|
+
// Non-lead should be rejected
|
|
107
|
+
const canUpdate = callingAgent!.isLead;
|
|
108
|
+
expect(canUpdate).toBe(false);
|
|
109
|
+
});
|
|
110
|
+
|
|
111
|
+
test("non-lead agent cannot update lead's profile", () => {
|
|
112
|
+
const callingAgent = getAgentById(workerId);
|
|
113
|
+
expect(callingAgent!.isLead).toBe(false);
|
|
114
|
+
|
|
115
|
+
const isUpdatingSelf = leadId === workerId; // false
|
|
116
|
+
expect(isUpdatingSelf).toBe(false);
|
|
117
|
+
|
|
118
|
+
// Authorization would reject this
|
|
119
|
+
expect(callingAgent!.isLead).toBe(false);
|
|
120
|
+
});
|
|
121
|
+
});
|
|
122
|
+
|
|
123
|
+
describe("self-update via explicit agentId works without lead check", () => {
|
|
124
|
+
test("agent providing own agentId is treated as self-update", () => {
|
|
125
|
+
// Simulate: agentId param = caller's own ID
|
|
126
|
+
const requestAgentId = workerId;
|
|
127
|
+
const paramAgentId = workerId;
|
|
128
|
+
const isUpdatingSelf = !paramAgentId || paramAgentId === requestAgentId;
|
|
129
|
+
expect(isUpdatingSelf).toBe(true);
|
|
130
|
+
|
|
131
|
+
// Self-update should succeed regardless of isLead status
|
|
132
|
+
const updated = updateAgentProfile(
|
|
133
|
+
workerId,
|
|
134
|
+
{ description: "Self-updated via explicit agentId" },
|
|
135
|
+
{ changeSource: "self_edit", changedByAgentId: workerId },
|
|
136
|
+
);
|
|
137
|
+
expect(updated).not.toBeNull();
|
|
138
|
+
expect(updated!.description).toBe("Self-updated via explicit agentId");
|
|
139
|
+
});
|
|
140
|
+
|
|
141
|
+
test("self-update with omitted agentId also works", () => {
|
|
142
|
+
// When agentId is undefined, isUpdatingSelf = true
|
|
143
|
+
const agentId = undefined;
|
|
144
|
+
const isUpdatingSelf = !agentId || agentId === workerId;
|
|
145
|
+
expect(isUpdatingSelf).toBe(true);
|
|
146
|
+
|
|
147
|
+
const updated = updateAgentProfile(
|
|
148
|
+
workerId,
|
|
149
|
+
{ role: "updated-role" },
|
|
150
|
+
{ changeSource: "self_edit", changedByAgentId: workerId },
|
|
151
|
+
);
|
|
152
|
+
expect(updated).not.toBeNull();
|
|
153
|
+
expect(updated!.role).toBe("updated-role");
|
|
154
|
+
});
|
|
155
|
+
});
|
|
156
|
+
|
|
157
|
+
describe("invalid agentId returns appropriate error", () => {
|
|
158
|
+
test("updateAgentProfile returns null for non-existent target agent", () => {
|
|
159
|
+
const result = updateAgentProfile(
|
|
160
|
+
nonExistentId,
|
|
161
|
+
{ description: "Should fail" },
|
|
162
|
+
{ changeSource: "lead_coaching", changedByAgentId: leadId },
|
|
163
|
+
);
|
|
164
|
+
expect(result).toBeNull();
|
|
165
|
+
});
|
|
166
|
+
|
|
167
|
+
test("updateAgentName returns null for non-existent target agent", () => {
|
|
168
|
+
const result = updateAgentName(nonExistentId, "Ghost Agent");
|
|
169
|
+
expect(result).toBeNull();
|
|
170
|
+
});
|
|
171
|
+
|
|
172
|
+
test("getAgentById returns null for non-existent agent", () => {
|
|
173
|
+
const agent = getAgentById(nonExistentId);
|
|
174
|
+
expect(agent).toBeNull();
|
|
175
|
+
});
|
|
176
|
+
});
|
|
177
|
+
|
|
178
|
+
describe("changeSource is correct for remote vs self updates", () => {
|
|
179
|
+
test("lead_coaching changeSource for lead updating worker identityMd", () => {
|
|
180
|
+
updateAgentProfile(
|
|
181
|
+
otherWorkerId,
|
|
182
|
+
{ identityMd: "# Identity set by lead" },
|
|
183
|
+
{ changeSource: "lead_coaching", changedByAgentId: leadId },
|
|
184
|
+
);
|
|
185
|
+
|
|
186
|
+
const version = getLatestContextVersion(otherWorkerId, "identityMd");
|
|
187
|
+
expect(version).not.toBeNull();
|
|
188
|
+
expect(version!.changeSource).toBe("lead_coaching");
|
|
189
|
+
expect(version!.changedByAgentId).toBe(leadId);
|
|
190
|
+
});
|
|
191
|
+
|
|
192
|
+
test("self_edit changeSource for agent updating own claudeMd", () => {
|
|
193
|
+
updateAgentProfile(
|
|
194
|
+
workerId,
|
|
195
|
+
{ claudeMd: "# My notes" },
|
|
196
|
+
{ changeSource: "self_edit", changedByAgentId: workerId },
|
|
197
|
+
);
|
|
198
|
+
|
|
199
|
+
const version = getLatestContextVersion(workerId, "claudeMd");
|
|
200
|
+
expect(version).not.toBeNull();
|
|
201
|
+
expect(version!.changeSource).toBe("self_edit");
|
|
202
|
+
expect(version!.changedByAgentId).toBe(workerId);
|
|
203
|
+
});
|
|
204
|
+
|
|
205
|
+
test("lead_coaching changeSource for lead updating worker toolsMd", () => {
|
|
206
|
+
updateAgentProfile(
|
|
207
|
+
workerId,
|
|
208
|
+
{ toolsMd: "# Tools set by lead" },
|
|
209
|
+
{ changeSource: "lead_coaching", changedByAgentId: leadId },
|
|
210
|
+
);
|
|
211
|
+
|
|
212
|
+
const version = getLatestContextVersion(workerId, "toolsMd");
|
|
213
|
+
expect(version).not.toBeNull();
|
|
214
|
+
expect(version!.changeSource).toBe("lead_coaching");
|
|
215
|
+
expect(version!.changedByAgentId).toBe(leadId);
|
|
216
|
+
});
|
|
217
|
+
|
|
218
|
+
test("self_edit changeSource for agent updating own setupScript", () => {
|
|
219
|
+
updateAgentProfile(
|
|
220
|
+
otherWorkerId,
|
|
221
|
+
{ setupScript: "echo hello" },
|
|
222
|
+
{ changeSource: "self_edit", changedByAgentId: otherWorkerId },
|
|
223
|
+
);
|
|
224
|
+
|
|
225
|
+
const version = getLatestContextVersion(otherWorkerId, "setupScript");
|
|
226
|
+
expect(version).not.toBeNull();
|
|
227
|
+
expect(version!.changeSource).toBe("self_edit");
|
|
228
|
+
expect(version!.changedByAgentId).toBe(otherWorkerId);
|
|
229
|
+
});
|
|
230
|
+
});
|
|
231
|
+
|
|
232
|
+
// ==========================================================================
|
|
233
|
+
// Target agent existence validation (non-blocking suggestion)
|
|
234
|
+
// ==========================================================================
|
|
235
|
+
|
|
236
|
+
describe("target agent existence validation", () => {
|
|
237
|
+
test("getAgentById can validate target exists before update", () => {
|
|
238
|
+
// The tool should validate the target agent exists before attempting updates
|
|
239
|
+
const targetAgent = getAgentById(nonExistentId);
|
|
240
|
+
expect(targetAgent).toBeNull();
|
|
241
|
+
|
|
242
|
+
// Valid target should be found
|
|
243
|
+
const validTarget = getAgentById(workerId);
|
|
244
|
+
expect(validTarget).not.toBeNull();
|
|
245
|
+
expect(validTarget!.id).toBe(workerId);
|
|
246
|
+
});
|
|
247
|
+
});
|
|
248
|
+
});
|
|
@@ -30,7 +30,14 @@ async function handleRequest(
|
|
|
30
30
|
) {
|
|
31
31
|
const agentId = pathSegments[2];
|
|
32
32
|
|
|
33
|
-
let body: {
|
|
33
|
+
let body: {
|
|
34
|
+
role?: string;
|
|
35
|
+
description?: string;
|
|
36
|
+
capabilities?: string[];
|
|
37
|
+
claudeMd?: string;
|
|
38
|
+
soulMd?: string;
|
|
39
|
+
identityMd?: string;
|
|
40
|
+
};
|
|
34
41
|
try {
|
|
35
42
|
body = JSON.parse(bodyText);
|
|
36
43
|
} catch {
|
|
@@ -42,13 +49,15 @@ async function handleRequest(
|
|
|
42
49
|
body.role === undefined &&
|
|
43
50
|
body.description === undefined &&
|
|
44
51
|
body.capabilities === undefined &&
|
|
45
|
-
body.claudeMd === undefined
|
|
52
|
+
body.claudeMd === undefined &&
|
|
53
|
+
body.soulMd === undefined &&
|
|
54
|
+
body.identityMd === undefined
|
|
46
55
|
) {
|
|
47
56
|
return {
|
|
48
57
|
status: 400,
|
|
49
58
|
body: {
|
|
50
59
|
error:
|
|
51
|
-
"At least one field (role, description, capabilities, or
|
|
60
|
+
"At least one field (role, description, capabilities, claudeMd, soulMd, or identityMd) must be provided",
|
|
52
61
|
},
|
|
53
62
|
};
|
|
54
63
|
}
|
|
@@ -68,11 +77,23 @@ async function handleRequest(
|
|
|
68
77
|
return { status: 400, body: { error: "claudeMd must be 64KB or less" } };
|
|
69
78
|
}
|
|
70
79
|
|
|
80
|
+
// Validate soulMd size if provided (max 64KB)
|
|
81
|
+
if (body.soulMd !== undefined && body.soulMd.length > 65536) {
|
|
82
|
+
return { status: 400, body: { error: "soulMd must be 64KB or less" } };
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
// Validate identityMd size if provided (max 64KB)
|
|
86
|
+
if (body.identityMd !== undefined && body.identityMd.length > 65536) {
|
|
87
|
+
return { status: 400, body: { error: "identityMd must be 64KB or less" } };
|
|
88
|
+
}
|
|
89
|
+
|
|
71
90
|
const agent = updateAgentProfile(agentId, {
|
|
72
91
|
role: body.role,
|
|
73
92
|
description: body.description,
|
|
74
93
|
capabilities: body.capabilities,
|
|
75
94
|
claudeMd: body.claudeMd,
|
|
95
|
+
soulMd: body.soulMd,
|
|
96
|
+
identityMd: body.identityMd,
|
|
76
97
|
});
|
|
77
98
|
|
|
78
99
|
if (!agent) {
|
|
@@ -558,4 +579,123 @@ describe("PUT /api/agents/:id/profile", () => {
|
|
|
558
579
|
expect(data.claudeMd).toBe("My notes");
|
|
559
580
|
});
|
|
560
581
|
});
|
|
582
|
+
|
|
583
|
+
describe("Update soulMd", () => {
|
|
584
|
+
test("should update agent soulMd successfully", async () => {
|
|
585
|
+
const agentId = "test-agent-soulmd-update";
|
|
586
|
+
createAgent({
|
|
587
|
+
id: agentId,
|
|
588
|
+
name: "Test Agent SoulMd Update",
|
|
589
|
+
isLead: false,
|
|
590
|
+
status: "idle",
|
|
591
|
+
});
|
|
592
|
+
|
|
593
|
+
const soulContent = "# SOUL.md — Test Agent\n\nYou are a coding specialist.";
|
|
594
|
+
const response = await fetch(`${baseUrl}/api/agents/${agentId}/profile`, {
|
|
595
|
+
method: "PUT",
|
|
596
|
+
headers: { "Content-Type": "application/json" },
|
|
597
|
+
body: JSON.stringify({ soulMd: soulContent }),
|
|
598
|
+
});
|
|
599
|
+
|
|
600
|
+
expect(response.status).toBe(200);
|
|
601
|
+
const data = (await response.json()) as { id?: string; soulMd?: string };
|
|
602
|
+
expect(data.id).toBe(agentId);
|
|
603
|
+
expect(data.soulMd).toBe(soulContent);
|
|
604
|
+
|
|
605
|
+
// Verify in database
|
|
606
|
+
const agent = getAgentById(agentId);
|
|
607
|
+
expect(agent?.soulMd).toBe(soulContent);
|
|
608
|
+
});
|
|
609
|
+
|
|
610
|
+
test("should return 400 if soulMd exceeds 64KB", async () => {
|
|
611
|
+
const largeContent = "a".repeat(65537);
|
|
612
|
+
const response = await fetch(`${baseUrl}/api/agents/test-agent/profile`, {
|
|
613
|
+
method: "PUT",
|
|
614
|
+
headers: { "Content-Type": "application/json" },
|
|
615
|
+
body: JSON.stringify({ soulMd: largeContent }),
|
|
616
|
+
});
|
|
617
|
+
|
|
618
|
+
expect(response.status).toBe(400);
|
|
619
|
+
const data = (await response.json()) as { error?: string };
|
|
620
|
+
expect(data.error).toContain("64KB");
|
|
621
|
+
});
|
|
622
|
+
});
|
|
623
|
+
|
|
624
|
+
describe("Update identityMd", () => {
|
|
625
|
+
test("should update agent identityMd successfully", async () => {
|
|
626
|
+
const agentId = "test-agent-identitymd-update";
|
|
627
|
+
createAgent({
|
|
628
|
+
id: agentId,
|
|
629
|
+
name: "Test Agent IdentityMd Update",
|
|
630
|
+
isLead: false,
|
|
631
|
+
status: "idle",
|
|
632
|
+
});
|
|
633
|
+
|
|
634
|
+
const identityContent = "# IDENTITY.md — Test Agent\n\nRole: developer\nVibe: focused";
|
|
635
|
+
const response = await fetch(`${baseUrl}/api/agents/${agentId}/profile`, {
|
|
636
|
+
method: "PUT",
|
|
637
|
+
headers: { "Content-Type": "application/json" },
|
|
638
|
+
body: JSON.stringify({ identityMd: identityContent }),
|
|
639
|
+
});
|
|
640
|
+
|
|
641
|
+
expect(response.status).toBe(200);
|
|
642
|
+
const data = (await response.json()) as { id?: string; identityMd?: string };
|
|
643
|
+
expect(data.id).toBe(agentId);
|
|
644
|
+
expect(data.identityMd).toBe(identityContent);
|
|
645
|
+
|
|
646
|
+
// Verify in database
|
|
647
|
+
const agent = getAgentById(agentId);
|
|
648
|
+
expect(agent?.identityMd).toBe(identityContent);
|
|
649
|
+
});
|
|
650
|
+
|
|
651
|
+
test("should return 400 if identityMd exceeds 64KB", async () => {
|
|
652
|
+
const largeContent = "a".repeat(65537);
|
|
653
|
+
const response = await fetch(`${baseUrl}/api/agents/test-agent/profile`, {
|
|
654
|
+
method: "PUT",
|
|
655
|
+
headers: { "Content-Type": "application/json" },
|
|
656
|
+
body: JSON.stringify({ identityMd: largeContent }),
|
|
657
|
+
});
|
|
658
|
+
|
|
659
|
+
expect(response.status).toBe(400);
|
|
660
|
+
const data = (await response.json()) as { error?: string };
|
|
661
|
+
expect(data.error).toContain("64KB");
|
|
662
|
+
});
|
|
663
|
+
|
|
664
|
+
test("should preserve soulMd and identityMd when updating other fields", async () => {
|
|
665
|
+
const agentId = "test-agent-identity-preserve";
|
|
666
|
+
createAgent({
|
|
667
|
+
id: agentId,
|
|
668
|
+
name: "Test Agent Identity Preserve",
|
|
669
|
+
isLead: false,
|
|
670
|
+
status: "idle",
|
|
671
|
+
});
|
|
672
|
+
|
|
673
|
+
// Set initial soul and identity
|
|
674
|
+
await fetch(`${baseUrl}/api/agents/${agentId}/profile`, {
|
|
675
|
+
method: "PUT",
|
|
676
|
+
headers: { "Content-Type": "application/json" },
|
|
677
|
+
body: JSON.stringify({
|
|
678
|
+
soulMd: "My soul",
|
|
679
|
+
identityMd: "My identity",
|
|
680
|
+
}),
|
|
681
|
+
});
|
|
682
|
+
|
|
683
|
+
// Update only the role
|
|
684
|
+
const response = await fetch(`${baseUrl}/api/agents/${agentId}/profile`, {
|
|
685
|
+
method: "PUT",
|
|
686
|
+
headers: { "Content-Type": "application/json" },
|
|
687
|
+
body: JSON.stringify({ role: "Developer" }),
|
|
688
|
+
});
|
|
689
|
+
|
|
690
|
+
expect(response.status).toBe(200);
|
|
691
|
+
const data = (await response.json()) as {
|
|
692
|
+
role?: string;
|
|
693
|
+
soulMd?: string;
|
|
694
|
+
identityMd?: string;
|
|
695
|
+
};
|
|
696
|
+
expect(data.role).toBe("Developer");
|
|
697
|
+
expect(data.soulMd).toBe("My soul");
|
|
698
|
+
expect(data.identityMd).toBe("My identity");
|
|
699
|
+
});
|
|
700
|
+
});
|
|
561
701
|
});
|
|
@@ -0,0 +1,195 @@
|
|
|
1
|
+
import { afterAll, beforeAll, describe, expect, test } from "bun:test";
|
|
2
|
+
import { unlink } from "node:fs/promises";
|
|
3
|
+
import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
|
|
4
|
+
import { closeDb, createAgent, getAgentById, getLatestContextVersion, initDb } from "../be/db";
|
|
5
|
+
import { registerUpdateProfileTool } from "../tools/update-profile";
|
|
6
|
+
|
|
7
|
+
const TEST_DB_PATH = "./test-update-profile-auth.sqlite";
|
|
8
|
+
|
|
9
|
+
const LEAD_ID = "aaaa0000-0000-4000-8000-000000000001";
|
|
10
|
+
const WORKER_ID = "bbbb0000-0000-4000-8000-000000000002";
|
|
11
|
+
const OTHER_WORKER_ID = "cccc0000-0000-4000-8000-000000000003";
|
|
12
|
+
const NONEXISTENT_ID = "dddd0000-0000-4000-8000-000000000099";
|
|
13
|
+
|
|
14
|
+
type StructuredContent = {
|
|
15
|
+
yourAgentId?: string;
|
|
16
|
+
success: boolean;
|
|
17
|
+
message: string;
|
|
18
|
+
agent?: { id: string; role?: string; description?: string };
|
|
19
|
+
};
|
|
20
|
+
|
|
21
|
+
/**
|
|
22
|
+
* Call the update-profile tool handler directly via the MCP server's internal registry.
|
|
23
|
+
* This bypasses the MCP transport layer but exercises the full tool handler logic
|
|
24
|
+
* including authorization checks.
|
|
25
|
+
*/
|
|
26
|
+
async function callUpdateProfile(
|
|
27
|
+
server: McpServer,
|
|
28
|
+
callerAgentId: string | undefined,
|
|
29
|
+
args: Record<string, unknown>,
|
|
30
|
+
): Promise<{ structuredContent: StructuredContent }> {
|
|
31
|
+
const tools = (server as unknown as { _registeredTools: Record<string, { handler: Function }> })
|
|
32
|
+
._registeredTools;
|
|
33
|
+
const handler = tools["update-profile"].handler;
|
|
34
|
+
|
|
35
|
+
// The handler receives (args, extra) where extra includes requestInfo headers.
|
|
36
|
+
// createToolRegistrar extracts agentId from extra.requestInfo.headers["x-agent-id"].
|
|
37
|
+
const extra = {
|
|
38
|
+
sessionId: "test-session",
|
|
39
|
+
requestInfo: {
|
|
40
|
+
headers: {
|
|
41
|
+
"x-agent-id": callerAgentId ?? "",
|
|
42
|
+
},
|
|
43
|
+
},
|
|
44
|
+
};
|
|
45
|
+
|
|
46
|
+
const result = await handler(args, extra);
|
|
47
|
+
return result as { structuredContent: StructuredContent };
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
describe("update-profile authorization", () => {
|
|
51
|
+
let server: McpServer;
|
|
52
|
+
|
|
53
|
+
beforeAll(async () => {
|
|
54
|
+
for (const suffix of ["", "-wal", "-shm"]) {
|
|
55
|
+
try {
|
|
56
|
+
await unlink(TEST_DB_PATH + suffix);
|
|
57
|
+
} catch {
|
|
58
|
+
// File doesn't exist
|
|
59
|
+
}
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
closeDb();
|
|
63
|
+
initDb(TEST_DB_PATH);
|
|
64
|
+
|
|
65
|
+
createAgent({ id: LEAD_ID, name: "Test Lead", isLead: true, status: "idle" });
|
|
66
|
+
createAgent({ id: WORKER_ID, name: "Test Worker", isLead: false, status: "idle" });
|
|
67
|
+
createAgent({ id: OTHER_WORKER_ID, name: "Other Worker", isLead: false, status: "idle" });
|
|
68
|
+
|
|
69
|
+
server = new McpServer({ name: "test-update-profile-auth", version: "1.0.0" });
|
|
70
|
+
registerUpdateProfileTool(server);
|
|
71
|
+
});
|
|
72
|
+
|
|
73
|
+
afterAll(async () => {
|
|
74
|
+
closeDb();
|
|
75
|
+
for (const suffix of ["", "-wal", "-shm"]) {
|
|
76
|
+
try {
|
|
77
|
+
await unlink(TEST_DB_PATH + suffix);
|
|
78
|
+
} catch {
|
|
79
|
+
// ignore
|
|
80
|
+
}
|
|
81
|
+
}
|
|
82
|
+
});
|
|
83
|
+
|
|
84
|
+
// ===================================================================
|
|
85
|
+
// 1. Lead can update another agent's profile (happy path)
|
|
86
|
+
// ===================================================================
|
|
87
|
+
test("lead can update another agent's profile", async () => {
|
|
88
|
+
const result = await callUpdateProfile(server, LEAD_ID, {
|
|
89
|
+
agentId: WORKER_ID,
|
|
90
|
+
role: "Senior Developer",
|
|
91
|
+
});
|
|
92
|
+
|
|
93
|
+
expect(result.structuredContent.success).toBe(true);
|
|
94
|
+
expect(result.structuredContent.message).toContain(WORKER_ID);
|
|
95
|
+
expect(result.structuredContent.agent?.role).toBe("Senior Developer");
|
|
96
|
+
|
|
97
|
+
// Verify DB was updated
|
|
98
|
+
const agent = getAgentById(WORKER_ID);
|
|
99
|
+
expect(agent?.role).toBe("Senior Developer");
|
|
100
|
+
});
|
|
101
|
+
|
|
102
|
+
// ===================================================================
|
|
103
|
+
// 2. Non-lead is rejected when providing agentId (targeting another agent)
|
|
104
|
+
// ===================================================================
|
|
105
|
+
test("non-lead is rejected when targeting another agent", async () => {
|
|
106
|
+
const result = await callUpdateProfile(server, WORKER_ID, {
|
|
107
|
+
agentId: OTHER_WORKER_ID,
|
|
108
|
+
role: "Hacked Role",
|
|
109
|
+
});
|
|
110
|
+
|
|
111
|
+
expect(result.structuredContent.success).toBe(false);
|
|
112
|
+
expect(result.structuredContent.message).toContain("Only lead agents");
|
|
113
|
+
|
|
114
|
+
// Verify DB was NOT updated
|
|
115
|
+
const agent = getAgentById(OTHER_WORKER_ID);
|
|
116
|
+
expect(agent?.role).not.toBe("Hacked Role");
|
|
117
|
+
});
|
|
118
|
+
|
|
119
|
+
// ===================================================================
|
|
120
|
+
// 3. Self-update via explicit agentId (matching own ID) works without lead check
|
|
121
|
+
// ===================================================================
|
|
122
|
+
test("self-update via explicit own agentId works for non-lead", async () => {
|
|
123
|
+
const result = await callUpdateProfile(server, OTHER_WORKER_ID, {
|
|
124
|
+
agentId: OTHER_WORKER_ID,
|
|
125
|
+
description: "I updated myself",
|
|
126
|
+
});
|
|
127
|
+
|
|
128
|
+
expect(result.structuredContent.success).toBe(true);
|
|
129
|
+
expect(result.structuredContent.message).toContain("own");
|
|
130
|
+
|
|
131
|
+
// Verify DB was updated
|
|
132
|
+
const agent = getAgentById(OTHER_WORKER_ID);
|
|
133
|
+
expect(agent?.description).toBe("I updated myself");
|
|
134
|
+
});
|
|
135
|
+
|
|
136
|
+
// ===================================================================
|
|
137
|
+
// 4. Invalid agentId returns appropriate error
|
|
138
|
+
// ===================================================================
|
|
139
|
+
test("invalid agentId returns error when lead updates non-existent agent", async () => {
|
|
140
|
+
const result = await callUpdateProfile(server, LEAD_ID, {
|
|
141
|
+
agentId: NONEXISTENT_ID,
|
|
142
|
+
role: "Ghost Role",
|
|
143
|
+
});
|
|
144
|
+
|
|
145
|
+
// The tool should fail because the target agent doesn't exist in the DB
|
|
146
|
+
expect(result.structuredContent.success).toBe(false);
|
|
147
|
+
expect(result.structuredContent.message).toContain("not found");
|
|
148
|
+
});
|
|
149
|
+
|
|
150
|
+
// ===================================================================
|
|
151
|
+
// 5. changeSource is correct for remote vs self updates
|
|
152
|
+
// ===================================================================
|
|
153
|
+
test("changeSource is 'lead_coaching' for remote updates", async () => {
|
|
154
|
+
const result = await callUpdateProfile(server, LEAD_ID, {
|
|
155
|
+
agentId: WORKER_ID,
|
|
156
|
+
soulMd: "Updated by lead",
|
|
157
|
+
});
|
|
158
|
+
|
|
159
|
+
expect(result.structuredContent.success).toBe(true);
|
|
160
|
+
|
|
161
|
+
// Verify the context version has the correct changeSource
|
|
162
|
+
const version = getLatestContextVersion(WORKER_ID, "soulMd");
|
|
163
|
+
expect(version).not.toBeNull();
|
|
164
|
+
expect(version!.changeSource).toBe("lead_coaching");
|
|
165
|
+
expect(version!.changedByAgentId).toBe(LEAD_ID);
|
|
166
|
+
});
|
|
167
|
+
|
|
168
|
+
test("changeSource is 'self_edit' for self updates", async () => {
|
|
169
|
+
const result = await callUpdateProfile(server, WORKER_ID, {
|
|
170
|
+
soulMd: "Updated by myself",
|
|
171
|
+
});
|
|
172
|
+
|
|
173
|
+
expect(result.structuredContent.success).toBe(true);
|
|
174
|
+
|
|
175
|
+
// Verify the context version has the correct changeSource
|
|
176
|
+
const version = getLatestContextVersion(WORKER_ID, "soulMd");
|
|
177
|
+
expect(version).not.toBeNull();
|
|
178
|
+
expect(version!.changeSource).toBe("self_edit");
|
|
179
|
+
expect(version!.changedByAgentId).toBe(WORKER_ID);
|
|
180
|
+
});
|
|
181
|
+
|
|
182
|
+
test("changeSource is 'self_edit' when explicit agentId matches caller", async () => {
|
|
183
|
+
const result = await callUpdateProfile(server, WORKER_ID, {
|
|
184
|
+
agentId: WORKER_ID,
|
|
185
|
+
identityMd: "Self update with explicit ID",
|
|
186
|
+
});
|
|
187
|
+
|
|
188
|
+
expect(result.structuredContent.success).toBe(true);
|
|
189
|
+
expect(result.structuredContent.message).toContain("own");
|
|
190
|
+
|
|
191
|
+
const version = getLatestContextVersion(WORKER_ID, "identityMd");
|
|
192
|
+
expect(version).not.toBeNull();
|
|
193
|
+
expect(version!.changeSource).toBe("self_edit");
|
|
194
|
+
});
|
|
195
|
+
});
|