@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,233 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Block Kit message builders for structured Slack messages.
|
|
3
|
+
*
|
|
4
|
+
* Consolidates getTaskLink and markdownToSlack (previously duplicated
|
|
5
|
+
* across responses.ts, handlers.ts, thread-buffer.ts).
|
|
6
|
+
*/
|
|
7
|
+
|
|
8
|
+
const appUrl = process.env.APP_URL || "";
|
|
9
|
+
|
|
10
|
+
// Slack limits section text to 3000 chars; we use 2900 for safety
|
|
11
|
+
const MAX_SECTION_LENGTH = 2900;
|
|
12
|
+
|
|
13
|
+
// biome-ignore lint/suspicious/noExplicitAny: Slack block types are complex unions; we build plain objects
|
|
14
|
+
type SlackBlock = any;
|
|
15
|
+
|
|
16
|
+
// --- Shared utilities ---
|
|
17
|
+
|
|
18
|
+
/**
|
|
19
|
+
* Get a Slack-formatted link to the task in the dashboard, or just the short ID.
|
|
20
|
+
*/
|
|
21
|
+
export function getTaskLink(taskId: string): string {
|
|
22
|
+
const shortId = taskId.slice(0, 8);
|
|
23
|
+
if (appUrl) {
|
|
24
|
+
return `<${appUrl}?tab=tasks&task=${taskId}&expand=true|\`${shortId}\`>`;
|
|
25
|
+
}
|
|
26
|
+
return `\`${shortId}\``;
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
/**
|
|
30
|
+
* Get a raw dashboard URL for a task (for link buttons).
|
|
31
|
+
*/
|
|
32
|
+
export function getTaskUrl(taskId: string): string {
|
|
33
|
+
if (appUrl) {
|
|
34
|
+
return `${appUrl}?tab=tasks&task=${taskId}&expand=true`;
|
|
35
|
+
}
|
|
36
|
+
return "";
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
/**
|
|
40
|
+
* Convert GitHub-flavored markdown to Slack mrkdwn format.
|
|
41
|
+
*
|
|
42
|
+
* Key differences:
|
|
43
|
+
* - GitHub: **bold**, *italic*, ~~strike~~, [text](url)
|
|
44
|
+
* - Slack: *bold*, _italic_, ~strike~, <url|text>
|
|
45
|
+
*/
|
|
46
|
+
export function markdownToSlack(text: string): string {
|
|
47
|
+
return (
|
|
48
|
+
text
|
|
49
|
+
// Headers to bold placeholder (# Header -> bold, protected from italic)
|
|
50
|
+
.replace(/^#{1,6}\s+(.+)$/gm, "\uE000$1\uE001")
|
|
51
|
+
// Bold **text** -> placeholder (to avoid italic chain converting *bold* to _italic_)
|
|
52
|
+
.replace(/\*\*(.+?)\*\*/g, "\uE000$1\uE001")
|
|
53
|
+
// Italic *text* -> _text_ (single asterisks, now safe from bold placeholders)
|
|
54
|
+
.replace(/(?<!\*)\*([^*]+)\*(?!\*)/g, "_$1_")
|
|
55
|
+
// Restore bold from placeholder -> *text*
|
|
56
|
+
.replace(/\uE000(.+?)\uE001/g, "*$1*")
|
|
57
|
+
// Strikethrough ~~text~~ -> ~text~
|
|
58
|
+
.replace(/~~(.+?)~~/g, "~$1~")
|
|
59
|
+
// Links [text](url) -> <url|text>
|
|
60
|
+
.replace(/\[([^\]]+)\]\(([^)]+)\)/g, "<$2|$1>")
|
|
61
|
+
// Inline code already works the same
|
|
62
|
+
// Bullet points already work the same
|
|
63
|
+
// Remove excessive blank lines
|
|
64
|
+
.replace(/\n{3,}/g, "\n\n")
|
|
65
|
+
);
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
/**
|
|
69
|
+
* Split text into chunks that fit within Slack's section text limit.
|
|
70
|
+
*/
|
|
71
|
+
function splitText(text: string): string[] {
|
|
72
|
+
if (text.length <= MAX_SECTION_LENGTH) return [text];
|
|
73
|
+
|
|
74
|
+
const chunks: string[] = [];
|
|
75
|
+
let remaining = text;
|
|
76
|
+
while (remaining.length > 0) {
|
|
77
|
+
if (remaining.length <= MAX_SECTION_LENGTH) {
|
|
78
|
+
chunks.push(remaining);
|
|
79
|
+
break;
|
|
80
|
+
}
|
|
81
|
+
// Try to split at a newline
|
|
82
|
+
let splitIdx = remaining.lastIndexOf("\n", MAX_SECTION_LENGTH);
|
|
83
|
+
if (splitIdx < MAX_SECTION_LENGTH / 2) {
|
|
84
|
+
// No good newline break, try splitting at space
|
|
85
|
+
splitIdx = remaining.lastIndexOf(" ", MAX_SECTION_LENGTH);
|
|
86
|
+
}
|
|
87
|
+
if (splitIdx < MAX_SECTION_LENGTH / 2) {
|
|
88
|
+
// No good break point at all, hard split
|
|
89
|
+
splitIdx = MAX_SECTION_LENGTH;
|
|
90
|
+
}
|
|
91
|
+
chunks.push(remaining.slice(0, splitIdx));
|
|
92
|
+
remaining = remaining.slice(splitIdx).trimStart();
|
|
93
|
+
}
|
|
94
|
+
return chunks;
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
// --- Block primitives ---
|
|
98
|
+
|
|
99
|
+
function contextBlock(...elements: string[]): SlackBlock {
|
|
100
|
+
return {
|
|
101
|
+
type: "context",
|
|
102
|
+
elements: elements.map((text) => ({ type: "mrkdwn", text })),
|
|
103
|
+
};
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
function sectionBlock(text: string): SlackBlock {
|
|
107
|
+
return { type: "section", text: { type: "mrkdwn", text } };
|
|
108
|
+
}
|
|
109
|
+
|
|
110
|
+
function cancelActionBlock(taskId: string): SlackBlock {
|
|
111
|
+
return {
|
|
112
|
+
type: "actions",
|
|
113
|
+
elements: [
|
|
114
|
+
{
|
|
115
|
+
type: "button",
|
|
116
|
+
text: { type: "plain_text", text: "Cancel" },
|
|
117
|
+
action_id: "cancel_task",
|
|
118
|
+
value: taskId,
|
|
119
|
+
style: "danger",
|
|
120
|
+
confirm: {
|
|
121
|
+
title: { type: "plain_text", text: "Cancel task?" },
|
|
122
|
+
text: { type: "mrkdwn", text: "This will cancel the task. Are you sure?" },
|
|
123
|
+
confirm: { type: "plain_text", text: "Yes, cancel" },
|
|
124
|
+
deny: { type: "plain_text", text: "Never mind" },
|
|
125
|
+
},
|
|
126
|
+
},
|
|
127
|
+
],
|
|
128
|
+
};
|
|
129
|
+
}
|
|
130
|
+
|
|
131
|
+
// --- High-level block builders ---
|
|
132
|
+
|
|
133
|
+
/**
|
|
134
|
+
* Build blocks for a completed task response.
|
|
135
|
+
* Single-line header with agent/task metadata, then body content.
|
|
136
|
+
*/
|
|
137
|
+
export function buildCompletedBlocks(opts: {
|
|
138
|
+
agentName: string;
|
|
139
|
+
taskId: string;
|
|
140
|
+
body: string;
|
|
141
|
+
duration?: string;
|
|
142
|
+
}): SlackBlock[] {
|
|
143
|
+
const taskLink = getTaskLink(opts.taskId);
|
|
144
|
+
let line = `✅ *${opts.agentName}* (${taskLink})`;
|
|
145
|
+
if (opts.duration) line += ` · ${opts.duration}`;
|
|
146
|
+
|
|
147
|
+
const blocks: SlackBlock[] = [sectionBlock(line)];
|
|
148
|
+
for (const chunk of splitText(opts.body)) {
|
|
149
|
+
blocks.push(sectionBlock(chunk));
|
|
150
|
+
}
|
|
151
|
+
return blocks;
|
|
152
|
+
}
|
|
153
|
+
|
|
154
|
+
/**
|
|
155
|
+
* Build blocks for a failed task response.
|
|
156
|
+
* Single-line header, then error in code block.
|
|
157
|
+
*/
|
|
158
|
+
export function buildFailedBlocks(opts: {
|
|
159
|
+
agentName: string;
|
|
160
|
+
taskId: string;
|
|
161
|
+
reason: string;
|
|
162
|
+
duration?: string;
|
|
163
|
+
}): SlackBlock[] {
|
|
164
|
+
const taskLink = getTaskLink(opts.taskId);
|
|
165
|
+
let line = `❌ *${opts.agentName}* (${taskLink})`;
|
|
166
|
+
if (opts.duration) line += ` · ${opts.duration}`;
|
|
167
|
+
|
|
168
|
+
return [sectionBlock(line), sectionBlock(`\`\`\`${opts.reason}\`\`\``)];
|
|
169
|
+
}
|
|
170
|
+
|
|
171
|
+
/**
|
|
172
|
+
* Build blocks for a progress update.
|
|
173
|
+
* Single line with cancel button.
|
|
174
|
+
*/
|
|
175
|
+
export function buildProgressBlocks(opts: {
|
|
176
|
+
agentName: string;
|
|
177
|
+
taskId: string;
|
|
178
|
+
progress: string;
|
|
179
|
+
}): SlackBlock[] {
|
|
180
|
+
const shortId = opts.taskId.slice(0, 8);
|
|
181
|
+
return [
|
|
182
|
+
sectionBlock(`⏳ *${opts.agentName}* (\`${shortId}\`): ${opts.progress}`),
|
|
183
|
+
cancelActionBlock(opts.taskId),
|
|
184
|
+
];
|
|
185
|
+
}
|
|
186
|
+
|
|
187
|
+
/**
|
|
188
|
+
* Build blocks for a cancelled task card (used when cancel button is clicked).
|
|
189
|
+
*/
|
|
190
|
+
export function buildCancelledBlocks(opts: { agentName: string; taskId: string }): SlackBlock[] {
|
|
191
|
+
const taskLink = getTaskLink(opts.taskId);
|
|
192
|
+
return [sectionBlock(`🚫 *${opts.agentName}* (${taskLink}) — Cancelled`)];
|
|
193
|
+
}
|
|
194
|
+
|
|
195
|
+
/**
|
|
196
|
+
* Build blocks for a consolidated task assignment summary.
|
|
197
|
+
* Each assignment/queue/failure is a single line.
|
|
198
|
+
*/
|
|
199
|
+
export function buildAssignmentSummaryBlocks(results: {
|
|
200
|
+
assigned: Array<{ agentName: string; taskId: string }>;
|
|
201
|
+
queued: Array<{ agentName: string; taskId: string }>;
|
|
202
|
+
failed: Array<{ agentName: string; reason: string }>;
|
|
203
|
+
}): SlackBlock[] {
|
|
204
|
+
const lines: string[] = [];
|
|
205
|
+
|
|
206
|
+
for (const a of results.assigned) {
|
|
207
|
+
lines.push(`📡 Task assigned to: *${a.agentName}* (${getTaskLink(a.taskId)})`);
|
|
208
|
+
}
|
|
209
|
+
for (const q of results.queued) {
|
|
210
|
+
lines.push(`📡 Task queued for: *${q.agentName}* (${getTaskLink(q.taskId)})`);
|
|
211
|
+
}
|
|
212
|
+
for (const f of results.failed) {
|
|
213
|
+
lines.push(`⚠️ Could not assign to: *${f.agentName}* — ${f.reason}`);
|
|
214
|
+
}
|
|
215
|
+
|
|
216
|
+
return [sectionBlock(lines.join("\n"))];
|
|
217
|
+
}
|
|
218
|
+
|
|
219
|
+
/**
|
|
220
|
+
* Build blocks for buffer flush feedback.
|
|
221
|
+
*/
|
|
222
|
+
export function buildBufferFlushBlocks(opts: {
|
|
223
|
+
messageCount: number;
|
|
224
|
+
taskId: string;
|
|
225
|
+
hasDependency: boolean;
|
|
226
|
+
}): SlackBlock[] {
|
|
227
|
+
const taskLink = getTaskLink(opts.taskId);
|
|
228
|
+
const text = opts.hasDependency
|
|
229
|
+
? `${opts.messageCount} follow-up message(s) queued pending completion of current task`
|
|
230
|
+
: `${opts.messageCount} follow-up message(s) batched into task`;
|
|
231
|
+
|
|
232
|
+
return [contextBlock(`📡 _${text}_ (${taskLink})`)];
|
|
233
|
+
}
|
|
@@ -0,0 +1,177 @@
|
|
|
1
|
+
import { getSlackApp } from "./app";
|
|
2
|
+
|
|
3
|
+
export interface ChannelMessage {
|
|
4
|
+
channelId: string;
|
|
5
|
+
channelName?: string;
|
|
6
|
+
ts: string;
|
|
7
|
+
user: string;
|
|
8
|
+
text: string;
|
|
9
|
+
}
|
|
10
|
+
|
|
11
|
+
// ─── Caches ─────────────────────────────────────────────────────────────────
|
|
12
|
+
|
|
13
|
+
let cachedBotUserId: string | null = null;
|
|
14
|
+
|
|
15
|
+
interface ChannelCache {
|
|
16
|
+
channels: Array<{ id: string; name?: string }>;
|
|
17
|
+
fetchedAt: number;
|
|
18
|
+
}
|
|
19
|
+
let channelCache: ChannelCache | null = null;
|
|
20
|
+
const CHANNEL_CACHE_TTL_MS = 5 * 60 * 1000; // 5 minutes
|
|
21
|
+
|
|
22
|
+
// ─── Public API ─────────────────────────────────────────────────────────────
|
|
23
|
+
|
|
24
|
+
/**
|
|
25
|
+
* Fetch recent non-bot messages from channels the bot is in,
|
|
26
|
+
* filtering to only messages newer than the provided cursors.
|
|
27
|
+
*
|
|
28
|
+
* For channels with no cursor (first run), returns the latest message ts
|
|
29
|
+
* as a seed cursor without triggering — prevents cold-start flood.
|
|
30
|
+
*
|
|
31
|
+
* Returns messages sorted oldest-first, plus seed cursors for new channels.
|
|
32
|
+
*/
|
|
33
|
+
export async function fetchChannelActivity(
|
|
34
|
+
cursors: Map<string, string>,
|
|
35
|
+
allowedChannelIds?: string[],
|
|
36
|
+
limit = 10,
|
|
37
|
+
): Promise<{ messages: ChannelMessage[]; seedCursors: Map<string, string> }> {
|
|
38
|
+
const app = getSlackApp();
|
|
39
|
+
if (!app) return { messages: [], seedCursors: new Map() };
|
|
40
|
+
|
|
41
|
+
const client = app.client;
|
|
42
|
+
|
|
43
|
+
// Get channels the bot is a member of (cached, with pagination)
|
|
44
|
+
let channels = getCachedChannels();
|
|
45
|
+
if (!channels) {
|
|
46
|
+
channels = await fetchAllBotChannels(client);
|
|
47
|
+
setCachedChannels(channels);
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
// Filter by allowlist if configured
|
|
51
|
+
if (allowedChannelIds && allowedChannelIds.length > 0) {
|
|
52
|
+
const allowed = new Set(allowedChannelIds);
|
|
53
|
+
channels = channels.filter((ch) => allowed.has(ch.id));
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
if (channels.length === 0) return { messages: [], seedCursors: new Map() };
|
|
57
|
+
|
|
58
|
+
const messages: ChannelMessage[] = [];
|
|
59
|
+
const seedCursors = new Map<string, string>();
|
|
60
|
+
|
|
61
|
+
// Cache bot user ID (never changes during runtime)
|
|
62
|
+
if (!cachedBotUserId) {
|
|
63
|
+
const authResult = await client.auth.test();
|
|
64
|
+
cachedBotUserId = authResult.user_id as string;
|
|
65
|
+
}
|
|
66
|
+
const botUserId = cachedBotUserId;
|
|
67
|
+
|
|
68
|
+
for (const channel of channels) {
|
|
69
|
+
const channelId = channel.id;
|
|
70
|
+
const cursor = cursors.get(channelId);
|
|
71
|
+
|
|
72
|
+
try {
|
|
73
|
+
// Cold-start: no cursor for this channel — seed it without triggering
|
|
74
|
+
if (!cursor) {
|
|
75
|
+
const seedResult = await client.conversations.history({
|
|
76
|
+
channel: channelId,
|
|
77
|
+
limit: 1,
|
|
78
|
+
});
|
|
79
|
+
const latestMsg = seedResult.messages?.[0];
|
|
80
|
+
if (latestMsg?.ts) {
|
|
81
|
+
seedCursors.set(channelId, latestMsg.ts);
|
|
82
|
+
}
|
|
83
|
+
continue; // Don't trigger on first run
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
const historyResult = await client.conversations.history({
|
|
87
|
+
channel: channelId,
|
|
88
|
+
oldest: cursor,
|
|
89
|
+
limit,
|
|
90
|
+
});
|
|
91
|
+
|
|
92
|
+
for (const msg of historyResult.messages || []) {
|
|
93
|
+
// Skip the cursor message itself (oldest is inclusive)
|
|
94
|
+
if (msg.ts === cursor) continue;
|
|
95
|
+
// Skip bot messages (bot_id present, or subtype bot_message)
|
|
96
|
+
if (msg.bot_id || msg.subtype === "bot_message") continue;
|
|
97
|
+
// Skip our own bot's messages
|
|
98
|
+
if (msg.user === botUserId) continue;
|
|
99
|
+
// Skip messages without text or user
|
|
100
|
+
if (!msg.text?.trim() || !msg.user) continue;
|
|
101
|
+
// Skip thread replies (only top-level channel messages)
|
|
102
|
+
if (msg.thread_ts && msg.thread_ts !== msg.ts) continue;
|
|
103
|
+
|
|
104
|
+
messages.push({
|
|
105
|
+
channelId,
|
|
106
|
+
channelName: channel.name,
|
|
107
|
+
ts: msg.ts!,
|
|
108
|
+
user: msg.user,
|
|
109
|
+
text: msg.text,
|
|
110
|
+
});
|
|
111
|
+
}
|
|
112
|
+
} catch (err) {
|
|
113
|
+
// Log but don't fail — channel might have been archived or bot removed
|
|
114
|
+
console.warn(`[channel-activity] Failed to fetch history for ${channelId}: ${err}`);
|
|
115
|
+
}
|
|
116
|
+
}
|
|
117
|
+
|
|
118
|
+
// Sort by timestamp ascending (oldest first)
|
|
119
|
+
messages.sort((a, b) => Number.parseFloat(a.ts) - Number.parseFloat(b.ts));
|
|
120
|
+
|
|
121
|
+
return { messages, seedCursors };
|
|
122
|
+
}
|
|
123
|
+
|
|
124
|
+
// ─── Internal helpers ───────────────────────────────────────────────────────
|
|
125
|
+
|
|
126
|
+
function getCachedChannels(): Array<{ id: string; name?: string }> | null {
|
|
127
|
+
if (!channelCache) return null;
|
|
128
|
+
if (Date.now() - channelCache.fetchedAt > CHANNEL_CACHE_TTL_MS) {
|
|
129
|
+
channelCache = null;
|
|
130
|
+
return null;
|
|
131
|
+
}
|
|
132
|
+
return channelCache.channels;
|
|
133
|
+
}
|
|
134
|
+
|
|
135
|
+
function setCachedChannels(channels: Array<{ id: string; name?: string }>): void {
|
|
136
|
+
channelCache = { channels, fetchedAt: Date.now() };
|
|
137
|
+
}
|
|
138
|
+
|
|
139
|
+
/** Fetch all channels the bot is a member of, handling pagination */
|
|
140
|
+
async function fetchAllBotChannels(
|
|
141
|
+
client: ReturnType<typeof getSlackApp> extends infer A
|
|
142
|
+
? A extends { client: infer C }
|
|
143
|
+
? C
|
|
144
|
+
: never
|
|
145
|
+
: never,
|
|
146
|
+
): Promise<Array<{ id: string; name?: string }>> {
|
|
147
|
+
const channels: Array<{ id: string; name?: string }> = [];
|
|
148
|
+
let nextCursor: string | undefined;
|
|
149
|
+
|
|
150
|
+
do {
|
|
151
|
+
const result = await (
|
|
152
|
+
client as {
|
|
153
|
+
conversations: {
|
|
154
|
+
list: (args: Record<string, unknown>) => Promise<{
|
|
155
|
+
channels?: Array<{ id?: string; name?: string; is_member?: boolean }>;
|
|
156
|
+
response_metadata?: { next_cursor?: string };
|
|
157
|
+
}>;
|
|
158
|
+
};
|
|
159
|
+
}
|
|
160
|
+
).conversations.list({
|
|
161
|
+
types: "public_channel,private_channel",
|
|
162
|
+
exclude_archived: true,
|
|
163
|
+
limit: 200,
|
|
164
|
+
cursor: nextCursor || undefined,
|
|
165
|
+
});
|
|
166
|
+
|
|
167
|
+
for (const ch of result.channels || []) {
|
|
168
|
+
if (ch.id && ch.is_member) {
|
|
169
|
+
channels.push({ id: ch.id, name: ch.name });
|
|
170
|
+
}
|
|
171
|
+
}
|
|
172
|
+
|
|
173
|
+
nextCursor = result.response_metadata?.next_cursor || undefined;
|
|
174
|
+
} while (nextCursor);
|
|
175
|
+
|
|
176
|
+
return channels;
|
|
177
|
+
}
|
package/src/slack/commands.ts
CHANGED
|
@@ -59,30 +59,44 @@ export function registerCommandHandler(app: App): void {
|
|
|
59
59
|
|
|
60
60
|
app.command("/agent-swarm-help", async ({ ack, respond }) => {
|
|
61
61
|
await ack();
|
|
62
|
+
console.log("[Slack] /agent-swarm-help command invoked");
|
|
62
63
|
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
type: "header",
|
|
68
|
-
text: { type: "plain_text", text: "Agent Swarm Help" },
|
|
69
|
-
},
|
|
70
|
-
{
|
|
71
|
-
type: "section",
|
|
72
|
-
text: {
|
|
73
|
-
type: "mrkdwn",
|
|
74
|
-
text: `*How to assign tasks:*
|
|
64
|
+
const additiveSlack = process.env.ADDITIVE_SLACK === "true";
|
|
65
|
+
|
|
66
|
+
const sections = [
|
|
67
|
+
`*How to assign tasks:*
|
|
75
68
|
• Mention an agent by name: \`Hey Alpha, can you review this code?\`
|
|
76
69
|
• Use explicit ID: \`swarm#<uuid> please analyze the logs\`
|
|
77
70
|
• Broadcast to all: \`swarm#all status report please\`
|
|
78
71
|
• Mention the bot: \`@agent-swarm help me\` (routes to lead agent)
|
|
72
|
+
• Thread @mentions auto-route to the worker already active in that thread`,
|
|
73
|
+
];
|
|
74
|
+
|
|
75
|
+
if (additiveSlack) {
|
|
76
|
+
sections.push(`*Additive Slack (enabled):*
|
|
77
|
+
• Thread replies (no @mention needed) are automatically buffered and batched into follow-up tasks
|
|
78
|
+
• \`!now <message>\` in a thread — Flush buffer immediately, skip dependency queue
|
|
79
|
+
• Follow-up tasks auto-depend on the active task in the thread for natural sequencing`);
|
|
80
|
+
}
|
|
79
81
|
|
|
80
|
-
|
|
82
|
+
sections.push(`*Commands:*
|
|
81
83
|
• \`/agent-swarm-status\` - Show all agents and their current status
|
|
82
|
-
• \`/agent-swarm-help\` - Show this help message
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
84
|
+
• \`/agent-swarm-help\` - Show this help message`);
|
|
85
|
+
|
|
86
|
+
const blocks = [
|
|
87
|
+
{
|
|
88
|
+
type: "header" as const,
|
|
89
|
+
text: { type: "plain_text" as const, text: "Agent Swarm Help" },
|
|
90
|
+
},
|
|
91
|
+
...sections.map((text) => ({
|
|
92
|
+
type: "section" as const,
|
|
93
|
+
text: { type: "mrkdwn" as const, text },
|
|
94
|
+
})),
|
|
95
|
+
];
|
|
96
|
+
|
|
97
|
+
await respond({
|
|
98
|
+
response_type: "ephemeral",
|
|
99
|
+
blocks,
|
|
86
100
|
});
|
|
87
101
|
});
|
|
88
102
|
}
|
package/src/slack/files.ts
CHANGED
|
@@ -11,7 +11,7 @@ export const MAX_FILE_SIZE = 1073741824;
|
|
|
11
11
|
/**
|
|
12
12
|
* Default download directory for Slack files
|
|
13
13
|
*/
|
|
14
|
-
export const DEFAULT_DOWNLOAD_DIR =
|
|
14
|
+
export const DEFAULT_DOWNLOAD_DIR = `/workspace/shared/downloads/${process.env.AGENT_ID || "default"}/slack`;
|
|
15
15
|
|
|
16
16
|
/**
|
|
17
17
|
* File object from Slack API (partial, commonly used fields)
|
|
@@ -1,5 +1,12 @@
|
|
|
1
1
|
import { describe, expect, test } from "bun:test";
|
|
2
|
-
import {
|
|
2
|
+
import type { SlackFile } from "./files";
|
|
3
|
+
import {
|
|
4
|
+
buildAttachmentText,
|
|
5
|
+
buildEffectiveText,
|
|
6
|
+
checkUserAccess,
|
|
7
|
+
formatFileSize,
|
|
8
|
+
type UserFilterConfig,
|
|
9
|
+
} from "./handlers";
|
|
3
10
|
|
|
4
11
|
describe("checkUserAccess", () => {
|
|
5
12
|
describe("when filtering is disabled (empty config)", () => {
|
|
@@ -163,3 +170,109 @@ describe("checkUserAccess", () => {
|
|
|
163
170
|
});
|
|
164
171
|
});
|
|
165
172
|
});
|
|
173
|
+
|
|
174
|
+
// Helper to create a minimal SlackFile for testing
|
|
175
|
+
function makeFile(overrides: Partial<SlackFile> = {}): SlackFile {
|
|
176
|
+
return {
|
|
177
|
+
id: "F0TEST123",
|
|
178
|
+
name: "test_file.txt",
|
|
179
|
+
mimetype: "text/plain",
|
|
180
|
+
filetype: "txt",
|
|
181
|
+
size: 1024,
|
|
182
|
+
url_private: "https://files.slack.com/private",
|
|
183
|
+
url_private_download: "https://files.slack.com/download",
|
|
184
|
+
...overrides,
|
|
185
|
+
};
|
|
186
|
+
}
|
|
187
|
+
|
|
188
|
+
describe("formatFileSize", () => {
|
|
189
|
+
test("formats bytes", () => {
|
|
190
|
+
expect(formatFileSize(0)).toBe("0 B");
|
|
191
|
+
expect(formatFileSize(512)).toBe("512 B");
|
|
192
|
+
expect(formatFileSize(1023)).toBe("1023 B");
|
|
193
|
+
});
|
|
194
|
+
|
|
195
|
+
test("formats kilobytes", () => {
|
|
196
|
+
expect(formatFileSize(1024)).toBe("1.0 KB");
|
|
197
|
+
expect(formatFileSize(1536)).toBe("1.5 KB");
|
|
198
|
+
expect(formatFileSize(1024 * 100)).toBe("100.0 KB");
|
|
199
|
+
});
|
|
200
|
+
|
|
201
|
+
test("formats megabytes", () => {
|
|
202
|
+
expect(formatFileSize(1024 * 1024)).toBe("1.0 MB");
|
|
203
|
+
expect(formatFileSize(1024 * 1024 * 2.5)).toBe("2.5 MB");
|
|
204
|
+
});
|
|
205
|
+
|
|
206
|
+
test("formats gigabytes", () => {
|
|
207
|
+
expect(formatFileSize(1024 * 1024 * 1024)).toBe("1.0 GB");
|
|
208
|
+
expect(formatFileSize(1024 * 1024 * 1024 * 1.5)).toBe("1.5 GB");
|
|
209
|
+
});
|
|
210
|
+
});
|
|
211
|
+
|
|
212
|
+
describe("buildAttachmentText", () => {
|
|
213
|
+
test("formats a single file", () => {
|
|
214
|
+
const files = [
|
|
215
|
+
makeFile({ id: "F001", name: "voice.m4a", mimetype: "audio/mp4", size: 156000 }),
|
|
216
|
+
];
|
|
217
|
+
expect(buildAttachmentText(files)).toBe("[File: voice.m4a (audio/mp4, 152.3 KB) id=F001]");
|
|
218
|
+
});
|
|
219
|
+
|
|
220
|
+
test("formats multiple files", () => {
|
|
221
|
+
const files = [
|
|
222
|
+
makeFile({ id: "F001", name: "photo.png", mimetype: "image/png", size: 2048000 }),
|
|
223
|
+
makeFile({ id: "F002", name: "doc.pdf", mimetype: "application/pdf", size: 512000 }),
|
|
224
|
+
];
|
|
225
|
+
const result = buildAttachmentText(files);
|
|
226
|
+
expect(result).toContain("[File: photo.png (image/png,");
|
|
227
|
+
expect(result).toContain("[File: doc.pdf (application/pdf,");
|
|
228
|
+
expect(result).toContain("id=F001");
|
|
229
|
+
expect(result).toContain("id=F002");
|
|
230
|
+
expect(result.split("\n")).toHaveLength(2);
|
|
231
|
+
});
|
|
232
|
+
|
|
233
|
+
test("handles empty array", () => {
|
|
234
|
+
expect(buildAttachmentText([])).toBe("");
|
|
235
|
+
});
|
|
236
|
+
});
|
|
237
|
+
|
|
238
|
+
describe("buildEffectiveText", () => {
|
|
239
|
+
test("returns text as-is when no files", () => {
|
|
240
|
+
expect(buildEffectiveText("hello world")).toBe("hello world");
|
|
241
|
+
expect(buildEffectiveText("hello world", undefined)).toBe("hello world");
|
|
242
|
+
expect(buildEffectiveText("hello world", [])).toBe("hello world");
|
|
243
|
+
});
|
|
244
|
+
|
|
245
|
+
test("returns attachment text when no text", () => {
|
|
246
|
+
const files = [makeFile({ id: "F001", name: "voice.m4a", mimetype: "audio/mp4", size: 1024 })];
|
|
247
|
+
const result = buildEffectiveText(undefined, files);
|
|
248
|
+
expect(result).toBe("[File: voice.m4a (audio/mp4, 1.0 KB) id=F001]");
|
|
249
|
+
});
|
|
250
|
+
|
|
251
|
+
test("returns attachment text when text is empty string", () => {
|
|
252
|
+
const files = [makeFile({ id: "F001", name: "voice.m4a", mimetype: "audio/mp4", size: 1024 })];
|
|
253
|
+
const result = buildEffectiveText("", files);
|
|
254
|
+
expect(result).toBe("[File: voice.m4a (audio/mp4, 1.0 KB) id=F001]");
|
|
255
|
+
});
|
|
256
|
+
|
|
257
|
+
test("returns attachment text when text is whitespace only", () => {
|
|
258
|
+
const files = [makeFile({ id: "F001", name: "voice.m4a", mimetype: "audio/mp4", size: 1024 })];
|
|
259
|
+
const result = buildEffectiveText(" ", files);
|
|
260
|
+
expect(result).toBe("[File: voice.m4a (audio/mp4, 1.0 KB) id=F001]");
|
|
261
|
+
});
|
|
262
|
+
|
|
263
|
+
test("combines text and files when both present", () => {
|
|
264
|
+
const files = [
|
|
265
|
+
makeFile({ id: "F001", name: "screenshot.png", mimetype: "image/png", size: 2048 }),
|
|
266
|
+
];
|
|
267
|
+
const result = buildEffectiveText("<@UBOT> fix this bug", files);
|
|
268
|
+
expect(result).toContain("<@UBOT> fix this bug");
|
|
269
|
+
expect(result).toContain("[File: screenshot.png (image/png,");
|
|
270
|
+
expect(result).toContain("\n\n");
|
|
271
|
+
});
|
|
272
|
+
|
|
273
|
+
test("returns empty string when neither text nor files", () => {
|
|
274
|
+
expect(buildEffectiveText(undefined)).toBe("");
|
|
275
|
+
expect(buildEffectiveText(undefined, [])).toBe("");
|
|
276
|
+
expect(buildEffectiveText("")).toBe("");
|
|
277
|
+
});
|
|
278
|
+
});
|