chat 4.14.0 → 4.16.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.
Files changed (38) hide show
  1. package/README.md +12 -0
  2. package/dist/{chunk-THM4ACIE.js → chunk-7S5DLTN2.js} +372 -9
  3. package/dist/chunk-7S5DLTN2.js.map +1 -0
  4. package/dist/index.d.ts +122 -5
  5. package/dist/index.js +367 -235
  6. package/dist/index.js.map +1 -1
  7. package/dist/{jsx-runtime-Bdt1Dwzf.d.ts → jsx-runtime-Bokk9xw5.d.ts} +80 -5
  8. package/dist/jsx-runtime.d.ts +1 -1
  9. package/dist/jsx-runtime.js +3 -1
  10. package/docs/adapters/discord.mdx +1 -0
  11. package/docs/adapters/index.mdx +51 -34
  12. package/docs/adapters/meta.json +1 -0
  13. package/docs/adapters/slack.mdx +16 -0
  14. package/docs/adapters/telegram.mdx +161 -0
  15. package/docs/api/cards.mdx +57 -1
  16. package/docs/api/channel.mdx +4 -1
  17. package/docs/api/chat.mdx +5 -0
  18. package/docs/api/markdown.mdx +42 -0
  19. package/docs/api/thread.mdx +4 -1
  20. package/docs/cards.mdx +44 -1
  21. package/docs/contributing/building.mdx +626 -0
  22. package/docs/contributing/documenting.mdx +218 -0
  23. package/docs/contributing/meta.json +4 -0
  24. package/docs/contributing/publishing.mdx +161 -0
  25. package/docs/contributing/testing.mdx +494 -0
  26. package/docs/error-handling.mdx +1 -1
  27. package/docs/getting-started.mdx +23 -1
  28. package/docs/handling-events.mdx +375 -0
  29. package/docs/index.mdx +4 -2
  30. package/docs/meta.json +6 -1
  31. package/docs/posting-messages.mdx +7 -7
  32. package/docs/slash-commands.mdx +23 -0
  33. package/docs/state/meta.json +1 -6
  34. package/docs/streaming.mdx +32 -0
  35. package/docs/threads-messages-channels.mdx +237 -0
  36. package/docs/usage.mdx +82 -276
  37. package/package.json +4 -3
  38. package/dist/chunk-THM4ACIE.js.map +0 -1
package/dist/index.js CHANGED
@@ -1,7 +1,9 @@
1
1
  import {
2
2
  Actions,
3
+ BaseFormatConverter,
3
4
  Button,
4
5
  Card,
6
+ CardLink,
5
7
  CardText,
6
8
  Divider,
7
9
  Field,
@@ -13,16 +15,51 @@ import {
13
15
  Section,
14
16
  Select,
15
17
  SelectOption,
18
+ Table,
16
19
  TextInput,
20
+ blockquote,
21
+ cardChildToFallbackText,
17
22
  cardToFallbackText,
23
+ codeBlock,
24
+ emphasis,
18
25
  fromReactElement,
19
26
  fromReactModalElement,
27
+ getNodeChildren,
28
+ getNodeValue,
29
+ inlineCode,
30
+ isBlockquoteNode,
20
31
  isCardElement,
32
+ isCodeNode,
33
+ isDeleteNode,
34
+ isEmphasisNode,
35
+ isInlineCodeNode,
21
36
  isJSX,
37
+ isLinkNode,
38
+ isListItemNode,
39
+ isListNode,
22
40
  isModalElement,
41
+ isParagraphNode,
42
+ isStrongNode,
43
+ isTableCellNode,
44
+ isTableNode,
45
+ isTableRowNode,
46
+ isTextNode,
47
+ link,
48
+ markdownToPlainText,
49
+ paragraph,
50
+ parseMarkdown,
51
+ root,
52
+ strikethrough,
53
+ stringifyMarkdown,
54
+ strong,
55
+ tableElementToAscii,
56
+ tableToAscii,
57
+ text,
23
58
  toCardElement,
24
- toModalElement
25
- } from "./chunk-THM4ACIE.js";
59
+ toModalElement,
60
+ toPlainText,
61
+ walkAst
62
+ } from "./chunk-7S5DLTN2.js";
26
63
 
27
64
  // src/channel.ts
28
65
  import { WORKFLOW_DESERIALIZE as WORKFLOW_DESERIALIZE2, WORKFLOW_SERIALIZE as WORKFLOW_SERIALIZE2 } from "@workflow/serde";
