@integrity-labs/agt-cli 0.27.82 → 0.27.84
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-3JXDBRNG.js → chunk-S2QLE5BQ.js} +2 -2
- package/dist/{chunk-GNIA4KN5.js → chunk-TXE2LLKI.js} +11 -1
- package/dist/chunk-TXE2LLKI.js.map +1 -0
- package/dist/{chunk-AN5X6CN2.js → chunk-WISOJYIV.js} +27 -20
- package/dist/chunk-WISOJYIV.js.map +1 -0
- package/dist/{claude-pair-runtime-RTM4GWZG.js → claude-pair-runtime-MKK7LSQG.js} +2 -2
- package/dist/lib/manager-worker.js +7 -7
- package/dist/mcp/slack-channel.js +431 -95
- package/dist/{persistent-session-B5SRS4N4.js → persistent-session-BTXWOKJ6.js} +3 -3
- package/dist/{responsiveness-probe-2QWNZTF4.js → responsiveness-probe-Y6TCM6T4.js} +3 -3
- package/package.json +1 -1
- package/dist/chunk-AN5X6CN2.js.map +0 -1
- package/dist/chunk-GNIA4KN5.js.map +0 -1
- /package/dist/{chunk-3JXDBRNG.js.map → chunk-S2QLE5BQ.js.map} +0 -0
- /package/dist/{claude-pair-runtime-RTM4GWZG.js.map → claude-pair-runtime-MKK7LSQG.js.map} +0 -0
- /package/dist/{persistent-session-B5SRS4N4.js.map → persistent-session-BTXWOKJ6.js.map} +0 -0
- /package/dist/{responsiveness-probe-2QWNZTF4.js.map → responsiveness-probe-Y6TCM6T4.js.map} +0 -0
|
@@ -14449,6 +14449,232 @@ function probeAgentSessionCached(codeName, ttlMs = SESSION_PROBE_TTL_MS, now = D
|
|
|
14449
14449
|
return value;
|
|
14450
14450
|
}
|
|
14451
14451
|
|
|
14452
|
+
// src/pane-tail.ts
|
|
14453
|
+
import { execFile } from "child_process";
|
|
14454
|
+
import { promisify } from "util";
|
|
14455
|
+
import { open, stat } from "fs/promises";
|
|
14456
|
+
|
|
14457
|
+
// src/channel-attachments.ts
|
|
14458
|
+
import { homedir } from "os";
|
|
14459
|
+
import { join as join2, resolve, sep } from "path";
|
|
14460
|
+
function resolveChannelInboundDir(codeName, channelSlug) {
|
|
14461
|
+
const base = join2(homedir(), ".augmented");
|
|
14462
|
+
const allowedSegment = /^[A-Za-z0-9_-]+$/;
|
|
14463
|
+
if (!allowedSegment.test(codeName) || !allowedSegment.test(channelSlug)) {
|
|
14464
|
+
throw new Error(
|
|
14465
|
+
`Refusing to resolve inbound dir \u2014 invalid codeName/channelSlug (got ${JSON.stringify({ codeName, channelSlug })})`
|
|
14466
|
+
);
|
|
14467
|
+
}
|
|
14468
|
+
const candidate = resolve(base, codeName, channelSlug);
|
|
14469
|
+
if (!isPathInside(candidate, base)) {
|
|
14470
|
+
throw new Error(`Refusing inbound dir outside ${base} (got ${candidate})`);
|
|
14471
|
+
}
|
|
14472
|
+
return candidate;
|
|
14473
|
+
}
|
|
14474
|
+
function classifyMimetype(mimetype) {
|
|
14475
|
+
if (typeof mimetype === "string" && mimetype.startsWith("image/")) return "image";
|
|
14476
|
+
return "attachment";
|
|
14477
|
+
}
|
|
14478
|
+
function buildSafeInboundPath(root, fileId, mimetype) {
|
|
14479
|
+
const safeId = fileId.replace(/[^A-Za-z0-9_-]/g, "");
|
|
14480
|
+
if (!safeId) throw new Error("Refusing to build inbound path for empty/invalid file id");
|
|
14481
|
+
const ext = extensionForMimetype(mimetype);
|
|
14482
|
+
const candidate = resolve(root, `${safeId}${ext}`);
|
|
14483
|
+
if (!isPathInside(candidate, root)) {
|
|
14484
|
+
throw new Error(`Refusing to build inbound path outside agent dir (got ${candidate})`);
|
|
14485
|
+
}
|
|
14486
|
+
return candidate;
|
|
14487
|
+
}
|
|
14488
|
+
function extensionForMimetype(mimetype) {
|
|
14489
|
+
if (!mimetype) return ".bin";
|
|
14490
|
+
switch (mimetype) {
|
|
14491
|
+
// Images
|
|
14492
|
+
case "image/png":
|
|
14493
|
+
return ".png";
|
|
14494
|
+
case "image/jpeg":
|
|
14495
|
+
case "image/jpg":
|
|
14496
|
+
return ".jpg";
|
|
14497
|
+
case "image/gif":
|
|
14498
|
+
return ".gif";
|
|
14499
|
+
case "image/webp":
|
|
14500
|
+
return ".webp";
|
|
14501
|
+
case "image/svg+xml":
|
|
14502
|
+
return ".svg";
|
|
14503
|
+
// Docs
|
|
14504
|
+
case "application/pdf":
|
|
14505
|
+
return ".pdf";
|
|
14506
|
+
case "text/plain":
|
|
14507
|
+
return ".txt";
|
|
14508
|
+
case "text/csv":
|
|
14509
|
+
return ".csv";
|
|
14510
|
+
case "application/json":
|
|
14511
|
+
return ".json";
|
|
14512
|
+
case "application/vnd.openxmlformats-officedocument.wordprocessingml.document":
|
|
14513
|
+
return ".docx";
|
|
14514
|
+
case "application/vnd.openxmlformats-officedocument.spreadsheetml.sheet":
|
|
14515
|
+
return ".xlsx";
|
|
14516
|
+
// Audio (Telegram voice notes are typically audio/ogg; regular
|
|
14517
|
+
// audio messages are audio/mpeg)
|
|
14518
|
+
case "audio/ogg":
|
|
14519
|
+
return ".ogg";
|
|
14520
|
+
case "audio/mpeg":
|
|
14521
|
+
return ".mp3";
|
|
14522
|
+
case "audio/mp4":
|
|
14523
|
+
return ".m4a";
|
|
14524
|
+
// Video
|
|
14525
|
+
case "video/mp4":
|
|
14526
|
+
return ".mp4";
|
|
14527
|
+
case "video/quicktime":
|
|
14528
|
+
return ".mov";
|
|
14529
|
+
default:
|
|
14530
|
+
return ".bin";
|
|
14531
|
+
}
|
|
14532
|
+
}
|
|
14533
|
+
function isPathInside(target, root) {
|
|
14534
|
+
const normalizedRoot = resolve(root) + sep;
|
|
14535
|
+
const normalizedTarget = resolve(target);
|
|
14536
|
+
return normalizedTarget === resolve(root) || normalizedTarget.startsWith(normalizedRoot);
|
|
14537
|
+
}
|
|
14538
|
+
function redactAugmentedPaths(msg) {
|
|
14539
|
+
const homePrefix = homedir().replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
|
|
14540
|
+
return msg.replaceAll(
|
|
14541
|
+
new RegExp(`${homePrefix}[\\\\/]\\.augmented(?:[\\\\/][^\\s'"\`]*)*`, "g"),
|
|
14542
|
+
"<augmented-path>"
|
|
14543
|
+
);
|
|
14544
|
+
}
|
|
14545
|
+
|
|
14546
|
+
// src/pane-tail.ts
|
|
14547
|
+
var execFileAsync = promisify(execFile);
|
|
14548
|
+
function evaluateDebugGate(opts) {
|
|
14549
|
+
if (!opts.channelId || !opts.channelId.startsWith("D")) {
|
|
14550
|
+
return { ok: false, reason: "not-dm" };
|
|
14551
|
+
}
|
|
14552
|
+
if (opts.allowedUsers.size === 0) {
|
|
14553
|
+
return { ok: false, reason: "allowlist-empty" };
|
|
14554
|
+
}
|
|
14555
|
+
if (!opts.userId || !opts.allowedUsers.has(opts.userId)) {
|
|
14556
|
+
return { ok: false, reason: "not-allowlisted" };
|
|
14557
|
+
}
|
|
14558
|
+
return { ok: true };
|
|
14559
|
+
}
|
|
14560
|
+
var PANE_LOG_TAIL_BYTES = 64 * 1024;
|
|
14561
|
+
var TMUX_CAPTURE_TIMEOUT_MS = 2e3;
|
|
14562
|
+
async function capturePaneSnapshot(opts) {
|
|
14563
|
+
const scrollback = opts.scrollbackLines ?? 200;
|
|
14564
|
+
try {
|
|
14565
|
+
const { stdout } = await execFileAsync(
|
|
14566
|
+
"tmux",
|
|
14567
|
+
["capture-pane", "-p", "-t", agentTmuxSessionName(opts.codeName), "-S", `-${scrollback}`],
|
|
14568
|
+
{ timeout: TMUX_CAPTURE_TIMEOUT_MS, maxBuffer: 4 * 1024 * 1024 }
|
|
14569
|
+
);
|
|
14570
|
+
return { source: "tmux", text: stdout };
|
|
14571
|
+
} catch {
|
|
14572
|
+
}
|
|
14573
|
+
if (!opts.agentDir) return null;
|
|
14574
|
+
const paneLogPath = `${opts.agentDir}/pane.log`;
|
|
14575
|
+
try {
|
|
14576
|
+
const tail = await readFileTail(paneLogPath, PANE_LOG_TAIL_BYTES);
|
|
14577
|
+
if (tail === null) return null;
|
|
14578
|
+
return { source: "pane-log", text: stripAnsi(tail) };
|
|
14579
|
+
} catch {
|
|
14580
|
+
return null;
|
|
14581
|
+
}
|
|
14582
|
+
}
|
|
14583
|
+
async function readFileTail(path, maxBytes) {
|
|
14584
|
+
const st = await stat(path);
|
|
14585
|
+
if (!st.isFile()) return null;
|
|
14586
|
+
const start = Math.max(0, st.size - maxBytes);
|
|
14587
|
+
const length = st.size - start;
|
|
14588
|
+
if (length === 0) return "";
|
|
14589
|
+
const handle = await open(path, "r");
|
|
14590
|
+
try {
|
|
14591
|
+
const buf = Buffer.alloc(length);
|
|
14592
|
+
await handle.read(buf, 0, length, start);
|
|
14593
|
+
let text = buf.toString("utf8");
|
|
14594
|
+
if (start > 0) {
|
|
14595
|
+
const nl = text.indexOf("\n");
|
|
14596
|
+
if (nl !== -1) text = text.slice(nl + 1);
|
|
14597
|
+
}
|
|
14598
|
+
return text;
|
|
14599
|
+
} finally {
|
|
14600
|
+
await handle.close();
|
|
14601
|
+
}
|
|
14602
|
+
}
|
|
14603
|
+
function stripAnsi(text) {
|
|
14604
|
+
return text.replace(/\x1b\][^\x07\x1b]*(?:\x07|\x1b\\)/g, "").replace(/\x1b\[[0-9;?<=>!]*[ -\/]*[@-~]/g, "").replace(/\x1b./g, "").replace(/[\x00-\x08\x0b-\x1f\x7f]/g, "");
|
|
14605
|
+
}
|
|
14606
|
+
var SECRET_PATTERNS = [
|
|
14607
|
+
// Augmented host API keys
|
|
14608
|
+
{ re: /tlk_[A-Za-z0-9]{8,}/g, label: "agt-api-key" },
|
|
14609
|
+
// Slack tokens: bot/app/user/refresh/config
|
|
14610
|
+
{ re: /x(?:ox[abprs]|app)-[A-Za-z0-9-]{8,}/g, label: "slack-token" },
|
|
14611
|
+
// JWTs (three base64url segments, first one always starts with eyJ)
|
|
14612
|
+
{ re: /eyJ[A-Za-z0-9_-]{8,}\.[A-Za-z0-9_-]{8,}\.[A-Za-z0-9_-]{8,}/g, label: "jwt" },
|
|
14613
|
+
// OpenAI / Anthropic-style keys (sk-..., sk-ant-...)
|
|
14614
|
+
{ re: /sk-[A-Za-z0-9_-]{16,}/g, label: "sk-key" },
|
|
14615
|
+
// AWS access key IDs
|
|
14616
|
+
{ re: /(?:AKIA|ASIA)[0-9A-Z]{16}/g, label: "aws-key" },
|
|
14617
|
+
// GitHub tokens
|
|
14618
|
+
{ re: /gh[pousr]_[A-Za-z0-9]{20,}/g, label: "github-token" }
|
|
14619
|
+
];
|
|
14620
|
+
function redactPaneText(text) {
|
|
14621
|
+
let out = redactAugmentedPaths(text);
|
|
14622
|
+
for (const { re, label } of SECRET_PATTERNS) {
|
|
14623
|
+
out = out.replace(re, `<redacted:${label}>`);
|
|
14624
|
+
}
|
|
14625
|
+
return out;
|
|
14626
|
+
}
|
|
14627
|
+
var DEFAULT_MAX_CHARS = 3900;
|
|
14628
|
+
var REFLOW_COLS = 80;
|
|
14629
|
+
function formatPaneSnapshot(opts) {
|
|
14630
|
+
const maxChars = opts.maxChars ?? DEFAULT_MAX_CHARS;
|
|
14631
|
+
const header = opts.status.kind === "live" ? `\u{1F50E} Live pane tail for \`${opts.codeName}\` \u2014 captured ${opts.capturedAtLabel} \xB7 updates ~3s \xB7 expires in ${formatCountdown(opts.status.secondsRemaining)}` : `\u{1F50E} Pane tail for \`${opts.codeName}\` \u2014 captured ${opts.capturedAtLabel} \xB7 *expired* \u2014 run \`/debug\` to restart`;
|
|
14632
|
+
const fenceOverhead = header.length + "\n```\n".length + "\n```".length + 16;
|
|
14633
|
+
const contentBudget = Math.max(0, maxChars - fenceOverhead);
|
|
14634
|
+
const lines = reflowLines(sanitizeForCodeBlock(opts.text), REFLOW_COLS);
|
|
14635
|
+
const kept = [];
|
|
14636
|
+
let used = 0;
|
|
14637
|
+
for (let i = lines.length - 1; i >= 0; i--) {
|
|
14638
|
+
const cost = lines[i].length + 1;
|
|
14639
|
+
if (used + cost > contentBudget) break;
|
|
14640
|
+
kept.unshift(lines[i]);
|
|
14641
|
+
used += cost;
|
|
14642
|
+
}
|
|
14643
|
+
const truncated = kept.length < lines.length;
|
|
14644
|
+
const body = kept.join("\n").trimEnd();
|
|
14645
|
+
const block = body.length > 0 ? `\`\`\`
|
|
14646
|
+
${truncated ? "\u2026\n" : ""}${body}
|
|
14647
|
+
\`\`\`` : "_(pane is empty)_";
|
|
14648
|
+
return `${header}
|
|
14649
|
+
${block}`;
|
|
14650
|
+
}
|
|
14651
|
+
function formatCountdown(totalSeconds) {
|
|
14652
|
+
const s = Math.max(0, Math.floor(totalSeconds));
|
|
14653
|
+
return `${Math.floor(s / 60)}:${String(s % 60).padStart(2, "0")}`;
|
|
14654
|
+
}
|
|
14655
|
+
function sanitizeForCodeBlock(text) {
|
|
14656
|
+
return text.replace(/```/g, "`\u200B`\u200B`");
|
|
14657
|
+
}
|
|
14658
|
+
function reflowLines(text, cols) {
|
|
14659
|
+
const out = [];
|
|
14660
|
+
for (const rawLine of text.split("\n")) {
|
|
14661
|
+
const line = rawLine.replace(/\s+$/, "");
|
|
14662
|
+
if (line.length <= cols) {
|
|
14663
|
+
out.push(line);
|
|
14664
|
+
continue;
|
|
14665
|
+
}
|
|
14666
|
+
for (let i = 0; i < line.length; i += cols) {
|
|
14667
|
+
out.push(line.slice(i, i + cols));
|
|
14668
|
+
}
|
|
14669
|
+
}
|
|
14670
|
+
const collapsed = [];
|
|
14671
|
+
for (const line of out) {
|
|
14672
|
+
if (line === "" && collapsed[collapsed.length - 1] === "") continue;
|
|
14673
|
+
collapsed.push(line);
|
|
14674
|
+
}
|
|
14675
|
+
return collapsed;
|
|
14676
|
+
}
|
|
14677
|
+
|
|
14452
14678
|
// src/slack-loop-throttle.ts
|
|
14453
14679
|
var DEFAULT_THROTTLE = {
|
|
14454
14680
|
threshold: 3,
|
|
@@ -14658,7 +14884,7 @@ var SLACK_EGRESS_TOOLS = /* @__PURE__ */ new Set([
|
|
|
14658
14884
|
|
|
14659
14885
|
// src/slack-pending-inbound-cleanup.ts
|
|
14660
14886
|
import { existsSync, readdirSync as readdirSync2, statSync, unlinkSync } from "fs";
|
|
14661
|
-
import { join as
|
|
14887
|
+
import { join as join3 } from "path";
|
|
14662
14888
|
function sanitizeMarkerSegment(value) {
|
|
14663
14889
|
return value.replace(/[^A-Za-z0-9_-]/g, "_");
|
|
14664
14890
|
}
|
|
@@ -14675,7 +14901,7 @@ function clearAllSlackPendingMarkersForThread(dir, channel, threadTs, clear = de
|
|
|
14675
14901
|
try {
|
|
14676
14902
|
for (const f of readdirSync2(dir)) {
|
|
14677
14903
|
if (!f.startsWith(prefix) || !f.endsWith(".json")) continue;
|
|
14678
|
-
clear(
|
|
14904
|
+
clear(join3(dir, f));
|
|
14679
14905
|
cleared += 1;
|
|
14680
14906
|
}
|
|
14681
14907
|
} catch {
|
|
@@ -14690,7 +14916,7 @@ function clearSlackPendingMarkerByMessageTs(dir, channel, messageTs, clear = def
|
|
|
14690
14916
|
try {
|
|
14691
14917
|
for (const f of readdirSync2(dir)) {
|
|
14692
14918
|
if (!f.startsWith(channelPrefix) || !f.endsWith(messageSuffix)) continue;
|
|
14693
|
-
clear(
|
|
14919
|
+
clear(join3(dir, f));
|
|
14694
14920
|
cleared += 1;
|
|
14695
14921
|
}
|
|
14696
14922
|
} catch {
|
|
@@ -14702,7 +14928,7 @@ function clearOldestSlackPendingMarkerInChannel(dir, channel, clear = defaultCle
|
|
|
14702
14928
|
const channelPrefix = `${sanitizeMarkerSegment(channel)}__`;
|
|
14703
14929
|
try {
|
|
14704
14930
|
const entries = readdirSync2(dir).filter((f) => f.startsWith(channelPrefix) && f.endsWith(".json")).map((f) => {
|
|
14705
|
-
const full =
|
|
14931
|
+
const full = join3(dir, f);
|
|
14706
14932
|
let mtime = 0;
|
|
14707
14933
|
try {
|
|
14708
14934
|
mtime = statSync(full).mtimeMs;
|
|
@@ -14954,88 +15180,6 @@ async function runOrRetry(fn, opts) {
|
|
|
14954
15180
|
}
|
|
14955
15181
|
}
|
|
14956
15182
|
|
|
14957
|
-
// src/channel-attachments.ts
|
|
14958
|
-
import { homedir } from "os";
|
|
14959
|
-
import { join as join3, resolve, sep } from "path";
|
|
14960
|
-
function resolveChannelInboundDir(codeName, channelSlug) {
|
|
14961
|
-
const base = join3(homedir(), ".augmented");
|
|
14962
|
-
const allowedSegment = /^[A-Za-z0-9_-]+$/;
|
|
14963
|
-
if (!allowedSegment.test(codeName) || !allowedSegment.test(channelSlug)) {
|
|
14964
|
-
throw new Error(
|
|
14965
|
-
`Refusing to resolve inbound dir \u2014 invalid codeName/channelSlug (got ${JSON.stringify({ codeName, channelSlug })})`
|
|
14966
|
-
);
|
|
14967
|
-
}
|
|
14968
|
-
const candidate = resolve(base, codeName, channelSlug);
|
|
14969
|
-
if (!isPathInside(candidate, base)) {
|
|
14970
|
-
throw new Error(`Refusing inbound dir outside ${base} (got ${candidate})`);
|
|
14971
|
-
}
|
|
14972
|
-
return candidate;
|
|
14973
|
-
}
|
|
14974
|
-
function classifyMimetype(mimetype) {
|
|
14975
|
-
if (typeof mimetype === "string" && mimetype.startsWith("image/")) return "image";
|
|
14976
|
-
return "attachment";
|
|
14977
|
-
}
|
|
14978
|
-
function buildSafeInboundPath(root, fileId, mimetype) {
|
|
14979
|
-
const safeId = fileId.replace(/[^A-Za-z0-9_-]/g, "");
|
|
14980
|
-
if (!safeId) throw new Error("Refusing to build inbound path for empty/invalid file id");
|
|
14981
|
-
const ext = extensionForMimetype(mimetype);
|
|
14982
|
-
const candidate = resolve(root, `${safeId}${ext}`);
|
|
14983
|
-
if (!isPathInside(candidate, root)) {
|
|
14984
|
-
throw new Error(`Refusing to build inbound path outside agent dir (got ${candidate})`);
|
|
14985
|
-
}
|
|
14986
|
-
return candidate;
|
|
14987
|
-
}
|
|
14988
|
-
function extensionForMimetype(mimetype) {
|
|
14989
|
-
if (!mimetype) return ".bin";
|
|
14990
|
-
switch (mimetype) {
|
|
14991
|
-
// Images
|
|
14992
|
-
case "image/png":
|
|
14993
|
-
return ".png";
|
|
14994
|
-
case "image/jpeg":
|
|
14995
|
-
case "image/jpg":
|
|
14996
|
-
return ".jpg";
|
|
14997
|
-
case "image/gif":
|
|
14998
|
-
return ".gif";
|
|
14999
|
-
case "image/webp":
|
|
15000
|
-
return ".webp";
|
|
15001
|
-
case "image/svg+xml":
|
|
15002
|
-
return ".svg";
|
|
15003
|
-
// Docs
|
|
15004
|
-
case "application/pdf":
|
|
15005
|
-
return ".pdf";
|
|
15006
|
-
case "text/plain":
|
|
15007
|
-
return ".txt";
|
|
15008
|
-
case "text/csv":
|
|
15009
|
-
return ".csv";
|
|
15010
|
-
case "application/json":
|
|
15011
|
-
return ".json";
|
|
15012
|
-
case "application/vnd.openxmlformats-officedocument.wordprocessingml.document":
|
|
15013
|
-
return ".docx";
|
|
15014
|
-
case "application/vnd.openxmlformats-officedocument.spreadsheetml.sheet":
|
|
15015
|
-
return ".xlsx";
|
|
15016
|
-
// Audio (Telegram voice notes are typically audio/ogg; regular
|
|
15017
|
-
// audio messages are audio/mpeg)
|
|
15018
|
-
case "audio/ogg":
|
|
15019
|
-
return ".ogg";
|
|
15020
|
-
case "audio/mpeg":
|
|
15021
|
-
return ".mp3";
|
|
15022
|
-
case "audio/mp4":
|
|
15023
|
-
return ".m4a";
|
|
15024
|
-
// Video
|
|
15025
|
-
case "video/mp4":
|
|
15026
|
-
return ".mp4";
|
|
15027
|
-
case "video/quicktime":
|
|
15028
|
-
return ".mov";
|
|
15029
|
-
default:
|
|
15030
|
-
return ".bin";
|
|
15031
|
-
}
|
|
15032
|
-
}
|
|
15033
|
-
function isPathInside(target, root) {
|
|
15034
|
-
const normalizedRoot = resolve(root) + sep;
|
|
15035
|
-
const normalizedTarget = resolve(target);
|
|
15036
|
-
return normalizedTarget === resolve(root) || normalizedTarget.startsWith(normalizedRoot);
|
|
15037
|
-
}
|
|
15038
|
-
|
|
15039
15183
|
// src/slack-inbound-files.ts
|
|
15040
15184
|
function classifySlackFile(file) {
|
|
15041
15185
|
if (!file || typeof file.id !== "string" || !file.id) return null;
|
|
@@ -16346,7 +16490,8 @@ function buildSlackHelpMessage(codeName) {
|
|
|
16346
16490
|
"\u2022 `/restart` \u2014 restart this agent",
|
|
16347
16491
|
"\u2022 `/agent-status` \u2014 report whether this agent is online + last activity",
|
|
16348
16492
|
"\u2022 `/kill` \u2014 silence all agents in this thread for 6h (use as a thread reply)",
|
|
16349
|
-
"\u2022 `/unkill` \u2014 clear a kill (use as a thread reply)"
|
|
16493
|
+
"\u2022 `/unkill` \u2014 clear a kill (use as a thread reply)",
|
|
16494
|
+
"\u2022 `/debug` \u2014 live tail of this agent's terminal pane (DM only, allowlisted users; works while the channel process is alive \u2014 a wedged host still needs SSM diagnostics)"
|
|
16350
16495
|
].join("\n");
|
|
16351
16496
|
}
|
|
16352
16497
|
var lastActivityAt = null;
|
|
@@ -16435,6 +16580,193 @@ async function postEphemeralViaResponseUrl(responseUrl, text, logTag) {
|
|
|
16435
16580
|
);
|
|
16436
16581
|
}
|
|
16437
16582
|
}
|
|
16583
|
+
var DEBUG_TAIL_WINDOW_MS = 12e4;
|
|
16584
|
+
var DEBUG_TAIL_INTERVAL_MS = 3e3;
|
|
16585
|
+
var DEBUG_TAIL_MAX_CONSECUTIVE_FAILURES = 4;
|
|
16586
|
+
var activeDebugTailExpiresAtMs = null;
|
|
16587
|
+
function debugAuditLog(line) {
|
|
16588
|
+
process.stderr.write(`slack-channel(${AGENT_CODE_NAME ?? "unknown"}): /debug ${line}
|
|
16589
|
+
`);
|
|
16590
|
+
}
|
|
16591
|
+
function debugSleep(ms) {
|
|
16592
|
+
return new Promise((resolve3) => setTimeout(resolve3, ms));
|
|
16593
|
+
}
|
|
16594
|
+
function nowUtcLabel() {
|
|
16595
|
+
return `${(/* @__PURE__ */ new Date()).toISOString().slice(11, 19)} UTC`;
|
|
16596
|
+
}
|
|
16597
|
+
async function updateSlackMessage(channel, ts, text) {
|
|
16598
|
+
try {
|
|
16599
|
+
const res = await fetch("https://slack.com/api/chat.update", {
|
|
16600
|
+
method: "POST",
|
|
16601
|
+
headers: {
|
|
16602
|
+
"Content-Type": "application/json; charset=utf-8",
|
|
16603
|
+
Authorization: `Bearer ${BOT_TOKEN}`
|
|
16604
|
+
},
|
|
16605
|
+
body: JSON.stringify({ channel, ts, text }),
|
|
16606
|
+
signal: AbortSignal.timeout(SLACK_DOWNLOAD_TIMEOUT_MS)
|
|
16607
|
+
});
|
|
16608
|
+
const retryAfterHeader = res.headers.get("retry-after");
|
|
16609
|
+
const retryAfterSec = retryAfterHeader ? parseInt(retryAfterHeader, 10) : NaN;
|
|
16610
|
+
const retryAfterMs = Number.isFinite(retryAfterSec) ? Math.max(0, retryAfterSec * 1e3) : null;
|
|
16611
|
+
const data = await res.json().catch(() => ({ ok: false, error: `http-${res.status}` }));
|
|
16612
|
+
return { ok: data.ok === true, error: data.error, retryAfterMs };
|
|
16613
|
+
} catch (err) {
|
|
16614
|
+
return { ok: false, error: err.message, retryAfterMs: null };
|
|
16615
|
+
}
|
|
16616
|
+
}
|
|
16617
|
+
async function runDebugTailLoop(opts) {
|
|
16618
|
+
const expiresAtMs = opts.expiresAtMs;
|
|
16619
|
+
let last = opts.initialFrame;
|
|
16620
|
+
let consecutiveFailures = 0;
|
|
16621
|
+
let delayMs = DEBUG_TAIL_INTERVAL_MS;
|
|
16622
|
+
try {
|
|
16623
|
+
while (Date.now() + delayMs < expiresAtMs) {
|
|
16624
|
+
await debugSleep(delayMs);
|
|
16625
|
+
delayMs = DEBUG_TAIL_INTERVAL_MS;
|
|
16626
|
+
const snapshot = await capturePaneSnapshot({
|
|
16627
|
+
codeName: opts.codeName,
|
|
16628
|
+
agentDir: SLACK_AGENT_DIR
|
|
16629
|
+
});
|
|
16630
|
+
if (!snapshot) continue;
|
|
16631
|
+
const body = redactPaneText(snapshot.text);
|
|
16632
|
+
if (body === last.body) continue;
|
|
16633
|
+
const frame = { body, source: snapshot.source, capturedAtLabel: nowUtcLabel() };
|
|
16634
|
+
const secondsRemaining = Math.max(0, Math.round((expiresAtMs - Date.now()) / 1e3));
|
|
16635
|
+
const result = await updateSlackMessage(
|
|
16636
|
+
opts.channel,
|
|
16637
|
+
opts.ts,
|
|
16638
|
+
formatPaneSnapshot({
|
|
16639
|
+
codeName: opts.codeName,
|
|
16640
|
+
text: frame.body,
|
|
16641
|
+
source: frame.source,
|
|
16642
|
+
capturedAtLabel: frame.capturedAtLabel,
|
|
16643
|
+
status: { kind: "live", secondsRemaining }
|
|
16644
|
+
})
|
|
16645
|
+
);
|
|
16646
|
+
if (result.ok) {
|
|
16647
|
+
last = frame;
|
|
16648
|
+
consecutiveFailures = 0;
|
|
16649
|
+
continue;
|
|
16650
|
+
}
|
|
16651
|
+
if (result.retryAfterMs !== null || result.error === "ratelimited") {
|
|
16652
|
+
delayMs = Math.max(result.retryAfterMs ?? 0, DEBUG_TAIL_INTERVAL_MS * 2);
|
|
16653
|
+
continue;
|
|
16654
|
+
}
|
|
16655
|
+
consecutiveFailures++;
|
|
16656
|
+
if (consecutiveFailures >= DEBUG_TAIL_MAX_CONSECUTIVE_FAILURES) {
|
|
16657
|
+
debugAuditLog(
|
|
16658
|
+
`tail aborted after ${consecutiveFailures} consecutive update failures (last: ${result.error ?? "unknown"})`
|
|
16659
|
+
);
|
|
16660
|
+
break;
|
|
16661
|
+
}
|
|
16662
|
+
}
|
|
16663
|
+
} finally {
|
|
16664
|
+
activeDebugTailExpiresAtMs = null;
|
|
16665
|
+
await updateSlackMessage(
|
|
16666
|
+
opts.channel,
|
|
16667
|
+
opts.ts,
|
|
16668
|
+
formatPaneSnapshot({
|
|
16669
|
+
codeName: opts.codeName,
|
|
16670
|
+
text: last.body,
|
|
16671
|
+
source: last.source,
|
|
16672
|
+
capturedAtLabel: last.capturedAtLabel,
|
|
16673
|
+
status: { kind: "expired" }
|
|
16674
|
+
})
|
|
16675
|
+
);
|
|
16676
|
+
}
|
|
16677
|
+
}
|
|
16678
|
+
async function handleDebugSlashCommand(payload, responseUrl) {
|
|
16679
|
+
const codeName = AGENT_CODE_NAME ?? "unknown";
|
|
16680
|
+
const verdict = evaluateDebugGate({
|
|
16681
|
+
channelId: payload.channel_id,
|
|
16682
|
+
userId: payload.user_id,
|
|
16683
|
+
allowedUsers: ALLOWED_USERS
|
|
16684
|
+
});
|
|
16685
|
+
if (!verdict.ok) {
|
|
16686
|
+
debugAuditLog(`denied reason=${verdict.reason} user=${payload.user_id ?? "unknown"}`);
|
|
16687
|
+
const denialText = verdict.reason === "not-dm" ? `:warning: \`/debug\` only works in a direct message with \`${codeName}\` \u2014 it shows the agent's raw terminal, so it stays 1:1. Open a DM with the bot and run \`/debug\` there.` : verdict.reason === "allowlist-empty" ? `\u{1F6AB} \`/debug\` is disabled for \`${codeName}\` \u2014 no diagnostic allowlist is configured. Because it exposes the agent's raw terminal, \`/debug\` requires a non-empty \`SLACK_ALLOWED_USERS\` on the host (unlike \`/restart\`, it does not open up when the allowlist is unset).` : `\u{1F6AB} \`/debug\` denied \u2014 your Slack user isn't on the diagnostic allowlist for \`${codeName}\`. Ask whoever operates this host to add you to \`SLACK_ALLOWED_USERS\`.`;
|
|
16688
|
+
await postEphemeralViaResponseUrl(responseUrl, denialText, codeName);
|
|
16689
|
+
return;
|
|
16690
|
+
}
|
|
16691
|
+
const channelId = payload.channel_id;
|
|
16692
|
+
if (!channelId || !BOT_TOKEN) {
|
|
16693
|
+
debugAuditLog(
|
|
16694
|
+
`not-started reason=missing-${channelId ? "bot-token" : "channel"} user=${payload.user_id ?? "unknown"}`
|
|
16695
|
+
);
|
|
16696
|
+
await postEphemeralViaResponseUrl(
|
|
16697
|
+
responseUrl,
|
|
16698
|
+
`:warning: \`/debug\` can't run \u2014 this channel process has no bot token wired.`,
|
|
16699
|
+
codeName
|
|
16700
|
+
);
|
|
16701
|
+
return;
|
|
16702
|
+
}
|
|
16703
|
+
if (activeDebugTailExpiresAtMs !== null && activeDebugTailExpiresAtMs > Date.now()) {
|
|
16704
|
+
const remaining = formatCountdown((activeDebugTailExpiresAtMs - Date.now()) / 1e3);
|
|
16705
|
+
debugAuditLog(
|
|
16706
|
+
`not-started reason=tail-already-running user=${payload.user_id ?? "unknown"} remaining=${remaining}`
|
|
16707
|
+
);
|
|
16708
|
+
await postEphemeralViaResponseUrl(
|
|
16709
|
+
responseUrl,
|
|
16710
|
+
`:hourglass: A \`/debug\` tail is already running for \`${codeName}\` (one at a time) \u2014 it expires in ${remaining}.`,
|
|
16711
|
+
codeName
|
|
16712
|
+
);
|
|
16713
|
+
return;
|
|
16714
|
+
}
|
|
16715
|
+
const reservedExpiresAtMs = Date.now() + DEBUG_TAIL_WINDOW_MS;
|
|
16716
|
+
activeDebugTailExpiresAtMs = reservedExpiresAtMs;
|
|
16717
|
+
const snapshot = await capturePaneSnapshot({ codeName, agentDir: SLACK_AGENT_DIR });
|
|
16718
|
+
if (!snapshot) {
|
|
16719
|
+
activeDebugTailExpiresAtMs = null;
|
|
16720
|
+
debugAuditLog(`not-started reason=no-pane-output user=${payload.user_id ?? "unknown"}`);
|
|
16721
|
+
await postEphemeralViaResponseUrl(
|
|
16722
|
+
responseUrl,
|
|
16723
|
+
`:warning: No pane output available for \`${codeName}\` yet \u2014 the agent session may not have started. (If the whole host is wedged, \`/debug\` can't reach it either \u2014 fall back to the SSM diagnostics runbook.)`,
|
|
16724
|
+
codeName
|
|
16725
|
+
);
|
|
16726
|
+
return;
|
|
16727
|
+
}
|
|
16728
|
+
debugAuditLog(`granted user=${payload.user_id ?? "unknown"} \u2014 starting live tail (source=${snapshot.source})`);
|
|
16729
|
+
const initialFrame = {
|
|
16730
|
+
body: redactPaneText(snapshot.text),
|
|
16731
|
+
source: snapshot.source,
|
|
16732
|
+
capturedAtLabel: nowUtcLabel()
|
|
16733
|
+
};
|
|
16734
|
+
const posted = await postSlackMessageWithTs({
|
|
16735
|
+
channel: channelId,
|
|
16736
|
+
text: formatPaneSnapshot({
|
|
16737
|
+
codeName,
|
|
16738
|
+
text: initialFrame.body,
|
|
16739
|
+
source: initialFrame.source,
|
|
16740
|
+
capturedAtLabel: initialFrame.capturedAtLabel,
|
|
16741
|
+
status: {
|
|
16742
|
+
kind: "live",
|
|
16743
|
+
secondsRemaining: Math.max(0, Math.round((reservedExpiresAtMs - Date.now()) / 1e3))
|
|
16744
|
+
}
|
|
16745
|
+
})
|
|
16746
|
+
});
|
|
16747
|
+
if (!posted.ok || !posted.ts) {
|
|
16748
|
+
activeDebugTailExpiresAtMs = null;
|
|
16749
|
+
debugAuditLog(
|
|
16750
|
+
`not-started reason=post-failed error=${posted.error ?? "unknown"} user=${payload.user_id ?? "unknown"}`
|
|
16751
|
+
);
|
|
16752
|
+
await postEphemeralViaResponseUrl(
|
|
16753
|
+
responseUrl,
|
|
16754
|
+
`:x: Failed to post the pane snapshot${posted.error ? ` (${posted.error})` : ""}. Try again in a moment.`,
|
|
16755
|
+
codeName
|
|
16756
|
+
);
|
|
16757
|
+
return;
|
|
16758
|
+
}
|
|
16759
|
+
void runDebugTailLoop({
|
|
16760
|
+
channel: channelId,
|
|
16761
|
+
ts: posted.ts,
|
|
16762
|
+
codeName,
|
|
16763
|
+
initialFrame,
|
|
16764
|
+
expiresAtMs: reservedExpiresAtMs
|
|
16765
|
+
}).catch((err) => {
|
|
16766
|
+
activeDebugTailExpiresAtMs = null;
|
|
16767
|
+
debugAuditLog(`tail loop crashed: ${redactAugmentedPaths2(err.message)}`);
|
|
16768
|
+
});
|
|
16769
|
+
}
|
|
16438
16770
|
async function handleSlashCommandEnvelope(payload) {
|
|
16439
16771
|
const command = payload.command;
|
|
16440
16772
|
const responseUrl = payload.response_url;
|
|
@@ -16497,7 +16829,7 @@ async function handleSlashCommandEnvelope(payload) {
|
|
|
16497
16829
|
);
|
|
16498
16830
|
} catch (err) {
|
|
16499
16831
|
process.stderr.write(
|
|
16500
|
-
`slack-channel(${codeName}): /restart slash-command flag write failed: ${
|
|
16832
|
+
`slack-channel(${codeName}): /restart slash-command flag write failed: ${redactAugmentedPaths2(err.message)}
|
|
16501
16833
|
`
|
|
16502
16834
|
);
|
|
16503
16835
|
await postEphemeralViaResponseUrl(
|
|
@@ -16508,6 +16840,10 @@ async function handleSlashCommandEnvelope(payload) {
|
|
|
16508
16840
|
}
|
|
16509
16841
|
return;
|
|
16510
16842
|
}
|
|
16843
|
+
if (command === "/debug") {
|
|
16844
|
+
await handleDebugSlashCommand(payload, responseUrl);
|
|
16845
|
+
return;
|
|
16846
|
+
}
|
|
16511
16847
|
if (command === "/kill" || command === "/unkill") {
|
|
16512
16848
|
if (!AGT_HOST || !AGT_API_KEY || !AGT_AGENT_ID) {
|
|
16513
16849
|
await postEphemeralViaResponseUrl(
|
|
@@ -16611,7 +16947,7 @@ async function handleRestartCommand(opts) {
|
|
|
16611
16947
|
}
|
|
16612
16948
|
} catch (err) {
|
|
16613
16949
|
process.stderr.write(
|
|
16614
|
-
`slack-channel(${codeName}): /restart flag write failed: ${
|
|
16950
|
+
`slack-channel(${codeName}): /restart flag write failed: ${redactAugmentedPaths2(err.message)}
|
|
16615
16951
|
`
|
|
16616
16952
|
);
|
|
16617
16953
|
await postSlackMessage({
|
|
@@ -17253,14 +17589,14 @@ ${result.formatted}` : "Thread is empty or not found."
|
|
|
17253
17589
|
let bytes;
|
|
17254
17590
|
let size;
|
|
17255
17591
|
try {
|
|
17256
|
-
const
|
|
17257
|
-
if (!
|
|
17592
|
+
const stat2 = statSync2(resolvedPath);
|
|
17593
|
+
if (!stat2.isFile()) {
|
|
17258
17594
|
return {
|
|
17259
17595
|
content: [{ type: "text", text: `Upload refused: ${resolvedPath} is not a regular file.` }],
|
|
17260
17596
|
isError: true
|
|
17261
17597
|
};
|
|
17262
17598
|
}
|
|
17263
|
-
size =
|
|
17599
|
+
size = stat2.size;
|
|
17264
17600
|
bytes = readFileSync4(resolvedPath);
|
|
17265
17601
|
} catch (err) {
|
|
17266
17602
|
return {
|
|
@@ -17371,7 +17707,7 @@ ${result.formatted}` : "Thread is empty or not found."
|
|
|
17371
17707
|
return {
|
|
17372
17708
|
content: [{
|
|
17373
17709
|
type: "text",
|
|
17374
|
-
text: `Failed to download attachment: ${
|
|
17710
|
+
text: `Failed to download attachment: ${redactAugmentedPaths2(err.message)}`
|
|
17375
17711
|
}],
|
|
17376
17712
|
isError: true
|
|
17377
17713
|
};
|
|
@@ -17928,7 +18264,7 @@ function isDownloadableFileId(fileId, channel) {
|
|
|
17928
18264
|
}
|
|
17929
18265
|
return entry.channel === channel;
|
|
17930
18266
|
}
|
|
17931
|
-
function
|
|
18267
|
+
function redactAugmentedPaths2(msg) {
|
|
17932
18268
|
return msg.replaceAll(
|
|
17933
18269
|
new RegExp(`${homedir2().replace(/[.*+?^${}()|[\\]\\\\]/g, "\\\\$&")}/\\.augmented/[^\\s'"\`]*`, "g"),
|
|
17934
18270
|
"<augmented-path>"
|
|
@@ -18010,7 +18346,7 @@ async function buildInboundFileMeta(rawFiles, codeName, channel) {
|
|
|
18010
18346
|
});
|
|
18011
18347
|
} catch (err) {
|
|
18012
18348
|
process.stderr.write(
|
|
18013
|
-
`slack-channel: image auto-download failed for ${classified.id}: ${
|
|
18349
|
+
`slack-channel: image auto-download failed for ${classified.id}: ${redactAugmentedPaths2(err.message)}
|
|
18014
18350
|
`
|
|
18015
18351
|
);
|
|
18016
18352
|
registerDownloadableFileId(classified.id, channel);
|
|
@@ -21,8 +21,8 @@ import {
|
|
|
21
21
|
stopPersistentSession,
|
|
22
22
|
takeZombieDetection,
|
|
23
23
|
writePersistentClaudeWrapper
|
|
24
|
-
} from "./chunk-
|
|
25
|
-
import "./chunk-
|
|
24
|
+
} from "./chunk-S2QLE5BQ.js";
|
|
25
|
+
import "./chunk-TXE2LLKI.js";
|
|
26
26
|
import "./chunk-XWVM4KPK.js";
|
|
27
27
|
export {
|
|
28
28
|
SEND_KEYS_ENTER_DELAY_MS,
|
|
@@ -48,4 +48,4 @@ export {
|
|
|
48
48
|
takeZombieDetection,
|
|
49
49
|
writePersistentClaudeWrapper
|
|
50
50
|
};
|
|
51
|
-
//# sourceMappingURL=persistent-session-
|
|
51
|
+
//# sourceMappingURL=persistent-session-BTXWOKJ6.js.map
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import {
|
|
2
2
|
paneLogPath
|
|
3
|
-
} from "./chunk-
|
|
4
|
-
import "./chunk-
|
|
3
|
+
} from "./chunk-S2QLE5BQ.js";
|
|
4
|
+
import "./chunk-TXE2LLKI.js";
|
|
5
5
|
import "./chunk-XWVM4KPK.js";
|
|
6
6
|
|
|
7
7
|
// src/lib/responsiveness-probe.ts
|
|
@@ -70,4 +70,4 @@ export {
|
|
|
70
70
|
collectResponsivenessProbes,
|
|
71
71
|
getResponsivenessIntervalMs
|
|
72
72
|
};
|
|
73
|
-
//# sourceMappingURL=responsiveness-probe-
|
|
73
|
+
//# sourceMappingURL=responsiveness-probe-Y6TCM6T4.js.map
|