switchroom 0.14.17 → 0.14.19
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 +3 -0
- package/dist/auth-broker/index.js +3 -0
- package/dist/cli/notion-write-pretool.mjs +3 -0
- package/dist/cli/switchroom.js +39 -2
- package/dist/host-control/main.js +3 -0
- package/dist/vault/approvals/kernel-server.js +3 -0
- package/dist/vault/broker/server.js +3 -0
- package/package.json +1 -1
- package/profiles/_shared/telegram-style.md.hbs +6 -5
- package/telegram-plugin/dist/gateway/gateway.js +166 -33
- package/telegram-plugin/gateway/gateway.ts +119 -29
- package/telegram-plugin/gateway/inbound-coalesce.ts +8 -7
- package/telegram-plugin/gateway/pending-inbound-buffer.ts +100 -9
- package/telegram-plugin/status-reactions.ts +18 -0
- package/telegram-plugin/tests/inbound-coalesce.test.ts +21 -0
- package/telegram-plugin/tests/pending-inbound-buffer.test.ts +285 -1
- package/telegram-plugin/tests/status-reactions.test.ts +69 -0
- package/telegram-plugin/tests/worker-feed-dispatch.test.ts +77 -0
|
@@ -11068,6 +11068,9 @@ var TelegramChannelSchema = exports_external.object({
|
|
|
11068
11068
|
short_name: exports_external.string().optional().describe("Telegraph account display name. Defaults to the agent's slug. Used at " + "first-publish to lazily create the account; cached thereafter."),
|
|
11069
11069
|
author_name: exports_external.string().optional().describe("Telegraph article byline. Defaults to soul.name when set.")
|
|
11070
11070
|
}).optional().describe("Long-reply publishing via Telegraph (#579). When enabled, replies " + "above the threshold publish as a Telegraph article rendered in " + "Telegram via native Instant View. Off by default — content " + "residency is real for some personas (lawyer, health-coach with PHI). " + "Cascades from defaults.channels.telegram.telegraph. " + "(Migrated from per-agent root in #596.)"),
|
|
11071
|
+
coalesce: exports_external.object({
|
|
11072
|
+
window_ms: exports_external.number().int().nonnegative().optional().describe("Sliding-window (ms) for merging consecutive inbound messages from " + "the same sender+topic into ONE Claude turn. Each new message resets " + "the timer; the turn starts once the sender pauses for this long. " + "Catches forwarded bursts, pasted text the Telegram client split " + "into several messages, and mixed text+media forwards. Default 500. " + "Set 0 to disable (every message becomes its own turn). Raise for " + "users who think in multiple short messages; the trade-off is the " + "single-message turn start is delayed by this much (the \uD83D\uDC40 ack still " + "fires immediately, so perceived latency is unchanged).")
|
|
11073
|
+
}).optional().describe("Inbound coalescing — how the gateway groups rapid consecutive messages " + "into a single turn so a forwarded album or split paste doesn't fan out " + "into N separate turns. Cascades from defaults.channels.telegram.coalesce."),
|
|
11071
11074
|
webhook_sources: exports_external.array(exports_external.enum(["github", "generic"])).optional().describe("External webhook sources allowed to ingest events into this agent's " + "log. POST /webhook/<agent>/<source> on the switchroom web server. " + "Each source has its own signature verification ('github' = " + "X-Hub-Signature-256 HMAC-SHA256, 'generic' = Bearer token). " + "Per-source secret read from ~/.switchroom/webhook-secrets.json " + "keyed by [agent][source]. Verified events append to " + "<agent>/telegram/webhook-events.jsonl for the agent to read on " + "demand. Off by default — webhook is the only untrusted-inbound " + "surface in the system, so opt-in is mandatory. " + "Cascades from defaults.channels.telegram.webhook_sources. " + "(Migrated from per-agent root in #596 — see #577.)"),
|
|
11072
11075
|
webhook_dispatch: exports_external.object({
|
|
11073
11076
|
github: exports_external.array(exports_external.object({
|
|
@@ -11068,6 +11068,9 @@ var TelegramChannelSchema = exports_external.object({
|
|
|
11068
11068
|
short_name: exports_external.string().optional().describe("Telegraph account display name. Defaults to the agent's slug. Used at " + "first-publish to lazily create the account; cached thereafter."),
|
|
11069
11069
|
author_name: exports_external.string().optional().describe("Telegraph article byline. Defaults to soul.name when set.")
|
|
11070
11070
|
}).optional().describe("Long-reply publishing via Telegraph (#579). When enabled, replies " + "above the threshold publish as a Telegraph article rendered in " + "Telegram via native Instant View. Off by default — content " + "residency is real for some personas (lawyer, health-coach with PHI). " + "Cascades from defaults.channels.telegram.telegraph. " + "(Migrated from per-agent root in #596.)"),
|
|
11071
|
+
coalesce: exports_external.object({
|
|
11072
|
+
window_ms: exports_external.number().int().nonnegative().optional().describe("Sliding-window (ms) for merging consecutive inbound messages from " + "the same sender+topic into ONE Claude turn. Each new message resets " + "the timer; the turn starts once the sender pauses for this long. " + "Catches forwarded bursts, pasted text the Telegram client split " + "into several messages, and mixed text+media forwards. Default 500. " + "Set 0 to disable (every message becomes its own turn). Raise for " + "users who think in multiple short messages; the trade-off is the " + "single-message turn start is delayed by this much (the \uD83D\uDC40 ack still " + "fires immediately, so perceived latency is unchanged).")
|
|
11073
|
+
}).optional().describe("Inbound coalescing — how the gateway groups rapid consecutive messages " + "into a single turn so a forwarded album or split paste doesn't fan out " + "into N separate turns. Cascades from defaults.channels.telegram.coalesce."),
|
|
11071
11074
|
webhook_sources: exports_external.array(exports_external.enum(["github", "generic"])).optional().describe("External webhook sources allowed to ingest events into this agent's " + "log. POST /webhook/<agent>/<source> on the switchroom web server. " + "Each source has its own signature verification ('github' = " + "X-Hub-Signature-256 HMAC-SHA256, 'generic' = Bearer token). " + "Per-source secret read from ~/.switchroom/webhook-secrets.json " + "keyed by [agent][source]. Verified events append to " + "<agent>/telegram/webhook-events.jsonl for the agent to read on " + "demand. Off by default — webhook is the only untrusted-inbound " + "surface in the system, so opt-in is mandatory. " + "Cascades from defaults.channels.telegram.webhook_sources. " + "(Migrated from per-agent root in #596 — see #577.)"),
|
|
11072
11075
|
webhook_dispatch: exports_external.object({
|
|
11073
11076
|
github: exports_external.array(exports_external.object({
|
|
@@ -11815,6 +11815,9 @@ var TelegramChannelSchema = exports_external.object({
|
|
|
11815
11815
|
short_name: exports_external.string().optional().describe("Telegraph account display name. Defaults to the agent's slug. Used at " + "first-publish to lazily create the account; cached thereafter."),
|
|
11816
11816
|
author_name: exports_external.string().optional().describe("Telegraph article byline. Defaults to soul.name when set.")
|
|
11817
11817
|
}).optional().describe("Long-reply publishing via Telegraph (#579). When enabled, replies " + "above the threshold publish as a Telegraph article rendered in " + "Telegram via native Instant View. Off by default \u2014 content " + "residency is real for some personas (lawyer, health-coach with PHI). " + "Cascades from defaults.channels.telegram.telegraph. " + "(Migrated from per-agent root in #596.)"),
|
|
11818
|
+
coalesce: exports_external.object({
|
|
11819
|
+
window_ms: exports_external.number().int().nonnegative().optional().describe("Sliding-window (ms) for merging consecutive inbound messages from " + "the same sender+topic into ONE Claude turn. Each new message resets " + "the timer; the turn starts once the sender pauses for this long. " + "Catches forwarded bursts, pasted text the Telegram client split " + "into several messages, and mixed text+media forwards. Default 500. " + "Set 0 to disable (every message becomes its own turn). Raise for " + "users who think in multiple short messages; the trade-off is the " + "single-message turn start is delayed by this much (the \uD83D\uDC40 ack still " + "fires immediately, so perceived latency is unchanged).")
|
|
11820
|
+
}).optional().describe("Inbound coalescing \u2014 how the gateway groups rapid consecutive messages " + "into a single turn so a forwarded album or split paste doesn't fan out " + "into N separate turns. Cascades from defaults.channels.telegram.coalesce."),
|
|
11818
11821
|
webhook_sources: exports_external.array(exports_external.enum(["github", "generic"])).optional().describe("External webhook sources allowed to ingest events into this agent's " + "log. POST /webhook/<agent>/<source> on the switchroom web server. " + "Each source has its own signature verification ('github' = " + "X-Hub-Signature-256 HMAC-SHA256, 'generic' = Bearer token). " + "Per-source secret read from ~/.switchroom/webhook-secrets.json " + "keyed by [agent][source]. Verified events append to " + "<agent>/telegram/webhook-events.jsonl for the agent to read on " + "demand. Off by default \u2014 webhook is the only untrusted-inbound " + "surface in the system, so opt-in is mandatory. " + "Cascades from defaults.channels.telegram.webhook_sources. " + "(Migrated from per-agent root in #596 \u2014 see #577.)"),
|
|
11819
11822
|
webhook_dispatch: exports_external.object({
|
|
11820
11823
|
github: exports_external.array(exports_external.object({
|
package/dist/cli/switchroom.js
CHANGED
|
@@ -13632,6 +13632,9 @@ var init_schema = __esm(() => {
|
|
|
13632
13632
|
short_name: exports_external.string().optional().describe("Telegraph account display name. Defaults to the agent's slug. Used at " + "first-publish to lazily create the account; cached thereafter."),
|
|
13633
13633
|
author_name: exports_external.string().optional().describe("Telegraph article byline. Defaults to soul.name when set.")
|
|
13634
13634
|
}).optional().describe("Long-reply publishing via Telegraph (#579). When enabled, replies " + "above the threshold publish as a Telegraph article rendered in " + "Telegram via native Instant View. Off by default \u2014 content " + "residency is real for some personas (lawyer, health-coach with PHI). " + "Cascades from defaults.channels.telegram.telegraph. " + "(Migrated from per-agent root in #596.)"),
|
|
13635
|
+
coalesce: exports_external.object({
|
|
13636
|
+
window_ms: exports_external.number().int().nonnegative().optional().describe("Sliding-window (ms) for merging consecutive inbound messages from " + "the same sender+topic into ONE Claude turn. Each new message resets " + "the timer; the turn starts once the sender pauses for this long. " + "Catches forwarded bursts, pasted text the Telegram client split " + "into several messages, and mixed text+media forwards. Default 500. " + "Set 0 to disable (every message becomes its own turn). Raise for " + "users who think in multiple short messages; the trade-off is the " + "single-message turn start is delayed by this much (the \uD83D\uDC40 ack still " + "fires immediately, so perceived latency is unchanged).")
|
|
13637
|
+
}).optional().describe("Inbound coalescing \u2014 how the gateway groups rapid consecutive messages " + "into a single turn so a forwarded album or split paste doesn't fan out " + "into N separate turns. Cascades from defaults.channels.telegram.coalesce."),
|
|
13635
13638
|
webhook_sources: exports_external.array(exports_external.enum(["github", "generic"])).optional().describe("External webhook sources allowed to ingest events into this agent's " + "log. POST /webhook/<agent>/<source> on the switchroom web server. " + "Each source has its own signature verification ('github' = " + "X-Hub-Signature-256 HMAC-SHA256, 'generic' = Bearer token). " + "Per-source secret read from ~/.switchroom/webhook-secrets.json " + "keyed by [agent][source]. Verified events append to " + "<agent>/telegram/webhook-events.jsonl for the agent to read on " + "demand. Off by default \u2014 webhook is the only untrusted-inbound " + "surface in the system, so opt-in is mandatory. " + "Cascades from defaults.channels.telegram.webhook_sources. " + "(Migrated from per-agent root in #596 \u2014 see #577.)"),
|
|
13636
13639
|
webhook_dispatch: exports_external.object({
|
|
13637
13640
|
github: exports_external.array(exports_external.object({
|
|
@@ -49413,8 +49416,8 @@ var {
|
|
|
49413
49416
|
} = import__.default;
|
|
49414
49417
|
|
|
49415
49418
|
// src/build-info.ts
|
|
49416
|
-
var VERSION = "0.14.
|
|
49417
|
-
var COMMIT_SHA = "
|
|
49419
|
+
var VERSION = "0.14.19";
|
|
49420
|
+
var COMMIT_SHA = "21863276";
|
|
49418
49421
|
|
|
49419
49422
|
// src/cli/agent.ts
|
|
49420
49423
|
init_source();
|
|
@@ -50213,6 +50216,37 @@ a flood. Going quiet mid-work is fine \u2014 going quiet *instead* of
|
|
|
50213
50216
|
acknowledging, or *instead* of an update at a real milestone, is the
|
|
50214
50217
|
black box this exists to prevent.
|
|
50215
50218
|
|
|
50219
|
+
### Formatting \u2014 make it scannable
|
|
50220
|
+
|
|
50221
|
+
\`reply\` and \`stream_reply\` render Markdown as Telegram HTML for you, so
|
|
50222
|
+
\`**bold**\` becomes bold and backtick-wrapped text becomes monospace. Use it.
|
|
50223
|
+
|
|
50224
|
+
- **A one- or two-line conversational reply needs almost no markup.** Keep
|
|
50225
|
+
bold for the single fact that matters, never for decoration. "on it, pulling
|
|
50226
|
+
the logs now" is already perfect.
|
|
50227
|
+
- **A multi-section message needs visual hierarchy or it reads as a flat
|
|
50228
|
+
wall.** When you group several blocks \u2014 a status update, a "where things
|
|
50229
|
+
stand", a summary with distinct buckets, or **the message you post before
|
|
50230
|
+
kicking off a sub-agent or worker** \u2014 give each section a **bold label on
|
|
50231
|
+
its own line** and separate sections with **one blank line**. A row of
|
|
50232
|
+
emoji and bullets at equal weight with no spacing is the plain-text dump to
|
|
50233
|
+
avoid; bold labels + blank lines are what let the eye find the structure.
|
|
50234
|
+
Example shape:
|
|
50235
|
+
|
|
50236
|
+
**Dispatching**
|
|
50237
|
+
Kicking off a worker to crawl the changelog.
|
|
50238
|
+
|
|
50239
|
+
**What it'll do**
|
|
50240
|
+
\u2022 pull every entry since v0.14
|
|
50241
|
+
\u2022 flag anything user-facing
|
|
50242
|
+
|
|
50243
|
+
**Back in** ~2 min with a synthesized summary.
|
|
50244
|
+
- Bullets stay one level deep \u2014 Telegram flattens nested lists awkwardly. Use
|
|
50245
|
+
backtick-wrapped \`inline code\` for filenames, commands, and identifiers.
|
|
50246
|
+
- Don't use Markdown headings (\`#\` / \`##\`) in a reply \u2014 bold the label
|
|
50247
|
+
instead (\`**Blockers**\`, not \`## Blockers\`). Keep lines short; long
|
|
50248
|
+
unwrapped lines are hard to read on a phone.
|
|
50249
|
+
|
|
50216
50250
|
Every turn that answers a user message ends with a user-visible
|
|
50217
50251
|
\`reply\` (or \`stream_reply\` done=true) \u2014 Telegram is all the user
|
|
50218
50252
|
sees; your terminal output never reaches them.`;
|
|
@@ -52681,6 +52715,9 @@ function buildAccessJson2(agentConfig, telegramConfig, resolvedTopicId, userId)
|
|
|
52681
52715
|
if (tg?.telegraph) {
|
|
52682
52716
|
access.telegraph = tg.telegraph;
|
|
52683
52717
|
}
|
|
52718
|
+
if (typeof tg?.coalesce?.window_ms === "number") {
|
|
52719
|
+
access.coalescingGapMs = tg.coalesce.window_ms;
|
|
52720
|
+
}
|
|
52684
52721
|
return JSON.stringify(access, null, 2) + `
|
|
52685
52722
|
`;
|
|
52686
52723
|
}
|
|
@@ -13803,6 +13803,9 @@ var TelegramChannelSchema = exports_external.object({
|
|
|
13803
13803
|
short_name: exports_external.string().optional().describe("Telegraph account display name. Defaults to the agent's slug. Used at " + "first-publish to lazily create the account; cached thereafter."),
|
|
13804
13804
|
author_name: exports_external.string().optional().describe("Telegraph article byline. Defaults to soul.name when set.")
|
|
13805
13805
|
}).optional().describe("Long-reply publishing via Telegraph (#579). When enabled, replies " + "above the threshold publish as a Telegraph article rendered in " + "Telegram via native Instant View. Off by default — content " + "residency is real for some personas (lawyer, health-coach with PHI). " + "Cascades from defaults.channels.telegram.telegraph. " + "(Migrated from per-agent root in #596.)"),
|
|
13806
|
+
coalesce: exports_external.object({
|
|
13807
|
+
window_ms: exports_external.number().int().nonnegative().optional().describe("Sliding-window (ms) for merging consecutive inbound messages from " + "the same sender+topic into ONE Claude turn. Each new message resets " + "the timer; the turn starts once the sender pauses for this long. " + "Catches forwarded bursts, pasted text the Telegram client split " + "into several messages, and mixed text+media forwards. Default 500. " + "Set 0 to disable (every message becomes its own turn). Raise for " + "users who think in multiple short messages; the trade-off is the " + "single-message turn start is delayed by this much (the \uD83D\uDC40 ack still " + "fires immediately, so perceived latency is unchanged).")
|
|
13808
|
+
}).optional().describe("Inbound coalescing — how the gateway groups rapid consecutive messages " + "into a single turn so a forwarded album or split paste doesn't fan out " + "into N separate turns. Cascades from defaults.channels.telegram.coalesce."),
|
|
13806
13809
|
webhook_sources: exports_external.array(exports_external.enum(["github", "generic"])).optional().describe("External webhook sources allowed to ingest events into this agent's " + "log. POST /webhook/<agent>/<source> on the switchroom web server. " + "Each source has its own signature verification ('github' = " + "X-Hub-Signature-256 HMAC-SHA256, 'generic' = Bearer token). " + "Per-source secret read from ~/.switchroom/webhook-secrets.json " + "keyed by [agent][source]. Verified events append to " + "<agent>/telegram/webhook-events.jsonl for the agent to read on " + "demand. Off by default — webhook is the only untrusted-inbound " + "surface in the system, so opt-in is mandatory. " + "Cascades from defaults.channels.telegram.webhook_sources. " + "(Migrated from per-agent root in #596 — see #577.)"),
|
|
13807
13810
|
webhook_dispatch: exports_external.object({
|
|
13808
13811
|
github: exports_external.array(exports_external.object({
|
|
@@ -11383,6 +11383,9 @@ var init_schema = __esm(() => {
|
|
|
11383
11383
|
short_name: exports_external.string().optional().describe("Telegraph account display name. Defaults to the agent's slug. Used at " + "first-publish to lazily create the account; cached thereafter."),
|
|
11384
11384
|
author_name: exports_external.string().optional().describe("Telegraph article byline. Defaults to soul.name when set.")
|
|
11385
11385
|
}).optional().describe("Long-reply publishing via Telegraph (#579). When enabled, replies " + "above the threshold publish as a Telegraph article rendered in " + "Telegram via native Instant View. Off by default — content " + "residency is real for some personas (lawyer, health-coach with PHI). " + "Cascades from defaults.channels.telegram.telegraph. " + "(Migrated from per-agent root in #596.)"),
|
|
11386
|
+
coalesce: exports_external.object({
|
|
11387
|
+
window_ms: exports_external.number().int().nonnegative().optional().describe("Sliding-window (ms) for merging consecutive inbound messages from " + "the same sender+topic into ONE Claude turn. Each new message resets " + "the timer; the turn starts once the sender pauses for this long. " + "Catches forwarded bursts, pasted text the Telegram client split " + "into several messages, and mixed text+media forwards. Default 500. " + "Set 0 to disable (every message becomes its own turn). Raise for " + "users who think in multiple short messages; the trade-off is the " + "single-message turn start is delayed by this much (the \uD83D\uDC40 ack still " + "fires immediately, so perceived latency is unchanged).")
|
|
11388
|
+
}).optional().describe("Inbound coalescing — how the gateway groups rapid consecutive messages " + "into a single turn so a forwarded album or split paste doesn't fan out " + "into N separate turns. Cascades from defaults.channels.telegram.coalesce."),
|
|
11386
11389
|
webhook_sources: exports_external.array(exports_external.enum(["github", "generic"])).optional().describe("External webhook sources allowed to ingest events into this agent's " + "log. POST /webhook/<agent>/<source> on the switchroom web server. " + "Each source has its own signature verification ('github' = " + "X-Hub-Signature-256 HMAC-SHA256, 'generic' = Bearer token). " + "Per-source secret read from ~/.switchroom/webhook-secrets.json " + "keyed by [agent][source]. Verified events append to " + "<agent>/telegram/webhook-events.jsonl for the agent to read on " + "demand. Off by default — webhook is the only untrusted-inbound " + "surface in the system, so opt-in is mandatory. " + "Cascades from defaults.channels.telegram.webhook_sources. " + "(Migrated from per-agent root in #596 — see #577.)"),
|
|
11387
11390
|
webhook_dispatch: exports_external.object({
|
|
11388
11391
|
github: exports_external.array(exports_external.object({
|
|
@@ -11383,6 +11383,9 @@ var init_schema = __esm(() => {
|
|
|
11383
11383
|
short_name: exports_external.string().optional().describe("Telegraph account display name. Defaults to the agent's slug. Used at " + "first-publish to lazily create the account; cached thereafter."),
|
|
11384
11384
|
author_name: exports_external.string().optional().describe("Telegraph article byline. Defaults to soul.name when set.")
|
|
11385
11385
|
}).optional().describe("Long-reply publishing via Telegraph (#579). When enabled, replies " + "above the threshold publish as a Telegraph article rendered in " + "Telegram via native Instant View. Off by default — content " + "residency is real for some personas (lawyer, health-coach with PHI). " + "Cascades from defaults.channels.telegram.telegraph. " + "(Migrated from per-agent root in #596.)"),
|
|
11386
|
+
coalesce: exports_external.object({
|
|
11387
|
+
window_ms: exports_external.number().int().nonnegative().optional().describe("Sliding-window (ms) for merging consecutive inbound messages from " + "the same sender+topic into ONE Claude turn. Each new message resets " + "the timer; the turn starts once the sender pauses for this long. " + "Catches forwarded bursts, pasted text the Telegram client split " + "into several messages, and mixed text+media forwards. Default 500. " + "Set 0 to disable (every message becomes its own turn). Raise for " + "users who think in multiple short messages; the trade-off is the " + "single-message turn start is delayed by this much (the \uD83D\uDC40 ack still " + "fires immediately, so perceived latency is unchanged).")
|
|
11388
|
+
}).optional().describe("Inbound coalescing — how the gateway groups rapid consecutive messages " + "into a single turn so a forwarded album or split paste doesn't fan out " + "into N separate turns. Cascades from defaults.channels.telegram.coalesce."),
|
|
11386
11389
|
webhook_sources: exports_external.array(exports_external.enum(["github", "generic"])).optional().describe("External webhook sources allowed to ingest events into this agent's " + "log. POST /webhook/<agent>/<source> on the switchroom web server. " + "Each source has its own signature verification ('github' = " + "X-Hub-Signature-256 HMAC-SHA256, 'generic' = Bearer token). " + "Per-source secret read from ~/.switchroom/webhook-secrets.json " + "keyed by [agent][source]. Verified events append to " + "<agent>/telegram/webhook-events.jsonl for the agent to read on " + "demand. Off by default — webhook is the only untrusted-inbound " + "surface in the system, so opt-in is mandatory. " + "Cascades from defaults.channels.telegram.webhook_sources. " + "(Migrated from per-agent root in #596 — see #577.)"),
|
|
11387
11390
|
webhook_dispatch: exports_external.object({
|
|
11388
11391
|
github: exports_external.array(exports_external.object({
|
package/package.json
CHANGED
|
@@ -34,13 +34,14 @@ If both `queued` and `steering` are somehow present, `steering` wins (explicit o
|
|
|
34
34
|
**Self-narrate the classification.** At the top of your reply for any `steering` or `queued` message, include a brief italic one-liner so the user can correct you — e.g. `_↪️ Treating as steer on the prior task_` or `_📥 Queued as a new task_`.
|
|
35
35
|
|
|
36
36
|
**Formatting** (Telegram HTML — `reply` and `stream_reply` default to `format: "html"` and convert markdown for you):
|
|
37
|
-
-
|
|
37
|
+
- In ordinary one-or-two-line conversational replies, keep **bold** light — emphasis on key facts only, never decoration.
|
|
38
|
+
- **A multi-section message needs visual hierarchy or it reads as a flat wall.** When a reply groups several blocks — a status update, a "where things stand", a message announcing you're dispatching work, a summary with distinct buckets — give each section a **bold label on its own line** and separate sections with **one blank line**. Bold the label (e.g. `**✅ Done / running**`, `**🔲 Remaining**`, `**Next**`); the markdown→HTML converter renders `**label**` as bold, so the eye can find the structure. An emoji prefix with no bold leaves every line at equal weight — that's the plain-text dump to avoid. Bullet lines under a label are fine (`•` or `-`, one point per line).
|
|
38
39
|
- Use `inline code` for filenames, commands, identifiers
|
|
39
40
|
- Use ```fenced code blocks``` for multi-line code
|
|
40
|
-
-
|
|
41
|
-
- Don't use markdown headings (`##`) in replies —
|
|
42
|
-
- Keep lines short — long unwrapped lines are hard to read on mobile
|
|
43
|
-
- One idea per message
|
|
41
|
+
- Nested lists are not supported (Telegram flattens them awkwardly) — keep bullets one level deep.
|
|
42
|
+
- Don't use markdown headings (`##`) in replies — bold the label instead (`**Blockers**`, not `## Blockers`).
|
|
43
|
+
- Keep lines short — long unwrapped lines are hard to read on mobile.
|
|
44
|
+
- One idea per message for a quick exchange; a structured update can carry several ideas, but only when each sits under its own bold label with blank-line spacing between them.
|
|
44
45
|
|
|
45
46
|
**Sound human, not AI.** The canonical list of AI-tells to avoid lives in `SOUL.md` under "Never". Apply those rules to every outbound message, not just long-form. For drafts above ~500 chars, or where you're unsure if the voice lands right, invoke the bundled `/humanizer` skill for a polish pass (it catalogues 29 patterns in detail). If `HUMANIZER_VOICE_FILE` is set and readable, treat its content as the user's personal voice template: match length, tone, vocabulary, and formatting habits described there. The user can generate one with `/humanizer-calibrate`.
|
|
46
47
|
|
|
@@ -23737,6 +23737,9 @@ var init_schema = __esm(() => {
|
|
|
23737
23737
|
short_name: exports_external.string().optional().describe("Telegraph account display name. Defaults to the agent's slug. Used at " + "first-publish to lazily create the account; cached thereafter."),
|
|
23738
23738
|
author_name: exports_external.string().optional().describe("Telegraph article byline. Defaults to soul.name when set.")
|
|
23739
23739
|
}).optional().describe("Long-reply publishing via Telegraph (#579). When enabled, replies " + "above the threshold publish as a Telegraph article rendered in " + "Telegram via native Instant View. Off by default \u2014 content " + "residency is real for some personas (lawyer, health-coach with PHI). " + "Cascades from defaults.channels.telegram.telegraph. " + "(Migrated from per-agent root in #596.)"),
|
|
23740
|
+
coalesce: exports_external.object({
|
|
23741
|
+
window_ms: exports_external.number().int().nonnegative().optional().describe("Sliding-window (ms) for merging consecutive inbound messages from " + "the same sender+topic into ONE Claude turn. Each new message resets " + "the timer; the turn starts once the sender pauses for this long. " + "Catches forwarded bursts, pasted text the Telegram client split " + "into several messages, and mixed text+media forwards. Default 500. " + "Set 0 to disable (every message becomes its own turn). Raise for " + "users who think in multiple short messages; the trade-off is the " + "single-message turn start is delayed by this much (the \uD83D\uDC40 ack still " + "fires immediately, so perceived latency is unchanged).")
|
|
23742
|
+
}).optional().describe("Inbound coalescing \u2014 how the gateway groups rapid consecutive messages " + "into a single turn so a forwarded album or split paste doesn't fan out " + "into N separate turns. Cascades from defaults.channels.telegram.coalesce."),
|
|
23740
23743
|
webhook_sources: exports_external.array(exports_external.enum(["github", "generic"])).optional().describe("External webhook sources allowed to ingest events into this agent's " + "log. POST /webhook/<agent>/<source> on the switchroom web server. " + "Each source has its own signature verification ('github' = " + "X-Hub-Signature-256 HMAC-SHA256, 'generic' = Bearer token). " + "Per-source secret read from ~/.switchroom/webhook-secrets.json " + "keyed by [agent][source]. Verified events append to " + "<agent>/telegram/webhook-events.jsonl for the agent to read on " + "demand. Off by default \u2014 webhook is the only untrusted-inbound " + "surface in the system, so opt-in is mandatory. " + "Cascades from defaults.channels.telegram.webhook_sources. " + "(Migrated from per-agent root in #596 \u2014 see #577.)"),
|
|
23741
23744
|
webhook_dispatch: exports_external.object({
|
|
23742
23745
|
github: exports_external.array(exports_external.object({
|
|
@@ -31642,6 +31645,7 @@ var REACTION_VARIANTS = {
|
|
|
31642
31645
|
coding: ["\uD83D\uDC68\u200d\uD83D\uDCBB", "\u270d", "\u26a1"],
|
|
31643
31646
|
web: ["\u26a1", "\uD83E\uDD14", "\uD83D\uDC4C"],
|
|
31644
31647
|
compacting: ["\u270d", "\uD83E\uDD14", "\uD83D\uDC40"],
|
|
31648
|
+
awaiting: ["\uD83D\uDE4F", "\uD83E\uDD14", "\uD83D\uDC40"],
|
|
31645
31649
|
done: ["\uD83D\uDC4D", "\uD83D\uDCAF", "\uD83C\uDF89"],
|
|
31646
31650
|
error: ["\uD83D\uDE31", "\uD83D\uDE28", "\uD83E\uDD2F"],
|
|
31647
31651
|
stallSoft: ["\uD83E\uDD71", "\uD83D\uDE34", "\uD83E\uDD14"],
|
|
@@ -31694,6 +31698,12 @@ class StatusReactionController {
|
|
|
31694
31698
|
setCompacting() {
|
|
31695
31699
|
this.scheduleState("compacting");
|
|
31696
31700
|
}
|
|
31701
|
+
setAwaiting() {
|
|
31702
|
+
if (this.finished)
|
|
31703
|
+
return;
|
|
31704
|
+
this.scheduleState("awaiting", { immediate: true, skipStallReset: true });
|
|
31705
|
+
this.clearStallTimers();
|
|
31706
|
+
}
|
|
31697
31707
|
setError() {
|
|
31698
31708
|
this.scheduleState("error");
|
|
31699
31709
|
}
|
|
@@ -46341,23 +46351,70 @@ function redeliverBufferedInbound(buffer, agent, send, spool) {
|
|
|
46341
46351
|
const pending = buffer.drain(agent);
|
|
46342
46352
|
let redelivered = 0;
|
|
46343
46353
|
let rebuffered = 0;
|
|
46344
|
-
for (const
|
|
46354
|
+
for (const { merged, originals } of planBufferedRedelivery(pending)) {
|
|
46345
46355
|
let delivered = false;
|
|
46346
46356
|
try {
|
|
46347
|
-
delivered = send(
|
|
46357
|
+
delivered = send(merged);
|
|
46348
46358
|
} catch {
|
|
46349
46359
|
delivered = false;
|
|
46350
46360
|
}
|
|
46351
46361
|
if (delivered) {
|
|
46352
|
-
|
|
46353
|
-
|
|
46362
|
+
for (const o of originals)
|
|
46363
|
+
spool?.ack(o);
|
|
46364
|
+
redelivered += originals.length;
|
|
46354
46365
|
} else {
|
|
46355
|
-
|
|
46356
|
-
|
|
46366
|
+
for (const o of originals)
|
|
46367
|
+
buffer.push(agent, o);
|
|
46368
|
+
rebuffered += originals.length;
|
|
46357
46369
|
}
|
|
46358
46370
|
}
|
|
46359
46371
|
return { drained: pending.length, redelivered, rebuffered };
|
|
46360
46372
|
}
|
|
46373
|
+
function isMergeableUserInbound(msg) {
|
|
46374
|
+
return msg.type === "inbound" && (msg.meta == null || msg.meta.source == null);
|
|
46375
|
+
}
|
|
46376
|
+
function inboundHasMedia(msg) {
|
|
46377
|
+
return msg.imagePath != null || msg.attachment != null;
|
|
46378
|
+
}
|
|
46379
|
+
function planBufferedRedelivery(pending) {
|
|
46380
|
+
const out = [];
|
|
46381
|
+
let run2 = [];
|
|
46382
|
+
let runHasMedia = false;
|
|
46383
|
+
const sameTarget = (a, b) => a.chatId === b.chatId && (a.threadId ?? null) === (b.threadId ?? null) && a.userId === b.userId;
|
|
46384
|
+
const flush = () => {
|
|
46385
|
+
if (run2.length === 0)
|
|
46386
|
+
return;
|
|
46387
|
+
out.push({ merged: run2.length === 1 ? run2[0] : mergeRun(run2), originals: run2 });
|
|
46388
|
+
run2 = [];
|
|
46389
|
+
runHasMedia = false;
|
|
46390
|
+
};
|
|
46391
|
+
for (const msg of pending) {
|
|
46392
|
+
const msgHasMedia = inboundHasMedia(msg);
|
|
46393
|
+
const canJoin = run2.length > 0 && isMergeableUserInbound(msg) && isMergeableUserInbound(run2[run2.length - 1]) && sameTarget(run2[run2.length - 1], msg) && !(runHasMedia && msgHasMedia);
|
|
46394
|
+
if (!canJoin)
|
|
46395
|
+
flush();
|
|
46396
|
+
run2.push(msg);
|
|
46397
|
+
runHasMedia = runHasMedia || msgHasMedia;
|
|
46398
|
+
}
|
|
46399
|
+
flush();
|
|
46400
|
+
return out;
|
|
46401
|
+
}
|
|
46402
|
+
function mergeRun(run2) {
|
|
46403
|
+
const last = run2[run2.length - 1];
|
|
46404
|
+
const mediaEntry = run2.find(inboundHasMedia);
|
|
46405
|
+
const merged = {
|
|
46406
|
+
...last,
|
|
46407
|
+
text: run2.map((m) => m.text).join(`
|
|
46408
|
+
`)
|
|
46409
|
+
};
|
|
46410
|
+
delete merged.imagePath;
|
|
46411
|
+
delete merged.attachment;
|
|
46412
|
+
if (mediaEntry?.imagePath != null)
|
|
46413
|
+
merged.imagePath = mediaEntry.imagePath;
|
|
46414
|
+
if (mediaEntry?.attachment != null)
|
|
46415
|
+
merged.attachment = mediaEntry.attachment;
|
|
46416
|
+
return merged;
|
|
46417
|
+
}
|
|
46361
46418
|
function idleDrainTick(buffer, agent, isBridgeAlive, send, spool) {
|
|
46362
46419
|
if (!agent)
|
|
46363
46420
|
return null;
|
|
@@ -46932,23 +46989,70 @@ function redeliverBufferedInbound2(buffer, agent, send, spool) {
|
|
|
46932
46989
|
const pending = buffer.drain(agent);
|
|
46933
46990
|
let redelivered = 0;
|
|
46934
46991
|
let rebuffered = 0;
|
|
46935
|
-
for (const
|
|
46992
|
+
for (const { merged, originals } of planBufferedRedelivery2(pending)) {
|
|
46936
46993
|
let delivered = false;
|
|
46937
46994
|
try {
|
|
46938
|
-
delivered = send(
|
|
46995
|
+
delivered = send(merged);
|
|
46939
46996
|
} catch {
|
|
46940
46997
|
delivered = false;
|
|
46941
46998
|
}
|
|
46942
46999
|
if (delivered) {
|
|
46943
|
-
|
|
46944
|
-
|
|
47000
|
+
for (const o of originals)
|
|
47001
|
+
spool?.ack(o);
|
|
47002
|
+
redelivered += originals.length;
|
|
46945
47003
|
} else {
|
|
46946
|
-
|
|
46947
|
-
|
|
47004
|
+
for (const o of originals)
|
|
47005
|
+
buffer.push(agent, o);
|
|
47006
|
+
rebuffered += originals.length;
|
|
46948
47007
|
}
|
|
46949
47008
|
}
|
|
46950
47009
|
return { drained: pending.length, redelivered, rebuffered };
|
|
46951
47010
|
}
|
|
47011
|
+
function isMergeableUserInbound2(msg) {
|
|
47012
|
+
return msg.type === "inbound" && (msg.meta == null || msg.meta.source == null);
|
|
47013
|
+
}
|
|
47014
|
+
function inboundHasMedia2(msg) {
|
|
47015
|
+
return msg.imagePath != null || msg.attachment != null;
|
|
47016
|
+
}
|
|
47017
|
+
function planBufferedRedelivery2(pending) {
|
|
47018
|
+
const out = [];
|
|
47019
|
+
let run2 = [];
|
|
47020
|
+
let runHasMedia = false;
|
|
47021
|
+
const sameTarget = (a, b) => a.chatId === b.chatId && (a.threadId ?? null) === (b.threadId ?? null) && a.userId === b.userId;
|
|
47022
|
+
const flush = () => {
|
|
47023
|
+
if (run2.length === 0)
|
|
47024
|
+
return;
|
|
47025
|
+
out.push({ merged: run2.length === 1 ? run2[0] : mergeRun2(run2), originals: run2 });
|
|
47026
|
+
run2 = [];
|
|
47027
|
+
runHasMedia = false;
|
|
47028
|
+
};
|
|
47029
|
+
for (const msg of pending) {
|
|
47030
|
+
const msgHasMedia = inboundHasMedia2(msg);
|
|
47031
|
+
const canJoin = run2.length > 0 && isMergeableUserInbound2(msg) && isMergeableUserInbound2(run2[run2.length - 1]) && sameTarget(run2[run2.length - 1], msg) && !(runHasMedia && msgHasMedia);
|
|
47032
|
+
if (!canJoin)
|
|
47033
|
+
flush();
|
|
47034
|
+
run2.push(msg);
|
|
47035
|
+
runHasMedia = runHasMedia || msgHasMedia;
|
|
47036
|
+
}
|
|
47037
|
+
flush();
|
|
47038
|
+
return out;
|
|
47039
|
+
}
|
|
47040
|
+
function mergeRun2(run2) {
|
|
47041
|
+
const last = run2[run2.length - 1];
|
|
47042
|
+
const mediaEntry = run2.find(inboundHasMedia2);
|
|
47043
|
+
const merged = {
|
|
47044
|
+
...last,
|
|
47045
|
+
text: run2.map((m) => m.text).join(`
|
|
47046
|
+
`)
|
|
47047
|
+
};
|
|
47048
|
+
delete merged.imagePath;
|
|
47049
|
+
delete merged.attachment;
|
|
47050
|
+
if (mediaEntry?.imagePath != null)
|
|
47051
|
+
merged.imagePath = mediaEntry.imagePath;
|
|
47052
|
+
if (mediaEntry?.attachment != null)
|
|
47053
|
+
merged.attachment = mediaEntry.attachment;
|
|
47054
|
+
return merged;
|
|
47055
|
+
}
|
|
46952
47056
|
|
|
46953
47057
|
// gateway/inbound-delivery-machine-dispatch.ts
|
|
46954
47058
|
var enabled6 = process.env.SWITCHROOM_DELIVERY_MACHINE_CUTOVER !== "0";
|
|
@@ -51140,10 +51244,10 @@ function sweepStaleTurnActiveMarker(stateDir, opts) {
|
|
|
51140
51244
|
}
|
|
51141
51245
|
|
|
51142
51246
|
// ../src/build-info.ts
|
|
51143
|
-
var VERSION = "0.14.
|
|
51144
|
-
var COMMIT_SHA = "
|
|
51145
|
-
var COMMIT_DATE = "2026-05-
|
|
51146
|
-
var LATEST_PR =
|
|
51247
|
+
var VERSION = "0.14.19";
|
|
51248
|
+
var COMMIT_SHA = "21863276";
|
|
51249
|
+
var COMMIT_DATE = "2026-05-31T00:15:08Z";
|
|
51250
|
+
var LATEST_PR = 2013;
|
|
51147
51251
|
var COMMITS_AHEAD_OF_TAG = 0;
|
|
51148
51252
|
|
|
51149
51253
|
// gateway/boot-version.ts
|
|
@@ -52361,6 +52465,12 @@ function countRunningWorkers() {
|
|
|
52361
52465
|
}
|
|
52362
52466
|
return n;
|
|
52363
52467
|
}
|
|
52468
|
+
function resumeReactionAfterVerdict() {
|
|
52469
|
+
const turn = currentTurn;
|
|
52470
|
+
if (turn == null)
|
|
52471
|
+
return;
|
|
52472
|
+
activeStatusReactions.get(statusKey(turn.sessionChatId, turn.sessionThreadId))?.setThinking();
|
|
52473
|
+
}
|
|
52364
52474
|
function resolveThreadId(chat_id, explicit) {
|
|
52365
52475
|
if (explicit != null)
|
|
52366
52476
|
return Number(explicit);
|
|
@@ -52794,6 +52904,7 @@ var pendingStateReaper = setInterval(() => {
|
|
|
52794
52904
|
for (const [k, v] of pendingPermissions) {
|
|
52795
52905
|
if (now - v.startedAt > PERMISSION_TTL_MS) {
|
|
52796
52906
|
dispatchPermissionVerdict({ type: "permission", requestId: k, behavior: "deny" });
|
|
52907
|
+
resumeReactionAfterVerdict();
|
|
52797
52908
|
process.stderr.write(`telegram gateway: permission TTL expired \u2014 auto-deny request=${k} tool=${v.tool_name} (no operator response in ${Math.round(PERMISSION_TTL_MS / 60000)}m)
|
|
52798
52909
|
`);
|
|
52799
52910
|
pendingPermissions.delete(k);
|
|
@@ -52852,19 +52963,22 @@ function looksLikeAuthCode(text) {
|
|
|
52852
52963
|
return true;
|
|
52853
52964
|
return false;
|
|
52854
52965
|
}
|
|
52966
|
+
var bufferedAttachmentKeys = new Set;
|
|
52855
52967
|
var inboundCoalescer = createInboundCoalescer({
|
|
52856
52968
|
gapMs: () => loadAccess().coalescingGapMs ?? 500,
|
|
52857
52969
|
merge: (entries) => {
|
|
52858
52970
|
const last = entries[entries.length - 1];
|
|
52971
|
+
const withAttachment = entries.find((e) => e.downloadImage != null || e.attachment != null);
|
|
52859
52972
|
return {
|
|
52860
52973
|
text: entries.map((e) => e.text).join(`
|
|
52861
52974
|
`),
|
|
52862
52975
|
ctx: last.ctx,
|
|
52863
|
-
downloadImage:
|
|
52864
|
-
attachment:
|
|
52976
|
+
downloadImage: withAttachment?.downloadImage,
|
|
52977
|
+
attachment: withAttachment?.attachment
|
|
52865
52978
|
};
|
|
52866
52979
|
},
|
|
52867
|
-
onFlush: (
|
|
52980
|
+
onFlush: (key, merged) => {
|
|
52981
|
+
bufferedAttachmentKeys.delete(key);
|
|
52868
52982
|
handleInbound(merged.ctx, merged.text, merged.downloadImage, merged.attachment);
|
|
52869
52983
|
}
|
|
52870
52984
|
});
|
|
@@ -53432,6 +53546,9 @@ var ipcServer = createIpcServer({
|
|
|
53432
53546
|
`);
|
|
53433
53547
|
});
|
|
53434
53548
|
}
|
|
53549
|
+
if (activeTurn != null) {
|
|
53550
|
+
activeStatusReactions.get(statusKey(activeTurn.sessionChatId, activeTurn.sessionThreadId))?.setAwaiting();
|
|
53551
|
+
}
|
|
53435
53552
|
},
|
|
53436
53553
|
onHeartbeat(_client, _msg) {},
|
|
53437
53554
|
onScheduleRestart(client3, msg) {
|
|
@@ -56020,19 +56137,29 @@ function safeName(s) {
|
|
|
56020
56137
|
return s?.replace(/[<>\[\]\r\n;]/g, "_");
|
|
56021
56138
|
}
|
|
56022
56139
|
async function handleInboundCoalesced(ctx, text, downloadImage, attachment) {
|
|
56023
|
-
if (downloadImage || attachment)
|
|
56024
|
-
return handleInbound(ctx, text, downloadImage, attachment);
|
|
56025
56140
|
if (parseInterruptMarker(text).isInterrupt) {
|
|
56026
|
-
return handleInbound(ctx, text,
|
|
56141
|
+
return handleInbound(ctx, text, downloadImage, attachment);
|
|
56142
|
+
}
|
|
56143
|
+
const hasAttachment = downloadImage != null || attachment != null;
|
|
56144
|
+
if (hasAttachment && ctx.message?.media_group_id != null) {
|
|
56145
|
+
return handleInbound(ctx, text, downloadImage, attachment);
|
|
56027
56146
|
}
|
|
56028
56147
|
const from = ctx.from;
|
|
56029
56148
|
if (!from)
|
|
56030
56149
|
return;
|
|
56150
|
+
if (hasAttachment) {
|
|
56151
|
+
const probeKey = inboundCoalesceKey(String(ctx.chat.id), ctx.message?.message_thread_id, String(from.id));
|
|
56152
|
+
if (bufferedAttachmentKeys.has(probeKey)) {
|
|
56153
|
+
return handleInbound(ctx, text, downloadImage, attachment);
|
|
56154
|
+
}
|
|
56155
|
+
}
|
|
56031
56156
|
maybeEarlyAckReaction(ctx, from);
|
|
56032
56157
|
const key = inboundCoalesceKey(String(ctx.chat.id), ctx.message?.message_thread_id, String(from.id));
|
|
56033
56158
|
const result = inboundCoalescer.enqueue(key, { text, ctx, downloadImage, attachment });
|
|
56034
56159
|
if (result.bypass)
|
|
56035
|
-
return handleInbound(ctx, text,
|
|
56160
|
+
return handleInbound(ctx, text, downloadImage, attachment);
|
|
56161
|
+
if (hasAttachment)
|
|
56162
|
+
bufferedAttachmentKeys.add(key);
|
|
56036
56163
|
}
|
|
56037
56164
|
function maybeEarlyAckReaction(ctx, from) {
|
|
56038
56165
|
const msgId = ctx.message?.message_id;
|
|
@@ -56173,6 +56300,7 @@ async function handleInbound(ctx, text, downloadImage, attachment) {
|
|
|
56173
56300
|
requestId: request_id,
|
|
56174
56301
|
behavior
|
|
56175
56302
|
});
|
|
56303
|
+
resumeReactionAfterVerdict();
|
|
56176
56304
|
if (msgId != null) {
|
|
56177
56305
|
const emoji = behavior === "allow" ? "\u2705" : "\u274C";
|
|
56178
56306
|
bot.api.setMessageReaction(chat_id, msgId, [
|
|
@@ -57878,6 +58006,7 @@ async function handlePermissionSlash(ctx, behavior) {
|
|
|
57878
58006
|
return;
|
|
57879
58007
|
}
|
|
57880
58008
|
dispatchPermissionVerdict({ type: "permission", requestId: request_id, behavior });
|
|
58009
|
+
resumeReactionAfterVerdict();
|
|
57881
58010
|
pendingPermissions.delete(request_id);
|
|
57882
58011
|
process.stderr.write(`[telegram gateway] slash-${behavior} request_id=${request_id} tool=${details.tool_name} by=${senderId}
|
|
57883
58012
|
`);
|
|
@@ -60174,6 +60303,7 @@ ${preBlock(formatSwitchroomOutput(err.message ?? "unknown error"))}`, { html: tr
|
|
|
60174
60303
|
behavior: "allow",
|
|
60175
60304
|
rule: chosen.rule
|
|
60176
60305
|
});
|
|
60306
|
+
resumeReactionAfterVerdict();
|
|
60177
60307
|
let durable = false;
|
|
60178
60308
|
let legacy = false;
|
|
60179
60309
|
let failReason = "";
|
|
@@ -60275,7 +60405,9 @@ ${editLabel}` : editLabel,
|
|
|
60275
60405
|
return;
|
|
60276
60406
|
}
|
|
60277
60407
|
pendingPermissions.delete(request_id);
|
|
60278
|
-
const
|
|
60408
|
+
const resumeAgent = process.env.SWITCHROOM_AGENT_NAME;
|
|
60409
|
+
const resumeBeat = resumeAgent ? `\u25B6\uFE0F ${escapeHtmlForTg(resumeAgent)} resuming\u2026` : "\u25B6\uFE0F resuming\u2026";
|
|
60410
|
+
const label = `${behavior === "allow" ? "\u2705 Allowed" : "\u274C Denied"} \xB7 ${resumeBeat}`;
|
|
60279
60411
|
const msg = ctx.callbackQuery?.message;
|
|
60280
60412
|
const baseText = msg && "text" in msg && msg.text ? escapeHtmlForTg(msg.text) : "";
|
|
60281
60413
|
await finalizeCallback(ctx, {
|
|
@@ -60290,6 +60422,7 @@ ${label}` : label,
|
|
|
60290
60422
|
requestId: request_id,
|
|
60291
60423
|
behavior
|
|
60292
60424
|
});
|
|
60425
|
+
resumeReactionAfterVerdict();
|
|
60293
60426
|
}
|
|
60294
60427
|
});
|
|
60295
60428
|
});
|
|
@@ -60298,7 +60431,7 @@ bot.on("message:text", async (ctx) => {
|
|
|
60298
60431
|
});
|
|
60299
60432
|
bot.on("message:photo", async (ctx) => {
|
|
60300
60433
|
const caption = ctx.message.caption ?? "(photo)";
|
|
60301
|
-
await
|
|
60434
|
+
await handleInboundCoalesced(ctx, caption, async () => {
|
|
60302
60435
|
const photos = ctx.message.photo;
|
|
60303
60436
|
const best = photos[photos.length - 1];
|
|
60304
60437
|
try {
|
|
@@ -60334,7 +60467,7 @@ bot.on("message:photo", async (ctx) => {
|
|
|
60334
60467
|
bot.on("message:document", async (ctx) => {
|
|
60335
60468
|
const doc = ctx.message.document;
|
|
60336
60469
|
const name = safeName(doc.file_name);
|
|
60337
|
-
await
|
|
60470
|
+
await handleInboundCoalesced(ctx, ctx.message.caption ?? `(document: ${name ?? "file"})`, undefined, { kind: "document", file_id: doc.file_id, size: doc.file_size, mime: doc.mime_type, name });
|
|
60338
60471
|
});
|
|
60339
60472
|
bot.on("message:voice", async (ctx) => {
|
|
60340
60473
|
const voice = ctx.message.voice;
|
|
@@ -60346,7 +60479,7 @@ bot.on("message:voice", async (ctx) => {
|
|
|
60346
60479
|
const text = ctx.message.caption ? `${ctx.message.caption}
|
|
60347
60480
|
|
|
60348
60481
|
[voice transcript] ${transcript}` : `[voice transcript] ${transcript}`;
|
|
60349
|
-
await
|
|
60482
|
+
await handleInboundCoalesced(ctx, text, undefined, {
|
|
60350
60483
|
kind: "voice",
|
|
60351
60484
|
file_id: voice.file_id,
|
|
60352
60485
|
size: voice.file_size,
|
|
@@ -60355,7 +60488,7 @@ bot.on("message:voice", async (ctx) => {
|
|
|
60355
60488
|
return;
|
|
60356
60489
|
}
|
|
60357
60490
|
}
|
|
60358
|
-
await
|
|
60491
|
+
await handleInboundCoalesced(ctx, ctx.message.caption ?? "(voice message)", undefined, { kind: "voice", file_id: voice.file_id, size: voice.file_size, mime: voice.mime_type });
|
|
60359
60492
|
});
|
|
60360
60493
|
async function maybeTranscribeVoice(fileId, mimeType, language) {
|
|
60361
60494
|
let apiKey = null;
|
|
@@ -60415,15 +60548,15 @@ async function maybeTranscribeVoice(fileId, mimeType, language) {
|
|
|
60415
60548
|
bot.on("message:audio", async (ctx) => {
|
|
60416
60549
|
const audio = ctx.message.audio;
|
|
60417
60550
|
const name = safeName(audio.file_name);
|
|
60418
|
-
await
|
|
60551
|
+
await handleInboundCoalesced(ctx, ctx.message.caption ?? `(audio: ${safeName(audio.title) ?? name ?? "audio"})`, undefined, { kind: "audio", file_id: audio.file_id, size: audio.file_size, mime: audio.mime_type, name });
|
|
60419
60552
|
});
|
|
60420
60553
|
bot.on("message:video", async (ctx) => {
|
|
60421
60554
|
const video = ctx.message.video;
|
|
60422
|
-
await
|
|
60555
|
+
await handleInboundCoalesced(ctx, ctx.message.caption ?? "(video)", undefined, { kind: "video", file_id: video.file_id, size: video.file_size, mime: video.mime_type, name: safeName(video.file_name) });
|
|
60423
60556
|
});
|
|
60424
60557
|
bot.on("message:video_note", async (ctx) => {
|
|
60425
60558
|
const vn = ctx.message.video_note;
|
|
60426
|
-
await
|
|
60559
|
+
await handleInboundCoalesced(ctx, "(video note)", undefined, { kind: "video_note", file_id: vn.file_id, size: vn.file_size });
|
|
60427
60560
|
});
|
|
60428
60561
|
bot.on("message:sticker", async (ctx) => {
|
|
60429
60562
|
const sticker = ctx.message.sticker;
|
|
@@ -60433,13 +60566,13 @@ bot.on("message:sticker", async (ctx) => {
|
|
|
60433
60566
|
if (sticker.set_name)
|
|
60434
60567
|
parts.push(`from "${sticker.set_name}"`);
|
|
60435
60568
|
const text = parts.length > 0 ? `(sticker \u2014 ${parts.join(" ")})` : "(sticker)";
|
|
60436
|
-
await
|
|
60569
|
+
await handleInboundCoalesced(ctx, text, undefined, { kind: "sticker", file_id: sticker.file_id, size: sticker.file_size });
|
|
60437
60570
|
});
|
|
60438
60571
|
bot.on("message:animation", async (ctx) => {
|
|
60439
60572
|
const animation = ctx.message.animation;
|
|
60440
60573
|
const caption = ctx.message.caption;
|
|
60441
60574
|
const text = caption ? `(gif) ${caption}` : "(gif)";
|
|
60442
|
-
await
|
|
60575
|
+
await handleInboundCoalesced(ctx, text, undefined, {
|
|
60443
60576
|
kind: "animation",
|
|
60444
60577
|
file_id: animation.file_id,
|
|
60445
60578
|
size: animation.file_size,
|