eniac-slack 0.1.4 → 0.1.5
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/handlers/mention.d.ts +3 -2
- package/dist/handlers/mention.d.ts.map +1 -1
- package/dist/handlers/mention.js +15 -10
- package/dist/handlers/mention.js.map +1 -1
- package/dist/handlers/thread.d.ts.map +1 -1
- package/dist/handlers/thread.js +11 -5
- package/dist/handlers/thread.js.map +1 -1
- package/dist/services/claude.d.ts +2 -1
- package/dist/services/claude.d.ts.map +1 -1
- package/dist/services/claude.js +27 -2
- package/dist/services/claude.js.map +1 -1
- package/dist/utils/slack-files.d.ts +16 -0
- package/dist/utils/slack-files.d.ts.map +1 -0
- package/dist/utils/slack-files.js +44 -0
- package/dist/utils/slack-files.js.map +1 -0
- package/package.json +1 -1
- package/src/handlers/mention.ts +19 -10
- package/src/handlers/thread.ts +12 -5
- package/src/services/claude.ts +31 -2
- package/src/utils/slack-files.ts +57 -0
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import type { AllMiddlewareArgs, SlackEventMiddlewareArgs } from "@slack/bolt";
|
|
2
2
|
import { type GithubRepo } from "../utils/parse.js";
|
|
3
|
+
import { type SlackImageFile } from "../utils/slack-files.js";
|
|
3
4
|
/**
|
|
4
5
|
* Pending repo creation requests — keyed by a unique request ID.
|
|
5
6
|
*/
|
|
@@ -23,9 +24,9 @@ export declare function handleMention(args: AllMiddlewareArgs & SlackEventMiddle
|
|
|
23
24
|
/**
|
|
24
25
|
* Start a session with a GitHub repository working directory.
|
|
25
26
|
*/
|
|
26
|
-
export declare function startSessionWithRepo(client: AllMiddlewareArgs["client"], channel: string, threadTs: string, repoInfo: GithubRepo, cleanText: string, authorUserId: string): Promise<void>;
|
|
27
|
+
export declare function startSessionWithRepo(client: AllMiddlewareArgs["client"], channel: string, threadTs: string, repoInfo: GithubRepo, cleanText: string, authorUserId: string, images?: SlackImageFile[]): Promise<void>;
|
|
27
28
|
/**
|
|
28
29
|
* Start a session without a repository (temp directory).
|
|
29
30
|
*/
|
|
30
|
-
export declare function startSessionWithoutRepo(client: AllMiddlewareArgs["client"], channel: string, threadTs: string, cleanText: string, authorUserId: string): Promise<void>;
|
|
31
|
+
export declare function startSessionWithoutRepo(client: AllMiddlewareArgs["client"], channel: string, threadTs: string, cleanText: string, authorUserId: string, images?: SlackImageFile[]): Promise<void>;
|
|
31
32
|
//# sourceMappingURL=mention.d.ts.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"mention.d.ts","sourceRoot":"","sources":["../../src/handlers/mention.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,iBAAiB,EAAE,wBAAwB,EAAE,MAAM,aAAa,CAAC;AAC/E,OAAO,EAAoC,KAAK,UAAU,EAAE,MAAM,mBAAmB,CAAC;
|
|
1
|
+
{"version":3,"file":"mention.d.ts","sourceRoot":"","sources":["../../src/handlers/mention.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,iBAAiB,EAAE,wBAAwB,EAAE,MAAM,aAAa,CAAC;AAC/E,OAAO,EAAoC,KAAK,UAAU,EAAE,MAAM,mBAAmB,CAAC;AAItF,OAAO,EAAuB,KAAK,cAAc,EAAE,MAAM,yBAAyB,CAAC;AAMnF;;GAEG;AACH,MAAM,WAAW,kBAAkB;IACjC,QAAQ,EAAE,UAAU,CAAC;IACrB,QAAQ,EAAE,MAAM,CAAC;IACjB,OAAO,EAAE,MAAM,CAAC;IAChB,SAAS,EAAE,MAAM,CAAC;IAClB,YAAY,EAAE,MAAM,CAAC;CACtB;AAMD,wBAAgB,qBAAqB,CACnC,SAAS,EAAE,MAAM,GAChB,kBAAkB,GAAG,SAAS,CAEhC;AAED,wBAAgB,wBAAwB,CAAC,SAAS,EAAE,MAAM,GAAG,IAAI,CAEhE;AAID,wBAAgB,eAAe,CAAC,GAAG,EAAE,MAAM,GAAG,IAAI,CAEjD;AASD;;;;;GAKG;AACH,wBAAsB,aAAa,CACjC,IAAI,EAAE,iBAAiB,GAAG,wBAAwB,CAAC,aAAa,CAAC,GAChE,OAAO,CAAC,IAAI,CAAC,CA+Ff;AAED;;GAEG;AACH,wBAAsB,oBAAoB,CACxC,MAAM,EAAE,iBAAiB,CAAC,QAAQ,CAAC,EACnC,OAAO,EAAE,MAAM,EACf,QAAQ,EAAE,MAAM,EAChB,QAAQ,EAAE,UAAU,EACpB,SAAS,EAAE,MAAM,EACjB,YAAY,EAAE,MAAM,EACpB,MAAM,CAAC,EAAE,cAAc,EAAE,GACxB,OAAO,CAAC,IAAI,CAAC,CA4Bf;AAED;;GAEG;AACH,wBAAsB,uBAAuB,CAC3C,MAAM,EAAE,iBAAiB,CAAC,QAAQ,CAAC,EACnC,OAAO,EAAE,MAAM,EACf,QAAQ,EAAE,MAAM,EAChB,SAAS,EAAE,MAAM,EACjB,YAAY,EAAE,MAAM,EACpB,MAAM,CAAC,EAAE,cAAc,EAAE,GACxB,OAAO,CAAC,IAAI,CAAC,CAMf"}
|
package/dist/handlers/mention.js
CHANGED
|
@@ -2,6 +2,7 @@ import { removeMention, extractGithubRepo } from "../utils/parse.js";
|
|
|
2
2
|
import { createSession, getSession, chat } from "../services/claude.js";
|
|
3
3
|
import { prepareWorkDir, checkRepoExists } from "../services/git.js";
|
|
4
4
|
import { postStreamingReply } from "../services/slack-messenger.js";
|
|
5
|
+
import { downloadSlackImages } from "../utils/slack-files.js";
|
|
5
6
|
import { CLAUDE_ICON_URL } from "../constants.js";
|
|
6
7
|
import fs from "node:fs/promises";
|
|
7
8
|
import os from "node:os";
|
|
@@ -34,9 +35,12 @@ export async function handleMention(args) {
|
|
|
34
35
|
const { event, client } = args;
|
|
35
36
|
const { text, channel, ts, thread_ts } = event;
|
|
36
37
|
const authorUserId = event.user ?? "unknown";
|
|
38
|
+
const files = "files" in event ? event.files : undefined;
|
|
39
|
+
// Download any image attachments
|
|
40
|
+
const images = files?.length ? await downloadSlackImages(client, files) : [];
|
|
37
41
|
// If the mention is inside an existing thread, treat it as a thread reply
|
|
38
42
|
if (thread_ts) {
|
|
39
|
-
await handleThreadedMention(client, channel, thread_ts, text, authorUserId);
|
|
43
|
+
await handleThreadedMention(client, channel, thread_ts, text, authorUserId, images);
|
|
40
44
|
return;
|
|
41
45
|
}
|
|
42
46
|
// New conversation — ts becomes the thread_ts
|
|
@@ -108,17 +112,17 @@ export async function handleMention(args) {
|
|
|
108
112
|
return;
|
|
109
113
|
}
|
|
110
114
|
// Repo exists — proceed with normal setup
|
|
111
|
-
await startSessionWithRepo(client, channel, threadTs, repoInfo, cleanText, authorUserId);
|
|
115
|
+
await startSessionWithRepo(client, channel, threadTs, repoInfo, cleanText, authorUserId, images);
|
|
112
116
|
}
|
|
113
117
|
else {
|
|
114
118
|
// No repo mentioned — use temp directory
|
|
115
|
-
await startSessionWithoutRepo(client, channel, threadTs, cleanText, authorUserId);
|
|
119
|
+
await startSessionWithoutRepo(client, channel, threadTs, cleanText, authorUserId, images);
|
|
116
120
|
}
|
|
117
121
|
}
|
|
118
122
|
/**
|
|
119
123
|
* Start a session with a GitHub repository working directory.
|
|
120
124
|
*/
|
|
121
|
-
export async function startSessionWithRepo(client, channel, threadTs, repoInfo, cleanText, authorUserId) {
|
|
125
|
+
export async function startSessionWithRepo(client, channel, threadTs, repoInfo, cleanText, authorUserId, images) {
|
|
122
126
|
let workDir;
|
|
123
127
|
try {
|
|
124
128
|
await client.chat.postMessage({
|
|
@@ -141,31 +145,32 @@ export async function startSessionWithRepo(client, channel, threadTs, repoInfo,
|
|
|
141
145
|
}
|
|
142
146
|
createSession(threadTs, workDir, authorUserId);
|
|
143
147
|
const userMessage = `[Working directory: ${workDir} — Repository: ${repoInfo.owner}/${repoInfo.repo}]\n\n${cleanText}`;
|
|
144
|
-
const stream = chat(threadTs, userMessage, client, channel);
|
|
148
|
+
const stream = chat(threadTs, userMessage, client, channel, images?.length ? images : undefined);
|
|
145
149
|
await postStreamingReply(client, channel, threadTs, stream);
|
|
146
150
|
}
|
|
147
151
|
/**
|
|
148
152
|
* Start a session without a repository (temp directory).
|
|
149
153
|
*/
|
|
150
|
-
export async function startSessionWithoutRepo(client, channel, threadTs, cleanText, authorUserId) {
|
|
154
|
+
export async function startSessionWithoutRepo(client, channel, threadTs, cleanText, authorUserId, images) {
|
|
151
155
|
const workDir = await fs.mkdtemp(path.join(os.tmpdir(), "eniac-"));
|
|
152
156
|
createSession(threadTs, workDir, authorUserId);
|
|
153
|
-
const stream = chat(threadTs, cleanText, client, channel);
|
|
157
|
+
const stream = chat(threadTs, cleanText, client, channel, images?.length ? images : undefined);
|
|
154
158
|
await postStreamingReply(client, channel, threadTs, stream);
|
|
155
159
|
}
|
|
156
160
|
/**
|
|
157
161
|
* Handle a mention that occurs inside an existing thread.
|
|
158
162
|
*/
|
|
159
|
-
async function handleThreadedMention(client, channel, threadTs, text, authorUserId) {
|
|
163
|
+
async function handleThreadedMention(client, channel, threadTs, text, authorUserId, images) {
|
|
160
164
|
const cleanText = removeMention(text);
|
|
161
|
-
if (!cleanText)
|
|
165
|
+
if (!cleanText && (!images || images.length === 0))
|
|
162
166
|
return;
|
|
163
167
|
let session = getSession(threadTs);
|
|
164
168
|
if (!session) {
|
|
165
169
|
const workDir = await fs.mkdtemp(path.join(os.tmpdir(), "eniac-"));
|
|
166
170
|
session = createSession(threadTs, workDir, authorUserId);
|
|
167
171
|
}
|
|
168
|
-
const
|
|
172
|
+
const userMessage = cleanText || "이미지를 분석해주세요.";
|
|
173
|
+
const stream = chat(threadTs, userMessage, client, channel, images?.length ? images : undefined);
|
|
169
174
|
await postStreamingReply(client, channel, threadTs, stream);
|
|
170
175
|
}
|
|
171
176
|
//# sourceMappingURL=mention.js.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"mention.js","sourceRoot":"","sources":["../../src/handlers/mention.ts"],"names":[],"mappings":"AACA,OAAO,EAAE,aAAa,EAAE,iBAAiB,EAAmB,MAAM,mBAAmB,CAAC;AACtF,OAAO,EAAE,aAAa,EAAE,UAAU,EAAE,IAAI,EAAE,MAAM,uBAAuB,CAAC;AACxE,OAAO,EAAE,cAAc,EAAE,eAAe,EAAoB,MAAM,oBAAoB,CAAC;AACvF,OAAO,EAAE,kBAAkB,EAAE,MAAM,gCAAgC,CAAC;AACpE,OAAO,EAAE,eAAe,EAAE,MAAM,iBAAiB,CAAC;AAClD,OAAO,EAAE,MAAM,kBAAkB,CAAC;AAClC,OAAO,EAAE,MAAM,SAAS,CAAC;AACzB,OAAO,IAAI,MAAM,WAAW,CAAC;AAa7B,MAAM,mBAAmB,GAAG,IAAI,GAAG,EAA8B,CAAC;AAElE,IAAI,gBAAgB,GAAG,CAAC,CAAC;AAEzB,MAAM,UAAU,qBAAqB,CACnC,SAAiB;IAEjB,OAAO,mBAAmB,CAAC,GAAG,CAAC,SAAS,CAAC,CAAC;AAC5C,CAAC;AAED,MAAM,UAAU,wBAAwB,CAAC,SAAiB;IACxD,mBAAmB,CAAC,MAAM,CAAC,SAAS,CAAC,CAAC;AACxC,CAAC;AAED,IAAI,YAAoB,CAAC;AAEzB,MAAM,UAAU,eAAe,CAAC,GAAW;IACzC,YAAY,GAAG,GAAG,CAAC;AACrB,CAAC;AAED,SAAS,eAAe;IACtB,IAAI,CAAC,YAAY,EAAE,CAAC;QAClB,YAAY,GAAG,IAAI,CAAC,IAAI,CAAC,EAAE,CAAC,OAAO,EAAE,EAAE,QAAQ,EAAE,OAAO,CAAC,CAAC;IAC5D,CAAC;IACD,OAAO,YAAY,CAAC;AACtB,CAAC;AAED;;;;;GAKG;AACH,MAAM,CAAC,KAAK,UAAU,aAAa,CACjC,IAAiE;IAEjE,MAAM,EAAE,KAAK,EAAE,MAAM,EAAE,GAAG,IAAI,CAAC;IAC/B,MAAM,EAAE,IAAI,EAAE,OAAO,EAAE,EAAE,EAAE,SAAS,EAAE,GAAG,KAAK,CAAC;IAC/C,MAAM,YAAY,GAAG,KAAK,CAAC,IAAI,IAAI,SAAS,CAAC;
|
|
1
|
+
{"version":3,"file":"mention.js","sourceRoot":"","sources":["../../src/handlers/mention.ts"],"names":[],"mappings":"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"}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"thread.d.ts","sourceRoot":"","sources":["../../src/handlers/thread.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,iBAAiB,EAAE,wBAAwB,EAAE,MAAM,aAAa,CAAC;
|
|
1
|
+
{"version":3,"file":"thread.d.ts","sourceRoot":"","sources":["../../src/handlers/thread.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,iBAAiB,EAAE,wBAAwB,EAAE,MAAM,aAAa,CAAC;AAK/E;;;;GAIG;AACH,wBAAsB,mBAAmB,CACvC,IAAI,EAAE,iBAAiB,GAAG,wBAAwB,CAAC,SAAS,CAAC,GAC5D,OAAO,CAAC,IAAI,CAAC,CAsDf"}
|
package/dist/handlers/thread.js
CHANGED
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import { getSession, chat } from "../services/claude.js";
|
|
2
2
|
import { postStreamingReply } from "../services/slack-messenger.js";
|
|
3
|
+
import { downloadSlackImages } from "../utils/slack-files.js";
|
|
3
4
|
/**
|
|
4
5
|
* Handle messages posted in threads where the bot has an active session.
|
|
5
6
|
*
|
|
@@ -29,6 +30,7 @@ export async function handleThreadMessage(args) {
|
|
|
29
30
|
return;
|
|
30
31
|
}
|
|
31
32
|
const text = "text" in event ? event.text : undefined;
|
|
33
|
+
const files = "files" in event ? event.files : undefined;
|
|
32
34
|
const channel = event.channel;
|
|
33
35
|
// Only respond if we have an active session for this thread
|
|
34
36
|
const session = getSession(threadTs);
|
|
@@ -36,14 +38,18 @@ export async function handleThreadMessage(args) {
|
|
|
36
38
|
console.log(`[thread] skipped: no session for thread_ts=${threadTs}`);
|
|
37
39
|
return;
|
|
38
40
|
}
|
|
39
|
-
console.log(`[thread] session found! sessionId=${session.sessionId}, text=${text?.slice(0, 50)}`);
|
|
40
|
-
|
|
41
|
+
console.log(`[thread] session found! sessionId=${session.sessionId}, text=${text?.slice(0, 50)}, files=${files?.length ?? 0}`);
|
|
42
|
+
// Download any image attachments
|
|
43
|
+
const images = files?.length ? await downloadSlackImages(client, files) : [];
|
|
44
|
+
const hasText = text && text.trim().length > 0;
|
|
45
|
+
if (!hasText && images.length === 0)
|
|
41
46
|
return;
|
|
42
47
|
// Strip any bot mentions from the text (user might @mention the bot in-thread)
|
|
43
|
-
const cleanText = text.replace(/<@[A-Z0-9]+>/g, "").trim();
|
|
44
|
-
if (!cleanText)
|
|
48
|
+
const cleanText = (text ?? "").replace(/<@[A-Z0-9]+>/g, "").trim();
|
|
49
|
+
if (!cleanText && images.length === 0)
|
|
45
50
|
return;
|
|
46
|
-
const
|
|
51
|
+
const userMessage = cleanText || "이미지를 분석해주세요.";
|
|
52
|
+
const stream = chat(threadTs, userMessage, client, channel, images.length > 0 ? images : undefined);
|
|
47
53
|
await postStreamingReply(client, channel, threadTs, stream);
|
|
48
54
|
}
|
|
49
55
|
//# sourceMappingURL=thread.js.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"thread.js","sourceRoot":"","sources":["../../src/handlers/thread.ts"],"names":[],"mappings":"AACA,OAAO,EAAE,UAAU,EAAE,IAAI,EAAE,MAAM,uBAAuB,CAAC;AACzD,OAAO,EAAE,kBAAkB,EAAE,MAAM,gCAAgC,CAAC;
|
|
1
|
+
{"version":3,"file":"thread.js","sourceRoot":"","sources":["../../src/handlers/thread.ts"],"names":[],"mappings":"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"}
|
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import type { SlackImageFile } from "../utils/slack-files.js";
|
|
1
2
|
import type { WebClient } from "@slack/web-api";
|
|
2
3
|
interface Session {
|
|
3
4
|
sessionId: string;
|
|
@@ -22,6 +23,6 @@ export declare function getSession(threadTs: string): Session | undefined;
|
|
|
22
23
|
* Uses `canUseTool` callback to handle permission requests
|
|
23
24
|
* through Slack interactive buttons.
|
|
24
25
|
*/
|
|
25
|
-
export declare function chat(threadTs: string, userMessage: string, slackClient: WebClient, channel: string): AsyncGenerator<ChatEvent, void, unknown>;
|
|
26
|
+
export declare function chat(threadTs: string, userMessage: string, slackClient: WebClient, channel: string, images?: SlackImageFile[]): AsyncGenerator<ChatEvent, void, unknown>;
|
|
26
27
|
export {};
|
|
27
28
|
//# sourceMappingURL=claude.d.ts.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"claude.d.ts","sourceRoot":"","sources":["../../src/services/claude.ts"],"names":[],"mappings":"
|
|
1
|
+
{"version":3,"file":"claude.d.ts","sourceRoot":"","sources":["../../src/services/claude.ts"],"names":[],"mappings":"AAQA,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,yBAAyB,CAAC;AAK9D,OAAO,KAAK,EAAE,SAAS,EAAE,MAAM,gBAAgB,CAAC;AAGhD,UAAU,OAAO;IACf,SAAS,EAAE,MAAM,CAAC;IAClB,OAAO,EAAE,MAAM,CAAC;IAChB,UAAU,EAAE,OAAO,CAAC;IACpB,YAAY,EAAE,MAAM,CAAC;IACrB,SAAS,EAAE,MAAM,CAAC;IAClB,cAAc,EAAE,MAAM,CAAC;CACxB;AAED,MAAM,MAAM,SAAS,GACjB;IAAE,IAAI,EAAE,MAAM,CAAC;IAAC,OAAO,EAAE,MAAM,CAAA;CAAE,GACjC;IAAE,IAAI,EAAE,OAAO,CAAC;IAAC,OAAO,EAAE,MAAM,CAAA;CAAE,CAAC;AAoJvC,wBAAgB,aAAa,CAC3B,QAAQ,EAAE,MAAM,EAChB,OAAO,EAAE,MAAM,EACf,YAAY,EAAE,MAAM,GACnB,OAAO,CAaT;AAED,wBAAgB,UAAU,CAAC,QAAQ,EAAE,MAAM,GAAG,OAAO,GAAG,SAAS,CAEhE;AAkBD;;;;;GAKG;AACH,wBAAuB,IAAI,CACzB,QAAQ,EAAE,MAAM,EAChB,WAAW,EAAE,MAAM,EACnB,WAAW,EAAE,SAAS,EACtB,OAAO,EAAE,MAAM,EACf,MAAM,CAAC,EAAE,cAAc,EAAE,GACxB,cAAc,CAAC,SAAS,EAAE,IAAI,EAAE,OAAO,CAAC,CA6J1C"}
|
package/dist/services/claude.js
CHANGED
|
@@ -160,7 +160,7 @@ function describeToolInput(toolName, input) {
|
|
|
160
160
|
* Uses `canUseTool` callback to handle permission requests
|
|
161
161
|
* through Slack interactive buttons.
|
|
162
162
|
*/
|
|
163
|
-
export async function* chat(threadTs, userMessage, slackClient, channel) {
|
|
163
|
+
export async function* chat(threadTs, userMessage, slackClient, channel, images) {
|
|
164
164
|
const session = sessions.get(threadTs);
|
|
165
165
|
if (!session) {
|
|
166
166
|
throw new Error(`No session found for thread ${threadTs}`);
|
|
@@ -207,9 +207,34 @@ export async function* chat(threadTs, userMessage, slackClient, channel) {
|
|
|
207
207
|
session.lastActivityAt = Date.now();
|
|
208
208
|
saveSessions();
|
|
209
209
|
console.log(`[claude] starting query, cwd=${session.workDir}, hasStarted=${session.hasStarted}`);
|
|
210
|
+
// Build prompt: plain string when no images, SDKUserMessage iterable otherwise
|
|
211
|
+
const hasImages = images && images.length > 0;
|
|
212
|
+
const prompt = hasImages
|
|
213
|
+
? (async function* () {
|
|
214
|
+
yield {
|
|
215
|
+
type: "user",
|
|
216
|
+
message: {
|
|
217
|
+
role: "user",
|
|
218
|
+
content: [
|
|
219
|
+
...images.map((img) => ({
|
|
220
|
+
type: "image",
|
|
221
|
+
source: {
|
|
222
|
+
type: "base64",
|
|
223
|
+
media_type: img.mediaType,
|
|
224
|
+
data: img.base64,
|
|
225
|
+
},
|
|
226
|
+
})),
|
|
227
|
+
{ type: "text", text: userMessage },
|
|
228
|
+
],
|
|
229
|
+
},
|
|
230
|
+
parent_tool_use_id: null,
|
|
231
|
+
session_id: session.sessionId,
|
|
232
|
+
};
|
|
233
|
+
})()
|
|
234
|
+
: userMessage;
|
|
210
235
|
try {
|
|
211
236
|
const q = query({
|
|
212
|
-
prompt
|
|
237
|
+
prompt,
|
|
213
238
|
options: {
|
|
214
239
|
cwd: session.workDir,
|
|
215
240
|
canUseTool,
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"claude.js","sourceRoot":"","sources":["../../src/services/claude.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,KAAK,EAAE,MAAM,gCAAgC,CAAC;
|
|
1
|
+
{"version":3,"file":"claude.js","sourceRoot":"","sources":["../../src/services/claude.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,KAAK,EAAE,MAAM,gCAAgC,CAAC;AASvD,OAAO,EAAE,UAAU,EAAE,MAAM,aAAa,CAAC;AACzC,OAAO,EAAE,MAAM,SAAS,CAAC;AACzB,OAAO,IAAI,MAAM,WAAW,CAAC;AAC7B,OAAO,EAAE,MAAM,SAAS,CAAC;AAEzB,OAAO,EAAE,iBAAiB,EAAE,MAAM,kBAAkB,CAAC;AAerD,mCAAmC;AACnC,MAAM,aAAa,GAAG,IAAI,CAAC,IAAI,CAAC,EAAE,CAAC,OAAO,EAAE,EAAE,QAAQ,EAAE,eAAe,CAAC,CAAC;AAEzE,SAAS,YAAY;IACnB,IAAI,CAAC;QACH,MAAM,IAAI,GAAG,EAAE,CAAC,YAAY,CAAC,aAAa,EAAE,OAAO,CAAC,CAAC;QACrD,MAAM,OAAO,GAAG,IAAI,CAAC,KAAK,CAAC,IAAI,CAAwB,CAAC;QACxD,OAAO,CAAC,GAAG,CAAC,qBAAqB,OAAO,CAAC,MAAM,qBAAqB,CAAC,CAAC;QACtE,OAAO,IAAI,GAAG,CAAC,OAAO,CAAC,CAAC;IAC1B,CAAC;IAAC,MAAM,CAAC;QACP,OAAO,IAAI,GAAG,EAAE,CAAC;IACnB,CAAC;AACH,CAAC;AAED,SAAS,YAAY;IACnB,IAAI,CAAC;QACH,MAAM,GAAG,GAAG,IAAI,CAAC,OAAO,CAAC,aAAa,CAAC,CAAC;QACxC,EAAE,CAAC,SAAS,CAAC,GAAG,EAAE,EAAE,SAAS,EAAE,IAAI,EAAE,CAAC,CAAC;QACvC,EAAE,CAAC,aAAa,CACd,aAAa,EACb,IAAI,CAAC,SAAS,CAAC,CAAC,GAAG,QAAQ,CAAC,OAAO,EAAE,CAAC,EAAE,IAAI,EAAE,CAAC,CAAC,CACjD,CAAC;IACJ,CAAC;IAAC,OAAO,GAAG,EAAE,CAAC;QACb,OAAO,CAAC,IAAI,CAAC,4BAA4B,EAAE,GAAG,CAAC,CAAC;IAClD,CAAC;AACH,CAAC;AAED,mCAAmC;AACnC,MAAM,eAAe,GAAG,IAAI,CAAC,IAAI,CAAC,EAAE,CAAC,OAAO,EAAE,EAAE,SAAS,EAAE,WAAW,CAAC,CAAC;AAExE,SAAS,cAAc;IACrB,IAAI,CAAC;QACH,MAAM,IAAI,GAAG,EAAE,CAAC,YAAY,CAAC,eAAe,EAAE,OAAO,CAAC,CAAC;QACvD,MAAM,MAAM,GAAG,IAAI,CAAC,KAAK,CAAC,IAAI,CAE7B,CAAC;QACF,IAAI,MAAM,CAAC,UAAU,IAAI,MAAM,CAAC,IAAI,CAAC,MAAM,CAAC,UAAU,CAAC,CAAC,MAAM,GAAG,CAAC,EAAE,CAAC;YACnE,OAAO,CAAC,GAAG,CACT,gBAAgB,MAAM,CAAC,IAAI,CAAC,MAAM,CAAC,UAAU,CAAC,CAAC,MAAM,mBAAmB,MAAM,CAAC,IAAI,CAAC,MAAM,CAAC,UAAU,CAAC,CAAC,IAAI,CAAC,IAAI,CAAC,EAAE,CACpH,CAAC;YACF,OAAO,MAAM,CAAC,UAAU,CAAC;QAC3B,CAAC;IACH,CAAC;IAAC,MAAM,CAAC;QACP,OAAO,CAAC,GAAG,CAAC,8CAA8C,CAAC,CAAC;IAC9D,CAAC;IACD,OAAO,SAAS,CAAC;AACnB,CAAC;AAED,MAAM,UAAU,GAAG,cAAc,EAAE,CAAC;AAEpC,yCAAyC;AACzC,MAAM,mBAAmB,GAAG;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;CA0C3B,CAAC,IAAI,EAAE,CAAC;AAET,MAAM,cAAc,GAAG,EAAE,GAAG,EAAE,GAAG,EAAE,GAAG,EAAE,GAAG,IAAI,CAAC,CAAC,UAAU;AAE3D,SAAS,sBAAsB;IAC7B,MAAM,GAAG,GAAG,IAAI,CAAC,GAAG,EAAE,CAAC;IACvB,IAAI,OAAO,GAAG,CAAC,CAAC;IAEhB,KAAK,MAAM,CAAC,QAAQ,EAAE,OAAO,CAAC,IAAI,QAAQ,EAAE,CAAC;QAC3C,MAAM,YAAY,GAAG,OAAO,CAAC,cAAc,IAAI,OAAO,CAAC,SAAS,CAAC;QACjE,IAAI,GAAG,GAAG,YAAY,GAAG,cAAc;YAAE,SAAS;QAElD,OAAO,CAAC,GAAG,CACT,iDAAiD,QAAQ,eAAe,OAAO,CAAC,SAAS,kBAAkB,IAAI,IAAI,CAAC,YAAY,CAAC,CAAC,WAAW,EAAE,EAAE,CAClJ,CAAC;QAEF,+EAA+E;QAC/E,MAAM,UAAU,GAAG,OAAO,CAAC,OAAO,CAAC,OAAO,CAAC,KAAK,EAAE,GAAG,CAAC,CAAC;QACvD,MAAM,WAAW,GAAG,IAAI,CAAC,IAAI,CAC3B,EAAE,CAAC,OAAO,EAAE,EACZ,SAAS,EACT,UAAU,EACV,UAAU,EACV,GAAG,OAAO,CAAC,SAAS,QAAQ,CAC7B,CAAC;QACF,IAAI,CAAC;YACH,EAAE,CAAC,UAAU,CAAC,WAAW,CAAC,CAAC;YAC3B,OAAO,CAAC,GAAG,CAAC,oCAAoC,WAAW,EAAE,CAAC,CAAC;QACjE,CAAC;QAAC,MAAM,CAAC;YACP,qBAAqB;QACvB,CAAC;QAED,4BAA4B;QAC5B,IAAI,CAAC;YACH,EAAE,CAAC,MAAM,CAAC,OAAO,CAAC,OAAO,EAAE,EAAE,SAAS,EAAE,IAAI,EAAE,KAAK,EAAE,IAAI,EAAE,CAAC,CAAC;YAC7D,OAAO,CAAC,GAAG,CAAC,+BAA+B,OAAO,CAAC,OAAO,EAAE,CAAC,CAAC;QAChE,CAAC;QAAC,MAAM,CAAC;YACP,0BAA0B;QAC5B,CAAC;QAED,QAAQ,CAAC,MAAM,CAAC,QAAQ,CAAC,CAAC;QAC1B,OAAO,EAAE,CAAC;IACZ,CAAC;IAED,IAAI,OAAO,GAAG,CAAC,EAAE,CAAC;QAChB,YAAY,EAAE,CAAC;QACf,OAAO,CAAC,GAAG,CAAC,sBAAsB,OAAO,mBAAmB,CAAC,CAAC;IAChE,CAAC;AACH,CAAC;AAED,MAAM,QAAQ,GAAG,YAAY,EAAE,CAAC;AAChC,sBAAsB,EAAE,CAAC;AAEzB,MAAM,UAAU,aAAa,CAC3B,QAAgB,EAChB,OAAe,EACf,YAAoB;IAEpB,MAAM,GAAG,GAAG,IAAI,CAAC,GAAG,EAAE,CAAC;IACvB,MAAM,OAAO,GAAY;QACvB,SAAS,EAAE,UAAU,EAAE;QACvB,OAAO;QACP,UAAU,EAAE,KAAK;QACjB,YAAY;QACZ,SAAS,EAAE,GAAG;QACd,cAAc,EAAE,GAAG;KACpB,CAAC;IACF,QAAQ,CAAC,GAAG,CAAC,QAAQ,EAAE,OAAO,CAAC,CAAC;IAChC,YAAY,EAAE,CAAC;IACf,OAAO,OAAO,CAAC;AACjB,CAAC;AAED,MAAM,UAAU,UAAU,CAAC,QAAgB;IACzC,OAAO,QAAQ,CAAC,GAAG,CAAC,QAAQ,CAAC,CAAC;AAChC,CAAC;AAED,SAAS,iBAAiB,CACxB,QAAgB,EAChB,KAA8B;IAE9B,QAAQ,QAAQ,EAAE,CAAC;QACjB,KAAK,MAAM;YACT,OAAO,WAAW,KAAK,CAAC,SAAS,CAAC,IAAI,EAAE,UAAU,CAAC;QACrD,KAAK,MAAM,CAAC;QACZ,KAAK,OAAO,CAAC;QACb,KAAK,MAAM;YACT,OAAO,WAAW,KAAK,CAAC,WAAW,CAAC,IAAI,KAAK,CAAC,MAAM,CAAC,IAAI,SAAS,IAAI,CAAC;QACzE;YACE,OAAO,WAAW,IAAI,CAAC,SAAS,CAAC,KAAK,EAAE,IAAI,EAAE,CAAC,CAAC,CAAC,KAAK,CAAC,CAAC,EAAE,GAAG,CAAC,UAAU,CAAC;IAC7E,CAAC;AACH,CAAC;AAED;;;;;GAKG;AACH,MAAM,CAAC,KAAK,SAAS,CAAC,CAAC,IAAI,CACzB,QAAgB,EAChB,WAAmB,EACnB,WAAsB,EACtB,OAAe,EACf,MAAyB;IAEzB,MAAM,OAAO,GAAG,QAAQ,CAAC,GAAG,CAAC,QAAQ,CAAC,CAAC;IACvC,IAAI,CAAC,OAAO,EAAE,CAAC;QACb,MAAM,IAAI,KAAK,CAAC,+BAA+B,QAAQ,EAAE,CAAC,CAAC;IAC7D,CAAC;IAED,2DAA2D;IAC3D,MAAM,gBAAgB,GAAG,IAAI,GAAG,CAAC;QAC/B,MAAM;QACN,MAAM;QACN,MAAM;QACN,WAAW;QACX,UAAU;QACV,OAAO;QACP,UAAU;QACV,kBAAkB;QAClB,iBAAiB;KAClB,CAAC,CAAC;IAEH,MAAM,qBAAqB,GAAG,CAAC,GAAG,EAAE,GAAG,IAAI,CAAC,CAAC,YAAY;IAEzD,6EAA6E;IAC7E,MAAM,UAAU,GAAe,KAAK,EAClC,QAAQ,EACR,KAAK,EACL,EAAE,MAAM,EAAE,EACiB,EAAE;QAC7B,kCAAkC;QAClC,IAAI,gBAAgB,CAAC,GAAG,CAAC,QAAQ,CAAC,EAAE,CAAC;YACnC,OAAO,CAAC,GAAG,CAAC,wBAAwB,QAAQ,EAAE,CAAC,CAAC;YAChD,OAAO,EAAE,QAAQ,EAAE,OAAO,EAAE,CAAC;QAC/B,CAAC;QAED,MAAM,MAAM,GAAG,UAAU,EAAE,CAAC;QAC5B,MAAM,WAAW,GAAG,iBAAiB,CAAC,QAAQ,EAAE,KAAK,CAAC,CAAC;QAEvD,OAAO,CAAC,GAAG,CAAC,qCAAqC,QAAQ,QAAQ,MAAM,EAAE,CAAC,CAAC;QAE3E,iDAAiD;QACjD,MAAM,OAAO,GAAG,MAAM,OAAO,CAAC,IAAI,CAAC;YACjC,iBAAiB,CACf,WAAW,EACX,OAAO,EACP,QAAQ,EACR,MAAM,EACN,QAAQ,EACR,WAAW,EACX,OAAO,CAAC,YAAY,CACrB;YACD,IAAI,OAAO,CAAU,CAAC,OAAO,EAAE,EAAE,CAC/B,UAAU,CAAC,GAAG,EAAE;gBACd,OAAO,CAAC,GAAG,CAAC,gCAAgC,MAAM,EAAE,CAAC,CAAC;gBACtD,OAAO,CAAC,KAAK,CAAC,CAAC;YACjB,CAAC,EAAE,qBAAqB,CAAC,CAC1B;SACF,CAAC,CAAC;QAEH,OAAO,CAAC,GAAG,CAAC,uBAAuB,OAAO,CAAC,CAAC,CAAC,UAAU,CAAC,CAAC,CAAC,QAAQ,UAAU,QAAQ,EAAE,CAAC,CAAC;QAExF,IAAI,OAAO,EAAE,CAAC;YACZ,OAAO,EAAE,QAAQ,EAAE,OAAO,EAAE,CAAC;QAC/B,CAAC;aAAM,CAAC;YACN,OAAO,EAAE,QAAQ,EAAE,MAAM,EAAE,OAAO,EAAE,oCAAoC,EAAE,CAAC;QAC7E,CAAC;IACH,CAAC,CAAC;IAEF,OAAO,CAAC,cAAc,GAAG,IAAI,CAAC,GAAG,EAAE,CAAC;IACpC,YAAY,EAAE,CAAC;IAEf,OAAO,CAAC,GAAG,CAAC,gCAAgC,OAAO,CAAC,OAAO,gBAAgB,OAAO,CAAC,UAAU,EAAE,CAAC,CAAC;IAEjG,+EAA+E;IAC/E,MAAM,SAAS,GAAG,MAAM,IAAI,MAAM,CAAC,MAAM,GAAG,CAAC,CAAC;IAC9C,MAAM,MAAM,GAA2C,SAAS;QAC9D,CAAC,CAAC,CAAC,KAAK,SAAS,CAAC;YACd,MAAM;gBACJ,IAAI,EAAE,MAAM;gBACZ,OAAO,EAAE;oBACP,IAAI,EAAE,MAAM;oBACZ,OAAO,EAAE;wBACP,GAAG,MAAM,CAAC,GAAG,CAAC,CAAC,GAAG,EAAE,EAAE,CAAC,CAAC;4BACtB,IAAI,EAAE,OAAgB;4BACtB,MAAM,EAAE;gCACN,IAAI,EAAE,QAAiB;gCACvB,UAAU,EAAE,GAAG,CAAC,SAAS;gCACzB,IAAI,EAAE,GAAG,CAAC,MAAM;6BACjB;yBACF,CAAC,CAAC;wBACH,EAAE,IAAI,EAAE,MAAe,EAAE,IAAI,EAAE,WAAW,EAAE;qBAC7C;iBACF;gBACD,kBAAkB,EAAE,IAAI;gBACxB,UAAU,EAAE,OAAO,CAAC,SAAS;aACL,CAAC;QAC7B,CAAC,CAAC,EAAE;QACN,CAAC,CAAC,WAAW,CAAC;IAEhB,IAAI,CAAC;QACH,MAAM,CAAC,GAAG,KAAK,CAAC;YACd,MAAM;YACN,OAAO,EAAE;gBACP,GAAG,EAAE,OAAO,CAAC,OAAO;gBACpB,UAAU;gBACV,cAAc,EAAE,mBAAmB;gBACnC,+BAA+B,EAAE,IAAI;gBACrC,sBAAsB,EAAE,IAAI;gBAC5B,YAAY,EAAE;oBACZ,IAAI,EAAE,QAAQ;oBACd,MAAM,EAAE,aAAa;oBACrB,MAAM,EAAE,mBAAmB;iBAC5B;gBACD,GAAG,CAAC,UAAU,IAAI,EAAE,UAAU,EAAE,CAAC;gBACjC,GAAG,CAAC,OAAO,CAAC,UAAU;oBACpB,CAAC,CAAC,EAAE,MAAM,EAAE,OAAO,CAAC,SAAS,EAAE;oBAC/B,CAAC,CAAC,EAAE,SAAS,EAAE,OAAO,CAAC,SAAS,EAAE,CAAC;aACtC;SACF,CAAC,CAAC;QAEH,OAAO,CAAC,UAAU,GAAG,IAAI,CAAC;QAC1B,YAAY,EAAE,CAAC;QAEf,IAAI,KAAK,EAAE,MAAM,OAAO,IAAI,CAAC,EAAE,CAAC;YAC9B,MAAM,OAAO,GAAG,OAAO,CAAC,IAAc,CAAC;YAEvC,kCAAkC;YAClC,IAAI,OAAO,KAAK,cAAc,EAAE,CAAC;gBAC/B,MAAM,OAAO,GAAG,OAEf,CAAC;gBACF,IAAI,OAAO,CAAC,KAAK,EAAE,IAAI,KAAK,qBAAqB,EAAE,CAAC;oBAClD,IACE,OAAO,CAAC,KAAK,CAAC,KAAK,EAAE,IAAI,KAAK,YAAY;wBAC1C,OAAO,CAAC,KAAK,CAAC,KAAK,CAAC,IAAI,EACxB,CAAC;wBACD,MAAM,EAAE,IAAI,EAAE,MAAM,EAAE,OAAO,EAAE,OAAO,CAAC,KAAK,CAAC,KAAK,CAAC,IAAI,EAAE,CAAC;oBAC5D,CAAC;gBACH,CAAC;gBACD,SAAS;YACX,CAAC;YAED,eAAe;YACf,IAAI,OAAO,KAAK,QAAQ,EAAE,CAAC;gBACzB,MAAM,MAAM,GAAG,OAGd,CAAC;gBACF,OAAO,CAAC,GAAG,CACT,4BAA4B,MAAM,CAAC,OAAO,SAAS,MAAM,CAAC,MAAM,EAAE,MAAM,IAAI,CAAC,EAAE,CAChF,CAAC;gBACF,SAAS;YACX,CAAC;QACH,CAAC;IACH,CAAC;IAAC,OAAO,KAAK,EAAE,CAAC;QACf,MAAM,MAAM,GAAG,KAAK,YAAY,KAAK,CAAC,CAAC,CAAC,KAAK,CAAC,OAAO,CAAC,CAAC,CAAC,MAAM,CAAC,KAAK,CAAC,CAAC;QACtE,OAAO,CAAC,KAAK,CAAC,mBAAmB,MAAM,EAAE,CAAC,CAAC;QAC3C,MAAM,EAAE,IAAI,EAAE,OAAO,EAAE,OAAO,EAAE,MAAM,EAAE,CAAC;IAC3C,CAAC;AACH,CAAC"}
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
import type { WebClient } from "@slack/web-api";
|
|
2
|
+
export interface SlackImageFile {
|
|
3
|
+
base64: string;
|
|
4
|
+
mediaType: "image/jpeg" | "image/png" | "image/gif" | "image/webp";
|
|
5
|
+
}
|
|
6
|
+
/**
|
|
7
|
+
* Download image files from a Slack message's `files` array.
|
|
8
|
+
*
|
|
9
|
+
* Only images with supported media types are returned; non-image files are skipped.
|
|
10
|
+
*/
|
|
11
|
+
export declare function downloadSlackImages(client: WebClient, files: Array<{
|
|
12
|
+
url_private_download?: string;
|
|
13
|
+
url_private?: string;
|
|
14
|
+
mimetype?: string;
|
|
15
|
+
}>): Promise<SlackImageFile[]>;
|
|
16
|
+
//# sourceMappingURL=slack-files.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"slack-files.d.ts","sourceRoot":"","sources":["../../src/utils/slack-files.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,SAAS,EAAE,MAAM,gBAAgB,CAAC;AAEhD,MAAM,WAAW,cAAc;IAC7B,MAAM,EAAE,MAAM,CAAC;IACf,SAAS,EAAE,YAAY,GAAG,WAAW,GAAG,WAAW,GAAG,YAAY,CAAC;CACpE;AASD;;;;GAIG;AACH,wBAAsB,mBAAmB,CACvC,MAAM,EAAE,SAAS,EACjB,KAAK,EAAE,KAAK,CAAC;IAAE,oBAAoB,CAAC,EAAE,MAAM,CAAC;IAAC,WAAW,CAAC,EAAE,MAAM,CAAC;IAAC,QAAQ,CAAC,EAAE,MAAM,CAAA;CAAE,CAAC,GACvF,OAAO,CAAC,cAAc,EAAE,CAAC,CAkC3B"}
|
|
@@ -0,0 +1,44 @@
|
|
|
1
|
+
const SUPPORTED_TYPES = new Set([
|
|
2
|
+
"image/jpeg",
|
|
3
|
+
"image/png",
|
|
4
|
+
"image/gif",
|
|
5
|
+
"image/webp",
|
|
6
|
+
]);
|
|
7
|
+
/**
|
|
8
|
+
* Download image files from a Slack message's `files` array.
|
|
9
|
+
*
|
|
10
|
+
* Only images with supported media types are returned; non-image files are skipped.
|
|
11
|
+
*/
|
|
12
|
+
export async function downloadSlackImages(client, files) {
|
|
13
|
+
const token = client.token;
|
|
14
|
+
if (!token)
|
|
15
|
+
return [];
|
|
16
|
+
const results = [];
|
|
17
|
+
for (const file of files) {
|
|
18
|
+
const mime = file.mimetype;
|
|
19
|
+
if (!mime || !SUPPORTED_TYPES.has(mime))
|
|
20
|
+
continue;
|
|
21
|
+
const url = file.url_private_download ?? file.url_private;
|
|
22
|
+
if (!url)
|
|
23
|
+
continue;
|
|
24
|
+
try {
|
|
25
|
+
const resp = await fetch(url, {
|
|
26
|
+
headers: { Authorization: `Bearer ${token}` },
|
|
27
|
+
});
|
|
28
|
+
if (!resp.ok) {
|
|
29
|
+
console.warn(`[slack-files] failed to download ${url}: ${resp.status}`);
|
|
30
|
+
continue;
|
|
31
|
+
}
|
|
32
|
+
const buf = Buffer.from(await resp.arrayBuffer());
|
|
33
|
+
results.push({
|
|
34
|
+
base64: buf.toString("base64"),
|
|
35
|
+
mediaType: mime,
|
|
36
|
+
});
|
|
37
|
+
}
|
|
38
|
+
catch (err) {
|
|
39
|
+
console.warn(`[slack-files] error downloading file:`, err);
|
|
40
|
+
}
|
|
41
|
+
}
|
|
42
|
+
return results;
|
|
43
|
+
}
|
|
44
|
+
//# sourceMappingURL=slack-files.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"slack-files.js","sourceRoot":"","sources":["../../src/utils/slack-files.ts"],"names":[],"mappings":"AAOA,MAAM,eAAe,GAAG,IAAI,GAAG,CAAC;IAC9B,YAAY;IACZ,WAAW;IACX,WAAW;IACX,YAAY;CACb,CAAC,CAAC;AAEH;;;;GAIG;AACH,MAAM,CAAC,KAAK,UAAU,mBAAmB,CACvC,MAAiB,EACjB,KAAwF;IAExF,MAAM,KAAK,GAAI,MAAwC,CAAC,KAAK,CAAC;IAC9D,IAAI,CAAC,KAAK;QAAE,OAAO,EAAE,CAAC;IAEtB,MAAM,OAAO,GAAqB,EAAE,CAAC;IAErC,KAAK,MAAM,IAAI,IAAI,KAAK,EAAE,CAAC;QACzB,MAAM,IAAI,GAAG,IAAI,CAAC,QAAQ,CAAC;QAC3B,IAAI,CAAC,IAAI,IAAI,CAAC,eAAe,CAAC,GAAG,CAAC,IAAI,CAAC;YAAE,SAAS;QAElD,MAAM,GAAG,GAAG,IAAI,CAAC,oBAAoB,IAAI,IAAI,CAAC,WAAW,CAAC;QAC1D,IAAI,CAAC,GAAG;YAAE,SAAS;QAEnB,IAAI,CAAC;YACH,MAAM,IAAI,GAAG,MAAM,KAAK,CAAC,GAAG,EAAE;gBAC5B,OAAO,EAAE,EAAE,aAAa,EAAE,UAAU,KAAK,EAAE,EAAE;aAC9C,CAAC,CAAC;YAEH,IAAI,CAAC,IAAI,CAAC,EAAE,EAAE,CAAC;gBACb,OAAO,CAAC,IAAI,CAAC,oCAAoC,GAAG,KAAK,IAAI,CAAC,MAAM,EAAE,CAAC,CAAC;gBACxE,SAAS;YACX,CAAC;YAED,MAAM,GAAG,GAAG,MAAM,CAAC,IAAI,CAAC,MAAM,IAAI,CAAC,WAAW,EAAE,CAAC,CAAC;YAClD,OAAO,CAAC,IAAI,CAAC;gBACX,MAAM,EAAE,GAAG,CAAC,QAAQ,CAAC,QAAQ,CAAC;gBAC9B,SAAS,EAAE,IAAmC;aAC/C,CAAC,CAAC;QACL,CAAC;QAAC,OAAO,GAAG,EAAE,CAAC;YACb,OAAO,CAAC,IAAI,CAAC,uCAAuC,EAAE,GAAG,CAAC,CAAC;QAC7D,CAAC;IACH,CAAC;IAED,OAAO,OAAO,CAAC;AACjB,CAAC"}
|
package/package.json
CHANGED
package/src/handlers/mention.ts
CHANGED
|
@@ -3,6 +3,7 @@ import { removeMention, extractGithubRepo, type GithubRepo } from "../utils/pars
|
|
|
3
3
|
import { createSession, getSession, chat } from "../services/claude.js";
|
|
4
4
|
import { prepareWorkDir, checkRepoExists, createGithubRepo } from "../services/git.js";
|
|
5
5
|
import { postStreamingReply } from "../services/slack-messenger.js";
|
|
6
|
+
import { downloadSlackImages, type SlackImageFile } from "../utils/slack-files.js";
|
|
6
7
|
import { CLAUDE_ICON_URL } from "../constants.js";
|
|
7
8
|
import fs from "node:fs/promises";
|
|
8
9
|
import os from "node:os";
|
|
@@ -58,10 +59,14 @@ export async function handleMention(
|
|
|
58
59
|
const { event, client } = args;
|
|
59
60
|
const { text, channel, ts, thread_ts } = event;
|
|
60
61
|
const authorUserId = event.user ?? "unknown";
|
|
62
|
+
const files = "files" in event ? (event as unknown as { files?: Array<{ url_private_download?: string; url_private?: string; mimetype?: string }> }).files : undefined;
|
|
63
|
+
|
|
64
|
+
// Download any image attachments
|
|
65
|
+
const images = files?.length ? await downloadSlackImages(client, files) : [];
|
|
61
66
|
|
|
62
67
|
// If the mention is inside an existing thread, treat it as a thread reply
|
|
63
68
|
if (thread_ts) {
|
|
64
|
-
await handleThreadedMention(client, channel, thread_ts, text, authorUserId);
|
|
69
|
+
await handleThreadedMention(client, channel, thread_ts, text, authorUserId, images);
|
|
65
70
|
return;
|
|
66
71
|
}
|
|
67
72
|
|
|
@@ -140,10 +145,10 @@ export async function handleMention(
|
|
|
140
145
|
}
|
|
141
146
|
|
|
142
147
|
// Repo exists — proceed with normal setup
|
|
143
|
-
await startSessionWithRepo(client, channel, threadTs, repoInfo, cleanText, authorUserId);
|
|
148
|
+
await startSessionWithRepo(client, channel, threadTs, repoInfo, cleanText, authorUserId, images);
|
|
144
149
|
} else {
|
|
145
150
|
// No repo mentioned — use temp directory
|
|
146
|
-
await startSessionWithoutRepo(client, channel, threadTs, cleanText, authorUserId);
|
|
151
|
+
await startSessionWithoutRepo(client, channel, threadTs, cleanText, authorUserId, images);
|
|
147
152
|
}
|
|
148
153
|
}
|
|
149
154
|
|
|
@@ -156,7 +161,8 @@ export async function startSessionWithRepo(
|
|
|
156
161
|
threadTs: string,
|
|
157
162
|
repoInfo: GithubRepo,
|
|
158
163
|
cleanText: string,
|
|
159
|
-
authorUserId: string
|
|
164
|
+
authorUserId: string,
|
|
165
|
+
images?: SlackImageFile[]
|
|
160
166
|
): Promise<void> {
|
|
161
167
|
let workDir: string;
|
|
162
168
|
|
|
@@ -183,7 +189,7 @@ export async function startSessionWithRepo(
|
|
|
183
189
|
createSession(threadTs, workDir, authorUserId);
|
|
184
190
|
|
|
185
191
|
const userMessage = `[Working directory: ${workDir} — Repository: ${repoInfo.owner}/${repoInfo.repo}]\n\n${cleanText}`;
|
|
186
|
-
const stream = chat(threadTs, userMessage, client, channel);
|
|
192
|
+
const stream = chat(threadTs, userMessage, client, channel, images?.length ? images : undefined);
|
|
187
193
|
await postStreamingReply(client, channel, threadTs, stream);
|
|
188
194
|
}
|
|
189
195
|
|
|
@@ -195,12 +201,13 @@ export async function startSessionWithoutRepo(
|
|
|
195
201
|
channel: string,
|
|
196
202
|
threadTs: string,
|
|
197
203
|
cleanText: string,
|
|
198
|
-
authorUserId: string
|
|
204
|
+
authorUserId: string,
|
|
205
|
+
images?: SlackImageFile[]
|
|
199
206
|
): Promise<void> {
|
|
200
207
|
const workDir = await fs.mkdtemp(path.join(os.tmpdir(), "eniac-"));
|
|
201
208
|
createSession(threadTs, workDir, authorUserId);
|
|
202
209
|
|
|
203
|
-
const stream = chat(threadTs, cleanText, client, channel);
|
|
210
|
+
const stream = chat(threadTs, cleanText, client, channel, images?.length ? images : undefined);
|
|
204
211
|
await postStreamingReply(client, channel, threadTs, stream);
|
|
205
212
|
}
|
|
206
213
|
|
|
@@ -212,10 +219,11 @@ async function handleThreadedMention(
|
|
|
212
219
|
channel: string,
|
|
213
220
|
threadTs: string,
|
|
214
221
|
text: string,
|
|
215
|
-
authorUserId: string
|
|
222
|
+
authorUserId: string,
|
|
223
|
+
images?: SlackImageFile[]
|
|
216
224
|
): Promise<void> {
|
|
217
225
|
const cleanText = removeMention(text);
|
|
218
|
-
if (!cleanText) return;
|
|
226
|
+
if (!cleanText && (!images || images.length === 0)) return;
|
|
219
227
|
|
|
220
228
|
let session = getSession(threadTs);
|
|
221
229
|
|
|
@@ -224,6 +232,7 @@ async function handleThreadedMention(
|
|
|
224
232
|
session = createSession(threadTs, workDir, authorUserId);
|
|
225
233
|
}
|
|
226
234
|
|
|
227
|
-
const
|
|
235
|
+
const userMessage = cleanText || "이미지를 분석해주세요.";
|
|
236
|
+
const stream = chat(threadTs, userMessage, client, channel, images?.length ? images : undefined);
|
|
228
237
|
await postStreamingReply(client, channel, threadTs, stream);
|
|
229
238
|
}
|
package/src/handlers/thread.ts
CHANGED
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import type { AllMiddlewareArgs, SlackEventMiddlewareArgs } from "@slack/bolt";
|
|
2
2
|
import { getSession, chat } from "../services/claude.js";
|
|
3
3
|
import { postStreamingReply } from "../services/slack-messenger.js";
|
|
4
|
+
import { downloadSlackImages } from "../utils/slack-files.js";
|
|
4
5
|
|
|
5
6
|
/**
|
|
6
7
|
* Handle messages posted in threads where the bot has an active session.
|
|
@@ -38,6 +39,7 @@ export async function handleThreadMessage(
|
|
|
38
39
|
}
|
|
39
40
|
|
|
40
41
|
const text = "text" in event ? event.text : undefined;
|
|
42
|
+
const files = "files" in event ? (event as unknown as { files?: Array<{ url_private_download?: string; url_private?: string; mimetype?: string }> }).files : undefined;
|
|
41
43
|
const channel = event.channel;
|
|
42
44
|
|
|
43
45
|
// Only respond if we have an active session for this thread
|
|
@@ -47,14 +49,19 @@ export async function handleThreadMessage(
|
|
|
47
49
|
return;
|
|
48
50
|
}
|
|
49
51
|
|
|
50
|
-
console.log(`[thread] session found! sessionId=${session.sessionId}, text=${text?.slice(0, 50)}`);
|
|
52
|
+
console.log(`[thread] session found! sessionId=${session.sessionId}, text=${text?.slice(0, 50)}, files=${files?.length ?? 0}`);
|
|
51
53
|
|
|
52
|
-
|
|
54
|
+
// Download any image attachments
|
|
55
|
+
const images = files?.length ? await downloadSlackImages(client, files) : [];
|
|
56
|
+
|
|
57
|
+
const hasText = text && text.trim().length > 0;
|
|
58
|
+
if (!hasText && images.length === 0) return;
|
|
53
59
|
|
|
54
60
|
// Strip any bot mentions from the text (user might @mention the bot in-thread)
|
|
55
|
-
const cleanText = text.replace(/<@[A-Z0-9]+>/g, "").trim();
|
|
56
|
-
if (!cleanText) return;
|
|
61
|
+
const cleanText = (text ?? "").replace(/<@[A-Z0-9]+>/g, "").trim();
|
|
62
|
+
if (!cleanText && images.length === 0) return;
|
|
57
63
|
|
|
58
|
-
const
|
|
64
|
+
const userMessage = cleanText || "이미지를 분석해주세요.";
|
|
65
|
+
const stream = chat(threadTs, userMessage, client, channel, images.length > 0 ? images : undefined);
|
|
59
66
|
await postStreamingReply(client, channel, threadTs, stream);
|
|
60
67
|
}
|
package/src/services/claude.ts
CHANGED
|
@@ -1,10 +1,12 @@
|
|
|
1
1
|
import { query } from "@anthropic-ai/claude-agent-sdk";
|
|
2
2
|
import type {
|
|
3
3
|
SDKMessage,
|
|
4
|
+
SDKUserMessage,
|
|
4
5
|
CanUseTool,
|
|
5
6
|
PermissionResult,
|
|
6
7
|
McpServerConfig,
|
|
7
8
|
} from "@anthropic-ai/claude-agent-sdk";
|
|
9
|
+
import type { SlackImageFile } from "../utils/slack-files.js";
|
|
8
10
|
import { randomUUID } from "node:crypto";
|
|
9
11
|
import fs from "node:fs";
|
|
10
12
|
import path from "node:path";
|
|
@@ -220,7 +222,8 @@ export async function* chat(
|
|
|
220
222
|
threadTs: string,
|
|
221
223
|
userMessage: string,
|
|
222
224
|
slackClient: WebClient,
|
|
223
|
-
channel: string
|
|
225
|
+
channel: string,
|
|
226
|
+
images?: SlackImageFile[]
|
|
224
227
|
): AsyncGenerator<ChatEvent, void, unknown> {
|
|
225
228
|
const session = sessions.get(threadTs);
|
|
226
229
|
if (!session) {
|
|
@@ -292,9 +295,35 @@ export async function* chat(
|
|
|
292
295
|
|
|
293
296
|
console.log(`[claude] starting query, cwd=${session.workDir}, hasStarted=${session.hasStarted}`);
|
|
294
297
|
|
|
298
|
+
// Build prompt: plain string when no images, SDKUserMessage iterable otherwise
|
|
299
|
+
const hasImages = images && images.length > 0;
|
|
300
|
+
const prompt: string | AsyncIterable<SDKUserMessage> = hasImages
|
|
301
|
+
? (async function* () {
|
|
302
|
+
yield {
|
|
303
|
+
type: "user",
|
|
304
|
+
message: {
|
|
305
|
+
role: "user",
|
|
306
|
+
content: [
|
|
307
|
+
...images.map((img) => ({
|
|
308
|
+
type: "image" as const,
|
|
309
|
+
source: {
|
|
310
|
+
type: "base64" as const,
|
|
311
|
+
media_type: img.mediaType,
|
|
312
|
+
data: img.base64,
|
|
313
|
+
},
|
|
314
|
+
})),
|
|
315
|
+
{ type: "text" as const, text: userMessage },
|
|
316
|
+
],
|
|
317
|
+
},
|
|
318
|
+
parent_tool_use_id: null,
|
|
319
|
+
session_id: session.sessionId,
|
|
320
|
+
} satisfies SDKUserMessage;
|
|
321
|
+
})()
|
|
322
|
+
: userMessage;
|
|
323
|
+
|
|
295
324
|
try {
|
|
296
325
|
const q = query({
|
|
297
|
-
prompt
|
|
326
|
+
prompt,
|
|
298
327
|
options: {
|
|
299
328
|
cwd: session.workDir,
|
|
300
329
|
canUseTool,
|
|
@@ -0,0 +1,57 @@
|
|
|
1
|
+
import type { WebClient } from "@slack/web-api";
|
|
2
|
+
|
|
3
|
+
export interface SlackImageFile {
|
|
4
|
+
base64: string;
|
|
5
|
+
mediaType: "image/jpeg" | "image/png" | "image/gif" | "image/webp";
|
|
6
|
+
}
|
|
7
|
+
|
|
8
|
+
const SUPPORTED_TYPES = new Set([
|
|
9
|
+
"image/jpeg",
|
|
10
|
+
"image/png",
|
|
11
|
+
"image/gif",
|
|
12
|
+
"image/webp",
|
|
13
|
+
]);
|
|
14
|
+
|
|
15
|
+
/**
|
|
16
|
+
* Download image files from a Slack message's `files` array.
|
|
17
|
+
*
|
|
18
|
+
* Only images with supported media types are returned; non-image files are skipped.
|
|
19
|
+
*/
|
|
20
|
+
export async function downloadSlackImages(
|
|
21
|
+
client: WebClient,
|
|
22
|
+
files: Array<{ url_private_download?: string; url_private?: string; mimetype?: string }>
|
|
23
|
+
): Promise<SlackImageFile[]> {
|
|
24
|
+
const token = (client as unknown as { token?: string }).token;
|
|
25
|
+
if (!token) return [];
|
|
26
|
+
|
|
27
|
+
const results: SlackImageFile[] = [];
|
|
28
|
+
|
|
29
|
+
for (const file of files) {
|
|
30
|
+
const mime = file.mimetype;
|
|
31
|
+
if (!mime || !SUPPORTED_TYPES.has(mime)) continue;
|
|
32
|
+
|
|
33
|
+
const url = file.url_private_download ?? file.url_private;
|
|
34
|
+
if (!url) continue;
|
|
35
|
+
|
|
36
|
+
try {
|
|
37
|
+
const resp = await fetch(url, {
|
|
38
|
+
headers: { Authorization: `Bearer ${token}` },
|
|
39
|
+
});
|
|
40
|
+
|
|
41
|
+
if (!resp.ok) {
|
|
42
|
+
console.warn(`[slack-files] failed to download ${url}: ${resp.status}`);
|
|
43
|
+
continue;
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
const buf = Buffer.from(await resp.arrayBuffer());
|
|
47
|
+
results.push({
|
|
48
|
+
base64: buf.toString("base64"),
|
|
49
|
+
mediaType: mime as SlackImageFile["mediaType"],
|
|
50
|
+
});
|
|
51
|
+
} catch (err) {
|
|
52
|
+
console.warn(`[slack-files] error downloading file:`, err);
|
|
53
|
+
}
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
return results;
|
|
57
|
+
}
|