@integrity-labs/agt-cli 0.28.1052 → 0.28.1054

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.
Files changed (23) hide show
  1. package/dist/bin/agt.js +5 -5
  2. package/dist/{chunk-MQ4COFCI.js → chunk-2SGXGNDW.js} +11 -1
  3. package/dist/{chunk-MQ4COFCI.js.map → chunk-2SGXGNDW.js.map} +1 -1
  4. package/dist/{chunk-4ZH75WMF.js → chunk-AT7E625H.js} +2 -2
  5. package/dist/{chunk-3E63PEXT.js → chunk-M4ADTK6F.js} +8 -4
  6. package/dist/{chunk-3E63PEXT.js.map → chunk-M4ADTK6F.js.map} +1 -1
  7. package/dist/{claude-pair-runtime-KMPRE2ZJ.js → claude-pair-runtime-GO3C7KT6.js} +2 -2
  8. package/dist/lib/manager-worker.js +78 -14
  9. package/dist/lib/manager-worker.js.map +1 -1
  10. package/dist/mcp/direct-chat-channel.js +10 -0
  11. package/dist/mcp/index.js +39 -0
  12. package/dist/mcp/origami.js +10 -0
  13. package/dist/mcp/slack-channel.js +159 -22
  14. package/dist/mcp/telegram-channel.js +10 -0
  15. package/dist/{persistent-session-5RX4P7GP.js → persistent-session-3WRSJIDM.js} +3 -3
  16. package/dist/{responsiveness-probe-C75AUK6I.js → responsiveness-probe-2DF6VH7Q.js} +3 -3
  17. package/dist/{session-auth-dead-VFRUHVTF.js → session-auth-dead-GRTHA2Z5.js} +2 -2
  18. package/package.json +1 -1
  19. /package/dist/{chunk-4ZH75WMF.js.map → chunk-AT7E625H.js.map} +0 -0
  20. /package/dist/{claude-pair-runtime-KMPRE2ZJ.js.map → claude-pair-runtime-GO3C7KT6.js.map} +0 -0
  21. /package/dist/{persistent-session-5RX4P7GP.js.map → persistent-session-3WRSJIDM.js.map} +0 -0
  22. /package/dist/{responsiveness-probe-C75AUK6I.js.map → responsiveness-probe-2DF6VH7Q.js.map} +0 -0
  23. /package/dist/{session-auth-dead-VFRUHVTF.js.map → session-auth-dead-GRTHA2Z5.js.map} +0 -0
@@ -37900,6 +37900,16 @@ var FLAG_REGISTRY = [
37900
37900
  // put a first-of-its-kind classifier in front of real customer traffic with nothing
37901
37901
  // observed. Arm per-org, then revisit once there is evidence.
37902
37902
  },
