@integrity-labs/agt-cli 0.27.9-test.12 → 0.27.9-test.14
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 +4 -4
- package/dist/chunk-354FAVQR.js +173 -0
- package/dist/chunk-354FAVQR.js.map +1 -0
- package/dist/{chunk-MQJ5DMPT.js → chunk-7GKJZBTB.js} +111 -204
- package/dist/chunk-7GKJZBTB.js.map +1 -0
- package/dist/{chunk-HR5T2RQF.js → chunk-I3YS5WFV.js} +3 -1
- package/dist/chunk-I3YS5WFV.js.map +1 -0
- package/dist/{chunk-SXBZDUKN.js → chunk-R7NKCGWN.js} +341 -46
- package/dist/chunk-R7NKCGWN.js.map +1 -0
- package/dist/{chunk-I2OTQJH3.js → chunk-WOOYOAPG.js} +2644 -2142
- package/dist/chunk-WOOYOAPG.js.map +1 -0
- package/dist/{claude-pair-runtime-F6USL3EW.js → claude-pair-runtime-GIUCD7IG.js} +2 -2
- package/dist/{claude-scheduler-EM24LTGV.js → claude-scheduler-FATCLHDM.js} +2 -2
- package/dist/daily-session-PNQX5URX.js +27 -0
- package/dist/lib/manager-worker.js +1125 -149
- package/dist/lib/manager-worker.js.map +1 -1
- package/dist/mcp/augmented-admin.js +21335 -0
- package/dist/mcp/index.js +121 -4
- package/dist/mcp/slack-channel.js +845 -208
- package/dist/mcp/teams-channel.js +73 -29
- package/dist/mcp/telegram-channel.js +579 -157
- package/dist/{persistent-session-OL5EDYB4.js → persistent-session-35PWSTLO.js} +10 -3
- package/dist/persistent-session-35PWSTLO.js.map +1 -0
- package/dist/responsiveness-probe-MA4M2QM4.js +158 -0
- package/dist/responsiveness-probe-MA4M2QM4.js.map +1 -0
- package/package.json +3 -2
- package/dist/chunk-HR5T2RQF.js.map +0 -1
- package/dist/chunk-I2OTQJH3.js.map +0 -1
- package/dist/chunk-MQJ5DMPT.js.map +0 -1
- package/dist/chunk-SXBZDUKN.js.map +0 -1
- package/dist/responsiveness-probe-B6LJJRUD.js +0 -74
- package/dist/responsiveness-probe-B6LJJRUD.js.map +0 -1
- /package/dist/{claude-pair-runtime-F6USL3EW.js.map → claude-pair-runtime-GIUCD7IG.js.map} +0 -0
- /package/dist/{claude-scheduler-EM24LTGV.js.map → claude-scheduler-FATCLHDM.js.map} +0 -0
- /package/dist/{persistent-session-OL5EDYB4.js.map → daily-session-PNQX5URX.js.map} +0 -0
|
@@ -14211,6 +14211,10 @@ function decideSlackEngagement(input) {
|
|
|
14211
14211
|
function decideSlackAckReaction(input) {
|
|
14212
14212
|
return Boolean(input.channel && input.ts);
|
|
14213
14213
|
}
|
|
14214
|
+
function isRestartSenderAllowed(allowedUsers, userId) {
|
|
14215
|
+
if (allowedUsers.size === 0) return true;
|
|
14216
|
+
return userId != null && allowedUsers.has(userId);
|
|
14217
|
+
}
|
|
14214
14218
|
function extractAugmentedSlackLabel(evt) {
|
|
14215
14219
|
if (evt.metadata?.event_type !== "augmented_agent_message") return null;
|
|
14216
14220
|
return {
|
|
@@ -14301,6 +14305,45 @@ function readDeclineCooldownMs(envVarName, defaultSeconds = 1800) {
|
|
|
14301
14305
|
return parsed * 1e3;
|
|
14302
14306
|
}
|
|
14303
14307
|
|
|
14308
|
+
// src/inbound-access.ts
|
|
14309
|
+
function decideInboundAccess(input) {
|
|
14310
|
+
if (!input.content.forward) {
|
|
14311
|
+
return { kind: "drop", reason: `content:${input.content.reason}` };
|
|
14312
|
+
}
|
|
14313
|
+
if (input.isSelf) {
|
|
14314
|
+
return { kind: "drop", reason: "self" };
|
|
14315
|
+
}
|
|
14316
|
+
if (input.orgBoundary && !input.orgBoundary.forward) {
|
|
14317
|
+
return { kind: "drop", reason: "org_boundary" };
|
|
14318
|
+
}
|
|
14319
|
+
if (input.senderPolicy && !input.senderPolicy.forward) {
|
|
14320
|
+
const reason = input.senderPolicy.reason;
|
|
14321
|
+
const declineWorthy = input.sameOrg && reason !== "internal_only";
|
|
14322
|
+
return {
|
|
14323
|
+
kind: "drop",
|
|
14324
|
+
reason: `sender_policy:${reason}`,
|
|
14325
|
+
decline: declineWorthy ? politeDeclineCopy(reason) : void 0
|
|
14326
|
+
};
|
|
14327
|
+
}
|
|
14328
|
+
if (input.command && !input.isBot) {
|
|
14329
|
+
return {
|
|
14330
|
+
kind: "command",
|
|
14331
|
+
command: input.command.command,
|
|
14332
|
+
authorized: input.command.authorized
|
|
14333
|
+
};
|
|
14334
|
+
}
|
|
14335
|
+
if (input.isBot) {
|
|
14336
|
+
const peer = input.peer;
|
|
14337
|
+
if (!peer || peer.kind === "self") {
|
|
14338
|
+
return { kind: "drop", reason: peer?.kind === "self" ? "self" : "peer:unclassified" };
|
|
14339
|
+
}
|
|
14340
|
+
if (peer.kind === "drop") {
|
|
14341
|
+
return { kind: "drop", reason: `peer:${peer.reason ?? "unspecified"}` };
|
|
14342
|
+
}
|
|
14343
|
+
}
|
|
14344
|
+
return { kind: "admit" };
|
|
14345
|
+
}
|
|
14346
|
+
|
|
14304
14347
|
// src/ack-reaction.ts
|
|
14305
14348
|
import { readdirSync, readFileSync } from "fs";
|
|
14306
14349
|
import { join } from "path";
|
|
@@ -14335,6 +14378,29 @@ function shouldPostUndeliverableNotice(lastNoticeAtMs, nowMs, throttleMs = UNDEL
|
|
|
14335
14378
|
function undeliverableNoticeText() {
|
|
14336
14379
|
return "\u23F3 I can't get to this right now. Please resend in a few minutes \u2014 I may not see this one.";
|
|
14337
14380
|
}
|
|
14381
|
+
var BUSY_ACK_THRESHOLD_MS = 9e4;
|
|
14382
|
+
var BUSY_ACK_NOTICE_THROTTLE_MS = 10 * 60 * 1e3;
|
|
14383
|
+
function channelBusyAckEnabled() {
|
|
14384
|
+
const v = process.env.AGT_CHANNEL_BUSY_ACK_ENABLED;
|
|
14385
|
+
return v === "1" || v === "true";
|
|
14386
|
+
}
|
|
14387
|
+
function channelBusyAckThresholdMs() {
|
|
14388
|
+
const raw = parseInt(process.env.AGT_CHANNEL_BUSY_ACK_THRESHOLD_MS ?? "", 10);
|
|
14389
|
+
return Number.isFinite(raw) && raw > 0 ? raw : BUSY_ACK_THRESHOLD_MS;
|
|
14390
|
+
}
|
|
14391
|
+
function decideBusyAck(i) {
|
|
14392
|
+
if (!i.hasTarget) return false;
|
|
14393
|
+
if (!i.stillPending) return false;
|
|
14394
|
+
if (!Number.isFinite(i.pendingAgeMs)) return false;
|
|
14395
|
+
const threshold = i.thresholdMs ?? channelBusyAckThresholdMs();
|
|
14396
|
+
if (i.pendingAgeMs < threshold) return false;
|
|
14397
|
+
if (!i.sessionAlive) return false;
|
|
14398
|
+
const paneFreshThreshold = i.paneFreshThresholdMs ?? ACK_PANE_FRESH_THRESHOLD_MS;
|
|
14399
|
+
return i.paneLogFreshAgeMs != null && i.paneLogFreshAgeMs <= paneFreshThreshold;
|
|
14400
|
+
}
|
|
14401
|
+
function busyAckNoticeText() {
|
|
14402
|
+
return "\u{1F6E0}\uFE0F I'm in the middle of something right now \u2014 I'll follow up on this as soon as I'm free.";
|
|
14403
|
+
}
|
|
14338
14404
|
var GIVE_UP_SIGNAL_FILENAME = "watchdog-give-up.json";
|
|
14339
14405
|
var GIVE_UP_SIGNAL_MAX_AGE_MS = 30 * 60 * 1e3;
|
|
14340
14406
|
function readGiveUpSignalAtMs(path, now = Date.now()) {
|
|
@@ -14454,6 +14520,81 @@ function probeAgentSessionCached(codeName, ttlMs = SESSION_PROBE_TTL_MS, now = D
|
|
|
14454
14520
|
return value;
|
|
14455
14521
|
}
|
|
14456
14522
|
|
|
14523
|
+
// src/agent-config-state.ts
|
|
14524
|
+
import { existsSync, readFileSync as readFileSync2 } from "fs";
|
|
14525
|
+
import { join as join2 } from "path";
|
|
14526
|
+
var SESSION_STATE_FILENAME = "session-state.json";
|
|
14527
|
+
function readAgentSessionState(stateDir) {
|
|
14528
|
+
if (!stateDir) return null;
|
|
14529
|
+
const path = join2(stateDir, SESSION_STATE_FILENAME);
|
|
14530
|
+
if (!existsSync(path)) return null;
|
|
14531
|
+
try {
|
|
14532
|
+
const parsed = JSON.parse(readFileSync2(path, "utf-8"));
|
|
14533
|
+
if (!parsed || typeof parsed !== "object" || Array.isArray(parsed)) return null;
|
|
14534
|
+
return parsed;
|
|
14535
|
+
} catch {
|
|
14536
|
+
return null;
|
|
14537
|
+
}
|
|
14538
|
+
}
|
|
14539
|
+
function describeSessionOrigin(source) {
|
|
14540
|
+
switch (source) {
|
|
14541
|
+
case "startup":
|
|
14542
|
+
return "fresh start";
|
|
14543
|
+
case "resume":
|
|
14544
|
+
return "resumed";
|
|
14545
|
+
case "clear":
|
|
14546
|
+
return "cleared (/clear)";
|
|
14547
|
+
case "compact":
|
|
14548
|
+
return "compacted";
|
|
14549
|
+
default:
|
|
14550
|
+
return "unknown";
|
|
14551
|
+
}
|
|
14552
|
+
}
|
|
14553
|
+
function formatModelLabel(model) {
|
|
14554
|
+
const trimmed = model?.trim();
|
|
14555
|
+
return trimmed && trimmed.length > 0 ? trimmed : "unknown";
|
|
14556
|
+
}
|
|
14557
|
+
function shortSessionId(id) {
|
|
14558
|
+
const trimmed = id?.trim();
|
|
14559
|
+
if (!trimmed) return "unknown";
|
|
14560
|
+
return trimmed.length > 8 ? trimmed.slice(0, 8) : trimmed;
|
|
14561
|
+
}
|
|
14562
|
+
function formatRelativeAge(ts, now = Date.now()) {
|
|
14563
|
+
if (typeof ts !== "number" || !Number.isFinite(ts) || ts <= 0) return "unknown";
|
|
14564
|
+
const seconds = Math.floor((now - ts) / 1e3);
|
|
14565
|
+
if (seconds < 0) return "unknown";
|
|
14566
|
+
if (seconds < 5) return "just now";
|
|
14567
|
+
if (seconds < 60) return `${seconds}s ago`;
|
|
14568
|
+
if (seconds < 3600) return `${Math.floor(seconds / 60)}m ago`;
|
|
14569
|
+
if (seconds < 86400) return `${Math.floor(seconds / 3600)}h ago`;
|
|
14570
|
+
return `${Math.floor(seconds / 86400)}d ago`;
|
|
14571
|
+
}
|
|
14572
|
+
function formatChannels(channels) {
|
|
14573
|
+
if (!Array.isArray(channels) || channels.length === 0) return "unknown";
|
|
14574
|
+
const cleaned = channels.map((c) => String(c).trim()).filter((c) => c.length > 0);
|
|
14575
|
+
return cleaned.length > 0 ? cleaned.join(", ") : "unknown";
|
|
14576
|
+
}
|
|
14577
|
+
function buildAgentConfigReport(opts) {
|
|
14578
|
+
const { codeName, connectivityLine, state, now = Date.now() } = opts;
|
|
14579
|
+
const lines = [`\u{1F916} *Config for \`${codeName}\`*`, connectivityLine];
|
|
14580
|
+
if (!state) {
|
|
14581
|
+
lines.push("\u2022 *Model:* unknown \u2014 session state not recorded yet");
|
|
14582
|
+
lines.push("\u2022 *Session:* unknown");
|
|
14583
|
+
return lines.join("\n");
|
|
14584
|
+
}
|
|
14585
|
+
lines.push(`\u2022 *Model:* \`${formatModelLabel(state.model)}\``);
|
|
14586
|
+
const origin = describeSessionOrigin(state.source);
|
|
14587
|
+
const age = formatRelativeAge(state.recorded_at, now);
|
|
14588
|
+
const sessionLine = age === "unknown" ? `\u2022 *Session:* ${origin}` : `\u2022 *Session:* ${origin} \xB7 started ${age}`;
|
|
14589
|
+
lines.push(sessionLine);
|
|
14590
|
+
lines.push(`\u2022 *Session ID:* \`${shortSessionId(state.session_id)}\``);
|
|
14591
|
+
if (state.environment && state.environment.trim().length > 0) {
|
|
14592
|
+
lines.push(`\u2022 *Environment:* ${state.environment.trim()}`);
|
|
14593
|
+
}
|
|
14594
|
+
lines.push(`\u2022 *Channels:* ${formatChannels(state.channels)}`);
|
|
14595
|
+
return lines.join("\n");
|
|
14596
|
+
}
|
|
14597
|
+
|
|
14457
14598
|
// src/pane-tail.ts
|
|
14458
14599
|
import { execFile } from "child_process";
|
|
14459
14600
|
import { promisify } from "util";
|
|
@@ -14461,9 +14602,9 @@ import { open, stat } from "fs/promises";
|
|
|
14461
14602
|
|
|
14462
14603
|
// src/channel-attachments.ts
|
|
14463
14604
|
import { homedir } from "os";
|
|
14464
|
-
import { join as
|
|
14605
|
+
import { join as join3, resolve, sep } from "path";
|
|
14465
14606
|
function resolveChannelInboundDir(codeName, channelSlug) {
|
|
14466
|
-
const base =
|
|
14607
|
+
const base = join3(homedir(), ".augmented");
|
|
14467
14608
|
const allowedSegment = /^[A-Za-z0-9_-]+$/;
|
|
14468
14609
|
if (!allowedSegment.test(codeName) || !allowedSegment.test(channelSlug)) {
|
|
14469
14610
|
throw new Error(
|
|
@@ -14889,14 +15030,14 @@ var SLACK_EGRESS_TOOLS = /* @__PURE__ */ new Set([
|
|
|
14889
15030
|
]);
|
|
14890
15031
|
|
|
14891
15032
|
// src/slack-pending-inbound-cleanup.ts
|
|
14892
|
-
import { existsSync, readdirSync as readdirSync2, statSync, unlinkSync } from "fs";
|
|
14893
|
-
import { join as
|
|
15033
|
+
import { existsSync as existsSync2, readdirSync as readdirSync2, statSync, unlinkSync } from "fs";
|
|
15034
|
+
import { join as join4 } from "path";
|
|
14894
15035
|
function sanitizeMarkerSegment(value) {
|
|
14895
15036
|
return value.replace(/[^A-Za-z0-9_-]/g, "_");
|
|
14896
15037
|
}
|
|
14897
15038
|
var defaultClearMarkerFile = (fullPath) => {
|
|
14898
15039
|
try {
|
|
14899
|
-
if (
|
|
15040
|
+
if (existsSync2(fullPath)) unlinkSync(fullPath);
|
|
14900
15041
|
} catch {
|
|
14901
15042
|
}
|
|
14902
15043
|
};
|
|
@@ -14907,7 +15048,7 @@ function clearAllSlackPendingMarkersForThread(dir, channel, threadTs, clear = de
|
|
|
14907
15048
|
try {
|
|
14908
15049
|
for (const f of readdirSync2(dir)) {
|
|
14909
15050
|
if (!f.startsWith(prefix) || !f.endsWith(".json")) continue;
|
|
14910
|
-
clear(
|
|
15051
|
+
clear(join4(dir, f));
|
|
14911
15052
|
cleared += 1;
|
|
14912
15053
|
}
|
|
14913
15054
|
} catch {
|
|
@@ -14922,7 +15063,7 @@ function clearSlackPendingMarkerByMessageTs(dir, channel, messageTs, clear = def
|
|
|
14922
15063
|
try {
|
|
14923
15064
|
for (const f of readdirSync2(dir)) {
|
|
14924
15065
|
if (!f.startsWith(channelPrefix) || !f.endsWith(messageSuffix)) continue;
|
|
14925
|
-
clear(
|
|
15066
|
+
clear(join4(dir, f));
|
|
14926
15067
|
cleared += 1;
|
|
14927
15068
|
}
|
|
14928
15069
|
} catch {
|
|
@@ -14934,7 +15075,7 @@ function clearOldestSlackPendingMarkerInChannel(dir, channel, clear = defaultCle
|
|
|
14934
15075
|
const channelPrefix = `${sanitizeMarkerSegment(channel)}__`;
|
|
14935
15076
|
try {
|
|
14936
15077
|
const entries = readdirSync2(dir).filter((f) => f.startsWith(channelPrefix) && f.endsWith(".json")).map((f) => {
|
|
14937
|
-
const full =
|
|
15078
|
+
const full = join4(dir, f);
|
|
14938
15079
|
let mtime = 0;
|
|
14939
15080
|
try {
|
|
14940
15081
|
mtime = statSync(full).mtimeMs;
|
|
@@ -14951,6 +15092,47 @@ function clearOldestSlackPendingMarkerInChannel(dir, channel, clear = defaultCle
|
|
|
14951
15092
|
}
|
|
14952
15093
|
}
|
|
14953
15094
|
|
|
15095
|
+
// src/restart-confirm.ts
|
|
15096
|
+
import { existsSync as existsSync3, mkdirSync, readFileSync as readFileSync3, renameSync, unlinkSync as unlinkSync2, writeFileSync } from "fs";
|
|
15097
|
+
import { dirname } from "path";
|
|
15098
|
+
import { randomUUID } from "crypto";
|
|
15099
|
+
var RESTART_CONFIRM_MAX_AGE_MS = 10 * 60 * 1e3;
|
|
15100
|
+
function shouldPostRestartConfirm(marker, processBootMs, nowMs) {
|
|
15101
|
+
if (!marker) return false;
|
|
15102
|
+
const requestedAt = marker.requested_at;
|
|
15103
|
+
if (typeof requestedAt !== "number" || !Number.isFinite(requestedAt)) return false;
|
|
15104
|
+
if (requestedAt >= processBootMs) return false;
|
|
15105
|
+
if (nowMs - requestedAt > RESTART_CONFIRM_MAX_AGE_MS) return false;
|
|
15106
|
+
return true;
|
|
15107
|
+
}
|
|
15108
|
+
function buildBackOnlineText(name) {
|
|
15109
|
+
const trimmed = typeof name === "string" ? name.trim() : "";
|
|
15110
|
+
return trimmed ? `Hey ${trimmed}, I'm back online after my restart. \u{1F7E2}` : `I'm back online after my restart. \u{1F7E2}`;
|
|
15111
|
+
}
|
|
15112
|
+
function writeRestartConfirmMarker(filePath, marker) {
|
|
15113
|
+
const dir = dirname(filePath);
|
|
15114
|
+
if (!existsSync3(dir)) mkdirSync(dir, { recursive: true, mode: 448 });
|
|
15115
|
+
const tmpPath = `${filePath}.${process.pid}.${randomUUID()}.tmp`;
|
|
15116
|
+
writeFileSync(tmpPath, JSON.stringify(marker) + "\n", { encoding: "utf8", mode: 384 });
|
|
15117
|
+
renameSync(tmpPath, filePath);
|
|
15118
|
+
}
|
|
15119
|
+
function readRestartConfirmMarker(filePath) {
|
|
15120
|
+
try {
|
|
15121
|
+
if (!existsSync3(filePath)) return null;
|
|
15122
|
+
const parsed = JSON.parse(readFileSync3(filePath, "utf8"));
|
|
15123
|
+
if (!parsed || typeof parsed !== "object") return null;
|
|
15124
|
+
return parsed;
|
|
15125
|
+
} catch {
|
|
15126
|
+
return null;
|
|
15127
|
+
}
|
|
15128
|
+
}
|
|
15129
|
+
function clearRestartConfirmMarker(filePath) {
|
|
15130
|
+
try {
|
|
15131
|
+
if (existsSync3(filePath)) unlinkSync2(filePath);
|
|
15132
|
+
} catch {
|
|
15133
|
+
}
|
|
15134
|
+
}
|
|
15135
|
+
|
|
14954
15136
|
// ../../node_modules/.pnpm/@modelcontextprotocol+sdk@1.27.1_zod@3.25.76/node_modules/@modelcontextprotocol/sdk/dist/esm/server/stdio.js
|
|
14955
15137
|
import process2 from "process";
|
|
14956
15138
|
|
|
@@ -15047,23 +15229,23 @@ var StdioServerTransport = class {
|
|
|
15047
15229
|
import {
|
|
15048
15230
|
chmodSync,
|
|
15049
15231
|
createWriteStream,
|
|
15050
|
-
existsSync as
|
|
15051
|
-
mkdirSync as
|
|
15052
|
-
readFileSync as
|
|
15232
|
+
existsSync as existsSync6,
|
|
15233
|
+
mkdirSync as mkdirSync5,
|
|
15234
|
+
readFileSync as readFileSync7,
|
|
15053
15235
|
readdirSync as readdirSync3,
|
|
15054
|
-
renameSync as
|
|
15236
|
+
renameSync as renameSync3,
|
|
15055
15237
|
statSync as statSync2,
|
|
15056
|
-
unlinkSync as
|
|
15238
|
+
unlinkSync as unlinkSync4,
|
|
15057
15239
|
watch,
|
|
15058
|
-
writeFileSync as
|
|
15240
|
+
writeFileSync as writeFileSync5
|
|
15059
15241
|
} from "fs";
|
|
15060
|
-
import { basename, join as
|
|
15242
|
+
import { basename, join as join6, resolve as resolve2 } from "path";
|
|
15061
15243
|
import { homedir as homedir2 } from "os";
|
|
15062
|
-
import { createHash, randomUUID } from "crypto";
|
|
15244
|
+
import { createHash, randomUUID as randomUUID2 } from "crypto";
|
|
15063
15245
|
|
|
15064
15246
|
// src/slack-thread-store.ts
|
|
15065
|
-
import { mkdirSync, readFileSync as
|
|
15066
|
-
import { dirname } from "path";
|
|
15247
|
+
import { mkdirSync as mkdirSync2, readFileSync as readFileSync4, writeFileSync as writeFileSync2 } from "fs";
|
|
15248
|
+
import { dirname as dirname2 } from "path";
|
|
15067
15249
|
var FILE_VERSION = 1;
|
|
15068
15250
|
var DEFAULT_TTL_DAYS = 30;
|
|
15069
15251
|
var DEFAULT_MIN_INTERVAL_MS = 1e3;
|
|
@@ -15073,7 +15255,7 @@ function loadThreadStore(filePath, opts = {}) {
|
|
|
15073
15255
|
const ttlMs = ttlDays * 24 * 60 * 60 * 1e3;
|
|
15074
15256
|
let raw;
|
|
15075
15257
|
try {
|
|
15076
|
-
raw =
|
|
15258
|
+
raw = readFileSync4(filePath, "utf-8");
|
|
15077
15259
|
} catch {
|
|
15078
15260
|
return { threads: /* @__PURE__ */ new Map(), pruned: 0 };
|
|
15079
15261
|
}
|
|
@@ -15117,8 +15299,8 @@ function createThreadPersister(opts) {
|
|
|
15117
15299
|
let pendingSnapshot = null;
|
|
15118
15300
|
const writeNow = (snap) => {
|
|
15119
15301
|
try {
|
|
15120
|
-
|
|
15121
|
-
|
|
15302
|
+
mkdirSync2(dirname2(opts.filePath), { recursive: true });
|
|
15303
|
+
writeFileSync2(opts.filePath, serializeThreadStore(snap), "utf-8");
|
|
15122
15304
|
lastWriteAt = Date.now();
|
|
15123
15305
|
} catch (err) {
|
|
15124
15306
|
opts.onError?.(
|
|
@@ -15186,6 +15368,72 @@ async function runOrRetry(fn, opts) {
|
|
|
15186
15368
|
}
|
|
15187
15369
|
}
|
|
15188
15370
|
|
|
15371
|
+
// src/slack-bot-photo.ts
|
|
15372
|
+
import { existsSync as existsSync4, mkdirSync as mkdirSync3, readFileSync as readFileSync5, writeFileSync as writeFileSync3 } from "fs";
|
|
15373
|
+
import { dirname as dirname3 } from "path";
|
|
15374
|
+
async function applyBotPhoto(opts) {
|
|
15375
|
+
const fetchImpl = opts.fetchImpl ?? fetch;
|
|
15376
|
+
const log = opts.log ?? ((m) => {
|
|
15377
|
+
process.stderr.write(m);
|
|
15378
|
+
});
|
|
15379
|
+
const { token, avatarUrl, markerPath } = opts;
|
|
15380
|
+
if (markerPath && existsSync4(markerPath)) {
|
|
15381
|
+
try {
|
|
15382
|
+
if (readFileSync5(markerPath, "utf-8").trim() === avatarUrl) {
|
|
15383
|
+
return { status: "skipped-unchanged" };
|
|
15384
|
+
}
|
|
15385
|
+
} catch {
|
|
15386
|
+
}
|
|
15387
|
+
}
|
|
15388
|
+
try {
|
|
15389
|
+
const imgRes = await fetchImpl(avatarUrl, { signal: AbortSignal.timeout(1e4) });
|
|
15390
|
+
if (!imgRes.ok) {
|
|
15391
|
+
log(`slack-channel: avatar download failed (HTTP ${imgRes.status}) \u2014 skipping bot photo
|
|
15392
|
+
`);
|
|
15393
|
+
return { status: "download-failed", httpStatus: imgRes.status };
|
|
15394
|
+
}
|
|
15395
|
+
const bytes = new Uint8Array(await imgRes.arrayBuffer());
|
|
15396
|
+
const form = new FormData();
|
|
15397
|
+
form.append("image", new Blob([bytes], { type: "image/jpeg" }), "avatar.jpg");
|
|
15398
|
+
const res = await fetchImpl("https://slack.com/api/users.setPhoto", {
|
|
15399
|
+
method: "POST",
|
|
15400
|
+
headers: { Authorization: `Bearer ${token}` },
|
|
15401
|
+
body: form,
|
|
15402
|
+
signal: AbortSignal.timeout(15e3)
|
|
15403
|
+
});
|
|
15404
|
+
const data = await res.json();
|
|
15405
|
+
if (!data.ok) {
|
|
15406
|
+
const code = data.error ?? "unknown";
|
|
15407
|
+
if (code === "missing_scope") {
|
|
15408
|
+
if (!opts.warned || !opts.warned.has(code)) {
|
|
15409
|
+
opts.warned?.add(code);
|
|
15410
|
+
log(
|
|
15411
|
+
`slack-channel: users.setPhoto failed (missing_scope) \u2014 bot avatar disabled until users.profile:write is granted (re-OAuth required)
|
|
15412
|
+
`
|
|
15413
|
+
);
|
|
15414
|
+
}
|
|
15415
|
+
} else {
|
|
15416
|
+
log(`slack-channel: users.setPhoto failed (${code}) \u2014 bot avatar not updated
|
|
15417
|
+
`);
|
|
15418
|
+
}
|
|
15419
|
+
return { status: "slack-error", error: code };
|
|
15420
|
+
}
|
|
15421
|
+
if (markerPath) {
|
|
15422
|
+
try {
|
|
15423
|
+
mkdirSync3(dirname3(markerPath), { recursive: true, mode: 448 });
|
|
15424
|
+
writeFileSync3(markerPath, avatarUrl, { mode: 384 });
|
|
15425
|
+
} catch {
|
|
15426
|
+
}
|
|
15427
|
+
}
|
|
15428
|
+
log("slack-channel: bot profile photo updated from agent avatar\n");
|
|
15429
|
+
return { status: "applied" };
|
|
15430
|
+
} catch (err) {
|
|
15431
|
+
log(`slack-channel: applyBotPhotoOnFirstConnect threw: ${err.message}
|
|
15432
|
+
`);
|
|
15433
|
+
return { status: "threw", message: err.message };
|
|
15434
|
+
}
|
|
15435
|
+
}
|
|
15436
|
+
|
|
15189
15437
|
// src/slack-inbound-files.ts
|
|
15190
15438
|
function classifySlackFile(file) {
|
|
15191
15439
|
if (!file || typeof file.id !== "string" || !file.id) return null;
|
|
@@ -15200,6 +15448,178 @@ function buildSafeInboundPath2(codeName, fileId, mimetype) {
|
|
|
15200
15448
|
return buildSafeInboundPath(resolveSlackInboundDir(codeName), fileId, mimetype);
|
|
15201
15449
|
}
|
|
15202
15450
|
|
|
15451
|
+
// src/slack-rich-text.ts
|
|
15452
|
+
var MAX_DEPTH = 12;
|
|
15453
|
+
function isRecord(v) {
|
|
15454
|
+
return typeof v === "object" && v !== null && !Array.isArray(v);
|
|
15455
|
+
}
|
|
15456
|
+
function asString(v) {
|
|
15457
|
+
return typeof v === "string" ? v : "";
|
|
15458
|
+
}
|
|
15459
|
+
function asArray(v) {
|
|
15460
|
+
return Array.isArray(v) ? v : [];
|
|
15461
|
+
}
|
|
15462
|
+
function isTableElement(el) {
|
|
15463
|
+
return isRecord(el) && asString(el.type).includes("table");
|
|
15464
|
+
}
|
|
15465
|
+
function renderEmoji(el) {
|
|
15466
|
+
const unicode = asString(el.unicode);
|
|
15467
|
+
if (unicode) {
|
|
15468
|
+
try {
|
|
15469
|
+
const cps = unicode.split("-").map((h) => parseInt(h, 16));
|
|
15470
|
+
if (cps.every((n) => Number.isFinite(n) && n >= 0 && n <= 1114111)) {
|
|
15471
|
+
return String.fromCodePoint(...cps);
|
|
15472
|
+
}
|
|
15473
|
+
} catch {
|
|
15474
|
+
}
|
|
15475
|
+
}
|
|
15476
|
+
const name = asString(el.name);
|
|
15477
|
+
return name ? `:${name}:` : "";
|
|
15478
|
+
}
|
|
15479
|
+
function renderLeaf(el) {
|
|
15480
|
+
if (!isRecord(el)) return "";
|
|
15481
|
+
switch (asString(el.type)) {
|
|
15482
|
+
case "text":
|
|
15483
|
+
return asString(el.text);
|
|
15484
|
+
case "link": {
|
|
15485
|
+
const label = asString(el.text);
|
|
15486
|
+
const url = asString(el.url);
|
|
15487
|
+
if (label && url && label !== url) return `${label} (${url})`;
|
|
15488
|
+
return label || url;
|
|
15489
|
+
}
|
|
15490
|
+
case "emoji":
|
|
15491
|
+
return renderEmoji(el);
|
|
15492
|
+
case "user":
|
|
15493
|
+
return `<@${asString(el.user_id)}>`;
|
|
15494
|
+
case "usergroup":
|
|
15495
|
+
return `<!subteam^${asString(el.usergroup_id)}>`;
|
|
15496
|
+
case "channel":
|
|
15497
|
+
return `<#${asString(el.channel_id)}>`;
|
|
15498
|
+
case "broadcast":
|
|
15499
|
+
return `@${asString(el.range) || "channel"}`;
|
|
15500
|
+
case "date":
|
|
15501
|
+
return asString(el.fallback) || asString(el.timestamp);
|
|
15502
|
+
default:
|
|
15503
|
+
if (Array.isArray(el.elements)) return renderInline(el.elements);
|
|
15504
|
+
return asString(el.text);
|
|
15505
|
+
}
|
|
15506
|
+
}
|
|
15507
|
+
function renderInline(elements) {
|
|
15508
|
+
return elements.map(renderLeaf).join("");
|
|
15509
|
+
}
|
|
15510
|
+
function deepText(node, depth) {
|
|
15511
|
+
if (depth > MAX_DEPTH) return "";
|
|
15512
|
+
if (typeof node === "string") return node;
|
|
15513
|
+
if (Array.isArray(node)) return node.map((n) => deepText(n, depth + 1)).join("");
|
|
15514
|
+
if (!isRecord(node)) return "";
|
|
15515
|
+
const type = asString(node.type);
|
|
15516
|
+
if (type && type !== "rich_text" && !type.startsWith("rich_text_") && !type.includes("table")) {
|
|
15517
|
+
return renderLeaf(node);
|
|
15518
|
+
}
|
|
15519
|
+
if (Array.isArray(node.elements)) return node.elements.map((n) => deepText(n, depth + 1)).join("");
|
|
15520
|
+
if (Array.isArray(node.rows)) return node.rows.map((n) => deepText(n, depth + 1)).join(" ");
|
|
15521
|
+
return asString(node.text);
|
|
15522
|
+
}
|
|
15523
|
+
function renderCell(cell) {
|
|
15524
|
+
if (!isRecord(cell)) return deepText(cell, 0).replace(/\s*\n\s*/g, " ").trim();
|
|
15525
|
+
const inner = Array.isArray(cell.elements) ? renderSection(cell, 0) : deepText(cell, 0);
|
|
15526
|
+
return inner.replace(/\s*\n\s*/g, " ").trim();
|
|
15527
|
+
}
|
|
15528
|
+
function renderTable(el) {
|
|
15529
|
+
const rawRows = asArray(el.elements).length ? asArray(el.elements) : asArray(el.rows);
|
|
15530
|
+
const rows = [];
|
|
15531
|
+
for (const row of rawRows) {
|
|
15532
|
+
if (!isRecord(row) && !Array.isArray(row)) continue;
|
|
15533
|
+
const rawCells = Array.isArray(row) ? row : asArray(row.elements).length ? asArray(row.elements) : asArray(row.cells);
|
|
15534
|
+
if (rawCells.length === 0) continue;
|
|
15535
|
+
rows.push(rawCells.map(renderCell));
|
|
15536
|
+
}
|
|
15537
|
+
if (rows.length === 0) {
|
|
15538
|
+
const fallback = deepText(el, 0).replace(/[ \t]+\n/g, "\n").trim();
|
|
15539
|
+
return fallback;
|
|
15540
|
+
}
|
|
15541
|
+
const colCount = rows.reduce((max, r) => Math.max(max, r.length), 0);
|
|
15542
|
+
const pad = (r) => {
|
|
15543
|
+
const cells = r.slice();
|
|
15544
|
+
while (cells.length < colCount) cells.push("");
|
|
15545
|
+
return cells.map((c) => c.replace(/\|/g, "\\|"));
|
|
15546
|
+
};
|
|
15547
|
+
const lines = [];
|
|
15548
|
+
const header = rows[0] ?? [];
|
|
15549
|
+
const body = rows.slice(1);
|
|
15550
|
+
lines.push(`| ${pad(header).join(" | ")} |`);
|
|
15551
|
+
lines.push(`| ${Array.from({ length: colCount }, () => "---").join(" | ")} |`);
|
|
15552
|
+
for (const r of body) lines.push(`| ${pad(r).join(" | ")} |`);
|
|
15553
|
+
return lines.join("\n");
|
|
15554
|
+
}
|
|
15555
|
+
function renderSection(section, depth) {
|
|
15556
|
+
if (depth > MAX_DEPTH || !isRecord(section)) return "";
|
|
15557
|
+
const type = asString(section.type);
|
|
15558
|
+
if (isTableElement(section)) return renderTable(section);
|
|
15559
|
+
switch (type) {
|
|
15560
|
+
case "rich_text_section":
|
|
15561
|
+
return renderInline(asArray(section.elements));
|
|
15562
|
+
case "rich_text_preformatted": {
|
|
15563
|
+
const code = renderInline(asArray(section.elements));
|
|
15564
|
+
return code ? `\`\`\`
|
|
15565
|
+
${code}
|
|
15566
|
+
\`\`\`` : "";
|
|
15567
|
+
}
|
|
15568
|
+
case "rich_text_quote": {
|
|
15569
|
+
const quoted = renderInline(asArray(section.elements));
|
|
15570
|
+
return quoted ? quoted.split("\n").map((l) => `> ${l}`).join("\n") : "";
|
|
15571
|
+
}
|
|
15572
|
+
case "rich_text_list": {
|
|
15573
|
+
const ordered = asString(section.style) === "ordered";
|
|
15574
|
+
const items = asArray(section.elements).map((item, i) => {
|
|
15575
|
+
const body = renderSection(item, depth + 1) || renderInline(asArray(item?.elements));
|
|
15576
|
+
const marker = ordered ? `${i + 1}.` : "-";
|
|
15577
|
+
return `${marker} ${body}`.trimEnd();
|
|
15578
|
+
});
|
|
15579
|
+
return items.join("\n");
|
|
15580
|
+
}
|
|
15581
|
+
default:
|
|
15582
|
+
if (Array.isArray(section.elements)) return renderInline(asArray(section.elements));
|
|
15583
|
+
return deepText(section, depth + 1);
|
|
15584
|
+
}
|
|
15585
|
+
}
|
|
15586
|
+
function renderSlackBlocks(blocks) {
|
|
15587
|
+
let hadTable = false;
|
|
15588
|
+
const out = [];
|
|
15589
|
+
try {
|
|
15590
|
+
for (const block of asArray(blocks)) {
|
|
15591
|
+
if (!isRecord(block) || asString(block.type) !== "rich_text") continue;
|
|
15592
|
+
for (const section of asArray(block.elements)) {
|
|
15593
|
+
if (isTableElement(section)) hadTable = true;
|
|
15594
|
+
const rendered = renderSection(section, 0);
|
|
15595
|
+
if (rendered.trim().length > 0) out.push(rendered);
|
|
15596
|
+
}
|
|
15597
|
+
}
|
|
15598
|
+
} catch {
|
|
15599
|
+
}
|
|
15600
|
+
const text = out.join("\n\n").replace(/[ \t]+$/gm, "").replace(/\n{3,}/g, "\n\n").trim();
|
|
15601
|
+
return { text, hadTable };
|
|
15602
|
+
}
|
|
15603
|
+
function mergeInboundText(text, blocks) {
|
|
15604
|
+
const { text: rendered, hadTable } = renderSlackBlocks(blocks);
|
|
15605
|
+
if (!hadTable) return { content: text, recoveredTable: false };
|
|
15606
|
+
const content = rendered.length > 0 ? rendered : text;
|
|
15607
|
+
return { content, recoveredTable: rendered.length > 0 && content !== text };
|
|
15608
|
+
}
|
|
15609
|
+
|
|
15610
|
+
// src/slack-allowlist-source.ts
|
|
15611
|
+
function parseAllowedUsersCsv(raw) {
|
|
15612
|
+
return new Set(
|
|
15613
|
+
(raw ?? "").split(",").map((s) => s.trim()).filter((s) => s.length > 0)
|
|
15614
|
+
);
|
|
15615
|
+
}
|
|
15616
|
+
function extractAllowedUsersFromMcpJson(jsonText) {
|
|
15617
|
+
const parsed = JSON.parse(jsonText);
|
|
15618
|
+
const slackServer = parsed.mcpServers?.["slack"];
|
|
15619
|
+
if (!slackServer) return null;
|
|
15620
|
+
return parseAllowedUsersCsv(slackServer.env?.["SLACK_ALLOWED_USERS"]);
|
|
15621
|
+
}
|
|
15622
|
+
|
|
15203
15623
|
// src/slack-response-mode.ts
|
|
15204
15624
|
var MODES = [
|
|
15205
15625
|
"mention_only",
|
|
@@ -15752,14 +16172,14 @@ function createSlackBotUserIdClient(args) {
|
|
|
15752
16172
|
|
|
15753
16173
|
// src/mcp-spawn-lock.ts
|
|
15754
16174
|
import {
|
|
15755
|
-
existsSync as
|
|
15756
|
-
mkdirSync as
|
|
15757
|
-
readFileSync as
|
|
15758
|
-
renameSync,
|
|
15759
|
-
unlinkSync as
|
|
15760
|
-
writeFileSync as
|
|
16175
|
+
existsSync as existsSync5,
|
|
16176
|
+
mkdirSync as mkdirSync4,
|
|
16177
|
+
readFileSync as readFileSync6,
|
|
16178
|
+
renameSync as renameSync2,
|
|
16179
|
+
unlinkSync as unlinkSync3,
|
|
16180
|
+
writeFileSync as writeFileSync4
|
|
15761
16181
|
} from "fs";
|
|
15762
|
-
import { join as
|
|
16182
|
+
import { join as join5 } from "path";
|
|
15763
16183
|
function defaultIsPidAlive(pid) {
|
|
15764
16184
|
if (!Number.isFinite(pid) || pid <= 0) return false;
|
|
15765
16185
|
try {
|
|
@@ -15777,7 +16197,7 @@ function acquireMcpSpawnLock(args) {
|
|
|
15777
16197
|
const isPidAlive = options.isPidAlive ?? defaultIsPidAlive;
|
|
15778
16198
|
const selfPid = options.selfPid ?? process.pid;
|
|
15779
16199
|
const now = options.now ?? (() => (/* @__PURE__ */ new Date()).toISOString());
|
|
15780
|
-
const path =
|
|
16200
|
+
const path = join5(agentDir, basename2);
|
|
15781
16201
|
const existing = readLockHolder(path);
|
|
15782
16202
|
if (existing) {
|
|
15783
16203
|
if (existing.pid === selfPid) {
|
|
@@ -15787,11 +16207,11 @@ function acquireMcpSpawnLock(args) {
|
|
|
15787
16207
|
return { kind: "blocked", path, holder: existing };
|
|
15788
16208
|
}
|
|
15789
16209
|
}
|
|
15790
|
-
|
|
16210
|
+
mkdirSync4(agentDir, { recursive: true, mode: 448 });
|
|
15791
16211
|
const tmpPath = `${path}.${selfPid}.tmp`;
|
|
15792
16212
|
const payload = { pid: selfPid, started_at: now() };
|
|
15793
|
-
|
|
15794
|
-
|
|
16213
|
+
writeFileSync4(tmpPath, JSON.stringify(payload), { mode: 384 });
|
|
16214
|
+
renameSync2(tmpPath, path);
|
|
15795
16215
|
return { kind: "acquired", path };
|
|
15796
16216
|
}
|
|
15797
16217
|
function releaseMcpSpawnLock(lockPath, opts = {}) {
|
|
@@ -15801,14 +16221,14 @@ function releaseMcpSpawnLock(lockPath, opts = {}) {
|
|
|
15801
16221
|
if (!existing) return;
|
|
15802
16222
|
if (existing.pid !== selfPid) return;
|
|
15803
16223
|
try {
|
|
15804
|
-
|
|
16224
|
+
unlinkSync3(lockPath);
|
|
15805
16225
|
} catch {
|
|
15806
16226
|
}
|
|
15807
16227
|
}
|
|
15808
16228
|
function readLockHolder(path) {
|
|
15809
|
-
if (!
|
|
16229
|
+
if (!existsSync5(path)) return null;
|
|
15810
16230
|
try {
|
|
15811
|
-
const raw =
|
|
16231
|
+
const raw = readFileSync6(path, "utf8");
|
|
15812
16232
|
const parsed = JSON.parse(raw);
|
|
15813
16233
|
const pid = typeof parsed.pid === "number" ? parsed.pid : Number(parsed.pid);
|
|
15814
16234
|
if (!Number.isFinite(pid) || pid <= 0) return null;
|
|
@@ -15823,6 +16243,7 @@ function readLockHolder(path) {
|
|
|
15823
16243
|
var BOT_TOKEN = process.env.SLACK_BOT_TOKEN;
|
|
15824
16244
|
var APP_TOKEN = process.env.SLACK_APP_TOKEN;
|
|
15825
16245
|
var AGENT_CODE_NAME = process.env.AGT_AGENT_CODE_NAME ?? null;
|
|
16246
|
+
var AGENT_AVATAR_URL = process.env.SLACK_AGENT_AVATAR_URL?.trim() || null;
|
|
15826
16247
|
var SLACK_COMMAND_MAX_LENGTH = 32;
|
|
15827
16248
|
function agentSlashCommand(base) {
|
|
15828
16249
|
const codeName = AGENT_CODE_NAME?.trim().toLowerCase();
|
|
@@ -15937,9 +16358,7 @@ async function maybeSendSenderPolicyDecline(args) {
|
|
|
15937
16358
|
var BLOCK_KIT_ENABLED = process.env.SLACK_BLOCK_KIT_ENABLED === "true";
|
|
15938
16359
|
var BLOCK_KIT_ASK_USER_ENABLED = process.env.SLACK_BLOCK_KIT_ASK_USER_ENABLED === "true";
|
|
15939
16360
|
var BLOCK_KIT_DISABLED = process.env.SLACK_BLOCK_KIT_DISABLED === "true";
|
|
15940
|
-
var ALLOWED_USERS =
|
|
15941
|
-
(process.env.SLACK_ALLOWED_USERS ?? "").split(",").map((s) => s.trim()).filter(Boolean)
|
|
15942
|
-
);
|
|
16361
|
+
var ALLOWED_USERS = parseAllowedUsersCsv(process.env.SLACK_ALLOWED_USERS);
|
|
15943
16362
|
var THREAD_AUTO_FOLLOW = process.env.SLACK_THREAD_AUTO_FOLLOW ?? "off";
|
|
15944
16363
|
var CHANNEL_RESPONSE_MODE = parseResponseMode(process.env.SLACK_CHANNEL_RESPONSE_MODE);
|
|
15945
16364
|
var SLACK_PEER_DISABLED_MODE = (() => {
|
|
@@ -15985,10 +16404,34 @@ var SLACK_PEER_CLASSIFIER_CONFIG = {
|
|
|
15985
16404
|
peers: parsePeersEnv(process.env.SLACK_PEERS, process.env.SLACK_PEERS_GATE),
|
|
15986
16405
|
peer_disabled_mode: SLACK_PEER_DISABLED_MODE
|
|
15987
16406
|
};
|
|
15988
|
-
var SLACK_AGENT_DIR = AGENT_CODE_NAME ?
|
|
15989
|
-
var
|
|
15990
|
-
var
|
|
16407
|
+
var SLACK_AGENT_DIR = AGENT_CODE_NAME ? join6(homedir2(), ".augmented", AGENT_CODE_NAME) : null;
|
|
16408
|
+
var SLACK_MCP_CONFIG_PATH = SLACK_AGENT_DIR ? join6(SLACK_AGENT_DIR, "project", ".mcp.json") : null;
|
|
16409
|
+
var liveAllowedUsersCache = null;
|
|
16410
|
+
function readLiveAllowedUsers() {
|
|
16411
|
+
if (!SLACK_MCP_CONFIG_PATH) return null;
|
|
16412
|
+
try {
|
|
16413
|
+
const mtimeMs = statSync2(SLACK_MCP_CONFIG_PATH).mtimeMs;
|
|
16414
|
+
if (liveAllowedUsersCache && liveAllowedUsersCache.mtimeMs === mtimeMs) {
|
|
16415
|
+
return liveAllowedUsersCache.value;
|
|
16416
|
+
}
|
|
16417
|
+
const value = extractAllowedUsersFromMcpJson(
|
|
16418
|
+
readFileSync7(SLACK_MCP_CONFIG_PATH, "utf-8")
|
|
16419
|
+
);
|
|
16420
|
+
if (value === null) return null;
|
|
16421
|
+
liveAllowedUsersCache = { mtimeMs, value };
|
|
16422
|
+
return value;
|
|
16423
|
+
} catch {
|
|
16424
|
+
return null;
|
|
16425
|
+
}
|
|
16426
|
+
}
|
|
16427
|
+
function getEffectiveAllowedUsers() {
|
|
16428
|
+
return readLiveAllowedUsers() ?? ALLOWED_USERS;
|
|
16429
|
+
}
|
|
16430
|
+
var SLACK_PENDING_INBOUND_DIR = SLACK_AGENT_DIR ? join6(SLACK_AGENT_DIR, "slack-pending-inbound") : null;
|
|
16431
|
+
var SLACK_RECOVERY_OUTBOX_DIR = SLACK_AGENT_DIR ? join6(SLACK_AGENT_DIR, "slack-recovery-outbox") : null;
|
|
16432
|
+
var SLACK_RESTART_CONFIRM_FILE = SLACK_AGENT_DIR ? join6(SLACK_AGENT_DIR, "slack-restart-confirm.json") : null;
|
|
15991
16433
|
var SLACK_MAX_RECOVERY_ATTEMPTS = 3;
|
|
16434
|
+
var SLACK_AVATAR_MARKER_PATH = SLACK_AGENT_DIR ? join6(SLACK_AGENT_DIR, "slack-avatar-applied") : null;
|
|
15992
16435
|
function redactSlackId(id) {
|
|
15993
16436
|
if (!id) return "<none>";
|
|
15994
16437
|
return createHash("sha256").update(id).digest("hex").slice(0, 8);
|
|
@@ -15999,7 +16442,7 @@ function safeSlackMarkerName(channel, threadTs, messageTs) {
|
|
|
15999
16442
|
}
|
|
16000
16443
|
function slackPendingInboundPath(channel, threadTs, messageTs) {
|
|
16001
16444
|
if (!SLACK_PENDING_INBOUND_DIR) return null;
|
|
16002
|
-
return
|
|
16445
|
+
return join6(SLACK_PENDING_INBOUND_DIR, safeSlackMarkerName(channel, threadTs, messageTs));
|
|
16003
16446
|
}
|
|
16004
16447
|
function writeSlackPendingInboundMarker(channel, threadTs, messageTs, undeliverable = false) {
|
|
16005
16448
|
const path = slackPendingInboundPath(channel, threadTs, messageTs);
|
|
@@ -16013,8 +16456,8 @@ function writeSlackPendingInboundMarker(channel, threadTs, messageTs, undelivera
|
|
|
16013
16456
|
...undeliverable ? { undeliverable: true } : {}
|
|
16014
16457
|
};
|
|
16015
16458
|
try {
|
|
16016
|
-
|
|
16017
|
-
|
|
16459
|
+
mkdirSync5(SLACK_PENDING_INBOUND_DIR, { recursive: true, mode: 448 });
|
|
16460
|
+
writeFileSync5(path, JSON.stringify(marker), { mode: 384 });
|
|
16018
16461
|
} catch (err) {
|
|
16019
16462
|
process.stderr.write(
|
|
16020
16463
|
`slack-channel(${AGENT_CODE_NAME}): pending-inbound marker write failed: ${err.message}
|
|
@@ -16022,6 +16465,15 @@ function writeSlackPendingInboundMarker(channel, threadTs, messageTs, undelivera
|
|
|
16022
16465
|
);
|
|
16023
16466
|
}
|
|
16024
16467
|
}
|
|
16468
|
+
function readSlackPendingInboundMarker(channel, threadTs, messageTs) {
|
|
16469
|
+
const path = slackPendingInboundPath(channel, threadTs, messageTs);
|
|
16470
|
+
if (!path || !existsSync6(path)) return null;
|
|
16471
|
+
try {
|
|
16472
|
+
return JSON.parse(readFileSync7(path, "utf-8"));
|
|
16473
|
+
} catch {
|
|
16474
|
+
return null;
|
|
16475
|
+
}
|
|
16476
|
+
}
|
|
16025
16477
|
function healSlackUndeliverable(channel, messageTs) {
|
|
16026
16478
|
if (!BOT_TOKEN || !channel || !messageTs) return;
|
|
16027
16479
|
const headers = {
|
|
@@ -16072,10 +16524,71 @@ function postUndeliverableNotice(channel, threadTs, isThreadReply) {
|
|
|
16072
16524
|
function __resetSlackUndeliverableNoticeThrottle() {
|
|
16073
16525
|
lastSlackUndeliverableNoticeAt.clear();
|
|
16074
16526
|
}
|
|
16527
|
+
var lastSlackBusyAckNoticeAt = /* @__PURE__ */ new Map();
|
|
16528
|
+
function postBusyAckNotice(channel, threadTs, isThreadReply) {
|
|
16529
|
+
if (!BOT_TOKEN || !channel) return;
|
|
16530
|
+
const now = Date.now();
|
|
16531
|
+
const conversationKey = isThreadReply && threadTs ? `${channel}:${threadTs}` : channel;
|
|
16532
|
+
if (!shouldPostUndeliverableNotice(
|
|
16533
|
+
lastSlackBusyAckNoticeAt.get(conversationKey),
|
|
16534
|
+
now,
|
|
16535
|
+
BUSY_ACK_NOTICE_THROTTLE_MS
|
|
16536
|
+
)) {
|
|
16537
|
+
return;
|
|
16538
|
+
}
|
|
16539
|
+
lastSlackBusyAckNoticeAt.set(conversationKey, now);
|
|
16540
|
+
fetch("https://slack.com/api/chat.postMessage", {
|
|
16541
|
+
method: "POST",
|
|
16542
|
+
headers: {
|
|
16543
|
+
"Content-Type": "application/json",
|
|
16544
|
+
Authorization: `Bearer ${BOT_TOKEN}`
|
|
16545
|
+
},
|
|
16546
|
+
body: JSON.stringify({
|
|
16547
|
+
channel,
|
|
16548
|
+
text: busyAckNoticeText(),
|
|
16549
|
+
...threadTs ? { thread_ts: threadTs } : {}
|
|
16550
|
+
}),
|
|
16551
|
+
// Bound the request so a hung Slack call can't dangle to the platform
|
|
16552
|
+
// timeout (matches the other bounded REST calls in this file). 10s mirrors
|
|
16553
|
+
// Telegram's busy-ack send; fire-and-forget, an abort is swallowed below.
|
|
16554
|
+
signal: AbortSignal.timeout(1e4)
|
|
16555
|
+
}).catch(() => {
|
|
16556
|
+
});
|
|
16557
|
+
}
|
|
16558
|
+
function scheduleBusyAck(channel, threadTs, messageTs, isThreadReply) {
|
|
16559
|
+
if (!channelBusyAckEnabled()) return;
|
|
16560
|
+
const thresholdMs = channelBusyAckThresholdMs();
|
|
16561
|
+
const timer = setTimeout(() => {
|
|
16562
|
+
const probe = process.env.TMUX && AGENT_CODE_NAME ? probeAgentSessionCached(AGENT_CODE_NAME) : { tmux: "unknown", claude: "unknown" };
|
|
16563
|
+
let paneLogFreshAgeMs = null;
|
|
16564
|
+
if (SLACK_AGENT_DIR) {
|
|
16565
|
+
try {
|
|
16566
|
+
const paneMtimeMs = statSync2(join6(SLACK_AGENT_DIR, "pane.log")).mtimeMs;
|
|
16567
|
+
paneLogFreshAgeMs = Math.max(0, Date.now() - paneMtimeMs);
|
|
16568
|
+
} catch {
|
|
16569
|
+
}
|
|
16570
|
+
}
|
|
16571
|
+
const marker = readSlackPendingInboundMarker(channel, threadTs, messageTs);
|
|
16572
|
+
if (!marker) return;
|
|
16573
|
+
const post = decideBusyAck({
|
|
16574
|
+
hasTarget: Boolean(channel && messageTs),
|
|
16575
|
+
stillPending: true,
|
|
16576
|
+
pendingAgeMs: Math.max(0, Date.now() - Date.parse(marker.received_at)),
|
|
16577
|
+
sessionAlive: probe.tmux === "alive" && probe.claude === "alive",
|
|
16578
|
+
paneLogFreshAgeMs,
|
|
16579
|
+
thresholdMs
|
|
16580
|
+
});
|
|
16581
|
+
if (post) postBusyAckNotice(channel, threadTs, isThreadReply);
|
|
16582
|
+
}, thresholdMs);
|
|
16583
|
+
timer.unref();
|
|
16584
|
+
}
|
|
16585
|
+
function __resetSlackBusyAckNoticeThrottle() {
|
|
16586
|
+
lastSlackBusyAckNoticeAt.clear();
|
|
16587
|
+
}
|
|
16075
16588
|
function clearSlackMarkerFileWithHeal(fullPath) {
|
|
16076
16589
|
let marker = null;
|
|
16077
16590
|
try {
|
|
16078
|
-
marker = JSON.parse(
|
|
16591
|
+
marker = JSON.parse(readFileSync7(fullPath, "utf-8"));
|
|
16079
16592
|
} catch {
|
|
16080
16593
|
}
|
|
16081
16594
|
if (marker && decideRecoveryHeal({
|
|
@@ -16085,7 +16598,7 @@ function clearSlackMarkerFileWithHeal(fullPath) {
|
|
|
16085
16598
|
healSlackUndeliverable(marker.channel, marker.message_ts);
|
|
16086
16599
|
}
|
|
16087
16600
|
try {
|
|
16088
|
-
if (
|
|
16601
|
+
if (existsSync6(fullPath)) unlinkSync4(fullPath);
|
|
16089
16602
|
} catch {
|
|
16090
16603
|
}
|
|
16091
16604
|
}
|
|
@@ -16126,17 +16639,17 @@ function slackNextRetryName(filename) {
|
|
|
16126
16639
|
async function processSlackRecoveryOutboxFile(filename) {
|
|
16127
16640
|
if (!SLACK_RECOVERY_OUTBOX_DIR) return;
|
|
16128
16641
|
if (filename.endsWith(".poison.json") || filename.endsWith(".tmp")) return;
|
|
16129
|
-
const fullPath =
|
|
16642
|
+
const fullPath = join6(SLACK_RECOVERY_OUTBOX_DIR, filename);
|
|
16130
16643
|
let payload;
|
|
16131
16644
|
try {
|
|
16132
|
-
payload = JSON.parse(
|
|
16645
|
+
payload = JSON.parse(readFileSync7(fullPath, "utf-8"));
|
|
16133
16646
|
} catch (err) {
|
|
16134
16647
|
process.stderr.write(
|
|
16135
16648
|
`slack-channel(${AGENT_CODE_NAME}): recovery outbox parse failed (${filename}): ${err.message}
|
|
16136
16649
|
`
|
|
16137
16650
|
);
|
|
16138
16651
|
try {
|
|
16139
|
-
|
|
16652
|
+
renameSync3(fullPath, `${fullPath}.parse-error.poison`);
|
|
16140
16653
|
} catch {
|
|
16141
16654
|
}
|
|
16142
16655
|
return;
|
|
@@ -16147,7 +16660,7 @@ async function processSlackRecoveryOutboxFile(filename) {
|
|
|
16147
16660
|
`
|
|
16148
16661
|
);
|
|
16149
16662
|
try {
|
|
16150
|
-
|
|
16663
|
+
renameSync3(fullPath, `${fullPath}.malformed.poison`);
|
|
16151
16664
|
} catch {
|
|
16152
16665
|
}
|
|
16153
16666
|
return;
|
|
@@ -16195,7 +16708,7 @@ async function processSlackRecoveryOutboxFile(filename) {
|
|
|
16195
16708
|
}
|
|
16196
16709
|
if (sendSucceeded) {
|
|
16197
16710
|
try {
|
|
16198
|
-
|
|
16711
|
+
unlinkSync4(fullPath);
|
|
16199
16712
|
} catch {
|
|
16200
16713
|
}
|
|
16201
16714
|
return;
|
|
@@ -16203,7 +16716,7 @@ async function processSlackRecoveryOutboxFile(filename) {
|
|
|
16203
16716
|
const next = slackNextRetryName(filename);
|
|
16204
16717
|
if (next) {
|
|
16205
16718
|
try {
|
|
16206
|
-
|
|
16719
|
+
renameSync3(fullPath, join6(SLACK_RECOVERY_OUTBOX_DIR, next.next));
|
|
16207
16720
|
if (next.attempt >= SLACK_MAX_RECOVERY_ATTEMPTS) {
|
|
16208
16721
|
process.stderr.write(
|
|
16209
16722
|
`slack-channel(${AGENT_CODE_NAME}): ghost-reply recovery exhausted retries \u2014 moved to ${next.next}
|
|
@@ -16242,7 +16755,7 @@ function scanSlackRecoveryRetries() {
|
|
|
16242
16755
|
if (!f.includes(".retry-") || f.endsWith(".poison.json")) continue;
|
|
16243
16756
|
let mtimeMs;
|
|
16244
16757
|
try {
|
|
16245
|
-
mtimeMs = statSync2(
|
|
16758
|
+
mtimeMs = statSync2(join6(SLACK_RECOVERY_OUTBOX_DIR, f)).mtimeMs;
|
|
16246
16759
|
} catch {
|
|
16247
16760
|
continue;
|
|
16248
16761
|
}
|
|
@@ -16254,7 +16767,7 @@ function scanSlackRecoveryRetries() {
|
|
|
16254
16767
|
function startSlackRecoveryOutboxWatcher() {
|
|
16255
16768
|
if (!SLACK_RECOVERY_OUTBOX_DIR) return;
|
|
16256
16769
|
try {
|
|
16257
|
-
|
|
16770
|
+
mkdirSync5(SLACK_RECOVERY_OUTBOX_DIR, { recursive: true, mode: 448 });
|
|
16258
16771
|
} catch (err) {
|
|
16259
16772
|
process.stderr.write(
|
|
16260
16773
|
`slack-channel(${AGENT_CODE_NAME}): recovery outbox mkdir failed: ${err.message}
|
|
@@ -16272,7 +16785,7 @@ function startSlackRecoveryOutboxWatcher() {
|
|
|
16272
16785
|
const watcher = watch(SLACK_RECOVERY_OUTBOX_DIR, (event, filename) => {
|
|
16273
16786
|
if (event !== "rename" || !filename) return;
|
|
16274
16787
|
if (!isFirstAttemptSlackOutboxFile(filename)) return;
|
|
16275
|
-
if (
|
|
16788
|
+
if (existsSync6(join6(SLACK_RECOVERY_OUTBOX_DIR, filename))) {
|
|
16276
16789
|
void processSlackRecoveryOutboxFile(filename);
|
|
16277
16790
|
}
|
|
16278
16791
|
});
|
|
@@ -16293,7 +16806,7 @@ function trackPendingMessage(channel, threadTs, messageTs, undeliverable = false
|
|
|
16293
16806
|
}
|
|
16294
16807
|
function sweepSlackStaleMarkers(thresholdMs) {
|
|
16295
16808
|
if (!SLACK_PENDING_INBOUND_DIR) return;
|
|
16296
|
-
if (!
|
|
16809
|
+
if (!existsSync6(SLACK_PENDING_INBOUND_DIR)) return;
|
|
16297
16810
|
let filenames;
|
|
16298
16811
|
try {
|
|
16299
16812
|
filenames = readdirSync3(SLACK_PENDING_INBOUND_DIR);
|
|
@@ -16309,17 +16822,17 @@ function sweepSlackStaleMarkers(thresholdMs) {
|
|
|
16309
16822
|
for (const filename of filenames) {
|
|
16310
16823
|
if (!filename.endsWith(".json")) continue;
|
|
16311
16824
|
if (filename.endsWith(".tmp")) continue;
|
|
16312
|
-
const fullPath =
|
|
16825
|
+
const fullPath = join6(SLACK_PENDING_INBOUND_DIR, filename);
|
|
16313
16826
|
let marker;
|
|
16314
16827
|
try {
|
|
16315
|
-
marker = JSON.parse(
|
|
16828
|
+
marker = JSON.parse(readFileSync7(fullPath, "utf-8"));
|
|
16316
16829
|
} catch (err) {
|
|
16317
16830
|
process.stderr.write(
|
|
16318
16831
|
`slack-channel(${AGENT_CODE_NAME}): stale-marker parse failed for ${redactSlackId(filename)}: ${err.message}
|
|
16319
16832
|
`
|
|
16320
16833
|
);
|
|
16321
16834
|
try {
|
|
16322
|
-
|
|
16835
|
+
unlinkSync4(fullPath);
|
|
16323
16836
|
} catch {
|
|
16324
16837
|
}
|
|
16325
16838
|
cleared++;
|
|
@@ -16328,7 +16841,7 @@ function sweepSlackStaleMarkers(thresholdMs) {
|
|
|
16328
16841
|
const { channel, thread_ts, message_ts, received_at } = marker;
|
|
16329
16842
|
if (!channel || !thread_ts || !message_ts || isPendingMarkerStale(received_at, now, thresholdMs)) {
|
|
16330
16843
|
try {
|
|
16331
|
-
|
|
16844
|
+
unlinkSync4(fullPath);
|
|
16332
16845
|
} catch {
|
|
16333
16846
|
}
|
|
16334
16847
|
cleared++;
|
|
@@ -16351,14 +16864,14 @@ var slackOrphanSweepTimer = setInterval(() => {
|
|
|
16351
16864
|
slackOrphanSweepTimer.unref?.();
|
|
16352
16865
|
var lastSlackGiveUpHandledAtMs = null;
|
|
16353
16866
|
function listPendingSlackConversations() {
|
|
16354
|
-
if (!SLACK_PENDING_INBOUND_DIR || !
|
|
16867
|
+
if (!SLACK_PENDING_INBOUND_DIR || !existsSync6(SLACK_PENDING_INBOUND_DIR)) return [];
|
|
16355
16868
|
const byKey = /* @__PURE__ */ new Map();
|
|
16356
16869
|
try {
|
|
16357
16870
|
for (const name of readdirSync3(SLACK_PENDING_INBOUND_DIR)) {
|
|
16358
16871
|
if (!name.endsWith(".json")) continue;
|
|
16359
16872
|
try {
|
|
16360
16873
|
const marker = JSON.parse(
|
|
16361
|
-
|
|
16874
|
+
readFileSync7(join6(SLACK_PENDING_INBOUND_DIR, name), "utf8")
|
|
16362
16875
|
);
|
|
16363
16876
|
if (typeof marker.channel !== "string" || !marker.channel) continue;
|
|
16364
16877
|
if (typeof marker.thread_ts !== "string" || !marker.thread_ts) continue;
|
|
@@ -16408,7 +16921,7 @@ function postSlackWatchdogGiveUpNotice(channel, threadTs, isThreadReply) {
|
|
|
16408
16921
|
}
|
|
16409
16922
|
function checkSlackWatchdogGiveUpNotice() {
|
|
16410
16923
|
if (!SLACK_AGENT_DIR) return;
|
|
16411
|
-
const signalAtMs = readGiveUpSignalAtMs(
|
|
16924
|
+
const signalAtMs = readGiveUpSignalAtMs(join6(SLACK_AGENT_DIR, GIVE_UP_SIGNAL_FILENAME));
|
|
16412
16925
|
const act = decideGiveUpNotice({
|
|
16413
16926
|
signalAtMs,
|
|
16414
16927
|
lastHandledAtMs: lastSlackGiveUpHandledAtMs,
|
|
@@ -16435,7 +16948,7 @@ async function notifyStrandedInboundsOnFirstConnect() {
|
|
|
16435
16948
|
strandedInboundNoticeInFlight = true;
|
|
16436
16949
|
let hadFailure = false;
|
|
16437
16950
|
try {
|
|
16438
|
-
if (!SLACK_PENDING_INBOUND_DIR || !
|
|
16951
|
+
if (!SLACK_PENDING_INBOUND_DIR || !existsSync6(SLACK_PENDING_INBOUND_DIR)) return;
|
|
16439
16952
|
let filenames;
|
|
16440
16953
|
try {
|
|
16441
16954
|
filenames = readdirSync3(SLACK_PENDING_INBOUND_DIR);
|
|
@@ -16448,10 +16961,10 @@ async function notifyStrandedInboundsOnFirstConnect() {
|
|
|
16448
16961
|
let notified = 0;
|
|
16449
16962
|
for (const filename of filenames) {
|
|
16450
16963
|
if (!filename.endsWith(".json")) continue;
|
|
16451
|
-
const fullPath =
|
|
16964
|
+
const fullPath = join6(SLACK_PENDING_INBOUND_DIR, filename);
|
|
16452
16965
|
let marker;
|
|
16453
16966
|
try {
|
|
16454
|
-
marker = JSON.parse(
|
|
16967
|
+
marker = JSON.parse(readFileSync7(fullPath, "utf-8"));
|
|
16455
16968
|
} catch {
|
|
16456
16969
|
continue;
|
|
16457
16970
|
}
|
|
@@ -16464,7 +16977,7 @@ async function notifyStrandedInboundsOnFirstConnect() {
|
|
|
16464
16977
|
const key2 = `${channel}__${thread_ts}`;
|
|
16465
16978
|
if (seen.has(key2)) {
|
|
16466
16979
|
try {
|
|
16467
|
-
|
|
16980
|
+
unlinkSync4(fullPath);
|
|
16468
16981
|
} catch {
|
|
16469
16982
|
}
|
|
16470
16983
|
continue;
|
|
@@ -16478,7 +16991,7 @@ async function notifyStrandedInboundsOnFirstConnect() {
|
|
|
16478
16991
|
if (res.ok) {
|
|
16479
16992
|
notified += 1;
|
|
16480
16993
|
try {
|
|
16481
|
-
|
|
16994
|
+
unlinkSync4(fullPath);
|
|
16482
16995
|
} catch {
|
|
16483
16996
|
}
|
|
16484
16997
|
} else {
|
|
@@ -16500,6 +17013,73 @@ async function notifyStrandedInboundsOnFirstConnect() {
|
|
|
16500
17013
|
strandedInboundNoticeInFlight = false;
|
|
16501
17014
|
}
|
|
16502
17015
|
}
|
|
17016
|
+
var restartConfirmNoticeFired = false;
|
|
17017
|
+
var restartConfirmNoticeInFlight = false;
|
|
17018
|
+
var firstConnectNoticesGated = false;
|
|
17019
|
+
var suppressStrandedForBoot = false;
|
|
17020
|
+
async function notifyRestartCompleteOnFirstConnect() {
|
|
17021
|
+
if (restartConfirmNoticeFired || restartConfirmNoticeInFlight) return;
|
|
17022
|
+
if (!SLACK_RESTART_CONFIRM_FILE) return;
|
|
17023
|
+
restartConfirmNoticeInFlight = true;
|
|
17024
|
+
let hadFailure = false;
|
|
17025
|
+
try {
|
|
17026
|
+
const marker = readRestartConfirmMarker(SLACK_RESTART_CONFIRM_FILE);
|
|
17027
|
+
if (!shouldPostRestartConfirm(marker, SLACK_PROCESS_BOOT_MS, Date.now())) {
|
|
17028
|
+
if (marker) clearRestartConfirmMarker(SLACK_RESTART_CONFIRM_FILE);
|
|
17029
|
+
return;
|
|
17030
|
+
}
|
|
17031
|
+
const channel = marker.reply["channel"];
|
|
17032
|
+
if (channel == null) {
|
|
17033
|
+
clearRestartConfirmMarker(SLACK_RESTART_CONFIRM_FILE);
|
|
17034
|
+
return;
|
|
17035
|
+
}
|
|
17036
|
+
const threadTs = marker.reply["thread_ts"];
|
|
17037
|
+
const res = await postSlackMessage({
|
|
17038
|
+
channel: String(channel),
|
|
17039
|
+
...threadTs != null ? { thread_ts: String(threadTs) } : {},
|
|
17040
|
+
text: buildBackOnlineText(marker.requester_name)
|
|
17041
|
+
});
|
|
17042
|
+
if (res.ok) {
|
|
17043
|
+
clearRestartConfirmMarker(SLACK_RESTART_CONFIRM_FILE);
|
|
17044
|
+
process.stderr.write(
|
|
17045
|
+
`slack-channel(${AGENT_CODE_NAME}): posted back-online confirmation after restart
|
|
17046
|
+
`
|
|
17047
|
+
);
|
|
17048
|
+
} else {
|
|
17049
|
+
hadFailure = true;
|
|
17050
|
+
process.stderr.write(
|
|
17051
|
+
`slack-channel(${AGENT_CODE_NAME}): back-online confirmation failed: ${res.error}
|
|
17052
|
+
`
|
|
17053
|
+
);
|
|
17054
|
+
}
|
|
17055
|
+
} catch (err) {
|
|
17056
|
+
hadFailure = true;
|
|
17057
|
+
process.stderr.write(
|
|
17058
|
+
`slack-channel(${AGENT_CODE_NAME}): back-online confirmation threw: ${redactAugmentedPaths2(err.message)}
|
|
17059
|
+
`
|
|
17060
|
+
);
|
|
17061
|
+
} finally {
|
|
17062
|
+
if (!hadFailure) restartConfirmNoticeFired = true;
|
|
17063
|
+
restartConfirmNoticeInFlight = false;
|
|
17064
|
+
}
|
|
17065
|
+
}
|
|
17066
|
+
function writeSlackRestartConfirm(reply, requesterName) {
|
|
17067
|
+
if (!SLACK_RESTART_CONFIRM_FILE) return;
|
|
17068
|
+
const marker = {
|
|
17069
|
+
source: "slack",
|
|
17070
|
+
requested_at: Date.now(),
|
|
17071
|
+
...requesterName ? { requester_name: requesterName } : {},
|
|
17072
|
+
reply
|
|
17073
|
+
};
|
|
17074
|
+
try {
|
|
17075
|
+
writeRestartConfirmMarker(SLACK_RESTART_CONFIRM_FILE, marker);
|
|
17076
|
+
} catch (err) {
|
|
17077
|
+
process.stderr.write(
|
|
17078
|
+
`slack-channel(${AGENT_CODE_NAME}): restart-confirm marker write failed: ${redactAugmentedPaths2(err.message)}
|
|
17079
|
+
`
|
|
17080
|
+
);
|
|
17081
|
+
}
|
|
17082
|
+
}
|
|
16503
17083
|
function clearPendingMessage(channel, threadTs) {
|
|
16504
17084
|
clearAllSlackPendingMarkersForThread2(channel, threadTs);
|
|
16505
17085
|
}
|
|
@@ -16511,7 +17091,7 @@ function noteThreadActivityByMessageTs(channel, messageTs) {
|
|
|
16511
17091
|
if (!channel || !messageTs) return;
|
|
16512
17092
|
clearPendingMessage(channel, messageTs);
|
|
16513
17093
|
if (!SLACK_PENDING_INBOUND_DIR) return;
|
|
16514
|
-
if (!
|
|
17094
|
+
if (!existsSync6(SLACK_PENDING_INBOUND_DIR)) return;
|
|
16515
17095
|
let filenames;
|
|
16516
17096
|
try {
|
|
16517
17097
|
filenames = readdirSync3(SLACK_PENDING_INBOUND_DIR);
|
|
@@ -16525,10 +17105,10 @@ function noteThreadActivityByMessageTs(channel, messageTs) {
|
|
|
16525
17105
|
for (const filename of filenames) {
|
|
16526
17106
|
if (!filename.startsWith(channelPrefix)) continue;
|
|
16527
17107
|
if (!filename.endsWith(messageSuffix)) continue;
|
|
16528
|
-
clearSlackMarkerFileWithHeal(
|
|
17108
|
+
clearSlackMarkerFileWithHeal(join6(SLACK_PENDING_INBOUND_DIR, filename));
|
|
16529
17109
|
}
|
|
16530
17110
|
}
|
|
16531
|
-
var RESTART_FLAGS_DIR =
|
|
17111
|
+
var RESTART_FLAGS_DIR = join6(homedir2(), ".augmented", "restart-flags");
|
|
16532
17112
|
function buildAugmentedSlackMetadata() {
|
|
16533
17113
|
if (!AGT_TEAM_ID) return void 0;
|
|
16534
17114
|
return {
|
|
@@ -16579,9 +17159,9 @@ function buildSlackHelpMessage(codeName) {
|
|
|
16579
17159
|
`\u{1F916} *Available commands for \`${codeName}\`*`,
|
|
16580
17160
|
"",
|
|
16581
17161
|
"All commands are real Slack slash commands (autocomplete via `/`). For backward compatibility, `/help` and the restart command can also be typed as plain messages in any channel where the bot is present:",
|
|
16582
|
-
|
|
17162
|
+
`\u2022 \`${agentSlashCommand("/help")}\` (or type \`/help\`) \u2014 show this help`,
|
|
16583
17163
|
`\u2022 \`${agentSlashCommand("/restart")}\` \u2014 restart this agent`,
|
|
16584
|
-
`\u2022 \`${agentSlashCommand("/
|
|
17164
|
+
`\u2022 \`${agentSlashCommand("/status")}\` \u2014 this agent's model, session origin, uptime + connectivity`,
|
|
16585
17165
|
"\u2022 `/kill` \u2014 silence all agents in this thread for 6h (use as a thread reply)",
|
|
16586
17166
|
"\u2022 `/unkill` \u2014 clear a kill (use as a thread reply)",
|
|
16587
17167
|
`\u2022 \`${agentSlashCommand("/investigate")}\` \u2014 live tail of this agent's terminal pane (DM only, allowlisted users; works while the channel process is alive \u2014 a wedged host still needs SSM diagnostics)`
|
|
@@ -16642,6 +17222,19 @@ function clearBotStatusBestEffort() {
|
|
|
16642
17222
|
}).catch(() => {
|
|
16643
17223
|
});
|
|
16644
17224
|
}
|
|
17225
|
+
var botPhotoApplyFired = false;
|
|
17226
|
+
var setBotPhotoWarnedErrors = /* @__PURE__ */ new Set();
|
|
17227
|
+
async function applyBotPhotoOnFirstConnect() {
|
|
17228
|
+
if (botPhotoApplyFired) return;
|
|
17229
|
+
if (!BOT_TOKEN || !AGENT_AVATAR_URL) return;
|
|
17230
|
+
botPhotoApplyFired = true;
|
|
17231
|
+
await applyBotPhoto({
|
|
17232
|
+
token: BOT_TOKEN,
|
|
17233
|
+
avatarUrl: AGENT_AVATAR_URL,
|
|
17234
|
+
markerPath: SLACK_AVATAR_MARKER_PATH,
|
|
17235
|
+
warned: setBotPhotoWarnedErrors
|
|
17236
|
+
});
|
|
17237
|
+
}
|
|
16645
17238
|
function formatLastActivity() {
|
|
16646
17239
|
if (lastActivityAt == null || lastActivityKind == null) return "no activity yet";
|
|
16647
17240
|
const seconds = Math.max(0, Math.floor((Date.now() - lastActivityAt) / 1e3));
|
|
@@ -16653,7 +17246,9 @@ function buildAgentStatusReply(codeName) {
|
|
|
16653
17246
|
const connected = currentWs != null && !isShuttingDown;
|
|
16654
17247
|
const dot = connected ? "\u{1F7E2}" : "\u{1F534}";
|
|
16655
17248
|
const state = connected ? "online" : "offline";
|
|
16656
|
-
|
|
17249
|
+
const connectivityLine = `${dot} *${state}* \u2014 Socket Mode ${connected ? "connected" : "disconnected"}. Last activity: ${formatLastActivity()}.`;
|
|
17250
|
+
const sessionState = readAgentSessionState(SLACK_AGENT_DIR);
|
|
17251
|
+
return buildAgentConfigReport({ codeName, connectivityLine, state: sessionState });
|
|
16657
17252
|
}
|
|
16658
17253
|
async function postEphemeralViaResponseUrl(responseUrl, text, logTag) {
|
|
16659
17254
|
try {
|
|
@@ -16775,7 +17370,7 @@ async function handleDebugSlashCommand(payload, responseUrl) {
|
|
|
16775
17370
|
const verdict = evaluateDebugGate({
|
|
16776
17371
|
channelId: payload.channel_id,
|
|
16777
17372
|
userId: payload.user_id,
|
|
16778
|
-
allowedUsers:
|
|
17373
|
+
allowedUsers: getEffectiveAllowedUsers()
|
|
16779
17374
|
});
|
|
16780
17375
|
const investigateCmd = agentSlashCommand("/investigate");
|
|
16781
17376
|
if (!verdict.ok) {
|
|
@@ -16868,16 +17463,17 @@ async function handleSlashCommandEnvelope(payload) {
|
|
|
16868
17463
|
const responseUrl = payload.response_url;
|
|
16869
17464
|
const codeName = AGENT_CODE_NAME ?? "unknown";
|
|
16870
17465
|
if (!command || !responseUrl) return;
|
|
16871
|
-
if (matchesAgentCommand(command, "/agent-status")) {
|
|
17466
|
+
if (matchesAgentCommand(command, "/status") || matchesAgentCommand(command, "/agent-status")) {
|
|
16872
17467
|
await postEphemeralViaResponseUrl(responseUrl, buildAgentStatusReply(codeName), codeName);
|
|
16873
17468
|
return;
|
|
16874
17469
|
}
|
|
16875
|
-
if (command
|
|
17470
|
+
if (matchesAgentCommand(command, "/help")) {
|
|
16876
17471
|
await postEphemeralViaResponseUrl(responseUrl, buildSlackHelpMessage(codeName), codeName);
|
|
16877
17472
|
return;
|
|
16878
17473
|
}
|
|
16879
17474
|
if (matchesAgentCommand(command, "/restart")) {
|
|
16880
|
-
|
|
17475
|
+
const restartAllowedUsers = getEffectiveAllowedUsers();
|
|
17476
|
+
if (restartAllowedUsers.size > 0 && (!payload.user_id || !restartAllowedUsers.has(payload.user_id))) {
|
|
16881
17477
|
process.stderr.write(
|
|
16882
17478
|
`slack-channel(${codeName}): /restart slash-command denied \u2014 user not in SLACK_ALLOWED_USERS
|
|
16883
17479
|
`
|
|
@@ -16898,10 +17494,17 @@ async function handleSlashCommandEnvelope(payload) {
|
|
|
16898
17494
|
return;
|
|
16899
17495
|
}
|
|
16900
17496
|
try {
|
|
16901
|
-
if (!
|
|
16902
|
-
|
|
17497
|
+
if (!existsSync6(RESTART_FLAGS_DIR)) {
|
|
17498
|
+
mkdirSync5(RESTART_FLAGS_DIR, { recursive: true });
|
|
16903
17499
|
}
|
|
16904
|
-
const flagPath =
|
|
17500
|
+
const flagPath = join6(RESTART_FLAGS_DIR, `${codeName}.flag`);
|
|
17501
|
+
writeSlackRestartConfirm(
|
|
17502
|
+
{
|
|
17503
|
+
channel: payload.channel_id,
|
|
17504
|
+
...payload.thread_ts ? { thread_ts: payload.thread_ts } : {}
|
|
17505
|
+
},
|
|
17506
|
+
payload.user_name
|
|
17507
|
+
);
|
|
16905
17508
|
const flag = {
|
|
16906
17509
|
codeName,
|
|
16907
17510
|
source: "slack",
|
|
@@ -16911,9 +17514,9 @@ async function handleSlashCommandEnvelope(payload) {
|
|
|
16911
17514
|
...payload.thread_ts ? { thread_ts: payload.thread_ts } : {}
|
|
16912
17515
|
}
|
|
16913
17516
|
};
|
|
16914
|
-
const tmpPath = `${flagPath}.${process.pid}.${
|
|
16915
|
-
|
|
16916
|
-
|
|
17517
|
+
const tmpPath = `${flagPath}.${process.pid}.${randomUUID2()}.tmp`;
|
|
17518
|
+
writeFileSync5(tmpPath, JSON.stringify(flag) + "\n", "utf8");
|
|
17519
|
+
renameSync3(tmpPath, flagPath);
|
|
16917
17520
|
process.stderr.write(
|
|
16918
17521
|
`slack-channel(${codeName}): /restart slash-command queued from channel ${hashChannelId(payload.channel_id)}
|
|
16919
17522
|
`
|
|
@@ -17009,10 +17612,17 @@ async function handleHelpCommand(opts) {
|
|
|
17009
17612
|
async function handleRestartCommand(opts) {
|
|
17010
17613
|
const codeName = AGENT_CODE_NAME ?? "unknown";
|
|
17011
17614
|
try {
|
|
17012
|
-
if (!
|
|
17013
|
-
|
|
17615
|
+
if (!existsSync6(RESTART_FLAGS_DIR)) {
|
|
17616
|
+
mkdirSync5(RESTART_FLAGS_DIR, { recursive: true });
|
|
17014
17617
|
}
|
|
17015
|
-
const flagPath =
|
|
17618
|
+
const flagPath = join6(RESTART_FLAGS_DIR, `${codeName}.flag`);
|
|
17619
|
+
writeSlackRestartConfirm(
|
|
17620
|
+
{
|
|
17621
|
+
channel: opts.channel,
|
|
17622
|
+
...opts.threadTs ? { thread_ts: opts.threadTs } : {}
|
|
17623
|
+
},
|
|
17624
|
+
opts.requesterName
|
|
17625
|
+
);
|
|
17016
17626
|
const flag = {
|
|
17017
17627
|
codeName,
|
|
17018
17628
|
source: "slack",
|
|
@@ -17023,9 +17633,9 @@ async function handleRestartCommand(opts) {
|
|
|
17023
17633
|
message_ts: opts.ts
|
|
17024
17634
|
}
|
|
17025
17635
|
};
|
|
17026
|
-
const tmpPath = `${flagPath}.${process.pid}.${
|
|
17027
|
-
|
|
17028
|
-
|
|
17636
|
+
const tmpPath = `${flagPath}.${process.pid}.${randomUUID2()}.tmp`;
|
|
17637
|
+
writeFileSync5(tmpPath, JSON.stringify(flag) + "\n", "utf8");
|
|
17638
|
+
renameSync3(tmpPath, flagPath);
|
|
17029
17639
|
process.stderr.write(
|
|
17030
17640
|
`slack-channel(${codeName}): /restart queued from channel ${hashChannelId(opts.channel)}
|
|
17031
17641
|
`
|
|
@@ -17071,7 +17681,7 @@ var THREAD_STORE_TTL_DAYS = parseTtlDays(process.env.SLACK_THREAD_FOLLOW_TTL_DAY
|
|
|
17071
17681
|
var threadPersister = null;
|
|
17072
17682
|
function resolveThreadStorePath() {
|
|
17073
17683
|
if (!AGENT_CODE_NAME) return null;
|
|
17074
|
-
return
|
|
17684
|
+
return join6(homedir2(), ".augmented", AGENT_CODE_NAME, "slack-tracked-threads.json");
|
|
17075
17685
|
}
|
|
17076
17686
|
function parseTtlDays(raw) {
|
|
17077
17687
|
if (!raw) return void 0;
|
|
@@ -17106,9 +17716,9 @@ if (!BOT_TOKEN || !APP_TOKEN) {
|
|
|
17106
17716
|
var slackStderrLogStream = null;
|
|
17107
17717
|
if (AGENT_CODE_NAME) {
|
|
17108
17718
|
try {
|
|
17109
|
-
const logDir =
|
|
17110
|
-
|
|
17111
|
-
slackStderrLogStream = createWriteStream(
|
|
17719
|
+
const logDir = join6(homedir2(), ".augmented", AGENT_CODE_NAME);
|
|
17720
|
+
mkdirSync5(logDir, { recursive: true });
|
|
17721
|
+
slackStderrLogStream = createWriteStream(join6(logDir, "slack-channel-stderr.log"), {
|
|
17112
17722
|
flags: "a",
|
|
17113
17723
|
mode: 384
|
|
17114
17724
|
});
|
|
@@ -17682,7 +18292,7 @@ ${result.formatted}` : "Thread is empty or not found."
|
|
|
17682
18292
|
};
|
|
17683
18293
|
}
|
|
17684
18294
|
size = stat2.size;
|
|
17685
|
-
bytes =
|
|
18295
|
+
bytes = readFileSync7(resolvedPath);
|
|
17686
18296
|
} catch (err) {
|
|
17687
18297
|
return {
|
|
17688
18298
|
content: [{ type: "text", text: `Failed to read file: ${err.message}` }],
|
|
@@ -18320,8 +18930,8 @@ async function downloadSlackFile(fileId, codeName) {
|
|
|
18320
18930
|
if (!isPathInside(savedPath, dir)) {
|
|
18321
18931
|
throw new Error(`refusing to write ${savedPath} outside ${dir}`);
|
|
18322
18932
|
}
|
|
18323
|
-
|
|
18324
|
-
|
|
18933
|
+
mkdirSync5(dir, { recursive: true });
|
|
18934
|
+
writeFileSync5(savedPath, bytes, { mode: 384 });
|
|
18325
18935
|
try {
|
|
18326
18936
|
chmodSync(savedPath, 384);
|
|
18327
18937
|
} catch {
|
|
@@ -18432,7 +19042,19 @@ async function connectSocketMode() {
|
|
|
18432
19042
|
process.stderr.write("slack-channel: Socket Mode connected\n");
|
|
18433
19043
|
recordActivity("connect");
|
|
18434
19044
|
void setBotStatus(":large_green_circle:", "Active");
|
|
18435
|
-
|
|
19045
|
+
if (!firstConnectNoticesGated) {
|
|
19046
|
+
firstConnectNoticesGated = true;
|
|
19047
|
+
suppressStrandedForBoot = SLACK_RESTART_CONFIRM_FILE != null && shouldPostRestartConfirm(
|
|
19048
|
+
readRestartConfirmMarker(SLACK_RESTART_CONFIRM_FILE),
|
|
19049
|
+
SLACK_PROCESS_BOOT_MS,
|
|
19050
|
+
Date.now()
|
|
19051
|
+
);
|
|
19052
|
+
}
|
|
19053
|
+
if (!suppressStrandedForBoot) {
|
|
19054
|
+
void notifyStrandedInboundsOnFirstConnect();
|
|
19055
|
+
}
|
|
19056
|
+
void notifyRestartCompleteOnFirstConnect();
|
|
19057
|
+
void applyBotPhotoOnFirstConnect();
|
|
18436
19058
|
};
|
|
18437
19059
|
ws.onmessage = async (event) => {
|
|
18438
19060
|
try {
|
|
@@ -18486,130 +19108,134 @@ async function connectSocketMode() {
|
|
|
18486
19108
|
const evt = msg.payload.event;
|
|
18487
19109
|
if (evt.user === botUserId) return;
|
|
18488
19110
|
if (evt.type !== "app_mention" && evt.type !== "message") return;
|
|
19111
|
+
const isBot = Boolean(evt.bot_id);
|
|
18489
19112
|
const filterDecision = decideSlackMessageForward(evt);
|
|
18490
|
-
if (!filterDecision.forward) {
|
|
18491
|
-
process.stderr.write(`slack-channel: dropped message event (reason=${filterDecision.reason}, subtype=${evt.subtype ?? "none"}, ts=${evt.ts ?? "n/a"})
|
|
18492
|
-
`);
|
|
18493
|
-
return;
|
|
18494
|
-
}
|
|
18495
19113
|
const senderPolicyDecision = decideSenderPolicyForward(evt, SLACK_SENDER_POLICY);
|
|
18496
|
-
|
|
18497
|
-
|
|
18498
|
-
|
|
18499
|
-
|
|
18500
|
-
|
|
18501
|
-
|
|
18502
|
-
|
|
18503
|
-
|
|
18504
|
-
|
|
18505
|
-
|
|
18506
|
-
|
|
18507
|
-
|
|
18508
|
-
|
|
18509
|
-
|
|
18510
|
-
threadTs: evt.thread_ts,
|
|
18511
|
-
subReason
|
|
18512
|
-
}).catch((err) => {
|
|
18513
|
-
process.stderr.write(
|
|
18514
|
-
`slack-channel(${AGENT_CODE_NAME}): decline reply failed: ${err.message}
|
|
18515
|
-
`
|
|
18516
|
-
);
|
|
18517
|
-
});
|
|
18518
|
-
return;
|
|
18519
|
-
}
|
|
18520
|
-
recordActivity("inbound");
|
|
18521
|
-
const isDirectMessage = evt.channel?.startsWith("D");
|
|
18522
|
-
const isThreadReply = !!evt.thread_ts && evt.thread_ts !== evt.ts;
|
|
18523
|
-
const trackTs = evt.thread_ts ?? evt.ts ?? "";
|
|
18524
|
-
const threadKey = buildThreadKey(evt.channel, trackTs);
|
|
19114
|
+
const policyBlockReason = senderPolicyDecision.forward ? void 0 : classifySlackPolicyBlock(evt, SLACK_SENDER_POLICY);
|
|
19115
|
+
const peerClassification = isBot ? classifyPeerMessage(
|
|
19116
|
+
{
|
|
19117
|
+
text: evt.text,
|
|
19118
|
+
channel: evt.channel ?? "",
|
|
19119
|
+
channel_type: evt.channel_type,
|
|
19120
|
+
user: evt.user,
|
|
19121
|
+
bot_id: evt.bot_id,
|
|
19122
|
+
thread_ts: evt.thread_ts,
|
|
19123
|
+
parent_user_id: evt.parent_user_id
|
|
19124
|
+
},
|
|
19125
|
+
SLACK_PEER_CLASSIFIER_CONFIG,
|
|
19126
|
+
{ bot_user_id: botUserId }
|
|
19127
|
+
) : void 0;
|
|
18525
19128
|
const rawText = evt.text ?? "";
|
|
18526
19129
|
const strippedText = rawText.replace(/^\s*<@[^>]+>\s*/, "").trim();
|
|
18527
19130
|
const restartSuffixed = agentSlashCommand("/restart");
|
|
19131
|
+
const helpSuffixed = agentSlashCommand("/help");
|
|
18528
19132
|
const isRestartCommand = strippedText === "/restart" || strippedText.startsWith("/restart ") || strippedText === restartSuffixed || strippedText.startsWith(`${restartSuffixed} `);
|
|
18529
|
-
const isHelpCommand = strippedText === "/help" || strippedText.startsWith("/help ");
|
|
18530
|
-
|
|
18531
|
-
|
|
18532
|
-
|
|
18533
|
-
|
|
18534
|
-
|
|
18535
|
-
|
|
19133
|
+
const isHelpCommand = strippedText === "/help" || strippedText.startsWith("/help ") || strippedText === helpSuffixed || strippedText.startsWith(`${helpSuffixed} `);
|
|
19134
|
+
const command = isBot ? void 0 : isHelpCommand ? { command: "help", authorized: true } : isRestartCommand ? { command: "restart", authorized: isRestartSenderAllowed(getEffectiveAllowedUsers(), evt.user) } : void 0;
|
|
19135
|
+
const slackHomeTeamId = process.env.SLACK_HOME_TEAM_ID;
|
|
19136
|
+
const sameOrg = !!slackHomeTeamId && evt.team === slackHomeTeamId;
|
|
19137
|
+
const access = decideInboundAccess({
|
|
19138
|
+
content: filterDecision.forward ? { forward: true } : { forward: false, reason: filterDecision.reason },
|
|
19139
|
+
// The agent's own user-echo was already skipped above; peer 'self'
|
|
19140
|
+
// covers the bot-id self case inside the composer.
|
|
19141
|
+
isSelf: false,
|
|
19142
|
+
isBot,
|
|
19143
|
+
peer: peerClassification ? {
|
|
19144
|
+
kind: peerClassification.kind,
|
|
19145
|
+
reason: peerClassification.kind === "drop" ? peerClassification.reason : void 0
|
|
19146
|
+
} : void 0,
|
|
19147
|
+
senderPolicy: senderPolicyDecision.forward ? { forward: true } : { forward: false, reason: policyBlockReason },
|
|
19148
|
+
command,
|
|
19149
|
+
sameOrg
|
|
19150
|
+
});
|
|
19151
|
+
const passedIdentity = access.kind !== "drop" || access.reason.startsWith("peer:");
|
|
19152
|
+
if (passedIdentity) recordActivity("inbound");
|
|
19153
|
+
if (access.kind === "drop") {
|
|
19154
|
+
const channelHash = createHash("sha256").update(evt.channel ?? "").digest("hex").slice(0, 8);
|
|
19155
|
+
process.stderr.write(
|
|
19156
|
+
`slack-channel: inbound drop reason=${access.reason} channel=${channelHash} ts=${redactSlackId(evt.ts)}
|
|
19157
|
+
`
|
|
19158
|
+
);
|
|
19159
|
+
if (access.decline !== void 0 && policyBlockReason) {
|
|
19160
|
+
await maybeSendSenderPolicyDecline({
|
|
19161
|
+
channel: evt.channel,
|
|
19162
|
+
senderId: evt.user,
|
|
19163
|
+
threadTs: evt.thread_ts,
|
|
19164
|
+
subReason: policyBlockReason
|
|
19165
|
+
}).catch((err) => {
|
|
19166
|
+
process.stderr.write(
|
|
19167
|
+
`slack-channel(${AGENT_CODE_NAME}): decline reply failed: ${err.message}
|
|
19168
|
+
`
|
|
19169
|
+
);
|
|
19170
|
+
});
|
|
19171
|
+
}
|
|
19172
|
+
if (access.reason === "peer:cross_team_grant_missing") {
|
|
19173
|
+
crossTeamPeerAuditClient?.emit({
|
|
19174
|
+
event_type: "slack.peer.drop.cross_team_grant_missing",
|
|
19175
|
+
peer_agent_id: null,
|
|
19176
|
+
gate_path: null,
|
|
19177
|
+
grant_id: null,
|
|
19178
|
+
chat_id: evt.channel ?? null,
|
|
19179
|
+
message_id: evt.ts ?? null
|
|
19180
|
+
});
|
|
19181
|
+
}
|
|
18536
19182
|
return;
|
|
18537
19183
|
}
|
|
18538
|
-
if (
|
|
18539
|
-
|
|
18540
|
-
|
|
18541
|
-
await denyUnauthorizedRestart({
|
|
19184
|
+
if (access.kind === "command") {
|
|
19185
|
+
if (access.command === "help") {
|
|
19186
|
+
await handleHelpCommand({
|
|
18542
19187
|
channel: evt.channel ?? "",
|
|
18543
|
-
threadTs: evt.thread_ts
|
|
19188
|
+
threadTs: evt.thread_ts,
|
|
19189
|
+
ts: evt.ts ?? ""
|
|
18544
19190
|
});
|
|
18545
19191
|
return;
|
|
18546
19192
|
}
|
|
18547
|
-
|
|
18548
|
-
|
|
18549
|
-
|
|
18550
|
-
|
|
18551
|
-
|
|
18552
|
-
threadTs: evt.thread_ts,
|
|
18553
|
-
ts: evt.ts ?? ""
|
|
18554
|
-
});
|
|
18555
|
-
return;
|
|
18556
|
-
}
|
|
18557
|
-
if (ALLOWED_USERS.size > 0 && evt.user && !ALLOWED_USERS.has(evt.user)) return;
|
|
18558
|
-
if (evt.bot_id) {
|
|
18559
|
-
const peerMsg = {
|
|
18560
|
-
text: evt.text,
|
|
18561
|
-
channel: evt.channel ?? "",
|
|
18562
|
-
channel_type: evt.channel_type,
|
|
18563
|
-
user: evt.user,
|
|
18564
|
-
bot_id: evt.bot_id,
|
|
18565
|
-
thread_ts: evt.thread_ts,
|
|
18566
|
-
parent_user_id: evt.parent_user_id
|
|
18567
|
-
};
|
|
18568
|
-
const decision = classifyPeerMessage(
|
|
18569
|
-
peerMsg,
|
|
18570
|
-
SLACK_PEER_CLASSIFIER_CONFIG,
|
|
18571
|
-
{ bot_user_id: botUserId }
|
|
18572
|
-
);
|
|
18573
|
-
if (decision.kind === "self") return;
|
|
18574
|
-
if (decision.kind === "drop") {
|
|
18575
|
-
const channelHash = createHash("sha256").update(evt.channel ?? "").digest("hex").slice(0, 8);
|
|
18576
|
-
process.stderr.write(
|
|
18577
|
-
`slack-channel: peer drop reason=${decision.reason} channel=${channelHash}
|
|
18578
|
-
`
|
|
18579
|
-
);
|
|
18580
|
-
if (decision.reason === "cross_team_grant_missing") {
|
|
18581
|
-
crossTeamPeerAuditClient?.emit({
|
|
18582
|
-
event_type: "slack.peer.drop.cross_team_grant_missing",
|
|
18583
|
-
peer_agent_id: null,
|
|
18584
|
-
gate_path: null,
|
|
18585
|
-
grant_id: null,
|
|
18586
|
-
chat_id: evt.channel ?? null,
|
|
18587
|
-
message_id: evt.ts ?? null
|
|
19193
|
+
if (access.command === "restart") {
|
|
19194
|
+
if (!access.authorized) {
|
|
19195
|
+
await denyUnauthorizedRestart({
|
|
19196
|
+
channel: evt.channel ?? "",
|
|
19197
|
+
threadTs: evt.thread_ts
|
|
18588
19198
|
});
|
|
19199
|
+
return;
|
|
18589
19200
|
}
|
|
19201
|
+
const resolvedName = await resolveUserName(evt.user);
|
|
19202
|
+
const requesterName = resolvedName && resolvedName !== evt.user ? resolvedName : void 0;
|
|
19203
|
+
await handleRestartCommand({
|
|
19204
|
+
channel: evt.channel ?? "",
|
|
19205
|
+
// Only carry thread_ts when the originating message was already
|
|
19206
|
+
// threaded; a top-level command acks in-channel.
|
|
19207
|
+
threadTs: evt.thread_ts,
|
|
19208
|
+
ts: evt.ts ?? "",
|
|
19209
|
+
requesterName
|
|
19210
|
+
});
|
|
18590
19211
|
return;
|
|
18591
19212
|
}
|
|
18592
|
-
|
|
18593
|
-
|
|
18594
|
-
|
|
18595
|
-
|
|
18596
|
-
|
|
18597
|
-
|
|
18598
|
-
|
|
18599
|
-
|
|
18600
|
-
|
|
18601
|
-
|
|
18602
|
-
|
|
18603
|
-
|
|
18604
|
-
|
|
18605
|
-
|
|
18606
|
-
|
|
18607
|
-
|
|
18608
|
-
|
|
18609
|
-
|
|
18610
|
-
|
|
18611
|
-
|
|
18612
|
-
|
|
19213
|
+
return;
|
|
19214
|
+
}
|
|
19215
|
+
const isDirectMessage = evt.channel?.startsWith("D");
|
|
19216
|
+
const isThreadReply = !!evt.thread_ts && evt.thread_ts !== evt.ts;
|
|
19217
|
+
const trackTs = evt.thread_ts ?? evt.ts ?? "";
|
|
19218
|
+
const threadKey = buildThreadKey(evt.channel, trackTs);
|
|
19219
|
+
if (isBot && peerClassification?.kind === "peer-ingress") {
|
|
19220
|
+
const peerGate = peerClassification.peer.gate_path;
|
|
19221
|
+
if (peerGate === "intra_org_unrestricted") {
|
|
19222
|
+
crossTeamPeerAuditClient?.emit({
|
|
19223
|
+
event_type: "slack.peer.ingress.cross_team",
|
|
19224
|
+
peer_agent_id: peerClassification.peer.agent_id || null,
|
|
19225
|
+
gate_path: peerGate,
|
|
19226
|
+
grant_id: null,
|
|
19227
|
+
chat_id: evt.channel ?? null,
|
|
19228
|
+
message_id: evt.ts ?? null
|
|
19229
|
+
});
|
|
19230
|
+
} else if (typeof peerGate === "string" && peerGate.startsWith("grant:")) {
|
|
19231
|
+
crossTeamPeerAuditClient?.emit({
|
|
19232
|
+
event_type: "slack.peer.ingress.cross_team",
|
|
19233
|
+
peer_agent_id: peerClassification.peer.agent_id || null,
|
|
19234
|
+
gate_path: peerGate,
|
|
19235
|
+
grant_id: peerGate.slice("grant:".length),
|
|
19236
|
+
chat_id: evt.channel ?? null,
|
|
19237
|
+
message_id: evt.ts ?? null
|
|
19238
|
+
});
|
|
18613
19239
|
}
|
|
18614
19240
|
}
|
|
18615
19241
|
if (evt.type === "app_mention") {
|
|
@@ -18645,7 +19271,7 @@ async function connectSocketMode() {
|
|
|
18645
19271
|
let paneLogFreshAgeMs = null;
|
|
18646
19272
|
if (SLACK_AGENT_DIR) {
|
|
18647
19273
|
try {
|
|
18648
|
-
const paneMtimeMs = statSync2(
|
|
19274
|
+
const paneMtimeMs = statSync2(join6(SLACK_AGENT_DIR, "pane.log")).mtimeMs;
|
|
18649
19275
|
paneLogFreshAgeMs = Math.max(0, Date.now() - paneMtimeMs);
|
|
18650
19276
|
} catch {
|
|
18651
19277
|
}
|
|
@@ -18676,6 +19302,9 @@ async function connectSocketMode() {
|
|
|
18676
19302
|
}
|
|
18677
19303
|
if (channel && ts && shouldEngage) {
|
|
18678
19304
|
trackPendingMessage(channel, threadTs, ts, ackDecision === "undeliverable");
|
|
19305
|
+
if (ackDecision === "ack") {
|
|
19306
|
+
scheduleBusyAck(channel, threadTs, ts, isThreadReply);
|
|
19307
|
+
}
|
|
18679
19308
|
}
|
|
18680
19309
|
const userName = await resolveUserName(user);
|
|
18681
19310
|
const fileMeta = await buildInboundFileMeta(evt.files, AGENT_CODE_NAME, channel);
|
|
@@ -18706,10 +19335,17 @@ async function connectSocketMode() {
|
|
|
18706
19335
|
);
|
|
18707
19336
|
}
|
|
18708
19337
|
}
|
|
19338
|
+
const { content: inboundContent, recoveredTable } = mergeInboundText(text, evt.blocks);
|
|
19339
|
+
if (recoveredTable) {
|
|
19340
|
+
process.stderr.write(
|
|
19341
|
+
`slack-channel(${AGENT_CODE_NAME}): recovered rich_text table from blocks (channel=${redactSlackId(channel)} ts=${redactSlackId(ts)})
|
|
19342
|
+
`
|
|
19343
|
+
);
|
|
19344
|
+
}
|
|
18709
19345
|
await mcp.notification({
|
|
18710
19346
|
method: "notifications/claude/channel",
|
|
18711
19347
|
params: {
|
|
18712
|
-
content:
|
|
19348
|
+
content: inboundContent,
|
|
18713
19349
|
meta: {
|
|
18714
19350
|
user,
|
|
18715
19351
|
user_name: userName,
|
|
@@ -18843,6 +19479,7 @@ if (THREAD_STORE_PATH) {
|
|
|
18843
19479
|
}
|
|
18844
19480
|
connectSocketModeSafely();
|
|
18845
19481
|
export {
|
|
19482
|
+
__resetSlackBusyAckNoticeThrottle,
|
|
18846
19483
|
__resetSlackGiveUpNoticeStateForTests,
|
|
18847
19484
|
__resetSlackUndeliverableNoticeThrottle
|
|
18848
19485
|
};
|