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.
Files changed (121) hide show
  1. package/dist/auth-broker/index.js +17 -1
  2. package/dist/cli/switchroom.js +847 -729
  3. package/dist/host-control/main.js +18 -2
  4. package/dist/vault/approvals/kernel-server.js +17 -1
  5. package/dist/vault/broker/server.js +44 -2
  6. package/package.json +2 -2
  7. package/profiles/_base/start.sh.hbs +105 -18
  8. package/telegram-plugin/dist/gateway/gateway.js +60612 -56998
  9. package/telegram-plugin/gateway/agent-button-callback-handler.ts +237 -0
  10. package/telegram-plugin/gateway/ask-callback-handler.ts +92 -0
  11. package/telegram-plugin/gateway/attachment-message-handlers.ts +152 -0
  12. package/telegram-plugin/gateway/boot-card.ts +169 -1
  13. package/telegram-plugin/gateway/bot-commands-model-effort.ts +209 -0
  14. package/telegram-plugin/gateway/bot-commands-start-info.ts +108 -0
  15. package/telegram-plugin/gateway/callback-query-handlers.ts +124 -0
  16. package/telegram-plugin/gateway/card-approval-keyboards.test.ts +28 -0
  17. package/telegram-plugin/gateway/card-tool-handlers.ts +639 -0
  18. package/telegram-plugin/gateway/checklist-message-handler.ts +107 -0
  19. package/telegram-plugin/gateway/delivery-confirm-wiring.ts +133 -0
  20. package/telegram-plugin/gateway/gateway.ts +1347 -6758
  21. package/telegram-plugin/gateway/inbound-interceptors.ts +1133 -0
  22. package/telegram-plugin/gateway/inbound-router.ts +400 -0
  23. package/telegram-plugin/gateway/liveness-wiring.ts +440 -0
  24. package/telegram-plugin/gateway/media-message-handlers.ts +256 -0
  25. package/telegram-plugin/gateway/mental-model-propose-card.ts +16 -0
  26. package/telegram-plugin/gateway/model-command.ts +23 -0
  27. package/telegram-plugin/gateway/narrative-lane.ts +865 -0
  28. package/telegram-plugin/gateway/obligation-wiring.ts +333 -0
  29. package/telegram-plugin/gateway/photo-message-handler.ts +80 -0
  30. package/telegram-plugin/gateway/pinned-message-handler.ts +86 -0
  31. package/telegram-plugin/gateway/secret-request-card.test.ts +46 -0
  32. package/telegram-plugin/gateway/secret-request-card.ts +45 -0
  33. package/telegram-plugin/gateway/stream-render.ts +2166 -0
  34. package/telegram-plugin/gateway/turn-end.ts +606 -0
  35. package/telegram-plugin/gateway/turn-start-surfaces.ts +298 -0
  36. package/telegram-plugin/gateway/vault-request-access-card.ts +16 -0
  37. package/telegram-plugin/gateway/vault-request-save-card.test.ts +49 -0
  38. package/telegram-plugin/gateway/vault-request-save-card.ts +52 -0
  39. package/telegram-plugin/gateway/voice-message-handler.ts +123 -0
  40. package/telegram-plugin/gateway/voice-ondemand-callback-handler.ts +204 -0
  41. package/telegram-plugin/gateway/worker-feed-dispatch.ts +40 -0
  42. package/telegram-plugin/narrative-dedup.ts +24 -1
  43. package/telegram-plugin/narrative-flush.ts +2 -2
  44. package/telegram-plugin/render/render.ts +25 -1
  45. package/telegram-plugin/status-no-truncate.ts +13 -0
  46. package/telegram-plugin/subagent-watcher.ts +186 -3
  47. package/telegram-plugin/tests/activity-card-wiring.test.ts +8 -3
  48. package/telegram-plugin/tests/activity-ever-opened-sticky.test.ts +18 -3
  49. package/telegram-plugin/tests/agent-button-callback-handler.test.ts +149 -0
  50. package/telegram-plugin/tests/ask-callback-handler.test.ts +118 -0
  51. package/telegram-plugin/tests/attachment-message-handlers.test.ts +135 -0
  52. package/telegram-plugin/tests/boot-card-routing.test.ts +139 -0
  53. package/telegram-plugin/tests/bot-commands-model-effort.test.ts +189 -0
  54. package/telegram-plugin/tests/bot-commands-start-info.test.ts +240 -0
  55. package/telegram-plugin/tests/buffer-gate-broadened.test.ts +15 -6
  56. package/telegram-plugin/tests/busy-ack-wiring.test.ts +6 -1
  57. package/telegram-plugin/tests/button-tap-turn-gated.test.ts +18 -9
  58. package/telegram-plugin/tests/callback-query-handlers.test.ts +101 -0
  59. package/telegram-plugin/tests/card-tool-handlers.test.ts +497 -0
  60. package/telegram-plugin/tests/catch-all-unhandled-message.test.ts +5 -2
  61. package/telegram-plugin/tests/checklist-message-handler.test.ts +160 -0
  62. package/telegram-plugin/tests/emission-authority-facade.test.ts +47 -10
  63. package/telegram-plugin/tests/emission-determinism-wiring.test.ts +27 -9
  64. package/telegram-plugin/tests/feed-heartbeat-liveness-open.test.ts +30 -7
  65. package/telegram-plugin/tests/gateway-boot-side-effect-gating.test.ts +39 -18
  66. package/telegram-plugin/tests/gateway-boot-smoke.test.ts +160 -0
  67. package/telegram-plugin/tests/gateway-handler-registration-wiring.test.ts +3 -7
  68. package/telegram-plugin/tests/gateway-loopback-paste-redact.test.ts +44 -29
  69. package/telegram-plugin/tests/gateway-outbound-redact.test.ts +8 -2
  70. package/telegram-plugin/tests/gateway-request-secret.test.ts +7 -3
  71. package/telegram-plugin/tests/gateway-secret-detect.test.ts +20 -10
  72. package/telegram-plugin/tests/gateway-session-model-relaunch.test.ts +8 -2
  73. package/telegram-plugin/tests/inbound-emit-after-intercepts.test.ts +14 -3
  74. package/telegram-plugin/tests/inbound-message-types.test.ts +52 -16
  75. package/telegram-plugin/tests/media-message-handlers.test.ts +276 -0
  76. package/telegram-plugin/tests/mental-model-propose-callback-gate.test.ts +8 -4
  77. package/telegram-plugin/tests/model-command.test.ts +30 -0
  78. package/telegram-plugin/tests/multitopic-routing-wiring.test.ts +27 -9
  79. package/telegram-plugin/tests/narrative-dedup.test.ts +32 -0
  80. package/telegram-plugin/tests/narrative-flush.test.ts +6 -2
  81. package/telegram-plugin/tests/narrative-lane-golden.test.ts +458 -0
  82. package/telegram-plugin/tests/no-reply-bounded-drain.test.ts +14 -3
  83. package/telegram-plugin/tests/pending-card-durability-wiring.test.ts +16 -7
  84. package/telegram-plugin/tests/per-topic-current-turn.test.ts +32 -8
  85. package/telegram-plugin/tests/photo-message-handler.test.ts +114 -0
  86. package/telegram-plugin/tests/pinned-message-handler.test.ts +108 -0
  87. package/telegram-plugin/tests/render/render.test.ts +42 -0
  88. package/telegram-plugin/tests/secret-detect-delete-must-surface-failures.test.ts +8 -4
  89. package/telegram-plugin/tests/secret-detect-fail-closed.test.ts +38 -28
  90. package/telegram-plugin/tests/secret-detect-oauth-code.test.ts +28 -18
  91. package/telegram-plugin/tests/silence-liveness-wiring.test.ts +22 -8
  92. package/telegram-plugin/tests/status-pin-service-message-suppression.test.ts +42 -49
  93. package/telegram-plugin/tests/stop-command.test.ts +22 -12
  94. package/telegram-plugin/tests/stream-render-golden.test.ts +424 -0
  95. package/telegram-plugin/tests/subagent-watcher-boot-skip-dead.test.ts +218 -0
  96. package/telegram-plugin/tests/subagent-watcher-resume-reregister.test.ts +14 -0
  97. package/telegram-plugin/tests/subagent-watcher.test.ts +35 -3
  98. package/telegram-plugin/tests/turn-flush-safety.test.ts +183 -5
  99. package/telegram-plugin/tests/turn-flush-suppression-wiring.test.ts +9 -4
  100. package/telegram-plugin/tests/vault-approval-posture.test.ts +8 -2
  101. package/telegram-plugin/tests/vault-grant-union.test.ts +4 -1
  102. package/telegram-plugin/tests/vault-key-regex-allows-slash.test.ts +16 -5
  103. package/telegram-plugin/tests/vault-request-access-tool.test.ts +10 -5
  104. package/telegram-plugin/tests/vault-request-access-unlock-resume.test.ts +4 -1
  105. package/telegram-plugin/tests/vault-subcommands.test.ts +6 -1
  106. package/telegram-plugin/tests/voice-message-handler.test.ts +111 -0
  107. package/telegram-plugin/tests/voice-ondemand-callback-handler.test.ts +140 -0
  108. package/telegram-plugin/tests/worker-activity-feed.test.ts +86 -19
  109. package/telegram-plugin/tests/worker-feed-coalesce.test.ts +110 -20
  110. package/telegram-plugin/tests/worker-feed-resume-guard.test.ts +86 -0
  111. package/telegram-plugin/tool-activity-summary.ts +83 -35
  112. package/telegram-plugin/turn-flush-safety.ts +80 -14
  113. package/telegram-plugin/uat/restart-capability.ts +76 -0
  114. package/telegram-plugin/uat/scenarios/bg-sub-agent-dispatch-dm.test.ts +14 -4
  115. package/telegram-plugin/uat/scenarios/bridge-flap-resilience-dm.test.ts +11 -1
  116. package/telegram-plugin/uat/scenarios/cross-turn-pending-progress-dm.test.ts +19 -2
  117. package/telegram-plugin/uat/scenarios/jtbd-always-on-after-restart-dm.test.ts +6 -12
  118. package/telegram-plugin/uat/scenarios/jtbd-deliberate-restart-resumes-dm.test.ts +6 -12
  119. package/telegram-plugin/uat/scenarios/jtbd-interrupted-turn-resumes-dm.test.ts +6 -12
  120. package/telegram-plugin/uat/scenarios/jtbd-multipart-render-dm.test.ts +47 -13
  121. 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
+ })