@integrity-labs/agt-cli 0.28.442 → 0.28.443

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.
@@ -18867,7 +18867,7 @@ var require_filters = __commonJS({
18867
18867
  return r.copySafeness(str, res);
18868
18868
  }
18869
18869
  _exports.indent = indent;
18870
- function join13(arr, del, attr) {
18870
+ function join14(arr, del, attr) {
18871
18871
  del = del || "";
18872
18872
  if (attr) {
18873
18873
  arr = lib.map(arr, function(v) {
@@ -18876,7 +18876,7 @@ var require_filters = __commonJS({
18876
18876
  }
18877
18877
  return arr.join(del);
18878
18878
  }
18879
- _exports.join = join13;
18879
+ _exports.join = join14;
18880
18880
  function last(arr) {
18881
18881
  return arr[arr.length - 1];
18882
18882
  }
@@ -30087,6 +30087,40 @@ function resolveHostBooleanFlag(opts) {
30087
30087
  if (cached2 !== void 0) return cached2;
30088
30088
  return opts.defaultValue;
30089
30089
  }
30090
+ var USAGE_LIMIT_REACTIVE_FLAG_KEY = "usage-limit-reactive-notice";
30091
+ function parseUsageLimitReactiveMode(raw) {
30092
+ if (raw === void 0) return void 0;
30093
+ const v = raw.trim().toLowerCase();
30094
+ if (v === "") return void 0;
30095
+ if (v === "off" || v === "shadow" || v === "enforce") return v;
30096
+ const b = envBoolean(raw);
30097
+ if (b === true) return "enforce";
30098
+ if (b === false) return "off";
30099
+ return void 0;
30100
+ }
30101
+ function cachedUsageLimitReactiveMode(path) {
30102
+ try {
30103
+ if (!existsSync4(path)) return void 0;
30104
+ const parsed = JSON.parse(readFileSync6(path, "utf8"));
30105
+ if (!parsed || typeof parsed !== "object") return void 0;
30106
+ const flags = parsed.flags;
30107
+ if (!flags || typeof flags !== "object") return void 0;
30108
+ const value = flags[USAGE_LIMIT_REACTIVE_FLAG_KEY];
30109
+ if (typeof value === "string") return parseUsageLimitReactiveMode(value);
30110
+ if (typeof value === "boolean") return value ? "enforce" : "off";
30111
+ return void 0;
30112
+ } catch {
30113
+ return void 0;
30114
+ }
30115
+ }
30116
+ function resolveUsageLimitReactiveMode(opts) {
30117
+ const env2 = opts?.env ?? process.env;
30118
+ const modeOverride = parseUsageLimitReactiveMode(env2["AGT_USAGE_LIMIT_REACTIVE_MODE"]);
30119
+ if (modeOverride !== void 0) return modeOverride;
30120
+ const cached2 = cachedUsageLimitReactiveMode(opts?.cachePath ?? defaultFlagsCachePath());
30121
+ if (cached2 !== void 0) return cached2;
30122
+ return "off";
30123
+ }
30090
30124
 
30091
30125
  // src/ack-reaction.ts
30092
30126
  var REPLY_WEDGED_THRESHOLD_MS = 5 * 60 * 1e3;
@@ -30399,19 +30433,19 @@ function readLockHolder(path) {
30399
30433
  }
30400
30434
 
30401
30435
  // src/direct-chat-channel.ts
30402
- import { homedir as homedir4 } from "os";
30403
- import { join as join12 } from "path";
30436
+ import { homedir as homedir5 } from "os";
30437
+ import { join as join13 } from "path";
30404
30438
  import { randomUUID } from "crypto";
30405
30439
  import {
30406
30440
  watch,
30407
30441
  mkdirSync as mkdirSync5,
30408
30442
  writeFileSync as writeFileSync6,
30409
- readFileSync as readFileSync11,
30410
- readdirSync as readdirSync4,
30443
+ readFileSync as readFileSync12,
30444
+ readdirSync as readdirSync5,
30411
30445
  existsSync as existsSync6,
30412
30446
  renameSync as renameSync5,
30413
30447
  unlinkSync as unlinkSync4,
30414
- statSync as statSync2,
30448
+ statSync as statSync3,
30415
30449
  createWriteStream
30416
30450
  } from "fs";
30417
30451
 
@@ -34165,7 +34199,113 @@ var BANNER_PATTERNS = [
34165
34199
  // pct=100.
34166
34200
  new RegExp(`${SUBJECT}hit\\s+your\\s+(?:[a-z0-9-]+\\s+)?limit${SEP}resets\\s+(${RESET_DATE})`, "i")
34167
34201
  ];
34202
+ function parseUsageBanner(text, now = /* @__PURE__ */ new Date()) {
34203
+ let bestIndex = -1;
34204
+ let best = null;
34205
+ for (let i = 0; i < BANNER_PATTERNS.length; i++) {
34206
+ const pattern = new RegExp(BANNER_PATTERNS[i].source, "gi");
34207
+ let match;
34208
+ while ((match = pattern.exec(text)) !== null) {
34209
+ if (match.index === pattern.lastIndex)
34210
+ pattern.lastIndex++;
34211
+ let pct;
34212
+ let resetStr;
34213
+ if (i === 0) {
34214
+ pct = Number.parseInt(match[1], 10);
34215
+ resetStr = match[2];
34216
+ } else {
34217
+ pct = 100;
34218
+ resetStr = match[1];
34219
+ }
34220
+ if (!Number.isFinite(pct) || pct < 0 || pct > 100)
34221
+ continue;
34222
+ const nextChar = text[match.index + match[0].length];
34223
+ if (nextChar !== void 0 && /[A-Za-z0-9]/.test(nextChar))
34224
+ continue;
34225
+ const weekResetsAt = parseResetDateTime(resetStr, now);
34226
+ if (!weekResetsAt)
34227
+ continue;
34228
+ if (match.index >= bestIndex) {
34229
+ bestIndex = match.index;
34230
+ best = { pct, weekResetsAt };
34231
+ }
34232
+ }
34233
+ }
34234
+ return best;
34235
+ }
34236
+ var MONTHS = [
34237
+ "jan",
34238
+ "feb",
34239
+ "mar",
34240
+ "apr",
34241
+ "may",
34242
+ "jun",
34243
+ "jul",
34244
+ "aug",
34245
+ "sep",
34246
+ "oct",
34247
+ "nov",
34248
+ "dec"
34249
+ ];
34250
+ var TIME_TAIL = /,\s*(\d{1,2})(?::(\d{2}))?\s*(am|pm)(?:\s*\(?UTC\)?)?\s*$/i;
34251
+ var TIME_ONLY = /^(\d{1,2})(?::(\d{2}))?\s*(am|pm)(?:\s*\(?UTC\)?)?$/i;
34168
34252
  var MS_PER_DAY = 24 * 60 * 60 * 1e3;
34253
+ function parseAmPm(hourStr, minStr, ampm) {
34254
+ const rawHour = Number.parseInt(hourStr, 10);
34255
+ if (!Number.isFinite(rawHour) || rawHour < 1 || rawHour > 12)
34256
+ return null;
34257
+ let minute = 0;
34258
+ if (minStr) {
34259
+ minute = Number.parseInt(minStr, 10);
34260
+ if (!Number.isFinite(minute) || minute < 0 || minute > 59)
34261
+ return null;
34262
+ }
34263
+ const isPm = ampm.toLowerCase() === "pm";
34264
+ return { hour: rawHour % 12 + (isPm ? 12 : 0), minute };
34265
+ }
34266
+ function parseResetDateTime(humanDate, now) {
34267
+ const trimmed = humanDate.trim();
34268
+ const timeOnly = trimmed.match(TIME_ONLY);
34269
+ if (timeOnly) {
34270
+ const hm = parseAmPm(timeOnly[1], timeOnly[2], timeOnly[3]);
34271
+ if (!hm)
34272
+ return null;
34273
+ const todayAt = Date.UTC(now.getUTCFullYear(), now.getUTCMonth(), now.getUTCDate(), hm.hour, hm.minute);
34274
+ return new Date(todayAt <= now.getTime() ? todayAt + MS_PER_DAY : todayAt);
34275
+ }
34276
+ const timeMatch = trimmed.match(TIME_TAIL);
34277
+ const dateOnly = timeMatch ? trimmed.slice(0, timeMatch.index).trim() : trimmed;
34278
+ const parts = dateOnly.split(/\s+/);
34279
+ if (parts.length !== 2)
34280
+ return null;
34281
+ const month = MONTHS.indexOf(parts[0].slice(0, 3).toLowerCase());
34282
+ if (month < 0)
34283
+ return null;
34284
+ const day = Number.parseInt(parts[1], 10);
34285
+ if (!Number.isFinite(day) || day < 1 || day > 31)
34286
+ return null;
34287
+ let hour = 0;
34288
+ let minute = 0;
34289
+ if (timeMatch) {
34290
+ const hm = parseAmPm(timeMatch[1], timeMatch[2], timeMatch[3]);
34291
+ if (!hm)
34292
+ return null;
34293
+ hour = hm.hour;
34294
+ minute = hm.minute;
34295
+ }
34296
+ const baseYear = now.getUTCFullYear();
34297
+ let resolved = null;
34298
+ let bestDelta = Number.POSITIVE_INFINITY;
34299
+ for (const y of [baseYear - 1, baseYear, baseYear + 1]) {
34300
+ const candidate = new Date(Date.UTC(y, month, day, hour, minute));
34301
+ const delta = Math.abs(candidate.getTime() - now.getTime());
34302
+ if (delta < bestDelta) {
34303
+ bestDelta = delta;
34304
+ resolved = candidate;
34305
+ }
34306
+ }
34307
+ return resolved;
34308
+ }
34169
34309
 
34170
34310
  // ../core/dist/claude-code-usage/usage-limit-marker.js
34171
34311
  var USAGE_LIMIT_MARKER_FILENAME = "claude-usage-limit.json";
@@ -34203,6 +34343,109 @@ function buildUsageLimitReplyText(limitedUntil) {
34203
34343
  return `Your agent has hit its Claude Code usage limit until ${formatUtcClock(limitedUntil)}. It'll pick back up once the limit resets \u2014 please try again after then.`;
34204
34344
  }
34205
34345
 
34346
+ // ../core/dist/claude-code-usage/rate-limit-classifier.js
34347
+ var UNKNOWN_RATE_LIMIT = Object.freeze({
34348
+ verdict: "unknown",
34349
+ atMs: null,
34350
+ resetsAt: null,
34351
+ text: null
34352
+ });
34353
+ function contentText(record2) {
34354
+ const candidates = [record2.content];
34355
+ const message = record2.message;
34356
+ if (typeof message === "object" && message !== null) {
34357
+ candidates.push(message.content);
34358
+ }
34359
+ const parts = [];
34360
+ for (const candidate of candidates) {
34361
+ if (typeof candidate === "string") {
34362
+ if (candidate)
34363
+ parts.push(candidate);
34364
+ continue;
34365
+ }
34366
+ if (!Array.isArray(candidate))
34367
+ continue;
34368
+ for (const block of candidate) {
34369
+ if (typeof block === "string") {
34370
+ if (block)
34371
+ parts.push(block);
34372
+ continue;
34373
+ }
34374
+ if (typeof block !== "object" || block === null)
34375
+ continue;
34376
+ const text = block.text;
34377
+ if (typeof text === "string" && text)
34378
+ parts.push(text);
34379
+ }
34380
+ }
34381
+ const joined = parts.join("\n").trim();
34382
+ return joined ? joined : null;
34383
+ }
34384
+ function classifyTranscriptLine(line, startMs, endMs, now) {
34385
+ const trimmed = line.trim();
34386
+ if (!trimmed)
34387
+ return null;
34388
+ let obj;
34389
+ try {
34390
+ obj = JSON.parse(trimmed);
34391
+ } catch {
34392
+ return null;
34393
+ }
34394
+ if (typeof obj !== "object" || obj === null)
34395
+ return null;
34396
+ const record2 = obj;
34397
+ if (record2.type !== "assistant")
34398
+ return null;
34399
+ const ts = record2.timestamp;
34400
+ if (typeof ts !== "string" || !ts)
34401
+ return null;
34402
+ const tsMs = new Date(ts).getTime();
34403
+ if (!Number.isFinite(tsMs) || tsMs < startMs || tsMs > endMs)
34404
+ return null;
34405
+ if (record2.error === "rate_limit" || record2.apiErrorStatus === 429) {
34406
+ const text = contentText(record2);
34407
+ const observation = text ? parseUsageBanner(text, now ?? new Date(endMs)) : null;
34408
+ return { verdict: "capped", atMs: tsMs, resetsAt: observation?.weekResetsAt ?? null, text };
34409
+ }
34410
+ if (record2.isApiErrorMessage === true)
34411
+ return null;
34412
+ const message = record2.message;
34413
+ if (typeof message !== "object" || message === null)
34414
+ return null;
34415
+ const msg = message;
34416
+ if (msg.model === "<synthetic>")
34417
+ return null;
34418
+ const usage = msg.usage;
34419
+ if (typeof usage !== "object" || usage === null)
34420
+ return null;
34421
+ const u = usage;
34422
+ 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);
34423
+ if (!Number.isFinite(spent) || spent <= 0)
34424
+ return null;
34425
+ return { verdict: "serving", atMs: tsMs, resetsAt: null, text: null };
34426
+ }
34427
+ function pickNewerClassification(current, next) {
34428
+ if (next.verdict === "unknown")
34429
+ return current;
34430
+ if (current.verdict === "unknown")
34431
+ return next;
34432
+ return next.atMs >= current.atMs ? next : current;
34433
+ }
34434
+ function classifyTranscriptRateLimit(jsonl, startMs, endMs, now) {
34435
+ let newest = UNKNOWN_RATE_LIMIT;
34436
+ for (const line of jsonl.split("\n")) {
34437
+ const classified = classifyTranscriptLine(line, startMs, endMs, now);
34438
+ if (classified)
34439
+ newest = pickNewerClassification(newest, classified);
34440
+ }
34441
+ return newest;
34442
+ }
34443
+
34444
+ // ../core/dist/claude-code-usage/transcript-location.js
34445
+ function encodeClaudeProjectPath(projectDir) {
34446
+ return "-" + projectDir.replace(/^\//, "").replace(/[/.]/g, "-");
34447
+ }
34448
+
34206
34449
  // ../core/dist/account-enforcement/marker.js
34207
34450
  var ACCOUNT_ENFORCEMENT_MARKER_FILENAME = "account-enforcement.json";
34208
34451
  var ACCOUNT_ENFORCEMENT_MARKER_VERSION = 1;
@@ -34847,6 +35090,33 @@ var FLAG_REGISTRY = [
34847
35090
  // projectDefinition, so setting it does not roll FLAGS_SCHEMA_VERSION.
34848
35091
  since: "0.28.421"
34849
35092
  },
35093
+ {
35094
+ key: "usage-limit-reactive-notice",
35095
+ description: "Report a Claude Code usage cap REACTIVELY instead of predicting it (ENG-8201). Today the manager guesses from the agent transcript that the next turn will be refused, writes a marker, and every channel MCP refuses to dispatch on the strength of that guess - so a wrong guess is a swallowed message, and because the notice is throttled per (channel, sender) while the DROP is not, the usual symptom is total silence rather than a wrong reply. A refused turn actually costs nothing (rejected in under a second, zero tokens) and Claude Code records it with the reset time in it, so there is no need to guess: dispatch, and report the refusal if one comes back. off = today behaviour (pre-dispatch marker gate, no watcher). shadow = still gate on the marker, but ALSO watch dispatched messages and log the refusal that would have been reported - measures the true-positive rate with no user-visible change. enforce = stop reading the marker before dispatch; every admitted human message reaches the agent and a refusal is answered in-thread with the reset time from the error itself. Read live from the heartbeat flags-cache (or the env override).",
35096
+ flagType: "enum",
35097
+ allowedValues: ["off", "shadow", "enforce"],
35098
+ // Ships dark: off preserves today's gate byte-for-byte. shadow is a free
35099
+ // local log line (the watch is a transcript read, no model spend), so it is a
35100
+ // cheap soak; enforce changes what reaches the agent, so it is the audited
35101
+ // per-org flip.
35102
+ defaultValue: "off",
35103
+ // Enum override AGT_USAGE_LIMIT_REACTIVE_MODE (off|shadow|enforce), resolved
35104
+ // by resolveUsageLimitReactiveMode in the channel-server bundle.
35105
+ envVar: "AGT_USAGE_LIMIT_REACTIVE_MODE",
35106
+ // enforce sends a message to an agent the host currently believes is capped -
35107
+ // a deliberate availability trade (the refusal is free, but it is still a
35108
+ // dispatch the operator previously suppressed), so flipping toward it is an
35109
+ // audited change (ADR-0022 sensitive-flag confirm).
35110
+ sensitive: true
35111
+ // ENG-8149: `since` is the agt-cli version that first carries
35112
+ // resolveUsageLimitReactiveMode + the MCP watcher. It cannot be known before
35113
+ // this lands (the auto-publish patch-bumps on merge), and an undefined
35114
+ // `since` makes the flip-reach modal claim FULL reach - true for flags that
35115
+ // predate the reach work, wrong for a NEW host-read flag whose reader older
35116
+ // hosts simply do not have. Backfilled by a follow-up commit once the
35117
+ // publishing run reports the version. Excluded from projectDefinition, so
35118
+ // setting it does not roll FLAGS_SCHEMA_VERSION.
35119
+ },
34850
35120
  {
34851
35121
  key: "slack-hot-thread-guard",
34852
35122
  description: "Server-side hot-thread guard on the slack.reply surface (ENG-7462). Prevents an agent posting a NEW top-level Slack message when it meant to reply inside the thread it is already working in - a prompt/memory rule proved insufficient (the agent had the rule and still slipped). When a reply would otherwise post to channel ROOT (no thread_ts / message_ts / inbound_id, no active kanban card) and the agent has a recent active thread in that channel (its last bot-posted thread, from the persisted trackedThreads cache, within a freshness window), the reply is redirected into that thread. proactive:true no longer implies channel root; posting at root becomes a deliberate action (the to_channel_root flag, or the thread_ts:null sentinel). off = guard never runs, replies with no coords root exactly as today (ships dark). shadow = compute + log the would-redirect but STILL post to root (measure the fire rate before acting). enforce = apply the redirect (a soft-block: redirect + inform, never a hard rejection). Read live from the heartbeat flags-cache (or the env override); enforce is a deliberate per-org flip after a shadow soak.",
@@ -35570,6 +35840,75 @@ function shouldSendAccountWarn(opts) {
35570
35840
  }).reply;
35571
35841
  }
35572
35842
 
35843
+ // src/rate-limit-watch.ts
35844
+ import { readFileSync as readFileSync11, readdirSync as readdirSync4, statSync as statSync2 } from "fs";
35845
+ import { homedir as homedir4 } from "os";
35846
+ import { join as join12 } from "path";
35847
+ var DEFAULT_WATCH_MS = 5e3;
35848
+ var DEFAULT_POLL_MS = 400;
35849
+ function agentTranscriptDir(opts) {
35850
+ const cwd = opts?.cwd ?? process.cwd();
35851
+ const home = opts?.home ?? homedir4();
35852
+ return join12(home, ".claude", "projects", encodeClaudeProjectPath(cwd));
35853
+ }
35854
+ function classifyTranscriptSince(opts) {
35855
+ const dir = opts.transcriptDir ?? agentTranscriptDir({ cwd: opts.cwd, home: opts.home });
35856
+ let entries;
35857
+ try {
35858
+ entries = readdirSync4(dir);
35859
+ } catch {
35860
+ return UNKNOWN_RATE_LIMIT;
35861
+ }
35862
+ let newest = UNKNOWN_RATE_LIMIT;
35863
+ for (const name of entries) {
35864
+ if (!name.endsWith(".jsonl")) continue;
35865
+ const path = join12(dir, name);
35866
+ try {
35867
+ const st = statSync2(path);
35868
+ if (!st.isFile() || st.mtimeMs < opts.sinceMs) continue;
35869
+ } catch {
35870
+ continue;
35871
+ }
35872
+ let content;
35873
+ try {
35874
+ content = readFileSync11(path, "utf-8");
35875
+ } catch {
35876
+ continue;
35877
+ }
35878
+ newest = pickNewerClassification(
35879
+ newest,
35880
+ classifyTranscriptRateLimit(content, opts.sinceMs, opts.nowMs, new Date(opts.nowMs))
35881
+ );
35882
+ }
35883
+ return newest;
35884
+ }
35885
+ var sleep = (ms) => new Promise((resolve) => setTimeout(resolve, ms));
35886
+ async function watchForRateLimitRefusal(opts) {
35887
+ const now = opts.now ?? (() => Date.now());
35888
+ const wait = opts.wait ?? sleep;
35889
+ const timeoutMs = opts.timeoutMs ?? DEFAULT_WATCH_MS;
35890
+ const pollMs = opts.pollMs ?? DEFAULT_POLL_MS;
35891
+ const deadline = now() + timeoutMs;
35892
+ for (; ; ) {
35893
+ let result;
35894
+ try {
35895
+ result = classifyTranscriptSince({
35896
+ sinceMs: opts.sinceMs,
35897
+ nowMs: now(),
35898
+ transcriptDir: opts.transcriptDir,
35899
+ cwd: opts.cwd,
35900
+ home: opts.home
35901
+ });
35902
+ } catch {
35903
+ return null;
35904
+ }
35905
+ if (result.verdict === "capped") return result;
35906
+ if (result.verdict === "serving") return null;
35907
+ if (now() >= deadline) return null;
35908
+ await wait(pollMs);
35909
+ }
35910
+ }
35911
+
35573
35912
  // src/direct-chat-channel.ts
35574
35913
  var DIRECT_CHAT_MAINTENANCE_CACHE = /* @__PURE__ */ new Map();
35575
35914
  var DIRECT_CHAT_MAINTENANCE_COOLDOWN_MS = (() => {
@@ -35588,21 +35927,21 @@ var DIRECT_CHAT_ACCOUNT_WARN_COOLDOWN_MS = (() => {
35588
35927
  var AGT_HOST = process.env.AGT_HOST;
35589
35928
  var AGT_API_KEY = process.env.AGT_API_KEY;
35590
35929
  var AGT_AGENT_ID = process.env.AGT_AGENT_ID;
35591
- var DIRECT_CHAT_AGENT_DIR = AGT_AGENT_ID ? join12(homedir4(), ".augmented", AGT_AGENT_ID) : null;
35930
+ var DIRECT_CHAT_AGENT_DIR = AGT_AGENT_ID ? join13(homedir5(), ".augmented", AGT_AGENT_ID) : null;
35592
35931
  var INBOUND_ATTACHMENTS_DIR = resolveInboundAttachmentsDir({
35593
35932
  codeName: process.env.AGT_AGENT_CODE_NAME,
35594
35933
  turnInitiatorFile: process.env.AGT_TURN_INITIATOR_FILE,
35595
35934
  agentId: AGT_AGENT_ID,
35596
- homeDir: homedir4()
35935
+ homeDir: homedir5()
35597
35936
  });
35598
35937
  var AGT_AGENT_CODE_NAME = process.env.AGT_AGENT_CODE_NAME;
35599
35938
  var directChatStderrLogStream = null;
35600
35939
  if (AGT_AGENT_CODE_NAME) {
35601
35940
  try {
35602
- const logDir = join12(homedir4(), ".augmented", AGT_AGENT_CODE_NAME);
35941
+ const logDir = join13(homedir5(), ".augmented", AGT_AGENT_CODE_NAME);
35603
35942
  mkdirSync5(logDir, { recursive: true });
35604
35943
  directChatStderrLogStream = createWriteStream(
35605
- join12(logDir, "direct-chat-channel-stderr.log"),
35944
+ join13(logDir, "direct-chat-channel-stderr.log"),
35606
35945
  { flags: "a", mode: 384 }
35607
35946
  );
35608
35947
  directChatStderrLogStream.on("error", () => {
@@ -35621,10 +35960,10 @@ if (AGT_AGENT_CODE_NAME) {
35621
35960
  } catch {
35622
35961
  }
35623
35962
  }
35624
- var PROGRESS_HEARTBEAT_PATH = AGT_AGENT_CODE_NAME ? join12(homedir4(), ".augmented", AGT_AGENT_CODE_NAME, "channel-progress-heartbeat.json") : null;
35625
- var DIRECT_CHAT_PENDING_INBOUND_DIR = AGT_AGENT_CODE_NAME ? join12(homedir4(), ".augmented", AGT_AGENT_CODE_NAME, "direct-chat-pending-inbound") : null;
35626
- var DIRECT_CHAT_DELIVERY_LEDGER_DIR = AGT_AGENT_CODE_NAME ? join12(homedir4(), ".augmented", AGT_AGENT_CODE_NAME, ".agt-inbound-delivery-ledger") : null;
35627
- var DIRECT_CHAT_CODE_NAME_AGENT_DIR = AGT_AGENT_CODE_NAME ? join12(homedir4(), ".augmented", AGT_AGENT_CODE_NAME) : null;
35963
+ var PROGRESS_HEARTBEAT_PATH = AGT_AGENT_CODE_NAME ? join13(homedir5(), ".augmented", AGT_AGENT_CODE_NAME, "channel-progress-heartbeat.json") : null;
35964
+ var DIRECT_CHAT_PENDING_INBOUND_DIR = AGT_AGENT_CODE_NAME ? join13(homedir5(), ".augmented", AGT_AGENT_CODE_NAME, "direct-chat-pending-inbound") : null;
35965
+ var DIRECT_CHAT_DELIVERY_LEDGER_DIR = AGT_AGENT_CODE_NAME ? join13(homedir5(), ".augmented", AGT_AGENT_CODE_NAME, ".agt-inbound-delivery-ledger") : null;
35966
+ var DIRECT_CHAT_CODE_NAME_AGENT_DIR = AGT_AGENT_CODE_NAME ? join13(homedir5(), ".augmented", AGT_AGENT_CODE_NAME) : null;
35628
35967
  var DIRECT_CHAT_STALE_MARKER_MS = 24 * 60 * 60 * 1e3;
35629
35968
  function recordDirectChatDelivery(sessionId, messageIds) {
35630
35969
  if (!sessionId) return;
@@ -35635,8 +35974,8 @@ function recordDirectChatDelivery(sessionId, messageIds) {
35635
35974
  delivered_at: (/* @__PURE__ */ new Date()).toISOString()
35636
35975
  });
35637
35976
  }
35638
- var DIRECT_CHAT_RECOVERY_OUTBOX_DIR = AGT_AGENT_CODE_NAME ? join12(homedir4(), ".augmented", AGT_AGENT_CODE_NAME, "direct-chat-recovery-outbox") : null;
35639
- var DIRECT_CHAT_RECOVERY_LEDGER_DIR = AGT_AGENT_CODE_NAME ? join12(homedir4(), ".augmented", AGT_AGENT_CODE_NAME, ".agt-direct-chat-recovery-ledger") : null;
35977
+ var DIRECT_CHAT_RECOVERY_OUTBOX_DIR = AGT_AGENT_CODE_NAME ? join13(homedir5(), ".augmented", AGT_AGENT_CODE_NAME, "direct-chat-recovery-outbox") : null;
35978
+ var DIRECT_CHAT_RECOVERY_LEDGER_DIR = AGT_AGENT_CODE_NAME ? join13(homedir5(), ".augmented", AGT_AGENT_CODE_NAME, ".agt-direct-chat-recovery-ledger") : null;
35640
35979
  var progressReceivedAt = /* @__PURE__ */ new Map();
35641
35980
  var directChatProgressState = { tracked: null };
35642
35981
  var directChatProgressTickRunning = false;
@@ -35655,7 +35994,7 @@ var directChatKanbanCardClient = createKanbanCardActiveClient({
35655
35994
  function readProgressHeartbeat() {
35656
35995
  if (!PROGRESS_HEARTBEAT_PATH || !existsSync6(PROGRESS_HEARTBEAT_PATH)) return null;
35657
35996
  try {
35658
- return parseProgressHeartbeat(readFileSync11(PROGRESS_HEARTBEAT_PATH, "utf-8"));
35997
+ return parseProgressHeartbeat(readFileSync12(PROGRESS_HEARTBEAT_PATH, "utf-8"));
35659
35998
  } catch {
35660
35999
  return null;
35661
36000
  }
@@ -35664,7 +36003,7 @@ function seedProgressHeartbeat() {
35664
36003
  if (!PROGRESS_HEARTBEAT_PATH) return;
35665
36004
  const tmp = `${PROGRESS_HEARTBEAT_PATH}.${process.pid}.tmp`;
35666
36005
  try {
35667
- mkdirSync5(join12(homedir4(), ".augmented", AGT_AGENT_CODE_NAME), { recursive: true });
36006
+ mkdirSync5(join13(homedir5(), ".augmented", AGT_AGENT_CODE_NAME), { recursive: true });
35668
36007
  writeFileSync6(tmp, serializeProgressHeartbeat(SEED_PROGRESS_STEP, Date.now()), { mode: 384 });
35669
36008
  renameSync5(tmp, PROGRESS_HEARTBEAT_PATH);
35670
36009
  } catch {
@@ -35785,7 +36124,7 @@ var STREAM_REPLY_FLUSH_MS = (() => {
35785
36124
  const n = raw ? Number(raw) : NaN;
35786
36125
  return Number.isFinite(n) && n >= 0 ? n : 60;
35787
36126
  })();
35788
- var sleep = (ms) => ms > 0 ? new Promise((resolve) => setTimeout(resolve, ms)) : Promise.resolve();
36127
+ var sleep2 = (ms) => ms > 0 ? new Promise((resolve) => setTimeout(resolve, ms)) : Promise.resolve();
35789
36128
  async function apiPost(path, body) {
35790
36129
  const token = await getAuthToken();
35791
36130
  const res = await fetchWithTimeout(`${AGT_HOST}${path}`, {
@@ -35856,7 +36195,7 @@ var inboundAttachmentDeps = {
35856
36195
  },
35857
36196
  ensureDir: (dir) => mkdirSync5(dir, { recursive: true }),
35858
36197
  writeFile: (path, bytes) => writeFileSync6(path, bytes, { mode: 384 }),
35859
- joinPath: (...parts) => join12(...parts),
36198
+ joinPath: (...parts) => join13(...parts),
35860
36199
  warn: (msg) => process.stderr.write(`${msg}
35861
36200
  `)
35862
36201
  };
@@ -36035,11 +36374,11 @@ mcp.setRequestHandler(CallToolRequestSchema, async (req) => {
36035
36374
  recordDirectChatDelivery(session_id, message_ids);
36036
36375
  const messageId = data.message_id;
36037
36376
  for (let i = 1; i < slices.length; i++) {
36038
- await sleep(STREAM_REPLY_FLUSH_MS);
36377
+ await sleep2(STREAM_REPLY_FLUSH_MS);
36039
36378
  const isFinal = i === slices.length - 1;
36040
36379
  let ok = await postDirectChatUpdate(messageId, session_id, slices[i]);
36041
36380
  if (!ok && isFinal) {
36042
- await sleep(STREAM_REPLY_FLUSH_MS);
36381
+ await sleep2(STREAM_REPLY_FLUSH_MS);
36043
36382
  ok = await postDirectChatUpdate(messageId, session_id, slices[i]);
36044
36383
  if (!ok) {
36045
36384
  return {
@@ -36188,7 +36527,7 @@ function scheduleDirectChatBusyAck(sessionId, messageId, arrivedWhileBusy) {
36188
36527
  let paneLogFreshAgeMs = null;
36189
36528
  if (DIRECT_CHAT_CODE_NAME_AGENT_DIR) {
36190
36529
  try {
36191
- const paneMtimeMs = statSync2(join12(DIRECT_CHAT_CODE_NAME_AGENT_DIR, "pane.log")).mtimeMs;
36530
+ const paneMtimeMs = statSync3(join13(DIRECT_CHAT_CODE_NAME_AGENT_DIR, "pane.log")).mtimeMs;
36192
36531
  paneLogFreshAgeMs = Math.max(0, Date.now() - paneMtimeMs);
36193
36532
  } catch {
36194
36533
  }
@@ -36236,6 +36575,63 @@ function scheduleDirectChatBusyAck(sessionId, messageId, arrivedWhileBusy) {
36236
36575
  }, thresholdMs);
36237
36576
  timer.unref();
36238
36577
  }
36578
+ function armUsageLimitWatch(args) {
36579
+ void (async () => {
36580
+ try {
36581
+ const refusal = await watchForRateLimitRefusal({ sinceMs: args.sinceMs });
36582
+ if (!refusal) return;
36583
+ if (!refusal.resetsAt) {
36584
+ process.stderr.write(
36585
+ `direct-chat-channel: [usage-limit-reactive] mode=${args.mode} refusal detected for session=${args.sessionId} but no reset time in it (${refusal.text ?? "no text"}) - no notice
36586
+ `
36587
+ );
36588
+ return;
36589
+ }
36590
+ if (args.mode === "shadow") {
36591
+ process.stderr.write(
36592
+ `direct-chat-channel: [usage-limit-reactive] mode=shadow WOULD notify session=${args.sessionId} message=${args.messageId} resets=${refusal.resetsAt.toISOString()}
36593
+ `
36594
+ );
36595
+ return;
36596
+ }
36597
+ const throttleKey = `${args.sessionId}|usage_limit`;
36598
+ const throttle = decideDeclineReply({
36599
+ cache: DIRECT_CHAT_USAGE_LIMIT_CACHE,
36600
+ key: throttleKey,
36601
+ cooldownMs: DIRECT_CHAT_MAINTENANCE_COOLDOWN_MS,
36602
+ now: Date.now()
36603
+ });
36604
+ const route = throttle.reply ? "/host/direct-chat/reply" : "/host/direct-chat/consume";
36605
+ const body = throttle.reply ? {
36606
+ agent_id: AGT_AGENT_ID,
36607
+ session_id: args.sessionId,
36608
+ content: buildUsageLimitReplyText(refusal.resetsAt),
36609
+ message_ids: [args.messageId]
36610
+ } : { agent_id: AGT_AGENT_ID, session_id: args.sessionId, message_ids: [args.messageId] };
36611
+ const res = await apiPost(route, body);
36612
+ const data = await res.json().catch(() => ({}));
36613
+ if (res.ok && data.error == null) {
36614
+ claimTracker.clear(args.sessionId, [args.messageId]);
36615
+ clearDirectChatPendingMarkersForSession(DIRECT_CHAT_PENDING_INBOUND_DIR, args.sessionId);
36616
+ process.stderr.write(
36617
+ `direct-chat-channel: [usage-limit-reactive] ${throttle.reply ? "reported" : "consumed (notice throttled)"} refusal for session=${args.sessionId} message=${args.messageId} resets=${refusal.resetsAt.toISOString()}
36618
+ `
36619
+ );
36620
+ } else {
36621
+ if (throttle.reply) DIRECT_CHAT_USAGE_LIMIT_CACHE.delete(throttleKey);
36622
+ process.stderr.write(
36623
+ `direct-chat-channel: [usage-limit-reactive] ${throttle.reply ? "notice" : "consume"} failed for session=${args.sessionId}: ${data.error ?? `HTTP ${res.status}`}
36624
+ `
36625
+ );
36626
+ }
36627
+ } catch (err) {
36628
+ process.stderr.write(
36629
+ `direct-chat-channel: [usage-limit-reactive] watch failed for session=${args.sessionId}: ${err.message}
36630
+ `
36631
+ );
36632
+ }
36633
+ })();
36634
+ }
36239
36635
  async function pollForMessages(sinceMs) {
36240
36636
  try {
36241
36637
  const res = await apiPost("/host/direct-chat/poll", {
@@ -36262,7 +36658,8 @@ async function pollForMessages(sinceMs) {
36262
36658
  const isNotice = msg.kind === "notice";
36263
36659
  const maintenanceActive = !isNotice && isMaintenanceModeActive();
36264
36660
  const accountMuted = !isNotice && !maintenanceActive ? readAccountEnforcementLevel({ codeName: AGT_AGENT_CODE_NAME ?? null }) === "mute" : false;
36265
- const usageLimitUntil = !isNotice && !maintenanceActive && !accountMuted ? readUsageLimitUntil({ codeName: AGT_AGENT_CODE_NAME ?? null }) : null;
36661
+ const reactiveMode = resolveUsageLimitReactiveMode();
36662
+ const usageLimitUntil = !isNotice && !maintenanceActive && !accountMuted && reactiveMode !== "enforce" ? readUsageLimitUntil({ codeName: AGT_AGENT_CODE_NAME ?? null }) : null;
36266
36663
  if (!isNotice && !maintenanceActive && !accountMuted && !usageLimitUntil) {
36267
36664
  markProcessed(msg.id);
36268
36665
  claimTracker.track(msg.session_id, msg.id);
@@ -36380,6 +36777,7 @@ async function pollForMessages(sinceMs) {
36380
36777
  if (!isNotice && msg.sender_id) {
36381
36778
  clearScheduledTurnMarker(DIRECT_CHAT_CODE_NAME_AGENT_DIR);
36382
36779
  }
36780
+ const dispatchedAtMs = Date.now();
36383
36781
  await mcp.notification({
36384
36782
  method: "notifications/claude/channel",
36385
36783
  params: {
@@ -36440,6 +36838,14 @@ async function pollForMessages(sinceMs) {
36440
36838
  `direct-chat-channel: Injected message ${msg.id} (session=${msg.session_id})
36441
36839
  `
36442
36840
  );
36841
+ if (!isNotice && reactiveMode !== "off") {
36842
+ armUsageLimitWatch({
36843
+ mode: reactiveMode,
36844
+ sessionId: msg.session_id,
36845
+ messageId: msg.id,
36846
+ sinceMs: dispatchedAtMs
36847
+ });
36848
+ }
36443
36849
  }
36444
36850
  } catch (err) {
36445
36851
  process.stderr.write(
@@ -36545,8 +36951,8 @@ function sweepAgedDirectChatMarkersNow(thresholdMs) {
36545
36951
  if (!DIRECT_CHAT_PENDING_INBOUND_DIR) return;
36546
36952
  const now = Date.now();
36547
36953
  const res = sweepAgedDirectChatMarkers(DIRECT_CHAT_PENDING_INBOUND_DIR, {
36548
- readdir: (dir) => readdirSync4(dir),
36549
- readFile: (p2) => readFileSync11(p2, "utf8"),
36954
+ readdir: (dir) => readdirSync5(dir),
36955
+ readFile: (p2) => readFileSync12(p2, "utf8"),
36550
36956
  unlink: (p2) => {
36551
36957
  if (existsSync6(p2)) unlinkSync4(p2);
36552
36958
  },
@@ -36582,7 +36988,7 @@ function sanitizeRecoveryText(text) {
36582
36988
  }
36583
36989
  function directChatRecoveryDeps() {
36584
36990
  return {
36585
- readFile: (p2) => readFileSync11(p2, "utf-8"),
36991
+ readFile: (p2) => readFileSync12(p2, "utf-8"),
36586
36992
  renameFile: (from, to) => renameSync5(from, to),
36587
36993
  unlinkFile: (p2) => {
36588
36994
  if (existsSync6(p2)) unlinkSync4(p2);
@@ -36595,7 +37001,7 @@ function directChatRecoveryDeps() {
36595
37001
  if (!DIRECT_CHAT_RECOVERY_LEDGER_DIR) return;
36596
37002
  if (markerName.includes("/") || markerName.includes("\\") || markerName.includes("..")) return;
36597
37003
  try {
36598
- const p2 = join12(DIRECT_CHAT_RECOVERY_LEDGER_DIR, markerName);
37004
+ const p2 = join13(DIRECT_CHAT_RECOVERY_LEDGER_DIR, markerName);
36599
37005
  if (existsSync6(p2)) unlinkSync4(p2);
36600
37006
  } catch {
36601
37007
  }
@@ -36631,7 +37037,7 @@ async function processDirectChatRecoveryOutboxFile(filename) {
36631
37037
  if (!enabled) return;
36632
37038
  directChatRecoveryInFlight.add(filename);
36633
37039
  try {
36634
- const fullPath = join12(DIRECT_CHAT_RECOVERY_OUTBOX_DIR, filename);
37040
+ const fullPath = join13(DIRECT_CHAT_RECOVERY_OUTBOX_DIR, filename);
36635
37041
  await consumeDirectChatRecoveryFile(fullPath, filename, directChatRecoveryDeps());
36636
37042
  } catch (err) {
36637
37043
  process.stderr.write(
@@ -36649,7 +37055,7 @@ if (DIRECT_CHAT_RECOVERY_OUTBOX_DIR) {
36649
37055
  } catch {
36650
37056
  }
36651
37057
  try {
36652
- for (const f of readdirSync4(DIRECT_CHAT_RECOVERY_OUTBOX_DIR)) {
37058
+ for (const f of readdirSync5(DIRECT_CHAT_RECOVERY_OUTBOX_DIR)) {
36653
37059
  if (f.endsWith(".json")) void processDirectChatRecoveryOutboxFile(f);
36654
37060
  }
36655
37061
  } catch {
@@ -36659,7 +37065,7 @@ if (DIRECT_CHAT_RECOVERY_OUTBOX_DIR) {
36659
37065
  if (!filename) return;
36660
37066
  const name = filename.toString();
36661
37067
  if (!name.endsWith(".json")) return;
36662
- if (existsSync6(join12(DIRECT_CHAT_RECOVERY_OUTBOX_DIR, name))) {
37068
+ if (existsSync6(join13(DIRECT_CHAT_RECOVERY_OUTBOX_DIR, name))) {
36663
37069
  void processDirectChatRecoveryOutboxFile(name);
36664
37070
  }
36665
37071
  });