sparkecoder 0.1.140 → 0.1.141
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/agent/index.js +554 -4
- package/dist/agent/index.js.map +1 -1
- package/dist/cli.js +628 -274
- package/dist/cli.js.map +1 -1
- package/dist/index.js +624 -272
- package/dist/index.js.map +1 -1
- package/dist/server/index.js +624 -272
- package/dist/server/index.js.map +1 -1
- package/dist/tools/index.js.map +1 -1
- package/package.json +1 -1
- package/web/.next/BUILD_ID +1 -1
- package/web/.next/standalone/web/.next/BUILD_ID +1 -1
- package/web/.next/standalone/web/.next/build-manifest.json +2 -2
- package/web/.next/standalone/web/.next/prerender-manifest.json +3 -3
- package/web/.next/standalone/web/.next/server/app/(main)/settings/page_client-reference-manifest.js +1 -1
- package/web/.next/standalone/web/.next/server/app/_global-error.html +2 -2
- package/web/.next/standalone/web/.next/server/app/_global-error.rsc +1 -1
- package/web/.next/standalone/web/.next/server/app/_global-error.segments/__PAGE__.segment.rsc +1 -1
- package/web/.next/standalone/web/.next/server/app/_global-error.segments/_full.segment.rsc +1 -1
- package/web/.next/standalone/web/.next/server/app/_global-error.segments/_head.segment.rsc +1 -1
- package/web/.next/standalone/web/.next/server/app/_global-error.segments/_index.segment.rsc +1 -1
- package/web/.next/standalone/web/.next/server/app/_global-error.segments/_tree.segment.rsc +1 -1
- package/web/.next/standalone/web/.next/server/app/_not-found.html +1 -1
- package/web/.next/standalone/web/.next/server/app/_not-found.rsc +1 -1
- package/web/.next/standalone/web/.next/server/app/_not-found.segments/_full.segment.rsc +1 -1
- package/web/.next/standalone/web/.next/server/app/_not-found.segments/_head.segment.rsc +1 -1
- package/web/.next/standalone/web/.next/server/app/_not-found.segments/_index.segment.rsc +1 -1
- package/web/.next/standalone/web/.next/server/app/_not-found.segments/_not-found/__PAGE__.segment.rsc +1 -1
- package/web/.next/standalone/web/.next/server/app/_not-found.segments/_not-found.segment.rsc +1 -1
- package/web/.next/standalone/web/.next/server/app/_not-found.segments/_tree.segment.rsc +1 -1
- package/web/.next/standalone/web/.next/server/app/agents.html +1 -1
- package/web/.next/standalone/web/.next/server/app/agents.rsc +1 -1
- package/web/.next/standalone/web/.next/server/app/agents.segments/!KG1haW4p/agents/__PAGE__.segment.rsc +1 -1
- package/web/.next/standalone/web/.next/server/app/agents.segments/!KG1haW4p/agents.segment.rsc +1 -1
- package/web/.next/standalone/web/.next/server/app/agents.segments/!KG1haW4p.segment.rsc +1 -1
- package/web/.next/standalone/web/.next/server/app/agents.segments/_full.segment.rsc +1 -1
- package/web/.next/standalone/web/.next/server/app/agents.segments/_head.segment.rsc +1 -1
- package/web/.next/standalone/web/.next/server/app/agents.segments/_index.segment.rsc +1 -1
- package/web/.next/standalone/web/.next/server/app/agents.segments/_tree.segment.rsc +1 -1
- package/web/.next/standalone/web/.next/server/app/docs/installation.html +2 -2
- package/web/.next/standalone/web/.next/server/app/docs/installation.rsc +1 -1
- package/web/.next/standalone/web/.next/server/app/docs/installation.segments/_full.segment.rsc +1 -1
- package/web/.next/standalone/web/.next/server/app/docs/installation.segments/_head.segment.rsc +1 -1
- package/web/.next/standalone/web/.next/server/app/docs/installation.segments/_index.segment.rsc +1 -1
- package/web/.next/standalone/web/.next/server/app/docs/installation.segments/_tree.segment.rsc +1 -1
- package/web/.next/standalone/web/.next/server/app/docs/installation.segments/docs/installation/__PAGE__.segment.rsc +1 -1
- package/web/.next/standalone/web/.next/server/app/docs/installation.segments/docs/installation.segment.rsc +1 -1
- package/web/.next/standalone/web/.next/server/app/docs/installation.segments/docs.segment.rsc +1 -1
- package/web/.next/standalone/web/.next/server/app/docs/skills.html +2 -2
- package/web/.next/standalone/web/.next/server/app/docs/skills.rsc +1 -1
- package/web/.next/standalone/web/.next/server/app/docs/skills.segments/_full.segment.rsc +1 -1
- package/web/.next/standalone/web/.next/server/app/docs/skills.segments/_head.segment.rsc +1 -1
- package/web/.next/standalone/web/.next/server/app/docs/skills.segments/_index.segment.rsc +1 -1
- package/web/.next/standalone/web/.next/server/app/docs/skills.segments/_tree.segment.rsc +1 -1
- package/web/.next/standalone/web/.next/server/app/docs/skills.segments/docs/skills/__PAGE__.segment.rsc +1 -1
- package/web/.next/standalone/web/.next/server/app/docs/skills.segments/docs/skills.segment.rsc +1 -1
- package/web/.next/standalone/web/.next/server/app/docs/skills.segments/docs.segment.rsc +1 -1
- package/web/.next/standalone/web/.next/server/app/docs/tools.html +2 -2
- package/web/.next/standalone/web/.next/server/app/docs/tools.rsc +1 -1
- package/web/.next/standalone/web/.next/server/app/docs/tools.segments/_full.segment.rsc +1 -1
- package/web/.next/standalone/web/.next/server/app/docs/tools.segments/_head.segment.rsc +1 -1
- package/web/.next/standalone/web/.next/server/app/docs/tools.segments/_index.segment.rsc +1 -1
- package/web/.next/standalone/web/.next/server/app/docs/tools.segments/_tree.segment.rsc +1 -1
- package/web/.next/standalone/web/.next/server/app/docs/tools.segments/docs/tools/__PAGE__.segment.rsc +1 -1
- package/web/.next/standalone/web/.next/server/app/docs/tools.segments/docs/tools.segment.rsc +1 -1
- package/web/.next/standalone/web/.next/server/app/docs/tools.segments/docs.segment.rsc +1 -1
- package/web/.next/standalone/web/.next/server/app/docs.html +2 -2
- package/web/.next/standalone/web/.next/server/app/docs.rsc +1 -1
- package/web/.next/standalone/web/.next/server/app/docs.segments/_full.segment.rsc +1 -1
- package/web/.next/standalone/web/.next/server/app/docs.segments/_head.segment.rsc +1 -1
- package/web/.next/standalone/web/.next/server/app/docs.segments/_index.segment.rsc +1 -1
- package/web/.next/standalone/web/.next/server/app/docs.segments/_tree.segment.rsc +1 -1
- package/web/.next/standalone/web/.next/server/app/docs.segments/docs/__PAGE__.segment.rsc +1 -1
- package/web/.next/standalone/web/.next/server/app/docs.segments/docs.segment.rsc +1 -1
- package/web/.next/standalone/web/.next/server/app/index.html +1 -1
- package/web/.next/standalone/web/.next/server/app/index.rsc +1 -1
- package/web/.next/standalone/web/.next/server/app/index.segments/!KG1haW4p/__PAGE__.segment.rsc +1 -1
- package/web/.next/standalone/web/.next/server/app/index.segments/!KG1haW4p.segment.rsc +1 -1
- package/web/.next/standalone/web/.next/server/app/index.segments/_full.segment.rsc +1 -1
- package/web/.next/standalone/web/.next/server/app/index.segments/_head.segment.rsc +1 -1
- package/web/.next/standalone/web/.next/server/app/index.segments/_index.segment.rsc +1 -1
- package/web/.next/standalone/web/.next/server/app/index.segments/_tree.segment.rsc +1 -1
- package/web/.next/standalone/web/.next/server/app/settings.html +1 -1
- package/web/.next/standalone/web/.next/server/app/settings.rsc +2 -2
- package/web/.next/standalone/web/.next/server/app/settings.segments/!KG1haW4p/settings/__PAGE__.segment.rsc +2 -2
- package/web/.next/standalone/web/.next/server/app/settings.segments/!KG1haW4p/settings.segment.rsc +1 -1
- package/web/.next/standalone/web/.next/server/app/settings.segments/!KG1haW4p.segment.rsc +1 -1
- package/web/.next/standalone/web/.next/server/app/settings.segments/_full.segment.rsc +2 -2
- package/web/.next/standalone/web/.next/server/app/settings.segments/_head.segment.rsc +1 -1
- package/web/.next/standalone/web/.next/server/app/settings.segments/_index.segment.rsc +1 -1
- package/web/.next/standalone/web/.next/server/app/settings.segments/_tree.segment.rsc +1 -1
- package/web/.next/standalone/web/.next/server/chunks/ssr/web_src_app_(main)_settings_page_tsx_eb320e07._.js +1 -1
- package/web/.next/standalone/web/.next/server/pages/404.html +1 -1
- package/web/.next/standalone/web/.next/server/pages/500.html +2 -2
- package/web/.next/standalone/web/.next/server/server-reference-manifest.js +1 -1
- package/web/.next/standalone/web/.next/server/server-reference-manifest.json +1 -1
- package/web/.next/standalone/web/.next/static/chunks/001c7ddc8dad6764.js +3 -0
- package/web/.next/standalone/web/.next/static/static/chunks/001c7ddc8dad6764.js +3 -0
- package/web/.next/standalone/web/runtime-config.json +2 -2
- package/web/.next/standalone/web/src/app/(main)/settings/page.tsx +14 -3
- package/web/.next/static/chunks/001c7ddc8dad6764.js +3 -0
- package/web/.next/standalone/web/.next/static/chunks/20ca4e35e9bb3e94.js +0 -3
- package/web/.next/standalone/web/.next/static/static/chunks/20ca4e35e9bb3e94.js +0 -3
- package/web/.next/static/chunks/20ca4e35e9bb3e94.js +0 -3
- /package/web/.next/standalone/web/.next/static/{QkKMkVPV-LLRD2i9PBP_Y → T0ihp-rxOYsKtonqcYLfo}/_buildManifest.js +0 -0
- /package/web/.next/standalone/web/.next/static/{QkKMkVPV-LLRD2i9PBP_Y → T0ihp-rxOYsKtonqcYLfo}/_clientMiddlewareManifest.json +0 -0
- /package/web/.next/standalone/web/.next/static/{QkKMkVPV-LLRD2i9PBP_Y → T0ihp-rxOYsKtonqcYLfo}/_ssgManifest.js +0 -0
- /package/web/.next/standalone/web/.next/static/static/{QkKMkVPV-LLRD2i9PBP_Y → T0ihp-rxOYsKtonqcYLfo}/_buildManifest.js +0 -0
- /package/web/.next/standalone/web/.next/static/static/{QkKMkVPV-LLRD2i9PBP_Y → T0ihp-rxOYsKtonqcYLfo}/_clientMiddlewareManifest.json +0 -0
- /package/web/.next/standalone/web/.next/static/static/{QkKMkVPV-LLRD2i9PBP_Y → T0ihp-rxOYsKtonqcYLfo}/_ssgManifest.js +0 -0
- /package/web/.next/static/{QkKMkVPV-LLRD2i9PBP_Y → T0ihp-rxOYsKtonqcYLfo}/_buildManifest.js +0 -0
- /package/web/.next/static/{QkKMkVPV-LLRD2i9PBP_Y → T0ihp-rxOYsKtonqcYLfo}/_clientMiddlewareManifest.json +0 -0
- /package/web/.next/static/{QkKMkVPV-LLRD2i9PBP_Y → T0ihp-rxOYsKtonqcYLfo}/_ssgManifest.js +0 -0
package/dist/agent/index.js
CHANGED
|
@@ -1798,6 +1798,17 @@ function hookExit() {
|
|
|
1798
1798
|
process.once("SIGINT", flush2);
|
|
1799
1799
|
process.once("SIGTERM", flush2);
|
|
1800
1800
|
}
|
|
1801
|
+
function getCachedUserName(userId) {
|
|
1802
|
+
load();
|
|
1803
|
+
return userMap.get(userId);
|
|
1804
|
+
}
|
|
1805
|
+
function setCachedUserName(userId, entry2) {
|
|
1806
|
+
load();
|
|
1807
|
+
hookExit();
|
|
1808
|
+
userMap.set(userId, entry2);
|
|
1809
|
+
evictIfOversized(userMap, MAX_USERS);
|
|
1810
|
+
scheduleSave();
|
|
1811
|
+
}
|
|
1801
1812
|
function setCachedThreadOwnership(key2, entry2) {
|
|
1802
1813
|
load();
|
|
1803
1814
|
hookExit();
|
|
@@ -1805,7 +1816,7 @@ function setCachedThreadOwnership(key2, entry2) {
|
|
|
1805
1816
|
evictIfOversized(threadMap, MAX_THREADS);
|
|
1806
1817
|
scheduleSave();
|
|
1807
1818
|
}
|
|
1808
|
-
var FILENAME, FILE_VERSION, SAVE_DEBOUNCE_MS, MAX_THREADS, loaded, userMap, threadMap, dirty, saveTimer, exitHooked;
|
|
1819
|
+
var FILENAME, FILE_VERSION, SAVE_DEBOUNCE_MS, MAX_USERS, MAX_THREADS, loaded, userMap, threadMap, dirty, saveTimer, exitHooked;
|
|
1809
1820
|
var init_persistence = __esm({
|
|
1810
1821
|
"src/integrations/slack/persistence.ts"() {
|
|
1811
1822
|
"use strict";
|
|
@@ -1813,6 +1824,7 @@ var init_persistence = __esm({
|
|
|
1813
1824
|
FILENAME = "slack-cache.json";
|
|
1814
1825
|
FILE_VERSION = 1;
|
|
1815
1826
|
SAVE_DEBOUNCE_MS = 500;
|
|
1827
|
+
MAX_USERS = 5e3;
|
|
1816
1828
|
MAX_THREADS = 5e3;
|
|
1817
1829
|
loaded = false;
|
|
1818
1830
|
userMap = /* @__PURE__ */ new Map();
|
|
@@ -1936,6 +1948,58 @@ function getSlackAdapter() {
|
|
|
1936
1948
|
function isSlackConfigured() {
|
|
1937
1949
|
return readSlackConfig() !== void 0;
|
|
1938
1950
|
}
|
|
1951
|
+
function getSlackBotToken() {
|
|
1952
|
+
return readSlackConfig()?.botToken ?? null;
|
|
1953
|
+
}
|
|
1954
|
+
async function fetchSlackUserInfo(userId) {
|
|
1955
|
+
const token = getSlackBotToken();
|
|
1956
|
+
if (!token) return null;
|
|
1957
|
+
try {
|
|
1958
|
+
const res = await fetch(`https://slack.com/api/users.info?user=${encodeURIComponent(userId)}`, {
|
|
1959
|
+
headers: { Authorization: `Bearer ${token}` }
|
|
1960
|
+
});
|
|
1961
|
+
const data = await res.json().catch(() => ({}));
|
|
1962
|
+
if (!data?.ok) {
|
|
1963
|
+
console.warn(`[slack] users.info(${userId}) failed: ${data?.error || `HTTP ${res.status}`}`);
|
|
1964
|
+
return null;
|
|
1965
|
+
}
|
|
1966
|
+
const profile = data.user?.profile || {};
|
|
1967
|
+
const name = profile.display_name_normalized || profile.display_name || profile.real_name_normalized || profile.real_name || data.user?.real_name || data.user?.name || null;
|
|
1968
|
+
const realName = profile.real_name_normalized || profile.real_name || data.user?.real_name || null;
|
|
1969
|
+
const email = profile.email || null;
|
|
1970
|
+
return {
|
|
1971
|
+
name: name ? String(name) : null,
|
|
1972
|
+
realName: realName ? String(realName) : null,
|
|
1973
|
+
email: email ? String(email) : null
|
|
1974
|
+
};
|
|
1975
|
+
} catch (err) {
|
|
1976
|
+
console.warn(`[slack] users.info(${userId}) error:`, err?.message || err);
|
|
1977
|
+
return null;
|
|
1978
|
+
}
|
|
1979
|
+
}
|
|
1980
|
+
async function resolveSlackUserInfo(userId) {
|
|
1981
|
+
if (!userId) return null;
|
|
1982
|
+
const now = Date.now();
|
|
1983
|
+
const hit = getCachedUserName(userId);
|
|
1984
|
+
if (hit && hit.expiresAt > now) {
|
|
1985
|
+
return { name: hit.name, realName: hit.realName ?? null, email: hit.email ?? null };
|
|
1986
|
+
}
|
|
1987
|
+
const inflight = userInflight.get(userId);
|
|
1988
|
+
if (inflight) return inflight;
|
|
1989
|
+
const p = (async () => {
|
|
1990
|
+
const info = await fetchSlackUserInfo(userId);
|
|
1991
|
+
setCachedUserName(userId, {
|
|
1992
|
+
name: info?.name ?? null,
|
|
1993
|
+
realName: info?.realName ?? null,
|
|
1994
|
+
email: info?.email ?? null,
|
|
1995
|
+
expiresAt: now + (info?.name ? USER_TTL_MS : USER_FAIL_TTL_MS)
|
|
1996
|
+
});
|
|
1997
|
+
userInflight.delete(userId);
|
|
1998
|
+
return info;
|
|
1999
|
+
})();
|
|
2000
|
+
userInflight.set(userId, p);
|
|
2001
|
+
return p;
|
|
2002
|
+
}
|
|
1939
2003
|
function threadCacheKey(channel, threadTs) {
|
|
1940
2004
|
return `${channel}\u241F${threadTs}`;
|
|
1941
2005
|
}
|
|
@@ -1946,7 +2010,7 @@ function noteBotPostedInThread(channel, threadTs) {
|
|
|
1946
2010
|
expiresAt: Date.now() + THREAD_OWNED_TTL_MS
|
|
1947
2011
|
});
|
|
1948
2012
|
}
|
|
1949
|
-
var RESULT_REACTIONS, SLACK_FETCH_ATTEMPTS, SLACK_BACKOFF_BASE_MS, SLACK_BACKOFF_CAP_MS, REACTION_SOFT_ERRORS, USER_TTL_MS, USER_FAIL_TTL_MS, THREAD_OWNED_TTL_MS, THREAD_NEG_TTL_MS;
|
|
2013
|
+
var RESULT_REACTIONS, SLACK_FETCH_ATTEMPTS, SLACK_BACKOFF_BASE_MS, SLACK_BACKOFF_CAP_MS, REACTION_SOFT_ERRORS, USER_TTL_MS, USER_FAIL_TTL_MS, userInflight, THREAD_OWNED_TTL_MS, THREAD_NEG_TTL_MS;
|
|
1950
2014
|
var init_client2 = __esm({
|
|
1951
2015
|
"src/integrations/slack/client.ts"() {
|
|
1952
2016
|
"use strict";
|
|
@@ -1971,6 +2035,7 @@ var init_client2 = __esm({
|
|
|
1971
2035
|
]);
|
|
1972
2036
|
USER_TTL_MS = 60 * 60 * 1e3;
|
|
1973
2037
|
USER_FAIL_TTL_MS = 5 * 60 * 1e3;
|
|
2038
|
+
userInflight = /* @__PURE__ */ new Map();
|
|
1974
2039
|
THREAD_OWNED_TTL_MS = 60 * 60 * 1e3;
|
|
1975
2040
|
THREAD_NEG_TTL_MS = 5 * 60 * 1e3;
|
|
1976
2041
|
}
|
|
@@ -2278,7 +2343,7 @@ async function reconcileOnce(now = Date.now()) {
|
|
|
2278
2343
|
entry2.updatedAt = Date.now();
|
|
2279
2344
|
const nudged = {
|
|
2280
2345
|
...entry2.event,
|
|
2281
|
-
content: `[REPLAY attempt ${entry2.attempts}/${MAX_ATTEMPTS} \u2014 you received this but have not yet replied to it or marked it handled. Respond now on the originating channel; if it genuinely needs no reply,
|
|
2346
|
+
content: `[REPLAY attempt ${entry2.attempts}/${MAX_ATTEMPTS} \u2014 you received this but have not yet replied to it or marked it handled. Respond now on the originating channel; if it genuinely needs no substantive reply, post a brief acknowledgement.]
|
|
2282
2347
|
${entry2.event.content}`,
|
|
2283
2348
|
wake: "now"
|
|
2284
2349
|
};
|
|
@@ -2392,6 +2457,428 @@ var init_inbox_acks = __esm({
|
|
|
2392
2457
|
}
|
|
2393
2458
|
});
|
|
2394
2459
|
|
|
2460
|
+
// src/integrations/slack/files.ts
|
|
2461
|
+
var files_exports = {};
|
|
2462
|
+
__export(files_exports, {
|
|
2463
|
+
INGEST_TIMEOUT_MS: () => INGEST_TIMEOUT_MS,
|
|
2464
|
+
MAX_BYTES: () => MAX_BYTES,
|
|
2465
|
+
formatFileBlock: () => formatFileBlock,
|
|
2466
|
+
ingestSlackFiles: () => ingestSlackFiles
|
|
2467
|
+
});
|
|
2468
|
+
function inferFileName(file) {
|
|
2469
|
+
return typeof file.name === "string" && file.name || typeof file.title === "string" && file.title || `slack-file-${file.id}`;
|
|
2470
|
+
}
|
|
2471
|
+
function inferContentType(file) {
|
|
2472
|
+
if (typeof file.mimetype === "string" && file.mimetype) return file.mimetype;
|
|
2473
|
+
return "application/octet-stream";
|
|
2474
|
+
}
|
|
2475
|
+
function formatBytes(n) {
|
|
2476
|
+
if (!Number.isFinite(n) || n <= 0) return "?";
|
|
2477
|
+
if (n < 1024) return `${n} B`;
|
|
2478
|
+
if (n < 1024 * 1024) return `${(n / 1024).toFixed(1)} KB`;
|
|
2479
|
+
return `${(n / 1024 / 1024).toFixed(2)} MB`;
|
|
2480
|
+
}
|
|
2481
|
+
function sleep(ms) {
|
|
2482
|
+
return new Promise((resolve11) => setTimeout(resolve11, ms));
|
|
2483
|
+
}
|
|
2484
|
+
function imageMagic(bytes) {
|
|
2485
|
+
if (bytes.length < 12) return null;
|
|
2486
|
+
if (bytes[0] === 255 && bytes[1] === 216 && bytes[2] === 255) return "image/jpeg";
|
|
2487
|
+
if (bytes[0] === 137 && bytes[1] === 80 && bytes[2] === 78 && bytes[3] === 71) return "image/png";
|
|
2488
|
+
if (bytes[0] === 71 && bytes[1] === 73 && bytes[2] === 70 && bytes[3] === 56) return "image/gif";
|
|
2489
|
+
if (bytes[0] === 82 && bytes[1] === 73 && bytes[2] === 70 && bytes[3] === 70 && bytes[8] === 87 && bytes[9] === 69 && bytes[10] === 66 && bytes[11] === 80) return "image/webp";
|
|
2490
|
+
return null;
|
|
2491
|
+
}
|
|
2492
|
+
function requiresRasterMagic(contentType) {
|
|
2493
|
+
const normalized = contentType.toLowerCase().split(";", 1)[0].trim();
|
|
2494
|
+
return normalized === "image/jpeg" || normalized === "image/jpg" || normalized === "image/png" || normalized === "image/gif" || normalized === "image/webp";
|
|
2495
|
+
}
|
|
2496
|
+
function validateDownloadedBytes(bytes, declaredContentType) {
|
|
2497
|
+
if (!requiresRasterMagic(declaredContentType)) return null;
|
|
2498
|
+
const actual = imageMagic(bytes);
|
|
2499
|
+
if (!actual) return "invalid_image_bytes";
|
|
2500
|
+
return null;
|
|
2501
|
+
}
|
|
2502
|
+
async function fetchSlackPrivateFile(sourceUrl, botToken) {
|
|
2503
|
+
let lastError = "unknown";
|
|
2504
|
+
for (let attempt = 0; attempt < 3; attempt++) {
|
|
2505
|
+
try {
|
|
2506
|
+
const res = await fetch(sourceUrl, {
|
|
2507
|
+
headers: { Authorization: `Bearer ${botToken}` }
|
|
2508
|
+
});
|
|
2509
|
+
if (res.status === 429 || res.status >= 500) {
|
|
2510
|
+
lastError = `slack_fetch_${res.status}`;
|
|
2511
|
+
const retryAfter = Number(res.headers.get("retry-after"));
|
|
2512
|
+
const waitMs = Number.isFinite(retryAfter) && retryAfter > 0 ? Math.min(retryAfter * 1e3, 2e3) : 250 * (attempt + 1);
|
|
2513
|
+
if (attempt < 2) {
|
|
2514
|
+
await sleep(waitMs);
|
|
2515
|
+
continue;
|
|
2516
|
+
}
|
|
2517
|
+
}
|
|
2518
|
+
if (!res.ok) {
|
|
2519
|
+
return { ok: false, error: `slack_fetch_${res.status}` };
|
|
2520
|
+
}
|
|
2521
|
+
const contentLength = Number(res.headers.get("content-length"));
|
|
2522
|
+
if (Number.isFinite(contentLength) && contentLength > MAX_BYTES) {
|
|
2523
|
+
return { ok: false, error: "size_exceeded" };
|
|
2524
|
+
}
|
|
2525
|
+
const ab = await res.arrayBuffer();
|
|
2526
|
+
if (ab.byteLength > MAX_BYTES) {
|
|
2527
|
+
return { ok: false, error: "size_exceeded" };
|
|
2528
|
+
}
|
|
2529
|
+
return { ok: true, bytes: Buffer.from(ab) };
|
|
2530
|
+
} catch (err) {
|
|
2531
|
+
lastError = `slack_fetch_error:${err?.message || "unknown"}`;
|
|
2532
|
+
if (attempt < 2) {
|
|
2533
|
+
await sleep(250 * (attempt + 1));
|
|
2534
|
+
continue;
|
|
2535
|
+
}
|
|
2536
|
+
}
|
|
2537
|
+
}
|
|
2538
|
+
return { ok: false, error: lastError };
|
|
2539
|
+
}
|
|
2540
|
+
function withTimeout(p, ms, label) {
|
|
2541
|
+
return new Promise((resolve11, reject) => {
|
|
2542
|
+
const t = setTimeout(() => reject(new Error(`${label}_timeout`)), ms);
|
|
2543
|
+
p.then(
|
|
2544
|
+
(v) => {
|
|
2545
|
+
clearTimeout(t);
|
|
2546
|
+
resolve11(v);
|
|
2547
|
+
},
|
|
2548
|
+
(e) => {
|
|
2549
|
+
clearTimeout(t);
|
|
2550
|
+
reject(e);
|
|
2551
|
+
}
|
|
2552
|
+
);
|
|
2553
|
+
});
|
|
2554
|
+
}
|
|
2555
|
+
async function ingestOne(file, sessionId, botToken) {
|
|
2556
|
+
const fileName = inferFileName(file);
|
|
2557
|
+
const contentType = inferContentType(file);
|
|
2558
|
+
const declaredSize = typeof file.size === "number" ? file.size : 0;
|
|
2559
|
+
const base = {
|
|
2560
|
+
slackFileId: file.id,
|
|
2561
|
+
fileName,
|
|
2562
|
+
contentType,
|
|
2563
|
+
sizeBytes: declaredSize
|
|
2564
|
+
};
|
|
2565
|
+
const sourceUrl = file.url_private_download || file.url_private;
|
|
2566
|
+
if (!sourceUrl || typeof sourceUrl !== "string") {
|
|
2567
|
+
return { ...base, shortUrl: null, error: "no_source_url" };
|
|
2568
|
+
}
|
|
2569
|
+
if (declaredSize > MAX_BYTES) {
|
|
2570
|
+
return { ...base, shortUrl: null, error: "size_exceeded" };
|
|
2571
|
+
}
|
|
2572
|
+
let bytes;
|
|
2573
|
+
const fetched = await fetchSlackPrivateFile(sourceUrl, botToken);
|
|
2574
|
+
if (!fetched.ok) {
|
|
2575
|
+
return { ...base, shortUrl: null, error: fetched.error };
|
|
2576
|
+
}
|
|
2577
|
+
bytes = fetched.bytes;
|
|
2578
|
+
const byteError = validateDownloadedBytes(bytes, contentType);
|
|
2579
|
+
if (byteError) {
|
|
2580
|
+
console.warn(
|
|
2581
|
+
`[slack-files] refusing to upload ${fileName}: Slack metadata says ${contentType}, but downloaded bytes are not a supported image (${bytes.slice(0, 32).toString("utf8").replace(/\s+/g, " ").slice(0, 32)})`
|
|
2582
|
+
);
|
|
2583
|
+
return { ...base, sizeBytes: bytes.length, shortUrl: null, error: byteError };
|
|
2584
|
+
}
|
|
2585
|
+
const { storageQueries: storageQueries2 } = await Promise.resolve().then(() => (init_remote(), remote_exports));
|
|
2586
|
+
let upload;
|
|
2587
|
+
try {
|
|
2588
|
+
upload = await storageQueries2.getUploadUrl(sessionId, fileName, contentType, "slack");
|
|
2589
|
+
} catch (err) {
|
|
2590
|
+
return { ...base, sizeBytes: bytes.length, shortUrl: null, error: `presign_failed:${err?.message || "unknown"}` };
|
|
2591
|
+
}
|
|
2592
|
+
try {
|
|
2593
|
+
const putRes = await fetch(upload.uploadUrl, {
|
|
2594
|
+
method: "PUT",
|
|
2595
|
+
headers: { "Content-Type": contentType },
|
|
2596
|
+
body: bytes
|
|
2597
|
+
});
|
|
2598
|
+
if (!putRes.ok) {
|
|
2599
|
+
return {
|
|
2600
|
+
...base,
|
|
2601
|
+
sizeBytes: bytes.length,
|
|
2602
|
+
shortUrl: null,
|
|
2603
|
+
error: `gcs_put_${putRes.status}`
|
|
2604
|
+
};
|
|
2605
|
+
}
|
|
2606
|
+
} catch (err) {
|
|
2607
|
+
return {
|
|
2608
|
+
...base,
|
|
2609
|
+
sizeBytes: bytes.length,
|
|
2610
|
+
shortUrl: null,
|
|
2611
|
+
error: `gcs_put_error:${err?.message || "unknown"}`
|
|
2612
|
+
};
|
|
2613
|
+
}
|
|
2614
|
+
try {
|
|
2615
|
+
await storageQueries2.updateFile(upload.fileId, { sizeBytes: bytes.length });
|
|
2616
|
+
} catch (err) {
|
|
2617
|
+
console.warn(`[slack-files] sizeBytes patch failed for ${upload.fileId}:`, err?.message || err);
|
|
2618
|
+
}
|
|
2619
|
+
const shortUrl = upload.shortUrl || // Defensive fallback: build it from the upload URL's origin if the
|
|
2620
|
+
// server somehow forgot to return it (older remote-server versions).
|
|
2621
|
+
inferShortUrlFromUploadUrl(upload.uploadUrl, upload.fileId);
|
|
2622
|
+
return {
|
|
2623
|
+
...base,
|
|
2624
|
+
sizeBytes: bytes.length,
|
|
2625
|
+
shortUrl
|
|
2626
|
+
};
|
|
2627
|
+
}
|
|
2628
|
+
function inferShortUrlFromUploadUrl(uploadUrl, fileId) {
|
|
2629
|
+
try {
|
|
2630
|
+
const u = new URL(uploadUrl);
|
|
2631
|
+
if (u.hostname.endsWith(".googleapis.com")) return null;
|
|
2632
|
+
return `${u.origin}/f/${fileId}`;
|
|
2633
|
+
} catch {
|
|
2634
|
+
return null;
|
|
2635
|
+
}
|
|
2636
|
+
}
|
|
2637
|
+
async function ingestSlackFiles(files, sessionId, options = {}) {
|
|
2638
|
+
if (!Array.isArray(files) || files.length === 0) return [];
|
|
2639
|
+
const { isRemoteConfigured: isRemoteConfigured2 } = await Promise.resolve().then(() => (init_remote(), remote_exports));
|
|
2640
|
+
if (!isRemoteConfigured2()) {
|
|
2641
|
+
console.warn(`[slack-files] storage not configured \u2014 skipping ingestion for ${files.length} file(s)`);
|
|
2642
|
+
return files.map((f) => ({
|
|
2643
|
+
slackFileId: f.id,
|
|
2644
|
+
fileName: inferFileName(f),
|
|
2645
|
+
contentType: inferContentType(f),
|
|
2646
|
+
sizeBytes: typeof f.size === "number" ? f.size : 0,
|
|
2647
|
+
shortUrl: null,
|
|
2648
|
+
error: "storage_unconfigured"
|
|
2649
|
+
}));
|
|
2650
|
+
}
|
|
2651
|
+
const botToken = getSlackBotToken();
|
|
2652
|
+
if (!botToken) {
|
|
2653
|
+
console.warn("[slack-files] no bot token \u2014 cannot download from Slack");
|
|
2654
|
+
return files.map((f) => ({
|
|
2655
|
+
slackFileId: f.id,
|
|
2656
|
+
fileName: inferFileName(f),
|
|
2657
|
+
contentType: inferContentType(f),
|
|
2658
|
+
sizeBytes: typeof f.size === "number" ? f.size : 0,
|
|
2659
|
+
shortUrl: null,
|
|
2660
|
+
error: "no_bot_token"
|
|
2661
|
+
}));
|
|
2662
|
+
}
|
|
2663
|
+
const timeoutMs = options.timeoutMs ?? INGEST_TIMEOUT_MS;
|
|
2664
|
+
const startedAt = Date.now();
|
|
2665
|
+
const pipeline = Promise.allSettled(
|
|
2666
|
+
files.map(
|
|
2667
|
+
(f) => withTimeout(ingestOne(f, sessionId, botToken), timeoutMs, `ingest:${f.id}`).catch((err) => {
|
|
2668
|
+
if (String(err?.message || err).includes("_timeout")) {
|
|
2669
|
+
return {
|
|
2670
|
+
slackFileId: f.id,
|
|
2671
|
+
fileName: inferFileName(f),
|
|
2672
|
+
contentType: inferContentType(f),
|
|
2673
|
+
sizeBytes: typeof f.size === "number" ? f.size : 0,
|
|
2674
|
+
shortUrl: null,
|
|
2675
|
+
error: "timeout"
|
|
2676
|
+
};
|
|
2677
|
+
}
|
|
2678
|
+
throw err;
|
|
2679
|
+
})
|
|
2680
|
+
)
|
|
2681
|
+
);
|
|
2682
|
+
const settled = await pipeline;
|
|
2683
|
+
const results = settled.map((s, i) => {
|
|
2684
|
+
if (s.status === "fulfilled") return s.value;
|
|
2685
|
+
const f = files[i];
|
|
2686
|
+
return {
|
|
2687
|
+
slackFileId: f.id,
|
|
2688
|
+
fileName: inferFileName(f),
|
|
2689
|
+
contentType: inferContentType(f),
|
|
2690
|
+
sizeBytes: typeof f.size === "number" ? f.size : 0,
|
|
2691
|
+
shortUrl: null,
|
|
2692
|
+
error: `unexpected:${s.reason?.message || String(s.reason)}`
|
|
2693
|
+
};
|
|
2694
|
+
});
|
|
2695
|
+
const okCount = results.filter((r) => r.shortUrl).length;
|
|
2696
|
+
console.log(
|
|
2697
|
+
`[slack-files] ingested ${okCount}/${files.length} file(s) in ${Date.now() - startedAt}ms`
|
|
2698
|
+
);
|
|
2699
|
+
return results;
|
|
2700
|
+
}
|
|
2701
|
+
function formatFileBlock(files) {
|
|
2702
|
+
if (!files || files.length === 0) return "";
|
|
2703
|
+
const lines = ["[files]"];
|
|
2704
|
+
for (const f of files) {
|
|
2705
|
+
const sizeLabel = formatBytes(f.sizeBytes);
|
|
2706
|
+
if (f.shortUrl) {
|
|
2707
|
+
lines.push(` - ${f.fileName} (${f.contentType}, ${sizeLabel}): ${f.shortUrl}`);
|
|
2708
|
+
} else {
|
|
2709
|
+
lines.push(
|
|
2710
|
+
` - ${f.fileName} (${f.contentType}, ${sizeLabel}): [ingestion failed: ${f.error || "unknown"}]`
|
|
2711
|
+
);
|
|
2712
|
+
}
|
|
2713
|
+
}
|
|
2714
|
+
return lines.join("\n");
|
|
2715
|
+
}
|
|
2716
|
+
var MAX_BYTES, INGEST_TIMEOUT_MS;
|
|
2717
|
+
var init_files = __esm({
|
|
2718
|
+
"src/integrations/slack/files.ts"() {
|
|
2719
|
+
"use strict";
|
|
2720
|
+
init_client2();
|
|
2721
|
+
MAX_BYTES = 100 * 1024 * 1024;
|
|
2722
|
+
INGEST_TIMEOUT_MS = 2500;
|
|
2723
|
+
}
|
|
2724
|
+
});
|
|
2725
|
+
|
|
2726
|
+
// src/integrations/slack/read.ts
|
|
2727
|
+
var read_exports = {};
|
|
2728
|
+
__export(read_exports, {
|
|
2729
|
+
findChannelByName: () => findChannelByName,
|
|
2730
|
+
findUsers: () => findUsers,
|
|
2731
|
+
getChannelHistory: () => getChannelHistory,
|
|
2732
|
+
getPermalink: () => getPermalink,
|
|
2733
|
+
getThreadReplies: () => getThreadReplies,
|
|
2734
|
+
ingestMessageFiles: () => ingestMessageFiles
|
|
2735
|
+
});
|
|
2736
|
+
async function slackGet(method, params) {
|
|
2737
|
+
const token = getSlackBotToken();
|
|
2738
|
+
if (!token) return { ok: false, error: "slack_not_configured" };
|
|
2739
|
+
try {
|
|
2740
|
+
const qs = new URLSearchParams(params).toString();
|
|
2741
|
+
const res = await fetch(`https://slack.com/api/${method}?${qs}`, {
|
|
2742
|
+
headers: { Authorization: `Bearer ${token}` }
|
|
2743
|
+
});
|
|
2744
|
+
const json = await res.json().catch(() => ({}));
|
|
2745
|
+
if (!json?.ok) return { ok: false, error: json?.error || `HTTP ${res.status}` };
|
|
2746
|
+
return { ok: true, json };
|
|
2747
|
+
} catch (err) {
|
|
2748
|
+
return { ok: false, error: err?.message || "unknown" };
|
|
2749
|
+
}
|
|
2750
|
+
}
|
|
2751
|
+
function clampLimit(n, def, max) {
|
|
2752
|
+
const v = typeof n === "number" && Number.isFinite(n) ? n : def;
|
|
2753
|
+
return String(Math.min(Math.max(Math.floor(v), 1), max));
|
|
2754
|
+
}
|
|
2755
|
+
function liteMessage(m) {
|
|
2756
|
+
return {
|
|
2757
|
+
ts: String(m?.ts ?? ""),
|
|
2758
|
+
threadTs: typeof m?.thread_ts === "string" ? m.thread_ts : void 0,
|
|
2759
|
+
user: typeof m?.user === "string" ? m.user : void 0,
|
|
2760
|
+
botId: typeof m?.bot_id === "string" ? m.bot_id : void 0,
|
|
2761
|
+
text: typeof m?.text === "string" ? m.text.slice(0, 4e3) : "",
|
|
2762
|
+
files: Array.isArray(m?.files) ? m.files.map((f) => ({ id: String(f?.id ?? ""), name: f?.name || f?.title, mimetype: f?.mimetype, size: typeof f?.size === "number" ? f.size : void 0 })) : void 0,
|
|
2763
|
+
replyCount: typeof m?.reply_count === "number" ? m.reply_count : void 0
|
|
2764
|
+
};
|
|
2765
|
+
}
|
|
2766
|
+
async function enrichUserNames(messages) {
|
|
2767
|
+
const ids = [...new Set(messages.map((m) => m.user).filter((u) => !!u))];
|
|
2768
|
+
const names = /* @__PURE__ */ new Map();
|
|
2769
|
+
await Promise.all(
|
|
2770
|
+
ids.map(async (id) => {
|
|
2771
|
+
const info = await resolveSlackUserInfo(id).catch(() => null);
|
|
2772
|
+
if (info?.name) names.set(id, info.name);
|
|
2773
|
+
})
|
|
2774
|
+
);
|
|
2775
|
+
return messages.map((m) => m.user && names.has(m.user) ? { ...m, userName: names.get(m.user) } : m);
|
|
2776
|
+
}
|
|
2777
|
+
async function getChannelHistory(channel, limit) {
|
|
2778
|
+
const r = await slackGet("conversations.history", { channel, limit: clampLimit(limit, 20, 100) });
|
|
2779
|
+
if (!r.ok) return { ok: false, error: r.error };
|
|
2780
|
+
const messages = Array.isArray(r.json.messages) ? r.json.messages.map(liteMessage) : [];
|
|
2781
|
+
return { ok: true, data: await enrichUserNames(messages) };
|
|
2782
|
+
}
|
|
2783
|
+
async function getThreadReplies(channel, threadTs, limit) {
|
|
2784
|
+
const r = await slackGet("conversations.replies", { channel, ts: threadTs, limit: clampLimit(limit, 50, 200) });
|
|
2785
|
+
if (!r.ok) return { ok: false, error: r.error };
|
|
2786
|
+
const messages = Array.isArray(r.json.messages) ? r.json.messages.map(liteMessage) : [];
|
|
2787
|
+
return { ok: true, data: await enrichUserNames(messages) };
|
|
2788
|
+
}
|
|
2789
|
+
async function getPermalink(channel, messageTs) {
|
|
2790
|
+
const r = await slackGet("chat.getPermalink", { channel, message_ts: messageTs });
|
|
2791
|
+
if (!r.ok) return { ok: false, error: r.error };
|
|
2792
|
+
return { ok: true, data: { permalink: String(r.json.permalink ?? "") } };
|
|
2793
|
+
}
|
|
2794
|
+
async function findChannelByName(name) {
|
|
2795
|
+
const clean = name.replace(/^#/, "").trim().toLowerCase();
|
|
2796
|
+
let cursor;
|
|
2797
|
+
for (let page = 0; page < 10; page++) {
|
|
2798
|
+
const params = {
|
|
2799
|
+
types: "public_channel,private_channel",
|
|
2800
|
+
exclude_archived: "true",
|
|
2801
|
+
limit: "999"
|
|
2802
|
+
};
|
|
2803
|
+
if (cursor) params.cursor = cursor;
|
|
2804
|
+
const r = await slackGet("conversations.list", params);
|
|
2805
|
+
if (!r.ok) return { ok: false, error: r.error };
|
|
2806
|
+
const match = (r.json.channels || []).find((c) => String(c?.name ?? "").toLowerCase() === clean);
|
|
2807
|
+
if (match) return { ok: true, data: { id: String(match.id), name: String(match.name) } };
|
|
2808
|
+
cursor = r.json.response_metadata?.next_cursor || "";
|
|
2809
|
+
if (!cursor) break;
|
|
2810
|
+
}
|
|
2811
|
+
return { ok: false, error: "channel_not_found" };
|
|
2812
|
+
}
|
|
2813
|
+
async function findUsers(query, max = 10) {
|
|
2814
|
+
const q = query.trim().toLowerCase();
|
|
2815
|
+
if (!q) return { ok: false, error: "empty_query" };
|
|
2816
|
+
if (q.includes("@")) {
|
|
2817
|
+
const r = await slackGet("users.lookupByEmail", { email: query.trim() });
|
|
2818
|
+
if (r.ok && r.json.user) {
|
|
2819
|
+
const u = r.json.user;
|
|
2820
|
+
return { ok: true, data: [{ id: String(u.id), name: u.profile?.display_name || u.real_name || u.name, realName: u.real_name, email: u.profile?.email }] };
|
|
2821
|
+
}
|
|
2822
|
+
if (r.error && !["users_not_found", "user_not_found", "not_found"].includes(r.error)) {
|
|
2823
|
+
return { ok: false, error: r.error };
|
|
2824
|
+
}
|
|
2825
|
+
}
|
|
2826
|
+
const matches = [];
|
|
2827
|
+
let cursor;
|
|
2828
|
+
for (let page = 0; page < 10 && matches.length < max; page++) {
|
|
2829
|
+
const params = { limit: "1000" };
|
|
2830
|
+
if (cursor) params.cursor = cursor;
|
|
2831
|
+
const r = await slackGet("users.list", params);
|
|
2832
|
+
if (!r.ok) return { ok: false, error: r.error };
|
|
2833
|
+
for (const u of r.json.members || []) {
|
|
2834
|
+
if (u?.deleted || u?.is_bot) continue;
|
|
2835
|
+
const dn = String(u?.profile?.display_name_normalized || u?.profile?.real_name || u?.name || "");
|
|
2836
|
+
const email = String(u?.profile?.email || "");
|
|
2837
|
+
if (dn.toLowerCase().includes(q) || email.toLowerCase().includes(q)) {
|
|
2838
|
+
matches.push({ id: String(u.id), name: dn || String(u.name), realName: u?.profile?.real_name, email: email || void 0 });
|
|
2839
|
+
if (matches.length >= max) break;
|
|
2840
|
+
}
|
|
2841
|
+
}
|
|
2842
|
+
cursor = r.json.response_metadata?.next_cursor || "";
|
|
2843
|
+
if (!cursor) break;
|
|
2844
|
+
}
|
|
2845
|
+
return matches.length > 0 ? { ok: true, data: matches } : { ok: false, error: "no_match" };
|
|
2846
|
+
}
|
|
2847
|
+
async function getSingleMessage(channel, ts, threadTs) {
|
|
2848
|
+
if (threadTs) {
|
|
2849
|
+
const rr2 = await slackGet("conversations.replies", { channel, ts: threadTs, limit: "200" });
|
|
2850
|
+
if (rr2.ok && Array.isArray(rr2.json.messages)) {
|
|
2851
|
+
const match = rr2.json.messages.find((m) => m?.ts === ts);
|
|
2852
|
+
if (match) return match;
|
|
2853
|
+
}
|
|
2854
|
+
}
|
|
2855
|
+
const r = await slackGet("conversations.history", { channel, latest: ts, oldest: ts, inclusive: "true", limit: "1" });
|
|
2856
|
+
if (r.ok && Array.isArray(r.json.messages) && r.json.messages[0]) return r.json.messages[0];
|
|
2857
|
+
const rr = await slackGet("conversations.replies", { channel, ts, limit: "1" });
|
|
2858
|
+
if (rr.ok && Array.isArray(rr.json.messages)) {
|
|
2859
|
+
return rr.json.messages.find((m) => m?.ts === ts) || rr.json.messages[0] || null;
|
|
2860
|
+
}
|
|
2861
|
+
return null;
|
|
2862
|
+
}
|
|
2863
|
+
async function ingestMessageFiles(channel, ts, orchestratorSessionId, threadTs) {
|
|
2864
|
+
const msg = await getSingleMessage(channel, ts, threadTs);
|
|
2865
|
+
if (!msg) return { ok: false, error: "message_not_found" };
|
|
2866
|
+
const files = Array.isArray(msg.files) ? msg.files : [];
|
|
2867
|
+
if (files.length === 0) return { ok: true, data: [] };
|
|
2868
|
+
const { ingestSlackFiles: ingestSlackFiles2 } = await Promise.resolve().then(() => (init_files(), files_exports));
|
|
2869
|
+
const ingested = await ingestSlackFiles2(files, orchestratorSessionId);
|
|
2870
|
+
return {
|
|
2871
|
+
ok: true,
|
|
2872
|
+
data: ingested.map((f) => ({ name: f.fileName, url: f.shortUrl, error: f.error }))
|
|
2873
|
+
};
|
|
2874
|
+
}
|
|
2875
|
+
var init_read = __esm({
|
|
2876
|
+
"src/integrations/slack/read.ts"() {
|
|
2877
|
+
"use strict";
|
|
2878
|
+
init_client2();
|
|
2879
|
+
}
|
|
2880
|
+
});
|
|
2881
|
+
|
|
2395
2882
|
// src/browser/stream-proxy.ts
|
|
2396
2883
|
var stream_proxy_exports = {};
|
|
2397
2884
|
__export(stream_proxy_exports, {
|
|
@@ -8528,7 +9015,16 @@ async function postMessage(opts) {
|
|
|
8528
9015
|
let ref;
|
|
8529
9016
|
switch (opts.channel) {
|
|
8530
9017
|
case "slack": {
|
|
8531
|
-
|
|
9018
|
+
let slackChannel2 = opts.to.trim();
|
|
9019
|
+
if (slackChannel2.startsWith("#")) {
|
|
9020
|
+
const { findChannelByName: findChannelByName2 } = await Promise.resolve().then(() => (init_read(), read_exports));
|
|
9021
|
+
const found = await findChannelByName2(slackChannel2);
|
|
9022
|
+
if (!found.ok || !found.data?.id) {
|
|
9023
|
+
return { ok: false, error: `slack channel lookup failed: ${found.error || "channel_not_found"}` };
|
|
9024
|
+
}
|
|
9025
|
+
slackChannel2 = found.data.id;
|
|
9026
|
+
}
|
|
9027
|
+
const slackRef = { channel: "slack", slackChannel: slackChannel2, threadTs: opts.threadTs };
|
|
8532
9028
|
ref = slackRef;
|
|
8533
9029
|
break;
|
|
8534
9030
|
}
|
|
@@ -8550,6 +9046,10 @@ function describeConfiguredChannels() {
|
|
|
8550
9046
|
}));
|
|
8551
9047
|
}
|
|
8552
9048
|
|
|
9049
|
+
// src/tools/orchestrator-actions.ts
|
|
9050
|
+
init_read();
|
|
9051
|
+
init_client2();
|
|
9052
|
+
|
|
8553
9053
|
// src/orchestrator/schedules-store.ts
|
|
8554
9054
|
init_db();
|
|
8555
9055
|
import { nanoid as nanoid5 } from "nanoid";
|
|
@@ -8858,6 +9358,55 @@ function buildMessengerTool() {
|
|
|
8858
9358
|
}
|
|
8859
9359
|
});
|
|
8860
9360
|
}
|
|
9361
|
+
var slackInputSchema = z14.object({
|
|
9362
|
+
action: z14.enum(["history", "replies", "permalink", "find_channel", "find_user", "user_info", "fetch_files"]),
|
|
9363
|
+
channel: z14.string().optional().describe("channel id (C0123/G0123/D0123). Required for history/replies/permalink/fetch_files."),
|
|
9364
|
+
threadTs: z14.string().optional().describe("replies/fetch_files: parent message ts of the thread."),
|
|
9365
|
+
messageTs: z14.string().optional().describe("permalink/fetch_files: ts of the target message; for thread-reply files also pass threadTs."),
|
|
9366
|
+
query: z14.string().optional().describe("find_channel: channel name (no #). find_user: name or email."),
|
|
9367
|
+
user: z14.string().optional().describe("user_info: user id (U0123)."),
|
|
9368
|
+
limit: z14.number().optional().describe("history/replies: max messages (history \u2264100, replies \u2264200).")
|
|
9369
|
+
});
|
|
9370
|
+
function buildSlackTool(opts) {
|
|
9371
|
+
return tool13({
|
|
9372
|
+
description: "Read from Slack like a human would. Actions: history (channel[, limit] \u2192 recent messages, newest first, with sender names + file metadata), replies (channel, threadTs[, limit] \u2192 full thread), permalink (channel, messageTs \u2192 shareable link), find_channel (query=name \u2192 channel id), find_user (query=name|email \u2192 matching members), user_info (user=U0123 \u2192 name/email), fetch_files (channel, messageTs[, threadTs] \u2192 re-uploads that message's attachments and returns fetchable URLs you can curl). Use this to scroll back, read context, and open files others posted before replying.",
|
|
9373
|
+
inputSchema: slackInputSchema,
|
|
9374
|
+
execute: async (input) => {
|
|
9375
|
+
if (!isSlackConfigured()) return { ok: false, error: "slack not configured" };
|
|
9376
|
+
switch (input.action) {
|
|
9377
|
+
case "history": {
|
|
9378
|
+
if (!input.channel) return { ok: false, error: "channel required" };
|
|
9379
|
+
return getChannelHistory(input.channel, input.limit);
|
|
9380
|
+
}
|
|
9381
|
+
case "replies": {
|
|
9382
|
+
if (!input.channel || !input.threadTs) return { ok: false, error: "channel and threadTs required" };
|
|
9383
|
+
return getThreadReplies(input.channel, input.threadTs, input.limit);
|
|
9384
|
+
}
|
|
9385
|
+
case "permalink": {
|
|
9386
|
+
if (!input.channel || !input.messageTs) return { ok: false, error: "channel and messageTs required" };
|
|
9387
|
+
return getPermalink(input.channel, input.messageTs);
|
|
9388
|
+
}
|
|
9389
|
+
case "find_channel": {
|
|
9390
|
+
if (!input.query) return { ok: false, error: "query (channel name) required" };
|
|
9391
|
+
return findChannelByName(input.query);
|
|
9392
|
+
}
|
|
9393
|
+
case "find_user": {
|
|
9394
|
+
if (!input.query) return { ok: false, error: "query (name or email) required" };
|
|
9395
|
+
return findUsers(input.query);
|
|
9396
|
+
}
|
|
9397
|
+
case "user_info": {
|
|
9398
|
+
if (!input.user) return { ok: false, error: "user required" };
|
|
9399
|
+
const info = await resolveSlackUserInfo(input.user);
|
|
9400
|
+
return info ? { ok: true, data: { id: input.user, ...info } } : { ok: false, error: "not_found" };
|
|
9401
|
+
}
|
|
9402
|
+
case "fetch_files": {
|
|
9403
|
+
if (!input.channel || !input.messageTs) return { ok: false, error: "channel and messageTs required" };
|
|
9404
|
+
return ingestMessageFiles(input.channel, input.messageTs, opts.orchestratorSessionId, input.threadTs);
|
|
9405
|
+
}
|
|
9406
|
+
}
|
|
9407
|
+
}
|
|
9408
|
+
});
|
|
9409
|
+
}
|
|
8861
9410
|
var scheduleInputSchema = z14.object({
|
|
8862
9411
|
action: z14.enum(["create", "list", "update", "delete", "pause", "resume"]),
|
|
8863
9412
|
// create / update
|
|
@@ -8958,6 +9507,7 @@ function createOrchestratorActionTools(opts) {
|
|
|
8958
9507
|
return {
|
|
8959
9508
|
agent: buildAgentTool(opts),
|
|
8960
9509
|
messenger: buildMessengerTool(),
|
|
9510
|
+
slack: buildSlackTool(opts),
|
|
8961
9511
|
schedule: buildScheduleTool(opts),
|
|
8962
9512
|
webhook: buildWebhookTool(opts)
|
|
8963
9513
|
};
|