@integrity-labs/agt-cli 0.27.14 → 0.27.16

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.
@@ -14249,6 +14249,102 @@ function decideSenderPolicyForward(evt, policy) {
14249
14249
  return { forward: true };
14250
14250
  }
14251
14251
 
14252
+ // src/ack-reaction.ts
14253
+ import { readdirSync, readFileSync } from "fs";
14254
+ import { join } from "path";
14255
+ var REPLY_WEDGED_THRESHOLD_MS = 5 * 60 * 1e3;
14256
+ var ACK_STARTUP_GRACE_MS = 6e4;
14257
+ function decideAckReaction(i) {
14258
+ if (!i.hasTarget) return "none";
14259
+ if (!i.integrationReady) return "undeliverable";
14260
+ if (i.tmux === "dead") return "undeliverable";
14261
+ if (!i.withinStartupGrace && i.claude === "dead") return "undeliverable";
14262
+ const threshold = i.pendingStaleThresholdMs ?? REPLY_WEDGED_THRESHOLD_MS;
14263
+ if (i.oldestPendingAgeMs != null && i.oldestPendingAgeMs > threshold) {
14264
+ return "undeliverable";
14265
+ }
14266
+ return "ack";
14267
+ }
14268
+ function oldestPendingMarkerAgeMs(dir, now = Date.now()) {
14269
+ if (!dir) return null;
14270
+ let names;
14271
+ try {
14272
+ names = readdirSync(dir);
14273
+ } catch {
14274
+ return null;
14275
+ }
14276
+ let oldest = null;
14277
+ for (const name of names) {
14278
+ if (!name.endsWith(".json")) continue;
14279
+ let receivedAt;
14280
+ try {
14281
+ const raw = JSON.parse(readFileSync(join(dir, name), "utf-8"));
14282
+ receivedAt = raw.received_at;
14283
+ } catch {
14284
+ continue;
14285
+ }
14286
+ if (!receivedAt) continue;
14287
+ const t = Date.parse(receivedAt);
14288
+ if (Number.isNaN(t)) continue;
14289
+ const age = now - t;
14290
+ if (age < 0) continue;
14291
+ if (oldest == null || age > oldest) oldest = age;
14292
+ }
14293
+ return oldest;
14294
+ }
14295
+
14296
+ // src/session-probe-runtime.ts
14297
+ import { execFileSync } from "child_process";
14298
+ function agentTmuxSessionName(codeName) {
14299
+ return `agt-${codeName}`;
14300
+ }
14301
+ function escapePgrepRegex(value) {
14302
+ return value.replace(/[.[\]{}()*+?^$|\\]/g, "\\$&");
14303
+ }
14304
+ function probeClaudeProcessInTmux(tmuxSession) {
14305
+ const escapedSession = escapePgrepRegex(tmuxSession);
14306
+ const pattern = `(^|[[:space:]])--name ${escapedSession}([[:space:]]|$)`;
14307
+ try {
14308
+ const out = execFileSync("pgrep", ["-f", "--", pattern], {
14309
+ encoding: "utf-8",
14310
+ timeout: 3e3
14311
+ }).trim();
14312
+ return out.length > 0 ? "alive" : "dead";
14313
+ } catch (err) {
14314
+ const e = err;
14315
+ if (e?.code === "ENOENT") return "unknown";
14316
+ return e?.status === 1 ? "dead" : "unknown";
14317
+ }
14318
+ }
14319
+ function probeTmuxSession(tmuxSession) {
14320
+ try {
14321
+ execFileSync("tmux", ["has-session", "-t", tmuxSession], {
14322
+ stdio: "ignore",
14323
+ timeout: 3e3
14324
+ });
14325
+ return "alive";
14326
+ } catch (err) {
14327
+ const e = err;
14328
+ if (e?.code === "ENOENT") return "unknown";
14329
+ return "dead";
14330
+ }
14331
+ }
14332
+ function probeAgentSession(codeName) {
14333
+ const session = agentTmuxSessionName(codeName);
14334
+ const tmux = probeTmuxSession(session);
14335
+ const claude = tmux === "alive" ? probeClaudeProcessInTmux(session) : tmux;
14336
+ return { tmux, claude };
14337
+ }
14338
+ var probeCache = /* @__PURE__ */ new Map();
14339
+ var SESSION_PROBE_TTL_MS = 15e3;
14340
+ function probeAgentSessionCached(codeName, ttlMs = SESSION_PROBE_TTL_MS, now = Date.now()) {
14341
+ const cached2 = probeCache.get(codeName);
14342
+ if (cached2 && now - cached2.at < ttlMs) return cached2.value;
14343
+ const value = probeAgentSession(codeName);
14344
+ probeCache.set(codeName, { at: now, value });
14345
+ return value;
14346
+ }
14347
+
14252
14348
  // src/slack-loop-throttle.ts
14253
14349
  var DEFAULT_THROTTLE = {
14254
14350
  threshold: 3,
@@ -14351,468 +14447,6 @@ async function isThreadKilled(opts) {
14351
14447
  }
14352
14448
  }
14353
14449
 
