switchroom 0.14.7 → 0.14.9

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.
@@ -30156,6 +30156,11 @@ async function handleRequestConfigApproval(client3, msg, deps) {
30156
30156
  reply("deny", `gateway serves '${deps.agentName}', not '${msg.agentName}'`, "dispatch_failure");
30157
30157
  return;
30158
30158
  }
30159
+ if (deps.tryAutoResolve?.(msg) === "approve") {
30160
+ deps.log?.(`config_approval_auto_resolved requestId=${msg.requestId} agent=${msg.agentName} (operator-tapped always-allow correlation)`);
30161
+ reply("approve");
30162
+ return;
30163
+ }
30159
30164
  const target = deps.loadTargetChat();
30160
30165
  if (target === null) {
30161
30166
  reply("deny", "no target chat available \u2014 operator not paired?", "dispatch_failure");
@@ -31754,142 +31759,26 @@ function isTelegramSurfaceTool(toolName) {
31754
31759
  return suffix === "reply" || suffix === "stream_reply" || suffix === "edit_message" || suffix === "react";
31755
31760
  }
31756
31761
 
31757
- // draft-transport.ts
31758
- var DRAFT_STREAM_STATE_KEY = Symbol.for("switchroom.draftStreamState");
31759
- function getDraftStreamState() {
31760
- const g = globalThis;
31761
- let state = g[DRAFT_STREAM_STATE_KEY];
31762
- if (!state) {
31763
- state = { nextDraftId: 0 };
31764
- g[DRAFT_STREAM_STATE_KEY] = state;
31765
- }
31766
- return state;
31767
- }
31768
- function allocateDraftId() {
31769
- const state = getDraftStreamState();
31770
- state.nextDraftId = state.nextDraftId >= 2147483647 ? 1 : state.nextDraftId + 1;
31771
- return state.nextDraftId;
31772
- }
31773
-
31774
31762
  // tool-activity-summary.ts
31775
- var READ_VERBS = new Set(["read"]);
31776
- var WRITE_VERBS = new Set(["wrote", "created", "edited"]);
31777
- function makeEmptyActivityState() {
31778
- return { counts: {}, order: [], firstToolName: null };
31779
- }
31780
- function verbForTool(toolName) {
31781
- if (!toolName)
31782
- return null;
31783
- const mcpMatch = /^mcp__(.+?)__(.+)$/.exec(toolName);
31784
- if (mcpMatch && mcpMatch[1] === "switchroom-telegram")
31785
- return null;
31786
- if (mcpMatch) {
31787
- const server = mcpMatch[1].toLowerCase();
31788
- const mcpTool = mcpMatch[2].toLowerCase();
31789
- if (server === "hindsight") {
31790
- if (mcpTool === "recall" || mcpTool === "reflect")
31791
- return "searched";
31792
- if (mcpTool === "retain" || mcpTool === "update_memory" || mcpTool === "sync_retain")
31793
- return "saved";
31794
- }
31795
- if (server === "google-workspace" || server === "claude_ai_google_drive" || server === "claude_ai_gmail" || server === "claude_ai_google_calendar") {
31796
- if (/^(search|list|query|read|get|fetch|download)/i.test(mcpTool))
31797
- return "searched";
31798
- if (/^(create|update|write|send|move|copy|duplicate)/i.test(mcpTool))
31799
- return "edited";
31800
- }
31801
- if (server === "notion" || server === "claude_ai_notion") {
31802
- const action = mcpTool.replace(/^notion-/, "");
31803
- if (/^(search|fetch|query|get|read)/i.test(action))
31804
- return "searched";
31805
- if (/^(create|update|move|duplicate|comment)/i.test(action))
31806
- return "edited";
31807
- }
31808
- }
31809
- const suffix = (mcpMatch ? mcpMatch[2] : toolName).toLowerCase();
31810
- switch (suffix) {
31811
- case "read":
31812
- return "read";
31813
- case "write":
31814
- return "created";
31815
- case "edit":
31816
- case "multiedit":
31817
- case "notebookedit":
31818
- return "edited";
31819
- case "bash":
31820
- case "bashoutput":
31821
- case "killshell":
31822
- return "ran";
31823
- case "websearch":
31824
- case "grep":
31825
- case "glob":
31826
- return "searched";
31827
- case "webfetch":
31828
- return "fetched";
31829
- case "task":
31830
- case "agent":
31831
- return "dispatched";
31832
- case "todowrite":
31833
- case "todoread":
31834
- return "noted";
31835
- default:
31836
- return "used";
31837
- }
31838
- }
31839
- function register(state, toolName) {
31840
- const verb = verbForTool(toolName);
31841
- if (!verb)
31842
- return false;
31843
- if (state.firstToolName == null)
31844
- state.firstToolName = toolName;
31845
- const prior = state.counts[verb] ?? 0;
31846
- if (prior === 0)
31847
- state.order.push(verb);
31848
- state.counts[verb] = prior + 1;
31849
- return true;
31850
- }
31851
- var VERB_PHRASE = {
31852
- read: { singular: "read a file", plural: "read $N files" },
31853
- edited: { singular: "edited a file", plural: "edited $N files" },
31854
- created: { singular: "created a file", plural: "created $N files" },
31855
- ran: { singular: "ran a command", plural: "ran $N commands" },
31856
- searched: { singular: "ran a search", plural: "ran $N searches" },
31857
- fetched: { singular: "fetched a URL", plural: "fetched $N URLs" },
31858
- dispatched: { singular: "dispatched a sub-agent", plural: "dispatched $N sub-agents" },
31859
- noted: { singular: "updated the todo list", plural: "updated the todo list ($N edits)" },
31860
- saved: { singular: "saved a memory", plural: "saved $N memories" },
31861
- used: { singular: "used a tool", plural: "used $N tools" }
31862
- };
31863
- function formatSummary(state) {
31864
- const phrases = [];
31865
- for (const verb of state.order) {
31866
- const n = state.counts[verb] ?? 0;
31867
- if (n <= 0)
31868
- continue;
31869
- const p = VERB_PHRASE[verb];
31870
- phrases.push(n === 1 ? p.singular : p.plural.replace("$N", String(n)));
31871
- }
31872
- if (phrases.length === 0)
31873
- return null;
31874
- const sentence = phrases.join(", ");
31875
- return sentence.charAt(0).toUpperCase() + sentence.slice(1);
31876
- }
31877
- function registerAndRender(state, toolName) {
31878
- const changed = register(state, toolName);
31879
- if (!changed)
31880
- return null;
31881
- return formatSummary(state);
31882
- }
31883
31763
  var MIRROR_MAX_LINES = 6;
31764
+ function escapeFeedHtml(s) {
31765
+ return s.replace(/&/g, "&amp;").replace(/</g, "&lt;").replace(/>/g, "&gt;");
31766
+ }
31884
31767
  function renderActivityFeed(lines) {
31885
31768
  if (lines.length === 0)
31886
31769
  return null;
31887
31770
  const shown = lines.slice(-MIRROR_MAX_LINES);
31888
31771
  const hidden = lines.length - shown.length;
31889
- const body = shown.map((l) => `\u00b7 ${l}`).join(`
31772
+ const out = [];
31773
+ if (hidden > 0)
31774
+ out.push(`<i>\u2713 +${hidden} earlier\u2026</i>`);
31775
+ const lastIdx = shown.length - 1;
31776
+ shown.forEach((l, i) => {
31777
+ const esc = escapeFeedHtml(l);
31778
+ out.push(i === lastIdx ? `<b>\u2192 ${esc}</b>` : `<i>\u2713 ${esc}</i>`);
31779
+ });
31780
+ return out.join(`
31890
31781
  `);
31891
- return hidden > 0 ? `\u00b7 +${hidden} earlier\u2026
31892
- ${body}` : body;
31893
31782
  }
31894
31783
  function appendActivityLabel(lines, label) {
31895
31784
  const l = (label ?? "").trim();
@@ -32205,18 +32094,18 @@ function isDraft429(err) {
32205
32094
  const text = typeof err === "string" ? err : err instanceof Error ? err.message : typeof err === "object" && err != null && ("description" in err) ? typeof err.description === "string" ? err.description : "" : "";
32206
32095
  return /sendMessageDraft/i.test(text);
32207
32096
  }
32208
- var DRAFT_STREAM_STATE_KEY2 = Symbol.for("switchroom.draftStreamState");
32209
- function getDraftStreamState2() {
32097
+ var DRAFT_STREAM_STATE_KEY = Symbol.for("switchroom.draftStreamState");
32098
+ function getDraftStreamState() {
32210
32099
  const g = globalThis;
32211
- let state = g[DRAFT_STREAM_STATE_KEY2];
32100
+ let state = g[DRAFT_STREAM_STATE_KEY];
32212
32101
  if (!state) {
32213
32102
  state = { nextDraftId: 0 };
32214
- g[DRAFT_STREAM_STATE_KEY2] = state;
32103
+ g[DRAFT_STREAM_STATE_KEY] = state;
32215
32104
  }
32216
32105
  return state;
32217
32106
  }
32218
- function allocateDraftId2() {
32219
- const state = getDraftStreamState2();
32107
+ function allocateDraftId() {
32108
+ const state = getDraftStreamState();
32220
32109
  state.nextDraftId = state.nextDraftId >= 2147483647 ? 1 : state.nextDraftId + 1;
32221
32110
  return state.nextDraftId;
32222
32111
  }
@@ -32246,7 +32135,7 @@ function createDraftStream(send, edit, config = {}) {
32246
32135
  warn?.('draft-stream: previewTransport="auto" with sendMessageDraft but isPrivateChat undefined \u2014 defaulting to message transport');
32247
32136
  }
32248
32137
  let usesDraftTransport = prefersDraft && draftApi != null;
32249
- let draftId = usesDraftTransport ? allocateDraftId2() : undefined;
32138
+ let draftId = usesDraftTransport ? allocateDraftId() : undefined;
32250
32139
  if (prefersDraft && !usesDraftTransport) {
32251
32140
  warn?.("draft-stream: sendMessageDraft unavailable; falling back to sendMessage/editMessageText");
32252
32141
  }
@@ -32354,7 +32243,7 @@ function createDraftStream(send, edit, config = {}) {
32354
32243
  const newMsgId = await send(chunk);
32355
32244
  messageId = newMsgId;
32356
32245
  persistedTextLen = textToSend.length;
32357
- draftId = allocateDraftId2();
32246
+ draftId = allocateDraftId();
32358
32247
  currentChunkStartedAt = null;
32359
32248
  persistChainFires++;
32360
32249
  sendFires++;
@@ -38636,7 +38525,7 @@ var TRIVIAL_CONFIRMATIONS = new Set(["SENT", "DONE", "OK", "OKAY", "ACK"]);
38636
38525
  var MIN_INITIAL_CHARS = 50;
38637
38526
  var DEFAULT_THROTTLE_MS = 1000;
38638
38527
  var TELEGRAM_MAX_CHARS2 = 4096;
38639
- var allocateDraftId3 = allocateDraftId2;
38528
+ var allocateDraftId2 = allocateDraftId;
38640
38529
  function createAnswerStream(config) {
38641
38530
  const {
38642
38531
  chatId,
@@ -38659,7 +38548,7 @@ function createAnswerStream(config) {
38659
38548
  const effectiveThrottle = Math.max(250, throttleMs);
38660
38549
  const preferDraft = isPrivateChat && draftApi != null;
38661
38550
  let usesDraftTransport = preferDraft;
38662
- let draftId = preferDraft ? allocateDraftId3() : undefined;
38551
+ let draftId = preferDraft ? allocateDraftId2() : undefined;
38663
38552
  let streamMsgId;
38664
38553
  let pendingText = null;
38665
38554
  let lastSentText = "";
@@ -38908,7 +38797,7 @@ function createAnswerStream(config) {
38908
38797
  if (staleDraftId != null) {
38909
38798
  clearDraftBestEffort(staleDraftId);
38910
38799
  }
38911
- draftId = allocateDraftId3();
38800
+ draftId = allocateDraftId2();
38912
38801
  }
38913
38802
  log?.(`answer-stream: forceNewMessage (gen=${generation})`);
38914
38803
  },
@@ -44601,14 +44490,14 @@ function hostdWillBeUsed(agentName3) {
44601
44490
  return false;
44602
44491
  return existsSync20(hostdSocketPath(agentName3));
44603
44492
  }
44604
- async function tryHostdDispatch(agentName3, req) {
44493
+ async function tryHostdDispatch(agentName3, req, timeoutMs = 5000) {
44605
44494
  if (!isHostdEnabled())
44606
44495
  return "not-configured";
44607
44496
  const sockPath = hostdSocketPath(agentName3);
44608
44497
  if (!existsSync20(sockPath))
44609
44498
  return "not-configured";
44610
44499
  try {
44611
- return await hostdRequest({ socketPath: sockPath, timeoutMs: 5000 }, req);
44500
+ return await hostdRequest({ socketPath: sockPath, timeoutMs }, req);
44612
44501
  } catch (err) {
44613
44502
  process.stderr.write(`telegram gateway: hostd dispatch failed ` + `(request_id=${req.request_id} op=${req.op}): ` + `${err.message}
44614
44503
  `);
@@ -49775,6 +49664,247 @@ function isRulePersisted(resolvedAllow, ruleRule) {
49775
49664
  return resolvedAllow.includes(ruleRule);
49776
49665
  }
49777
49666
 
49667
+ // permission-diff.ts
49668
+ var TARGET_HEADER_A = "--- a/switchroom.yaml";
49669
+ var TARGET_HEADER_B = "+++ b/switchroom.yaml";
49670
+ function indentOf(line) {
49671
+ let n = 0;
49672
+ while (n < line.length && line[n] === " ")
49673
+ n++;
49674
+ return n;
49675
+ }
49676
+ function isBlankOrComment(line) {
49677
+ const t = line.trim();
49678
+ return t.length === 0 || t.startsWith("#");
49679
+ }
49680
+ function locateAgentBlock(lines, agentName3) {
49681
+ let agentsIdx = -1;
49682
+ let agentsIndent = 0;
49683
+ for (let i = 0;i < lines.length; i++) {
49684
+ const line = lines[i];
49685
+ if (isBlankOrComment(line))
49686
+ continue;
49687
+ if (line.trim() === "agents:") {
49688
+ agentsIdx = i;
49689
+ agentsIndent = indentOf(line);
49690
+ break;
49691
+ }
49692
+ }
49693
+ if (agentsIdx === -1)
49694
+ return null;
49695
+ let childIndent = -1;
49696
+ for (let i = agentsIdx + 1;i < lines.length; i++) {
49697
+ const line = lines[i];
49698
+ if (isBlankOrComment(line))
49699
+ continue;
49700
+ const ind = indentOf(line);
49701
+ if (ind <= agentsIndent)
49702
+ break;
49703
+ if (childIndent === -1)
49704
+ childIndent = ind;
49705
+ if (ind !== childIndent)
49706
+ continue;
49707
+ const key = line.slice(ind).split(":")[0].trim();
49708
+ if (key === agentName3) {
49709
+ let end = lines.length;
49710
+ for (let j = i + 1;j < lines.length; j++) {
49711
+ const l2 = lines[j];
49712
+ if (isBlankOrComment(l2))
49713
+ continue;
49714
+ if (indentOf(l2) <= ind) {
49715
+ end = j;
49716
+ break;
49717
+ }
49718
+ }
49719
+ return { agentIndent: ind, agentLineIdx: i, agentBlockEnd: end };
49720
+ }
49721
+ }
49722
+ return null;
49723
+ }
49724
+ function locateToolsLine(lines, block) {
49725
+ for (let i = block.agentLineIdx + 1;i < block.agentBlockEnd; i++) {
49726
+ const line = lines[i];
49727
+ if (isBlankOrComment(line))
49728
+ continue;
49729
+ const ind = indentOf(line);
49730
+ if (ind <= block.agentIndent)
49731
+ break;
49732
+ const key = line.slice(ind).split(":")[0].trim();
49733
+ if (key === "tools" && ind === block.agentIndent + 2) {
49734
+ return { idx: i, indent: ind };
49735
+ }
49736
+ }
49737
+ return null;
49738
+ }
49739
+ function locateAllowLine(lines, toolsIdx, toolsIndent, blockEnd) {
49740
+ for (let i = toolsIdx + 1;i < blockEnd; i++) {
49741
+ const line = lines[i];
49742
+ if (isBlankOrComment(line))
49743
+ continue;
49744
+ const ind = indentOf(line);
49745
+ if (ind <= toolsIndent)
49746
+ break;
49747
+ const key = line.slice(ind).split(":")[0].trim();
49748
+ if (key === "allow" && ind === toolsIndent + 2) {
49749
+ const after = line.slice(line.indexOf(":") + 1);
49750
+ return { idx: i, indent: ind, inline: after };
49751
+ }
49752
+ }
49753
+ return null;
49754
+ }
49755
+ function buildHunk(lines, changeStart, changeEnd, removed, added, contextN = 3) {
49756
+ const preStart = Math.max(0, changeStart - contextN);
49757
+ let postEnd = Math.min(lines.length, changeEnd + contextN);
49758
+ if (postEnd === lines.length && lines.length > 0 && lines[lines.length - 1] === "") {
49759
+ postEnd -= 1;
49760
+ }
49761
+ const pre = lines.slice(preStart, changeStart);
49762
+ const post = lines.slice(changeEnd, Math.max(changeEnd, postEnd));
49763
+ const oldCount = pre.length + removed.length + post.length;
49764
+ const newCount = pre.length + added.length + post.length;
49765
+ const oldStartLine = preStart + 1;
49766
+ const newStartLine = preStart + 1;
49767
+ const out = [];
49768
+ out.push(`@@ -${oldStartLine},${oldCount} +${newStartLine},${newCount} @@`);
49769
+ for (const l of pre)
49770
+ out.push(` ${l}`);
49771
+ for (const l of removed)
49772
+ out.push(`-${l}`);
49773
+ for (const l of added)
49774
+ out.push(`+${l}`);
49775
+ for (const l of post)
49776
+ out.push(` ${l}`);
49777
+ return out.join(`
49778
+ `);
49779
+ }
49780
+ function wrapDiff(hunk) {
49781
+ return `${TARGET_HEADER_A}
49782
+ ${TARGET_HEADER_B}
49783
+ ${hunk}
49784
+ `;
49785
+ }
49786
+ function synthesizeAllowRuleDiff(args) {
49787
+ const { agentName: agentName3, rule, configText } = args;
49788
+ if (!agentName3 || !rule || !configText)
49789
+ return null;
49790
+ const lines = configText.split(`
49791
+ `);
49792
+ const block = locateAgentBlock(lines, agentName3);
49793
+ if (!block)
49794
+ return null;
49795
+ const tools = locateToolsLine(lines, block);
49796
+ if (!tools) {
49797
+ const baseIndent = block.agentIndent + 2;
49798
+ const insertAt2 = block.agentLineIdx + 1;
49799
+ const added2 = [
49800
+ `${" ".repeat(baseIndent)}tools:`,
49801
+ `${" ".repeat(baseIndent + 2)}allow:`,
49802
+ `${" ".repeat(baseIndent + 4)}- ${rule}`
49803
+ ];
49804
+ const hunk2 = buildHunk(lines, insertAt2, insertAt2, [], added2);
49805
+ return wrapDiff(hunk2);
49806
+ }
49807
+ const allow = locateAllowLine(lines, tools.idx, tools.indent, block.agentBlockEnd);
49808
+ if (!allow) {
49809
+ const baseIndent = tools.indent + 2;
49810
+ const insertAt2 = tools.idx + 1;
49811
+ const added2 = [
49812
+ `${" ".repeat(baseIndent)}allow:`,
49813
+ `${" ".repeat(baseIndent + 2)}- ${rule}`
49814
+ ];
49815
+ const hunk2 = buildHunk(lines, insertAt2, insertAt2, [], added2);
49816
+ return wrapDiff(hunk2);
49817
+ }
49818
+ const inlineTrimmed = allow.inline.trim();
49819
+ if (inlineTrimmed.startsWith("[")) {
49820
+ const original = lines[allow.idx];
49821
+ const close = original.lastIndexOf("]");
49822
+ if (close === -1)
49823
+ return null;
49824
+ const inner = original.slice(original.indexOf("[") + 1, close).trim();
49825
+ const replacement = inner.length === 0 ? `${original.slice(0, close)}${rule}${original.slice(close)}` : `${original.slice(0, close).replace(/\s*$/, "")}, ${rule}${original.slice(close)}`;
49826
+ const hunk2 = buildHunk(lines, allow.idx, allow.idx + 1, [original], [replacement]);
49827
+ return wrapDiff(hunk2);
49828
+ }
49829
+ const itemIndent = allow.indent + 2;
49830
+ let lastItemIdx = -1;
49831
+ for (let i = allow.idx + 1;i < block.agentBlockEnd; i++) {
49832
+ const line = lines[i];
49833
+ if (isBlankOrComment(line))
49834
+ continue;
49835
+ const ind = indentOf(line);
49836
+ if (ind <= allow.indent)
49837
+ break;
49838
+ const t = line.slice(ind);
49839
+ if (ind === itemIndent && t.startsWith("- ")) {
49840
+ lastItemIdx = i;
49841
+ }
49842
+ }
49843
+ if (lastItemIdx === -1) {
49844
+ const insertAt2 = allow.idx + 1;
49845
+ const added2 = [`${" ".repeat(itemIndent)}- ${rule}`];
49846
+ const hunk2 = buildHunk(lines, insertAt2, insertAt2, [], added2);
49847
+ return wrapDiff(hunk2);
49848
+ }
49849
+ const insertAt = lastItemIdx + 1;
49850
+ const added = [`${" ".repeat(itemIndent)}- ${rule}`];
49851
+ const hunk = buildHunk(lines, insertAt, insertAt, [], added);
49852
+ return wrapDiff(hunk);
49853
+ }
49854
+ function extractAddedAllowRule(unifiedDiff) {
49855
+ if (!unifiedDiff)
49856
+ return null;
49857
+ const lines = unifiedDiff.split(`
49858
+ `);
49859
+ const plus = [];
49860
+ const minus = [];
49861
+ for (const l of lines) {
49862
+ if (l.startsWith("+++") || l.startsWith("---"))
49863
+ continue;
49864
+ if (l.startsWith("@@"))
49865
+ continue;
49866
+ if (l.startsWith("+"))
49867
+ plus.push(l.slice(1));
49868
+ else if (l.startsWith("-"))
49869
+ minus.push(l.slice(1));
49870
+ }
49871
+ if (minus.length === 0) {
49872
+ const items = plus.map((p) => {
49873
+ const ind = indentOf(p);
49874
+ const t = p.slice(ind);
49875
+ return t.startsWith("- ") ? t.slice(2).trim() : null;
49876
+ }).filter((x) => x !== null);
49877
+ if (items.length === 1)
49878
+ return items[0];
49879
+ return null;
49880
+ }
49881
+ if (minus.length === 1 && plus.length === 1) {
49882
+ const before = extractFlowItems(minus[0]);
49883
+ const after = extractFlowItems(plus[0]);
49884
+ if (before === null || after === null)
49885
+ return null;
49886
+ if (after.length !== before.length + 1)
49887
+ return null;
49888
+ const added = after[after.length - 1];
49889
+ for (let i = 0;i < before.length; i++) {
49890
+ if (before[i] !== after[i])
49891
+ return null;
49892
+ }
49893
+ return added;
49894
+ }
49895
+ return null;
49896
+ }
49897
+ function extractFlowItems(line) {
49898
+ const open = line.indexOf("[");
49899
+ const close = line.lastIndexOf("]");
49900
+ if (open === -1 || close === -1 || close < open)
49901
+ return null;
49902
+ const inner = line.slice(open + 1, close).trim();
49903
+ if (inner.length === 0)
49904
+ return [];
49905
+ return inner.split(",").map((s) => s.trim()).filter((s) => s.length > 0);
49906
+ }
49907
+
49778
49908
  // credits-watch.ts
49779
49909
  import { readFileSync as readFileSync29, writeFileSync as writeFileSync18, existsSync as existsSync30, mkdirSync as mkdirSync16 } from "fs";
49780
49910
  import { join as join27 } from "path";
@@ -50094,10 +50224,10 @@ function sweepStaleTurnActiveMarker(stateDir, opts) {
50094
50224
  }
50095
50225
 
50096
50226
  // ../src/build-info.ts
50097
- var VERSION = "0.14.7";
50098
- var COMMIT_SHA = "0f782e65";
50099
- var COMMIT_DATE = "2026-05-28T22:15:06Z";
50100
- var LATEST_PR = 1976;
50227
+ var VERSION = "0.14.9";
50228
+ var COMMIT_SHA = "fd8ed8ed";
50229
+ var COMMIT_DATE = "2026-05-29T03:11:29Z";
50230
+ var LATEST_PR = 1985;
50101
50231
  var COMMITS_AHEAD_OF_TAG = 0;
50102
50232
 
50103
50233
  // gateway/boot-version.ts
@@ -51485,6 +51615,15 @@ var STATUS_QUERY_RE = /^\s*status\??\s*$/i;
51485
51615
  var PERMISSION_REPLY_RE = /^\s*(y|yes|n|no)\s+([a-km-z]{5})\s*$/i;
51486
51616
  var pendingPermissions = new Map;
51487
51617
  var PERMISSION_TTL_MS = 600000;
51618
+ var pendingAlwaysAllowCorrelations = new Map;
51619
+ var ALWAYS_ALLOW_CORRELATION_TTL_MS = 30000;
51620
+ function sweepStaleAlwaysAllowCorrelations(now = Date.now()) {
51621
+ for (const [key, entry] of pendingAlwaysAllowCorrelations) {
51622
+ if (now - entry.createdAt > ALWAYS_ALLOW_CORRELATION_TTL_MS) {
51623
+ pendingAlwaysAllowCorrelations.delete(key);
51624
+ }
51625
+ }
51626
+ }
51488
51627
  var pendingAskUser = new Map;
51489
51628
  var pendingReauthFlows = new Map;
51490
51629
  var REAUTH_INTERCEPT_TTL_MS = 600000;
@@ -51870,13 +52009,6 @@ var ANSWER_STREAM_VISIBLE_ENABLED = (() => {
51870
52009
  return false;
51871
52010
  return true;
51872
52011
  })();
51873
- var DRAFT_MIRROR_ENABLED = (() => {
51874
- const raw = process.env.SWITCHROOM_DRAFT_MIRROR;
51875
- if (raw == null)
51876
- return false;
51877
- const v = raw.trim().toLowerCase();
51878
- return !(v === "0" || v === "false" || v === "off" || v === "no");
51879
- })();
51880
52012
  var progressDriver = null;
51881
52013
  var unpinProgressCardForChat = null;
51882
52014
  var getPinnedProgressCardMessageId = null;
@@ -52589,7 +52721,20 @@ ${reminder}
52589
52721
  });
52590
52722
  },
52591
52723
  log: (m) => process.stderr.write(`telegram gateway: config-approval \u2014 ${m}
52592
- `)
52724
+ `),
52725
+ tryAutoResolve: (msg2) => {
52726
+ sweepStaleAlwaysAllowCorrelations();
52727
+ const added = extractAddedAllowRule(msg2.unifiedDiff);
52728
+ if (!added)
52729
+ return null;
52730
+ const key = `${msg2.agentName}::${added}`;
52731
+ const entry = pendingAlwaysAllowCorrelations.get(key);
52732
+ if (entry && entry.unifiedDiff === msg2.unifiedDiff) {
52733
+ pendingAlwaysAllowCorrelations.delete(key);
52734
+ return "approve";
52735
+ }
52736
+ return null;
52737
+ }
52593
52738
  });
