switchroom 0.16.46 β 0.16.47
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/agent-scheduler/index.js +80 -80
- package/dist/auth-broker/index.js +80 -80
- package/dist/cli/autoaccept-poll.js +8 -8
- package/dist/cli/drive-write-pretool.mjs +10 -10
- package/dist/cli/notion-write-pretool.mjs +82 -82
- package/dist/cli/skill-validate-pretool.mjs +91 -91
- package/dist/cli/switchroom.js +364 -364
- package/dist/host-control/main.js +157 -157
- package/dist/vault/approvals/kernel-server.js +82 -82
- package/dist/vault/broker/server.js +83 -83
- package/package.json +1 -1
- package/telegram-plugin/dist/bridge/bridge.js +112 -112
- package/telegram-plugin/dist/gateway/gateway.js +451 -262
- package/telegram-plugin/dist/server.js +160 -160
- package/telegram-plugin/gateway/gateway.ts +48 -7
- package/telegram-plugin/tests/vault-request-access-unlock-resume.test.ts +46 -0
|
@@ -20160,20 +20160,61 @@ async function handleVaultRequestAccessCallback(ctx: Context, data: string): Pro
|
|
|
20160
20160
|
// cards open at once.
|
|
20161
20161
|
const joiningBatch = items.length > 1
|
|
20162
20162
|
await ctx.answerCallbackQuery({ text: joiningBatch ? `π Queued β one passphrase covers ${items.length} cards` : 'π Send your passphraseβ¦' }).catch(() => {})
|
|
20163
|
+
|
|
20164
|
+
// Strip the buttons on the ORIGINAL card and mark it "waiting" so it
|
|
20165
|
+
// can't be re-tapped, but do NOT overload it as the passphrase prompt.
|
|
20166
|
+
// An in-place edit fires no notification and stays pinned to the card's
|
|
20167
|
+
// old position in the chat, so a busy topic buries it and the operator
|
|
20168
|
+
// never sees the passphrase ask β the exact admin-key miss this fixes
|
|
20169
|
+
// (v0.16.45: the prompt scrolled off, the passphrase never arrived, the
|
|
20170
|
+
// grant was never minted). The prompt goes out as a fresh message below.
|
|
20163
20171
|
await ctx.api
|
|
20164
20172
|
.editMessageText(
|
|
20165
20173
|
pending.chat_id,
|
|
20166
20174
|
pending.card_message_id,
|
|
20167
|
-
|
|
20168
|
-
? `π **Queued behind an earlier card.** Type your passphrase as your next message β it covers **${items.length}** pending approvals in this chat (one entry mints all grants, no re-type per card).`
|
|
20169
|
-
: isAdminOnly
|
|
20170
|
-
? `π **Admin-only credential.** \`${pending.key}\` requires your vault passphrase to grant β reply with it as your next message and we'll mint the grant for **${escapeHtmlForTg(pending.agent)}**, then delete the passphrase message.\n\n` +
|
|
20171
|
-
`_The passphrase is what proves it's you: an agent can never mint this key on its own._`
|
|
20172
|
-
: `π **Vault is locked.** Reply with your passphrase as your next message β we'll unlock, mint the grant for **${escapeHtmlForTg(pending.agent)}**, and delete the passphrase message in one step.\n\n` +
|
|
20173
|
-
richMessage(`_Mint authority stays operator-only: the broker only accepts the grant when the passphrase matches._`),
|
|
20175
|
+
richMessage(`π _Approved β waiting for your vault passphrase. See the prompt below._`),
|
|
20174
20176
|
{ reply_markup: { inline_keyboard: [] } },
|
|
20175
20177
|
)
|
|
20176
20178
|
.catch(() => {})
|
|
20179
|
+
|
|
20180
|
+
// The passphrase prompt as a NEW rich message. Three fixes vs. the old
|
|
20181
|
+
// in-place edit, all of which the reported bug needed:
|
|
20182
|
+
// 1. Real bold/italic β rendered through the sanctioned `richMessage`
|
|
20183
|
+
// GFM path (`sendRichMessage`), never a raw string. The old admin
|
|
20184
|
+
// and joining-batch branches passed raw markdown to editMessageText
|
|
20185
|
+
// (parse_mode=none), so `**`/`_` rendered as literal characters;
|
|
20186
|
+
// the "locked" branch even concatenated a string with a
|
|
20187
|
+
// `richMessage()` object (β `[object Object]`). All three are gone.
|
|
20188
|
+
// 2. It lands at the BOTTOM of the chat, not stapled to an old card
|
|
20189
|
+
// that later messages bury.
|
|
20190
|
+
// 3. It fires a notification β `disable_notification` is deliberately
|
|
20191
|
+
// NOT set β so the operator is actually pinged to act.
|
|
20192
|
+
// Attention-grabbing header, short lines, key in code formatting.
|
|
20193
|
+
const promptText = joiningBatch
|
|
20194
|
+
? `**β οΈπ ACTION NEEDED: passphrase required**\n\n` +
|
|
20195
|
+
`Type your vault passphrase as your **next message**.\n` +
|
|
20196
|
+
`One entry covers **${items.length}** pending approvals in this chat, no re-type per card.\n\n` +
|
|
20197
|
+
`_We delete the passphrase message the moment we read it._`
|
|
20198
|
+
: isAdminOnly
|
|
20199
|
+
? `**β οΈπ ACTION NEEDED: passphrase required**\n\n` +
|
|
20200
|
+
`\`${pending.key}\` is an **admin-only credential**.\n` +
|
|
20201
|
+
`Type your vault passphrase as your **next message** to mint the grant for **${escapeHtmlForTg(pending.agent)}**.\n\n` +
|
|
20202
|
+
`_The passphrase is what proves it's you. An agent can never mint this key on its own. We delete the passphrase message the moment we read it._`
|
|
20203
|
+
: `**β οΈπ ACTION NEEDED: passphrase required**\n\n` +
|
|
20204
|
+
`Your vault is locked.\n` +
|
|
20205
|
+
`Reply with your passphrase as your **next message** to unlock and mint the grant for **${escapeHtmlForTg(pending.agent)}**.\n\n` +
|
|
20206
|
+
`_Mint authority stays operator-only: the broker only accepts the grant when the passphrase matches. We delete the passphrase message the moment we read it._`
|
|
20207
|
+
|
|
20208
|
+
// #1075: deleted-topic safe β fall back to the main chat. Wrapped
|
|
20209
|
+
// through robustApiCall for flood-wait retries, mirroring the card send.
|
|
20210
|
+
await retryWithThreadFallback<{ message_id: number }>(
|
|
20211
|
+
robustApiCall,
|
|
20212
|
+
(tid) =>
|
|
20213
|
+
lockedBot.api.sendRichMessage(pending.chat_id, richMessage(promptText), {
|
|
20214
|
+
...(tid != null && Number.isFinite(tid) ? { message_thread_id: tid } : {}),
|
|
20215
|
+
}),
|
|
20216
|
+
{ threadId: pending.threadId, chat_id: pending.chat_id, verb: 'vault_request_access.passphrase_prompt' },
|
|
20217
|
+
).catch(() => {})
|
|
20177
20218
|
return
|
|
20178
20219
|
}
|
|
20179
20220
|
|
|
@@ -54,6 +54,52 @@ describe('vault_request_access β tap-to-unlock-and-approve UX', () => {
|
|
|
54
54
|
expect(approveBlock).not.toMatch(/ask the agent to re-issue the request card/)
|
|
55
55
|
})
|
|
56
56
|
|
|
57
|
+
it('passphrase prompt goes out as a NEW rich message, not an in-place edit', () => {
|
|
58
|
+
// fails when: the cache-miss branch reverts to overloading the
|
|
59
|
+
// ORIGINAL card as the prompt via editMessageText. An in-place
|
|
60
|
+
// edit fires no notification and stays stapled to the card's old
|
|
61
|
+
// position, so a busy topic buries it and the operator never sees
|
|
62
|
+
// the passphrase ask (the reported v0.16.45 admin-key miss). The
|
|
63
|
+
// prompt must be a fresh `sendRichMessage` below.
|
|
64
|
+
const approveBlock =
|
|
65
|
+
gatewaySrc.split('if (action === \'approve\')')[1]?.split('await ctx.answerCallbackQuery({ text: \'Unknown action\'')[0] ?? ''
|
|
66
|
+
// A distinct passphrase-prompt send exists (verb-tagged).
|
|
67
|
+
expect(approveBlock).toMatch(/sendRichMessage\(pending\.chat_id, richMessage\(promptText\)/)
|
|
68
|
+
expect(approveBlock).toMatch(/vault_request_access\.passphrase_prompt/)
|
|
69
|
+
})
|
|
70
|
+
|
|
71
|
+
it('passphrase prompt renders via richMessage β no raw literal-markdown edit', () => {
|
|
72
|
+
// ROOT CAUSE of the reported bug: the old admin-only and
|
|
73
|
+
// joining-batch branches passed RAW markdown strings (with literal
|
|
74
|
+
// `**`/`_`) straight to editMessageText (parse_mode=none), so the
|
|
75
|
+
// bold/italic rendered as literal characters, and the "locked"
|
|
76
|
+
// branch concatenated a string with a richMessage() object
|
|
77
|
+
// (β "[object Object]"). Every branch must now flow through the
|
|
78
|
+
// richMessage() GFM render path.
|
|
79
|
+
const approveBlock =
|
|
80
|
+
gatewaySrc.split('if (action === \'approve\')')[1]?.split('await ctx.answerCallbackQuery({ text: \'Unknown action\'')[0] ?? ''
|
|
81
|
+
// The prompt text is assembled once and wrapped in richMessage.
|
|
82
|
+
expect(approveBlock).toMatch(/const promptText =/)
|
|
83
|
+
// Regression guard: the old raw-string admin-only edit copy is gone.
|
|
84
|
+
expect(approveBlock).not.toMatch(/requires your vault passphrase to grant/)
|
|
85
|
+
// Regression guard: no string-concatenated richMessage() object
|
|
86
|
+
// (the "[object Object]" bug) remains.
|
|
87
|
+
expect(approveBlock).not.toMatch(/\+\s*\n\s*richMessage\(/)
|
|
88
|
+
})
|
|
89
|
+
|
|
90
|
+
it('passphrase prompt is attention-grabbing and does NOT suppress notifications', () => {
|
|
91
|
+
// fails when: the prompt loses its strong header or someone adds
|
|
92
|
+
// disable_notification to it. The whole point of the fix is that
|
|
93
|
+
// the operator gets PINGED β a silent prompt is the bug.
|
|
94
|
+
const approveBlock =
|
|
95
|
+
gatewaySrc.split('if (action === \'approve\')')[1]?.split('await ctx.answerCallbackQuery({ text: \'Unknown action\'')[0] ?? ''
|
|
96
|
+
expect(approveBlock).toMatch(/ACTION NEEDED: passphrase required/)
|
|
97
|
+
// The send options for the prompt must not carry disable_notification.
|
|
98
|
+
const promptSend =
|
|
99
|
+
approveBlock.split('const promptText =')[1]?.split('return')[0] ?? ''
|
|
100
|
+
expect(promptSend).not.toMatch(/disable_notification/)
|
|
101
|
+
})
|
|
102
|
+
|
|
57
103
|
it('passphrase intercept deletes the chat message and resumes mint', () => {
|
|
58
104
|
// fails when: the new pending-op handler stops calling
|
|
59
105
|
// deleteSensitiveMessage on the passphrase message OR stops
|