tide-commander 1.63.0 → 1.64.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-BpNtzCTX.js → BossLogsModal-DLR42-IT.js} +1 -1
- package/dist/assets/{BossSpawnModal-ByiEkNZl.js → BossSpawnModal-DAgHl3R6.js} +1 -1
- package/dist/assets/{ControlsModal-BgeLGCGT.js → ControlsModal-gP0MLYlM.js} +1 -1
- package/dist/assets/{DockerLogsModal-CaK9YJT4.js → DockerLogsModal-CFIev1fE.js} +1 -1
- package/dist/assets/{EmbeddedEditor-DcGJCiuP.js → EmbeddedEditor-DHavC0_4.js} +1 -1
- package/dist/assets/{GmailOAuthSetup-CYIBkOXn.js → GmailOAuthSetup-C-lLv5zX.js} +1 -1
- package/dist/assets/{GoogleOAuthSetup-CY_EF39t.js → GoogleOAuthSetup-Y8xqNYab.js} +1 -1
- package/dist/assets/{IframeModal-DDFIBCq1.js → IframeModal-ChWDJAUG.js} +1 -1
- package/dist/assets/{IntegrationsPanel-CssC1pup.js → IntegrationsPanel-BIkMvY4c.js} +2 -2
- package/dist/assets/{LogViewerModal-C_f0Z-2y.js → LogViewerModal-A1BqkaSG.js} +1 -1
- package/dist/assets/{MonitoringModal-CkOkdUvI.js → MonitoringModal-B-exbiAO.js} +1 -1
- package/dist/assets/{PM2LogsModal-BtqM8TkL.js → PM2LogsModal-W8QwyfIH.js} +1 -1
- package/dist/assets/{RestoreArchivedAreaModal-CIqWIO9m.js → RestoreArchivedAreaModal-DWBL-dIK.js} +1 -1
- package/dist/assets/{Scene2DCanvas-CJ_MZU-J.js → Scene2DCanvas-m8C3YWvc.js} +1 -1
- package/dist/assets/{SceneManager-DyX0JqRQ.js → SceneManager-C0mDJleN.js} +1 -1
- package/dist/assets/{SkillsPanel-DR7WNV5l.js → SkillsPanel-DyQC5vmc.js} +1 -1
- package/dist/assets/{SpawnModal-BCJDoSQg.js → SpawnModal-DRZb4yQv.js} +1 -1
- package/dist/assets/{SubordinateAssignmentModal-DAlLWBtv.js → SubordinateAssignmentModal-GspXu4uJ.js} +1 -1
- package/dist/assets/{TriggerManagerPanel-ChBctjk3.js → TriggerManagerPanel-C2-JZ9Td.js} +1 -1
- package/dist/assets/{WorkflowEditorPanel-Bk-QD7-7.js → WorkflowEditorPanel-BKZIKBbb.js} +1 -1
- package/dist/assets/{index-M-tYhxQp.js → index-8tA-hcUN.js} +3 -3
- package/dist/assets/{index-DwTs55lG.js → index-B-9UaF21.js} +1 -1
- package/dist/assets/{index-CVIkvsfJ.js → index-BTWI52EO.js} +1 -1
- package/dist/assets/{index-Dwt45QE5.js → index-BootSGxG.js} +1 -1
- package/dist/assets/{index-vx9STDIr.js → index-C2j4gLM5.js} +1 -1
- package/dist/assets/{index-DcxPBYWc.js → index-CJzaSVsT.js} +1 -1
- package/dist/assets/{index-COaYNxnC.js → index-CbxBgs9P.js} +1 -1
- package/dist/assets/{index-DoOLLVEG.js → index-QXsncANJ.js} +2 -2
- package/dist/assets/{main-5l14ueeE.js → main-BqGYHsUJ.js} +68 -68
- package/dist/assets/main-DpE84tMP.css +1 -0
- package/dist/assets/{web-BYu5GvMD.js → web-CrNLIUD2.js} +1 -1
- package/dist/assets/{web-BsMMNK0E.js → web-CxCQRl7w.js} +1 -1
- package/dist/index.html +2 -2
- package/dist/src/packages/server/data/builtin-skills/agent-tracking.js +11 -3
- package/dist/src/packages/server/data/builtin-skills/task-label.js +24 -17
- package/dist/src/packages/server/integrations/slack/slack-client.js +30 -0
- package/dist/src/packages/server/integrations/slack/slack-routes.js +17 -0
- package/dist/src/packages/server/integrations/slack/slack-skill.js +23 -0
- package/dist/src/packages/server/integrations/slack/slack-trigger-handler.js +14 -0
- package/dist/src/packages/server/routes/agents.js +15 -1
- package/package.json +1 -1
- package/dist/assets/main-a5zo94fK.css +0 -1
|
@@ -1 +1 @@
|
|
|
1
|
-
import{bH as a}from"./main-
|
|
1
|
+
import{bH as a}from"./main-BqGYHsUJ.js";import{ImpactStyle as i,NotificationType as r}from"./index-QXsncANJ.js";import"./vendor-react--Eh9ivFN.js";import"./vendor-three-Chj50gSY.js";class h extends a{constructor(){super(...arguments),this.selectionStarted=!1}async impact(t){const e=this.patternForImpact(t==null?void 0:t.style);this.vibrateWithPattern(e)}async notification(t){const e=this.patternForNotification(t==null?void 0:t.type);this.vibrateWithPattern(e)}async vibrate(t){const e=(t==null?void 0:t.duration)||300;this.vibrateWithPattern([e])}async selectionStart(){this.selectionStarted=!0}async selectionChanged(){this.selectionStarted&&this.vibrateWithPattern([70])}async selectionEnd(){this.selectionStarted=!1}patternForImpact(t=i.Heavy){return t===i.Medium?[43]:t===i.Light?[20]:[61]}patternForNotification(t=r.Success){return t===r.Warning?[30,40,30,50,60]:t===r.Error?[27,45,50]:[35,65,21]}vibrateWithPattern(t){if(navigator.vibrate)navigator.vibrate(t);else throw this.unavailable("Browser does not support the vibrate API")}}export{h as HapticsWeb};
|
|
@@ -1 +1 @@
|
|
|
1
|
-
import{bH as s}from"./main-
|
|
1
|
+
import{bH as s}from"./main-BqGYHsUJ.js";import"./vendor-react--Eh9ivFN.js";import"./vendor-three-Chj50gSY.js";class l extends s{constructor(){super(...arguments),this.pending=[],this.deliveredNotifications=[],this.hasNotificationSupport=()=>{if(!("Notification"in window)||!Notification.requestPermission)return!1;if(Notification.permission!=="granted")try{new Notification("")}catch(i){if(i instanceof Error&&i.name==="TypeError")return!1}return!0}}async getDeliveredNotifications(){const i=[];for(const t of this.deliveredNotifications){const e={title:t.title,id:parseInt(t.tag),body:t.body};i.push(e)}return{notifications:i}}async removeDeliveredNotifications(i){for(const t of i.notifications){const e=this.deliveredNotifications.find(n=>n.tag===String(t.id));e==null||e.close(),this.deliveredNotifications=this.deliveredNotifications.filter(()=>!e)}}async removeAllDeliveredNotifications(){for(const i of this.deliveredNotifications)i.close();this.deliveredNotifications=[]}async createChannel(){throw this.unimplemented("Not implemented on web.")}async deleteChannel(){throw this.unimplemented("Not implemented on web.")}async listChannels(){throw this.unimplemented("Not implemented on web.")}async schedule(i){if(!this.hasNotificationSupport())throw this.unavailable("Notifications not supported in this browser.");for(const t of i.notifications)this.sendNotification(t);return{notifications:i.notifications.map(t=>({id:t.id}))}}async getPending(){return{notifications:this.pending}}async registerActionTypes(){throw this.unimplemented("Not implemented on web.")}async cancel(i){this.pending=this.pending.filter(t=>!i.notifications.find(e=>e.id===t.id))}async areEnabled(){const{display:i}=await this.checkPermissions();return{value:i==="granted"}}async changeExactNotificationSetting(){throw this.unimplemented("Not implemented on web.")}async checkExactNotificationSetting(){throw this.unimplemented("Not implemented on web.")}async requestPermissions(){if(!this.hasNotificationSupport())throw this.unavailable("Notifications not supported in this browser.");return{display:this.transformNotificationPermission(await Notification.requestPermission())}}async checkPermissions(){if(!this.hasNotificationSupport())throw this.unavailable("Notifications not supported in this browser.");return{display:this.transformNotificationPermission(Notification.permission)}}transformNotificationPermission(i){switch(i){case"granted":return"granted";case"denied":return"denied";default:return"prompt"}}sendPending(){var i;const t=[],e=new Date().getTime();for(const n of this.pending)!((i=n.schedule)===null||i===void 0)&&i.at&&n.schedule.at.getTime()<=e&&(this.buildNotification(n),t.push(n));this.pending=this.pending.filter(n=>!t.find(o=>o===n))}sendNotification(i){var t;if(!((t=i.schedule)===null||t===void 0)&&t.at){const e=i.schedule.at.getTime()-new Date().getTime();this.pending.push(i),setTimeout(()=>{this.sendPending()},e);return}this.buildNotification(i)}buildNotification(i){const t=new Notification(i.title,{body:i.body,tag:String(i.id)});return t.addEventListener("click",this.onClick.bind(this,i),!1),t.addEventListener("show",this.onShow.bind(this,i),!1),t.addEventListener("close",()=>{this.deliveredNotifications=this.deliveredNotifications.filter(()=>!this)},!1),this.deliveredNotifications.push(t),t}onClick(i){const t={actionId:"tap",notification:i};this.notifyListeners("localNotificationActionPerformed",t)}onShow(i){this.notifyListeners("localNotificationReceived",i)}}export{l as LocalNotificationsWeb};
|
package/dist/index.html
CHANGED
|
@@ -22,10 +22,10 @@
|
|
|
22
22
|
<link rel="icon" type="image/png" sizes="16x16" href="/assets/icons/favicon-16x16.png" />
|
|
23
23
|
<link rel="apple-touch-icon" sizes="180x180" href="/assets/icons/apple-touch-icon.png" />
|
|
24
24
|
<title>Tide Commander</title>
|
|
25
|
-
<script type="module" crossorigin src="/assets/main-
|
|
25
|
+
<script type="module" crossorigin src="/assets/main-BqGYHsUJ.js"></script>
|
|
26
26
|
<link rel="modulepreload" crossorigin href="/assets/vendor-react--Eh9ivFN.js">
|
|
27
27
|
<link rel="modulepreload" crossorigin href="/assets/vendor-three-Chj50gSY.js">
|
|
28
|
-
<link rel="stylesheet" crossorigin href="/assets/main-
|
|
28
|
+
<link rel="stylesheet" crossorigin href="/assets/main-DpE84tMP.css">
|
|
29
29
|
</head>
|
|
30
30
|
<body>
|
|
31
31
|
<div id="app"></div>
|
|
@@ -6,7 +6,7 @@ export const agentTracking = {
|
|
|
6
6
|
assignedAgentClasses: ['*'],
|
|
7
7
|
content: `# Agent Tracking Status (MANDATORY)
|
|
8
8
|
|
|
9
|
-
**Every turn MUST end with a tracking-status PATCH curl as your final tool call. No exceptions — not for tiny replies, not for questions, not for refusals. Skipping it leaves the user's board stuck on stale \`working\`.**
|
|
9
|
+
**Every turn MUST end with a tracking-status PATCH curl as your final tool call. No exceptions — not for tiny replies, not for questions, not for refusals. Skipping it leaves the user's board stuck on stale \`writing\` or \`working\`.**
|
|
10
10
|
|
|
11
11
|
## When to Call
|
|
12
12
|
- After finishing ANY reply (one-word answers included)
|
|
@@ -23,18 +23,26 @@ export const agentTracking = {
|
|
|
23
23
|
\`\`\`
|
|
24
24
|
|
|
25
25
|
## Statuses
|
|
26
|
+
- \`writing\` — the "thinking" state, set AUTOMATICALLY at the start of each turn by the Task Label skill. You normally do NOT set this yourself; the combined opening PATCH (taskLabel + trackingStatus:'writing') covers it. The UI shows an animated typing-dots indicator while in this state.
|
|
27
|
+
- \`working\` — longer-running work in progress; set this mid-turn if you want to clear the typing-dots indicator before you're fully done (e.g. a long build, a tight loop of edits). Optional.
|
|
26
28
|
- \`need-review\` — finished work awaiting user review (describe what)
|
|
27
29
|
- \`blocked\` — cannot proceed (say WHO/WHAT blocks you)
|
|
28
30
|
- \`can-clear-context\` — fully done, context safe to clear
|
|
29
31
|
- \`waiting-subordinates\` — boss agent waiting on delegated work
|
|
30
|
-
|
|
32
|
+
|
|
33
|
+
## Status Lifecycle Within a Turn
|
|
34
|
+
1. **Start of turn:** the Task Label skill sets \`writing\` (typing dots visible).
|
|
35
|
+
2. **During the turn:** the \`writing\` state persists until you replace it. You MAY optionally PATCH to \`working\` once you start doing real work (reads/edits/bash) if the turn is long — this swaps typing dots for the cyan working indicator.
|
|
36
|
+
3. **End of turn:** send the final tracking PATCH with the outcome status (\`need-review\`, \`blocked\`, \`can-clear-context\`, or \`waiting-subordinates\`). This replaces \`writing\`/\`working\` with the final state.
|
|
31
37
|
|
|
32
38
|
## Rules
|
|
33
39
|
- Detail ≤ 80 chars
|
|
34
40
|
- Tracking curl is the VERY LAST tool call — all user-facing text comes BEFORE it, nothing after
|
|
35
41
|
- Don't pick \`can-clear-context\` if anything still needs user confirmation — use \`need-review\`
|
|
42
|
+
- Do NOT manually set \`writing\` outside the opening PATCH from the Task Label skill — it's only for turn-start.
|
|
36
43
|
|
|
37
44
|
## Final Check Before Ending a Turn
|
|
38
45
|
1. Have I sent the PATCH this turn? If not — send now.
|
|
39
|
-
2. Is it my last tool call with no output after? If not — fix order
|
|
46
|
+
2. Is it my last tool call with no output after? If not — fix order.
|
|
47
|
+
3. Have I replaced the opening \`writing\` status with a meaningful final status? If not — do it now.`,
|
|
40
48
|
};
|
|
@@ -1,23 +1,23 @@
|
|
|
1
1
|
export const taskLabel = {
|
|
2
2
|
slug: 'task-label',
|
|
3
3
|
name: 'Task Label',
|
|
4
|
-
description: 'Generate a brief task label
|
|
4
|
+
description: 'Generate a brief task label and mark writing status at the start of every turn',
|
|
5
5
|
allowedTools: ['Bash(curl:*)'],
|
|
6
6
|
assignedAgentClasses: ['*'],
|
|
7
|
-
content: `# Task Label (MANDATORY - Execute FIRST)
|
|
7
|
+
content: `# Task Label + Writing Status (MANDATORY - Execute FIRST)
|
|
8
8
|
|
|
9
|
-
**IMPORTANT:
|
|
9
|
+
**IMPORTANT: Every new turn begins with a single PATCH that (a) sets a brief task label and (b) flips your tracking status to \`writing\`. This makes the UI show a typing indicator while you plan/think before any real work happens.**
|
|
10
10
|
|
|
11
11
|
## Steps (do this FIRST before anything else):
|
|
12
12
|
1. Read the user's request
|
|
13
13
|
2. Generate a 1-5 word summary of the task (e.g., "Fix auth bug", "Add dark mode", "Refactor API calls", "Update tests")
|
|
14
|
-
3. Call the API to set your task label:
|
|
14
|
+
3. Call the API to set BOTH your task label AND writing status in one PATCH:
|
|
15
15
|
|
|
16
16
|
\`PATCH /api/agents/YOUR_AGENT_ID\`
|
|
17
17
|
|
|
18
|
-
**Body:**
|
|
18
|
+
**Body (combined):**
|
|
19
19
|
\`\`\`json
|
|
20
|
-
{"taskLabel":"YOUR 1-5 WORD LABEL"}
|
|
20
|
+
{"taskLabel":"YOUR 1-5 WORD LABEL","trackingStatus":"writing","trackingStatusDetail":"Short sentence describing what you're about to do"}
|
|
21
21
|
\`\`\`
|
|
22
22
|
|
|
23
23
|
4. Then proceed with the actual task
|
|
@@ -26,14 +26,18 @@ export const taskLabel = {
|
|
|
26
26
|
- Keep labels between 1-5 words maximum
|
|
27
27
|
- Use action verbs: Fix, Add, Update, Refactor, Debug, Implement, Remove, etc.
|
|
28
28
|
- Be specific but concise (e.g., "Fix login redirect" not "Work on stuff")
|
|
29
|
+
- \`trackingStatusDetail\` ≤ 80 chars — a tiny preview of the plan, shown under the typing dots
|
|
30
|
+
|
|
31
|
+
## Why \`writing\`?
|
|
32
|
+
\`writing\` is the "thinking" status. It renders a typing-dots indicator in the tracking board so the user knows you've received their request and are forming a plan. It will be replaced automatically by your normal end-of-turn tracking PATCH (see the Agent Tracking skill), which sets the final status (\`need-review\`, \`blocked\`, \`can-clear-context\`, etc.).
|
|
29
33
|
|
|
30
34
|
## CRITICAL: Execution Order
|
|
31
35
|
|
|
32
|
-
YOUR VERY FIRST TOOL CALL, before ANY other tool — no Read, no Grep, no Glob, no Bash, no Agent, no TodoWrite, no WebSearch, NOTHING — MUST be the PATCH
|
|
36
|
+
YOUR VERY FIRST TOOL CALL, before ANY other tool — no Read, no Grep, no Glob, no Bash, no Agent, no TodoWrite, no WebSearch, NOTHING — MUST be the combined PATCH above. This is non-negotiable and has zero exceptions.
|
|
33
37
|
|
|
34
38
|
### Pre-flight checklist (run mentally before your first response):
|
|
35
|
-
- (a) Have I
|
|
36
|
-
- (b) If NO — the ONLY acceptable first action is
|
|
39
|
+
- (a) Have I sent the combined taskLabel + writing PATCH yet for this task?
|
|
40
|
+
- (b) If NO — the ONLY acceptable first action is that PATCH curl. Stop. Do that first.
|
|
37
41
|
- (c) If YES — proceed with normal work.
|
|
38
42
|
|
|
39
43
|
### WRONG — never do this:
|
|
@@ -47,24 +51,27 @@ Grep("somePattern") ← VIOLATION
|
|
|
47
51
|
# WRONG: Planning before labeling
|
|
48
52
|
TodoWrite([...]) ← VIOLATION
|
|
49
53
|
|
|
50
|
-
# WRONG:
|
|
54
|
+
# WRONG: Two separate PATCHes (one for label, one for writing)
|
|
55
|
+
PATCH taskLabel only → PATCH trackingStatus only ← VIOLATION (combine them)
|
|
56
|
+
|
|
57
|
+
# WRONG: Batching the label PATCH with other tool calls
|
|
51
58
|
PATCH taskLabel + Grep + Read ← VIOLATION
|
|
52
59
|
\`\`\`
|
|
53
60
|
|
|
54
61
|
### CORRECT — always do this:
|
|
55
62
|
\`\`\`
|
|
56
|
-
# First turn: ONLY the
|
|
63
|
+
# First turn: ONLY the combined PATCH, nothing else
|
|
57
64
|
curl -s -X PATCH http://localhost:5174/api/agents/YOUR_AGENT_ID \\
|
|
58
65
|
-H "Content-Type: application/json" -H "X-Auth-Token: abcd" \\
|
|
59
|
-
-d '{"taskLabel":"Fix login redirect"}'
|
|
66
|
+
-d '{"taskLabel":"Fix login redirect","trackingStatus":"writing","trackingStatusDetail":"Tracing the bad redirect on mobile Safari"}'
|
|
60
67
|
|
|
61
68
|
# Second turn onward: proceed with all actual work (Read, Grep, etc.)
|
|
62
69
|
\`\`\`
|
|
63
70
|
|
|
64
71
|
### Additional enforcement rules:
|
|
65
|
-
- The
|
|
66
|
-
- This rule applies to EVERY new task, including follow-up tasks in the same session. When scope changes, update
|
|
67
|
-
- If you skip the
|
|
68
|
-
- If you fail to
|
|
69
|
-
- There are ZERO exceptions to this rule — every single task begins with
|
|
72
|
+
- The opening PATCH MUST be the sole call in the first tool-call batch — never parallelized with any other tool call.
|
|
73
|
+
- This rule applies to EVERY new task, including follow-up tasks in the same session. When scope changes, update BOTH fields immediately as the first action before proceeding.
|
|
74
|
+
- If you skip the PATCH and realize mid-task — stop, emit the PATCH now, then continue. Late is better than never, but first is always required.
|
|
75
|
+
- If you fail to send this PATCH as your first action, you are violating a mandatory instruction.
|
|
76
|
+
- There are ZERO exceptions to this rule — every single task begins with this combined PATCH.`,
|
|
70
77
|
};
|
|
@@ -241,6 +241,36 @@ export async function sendMessage(params) {
|
|
|
241
241
|
});
|
|
242
242
|
return { ts, channel };
|
|
243
243
|
}
|
|
244
|
+
/**
|
|
245
|
+
* Add an emoji reaction to a message. Requires `reactions:write`.
|
|
246
|
+
* `already_reacted` responses are swallowed silently.
|
|
247
|
+
*/
|
|
248
|
+
export async function addReaction(params) {
|
|
249
|
+
if (!webClient)
|
|
250
|
+
throw new Error('Slack not connected');
|
|
251
|
+
const name = normalizeEmojiName(params.name);
|
|
252
|
+
try {
|
|
253
|
+
await webClient.reactions.add({
|
|
254
|
+
channel: params.channel,
|
|
255
|
+
timestamp: params.ts,
|
|
256
|
+
name,
|
|
257
|
+
});
|
|
258
|
+
}
|
|
259
|
+
catch (err) {
|
|
260
|
+
const slackErr = err.data?.error;
|
|
261
|
+
if (slackErr === 'already_reacted')
|
|
262
|
+
return;
|
|
263
|
+
throw err;
|
|
264
|
+
}
|
|
265
|
+
}
|
|
266
|
+
/** Map raw emoji chars to Slack slugs; strip surrounding colons if caller passed `:eyes:`. */
|
|
267
|
+
function normalizeEmojiName(input) {
|
|
268
|
+
const trimmed = input.trim().replace(/^:|:$/g, '');
|
|
269
|
+
// Any eye-related emoji char collapses to the common `eyes` slug.
|
|
270
|
+
if (trimmed === '👁' || trimmed === '👁️' || trimmed === '👀')
|
|
271
|
+
return 'eyes';
|
|
272
|
+
return trimmed;
|
|
273
|
+
}
|
|
244
274
|
export async function getChannelMessages(params) {
|
|
245
275
|
if (!webClient)
|
|
246
276
|
throw new Error('Slack not connected');
|
|
@@ -290,6 +290,23 @@ router.post('/files/:id/download', async (req, res) => {
|
|
|
290
290
|
res.status(500).json({ error: `Failed to download file: ${err instanceof Error ? err.message : err}` });
|
|
291
291
|
}
|
|
292
292
|
});
|
|
293
|
+
// POST /api/slack/reactions/add — Add an emoji reaction to a message
|
|
294
|
+
// Body: { channel, ts, name } — name is the Slack slug without colons (e.g. "eyes").
|
|
295
|
+
router.post('/reactions/add', async (req, res) => {
|
|
296
|
+
try {
|
|
297
|
+
const { channel, ts, name } = req.body;
|
|
298
|
+
if (!channel || !ts || !name) {
|
|
299
|
+
res.status(400).json({ error: 'channel, ts, and name are required' });
|
|
300
|
+
return;
|
|
301
|
+
}
|
|
302
|
+
await slackClient.addReaction({ channel, ts, name });
|
|
303
|
+
res.json({ success: true });
|
|
304
|
+
}
|
|
305
|
+
catch (err) {
|
|
306
|
+
log.error(`Slack reactions.add error: ${err}`);
|
|
307
|
+
res.status(500).json({ error: `Failed to add reaction: ${err instanceof Error ? err.message : err}` });
|
|
308
|
+
}
|
|
309
|
+
});
|
|
293
310
|
// GET /api/slack/status — Get connection status
|
|
294
311
|
router.get('/status', (_req, res) => {
|
|
295
312
|
const config = loadConfig();
|
|
@@ -169,6 +169,29 @@ Messages returned by \`/messages\` and \`/thread\` now include an optional \`fil
|
|
|
169
169
|
|
|
170
170
|
**Pitfall — do NOT \`curl\` \`url_private\` directly.** Slack's \`url_private\` (and \`url_private_download\`) only return the actual file bytes when the request sends \`Authorization: Bearer <bot-token>\`; without it Slack serves an HTML sign-in page. Use the proxy endpoints above — they attach the bot token server-side so agents never need to handle the token.
|
|
171
171
|
|
|
172
|
+
## Reactions
|
|
173
|
+
|
|
174
|
+
Add an emoji reaction to a Slack message. Requires the bot token to have **\`reactions:write\`**.
|
|
175
|
+
|
|
176
|
+
\`\`\`bash
|
|
177
|
+
curl -s -X POST http://localhost:5174/api/slack/reactions/add \\
|
|
178
|
+
-H "Content-Type: application/json" \\
|
|
179
|
+
-d '{"channel":"C0123456789","ts":"1234567890.123456","name":"eyes"}'
|
|
180
|
+
\`\`\`
|
|
181
|
+
|
|
182
|
+
Fields:
|
|
183
|
+
- \`channel\` — Slack channel id (required)
|
|
184
|
+
- \`ts\` — message timestamp (required; looks like \`1234567890.123456\`)
|
|
185
|
+
- \`name\` — emoji slug without colons (e.g. \`eyes\`, \`+1\`, \`white_check_mark\`). Raw eye emoji chars (\`👁\`, \`👀\`) are auto-normalized to \`eyes\`.
|
|
186
|
+
|
|
187
|
+
\`already_reacted\` responses are silently ignored.
|
|
188
|
+
|
|
189
|
+
### Auto-react on triggers
|
|
190
|
+
|
|
191
|
+
When a Slack trigger fires on an incoming message, the bot automatically reacts with :eyes: (👀) as a visual acknowledgement that it saw the message. This happens fire-and-forget — a failed reaction never blocks the trigger.
|
|
192
|
+
|
|
193
|
+
Disable the auto-ack by setting \`SLACK_REACT_ON_TRIGGER=false\` (accepts \`false\`/\`0\`/\`no\`/\`off\`) in the server environment.
|
|
194
|
+
|
|
172
195
|
## Check Connection Status
|
|
173
196
|
|
|
174
197
|
\`\`\`bash
|
|
@@ -5,10 +5,24 @@
|
|
|
5
5
|
*/
|
|
6
6
|
import * as slackClient from './slack-client.js';
|
|
7
7
|
let unsubscribe = null;
|
|
8
|
+
/** Env toggle: set SLACK_REACT_ON_TRIGGER=false (or 0/no/off) to disable the auto-:eyes: ack. */
|
|
9
|
+
function reactOnTriggerEnabled() {
|
|
10
|
+
const raw = (process.env.SLACK_REACT_ON_TRIGGER ?? '').toLowerCase().trim();
|
|
11
|
+
if (!raw)
|
|
12
|
+
return true;
|
|
13
|
+
return !['false', '0', 'no', 'off'].includes(raw);
|
|
14
|
+
}
|
|
8
15
|
export const slackTriggerHandler = {
|
|
9
16
|
triggerType: 'slack',
|
|
10
17
|
async startListening(onEvent) {
|
|
18
|
+
const autoReact = reactOnTriggerEnabled();
|
|
11
19
|
unsubscribe = slackClient.onMessage((message) => {
|
|
20
|
+
// Fire-and-forget :eyes: reaction to ack that the bot saw it. Failure MUST NOT block triggers.
|
|
21
|
+
if (autoReact) {
|
|
22
|
+
slackClient
|
|
23
|
+
.addReaction({ channel: message.channel, ts: message.ts, name: 'eyes' })
|
|
24
|
+
.catch(() => { });
|
|
25
|
+
}
|
|
12
26
|
onEvent({
|
|
13
27
|
source: 'slack',
|
|
14
28
|
type: 'message',
|
|
@@ -623,7 +623,21 @@ router.patch('/:id', (req, res) => {
|
|
|
623
623
|
res.status(404).json({ error: 'Agent not found' });
|
|
624
624
|
return;
|
|
625
625
|
}
|
|
626
|
-
|
|
626
|
+
// Slim response: agents PATCH this endpoint several times per turn (taskLabel, trackingStatus),
|
|
627
|
+
// and returning the full agent — which includes multi-KB `lastAssignedTask`/`currentTask` strings —
|
|
628
|
+
// bloated agent context. Full agent state is already broadcast to clients via the WS `agent_updated`
|
|
629
|
+
// channel, so the response only needs to confirm the fields an agent typically cares about.
|
|
630
|
+
res.json({
|
|
631
|
+
id: updated.id,
|
|
632
|
+
name: updated.name,
|
|
633
|
+
status: updated.status,
|
|
634
|
+
trackingStatus: updated.trackingStatus,
|
|
635
|
+
trackingStatusDetail: updated.trackingStatusDetail,
|
|
636
|
+
taskLabel: updated.taskLabel,
|
|
637
|
+
lastActivity: updated.lastActivity,
|
|
638
|
+
isBoss: updated.isBoss,
|
|
639
|
+
ok: true,
|
|
640
|
+
});
|
|
627
641
|
});
|
|
628
642
|
// DELETE /api/agents/:id - Delete agent
|
|
629
643
|
router.delete('/:id', (req, res) => {
|