14354
- // src/slack-progress.ts
14355
- var MODE_EMOJI = {
14356
- thinking: "\u{1F4AD}",
14357
- working: "\u{1F6E0}\uFE0F",
14358
- waiting: "\u23F3"
14359
- };
14360
- var TERMINAL_EMOJI = {
14361
- completed: "\u2705",
14362
- failed: "\u274C"
14363
- };
14364
- function formatWallClock(ms, timeZone) {
14365
- const fmt = new Intl.DateTimeFormat("en-GB", {
14366
- hour: "2-digit",
14367
- minute: "2-digit",
14368
- second: "2-digit",
14369
- hour12: false,
14370
- timeZone,
14371
- timeZoneName: "short"
14372
- });
14373
- const parts = fmt.formatToParts(new Date(ms));
14374
- const get = (t) => parts.find((p) => p.type === t)?.value ?? "";
14375
- return `${get("hour")}:${get("minute")}:${get("second")} ${get("timeZoneName")}`;
14376
- }
14377
- function renderHeaderLine(state) {
14378
- if (state.terminal) {
14379
- return `${TERMINAL_EMOJI[state.terminal.kind]} *${state.terminal.kind === "completed" ? "Done" : "Failed"}* \u2014 ${state.initialLabel}`;
14380
- }
14381
- return `${MODE_EMOJI[state.mode]} *Working on:* ${state.initialLabel}`;
14382
- }
14383
- function renderStepLine(state) {
14384
- if (state.terminal) {
14385
- return state.terminal.message ? state.terminal.message : void 0;
14386
- }
14387
- const parts = [];
14388
- if (state.stepNumber) {
14389
- if (state.stepNumber.total != null) {
14390
- parts.push(`*Step ${state.stepNumber.current} of ${state.stepNumber.total}*`);
14391
- } else {
14392
- parts.push(`*Step ${state.stepNumber.current}*`);
14393
- }
14394
- }
14395
- if (state.stepLabel) parts.push(state.stepLabel);
14396
- return parts.length > 0 ? parts.join(": ") : void 0;
14397
- }
14398
- function renderProgressBlocks(state) {
14399
- const header = renderHeaderLine(state);
14400
- const step = renderStepLine(state);
14401
- const footer = state.terminal ? `_${state.terminal.kind === "completed" ? "completed" : "failed"}: ${formatWallClock(state.lastChangeAt)}_` : `_last update: ${formatWallClock(state.lastChangeAt)}_`;
14402
- const lines = [header];
14403
- if (step) lines.push(step);
14404
- if (state.detail) lines.push(`_${state.detail}_`);
14405
- lines.push(footer);
14406
- const fallbackText = state.terminal ? `${state.terminal.kind === "completed" ? "Done" : "Failed"} \u2014 ${state.initialLabel}` : `Working on: ${state.initialLabel}`;
14407
- return {
14408
- text: fallbackText,
14409
- blocks: [
14410
- {
14411
- type: "section",
14412
- text: { type: "mrkdwn", text: lines.join("\n") }
14413
- }
14414
- ]
14415
- };
14416
- }
14417
- var SLACK_POST_URL = "https://slack.com/api/chat.postMessage";
14418
- var SLACK_UPDATE_URL = "https://slack.com/api/chat.update";
14419
- var DEFAULT_TIMEOUT_MS = 5e3;
14420
- var SlackApiError = class extends Error {
14421
- constructor(endpoint, slackError, status) {
14422
- super(`Slack ${endpoint} failed: ${slackError} (status=${status})`);
14423
- this.endpoint = endpoint;
14424
- this.slackError = slackError;
14425
- this.status = status;
14426
- this.name = "SlackApiError";
14427
- }
14428
- };
14429
- function createSlackProgressFlush(opts) {
14430
- const fetchImpl = opts.fetchImpl ?? fetch;
14431
- const timeoutMs = opts.timeoutMs ?? DEFAULT_TIMEOUT_MS;
14432
- let anchorTs = null;
14433
- async function slackCall(endpoint, body) {
14434
- const url = endpoint === "chat.postMessage" ? SLACK_POST_URL : SLACK_UPDATE_URL;
14435
- const controller = new AbortController();
14436
- const timer = setTimeout(() => controller.abort(), timeoutMs);
14437
- let response;
14438
- try {
14439
- response = await fetchImpl(url, {
14440
- method: "POST",
14441
- headers: {
14442
- "Content-Type": "application/json",
14443
- Authorization: `Bearer ${opts.botToken}`
14444
- },
14445
- body: JSON.stringify(body),
14446
- signal: controller.signal
14447
- });
14448
- } finally {
14449
- clearTimeout(timer);
14450
- }
14451
- const parsed = await response.json().catch(() => ({ ok: false, error: "invalid_json" }));
14452
- if (!parsed.ok) {
14453
- throw new SlackApiError(endpoint, parsed.error ?? "unknown", response.status);
14454
- }
14455
- return parsed;
14456
- }
14457
- return async function flush(state) {
14458
- const payload = renderProgressBlocks(state);
14459
- if (anchorTs == null) {
14460
- const body = {
14461
- channel: opts.channel,
14462
- text: payload.text,
14463
- blocks: payload.blocks
14464
- };
14465
- if (opts.threadTs) body.thread_ts = opts.threadTs;
14466
- try {
14467
- const res = await slackCall("chat.postMessage", body);
14468
- if (!res.ts) {
14469
- throw new SlackApiError(
14470
- "chat.postMessage",
14471
- "missing ts in successful response",
14472
- 200
14473
- );
14474
- }
14475
- anchorTs = res.ts;
14476
- opts.onAnchorPosted?.(res.ts);
14477
- } catch (err) {
14478
- throw err;
14479
- }
14480
- return;
14481
- }
14482
- try {
14483
- await slackCall("chat.update", {
14484
- channel: opts.channel,
14485
- ts: anchorTs,
14486
- text: payload.text,
14487
- blocks: payload.blocks
14488
- });
14489
- } catch (err) {
14490
- if (opts.onApiError && err instanceof SlackApiError) {
14491
- await opts.onApiError(err);
14492
- return;
14493
- }
14494
- throw err;
14495
- }
14496
- };
14497
- }
14498
-
14499
- // src/progress-tools.ts
14500
- import { randomUUID } from "crypto";
14501
-
14502
- // src/progress-handle.ts
14503
- async function startProgressHandle(opts) {
14504
- const now = opts.now ?? (() => Date.now());
14505
- const setTimer = opts.setTimer ?? ((cb, ms) => setTimeout(cb, ms));
14506
- const clearTimer = opts.clearTimer ?? ((id) => clearTimeout(id));
14507
- const debounceMs = opts.debounceMs ?? 1e3;
14508
- const onPostTerminalUpdate = opts.onPostTerminalUpdate ?? defaultPostTerminalWarn;
14509
- const state = {
14510
- initialLabel: opts.initialLabel,
14511
- mode: opts.initialMode ?? "working",
14512
- lastChangeAt: now()
14513
- };
14514
- let lastFlushAt = 0;
14515
- let pendingTimer = null;
14516
- let inFlight = null;
14517
- let terminal = false;
14518
- async function doFlush() {
14519
- while (inFlight) await inFlight;
14520
- if (pendingTimer != null) {
14521
- clearTimer(pendingTimer);
14522
- pendingTimer = null;
14523
- }
14524
- lastFlushAt = now();
14525
- const promise = opts.flush(snapshotState());
14526
- inFlight = promise.then(
14527
- () => {
14528
- inFlight = null;
14529
- },
14530
- (err) => {
14531
- inFlight = null;
14532
- throw err;
14533
- }
14534
- );
14535
- await inFlight;
14536
- }
14537
- function snapshotState() {
14538
- return {
14539
- initialLabel: state.initialLabel,
14540
- mode: state.mode,
14541
- stepLabel: state.stepLabel,
14542
- stepNumber: state.stepNumber ? { ...state.stepNumber } : void 0,
14543
- detail: state.detail,
14544
- lastChangeAt: state.lastChangeAt,
14545
- terminal: state.terminal ? { ...state.terminal } : void 0
14546
- };
14547
- }
14548
- function applyUpdate(next) {
14549
- if (next.mode !== void 0) state.mode = next.mode;
14550
- if (next.stepLabel !== void 0) state.stepLabel = next.stepLabel;
14551
- if (next.stepNumber !== void 0) state.stepNumber = { ...next.stepNumber };
14552
- if (next.detail !== void 0) state.detail = next.detail;
14553
- state.lastChangeAt = now();
14554
- }
14555
- async function scheduleOrFlush() {
14556
- const elapsed = now() - lastFlushAt;
14557
- if (elapsed >= debounceMs) {
14558
- await doFlush();
14559
- return;
14560
- }
14561
- if (pendingTimer != null) return;
14562
- const remaining = debounceMs - elapsed;
14563
- pendingTimer = setTimer(() => {
14564
- pendingTimer = null;
14565
- void doFlush().catch(() => {
14566
- });
14567
- }, remaining);
14568
- }
14569
- lastFlushAt = now();
14570
- await opts.flush(snapshotState());
14571
- return {
14572
- snapshot: snapshotState,
14573
- async update(next) {
14574
- if (terminal) {
14575
- const peeked = snapshotState();
14576
- Object.assign(peeked, {
14577
- mode: next.mode ?? peeked.mode,
14578
- stepLabel: next.stepLabel ?? peeked.stepLabel,
14579
- stepNumber: next.stepNumber ?? peeked.stepNumber,
14580
- detail: next.detail ?? peeked.detail
14581
- });
14582
- try {
14583
- onPostTerminalUpdate(peeked);
14584
- } catch (err) {
14585
- console.warn(
14586
- "[progress-handle] onPostTerminalUpdate threw \u2014 swallowed to keep update() non-throwing:",
14587
- err
14588
- );
14589
- }
14590
- return;
14591
- }
14592
- applyUpdate(next);
14593
- await scheduleOrFlush();
14594
- },
14595
- async complete(summary) {
14596
- if (terminal) return;
14597
- terminal = true;
14598
- state.terminal = { kind: "completed", message: summary };
14599
- state.lastChangeAt = now();
14600
- await doFlush();
14601
- },
14602
- async fail(reason) {
14603
- if (terminal) return;
14604
- terminal = true;
14605
- state.terminal = { kind: "failed", message: reason };
14606
- state.lastChangeAt = now();
14607
- await doFlush();
14608
- },
14609
- isTerminal: () => terminal
14610
- };
14611
- }
14612
- function defaultPostTerminalWarn(state) {
14613
- console.warn(
14614
- `[progress-handle] update() called after terminal transition (${state.terminal?.kind}) \u2014 ignored.`
14615
- );
14616
- }
14617
-
14618
- // src/progress-tools.ts
14619
- var ProgressToolRegistry = class {
14620
- constructor(opts) {
14621
- this.opts = opts;
14622
- this.toolNames = {
14623
- start: `${opts.prefix}_progress_start`,
14624
- update: `${opts.prefix}_progress_update`,
14625
- complete: `${opts.prefix}_progress_complete`,
14626
- fail: `${opts.prefix}_progress_fail`
14627
- };
14628
- }
14629
- handles = /* @__PURE__ */ new Map();
14630
- toolNames;
14631
- /** Tool definitions to splice into the MCP ListToolsRequest response. */
14632
- getDefinitions() {
14633
- const { surfaceDescription, startArgsSchema } = this.opts;
14634
- const debounceMs = this.opts.debounceMs ?? 1e3;
14635
- const debounceText = debounceMs === 1e3 ? "per second" : `every ${debounceMs}ms`;
14636
- return [
14637
- {
14638
- name: this.toolNames.start,
14639
- description: `Post a "still working" anchor to ${surfaceDescription} and return a progress_id. The anchor message updates in place as you call ${this.toolNames.update} \u2014 operators see one message that ticks forward instead of a flood of new ones. Always end with ${this.toolNames.complete} or ${this.toolNames.fail}; otherwise the anchor lingers as "working" until the stale-state sweep flips it. Opt-in \u2014 only call this for tasks that will take more than a few seconds.`,
14640
- inputSchema: {
14641
- type: "object",
14642
- properties: {
14643
- label: {
14644
- type: "string",
14645
- description: 'Short top-line label for the task (e.g. "diagnose vigil"). Stays constant for the lifetime of this progress anchor.'
14646
- },
14647
- initial_mode: {
14648
- type: "string",
14649
- enum: ["thinking", "working", "waiting"],
14650
- description: 'Initial mode emoji \u2014 defaults to "working".'
14651
- },
14652
- ...startArgsSchema.properties
14653
- },
14654
- required: ["label", ...startArgsSchema.required]
14655
- }
14656
- },
14657
- {
14658
- name: this.toolNames.update,
14659
- description: `Update the progress anchor in place. The state machine debounces calls to one ${surfaceDescription} API call ${debounceText}, so spamming this every step is safe \u2014 only the latest pending state will paint. Any subset of fields can be passed; omitted ones retain their previous value.`,
14660
- inputSchema: {
14661
- type: "object",
14662
- properties: {
14663
- progress_id: { type: "string", description: "The progress_id returned from start." },
14664
- step_label: { type: "string", description: 'Short label for the current step (e.g. "Querying CloudWatch logs").' },
14665
- step_current: { type: "number", description: "1-based step counter \u2014 current." },
14666
- step_total: { type: "number", description: "Total steps if known. Omit for open-ended progress." },
14667
- mode: { type: "string", enum: ["thinking", "working", "waiting"] },
14668
- detail: { type: "string", description: "Free-text detail line under the step label." }
14669
- },
14670
- required: ["progress_id"]
14671
- }
14672
- },
14673
- {
14674
- name: this.toolNames.complete,
14675
- description: `Mark the progress anchor as \u2705 completed. Flushes any pending debounced state before painting the terminal banner \u2014 the operator never sees a stale "Step N" beside the \u2705. After this, the handle is dead; further updates are no-ops.`,
14676
- inputSchema: {
14677
- type: "object",
14678
- properties: {
14679
- progress_id: { type: "string" },
14680
- summary: { type: "string", description: "Optional one-line summary of what was achieved." }
14681
- },
14682
- required: ["progress_id"]
14683
- }
14684
- },
14685
- {
14686
- name: this.toolNames.fail,
14687
- description: `Mark the progress anchor as \u274C failed. Always include a one-sentence reason \u2014 the operator can't tell *why* from emoji alone. Same flush-then-paint behaviour as complete.`,
14688
- inputSchema: {
14689
- type: "object",
14690
- properties: {
14691
- progress_id: { type: "string" },
14692
- reason: { type: "string", description: "One-sentence failure reason." }
14693
- },
14694
- required: ["progress_id", "reason"]
14695
- }
14696
- }
14697
- ];
14698
- }
14699
- /**
14700
- * Dispatch a CallToolRequest. Returns `undefined` if the tool name
14701
- * isn't one of ours (so the caller can keep walking its dispatch
14702
- * chain). Returns a tool result otherwise.
14703
- */
14704
- async handle(name, args) {
14705
- if (name === this.toolNames.start) return this.handleStart(args);
14706
- if (name === this.toolNames.update) return this.handleUpdate(args);
14707
- if (name === this.toolNames.complete) return this.handleComplete(args);
14708
- if (name === this.toolNames.fail) return this.handleFail(args);
14709
- return void 0;
14710
- }
14711
- /** Live count of in-flight handles. Exposed for tests + diagnostics. */
14712
- size() {
14713
- return this.handles.size;
14714
- }
14715
- async handleStart(args) {
14716
- const label = typeof args.label === "string" ? args.label : null;
14717
- if (!label) return errResult(`${this.toolNames.start}: 'label' must be a non-empty string`);
14718
- if ("initial_mode" in args && !isMode(args.initial_mode)) {
14719
- return errResult(
14720
- `${this.toolNames.start}: 'initial_mode' must be one of 'thinking', 'working', or 'waiting'`
14721
- );
14722
- }
14723
- for (const k of this.opts.startArgsSchema.required) {
14724
- if (typeof args[k] !== "string" || args[k].length === 0) {
14725
- return errResult(`${this.toolNames.start}: '${k}' must be a non-empty string`);
14726
- }
14727
- }
14728
- const initialMode = isMode(args.initial_mode) ? args.initial_mode : void 0;
14729
- const progressId = randomUUID();
14730
- try {
14731
- const flush = this.opts.createFlush(args);
14732
- const handle = await startProgressHandle({
14733
- initialLabel: label,
14734
- initialMode,
14735
- flush,
14736
- debounceMs: this.opts.debounceMs ?? 1e3
14737
- });
14738
- this.handles.set(progressId, handle);
14739
- return okResult(JSON.stringify({ progress_id: progressId }));
14740
- } catch (err) {
14741
- return errResult(`${this.toolNames.start} failed: ${err.message ?? String(err)}`);
14742
- }
14743
- }
14744
- async handleUpdate(args) {
14745
- const id = typeof args.progress_id === "string" ? args.progress_id : null;
14746
- if (!id) return errResult(`${this.toolNames.update}: 'progress_id' must be a string`);
14747
- const handle = this.handles.get(id);
14748
- if (!handle) return errResult(`${this.toolNames.update}: unknown progress_id (already terminated, or never started)`);
14749
- if ("mode" in args && !isMode(args.mode)) {
14750
- return errResult(
14751
- `${this.toolNames.update}: 'mode' must be one of 'thinking', 'working', or 'waiting'`
14752
- );
14753
- }
14754
- if (typeof args.step_total === "number" && typeof args.step_current !== "number") {
14755
- return errResult(
14756
- `${this.toolNames.update}: 'step_total' requires 'step_current'`
14757
- );
14758
- }
14759
- const next = {};
14760
- if (typeof args.step_label === "string") next.stepLabel = args.step_label;
14761
- if (typeof args.step_current === "number") {
14762
- next.stepNumber = {
14763
- current: args.step_current,
14764
- ...typeof args.step_total === "number" ? { total: args.step_total } : {}
14765
- };
14766
- }
14767
- if (isMode(args.mode)) next.mode = args.mode;
14768
- if (typeof args.detail === "string") next.detail = args.detail;
14769
- try {
14770
- await handle.update(next);
14771
- return okResult("ok");
14772
- } catch (err) {
14773
- return errResult(`${this.toolNames.update} failed: ${err.message ?? String(err)}`);
14774
- }
14775
- }
14776
- async handleComplete(args) {
14777
- const id = typeof args.progress_id === "string" ? args.progress_id : null;
14778
- if (!id) return errResult(`${this.toolNames.complete}: 'progress_id' must be a string`);
14779
- const handle = this.handles.get(id);
14780
- if (!handle) return errResult(`${this.toolNames.complete}: unknown progress_id`);
14781
- const summary = typeof args.summary === "string" ? args.summary : void 0;
14782
- try {
14783
- await handle.complete(summary);
14784
- this.handles.delete(id);
14785
- return okResult("ok");
14786
- } catch (err) {
14787
- return errResult(`${this.toolNames.complete} failed: ${err.message ?? String(err)}`);
14788
- }
14789
- }
14790
- async handleFail(args) {
14791
- const id = typeof args.progress_id === "string" ? args.progress_id : null;
14792
- if (!id) return errResult(`${this.toolNames.fail}: 'progress_id' must be a string`);
14793
- const reason = typeof args.reason === "string" ? args.reason : null;
14794
- if (!reason) return errResult(`${this.toolNames.fail}: 'reason' must be a non-empty string`);
14795
- const handle = this.handles.get(id);
14796
- if (!handle) return errResult(`${this.toolNames.fail}: unknown progress_id`);
14797
- try {
14798
- await handle.fail(reason);
14799
- this.handles.delete(id);
14800
- return okResult("ok");
14801
- } catch (err) {
14802
- return errResult(`${this.toolNames.fail} failed: ${err.message ?? String(err)}`);
14803
- }
14804
- }
14805
- };
14806
- function okResult(text) {
14807
- return { content: [{ type: "text", text }] };
14808
- }
14809
- function errResult(text) {
14810
- return { content: [{ type: "text", text }], isError: true };
14811
- }
14812
- function isMode(value) {
14813
- return value === "thinking" || value === "working" || value === "waiting";
14814
- }
14815
-
14816
14450
  // src/slack-thread-context.ts
