switchroom 0.13.8 → 0.13.10

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.
@@ -27225,7 +27225,7 @@ var init_secretlint_source = __esm(() => {
27225
27225
  function escapeHtml8(s) {
27226
27226
  return s.replace(/[&<>]/g, (c) => ({ "&": "&amp;", "<": "&lt;", ">": "&gt;" })[c]);
27227
27227
  }
27228
- function truncate2(s, n) {
27228
+ function truncate3(s, n) {
27229
27229
  return s.length > n ? s.slice(0, n - 1) + "\u2026" : s;
27230
27230
  }
27231
27231
 
@@ -29017,6 +29017,161 @@ var init_materialize_bot_token = __esm(() => {
29017
29017
  };
29018
29018
  });
29019
29019
 
29020
+ // gateway/config-approval-handler.ts
29021
+ var exports_config_approval_handler = {};
29022
+ __export(exports_config_approval_handler, {
29023
+ resolvePendingConfigApproval: () => resolvePendingConfigApproval,
29024
+ parseConfigApprovalCallback: () => parseConfigApprovalCallback,
29025
+ handleRequestConfigFinalize: () => handleRequestConfigFinalize,
29026
+ handleRequestConfigApproval: () => handleRequestConfigApproval,
29027
+ buildConfigApprovalCardBody: () => buildConfigApprovalCardBody,
29028
+ _resetPendingConfigApprovalsForTest: () => _resetPendingConfigApprovalsForTest,
29029
+ _peekPendingConfigApprovalForTest: () => _peekPendingConfigApprovalForTest
29030
+ });
29031
+ function buildConfigApprovalCardBody(args) {
29032
+ const esc = (s) => s.replace(/&/g, "&amp;").replace(/</g, "&lt;").replace(/>/g, "&gt;");
29033
+ return `\uD83D\uDEE0 <b>Config edit proposed</b>
29034
+ ` + `Agent: <code>${esc(args.agentName)}</code>
29035
+ ` + `Reason: ${esc(args.reason)}
29036
+
29037
+ ` + `<pre>${esc(args.unifiedDiff)}</pre>`;
29038
+ }
29039
+ async function handleRequestConfigApproval(client3, msg, deps) {
29040
+ const reply = (verdict, reason) => {
29041
+ try {
29042
+ client3.send({
29043
+ type: "config_approval_resolved",
29044
+ requestId: msg.requestId,
29045
+ verdict,
29046
+ ...reason ? { reason } : {}
29047
+ });
29048
+ } catch (err) {
29049
+ deps.log?.(`config_approval_resolved send failed (requestId=${msg.requestId}): ${err.message}`);
29050
+ }
29051
+ };
29052
+ if (msg.agentName !== deps.agentName) {
29053
+ reply("deny", `gateway serves '${deps.agentName}', not '${msg.agentName}'`);
29054
+ return;
29055
+ }
29056
+ const target = deps.loadTargetChat();
29057
+ if (target === null) {
29058
+ reply("deny", "no target chat available \u2014 operator not paired?");
29059
+ return;
29060
+ }
29061
+ const body = buildConfigApprovalCardBody({
29062
+ agentName: msg.agentName,
29063
+ reason: msg.reason,
29064
+ unifiedDiff: msg.unifiedDiff
29065
+ });
29066
+ const replyMarkup = deps.buildKeyboard(msg.requestId);
29067
+ const posted = await deps.postCard({
29068
+ chatId: target.chatId,
29069
+ ...target.threadId !== undefined ? { threadId: target.threadId } : {},
29070
+ text: body,
29071
+ replyMarkup
29072
+ });
29073
+ if (posted === null) {
29074
+ reply("deny", "Telegram sendMessage failed");
29075
+ return;
29076
+ }
29077
+ const entry = {
29078
+ requestId: msg.requestId,
29079
+ client: client3,
29080
+ chatId: target.chatId,
29081
+ ...target.threadId !== undefined ? { threadId: target.threadId } : {},
29082
+ messageId: posted.messageId,
29083
+ timer: null,
29084
+ resolved: false
29085
+ };
29086
+ entry.timer = setTimeout(() => {
29087
+ resolvePendingConfigApproval(msg.requestId, "timeout", deps).catch((err) => deps.log?.(`config approval timeout handler threw (requestId=${msg.requestId}): ${err.message}`));
29088
+ }, msg.timeoutMs);
29089
+ pending.set(msg.requestId, entry);
29090
+ deps.log?.(`config_approval_posted requestId=${msg.requestId} agent=${msg.agentName} messageId=${posted.messageId}`);
29091
+ }
29092
+ async function resolvePendingConfigApproval(requestId, verdict, deps) {
29093
+ const entry = pending.get(requestId);
29094
+ if (!entry || entry.resolved)
29095
+ return false;
29096
+ entry.resolved = true;
29097
+ if (entry.timer !== null) {
29098
+ clearTimeout(entry.timer);
29099
+ entry.timer = null;
29100
+ }
29101
+ try {
29102
+ entry.client.send({
29103
+ type: "config_approval_resolved",
29104
+ requestId,
29105
+ verdict
29106
+ });
29107
+ } catch (err) {
29108
+ deps.log?.(`config_approval_resolved send failed (requestId=${requestId}): ${err.message}`);
29109
+ }
29110
+ const interim = verdict === "approve" ? "\uD83D\uDC40 <b>Applying\u2026</b>" : verdict === "deny" ? "\uD83D\uDEAB <b>Denied</b>" : "\u23f1 <b>Expired</b>";
29111
+ try {
29112
+ await deps.editCard({
29113
+ chatId: entry.chatId,
29114
+ messageId: entry.messageId,
29115
+ text: interim
29116
+ });
29117
+ } catch (err) {
29118
+ deps.log?.(`config approval card edit failed (requestId=${requestId}): ${err.message}`);
29119
+ }
29120
+ return true;
29121
+ }
29122
+ async function handleRequestConfigFinalize(_client, msg, deps) {
29123
+ const entry = pending.get(msg.requestId);
29124
+ if (!entry) {
29125
+ deps.log?.(`config_finalize: no pending entry for requestId=${msg.requestId} (likely already cleaned up)`);
29126
+ return;
29127
+ }
29128
+ pending.delete(msg.requestId);
29129
+ const body = msg.outcome === "applied" ? `\u2705 <b>Applied</b>${msg.detail ? `
29130
+ ${escapeHtml11(msg.detail)}` : ""}` : `\u26a0\ufe0f <b>Reconcile failed; rolled back</b>${msg.detail ? `
29131
+ ${escapeHtml11(msg.detail)}` : ""}`;
29132
+ try {
29133
+ await deps.editCard({
29134
+ chatId: entry.chatId,
29135
+ messageId: entry.messageId,
29136
+ text: body
29137
+ });
29138
+ } catch (err) {
29139
+ deps.log?.(`config finalize card edit failed (requestId=${msg.requestId}): ${err.message}`);
29140
+ }
29141
+ }
29142
+ function escapeHtml11(s) {
29143
+ return s.replace(/&/g, "&amp;").replace(/</g, "&lt;").replace(/>/g, "&gt;");
29144
+ }
29145
+ function _resetPendingConfigApprovalsForTest() {
29146
+ for (const entry of pending.values()) {
29147
+ if (entry.timer !== null)
29148
+ clearTimeout(entry.timer);
29149
+ }
29150
+ pending.clear();
29151
+ }
29152
+ function _peekPendingConfigApprovalForTest(requestId) {
29153
+ return pending.get(requestId);
29154
+ }
29155
+ function parseConfigApprovalCallback(data) {
29156
+ if (!data.startsWith("cfg:"))
29157
+ return null;
29158
+ const rest = data.slice(4);
29159
+ const colon = rest.lastIndexOf(":");
29160
+ if (colon < 0)
29161
+ return null;
29162
+ const requestId = rest.slice(0, colon);
29163
+ const choice = rest.slice(colon + 1);
29164
+ if (requestId.length === 0 || requestId.length > 64)
29165
+ return null;
29166
+ if (choice !== "approve" && choice !== "deny")
29167
+ return null;
29168
+ return { requestId, choice };
29169
+ }
29170
+ var pending;
29171
+ var init_config_approval_handler = __esm(() => {
29172
+ pending = new Map;
29173
+ });
29174
+
29020
29175
  // ../src/agents/tmux.ts
29021
29176
  var exports_tmux = {};
29022
29177
  __export(exports_tmux, {
@@ -29229,17 +29384,17 @@ function registerApprovalsCommands(bot, opts) {
29229
29384
  return;
29230
29385
  }
29231
29386
  if (decisions.length === 0) {
29232
- await ctx.reply(agentFilter ? `No active approvals for <code>${escapeHtml11(agentFilter)}</code>.` : "No active approvals.", { parse_mode: "HTML" });
29387
+ await ctx.reply(agentFilter ? `No active approvals for <code>${escapeHtml12(agentFilter)}</code>.` : "No active approvals.", { parse_mode: "HTML" });
29233
29388
  return;
29234
29389
  }
29235
29390
  const byAgent = new Map;
29236
29391
  for (const d of decisions)
29237
29392
  byAgent.set(d.agent_unit, (byAgent.get(d.agent_unit) ?? 0) + 1);
29238
- const summary = Array.from(byAgent.entries()).map(([a, n]) => `\u2022 <b>${escapeHtml11(a)}</b>: ${n}`).join(`
29393
+ const summary = Array.from(byAgent.entries()).map(([a, n]) => `\u2022 <b>${escapeHtml12(a)}</b>: ${n}`).join(`
29239
29394
  `);
29240
29395
  const detail = decisions.slice(0, 20).map((d) => {
29241
29396
  const ttl = d.ttl_expires_at === null ? "always" : `until ${new Date(d.ttl_expires_at).toISOString().slice(0, 16).replace("T", " ")}`;
29242
- return `<code>${escapeHtml11(d.id.slice(0, 8))}</code> ` + `${escapeHtml11(d.agent_unit)} \u2192 ` + `<code>${escapeHtml11(d.scope)}</code> ` + `(${escapeHtml11(d.action)}, ${ttl}) ` + `\u00b7 /approvals revoke ${escapeHtml11(d.id)}`;
29397
+ return `<code>${escapeHtml12(d.id.slice(0, 8))}</code> ` + `${escapeHtml12(d.agent_unit)} \u2192 ` + `<code>${escapeHtml12(d.scope)}</code> ` + `(${escapeHtml12(d.action)}, ${ttl}) ` + `\u00b7 /approvals revoke ${escapeHtml12(d.id)}`;
29243
29398
  }).join(`
29244
29399
  `);
29245
29400
  await ctx.reply(`<b>Active approvals</b>
@@ -29265,13 +29420,13 @@ ${detail}`, {
29265
29420
  await ctx.reply("Approval kernel unreachable.");
29266
29421
  return;
29267
29422
  }
29268
- await ctx.reply(ok ? `Revoked <code>${escapeHtml11(id)}</code>.` : `No such active decision <code>${escapeHtml11(id)}</code>.`, { parse_mode: "HTML" });
29423
+ await ctx.reply(ok ? `Revoked <code>${escapeHtml12(id)}</code>.` : `No such active decision <code>${escapeHtml12(id)}</code>.`, { parse_mode: "HTML" });
29269
29424
  return;
29270
29425
  }
29271
- await ctx.reply(`Unknown subcommand <code>${escapeHtml11(sub)}</code>. ` + `Use <code>/approvals list</code> or <code>/approvals revoke &lt;id&gt;</code>. ` + `(<code>add</code> and <code>stats</code> are coming in a follow-up.)`, { parse_mode: "HTML" });
29426
+ await ctx.reply(`Unknown subcommand <code>${escapeHtml12(sub)}</code>. ` + `Use <code>/approvals list</code> or <code>/approvals revoke &lt;id&gt;</code>. ` + `(<code>add</code> and <code>stats</code> are coming in a follow-up.)`, { parse_mode: "HTML" });
29272
29427
  });
29273
29428
  }
29274
- function escapeHtml11(s) {
29429
+ function escapeHtml12(s) {
29275
29430
  return s.replace(/&/g, "&amp;").replace(/</g, "&lt;").replace(/>/g, "&gt;");
29276
29431
  }
29277
29432
  var init_approvals_commands = __esm(() => {
@@ -29353,16 +29508,16 @@ function renderAccountRow2(snap, opts) {
29353
29508
  const lines = [];
29354
29509
  const marker = snap.isActive ? "\u25cf " : "";
29355
29510
  if (!snap.quota) {
29356
- lines.push(`${marker}<code>${escapeHtml12(snap.label)}</code> <i>quota probe failed</i>`);
29511
+ lines.push(`${marker}<code>${escapeHtml13(snap.label)}</code> <i>quota probe failed</i>`);
29357
29512
  if (snap.quotaError) {
29358
- lines.push(` <i>${escapeHtml12(snap.quotaError)}</i>`);
29513
+ lines.push(` <i>${escapeHtml13(snap.quotaError)}</i>`);
29359
29514
  }
29360
29515
  return lines;
29361
29516
  }
29362
29517
  const q = snap.quota;
29363
29518
  const fiveStr = fmtPct2(q.fiveHourUtilizationPct);
29364
29519
  const sevenStr = fmtPct2(q.sevenDayUtilizationPct);
29365
- lines.push(`${marker}<code>${escapeHtml12(snap.label)}</code> ${fiveStr} / ${sevenStr}`);
29520
+ lines.push(`${marker}<code>${escapeHtml13(snap.label)}</code> ${fiveStr} / ${sevenStr}`);
29366
29521
  const health = classifyHealth2(snap);
29367
29522
  if (health === "blocked") {
29368
29523
  const win = bindingWindow2(q);
@@ -29468,13 +29623,13 @@ function renderFallbackAnnouncement2(input) {
29468
29623
  const limitWord = input.oldQuota ? limitWordFor2(input.oldQuota) : "quota";
29469
29624
  const headerLimit = limitWord === "quota" ? "quota cap" : `${limitWord} limit`;
29470
29625
  if (!input.newLabel) {
29471
- lines.push(`\uD83D\uDD34 <b>All accounts blocked \u00b7 ${headerLimit} on ${escapeHtml12(input.oldLabel)}</b>`);
29626
+ lines.push(`\uD83D\uDD34 <b>All accounts blocked \u00b7 ${headerLimit} on ${escapeHtml13(input.oldLabel)}</b>`);
29472
29627
  lines.push("");
29473
- lines.push(`Triggered by: agent <b>${escapeHtml12(input.triggerAgent)}</b>`);
29628
+ lines.push(`Triggered by: agent <b>${escapeHtml13(input.triggerAgent)}</b>`);
29474
29629
  if (input.oldQuota) {
29475
29630
  const recovery = recoveryAtFor2(input.oldQuota);
29476
29631
  if (recovery) {
29477
- lines.push(`${escapeHtml12(input.oldLabel)} recovers ${formatAbsolute2(recovery, tz)} ` + `(in ${formatRelative2(recovery, now)})`);
29632
+ lines.push(`${escapeHtml13(input.oldLabel)} recovers ${formatAbsolute2(recovery, tz)} ` + `(in ${formatRelative2(recovery, now)})`);
29478
29633
  }
29479
29634
  }
29480
29635
  lines.push("");
@@ -29482,15 +29637,15 @@ function renderFallbackAnnouncement2(input) {
29482
29637
  return lines.join(`
29483
29638
  `);
29484
29639
  }
29485
- lines.push(`\u2713 <b>Switched fleet \u00b7 ${headerLimit} on ${escapeHtml12(input.oldLabel)}</b>`);
29640
+ lines.push(`\u2713 <b>Switched fleet \u00b7 ${headerLimit} on ${escapeHtml13(input.oldLabel)}</b>`);
29486
29641
  lines.push("");
29487
- lines.push(`<code>${escapeHtml12(input.oldLabel)}</code> \u2192 <code>${escapeHtml12(input.newLabel)}</code>`);
29488
- lines.push(`Triggered by: agent <b>${escapeHtml12(input.triggerAgent)}</b>`);
29642
+ lines.push(`<code>${escapeHtml13(input.oldLabel)}</code> \u2192 <code>${escapeHtml13(input.newLabel)}</code>`);
29643
+ lines.push(`Triggered by: agent <b>${escapeHtml13(input.triggerAgent)}</b>`);
29489
29644
  lines.push("");
29490
29645
  if (input.oldQuota) {
29491
29646
  const recovery = recoveryAtFor2(input.oldQuota);
29492
29647
  if (recovery) {
29493
- lines.push(`<code>${escapeHtml12(input.oldLabel)}</code> recovers ` + `${formatAbsolute2(recovery, tz)} (in ${formatRelative2(recovery, now)})`);
29648
+ lines.push(`<code>${escapeHtml13(input.oldLabel)}</code> recovers ` + `${formatAbsolute2(recovery, tz)} (in ${formatRelative2(recovery, now)})`);
29494
29649
  }
29495
29650
  }
29496
29651
  if (input.newQuota) {
@@ -29498,7 +29653,7 @@ function renderFallbackAnnouncement2(input) {
29498
29653
  const sevenStr = fmtPct2(input.newQuota.sevenDayUtilizationPct);
29499
29654
  const hasHeadroom = input.newQuota.fiveHourUtilizationPct < THROTTLING_THRESHOLD_PCT2 && input.newQuota.sevenDayUtilizationPct < THROTTLING_THRESHOLD_PCT2;
29500
29655
  const headroomStr = hasHeadroom ? "<i>(plenty of headroom)</i>" : "<i>(near limit \u2014 watch this)</i>";
29501
- lines.push(`<code>${escapeHtml12(input.newLabel)}</code> now: ${fiveStr} of 5h \u00b7 ${sevenStr} of 7d ${headroomStr}`);
29656
+ lines.push(`<code>${escapeHtml13(input.newLabel)}</code> now: ${fiveStr} of 5h \u00b7 ${sevenStr} of 7d ${headroomStr}`);
29502
29657
  } else {
29503
29658
  lines.push(`<i>(quota probe for new account is pending \u2014 will reflect on next /auth)</i>`);
29504
29659
  }
@@ -29557,7 +29712,7 @@ function switchPriority2(s) {
29557
29712
  return 2;
29558
29713
  return 3;
29559
29714
  }
29560
- function escapeHtml12(s) {
29715
+ function escapeHtml13(s) {
29561
29716
  return s.replace(/&/g, "&amp;").replace(/</g, "&lt;").replace(/>/g, "&gt;");
29562
29717
  }
29563
29718
  function buildSnapshotsFromState2(state4, quotas) {
@@ -43135,6 +43290,28 @@ function validateClientMessage(msg) {
43135
43290
  const inb = m.inbound;
43136
43291
  return inb.type === "inbound" && typeof inb.chatId === "string" && inb.chatId.length > 0 && typeof inb.text === "string" && typeof inb.messageId === "number" && typeof inb.user === "string" && typeof inb.userId === "number" && typeof inb.ts === "number" && typeof inb.meta === "object" && inb.meta !== null;
43137
43292
  }
43293
+ case "request_config_approval": {
43294
+ if (typeof m.requestId !== "string" || m.requestId.length === 0 || m.requestId.length > 64)
43295
+ return false;
43296
+ if (typeof m.agentName !== "string" || !AGENT_NAME_RE3.test(m.agentName))
43297
+ return false;
43298
+ if (typeof m.reason !== "string" || m.reason.length === 0 || m.reason.length > 500)
43299
+ return false;
43300
+ if (typeof m.unifiedDiff !== "string" || m.unifiedDiff.length === 0)
43301
+ return false;
43302
+ if (typeof m.timeoutMs !== "number" || !Number.isFinite(m.timeoutMs) || m.timeoutMs <= 0)
43303
+ return false;
43304
+ return true;
43305
+ }
43306
+ case "request_config_finalize": {
43307
+ if (typeof m.requestId !== "string" || m.requestId.length === 0 || m.requestId.length > 64)
43308
+ return false;
43309
+ if (m.outcome !== "applied" && m.outcome !== "reconcile_failed_rolled_back")
43310
+ return false;
43311
+ if (m.detail !== undefined && (typeof m.detail !== "string" || m.detail.length > 500))
43312
+ return false;
43313
+ return true;
43314
+ }
43138
43315
  case "request_drive_approval": {
43139
43316
  if (typeof m.correlationId !== "string" || m.correlationId.length === 0 || m.correlationId.length > 64)
43140
43317
  return false;
@@ -43164,6 +43341,8 @@ function createIpcServer(options) {
43164
43341
  onPtyPartial,
43165
43342
  onInjectInbound,
43166
43343
  onRequestDriveApproval,
43344
+ onRequestConfigApproval,
43345
+ onRequestConfigFinalize,
43167
43346
  log = () => {},
43168
43347
  heartbeatTimeoutMs = 30000
43169
43348
  } = options;
@@ -43268,6 +43447,37 @@ function createIpcServer(options) {
43268
43447
  } catch {}
43269
43448
  }
43270
43449
  break;
43450
+ case "request_config_approval":
43451
+ if (onRequestConfigApproval) {
43452
+ onRequestConfigApproval(client3, msg).catch((err) => {
43453
+ log(`request_config_approval handler threw (client=${client3.id}): ${err.message}`);
43454
+ try {
43455
+ client3.send({
43456
+ type: "config_approval_resolved",
43457
+ requestId: msg.requestId,
43458
+ verdict: "deny",
43459
+ reason: `gateway handler error: ${err.message}`
43460
+ });
43461
+ } catch {}
43462
+ });
43463
+ } else {
43464
+ try {
43465
+ client3.send({
43466
+ type: "config_approval_resolved",
43467
+ requestId: msg.requestId,
43468
+ verdict: "deny",
43469
+ reason: "gateway not configured for config-edit approval"
43470
+ });
43471
+ } catch {}
43472
+ }
43473
+ break;
43474
+ case "request_config_finalize":
43475
+ if (onRequestConfigFinalize) {
43476
+ onRequestConfigFinalize(client3, msg).catch((err) => {
43477
+ log(`request_config_finalize handler threw (client=${client3.id}): ${err.message}`);
43478
+ });
43479
+ }
43480
+ break;
43271
43481
  case "update_placeholder":
43272
43482
  if (!loggedLegacyUpdatePlaceholder.has(client3.id)) {
43273
43483
  loggedLegacyUpdatePlaceholder.add(client3.id);
@@ -44530,6 +44740,49 @@ function buildVaultSaveDiscardedInbound(opts) {
44530
44740
  };
44531
44741
  }
44532
44742
 
44743
+ // gateway/subagent-handback-inbound-builder.ts
44744
+ var HANDBACK_RESULT_MAX = 3000;
44745
+ var HANDBACK_DESC_MAX = 200;
44746
+ function truncate2(s, max) {
44747
+ const t = s.trim();
44748
+ return t.length > max ? t.slice(0, max) + "\u2026" : t;
44749
+ }
44750
+ function buildSubagentHandbackInbound(opts) {
44751
+ const ts = opts.nowMs ?? Date.now();
44752
+ const desc = truncate2(opts.ctx.taskDescription, HANDBACK_DESC_MAX) || "(no description)";
44753
+ const result = truncate2(opts.ctx.resultText, HANDBACK_RESULT_MAX);
44754
+ const text = opts.ctx.outcome === "failed" ? `\uD83E\uDD1D A background worker you dispatched has FAILED.
44755
+
44756
+ ` + `Task: ${desc}
44757
+
44758
+ ` + (result ? `What it reported before failing:
44759
+ ${result}
44760
+
44761
+ ` : "") + `This is beat 4 \u2014 the handback. Tell the user plainly that the ` + `delegated work did not complete, what is known, and your ` + `recommended next step \u2014 one \`reply\` in your own voice. Do not ` + `stay silent.` : `\uD83E\uDD1D A background worker you dispatched has finished.
44762
+
44763
+ ` + `Task: ${desc}
44764
+
44765
+ ` + (result ? `What the worker reported:
44766
+ ${result}
44767
+
44768
+ ` : `The worker left no summary text.
44769
+
44770
+ `) + `This is beat 4 \u2014 the handback. Synthesise this for the user ` + `now: one \`reply\` in your own voice covering what the worker ` + `found and your recommended next step. Do NOT paste the raw ` + `report and do NOT stay silent \u2014 the user dispatched this and ` + `is waiting to hear back.`;
44771
+ return {
44772
+ type: "inbound",
44773
+ chatId: opts.ctx.chatId,
44774
+ messageId: ts,
44775
+ user: "subagent-watcher",
44776
+ userId: 0,
44777
+ ts,
44778
+ text,
44779
+ meta: {
44780
+ source: "subagent_handback",
44781
+ outcome: opts.ctx.outcome
44782
+ }
44783
+ };
44784
+ }
44785
+
44533
44786
  // gateway/poll-health.ts
44534
44787
  var DEFAULT_LOG = (msg) => {
44535
44788
  process.stderr.write(msg.endsWith(`
@@ -46106,6 +46359,7 @@ var DEFAULT_RESCAN_MS = 1000;
46106
46359
  var DEFAULT_STALL_THRESHOLD_MS = 60000;
46107
46360
  var DEFAULT_SILENT_SYNTHESIS_STALL_THRESHOLD_MS = 300000;
46108
46361
  var DEFAULT_SILENT_STALL_TERMINAL_MS = 300000;
46362
+ var SUBAGENT_RESULT_TEXT_MAX = 3000;
46109
46363
  function parseEnvMs(varName) {
46110
46364
  const raw = process.env[varName];
46111
46365
  if (raw == null || raw === "")
@@ -46231,6 +46485,7 @@ function readSubTail(entry, tail, now, onDescriptionUpdate, fs2, log, db2, paren
46231
46485
  } else if (ev.kind === "sub_agent_text") {
46232
46486
  entry.lastSummaryLine = ev.text.split(`
46233
46487
  `)[0].trim().slice(0, 120);
46488
+ entry.lastResultText = ev.text.trim().slice(0, SUBAGENT_RESULT_TEXT_MAX);
46234
46489
  } else if (ev.kind === "sub_agent_turn_end") {
46235
46490
  if (entry.state === "running") {
46236
46491
  entry.state = "done";
@@ -46322,6 +46577,7 @@ function startSubagentWatcher(config) {
46322
46577
  completionNotified: false,
46323
46578
  stallTerminalSynthesised: false,
46324
46579
  lastSummaryLine: "",
46580
+ lastResultText: "",
46325
46581
  lastTool: null,
46326
46582
  historical: isHistorical
46327
46583
  };
@@ -46372,8 +46628,8 @@ function startSubagentWatcher(config) {
46372
46628
  return;
46373
46629
  if (entry.state === "done" && !entry.completionNotified) {
46374
46630
  entry.completionNotified = true;
46375
- const desc = escapeHtml8(truncate2(entry.description, 80));
46376
- const summary = entry.lastSummaryLine ? ` \u2014 ${escapeHtml8(truncate2(entry.lastSummaryLine, 120))}` : "";
46631
+ const desc = escapeHtml8(truncate3(entry.description, 80));
46632
+ const summary = entry.lastSummaryLine ? ` \u2014 ${escapeHtml8(truncate3(entry.lastSummaryLine, 120))}` : "";
46377
46633
  const tools = entry.toolCount > 0 ? ` (${entry.toolCount} tools)` : "";
46378
46634
  try {
46379
46635
  config.sendNotification(`\u2713 Worker done: ${desc}${tools}${summary}`);
@@ -46387,7 +46643,9 @@ function startSubagentWatcher(config) {
46387
46643
  state: entry.state,
46388
46644
  outcome: entry.historical ? "orphan" : "completed",
46389
46645
  toolCount: entry.toolCount,
46390
- durationMs: nowFn() - entry.dispatchedAt
46646
+ durationMs: nowFn() - entry.dispatchedAt,
46647
+ description: entry.description,
46648
+ resultText: entry.lastResultText
46391
46649
  });
46392
46650
  } catch (cbErr) {
46393
46651
  log?.(`subagent-watcher: onFinish callback error ${agentId}: ${cbErr.message}`);
@@ -46404,7 +46662,9 @@ function startSubagentWatcher(config) {
46404
46662
  state: entry.state,
46405
46663
  outcome: "failed",
46406
46664
  toolCount: entry.toolCount,
46407
- durationMs: nowFn() - entry.dispatchedAt
46665
+ durationMs: nowFn() - entry.dispatchedAt,
46666
+ description: entry.description,
46667
+ resultText: entry.lastResultText
46408
46668
  });
46409
46669
  } catch (cbErr) {
46410
46670
  log?.(`subagent-watcher: onFinish callback error ${agentId}: ${cbErr.message}`);
@@ -46457,7 +46717,7 @@ function startSubagentWatcher(config) {
46457
46717
  if (idleMs >= threshold) {
46458
46718
  entry.stallNotified = true;
46459
46719
  entry.stalledAt = n;
46460
- const desc = escapeHtml8(truncate2(entry.description, 80));
46720
+ const desc = escapeHtml8(truncate3(entry.description, 80));
46461
46721
  const idleSec = Math.floor(idleMs / 1000);
46462
46722
  log?.(`subagent-watcher: stall detected for ${entry.agentId} (idle ${idleSec}s): ${desc}`);
46463
46723
  if (db2 != null) {
@@ -47419,7 +47679,7 @@ function summarizeToolForTitle(toolName, inputPreview) {
47419
47679
  }
47420
47680
  case "Bash": {
47421
47681
  const command = readString(input, "command");
47422
- return command ? `${toolName}: ${truncate3(command, COMMAND_TITLE_MAX)}` : toolName;
47682
+ return command ? `${toolName}: ${truncate4(command, COMMAND_TITLE_MAX)}` : toolName;
47423
47683
  }
47424
47684
  case "Read":
47425
47685
  case "Edit":
@@ -47427,17 +47687,17 @@ function summarizeToolForTitle(toolName, inputPreview) {
47427
47687
  case "MultiEdit":
47428
47688
  case "NotebookEdit": {
47429
47689
  const filePath = readString(input, "file_path") ?? readString(input, "notebook_path");
47430
- return filePath ? `${toolName}: ${truncate3(basename5(filePath), PATH_TITLE_MAX)}` : toolName;
47690
+ return filePath ? `${toolName}: ${truncate4(basename5(filePath), PATH_TITLE_MAX)}` : toolName;
47431
47691
  }
47432
47692
  case "Glob":
47433
47693
  case "Grep": {
47434
47694
  const pattern = readString(input, "pattern");
47435
- return pattern ? `${toolName}: ${truncate3(pattern, COMMAND_TITLE_MAX)}` : toolName;
47695
+ return pattern ? `${toolName}: ${truncate4(pattern, COMMAND_TITLE_MAX)}` : toolName;
47436
47696
  }
47437
47697
  case "WebFetch":
47438
47698
  case "WebSearch": {
47439
47699
  const query2 = readString(input, "url") ?? readString(input, "query");
47440
- return query2 ? `${toolName}: ${truncate3(query2, COMMAND_TITLE_MAX)}` : toolName;
47700
+ return query2 ? `${toolName}: ${truncate4(query2, COMMAND_TITLE_MAX)}` : toolName;
47441
47701
  }
47442
47702
  default:
47443
47703
  return toolName;
@@ -47470,7 +47730,7 @@ function skillBasenameFromPath(input) {
47470
47730
  const basename6 = lastSlash >= 0 ? trimmed.slice(lastSlash + 1) : trimmed;
47471
47731
  return basename6.length > 0 ? basename6 : null;
47472
47732
  }
47473
- function truncate3(text, max) {
47733
+ function truncate4(text, max) {
47474
47734
  const collapsed = text.replace(/\s+/g, " ").trim();
47475
47735
  if (collapsed.length <= max)
47476
47736
  return collapsed;
@@ -47741,11 +48001,11 @@ function sweepStaleTurnActiveMarker(stateDir, opts) {
47741
48001
  }
47742
48002
 
47743
48003
  // ../src/build-info.ts
47744
- var VERSION = "0.13.8";
47745
- var COMMIT_SHA = "bb713414";
47746
- var COMMIT_DATE = "2026-05-22T10:15:33+10:00";
48004
+ var VERSION = "0.13.10";
48005
+ var COMMIT_SHA = "e0fd6617";
48006
+ var COMMIT_DATE = "2026-05-22T12:01:29+10:00";
47747
48007
  var LATEST_PR = null;
47748
- var COMMITS_AHEAD_OF_TAG = 4;
48008
+ var COMMITS_AHEAD_OF_TAG = 6;
47749
48009
 
47750
48010
  // gateway/boot-version.ts
47751
48011
  function formatRelativeAgo(iso) {
@@ -48562,23 +48822,23 @@ try {
48562
48822
  }
48563
48823
  const pendingEnvPath = join32(agentDir, ".pending-turn.env");
48564
48824
  try {
48565
- const pending = findMostRecentInterruptedTurn(turnsDb);
48566
- if (pending != null) {
48825
+ const pending2 = findMostRecentInterruptedTurn(turnsDb);
48826
+ if (pending2 != null) {
48567
48827
  const lines = [
48568
48828
  `SWITCHROOM_PENDING_TURN=true`,
48569
- `SWITCHROOM_PENDING_TURN_KEY=${pending.turn_key}`,
48570
- `SWITCHROOM_PENDING_CHAT_ID=${pending.chat_id}`,
48571
- pending.thread_id != null ? `SWITCHROOM_PENDING_THREAD_ID=${pending.thread_id}` : `SWITCHROOM_PENDING_THREAD_ID=`,
48572
- pending.last_user_msg_id != null ? `SWITCHROOM_PENDING_USER_MSG_ID=${pending.last_user_msg_id}` : `SWITCHROOM_PENDING_USER_MSG_ID=`,
48573
- `SWITCHROOM_PENDING_ENDED_VIA=${pending.ended_via ?? "unknown"}`,
48574
- `SWITCHROOM_PENDING_STARTED_AT=${pending.started_at}`
48829
+ `SWITCHROOM_PENDING_TURN_KEY=${pending2.turn_key}`,
48830
+ `SWITCHROOM_PENDING_CHAT_ID=${pending2.chat_id}`,
48831
+ pending2.thread_id != null ? `SWITCHROOM_PENDING_THREAD_ID=${pending2.thread_id}` : `SWITCHROOM_PENDING_THREAD_ID=`,
48832
+ pending2.last_user_msg_id != null ? `SWITCHROOM_PENDING_USER_MSG_ID=${pending2.last_user_msg_id}` : `SWITCHROOM_PENDING_USER_MSG_ID=`,
48833
+ `SWITCHROOM_PENDING_ENDED_VIA=${pending2.ended_via ?? "unknown"}`,
48834
+ `SWITCHROOM_PENDING_STARTED_AT=${pending2.started_at}`
48575
48835
  ];
48576
48836
  const pendingEnvTmp = `${pendingEnvPath}.tmp-${process.pid}`;
48577
48837
  writeFileSync21(pendingEnvTmp, lines.join(`
48578
48838
  `) + `
48579
48839
  `, { mode: 384 });
48580
48840
  renameSync12(pendingEnvTmp, pendingEnvPath);
48581
- process.stderr.write(`telegram gateway: pending-turn env written to ${pendingEnvPath} turnKey=${pending.turn_key} endedVia=${pending.ended_via ?? "open"}
48841
+ process.stderr.write(`telegram gateway: pending-turn env written to ${pendingEnvPath} turnKey=${pending2.turn_key} endedVia=${pending2.ended_via ?? "open"}
48582
48842
  `);
48583
48843
  } else if (existsSync34(pendingEnvPath)) {
48584
48844
  rmSync4(pendingEnvPath, { force: true });
@@ -49658,8 +49918,8 @@ var ipcServer = createIpcServer({
49658
49918
  client: client3
49659
49919
  });
49660
49920
  } else {
49661
- const pending = pendingInboundBuffer.drain(client3.agentName);
49662
- for (const msg of pending) {
49921
+ const pending2 = pendingInboundBuffer.drain(client3.agentName);
49922
+ for (const msg of pending2) {
49663
49923
  try {
49664
49924
  client3.send(msg);
49665
49925
  inboundSpool?.ack(msg);
@@ -49762,9 +50022,9 @@ var ipcServer = createIpcServer({
49762
50022
  }
49763
50023
  },
49764
50024
  onClientDisconnected(client3) {
49765
- process.stderr.write(`telegram gateway: bridge disconnected \u2014 agent=${client3.agentName}
49766
- `);
49767
50025
  if (client3.agentName != null) {
50026
+ process.stderr.write(`telegram gateway: bridge disconnected \u2014 agent=${client3.agentName}
50027
+ `);
49768
50028
  shadowEmit({ kind: "bridgeDown", at: Date.now() });
49769
50029
  }
49770
50030
  flushOnAgentDisconnect({
@@ -49954,6 +50214,68 @@ ${reminder}
49954
50214
  },
49955
50215
  buildCard: ({ preview, suggestRequestId }) => buildDiffPreviewCard({ preview, suggestRequestId }),
49956
50216
  log: (m) => process.stderr.write(`telegram gateway: drive-approval \u2014 ${m}
50217
+ `)
50218
+ });
50219
+ },
50220
+ async onRequestConfigApproval(client3, msg) {
50221
+ const { handleRequestConfigApproval: handleRequestConfigApproval2 } = await Promise.resolve().then(() => (init_config_approval_handler(), exports_config_approval_handler));
50222
+ const { InlineKeyboard: InlineKeyboard6 } = await Promise.resolve().then(() => __toESM(require_mod(), 1));
50223
+ await handleRequestConfigApproval2(client3, msg, {
50224
+ agentName: getMyAgentName(),
50225
+ loadTargetChat: () => {
50226
+ const access = loadAccess();
50227
+ const operator = access.allowFrom[0];
50228
+ if (operator === undefined)
50229
+ return null;
50230
+ return { chatId: operator };
50231
+ },
50232
+ buildKeyboard: (requestId) => new InlineKeyboard6().text("\u2705 Approve", `cfg:${requestId}:approve`).text("\uD83D\uDEAB Deny", `cfg:${requestId}:deny`),
50233
+ postCard: async (args) => {
50234
+ try {
50235
+ const sent = await robustApiCall(() => bot.api.sendMessage(args.chatId, args.text, {
50236
+ parse_mode: "HTML",
50237
+ ...args.threadId !== undefined ? { message_thread_id: args.threadId } : {},
50238
+ reply_markup: args.replyMarkup
50239
+ }), {
50240
+ chat_id: String(args.chatId),
50241
+ verb: "config-approval-card",
50242
+ ...args.threadId !== undefined ? { threadId: args.threadId } : {}
50243
+ });
50244
+ return { messageId: sent.message_id };
50245
+ } catch (err) {
50246
+ process.stderr.write(`telegram gateway: config-approval postCard failed: ${err.message}
50247
+ `);
50248
+ return null;
50249
+ }
50250
+ },
50251
+ editCard: async (args) => {
50252
+ try {
50253
+ await robustApiCall(() => bot.api.editMessageText(args.chatId, args.messageId, args.text, {
50254
+ parse_mode: "HTML"
50255
+ }), { chat_id: String(args.chatId), verb: "config-approval-edit" });
50256
+ } catch (err) {
50257
+ process.stderr.write(`telegram gateway: config-approval editCard failed: ${err.message}
50258
+ `);
50259
+ }
50260
+ },
50261
+ log: (m) => process.stderr.write(`telegram gateway: config-approval \u2014 ${m}
50262
+ `)
50263
+ });
50264
+ },
50265
+ async onRequestConfigFinalize(client3, msg) {
50266
+ const { handleRequestConfigFinalize: handleRequestConfigFinalize2 } = await Promise.resolve().then(() => (init_config_approval_handler(), exports_config_approval_handler));
50267
+ await handleRequestConfigFinalize2(client3, msg, {
50268
+ editCard: async (args) => {
50269
+ try {
50270
+ await robustApiCall(() => bot.api.editMessageText(args.chatId, args.messageId, args.text, {
50271
+ parse_mode: "HTML"
50272
+ }), { chat_id: String(args.chatId), verb: "config-approval-finalize" });
50273
+ } catch (err) {
50274
+ process.stderr.write(`telegram gateway: config-finalize editCard failed: ${err.message}
50275
+ `);
50276
+ }
50277
+ },
50278
+ log: (m) => process.stderr.write(`telegram gateway: config-finalize \u2014 ${m}
49957
50279
  `)
49958
50280
  });
49959
50281
  },
@@ -50847,7 +51169,7 @@ async function executeVaultRequestSave(args) {
50847
51169
  }
50848
51170
  const agentSlug = process.env.SWITCHROOM_AGENT_NAME || "agent";
50849
51171
  const stageId = randomBytes6(4).toString("hex");
50850
- const pending = {
51172
+ const pending2 = {
50851
51173
  agent: agentSlug,
50852
51174
  chat_id,
50853
51175
  key,
@@ -50856,16 +51178,16 @@ async function executeVaultRequestSave(args) {
50856
51178
  why,
50857
51179
  staged_at: Date.now()
50858
51180
  };
50859
- pendingVaultRequestSaves.set(stageId, pending);
51181
+ pendingVaultRequestSaves.set(stageId, pending2);
50860
51182
  sweepPendingVaultRequestSaves();
50861
- const text = renderVaultRequestSaveCard(pending, agentSlug);
51183
+ const text = renderVaultRequestSaveCard(pending2, agentSlug);
50862
51184
  const threadId = args.message_thread_id != null ? Number(args.message_thread_id) : undefined;
50863
51185
  const sent = await retryWithThreadFallback(robustApiCall, (tid) => lockedBot.api.sendMessage(chat_id, text, {
50864
51186
  parse_mode: "HTML",
50865
51187
  reply_markup: buildVaultRequestSaveKeyboard(stageId),
50866
51188
  ...tid != null && Number.isFinite(tid) ? { message_thread_id: tid } : {}
50867
51189
  }), { threadId, chat_id, verb: "vault_request_save.card" });
50868
- pending.card_message_id = sent.message_id;
51190
+ pending2.card_message_id = sent.message_id;
50869
51191
  return {
50870
51192
  content: [
50871
51193
  {
@@ -50950,7 +51272,7 @@ async function executeVaultRequestAccess(args) {
50950
51272
  } catch {}
50951
51273
  }
50952
51274
  const stageId = randomBytes6(4).toString("hex");
50953
- const pending = {
51275
+ const pending2 = {
50954
51276
  agent: agentSlug,
50955
51277
  chat_id,
50956
51278
  key,
@@ -50959,16 +51281,16 @@ async function executeVaultRequestAccess(args) {
50959
51281
  ttl_seconds,
50960
51282
  staged_at: Date.now()
50961
51283
  };
50962
- pendingVaultRequestAccesses.set(stageId, pending);
51284
+ pendingVaultRequestAccesses.set(stageId, pending2);
50963
51285
  sweepPendingVaultRequestAccesses();
50964
- const text = renderVaultRequestAccessCard(pending);
51286
+ const text = renderVaultRequestAccessCard(pending2);
50965
51287
  const threadId = args.message_thread_id != null ? Number(args.message_thread_id) : undefined;
50966
51288
  const sent = await retryWithThreadFallback(robustApiCall, (tid) => lockedBot.api.sendMessage(chat_id, text, {
50967
51289
  parse_mode: "HTML",
50968
51290
  reply_markup: buildVaultRequestAccessKeyboard(stageId),
50969
51291
  ...tid != null && Number.isFinite(tid) ? { message_thread_id: tid } : {}
50970
51292
  }), { threadId, chat_id, verb: "vault_request_access.card" });
50971
- pending.card_message_id = sent.message_id;
51293
+ pending2.card_message_id = sent.message_id;
50972
51294
  return {
50973
51295
  content: [
50974
51296
  {
@@ -51282,9 +51604,9 @@ function handleSessionEvent(ev) {
51282
51604
  });
51283
51605
  }
51284
51606
  if (pendingPtyPartial != null) {
51285
- const pending = pendingPtyPartial;
51607
+ const pending2 = pendingPtyPartial;
51286
51608
  pendingPtyPartial = null;
51287
- handlePtyPartial(pending);
51609
+ handlePtyPartial(pending2);
51288
51610
  }
51289
51611
  }
51290
51612
  return;
@@ -54096,15 +54418,15 @@ async function handleVaultRecentDenialCallback(ctx, data) {
54096
54418
  parseMode: "HTML"
54097
54419
  });
54098
54420
  }
54099
- async function performVaultAccessApproval(ctx, pending, stageId, senderId, attestation) {
54421
+ async function performVaultAccessApproval(ctx, pending2, stageId, senderId, attestation) {
54100
54422
  const brokerAuthOpts = attestation.kind === "passphrase" ? { passphrase: attestation.passphrase } : { attest_via_posture: true };
54101
- if (pending.scope === "read") {
54423
+ if (pending2.scope === "read") {
54102
54424
  try {
54103
54425
  const visible = await listViaBroker();
54104
- if (visible !== null && visible.includes(pending.key)) {
54426
+ if (visible !== null && visible.includes(pending2.key)) {
54105
54427
  pendingVaultRequestAccesses.delete(stageId);
54106
- if (pending.card_message_id != null) {
54107
- await ctx.api.editMessageText(pending.chat_id, pending.card_message_id, `\u2139\uFE0F <b>${escapeHtmlForTg(pending.agent)}</b> already has standing-ACL access to <code>${escapeHtmlForTg(pending.key)}</code> (schedule.secrets[]). ` + `<b>No grant minted</b> \u2014 a token would shadow the standing ACL. ` + `The agent can read it directly.`, { parse_mode: "HTML", reply_markup: { inline_keyboard: [] } }).catch(() => {});
54428
+ if (pending2.card_message_id != null) {
54429
+ await ctx.api.editMessageText(pending2.chat_id, pending2.card_message_id, `\u2139\uFE0F <b>${escapeHtmlForTg(pending2.agent)}</b> already has standing-ACL access to <code>${escapeHtmlForTg(pending2.key)}</code> (schedule.secrets[]). ` + `<b>No grant minted</b> \u2014 a token would shadow the standing ACL. ` + `The agent can read it directly.`, { parse_mode: "HTML", reply_markup: { inline_keyboard: [] } }).catch(() => {});
54108
54430
  }
54109
54431
  return;
54110
54432
  }
@@ -54112,8 +54434,8 @@ async function performVaultAccessApproval(ctx, pending, stageId, senderId, attes
54112
54434
  }
54113
54435
  let existingReadKeys = [];
54114
54436
  let existingWriteKeys = [];
54115
- if (pending.scope === "read" || pending.scope === "write") {
54116
- const list2 = await listGrantsViaBroker(pending.agent, brokerAuthOpts);
54437
+ if (pending2.scope === "read" || pending2.scope === "write") {
54438
+ const list2 = await listGrantsViaBroker(pending2.agent, brokerAuthOpts);
54117
54439
  if (list2.kind === "ok") {
54118
54440
  const now = Math.floor(Date.now() / 1000);
54119
54441
  const active = list2.grants.filter((g) => g.expires_at === null || g.expires_at > now).sort((a, b) => {
@@ -54130,15 +54452,15 @@ async function performVaultAccessApproval(ctx, pending, stageId, senderId, attes
54130
54452
  }
54131
54453
  const readKeys = new Set(existingReadKeys);
54132
54454
  const writeKeys = new Set(existingWriteKeys);
54133
- if (pending.scope === "read")
54134
- readKeys.add(pending.key);
54135
- if (pending.scope === "write")
54136
- writeKeys.add(pending.key);
54455
+ if (pending2.scope === "read")
54456
+ readKeys.add(pending2.key);
54457
+ if (pending2.scope === "write")
54458
+ writeKeys.add(pending2.key);
54137
54459
  const mintArgs = {
54138
- agent: pending.agent,
54460
+ agent: pending2.agent,
54139
54461
  keys: Array.from(readKeys),
54140
- ttl_seconds: pending.ttl_seconds,
54141
- description: `auto-mint via vault_request_access (#1012, scope=${pending.scope}, by op ${senderId}` + (existingReadKeys.length + existingWriteKeys.length > 0 ? `, unioned with prior grant` : ``) + `)`,
54462
+ ttl_seconds: pending2.ttl_seconds,
54463
+ description: `auto-mint via vault_request_access (#1012, scope=${pending2.scope}, by op ${senderId}` + (existingReadKeys.length + existingWriteKeys.length > 0 ? `, unioned with prior grant` : ``) + `)`,
54142
54464
  ...writeKeys.size > 0 ? { write_keys: Array.from(writeKeys) } : {},
54143
54465
  ...brokerAuthOpts
54144
54466
  };
@@ -54149,45 +54471,45 @@ async function performVaultAccessApproval(ctx, pending, stageId, senderId, attes
54149
54471
  }
54150
54472
  if (result.kind === "error") {
54151
54473
  pendingVaultRequestAccesses.delete(stageId);
54152
- if (pending.card_message_id != null) {
54153
- await ctx.api.editMessageText(pending.chat_id, pending.card_message_id, `<b>mint_grant failed:</b> ${escapeHtmlForTg(result.msg)}`, { parse_mode: "HTML", reply_markup: { inline_keyboard: [] } }).catch(() => {});
54474
+ if (pending2.card_message_id != null) {
54475
+ await ctx.api.editMessageText(pending2.chat_id, pending2.card_message_id, `<b>mint_grant failed:</b> ${escapeHtmlForTg(result.msg)}`, { parse_mode: "HTML", reply_markup: { inline_keyboard: [] } }).catch(() => {});
54154
54476
  }
54155
54477
  return;
54156
54478
  }
54157
54479
  const { token, id } = result;
54158
- const tokenPath = join32(homedir12(), ".switchroom", "agents", pending.agent, ".vault-token");
54480
+ const tokenPath = join32(homedir12(), ".switchroom", "agents", pending2.agent, ".vault-token");
54159
54481
  try {
54160
- mkdirSync21(join32(homedir12(), ".switchroom", "agents", pending.agent), { recursive: true });
54482
+ mkdirSync21(join32(homedir12(), ".switchroom", "agents", pending2.agent), { recursive: true });
54161
54483
  writeFileSync21(tokenPath, token, { mode: 384 });
54162
54484
  } catch (err) {
54163
54485
  await switchroomReply(ctx, `<b>Grant created (${escapeHtmlForTg(id)}) but token write failed:</b> ${escapeHtmlForTg(String(err))}
54164
- <i>Recover with: <code>switchroom vault grant ${escapeHtmlForTg(pending.agent)} --keys ${escapeHtmlForTg(pending.key)} --duration ${Math.round(pending.ttl_seconds / 86400)}d</code> on the host.</i>`, { html: true });
54486
+ <i>Recover with: <code>switchroom vault grant ${escapeHtmlForTg(pending2.agent)} --keys ${escapeHtmlForTg(pending2.key)} --duration ${Math.round(pending2.ttl_seconds / 86400)}d</code> on the host.</i>`, { html: true });
54165
54487
  return;
54166
54488
  }
54167
54489
  pendingVaultRequestAccesses.delete(stageId);
54168
- if (pending.card_message_id != null) {
54169
- const days = Math.round(pending.ttl_seconds / 86400);
54490
+ if (pending2.card_message_id != null) {
54491
+ const days = Math.round(pending2.ttl_seconds / 86400);
54170
54492
  const footer = VAULT_APPROVAL_AUTH_MODE === "telegram-id" ? `
54171
54493
  <i>Approver verified by Telegram identity \u2014 broker auto-unlocked at startup.</i>` : "";
54172
- await ctx.api.editMessageText(pending.chat_id, pending.card_message_id, `\u2705 Granted <b>${escapeHtmlForTg(pending.agent)}</b> ${pending.scope} access to <code>${escapeHtmlForTg(pending.key)}</code> for ${days}d. (grant <code>${escapeHtmlForTg(id)}</code>)` + footer, { parse_mode: "HTML", reply_markup: { inline_keyboard: [] } }).catch(() => {});
54494
+ await ctx.api.editMessageText(pending2.chat_id, pending2.card_message_id, `\u2705 Granted <b>${escapeHtmlForTg(pending2.agent)}</b> ${pending2.scope} access to <code>${escapeHtmlForTg(pending2.key)}</code> for ${days}d. (grant <code>${escapeHtmlForTg(id)}</code>)` + footer, { parse_mode: "HTML", reply_markup: { inline_keyboard: [] } }).catch(() => {});
54173
54495
  }
54174
54496
  const synthetic = buildVaultGrantApprovedInbound({
54175
54497
  ctx: {
54176
- agent: pending.agent,
54177
- key: pending.key,
54178
- scope: pending.scope,
54179
- chat_id: pending.chat_id,
54180
- ttl_seconds: pending.ttl_seconds
54498
+ agent: pending2.agent,
54499
+ key: pending2.key,
54500
+ scope: pending2.scope,
54501
+ chat_id: pending2.chat_id,
54502
+ ttl_seconds: pending2.ttl_seconds
54181
54503
  },
54182
54504
  grantId: id,
54183
54505
  stageId,
54184
54506
  operatorId: senderId
54185
54507
  });
54186
- const delivered = ipcServer.sendToAgent(pending.agent, synthetic);
54187
- process.stderr.write(`telegram gateway: vault_grant_approved injection agent=${pending.agent} key=${pending.key} stage=${stageId} delivered=${delivered}
54508
+ const delivered = ipcServer.sendToAgent(pending2.agent, synthetic);
54509
+ process.stderr.write(`telegram gateway: vault_grant_approved injection agent=${pending2.agent} key=${pending2.key} stage=${stageId} delivered=${delivered}
54188
54510
  `);
54189
54511
  if (!delivered) {
54190
- pendingInboundBuffer.push(pending.agent, synthetic);
54512
+ pendingInboundBuffer.push(pending2.agent, synthetic);
54191
54513
  }
54192
54514
  }
54193
54515
  async function handleVaultRequestAccessCallback(ctx, data) {
@@ -54204,8 +54526,8 @@ async function handleVaultRequestAccessCallback(ctx, data) {
54204
54526
  }
54205
54527
  const action = parts[1];
54206
54528
  const stageId = parts.slice(2).join(":");
54207
- const pending = pendingVaultRequestAccesses.get(stageId);
54208
- if (!pending) {
54529
+ const pending2 = pendingVaultRequestAccesses.get(stageId);
54530
+ if (!pending2) {
54209
54531
  await ctx.answerCallbackQuery({ text: "Card expired \u2014 ask the agent to re-request." }).catch(() => {});
54210
54532
  if (ctx.callbackQuery?.message) {
54211
54533
  await ctx.api.editMessageText(ctx.callbackQuery.message.chat.id, ctx.callbackQuery.message.message_id, "\u231B <i>This access-request card expired before you tapped. Ask the agent to re-issue if the need still stands.</i>", { parse_mode: "HTML", reply_markup: { inline_keyboard: [] } }).catch(() => {});
@@ -54215,66 +54537,66 @@ async function handleVaultRequestAccessCallback(ctx, data) {
54215
54537
  if (action === "deny") {
54216
54538
  pendingVaultRequestAccesses.delete(stageId);
54217
54539
  await ctx.answerCallbackQuery({ text: "\uD83D\uDEAB Denied" }).catch(() => {});
54218
- if (pending.card_message_id != null) {
54219
- await ctx.api.editMessageText(pending.chat_id, pending.card_message_id, `\uD83D\uDEAB <i>Denied. <b>${escapeHtmlForTg(pending.agent)}</b> will not get access to <code>${escapeHtmlForTg(pending.key)}</code>.</i>`, { parse_mode: "HTML", reply_markup: { inline_keyboard: [] } }).catch(() => {});
54540
+ if (pending2.card_message_id != null) {
54541
+ await ctx.api.editMessageText(pending2.chat_id, pending2.card_message_id, `\uD83D\uDEAB <i>Denied. <b>${escapeHtmlForTg(pending2.agent)}</b> will not get access to <code>${escapeHtmlForTg(pending2.key)}</code>.</i>`, { parse_mode: "HTML", reply_markup: { inline_keyboard: [] } }).catch(() => {});
54220
54542
  }
54221
54543
  const denyInbound = buildVaultGrantDeniedInbound({
54222
54544
  ctx: {
54223
- agent: pending.agent,
54224
- key: pending.key,
54225
- scope: pending.scope,
54226
- chat_id: pending.chat_id,
54227
- ttl_seconds: pending.ttl_seconds
54545
+ agent: pending2.agent,
54546
+ key: pending2.key,
54547
+ scope: pending2.scope,
54548
+ chat_id: pending2.chat_id,
54549
+ ttl_seconds: pending2.ttl_seconds
54228
54550
  },
54229
54551
  stageId,
54230
54552
  operatorId: senderId
54231
54553
  });
54232
- const denyDelivered = ipcServer.sendToAgent(pending.agent, denyInbound);
54233
- process.stderr.write(`telegram gateway: vault_grant_denied injection agent=${pending.agent} key=${pending.key} stage=${stageId} delivered=${denyDelivered}
54554
+ const denyDelivered = ipcServer.sendToAgent(pending2.agent, denyInbound);
54555
+ process.stderr.write(`telegram gateway: vault_grant_denied injection agent=${pending2.agent} key=${pending2.key} stage=${stageId} delivered=${denyDelivered}
54234
54556
  `);
54235
54557
  if (!denyDelivered) {
54236
- pendingInboundBuffer.push(pending.agent, denyInbound);
54558
+ pendingInboundBuffer.push(pending2.agent, denyInbound);
54237
54559
  }
54238
54560
  return;
54239
54561
  }
54240
54562
  if (action === "approve") {
54241
54563
  if (VAULT_APPROVAL_AUTH_MODE === "telegram-id") {
54242
54564
  const username = ctx.from?.username ?? ctx.from?.first_name ?? `id=${senderId}`;
54243
- if (pending.card_message_id != null) {
54244
- await ctx.api.editMessageText(pending.chat_id, pending.card_message_id, `\u2705 Approved by @${escapeHtmlForTg(username)} \u2014 minting\u2026`, { parse_mode: "HTML", reply_markup: { inline_keyboard: [] } }).catch(() => {});
54565
+ if (pending2.card_message_id != null) {
54566
+ await ctx.api.editMessageText(pending2.chat_id, pending2.card_message_id, `\u2705 Approved by @${escapeHtmlForTg(username)} \u2014 minting\u2026`, { parse_mode: "HTML", reply_markup: { inline_keyboard: [] } }).catch(() => {});
54245
54567
  }
54246
54568
  await ctx.answerCallbackQuery({ text: "\u23F3 Minting grant\u2026" }).catch(() => {});
54247
- await performVaultAccessApproval(ctx, pending, stageId, senderId, { kind: "posture" });
54569
+ await performVaultAccessApproval(ctx, pending2, stageId, senderId, { kind: "posture" });
54248
54570
  return;
54249
54571
  }
54250
- const cached = vaultPassphraseCache.get(pending.chat_id);
54572
+ const cached = vaultPassphraseCache.get(pending2.chat_id);
54251
54573
  if (!cached || cached.expiresAt <= Date.now()) {
54252
- if (pending.card_message_id == null) {
54574
+ if (pending2.card_message_id == null) {
54253
54575
  await ctx.answerCallbackQuery({ text: "Card missing \u2014 ask the agent to re-issue." }).catch(() => {});
54254
54576
  return;
54255
54577
  }
54256
- const existing = pendingVaultOps.get(pending.chat_id);
54578
+ const existing = pendingVaultOps.get(pending2.chat_id);
54257
54579
  const newItem = {
54258
54580
  stageId,
54259
- cardChatId: pending.chat_id,
54260
- cardMessageId: pending.card_message_id,
54581
+ cardChatId: pending2.chat_id,
54582
+ cardMessageId: pending2.card_message_id,
54261
54583
  senderId
54262
54584
  };
54263
54585
  const items = existing?.kind === "passphrase-for-access-approve" ? [...existing.items.filter((it) => it.stageId !== stageId), newItem] : [newItem];
54264
- pendingVaultOps.set(pending.chat_id, {
54586
+ pendingVaultOps.set(pending2.chat_id, {
54265
54587
  kind: "passphrase-for-access-approve",
54266
54588
  items,
54267
54589
  startedAt: existing?.kind === "passphrase-for-access-approve" ? existing.startedAt : Date.now()
54268
54590
  });
54269
54591
  const joiningBatch = items.length > 1;
54270
54592
  await ctx.answerCallbackQuery({ text: joiningBatch ? `\uD83D\uDD10 Queued \u2014 one passphrase covers ${items.length} cards` : "\uD83D\uDD10 Send your passphrase\u2026" }).catch(() => {});
54271
- await ctx.api.editMessageText(pending.chat_id, pending.card_message_id, joiningBatch ? `\uD83D\uDD10 <b>Queued behind an earlier card.</b> Type your passphrase as your next message \u2014 it covers <b>${items.length}</b> pending approvals in this chat (one entry mints all grants, no re-type per card).` : `\uD83D\uDD10 <b>Vault is locked.</b> Reply with your passphrase as your next message \u2014 we'll unlock, mint the grant for <b>${escapeHtmlForTg(pending.agent)}</b>, and delete the passphrase message in one step.
54593
+ await ctx.api.editMessageText(pending2.chat_id, pending2.card_message_id, joiningBatch ? `\uD83D\uDD10 <b>Queued behind an earlier card.</b> Type your passphrase as your next message \u2014 it covers <b>${items.length}</b> pending approvals in this chat (one entry mints all grants, no re-type per card).` : `\uD83D\uDD10 <b>Vault is locked.</b> Reply with your passphrase as your next message \u2014 we'll unlock, mint the grant for <b>${escapeHtmlForTg(pending2.agent)}</b>, and delete the passphrase message in one step.
54272
54594
 
54273
54595
  <i>Mint authority stays operator-only: the broker only accepts the grant when the passphrase matches.</i>`, { parse_mode: "HTML", reply_markup: { inline_keyboard: [] } }).catch(() => {});
54274
54596
  return;
54275
54597
  }
54276
54598
  await ctx.answerCallbackQuery({ text: "\u23F3 Minting grant\u2026" }).catch(() => {});
54277
- await performVaultAccessApproval(ctx, pending, stageId, senderId, { kind: "passphrase", passphrase: cached.passphrase });
54599
+ await performVaultAccessApproval(ctx, pending2, stageId, senderId, { kind: "passphrase", passphrase: cached.passphrase });
54278
54600
  return;
54279
54601
  }
54280
54602
  await ctx.answerCallbackQuery({ text: "Unknown action" }).catch(() => {});
@@ -54293,8 +54615,8 @@ async function handleVaultRequestSaveCallback(ctx, data) {
54293
54615
  }
54294
54616
  const action = parts[1];
54295
54617
  const stageId = parts.slice(2).join(":");
54296
- const pending = pendingVaultRequestSaves.get(stageId);
54297
- if (!pending) {
54618
+ const pending2 = pendingVaultRequestSaves.get(stageId);
54619
+ if (!pending2) {
54298
54620
  await ctx.answerCallbackQuery({ text: "Card expired \u2014 ask the agent to re-send." }).catch(() => {});
54299
54621
  if (ctx.callbackQuery?.message) {
54300
54622
  await ctx.api.editMessageText(ctx.callbackQuery.message.chat.id, ctx.callbackQuery.message.message_id, "\u231B <i>This vault-save card expired before you tapped. Ask the agent to re-issue if you still want to save.</i>", { parse_mode: "HTML", reply_markup: { inline_keyboard: [] } }).catch(() => {});
@@ -54304,23 +54626,23 @@ async function handleVaultRequestSaveCallback(ctx, data) {
54304
54626
  if (action === "discard") {
54305
54627
  pendingVaultRequestSaves.delete(stageId);
54306
54628
  await ctx.answerCallbackQuery({ text: "\uD83D\uDEAB Discarded" }).catch(() => {});
54307
- if (pending.card_message_id != null) {
54308
- await ctx.api.editMessageText(pending.chat_id, pending.card_message_id, `\uD83D\uDEAB <i>Discarded. The secret was not written to the vault.</i>`, { parse_mode: "HTML", reply_markup: { inline_keyboard: [] } }).catch(() => {});
54629
+ if (pending2.card_message_id != null) {
54630
+ await ctx.api.editMessageText(pending2.chat_id, pending2.card_message_id, `\uD83D\uDEAB <i>Discarded. The secret was not written to the vault.</i>`, { parse_mode: "HTML", reply_markup: { inline_keyboard: [] } }).catch(() => {});
54309
54631
  }
54310
54632
  const discardInbound = buildVaultSaveDiscardedInbound({
54311
- ctx: { agent: pending.agent, key: pending.key, chat_id: pending.chat_id },
54633
+ ctx: { agent: pending2.agent, key: pending2.key, chat_id: pending2.chat_id },
54312
54634
  stageId,
54313
54635
  operatorId: senderId
54314
54636
  });
54315
- const dDelivered = ipcServer.sendToAgent(pending.agent, discardInbound);
54316
- process.stderr.write(`telegram gateway: vault_save_discarded injection agent=${pending.agent} key=${pending.key} stage=${stageId} delivered=${dDelivered}
54637
+ const dDelivered = ipcServer.sendToAgent(pending2.agent, discardInbound);
54638
+ process.stderr.write(`telegram gateway: vault_save_discarded injection agent=${pending2.agent} key=${pending2.key} stage=${stageId} delivered=${dDelivered}
54317
54639
  `);
54318
54640
  if (!dDelivered)
54319
- pendingInboundBuffer.push(pending.agent, discardInbound);
54641
+ pendingInboundBuffer.push(pending2.agent, discardInbound);
54320
54642
  return;
54321
54643
  }
54322
54644
  if (action === "rename") {
54323
- pendingVaultOps.set(pending.chat_id, {
54645
+ pendingVaultOps.set(pending2.chat_id, {
54324
54646
  kind: "rename-vault-save",
54325
54647
  stageId,
54326
54648
  startedAt: Date.now()
@@ -54329,7 +54651,7 @@ async function handleVaultRequestSaveCallback(ctx, data) {
54329
54651
  const baseText = sourceMsg && "text" in sourceMsg && sourceMsg.text ? escapeHtmlForTg(sourceMsg.text) : "";
54330
54652
  const statusLine = `
54331
54653
 
54332
- \u270F\uFE0F <b>Rename mode</b> \u2014 send the new key name as your next message. ` + `The current proposed key is <code>${escapeHtmlForTg(pending.key)}</code>.`;
54654
+ \u270F\uFE0F <b>Rename mode</b> \u2014 send the new key name as your next message. ` + `The current proposed key is <code>${escapeHtmlForTg(pending2.key)}</code>.`;
54333
54655
  await finalizeCallback(ctx, {
54334
54656
  ackText: "Send the new key name as your next message.",
54335
54657
  newText: baseText ? `${baseText}${statusLine}` : statusLine,
@@ -54339,22 +54661,22 @@ async function handleVaultRequestSaveCallback(ctx, data) {
54339
54661
  }
54340
54662
  if (action === "save") {
54341
54663
  await ctx.answerCallbackQuery({ text: "\u23F3 Saving\u2026" }).catch(() => {});
54342
- const cached = vaultPassphraseCache.get(pending.chat_id);
54664
+ const cached = vaultPassphraseCache.get(pending2.chat_id);
54343
54665
  if (!cached || cached.expiresAt <= Date.now()) {
54344
- if (pending.card_message_id != null) {
54345
- await ctx.api.editMessageText(pending.chat_id, pending.card_message_id, `\uD83D\uDD12 <b>Vault is locked.</b> Run <code>/vault unlock</code> (or any /vault command) to cache the passphrase, then tap Save again on the next card.`, { parse_mode: "HTML", reply_markup: { inline_keyboard: [] } }).catch(() => {});
54666
+ if (pending2.card_message_id != null) {
54667
+ await ctx.api.editMessageText(pending2.chat_id, pending2.card_message_id, `\uD83D\uDD12 <b>Vault is locked.</b> Run <code>/vault unlock</code> (or any /vault command) to cache the passphrase, then tap Save again on the next card.`, { parse_mode: "HTML", reply_markup: { inline_keyboard: [] } }).catch(() => {});
54346
54668
  }
54347
54669
  pendingVaultRequestSaves.delete(stageId);
54348
54670
  return;
54349
54671
  }
54350
- const write = defaultVaultWrite(pending.key, pending.value, cached.passphrase);
54672
+ const write = defaultVaultWrite(pending2.key, pending2.value, cached.passphrase);
54351
54673
  if (!write.ok) {
54352
54674
  const parsed = parseVaultCliError(write.output);
54353
- const rendered = renderVaultCliError(parsed, { verb: "save", key: pending.key });
54675
+ const rendered = renderVaultCliError(parsed, { verb: "save", key: pending2.key });
54354
54676
  const body = rendered.suppressRaw ? rendered.html : `\u26A0\uFE0F vault write failed:
54355
54677
  <pre>${escapeHtmlForTg(write.output)}</pre>`;
54356
- if (pending.card_message_id != null) {
54357
- await ctx.api.editMessageText(pending.chat_id, pending.card_message_id, `${body}
54678
+ if (pending2.card_message_id != null) {
54679
+ await ctx.api.editMessageText(pending2.chat_id, pending2.card_message_id, `${body}
54358
54680
 
54359
54681
  <i>Tap a fresh card after fixing the underlying issue.</i>`, { parse_mode: "HTML", reply_markup: { inline_keyboard: [] } }).catch(() => {});
54360
54682
  }
@@ -54362,33 +54684,33 @@ async function handleVaultRequestSaveCallback(ctx, data) {
54362
54684
  const failReason = (write.output || "vault write error").split(`
54363
54685
  `)[0].slice(0, 200);
54364
54686
  const failInbound = buildVaultSaveFailedInbound({
54365
- ctx: { agent: pending.agent, key: pending.key, chat_id: pending.chat_id },
54687
+ ctx: { agent: pending2.agent, key: pending2.key, chat_id: pending2.chat_id },
54366
54688
  stageId,
54367
54689
  operatorId: senderId,
54368
54690
  reason: failReason
54369
54691
  });
54370
- const fDelivered = ipcServer.sendToAgent(pending.agent, failInbound);
54371
- process.stderr.write(`telegram gateway: vault_save_failed injection agent=${pending.agent} key=${pending.key} stage=${stageId} delivered=${fDelivered}
54692
+ const fDelivered = ipcServer.sendToAgent(pending2.agent, failInbound);
54693
+ process.stderr.write(`telegram gateway: vault_save_failed injection agent=${pending2.agent} key=${pending2.key} stage=${stageId} delivered=${fDelivered}
54372
54694
  `);
54373
54695
  if (!fDelivered)
54374
- pendingInboundBuffer.push(pending.agent, failInbound);
54696
+ pendingInboundBuffer.push(pending2.agent, failInbound);
54375
54697
  return;
54376
54698
  }
54377
54699
  pendingVaultRequestSaves.delete(stageId);
54378
- if (pending.card_message_id != null) {
54379
- await ctx.api.editMessageText(pending.chat_id, pending.card_message_id, `\u2705 saved as <code>vault:${escapeHtmlForTg(pending.key)}</code> (masked: <code>${escapeHtmlForTg(maskToken2(pending.value))}</code>)
54380
- <i>The agent can now reference this as <code>vault:${escapeHtmlForTg(pending.key)}</code>.</i>`, { parse_mode: "HTML", reply_markup: { inline_keyboard: [] } }).catch(() => {});
54700
+ if (pending2.card_message_id != null) {
54701
+ await ctx.api.editMessageText(pending2.chat_id, pending2.card_message_id, `\u2705 saved as <code>vault:${escapeHtmlForTg(pending2.key)}</code> (masked: <code>${escapeHtmlForTg(maskToken2(pending2.value))}</code>)
54702
+ <i>The agent can now reference this as <code>vault:${escapeHtmlForTg(pending2.key)}</code>.</i>`, { parse_mode: "HTML", reply_markup: { inline_keyboard: [] } }).catch(() => {});
54381
54703
  }
54382
54704
  const okInbound = buildVaultSaveCompletedInbound({
54383
- ctx: { agent: pending.agent, key: pending.key, chat_id: pending.chat_id },
54705
+ ctx: { agent: pending2.agent, key: pending2.key, chat_id: pending2.chat_id },
54384
54706
  stageId,
54385
54707
  operatorId: senderId
54386
54708
  });
54387
- const okDelivered = ipcServer.sendToAgent(pending.agent, okInbound);
54388
- process.stderr.write(`telegram gateway: vault_save_completed injection agent=${pending.agent} key=${pending.key} stage=${stageId} delivered=${okDelivered}
54709
+ const okDelivered = ipcServer.sendToAgent(pending2.agent, okInbound);
54710
+ process.stderr.write(`telegram gateway: vault_save_completed injection agent=${pending2.agent} key=${pending2.key} stage=${stageId} delivered=${okDelivered}
54389
54711
  `);
54390
54712
  if (!okDelivered)
54391
- pendingInboundBuffer.push(pending.agent, okInbound);
54713
+ pendingInboundBuffer.push(pending2.agent, okInbound);
54392
54714
  return;
54393
54715
  }
54394
54716
  await ctx.answerCallbackQuery({ text: "Unknown action" }).catch(() => {});
@@ -55650,6 +55972,35 @@ bot.on("callback_query:data", async (ctx) => {
55650
55972
  await handleApprovalCallback2(ctx, data);
55651
55973
  return;
55652
55974
  }
55975
+ if (data.startsWith("cfg:")) {
55976
+ const access2 = loadAccess();
55977
+ const senderId2 = String(ctx.from?.id ?? "");
55978
+ if (!access2.allowFrom.includes(senderId2)) {
55979
+ await ctx.answerCallbackQuery({ text: "Not authorized." });
55980
+ return;
55981
+ }
55982
+ const { parseConfigApprovalCallback: parseConfigApprovalCallback2, resolvePendingConfigApproval: resolvePendingConfigApproval2 } = await Promise.resolve().then(() => (init_config_approval_handler(), exports_config_approval_handler));
55983
+ const parsed = parseConfigApprovalCallback2(data);
55984
+ if (parsed === null) {
55985
+ await ctx.answerCallbackQuery({ text: "Malformed callback." });
55986
+ return;
55987
+ }
55988
+ const resolved = await resolvePendingConfigApproval2(parsed.requestId, parsed.choice, {
55989
+ editCard: async (args) => {
55990
+ try {
55991
+ await robustApiCall(() => bot.api.editMessageText(args.chatId, args.messageId, args.text, {
55992
+ parse_mode: "HTML"
55993
+ }));
55994
+ } catch {}
55995
+ },
55996
+ log: (m2) => process.stderr.write(`telegram gateway: config-approval cb \u2014 ${m2}
55997
+ `)
55998
+ });
55999
+ await ctx.answerCallbackQuery({
56000
+ text: resolved ? parsed.choice === "approve" ? "Approving\u2026" : "Denied" : "Already resolved."
56001
+ });
56002
+ return;
56003
+ }
55653
56004
  if (data.startsWith("drvpick:")) {
55654
56005
  const access2 = loadAccess();
55655
56006
  const senderId2 = String(ctx.from?.id ?? "");
@@ -56490,7 +56841,7 @@ async function handleMessageReaction(ctx) {
56490
56841
  `);
56491
56842
  return;
56492
56843
  }
56493
- const pending = {
56844
+ const pending2 = {
56494
56845
  targetMessageId: message_id,
56495
56846
  emoji,
56496
56847
  action,
@@ -56500,7 +56851,7 @@ async function handleMessageReaction(ctx) {
56500
56851
  user: reacter.first_name ?? reacter.username ?? String(reacter.id),
56501
56852
  ...typeof update.message_thread_id === "number" ? { threadId: update.message_thread_id } : {}
56502
56853
  };
56503
- getReactionDebounce().enqueue(update.chat.id, pending);
56854
+ getReactionDebounce().enqueue(update.chat.id, pending2);
56504
56855
  } catch (err) {
56505
56856
  process.stderr.write(`telegram gateway: message_reaction handler error: ${err}
56506
56857
  `);
@@ -56969,15 +57320,17 @@ var didOneTimeSetup = false;
56969
57320
  `);
56970
57321
  }
56971
57322
  },
56972
- onFinish: ({ agentId, outcome, toolCount, durationMs }) => {
56973
- let parentTurnKey = "";
57323
+ onFinish: ({ agentId, outcome, description, resultText }) => {
57324
+ if (process.env.SWITCHROOM_SUBAGENT_HANDBACK === "0")
57325
+ return;
57326
+ if (outcome !== "completed" && outcome !== "failed")
57327
+ return;
56974
57328
  let chatId = "";
56975
57329
  let isBackground = false;
56976
57330
  try {
56977
57331
  const fleets = progressDriver?.peekAllFleets() ?? [];
56978
57332
  for (const f of fleets) {
56979
57333
  if (f.fleet.has(agentId)) {
56980
- parentTurnKey = f.turnKey;
56981
57334
  chatId = f.chatId ?? "";
56982
57335
  break;
56983
57336
  }
@@ -56990,7 +57343,25 @@ var didOneTimeSetup = false;
56990
57343
  isBackground = row.background === 1;
56991
57344
  } catch {}
56992
57345
  }
56993
- const finalOutcome = isBackground ? "background" : outcome === "completed" ? "completed" : "orphan";
57346
+ if (!isBackground)
57347
+ return;
57348
+ const handbackChatId = chatId || (loadAccess().allowFrom[0] ?? "");
57349
+ if (!handbackChatId) {
57350
+ process.stderr.write(`telegram gateway: subagent-handback ${agentId} \u2014 no chat to deliver to; skipped
57351
+ `);
57352
+ return;
57353
+ }
57354
+ const inbound = buildSubagentHandbackInbound({
57355
+ ctx: {
57356
+ chatId: String(handbackChatId),
57357
+ taskDescription: description,
57358
+ resultText,
57359
+ outcome
57360
+ }
57361
+ });
57362
+ pendingInboundBuffer.push(process.env.SWITCHROOM_AGENT_NAME ?? "", inbound);
57363
+ process.stderr.write(`telegram gateway: subagent-handback queued agent=${agentId} outcome=${outcome} chat=${handbackChatId} resultChars=${resultText.length}
57364
+ `);
56994
57365
  }
56995
57366
  });
56996
57367
  process.stderr.write(`telegram gateway: subagent-watcher active