switchroom 0.14.27 → 0.14.29

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 (28) hide show
  1. package/dist/cli/switchroom.js +20 -4
  2. package/dist/host-control/main.js +2 -2
  3. package/package.json +1 -1
  4. package/telegram-plugin/bridge/bridge.ts +15 -0
  5. package/telegram-plugin/card-format.ts +7 -4
  6. package/telegram-plugin/dist/bridge/bridge.js +18 -0
  7. package/telegram-plugin/dist/gateway/gateway.js +2151 -1729
  8. package/telegram-plugin/dist/server.js +18 -0
  9. package/telegram-plugin/gateway/gateway.ts +464 -12
  10. package/telegram-plugin/history.ts +16 -4
  11. package/telegram-plugin/permission-title.ts +48 -0
  12. package/telegram-plugin/registry/subagents-schema.ts +35 -0
  13. package/telegram-plugin/registry/subagents.test.ts +78 -0
  14. package/telegram-plugin/secret-detect/patterns.ts +8 -0
  15. package/telegram-plugin/secret-detect/redact.ts +76 -0
  16. package/telegram-plugin/session-tail.ts +15 -0
  17. package/telegram-plugin/subagent-watcher.ts +19 -1
  18. package/telegram-plugin/tests/card-format.test.ts +16 -0
  19. package/telegram-plugin/tests/gateway-outbound-redact.test.ts +80 -0
  20. package/telegram-plugin/tests/gateway-request-secret.test.ts +78 -0
  21. package/telegram-plugin/tests/history.test.ts +59 -0
  22. package/telegram-plugin/tests/permission-title.test.ts +68 -0
  23. package/telegram-plugin/tests/permission-verdict-resume-guard.test.ts +35 -0
  24. package/telegram-plugin/tests/secret-detect-sanctum.test.ts +115 -0
  25. package/telegram-plugin/tests/session-tail.test.ts +43 -0
  26. package/telegram-plugin/tests/worker-activity-feed.test.ts +15 -0
  27. package/telegram-plugin/uat/scenarios/jtbd-request-secret-dm.test.ts +101 -0
  28. package/telegram-plugin/worker-activity-feed.ts +5 -2
@@ -49420,8 +49420,8 @@ var {
49420
49420
  } = import__.default;
49421
49421
 
49422
49422
  // src/build-info.ts
49423
- var VERSION = "0.14.27";
49424
- var COMMIT_SHA = "5ae9596e";
49423
+ var VERSION = "0.14.29";
49424
+ var COMMIT_SHA = "33f6300c";
49425
49425
 
49426
49426
  // src/cli/agent.ts
49427
49427
  init_source();
@@ -50220,6 +50220,22 @@ a flood. Going quiet mid-work is fine \u2014 going quiet *instead* of
50220
50220
  acknowledging, or *instead* of an update at a real milestone, is the
50221
50221
  black box this exists to prevent.
50222
50222
 
