@llblab/pi-kit 0.22.0 → 0.22.2
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 +12 -0
- package/README.md +1 -1
- package/node_modules/@llblab/pi-telegram/AGENTS.md +4 -2
- package/node_modules/@llblab/pi-telegram/BACKLOG.md +3 -8
- package/node_modules/@llblab/pi-telegram/CHANGELOG.md +11 -0
- package/node_modules/@llblab/pi-telegram/README.md +3 -1
- package/node_modules/@llblab/pi-telegram/dist/lib/bindings.js +1 -4
- package/node_modules/@llblab/pi-telegram/dist/lib/bus-api.d.ts +0 -1
- package/node_modules/@llblab/pi-telegram/dist/lib/bus-api.js +0 -3
- package/node_modules/@llblab/pi-telegram/dist/lib/bus-leader.js +22 -3
- package/node_modules/@llblab/pi-telegram/dist/lib/extension.js +1 -2
- package/node_modules/@llblab/pi-telegram/dist/lib/menu-model.d.ts +3 -3
- package/node_modules/@llblab/pi-telegram/dist/lib/menu-model.js +17 -3
- package/node_modules/@llblab/pi-telegram/dist/lib/menu.d.ts +5 -7
- package/node_modules/@llblab/pi-telegram/dist/lib/menu.js +4 -5
- package/node_modules/@llblab/pi-telegram/dist/lib/model.d.ts +12 -12
- package/node_modules/@llblab/pi-telegram/dist/lib/model.js +49 -35
- package/node_modules/@llblab/pi-telegram/dist/lib/queue.d.ts +0 -2
- package/node_modules/@llblab/pi-telegram/dist/lib/queue.js +1 -8
- package/node_modules/@llblab/pi-telegram/dist/lib/routing.js +0 -1
- package/node_modules/@llblab/pi-telegram/dist/lib/runtime.d.ts +3 -6
- package/node_modules/@llblab/pi-telegram/dist/lib/runtime.js +4 -55
- package/node_modules/@llblab/pi-telegram/dist/lib/skills.d.ts +2 -2
- package/node_modules/@llblab/pi-telegram/dist/lib/skills.js +9 -2
- package/node_modules/@llblab/pi-telegram/dist/lib/telegram-api.js +36 -10
- package/node_modules/@llblab/pi-telegram/dist/package.json +1 -1
- package/node_modules/@llblab/pi-telegram/docs/architecture.md +8 -8
- package/node_modules/@llblab/pi-telegram/docs/multi-instance-bus.md +3 -3
- package/node_modules/@llblab/pi-telegram/docs/public-api.md +1 -1
- package/node_modules/@llblab/pi-telegram/lib/bindings.ts +7 -4
- package/node_modules/@llblab/pi-telegram/lib/bus-api.ts +0 -7
- package/node_modules/@llblab/pi-telegram/lib/bus-leader.ts +27 -3
- package/node_modules/@llblab/pi-telegram/lib/extension.ts +1 -3
- package/node_modules/@llblab/pi-telegram/lib/menu-model.ts +25 -4
- package/node_modules/@llblab/pi-telegram/lib/menu.ts +14 -9
- package/node_modules/@llblab/pi-telegram/lib/model.ts +77 -44
- package/node_modules/@llblab/pi-telegram/lib/queue.ts +1 -8
- package/node_modules/@llblab/pi-telegram/lib/routing.ts +0 -1
- package/node_modules/@llblab/pi-telegram/lib/runtime.ts +7 -58
- package/node_modules/@llblab/pi-telegram/lib/skills.ts +10 -2
- package/node_modules/@llblab/pi-telegram/lib/telegram-api.ts +43 -17
- package/node_modules/@llblab/pi-telegram/package.json +1 -1
- package/package.json +2 -2
|
@@ -353,7 +353,6 @@ export interface TelegramAgentStartHookRuntimeDeps<TTurn extends PendingTelegram
|
|
|
353
353
|
}
|
|
354
354
|
export type TelegramAgentStartHookEvent = unknown;
|
|
355
355
|
export interface TelegramToolExecutionRuntimeDeps {
|
|
356
|
-
hasActiveTurn: () => boolean;
|
|
357
356
|
getActiveToolExecutions: () => number;
|
|
358
357
|
setActiveToolExecutions: (count: number) => void;
|
|
359
358
|
}
|
|
@@ -372,7 +371,6 @@ export declare function buildTelegramAgentStartPlan<TContext = unknown>(options:
|
|
|
372
371
|
export declare function handleTelegramAgentStartRuntime<TTurn extends PendingTelegramTurn, TContext = unknown>(deps: TelegramAgentStartRuntimeDeps<TTurn, TContext>): void;
|
|
373
372
|
export declare function createTelegramAgentStartHook<TTurn extends PendingTelegramTurn, TContext = unknown>(deps: TelegramAgentStartHookRuntimeDeps<TTurn, TContext>): (_event: TelegramAgentStartHookEvent, ctx: TContext) => Promise<void>;
|
|
374
373
|
export declare function getNextTelegramToolExecutionCount(options: {
|
|
375
|
-
hasActiveTurn: boolean;
|
|
376
374
|
currentCount: number;
|
|
377
375
|
event: "start" | "end";
|
|
378
376
|
}): number;
|
|
@@ -741,8 +741,6 @@ export function createTelegramAgentStartHook(deps) {
|
|
|
741
741
|
};
|
|
742
742
|
}
|
|
743
743
|
export function getNextTelegramToolExecutionCount(options) {
|
|
744
|
-
if (!options.hasActiveTurn)
|
|
745
|
-
return options.currentCount;
|
|
746
744
|
if (options.event === "start") {
|
|
747
745
|
return options.currentCount + 1;
|
|
748
746
|
}
|
|
@@ -750,20 +748,16 @@ export function getNextTelegramToolExecutionCount(options) {
|
|
|
750
748
|
}
|
|
751
749
|
export function handleTelegramToolExecutionStartRuntime(deps) {
|
|
752
750
|
deps.setActiveToolExecutions(getNextTelegramToolExecutionCount({
|
|
753
|
-
hasActiveTurn: deps.hasActiveTurn(),
|
|
754
751
|
currentCount: deps.getActiveToolExecutions(),
|
|
755
752
|
event: "start",
|
|
756
753
|
}));
|
|
757
754
|
}
|
|
758
755
|
export function handleTelegramToolExecutionEndRuntime(deps) {
|
|
759
|
-
const hasActiveTurn = deps.hasActiveTurn();
|
|
760
756
|
deps.setActiveToolExecutions(getNextTelegramToolExecutionCount({
|
|
761
|
-
hasActiveTurn,
|
|
762
757
|
currentCount: deps.getActiveToolExecutions(),
|
|
763
758
|
event: "end",
|
|
764
759
|
}));
|
|
765
|
-
|
|
766
|
-
deps.triggerPendingModelSwitchAbort();
|
|
760
|
+
deps.triggerPendingModelSwitchAbort();
|
|
767
761
|
}
|
|
768
762
|
export function createTelegramAgentLifecycleHooks(deps) {
|
|
769
763
|
const onAgentStart = createTelegramAgentStartHook(deps);
|
|
@@ -806,7 +800,6 @@ export function createTelegramToolExecutionHooks(deps) {
|
|
|
806
800
|
},
|
|
807
801
|
onToolExecutionEnd: (_event, ctx) => {
|
|
808
802
|
handleTelegramToolExecutionEndRuntime({
|
|
809
|
-
hasActiveTurn: deps.hasActiveTurn,
|
|
810
803
|
getActiveToolExecutions: deps.getActiveToolExecutions,
|
|
811
804
|
setActiveToolExecutions: deps.setActiveToolExecutions,
|
|
812
805
|
triggerPendingModelSwitchAbort: () => {
|
|
@@ -535,7 +535,6 @@ export function createTelegramInboundRouteRuntime(deps) {
|
|
|
535
535
|
updateSettingsMenuMessage: deps.updateSettingsMenuMessage,
|
|
536
536
|
answerCallbackQuery: deps.answerCallbackQuery,
|
|
537
537
|
isIdle: deps.isIdle,
|
|
538
|
-
hasActiveTelegramTurn: deps.activeTurnRuntime.has,
|
|
539
538
|
hasAbortHandler: deps.bridgeRuntime.abort.hasHandler,
|
|
540
539
|
getActiveToolExecutions: deps.bridgeRuntime.lifecycle.getActiveToolExecutions,
|
|
541
540
|
persistScopedModelPatterns: deps.persistScopedModelPatterns,
|
|
@@ -98,7 +98,6 @@ export interface TelegramTypingLoopDeps {
|
|
|
98
98
|
sendTypingAction: (chatId: number, options?: {
|
|
99
99
|
message_thread_id?: number;
|
|
100
100
|
}) => Promise<unknown>;
|
|
101
|
-
sendAggregateTypingAction?: (chatId: number) => Promise<unknown>;
|
|
102
101
|
shouldContinue?: () => boolean;
|
|
103
102
|
onStopped?: () => void;
|
|
104
103
|
}
|
|
@@ -111,7 +110,6 @@ export interface TelegramTypingLoopStarterDeps<TContext> extends TelegramRuntime
|
|
|
111
110
|
sendTypingAction: (chatId: number, options?: {
|
|
112
111
|
message_thread_id?: number;
|
|
113
112
|
}) => Promise<unknown>;
|
|
114
|
-
sendAggregateTypingAction?: (chatId: number) => Promise<unknown>;
|
|
115
113
|
updateStatus: (ctx: TContext, error?: string) => void;
|
|
116
114
|
isContextActive?: (ctx: TContext) => boolean;
|
|
117
115
|
isTransportAvailable?: () => boolean;
|
|
@@ -120,7 +118,7 @@ export interface TelegramTypingLoopStarterDeps<TContext> extends TelegramRuntime
|
|
|
120
118
|
}
|
|
121
119
|
export declare function createTelegramTypingLoopStarter<TContext>(deps: TelegramTypingLoopStarterDeps<TContext>): (ctx: TContext, chatId?: number, options?: {
|
|
122
120
|
target?: TelegramTypingLoopTarget;
|
|
123
|
-
}) =>
|
|
121
|
+
}) => boolean;
|
|
124
122
|
export declare function startTelegramTypingLoop(state: TelegramBridgeRuntimeState, deps: TelegramTypingLoopDeps): boolean;
|
|
125
123
|
export declare function stopTelegramTypingLoop(state: TelegramBridgeRuntimeState): boolean;
|
|
126
124
|
export declare function waitForTelegramTypingLoopIdle(state: TelegramBridgeRuntimeState, timeoutMs?: number): Promise<void>;
|
|
@@ -141,7 +139,7 @@ export interface TelegramPromptDispatchLifecycleDeps<TContext> extends TelegramR
|
|
|
141
139
|
typing: Pick<TelegramRuntimeTypingPort, "stop">;
|
|
142
140
|
startTypingLoop: (ctx: TContext, chatId?: number, options?: {
|
|
143
141
|
target?: TelegramTypingLoopTarget;
|
|
144
|
-
}) => void;
|
|
142
|
+
}) => boolean | void;
|
|
145
143
|
updateStatus: (ctx: TContext, error?: string) => void;
|
|
146
144
|
}
|
|
147
145
|
export interface TelegramPromptDispatchRuntimeDeps<TContext> extends TelegramRuntimeEventRecorderPort {
|
|
@@ -151,7 +149,6 @@ export interface TelegramPromptDispatchRuntimeDeps<TContext> extends TelegramRun
|
|
|
151
149
|
sendTypingAction: (chatId: number, options?: {
|
|
152
150
|
message_thread_id?: number;
|
|
153
151
|
}) => Promise<unknown>;
|
|
154
|
-
sendAggregateTypingAction?: (chatId: number) => Promise<unknown>;
|
|
155
152
|
updateStatus: (ctx: TContext, error?: string) => void;
|
|
156
153
|
isContextActive?: (ctx: TContext) => boolean;
|
|
157
154
|
isTransportAvailable?: () => boolean;
|
|
@@ -161,7 +158,7 @@ export interface TelegramPromptDispatchRuntimeDeps<TContext> extends TelegramRun
|
|
|
161
158
|
export interface TelegramPromptDispatchRuntime<TContext> {
|
|
162
159
|
startTypingLoop: (ctx: TContext, chatId?: number, options?: {
|
|
163
160
|
target?: TelegramTypingLoopTarget;
|
|
164
|
-
}) => void;
|
|
161
|
+
}) => boolean | void;
|
|
165
162
|
onPromptDispatchStart: (ctx: TContext, chatId?: number) => void;
|
|
166
163
|
onPromptDispatchFailure: (ctx: TContext, message: string) => void;
|
|
167
164
|
}
|
|
@@ -3,7 +3,7 @@
|
|
|
3
3
|
* Zones: pi agent runtime state, telegram session, shared coordination
|
|
4
4
|
* Owns small session-local runtime primitives that are shared by orchestration but are not specific to queueing, rendering, polling, or Telegram transport
|
|
5
5
|
*/
|
|
6
|
-
const TELEGRAM_TYPING_ACTION_INTERVAL_MS =
|
|
6
|
+
const TELEGRAM_TYPING_ACTION_INTERVAL_MS = 3_000;
|
|
7
7
|
const TELEGRAM_TYPING_IDLE_DRAIN_MAX_MS = 250;
|
|
8
8
|
export function createTelegramBridgeRuntimeState() {
|
|
9
9
|
return {
|
|
@@ -177,9 +177,9 @@ export function createTelegramTypingLoopStarter(deps) {
|
|
|
177
177
|
Object.is(deps.getTransportAuthority(), transportAuthority)
|
|
178
178
|
: deps.isTransportAvailable?.() !== false;
|
|
179
179
|
if (!hasTransport())
|
|
180
|
-
return;
|
|
180
|
+
return false;
|
|
181
181
|
let active = true;
|
|
182
|
-
deps.typing.start({
|
|
182
|
+
return deps.typing.start({
|
|
183
183
|
chatId: chatId ?? deps.getDefaultChatId(),
|
|
184
184
|
target: options?.target,
|
|
185
185
|
intervalMs: deps.intervalMs ?? TELEGRAM_TYPING_ACTION_INTERVAL_MS,
|
|
@@ -202,13 +202,6 @@ export function createTelegramTypingLoopStarter(deps) {
|
|
|
202
202
|
deps.typing.stop();
|
|
203
203
|
return;
|
|
204
204
|
}
|
|
205
|
-
const message = error instanceof Error ? error.message : String(error);
|
|
206
|
-
updateTelegramRuntimeStatusSafely(deps.updateStatus, ctx, {
|
|
207
|
-
error: message,
|
|
208
|
-
category: "typing",
|
|
209
|
-
phase: "status-update",
|
|
210
|
-
recordRuntimeEvent: deps.recordRuntimeEvent,
|
|
211
|
-
});
|
|
212
205
|
try {
|
|
213
206
|
deps.recordRuntimeEvent?.("typing", error, {
|
|
214
207
|
chatId: targetChatId,
|
|
@@ -219,45 +212,6 @@ export function createTelegramTypingLoopStarter(deps) {
|
|
|
219
212
|
}
|
|
220
213
|
}
|
|
221
214
|
},
|
|
222
|
-
sendAggregateTypingAction: deps.sendAggregateTypingAction
|
|
223
|
-
? async (targetChatId) => {
|
|
224
|
-
if (!active)
|
|
225
|
-
return;
|
|
226
|
-
if (!hasTransport()) {
|
|
227
|
-
deps.typing.stop();
|
|
228
|
-
return;
|
|
229
|
-
}
|
|
230
|
-
try {
|
|
231
|
-
await deps.sendAggregateTypingAction?.(targetChatId);
|
|
232
|
-
}
|
|
233
|
-
catch (error) {
|
|
234
|
-
if (deps.isContextActive?.(ctx) === false)
|
|
235
|
-
return;
|
|
236
|
-
if (!active)
|
|
237
|
-
return;
|
|
238
|
-
if (!hasTransport()) {
|
|
239
|
-
deps.typing.stop();
|
|
240
|
-
return;
|
|
241
|
-
}
|
|
242
|
-
const message = error instanceof Error ? error.message : String(error);
|
|
243
|
-
updateTelegramRuntimeStatusSafely(deps.updateStatus, ctx, {
|
|
244
|
-
error: message,
|
|
245
|
-
category: "typing",
|
|
246
|
-
phase: "status-update",
|
|
247
|
-
recordRuntimeEvent: deps.recordRuntimeEvent,
|
|
248
|
-
});
|
|
249
|
-
try {
|
|
250
|
-
deps.recordRuntimeEvent?.("typing", error, {
|
|
251
|
-
chatId: targetChatId,
|
|
252
|
-
aggregate: true,
|
|
253
|
-
});
|
|
254
|
-
}
|
|
255
|
-
catch {
|
|
256
|
-
// Typing diagnostics cannot escape the in-flight action owner.
|
|
257
|
-
}
|
|
258
|
-
}
|
|
259
|
-
}
|
|
260
|
-
: undefined,
|
|
261
215
|
shouldContinue: hasTransport,
|
|
262
216
|
onStopped: () => {
|
|
263
217
|
active = false;
|
|
@@ -296,12 +250,7 @@ export function startTelegramTypingLoop(state, deps) {
|
|
|
296
250
|
const targetChatId = activeDeps.chatId;
|
|
297
251
|
const threadParams = getTelegramTypingLoopThreadParams(activeDeps.target);
|
|
298
252
|
const typing = Promise.resolve()
|
|
299
|
-
.then(
|
|
300
|
-
await activeDeps.sendTypingAction(targetChatId, threadParams);
|
|
301
|
-
if (threadParams?.message_thread_id !== undefined) {
|
|
302
|
-
await activeDeps.sendAggregateTypingAction?.(targetChatId);
|
|
303
|
-
}
|
|
304
|
-
})
|
|
253
|
+
.then(() => activeDeps.sendTypingAction(targetChatId, threadParams))
|
|
305
254
|
.then(() => undefined)
|
|
306
255
|
.catch(() => undefined);
|
|
307
256
|
state.typingInFlight = typing;
|
|
@@ -1,8 +1,8 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* Bundled Telegram skill discovery
|
|
3
3
|
* Zones: pi agent, telegram guidance
|
|
4
|
-
* Owns source-checkout
|
|
4
|
+
* Owns source-checkout skill contribution; installed packages use their manifest
|
|
5
5
|
*/
|
|
6
6
|
import type { ExtensionAPI } from "./pi.ts";
|
|
7
7
|
export declare const TELEGRAM_SKILLS_PATH: string;
|
|
8
|
-
export declare function registerTelegramSkillDiscovery(pi: Pick<ExtensionAPI, "on"
|
|
8
|
+
export declare function registerTelegramSkillDiscovery(pi: Pick<ExtensionAPI, "on">, modulePath?: string): boolean;
|
|
@@ -1,12 +1,19 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* Bundled Telegram skill discovery
|
|
3
3
|
* Zones: pi agent, telegram guidance
|
|
4
|
-
* Owns source-checkout
|
|
4
|
+
* Owns source-checkout skill contribution; installed packages use their manifest
|
|
5
5
|
*/
|
|
6
|
+
import { extname } from "node:path";
|
|
6
7
|
import { fileURLToPath } from "node:url";
|
|
8
|
+
const TELEGRAM_SKILLS_MODULE_PATH = fileURLToPath(import.meta.url);
|
|
7
9
|
export const TELEGRAM_SKILLS_PATH = fileURLToPath(new URL("../skills", import.meta.url));
|
|
8
|
-
export function registerTelegramSkillDiscovery(pi) {
|
|
10
|
+
export function registerTelegramSkillDiscovery(pi, modulePath = TELEGRAM_SKILLS_MODULE_PATH) {
|
|
11
|
+
// A raw source extension has no package manifest owner. Compiled npm/git
|
|
12
|
+
// packages do, so contributing again would bypass their resource filters.
|
|
13
|
+
if (extname(modulePath) !== ".ts")
|
|
14
|
+
return false;
|
|
9
15
|
pi.on("resources_discover", () => ({
|
|
10
16
|
skillPaths: [TELEGRAM_SKILLS_PATH],
|
|
11
17
|
}));
|
|
18
|
+
return true;
|
|
12
19
|
}
|
|
@@ -900,7 +900,8 @@ export function createTelegramBridgeApiRuntime(deps) {
|
|
|
900
900
|
const chatActionMinIntervalMs = Math.max(0, deps.chatActionMinIntervalMs ?? 2_000);
|
|
901
901
|
const chatActionMaxGates = Math.max(1, deps.chatActionMaxGates ?? 256);
|
|
902
902
|
const chatActionGates = new Map();
|
|
903
|
-
const
|
|
903
|
+
const chatActionChatGates = new Map();
|
|
904
|
+
const getChatActionKeys = (method, body) => {
|
|
904
905
|
if (method !== "sendChatAction")
|
|
905
906
|
return undefined;
|
|
906
907
|
const chatId = body.chat_id;
|
|
@@ -909,32 +910,52 @@ export function createTelegramBridgeApiRuntime(deps) {
|
|
|
909
910
|
typeof action !== "string") {
|
|
910
911
|
return undefined;
|
|
911
912
|
}
|
|
913
|
+
const chat = String(chatId);
|
|
912
914
|
const threadId = body.message_thread_id;
|
|
913
|
-
return
|
|
914
|
-
|
|
915
|
-
|
|
915
|
+
return {
|
|
916
|
+
action: `${chat}:${typeof threadId === "number" || typeof threadId === "string"
|
|
917
|
+
? String(threadId)
|
|
918
|
+
: "all"}:${action}`,
|
|
919
|
+
chat,
|
|
920
|
+
};
|
|
916
921
|
};
|
|
917
922
|
const callRecorded = async (method, body, options) => {
|
|
918
923
|
const recoverError = deps.captureRequestErrorHandler?.(body);
|
|
919
|
-
const
|
|
920
|
-
if (
|
|
924
|
+
const chatActionKeys = getChatActionKeys(method, body);
|
|
925
|
+
if (chatActionKeys) {
|
|
921
926
|
const nowMs = now();
|
|
922
927
|
for (const [key, candidate] of chatActionGates) {
|
|
923
928
|
if (!candidate.inFlight && nowMs >= candidate.notBeforeMs) {
|
|
924
929
|
chatActionGates.delete(key);
|
|
925
930
|
}
|
|
926
931
|
}
|
|
927
|
-
|
|
932
|
+
for (const [key, candidate] of chatActionChatGates) {
|
|
933
|
+
if (!candidate.inFlight && nowMs >= candidate.notBeforeMs) {
|
|
934
|
+
chatActionChatGates.delete(key);
|
|
935
|
+
}
|
|
936
|
+
}
|
|
937
|
+
let gate = chatActionGates.get(chatActionKeys.action);
|
|
928
938
|
if (!gate) {
|
|
929
939
|
if (chatActionGates.size >= chatActionMaxGates)
|
|
930
940
|
return true;
|
|
931
941
|
gate = { notBeforeMs: 0 };
|
|
932
|
-
chatActionGates.set(
|
|
942
|
+
chatActionGates.set(chatActionKeys.action, gate);
|
|
943
|
+
}
|
|
944
|
+
let chatGate = chatActionChatGates.get(chatActionKeys.chat);
|
|
945
|
+
if (!chatGate) {
|
|
946
|
+
if (chatActionChatGates.size >= chatActionMaxGates) {
|
|
947
|
+
return true;
|
|
948
|
+
}
|
|
949
|
+
chatGate = { notBeforeMs: 0 };
|
|
950
|
+
chatActionChatGates.set(chatActionKeys.chat, chatGate);
|
|
933
951
|
}
|
|
934
952
|
if (gate.inFlight)
|
|
935
953
|
return (await gate.inFlight);
|
|
936
|
-
if (
|
|
954
|
+
if (chatGate.inFlight)
|
|
937
955
|
return true;
|
|
956
|
+
if (nowMs < gate.notBeforeMs || nowMs < chatGate.notBeforeMs) {
|
|
957
|
+
return true;
|
|
958
|
+
}
|
|
938
959
|
let request;
|
|
939
960
|
request = Promise.resolve()
|
|
940
961
|
.then(() => deps.client.call(method, body, {
|
|
@@ -949,7 +970,9 @@ export function createTelegramBridgeApiRuntime(deps) {
|
|
|
949
970
|
await recoverRequestError(recoverError, error);
|
|
950
971
|
if (error instanceof TelegramApiHttpError && error.status === 429) {
|
|
951
972
|
const retryAfterMs = Math.max(chatActionMinIntervalMs, (error.retryAfterSeconds ?? 0) * 1_000);
|
|
952
|
-
|
|
973
|
+
const notBeforeMs = now() + retryAfterMs;
|
|
974
|
+
gate.notBeforeMs = notBeforeMs;
|
|
975
|
+
chatGate.notBeforeMs = Math.max(chatGate.notBeforeMs, notBeforeMs);
|
|
953
976
|
deps.recordRuntimeEvent("api", error, withTelegramTransportDiagnostics(error, {
|
|
954
977
|
method,
|
|
955
978
|
rateLimited: true,
|
|
@@ -963,8 +986,11 @@ export function createTelegramBridgeApiRuntime(deps) {
|
|
|
963
986
|
.finally(() => {
|
|
964
987
|
if (gate.inFlight === request)
|
|
965
988
|
gate.inFlight = undefined;
|
|
989
|
+
if (chatGate.inFlight === request)
|
|
990
|
+
chatGate.inFlight = undefined;
|
|
966
991
|
});
|
|
967
992
|
gate.inFlight = request;
|
|
993
|
+
chatGate.inFlight = request;
|
|
968
994
|
return request;
|
|
969
995
|
}
|
|
970
996
|
try {
|
|
@@ -526,7 +526,7 @@ Queued controls:
|
|
|
526
526
|
|
|
527
527
|
- `/continue` creates a priority Telegram-owned `continue` prompt.
|
|
528
528
|
- Prompt-template commands expand Telegram-safe Pi template aliases before entering the prompt queue.
|
|
529
|
-
- Model-switch continuation uses the control lane when
|
|
529
|
+
- Model-switch continuation uses the control lane when any interruptible in-flight agent run in the current Pi session must be stopped and resumed. A Telegram-owned run retains its prompt target; otherwise the exact model-menu chat/Thread/message supplies continuation and reply ownership.
|
|
530
530
|
|
|
531
531
|
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).
|
|
532
532
|
|
|
@@ -539,10 +539,10 @@ Native typing during compaction follows connected-instance activity rather than
|
|
|
539
539
|
- Confirmed manual `/compact` starts a native `typing` keepalive in the command target and stops it on completion/failure.
|
|
540
540
|
- Automatic/session compaction with an active Telegram turn reuses that turn's target.
|
|
541
541
|
- Automatic/session compaction without an active Telegram turn uses the connected instance's assigned target; an unconnected instance sends nothing.
|
|
542
|
-
- Thread-targeted typing is sent to the concrete thread
|
|
542
|
+
- Thread-targeted typing is sent only to the concrete thread. Aggregate `All` mirroring is intentionally omitted because duplicating every keepalive multiplies shared-chat flood pressure. Compaction completion, failure, or timeout stops only a loop actually started by the compaction observer; a pre-existing agent-owned loop remains active. Authority loss and shutdown still stop the keyed loop.
|
|
543
543
|
- Pi `ui_prompt_start` pauses typing while an extension-owned local prompt waits for the operator; `ui_prompt_end` emits the matching Activity boundary and resumes typing whenever agent or compaction work remains unsettled.
|
|
544
544
|
|
|
545
|
-
At every connected instance `agent_start`, the lifecycle binding starts Telegram's native `…typing` indicator in that instance's assigned target, whether the run came from Telegram, the local TUI, or an autonomous continuation such as Grow Loop. Terminal `Active` remains Telegram-turn-specific; the native indicator answers the separate question of whether the instance is doing agent work. Each loop keeps one action in flight
|
|
545
|
+
At every connected instance `agent_start`, the lifecycle binding starts Telegram's native `…typing` indicator in that instance's assigned target, whether the run came from Telegram, the local TUI, or an autonomous continuation such as Grow Loop. Terminal `Active` remains Telegram-turn-specific; the native indicator answers the separate question of whether the instance is doing agent work. Each loop refreshes its exact target every three seconds and keeps one action in flight. The leader API runtime coalesces identical chat/thread/action calls for two seconds, permits at most one concurrent chat action per chat, and shares a Telegram 429 `retry_after` fence across every Thread key in that chat; expired gates prune opportunistically and each gate family retains at most 256 active keys. Suppression never schedules a delayed retry. A failed typing action remains a structured diagnostic but cannot project `error` onto an otherwise healthy connected/leader/follower status. Assistant message start/update hooks still re-arm typing during Telegram-owned turns so transient provider/model errors do not leave a continuing run without activity feedback, and agent/session completion stops it.
|
|
546
546
|
|
|
547
547
|
### Rendering And Delivery
|
|
548
548
|
|
|
@@ -625,14 +625,14 @@ Telegram prompt guidance is context- and authority-aware. The package and source
|
|
|
625
625
|
|
|
626
626
|
## In-Flight Model Switching
|
|
627
627
|
|
|
628
|
-
When `/model` is used during an active
|
|
628
|
+
When `/model` is used during an interruptible active agent run in the current Pi session, the bridge emulates Pi's interactive stop/switch/continue workflow:
|
|
629
629
|
|
|
630
630
|
1. Apply the selected model immediately.
|
|
631
|
-
2. Queue or stage a synthetic Telegram continuation turn.
|
|
632
|
-
3. Abort
|
|
633
|
-
4. Dispatch the continuation
|
|
631
|
+
2. Queue or stage a synthetic Telegram continuation turn before aborting.
|
|
632
|
+
3. Abort immediately, or wait for every active tool execution to finish before aborting.
|
|
633
|
+
4. Dispatch the continuation in the same session context under the selected model.
|
|
634
634
|
|
|
635
|
-
|
|
635
|
+
A Telegram-originated run retains its active prompt target and reply anchor. For local/TUI or autonomous work without an active Telegram turn, the exact authorized model-menu chat, Thread, and message become the continuation target and reply anchor. Merely busy non-agent lifecycle work remains ineligible because no active agent abort handler exists. Pending selection and fallback-target state clear together on cancellation, new agent start, settlement, or session replacement.
|
|
636
636
|
|
|
637
637
|
## Shutdown And Timer Lifecycle
|
|
638
638
|
|
|
@@ -151,7 +151,7 @@ A registered instance exposes:
|
|
|
151
151
|
|
|
152
152
|
## Approved Next Contract: Directory Names And Reclaimable Slots
|
|
153
153
|
|
|
154
|
-
Status: approved design with a locally tested pure selection policy in `lib/workspace-slots.ts` and profile-isolated display preference persistence/default resolution in `lib/config.ts`. Workspace claims now reserve global letters before provisioning and preserve legacy binding keys. An exact claim assigns the first free letter to a missing-slot binding or the selected member of a duplicate-slot set, but persistence waits for successful target recovery; unresolved duplicates block unrelated fresh allocation. Sticky suffix metadata and acknowledged `displayTitle` persist in Workspace bindings. `lib/thread-display.ts` provides the three-mode projection plus serialized title reconciliation wired into leader startup and follower registration. Heartbeat ACKs carry acknowledged display titles to followers and the current-thread/TUI projection uses them without changing restoration identity. Settings now exposes Letters (default), Names, and Directories; follower changes use the capability-gated leader-owned setting path. Live bot chooser/notice labels and cross-instance agent-target resolution use acknowledged titles without granting routing authority. The Thread store now persists the first proven `inactiveSinceMs` transition with exact confirmed target absence or fenced non-destructive detachment of a confirmed-dead follower or quiescent quitting leader whose tab is preserved; successful active provisioning clears it. Pressure selection, intents, mocked execution, and recovery are implemented. A 2/2 same-model independent post-fix quorum cleared the admission-composition blocker at 0.96 confidence per reviewer, and the operator has since authorized demand-driven rotation. Fresh allocation now invokes the existing retirement lifecycle under full capacity; `BACKLOG.md` owns remaining disposable-client acceptance.
|
|
154
|
+
Status: approved design with a locally tested pure selection policy in `lib/workspace-slots.ts` and profile-isolated display preference persistence/default resolution in `lib/config.ts`. Workspace claims now reserve global letters before provisioning and preserve legacy binding keys. An exact claim assigns the first free letter to a missing-slot binding or the selected member of a duplicate-slot set, but persistence waits for successful target recovery; unresolved duplicates block unrelated fresh allocation. If authenticated follower re-registration finds that its retained target record carries another letter, the exact claim is canonical: registration repairs that record before binding commit and leaves the unrelated binding that owns the stale letter intact. Sticky suffix metadata and acknowledged `displayTitle` persist in Workspace bindings. `lib/thread-display.ts` provides the three-mode projection plus serialized title reconciliation wired into leader startup and follower registration. Heartbeat ACKs carry acknowledged display titles to followers and the current-thread/TUI projection uses them without changing restoration identity. Settings now exposes Letters (default), Names, and Directories; follower changes use the capability-gated leader-owned setting path. Live bot chooser/notice labels and cross-instance agent-target resolution use acknowledged titles without granting routing authority. The Thread store now persists the first proven `inactiveSinceMs` transition with exact confirmed target absence or fenced non-destructive detachment of a confirmed-dead follower or quiescent quitting leader whose tab is preserved; successful active provisioning clears it. Pressure selection, intents, mocked execution, and recovery are implemented. A 2/2 same-model independent post-fix quorum cleared the admission-composition blocker at 0.96 confidence per reviewer, and the operator has since authorized demand-driven rotation. Fresh allocation now invokes the existing retirement lifecycle under full capacity; `BACKLOG.md` owns remaining disposable-client acceptance.
|
|
155
155
|
|
|
156
156
|
The pure policy distinguishes a free letter, a proposed pressure-reclamation victim, and protected/invalid capacity. Its caller must supply a validated profile-wide snapshot, reservations, proven inactivity start, and explicit protection classification; duplicate legacy letters block selection. The policy performs no filesystem or Telegram operations and does not establish liveness or deletion authority. It proposes a victim only when every profile-wide letter is occupied or reserved; elapsed time alone never triggers retirement.
|
|
157
157
|
|
|
@@ -336,7 +336,7 @@ In Telegram private-chat Threaded Mode:
|
|
|
336
336
|
- Prompts typed in a thread route to the owning instance.
|
|
337
337
|
- Replies, previews, files, voice, and buttons stay in that thread.
|
|
338
338
|
- Queue controls and reactions affect only that instance target.
|
|
339
|
-
- Telegram's native `…typing` indicator for real agent work is
|
|
339
|
+
- Telegram's native `…typing` indicator for real agent work is refreshed only in that instance's exact Thread. Aggregate `All` mirroring is intentionally omitted because it duplicates every keepalive against the shared chat and amplifies Telegram flood control. Telegram turns, local prompts, and autonomous continuations all use the exact instance target. Terminal `Active` remains Telegram-turn-specific. Startup/connect/reload/recovery must not send activity by themselves.
|
|
340
340
|
- Generic heartbeat pruning remains silent and preserves the thread as a restart hint. With cleanup enabled, a later exact-PID death confirmation may delete it without posting an `Instance offline` notice. With cleanup disabled, a fenced owner-detachment commit records inactivity but retains the Thread and its Workspace slot for restoration or fully guarded capacity rotation. Confirmed deletion removes live routing authority but retains the Workspace binding's friendly name and uppercase ordering slot; a later authenticated reopen probes the old target, replaces it only on exact stale/deleted evidence, and carries that name and slot onto the new Thread.
|
|
341
341
|
- If the same binding identity returns, authenticated registration can reclaim the thread after the required visibility proof.
|
|
342
342
|
|
|
@@ -406,7 +406,7 @@ Threaded Mode should make follower threads behave like normal Telegram instance
|
|
|
406
406
|
| Replies/finals | Final replies land in the same thread | Follower finals go through leader transport into follower thread | Outbound calls carry target and inject `message_thread_id` | Reply delivery and bus API tests |
|
|
407
407
|
| Previews/Rich Drafts | Draft previews use the active thread target | Follower previews use the same native draft lifecycle through the leader | Preview transport preserves target and draft id | Preview thread-target tests |
|
|
408
408
|
| Attachments/voice | Files and voice upload in the instance thread | Follower uploads route through leader multipart transport | Multipart calls are target-scoped and follower-authorized | Bus allowlist and outbound delivery tests |
|
|
409
|
-
| Native activity status | `sendChatAction(typing)`
|
|
409
|
+
| Native activity status | `sendChatAction(typing)` refreshes Telegram's native `…typing` indicator only in the assigned Thread for every agent run, including local and autonomous work; aggregate `All` is not mirrored, terminal identity remains `connected`, and active work stays included in the green Queue count | Followers route the exact Thread action through leader transport while retaining their stable terminal `follower` identity and green activity count; the leader admits one concurrent action per chat and shares Telegram 429 cooldown across that chat's Thread keys | Agent lifecycle targets the active Telegram turn when present and otherwise the instance binding; one keyed loop avoids duplicate aggregate sends/rate-limit pressure | Agent-start binding, typing-loop, target-routing, and terminal-status regressions |
|
|
410
410
|
| Leader election / promotion | Current leader keeps its thread across reload | A promoted follower keeps its existing thread, slot, and name when elected and after later reload | Promotion converts the current follower binding into the leader profile before forced lock acquisition, so leader startup reuses it instead of provisioning a new thread | Follower heartbeat recovery passes binding snapshot into promotion; own-topic provisioner reuses promoted bindings |
|
|
411
411
|
| `/start` command/menu bootstrap | Registers visible bot commands and opens the menu | Follower `/start` can refresh the bot command menu through the leader and open its local menu without warnings | Bot command registration is a validated global Bot API call allowed through trusted follower bus transport | Bus allowlist regression for `setMyCommands` |
|
|
412
412
|
| Follower reconnect | Existing leader binding is reused only when still usable | Same-process `/new` or `/reload` suspends the old follower socket/context and automatically re-registers the new session to the exact prior target; explicit reconnect to a genuinely closed/stale Telegram tab still recreates a visible thread before success | A short-lived handoff carries the assigned target across session replacement, and the leader transfers that binding to the new runtime instance id by stable manual-follower identity; stale Bot API errors remain the proof for fresh provisioning | Session handoff/refresh, leader binding-transfer, persisted leader-reload reuse, and stale-target replacement regressions |
|
|
@@ -71,7 +71,7 @@ Every assistant-authored HTML comment is transport-private on Telegram: previews
|
|
|
71
71
|
- `telegram_channel_post(action, operation_id, markdown?)` edits or deletes one exact `published` record returned by `telegram_channel_posts`. Edit requires Markdown and delete forbids it. A media-post edit replaces the caption through `editMessageCaption`, while a text post uses `editMessageText`; both render Markdown formatting, including spoilers, as Telegram HTML. The direct leader fences the tool call as outcome-unknown before the mutation call, so ambiguous failures are never replayed automatically.
|
|
72
72
|
- `telegram_channel_posts(chat_id?, limit?)` lists newest bounded records from the active profile's agent-owned post journal. It returns publication, edit/delete outcome-unknown, confirmed, and deleted local records only, including retained media kind/file name/size/SHA-256 identity; it never reads or claims completeness for Telegram channel history. This explicit successful listing is the only tool response that exposes retained authored Markdown; channel tool failures use fixed redacted messages, while pre-issuance media/caption validation errors stay actionable.
|
|
73
73
|
- `telegram_message(text, chat_id?, media?, channel?, thread_id?, thread?)` sends a direct Telegram Markdown message when this Pi instance owns `/telegram-connect` or is registered with the multi-instance bus. A public `@username`, or an exact negative numeric channel ID with `channel: true`, is passed as `chat_id` without a local registry; channel delivery requires the direct leader, and Telegram enforces whether the bot has channel posting permission. Channel delivery accepts `media` as one local `.jpg`/`.jpeg`/`.png`/`.webp` photo or `.mp4` video (photo ≤ 10 MiB, video ≤ 50 MiB), uploaded through multipart `sendPhoto`/`sendVideo` with `text` as its HTML caption (≤ 1024 visible characters); unsupported media types and albums are rejected before issuance, and the durable channel-post journal binds media identity and caption so duplicate or lost-acknowledgement retries never re-upload. `thread` accepts a live numeric Thread id or its current acknowledged display title; name matching is case-insensitive and fails closed when absent or ambiguous, while delivery captures the numeric target. During an active Telegram turn, omitted targeting and an explicit target equal to that turn are rejected so the ordinary final-reply path remains the sole current-target response; an explicit different chat/thread target remains allowed. Outside active turns, paired/default local/TUI delivery remains unchanged. Top-level `telegram_button` comments inside `text` are parsed with the same planner used for normal replies and attached to that message; buttons are never standalone Telegram messages.
|
|
74
|
-
- The bundled `telegram-bridge` Skill owns action syntax, target routing, Threaded Mode, formatting, Generative App operation, and profile-specific debugging guidance. The bundled `show-me` Skill owns portable evidence-honest explanations and adapts them to phone-width Markdown or self-contained HTML artifacts when Telegram is the active surface. The regular prompt routes applicable turns to these and the other bundled Skills. `telegram_attach`, `telegram_bind`, and `telegram_message` remain registered but are model-active only while this instance owns direct transport or holds a live follower registration; disconnect/loss suppresses their schemas and prompt metadata, and recovery restores only the operator's previously active pi-telegram subset.
|
|
74
|
+
- The bundled `telegram-bridge` Skill owns action syntax, target routing, Threaded Mode, formatting, Generative App operation, and profile-specific debugging guidance. The bundled `show-me` Skill owns portable evidence-honest explanations and adapts them to phone-width Markdown or self-contained HTML artifacts when Telegram is the active surface. The regular prompt routes applicable turns to these and the other bundled Skills. Compiled npm/git installations expose them through the package `pi.skills` manifest so Pi resource filters remain authoritative; a raw TypeScript checkout under Pi's `extensions` directory contributes its source Skill root at runtime instead. `telegram_attach`, `telegram_bind`, and `telegram_message` remain registered but are model-active only while this instance owns direct transport or holds a live follower registration; disconnect/loss suppresses their schemas and prompt metadata, and recovery restores only the operator's previously active pi-telegram subset.
|
|
75
75
|
- `telegram_voice` hidden comments request Telegram-native voice delivery through `{text}`, `{text|lang}`, `{text|lang|rate}`, or a JSON object. JSON is the fallback for multiline content, named fields, or escaping; equivalent `text` or `value` supplies the spoken payload, with explicit `text` taking precedence.
|
|
76
76
|
- `telegram_button` hidden comments create footer buttons; standalone column-zero triple-backtick `telegram_button` blocks create button rows between paragraphs in Native Rich Markdown. Both accept the same singleton or mixed JSON/CML matrix and share prompt/app routing; fenced blocks also accept adjacent top-level JSON/CML objects without an outer array or commas as vertical singleton rows. Native rows allow at most eight buttons and must fit one Rich Message chunk; invalid or incomplete blocks register nothing. Drafts hide action fences. HTML compatibility projects fenced controls into the footer. In-body clicks acknowledge without recoloring the Rich body; selected-style highlighting remains footer-only. One marker accepts a JSON object, adaptive JSON/CML matrix, or positional [Compact Matrix Literal](./compact-matrix-literal.md). Named JSON objects and positional cells may coexist in one matrix or row; separators are optional and one trailing comma is tolerated at matrix, row, and JSON-object boundaries. Top-level cells become full-width rows, while nested rows group one or more buttons horizontally without an artificial parser-width cap. CML uses `{value}`, `{label|prompt}`, prompt-only `{|prompt}`, or the corresponding three-atom form with `selected_style`; an omitted label uses the existing prompt-as-label fallback, and the optional third atom requires a non-empty prompt and accepts only `primary`, `success`, or `danger`. A fourth atom accepts `1` or `true` (disabled), and `0` or `false` (enabled), with exact lowercase spelling; an omitted fourth position stays enabled, and the third atom may be empty in this form (`{|Next||1}`). JSON uses boolean `disabled`. Disabled cells need no prompt or selected style: `{Next|||1}` is label-only and `{|||1}` is blank (JSON `{"label":"Next","disabled":true}` and `{"disabled":true}`). The Telegram renderer supplies a non-breaking space only when the label is empty. Disabled cells stay visible but carry `disabled: {}` instead of callback data and register no prompt or bound action; invalid disabled values reject the candidate matrix. It trims atom boundaries and supports only the minimal escapes `\|`, `\}`, and `\\`. Prefer one matrix comment for multiple buttons. Use JSON `label` plus `prompt`, or `value` when both strings are identical. Action markers are colon-free; colon-prefixed payloads are rejected. Use top-level column-zero action wrappers, outside quotes, lists, or enclosing code examples. Ordinary code fences and larger outer fences preserve literal examples; bare JSON/CML in prose never activates.
|
|
77
77
|
|
|
@@ -1203,10 +1203,13 @@ export function registerTelegramLifecycleRuntimeHooks({
|
|
|
1203
1203
|
if (uiPromptActive || !canSendAgentActivity(ctx)) return false;
|
|
1204
1204
|
const turn = activeTurnRuntime.get();
|
|
1205
1205
|
const target = turn?.target ?? proactivePushTargetGetter();
|
|
1206
|
-
|
|
1207
|
-
|
|
1208
|
-
|
|
1209
|
-
|
|
1206
|
+
return (
|
|
1207
|
+
promptDispatchRuntime.startTypingLoop(
|
|
1208
|
+
ctx,
|
|
1209
|
+
turn?.chatId ?? target?.chatId,
|
|
1210
|
+
{ target },
|
|
1211
|
+
) !== false
|
|
1212
|
+
);
|
|
1210
1213
|
};
|
|
1211
1214
|
const startActiveTurnTypingLoop = (ctx: Pi.ExtensionContext): void => {
|
|
1212
1215
|
if (uiPromptActive) return;
|
|
@@ -82,13 +82,6 @@ function rejectTelegramDirectOwnership(method: string): Promise<never> {
|
|
|
82
82
|
);
|
|
83
83
|
}
|
|
84
84
|
|
|
85
|
-
export function createTelegramAggregateTypingActionSender(
|
|
86
|
-
runtime: Pick<TelegramBridgeApiRuntime, "call">,
|
|
87
|
-
): (chatId: number) => Promise<unknown> {
|
|
88
|
-
return (chatId) =>
|
|
89
|
-
runtime.call("sendChatAction", { chat_id: chatId, action: "typing" });
|
|
90
|
-
}
|
|
91
|
-
|
|
92
85
|
export function createTelegramBusAwareApiRuntime(
|
|
93
86
|
deps: TelegramBusAwareApiRuntimeDeps,
|
|
94
87
|
): TelegramBridgeApiRuntime {
|
|
@@ -1210,13 +1210,36 @@ export function createTelegramBusFollowerTargetProvisioner(
|
|
|
1210
1210
|
: recoverableTarget && !pendingTargetRecovery
|
|
1211
1211
|
? await recoverRequestedTarget()
|
|
1212
1212
|
: await provisionTarget();
|
|
1213
|
+
const alignResultWithWorkspaceSlot = (): void => {
|
|
1214
|
+
if (
|
|
1215
|
+
!workspaceIdentity ||
|
|
1216
|
+
result.record.slot === workspaceIdentity.slot
|
|
1217
|
+
) return;
|
|
1218
|
+
deps.recordRuntimeEvent(
|
|
1219
|
+
"bus",
|
|
1220
|
+
"Telegram follower record slot reconciled to its Workspace claim",
|
|
1221
|
+
{
|
|
1222
|
+
phase: "follower-register-slot-reconcile",
|
|
1223
|
+
instanceId: registration.instanceId,
|
|
1224
|
+
chatId: result.target.chatId,
|
|
1225
|
+
threadId: result.target.threadId,
|
|
1226
|
+
previousSlot: result.record.slot,
|
|
1227
|
+
slot: workspaceIdentity.slot,
|
|
1228
|
+
},
|
|
1229
|
+
);
|
|
1230
|
+
result = {
|
|
1231
|
+
...result,
|
|
1232
|
+
record: { ...result.record, slot: workspaceIdentity.slot },
|
|
1233
|
+
};
|
|
1234
|
+
};
|
|
1235
|
+
alignResultWithWorkspaceSlot();
|
|
1213
1236
|
const crossSessionReuse =
|
|
1214
1237
|
!!reconnectRecord &&
|
|
1215
1238
|
reconnectRecord.instanceId !== registration.instanceId;
|
|
1216
1239
|
if (reconnectRecord && !crossSessionReuse) {
|
|
1217
1240
|
const nowMs = getNowMs();
|
|
1218
1241
|
const refreshedRecord = deps.topicTargetStore.upsert({
|
|
1219
|
-
...
|
|
1242
|
+
...result.record,
|
|
1220
1243
|
instanceId: registration.instanceId,
|
|
1221
1244
|
updatedAtMs: nowMs,
|
|
1222
1245
|
lastSyncObservedAtMs: nowMs,
|
|
@@ -1300,7 +1323,7 @@ export function createTelegramBusFollowerTargetProvisioner(
|
|
|
1300
1323
|
} else if (crossSessionReuse && reconnectRecord) {
|
|
1301
1324
|
const nowMs = getNowMs();
|
|
1302
1325
|
const transferredRecord = deps.topicTargetStore.upsert({
|
|
1303
|
-
...
|
|
1326
|
+
...result.record,
|
|
1304
1327
|
profileKey: followerProfileKey,
|
|
1305
1328
|
owner:
|
|
1306
1329
|
followerOwner.kind === "manual-follower"
|
|
@@ -1361,6 +1384,7 @@ export function createTelegramBusFollowerTargetProvisioner(
|
|
|
1361
1384
|
}
|
|
1362
1385
|
}
|
|
1363
1386
|
if (workspaceIdentity) {
|
|
1387
|
+
alignResultWithWorkspaceSlot();
|
|
1364
1388
|
const workspaceCommit =
|
|
1365
1389
|
Threads.commitTelegramWorkspaceProvisionBinding({
|
|
1366
1390
|
store: deps.topicTargetStore,
|
|
@@ -1373,7 +1397,7 @@ export function createTelegramBusFollowerTargetProvisioner(
|
|
|
1373
1397
|
...(result.record.threadName
|
|
1374
1398
|
? { threadName: result.record.threadName }
|
|
1375
1399
|
: {}),
|
|
1376
|
-
|
|
1400
|
+
slot: workspaceIdentity.slot,
|
|
1377
1401
|
journalBindingKeys: [followerProfileKey],
|
|
1378
1402
|
journalBindingsComplete: true,
|
|
1379
1403
|
updatedAtMs: getNowMs(),
|
|
@@ -667,8 +667,6 @@ export default function (pi: Pi.ExtensionAPI) {
|
|
|
667
667
|
typing,
|
|
668
668
|
getDefaultChatId: proactivePushChatIdGetter,
|
|
669
669
|
sendTypingAction,
|
|
670
|
-
sendAggregateTypingAction:
|
|
671
|
-
BusApi.createTelegramAggregateTypingActionSender(telegramApiRuntime),
|
|
672
670
|
updateStatus,
|
|
673
671
|
isContextActive: telegramSessionContextStore.isCurrent,
|
|
674
672
|
getTransportAuthority() {
|
|
@@ -1628,7 +1626,7 @@ export default function (pi: Pi.ExtensionAPI) {
|
|
|
1628
1626
|
loadConfig: configStore.load,
|
|
1629
1627
|
setQueuedItems: telegramQueueStore.setQueuedItems,
|
|
1630
1628
|
setCurrentModel: currentModelRuntime.set,
|
|
1631
|
-
setPendingModelSwitch:
|
|
1629
|
+
setPendingModelSwitch: modelSwitchController.clearPendingSwitch,
|
|
1632
1630
|
syncCounters: queue.syncCounters,
|
|
1633
1631
|
syncFlags: lifecycle.syncFlags,
|
|
1634
1632
|
bindDeferredDispatchContext: deferredQueueDispatchRuntime.bind,
|