14817
14451
  var SLACK_AUTOLOAD_THREAD_LIMIT = 30;
14818
14452
  var SLACK_AUTOLOAD_THREAD_MAX_CHARS = 4e3;
@@ -15016,20 +14650,20 @@ import {
15016
14650
  createWriteStream,
15017
14651
  existsSync as existsSync2,
15018
14652
  mkdirSync as mkdirSync3,
15019
- readFileSync as readFileSync3,
15020
- readdirSync,
14653
+ readFileSync as readFileSync4,
14654
+ readdirSync as readdirSync2,
15021
14655
  renameSync as renameSync2,
15022
14656
  statSync,
15023
14657
  unlinkSync as unlinkSync2,
15024
14658
  watch,
15025
14659
  writeFileSync as writeFileSync3
15026
14660
  } from "fs";
15027
- import { basename, join as join3, resolve as resolve2 } from "path";
14661
+ import { basename, join as join4, resolve as resolve2 } from "path";
15028
14662
  import { homedir as homedir2 } from "os";
15029
- import { createHash, randomUUID as randomUUID2 } from "crypto";
14663
+ import { createHash, randomUUID } from "crypto";
15030
14664
 
15031
14665
  // src/slack-thread-store.ts
15032
- import { mkdirSync, readFileSync, writeFileSync } from "fs";
14666
+ import { mkdirSync, readFileSync as readFileSync2, writeFileSync } from "fs";
15033
14667
  import { dirname } from "path";
