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.
- package/dist/cli/switchroom.js +40 -2
- package/package.json +1 -1
- package/profiles/_base/start.sh.hbs +23 -0
- package/telegram-plugin/dist/gateway/gateway.js +397 -225
- package/telegram-plugin/gateway/config-approval-handler.ts +36 -0
- package/telegram-plugin/gateway/gateway.ts +285 -225
- package/telegram-plugin/gateway/hostd-dispatch.ts +2 -1
- package/telegram-plugin/permission-diff.ts +382 -0
- package/telegram-plugin/tests/always-allow-correlation.test.ts +147 -0
- package/telegram-plugin/tests/always-allow-grant.test.ts +84 -88
- package/telegram-plugin/tests/permission-diff.test.ts +336 -0
- package/telegram-plugin/tests/tool-activity-summary.test.ts +25 -229
- package/telegram-plugin/tool-activity-summary.ts +45 -212
|
@@ -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, "&").replace(/</g, "<").replace(/>/g, ">");
|
|
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
|
|
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
|
|
32209
|
-
function
|
|
32097
|
+
var DRAFT_STREAM_STATE_KEY = Symbol.for("switchroom.draftStreamState");
|
|
32098
|
+
function getDraftStreamState() {
|
|
32210
32099
|
const g = globalThis;
|
|
32211
|
-
let state = g[
|
|
32100
|
+
let state = g[DRAFT_STREAM_STATE_KEY];
|
|
32212
32101
|
if (!state) {
|
|
32213
32102
|
state = { nextDraftId: 0 };
|
|
32214
|
-
g[
|
|
32103
|
+
g[DRAFT_STREAM_STATE_KEY] = state;
|
|
32215
32104
|
}
|
|
32216
32105
|
return state;
|
|
32217
32106
|
}
|
|
32218
|
-
function
|
|
32219
|
-
const state =
|
|
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 ?
|
|
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 =
|
|
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
|
|
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 ?
|
|
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 =
|
|
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
|
|
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.
|
|
50098
|
-
var COMMIT_SHA = "
|
|
50099
|
-
var COMMIT_DATE = "2026-05-
|
|
50100
|
-
var LATEST_PR =
|
|
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 =
|
|
54232
|
+
const html = target;
|
|
54088
54233
|
const chat = turn.sessionChatId;
|
|
54089
54234
|
const thread = turn.sessionThreadId;
|
|
54090
|
-
const
|
|
54235
|
+
const replyAnchor = turn.sourceMessageId != null ? { reply_parameters: { message_id: turn.sourceMessageId, allow_sending_without_reply: true } } : {};
|
|
54091
54236
|
try {
|
|
54092
|
-
if (
|
|
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.
|
|
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
|
|
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 (
|
|
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
|
-
|
|
59066
|
-
|
|
59067
|
-
|
|
59068
|
-
|
|
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
|
|
59071
|
-
const
|
|
59072
|
-
|
|
59073
|
-
|
|
59074
|
-
|
|
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
|
-
|
|
59086
|
-
|
|
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
|
-
|
|
59095
|
-
|
|
59096
|
-
|
|
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
|
-
|
|
59100
|
-
const
|
|
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 =
|
|
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
|
}
|