@integrity-labs/agt-cli 0.28.419 → 0.28.421

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.
@@ -25,7 +25,7 @@ import {
25
25
  resolveConnectivityProbe,
26
26
  worseConnectivityOutcome,
27
27
  wrapScheduledTaskPrompt
28
- } from "./chunk-IKQHNN7I.js";
28
+ } from "./chunk-B4NX2T2H.js";
29
29
  import {
30
30
  parsePsRows
31
31
  } from "./chunk-XWVM4KPK.js";
@@ -6011,7 +6011,7 @@ function requireHost() {
6011
6011
  }
6012
6012
 
6013
6013
  // src/lib/api-client.ts
6014
- var agtCliVersion = true ? "0.28.419" : "dev";
6014
+ var agtCliVersion = true ? "0.28.421" : "dev";
6015
6015
  var lastConfigHash = null;
6016
6016
  function setConfigHash(hash) {
6017
6017
  lastConfigHash = hash && hash.length > 0 ? hash : null;
@@ -8515,4 +8515,4 @@ export {
8515
8515
  managerInstallSystemUnitCommand,
8516
8516
  managerUninstallSystemUnitCommand
8517
8517
  };
8518
- //# sourceMappingURL=chunk-W63FDJBY.js.map
8518
+ //# sourceMappingURL=chunk-HVVEMQWZ.js.map
@@ -100,7 +100,7 @@ async function spawnPairSession(session) {
100
100
  return { ok: true };
101
101
  } catch {
102
102
  }
103
- const { resolveClaudeBinary } = await import("./persistent-session-AYCUX4DJ.js");
103
+ const { resolveClaudeBinary } = await import("./persistent-session-2OYYXMM7.js");
104
104
  const claudeBin = resolveClaudeBinary();
105
105
  const pairEnv = {
106
106
  ...process.env,
@@ -373,4 +373,4 @@ export {
373
373
  startClaudePair,
374
374
  submitClaudePairCode
375
375
  };
376
- //# sourceMappingURL=claude-pair-runtime-UOH5MOK4.js.map
376
+ //# sourceMappingURL=claude-pair-runtime-ZTZR3VCN.js.map
@@ -45,7 +45,7 @@ import {
45
45
  resolveEffectivePinRaw,
46
46
  safeWriteJsonAtomic,
47
47
  setConfigHash
48
- } from "../chunk-W63FDJBY.js";
48
+ } from "../chunk-HVVEMQWZ.js";
49
49
  import {
50
50
  getProjectDir as getProjectDir2,
51
51
  getReadyTasks,
@@ -150,7 +150,7 @@ import {
150
150
  toOpencodeModel,
151
151
  transcriptActivityAgeSeconds,
152
152
  writeEgressAllowlist
153
- } from "../chunk-IKQHNN7I.js";
153
+ } from "../chunk-B4NX2T2H.js";
154
154
  import {
155
155
  reapOrphanChannelMcps
156
156
  } from "../chunk-XWVM4KPK.js";
@@ -9458,7 +9458,7 @@ var agentRestartTimezoneInputs = /* @__PURE__ */ new Map();
9458
9458
  var lastVersionCheckAt = 0;
9459
9459
  var VERSION_CHECK_INTERVAL_MS = 5 * 60 * 1e3;
9460
9460
  var lastResponsivenessProbeAt = 0;
9461
- var agtCliVersion = true ? "0.28.419" : "dev";
9461
+ var agtCliVersion = true ? "0.28.421" : "dev";
9462
9462
  function resolveBrewPath(execFileSync2) {
9463
9463
  try {
9464
9464
  const out = execFileSync2("which", ["brew"], { timeout: 5e3 }).toString().trim();
@@ -10606,7 +10606,7 @@ function flushRestartedAgentDiagnostics(hostId, codeNames) {
10606
10606
  if (codeNames.length === 0) return;
10607
10607
  void (async () => {
10608
10608
  try {
10609
- const { collectDiagnostics } = await import("../persistent-session-AYCUX4DJ.js");
10609
+ const { collectDiagnostics } = await import("../persistent-session-2OYYXMM7.js");
10610
10610
  await api.post("/host/heartbeat", {
10611
10611
  host_id: hostId,
10612
10612
  agent_diagnostics: collectDiagnostics(codeNames, quarantineEntriesFor)
@@ -10713,7 +10713,7 @@ async function pollCycle() {
10713
10713
  }
10714
10714
  try {
10715
10715
  const { detectHostSecurity } = await import("../host-security-6PDFG7F5.js");
10716
- const { collectDiagnostics } = await import("../persistent-session-AYCUX4DJ.js");
10716
+ const { collectDiagnostics } = await import("../persistent-session-2OYYXMM7.js");
10717
10717
  const diagCodeNames = [...agentState.persistentSessionAgents];
10718
10718
  const agentDiagnostics = diagCodeNames.length > 0 ? collectDiagnostics(diagCodeNames, quarantineEntriesFor) : void 0;
10719
10719
  let tailscaleHostname;
@@ -10827,7 +10827,7 @@ async function pollCycle() {
10827
10827
  collectResponsivenessProbes,
10828
10828
  collectPanelessActivityProbes,
10829
10829
  getResponsivenessIntervalMs
10830
- } = await import("../responsiveness-probe-AQUOYHCE.js");
10830
+ } = await import("../responsiveness-probe-7OOA57BP.js");
10831
10831
  const probeIntervalMs = getResponsivenessIntervalMs();
10832
10832
  if (now - lastResponsivenessProbeAt > probeIntervalMs) {
10833
10833
  const probeCodeNames = [...agentState.persistentSessionAgents];
@@ -10866,7 +10866,7 @@ async function pollCycle() {
10866
10866
  collectResponsivenessProbes,
10867
10867
  livePendingInboundOldestAgeSeconds,
10868
10868
  parkPendingInbound
10869
- } = await import("../responsiveness-probe-AQUOYHCE.js");
10869
+ } = await import("../responsiveness-probe-7OOA57BP.js");
10870
10870
  const { getProjectDir: wedgeProjectDir } = await import("../scheduler-engine-HYOOQV4A.js");
10871
10871
  const wedgeNow = /* @__PURE__ */ new Date();
10872
10872
  const liveAgents = agentState.persistentSessionAgents;
@@ -14177,7 +14177,7 @@ async function handleRestartDoorbell(agentId, requestedAt, restartReason) {
14177
14177
  void api.post("/host/restart-ack", { host_id: hostId, agent_id: agentId, restart_requested_at: requestedAt }).catch((err) => log(`[restart-lane] ack failed for '${codeName}': ${err.message}`));
14178
14178
  void (async () => {
14179
14179
  try {
14180
- const { collectDiagnostics } = await import("../persistent-session-AYCUX4DJ.js");
14180
+ const { collectDiagnostics } = await import("../persistent-session-2OYYXMM7.js");
14181
14181
  await api.post("/host/heartbeat", {
14182
14182
  host_id: hostId,
14183
14183
  agent_diagnostics: collectDiagnostics([codeName], quarantineEntriesFor)
@@ -14227,7 +14227,7 @@ async function respawnAgentAfterMcpStop(codeName, reason) {
14227
14227
  }
14228
14228
  try {
14229
14229
  const hostId = await getHostId();
14230
- const { collectDiagnostics } = await import("../persistent-session-AYCUX4DJ.js");
14230
+ const { collectDiagnostics } = await import("../persistent-session-2OYYXMM7.js");
14231
14231
  await api.post("/host/heartbeat", {
14232
14232
  host_id: hostId,
14233
14233
  agent_diagnostics: collectDiagnostics([codeName], quarantineEntriesFor)
@@ -14680,7 +14680,7 @@ async function processClaudePairSessions(agents) {
14680
14680
  killPairSession,
14681
14681
  pairTmuxSession,
14682
14682
  finalizeClaudePairOnboarding
14683
- } = await import("../claude-pair-runtime-UOH5MOK4.js");
14683
+ } = await import("../claude-pair-runtime-ZTZR3VCN.js");
14684
14684
  for (const pairId of pendingResp.cancelled_pair_ids ?? []) {
14685
14685
  log(`[claude-pair] sweeping orphan tmux session for pair ${pairId.slice(0, 8)}`);
14686
14686
  const killed = await killPairSession(pairTmuxSession(pairId));
@@ -32300,19 +32300,27 @@ var env = new import_nunjucks.default.Environment(null, { autoescape: false });
32300
32300
 
32301
32301
  // ../core/dist/provisioning/ec2-capacity.js
32302
32302
  var CAPACITY_TABLE = {
32303
- // t3 family — burst-credit instances. Lookup is per-vCPU plus
32304
- // headroom for the manager process itself.
32303
+ // t3 family — burstable (CPU credits). Fine for these mostly-I/O-bound
32304
+ // agents; the monitor + resize path cover the rare sustained-load case.
32305
32305
  "t3.micro": 1,
32306
- // 1 vCPU / 1 GB — barely fits one agent
32307
- "t3.small": 1,
32308
- // 2 vCPU / 2 GB — still tight
32309
- "t3.medium": 2,
32310
- // 2 vCPU / 4 GB — the sweet spot for a 2-agent host
32311
- "t3.large": 4,
32312
- // 2 vCPU / 8 GB — bursts cover the headroom
32313
- "t3.xlarge": 8,
32306
+ // 2 vCPU / 1 GB — floor
32307
+ "t3.small": 2,
32308
+ // 2 vCPU / 2 GB
32309
+ "t3.medium": 4,
32310
+ // 2 vCPU / 4 GB
32311
+ "t3.large": 8,
32312
+ // 2 vCPU / 8 GB
32313
+ "t3.xlarge": 16,
32314
32314
  // 4 vCPU / 16 GB
32315
- "t3.2xlarge": 16
32315
+ "t3.2xlarge": 32,
32316
+ // 8 vCPU / 32 GB
32317
+ // m6i family — fixed performance (no burst-credit cliff), the preferred
32318
+ // family for dense / sustained multi-agent hosts (e.g. Enterprise default).
32319
+ "m6i.large": 8,
32320
+ // 2 vCPU / 8 GB
32321
+ "m6i.xlarge": 16,
32322
+ // 4 vCPU / 16 GB
32323
+ "m6i.2xlarge": 32
32316
32324
  // 8 vCPU / 32 GB
32317
32325
  };
32318
32326
  var KNOWN_INSTANCE_TYPES = Object.keys(CAPACITY_TABLE);
@@ -34654,6 +34662,27 @@ var FLAG_REGISTRY = [
34654
34662
  // change (ADR-0022 sensitive-flag confirm).
34655
34663
  sensitive: true
34656
34664
  },
34665
+ {
34666
+ key: "slack-scheduled-channel-guard",
34667
+ description: 'Guard on slack.reply that refuses to let an active scheduled-task destination silently move a reply to a DIFFERENT channel than the agent named (ENG-8137). CS-1505 gave an explicit inbound_id priority over the task destination but left the no-inbound_id case treating "no inbound reference" as "no destination specified" - and `channel` is a REQUIRED argument, so every call names one. The symptom: a question asked in one channel answered in another, and a reply meant as a DM landing in a public channel. Fires only when the marker carries an EXPLICIT target, the named channel differs from it, and neither an inbound_id, an enforce binding, nor an ENG-7542 channel correction is available to arbitrate. off = never runs (today behaviour: silent retarget). shadow = count + log the would-block but STILL route as today, so the false-positive rate is measurable before anyone refuses a send. enforce = block the reply and tell the agent to retry. Read live from the heartbeat flags-cache (or the env override). NOTE: enforce is currently CLAMPED to shadow host-side and setting it here will not take effect - it stays blocked until ENG-8142 teaches the Stop hook to tell a refused reply from a delivered one, because until then a blocked send fails silently (no recovery, no re-prompt, nothing in the channel). Lifting the clamp is a code change, not a flag flip.',
34668
+ flagType: "enum",
34669
+ allowedValues: ["off", "shadow", "enforce"],
34670
+ // Ships in SHADOW, not off: the counter is the whole point - the fleet metric
34671
+ // is currently blind to this misroute, which is why it went unnoticed. shadow
34672
+ // is a free local log line + counter (no model spend, no behaviour change), so
34673
+ // it measures the fire rate immediately. enforce turns a misroute into a
34674
+ // REFUSED send, which trades a confidentiality risk for an availability one -
34675
+ // that is the audited per-org flip, taken after reading the shadow numbers and
34676
+ // after ENG-8143 shrinks the stale-marker window that dominates the false
34677
+ // positives.
34678
+ defaultValue: "shadow",
34679
+ // Enum override AGT_SLACK_SCHEDULED_CHANNEL_GUARD_MODE (off|shadow|enforce),
34680
+ // resolved by resolveSlackScheduledChannelGuardMode in the channel-server bundle.
34681
+ envVar: "AGT_SLACK_SCHEDULED_CHANNEL_GUARD_MODE",
34682
+ // enforce REFUSES a send the agent asked to make - a visible availability
34683
+ // change, so flipping toward it is deliberate (ADR-0022 sensitive-flag confirm).
34684
+ sensitive: true
34685
+ },
34657
34686
  {
34658
34687
  key: "slack-hot-thread-guard",
34659
34688
  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.",
@@ -37952,19 +37952,27 @@ var env = new import_nunjucks.default.Environment(null, { autoescape: false });
37952
37952
 
37953
37953
  // ../core/dist/provisioning/ec2-capacity.js
37954
37954
  var CAPACITY_TABLE = {
37955
- // t3 family — burst-credit instances. Lookup is per-vCPU plus
37956
- // headroom for the manager process itself.
37955
+ // t3 family — burstable (CPU credits). Fine for these mostly-I/O-bound
37956
+ // agents; the monitor + resize path cover the rare sustained-load case.
37957
37957
  "t3.micro": 1,
37958
- // 1 vCPU / 1 GB — barely fits one agent
37959
- "t3.small": 1,
37960
- // 2 vCPU / 2 GB — still tight
37961
- "t3.medium": 2,
37962
- // 2 vCPU / 4 GB — the sweet spot for a 2-agent host
37963
- "t3.large": 4,
37964
- // 2 vCPU / 8 GB — bursts cover the headroom
37965
- "t3.xlarge": 8,
37958
+ // 2 vCPU / 1 GB — floor
37959
+ "t3.small": 2,
37960
+ // 2 vCPU / 2 GB
37961
+ "t3.medium": 4,
37962
+ // 2 vCPU / 4 GB
37963
+ "t3.large": 8,
37964
+ // 2 vCPU / 8 GB
37965
+ "t3.xlarge": 16,
37966
37966
  // 4 vCPU / 16 GB
37967
- "t3.2xlarge": 16
37967
+ "t3.2xlarge": 32,
37968
+ // 8 vCPU / 32 GB
37969
+ // m6i family — fixed performance (no burst-credit cliff), the preferred
37970
+ // family for dense / sustained multi-agent hosts (e.g. Enterprise default).
37971
+ "m6i.large": 8,
37972
+ // 2 vCPU / 8 GB
37973
+ "m6i.xlarge": 16,
37974
+ // 4 vCPU / 16 GB
37975
+ "m6i.2xlarge": 32
37968
37976
  // 8 vCPU / 32 GB
37969
37977
  };
37970
37978
  var KNOWN_INSTANCE_TYPES = Object.keys(CAPACITY_TABLE);
@@ -40270,6 +40278,27 @@ var FLAG_REGISTRY = [
40270
40278
  // change (ADR-0022 sensitive-flag confirm).
40271
40279
  sensitive: true
40272
40280
  },
40281
+ {
40282
+ key: "slack-scheduled-channel-guard",
40283
+ description: 'Guard on slack.reply that refuses to let an active scheduled-task destination silently move a reply to a DIFFERENT channel than the agent named (ENG-8137). CS-1505 gave an explicit inbound_id priority over the task destination but left the no-inbound_id case treating "no inbound reference" as "no destination specified" - and `channel` is a REQUIRED argument, so every call names one. The symptom: a question asked in one channel answered in another, and a reply meant as a DM landing in a public channel. Fires only when the marker carries an EXPLICIT target, the named channel differs from it, and neither an inbound_id, an enforce binding, nor an ENG-7542 channel correction is available to arbitrate. off = never runs (today behaviour: silent retarget). shadow = count + log the would-block but STILL route as today, so the false-positive rate is measurable before anyone refuses a send. enforce = block the reply and tell the agent to retry. Read live from the heartbeat flags-cache (or the env override). NOTE: enforce is currently CLAMPED to shadow host-side and setting it here will not take effect - it stays blocked until ENG-8142 teaches the Stop hook to tell a refused reply from a delivered one, because until then a blocked send fails silently (no recovery, no re-prompt, nothing in the channel). Lifting the clamp is a code change, not a flag flip.',
40284
+ flagType: "enum",
40285
+ allowedValues: ["off", "shadow", "enforce"],
40286
+ // Ships in SHADOW, not off: the counter is the whole point - the fleet metric
40287
+ // is currently blind to this misroute, which is why it went unnoticed. shadow
40288
+ // is a free local log line + counter (no model spend, no behaviour change), so
40289
+ // it measures the fire rate immediately. enforce turns a misroute into a
40290
+ // REFUSED send, which trades a confidentiality risk for an availability one -
40291
+ // that is the audited per-org flip, taken after reading the shadow numbers and
40292
+ // after ENG-8143 shrinks the stale-marker window that dominates the false
40293
+ // positives.
40294
+ defaultValue: "shadow",
40295
+ // Enum override AGT_SLACK_SCHEDULED_CHANNEL_GUARD_MODE (off|shadow|enforce),
40296
+ // resolved by resolveSlackScheduledChannelGuardMode in the channel-server bundle.
40297
+ envVar: "AGT_SLACK_SCHEDULED_CHANNEL_GUARD_MODE",
40298
+ // enforce REFUSES a send the agent asked to make - a visible availability
40299
+ // change, so flipping toward it is deliberate (ADR-0022 sensitive-flag confirm).
40300
+ sensitive: true
40301
+ },
40273
40302
  {
40274
40303
  key: "slack-hot-thread-guard",
40275
40304
  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.",
@@ -30345,6 +30345,54 @@ function resolveSlackHotThreadMode(opts) {
30345
30345
  if (cached2 !== void 0) return cached2;
30346
30346
  return "off";
30347
30347
  }
30348
+ var SLACK_SCHEDULED_CHANNEL_GUARD_FLAG_KEY = "slack-scheduled-channel-guard";
30349
+ function parseSlackScheduledChannelGuardMode(raw) {
30350
+ if (raw === void 0) return void 0;
30351
+ const v = raw.trim().toLowerCase();
30352
+ if (v === "") return void 0;
30353
+ if (v === "off" || v === "shadow" || v === "enforce") return v;
30354
+ const b = envBoolean(raw);
30355
+ if (b === true) return "enforce";
30356
+ if (b === false) return "off";
30357
+ return void 0;
30358
+ }
30359
+ function cachedSlackScheduledChannelGuardMode(path) {
30360
+ try {
30361
+ if (!existsSync(path)) return void 0;
30362
+ const parsed = JSON.parse(readFileSync(path, "utf8"));
30363
+ if (!parsed || typeof parsed !== "object") return void 0;
30364
+ const flags = parsed.flags;
30365
+ if (!flags || typeof flags !== "object") return void 0;
30366
+ const value = flags[SLACK_SCHEDULED_CHANNEL_GUARD_FLAG_KEY];
30367
+ if (typeof value === "string") return parseSlackScheduledChannelGuardMode(value);
30368
+ if (typeof value === "boolean") return value ? "enforce" : "off";
30369
+ return void 0;
30370
+ } catch {
30371
+ return void 0;
30372
+ }
30373
+ }
30374
+ var SCHEDULED_CHANNEL_GUARD_ENFORCE_READY = false;
30375
+ var warnedEnforceClamped = false;
30376
+ function clampScheduledChannelGuardMode(mode) {
30377
+ if (mode !== "enforce" || SCHEDULED_CHANNEL_GUARD_ENFORCE_READY) return mode;
30378
+ if (!warnedEnforceClamped) {
30379
+ warnedEnforceClamped = true;
30380
+ process.stderr.write(
30381
+ "slack-channel: slack-scheduled-channel-guard=enforce requested but CLAMPED to shadow - enforce is blocked until ENG-8142 (the Stop hook treats a refused reply as a delivered one, so a blocked send would fail silently). Detection and counters still run.\n"
30382
+ );
30383
+ }
30384
+ return "shadow";
30385
+ }
30386
+ function resolveSlackScheduledChannelGuardMode(opts) {
30387
+ const env2 = opts?.env ?? process.env;
30388
+ const modeOverride = parseSlackScheduledChannelGuardMode(
30389
+ env2["AGT_SLACK_SCHEDULED_CHANNEL_GUARD_MODE"]
30390
+ );
30391
+ if (modeOverride !== void 0) return clampScheduledChannelGuardMode(modeOverride);
30392
+ const cached2 = cachedSlackScheduledChannelGuardMode(opts?.cachePath ?? defaultFlagsCachePath());
30393
+ if (cached2 !== void 0) return clampScheduledChannelGuardMode(cached2);
30394
+ return "shadow";
30395
+ }
30348
30396
 
30349
30397
  // src/maintenance-mode.ts
30350
30398
  var FLAG_KEY = "platform-maintenance-mode";
@@ -31835,19 +31883,27 @@ var env = new import_nunjucks.default.Environment(null, { autoescape: false });
31835
31883
 
31836
31884
  // ../core/dist/provisioning/ec2-capacity.js
31837
31885
  var CAPACITY_TABLE = {
31838
- // t3 family — burst-credit instances. Lookup is per-vCPU plus
31839
- // headroom for the manager process itself.
31886
+ // t3 family — burstable (CPU credits). Fine for these mostly-I/O-bound
31887
+ // agents; the monitor + resize path cover the rare sustained-load case.
31840
31888
  "t3.micro": 1,
31841
- // 1 vCPU / 1 GB — barely fits one agent
31842
- "t3.small": 1,
31843
- // 2 vCPU / 2 GB — still tight
31844
- "t3.medium": 2,
31845
- // 2 vCPU / 4 GB — the sweet spot for a 2-agent host
31846
- "t3.large": 4,
31847
- // 2 vCPU / 8 GB — bursts cover the headroom
31848
- "t3.xlarge": 8,
31889
+ // 2 vCPU / 1 GB — floor
31890
+ "t3.small": 2,
31891
+ // 2 vCPU / 2 GB
31892
+ "t3.medium": 4,
31893
+ // 2 vCPU / 4 GB
31894
+ "t3.large": 8,
31895
+ // 2 vCPU / 8 GB
31896
+ "t3.xlarge": 16,
31897
+ // 4 vCPU / 16 GB
31898
+ "t3.2xlarge": 32,
31899
+ // 8 vCPU / 32 GB
31900
+ // m6i family — fixed performance (no burst-credit cliff), the preferred
31901
+ // family for dense / sustained multi-agent hosts (e.g. Enterprise default).
31902
+ "m6i.large": 8,
31903
+ // 2 vCPU / 8 GB
31904
+ "m6i.xlarge": 16,
31849
31905
  // 4 vCPU / 16 GB
31850
- "t3.2xlarge": 16
31906
+ "m6i.2xlarge": 32
31851
31907
  // 8 vCPU / 32 GB
31852
31908
  };
31853
31909
  var KNOWN_INSTANCE_TYPES = Object.keys(CAPACITY_TABLE);
@@ -34189,6 +34245,27 @@ var FLAG_REGISTRY = [
34189
34245
  // change (ADR-0022 sensitive-flag confirm).
34190
34246
  sensitive: true
34191
34247
  },
34248
+ {
34249
+ key: "slack-scheduled-channel-guard",
34250
+ description: 'Guard on slack.reply that refuses to let an active scheduled-task destination silently move a reply to a DIFFERENT channel than the agent named (ENG-8137). CS-1505 gave an explicit inbound_id priority over the task destination but left the no-inbound_id case treating "no inbound reference" as "no destination specified" - and `channel` is a REQUIRED argument, so every call names one. The symptom: a question asked in one channel answered in another, and a reply meant as a DM landing in a public channel. Fires only when the marker carries an EXPLICIT target, the named channel differs from it, and neither an inbound_id, an enforce binding, nor an ENG-7542 channel correction is available to arbitrate. off = never runs (today behaviour: silent retarget). shadow = count + log the would-block but STILL route as today, so the false-positive rate is measurable before anyone refuses a send. enforce = block the reply and tell the agent to retry. Read live from the heartbeat flags-cache (or the env override). NOTE: enforce is currently CLAMPED to shadow host-side and setting it here will not take effect - it stays blocked until ENG-8142 teaches the Stop hook to tell a refused reply from a delivered one, because until then a blocked send fails silently (no recovery, no re-prompt, nothing in the channel). Lifting the clamp is a code change, not a flag flip.',
34251
+ flagType: "enum",
34252
+ allowedValues: ["off", "shadow", "enforce"],
34253
+ // Ships in SHADOW, not off: the counter is the whole point - the fleet metric
34254
+ // is currently blind to this misroute, which is why it went unnoticed. shadow
34255
+ // is a free local log line + counter (no model spend, no behaviour change), so
34256
+ // it measures the fire rate immediately. enforce turns a misroute into a
34257
+ // REFUSED send, which trades a confidentiality risk for an availability one -
34258
+ // that is the audited per-org flip, taken after reading the shadow numbers and
34259
+ // after ENG-8143 shrinks the stale-marker window that dominates the false
34260
+ // positives.
34261
+ defaultValue: "shadow",
34262
+ // Enum override AGT_SLACK_SCHEDULED_CHANNEL_GUARD_MODE (off|shadow|enforce),
34263
+ // resolved by resolveSlackScheduledChannelGuardMode in the channel-server bundle.
34264
+ envVar: "AGT_SLACK_SCHEDULED_CHANNEL_GUARD_MODE",
34265
+ // enforce REFUSES a send the agent asked to make - a visible availability
34266
+ // change, so flipping toward it is deliberate (ADR-0022 sensitive-flag confirm).
34267
+ sensitive: true
34268
+ },
34192
34269
  {
34193
34270
  key: "slack-hot-thread-guard",
34194
34271
  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.",
@@ -37590,6 +37667,8 @@ var REPLY_BINDING_CLASSIFICATION_COUNTER_SUFFIX = "-reply-binding-classification
37590
37667
  var UNKNOWN_INBOUND_ID_KEY = "unknown_inbound_id";
37591
37668
  var CHANNEL_MISTARGET_CORRECTED_KEY = "channel_mistarget_corrected";
37592
37669
  var DM_CHANNEL_MISTARGET_KEY = "dm_channel_mistarget";
37670
+ var SCHEDULED_CHANNEL_OVERRIDE_SHADOW_KEY = "scheduled_channel_override_shadow";
37671
+ var SCHEDULED_CHANNEL_OVERRIDE_BLOCKED_KEY = "scheduled_channel_override_blocked";
37593
37672
  var VALID_MODES = /* @__PURE__ */ new Set(["shadow", "warn", "enforce"]);
37594
37673
  function slackReplyBindingMode() {
37595
37674
  const raw = process.env["AGT_SLACK_REPLY_BINDING"];
@@ -37614,6 +37693,22 @@ function recordReplyBindingClassification(agentDir, channel, input) {
37614
37693
  } catch {
37615
37694
  }
37616
37695
  }
37696
+ function recordScheduledChannelOverride(agentDir, channel, input) {
37697
+ if (!agentDir) return;
37698
+ const path = join13(agentDir, `${channel}${REPLY_BINDING_CLASSIFICATION_COUNTER_SUFFIX}`);
37699
+ let counts = {};
37700
+ try {
37701
+ const parsed = JSON.parse(readFileSync13(path, "utf-8"));
37702
+ if (parsed && typeof parsed === "object") counts = parsed;
37703
+ } catch {
37704
+ }
37705
+ const key2 = input.enforced ? SCHEDULED_CHANNEL_OVERRIDE_BLOCKED_KEY : SCHEDULED_CHANNEL_OVERRIDE_SHADOW_KEY;
37706
+ counts[key2] = (counts[key2] ?? 0) + 1;
37707
+ try {
37708
+ writeFileSync10(path, JSON.stringify(counts), { mode: 384 });
37709
+ } catch {
37710
+ }
37711
+ }
37617
37712
  function recordChannelMistarget(agentDir, channel, input) {
37618
37713
  if (!agentDir) return;
37619
37714
  const path = join13(agentDir, `${channel}${REPLY_BINDING_CLASSIFICATION_COUNTER_SUFFIX}`);
@@ -37742,6 +37837,13 @@ function clearScheduledTurnMarker(agentDir) {
37742
37837
  }
37743
37838
 
37744
37839
  // src/slack-reply-scheduled-priority.ts
37840
+ function scheduledDestOverridesNamedChannel(opts) {
37841
+ if (opts.inboundId || opts.enforceBound) return false;
37842
+ if (opts.channelGuardCorrected) return false;
37843
+ if (!opts.scheduledTargetChannel) return false;
37844
+ if (!opts.namedChannel) return false;
37845
+ return opts.scheduledTargetChannel !== opts.namedChannel;
37846
+ }
37745
37847
  function resolveSlackScheduledPriority(opts) {
37746
37848
  const inboundWins = !!(opts.inboundId || opts.enforceBound);
37747
37849
  const effectiveChannel = inboundWins ? opts.bindingChannel ?? opts.fallbackChannel : opts.scheduledDest ? opts.scheduledDest.channel : opts.fallbackChannel;
@@ -41559,8 +41661,41 @@ mcp.setRequestHandler(CallToolRequestSchema, async (req) => {
41559
41661
  );
41560
41662
  }
41561
41663
  const enforceBound = bindingMode === "enforce" && (binding.classification === "bound" || binding.classification === "coords_corrected");
41664
+ const resolvedFallbackChannel = channelGuard.corrected && channelGuard.channel ? channelGuard.channel : channel;
41562
41665
  const scheduledMarker = readScheduledTurnMarker(SLACK_AGENT_DIR);
41563
41666
  const scheduledDest = scheduledMarker ? resolveScheduledTurnDestination(scheduledMarker, { channel, thread_ts }) : null;
41667
+ const scheduledChannelGuardMode = resolveSlackScheduledChannelGuardMode();
41668
+ const scheduledChannelOverride = scheduledChannelGuardMode !== "off" && scheduledDestOverridesNamedChannel({
41669
+ inboundId: inbound_id,
41670
+ enforceBound,
41671
+ // ENG-7542 resolved a registry-VERIFIED destination - it arbitrates like
41672
+ // enforceBound rather than being treated as just another named channel.
41673
+ channelGuardCorrected: channelGuard.corrected,
41674
+ // The marker's OWN target, not the resolved dest: the resolver echoes the
41675
+ // requested channel for a target-less marker, so comparing the resolved
41676
+ // value compared the raw arg against the corrected one.
41677
+ scheduledTargetChannel: scheduledMarker?.target?.channel_id ?? null,
41678
+ namedChannel: resolvedFallbackChannel
41679
+ });
41680
+ if (scheduledChannelOverride) {
41681
+ const enforced = scheduledChannelGuardMode === "enforce";
41682
+ recordScheduledChannelOverride(SLACK_AGENT_DIR, "slack", { enforced });
41683
+ process.stderr.write(
41684
+ `slack-channel(${AGENT_CODE_NAME}): scheduled_dest_channel_override_${enforced ? "blocked" : "shadow"} task=${scheduledMarker?.task_id ?? "?"} named=${redactSlackId(resolvedFallbackChannel)} task_dest=${redactSlackId(scheduledMarker?.target?.channel_id)}
41685
+ `
41686
+ );
41687
+ if (enforced) {
41688
+ return {
41689
+ content: [
41690
+ {
41691
+ type: "text",
41692
+ text: "This reply was blocked: a scheduled task is active and its destination channel is not the channel you named, so this message would have gone somewhere you did not ask for. If you are answering someone, send again with the inbound_id from their <channel> tag - that always wins over the task destination. If this is the scheduled task's own output, write it on the task card (kanban_update / kanban_done) instead of sending it - the card delivers it for you. If this is deliberate outreach that has nothing to do with the running task, it can be sent once the task finishes."
41693
+ }
41694
+ ],
41695
+ isError: true
41696
+ };
41697
+ }
41698
+ }
41564
41699
  if (scheduledDest?.retargeted) {
41565
41700
  process.stderr.write(
41566
41701
  `slack-channel(${AGENT_CODE_NAME}): scheduled_turn_retargeted task=${scheduledMarker?.task_id ?? "?"} requested=${redactSlackId(channel)}/${redactSlackId(thread_ts)} routed=${redactSlackId(scheduledDest.channel)}/${redactSlackId(scheduledDest.thread_ts)}
@@ -41626,7 +41761,7 @@ mcp.setRequestHandler(CallToolRequestSchema, async (req) => {
41626
41761
  scheduledDest: scheduledDest ?? null,
41627
41762
  bindingChannel: binding.channel,
41628
41763
  bindingThreadTs: binding.threadTs,
41629
- fallbackChannel: channelGuard.corrected && channelGuard.channel ? channelGuard.channel : channel,
41764
+ fallbackChannel: resolvedFallbackChannel,
41630
41765
  fallbackThreadTs: channelGuard.corrected ? channelGuard.threadTs : hotThreadMode === "enforce" && hotGuard ? hotGuard.threadTs : baseThreadTs,
41631
41766
  fallbackSettleThreadTs: channelGuard.corrected ? channelGuard.threadTs : thread_ts
41632
41767
  });
@@ -32185,19 +32185,27 @@ var env = new import_nunjucks.default.Environment(null, { autoescape: false });
32185
32185
 
32186
32186
  // ../core/dist/provisioning/ec2-capacity.js
32187
32187
  var CAPACITY_TABLE = {
32188
- // t3 family — burst-credit instances. Lookup is per-vCPU plus
32189
- // headroom for the manager process itself.
32188
+ // t3 family — burstable (CPU credits). Fine for these mostly-I/O-bound
32189
+ // agents; the monitor + resize path cover the rare sustained-load case.
32190
32190
  "t3.micro": 1,
32191
- // 1 vCPU / 1 GB — barely fits one agent
32192
- "t3.small": 1,
32193
- // 2 vCPU / 2 GB — still tight
32194
- "t3.medium": 2,
32195
- // 2 vCPU / 4 GB — the sweet spot for a 2-agent host
32196
- "t3.large": 4,
32197
- // 2 vCPU / 8 GB — bursts cover the headroom
32198
- "t3.xlarge": 8,
32191
+ // 2 vCPU / 1 GB — floor
32192
+ "t3.small": 2,
32193
+ // 2 vCPU / 2 GB
32194
+ "t3.medium": 4,
32195
+ // 2 vCPU / 4 GB
32196
+ "t3.large": 8,
32197
+ // 2 vCPU / 8 GB
32198
+ "t3.xlarge": 16,
32199
32199
  // 4 vCPU / 16 GB
32200
- "t3.2xlarge": 16
32200
+ "t3.2xlarge": 32,
32201
+ // 8 vCPU / 32 GB
32202
+ // m6i family — fixed performance (no burst-credit cliff), the preferred
32203
+ // family for dense / sustained multi-agent hosts (e.g. Enterprise default).
32204
+ "m6i.large": 8,
32205
+ // 2 vCPU / 8 GB
32206
+ "m6i.xlarge": 16,
32207
+ // 4 vCPU / 16 GB
32208
+ "m6i.2xlarge": 32
32201
32209
  // 8 vCPU / 32 GB
32202
32210
  };
32203
32211
  var KNOWN_INSTANCE_TYPES = Object.keys(CAPACITY_TABLE);
@@ -34539,6 +34547,27 @@ var FLAG_REGISTRY = [
34539
34547
  // change (ADR-0022 sensitive-flag confirm).
34540
34548
  sensitive: true
34541
34549
  },
34550
+ {
34551
+ key: "slack-scheduled-channel-guard",
34552
+ description: 'Guard on slack.reply that refuses to let an active scheduled-task destination silently move a reply to a DIFFERENT channel than the agent named (ENG-8137). CS-1505 gave an explicit inbound_id priority over the task destination but left the no-inbound_id case treating "no inbound reference" as "no destination specified" - and `channel` is a REQUIRED argument, so every call names one. The symptom: a question asked in one channel answered in another, and a reply meant as a DM landing in a public channel. Fires only when the marker carries an EXPLICIT target, the named channel differs from it, and neither an inbound_id, an enforce binding, nor an ENG-7542 channel correction is available to arbitrate. off = never runs (today behaviour: silent retarget). shadow = count + log the would-block but STILL route as today, so the false-positive rate is measurable before anyone refuses a send. enforce = block the reply and tell the agent to retry. Read live from the heartbeat flags-cache (or the env override). NOTE: enforce is currently CLAMPED to shadow host-side and setting it here will not take effect - it stays blocked until ENG-8142 teaches the Stop hook to tell a refused reply from a delivered one, because until then a blocked send fails silently (no recovery, no re-prompt, nothing in the channel). Lifting the clamp is a code change, not a flag flip.',
34553
+ flagType: "enum",
34554
+ allowedValues: ["off", "shadow", "enforce"],
34555
+ // Ships in SHADOW, not off: the counter is the whole point - the fleet metric
34556
+ // is currently blind to this misroute, which is why it went unnoticed. shadow
34557
+ // is a free local log line + counter (no model spend, no behaviour change), so
34558
+ // it measures the fire rate immediately. enforce turns a misroute into a
34559
+ // REFUSED send, which trades a confidentiality risk for an availability one -
34560
+ // that is the audited per-org flip, taken after reading the shadow numbers and
34561
+ // after ENG-8143 shrinks the stale-marker window that dominates the false
34562
+ // positives.
34563
+ defaultValue: "shadow",
34564
+ // Enum override AGT_SLACK_SCHEDULED_CHANNEL_GUARD_MODE (off|shadow|enforce),
34565
+ // resolved by resolveSlackScheduledChannelGuardMode in the channel-server bundle.
34566
+ envVar: "AGT_SLACK_SCHEDULED_CHANNEL_GUARD_MODE",
34567
+ // enforce REFUSES a send the agent asked to make - a visible availability
34568
+ // change, so flipping toward it is deliberate (ADR-0022 sensitive-flag confirm).
34569
+ sensitive: true
34570
+ },
34542
34571
  {
34543
34572
  key: "slack-hot-thread-guard",
34544
34573
  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.",
@@ -36,7 +36,7 @@ import {
36
36
  writeDirectChatSessionState,
37
37
  writeEgressAllowlist,
38
38
  writePersistentClaudeWrapper
39
- } from "./chunk-IKQHNN7I.js";
39
+ } from "./chunk-B4NX2T2H.js";
40
40
  import "./chunk-XWVM4KPK.js";
41
41
  export {
42
42
  EGRESS_BASELINE_DOMAINS,
@@ -77,4 +77,4 @@ export {
77
77
  writeEgressAllowlist,
78
78
  writePersistentClaudeWrapper
79
79
  };
80
- //# sourceMappingURL=persistent-session-AYCUX4DJ.js.map
80
+ //# sourceMappingURL=persistent-session-2OYYXMM7.js.map
@@ -1,6 +1,6 @@
1
1
  import {
2
2
  paneLogPath
3
- } from "./chunk-IKQHNN7I.js";
3
+ } from "./chunk-B4NX2T2H.js";
4
4
  import "./chunk-XWVM4KPK.js";
5
5
 
6
6
  // src/lib/responsiveness-probe.ts
@@ -185,7 +185,16 @@ var KNOWN_REPLY_BINDING_KEYS = /* @__PURE__ */ new Set([
185
185
  "delivery_ledger_self",
186
186
  "delivery_ledger_moved_on",
187
187
  "delivery_ledger_clear",
188
- "delivery_ledger_fallback"
188
+ "delivery_ledger_fallback",
189
+ // ENG-8137: the scheduled-turn channel guard writes these into the same counter
190
+ // file (recordScheduledChannelOverride). `_shadow` counts detections while the
191
+ // flag observes without changing routing - the false-positive signal that gates
192
+ // the enforce flip; `_blocked` counts sends actually refused under enforce.
193
+ // Allowlisted so both reach CloudWatch: without them this guard would fire
194
+ // fleet-wide with no observable rate, which is the blindness that let the
195
+ // original misroute reach customers unnoticed.
196
+ "scheduled_channel_override_shadow",
197
+ "scheduled_channel_override_blocked"
189
198
  ]);
190
199
  function readAndResetSlackReplyBindingClassifications(agentHomeDir) {
191
200
  let names;
@@ -462,4 +471,4 @@ export {
462
471
  readAndResetSlackReplyBindingClassifications,
463
472
  readAndResetSlackReplyTargetClassifications
464
473
  };
465
- //# sourceMappingURL=responsiveness-probe-AQUOYHCE.js.map
474
+ //# sourceMappingURL=responsiveness-probe-7OOA57BP.js.map