switchroom 0.16.23 → 0.16.27
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/self-improve-apply-guard-pretool.mjs +6 -0
- package/dist/cli/skill-validate-pretool.mjs +2936 -119
- package/dist/cli/switchroom.js +804 -465
- package/dist/host-control/main.js +169 -163
- package/dist/vault/approvals/kernel-server.js +82 -82
- package/dist/vault/broker/server.js +83 -83
- package/package.json +4 -4
- package/telegram-plugin/answer-stream.ts +20 -49
- package/telegram-plugin/auth-snapshot-format.ts +27 -30
- package/telegram-plugin/auto-fallback-fleet.ts +6 -11
- package/telegram-plugin/bridge/bridge.ts +1 -1
- package/telegram-plugin/card-format.ts +28 -25
- package/telegram-plugin/credits-watch.ts +5 -10
- package/telegram-plugin/dist/bridge/bridge.js +113 -113
- package/telegram-plugin/dist/gateway/gateway.js +2085 -2102
- package/telegram-plugin/dist/server.js +161 -161
- package/telegram-plugin/draft-stream.ts +4 -4
- package/telegram-plugin/format.ts +427 -680
- package/telegram-plugin/gateway/approval-callback.ts +2 -3
- package/telegram-plugin/gateway/approval-card.test.ts +17 -4
- package/telegram-plugin/gateway/approval-card.ts +16 -6
- package/telegram-plugin/gateway/approvals-commands.ts +18 -24
- package/telegram-plugin/gateway/auth-command.ts +74 -74
- package/telegram-plugin/gateway/auth-line.ts +5 -15
- package/telegram-plugin/gateway/boot-card.ts +20 -22
- package/telegram-plugin/gateway/boot-version.ts +3 -2
- package/telegram-plugin/gateway/config-approval-handler.test.ts +35 -33
- package/telegram-plugin/gateway/config-approval-handler.ts +24 -24
- package/telegram-plugin/gateway/config-snapshot.ts +9 -9
- package/telegram-plugin/gateway/diff-preview-card.test.ts +8 -8
- package/telegram-plugin/gateway/diff-preview-card.ts +2 -5
- package/telegram-plugin/gateway/disconnect-flush.ts +0 -4
- package/telegram-plugin/gateway/drive-write-approval.test.ts +10 -10
- package/telegram-plugin/gateway/drive-write-approval.ts +14 -8
- package/telegram-plugin/gateway/effort-command.ts +17 -17
- package/telegram-plugin/gateway/folder-picker-handler.test.ts +8 -2
- package/telegram-plugin/gateway/folder-picker-handler.ts +3 -4
- package/telegram-plugin/gateway/gateway.ts +881 -633
- package/telegram-plugin/gateway/inject-handler.test.ts +15 -13
- package/telegram-plugin/gateway/inject-handler.ts +5 -5
- package/telegram-plugin/gateway/ipc-protocol.ts +33 -1
- package/telegram-plugin/gateway/ipc-server.ts +39 -6
- package/telegram-plugin/gateway/linear-activity.ts +16 -14
- package/telegram-plugin/gateway/linear-setup.ts +1 -1
- package/telegram-plugin/gateway/model-command.ts +25 -25
- package/telegram-plugin/gateway/oversize-card-body.ts +6 -7
- package/telegram-plugin/gateway/permission-timeout.ts +76 -0
- package/telegram-plugin/gateway/skill-proposal-card.ts +167 -0
- package/telegram-plugin/inline-keyboard-callbacks.ts +19 -13
- package/telegram-plugin/issues-card.ts +6 -7
- package/telegram-plugin/model-unavailable.ts +8 -12
- package/telegram-plugin/operator-events-history.ts +1 -1
- package/telegram-plugin/operator-events.ts +24 -28
- package/telegram-plugin/package.json +1 -1
- package/telegram-plugin/pending-work-progress.ts +36 -36
- package/telegram-plugin/permission-title.ts +39 -20
- package/telegram-plugin/pty-partial-handler.ts +5 -13
- package/telegram-plugin/quota-check.ts +5 -5
- package/telegram-plugin/quota-watch.ts +13 -18
- package/telegram-plugin/recent-outbound-dedup.ts +5 -5
- package/telegram-plugin/registry/turns-schema.ts +43 -3
- package/telegram-plugin/retry-api-call.ts +42 -7
- package/telegram-plugin/rich-send.ts +86 -0
- package/telegram-plugin/secret-detect/vault-error.test.ts +6 -6
- package/telegram-plugin/secret-detect/vault-error.ts +29 -22
- package/telegram-plugin/shared/bot-runtime.ts +29 -7
- package/telegram-plugin/silence-poke.ts +26 -69
- package/telegram-plugin/silent-reply-anchor.ts +9 -2
- package/telegram-plugin/slot-banner-driver.ts +9 -6
- package/telegram-plugin/slot-banner.ts +5 -8
- package/telegram-plugin/status-no-truncate.ts +11 -5
- package/telegram-plugin/steering.ts +0 -4
- package/telegram-plugin/stream-controller.ts +59 -62
- package/telegram-plugin/stream-reply-handler.ts +49 -98
- package/telegram-plugin/subagent-watcher.ts +2 -2
- package/telegram-plugin/tests/answer-stream-silent-markers.test.ts +5 -2
- package/telegram-plugin/tests/answer-stream.test.ts +54 -63
- package/telegram-plugin/tests/auth-command-format2.test.ts +4 -4
- package/telegram-plugin/tests/auth-command-vernacular.test.ts +3 -2
- package/telegram-plugin/tests/auth-snapshot-format.test.ts +19 -18
- package/telegram-plugin/tests/auto-fallback-fleet.test.ts +15 -13
- package/telegram-plugin/tests/boot-card-reason-to-render.test.ts +27 -12
- package/telegram-plugin/tests/boot-card-render.test.ts +59 -48
- package/telegram-plugin/tests/boot-version-string.test.ts +0 -0
- package/telegram-plugin/tests/bot-api.harness.ts +23 -1
- package/telegram-plugin/tests/bot-runtime.test.ts +23 -18
- package/telegram-plugin/tests/card-format.test.ts +6 -4
- package/telegram-plugin/tests/config-snapshot.test.ts +1 -1
- package/telegram-plugin/tests/credits-watch.test.ts +5 -5
- package/telegram-plugin/tests/fake-bot-api.ts +58 -4
- package/telegram-plugin/tests/finalize-callback.test.ts +11 -9
- package/telegram-plugin/tests/foreground-nesting.test.ts +1 -1
- package/telegram-plugin/tests/gateway-disconnect-flush.test.ts +3 -13
- package/telegram-plugin/tests/ipc-server-validate-send-outbound.test.ts +6 -2
- package/telegram-plugin/tests/issues-card.test.ts +15 -12
- package/telegram-plugin/tests/length-error-classify.test.ts +131 -0
- package/telegram-plugin/tests/linear-agent-activity.test.ts +8 -5
- package/telegram-plugin/tests/model-command.test.ts +2 -2
- package/telegram-plugin/tests/model-unavailable.test.ts +13 -13
- package/telegram-plugin/tests/multi-turn-continuity.test.ts +6 -10
- package/telegram-plugin/tests/operator-events.test.ts +7 -9
- package/telegram-plugin/tests/paragraph-normalizer.test.ts +273 -0
- package/telegram-plugin/tests/pending-work-progress.test.ts +20 -21
- package/telegram-plugin/tests/permission-no-repeat-wiring.test.ts +12 -2
- package/telegram-plugin/tests/permission-timeout.test.ts +77 -0
- package/telegram-plugin/tests/permission-title.test.ts +88 -41
- package/telegram-plugin/tests/pty-partial-handler.test.ts +8 -8
- package/telegram-plugin/tests/quota-check.test.ts +3 -3
- package/telegram-plugin/tests/quota-watch.test.ts +8 -4
- package/telegram-plugin/tests/secret-detect-delete-must-surface-failures.test.ts +4 -3
- package/telegram-plugin/tests/silence-poke.test.ts +75 -112
- package/telegram-plugin/tests/single-mode-stream-reply.test.ts +137 -0
- package/telegram-plugin/tests/skill-proposal-card.test.ts +103 -0
- package/telegram-plugin/tests/slot-banner-driver.e2e.test.ts +36 -24
- package/telegram-plugin/tests/slot-banner.test.ts +9 -6
- package/telegram-plugin/tests/status-accent.test.ts +29 -32
- package/telegram-plugin/tests/{stream-controller-html-fallback.test.ts → stream-controller-parse-fallback.test.ts} +40 -42
- package/telegram-plugin/tests/stream-controller.test.ts +63 -52
- package/telegram-plugin/tests/stream-reply-error-paths.test.ts +43 -38
- package/telegram-plugin/tests/stream-reply-handler.test.ts +122 -249
- package/telegram-plugin/tests/streaming-e2e.test.ts +35 -30
- package/telegram-plugin/tests/streaming-orchestration.test.ts +29 -28
- package/telegram-plugin/tests/telegram-format.test.ts +120 -1083
- package/telegram-plugin/tests/tool-activity-summary.test.ts +144 -145
- package/telegram-plugin/tests/welcome-text.test.ts +72 -65
- package/telegram-plugin/tests/worker-activity-feed.test.ts +119 -137
- package/telegram-plugin/text-voice-scrub.ts +8 -11
- package/telegram-plugin/tool-activity-summary.ts +29 -29
- package/telegram-plugin/welcome-text.ts +82 -83
- package/telegram-plugin/worker-activity-feed.ts +2 -3
- package/telegram-plugin/html-sanitize.ts +0 -244
- package/telegram-plugin/tests/html-sanitize.test.ts +0 -146
- package/telegram-plugin/tests/parse-mode-rotation.test.ts +0 -162
|
@@ -177,7 +177,7 @@ import {
|
|
|
177
177
|
STATUS_LINE_MAX,
|
|
178
178
|
NESTED_PREFIX,
|
|
179
179
|
} from './status-no-truncate.js'
|
|
180
|
-
import {
|
|
180
|
+
import { escapeMarkdown, stripMarkdown, truncate } from './card-format.js'
|
|
181
181
|
import { isTelegramSurfaceTool } from './tool-names.js'
|
|
182
182
|
|
|
183
183
|
/**
|
|
@@ -264,11 +264,11 @@ export function renderActivityHeader(
|
|
|
264
264
|
): [string, string] {
|
|
265
265
|
const toolWord = toolCount === 1 ? 'tool' : 'tools'
|
|
266
266
|
const elapsed = formatFeedElapsed(elapsedMs)
|
|
267
|
-
const descPart = description.length > 0 ? ` ·
|
|
268
|
-
const line1 = `${emoji}
|
|
267
|
+
const descPart = description.length > 0 ? ` · _${escapeMarkdown(description)}_` : ''
|
|
268
|
+
const line1 = `${emoji} **${escapeMarkdown(label)}**${descPart}`
|
|
269
269
|
const line2 = state === 'running'
|
|
270
|
-
?
|
|
271
|
-
:
|
|
270
|
+
? `_${elapsed} · ${toolCount} ${toolWord}_`
|
|
271
|
+
: `_${state} · ${toolCount} ${toolWord} · ${elapsed}_`
|
|
272
272
|
return [line1, line2]
|
|
273
273
|
}
|
|
274
274
|
|
|
@@ -286,14 +286,14 @@ export function formatFeedElapsed(ms: number): string {
|
|
|
286
286
|
// 1. stripMarkdown(raw)
|
|
287
287
|
// 2. .replace(/\s+/g, ' ').trim()
|
|
288
288
|
// 3. truncate(_, STATUS_LINE_MAX)
|
|
289
|
-
// 4.
|
|
289
|
+
// 4. escapeMarkdown(_) ← escape is ALWAYS the last per-line op.
|
|
290
290
|
// Escaping last is load-bearing: clipping an already-escaped string can split
|
|
291
|
-
//
|
|
291
|
+
// a markdown escape (\* → \), which renders wrong.
|
|
292
292
|
|
|
293
|
-
/** Clean + clip + escape a single raw step line. Returns ready-to-wrap
|
|
293
|
+
/** Clean + clip + escape a single raw step line. Returns ready-to-wrap markdown. */
|
|
294
294
|
function escapeStepLine(raw: string): string {
|
|
295
295
|
const cleaned = stripMarkdown(raw).replace(/\s+/g, ' ').trim()
|
|
296
|
-
return
|
|
296
|
+
return escapeMarkdown(truncate(cleaned, STATUS_LINE_MAX))
|
|
297
297
|
}
|
|
298
298
|
|
|
299
299
|
/**
|
|
@@ -317,10 +317,10 @@ export function renderStepFeed(
|
|
|
317
317
|
if (steps.length === 0) return
|
|
318
318
|
const shown = steps.slice(-STATUS_ROLLING_LINES)
|
|
319
319
|
const hidden = steps.length - shown.length
|
|
320
|
-
if (hidden > 0) out.push(
|
|
320
|
+
if (hidden > 0) out.push(`_✓ +${hidden} earlier…_`)
|
|
321
321
|
const lastIdx = shown.length - 1
|
|
322
322
|
shown.forEach((s, i) => {
|
|
323
|
-
out.push(!allDone && i === lastIdx ?
|
|
323
|
+
out.push(!allDone && i === lastIdx ? `**→ ${s}${liveSuffix}**` : `_✓ ${s}_`)
|
|
324
324
|
})
|
|
325
325
|
}
|
|
326
326
|
|
|
@@ -399,18 +399,18 @@ export function renderStatusCard(opts: StatusCardOpts): string | null {
|
|
|
399
399
|
// Parent lines all render done — the live → step lives in the nested block.
|
|
400
400
|
const shownParent = steps.slice(-STATUS_ROLLING_LINES)
|
|
401
401
|
const hiddenParent = steps.length - shownParent.length
|
|
402
|
-
if (hiddenParent > 0) out.push(
|
|
403
|
-
for (const s of shownParent) out.push(
|
|
402
|
+
if (hiddenParent > 0) out.push(`_✓ +${hiddenParent} earlier…_`)
|
|
403
|
+
for (const s of shownParent) out.push(`_✓ ${s}_`)
|
|
404
404
|
// Child block.
|
|
405
405
|
const shownChild = children.slice(-STATUS_ROLLING_LINES)
|
|
406
406
|
const hiddenChild = children.length - shownChild.length
|
|
407
|
-
if (hiddenChild > 0) out.push(`${NESTED_PREFIX}
|
|
407
|
+
if (hiddenChild > 0) out.push(`${NESTED_PREFIX}_+${hiddenChild} earlier…_`)
|
|
408
408
|
const lastChildIdx = shownChild.length - 1
|
|
409
409
|
shownChild.forEach((s, i) => {
|
|
410
410
|
out.push(
|
|
411
411
|
i === lastChildIdx && !final
|
|
412
|
-
? `${NESTED_PREFIX}
|
|
413
|
-
: `${NESTED_PREFIX}
|
|
412
|
+
? `${NESTED_PREFIX}**→ ${s}${liveSuffix}**`
|
|
413
|
+
: `${NESTED_PREFIX}_${s}_`,
|
|
414
414
|
)
|
|
415
415
|
})
|
|
416
416
|
} else {
|
|
@@ -418,12 +418,12 @@ export function renderStatusCard(opts: StatusCardOpts): string | null {
|
|
|
418
418
|
}
|
|
419
419
|
|
|
420
420
|
if (final && stepCount != null && stepCount > 0) {
|
|
421
|
-
out.push(
|
|
421
|
+
out.push(`_✓ ${stepCount} steps_`)
|
|
422
422
|
}
|
|
423
423
|
|
|
424
424
|
if (result != null && result.text.length > 0) {
|
|
425
425
|
out.push(WORKER_RESULT_RULE)
|
|
426
|
-
out.push(`${result.emoji}
|
|
426
|
+
out.push(`${result.emoji} _${escapeMarkdown(truncate(result.text, WORKER_RESULT_MAX))}_`)
|
|
427
427
|
}
|
|
428
428
|
|
|
429
429
|
// out always carries the two header lines, so it is never empty — but guard
|
|
@@ -444,7 +444,7 @@ const WORKER_RESULT_MAX = 320
|
|
|
444
444
|
* drops the oldest body bullets one at a time, re-inserting a `+N earlier…`
|
|
445
445
|
* marker, until the card fits STATUS_CARD_CHAR_BUDGET. In the extreme case
|
|
446
446
|
* (a single newest bullet that is itself oversized) it truncates the RAW
|
|
447
|
-
* newest text, THEN escapes, THEN wraps — never slicing already-escaped
|
|
447
|
+
* newest text, THEN escapes, THEN wraps — never slicing already-escaped markdown.
|
|
448
448
|
*/
|
|
449
449
|
function fitCardToBudget(opts: StatusCardOpts, headerLines: string[]): string {
|
|
450
450
|
const { final = false, liveSuffix = '', stepCount, result } = opts
|
|
@@ -454,10 +454,10 @@ function fitCardToBudget(opts: StatusCardOpts, headerLines: string[]): string {
|
|
|
454
454
|
|
|
455
455
|
// Fixed footer/result lines (always kept).
|
|
456
456
|
const footerLines: string[] = []
|
|
457
|
-
if (final && stepCount != null && stepCount > 0) footerLines.push(
|
|
457
|
+
if (final && stepCount != null && stepCount > 0) footerLines.push(`_✓ ${stepCount} steps_`)
|
|
458
458
|
if (result != null && result.text.length > 0) {
|
|
459
459
|
footerLines.push(WORKER_RESULT_RULE)
|
|
460
|
-
footerLines.push(`${result.emoji}
|
|
460
|
+
footerLines.push(`${result.emoji} _${escapeMarkdown(truncate(result.text, WORKER_RESULT_MAX))}_`)
|
|
461
461
|
}
|
|
462
462
|
const fixedCost = [...headerLines, ...footerLines].join('\n').length
|
|
463
463
|
|
|
@@ -468,17 +468,17 @@ function fitCardToBudget(opts: StatusCardOpts, headerLines: string[]): string {
|
|
|
468
468
|
const prefix = hasChildren ? NESTED_PREFIX : ''
|
|
469
469
|
|
|
470
470
|
const buildBullet = (esc: string, isLast: boolean): string =>
|
|
471
|
-
!final && isLast ? `${prefix}
|
|
471
|
+
!final && isLast ? `${prefix}**→ ${esc}${liveSuffix}**` : `${prefix}_${esc}_`
|
|
472
472
|
|
|
473
473
|
// Parent-collapsed marker line when we are dropping children but parent steps exist.
|
|
474
474
|
const parentMarker =
|
|
475
|
-
hasChildren && rawSteps.length > 0 ?
|
|
475
|
+
hasChildren && rawSteps.length > 0 ? `_✓ +${rawSteps.length} earlier…_` : null
|
|
476
476
|
|
|
477
477
|
for (let drop = 1; drop < escapedBody.length; drop++) {
|
|
478
478
|
const shown = escapedBody.slice(drop)
|
|
479
479
|
const lines: string[] = [...headerLines]
|
|
480
480
|
if (parentMarker != null) lines.push(parentMarker)
|
|
481
|
-
lines.push(hasChildren ? `${NESTED_PREFIX}
|
|
481
|
+
lines.push(hasChildren ? `${NESTED_PREFIX}_+${drop} earlier…_` : `_✓ +${drop} earlier…_`)
|
|
482
482
|
const lastIdx = shown.length - 1
|
|
483
483
|
shown.forEach((esc, i) => lines.push(buildBullet(esc, i === lastIdx)))
|
|
484
484
|
lines.push(...footerLines)
|
|
@@ -491,19 +491,19 @@ function fitCardToBudget(opts: StatusCardOpts, headerLines: string[]): string {
|
|
|
491
491
|
// escaping can expand the string (& → &).
|
|
492
492
|
const rawNewest = body.length > 0 ? stripMarkdown(body[body.length - 1]).replace(/\s+/g, ' ').trim() : ''
|
|
493
493
|
const wrapperOverhead = final
|
|
494
|
-
? (prefix + '
|
|
495
|
-
: (prefix + '
|
|
494
|
+
? (prefix + '_✓ _').length
|
|
495
|
+
: (prefix + '**→ **').length + liveSuffix.length
|
|
496
496
|
const headerFooterCost =
|
|
497
497
|
fixedCost + (fixedCost > 0 ? 1 : 0) + (parentMarker != null ? parentMarker.length + 1 : 0)
|
|
498
498
|
const budget = STATUS_CARD_CHAR_BUDGET - headerFooterCost - wrapperOverhead
|
|
499
499
|
let raw = rawNewest.slice(0, Math.max(0, budget))
|
|
500
|
-
let newest =
|
|
500
|
+
let newest = escapeMarkdown(raw)
|
|
501
501
|
while (raw.length > 0 && wrapperOverhead + headerFooterCost + newest.length > STATUS_CARD_CHAR_BUDGET) {
|
|
502
502
|
const excess = wrapperOverhead + headerFooterCost + newest.length - STATUS_CARD_CHAR_BUDGET
|
|
503
503
|
raw = raw.slice(0, Math.max(0, raw.length - excess - 1))
|
|
504
|
-
newest =
|
|
504
|
+
newest = escapeMarkdown(raw)
|
|
505
505
|
}
|
|
506
|
-
const newestLine = final ? `${prefix}
|
|
506
|
+
const newestLine = final ? `${prefix}_✓ ${newest}_` : `${prefix}**→ ${newest}${liveSuffix}**`
|
|
507
507
|
const lines: string[] = [...headerLines]
|
|
508
508
|
if (parentMarker != null) lines.push(parentMarker)
|
|
509
509
|
lines.push(newestLine)
|
|
@@ -5,13 +5,15 @@
|
|
|
5
5
|
* 1. single-sourced (no drift between gateway mode and monolith mode)
|
|
6
6
|
* 2. unit-testable without needing a grammy Context
|
|
7
7
|
*
|
|
8
|
-
* All functions return
|
|
9
|
-
* for
|
|
10
|
-
* for
|
|
11
|
-
* monospace inline and
|
|
8
|
+
* All functions return GFM markdown strings (bold, italic, inline code)
|
|
9
|
+
* ready for the rich-message path (#2669). The code-span wrapper is
|
|
10
|
+
* deliberate for command names and agent identifiers, which render as
|
|
11
|
+
* monospace inline (and inside a code span, characters are literal so no
|
|
12
|
+
* escaping is needed).
|
|
12
13
|
*/
|
|
13
14
|
|
|
14
15
|
import { maskUsername } from "./demo-mask.js";
|
|
16
|
+
import { escapeMarkdown } from "./card-format.js";
|
|
15
17
|
|
|
16
18
|
export type AuthSummary = {
|
|
17
19
|
authenticated: boolean;
|
|
@@ -93,14 +95,11 @@ export type AgentMetadata = {
|
|
|
93
95
|
live?: StatusProbeRow[];
|
|
94
96
|
};
|
|
95
97
|
|
|
96
|
-
//
|
|
97
|
-
//
|
|
98
|
+
// Markdown escaper for dynamic values interpolated into bold/plain card
|
|
99
|
+
// text (#2669). Kept under the legacy `escapeHtml` name so existing imports
|
|
100
|
+
// don't churn; it now escapes GFM-markdown specials.
|
|
98
101
|
export function escapeHtml(text: string): string {
|
|
99
|
-
return text
|
|
100
|
-
.replace(/&/g, "&")
|
|
101
|
-
.replace(/</g, "<")
|
|
102
|
-
.replace(/>/g, ">")
|
|
103
|
-
.replace(/"/g, """);
|
|
102
|
+
return escapeMarkdown(text);
|
|
104
103
|
}
|
|
105
104
|
|
|
106
105
|
/**
|
|
@@ -137,9 +136,9 @@ export function formatAgentLine(meta: AgentMetadata): string {
|
|
|
137
136
|
// restart, when the session reverts to the configured model).
|
|
138
137
|
const session =
|
|
139
138
|
meta.sessionModel && meta.sessionModel.length > 0
|
|
140
|
-
? ` · live session:
|
|
139
|
+
? ` · live session: \`${meta.sessionModel}\``
|
|
141
140
|
: "";
|
|
142
|
-
return
|
|
141
|
+
return `**${escapeHtml(meta.agentName)}** · model: \`${m}\`${session}${topic}`;
|
|
143
142
|
}
|
|
144
143
|
|
|
145
144
|
/**
|
|
@@ -150,15 +149,15 @@ export function formatAgentLine(meta: AgentMetadata): string {
|
|
|
150
149
|
export function startText(agentName: string, dmDisabled: boolean): string {
|
|
151
150
|
if (dmDisabled) return "This bot isn't accepting new connections.";
|
|
152
151
|
return [
|
|
153
|
-
|
|
152
|
+
`**Switchroom** — Telegram on your Claude Pro or Max subscription.`,
|
|
154
153
|
``,
|
|
155
|
-
`This bot is the
|
|
154
|
+
`This bot is the **${escapeHtml(agentName)}** agent. Pair first, then send messages here and they reach the agent; replies and reactions come back.`,
|
|
156
155
|
``,
|
|
157
|
-
|
|
156
|
+
`**To pair:**`,
|
|
158
157
|
`1. DM me anything — you'll get a 6-char code`,
|
|
159
|
-
`2. In Claude Code:
|
|
158
|
+
`2. In Claude Code: \`/telegram:access pair <code>\``,
|
|
160
159
|
``,
|
|
161
|
-
`After pairing, try
|
|
160
|
+
`After pairing, try \`/status\` or \`/commands\`.`,
|
|
162
161
|
].join("\n");
|
|
163
162
|
}
|
|
164
163
|
|
|
@@ -168,16 +167,16 @@ export function startText(agentName: string, dmDisabled: boolean): string {
|
|
|
168
167
|
*/
|
|
169
168
|
export function helpText(agentName: string): string {
|
|
170
169
|
return [
|
|
171
|
-
|
|
170
|
+
`**Switchroom** — your Pro/Max subscription, wired to Telegram.`,
|
|
172
171
|
``,
|
|
173
|
-
`This bot is the
|
|
172
|
+
`This bot is the **${escapeHtml(agentName)}** agent. Text and photos route through to it; replies, reactions and progress cards come back.`,
|
|
174
173
|
``,
|
|
175
|
-
`Tool approvals surface as inline buttons (✅ / ❌) or via
|
|
174
|
+
`Tool approvals surface as inline buttons (✅ / ❌) or via \`/approve\`, \`/deny\`, \`/pending\`. Start a fresh session with \`/new\`, or trim/clear context with \`/compact\` / \`/clear\`.`,
|
|
176
175
|
``,
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
176
|
+
`\`/start\` — pairing instructions`,
|
|
177
|
+
`\`/status\` — agent, model, auth`,
|
|
178
|
+
`\`/vault audit <agent>\` — admin: review agent's vault access + one-tap [🔓 Allow] on recent denials`,
|
|
179
|
+
`\`/commands\` — full command list`,
|
|
181
180
|
].join("\n");
|
|
182
181
|
}
|
|
183
182
|
|
|
@@ -217,7 +216,7 @@ export function statusPairedText(params: {
|
|
|
217
216
|
`Agent: ${formatAgentLine(meta)}`,
|
|
218
217
|
`Auth: ${formatAuthLine(meta.auth)}`,
|
|
219
218
|
];
|
|
220
|
-
if (meta.status) lines.push(`Status:
|
|
219
|
+
if (meta.status) lines.push(`Status: \`${meta.status}\`${meta.uptime ? ` · up ${escapeHtml(meta.uptime)}` : ""}`);
|
|
221
220
|
|
|
222
221
|
// Live health block — every probe (green and otherwise) so the user
|
|
223
222
|
// can see at a glance what's working AND what isn't. This is the
|
|
@@ -225,10 +224,10 @@ export function statusPairedText(params: {
|
|
|
225
224
|
// contract: the boot card is a quiet ack, /status is the dashboard.
|
|
226
225
|
if (meta.live && meta.live.length > 0) {
|
|
227
226
|
lines.push("");
|
|
228
|
-
lines.push("
|
|
227
|
+
lines.push("**Health**");
|
|
229
228
|
for (const row of meta.live) {
|
|
230
229
|
const dot = STATUS_DOT[row.status] ?? STATUS_DOT.fail;
|
|
231
|
-
lines.push(`${dot}
|
|
230
|
+
lines.push(`${dot} **${escapeHtml(row.label)}** ${escapeHtml(row.detail)}`);
|
|
232
231
|
}
|
|
233
232
|
}
|
|
234
233
|
|
|
@@ -237,14 +236,14 @@ export function statusPairedText(params: {
|
|
|
237
236
|
// Blank separator before the audit block so the reply reads as two
|
|
238
237
|
// sections: live state up top, config audit below.
|
|
239
238
|
lines.push("");
|
|
240
|
-
if (audit.version) lines.push(
|
|
241
|
-
if (meta.extendsProfile) lines.push(
|
|
242
|
-
if (audit.tools) lines.push(
|
|
243
|
-
if (audit.toolsDeny) lines.push(
|
|
244
|
-
if (audit.skills) lines.push(
|
|
245
|
-
if (audit.limits) lines.push(
|
|
246
|
-
if (audit.channel) lines.push(
|
|
247
|
-
if (audit.memoryBank) lines.push(
|
|
239
|
+
if (audit.version) lines.push(`**Version** ${escapeHtml(audit.version)}`);
|
|
240
|
+
if (meta.extendsProfile) lines.push(`**Profile** ${escapeHtml(meta.extendsProfile)}`);
|
|
241
|
+
if (audit.tools) lines.push(`**Tools** ${escapeHtml(audit.tools)}`);
|
|
242
|
+
if (audit.toolsDeny) lines.push(`**Deny** ${escapeHtml(audit.toolsDeny)}`);
|
|
243
|
+
if (audit.skills) lines.push(`**Skills** ${escapeHtml(audit.skills)}`);
|
|
244
|
+
if (audit.limits) lines.push(`**Limits** ${escapeHtml(audit.limits)}`);
|
|
245
|
+
if (audit.channel) lines.push(`**Channel** ${escapeHtml(audit.channel)}`);
|
|
246
|
+
if (audit.memoryBank) lines.push(`**Memory** ${escapeHtml(audit.memoryBank)}`);
|
|
248
247
|
}
|
|
249
248
|
|
|
250
249
|
return lines.join("\n");
|
|
@@ -254,7 +253,7 @@ export function statusPairedText(params: {
|
|
|
254
253
|
* `/status` when the sender isn't paired yet but has a pending code.
|
|
255
254
|
*/
|
|
256
255
|
export function statusPendingText(code: string): string {
|
|
257
|
-
return `Pending pairing — run in Claude Code:\n\n
|
|
256
|
+
return `Pending pairing — run in Claude Code:\n\n\`/telegram:access pair ${code}\``;
|
|
258
257
|
}
|
|
259
258
|
|
|
260
259
|
/**
|
|
@@ -367,55 +366,55 @@ export const TELEGRAM_SWITCHROOM_COMMANDS = TELEGRAM_MENU_COMMANDS.slice(3);
|
|
|
367
366
|
|
|
368
367
|
export function switchroomHelpText(agentName: string): string {
|
|
369
368
|
return [
|
|
370
|
-
|
|
369
|
+
`**Switchroom bot** — commands for the **${escapeHtml(agentName)}** agent.`,
|
|
371
370
|
``,
|
|
372
|
-
|
|
373
|
-
|
|
374
|
-
|
|
375
|
-
|
|
376
|
-
|
|
377
|
-
|
|
378
|
-
|
|
379
|
-
|
|
371
|
+
`**Session & approvals**`,
|
|
372
|
+
`\`/new\` — fresh session (flush handoff, restart)`,
|
|
373
|
+
`\`/compact\` — compact context (summarize, keep the thread)`,
|
|
374
|
+
`\`/clear\` — clear context (fresh slate; memory in Hindsight)`,
|
|
375
|
+
`\`/approve [id]\` — approve pending tool permission`,
|
|
376
|
+
`\`/deny [id]\` — deny pending tool permission`,
|
|
377
|
+
`\`/pending\` — list pending permission prompts`,
|
|
378
|
+
`\`/interrupt [name]\` — interrupt an agent turn`,
|
|
380
379
|
``,
|
|
381
|
-
|
|
382
|
-
|
|
383
|
-
|
|
384
|
-
|
|
385
|
-
|
|
386
|
-
|
|
380
|
+
`**Agents**`,
|
|
381
|
+
`\`/agents\` — list all agents`,
|
|
382
|
+
`\`/agentstart [name]\` — start an agent`,
|
|
383
|
+
`\`/stop [name]\` — stop an agent`,
|
|
384
|
+
`\`/logs [name] [lines]\` — show agent logs`,
|
|
385
|
+
`\`/memory <query>\` — search agent memory`,
|
|
387
386
|
``,
|
|
388
|
-
|
|
389
|
-
|
|
390
|
-
|
|
391
|
-
|
|
392
|
-
|
|
393
|
-
|
|
387
|
+
`**Fleet management**`,
|
|
388
|
+
`\`/upgradestatus\` — read-only: CLI version, image age, container age per service`,
|
|
389
|
+
`\`/update\` — dry-run plan; \`/update apply\` — actually pull images, reconcile, restart`,
|
|
390
|
+
`\`/restart [name|all]\` — bounce agent (drains in-flight turn by default)`,
|
|
391
|
+
`\`/version\` — show versions + running agent health summary`,
|
|
392
|
+
`\`/whoami\` — this agent's sandbox: tools, MCP, vault key-names, powers`,
|
|
394
393
|
``,
|
|
395
|
-
|
|
396
|
-
|
|
397
|
-
|
|
398
|
-
|
|
399
|
-
|
|
400
|
-
|
|
401
|
-
|
|
402
|
-
|
|
403
|
-
|
|
404
|
-
|
|
405
|
-
|
|
406
|
-
|
|
407
|
-
|
|
408
|
-
|
|
409
|
-
|
|
410
|
-
|
|
411
|
-
|
|
412
|
-
|
|
413
|
-
|
|
414
|
-
|
|
415
|
-
|
|
416
|
-
|
|
394
|
+
`**Auth & config**`,
|
|
395
|
+
`\`/auth\` — auth status or actions`,
|
|
396
|
+
`\`/auth add [agent]\` — add a new account slot (fallback pool)`,
|
|
397
|
+
`\`/auth list [agent]\` — list account slots and health`,
|
|
398
|
+
`\`/auth use [agent] <slot>\` — switch active slot and restart`,
|
|
399
|
+
`\`/auth rm [agent] <slot> [--force]\` — remove a slot`,
|
|
400
|
+
`\`/model\` — show the configured Claude model`,
|
|
401
|
+
`\`/model <name>\` — switch the live session's model (opus · sonnet · haiku or a full id; until restart)`,
|
|
402
|
+
`\`/effort\` — show or switch reasoning effort (low · medium · high · xhigh · max; until restart)`,
|
|
403
|
+
`\`/topics\` — topic-to-agent mappings`,
|
|
404
|
+
`\`/permissions [agent]\` — show agent permissions`,
|
|
405
|
+
`\`/grant <tool>\` — grant a tool permission`,
|
|
406
|
+
`\`/dangerous [off]\` — toggle full tool access`,
|
|
407
|
+
`\`/vault list|get|set|delete\` — manage encrypted secrets`,
|
|
408
|
+
`\`/vault status\` — show broker state (locked/unlocked, uptime, key count)`,
|
|
409
|
+
`\`/vault unlock\` — unlock the broker (prompts for passphrase via Telegram)`,
|
|
410
|
+
`\`/vault lock\` — lock the broker`,
|
|
411
|
+
`\`/vault grants [agent]\` — list active capability grants (tap to revoke)`,
|
|
412
|
+
`\`/doctor\` — health check (deps, services, MCP)`,
|
|
413
|
+
`\`/usage\` — Pro/Max plan quota (5h + 7d windows)`,
|
|
414
|
+
`\`/inject <slash>\` — inject a Claude Code REPL slash command (e.g. \`/inject /cost\`; allowlisted)`,
|
|
415
|
+
`\`/commands\` — this help`,
|
|
417
416
|
``,
|
|
418
|
-
|
|
417
|
+
`_Tip: \`/update\` shows the plan; \`/update apply\` executes it; \`/restart\` bounces a stuck agent; \`/version\` checks what's running._`,
|
|
419
418
|
].join("\n");
|
|
420
419
|
}
|
|
421
420
|
|
|
@@ -424,10 +423,10 @@ export function switchroomHelpText(agentName: string): string {
|
|
|
424
423
|
* Centralized so gateway and monolith agree on wording.
|
|
425
424
|
*/
|
|
426
425
|
export function restartAckText(agentName: string): string {
|
|
427
|
-
return `🔄 Restarting
|
|
426
|
+
return `🔄 Restarting **${escapeHtml(agentName)}**…`;
|
|
428
427
|
}
|
|
429
428
|
|
|
430
429
|
export function newSessionAckText(agentName: string, flushedHandoff: boolean): string {
|
|
431
430
|
const tail = flushedHandoff ? " · flushed handoff" : "";
|
|
432
|
-
return `🆕 Started fresh session for
|
|
431
|
+
return `🆕 Started fresh session for **${escapeHtml(agentName)}**${tail} · restarting…`;
|
|
433
432
|
}
|
|
@@ -155,11 +155,11 @@ export function renderWorkerActivity(v: WorkerActivityView, liveSuffix = ''): st
|
|
|
155
155
|
})
|
|
156
156
|
if (card == null) {
|
|
157
157
|
// Unreachable (header always present) — defensive.
|
|
158
|
-
return `🛠
|
|
158
|
+
return `🛠 **Worker** · _starting…_`
|
|
159
159
|
}
|
|
160
160
|
if (!finished && steps.length === 0) {
|
|
161
161
|
// Header-only running render → append the starting placeholder.
|
|
162
|
-
return `${card}\
|
|
162
|
+
return `${card}\n_starting…_`
|
|
163
163
|
}
|
|
164
164
|
return card
|
|
165
165
|
}
|
|
@@ -285,7 +285,6 @@ export function createWorkerActivityFeed(opts: WorkerActivityFeedOpts): WorkerAc
|
|
|
285
285
|
|
|
286
286
|
function sendOptsFor(h: WorkerHandle): Record<string, unknown> {
|
|
287
287
|
return {
|
|
288
|
-
parse_mode: 'HTML',
|
|
289
288
|
disable_web_page_preview: true,
|
|
290
289
|
// Sub-agent progress card is a status surface, never the user's
|
|
291
290
|
// answer — silence the open ping. (editMessageText ignores
|