@llblab/pi-telegram 0.16.2 → 0.16.3
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/CHANGELOG.md +6 -2
- package/README.md +2 -2
- package/docs/architecture.md +14 -2
- package/docs/locks.md +1 -1
- package/index.ts +0 -1
- package/lib/bindings.ts +2 -6
- package/lib/media.ts +1 -0
- package/lib/polling.ts +30 -9
- package/lib/preview.ts +1 -0
- package/lib/queue.ts +24 -13
- package/lib/runtime.ts +3 -1
- package/lib/status.ts +10 -1
- package/lib/text-groups.ts +1 -0
- package/package.json +1 -1
- package/screenshot.png +0 -0
package/CHANGELOG.md
CHANGED
|
@@ -1,8 +1,12 @@
|
|
|
1
1
|
# Changelog
|
|
2
2
|
|
|
3
|
-
##
|
|
3
|
+
## 0.16.3: Ownership And Shutdown Hotfix
|
|
4
4
|
|
|
5
|
-
|
|
5
|
+
- `[Ownership]` Lock-gated proactive local/headless final-result push so only the current `/telegram-connect` owner can send non-Telegram agent-end replies to the paired chat, while accepted Telegram turns and queued work still finalize session-locally after polling ownership moves away. Impact: child/headless/non-owner instances no longer leak unrelated local results into Telegram.
|
|
6
|
+
- `[Shutdown]` Made polling retry sleep abort-aware, `unref()`ed non-critical Telegram housekeeping timers, and routed registered session shutdown through the composed lifecycle runtime so session context cleanup runs with queue and polling cleanup. Impact: shutdown and print/headless runs are less likely to stay alive on retry/debounce/typing/preview timers, and direct-delivery ownership context is cleared at the session boundary.
|
|
7
|
+
- `[Tests]` Added regressions for inherited child lock refusal, child-process no-poll ownership, child-process direct-tool non-owner refusal, proactive owner/non-owner/stale/off states, queued dispatch after lock movement, abort-during-retry polling sleep, process-level session shutdown, and real `pi -p` exit/no-leak paths with a local custom provider. Impact: the cross-instance ownership, queue, and shutdown contracts are pinned from unit coverage through CLI smoke coverage.
|
|
8
|
+
- `[Status]` Changed `/telegram-status` bot identity fallback from `not configured` to `unknown` when a bot token exists but the bot username is absent. Impact: live sessions that are paired and polling no longer look unconfigured only because identity metadata is missing.
|
|
9
|
+
- `[Docs]` Documented ownership, proactive-push, timer/shutdown, and multi-extension prompt-boundary contracts in README and `/docs`. Impact: companion extensions get clearer boundaries without adding a core question/prompt-mirroring API.
|
|
6
10
|
|
|
7
11
|
## 0.16.2: Screenshot Refresh Hotfix
|
|
8
12
|
|
package/README.md
CHANGED
|
@@ -47,7 +47,7 @@ Paste your bot token when prompted. If a bot token is already saved in `~/.pi/ag
|
|
|
47
47
|
/telegram-connect
|
|
48
48
|
```
|
|
49
49
|
|
|
50
|
-
The adapter is session-local: only one π instance polls Telegram at a time. `/telegram-connect` records only external control/polling ownership in `~/.pi/agent/locks.json`; live ownership moves require confirmation,
|
|
50
|
+
The adapter is session-local: only one π instance polls Telegram at a time. `/telegram-connect` records only external control/polling ownership in `~/.pi/agent/locks.json`; live ownership moves require confirmation, inherited child sessions do not start polling unless they take ownership, and `/new` plus same-`cwd` restarts resume automatically. Local queue and reply state stay per Pi instance, so an instance that loses Telegram control still finishes work it already accepted.
|
|
51
51
|
|
|
52
52
|
### 4. Pair your Telegram account
|
|
53
53
|
|
|
@@ -215,7 +215,7 @@ Import `registerTelegramSection()` from `@llblab/pi-telegram/sections` and retur
|
|
|
215
215
|
|
|
216
216
|
### Proactive push
|
|
217
217
|
|
|
218
|
-
`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.
|
|
218
|
+
`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 and this π instance currently owns `/telegram-connect`. Non-owners skip proactive delivery instead of pushing unrelated local/headless results through a bot they no longer control. 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.
|
|
219
219
|
|
|
220
220
|
### Time context
|
|
221
221
|
|
package/docs/architecture.md
CHANGED
|
@@ -88,10 +88,12 @@ Telegram configuration lives in `~/.pi/agent/telegram.json`. Polling ownership l
|
|
|
88
88
|
- `/telegram-connect` acquires or moves singleton polling ownership before polling starts.
|
|
89
89
|
- `/telegram-disconnect` stops polling and releases ownership.
|
|
90
90
|
- Session start resumes polling only when the existing lock already points at the current `pid`/`cwd`, or when a stale same-`cwd` lock can be safely replaced after process restart.
|
|
91
|
+
- Inherited child sessions that see the same `telegram.json` but do not own the `pid`/`cwd` lock must not auto-start polling or call `getUpdates` unless the operator force-takes ownership.
|
|
91
92
|
- Session replacement suspends polling/watchers without releasing ownership so the next session-start hook in the same process can resume.
|
|
92
93
|
- Live polling owners require explicit takeover confirmation.
|
|
93
94
|
- Long-lived polling timers use snapshotted ownership context and stop local polling when the lock no longer points at their own process.
|
|
94
95
|
- `locks.json` owns only external Telegram control/polling. Local extension and queue state are per Pi instance: losing the lock stops live Telegram control here, but does not drain or silence this instance's accepted queue, previews, final delivery, or dispatch.
|
|
96
|
+
- Proactive local/headless final-result push is not accepted-turn delivery. It is allowed only when proactive push is enabled and this instance currently owns the Telegram lock.
|
|
95
97
|
|
|
96
98
|
Deleting `locks.json` resets runtime ownership without deleting Telegram configuration.
|
|
97
99
|
|
|
@@ -159,7 +161,7 @@ Queued controls:
|
|
|
159
161
|
- Prompt-template commands expand Telegram-safe π template aliases before entering the prompt queue.
|
|
160
162
|
- Model-switch continuation uses the control lane when an in-flight Telegram-owned run must be stopped and resumed.
|
|
161
163
|
|
|
162
|
-
UI label, navigation, tab, toggle, card, and dialog rules are defined in [UI Style](./ui-style.md). Callback prefix ownership is defined in [Callback Namespaces](./callback-namespaces.md).
|
|
164
|
+
Queue and menu mutations are reachable through Telegram updates handled by the current polling owner. After ownership moves, the old instance keeps processing its accepted local queue, but it no longer receives new menu callbacks or control updates for remote mutation. UI label, navigation, tab, toggle, card, and dialog rules are defined in [UI Style](./ui-style.md). Callback prefix ownership is defined in [Callback Namespaces](./callback-namespaces.md).
|
|
163
165
|
|
|
164
166
|
### Compaction And Typing Status
|
|
165
167
|
|
|
@@ -215,6 +217,8 @@ Unknown callback data outside owned prefixes is forwarded as `[callback] <data>`
|
|
|
215
217
|
|
|
216
218
|
Extension callbacks must avoid `pi-telegram` owned prefixes such as `compact:`, `tgbtn:`, `menu:`, `model:`, `thinking:`, `status:`, `queue:`, `settings:`, and `section:`. Workflow-specific Telegram slash commands should use the public command registry instead of becoming new core built-ins unless they are bridge lifecycle, transport ownership, queue safety, or essential operator controls.
|
|
217
219
|
|
|
220
|
+
The bridge does not mirror arbitrary `ctx.ui.confirm/input/select/custom` prompts from other extensions into Telegram. Companion extensions that need Telegram operation should expose a Telegram-native command, section, settings row, callback, status line, inbound/update handler, or assistant action-markup path instead of relying on hidden TUI-only prompts.
|
|
221
|
+
|
|
218
222
|
## Diagnostics And Operational Behavior
|
|
219
223
|
|
|
220
224
|
Status rendering distinguishes connected, active, dispatching, queued, tool-running, model-switching, and compacting states. If a queue mutation removes the last waiting item while Telegram-owned work still has running tools, status remains active instead of degrading to connected.
|
|
@@ -223,7 +227,7 @@ Queue reactions are shortcut controls for waiting turns. Promotion reactions (`
|
|
|
223
227
|
|
|
224
228
|
`/telegram-status` records grouped diagnostics for transport/API, polling/update, prompt dispatch, controls, typing, compaction, setup, session lifecycle, attachment queue/delivery, and recent redacted runtime events. Expected preview noise such as unchanged edit responses is filtered out.
|
|
225
229
|
|
|
226
|
-
When proactive push is enabled, successful local non-Telegram final replies are sent to the paired chat. Local prompt text is not mirrored because the bot does not own terminal user messages.
|
|
230
|
+
When proactive push is enabled and this instance owns the Telegram lock, successful local non-Telegram final replies are sent to the paired chat. Non-owners skip proactive delivery and record a runtime diagnostic. Local prompt text is not mirrored because the bot does not own terminal user messages.
|
|
227
231
|
|
|
228
232
|
Telegram prompt guidance asks assistants to keep dense mobile-visible text around 37 display cells where possible, because emoji and wide Unicode make raw character counts misleading.
|
|
229
233
|
|
|
@@ -238,6 +242,14 @@ When `/model` is used during an active Telegram-owned run, the bridge can emulat
|
|
|
238
242
|
|
|
239
243
|
This is limited to Telegram-owned runs. If π is busy with non-Telegram work, the bridge refuses the switch instead of hijacking unrelated activity.
|
|
240
244
|
|
|
245
|
+
## Shutdown And Timer Lifecycle
|
|
246
|
+
|
|
247
|
+
`session_shutdown` is the hard boundary for session-bound runtime work. It suspends Telegram polling through the locked polling runtime, aborts the poll controller, stops native typing, unbinds deferred queue dispatch, clears pending media/text-group input, clears preview state, clears active turns, and drops the active abort handler.
|
|
248
|
+
|
|
249
|
+
Non-critical timers are `unref()`ed so print/headless processes are not kept alive only by Telegram housekeeping. This includes typing keepalive intervals, bounded typing-idle waits, deferred queue dispatch, media/text-group debounce windows, preview flush timers, and polling retry sleeps. Polling retry sleep is abort-aware, so shutdown does not wait for the normal retry delay after a polling error.
|
|
250
|
+
|
|
251
|
+
Non-interactive `pi -p` runs must remain passive unless π provides a live Telegram session lifecycle. Loading the extension with `telegram.json`, proactive push settings, or existing lock state must not by itself keep the print-mode process alive or let a non-owner send proactive Telegram output.
|
|
252
|
+
|
|
241
253
|
## Related
|
|
242
254
|
|
|
243
255
|
- [README.md](../README.md)
|
package/docs/locks.md
CHANGED
|
@@ -123,7 +123,7 @@ Avoid repeating the extension name in the body. Color is encouraged: extension t
|
|
|
123
123
|
|
|
124
124
|
The previous owner may use `fs.watch`, mtime polling, or an existing status/timer tick. Long-lived watchers should compare against a snapshotted `pid`/`cwd` identity rather than a live pi context object, because session replacement such as `/new` makes captured contexts stale. The important contract is graceful singleton-runtime shutdown after ownership mismatch while session-local state that does not require polling remains owned by its original instance.
|
|
125
125
|
|
|
126
|
-
For `pi-telegram`, direct local/TUI delivery tools (`telegram_message` and no-active-turn `telegram_attach`) are singleton-controlled and require current `/telegram-connect` ownership. They must fail when the lock is inactive or active elsewhere. Already accepted Telegram-turn reply delivery, previews, queued attachments, and queue finalization remain session-local and may complete after polling ownership moves away.
|
|
126
|
+
For `pi-telegram`, direct local/TUI delivery tools (`telegram_message` and no-active-turn `telegram_attach`) and proactive local/headless final-result push are singleton-controlled and require current `/telegram-connect` ownership. They must fail or skip delivery when the lock is inactive or active elsewhere. Already accepted Telegram-turn reply delivery, previews, queued attachments, and queue finalization remain session-local and may complete after polling ownership moves away.
|
|
127
127
|
|
|
128
128
|
## Reset
|
|
129
129
|
|
package/index.ts
CHANGED
package/lib/bindings.ts
CHANGED
|
@@ -113,10 +113,6 @@ interface TelegramLifecycleBindingDeps {
|
|
|
113
113
|
Lifecycle.TelegramLifecycleRegistrationDeps,
|
|
114
114
|
"onSessionStart" | "onSessionShutdown" | "onModelSelect"
|
|
115
115
|
>;
|
|
116
|
-
queueSessionLifecycle: Pick<
|
|
117
|
-
Lifecycle.TelegramLifecycleRegistrationDeps,
|
|
118
|
-
"onSessionShutdown"
|
|
119
|
-
>;
|
|
120
116
|
configStore: Pick<Config.TelegramConfigStore, "getOutboundHandlers">;
|
|
121
117
|
abort: Runtime.TelegramRuntimeAbortPort;
|
|
122
118
|
typing: Runtime.TelegramRuntimeTypingPort;
|
|
@@ -190,7 +186,6 @@ interface TelegramLifecycleBindingDeps {
|
|
|
190
186
|
export function registerTelegramLifecycleRuntimeHooks({
|
|
191
187
|
pi,
|
|
192
188
|
sessionLifecycleRuntime,
|
|
193
|
-
queueSessionLifecycle,
|
|
194
189
|
configStore,
|
|
195
190
|
abort,
|
|
196
191
|
typing,
|
|
@@ -283,6 +278,7 @@ export function registerTelegramLifecycleRuntimeHooks({
|
|
|
283
278
|
sendOutboundReplyArtifacts: outboundReplyArtifactSender,
|
|
284
279
|
getDefaultChatId: proactivePushChatIdGetter,
|
|
285
280
|
isProactivePushEnabled,
|
|
281
|
+
canSendProactivePush: lockOwnershipGuard.ownsContext,
|
|
286
282
|
recordRuntimeEvent,
|
|
287
283
|
getActiveToolExecutions: lifecycle.getActiveToolExecutions,
|
|
288
284
|
setActiveToolExecutions: lifecycle.setActiveToolExecutions,
|
|
@@ -315,7 +311,7 @@ export function registerTelegramLifecycleRuntimeHooks({
|
|
|
315
311
|
...agentLifecycleHooks,
|
|
316
312
|
async onSessionShutdown(event, ctx) {
|
|
317
313
|
compactionObserver.onSessionShutdown();
|
|
318
|
-
await
|
|
314
|
+
await sessionLifecycleRuntime.onSessionShutdown(event, ctx);
|
|
319
315
|
},
|
|
320
316
|
onSessionBeforeCompact: compactionObserver.onSessionBeforeCompact,
|
|
321
317
|
onSessionCompact: compactionObserver.onSessionCompact,
|
package/lib/media.ts
CHANGED
|
@@ -299,6 +299,7 @@ export function queueTelegramMediaGroupMessage<
|
|
|
299
299
|
if (!state) return;
|
|
300
300
|
options.dispatchMessages(state.messages, state.context);
|
|
301
301
|
}, options.debounceMs);
|
|
302
|
+
existing.flushTimer.unref?.();
|
|
302
303
|
options.groups.set(key, existing);
|
|
303
304
|
return true;
|
|
304
305
|
}
|
package/lib/polling.ts
CHANGED
|
@@ -261,7 +261,7 @@ export interface TelegramPollLoopDeps<
|
|
|
261
261
|
handleUpdate: (update: TUpdate, ctx: TContext) => Promise<void>;
|
|
262
262
|
onErrorStatus: (message: string) => void;
|
|
263
263
|
onStatusReset: () => void;
|
|
264
|
-
sleep: (ms: number) => Promise<void>;
|
|
264
|
+
sleep: (ms: number, signal?: AbortSignal) => Promise<void>;
|
|
265
265
|
maxUpdateFailures?: number;
|
|
266
266
|
}
|
|
267
267
|
|
|
@@ -278,22 +278,42 @@ export interface TelegramPollLoopRunnerDeps<
|
|
|
278
278
|
persistConfig: () => Promise<void>;
|
|
279
279
|
handleUpdate: (update: TUpdate, ctx: TContext) => Promise<void>;
|
|
280
280
|
updateStatus: (ctx: TContext, message?: string) => void;
|
|
281
|
-
sleep?: (ms: number) => Promise<void>;
|
|
281
|
+
sleep?: (ms: number, signal?: AbortSignal) => Promise<void>;
|
|
282
282
|
maxUpdateFailures?: number;
|
|
283
283
|
}
|
|
284
284
|
|
|
285
|
+
export function sleepTelegramPollingRetry(
|
|
286
|
+
ms: number,
|
|
287
|
+
signal?: AbortSignal,
|
|
288
|
+
): Promise<void> {
|
|
289
|
+
if (ms <= 0 || signal?.aborted) return Promise.resolve();
|
|
290
|
+
return new Promise<void>((resolve) => {
|
|
291
|
+
let settled = false;
|
|
292
|
+
let timer: ReturnType<typeof setTimeout>;
|
|
293
|
+
const finish = () => {
|
|
294
|
+
if (settled) return;
|
|
295
|
+
settled = true;
|
|
296
|
+
signal?.removeEventListener("abort", onAbort);
|
|
297
|
+
resolve();
|
|
298
|
+
};
|
|
299
|
+
const onAbort = () => {
|
|
300
|
+
clearTimeout(timer);
|
|
301
|
+
finish();
|
|
302
|
+
};
|
|
303
|
+
timer = setTimeout(finish, ms);
|
|
304
|
+
timer.unref?.();
|
|
305
|
+
signal?.addEventListener("abort", onAbort, { once: true });
|
|
306
|
+
if (signal?.aborted) onAbort();
|
|
307
|
+
});
|
|
308
|
+
}
|
|
309
|
+
|
|
285
310
|
export function createTelegramPollLoopRunner<
|
|
286
311
|
TUpdate extends TelegramUpdate,
|
|
287
312
|
TContext = unknown,
|
|
288
313
|
>(
|
|
289
314
|
deps: TelegramPollLoopRunnerDeps<TUpdate, TContext>,
|
|
290
315
|
): (ctx: TContext, signal: AbortSignal) => Promise<void> {
|
|
291
|
-
const sleep =
|
|
292
|
-
deps.sleep ??
|
|
293
|
-
((ms: number) =>
|
|
294
|
-
new Promise<void>((resolve) => {
|
|
295
|
-
setTimeout(resolve, ms);
|
|
296
|
-
}));
|
|
316
|
+
const sleep = deps.sleep ?? sleepTelegramPollingRetry;
|
|
297
317
|
return (ctx, signal) =>
|
|
298
318
|
runTelegramPollLoop({
|
|
299
319
|
ctx,
|
|
@@ -393,7 +413,8 @@ export async function runTelegramPollLoop<
|
|
|
393
413
|
deps.recordRuntimeEvent?.("polling", error, { phase: "loop" });
|
|
394
414
|
}
|
|
395
415
|
deps.onErrorStatus(getTelegramPollingErrorMessage(error));
|
|
396
|
-
await deps.sleep(3000);
|
|
416
|
+
await deps.sleep(3000, deps.signal);
|
|
417
|
+
if (deps.signal.aborted) return;
|
|
397
418
|
deps.onStatusReset();
|
|
398
419
|
}
|
|
399
420
|
}
|
package/lib/preview.ts
CHANGED
|
@@ -467,6 +467,7 @@ export function createTelegramPreviewController<
|
|
|
467
467
|
state.flushTimer = setTimer(() => {
|
|
468
468
|
void flushTelegramPreview(chatId, getRuntimeDeps());
|
|
469
469
|
}, throttleMs);
|
|
470
|
+
state.flushTimer.unref?.();
|
|
470
471
|
},
|
|
471
472
|
finalize: (chatId, replyToMessageId) =>
|
|
472
473
|
finalizeTelegramPreview(chatId, getRuntimeDeps(replyToMessageId)),
|
package/lib/queue.ts
CHANGED
|
@@ -842,6 +842,7 @@ export interface TelegramAgentEndRuntimeDeps<
|
|
|
842
842
|
) => Promise<void>;
|
|
843
843
|
getDefaultChatId?: () => number | undefined;
|
|
844
844
|
isProactivePushEnabled?: () => boolean;
|
|
845
|
+
canSendProactivePush?: () => boolean;
|
|
845
846
|
recordRuntimeEvent?: (
|
|
846
847
|
category: string,
|
|
847
848
|
error: unknown,
|
|
@@ -888,6 +889,7 @@ export interface TelegramAgentEndHookRuntimeDeps<
|
|
|
888
889
|
sendOutboundReplyArtifacts?: TelegramAgentEndRuntimeDeps<TTurn>["sendOutboundReplyArtifacts"];
|
|
889
890
|
getDefaultChatId?: TelegramAgentEndRuntimeDeps<TTurn>["getDefaultChatId"];
|
|
890
891
|
isProactivePushEnabled?: TelegramAgentEndRuntimeDeps<TTurn>["isProactivePushEnabled"];
|
|
892
|
+
canSendProactivePush?: (ctx: TContext) => boolean;
|
|
891
893
|
recordRuntimeEvent?: TelegramAgentEndRuntimeDeps<TTurn>["recordRuntimeEvent"];
|
|
892
894
|
}
|
|
893
895
|
|
|
@@ -980,6 +982,7 @@ export function createTelegramAgentEndHook<
|
|
|
980
982
|
): Promise<void> {
|
|
981
983
|
const turn = deps.getActiveTurn();
|
|
982
984
|
const proactiveEnabled = deps.isProactivePushEnabled?.() ?? false;
|
|
985
|
+
const canProactivePush = deps.canSendProactivePush?.(ctx) ?? false;
|
|
983
986
|
await handleTelegramAgentEndRuntime({
|
|
984
987
|
turn,
|
|
985
988
|
assistant:
|
|
@@ -1005,6 +1008,7 @@ export function createTelegramAgentEndHook<
|
|
|
1005
1008
|
sendOutboundReplyArtifacts: deps.sendOutboundReplyArtifacts,
|
|
1006
1009
|
getDefaultChatId: deps.getDefaultChatId,
|
|
1007
1010
|
isProactivePushEnabled: deps.isProactivePushEnabled,
|
|
1011
|
+
canSendProactivePush: () => canProactivePush,
|
|
1008
1012
|
recordRuntimeEvent: deps.recordRuntimeEvent,
|
|
1009
1013
|
});
|
|
1010
1014
|
};
|
|
@@ -1057,20 +1061,26 @@ export async function handleTelegramAgentEndRuntime<
|
|
|
1057
1061
|
foldQueuedPromptsIntoHistory: deps.foldQueuedPromptsIntoHistory,
|
|
1058
1062
|
});
|
|
1059
1063
|
if (!turn) {
|
|
1060
|
-
|
|
1061
|
-
|
|
1062
|
-
|
|
1063
|
-
|
|
1064
|
-
|
|
1065
|
-
|
|
1066
|
-
|
|
1067
|
-
|
|
1068
|
-
|
|
1069
|
-
|
|
1070
|
-
|
|
1071
|
-
|
|
1072
|
-
}
|
|
1064
|
+
const proactiveEnabled = deps.isProactivePushEnabled?.() ?? false;
|
|
1065
|
+
const canProactivePush = deps.canSendProactivePush?.() ?? false;
|
|
1066
|
+
if (proactiveEnabled && finalText && !assistant.errorMessage) {
|
|
1067
|
+
if (canProactivePush) {
|
|
1068
|
+
const defaultChatId = deps.getDefaultChatId?.();
|
|
1069
|
+
if (defaultChatId !== undefined) {
|
|
1070
|
+
try {
|
|
1071
|
+
await deps.sendMarkdownReply(defaultChatId, undefined, finalText);
|
|
1072
|
+
} catch (error) {
|
|
1073
|
+
deps.recordRuntimeEvent?.("proactive-push", error, {
|
|
1074
|
+
chatId: defaultChatId,
|
|
1075
|
+
});
|
|
1076
|
+
}
|
|
1073
1077
|
}
|
|
1078
|
+
} else {
|
|
1079
|
+
deps.recordRuntimeEvent?.(
|
|
1080
|
+
"proactive-push",
|
|
1081
|
+
new Error("Proactive push skipped because this instance does not own Telegram polling."),
|
|
1082
|
+
{ phase: "ownership" },
|
|
1083
|
+
);
|
|
1074
1084
|
}
|
|
1075
1085
|
}
|
|
1076
1086
|
if (endPlan.shouldDispatchNext) deps.dispatchNextQueuedTelegramTurn();
|
|
@@ -1802,6 +1812,7 @@ export function createTelegramDeferredQueueDispatchRuntime<TContext = unknown>(
|
|
|
1802
1812
|
return;
|
|
1803
1813
|
dispatchNextQueuedTelegramTurn(boundContext);
|
|
1804
1814
|
}, delayMs);
|
|
1815
|
+
timer.unref?.();
|
|
1805
1816
|
timers.add(timer);
|
|
1806
1817
|
},
|
|
1807
1818
|
};
|
package/lib/runtime.ts
CHANGED
|
@@ -406,6 +406,7 @@ export function startTelegramTypingLoop(
|
|
|
406
406
|
};
|
|
407
407
|
sendTyping();
|
|
408
408
|
state.typingInterval = setInterval(sendTyping, deps.intervalMs);
|
|
409
|
+
state.typingInterval.unref?.();
|
|
409
410
|
return true;
|
|
410
411
|
}
|
|
411
412
|
|
|
@@ -431,7 +432,8 @@ export async function waitForTelegramTypingLoopIdle(
|
|
|
431
432
|
await Promise.race([
|
|
432
433
|
inFlight,
|
|
433
434
|
new Promise<void>((resolve) => {
|
|
434
|
-
setTimeout(resolve, timeoutMs);
|
|
435
|
+
const timer = setTimeout(resolve, timeoutMs);
|
|
436
|
+
timer.unref?.();
|
|
435
437
|
}),
|
|
436
438
|
]);
|
|
437
439
|
}
|
package/lib/status.ts
CHANGED
|
@@ -104,6 +104,7 @@ export interface TelegramRuntimeEventRecorderOptions {
|
|
|
104
104
|
}
|
|
105
105
|
|
|
106
106
|
export interface TelegramBridgeStatusLineState {
|
|
107
|
+
hasBotToken?: boolean;
|
|
107
108
|
botUsername?: string;
|
|
108
109
|
allowedUserId?: number;
|
|
109
110
|
lockState?: string;
|
|
@@ -488,6 +489,7 @@ export function createTelegramBridgeStatusRuntime<
|
|
|
488
489
|
getBridgeStatusLineState: () => {
|
|
489
490
|
const config = deps.getConfig();
|
|
490
491
|
return {
|
|
492
|
+
hasBotToken: Boolean(config.botToken),
|
|
491
493
|
botUsername: config.botUsername,
|
|
492
494
|
allowedUserId: config.allowedUserId,
|
|
493
495
|
lockState: deps.getRuntimeLockState?.(),
|
|
@@ -552,6 +554,13 @@ export function buildTelegramStatusBarText(
|
|
|
552
554
|
return `${label} ${theme.fg("success", "connected")}`;
|
|
553
555
|
}
|
|
554
556
|
|
|
557
|
+
function formatTelegramBridgeBotStatus(
|
|
558
|
+
state: Pick<TelegramBridgeStatusLineState, "hasBotToken" | "botUsername">,
|
|
559
|
+
): string {
|
|
560
|
+
if (state.botUsername) return `@${state.botUsername}`;
|
|
561
|
+
return state.hasBotToken ? "unknown" : "not configured";
|
|
562
|
+
}
|
|
563
|
+
|
|
555
564
|
export function buildTelegramBridgeStatusLines(
|
|
556
565
|
state: TelegramBridgeStatusLineState,
|
|
557
566
|
): string[] {
|
|
@@ -566,7 +575,7 @@ export function buildTelegramBridgeStatusLines(
|
|
|
566
575
|
).length;
|
|
567
576
|
return [
|
|
568
577
|
"connection:",
|
|
569
|
-
`- bot: ${state
|
|
578
|
+
`- bot: ${formatTelegramBridgeBotStatus(state)}`,
|
|
570
579
|
`- allowed user: ${state.allowedUserId ?? "not paired"}`,
|
|
571
580
|
...(state.lockState ? [`- owner: ${state.lockState}`] : []),
|
|
572
581
|
"",
|
package/lib/text-groups.ts
CHANGED
|
@@ -132,6 +132,7 @@ export function queueTelegramTextGroupMessage<
|
|
|
132
132
|
if (!queued || queued.context === undefined) return;
|
|
133
133
|
options.dispatchMessages(queued.messages, queued.context);
|
|
134
134
|
}, options.debounceMs);
|
|
135
|
+
state.flushTimer.unref?.();
|
|
135
136
|
options.groups.set(key, state);
|
|
136
137
|
return true;
|
|
137
138
|
}
|
package/package.json
CHANGED
package/screenshot.png
CHANGED
|
Binary file
|