@@ -44,217 +81,6 @@ function hasChatSingleton() {
44
81
  return _singleton !== null;
45
82
  }
46
83
 
47
- // src/markdown.ts
48
- import { toString as mdastToString } from "mdast-util-to-string";
49
- import remarkGfm from "remark-gfm";
50
- import remarkParse from "remark-parse";
51
- import remarkStringify from "remark-stringify";
52
- import { unified } from "unified";
53
- function isTextNode(node) {
54
- return node.type === "text";
55
- }
56
- function isParagraphNode(node) {
57
- return node.type === "paragraph";
58
- }
59
- function isStrongNode(node) {
60
- return node.type === "strong";
61
- }
62
- function isEmphasisNode(node) {
63
- return node.type === "emphasis";
64
- }
65
- function isDeleteNode(node) {
66
- return node.type === "delete";
67
- }
68
- function isInlineCodeNode(node) {
69
- return node.type === "inlineCode";
70
- }
71
- function isCodeNode(node) {
72
- return node.type === "code";
73
- }
74
- function isLinkNode(node) {
75
- return node.type === "link";
76
- }
77
- function isBlockquoteNode(node) {
78
- return node.type === "blockquote";
79
- }
80
- function isListNode(node) {
81
- return node.type === "list";
82
- }
83
- function isListItemNode(node) {
84
- return node.type === "listItem";
85
- }
86
- function getNodeChildren(node) {
87
- if ("children" in node && Array.isArray(node.children)) {
88
- return node.children;
89
- }
90
- return [];
91
- }
92
- function getNodeValue(node) {
93
- if ("value" in node && typeof node.value === "string") {
94
- return node.value;
95
- }
96
- return "";
97
- }
98
- function parseMarkdown(markdown) {
99
- const processor = unified().use(remarkParse).use(remarkGfm);
100
- return processor.parse(markdown);
101
- }
102
- function stringifyMarkdown(ast) {
103
- const processor = unified().use(remarkStringify).use(remarkGfm);
104
- return processor.stringify(ast);
105
- }
106
- function toPlainText(ast) {
107
- return mdastToString(ast);
108
- }
109
- function markdownToPlainText(markdown) {
110
- const ast = parseMarkdown(markdown);
111
- return mdastToString(ast);
112
- }
113
- function walkAst(node, visitor) {
114
- if ("children" in node && Array.isArray(node.children)) {
115
- node.children = node.children.map((child) => {
116
- const result = visitor(child);
117
- if (result === null) {
118
- return null;
119
- }
120
- return walkAst(result, visitor);
121
- }).filter((n) => n !== null);
122
- }
123
- return node;
124
- }
125
- function text(value) {
126
- return { type: "text", value };
127
- }
128
- function strong(children) {
129
- return { type: "strong", children };
130
- }
131
- function emphasis(children) {
132
- return { type: "emphasis", children };
133
- }
134
- function strikethrough(children) {
135
- return { type: "delete", children };
136
- }
137
- function inlineCode(value) {
138
- return { type: "inlineCode", value };
139
- }
140
- function codeBlock(value, lang) {
141
- return { type: "code", value, lang };
142
- }
143
- function link(url, children, title) {
144
- return { type: "link", url, children, title };
145
- }
146
- function blockquote(children) {
147
- return { type: "blockquote", children };
148
- }
149
- function paragraph(children) {
150
- return { type: "paragraph", children };
151
- }
152
- function root(children) {
153
- return { type: "root", children };
154
- }
155
- var BaseFormatConverter = class {
156
- /**
157
- * Template method for implementing fromAst with a node converter.
158
- * Iterates through AST children and converts each using the provided function.
159
- * Joins results with double newlines (standard paragraph separation).
160
- *
161
- * @param ast - The AST to convert
162
- * @param nodeConverter - Function to convert each Content node to string
163
- * @returns Platform-formatted string
164
- */
165
- fromAstWithNodeConverter(ast, nodeConverter) {
166
- const parts = [];
167
- for (const node of ast.children) {
168
- parts.push(nodeConverter(node));
169
- }
170
- return parts.join("\n\n");
171
- }
172
- extractPlainText(platformText) {
173
- return toPlainText(this.toAst(platformText));
174
- }
175
- // Convenience methods for markdown string I/O
176
- fromMarkdown(markdown) {
177
- return this.fromAst(parseMarkdown(markdown));
178
- }
179
- toMarkdown(platformText) {
180
- return stringifyMarkdown(this.toAst(platformText));
181
- }
182
- /** @deprecated Use extractPlainText instead */
183
- toPlainText(platformText) {
184
- return this.extractPlainText(platformText);
185
- }
186
- /**
187
- * Convert a PostableMessage to platform format (text only).
188
- * - string: passed through as raw text (no conversion)
189
- * - { raw: string }: passed through as raw text (no conversion)
190
- * - { markdown: string }: converted from markdown to platform format
191
- * - { ast: Root }: converted from AST to platform format
192
- * - { card: CardElement }: returns fallback text (cards should be handled by adapter)
193
- * - CardElement: returns fallback text (cards should be handled by adapter)
194
- *
195
- * Note: For cards, adapters should check for card content first and render
196
- * them using platform-specific card APIs, using this method only for fallback.
197
- */
198
- renderPostable(message) {
199
- if (typeof message === "string") {
200
- return message;
201
- }
202
- if ("raw" in message) {
203
- return message.raw;
204
- }
205
- if ("markdown" in message) {
206
- return this.fromMarkdown(message.markdown);
207
- }
208
- if ("ast" in message) {
209
- return this.fromAst(message.ast);
210
- }
211
- if ("card" in message) {
212
- return message.fallbackText || this.cardToFallbackText(message.card);
213
- }
214
- if ("type" in message && message.type === "card") {
215
- return this.cardToFallbackText(message);
216
- }
217
- throw new Error("Invalid PostableMessage format");
218
- }
219
- /**
220
- * Generate fallback text from a card element.
221
- * Override in subclasses for platform-specific formatting.
222
- */
223
- cardToFallbackText(card) {
224
- const parts = [];
225
- if (card.title) {
226
- parts.push(`**${card.title}**`);
227
- }
228
- if (card.subtitle) {
229
- parts.push(card.subtitle);
230
- }
231
- for (const child of card.children) {
232
- const text2 = this.cardChildToFallbackText(child);
233
- if (text2) {
234
- parts.push(text2);
235
- }
236
- }
237
- return parts.join("\n");
238
- }
239
- /**
240
- * Convert card child element to fallback text.
241
- */
242
- cardChildToFallbackText(child) {
243
- switch (child.type) {
244
- case "text":
245
- return child.content;
246
- case "fields":
247
- return child.children.map((f) => `**${f.label}**: ${f.value}`).join("\n");
248
- case "actions":
249
- return null;
250
- case "section":
251
- return child.children.map((c) => this.cardChildToFallbackText(c)).filter(Boolean).join("\n");
252
- default:
253
- return null;
254
- }
255
- }
256
- };
257
-
258
84
  // src/message.ts
