@integrity-labs/agt-cli 0.28.237 → 0.28.239
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-PYVEJMY2.js → chunk-GO4VMK2P.js} +37 -4
- package/dist/chunk-GO4VMK2P.js.map +1 -0
- package/dist/{chunk-DN4HSL6M.js → chunk-O3MVFYYC.js} +298 -9
- package/dist/chunk-O3MVFYYC.js.map +1 -0
- package/dist/{claude-pair-runtime-2ZFH6K4I.js → claude-pair-runtime-422CI7SM.js} +2 -2
- package/dist/lib/manager-worker.js +22 -11
- package/dist/lib/manager-worker.js.map +1 -1
- package/dist/mcp/direct-chat-channel.js +63 -21
- package/dist/mcp/origami.js +40510 -0
- package/dist/mcp/slack-channel.js +551 -100
- package/dist/mcp/telegram-channel.js +111 -69
- package/dist/{persistent-session-LVDRHYX7.js → persistent-session-RRCU7CQJ.js} +2 -2
- package/dist/{responsiveness-probe-UW5TXKMS.js → responsiveness-probe-K5HFFW5F.js} +118 -3
- package/dist/responsiveness-probe-K5HFFW5F.js.map +1 -0
- package/package.json +3 -2
- package/dist/chunk-DN4HSL6M.js.map +0 -1
- package/dist/chunk-PYVEJMY2.js.map +0 -1
- package/dist/responsiveness-probe-UW5TXKMS.js.map +0 -1
- /package/dist/{claude-pair-runtime-2ZFH6K4I.js.map → claude-pair-runtime-422CI7SM.js.map} +0 -0
- /package/dist/{persistent-session-LVDRHYX7.js.map → persistent-session-RRCU7CQJ.js.map} +0 -0
|
@@ -16256,18 +16256,18 @@ import {
|
|
|
16256
16256
|
ftruncateSync,
|
|
16257
16257
|
mkdirSync as mkdirSync8,
|
|
16258
16258
|
openSync,
|
|
16259
|
-
readFileSync as
|
|
16260
|
-
readdirSync as
|
|
16259
|
+
readFileSync as readFileSync14,
|
|
16260
|
+
readdirSync as readdirSync4,
|
|
16261
16261
|
renameSync as renameSync4,
|
|
16262
16262
|
statSync as statSync2,
|
|
16263
16263
|
unlinkSync as unlinkSync5,
|
|
16264
16264
|
watch,
|
|
16265
|
-
writeFileSync as
|
|
16265
|
+
writeFileSync as writeFileSync12,
|
|
16266
16266
|
writeSync
|
|
16267
16267
|
} from "fs";
|
|
16268
|
-
import { basename, join as
|
|
16268
|
+
import { basename, join as join13, resolve as resolve2 } from "path";
|
|
16269
16269
|
import { homedir as homedir4 } from "os";
|
|
16270
|
-
import { createHash, randomUUID as randomUUID2 } from "crypto";
|
|
16270
|
+
import { createHash as createHash2, randomUUID as randomUUID2 } from "crypto";
|
|
16271
16271
|
|
|
16272
16272
|
// src/slack-thread-store.ts
|
|
16273
16273
|
import { mkdirSync as mkdirSync3, readFileSync as readFileSync5, writeFileSync as writeFileSync4 } from "fs";
|
|
@@ -16549,8 +16549,46 @@ async function runOrRetry(fn, opts) {
|
|
|
16549
16549
|
}
|
|
16550
16550
|
|
|
16551
16551
|
// src/turn-initiator-marker.ts
|
|
16552
|
-
import { writeFileSync as writeFileSync6, mkdirSync as mkdirSync5, renameSync as renameSync2 } from "fs";
|
|
16553
|
-
import { dirname as dirname4 } from "path";
|
|
16552
|
+
import { writeFileSync as writeFileSync6, readFileSync as readFileSync7, mkdirSync as mkdirSync5, renameSync as renameSync2 } from "fs";
|
|
16553
|
+
import { dirname as dirname4, join as join7 } from "path";
|
|
16554
|
+
var TURN_INITIATOR_MAX_AGE_MS = 5 * 60 * 1e3;
|
|
16555
|
+
var TURN_INITIATOR_LEDGER_MAX_ENTRIES = 20;
|
|
16556
|
+
var TURN_INITIATOR_LEDGER_FILENAME = ".turn-initiator-ledger.json";
|
|
16557
|
+
function turnInitiatorLedgerPath(singleSlotFile) {
|
|
16558
|
+
return join7(dirname4(singleSlotFile), TURN_INITIATOR_LEDGER_FILENAME);
|
|
16559
|
+
}
|
|
16560
|
+
function foldTurnInitiatorLedger(existing, marker, maxAgeMs = TURN_INITIATOR_MAX_AGE_MS, maxEntries = TURN_INITIATOR_LEDGER_MAX_ENTRIES) {
|
|
16561
|
+
const now = marker.ts;
|
|
16562
|
+
const key2 = (e) => `${e.channel}\0${e.channel_ref ?? ""}`;
|
|
16563
|
+
const markerKey = key2(marker);
|
|
16564
|
+
const prior = existing && Array.isArray(existing.entries) ? existing.entries : [];
|
|
16565
|
+
const kept = prior.filter(
|
|
16566
|
+
(e) => e && typeof e.ts === "number" && Number.isFinite(e.ts) && now - e.ts <= maxAgeMs && now - e.ts >= 0 && key2(e) !== markerKey
|
|
16567
|
+
// the same thread is replaced by the new entry below
|
|
16568
|
+
);
|
|
16569
|
+
kept.push({
|
|
16570
|
+
channel: marker.channel,
|
|
16571
|
+
sender_id: marker.sender_id,
|
|
16572
|
+
...marker.sender_name ? { sender_name: marker.sender_name } : {},
|
|
16573
|
+
...marker.channel_ref ? { channel_ref: marker.channel_ref } : {},
|
|
16574
|
+
ts: marker.ts
|
|
16575
|
+
});
|
|
16576
|
+
kept.sort((a, b) => b.ts - a.ts);
|
|
16577
|
+
return { v: 1, entries: kept.slice(0, maxEntries) };
|
|
16578
|
+
}
|
|
16579
|
+
function updateTurnInitiatorLedger(singleSlotFile, marker) {
|
|
16580
|
+
const ledgerFile = turnInitiatorLedgerPath(singleSlotFile);
|
|
16581
|
+
let existing = null;
|
|
16582
|
+
try {
|
|
16583
|
+
const parsed = JSON.parse(readFileSync7(ledgerFile, "utf8"));
|
|
16584
|
+
if (parsed && parsed.v === 1 && Array.isArray(parsed.entries)) existing = parsed;
|
|
16585
|
+
} catch {
|
|
16586
|
+
}
|
|
16587
|
+
const next = foldTurnInitiatorLedger(existing, marker);
|
|
16588
|
+
const tmp = `${ledgerFile}.tmp`;
|
|
16589
|
+
writeFileSync6(tmp, JSON.stringify(next), "utf8");
|
|
16590
|
+
renameSync2(tmp, ledgerFile);
|
|
16591
|
+
}
|
|
16554
16592
|
function writeTurnInitiatorMarker(input) {
|
|
16555
16593
|
const file = process.env["AGT_TURN_INITIATOR_FILE"];
|
|
16556
16594
|
if (!file || !input.sender_id) return;
|
|
@@ -16560,12 +16598,16 @@ function writeTurnInitiatorMarker(input) {
|
|
|
16560
16598
|
const tmp = `${file}.tmp`;
|
|
16561
16599
|
writeFileSync6(tmp, JSON.stringify(marker), "utf8");
|
|
16562
16600
|
renameSync2(tmp, file);
|
|
16601
|
+
try {
|
|
16602
|
+
updateTurnInitiatorLedger(file, marker);
|
|
16603
|
+
} catch {
|
|
16604
|
+
}
|
|
16563
16605
|
} catch {
|
|
16564
16606
|
}
|
|
16565
16607
|
}
|
|
16566
16608
|
|
|
16567
16609
|
// src/slack-bot-photo.ts
|
|
16568
|
-
import { existsSync as existsSync6, mkdirSync as mkdirSync6, readFileSync as
|
|
16610
|
+
import { existsSync as existsSync6, mkdirSync as mkdirSync6, readFileSync as readFileSync8, writeFileSync as writeFileSync7 } from "fs";
|
|
16569
16611
|
import { dirname as dirname5 } from "path";
|
|
16570
16612
|
async function applyBotPhoto(opts) {
|
|
16571
16613
|
const fetchImpl = opts.fetchImpl ?? fetch;
|
|
@@ -16575,7 +16617,7 @@ async function applyBotPhoto(opts) {
|
|
|
16575
16617
|
const { token, avatarUrl, markerPath } = opts;
|
|
16576
16618
|
if (markerPath && existsSync6(markerPath)) {
|
|
16577
16619
|
try {
|
|
16578
|
-
if (
|
|
16620
|
+
if (readFileSync8(markerPath, "utf-8").trim() === avatarUrl) {
|
|
16579
16621
|
return { status: "skipped-unchanged" };
|
|
16580
16622
|
}
|
|
16581
16623
|
} catch {
|
|
@@ -16700,8 +16742,8 @@ function conversationalLaneMeta(expectsReply = true) {
|
|
|
16700
16742
|
}
|
|
16701
16743
|
|
|
16702
16744
|
// src/inbound-lane-telemetry.ts
|
|
16703
|
-
import { readFileSync as
|
|
16704
|
-
import { join as
|
|
16745
|
+
import { readFileSync as readFileSync9, writeFileSync as writeFileSync8 } from "fs";
|
|
16746
|
+
import { join as join8 } from "path";
|
|
16705
16747
|
var LANE_CLASSIFICATION_COUNTER_SUFFIX = "-lane-classifications.json";
|
|
16706
16748
|
var SUSPECTED_MISCLASSIFICATION_KEY = "suspected_misclassification";
|
|
16707
16749
|
var HUMAN_CHANNEL_SOURCES = /* @__PURE__ */ new Set([
|
|
@@ -16718,10 +16760,10 @@ function isSuspectedMisclassification(lane, source) {
|
|
|
16718
16760
|
}
|
|
16719
16761
|
function recordLaneClassification(agentDir, channel, classification) {
|
|
16720
16762
|
if (!agentDir) return;
|
|
16721
|
-
const path =
|
|
16763
|
+
const path = join8(agentDir, `${channel}${LANE_CLASSIFICATION_COUNTER_SUFFIX}`);
|
|
16722
16764
|
let counts = {};
|
|
16723
16765
|
try {
|
|
16724
|
-
const parsed = JSON.parse(
|
|
16766
|
+
const parsed = JSON.parse(readFileSync9(path, "utf-8"));
|
|
16725
16767
|
if (parsed && typeof parsed === "object") counts = parsed;
|
|
16726
16768
|
} catch {
|
|
16727
16769
|
}
|
|
@@ -16736,6 +16778,333 @@ function recordLaneClassification(agentDir, channel, classification) {
|
|
|
16736
16778
|
}
|
|
16737
16779
|
}
|
|
16738
16780
|
|
|
16781
|
+
// src/slack-inbound-registry.ts
|
|
16782
|
+
import { readdirSync as readdirSync3, readFileSync as readFileSync10 } from "fs";
|
|
16783
|
+
import { join as join9 } from "path";
|
|
16784
|
+
var DEFAULT_MAX_ENTRIES = 500;
|
|
16785
|
+
var DEFAULT_CLEARED_TTL_MS = 6 * 60 * 60 * 1e3;
|
|
16786
|
+
function entryKey(channel, messageTs) {
|
|
16787
|
+
return `${channel}\0${messageTs}`;
|
|
16788
|
+
}
|
|
16789
|
+
function createInboundRegistry(opts = {}) {
|
|
16790
|
+
const maxEntries = opts.maxEntries ?? DEFAULT_MAX_ENTRIES;
|
|
16791
|
+
const clearedTtlMs = opts.clearedTtlMs ?? DEFAULT_CLEARED_TTL_MS;
|
|
16792
|
+
const now = opts.now ?? Date.now;
|
|
16793
|
+
const entries = /* @__PURE__ */ new Map();
|
|
16794
|
+
function prune() {
|
|
16795
|
+
const cutoff = now() - clearedTtlMs;
|
|
16796
|
+
for (const [key2, e] of entries) {
|
|
16797
|
+
if (e.clearedAt !== void 0 && e.clearedAt < cutoff) entries.delete(key2);
|
|
16798
|
+
}
|
|
16799
|
+
if (entries.size <= maxEntries) return;
|
|
16800
|
+
for (const [key2, e] of entries) {
|
|
16801
|
+
if (entries.size <= maxEntries) break;
|
|
16802
|
+
if (e.clearedAt !== void 0) entries.delete(key2);
|
|
16803
|
+
}
|
|
16804
|
+
for (const key2 of entries.keys()) {
|
|
16805
|
+
if (entries.size <= maxEntries) break;
|
|
16806
|
+
entries.delete(key2);
|
|
16807
|
+
}
|
|
16808
|
+
}
|
|
16809
|
+
function record2(i) {
|
|
16810
|
+
const key2 = entryKey(i.channel, i.messageTs);
|
|
16811
|
+
entries.delete(key2);
|
|
16812
|
+
entries.set(key2, { ...i });
|
|
16813
|
+
prune();
|
|
16814
|
+
}
|
|
16815
|
+
function markCleared(channel, messageTs) {
|
|
16816
|
+
const e = entries.get(entryKey(channel, messageTs));
|
|
16817
|
+
if (e && e.clearedAt === void 0) e.clearedAt = now();
|
|
16818
|
+
}
|
|
16819
|
+
function markClearedThread(channel, threadTs) {
|
|
16820
|
+
const t = now();
|
|
16821
|
+
for (const e of entries.values()) {
|
|
16822
|
+
if (e.channel === channel && e.threadTs === threadTs && e.clearedAt === void 0) {
|
|
16823
|
+
e.clearedAt = t;
|
|
16824
|
+
}
|
|
16825
|
+
}
|
|
16826
|
+
}
|
|
16827
|
+
function markClearedOldestInChannel(channel) {
|
|
16828
|
+
let oldest = null;
|
|
16829
|
+
for (const e of entries.values()) {
|
|
16830
|
+
if (e.channel !== channel || e.clearedAt !== void 0) continue;
|
|
16831
|
+
if (oldest === null || e.receivedAt < oldest.receivedAt) oldest = e;
|
|
16832
|
+
}
|
|
16833
|
+
if (oldest) oldest.clearedAt = now();
|
|
16834
|
+
}
|
|
16835
|
+
function byInboundId(id) {
|
|
16836
|
+
if (!id) return null;
|
|
16837
|
+
for (const e of entries.values()) {
|
|
16838
|
+
if (e.inboundId === id) return e;
|
|
16839
|
+
}
|
|
16840
|
+
return null;
|
|
16841
|
+
}
|
|
16842
|
+
function byCoords(channel, threadOrMessageTs) {
|
|
16843
|
+
let best = null;
|
|
16844
|
+
let bestExact = false;
|
|
16845
|
+
for (const e of entries.values()) {
|
|
16846
|
+
if (e.channel !== channel) continue;
|
|
16847
|
+
const exact = e.messageTs === threadOrMessageTs;
|
|
16848
|
+
const thread = e.threadTs === threadOrMessageTs;
|
|
16849
|
+
if (!exact && !thread) continue;
|
|
16850
|
+
if (best === null) {
|
|
16851
|
+
best = e;
|
|
16852
|
+
bestExact = exact;
|
|
16853
|
+
continue;
|
|
16854
|
+
}
|
|
16855
|
+
if (exact && !bestExact) {
|
|
16856
|
+
best = e;
|
|
16857
|
+
bestExact = true;
|
|
16858
|
+
} else if (exact === bestExact && e.receivedAt >= best.receivedAt) {
|
|
16859
|
+
best = e;
|
|
16860
|
+
}
|
|
16861
|
+
}
|
|
16862
|
+
return best;
|
|
16863
|
+
}
|
|
16864
|
+
function livePending() {
|
|
16865
|
+
const out = [];
|
|
16866
|
+
for (const e of entries.values()) {
|
|
16867
|
+
if (e.clearedAt === void 0) out.push(e);
|
|
16868
|
+
}
|
|
16869
|
+
return out;
|
|
16870
|
+
}
|
|
16871
|
+
function seedFromMarkerDir(dir) {
|
|
16872
|
+
let names;
|
|
16873
|
+
try {
|
|
16874
|
+
names = readdirSync3(dir);
|
|
16875
|
+
} catch {
|
|
16876
|
+
return;
|
|
16877
|
+
}
|
|
16878
|
+
for (const name of names) {
|
|
16879
|
+
if (!name.endsWith(".json") || name.startsWith(".")) continue;
|
|
16880
|
+
if (name.includes(".retry-") || name.includes(".poison")) continue;
|
|
16881
|
+
let marker;
|
|
16882
|
+
try {
|
|
16883
|
+
marker = JSON.parse(readFileSync10(join9(dir, name), "utf-8"));
|
|
16884
|
+
} catch {
|
|
16885
|
+
continue;
|
|
16886
|
+
}
|
|
16887
|
+
const channel = typeof marker.channel === "string" ? marker.channel : "";
|
|
16888
|
+
const threadTs = typeof marker.thread_ts === "string" ? marker.thread_ts : "";
|
|
16889
|
+
const messageTs = typeof marker.message_ts === "string" ? marker.message_ts : "";
|
|
16890
|
+
if (!channel || !messageTs) continue;
|
|
16891
|
+
const receivedRaw = typeof marker.received_at === "string" ? Date.parse(marker.received_at) : NaN;
|
|
16892
|
+
record2({
|
|
16893
|
+
inboundId: typeof marker.inbound_id === "string" ? marker.inbound_id : "",
|
|
16894
|
+
channel,
|
|
16895
|
+
threadTs: threadTs || messageTs,
|
|
16896
|
+
// top-level markers may omit thread_ts
|
|
16897
|
+
messageTs,
|
|
16898
|
+
user: "",
|
|
16899
|
+
// markers don't persist the sender - unknown after restart
|
|
16900
|
+
userName: "",
|
|
16901
|
+
receivedAt: Number.isFinite(receivedRaw) ? receivedRaw : now()
|
|
16902
|
+
});
|
|
16903
|
+
}
|
|
16904
|
+
}
|
|
16905
|
+
return {
|
|
16906
|
+
record: record2,
|
|
16907
|
+
markCleared,
|
|
16908
|
+
markClearedThread,
|
|
16909
|
+
markClearedOldestInChannel,
|
|
16910
|
+
byInboundId,
|
|
16911
|
+
byCoords,
|
|
16912
|
+
livePending,
|
|
16913
|
+
seedFromMarkerDir,
|
|
16914
|
+
size: () => entries.size
|
|
16915
|
+
};
|
|
16916
|
+
}
|
|
16917
|
+
|
|
16918
|
+
// src/slack-inbound-id.ts
|
|
16919
|
+
import { createHash } from "crypto";
|
|
16920
|
+
var INBOUND_ID_HEX_LEN = 12;
|
|
16921
|
+
function slackInboundId(channel, threadTs, messageTs) {
|
|
16922
|
+
const digest = createHash("sha256").update(`${channel}|${threadTs}|${messageTs}`).digest("hex").slice(0, INBOUND_ID_HEX_LEN);
|
|
16923
|
+
return `ib_${digest}`;
|
|
16924
|
+
}
|
|
16925
|
+
|
|
16926
|
+
// src/slack-reply-binding.ts
|
|
16927
|
+
function matchByCoords(registry2, channel, messageTs, threadTs) {
|
|
16928
|
+
if (!channel) return null;
|
|
16929
|
+
return (messageTs ? registry2.byCoords(channel, messageTs) : null) ?? (threadTs ? registry2.byCoords(channel, threadTs) : null);
|
|
16930
|
+
}
|
|
16931
|
+
function describeLivePending(registry2, now) {
|
|
16932
|
+
const live = registry2.livePending();
|
|
16933
|
+
if (live.length === 0) return "none";
|
|
16934
|
+
return live.slice(0, 8).map((i) => {
|
|
16935
|
+
const ageS = Math.max(0, Math.floor((now - i.receivedAt) / 1e3));
|
|
16936
|
+
const who = i.userName || i.user || "unknown";
|
|
16937
|
+
const id = i.inboundId || "(no id)";
|
|
16938
|
+
return `${id} (${who}, ${ageS}s ago)`;
|
|
16939
|
+
}).join("; ");
|
|
16940
|
+
}
|
|
16941
|
+
function resolveReplyBinding(input) {
|
|
16942
|
+
const { mode, args, registry: registry2 } = input;
|
|
16943
|
+
const now = input.now ?? Date.now();
|
|
16944
|
+
const modelChannel = args.channel;
|
|
16945
|
+
const modelThread = args.thread_ts;
|
|
16946
|
+
if (args.inbound_id) {
|
|
16947
|
+
const known = registry2.byInboundId(args.inbound_id);
|
|
16948
|
+
if (known) {
|
|
16949
|
+
const conflict = !!modelChannel && modelChannel !== known.channel || !!modelThread && modelThread !== known.threadTs;
|
|
16950
|
+
return {
|
|
16951
|
+
channel: known.channel,
|
|
16952
|
+
threadTs: known.threadTs,
|
|
16953
|
+
classification: conflict ? "coords_corrected" : "bound",
|
|
16954
|
+
unknownInboundId: false
|
|
16955
|
+
};
|
|
16956
|
+
}
|
|
16957
|
+
if (args.proactive === true) {
|
|
16958
|
+
return {
|
|
16959
|
+
channel: modelChannel,
|
|
16960
|
+
threadTs: modelThread,
|
|
16961
|
+
classification: "proactive",
|
|
16962
|
+
unknownInboundId: true
|
|
16963
|
+
};
|
|
16964
|
+
}
|
|
16965
|
+
return classifyByCoords(mode, args, registry2, now, true);
|
|
16966
|
+
}
|
|
16967
|
+
if (args.proactive === true) {
|
|
16968
|
+
return {
|
|
16969
|
+
channel: modelChannel,
|
|
16970
|
+
threadTs: modelThread,
|
|
16971
|
+
classification: "proactive",
|
|
16972
|
+
unknownInboundId: false
|
|
16973
|
+
};
|
|
16974
|
+
}
|
|
16975
|
+
return classifyByCoords(mode, args, registry2, now, false);
|
|
16976
|
+
}
|
|
16977
|
+
function classifyByCoords(mode, args, registry2, now, unknownInboundId) {
|
|
16978
|
+
const modelChannel = args.channel;
|
|
16979
|
+
const modelThread = args.thread_ts;
|
|
16980
|
+
const matched = matchByCoords(registry2, modelChannel, args.message_ts, modelThread);
|
|
16981
|
+
if (matched) {
|
|
16982
|
+
return {
|
|
16983
|
+
channel: modelChannel ?? matched.channel,
|
|
16984
|
+
threadTs: modelThread ?? matched.threadTs,
|
|
16985
|
+
classification: "coords_match",
|
|
16986
|
+
unknownInboundId
|
|
16987
|
+
};
|
|
16988
|
+
}
|
|
16989
|
+
if (registry2.size() === 0) {
|
|
16990
|
+
return {
|
|
16991
|
+
channel: modelChannel,
|
|
16992
|
+
threadTs: modelThread,
|
|
16993
|
+
classification: "legacy",
|
|
16994
|
+
unknownInboundId
|
|
16995
|
+
};
|
|
16996
|
+
}
|
|
16997
|
+
const decision = {
|
|
16998
|
+
channel: modelChannel,
|
|
16999
|
+
threadTs: modelThread,
|
|
17000
|
+
classification: "unknown_target",
|
|
17001
|
+
unknownInboundId
|
|
17002
|
+
};
|
|
17003
|
+
if (mode === "warn") {
|
|
17004
|
+
decision.agentFeedback = `Note: this reply does not match any message you received. If it is a self-initiated message set proactive:true; otherwise reply with the inbound_id of the message you are answering. Live inbounds awaiting a reply: ${describeLivePending(registry2, now)}.`;
|
|
17005
|
+
} else if (mode === "enforce") {
|
|
17006
|
+
decision.reject = `This reply was blocked: its target does not match any message you received, so it may be going to the wrong thread. Reply with the inbound_id of the message you are answering, or set proactive:true for a self-initiated message. Live inbounds: ${describeLivePending(registry2, now)}.`;
|
|
17007
|
+
}
|
|
17008
|
+
return decision;
|
|
17009
|
+
}
|
|
17010
|
+
|
|
17011
|
+
// src/slack-mcp-instructions.ts
|
|
17012
|
+
function buildSlackMcpInstructions(opts) {
|
|
17013
|
+
const { ackReaction, skipReactionOn, skipReaction } = opts;
|
|
17014
|
+
return [
|
|
17015
|
+
"CRITICAL: every response to a Slack <channel> tag MUST go through slack.reply - typed text never reaches the user. Slow work: interim ack (slack.reply interim:true), then your FINAL answer as a separate slack.reply (interim omitted). An ack is not the answer.",
|
|
17016
|
+
`Inbound: <channel ... inbound_id="ib_..." message_ts="..." [thread_context="..."]>. Reply with slack.reply passing inbound_id (copy it verbatim from the tag you are answering) + text; the server routes it to the right thread. Multiple <channel> tags = separate conversations: NEVER answer one user in another's inbound_id/thread. thread_context = pre-loaded thread; ground replies ONLY in it. For a self-initiated message (not a reply) set proactive:true.`,
|
|
17017
|
+
"Attachments: <channel> `files` is a JSON string - JSON.parse it. Entry with `path` (or a top-level `image_path`) is already downloaded - Read it; do NOT call slack.download_attachment. Only entries with `file_id` and no `path` (PDF/docx/csv) need slack.download_attachment (pass file_id + channel), then Read the returned path. Don't surface file-handling errors that don't affect the answer.",
|
|
17018
|
+
"Address users by user_name, never raw user ID. In multi-participant threads the CURRENT speaker is on the latest <channel> tag.",
|
|
17019
|
+
`Reactions (slack.react sparingly - prefer a reply): :${ackReaction}: = ack (auto-added on inbound, don't duplicate); \u2705 = success.` + (skipReactionOn ? ` "${skipReaction}" = seen, not replying.` : "") + " NEVER react to signal failure of your work - on failure slack.reply one sentence explaining it (no stack traces/secrets). \u23F3 and \u274C are system-only; never add them.",
|
|
17020
|
+
skipReactionOn ? `Decided NOT to reply to a DM or thread message (different @-mention, side chat, auto_followed catch-up, nothing useful)? slack.react with "${skipReaction}" so the sender knows you saw it - never post "this wasn't for me". Top-level channel messages you may skip silently.` : "A thread message NOT addressed to you (different @-mention, side chat, auto_followed catch-up): SILENTLY SKIP - no reaction, no reply.",
|
|
17021
|
+
'auto_followed="true" \u2192 reply only if useful OR your bot user is @-mentioned. Deliver a file: save under your project dir, call slack.upload_file with path + channel + inbound_id.'
|
|
17022
|
+
].join(" ");
|
|
17023
|
+
}
|
|
17024
|
+
|
|
17025
|
+
// src/slack-reply-binding-telemetry.ts
|
|
17026
|
+
import { readFileSync as readFileSync11, writeFileSync as writeFileSync9 } from "fs";
|
|
17027
|
+
import { join as join10 } from "path";
|
|
17028
|
+
var REPLY_BINDING_CLASSIFICATION_COUNTER_SUFFIX = "-reply-binding-classifications.json";
|
|
17029
|
+
var UNKNOWN_INBOUND_ID_KEY = "unknown_inbound_id";
|
|
17030
|
+
var VALID_MODES = /* @__PURE__ */ new Set(["shadow", "warn", "enforce"]);
|
|
17031
|
+
function slackReplyBindingMode() {
|
|
17032
|
+
const raw = process.env["AGT_SLACK_REPLY_BINDING"];
|
|
17033
|
+
if (raw && VALID_MODES.has(raw)) return raw;
|
|
17034
|
+
return "shadow";
|
|
17035
|
+
}
|
|
17036
|
+
function recordReplyBindingClassification(agentDir, channel, input) {
|
|
17037
|
+
if (!agentDir) return;
|
|
17038
|
+
const path = join10(agentDir, `${channel}${REPLY_BINDING_CLASSIFICATION_COUNTER_SUFFIX}`);
|
|
17039
|
+
let counts = {};
|
|
17040
|
+
try {
|
|
17041
|
+
const parsed = JSON.parse(readFileSync11(path, "utf-8"));
|
|
17042
|
+
if (parsed && typeof parsed === "object") counts = parsed;
|
|
17043
|
+
} catch {
|
|
17044
|
+
}
|
|
17045
|
+
counts[input.classification] = (counts[input.classification] ?? 0) + 1;
|
|
17046
|
+
if (input.unknownInboundId) {
|
|
17047
|
+
counts[UNKNOWN_INBOUND_ID_KEY] = (counts[UNKNOWN_INBOUND_ID_KEY] ?? 0) + 1;
|
|
17048
|
+
}
|
|
17049
|
+
try {
|
|
17050
|
+
writeFileSync9(path, JSON.stringify(counts), { mode: 384 });
|
|
17051
|
+
} catch {
|
|
17052
|
+
}
|
|
17053
|
+
}
|
|
17054
|
+
|
|
17055
|
+
// src/slack-reply-target-telemetry.ts
|
|
17056
|
+
import { readFileSync as readFileSync12, writeFileSync as writeFileSync10 } from "fs";
|
|
17057
|
+
import { join as join11 } from "path";
|
|
17058
|
+
var REPLY_TARGET_CLASSIFICATION_COUNTER_SUFFIX = "-reply-target-classifications.json";
|
|
17059
|
+
function pendingThreadsBucket(n) {
|
|
17060
|
+
if (n <= 0) return "0";
|
|
17061
|
+
if (n === 1) return "1";
|
|
17062
|
+
if (n === 2) return "2";
|
|
17063
|
+
return "3plus";
|
|
17064
|
+
}
|
|
17065
|
+
function replyTargetKey(result, bucket) {
|
|
17066
|
+
return `${result}|${bucket}`;
|
|
17067
|
+
}
|
|
17068
|
+
function classifyReplyTarget(input) {
|
|
17069
|
+
const { channel, threadTs, messageTs, registry: registry2 } = input;
|
|
17070
|
+
const pending = registry2.livePending();
|
|
17071
|
+
const pendingThreadKeys = new Set(pending.map((p) => `${p.channel} ${p.threadTs}`));
|
|
17072
|
+
const pendingThreads = pendingThreadKeys.size;
|
|
17073
|
+
const targetThread = threadTs ?? messageTs;
|
|
17074
|
+
const targetThreadPending = targetThread !== void 0 && pendingThreadKeys.has(`${channel} ${targetThread}`);
|
|
17075
|
+
const matched = (messageTs ? registry2.byCoords(channel, messageTs) : null) ?? (threadTs ? registry2.byCoords(channel, threadTs) : null);
|
|
17076
|
+
let result;
|
|
17077
|
+
if (!matched) {
|
|
17078
|
+
result = "unknown_target";
|
|
17079
|
+
} else if (matched.clearedAt === void 0 || targetThreadPending) {
|
|
17080
|
+
result = "matched_pending";
|
|
17081
|
+
} else if (pendingThreads > 0) {
|
|
17082
|
+
result = "crossed_candidate";
|
|
17083
|
+
} else {
|
|
17084
|
+
result = "matched_recent";
|
|
17085
|
+
}
|
|
17086
|
+
return { result, pendingThreads };
|
|
17087
|
+
}
|
|
17088
|
+
function recordReplyTargetClassification(agentDir, channel, classification) {
|
|
17089
|
+
if (!agentDir) return;
|
|
17090
|
+
const path = join11(agentDir, `${channel}${REPLY_TARGET_CLASSIFICATION_COUNTER_SUFFIX}`);
|
|
17091
|
+
let counts = {};
|
|
17092
|
+
try {
|
|
17093
|
+
const parsed = JSON.parse(readFileSync12(path, "utf-8"));
|
|
17094
|
+
if (parsed && typeof parsed === "object") counts = parsed;
|
|
17095
|
+
} catch {
|
|
17096
|
+
}
|
|
17097
|
+
const key2 = replyTargetKey(
|
|
17098
|
+
classification.result,
|
|
17099
|
+
pendingThreadsBucket(classification.pendingThreads)
|
|
17100
|
+
);
|
|
17101
|
+
counts[key2] = (counts[key2] ?? 0) + 1;
|
|
17102
|
+
try {
|
|
17103
|
+
writeFileSync10(path, JSON.stringify(counts), { mode: 384 });
|
|
17104
|
+
} catch {
|
|
17105
|
+
}
|
|
17106
|
+
}
|
|
17107
|
+
|
|
16739
17108
|
// src/slack-allowlist-source.ts
|
|
16740
17109
|
function parseAllowedUsersCsv(raw) {
|
|
16741
17110
|
return new Set(
|
|
@@ -17402,12 +17771,12 @@ async function actuateHostRestart(opts) {
|
|
|
17402
17771
|
import {
|
|
17403
17772
|
existsSync as existsSync7,
|
|
17404
17773
|
mkdirSync as mkdirSync7,
|
|
17405
|
-
readFileSync as
|
|
17774
|
+
readFileSync as readFileSync13,
|
|
17406
17775
|
renameSync as renameSync3,
|
|
17407
17776
|
unlinkSync as unlinkSync4,
|
|
17408
|
-
writeFileSync as
|
|
17777
|
+
writeFileSync as writeFileSync11
|
|
17409
17778
|
} from "fs";
|
|
17410
|
-
import { join as
|
|
17779
|
+
import { join as join12 } from "path";
|
|
17411
17780
|
function defaultIsPidAlive(pid) {
|
|
17412
17781
|
if (!Number.isFinite(pid) || pid <= 0) return false;
|
|
17413
17782
|
try {
|
|
@@ -17425,7 +17794,7 @@ function acquireMcpSpawnLock(args) {
|
|
|
17425
17794
|
const isPidAlive = options.isPidAlive ?? defaultIsPidAlive;
|
|
17426
17795
|
const selfPid = options.selfPid ?? process.pid;
|
|
17427
17796
|
const now = options.now ?? (() => (/* @__PURE__ */ new Date()).toISOString());
|
|
17428
|
-
const path =
|
|
17797
|
+
const path = join12(agentDir, basename2);
|
|
17429
17798
|
const existing = readLockHolder(path);
|
|
17430
17799
|
if (existing) {
|
|
17431
17800
|
if (existing.pid === selfPid) {
|
|
@@ -17438,7 +17807,7 @@ function acquireMcpSpawnLock(args) {
|
|
|
17438
17807
|
mkdirSync7(agentDir, { recursive: true, mode: 448 });
|
|
17439
17808
|
const tmpPath = `${path}.${selfPid}.tmp`;
|
|
17440
17809
|
const payload = { pid: selfPid, started_at: now() };
|
|
17441
|
-
|
|
17810
|
+
writeFileSync11(tmpPath, JSON.stringify(payload), { mode: 384 });
|
|
17442
17811
|
renameSync3(tmpPath, path);
|
|
17443
17812
|
return { kind: "acquired", path };
|
|
17444
17813
|
}
|
|
@@ -17456,7 +17825,7 @@ function releaseMcpSpawnLock(lockPath, opts = {}) {
|
|
|
17456
17825
|
function readLockHolder(path) {
|
|
17457
17826
|
if (!existsSync7(path)) return null;
|
|
17458
17827
|
try {
|
|
17459
|
-
const raw =
|
|
17828
|
+
const raw = readFileSync13(path, "utf8");
|
|
17460
17829
|
const parsed = JSON.parse(raw);
|
|
17461
17830
|
const pid = typeof parsed.pid === "number" ? parsed.pid : Number(parsed.pid);
|
|
17462
17831
|
if (!Number.isFinite(pid) || pid <= 0) return null;
|
|
@@ -17690,8 +18059,8 @@ var SLACK_TEAM_PEER_USER_IDS = parseTeamPeerUserIdsEnv(
|
|
|
17690
18059
|
process.env.SLACK_TEAM_PEER_USER_IDS
|
|
17691
18060
|
);
|
|
17692
18061
|
var PEER_HINT_SEEN = /* @__PURE__ */ new Set();
|
|
17693
|
-
var SLACK_AGENT_DIR = AGENT_CODE_NAME ?
|
|
17694
|
-
var SLACK_MCP_CONFIG_PATH = SLACK_AGENT_DIR ?
|
|
18062
|
+
var SLACK_AGENT_DIR = AGENT_CODE_NAME ? join13(homedir4(), ".augmented", AGENT_CODE_NAME) : null;
|
|
18063
|
+
var SLACK_MCP_CONFIG_PATH = SLACK_AGENT_DIR ? join13(SLACK_AGENT_DIR, "project", ".mcp.json") : null;
|
|
17695
18064
|
var liveAllowedUsersCache = null;
|
|
17696
18065
|
function readLiveAllowedUsers() {
|
|
17697
18066
|
if (!SLACK_MCP_CONFIG_PATH) return null;
|
|
@@ -17701,7 +18070,7 @@ function readLiveAllowedUsers() {
|
|
|
17701
18070
|
return liveAllowedUsersCache.value;
|
|
17702
18071
|
}
|
|
17703
18072
|
const value = extractAllowedUsersFromMcpJson(
|
|
17704
|
-
|
|
18073
|
+
readFileSync14(SLACK_MCP_CONFIG_PATH, "utf-8")
|
|
17705
18074
|
);
|
|
17706
18075
|
if (value === null) return null;
|
|
17707
18076
|
liveAllowedUsersCache = { mtimeMs, value };
|
|
@@ -17722,7 +18091,7 @@ function readLivePingAllowedUsers() {
|
|
|
17722
18091
|
return livePingAllowedUsersCache.value;
|
|
17723
18092
|
}
|
|
17724
18093
|
const value = extractPingAllowedUsersFromMcpJson(
|
|
17725
|
-
|
|
18094
|
+
readFileSync14(SLACK_MCP_CONFIG_PATH, "utf-8")
|
|
17726
18095
|
);
|
|
17727
18096
|
if (value === null) return null;
|
|
17728
18097
|
livePingAllowedUsersCache = { mtimeMs, value };
|
|
@@ -17734,16 +18103,17 @@ function readLivePingAllowedUsers() {
|
|
|
17734
18103
|
function getEffectivePingAllowedUsers() {
|
|
17735
18104
|
return readLivePingAllowedUsers() ?? PING_ALLOWED_USERS;
|
|
17736
18105
|
}
|
|
17737
|
-
var SLACK_PENDING_INBOUND_DIR = SLACK_AGENT_DIR ?
|
|
17738
|
-
var SLACK_RECOVERY_OUTBOX_DIR = SLACK_AGENT_DIR ?
|
|
17739
|
-
var
|
|
17740
|
-
var
|
|
17741
|
-
var
|
|
18106
|
+
var SLACK_PENDING_INBOUND_DIR = SLACK_AGENT_DIR ? join13(SLACK_AGENT_DIR, "slack-pending-inbound") : null;
|
|
18107
|
+
var SLACK_RECOVERY_OUTBOX_DIR = SLACK_AGENT_DIR ? join13(SLACK_AGENT_DIR, "slack-recovery-outbox") : null;
|
|
18108
|
+
var slackInboundRegistry = createInboundRegistry();
|
|
18109
|
+
var SLACK_RESTART_CONFIRM_FILE = SLACK_AGENT_DIR ? join13(SLACK_AGENT_DIR, "slack-restart-confirm.json") : null;
|
|
18110
|
+
var SLACK_RECENT_DMS_FILE = SLACK_AGENT_DIR ? join13(SLACK_AGENT_DIR, "slack-recent-dms.json") : null;
|
|
18111
|
+
var SLACK_CHANNEL_ADD_RESTART_FILE = SLACK_AGENT_DIR ? join13(SLACK_AGENT_DIR, "slack-channel-add-restart.json") : null;
|
|
17742
18112
|
var SLACK_MAX_RECOVERY_ATTEMPTS = 3;
|
|
17743
|
-
var SLACK_AVATAR_MARKER_PATH = SLACK_AGENT_DIR ?
|
|
18113
|
+
var SLACK_AVATAR_MARKER_PATH = SLACK_AGENT_DIR ? join13(SLACK_AGENT_DIR, "slack-avatar-applied") : null;
|
|
17744
18114
|
function redactSlackId(id) {
|
|
17745
18115
|
if (!id) return "<none>";
|
|
17746
|
-
return
|
|
18116
|
+
return createHash2("sha256").update(id).digest("hex").slice(0, 8);
|
|
17747
18117
|
}
|
|
17748
18118
|
function safeSlackMarkerName(channel, threadTs, messageTs) {
|
|
17749
18119
|
const safe = (s) => s.replace(/[^A-Za-z0-9_-]/g, "_");
|
|
@@ -17751,7 +18121,7 @@ function safeSlackMarkerName(channel, threadTs, messageTs) {
|
|
|
17751
18121
|
}
|
|
17752
18122
|
function slackPendingInboundPath(channel, threadTs, messageTs) {
|
|
17753
18123
|
if (!SLACK_PENDING_INBOUND_DIR) return null;
|
|
17754
|
-
return
|
|
18124
|
+
return join13(SLACK_PENDING_INBOUND_DIR, safeSlackMarkerName(channel, threadTs, messageTs));
|
|
17755
18125
|
}
|
|
17756
18126
|
function writeSlackPendingInboundMarker(channel, threadTs, messageTs, undeliverable = false, discretionary = false, payload) {
|
|
17757
18127
|
const path = slackPendingInboundPath(channel, threadTs, messageTs);
|
|
@@ -17761,6 +18131,10 @@ function writeSlackPendingInboundMarker(channel, threadTs, messageTs, undelivera
|
|
|
17761
18131
|
thread_ts: threadTs,
|
|
17762
18132
|
message_ts: messageTs,
|
|
17763
18133
|
received_at: (/* @__PURE__ */ new Date()).toISOString(),
|
|
18134
|
+
// WS2: the deterministic inbound id. Always written (derivable from the
|
|
18135
|
+
// coords, marker-semantics-neutral) so a marker recovered after a restart
|
|
18136
|
+
// carries the same id the tag emitted.
|
|
18137
|
+
inbound_id: slackInboundId(channel, threadTs, messageTs),
|
|
17764
18138
|
// Only persist the flags when set — keeps healthy-path markers byte-identical.
|
|
17765
18139
|
...undeliverable ? { undeliverable: true } : {},
|
|
17766
18140
|
...discretionary ? { discretionary: true } : {},
|
|
@@ -17770,7 +18144,7 @@ function writeSlackPendingInboundMarker(channel, threadTs, messageTs, undelivera
|
|
|
17770
18144
|
};
|
|
17771
18145
|
try {
|
|
17772
18146
|
mkdirSync8(SLACK_PENDING_INBOUND_DIR, { recursive: true, mode: 448 });
|
|
17773
|
-
|
|
18147
|
+
writeFileSync12(path, JSON.stringify(marker), { mode: 384 });
|
|
17774
18148
|
} catch (err) {
|
|
17775
18149
|
process.stderr.write(
|
|
17776
18150
|
`slack-channel(${AGENT_CODE_NAME}): pending-inbound marker write failed: ${err.message}
|
|
@@ -17798,7 +18172,7 @@ function rewriteSlackMarkerInPlace(path, marker) {
|
|
|
17798
18172
|
function markSlackMarkerSeenInPlace(fullPath) {
|
|
17799
18173
|
let marker;
|
|
17800
18174
|
try {
|
|
17801
|
-
marker = JSON.parse(
|
|
18175
|
+
marker = JSON.parse(readFileSync14(fullPath, "utf-8"));
|
|
17802
18176
|
} catch {
|
|
17803
18177
|
return;
|
|
17804
18178
|
}
|
|
@@ -17812,7 +18186,7 @@ function attachSlackReplayPayload(channel, threadTs, messageTs, payload) {
|
|
|
17812
18186
|
if (!path) return;
|
|
17813
18187
|
let marker;
|
|
17814
18188
|
try {
|
|
17815
|
-
marker = JSON.parse(
|
|
18189
|
+
marker = JSON.parse(readFileSync14(path, "utf-8"));
|
|
17816
18190
|
} catch {
|
|
17817
18191
|
return;
|
|
17818
18192
|
}
|
|
@@ -17823,7 +18197,7 @@ function readSlackPendingInboundMarker(channel, threadTs, messageTs) {
|
|
|
17823
18197
|
const path = slackPendingInboundPath(channel, threadTs, messageTs);
|
|
17824
18198
|
if (!path || !existsSync8(path)) return null;
|
|
17825
18199
|
try {
|
|
17826
|
-
return JSON.parse(
|
|
18200
|
+
return JSON.parse(readFileSync14(path, "utf-8"));
|
|
17827
18201
|
} catch {
|
|
17828
18202
|
return null;
|
|
17829
18203
|
}
|
|
@@ -17918,7 +18292,7 @@ function scheduleBusyAck(channel, threadTs, messageTs, isThreadReply, arrivedWhi
|
|
|
17918
18292
|
let paneLogFreshAgeMs = null;
|
|
17919
18293
|
if (SLACK_AGENT_DIR) {
|
|
17920
18294
|
try {
|
|
17921
|
-
const paneMtimeMs = statSync2(
|
|
18295
|
+
const paneMtimeMs = statSync2(join13(SLACK_AGENT_DIR, "pane.log")).mtimeMs;
|
|
17922
18296
|
paneLogFreshAgeMs = Math.max(0, Date.now() - paneMtimeMs);
|
|
17923
18297
|
} catch {
|
|
17924
18298
|
}
|
|
@@ -17951,7 +18325,7 @@ function __resetSlackBusyAckNoticeThrottle() {
|
|
|
17951
18325
|
function clearSlackMarkerFileWithHeal(fullPath) {
|
|
17952
18326
|
let marker = null;
|
|
17953
18327
|
try {
|
|
17954
|
-
marker = JSON.parse(
|
|
18328
|
+
marker = JSON.parse(readFileSync14(fullPath, "utf-8"));
|
|
17955
18329
|
} catch {
|
|
17956
18330
|
}
|
|
17957
18331
|
if (marker && decideRecoveryHeal({
|
|
@@ -17968,7 +18342,7 @@ function clearSlackMarkerFileWithHeal(fullPath) {
|
|
|
17968
18342
|
function markSlackMarkerSeenWithHeal(fullPath) {
|
|
17969
18343
|
let marker = null;
|
|
17970
18344
|
try {
|
|
17971
|
-
marker = JSON.parse(
|
|
18345
|
+
marker = JSON.parse(readFileSync14(fullPath, "utf-8"));
|
|
17972
18346
|
} catch {
|
|
17973
18347
|
return;
|
|
17974
18348
|
}
|
|
@@ -17987,6 +18361,7 @@ function clearAllSlackPendingMarkersForThread2(channel, threadTs) {
|
|
|
17987
18361
|
threadTs,
|
|
17988
18362
|
clearSlackMarkerFileWithHeal
|
|
17989
18363
|
);
|
|
18364
|
+
slackInboundRegistry.markClearedThread(channel, threadTs);
|
|
17990
18365
|
}
|
|
17991
18366
|
function clearSlackPendingMarkerByMessageTs2(channel, messageTs) {
|
|
17992
18367
|
clearSlackPendingMarkerByMessageTs(
|
|
@@ -17995,6 +18370,7 @@ function clearSlackPendingMarkerByMessageTs2(channel, messageTs) {
|
|
|
17995
18370
|
messageTs,
|
|
17996
18371
|
clearSlackMarkerFileWithHeal
|
|
17997
18372
|
);
|
|
18373
|
+
slackInboundRegistry.markCleared(channel, messageTs);
|
|
17998
18374
|
}
|
|
17999
18375
|
function clearOldestSlackPendingMarkerInChannel2(channel) {
|
|
18000
18376
|
clearOldestSlackPendingMarkerInChannel(
|
|
@@ -18002,6 +18378,7 @@ function clearOldestSlackPendingMarkerInChannel2(channel) {
|
|
|
18002
18378
|
channel,
|
|
18003
18379
|
clearSlackMarkerFileWithHeal
|
|
18004
18380
|
);
|
|
18381
|
+
slackInboundRegistry.markClearedOldestInChannel(channel);
|
|
18005
18382
|
}
|
|
18006
18383
|
function markSeenAllSlackPendingMarkersForThread2(channel, threadTs) {
|
|
18007
18384
|
markSeenAllSlackPendingMarkersForThread(
|
|
@@ -18033,10 +18410,10 @@ function slackNextRetryName(filename) {
|
|
|
18033
18410
|
async function processSlackRecoveryOutboxFile(filename) {
|
|
18034
18411
|
if (!SLACK_RECOVERY_OUTBOX_DIR) return;
|
|
18035
18412
|
if (filename.endsWith(".poison.json") || filename.endsWith(".tmp")) return;
|
|
18036
|
-
const fullPath =
|
|
18413
|
+
const fullPath = join13(SLACK_RECOVERY_OUTBOX_DIR, filename);
|
|
18037
18414
|
let payload;
|
|
18038
18415
|
try {
|
|
18039
|
-
payload = JSON.parse(
|
|
18416
|
+
payload = JSON.parse(readFileSync14(fullPath, "utf-8"));
|
|
18040
18417
|
} catch (err) {
|
|
18041
18418
|
process.stderr.write(
|
|
18042
18419
|
`slack-channel(${AGENT_CODE_NAME}): recovery outbox parse failed (${filename}): ${err.message}
|
|
@@ -18136,7 +18513,7 @@ async function processSlackRecoveryOutboxFile(filename) {
|
|
|
18136
18513
|
const next = slackNextRetryName(filename);
|
|
18137
18514
|
if (next) {
|
|
18138
18515
|
try {
|
|
18139
|
-
renameSync4(fullPath,
|
|
18516
|
+
renameSync4(fullPath, join13(SLACK_RECOVERY_OUTBOX_DIR, next.next));
|
|
18140
18517
|
if (next.attempt >= SLACK_MAX_RECOVERY_ATTEMPTS) {
|
|
18141
18518
|
process.stderr.write(
|
|
18142
18519
|
`slack-channel(${AGENT_CODE_NAME}): ghost-reply recovery exhausted retries \u2014 moved to ${next.next}
|
|
@@ -18166,7 +18543,7 @@ function scanSlackRecoveryRetries() {
|
|
|
18166
18543
|
if (!SLACK_RECOVERY_OUTBOX_DIR) return;
|
|
18167
18544
|
let entries;
|
|
18168
18545
|
try {
|
|
18169
|
-
entries =
|
|
18546
|
+
entries = readdirSync4(SLACK_RECOVERY_OUTBOX_DIR);
|
|
18170
18547
|
} catch {
|
|
18171
18548
|
return;
|
|
18172
18549
|
}
|
|
@@ -18175,7 +18552,7 @@ function scanSlackRecoveryRetries() {
|
|
|
18175
18552
|
if (!f.includes(".retry-") || f.endsWith(".poison.json")) continue;
|
|
18176
18553
|
let mtimeMs;
|
|
18177
18554
|
try {
|
|
18178
|
-
mtimeMs = statSync2(
|
|
18555
|
+
mtimeMs = statSync2(join13(SLACK_RECOVERY_OUTBOX_DIR, f)).mtimeMs;
|
|
18179
18556
|
} catch {
|
|
18180
18557
|
continue;
|
|
18181
18558
|
}
|
|
@@ -18196,7 +18573,7 @@ function startSlackRecoveryOutboxWatcher() {
|
|
|
18196
18573
|
return;
|
|
18197
18574
|
}
|
|
18198
18575
|
try {
|
|
18199
|
-
for (const f of
|
|
18576
|
+
for (const f of readdirSync4(SLACK_RECOVERY_OUTBOX_DIR)) {
|
|
18200
18577
|
if (isFirstAttemptSlackOutboxFile(f)) void processSlackRecoveryOutboxFile(f);
|
|
18201
18578
|
}
|
|
18202
18579
|
} catch {
|
|
@@ -18205,7 +18582,7 @@ function startSlackRecoveryOutboxWatcher() {
|
|
|
18205
18582
|
const watcher = watch(SLACK_RECOVERY_OUTBOX_DIR, (event, filename) => {
|
|
18206
18583
|
if (event !== "rename" || !filename) return;
|
|
18207
18584
|
if (!isFirstAttemptSlackOutboxFile(filename)) return;
|
|
18208
|
-
if (existsSync8(
|
|
18585
|
+
if (existsSync8(join13(SLACK_RECOVERY_OUTBOX_DIR, filename))) {
|
|
18209
18586
|
void processSlackRecoveryOutboxFile(filename);
|
|
18210
18587
|
}
|
|
18211
18588
|
});
|
|
@@ -18220,7 +18597,7 @@ function startSlackRecoveryOutboxWatcher() {
|
|
|
18220
18597
|
retryTimer.unref?.();
|
|
18221
18598
|
}
|
|
18222
18599
|
startSlackRecoveryOutboxWatcher();
|
|
18223
|
-
var SLACK_NOTICE_OUTBOX_DIR = SLACK_AGENT_DIR ?
|
|
18600
|
+
var SLACK_NOTICE_OUTBOX_DIR = SLACK_AGENT_DIR ? join13(SLACK_AGENT_DIR, "slack-notice-outbox") : null;
|
|
18224
18601
|
var SLACK_NOTICE_MAX_AGE_MS = 9e4;
|
|
18225
18602
|
var SLACK_NOTICE_INFLIGHT = /* @__PURE__ */ new Set();
|
|
18226
18603
|
async function processSlackNoticeOutboxFile(filename) {
|
|
@@ -18228,7 +18605,7 @@ async function processSlackNoticeOutboxFile(filename) {
|
|
|
18228
18605
|
if (filename.startsWith(".") || filename.endsWith(".tmp") || !filename.endsWith(".json")) return;
|
|
18229
18606
|
if (SLACK_NOTICE_INFLIGHT.has(filename)) return;
|
|
18230
18607
|
SLACK_NOTICE_INFLIGHT.add(filename);
|
|
18231
|
-
const fullPath =
|
|
18608
|
+
const fullPath = join13(SLACK_NOTICE_OUTBOX_DIR, filename);
|
|
18232
18609
|
try {
|
|
18233
18610
|
let mtimeMs;
|
|
18234
18611
|
try {
|
|
@@ -18245,7 +18622,7 @@ async function processSlackNoticeOutboxFile(filename) {
|
|
|
18245
18622
|
}
|
|
18246
18623
|
let payload;
|
|
18247
18624
|
try {
|
|
18248
|
-
const parsed = JSON.parse(
|
|
18625
|
+
const parsed = JSON.parse(readFileSync14(fullPath, "utf-8"));
|
|
18249
18626
|
if (!parsed || typeof parsed !== "object") throw new Error("not an object");
|
|
18250
18627
|
payload = parsed;
|
|
18251
18628
|
} catch {
|
|
@@ -18328,13 +18705,13 @@ function startSlackNoticeOutboxWatcher() {
|
|
|
18328
18705
|
return;
|
|
18329
18706
|
}
|
|
18330
18707
|
try {
|
|
18331
|
-
for (const f of
|
|
18708
|
+
for (const f of readdirSync4(SLACK_NOTICE_OUTBOX_DIR)) void processSlackNoticeOutboxFile(f);
|
|
18332
18709
|
} catch {
|
|
18333
18710
|
}
|
|
18334
18711
|
try {
|
|
18335
18712
|
const watcher = watch(SLACK_NOTICE_OUTBOX_DIR, (event, filename) => {
|
|
18336
18713
|
if (event !== "rename" || !filename) return;
|
|
18337
|
-
if (existsSync8(
|
|
18714
|
+
if (existsSync8(join13(SLACK_NOTICE_OUTBOX_DIR, filename))) {
|
|
18338
18715
|
void processSlackNoticeOutboxFile(filename);
|
|
18339
18716
|
}
|
|
18340
18717
|
});
|
|
@@ -18357,7 +18734,7 @@ function sweepSlackStaleMarkers(thresholdMs) {
|
|
|
18357
18734
|
if (!existsSync8(SLACK_PENDING_INBOUND_DIR)) return;
|
|
18358
18735
|
let filenames;
|
|
18359
18736
|
try {
|
|
18360
|
-
filenames =
|
|
18737
|
+
filenames = readdirSync4(SLACK_PENDING_INBOUND_DIR);
|
|
18361
18738
|
} catch (err) {
|
|
18362
18739
|
process.stderr.write(
|
|
18363
18740
|
`slack-channel(${AGENT_CODE_NAME}): stale-marker readdir failed: ${err.message}
|
|
@@ -18370,10 +18747,10 @@ function sweepSlackStaleMarkers(thresholdMs) {
|
|
|
18370
18747
|
for (const filename of filenames) {
|
|
18371
18748
|
if (!filename.endsWith(".json")) continue;
|
|
18372
18749
|
if (filename.endsWith(".tmp")) continue;
|
|
18373
|
-
const fullPath =
|
|
18750
|
+
const fullPath = join13(SLACK_PENDING_INBOUND_DIR, filename);
|
|
18374
18751
|
let marker;
|
|
18375
18752
|
try {
|
|
18376
|
-
marker = JSON.parse(
|
|
18753
|
+
marker = JSON.parse(readFileSync14(fullPath, "utf-8"));
|
|
18377
18754
|
} catch (err) {
|
|
18378
18755
|
process.stderr.write(
|
|
18379
18756
|
`slack-channel(${AGENT_CODE_NAME}): stale-marker parse failed for ${redactSlackId(filename)}: ${err.message}
|
|
@@ -18409,6 +18786,9 @@ function sweepSlackStaleMarkers(thresholdMs) {
|
|
|
18409
18786
|
}
|
|
18410
18787
|
}
|
|
18411
18788
|
sweepSlackStaleMarkers(STALE_MARKER_MS);
|
|
18789
|
+
if (SLACK_PENDING_INBOUND_DIR) {
|
|
18790
|
+
slackInboundRegistry.seedFromMarkerDir(SLACK_PENDING_INBOUND_DIR);
|
|
18791
|
+
}
|
|
18412
18792
|
var slackOrphanSweepTimer = setInterval(() => {
|
|
18413
18793
|
const probe = process.env.TMUX && AGENT_CODE_NAME ? probeAgentSessionCached(AGENT_CODE_NAME) : { tmux: "unknown", claude: "unknown" };
|
|
18414
18794
|
const sessionAlive = probe.tmux === "alive" && probe.claude === "alive";
|
|
@@ -18416,13 +18796,13 @@ var slackOrphanSweepTimer = setInterval(() => {
|
|
|
18416
18796
|
checkSlackWatchdogGiveUpNotice();
|
|
18417
18797
|
}, orphanSweepIntervalMs());
|
|
18418
18798
|
slackOrphanSweepTimer.unref?.();
|
|
18419
|
-
var SLACK_PROGRESS_HEARTBEAT_PATH = SLACK_AGENT_DIR ?
|
|
18799
|
+
var SLACK_PROGRESS_HEARTBEAT_PATH = SLACK_AGENT_DIR ? join13(SLACK_AGENT_DIR, "channel-progress-heartbeat.json") : null;
|
|
18420
18800
|
var slackTrackedProgress = null;
|
|
18421
18801
|
var slackProgressTickRunning = false;
|
|
18422
18802
|
function readSlackProgressHeartbeat() {
|
|
18423
18803
|
if (!SLACK_PROGRESS_HEARTBEAT_PATH || !existsSync8(SLACK_PROGRESS_HEARTBEAT_PATH)) return null;
|
|
18424
18804
|
try {
|
|
18425
|
-
return parseProgressHeartbeat(
|
|
18805
|
+
return parseProgressHeartbeat(readFileSync14(SLACK_PROGRESS_HEARTBEAT_PATH, "utf-8"));
|
|
18426
18806
|
} catch {
|
|
18427
18807
|
return null;
|
|
18428
18808
|
}
|
|
@@ -18431,7 +18811,7 @@ function seedSlackProgressHeartbeat() {
|
|
|
18431
18811
|
if (!SLACK_PROGRESS_HEARTBEAT_PATH) return;
|
|
18432
18812
|
const tmp = `${SLACK_PROGRESS_HEARTBEAT_PATH}.${process.pid}.tmp`;
|
|
18433
18813
|
try {
|
|
18434
|
-
|
|
18814
|
+
writeFileSync12(tmp, serializeProgressHeartbeat(SEED_PROGRESS_STEP, Date.now()), { mode: 384 });
|
|
18435
18815
|
renameSync4(tmp, SLACK_PROGRESS_HEARTBEAT_PATH);
|
|
18436
18816
|
} catch {
|
|
18437
18817
|
try {
|
|
@@ -18445,11 +18825,11 @@ function findSlackProgressTarget() {
|
|
|
18445
18825
|
let best = null;
|
|
18446
18826
|
let bestMs = Infinity;
|
|
18447
18827
|
try {
|
|
18448
|
-
for (const name of
|
|
18828
|
+
for (const name of readdirSync4(SLACK_PENDING_INBOUND_DIR)) {
|
|
18449
18829
|
if (!name.endsWith(".json")) continue;
|
|
18450
18830
|
let m;
|
|
18451
18831
|
try {
|
|
18452
|
-
m = JSON.parse(
|
|
18832
|
+
m = JSON.parse(readFileSync14(join13(SLACK_PENDING_INBOUND_DIR, name), "utf-8"));
|
|
18453
18833
|
} catch {
|
|
18454
18834
|
continue;
|
|
18455
18835
|
}
|
|
@@ -18596,11 +18976,11 @@ function listPendingSlackConversations() {
|
|
|
18596
18976
|
if (!SLACK_PENDING_INBOUND_DIR || !existsSync8(SLACK_PENDING_INBOUND_DIR)) return [];
|
|
18597
18977
|
const byKey = /* @__PURE__ */ new Map();
|
|
18598
18978
|
try {
|
|
18599
|
-
for (const name of
|
|
18979
|
+
for (const name of readdirSync4(SLACK_PENDING_INBOUND_DIR)) {
|
|
18600
18980
|
if (!name.endsWith(".json")) continue;
|
|
18601
18981
|
try {
|
|
18602
18982
|
const marker = JSON.parse(
|
|
18603
|
-
|
|
18983
|
+
readFileSync14(join13(SLACK_PENDING_INBOUND_DIR, name), "utf8")
|
|
18604
18984
|
);
|
|
18605
18985
|
if (typeof marker.channel !== "string" || !marker.channel) continue;
|
|
18606
18986
|
if (typeof marker.thread_ts !== "string" || !marker.thread_ts) continue;
|
|
@@ -18652,7 +19032,7 @@ function postSlackWatchdogGiveUpNotice(channel, threadTs, isThreadReply, reason)
|
|
|
18652
19032
|
}
|
|
18653
19033
|
function checkSlackWatchdogGiveUpNotice() {
|
|
18654
19034
|
if (!SLACK_AGENT_DIR) return;
|
|
18655
|
-
const signal = readGiveUpSignal(
|
|
19035
|
+
const signal = readGiveUpSignal(join13(SLACK_AGENT_DIR, GIVE_UP_SIGNAL_FILENAME));
|
|
18656
19036
|
const signalAtMs = signal?.atMs ?? null;
|
|
18657
19037
|
const act = decideGiveUpNotice({
|
|
18658
19038
|
signalAtMs,
|
|
@@ -18689,7 +19069,7 @@ async function notifyStrandedInboundsOnFirstConnect() {
|
|
|
18689
19069
|
if (!SLACK_PENDING_INBOUND_DIR || !existsSync8(SLACK_PENDING_INBOUND_DIR)) return;
|
|
18690
19070
|
let filenames;
|
|
18691
19071
|
try {
|
|
18692
|
-
filenames =
|
|
19072
|
+
filenames = readdirSync4(SLACK_PENDING_INBOUND_DIR);
|
|
18693
19073
|
} catch {
|
|
18694
19074
|
hadFailure = true;
|
|
18695
19075
|
return;
|
|
@@ -18699,10 +19079,10 @@ async function notifyStrandedInboundsOnFirstConnect() {
|
|
|
18699
19079
|
let notified = 0;
|
|
18700
19080
|
for (const filename of filenames) {
|
|
18701
19081
|
if (!filename.endsWith(".json")) continue;
|
|
18702
|
-
const fullPath =
|
|
19082
|
+
const fullPath = join13(SLACK_PENDING_INBOUND_DIR, filename);
|
|
18703
19083
|
let marker;
|
|
18704
19084
|
try {
|
|
18705
|
-
marker = JSON.parse(
|
|
19085
|
+
marker = JSON.parse(readFileSync14(fullPath, "utf-8"));
|
|
18706
19086
|
} catch {
|
|
18707
19087
|
continue;
|
|
18708
19088
|
}
|
|
@@ -18873,7 +19253,7 @@ function noteThreadActivityByMessageTs(channel, messageTs) {
|
|
|
18873
19253
|
markSeenAllSlackPendingMarkersForThread2(channel, messageTs);
|
|
18874
19254
|
markSeenSlackPendingMarkerByMessageTs2(channel, messageTs);
|
|
18875
19255
|
}
|
|
18876
|
-
var RESTART_FLAGS_DIR =
|
|
19256
|
+
var RESTART_FLAGS_DIR = join13(homedir4(), ".augmented", "restart-flags");
|
|
18877
19257
|
function actuateHostRestartSlack() {
|
|
18878
19258
|
return actuateHostRestart({
|
|
18879
19259
|
agtHost: AGT_HOST,
|
|
@@ -18894,10 +19274,10 @@ function buildAugmentedSlackMetadata() {
|
|
|
18894
19274
|
};
|
|
18895
19275
|
}
|
|
18896
19276
|
function hashChannelId(id) {
|
|
18897
|
-
return
|
|
19277
|
+
return createHash2("sha256").update(id).digest("hex").slice(0, 8);
|
|
18898
19278
|
}
|
|
18899
19279
|
function hashId(id) {
|
|
18900
|
-
return
|
|
19280
|
+
return createHash2("sha256").update(id).digest("hex").slice(0, 8);
|
|
18901
19281
|
}
|
|
18902
19282
|
async function postSlackMessage(body) {
|
|
18903
19283
|
const augmentedMeta = buildAugmentedSlackMetadata();
|
|
@@ -19419,7 +19799,7 @@ async function handleSlashCommandEnvelope(payload) {
|
|
|
19419
19799
|
if (!existsSync8(RESTART_FLAGS_DIR)) {
|
|
19420
19800
|
mkdirSync8(RESTART_FLAGS_DIR, { recursive: true });
|
|
19421
19801
|
}
|
|
19422
|
-
const flagPath =
|
|
19802
|
+
const flagPath = join13(RESTART_FLAGS_DIR, `${codeName}.flag`);
|
|
19423
19803
|
const flag = {
|
|
19424
19804
|
codeName,
|
|
19425
19805
|
source: "slack",
|
|
@@ -19427,7 +19807,7 @@ async function handleSlashCommandEnvelope(payload) {
|
|
|
19427
19807
|
reply: confirmReply
|
|
19428
19808
|
};
|
|
19429
19809
|
const tmpPath = `${flagPath}.${process.pid}.${randomUUID2()}.tmp`;
|
|
19430
|
-
|
|
19810
|
+
writeFileSync12(tmpPath, JSON.stringify(flag) + "\n", "utf8");
|
|
19431
19811
|
renameSync4(tmpPath, flagPath);
|
|
19432
19812
|
}
|
|
19433
19813
|
process.stderr.write(
|
|
@@ -19562,7 +19942,7 @@ async function handleRestartCommand(opts) {
|
|
|
19562
19942
|
if (!existsSync8(RESTART_FLAGS_DIR)) {
|
|
19563
19943
|
mkdirSync8(RESTART_FLAGS_DIR, { recursive: true });
|
|
19564
19944
|
}
|
|
19565
|
-
const flagPath =
|
|
19945
|
+
const flagPath = join13(RESTART_FLAGS_DIR, `${codeName}.flag`);
|
|
19566
19946
|
const flag = {
|
|
19567
19947
|
codeName,
|
|
19568
19948
|
source: "slack",
|
|
@@ -19570,7 +19950,7 @@ async function handleRestartCommand(opts) {
|
|
|
19570
19950
|
reply: { ...confirmReply, message_ts: opts.ts }
|
|
19571
19951
|
};
|
|
19572
19952
|
const tmpPath = `${flagPath}.${process.pid}.${randomUUID2()}.tmp`;
|
|
19573
|
-
|
|
19953
|
+
writeFileSync12(tmpPath, JSON.stringify(flag) + "\n", "utf8");
|
|
19574
19954
|
renameSync4(tmpPath, flagPath);
|
|
19575
19955
|
}
|
|
19576
19956
|
process.stderr.write(
|
|
@@ -19675,7 +20055,7 @@ var THREAD_STORE_TTL_DAYS = parseTtlDays(process.env.SLACK_THREAD_FOLLOW_TTL_DAY
|
|
|
19675
20055
|
var threadPersister = null;
|
|
19676
20056
|
function resolveThreadStorePath() {
|
|
19677
20057
|
if (!AGENT_CODE_NAME) return null;
|
|
19678
|
-
return
|
|
20058
|
+
return join13(homedir4(), ".augmented", AGENT_CODE_NAME, "slack-tracked-threads.json");
|
|
19679
20059
|
}
|
|
19680
20060
|
function parseTtlDays(raw) {
|
|
19681
20061
|
if (!raw) return void 0;
|
|
@@ -19714,9 +20094,9 @@ if (!BOT_TOKEN || !APP_TOKEN) {
|
|
|
19714
20094
|
var slackStderrLogStream = null;
|
|
19715
20095
|
if (AGENT_CODE_NAME) {
|
|
19716
20096
|
try {
|
|
19717
|
-
const logDir =
|
|
20097
|
+
const logDir = join13(homedir4(), ".augmented", AGENT_CODE_NAME);
|
|
19718
20098
|
mkdirSync8(logDir, { recursive: true });
|
|
19719
|
-
slackStderrLogStream = createWriteStream(
|
|
20099
|
+
slackStderrLogStream = createWriteStream(join13(logDir, "slack-channel-stderr.log"), {
|
|
19720
20100
|
flags: "a",
|
|
19721
20101
|
mode: 384
|
|
19722
20102
|
});
|
|
@@ -19795,20 +20175,13 @@ var mcp = new Server(
|
|
|
19795
20175
|
tools: {}
|
|
19796
20176
|
},
|
|
19797
20177
|
// NOTE: Claude Code truncates MCP server instructions at 2048 chars.
|
|
19798
|
-
//
|
|
19799
|
-
//
|
|
19800
|
-
instructions:
|
|
19801
|
-
|
|
19802
|
-
|
|
19803
|
-
|
|
19804
|
-
|
|
19805
|
-
"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.",
|
|
19806
|
-
"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.",
|
|
19807
|
-
`Always reply in a thread \u2014 pass message_ts and the server threads your reply off it, for channels AND DMs (it creates the thread if one doesn't exist yet). auto_followed="true" \u2192 only reply if useful, OR if your own bot user is @-mentioned (counts even in auto_followed).`,
|
|
19808
|
-
`Reaction taxonomy (use slack.react sparingly \u2014 prefer a reply): :${SLACK_ACK_REACTION}: = ack (already auto-added on inbound, do not duplicate); \u2705 = success.` + (SKIP_REACTION_ON ? ` "${SLACK_SKIP_REACTION}" = seen but not replying (see below).` : "") + " NEVER react to signal failure of YOUR work \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). (The \u23F3 you may see on an inbound is applied by the system, not you \u2014 it marks a message that arrived while the agent couldn't reply; never add \u23F3 or \u274C yourself.)",
|
|
19809
|
-
SKIP_REACTION_ON ? `When you read a DM or thread message and decide NOT to reply (different @-mention, side conversation, auto_followed catch-up, or simply nothing useful to add): call slack.react with emoji "${SLACK_SKIP_REACTION}" so the sender knows you saw it \u2014 never post a "this wasn't for me" message. Top-level channel messages (not a DM, not a thread reply) you may still skip silently.` : `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.`,
|
|
19810
|
-
"To deliver a file: save under your project dir, call slack.upload_file with path + channel + thread_ts."
|
|
19811
|
-
].join(" ")
|
|
20178
|
+
// Built by a pure, length-tested builder (slack-mcp-instructions.ts) so the
|
|
20179
|
+
// routing guidance can't silently fall off the end (ENG-6296 regression).
|
|
20180
|
+
instructions: buildSlackMcpInstructions({
|
|
20181
|
+
ackReaction: SLACK_ACK_REACTION,
|
|
20182
|
+
skipReactionOn: SKIP_REACTION_ON,
|
|
20183
|
+
skipReaction: SLACK_SKIP_REACTION
|
|
20184
|
+
})
|
|
19812
20185
|
}
|
|
19813
20186
|
);
|
|
19814
20187
|
mcp.setRequestHandler(ListToolsRequestSchema, async () => ({
|
|
@@ -19844,6 +20217,15 @@ mcp.setRequestHandler(ListToolsRequestSchema, async () => ({
|
|
|
19844
20217
|
interim: {
|
|
19845
20218
|
type: "boolean",
|
|
19846
20219
|
description: 'Set true ONLY when this is an interim acknowledgement (e.g. "On it \u2014 checking now\u2026") and the real answer is still coming. The system keeps tracking this conversation as awaiting your final reply. Your eventual substantive answer MUST be a separate slack.reply with interim omitted/false \u2014 that is what marks the request complete. Omit (default false) for any reply that fully answers the user.'
|
|
20220
|
+
},
|
|
20221
|
+
// WS2 (cross-thread reply routing): the preferred way to target a reply.
|
|
20222
|
+
inbound_id: {
|
|
20223
|
+
type: "string",
|
|
20224
|
+
description: `The inbound_id attribute of the <channel> tag you are answering (e.g. "ib_a1b2c3d4e5f6"). PREFERRED over channel/thread_ts/message_ts: the server resolves the exact destination thread from it, so your reply can never land in another user's thread. Copy it verbatim from the tag of the message you are replying to.`
|
|
20225
|
+
},
|
|
20226
|
+
proactive: {
|
|
20227
|
+
type: "boolean",
|
|
20228
|
+
description: "Set true ONLY when this message is NOT a reply to any inbound <channel> tag - a self-initiated outreach or scheduled-task output. Leave it off (default) for every reply to a user message."
|
|
19847
20229
|
}
|
|
19848
20230
|
},
|
|
19849
20231
|
required: ["channel", "text"]
|
|
@@ -20075,12 +20457,58 @@ mcp.setRequestHandler(CallToolRequestSchema, async (req) => {
|
|
|
20075
20457
|
return buildImpersonationRefusal(name);
|
|
20076
20458
|
}
|
|
20077
20459
|
if (name === "slack.reply") {
|
|
20078
|
-
const { channel, text, thread_ts, message_ts, interim } = args;
|
|
20079
|
-
const
|
|
20460
|
+
const { channel, text, thread_ts, message_ts, interim, inbound_id, proactive } = args;
|
|
20461
|
+
const bindingMode = slackReplyBindingMode();
|
|
20462
|
+
const binding = resolveReplyBinding({
|
|
20463
|
+
mode: bindingMode,
|
|
20464
|
+
args: { inbound_id, channel, thread_ts, message_ts, proactive },
|
|
20465
|
+
registry: slackInboundRegistry
|
|
20466
|
+
});
|
|
20467
|
+
recordReplyBindingClassification(SLACK_AGENT_DIR, "slack", {
|
|
20468
|
+
classification: binding.classification,
|
|
20469
|
+
unknownInboundId: binding.unknownInboundId
|
|
20470
|
+
});
|
|
20471
|
+
if (bindingMode === "enforce" && binding.reject) {
|
|
20472
|
+
process.stderr.write(
|
|
20473
|
+
`slack-channel(${AGENT_CODE_NAME}): reply_binding_rejected class=${binding.classification} channel=${redactSlackId(channel)}
|
|
20474
|
+
`
|
|
20475
|
+
);
|
|
20476
|
+
return { content: [{ type: "text", text: binding.reject }], isError: true };
|
|
20477
|
+
}
|
|
20478
|
+
const enforceBound = bindingMode === "enforce" && (binding.classification === "bound" || binding.classification === "coords_corrected");
|
|
20479
|
+
if (enforceBound && binding.channel && binding.channel !== channel) {
|
|
20480
|
+
process.stderr.write(
|
|
20481
|
+
`slack-channel(${AGENT_CODE_NAME}): reply_binding_channel_mismatch_rejected class=${binding.classification} model=${redactSlackId(channel)} registry=${redactSlackId(binding.channel)}
|
|
20482
|
+
`
|
|
20483
|
+
);
|
|
20484
|
+
return {
|
|
20485
|
+
content: [
|
|
20486
|
+
{
|
|
20487
|
+
type: "text",
|
|
20488
|
+
text: "This reply was blocked: the channel does not match the inbound_id you named, so it may go to the wrong place. Reply again using the channel from the <channel> tag whose inbound_id you are answering."
|
|
20489
|
+
}
|
|
20490
|
+
],
|
|
20491
|
+
isError: true
|
|
20492
|
+
};
|
|
20493
|
+
}
|
|
20494
|
+
const bindingFeedback = bindingMode === "warn" ? binding.agentFeedback : void 0;
|
|
20495
|
+
const effectiveThreadTs = enforceBound ? binding.threadTs : resolveReplyThreadTs({
|
|
20080
20496
|
channel,
|
|
20081
20497
|
threadTs: thread_ts,
|
|
20082
20498
|
messageTs: message_ts
|
|
20083
20499
|
});
|
|
20500
|
+
if (channel) {
|
|
20501
|
+
recordReplyTargetClassification(
|
|
20502
|
+
SLACK_AGENT_DIR,
|
|
20503
|
+
"slack",
|
|
20504
|
+
classifyReplyTarget({
|
|
20505
|
+
channel,
|
|
20506
|
+
threadTs: effectiveThreadTs,
|
|
20507
|
+
messageTs: message_ts,
|
|
20508
|
+
registry: slackInboundRegistry
|
|
20509
|
+
})
|
|
20510
|
+
);
|
|
20511
|
+
}
|
|
20084
20512
|
const throttleCfg = configFromEnv();
|
|
20085
20513
|
const throttleKey = effectiveThreadTs ?? channel;
|
|
20086
20514
|
const throttleNow = Date.now();
|
|
@@ -20192,7 +20620,9 @@ mcp.setRequestHandler(CallToolRequestSchema, async (req) => {
|
|
|
20192
20620
|
}
|
|
20193
20621
|
})();
|
|
20194
20622
|
}
|
|
20195
|
-
return {
|
|
20623
|
+
return {
|
|
20624
|
+
content: [{ type: "text", text: bindingFeedback ? `sent. ${bindingFeedback}` : "sent" }]
|
|
20625
|
+
};
|
|
20196
20626
|
} catch (err) {
|
|
20197
20627
|
return {
|
|
20198
20628
|
content: [{ type: "text", text: `Failed: ${err.message}` }],
|
|
@@ -20308,7 +20738,7 @@ ${result.formatted}` : "Thread is empty or not found."
|
|
|
20308
20738
|
};
|
|
20309
20739
|
}
|
|
20310
20740
|
size = stat2.size;
|
|
20311
|
-
bytes =
|
|
20741
|
+
bytes = readFileSync14(resolvedPath);
|
|
20312
20742
|
} catch (err) {
|
|
20313
20743
|
return {
|
|
20314
20744
|
content: [{ type: "text", text: `Failed to read file: ${err.message}` }],
|
|
@@ -20668,6 +21098,16 @@ async function handleSendStructured(args) {
|
|
|
20668
21098
|
threadTs: thread_ts,
|
|
20669
21099
|
messageTs: message_ts
|
|
20670
21100
|
});
|
|
21101
|
+
recordReplyTargetClassification(
|
|
21102
|
+
SLACK_AGENT_DIR,
|
|
21103
|
+
"slack",
|
|
21104
|
+
classifyReplyTarget({
|
|
21105
|
+
channel,
|
|
21106
|
+
threadTs: effectiveThreadTs,
|
|
21107
|
+
messageTs: message_ts,
|
|
21108
|
+
registry: slackInboundRegistry
|
|
21109
|
+
})
|
|
21110
|
+
);
|
|
20671
21111
|
const validation = validateSlackBlocks2(blocks);
|
|
20672
21112
|
if (!validation.ok) {
|
|
20673
21113
|
return errResult(`Invalid blocks: ${validation.errors.map((e) => `${e.path}: ${e.message}`).join("; ")}`);
|
|
@@ -21010,7 +21450,7 @@ async function downloadSlackFile(fileId, codeName) {
|
|
|
21010
21450
|
throw new Error(`refusing to write ${savedPath} outside ${dir}`);
|
|
21011
21451
|
}
|
|
21012
21452
|
mkdirSync8(dir, { recursive: true });
|
|
21013
|
-
|
|
21453
|
+
writeFileSync12(savedPath, bytes, { mode: 384 });
|
|
21014
21454
|
try {
|
|
21015
21455
|
chmodSync(savedPath, 384);
|
|
21016
21456
|
} catch {
|
|
@@ -21071,7 +21511,7 @@ async function replayPendingSlackMarkers() {
|
|
|
21071
21511
|
if (!sessionAlive) return;
|
|
21072
21512
|
let filenames;
|
|
21073
21513
|
try {
|
|
21074
|
-
filenames =
|
|
21514
|
+
filenames = readdirSync4(SLACK_PENDING_INBOUND_DIR);
|
|
21075
21515
|
} catch {
|
|
21076
21516
|
return;
|
|
21077
21517
|
}
|
|
@@ -21079,17 +21519,17 @@ async function replayPendingSlackMarkers() {
|
|
|
21079
21519
|
let paneFreshAgeMs = null;
|
|
21080
21520
|
if (SLACK_AGENT_DIR) {
|
|
21081
21521
|
try {
|
|
21082
|
-
paneFreshAgeMs = Math.max(0, now - statSync2(
|
|
21522
|
+
paneFreshAgeMs = Math.max(0, now - statSync2(join13(SLACK_AGENT_DIR, "pane.log")).mtimeMs);
|
|
21083
21523
|
} catch {
|
|
21084
21524
|
}
|
|
21085
21525
|
}
|
|
21086
21526
|
const entries = [];
|
|
21087
21527
|
for (const name of filenames) {
|
|
21088
21528
|
if (!name.endsWith(".json") || name.endsWith(".tmp")) continue;
|
|
21089
|
-
const fullPath =
|
|
21529
|
+
const fullPath = join13(SLACK_PENDING_INBOUND_DIR, name);
|
|
21090
21530
|
let marker;
|
|
21091
21531
|
try {
|
|
21092
|
-
marker = JSON.parse(
|
|
21532
|
+
marker = JSON.parse(readFileSync14(fullPath, "utf-8"));
|
|
21093
21533
|
} catch {
|
|
21094
21534
|
continue;
|
|
21095
21535
|
}
|
|
@@ -21392,7 +21832,7 @@ async function connectSocketMode() {
|
|
|
21392
21832
|
const passedIdentity = access.kind !== "drop" || access.reason.startsWith("peer:");
|
|
21393
21833
|
if (passedIdentity) recordActivity("inbound");
|
|
21394
21834
|
if (access.kind === "drop") {
|
|
21395
|
-
const channelHash =
|
|
21835
|
+
const channelHash = createHash2("sha256").update(evt.channel ?? "").digest("hex").slice(0, 8);
|
|
21396
21836
|
const subtypeSuffix = access.reason === "content:subtype" ? ` subtype=${evt.subtype ?? "none"}` : "";
|
|
21397
21837
|
process.stderr.write(
|
|
21398
21838
|
`slack-channel: inbound drop reason=${access.reason}${subtypeSuffix} channel=${channelHash} ts=${redactSlackId(evt.ts)}
|
|
@@ -21540,7 +21980,7 @@ async function connectSocketMode() {
|
|
|
21540
21980
|
if (evt.type === "message" && evt.channel && !isDirectMessage) {
|
|
21541
21981
|
if (isThreadReply) {
|
|
21542
21982
|
const logThreadDrop = (reason) => {
|
|
21543
|
-
const channelHash =
|
|
21983
|
+
const channelHash = createHash2("sha256").update(evt.channel ?? "").digest("hex").slice(0, 8);
|
|
21544
21984
|
process.stderr.write(
|
|
21545
21985
|
`slack-channel(${AGENT_CODE_NAME}): thread-reply drop reason=${reason} auto_follow=${THREAD_AUTO_FOLLOW} channel=${channelHash} ts=${redactSlackId(evt.ts)}
|
|
21546
21986
|
`
|
|
@@ -21586,7 +22026,7 @@ async function connectSocketMode() {
|
|
|
21586
22026
|
let paneLogFreshAgeMs = null;
|
|
21587
22027
|
if (SLACK_AGENT_DIR) {
|
|
21588
22028
|
try {
|
|
21589
|
-
const paneMtimeMs = statSync2(
|
|
22029
|
+
const paneMtimeMs = statSync2(join13(SLACK_AGENT_DIR, "pane.log")).mtimeMs;
|
|
21590
22030
|
paneLogFreshAgeMs = Math.max(0, Date.now() - paneMtimeMs);
|
|
21591
22031
|
} catch {
|
|
21592
22032
|
}
|
|
@@ -21688,6 +22128,7 @@ ${forwarded.text}` : forwarded.text;
|
|
|
21688
22128
|
`
|
|
21689
22129
|
);
|
|
21690
22130
|
}
|
|
22131
|
+
const inboundId = channel && ts ? slackInboundId(channel, threadTs, ts) : "";
|
|
21691
22132
|
const replayPayload = {
|
|
21692
22133
|
content: inboundContent,
|
|
21693
22134
|
meta: {
|
|
@@ -21695,6 +22136,7 @@ ${forwarded.text}` : forwarded.text;
|
|
|
21695
22136
|
user_name: userName,
|
|
21696
22137
|
channel,
|
|
21697
22138
|
thread_ts: threadTs,
|
|
22139
|
+
...inboundId ? { inbound_id: inboundId } : {},
|
|
21698
22140
|
// ENG-5861: explicit message_ts so the agent can pass it back
|
|
21699
22141
|
// to slack.reply for reliable pending-inbound cleanup. For
|
|
21700
22142
|
// top-level messages threadTs === ts; for threaded replies
|
|
@@ -21725,6 +22167,15 @@ ${forwarded.text}` : forwarded.text;
|
|
|
21725
22167
|
});
|
|
21726
22168
|
if (channel && ts && armMarker) {
|
|
21727
22169
|
attachSlackReplayPayload(channel, threadTs, ts, replayPayload);
|
|
22170
|
+
slackInboundRegistry.record({
|
|
22171
|
+
inboundId,
|
|
22172
|
+
channel,
|
|
22173
|
+
threadTs,
|
|
22174
|
+
messageTs: ts,
|
|
22175
|
+
user,
|
|
22176
|
+
userName,
|
|
22177
|
+
receivedAt: Date.now()
|
|
22178
|
+
});
|
|
21728
22179
|
}
|
|
21729
22180
|
if (!isFromBot && evt.user && channel) {
|
|
21730
22181
|
writeTurnInitiatorMarker({
|