@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,298 @@
|
|
|
1
|
+
import { afterAll, beforeAll, describe, expect, test } from "bun:test";
|
|
2
|
+
import { unlinkSync } from "node:fs";
|
|
3
|
+
import {
|
|
4
|
+
closeDb,
|
|
5
|
+
createAgent,
|
|
6
|
+
createTaskExtended,
|
|
7
|
+
getCompletedSlackTasks,
|
|
8
|
+
getInProgressSlackTasks,
|
|
9
|
+
getMostRecentTaskInThread,
|
|
10
|
+
initDb,
|
|
11
|
+
} from "../be/db";
|
|
12
|
+
import { routeMessage } from "../slack/router";
|
|
13
|
+
|
|
14
|
+
const TEST_DB_PATH = "./test-slack-thread-followups.sqlite";
|
|
15
|
+
|
|
16
|
+
beforeAll(() => {
|
|
17
|
+
initDb(TEST_DB_PATH);
|
|
18
|
+
});
|
|
19
|
+
|
|
20
|
+
afterAll(() => {
|
|
21
|
+
closeDb();
|
|
22
|
+
try {
|
|
23
|
+
unlinkSync(TEST_DB_PATH);
|
|
24
|
+
unlinkSync(`${TEST_DB_PATH}-wal`);
|
|
25
|
+
unlinkSync(`${TEST_DB_PATH}-shm`);
|
|
26
|
+
} catch {
|
|
27
|
+
// ignore if files don't exist
|
|
28
|
+
}
|
|
29
|
+
});
|
|
30
|
+
|
|
31
|
+
describe("getMostRecentTaskInThread", () => {
|
|
32
|
+
test("returns null when no tasks exist for thread", () => {
|
|
33
|
+
const result = getMostRecentTaskInThread("C_EMPTY", "9999.0001");
|
|
34
|
+
expect(result).toBeNull();
|
|
35
|
+
});
|
|
36
|
+
|
|
37
|
+
test("returns a task regardless of source (slack, system, etc.)", () => {
|
|
38
|
+
const agent = createAgent({
|
|
39
|
+
name: "source-test-agent",
|
|
40
|
+
isLead: false,
|
|
41
|
+
status: "idle",
|
|
42
|
+
capabilities: [],
|
|
43
|
+
});
|
|
44
|
+
|
|
45
|
+
// Create tasks with different sources
|
|
46
|
+
const slackTask = createTaskExtended("slack source task", {
|
|
47
|
+
agentId: agent.id,
|
|
48
|
+
source: "slack",
|
|
49
|
+
slackChannelId: "C_SOURCE",
|
|
50
|
+
slackThreadTs: "1000.0001",
|
|
51
|
+
});
|
|
52
|
+
|
|
53
|
+
const systemTask = createTaskExtended("system source task", {
|
|
54
|
+
agentId: agent.id,
|
|
55
|
+
source: "system",
|
|
56
|
+
slackChannelId: "C_SOURCE",
|
|
57
|
+
slackThreadTs: "1000.0001",
|
|
58
|
+
});
|
|
59
|
+
|
|
60
|
+
const result = getMostRecentTaskInThread("C_SOURCE", "1000.0001");
|
|
61
|
+
expect(result).not.toBeNull();
|
|
62
|
+
// Should return one of the tasks — importantly, it doesn't filter by source
|
|
63
|
+
expect([slackTask.id, systemTask.id]).toContain(result!.id);
|
|
64
|
+
});
|
|
65
|
+
|
|
66
|
+
test("returns a task regardless of status (completed, pending, etc.)", () => {
|
|
67
|
+
const agent = createAgent({
|
|
68
|
+
name: "status-test-agent",
|
|
69
|
+
isLead: false,
|
|
70
|
+
status: "idle",
|
|
71
|
+
capabilities: [],
|
|
72
|
+
});
|
|
73
|
+
|
|
74
|
+
const task1 = createTaskExtended("completed task", {
|
|
75
|
+
agentId: agent.id,
|
|
76
|
+
source: "slack",
|
|
77
|
+
slackChannelId: "C_STATUS",
|
|
78
|
+
slackThreadTs: "2000.0001",
|
|
79
|
+
});
|
|
80
|
+
|
|
81
|
+
const task2 = createTaskExtended("in progress task", {
|
|
82
|
+
agentId: agent.id,
|
|
83
|
+
source: "slack",
|
|
84
|
+
slackChannelId: "C_STATUS",
|
|
85
|
+
slackThreadTs: "2000.0001",
|
|
86
|
+
});
|
|
87
|
+
|
|
88
|
+
const result = getMostRecentTaskInThread("C_STATUS", "2000.0001");
|
|
89
|
+
expect(result).not.toBeNull();
|
|
90
|
+
// Returns one of the tasks — importantly, it doesn't filter by status
|
|
91
|
+
expect([task1.id, task2.id]).toContain(result!.id);
|
|
92
|
+
});
|
|
93
|
+
|
|
94
|
+
test("returns task with source=null (worker tasks via send-task)", () => {
|
|
95
|
+
const agent = createAgent({
|
|
96
|
+
name: "null-source-agent",
|
|
97
|
+
isLead: false,
|
|
98
|
+
status: "idle",
|
|
99
|
+
capabilities: [],
|
|
100
|
+
});
|
|
101
|
+
|
|
102
|
+
// Worker tasks created via send-task default to source=null — simulate with no source
|
|
103
|
+
const workerTask = createTaskExtended("worker task", {
|
|
104
|
+
agentId: agent.id,
|
|
105
|
+
slackChannelId: "C_WORKER",
|
|
106
|
+
slackThreadTs: "3000.0001",
|
|
107
|
+
});
|
|
108
|
+
|
|
109
|
+
const result = getMostRecentTaskInThread("C_WORKER", "3000.0001");
|
|
110
|
+
expect(result).not.toBeNull();
|
|
111
|
+
expect(result!.id).toBe(workerTask.id);
|
|
112
|
+
});
|
|
113
|
+
});
|
|
114
|
+
|
|
115
|
+
describe("routeMessage — thread follow-up with offline agent", () => {
|
|
116
|
+
let leadAgent: ReturnType<typeof createAgent>;
|
|
117
|
+
let workerAgent: ReturnType<typeof createAgent>;
|
|
118
|
+
|
|
119
|
+
beforeAll(() => {
|
|
120
|
+
leadAgent = createAgent({ name: "route-lead", isLead: true, status: "idle", capabilities: [] });
|
|
121
|
+
workerAgent = createAgent({
|
|
122
|
+
name: "route-worker",
|
|
123
|
+
isLead: false,
|
|
124
|
+
status: "offline",
|
|
125
|
+
capabilities: [],
|
|
126
|
+
});
|
|
127
|
+
|
|
128
|
+
// Create a task in the thread so getAgentWorkingOnThread finds the offline worker
|
|
129
|
+
createTaskExtended("original task", {
|
|
130
|
+
agentId: workerAgent.id,
|
|
131
|
+
source: "slack",
|
|
132
|
+
slackChannelId: "C_ROUTE",
|
|
133
|
+
slackThreadTs: "4000.0001",
|
|
134
|
+
});
|
|
135
|
+
});
|
|
136
|
+
|
|
137
|
+
test("routes to lead when thread agent is offline and bot NOT mentioned", () => {
|
|
138
|
+
const matches = routeMessage("follow up question", "B_BOT", false, {
|
|
139
|
+
channelId: "C_ROUTE",
|
|
140
|
+
threadTs: "4000.0001",
|
|
141
|
+
});
|
|
142
|
+
|
|
143
|
+
expect(matches.length).toBe(1);
|
|
144
|
+
expect(matches[0].agent.id).toBe(leadAgent.id);
|
|
145
|
+
expect(matches[0].matchedText).toBe("thread follow-up (lead fallback)");
|
|
146
|
+
});
|
|
147
|
+
|
|
148
|
+
test("routes to lead when thread agent is offline and bot IS mentioned", () => {
|
|
149
|
+
// With botMentioned=true and thread context, the thread follow-up path should still fire
|
|
150
|
+
// because it checks before the botMentioned fallback
|
|
151
|
+
const matches = routeMessage("follow up question", "B_BOT", true, {
|
|
152
|
+
channelId: "C_ROUTE",
|
|
153
|
+
threadTs: "4000.0001",
|
|
154
|
+
});
|
|
155
|
+
|
|
156
|
+
expect(matches.length).toBe(1);
|
|
157
|
+
expect(matches[0].agent.id).toBe(leadAgent.id);
|
|
158
|
+
expect(matches[0].matchedText).toBe("thread follow-up (lead fallback)");
|
|
159
|
+
});
|
|
160
|
+
|
|
161
|
+
test("still routes to working agent when agent is online (no regression)", () => {
|
|
162
|
+
const onlineWorker = createAgent({
|
|
163
|
+
name: "route-online-worker",
|
|
164
|
+
isLead: false,
|
|
165
|
+
status: "busy",
|
|
166
|
+
capabilities: [],
|
|
167
|
+
});
|
|
168
|
+
|
|
169
|
+
createTaskExtended("online task", {
|
|
170
|
+
agentId: onlineWorker.id,
|
|
171
|
+
source: "slack",
|
|
172
|
+
slackChannelId: "C_ROUTE_ONLINE",
|
|
173
|
+
slackThreadTs: "5000.0001",
|
|
174
|
+
});
|
|
175
|
+
|
|
176
|
+
const matches = routeMessage("follow up", "B_BOT", false, {
|
|
177
|
+
channelId: "C_ROUTE_ONLINE",
|
|
178
|
+
threadTs: "5000.0001",
|
|
179
|
+
});
|
|
180
|
+
|
|
181
|
+
expect(matches.length).toBe(1);
|
|
182
|
+
expect(matches[0].agent.id).toBe(onlineWorker.id);
|
|
183
|
+
expect(matches[0].matchedText).toBe("thread follow-up");
|
|
184
|
+
});
|
|
185
|
+
});
|
|
186
|
+
|
|
187
|
+
describe("follow-up task creation includes parentTaskId", () => {
|
|
188
|
+
test("parentTaskId is null when thread has no previous tasks (new thread)", () => {
|
|
189
|
+
const result = getMostRecentTaskInThread("C_NEW_THREAD", "8000.0001");
|
|
190
|
+
expect(result).toBeNull();
|
|
191
|
+
});
|
|
192
|
+
|
|
193
|
+
test("parentTaskId links to most recent task even if that task is completed", () => {
|
|
194
|
+
const agent = createAgent({
|
|
195
|
+
name: "parent-test-agent",
|
|
196
|
+
isLead: false,
|
|
197
|
+
status: "idle",
|
|
198
|
+
capabilities: [],
|
|
199
|
+
});
|
|
200
|
+
|
|
201
|
+
const firstTask = createTaskExtended("first task", {
|
|
202
|
+
agentId: agent.id,
|
|
203
|
+
source: "slack",
|
|
204
|
+
slackChannelId: "C_PARENT",
|
|
205
|
+
slackThreadTs: "6000.0001",
|
|
206
|
+
});
|
|
207
|
+
|
|
208
|
+
// The most recent task should be returned for parentTaskId linking
|
|
209
|
+
const result = getMostRecentTaskInThread("C_PARENT", "6000.0001");
|
|
210
|
+
expect(result).not.toBeNull();
|
|
211
|
+
expect(result!.id).toBe(firstTask.id);
|
|
212
|
+
|
|
213
|
+
// Create a follow-up with parentTaskId
|
|
214
|
+
const followUp = createTaskExtended("follow up task", {
|
|
215
|
+
agentId: agent.id,
|
|
216
|
+
source: "slack",
|
|
217
|
+
slackChannelId: "C_PARENT",
|
|
218
|
+
slackThreadTs: "6000.0001",
|
|
219
|
+
parentTaskId: result!.id,
|
|
220
|
+
});
|
|
221
|
+
|
|
222
|
+
expect(followUp.parentTaskId).toBe(firstTask.id);
|
|
223
|
+
});
|
|
224
|
+
});
|
|
225
|
+
|
|
226
|
+
describe("watcher query scope — getCompletedSlackTasks / getInProgressSlackTasks", () => {
|
|
227
|
+
test("includes tasks with source='slack' and slackChannelId set", () => {
|
|
228
|
+
const agent = createAgent({
|
|
229
|
+
name: "watcher-slack-agent",
|
|
230
|
+
isLead: false,
|
|
231
|
+
status: "idle",
|
|
232
|
+
capabilities: [],
|
|
233
|
+
});
|
|
234
|
+
|
|
235
|
+
createTaskExtended("slack completed task", {
|
|
236
|
+
agentId: agent.id,
|
|
237
|
+
source: "slack",
|
|
238
|
+
slackChannelId: "C_WATCHER",
|
|
239
|
+
slackThreadTs: "7000.0001",
|
|
240
|
+
status: "unassigned",
|
|
241
|
+
});
|
|
242
|
+
|
|
243
|
+
const completed = getCompletedSlackTasks();
|
|
244
|
+
const inProgress = getInProgressSlackTasks();
|
|
245
|
+
// Just verify the query doesn't error — specific status filtering tested below
|
|
246
|
+
expect(Array.isArray(completed)).toBe(true);
|
|
247
|
+
expect(Array.isArray(inProgress)).toBe(true);
|
|
248
|
+
});
|
|
249
|
+
|
|
250
|
+
test("includes tasks with source=null (worker tasks) that have slackChannelId set", () => {
|
|
251
|
+
const agent = createAgent({
|
|
252
|
+
name: "watcher-null-agent",
|
|
253
|
+
isLead: false,
|
|
254
|
+
status: "idle",
|
|
255
|
+
capabilities: [],
|
|
256
|
+
});
|
|
257
|
+
|
|
258
|
+
// Worker task with no source but has Slack metadata (inherited via parentTaskId)
|
|
259
|
+
const _workerTask = createTaskExtended("worker completed", {
|
|
260
|
+
agentId: agent.id,
|
|
261
|
+
slackChannelId: "C_WATCHER_NULL",
|
|
262
|
+
slackThreadTs: "7100.0001",
|
|
263
|
+
});
|
|
264
|
+
|
|
265
|
+
// All tasks with slackChannelId should be visible regardless of source
|
|
266
|
+
const allCompleted = getCompletedSlackTasks();
|
|
267
|
+
const allInProgress = getInProgressSlackTasks();
|
|
268
|
+
// Task is pending, not completed or in_progress, so it won't appear in either
|
|
269
|
+
// But the queries shouldn't filter by source anymore
|
|
270
|
+
expect(Array.isArray(allCompleted)).toBe(true);
|
|
271
|
+
expect(Array.isArray(allInProgress)).toBe(true);
|
|
272
|
+
});
|
|
273
|
+
|
|
274
|
+
test("excludes tasks without slackChannelId (non-Slack tasks)", () => {
|
|
275
|
+
const agent = createAgent({
|
|
276
|
+
name: "watcher-no-slack-agent",
|
|
277
|
+
isLead: false,
|
|
278
|
+
status: "idle",
|
|
279
|
+
capabilities: [],
|
|
280
|
+
});
|
|
281
|
+
|
|
282
|
+
createTaskExtended("non-slack task", {
|
|
283
|
+
agentId: agent.id,
|
|
284
|
+
source: "mcp",
|
|
285
|
+
});
|
|
286
|
+
|
|
287
|
+
const completed = getCompletedSlackTasks();
|
|
288
|
+
const inProgress = getInProgressSlackTasks();
|
|
289
|
+
|
|
290
|
+
// None of the returned tasks should have null slackChannelId
|
|
291
|
+
for (const task of completed) {
|
|
292
|
+
expect(task.slackChannelId).not.toBeNull();
|
|
293
|
+
}
|
|
294
|
+
for (const task of inProgress) {
|
|
295
|
+
expect(task.slackChannelId).not.toBeNull();
|
|
296
|
+
}
|
|
297
|
+
});
|
|
298
|
+
});
|
|
@@ -0,0 +1,101 @@
|
|
|
1
|
+
import { afterAll, beforeAll, describe, expect, test } from "bun:test";
|
|
2
|
+
import { unlinkSync } from "node:fs";
|
|
3
|
+
import {
|
|
4
|
+
cancelTask,
|
|
5
|
+
closeDb,
|
|
6
|
+
createAgent,
|
|
7
|
+
createTaskExtended,
|
|
8
|
+
getCompletedSlackTasks,
|
|
9
|
+
getInProgressSlackTasks,
|
|
10
|
+
initDb,
|
|
11
|
+
} from "../be/db";
|
|
12
|
+
import { startTaskWatcher, stopTaskWatcher } from "../slack/watcher";
|
|
13
|
+
|
|
14
|
+
const TEST_DB_PATH = "./test-slack-watcher.sqlite";
|
|
15
|
+
|
|
16
|
+
beforeAll(() => {
|
|
17
|
+
initDb(TEST_DB_PATH);
|
|
18
|
+
});
|
|
19
|
+
|
|
20
|
+
afterAll(() => {
|
|
21
|
+
stopTaskWatcher();
|
|
22
|
+
closeDb();
|
|
23
|
+
try {
|
|
24
|
+
unlinkSync(TEST_DB_PATH);
|
|
25
|
+
unlinkSync(`${TEST_DB_PATH}-wal`);
|
|
26
|
+
unlinkSync(`${TEST_DB_PATH}-shm`);
|
|
27
|
+
} catch {
|
|
28
|
+
// ignore if files don't exist
|
|
29
|
+
}
|
|
30
|
+
});
|
|
31
|
+
|
|
32
|
+
describe("startTaskWatcher / stopTaskWatcher", () => {
|
|
33
|
+
test("starts and stops without error", () => {
|
|
34
|
+
startTaskWatcher(60000); // Long interval so it doesn't fire during test
|
|
35
|
+
stopTaskWatcher();
|
|
36
|
+
});
|
|
37
|
+
|
|
38
|
+
test("is idempotent — starting twice does not error", () => {
|
|
39
|
+
startTaskWatcher(60000);
|
|
40
|
+
startTaskWatcher(60000); // Should log "already running", not throw
|
|
41
|
+
stopTaskWatcher();
|
|
42
|
+
});
|
|
43
|
+
|
|
44
|
+
test("stopping when not running does not error", () => {
|
|
45
|
+
stopTaskWatcher();
|
|
46
|
+
stopTaskWatcher();
|
|
47
|
+
});
|
|
48
|
+
});
|
|
49
|
+
|
|
50
|
+
describe("watcher DB queries", () => {
|
|
51
|
+
test("getInProgressSlackTasks excludes pending tasks (only in_progress)", () => {
|
|
52
|
+
// createTaskExtended creates tasks as 'pending', not 'in_progress'
|
|
53
|
+
const agent = createAgent({ name: "WatcherTestAgent", isLead: false, status: "idle" });
|
|
54
|
+
const task = createTaskExtended("watcher pending test", {
|
|
55
|
+
agentId: agent.id,
|
|
56
|
+
source: "slack",
|
|
57
|
+
slackChannelId: "C_WATCHER",
|
|
58
|
+
slackThreadTs: "1111111111.000001",
|
|
59
|
+
slackUserId: "U_WATCHER",
|
|
60
|
+
});
|
|
61
|
+
|
|
62
|
+
const inProgress = getInProgressSlackTasks();
|
|
63
|
+
const found = inProgress.find((t) => t.id === task.id);
|
|
64
|
+
// Task is 'pending', not 'in_progress', so it should NOT appear
|
|
65
|
+
expect(found).toBeUndefined();
|
|
66
|
+
});
|
|
67
|
+
|
|
68
|
+
test("getInProgressSlackTasks returns array", () => {
|
|
69
|
+
const inProgress = getInProgressSlackTasks();
|
|
70
|
+
expect(Array.isArray(inProgress)).toBe(true);
|
|
71
|
+
});
|
|
72
|
+
|
|
73
|
+
test("getCompletedSlackTasks excludes cancelled tasks (only completed/failed)", () => {
|
|
74
|
+
const agent = createAgent({ name: "WatcherCompAgent", isLead: false, status: "idle" });
|
|
75
|
+
const task = createTaskExtended("watcher cancel test", {
|
|
76
|
+
agentId: agent.id,
|
|
77
|
+
source: "slack",
|
|
78
|
+
slackChannelId: "C_WATCHER2",
|
|
79
|
+
slackThreadTs: "2222222222.000001",
|
|
80
|
+
slackUserId: "U_WATCHER2",
|
|
81
|
+
});
|
|
82
|
+
|
|
83
|
+
cancelTask(task.id, "test cancel");
|
|
84
|
+
|
|
85
|
+
const completed = getCompletedSlackTasks();
|
|
86
|
+
const found = completed.find((t) => t.id === task.id);
|
|
87
|
+
// Cancelled tasks are NOT included in getCompletedSlackTasks (only completed/failed)
|
|
88
|
+
expect(found).toBeUndefined();
|
|
89
|
+
});
|
|
90
|
+
|
|
91
|
+
test("getCompletedSlackTasks returns array", () => {
|
|
92
|
+
const completed = getCompletedSlackTasks();
|
|
93
|
+
expect(Array.isArray(completed)).toBe(true);
|
|
94
|
+
});
|
|
95
|
+
|
|
96
|
+
test("initializes notifiedCompletions on start to skip existing completed tasks", () => {
|
|
97
|
+
// Starting the watcher with existing data should not crash
|
|
98
|
+
startTaskWatcher(60000);
|
|
99
|
+
stopTaskWatcher();
|
|
100
|
+
});
|
|
101
|
+
});
|
|
@@ -0,0 +1,307 @@
|
|
|
1
|
+
import { afterAll, beforeAll, describe, expect, test } from "bun:test";
|
|
2
|
+
import { unlink } from "node:fs/promises";
|
|
3
|
+
import {
|
|
4
|
+
closeDb,
|
|
5
|
+
createTaskExtended,
|
|
6
|
+
createWorkflow,
|
|
7
|
+
createWorkflowRun,
|
|
8
|
+
createWorkflowRunStep,
|
|
9
|
+
getTaskById,
|
|
10
|
+
initDb,
|
|
11
|
+
} from "../be/db";
|
|
12
|
+
import { validateJsonSchema } from "../workflows/json-schema-validator";
|
|
13
|
+
|
|
14
|
+
const TEST_DB_PATH = "./test-structured-output.sqlite";
|
|
15
|
+
|
|
16
|
+
// ─── Setup / Teardown ────────────────────────────────────────
|
|
17
|
+
|
|
18
|
+
beforeAll(async () => {
|
|
19
|
+
try {
|
|
20
|
+
await unlink(TEST_DB_PATH);
|
|
21
|
+
} catch {}
|
|
22
|
+
initDb(TEST_DB_PATH);
|
|
23
|
+
});
|
|
24
|
+
|
|
25
|
+
afterAll(async () => {
|
|
26
|
+
closeDb();
|
|
27
|
+
for (const suffix of ["", "-wal", "-shm"]) {
|
|
28
|
+
try {
|
|
29
|
+
await unlink(`${TEST_DB_PATH}${suffix}`);
|
|
30
|
+
} catch {}
|
|
31
|
+
}
|
|
32
|
+
});
|
|
33
|
+
|
|
34
|
+
// ─── JSON Schema Validator Tests ─────────────────────────────
|
|
35
|
+
|
|
36
|
+
describe("validateJsonSchema — enum and const extensions", () => {
|
|
37
|
+
test("enum: valid value passes", () => {
|
|
38
|
+
const schema = { type: "string", enum: ["red", "green", "blue"] };
|
|
39
|
+
const errors = validateJsonSchema(schema, "red");
|
|
40
|
+
expect(errors).toEqual([]);
|
|
41
|
+
});
|
|
42
|
+
|
|
43
|
+
test("enum: invalid value fails", () => {
|
|
44
|
+
const schema = { type: "string", enum: ["red", "green", "blue"] };
|
|
45
|
+
const errors = validateJsonSchema(schema, "yellow");
|
|
46
|
+
expect(errors.length).toBe(1);
|
|
47
|
+
expect(errors[0]).toContain("not in enum");
|
|
48
|
+
});
|
|
49
|
+
|
|
50
|
+
test("enum: numeric enum works", () => {
|
|
51
|
+
const schema = { type: "number", enum: [1, 2, 3] };
|
|
52
|
+
expect(validateJsonSchema(schema, 2)).toEqual([]);
|
|
53
|
+
expect(validateJsonSchema(schema, 5).length).toBe(1);
|
|
54
|
+
});
|
|
55
|
+
|
|
56
|
+
test("const: matching value passes", () => {
|
|
57
|
+
const schema = { const: "fixed-value" };
|
|
58
|
+
expect(validateJsonSchema(schema, "fixed-value")).toEqual([]);
|
|
59
|
+
});
|
|
60
|
+
|
|
61
|
+
test("const: mismatched value fails", () => {
|
|
62
|
+
const schema = { const: 42 };
|
|
63
|
+
const errors = validateJsonSchema(schema, 43);
|
|
64
|
+
expect(errors.length).toBe(1);
|
|
65
|
+
expect(errors[0]).toContain("does not match const");
|
|
66
|
+
});
|
|
67
|
+
|
|
68
|
+
test("items: validates array items", () => {
|
|
69
|
+
const schema = {
|
|
70
|
+
type: "array",
|
|
71
|
+
items: { type: "number" },
|
|
72
|
+
};
|
|
73
|
+
expect(validateJsonSchema(schema, [1, 2, 3])).toEqual([]);
|
|
74
|
+
const errors = validateJsonSchema(schema, [1, "two", 3]);
|
|
75
|
+
expect(errors.length).toBe(1);
|
|
76
|
+
expect(errors[0]).toContain("root[1]");
|
|
77
|
+
});
|
|
78
|
+
|
|
79
|
+
test("nested object with enum property", () => {
|
|
80
|
+
const schema = {
|
|
81
|
+
type: "object",
|
|
82
|
+
properties: {
|
|
83
|
+
status: { type: "string", enum: ["active", "inactive"] },
|
|
84
|
+
count: { type: "number" },
|
|
85
|
+
},
|
|
86
|
+
required: ["status"],
|
|
87
|
+
};
|
|
88
|
+
expect(validateJsonSchema(schema, { status: "active", count: 5 })).toEqual([]);
|
|
89
|
+
expect(validateJsonSchema(schema, { status: "unknown" }).length).toBe(1);
|
|
90
|
+
});
|
|
91
|
+
});
|
|
92
|
+
|
|
93
|
+
// ─── DB: outputSchema storage ────────────────────────────────
|
|
94
|
+
|
|
95
|
+
describe("Task outputSchema storage", () => {
|
|
96
|
+
test("createTaskExtended stores outputSchema", () => {
|
|
97
|
+
const schema = {
|
|
98
|
+
type: "object",
|
|
99
|
+
properties: { fileCount: { type: "number" } },
|
|
100
|
+
required: ["fileCount"],
|
|
101
|
+
};
|
|
102
|
+
const task = createTaskExtended("Count files", {
|
|
103
|
+
source: "api",
|
|
104
|
+
outputSchema: schema,
|
|
105
|
+
});
|
|
106
|
+
|
|
107
|
+
const fetched = getTaskById(task.id);
|
|
108
|
+
expect(fetched).toBeDefined();
|
|
109
|
+
expect(fetched!.outputSchema).toBeDefined();
|
|
110
|
+
expect(fetched!.outputSchema).toEqual(schema);
|
|
111
|
+
});
|
|
112
|
+
|
|
113
|
+
test("createTaskExtended without outputSchema returns undefined", () => {
|
|
114
|
+
const task = createTaskExtended("Simple task", { source: "api" });
|
|
115
|
+
const fetched = getTaskById(task.id);
|
|
116
|
+
expect(fetched).toBeDefined();
|
|
117
|
+
expect(fetched!.outputSchema).toBeUndefined();
|
|
118
|
+
});
|
|
119
|
+
});
|
|
120
|
+
|
|
121
|
+
// ─── Store-progress validation logic (unit-level) ────────────
|
|
122
|
+
|
|
123
|
+
describe("Structured output validation logic", () => {
|
|
124
|
+
test("valid JSON matching schema passes validation", () => {
|
|
125
|
+
const schema = {
|
|
126
|
+
type: "object",
|
|
127
|
+
properties: {
|
|
128
|
+
fileCount: { type: "number" },
|
|
129
|
+
files: { type: "array", items: { type: "string" } },
|
|
130
|
+
},
|
|
131
|
+
required: ["fileCount"],
|
|
132
|
+
};
|
|
133
|
+
const output = JSON.stringify({ fileCount: 42, files: ["a.txt", "b.txt"] });
|
|
134
|
+
const parsed = JSON.parse(output);
|
|
135
|
+
const errors = validateJsonSchema(schema, parsed);
|
|
136
|
+
expect(errors).toEqual([]);
|
|
137
|
+
});
|
|
138
|
+
|
|
139
|
+
test("valid JSON not matching schema produces errors", () => {
|
|
140
|
+
const schema = {
|
|
141
|
+
type: "object",
|
|
142
|
+
properties: {
|
|
143
|
+
fileCount: { type: "number" },
|
|
144
|
+
},
|
|
145
|
+
required: ["fileCount"],
|
|
146
|
+
};
|
|
147
|
+
const parsed = { name: "test" }; // Missing fileCount
|
|
148
|
+
const errors = validateJsonSchema(schema, parsed);
|
|
149
|
+
expect(errors.length).toBeGreaterThan(0);
|
|
150
|
+
expect(errors[0]).toContain("fileCount");
|
|
151
|
+
});
|
|
152
|
+
|
|
153
|
+
test("wrong type produces error", () => {
|
|
154
|
+
const schema = {
|
|
155
|
+
type: "object",
|
|
156
|
+
properties: {
|
|
157
|
+
count: { type: "number" },
|
|
158
|
+
},
|
|
159
|
+
};
|
|
160
|
+
const parsed = { count: "not-a-number" };
|
|
161
|
+
const errors = validateJsonSchema(schema, parsed);
|
|
162
|
+
expect(errors.length).toBe(1);
|
|
163
|
+
expect(errors[0]).toContain("expected type");
|
|
164
|
+
});
|
|
165
|
+
});
|
|
166
|
+
|
|
167
|
+
// ─── Resume: JSON-parse structured output ────────────────────
|
|
168
|
+
|
|
169
|
+
describe("Resume — structured output JSON parsing", () => {
|
|
170
|
+
test("JSON string output gets parsed to object", () => {
|
|
171
|
+
const output = JSON.stringify({ fileCount: 42, path: "/tmp" });
|
|
172
|
+
let taskOutput: unknown = output;
|
|
173
|
+
try {
|
|
174
|
+
const parsed = JSON.parse(output);
|
|
175
|
+
if (typeof parsed === "object" && parsed !== null) {
|
|
176
|
+
taskOutput = parsed;
|
|
177
|
+
}
|
|
178
|
+
} catch {
|
|
179
|
+
// keep as string
|
|
180
|
+
}
|
|
181
|
+
expect(typeof taskOutput).toBe("object");
|
|
182
|
+
expect((taskOutput as { fileCount: number }).fileCount).toBe(42);
|
|
183
|
+
});
|
|
184
|
+
|
|
185
|
+
test("plain text output stays as string", () => {
|
|
186
|
+
const output = "Just a plain text result";
|
|
187
|
+
let taskOutput: unknown = output;
|
|
188
|
+
try {
|
|
189
|
+
const parsed = JSON.parse(output);
|
|
190
|
+
if (typeof parsed === "object" && parsed !== null) {
|
|
191
|
+
taskOutput = parsed;
|
|
192
|
+
}
|
|
193
|
+
} catch {
|
|
194
|
+
// keep as string
|
|
195
|
+
}
|
|
196
|
+
expect(typeof taskOutput).toBe("string");
|
|
197
|
+
expect(taskOutput).toBe("Just a plain text result");
|
|
198
|
+
});
|
|
199
|
+
|
|
200
|
+
test("JSON number stays as string (not object)", () => {
|
|
201
|
+
const output = "42";
|
|
202
|
+
let taskOutput: unknown = output;
|
|
203
|
+
try {
|
|
204
|
+
const parsed = JSON.parse(output);
|
|
205
|
+
if (typeof parsed === "object" && parsed !== null) {
|
|
206
|
+
taskOutput = parsed;
|
|
207
|
+
}
|
|
208
|
+
} catch {
|
|
209
|
+
// keep as string
|
|
210
|
+
}
|
|
211
|
+
// 42 parses but is not an object, so stays as string
|
|
212
|
+
expect(typeof taskOutput).toBe("string");
|
|
213
|
+
});
|
|
214
|
+
|
|
215
|
+
test("JSON array gets parsed", () => {
|
|
216
|
+
const output = JSON.stringify([1, 2, 3]);
|
|
217
|
+
let taskOutput: unknown = output;
|
|
218
|
+
try {
|
|
219
|
+
const parsed = JSON.parse(output);
|
|
220
|
+
if (typeof parsed === "object" && parsed !== null) {
|
|
221
|
+
taskOutput = parsed;
|
|
222
|
+
}
|
|
223
|
+
} catch {
|
|
224
|
+
// keep as string
|
|
225
|
+
}
|
|
226
|
+
expect(Array.isArray(taskOutput)).toBe(true);
|
|
227
|
+
});
|
|
228
|
+
});
|
|
229
|
+
|
|
230
|
+
// ─── AgentTask executor config schema ────────────────────────
|
|
231
|
+
|
|
232
|
+
describe("AgentTaskConfigSchema — outputSchema", () => {
|
|
233
|
+
test("accepts outputSchema in config", async () => {
|
|
234
|
+
const { AgentTaskExecutor } = await import("../workflows/executors/agent-task");
|
|
235
|
+
const executor = new AgentTaskExecutor({
|
|
236
|
+
db: {} as any,
|
|
237
|
+
eventBus: { emit: () => {}, on: () => {}, off: () => {} },
|
|
238
|
+
interpolate: (t: string) => t,
|
|
239
|
+
});
|
|
240
|
+
|
|
241
|
+
const config = {
|
|
242
|
+
template: "Count files",
|
|
243
|
+
outputSchema: {
|
|
244
|
+
type: "object",
|
|
245
|
+
properties: { fileCount: { type: "number" } },
|
|
246
|
+
required: ["fileCount"],
|
|
247
|
+
},
|
|
248
|
+
};
|
|
249
|
+
const parsed = executor.configSchema.safeParse(config);
|
|
250
|
+
expect(parsed.success).toBe(true);
|
|
251
|
+
if (parsed.success) {
|
|
252
|
+
expect(parsed.data.outputSchema).toEqual(config.outputSchema);
|
|
253
|
+
}
|
|
254
|
+
});
|
|
255
|
+
});
|
|
256
|
+
|
|
257
|
+
// ─── Workflow agent-task → DB with outputSchema ──────────────
|
|
258
|
+
|
|
259
|
+
describe("Workflow agent-task creates task with outputSchema", () => {
|
|
260
|
+
test("outputSchema flows from executor config to created task", async () => {
|
|
261
|
+
const { AgentTaskExecutor } = await import("../workflows/executors/agent-task");
|
|
262
|
+
const db = await import("../be/db");
|
|
263
|
+
|
|
264
|
+
const executor = new AgentTaskExecutor({
|
|
265
|
+
db: db as typeof import("../be/db"),
|
|
266
|
+
eventBus: { emit: () => {}, on: () => {}, off: () => {} },
|
|
267
|
+
interpolate: (t: string) => t,
|
|
268
|
+
});
|
|
269
|
+
|
|
270
|
+
// Create prerequisites for FK constraints
|
|
271
|
+
const wf = createWorkflow({ name: "test-output-schema", definition: { nodes: [], edges: [] } });
|
|
272
|
+
const run = createWorkflowRun({ id: crypto.randomUUID(), workflowId: wf.id });
|
|
273
|
+
const step = createWorkflowRunStep({
|
|
274
|
+
id: crypto.randomUUID(),
|
|
275
|
+
runId: run.id,
|
|
276
|
+
nodeId: "n1",
|
|
277
|
+
nodeType: "agent-task",
|
|
278
|
+
});
|
|
279
|
+
|
|
280
|
+
const schema = {
|
|
281
|
+
type: "object",
|
|
282
|
+
properties: { summary: { type: "string" } },
|
|
283
|
+
required: ["summary"],
|
|
284
|
+
};
|
|
285
|
+
|
|
286
|
+
const result = await executor.run({
|
|
287
|
+
config: {
|
|
288
|
+
template: "Summarize the document",
|
|
289
|
+
outputSchema: schema,
|
|
290
|
+
},
|
|
291
|
+
context: {},
|
|
292
|
+
meta: {
|
|
293
|
+
runId: run.id,
|
|
294
|
+
stepId: step.id,
|
|
295
|
+
nodeId: "n1",
|
|
296
|
+
workflowId: wf.id,
|
|
297
|
+
dryRun: false,
|
|
298
|
+
},
|
|
299
|
+
});
|
|
300
|
+
|
|
301
|
+
expect(result.status).toBe("success");
|
|
302
|
+
const taskId = (result as { correlationId?: string }).correlationId!;
|
|
303
|
+
const task = getTaskById(taskId);
|
|
304
|
+
expect(task).toBeDefined();
|
|
305
|
+
expect(task!.outputSchema).toEqual(schema);
|
|
306
|
+
});
|
|
307
|
+
});
|