@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.
Files changed (114) hide show
  1. package/README.md +3 -2
  2. package/openapi.json +1005 -152
  3. package/package.json +6 -6
  4. package/plugin/skills/pages/SKILL.md +5 -2
  5. package/src/be/db.ts +662 -19
  6. package/src/be/memory/constants.ts +2 -1
  7. package/src/be/memory/providers/openai-embedding.ts +2 -5
  8. package/src/be/memory/providers/sqlite-store.ts +293 -76
  9. package/src/be/memory/types.ts +35 -0
  10. package/src/be/migrations/083_script_workflows.sql +51 -0
  11. package/src/be/migrations/084_script_run_journal_duration.sql +5 -0
  12. package/src/be/migrations/085_script_runs_kind.sql +9 -0
  13. package/src/be/migrations/086_pages_default_authed.sql +64 -0
  14. package/src/be/migrations/087_skill_files.sql +19 -0
  15. package/src/be/modelsdev-cache.json +42310 -38617
  16. package/src/be/scripts/typecheck.ts +49 -0
  17. package/src/be/seed-scripts/catalog/boot-triage.ts +221 -0
  18. package/src/be/seed-scripts/catalog/catalog-report.ts +457 -0
  19. package/src/be/seed-scripts/catalog/compound-insights.ts +310 -6
  20. package/src/be/seed-scripts/catalog/gh-pr-snapshot.ts +1 -1
  21. package/src/be/seed-scripts/catalog/memory-eval.ts +1059 -0
  22. package/src/be/seed-scripts/catalog/ops-catalog-audit.ts +506 -0
  23. package/src/be/seed-scripts/catalog/schedule-health.ts +78 -2
  24. package/src/be/seed-scripts/catalog/task-context-gathering.ts +92 -0
  25. package/src/be/seed-scripts/catalog/task-failure-audit.ts +48 -1
  26. package/src/be/seed-scripts/catalog/tool-usage.ts +6 -3
  27. package/src/be/seed-scripts/index.ts +51 -5
  28. package/src/be/seed-skills/index.ts +3 -3
  29. package/src/be/skill-sync.ts +91 -7
  30. package/src/be/swarm-config-guard.ts +17 -0
  31. package/src/commands/runner.ts +49 -4
  32. package/src/heartbeat/templates.ts +20 -16
  33. package/src/http/db-query.ts +20 -5
  34. package/src/http/index.ts +51 -7
  35. package/src/http/mcp-user.ts +23 -0
  36. package/src/http/mcp.ts +58 -0
  37. package/src/http/memory.ts +58 -0
  38. package/src/http/pages.ts +1 -1
  39. package/src/http/script-runs.ts +557 -0
  40. package/src/http/scripts.ts +39 -2
  41. package/src/http/skills.ts +225 -0
  42. package/src/prompts/session-templates.ts +24 -4
  43. package/src/providers/claude-adapter.ts +107 -28
  44. package/src/script-workflows/executor.ts +110 -0
  45. package/src/script-workflows/harness.ts +73 -0
  46. package/src/script-workflows/label-lint.ts +51 -0
  47. package/src/script-workflows/limits.ts +22 -0
  48. package/src/script-workflows/supervisor.ts +139 -0
  49. package/src/script-workflows/workflow-ctx.ts +209 -0
  50. package/src/scripts-runtime/sdk-allowlist.ts +4 -0
  51. package/src/scripts-runtime/swarm-sdk.ts +13 -0
  52. package/src/scripts-runtime/types/stdlib.d.ts +61 -0
  53. package/src/scripts-runtime/types/swarm-sdk.d.ts +61 -0
  54. package/src/server.ts +4 -0
  55. package/src/slack/handlers.ts +11 -4
  56. package/src/slack/message-text.ts +98 -0
  57. package/src/slack/thread-buffer.ts +5 -3
  58. package/src/tests/claude-adapter-binary.test.ts +271 -74
  59. package/src/tests/create-page-tool.test.ts +19 -2
  60. package/src/tests/db-query.test.ts +28 -0
  61. package/src/tests/error-tracker.test.ts +121 -0
  62. package/src/tests/harness-provider-resolution.test.ts +33 -0
  63. package/src/tests/heartbeat-checklist.test.ts +36 -0
  64. package/src/tests/mcp-tools.test.ts +6 -0
  65. package/src/tests/mcp-transport-gc.test.ts +58 -0
  66. package/src/tests/memory-health-endpoint.test.ts +78 -0
  67. package/src/tests/memory-store.test.ts +221 -1
  68. package/src/tests/pages-http.test.ts +20 -2
  69. package/src/tests/pages-storage.test.ts +26 -0
  70. package/src/tests/prompt-template-session.test.ts +34 -5
  71. package/src/tests/script-runs-http.test.ts +278 -0
  72. package/src/tests/script-workflows-label-lint.test.ts +43 -0
  73. package/src/tests/script-workflows-runtime-e2e.test.ts +170 -0
  74. package/src/tests/scripts-mcp-e2e.test.ts +102 -2
  75. package/src/tests/seed-scripts.test.ts +468 -3
  76. package/src/tests/skill-files-http.test.ts +171 -0
  77. package/src/tests/skill-files.test.ts +162 -0
  78. package/src/tests/skill-get-file-tool.test.ts +110 -0
  79. package/src/tests/skill-sync.test.ts +125 -6
  80. package/src/tests/slack-message-text.test.ts +250 -0
  81. package/src/tests/system-default-skills.test.ts +40 -0
  82. package/src/tools/create-page.ts +2 -2
  83. package/src/tools/db-query.ts +16 -6
  84. package/src/tools/script-runs.ts +123 -0
  85. package/src/tools/skills/index.ts +1 -0
  86. package/src/tools/skills/skill-get-file.ts +80 -0
  87. package/src/tools/slack-read.ts +12 -3
  88. package/src/tools/tool-config.ts +6 -2
  89. package/src/types.ts +72 -0
  90. package/src/utils/error-tracker.ts +40 -1
  91. package/src/utils/internal-ai/complete-structured.ts +10 -4
  92. package/src/workflows/executors/raw-llm.ts +76 -59
  93. package/templates/schedules/daily-blocker-digest/content.md +68 -54
  94. package/templates/schedules/daily-compounding-reflection/content.md +4 -4
  95. package/templates/schedules/daily-hn-briefing/content.md +5 -5
  96. package/templates/schedules/daily-workflow-health-audit/content.md +6 -6
  97. package/templates/schedules/gtm-weekly-review/content.md +9 -9
  98. package/templates/schedules/weekly-dependabot-triage/content.md +24 -20
  99. package/templates/skills/agentmail-sending/content.md +6 -7
  100. package/templates/skills/desloppify/content.md +8 -9
  101. package/templates/skills/jira-interaction/content.md +25 -33
  102. package/templates/skills/kapso-whatsapp/content.md +29 -30
  103. package/templates/skills/linear-interaction/content.md +8 -9
  104. package/templates/skills/pages/content.md +205 -55
  105. package/templates/skills/profile-corruption-escalation/content.md +44 -85
  106. package/templates/skills/script-workflows/config.json +14 -0
  107. package/templates/skills/script-workflows/content.md +68 -0
  108. package/templates/skills/sprite-cli/content.md +4 -5
  109. package/templates/skills/swarm-scripts/content.md +2 -3
  110. package/templates/skills/turso-interaction/content.md +14 -17
  111. package/templates/skills/workflow-iterate/content.md +38 -391
  112. package/templates/skills/x-api-interactions/content.md +4 -6
  113. package/templates/skills/scheduled-task-resilience/config.json +0 -14
  114. 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 we run for repos like `agent-swarm`, and the **publish-to-agent-fs** step so humans (Eze) can see the findings.
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 (agent-swarm, landing, agent-swarm-landing, desplega-ai, etc.).
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 is the working recipe verified in PR #463 (Dockerfile.worker) and across multiple sprite smokes:
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.0 has an ABI mismatch with `tree-sitter` that crashes desloppify in `cohesion.py:52` → `extractors.py:90` with a `TypeError`. Upstream fix is [peteromallet/desloppify#605](https://github.com/peteromallet/desloppify/pull/605) open, unmerged. Until that ships, we cap locally. (See task `616b4bba-43de-40b5-af3b-4e219b622218` for the full repro.)
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 so Eze and the swarm can see it. Use the shared org (`648a5f3c-35c8-4f11-8673-b89de52cd6bd`) and namespace the path under your own agent ID:
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 648a5f3c-35c8-4f11-8673-b89de52cd6bd write \
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/desplega-ai/<repo>.git /tmp/repo
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 648a5f3c-35c8-4f11-8673-b89de52cd6bd write thoughts/$AGENT_ID/research/$(date +%F)-desloppify-<repo>-scan.md --content "..." -m "scan memo"
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/...` — *not* `desplega.atlassian.net` directly. 3LO bearer tokens only work via the `api.atlassian.com` proxy.
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
- ## Known constants (Desplega tenant)
11
+ ## Deployment constants
12
12
 
13
- - Site: `desplega.atlassian.net`
14
- - Cloud ID: `0054e739-8d39-4f01-8d6a-431619cae8fc`
15
- - Default project: `KAN` ("Swarm")
16
- - Scopes on the stored token: `manage:jira-webhook offline_access read:jira-work read:me write:jira-work`
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
- https://api.desplega.agent-swarm.dev/api/trackers/jira/authorize
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/KAN" \
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 = KAN AND statusCategory != Done' \
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": "KAN" },
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. `KAN-7`) is what humans use; URL is `https://desplega.atlassian.net/browse/<KEY>`.
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 in `KAN` (verify per-project): `Epic, Subtask, Task, Story, Feature, Request, Bug`.
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
- For project `KAN` (verified 2026-04-27), the workflow exposes ALL transitions from any state you do not need to walk through intermediate states:
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/KAN-3/transitions" \
139
- -d '{"transition":{"id":"51"}}'
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/KAN-3/comment" \
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=taras' \
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/KAN-3/assignee" \
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/KAN-3" \
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 `desplega.atlassian.net/rest/api/3/...` with a 3LO bearer token will fail.
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="0054e739-8d39-4f01-8d6a-431619cae8fc"
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 'jql=project = KAN AND statusCategory != Done' \
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 (id 51 in KAN)
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":"51"}}'
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 the WhatsApp platform vendor we use for the Meta Cloud API. The swarm has one phone number provisioned and either a native inbound handler (PR #560) or a workflow wired to dispatch a task per inbound message.
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
- PR #560 ships **thin MCP-tool wrappers for the common case only**:
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` | `1035039933036854` — our provisioned number's Meta 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
- The `kapso-whatsapp-inbound-demo` workflow (`a5700897-8f6b-4e1e-9e6b-dcc10e479cb5`) receives `whatsapp.message.*` and `whatsapp.conversation.*` events at `POST https://api.desplega.agent-swarm.dev/api/webhooks/<workflow-id>`.
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": "34679077777", // E.164 without +
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": "34679077777",
75
- "phone_number_id": "1035039933036854",
76
- "contact_name": "Taras",
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": "1035039933036854"
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 (Taras / Eze).
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=1035039933036854&status=active" | jq
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="34679077777"
187
- TEXT="Hi Taras 👋"
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/1035039933036854/messages" | jq
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` (no public-host requirement) validated 2026-05-20.
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/1035039933036854/media"
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 (validated 2026-05-20)
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/1035039933036854/messages"
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/1035039933036854/messages"
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": "34679077777",
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/1035039933036854/messages"
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=1035039933036854`.
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`). The native handler (`/api/integrations/kapso/webhook`, PR #560) and the workflow webhook trigger both verify automatically the trigger's `hmacHeader` is `X-Webhook-Signature` and `hmacSecret` resolves from swarm config.
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 Desplega agent Taras wired me up to handle WhatsApp."
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 (PR #560 adds the first, the original demo workflow remains):
347
+ Two common inbound paths exist:
348
348
 
349
- - **Native handler** (`/api/integrations/kapso/webhook`, PR #560) — fires for any phone number registered via `register-kapso-number`. Verifies HMAC, dedupes by message id (KV `integrations:kapso:dedupe`, 24h TTL), reads the routing mapping from KV (`integrations:kapso:numbers`), and either dispatches a `kapso-inbound` task or delegates to a workflow trigger (advanced override). Also emits a `kapso.message.received` event on the workflow event bus.
350
- - **Workflow `kapso-whatsapp-inbound-demo`** (`a5700897-8f6b-4e1e-9e6b-dcc10e479cb5`) — fires for unregistered numbers (or numbers whose mapping points at this workflow). Pipeline: `react-eyes` (mark read + typing + 👀) `debounce` (collapse rapid-fire bursts) `gate-proceed` `notify-taras` (agent-task triage) `finalize` (✅/❌ reaction).
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. `34679077777`). Add `+` when displaying to humans, drop it when calling the API.
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 (`1035039933036854`) is **our** number, not the recipient's. The recipient is in `message.from` / `conversation.phone_number`.
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 the user should use: https://api.desplega.agent-swarm.dev/api/trackers/linear/authorize (potentially needing to remove the app and re-auth). Only mention this if you can confirm it's expired or not present.
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
- - **Sprint cadence with direct-to-main commits (e.g. chat-py port):** Transition the Linear ticket to **Done** the moment the worker reports ship (commits on `main`). Do NOT wait for review, test-run, or merge there's no PR to wait for. Waiting causes HEARTBEAT/blocker-digest to flag RESOLVED-STALE tickets.
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` (added 2026-05-19)
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. The swarm's agent-activity sync was broken for ~weeks because they were confused; PRs #495 / #497 fixed it. Use this section whenever emitting `agentActivityCreate` mutations.
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`. See shared memory `linear-agent-activity-action-vs-thought-2026-05-18`.
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 for Desplega Labs team:**
124
- - Done: `83d3fcc6-dfeb-44fa-b719-64108ddc850d`
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. Sprint-cadence ports = transition on ship, not on merge.
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
-