37903
+ {
37904
+ key: "heartbeat-writes-claude-account-email",
37905
+ description: "ENG-10796 (#6382): when ON, the host heartbeat writes the Claude account address it reads from the host (oauthAccount.emailAddress) into hosts.claude_account_email, so the Tokens label self-heals. OFF (default) the heartbeat never writes the column and the label stays whatever an operator set. Ships DARK because the column must first sit behind column-level grants (ENG-11162, #6596): until then any org member, and every co-tenant org on a pool host, can read it raw through Supabase REST, and on an IL-provisioned box the harvested address is a staff login (Obi Wan ecad15b6eff5). Evaluated at PLATFORM scope only: the heartbeat does not know the host org before its UPDATE, so flip the platform value once ENG-11162 is live.",
37906
+ flagType: "boolean",
37907
+ // Safe value is `false` (write nothing). A flag read error degrades here, so a flags outage
37908
+ // can never start harvesting addresses into a customer-readable row.
37909
+ defaultValue: false,
37910
+ // Turning it on starts publishing an identifier into customer-visible rows (ADR-0022 §4).
37911
+ sensitive: true
37912
+ },
37903
37913
  {
37904
37914
  key: "email-guard-require-approval-enabled",
37905
37915
  description: "Platform enable-gate for the email-domain guardrail REQUIRE_APPROVAL stage (ENG-7939). When ON for an org, a scope at email_guard_stage=require_approval routes a send to a non-allowed domain into Human Approval (an Approve/Deny card) instead of a hard block: the rollout UI unlocks the option and the write routes accept require_approval. When OFF (default) the write routes reject require_approval and the UI shows it as not yet available, so no scope can select it. Structurally also depends on hitl-approval-routing being on (the approval broker fails closed to a hard block when routing is off). Evaluated per-org. Ships dark.",
package/dist/mcp/index.js CHANGED
@@ -30231,6 +30231,16 @@ var FLAG_REGISTRY = [
30231
30231
  // put a first-of-its-kind classifier in front of real customer traffic with nothing
30232
30232
  // observed. Arm per-org, then revisit once there is evidence.
30233
30233
  },
30234
+ {
30235
+ key: "heartbeat-writes-claude-account-email",
30236
+ description: "ENG-10796 (#6382): when ON, the host heartbeat writes the Claude account address it reads from the host (oauthAccount.emailAddress) into hosts.claude_account_email, so the Tokens label self-heals. OFF (default) the heartbeat never writes the column and the label stays whatever an operator set. Ships DARK because the column must first sit behind column-level grants (ENG-11162, #6596): until then any org member, and every co-tenant org on a pool host, can read it raw through Supabase REST, and on an IL-provisioned box the harvested address is a staff login (Obi Wan ecad15b6eff5). Evaluated at PLATFORM scope only: the heartbeat does not know the host org before its UPDATE, so flip the platform value once ENG-11162 is live.",
30237
+ flagType: "boolean",
30238
+ // Safe value is `false` (write nothing). A flag read error degrades here, so a flags outage
30239
+ // can never start harvesting addresses into a customer-readable row.
30240
+ defaultValue: false,
30241
+ // Turning it on starts publishing an identifier into customer-visible rows (ADR-0022 §4).
30242
+ sensitive: true
30243
+ },
30234
30244
  {
30235
30245
  key: "email-guard-require-approval-enabled",
30236
30246
  description: "Platform enable-gate for the email-domain guardrail REQUIRE_APPROVAL stage (ENG-7939). When ON for an org, a scope at email_guard_stage=require_approval routes a send to a non-allowed domain into Human Approval (an Approve/Deny card) instead of a hard block: the rollout UI unlocks the option and the write routes accept require_approval. When OFF (default) the write routes reject require_approval and the UI shows it as not yet available, so no scope can select it. Structurally also depends on hitl-approval-routing being on (the approval broker fails closed to a hard block when routing is off). Evaluated per-org. Ships dark.",
@@ -50585,6 +50595,8 @@ var LOCAL_TOOL_NAMES = /* @__PURE__ */ new Set([
50585
50595
  "projects_list",
50586
50596
  "projects_get",
50587
50597
  "projects_create",
50598
+ // CS-1900: rename / archive / Linear link on an existing project.
50599
+ "projects_update",
50588
50600
  "artefacts_assign_project",
50589
50601
  "kanban_assign_project",
50590
50602
  // ENG-6229: self-restart. Conditionally registered (dark unless
@@ -51122,6 +51134,33 @@ server.tool(
51122
51134
  content: [{ type: "text", text: await createProjectText(params) }]
51123
51135
  })
51124
51136
  );
51137
+ async function updateProjectText(params) {
51138
+ const data = await apiPost(
51139
+ `/host/projects/${encodeURIComponent(params.project_id)}/update`,
51140
+ {
51141
+ agent_id: AGT_AGENT_ID,
51142
+ name: params.name,
51143
+ status: params.status,
51144
+ linear_project_id: params.linear_project_id
51145
+ }
51146
+ );
51147
+ const p2 = data.project;
51148
+ return `Updated project "${p2.name}" [${p2.status}] (id: ${p2.id}).`;
51149
+ }
51150
+ server.tool(
51151
+ "projects_update",
51152
+ 'Rename a project, archive or unarchive it, or set/clear its Linear link. Pass only the fields to change. A project is shared by your whole team, so ask the human to confirm before renaming or archiving one, naming the project and the change (e.g. archive "Theoretical Store Profitability"). Archiving hides it from active lists but keeps its tasks and artefacts; set status "active" to undo. Projects have no cover image, so there is nothing to set for one.',
51153
+ {
51154
+ project_id: external_exports.string().uuid().describe("The project id returned by projects_list"),
51155
+ // Trim BEFORE the length check, as the host does (CodeRabbit on #6642).
51156
+ name: external_exports.string().trim().min(1).max(200).optional().describe("New project name"),
51157
+ status: external_exports.enum(["active", "archived"]).optional().describe('"archived" to archive, "active" to unarchive'),
51158
+ linear_project_id: external_exports.string().nullable().optional().describe("Linear project id to deep-link, or null to clear the link")
51159
+ },
51160
+ async (params) => ({
51161
+ content: [{ type: "text", text: await updateProjectText(params) }]
51162
+ })
51163
+ );
51125
51164
  async function assignArtefactProjectText(params) {
51126
51165
  const data = await apiPost(
51127
51166
  `/host/artefact/${encodeURIComponent(params.artefact_id)}/assign-project`,
@@ -44145,6 +44145,16 @@ var FLAG_REGISTRY = [
44145
44145
  // put a first-of-its-kind classifier in front of real customer traffic with nothing
44146
44146
  // observed. Arm per-org, then revisit once there is evidence.
44147
44147
  },
44148
+ {
44149
+ key: "heartbeat-writes-claude-account-email",
44150
+ description: "ENG-10796 (#6382): when ON, the host heartbeat writes the Claude account address it reads from the host (oauthAccount.emailAddress) into hosts.claude_account_email, so the Tokens label self-heals. OFF (default) the heartbeat never writes the column and the label stays whatever an operator set. Ships DARK because the column must first sit behind column-level grants (ENG-11162, #6596): until then any org member, and every co-tenant org on a pool host, can read it raw through Supabase REST, and on an IL-provisioned box the harvested address is a staff login (Obi Wan ecad15b6eff5). Evaluated at PLATFORM scope only: the heartbeat does not know the host org before its UPDATE, so flip the platform value once ENG-11162 is live.",
44151
+ flagType: "boolean",
44152
+ // Safe value is `false` (write nothing). A flag read error degrades here, so a flags outage
44153
+ // can never start harvesting addresses into a customer-readable row.
44154
+ defaultValue: false,
44155
+ // Turning it on starts publishing an identifier into customer-visible rows (ADR-0022 §4).
44156
+ sensitive: true
44157
+ },
44148
44158
  {
44149
44159
  key: "email-guard-require-approval-enabled",
44150
44160
  description: "Platform enable-gate for the email-domain guardrail REQUIRE_APPROVAL stage (ENG-7939). When ON for an org, a scope at email_guard_stage=require_approval routes a send to a non-allowed domain into Human Approval (an Approve/Deny card) instead of a hard block: the rollout UI unlocks the option and the write routes accept require_approval. When OFF (default) the write routes reject require_approval and the UI shows it as not yet available, so no scope can select it. Structurally also depends on hitl-approval-routing being on (the approval broker fails closed to a hard block when routing is off). Evaluated per-org. Ships dark.",
@@ -38784,6 +38784,16 @@ var FLAG_REGISTRY = [
38784
38784
  // put a first-of-its-kind classifier in front of real customer traffic with nothing
38785
38785
  // observed. Arm per-org, then revisit once there is evidence.
38786
38786
  },
38787
+ {
38788
+ key: "heartbeat-writes-claude-account-email",
38789
+ description: "ENG-10796 (#6382): when ON, the host heartbeat writes the Claude account address it reads from the host (oauthAccount.emailAddress) into hosts.claude_account_email, so the Tokens label self-heals. OFF (default) the heartbeat never writes the column and the label stays whatever an operator set. Ships DARK because the column must first sit behind column-level grants (ENG-11162, #6596): until then any org member, and every co-tenant org on a pool host, can read it raw through Supabase REST, and on an IL-provisioned box the harvested address is a staff login (Obi Wan ecad15b6eff5). Evaluated at PLATFORM scope only: the heartbeat does not know the host org before its UPDATE, so flip the platform value once ENG-11162 is live.",
38790
+ flagType: "boolean",
38791
+ // Safe value is `false` (write nothing). A flag read error degrades here, so a flags outage
38792
+ // can never start harvesting addresses into a customer-readable row.
38793
+ defaultValue: false,
38794
+ // Turning it on starts publishing an identifier into customer-visible rows (ADR-0022 §4).
38795
+ sensitive: true
38796
+ },
38787
38797
  {
38788
38798
  key: "email-guard-require-approval-enabled",
38789
38799
  description: "Platform enable-gate for the email-domain guardrail REQUIRE_APPROVAL stage (ENG-7939). When ON for an org, a scope at email_guard_stage=require_approval routes a send to a non-allowed domain into Human Approval (an Approve/Deny card) instead of a hard block: the rollout UI unlocks the option and the write routes accept require_approval. When OFF (default) the write routes reject require_approval and the UI shows it as not yet available, so no scope can select it. Structurally also depends on hitl-approval-routing being on (the approval broker fails closed to a hard block when routing is off). Evaluated per-org. Ships dark.",
@@ -41181,11 +41191,24 @@ function buildIngressThreadKey(channel, threadTs) {
41181
41191
  }
41182
41192
  function createIngressOnlyThreadRegistry(maxEntries = 500) {
41183
41193
  const marked = /* @__PURE__ */ new Set();
41194
+ const cleared = /* @__PURE__ */ new Set();
41195
+ const remember = (set, key2) => {
41196
+ set.delete(key2);
41197
+ set.add(key2);
41198
+ while (set.size > maxEntries) {
41199
+ const oldest = set.values().next();
41200
+ if (oldest.done)
41201
+ break;
41202
+ set.delete(oldest.value);
41203
+ }
41204
+ };
41184
41205
  return {
41185
41206
  mark(channel, threadTs) {
41186
41207
  const key2 = buildIngressThreadKey(channel, threadTs);
41187
41208
  if (!key2)
41188
41209
  return;
41210
+ if (cleared.has(key2))
41211
+ return;
41189
41212
  marked.delete(key2);
41190
41213
  marked.add(key2);
41191
41214
  while (marked.size > maxEntries) {
@@ -41200,6 +41223,7 @@ function createIngressOnlyThreadRegistry(maxEntries = 500) {
41200
41223
  if (!key2)
41201
41224
  return;
41202
41225
  marked.delete(key2);
41226
+ remember(cleared, key2);
41203
41227
  },
41204
41228
  isIngressOnly(channel, threadTs) {
41205
41229
  const key2 = buildIngressThreadKey(channel, threadTs);
@@ -41212,6 +41236,16 @@ function createIngressOnlyThreadRegistry(maxEntries = 500) {
41212
41236
  }
41213
41237
  };
41214
41238
  }
41239
+ function shouldSuppressDirectPost(input) {
41240
+ if (input.persistedHumanOwed === true)
41241
+ return false;
41242
+ if (input.persistedIngressOnly === true)
41243
+ return true;
41244
+ return input.ingressOnly.isIngressOnly(input.channel, input.threadTs);
41245
+ }
41246
+ function threadMarkersAllIngressOnly(markers) {
41247
+ return markers.length > 0 && markers.every((m) => m.ingress_only === true);
41248
+ }
41215
41249
  var INGRESS_ONLY_REFUSAL = "This thread was opened by a third-party bot (an alert or issue card), which can wake you but is not someone you can talk to - replying would start a bot-to-bot exchange in a shared channel. Act on the content instead: work the issue, or raise it with a person in a thread they are in. If a human posts here, this thread becomes repliable.";
41216
41250
 
41217
41251
  // ../core/dist/channels/governance/mention-echo-twin-audit.js
@@ -44020,6 +44054,24 @@ function channelMessageShouldWake(text, responseMode, notifyMode, channelId, mut
44020
44054
  }
44021
44055
  return channelMessageShouldRespond(text, responseMode);
44022
44056
  }
44057
+ function decideTopLevelChannelWake(input) {
44058
+ const muted = Boolean(input.channelId && input.mutedChannels?.has(input.channelId));
44059
+ if (input.thirdPartyBotGranted) {
44060
+ return muted ? { wake: false, reason: "muted" } : { wake: true, via: "third_party_bot" };
44061
+ }
44062
+ if (channelMessageShouldWake(
44063
+ input.text,
44064
+ input.responseMode,
44065
+ input.notifyMode,
44066
+ input.channelId,
44067
+ input.mutedChannels,
44068
+ input.agent
44069
+ )) {
44070
+ return { wake: true, via: "engagement" };
44071
+ }
44072
+ const muteApplies = input.notifyMode === "filter" || input.notifyMode === "addressed";
44073
+ return { wake: false, reason: muted && muteApplies ? "muted" : "not_engaged" };
44074
+ }
44023
44075
  function parseNotifyCommand(text, currentChannelId) {
44024
44076
  const arg = (text ?? "").trim().toLowerCase().split(/\s+/)[0] ?? "";
44025
44077
  if (arg === "off" || arg === "mute") return { action: "mute", channelId: currentChannelId };
@@ -44035,7 +44087,12 @@ function notifyStatusMessage(muted, mode) {
44035
44087
  return muted ? ":mute: This channel is *muted* \u2014 I won\u2019t wake on its messages, including ones that name me (a direct @-mention still reaches me). `/notify on` to resume." : ":bell: This channel is *active* \u2014 I wake on messages that address me by name, not on every message here (an @-mention always reaches me). `/notify off` to mute.";
44036
44088
  }
44037
44089
  const unblock = "This is the `notify-dispatch` flag: an operator turns it on, and a session already running only picks it up after a restart \u2014 so if it has just been enabled, ask for me to be restarted.";
44038
- return muted ? `:grey_question: Your *mute* for this channel is *saved but not in effect* \u2014 I\u2019m not in a per-channel dispatch mode right now, so it isn\u2019t changing what wakes me. It applies as soon as one is live. ${unblock}` : `:grey_question: *No mute saved* for this channel \u2014 though it wouldn\u2019t apply yet either way: I\u2019m not in a per-channel dispatch mode right now. \`/notify off\` saves one for when it is live. ${unblock}`;
44090
+ return muted ? `:grey_question: Your *mute* for this channel is saved and *only partly in effect*: ${NOTIFY_BOT_MUTE_CLAUSE} Everything else here is unchanged, because I\u2019m not in a per-channel dispatch mode right now; it applies to those as soon as one is live. ${unblock}` : `:grey_question: *No mute saved* for this channel. I\u2019m not in a per-channel dispatch mode right now, so a mute would not change what other messages do here, but \`/notify off\` would still stop any allowlisted third-party bot (an alert or issue integration such as Linear) waking me here straight away. ${unblock}`;
44091
+ }
44092
+ var NOTIFY_BOT_MUTE_CLAUSE = "it already stops any allowlisted third-party bot (an alert or issue integration such as Linear) from waking me here.";
44093
+ function notifyAckNote(mode) {
44094
+ if (mode === "filter" || mode === "addressed") return "";
44095
+ return ` _(note: I\u2019m not in a per-channel dispatch mode right now. A mute here ${NOTIFY_BOT_MUTE_CLAUSE.replace(/^it /, "")} Other messages are unaffected until an operator enables per-channel dispatch.)_`;
44039
44096
  }
44040
44097
 
44041
44098
  // src/slack-list-channels.ts
@@ -45445,7 +45502,8 @@ function writeSlackPendingInboundMarker(channel, threadTs, messageTs, undelivera
45445
45502
  ...discretionary ? { discretionary: true } : {},
45446
45503
  // ENG-5969/ENG-6327: carry the replay payload only when provided (the
45447
45504
  // durable-replay path). Absent ⇒ the marker can't be replayed.
45448
- ...payload ? { payload } : {}
45505
+ ...payload ? { payload } : {},
45506
+ ...ingressOnlyThreads.isIngressOnly(channel, threadTs) ? { ingress_only: true } : {}
45449
45507
  };
45450
45508
  try {
45451
45509
  mkdirSync9(SLACK_PENDING_INBOUND_DIR, { recursive: true, mode: 448 });
@@ -45589,6 +45647,18 @@ function slackConversationReplayFacts(channel, threadTs) {
45589
45647
  }
45590
45648
  function sendUndeliverableNotice(channel, threadTs, conversationKey) {
45591
45649
  if (!BOT_TOKEN || !channel) return;
45650
+ if (shouldSuppressDirectPost({
45651
+ channel,
45652
+ threadTs,
45653
+ ingressOnly: ingressOnlyThreads,
45654
+ persistedHumanOwed: readSlackThreadMarkerState(channel, threadTs).humanOwed
45655
+ })) {
45656
+ process.stderr.write(
45657
+ `slack-channel(${AGENT_CODE_NAME}): undeliverable notice suppressed reason=ingress_only thread=${redactSlackId(threadTs)}
45658
+ `
45659
+ );
45660
+ return;
45661
+ }
45592
45662
  const text = undeliverableNoticeTextForRepeat({
45593
45663
  replay: slackConversationReplayFacts(channel, threadTs) ?? void 0,
45594
45664
  priorNotices: slackUndeliverableNotices.noticeCount(conversationKey)
@@ -45641,6 +45711,18 @@ function deleteSlackUndeliverableNotice(conversationKey) {
45641
45711
  var lastSlackBusyAckNoticeAt = /* @__PURE__ */ new Map();
45642
45712
  function postBusyAckNotice(channel, threadTs, isThreadReply, state) {
45643
45713
  if (!BOT_TOKEN || !channel) return;
45714
+ if (shouldSuppressDirectPost({
45715
+ channel,
45716
+ threadTs,
45717
+ ingressOnly: ingressOnlyThreads,
45718
+ persistedHumanOwed: readSlackThreadMarkerState(channel, threadTs).humanOwed
45719
+ })) {
45720
+ process.stderr.write(
45721
+ `slack-channel(${AGENT_CODE_NAME}): busy-ack suppressed reason=ingress_only thread=${redactSlackId(threadTs)}
45722
+ `
45723
+ );
45724
+ return;
45725
+ }
45644
45726
  const now = Date.now();
45645
45727
  const conversationKey = isThreadReply && threadTs ? `${channel}:${threadTs}` : channel;
45646
45728
  if (!shouldPostUndeliverableNotice(
@@ -45834,6 +45916,32 @@ function slackNextRetryName(filename) {
45834
45916
  }
45835
45917
  return { next: `${base}.retry-${attempt}.json`, attempt };
45836
45918
  }
45919
+ function readSlackThreadMarkerState(channel, threadTs) {
45920
+ const none = { allIngressOnly: false, humanOwed: false };
45921
+ if (!threadTs || !SLACK_PENDING_INBOUND_DIR) return none;
45922
+ const markers = [];
45923
+ try {
45924
+ markSeenAllSlackPendingMarkersForThread(
45925
+ SLACK_PENDING_INBOUND_DIR,
45926
+ channel,
45927
+ threadTs,
45928
+ (fullPath) => {
45929
+ try {
45930
+ const m = JSON.parse(readFileSync18(fullPath, "utf-8"));
45931
+ markers.push({ ingress_only: m.ingress_only });
45932
+ } catch {
45933
+ markers.push({});
45934
+ }
45935
+ }
45936
+ );
45937
+ } catch {
45938
+ return none;
45939
+ }
45940
+ return {
45941
+ allIngressOnly: threadMarkersAllIngressOnly(markers),
45942
+ humanOwed: markers.some((m) => m.ingress_only !== true)
45943
+ };
45944
+ }
45837
45945
  async function processSlackRecoveryOutboxFile(filename) {
45838
45946
  if (!SLACK_RECOVERY_OUTBOX_DIR) return;
45839
45947
  if (filename.endsWith(".poison.json") || filename.endsWith(".tmp")) return;
@@ -45877,6 +45985,26 @@ async function processSlackRecoveryOutboxFile(filename) {
45877
45985
  const urlFixed = stripUrlWrappingAsterisks(mrkdwn.text);
45878
45986
  if (urlFixed.stripped) emitUrlAsteriskStripTelemetry("slack", urlFixed.count);
45879
45987
  const text = urlFixed.text;
45988
+ const recoveryMarkers = readSlackThreadMarkerState(payload.channel, payload.thread_ts);
45989
+ if (shouldSuppressDirectPost({
45990
+ channel: payload.channel,
45991
+ threadTs: payload.thread_ts,
45992
+ ingressOnly: ingressOnlyThreads,
45993
+ persistedIngressOnly: recoveryMarkers.allIngressOnly,
45994
+ persistedHumanOwed: recoveryMarkers.humanOwed
45995
+ })) {
45996
+ process.stderr.write(
45997
+ `slack-channel(${AGENT_CODE_NAME}): ghost-reply recovery suppressed reason=ingress_only channel=${redactSlackId(payload.channel)} thread=${redactSlackId(payload.thread_ts)}
45998
+ `
45999
+ );
46000
+ if (payload.thread_ts) clearPendingMessage(payload.channel, payload.thread_ts);
46001
+ removeRecoveryLedgerEntry(SLACK_RECOVERY_LEDGER_DIR, payload.marker_name);
46002
+ try {
46003
+ unlinkSync7(fullPath);
46004
+ } catch {
46005
+ }
46006
+ return;
46007
+ }
45880
46008
  const ghostReplyMode = resolveGhostReplyMode();
45881
46009
  if (ghostReplyMode !== "off") {
45882
46010
  const decision = await decideRecovery(text, ghostReplyMode);
@@ -47119,8 +47247,7 @@ async function handleNotifyCommand(payload, codeName) {
47119
47247
  );
47120
47248
  return;
47121
47249
  }
47122
- const muteIsLive = NOTIFY_DISPATCH_MODE === "filter" || NOTIFY_DISPATCH_MODE === "addressed";
47123
- const inactiveNote = muteIsLive ? "" : " _(note: I\u2019m not in a per-channel dispatch mode right now, so this is saved but won\u2019t take effect until an operator enables it.)_";
47250
+ const inactiveNote = notifyAckNote(NOTIFY_DISPATCH_MODE);
47124
47251
  const { action } = parseNotifyCommand(payload.text, channelId);
47125
47252
  if (action === "help") {
47126
47253
  await postEphemeralViaResponseUrl(
@@ -49801,15 +49928,13 @@ function connectSocketModeSafely() {
49801
49928
  void startSocketModeAfterSeedingMutes();
49802
49929
  }
49803
49930
  async function startSocketModeAfterSeedingMutes() {
49804
- if (NOTIFY_DISPATCH_MODE === "filter" || NOTIFY_DISPATCH_MODE === "addressed") {
49805
- try {
49806
- await seedMutedChannelsFromApi();
49807
- } catch (err) {
49808
- process.stderr.write(
49809
- `slack-channel: notify-prefs seed threw, connecting anyway: ${err.message}
49931
+ try {
49932
+ await seedMutedChannelsFromApi();
49933
+ } catch (err) {
49934
+ process.stderr.write(
49935
+ `slack-channel: notify-prefs seed threw, connecting anyway: ${err.message}
49810
49936
  `
49811
- );
49812
- }
49937
+ );
49813
49938
  }
49814
49939
  if (isShuttingDown) return;
49815
49940
  void runOrRetry(connectSocketMode, {
@@ -50074,8 +50199,12 @@ async function connectSocketMode() {
50074
50199
  if (passedIdentity) recordActivity("inbound");
50075
50200
  const ingressThreadTs = evt.thread_ts ?? evt.ts;
50076
50201
  if (peerClassification?.kind === "bot-ingress" && access.kind !== "drop") {
50077
- ingressOnlyThreads.mark(evt.channel, ingressThreadTs);
50078
- } else if (!isBot) {
50202
+ if (evt.channel && readSlackThreadMarkerState(evt.channel, ingressThreadTs).humanOwed) {
50203
+ ingressOnlyThreads.clear(evt.channel, ingressThreadTs);
50204
+ } else {
50205
+ ingressOnlyThreads.mark(evt.channel, ingressThreadTs);
50206
+ }
50207
+ } else if (access.kind !== "drop" && (!isBot || peerClassification?.kind === "peer-ingress")) {
50079
50208
  ingressOnlyThreads.clear(evt.channel, ingressThreadTs);
50080
50209
  }
50081
50210
  if (access.kind === "drop") {
@@ -50277,17 +50406,25 @@ async function connectSocketMode() {
50277
50406
  return;
50278
50407
  }
50279
50408
  } else {
50280
- if (!channelMessageShouldWake(
50281
- evt.text ?? "",
50282
- CHANNEL_RESPONSE_MODE,
50283
- NOTIFY_DISPATCH_MODE,
50284
- evt.channel,
50285
- MUTED_CHANNELS,
50409
+ const wake = decideTopLevelChannelWake({
50410
+ text: evt.text ?? "",
50411
+ responseMode: CHANNEL_RESPONSE_MODE,
50412
+ notifyMode: NOTIFY_DISPATCH_MODE,
50413
+ channelId: evt.channel,
50414
+ mutedChannels: MUTED_CHANNELS,
50286
50415
  // ENG-8657: who this agent is, so `addressed` can tell a message
50287
50416
  // naming IT from one naming a different agent in the same channel.
50288
- { botUserId, codeName: AGENT_CODE_NAME }
50289
- ))
50417
+ agent: { botUserId, codeName: AGENT_CODE_NAME },
50418
+ thirdPartyBotGranted: peerClassification?.kind === "bot-ingress"
50419
+ });
50420
+ if (!wake.wake) {
50421
+ const channelHash = createHash3("sha256").update(evt.channel ?? "").digest("hex").slice(0, 8);
50422
+ process.stderr.write(
50423
+ `slack-channel: inbound drop reason=engagement:${wake.reason} notify=${NOTIFY_DISPATCH_MODE} response=${CHANNEL_RESPONSE_MODE} bot=${isBot} channel=${channelHash} ts=${redactSlackId(evt.ts)}
50424
+ `
50425
+ );
50290
50426
  return;
50427
+ }
50291
50428
  }
50292
50429
  }
50293
50430
  const composed = composeInboundText(evt);
@@ -38890,6 +38890,16 @@ var FLAG_REGISTRY = [
38890
38890
  // put a first-of-its-kind classifier in front of real customer traffic with nothing
38891
38891
  // observed. Arm per-org, then revisit once there is evidence.
38892
38892
  },
38893
+ {
38894
+ key: "heartbeat-writes-claude-account-email",
38895
+ description: "ENG-10796 (#6382): when ON, the host heartbeat writes the Claude account address it reads from the host (oauthAccount.emailAddress) into hosts.claude_account_email, so the Tokens label self-heals. OFF (default) the heartbeat never writes the column and the label stays whatever an operator set. Ships DARK because the column must first sit behind column-level grants (ENG-11162, #6596): until then any org member, and every co-tenant org on a pool host, can read it raw through Supabase REST, and on an IL-provisioned box the harvested address is a staff login (Obi Wan ecad15b6eff5). Evaluated at PLATFORM scope only: the heartbeat does not know the host org before its UPDATE, so flip the platform value once ENG-11162 is live.",
38896
+ flagType: "boolean",
38897
+ // Safe value is `false` (write nothing). A flag read error degrades here, so a flags outage
38898
+ // can never start harvesting addresses into a customer-readable row.
38899
+ defaultValue: false,
38900
+ // Turning it on starts publishing an identifier into customer-visible rows (ADR-0022 §4).
38901
+ sensitive: true
38902
+ },
38893
38903
  {
38894
38904
  key: "email-guard-require-approval-enabled",
38895
38905
  description: "Platform enable-gate for the email-domain guardrail REQUIRE_APPROVAL stage (ENG-7939). When ON for an org, a scope at email_guard_stage=require_approval routes a send to a non-allowed domain into Human Approval (an Approve/Deny card) instead of a hard block: the rollout UI unlocks the option and the write routes accept require_approval. When OFF (default) the write routes reject require_approval and the UI shows it as not yet available, so no scope can select it. Structurally also depends on hitl-approval-routing being on (the approval broker fails closed to a hard block when routing is off). Evaluated per-org. Ships dark.",
@@ -65,8 +65,8 @@ import {
65
65
  writeDirectChatSessionState,
66
66
  writeEgressAllowlist,
67
67
  writePersistentClaudeWrapper
68
- } from "./chunk-4ZH75WMF.js";
69
- import "./chunk-MQ4COFCI.js";
68
+ } from "./chunk-AT7E625H.js";
69
+ import "./chunk-2SGXGNDW.js";
70
70
  import "./chunk-OCMJ5E6J.js";
71
71
  import "./chunk-XWVM4KPK.js";
72
72
  import "./chunk-EL5MWGGH.js";
@@ -139,4 +139,4 @@ export {
139
139
  writeEgressAllowlist,
140
140
  writePersistentClaudeWrapper
141
141
  };
142
- //# sourceMappingURL=persistent-session-5RX4P7GP.js.map
142
+ //# sourceMappingURL=persistent-session-3WRSJIDM.js.map
@@ -1,7 +1,7 @@
1
1
  import {
2
2
  paneLogPath
3
- } from "./chunk-4ZH75WMF.js";
4
- import "./chunk-MQ4COFCI.js";
3
+ } from "./chunk-AT7E625H.js";
4
+ import "./chunk-2SGXGNDW.js";
5
5
  import "./chunk-OCMJ5E6J.js";
6
6
  import "./chunk-XWVM4KPK.js";
7
7
  import "./chunk-EL5MWGGH.js";
@@ -802,4 +802,4 @@ export {
802
802
  readAndResetSlackReplyBindingClassifications,
803
803
  readAndResetSlackReplyTargetClassifications
804
804
  };
805
- //# sourceMappingURL=responsiveness-probe-C75AUK6I.js.map
805
+ //# sourceMappingURL=responsiveness-probe-2DF6VH7Q.js.map
@@ -1,6 +1,6 @@
1
1
  import {
2
2
  sessionTranscriptDir
3
- } from "./chunk-MQ4COFCI.js";
3
+ } from "./chunk-2SGXGNDW.js";
4
4
  import "./chunk-DGUM43GV.js";
5
5
 
6
6
  // src/lib/session-auth-dead.ts
@@ -204,4 +204,4 @@ export {
204
204
  decideSessionAuthState,
205
205
  probeSessionAuth
206
206
  };
207
- //# sourceMappingURL=session-auth-dead-VFRUHVTF.js.map
207
+ //# sourceMappingURL=session-auth-dead-GRTHA2Z5.js.map
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@integrity-labs/agt-cli",
3
- "version": "0.28.1052",
3
+ "version": "0.28.1054",
4
4
  "description": "Augmented Team CLI — agent provisioning and management",
5
5
  "type": "module",
6
6
  "engines": {