@desplega.ai/agent-swarm 1.91.0 → 1.92.1
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 +3 -2
- package/openapi.json +1005 -152
- package/package.json +6 -6
- package/plugin/skills/pages/SKILL.md +5 -2
- package/src/be/db.ts +662 -19
- package/src/be/memory/constants.ts +2 -1
- package/src/be/memory/providers/openai-embedding.ts +2 -5
- package/src/be/memory/providers/sqlite-store.ts +293 -76
- package/src/be/memory/types.ts +35 -0
- package/src/be/migrations/083_script_workflows.sql +51 -0
- package/src/be/migrations/084_script_run_journal_duration.sql +5 -0
- package/src/be/migrations/085_script_runs_kind.sql +9 -0
- package/src/be/migrations/086_pages_default_authed.sql +64 -0
- package/src/be/migrations/087_skill_files.sql +19 -0
- package/src/be/modelsdev-cache.json +42310 -38617
- package/src/be/scripts/typecheck.ts +49 -0
- package/src/be/seed-scripts/catalog/boot-triage.ts +221 -0
- package/src/be/seed-scripts/catalog/catalog-report.ts +457 -0
- package/src/be/seed-scripts/catalog/compound-insights.ts +310 -6
- package/src/be/seed-scripts/catalog/gh-pr-snapshot.ts +1 -1
- package/src/be/seed-scripts/catalog/memory-eval.ts +1059 -0
- package/src/be/seed-scripts/catalog/ops-catalog-audit.ts +506 -0
- package/src/be/seed-scripts/catalog/schedule-health.ts +78 -2
- package/src/be/seed-scripts/catalog/task-context-gathering.ts +92 -0
- package/src/be/seed-scripts/catalog/task-failure-audit.ts +48 -1
- package/src/be/seed-scripts/catalog/tool-usage.ts +6 -3
- package/src/be/seed-scripts/index.ts +51 -5
- package/src/be/seed-skills/index.ts +3 -3
- package/src/be/skill-sync.ts +91 -7
- package/src/be/swarm-config-guard.ts +17 -0
- package/src/commands/runner.ts +49 -4
- package/src/heartbeat/templates.ts +20 -16
- package/src/http/db-query.ts +20 -5
- package/src/http/index.ts +51 -7
- package/src/http/mcp-user.ts +23 -0
- package/src/http/mcp.ts +58 -0
- package/src/http/memory.ts +58 -0
- package/src/http/pages.ts +1 -1
- package/src/http/script-runs.ts +557 -0
- package/src/http/scripts.ts +39 -2
- package/src/http/skills.ts +225 -0
- package/src/prompts/session-templates.ts +24 -4
- package/src/providers/claude-adapter.ts +107 -28
- package/src/script-workflows/executor.ts +110 -0
- package/src/script-workflows/harness.ts +73 -0
- package/src/script-workflows/label-lint.ts +51 -0
- package/src/script-workflows/limits.ts +22 -0
- package/src/script-workflows/supervisor.ts +139 -0
- package/src/script-workflows/workflow-ctx.ts +209 -0
- package/src/scripts-runtime/sdk-allowlist.ts +4 -0
- package/src/scripts-runtime/swarm-sdk.ts +13 -0
- package/src/scripts-runtime/types/stdlib.d.ts +61 -0
- package/src/scripts-runtime/types/swarm-sdk.d.ts +61 -0
- package/src/server.ts +4 -0
- package/src/slack/handlers.ts +11 -4
- package/src/slack/message-text.ts +98 -0
- package/src/slack/thread-buffer.ts +5 -3
- package/src/tests/claude-adapter-binary.test.ts +271 -74
- package/src/tests/create-page-tool.test.ts +19 -2
- package/src/tests/db-query.test.ts +28 -0
- package/src/tests/error-tracker.test.ts +121 -0
- package/src/tests/harness-provider-resolution.test.ts +33 -0
- package/src/tests/heartbeat-checklist.test.ts +36 -0
- package/src/tests/mcp-tools.test.ts +6 -0
- package/src/tests/mcp-transport-gc.test.ts +58 -0
- package/src/tests/memory-health-endpoint.test.ts +78 -0
- package/src/tests/memory-store.test.ts +221 -1
- package/src/tests/pages-http.test.ts +20 -2
- package/src/tests/pages-storage.test.ts +26 -0
- package/src/tests/prompt-template-session.test.ts +34 -5
- package/src/tests/script-runs-http.test.ts +278 -0
- package/src/tests/script-workflows-label-lint.test.ts +43 -0
- package/src/tests/script-workflows-runtime-e2e.test.ts +170 -0
- package/src/tests/scripts-mcp-e2e.test.ts +102 -2
- package/src/tests/seed-scripts.test.ts +468 -3
- package/src/tests/skill-files-http.test.ts +171 -0
- package/src/tests/skill-files.test.ts +162 -0
- package/src/tests/skill-get-file-tool.test.ts +110 -0
- package/src/tests/skill-sync.test.ts +125 -6
- package/src/tests/slack-message-text.test.ts +250 -0
- package/src/tests/system-default-skills.test.ts +40 -0
- package/src/tools/create-page.ts +2 -2
- package/src/tools/db-query.ts +16 -6
- package/src/tools/script-runs.ts +123 -0
- package/src/tools/skills/index.ts +1 -0
- package/src/tools/skills/skill-get-file.ts +80 -0
- package/src/tools/slack-read.ts +12 -3
- package/src/tools/tool-config.ts +6 -2
- package/src/types.ts +72 -0
- package/src/utils/error-tracker.ts +40 -1
- package/src/utils/internal-ai/complete-structured.ts +10 -4
- package/src/workflows/executors/raw-llm.ts +76 -59
- package/templates/schedules/daily-blocker-digest/content.md +68 -54
- package/templates/schedules/daily-compounding-reflection/content.md +4 -4
- package/templates/schedules/daily-hn-briefing/content.md +5 -5
- package/templates/schedules/daily-workflow-health-audit/content.md +6 -6
- package/templates/schedules/gtm-weekly-review/content.md +9 -9
- package/templates/schedules/weekly-dependabot-triage/content.md +24 -20
- package/templates/skills/agentmail-sending/content.md +6 -7
- package/templates/skills/desloppify/content.md +8 -9
- package/templates/skills/jira-interaction/content.md +25 -33
- package/templates/skills/kapso-whatsapp/content.md +29 -30
- package/templates/skills/linear-interaction/content.md +8 -9
- package/templates/skills/pages/content.md +205 -55
- package/templates/skills/profile-corruption-escalation/content.md +44 -85
- package/templates/skills/script-workflows/config.json +14 -0
- package/templates/skills/script-workflows/content.md +68 -0
- package/templates/skills/sprite-cli/content.md +4 -5
- package/templates/skills/swarm-scripts/content.md +2 -3
- package/templates/skills/turso-interaction/content.md +14 -17
- package/templates/skills/workflow-iterate/content.md +38 -391
- package/templates/skills/x-api-interactions/content.md +4 -6
- package/templates/skills/scheduled-task-resilience/config.json +0 -14
- package/templates/skills/scheduled-task-resilience/content.md +0 -95
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
# desloppify — Code-health scan workflow (swarm edition)
|
|
2
2
|
|
|
3
|
-
`desloppify` is a multi-language codebase health scanner (peteromallet/desloppify). This skill is the swarm-adapted SKILL.md: it codifies the **install recipe with the tree-sitter pin**, the surface-only **scan → status → next → triage** workflow
|
|
3
|
+
`desloppify` is a multi-language codebase health scanner (peteromallet/desloppify). This skill is the swarm-adapted SKILL.md: it codifies the **install recipe with the tree-sitter pin**, the surface-only **scan → status → next → triage** workflow for code-health scans, and the **publish-to-agent-fs** step so humans can see the findings.
|
|
4
4
|
|
|
5
5
|
> **Default mode for swarm workers: surface-only.** Run Phase 1 + early Phase 2, publish a memo to agent-fs, stop. Do **not** run Phase 3 (queue-grinding refactors) unless the task explicitly asks.
|
|
6
6
|
|
|
@@ -8,7 +8,7 @@
|
|
|
8
8
|
|
|
9
9
|
- A task asks you to run desloppify, do a code-health scan, get a health score, or surface debt themes on a repo.
|
|
10
10
|
- A task references `peteromallet/desloppify` or the upstream `docs/SKILL.md`.
|
|
11
|
-
- You need to triage tech debt on a TS / Python / multi-lang repo
|
|
11
|
+
- You need to triage tech debt on a TS / Python / multi-lang repo.
|
|
12
12
|
|
|
13
13
|
If the task is "fix this one bug" or "rename X" → not this skill. Desloppify is for batch debt-surfacing, not point fixes.
|
|
14
14
|
|
|
@@ -28,7 +28,7 @@ pipx runpip desloppify show tree-sitter-language-pack | grep Version
|
|
|
28
28
|
|
|
29
29
|
## Step 2 — Install (first run only)
|
|
30
30
|
|
|
31
|
-
Use `pipx`. This
|
|
31
|
+
Use `pipx`. This keeps the scanner isolated from the project environment:
|
|
32
32
|
|
|
33
33
|
```bash
|
|
34
34
|
pipx install 'desloppify[full]==0.9.15'
|
|
@@ -39,7 +39,7 @@ pipx runpip desloppify show tree-sitter-language-pack | grep Version
|
|
|
39
39
|
# → must show 1.6.2 (or another <1.8)
|
|
40
40
|
```
|
|
41
41
|
|
|
42
|
-
**Why the pin:** `tree-sitter-language-pack` 1.8.
|
|
42
|
+
**Why the pin:** some `tree-sitter-language-pack` 1.8.x builds have ABI mismatches with `tree-sitter` that crash scans during language extraction. Until the installed scanner version is known to work with newer language-pack releases, cap it below 1.8.
|
|
43
43
|
|
|
44
44
|
If `pipx` isn't installed: `python3 -m pip install --user pipx && python3 -m pipx ensurepath` and start a new shell. If `pipx install` fails with build errors, jump to **Sprite escape hatch** below.
|
|
45
45
|
|
|
@@ -94,11 +94,11 @@ What to capture for the memo:
|
|
|
94
94
|
|
|
95
95
|
## Step 6 — Publish findings to agent-fs
|
|
96
96
|
|
|
97
|
-
Save the scan memo
|
|
97
|
+
Save the scan memo somewhere durable so operators can review it. If your deployment uses agent-fs, write it under your own agent namespace:
|
|
98
98
|
|
|
99
99
|
```bash
|
|
100
100
|
DATE=$(date +%Y-%m-%d)
|
|
101
|
-
agent-fs --org
|
|
101
|
+
agent-fs --org <org-id> write \
|
|
102
102
|
thoughts/$AGENT_ID/research/$DATE-desloppify-<repo>-scan.md \
|
|
103
103
|
--content "$(cat <<'EOF'
|
|
104
104
|
# desloppify scan — <repo> @ <SHA>
|
|
@@ -160,7 +160,7 @@ sprite exec -s desloppify-scan -- bash -c '
|
|
|
160
160
|
pipx install "desloppify[full]==0.9.15"
|
|
161
161
|
pipx inject --force desloppify "tree-sitter-language-pack<1.8"
|
|
162
162
|
pipx runpip desloppify show tree-sitter-language-pack | grep Version
|
|
163
|
-
git clone --depth=1 https://github.com
|
|
163
|
+
git clone --depth=1 https://github.com/<owner>/<repo>.git /tmp/repo
|
|
164
164
|
cd /tmp/repo
|
|
165
165
|
desloppify scan --path .
|
|
166
166
|
desloppify status
|
|
@@ -191,11 +191,10 @@ desloppify status
|
|
|
191
191
|
desloppify next --count 15
|
|
192
192
|
|
|
193
193
|
# Publish + report
|
|
194
|
-
agent-fs --org
|
|
194
|
+
agent-fs --org <org-id> write thoughts/$AGENT_ID/research/$(date +%F)-desloppify-<repo>-scan.md --content "..." -m "scan memo"
|
|
195
195
|
# slack-reply on the originating thread
|
|
196
196
|
```
|
|
197
197
|
|
|
198
198
|
## Upstream reference
|
|
199
199
|
|
|
200
200
|
Full Phase 3 / review workflow / plan commands are in the upstream SKILL.md: <https://github.com/peteromallet/desloppify/blob/main/docs/SKILL.md>. Reach for it when you're explicitly asked to grind the queue (rare). Default swarm mode stops after the memo + Slack reply.
|
|
201
|
-
|
|
@@ -5,15 +5,15 @@ The swarm has Jira OAuth connected but **no inbound sync** (unlike Linear). Ever
|
|
|
5
5
|
## TL;DR — minimum knowledge
|
|
6
6
|
|
|
7
7
|
1. Pull the access token from `oauth_tokens` (provider = `jira`).
|
|
8
|
-
2. Hit `https://api.atlassian.com/ex/jira/<CLOUD_ID>/rest/api/3/...` —
|
|
8
|
+
2. Hit `https://api.atlassian.com/ex/jira/<CLOUD_ID>/rest/api/3/...` — not your site hostname directly. 3LO bearer tokens only work via the `api.atlassian.com` proxy.
|
|
9
9
|
3. Bodies for descriptions/comments must be in **ADF** (Atlassian Document Format), not plain text or markdown.
|
|
10
10
|
|
|
11
|
-
##
|
|
11
|
+
## Deployment constants
|
|
12
12
|
|
|
13
|
-
- Site:
|
|
14
|
-
- Cloud ID:
|
|
15
|
-
- Default project:
|
|
16
|
-
- Scopes on the stored token:
|
|
13
|
+
- Site: `<your-site>.atlassian.net`
|
|
14
|
+
- Cloud ID: `<cloud-id>`
|
|
15
|
+
- Default project: `<PROJECT>`
|
|
16
|
+
- Scopes on the stored token: confirm from `oauth_tokens.scope` before write operations
|
|
17
17
|
|
|
18
18
|
If the cloudId ever changes, rediscover it:
|
|
19
19
|
```bash
|
|
@@ -33,7 +33,7 @@ SELECT accessToken, expiresAt, scope FROM oauth_tokens WHERE provider = 'jira';
|
|
|
33
33
|
**Always check `expiresAt` first.** Atlassian access tokens are short-lived (~1h). If expired, do NOT keep retrying — report it. Re-auth path:
|
|
34
34
|
|
|
35
35
|
```
|
|
36
|
-
|
|
36
|
+
<SWARM_API_BASE_URL>/api/trackers/jira/authorize
|
|
37
37
|
```
|
|
38
38
|
(User may need to remove the app and re-auth.)
|
|
39
39
|
|
|
@@ -65,7 +65,7 @@ curl -s -H "Authorization: Bearer $TOKEN" -H "Accept: application/json" \
|
|
|
65
65
|
|
|
66
66
|
```bash
|
|
67
67
|
curl -s -H "Authorization: Bearer $TOKEN" -H "Accept: application/json" \
|
|
68
|
-
"https://api.atlassian.com/ex/jira/$CLOUD_ID/rest/api/3/project
|
|
68
|
+
"https://api.atlassian.com/ex/jira/$CLOUD_ID/rest/api/3/project/$PROJECT_KEY" \
|
|
69
69
|
| jq '{key, name, lead: .lead.displayName, issueTypes: [.issueTypes[] | {id, name, subtask}]}'
|
|
70
70
|
```
|
|
71
71
|
|
|
@@ -76,7 +76,7 @@ Use the **`/search/jql`** endpoint (the older `/search` is deprecated for cloud)
|
|
|
76
76
|
```bash
|
|
77
77
|
curl -s -G \
|
|
78
78
|
-H "Authorization: Bearer $TOKEN" -H "Accept: application/json" \
|
|
79
|
-
--data-urlencode 'jql=project =
|
|
79
|
+
--data-urlencode 'jql=project = <PROJECT> AND statusCategory != Done' \
|
|
80
80
|
--data-urlencode 'fields=summary,status,assignee,priority' \
|
|
81
81
|
"https://api.atlassian.com/ex/jira/$CLOUD_ID/rest/api/3/search/jql" \
|
|
82
82
|
| jq '[.issues[] | {key, summary: .fields.summary, status: .fields.status.name, assignee: .fields.assignee.displayName}]'
|
|
@@ -92,7 +92,7 @@ curl -s -X POST \
|
|
|
92
92
|
"https://api.atlassian.com/ex/jira/$CLOUD_ID/rest/api/3/issue" \
|
|
93
93
|
-d '{
|
|
94
94
|
"fields": {
|
|
95
|
-
"project": { "key": "
|
|
95
|
+
"project": { "key": "<PROJECT>" },
|
|
96
96
|
"summary": "Short title",
|
|
97
97
|
"issuetype": { "name": "Task" },
|
|
98
98
|
"description": {
|
|
@@ -106,9 +106,9 @@ curl -s -X POST \
|
|
|
106
106
|
}'
|
|
107
107
|
```
|
|
108
108
|
|
|
109
|
-
Returns `{ id, key, self }` on success (HTTP 201). The `key` (e.g.
|
|
109
|
+
Returns `{ id, key, self }` on success (HTTP 201). The `key` (e.g. `<PROJECT>-7`) is what humans use; URL is `https://<your-site>.atlassian.net/browse/<KEY>`.
|
|
110
110
|
|
|
111
|
-
Available issue types
|
|
111
|
+
Available issue types are project-specific; list the project before creating issues.
|
|
112
112
|
|
|
113
113
|
### 5. Transition issue status (e.g. → Done)
|
|
114
114
|
|
|
@@ -120,23 +120,15 @@ curl -s -H "Authorization: Bearer $TOKEN" -H "Accept: application/json" \
|
|
|
120
120
|
| jq '.transitions[] | {id, name, to: .to.name}'
|
|
121
121
|
```
|
|
122
122
|
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
| Transition ID | Target state |
|
|
126
|
-
|---|---|
|
|
127
|
-
| `11` | To Do |
|
|
128
|
-
| `21` | In Progress |
|
|
129
|
-
| `31` | In Review |
|
|
130
|
-
| `41` | Backlog |
|
|
131
|
-
| `51` | **Done** |
|
|
123
|
+
Transition IDs are project-specific. Do not copy IDs between Jira projects; discover them for the issue you are about to update.
|
|
132
124
|
|
|
133
125
|
Transition (returns HTTP 204 on success, no body):
|
|
134
126
|
|
|
135
127
|
```bash
|
|
136
128
|
curl -s -X POST \
|
|
137
129
|
-H "Authorization: Bearer $TOKEN" -H "Accept: application/json" -H "Content-Type: application/json" \
|
|
138
|
-
"https://api.atlassian.com/ex/jira/$CLOUD_ID/rest/api/3/issue
|
|
139
|
-
-d '{"transition":{"id":"
|
|
130
|
+
"https://api.atlassian.com/ex/jira/$CLOUD_ID/rest/api/3/issue/<KEY>/transitions" \
|
|
131
|
+
-d '{"transition":{"id":"<TRANSITION_ID>"}}'
|
|
140
132
|
```
|
|
141
133
|
|
|
142
134
|
### 6. Comment on an issue
|
|
@@ -146,7 +138,7 @@ ADF body again:
|
|
|
146
138
|
```bash
|
|
147
139
|
curl -s -X POST \
|
|
148
140
|
-H "Authorization: Bearer $TOKEN" -H "Accept: application/json" -H "Content-Type: application/json" \
|
|
149
|
-
"https://api.atlassian.com/ex/jira/$CLOUD_ID/rest/api/3/issue
|
|
141
|
+
"https://api.atlassian.com/ex/jira/$CLOUD_ID/rest/api/3/issue/<KEY>/comment" \
|
|
150
142
|
-d '{
|
|
151
143
|
"body": {
|
|
152
144
|
"type": "doc", "version": 1,
|
|
@@ -161,7 +153,7 @@ Atlassian Cloud uses **accountId**, not username. Find one via:
|
|
|
161
153
|
|
|
162
154
|
```bash
|
|
163
155
|
curl -s -G -H "Authorization: Bearer $TOKEN" -H "Accept: application/json" \
|
|
164
|
-
--data-urlencode 'query
|
|
156
|
+
--data-urlencode 'query=<name-or-email>' \
|
|
165
157
|
"https://api.atlassian.com/ex/jira/$CLOUD_ID/rest/api/3/user/search" \
|
|
166
158
|
| jq '.[] | {accountId, displayName, emailAddress}'
|
|
167
159
|
```
|
|
@@ -170,7 +162,7 @@ Assign:
|
|
|
170
162
|
```bash
|
|
171
163
|
curl -s -X PUT \
|
|
172
164
|
-H "Authorization: Bearer $TOKEN" -H "Accept: application/json" -H "Content-Type: application/json" \
|
|
173
|
-
"https://api.atlassian.com/ex/jira/$CLOUD_ID/rest/api/3/issue
|
|
165
|
+
"https://api.atlassian.com/ex/jira/$CLOUD_ID/rest/api/3/issue/<KEY>/assignee" \
|
|
174
166
|
-d '{"accountId":"<ACCOUNT_ID>"}'
|
|
175
167
|
```
|
|
176
168
|
|
|
@@ -181,7 +173,7 @@ To unassign: `{"accountId": null}`.
|
|
|
181
173
|
```bash
|
|
182
174
|
curl -s -X PUT \
|
|
183
175
|
-H "Authorization: Bearer $TOKEN" -H "Accept: application/json" -H "Content-Type: application/json" \
|
|
184
|
-
"https://api.atlassian.com/ex/jira/$CLOUD_ID/rest/api/3/issue
|
|
176
|
+
"https://api.atlassian.com/ex/jira/$CLOUD_ID/rest/api/3/issue/<KEY>" \
|
|
185
177
|
-d '{ "fields": { "summary": "New summary", "labels": ["swarm","auto"] } }'
|
|
186
178
|
```
|
|
187
179
|
|
|
@@ -204,7 +196,7 @@ If you need rich content, build it in a script — don't try to write deep ADF i
|
|
|
204
196
|
## Operational rules
|
|
205
197
|
|
|
206
198
|
- **Token-expiry first.** Always check `expiresAt`. Don't loop on 401s.
|
|
207
|
-
- **Use the proxy.** All authenticated calls go through `api.atlassian.com/ex/jira/<cloudId>/...`. Hitting
|
|
199
|
+
- **Use the proxy.** All authenticated calls go through `api.atlassian.com/ex/jira/<cloudId>/...`. Hitting `<your-site>.atlassian.net/rest/api/3/...` with a 3LO bearer token will fail.
|
|
208
200
|
- **Discover transitions per issue** before transitioning — different projects/workflows have different IDs.
|
|
209
201
|
- **Use `/search/jql`**, not the legacy `/search` (which is deprecated and may be removed).
|
|
210
202
|
- **ADF is mandatory** for `description`, `comment`, and rich text fields. Plain strings will be rejected.
|
|
@@ -226,21 +218,22 @@ If you need rich content, build it in a script — don't try to write deep ADF i
|
|
|
226
218
|
|
|
227
219
|
```bash
|
|
228
220
|
TOKEN=$(db-query "SELECT accessToken FROM oauth_tokens WHERE provider='jira'")
|
|
229
|
-
CLOUD_ID="
|
|
221
|
+
CLOUD_ID="<cloud-id>"
|
|
222
|
+
PROJECT_KEY="<PROJECT>"
|
|
230
223
|
|
|
231
224
|
# 1. List open issues in KAN
|
|
232
225
|
curl -s -G -H "Authorization: Bearer $TOKEN" -H "Accept: application/json" \
|
|
233
|
-
--data-urlencode
|
|
226
|
+
--data-urlencode "jql=project = $PROJECT_KEY AND statusCategory != Done" \
|
|
234
227
|
--data-urlencode 'fields=summary,status' \
|
|
235
228
|
"https://api.atlassian.com/ex/jira/$CLOUD_ID/rest/api/3/search/jql" \
|
|
236
229
|
| jq -r '.issues[].key' > /tmp/keys.txt
|
|
237
230
|
|
|
238
|
-
# 2. Transition each to Done
|
|
231
|
+
# 2. Transition each to Done using the transition ID discovered for this workflow
|
|
239
232
|
for KEY in $(cat /tmp/keys.txt); do
|
|
240
233
|
curl -s -X POST \
|
|
241
234
|
-H "Authorization: Bearer $TOKEN" -H "Accept: application/json" -H "Content-Type: application/json" \
|
|
242
235
|
"https://api.atlassian.com/ex/jira/$CLOUD_ID/rest/api/3/issue/$KEY/transitions" \
|
|
243
|
-
-d '{"transition":{"id":"
|
|
236
|
+
-d '{"transition":{"id":"<DONE_TRANSITION_ID>"}}'
|
|
244
237
|
sleep 0.3
|
|
245
238
|
done
|
|
246
239
|
```
|
|
@@ -249,4 +242,3 @@ done
|
|
|
249
242
|
|
|
250
243
|
- The MCP tracker tools (`tracker-link-task`, `tracker-sync-status`, etc.) are designed for two-way sync mappings. Jira tracker support exists at the schema level but is not currently wired up to inbound webhooks. Until it is, all Jira interaction must go through this skill.
|
|
251
244
|
- If/when inbound Jira webhooks land, this skill should add a "When to transition" section mirroring the Linear one.
|
|
252
|
-
|
|
@@ -1,10 +1,10 @@
|
|
|
1
1
|
# Kapso WhatsApp
|
|
2
2
|
|
|
3
|
-
Kapso (https://kapso.ai) is
|
|
3
|
+
Kapso (https://kapso.ai) is a WhatsApp platform vendor for the Meta Cloud API. A swarm can provision one or more phone numbers and use either native inbound handlers or workflows to dispatch a task per inbound message.
|
|
4
4
|
|
|
5
5
|
## When to use MCP tools vs this skill's REST recipes
|
|
6
6
|
|
|
7
|
-
|
|
7
|
+
Some deployments expose **thin MCP-tool wrappers for the common case only**:
|
|
8
8
|
|
|
9
9
|
| Tool | Use for |
|
|
10
10
|
|---|---|
|
|
@@ -32,7 +32,7 @@ Swarm config keys (resolve with `get-config key:<NAME> includeSecrets:true` —
|
|
|
32
32
|
|---|---|
|
|
33
33
|
| `KAPSO_API_BASE_URL` | `https://api.kapso.ai` (host only, no `/platform/v1`) |
|
|
34
34
|
| `KAPSO_API_KEY` | API key (`X-API-Key` header) |
|
|
35
|
-
| `KAPSO_PHONE_NUMBER_ID` |
|
|
35
|
+
| `KAPSO_PHONE_NUMBER_ID` | Provisioned number's Meta ID |
|
|
36
36
|
| `KAPSO_WEBHOOK_HMAC_SECRET` | Shared HMAC secret. Kapso signs every webhook request with `X-Webhook-Signature: <hex>` |
|
|
37
37
|
|
|
38
38
|
The Kapso CLI is NOT installed in worker containers. Use direct HTTP or clone the `gokapso/agent-skills` repo for fallback scripts.
|
|
@@ -46,7 +46,7 @@ The Meta Cloud API is proxied at `$KAPSO_API_BASE_URL/meta/whatsapp/v24.0/...` (
|
|
|
46
46
|
|
|
47
47
|
## Inbound webhook payload (v2)
|
|
48
48
|
|
|
49
|
-
|
|
49
|
+
Your inbound workflow receives `whatsapp.message.*` and `whatsapp.conversation.*` events at `POST <SWARM_API_BASE_URL>/api/webhooks/<workflow-id>`.
|
|
50
50
|
|
|
51
51
|
Shape (top-level keys):
|
|
52
52
|
|
|
@@ -54,7 +54,7 @@ Shape (top-level keys):
|
|
|
54
54
|
{
|
|
55
55
|
"message": {
|
|
56
56
|
"id": "wamid.HBgL...", // Meta message id (WAMID)
|
|
57
|
-
"from": "
|
|
57
|
+
"from": "15550100000", // dummy E.164 without +
|
|
58
58
|
"from_user_id": "ES.26772...", // Meta-internal user id
|
|
59
59
|
"timestamp": "1779281573", // unix seconds (string)
|
|
60
60
|
"type": "text", // text | image | audio | video | document | sticker | location | contacts | reaction | ...
|
|
@@ -71,9 +71,9 @@ Shape (top-level keys):
|
|
|
71
71
|
},
|
|
72
72
|
"conversation": {
|
|
73
73
|
"id": "bd7e888e-...",
|
|
74
|
-
"phone_number": "
|
|
75
|
-
"phone_number_id": "
|
|
76
|
-
"contact_name": "
|
|
74
|
+
"phone_number": "15550100000",
|
|
75
|
+
"phone_number_id": "<phone-number-id>",
|
|
76
|
+
"contact_name": "Example Contact",
|
|
77
77
|
"status": "active",
|
|
78
78
|
"last_active_at": "...",
|
|
79
79
|
"created_at": "...",
|
|
@@ -86,7 +86,7 @@ Shape (top-level keys):
|
|
|
86
86
|
}
|
|
87
87
|
},
|
|
88
88
|
"is_new_conversation": false,
|
|
89
|
-
"phone_number_id": "
|
|
89
|
+
"phone_number_id": "<phone-number-id>"
|
|
90
90
|
}
|
|
91
91
|
```
|
|
92
92
|
|
|
@@ -143,7 +143,7 @@ NB: verify the exact proxy path against a real media message — the swarm has o
|
|
|
143
143
|
|
|
144
144
|
Two paths in order:
|
|
145
145
|
|
|
146
|
-
1. By name: `resolve-user name:"<contact_name>"` (fuzzy substring match). Returns the canonical profile if there's one. Useful for known team members
|
|
146
|
+
1. By name: `resolve-user name:"<contact_name>"` (fuzzy substring match). Returns the canonical profile if there's one. Useful for known team members.
|
|
147
147
|
2. If no match — the contact is unknown. Lead can run `manage-user create name:"<contact_name>" notes:"WhatsApp +<phone>"` to register them. Workers should NOT create users autonomously; ask Lead.
|
|
148
148
|
|
|
149
149
|
Always quote the phone number in `manage-user notes` so future lookups by phone work (until we add a `phone` column to the user registry).
|
|
@@ -158,7 +158,7 @@ API_KEY=$(get-config KAPSO_API_KEY)
|
|
|
158
158
|
|
|
159
159
|
# List conversations for our number
|
|
160
160
|
curl -s -H "X-API-Key: $API_KEY" \
|
|
161
|
-
"$API_BASE/platform/v1/whatsapp/conversations?phone_number_id
|
|
161
|
+
"$API_BASE/platform/v1/whatsapp/conversations?phone_number_id=$KAPSO_PHONE_NUMBER_ID&status=active" | jq
|
|
162
162
|
|
|
163
163
|
# Get a single conversation
|
|
164
164
|
curl -s -H "X-API-Key: $API_KEY" \
|
|
@@ -183,8 +183,8 @@ Per WhatsApp policy, free-form text is only allowed within 24h of the last inbou
|
|
|
183
183
|
**Common case shortcut:** call the `send-whatsapp-message` MCP tool — it wraps exactly this REST call. The recipe below is the canonical reference and the fallback when you need fields the tool doesn't expose.
|
|
184
184
|
|
|
185
185
|
```bash
|
|
186
|
-
TO="
|
|
187
|
-
TEXT="Hi
|
|
186
|
+
TO="15550100000"
|
|
187
|
+
TEXT="Hi there"
|
|
188
188
|
|
|
189
189
|
curl -s -X POST -H "X-API-Key: $API_KEY" -H "Content-Type: application/json" \
|
|
190
190
|
-d "{
|
|
@@ -194,7 +194,7 @@ curl -s -X POST -H "X-API-Key: $API_KEY" -H "Content-Type: application/json" \
|
|
|
194
194
|
\"type\": \"text\",
|
|
195
195
|
\"text\": { \"preview_url\": false, \"body\": \"$TEXT\" }
|
|
196
196
|
}" \
|
|
197
|
-
"$API_BASE/meta/whatsapp/v24.0/
|
|
197
|
+
"$API_BASE/meta/whatsapp/v24.0/$KAPSO_PHONE_NUMBER_ID/messages" | jq
|
|
198
198
|
```
|
|
199
199
|
|
|
200
200
|
Returns `{ "messages": [{ "id": "wamid..." }] }` on success. Log the wamid.
|
|
@@ -218,7 +218,7 @@ Prefer quote-replies when answering a specific question — it keeps long conver
|
|
|
218
218
|
|
|
219
219
|
## Sending media (image, document, audio, video)
|
|
220
220
|
|
|
221
|
-
Two-step pipeline through Kapso's Meta proxy: **upload, then send by id**. Sending by `id` is more reliable than `link`
|
|
221
|
+
Two-step pipeline through Kapso's Meta proxy: **upload, then send by id**. Sending by `id` is more reliable than `link` because it does not require the media to be hosted at a public URL.
|
|
222
222
|
|
|
223
223
|
### 1. Upload
|
|
224
224
|
|
|
@@ -227,7 +227,7 @@ curl -s -X POST -H "X-API-Key: $API_KEY" \
|
|
|
227
227
|
-F "messaging_product=whatsapp" \
|
|
228
228
|
-F "type=<mime>" \
|
|
229
229
|
-F "file=@/path/to/file.ext;type=<mime>" \
|
|
230
|
-
"$API_BASE/meta/whatsapp/v24.0/
|
|
230
|
+
"$API_BASE/meta/whatsapp/v24.0/$KAPSO_PHONE_NUMBER_ID/media"
|
|
231
231
|
# → {"id":"<media-id>"}
|
|
232
232
|
```
|
|
233
233
|
|
|
@@ -242,7 +242,7 @@ curl -s -X POST -H "X-API-Key: $API_KEY" \
|
|
|
242
242
|
|
|
243
243
|
Quote-reply works on media too — add `"context": { "message_id": "<wamid>" }` at the top level.
|
|
244
244
|
|
|
245
|
-
### Wide images: pad to ~square, send as image
|
|
245
|
+
### Wide images: pad to ~square, send as image
|
|
246
246
|
|
|
247
247
|
WhatsApp scales `type:image` to bubble width + recompresses, so a wide 1200×630 social card renders as a tiny shrunken strip. **The fix is NOT `type:document`** — a `.png` sent as a document shows a plain file card with NO inline preview (must tap+download). Bad UX both ways.
|
|
248
248
|
|
|
@@ -280,7 +280,7 @@ curl -s -X POST -H "X-API-Key: $API_KEY" -H "Content-Type: application/json" \
|
|
|
280
280
|
"message_id": "<inbound_wamid>",
|
|
281
281
|
"typing_indicator": { "type": "text" }
|
|
282
282
|
}' \
|
|
283
|
-
"$API_BASE/meta/whatsapp/v24.0/
|
|
283
|
+
"$API_BASE/meta/whatsapp/v24.0/$KAPSO_PHONE_NUMBER_ID/messages"
|
|
284
284
|
# → {"success":true}
|
|
285
285
|
```
|
|
286
286
|
|
|
@@ -297,7 +297,7 @@ curl -s -X POST -H "X-API-Key: $API_KEY" -H "Content-Type: application/json" \
|
|
|
297
297
|
"type": "reaction",
|
|
298
298
|
"reaction": { "message_id": "<wamid>", "emoji": "👀" }
|
|
299
299
|
}' \
|
|
300
|
-
"$API_BASE/meta/whatsapp/v24.0/
|
|
300
|
+
"$API_BASE/meta/whatsapp/v24.0/$KAPSO_PHONE_NUMBER_ID/messages"
|
|
301
301
|
```
|
|
302
302
|
|
|
303
303
|
A user can have only ONE reaction per message — sending a new emoji REPLACES the previous one (no explicit remove needed). Send `"emoji": ""` to clear a reaction entirely.
|
|
@@ -310,21 +310,21 @@ If `send-whatsapp-message` returns `sessionWindowExpired: true`, fall through to
|
|
|
310
310
|
curl -s -X POST -H "X-API-Key: $API_KEY" -H "Content-Type: application/json" \
|
|
311
311
|
-d '{
|
|
312
312
|
"messaging_product": "whatsapp",
|
|
313
|
-
"to": "
|
|
313
|
+
"to": "15550100000",
|
|
314
314
|
"type": "template",
|
|
315
315
|
"template": {
|
|
316
316
|
"name": "<template_name>",
|
|
317
317
|
"language": { "code": "en_US" }
|
|
318
318
|
}
|
|
319
319
|
}' \
|
|
320
|
-
"$API_BASE/meta/whatsapp/v24.0/
|
|
320
|
+
"$API_BASE/meta/whatsapp/v24.0/$KAPSO_PHONE_NUMBER_ID/messages"
|
|
321
321
|
```
|
|
322
322
|
|
|
323
|
-
List approved templates first: `GET $API_BASE/platform/v1/whatsapp/templates?phone_number_id
|
|
323
|
+
List approved templates first: `GET $API_BASE/platform/v1/whatsapp/templates?phone_number_id=$KAPSO_PHONE_NUMBER_ID`.
|
|
324
324
|
|
|
325
325
|
## Webhook signature verification
|
|
326
326
|
|
|
327
|
-
Every Kapso webhook delivery includes `X-Webhook-Signature: <hex>` (HMAC-SHA256 of the raw body using `KAPSO_WEBHOOK_HMAC_SECRET`).
|
|
327
|
+
Every Kapso webhook delivery includes `X-Webhook-Signature: <hex>` (HMAC-SHA256 of the raw body using `KAPSO_WEBHOOK_HMAC_SECRET`). Native handlers and workflow webhook triggers should verify the signature automatically; if you configure a custom trigger, set its HMAC header to `X-Webhook-Signature` and resolve the secret from swarm config.
|
|
328
328
|
|
|
329
329
|
To verify manually:
|
|
330
330
|
|
|
@@ -337,17 +337,17 @@ echo -n "$RAW_BODY" | openssl dgst -sha256 -hmac "$HMAC_SECRET" -hex | awk '{pri
|
|
|
337
337
|
|
|
338
338
|
- Same language as the inbound message (Spanish/English/Catalan — match what they wrote).
|
|
339
339
|
- Brief. WhatsApp is not Slack — 1-3 short messages max.
|
|
340
|
-
- Identify yourself if it's a first interaction in the conversation: "Hi! This is the
|
|
340
|
+
- Identify yourself if it's a first interaction in the conversation: "Hi! This is the agent handling this WhatsApp inbox."
|
|
341
341
|
- Quote-reply (`context.message_id`) when answering a specific question.
|
|
342
342
|
- If you can't help (no skill for the request, out of scope) — say so and either escalate to Lead or ask the human to use Slack instead.
|
|
343
343
|
- Always log the outbound wamid in your task output so it's traceable.
|
|
344
344
|
|
|
345
345
|
## Where this fits in the swarm
|
|
346
346
|
|
|
347
|
-
Two inbound paths exist
|
|
347
|
+
Two common inbound paths exist:
|
|
348
348
|
|
|
349
|
-
- **Native handler** (`/api/integrations/kapso/webhook
|
|
350
|
-
- **Workflow
|
|
349
|
+
- **Native handler** (`/api/integrations/kapso/webhook`) — fires for any phone number registered via `register-kapso-number`. Verifies HMAC, dedupes by message id, reads the routing mapping, and either dispatches an inbound-message task or delegates to a workflow trigger.
|
|
350
|
+
- **Workflow trigger** (`<workflow-id>`) — useful when you want custom routing, batching, triage, or approval steps before a reply is sent. A typical pipeline marks the message read, debounces rapid-fire bursts, routes to an agent task, and optionally sends a final reaction or status update.
|
|
351
351
|
|
|
352
352
|
**Debounce / batching:** the demo workflow's `debounce` node waits ~8s after each message and only the LAST message of a burst proceeds to the agent task — so a user firing 3 quick messages produces ONE task, not three. The agent is told the `batchSize` and should read trailing history and answer the whole burst in one reply. When >1 messages are collapsed, the user sees a "🧵 Got your N messages" note.
|
|
353
353
|
|
|
@@ -357,13 +357,12 @@ HMAC verification is enforced (signed mode) on both paths.
|
|
|
357
357
|
|
|
358
358
|
## Common gotchas
|
|
359
359
|
|
|
360
|
-
- Phone numbers from Kapso are E.164 **without `+`** (e.g. `
|
|
360
|
+
- Phone numbers from Kapso are E.164 **without `+`** (e.g. `15550100000`). Add `+` when displaying to humans, drop it when calling the API.
|
|
361
361
|
- `message.text.body` is only present for `type:"text"`. For other types read `message.<type>` (see the table above) or `message.kapso.content` for a text representation.
|
|
362
362
|
- Outbound status events (`delivered`, `read`) are NOT a customer interaction — skip them. Filter by `message.kapso.direction == "inbound"`.
|
|
363
363
|
- Real inbound messages commonly arrive with `status: "delivered"` (delivered to us). Do NOT skip on status — only `direction` signals inbound vs outbound.
|
|
364
364
|
- Kapso sometimes sends test payloads with `"test": true` and `wamid.TEST_*` ids. Don't reply to test payloads — just complete the task with a note.
|
|
365
|
-
- The provisioned phone number id
|
|
365
|
+
- The provisioned phone number id is your sender number, not the recipient's. The recipient is in `message.from` / `conversation.phone_number`.
|
|
366
366
|
- The message-list endpoint is `/platform/v1/whatsapp/messages?conversation_id=X` — the conversation-scoped `/conversations/<id>/messages` path 404s.
|
|
367
367
|
- **Wide images shrink — pad them, don't send as document.** `type:image` scales to bubble width; a wide social card becomes a strip. Sending it as `type:document` removes the preview entirely. Fix: pad onto a ~1:1/4:5 canvas and send as `type:image`. See "Sending media".
|
|
368
368
|
- **MCP tools cover text-only.** `send-whatsapp-message` and `reply-whatsapp-message` are deliberately thin — templates / media / reactions / typing / mark-as-read are NOT in the tool surface. For those, use the REST recipes in this skill directly.
|
|
369
|
-
|
|
@@ -7,7 +7,7 @@ db-query: SELECT accessToken, expiresAt FROM oauth_tokens WHERE provider = 'line
|
|
|
7
7
|
```
|
|
8
8
|
If `expiresAt` is in the past, do NOT attempt the API calls — just report the needed update in your task output.
|
|
9
9
|
|
|
10
|
-
To authorize
|
|
10
|
+
To re-authorize, use your swarm API base URL with `/api/trackers/linear/authorize` (potentially needing to remove the app and re-auth). Only mention this if you can confirm the token is expired or not present.
|
|
11
11
|
|
|
12
12
|
---
|
|
13
13
|
|
|
@@ -22,7 +22,7 @@ The available MCP tracker tools (`tracker-link-task`, `tracker-link-epic`, `trac
|
|
|
22
22
|
|
|
23
23
|
## When to Transition (Timing)
|
|
24
24
|
|
|
25
|
-
- **
|
|
25
|
+
- **Direct-to-main work:** Transition the Linear ticket to **Done** the moment the worker reports ship (commits on `main`). Do NOT wait for review, test-run, or merge when there is no PR to wait for. Waiting causes blocker digests to flag RESOLVED-STALE tickets.
|
|
26
26
|
- **Standard PR workflow:** Transition to **In Review** on PR open, **Done** after merge. If the ticket is still "In Progress" 30 min after the PR merges, you're late.
|
|
27
27
|
- **Blocked:** If a ticket is stuck on a dependency, add a comment linking the blocker — don't leave it silent.
|
|
28
28
|
|
|
@@ -54,9 +54,9 @@ curl -s -X POST https://api.linear.app/graphql \
|
|
|
54
54
|
-d '{"query": "<GRAPHQL_QUERY>"}'
|
|
55
55
|
```
|
|
56
56
|
|
|
57
|
-
## Agent Interaction API — `action` vs `thought`
|
|
57
|
+
## Agent Interaction API — `action` vs `thought`
|
|
58
58
|
|
|
59
|
-
Linear's **Agent Interaction API** (different from issue mutations above) supports two activity payload kinds.
|
|
59
|
+
Linear's **Agent Interaction API** (different from issue mutations above) supports two activity payload kinds. Use this section whenever emitting `agentActivityCreate` mutations.
|
|
60
60
|
|
|
61
61
|
| Activity kind | When to use | `parameter` field |
|
|
62
62
|
|---|---|---|
|
|
@@ -76,7 +76,7 @@ Linear's **Agent Interaction API** (different from issue mutations above) suppor
|
|
|
76
76
|
| Branch create / merge / delete | `action` | branch name |
|
|
77
77
|
| PR open / review / merge | `action` | PR URL |
|
|
78
78
|
|
|
79
|
-
**Why this trips people:** "action" reads naturally as "every tool call IS an action…". But Linear uses `action` to mean *parameterized operation Linear can index/route on*, not *task-progress-narration*. Narration is `thought`.
|
|
79
|
+
**Why this trips people:** "action" reads naturally as "every tool call IS an action…". But Linear uses `action` to mean *parameterized operation Linear can index/route on*, not *task-progress-narration*. Narration is `thought`.
|
|
80
80
|
|
|
81
81
|
## Common Operations
|
|
82
82
|
|
|
@@ -120,8 +120,8 @@ mutation {
|
|
|
120
120
|
}
|
|
121
121
|
```
|
|
122
122
|
|
|
123
|
-
**Known state IDs
|
|
124
|
-
-
|
|
123
|
+
**Known state IDs:**
|
|
124
|
+
- Store your team's common state UUIDs in local notes or swarm config; do not hardcode another team's IDs into this template.
|
|
125
125
|
|
|
126
126
|
### 2. Add a Comment to an Issue
|
|
127
127
|
|
|
@@ -213,7 +213,7 @@ curl -s -X POST https://api.linear.app/graphql \
|
|
|
213
213
|
## Important Notes
|
|
214
214
|
|
|
215
215
|
- **Always update Linear when completing Linear-sourced tasks.** The user expects the ticket to reflect the swarm's work. Marking only the swarm task as complete is insufficient. Do not complete only the swarm task — failing to update Linear breaks the sync and wastes resources.
|
|
216
|
-
- **Transition timing:** see the "When to Transition" section above.
|
|
216
|
+
- **Transition timing:** see the "When to Transition" section above. Direct-to-main work transitions on ship, not on merge.
|
|
217
217
|
- **Token expiry:** Check `expiresAt` before making calls. If expired, notify the user — they need to re-authorize.
|
|
218
218
|
- **Rate limits:** Linear has rate limits. For bulk operations, add small delays between calls.
|
|
219
219
|
- **Issue identifiers vs UUIDs:** The human-readable identifier (e.g., "DES-12") works for queries but the `issueUpdate` mutation requires the actual UUID. Always fetch the UUID first via a query.
|
|
@@ -227,4 +227,3 @@ Common errors:
|
|
|
227
227
|
- `Entity not found` → Wrong issue ID/identifier
|
|
228
228
|
- `"parameter must not be empty"` (or similar on Agent Interaction API) → You sent an `action` activity without a `parameter` — convert to `thought` or fill in a real noun. See "Agent Interaction API — action vs thought" above.
|
|
229
229
|
- Rate limited → Back off and retry after delay
|
|
230
|
-
|