@llblab/pi-telegram 0.11.0 → 0.11.1
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/AGENTS.md +3 -2
- package/CHANGELOG.md +7 -0
- package/README.md +15 -0
- package/docs/architecture.md +6 -5
- package/index.ts +12 -0
- package/lib/config.ts +64 -0
- package/lib/menu-settings.ts +108 -10
- package/lib/prompts.ts +1 -0
- package/lib/routing.ts +2 -0
- package/lib/time-injection.ts +78 -0
- package/lib/turns.ts +13 -0
- package/package.json +1 -1
package/AGENTS.md
CHANGED
|
@@ -73,7 +73,8 @@
|
|
|
73
73
|
- Telegram delivery strips top-level HTML comments from preview/final text; column-zero top-level `<!-- telegram_voice ... -->` and `<!-- telegram_button ... -->` blocks are special outbound comments handled after `agent_end` without requiring agent-side transport tool calls, while comments inside code, quotes, lists, or indented examples stay literal
|
|
74
74
|
- `telegram_voice` and `telegram_button` are not π tools; keep prompts/docs explicit that agents should author markup while voice synthesis provider extensions own TTS/OGG conversion, and pi-telegram owns button routing plus Telegram delivery
|
|
75
75
|
- Voice reply policy and prompt context are owned by pi-telegram's `telegram.json` `voice.replyMode`: missing/invalid config behaves as `manual` but does not add a `[voice]` prompt-context block; only an explicit valid `voice.replyMode` renders context. Render a single voice field as `[voice] reply mode: manual|mirror|always`, and render multiple fields as a `[voice]` list; place voice context after `[outputs]` when handler output exists, otherwise after `[attachments]`; provider prompt contributions are optional provider-specific additions, not the default policy channel
|
|
76
|
-
-
|
|
76
|
+
- Optional `telegram.json` `time` may add `[time] YYYY-MM-DD HH:mm:ss <timezone>` to Telegram-originated prompts for wall-clock context. It is off by default, uses `time.injectionMode` values `off|always|interval`, stores `time.interval` in milliseconds, uses the system timezone, and should render last after `[attachments]`, `[outputs]`, and `[voice]` sections. The Settings row `🕒 Time injection: off|always|interval` controls `time.injectionMode` only.
|
|
77
|
+
- Voice reply mode Settings UI standard: the top-level Settings row is `👄 Voice reply: hidden|manual|mirror|always`; `hidden` is the true default and means no valid `voice.replyMode` is persisted, behavior is manual, and no voice policy is added to prompt context; explicit `manual` behaves the same operationally but renders reply-mode context. The submenu title is `👄 Voice reply mode:`; choice buttons use lowercase labels with a model-style active dot (`🟢 hidden`, `🟢 mirror`) rather than per-mode emoji; the explanatory submenu body uses compact HTML-code bullets such as `<code>-</code> <code>hidden</code> (default): ...`. Preserve this wording/icons unless the operator explicitly asks to redesign it
|
|
77
78
|
- Outbound voice delivery is one fallback pipeline: configured `outboundHandlers` with `type: "voice"` run first in `telegram.json` order, then programmatic voice handlers, then registered voice synthesis providers as zero-config progressive fallbacks; provider extensions must not override operator-configured handlers
|
|
78
79
|
- `telegram_voice` text is arbitrary TTS-target text: use body form for multiline text, `<!-- telegram_voice text="Short summary" -->` for explicit one-line text, or `<!-- telegram_voice: Short summary -->` for one-line text with no attributes
|
|
79
80
|
- `telegram_button` has three canonical forms: `<!-- telegram_button: OK -->` for label-only buttons, `<!-- telegram_button label=Continue prompt="Continue with the current plan." -->` for one-line prompts, or `<!-- telegram_button label="Show risks"\nList the main risks first.\n-->` for multiline prompts
|
|
@@ -128,7 +129,7 @@ The canonical detailed ownership map lives in [`docs/architecture.md`](./docs/ar
|
|
|
128
129
|
- When durable runtime constraints or repeat bug patterns emerge, record them here instead of burying them in changelog prose
|
|
129
130
|
- When fork identity changes, keep `README.md`, package metadata, and docs aligned so the published package does not point back at stale upstream coordinates
|
|
130
131
|
- README positioning should emphasize `/start` as the primary Telegram operator menu and keep reaction shortcuts secondary. Reactions are useful queue affordances, but menu controls are the core CLI-to-Telegram bridge.
|
|
131
|
-
- Document configuration knobs without UI in the root README when they affect bootstrap, networking, or
|
|
132
|
+
- Document configuration knobs without UI in the root README when they affect bootstrap, networking, transport limits, or prompt context; currently this includes token env bootstrap, Node env proxy mode, inbound/outbound size limits, and `time`.
|
|
132
133
|
- Work only inside this repository during development tasks; updating the installed Pi extension checkout is a separate manual operator step, not part of normal in-repo implementation work
|
|
133
134
|
|
|
134
135
|
## 8. Integration Protocols
|
package/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,12 @@
|
|
|
1
1
|
# Changelog
|
|
2
2
|
|
|
3
|
+
## Unreleased
|
|
4
|
+
|
|
5
|
+
## 0.11.1: Time Context And Settings Polish
|
|
6
|
+
|
|
7
|
+
- `[Time Context]` Added optional `telegram.json` `time` prompt context for Telegram-originated turns. `time.injectionMode` values are `off`, `always`, and per-chat `interval`; `time.interval` is stored in milliseconds and timezone comes from the system. The `[time]` line renders last after attachments, handler outputs, and voice context, and Settings exposes a `🕒 Time` mode selector.
|
|
8
|
+
- `[Settings UI]` The proactive push row now uses `📌 Proactive push: on|off`, and proactive push, time, and voice reply submenus use matching emoji headings.
|
|
9
|
+
|
|
3
10
|
## 0.11.0: Voice Provider Platform
|
|
4
11
|
|
|
5
12
|
- `[Voice Synthesis Provider API]` Added a first-class voice synthesis provider surface for Telegram voice replies. Providers register with `registerTelegramVoiceSynthesisProvider()` from `@llblab/pi-telegram/lib/voice.ts`, synthesize text into `.ogg`/`.opus`, may return `{ audioPath, transcriptText }`, and can contribute voice-specific prompt guidance through `getVoicePromptContribution(view)`.
|
package/README.md
CHANGED
|
@@ -238,6 +238,21 @@ Import `registerTelegramSection()` from `@llblab/pi-telegram/lib/extension-secti
|
|
|
238
238
|
|
|
239
239
|
`telegram.json` can set `proactivePush: true` to send successful local non-Telegram final replies to the paired Telegram chat when no Telegram turn is active. Local prompt text is not mirrored because the bot does not own terminal user messages. The mode is off by default and can be toggled from settings.
|
|
240
240
|
|
|
241
|
+
### Time context
|
|
242
|
+
|
|
243
|
+
`telegram.json` can opt into a compact `[time]` line in Telegram-originated prompts so π has a wall-clock reference for requests such as "today", "now", or scheduling. It is off by default and uses the system timezone; the mode can also be changed from Settings → `🕒 Time`.
|
|
244
|
+
|
|
245
|
+
```json
|
|
246
|
+
{
|
|
247
|
+
"time": {
|
|
248
|
+
"injectionMode": "interval",
|
|
249
|
+
"interval": 3600000
|
|
250
|
+
}
|
|
251
|
+
}
|
|
252
|
+
```
|
|
253
|
+
|
|
254
|
+
Modes are `off`, `always`, and `interval`. `interval` is measured in milliseconds and rate-limits the time line per chat in memory, so back-to-back messages do not repeatedly spend context on the same timestamp. When present, `[time]` is the final prompt-context section after attachments, handler outputs, and voice policy.
|
|
255
|
+
|
|
241
256
|
## Docs
|
|
242
257
|
|
|
243
258
|
- [Project Context](./AGENTS.md): durable engineering conventions and architecture constraints.
|
package/docs/architecture.md
CHANGED
|
@@ -28,7 +28,7 @@ Current runtime areas use these ownership boundaries:
|
|
|
28
28
|
- `config` / `setup`: persisted bot/session pairing state, authorization, first-user pairing, token prompting, env fallback, validation, and config persistence.
|
|
29
29
|
- `locks` / `polling`: singleton `locks.json` ownership, takeover/restart semantics, long-poll controller state, update offset persistence, and poll-loop runtime wiring.
|
|
30
30
|
- `updates` / `routing`: update classification/execution planning, paired authorization, reactions, edits, callbacks, and inbound route composition.
|
|
31
|
-
- `media` / `text-groups` / `turns` / `inbound-handlers`: text/media extraction, media-group debounce, long-text split coalescing, inbound downloads, configured and programmatic inbound text/media handler execution, turn building/editing, image reads, and legacy `attachmentHandlers` compatibility.
|
|
31
|
+
- `media` / `text-groups` / `time-injection` / `turns` / `inbound-handlers`: text/media extraction, media-group debounce, long-text split coalescing, optional per-chat wall-clock prompt context, inbound downloads, configured and programmatic inbound text/media handler execution, turn building/editing, image reads, and legacy `attachmentHandlers` compatibility.
|
|
32
32
|
- `queue`: queue item contracts, lane admission/order, stores, mutations, dispatch readiness/runtime, prompt/control enqueueing, and session/agent/tool lifecycle sequencing.
|
|
33
33
|
- `runtime`: session-local coordination primitives: counters, lifecycle flags, setup guard, abort handler, typing-loop timers, prompt-dispatch flags, and agent-end reset binding.
|
|
34
34
|
- `model` / `menu-model` / `menu-thinking` / `menu-status` / `menu` / `menu-queue` / `menu-settings` / `commands`: model identity/thinking levels, scoped model resolution, in-flight switching, model/thinking/status/queue/settings menu UI, inline application callback composition, slash commands, and bot command registration.
|
|
@@ -79,9 +79,10 @@ Telegram bot configuration stays in `~/.pi/agent/telegram.json`; singleton runti
|
|
|
79
79
|
7. Configured inbound handlers may run on raw text or downloaded files by MIME wildcard, Telegram attachment type, or generic match selector; command templates receive safe command-arg substitution for `{text}`, `{file}`, `{mime}`, and `{type}` where applicable
|
|
80
80
|
8. Matching media/file handlers are tried in config order: a non-zero exit records diagnostics and falls back to the next matching handler, while the first successful handler stops the chain
|
|
81
81
|
9. Local attachments stay visible under `[attachments] <directory>` with relative file entries, and handler stdout is appended under `[outputs]` before the agent sees the turn; failed handlers omit output while keeping the attachment entry
|
|
82
|
-
10.
|
|
83
|
-
11.
|
|
84
|
-
12.
|
|
82
|
+
10. Optional `time` config may add a compact final `[time]` prompt line after attachment/output/voice sections, either every turn or per-chat after the configured millisecond interval, using the system timezone
|
|
83
|
+
11. A `PendingTelegramTurn` is created and queued locally
|
|
84
|
+
12. Telegram `edited_message` updates are routed separately and update a matching queued turn when the original message has not been dispatched yet
|
|
85
|
+
13. The queue dispatcher sends the turn into π only when dispatch is safe
|
|
85
86
|
|
|
86
87
|
### Queue Safety Model
|
|
87
88
|
|
|
@@ -114,7 +115,7 @@ Dispatch is gated by:
|
|
|
114
115
|
|
|
115
116
|
This prevents queue races around rapid follow-ups, `/compact`, and mixed local plus Telegram activity. Post-agent-end dispatch retries are scheduled through a session-bound deferred dispatcher that activates on session start, cancels timers on session shutdown, and skips callbacks from older generations before they touch `ExtensionContext`. Telegram `/start` and hidden compatibility shortcuts `/status`, `/model`, `/thinking`, `/queue`, and `/settings` execute immediately; the dispatch controller still serializes any deferred control items so a queued control action must settle before the next queued action can dispatch.
|
|
116
117
|
|
|
117
|
-
`/start` opens the main application menu: visible command help, compact command-only prompt-template rows when π exposes Telegram-compatible prompt-template names, status rows (`Status`, `Usage`, `Cost`, `Context`), and top-level buttons for model, thinking, and queue sections. The `Status` row reports `compacting` while a Telegram `/compact` run is active, and the bridge sends Telegram's native `typing` chat action as a keepalive for the same compaction window. The Queue button includes the current queued-item count. Hidden compatibility shortcuts `/help`, `/status`, `/model`, `/thinking`, and `/queue` jump directly to their corresponding menu screens, while `/settings` opens the hidden settings menu for bridge toggles such as proactive push. Settings options open detail submenus; checkbox-like settings use Back plus green/black/yellow `on` and `off` controls instead of mutating directly from the list. Command emoji come from the `commands` domain map so visible command descriptions and matching menu buttons share one fixed adornment source. Prompt-template commands use a fixed `🧩` marker, map π template names to Telegram-safe aliases such as `fix-tests` → `/fix_tests`, stay visible only inside the `/start` menu, and expand before queueing because `ExtensionAPI.sendUserMessage()` intentionally bypasses π prompt-template expansion for extension-originated messages. Every submenu starts with a top Back row so navigation stays anchored near the original user message above the inline keyboard; model-menu pagination controls sit near the top, tapping the pagination indicator opens a compact page picker headed by `<b>Choose a page:</b>`, and tapping a model opens a detail submenu with Back, ☑️ Activate/🟢 Active selection, and yellow/black-marked Scoped/All membership tabs. `menu-model` owns model-menu state, scoped model pages, model detail rendering, scoped-list persistence planning, and model-menu rendering while `model` owns core model identity/switching semantics. `menu-thinking` owns thinking-menu text, reply markup, callback handling, and message rendering. `menu-status` owns status-menu payloads, status callback handling, and status-message rendering. `menu-queue` owns queue-menu UI only: queue items are rendered under a compact `<b>Queue:</b>` heading, top-to-bottom in dispatch order, numbered, and marked with `⚡` for priority prompts or `📎` for prompts with attachments. An empty queue renders bold message text with the bottom-filled `⌛` hourglass plus the top Main menu button, while non-empty queue states keep the running `⏳` hourglass. Selecting an item opens a submenu that displays the queue item number above the full queued prompt text with Back, side-by-side Priority/Normal tabs, and Cancel. If a callback targets an item that has already left the queue, the menu refreshes the list instead of applying a stale mutation.
|
|
118
|
+
`/start` opens the main application menu: visible command help, compact command-only prompt-template rows when π exposes Telegram-compatible prompt-template names, status rows (`Status`, `Usage`, `Cost`, `Context`), and top-level buttons for model, thinking, and queue sections. The `Status` row reports `compacting` while a Telegram `/compact` run is active, and the bridge sends Telegram's native `typing` chat action as a keepalive for the same compaction window. The Queue button includes the current queued-item count. Hidden compatibility shortcuts `/help`, `/status`, `/model`, `/thinking`, and `/queue` jump directly to their corresponding menu screens, while `/settings` opens the hidden settings menu for bridge toggles such as proactive push, voice reply mode, and `time.injectionMode`. Settings options open detail submenus; checkbox-like settings use Back plus green/black/yellow `on` and `off` controls instead of mutating directly from the list. Command emoji come from the `commands` domain map so visible command descriptions and matching menu buttons share one fixed adornment source. Prompt-template commands use a fixed `🧩` marker, map π template names to Telegram-safe aliases such as `fix-tests` → `/fix_tests`, stay visible only inside the `/start` menu, and expand before queueing because `ExtensionAPI.sendUserMessage()` intentionally bypasses π prompt-template expansion for extension-originated messages. Every submenu starts with a top Back row so navigation stays anchored near the original user message above the inline keyboard; model-menu pagination controls sit near the top, tapping the pagination indicator opens a compact page picker headed by `<b>Choose a page:</b>`, and tapping a model opens a detail submenu with Back, ☑️ Activate/🟢 Active selection, and yellow/black-marked Scoped/All membership tabs. `menu-model` owns model-menu state, scoped model pages, model detail rendering, scoped-list persistence planning, and model-menu rendering while `model` owns core model identity/switching semantics. `menu-thinking` owns thinking-menu text, reply markup, callback handling, and message rendering. `menu-status` owns status-menu payloads, status callback handling, and status-message rendering. `menu-queue` owns queue-menu UI only: queue items are rendered under a compact `<b>Queue:</b>` heading, top-to-bottom in dispatch order, numbered, and marked with `⚡` for priority prompts or `📎` for prompts with attachments. An empty queue renders bold message text with the bottom-filled `⌛` hourglass plus the top Main menu button, while non-empty queue states keep the running `⏳` hourglass. Selecting an item opens a submenu that displays the queue item number above the full queued prompt text with Back, side-by-side Priority/Normal tabs, and Cancel. If a callback targets an item that has already left the queue, the menu refreshes the list instead of applying a stale mutation.
|
|
118
119
|
|
|
119
120
|
### Abort Behavior
|
|
120
121
|
|
package/index.ts
CHANGED
|
@@ -38,6 +38,7 @@ import * as Runtime from "./lib/runtime.ts";
|
|
|
38
38
|
import * as Setup from "./lib/setup.ts";
|
|
39
39
|
import * as Status from "./lib/status.ts";
|
|
40
40
|
import * as TextGroups from "./lib/text-groups.ts";
|
|
41
|
+
import * as TimeInjection from "./lib/time-injection.ts";
|
|
41
42
|
import * as Voice from "./lib/voice.ts";
|
|
42
43
|
|
|
43
44
|
const VOICE_EVENT_RECORDER_KEY = "__piTelegramVoiceEventRecorder__";
|
|
@@ -113,6 +114,10 @@ export default function (pi: Pi.ExtensionAPI) {
|
|
|
113
114
|
Config.createTelegramVoiceReplyModeConfiguredChecker(configStore);
|
|
114
115
|
const setVoiceReplyMode =
|
|
115
116
|
Config.createTelegramVoiceReplyModeSetter(configStore);
|
|
117
|
+
const getTimeInjectionMode =
|
|
118
|
+
Config.createTelegramTimeInjectionModeGetter(configStore);
|
|
119
|
+
const setTimeInjectionMode =
|
|
120
|
+
Config.createTelegramTimeInjectionModeSetter(configStore);
|
|
116
121
|
const lockRuntime = Locks.createTelegramLockRuntime<Pi.ExtensionContext>();
|
|
117
122
|
const lockOwnershipGuard =
|
|
118
123
|
Locks.createTelegramLockOwnershipGuard(lockRuntime);
|
|
@@ -137,6 +142,10 @@ export default function (pi: Pi.ExtensionAPI) {
|
|
|
137
142
|
getBotToken: configStore.getBotToken,
|
|
138
143
|
});
|
|
139
144
|
const recordRuntimeEvent = runtimeEvents.record;
|
|
145
|
+
const timeInjectionRuntime = TimeInjection.createTimeInjectionRuntime({
|
|
146
|
+
getConfig: Config.createTelegramTimeConfigGetter(configStore),
|
|
147
|
+
recordRuntimeEvent,
|
|
148
|
+
});
|
|
140
149
|
(globalThis as Record<string, unknown>)[
|
|
141
150
|
VOICE_EVENT_RECORDER_KEY
|
|
142
151
|
] = recordRuntimeEvent;
|
|
@@ -386,8 +395,10 @@ export default function (pi: Pi.ExtensionAPI) {
|
|
|
386
395
|
isProactivePushEnabled,
|
|
387
396
|
getVoiceReplyMode,
|
|
388
397
|
isVoiceReplyModeConfigured,
|
|
398
|
+
getTimeInjectionMode,
|
|
389
399
|
setProactivePushEnabled,
|
|
390
400
|
setVoiceReplyMode,
|
|
401
|
+
setTimeInjectionMode,
|
|
391
402
|
},
|
|
392
403
|
sectionRegistry,
|
|
393
404
|
);
|
|
@@ -435,6 +446,7 @@ export default function (pi: Pi.ExtensionAPI) {
|
|
|
435
446
|
setMyCommands,
|
|
436
447
|
getCommands,
|
|
437
448
|
downloadFile: downloadTelegramBridgeFile,
|
|
449
|
+
resolveTimeLine: timeInjectionRuntime.resolveLine,
|
|
438
450
|
getThinkingLevel,
|
|
439
451
|
setThinkingLevel,
|
|
440
452
|
persistScopedModelPatterns: Pi.createScopedModelPatternPersister({
|
package/lib/config.ts
CHANGED
|
@@ -35,6 +35,19 @@ export interface TelegramOutboundHandlerConfig extends CommandTemplateObjectConf
|
|
|
35
35
|
timeout?: number;
|
|
36
36
|
}
|
|
37
37
|
|
|
38
|
+
export type TelegramTimeMode = "off" | "always" | "interval";
|
|
39
|
+
|
|
40
|
+
export interface TelegramTimeConfig {
|
|
41
|
+
injectionMode?: TelegramTimeMode;
|
|
42
|
+
interval?: number;
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
export interface ResolvedTelegramTimeConfig {
|
|
46
|
+
injectionMode: TelegramTimeMode;
|
|
47
|
+
interval: number;
|
|
48
|
+
timezone: string;
|
|
49
|
+
}
|
|
50
|
+
|
|
38
51
|
export interface TelegramConfig {
|
|
39
52
|
botToken?: string;
|
|
40
53
|
botUsername?: string;
|
|
@@ -50,6 +63,7 @@ export interface TelegramConfig {
|
|
|
50
63
|
/** Whether to attach the provider's transcriptText as caption on voice messages */
|
|
51
64
|
sendTranscript?: boolean;
|
|
52
65
|
};
|
|
66
|
+
time?: TelegramTimeConfig;
|
|
53
67
|
}
|
|
54
68
|
|
|
55
69
|
export interface TelegramConfigStore {
|
|
@@ -211,6 +225,56 @@ export function createTelegramVoiceReplyModeSetter(
|
|
|
211
225
|
};
|
|
212
226
|
}
|
|
213
227
|
|
|
228
|
+
function getSystemTimezone(): string {
|
|
229
|
+
try {
|
|
230
|
+
const tz = Intl.DateTimeFormat().resolvedOptions().timeZone;
|
|
231
|
+
return tz && tz.length > 0 ? tz : "UTC";
|
|
232
|
+
} catch {
|
|
233
|
+
return "UTC";
|
|
234
|
+
}
|
|
235
|
+
}
|
|
236
|
+
|
|
237
|
+
export function resolveTelegramTimeConfig(
|
|
238
|
+
raw: TelegramTimeConfig | undefined,
|
|
239
|
+
): ResolvedTelegramTimeConfig {
|
|
240
|
+
const injectionMode: TelegramTimeMode =
|
|
241
|
+
raw?.injectionMode === "always" || raw?.injectionMode === "interval"
|
|
242
|
+
? raw.injectionMode
|
|
243
|
+
: "off";
|
|
244
|
+
const interval =
|
|
245
|
+
typeof raw?.interval === "number" && raw.interval > 0
|
|
246
|
+
? raw.interval
|
|
247
|
+
: 60 * 60 * 1000;
|
|
248
|
+
const timezone = getSystemTimezone();
|
|
249
|
+
return { injectionMode, interval, timezone };
|
|
250
|
+
}
|
|
251
|
+
|
|
252
|
+
export function createTelegramTimeConfigGetter(
|
|
253
|
+
configStore: Pick<TelegramConfigStore, "get">,
|
|
254
|
+
): () => ResolvedTelegramTimeConfig {
|
|
255
|
+
return () => resolveTelegramTimeConfig(configStore.get().time);
|
|
256
|
+
}
|
|
257
|
+
|
|
258
|
+
export function createTelegramTimeInjectionModeGetter(
|
|
259
|
+
configStore: Pick<TelegramConfigStore, "get">,
|
|
260
|
+
): () => TelegramTimeMode {
|
|
261
|
+
return () => resolveTelegramTimeConfig(configStore.get().time).injectionMode;
|
|
262
|
+
}
|
|
263
|
+
|
|
264
|
+
export function createTelegramTimeInjectionModeSetter(
|
|
265
|
+
configStore: Pick<TelegramConfigStore, "get" | "set" | "persist">,
|
|
266
|
+
): (injectionMode: TelegramTimeMode) => Promise<void> {
|
|
267
|
+
return async (injectionMode) => {
|
|
268
|
+
const current = configStore.get();
|
|
269
|
+
const next = {
|
|
270
|
+
...current,
|
|
271
|
+
time: { ...(current.time ?? {}), injectionMode },
|
|
272
|
+
};
|
|
273
|
+
configStore.set(next);
|
|
274
|
+
await configStore.persist(next);
|
|
275
|
+
};
|
|
276
|
+
}
|
|
277
|
+
|
|
214
278
|
export function createTelegramProactivePushChatIdGetter(deps: {
|
|
215
279
|
getActiveTurnChatId: () => number | undefined;
|
|
216
280
|
getAllowedUserId: () => number | undefined;
|
package/lib/menu-settings.ts
CHANGED
|
@@ -8,6 +8,7 @@ import {
|
|
|
8
8
|
getTelegramExtensionSettingsRows,
|
|
9
9
|
type TelegramSectionRegistry,
|
|
10
10
|
} from "./extension-sections.ts";
|
|
11
|
+
import type { TelegramTimeMode } from "./config.ts";
|
|
11
12
|
import type { TelegramInlineKeyboardMarkup } from "./keyboard.ts";
|
|
12
13
|
import type { TelegramModelMenuState } from "./menu-model.ts";
|
|
13
14
|
import type { MenuModel } from "./model.ts";
|
|
@@ -17,6 +18,7 @@ export type TelegramSettingsMenuReplyMarkup = TelegramInlineKeyboardMarkup;
|
|
|
17
18
|
|
|
18
19
|
export interface TelegramSettingsStateDeps {
|
|
19
20
|
isProactivePushEnabled: () => boolean;
|
|
21
|
+
getTimeInjectionMode: () => TelegramTimeMode;
|
|
20
22
|
getVoiceReplyMode: () => TelegramVoiceReplyMode;
|
|
21
23
|
isVoiceReplyModeConfigured: () => boolean;
|
|
22
24
|
}
|
|
@@ -26,6 +28,7 @@ export interface TelegramSettingsMutationDeps extends TelegramSettingsStateDeps
|
|
|
26
28
|
setVoiceReplyMode: (
|
|
27
29
|
mode: TelegramVoiceReplyMode | undefined,
|
|
28
30
|
) => Promise<void>;
|
|
31
|
+
setTimeInjectionMode: (mode: TelegramTimeMode) => Promise<void>;
|
|
29
32
|
}
|
|
30
33
|
|
|
31
34
|
export interface TelegramSettingsMenuOpenDeps<
|
|
@@ -111,8 +114,9 @@ export interface TelegramSettingsMenuRuntimeDeps<
|
|
|
111
114
|
}
|
|
112
115
|
|
|
113
116
|
export const SETTINGS_MENU_TITLE = "<b>⚙️ Settings:</b>";
|
|
114
|
-
export const PROACTIVE_PUSH_SETTINGS_TITLE = "<b
|
|
115
|
-
export const
|
|
117
|
+
export const PROACTIVE_PUSH_SETTINGS_TITLE = "<b>📌 Proactive push:</b>";
|
|
118
|
+
export const TIME_INJECTION_MODE_SETTINGS_TITLE = "<b>🕒 Time injection mode:</b>";
|
|
119
|
+
export const VOICE_REPLY_MODE_SETTINGS_TITLE = "<b>👄 Voice reply mode:</b>";
|
|
116
120
|
|
|
117
121
|
type TelegramVoiceReplyModeSetting = TelegramVoiceReplyMode | "hidden";
|
|
118
122
|
|
|
@@ -152,9 +156,22 @@ export function buildVoiceReplyModeSettingsText(): string {
|
|
|
152
156
|
].join("\n");
|
|
153
157
|
}
|
|
154
158
|
|
|
159
|
+
export function buildTimeInjectionModeSettingsText(): string {
|
|
160
|
+
return [
|
|
161
|
+
TIME_INJECTION_MODE_SETTINGS_TITLE,
|
|
162
|
+
"",
|
|
163
|
+
"Controls whether Telegram-originated prompts include a compact wall-clock [time] line.",
|
|
164
|
+
"",
|
|
165
|
+
"<code>-</code> <code>off</code> (default): no time line is added.",
|
|
166
|
+
"<code>-</code> <code>always</code>: add time to every Telegram turn.",
|
|
167
|
+
"<code>-</code> <code>interval</code>: add time at most once per chat interval (default: 1 hour).",
|
|
168
|
+
].join("\n");
|
|
169
|
+
}
|
|
170
|
+
|
|
155
171
|
export function buildTelegramSettingsMenuReplyMarkup(
|
|
156
172
|
proactivePushEnabled: boolean,
|
|
157
173
|
voiceReplyMode: TelegramVoiceReplyMode,
|
|
174
|
+
timeInjectionMode: TelegramTimeMode,
|
|
158
175
|
sectionRegistry?: TelegramSectionRegistry,
|
|
159
176
|
voiceReplyModeConfigured = true,
|
|
160
177
|
): TelegramSettingsMenuReplyMarkup {
|
|
@@ -179,7 +196,13 @@ export function buildTelegramSettingsMenuReplyMarkup(
|
|
|
179
196
|
],
|
|
180
197
|
[
|
|
181
198
|
{
|
|
182
|
-
text:
|
|
199
|
+
text: `🕒 Time injection: ${timeInjectionMode}`,
|
|
200
|
+
callback_data: "settings:open:time-injection",
|
|
201
|
+
},
|
|
202
|
+
],
|
|
203
|
+
[
|
|
204
|
+
{
|
|
205
|
+
text: `📌 Proactive push: ${proactivePushEnabled ? "on" : "off"}`,
|
|
183
206
|
callback_data: "settings:open:proactive",
|
|
184
207
|
},
|
|
185
208
|
],
|
|
@@ -200,6 +223,7 @@ export async function openTelegramSettingsMenu<
|
|
|
200
223
|
buildTelegramSettingsMenuReplyMarkup(
|
|
201
224
|
deps.isProactivePushEnabled(),
|
|
202
225
|
deps.getVoiceReplyMode(),
|
|
226
|
+
deps.getTimeInjectionMode(),
|
|
203
227
|
sectionRegistry,
|
|
204
228
|
deps.isVoiceReplyModeConfigured(),
|
|
205
229
|
),
|
|
@@ -230,6 +254,23 @@ export function buildProactivePushSettingsReplyMarkup(
|
|
|
230
254
|
};
|
|
231
255
|
}
|
|
232
256
|
|
|
257
|
+
export function buildTimeInjectionModeSettingsReplyMarkup(
|
|
258
|
+
mode: TelegramTimeMode,
|
|
259
|
+
): TelegramSettingsMenuReplyMarkup {
|
|
260
|
+
const modes: TelegramTimeMode[] = ["off", "always", "interval"];
|
|
261
|
+
return {
|
|
262
|
+
inline_keyboard: [
|
|
263
|
+
[{ text: "⬆️ Back", callback_data: "settings:list" }],
|
|
264
|
+
...modes.map((value) => [
|
|
265
|
+
{
|
|
266
|
+
text: `${value === mode ? "🟢 " : ""}${value}`,
|
|
267
|
+
callback_data: `settings:set:time-injection:${value}`,
|
|
268
|
+
},
|
|
269
|
+
]),
|
|
270
|
+
],
|
|
271
|
+
};
|
|
272
|
+
}
|
|
273
|
+
|
|
233
274
|
export function buildVoiceReplyModeSettingsReplyMarkup(
|
|
234
275
|
mode: TelegramVoiceReplyMode,
|
|
235
276
|
configured = true,
|
|
@@ -263,6 +304,7 @@ export async function updateTelegramSettingsMenuMessage(
|
|
|
263
304
|
buildTelegramSettingsMenuReplyMarkup(
|
|
264
305
|
deps.isProactivePushEnabled(),
|
|
265
306
|
deps.getVoiceReplyMode(),
|
|
307
|
+
deps.getTimeInjectionMode(),
|
|
266
308
|
sectionRegistry,
|
|
267
309
|
deps.isVoiceReplyModeConfigured(),
|
|
268
310
|
),
|
|
@@ -278,6 +320,15 @@ export async function updateProactivePushSettingsMessage(
|
|
|
278
320
|
);
|
|
279
321
|
}
|
|
280
322
|
|
|
323
|
+
export async function updateTimeInjectionModeSettingsMessage(
|
|
324
|
+
deps: TelegramSettingsMenuCallbackDeps,
|
|
325
|
+
): Promise<void> {
|
|
326
|
+
await deps.updateSettingsMessage(
|
|
327
|
+
buildTimeInjectionModeSettingsText(),
|
|
328
|
+
buildTimeInjectionModeSettingsReplyMarkup(deps.getTimeInjectionMode()),
|
|
329
|
+
);
|
|
330
|
+
}
|
|
331
|
+
|
|
281
332
|
export async function updateVoiceReplyModeSettingsMessage(
|
|
282
333
|
deps: TelegramSettingsMenuCallbackDeps,
|
|
283
334
|
): Promise<void> {
|
|
@@ -311,6 +362,11 @@ export async function handleTelegramSettingsMenuCallbackAction(
|
|
|
311
362
|
await deps.answerCallbackQuery(callbackQueryId);
|
|
312
363
|
return true;
|
|
313
364
|
}
|
|
365
|
+
if (data === "settings:open:time-injection" || data === "settings:open:time") {
|
|
366
|
+
await updateTimeInjectionModeSettingsMessage(deps);
|
|
367
|
+
await deps.answerCallbackQuery(callbackQueryId);
|
|
368
|
+
return true;
|
|
369
|
+
}
|
|
314
370
|
if (data.startsWith("settings:set:voice-reply:")) {
|
|
315
371
|
const mode = data.slice("settings:set:voice-reply:".length);
|
|
316
372
|
if (
|
|
@@ -328,6 +384,20 @@ export async function handleTelegramSettingsMenuCallbackAction(
|
|
|
328
384
|
return true;
|
|
329
385
|
}
|
|
330
386
|
}
|
|
387
|
+
if (
|
|
388
|
+
data.startsWith("settings:set:time-injection:") ||
|
|
389
|
+
data.startsWith("settings:set:time:")
|
|
390
|
+
) {
|
|
391
|
+
const mode = data.startsWith("settings:set:time-injection:")
|
|
392
|
+
? data.slice("settings:set:time-injection:".length)
|
|
393
|
+
: data.slice("settings:set:time:".length);
|
|
394
|
+
if (mode === "off" || mode === "always" || mode === "interval") {
|
|
395
|
+
await deps.setTimeInjectionMode(mode);
|
|
396
|
+
await updateTimeInjectionModeSettingsMessage(deps);
|
|
397
|
+
await deps.answerCallbackQuery(callbackQueryId, `Time injection: ${mode}`);
|
|
398
|
+
return true;
|
|
399
|
+
}
|
|
400
|
+
}
|
|
331
401
|
if (
|
|
332
402
|
data === "settings:set:proactive:on" ||
|
|
333
403
|
data === "settings:set:proactive:off"
|
|
@@ -360,6 +430,7 @@ export function createTelegramSettingsMenuRuntime<
|
|
|
360
430
|
isProactivePushEnabled: deps.isProactivePushEnabled,
|
|
361
431
|
getVoiceReplyMode: deps.getVoiceReplyMode,
|
|
362
432
|
isVoiceReplyModeConfigured: deps.isVoiceReplyModeConfigured,
|
|
433
|
+
getTimeInjectionMode: deps.getTimeInjectionMode,
|
|
363
434
|
sendSettingsMenu: (state, text, replyMarkup) =>
|
|
364
435
|
deps.sendInteractiveMessage(
|
|
365
436
|
state.chatId,
|
|
@@ -377,6 +448,7 @@ export function createTelegramSettingsMenuRuntime<
|
|
|
377
448
|
isProactivePushEnabled: deps.isProactivePushEnabled,
|
|
378
449
|
getVoiceReplyMode: deps.getVoiceReplyMode,
|
|
379
450
|
isVoiceReplyModeConfigured: deps.isVoiceReplyModeConfigured,
|
|
451
|
+
getTimeInjectionMode: deps.getTimeInjectionMode,
|
|
380
452
|
updateSettingsMessage: (text, replyMarkup) =>
|
|
381
453
|
deps.editInteractiveMessage(
|
|
382
454
|
state.chatId,
|
|
@@ -392,16 +464,40 @@ export function createTelegramSettingsMenuRuntime<
|
|
|
392
464
|
if (!query.data?.startsWith("settings:")) return false;
|
|
393
465
|
const state = deps.getStoredModelMenuState(query.message?.message_id);
|
|
394
466
|
if (!state) {
|
|
395
|
-
const
|
|
467
|
+
const voiceMode = query.data.slice("settings:set:voice-reply:".length);
|
|
396
468
|
if (
|
|
397
469
|
query.data.startsWith("settings:set:voice-reply:") &&
|
|
398
|
-
(
|
|
399
|
-
|
|
400
|
-
|
|
401
|
-
|
|
470
|
+
(voiceMode === "hidden" ||
|
|
471
|
+
voiceMode === "manual" ||
|
|
472
|
+
voiceMode === "mirror" ||
|
|
473
|
+
voiceMode === "always")
|
|
474
|
+
) {
|
|
475
|
+
await deps.setVoiceReplyMode(
|
|
476
|
+
voiceMode === "hidden" ? undefined : voiceMode,
|
|
477
|
+
);
|
|
478
|
+
await deps.answerCallbackQuery(
|
|
479
|
+
query.id,
|
|
480
|
+
`Voice reply mode: ${voiceMode}`,
|
|
481
|
+
);
|
|
482
|
+
return true;
|
|
483
|
+
}
|
|
484
|
+
const hasTimeInjectionPrefix = query.data.startsWith(
|
|
485
|
+
"settings:set:time-injection:",
|
|
486
|
+
);
|
|
487
|
+
const timeMode = hasTimeInjectionPrefix
|
|
488
|
+
? query.data.slice("settings:set:time-injection:".length)
|
|
489
|
+
: query.data.slice("settings:set:time:".length);
|
|
490
|
+
if (
|
|
491
|
+
(hasTimeInjectionPrefix || query.data.startsWith("settings:set:time:")) &&
|
|
492
|
+
(timeMode === "off" ||
|
|
493
|
+
timeMode === "always" ||
|
|
494
|
+
timeMode === "interval")
|
|
402
495
|
) {
|
|
403
|
-
await deps.
|
|
404
|
-
await deps.answerCallbackQuery(
|
|
496
|
+
await deps.setTimeInjectionMode(timeMode);
|
|
497
|
+
await deps.answerCallbackQuery(
|
|
498
|
+
query.id,
|
|
499
|
+
`Time injection: ${timeMode}`,
|
|
500
|
+
);
|
|
405
501
|
return true;
|
|
406
502
|
}
|
|
407
503
|
await deps.answerCallbackQuery(
|
|
@@ -414,8 +510,10 @@ export function createTelegramSettingsMenuRuntime<
|
|
|
414
510
|
isProactivePushEnabled: deps.isProactivePushEnabled,
|
|
415
511
|
getVoiceReplyMode: deps.getVoiceReplyMode,
|
|
416
512
|
isVoiceReplyModeConfigured: deps.isVoiceReplyModeConfigured,
|
|
513
|
+
getTimeInjectionMode: deps.getTimeInjectionMode,
|
|
417
514
|
setProactivePushEnabled: deps.setProactivePushEnabled,
|
|
418
515
|
setVoiceReplyMode: deps.setVoiceReplyMode,
|
|
516
|
+
setTimeInjectionMode: deps.setTimeInjectionMode,
|
|
419
517
|
updateSettingsMessage: (text, replyMarkup) =>
|
|
420
518
|
deps.editInteractiveMessage(
|
|
421
519
|
state.chatId,
|
package/lib/prompts.ts
CHANGED
|
@@ -15,6 +15,7 @@ Inbound context:
|
|
|
15
15
|
- \`[telegram]\` marks Telegram-originated messages. Suffixes \`|from:user\` (sender) and \`|guest:group\` (guest mode — message from another chat where the bot is not a member) may be present; the bot sees the message as if forwarded from that user/chat.
|
|
16
16
|
- \`[reply]\` is quoted context from the replied-to message, not a new instruction by itself. Suffix \`|from:user\` identifies the original author in guest-mode replies. Use it to resolve references like "this", "it", or "that message"; the actual instruction is before [reply] unless it explicitly asks to act on the quote.
|
|
17
17
|
- \`[attachments]\` gives a base directory plus relative local files; resolve and read them as needed. \`[outputs]\` contains inbound-handler stdout such as transcriptions or extracted text for those attachments.
|
|
18
|
+
- \`[time]\` gives the wall-clock time for this Telegram turn when the operator enabled time injection. Use it for relative-date requests like "today", "now", or scheduling; otherwise do not mention it.
|
|
18
19
|
- \`[voice]\` describes Telegram voice reply policy for this turn. \`manual\` means answer normally and use explicit \`telegram_voice\` markup only when a spoken reply is useful; \`mirror\` means voice input prefers a voice reply; \`always\` means the final reply is expected to be converted to voice, so keep it TTS-friendly.
|
|
19
20
|
- Unknown \`[callback] ...\` messages may be intended for another extension; if you see one, say the callback was not handled and the environment may be misconfigured.
|
|
20
21
|
|
package/lib/routing.ts
CHANGED
|
@@ -114,6 +114,7 @@ export interface TelegramInboundRouteRuntimeDeps<
|
|
|
114
114
|
typeof PromptTemplates.getTelegramPromptTemplateCommands
|
|
115
115
|
>[0];
|
|
116
116
|
downloadFile: Media.DownloadTelegramMessageFilesDeps["downloadFile"];
|
|
117
|
+
resolveTimeLine?: (chatId: number) => string | null;
|
|
117
118
|
getThinkingLevel: () => Model.ThinkingLevel;
|
|
118
119
|
setThinkingLevel: (level: Model.ThinkingLevel) => void;
|
|
119
120
|
persistScopedModelPatterns?: (
|
|
@@ -287,6 +288,7 @@ export function createTelegramInboundRouteRuntime<
|
|
|
287
288
|
allocateQueueOrder: deps.bridgeRuntime.queue.allocateItemOrder,
|
|
288
289
|
downloadFile: deps.downloadFile,
|
|
289
290
|
processAttachments: deps.inboundHandlerRuntime.process,
|
|
291
|
+
resolveTimeLine: deps.resolveTimeLine,
|
|
290
292
|
|
|
291
293
|
// Voice policy for the current turn. Missing config still behaves as manual,
|
|
292
294
|
// but only explicit telegram.json voice.replyMode is shown in prompt context.
|
|
@@ -0,0 +1,78 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Telegram per-chat time injection runtime
|
|
3
|
+
* Zones: telegram inbound, prompt content
|
|
4
|
+
* Owns the formatted `[time]` line and the per-chat interval bookkeeping that decides when to emit it
|
|
5
|
+
*/
|
|
6
|
+
|
|
7
|
+
import type { ResolvedTelegramTimeConfig } from "./config.ts";
|
|
8
|
+
|
|
9
|
+
export interface TimeInjectionRuntime {
|
|
10
|
+
resolveLine: (chatId: number, now?: Date) => string | null;
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
export interface TimeInjectionRuntimeDeps {
|
|
14
|
+
getConfig: () => ResolvedTelegramTimeConfig;
|
|
15
|
+
recordRuntimeEvent?: (
|
|
16
|
+
category: string,
|
|
17
|
+
error: unknown,
|
|
18
|
+
details?: Record<string, unknown>,
|
|
19
|
+
) => void;
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
export function formatTelegramTimeInjectionLine(
|
|
23
|
+
now: Date,
|
|
24
|
+
timezone: string,
|
|
25
|
+
): string {
|
|
26
|
+
const parts = new Intl.DateTimeFormat("en-CA", {
|
|
27
|
+
timeZone: timezone,
|
|
28
|
+
year: "numeric",
|
|
29
|
+
month: "2-digit",
|
|
30
|
+
day: "2-digit",
|
|
31
|
+
hour: "2-digit",
|
|
32
|
+
minute: "2-digit",
|
|
33
|
+
second: "2-digit",
|
|
34
|
+
hour12: false,
|
|
35
|
+
}).formatToParts(now);
|
|
36
|
+
const get = (type: Intl.DateTimeFormatPartTypes): string =>
|
|
37
|
+
parts.find((part) => part.type === type)?.value ?? "";
|
|
38
|
+
const year = get("year");
|
|
39
|
+
const month = get("month");
|
|
40
|
+
const day = get("day");
|
|
41
|
+
const hourRaw = get("hour");
|
|
42
|
+
const hour = hourRaw === "24" ? "00" : hourRaw;
|
|
43
|
+
const minute = get("minute");
|
|
44
|
+
const second = get("second");
|
|
45
|
+
return `${year}-${month}-${day} ${hour}:${minute}:${second} ${timezone}`;
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
export function createTimeInjectionRuntime(
|
|
49
|
+
deps: TimeInjectionRuntimeDeps,
|
|
50
|
+
): TimeInjectionRuntime {
|
|
51
|
+
const lastInjectedAt = new Map<number, number>();
|
|
52
|
+
return {
|
|
53
|
+
resolveLine: (chatId, now = new Date()) => {
|
|
54
|
+
const config = deps.getConfig();
|
|
55
|
+
if (config.injectionMode === "off") return null;
|
|
56
|
+
let line: string;
|
|
57
|
+
try {
|
|
58
|
+
line = formatTelegramTimeInjectionLine(now, config.timezone);
|
|
59
|
+
} catch (error) {
|
|
60
|
+
deps.recordRuntimeEvent?.("time-injection", error, {
|
|
61
|
+
timezone: config.timezone,
|
|
62
|
+
});
|
|
63
|
+
return null;
|
|
64
|
+
}
|
|
65
|
+
if (config.injectionMode === "always") return line;
|
|
66
|
+
const previous = lastInjectedAt.get(chatId);
|
|
67
|
+
const nowMs = now.getTime();
|
|
68
|
+
if (
|
|
69
|
+
previous !== undefined &&
|
|
70
|
+
nowMs - previous < config.interval
|
|
71
|
+
) {
|
|
72
|
+
return null;
|
|
73
|
+
}
|
|
74
|
+
lastInjectedAt.set(chatId, nowMs);
|
|
75
|
+
return line;
|
|
76
|
+
},
|
|
77
|
+
};
|
|
78
|
+
}
|
package/lib/turns.ts
CHANGED
|
@@ -127,6 +127,7 @@ export function buildTelegramTurnPrompt(options: {
|
|
|
127
127
|
promptFiles?: DownloadedTelegramTurnFile[];
|
|
128
128
|
handlerOutputs?: string[];
|
|
129
129
|
historyTurns?: Pick<PendingTelegramTurn, "historyText">[];
|
|
130
|
+
timeLine?: string | null;
|
|
130
131
|
voiceContext?: Record<string, string>;
|
|
131
132
|
}): string {
|
|
132
133
|
let prompt = options.telegramPrefix;
|
|
@@ -154,6 +155,9 @@ export function buildTelegramTurnPrompt(options: {
|
|
|
154
155
|
if (options.voiceContext) {
|
|
155
156
|
prompt = appendTelegramVoiceContext(prompt, options.voiceContext);
|
|
156
157
|
}
|
|
158
|
+
if (options.timeLine) {
|
|
159
|
+
prompt = `${prompt}\n\n[time] ${options.timeLine}`;
|
|
160
|
+
}
|
|
157
161
|
return prompt;
|
|
158
162
|
}
|
|
159
163
|
|
|
@@ -330,6 +334,7 @@ export interface BuildTelegramPromptTurnOptions {
|
|
|
330
334
|
files: DownloadedTelegramTurnFile[];
|
|
331
335
|
promptFiles?: DownloadedTelegramTurnFile[];
|
|
332
336
|
handlerOutputs?: string[];
|
|
337
|
+
timeLine?: string | null;
|
|
333
338
|
readBinaryFile: (path: string) => Promise<Uint8Array>;
|
|
334
339
|
inferImageMimeType: (path: string) => string | undefined;
|
|
335
340
|
voiceReplyMode?: TelegramVoiceReplyMode;
|
|
@@ -355,6 +360,7 @@ export interface TelegramPromptTurnRuntimeBuilderDeps<
|
|
|
355
360
|
promptFiles?: DownloadedTelegramTurnFile[];
|
|
356
361
|
handlerOutputs?: string[];
|
|
357
362
|
}>;
|
|
363
|
+
resolveTimeLine?: (chatId: number) => string | null;
|
|
358
364
|
getVoiceReplyMode?: () => TelegramVoiceReplyMode;
|
|
359
365
|
isVoiceReplyModeConfigured?: () => boolean;
|
|
360
366
|
}
|
|
@@ -386,6 +392,11 @@ export function createTelegramPromptTurnRuntimeBuilder<
|
|
|
386
392
|
);
|
|
387
393
|
// Compute voice mode once and pass it to both the turn builder and the prompt contribution helper
|
|
388
394
|
const voiceReplyMode = deps.getVoiceReplyMode?.();
|
|
395
|
+
const chatId = messages[0]?.chat.id;
|
|
396
|
+
const timeLine =
|
|
397
|
+
deps.resolveTimeLine && chatId !== undefined
|
|
398
|
+
? deps.resolveTimeLine(chatId)
|
|
399
|
+
: null;
|
|
389
400
|
return buildTelegramPromptTurnRuntime({
|
|
390
401
|
telegramPrefix: TELEGRAM_PREFIX,
|
|
391
402
|
messages,
|
|
@@ -396,6 +407,7 @@ export function createTelegramPromptTurnRuntimeBuilder<
|
|
|
396
407
|
files,
|
|
397
408
|
promptFiles: processed.promptFiles,
|
|
398
409
|
handlerOutputs: processed.handlerOutputs,
|
|
410
|
+
timeLine,
|
|
399
411
|
inferImageMimeType: guessMediaType,
|
|
400
412
|
voiceReplyMode,
|
|
401
413
|
voiceReplyModeConfigured: deps.isVoiceReplyModeConfigured?.(),
|
|
@@ -440,6 +452,7 @@ export async function buildTelegramPromptTurn(
|
|
|
440
452
|
promptFiles: options.promptFiles,
|
|
441
453
|
handlerOutputs: options.handlerOutputs,
|
|
442
454
|
historyTurns: options.historyTurns,
|
|
455
|
+
timeLine: options.timeLine,
|
|
443
456
|
voiceContext: showVoiceContext
|
|
444
457
|
? getTelegramVoicePromptContext(voiceReplyMode, hasVoiceFile)
|
|
445
458
|
: undefined,
|