switchroom 0.15.19 → 0.15.21
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 +519 -336
- package/package.json +1 -1
- package/profiles/_shared/agent-self-service.md.hbs +24 -15
- package/telegram-plugin/dist/gateway/gateway.js +217 -88
- package/telegram-plugin/gateway/effort-command.ts +56 -47
- package/telegram-plugin/gateway/gateway.ts +178 -39
- package/telegram-plugin/gateway/grant-restart.ts +30 -0
- package/telegram-plugin/tests/effort-command.test.ts +43 -34
- package/telegram-plugin/tests/grant-restart.test.ts +38 -0
- package/telegram-plugin/tests/vault-grant-auto-resume.test.ts +15 -4
- package/telegram-plugin/tests/vault-resume-turn-gated.test.ts +97 -0
- package/telegram-plugin/welcome-text.ts +2 -1
package/package.json
CHANGED
|
@@ -38,22 +38,31 @@ tools is to let you do the edit yourself.
|
|
|
38
38
|
derived from the entry content is assigned.
|
|
39
39
|
|
|
40
40
|
**Mind the cost — pick the cheapest tier that does the job:**
|
|
41
|
-
- *Default (no `model`)* —
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
(summarise a feed, format a
|
|
49
|
-
|
|
50
|
-
|
|
41
|
+
- *Default (no `model`)* — a **frequent** cron (every ≤60min) is auto-routed
|
|
42
|
+
to a **cheap, minimal-context cron session** (Tier 1: a fresh Sonnet that
|
|
43
|
+
still shares your memory + tools but drops your accumulated conversation
|
|
44
|
+
context). A **daily/weekly** cron defaults to a **full turn in your live
|
|
45
|
+
session** (Tier 2: your model, your whole context). So routine frequent
|
|
46
|
+
checks are already cheap without you doing anything.
|
|
47
|
+
- *`model: "sonnet"` / `context: "fresh"`* — force the cheap Tier-1 session
|
|
48
|
+
even for a **daily/weekly** self-contained job (summarise a feed, format a
|
|
49
|
+
digest) — overrides the cadence default. *`context: "agent"`* does the
|
|
50
|
+
opposite: pins a fire to your full live session when it genuinely needs your
|
|
51
|
+
accumulated conversation context (this always wins).
|
|
52
|
+
- *"Post/send a FIXED thing on a schedule"* (a set reminder message, a webhook
|
|
53
|
+
ping — text fully determined, no thinking) — ask the **operator** for a
|
|
54
|
+
**`kind: action`**: it runs **model-free, zero tokens** (no session at all),
|
|
55
|
+
posting your fixed/templated text or firing a fixed request. The cheapest
|
|
56
|
+
tier there is.
|
|
51
57
|
- *"Only act when X changes"* — don't poll with a frequent prompt cron (every
|
|
52
|
-
fire is a wasted turn when nothing changed). Ask the **operator**
|
|
53
|
-
|
|
54
|
-
reaction-triggered work, **`reaction_dispatch`** (an
|
|
55
|
-
you instantly — zero polling).
|
|
56
|
-
|
|
58
|
+
fire is a wasted turn when nothing changed). Ask the **operator** for a
|
|
59
|
+
**`kind: poll`** (model-free check, e.g. a webpage/API — only a *change*
|
|
60
|
+
wakes you) or, for reaction-triggered work, **`reaction_dispatch`** (an
|
|
61
|
+
emoji reaction wakes you instantly — zero polling).
|
|
62
|
+
- `kind: action` / `kind: poll` / `reaction_dispatch` need an operator config
|
|
63
|
+
commit (egress / identity gates), so **request** them rather than authoring
|
|
64
|
+
them yourself — you can only self-author plain prompt crons (plus the
|
|
65
|
+
`model`/`context` tier hints).
|
|
57
66
|
|
|
58
67
|
- **`schedule_remove(name | cron_hash)`** — delete by `name` (the slug from
|
|
59
68
|
add) or by 12-hex `cron_hash` (shown in `cron_list` output). Both
|
|
@@ -43177,6 +43177,7 @@ function switchroomHelpText(agentName3) {
|
|
|
43177
43177
|
`<code>/update</code> \u2014 dry-run plan; <code>/update apply</code> \u2014 actually pull images, reconcile, restart`,
|
|
43178
43178
|
`<code>/restart [name|all]</code> \u2014 bounce agent (drains in-flight turn by default)`,
|
|
43179
43179
|
`<code>/version</code> \u2014 show versions + running agent health summary`,
|
|
43180
|
+
`<code>/whoami</code> \u2014 this agent's sandbox: tools, MCP, vault key-names, powers`,
|
|
43180
43181
|
``,
|
|
43181
43182
|
`<b>Auth & config</b>`,
|
|
43182
43183
|
`<code>/auth</code> \u2014 auth status or actions`,
|
|
@@ -45453,43 +45454,32 @@ async function handleEffortCommand(parsed, deps) {
|
|
|
45453
45454
|
const verbHtml = `<code>/effort ${deps.escapeHtml(parsed.level)}</code>`;
|
|
45454
45455
|
let result;
|
|
45455
45456
|
try {
|
|
45456
|
-
result = await deps.
|
|
45457
|
+
result = await deps.applyEffort(deps.getAgentName(), parsed.level);
|
|
45457
45458
|
} catch (err) {
|
|
45458
45459
|
const msg = err instanceof Error ? err.message : String(err);
|
|
45459
|
-
return { text: `\u274c ${verbHtml} \u2014
|
|
45460
|
+
return { text: `\u274c ${verbHtml} \u2014 failed: ${deps.escapeHtml(msg)}`, html: true };
|
|
45460
45461
|
}
|
|
45461
|
-
|
|
45462
|
-
|
|
45463
|
-
|
|
45464
|
-
|
|
45465
|
-
|
|
45466
|
-
|
|
45467
|
-
|
|
45468
|
-
|
|
45469
|
-
|
|
45470
|
-
|
|
45471
|
-
|
|
45462
|
+
return { text: applyResultText(parsed.level, result, deps), html: true };
|
|
45463
|
+
}
|
|
45464
|
+
function applyResultText(level, result, deps) {
|
|
45465
|
+
const verbHtml = `<code>/effort ${deps.escapeHtml(level)}</code>`;
|
|
45466
|
+
if (result.ok) {
|
|
45467
|
+
const lines = [`\u2705 ${verbHtml} \u2014 ${deps.escapeHtml(result.output)}`];
|
|
45468
|
+
if (result.confirmed) {
|
|
45469
|
+
lines.push("<i>Switched mid-conversation \u2014 your next turn re-reads the cached history (slower, one time).</i>");
|
|
45470
|
+
}
|
|
45471
|
+
lines.push(PERSIST_NOTE2);
|
|
45472
|
+
return lines.join(`
|
|
45473
|
+
`);
|
|
45472
45474
|
}
|
|
45473
|
-
if (result.
|
|
45474
|
-
return
|
|
45475
|
-
text: [
|
|
45476
|
-
`${verbHtml} \u2014 sent, but no response captured. The agent may be mid-turn; check <code>/inject /status</code> to confirm the active effort.`,
|
|
45477
|
-
PERSIST_NOTE2
|
|
45478
|
-
].join(`
|
|
45479
|
-
`),
|
|
45480
|
-
html: true
|
|
45481
|
-
};
|
|
45475
|
+
if (result.reason === "session_missing") {
|
|
45476
|
+
return "\u274c tmux session not found \u2014 the agent must be running under the tmux supervisor (the default). Remove <code>experimental.legacy_pty: true</code> if set.";
|
|
45482
45477
|
}
|
|
45483
|
-
if (result.
|
|
45484
|
-
|
|
45485
|
-
|
|
45486
|
-
html: true
|
|
45487
|
-
};
|
|
45478
|
+
if (result.reason === "confirm_failed") {
|
|
45479
|
+
const wedged = result.wedged ? " The confirmation prompt may still be open on the pane \u2014 check it." : " The change was cancelled and the pane left as it was.";
|
|
45480
|
+
return `\u274c ${verbHtml} \u2014 couldn't confirm the switch.${wedged}`;
|
|
45488
45481
|
}
|
|
45489
|
-
return {
|
|
45490
|
-
text: `\u274c ${verbHtml} \u2014 ${deps.escapeHtml(result.errorMessage ?? "inject failed")}`,
|
|
45491
|
-
html: true
|
|
45492
|
-
};
|
|
45482
|
+
return `\u274c ${verbHtml} \u2014 sent, but couldn't confirm it applied. The agent may be mid-turn; check <code>/inject /status</code>.`;
|
|
45493
45483
|
}
|
|
45494
45484
|
var EFFORT_CALLBACK_PREFIX = "eff:";
|
|
45495
45485
|
var EFFORT_CALLBACK_SELECT = "eff:s:";
|
|
@@ -45530,18 +45520,20 @@ async function handleEffortMenuCallback(data, deps) {
|
|
|
45530
45520
|
let banner;
|
|
45531
45521
|
let selected;
|
|
45532
45522
|
try {
|
|
45533
|
-
const result = await deps.
|
|
45534
|
-
if (result.
|
|
45535
|
-
banner = `\u2705 Effort \u2192 <code>${deps.escapeHtml(level)}</code> for this session`;
|
|
45523
|
+
const result = await deps.applyEffort(deps.getAgentName(), level);
|
|
45524
|
+
if (result.ok) {
|
|
45525
|
+
banner = result.confirmed ? `\u2705 Effort \u2192 <code>${deps.escapeHtml(level)}</code> (mid-conversation: next turn re-reads history)` : `\u2705 Effort \u2192 <code>${deps.escapeHtml(level)}</code> for this session`;
|
|
45536
45526
|
selected = level;
|
|
45537
|
-
} else if (result.
|
|
45527
|
+
} else if (result.reason === "session_missing") {
|
|
45538
45528
|
banner = "\u274c tmux session not found \u2014 is the agent running under the supervisor?";
|
|
45529
|
+
} else if (result.reason === "confirm_failed") {
|
|
45530
|
+
banner = result.wedged ? "\u26a0\ufe0f Couldn\u2019t confirm the switch \u2014 the prompt may still be open on the pane." : "\u274c Couldn\u2019t confirm the switch \u2014 cancelled, effort unchanged.";
|
|
45539
45531
|
} else {
|
|
45540
|
-
banner =
|
|
45532
|
+
banner = "\u274c Sent, but couldn\u2019t confirm it applied (agent may be mid-turn).";
|
|
45541
45533
|
}
|
|
45542
45534
|
} catch (err) {
|
|
45543
45535
|
const msg = err instanceof Error ? err.message : String(err);
|
|
45544
|
-
banner = `\u274c
|
|
45536
|
+
banner = `\u274c failed: ${deps.escapeHtml(msg)}`;
|
|
45545
45537
|
}
|
|
45546
45538
|
const menu = buildEffortMenu(deps, selected);
|
|
45547
45539
|
return {
|
|
@@ -45551,6 +45543,80 @@ ${menu.text}` },
|
|
|
45551
45543
|
};
|
|
45552
45544
|
}
|
|
45553
45545
|
|
|
45546
|
+
// ../src/agents/effort-picker.ts
|
|
45547
|
+
var CONFIRM_RE = /Change effort level\?/i;
|
|
45548
|
+
function appliedRe(level) {
|
|
45549
|
+
return new RegExp(`${level}\\s*\\u00b7\\s*/effort`);
|
|
45550
|
+
}
|
|
45551
|
+
function applyLine(pane, level) {
|
|
45552
|
+
const re = new RegExp(`Set effort level to ${level}\\b.*`, "i");
|
|
45553
|
+
for (const line of pane.split(`
|
|
45554
|
+
`)) {
|
|
45555
|
+
const m = line.match(re);
|
|
45556
|
+
if (m)
|
|
45557
|
+
return m[0].trim();
|
|
45558
|
+
}
|
|
45559
|
+
return `Set effort level to ${level}`;
|
|
45560
|
+
}
|
|
45561
|
+
var realSleep2 = (ms) => new Promise((r) => setTimeout(r, ms));
|
|
45562
|
+
async function applyEffort(agentName3, level, opts = {}) {
|
|
45563
|
+
const runner = opts._runner ?? makeTmuxRunner2(opts.tmuxBin ?? "tmux");
|
|
45564
|
+
const socket = opts.socketName ?? `switchroom-${agentName3}`;
|
|
45565
|
+
const session = opts.sessionName ?? agentName3;
|
|
45566
|
+
const stepMs = opts.stepMs ?? 600;
|
|
45567
|
+
const timeoutMs = opts.timeoutMs ?? 1e4;
|
|
45568
|
+
const sleep2 = opts._sleep ?? realSleep2;
|
|
45569
|
+
const log = opts._log ?? ((line) => process.stderr.write(`${line}
|
|
45570
|
+
`));
|
|
45571
|
+
if (!runner.hasSession(socket, session)) {
|
|
45572
|
+
return { ok: false, reason: "session_missing" };
|
|
45573
|
+
}
|
|
45574
|
+
return withPaneLock(`${socket}:${session}`, async () => {
|
|
45575
|
+
const startedAt = Date.now();
|
|
45576
|
+
const expired2 = () => Date.now() - startedAt >= timeoutMs;
|
|
45577
|
+
try {
|
|
45578
|
+
runner.send(socket, session, ["send-keys", "-l", `/effort ${level}`]);
|
|
45579
|
+
runner.send(socket, session, ["send-keys", "Enter"]);
|
|
45580
|
+
let confirmed = false;
|
|
45581
|
+
let confirmKeys = 0;
|
|
45582
|
+
while (!expired2()) {
|
|
45583
|
+
await sleep2(stepMs);
|
|
45584
|
+
const pane = runner.capture(socket, session) ?? "";
|
|
45585
|
+
if (CONFIRM_RE.test(pane)) {
|
|
45586
|
+
if (confirmKeys >= 2) {
|
|
45587
|
+
runner.send(socket, session, ["send-keys", "Escape"]);
|
|
45588
|
+
await sleep2(stepMs);
|
|
45589
|
+
const after = runner.capture(socket, session) ?? "";
|
|
45590
|
+
log(`effort-picker: confirm modal would not dismiss for ${agentName3} ` + `(socket=${socket}) \u2014 cancelled`);
|
|
45591
|
+
return { ok: false, reason: "confirm_failed", wedged: CONFIRM_RE.test(after) };
|
|
45592
|
+
}
|
|
45593
|
+
runner.send(socket, session, ["send-keys", "Enter"]);
|
|
45594
|
+
confirmed = true;
|
|
45595
|
+
confirmKeys += 1;
|
|
45596
|
+
continue;
|
|
45597
|
+
}
|
|
45598
|
+
if (appliedRe(level).test(pane)) {
|
|
45599
|
+
return { ok: true, level, confirmed, output: applyLine(pane, level) };
|
|
45600
|
+
}
|
|
45601
|
+
}
|
|
45602
|
+
const final = runner.capture(socket, session) ?? "";
|
|
45603
|
+
if (CONFIRM_RE.test(final)) {
|
|
45604
|
+
runner.send(socket, session, ["send-keys", "Escape"]);
|
|
45605
|
+
log(`effort-picker: timeout with modal open for ${agentName3} \u2014 cancelled`);
|
|
45606
|
+
return { ok: false, reason: "confirm_failed", wedged: true };
|
|
45607
|
+
}
|
|
45608
|
+
return { ok: false, reason: "apply_unverified" };
|
|
45609
|
+
} finally {
|
|
45610
|
+
try {
|
|
45611
|
+
const pane = runner.capture(socket, session) ?? "";
|
|
45612
|
+
if (CONFIRM_RE.test(pane)) {
|
|
45613
|
+
runner.send(socket, session, ["send-keys", "Escape"]);
|
|
45614
|
+
}
|
|
45615
|
+
} catch {}
|
|
45616
|
+
}
|
|
45617
|
+
});
|
|
45618
|
+
}
|
|
45619
|
+
|
|
45554
45620
|
// ../src/config/loader.ts
|
|
45555
45621
|
init_dist();
|
|
45556
45622
|
init_zod();
|
|
@@ -53646,6 +53712,15 @@ function readBashCommand(inputPreview) {
|
|
|
53646
53712
|
}
|
|
53647
53713
|
}
|
|
53648
53714
|
|
|
53715
|
+
// gateway/grant-restart.ts
|
|
53716
|
+
function grantRestartDecision(opts) {
|
|
53717
|
+
if ((opts.killSwitch ?? "") === "0")
|
|
53718
|
+
return "disabled";
|
|
53719
|
+
if (!opts.selfAgent || opts.selfAgent !== opts.agentName)
|
|
53720
|
+
return "disabled";
|
|
53721
|
+
return opts.turnInFlight ? "deferred" : "now";
|
|
53722
|
+
}
|
|
53723
|
+
|
|
53649
53724
|
// permission-diff.ts
|
|
53650
53725
|
var TARGET_HEADER_A = "--- a/switchroom.yaml";
|
|
53651
53726
|
var TARGET_HEADER_B = "+++ b/switchroom.yaml";
|
|
@@ -54317,10 +54392,10 @@ function readTurnActiveMarkerAgeMs(stateDir, now) {
|
|
|
54317
54392
|
}
|
|
54318
54393
|
|
|
54319
54394
|
// ../src/build-info.ts
|
|
54320
|
-
var VERSION = "0.15.
|
|
54321
|
-
var COMMIT_SHA = "
|
|
54322
|
-
var COMMIT_DATE = "2026-06-
|
|
54323
|
-
var LATEST_PR =
|
|
54395
|
+
var VERSION = "0.15.21";
|
|
54396
|
+
var COMMIT_SHA = "36706e85";
|
|
54397
|
+
var COMMIT_DATE = "2026-06-14T01:34:05Z";
|
|
54398
|
+
var LATEST_PR = 2345;
|
|
54324
54399
|
var COMMITS_AHEAD_OF_TAG = 0;
|
|
54325
54400
|
|
|
54326
54401
|
// gateway/boot-version.ts
|
|
@@ -55819,6 +55894,23 @@ function formatFeedElapsed(ms) {
|
|
|
55819
55894
|
function turnInFlightForGate() {
|
|
55820
55895
|
return isDeliveryCutoverEnabled() ? isMachineInTurn() : claudeBusyKeys.size > 0;
|
|
55821
55896
|
}
|
|
55897
|
+
function deliverResumeSyntheticOrBuffer(agent, inbound) {
|
|
55898
|
+
const decision = decideInboundDelivery({
|
|
55899
|
+
turnInFlight: turnInFlightForGate(),
|
|
55900
|
+
isSteering: false,
|
|
55901
|
+
isInterrupt: false
|
|
55902
|
+
});
|
|
55903
|
+
if (decision === "buffer-until-idle") {
|
|
55904
|
+
pendingInboundBuffer.push(agent, inbound);
|
|
55905
|
+
return false;
|
|
55906
|
+
}
|
|
55907
|
+
const delivered = ipcServer.sendToAgent(agent, inbound);
|
|
55908
|
+
if (delivered)
|
|
55909
|
+
markClaudeBusyForInbound(inbound);
|
|
55910
|
+
else
|
|
55911
|
+
pendingInboundBuffer.push(agent, inbound);
|
|
55912
|
+
return delivered;
|
|
55913
|
+
}
|
|
55822
55914
|
var pendingRestarts = new Map;
|
|
55823
55915
|
var lastSessionActiveFile = null;
|
|
55824
55916
|
var compactState = initialCompactState();
|
|
@@ -59301,11 +59393,7 @@ async function captureProvidedSecret(ctx, chat_id, msgId, value) {
|
|
|
59301
59393
|
stage_id: armed.stageId
|
|
59302
59394
|
}
|
|
59303
59395
|
};
|
|
59304
|
-
|
|
59305
|
-
if (fdelivered)
|
|
59306
|
-
markClaudeBusyForInbound(failMsg);
|
|
59307
|
-
else
|
|
59308
|
-
pendingInboundBuffer.push(armed.agent, failMsg);
|
|
59396
|
+
deliverResumeSyntheticOrBuffer(armed.agent, failMsg);
|
|
59309
59397
|
return true;
|
|
59310
59398
|
}
|
|
59311
59399
|
await switchroomReply(ctx, `\u2705 saved as <code>vault:${escapeHtmlForTg(armed.key)}</code> (masked: <code>${escapeHtmlForTg(maskToken2(value))}</code>). The agent can now reference it.`, { html: true });
|
|
@@ -59327,11 +59415,7 @@ async function captureProvidedSecret(ctx, chat_id, msgId, value) {
|
|
|
59327
59415
|
stage_id: armed.stageId
|
|
59328
59416
|
}
|
|
59329
59417
|
};
|
|
59330
|
-
const delivered =
|
|
59331
|
-
if (delivered)
|
|
59332
|
-
markClaudeBusyForInbound(synthetic);
|
|
59333
|
-
else
|
|
59334
|
-
pendingInboundBuffer.push(armed.agent, synthetic);
|
|
59418
|
+
const delivered = deliverResumeSyntheticOrBuffer(armed.agent, synthetic);
|
|
59335
59419
|
process.stderr.write(`telegram gateway: secret_provided injection agent=${armed.agent} key=${armed.key} stage=${armed.stageId} delivered=${delivered}
|
|
59336
59420
|
`);
|
|
59337
59421
|
return true;
|
|
@@ -59393,11 +59477,7 @@ async function handleSecretRequestCallback(ctx, data) {
|
|
|
59393
59477
|
stage_id: stageId
|
|
59394
59478
|
}
|
|
59395
59479
|
};
|
|
59396
|
-
|
|
59397
|
-
if (delivered)
|
|
59398
|
-
markClaudeBusyForInbound(synthetic);
|
|
59399
|
-
else
|
|
59400
|
-
pendingInboundBuffer.push(pending2.agent, synthetic);
|
|
59480
|
+
deliverResumeSyntheticOrBuffer(pending2.agent, synthetic);
|
|
59401
59481
|
return;
|
|
59402
59482
|
}
|
|
59403
59483
|
await ctx.answerCallbackQuery().catch(() => {});
|
|
@@ -61772,6 +61852,26 @@ async function sweepBeforeSelfRestart() {
|
|
|
61772
61852
|
`);
|
|
61773
61853
|
}
|
|
61774
61854
|
}
|
|
61855
|
+
function scheduleGrantRestart(agentName3, chatId, threadId, reason) {
|
|
61856
|
+
const decision = grantRestartDecision({
|
|
61857
|
+
killSwitch: process.env.SWITCHROOM_AUTORESTART_ON_GRANT,
|
|
61858
|
+
selfAgent: process.env.SWITCHROOM_AGENT_NAME,
|
|
61859
|
+
agentName: agentName3,
|
|
61860
|
+
turnInFlight: turnInFlightForGate()
|
|
61861
|
+
});
|
|
61862
|
+
if (decision === "disabled")
|
|
61863
|
+
return decision;
|
|
61864
|
+
if (chatId != null) {
|
|
61865
|
+
writeRestartMarker({ chat_id: String(chatId), thread_id: threadId ?? null, ack_message_id: null, ts: Date.now() });
|
|
61866
|
+
}
|
|
61867
|
+
stampUserRestartReason(reason);
|
|
61868
|
+
if (decision === "deferred") {
|
|
61869
|
+
pendingRestarts.set(agentName3, Date.now());
|
|
61870
|
+
} else {
|
|
61871
|
+
sweepBeforeSelfRestart().finally(() => triggerSelfRestart(agentName3, reason, 1500));
|
|
61872
|
+
}
|
|
61873
|
+
return decision;
|
|
61874
|
+
}
|
|
61775
61875
|
function formatAuthOutputForTelegram(output) {
|
|
61776
61876
|
const trimmed = stripAnsi2(output).trim();
|
|
61777
61877
|
const url = trimmed.match(/https:\/\/\S+/)?.[0] ?? null;
|
|
@@ -62304,14 +62404,13 @@ bot.command("model", async (ctx) => {
|
|
|
62304
62404
|
});
|
|
62305
62405
|
function buildEffortDeps() {
|
|
62306
62406
|
return {
|
|
62307
|
-
|
|
62407
|
+
applyEffort: (agent, level) => applyEffort(agent, level),
|
|
62308
62408
|
getAgentName: getMyAgentName,
|
|
62309
62409
|
getConfiguredEffort: () => {
|
|
62310
62410
|
const data = switchroomExecJson(["agent", "list"]);
|
|
62311
62411
|
return data?.agents?.find((a) => a.name === getMyAgentName())?.thinking_effort ?? null;
|
|
62312
62412
|
},
|
|
62313
|
-
escapeHtml: escapeHtmlForTg
|
|
62314
|
-
preBlock
|
|
62413
|
+
escapeHtml: escapeHtmlForTg
|
|
62315
62414
|
};
|
|
62316
62415
|
}
|
|
62317
62416
|
function effortMenuReplyMarkup(reply) {
|
|
@@ -63578,14 +63677,9 @@ async function performVaultAccessApproval(ctx, pending2, stageId, senderId, atte
|
|
|
63578
63677
|
stageId,
|
|
63579
63678
|
operatorId: senderId
|
|
63580
63679
|
});
|
|
63581
|
-
const delivered =
|
|
63582
|
-
if (delivered)
|
|
63583
|
-
markClaudeBusyForInbound(synthetic);
|
|
63680
|
+
const delivered = deliverResumeSyntheticOrBuffer(pending2.agent, synthetic);
|
|
63584
63681
|
process.stderr.write(`telegram gateway: vault_grant_approved injection agent=${pending2.agent} key=${pending2.key} stage=${stageId} delivered=${delivered}
|
|
63585
63682
|
`);
|
|
63586
|
-
if (!delivered) {
|
|
63587
|
-
pendingInboundBuffer.push(pending2.agent, synthetic);
|
|
63588
|
-
}
|
|
63589
63683
|
}
|
|
63590
63684
|
async function handleVaultRequestAccessCallback(ctx, data) {
|
|
63591
63685
|
const senderId = String(ctx.from?.id ?? "");
|
|
@@ -63627,14 +63721,9 @@ async function handleVaultRequestAccessCallback(ctx, data) {
|
|
|
63627
63721
|
stageId,
|
|
63628
63722
|
operatorId: senderId
|
|
63629
63723
|
});
|
|
63630
|
-
const denyDelivered =
|
|
63631
|
-
if (denyDelivered)
|
|
63632
|
-
markClaudeBusyForInbound(denyInbound);
|
|
63724
|
+
const denyDelivered = deliverResumeSyntheticOrBuffer(pending2.agent, denyInbound);
|
|
63633
63725
|
process.stderr.write(`telegram gateway: vault_grant_denied injection agent=${pending2.agent} key=${pending2.key} stage=${stageId} delivered=${denyDelivered}
|
|
63634
63726
|
`);
|
|
63635
|
-
if (!denyDelivered) {
|
|
63636
|
-
pendingInboundBuffer.push(pending2.agent, denyInbound);
|
|
63637
|
-
}
|
|
63638
63727
|
return;
|
|
63639
63728
|
}
|
|
63640
63729
|
if (action === "approve") {
|
|
@@ -63726,13 +63815,9 @@ async function handleVaultRequestSaveCallback(ctx, data) {
|
|
|
63726
63815
|
stageId,
|
|
63727
63816
|
operatorId: senderId
|
|
63728
63817
|
});
|
|
63729
|
-
const dDelivered =
|
|
63730
|
-
if (dDelivered)
|
|
63731
|
-
markClaudeBusyForInbound(discardInbound);
|
|
63818
|
+
const dDelivered = deliverResumeSyntheticOrBuffer(pending2.agent, discardInbound);
|
|
63732
63819
|
process.stderr.write(`telegram gateway: vault_save_discarded injection agent=${pending2.agent} key=${pending2.key} stage=${stageId} delivered=${dDelivered}
|
|
63733
63820
|
`);
|
|
63734
|
-
if (!dDelivered)
|
|
63735
|
-
pendingInboundBuffer.push(pending2.agent, discardInbound);
|
|
63736
63821
|
return;
|
|
63737
63822
|
}
|
|
63738
63823
|
if (action === "rename") {
|
|
@@ -63793,13 +63878,9 @@ async function handleVaultRequestSaveCallback(ctx, data) {
|
|
|
63793
63878
|
operatorId: senderId,
|
|
63794
63879
|
reason: failReason
|
|
63795
63880
|
});
|
|
63796
|
-
const fDelivered =
|
|
63797
|
-
if (fDelivered)
|
|
63798
|
-
markClaudeBusyForInbound(failInbound);
|
|
63881
|
+
const fDelivered = deliverResumeSyntheticOrBuffer(pending2.agent, failInbound);
|
|
63799
63882
|
process.stderr.write(`telegram gateway: vault_save_failed injection agent=${pending2.agent} key=${pending2.key} stage=${stageId} delivered=${fDelivered}
|
|
63800
63883
|
`);
|
|
63801
|
-
if (!fDelivered)
|
|
63802
|
-
pendingInboundBuffer.push(pending2.agent, failInbound);
|
|
63803
63884
|
return;
|
|
63804
63885
|
}
|
|
63805
63886
|
pendingVaultRequestSaves.delete(stageId);
|
|
@@ -63817,13 +63898,9 @@ async function handleVaultRequestSaveCallback(ctx, data) {
|
|
|
63817
63898
|
stageId,
|
|
63818
63899
|
operatorId: senderId
|
|
63819
63900
|
});
|
|
63820
|
-
const okDelivered =
|
|
63821
|
-
if (okDelivered)
|
|
63822
|
-
markClaudeBusyForInbound(okInbound);
|
|
63901
|
+
const okDelivered = deliverResumeSyntheticOrBuffer(pending2.agent, okInbound);
|
|
63823
63902
|
process.stderr.write(`telegram gateway: vault_save_completed injection agent=${pending2.agent} key=${pending2.key} stage=${stageId} delivered=${okDelivered}
|
|
63824
63903
|
`);
|
|
63825
|
-
if (!okDelivered)
|
|
63826
|
-
pendingInboundBuffer.push(pending2.agent, okInbound);
|
|
63827
63904
|
return;
|
|
63828
63905
|
}
|
|
63829
63906
|
await ctx.answerCallbackQuery({ text: "Unknown action" }).catch(() => {});
|
|
@@ -65051,6 +65128,56 @@ bot.command("version", async (ctx) => {
|
|
|
65051
65128
|
${preBlock(formatSwitchroomOutput(err.message ?? "unknown error"))}`, { html: true });
|
|
65052
65129
|
}
|
|
65053
65130
|
});
|
|
65131
|
+
bot.command("whoami", async (ctx) => {
|
|
65132
|
+
if (!isAuthorizedSender(ctx))
|
|
65133
|
+
return;
|
|
65134
|
+
try {
|
|
65135
|
+
let raw;
|
|
65136
|
+
try {
|
|
65137
|
+
raw = switchroomExecCombined(["config", "whoami"], 1e4);
|
|
65138
|
+
} catch (err) {
|
|
65139
|
+
raw = err.stdout ?? err.message ?? "whoami failed";
|
|
65140
|
+
}
|
|
65141
|
+
const trimmed = stripAnsi2(raw).trim();
|
|
65142
|
+
let card;
|
|
65143
|
+
try {
|
|
65144
|
+
card = formatWhoamiCard(JSON.parse(trimmed.split(`
|
|
65145
|
+
`).pop() ?? trimmed));
|
|
65146
|
+
} catch {
|
|
65147
|
+
card = preBlock(formatSwitchroomOutput(trimmed || "whoami: no output"));
|
|
65148
|
+
}
|
|
65149
|
+
await switchroomReply(ctx, card, { html: true });
|
|
65150
|
+
} catch (err) {
|
|
65151
|
+
await switchroomReply(ctx, `<b>whoami failed:</b>
|
|
65152
|
+
${preBlock(formatSwitchroomOutput(err.message ?? "unknown error"))}`, { html: true });
|
|
65153
|
+
}
|
|
65154
|
+
});
|
|
65155
|
+
function formatWhoamiCard(v) {
|
|
65156
|
+
const esc = escapeHtmlForTg;
|
|
65157
|
+
const yn = (b) => b ? "\u2713" : "\u2717";
|
|
65158
|
+
const lines = [];
|
|
65159
|
+
lines.push(`\uD83D\uDC64 <b>${esc(v.name ?? "?")}</b> \xB7 ${esc(v.tier ?? "standard")}`);
|
|
65160
|
+
if (v.persona)
|
|
65161
|
+
lines.push(esc(v.persona));
|
|
65162
|
+
if (v.model)
|
|
65163
|
+
lines.push(`Model: ${esc(v.model)}`);
|
|
65164
|
+
const allow = v.tools?.allow ?? [];
|
|
65165
|
+
lines.push(`Tools: ${allow.length ? esc(allow.slice(0, 8).join(", ")) + (allow.length > 8 ? ` \u2026(+${allow.length - 8})` : "") : "\u2014"}`);
|
|
65166
|
+
if ((v.tools?.deny ?? []).length)
|
|
65167
|
+
lines.push(`Denied: ${esc(v.tools.deny.join(", "))}`);
|
|
65168
|
+
if ((v.mcpServers ?? []).length)
|
|
65169
|
+
lines.push(`MCP: ${esc(v.mcpServers.join(", "))}`);
|
|
65170
|
+
if ((v.skills ?? []).length)
|
|
65171
|
+
lines.push(`Skills: ${esc(v.skills.join(", "))}`);
|
|
65172
|
+
if ((v.vault ?? []).length) {
|
|
65173
|
+
lines.push(`Vault keys (names only): ${v.vault.map((k) => `${esc(k.key)} ${yn(k.readable)}`).join(", ")}`);
|
|
65174
|
+
}
|
|
65175
|
+
const p = v.powers ?? {};
|
|
65176
|
+
lines.push(`Powers: admin ${yn(p.admin)} \xB7 root ${yn(p.root)} \xB7 config-edit ${yn(p.configEdit)} \xB7 cross-agent verbs ${yn(p.crossAgentHostVerbs)}`);
|
|
65177
|
+
lines.push(`Schedule: ${v.scheduleCount ?? 0} cron \xB7 Memory: ${esc(v.memoryBackend ?? "none")}`);
|
|
65178
|
+
return lines.join(`
|
|
65179
|
+
`);
|
|
65180
|
+
}
|
|
65054
65181
|
bot.command("commands", async (ctx) => {
|
|
65055
65182
|
if (!isAuthorizedSender(ctx))
|
|
65056
65183
|
return;
|
|
@@ -65515,10 +65642,12 @@ ${preBlock(formatSwitchroomOutput(err.message ?? "unknown error"))}`, { html: tr
|
|
|
65515
65642
|
}
|
|
65516
65643
|
const ok = durable;
|
|
65517
65644
|
const legacyNote = legacy && durable;
|
|
65518
|
-
const
|
|
65645
|
+
const restartScheduled = ok && !legacy && scheduleGrantRestart(agentName3, ctx.chat?.id, ctx.callbackQuery?.message?.message_thread_id, `always-allow: ${grantPhrase}`) !== "disabled";
|
|
65646
|
+
const liveSuffix = restartScheduled ? " \u2014 applying now (restarting to take effect)" : "";
|
|
65647
|
+
const ackText2 = ok ? legacyNote ? `\u2705 Saved. ${agentName3} can now ${grantPhrase} without asking (legacy path).` : `\u2705 Saved. ${agentName3} can now ${grantPhrase} without asking.${liveSuffix}` : editLockHint ? `\u26A0\uFE0F Allowed for now \u2014 config edits are locked. Enable hostd.config_edit_enabled.` : `\u26A0\uFE0F Allowed for now, but "always" did NOT save \u2014 it will ask again after restart. Check gateway log.`;
|
|
65519
65648
|
const sourceMsg = ctx.callbackQuery?.message;
|
|
65520
65649
|
const baseText2 = sourceMsg && "text" in sourceMsg && sourceMsg.text ? escapeHtmlForTg(sourceMsg.text) : "";
|
|
65521
|
-
const editLabel = ok ? legacyNote ? `\u2705 <b>${escapeHtmlForTg(agentName3)} can now ${escapeHtmlForTg(grantPhrase)}</b> without asking (legacy path); restart agent for full effect` : `\u2705 <b>${escapeHtmlForTg(agentName3)} can now ${escapeHtmlForTg(grantPhrase)}</b> without asking; restart agent for full effect` : editLockHint ? `\u26A0\uFE0F <b>Allowed for now \u2014 "always" did NOT save.</b> Config edits are locked; enable <code>hostd.config_edit_enabled</code>.` : `\u26A0\uFE0F <b>Allowed for now \u2014 "always" did NOT save.</b> It will ask again after restart. Check gateway log.`;
|
|
65650
|
+
const editLabel = ok ? legacyNote ? `\u2705 <b>${escapeHtmlForTg(agentName3)} can now ${escapeHtmlForTg(grantPhrase)}</b> without asking (legacy path); restart agent for full effect` : restartScheduled ? `\u2705 <b>${escapeHtmlForTg(agentName3)} can now ${escapeHtmlForTg(grantPhrase)}</b> without asking \u2014 applying now (restarting to take effect).` : `\u2705 <b>${escapeHtmlForTg(agentName3)} can now ${escapeHtmlForTg(grantPhrase)}</b> without asking; restart agent for full effect` : editLockHint ? `\u26A0\uFE0F <b>Allowed for now \u2014 "always" did NOT save.</b> Config edits are locked; enable <code>hostd.config_edit_enabled</code>.` : `\u26A0\uFE0F <b>Allowed for now \u2014 "always" did NOT save.</b> It will ask again after restart. Check gateway log.`;
|
|
65522
65651
|
await finalizeCallback(ctx, {
|
|
65523
65652
|
ackText: ackText2.slice(0, 200),
|
|
65524
65653
|
newText: baseText2 ? `${baseText2}
|