@integrity-labs/agt-cli 0.27.170 → 0.28.1
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/bin/agt.js.map +1 -1
- package/dist/{chunk-YX35EONG.js → chunk-I6QV3IE7.js} +8 -1
- package/dist/{chunk-YX35EONG.js.map → chunk-I6QV3IE7.js.map} +1 -1
- package/dist/{chunk-BMQQ63W2.js → chunk-K2HIV5DB.js} +2 -2
- package/dist/{chunk-U6LHLWWG.js → chunk-PPCOPZTK.js} +2 -2
- package/dist/{claude-pair-runtime-IDCPU7YT.js → claude-pair-runtime-H73LQ5AR.js} +2 -2
- package/dist/lib/manager-worker.js +8 -8
- package/dist/lib/manager-worker.js.map +1 -1
- package/dist/mcp/slack-channel.js +215 -53
- package/dist/mcp/telegram-channel.js +91 -27
- package/dist/{persistent-session-YQTDRXSB.js → persistent-session-QXLGGGO2.js} +3 -3
- package/dist/{responsiveness-probe-IGQQFMSC.js → responsiveness-probe-3BUUOZZO.js} +56 -5
- package/dist/responsiveness-probe-3BUUOZZO.js.map +1 -0
- package/package.json +1 -1
- package/dist/responsiveness-probe-IGQQFMSC.js.map +0 -1
- /package/dist/{chunk-BMQQ63W2.js.map → chunk-K2HIV5DB.js.map} +0 -0
- /package/dist/{chunk-U6LHLWWG.js.map → chunk-PPCOPZTK.js.map} +0 -0
- /package/dist/{claude-pair-runtime-IDCPU7YT.js.map → claude-pair-runtime-H73LQ5AR.js.map} +0 -0
- /package/dist/{persistent-session-YQTDRXSB.js.map → persistent-session-QXLGGGO2.js.map} +0 -0
|
@@ -14452,7 +14452,7 @@ function watchSuccessTextSlack(fileId, durationMs, reused) {
|
|
|
14452
14452
|
}
|
|
14453
14453
|
|
|
14454
14454
|
// src/ack-reaction.ts
|
|
14455
|
-
import { readdirSync, readFileSync as readFileSync2 } from "fs";
|
|
14455
|
+
import { readdirSync, readFileSync as readFileSync2, writeFileSync } from "fs";
|
|
14456
14456
|
import { join as join2 } from "path";
|
|
14457
14457
|
|
|
14458
14458
|
// src/flags-cache-read.ts
|
|
@@ -14498,19 +14498,23 @@ var ACK_STARTUP_GRACE_MS = 6e4;
|
|
|
14498
14498
|
var ACK_PANE_FRESH_THRESHOLD_MS = 6e4;
|
|
14499
14499
|
function decideAckReaction(i) {
|
|
14500
14500
|
if (!i.hasTarget) return "none";
|
|
14501
|
-
|
|
14502
|
-
|
|
14503
|
-
|
|
14501
|
+
return classifyUndeliverableCause(i) !== null ? "undeliverable" : "ack";
|
|
14502
|
+
}
|
|
14503
|
+
function classifyUndeliverableCause(i) {
|
|
14504
|
+
if (!i.hasTarget) return null;
|
|
14505
|
+
if (!i.integrationReady) return "integration_down";
|
|
14506
|
+
if (i.tmux === "dead") return "session_dead";
|
|
14507
|
+
if (!i.withinStartupGrace && i.claude === "dead") return "session_dead";
|
|
14504
14508
|
const threshold = i.pendingStaleThresholdMs ?? REPLY_WEDGED_THRESHOLD_MS;
|
|
14505
14509
|
if (i.oldestPendingAgeMs != null && i.oldestPendingAgeMs > threshold) {
|
|
14506
14510
|
const paneFreshThreshold = i.paneFreshThresholdMs ?? ACK_PANE_FRESH_THRESHOLD_MS;
|
|
14507
14511
|
const paneIsFresh = i.paneLogFreshAgeMs != null && i.paneLogFreshAgeMs <= paneFreshThreshold;
|
|
14508
14512
|
if (paneIsFresh && i.tmux === "alive" && i.claude === "alive") {
|
|
14509
|
-
return
|
|
14513
|
+
return null;
|
|
14510
14514
|
}
|
|
14511
|
-
return "
|
|
14515
|
+
return "wedged";
|
|
14512
14516
|
}
|
|
14513
|
-
return
|
|
14517
|
+
return null;
|
|
14514
14518
|
}
|
|
14515
14519
|
function decideRecoveryHeal(i) {
|
|
14516
14520
|
return i.wasUndeliverable && i.hasTarget ? "heal" : "none";
|
|
@@ -14521,8 +14525,8 @@ var UNDELIVERABLE_NOTICE_THROTTLE_MS = 5 * 60 * 1e3;
|
|
|
14521
14525
|
function shouldPostUndeliverableNotice(lastNoticeAtMs, nowMs, throttleMs = UNDELIVERABLE_NOTICE_THROTTLE_MS) {
|
|
14522
14526
|
return lastNoticeAtMs == null || nowMs - lastNoticeAtMs >= throttleMs;
|
|
14523
14527
|
}
|
|
14524
|
-
function undeliverableNoticeText() {
|
|
14525
|
-
return "\u23F3 I can't get to this right now. Please resend in a few minutes \u2014 I may not see this one.";
|
|
14528
|
+
function undeliverableNoticeText(replayEnabled = channelReplayEnabled()) {
|
|
14529
|
+
return replayEnabled ? "\u23F3 I can't get to this right now \u2014 no need to resend; I'll pick it up automatically as soon as I'm free." : "\u23F3 I can't get to this right now. Please resend in a few minutes \u2014 I may not see this one.";
|
|
14526
14530
|
}
|
|
14527
14531
|
var BUSY_ACK_THRESHOLD_MS = 9e4;
|
|
14528
14532
|
var BUSY_ACK_NOTICE_THROTTLE_MS = 10 * 60 * 1e3;
|
|
@@ -14617,6 +14621,37 @@ var ORPHAN_SWEEP_INTERVAL_MS = 30 * 60 * 1e3;
|
|
|
14617
14621
|
function orphanSweepIntervalMs() {
|
|
14618
14622
|
return Math.max(6e4, Math.min(ORPHAN_SWEEP_INTERVAL_MS, channelOrphanMarkerMs()));
|
|
14619
14623
|
}
|
|
14624
|
+
var MAX_MARKER_REPLAYS = 3;
|
|
14625
|
+
function channelReplayEnabled() {
|
|
14626
|
+
const v = process.env.AGT_CHANNEL_REPLAY_ENABLED;
|
|
14627
|
+
return v === "1" || v === "true";
|
|
14628
|
+
}
|
|
14629
|
+
function shouldReplayMarker(i) {
|
|
14630
|
+
if (!i.enabled) return false;
|
|
14631
|
+
if (!i.hasPayload) return false;
|
|
14632
|
+
if (!i.sessionAlive) return false;
|
|
14633
|
+
if (i.markerAgeMs < (i.minAgeMs ?? REPLY_WEDGED_THRESHOLD_MS)) return false;
|
|
14634
|
+
return i.replayCount < (i.maxReplays ?? MAX_MARKER_REPLAYS);
|
|
14635
|
+
}
|
|
14636
|
+
var DEFLECTION_COUNTER_SUFFIX = "-deflections.json";
|
|
14637
|
+
function deflectionCounterPath(agentDir, channel) {
|
|
14638
|
+
return join2(agentDir, `${channel}${DEFLECTION_COUNTER_SUFFIX}`);
|
|
14639
|
+
}
|
|
14640
|
+
function recordChannelDeflection(agentDir, channel, cause) {
|
|
14641
|
+
if (!agentDir) return;
|
|
14642
|
+
const path = deflectionCounterPath(agentDir, channel);
|
|
14643
|
+
let counts = {};
|
|
14644
|
+
try {
|
|
14645
|
+
const parsed = JSON.parse(readFileSync2(path, "utf-8"));
|
|
14646
|
+
if (parsed && typeof parsed === "object") counts = parsed;
|
|
14647
|
+
} catch {
|
|
14648
|
+
}
|
|
14649
|
+
counts[cause] = (counts[cause] ?? 0) + 1;
|
|
14650
|
+
try {
|
|
14651
|
+
writeFileSync(path, JSON.stringify(counts), { mode: 384 });
|
|
14652
|
+
} catch {
|
|
14653
|
+
}
|
|
14654
|
+
}
|
|
14620
14655
|
|
|
14621
14656
|
// src/session-probe-runtime.ts
|
|
14622
14657
|
import { execFileSync } from "child_process";
|
|
@@ -15253,7 +15288,7 @@ function resolveReplyThreadTs(input) {
|
|
|
15253
15288
|
}
|
|
15254
15289
|
|
|
15255
15290
|
// src/restart-confirm.ts
|
|
15256
|
-
import { existsSync as existsSync4, mkdirSync, readFileSync as readFileSync4, renameSync, unlinkSync as unlinkSync2, writeFileSync } from "fs";
|
|
15291
|
+
import { existsSync as existsSync4, mkdirSync, readFileSync as readFileSync4, renameSync, unlinkSync as unlinkSync2, writeFileSync as writeFileSync2 } from "fs";
|
|
15257
15292
|
import { dirname } from "path";
|
|
15258
15293
|
import { randomUUID } from "crypto";
|
|
15259
15294
|
var RESTART_CONFIRM_MAX_AGE_MS = 10 * 60 * 1e3;
|
|
@@ -15273,7 +15308,7 @@ function writeRestartConfirmMarker(filePath, marker) {
|
|
|
15273
15308
|
const dir = dirname(filePath);
|
|
15274
15309
|
if (!existsSync4(dir)) mkdirSync(dir, { recursive: true, mode: 448 });
|
|
15275
15310
|
const tmpPath = `${filePath}.${process.pid}.${randomUUID()}.tmp`;
|
|
15276
|
-
|
|
15311
|
+
writeFileSync2(tmpPath, JSON.stringify(marker) + "\n", { encoding: "utf8", mode: 384 });
|
|
15277
15312
|
renameSync(tmpPath, filePath);
|
|
15278
15313
|
}
|
|
15279
15314
|
function readRestartConfirmMarker(filePath) {
|
|
@@ -15388,23 +15423,27 @@ var StdioServerTransport = class {
|
|
|
15388
15423
|
// src/slack-channel.ts
|
|
15389
15424
|
import {
|
|
15390
15425
|
chmodSync,
|
|
15426
|
+
closeSync,
|
|
15391
15427
|
createWriteStream,
|
|
15392
15428
|
existsSync as existsSync7,
|
|
15429
|
+
ftruncateSync,
|
|
15393
15430
|
mkdirSync as mkdirSync5,
|
|
15431
|
+
openSync,
|
|
15394
15432
|
readFileSync as readFileSync8,
|
|
15395
15433
|
readdirSync as readdirSync3,
|
|
15396
15434
|
renameSync as renameSync3,
|
|
15397
15435
|
statSync as statSync2,
|
|
15398
15436
|
unlinkSync as unlinkSync4,
|
|
15399
15437
|
watch,
|
|
15400
|
-
writeFileSync as
|
|
15438
|
+
writeFileSync as writeFileSync6,
|
|
15439
|
+
writeSync
|
|
15401
15440
|
} from "fs";
|
|
15402
15441
|
import { basename, join as join7, resolve as resolve2 } from "path";
|
|
15403
15442
|
import { homedir as homedir3 } from "os";
|
|
15404
15443
|
import { createHash, randomUUID as randomUUID2 } from "crypto";
|
|
15405
15444
|
|
|
15406
15445
|
// src/slack-thread-store.ts
|
|
15407
|
-
import { mkdirSync as mkdirSync2, readFileSync as readFileSync5, writeFileSync as
|
|
15446
|
+
import { mkdirSync as mkdirSync2, readFileSync as readFileSync5, writeFileSync as writeFileSync3 } from "fs";
|
|
15408
15447
|
import { dirname as dirname2 } from "path";
|
|
15409
15448
|
var FILE_VERSION = 1;
|
|
15410
15449
|
var DEFAULT_TTL_DAYS = 30;
|
|
@@ -15464,7 +15503,7 @@ function createThreadPersister(opts) {
|
|
|
15464
15503
|
const writeNow = (snap) => {
|
|
15465
15504
|
try {
|
|
15466
15505
|
mkdirSync2(dirname2(opts.filePath), { recursive: true });
|
|
15467
|
-
|
|
15506
|
+
writeFileSync3(opts.filePath, serializeThreadStore(snap), "utf-8");
|
|
15468
15507
|
lastWriteAt = Date.now();
|
|
15469
15508
|
} catch (err) {
|
|
15470
15509
|
opts.onError?.(
|
|
@@ -15533,7 +15572,7 @@ async function runOrRetry(fn, opts) {
|
|
|
15533
15572
|
}
|
|
15534
15573
|
|
|
15535
15574
|
// src/slack-bot-photo.ts
|
|
15536
|
-
import { existsSync as existsSync5, mkdirSync as mkdirSync3, readFileSync as readFileSync6, writeFileSync as
|
|
15575
|
+
import { existsSync as existsSync5, mkdirSync as mkdirSync3, readFileSync as readFileSync6, writeFileSync as writeFileSync4 } from "fs";
|
|
15537
15576
|
import { dirname as dirname3 } from "path";
|
|
15538
15577
|
async function applyBotPhoto(opts) {
|
|
15539
15578
|
const fetchImpl = opts.fetchImpl ?? fetch;
|
|
@@ -15585,7 +15624,7 @@ async function applyBotPhoto(opts) {
|
|
|
15585
15624
|
if (markerPath) {
|
|
15586
15625
|
try {
|
|
15587
15626
|
mkdirSync3(dirname3(markerPath), { recursive: true, mode: 448 });
|
|
15588
|
-
|
|
15627
|
+
writeFileSync4(markerPath, avatarUrl, { mode: 384 });
|
|
15589
15628
|
} catch {
|
|
15590
15629
|
}
|
|
15591
15630
|
}
|
|
@@ -16341,7 +16380,7 @@ import {
|
|
|
16341
16380
|
readFileSync as readFileSync7,
|
|
16342
16381
|
renameSync as renameSync2,
|
|
16343
16382
|
unlinkSync as unlinkSync3,
|
|
16344
|
-
writeFileSync as
|
|
16383
|
+
writeFileSync as writeFileSync5
|
|
16345
16384
|
} from "fs";
|
|
16346
16385
|
import { join as join6 } from "path";
|
|
16347
16386
|
function defaultIsPidAlive(pid) {
|
|
@@ -16374,7 +16413,7 @@ function acquireMcpSpawnLock(args) {
|
|
|
16374
16413
|
mkdirSync4(agentDir, { recursive: true, mode: 448 });
|
|
16375
16414
|
const tmpPath = `${path}.${selfPid}.tmp`;
|
|
16376
16415
|
const payload = { pid: selfPid, started_at: now() };
|
|
16377
|
-
|
|
16416
|
+
writeFileSync5(tmpPath, JSON.stringify(payload), { mode: 384 });
|
|
16378
16417
|
renameSync2(tmpPath, path);
|
|
16379
16418
|
return { kind: "acquired", path };
|
|
16380
16419
|
}
|
|
@@ -16608,7 +16647,7 @@ function slackPendingInboundPath(channel, threadTs, messageTs) {
|
|
|
16608
16647
|
if (!SLACK_PENDING_INBOUND_DIR) return null;
|
|
16609
16648
|
return join7(SLACK_PENDING_INBOUND_DIR, safeSlackMarkerName(channel, threadTs, messageTs));
|
|
16610
16649
|
}
|
|
16611
|
-
function writeSlackPendingInboundMarker(channel, threadTs, messageTs, undeliverable = false, discretionary = false) {
|
|
16650
|
+
function writeSlackPendingInboundMarker(channel, threadTs, messageTs, undeliverable = false, discretionary = false, payload) {
|
|
16612
16651
|
const path = slackPendingInboundPath(channel, threadTs, messageTs);
|
|
16613
16652
|
if (!path || !SLACK_PENDING_INBOUND_DIR) return;
|
|
16614
16653
|
const marker = {
|
|
@@ -16618,11 +16657,14 @@ function writeSlackPendingInboundMarker(channel, threadTs, messageTs, undelivera
|
|
|
16618
16657
|
received_at: (/* @__PURE__ */ new Date()).toISOString(),
|
|
16619
16658
|
// Only persist the flags when set — keeps healthy-path markers byte-identical.
|
|
16620
16659
|
...undeliverable ? { undeliverable: true } : {},
|
|
16621
|
-
...discretionary ? { discretionary: true } : {}
|
|
16660
|
+
...discretionary ? { discretionary: true } : {},
|
|
16661
|
+
// ENG-5969/ENG-6327: carry the replay payload only when provided (the
|
|
16662
|
+
// durable-replay path). Absent ⇒ the marker can't be replayed.
|
|
16663
|
+
...payload ? { payload } : {}
|
|
16622
16664
|
};
|
|
16623
16665
|
try {
|
|
16624
16666
|
mkdirSync5(SLACK_PENDING_INBOUND_DIR, { recursive: true, mode: 448 });
|
|
16625
|
-
|
|
16667
|
+
writeFileSync6(path, JSON.stringify(marker), { mode: 384 });
|
|
16626
16668
|
} catch (err) {
|
|
16627
16669
|
process.stderr.write(
|
|
16628
16670
|
`slack-channel(${AGENT_CODE_NAME}): pending-inbound marker write failed: ${err.message}
|
|
@@ -16630,6 +16672,35 @@ function writeSlackPendingInboundMarker(channel, threadTs, messageTs, undelivera
|
|
|
16630
16672
|
);
|
|
16631
16673
|
}
|
|
16632
16674
|
}
|
|
16675
|
+
function rewriteSlackMarkerInPlace(path, marker) {
|
|
16676
|
+
let fd;
|
|
16677
|
+
try {
|
|
16678
|
+
fd = openSync(path, "r+");
|
|
16679
|
+
const buf = Buffer.from(JSON.stringify(marker));
|
|
16680
|
+
ftruncateSync(fd, 0);
|
|
16681
|
+
writeSync(fd, buf, 0, buf.length, 0);
|
|
16682
|
+
} catch {
|
|
16683
|
+
} finally {
|
|
16684
|
+
if (fd !== void 0) {
|
|
16685
|
+
try {
|
|
16686
|
+
closeSync(fd);
|
|
16687
|
+
} catch {
|
|
16688
|
+
}
|
|
16689
|
+
}
|
|
16690
|
+
}
|
|
16691
|
+
}
|
|
16692
|
+
function attachSlackReplayPayload(channel, threadTs, messageTs, payload) {
|
|
16693
|
+
const path = slackPendingInboundPath(channel, threadTs, messageTs);
|
|
16694
|
+
if (!path) return;
|
|
16695
|
+
let marker;
|
|
16696
|
+
try {
|
|
16697
|
+
marker = JSON.parse(readFileSync8(path, "utf-8"));
|
|
16698
|
+
} catch {
|
|
16699
|
+
return;
|
|
16700
|
+
}
|
|
16701
|
+
marker.payload = payload;
|
|
16702
|
+
rewriteSlackMarkerInPlace(path, marker);
|
|
16703
|
+
}
|
|
16633
16704
|
function readSlackPendingInboundMarker(channel, threadTs, messageTs) {
|
|
16634
16705
|
const path = slackPendingInboundPath(channel, threadTs, messageTs);
|
|
16635
16706
|
if (!path || !existsSync7(path)) return null;
|
|
@@ -16743,7 +16814,14 @@ function scheduleBusyAck(channel, threadTs, messageTs, isThreadReply) {
|
|
|
16743
16814
|
paneLogFreshAgeMs,
|
|
16744
16815
|
thresholdMs
|
|
16745
16816
|
});
|
|
16746
|
-
if (post)
|
|
16817
|
+
if (post) {
|
|
16818
|
+
recordChannelDeflection(SLACK_AGENT_DIR, "slack", "busy");
|
|
16819
|
+
process.stderr.write(
|
|
16820
|
+
`slack-channel(${AGENT_CODE_NAME}): [channel-deflection] cause=busy channel=slack ts=${redactSlackId(messageTs)}
|
|
16821
|
+
`
|
|
16822
|
+
);
|
|
16823
|
+
postBusyAckNotice(channel, threadTs, isThreadReply);
|
|
16824
|
+
}
|
|
16747
16825
|
}, thresholdMs);
|
|
16748
16826
|
timer.unref();
|
|
16749
16827
|
}
|
|
@@ -16967,8 +17045,8 @@ function startSlackRecoveryOutboxWatcher() {
|
|
|
16967
17045
|
startSlackRecoveryOutboxWatcher();
|
|
16968
17046
|
var STALE_MARKER_MS = 24 * 60 * 60 * 1e3;
|
|
16969
17047
|
var DISCRETIONARY_MARKER_MS = 10 * 60 * 1e3;
|
|
16970
|
-
function trackPendingMessage(channel, threadTs, messageTs, undeliverable = false, discretionary = false) {
|
|
16971
|
-
writeSlackPendingInboundMarker(channel, threadTs, messageTs, undeliverable, discretionary);
|
|
17048
|
+
function trackPendingMessage(channel, threadTs, messageTs, undeliverable = false, discretionary = false, payload) {
|
|
17049
|
+
writeSlackPendingInboundMarker(channel, threadTs, messageTs, undeliverable, discretionary, payload);
|
|
16972
17050
|
}
|
|
16973
17051
|
function sweepSlackStaleMarkers(thresholdMs) {
|
|
16974
17052
|
if (!SLACK_PENDING_INBOUND_DIR) return;
|
|
@@ -17684,7 +17762,7 @@ async function handleSlashCommandEnvelope(payload) {
|
|
|
17684
17762
|
}
|
|
17685
17763
|
};
|
|
17686
17764
|
const tmpPath = `${flagPath}.${process.pid}.${randomUUID2()}.tmp`;
|
|
17687
|
-
|
|
17765
|
+
writeFileSync6(tmpPath, JSON.stringify(flag) + "\n", "utf8");
|
|
17688
17766
|
renameSync3(tmpPath, flagPath);
|
|
17689
17767
|
process.stderr.write(
|
|
17690
17768
|
`slack-channel(${codeName}): /restart slash-command queued from channel ${hashChannelId(payload.channel_id)}
|
|
@@ -17803,7 +17881,7 @@ async function handleRestartCommand(opts) {
|
|
|
17803
17881
|
}
|
|
17804
17882
|
};
|
|
17805
17883
|
const tmpPath = `${flagPath}.${process.pid}.${randomUUID2()}.tmp`;
|
|
17806
|
-
|
|
17884
|
+
writeFileSync6(tmpPath, JSON.stringify(flag) + "\n", "utf8");
|
|
17807
17885
|
renameSync3(tmpPath, flagPath);
|
|
17808
17886
|
process.stderr.write(
|
|
17809
17887
|
`slack-channel(${codeName}): /restart queued from channel ${hashChannelId(opts.channel)}
|
|
@@ -19164,7 +19242,7 @@ async function downloadSlackFile(fileId, codeName) {
|
|
|
19164
19242
|
throw new Error(`refusing to write ${savedPath} outside ${dir}`);
|
|
19165
19243
|
}
|
|
19166
19244
|
mkdirSync5(dir, { recursive: true });
|
|
19167
|
-
|
|
19245
|
+
writeFileSync6(savedPath, bytes, { mode: 384 });
|
|
19168
19246
|
try {
|
|
19169
19247
|
chmodSync(savedPath, 384);
|
|
19170
19248
|
} catch {
|
|
@@ -19216,6 +19294,77 @@ async function buildInboundFileMeta(rawFiles, codeName, channel) {
|
|
|
19216
19294
|
return out;
|
|
19217
19295
|
}
|
|
19218
19296
|
await mcp.connect(new StdioServerTransport());
|
|
19297
|
+
var SLACK_REPLAY_SCAN_INTERVAL_MS = 6e4;
|
|
19298
|
+
async function replayPendingSlackMarkers() {
|
|
19299
|
+
if (!channelReplayEnabled()) return;
|
|
19300
|
+
if (!SLACK_PENDING_INBOUND_DIR || !existsSync7(SLACK_PENDING_INBOUND_DIR)) return;
|
|
19301
|
+
const probe = process.env.TMUX && AGENT_CODE_NAME ? probeAgentSessionCached(AGENT_CODE_NAME) : { tmux: "unknown", claude: "unknown" };
|
|
19302
|
+
const sessionAlive = probe.tmux === "alive" && probe.claude === "alive";
|
|
19303
|
+
if (!sessionAlive) return;
|
|
19304
|
+
let filenames;
|
|
19305
|
+
try {
|
|
19306
|
+
filenames = readdirSync3(SLACK_PENDING_INBOUND_DIR);
|
|
19307
|
+
} catch {
|
|
19308
|
+
return;
|
|
19309
|
+
}
|
|
19310
|
+
const now = Date.now();
|
|
19311
|
+
const entries = [];
|
|
19312
|
+
for (const name of filenames) {
|
|
19313
|
+
if (!name.endsWith(".json") || name.endsWith(".tmp")) continue;
|
|
19314
|
+
const fullPath = join7(SLACK_PENDING_INBOUND_DIR, name);
|
|
19315
|
+
let marker;
|
|
19316
|
+
try {
|
|
19317
|
+
marker = JSON.parse(readFileSync8(fullPath, "utf-8"));
|
|
19318
|
+
} catch {
|
|
19319
|
+
continue;
|
|
19320
|
+
}
|
|
19321
|
+
const t = Date.parse(marker.received_at ?? "");
|
|
19322
|
+
const ageMs = Number.isFinite(t) ? now - t : 0;
|
|
19323
|
+
entries.push({ path: fullPath, marker, ageMs });
|
|
19324
|
+
}
|
|
19325
|
+
entries.sort((a, b) => b.ageMs - a.ageMs);
|
|
19326
|
+
for (const { path, marker, ageMs } of entries) {
|
|
19327
|
+
if (!shouldReplayMarker({
|
|
19328
|
+
enabled: true,
|
|
19329
|
+
hasPayload: Boolean(marker.payload),
|
|
19330
|
+
sessionAlive,
|
|
19331
|
+
markerAgeMs: ageMs,
|
|
19332
|
+
replayCount: marker.replay_count ?? 0
|
|
19333
|
+
})) {
|
|
19334
|
+
continue;
|
|
19335
|
+
}
|
|
19336
|
+
const payload = marker.payload;
|
|
19337
|
+
const replayParams = {
|
|
19338
|
+
...payload,
|
|
19339
|
+
meta: { ...payload.meta, replayed: "true" }
|
|
19340
|
+
};
|
|
19341
|
+
try {
|
|
19342
|
+
await mcp.notification({
|
|
19343
|
+
method: "notifications/claude/channel",
|
|
19344
|
+
params: replayParams
|
|
19345
|
+
});
|
|
19346
|
+
} catch (err) {
|
|
19347
|
+
process.stderr.write(
|
|
19348
|
+
`slack-channel(${AGENT_CODE_NAME}): replay push failed: ${err.message}
|
|
19349
|
+
`
|
|
19350
|
+
);
|
|
19351
|
+
continue;
|
|
19352
|
+
}
|
|
19353
|
+
rewriteSlackMarkerInPlace(path, {
|
|
19354
|
+
...marker,
|
|
19355
|
+
replay_count: (marker.replay_count ?? 0) + 1
|
|
19356
|
+
});
|
|
19357
|
+
}
|
|
19358
|
+
}
|
|
19359
|
+
var slackReplayScanInFlight = false;
|
|
19360
|
+
var slackReplayTimer = setInterval(() => {
|
|
19361
|
+
if (slackReplayScanInFlight) return;
|
|
19362
|
+
slackReplayScanInFlight = true;
|
|
19363
|
+
void replayPendingSlackMarkers().finally(() => {
|
|
19364
|
+
slackReplayScanInFlight = false;
|
|
19365
|
+
});
|
|
19366
|
+
}, SLACK_REPLAY_SCAN_INTERVAL_MS);
|
|
19367
|
+
slackReplayTimer.unref?.();
|
|
19219
19368
|
var userNameCache = /* @__PURE__ */ new Map();
|
|
19220
19369
|
async function resolveUserName(userId) {
|
|
19221
19370
|
if (!userId || userId === "unknown") return userId ?? "unknown";
|
|
@@ -19530,7 +19679,7 @@ async function connectSocketMode() {
|
|
|
19530
19679
|
} catch {
|
|
19531
19680
|
}
|
|
19532
19681
|
}
|
|
19533
|
-
const
|
|
19682
|
+
const ackInputs = {
|
|
19534
19683
|
hasTarget: decideSlackAckReaction({ channel, ts }),
|
|
19535
19684
|
integrationReady: Boolean(BOT_TOKEN),
|
|
19536
19685
|
tmux: ackProbe.tmux,
|
|
@@ -19538,7 +19687,16 @@ async function connectSocketMode() {
|
|
|
19538
19687
|
withinStartupGrace: process.uptime() * 1e3 < ACK_STARTUP_GRACE_MS,
|
|
19539
19688
|
oldestPendingAgeMs: oldestPendingMarkerAgeMs(SLACK_PENDING_INBOUND_DIR),
|
|
19540
19689
|
paneLogFreshAgeMs
|
|
19541
|
-
}
|
|
19690
|
+
};
|
|
19691
|
+
const ackDecision = decideAckReaction(ackInputs);
|
|
19692
|
+
if (ackDecision === "undeliverable") {
|
|
19693
|
+
const cause = classifyUndeliverableCause(ackInputs) ?? "unknown";
|
|
19694
|
+
recordChannelDeflection(SLACK_AGENT_DIR, "slack", cause);
|
|
19695
|
+
process.stderr.write(
|
|
19696
|
+
`slack-channel(${AGENT_CODE_NAME}): [channel-deflection] cause=${cause} channel=slack ts=${redactSlackId(ts)}
|
|
19697
|
+
`
|
|
19698
|
+
);
|
|
19699
|
+
}
|
|
19542
19700
|
if (ackDecision !== "none") {
|
|
19543
19701
|
const reactionName = ackDecision === "undeliverable" ? SLACK_UNDELIVERABLE_REACTION : "eyes";
|
|
19544
19702
|
fetch("https://slack.com/api/reactions.add", {
|
|
@@ -19596,32 +19754,36 @@ async function connectSocketMode() {
|
|
|
19596
19754
|
`
|
|
19597
19755
|
);
|
|
19598
19756
|
}
|
|
19757
|
+
const replayPayload = {
|
|
19758
|
+
content: inboundContent,
|
|
19759
|
+
meta: {
|
|
19760
|
+
user,
|
|
19761
|
+
user_name: userName,
|
|
19762
|
+
channel,
|
|
19763
|
+
thread_ts: threadTs,
|
|
19764
|
+
// ENG-5861: explicit message_ts so the agent can pass it back
|
|
19765
|
+
// to slack.reply for reliable pending-inbound cleanup. For
|
|
19766
|
+
// top-level messages threadTs === ts; for threaded replies
|
|
19767
|
+
// threadTs is the thread root and message_ts is the specific
|
|
19768
|
+
// reply being addressed — different keys, both needed by the
|
|
19769
|
+
// marker-cleanup gate.
|
|
19770
|
+
message_ts: ts,
|
|
19771
|
+
event_type: evt.type,
|
|
19772
|
+
...isAutoFollowed ? { auto_followed: "true" } : {},
|
|
19773
|
+
// Only set these when we actually have attachments to avoid
|
|
19774
|
+
// bloating every notification with empty metadata.
|
|
19775
|
+
...fileMeta.length > 0 ? { files: JSON.stringify(fileMeta) } : {},
|
|
19776
|
+
...imagePath ? { image_path: imagePath } : {},
|
|
19777
|
+
// ENG-5830: the pre-loaded surrounding thread (thread replies only).
|
|
19778
|
+
...threadContext ? { thread_context: threadContext } : {}
|
|
19779
|
+
}
|
|
19780
|
+
};
|
|
19781
|
+
if (channel && ts && armMarker) {
|
|
19782
|
+
attachSlackReplayPayload(channel, threadTs, ts, replayPayload);
|
|
19783
|
+
}
|
|
19599
19784
|
await mcp.notification({
|
|
19600
19785
|
method: "notifications/claude/channel",
|
|
19601
|
-
params:
|
|
19602
|
-
content: inboundContent,
|
|
19603
|
-
meta: {
|
|
19604
|
-
user,
|
|
19605
|
-
user_name: userName,
|
|
19606
|
-
channel,
|
|
19607
|
-
thread_ts: threadTs,
|
|
19608
|
-
// ENG-5861: explicit message_ts so the agent can pass it back
|
|
19609
|
-
// to slack.reply for reliable pending-inbound cleanup. For
|
|
19610
|
-
// top-level messages threadTs === ts; for threaded replies
|
|
19611
|
-
// threadTs is the thread root and message_ts is the specific
|
|
19612
|
-
// reply being addressed — different keys, both needed by the
|
|
19613
|
-
// marker-cleanup gate.
|
|
19614
|
-
message_ts: ts,
|
|
19615
|
-
event_type: evt.type,
|
|
19616
|
-
...isAutoFollowed ? { auto_followed: "true" } : {},
|
|
19617
|
-
// Only set these when we actually have attachments to avoid
|
|
19618
|
-
// bloating every notification with empty metadata.
|
|
19619
|
-
...fileMeta.length > 0 ? { files: JSON.stringify(fileMeta) } : {},
|
|
19620
|
-
...imagePath ? { image_path: imagePath } : {},
|
|
19621
|
-
// ENG-5830: the pre-loaded surrounding thread (thread replies only).
|
|
19622
|
-
...threadContext ? { thread_context: threadContext } : {}
|
|
19623
|
-
}
|
|
19624
|
-
}
|
|
19786
|
+
params: replayPayload
|
|
19625
19787
|
});
|
|
19626
19788
|
if (channel) {
|
|
19627
19789
|
conversationIngestClient?.ingest({
|
|
@@ -14317,16 +14317,20 @@ var StdioServerTransport = class {
|
|
|
14317
14317
|
import https from "https";
|
|
14318
14318
|
import { createHash, randomUUID as randomUUID2 } from "crypto";
|
|
14319
14319
|
import {
|
|
14320
|
+
closeSync,
|
|
14320
14321
|
createWriteStream,
|
|
14321
14322
|
existsSync as existsSync5,
|
|
14323
|
+
ftruncateSync,
|
|
14322
14324
|
mkdirSync as mkdirSync4,
|
|
14325
|
+
openSync,
|
|
14323
14326
|
readFileSync as readFileSync7,
|
|
14324
14327
|
readdirSync as readdirSync3,
|
|
14325
14328
|
renameSync as renameSync4,
|
|
14326
14329
|
statSync as statSync2,
|
|
14327
14330
|
unlinkSync as unlinkSync4,
|
|
14328
14331
|
watch,
|
|
14329
|
-
writeFileSync as
|
|
14332
|
+
writeFileSync as writeFileSync5,
|
|
14333
|
+
writeSync
|
|
14330
14334
|
} from "fs";
|
|
14331
14335
|
import { homedir as homedir3 } from "os";
|
|
14332
14336
|
import { join as join7 } from "path";
|
|
@@ -16107,7 +16111,7 @@ function readLockHolder(path) {
|
|
|
16107
16111
|
}
|
|
16108
16112
|
|
|
16109
16113
|
// src/ack-reaction.ts
|
|
16110
|
-
import { readdirSync as readdirSync2, readFileSync as readFileSync6 } from "fs";
|
|
16114
|
+
import { readdirSync as readdirSync2, readFileSync as readFileSync6, writeFileSync as writeFileSync4 } from "fs";
|
|
16111
16115
|
import { join as join6 } from "path";
|
|
16112
16116
|
|
|
16113
16117
|
// src/flags-cache-read.ts
|
|
@@ -16153,19 +16157,23 @@ var ACK_STARTUP_GRACE_MS = 6e4;
|
|
|
16153
16157
|
var ACK_PANE_FRESH_THRESHOLD_MS = 6e4;
|
|
16154
16158
|
function decideAckReaction(i) {
|
|
16155
16159
|
if (!i.hasTarget) return "none";
|
|
16156
|
-
|
|
16157
|
-
|
|
16158
|
-
|
|
16160
|
+
return classifyUndeliverableCause(i) !== null ? "undeliverable" : "ack";
|
|
16161
|
+
}
|
|
16162
|
+
function classifyUndeliverableCause(i) {
|
|
16163
|
+
if (!i.hasTarget) return null;
|
|
16164
|
+
if (!i.integrationReady) return "integration_down";
|
|
16165
|
+
if (i.tmux === "dead") return "session_dead";
|
|
16166
|
+
if (!i.withinStartupGrace && i.claude === "dead") return "session_dead";
|
|
16159
16167
|
const threshold = i.pendingStaleThresholdMs ?? REPLY_WEDGED_THRESHOLD_MS;
|
|
16160
16168
|
if (i.oldestPendingAgeMs != null && i.oldestPendingAgeMs > threshold) {
|
|
16161
16169
|
const paneFreshThreshold = i.paneFreshThresholdMs ?? ACK_PANE_FRESH_THRESHOLD_MS;
|
|
16162
16170
|
const paneIsFresh = i.paneLogFreshAgeMs != null && i.paneLogFreshAgeMs <= paneFreshThreshold;
|
|
16163
16171
|
if (paneIsFresh && i.tmux === "alive" && i.claude === "alive") {
|
|
16164
|
-
return
|
|
16172
|
+
return null;
|
|
16165
16173
|
}
|
|
16166
|
-
return "
|
|
16174
|
+
return "wedged";
|
|
16167
16175
|
}
|
|
16168
|
-
return
|
|
16176
|
+
return null;
|
|
16169
16177
|
}
|
|
16170
16178
|
function decideRecoveryHeal(i) {
|
|
16171
16179
|
return i.wasUndeliverable && i.hasTarget ? "heal" : "none";
|
|
@@ -16174,8 +16182,8 @@ var UNDELIVERABLE_NOTICE_THROTTLE_MS = 5 * 60 * 1e3;
|
|
|
16174
16182
|
function shouldPostUndeliverableNotice(lastNoticeAtMs, nowMs, throttleMs = UNDELIVERABLE_NOTICE_THROTTLE_MS) {
|
|
16175
16183
|
return lastNoticeAtMs == null || nowMs - lastNoticeAtMs >= throttleMs;
|
|
16176
16184
|
}
|
|
16177
|
-
function undeliverableNoticeText() {
|
|
16178
|
-
return "\u23F3 I can't get to this right now. Please resend in a few minutes \u2014 I may not see this one.";
|
|
16185
|
+
function undeliverableNoticeText(replayEnabled = channelReplayEnabled()) {
|
|
16186
|
+
return replayEnabled ? "\u23F3 I can't get to this right now \u2014 no need to resend; I'll pick it up automatically as soon as I'm free." : "\u23F3 I can't get to this right now. Please resend in a few minutes \u2014 I may not see this one.";
|
|
16179
16187
|
}
|
|
16180
16188
|
var BUSY_ACK_THRESHOLD_MS = 9e4;
|
|
16181
16189
|
var BUSY_ACK_NOTICE_THROTTLE_MS = 10 * 60 * 1e3;
|
|
@@ -16282,6 +16290,25 @@ function shouldReplayMarker(i) {
|
|
|
16282
16290
|
if (i.markerAgeMs < (i.minAgeMs ?? REPLY_WEDGED_THRESHOLD_MS)) return false;
|
|
16283
16291
|
return i.replayCount < (i.maxReplays ?? MAX_MARKER_REPLAYS);
|
|
16284
16292
|
}
|
|
16293
|
+
var DEFLECTION_COUNTER_SUFFIX = "-deflections.json";
|
|
16294
|
+
function deflectionCounterPath(agentDir, channel) {
|
|
16295
|
+
return join6(agentDir, `${channel}${DEFLECTION_COUNTER_SUFFIX}`);
|
|
16296
|
+
}
|
|
16297
|
+
function recordChannelDeflection(agentDir, channel, cause) {
|
|
16298
|
+
if (!agentDir) return;
|
|
16299
|
+
const path = deflectionCounterPath(agentDir, channel);
|
|
16300
|
+
let counts = {};
|
|
16301
|
+
try {
|
|
16302
|
+
const parsed = JSON.parse(readFileSync6(path, "utf-8"));
|
|
16303
|
+
if (parsed && typeof parsed === "object") counts = parsed;
|
|
16304
|
+
} catch {
|
|
16305
|
+
}
|
|
16306
|
+
counts[cause] = (counts[cause] ?? 0) + 1;
|
|
16307
|
+
try {
|
|
16308
|
+
writeFileSync4(path, JSON.stringify(counts), { mode: 384 });
|
|
16309
|
+
} catch {
|
|
16310
|
+
}
|
|
16311
|
+
}
|
|
16285
16312
|
|
|
16286
16313
|
// src/telegram-channel.ts
|
|
16287
16314
|
function redactId(id) {
|
|
@@ -16590,7 +16617,14 @@ function scheduleBusyAck(chatId, messageId) {
|
|
|
16590
16617
|
paneLogFreshAgeMs,
|
|
16591
16618
|
thresholdMs
|
|
16592
16619
|
});
|
|
16593
|
-
if (post)
|
|
16620
|
+
if (post) {
|
|
16621
|
+
recordChannelDeflection(AGENT_DIR, "telegram", "busy");
|
|
16622
|
+
process.stderr.write(
|
|
16623
|
+
`telegram-channel(${AGENT_CODE_NAME}): [channel-deflection] cause=busy channel=telegram chat=${redactId(chatId)}
|
|
16624
|
+
`
|
|
16625
|
+
);
|
|
16626
|
+
postBusyAckNotice(chatId, messageId);
|
|
16627
|
+
}
|
|
16594
16628
|
}, thresholdMs);
|
|
16595
16629
|
timer.unref();
|
|
16596
16630
|
}
|
|
@@ -16802,7 +16836,7 @@ async function handleRestartCommand(opts) {
|
|
|
16802
16836
|
reply: { chat_id: opts.chatId, message_id: opts.messageId }
|
|
16803
16837
|
};
|
|
16804
16838
|
const tmpPath = `${flagPath}.${process.pid}.${randomUUID2()}.tmp`;
|
|
16805
|
-
|
|
16839
|
+
writeFileSync5(tmpPath, JSON.stringify(flag) + "\n", "utf8");
|
|
16806
16840
|
renameSync4(tmpPath, flagPath);
|
|
16807
16841
|
process.stderr.write(
|
|
16808
16842
|
`telegram-channel(${AGENT_CODE_NAME}): /restart queued from chat ${redactId(opts.chatId)}
|
|
@@ -17202,7 +17236,7 @@ function writePendingInboundMarker(chatId, messageId, chatType, undeliverable =
|
|
|
17202
17236
|
};
|
|
17203
17237
|
try {
|
|
17204
17238
|
mkdirSync4(PENDING_INBOUND_DIR, { recursive: true, mode: 448 });
|
|
17205
|
-
|
|
17239
|
+
writeFileSync5(path, JSON.stringify(marker), { mode: 384 });
|
|
17206
17240
|
} catch (err) {
|
|
17207
17241
|
process.stderr.write(
|
|
17208
17242
|
`telegram-channel(${AGENT_CODE_NAME}): pending-inbound marker write failed: ${err.message}
|
|
@@ -17210,6 +17244,23 @@ function writePendingInboundMarker(chatId, messageId, chatType, undeliverable =
|
|
|
17210
17244
|
);
|
|
17211
17245
|
}
|
|
17212
17246
|
}
|
|
17247
|
+
function rewriteTelegramMarkerInPlace(path, marker) {
|
|
17248
|
+
let fd;
|
|
17249
|
+
try {
|
|
17250
|
+
fd = openSync(path, "r+");
|
|
17251
|
+
const buf = Buffer.from(JSON.stringify(marker));
|
|
17252
|
+
ftruncateSync(fd, 0);
|
|
17253
|
+
writeSync(fd, buf, 0, buf.length, 0);
|
|
17254
|
+
} catch {
|
|
17255
|
+
} finally {
|
|
17256
|
+
if (fd !== void 0) {
|
|
17257
|
+
try {
|
|
17258
|
+
closeSync(fd);
|
|
17259
|
+
} catch {
|
|
17260
|
+
}
|
|
17261
|
+
}
|
|
17262
|
+
}
|
|
17263
|
+
}
|
|
17213
17264
|
function clearTelegramMarkerFileWithHeal(fullPath) {
|
|
17214
17265
|
let marker = null;
|
|
17215
17266
|
try {
|
|
@@ -18232,10 +18283,15 @@ async function replayPendingTelegramMarkers() {
|
|
|
18232
18283
|
})) {
|
|
18233
18284
|
continue;
|
|
18234
18285
|
}
|
|
18286
|
+
const replayPayload = marker.payload;
|
|
18287
|
+
const replayParams = {
|
|
18288
|
+
...replayPayload,
|
|
18289
|
+
meta: { ...replayPayload.meta, replayed: "true" }
|
|
18290
|
+
};
|
|
18235
18291
|
try {
|
|
18236
18292
|
await mcp.notification({
|
|
18237
18293
|
method: "notifications/claude/channel",
|
|
18238
|
-
params:
|
|
18294
|
+
params: replayParams
|
|
18239
18295
|
});
|
|
18240
18296
|
} catch (err) {
|
|
18241
18297
|
process.stderr.write(
|
|
@@ -18244,20 +18300,19 @@ async function replayPendingTelegramMarkers() {
|
|
|
18244
18300
|
);
|
|
18245
18301
|
continue;
|
|
18246
18302
|
}
|
|
18247
|
-
|
|
18248
|
-
|
|
18249
|
-
|
|
18250
|
-
|
|
18251
|
-
replay_count: (marker.replay_count ?? 0) + 1
|
|
18252
|
-
};
|
|
18253
|
-
writeFileSync4(path, JSON.stringify(updated), { mode: 384 });
|
|
18254
|
-
}
|
|
18255
|
-
} catch {
|
|
18256
|
-
}
|
|
18303
|
+
rewriteTelegramMarkerInPlace(path, {
|
|
18304
|
+
...marker,
|
|
18305
|
+
replay_count: (marker.replay_count ?? 0) + 1
|
|
18306
|
+
});
|
|
18257
18307
|
}
|
|
18258
18308
|
}
|
|
18309
|
+
var telegramReplayScanInFlight = false;
|
|
18259
18310
|
var replayTimer = setInterval(() => {
|
|
18260
|
-
|
|
18311
|
+
if (telegramReplayScanInFlight) return;
|
|
18312
|
+
telegramReplayScanInFlight = true;
|
|
18313
|
+
void replayPendingTelegramMarkers().finally(() => {
|
|
18314
|
+
telegramReplayScanInFlight = false;
|
|
18315
|
+
});
|
|
18261
18316
|
}, REPLAY_SCAN_INTERVAL_MS);
|
|
18262
18317
|
replayTimer.unref?.();
|
|
18263
18318
|
var LONG_POLL_SECONDS = 50;
|
|
@@ -18626,7 +18681,7 @@ async function pollLoop() {
|
|
|
18626
18681
|
} catch {
|
|
18627
18682
|
}
|
|
18628
18683
|
}
|
|
18629
|
-
const
|
|
18684
|
+
const ackInputs = {
|
|
18630
18685
|
hasTarget: Boolean(chatId && messageId),
|
|
18631
18686
|
integrationReady: Boolean(BOT_TOKEN),
|
|
18632
18687
|
tmux: ackProbe.tmux,
|
|
@@ -18634,7 +18689,16 @@ async function pollLoop() {
|
|
|
18634
18689
|
withinStartupGrace: process.uptime() * 1e3 < ACK_STARTUP_GRACE_MS,
|
|
18635
18690
|
oldestPendingAgeMs: oldestPendingMarkerAgeMs(PENDING_INBOUND_DIR),
|
|
18636
18691
|
paneLogFreshAgeMs
|
|
18637
|
-
}
|
|
18692
|
+
};
|
|
18693
|
+
const ackDecision = decideAckReaction(ackInputs);
|
|
18694
|
+
if (ackDecision === "undeliverable") {
|
|
18695
|
+
const cause = classifyUndeliverableCause(ackInputs) ?? "unknown";
|
|
18696
|
+
recordChannelDeflection(AGENT_DIR, "telegram", cause);
|
|
18697
|
+
process.stderr.write(
|
|
18698
|
+
`telegram-channel(${AGENT_CODE_NAME}): [channel-deflection] cause=${cause} channel=telegram chat=${redactId(chatId)}
|
|
18699
|
+
`
|
|
18700
|
+
);
|
|
18701
|
+
}
|
|
18638
18702
|
if (ackDecision === "ack") {
|
|
18639
18703
|
void setMessageReaction(chatId, messageId, ACK_EMOJI);
|
|
18640
18704
|
} else if (ackDecision === "undeliverable") {
|