@llblab/pi-telegram 0.27.12 → 0.28.0

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.
@@ -294,6 +294,32 @@ export interface TelegramActivityVerbosityRuntime {
294
294
  waitForIdle: () => Promise<void>;
295
295
  }
296
296
 
297
+ export interface TelegramActivityVerbosityBinding
298
+ extends TelegramActivityVerbosityRuntime {
299
+ bind: (runtime: TelegramActivityVerbosityRuntime) => void;
300
+ }
301
+
302
+ export function createTelegramActivityVerbosityBinding(): TelegramActivityVerbosityBinding {
303
+ let runtime: TelegramActivityVerbosityRuntime | undefined;
304
+ return {
305
+ bind(next) {
306
+ runtime = next;
307
+ },
308
+ accept(event) {
309
+ runtime?.accept(event);
310
+ },
311
+ reset() {
312
+ runtime?.reset();
313
+ },
314
+ stop() {
315
+ runtime?.stop();
316
+ },
317
+ waitForIdle() {
318
+ return runtime?.waitForIdle() ?? Promise.resolve();
319
+ },
320
+ };
321
+ }
322
+
297
323
  export function createTelegramActivityVerbosityRuntime<TAuthority>(deps: {
298
324
  getActivityMode: () => "quiet" | "thinking" | "tools" | "verbose";
299
325
  refreshActivityMode?: () => Promise<void>;
package/lib/bindings.ts CHANGED
@@ -5,7 +5,7 @@
5
5
  */
6
6
 
7
7
  import * as Activity from "./activity.ts";
8
- import type { TelegramActivityVerbosityRuntime } from "./activity-verbosity.ts";
8
+ import * as ActivityVerbosity from "./activity-verbosity.ts";
9
9
  import * as CommandTemplates from "./command-templates.ts";
10
10
  import * as Commands from "./commands.ts";
11
11
  import * as Config from "./config.ts";
@@ -25,6 +25,7 @@ import * as Runtime from "./runtime.ts";
25
25
  import * as Setup from "./setup.ts";
26
26
  import * as Status from "./status.ts";
27
27
  import * as TelegramApi from "./telegram-api.ts";
28
+ import type { TelegramTarget } from "./target.ts";
28
29
 
29
30
  type ActivePiModel = NonNullable<Pi.ExtensionContext["model"]>;
30
31
 
@@ -37,6 +38,167 @@ type TelegramRuntimeEventRecorder = (
37
38
  type TelegramBridgeStatusUpdater =
38
39
  Status.TelegramStatusRuntime<Pi.ExtensionContext>["updateStatus"];
39
40
 
41
+ type TelegramAgentTargetResolver = NonNullable<
42
+ OutboundAttachments.TelegramOutboundMessageToolRegistrationDeps["resolveAgentTarget"]
43
+ >;
44
+ type TelegramAgentMessageRouter = NonNullable<
45
+ OutboundAttachments.TelegramOutboundMessageToolRegistrationDeps["routeAgentMessage"]
46
+ >;
47
+
48
+ export interface TelegramQueueBindingRuntime<TContext> {
49
+ mutation: Queue.TelegramQueueMutationController<TContext>;
50
+ dispatchNext: (ctx: TContext) => void;
51
+ watchdog: Queue.TelegramQueueDispatchWatchdogRuntime<TContext>;
52
+ }
53
+
54
+ export function createTelegramQueueBindingRuntime<TContext>(deps: {
55
+ store: Queue.TelegramQueueStateStore<TContext>;
56
+ queue: Pick<
57
+ Runtime.TelegramBridgeRuntime["queue"],
58
+ "getNextPriorityReactionOrder" | "incrementNextPriorityReactionOrder"
59
+ >;
60
+ lifecycle: Pick<
61
+ Runtime.TelegramBridgeRuntime["lifecycle"],
62
+ "isCompactionInProgress" | "hasDispatchPending"
63
+ >;
64
+ activeTurn: Pick<Queue.TelegramActiveTurnStore, "has">;
65
+ admission: {
66
+ getSettlement: () =>
67
+ | {
68
+ onItemsDiscarded: (
69
+ items: readonly Queue.TelegramQueueItem<TContext>[],
70
+ ctx: TContext,
71
+ ) => void;
72
+ isItemReady: (item: Queue.TelegramQueueItem<TContext>) => boolean;
73
+ onControlSettled: (
74
+ item: Queue.PendingTelegramControlItem<TContext>,
75
+ ctx: TContext,
76
+ ) => void;
77
+ }
78
+ | undefined;
79
+ hasPendingQueueMutationForItem: (
80
+ item: Queue.TelegramQueueItem<TContext>,
81
+ ) => boolean;
82
+ };
83
+ transportStamp: Pick<Queue.TelegramTransportStampRuntime, "isActive">;
84
+ deferredDispatch: Pick<
85
+ Queue.TelegramDeferredQueueDispatchRuntime<TContext>,
86
+ "isBound" | "getGeneration" | "isGenerationActive"
87
+ >;
88
+ promptDispatch: Runtime.TelegramPromptDispatchRuntime<TContext>;
89
+ isIdle: (ctx: TContext) => boolean;
90
+ hasPendingMessages: (ctx: TContext) => boolean;
91
+ updateStatus: (ctx: TContext, error?: string) => void;
92
+ sendTextReply: Queue.TelegramQueueDispatchRuntimeDeps<TContext>["sendTextReply"];
93
+ sendUserMessage: Queue.TelegramQueueDispatchRuntimeDeps<TContext>["sendUserMessage"];
94
+ recordRuntimeEvent?: TelegramRuntimeEventRecorder;
95
+ }): TelegramQueueBindingRuntime<TContext> {
96
+ const mutation = Queue.createTelegramQueueMutationController({
97
+ ...deps.store,
98
+ getNextPriorityReactionOrder: deps.queue.getNextPriorityReactionOrder,
99
+ incrementNextPriorityReactionOrder:
100
+ deps.queue.incrementNextPriorityReactionOrder,
101
+ onItemsDiscarded(items, ctx) {
102
+ deps.admission.getSettlement()?.onItemsDiscarded(items, ctx);
103
+ },
104
+ updateStatus: deps.updateStatus,
105
+ recordRuntimeEvent: deps.recordRuntimeEvent,
106
+ });
107
+ const dispatchNext = Queue.createTelegramQueueDispatchRuntime({
108
+ ...deps.store,
109
+ isCompactionInProgress: deps.lifecycle.isCompactionInProgress,
110
+ hasActiveTurn: deps.activeTurn.has,
111
+ hasDispatchPending: deps.lifecycle.hasDispatchPending,
112
+ isIdle: deps.isIdle,
113
+ hasPendingMessages: deps.hasPendingMessages,
114
+ hasDispatchContext: deps.deferredDispatch.isBound,
115
+ getDispatchGeneration: deps.deferredDispatch.getGeneration,
116
+ isDispatchGenerationActive: deps.deferredDispatch.isGenerationActive,
117
+ isQueueItemTransportActive(item) {
118
+ return deps.transportStamp.isActive(item.transportStamp);
119
+ },
120
+ hasPendingInboundQueueMutationForItem(item) {
121
+ return deps.admission.hasPendingQueueMutationForItem(item);
122
+ },
123
+ isQueueItemAdmissionReady(item) {
124
+ return (
125
+ deps.admission.getSettlement()?.isItemReady(item) ??
126
+ (item.admissionReceipts?.length ?? 0) === 0
127
+ );
128
+ },
129
+ onControlSettled(item, ctx) {
130
+ deps.admission.getSettlement()?.onControlSettled(item, ctx);
131
+ },
132
+ updateStatus: deps.updateStatus,
133
+ sendTextReply: deps.sendTextReply,
134
+ recordRuntimeEvent: deps.recordRuntimeEvent,
135
+ ...deps.promptDispatch,
136
+ sendUserMessage: deps.sendUserMessage,
137
+ }).dispatchNext;
138
+ return {
139
+ mutation,
140
+ dispatchNext,
141
+ watchdog: Queue.createTelegramQueueDispatchWatchdogRuntime({
142
+ hasQueuedItems: deps.store.hasQueuedItems,
143
+ dispatchNextQueuedTelegramTurn: dispatchNext,
144
+ recordRuntimeEvent: deps.recordRuntimeEvent,
145
+ }),
146
+ };
147
+ }
148
+
149
+ export interface TelegramAgentMessageToolRoutingRuntime {
150
+ resolveAgentTarget: TelegramAgentTargetResolver;
151
+ routeAgentMessage: TelegramAgentMessageRouter;
152
+ canSendDirect: () => boolean;
153
+ }
154
+
155
+ export function createTelegramAgentMessageToolRoutingRuntime(deps: {
156
+ ownsLeader: () => boolean;
157
+ ownsDirectDelivery: () => boolean;
158
+ isFollowerRegistered: () => boolean;
159
+ getSourceTarget: () => TelegramTarget | undefined;
160
+ getSourceThreadName: () => string | undefined;
161
+ local: {
162
+ resolveTarget: (
163
+ selector: Parameters<TelegramAgentTargetResolver>[0],
164
+ sourceTarget?: TelegramTarget,
165
+ ) => Awaited<ReturnType<TelegramAgentTargetResolver>> | undefined;
166
+ route: (input: {
167
+ sourceTarget?: TelegramTarget;
168
+ sourceThreadName?: string;
169
+ message: Parameters<TelegramAgentMessageRouter>[0];
170
+ }) => Promise<void>;
171
+ };
172
+ follower: {
173
+ resolveTarget: TelegramAgentTargetResolver;
174
+ routeMessage: TelegramAgentMessageRouter;
175
+ };
176
+ }): TelegramAgentMessageToolRoutingRuntime {
177
+ return {
178
+ async resolveAgentTarget(selector) {
179
+ if (!deps.ownsLeader()) return deps.follower.resolveTarget(selector);
180
+ const target = deps.local.resolveTarget(selector, deps.getSourceTarget());
181
+ if (!target) {
182
+ throw new Error(
183
+ "Telegram agent target is unavailable, ambiguous, or not live.",
184
+ );
185
+ }
186
+ return target;
187
+ },
188
+ async routeAgentMessage(message) {
189
+ if (!deps.ownsLeader()) return deps.follower.routeMessage(message);
190
+ await deps.local.route({
191
+ sourceTarget: deps.getSourceTarget(),
192
+ sourceThreadName: deps.getSourceThreadName(),
193
+ message,
194
+ });
195
+ },
196
+ canSendDirect() {
197
+ return deps.ownsDirectDelivery() || deps.isFollowerRegistered();
198
+ },
199
+ };
200
+ }
201
+
40
202
  export interface TelegramAssistantOutputBindingRuntime<TTransportStamp> {
41
203
  runtime: Activity.TelegramAssistantOutputRuntime;
42
204
  observeEvent: (event: Activity.TelegramActivityEvent) => void;
@@ -97,6 +259,76 @@ export function createTelegramAssistantOutputBindingRuntime<
97
259
  };
98
260
  }
99
261
 
262
+ type TelegramAssistantOutputAuthority<TTransportStamp> = ReturnType<
263
+ Routing.TelegramAssistantOutputAuthorityRuntime<TTransportStamp>["captureAuthority"]
264
+ >;
265
+
266
+ export interface TelegramActivityBindingRuntime {
267
+ activityRuntime: Activity.TelegramActivityRuntime;
268
+ activityVerbosityRuntime: ActivityVerbosity.TelegramActivityVerbosityRuntime;
269
+ assistantOutputRuntime: Activity.TelegramAssistantOutputRuntime;
270
+ }
271
+
272
+ /** Compose public activity fanout, verbosity, and assistant output ordering. */
273
+ export function createTelegramActivityBindingRuntime<TTransportStamp>(deps: {
274
+ generation: string;
275
+ assistantOutput: Omit<
276
+ Parameters<
277
+ typeof createTelegramAssistantOutputBindingRuntime<TTransportStamp>
278
+ >[0],
279
+ "waitForActivityIdle"
280
+ >;
281
+ activityVerbosity: Omit<
282
+ Parameters<
283
+ typeof ActivityVerbosity.createTelegramActivityVerbosityRuntime<
284
+ TelegramAssistantOutputAuthority<TTransportStamp>
285
+ >
286
+ >[0],
287
+ "captureAuthority" | "isAuthorityActive" | "recordFailure"
288
+ >;
289
+ }): TelegramActivityBindingRuntime {
290
+ const activityVerbosityBinding =
291
+ ActivityVerbosity.createTelegramActivityVerbosityBinding();
292
+ const assistantOutputBinding =
293
+ createTelegramAssistantOutputBindingRuntime({
294
+ ...deps.assistantOutput,
295
+ waitForActivityIdle: activityVerbosityBinding.waitForIdle,
296
+ });
297
+ const activityVerbosityRuntime =
298
+ ActivityVerbosity.createTelegramActivityVerbosityRuntime({
299
+ ...deps.activityVerbosity,
300
+ captureAuthority: assistantOutputBinding.authority.captureAuthority,
301
+ isAuthorityActive: assistantOutputBinding.authority.isAuthorityActive,
302
+ recordFailure(operation, event, error) {
303
+ deps.assistantOutput.recordRuntimeEvent("activity", error, {
304
+ operation,
305
+ eventType: event.type,
306
+ activityId: event.activityId,
307
+ });
308
+ },
309
+ });
310
+ activityVerbosityBinding.bind(activityVerbosityRuntime);
311
+ const activityRuntime = Activity.createTelegramActivityBridgeRuntime({
312
+ generation: deps.generation,
313
+ observeEvent(event) {
314
+ assistantOutputBinding.observeEvent(event);
315
+ activityVerbosityRuntime.accept(event);
316
+ },
317
+ recordFailure(handlerId, event, error) {
318
+ deps.assistantOutput.recordRuntimeEvent("activity", error, {
319
+ handlerId,
320
+ eventType: event.type,
321
+ activityId: event.activityId,
322
+ });
323
+ },
324
+ });
325
+ return {
326
+ activityRuntime,
327
+ activityVerbosityRuntime,
328
+ assistantOutputRuntime: assistantOutputBinding.runtime,
329
+ };
330
+ }
331
+
100
332
  interface TelegramCommandsAndToolsBindingDeps {
101
333
  pi: Pi.ExtensionAPI;
102
334
  configStore: Config.TelegramConfigStore;
@@ -291,7 +523,7 @@ export function registerTelegramCommandsAndTools({
291
523
  interface TelegramLifecycleBindingDeps {
292
524
  pi: Pi.ExtensionAPI;
293
525
  activityRuntime: Activity.TelegramActivityRuntime;
294
- activityVerbosityRuntime?: TelegramActivityVerbosityRuntime;
526
+ activityVerbosityRuntime?: ActivityVerbosity.TelegramActivityVerbosityRuntime;
295
527
  assistantOutputRuntime: Pick<
296
528
  Activity.TelegramAssistantOutputRuntime,
297
529
  "start" | "waitForIdle" | "stop"
@@ -344,6 +576,10 @@ interface TelegramLifecycleBindingDeps {
344
576
  >["sendTextReply"] &
345
577
  NonNullable<OutboundHandlers.TelegramVoiceReplySenderDeps["sendTextReply"]>;
346
578
  dispatchNextQueuedTelegramTurn: (ctx: Pi.ExtensionContext) => void;
579
+ onPromptHandedOff?: (
580
+ turn: Queue.PendingTelegramTurn,
581
+ ctx: Pi.ExtensionContext,
582
+ ) => void;
347
583
  answerGuestQuery: TelegramApi.TelegramBridgeApiRuntime["answerGuestQuery"];
348
584
  deleteMessage: TelegramApi.TelegramBridgeApiRuntime["deleteMessage"];
349
585
  sendGuestReply: NonNullable<
@@ -400,6 +636,7 @@ export function registerTelegramLifecycleRuntimeHooks({
400
636
  sendMarkdownReply,
401
637
  sendTextReply,
402
638
  dispatchNextQueuedTelegramTurn,
639
+ onPromptHandedOff,
403
640
  answerGuestQuery,
404
641
  deleteMessage,
405
642
  sendGuestReply,
@@ -554,6 +791,7 @@ export function registerTelegramLifecycleRuntimeHooks({
554
791
  clearDispatchPending: lifecycle.clearDispatchPending,
555
792
  setFoldQueuedPromptsIntoHistory: lifecycle.setFoldQueuedPromptsIntoHistory,
556
793
  setActiveTurn: activeTurnRuntime.set,
794
+ onPromptHandedOff,
557
795
  createPreviewState: previewRuntime.resetState,
558
796
  startTypingLoop: (ctx) => {
559
797
  const turn = activeTurnRuntime.get();