259
85
  import { WORKFLOW_DESERIALIZE, WORKFLOW_SERIALIZE } from "@workflow/serde";
260
86
  var Message = class _Message {
@@ -786,6 +612,233 @@ function extractMessageContent(message) {
786
612
 
787
613
  // src/thread.ts
788
614
  import { WORKFLOW_DESERIALIZE as WORKFLOW_DESERIALIZE3, WORKFLOW_SERIALIZE as WORKFLOW_SERIALIZE3 } from "@workflow/serde";
615
+
616
+ // src/streaming-markdown.ts
617
+ import remend from "remend";
618
+ var StreamingMarkdownRenderer = class {
619
+ accumulated = "";
620
+ dirty = true;
621
+ cachedRender = "";
622
+ finished = false;
623
+ /** Number of code fence toggles from completed lines (odd = inside). */
624
+ fenceToggles = 0;
625
+ /** Incomplete trailing line buffer for incremental fence tracking. */
626
+ incompleteLine = "";
627
+ /** Append a chunk from the LLM stream. */
628
+ push(chunk) {
629
+ this.accumulated += chunk;
630
+ this.dirty = true;
631
+ this.incompleteLine += chunk;
632
+ const parts = this.incompleteLine.split("\n");
633
+ this.incompleteLine = parts.pop() ?? "";
634
+ for (const line of parts) {
635
+ const trimmed = line.trimStart();
636
+ if (trimmed.startsWith("```") || trimmed.startsWith("~~~")) {
637
+ this.fenceToggles++;
638
+ }
639
+ }
640
+ }
641
+ /** O(1) check if accumulated text is inside an unclosed code fence. */
642
+ isAccumulatedInsideFence() {
643
+ let inside = this.fenceToggles % 2 === 1;
644
+ const trimmed = this.incompleteLine.trimStart();
645
+ if (trimmed.startsWith("```") || trimmed.startsWith("~~~")) {
646
+ inside = !inside;
647
+ }
648
+ return inside;
649
+ }
650
+ /**
651
+ * Get renderable markdown for an intermediate edit.
652
+ * - Holds back trailing lines that look like a table header (|...|)
653
+ * until a separator line (|---|---|) confirms or the next line denies.
654
+ * - Applies remend() to close incomplete inline markers.
655
+ * - Idempotent: returns cached result if no push() since last call.
656
+ */
657
+ render() {
658
+ if (!this.dirty) {
659
+ return this.cachedRender;
660
+ }
661
+ this.dirty = false;
662
+ if (this.finished) {
663
+ this.cachedRender = remend(this.accumulated);
664
+ return this.cachedRender;
665
+ }
666
+ if (this.isAccumulatedInsideFence()) {
667
+ this.cachedRender = remend(this.accumulated);
668
+ return this.cachedRender;
669
+ }
670
+ const committable = getCommittablePrefix(this.accumulated);
671
+ this.cachedRender = remend(committable);
672
+ return this.cachedRender;
673
+ }
674
+ /**
675
+ * Get text safe for append-only streaming (e.g. Slack native streaming).
676
+ *
677
+ * - Holds back unconfirmed table headers until separator arrives.
678
+ * - Wraps confirmed tables in code fences so pipes render as literal
679
+ * text (not broken mrkdwn). The code fence is left OPEN while
680
+ * the table is still streaming, keeping output monotonic for deltas.
681
+ * - Holds back unclosed inline markers (**, *, ~~, `, [).
682
+ * - The final editMessage replaces everything with properly formatted text.
683
+ */
684
+ getCommittableText() {
685
+ if (this.finished) {
686
+ return wrapTablesForAppend(this.accumulated, true);
687
+ }
688
+ let text2 = this.accumulated;
689
+ if (text2.length > 0 && !text2.endsWith("\n")) {
690
+ const lastNewline = text2.lastIndexOf("\n");
691
+ const withoutIncompleteLine = lastNewline >= 0 ? text2.slice(0, lastNewline + 1) : "";
692
+ if (isInsideCodeFence(withoutIncompleteLine)) {
693
+ return wrapTablesForAppend(text2);
694
+ }
695
+ text2 = withoutIncompleteLine;
696
+ }
697
+ if (isInsideCodeFence(text2)) {
698
+ return wrapTablesForAppend(text2);
699
+ }
700
+ const committed = getCommittablePrefix(text2);
701
+ const wrapped = wrapTablesForAppend(committed);
702
+ if (isInsideCodeFence(wrapped)) {
703
+ return wrapped;
704
+ }
705
+ return findCleanPrefix(wrapped);
706
+ }
707
+ /** Raw accumulated text (no remend, no buffering). For the final edit. */
708
+ getText() {
709
+ return this.accumulated;
710
+ }
711
+ /** Signal stream end. Flushes held-back lines. Returns final render. */
712
+ finish() {
713
+ this.finished = true;
714
+ this.dirty = true;
715
+ return this.render();
716
+ }
717
+ };
718
+ var INLINE_MARKER_CHARS = /* @__PURE__ */ new Set(["*", "~", "`", "["]);
719
+ function isClean(text2) {
720
+ return remend(text2).length <= text2.length;
721
+ }
722
+ function findCleanPrefix(text2) {
723
+ if (text2.length === 0 || isClean(text2)) {
724
+ return text2;
725
+ }
726
+ for (let i = text2.length - 1; i >= 0; i--) {
727
+ if (INLINE_MARKER_CHARS.has(text2[i])) {
728
+ while (i > 0 && text2[i - 1] === text2[i]) {
729
+ i--;
730
+ }
731
+ const candidate = text2.slice(0, i);
732
+ if (isClean(candidate)) {
733
+ return candidate;
734
+ }
735
+ }
736
+ }
737
+ return "";
738
+ }
739
+ var TABLE_ROW_RE = /^\|.*\|$/;
740
+ var TABLE_SEPARATOR_RE = /^\|[\s:]*-{1,}[\s:]*(\|[\s:]*-{1,}[\s:]*)*\|$/;
741
+ function isInsideCodeFence(text2) {
742
+ let inside = false;
743
+ for (const line of text2.split("\n")) {
744
+ const trimmed = line.trimStart();
745
+ if (trimmed.startsWith("```") || trimmed.startsWith("~~~")) {
746
+ inside = !inside;
747
+ }
748
+ }
749
+ return inside;
750
+ }
751
+ function getCommittablePrefix(text2) {
752
+ const endsWithNewline = text2.endsWith("\n");
753
+ const lines = text2.split("\n");
754
+ if (!endsWithNewline && lines.length > 0) {
755
+ lines.pop();
756
+ }
757
+ if (endsWithNewline && lines.length > 0 && lines.at(-1) === "") {
758
+ lines.pop();
759
+ }
760
+ let heldCount = 0;
761
+ let separatorFound = false;
762
+ for (let i = lines.length - 1; i >= 0; i--) {
763
+ const trimmed = lines[i].trim();
764
+ if (trimmed === "") {
765
+ break;
766
+ }
767
+ if (TABLE_SEPARATOR_RE.test(trimmed)) {
768
+ separatorFound = true;
769
+ break;
770
+ }
771
+ if (TABLE_ROW_RE.test(trimmed)) {
772
+ heldCount++;
773
+ } else {
774
+ break;
775
+ }
776
+ }
777
+ if (separatorFound || heldCount === 0) {
778
+ return text2;
779
+ }
780
+ const commitLineCount = lines.length - heldCount;
781
+ const committedLines = lines.slice(0, commitLineCount);
782
+ let result = committedLines.join("\n");
783
+ if (committedLines.length > 0) {
784
+ result += "\n";
785
+ }
786
+ return result;
787
+ }
788
+ function wrapTablesForAppend(text2, closeFences = false) {
789
+ const hadTrailingNewline = text2.endsWith("\n");
790
+ const lines = text2.split("\n");
791
+ if (hadTrailingNewline && lines.length > 0 && lines.at(-1) === "") {
792
+ lines.pop();
793
+ }
794
+ const result = [];
795
+ let inTable = false;
796
+ let inUserCodeFence = false;
797
+ for (let i = 0; i < lines.length; i++) {
798
+ const trimmed = lines[i].trim();
799
+ if (!inTable && (trimmed.startsWith("```") || trimmed.startsWith("~~~"))) {
800
+ inUserCodeFence = !inUserCodeFence;
801
+ result.push(lines[i]);
802
+ continue;
803
+ }
804
+ if (inUserCodeFence) {
805
+ result.push(lines[i]);
806
+ continue;
807
+ }
808
+ const isTableLine = trimmed !== "" && (TABLE_ROW_RE.test(trimmed) || TABLE_SEPARATOR_RE.test(trimmed));
809
+ if (isTableLine && !inTable) {
810
+ let hasSeparator = false;
811
+ for (let j = i; j < lines.length; j++) {
812
+ const t = lines[j].trim();
813
+ if (TABLE_SEPARATOR_RE.test(t)) {
814
+ hasSeparator = true;
815
+ break;
816
+ }
817
+ if (t === "" || !TABLE_ROW_RE.test(t)) {
818
+ break;
819
+ }
820
+ }
821
+ if (hasSeparator) {
822
+ result.push("```");
823
+ inTable = true;
824
+ }
825
+ } else if (!isTableLine && inTable) {
826
+ result.push("```");
827
+ inTable = false;
828
+ }
829
+ result.push(lines[i]);
830
+ }
831
+ if (inTable && closeFences) {
832
+ result.push("```");
833
+ }
834
+ let output = result.join("\n");
835
+ if (hadTrailingNewline) {
836
+ output += "\n";
837
+ }
838
+ return output;
839
+ }
840
+
841
+ // src/thread.ts
789
842
  function isLazyConfig2(config) {
790
843
  return "adapterName" in config && !("adapter" in config);
791
844
  }
@@ -809,6 +862,8 @@ var ThreadImpl = class _ThreadImpl {
809
862
  _currentMessage;
810
863
  /** Update interval for fallback streaming */
811
864
  _streamingUpdateIntervalMs;
865
+ /** Placeholder text for fallback streaming (post + edit) */
866
+ _fallbackStreamingPlaceholderText;
812
867
  /** Cached channel instance */
813
868
  _channel;
814
869
  constructor(config) {
@@ -818,6 +873,7 @@ var ThreadImpl = class _ThreadImpl {
818
873
  this._isSubscribedContext = config.isSubscribedContext ?? false;
819
874
  this._currentMessage = config.currentMessage;
820
875
  this._streamingUpdateIntervalMs = config.streamingUpdateIntervalMs ?? 500;
876
+ this._fallbackStreamingPlaceholderText = config.fallbackStreamingPlaceholderText !== void 0 ? config.fallbackStreamingPlaceholderText : "...";
821
877
  if (isLazyConfig2(config)) {
822
878
  this._adapterName = config.adapterName;
823
879
  } else {
@@ -1050,7 +1106,11 @@ var ThreadImpl = class _ThreadImpl {
1050
1106
  }
1051
1107
  };
1052
1108
  const raw = await this.adapter.stream(this.id, wrappedStream, options);
1053
- return this.createSentMessage(raw.id, accumulated, raw.threadId);
1109
+ return this.createSentMessage(
1110
+ raw.id,
1111
+ { markdown: accumulated },
1112
+ raw.threadId
1113
+ );
1054
1114
  }
1055
1115
  return this.fallbackStream(textStream, options);
1056
1116
  }
@@ -1065,37 +1125,56 @@ var ThreadImpl = class _ThreadImpl {
1065
1125
  */
1066
1126
  async fallbackStream(textStream, options) {
1067
1127
  const intervalMs = options?.updateIntervalMs ?? this._streamingUpdateIntervalMs;
1068
- const msg = await this.adapter.postMessage(this.id, "...");
1069
- const threadIdForEdits = msg.threadId || this.id;
1070
- let accumulated = "";
1071
- let lastEditContent = "...";
1128
+ const placeholderText = this._fallbackStreamingPlaceholderText;
1129
+ let msg = placeholderText === null ? null : await this.adapter.postMessage(this.id, placeholderText);
1130
+ let threadIdForEdits = this.id;
1131
+ const renderer = new StreamingMarkdownRenderer();
1132
+ let lastEditContent = "";
1072
1133
  let stopped = false;
1073
1134
  let pendingEdit = null;
1074
1135
  let timerId = null;
1136
+ if (msg) {
1137
+ threadIdForEdits = msg.threadId || this.id;
1138
+ lastEditContent = placeholderText ?? "";
1139
+ }
1140
+ const scheduleNextEdit = () => {
1141
+ timerId = setTimeout(() => {
1142
+ pendingEdit = doEditAndReschedule();
1143
+ }, intervalMs);
1144
+ };
1075
1145
  const doEditAndReschedule = async () => {
1076
- if (stopped) {
1146
+ if (stopped || !msg) {
1077
1147
  return;
1078
1148
  }
1079
- if (accumulated !== lastEditContent) {
1080
- const content = accumulated;
1149
+ const content = renderer.render();
1150
+ if (content !== lastEditContent) {
1081
1151
  try {
1082
- await this.adapter.editMessage(threadIdForEdits, msg.id, content);
1152
+ await this.adapter.editMessage(threadIdForEdits, msg.id, {
1153
+ markdown: content
1154
+ });
1083
1155
  lastEditContent = content;
1084
1156
  } catch {
1085
1157
  }
1086
1158
  }
1087
1159
  if (!stopped) {
1088
- timerId = setTimeout(() => {
1089
- pendingEdit = doEditAndReschedule();
1090
- }, intervalMs);
1160
+ scheduleNextEdit();
1091
1161
  }
1092
1162
  };
1093
- timerId = setTimeout(() => {
1094
- pendingEdit = doEditAndReschedule();
1095
- }, intervalMs);
1163
+ if (msg) {
1164
+ scheduleNextEdit();
1165
+ }
1096
1166
  try {
1097
1167
  for await (const chunk of textStream) {
1098
- accumulated += chunk;
1168
+ renderer.push(chunk);
1169
+ if (!msg) {
1170
+ const content = renderer.render();
1171
+ msg = await this.adapter.postMessage(this.id, {
1172
+ markdown: content
1173
+ });
1174
+ threadIdForEdits = msg.threadId || this.id;
1175
+ lastEditContent = content;
1176
+ scheduleNextEdit();
1177
+ }
1099
1178
  }
1100
1179
  } finally {
1101
1180
  stopped = true;
@@ -1107,10 +1186,25 @@ var ThreadImpl = class _ThreadImpl {
1107
1186
  if (pendingEdit) {
1108
1187
  await pendingEdit;
1109
1188
  }
1110
- if (accumulated !== lastEditContent) {
1111
- await this.adapter.editMessage(threadIdForEdits, msg.id, accumulated);
1189
+ const accumulated = renderer.getText();
1190
+ const finalContent = renderer.finish();
1191
+ if (!msg) {
1192
+ msg = await this.adapter.postMessage(this.id, {
1193
+ markdown: accumulated
1194
+ });
1195
+ threadIdForEdits = msg.threadId || this.id;
1196
+ lastEditContent = accumulated;
1112
1197
  }
1113
- return this.createSentMessage(msg.id, accumulated, threadIdForEdits);
1198
+ if (finalContent !== lastEditContent) {
1199
+ await this.adapter.editMessage(threadIdForEdits, msg.id, {
1200
+ markdown: accumulated
1201
+ });
1202
+ }
1203
+ return this.createSentMessage(
1204
+ msg.id,
1205
+ { markdown: accumulated },
1206
+ threadIdForEdits
1207
+ );
1114
1208
  }
1115
1209
  async refresh() {
1116
1210
  const result = await this.adapter.fetchMessages(this.id, { limit: 50 });
@@ -1330,7 +1424,7 @@ function extractMessageContent2(message) {
1330
1424
  var DEFAULT_LOCK_TTL_MS = 3e4;
1331
1425
  var SLACK_USER_ID_REGEX = /^U[A-Z0-9]+$/i;
1332
1426
  var DISCORD_SNOWFLAKE_REGEX = /^\d{17,19}$/;
1333
- var DEDUPE_TTL_MS = 6e4;
1427
+ var DEDUPE_TTL_MS = 5 * 60 * 1e3;
1334
1428
  var MODAL_CONTEXT_TTL_MS = 24 * 60 * 60 * 1e3;
1335
1429
  var Chat = class {
1336
1430
  /**
@@ -1368,6 +1462,8 @@ var Chat = class {
1368
1462
  userName;
1369
1463
  logger;
1370
1464
  _streamingUpdateIntervalMs;
1465
+ _fallbackStreamingPlaceholderText;
1466
+ _dedupeTtlMs;
1371
1467
  mentionHandlers = [];
1372
1468
  messagePatterns = [];
1373
1469
  subscribedMessageHandlers = [];
@@ -1379,6 +1475,7 @@ var Chat = class {
1379
1475
  assistantThreadStartedHandlers = [];
1380
1476
  assistantContextChangedHandlers = [];
1381
1477
  appHomeOpenedHandlers = [];
1478
+ memberJoinedChannelHandlers = [];
1382
1479
  /** Initialization state */
1383
1480
  initPromise = null;
1384
1481
  initialized = false;
@@ -1393,6 +1490,8 @@ var Chat = class {
1393
1490
  this._stateAdapter = config.state;
1394
1491
  this.adapters = /* @__PURE__ */ new Map();
1395
1492
  this._streamingUpdateIntervalMs = config.streamingUpdateIntervalMs ?? 500;
1493
+ this._fallbackStreamingPlaceholderText = config.fallbackStreamingPlaceholderText !== void 0 ? config.fallbackStreamingPlaceholderText : "...";
1494
+ this._dedupeTtlMs = config.dedupeTtlMs ?? DEDUPE_TTL_MS;
1396
1495
  if (typeof config.logger === "string") {
1397
1496
  this.logger = new ConsoleLogger(config.logger);
1398
1497
  } else {
@@ -1623,6 +1722,10 @@ var Chat = class {
1623
1722
  this.appHomeOpenedHandlers.push(handler);
1624
1723
  this.logger.debug("Registered app home opened handler");
1625
1724
  }
1725
+ onMemberJoinedChannel(handler) {
1726
+ this.memberJoinedChannelHandlers.push(handler);
1727
+ this.logger.debug("Registered member joined channel handler");
1728
+ }
1626
1729
  /**
1627
1730
  * Get an adapter by name with type safety.
1628
1731
  */
@@ -1824,6 +1927,22 @@ var Chat = class {
1824
1927
  options.waitUntil(task);
1825
1928
  }
1826
1929
  }
1930
+ processMemberJoinedChannel(event, options) {
1931
+ const task = (async () => {
1932
+ for (const handler of this.memberJoinedChannelHandlers) {
1933
+ await handler(event);
1934
+ }
1935
+ })().catch((err) => {
1936
+ this.logger.error("Member joined channel handler error", {
1937
+ error: err,
1938
+ channelId: event.channelId,
1939
+ userId: event.userId
1940
+ });
1941
+ });
1942
+ if (options?.waitUntil) {
1943
+ options.waitUntil(task);
1944
+ }
1945
+ }
1827
1946
  /**
1828
1947
  * Handle a slash command event internally.
1829
1948
  */
@@ -2299,7 +2418,7 @@ var Chat = class {
2299
2418
  });
2300
2419
  return;
2301
2420
  }
2302
- await this._stateAdapter.set(dedupeKey, true, DEDUPE_TTL_MS);
2421
+ await this._stateAdapter.set(dedupeKey, true, this._dedupeTtlMs);
2303
2422
  const lock = await this._stateAdapter.acquireLock(
2304
2423
  threadId,
2305
2424
  DEFAULT_LOCK_TTL_MS
@@ -2386,7 +2505,8 @@ var Chat = class {
2386
2505
  isSubscribedContext,
2387
2506
  isDM,
2388
2507
  currentMessage: initialMessage,
2389
- streamingUpdateIntervalMs: this._streamingUpdateIntervalMs
2508
+ streamingUpdateIntervalMs: this._streamingUpdateIntervalMs,
2509
+ fallbackStreamingPlaceholderText: this._fallbackStreamingPlaceholderText
2390
2510
  });
2391
2511
  }
2392
2512
  /**
@@ -2822,6 +2942,8 @@ var emoji = createEmoji();
2822
2942
  var Actions2 = Actions;
2823
2943
  var Button2 = Button;
2824
2944
  var Card2 = Card;
2945
+ var cardChildToFallbackText2 = cardChildToFallbackText;
2946
+ var CardLink2 = CardLink;
2825
2947
  var CardText2 = CardText;
2826
2948
  var Divider2 = Divider;
2827
2949
  var Field2 = Field;
@@ -2832,6 +2954,7 @@ var isCardElement2 = isCardElement;
2832
2954
  var isJSX2 = isJSX;
2833
2955
  var LinkButton2 = LinkButton;
2834
2956
  var Section2 = Section;
2957
+ var Table2 = Table;
2835
2958
  var toCardElement2 = toCardElement;
2836
2959
  var toModalElement2 = toModalElement;
2837
2960
  var fromReactModalElement2 = fromReactModalElement;
@@ -2846,6 +2969,7 @@ export {
2846
2969
  BaseFormatConverter,
2847
2970
  Button2 as Button,
2848
2971
  Card2 as Card,
2972
+ CardLink2 as CardLink,
2849
2973
  CardText2 as CardText,
2850
2974
  ChannelImpl,
2851
2975
  Chat,
@@ -2867,10 +2991,13 @@ export {
2867
2991
  Section2 as Section,
2868
2992
  Select2 as Select,
2869
2993
  SelectOption2 as SelectOption,
2994
+ StreamingMarkdownRenderer,
2870
2995
  THREAD_STATE_TTL_MS,
2996
+ Table2 as Table,
2871
2997
  TextInput2 as TextInput,
2872
2998
  ThreadImpl,
2873
2999
  blockquote,
3000
+ cardChildToFallbackText2 as cardChildToFallbackText,
2874
3001
  codeBlock,
2875
3002
  convertEmojiPlaceholders,
2876
3003
  createEmoji,
@@ -2897,6 +3024,9 @@ export {
2897
3024
  isModalElement2 as isModalElement,
2898
3025
  isParagraphNode,
2899
3026
  isStrongNode,
3027
+ isTableCellNode,
3028
+ isTableNode,
3029
+ isTableRowNode,
2900
3030
  isTextNode,
2901
3031
  link,
2902
3032
  markdownToPlainText,
@@ -2906,6 +3036,8 @@ export {
2906
3036
  strikethrough,
2907
3037
  stringifyMarkdown,
2908
3038
  strong,
3039
+ tableElementToAscii,
3040
+ tableToAscii,
2909
3041
  text,
2910
3042
  toCardElement2 as toCardElement,
2911
3043
  toModalElement2 as toModalElement,