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.
- package/dist/cli/switchroom.js +53 -25
- package/dist/host-control/main.js +222 -7
- package/examples/switchroom.yaml +25 -7
- package/package.json +1 -1
- package/profiles/_shared/telegram-style.md.hbs +2 -2
- package/telegram-plugin/dist/gateway/gateway.js +514 -143
- package/telegram-plugin/gateway/config-approval-handler.test.ts +246 -0
- package/telegram-plugin/gateway/config-approval-handler.ts +284 -0
- package/telegram-plugin/gateway/gateway.ts +206 -21
- package/telegram-plugin/gateway/ipc-protocol.ts +72 -2
- package/telegram-plugin/gateway/ipc-server.ts +101 -0
- package/telegram-plugin/gateway/subagent-handback-inbound-builder.ts +103 -0
- package/telegram-plugin/hooks/subagent-tracker-posttool.mjs +69 -0
- package/telegram-plugin/subagent-watcher.ts +39 -0
- package/telegram-plugin/tests/subagent-handback-inbound-builder.test.ts +105 -0
- package/telegram-plugin/tests/subagent-tracker-hooks.test.ts +61 -0
- package/telegram-plugin/tests/subagent-watcher.test.ts +67 -1
- package/telegram-plugin/uat/scenarios/jtbd-subagent-handback-dm.test.ts +95 -0
- package/profiles/default/CLAUDE.md +0 -193
|
@@ -27225,7 +27225,7 @@ var init_secretlint_source = __esm(() => {
|
|
|
27225
27225
|
function escapeHtml8(s) {
|
|
27226
27226
|
return s.replace(/[&<>]/g, (c) => ({ "&": "&", "<": "<", ">": ">" })[c]);
|
|
27227
27227
|
}
|
|
27228
|
-
function
|
|
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, "&").replace(/</g, "<").replace(/>/g, ">");
|
|
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, "&").replace(/</g, "<").replace(/>/g, ">");
|
|
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>${
|
|
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>${
|
|
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>${
|
|
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>${
|
|
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>${
|
|
29426
|
+
await ctx.reply(`Unknown subcommand <code>${escapeHtml12(sub)}</code>. ` + `Use <code>/approvals list</code> or <code>/approvals revoke <id></code>. ` + `(<code>add</code> and <code>stats</code> are coming in a follow-up.)`, { parse_mode: "HTML" });
|
|
29272
29427
|
});
|
|
29273
29428
|
}
|
|
29274
|
-
function
|
|
29429
|
+
function escapeHtml12(s) {
|
|
29275
29430
|
return s.replace(/&/g, "&").replace(/</g, "<").replace(/>/g, ">");
|
|
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>${
|
|
29511
|
+
lines.push(`${marker}<code>${escapeHtml13(snap.label)}</code> <i>quota probe failed</i>`);
|
|
29357
29512
|
if (snap.quotaError) {
|
|
29358
|
-
lines.push(` <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>${
|
|
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 ${
|
|
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>${
|
|
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(`${
|
|
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 ${
|
|
29640
|
+
lines.push(`\u2713 <b>Switched fleet \u00b7 ${headerLimit} on ${escapeHtml13(input.oldLabel)}</b>`);
|
|
29486
29641
|
lines.push("");
|
|
29487
|
-
lines.push(`<code>${
|
|
29488
|
-
lines.push(`Triggered by: agent <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>${
|
|
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>${
|
|
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
|
|
29715
|
+
function escapeHtml13(s) {
|
|
29561
29716
|
return s.replace(/&/g, "&").replace(/</g, "<").replace(/>/g, ">");
|
|
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(
|
|
46376
|
-
const summary = entry.lastSummaryLine ? ` \u2014 ${escapeHtml8(
|
|
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(
|
|
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}: ${
|
|
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}: ${
|
|
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}: ${
|
|
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}: ${
|
|
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
|
|
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.
|
|
47745
|
-
var COMMIT_SHA = "
|
|
47746
|
-
var COMMIT_DATE = "2026-05-
|
|
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 =
|
|
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
|
|
48566
|
-
if (
|
|
48825
|
+
const pending2 = findMostRecentInterruptedTurn(turnsDb);
|
|
48826
|
+
if (pending2 != null) {
|
|
48567
48827
|
const lines = [
|
|
48568
48828
|
`SWITCHROOM_PENDING_TURN=true`,
|
|
48569
|
-
`SWITCHROOM_PENDING_TURN_KEY=${
|
|
48570
|
-
`SWITCHROOM_PENDING_CHAT_ID=${
|
|
48571
|
-
|
|
48572
|
-
|
|
48573
|
-
`SWITCHROOM_PENDING_ENDED_VIA=${
|
|
48574
|
-
`SWITCHROOM_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=${
|
|
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
|
|
49662
|
-
for (const msg of
|
|
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
|
|
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,
|
|
51181
|
+
pendingVaultRequestSaves.set(stageId, pending2);
|
|
50860
51182
|
sweepPendingVaultRequestSaves();
|
|
50861
|
-
const text = renderVaultRequestSaveCard(
|
|
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
|
-
|
|
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
|
|
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,
|
|
51284
|
+
pendingVaultRequestAccesses.set(stageId, pending2);
|
|
50963
51285
|
sweepPendingVaultRequestAccesses();
|
|
50964
|
-
const text = renderVaultRequestAccessCard(
|
|
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
|
-
|
|
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
|
|
51607
|
+
const pending2 = pendingPtyPartial;
|
|
51286
51608
|
pendingPtyPartial = null;
|
|
51287
|
-
handlePtyPartial(
|
|
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,
|
|
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 (
|
|
54423
|
+
if (pending2.scope === "read") {
|
|
54102
54424
|
try {
|
|
54103
54425
|
const visible = await listViaBroker();
|
|
54104
|
-
if (visible !== null && visible.includes(
|
|
54426
|
+
if (visible !== null && visible.includes(pending2.key)) {
|
|
54105
54427
|
pendingVaultRequestAccesses.delete(stageId);
|
|
54106
|
-
if (
|
|
54107
|
-
await ctx.api.editMessageText(
|
|
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 (
|
|
54116
|
-
const list2 = await listGrantsViaBroker(
|
|
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 (
|
|
54134
|
-
readKeys.add(
|
|
54135
|
-
if (
|
|
54136
|
-
writeKeys.add(
|
|
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:
|
|
54460
|
+
agent: pending2.agent,
|
|
54139
54461
|
keys: Array.from(readKeys),
|
|
54140
|
-
ttl_seconds:
|
|
54141
|
-
description: `auto-mint via vault_request_access (#1012, scope=${
|
|
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 (
|
|
54153
|
-
await ctx.api.editMessageText(
|
|
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",
|
|
54480
|
+
const tokenPath = join32(homedir12(), ".switchroom", "agents", pending2.agent, ".vault-token");
|
|
54159
54481
|
try {
|
|
54160
|
-
mkdirSync21(join32(homedir12(), ".switchroom", "agents",
|
|
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(
|
|
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 (
|
|
54169
|
-
const days = Math.round(
|
|
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(
|
|
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:
|
|
54177
|
-
key:
|
|
54178
|
-
scope:
|
|
54179
|
-
chat_id:
|
|
54180
|
-
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(
|
|
54187
|
-
process.stderr.write(`telegram gateway: vault_grant_approved injection agent=${
|
|
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(
|
|
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
|
|
54208
|
-
if (!
|
|
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 (
|
|
54219
|
-
await ctx.api.editMessageText(
|
|
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:
|
|
54224
|
-
key:
|
|
54225
|
-
scope:
|
|
54226
|
-
chat_id:
|
|
54227
|
-
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(
|
|
54233
|
-
process.stderr.write(`telegram gateway: vault_grant_denied injection agent=${
|
|
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(
|
|
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 (
|
|
54244
|
-
await ctx.api.editMessageText(
|
|
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,
|
|
54569
|
+
await performVaultAccessApproval(ctx, pending2, stageId, senderId, { kind: "posture" });
|
|
54248
54570
|
return;
|
|
54249
54571
|
}
|
|
54250
|
-
const cached = vaultPassphraseCache.get(
|
|
54572
|
+
const cached = vaultPassphraseCache.get(pending2.chat_id);
|
|
54251
54573
|
if (!cached || cached.expiresAt <= Date.now()) {
|
|
54252
|
-
if (
|
|
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(
|
|
54578
|
+
const existing = pendingVaultOps.get(pending2.chat_id);
|
|
54257
54579
|
const newItem = {
|
|
54258
54580
|
stageId,
|
|
54259
|
-
cardChatId:
|
|
54260
|
-
cardMessageId:
|
|
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(
|
|
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(
|
|
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,
|
|
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
|
|
54297
|
-
if (!
|
|
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 (
|
|
54308
|
-
await ctx.api.editMessageText(
|
|
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:
|
|
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(
|
|
54316
|
-
process.stderr.write(`telegram gateway: vault_save_discarded injection agent=${
|
|
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(
|
|
54641
|
+
pendingInboundBuffer.push(pending2.agent, discardInbound);
|
|
54320
54642
|
return;
|
|
54321
54643
|
}
|
|
54322
54644
|
if (action === "rename") {
|
|
54323
|
-
pendingVaultOps.set(
|
|
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(
|
|
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(
|
|
54664
|
+
const cached = vaultPassphraseCache.get(pending2.chat_id);
|
|
54343
54665
|
if (!cached || cached.expiresAt <= Date.now()) {
|
|
54344
|
-
if (
|
|
54345
|
-
await ctx.api.editMessageText(
|
|
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(
|
|
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:
|
|
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 (
|
|
54357
|
-
await ctx.api.editMessageText(
|
|
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:
|
|
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(
|
|
54371
|
-
process.stderr.write(`telegram gateway: vault_save_failed injection agent=${
|
|
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(
|
|
54696
|
+
pendingInboundBuffer.push(pending2.agent, failInbound);
|
|
54375
54697
|
return;
|
|
54376
54698
|
}
|
|
54377
54699
|
pendingVaultRequestSaves.delete(stageId);
|
|
54378
|
-
if (
|
|
54379
|
-
await ctx.api.editMessageText(
|
|
54380
|
-
<i>The agent can now reference this as <code>vault:${escapeHtmlForTg(
|
|
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:
|
|
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(
|
|
54388
|
-
process.stderr.write(`telegram gateway: vault_save_completed injection agent=${
|
|
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(
|
|
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
|
|
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,
|
|
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,
|
|
56973
|
-
|
|
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
|
-
|
|
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
|