@llblab/pi-kit 0.36.1 → 0.36.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 +10 -0
- package/README.md +3 -3
- package/node_modules/@llblab/pi-claude-usage/CHANGELOG.md +4 -0
- package/node_modules/@llblab/pi-claude-usage/README.md +2 -2
- package/node_modules/@llblab/pi-claude-usage/lib/status-format.ts +2 -3
- package/node_modules/@llblab/pi-claude-usage/package.json +1 -1
- package/node_modules/@llblab/pi-codex-usage/CHANGELOG.md +5 -0
- package/node_modules/@llblab/pi-codex-usage/README.md +1 -1
- package/node_modules/@llblab/pi-codex-usage/lib/status-format.ts +2 -3
- package/node_modules/@llblab/pi-codex-usage/package.json +1 -1
- package/node_modules/@llblab/pi-telegram/AGENTS.md +2 -2
- package/node_modules/@llblab/pi-telegram/BACKLOG.md +43 -5
- package/node_modules/@llblab/pi-telegram/CHANGELOG.md +45 -0
- package/node_modules/@llblab/pi-telegram/README.md +4 -2
- package/node_modules/@llblab/pi-telegram/dist/lib/bindings.js +17 -1
- package/node_modules/@llblab/pi-telegram/dist/lib/bus-follower.js +23 -11
- package/node_modules/@llblab/pi-telegram/dist/lib/bus-leader.js +24 -6
- package/node_modules/@llblab/pi-telegram/dist/lib/bus.d.ts +2 -1
- package/node_modules/@llblab/pi-telegram/dist/lib/bus.js +7 -0
- package/node_modules/@llblab/pi-telegram/dist/lib/commands.d.ts +7 -4
- package/node_modules/@llblab/pi-telegram/dist/lib/commands.js +22 -4
- package/node_modules/@llblab/pi-telegram/dist/lib/extension.js +52 -10
- package/node_modules/@llblab/pi-telegram/dist/lib/generative-apps.d.ts +1 -0
- package/node_modules/@llblab/pi-telegram/dist/lib/generative-apps.js +9 -0
- package/node_modules/@llblab/pi-telegram/dist/lib/lifecycle.d.ts +2 -1
- package/node_modules/@llblab/pi-telegram/dist/lib/menu-settings.d.ts +1 -1
- package/node_modules/@llblab/pi-telegram/dist/lib/menu-settings.js +3 -2
- package/node_modules/@llblab/pi-telegram/dist/lib/menu.d.ts +4 -4
- package/node_modules/@llblab/pi-telegram/dist/lib/pi.d.ts +2 -2
- package/node_modules/@llblab/pi-telegram/dist/lib/prompts.d.ts +6 -3
- package/node_modules/@llblab/pi-telegram/dist/lib/prompts.js +50 -9
- package/node_modules/@llblab/pi-telegram/dist/lib/routing.d.ts +3 -2
- package/node_modules/@llblab/pi-telegram/dist/lib/routing.js +120 -14
- package/node_modules/@llblab/pi-telegram/dist/lib/telegram-api.d.ts +13 -0
- package/node_modules/@llblab/pi-telegram/dist/lib/telegram-api.js +12 -0
- package/node_modules/@llblab/pi-telegram/dist/lib/thread-cleanup-manager.d.ts +39 -3
- package/node_modules/@llblab/pi-telegram/dist/lib/thread-cleanup-manager.js +144 -16
- package/node_modules/@llblab/pi-telegram/dist/lib/thread-display.d.ts +1 -1
- package/node_modules/@llblab/pi-telegram/dist/lib/thread-display.js +15 -8
- package/node_modules/@llblab/pi-telegram/dist/lib/threads.d.ts +14 -1
- package/node_modules/@llblab/pi-telegram/dist/lib/threads.js +122 -8
- package/node_modules/@llblab/pi-telegram/dist/lib/updates.js +9 -6
- package/node_modules/@llblab/pi-telegram/dist/lib/workspace-admission.d.ts +4 -3
- package/node_modules/@llblab/pi-telegram/dist/lib/workspace-admission.js +3 -3
- package/node_modules/@llblab/pi-telegram/dist/lib/workspace-retirement.d.ts +1 -0
- package/node_modules/@llblab/pi-telegram/dist/lib/workspace-retirement.js +52 -2
- package/node_modules/@llblab/pi-telegram/dist/lib/workspace-slots.d.ts +17 -0
- package/node_modules/@llblab/pi-telegram/dist/lib/workspace-slots.js +25 -0
- package/node_modules/@llblab/pi-telegram/dist/package.json +2 -1
- package/node_modules/@llblab/pi-telegram/docs/architecture.md +10 -10
- package/node_modules/@llblab/pi-telegram/docs/multi-instance-bus.md +23 -3
- package/node_modules/@llblab/pi-telegram/docs/public-api.md +2 -0
- package/node_modules/@llblab/pi-telegram/docs/ui-style.md +1 -1
- package/node_modules/@llblab/pi-telegram/lib/bindings.ts +24 -1
- package/node_modules/@llblab/pi-telegram/lib/bus-follower.ts +30 -13
- package/node_modules/@llblab/pi-telegram/lib/bus-leader.ts +25 -8
- package/node_modules/@llblab/pi-telegram/lib/bus.ts +11 -0
- package/node_modules/@llblab/pi-telegram/lib/commands.ts +34 -8
- package/node_modules/@llblab/pi-telegram/lib/extension.ts +54 -11
- package/node_modules/@llblab/pi-telegram/lib/generative-apps.ts +12 -0
- package/node_modules/@llblab/pi-telegram/lib/lifecycle.ts +2 -0
- package/node_modules/@llblab/pi-telegram/lib/menu-settings.ts +4 -3
- package/node_modules/@llblab/pi-telegram/lib/menu.ts +4 -4
- package/node_modules/@llblab/pi-telegram/lib/pi.ts +2 -0
- package/node_modules/@llblab/pi-telegram/lib/prompts.ts +79 -16
- package/node_modules/@llblab/pi-telegram/lib/routing.ts +142 -26
- package/node_modules/@llblab/pi-telegram/lib/telegram-api.ts +18 -0
- package/node_modules/@llblab/pi-telegram/lib/thread-cleanup-manager.ts +173 -17
- package/node_modules/@llblab/pi-telegram/lib/thread-display.ts +14 -8
- package/node_modules/@llblab/pi-telegram/lib/threads.ts +166 -10
- package/node_modules/@llblab/pi-telegram/lib/updates.ts +9 -6
- package/node_modules/@llblab/pi-telegram/lib/workspace-admission.ts +8 -7
- package/node_modules/@llblab/pi-telegram/lib/workspace-retirement.ts +56 -2
- package/node_modules/@llblab/pi-telegram/lib/workspace-slots.ts +44 -0
- package/node_modules/@llblab/pi-telegram/package.json +2 -1
- package/node_modules/@llblab/pi-telegram/scripts/run-macos-tests.mjs +43 -0
- package/package.json +4 -4
|
@@ -70,6 +70,7 @@ import {
|
|
|
70
70
|
type TelegramBusSelectedMenuTextEffect,
|
|
71
71
|
type TelegramBusSocketPathSource,
|
|
72
72
|
type TelegramBusWorkspaceRestoreObservation,
|
|
73
|
+
TELEGRAM_BUS_FOLLOWER_API_NOT_ALLOWED_MESSAGE,
|
|
73
74
|
} from "./bus.ts";
|
|
74
75
|
import type {
|
|
75
76
|
TelegramConfigStore,
|
|
@@ -96,6 +97,7 @@ import {
|
|
|
96
97
|
assertTelegramApiCallAuthority,
|
|
97
98
|
TelegramApiAuthorityError,
|
|
98
99
|
TelegramApiCommitUnknownError,
|
|
100
|
+
TelegramApiFollowerNotAllowedError,
|
|
99
101
|
TelegramApiStaleTargetError,
|
|
100
102
|
type TelegramApiCallOptions,
|
|
101
103
|
} from "./telegram-api.ts";
|
|
@@ -1917,11 +1919,16 @@ export function createTelegramBusFollowerApiCaller(
|
|
|
1917
1919
|
method: string,
|
|
1918
1920
|
args: unknown[],
|
|
1919
1921
|
localOptions: Pick<TelegramApiCallOptions, "assertAuthority"> | undefined,
|
|
1920
|
-
timing: { registeredAtMs?: number },
|
|
1922
|
+
timing: { registeredAtMs?: number; apiMethod?: string },
|
|
1921
1923
|
): Promise<unknown> => {
|
|
1922
1924
|
const assertion = localOptions?.assertAuthority;
|
|
1923
1925
|
const assertCurrent = () => assertTelegramApiCallAuthority(assertion, false);
|
|
1924
1926
|
assertCurrent();
|
|
1927
|
+
// Match IPC's JSON semantics once, before awaits; send and refusal metadata share this identity.
|
|
1928
|
+
const requestArgs = JSON.parse(JSON.stringify(args)) as unknown[];
|
|
1929
|
+
const apiMethod = (method === "call" || method === "callMultipart") && typeof requestArgs[0] === "string"
|
|
1930
|
+
? requestArgs[0] : method;
|
|
1931
|
+
timing.apiMethod = apiMethod;
|
|
1925
1932
|
const registration = await resolveTelegramBusFollowerRegistration(
|
|
1926
1933
|
deps,
|
|
1927
1934
|
timeoutMs,
|
|
@@ -1941,18 +1948,13 @@ export function createTelegramBusFollowerApiCaller(
|
|
|
1941
1948
|
instanceId: deps.instanceId,
|
|
1942
1949
|
registrationGeneration: registration.generation,
|
|
1943
1950
|
method,
|
|
1944
|
-
args,
|
|
1951
|
+
args: requestArgs,
|
|
1945
1952
|
sentAtMs: getNowMs(),
|
|
1946
1953
|
},
|
|
1947
1954
|
assertion ? assertCurrent : undefined,
|
|
1948
1955
|
);
|
|
1949
1956
|
} catch (error) {
|
|
1950
1957
|
if (error instanceof TelegramBusLocalAuthorityError) throw new TelegramApiAuthorityError(error.requestIssued);
|
|
1951
|
-
const apiMethod =
|
|
1952
|
-
(method === "call" || method === "callMultipart") &&
|
|
1953
|
-
typeof args[0] === "string"
|
|
1954
|
-
? args[0]
|
|
1955
|
-
: method;
|
|
1956
1958
|
if (!isTelegramApiMethodRetrySafe(apiMethod)) {
|
|
1957
1959
|
throw new TelegramApiCommitUnknownError(apiMethod, error);
|
|
1958
1960
|
}
|
|
@@ -1986,11 +1988,30 @@ export function createTelegramBusFollowerApiCaller(
|
|
|
1986
1988
|
new Error(message ?? "Telegram bus API call result is ambiguous."),
|
|
1987
1989
|
);
|
|
1988
1990
|
}
|
|
1991
|
+
const refusedMethod = response?.kind === "bus.ack" && !response.ok &&
|
|
1992
|
+
response.error?.code === "follower-api-not-allowed" &&
|
|
1993
|
+
response.error.method === apiMethod
|
|
1994
|
+
? apiMethod : undefined;
|
|
1995
|
+
if (refusedMethod || message === TELEGRAM_BUS_FOLLOWER_API_NOT_ALLOWED_MESSAGE) {
|
|
1996
|
+
const body =
|
|
1997
|
+
(method === "call" || method === "callMultipart") && isRecord(requestArgs[1])
|
|
1998
|
+
? requestArgs[1]
|
|
1999
|
+
: undefined;
|
|
2000
|
+
const chatId = Number(body?.chat_id);
|
|
2001
|
+
const threadId = Number(body?.message_thread_id);
|
|
2002
|
+
throw new TelegramApiFollowerNotAllowedError(
|
|
2003
|
+
message ?? TELEGRAM_BUS_FOLLOWER_API_NOT_ALLOWED_MESSAGE,
|
|
2004
|
+
Number.isSafeInteger(chatId) && Number.isSafeInteger(threadId)
|
|
2005
|
+
? { chatId, threadId }
|
|
2006
|
+
: undefined,
|
|
2007
|
+
refusedMethod,
|
|
2008
|
+
);
|
|
2009
|
+
}
|
|
1989
2010
|
throw new Error(message ?? "Telegram bus API call failed.");
|
|
1990
2011
|
};
|
|
1991
2012
|
return async (method, args, localOptions) => {
|
|
1992
2013
|
const startedAtMs = getNowMs();
|
|
1993
|
-
const timing: { registeredAtMs?: number } = {};
|
|
2014
|
+
const timing: { registeredAtMs?: number; apiMethod?: string } = {};
|
|
1994
2015
|
let outcome = "ok";
|
|
1995
2016
|
try {
|
|
1996
2017
|
return await caller(method, args, localOptions, timing);
|
|
@@ -2000,11 +2021,7 @@ export function createTelegramBusFollowerApiCaller(
|
|
|
2000
2021
|
} finally {
|
|
2001
2022
|
const elapsedMs = getNowMs() - startedAtMs;
|
|
2002
2023
|
if (elapsedMs >= slowCallMs) {
|
|
2003
|
-
const apiMethod =
|
|
2004
|
-
(method === "call" || method === "callMultipart") &&
|
|
2005
|
-
typeof args[0] === "string"
|
|
2006
|
-
? args[0]
|
|
2007
|
-
: method;
|
|
2024
|
+
const apiMethod = timing.apiMethod ?? method;
|
|
2008
2025
|
try {
|
|
2009
2026
|
deps.recordRuntimeEvent?.(
|
|
2010
2027
|
"bus",
|
|
@@ -40,6 +40,7 @@ import {
|
|
|
40
40
|
type TelegramBusProtocolIdentity,
|
|
41
41
|
type TelegramBusSelectedMenuDelivery,
|
|
42
42
|
type TelegramBusSocketPathSource,
|
|
43
|
+
TELEGRAM_BUS_FOLLOWER_API_NOT_ALLOWED_MESSAGE,
|
|
43
44
|
} from "./bus.ts";
|
|
44
45
|
import type { TelegramThreadDisplayMode } from "./config.ts";
|
|
45
46
|
import type { TelegramAttachmentSource } from "./media.ts";
|
|
@@ -464,6 +465,9 @@ export function createTelegramBusLeaderRuntimeAssembly<TContext>(
|
|
|
464
465
|
store: deps.topicTargetStore,
|
|
465
466
|
getLeaderEpoch: deps.getCurrentLeaderEpoch,
|
|
466
467
|
getExternalProtection: deps.captureWorkspaceExternalProtection,
|
|
468
|
+
onSlotReused: (bindingKey, isCurrent) => display
|
|
469
|
+
? reconcileThreadDisplay(bindingKey, isCurrent).then(() => undefined)
|
|
470
|
+
: Promise.resolve(),
|
|
467
471
|
recordEvent(message, details) {
|
|
468
472
|
deps.recordRuntimeEvent("bus", message, details);
|
|
469
473
|
},
|
|
@@ -513,6 +517,9 @@ export function createTelegramBusLeaderRuntimeAssembly<TContext>(
|
|
|
513
517
|
getLeaderEpoch: () => deps.getCurrentLeaderEpoch?.(),
|
|
514
518
|
captureLiveBindingKeys,
|
|
515
519
|
captureBindingAuthority(binding) {
|
|
520
|
+
// Slotless retained bindings need display repair even without a live owner.
|
|
521
|
+
// The reconciler still fences the exact binding snapshot, profile, mode and epoch.
|
|
522
|
+
if (binding.slot === undefined) return () => true;
|
|
516
523
|
const matches = (target: TelegramTarget | undefined) =>
|
|
517
524
|
target?.chatId === binding.target.chatId &&
|
|
518
525
|
target?.threadId === binding.target.threadId;
|
|
@@ -544,19 +551,22 @@ export function createTelegramBusLeaderRuntimeAssembly<TContext>(
|
|
|
544
551
|
callApi: deps.callApi,
|
|
545
552
|
})
|
|
546
553
|
: undefined;
|
|
547
|
-
const reconcileThreadDisplayOperation = async () => {
|
|
548
|
-
const result = await display!.reconcile();
|
|
554
|
+
const reconcileThreadDisplayOperation = async (bindingKey?: string) => {
|
|
555
|
+
const result = await display!.reconcile(bindingKey);
|
|
549
556
|
deps.onThreadDisplayChanged?.();
|
|
550
557
|
return result;
|
|
551
558
|
};
|
|
552
|
-
const reconcileThreadDisplay = () =>
|
|
559
|
+
const reconcileThreadDisplay = (bindingKey?: string, isCurrent: () => boolean = () => true) =>
|
|
553
560
|
runWorkspaceOperation(
|
|
554
561
|
{
|
|
555
562
|
operationId: createTelegramWorkspaceAdmissionOperationId(),
|
|
556
563
|
operationKind: "workspace.reconcile-display",
|
|
557
564
|
scopes: [{ kind: "profile" }],
|
|
558
565
|
},
|
|
559
|
-
|
|
566
|
+
() => {
|
|
567
|
+
if (!isCurrent()) throw new Error("Telegram Thread display update lost allocation authority.");
|
|
568
|
+
return reconcileThreadDisplayOperation(bindingKey);
|
|
569
|
+
},
|
|
560
570
|
);
|
|
561
571
|
let startupDisplayTimer: ReturnType<typeof setTimeout> | undefined;
|
|
562
572
|
let startupDisplayGeneration = 0;
|
|
@@ -4179,10 +4189,17 @@ async function handleFollowerApiCall(
|
|
|
4179
4189
|
args: envelope.args,
|
|
4180
4190
|
})
|
|
4181
4191
|
) {
|
|
4182
|
-
|
|
4183
|
-
|
|
4184
|
-
|
|
4185
|
-
|
|
4192
|
+
const apiMethod = getFollowerApiMethodAndBody(envelope).apiMethod;
|
|
4193
|
+
return {
|
|
4194
|
+
...rejectTelegramBusRequest(
|
|
4195
|
+
envelope.requestId,
|
|
4196
|
+
TELEGRAM_BUS_FOLLOWER_API_NOT_ALLOWED_MESSAGE,
|
|
4197
|
+
),
|
|
4198
|
+
// Only this pre-dispatch authorizer refusal certifies that the API method was not called.
|
|
4199
|
+
...(/^[A-Za-z][A-Za-z0-9]{0,63}$/.test(apiMethod)
|
|
4200
|
+
? { error: { code: "follower-api-not-allowed" as const, method: apiMethod } }
|
|
4201
|
+
: {}),
|
|
4202
|
+
};
|
|
4186
4203
|
}
|
|
4187
4204
|
if (!deps.callApi) {
|
|
4188
4205
|
return rejectTelegramBusRequest(
|
|
@@ -1260,6 +1260,7 @@ export type TelegramBusEnvelope = (
|
|
|
1260
1260
|
| "ledger-overloaded"
|
|
1261
1261
|
| "incompatible-protocol"
|
|
1262
1262
|
| "stale-target"
|
|
1263
|
+
| "follower-api-not-allowed"
|
|
1263
1264
|
| "workspace-binding-unavailable";
|
|
1264
1265
|
method?: string;
|
|
1265
1266
|
chatId?: number;
|
|
@@ -2873,6 +2874,9 @@ export function isTelegramBusEnvelopeAuthorized(
|
|
|
2873
2874
|
}
|
|
2874
2875
|
|
|
2875
2876
|
/** Negative acknowledgement carrying only a diagnostic message. */
|
|
2877
|
+
export const TELEGRAM_BUS_FOLLOWER_API_NOT_ALLOWED_MESSAGE =
|
|
2878
|
+
"Telegram bus API call is not allowed for this follower.";
|
|
2879
|
+
|
|
2876
2880
|
export function rejectTelegramBusRequest(
|
|
2877
2881
|
requestId: string,
|
|
2878
2882
|
message: string,
|
|
@@ -4145,10 +4149,17 @@ function parseAckEnvelope(
|
|
|
4145
4149
|
code === "ledger-overloaded" ||
|
|
4146
4150
|
code === "incompatible-protocol" ||
|
|
4147
4151
|
code === "stale-target" ||
|
|
4152
|
+
code === "follower-api-not-allowed" ||
|
|
4148
4153
|
code === "workspace-binding-unavailable"
|
|
4149
4154
|
) {
|
|
4150
4155
|
const chatId = value.error.chatId;
|
|
4151
4156
|
const threadId = value.error.threadId;
|
|
4157
|
+
if (
|
|
4158
|
+
code === "follower-api-not-allowed" &&
|
|
4159
|
+
(value.ok || Object.hasOwn(value, "result") ||
|
|
4160
|
+
typeof value.error.method !== "string" ||
|
|
4161
|
+
!/^[A-Za-z][A-Za-z0-9]{0,63}$/.test(value.error.method))
|
|
4162
|
+
) return undefined;
|
|
4152
4163
|
if (
|
|
4153
4164
|
code === "stale-target" &&
|
|
4154
4165
|
(!Number.isSafeInteger(chatId) || !Number.isSafeInteger(threadId))
|
|
@@ -36,6 +36,7 @@ import {
|
|
|
36
36
|
TelegramApiAuthorityError,
|
|
37
37
|
type TelegramApiCallOptions,
|
|
38
38
|
} from "./telegram-api.ts";
|
|
39
|
+
import type { TelegramTarget } from "./target.ts";
|
|
39
40
|
import type { TelegramSessionReplacementIntent } from "./threads.ts";
|
|
40
41
|
import {
|
|
41
42
|
acquireTelegramUpdateRouting,
|
|
@@ -1194,34 +1195,38 @@ export interface TelegramCommandTargetRuntimeDeps<TContext> {
|
|
|
1194
1195
|
) => void;
|
|
1195
1196
|
getAdmissionScope?: () => string | undefined;
|
|
1196
1197
|
getAdmissionJournalBinding?: () => string | undefined;
|
|
1198
|
+
/** Ordinary reply destination selected before publication, not resend authority. */
|
|
1199
|
+
getReplyTarget?: (
|
|
1200
|
+
message: TelegramCommandRuntimeMessage,
|
|
1201
|
+
) => TelegramTarget | undefined;
|
|
1197
1202
|
onControlQueued?: (
|
|
1198
1203
|
message: TelegramCommandRuntimeMessage,
|
|
1199
1204
|
receipt: TelegramQueueAdmissionReceipt,
|
|
1200
1205
|
) => void;
|
|
1201
1206
|
showStatus: (
|
|
1202
1207
|
chatId: number,
|
|
1203
|
-
replyToMessageId: number,
|
|
1208
|
+
replyToMessageId: number | undefined,
|
|
1204
1209
|
ctx: TContext,
|
|
1205
1210
|
threadId?: number,
|
|
1206
1211
|
options?: Pick<TelegramApiCallOptions, "assertAuthority">,
|
|
1207
1212
|
) => Promise<void>;
|
|
1208
1213
|
openModelMenu: (
|
|
1209
1214
|
chatId: number,
|
|
1210
|
-
replyToMessageId: number,
|
|
1215
|
+
replyToMessageId: number | undefined,
|
|
1211
1216
|
ctx: TContext,
|
|
1212
1217
|
threadId?: number,
|
|
1213
1218
|
options?: Pick<TelegramApiCallOptions, "assertAuthority">,
|
|
1214
1219
|
) => Promise<void>;
|
|
1215
1220
|
openSettingsMenu?: (
|
|
1216
1221
|
chatId: number,
|
|
1217
|
-
replyToMessageId: number,
|
|
1222
|
+
replyToMessageId: number | undefined,
|
|
1218
1223
|
ctx: TContext,
|
|
1219
1224
|
threadId?: number,
|
|
1220
1225
|
options?: Pick<TelegramApiCallOptions, "assertAuthority">,
|
|
1221
1226
|
) => Promise<void>;
|
|
1222
1227
|
sendTextReply: (
|
|
1223
1228
|
chatId: number,
|
|
1224
|
-
replyToMessageId: number,
|
|
1229
|
+
replyToMessageId: number | undefined,
|
|
1225
1230
|
text: string,
|
|
1226
1231
|
options?: {
|
|
1227
1232
|
parseMode?: "HTML";
|
|
@@ -1336,6 +1341,7 @@ export function createTelegramCommandTargetQueueRuntime<
|
|
|
1336
1341
|
createControlItem,
|
|
1337
1342
|
enqueueControlItem: controlQueueController.enqueue,
|
|
1338
1343
|
}),
|
|
1344
|
+
getReplyTarget: deps.getReplyTarget,
|
|
1339
1345
|
getAdmissionScope: deps.getAdmissionScope,
|
|
1340
1346
|
getAdmissionJournalBinding: deps.getAdmissionJournalBinding,
|
|
1341
1347
|
onControlQueued: deps.onControlQueued,
|
|
@@ -1352,6 +1358,25 @@ export function createTelegramCommandTargetRuntime<
|
|
|
1352
1358
|
>(
|
|
1353
1359
|
deps: TelegramCommandTargetRuntimeDeps<TContext>,
|
|
1354
1360
|
): TelegramCommandTargetRuntime<TMessage, TContext> {
|
|
1361
|
+
const getReplyTarget = (
|
|
1362
|
+
message: TMessage,
|
|
1363
|
+
options?: Pick<TelegramApiCallOptions, "assertAuthority">,
|
|
1364
|
+
) => {
|
|
1365
|
+
const source = getTelegramCommandMessageTarget(message);
|
|
1366
|
+
const recipient = options?.assertAuthority
|
|
1367
|
+
? undefined
|
|
1368
|
+
: deps.getReplyTarget?.(message);
|
|
1369
|
+
if (!recipient) return source;
|
|
1370
|
+
if (recipient.chatId !== source.chatId)
|
|
1371
|
+
throw new Error("Telegram command reply cannot change its recipient chat.");
|
|
1372
|
+
if (source.threadId !== undefined && recipient.threadId === undefined)
|
|
1373
|
+
throw new Error("Telegram command cannot move a Thread reply to All.");
|
|
1374
|
+
return {
|
|
1375
|
+
...recipient,
|
|
1376
|
+
replyToMessageId:
|
|
1377
|
+
recipient.threadId === source.threadId ? source.replyToMessageId : undefined,
|
|
1378
|
+
};
|
|
1379
|
+
};
|
|
1355
1380
|
return {
|
|
1356
1381
|
enqueueControlItem: (message, ctx, controlType, statusSummary, execute) => {
|
|
1357
1382
|
const sourceUpdateId = message.pi_telegram_source_update_id;
|
|
@@ -1381,7 +1406,7 @@ export function createTelegramCommandTargetRuntime<
|
|
|
1381
1406
|
);
|
|
1382
1407
|
},
|
|
1383
1408
|
showStatus: (message, ctx, options) => {
|
|
1384
|
-
const target =
|
|
1409
|
+
const target = getReplyTarget(message, options);
|
|
1385
1410
|
return deps.showStatus(
|
|
1386
1411
|
target.chatId,
|
|
1387
1412
|
target.replyToMessageId,
|
|
@@ -1391,7 +1416,7 @@ export function createTelegramCommandTargetRuntime<
|
|
|
1391
1416
|
);
|
|
1392
1417
|
},
|
|
1393
1418
|
openModelMenu: (message, ctx, options) => {
|
|
1394
|
-
const target =
|
|
1419
|
+
const target = getReplyTarget(message, options);
|
|
1395
1420
|
return deps.openModelMenu(
|
|
1396
1421
|
target.chatId,
|
|
1397
1422
|
target.replyToMessageId,
|
|
@@ -1401,7 +1426,7 @@ export function createTelegramCommandTargetRuntime<
|
|
|
1401
1426
|
);
|
|
1402
1427
|
},
|
|
1403
1428
|
openSettingsMenu: async (message, ctx, options) => {
|
|
1404
|
-
const target =
|
|
1429
|
+
const target = getReplyTarget(message, options);
|
|
1405
1430
|
if (!deps.openSettingsMenu) {
|
|
1406
1431
|
await deps.sendTextReply(
|
|
1407
1432
|
target.chatId,
|
|
@@ -1423,7 +1448,7 @@ export function createTelegramCommandTargetRuntime<
|
|
|
1423
1448
|
);
|
|
1424
1449
|
},
|
|
1425
1450
|
sendTextReply: async (message, text, options) => {
|
|
1426
|
-
const target =
|
|
1451
|
+
const target = getReplyTarget(message, options);
|
|
1427
1452
|
const assertAuthority = options?.assertAuthority,
|
|
1428
1453
|
send = deps.sendTextReply;
|
|
1429
1454
|
const capturedOptions = { ...options, target };
|
|
@@ -2217,6 +2242,7 @@ export function createTelegramCommandHandlerTargetRuntime<
|
|
|
2217
2242
|
}),
|
|
2218
2243
|
appendControlItem: deps.appendControlItem,
|
|
2219
2244
|
dispatchNextQueuedTelegramTurn: deps.dispatchNextQueuedTelegramTurn,
|
|
2245
|
+
getReplyTarget: deps.getReplyTarget,
|
|
2220
2246
|
getAdmissionScope: deps.getAdmissionScope,
|
|
2221
2247
|
getAdmissionJournalBinding: deps.getAdmissionJournalBinding,
|
|
2222
2248
|
onControlQueued: deps.onControlQueued,
|
|
@@ -590,18 +590,32 @@ export default function (pi: Pi.ExtensionAPI) {
|
|
|
590
590
|
);
|
|
591
591
|
},
|
|
592
592
|
});
|
|
593
|
-
const
|
|
594
|
-
ThreadCleanupManager.
|
|
595
|
-
|
|
596
|
-
|
|
593
|
+
const inactiveThreadCleanupRuntime =
|
|
594
|
+
ThreadCleanupManager.createTelegramInactiveThreadCleanupRuntime({
|
|
595
|
+
store: threadStore,
|
|
596
|
+
captureAuthority() {
|
|
597
|
+
const ledger = workspaceAdmissionRuntime.resolve();
|
|
598
|
+
const leaderEpoch = getCurrentLeaderEpoch();
|
|
599
|
+
const operator = configStore.getAllowedUserId();
|
|
600
|
+
if (!ledger || leaderEpoch === undefined || operator === undefined) return undefined;
|
|
601
|
+
const stamp = telegramTransportStampRuntime.getStamp();
|
|
602
|
+
const generation = telegramSessionContextStore.getGeneration();
|
|
603
|
+
return {
|
|
604
|
+
profileName: configStore.getActiveProfileName() ?? "default",
|
|
605
|
+
leaderEpoch, ledger,
|
|
606
|
+
isCurrent() {
|
|
607
|
+
return getCurrentLeaderEpoch() === leaderEpoch &&
|
|
608
|
+
telegramTransportStampRuntime.isActive(stamp) &&
|
|
609
|
+
configStore.getAllowedUserId() === operator &&
|
|
610
|
+
telegramSessionContextStore.getGeneration() === generation;
|
|
611
|
+
},
|
|
612
|
+
};
|
|
597
613
|
},
|
|
598
|
-
listBindings: threadStore.listWorkspaceBindings,
|
|
599
614
|
getProtection: captureWorkspaceExternalProtection,
|
|
600
|
-
|
|
601
|
-
|
|
602
|
-
|
|
603
|
-
|
|
604
|
-
const profileName = configStore.getActiveProfileName() ?? "default";
|
|
615
|
+
getProcessLiveness: ProcessIdentity.getTelegramProcessLiveness,
|
|
616
|
+
runExclusive: telegramWorkspaceOperationRuntime.runExclusive,
|
|
617
|
+
getWorkStore(authority) {
|
|
618
|
+
const profileName = authority.profileName;
|
|
605
619
|
const botToken = configStore.getBotToken();
|
|
606
620
|
if (!botToken)
|
|
607
621
|
throw new Error(
|
|
@@ -617,9 +631,16 @@ export default function (pi: Pi.ExtensionAPI) {
|
|
|
617
631
|
tokenSha256: Journal.createTelegramUpdateJournalBotIdentity({
|
|
618
632
|
botToken,
|
|
619
633
|
}).tokenSha256,
|
|
634
|
+
admissionProfileKey: authority.ledger.getProfileKey(),
|
|
620
635
|
});
|
|
621
636
|
},
|
|
622
637
|
runWorkspaceOperation: telegramWorkspaceOperationRuntime.run,
|
|
638
|
+
deleteThread(authorize) {
|
|
639
|
+
return directTelegramApiRuntime.deleteWorkspaceThread(authorize);
|
|
640
|
+
},
|
|
641
|
+
onError(error) {
|
|
642
|
+
recordRuntimeEvent("telegram", error, { phase: "manual-inactive-thread-cleanup" });
|
|
643
|
+
},
|
|
623
644
|
});
|
|
624
645
|
const updateAdmissionRuntimeBinding =
|
|
625
646
|
Updates.createTelegramUpdateAdmissionRuntimeBinding<Pi.ExtensionContext>({
|
|
@@ -1187,7 +1208,8 @@ export default function (pi: Pi.ExtensionAPI) {
|
|
|
1187
1208
|
sendInteractiveMessage,
|
|
1188
1209
|
answerCallbackQuery,
|
|
1189
1210
|
...configControls,
|
|
1190
|
-
reviewInactiveThreads:
|
|
1211
|
+
reviewInactiveThreads: inactiveThreadCleanupRuntime.review,
|
|
1212
|
+
cleanInactiveThreads: inactiveThreadCleanupRuntime.clean,
|
|
1191
1213
|
getThreadDisplayMode() {
|
|
1192
1214
|
return threadStore.getBotState().threadMode === "enabled"
|
|
1193
1215
|
? Config.resolveTelegramThreadDisplayMode(configStore.get())
|
|
@@ -1342,6 +1364,27 @@ export default function (pi: Pi.ExtensionAPI) {
|
|
|
1342
1364
|
},
|
|
1343
1365
|
getAdmissionScope: getTelegramUpdateAdmissionScope,
|
|
1344
1366
|
getAdmissionJournalBinding: getTelegramQueueJournalBinding,
|
|
1367
|
+
getCommandReplyTarget(message) {
|
|
1368
|
+
if (
|
|
1369
|
+
message.chat.type !== "private" ||
|
|
1370
|
+
"business_connection_id" in message ||
|
|
1371
|
+
!Updates.getTelegramUpdateExecutionFence(message) ||
|
|
1372
|
+
lockRuntime.owns()
|
|
1373
|
+
) return undefined;
|
|
1374
|
+
Updates.assertTelegramUpdateExecutionCurrent(message);
|
|
1375
|
+
const binding = resolveTelegramFollowerJournalBinding();
|
|
1376
|
+
const target = telegramBusFollowerRegistrationState.getTarget();
|
|
1377
|
+
if (
|
|
1378
|
+
!telegramBusFollowerRegistrationState.isRegistered() ||
|
|
1379
|
+
!binding ||
|
|
1380
|
+
binding.recoveryKey !==
|
|
1381
|
+
followerAdmissionLifecycleRuntime.getJournalBindingKey() ||
|
|
1382
|
+
!target ||
|
|
1383
|
+
(threadStore.getBotState().threadMode === "enabled" &&
|
|
1384
|
+
target.threadId === undefined)
|
|
1385
|
+
) throw new Error("Telegram command reply lost its registered recipient.");
|
|
1386
|
+
return { ...target };
|
|
1387
|
+
},
|
|
1345
1388
|
beginCommandEffectWork: publicationRuntime.beginWork,
|
|
1346
1389
|
getMessageOwnership: messageOwnershipRuntime.getForwardOwnership,
|
|
1347
1390
|
recordMessageOwnership: messageOwnershipRuntime.recordRouted,
|
|
@@ -288,6 +288,18 @@ export function resolveGenerativeAppDir(agentDir: string, app: string): string {
|
|
|
288
288
|
return join(getAppsRoot(agentDir), app);
|
|
289
289
|
}
|
|
290
290
|
|
|
291
|
+
export async function isGenerativeAppInstalled(
|
|
292
|
+
agentDir: string,
|
|
293
|
+
app: string,
|
|
294
|
+
): Promise<boolean> {
|
|
295
|
+
try {
|
|
296
|
+
await stat(resolveGenerativeAppModulePath(agentDir, app));
|
|
297
|
+
return true;
|
|
298
|
+
} catch {
|
|
299
|
+
return false;
|
|
300
|
+
}
|
|
301
|
+
}
|
|
302
|
+
|
|
291
303
|
export function resolveGenerativeAppModulePath(
|
|
292
304
|
agentDir: string,
|
|
293
305
|
app: string,
|
|
@@ -14,6 +14,7 @@ import type {
|
|
|
14
14
|
AgentStartEvent,
|
|
15
15
|
AssistantMessageEvent,
|
|
16
16
|
BeforeAgentStartEvent,
|
|
17
|
+
BeforeAgentStartEventResult,
|
|
17
18
|
ExtensionAPI,
|
|
18
19
|
ExtensionContext,
|
|
19
20
|
InputEvent,
|
|
@@ -61,6 +62,7 @@ type TelegramBeforeAgentStartEvent = Omit<
|
|
|
61
62
|
|
|
62
63
|
export interface TelegramBeforeAgentStartResult {
|
|
63
64
|
systemPrompt?: string | string[];
|
|
65
|
+
message?: BeforeAgentStartEventResult["message"];
|
|
64
66
|
}
|
|
65
67
|
|
|
66
68
|
type TelegramBeforeAgentStartReturn =
|
|
@@ -90,7 +90,7 @@ export interface TelegramSettingsMenuCallbackDeps extends TelegramSettingsMutati
|
|
|
90
90
|
export interface TelegramSettingsMenuRuntime<TContext> {
|
|
91
91
|
openSettingsMenu: (
|
|
92
92
|
chatId: number,
|
|
93
|
-
replyToMessageId: number,
|
|
93
|
+
replyToMessageId: number | undefined,
|
|
94
94
|
ctx: TContext,
|
|
95
95
|
threadId?: number,
|
|
96
96
|
options?: Pick<TelegramApiCallOptions, "assertAuthority">,
|
|
@@ -207,7 +207,7 @@ export function buildAutomaticThreadCleanupSettingsText(
|
|
|
207
207
|
"<code>-</code> <code>on</code> (default): delete the bound thread and release Telegram authority on graceful quit.",
|
|
208
208
|
"<code>-</code> <code>off</code>: preserve the tab as a restart hint; manual <code>/telegram-disconnect</code> still confirms and deletes it.",
|
|
209
209
|
"",
|
|
210
|
-
"Review inactive tabs checks current owner and work evidence. Review never deletes tabs.",
|
|
210
|
+
"On the leader instance, Review inactive tabs checks current owner and work evidence, including tabs without a letter. Review never deletes tabs.",
|
|
211
211
|
].join("\n");
|
|
212
212
|
}
|
|
213
213
|
|
|
@@ -217,7 +217,8 @@ function buildInactiveThreadReviewText(count: number): string {
|
|
|
217
217
|
"",
|
|
218
218
|
`${count} proven inactive tab${count === 1 ? "" : "s"}.`,
|
|
219
219
|
"No tabs were deleted.",
|
|
220
|
-
"
|
|
220
|
+
"Clean inactive tabs deletes these Telegram tabs and all their messages. Pi session history and project files are not removed.",
|
|
221
|
+
"Clean rechecks owner and work protection; a changed or protected tab is skipped. Choose Back to leave without deleting anything.",
|
|
221
222
|
].join("\n");
|
|
222
223
|
}
|
|
223
224
|
|
|
@@ -264,7 +264,7 @@ export interface TelegramMenuActionRuntimeDeps<
|
|
|
264
264
|
canOfferInFlightModelSwitch: (ctx: TContext) => boolean;
|
|
265
265
|
sendTextReply: (
|
|
266
266
|
chatId: number,
|
|
267
|
-
replyToMessageId: number,
|
|
267
|
+
replyToMessageId: number | undefined,
|
|
268
268
|
text: string,
|
|
269
269
|
options?: {
|
|
270
270
|
target?: { chatId: number; threadId?: number };
|
|
@@ -294,21 +294,21 @@ export interface TelegramMenuActionRuntime<
|
|
|
294
294
|
) => Promise<void>;
|
|
295
295
|
sendStatusMessage: (
|
|
296
296
|
chatId: number,
|
|
297
|
-
replyToMessageId: number,
|
|
297
|
+
replyToMessageId: number | undefined,
|
|
298
298
|
ctx: TContext,
|
|
299
299
|
threadId?: number,
|
|
300
300
|
options?: Pick<TelegramApiCallOptions, "assertAuthority">,
|
|
301
301
|
) => Promise<void>;
|
|
302
302
|
openModelMenu: (
|
|
303
303
|
chatId: number,
|
|
304
|
-
replyToMessageId: number,
|
|
304
|
+
replyToMessageId: number | undefined,
|
|
305
305
|
ctx: TContext,
|
|
306
306
|
threadId?: number,
|
|
307
307
|
options?: Pick<TelegramApiCallOptions, "assertAuthority">,
|
|
308
308
|
) => Promise<void>;
|
|
309
309
|
openThinkingMenu: (
|
|
310
310
|
chatId: number,
|
|
311
|
-
replyToMessageId: number,
|
|
311
|
+
replyToMessageId: number | undefined,
|
|
312
312
|
ctx: TContext,
|
|
313
313
|
threadId?: number,
|
|
314
314
|
options?: Pick<TelegramApiCallOptions, "assertAuthority">,
|
|
@@ -10,6 +10,7 @@ import {
|
|
|
10
10
|
type AgentSettledEvent,
|
|
11
11
|
type AgentStartEvent,
|
|
12
12
|
type BeforeAgentStartEvent,
|
|
13
|
+
type BeforeAgentStartEventResult,
|
|
13
14
|
type ExtensionAPI,
|
|
14
15
|
type ExtensionCommandContext,
|
|
15
16
|
type ExtensionContext,
|
|
@@ -33,6 +34,7 @@ export type {
|
|
|
33
34
|
AgentStartEvent,
|
|
34
35
|
AssistantMessageEvent,
|
|
35
36
|
BeforeAgentStartEvent,
|
|
37
|
+
BeforeAgentStartEventResult,
|
|
36
38
|
ExtensionAPI,
|
|
37
39
|
ExtensionCommandContext,
|
|
38
40
|
ExtensionContext,
|