@spinabot/brigade 1.4.0 → 1.6.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/README.md +20 -1
- package/dist/agents/channels/bundled-channel-metas.d.ts +2 -0
- package/dist/agents/channels/bundled-channel-metas.d.ts.map +1 -1
- package/dist/agents/channels/bundled-channel-metas.js +11 -0
- package/dist/agents/channels/bundled-channel-metas.js.map +1 -1
- package/dist/agents/channels/manager.d.ts.map +1 -1
- package/dist/agents/channels/manager.js +18 -0
- package/dist/agents/channels/manager.js.map +1 -1
- package/dist/agents/channels/sdk.d.ts +2 -0
- package/dist/agents/channels/sdk.d.ts.map +1 -1
- package/dist/agents/channels/sdk.js +2 -0
- package/dist/agents/channels/sdk.js.map +1 -1
- package/dist/agents/channels/slack/account-config.d.ts +172 -0
- package/dist/agents/channels/slack/account-config.d.ts.map +1 -0
- package/dist/agents/channels/slack/account-config.js +353 -0
- package/dist/agents/channels/slack/account-config.js.map +1 -0
- package/dist/agents/channels/slack/account-registry.d.ts +45 -0
- package/dist/agents/channels/slack/account-registry.d.ts.map +1 -0
- package/dist/agents/channels/slack/account-registry.js +58 -0
- package/dist/agents/channels/slack/account-registry.js.map +1 -0
- package/dist/agents/channels/slack/adapter.d.ts +66 -0
- package/dist/agents/channels/slack/adapter.d.ts.map +1 -0
- package/dist/agents/channels/slack/adapter.js +547 -0
- package/dist/agents/channels/slack/adapter.js.map +1 -0
- package/dist/agents/channels/slack/approval-authorize.d.ts +43 -0
- package/dist/agents/channels/slack/approval-authorize.d.ts.map +1 -0
- package/dist/agents/channels/slack/approval-authorize.js +71 -0
- package/dist/agents/channels/slack/approval-authorize.js.map +1 -0
- package/dist/agents/channels/slack/approval-native.d.ts +70 -0
- package/dist/agents/channels/slack/approval-native.d.ts.map +1 -0
- package/dist/agents/channels/slack/approval-native.js +85 -0
- package/dist/agents/channels/slack/approval-native.js.map +1 -0
- package/dist/agents/channels/slack/blocks.d.ts +125 -0
- package/dist/agents/channels/slack/blocks.d.ts.map +1 -0
- package/dist/agents/channels/slack/blocks.js +145 -0
- package/dist/agents/channels/slack/blocks.js.map +1 -0
- package/dist/agents/channels/slack/command-menu.d.ts +44 -0
- package/dist/agents/channels/slack/command-menu.d.ts.map +1 -0
- package/dist/agents/channels/slack/command-menu.js +66 -0
- package/dist/agents/channels/slack/command-menu.js.map +1 -0
- package/dist/agents/channels/slack/connection.d.ts +422 -0
- package/dist/agents/channels/slack/connection.d.ts.map +1 -0
- package/dist/agents/channels/slack/connection.js +1042 -0
- package/dist/agents/channels/slack/connection.js.map +1 -0
- package/dist/agents/channels/slack/directory-live.d.ts +129 -0
- package/dist/agents/channels/slack/directory-live.d.ts.map +1 -0
- package/dist/agents/channels/slack/directory-live.js +148 -0
- package/dist/agents/channels/slack/directory-live.js.map +1 -0
- package/dist/agents/channels/slack/draft-stream.d.ts +93 -0
- package/dist/agents/channels/slack/draft-stream.d.ts.map +1 -0
- package/dist/agents/channels/slack/draft-stream.js +218 -0
- package/dist/agents/channels/slack/draft-stream.js.map +1 -0
- package/dist/agents/channels/slack/format.d.ts +41 -0
- package/dist/agents/channels/slack/format.d.ts.map +1 -0
- package/dist/agents/channels/slack/format.js +271 -0
- package/dist/agents/channels/slack/format.js.map +1 -0
- package/dist/agents/channels/slack/inbound-extras.d.ts +179 -0
- package/dist/agents/channels/slack/inbound-extras.d.ts.map +1 -0
- package/dist/agents/channels/slack/inbound-extras.js +257 -0
- package/dist/agents/channels/slack/inbound-extras.js.map +1 -0
- package/dist/agents/channels/slack/index.d.ts +15 -0
- package/dist/agents/channels/slack/index.d.ts.map +1 -0
- package/dist/agents/channels/slack/index.js +15 -0
- package/dist/agents/channels/slack/index.js.map +1 -0
- package/dist/agents/channels/slack/media.d.ts +90 -0
- package/dist/agents/channels/slack/media.d.ts.map +1 -0
- package/dist/agents/channels/slack/media.js +215 -0
- package/dist/agents/channels/slack/media.js.map +1 -0
- package/dist/agents/channels/slack/module.d.ts +26 -0
- package/dist/agents/channels/slack/module.d.ts.map +1 -0
- package/dist/agents/channels/slack/module.js +67 -0
- package/dist/agents/channels/slack/module.js.map +1 -0
- package/dist/agents/channels/slack/plugin.d.ts +69 -0
- package/dist/agents/channels/slack/plugin.d.ts.map +1 -0
- package/dist/agents/channels/slack/plugin.js +318 -0
- package/dist/agents/channels/slack/plugin.js.map +1 -0
- package/dist/agents/channels/slack/probe.d.ts +72 -0
- package/dist/agents/channels/slack/probe.d.ts.map +1 -0
- package/dist/agents/channels/slack/probe.js +103 -0
- package/dist/agents/channels/slack/probe.js.map +1 -0
- package/dist/agents/channels/slack/proxy-agent.d.ts +30 -0
- package/dist/agents/channels/slack/proxy-agent.d.ts.map +1 -0
- package/dist/agents/channels/slack/proxy-agent.js +44 -0
- package/dist/agents/channels/slack/proxy-agent.js.map +1 -0
- package/dist/agents/channels/slack/reasoning-lane.d.ts +42 -0
- package/dist/agents/channels/slack/reasoning-lane.d.ts.map +1 -0
- package/dist/agents/channels/slack/reasoning-lane.js +68 -0
- package/dist/agents/channels/slack/reasoning-lane.js.map +1 -0
- package/dist/agents/channels/slack/user-directory.d.ts +69 -0
- package/dist/agents/channels/slack/user-directory.d.ts.map +1 -0
- package/dist/agents/channels/slack/user-directory.js +94 -0
- package/dist/agents/channels/slack/user-directory.js.map +1 -0
- package/dist/agents/channels/slack/webhook.d.ts +89 -0
- package/dist/agents/channels/slack/webhook.d.ts.map +1 -0
- package/dist/agents/channels/slack/webhook.js +228 -0
- package/dist/agents/channels/slack/webhook.js.map +1 -0
- package/dist/agents/channels/telegram/adapter.d.ts.map +1 -1
- package/dist/agents/channels/telegram/adapter.js +10 -3
- package/dist/agents/channels/telegram/adapter.js.map +1 -1
- package/dist/agents/channels/telegram/connection.d.ts +10 -0
- package/dist/agents/channels/telegram/connection.d.ts.map +1 -1
- package/dist/agents/channels/telegram/connection.js +161 -5
- package/dist/agents/channels/telegram/connection.js.map +1 -1
- package/dist/agents/channels/telegram/format.d.ts +17 -0
- package/dist/agents/channels/telegram/format.d.ts.map +1 -1
- package/dist/agents/channels/telegram/format.js +53 -1
- package/dist/agents/channels/telegram/format.js.map +1 -1
- package/dist/agents/channels/telegram/inbound-extras.d.ts +17 -1
- package/dist/agents/channels/telegram/inbound-extras.d.ts.map +1 -1
- package/dist/agents/channels/telegram/inbound-extras.js +68 -7
- package/dist/agents/channels/telegram/inbound-extras.js.map +1 -1
- package/dist/agents/channels/telegram/media.d.ts +8 -0
- package/dist/agents/channels/telegram/media.d.ts.map +1 -1
- package/dist/agents/channels/telegram/media.js +30 -2
- package/dist/agents/channels/telegram/media.js.map +1 -1
- package/dist/agents/channels/telegram/webhook.d.ts.map +1 -1
- package/dist/agents/channels/telegram/webhook.js +7 -1
- package/dist/agents/channels/telegram/webhook.js.map +1 -1
- package/dist/agents/extensions/modules/index.d.ts.map +1 -1
- package/dist/agents/extensions/modules/index.js +5 -0
- package/dist/agents/extensions/modules/index.js.map +1 -1
- package/dist/agents/extensions/types.d.ts +11 -0
- package/dist/agents/extensions/types.d.ts.map +1 -1
- package/dist/agents/extensions/types.js.map +1 -1
- package/dist/buildstamp.json +1 -1
- package/dist/cli/commands/convex-cmd.d.ts +2 -1
- package/dist/cli/commands/convex-cmd.d.ts.map +1 -1
- package/dist/cli/commands/convex-cmd.js +79 -6
- package/dist/cli/commands/convex-cmd.js.map +1 -1
- package/dist/cli/program/build-program.js +1 -1
- package/dist/cli/program/build-program.js.map +1 -1
- package/dist/core/server.d.ts.map +1 -1
- package/dist/core/server.js +24 -5
- package/dist/core/server.js.map +1 -1
- package/package.json +4 -1
- package/scripts/convex-dev.mjs +28 -2
|
@@ -0,0 +1,218 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Slack live-streaming draft message.
|
|
3
|
+
*
|
|
4
|
+
* Brigade agents normally deliver one FINAL chunked reply (see
|
|
5
|
+
* `inbound-pipeline.dispatchTurn` → `adapter.sendText`). This module adds the
|
|
6
|
+
* OPTIONAL progressive-edit behavior: post a placeholder message on the first
|
|
7
|
+
* content, then `chat.update` it with the accumulating answer as tokens arrive —
|
|
8
|
+
* THROTTLED to roughly one edit per second so a long reply doesn't hammer the
|
|
9
|
+
* Web API. When the running text outgrows the per-message limit the stream
|
|
10
|
+
* FINALIZES the current message at its last safe chunk boundary and ROLLS to a
|
|
11
|
+
* fresh message for the overflow, so a multi-message reply streams naturally.
|
|
12
|
+
*
|
|
13
|
+
* Design notes (vs the simpler one-shot send):
|
|
14
|
+
* - Transport-agnostic + pure-ish: the two Slack calls (`postMessage`,
|
|
15
|
+
* `update`) are INJECTED, so the throttle / roll / finalize state machine is
|
|
16
|
+
* unit-tested with fakes and never imports `@slack/web-api`.
|
|
17
|
+
* - Throttle window: edits coalesce — `update(text)` only records the latest
|
|
18
|
+
* text; the loop flushes at most once per `throttleMs`. `flush()` forces an
|
|
19
|
+
* immediate delivery (used between throttle ticks and on finalize).
|
|
20
|
+
* - Char-limit roll: when an edit would exceed the limit, the current message
|
|
21
|
+
* is finalized to the largest chunk that fits (split on a paragraph /
|
|
22
|
+
* newline / space boundary) and the remainder starts a NEW message.
|
|
23
|
+
* - Render hook: `renderText` converts a markdown chunk to Slack mrkdwn (same
|
|
24
|
+
* `markdownToSlackMrkdwn` the final path uses). A render that throws or
|
|
25
|
+
* yields empty text falls back to the plain chunk so a half-streamed code
|
|
26
|
+
* fence never wedges the stream.
|
|
27
|
+
* - Idempotent finalize: `finalize(text)` is safe to call once at turn end —
|
|
28
|
+
* it flushes the final text and stops the loop. After finalize the stream is
|
|
29
|
+
* inert.
|
|
30
|
+
*
|
|
31
|
+
* This is the Slack-native analogue of `telegram/draft-stream.ts` — the same
|
|
32
|
+
* state machine, with Slack's string `ts` message ids and Slack's `mrkdwn` (no
|
|
33
|
+
* per-message parse mode; the adapter sends every chunk with `mrkdwn: true`).
|
|
34
|
+
*/
|
|
35
|
+
/** Slack's practical per-message body limit for a streamed reply (chars). */
|
|
36
|
+
export const SLACK_STREAM_MAX_CHARS = 8000;
|
|
37
|
+
/** Default minimum gap between edits (≈ 1 edit/sec, Web API friendly). */
|
|
38
|
+
export const DEFAULT_THROTTLE_MS = 1000;
|
|
39
|
+
/** Floor on the throttle so a misconfig can't spam the API. */
|
|
40
|
+
const MIN_THROTTLE_MS = 250;
|
|
41
|
+
/**
|
|
42
|
+
* Split `text` so the head fits within `limit` chars, preferring (in order) a
|
|
43
|
+
* paragraph break, a newline, then a space, falling back to a hard cut. Returns
|
|
44
|
+
* `[head, rest]`; `rest` is "" when the whole text fits.
|
|
45
|
+
*/
|
|
46
|
+
export function splitAtBoundary(text, limit) {
|
|
47
|
+
if (text.length <= limit)
|
|
48
|
+
return [text, ""];
|
|
49
|
+
const window = text.slice(0, limit);
|
|
50
|
+
const candidates = [window.lastIndexOf("\n\n"), window.lastIndexOf("\n"), window.lastIndexOf(" ")];
|
|
51
|
+
for (const idx of candidates) {
|
|
52
|
+
// Require the boundary to land past the half-way mark so we don't emit a
|
|
53
|
+
// tiny head and shove almost everything into the overflow message.
|
|
54
|
+
if (idx > limit * 0.5) {
|
|
55
|
+
return [text.slice(0, idx).trimEnd(), text.slice(idx).trimStart()];
|
|
56
|
+
}
|
|
57
|
+
}
|
|
58
|
+
return [text.slice(0, limit), text.slice(limit)];
|
|
59
|
+
}
|
|
60
|
+
export function createDraftStream(params) {
|
|
61
|
+
const throttleMs = Math.max(MIN_THROTTLE_MS, params.throttleMs ?? DEFAULT_THROTTLE_MS);
|
|
62
|
+
const maxChars = Math.min(SLACK_STREAM_MAX_CHARS, Math.max(1, params.maxChars ?? SLACK_STREAM_MAX_CHARS));
|
|
63
|
+
const render = params.renderText ?? ((t) => ({ text: t }));
|
|
64
|
+
const warn = params.warn ?? (() => { });
|
|
65
|
+
// Full accumulated answer text the agent has produced so far.
|
|
66
|
+
let pending = "";
|
|
67
|
+
// Text of the CURRENT (live) message that's already been delivered — used to
|
|
68
|
+
// skip no-op edits and to know how much of `pending` belongs to this message.
|
|
69
|
+
let liveDelivered = "";
|
|
70
|
+
// Char offset into `pending` where the current live message STARTS. Each roll
|
|
71
|
+
// advances this past the finalized head.
|
|
72
|
+
let baseOffset = 0;
|
|
73
|
+
const sentIds = [];
|
|
74
|
+
let liveId;
|
|
75
|
+
let done = false;
|
|
76
|
+
let inFlight = false;
|
|
77
|
+
let lastSentAt = 0;
|
|
78
|
+
let timer;
|
|
79
|
+
const clearTimer = () => {
|
|
80
|
+
if (timer) {
|
|
81
|
+
clearTimeout(timer);
|
|
82
|
+
timer = undefined;
|
|
83
|
+
}
|
|
84
|
+
};
|
|
85
|
+
/** The slice of `pending` that belongs to the current live message. */
|
|
86
|
+
const liveSlice = () => pending.slice(baseOffset);
|
|
87
|
+
/**
|
|
88
|
+
* Deliver the current live slice: post a placeholder on first content, edit
|
|
89
|
+
* thereafter, and roll to a new message when the slice exceeds `maxChars`.
|
|
90
|
+
* Re-entrancy guarded by `inFlight` so overlapping flushes serialize.
|
|
91
|
+
*/
|
|
92
|
+
const deliver = async (isFinal) => {
|
|
93
|
+
if (done && !isFinal)
|
|
94
|
+
return;
|
|
95
|
+
if (inFlight)
|
|
96
|
+
return;
|
|
97
|
+
if (!liveSlice().trim())
|
|
98
|
+
return;
|
|
99
|
+
inFlight = true;
|
|
100
|
+
try {
|
|
101
|
+
// Roll: finalize the current message at a boundary, start a new one for
|
|
102
|
+
// the overflow. Loop because a very long burst can overflow twice. We
|
|
103
|
+
// work against the UNTRIMMED live slice so `baseOffset` advances by the
|
|
104
|
+
// exact number of `pending` chars consumed (including the boundary
|
|
105
|
+
// whitespace `splitAtBoundary` drops), keeping the next message aligned.
|
|
106
|
+
let rawSlice = liveSlice();
|
|
107
|
+
while (rawSlice.trimEnd().length > maxChars) {
|
|
108
|
+
const [head] = splitAtBoundary(rawSlice.trimEnd(), maxChars);
|
|
109
|
+
await deliverOne(head);
|
|
110
|
+
// Advance past everything up to and including the boundary: find where
|
|
111
|
+
// the trimmed head ends in the raw slice, then skip following
|
|
112
|
+
// whitespace so the next message starts on real content.
|
|
113
|
+
const headEnd = rawSlice.indexOf(head) + head.length;
|
|
114
|
+
let advance = headEnd;
|
|
115
|
+
while (advance < rawSlice.length && /\s/.test(rawSlice[advance] ?? ""))
|
|
116
|
+
advance++;
|
|
117
|
+
baseOffset += advance;
|
|
118
|
+
liveDelivered = "";
|
|
119
|
+
liveId = undefined;
|
|
120
|
+
rawSlice = liveSlice();
|
|
121
|
+
}
|
|
122
|
+
const slice = rawSlice.trimEnd();
|
|
123
|
+
if (slice && (slice !== liveDelivered || isFinal)) {
|
|
124
|
+
await deliverOne(slice);
|
|
125
|
+
}
|
|
126
|
+
lastSentAt = Date.now();
|
|
127
|
+
}
|
|
128
|
+
finally {
|
|
129
|
+
inFlight = false;
|
|
130
|
+
}
|
|
131
|
+
};
|
|
132
|
+
/** Post-or-edit a single message body (≤ maxChars). */
|
|
133
|
+
const deliverOne = async (body) => {
|
|
134
|
+
let rendered;
|
|
135
|
+
try {
|
|
136
|
+
rendered = render(body);
|
|
137
|
+
if (!rendered.text.trim())
|
|
138
|
+
rendered = { text: body };
|
|
139
|
+
}
|
|
140
|
+
catch {
|
|
141
|
+
rendered = { text: body };
|
|
142
|
+
}
|
|
143
|
+
// A render can inflate length (mrkdwn entities); guard the hard cap.
|
|
144
|
+
if (rendered.text.length > SLACK_STREAM_MAX_CHARS) {
|
|
145
|
+
rendered = { text: body.slice(0, maxChars) };
|
|
146
|
+
}
|
|
147
|
+
try {
|
|
148
|
+
if (typeof liveId === "string") {
|
|
149
|
+
if (rendered.text === liveDelivered)
|
|
150
|
+
return;
|
|
151
|
+
await params.transport.updateMessage(liveId, rendered.text);
|
|
152
|
+
}
|
|
153
|
+
else {
|
|
154
|
+
const sent = await params.transport.postMessage(rendered.text, {
|
|
155
|
+
...(params.threadId !== undefined ? { threadId: params.threadId } : {}),
|
|
156
|
+
});
|
|
157
|
+
liveId = sent.ts;
|
|
158
|
+
sentIds.push(sent.ts);
|
|
159
|
+
}
|
|
160
|
+
liveDelivered = rendered.text;
|
|
161
|
+
}
|
|
162
|
+
catch (err) {
|
|
163
|
+
// A post / edit failure must never wedge the turn — the final-only
|
|
164
|
+
// fallback in the pipeline still delivers the complete reply.
|
|
165
|
+
warn(`slack stream deliver failed: ${err instanceof Error ? err.message : String(err)}`);
|
|
166
|
+
}
|
|
167
|
+
};
|
|
168
|
+
/** Schedule a throttled flush if one isn't already pending. */
|
|
169
|
+
const scheduleFlush = () => {
|
|
170
|
+
if (done || timer || inFlight)
|
|
171
|
+
return;
|
|
172
|
+
const elapsed = Date.now() - lastSentAt;
|
|
173
|
+
const wait = elapsed >= throttleMs ? 0 : throttleMs - elapsed;
|
|
174
|
+
timer = setTimeout(() => {
|
|
175
|
+
timer = undefined;
|
|
176
|
+
void deliver(false);
|
|
177
|
+
}, wait);
|
|
178
|
+
};
|
|
179
|
+
return {
|
|
180
|
+
update(fullText) {
|
|
181
|
+
if (done)
|
|
182
|
+
return;
|
|
183
|
+
if (typeof fullText !== "string")
|
|
184
|
+
return;
|
|
185
|
+
pending = fullText;
|
|
186
|
+
scheduleFlush();
|
|
187
|
+
},
|
|
188
|
+
async flush() {
|
|
189
|
+
if (done)
|
|
190
|
+
return;
|
|
191
|
+
clearTimer();
|
|
192
|
+
await deliver(false);
|
|
193
|
+
},
|
|
194
|
+
async finalize(fullText) {
|
|
195
|
+
if (done) {
|
|
196
|
+
return;
|
|
197
|
+
}
|
|
198
|
+
clearTimer();
|
|
199
|
+
if (typeof fullText === "string")
|
|
200
|
+
pending = fullText;
|
|
201
|
+
// Drain: deliver until the live slice is fully sent (handles a final
|
|
202
|
+
// burst that rolls into one or more new messages).
|
|
203
|
+
await deliver(true);
|
|
204
|
+
done = true;
|
|
205
|
+
},
|
|
206
|
+
stop() {
|
|
207
|
+
clearTimer();
|
|
208
|
+
done = true;
|
|
209
|
+
},
|
|
210
|
+
messageIds() {
|
|
211
|
+
return [...sentIds];
|
|
212
|
+
},
|
|
213
|
+
isDone() {
|
|
214
|
+
return done;
|
|
215
|
+
},
|
|
216
|
+
};
|
|
217
|
+
}
|
|
218
|
+
//# sourceMappingURL=draft-stream.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"draft-stream.js","sourceRoot":"","sources":["../../../../src/agents/channels/slack/draft-stream.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,41 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Convert agent-style markdown (Brigade agents output this by default) into
|
|
3
|
+
* Slack's `mrkdwn` message format.
|
|
4
|
+
*
|
|
5
|
+
* Slack's mrkdwn differs from CommonMark in load-bearing ways:
|
|
6
|
+
* - **bold** / __bold__ → *bold* (SINGLE asterisk in Slack)
|
|
7
|
+
* - *italic* / _italic_ → _italic_ (UNDERSCORE in Slack)
|
|
8
|
+
* - ~~strike~~ → ~strike~ (SINGLE tilde)
|
|
9
|
+
* - `code` → `code` (same; interior verbatim)
|
|
10
|
+
* - ```block``` → ```block``` (lang dropped; interior verbatim)
|
|
11
|
+
* - [label](url) → <url|label> (angle-bracket link)
|
|
12
|
+
* - # heading → *heading* (Slack has no headings)
|
|
13
|
+
* - `-`/`*`/`+` bullet → • bullet
|
|
14
|
+
* - > quote → > quote (Slack supports blockquote)
|
|
15
|
+
* - | pipe | tables | → flattened "cell | cell" lines
|
|
16
|
+
*
|
|
17
|
+
* `&`, `<`, `>` in TEXT must be escaped (`&` `<` `>`); the link token
|
|
18
|
+
* `<url|label>` uses literal `<` `>` and is emitted AFTER escaping the
|
|
19
|
+
* surrounding text, so it is never double-escaped. Inline code + fenced blocks
|
|
20
|
+
* keep their interior verbatim (escaped once, never re-scanned for emphasis).
|
|
21
|
+
*
|
|
22
|
+
* This is a Brigade-native re-implementation that models the SHAPE of
|
|
23
|
+
* `telegram/format.ts` (markdown in → channel-native formatting out) but emits
|
|
24
|
+
* mrkdwn instead of HTML. Pure / deterministic — no I/O, no globals.
|
|
25
|
+
*/
|
|
26
|
+
/** Escape the three characters Slack requires escaped in mrkdwn text nodes. */
|
|
27
|
+
export declare function escapeSlackMrkdwn(text: string): string;
|
|
28
|
+
/**
|
|
29
|
+
* Convert agent-style markdown into Slack mrkdwn. Block structure (fences,
|
|
30
|
+
* headings, bullets, blockquotes, tables) is handled line-by-line; inline
|
|
31
|
+
* markup is handled per-line by {@link renderInlineSpans}.
|
|
32
|
+
*/
|
|
33
|
+
export declare function markdownToSlackMrkdwn(markdown: string): string;
|
|
34
|
+
/**
|
|
35
|
+
* True when the rendered mrkdwn carries no visible content — only markers,
|
|
36
|
+
* whitespace, and/or empty tokens. Slack rejects an empty message body, so the
|
|
37
|
+
* send path falls back / skips when this returns true. Mirrors
|
|
38
|
+
* `telegramHtmlIsEmpty` intent.
|
|
39
|
+
*/
|
|
40
|
+
export declare function slackMrkdwnIsEmpty(mrkdwn: string): boolean;
|
|
41
|
+
//# sourceMappingURL=format.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"format.d.ts","sourceRoot":"","sources":["../../../../src/agents/channels/slack/format.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;GAwBG;AAEH,+EAA+E;AAC/E,wBAAgB,iBAAiB,CAAC,IAAI,EAAE,MAAM,GAAG,MAAM,CAEtD;AAyJD;;;;GAIG;AACH,wBAAgB,qBAAqB,CAAC,QAAQ,EAAE,MAAM,GAAG,MAAM,CAoE9D;AAED;;;;;GAKG;AACH,wBAAgB,kBAAkB,CAAC,MAAM,EAAE,MAAM,GAAG,OAAO,CAY1D"}
|
|
@@ -0,0 +1,271 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Convert agent-style markdown (Brigade agents output this by default) into
|
|
3
|
+
* Slack's `mrkdwn` message format.
|
|
4
|
+
*
|
|
5
|
+
* Slack's mrkdwn differs from CommonMark in load-bearing ways:
|
|
6
|
+
* - **bold** / __bold__ → *bold* (SINGLE asterisk in Slack)
|
|
7
|
+
* - *italic* / _italic_ → _italic_ (UNDERSCORE in Slack)
|
|
8
|
+
* - ~~strike~~ → ~strike~ (SINGLE tilde)
|
|
9
|
+
* - `code` → `code` (same; interior verbatim)
|
|
10
|
+
* - ```block``` → ```block``` (lang dropped; interior verbatim)
|
|
11
|
+
* - [label](url) → <url|label> (angle-bracket link)
|
|
12
|
+
* - # heading → *heading* (Slack has no headings)
|
|
13
|
+
* - `-`/`*`/`+` bullet → • bullet
|
|
14
|
+
* - > quote → > quote (Slack supports blockquote)
|
|
15
|
+
* - | pipe | tables | → flattened "cell | cell" lines
|
|
16
|
+
*
|
|
17
|
+
* `&`, `<`, `>` in TEXT must be escaped (`&` `<` `>`); the link token
|
|
18
|
+
* `<url|label>` uses literal `<` `>` and is emitted AFTER escaping the
|
|
19
|
+
* surrounding text, so it is never double-escaped. Inline code + fenced blocks
|
|
20
|
+
* keep their interior verbatim (escaped once, never re-scanned for emphasis).
|
|
21
|
+
*
|
|
22
|
+
* This is a Brigade-native re-implementation that models the SHAPE of
|
|
23
|
+
* `telegram/format.ts` (markdown in → channel-native formatting out) but emits
|
|
24
|
+
* mrkdwn instead of HTML. Pure / deterministic — no I/O, no globals.
|
|
25
|
+
*/
|
|
26
|
+
/** Escape the three characters Slack requires escaped in mrkdwn text nodes. */
|
|
27
|
+
export function escapeSlackMrkdwn(text) {
|
|
28
|
+
return text.replace(/&/g, "&").replace(/</g, "<").replace(/>/g, ">");
|
|
29
|
+
}
|
|
30
|
+
/** Try to match a `[label](url)` link starting at `start` (the `[`). */
|
|
31
|
+
function matchMarkdownLink(text, start) {
|
|
32
|
+
const labelEnd = text.indexOf("]", start + 1);
|
|
33
|
+
if (labelEnd === -1)
|
|
34
|
+
return null;
|
|
35
|
+
if (text[labelEnd + 1] !== "(")
|
|
36
|
+
return null;
|
|
37
|
+
// Balanced-paren scan from just after the opening `(` so a URL that itself
|
|
38
|
+
// contains parentheses (e.g. `…/Mercury_(planet)`) keeps its closing `)`. A
|
|
39
|
+
// plain `indexOf(")")` truncated at the FIRST `)`, dropping the rest of the url.
|
|
40
|
+
let depth = 1;
|
|
41
|
+
let urlEnd = -1;
|
|
42
|
+
for (let j = labelEnd + 2; j < text.length; j++) {
|
|
43
|
+
const c = text[j];
|
|
44
|
+
if (c === "(")
|
|
45
|
+
depth += 1;
|
|
46
|
+
else if (c === ")") {
|
|
47
|
+
depth -= 1;
|
|
48
|
+
if (depth === 0) {
|
|
49
|
+
urlEnd = j;
|
|
50
|
+
break;
|
|
51
|
+
}
|
|
52
|
+
}
|
|
53
|
+
}
|
|
54
|
+
if (urlEnd === -1)
|
|
55
|
+
return null;
|
|
56
|
+
const label = text.slice(start + 1, labelEnd);
|
|
57
|
+
const url = text.slice(labelEnd + 2, urlEnd).trim();
|
|
58
|
+
// Only honour http/https/mailto/tel/slack links — anything else stays literal
|
|
59
|
+
// so we never emit a link token Slack will mangle (and never linkify a path).
|
|
60
|
+
if (!/^(https?:\/\/|mailto:|tel:|slack:\/\/)/i.test(url))
|
|
61
|
+
return null;
|
|
62
|
+
if (!label)
|
|
63
|
+
return null;
|
|
64
|
+
return { label, url, end: urlEnd + 1 };
|
|
65
|
+
}
|
|
66
|
+
/**
|
|
67
|
+
* Apply emphasis markers to a plain text run and entity-escape everything else,
|
|
68
|
+
* emitting Slack mrkdwn markers. Markdown bold (`**`/`__`) becomes Slack `*`;
|
|
69
|
+
* markdown italic (`*`/`_`) becomes Slack `_`. Bold is processed BEFORE italic
|
|
70
|
+
* so `**x**` is not eaten by the single-asterisk italic rule.
|
|
71
|
+
*/
|
|
72
|
+
function renderEmphasis(text) {
|
|
73
|
+
// Slack has no bold-italic marker. Pre-collapse the CommonMark triple form
|
|
74
|
+
// (`***x***` / `___x___`) to markdown BOLD (`**x**`) before the rule loop, so
|
|
75
|
+
// the bold rule below claims it and emits Slack bold (`*x*`). Without this the
|
|
76
|
+
// bold rule would eat only the outer `**` and strand a stray `*` that mangles
|
|
77
|
+
// the output. Idempotent over recursion.
|
|
78
|
+
text = text
|
|
79
|
+
.replace(/\*\*\*([^*\n]+?)\*\*\*/g, "**$1**")
|
|
80
|
+
.replace(/(?<![A-Za-z0-9_])___([^_\n]+?)___(?![A-Za-z0-9_])/g, "**$1**");
|
|
81
|
+
const rules = [
|
|
82
|
+
{ re: /\*\*([^*]+?)\*\*/, open: "*", close: "*" }, // **bold** → *bold*
|
|
83
|
+
{ re: /__([^_]+?)__/, open: "*", close: "*" }, // __bold__ → *bold*
|
|
84
|
+
{ re: /~~([^~]+?)~~/, open: "~", close: "~" }, // ~~strike~~ → ~strike~
|
|
85
|
+
// *italic* → _italic_ — flanking-guarded so a whitespace-flanked `*`
|
|
86
|
+
// (a glob `*.ts` / arithmetic `2 * 3`) is NOT treated as an italic marker.
|
|
87
|
+
// The marker must hug its content (no inner space) and not sit between
|
|
88
|
+
// alphanumerics (which would be a mid-word `*`).
|
|
89
|
+
{ re: /(?<![A-Za-z0-9])\*(?!\s)([^*\n]+?)(?<!\s)\*(?![A-Za-z0-9])/, open: "_", close: "_" },
|
|
90
|
+
{ re: /(?<![A-Za-z0-9_])_([^_\n]+?)_(?![A-Za-z0-9_])/, open: "_", close: "_" }, // _italic_ → _italic_
|
|
91
|
+
];
|
|
92
|
+
for (const rule of rules) {
|
|
93
|
+
const m = rule.re.exec(text);
|
|
94
|
+
if (m && m.index >= 0) {
|
|
95
|
+
const before = text.slice(0, m.index);
|
|
96
|
+
const inner = m[1] ?? "";
|
|
97
|
+
const after = text.slice(m.index + m[0].length);
|
|
98
|
+
return escapeSlackMrkdwn(before) + rule.open + renderEmphasis(inner) + rule.close + renderEmphasis(after);
|
|
99
|
+
}
|
|
100
|
+
}
|
|
101
|
+
return escapeSlackMrkdwn(text);
|
|
102
|
+
}
|
|
103
|
+
/**
|
|
104
|
+
* Render the INLINE span markup of one already-newline-free run into Slack
|
|
105
|
+
* mrkdwn. Inline code is extracted first (verbatim interior), then links, then
|
|
106
|
+
* emphasis. Plain text between spans is entity-escaped exactly once.
|
|
107
|
+
*/
|
|
108
|
+
function renderInlineSpans(text) {
|
|
109
|
+
const out = [];
|
|
110
|
+
let i = 0;
|
|
111
|
+
const n = text.length;
|
|
112
|
+
let plain = "";
|
|
113
|
+
const flushPlain = () => {
|
|
114
|
+
if (plain) {
|
|
115
|
+
out.push(renderEmphasis(plain));
|
|
116
|
+
plain = "";
|
|
117
|
+
}
|
|
118
|
+
};
|
|
119
|
+
while (i < n) {
|
|
120
|
+
const ch = text[i];
|
|
121
|
+
// Inline code: `…` — interior is verbatim (escaped, no emphasis).
|
|
122
|
+
if (ch === "`") {
|
|
123
|
+
const close = text.indexOf("`", i + 1);
|
|
124
|
+
if (close !== -1) {
|
|
125
|
+
flushPlain();
|
|
126
|
+
out.push(`\`${escapeSlackMrkdwn(text.slice(i + 1, close))}\``);
|
|
127
|
+
i = close + 1;
|
|
128
|
+
continue;
|
|
129
|
+
}
|
|
130
|
+
}
|
|
131
|
+
// Markdown link: [label](url) → <url|label>
|
|
132
|
+
if (ch === "[") {
|
|
133
|
+
const link = matchMarkdownLink(text, i);
|
|
134
|
+
if (link) {
|
|
135
|
+
flushPlain();
|
|
136
|
+
out.push(`<${link.url}|${escapeSlackMrkdwn(link.label)}>`);
|
|
137
|
+
i = link.end;
|
|
138
|
+
continue;
|
|
139
|
+
}
|
|
140
|
+
}
|
|
141
|
+
// Pre-formed Slack token typed by the agent: a mention `<@U…>`, channel
|
|
142
|
+
// `<#C…|label>`, special `<!here>` / `<!subteam^S|label>`, or a bare
|
|
143
|
+
// `<url|label>`. Pass it through VERBATIM so the mention actually pings and
|
|
144
|
+
// the link actually links — entity-escaping the `<` would neutralize it.
|
|
145
|
+
// Only inner forms Slack recognises pass; any other `<…>` (e.g. `a < b`)
|
|
146
|
+
// stays plain text and is escaped as before.
|
|
147
|
+
if (ch === "<") {
|
|
148
|
+
const close = text.indexOf(">", i + 1);
|
|
149
|
+
if (close > i) {
|
|
150
|
+
const inner = text.slice(i + 1, close);
|
|
151
|
+
if (/^(?:[@#!]|https?:|mailto:|tel:|slack:)/.test(inner)) {
|
|
152
|
+
flushPlain();
|
|
153
|
+
out.push(`<${inner}>`);
|
|
154
|
+
i = close + 1;
|
|
155
|
+
continue;
|
|
156
|
+
}
|
|
157
|
+
}
|
|
158
|
+
}
|
|
159
|
+
plain += ch;
|
|
160
|
+
i += 1;
|
|
161
|
+
}
|
|
162
|
+
flushPlain();
|
|
163
|
+
return out.join("");
|
|
164
|
+
}
|
|
165
|
+
/** Render a markdown pipe-table block as flat "cell | cell" mrkdwn lines. */
|
|
166
|
+
function renderTableBlock(block) {
|
|
167
|
+
const rows = [];
|
|
168
|
+
for (const row of block) {
|
|
169
|
+
// Drop the separator row (`| --- | :--: |`).
|
|
170
|
+
if (/^\s*\|?[\s|:-]+\|?\s*$/.test(row))
|
|
171
|
+
continue;
|
|
172
|
+
const cells = row
|
|
173
|
+
.trim()
|
|
174
|
+
.replace(/^\||\|$/g, "")
|
|
175
|
+
.split("|")
|
|
176
|
+
.map((c) => c.trim())
|
|
177
|
+
.filter(Boolean);
|
|
178
|
+
if (cells.length)
|
|
179
|
+
rows.push(cells.map((c) => renderInlineSpans(c)).join(" | "));
|
|
180
|
+
}
|
|
181
|
+
return rows.join("\n");
|
|
182
|
+
}
|
|
183
|
+
/**
|
|
184
|
+
* Convert agent-style markdown into Slack mrkdwn. Block structure (fences,
|
|
185
|
+
* headings, bullets, blockquotes, tables) is handled line-by-line; inline
|
|
186
|
+
* markup is handled per-line by {@link renderInlineSpans}.
|
|
187
|
+
*/
|
|
188
|
+
export function markdownToSlackMrkdwn(markdown) {
|
|
189
|
+
if (!markdown)
|
|
190
|
+
return "";
|
|
191
|
+
const lines = markdown.split("\n");
|
|
192
|
+
const out = [];
|
|
193
|
+
let i = 0;
|
|
194
|
+
const n = lines.length;
|
|
195
|
+
while (i < n) {
|
|
196
|
+
const line = lines[i] ?? "";
|
|
197
|
+
// Fenced code block: ```lang … ``` → ``` … ``` (lang dropped, interior escaped).
|
|
198
|
+
const fenceOpen = /^\s*```(.*)$/.exec(line);
|
|
199
|
+
if (fenceOpen) {
|
|
200
|
+
const body = [];
|
|
201
|
+
i += 1;
|
|
202
|
+
while (i < n) {
|
|
203
|
+
const inner = lines[i] ?? "";
|
|
204
|
+
if (/^\s*```\s*$/.test(inner)) {
|
|
205
|
+
i += 1;
|
|
206
|
+
break;
|
|
207
|
+
}
|
|
208
|
+
body.push(inner);
|
|
209
|
+
i += 1;
|
|
210
|
+
}
|
|
211
|
+
out.push("```\n" + escapeSlackMrkdwn(body.join("\n")) + "\n```");
|
|
212
|
+
continue;
|
|
213
|
+
}
|
|
214
|
+
// Pipe-table block: ≥2 contiguous lines that start+end with `|`.
|
|
215
|
+
if (/^\s*\|.*\|\s*$/.test(line)) {
|
|
216
|
+
const start = i;
|
|
217
|
+
while (i < n && /^\s*\|.*\|\s*$/.test(lines[i] ?? ""))
|
|
218
|
+
i += 1;
|
|
219
|
+
if (i - start >= 2) {
|
|
220
|
+
out.push(renderTableBlock(lines.slice(start, i)));
|
|
221
|
+
continue;
|
|
222
|
+
}
|
|
223
|
+
i = start;
|
|
224
|
+
}
|
|
225
|
+
// ATX heading: # … → bold line (Slack mrkdwn has no headings).
|
|
226
|
+
const heading = /^(#{1,6})\s+(.+?)\s*$/.exec(line);
|
|
227
|
+
if (heading) {
|
|
228
|
+
out.push(`*${renderInlineSpans(heading[2] ?? "")}*`);
|
|
229
|
+
i += 1;
|
|
230
|
+
continue;
|
|
231
|
+
}
|
|
232
|
+
// Blockquote: a leading `>` → Slack blockquote line.
|
|
233
|
+
if (/^\s*>\s?/.test(line)) {
|
|
234
|
+
out.push(`> ${renderInlineSpans(line.replace(/^\s*>\s?/, ""))}`);
|
|
235
|
+
i += 1;
|
|
236
|
+
continue;
|
|
237
|
+
}
|
|
238
|
+
// Bullet list item: -, *, + → "• ".
|
|
239
|
+
const bullet = /^(\s*)[-*+]\s+(.*)$/.exec(line);
|
|
240
|
+
if (bullet) {
|
|
241
|
+
out.push(`${bullet[1] ?? ""}• ${renderInlineSpans(bullet[2] ?? "")}`);
|
|
242
|
+
i += 1;
|
|
243
|
+
continue;
|
|
244
|
+
}
|
|
245
|
+
// Plain line — render inline markup + escape.
|
|
246
|
+
out.push(renderInlineSpans(line));
|
|
247
|
+
i += 1;
|
|
248
|
+
}
|
|
249
|
+
return out.join("\n");
|
|
250
|
+
}
|
|
251
|
+
/**
|
|
252
|
+
* True when the rendered mrkdwn carries no visible content — only markers,
|
|
253
|
+
* whitespace, and/or empty tokens. Slack rejects an empty message body, so the
|
|
254
|
+
* send path falls back / skips when this returns true. Mirrors
|
|
255
|
+
* `telegramHtmlIsEmpty` intent.
|
|
256
|
+
*/
|
|
257
|
+
export function slackMrkdwnIsEmpty(mrkdwn) {
|
|
258
|
+
if (!mrkdwn)
|
|
259
|
+
return true;
|
|
260
|
+
const decoded = mrkdwn
|
|
261
|
+
.replace(/&/g, "&")
|
|
262
|
+
.replace(/</g, "<")
|
|
263
|
+
.replace(/>/g, ">");
|
|
264
|
+
const stripped = decoded
|
|
265
|
+
.replace(/<[@#!][^>]+>/g, "x") // user/channel/special mentions ARE content
|
|
266
|
+
.replace(/<[^>|]+\|([^>]*)>/g, "$1") // <url|label> → label (content)
|
|
267
|
+
.replace(/[*_~`>•|]/g, "")
|
|
268
|
+
.trim();
|
|
269
|
+
return stripped.length === 0;
|
|
270
|
+
}
|
|
271
|
+
//# sourceMappingURL=format.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"format.js","sourceRoot":"","sources":["../../../../src/agents/channels/slack/format.ts"],"names":[],"mappings":"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"}
|