switchroom 0.13.9 → 0.13.11
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 +38 -14
- 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 +1 -1
- package/telegram-plugin/dist/bridge/bridge.js +23 -4
- package/telegram-plugin/dist/gateway/gateway.js +540 -147
- package/telegram-plugin/dist/server.js +23 -4
- 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 +218 -25
- 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 +185 -0
- package/telegram-plugin/hooks/subagent-tracker-posttool.mjs +69 -0
- package/telegram-plugin/model-unavailable.ts +11 -1
- package/telegram-plugin/operator-events.fixtures.json +14 -24
- package/telegram-plugin/operator-events.ts +11 -2
- package/telegram-plugin/session-tail.ts +71 -4
- package/telegram-plugin/subagent-watcher.ts +39 -0
- package/telegram-plugin/tests/model-unavailable.test.ts +15 -2
- package/telegram-plugin/tests/operator-events-session-tail.test.ts +53 -2
- package/telegram-plugin/tests/operator-events.test.ts +14 -7
- package/telegram-plugin/tests/subagent-handback-decision.test.ts +112 -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) {
|
|
@@ -39477,7 +39632,8 @@ function resolveModelUnavailableFromOperatorEvent(ev) {
|
|
|
39477
39632
|
return detectModelUnavailable(detail) ?? { kind: "quota_exhausted", raw: detail };
|
|
39478
39633
|
}
|
|
39479
39634
|
if (ev.kind === "rate-limited") {
|
|
39480
|
-
|
|
39635
|
+
const detected = detectModelUnavailable(detail);
|
|
39636
|
+
return detected?.kind === "quota_exhausted" ? detected : null;
|
|
39481
39637
|
}
|
|
39482
39638
|
if (ev.kind === "unknown-5xx") {
|
|
39483
39639
|
return detectModelUnavailable(detail) ?? { kind: "overload", raw: detail };
|
|
@@ -43135,6 +43291,28 @@ function validateClientMessage(msg) {
|
|
|
43135
43291
|
const inb = m.inbound;
|
|
43136
43292
|
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
43293
|
}
|
|
43294
|
+
case "request_config_approval": {
|
|
43295
|
+
if (typeof m.requestId !== "string" || m.requestId.length === 0 || m.requestId.length > 64)
|
|
43296
|
+
return false;
|
|
43297
|
+
if (typeof m.agentName !== "string" || !AGENT_NAME_RE3.test(m.agentName))
|
|
43298
|
+
return false;
|
|
43299
|
+
if (typeof m.reason !== "string" || m.reason.length === 0 || m.reason.length > 500)
|
|
43300
|
+
return false;
|
|
43301
|
+
if (typeof m.unifiedDiff !== "string" || m.unifiedDiff.length === 0)
|
|
43302
|
+
return false;
|
|
43303
|
+
if (typeof m.timeoutMs !== "number" || !Number.isFinite(m.timeoutMs) || m.timeoutMs <= 0)
|
|
43304
|
+
return false;
|
|
43305
|
+
return true;
|
|
43306
|
+
}
|
|
43307
|
+
case "request_config_finalize": {
|
|
43308
|
+
if (typeof m.requestId !== "string" || m.requestId.length === 0 || m.requestId.length > 64)
|
|
43309
|
+
return false;
|
|
43310
|
+
if (m.outcome !== "applied" && m.outcome !== "reconcile_failed_rolled_back")
|
|
43311
|
+
return false;
|
|
43312
|
+
if (m.detail !== undefined && (typeof m.detail !== "string" || m.detail.length > 500))
|
|
43313
|
+
return false;
|
|
43314
|
+
return true;
|
|
43315
|
+
}
|
|
43138
43316
|
case "request_drive_approval": {
|
|
43139
43317
|
if (typeof m.correlationId !== "string" || m.correlationId.length === 0 || m.correlationId.length > 64)
|
|
43140
43318
|
return false;
|
|
@@ -43164,6 +43342,8 @@ function createIpcServer(options) {
|
|
|
43164
43342
|
onPtyPartial,
|
|
43165
43343
|
onInjectInbound,
|
|
43166
43344
|
onRequestDriveApproval,
|
|
43345
|
+
onRequestConfigApproval,
|
|
43346
|
+
onRequestConfigFinalize,
|
|
43167
43347
|
log = () => {},
|
|
43168
43348
|
heartbeatTimeoutMs = 30000
|
|
43169
43349
|
} = options;
|
|
@@ -43268,6 +43448,37 @@ function createIpcServer(options) {
|
|
|
43268
43448
|
} catch {}
|
|
43269
43449
|
}
|
|
43270
43450
|
break;
|
|
43451
|
+
case "request_config_approval":
|
|
43452
|
+
if (onRequestConfigApproval) {
|
|
43453
|
+
onRequestConfigApproval(client3, msg).catch((err) => {
|
|
43454
|
+
log(`request_config_approval handler threw (client=${client3.id}): ${err.message}`);
|
|
43455
|
+
try {
|
|
43456
|
+
client3.send({
|
|
43457
|
+
type: "config_approval_resolved",
|
|
43458
|
+
requestId: msg.requestId,
|
|
43459
|
+
verdict: "deny",
|
|
43460
|
+
reason: `gateway handler error: ${err.message}`
|
|
43461
|
+
});
|
|
43462
|
+
} catch {}
|
|
43463
|
+
});
|
|
43464
|
+
} else {
|
|
43465
|
+
try {
|
|
43466
|
+
client3.send({
|
|
43467
|
+
type: "config_approval_resolved",
|
|
43468
|
+
requestId: msg.requestId,
|
|
43469
|
+
verdict: "deny",
|
|
43470
|
+
reason: "gateway not configured for config-edit approval"
|
|
43471
|
+
});
|
|
43472
|
+
} catch {}
|
|
43473
|
+
}
|
|
43474
|
+
break;
|
|
43475
|
+
case "request_config_finalize":
|
|
43476
|
+
if (onRequestConfigFinalize) {
|
|
43477
|
+
onRequestConfigFinalize(client3, msg).catch((err) => {
|
|
43478
|
+
log(`request_config_finalize handler threw (client=${client3.id}): ${err.message}`);
|
|
43479
|
+
});
|
|
43480
|
+
}
|
|
43481
|
+
break;
|
|
43271
43482
|
case "update_placeholder":
|
|
43272
43483
|
if (!loggedLegacyUpdatePlaceholder.has(client3.id)) {
|
|
43273
43484
|
loggedLegacyUpdatePlaceholder.add(client3.id);
|
|
@@ -44530,6 +44741,74 @@ function buildVaultSaveDiscardedInbound(opts) {
|
|
|
44530
44741
|
};
|
|
44531
44742
|
}
|
|
44532
44743
|
|
|
44744
|
+
// gateway/subagent-handback-inbound-builder.ts
|
|
44745
|
+
var HANDBACK_RESULT_MAX = 3000;
|
|
44746
|
+
var HANDBACK_DESC_MAX = 200;
|
|
44747
|
+
function truncate2(s, max) {
|
|
44748
|
+
const t = s.trim();
|
|
44749
|
+
return t.length > max ? t.slice(0, max) + "\u2026" : t;
|
|
44750
|
+
}
|
|
44751
|
+
function buildSubagentHandbackInbound(opts) {
|
|
44752
|
+
const ts = opts.nowMs ?? Date.now();
|
|
44753
|
+
const desc = truncate2(opts.ctx.taskDescription, HANDBACK_DESC_MAX) || "(no description)";
|
|
44754
|
+
const result = truncate2(opts.ctx.resultText, HANDBACK_RESULT_MAX);
|
|
44755
|
+
const text = opts.ctx.outcome === "failed" ? `\uD83E\uDD1D A background worker you dispatched has FAILED.
|
|
44756
|
+
|
|
44757
|
+
` + `Task: ${desc}
|
|
44758
|
+
|
|
44759
|
+
` + (result ? `What it reported before failing:
|
|
44760
|
+
${result}
|
|
44761
|
+
|
|
44762
|
+
` : "") + `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.
|
|
44763
|
+
|
|
44764
|
+
` + `Task: ${desc}
|
|
44765
|
+
|
|
44766
|
+
` + (result ? `What the worker reported:
|
|
44767
|
+
${result}
|
|
44768
|
+
|
|
44769
|
+
` : `The worker left no summary text.
|
|
44770
|
+
|
|
44771
|
+
`) + `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.`;
|
|
44772
|
+
return {
|
|
44773
|
+
type: "inbound",
|
|
44774
|
+
chatId: opts.ctx.chatId,
|
|
44775
|
+
messageId: ts,
|
|
44776
|
+
user: "subagent-watcher",
|
|
44777
|
+
userId: 0,
|
|
44778
|
+
ts,
|
|
44779
|
+
text,
|
|
44780
|
+
meta: {
|
|
44781
|
+
source: "subagent_handback",
|
|
44782
|
+
outcome: opts.ctx.outcome
|
|
44783
|
+
}
|
|
44784
|
+
};
|
|
44785
|
+
}
|
|
44786
|
+
function decideSubagentHandback(input) {
|
|
44787
|
+
if (input.handbackEnvValue === "0") {
|
|
44788
|
+
return { deliver: false, reason: "env-disabled" };
|
|
44789
|
+
}
|
|
44790
|
+
if (input.outcome !== "completed" && input.outcome !== "failed") {
|
|
44791
|
+
return { deliver: false, reason: "outcome-not-terminal" };
|
|
44792
|
+
}
|
|
44793
|
+
if (!input.isBackground) {
|
|
44794
|
+
return { deliver: false, reason: "foreground" };
|
|
44795
|
+
}
|
|
44796
|
+
const chatId = input.fleetChatId || input.ownerChatId;
|
|
44797
|
+
if (!chatId) {
|
|
44798
|
+
return { deliver: false, reason: "no-chat" };
|
|
44799
|
+
}
|
|
44800
|
+
const inbound = buildSubagentHandbackInbound({
|
|
44801
|
+
ctx: {
|
|
44802
|
+
chatId,
|
|
44803
|
+
taskDescription: input.taskDescription,
|
|
44804
|
+
resultText: input.resultText,
|
|
44805
|
+
outcome: input.outcome
|
|
44806
|
+
},
|
|
44807
|
+
...input.nowMs !== undefined ? { nowMs: input.nowMs } : {}
|
|
44808
|
+
});
|
|
44809
|
+
return { deliver: true, chatId, inbound };
|
|
44810
|
+
}
|
|
44811
|
+
|
|
44533
44812
|
// gateway/poll-health.ts
|
|
44534
44813
|
var DEFAULT_LOG = (msg) => {
|
|
44535
44814
|
process.stderr.write(msg.endsWith(`
|
|
@@ -46106,6 +46385,7 @@ var DEFAULT_RESCAN_MS = 1000;
|
|
|
46106
46385
|
var DEFAULT_STALL_THRESHOLD_MS = 60000;
|
|
46107
46386
|
var DEFAULT_SILENT_SYNTHESIS_STALL_THRESHOLD_MS = 300000;
|
|
46108
46387
|
var DEFAULT_SILENT_STALL_TERMINAL_MS = 300000;
|
|
46388
|
+
var SUBAGENT_RESULT_TEXT_MAX = 3000;
|
|
46109
46389
|
function parseEnvMs(varName) {
|
|
46110
46390
|
const raw = process.env[varName];
|
|
46111
46391
|
if (raw == null || raw === "")
|
|
@@ -46231,6 +46511,7 @@ function readSubTail(entry, tail, now, onDescriptionUpdate, fs2, log, db2, paren
|
|
|
46231
46511
|
} else if (ev.kind === "sub_agent_text") {
|
|
46232
46512
|
entry.lastSummaryLine = ev.text.split(`
|
|
46233
46513
|
`)[0].trim().slice(0, 120);
|
|
46514
|
+
entry.lastResultText = ev.text.trim().slice(0, SUBAGENT_RESULT_TEXT_MAX);
|
|
46234
46515
|
} else if (ev.kind === "sub_agent_turn_end") {
|
|
46235
46516
|
if (entry.state === "running") {
|
|
46236
46517
|
entry.state = "done";
|
|
@@ -46322,6 +46603,7 @@ function startSubagentWatcher(config) {
|
|
|
46322
46603
|
completionNotified: false,
|
|
46323
46604
|
stallTerminalSynthesised: false,
|
|
46324
46605
|
lastSummaryLine: "",
|
|
46606
|
+
lastResultText: "",
|
|
46325
46607
|
lastTool: null,
|
|
46326
46608
|
historical: isHistorical
|
|
46327
46609
|
};
|
|
@@ -46372,8 +46654,8 @@ function startSubagentWatcher(config) {
|
|
|
46372
46654
|
return;
|
|
46373
46655
|
if (entry.state === "done" && !entry.completionNotified) {
|
|
46374
46656
|
entry.completionNotified = true;
|
|
46375
|
-
const desc = escapeHtml8(
|
|
46376
|
-
const summary = entry.lastSummaryLine ? ` \u2014 ${escapeHtml8(
|
|
46657
|
+
const desc = escapeHtml8(truncate3(entry.description, 80));
|
|
46658
|
+
const summary = entry.lastSummaryLine ? ` \u2014 ${escapeHtml8(truncate3(entry.lastSummaryLine, 120))}` : "";
|
|
46377
46659
|
const tools = entry.toolCount > 0 ? ` (${entry.toolCount} tools)` : "";
|
|
46378
46660
|
try {
|
|
46379
46661
|
config.sendNotification(`\u2713 Worker done: ${desc}${tools}${summary}`);
|
|
@@ -46387,7 +46669,9 @@ function startSubagentWatcher(config) {
|
|
|
46387
46669
|
state: entry.state,
|
|
46388
46670
|
outcome: entry.historical ? "orphan" : "completed",
|
|
46389
46671
|
toolCount: entry.toolCount,
|
|
46390
|
-
durationMs: nowFn() - entry.dispatchedAt
|
|
46672
|
+
durationMs: nowFn() - entry.dispatchedAt,
|
|
46673
|
+
description: entry.description,
|
|
46674
|
+
resultText: entry.lastResultText
|
|
46391
46675
|
});
|
|
46392
46676
|
} catch (cbErr) {
|
|
46393
46677
|
log?.(`subagent-watcher: onFinish callback error ${agentId}: ${cbErr.message}`);
|
|
@@ -46404,7 +46688,9 @@ function startSubagentWatcher(config) {
|
|
|
46404
46688
|
state: entry.state,
|
|
46405
46689
|
outcome: "failed",
|
|
46406
46690
|
toolCount: entry.toolCount,
|
|
46407
|
-
durationMs: nowFn() - entry.dispatchedAt
|
|
46691
|
+
durationMs: nowFn() - entry.dispatchedAt,
|
|
46692
|
+
description: entry.description,
|
|
46693
|
+
resultText: entry.lastResultText
|
|
46408
46694
|
});
|
|
46409
46695
|
} catch (cbErr) {
|
|
46410
46696
|
log?.(`subagent-watcher: onFinish callback error ${agentId}: ${cbErr.message}`);
|
|
@@ -46457,7 +46743,7 @@ function startSubagentWatcher(config) {
|
|
|
46457
46743
|
if (idleMs >= threshold) {
|
|
46458
46744
|
entry.stallNotified = true;
|
|
46459
46745
|
entry.stalledAt = n;
|
|
46460
|
-
const desc = escapeHtml8(
|
|
46746
|
+
const desc = escapeHtml8(truncate3(entry.description, 80));
|
|
46461
46747
|
const idleSec = Math.floor(idleMs / 1000);
|
|
46462
46748
|
log?.(`subagent-watcher: stall detected for ${entry.agentId} (idle ${idleSec}s): ${desc}`);
|
|
46463
46749
|
if (db2 != null) {
|
|
@@ -47419,7 +47705,7 @@ function summarizeToolForTitle(toolName, inputPreview) {
|
|
|
47419
47705
|
}
|
|
47420
47706
|
case "Bash": {
|
|
47421
47707
|
const command = readString(input, "command");
|
|
47422
|
-
return command ? `${toolName}: ${
|
|
47708
|
+
return command ? `${toolName}: ${truncate4(command, COMMAND_TITLE_MAX)}` : toolName;
|
|
47423
47709
|
}
|
|
47424
47710
|
case "Read":
|
|
47425
47711
|
case "Edit":
|
|
@@ -47427,17 +47713,17 @@ function summarizeToolForTitle(toolName, inputPreview) {
|
|
|
47427
47713
|
case "MultiEdit":
|
|
47428
47714
|
case "NotebookEdit": {
|
|
47429
47715
|
const filePath = readString(input, "file_path") ?? readString(input, "notebook_path");
|
|
47430
|
-
return filePath ? `${toolName}: ${
|
|
47716
|
+
return filePath ? `${toolName}: ${truncate4(basename5(filePath), PATH_TITLE_MAX)}` : toolName;
|
|
47431
47717
|
}
|
|
47432
47718
|
case "Glob":
|
|
47433
47719
|
case "Grep": {
|
|
47434
47720
|
const pattern = readString(input, "pattern");
|
|
47435
|
-
return pattern ? `${toolName}: ${
|
|
47721
|
+
return pattern ? `${toolName}: ${truncate4(pattern, COMMAND_TITLE_MAX)}` : toolName;
|
|
47436
47722
|
}
|
|
47437
47723
|
case "WebFetch":
|
|
47438
47724
|
case "WebSearch": {
|
|
47439
47725
|
const query2 = readString(input, "url") ?? readString(input, "query");
|
|
47440
|
-
return query2 ? `${toolName}: ${
|
|
47726
|
+
return query2 ? `${toolName}: ${truncate4(query2, COMMAND_TITLE_MAX)}` : toolName;
|
|
47441
47727
|
}
|
|
47442
47728
|
default:
|
|
47443
47729
|
return toolName;
|
|
@@ -47470,7 +47756,7 @@ function skillBasenameFromPath(input) {
|
|
|
47470
47756
|
const basename6 = lastSlash >= 0 ? trimmed.slice(lastSlash + 1) : trimmed;
|
|
47471
47757
|
return basename6.length > 0 ? basename6 : null;
|
|
47472
47758
|
}
|
|
47473
|
-
function
|
|
47759
|
+
function truncate4(text, max) {
|
|
47474
47760
|
const collapsed = text.replace(/\s+/g, " ").trim();
|
|
47475
47761
|
if (collapsed.length <= max)
|
|
47476
47762
|
return collapsed;
|
|
@@ -47741,11 +48027,11 @@ function sweepStaleTurnActiveMarker(stateDir, opts) {
|
|
|
47741
48027
|
}
|
|
47742
48028
|
|
|
47743
48029
|
// ../src/build-info.ts
|
|
47744
|
-
var VERSION = "0.13.
|
|
47745
|
-
var COMMIT_SHA = "
|
|
47746
|
-
var COMMIT_DATE = "2026-05-
|
|
48030
|
+
var VERSION = "0.13.11";
|
|
48031
|
+
var COMMIT_SHA = "5984798c";
|
|
48032
|
+
var COMMIT_DATE = "2026-05-22T15:59:07+10:00";
|
|
47747
48033
|
var LATEST_PR = null;
|
|
47748
|
-
var COMMITS_AHEAD_OF_TAG =
|
|
48034
|
+
var COMMITS_AHEAD_OF_TAG = 3;
|
|
47749
48035
|
|
|
47750
48036
|
// gateway/boot-version.ts
|
|
47751
48037
|
function formatRelativeAgo(iso) {
|
|
@@ -48562,23 +48848,23 @@ try {
|
|
|
48562
48848
|
}
|
|
48563
48849
|
const pendingEnvPath = join32(agentDir, ".pending-turn.env");
|
|
48564
48850
|
try {
|
|
48565
|
-
const
|
|
48566
|
-
if (
|
|
48851
|
+
const pending2 = findMostRecentInterruptedTurn(turnsDb);
|
|
48852
|
+
if (pending2 != null) {
|
|
48567
48853
|
const lines = [
|
|
48568
48854
|
`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=${
|
|
48855
|
+
`SWITCHROOM_PENDING_TURN_KEY=${pending2.turn_key}`,
|
|
48856
|
+
`SWITCHROOM_PENDING_CHAT_ID=${pending2.chat_id}`,
|
|
48857
|
+
pending2.thread_id != null ? `SWITCHROOM_PENDING_THREAD_ID=${pending2.thread_id}` : `SWITCHROOM_PENDING_THREAD_ID=`,
|
|
48858
|
+
pending2.last_user_msg_id != null ? `SWITCHROOM_PENDING_USER_MSG_ID=${pending2.last_user_msg_id}` : `SWITCHROOM_PENDING_USER_MSG_ID=`,
|
|
48859
|
+
`SWITCHROOM_PENDING_ENDED_VIA=${pending2.ended_via ?? "unknown"}`,
|
|
48860
|
+
`SWITCHROOM_PENDING_STARTED_AT=${pending2.started_at}`
|
|
48575
48861
|
];
|
|
48576
48862
|
const pendingEnvTmp = `${pendingEnvPath}.tmp-${process.pid}`;
|
|
48577
48863
|
writeFileSync21(pendingEnvTmp, lines.join(`
|
|
48578
48864
|
`) + `
|
|
48579
48865
|
`, { mode: 384 });
|
|
48580
48866
|
renameSync12(pendingEnvTmp, pendingEnvPath);
|
|
48581
|
-
process.stderr.write(`telegram gateway: pending-turn env written to ${pendingEnvPath} turnKey=${
|
|
48867
|
+
process.stderr.write(`telegram gateway: pending-turn env written to ${pendingEnvPath} turnKey=${pending2.turn_key} endedVia=${pending2.ended_via ?? "open"}
|
|
48582
48868
|
`);
|
|
48583
48869
|
} else if (existsSync34(pendingEnvPath)) {
|
|
48584
48870
|
rmSync4(pendingEnvPath, { force: true });
|
|
@@ -49368,7 +49654,7 @@ function emitGatewayOperatorEvent(event) {
|
|
|
49368
49654
|
let renderedText;
|
|
49369
49655
|
let renderedKeyboard;
|
|
49370
49656
|
if (modelUnavailable) {
|
|
49371
|
-
const isAutoKind = modelUnavailable.kind === "quota_exhausted"
|
|
49657
|
+
const isAutoKind = modelUnavailable.kind === "quota_exhausted";
|
|
49372
49658
|
const willActuallyFire = isAutoKind && wouldFireFleetAutoFallback();
|
|
49373
49659
|
process.stderr.write(`telegram gateway: operator-event suppressing-raw-stderr-for-model-unavailable agent=${agent} kind=${kind} detected=${modelUnavailable.kind} autoKind=${isAutoKind} willFire=${willActuallyFire}
|
|
49374
49660
|
`);
|
|
@@ -49658,8 +49944,8 @@ var ipcServer = createIpcServer({
|
|
|
49658
49944
|
client: client3
|
|
49659
49945
|
});
|
|
49660
49946
|
} else {
|
|
49661
|
-
const
|
|
49662
|
-
for (const msg of
|
|
49947
|
+
const pending2 = pendingInboundBuffer.drain(client3.agentName);
|
|
49948
|
+
for (const msg of pending2) {
|
|
49663
49949
|
try {
|
|
49664
49950
|
client3.send(msg);
|
|
49665
49951
|
inboundSpool?.ack(msg);
|
|
@@ -49762,9 +50048,9 @@ var ipcServer = createIpcServer({
|
|
|
49762
50048
|
}
|
|
49763
50049
|
},
|
|
49764
50050
|
onClientDisconnected(client3) {
|
|
49765
|
-
process.stderr.write(`telegram gateway: bridge disconnected \u2014 agent=${client3.agentName}
|
|
49766
|
-
`);
|
|
49767
50051
|
if (client3.agentName != null) {
|
|
50052
|
+
process.stderr.write(`telegram gateway: bridge disconnected \u2014 agent=${client3.agentName}
|
|
50053
|
+
`);
|
|
49768
50054
|
shadowEmit({ kind: "bridgeDown", at: Date.now() });
|
|
49769
50055
|
}
|
|
49770
50056
|
flushOnAgentDisconnect({
|
|
@@ -49954,6 +50240,68 @@ ${reminder}
|
|
|
49954
50240
|
},
|
|
49955
50241
|
buildCard: ({ preview, suggestRequestId }) => buildDiffPreviewCard({ preview, suggestRequestId }),
|
|
49956
50242
|
log: (m) => process.stderr.write(`telegram gateway: drive-approval \u2014 ${m}
|
|
50243
|
+
`)
|
|
50244
|
+
});
|
|
50245
|
+
},
|
|
50246
|
+
async onRequestConfigApproval(client3, msg) {
|
|
50247
|
+
const { handleRequestConfigApproval: handleRequestConfigApproval2 } = await Promise.resolve().then(() => (init_config_approval_handler(), exports_config_approval_handler));
|
|
50248
|
+
const { InlineKeyboard: InlineKeyboard6 } = await Promise.resolve().then(() => __toESM(require_mod(), 1));
|
|
50249
|
+
await handleRequestConfigApproval2(client3, msg, {
|
|
50250
|
+
agentName: getMyAgentName(),
|
|
50251
|
+
loadTargetChat: () => {
|
|
50252
|
+
const access = loadAccess();
|
|
50253
|
+
const operator = access.allowFrom[0];
|
|
50254
|
+
if (operator === undefined)
|
|
50255
|
+
return null;
|
|
50256
|
+
return { chatId: operator };
|
|
50257
|
+
},
|
|
50258
|
+
buildKeyboard: (requestId) => new InlineKeyboard6().text("\u2705 Approve", `cfg:${requestId}:approve`).text("\uD83D\uDEAB Deny", `cfg:${requestId}:deny`),
|
|
50259
|
+
postCard: async (args) => {
|
|
50260
|
+
try {
|
|
50261
|
+
const sent = await robustApiCall(() => bot.api.sendMessage(args.chatId, args.text, {
|
|
50262
|
+
parse_mode: "HTML",
|
|
50263
|
+
...args.threadId !== undefined ? { message_thread_id: args.threadId } : {},
|
|
50264
|
+
reply_markup: args.replyMarkup
|
|
50265
|
+
}), {
|
|
50266
|
+
chat_id: String(args.chatId),
|
|
50267
|
+
verb: "config-approval-card",
|
|
50268
|
+
...args.threadId !== undefined ? { threadId: args.threadId } : {}
|
|
50269
|
+
});
|
|
50270
|
+
return { messageId: sent.message_id };
|
|
50271
|
+
} catch (err) {
|
|
50272
|
+
process.stderr.write(`telegram gateway: config-approval postCard failed: ${err.message}
|
|
50273
|
+
`);
|
|
50274
|
+
return null;
|
|
50275
|
+
}
|
|
50276
|
+
},
|
|
50277
|
+
editCard: async (args) => {
|
|
50278
|
+
try {
|
|
50279
|
+
await robustApiCall(() => bot.api.editMessageText(args.chatId, args.messageId, args.text, {
|
|
50280
|
+
parse_mode: "HTML"
|
|
50281
|
+
}), { chat_id: String(args.chatId), verb: "config-approval-edit" });
|
|
50282
|
+
} catch (err) {
|
|
50283
|
+
process.stderr.write(`telegram gateway: config-approval editCard failed: ${err.message}
|
|
50284
|
+
`);
|
|
50285
|
+
}
|
|
50286
|
+
},
|
|
50287
|
+
log: (m) => process.stderr.write(`telegram gateway: config-approval \u2014 ${m}
|
|
50288
|
+
`)
|
|
50289
|
+
});
|
|
50290
|
+
},
|
|
50291
|
+
async onRequestConfigFinalize(client3, msg) {
|
|
50292
|
+
const { handleRequestConfigFinalize: handleRequestConfigFinalize2 } = await Promise.resolve().then(() => (init_config_approval_handler(), exports_config_approval_handler));
|
|
50293
|
+
await handleRequestConfigFinalize2(client3, msg, {
|
|
50294
|
+
editCard: async (args) => {
|
|
50295
|
+
try {
|
|
50296
|
+
await robustApiCall(() => bot.api.editMessageText(args.chatId, args.messageId, args.text, {
|
|
50297
|
+
parse_mode: "HTML"
|
|
50298
|
+
}), { chat_id: String(args.chatId), verb: "config-approval-finalize" });
|
|
50299
|
+
} catch (err) {
|
|
50300
|
+
process.stderr.write(`telegram gateway: config-finalize editCard failed: ${err.message}
|
|
50301
|
+
`);
|
|
50302
|
+
}
|
|
50303
|
+
},
|
|
50304
|
+
log: (m) => process.stderr.write(`telegram gateway: config-finalize \u2014 ${m}
|
|
49957
50305
|
`)
|
|
49958
50306
|
});
|
|
49959
50307
|
},
|
|
@@ -50847,7 +51195,7 @@ async function executeVaultRequestSave(args) {
|
|
|
50847
51195
|
}
|
|
50848
51196
|
const agentSlug = process.env.SWITCHROOM_AGENT_NAME || "agent";
|
|
50849
51197
|
const stageId = randomBytes6(4).toString("hex");
|
|
50850
|
-
const
|
|
51198
|
+
const pending2 = {
|
|
50851
51199
|
agent: agentSlug,
|
|
50852
51200
|
chat_id,
|
|
50853
51201
|
key,
|
|
@@ -50856,16 +51204,16 @@ async function executeVaultRequestSave(args) {
|
|
|
50856
51204
|
why,
|
|
50857
51205
|
staged_at: Date.now()
|
|
50858
51206
|
};
|
|
50859
|
-
pendingVaultRequestSaves.set(stageId,
|
|
51207
|
+
pendingVaultRequestSaves.set(stageId, pending2);
|
|
50860
51208
|
sweepPendingVaultRequestSaves();
|
|
50861
|
-
const text = renderVaultRequestSaveCard(
|
|
51209
|
+
const text = renderVaultRequestSaveCard(pending2, agentSlug);
|
|
50862
51210
|
const threadId = args.message_thread_id != null ? Number(args.message_thread_id) : undefined;
|
|
50863
51211
|
const sent = await retryWithThreadFallback(robustApiCall, (tid) => lockedBot.api.sendMessage(chat_id, text, {
|
|
50864
51212
|
parse_mode: "HTML",
|
|
50865
51213
|
reply_markup: buildVaultRequestSaveKeyboard(stageId),
|
|
50866
51214
|
...tid != null && Number.isFinite(tid) ? { message_thread_id: tid } : {}
|
|
50867
51215
|
}), { threadId, chat_id, verb: "vault_request_save.card" });
|
|
50868
|
-
|
|
51216
|
+
pending2.card_message_id = sent.message_id;
|
|
50869
51217
|
return {
|
|
50870
51218
|
content: [
|
|
50871
51219
|
{
|
|
@@ -50950,7 +51298,7 @@ async function executeVaultRequestAccess(args) {
|
|
|
50950
51298
|
} catch {}
|
|
50951
51299
|
}
|
|
50952
51300
|
const stageId = randomBytes6(4).toString("hex");
|
|
50953
|
-
const
|
|
51301
|
+
const pending2 = {
|
|
50954
51302
|
agent: agentSlug,
|
|
50955
51303
|
chat_id,
|
|
50956
51304
|
key,
|
|
@@ -50959,16 +51307,16 @@ async function executeVaultRequestAccess(args) {
|
|
|
50959
51307
|
ttl_seconds,
|
|
50960
51308
|
staged_at: Date.now()
|
|
50961
51309
|
};
|
|
50962
|
-
pendingVaultRequestAccesses.set(stageId,
|
|
51310
|
+
pendingVaultRequestAccesses.set(stageId, pending2);
|
|
50963
51311
|
sweepPendingVaultRequestAccesses();
|
|
50964
|
-
const text = renderVaultRequestAccessCard(
|
|
51312
|
+
const text = renderVaultRequestAccessCard(pending2);
|
|
50965
51313
|
const threadId = args.message_thread_id != null ? Number(args.message_thread_id) : undefined;
|
|
50966
51314
|
const sent = await retryWithThreadFallback(robustApiCall, (tid) => lockedBot.api.sendMessage(chat_id, text, {
|
|
50967
51315
|
parse_mode: "HTML",
|
|
50968
51316
|
reply_markup: buildVaultRequestAccessKeyboard(stageId),
|
|
50969
51317
|
...tid != null && Number.isFinite(tid) ? { message_thread_id: tid } : {}
|
|
50970
51318
|
}), { threadId, chat_id, verb: "vault_request_access.card" });
|
|
50971
|
-
|
|
51319
|
+
pending2.card_message_id = sent.message_id;
|
|
50972
51320
|
return {
|
|
50973
51321
|
content: [
|
|
50974
51322
|
{
|
|
@@ -51282,9 +51630,9 @@ function handleSessionEvent(ev) {
|
|
|
51282
51630
|
});
|
|
51283
51631
|
}
|
|
51284
51632
|
if (pendingPtyPartial != null) {
|
|
51285
|
-
const
|
|
51633
|
+
const pending2 = pendingPtyPartial;
|
|
51286
51634
|
pendingPtyPartial = null;
|
|
51287
|
-
handlePtyPartial(
|
|
51635
|
+
handlePtyPartial(pending2);
|
|
51288
51636
|
}
|
|
51289
51637
|
}
|
|
51290
51638
|
return;
|
|
@@ -54096,15 +54444,15 @@ async function handleVaultRecentDenialCallback(ctx, data) {
|
|
|
54096
54444
|
parseMode: "HTML"
|
|
54097
54445
|
});
|
|
54098
54446
|
}
|
|
54099
|
-
async function performVaultAccessApproval(ctx,
|
|
54447
|
+
async function performVaultAccessApproval(ctx, pending2, stageId, senderId, attestation) {
|
|
54100
54448
|
const brokerAuthOpts = attestation.kind === "passphrase" ? { passphrase: attestation.passphrase } : { attest_via_posture: true };
|
|
54101
|
-
if (
|
|
54449
|
+
if (pending2.scope === "read") {
|
|
54102
54450
|
try {
|
|
54103
54451
|
const visible = await listViaBroker();
|
|
54104
|
-
if (visible !== null && visible.includes(
|
|
54452
|
+
if (visible !== null && visible.includes(pending2.key)) {
|
|
54105
54453
|
pendingVaultRequestAccesses.delete(stageId);
|
|
54106
|
-
if (
|
|
54107
|
-
await ctx.api.editMessageText(
|
|
54454
|
+
if (pending2.card_message_id != null) {
|
|
54455
|
+
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
54456
|
}
|
|
54109
54457
|
return;
|
|
54110
54458
|
}
|
|
@@ -54112,8 +54460,8 @@ async function performVaultAccessApproval(ctx, pending, stageId, senderId, attes
|
|
|
54112
54460
|
}
|
|
54113
54461
|
let existingReadKeys = [];
|
|
54114
54462
|
let existingWriteKeys = [];
|
|
54115
|
-
if (
|
|
54116
|
-
const list2 = await listGrantsViaBroker(
|
|
54463
|
+
if (pending2.scope === "read" || pending2.scope === "write") {
|
|
54464
|
+
const list2 = await listGrantsViaBroker(pending2.agent, brokerAuthOpts);
|
|
54117
54465
|
if (list2.kind === "ok") {
|
|
54118
54466
|
const now = Math.floor(Date.now() / 1000);
|
|
54119
54467
|
const active = list2.grants.filter((g) => g.expires_at === null || g.expires_at > now).sort((a, b) => {
|
|
@@ -54130,15 +54478,15 @@ async function performVaultAccessApproval(ctx, pending, stageId, senderId, attes
|
|
|
54130
54478
|
}
|
|
54131
54479
|
const readKeys = new Set(existingReadKeys);
|
|
54132
54480
|
const writeKeys = new Set(existingWriteKeys);
|
|
54133
|
-
if (
|
|
54134
|
-
readKeys.add(
|
|
54135
|
-
if (
|
|
54136
|
-
writeKeys.add(
|
|
54481
|
+
if (pending2.scope === "read")
|
|
54482
|
+
readKeys.add(pending2.key);
|
|
54483
|
+
if (pending2.scope === "write")
|
|
54484
|
+
writeKeys.add(pending2.key);
|
|
54137
54485
|
const mintArgs = {
|
|
54138
|
-
agent:
|
|
54486
|
+
agent: pending2.agent,
|
|
54139
54487
|
keys: Array.from(readKeys),
|
|
54140
|
-
ttl_seconds:
|
|
54141
|
-
description: `auto-mint via vault_request_access (#1012, scope=${
|
|
54488
|
+
ttl_seconds: pending2.ttl_seconds,
|
|
54489
|
+
description: `auto-mint via vault_request_access (#1012, scope=${pending2.scope}, by op ${senderId}` + (existingReadKeys.length + existingWriteKeys.length > 0 ? `, unioned with prior grant` : ``) + `)`,
|
|
54142
54490
|
...writeKeys.size > 0 ? { write_keys: Array.from(writeKeys) } : {},
|
|
54143
54491
|
...brokerAuthOpts
|
|
54144
54492
|
};
|
|
@@ -54149,45 +54497,45 @@ async function performVaultAccessApproval(ctx, pending, stageId, senderId, attes
|
|
|
54149
54497
|
}
|
|
54150
54498
|
if (result.kind === "error") {
|
|
54151
54499
|
pendingVaultRequestAccesses.delete(stageId);
|
|
54152
|
-
if (
|
|
54153
|
-
await ctx.api.editMessageText(
|
|
54500
|
+
if (pending2.card_message_id != null) {
|
|
54501
|
+
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
54502
|
}
|
|
54155
54503
|
return;
|
|
54156
54504
|
}
|
|
54157
54505
|
const { token, id } = result;
|
|
54158
|
-
const tokenPath = join32(homedir12(), ".switchroom", "agents",
|
|
54506
|
+
const tokenPath = join32(homedir12(), ".switchroom", "agents", pending2.agent, ".vault-token");
|
|
54159
54507
|
try {
|
|
54160
|
-
mkdirSync21(join32(homedir12(), ".switchroom", "agents",
|
|
54508
|
+
mkdirSync21(join32(homedir12(), ".switchroom", "agents", pending2.agent), { recursive: true });
|
|
54161
54509
|
writeFileSync21(tokenPath, token, { mode: 384 });
|
|
54162
54510
|
} catch (err) {
|
|
54163
54511
|
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(
|
|
54512
|
+
<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
54513
|
return;
|
|
54166
54514
|
}
|
|
54167
54515
|
pendingVaultRequestAccesses.delete(stageId);
|
|
54168
|
-
if (
|
|
54169
|
-
const days = Math.round(
|
|
54516
|
+
if (pending2.card_message_id != null) {
|
|
54517
|
+
const days = Math.round(pending2.ttl_seconds / 86400);
|
|
54170
54518
|
const footer = VAULT_APPROVAL_AUTH_MODE === "telegram-id" ? `
|
|
54171
54519
|
<i>Approver verified by Telegram identity \u2014 broker auto-unlocked at startup.</i>` : "";
|
|
54172
|
-
await ctx.api.editMessageText(
|
|
54520
|
+
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
54521
|
}
|
|
54174
54522
|
const synthetic = buildVaultGrantApprovedInbound({
|
|
54175
54523
|
ctx: {
|
|
54176
|
-
agent:
|
|
54177
|
-
key:
|
|
54178
|
-
scope:
|
|
54179
|
-
chat_id:
|
|
54180
|
-
ttl_seconds:
|
|
54524
|
+
agent: pending2.agent,
|
|
54525
|
+
key: pending2.key,
|
|
54526
|
+
scope: pending2.scope,
|
|
54527
|
+
chat_id: pending2.chat_id,
|
|
54528
|
+
ttl_seconds: pending2.ttl_seconds
|
|
54181
54529
|
},
|
|
54182
54530
|
grantId: id,
|
|
54183
54531
|
stageId,
|
|
54184
54532
|
operatorId: senderId
|
|
54185
54533
|
});
|
|
54186
|
-
const delivered = ipcServer.sendToAgent(
|
|
54187
|
-
process.stderr.write(`telegram gateway: vault_grant_approved injection agent=${
|
|
54534
|
+
const delivered = ipcServer.sendToAgent(pending2.agent, synthetic);
|
|
54535
|
+
process.stderr.write(`telegram gateway: vault_grant_approved injection agent=${pending2.agent} key=${pending2.key} stage=${stageId} delivered=${delivered}
|
|
54188
54536
|
`);
|
|
54189
54537
|
if (!delivered) {
|
|
54190
|
-
pendingInboundBuffer.push(
|
|
54538
|
+
pendingInboundBuffer.push(pending2.agent, synthetic);
|
|
54191
54539
|
}
|
|
54192
54540
|
}
|
|
54193
54541
|
async function handleVaultRequestAccessCallback(ctx, data) {
|
|
@@ -54204,8 +54552,8 @@ async function handleVaultRequestAccessCallback(ctx, data) {
|
|
|
54204
54552
|
}
|
|
54205
54553
|
const action = parts[1];
|
|
54206
54554
|
const stageId = parts.slice(2).join(":");
|
|
54207
|
-
const
|
|
54208
|
-
if (!
|
|
54555
|
+
const pending2 = pendingVaultRequestAccesses.get(stageId);
|
|
54556
|
+
if (!pending2) {
|
|
54209
54557
|
await ctx.answerCallbackQuery({ text: "Card expired \u2014 ask the agent to re-request." }).catch(() => {});
|
|
54210
54558
|
if (ctx.callbackQuery?.message) {
|
|
54211
54559
|
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 +54563,66 @@ async function handleVaultRequestAccessCallback(ctx, data) {
|
|
|
54215
54563
|
if (action === "deny") {
|
|
54216
54564
|
pendingVaultRequestAccesses.delete(stageId);
|
|
54217
54565
|
await ctx.answerCallbackQuery({ text: "\uD83D\uDEAB Denied" }).catch(() => {});
|
|
54218
|
-
if (
|
|
54219
|
-
await ctx.api.editMessageText(
|
|
54566
|
+
if (pending2.card_message_id != null) {
|
|
54567
|
+
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
54568
|
}
|
|
54221
54569
|
const denyInbound = buildVaultGrantDeniedInbound({
|
|
54222
54570
|
ctx: {
|
|
54223
|
-
agent:
|
|
54224
|
-
key:
|
|
54225
|
-
scope:
|
|
54226
|
-
chat_id:
|
|
54227
|
-
ttl_seconds:
|
|
54571
|
+
agent: pending2.agent,
|
|
54572
|
+
key: pending2.key,
|
|
54573
|
+
scope: pending2.scope,
|
|
54574
|
+
chat_id: pending2.chat_id,
|
|
54575
|
+
ttl_seconds: pending2.ttl_seconds
|
|
54228
54576
|
},
|
|
54229
54577
|
stageId,
|
|
54230
54578
|
operatorId: senderId
|
|
54231
54579
|
});
|
|
54232
|
-
const denyDelivered = ipcServer.sendToAgent(
|
|
54233
|
-
process.stderr.write(`telegram gateway: vault_grant_denied injection agent=${
|
|
54580
|
+
const denyDelivered = ipcServer.sendToAgent(pending2.agent, denyInbound);
|
|
54581
|
+
process.stderr.write(`telegram gateway: vault_grant_denied injection agent=${pending2.agent} key=${pending2.key} stage=${stageId} delivered=${denyDelivered}
|
|
54234
54582
|
`);
|
|
54235
54583
|
if (!denyDelivered) {
|
|
54236
|
-
pendingInboundBuffer.push(
|
|
54584
|
+
pendingInboundBuffer.push(pending2.agent, denyInbound);
|
|
54237
54585
|
}
|
|
54238
54586
|
return;
|
|
54239
54587
|
}
|
|
54240
54588
|
if (action === "approve") {
|
|
54241
54589
|
if (VAULT_APPROVAL_AUTH_MODE === "telegram-id") {
|
|
54242
54590
|
const username = ctx.from?.username ?? ctx.from?.first_name ?? `id=${senderId}`;
|
|
54243
|
-
if (
|
|
54244
|
-
await ctx.api.editMessageText(
|
|
54591
|
+
if (pending2.card_message_id != null) {
|
|
54592
|
+
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
54593
|
}
|
|
54246
54594
|
await ctx.answerCallbackQuery({ text: "\u23F3 Minting grant\u2026" }).catch(() => {});
|
|
54247
|
-
await performVaultAccessApproval(ctx,
|
|
54595
|
+
await performVaultAccessApproval(ctx, pending2, stageId, senderId, { kind: "posture" });
|
|
54248
54596
|
return;
|
|
54249
54597
|
}
|
|
54250
|
-
const cached = vaultPassphraseCache.get(
|
|
54598
|
+
const cached = vaultPassphraseCache.get(pending2.chat_id);
|
|
54251
54599
|
if (!cached || cached.expiresAt <= Date.now()) {
|
|
54252
|
-
if (
|
|
54600
|
+
if (pending2.card_message_id == null) {
|
|
54253
54601
|
await ctx.answerCallbackQuery({ text: "Card missing \u2014 ask the agent to re-issue." }).catch(() => {});
|
|
54254
54602
|
return;
|
|
54255
54603
|
}
|
|
54256
|
-
const existing = pendingVaultOps.get(
|
|
54604
|
+
const existing = pendingVaultOps.get(pending2.chat_id);
|
|
54257
54605
|
const newItem = {
|
|
54258
54606
|
stageId,
|
|
54259
|
-
cardChatId:
|
|
54260
|
-
cardMessageId:
|
|
54607
|
+
cardChatId: pending2.chat_id,
|
|
54608
|
+
cardMessageId: pending2.card_message_id,
|
|
54261
54609
|
senderId
|
|
54262
54610
|
};
|
|
54263
54611
|
const items = existing?.kind === "passphrase-for-access-approve" ? [...existing.items.filter((it) => it.stageId !== stageId), newItem] : [newItem];
|
|
54264
|
-
pendingVaultOps.set(
|
|
54612
|
+
pendingVaultOps.set(pending2.chat_id, {
|
|
54265
54613
|
kind: "passphrase-for-access-approve",
|
|
54266
54614
|
items,
|
|
54267
54615
|
startedAt: existing?.kind === "passphrase-for-access-approve" ? existing.startedAt : Date.now()
|
|
54268
54616
|
});
|
|
54269
54617
|
const joiningBatch = items.length > 1;
|
|
54270
54618
|
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(
|
|
54619
|
+
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
54620
|
|
|
54273
54621
|
<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
54622
|
return;
|
|
54275
54623
|
}
|
|
54276
54624
|
await ctx.answerCallbackQuery({ text: "\u23F3 Minting grant\u2026" }).catch(() => {});
|
|
54277
|
-
await performVaultAccessApproval(ctx,
|
|
54625
|
+
await performVaultAccessApproval(ctx, pending2, stageId, senderId, { kind: "passphrase", passphrase: cached.passphrase });
|
|
54278
54626
|
return;
|
|
54279
54627
|
}
|
|
54280
54628
|
await ctx.answerCallbackQuery({ text: "Unknown action" }).catch(() => {});
|
|
@@ -54293,8 +54641,8 @@ async function handleVaultRequestSaveCallback(ctx, data) {
|
|
|
54293
54641
|
}
|
|
54294
54642
|
const action = parts[1];
|
|
54295
54643
|
const stageId = parts.slice(2).join(":");
|
|
54296
|
-
const
|
|
54297
|
-
if (!
|
|
54644
|
+
const pending2 = pendingVaultRequestSaves.get(stageId);
|
|
54645
|
+
if (!pending2) {
|
|
54298
54646
|
await ctx.answerCallbackQuery({ text: "Card expired \u2014 ask the agent to re-send." }).catch(() => {});
|
|
54299
54647
|
if (ctx.callbackQuery?.message) {
|
|
54300
54648
|
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 +54652,23 @@ async function handleVaultRequestSaveCallback(ctx, data) {
|
|
|
54304
54652
|
if (action === "discard") {
|
|
54305
54653
|
pendingVaultRequestSaves.delete(stageId);
|
|
54306
54654
|
await ctx.answerCallbackQuery({ text: "\uD83D\uDEAB Discarded" }).catch(() => {});
|
|
54307
|
-
if (
|
|
54308
|
-
await ctx.api.editMessageText(
|
|
54655
|
+
if (pending2.card_message_id != null) {
|
|
54656
|
+
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
54657
|
}
|
|
54310
54658
|
const discardInbound = buildVaultSaveDiscardedInbound({
|
|
54311
|
-
ctx: { agent:
|
|
54659
|
+
ctx: { agent: pending2.agent, key: pending2.key, chat_id: pending2.chat_id },
|
|
54312
54660
|
stageId,
|
|
54313
54661
|
operatorId: senderId
|
|
54314
54662
|
});
|
|
54315
|
-
const dDelivered = ipcServer.sendToAgent(
|
|
54316
|
-
process.stderr.write(`telegram gateway: vault_save_discarded injection agent=${
|
|
54663
|
+
const dDelivered = ipcServer.sendToAgent(pending2.agent, discardInbound);
|
|
54664
|
+
process.stderr.write(`telegram gateway: vault_save_discarded injection agent=${pending2.agent} key=${pending2.key} stage=${stageId} delivered=${dDelivered}
|
|
54317
54665
|
`);
|
|
54318
54666
|
if (!dDelivered)
|
|
54319
|
-
pendingInboundBuffer.push(
|
|
54667
|
+
pendingInboundBuffer.push(pending2.agent, discardInbound);
|
|
54320
54668
|
return;
|
|
54321
54669
|
}
|
|
54322
54670
|
if (action === "rename") {
|
|
54323
|
-
pendingVaultOps.set(
|
|
54671
|
+
pendingVaultOps.set(pending2.chat_id, {
|
|
54324
54672
|
kind: "rename-vault-save",
|
|
54325
54673
|
stageId,
|
|
54326
54674
|
startedAt: Date.now()
|
|
@@ -54329,7 +54677,7 @@ async function handleVaultRequestSaveCallback(ctx, data) {
|
|
|
54329
54677
|
const baseText = sourceMsg && "text" in sourceMsg && sourceMsg.text ? escapeHtmlForTg(sourceMsg.text) : "";
|
|
54330
54678
|
const statusLine = `
|
|
54331
54679
|
|
|
54332
|
-
\u270F\uFE0F <b>Rename mode</b> \u2014 send the new key name as your next message. ` + `The current proposed key is <code>${escapeHtmlForTg(
|
|
54680
|
+
\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
54681
|
await finalizeCallback(ctx, {
|
|
54334
54682
|
ackText: "Send the new key name as your next message.",
|
|
54335
54683
|
newText: baseText ? `${baseText}${statusLine}` : statusLine,
|
|
@@ -54339,22 +54687,22 @@ async function handleVaultRequestSaveCallback(ctx, data) {
|
|
|
54339
54687
|
}
|
|
54340
54688
|
if (action === "save") {
|
|
54341
54689
|
await ctx.answerCallbackQuery({ text: "\u23F3 Saving\u2026" }).catch(() => {});
|
|
54342
|
-
const cached = vaultPassphraseCache.get(
|
|
54690
|
+
const cached = vaultPassphraseCache.get(pending2.chat_id);
|
|
54343
54691
|
if (!cached || cached.expiresAt <= Date.now()) {
|
|
54344
|
-
if (
|
|
54345
|
-
await ctx.api.editMessageText(
|
|
54692
|
+
if (pending2.card_message_id != null) {
|
|
54693
|
+
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
54694
|
}
|
|
54347
54695
|
pendingVaultRequestSaves.delete(stageId);
|
|
54348
54696
|
return;
|
|
54349
54697
|
}
|
|
54350
|
-
const write = defaultVaultWrite(
|
|
54698
|
+
const write = defaultVaultWrite(pending2.key, pending2.value, cached.passphrase);
|
|
54351
54699
|
if (!write.ok) {
|
|
54352
54700
|
const parsed = parseVaultCliError(write.output);
|
|
54353
|
-
const rendered = renderVaultCliError(parsed, { verb: "save", key:
|
|
54701
|
+
const rendered = renderVaultCliError(parsed, { verb: "save", key: pending2.key });
|
|
54354
54702
|
const body = rendered.suppressRaw ? rendered.html : `\u26A0\uFE0F vault write failed:
|
|
54355
54703
|
<pre>${escapeHtmlForTg(write.output)}</pre>`;
|
|
54356
|
-
if (
|
|
54357
|
-
await ctx.api.editMessageText(
|
|
54704
|
+
if (pending2.card_message_id != null) {
|
|
54705
|
+
await ctx.api.editMessageText(pending2.chat_id, pending2.card_message_id, `${body}
|
|
54358
54706
|
|
|
54359
54707
|
<i>Tap a fresh card after fixing the underlying issue.</i>`, { parse_mode: "HTML", reply_markup: { inline_keyboard: [] } }).catch(() => {});
|
|
54360
54708
|
}
|
|
@@ -54362,33 +54710,33 @@ async function handleVaultRequestSaveCallback(ctx, data) {
|
|
|
54362
54710
|
const failReason = (write.output || "vault write error").split(`
|
|
54363
54711
|
`)[0].slice(0, 200);
|
|
54364
54712
|
const failInbound = buildVaultSaveFailedInbound({
|
|
54365
|
-
ctx: { agent:
|
|
54713
|
+
ctx: { agent: pending2.agent, key: pending2.key, chat_id: pending2.chat_id },
|
|
54366
54714
|
stageId,
|
|
54367
54715
|
operatorId: senderId,
|
|
54368
54716
|
reason: failReason
|
|
54369
54717
|
});
|
|
54370
|
-
const fDelivered = ipcServer.sendToAgent(
|
|
54371
|
-
process.stderr.write(`telegram gateway: vault_save_failed injection agent=${
|
|
54718
|
+
const fDelivered = ipcServer.sendToAgent(pending2.agent, failInbound);
|
|
54719
|
+
process.stderr.write(`telegram gateway: vault_save_failed injection agent=${pending2.agent} key=${pending2.key} stage=${stageId} delivered=${fDelivered}
|
|
54372
54720
|
`);
|
|
54373
54721
|
if (!fDelivered)
|
|
54374
|
-
pendingInboundBuffer.push(
|
|
54722
|
+
pendingInboundBuffer.push(pending2.agent, failInbound);
|
|
54375
54723
|
return;
|
|
54376
54724
|
}
|
|
54377
54725
|
pendingVaultRequestSaves.delete(stageId);
|
|
54378
|
-
if (
|
|
54379
|
-
await ctx.api.editMessageText(
|
|
54380
|
-
<i>The agent can now reference this as <code>vault:${escapeHtmlForTg(
|
|
54726
|
+
if (pending2.card_message_id != null) {
|
|
54727
|
+
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>)
|
|
54728
|
+
<i>The agent can now reference this as <code>vault:${escapeHtmlForTg(pending2.key)}</code>.</i>`, { parse_mode: "HTML", reply_markup: { inline_keyboard: [] } }).catch(() => {});
|
|
54381
54729
|
}
|
|
54382
54730
|
const okInbound = buildVaultSaveCompletedInbound({
|
|
54383
|
-
ctx: { agent:
|
|
54731
|
+
ctx: { agent: pending2.agent, key: pending2.key, chat_id: pending2.chat_id },
|
|
54384
54732
|
stageId,
|
|
54385
54733
|
operatorId: senderId
|
|
54386
54734
|
});
|
|
54387
|
-
const okDelivered = ipcServer.sendToAgent(
|
|
54388
|
-
process.stderr.write(`telegram gateway: vault_save_completed injection agent=${
|
|
54735
|
+
const okDelivered = ipcServer.sendToAgent(pending2.agent, okInbound);
|
|
54736
|
+
process.stderr.write(`telegram gateway: vault_save_completed injection agent=${pending2.agent} key=${pending2.key} stage=${stageId} delivered=${okDelivered}
|
|
54389
54737
|
`);
|
|
54390
54738
|
if (!okDelivered)
|
|
54391
|
-
pendingInboundBuffer.push(
|
|
54739
|
+
pendingInboundBuffer.push(pending2.agent, okInbound);
|
|
54392
54740
|
return;
|
|
54393
54741
|
}
|
|
54394
54742
|
await ctx.answerCallbackQuery({ text: "Unknown action" }).catch(() => {});
|
|
@@ -55650,6 +55998,35 @@ bot.on("callback_query:data", async (ctx) => {
|
|
|
55650
55998
|
await handleApprovalCallback2(ctx, data);
|
|
55651
55999
|
return;
|
|
55652
56000
|
}
|
|
56001
|
+
if (data.startsWith("cfg:")) {
|
|
56002
|
+
const access2 = loadAccess();
|
|
56003
|
+
const senderId2 = String(ctx.from?.id ?? "");
|
|
56004
|
+
if (!access2.allowFrom.includes(senderId2)) {
|
|
56005
|
+
await ctx.answerCallbackQuery({ text: "Not authorized." });
|
|
56006
|
+
return;
|
|
56007
|
+
}
|
|
56008
|
+
const { parseConfigApprovalCallback: parseConfigApprovalCallback2, resolvePendingConfigApproval: resolvePendingConfigApproval2 } = await Promise.resolve().then(() => (init_config_approval_handler(), exports_config_approval_handler));
|
|
56009
|
+
const parsed = parseConfigApprovalCallback2(data);
|
|
56010
|
+
if (parsed === null) {
|
|
56011
|
+
await ctx.answerCallbackQuery({ text: "Malformed callback." });
|
|
56012
|
+
return;
|
|
56013
|
+
}
|
|
56014
|
+
const resolved = await resolvePendingConfigApproval2(parsed.requestId, parsed.choice, {
|
|
56015
|
+
editCard: async (args) => {
|
|
56016
|
+
try {
|
|
56017
|
+
await robustApiCall(() => bot.api.editMessageText(args.chatId, args.messageId, args.text, {
|
|
56018
|
+
parse_mode: "HTML"
|
|
56019
|
+
}));
|
|
56020
|
+
} catch {}
|
|
56021
|
+
},
|
|
56022
|
+
log: (m2) => process.stderr.write(`telegram gateway: config-approval cb \u2014 ${m2}
|
|
56023
|
+
`)
|
|
56024
|
+
});
|
|
56025
|
+
await ctx.answerCallbackQuery({
|
|
56026
|
+
text: resolved ? parsed.choice === "approve" ? "Approving\u2026" : "Denied" : "Already resolved."
|
|
56027
|
+
});
|
|
56028
|
+
return;
|
|
56029
|
+
}
|
|
55653
56030
|
if (data.startsWith("drvpick:")) {
|
|
55654
56031
|
const access2 = loadAccess();
|
|
55655
56032
|
const senderId2 = String(ctx.from?.id ?? "");
|
|
@@ -56490,7 +56867,7 @@ async function handleMessageReaction(ctx) {
|
|
|
56490
56867
|
`);
|
|
56491
56868
|
return;
|
|
56492
56869
|
}
|
|
56493
|
-
const
|
|
56870
|
+
const pending2 = {
|
|
56494
56871
|
targetMessageId: message_id,
|
|
56495
56872
|
emoji,
|
|
56496
56873
|
action,
|
|
@@ -56500,7 +56877,7 @@ async function handleMessageReaction(ctx) {
|
|
|
56500
56877
|
user: reacter.first_name ?? reacter.username ?? String(reacter.id),
|
|
56501
56878
|
...typeof update.message_thread_id === "number" ? { threadId: update.message_thread_id } : {}
|
|
56502
56879
|
};
|
|
56503
|
-
getReactionDebounce().enqueue(update.chat.id,
|
|
56880
|
+
getReactionDebounce().enqueue(update.chat.id, pending2);
|
|
56504
56881
|
} catch (err) {
|
|
56505
56882
|
process.stderr.write(`telegram gateway: message_reaction handler error: ${err}
|
|
56506
56883
|
`);
|
|
@@ -56969,16 +57346,14 @@ var didOneTimeSetup = false;
|
|
|
56969
57346
|
`);
|
|
56970
57347
|
}
|
|
56971
57348
|
},
|
|
56972
|
-
onFinish: ({ agentId, outcome,
|
|
56973
|
-
let
|
|
56974
|
-
let chatId = "";
|
|
57349
|
+
onFinish: ({ agentId, outcome, description, resultText }) => {
|
|
57350
|
+
let fleetChatId = "";
|
|
56975
57351
|
let isBackground = false;
|
|
56976
57352
|
try {
|
|
56977
57353
|
const fleets = progressDriver?.peekAllFleets() ?? [];
|
|
56978
57354
|
for (const f of fleets) {
|
|
56979
57355
|
if (f.fleet.has(agentId)) {
|
|
56980
|
-
|
|
56981
|
-
chatId = f.chatId ?? "";
|
|
57356
|
+
fleetChatId = f.chatId ?? "";
|
|
56982
57357
|
break;
|
|
56983
57358
|
}
|
|
56984
57359
|
}
|
|
@@ -56990,7 +57365,25 @@ var didOneTimeSetup = false;
|
|
|
56990
57365
|
isBackground = row.background === 1;
|
|
56991
57366
|
} catch {}
|
|
56992
57367
|
}
|
|
56993
|
-
const
|
|
57368
|
+
const decision = decideSubagentHandback({
|
|
57369
|
+
handbackEnvValue: process.env.SWITCHROOM_SUBAGENT_HANDBACK,
|
|
57370
|
+
outcome,
|
|
57371
|
+
isBackground,
|
|
57372
|
+
fleetChatId,
|
|
57373
|
+
ownerChatId: loadAccess().allowFrom[0] ?? "",
|
|
57374
|
+
taskDescription: description,
|
|
57375
|
+
resultText
|
|
57376
|
+
});
|
|
57377
|
+
if (!decision.deliver) {
|
|
57378
|
+
if (decision.reason === "no-chat") {
|
|
57379
|
+
process.stderr.write(`telegram gateway: subagent-handback ${agentId} \u2014 no chat to deliver to; skipped
|
|
57380
|
+
`);
|
|
57381
|
+
}
|
|
57382
|
+
return;
|
|
57383
|
+
}
|
|
57384
|
+
pendingInboundBuffer.push(process.env.SWITCHROOM_AGENT_NAME ?? "", decision.inbound);
|
|
57385
|
+
process.stderr.write(`telegram gateway: subagent-handback queued agent=${agentId} outcome=${outcome} chat=${decision.chatId} resultChars=${resultText.length}
|
|
57386
|
+
`);
|
|
56994
57387
|
}
|
|
56995
57388
|
});
|
|
56996
57389
|
process.stderr.write(`telegram gateway: subagent-watcher active
|