@integrity-labs/agt-cli 0.15.8 → 0.15.9
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/bin/agt.js +3 -3
- package/dist/{chunk-32D5TUSD.js → chunk-C6UBNLUC.js} +117 -5
- package/dist/{chunk-32D5TUSD.js.map → chunk-C6UBNLUC.js.map} +1 -1
- package/dist/lib/manager-worker.js +348 -177
- package/dist/lib/manager-worker.js.map +1 -1
- package/mcp/slack-channel.js +388 -11
- package/mcp/telegram-channel.js +386 -8
- package/package.json +1 -1
package/mcp/slack-channel.js
CHANGED
|
@@ -13887,9 +13887,22 @@ var StdioServerTransport = class {
|
|
|
13887
13887
|
};
|
|
13888
13888
|
|
|
13889
13889
|
// src/slack-channel.ts
|
|
13890
|
-
import {
|
|
13890
|
+
import {
|
|
13891
|
+
chmodSync,
|
|
13892
|
+
createWriteStream,
|
|
13893
|
+
existsSync,
|
|
13894
|
+
mkdirSync as mkdirSync2,
|
|
13895
|
+
readFileSync as readFileSync2,
|
|
13896
|
+
readdirSync,
|
|
13897
|
+
renameSync,
|
|
13898
|
+
statSync,
|
|
13899
|
+
unlinkSync,
|
|
13900
|
+
watch,
|
|
13901
|
+
writeFileSync as writeFileSync2
|
|
13902
|
+
} from "fs";
|
|
13891
13903
|
import { basename, join as join2, resolve as resolve2 } from "path";
|
|
13892
13904
|
import { homedir as homedir2 } from "os";
|
|
13905
|
+
import { createHash, randomUUID } from "crypto";
|
|
13893
13906
|
|
|
13894
13907
|
// src/slack-thread-store.ts
|
|
13895
13908
|
import { mkdirSync, readFileSync, writeFileSync } from "fs";
|
|
@@ -14158,6 +14171,239 @@ var THREAD_AUTO_FOLLOW = process.env.SLACK_THREAD_AUTO_FOLLOW ?? "off";
|
|
|
14158
14171
|
var CHANNEL_RESPONSE_MODE = parseResponseMode(process.env.SLACK_CHANNEL_RESPONSE_MODE);
|
|
14159
14172
|
var RESPONSE_TIMEOUT_MS = 3e5;
|
|
14160
14173
|
var pendingMessages = /* @__PURE__ */ new Map();
|
|
14174
|
+
var SLACK_AGENT_DIR = AGENT_CODE_NAME ? join2(homedir2(), ".augmented", AGENT_CODE_NAME) : null;
|
|
14175
|
+
var SLACK_PENDING_INBOUND_DIR = SLACK_AGENT_DIR ? join2(SLACK_AGENT_DIR, "slack-pending-inbound") : null;
|
|
14176
|
+
var SLACK_RECOVERY_OUTBOX_DIR = SLACK_AGENT_DIR ? join2(SLACK_AGENT_DIR, "slack-recovery-outbox") : null;
|
|
14177
|
+
var SLACK_MAX_RECOVERY_ATTEMPTS = 3;
|
|
14178
|
+
function redactSlackId(id) {
|
|
14179
|
+
if (!id) return "<none>";
|
|
14180
|
+
return createHash("sha256").update(id).digest("hex").slice(0, 8);
|
|
14181
|
+
}
|
|
14182
|
+
function safeSlackMarkerName(channel, threadTs, messageTs) {
|
|
14183
|
+
const safe = (s) => s.replace(/[^A-Za-z0-9_-]/g, "_");
|
|
14184
|
+
return `${safe(channel)}__${safe(threadTs)}__${safe(messageTs)}.json`;
|
|
14185
|
+
}
|
|
14186
|
+
function slackPendingInboundPath(channel, threadTs, messageTs) {
|
|
14187
|
+
if (!SLACK_PENDING_INBOUND_DIR) return null;
|
|
14188
|
+
return join2(SLACK_PENDING_INBOUND_DIR, safeSlackMarkerName(channel, threadTs, messageTs));
|
|
14189
|
+
}
|
|
14190
|
+
function writeSlackPendingInboundMarker(channel, threadTs, messageTs) {
|
|
14191
|
+
const path = slackPendingInboundPath(channel, threadTs, messageTs);
|
|
14192
|
+
if (!path || !SLACK_PENDING_INBOUND_DIR) return;
|
|
14193
|
+
const marker = {
|
|
14194
|
+
channel,
|
|
14195
|
+
thread_ts: threadTs,
|
|
14196
|
+
message_ts: messageTs,
|
|
14197
|
+
received_at: (/* @__PURE__ */ new Date()).toISOString()
|
|
14198
|
+
};
|
|
14199
|
+
try {
|
|
14200
|
+
mkdirSync2(SLACK_PENDING_INBOUND_DIR, { recursive: true, mode: 448 });
|
|
14201
|
+
writeFileSync2(path, JSON.stringify(marker), { mode: 384 });
|
|
14202
|
+
} catch (err) {
|
|
14203
|
+
process.stderr.write(
|
|
14204
|
+
`slack-channel(${AGENT_CODE_NAME}): pending-inbound marker write failed: ${err.message}
|
|
14205
|
+
`
|
|
14206
|
+
);
|
|
14207
|
+
}
|
|
14208
|
+
}
|
|
14209
|
+
function clearSlackPendingInboundMarker(channel, threadTs, messageTs) {
|
|
14210
|
+
const path = slackPendingInboundPath(channel, threadTs, messageTs);
|
|
14211
|
+
if (!path) return;
|
|
14212
|
+
try {
|
|
14213
|
+
if (existsSync(path)) unlinkSync(path);
|
|
14214
|
+
} catch {
|
|
14215
|
+
}
|
|
14216
|
+
}
|
|
14217
|
+
function clearAllSlackPendingMarkersForThread(channel, threadTs) {
|
|
14218
|
+
if (!SLACK_PENDING_INBOUND_DIR) return;
|
|
14219
|
+
const safeChan = channel.replace(/[^A-Za-z0-9_-]/g, "_");
|
|
14220
|
+
const safeThread = threadTs.replace(/[^A-Za-z0-9_-]/g, "_");
|
|
14221
|
+
const prefix = `${safeChan}__${safeThread}__`;
|
|
14222
|
+
try {
|
|
14223
|
+
for (const f of readdirSync(SLACK_PENDING_INBOUND_DIR)) {
|
|
14224
|
+
if (!f.startsWith(prefix) || !f.endsWith(".json")) continue;
|
|
14225
|
+
try {
|
|
14226
|
+
unlinkSync(join2(SLACK_PENDING_INBOUND_DIR, f));
|
|
14227
|
+
} catch {
|
|
14228
|
+
}
|
|
14229
|
+
}
|
|
14230
|
+
} catch {
|
|
14231
|
+
}
|
|
14232
|
+
}
|
|
14233
|
+
function slackNextRetryName(filename) {
|
|
14234
|
+
const match = filename.match(/^(.*?)(?:\.retry-(\d+))?\.json$/);
|
|
14235
|
+
if (!match) return null;
|
|
14236
|
+
const base = match[1];
|
|
14237
|
+
const prev = match[2] ? parseInt(match[2], 10) : 0;
|
|
14238
|
+
const attempt = prev + 1;
|
|
14239
|
+
if (attempt >= SLACK_MAX_RECOVERY_ATTEMPTS) {
|
|
14240
|
+
return { next: `${base}.retry-${attempt}.poison.json`, attempt };
|
|
14241
|
+
}
|
|
14242
|
+
return { next: `${base}.retry-${attempt}.json`, attempt };
|
|
14243
|
+
}
|
|
14244
|
+
async function processSlackRecoveryOutboxFile(filename) {
|
|
14245
|
+
if (!SLACK_RECOVERY_OUTBOX_DIR) return;
|
|
14246
|
+
if (filename.endsWith(".poison.json") || filename.endsWith(".tmp")) return;
|
|
14247
|
+
const fullPath = join2(SLACK_RECOVERY_OUTBOX_DIR, filename);
|
|
14248
|
+
let payload;
|
|
14249
|
+
try {
|
|
14250
|
+
payload = JSON.parse(readFileSync2(fullPath, "utf-8"));
|
|
14251
|
+
} catch (err) {
|
|
14252
|
+
process.stderr.write(
|
|
14253
|
+
`slack-channel(${AGENT_CODE_NAME}): recovery outbox parse failed (${filename}): ${err.message}
|
|
14254
|
+
`
|
|
14255
|
+
);
|
|
14256
|
+
try {
|
|
14257
|
+
renameSync(fullPath, `${fullPath}.parse-error.poison`);
|
|
14258
|
+
} catch {
|
|
14259
|
+
}
|
|
14260
|
+
return;
|
|
14261
|
+
}
|
|
14262
|
+
if (!payload.channel || !payload.text) {
|
|
14263
|
+
process.stderr.write(
|
|
14264
|
+
`slack-channel(${AGENT_CODE_NAME}): recovery outbox malformed (${filename}): missing channel or text
|
|
14265
|
+
`
|
|
14266
|
+
);
|
|
14267
|
+
try {
|
|
14268
|
+
renameSync(fullPath, `${fullPath}.malformed.poison`);
|
|
14269
|
+
} catch {
|
|
14270
|
+
}
|
|
14271
|
+
return;
|
|
14272
|
+
}
|
|
14273
|
+
const text = `[recovered reply] ${payload.text}`;
|
|
14274
|
+
let sendSucceeded = false;
|
|
14275
|
+
const controller = new AbortController();
|
|
14276
|
+
const timeoutId = setTimeout(() => controller.abort(), 15e3);
|
|
14277
|
+
try {
|
|
14278
|
+
const res = await fetch("https://slack.com/api/chat.postMessage", {
|
|
14279
|
+
method: "POST",
|
|
14280
|
+
signal: controller.signal,
|
|
14281
|
+
headers: { "Content-Type": "application/json", Authorization: `Bearer ${BOT_TOKEN}` },
|
|
14282
|
+
body: JSON.stringify({
|
|
14283
|
+
channel: payload.channel,
|
|
14284
|
+
text,
|
|
14285
|
+
...payload.thread_ts ? { thread_ts: payload.thread_ts } : {}
|
|
14286
|
+
})
|
|
14287
|
+
});
|
|
14288
|
+
const data = await res.json();
|
|
14289
|
+
if (data.ok) {
|
|
14290
|
+
sendSucceeded = true;
|
|
14291
|
+
process.stderr.write(
|
|
14292
|
+
`slack-channel(${AGENT_CODE_NAME}): ghost-reply recovery sent (channel=${redactSlackId(payload.channel)} thread=${redactSlackId(payload.thread_ts)})
|
|
14293
|
+
`
|
|
14294
|
+
);
|
|
14295
|
+
if (payload.thread_ts) {
|
|
14296
|
+
clearAllSlackPendingMarkersForThread(payload.channel, payload.thread_ts);
|
|
14297
|
+
}
|
|
14298
|
+
} else {
|
|
14299
|
+
process.stderr.write(
|
|
14300
|
+
`slack-channel(${AGENT_CODE_NAME}): ghost-reply recovery failed (channel=${redactSlackId(payload.channel)}): ${data.error ?? "unknown"}
|
|
14301
|
+
`
|
|
14302
|
+
);
|
|
14303
|
+
}
|
|
14304
|
+
} catch (err) {
|
|
14305
|
+
const msg = err.name === "AbortError" ? "timed out after 15s" : err.message;
|
|
14306
|
+
process.stderr.write(
|
|
14307
|
+
`slack-channel(${AGENT_CODE_NAME}): ghost-reply recovery error (channel=${redactSlackId(payload.channel)}): ${msg}
|
|
14308
|
+
`
|
|
14309
|
+
);
|
|
14310
|
+
} finally {
|
|
14311
|
+
clearTimeout(timeoutId);
|
|
14312
|
+
}
|
|
14313
|
+
if (sendSucceeded) {
|
|
14314
|
+
try {
|
|
14315
|
+
unlinkSync(fullPath);
|
|
14316
|
+
} catch {
|
|
14317
|
+
}
|
|
14318
|
+
return;
|
|
14319
|
+
}
|
|
14320
|
+
const next = slackNextRetryName(filename);
|
|
14321
|
+
if (next) {
|
|
14322
|
+
try {
|
|
14323
|
+
renameSync(fullPath, join2(SLACK_RECOVERY_OUTBOX_DIR, next.next));
|
|
14324
|
+
if (next.attempt >= SLACK_MAX_RECOVERY_ATTEMPTS) {
|
|
14325
|
+
process.stderr.write(
|
|
14326
|
+
`slack-channel(${AGENT_CODE_NAME}): ghost-reply recovery exhausted retries \u2014 moved to ${next.next}
|
|
14327
|
+
`
|
|
14328
|
+
);
|
|
14329
|
+
}
|
|
14330
|
+
} catch {
|
|
14331
|
+
}
|
|
14332
|
+
}
|
|
14333
|
+
}
|
|
14334
|
+
function isFirstAttemptSlackOutboxFile(filename) {
|
|
14335
|
+
if (!filename.endsWith(".json")) return false;
|
|
14336
|
+
if (filename.includes(".retry-")) return false;
|
|
14337
|
+
if (filename.endsWith(".poison.json") || filename.endsWith(".tmp")) return false;
|
|
14338
|
+
if (filename.startsWith(".")) return false;
|
|
14339
|
+
return true;
|
|
14340
|
+
}
|
|
14341
|
+
var SLACK_RETRY_SCAN_INTERVAL_MS = 6e4;
|
|
14342
|
+
var SLACK_RETRY_BACKOFF_BASE_MS = 6e4;
|
|
14343
|
+
function shouldRetrySlackNow(filename, mtimeMs) {
|
|
14344
|
+
const match = filename.match(/\.retry-(\d+)\.json$/);
|
|
14345
|
+
if (!match) return false;
|
|
14346
|
+
const attempt = parseInt(match[1], 10);
|
|
14347
|
+
return Date.now() - mtimeMs >= attempt * SLACK_RETRY_BACKOFF_BASE_MS;
|
|
14348
|
+
}
|
|
14349
|
+
function scanSlackRecoveryRetries() {
|
|
14350
|
+
if (!SLACK_RECOVERY_OUTBOX_DIR) return;
|
|
14351
|
+
let entries;
|
|
14352
|
+
try {
|
|
14353
|
+
entries = readdirSync(SLACK_RECOVERY_OUTBOX_DIR);
|
|
14354
|
+
} catch {
|
|
14355
|
+
return;
|
|
14356
|
+
}
|
|
14357
|
+
for (const f of entries) {
|
|
14358
|
+
if (!f.endsWith(".json")) continue;
|
|
14359
|
+
if (!f.includes(".retry-") || f.endsWith(".poison.json")) continue;
|
|
14360
|
+
let mtimeMs;
|
|
14361
|
+
try {
|
|
14362
|
+
mtimeMs = statSync(join2(SLACK_RECOVERY_OUTBOX_DIR, f)).mtimeMs;
|
|
14363
|
+
} catch {
|
|
14364
|
+
continue;
|
|
14365
|
+
}
|
|
14366
|
+
if (shouldRetrySlackNow(f, mtimeMs)) {
|
|
14367
|
+
void processSlackRecoveryOutboxFile(f);
|
|
14368
|
+
}
|
|
14369
|
+
}
|
|
14370
|
+
}
|
|
14371
|
+
function startSlackRecoveryOutboxWatcher() {
|
|
14372
|
+
if (!SLACK_RECOVERY_OUTBOX_DIR) return;
|
|
14373
|
+
try {
|
|
14374
|
+
mkdirSync2(SLACK_RECOVERY_OUTBOX_DIR, { recursive: true, mode: 448 });
|
|
14375
|
+
} catch (err) {
|
|
14376
|
+
process.stderr.write(
|
|
14377
|
+
`slack-channel(${AGENT_CODE_NAME}): recovery outbox mkdir failed: ${err.message}
|
|
14378
|
+
`
|
|
14379
|
+
);
|
|
14380
|
+
return;
|
|
14381
|
+
}
|
|
14382
|
+
try {
|
|
14383
|
+
for (const f of readdirSync(SLACK_RECOVERY_OUTBOX_DIR)) {
|
|
14384
|
+
if (isFirstAttemptSlackOutboxFile(f)) void processSlackRecoveryOutboxFile(f);
|
|
14385
|
+
}
|
|
14386
|
+
} catch {
|
|
14387
|
+
}
|
|
14388
|
+
try {
|
|
14389
|
+
const watcher = watch(SLACK_RECOVERY_OUTBOX_DIR, (event, filename) => {
|
|
14390
|
+
if (event !== "rename" || !filename) return;
|
|
14391
|
+
if (!isFirstAttemptSlackOutboxFile(filename)) return;
|
|
14392
|
+
if (existsSync(join2(SLACK_RECOVERY_OUTBOX_DIR, filename))) {
|
|
14393
|
+
void processSlackRecoveryOutboxFile(filename);
|
|
14394
|
+
}
|
|
14395
|
+
});
|
|
14396
|
+
watcher.unref?.();
|
|
14397
|
+
} catch (err) {
|
|
14398
|
+
process.stderr.write(
|
|
14399
|
+
`slack-channel(${AGENT_CODE_NAME}): recovery outbox watch failed: ${err.message}
|
|
14400
|
+
`
|
|
14401
|
+
);
|
|
14402
|
+
}
|
|
14403
|
+
const retryTimer = setInterval(scanSlackRecoveryRetries, SLACK_RETRY_SCAN_INTERVAL_MS);
|
|
14404
|
+
retryTimer.unref?.();
|
|
14405
|
+
}
|
|
14406
|
+
startSlackRecoveryOutboxWatcher();
|
|
14161
14407
|
function trackPendingMessage(channel, threadTs, messageTs) {
|
|
14162
14408
|
const key = `${channel}:${threadTs}:${messageTs}`;
|
|
14163
14409
|
const timer = setTimeout(async () => {
|
|
@@ -14182,10 +14428,12 @@ function trackPendingMessage(channel, threadTs, messageTs) {
|
|
|
14182
14428
|
});
|
|
14183
14429
|
} catch {
|
|
14184
14430
|
}
|
|
14185
|
-
process.stderr.write(`slack-channel: Response timeout for message ${messageTs} in ${channel}
|
|
14431
|
+
process.stderr.write(`slack-channel: Response timeout for message ${redactSlackId(messageTs)} in ${redactSlackId(channel)}
|
|
14186
14432
|
`);
|
|
14433
|
+
clearSlackPendingInboundMarker(channel, threadTs, messageTs);
|
|
14187
14434
|
}, RESPONSE_TIMEOUT_MS);
|
|
14188
14435
|
pendingMessages.set(key, timer);
|
|
14436
|
+
writeSlackPendingInboundMarker(channel, threadTs, messageTs);
|
|
14189
14437
|
}
|
|
14190
14438
|
function clearPendingMessage(channel, threadTs) {
|
|
14191
14439
|
for (const [key, timer] of pendingMessages) {
|
|
@@ -14194,6 +14442,90 @@ function clearPendingMessage(channel, threadTs) {
|
|
|
14194
14442
|
pendingMessages.delete(key);
|
|
14195
14443
|
}
|
|
14196
14444
|
}
|
|
14445
|
+
clearAllSlackPendingMarkersForThread(channel, threadTs);
|
|
14446
|
+
}
|
|
14447
|
+
var RESTART_FLAGS_DIR = join2(homedir2(), ".augmented", "restart-flags");
|
|
14448
|
+
function hashChannelId(id) {
|
|
14449
|
+
return createHash("sha256").update(id).digest("hex").slice(0, 8);
|
|
14450
|
+
}
|
|
14451
|
+
async function postSlackMessage(body) {
|
|
14452
|
+
try {
|
|
14453
|
+
const res = await fetch("https://slack.com/api/chat.postMessage", {
|
|
14454
|
+
method: "POST",
|
|
14455
|
+
headers: {
|
|
14456
|
+
"Content-Type": "application/json; charset=utf-8",
|
|
14457
|
+
Authorization: `Bearer ${BOT_TOKEN}`
|
|
14458
|
+
},
|
|
14459
|
+
body: JSON.stringify(body),
|
|
14460
|
+
// Hard deadline so a hung Slack API can't stall the live Socket Mode
|
|
14461
|
+
// event loop or the manager's restart ack path. Matches the timeout
|
|
14462
|
+
// used elsewhere in this file for chat.postMessage.
|
|
14463
|
+
signal: AbortSignal.timeout(SLACK_DOWNLOAD_TIMEOUT_MS)
|
|
14464
|
+
});
|
|
14465
|
+
return await res.json();
|
|
14466
|
+
} catch (err) {
|
|
14467
|
+
const isTimeout = err.name === "TimeoutError" || err.name === "AbortError";
|
|
14468
|
+
return { ok: false, error: isTimeout ? "timeout" : err.message };
|
|
14469
|
+
}
|
|
14470
|
+
}
|
|
14471
|
+
async function handleRestartCommand(opts) {
|
|
14472
|
+
const codeName = AGENT_CODE_NAME ?? "unknown";
|
|
14473
|
+
try {
|
|
14474
|
+
if (!existsSync(RESTART_FLAGS_DIR)) {
|
|
14475
|
+
mkdirSync2(RESTART_FLAGS_DIR, { recursive: true });
|
|
14476
|
+
}
|
|
14477
|
+
const flagPath = join2(RESTART_FLAGS_DIR, `${codeName}.flag`);
|
|
14478
|
+
const flag = {
|
|
14479
|
+
codeName,
|
|
14480
|
+
source: "slack",
|
|
14481
|
+
ts: Date.now(),
|
|
14482
|
+
reply: {
|
|
14483
|
+
channel: opts.channel,
|
|
14484
|
+
...opts.threadTs ? { thread_ts: opts.threadTs } : {},
|
|
14485
|
+
message_ts: opts.ts
|
|
14486
|
+
}
|
|
14487
|
+
};
|
|
14488
|
+
const tmpPath = `${flagPath}.${process.pid}.${randomUUID()}.tmp`;
|
|
14489
|
+
writeFileSync2(tmpPath, JSON.stringify(flag) + "\n", "utf8");
|
|
14490
|
+
renameSync(tmpPath, flagPath);
|
|
14491
|
+
process.stderr.write(
|
|
14492
|
+
`slack-channel(${codeName}): /restart queued from channel ${hashChannelId(opts.channel)}
|
|
14493
|
+
`
|
|
14494
|
+
);
|
|
14495
|
+
const ack = await postSlackMessage({
|
|
14496
|
+
channel: opts.channel,
|
|
14497
|
+
text: `\u23F3 Restart queued for \`${codeName}\`. The session will reconnect in a few seconds.`,
|
|
14498
|
+
...opts.threadTs ? { thread_ts: opts.threadTs } : {}
|
|
14499
|
+
});
|
|
14500
|
+
if (!ack.ok) {
|
|
14501
|
+
process.stderr.write(
|
|
14502
|
+
`slack-channel(${codeName}): /restart ack send failed: ${ack.error ?? "unknown"}
|
|
14503
|
+
`
|
|
14504
|
+
);
|
|
14505
|
+
}
|
|
14506
|
+
} catch (err) {
|
|
14507
|
+
process.stderr.write(
|
|
14508
|
+
`slack-channel(${codeName}): /restart flag write failed: ${redactAugmentedPaths(err.message)}
|
|
14509
|
+
`
|
|
14510
|
+
);
|
|
14511
|
+
await postSlackMessage({
|
|
14512
|
+
channel: opts.channel,
|
|
14513
|
+
text: `\u274C Failed to queue restart for ${codeName}. Please try again in a few seconds.`,
|
|
14514
|
+
...opts.threadTs ? { thread_ts: opts.threadTs } : {}
|
|
14515
|
+
});
|
|
14516
|
+
}
|
|
14517
|
+
}
|
|
14518
|
+
async function denyUnauthorizedRestart(opts) {
|
|
14519
|
+
const codeName = AGENT_CODE_NAME ?? "unknown";
|
|
14520
|
+
process.stderr.write(
|
|
14521
|
+
`slack-channel(${codeName}): /restart denied \u2014 sender not in SLACK_ALLOWED_USERS, channel ${hashChannelId(opts.channel)}
|
|
14522
|
+
`
|
|
14523
|
+
);
|
|
14524
|
+
await postSlackMessage({
|
|
14525
|
+
channel: opts.channel,
|
|
14526
|
+
text: `\u{1F6AB} \`/restart\` denied \u2014 your Slack user is not in the allowlist for \`${codeName}\`.`,
|
|
14527
|
+
...opts.threadTs ? { thread_ts: opts.threadTs } : {}
|
|
14528
|
+
});
|
|
14197
14529
|
}
|
|
14198
14530
|
var trackedThreads = /* @__PURE__ */ new Map();
|
|
14199
14531
|
var THREAD_STORE_PATH = resolveThreadStorePath();
|
|
@@ -14233,6 +14565,28 @@ if (!BOT_TOKEN || !APP_TOKEN) {
|
|
|
14233
14565
|
);
|
|
14234
14566
|
process.exit(1);
|
|
14235
14567
|
}
|
|
14568
|
+
var slackStderrLogStream = null;
|
|
14569
|
+
if (AGENT_CODE_NAME) {
|
|
14570
|
+
try {
|
|
14571
|
+
const logDir = join2(homedir2(), ".augmented", AGENT_CODE_NAME);
|
|
14572
|
+
mkdirSync2(logDir, { recursive: true });
|
|
14573
|
+
slackStderrLogStream = createWriteStream(join2(logDir, "slack-channel-stderr.log"), {
|
|
14574
|
+
flags: "a",
|
|
14575
|
+
mode: 384
|
|
14576
|
+
});
|
|
14577
|
+
const origWrite = process.stderr.write.bind(process.stderr);
|
|
14578
|
+
process.stderr.write = (chunk, ...rest) => {
|
|
14579
|
+
try {
|
|
14580
|
+
const buf = typeof chunk === "string" ? chunk : String(chunk);
|
|
14581
|
+
const ts = (/* @__PURE__ */ new Date()).toISOString();
|
|
14582
|
+
slackStderrLogStream?.write(`[${ts}] ${buf}${buf.endsWith("\n") ? "" : "\n"}`);
|
|
14583
|
+
} catch {
|
|
14584
|
+
}
|
|
14585
|
+
return origWrite(chunk, ...rest);
|
|
14586
|
+
};
|
|
14587
|
+
} catch {
|
|
14588
|
+
}
|
|
14589
|
+
}
|
|
14236
14590
|
async function getBotUserId() {
|
|
14237
14591
|
try {
|
|
14238
14592
|
const res = await fetch("https://slack.com/api/auth.test", {
|
|
@@ -14268,16 +14622,17 @@ var mcp = new Server(
|
|
|
14268
14622
|
// Attachments rules live near the top because they get chopped otherwise
|
|
14269
14623
|
// and the agent silently loses attachment-handling guidance.
|
|
14270
14624
|
instructions: [
|
|
14271
|
-
|
|
14625
|
+
// Highest-priority lines first — Claude Code truncates this string at
|
|
14626
|
+
// 2048 chars, so anything appended late silently disappears.
|
|
14627
|
+
"CRITICAL: every response to a Slack <channel> tag MUST go through slack.reply. Text in your session WITHOUT a slack.reply call never reaches the user \u2014 the message dies inside the agent process.",
|
|
14628
|
+
'Messages from Slack arrive as <channel source="slack" user="<slack-id>" user_name="<display-name>" channel="..." thread_ts="...">. Pass channel + thread_ts from the tag to slack.reply; always include thread_ts on threaded replies.',
|
|
14272
14629
|
selfIdentityInstruction,
|
|
14273
|
-
"Inbound attachments:
|
|
14274
|
-
"
|
|
14275
|
-
|
|
14276
|
-
"
|
|
14277
|
-
|
|
14278
|
-
"
|
|
14279
|
-
`When a message in a thread is not addressed to you (different @-mention, conversation between others, auto_followed catch-up): SILENTLY SKIP \u2014 no reaction, no reply, do nothing. Do NOT post a "this wasn't for me" message and do NOT add a failure reaction; either is misleading.`,
|
|
14280
|
-
"To deliver a file, save it under your project dir and call slack.upload_file with path, channel, thread_ts."
|
|
14630
|
+
"Inbound attachments: <channel> `files` is a JSON-serialised array \u2014 JSON.parse it. If an entry has `path`, the image is already downloaded \u2014 Read it directly, do NOT call slack.download_attachment. Use that tool only for entries with `file_id` but NO `path` (PDF, docx, csv): pass file_id + channel verbatim, then Read the returned path. Single-image messages also get a top-level `image_path`. Don't surface internal file-handling errors that don't affect the answer.",
|
|
14631
|
+
"Address users by user_name, never by raw user ID. In multi-participant threads the CURRENT speaker is the one on the latest <channel> tag.",
|
|
14632
|
+
'Mentioned in a channel \u2192 respond in that thread. DM \u2192 respond directly. auto_followed="true" \u2192 only reply if you have something useful to add.',
|
|
14633
|
+
"Reaction taxonomy (use slack.react sparingly \u2014 prefer a reply): \u{1F440} = ack (already auto-added on inbound, do not duplicate); \u2705 = success. NEVER react to signal failure \u2014 users can't tell why something failed from an emoji. On failure, slack.reply with one sentence explaining what went wrong (no stack traces, no secrets).",
|
|
14634
|
+
`When a thread message is NOT addressed to you (different @-mention, side conversation, auto_followed catch-up): SILENTLY SKIP \u2014 no reaction, no reply, no "this wasn't for me" message.`,
|
|
14635
|
+
"To deliver a file: save under your project dir, call slack.upload_file with path + channel + thread_ts."
|
|
14281
14636
|
].join(" ")
|
|
14282
14637
|
}
|
|
14283
14638
|
);
|
|
@@ -14876,6 +15231,28 @@ async function connectSocketMode() {
|
|
|
14876
15231
|
const isThreadReply = !!evt.thread_ts && evt.thread_ts !== evt.ts;
|
|
14877
15232
|
const trackTs = evt.thread_ts ?? evt.ts ?? "";
|
|
14878
15233
|
const threadKey = buildThreadKey(evt.channel, trackTs);
|
|
15234
|
+
const rawText = evt.text ?? "";
|
|
15235
|
+
const strippedText = rawText.replace(/^\s*<@[^>]+>\s*/, "").trim();
|
|
15236
|
+
const isRestartCommand = strippedText === "/restart" || strippedText.startsWith("/restart ");
|
|
15237
|
+
if (isRestartCommand) {
|
|
15238
|
+
const senderAllowed = ALLOWED_USERS.size === 0 || evt.user != null && ALLOWED_USERS.has(evt.user);
|
|
15239
|
+
if (!senderAllowed) {
|
|
15240
|
+
await denyUnauthorizedRestart({
|
|
15241
|
+
channel: evt.channel ?? "",
|
|
15242
|
+
threadTs: evt.thread_ts
|
|
15243
|
+
});
|
|
15244
|
+
return;
|
|
15245
|
+
}
|
|
15246
|
+
await handleRestartCommand({
|
|
15247
|
+
channel: evt.channel ?? "",
|
|
15248
|
+
// Only carry thread_ts when the originating message was already
|
|
15249
|
+
// threaded; a top-level command acks in-channel rather than
|
|
15250
|
+
// synthesising a brand-new thread (CodeRabbit feedback).
|
|
15251
|
+
threadTs: evt.thread_ts,
|
|
15252
|
+
ts: evt.ts ?? ""
|
|
15253
|
+
});
|
|
15254
|
+
return;
|
|
15255
|
+
}
|
|
14879
15256
|
if (ALLOWED_USERS.size > 0 && evt.user && !ALLOWED_USERS.has(evt.user)) return;
|
|
14880
15257
|
if (evt.type === "app_mention") {
|
|
14881
15258
|
rememberThread(evt.channel, trackTs, "mentioned");
|