switchroom 0.18.32 → 0.19.0
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/auth-broker/index.js +17 -1
- package/dist/cli/switchroom.js +847 -729
- package/dist/host-control/main.js +18 -2
- package/dist/vault/approvals/kernel-server.js +17 -1
- package/dist/vault/broker/server.js +44 -2
- package/package.json +2 -2
- package/profiles/_base/start.sh.hbs +105 -18
- package/telegram-plugin/dist/gateway/gateway.js +60612 -56998
- package/telegram-plugin/gateway/agent-button-callback-handler.ts +237 -0
- package/telegram-plugin/gateway/ask-callback-handler.ts +92 -0
- package/telegram-plugin/gateway/attachment-message-handlers.ts +152 -0
- package/telegram-plugin/gateway/boot-card.ts +169 -1
- package/telegram-plugin/gateway/bot-commands-model-effort.ts +209 -0
- package/telegram-plugin/gateway/bot-commands-start-info.ts +108 -0
- package/telegram-plugin/gateway/callback-query-handlers.ts +124 -0
- package/telegram-plugin/gateway/card-approval-keyboards.test.ts +28 -0
- package/telegram-plugin/gateway/card-tool-handlers.ts +639 -0
- package/telegram-plugin/gateway/checklist-message-handler.ts +107 -0
- package/telegram-plugin/gateway/delivery-confirm-wiring.ts +133 -0
- package/telegram-plugin/gateway/gateway.ts +1347 -6758
- package/telegram-plugin/gateway/inbound-interceptors.ts +1133 -0
- package/telegram-plugin/gateway/inbound-router.ts +400 -0
- package/telegram-plugin/gateway/liveness-wiring.ts +440 -0
- package/telegram-plugin/gateway/media-message-handlers.ts +256 -0
- package/telegram-plugin/gateway/mental-model-propose-card.ts +16 -0
- package/telegram-plugin/gateway/model-command.ts +23 -0
- package/telegram-plugin/gateway/narrative-lane.ts +865 -0
- package/telegram-plugin/gateway/obligation-wiring.ts +333 -0
- package/telegram-plugin/gateway/photo-message-handler.ts +80 -0
- package/telegram-plugin/gateway/pinned-message-handler.ts +86 -0
- package/telegram-plugin/gateway/secret-request-card.test.ts +46 -0
- package/telegram-plugin/gateway/secret-request-card.ts +45 -0
- package/telegram-plugin/gateway/stream-render.ts +2166 -0
- package/telegram-plugin/gateway/turn-end.ts +606 -0
- package/telegram-plugin/gateway/turn-start-surfaces.ts +298 -0
- package/telegram-plugin/gateway/vault-request-access-card.ts +16 -0
- package/telegram-plugin/gateway/vault-request-save-card.test.ts +49 -0
- package/telegram-plugin/gateway/vault-request-save-card.ts +52 -0
- package/telegram-plugin/gateway/voice-message-handler.ts +123 -0
- package/telegram-plugin/gateway/voice-ondemand-callback-handler.ts +204 -0
- package/telegram-plugin/gateway/worker-feed-dispatch.ts +40 -0
- package/telegram-plugin/narrative-dedup.ts +24 -1
- package/telegram-plugin/narrative-flush.ts +2 -2
- package/telegram-plugin/render/render.ts +25 -1
- package/telegram-plugin/status-no-truncate.ts +13 -0
- package/telegram-plugin/subagent-watcher.ts +186 -3
- package/telegram-plugin/tests/activity-card-wiring.test.ts +8 -3
- package/telegram-plugin/tests/activity-ever-opened-sticky.test.ts +18 -3
- package/telegram-plugin/tests/agent-button-callback-handler.test.ts +149 -0
- package/telegram-plugin/tests/ask-callback-handler.test.ts +118 -0
- package/telegram-plugin/tests/attachment-message-handlers.test.ts +135 -0
- package/telegram-plugin/tests/boot-card-routing.test.ts +139 -0
- package/telegram-plugin/tests/bot-commands-model-effort.test.ts +189 -0
- package/telegram-plugin/tests/bot-commands-start-info.test.ts +240 -0
- package/telegram-plugin/tests/buffer-gate-broadened.test.ts +15 -6
- package/telegram-plugin/tests/busy-ack-wiring.test.ts +6 -1
- package/telegram-plugin/tests/button-tap-turn-gated.test.ts +18 -9
- package/telegram-plugin/tests/callback-query-handlers.test.ts +101 -0
- package/telegram-plugin/tests/card-tool-handlers.test.ts +497 -0
- package/telegram-plugin/tests/catch-all-unhandled-message.test.ts +5 -2
- package/telegram-plugin/tests/checklist-message-handler.test.ts +160 -0
- package/telegram-plugin/tests/emission-authority-facade.test.ts +47 -10
- package/telegram-plugin/tests/emission-determinism-wiring.test.ts +27 -9
- package/telegram-plugin/tests/feed-heartbeat-liveness-open.test.ts +30 -7
- package/telegram-plugin/tests/gateway-boot-side-effect-gating.test.ts +39 -18
- package/telegram-plugin/tests/gateway-boot-smoke.test.ts +160 -0
- package/telegram-plugin/tests/gateway-handler-registration-wiring.test.ts +3 -7
- package/telegram-plugin/tests/gateway-loopback-paste-redact.test.ts +44 -29
- package/telegram-plugin/tests/gateway-outbound-redact.test.ts +8 -2
- package/telegram-plugin/tests/gateway-request-secret.test.ts +7 -3
- package/telegram-plugin/tests/gateway-secret-detect.test.ts +20 -10
- package/telegram-plugin/tests/gateway-session-model-relaunch.test.ts +8 -2
- package/telegram-plugin/tests/inbound-emit-after-intercepts.test.ts +14 -3
- package/telegram-plugin/tests/inbound-message-types.test.ts +52 -16
- package/telegram-plugin/tests/media-message-handlers.test.ts +276 -0
- package/telegram-plugin/tests/mental-model-propose-callback-gate.test.ts +8 -4
- package/telegram-plugin/tests/model-command.test.ts +30 -0
- package/telegram-plugin/tests/multitopic-routing-wiring.test.ts +27 -9
- package/telegram-plugin/tests/narrative-dedup.test.ts +32 -0
- package/telegram-plugin/tests/narrative-flush.test.ts +6 -2
- package/telegram-plugin/tests/narrative-lane-golden.test.ts +458 -0
- package/telegram-plugin/tests/no-reply-bounded-drain.test.ts +14 -3
- package/telegram-plugin/tests/pending-card-durability-wiring.test.ts +16 -7
- package/telegram-plugin/tests/per-topic-current-turn.test.ts +32 -8
- package/telegram-plugin/tests/photo-message-handler.test.ts +114 -0
- package/telegram-plugin/tests/pinned-message-handler.test.ts +108 -0
- package/telegram-plugin/tests/render/render.test.ts +42 -0
- package/telegram-plugin/tests/secret-detect-delete-must-surface-failures.test.ts +8 -4
- package/telegram-plugin/tests/secret-detect-fail-closed.test.ts +38 -28
- package/telegram-plugin/tests/secret-detect-oauth-code.test.ts +28 -18
- package/telegram-plugin/tests/silence-liveness-wiring.test.ts +22 -8
- package/telegram-plugin/tests/status-pin-service-message-suppression.test.ts +42 -49
- package/telegram-plugin/tests/stop-command.test.ts +22 -12
- package/telegram-plugin/tests/stream-render-golden.test.ts +424 -0
- package/telegram-plugin/tests/subagent-watcher-boot-skip-dead.test.ts +218 -0
- package/telegram-plugin/tests/subagent-watcher-resume-reregister.test.ts +14 -0
- package/telegram-plugin/tests/subagent-watcher.test.ts +35 -3
- package/telegram-plugin/tests/turn-flush-safety.test.ts +183 -5
- package/telegram-plugin/tests/turn-flush-suppression-wiring.test.ts +9 -4
- package/telegram-plugin/tests/vault-approval-posture.test.ts +8 -2
- package/telegram-plugin/tests/vault-grant-union.test.ts +4 -1
- package/telegram-plugin/tests/vault-key-regex-allows-slash.test.ts +16 -5
- package/telegram-plugin/tests/vault-request-access-tool.test.ts +10 -5
- package/telegram-plugin/tests/vault-request-access-unlock-resume.test.ts +4 -1
- package/telegram-plugin/tests/vault-subcommands.test.ts +6 -1
- package/telegram-plugin/tests/voice-message-handler.test.ts +111 -0
- package/telegram-plugin/tests/voice-ondemand-callback-handler.test.ts +140 -0
- package/telegram-plugin/tests/worker-activity-feed.test.ts +86 -19
- package/telegram-plugin/tests/worker-feed-coalesce.test.ts +110 -20
- package/telegram-plugin/tests/worker-feed-resume-guard.test.ts +86 -0
- package/telegram-plugin/tool-activity-summary.ts +83 -35
- package/telegram-plugin/turn-flush-safety.ts +80 -14
- package/telegram-plugin/uat/restart-capability.ts +76 -0
- package/telegram-plugin/uat/scenarios/bg-sub-agent-dispatch-dm.test.ts +14 -4
- package/telegram-plugin/uat/scenarios/bridge-flap-resilience-dm.test.ts +11 -1
- package/telegram-plugin/uat/scenarios/cross-turn-pending-progress-dm.test.ts +19 -2
- package/telegram-plugin/uat/scenarios/jtbd-always-on-after-restart-dm.test.ts +6 -12
- package/telegram-plugin/uat/scenarios/jtbd-deliberate-restart-resumes-dm.test.ts +6 -12
- package/telegram-plugin/uat/scenarios/jtbd-interrupted-turn-resumes-dm.test.ts +6 -12
- package/telegram-plugin/uat/scenarios/jtbd-multipart-render-dm.test.ts +47 -13
- package/telegram-plugin/worker-activity-feed.ts +10 -4
|
@@ -226,6 +226,38 @@ export interface PendingVaultRequestSave {
|
|
|
226
226
|
restoredWithoutValue?: boolean
|
|
227
227
|
}
|
|
228
228
|
|
|
229
|
+
/**
|
|
230
|
+
* #2045 `request_secret` — agent asks the operator to PROVIDE a secret it does
|
|
231
|
+
* NOT have. No `value` is staged (unlike PendingVaultRequestSave): the value
|
|
232
|
+
* arrives via secure capture (the operator's next message after they tap
|
|
233
|
+
* [Provide securely]). Only request metadata lives here — nothing sensitive.
|
|
234
|
+
*/
|
|
235
|
+
export interface PendingSecretRequest {
|
|
236
|
+
agent: string
|
|
237
|
+
chat_id: string
|
|
238
|
+
key: string
|
|
239
|
+
reason?: string
|
|
240
|
+
staged_at: number
|
|
241
|
+
card_message_id?: number
|
|
242
|
+
/** Supergroup forum topic the agent was working in — carried into the
|
|
243
|
+
* provide/decline/fail outcome inbounds so the resumed reply lands back
|
|
244
|
+
* in that topic, not General. */
|
|
245
|
+
threadId?: number
|
|
246
|
+
}
|
|
247
|
+
|
|
248
|
+
/**
|
|
249
|
+
* The armed capture for a `request_secret` card: after [Provide securely] is
|
|
250
|
+
* tapped, the operator's NEXT message in `chat_id` is the value for `key`.
|
|
251
|
+
* Transient post-tap window (never persisted).
|
|
252
|
+
*/
|
|
253
|
+
export interface ArmedSecretCapture {
|
|
254
|
+
key: string
|
|
255
|
+
agent: string
|
|
256
|
+
stageId: string
|
|
257
|
+
armed_at: number
|
|
258
|
+
threadId?: number
|
|
259
|
+
}
|
|
260
|
+
|
|
229
261
|
/**
|
|
230
262
|
* Issue #1012 — agent-initiated vault ACL request. The agent calls
|
|
231
263
|
* `vault_request_access` when it hits VAULT-BROKER-DENIED (or
|
|
@@ -367,6 +399,10 @@ export interface CallbackQueryHandlersDeps {
|
|
|
367
399
|
// Pending-state stores (consolidated surfaces from #3008/#3011).
|
|
368
400
|
pendingVaultRequestAccesses: SweepableCardStore<PendingVaultRequestAccess>
|
|
369
401
|
pendingVaultRequestSaves: SweepableCardStore<PendingVaultRequestSave>
|
|
402
|
+
/** Agent-initiated `request_secret` cards awaiting an operator tap (`vsp:`). */
|
|
403
|
+
pendingSecretRequests: SweepableCardStore<PendingSecretRequest>
|
|
404
|
+
/** Post-[Provide securely] armed captures, keyed by chat_id. */
|
|
405
|
+
armedSecretCaptures: SweepableStore<ArmedSecretCapture>
|
|
370
406
|
pendingMentalModelProposes: SweepableCardStore<PendingMentalModelPropose>
|
|
371
407
|
pendingCardStore: { remove(stageId: string): void }
|
|
372
408
|
pendingMentalModelCorrelations: SweepableStore<{
|
|
@@ -431,6 +467,8 @@ export function createCallbackQueryHandlers(deps: CallbackQueryHandlersDeps) {
|
|
|
431
467
|
swallowingApiCall,
|
|
432
468
|
pendingVaultRequestAccesses,
|
|
433
469
|
pendingVaultRequestSaves,
|
|
470
|
+
pendingSecretRequests,
|
|
471
|
+
armedSecretCaptures,
|
|
434
472
|
pendingMentalModelProposes,
|
|
435
473
|
pendingCardStore,
|
|
436
474
|
pendingMentalModelCorrelations,
|
|
@@ -1676,6 +1714,91 @@ async function handleVaultRequestSaveCallback(ctx: Context, data: string): Promi
|
|
|
1676
1714
|
await ctx.answerCallbackQuery({ text: 'Unknown action' }).catch(() => {})
|
|
1677
1715
|
}
|
|
1678
1716
|
|
|
1717
|
+
/**
|
|
1718
|
+
* `vsp:` callbacks — agent-requested-secret card (#2045).
|
|
1719
|
+
* vsp:provide:<stageId> — arm capture: operator's next message is the value
|
|
1720
|
+
* vsp:decline:<stageId> — drop the request; tell the agent it was declined
|
|
1721
|
+
*
|
|
1722
|
+
* Extracted verbatim from gateway.ts (#2996 P5) — behavior-preserving. The
|
|
1723
|
+
* `pendingSecretRequests` / `armedSecretCaptures` stores are the SAME singleton
|
|
1724
|
+
* instances gateway.ts constructs (injected via deps, never re-`new`ed here).
|
|
1725
|
+
*/
|
|
1726
|
+
async function handleSecretRequestCallback(ctx: Context, data: string): Promise<void> {
|
|
1727
|
+
const senderId = String(ctx.from?.id ?? '')
|
|
1728
|
+
const access = loadAccess()
|
|
1729
|
+
if (!access.allowFrom.includes(senderId)) {
|
|
1730
|
+
await ctx.answerCallbackQuery({ text: 'Not authorized.' }).catch(() => {})
|
|
1731
|
+
return
|
|
1732
|
+
}
|
|
1733
|
+
const parts = data.split(':')
|
|
1734
|
+
const action = parts[1]
|
|
1735
|
+
const stageId = parts[2] ?? ''
|
|
1736
|
+
const pending = pendingSecretRequests.get(stageId)
|
|
1737
|
+
if (!pending) {
|
|
1738
|
+
await ctx.answerCallbackQuery({ text: 'This request expired.' }).catch(() => {})
|
|
1739
|
+
return
|
|
1740
|
+
}
|
|
1741
|
+
|
|
1742
|
+
if (action === 'provide') {
|
|
1743
|
+
armedSecretCaptures.set(pending.chat_id, {
|
|
1744
|
+
key: pending.key,
|
|
1745
|
+
agent: pending.agent,
|
|
1746
|
+
stageId,
|
|
1747
|
+
armed_at: Date.now(),
|
|
1748
|
+
...(pending.threadId != null ? { threadId: pending.threadId } : {}),
|
|
1749
|
+
})
|
|
1750
|
+
await ctx.answerCallbackQuery({ text: 'Send the value now — it auto-deletes.' }).catch(() => {})
|
|
1751
|
+
if (pending.card_message_id != null) {
|
|
1752
|
+
await ctx.api
|
|
1753
|
+
.editMessageText(
|
|
1754
|
+
pending.chat_id,
|
|
1755
|
+
pending.card_message_id,
|
|
1756
|
+
richMessage(`🔐 Send the value for \`${pending.key}\` as your next message — a single message, exactly as-is (don't add other text). I’ll delete it instantly and store it in the vault.`),
|
|
1757
|
+
{ reply_markup: { inline_keyboard: [] } },
|
|
1758
|
+
)
|
|
1759
|
+
.catch(() => {})
|
|
1760
|
+
}
|
|
1761
|
+
return
|
|
1762
|
+
}
|
|
1763
|
+
|
|
1764
|
+
if (action === 'decline') {
|
|
1765
|
+
pendingSecretRequests.delete(stageId)
|
|
1766
|
+
pendingCardStore.remove(stageId)
|
|
1767
|
+
armedSecretCaptures.delete(pending.chat_id)
|
|
1768
|
+
await ctx.answerCallbackQuery({ text: 'Declined.' }).catch(() => {})
|
|
1769
|
+
if (pending.card_message_id != null) {
|
|
1770
|
+
await ctx.api
|
|
1771
|
+
.editMessageText(pending.chat_id, pending.card_message_id, richMessage(`🚫 Declined — \`${pending.key}\` not provided.`), {
|
|
1772
|
+
reply_markup: { inline_keyboard: [] },
|
|
1773
|
+
})
|
|
1774
|
+
.catch(() => {})
|
|
1775
|
+
}
|
|
1776
|
+
// Tell the agent so it stops waiting.
|
|
1777
|
+
const ts = Date.now()
|
|
1778
|
+
const synthetic: InboundMessage = {
|
|
1779
|
+
type: 'inbound',
|
|
1780
|
+
chatId: pending.chat_id,
|
|
1781
|
+
...(pending.threadId != null ? { threadId: pending.threadId } : {}),
|
|
1782
|
+
messageId: ts,
|
|
1783
|
+
user: 'vault-broker',
|
|
1784
|
+
userId: 0,
|
|
1785
|
+
ts,
|
|
1786
|
+
text: `🚫 Operator declined your request for \`vault:${pending.key}\`. Proceed without it or ask how they'd like to handle the task.`,
|
|
1787
|
+
meta: {
|
|
1788
|
+
source: 'secret_declined',
|
|
1789
|
+
agent: pending.agent,
|
|
1790
|
+
...(pending.threadId != null ? { message_thread_id: String(pending.threadId) } : {}),
|
|
1791
|
+
key: pending.key,
|
|
1792
|
+
stage_id: stageId,
|
|
1793
|
+
},
|
|
1794
|
+
}
|
|
1795
|
+
deliverResumeSyntheticOrBuffer(pending.agent, synthetic)
|
|
1796
|
+
return
|
|
1797
|
+
}
|
|
1798
|
+
|
|
1799
|
+
await ctx.answerCallbackQuery().catch(() => {})
|
|
1800
|
+
}
|
|
1801
|
+
|
|
1679
1802
|
async function handleVaultDeferCallback(ctx: Context, data: string): Promise<void> {
|
|
1680
1803
|
const senderId = String(ctx.from?.id ?? '')
|
|
1681
1804
|
const access = loadAccess()
|
|
@@ -2734,6 +2857,7 @@ async function handleAuthDashboardCallback(ctx: Context): Promise<void> {
|
|
|
2734
2857
|
handleMentalModelProposeCallback,
|
|
2735
2858
|
handleVaultRequestAccessCallback,
|
|
2736
2859
|
handleVaultRequestSaveCallback,
|
|
2860
|
+
handleSecretRequestCallback,
|
|
2737
2861
|
handleVaultDeferCallback,
|
|
2738
2862
|
parseGrantDuration,
|
|
2739
2863
|
formatGrantExpiry,
|
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
import { describe, it, expect } from 'vitest'
|
|
2
|
+
import { buildVaultRequestAccessKeyboard } from './vault-request-access-card.js'
|
|
3
|
+
import { buildMentalModelProposeKeyboard } from './mental-model-propose-card.js'
|
|
4
|
+
|
|
5
|
+
// The approve/deny keyboards for the vault-access (vra:) and mental-model
|
|
6
|
+
// proposal (mmp:) cards moved out of gateway.ts in #2996 P5. Their callback_data
|
|
7
|
+
// prefixes are the contract the callback dispatch table in gateway.ts + the
|
|
8
|
+
// handlers in callback-query-handlers.ts route on — pin them so a rename here
|
|
9
|
+
// can't silently break the tap routing.
|
|
10
|
+
describe('approval-card keyboards (#2996 P5 extraction)', () => {
|
|
11
|
+
it('buildVaultRequestAccessKeyboard carries the vra:* contract', () => {
|
|
12
|
+
const flat = buildVaultRequestAccessKeyboard('abcd1234').inline_keyboard.flat()
|
|
13
|
+
expect(flat.map((b) => b.callback_data)).toEqual([
|
|
14
|
+
'vra:approve:abcd1234',
|
|
15
|
+
'vra:deny:abcd1234',
|
|
16
|
+
])
|
|
17
|
+
expect(flat.map((b) => b.text)).toEqual(['✅ Approve', '🚫 Deny'])
|
|
18
|
+
})
|
|
19
|
+
|
|
20
|
+
it('buildMentalModelProposeKeyboard carries the mmp:* contract', () => {
|
|
21
|
+
const flat = buildMentalModelProposeKeyboard('abcd1234').inline_keyboard.flat()
|
|
22
|
+
expect(flat.map((b) => b.callback_data)).toEqual([
|
|
23
|
+
'mmp:approve:abcd1234',
|
|
24
|
+
'mmp:deny:abcd1234',
|
|
25
|
+
])
|
|
26
|
+
expect(flat.map((b) => b.text)).toEqual(['✅ Approve', '🚫 Deny'])
|
|
27
|
+
})
|
|
28
|
+
})
|