50223
+ ### Secrets \u2014 never ask for a chat paste
50224
+
50225
+ Never ask the user to paste a secret \u2014 an API key, token, password, or
50226
+ private key \u2014 as a normal chat message. It would persist in plaintext
50227
+ before anything can scrub it. Instead:
50228
+
50229
+ - Need a credential that isn't in the vault? Call
50230
+ \`request_secret(key, reason)\`. The operator gets a secure card, provides
50231
+ the value once, and it goes straight to the vault \u2014 you only ever see
50232
+ \`vault:<key>\`. After calling it, end your turn and wait for the reply.
50233
+ - The user *handed* you a value to keep? Use \`vault_request_save\`.
50234
+ - The key exists but you hit \`VAULT-BROKER-DENIED\`? Use \`vault_request_access\`.
50235
+
50236
+ Reference stored secrets only as \`vault:<key>\` \u2014 never echo a raw secret
50237
+ value back into chat.
50238
+
50223
50239
  ### Formatting \u2014 make it scannable
50224
50240
 
50225
50241
  \`reply\` and \`stream_reply\` render Markdown as Telegram HTML for you, so
@@ -73962,6 +73978,7 @@ var ANCHORED_PATTERNS = [
73962
73978
  { rule_id: "npm_token", regex: /\b(npm_[A-Za-z0-9]{10,})\b/g, captureIndex: 1, slugHint: "npm_token" },
73963
73979
  { rule_id: "telegram_bot_token_prefixed", regex: /\bbot(\d{6,}:[A-Za-z0-9_-]{20,})\b/g, captureIndex: 1, slugHint: "telegram_bot_token" },
73964
73980
  { rule_id: "telegram_bot_token", regex: /\b(\d{6,}:[A-Za-z0-9_-]{20,})\b/g, captureIndex: 1, slugHint: "telegram_bot_token" },
73981
+ { rule_id: "laravel_sanctum_token", regex: /\b(\d+\|[A-Za-z0-9]{40,})\b/g, captureIndex: 1, slugHint: "api_token" },
73965
73982
  { rule_id: "aws_access_key", regex: /\b(AKIA[0-9A-Z]{16})\b/g, captureIndex: 1, slugHint: "aws_access_key" },
73966
73983
  { rule_id: "jwt", regex: /\b(eyJ[A-Za-z0-9_-]{10,}\.[A-Za-z0-9_-]{10,}\.[A-Za-z0-9_-]{10,})\b/g, captureIndex: 1, slugHint: "jwt" }
73967
73984
  ];
@@ -74216,7 +74233,7 @@ function dropOverlaps(hits) {
74216
74233
  return out;
74217
74234
  }
74218
74235
 
74219
- // src/secret-detect/redact.ts
74236
+ // telegram-plugin/secret-detect/redact.ts
74220
74237
  var REDACTED_MARKER = "[REDACTED]";
74221
74238
  function redact(text) {
74222
74239
  if (!text || text.length === 0)
@@ -74239,7 +74256,6 @@ function redactedMarker(ruleId) {
74239
74256
  }
74240
74257
  return `[REDACTED:${trimmed}]`;
74241
74258
  }
74242
-
74243
74259
  // src/issues/store.ts
74244
74260
  var ISSUES_FILE = "issues.jsonl";
74245
74261
  var ISSUES_LOCK = "issues.lock";
@@ -19866,6 +19866,7 @@ var ANCHORED_PATTERNS = [
19866
19866
  { rule_id: "npm_token", regex: /\b(npm_[A-Za-z0-9]{10,})\b/g, captureIndex: 1, slugHint: "npm_token" },
19867
19867
  { rule_id: "telegram_bot_token_prefixed", regex: /\bbot(\d{6,}:[A-Za-z0-9_-]{20,})\b/g, captureIndex: 1, slugHint: "telegram_bot_token" },
19868
19868
  { rule_id: "telegram_bot_token", regex: /\b(\d{6,}:[A-Za-z0-9_-]{20,})\b/g, captureIndex: 1, slugHint: "telegram_bot_token" },
19869
+ { rule_id: "laravel_sanctum_token", regex: /\b(\d+\|[A-Za-z0-9]{40,})\b/g, captureIndex: 1, slugHint: "api_token" },
19869
19870
  { rule_id: "aws_access_key", regex: /\b(AKIA[0-9A-Z]{16})\b/g, captureIndex: 1, slugHint: "aws_access_key" },
19870
19871
  { rule_id: "jwt", regex: /\b(eyJ[A-Za-z0-9_-]{10,}\.[A-Za-z0-9_-]{10,}\.[A-Za-z0-9_-]{10,})\b/g, captureIndex: 1, slugHint: "jwt" }
19871
19872
  ];
@@ -20120,7 +20121,7 @@ function dropOverlaps(hits) {
20120
20121
  return out;
20121
20122
  }
20122
20123
 
20123
- // src/secret-detect/redact.ts
20124
+ // telegram-plugin/secret-detect/redact.ts
20124
20125
  var REDACTED_MARKER = "[REDACTED]";
20125
20126
  function redact(text) {
20126
20127
  if (!text || text.length === 0)
@@ -20143,7 +20144,6 @@ function redactedMarker(ruleId) {
20143
20144
  }
20144
20145
  return `[REDACTED:${trimmed}]`;
20145
20146
  }
20146
-
20147
20147
  // src/cli/install-detect.ts
20148
20148
  import * as fs from "node:fs";
20149
20149
  import * as path from "node:path";
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "switchroom",
3
- "version": "0.14.27",
3
+ "version": "0.14.29",
4
4
  "description": "Run Claude Code 24/7 on your Claude Pro/Max subscription over Telegram. Open-source alternative to OpenClaw and NanoClaw — no API keys.",
5
5
  "type": "module",
6
6
  "bin": {
@@ -438,6 +438,21 @@ const TOOL_SCHEMAS = [
438
438
  required: ['chat_id', 'key'],
439
439
  },
440
440
  },
441
+ {
442
+ name: 'request_secret',
443
+ description:
444
+ 'Ask the operator to PROVIDE a secret you do not have, securely — NEVER ask the user to paste a token/key/password as a normal chat message. Use this when you need a credential that is not in the vault (a `vault:<key>` reference is missing/empty, or you know an upcoming task needs one you lack). Renders a Telegram card with [Provide securely] [Decline]; on tap, the operator sends the value once and the gateway DELETES their message instantly and writes it straight to the vault — the raw value is never echoed back to you. You receive only `vault:<key>`. This is the sibling of `vault_request_save` (use that when the user already handed YOU a value to store) and `vault_request_access` (use that when the key exists but you lack read access). After firing this tool, END YOUR TURN cleanly — a fresh inbound message arrives once the operator provides (or declines) the secret. Do NOT call this for keys you already have, and do NOT spam (one open request per key; the operator sees every card).',
445
+ inputSchema: {
446
+ type: 'object',
447
+ properties: {
448
+ chat_id: { type: 'string', description: 'Chat to render the card in (use the chat_id of the user message that triggered the workflow).' },
449
+ key: { type: 'string', description: 'Vault key to store the provided secret under. Use lowercase namespaced snake_case, e.g. `coolify/api-token`.' },
450
+ reason: { type: 'string', description: 'REQUIRED in practice — one-line human-readable rationale rendered on the card (e.g. "to trigger a redeploy on Coolify"). Omitting it makes the operator more likely to Decline.' },
451
+ message_thread_id: { type: 'string', description: 'Forum topic thread ID. Auto-applied from the last inbound message if not specified.' },
452
+ },
453
+ required: ['chat_id', 'key'],
454
+ },
455
+ },
441
456
  ]
442
457
 
443
458
  mcp.setRequestHandler(ListToolsRequestSchema, async () => ({ tools: TOOL_SCHEMAS }))
@@ -85,10 +85,13 @@ export function stripMarkdown(s: string): string {
85
85
  out = out.replace(/\*(.+?)\*/g, '$1');
86
86
  out = out.replace(/(?<![A-Za-z0-9])_(.+?)_(?![A-Za-z0-9])/g, '$1');
87
87
  // Leading block markup: heading, blockquote, bullet, ordered item.
88
- out = out.replace(/^\s{0,3}#{1,6}\s+/, '');
89
- out = out.replace(/^\s{0,3}>\s?/, '');
90
- out = out.replace(/^\s{0,3}[-*+]\s+/, '');
91
- out = out.replace(/^\s{0,3}\d+[.)]\s+/, '');
88
+ // `gm` so the markers are stripped on EVERY line, not just the string
89
+ // start a multi-line summary like "Done.\n\n## Summary\n…" must not
90
+ // leak a raw `## Summary` when rendered as a single card step.
91
+ out = out.replace(/^\s{0,3}#{1,6}\s+/gm, '');
92
+ out = out.replace(/^\s{0,3}>\s?/gm, '');
93
+ out = out.replace(/^\s{0,3}[-*+]\s+/gm, '');
94
+ out = out.replace(/^\s{0,3}\d+[.)]\s+/gm, '');
92
95
  // Residual unpaired bold markers (a lone `*` is left alone so `3 * 4`
