@integrity-labs/agt-cli 0.28.457 → 0.28.459

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.
@@ -18906,7 +18906,7 @@ var require_filters = __commonJS({
18906
18906
  return r.copySafeness(str, res);
18907
18907
  }
18908
18908
  _exports.indent = indent;
18909
- function join21(arr, del, attr) {
18909
+ function join22(arr, del, attr) {
18910
18910
  del = del || "";
18911
18911
  if (attr) {
18912
18912
  arr = lib.map(arr, function(v) {
@@ -18915,7 +18915,7 @@ var require_filters = __commonJS({
18915
18915
  }
18916
18916
  return arr.join(del);
18917
18917
  }
18918
- _exports.join = join21;
18918
+ _exports.join = join22;
18919
18919
  function last(arr) {
18920
18920
  return arr[arr.length - 1];
18921
18921
  }
@@ -22516,11 +22516,11 @@ async function waitForResolution(cfg, callbackId, opts) {
22516
22516
  }
22517
22517
  } catch {
22518
22518
  }
22519
- await sleep2(interval);
22519
+ await sleep3(interval);
22520
22520
  }
22521
22521
  return { kind: "timeout" };
22522
22522
  }
22523
- function sleep2(ms) {
22523
+ function sleep3(ms) {
22524
22524
  return new Promise((r) => setTimeout(r, ms));
22525
22525
  }
22526
22526
  function generateOptionToken() {
@@ -34052,6 +34052,160 @@ function classifyTranscriptRateLimit(jsonl, startMs, endMs, now) {
34052
34052
  return newest;
34053
34053
  }
34054
34054
 
34055
+ // ../core/dist/claude-code-usage/turn-failure-classifier.js
34056
+ var UNKNOWN_TURN_FAILURE = Object.freeze({
34057
+ outcome: "unknown",
34058
+ atMs: null,
34059
+ failureClass: null,
34060
+ httpStatus: null,
34061
+ attempt: null,
34062
+ maxAttempts: null
34063
+ });
34064
+ var EXCLUDED_STATUSES = /* @__PURE__ */ new Set([429]);
34065
+ function classifyTransientStatus(status) {
34066
+ if (!Number.isFinite(status))
34067
+ return null;
34068
+ if (EXCLUDED_STATUSES.has(status))
34069
+ return null;
34070
+ if (status === 529)
34071
+ return "overloaded";
34072
+ if (status >= 500 && status <= 599)
34073
+ return "server_error";
34074
+ return null;
34075
+ }
34076
+ function numberOrNull(value) {
34077
+ return typeof value === "number" && Number.isFinite(value) ? value : null;
34078
+ }
34079
+ function isErrorShaped(record2) {
34080
+ if (record2.isApiErrorMessage === true)
34081
+ return true;
34082
+ if (record2.type === "system" && record2.level === "error")
34083
+ return true;
34084
+ if (record2.type === "assistant" && typeof record2.error === "string" && record2.error)
34085
+ return true;
34086
+ return false;
34087
+ }
34088
+ function classifyRecord(record2, tsMs) {
34089
+ if (record2.isSidechain === true)
34090
+ return null;
34091
+ if (record2.type === "system" && record2.subtype === "api_error") {
34092
+ const error2 = record2.error;
34093
+ const status = typeof error2 === "object" && error2 !== null ? numberOrNull(error2.status) : null;
34094
+ if (status === null)
34095
+ return null;
34096
+ const failureClass = classifyTransientStatus(status);
34097
+ if (!failureClass)
34098
+ return null;
34099
+ return {
34100
+ outcome: "retrying",
34101
+ atMs: tsMs,
34102
+ failureClass,
34103
+ httpStatus: status,
34104
+ attempt: numberOrNull(record2.retryAttempt),
34105
+ maxAttempts: numberOrNull(record2.maxRetries)
34106
+ };
34107
+ }
34108
+ if (record2.type !== "assistant")
34109
+ return null;
34110
+ if (record2.isApiErrorMessage === true) {
34111
+ const status = numberOrNull(record2.apiErrorStatus);
34112
+ const failureClass = status === null ? null : classifyTransientStatus(status);
34113
+ if (!failureClass)
34114
+ return null;
34115
+ return {
34116
+ outcome: "failed",
34117
+ atMs: tsMs,
34118
+ failureClass,
34119
+ httpStatus: status,
34120
+ attempt: null,
34121
+ maxAttempts: null
34122
+ };
34123
+ }
34124
+ const message = record2.message;
34125
+ if (typeof message !== "object" || message === null)
34126
+ return null;
34127
+ const msg = message;
34128
+ if (msg.model === "<synthetic>")
34129
+ return null;
34130
+ const usage = msg.usage;
34131
+ if (typeof usage !== "object" || usage === null)
34132
+ return null;
34133
+ const u = usage;
34134
+ const spent = Number(u.input_tokens ?? 0) + Number(u.output_tokens ?? 0) + Number(u.cache_creation_input_tokens ?? 0) + Number(u.cache_read_input_tokens ?? 0);
34135
+ if (!Number.isFinite(spent) || spent <= 0)
34136
+ return null;
34137
+ return {
34138
+ outcome: "served",
34139
+ atMs: tsMs,
34140
+ failureClass: null,
34141
+ httpStatus: null,
34142
+ attempt: null,
34143
+ maxAttempts: null
34144
+ };
34145
+ }
34146
+ function pickNewerTurnFailure(current, next) {
34147
+ if (next.outcome === "unknown")
34148
+ return current;
34149
+ if (current.outcome === "unknown")
34150
+ return next;
34151
+ return (next.atMs ?? 0) >= (current.atMs ?? 0) ? next : current;
34152
+ }
34153
+ function isShapeRecognised(record2) {
34154
+ if (record2.type === "system" && record2.subtype === "api_error") {
34155
+ const error2 = record2.error;
34156
+ return typeof error2 === "object" && error2 !== null && numberOrNull(error2.status) !== null;
34157
+ }
34158
+ if (record2.type === "assistant" && record2.isApiErrorMessage === true) {
34159
+ return numberOrNull(record2.apiErrorStatus) !== null;
34160
+ }
34161
+ return false;
34162
+ }
34163
+ function analyzeTranscriptTurnFailure(jsonl, startMs, endMs, opts = {}) {
34164
+ const maxKeys = opts.maxKeys ?? 40;
34165
+ let newest = UNKNOWN_TURN_FAILURE;
34166
+ let coarse = 0;
34167
+ let fine = 0;
34168
+ const keys = /* @__PURE__ */ new Set();
34169
+ for (const line of jsonl.split("\n")) {
34170
+ const trimmed = line.trim();
34171
+ if (!trimmed)
34172
+ continue;
34173
+ let obj;
34174
+ try {
34175
+ obj = JSON.parse(trimmed);
34176
+ } catch {
34177
+ continue;
34178
+ }
34179
+ if (typeof obj !== "object" || obj === null)
34180
+ continue;
34181
+ const record2 = obj;
34182
+ const ts = record2.timestamp;
34183
+ if (typeof ts !== "string" || !ts)
34184
+ continue;
34185
+ const tsMs = new Date(ts).getTime();
34186
+ if (!Number.isFinite(tsMs) || tsMs < startMs || tsMs > endMs)
34187
+ continue;
34188
+ const classified = classifyRecord(record2, tsMs);
34189
+ if (classified)
34190
+ newest = pickNewerTurnFailure(newest, classified);
34191
+ if (!isErrorShaped(record2) || record2.isSidechain === true)
34192
+ continue;
34193
+ coarse++;
34194
+ if (isShapeRecognised(record2)) {
34195
+ fine++;
34196
+ continue;
34197
+ }
34198
+ if (keys.size < maxKeys) {
34199
+ for (const key2 of Object.keys(record2)) {
34200
+ if (keys.size >= maxKeys)
34201
+ break;
34202
+ keys.add(key2);
34203
+ }
34204
+ }
34205
+ }
34206
+ return { result: newest, coarse, fine, unrecognisedKeys: [...keys].sort() };
34207
+ }
34208
+
34055
34209
  // ../core/dist/claude-code-usage/transcript-location.js
34056
34210
  function encodeClaudeProjectPath(projectDir) {
34057
34211
  return "-" + projectDir.replace(/^\//, "").replace(/[/.]/g, "-");
@@ -34908,7 +35062,7 @@ var FLAG_REGISTRY = [
34908
35062
  },
34909
35063
  {
34910
35064
  key: "wedge-transient-notice",
34911
- description: 'When a wedge-respawn was preceded by a transient LLM-API error (529/429/503/500 that exhausted its retries and wedged the turn), the manager writes the ENG-6058 give-up signal tagged reason=transient_overload so the channel sweeps post a friendly "I hit a brief overload \u2014 please resend" notice instead of leaving the user in silence (ENG-7360, extends ENG-6861 to the retry-exhaustion + wedge path). Boolean gate; ships dark \u2014 channel-visible copy soaks per host before going wide.',
35065
+ description: `Tell the person waiting when their turn dies on a transient LLM-API failure (529 overloaded / 5xx). TWO consumers now share this gate. (1) ENG-7360: a wedge-respawn preceded by such an error writes the ENG-6058 give-up signal tagged reason=transient_overload so the channel sweeps post a "please resend" notice. (2) ENG-8269: the channel MCPs watch the dispatched turn in Claude Code's own transcript and notify the conversation that was actually waiting \u2014 Slack, Telegram AND direct chat \u2014 plus a "still working on this" notice after ~3min on Slack/Telegram only (direct chat already shows a client-side one at 90s). NOTE: (2) fires on a MUCH larger population than (1) \u2014 any dispatched turn that dies, not only one that also wedged the session \u2014 and (1) has never actually been able to fire, because its pane.log detector cannot match the banner Claude Code renders today. So flipping this on is in practice enabling (2) for the first time. Boolean gate; ships dark \u2014 channel-visible copy soaks per host before going wide. Materialized into the channel-MCP spawn env: a Docker-isolated agent never mounts the host flags-cache, so a central flip reaches it only that way.`,
34912
35066
  flagType: "boolean",
34913
35067
  defaultValue: false,
34914
35068
  envVar: "AGT_WEDGE_TRANSIENT_NOTICE_ENABLED"
@@ -35402,6 +35556,22 @@ var FLAG_REGISTRY = [
35402
35556
  // registry-only (ADR-0022). NOT `public`: it is resolved server-side only, so it
35403
35557
  // must never be serialized into the browser map.
35404
35558
  defaultValue: false
35559
+ },
35560
+ {
35561
+ key: "ninjafy-brand",
35562
+ description: 'Present the product under the Ninjafy brand instead of Augmented Team (ENG-8250). This is the UMBRELLA brand gate, not a one-off nav toggle: every subsequent rebrand surface (page titles, email templates, marketing-facing copy) reads THIS key rather than adding its own flag, so the whole rebrand keeps a single kill switch. First surface is the left-hand nav wordmark \u2014 ON replaces the human+robot mark and the "augmented.team" text with italic lowercase "ninjafy"; OFF renders exactly what shipped before. Scope is USER-FACING BRAND TEXT ONLY: it must never gate a code identifier, package name, env var or CLI name, which stay `Augmented`/`agt` per the CLAUDE.md naming contract (the deep code rename is workstream C of docs/runbooks/rebrand-ninjafy-migration.md and is out of scope here). Set the stage-wide default to flip a whole environment, or add a feature_flag_overrides row to pilot one organization while every other org still sees Augmented. Ships dark.',
35563
+ flagType: "boolean",
35564
+ // Declared safe value is `false`: the pre-rebrand brand. `false` is also the
35565
+ // fail-closed direction — if the flag DB is unreachable we must show the brand
35566
+ // that is currently live and contractually correct, never leak an unannounced
35567
+ // rebrand to every customer at once.
35568
+ defaultValue: false,
35569
+ // Read CLIENT-SIDE: sidebar.tsx is a "use client" component and resolves this
35570
+ // via usePublicBooleanFlag, so the key must be in the browser-exposed public
35571
+ // map. Unlike onboarding-msteams-channel above there is no wrong-org hazard —
35572
+ // the sidebar renders inside the active-org cookie's scope, which is exactly
35573
+ // the org whose brand should be shown.
35574
+ public: true
35405
35575
  }
35406
35576
  ];
35407
35577
  var REGISTRY_BY_KEY = new Map(FLAG_REGISTRY.map((definition) => [definition.key, definition]));
@@ -35715,6 +35885,12 @@ function giveUpNoticeText(reason = null) {
35715
35885
  }
35716
35886
  return "\u26A0\uFE0F I couldn't read your last message \u2014 please resend it.";
35717
35887
  }
35888
+ function turnFailedNoticeText() {
35889
+ return "\u26A0\uFE0F Something went wrong on my side and I couldn\u2019t finish that. Sorry \u2014 send it again when you get a chance and I\u2019ll pick it straight up.";
35890
+ }
35891
+ function turnRetryingNoticeText() {
35892
+ return "\u23F3 Still working on this \u2014 things are running slower than usual on my side. I\u2019ll reply as soon as I have it.";
35893
+ }
35718
35894
  function oldestPendingMarkerAgeMs(dir, now = Date.now(), opts) {
35719
35895
  if (!dir) return null;
35720
35896
  let names;
@@ -36690,6 +36866,149 @@ async function watchForRateLimitRefusal(opts) {
36690
36866
  }
36691
36867
  }
36692
36868
 
36869
+ // src/turn-failure-watch.ts
36870
+ import { readFileSync as readFileSync7, readdirSync as readdirSync3, statSync as statSync2 } from "fs";
36871
+ import { join as join8 } from "path";
36872
+ function turnFailureNoticeEnabled(env2) {
36873
+ return resolveHostBooleanFlag({
36874
+ key: "wedge-transient-notice",
36875
+ envVar: "AGT_WEDGE_TRANSIENT_NOTICE_ENABLED",
36876
+ defaultValue: false,
36877
+ ...env2 ? { env: env2 } : {}
36878
+ });
36879
+ }
36880
+ var DEFAULT_FAILURE_WATCH_MS = 5 * 6e4;
36881
+ var FAST_POLL_MS = 500;
36882
+ var SLOW_POLL_MS = 3e3;
36883
+ var FAST_PHASE_MS = 1e4;
36884
+ var RETRYING_NOTICE_AFTER_MS = 3 * 6e4;
36885
+ var sleep2 = (ms) => new Promise((resolve3) => setTimeout(resolve3, ms));
36886
+ function classifyTurnFailureSince(opts) {
36887
+ const dir = opts.transcriptDir ?? agentTranscriptDir({ cwd: opts.cwd, home: opts.home });
36888
+ let entries;
36889
+ try {
36890
+ entries = readdirSync3(dir);
36891
+ } catch {
36892
+ return { result: UNKNOWN_TURN_FAILURE, coarse: 0, fine: 0, unrecognisedKeys: [] };
36893
+ }
36894
+ let newest = UNKNOWN_TURN_FAILURE;
36895
+ let coarse = 0;
36896
+ let fine = 0;
36897
+ const unrecognised = /* @__PURE__ */ new Set();
36898
+ for (const name of entries) {
36899
+ if (!name.endsWith(".jsonl")) continue;
36900
+ const path = join8(dir, name);
36901
+ let fingerprint;
36902
+ try {
36903
+ const st = statSync2(path);
36904
+ if (!st.isFile() || st.mtimeMs < opts.sinceMs) continue;
36905
+ fingerprint = `${st.mtimeMs}:${st.size}`;
36906
+ } catch {
36907
+ continue;
36908
+ }
36909
+ const cached2 = opts.cache?.get(path);
36910
+ let scan;
36911
+ if (cached2 && cached2.fingerprint === fingerprint) {
36912
+ scan = cached2.scan;
36913
+ } else {
36914
+ let content;
36915
+ try {
36916
+ content = readFileSync7(path, "utf-8");
36917
+ } catch {
36918
+ continue;
36919
+ }
36920
+ const analysis = analyzeTranscriptTurnFailure(content, opts.sinceMs, opts.nowMs);
36921
+ scan = {
36922
+ result: analysis.result,
36923
+ coarse: analysis.coarse,
36924
+ fine: analysis.fine,
36925
+ unrecognisedKeys: analysis.unrecognisedKeys
36926
+ };
36927
+ opts.cache?.set(path, { fingerprint, scan });
36928
+ }
36929
+ newest = pickNewerTurnFailure(newest, scan.result);
36930
+ coarse += scan.coarse;
36931
+ fine += scan.fine;
36932
+ for (const key2 of scan.unrecognisedKeys) unrecognised.add(key2);
36933
+ }
36934
+ return { result: newest, coarse, fine, unrecognisedKeys: [...unrecognised].sort() };
36935
+ }
36936
+ function emitDriftTelemetryIfBlind(args) {
36937
+ if (args.coarse <= 0 || args.fine > 0) return;
36938
+ try {
36939
+ process.stderr.write(
36940
+ `agt.transcript.api_error.unclassified ${JSON.stringify({
36941
+ channel: args.channel,
36942
+ agent_code: process.env.AGT_AGENT_CODE_NAME ?? "unknown",
36943
+ coarse: args.coarse,
36944
+ keys: args.unrecognisedKeys.slice(0, 20)
36945
+ })}
36946
+ `
36947
+ );
36948
+ } catch {
36949
+ }
36950
+ }
36951
+ async function watchForTurnFailure(opts) {
36952
+ const now = opts.now ?? (() => Date.now());
36953
+ const wait = opts.wait ?? sleep2;
36954
+ const timeoutMs = opts.timeoutMs ?? DEFAULT_FAILURE_WATCH_MS;
36955
+ const fastPollMs = opts.fastPollMs ?? FAST_POLL_MS;
36956
+ const slowPollMs = opts.slowPollMs ?? SLOW_POLL_MS;
36957
+ const fastPhaseMs = opts.fastPhaseMs ?? FAST_PHASE_MS;
36958
+ const retryingAfterMs = opts.retryingNoticeAfterMs ?? RETRYING_NOTICE_AFTER_MS;
36959
+ const startedAt = now();
36960
+ const deadline = startedAt + timeoutMs;
36961
+ let longRetryFired = false;
36962
+ let driftEmitted = false;
36963
+ const cache = /* @__PURE__ */ new Map();
36964
+ for (; ; ) {
36965
+ let scan;
36966
+ try {
36967
+ scan = classifyTurnFailureSince({
36968
+ sinceMs: opts.sinceMs,
36969
+ // No upper bound, deliberately. Two reasons, and the second is a
36970
+ // correctness requirement of the cache above:
36971
+ // 1. For a post-dispatch watch the question is "has anything since the
36972
+ // dispatch killed this turn?" — a future-dated entry is still an
36973
+ // answer, and rejecting it on clock skew would drop the notice.
36974
+ // 2. A window that widens each poll makes a cached scan unsound: an
36975
+ // entry stamped a hair ahead of our clock would be parsed as
36976
+ // out-of-window, cached as "not seen", and — because a DEAD turn
36977
+ // produces no further appends to invalidate the entry — never looked
36978
+ // at again. That is exactly the silence this feature exists to end.
36979
+ nowMs: Number.POSITIVE_INFINITY,
36980
+ transcriptDir: opts.transcriptDir,
36981
+ cwd: opts.cwd,
36982
+ home: opts.home,
36983
+ cache
36984
+ });
36985
+ } catch {
36986
+ return null;
36987
+ }
36988
+ if (!driftEmitted && scan.coarse > 0 && scan.fine === 0) {
36989
+ driftEmitted = true;
36990
+ emitDriftTelemetryIfBlind({
36991
+ channel: opts.channel ?? "unknown",
36992
+ coarse: scan.coarse,
36993
+ fine: scan.fine,
36994
+ unrecognisedKeys: scan.unrecognisedKeys
36995
+ });
36996
+ }
36997
+ const { result } = scan;
36998
+ if (result.outcome === "failed") return result;
36999
+ if (result.outcome === "served") return null;
37000
+ if (result.outcome === "retrying" && !longRetryFired && opts.onLongRetry && now() - startedAt >= retryingAfterMs) {
37001
+ longRetryFired = true;
37002
+ try {
37003
+ await opts.onLongRetry(result);
37004
+ } catch {
37005
+ }
37006
+ }
37007
+ if (now() >= deadline) return null;
37008
+ await wait(now() - startedAt < fastPhaseMs ? fastPollMs : slowPollMs);
37009
+ }
37010
+ }
37011
+
36693
37012
  // src/usage-limit-reactive-decision.ts
36694
37013
  import { createHash } from "crypto";
36695
37014
  function shouldReadPredictiveMarker(mode) {
@@ -36716,7 +37035,7 @@ function describeUnparsedRefusal(text) {
36716
37035
  import { execFile as execFile2 } from "child_process";
36717
37036
  import { existsSync as existsSync3, mkdirSync, writeFileSync as writeFileSync2 } from "fs";
36718
37037
  import { homedir as homedir6 } from "os";
36719
- import { join as join8 } from "path";
37038
+ import { join as join9 } from "path";
36720
37039
  var DEFAULT_CLAUDE_EVAL_MODEL = "claude-haiku-4-5-20251001";
36721
37040
  var DEFAULT_ANTHROPIC_MESSAGES_URL = "https://api.anthropic.com/v1/messages";
36722
37041
  var ANTHROPIC_API_VERSION = "2023-06-01";
@@ -36815,12 +37134,12 @@ async function runAnthropicMessages(prompt, opts) {
36815
37134
  var emptyMcpConfigPath = null;
36816
37135
  function ensureEmptyMcpConfig() {
36817
37136
  if (emptyMcpConfigPath && existsSync3(emptyMcpConfigPath)) return emptyMcpConfigPath;
36818
- const dir = join8(homedir6(), ".augmented");
37137
+ const dir = join9(homedir6(), ".augmented");
36819
37138
  try {
36820
37139
  mkdirSync(dir, { recursive: true });
36821
37140
  } catch {
36822
37141
  }
36823
- const p2 = join8(dir, ".reply-intent-empty-mcp.json");
37142
+ const p2 = join9(dir, ".reply-intent-empty-mcp.json");
36824
37143
  writeFileSync2(p2, JSON.stringify({ mcpServers: {} }));
36825
37144
  emptyMcpConfigPath = p2;
36826
37145
  return p2;
@@ -36967,8 +37286,8 @@ function emitTransientApiErrorTelemetry(channel, match, original) {
36967
37286
  }
36968
37287
 
36969
37288
  // src/slack-pending-inbound-cleanup.ts
36970
- import { existsSync as existsSync4, readdirSync as readdirSync3, statSync as statSync2, unlinkSync } from "fs";
36971
- import { join as join9 } from "path";
37289
+ import { existsSync as existsSync4, readdirSync as readdirSync4, statSync as statSync3, unlinkSync } from "fs";
37290
+ import { join as join10 } from "path";
36972
37291
  function sanitizeMarkerSegment(value) {
36973
37292
  return value.replace(/[^A-Za-z0-9_-]/g, "_");
36974
37293
  }
@@ -36982,9 +37301,9 @@ function applyToMatchingMarkers(dir, prefix, suffix, op) {
36982
37301
  if (!dir) return 0;
36983
37302
  let applied = 0;
36984
37303
  try {
36985
- for (const f of readdirSync3(dir)) {
37304
+ for (const f of readdirSync4(dir)) {
36986
37305
  if (!f.startsWith(prefix) || !f.endsWith(suffix)) continue;
36987
- op(join9(dir, f));
37306
+ op(join10(dir, f));
36988
37307
  applied += 1;
36989
37308
  }
36990
37309
  } catch {
@@ -37013,11 +37332,11 @@ function clearOldestSlackPendingMarkerInChannel(dir, channel, clear = defaultCle
37013
37332
  if (!dir) return null;
37014
37333
  const channelPrefix = `${sanitizeMarkerSegment(channel)}__`;
37015
37334
  try {
37016
- const entries = readdirSync3(dir).filter((f) => f.startsWith(channelPrefix) && f.endsWith(".json")).map((f) => {
37017
- const full = join9(dir, f);
37335
+ const entries = readdirSync4(dir).filter((f) => f.startsWith(channelPrefix) && f.endsWith(".json")).map((f) => {
37336
+ const full = join10(dir, f);
37018
37337
  let mtime = 0;
37019
37338
  try {
37020
- mtime = statSync2(full).mtimeMs;
37339
+ mtime = statSync3(full).mtimeMs;
37021
37340
  } catch {
37022
37341
  }
37023
37342
  return { name: f, full, mtime };
@@ -37033,12 +37352,12 @@ function clearOldestSlackPendingMarkerInChannel(dir, channel, clear = defaultCle
37033
37352
 
37034
37353
  // src/recovery-ledger.ts
37035
37354
  import { existsSync as existsSync5, unlinkSync as unlinkSync2 } from "fs";
37036
- import { join as join10 } from "path";
37355
+ import { join as join11 } from "path";
37037
37356
  function recoveryLedgerEntryExists(ledgerDir, markerName, exists = (p2) => existsSync5(p2)) {
37038
37357
  if (!ledgerDir || !markerName) return false;
37039
37358
  if (markerName.includes("/") || markerName.includes("\\") || markerName.includes("..")) return false;
37040
37359
  try {
37041
- return exists(join10(ledgerDir, markerName));
37360
+ return exists(join11(ledgerDir, markerName));
37042
37361
  } catch {
37043
37362
  return false;
37044
37363
  }
@@ -37049,7 +37368,7 @@ function removeRecoveryLedgerEntry(ledgerDir, markerName, unlink = (p2) => {
37049
37368
  if (!ledgerDir || !markerName) return;
37050
37369
  if (markerName.includes("/") || markerName.includes("\\") || markerName.includes("..")) return;
37051
37370
  try {
37052
- unlink(join10(ledgerDir, markerName));
37371
+ unlink(join11(ledgerDir, markerName));
37053
37372
  } catch {
37054
37373
  }
37055
37374
  }
@@ -37286,18 +37605,18 @@ function applyHotThreadGuard(input) {
37286
37605
  }
37287
37606
 
37288
37607
  // src/slack-hot-thread-telemetry.ts
37289
- import { readFileSync as readFileSync7, writeFileSync as writeFileSync3 } from "fs";
37290
- import { join as join11 } from "path";
37608
+ import { readFileSync as readFileSync8, writeFileSync as writeFileSync3 } from "fs";
37609
+ import { join as join12 } from "path";
37291
37610
  var HOT_THREAD_CLASSIFICATION_COUNTER_SUFFIX = "-hot-thread-classifications.json";
37292
37611
  function hotThreadKey(mode, outcome, proactive) {
37293
37612
  return `${mode}|${outcome}|${proactive ? "true" : "false"}`;
37294
37613
  }
37295
37614
  function recordHotThreadClassification(agentDir, channel, classification) {
37296
37615
  if (!agentDir || !channel) return;
37297
- const path = join11(agentDir, `${channel}${HOT_THREAD_CLASSIFICATION_COUNTER_SUFFIX}`);
37616
+ const path = join12(agentDir, `${channel}${HOT_THREAD_CLASSIFICATION_COUNTER_SUFFIX}`);
37298
37617
  let counts = {};
37299
37618
  try {
37300
- const parsed = JSON.parse(readFileSync7(path, "utf-8"));
37619
+ const parsed = JSON.parse(readFileSync8(path, "utf-8"));
37301
37620
  if (parsed && typeof parsed === "object" && !Array.isArray(parsed)) {
37302
37621
  for (const [k, v] of Object.entries(parsed)) {
37303
37622
  if (typeof v === "number" && Number.isInteger(v) && v >= 0) counts[k] = v;
@@ -37314,7 +37633,7 @@ function recordHotThreadClassification(agentDir, channel, classification) {
37314
37633
  }
37315
37634
 
37316
37635
  // src/restart-confirm.ts
37317
- import { existsSync as existsSync6, mkdirSync as mkdirSync2, readFileSync as readFileSync8, renameSync, unlinkSync as unlinkSync3, writeFileSync as writeFileSync4 } from "fs";
37636
+ import { existsSync as existsSync6, mkdirSync as mkdirSync2, readFileSync as readFileSync9, renameSync, unlinkSync as unlinkSync3, writeFileSync as writeFileSync4 } from "fs";
37318
37637
  import { dirname } from "path";
37319
37638
  import { randomUUID } from "crypto";
37320
37639
  var RESTART_CONFIRM_MAX_AGE_MS = 10 * 60 * 1e3;
@@ -37340,7 +37659,7 @@ function writeRestartConfirmMarker(filePath, marker) {
37340
37659
  function readRestartConfirmMarker(filePath) {
37341
37660
  try {
37342
37661
  if (!existsSync6(filePath)) return null;
37343
- const parsed = JSON.parse(readFileSync8(filePath, "utf8"));
37662
+ const parsed = JSON.parse(readFileSync9(filePath, "utf8"));
37344
37663
  if (!parsed || typeof parsed !== "object") return null;
37345
37664
  return parsed;
37346
37665
  } catch {
@@ -37485,21 +37804,21 @@ import {
37485
37804
  ftruncateSync,
37486
37805
  mkdirSync as mkdirSync9,
37487
37806
  openSync,
37488
- readFileSync as readFileSync20,
37489
- readdirSync as readdirSync6,
37807
+ readFileSync as readFileSync21,
37808
+ readdirSync as readdirSync7,
37490
37809
  renameSync as renameSync5,
37491
- statSync as statSync4,
37810
+ statSync as statSync5,
37492
37811
  unlinkSync as unlinkSync7,
37493
37812
  watch,
37494
37813
  writeFileSync as writeFileSync14,
37495
37814
  writeSync
37496
37815
  } from "fs";
37497
- import { basename, join as join20, resolve as resolve2 } from "path";
37816
+ import { basename, join as join21, resolve as resolve2 } from "path";
37498
37817
  import { homedir as homedir7 } from "os";
37499
37818
  import { createHash as createHash3, randomUUID as randomUUID2 } from "crypto";
37500
37819
 
37501
37820
  // src/slack-thread-store.ts
37502
- import { mkdirSync as mkdirSync3, readFileSync as readFileSync9, writeFileSync as writeFileSync5 } from "fs";
37821
+ import { mkdirSync as mkdirSync3, readFileSync as readFileSync10, writeFileSync as writeFileSync5 } from "fs";
37503
37822
  import { dirname as dirname2 } from "path";
37504
37823
  function isParticipatingThread(entry) {
37505
37824
  if (!entry) return false;
@@ -37514,7 +37833,7 @@ function loadThreadStore(filePath, opts = {}) {
37514
37833
  const ttlMs = ttlDays * 24 * 60 * 60 * 1e3;
37515
37834
  let raw;
37516
37835
  try {
37517
- raw = readFileSync9(filePath, "utf-8");
37836
+ raw = readFileSync10(filePath, "utf-8");
37518
37837
  } catch {
37519
37838
  return { threads: /* @__PURE__ */ new Map(), pruned: 0 };
37520
37839
  }
@@ -37615,7 +37934,7 @@ function isThreadEntry(value) {
37615
37934
  }
37616
37935
 
37617
37936
  // src/dm-restart-notice.ts
37618
- import { mkdirSync as mkdirSync4, readFileSync as readFileSync10, unlinkSync as unlinkSync4, writeFileSync as writeFileSync6 } from "fs";
37937
+ import { mkdirSync as mkdirSync4, readFileSync as readFileSync11, unlinkSync as unlinkSync4, writeFileSync as writeFileSync6 } from "fs";
37619
37938
  import { dirname as dirname3 } from "path";
37620
37939
  var RECENT_DM_VERSION = 1;
37621
37940
  var DEFAULT_RECENT_DM_TTL_MS = 30 * 60 * 1e3;
@@ -37639,7 +37958,7 @@ function loadRecentDms(filePath, opts = {}) {
37639
37958
  const ttlMs = opts.ttlMs ?? DEFAULT_RECENT_DM_TTL_MS;
37640
37959
  let raw;
37641
37960
  try {
37642
- raw = readFileSync10(filePath, "utf-8");
37961
+ raw = readFileSync11(filePath, "utf-8");
37643
37962
  } catch (err) {
37644
37963
  const code = err.code;
37645
37964
  if (code !== "ENOENT") {
@@ -37724,7 +38043,7 @@ var CHANNEL_ADD_RESTART_MAX_AGE_MS = 15 * 60 * 1e3;
37724
38043
  function readChannelAddRestartMarker(filePath) {
37725
38044
  let raw;
37726
38045
  try {
37727
- raw = readFileSync10(filePath, "utf-8");
38046
+ raw = readFileSync11(filePath, "utf-8");
37728
38047
  } catch {
37729
38048
  return null;
37730
38049
  }
@@ -37782,13 +38101,13 @@ async function runOrRetry(fn, opts) {
37782
38101
  }
37783
38102
 
37784
38103
  // src/turn-initiator-marker.ts
37785
- import { writeFileSync as writeFileSync7, readFileSync as readFileSync11, mkdirSync as mkdirSync5, renameSync as renameSync2 } from "fs";
37786
- import { dirname as dirname4, join as join12 } from "path";
38104
+ import { writeFileSync as writeFileSync7, readFileSync as readFileSync12, mkdirSync as mkdirSync5, renameSync as renameSync2 } from "fs";
38105
+ import { dirname as dirname4, join as join13 } from "path";
37787
38106
  var TURN_INITIATOR_MAX_AGE_MS = 5 * 60 * 1e3;
37788
38107
  var TURN_INITIATOR_LEDGER_MAX_ENTRIES = 20;
37789
38108
  var TURN_INITIATOR_LEDGER_FILENAME = ".turn-initiator-ledger.json";
37790
38109
  function turnInitiatorLedgerPath(singleSlotFile) {
37791
- return join12(dirname4(singleSlotFile), TURN_INITIATOR_LEDGER_FILENAME);
38110
+ return join13(dirname4(singleSlotFile), TURN_INITIATOR_LEDGER_FILENAME);
37792
38111
  }
37793
38112
  function foldTurnInitiatorLedger(existing, marker, maxAgeMs = TURN_INITIATOR_MAX_AGE_MS, maxEntries = TURN_INITIATOR_LEDGER_MAX_ENTRIES) {
37794
38113
  const now = marker.ts;
@@ -37813,7 +38132,7 @@ function updateTurnInitiatorLedger(singleSlotFile, marker) {
37813
38132
  const ledgerFile = turnInitiatorLedgerPath(singleSlotFile);
37814
38133
  let existing = null;
37815
38134
  try {
37816
- const parsed = JSON.parse(readFileSync11(ledgerFile, "utf8"));
38135
+ const parsed = JSON.parse(readFileSync12(ledgerFile, "utf8"));
37817
38136
  if (parsed && parsed.v === 1 && Array.isArray(parsed.entries)) existing = parsed;
37818
38137
  } catch {
37819
38138
  }
@@ -37840,7 +38159,7 @@ function writeTurnInitiatorMarker(input) {
37840
38159
  }
37841
38160
 
37842
38161
  // src/slack-bot-photo.ts
37843
- import { existsSync as existsSync7, mkdirSync as mkdirSync6, readFileSync as readFileSync12, writeFileSync as writeFileSync8 } from "fs";
38162
+ import { existsSync as existsSync7, mkdirSync as mkdirSync6, readFileSync as readFileSync13, writeFileSync as writeFileSync8 } from "fs";
37844
38163
  import { dirname as dirname5 } from "path";
37845
38164
  async function applyBotPhoto(opts) {
37846
38165
  const fetchImpl = opts.fetchImpl ?? fetch;
@@ -37850,7 +38169,7 @@ async function applyBotPhoto(opts) {
37850
38169
  const { token, avatarUrl, markerPath } = opts;
37851
38170
  if (markerPath && existsSync7(markerPath)) {
37852
38171
  try {
37853
- if (readFileSync12(markerPath, "utf-8").trim() === avatarUrl) {
38172
+ if (readFileSync13(markerPath, "utf-8").trim() === avatarUrl) {
37854
38173
  return { status: "skipped-unchanged" };
37855
38174
  }
37856
38175
  } catch {
@@ -37975,8 +38294,8 @@ function conversationalLaneMeta(expectsReply = true) {
37975
38294
  }
37976
38295
 
37977
38296
  // src/inbound-lane-telemetry.ts
37978
- import { readFileSync as readFileSync13, writeFileSync as writeFileSync9 } from "fs";
37979
- import { join as join13 } from "path";
38297
+ import { readFileSync as readFileSync14, writeFileSync as writeFileSync9 } from "fs";
38298
+ import { join as join14 } from "path";
37980
38299
  var LANE_CLASSIFICATION_COUNTER_SUFFIX = "-lane-classifications.json";
37981
38300
  var SUSPECTED_MISCLASSIFICATION_KEY = "suspected_misclassification";
37982
38301
  var HUMAN_CHANNEL_SOURCES = /* @__PURE__ */ new Set([
@@ -37993,10 +38312,10 @@ function isSuspectedMisclassification(lane, source) {
37993
38312
  }
37994
38313
  function recordLaneClassification(agentDir, channel, classification) {
37995
38314
  if (!agentDir) return;
37996
- const path = join13(agentDir, `${channel}${LANE_CLASSIFICATION_COUNTER_SUFFIX}`);
38315
+ const path = join14(agentDir, `${channel}${LANE_CLASSIFICATION_COUNTER_SUFFIX}`);
37997
38316
  let counts = {};
37998
38317
  try {
37999
- const parsed = JSON.parse(readFileSync13(path, "utf-8"));
38318
+ const parsed = JSON.parse(readFileSync14(path, "utf-8"));
38000
38319
  if (parsed && typeof parsed === "object") counts = parsed;
38001
38320
  } catch {
38002
38321
  }
@@ -38012,8 +38331,8 @@ function recordLaneClassification(agentDir, channel, classification) {
38012
38331
  }
38013
38332
 
38014
38333
  // src/slack-inbound-registry.ts
38015
- import { readdirSync as readdirSync4, readFileSync as readFileSync14 } from "fs";
38016
- import { join as join14 } from "path";
38334
+ import { readdirSync as readdirSync5, readFileSync as readFileSync15 } from "fs";
38335
+ import { join as join15 } from "path";
38017
38336
  var DEFAULT_MAX_ENTRIES = 500;
38018
38337
  var DEFAULT_CLEARED_TTL_MS = 6 * 60 * 60 * 1e3;
38019
38338
  function entryKey(channel, messageTs) {
@@ -38125,7 +38444,7 @@ function createInboundRegistry(opts = {}) {
38125
38444
  function seedFromMarkerDir(dir) {
38126
38445
  let names;
38127
38446
  try {
38128
- names = readdirSync4(dir);
38447
+ names = readdirSync5(dir);
38129
38448
  } catch {
38130
38449
  return;
38131
38450
  }
@@ -38134,7 +38453,7 @@ function createInboundRegistry(opts = {}) {
38134
38453
  if (name.includes(".retry-") || name.includes(".poison")) continue;
38135
38454
  let marker;
38136
38455
  try {
38137
- marker = JSON.parse(readFileSync14(join14(dir, name), "utf-8"));
38456
+ marker = JSON.parse(readFileSync15(join15(dir, name), "utf-8"));
38138
38457
  } catch {
38139
38458
  continue;
38140
38459
  }
@@ -38179,8 +38498,8 @@ function slackInboundId(channel, threadTs, messageTs) {
38179
38498
  }
38180
38499
 
38181
38500
  // src/inbound-delivery-ledger.ts
38182
- import { existsSync as existsSync8, mkdirSync as mkdirSync7, readdirSync as readdirSync5, readFileSync as readFileSync15, renameSync as renameSync3, writeFileSync as writeFileSync10 } from "fs";
38183
- import { join as join15 } from "path";
38501
+ import { existsSync as existsSync8, mkdirSync as mkdirSync7, readdirSync as readdirSync6, readFileSync as readFileSync16, renameSync as renameSync3, writeFileSync as writeFileSync10 } from "fs";
38502
+ import { join as join16 } from "path";
38184
38503
  function safeInboundId(inboundId) {
38185
38504
  return inboundId.replace(/[^A-Za-z0-9_-]/g, "_");
38186
38505
  }
@@ -38188,8 +38507,8 @@ var defaultDeps = {
38188
38507
  mkdir: (dir) => mkdirSync7(dir, { recursive: true }),
38189
38508
  writeFile: (path, data) => writeFileSync10(path, data, "utf8"),
38190
38509
  rename: (from, to) => renameSync3(from, to),
38191
- readdir: (dir) => readdirSync5(dir),
38192
- readFile: (path) => readFileSync15(path, "utf8"),
38510
+ readdir: (dir) => readdirSync6(dir),
38511
+ readFile: (path) => readFileSync16(path, "utf8"),
38193
38512
  exists: (path) => existsSync8(path)
38194
38513
  };
38195
38514
  function writeInboundDeliveryLedgerEntry(dir, record2, deps = defaultDeps) {
@@ -38198,7 +38517,7 @@ function writeInboundDeliveryLedgerEntry(dir, record2, deps = defaultDeps) {
38198
38517
  if (!safe || safe.includes("/") || safe.includes("\\") || safe.includes("..")) return;
38199
38518
  try {
38200
38519
  deps.mkdir(dir);
38201
- const final = join15(dir, `${safe}.json`);
38520
+ const final = join16(dir, `${safe}.json`);
38202
38521
  const tmp = `${final}.tmp`;
38203
38522
  deps.writeFile(tmp, JSON.stringify(record2));
38204
38523
  deps.rename(tmp, final);
@@ -38354,8 +38673,8 @@ function describeChannelRedirect(input) {
38354
38673
  }
38355
38674
 
38356
38675
  // src/slack-reply-binding-telemetry.ts
38357
- import { readFileSync as readFileSync16, writeFileSync as writeFileSync11 } from "fs";
38358
- import { join as join16 } from "path";
38676
+ import { readFileSync as readFileSync17, writeFileSync as writeFileSync11 } from "fs";
38677
+ import { join as join17 } from "path";
38359
38678
  var REPLY_BINDING_CLASSIFICATION_COUNTER_SUFFIX = "-reply-binding-classifications.json";
38360
38679
  var UNKNOWN_INBOUND_ID_KEY = "unknown_inbound_id";
38361
38680
  var CHANNEL_MISTARGET_CORRECTED_KEY = "channel_mistarget_corrected";
@@ -38370,10 +38689,10 @@ function slackReplyBindingMode() {
38370
38689
  }
38371
38690
  function recordReplyBindingClassification(agentDir, channel, input) {
38372
38691
  if (!agentDir) return;
38373
- const path = join16(agentDir, `${channel}${REPLY_BINDING_CLASSIFICATION_COUNTER_SUFFIX}`);
38692
+ const path = join17(agentDir, `${channel}${REPLY_BINDING_CLASSIFICATION_COUNTER_SUFFIX}`);
38374
38693
  let counts = {};
38375
38694
  try {
38376
- const parsed = JSON.parse(readFileSync16(path, "utf-8"));
38695
+ const parsed = JSON.parse(readFileSync17(path, "utf-8"));
38377
38696
  if (parsed && typeof parsed === "object") counts = parsed;
38378
38697
  } catch {
38379
38698
  }
@@ -38388,10 +38707,10 @@ function recordReplyBindingClassification(agentDir, channel, input) {
38388
38707
  }
38389
38708
  function recordScheduledChannelOverride(agentDir, channel, input) {
38390
38709
  if (!agentDir) return;
38391
- const path = join16(agentDir, `${channel}${REPLY_BINDING_CLASSIFICATION_COUNTER_SUFFIX}`);
38710
+ const path = join17(agentDir, `${channel}${REPLY_BINDING_CLASSIFICATION_COUNTER_SUFFIX}`);
38392
38711
  let counts = {};
38393
38712
  try {
38394
- const parsed = JSON.parse(readFileSync16(path, "utf-8"));
38713
+ const parsed = JSON.parse(readFileSync17(path, "utf-8"));
38395
38714
  if (parsed && typeof parsed === "object") counts = parsed;
38396
38715
  } catch {
38397
38716
  }
@@ -38404,10 +38723,10 @@ function recordScheduledChannelOverride(agentDir, channel, input) {
38404
38723
  }
38405
38724
  function recordChannelMistarget(agentDir, channel, input) {
38406
38725
  if (!agentDir) return;
38407
- const path = join16(agentDir, `${channel}${REPLY_BINDING_CLASSIFICATION_COUNTER_SUFFIX}`);
38726
+ const path = join17(agentDir, `${channel}${REPLY_BINDING_CLASSIFICATION_COUNTER_SUFFIX}`);
38408
38727
  let counts = {};
38409
38728
  try {
38410
- const parsed = JSON.parse(readFileSync16(path, "utf-8"));
38729
+ const parsed = JSON.parse(readFileSync17(path, "utf-8"));
38411
38730
  if (parsed && typeof parsed === "object") counts = parsed;
38412
38731
  } catch {
38413
38732
  }
@@ -38422,8 +38741,8 @@ function recordChannelMistarget(agentDir, channel, input) {
38422
38741
  }
38423
38742
 
38424
38743
  // src/slack-reply-target-telemetry.ts
38425
- import { readFileSync as readFileSync17, writeFileSync as writeFileSync12 } from "fs";
38426
- import { join as join17 } from "path";
38744
+ import { readFileSync as readFileSync18, writeFileSync as writeFileSync12 } from "fs";
38745
+ import { join as join18 } from "path";
38427
38746
  var REPLY_TARGET_CLASSIFICATION_COUNTER_SUFFIX = "-reply-target-classifications.json";
38428
38747
  function pendingThreadsBucket(n) {
38429
38748
  if (n <= 0) return "0";
@@ -38456,10 +38775,10 @@ function classifyReplyTarget(input) {
38456
38775
  }
38457
38776
  function recordReplyTargetClassification(agentDir, channel, classification) {
38458
38777
  if (!agentDir) return;
38459
- const path = join17(agentDir, `${channel}${REPLY_TARGET_CLASSIFICATION_COUNTER_SUFFIX}`);
38778
+ const path = join18(agentDir, `${channel}${REPLY_TARGET_CLASSIFICATION_COUNTER_SUFFIX}`);
38460
38779
  let counts = {};
38461
38780
  try {
38462
- const parsed = JSON.parse(readFileSync17(path, "utf-8"));
38781
+ const parsed = JSON.parse(readFileSync18(path, "utf-8"));
38463
38782
  if (parsed && typeof parsed === "object") counts = parsed;
38464
38783
  } catch {
38465
38784
  }
@@ -38475,8 +38794,8 @@ function recordReplyTargetClassification(agentDir, channel, classification) {
38475
38794
  }
38476
38795
 
38477
38796
  // src/scheduled-turn-marker.ts
38478
- import { readFileSync as readFileSync18, unlinkSync as unlinkSync5 } from "fs";
38479
- import { join as join18 } from "path";
38797
+ import { readFileSync as readFileSync19, unlinkSync as unlinkSync5 } from "fs";
38798
+ import { join as join19 } from "path";
38480
38799
  var SCHEDULED_TURN_MARKER_FILENAME2 = ".current-scheduled-turn.json";
38481
38800
  var SCHEDULED_TURN_MAX_AGE_MS = 15 * 60 * 1e3;
38482
38801
  function validateScheduledTurnMarker(raw, now, maxAgeMs = SCHEDULED_TURN_MAX_AGE_MS) {
@@ -38514,7 +38833,7 @@ function readScheduledTurnMarker(agentDir, now = Date.now()) {
38514
38833
  if (!agentDir) return null;
38515
38834
  try {
38516
38835
  const raw = JSON.parse(
38517
- readFileSync18(join18(agentDir, SCHEDULED_TURN_MARKER_FILENAME2), "utf8")
38836
+ readFileSync19(join19(agentDir, SCHEDULED_TURN_MARKER_FILENAME2), "utf8")
38518
38837
  );
38519
38838
  return validateScheduledTurnMarker(raw, now);
38520
38839
  } catch {
@@ -38524,7 +38843,7 @@ function readScheduledTurnMarker(agentDir, now = Date.now()) {
38524
38843
  function clearScheduledTurnMarker(agentDir) {
38525
38844
  if (!agentDir) return;
38526
38845
  try {
38527
- unlinkSync5(join18(agentDir, SCHEDULED_TURN_MARKER_FILENAME2));
38846
+ unlinkSync5(join19(agentDir, SCHEDULED_TURN_MARKER_FILENAME2));
38528
38847
  } catch {
38529
38848
  }
38530
38849
  }
@@ -39322,14 +39641,14 @@ async function actuateHostRestart(opts) {
39322
39641
  import {
39323
39642
  existsSync as existsSync9,
39324
39643
  mkdirSync as mkdirSync8,
39325
- readFileSync as readFileSync19,
39644
+ readFileSync as readFileSync20,
39326
39645
  renameSync as renameSync4,
39327
- statSync as statSync3,
39646
+ statSync as statSync4,
39328
39647
  unlinkSync as unlinkSync6,
39329
39648
  utimesSync,
39330
39649
  writeFileSync as writeFileSync13
39331
39650
  } from "fs";
39332
- import { join as join19 } from "path";
39651
+ import { join as join20 } from "path";
39333
39652
  var STALE_LOCK_MS = 9e4;
39334
39653
  var HEARTBEAT_INTERVAL_MS = 3e4;
39335
39654
  function defaultIsPidAlive(pid) {
@@ -39352,7 +39671,7 @@ function acquireMcpSpawnLock(args) {
39352
39671
  const nowMs = options.nowMs ?? (() => Date.now());
39353
39672
  const lockMtimeMs = options.lockMtimeMs ?? defaultLockMtimeMs;
39354
39673
  const staleMs = options.staleMs ?? STALE_LOCK_MS;
39355
- const path = join19(agentDir, basename2);
39674
+ const path = join20(agentDir, basename2);
39356
39675
  const existing = readLockHolder(path);
39357
39676
  if (existing) {
39358
39677
  if (existing.pid === selfPid) {
@@ -39411,7 +39730,7 @@ function startMcpSpawnLockHeartbeat(lockPath, opts = {}) {
39411
39730
  }
39412
39731
  function defaultLockMtimeMs(path) {
39413
39732
  try {
39414
- return statSync3(path).mtimeMs;
39733
+ return statSync4(path).mtimeMs;
39415
39734
  } catch {
39416
39735
  return null;
39417
39736
  }
@@ -39419,7 +39738,7 @@ function defaultLockMtimeMs(path) {
39419
39738
  function readLockHolder(path) {
39420
39739
  if (!existsSync9(path)) return null;
39421
39740
  try {
39422
- const raw = readFileSync19(path, "utf8");
39741
+ const raw = readFileSync20(path, "utf8");
39423
39742
  const parsed = JSON.parse(raw);
39424
39743
  const pid = typeof parsed.pid === "number" ? parsed.pid : Number(parsed.pid);
39425
39744
  if (!Number.isFinite(pid) || pid <= 0) return null;
@@ -39868,18 +40187,18 @@ var SLACK_TEAM_PEER_USER_IDS = parseTeamPeerUserIdsEnv(
39868
40187
  process.env.SLACK_TEAM_PEER_USER_IDS
39869
40188
  );
39870
40189
  var PEER_HINT_SEEN = /* @__PURE__ */ new Set();
39871
- var SLACK_AGENT_DIR = AGENT_CODE_NAME ? join20(homedir7(), ".augmented", AGENT_CODE_NAME) : null;
39872
- var SLACK_MCP_CONFIG_PATH = SLACK_AGENT_DIR ? join20(SLACK_AGENT_DIR, "project", ".mcp.json") : null;
40190
+ var SLACK_AGENT_DIR = AGENT_CODE_NAME ? join21(homedir7(), ".augmented", AGENT_CODE_NAME) : null;
40191
+ var SLACK_MCP_CONFIG_PATH = SLACK_AGENT_DIR ? join21(SLACK_AGENT_DIR, "project", ".mcp.json") : null;
39873
40192
  var liveAllowedUsersCache = null;
39874
40193
  function readLiveAllowedUsers() {
39875
40194
  if (!SLACK_MCP_CONFIG_PATH) return null;
39876
40195
  try {
39877
- const mtimeMs = statSync4(SLACK_MCP_CONFIG_PATH).mtimeMs;
40196
+ const mtimeMs = statSync5(SLACK_MCP_CONFIG_PATH).mtimeMs;
39878
40197
  if (liveAllowedUsersCache && liveAllowedUsersCache.mtimeMs === mtimeMs) {
39879
40198
  return liveAllowedUsersCache.value;
39880
40199
  }
39881
40200
  const value = extractAllowedUsersFromMcpJson(
39882
- readFileSync20(SLACK_MCP_CONFIG_PATH, "utf-8")
40201
+ readFileSync21(SLACK_MCP_CONFIG_PATH, "utf-8")
39883
40202
  );
39884
40203
  if (value === null) return null;
39885
40204
  liveAllowedUsersCache = { mtimeMs, value };
@@ -39895,12 +40214,12 @@ var livePingAllowedUsersCache = null;
39895
40214
  function readLivePingAllowedUsers() {
39896
40215
  if (!SLACK_MCP_CONFIG_PATH) return null;
39897
40216
  try {
39898
- const mtimeMs = statSync4(SLACK_MCP_CONFIG_PATH).mtimeMs;
40217
+ const mtimeMs = statSync5(SLACK_MCP_CONFIG_PATH).mtimeMs;
39899
40218
  if (livePingAllowedUsersCache && livePingAllowedUsersCache.mtimeMs === mtimeMs) {
39900
40219
  return livePingAllowedUsersCache.value;
39901
40220
  }
39902
40221
  const value = extractPingAllowedUsersFromMcpJson(
39903
- readFileSync20(SLACK_MCP_CONFIG_PATH, "utf-8")
40222
+ readFileSync21(SLACK_MCP_CONFIG_PATH, "utf-8")
39904
40223
  );
39905
40224
  if (value === null) return null;
39906
40225
  livePingAllowedUsersCache = { mtimeMs, value };
@@ -39912,17 +40231,17 @@ function readLivePingAllowedUsers() {
39912
40231
  function getEffectivePingAllowedUsers() {
39913
40232
  return readLivePingAllowedUsers() ?? PING_ALLOWED_USERS;
39914
40233
  }
39915
- var SLACK_PENDING_INBOUND_DIR = SLACK_AGENT_DIR ? join20(SLACK_AGENT_DIR, "slack-pending-inbound") : null;
39916
- var SLACK_RESTART_CONTEXT_DIR = SLACK_AGENT_DIR ? join20(SLACK_AGENT_DIR, "slack-restart-context") : null;
39917
- var SLACK_RECOVERY_OUTBOX_DIR = SLACK_AGENT_DIR ? join20(SLACK_AGENT_DIR, "slack-recovery-outbox") : null;
39918
- var SLACK_RECOVERY_LEDGER_DIR = SLACK_AGENT_DIR ? join20(SLACK_AGENT_DIR, ".agt-slack-recovery-ledger") : null;
39919
- var SLACK_DELIVERY_LEDGER_DIR = SLACK_AGENT_DIR ? join20(SLACK_AGENT_DIR, ".agt-inbound-delivery-ledger") : null;
40234
+ var SLACK_PENDING_INBOUND_DIR = SLACK_AGENT_DIR ? join21(SLACK_AGENT_DIR, "slack-pending-inbound") : null;
40235
+ var SLACK_RESTART_CONTEXT_DIR = SLACK_AGENT_DIR ? join21(SLACK_AGENT_DIR, "slack-restart-context") : null;
40236
+ var SLACK_RECOVERY_OUTBOX_DIR = SLACK_AGENT_DIR ? join21(SLACK_AGENT_DIR, "slack-recovery-outbox") : null;
40237
+ var SLACK_RECOVERY_LEDGER_DIR = SLACK_AGENT_DIR ? join21(SLACK_AGENT_DIR, ".agt-slack-recovery-ledger") : null;
40238
+ var SLACK_DELIVERY_LEDGER_DIR = SLACK_AGENT_DIR ? join21(SLACK_AGENT_DIR, ".agt-inbound-delivery-ledger") : null;
39920
40239
  var slackInboundRegistry = createInboundRegistry();
39921
- var SLACK_RESTART_CONFIRM_FILE = SLACK_AGENT_DIR ? join20(SLACK_AGENT_DIR, "slack-restart-confirm.json") : null;
39922
- var SLACK_RECENT_DMS_FILE = SLACK_AGENT_DIR ? join20(SLACK_AGENT_DIR, "slack-recent-dms.json") : null;
39923
- var SLACK_CHANNEL_ADD_RESTART_FILE = SLACK_AGENT_DIR ? join20(SLACK_AGENT_DIR, "slack-channel-add-restart.json") : null;
40240
+ var SLACK_RESTART_CONFIRM_FILE = SLACK_AGENT_DIR ? join21(SLACK_AGENT_DIR, "slack-restart-confirm.json") : null;
40241
+ var SLACK_RECENT_DMS_FILE = SLACK_AGENT_DIR ? join21(SLACK_AGENT_DIR, "slack-recent-dms.json") : null;
40242
+ var SLACK_CHANNEL_ADD_RESTART_FILE = SLACK_AGENT_DIR ? join21(SLACK_AGENT_DIR, "slack-channel-add-restart.json") : null;
39924
40243
  var SLACK_MAX_RECOVERY_ATTEMPTS = 3;
39925
- var SLACK_AVATAR_MARKER_PATH = SLACK_AGENT_DIR ? join20(SLACK_AGENT_DIR, "slack-avatar-applied") : null;
40244
+ var SLACK_AVATAR_MARKER_PATH = SLACK_AGENT_DIR ? join21(SLACK_AGENT_DIR, "slack-avatar-applied") : null;
39926
40245
  function redactSlackId(id) {
39927
40246
  if (!id) return "<none>";
39928
40247
  return createHash3("sha256").update(id).digest("hex").slice(0, 8);
@@ -39933,7 +40252,7 @@ function safeSlackMarkerName(channel, threadTs, messageTs) {
39933
40252
  }
39934
40253
  function slackPendingInboundPath(channel, threadTs, messageTs) {
39935
40254
  if (!SLACK_PENDING_INBOUND_DIR) return null;
39936
- return join20(SLACK_PENDING_INBOUND_DIR, safeSlackMarkerName(channel, threadTs, messageTs));
40255
+ return join21(SLACK_PENDING_INBOUND_DIR, safeSlackMarkerName(channel, threadTs, messageTs));
39937
40256
  }
39938
40257
  function writeSlackPendingInboundMarker(channel, threadTs, messageTs, undeliverable = false, discretionary = false, payload) {
39939
40258
  const path = slackPendingInboundPath(channel, threadTs, messageTs);
@@ -39984,7 +40303,7 @@ function rewriteSlackMarkerInPlace(path, marker) {
39984
40303
  function markSlackMarkerSeenInPlace(fullPath) {
39985
40304
  let marker;
39986
40305
  try {
39987
- marker = JSON.parse(readFileSync20(fullPath, "utf-8"));
40306
+ marker = JSON.parse(readFileSync21(fullPath, "utf-8"));
39988
40307
  } catch {
39989
40308
  return;
39990
40309
  }
@@ -39998,7 +40317,7 @@ function attachSlackReplayPayload(channel, threadTs, messageTs, payload) {
39998
40317
  if (!path) return;
39999
40318
  let marker;
40000
40319
  try {
40001
- marker = JSON.parse(readFileSync20(path, "utf-8"));
40320
+ marker = JSON.parse(readFileSync21(path, "utf-8"));
40002
40321
  } catch {
40003
40322
  return;
40004
40323
  }
@@ -40009,7 +40328,7 @@ function readSlackPendingInboundMarker(channel, threadTs, messageTs) {
40009
40328
  const path = slackPendingInboundPath(channel, threadTs, messageTs);
40010
40329
  if (!path || !existsSync10(path)) return null;
40011
40330
  try {
40012
- return JSON.parse(readFileSync20(path, "utf-8"));
40331
+ return JSON.parse(readFileSync21(path, "utf-8"));
40013
40332
  } catch {
40014
40333
  return null;
40015
40334
  }
@@ -40125,7 +40444,7 @@ function scheduleBusyAck(channel, threadTs, messageTs, isThreadReply, arrivedWhi
40125
40444
  let paneLogFreshAgeMs = null;
40126
40445
  if (SLACK_AGENT_DIR) {
40127
40446
  try {
40128
- const paneMtimeMs = statSync4(join20(SLACK_AGENT_DIR, "pane.log")).mtimeMs;
40447
+ const paneMtimeMs = statSync5(join21(SLACK_AGENT_DIR, "pane.log")).mtimeMs;
40129
40448
  paneLogFreshAgeMs = Math.max(0, Date.now() - paneMtimeMs);
40130
40449
  } catch {
40131
40450
  }
@@ -40158,7 +40477,7 @@ function __resetSlackBusyAckNoticeThrottle() {
40158
40477
  function clearSlackMarkerFileWithHeal(fullPath) {
40159
40478
  let marker = null;
40160
40479
  try {
40161
- marker = JSON.parse(readFileSync20(fullPath, "utf-8"));
40480
+ marker = JSON.parse(readFileSync21(fullPath, "utf-8"));
40162
40481
  } catch {
40163
40482
  }
40164
40483
  if (marker && decideRecoveryHeal({
@@ -40176,7 +40495,7 @@ function clearSlackMarkerFileWithHeal(fullPath) {
40176
40495
  function markSlackMarkerSeenWithHeal(fullPath) {
40177
40496
  let marker = null;
40178
40497
  try {
40179
- marker = JSON.parse(readFileSync20(fullPath, "utf-8"));
40498
+ marker = JSON.parse(readFileSync21(fullPath, "utf-8"));
40180
40499
  } catch {
40181
40500
  return;
40182
40501
  }
@@ -40245,10 +40564,10 @@ function slackNextRetryName(filename) {
40245
40564
  async function processSlackRecoveryOutboxFile(filename) {
40246
40565
  if (!SLACK_RECOVERY_OUTBOX_DIR) return;
40247
40566
  if (filename.endsWith(".poison.json") || filename.endsWith(".tmp")) return;
40248
- const fullPath = join20(SLACK_RECOVERY_OUTBOX_DIR, filename);
40567
+ const fullPath = join21(SLACK_RECOVERY_OUTBOX_DIR, filename);
40249
40568
  let payload;
40250
40569
  try {
40251
- payload = JSON.parse(readFileSync20(fullPath, "utf-8"));
40570
+ payload = JSON.parse(readFileSync21(fullPath, "utf-8"));
40252
40571
  } catch (err) {
40253
40572
  process.stderr.write(
40254
40573
  `slack-channel(${AGENT_CODE_NAME}): recovery outbox parse failed (${filename}): ${err.message}
@@ -40352,7 +40671,7 @@ async function processSlackRecoveryOutboxFile(filename) {
40352
40671
  const next = slackNextRetryName(filename);
40353
40672
  if (next) {
40354
40673
  try {
40355
- renameSync5(fullPath, join20(SLACK_RECOVERY_OUTBOX_DIR, next.next));
40674
+ renameSync5(fullPath, join21(SLACK_RECOVERY_OUTBOX_DIR, next.next));
40356
40675
  if (next.attempt >= SLACK_MAX_RECOVERY_ATTEMPTS) {
40357
40676
  process.stderr.write(
40358
40677
  `slack-channel(${AGENT_CODE_NAME}): ghost-reply recovery exhausted retries \u2014 moved to ${next.next}
@@ -40383,7 +40702,7 @@ function scanSlackRecoveryRetries() {
40383
40702
  if (!SLACK_RECOVERY_OUTBOX_DIR) return;
40384
40703
  let entries;
40385
40704
  try {
40386
- entries = readdirSync6(SLACK_RECOVERY_OUTBOX_DIR);
40705
+ entries = readdirSync7(SLACK_RECOVERY_OUTBOX_DIR);
40387
40706
  } catch {
40388
40707
  return;
40389
40708
  }
@@ -40392,7 +40711,7 @@ function scanSlackRecoveryRetries() {
40392
40711
  if (!f.includes(".retry-") || f.endsWith(".poison.json")) continue;
40393
40712
  let mtimeMs;
40394
40713
  try {
40395
- mtimeMs = statSync4(join20(SLACK_RECOVERY_OUTBOX_DIR, f)).mtimeMs;
40714
+ mtimeMs = statSync5(join21(SLACK_RECOVERY_OUTBOX_DIR, f)).mtimeMs;
40396
40715
  } catch {
40397
40716
  continue;
40398
40717
  }
@@ -40413,7 +40732,7 @@ function startSlackRecoveryOutboxWatcher() {
40413
40732
  return;
40414
40733
  }
40415
40734
  try {
40416
- for (const f of readdirSync6(SLACK_RECOVERY_OUTBOX_DIR)) {
40735
+ for (const f of readdirSync7(SLACK_RECOVERY_OUTBOX_DIR)) {
40417
40736
  if (isFirstAttemptSlackOutboxFile(f)) void processSlackRecoveryOutboxFile(f);
40418
40737
  }
40419
40738
  } catch {
@@ -40422,7 +40741,7 @@ function startSlackRecoveryOutboxWatcher() {
40422
40741
  const watcher = watch(SLACK_RECOVERY_OUTBOX_DIR, (event, filename) => {
40423
40742
  if (event !== "rename" || !filename) return;
40424
40743
  if (!isFirstAttemptSlackOutboxFile(filename)) return;
40425
- if (existsSync10(join20(SLACK_RECOVERY_OUTBOX_DIR, filename))) {
40744
+ if (existsSync10(join21(SLACK_RECOVERY_OUTBOX_DIR, filename))) {
40426
40745
  void processSlackRecoveryOutboxFile(filename);
40427
40746
  }
40428
40747
  });
@@ -40437,7 +40756,7 @@ function startSlackRecoveryOutboxWatcher() {
40437
40756
  retryTimer.unref?.();
40438
40757
  }
40439
40758
  startSlackRecoveryOutboxWatcher();
40440
- var SLACK_NOTICE_OUTBOX_DIR = SLACK_AGENT_DIR ? join20(SLACK_AGENT_DIR, "slack-notice-outbox") : null;
40759
+ var SLACK_NOTICE_OUTBOX_DIR = SLACK_AGENT_DIR ? join21(SLACK_AGENT_DIR, "slack-notice-outbox") : null;
40441
40760
  var SLACK_NOTICE_MAX_AGE_MS = 9e4;
40442
40761
  var SLACK_NOTICE_INFLIGHT = /* @__PURE__ */ new Set();
40443
40762
  async function processSlackNoticeOutboxFile(filename) {
@@ -40445,11 +40764,11 @@ async function processSlackNoticeOutboxFile(filename) {
40445
40764
  if (filename.startsWith(".") || filename.endsWith(".tmp") || !filename.endsWith(".json")) return;
40446
40765
  if (SLACK_NOTICE_INFLIGHT.has(filename)) return;
40447
40766
  SLACK_NOTICE_INFLIGHT.add(filename);
40448
- const fullPath = join20(SLACK_NOTICE_OUTBOX_DIR, filename);
40767
+ const fullPath = join21(SLACK_NOTICE_OUTBOX_DIR, filename);
40449
40768
  try {
40450
40769
  let mtimeMs;
40451
40770
  try {
40452
- mtimeMs = statSync4(fullPath).mtimeMs;
40771
+ mtimeMs = statSync5(fullPath).mtimeMs;
40453
40772
  } catch {
40454
40773
  return;
40455
40774
  }
@@ -40462,7 +40781,7 @@ async function processSlackNoticeOutboxFile(filename) {
40462
40781
  }
40463
40782
  let payload;
40464
40783
  try {
40465
- const parsed = JSON.parse(readFileSync20(fullPath, "utf-8"));
40784
+ const parsed = JSON.parse(readFileSync21(fullPath, "utf-8"));
40466
40785
  if (!parsed || typeof parsed !== "object") throw new Error("not an object");
40467
40786
  payload = parsed;
40468
40787
  } catch {
@@ -40545,13 +40864,13 @@ function startSlackNoticeOutboxWatcher() {
40545
40864
  return;
40546
40865
  }
40547
40866
  try {
40548
- for (const f of readdirSync6(SLACK_NOTICE_OUTBOX_DIR)) void processSlackNoticeOutboxFile(f);
40867
+ for (const f of readdirSync7(SLACK_NOTICE_OUTBOX_DIR)) void processSlackNoticeOutboxFile(f);
40549
40868
  } catch {
40550
40869
  }
40551
40870
  try {
40552
40871
  const watcher = watch(SLACK_NOTICE_OUTBOX_DIR, (event, filename) => {
40553
40872
  if (event !== "rename" || !filename) return;
40554
- if (existsSync10(join20(SLACK_NOTICE_OUTBOX_DIR, filename))) {
40873
+ if (existsSync10(join21(SLACK_NOTICE_OUTBOX_DIR, filename))) {
40555
40874
  void processSlackNoticeOutboxFile(filename);
40556
40875
  }
40557
40876
  });
@@ -40574,7 +40893,7 @@ function sweepSlackStaleMarkers(thresholdMs) {
40574
40893
  if (!existsSync10(SLACK_PENDING_INBOUND_DIR)) return;
40575
40894
  let filenames;
40576
40895
  try {
40577
- filenames = readdirSync6(SLACK_PENDING_INBOUND_DIR);
40896
+ filenames = readdirSync7(SLACK_PENDING_INBOUND_DIR);
40578
40897
  } catch (err) {
40579
40898
  process.stderr.write(
40580
40899
  `slack-channel(${AGENT_CODE_NAME}): stale-marker readdir failed: ${err.message}
@@ -40588,10 +40907,10 @@ function sweepSlackStaleMarkers(thresholdMs) {
40588
40907
  for (const filename of filenames) {
40589
40908
  if (!filename.endsWith(".json")) continue;
40590
40909
  if (filename.endsWith(".tmp")) continue;
40591
- const fullPath = join20(SLACK_PENDING_INBOUND_DIR, filename);
40910
+ const fullPath = join21(SLACK_PENDING_INBOUND_DIR, filename);
40592
40911
  let marker;
40593
40912
  try {
40594
- marker = JSON.parse(readFileSync20(fullPath, "utf-8"));
40913
+ marker = JSON.parse(readFileSync21(fullPath, "utf-8"));
40595
40914
  } catch (err) {
40596
40915
  process.stderr.write(
40597
40916
  `slack-channel(${AGENT_CODE_NAME}): stale-marker parse failed for ${redactSlackId(filename)}: ${err.message}
@@ -40649,13 +40968,13 @@ var slackOrphanSweepTimer = setInterval(() => {
40649
40968
  checkSlackWatchdogGiveUpNotice();
40650
40969
  }, orphanSweepIntervalMs());
40651
40970
  slackOrphanSweepTimer.unref?.();
40652
- var SLACK_PROGRESS_HEARTBEAT_PATH = SLACK_AGENT_DIR ? join20(SLACK_AGENT_DIR, "channel-progress-heartbeat.json") : null;
40971
+ var SLACK_PROGRESS_HEARTBEAT_PATH = SLACK_AGENT_DIR ? join21(SLACK_AGENT_DIR, "channel-progress-heartbeat.json") : null;
40653
40972
  var slackTrackedProgress = null;
40654
40973
  var slackProgressTickRunning = false;
40655
40974
  function readSlackProgressHeartbeat() {
40656
40975
  if (!SLACK_PROGRESS_HEARTBEAT_PATH || !existsSync10(SLACK_PROGRESS_HEARTBEAT_PATH)) return null;
40657
40976
  try {
40658
- return parseProgressHeartbeat(readFileSync20(SLACK_PROGRESS_HEARTBEAT_PATH, "utf-8"));
40977
+ return parseProgressHeartbeat(readFileSync21(SLACK_PROGRESS_HEARTBEAT_PATH, "utf-8"));
40659
40978
  } catch {
40660
40979
  return null;
40661
40980
  }
@@ -40678,11 +40997,11 @@ function findSlackProgressTarget() {
40678
40997
  let best = null;
40679
40998
  let bestMs = Infinity;
40680
40999
  try {
40681
- for (const name of readdirSync6(SLACK_PENDING_INBOUND_DIR)) {
41000
+ for (const name of readdirSync7(SLACK_PENDING_INBOUND_DIR)) {
40682
41001
  if (!name.endsWith(".json")) continue;
40683
41002
  let m;
40684
41003
  try {
40685
- m = JSON.parse(readFileSync20(join20(SLACK_PENDING_INBOUND_DIR, name), "utf-8"));
41004
+ m = JSON.parse(readFileSync21(join21(SLACK_PENDING_INBOUND_DIR, name), "utf-8"));
40686
41005
  } catch {
40687
41006
  continue;
40688
41007
  }
@@ -40829,11 +41148,11 @@ function listPendingSlackConversations() {
40829
41148
  if (!SLACK_PENDING_INBOUND_DIR || !existsSync10(SLACK_PENDING_INBOUND_DIR)) return [];
40830
41149
  const byKey = /* @__PURE__ */ new Map();
40831
41150
  try {
40832
- for (const name of readdirSync6(SLACK_PENDING_INBOUND_DIR)) {
41151
+ for (const name of readdirSync7(SLACK_PENDING_INBOUND_DIR)) {
40833
41152
  if (!name.endsWith(".json")) continue;
40834
41153
  try {
40835
41154
  const marker = JSON.parse(
40836
- readFileSync20(join20(SLACK_PENDING_INBOUND_DIR, name), "utf8")
41155
+ readFileSync21(join21(SLACK_PENDING_INBOUND_DIR, name), "utf8")
40837
41156
  );
40838
41157
  if (typeof marker.channel !== "string" || !marker.channel) continue;
40839
41158
  if (typeof marker.thread_ts !== "string" || !marker.thread_ts) continue;
@@ -40852,6 +41171,95 @@ function listPendingSlackConversations() {
40852
41171
  }
40853
41172
  return [...byKey.values()];
40854
41173
  }
41174
+ var lastSlackTurnFailedNoticeAt = /* @__PURE__ */ new Map();
41175
+ var lastSlackTurnRetryingNoticeAt = /* @__PURE__ */ new Map();
41176
+ async function postSlackTurnNotice(args) {
41177
+ if (!BOT_TOKEN || !args.channel) return { ok: false, error: "no_bot_token_or_channel" };
41178
+ try {
41179
+ const res = await fetch("https://slack.com/api/chat.postMessage", {
41180
+ method: "POST",
41181
+ headers: {
41182
+ "Content-Type": "application/json",
41183
+ Authorization: `Bearer ${BOT_TOKEN}`
41184
+ },
41185
+ body: JSON.stringify({
41186
+ channel: args.channel,
41187
+ text: args.text,
41188
+ // Anchor to the originating message, matching the give-up notice: the
41189
+ // user must see this against the message it concerns.
41190
+ ...args.threadTs ? { thread_ts: args.threadTs } : {}
41191
+ }),
41192
+ signal: AbortSignal.timeout(1e4)
41193
+ });
41194
+ const body = await res.json().catch(() => ({}));
41195
+ if (body.ok === true) return { ok: true };
41196
+ return { ok: false, error: body.error ?? `http_${res.status}` };
41197
+ } catch (err) {
41198
+ return { ok: false, error: err.name || "fetch_failed" };
41199
+ }
41200
+ }
41201
+ function armSlackTurnFailureWatch(args) {
41202
+ const channel = args.channel;
41203
+ if (!channel) return;
41204
+ const conversationKey = args.threadTs ? `${channel}:${args.threadTs}` : channel;
41205
+ void (async () => {
41206
+ try {
41207
+ const failure = await watchForTurnFailure({
41208
+ sinceMs: args.sinceMs,
41209
+ channel: "slack",
41210
+ onLongRetry: async () => {
41211
+ const now2 = Date.now();
41212
+ if (!shouldPostUndeliverableNotice(lastSlackTurnRetryingNoticeAt.get(conversationKey), now2)) {
41213
+ return;
41214
+ }
41215
+ lastSlackTurnRetryingNoticeAt.set(conversationKey, now2);
41216
+ const posted2 = await postSlackTurnNotice({
41217
+ channel,
41218
+ threadTs: args.threadTs,
41219
+ text: turnRetryingNoticeText()
41220
+ });
41221
+ if (!posted2.ok) lastSlackTurnRetryingNoticeAt.delete(conversationKey);
41222
+ process.stderr.write(
41223
+ `slack-channel(${AGENT_CODE_NAME}): [turn-failure] retrying notice ${posted2.ok ? "posted" : `FAILED (${posted2.error})`} channel=${redactSlackId(channel)}
41224
+ `
41225
+ );
41226
+ }
41227
+ });
41228
+ if (!failure) return;
41229
+ const now = Date.now();
41230
+ if (!shouldPostUndeliverableNotice(lastSlackTurnFailedNoticeAt.get(conversationKey), now)) {
41231
+ process.stderr.write(
41232
+ `slack-channel(${AGENT_CODE_NAME}): [turn-failure] suppressed (throttled) channel=${redactSlackId(channel)} class=${failure.failureClass}
41233
+ `
41234
+ );
41235
+ return;
41236
+ }
41237
+ lastSlackTurnFailedNoticeAt.set(conversationKey, now);
41238
+ const posted = await postSlackTurnNotice({
41239
+ channel,
41240
+ threadTs: args.threadTs,
41241
+ text: turnFailedNoticeText()
41242
+ });
41243
+ if (!posted.ok) {
41244
+ lastSlackTurnFailedNoticeAt.delete(conversationKey);
41245
+ process.stderr.write(
41246
+ `slack-channel(${AGENT_CODE_NAME}): [turn-failure] NOTICE POST FAILED (${posted.error}) channel=${redactSlackId(channel)} - the user is still waiting on a dead turn
41247
+ `
41248
+ );
41249
+ return;
41250
+ }
41251
+ process.stderr.write(
41252
+ `slack-channel(${AGENT_CODE_NAME}): [turn-failure] notified channel=${redactSlackId(channel)} ts=${redactSlackId(args.messageTs)} class=${failure.failureClass} status=${failure.httpStatus}
41253
+ `
41254
+ );
41255
+ } catch (err) {
41256
+ process.stderr.write(
41257
+ `slack-channel(${AGENT_CODE_NAME}): [turn-failure] watch error: ${err.message}
41258
+ `
41259
+ );
41260
+ }
41261
+ })();
41262
+ }
40855
41263
  function postSlackWatchdogGiveUpNotice(channel, threadTs, isThreadReply, reason) {
40856
41264
  if (!BOT_TOKEN || !channel) return;
40857
41265
  const now = Date.now();
@@ -40885,7 +41293,7 @@ function postSlackWatchdogGiveUpNotice(channel, threadTs, isThreadReply, reason)
40885
41293
  }
40886
41294
  function checkSlackWatchdogGiveUpNotice() {
40887
41295
  if (!SLACK_AGENT_DIR) return;
40888
- const signal = readGiveUpSignal(join20(SLACK_AGENT_DIR, GIVE_UP_SIGNAL_FILENAME));
41296
+ const signal = readGiveUpSignal(join21(SLACK_AGENT_DIR, GIVE_UP_SIGNAL_FILENAME));
40889
41297
  const signalAtMs = signal?.atMs ?? null;
40890
41298
  const act = decideGiveUpNotice({
40891
41299
  signalAtMs,
@@ -40917,7 +41325,7 @@ function readRestartTopicForMarker(filename, channel, threadTs) {
40917
41325
  if (!SLACK_RESTART_CONTEXT_DIR) return null;
40918
41326
  let raw;
40919
41327
  try {
40920
- raw = readFileSync20(join20(SLACK_RESTART_CONTEXT_DIR, filename), "utf-8");
41328
+ raw = readFileSync21(join21(SLACK_RESTART_CONTEXT_DIR, filename), "utf-8");
40921
41329
  } catch {
40922
41330
  return null;
40923
41331
  }
@@ -40926,7 +41334,7 @@ function readRestartTopicForMarker(filename, channel, threadTs) {
40926
41334
  function removeRestartContextHint(filename) {
40927
41335
  if (!SLACK_RESTART_CONTEXT_DIR) return;
40928
41336
  try {
40929
- unlinkSync7(join20(SLACK_RESTART_CONTEXT_DIR, filename));
41337
+ unlinkSync7(join21(SLACK_RESTART_CONTEXT_DIR, filename));
40930
41338
  } catch {
40931
41339
  }
40932
41340
  }
@@ -40934,14 +41342,14 @@ function clearAllRestartContextHints() {
40934
41342
  if (!SLACK_RESTART_CONTEXT_DIR) return;
40935
41343
  let names;
40936
41344
  try {
40937
- names = readdirSync6(SLACK_RESTART_CONTEXT_DIR);
41345
+ names = readdirSync7(SLACK_RESTART_CONTEXT_DIR);
40938
41346
  } catch {
40939
41347
  return;
40940
41348
  }
40941
41349
  for (const name of names) {
40942
41350
  if (!name.endsWith(".json")) continue;
40943
41351
  try {
40944
- unlinkSync7(join20(SLACK_RESTART_CONTEXT_DIR, name));
41352
+ unlinkSync7(join21(SLACK_RESTART_CONTEXT_DIR, name));
40945
41353
  } catch {
40946
41354
  }
40947
41355
  }
@@ -40958,7 +41366,7 @@ async function notifyStrandedInboundsOnFirstConnect() {
40958
41366
  if (!SLACK_PENDING_INBOUND_DIR || !existsSync10(SLACK_PENDING_INBOUND_DIR)) return;
40959
41367
  let filenames;
40960
41368
  try {
40961
- filenames = readdirSync6(SLACK_PENDING_INBOUND_DIR);
41369
+ filenames = readdirSync7(SLACK_PENDING_INBOUND_DIR);
40962
41370
  } catch {
40963
41371
  hadFailure = true;
40964
41372
  return;
@@ -40968,10 +41376,10 @@ async function notifyStrandedInboundsOnFirstConnect() {
40968
41376
  let notified = 0;
40969
41377
  for (const filename of filenames) {
40970
41378
  if (!filename.endsWith(".json")) continue;
40971
- const fullPath = join20(SLACK_PENDING_INBOUND_DIR, filename);
41379
+ const fullPath = join21(SLACK_PENDING_INBOUND_DIR, filename);
40972
41380
  let marker;
40973
41381
  try {
40974
- marker = JSON.parse(readFileSync20(fullPath, "utf-8"));
41382
+ marker = JSON.parse(readFileSync21(fullPath, "utf-8"));
40975
41383
  } catch {
40976
41384
  continue;
40977
41385
  }
@@ -41145,7 +41553,7 @@ function noteThreadActivityByMessageTs(channel, messageTs) {
41145
41553
  markSeenAllSlackPendingMarkersForThread2(channel, messageTs);
41146
41554
  markSeenSlackPendingMarkerByMessageTs2(channel, messageTs);
41147
41555
  }
41148
- var RESTART_FLAGS_DIR = join20(homedir7(), ".augmented", "restart-flags");
41556
+ var RESTART_FLAGS_DIR = join21(homedir7(), ".augmented", "restart-flags");
41149
41557
  function actuateHostRestartSlack() {
41150
41558
  return actuateHostRestart({
41151
41559
  agtHost: AGT_HOST,
@@ -41750,7 +42158,7 @@ async function handleSlashCommandEnvelope(payload) {
41750
42158
  if (!existsSync10(RESTART_FLAGS_DIR)) {
41751
42159
  mkdirSync9(RESTART_FLAGS_DIR, { recursive: true });
41752
42160
  }
41753
- const flagPath = join20(RESTART_FLAGS_DIR, `${codeName}.flag`);
42161
+ const flagPath = join21(RESTART_FLAGS_DIR, `${codeName}.flag`);
41754
42162
  const flag = {
41755
42163
  codeName,
41756
42164
  source: "slack",
@@ -41893,7 +42301,7 @@ async function handleRestartCommand(opts) {
41893
42301
  if (!existsSync10(RESTART_FLAGS_DIR)) {
41894
42302
  mkdirSync9(RESTART_FLAGS_DIR, { recursive: true });
41895
42303
  }
41896
- const flagPath = join20(RESTART_FLAGS_DIR, `${codeName}.flag`);
42304
+ const flagPath = join21(RESTART_FLAGS_DIR, `${codeName}.flag`);
41897
42305
  const flag = {
41898
42306
  codeName,
41899
42307
  source: "slack",
@@ -42015,7 +42423,7 @@ var SLACK_HOT_THREAD_WINDOW_MS = parseHotThreadWindowMs(
42015
42423
  );
42016
42424
  function resolveThreadStorePath() {
42017
42425
  if (!AGENT_CODE_NAME) return null;
42018
- return join20(homedir7(), ".augmented", AGENT_CODE_NAME, "slack-tracked-threads.json");
42426
+ return join21(homedir7(), ".augmented", AGENT_CODE_NAME, "slack-tracked-threads.json");
42019
42427
  }
42020
42428
  function parseTtlDays(raw) {
42021
42429
  if (!raw) return void 0;
@@ -42054,9 +42462,9 @@ if (!BOT_TOKEN || !APP_TOKEN) {
42054
42462
  var slackStderrLogStream = null;
42055
42463
  if (AGENT_CODE_NAME) {
42056
42464
  try {
42057
- const logDir = join20(homedir7(), ".augmented", AGENT_CODE_NAME);
42465
+ const logDir = join21(homedir7(), ".augmented", AGENT_CODE_NAME);
42058
42466
  mkdirSync9(logDir, { recursive: true });
42059
- slackStderrLogStream = createWriteStream(join20(logDir, "slack-channel-stderr.log"), {
42467
+ slackStderrLogStream = createWriteStream(join21(logDir, "slack-channel-stderr.log"), {
42060
42468
  flags: "a",
42061
42469
  mode: 384
42062
42470
  });
@@ -42946,7 +43354,7 @@ ${result.formatted}` : "No messages in range, or the bot is not a member of this
42946
43354
  let bytes;
42947
43355
  let size;
42948
43356
  try {
42949
- const stat2 = statSync4(resolvedPath);
43357
+ const stat2 = statSync5(resolvedPath);
42950
43358
  if (!stat2.isFile()) {
42951
43359
  return {
42952
43360
  content: [{ type: "text", text: `Upload refused: ${resolvedPath} is not a regular file.` }],
@@ -42954,7 +43362,7 @@ ${result.formatted}` : "No messages in range, or the bot is not a member of this
42954
43362
  };
42955
43363
  }
42956
43364
  size = stat2.size;
42957
- bytes = readFileSync20(resolvedPath);
43365
+ bytes = readFileSync21(resolvedPath);
42958
43366
  } catch (err) {
42959
43367
  return {
42960
43368
  content: [{ type: "text", text: `Failed to read file: ${err.message}` }],
@@ -43729,7 +44137,7 @@ async function replayPendingSlackMarkers() {
43729
44137
  if (!sessionAlive) return;
43730
44138
  let filenames;
43731
44139
  try {
43732
- filenames = readdirSync6(SLACK_PENDING_INBOUND_DIR);
44140
+ filenames = readdirSync7(SLACK_PENDING_INBOUND_DIR);
43733
44141
  } catch {
43734
44142
  return;
43735
44143
  }
@@ -43737,7 +44145,7 @@ async function replayPendingSlackMarkers() {
43737
44145
  let paneFreshAgeMs = null;
43738
44146
  if (SLACK_AGENT_DIR) {
43739
44147
  try {
43740
- paneFreshAgeMs = Math.max(0, now - statSync4(join20(SLACK_AGENT_DIR, "pane.log")).mtimeMs);
44148
+ paneFreshAgeMs = Math.max(0, now - statSync5(join21(SLACK_AGENT_DIR, "pane.log")).mtimeMs);
43741
44149
  } catch {
43742
44150
  }
43743
44151
  }
@@ -43745,10 +44153,10 @@ async function replayPendingSlackMarkers() {
43745
44153
  const entries = [];
43746
44154
  for (const name of filenames) {
43747
44155
  if (!name.endsWith(".json") || name.endsWith(".tmp")) continue;
43748
- const fullPath = join20(SLACK_PENDING_INBOUND_DIR, name);
44156
+ const fullPath = join21(SLACK_PENDING_INBOUND_DIR, name);
43749
44157
  let marker;
43750
44158
  try {
43751
- marker = JSON.parse(readFileSync20(fullPath, "utf-8"));
44159
+ marker = JSON.parse(readFileSync21(fullPath, "utf-8"));
43752
44160
  } catch {
43753
44161
  continue;
43754
44162
  }
@@ -44346,7 +44754,7 @@ async function connectSocketMode() {
44346
44754
  let paneLogFreshAgeMs = null;
44347
44755
  if (SLACK_AGENT_DIR) {
44348
44756
  try {
44349
- const paneMtimeMs = statSync4(join20(SLACK_AGENT_DIR, "pane.log")).mtimeMs;
44757
+ const paneMtimeMs = statSync5(join21(SLACK_AGENT_DIR, "pane.log")).mtimeMs;
44350
44758
  paneLogFreshAgeMs = Math.max(0, Date.now() - paneMtimeMs);
44351
44759
  } catch {
44352
44760
  }
@@ -44532,6 +44940,14 @@ ${forwarded.text}` : forwarded.text;
44532
44940
  sinceMs: dispatchedAtMs
44533
44941
  });
44534
44942
  }
44943
+ if (shouldEngage && !isFromBot && turnFailureNoticeEnabled()) {
44944
+ armSlackTurnFailureWatch({
44945
+ channel,
44946
+ threadTs,
44947
+ messageTs: ts,
44948
+ sinceMs: dispatchedAtMs
44949
+ });
44950
+ }
44535
44951
  if (shouldEngage) seedSlackProgressHeartbeat();
44536
44952
  if (channel) {
44537
44953
  conversationIngestClient?.ingest({