eniac-slack 0.1.49 → 0.1.50
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/CHANGELOG.md
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"slack-messenger.d.ts","sourceRoot":"","sources":["../../src/services/slack-messenger.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,SAAS,EAAE,MAAM,gBAAgB,CAAC;AAChD,OAAO,KAAK,EAAE,SAAS,EAAE,MAAM,aAAa,CAAC;
|
|
1
|
+
{"version":3,"file":"slack-messenger.d.ts","sourceRoot":"","sources":["../../src/services/slack-messenger.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,SAAS,EAAE,MAAM,gBAAgB,CAAC;AAChD,OAAO,KAAK,EAAE,SAAS,EAAE,MAAM,aAAa,CAAC;AAO7C;;;;;;GAMG;AACH,wBAAsB,kBAAkB,CACtC,MAAM,EAAE,SAAS,EACjB,OAAO,EAAE,MAAM,EACf,QAAQ,EAAE,MAAM,EAChB,WAAW,EAAE,cAAc,CAAC,SAAS,EAAE,IAAI,EAAE,OAAO,CAAC,EACrD,OAAO,CAAC,EAAE;IAAE,MAAM,CAAC,EAAE,WAAW,CAAA;CAAE,GACjC,OAAO,CAAC,MAAM,CAAC,CAyKjB"}
|
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import { markdownToSlackMrkdwn } from "../utils/slack-format.js";
|
|
2
2
|
import { CLAUDE_ICON_URL } from "../constants.js";
|
|
3
3
|
const THROTTLE_MS = 500;
|
|
4
|
+
const MAX_MESSAGE_LENGTH = 2500;
|
|
4
5
|
/**
|
|
5
6
|
* Post a streaming reply in a Slack thread, handling both text and tool events.
|
|
6
7
|
*
|
|
@@ -15,25 +16,69 @@ export async function postStreamingReply(client, channel, threadTs, eventStream,
|
|
|
15
16
|
text: ":hourglass_flowing_sand: 생각하는 중...",
|
|
16
17
|
icon_url: CLAUDE_ICON_URL,
|
|
17
18
|
});
|
|
18
|
-
|
|
19
|
-
if (!messageTs) {
|
|
19
|
+
if (!initial.ts) {
|
|
20
20
|
throw new Error("Failed to post initial message — no ts returned");
|
|
21
21
|
}
|
|
22
|
+
let messageTs = initial.ts;
|
|
22
23
|
let accumulated = "";
|
|
23
24
|
let lastUpdateTime = 0;
|
|
24
25
|
let pendingUpdate = false;
|
|
25
|
-
|
|
26
|
-
// partial Korean characters being interpreted as punycode URLs by Slack
|
|
26
|
+
let committedLength = 0;
|
|
27
27
|
const safeSlice = (text) => {
|
|
28
|
-
// Find last whitespace or newline
|
|
29
28
|
const lastBreak = Math.max(text.lastIndexOf(" "), text.lastIndexOf("\n"), text.lastIndexOf("\t"));
|
|
30
|
-
// If break is near the end (within 20 chars), use it; otherwise send everything
|
|
31
29
|
if (lastBreak > 0 && text.length - lastBreak <= 20) {
|
|
32
30
|
return text.slice(0, lastBreak + 1);
|
|
33
31
|
}
|
|
34
32
|
return text;
|
|
35
33
|
};
|
|
34
|
+
const findSplitPoint = (text, maxLen) => {
|
|
35
|
+
const region = text.slice(0, maxLen);
|
|
36
|
+
const lastParagraph = region.lastIndexOf("\n\n");
|
|
37
|
+
if (lastParagraph > maxLen * 0.5)
|
|
38
|
+
return lastParagraph + 2;
|
|
39
|
+
const lastLine = region.lastIndexOf("\n");
|
|
40
|
+
if (lastLine > maxLen * 0.5)
|
|
41
|
+
return lastLine + 1;
|
|
42
|
+
const lastSpace = region.lastIndexOf(" ");
|
|
43
|
+
if (lastSpace > maxLen * 0.5)
|
|
44
|
+
return lastSpace + 1;
|
|
45
|
+
return maxLen;
|
|
46
|
+
};
|
|
47
|
+
const commitCurrentMessage = async (text) => {
|
|
48
|
+
try {
|
|
49
|
+
await client.chat.update({
|
|
50
|
+
channel,
|
|
51
|
+
ts: messageTs,
|
|
52
|
+
text: markdownToSlackMrkdwn(text),
|
|
53
|
+
});
|
|
54
|
+
}
|
|
55
|
+
catch (error) {
|
|
56
|
+
console.warn("[slack-messenger] Failed to finalize split message:", error instanceof Error ? error.message : error);
|
|
57
|
+
}
|
|
58
|
+
};
|
|
59
|
+
const startNewMessage = async (initialText) => {
|
|
60
|
+
const next = await client.chat.postMessage({
|
|
61
|
+
channel,
|
|
62
|
+
thread_ts: threadTs,
|
|
63
|
+
text: initialText,
|
|
64
|
+
icon_url: CLAUDE_ICON_URL,
|
|
65
|
+
});
|
|
66
|
+
if (next.ts)
|
|
67
|
+
messageTs = next.ts;
|
|
68
|
+
lastUpdateTime = Date.now();
|
|
69
|
+
pendingUpdate = false;
|
|
70
|
+
};
|
|
36
71
|
const doUpdate = async (text, isFinal) => {
|
|
72
|
+
const currentText = text.slice(committedLength);
|
|
73
|
+
if (!isFinal && currentText.length > MAX_MESSAGE_LENGTH) {
|
|
74
|
+
const splitPoint = findSplitPoint(currentText, MAX_MESSAGE_LENGTH);
|
|
75
|
+
const chunk = currentText.slice(0, splitPoint);
|
|
76
|
+
await commitCurrentMessage(chunk);
|
|
77
|
+
committedLength += splitPoint;
|
|
78
|
+
const remainder = currentText.slice(splitPoint);
|
|
79
|
+
await startNewMessage(remainder ? safeSlice(remainder) + "\n(생각 중)" : ":hourglass_flowing_sand: 이어서 작성 중...");
|
|
80
|
+
return;
|
|
81
|
+
}
|
|
37
82
|
const now = Date.now();
|
|
38
83
|
const elapsed = now - lastUpdateTime;
|
|
39
84
|
if (!isFinal && elapsed < THROTTLE_MS) {
|
|
@@ -42,8 +87,7 @@ export async function postStreamingReply(client, channel, threadTs, eventStream,
|
|
|
42
87
|
}
|
|
43
88
|
pendingUpdate = false;
|
|
44
89
|
lastUpdateTime = Date.now();
|
|
45
|
-
|
|
46
|
-
const displayText = isFinal ? text : safeSlice(text) + "\n(생각 중)";
|
|
90
|
+
const displayText = isFinal ? currentText : safeSlice(currentText) + "\n(생각 중)";
|
|
47
91
|
try {
|
|
48
92
|
await client.chat.update({
|
|
49
93
|
channel,
|
|
@@ -82,17 +126,28 @@ export async function postStreamingReply(client, channel, threadTs, eventStream,
|
|
|
82
126
|
}
|
|
83
127
|
// If aborted, edit in-place with interruption indicator.
|
|
84
128
|
if (options?.signal?.aborted) {
|
|
85
|
-
const
|
|
86
|
-
|
|
129
|
+
const currentText = accumulated.slice(committedLength);
|
|
130
|
+
const abortText = currentText
|
|
131
|
+
? markdownToSlackMrkdwn(currentText) + "\n\n:no_entry_sign: 새로운 입력으로 인해 중단되었습니다."
|
|
87
132
|
: ":no_entry_sign: 새로운 입력으로 인해 중단되었습니다.";
|
|
88
|
-
|
|
133
|
+
try {
|
|
134
|
+
await client.chat.update({ channel, ts: messageTs, text: abortText });
|
|
135
|
+
}
|
|
136
|
+
catch (error) {
|
|
137
|
+
console.warn("[slack-messenger] Failed to update abort message:", error instanceof Error ? error.message : error);
|
|
138
|
+
}
|
|
89
139
|
return messageTs;
|
|
90
140
|
}
|
|
91
141
|
// Normal completion — always edit in-place (no delete+resend race conditions).
|
|
92
|
-
const
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
142
|
+
const currentText = accumulated.slice(committedLength);
|
|
143
|
+
const finalText = currentText
|
|
144
|
+
? markdownToSlackMrkdwn(currentText)
|
|
145
|
+
: committedLength > 0
|
|
146
|
+
? null
|
|
147
|
+
: ":speech_balloon: 응답을 생성하지 못했어요. 다시 한번 말씀해 주시겠어요?";
|
|
148
|
+
if (finalText) {
|
|
149
|
+
await doUpdate(finalText, true);
|
|
150
|
+
}
|
|
96
151
|
return messageTs;
|
|
97
152
|
}
|
|
98
153
|
//# sourceMappingURL=slack-messenger.js.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"slack-messenger.js","sourceRoot":"","sources":["../../src/services/slack-messenger.ts"],"names":[],"mappings":"AAEA,OAAO,EAAE,qBAAqB,EAAE,MAAM,0BAA0B,CAAC;AACjE,OAAO,EAAE,eAAe,EAAE,MAAM,iBAAiB,CAAC;AAElD,MAAM,WAAW,GAAG,GAAG,CAAC;
|
|
1
|
+
{"version":3,"file":"slack-messenger.js","sourceRoot":"","sources":["../../src/services/slack-messenger.ts"],"names":[],"mappings":"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"}
|
package/package.json
CHANGED
|
@@ -4,6 +4,7 @@ import { markdownToSlackMrkdwn } from "../utils/slack-format.js";
|
|
|
4
4
|
import { CLAUDE_ICON_URL } from "../constants.js";
|
|
5
5
|
|
|
6
6
|
const THROTTLE_MS = 500;
|
|
7
|
+
const MAX_MESSAGE_LENGTH = 2500;
|
|
7
8
|
|
|
8
9
|
/**
|
|
9
10
|
* Post a streaming reply in a Slack thread, handling both text and tool events.
|
|
@@ -26,32 +27,83 @@ export async function postStreamingReply(
|
|
|
26
27
|
icon_url: CLAUDE_ICON_URL,
|
|
27
28
|
});
|
|
28
29
|
|
|
29
|
-
|
|
30
|
-
if (!messageTs) {
|
|
30
|
+
if (!initial.ts) {
|
|
31
31
|
throw new Error("Failed to post initial message — no ts returned");
|
|
32
32
|
}
|
|
33
|
+
let messageTs: string = initial.ts;
|
|
33
34
|
|
|
34
35
|
let accumulated = "";
|
|
35
36
|
let lastUpdateTime = 0;
|
|
36
37
|
let pendingUpdate = false;
|
|
38
|
+
let committedLength = 0;
|
|
37
39
|
|
|
38
|
-
// Trim accumulated text to the last word boundary to avoid
|
|
39
|
-
// partial Korean characters being interpreted as punycode URLs by Slack
|
|
40
40
|
const safeSlice = (text: string): string => {
|
|
41
|
-
// Find last whitespace or newline
|
|
42
41
|
const lastBreak = Math.max(
|
|
43
42
|
text.lastIndexOf(" "),
|
|
44
43
|
text.lastIndexOf("\n"),
|
|
45
44
|
text.lastIndexOf("\t")
|
|
46
45
|
);
|
|
47
|
-
// If break is near the end (within 20 chars), use it; otherwise send everything
|
|
48
46
|
if (lastBreak > 0 && text.length - lastBreak <= 20) {
|
|
49
47
|
return text.slice(0, lastBreak + 1);
|
|
50
48
|
}
|
|
51
49
|
return text;
|
|
52
50
|
};
|
|
53
51
|
|
|
52
|
+
const findSplitPoint = (text: string, maxLen: number): number => {
|
|
53
|
+
const region = text.slice(0, maxLen);
|
|
54
|
+
const lastParagraph = region.lastIndexOf("\n\n");
|
|
55
|
+
if (lastParagraph > maxLen * 0.5) return lastParagraph + 2;
|
|
56
|
+
const lastLine = region.lastIndexOf("\n");
|
|
57
|
+
if (lastLine > maxLen * 0.5) return lastLine + 1;
|
|
58
|
+
const lastSpace = region.lastIndexOf(" ");
|
|
59
|
+
if (lastSpace > maxLen * 0.5) return lastSpace + 1;
|
|
60
|
+
return maxLen;
|
|
61
|
+
};
|
|
62
|
+
|
|
63
|
+
const commitCurrentMessage = async (text: string) => {
|
|
64
|
+
try {
|
|
65
|
+
await client.chat.update({
|
|
66
|
+
channel,
|
|
67
|
+
ts: messageTs,
|
|
68
|
+
text: markdownToSlackMrkdwn(text),
|
|
69
|
+
});
|
|
70
|
+
} catch (error) {
|
|
71
|
+
console.warn(
|
|
72
|
+
"[slack-messenger] Failed to finalize split message:",
|
|
73
|
+
error instanceof Error ? error.message : error
|
|
74
|
+
);
|
|
75
|
+
}
|
|
76
|
+
};
|
|
77
|
+
|
|
78
|
+
const startNewMessage = async (initialText: string) => {
|
|
79
|
+
const next = await client.chat.postMessage({
|
|
80
|
+
channel,
|
|
81
|
+
thread_ts: threadTs,
|
|
82
|
+
text: initialText,
|
|
83
|
+
icon_url: CLAUDE_ICON_URL,
|
|
84
|
+
});
|
|
85
|
+
if (next.ts) messageTs = next.ts;
|
|
86
|
+
lastUpdateTime = Date.now();
|
|
87
|
+
pendingUpdate = false;
|
|
88
|
+
};
|
|
89
|
+
|
|
54
90
|
const doUpdate = async (text: string, isFinal: boolean) => {
|
|
91
|
+
const currentText = text.slice(committedLength);
|
|
92
|
+
|
|
93
|
+
if (!isFinal && currentText.length > MAX_MESSAGE_LENGTH) {
|
|
94
|
+
const splitPoint = findSplitPoint(currentText, MAX_MESSAGE_LENGTH);
|
|
95
|
+
const chunk = currentText.slice(0, splitPoint);
|
|
96
|
+
|
|
97
|
+
await commitCurrentMessage(chunk);
|
|
98
|
+
committedLength += splitPoint;
|
|
99
|
+
|
|
100
|
+
const remainder = currentText.slice(splitPoint);
|
|
101
|
+
await startNewMessage(
|
|
102
|
+
remainder ? safeSlice(remainder) + "\n(생각 중)" : ":hourglass_flowing_sand: 이어서 작성 중..."
|
|
103
|
+
);
|
|
104
|
+
return;
|
|
105
|
+
}
|
|
106
|
+
|
|
55
107
|
const now = Date.now();
|
|
56
108
|
const elapsed = now - lastUpdateTime;
|
|
57
109
|
|
|
@@ -63,8 +115,7 @@ export async function postStreamingReply(
|
|
|
63
115
|
pendingUpdate = false;
|
|
64
116
|
lastUpdateTime = Date.now();
|
|
65
117
|
|
|
66
|
-
|
|
67
|
-
const displayText = isFinal ? text : safeSlice(text) + "\n(생각 중)";
|
|
118
|
+
const displayText = isFinal ? currentText : safeSlice(currentText) + "\n(생각 중)";
|
|
68
119
|
|
|
69
120
|
try {
|
|
70
121
|
await client.chat.update({
|
|
@@ -109,17 +160,32 @@ export async function postStreamingReply(
|
|
|
109
160
|
|
|
110
161
|
// If aborted, edit in-place with interruption indicator.
|
|
111
162
|
if (options?.signal?.aborted) {
|
|
112
|
-
const
|
|
113
|
-
|
|
163
|
+
const currentText = accumulated.slice(committedLength);
|
|
164
|
+
const abortText = currentText
|
|
165
|
+
? markdownToSlackMrkdwn(currentText) + "\n\n:no_entry_sign: 새로운 입력으로 인해 중단되었습니다."
|
|
114
166
|
: ":no_entry_sign: 새로운 입력으로 인해 중단되었습니다.";
|
|
115
|
-
|
|
167
|
+
try {
|
|
168
|
+
await client.chat.update({ channel, ts: messageTs, text: abortText });
|
|
169
|
+
} catch (error) {
|
|
170
|
+
console.warn(
|
|
171
|
+
"[slack-messenger] Failed to update abort message:",
|
|
172
|
+
error instanceof Error ? error.message : error
|
|
173
|
+
);
|
|
174
|
+
}
|
|
116
175
|
return messageTs;
|
|
117
176
|
}
|
|
118
177
|
|
|
119
178
|
// Normal completion — always edit in-place (no delete+resend race conditions).
|
|
120
|
-
const
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
179
|
+
const currentText = accumulated.slice(committedLength);
|
|
180
|
+
const finalText = currentText
|
|
181
|
+
? markdownToSlackMrkdwn(currentText)
|
|
182
|
+
: committedLength > 0
|
|
183
|
+
? null
|
|
184
|
+
: ":speech_balloon: 응답을 생성하지 못했어요. 다시 한번 말씀해 주시겠어요?";
|
|
185
|
+
|
|
186
|
+
if (finalText) {
|
|
187
|
+
await doUpdate(finalText, true);
|
|
188
|
+
}
|
|
189
|
+
|
|
124
190
|
return messageTs;
|
|
125
191
|
}
|