52594
52739
  },
52595
52740
  async onRequestConfigFinalize(client3, msg) {
@@ -54084,22 +54229,17 @@ async function drainActivitySummary(turn) {
54084
54229
  const target = turn.activityPendingRender;
54085
54230
  if (target == null)
54086
54231
  break;
54087
- const html = `<i>${escapeHtmlForTg(target)}</i>`;
54232
+ const html = target;
54088
54233
  const chat = turn.sessionChatId;
54089
54234
  const thread = turn.sessionThreadId;
54090
- const useDraft = turn.isDm && thread == null && sendMessageDraftFn != null;
54235
+ const replyAnchor = turn.sourceMessageId != null ? { reply_parameters: { message_id: turn.sourceMessageId, allow_sending_without_reply: true } } : {};
54091
54236
  try {
54092
- if (useDraft) {
54093
- if (turn.activityDraftId == null) {
54094
- turn.activityDraftId = allocateDraftId();
54095
- }
54096
- const draftId = turn.activityDraftId;
54097
- await sendMessageDraftFn(chat, draftId, html, { parse_mode: "HTML" });
54098
- } else if (turn.activityMessageId == null) {
54237
+ if (turn.activityMessageId == null) {
54099
54238
  const sent = await robustApiCall(() => bot.api.sendMessage(chat, html, {
54100
54239
  ...thread != null ? { message_thread_id: thread } : {},
54101
54240
  parse_mode: "HTML",
54102
- disable_notification: true
54241
+ disable_notification: true,
54242
+ ...replyAnchor
54103
54243
  }), { chat_id: chat, ...thread != null ? { threadId: thread } : {}, verb: "activity-summary.send" });
54104
54244
  turn.activityMessageId = sent.message_id;
54105
54245
  } else {
@@ -54125,16 +54265,7 @@ function clearActivitySummary(turn) {
54125
54265
  const thread = turn.sessionThreadId;
54126
54266
  const inFlight = turn.activityInFlight ?? Promise.resolve();
54127
54267
  inFlight.then(async () => {
54128
- if (turn.activityDraftId != null && sendMessageDraftFn != null) {
54129
- const draftId = turn.activityDraftId;
54130
- turn.activityDraftId = null;
54131
- try {
54132
- await sendMessageDraftFn(chat, draftId, "", undefined);
54133
- } catch (err) {
54134
- process.stderr.write(`telegram gateway: activity-summary draft-clear failed: ${err}
54135
- `);
54136
- }
54137
- } else if (turn.activityMessageId != null) {
54268
+ if (turn.activityMessageId != null) {
54138
54269
  const id = turn.activityMessageId;
54139
54270
  turn.activityMessageId = null;
54140
54271
  try {
@@ -54165,6 +54296,7 @@ function handleSessionEvent(ev) {
54165
54296
  const next = {
54166
54297
  sessionChatId: ev.chatId,
54167
54298
  sessionThreadId: ev.threadId != null ? Number(ev.threadId) : undefined,
54299
+ sourceMessageId: ev.messageId != null && /^\d+$/.test(ev.messageId) ? Number(ev.messageId) : null,
54168
54300
  startedAt,
54169
54301
  gatewayReceiveAt: startedAt,
54170
54302
  replyCalled: false,
@@ -54178,9 +54310,7 @@ function handleSessionEvent(ev) {
54178
54310
  lastAssistantMsgId: null,
54179
54311
  lastAssistantDone: false,
54180
54312
  toolCallCount: 0,
54181
- toolActivity: makeEmptyActivityState(),
54182
54313
  activityMessageId: null,
54183
- activityDraftId: null,
54184
54314
  activityInFlight: null,
54185
54315
  activityPendingRender: null,
54186
54316
  activityLastSentRender: null,
@@ -54255,19 +54385,10 @@ function handleSessionEvent(ev) {
54255
54385
  clearTimeout(turn.orphanedReplyTimeoutId);
54256
54386
  turn.orphanedReplyTimeoutId = null;
54257
54387
  }
54258
- if (wasFirstReply && !DRAFT_MIRROR_ENABLED) {
54388
+ if (wasFirstReply) {
54259
54389
  clearActivitySummary(turn);
54260
54390
  }
54261
54391
  }
54262
- if (!DRAFT_MIRROR_ENABLED && !turn.replyCalled && !isTelegramSurfaceTool(name)) {
54263
- const rendered = registerAndRender(turn.toolActivity, name);
54264
- if (rendered != null) {
54265
- turn.activityPendingRender = rendered;
54266
- if (turn.activityInFlight == null) {
54267
- turn.activityInFlight = drainActivitySummary(turn);
54268
- }
54269
- }
54270
- }
54271
54392
  if (!ctrl)
54272
54393
  return;
54273
54394
  if (isTelegramSurfaceTool(name))
@@ -54279,13 +54400,13 @@ function handleSessionEvent(ev) {
54279
54400
  return;
54280
54401
  }
54281
54402
  case "tool_label": {
54282
- if (!DRAFT_MIRROR_ENABLED)
54283
- return;
54284
54403
  const turn = currentTurn;
54285
54404
  if (turn == null)
54286
54405
  return;
54287
54406
  if (isTelegramSurfaceTool(ev.toolName))
54288
54407
  return;
54408
+ if (turn.replyCalled)
54409
+ return;
54289
54410
  const rendered = appendActivityLabel(turn.mirrorLines, ev.label);
54290
54411
  if (rendered != null) {
54291
54412
  turn.activityPendingRender = rendered;
@@ -54424,7 +54545,7 @@ function handleSessionEvent(ev) {
54424
54545
  clearTimeout(turn.orphanedReplyTimeoutId);
54425
54546
  turn.orphanedReplyTimeoutId = null;
54426
54547
  }
54427
- if (DRAFT_MIRROR_ENABLED && turn != null) {
54548
+ if (turn != null) {
54428
54549
  clearActivitySummary(turn);
54429
54550
  }
54430
54551
  preambleSuppressor.flushNow();
@@ -59062,59 +59183,110 @@ ${prettyInput}`;
59062
59183
  await ctx.answerCallbackQuery({ text: "Always-allow needs SWITCHROOM_AGENT_NAME \u2014 gateway is misconfigured." }).catch(() => {});
59063
59184
  return;
59064
59185
  }
59065
- let grantOk = false;
59066
- let grantFailReason = "";
59067
- try {
59068
- switchroomExec(["agent", "grant", agentName3, rule.rule, "--no-restart"]);
59186
+ pendingPermissions.delete(request_id);
59187
+ dispatchPermissionVerdict({
59188
+ type: "permission",
59189
+ requestId: request_id,
59190
+ behavior: "allow",
59191
+ rule: rule.rule
59192
+ });
59193
+ let durable = false;
59194
+ let legacy = false;
59195
+ let failReason = "";
59196
+ let editLockHint = false;
59197
+ const configEditDisabled = (msg2) => msg2.includes("E_CONFIG_EDIT_DISABLED");
59198
+ const unifiedDiff = (() => {
59069
59199
  try {
59070
- const cfg = loadConfig2();
59071
- const rawAgent = cfg.agents?.[agentName3];
59072
- if (rawAgent) {
59073
- const resolved = resolveAgentConfig2(cfg.defaults, cfg.profiles, rawAgent);
59074
- const allowList = resolved.tools?.allow ?? [];
59075
- if (isRulePersisted(allowList, rule.rule)) {
59076
- grantOk = true;
59077
- process.stderr.write(`telegram gateway: always-allow added rule="${rule.rule}" agent=${agentName3} (request_id=${request_id})
59078
- `);
59079
- } else {
59080
- grantFailReason = `rule "${rule.rule}" not found in resolved tools.allow after write \u2014 config location may have drifted`;
59081
- process.stderr.write(`telegram gateway: always-allow VERIFY FAILED: ${grantFailReason} (request_id=${request_id})
59200
+ const cfgPath = process.env.SWITCHROOM_CONFIG ?? SWITCHROOM_CONFIG ?? findConfigFile2();
59201
+ const raw = readFileSync34(cfgPath, "utf8");
59202
+ return synthesizeAllowRuleDiff({ agentName: agentName3, rule: rule.rule, configText: raw });
59203
+ } catch (err) {
59204
+ process.stderr.write(`telegram gateway: always-allow diff synth failed: ${err.message}
59082
59205
  `);
59206
+ return null;
59207
+ }
59208
+ })();
59209
+ const correlationKey = `${agentName3}::${rule.rule}`;
59210
+ try {
59211
+ if (unifiedDiff == null) {
59212
+ legacy = true;
59213
+ } else {
59214
+ pendingAlwaysAllowCorrelations.set(correlationKey, { agentName: agentName3, rule: rule.rule, unifiedDiff, createdAt: Date.now() });
59215
+ const req = {
59216
+ v: 1,
59217
+ op: "config_propose_edit",
59218
+ request_id: hostdRequestId("gw-always-allow"),
59219
+ args: {
59220
+ unified_diff: unifiedDiff,
59221
+ reason: `Operator 'always allow' for ${rule.label}`,
59222
+ target_path: "/state/config/switchroom.yaml"
59083
59223
  }
59224
+ };
59225
+ const resp = await tryHostdDispatch(agentName3, req, 60000);
59226
+ if (resp === "not-configured") {
59227
+ warnLegacySpawnIfHostdDisabled("always-allow");
59228
+ legacy = true;
59229
+ } else if (resp.result === "completed") {
59230
+ durable = true;
59231
+ process.stderr.write(`telegram gateway: always-allow durable via hostd rule="${rule.rule}" agent=${agentName3} (request_id=${request_id})
59232
+ `);
59084
59233
  } else {
59085
- grantFailReason = `agent "${agentName3}" not found in config after write`;
59086
- process.stderr.write(`telegram gateway: always-allow VERIFY FAILED: ${grantFailReason} (request_id=${request_id})
59234
+ failReason = resp.error ?? `hostd ${resp.result}`;
59235
+ if (configEditDisabled(failReason))
59236
+ editLockHint = true;
59237
+ process.stderr.write(`telegram gateway: always-allow hostd FAILED: ${failReason} (request_id=${request_id})
59087
59238
  `);
59088
59239
  }
59089
- } catch (verifyErr) {
59090
- grantFailReason = `config re-read failed: ${verifyErr.message}`;
59091
- process.stderr.write(`telegram gateway: always-allow VERIFY FAILED: ${grantFailReason} (request_id=${request_id})
59092
- `);
59093
59240
  }
59094
- } catch (err) {
59095
- grantFailReason = err.message;
59096
- process.stderr.write(`telegram gateway: always-allow grant failed: ${grantFailReason}
59241
+ if (legacy) {
59242
+ try {
59243
+ switchroomExec(["agent", "grant", agentName3, rule.rule, "--no-restart"]);
59244
+ try {
59245
+ const cfg = loadConfig2();
59246
+ const rawAgent = cfg.agents?.[agentName3];
59247
+ if (rawAgent) {
59248
+ const resolved = resolveAgentConfig2(cfg.defaults, cfg.profiles, rawAgent);
59249
+ const allowList = resolved.tools?.allow ?? [];
59250
+ if (isRulePersisted(allowList, rule.rule)) {
59251
+ durable = true;
59252
+ process.stderr.write(`telegram gateway: always-allow added rule="${rule.rule}" agent=${agentName3} via legacy grant (request_id=${request_id})
59253
+ `);
59254
+ } else {
59255
+ failReason = `rule "${rule.rule}" not found in resolved tools.allow after write \u2014 config location may have drifted`;
59256
+ process.stderr.write(`telegram gateway: always-allow VERIFY FAILED: ${failReason} (request_id=${request_id})
59257
+ `);
59258
+ }
59259
+ } else {
59260
+ failReason = `agent "${agentName3}" not found in config after write`;
59261
+ process.stderr.write(`telegram gateway: always-allow VERIFY FAILED: ${failReason} (request_id=${request_id})
59262
+ `);
59263
+ }
59264
+ } catch (verifyErr) {
59265
+ failReason = `config re-read failed: ${verifyErr.message}`;
59266
+ process.stderr.write(`telegram gateway: always-allow VERIFY FAILED: ${failReason} (request_id=${request_id})
59097
59267
  `);
59268
+ }
59269
+ } catch (err) {
59270
+ failReason = err.message;
59271
+ process.stderr.write(`telegram gateway: always-allow grant failed: ${failReason}
59272
+ `);
59273
+ }
59274
+ }
59275
+ } finally {
59276
+ pendingAlwaysAllowCorrelations.delete(correlationKey);
59098
59277
  }
59099
- pendingPermissions.delete(request_id);
59100
- const ackText = grantOk ? `\uD83D\uDD01 Always allow ${rule.label} for ${agentName3}` : `\u26A0\uFE0F Allowed for now, but "always" did NOT save \u2014 it will ask again after restart. Check gateway log.`;
59278
+ const ok = durable;
59279
+ const legacyNote = legacy && durable;
59280
+ const ackText = ok ? legacyNote ? `\uD83D\uDD01 Always allow ${rule.label} for ${agentName3} (legacy path)` : `\uD83D\uDD01 Always allow ${rule.label} for ${agentName3}` : editLockHint ? `\u26A0\uFE0F Allowed for now \u2014 config edits are locked. Enable hostd.config_edit_enabled.` : `\u26A0\uFE0F Allowed for now, but "always" did NOT save \u2014 it will ask again after restart. Check gateway log.`;
59101
59281
  const sourceMsg = ctx.callbackQuery?.message;
59102
59282
  const baseText2 = sourceMsg && "text" in sourceMsg && sourceMsg.text ? escapeHtmlForTg(sourceMsg.text) : "";
59103
- const editLabel = grantOk ? `\uD83D\uDD01 <b>Always allow ${escapeHtmlForTg(rule.label)}</b> for ${escapeHtmlForTg(agentName3)} \u2014 restart agent for full effect` : `\u26A0\uFE0F <b>Allowed for now \u2014 "always" did NOT save.</b> It will ask again after restart. Check gateway log.`;
59283
+ const editLabel = ok ? legacyNote ? `\uD83D\uDD01 <b>Always allow ${escapeHtmlForTg(rule.label)}</b> for ${escapeHtmlForTg(agentName3)} \u2014 saved (legacy path); restart agent for full effect` : `\uD83D\uDD01 <b>Always allow ${escapeHtmlForTg(rule.label)}</b> for ${escapeHtmlForTg(agentName3)} \u2014 saved; restart agent for full effect` : editLockHint ? `\u26A0\uFE0F <b>Allowed for now \u2014 "always" did NOT save.</b> Config edits are locked; enable <code>hostd.config_edit_enabled</code>.` : `\u26A0\uFE0F <b>Allowed for now \u2014 "always" did NOT save.</b> It will ask again after restart. Check gateway log.`;
59104
59284
  await finalizeCallback(ctx, {
59105
59285
  ackText: ackText.slice(0, 200),
59106
59286
  newText: baseText2 ? `${baseText2}
59107
59287
 
59108
59288
  ${editLabel}` : editLabel,
59109
- parseMode: "HTML",
59110
- synthInbound: () => {
59111
- dispatchPermissionVerdict({
59112
- type: "permission",
59113
- requestId: request_id,
59114
- behavior: "allow",
59115
- ...grantOk ? { rule: rule.rule } : {}
59116
- });
59117
- }
59289
+ parseMode: "HTML"
59118
59290
  });
59119
59291
  return;
59120
59292
  }