tide-commander 1.94.0 → 1.96.0
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/dist/assets/{BossLogsModal-b4O2O00j.js → BossLogsModal-BFy6Rxdn.js} +1 -1
- package/dist/assets/{BossSpawnModal--Cn40tMQ.js → BossSpawnModal-KQyJYj_B.js} +1 -1
- package/dist/assets/{ControlsModal-SNJ95Qn9.js → ControlsModal-W_ghuG_Q.js} +1 -1
- package/dist/assets/{DockerLogsModal-DOkOsLaK.js → DockerLogsModal-valTcuRy.js} +1 -1
- package/dist/assets/{EmbeddedEditor-BKLJKK3B.js → EmbeddedEditor-CIsFWo6A.js} +1 -1
- package/dist/assets/{GmailOAuthSetup-B8ao6401.js → GmailOAuthSetup-AcW1zAlk.js} +1 -1
- package/dist/assets/{GoogleOAuthSetup-By-gup6S.js → GoogleOAuthSetup-C9uZ46Oe.js} +1 -1
- package/dist/assets/{IframeModal-KzrFyAG0.js → IframeModal-CmWv7GXl.js} +1 -1
- package/dist/assets/{IntegrationsPanel-CXMEldir.js → IntegrationsPanel-CNvzosgx.js} +2 -2
- package/dist/assets/{LogViewerModal-C70SfK7U.js → LogViewerModal-imTE-619.js} +1 -1
- package/dist/assets/{MonitoringModal-CbZtFYh9.js → MonitoringModal-DLxBY738.js} +1 -1
- package/dist/assets/{PM2LogsModal-C8mo6Tqu.js → PM2LogsModal-S4n0D11V.js} +1 -1
- package/dist/assets/{RestoreArchivedAreaModal-BUB-mruP.js → RestoreArchivedAreaModal-B32sqCxN.js} +1 -1
- package/dist/assets/{Scene2DCanvas-zinDgDAK.js → Scene2DCanvas-bEu0UPUm.js} +1 -1
- package/dist/assets/{SceneManager-CAxi1UHT.js → SceneManager-Ce3X0awy.js} +1 -1
- package/dist/assets/{SkillsPanel-Bh_J9suc.js → SkillsPanel-Dj22nDU8.js} +1 -1
- package/dist/assets/{SlackMultiInstanceSetup-DZSpM5ew.js → SlackMultiInstanceSetup-J9NdRY0U.js} +1 -1
- package/dist/assets/{SpawnModal-BrcKWjDs.js → SpawnModal-F9nspdWd.js} +1 -1
- package/dist/assets/{SubordinateAssignmentModal-CBqmszjc.js → SubordinateAssignmentModal-DT31CkPr.js} +1 -1
- package/dist/assets/TriggerManagerPanel-CVJ9ozHz.js +9 -0
- package/dist/assets/{WorkflowEditorPanel-DE9448gZ.js → WorkflowEditorPanel-BYRi0cU9.js} +1 -1
- package/dist/assets/{index-DuDcnBNX.js → index-B9G4hrFR.js} +1 -1
- package/dist/assets/{index-DPvgEcBT.js → index-BPy8NE5B.js} +3 -3
- package/dist/assets/{index-Cbk6B5BY.js → index-BmjrPptb.js} +1 -1
- package/dist/assets/{index-DelPL0HI.js → index-ByfAbkqv.js} +1 -1
- package/dist/assets/{index-B4VnKOHB.js → index-C5wC_Xis.js} +1 -1
- package/dist/assets/{index-Dk2THz0j.js → index-CLr8DpLX.js} +2 -2
- package/dist/assets/{index-BkZ422b_.js → index-DJm7oMXk.js} +1 -1
- package/dist/assets/{index-CpLZo6IM.js → index-DkjWoHhy.js} +1 -1
- package/dist/assets/{index-C7EieXis.js → index-DqZhHROd.js} +1 -1
- package/dist/assets/{main-CteALl_G.js → main-9uTEp9Lr.js} +4 -4
- package/dist/assets/{web-CGYGq80f.js → web-CLuvtqCm.js} +1 -1
- package/dist/assets/{web-BZa7rL0p.js → web-D-a3phls.js} +1 -1
- package/dist/assets/{web-DtevCxzm.js → web-DM2bFWBN.js} +1 -1
- package/dist/index.html +1 -1
- package/dist/src/packages/server/app.js +14 -0
- package/dist/src/packages/server/data/builtin-skills/bitbucket-pr-review.js +431 -0
- package/dist/src/packages/server/data/builtin-skills/bitbucket-reviewer.js +191 -0
- package/dist/src/packages/server/data/builtin-skills/create-building.js +521 -484
- package/dist/src/packages/server/data/builtin-skills/index.js +4 -0
- package/dist/src/packages/server/data/builtin-skills/trigger-designer.js +246 -71
- package/dist/src/packages/server/routes/bitbucket-author-loop.js +54 -0
- package/dist/src/packages/server/routes/buildings.js +298 -0
- package/dist/src/packages/server/routes/index.js +3 -1
- package/dist/src/packages/server/routes/trigger-routes.js +68 -15
- package/dist/src/packages/server/routes/webhook-dedupe.js +58 -0
- package/dist/src/packages/server/routes/webhook-signatures.js +67 -0
- package/dist/src/packages/server/services/building-service.js +400 -85
- package/dist/src/packages/server/services/trigger-service.js +5 -3
- package/dist/src/packages/server/websocket/handler.js +2 -4
- package/package.json +1 -1
- package/dist/assets/TriggerManagerPanel-BD7VnHYc.js +0 -9
|
@@ -11,6 +11,8 @@ import { gitCaptain } from './git-captain.js';
|
|
|
11
11
|
import { serverLogs } from './server-logs.js';
|
|
12
12
|
import { streamingExec } from './streaming-exec.js';
|
|
13
13
|
import { bitbucketPR } from './bitbucket-pr.js';
|
|
14
|
+
import { bitbucketPRReview } from './bitbucket-pr-review.js';
|
|
15
|
+
import { bitbucketReviewer } from './bitbucket-reviewer.js';
|
|
14
16
|
import { pm2Logs } from './pm2-logs.js';
|
|
15
17
|
import { createBuilding } from './create-building.js';
|
|
16
18
|
import { exploreDatabase } from './explore-database.js';
|
|
@@ -34,6 +36,8 @@ export const BUILTIN_SKILLS = [
|
|
|
34
36
|
serverLogs,
|
|
35
37
|
streamingExec,
|
|
36
38
|
bitbucketPR,
|
|
39
|
+
bitbucketPRReview,
|
|
40
|
+
bitbucketReviewer,
|
|
37
41
|
pm2Logs,
|
|
38
42
|
createBuilding,
|
|
39
43
|
exploreDatabase,
|
|
@@ -17,22 +17,23 @@ Triggers are event-driven rules that call agents with predefined templates.
|
|
|
17
17
|
|
|
18
18
|
A trigger has:
|
|
19
19
|
- **Name & Description**: What the trigger does
|
|
20
|
-
- **Trigger Type**: How the event is detected (webhook, cron, slack, email, jira,
|
|
21
|
-
- **
|
|
20
|
+
- **Trigger Type**: How the event is detected (webhook, cron, slack, email, jira, whatsapp, bitbucket)
|
|
21
|
+
- **Match Mode**: How to evaluate if an event matches (\`structural\`, \`llm\`, \`hybrid\`)
|
|
22
|
+
- **Extraction Mode** (optional): How to pull template variables out of the event (\`structural\` or \`llm\`)
|
|
22
23
|
- **Agent Assignment**: Which agent to fire
|
|
23
|
-
- **Template**: Prompt template
|
|
24
|
-
- **Rate Limiting**: Max fires per minute (default 10)
|
|
24
|
+
- **Template**: Prompt template sent to the agent, supports \`{{variable}}\` interpolation
|
|
25
25
|
|
|
26
26
|
## Trigger Types
|
|
27
27
|
|
|
28
28
|
| Type | When It Fires | Use Cases |
|
|
29
29
|
|---|---|---|
|
|
30
|
-
| \`webhook\` | When webhook
|
|
31
|
-
| \`cron\` | On a schedule (
|
|
32
|
-
| \`slack\` | When Slack message matches
|
|
33
|
-
| \`email\` | When
|
|
34
|
-
| \`jira\` | When Jira
|
|
35
|
-
| \`
|
|
30
|
+
| \`webhook\` | When \`POST /api/triggers/webhook/:triggerId\` receives a request | External systems push events; HMAC-validated; recommended path for GitHub/Bitbucket Cloud webhooks (see Bitbucket section below) |
|
|
31
|
+
| \`cron\` | On a schedule (recurring expression or one-shot \`runAt\`) | Periodic tasks, reports, maintenance, scheduled reminders |
|
|
32
|
+
| \`slack\` | When a Slack message matches the configured filters | Team notifications, approvals, command parsing |
|
|
33
|
+
| \`email\` | When a Gmail message matches the configured filters | Customer requests, alerts, approval workflows |
|
|
34
|
+
| \`jira\` | When a Jira webhook delivery matches | Issue escalation, automation, comment routing |
|
|
35
|
+
| \`whatsapp\` | When a WhatsApp message arrives matching the configured filters | Inbound customer messages, group monitoring |
|
|
36
|
+
| \`bitbucket\` | (Type exists in the type system but the webhook receiver currently only accepts \`type: 'webhook'\` — for Bitbucket Cloud, use a webhook trigger as shown below) | n/a — see Bitbucket section |
|
|
36
37
|
|
|
37
38
|
## List All Triggers
|
|
38
39
|
|
|
@@ -53,20 +54,26 @@ curl -s -X POST "http://localhost:{{PORT}}/api/triggers" \\
|
|
|
53
54
|
"type": "webhook",
|
|
54
55
|
"agentId": "AGENT_ID",
|
|
55
56
|
"matchMode": "structural",
|
|
56
|
-
"
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
57
|
+
"config": {
|
|
58
|
+
"method": "POST",
|
|
59
|
+
"secret": "OPTIONAL_HMAC_SECRET",
|
|
60
|
+
"extractFields": ["eventType", "data.user.id"]
|
|
61
|
+
},
|
|
62
|
+
"promptTemplate": "Process external event: {{eventType}} for user {{data.user.id}}\\n\\nFull payload: {{payload}}",
|
|
63
|
+
"enabled": true
|
|
61
64
|
}
|
|
62
65
|
EOF
|
|
63
66
|
\`\`\`
|
|
64
67
|
|
|
65
|
-
The webhook endpoint will be: \`http://localhost:{{PORT}}/api/webhook/
|
|
68
|
+
The webhook endpoint will be: \`http://localhost:{{PORT}}/api/triggers/webhook/TRIGGER_ID\`
|
|
69
|
+
|
|
70
|
+
POST JSON to this endpoint to fire the trigger. If \`config.secret\` is set, the receiver requires either:
|
|
71
|
+
- \`X-Hub-Signature-256\` (GitHub-style HMAC-SHA256) or \`X-Hub-Signature\` (Bitbucket-style), with the signature computed over the raw request body, OR
|
|
72
|
+
- \`X-Webhook-Secret\` header set to the literal secret value (plain comparison).
|
|
66
73
|
|
|
67
|
-
|
|
74
|
+
Each path listed in \`config.extractFields\` becomes a template variable named after the dotted path (e.g. \`{{data.user.id}}\`). The full payload is also available as \`{{payload}}\` (JSON-stringified).
|
|
68
75
|
|
|
69
|
-
## Create a Cron Trigger
|
|
76
|
+
## Create a Cron Trigger (recurring)
|
|
70
77
|
|
|
71
78
|
\`\`\`bash
|
|
72
79
|
curl -s -X POST "http://localhost:{{PORT}}/api/triggers" \\
|
|
@@ -79,13 +86,19 @@ curl -s -X POST "http://localhost:{{PORT}}/api/triggers" \\
|
|
|
79
86
|
"type": "cron",
|
|
80
87
|
"agentId": "AGENT_ID",
|
|
81
88
|
"matchMode": "structural",
|
|
82
|
-
"config": {
|
|
83
|
-
|
|
89
|
+
"config": {
|
|
90
|
+
"expression": "0 9 * * 1-5",
|
|
91
|
+
"timezone": "America/Mexico_City",
|
|
92
|
+
"payload": { "report": "daily-status" }
|
|
93
|
+
},
|
|
94
|
+
"promptTemplate": "Generate {{report}} report for {{cron.scheduledAt}}",
|
|
84
95
|
"enabled": true
|
|
85
96
|
}
|
|
86
97
|
EOF
|
|
87
98
|
\`\`\`
|
|
88
99
|
|
|
100
|
+
Keys in \`config.payload\` are merged into the template variables on every fire, in addition to the built-in \`{{cron.expression}}\` and \`{{cron.scheduledAt}}\`.
|
|
101
|
+
|
|
89
102
|
### Common Cron Expressions
|
|
90
103
|
|
|
91
104
|
| Expression | When It Runs |
|
|
@@ -96,6 +109,48 @@ EOF
|
|
|
96
109
|
| \`0 0 1 * *\` | First day of every month |
|
|
97
110
|
| \`*/15 * * * *\` | Every 15 minutes |
|
|
98
111
|
|
|
112
|
+
## Create a One-Shot Cron Trigger
|
|
113
|
+
|
|
114
|
+
For a single fire at a specific moment, omit \`expression\` and set \`runOnce: true\` + \`runAt\` (ISO 8601, treat as UTC):
|
|
115
|
+
|
|
116
|
+
\`\`\`bash
|
|
117
|
+
curl -s -X POST "http://localhost:{{PORT}}/api/triggers" \\
|
|
118
|
+
-H "Content-Type: application/json" \\
|
|
119
|
+
-H "X-Auth-Token: {{AUTH_TOKEN}}" \\
|
|
120
|
+
-d @- <<'EOF'
|
|
121
|
+
{
|
|
122
|
+
"name": "One-Shot Reminder",
|
|
123
|
+
"type": "cron",
|
|
124
|
+
"agentId": "AGENT_ID",
|
|
125
|
+
"matchMode": "structural",
|
|
126
|
+
"config": {
|
|
127
|
+
"expression": "",
|
|
128
|
+
"timezone": "UTC",
|
|
129
|
+
"runOnce": true,
|
|
130
|
+
"runAt": "2026-05-15T17:00:00Z",
|
|
131
|
+
"payload": { "topic": "Q2 review prep" }
|
|
132
|
+
},
|
|
133
|
+
"promptTemplate": "Remind me about {{topic}} (scheduled at {{cron.runAt}})",
|
|
134
|
+
"enabled": true
|
|
135
|
+
}
|
|
136
|
+
EOF
|
|
137
|
+
\`\`\`
|
|
138
|
+
|
|
139
|
+
After firing, the trigger is auto-disabled and \`config.completedAt\` is set. If the server was down at \`runAt\` past the grace window, the trigger is marked errored with \`config.missedAt\`.
|
|
140
|
+
|
|
141
|
+
## Validate a Cron Expression
|
|
142
|
+
|
|
143
|
+
Before saving, sanity-check the expression and preview the next fire times:
|
|
144
|
+
|
|
145
|
+
\`\`\`bash
|
|
146
|
+
curl -s -X POST "http://localhost:{{PORT}}/api/triggers/validate-cron" \\
|
|
147
|
+
-H "Content-Type: application/json" \\
|
|
148
|
+
-H "X-Auth-Token: {{AUTH_TOKEN}}" \\
|
|
149
|
+
-d '{ "expression": "0 9 * * 1-5", "timezone": "America/Mexico_City" }'
|
|
150
|
+
\`\`\`
|
|
151
|
+
|
|
152
|
+
Response: \`{ "valid": true, "nextFires": ["2026-05-11T15:00:00.000Z", ...] }\` or \`{ "valid": false, "error": "..." }\`.
|
|
153
|
+
|
|
99
154
|
## Create a Slack Trigger
|
|
100
155
|
|
|
101
156
|
\`\`\`bash
|
|
@@ -109,7 +164,7 @@ curl -s -X POST "http://localhost:{{PORT}}/api/triggers" \\
|
|
|
109
164
|
"type": "slack",
|
|
110
165
|
"agentId": "AGENT_ID",
|
|
111
166
|
"matchMode": "structural",
|
|
112
|
-
"config": { "channelId": "C0123456789" },
|
|
167
|
+
"config": { "channelId": "C0123456789", "messagePattern": "approve|deny" },
|
|
113
168
|
"promptTemplate": "Process approval request from @{{slack.user}}: {{slack.message}}",
|
|
114
169
|
"enabled": true
|
|
115
170
|
}
|
|
@@ -130,7 +185,8 @@ curl -s -X POST "http://localhost:{{PORT}}/api/triggers" \\
|
|
|
130
185
|
"agentId": "AGENT_ID",
|
|
131
186
|
"matchMode": "llm",
|
|
132
187
|
"config": { "subjectPattern": "urgent|critical" },
|
|
133
|
-
"
|
|
188
|
+
"llmMatch": { "prompt": "Is this a real customer escalation requiring same-day reply?" },
|
|
189
|
+
"promptTemplate": "Handle customer inquiry from {{email.from}}: {{email.subject}}\\n\\n{{email.body}}",
|
|
134
190
|
"enabled": true
|
|
135
191
|
}
|
|
136
192
|
EOF
|
|
@@ -149,20 +205,99 @@ curl -s -X POST "http://localhost:{{PORT}}/api/triggers" \\
|
|
|
149
205
|
"type": "jira",
|
|
150
206
|
"agentId": "AGENT_ID",
|
|
151
207
|
"matchMode": "structural",
|
|
152
|
-
"config": { "projectKey": "INCIDENT", "events": ["jira:issue_created"] },
|
|
153
|
-
"promptTemplate": "Respond to P1 incident {{
|
|
208
|
+
"config": { "projectKey": "INCIDENT", "events": ["jira:issue_created"], "jqlFilter": "priority = P1" },
|
|
209
|
+
"promptTemplate": "Respond to P1 incident {{jira.issueKey}}: {{jira.summary}}",
|
|
210
|
+
"enabled": true
|
|
211
|
+
}
|
|
212
|
+
EOF
|
|
213
|
+
\`\`\`
|
|
214
|
+
|
|
215
|
+
## Create a WhatsApp Trigger
|
|
216
|
+
|
|
217
|
+
\`\`\`bash
|
|
218
|
+
curl -s -X POST "http://localhost:{{PORT}}/api/triggers" \\
|
|
219
|
+
-H "Content-Type: application/json" \\
|
|
220
|
+
-H "X-Auth-Token: {{AUTH_TOKEN}}" \\
|
|
221
|
+
-d @- <<'EOF'
|
|
222
|
+
{
|
|
223
|
+
"name": "Inbound DM Handler",
|
|
224
|
+
"description": "Fire on inbound 1:1 DMs that mention 'order'",
|
|
225
|
+
"type": "whatsapp",
|
|
226
|
+
"agentId": "AGENT_ID",
|
|
227
|
+
"matchMode": "structural",
|
|
228
|
+
"config": { "direction": "inbound", "dmOnly": true, "bodyPattern": "order" },
|
|
229
|
+
"promptTemplate": "Reply to {{whatsapp.fromName}} ({{whatsapp.from}}): {{whatsapp.body}}",
|
|
230
|
+
"enabled": true
|
|
231
|
+
}
|
|
232
|
+
EOF
|
|
233
|
+
\`\`\`
|
|
234
|
+
|
|
235
|
+
## Bitbucket Cloud Webhooks
|
|
236
|
+
|
|
237
|
+
Use \`type: 'bitbucket'\` so the trigger gets the typed UX in the UI (workspace / repo-slug / event-key fields). The central webhook receiver at \`POST /api/triggers/webhook/:triggerId\` accepts both \`webhook\` and \`bitbucket\` types — its signature, dedupe, and author-loop helpers auto-detect the Bitbucket vs GitHub flavor from request headers, so the trigger type is purely a UI / metadata concern. The \`bitbucket-reviewer\` skill walks through the agent-side prompt; this skill just creates the trigger:
|
|
238
|
+
|
|
239
|
+
\`\`\`bash
|
|
240
|
+
curl -s -X POST "http://localhost:{{PORT}}/api/triggers" \\
|
|
241
|
+
-H "Content-Type: application/json" \\
|
|
242
|
+
-H "X-Auth-Token: {{AUTH_TOKEN}}" \\
|
|
243
|
+
-d @- <<'EOF'
|
|
244
|
+
{
|
|
245
|
+
"name": "Bitbucket PR Reviewer (wind)",
|
|
246
|
+
"description": "Review every PR opened or updated on tide/wind",
|
|
247
|
+
"type": "bitbucket",
|
|
248
|
+
"agentId": "AGENT_ID",
|
|
249
|
+
"matchMode": "structural",
|
|
250
|
+
"config": {
|
|
251
|
+
"method": "POST",
|
|
252
|
+
"secret": "GENERATE_A_RANDOM_HIGH_ENTROPY_STRING",
|
|
253
|
+
"extractFields": [
|
|
254
|
+
"pullrequest.id",
|
|
255
|
+
"pullrequest.title",
|
|
256
|
+
"repository.full_name",
|
|
257
|
+
"actor.nickname"
|
|
258
|
+
]
|
|
259
|
+
},
|
|
260
|
+
"promptTemplate": "Review pull request #{{pullrequest.id}} on {{repository.full_name}}: {{pullrequest.title}}",
|
|
154
261
|
"enabled": true
|
|
155
262
|
}
|
|
156
263
|
EOF
|
|
157
264
|
\`\`\`
|
|
158
265
|
|
|
266
|
+
After creation, paste the per-trigger URL \`http://<public-host>/api/triggers/webhook/TRIGGER_ID\` and the same \`secret\` into Bitbucket → Repository settings → Webhooks. The receiver verifies the \`X-Hub-Signature\` (Bitbucket's HMAC-SHA256 header), dedupes retries via \`X-Request-UUID\`, and respects an author-loop guard via the \`BITBUCKET_BOT_USERNAME\` env var. See \`docs/bitbucket-pr-review.md\` and the \`bitbucket-reviewer\` skill for the full setup.
|
|
267
|
+
|
|
159
268
|
## Matching Modes
|
|
160
269
|
|
|
161
270
|
| Mode | How It Works | Best For |
|
|
162
271
|
|---|---|---|
|
|
163
|
-
| \`structural\` |
|
|
164
|
-
| \`llm\` | LLM evaluates if event is relevant | Complex, semantic matching |
|
|
165
|
-
| \`hybrid\` | Structural
|
|
272
|
+
| \`structural\` | Per-handler field matching (regex/equality on the typed payload) | Predictable, structured events |
|
|
273
|
+
| \`llm\` | Haiku-by-default LLM evaluates if the event is relevant given \`llmMatch.prompt\` | Complex, semantic matching |
|
|
274
|
+
| \`hybrid\` | Structural pre-filter; if it passes, LLM verifies | Balance of speed and cost |
|
|
275
|
+
|
|
276
|
+
For \`llm\` and \`hybrid\`, also set:
|
|
277
|
+
|
|
278
|
+
\`\`\`json
|
|
279
|
+
"llmMatch": {
|
|
280
|
+
"prompt": "Natural-language condition the event must satisfy",
|
|
281
|
+
"model": "haiku",
|
|
282
|
+
"temperature": 0,
|
|
283
|
+
"maxTokens": 150,
|
|
284
|
+
"minConfidence": 0.6
|
|
285
|
+
}
|
|
286
|
+
\`\`\`
|
|
287
|
+
|
|
288
|
+
## Variable Extraction (LLM)
|
|
289
|
+
|
|
290
|
+
To pull free-form variables out of unstructured event payloads, set \`extractionMode: "llm"\` and \`llmExtract\`:
|
|
291
|
+
|
|
292
|
+
\`\`\`json
|
|
293
|
+
"extractionMode": "llm",
|
|
294
|
+
"llmExtract": {
|
|
295
|
+
"prompt": "Extract the customer's account ID and request type",
|
|
296
|
+
"variables": ["accountId", "requestType"]
|
|
297
|
+
}
|
|
298
|
+
\`\`\`
|
|
299
|
+
|
|
300
|
+
Variables returned by the extractor are merged on top of the structural variables before the prompt is interpolated.
|
|
166
301
|
|
|
167
302
|
## Get Trigger Details
|
|
168
303
|
|
|
@@ -186,92 +321,132 @@ curl -s -X DELETE "http://localhost:{{PORT}}/api/triggers/TRIGGER_ID" \\
|
|
|
186
321
|
-H "X-Auth-Token: {{AUTH_TOKEN}}"
|
|
187
322
|
\`\`\`
|
|
188
323
|
|
|
189
|
-
## Test a Trigger's
|
|
324
|
+
## Test a Trigger's Match Pipeline (dry run)
|
|
325
|
+
|
|
326
|
+
Send a sample payload through the trigger's full match + extract pipeline without firing the agent:
|
|
190
327
|
|
|
191
328
|
\`\`\`bash
|
|
192
329
|
curl -s -X POST "http://localhost:{{PORT}}/api/triggers/TRIGGER_ID/test-match" \\
|
|
193
330
|
-H "Content-Type: application/json" \\
|
|
194
331
|
-H "X-Auth-Token: {{AUTH_TOKEN}}" \\
|
|
195
332
|
-d '{
|
|
196
|
-
"
|
|
197
|
-
|
|
198
|
-
|
|
333
|
+
"payload": {
|
|
334
|
+
"messageText": "urgent: fix the database",
|
|
335
|
+
"sender": "alice@example.com"
|
|
336
|
+
}
|
|
199
337
|
}'
|
|
200
338
|
\`\`\`
|
|
201
339
|
|
|
202
|
-
|
|
340
|
+
You may also send a fully-formed event under the \`event\` key: \`{ "event": { "source": "slack", "type": "message", "data": { ... }, "timestamp": 1700000000000 } }\`.
|
|
341
|
+
|
|
342
|
+
Response shape (\`TestMatchResult\` from \`src/packages/shared/trigger-types.ts\`):
|
|
343
|
+
|
|
344
|
+
\`\`\`json
|
|
345
|
+
{
|
|
346
|
+
"structuralMatch": true,
|
|
347
|
+
"llmMatch": { "match": true, "confidence": 0.92, "reason": "...", "durationMs": 412, "model": "haiku", "tokensUsed": 84 },
|
|
348
|
+
"extractedVariables": { "slack.user": "alice", "slack.message": "..." },
|
|
349
|
+
"wouldFire": true,
|
|
350
|
+
"matcherExecutions": [ ... ]
|
|
351
|
+
}
|
|
352
|
+
\`\`\`
|
|
203
353
|
|
|
204
354
|
## Fire a Trigger Manually
|
|
205
355
|
|
|
356
|
+
Bypasses match logic; jumps straight to firing the agent with the supplied template variables:
|
|
357
|
+
|
|
206
358
|
\`\`\`bash
|
|
207
359
|
curl -s -X POST "http://localhost:{{PORT}}/api/triggers/TRIGGER_ID/fire" \\
|
|
208
360
|
-H "Content-Type: application/json" \\
|
|
209
361
|
-H "X-Auth-Token: {{AUTH_TOKEN}}" \\
|
|
210
362
|
-d '{
|
|
211
|
-
"
|
|
212
|
-
"
|
|
213
|
-
"requester": "alice@example.com",
|
|
214
|
-
"priority": "high"
|
|
215
|
-
}
|
|
363
|
+
"variables": { "requester": "alice@example.com", "priority": "high" },
|
|
364
|
+
"payload": { "raw": "optional raw payload, recorded with the fire" }
|
|
216
365
|
}'
|
|
217
366
|
\`\`\`
|
|
218
367
|
|
|
219
|
-
##
|
|
368
|
+
## Trigger Fire History
|
|
220
369
|
|
|
221
370
|
\`\`\`bash
|
|
222
|
-
curl -s -H "X-Auth-Token: {{AUTH_TOKEN}}" "http://localhost:{{PORT}}/api/triggers/TRIGGER_ID/
|
|
371
|
+
curl -s -H "X-Auth-Token: {{AUTH_TOKEN}}" "http://localhost:{{PORT}}/api/triggers/TRIGGER_ID/events?limit=10"
|
|
223
372
|
\`\`\`
|
|
224
373
|
|
|
225
|
-
Returns recent
|
|
374
|
+
Returns recent fire records (\`TriggerFireRow\` from \`src/packages/shared/trigger-types.ts\`) with status, variables, payload, LLM results, and duration.
|
|
226
375
|
|
|
227
|
-
##
|
|
376
|
+
## Matcher Diagnostics
|
|
228
377
|
|
|
229
|
-
|
|
378
|
+
When debugging why a trigger did or didn't fire:
|
|
230
379
|
|
|
231
|
-
|
|
232
|
-
|
|
233
|
-
-
|
|
234
|
-
|
|
235
|
-
-
|
|
236
|
-
-
|
|
380
|
+
\`\`\`bash
|
|
381
|
+
# All matchers that ran for one trigger event
|
|
382
|
+
curl -s -H "X-Auth-Token: {{AUTH_TOKEN}}" "http://localhost:{{PORT}}/api/triggers/events/EVENT_ID/matchers"
|
|
383
|
+
|
|
384
|
+
# Matcher-execution history for a trigger across all events
|
|
385
|
+
curl -s -H "X-Auth-Token: {{AUTH_TOKEN}}" "http://localhost:{{PORT}}/api/triggers/TRIGGER_ID/matcher-history?limit=100"
|
|
386
|
+
|
|
387
|
+
# Every matcher that evaluated against a specific source message (e.g. one Slack ts)
|
|
388
|
+
curl -s -H "X-Auth-Token: {{AUTH_TOKEN}}" "http://localhost:{{PORT}}/api/triggers/matchers/by-source/SOURCE_TYPE/SOURCE_ID"
|
|
389
|
+
\`\`\`
|
|
390
|
+
|
|
391
|
+
## Common Template Variables
|
|
392
|
+
|
|
393
|
+
Every fire receives \`{{trigger.name}}\` and \`{{timestamp}}\` (ISO string). The handler-specific variables below come from each integration's \`extractVariables\` implementation.
|
|
237
394
|
|
|
238
395
|
### Webhook triggers
|
|
239
|
-
- \`{{
|
|
240
|
-
- \`{{
|
|
396
|
+
- \`{{payload}}\` — full request body, JSON-stringified
|
|
397
|
+
- Each path in \`config.extractFields\` becomes a variable named after the dotted path (e.g. \`{{data.user.id}}\`)
|
|
398
|
+
|
|
399
|
+
### Cron triggers
|
|
400
|
+
- \`{{cron.expression}}\` — the cron expression (recurring jobs only)
|
|
401
|
+
- \`{{cron.scheduledAt}}\` — ISO timestamp when the fire was scheduled
|
|
402
|
+
- \`{{cron.runAt}}\` — the ISO \`runAt\` (one-shot only)
|
|
403
|
+
- Each key in \`config.payload\` is merged in as a top-level variable
|
|
404
|
+
|
|
405
|
+
### Slack triggers
|
|
406
|
+
- \`{{slack.user}}\`, \`{{slack.userId}}\`, \`{{slack.message}}\`, \`{{slack.channel}}\`, \`{{slack.threadTs}}\`
|
|
407
|
+
- \`{{slack.fileCount}}\`, \`{{slack.fileIds}}\`, \`{{slack.fileNames}}\`, \`{{slack.instanceId}}\`
|
|
241
408
|
|
|
242
409
|
### Email triggers
|
|
243
|
-
- \`{{
|
|
244
|
-
- \`{{
|
|
245
|
-
- \`{{
|
|
410
|
+
- \`{{email.from}}\`, \`{{email.to}}\`, \`{{email.subject}}\`, \`{{email.body}}\`
|
|
411
|
+
- \`{{email.threadId}}\`, \`{{email.messageId}}\`, \`{{email.date}}\`
|
|
412
|
+
- \`{{email.hasAttachments}}\`, \`{{email.attachments}}\`, \`{{email.direction}}\`, \`{{email.labels}}\`
|
|
246
413
|
|
|
247
414
|
### Jira triggers
|
|
248
|
-
- \`{{
|
|
249
|
-
- \`{{
|
|
250
|
-
- \`{{
|
|
415
|
+
- \`{{jira.issueKey}}\`, \`{{jira.issueId}}\`, \`{{jira.summary}}\`, \`{{jira.status}}\`
|
|
416
|
+
- \`{{jira.project}}\`, \`{{jira.projectName}}\`, \`{{jira.issueType}}\`, \`{{jira.priority}}\`
|
|
417
|
+
- \`{{jira.assignee}}\`, \`{{jira.reporter}}\`, \`{{jira.user}}\`, \`{{jira.eventType}}\`
|
|
418
|
+
- \`{{jira.labels}}\`, \`{{jira.changes}}\`, \`{{jira.commentAuthor}}\`, \`{{jira.commentBody}}\`
|
|
419
|
+
|
|
420
|
+
### WhatsApp triggers
|
|
421
|
+
- \`{{whatsapp.from}}\`, \`{{whatsapp.fromName}}\`, \`{{whatsapp.body}}\`
|
|
422
|
+
- \`{{whatsapp.sessionId}}\`, \`{{whatsapp.chatId}}\`, \`{{whatsapp.isGroup}}\`, \`{{whatsapp.groupName}}\`
|
|
423
|
+
- \`{{whatsapp.direction}}\`, \`{{whatsapp.mediaType}}\`, \`{{whatsapp.mediaUrl}}\`, \`{{whatsapp.timestamp}}\`
|
|
251
424
|
|
|
252
425
|
## Design Guidelines
|
|
253
426
|
|
|
254
427
|
1. **Names**: Use clear, action-oriented names ("Daily Report", "Slack Approval Processor")
|
|
255
428
|
2. **Prompts**: Be specific. Tell the agent what to do with the event data
|
|
256
|
-
3. **Matching**: Start with \`structural\` for simple patterns, use \`
|
|
257
|
-
4. **
|
|
258
|
-
5. **
|
|
259
|
-
6. **
|
|
260
|
-
7. **Error Handling**: Agent responses are logged in trigger history for debugging
|
|
429
|
+
3. **Matching**: Start with \`structural\` for simple patterns, use \`hybrid\` to gate LLM calls behind a cheap pre-filter
|
|
430
|
+
4. **Testing**: Always run \`/test-match\` before enabling a trigger that will fire on production traffic
|
|
431
|
+
5. **Template Variables**: Reference only variables that exist for that trigger type (see the section above)
|
|
432
|
+
6. **Debugging**: Use the matcher-diagnostics endpoints to see exactly which matcher passed/failed and why
|
|
261
433
|
|
|
262
434
|
## Rate Limiting
|
|
263
435
|
|
|
264
|
-
|
|
265
|
-
|
|
266
|
-
|
|
267
|
-
|
|
436
|
+
Each trigger is rate-limited to **10 fires per minute**. The cap is hardcoded in \`src/packages/server/services/trigger-service.ts\` (\`RATE_LIMIT_MAX\`) and is not configurable per-trigger via the API. Requests exceeding the cap are dropped silently after the limit is hit; check fire history if a trigger appears to "miss" events.
|
|
437
|
+
|
|
438
|
+
## Webhook Security
|
|
439
|
+
|
|
440
|
+
Webhook triggers can require one of two authentication mechanisms by setting \`config.secret\`:
|
|
441
|
+
|
|
442
|
+
| Mechanism | Header | Body input |
|
|
443
|
+
|---|---|---|
|
|
444
|
+
| HMAC-SHA256 (GitHub style) | \`X-Hub-Signature-256: sha256=<hex>\` | Raw request bytes |
|
|
445
|
+
| HMAC-SHA256 (Bitbucket style) | \`X-Hub-Signature: sha256=<hex>\` | Raw request bytes |
|
|
446
|
+
| Plain shared secret | \`X-Webhook-Secret: <secret>\` | Header value compared directly to \`config.secret\` |
|
|
268
447
|
|
|
269
|
-
|
|
448
|
+
When \`config.secret\` is set, requests with no signature are rejected with \`401 { "error": "Missing signature" }\`. The receiver also dedupes retries via \`X-Request-UUID\` (Bitbucket) or \`X-GitHub-Delivery\` (GitHub) within a 10-minute window.
|
|
270
449
|
|
|
271
|
-
|
|
272
|
-
1. Only expose to trusted systems
|
|
273
|
-
2. Use network-level access controls
|
|
274
|
-
3. Monitor trigger history for unusual activity
|
|
275
|
-
4. Disable triggers when not in use
|
|
450
|
+
If \`config.secret\` is unset, the endpoint accepts any POST — only do this for triggers behind a trusted network or dedicated path.
|
|
276
451
|
`,
|
|
277
452
|
};
|
|
@@ -0,0 +1,54 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Bitbucket author-loop guard.
|
|
3
|
+
*
|
|
4
|
+
* When the bot account itself comments / approves / requests-changes on a PR,
|
|
5
|
+
* Bitbucket fires another webhook with the bot as `actor`. Without this guard,
|
|
6
|
+
* the agent would react to its own activity in a loop.
|
|
7
|
+
*
|
|
8
|
+
* Scope: only events where the actor's identity is meaningfully separable
|
|
9
|
+
* from the PR author. `pullrequest:created` is excluded — the actor IS the PR
|
|
10
|
+
* author and we DO want to route on PR creation regardless of who opened it.
|
|
11
|
+
*
|
|
12
|
+
* Identity match: compares the configured `BITBUCKET_BOT_USERNAME` against
|
|
13
|
+
* three actor fields in the payload, returning true if any match. The env
|
|
14
|
+
* var name suggests `nickname` (the typical case), but UUID and account_id
|
|
15
|
+
* are also accepted for higher reliability — UUIDs/account_ids never change,
|
|
16
|
+
* whereas a nickname can be re-claimed if the bot is renamed.
|
|
17
|
+
* - `actor.nickname` (e.g. "review-bot") — most readable
|
|
18
|
+
* - `actor.uuid` (e.g. "{abc-1234-...}") — most reliable
|
|
19
|
+
* - `actor.account_id` (e.g. "557058:abcd-...") — also reliable
|
|
20
|
+
*/
|
|
21
|
+
const GUARDED_EVENT_KEYS = new Set([
|
|
22
|
+
'pullrequest:approved',
|
|
23
|
+
'pullrequest:unapproved',
|
|
24
|
+
'pullrequest:changes_request_created',
|
|
25
|
+
'pullrequest:changes_request_removed',
|
|
26
|
+
'pullrequest:comment_created',
|
|
27
|
+
'pullrequest:comment_updated',
|
|
28
|
+
'pullrequest:comment_deleted',
|
|
29
|
+
]);
|
|
30
|
+
/**
|
|
31
|
+
* Returns true when the event is a guarded action AND the actor in the
|
|
32
|
+
* payload matches the configured bot identifier. Caller should ACK 200 with
|
|
33
|
+
* `{ skipped: 'author-loop-guard' }` and skip routing.
|
|
34
|
+
*
|
|
35
|
+
* Returns false when:
|
|
36
|
+
* - `botIdentifier` is empty (guard disabled)
|
|
37
|
+
* - `eventKey` is not in the guarded set (e.g. pullrequest:created)
|
|
38
|
+
* - the payload's actor doesn't match any of the candidate fields
|
|
39
|
+
*/
|
|
40
|
+
export function isBitbucketAuthorLoop(eventKey, payload, botIdentifier) {
|
|
41
|
+
if (!botIdentifier || !eventKey)
|
|
42
|
+
return false;
|
|
43
|
+
if (!GUARDED_EVENT_KEYS.has(eventKey))
|
|
44
|
+
return false;
|
|
45
|
+
if (!payload || typeof payload !== 'object')
|
|
46
|
+
return false;
|
|
47
|
+
const actor = payload.actor;
|
|
48
|
+
if (!actor || typeof actor !== 'object')
|
|
49
|
+
return false;
|
|
50
|
+
const a = actor;
|
|
51
|
+
const candidates = [a.nickname, a.uuid, a.account_id]
|
|
52
|
+
.filter((v) => typeof v === 'string' && v.length > 0);
|
|
53
|
+
return candidates.includes(botIdentifier);
|
|
54
|
+
}
|