15034
14668
  var FILE_VERSION = 1;
15035
14669
  var DEFAULT_TTL_DAYS = 30;
@@ -15040,7 +14674,7 @@ function loadThreadStore(filePath, opts = {}) {
15040
14674
  const ttlMs = ttlDays * 24 * 60 * 60 * 1e3;
15041
14675
  let raw;
15042
14676
  try {
15043
- raw = readFileSync(filePath, "utf-8");
14677
+ raw = readFileSync2(filePath, "utf-8");
15044
14678
  } catch {
15045
14679
  return { threads: /* @__PURE__ */ new Map(), pruned: 0 };
15046
14680
  }
@@ -15155,9 +14789,9 @@ async function runOrRetry(fn, opts) {
15155
14789
 
15156
14790
  // src/channel-attachments.ts
15157
14791
  import { homedir } from "os";
15158
- import { join, resolve, sep } from "path";
14792
+ import { join as join2, resolve, sep } from "path";
15159
14793
  function resolveChannelInboundDir(codeName, channelSlug) {
15160
- const base = join(homedir(), ".augmented");
14794
+ const base = join2(homedir(), ".augmented");
15161
14795
  const allowedSegment = /^[A-Za-z0-9_-]+$/;
15162
14796
  if (!allowedSegment.test(codeName) || !allowedSegment.test(channelSlug)) {
15163
14797
  throw new Error(
@@ -15803,12 +15437,12 @@ function createSlackBotUserIdClient(args) {
15803
15437
  import {
15804
15438
  existsSync,
15805
15439
  mkdirSync as mkdirSync2,
15806
- readFileSync as readFileSync2,
15440
+ readFileSync as readFileSync3,
15807
15441
  renameSync,
15808
15442
  unlinkSync,
15809
15443
  writeFileSync as writeFileSync2
15810
15444
  } from "fs";
15811
- import { join as join2 } from "path";
15445
+ import { join as join3 } from "path";
15812
15446
  function defaultIsPidAlive(pid) {
15813
15447
  if (!Number.isFinite(pid) || pid <= 0) return false;
15814
15448
  try {
@@ -15826,7 +15460,7 @@ function acquireMcpSpawnLock(args) {
15826
15460
  const isPidAlive = options.isPidAlive ?? defaultIsPidAlive;
15827
15461
  const selfPid = options.selfPid ?? process.pid;
15828
15462
  const now = options.now ?? (() => (/* @__PURE__ */ new Date()).toISOString());
15829
- const path = join2(agentDir, basename2);
15463
+ const path = join3(agentDir, basename2);
15830
15464
  const existing = readLockHolder(path);
15831
15465
  if (existing) {
15832
15466
  if (existing.pid === selfPid) {
@@ -15857,7 +15491,7 @@ function releaseMcpSpawnLock(lockPath, opts = {}) {
15857
15491
  function readLockHolder(path) {
15858
15492
  if (!existsSync(path)) return null;
15859
15493
  try {
15860
- const raw = readFileSync2(path, "utf8");
15494
+ const raw = readFileSync3(path, "utf8");
15861
15495
  const parsed = JSON.parse(raw);
15862
15496
  const pid = typeof parsed.pid === "number" ? parsed.pid : Number(parsed.pid);
15863
15497
  if (!Number.isFinite(pid) || pid <= 0) return null;
@@ -15939,9 +15573,9 @@ var SLACK_PEER_CLASSIFIER_CONFIG = {
15939
15573
  peers: parsePeersEnv(process.env.SLACK_PEERS, process.env.SLACK_PEERS_GATE),
15940
15574
  peer_disabled_mode: SLACK_PEER_DISABLED_MODE
15941
15575
  };
15942
- var SLACK_AGENT_DIR = AGENT_CODE_NAME ? join3(homedir2(), ".augmented", AGENT_CODE_NAME) : null;
15943
- var SLACK_PENDING_INBOUND_DIR = SLACK_AGENT_DIR ? join3(SLACK_AGENT_DIR, "slack-pending-inbound") : null;
15944
- var SLACK_RECOVERY_OUTBOX_DIR = SLACK_AGENT_DIR ? join3(SLACK_AGENT_DIR, "slack-recovery-outbox") : null;
15576
+ var SLACK_AGENT_DIR = AGENT_CODE_NAME ? join4(homedir2(), ".augmented", AGENT_CODE_NAME) : null;
15577
+ var SLACK_PENDING_INBOUND_DIR = SLACK_AGENT_DIR ? join4(SLACK_AGENT_DIR, "slack-pending-inbound") : null;
15578
+ var SLACK_RECOVERY_OUTBOX_DIR = SLACK_AGENT_DIR ? join4(SLACK_AGENT_DIR, "slack-recovery-outbox") : null;
15945
15579
  var SLACK_MAX_RECOVERY_ATTEMPTS = 3;
15946
15580
  function redactSlackId(id) {
15947
15581
  if (!id) return "<none>";
@@ -15953,7 +15587,7 @@ function safeSlackMarkerName(channel, threadTs, messageTs) {
15953
15587
  }
15954
15588
  function slackPendingInboundPath(channel, threadTs, messageTs) {
15955
15589
  if (!SLACK_PENDING_INBOUND_DIR) return null;
15956
- return join3(SLACK_PENDING_INBOUND_DIR, safeSlackMarkerName(channel, threadTs, messageTs));
15590
+ return join4(SLACK_PENDING_INBOUND_DIR, safeSlackMarkerName(channel, threadTs, messageTs));
15957
15591
  }
15958
15592
  function writeSlackPendingInboundMarker(channel, threadTs, messageTs) {
15959
15593
  const path = slackPendingInboundPath(channel, threadTs, messageTs);
@@ -15980,10 +15614,10 @@ function clearAllSlackPendingMarkersForThread(channel, threadTs) {
15980
15614
  const safeThread = threadTs.replace(/[^A-Za-z0-9_-]/g, "_");
15981
15615
  const prefix = `${safeChan}__${safeThread}__`;
15982
15616
  try {
15983
- for (const f of readdirSync(SLACK_PENDING_INBOUND_DIR)) {
15617
+ for (const f of readdirSync2(SLACK_PENDING_INBOUND_DIR)) {
15984
15618
  if (!f.startsWith(prefix) || !f.endsWith(".json")) continue;
15985
15619
  try {
15986
- unlinkSync2(join3(SLACK_PENDING_INBOUND_DIR, f));
15620
+ unlinkSync2(join4(SLACK_PENDING_INBOUND_DIR, f));
15987
15621
  } catch {
15988
15622
  }
15989
15623
  }
@@ -16004,10 +15638,10 @@ function slackNextRetryName(filename) {
16004
15638
  async function processSlackRecoveryOutboxFile(filename) {
16005
15639
  if (!SLACK_RECOVERY_OUTBOX_DIR) return;
16006
15640
  if (filename.endsWith(".poison.json") || filename.endsWith(".tmp")) return;
16007
- const fullPath = join3(SLACK_RECOVERY_OUTBOX_DIR, filename);
15641
+ const fullPath = join4(SLACK_RECOVERY_OUTBOX_DIR, filename);
16008
15642
  let payload;
16009
15643
  try {
16010
- payload = JSON.parse(readFileSync3(fullPath, "utf-8"));
15644
+ payload = JSON.parse(readFileSync4(fullPath, "utf-8"));
16011
15645
  } catch (err) {
16012
15646
  process.stderr.write(
16013
15647
  `slack-channel(${AGENT_CODE_NAME}): recovery outbox parse failed (${filename}): ${err.message}
@@ -16081,7 +15715,7 @@ async function processSlackRecoveryOutboxFile(filename) {
16081
15715
  const next = slackNextRetryName(filename);
16082
15716
  if (next) {
16083
15717
  try {
16084
- renameSync2(fullPath, join3(SLACK_RECOVERY_OUTBOX_DIR, next.next));
15718
+ renameSync2(fullPath, join4(SLACK_RECOVERY_OUTBOX_DIR, next.next));
16085
15719
  if (next.attempt >= SLACK_MAX_RECOVERY_ATTEMPTS) {
16086
15720
  process.stderr.write(
16087
15721
  `slack-channel(${AGENT_CODE_NAME}): ghost-reply recovery exhausted retries \u2014 moved to ${next.next}
@@ -16111,7 +15745,7 @@ function scanSlackRecoveryRetries() {
16111
15745
  if (!SLACK_RECOVERY_OUTBOX_DIR) return;
16112
15746
  let entries;
16113
15747
  try {
16114
- entries = readdirSync(SLACK_RECOVERY_OUTBOX_DIR);
15748
+ entries = readdirSync2(SLACK_RECOVERY_OUTBOX_DIR);
16115
15749
  } catch {
16116
15750
  return;
16117
15751
  }
@@ -16120,7 +15754,7 @@ function scanSlackRecoveryRetries() {
16120
15754
  if (!f.includes(".retry-") || f.endsWith(".poison.json")) continue;
16121
15755
  let mtimeMs;
16122
15756
  try {
16123
- mtimeMs = statSync(join3(SLACK_RECOVERY_OUTBOX_DIR, f)).mtimeMs;
15757
+ mtimeMs = statSync(join4(SLACK_RECOVERY_OUTBOX_DIR, f)).mtimeMs;
16124
15758
  } catch {
16125
15759
  continue;
16126
15760
  }
@@ -16141,7 +15775,7 @@ function startSlackRecoveryOutboxWatcher() {
16141
15775
  return;
16142
15776
  }
16143
15777
  try {
16144
- for (const f of readdirSync(SLACK_RECOVERY_OUTBOX_DIR)) {
15778
+ for (const f of readdirSync2(SLACK_RECOVERY_OUTBOX_DIR)) {
16145
15779
  if (isFirstAttemptSlackOutboxFile(f)) void processSlackRecoveryOutboxFile(f);
16146
15780
  }
16147
15781
  } catch {
@@ -16150,7 +15784,7 @@ function startSlackRecoveryOutboxWatcher() {
16150
15784
  const watcher = watch(SLACK_RECOVERY_OUTBOX_DIR, (event, filename) => {
16151
15785
  if (event !== "rename" || !filename) return;
16152
15786
  if (!isFirstAttemptSlackOutboxFile(filename)) return;
16153
- if (existsSync2(join3(SLACK_RECOVERY_OUTBOX_DIR, filename))) {
15787
+ if (existsSync2(join4(SLACK_RECOVERY_OUTBOX_DIR, filename))) {
16154
15788
  void processSlackRecoveryOutboxFile(filename);
16155
15789
  }
16156
15790
  });
@@ -16174,7 +15808,7 @@ function sweepSlackStaleMarkersOnBoot() {
16174
15808
  if (!existsSync2(SLACK_PENDING_INBOUND_DIR)) return;
16175
15809
  let filenames;
16176
15810
  try {
16177
- filenames = readdirSync(SLACK_PENDING_INBOUND_DIR);
15811
+ filenames = readdirSync2(SLACK_PENDING_INBOUND_DIR);
16178
15812
  } catch (err) {
16179
15813
  process.stderr.write(
16180
15814
  `slack-channel(${AGENT_CODE_NAME}): stale-marker readdir failed: ${err.message}
@@ -16187,10 +15821,10 @@ function sweepSlackStaleMarkersOnBoot() {
16187
15821
  for (const filename of filenames) {
16188
15822
  if (!filename.endsWith(".json")) continue;
16189
15823
  if (filename.endsWith(".tmp")) continue;
16190
- const fullPath = join3(SLACK_PENDING_INBOUND_DIR, filename);
15824
+ const fullPath = join4(SLACK_PENDING_INBOUND_DIR, filename);
16191
15825
  let marker;
16192
15826
  try {
16193
- marker = JSON.parse(readFileSync3(fullPath, "utf-8"));
15827
+ marker = JSON.parse(readFileSync4(fullPath, "utf-8"));
16194
15828
  } catch (err) {
16195
15829
  process.stderr.write(
16196
15830
  `slack-channel(${AGENT_CODE_NAME}): stale-marker parse failed for ${redactSlackId(filename)}: ${err.message}
@@ -16243,7 +15877,7 @@ function noteThreadActivityByMessageTs(channel, messageTs) {
16243
15877
  if (!existsSync2(SLACK_PENDING_INBOUND_DIR)) return;
16244
15878
  let filenames;
16245
15879
  try {
16246
- filenames = readdirSync(SLACK_PENDING_INBOUND_DIR);
15880
+ filenames = readdirSync2(SLACK_PENDING_INBOUND_DIR);
16247
15881
  } catch {
16248
15882
  return;
16249
15883
  }
@@ -16255,12 +15889,12 @@ function noteThreadActivityByMessageTs(channel, messageTs) {
16255
15889
  if (!filename.startsWith(channelPrefix)) continue;
16256
15890
  if (!filename.endsWith(messageSuffix)) continue;
16257
15891
  try {
16258
- unlinkSync2(join3(SLACK_PENDING_INBOUND_DIR, filename));
15892
+ unlinkSync2(join4(SLACK_PENDING_INBOUND_DIR, filename));
16259
15893
  } catch {
16260
15894
  }
16261
15895
  }
16262
15896
  }
16263
- var RESTART_FLAGS_DIR = join3(homedir2(), ".augmented", "restart-flags");
15897
+ var RESTART_FLAGS_DIR = join4(homedir2(), ".augmented", "restart-flags");
16264
15898
  function buildAugmentedSlackMetadata() {
16265
15899
  if (!AGT_TEAM_ID) return void 0;
16266
15900
  return {
@@ -16442,7 +16076,7 @@ async function handleSlashCommandEnvelope(payload) {
16442
16076
  if (!existsSync2(RESTART_FLAGS_DIR)) {
16443
16077
  mkdirSync3(RESTART_FLAGS_DIR, { recursive: true });
16444
16078
  }
16445
- const flagPath = join3(RESTART_FLAGS_DIR, `${codeName}.flag`);
16079
+ const flagPath = join4(RESTART_FLAGS_DIR, `${codeName}.flag`);
16446
16080
  const flag = {
16447
16081
  codeName,
16448
16082
  source: "slack",
@@ -16452,7 +16086,7 @@ async function handleSlashCommandEnvelope(payload) {
16452
16086
  ...payload.thread_ts ? { thread_ts: payload.thread_ts } : {}
16453
16087
  }
16454
16088
  };
16455
- const tmpPath = `${flagPath}.${process.pid}.${randomUUID2()}.tmp`;
16089
+ const tmpPath = `${flagPath}.${process.pid}.${randomUUID()}.tmp`;
16456
16090
  writeFileSync3(tmpPath, JSON.stringify(flag) + "\n", "utf8");
16457
16091
  renameSync2(tmpPath, flagPath);
16458
16092
  process.stderr.write(
@@ -16549,7 +16183,7 @@ async function handleRestartCommand(opts) {
16549
16183
  if (!existsSync2(RESTART_FLAGS_DIR)) {
16550
16184
  mkdirSync3(RESTART_FLAGS_DIR, { recursive: true });
16551
16185
  }
16552
- const flagPath = join3(RESTART_FLAGS_DIR, `${codeName}.flag`);
16186
+ const flagPath = join4(RESTART_FLAGS_DIR, `${codeName}.flag`);
16553
16187
  const flag = {
16554
16188
  codeName,
16555
16189
  source: "slack",
@@ -16560,7 +16194,7 @@ async function handleRestartCommand(opts) {
16560
16194
  message_ts: opts.ts
16561
16195
  }
16562
16196
  };
16563
- const tmpPath = `${flagPath}.${process.pid}.${randomUUID2()}.tmp`;
16197
+ const tmpPath = `${flagPath}.${process.pid}.${randomUUID()}.tmp`;
16564
16198
  writeFileSync3(tmpPath, JSON.stringify(flag) + "\n", "utf8");
16565
16199
  renameSync2(tmpPath, flagPath);
16566
16200
  process.stderr.write(
@@ -16608,7 +16242,7 @@ var THREAD_STORE_TTL_DAYS = parseTtlDays(process.env.SLACK_THREAD_FOLLOW_TTL_DAY
16608
16242
  var threadPersister = null;
16609
16243
  function resolveThreadStorePath() {
16610
16244
  if (!AGENT_CODE_NAME) return null;
16611
- return join3(homedir2(), ".augmented", AGENT_CODE_NAME, "slack-tracked-threads.json");
16245
+ return join4(homedir2(), ".augmented", AGENT_CODE_NAME, "slack-tracked-threads.json");
16612
16246
  }
16613
16247
  function parseTtlDays(raw) {
16614
16248
  if (!raw) return void 0;
@@ -16643,9 +16277,9 @@ if (!BOT_TOKEN || !APP_TOKEN) {
16643
16277
  var slackStderrLogStream = null;
16644
16278
  if (AGENT_CODE_NAME) {
16645
16279
  try {
16646
- const logDir = join3(homedir2(), ".augmented", AGENT_CODE_NAME);
16280
+ const logDir = join4(homedir2(), ".augmented", AGENT_CODE_NAME);
16647
16281
  mkdirSync3(logDir, { recursive: true });
16648
- slackStderrLogStream = createWriteStream(join3(logDir, "slack-channel-stderr.log"), {
16282
+ slackStderrLogStream = createWriteStream(join4(logDir, "slack-channel-stderr.log"), {
16649
16283
  flags: "a",
16650
16284
  mode: 384
16651
16285
  });
@@ -16730,41 +16364,17 @@ var mcp = new Server(
16730
16364
  // 2048 chars, so anything appended late silently disappears.
16731
16365
  "CRITICAL: every response to a Slack <channel> tag MUST go through slack.reply. Text in your session WITHOUT a slack.reply call never reaches the user \u2014 the message dies inside the agent process.",
16732
16366
  `Inbound: <channel ... thread_ts="..." [thread_context="..."]>. Pass channel + thread_ts to slack.reply on threads. thread_context = thread pre-loaded; ground replies ONLY in it, never another channel's.`,
16733
- "Long task (3+ tool calls or >5s)? slack_progress_start (channel + thread_ts), slack_progress_update between steps, slack_progress_complete/_fail to close. One anchor edits in place; avoids thread spam. Not for one-shots.",
16734
16367
  "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.",
16735
16368
  "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.",
16736
16369
  'Mentioned in a channel \u2192 respond in that thread. DM \u2192 respond directly. auto_followed="true" \u2192 only reply if useful, OR if your own bot user is @-mentioned (counts even in auto_followed).',
16737
- "Reaction taxonomy (use slack.react sparingly \u2014 prefer a reply): \u{1F440} = ack (already auto-added on inbound, do not duplicate); \u2705 = success. NEVER react to signal failure \u2014 users can't tell why something failed from an emoji. On failure, slack.reply with one sentence explaining what went wrong (no stack traces, no secrets).",
16370
+ "Reaction taxonomy (use slack.react sparingly \u2014 prefer a reply): \u{1F440} = ack (already auto-added on inbound, do not duplicate); \u2705 = success. NEVER react to signal failure 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 \u274C 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 \u274C yourself.)",
16738
16371
  `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.`,
16739
16372
  "To deliver a file: save under your project dir, call slack.upload_file with path + channel + thread_ts."
16740
16373
  ].join(" ")
16741
16374
  }
16742
16375
  );
16743
- var progressRegistry = new ProgressToolRegistry({
16744
- prefix: "slack",
16745
- surfaceDescription: "a Slack thread",
16746
- startArgsSchema: {
16747
- properties: {
16748
- channel: {
16749
- type: "string",
16750
- description: "Slack channel id (from the `channel` attribute on the inbound <channel> tag)."
16751
- },
16752
- thread_ts: {
16753
- type: "string",
16754
- description: "Thread `ts` so the anchor lands in the same thread as the request (from the `thread_ts` attribute on the inbound <channel> tag). Omit to post at channel-root."
16755
- }
16756
- },
16757
- required: ["channel"]
16758
- },
16759
- createFlush: (args) => createSlackProgressFlush({
16760
- botToken: BOT_TOKEN,
16761
- channel: args.channel,
16762
- threadTs: typeof args.thread_ts === "string" ? args.thread_ts : void 0
16763
- })
16764
- });
16765
16376
  mcp.setRequestHandler(ListToolsRequestSchema, async () => ({
16766
16377
  tools: [
16767
- ...progressRegistry.getDefinitions(),
16768
16378
  {
16769
16379
  name: "slack.reply",
16770
16380
  description: "Send a message back to a Slack channel or thread",
@@ -16786,7 +16396,7 @@ mcp.setRequestHandler(ListToolsRequestSchema, async () => ({
16786
16396
  },
16787
16397
  {
16788
16398
  name: "slack.react",
16789
- description: "Add an emoji reaction to a Slack message. Use sparingly \u2014 prefer a text reply. Reaction taxonomy: \u2705 = action completed successfully. NEVER react to signal failure \u2014 users can't tell why it failed from a reaction. On failure, call slack.reply with one sentence explaining what went wrong instead. \u{1F440} (eyes) is already auto-applied on inbound; do not duplicate.",
16399
+ description: "Add an emoji reaction to a Slack message. Use sparingly \u2014 prefer a text reply. Reaction taxonomy: \u2705 = action completed successfully. NEVER react to signal failure of your work \u2014 users can't tell why it failed from a reaction. On failure, call slack.reply with one sentence explaining what went wrong instead. \u{1F440} (eyes) is already auto-applied on inbound; do not duplicate. \u274C (:x:) is reserved for the system to mark an inbound that arrived while the agent couldn't reply \u2014 never add \u274C yourself.",
16790
16400
  inputSchema: {
16791
16401
  type: "object",
16792
16402
  properties: {
@@ -17015,8 +16625,6 @@ mcp.setRequestHandler(CallToolRequestSchema, async (req) => {
17015
16625
  if (isImpersonating() && !channelsEnabledOverride() && SLACK_EGRESS_TOOLS.has(name)) {
17016
16626
  return buildImpersonationRefusal(name);
17017
16627
  }
17018
- const progressResult = await progressRegistry.handle(name, args ?? {});
17019
- if (progressResult !== void 0) return progressResult;
17020
16628
  if (name === "slack.reply") {
17021
16629
  const { channel, text, thread_ts } = args;
17022
16630
  if (channel && thread_ts) {
@@ -17238,7 +16846,7 @@ ${result.formatted}` : "Thread is empty or not found."
17238
16846
  };
17239
16847
  }
17240
16848
  size = stat.size;
17241
- bytes = readFileSync3(resolvedPath);
16849
+ bytes = readFileSync4(resolvedPath);
17242
16850
  } catch (err) {
17243
16851
  return {
17244
16852
  content: [{ type: "text", text: `Failed to read file: ${err.message}` }],
@@ -17537,15 +17145,15 @@ async function handleSendStructured(args) {
17537
17145
  const { validateSlackBlocks: validateSlackBlocks2 } = runtime;
17538
17146
  const { channel, blocks, text, thread_ts, interactive } = args;
17539
17147
  if (typeof channel !== "string" || !channel) {
17540
- return errResult2("channel is required");
17148
+ return errResult("channel is required");
17541
17149
  }
17542
17150
  if (typeof text !== "string" || !text) {
17543
- return errResult2("text is required (used as fallback for push notifications and unfurls)");
17151
+ return errResult("text is required (used as fallback for push notifications and unfurls)");
17544
17152
  }
17545
17153
  noteThreadActivity(channel, thread_ts);
17546
17154
  const validation = validateSlackBlocks2(blocks);
17547
17155
  if (!validation.ok) {
17548
- return errResult2(`Invalid blocks: ${validation.errors.map((e) => `${e.path}: ${e.message}`).join("; ")}`);
17156
+ return errResult(`Invalid blocks: ${validation.errors.map((e) => `${e.path}: ${e.message}`).join("; ")}`);
17549
17157
  }
17550
17158
  let effectiveBlocks = blocks;
17551
17159
  let ratingCallbackId = null;
@@ -17553,13 +17161,13 @@ async function handleSendStructured(args) {
17553
17161
  let ratingTokenised = [];
17554
17162
  if (interactive) {
17555
17163
  if (interactive.type !== "rate_run") {
17556
- return errResult2(`Unsupported interactive.type '${interactive.type}'. Supported: rate_run.`);
17164
+ return errResult(`Unsupported interactive.type '${interactive.type}'. Supported: rate_run.`);
17557
17165
  }
17558
17166
  if (typeof interactive.run_id !== "string" || !interactive.run_id) {
17559
- return errResult2("interactive.run_id is required when interactive is set");
17167
+ return errResult("interactive.run_id is required when interactive is set");
17560
17168
  }
17561
17169
  if (!interactiveHostAvailable()) {
17562
- return errResult2(
17170
+ return errResult(
17563
17171
  "interactive=rate_run requires Block Kit plus host MCP wiring (AGT_HOST, AGT_API_KEY, AGT_AGENT_ID)."
17564
17172
  );
17565
17173
  }
@@ -17590,7 +17198,7 @@ async function handleSendStructured(args) {
17590
17198
  effectiveBlocks = [...blocks, ratingBlock];
17591
17199
  const finalValidation = runtime.validateSlackBlocks(effectiveBlocks);
17592
17200
  if (!finalValidation.ok) {
17593
- return errResult2(
17201
+ return errResult(
17594
17202
  `Invalid blocks (after appending rating row): ${finalValidation.errors.map((e) => `${e.path}: ${e.message}`).join("; ")}`
17595
17203
  );
17596
17204
  }
@@ -17611,7 +17219,7 @@ async function handleSendStructured(args) {
17611
17219
  kindData: { run_id: interactive.run_id, scale }
17612
17220
  });
17613
17221
  } catch (err) {
17614
- return errResult2(`Failed to register rating row: ${err.message}`);
17222
+ return errResult(`Failed to register rating row: ${err.message}`);
17615
17223
  }
17616
17224
  }
17617
17225
  const result = await postSlackMessageWithTs({
@@ -17621,7 +17229,7 @@ async function handleSendStructured(args) {
17621
17229
  ...thread_ts ? { thread_ts } : {}
17622
17230
  });
17623
17231
  if (!result.ok || !result.ts) {
17624
- return errResult2(`Slack chat.postMessage failed: ${result.error ?? "unknown"}`);
17232
+ return errResult(`Slack chat.postMessage failed: ${result.error ?? "unknown"}`);
17625
17233
  }
17626
17234
  if (ratingCallbackId) {
17627
17235
  try {
@@ -17676,17 +17284,17 @@ async function handleSendStructured(args) {
17676
17284
  }
17677
17285
  async function handleAskUser(args) {
17678
17286
  if (!askUserToolAvailable()) {
17679
- return errResult2("slack.ask_user is disabled or the host MCP is missing AGT_HOST/AGT_API_KEY/AGT_AGENT_ID env wiring.");
17287
+ return errResult("slack.ask_user is disabled or the host MCP is missing AGT_HOST/AGT_API_KEY/AGT_AGENT_ID env wiring.");
17680
17288
  }
17681
17289
  const { randomUUID: rndUUID } = await import("crypto");
17682
17290
  const runtime = await Promise.resolve().then(() => (init_slack_block_kit_runtime(), slack_block_kit_runtime_exports));
17683
17291
  const { validateAskUserOptions: validateAskUserOptions2, buildAskUserBlocks: buildAskUserBlocks2 } = runtime;
17684
17292
  const { channel, question, options, timeout_seconds, thread_ts } = args;
17685
- if (typeof channel !== "string" || !channel) return errResult2("channel is required");
17686
- if (typeof question !== "string" || !question) return errResult2("question is required");
17293
+ if (typeof channel !== "string" || !channel) return errResult("channel is required");
17294
+ if (typeof question !== "string" || !question) return errResult("question is required");
17687
17295
  const optsValidation = validateAskUserOptions2(options);
17688
17296
  if (!optsValidation.ok) {
17689
- return errResult2(`Invalid options: ${optsValidation.errors.map((e) => `${e.path}: ${e.message}`).join("; ")}`);
17297
+ return errResult(`Invalid options: ${optsValidation.errors.map((e) => `${e.path}: ${e.message}`).join("; ")}`);
17690
17298
  }
17691
17299
  const requestedTimeout = typeof timeout_seconds === "number" ? timeout_seconds : 300;
17692
17300
  const timeoutSec = Math.max(10, Math.min(3600, requestedTimeout));
@@ -17719,7 +17327,7 @@ async function handleAskUser(args) {
17719
17327
  expiresAt
17720
17328
  });
17721
17329
  } catch (err) {
17722
- return errResult2(`Failed to register pending interaction: ${err.message}`);
17330
+ return errResult(`Failed to register pending interaction: ${err.message}`);
17723
17331
  }
17724
17332
  const slackResult = await postSlackMessageWithTs({
17725
17333
  channel,
@@ -17729,7 +17337,7 @@ async function handleAskUser(args) {
17729
17337
  ...thread_ts ? { thread_ts } : {}
17730
17338
  });
17731
17339
  if (!slackResult.ok || !slackResult.ts) {
17732
- return errResult2(`Slack chat.postMessage failed: ${slackResult.error ?? "unknown"}`);
17340
+ return errResult(`Slack chat.postMessage failed: ${slackResult.error ?? "unknown"}`);
17733
17341
  }
17734
17342
  try {
17735
17343
  await runtime.updatePendingInteractionMessageTs(cfg, callbackId, slackResult.ts);
@@ -17776,7 +17384,7 @@ async function handleAskUser(args) {
17776
17384
  }
17777
17385
  async function handleChannelRequestInput(args) {
17778
17386
  if (!askUserToolAvailable()) {
17779
- return errResult2(
17387
+ return errResult(
17780
17388
  "channel_request_input is disabled or the host MCP is missing AGT_HOST/AGT_API_KEY/AGT_AGENT_ID env wiring."
17781
17389
  );
17782
17390
  }
@@ -17784,10 +17392,10 @@ async function handleChannelRequestInput(args) {
17784
17392
  const { apiCall: apiCall2, waitForResolution: waitForResolution2, validateAskUserOptions: validateAskUserOptions2 } = runtime;
17785
17393
  const { thread_id, prompt_text, options, schema, request_id, timeout_seconds } = args;
17786
17394
  if (typeof thread_id !== "string" || !thread_id) {
17787
- return errResult2("thread_id is required (shape: `<channel_id>:<thread_ts>`)");
17395
+ return errResult("thread_id is required (shape: `<channel_id>:<thread_ts>`)");
17788
17396
  }
17789
17397
  if (typeof prompt_text !== "string" || !prompt_text) {
17790
- return errResult2("prompt_text is required");
17398
+ return errResult("prompt_text is required");
17791
17399
  }
17792
17400
  const optsValidation = validateAskUserOptions2(options, {
17793
17401
  maxOptions: 4,
@@ -17795,25 +17403,25 @@ async function handleChannelRequestInput(args) {
17795
17403
  maxValueChars: 2e3
17796
17404
  });
17797
17405
  if (!optsValidation.ok) {
17798
- return errResult2(
17406
+ return errResult(
17799
17407
  `Invalid options: ${optsValidation.errors.map((e) => `${e.path}: ${e.message}`).join("; ")}`
17800
17408
  );
17801
17409
  }
17802
17410
  const optionsArr = options;
17803
17411
  if (optionsArr.length < 2) {
17804
- return errResult2("options must contain at least 2 entries (this is a picker, not a notification)");
17412
+ return errResult("options must contain at least 2 entries (this is a picker, not a notification)");
17805
17413
  }
17806
17414
  if (!schema || typeof schema.type !== "string") {
17807
- return errResult2("schema is required with shape { type: 'yes_no' | 'choice' }");
17415
+ return errResult("schema is required with shape { type: 'yes_no' | 'choice' }");
17808
17416
  }
17809
17417
  if (schema.type !== "yes_no" && schema.type !== "choice") {
17810
- return errResult2("schema.type must be 'yes_no' or 'choice'");
17418
+ return errResult("schema.type must be 'yes_no' or 'choice'");
17811
17419
  }
17812
17420
  if (schema.type === "yes_no" && optionsArr.length !== 2) {
17813
- return errResult2("schema.type='yes_no' requires exactly 2 options");
17421
+ return errResult("schema.type='yes_no' requires exactly 2 options");
17814
17422
  }
17815
17423
  if (request_id !== void 0 && (typeof request_id !== "string" || !request_id)) {
17816
- return errResult2("request_id must be a non-empty string when provided");
17424
+ return errResult("request_id must be a non-empty string when provided");
17817
17425
  }
17818
17426
  const requestedTimeout = typeof timeout_seconds === "number" ? timeout_seconds : 300;
17819
17427
  const timeoutSec = Math.max(10, Math.min(3600, requestedTimeout));
@@ -17835,10 +17443,10 @@ async function handleChannelRequestInput(args) {
17835
17443
  dispatchPayload = await res.json().catch(() => ({}));
17836
17444
  if (!res.ok || !dispatchPayload.ok || !dispatchPayload.callback_id) {
17837
17445
  const reason = dispatchPayload.reason ?? `http_${res.status}`;
17838
- return errResult2(`channel_request_input dispatch failed: ${reason}`);
17446
+ return errResult(`channel_request_input dispatch failed: ${reason}`);
17839
17447
  }
17840
17448
  } catch (err) {
17841
- return errResult2(
17449
+ return errResult(
17842
17450
  `channel_request_input dispatch failed: ${err.message}`
17843
17451
  );
17844
17452
  }
@@ -17876,7 +17484,7 @@ async function handleChannelRequestInput(args) {
17876
17484
  ]
17877
17485
  };
17878
17486
  }
17879
- function errResult2(text) {
17487
+ function errResult(text) {
17880
17488
  return { content: [{ type: "text", text }], isError: true };
17881
17489
  }
17882
17490
  var MAX_INBOUND_FILE_BYTES = 25 * 1024 * 1024;
@@ -18258,14 +17866,24 @@ async function connectSocketMode() {
18258
17866
  isAutoFollowed,
18259
17867
  botUserId
18260
17868
  });
18261
- if (decideSlackAckReaction({ channel, ts })) {
17869
+ const ackProbe = process.env.TMUX && AGENT_CODE_NAME ? probeAgentSessionCached(AGENT_CODE_NAME) : { tmux: "unknown", claude: "unknown" };
17870
+ const ackDecision = decideAckReaction({
17871
+ hasTarget: decideSlackAckReaction({ channel, ts }),
17872
+ integrationReady: Boolean(BOT_TOKEN),
17873
+ tmux: ackProbe.tmux,
17874
+ claude: ackProbe.claude,
17875
+ withinStartupGrace: process.uptime() * 1e3 < ACK_STARTUP_GRACE_MS,
17876
+ oldestPendingAgeMs: oldestPendingMarkerAgeMs(SLACK_PENDING_INBOUND_DIR)
17877
+ });
17878
+ if (ackDecision !== "none") {
17879
+ const reactionName = ackDecision === "undeliverable" ? "x" : "eyes";
18262
17880
  fetch("https://slack.com/api/reactions.add", {
18263
17881
  method: "POST",
18264
17882
  headers: {
18265
17883
  "Content-Type": "application/json",
18266
17884
  Authorization: `Bearer ${BOT_TOKEN}`
18267
17885
  },
18268
- body: JSON.stringify({ channel, timestamp: ts, name: "eyes" })
17886
+ body: JSON.stringify({ channel, timestamp: ts, name: reactionName })
18269
17887
  }).catch(() => {
18270
17888
  });
18271
17889
  }