93
96
  // survives; only the doubled form is markup-by-construction).
94
97
  out = out.replace(/\*\*/g, '');
@@ -23349,6 +23349,10 @@ function projectSubagentLine(line, agentId, state) {
23349
23349
  events.push({ kind: "sub_agent_text", agentId, text });
23350
23350
  }
23351
23351
  }
23352
+ const stopReason = message?.stop_reason;
23353
+ if (stopReason === "end_turn") {
23354
+ events.push({ kind: "sub_agent_turn_end", agentId });
23355
+ }
23352
23356
  return events;
23353
23357
  }
23354
23358
  if (type === "system" && obj.subtype === "turn_duration") {
@@ -24914,6 +24918,20 @@ var TOOL_SCHEMAS = [
24914
24918
  },
24915
24919
  required: ["chat_id", "key"]
24916
24920
  }
24921
+ },
24922
+ {
24923
+ name: "request_secret",
24924
+ description: "Ask the operator to PROVIDE a secret you do not have, securely \u2014 NEVER ask the user to paste a token/key/password as a normal chat message. Use this when you need a credential that is not in the vault (a `vault:<key>` reference is missing/empty, or you know an upcoming task needs one you lack). Renders a Telegram card with [Provide securely] [Decline]; on tap, the operator sends the value once and the gateway DELETES their message instantly and writes it straight to the vault \u2014 the raw value is never echoed back to you. You receive only `vault:<key>`. This is the sibling of `vault_request_save` (use that when the user already handed YOU a value to store) and `vault_request_access` (use that when the key exists but you lack read access). After firing this tool, END YOUR TURN cleanly \u2014 a fresh inbound message arrives once the operator provides (or declines) the secret. Do NOT call this for keys you already have, and do NOT spam (one open request per key; the operator sees every card).",
24925
+ inputSchema: {
24926
+ type: "object",
24927
+ properties: {
24928
+ chat_id: { type: "string", description: "Chat to render the card in (use the chat_id of the user message that triggered the workflow)." },
24929
+ key: { type: "string", description: "Vault key to store the provided secret under. Use lowercase namespaced snake_case, e.g. `coolify/api-token`." },
24930
+ reason: { type: "string", description: 'REQUIRED in practice \u2014 one-line human-readable rationale rendered on the card (e.g. "to trigger a redeploy on Coolify"). Omitting it makes the operator more likely to Decline.' },
24931
+ message_thread_id: { type: "string", description: "Forum topic thread ID. Auto-applied from the last inbound message if not specified." }
24932
+ },
24933
+ required: ["chat_id", "key"]
24934
+ }
24917
24935
  }
24918
24936
  ];
24919
24937
  mcp.setRequestHandler(ListToolsRequestSchema2, async () => ({ tools: TOOL_SCHEMAS }));