@stratusagent/channel-slack 0.14.1 → 0.14.2

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.
@@ -0,0 +1,30 @@
1
+ /**
2
+ * A reply as a few short messages, the way a person texts (card 951).
3
+ *
4
+ * The `texts` reply mode posts each paragraph of a finished reply as a
5
+ * message of its own instead of one block of paragraphs. This file decides
6
+ * only where those cuts go; sizing each piece to Slack's limit and
7
+ * converting it to mrkdwn stay with the send path (`messageChunks`).
8
+ *
9
+ * It reads the Markdown the model wrote, before conversion, so a heading
10
+ * and a list item still look like what they are. Code is found with the
11
+ * same scanner the conversion uses (`codeRunsOf`), so a blank line inside
12
+ * a fence, or inside a span Slack would pair across paragraphs, is never a
13
+ * cut: converting each piece on its own then gives the text converting the
14
+ * whole would have, since nothing else in the conversion crosses a line.
15
+ */
16
+ /**
17
+ * The most messages one reply becomes. A reply longer than that is a
18
+ * document rather than a few texts, and a dozen posts in a row would buzz
19
+ * a phone a dozen times and run into Slack's posting rate. The paragraphs
20
+ * past the limit stay together in the last message.
21
+ */
22
+ export declare const TEXTS_MAX_MESSAGES = 5;
23
+ /**
24
+ * `text` as the messages it is sent as in `texts` mode, in order: cut at
25
+ * blank lines outside code, the pieces that belong together rejoined, at
26
+ * most `TEXTS_MAX_MESSAGES` of them. Text with nothing in it comes back
27
+ * as it is, for the caller's ordinary handling of an empty reply.
28
+ */
29
+ export declare const textingParts: (text: string) => string[];
30
+ //# sourceMappingURL=texts.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"texts.d.ts","sourceRoot":"","sources":["../src/texts.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;GAcG;AAIH;;;;;GAKG;AACH,eAAO,MAAM,kBAAkB,IAAI,CAAC;AAyCpC;;;;;GAKG;AACH,eAAO,MAAM,YAAY,GAAI,MAAM,MAAM,KAAG,MAAM,EAmCjD,CAAC"}
package/dist/texts.js ADDED
@@ -0,0 +1,101 @@
1
+ /**
2
+ * A reply as a few short messages, the way a person texts (card 951).
3
+ *
4
+ * The `texts` reply mode posts each paragraph of a finished reply as a
5
+ * message of its own instead of one block of paragraphs. This file decides
6
+ * only where those cuts go; sizing each piece to Slack's limit and
7
+ * converting it to mrkdwn stay with the send path (`messageChunks`).
8
+ *
9
+ * It reads the Markdown the model wrote, before conversion, so a heading
10
+ * and a list item still look like what they are. Code is found with the
11
+ * same scanner the conversion uses (`codeRunsOf`), so a blank line inside
12
+ * a fence, or inside a span Slack would pair across paragraphs, is never a
13
+ * cut: converting each piece on its own then gives the text converting the
14
+ * whole would have, since nothing else in the conversion crosses a line.
15
+ */
16
+ import { codeRunsOf } from "./mrkdwn.js";
17
+ /**
18
+ * The most messages one reply becomes. A reply longer than that is a
19
+ * document rather than a few texts, and a dozen posts in a row would buzz
20
+ * a phone a dozen times and run into Slack's posting rate. The paragraphs
21
+ * past the limit stay together in the last message.
22
+ */
23
+ export const TEXTS_MAX_MESSAGES = 5;
24
+ /**
25
+ * One or more blank lines (whitespace-only counts), with the line break
26
+ * before them. A `\r` is whitespace here, so a reply with Windows line
27
+ * endings splits where its blank lines are rather than not at all.
28
+ */
29
+ const BLANK_LINES = /\n[ \t\r]*\n(?:[ \t\r]*\n)*/g;
30
+ /** A Markdown heading line. */
31
+ const HEADING = /^ {0,3}#{1,6}(?:[ \t]|$)/;
32
+ /** A list item: a bullet or a number, then air. */
33
+ const LIST_ITEM = /^ {0,3}(?:[-*+]|\d{1,9}[.)])[ \t]/;
34
+ /** A block indented under the one above it: a list item's continuation, or indented code. */
35
+ const INDENTED = /^(?: {2,}|\t)/;
36
+ const isList = (block) => block.split('\n').some((line) => LIST_ITEM.test(line));
37
+ /** The rules that read only the block just before. */
38
+ const continuesAfter = (previous, block) =>
39
+ // A heading on its own line leads the paragraph under it.
40
+ (HEADING.test(previous) && !previous.includes('\n'))
41
+ // "Here is what changed:" leads what it introduces; a closing `:tada:`
42
+ // introduces nothing.
43
+ || (/:[ \t]*$/.test(previous) && !/:[\w+-]+:[ \t]*$/.test(previous))
44
+ // An indented block hangs off the one above it.
45
+ || INDENTED.test(block);
46
+ /**
47
+ * Whether `block` belongs in the same message as `group`, the blocks
48
+ * gathered so far for the current one. Each rule keeps together what reads
49
+ * wrong apart.
50
+ */
51
+ const continues = (group, block) => {
52
+ const previous = group.at(-1) ?? '';
53
+ return continuesAfter(previous, block)
54
+ // A loose list, its items apart by blank lines, is still one list,
55
+ // including past an item's indented continuation: the list is the
56
+ // message's, not only the block just before.
57
+ || (LIST_ITEM.test(block) && group.some(isList));
58
+ };
59
+ /**
60
+ * `text` as the messages it is sent as in `texts` mode, in order: cut at
61
+ * blank lines outside code, the pieces that belong together rejoined, at
62
+ * most `TEXTS_MAX_MESSAGES` of them. Text with nothing in it comes back
63
+ * as it is, for the caller's ordinary handling of an empty reply.
64
+ */
65
+ export const textingParts = (text) => {
66
+ const code = codeRunsOf(text);
67
+ const blocks = [];
68
+ let start = 0;
69
+ for (const gap of text.matchAll(BLANK_LINES)) {
70
+ const from = gap.index;
71
+ const to = from + gap[0].length;
72
+ if (code.some((run) => run.start < to && from < run.end)) {
73
+ continue;
74
+ }
75
+ blocks.push(text.slice(start, from));
76
+ start = to;
77
+ }
78
+ blocks.push(text.slice(start));
79
+ const groups = [];
80
+ for (const raw of blocks) {
81
+ const block = raw.trimEnd();
82
+ if (block.trim().length === 0) {
83
+ continue;
84
+ }
85
+ const last = groups.at(-1);
86
+ if (last !== undefined && continues(last, block)) {
87
+ last.push(block);
88
+ }
89
+ else {
90
+ groups.push([block]);
91
+ }
92
+ }
93
+ if (groups.length === 0) {
94
+ return [text];
95
+ }
96
+ if (groups.length > TEXTS_MAX_MESSAGES) {
97
+ groups.splice(TEXTS_MAX_MESSAGES - 1, groups.length, groups.slice(TEXTS_MAX_MESSAGES - 1).flat());
98
+ }
99
+ return groups.map((group) => group.join('\n\n'));
100
+ };
101
+ //# sourceMappingURL=texts.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"texts.js","sourceRoot":"","sources":["../src/texts.ts"],"names":[],"mappings":"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"}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@stratusagent/channel-slack",
3
- "version": "0.14.1",
3
+ "version": "0.14.2",
4
4
  "type": "module",
5
5
  "main": "./dist/index.js",
6
6
  "types": "./dist/index.d.ts",
@@ -21,7 +21,7 @@
21
21
  "@slack/socket-mode": "^3.1.0",
22
22
  "@slack/web-api": "^8.2.0",
23
23
  "undici": "^7.29.0",
24
- "@stratusagent/core": "0.14.1"
24
+ "@stratusagent/core": "0.14.2"
25
25
  },
26
26
  "devDependencies": {
27
27
  "@types/node": "^24.6.0",