@llblab/pi-kit 0.6.0 → 0.7.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +8 -0
- package/README.md +4 -4
- package/node_modules/@llblab/pi-actors/AGENTS.md +1 -1
- package/node_modules/@llblab/pi-actors/CHANGELOG.md +6 -0
- package/node_modules/@llblab/pi-actors/README.md +1 -1
- package/node_modules/@llblab/pi-actors/dist/lib/async-runs.d.ts +3 -0
- package/node_modules/@llblab/pi-actors/dist/lib/async-runs.js +14 -1
- package/node_modules/@llblab/pi-actors/dist/lib/command-templates.js +45 -3
- package/node_modules/@llblab/pi-actors/dist/lib/extension-runtime.js +1 -1
- package/node_modules/@llblab/pi-actors/dist/lib/observability.d.ts +16 -3
- package/node_modules/@llblab/pi-actors/dist/lib/observability.js +92 -7
- package/node_modules/@llblab/pi-actors/dist/lib/pi.d.ts +0 -1
- package/node_modules/@llblab/pi-actors/dist/lib/pi.js +15 -24
- package/node_modules/@llblab/pi-actors/dist/lib/run-delivery-lineage.d.ts +17 -0
- package/node_modules/@llblab/pi-actors/dist/lib/run-delivery-lineage.js +44 -0
- package/node_modules/@llblab/pi-actors/dist/lib/run-delivery.d.ts +4 -0
- package/node_modules/@llblab/pi-actors/dist/lib/run-delivery.js +102 -4
- package/node_modules/@llblab/pi-actors/dist/lib/run-ui-runtime.js +58 -39
- package/node_modules/@llblab/pi-actors/dist/lib/runtime.js +14 -6
- package/node_modules/@llblab/pi-actors/dist/skills/actors/SKILL.md +1 -1
- package/node_modules/@llblab/pi-actors/docs/async-runs.md +1 -1
- package/node_modules/@llblab/pi-actors/docs/coordinator-delivery.md +18 -23
- package/node_modules/@llblab/pi-actors/lib/async-runs.ts +18 -1
- package/node_modules/@llblab/pi-actors/lib/command-templates.ts +41 -3
- package/node_modules/@llblab/pi-actors/lib/extension-runtime.ts +1 -1
- package/node_modules/@llblab/pi-actors/lib/observability.ts +119 -5
- package/node_modules/@llblab/pi-actors/lib/pi.ts +15 -28
- package/node_modules/@llblab/pi-actors/lib/run-delivery-lineage.ts +68 -0
- package/node_modules/@llblab/pi-actors/lib/run-delivery.ts +120 -4
- package/node_modules/@llblab/pi-actors/lib/run-ui-runtime.ts +69 -44
- package/node_modules/@llblab/pi-actors/lib/runtime.ts +17 -6
- package/node_modules/@llblab/pi-actors/package.json +1 -1
- package/node_modules/@llblab/pi-actors/skills/actors/SKILL.md +1 -1
- package/node_modules/@llblab/pi-state-flow/AGENTS.md +33 -12
- package/node_modules/@llblab/pi-state-flow/BACKLOG.md +125 -2
- package/node_modules/@llblab/pi-state-flow/CHANGELOG.md +87 -45
- package/node_modules/@llblab/pi-state-flow/README.md +203 -107
- package/node_modules/@llblab/pi-state-flow/docs/README.md +4 -0
- package/node_modules/@llblab/pi-state-flow/docs/architecture.md +188 -0
- package/node_modules/@llblab/pi-state-flow/docs/temporal-acceptance.md +42 -0
- package/node_modules/@llblab/pi-state-flow/index.ts +164 -2
- package/node_modules/@llblab/pi-state-flow/lib/acquisition.ts +138 -0
- package/node_modules/@llblab/pi-state-flow/lib/artifact.ts +273 -0
- package/node_modules/@llblab/pi-state-flow/lib/config.ts +48 -0
- package/node_modules/@llblab/pi-state-flow/lib/context.ts +18 -5
- package/node_modules/@llblab/pi-state-flow/lib/continuation.ts +268 -0
- package/node_modules/@llblab/pi-state-flow/lib/discovery.ts +117 -0
- package/node_modules/@llblab/pi-state-flow/lib/durable.ts +562 -0
- package/node_modules/@llblab/pi-state-flow/lib/episode.ts +24 -12
- package/node_modules/@llblab/pi-state-flow/lib/extension.ts +606 -70
- package/node_modules/@llblab/pi-state-flow/lib/git.ts +666 -0
- package/node_modules/@llblab/pi-state-flow/lib/history.ts +95 -0
- package/node_modules/@llblab/pi-state-flow/lib/json.ts +24 -0
- package/node_modules/@llblab/pi-state-flow/lib/maintenance.ts +141 -0
- package/node_modules/@llblab/pi-state-flow/lib/memory.ts +52 -0
- package/node_modules/@llblab/pi-state-flow/lib/migration.ts +88 -0
- package/node_modules/@llblab/pi-state-flow/lib/publication.ts +296 -0
- package/node_modules/@llblab/pi-state-flow/lib/recovery.ts +23 -7
- package/node_modules/@llblab/pi-state-flow/lib/rehydration.ts +79 -0
- package/node_modules/@llblab/pi-state-flow/lib/runtime.ts +264 -0
- package/node_modules/@llblab/pi-state-flow/lib/session.ts +6 -0
- package/node_modules/@llblab/pi-state-flow/lib/skills.ts +99 -7
- package/node_modules/@llblab/pi-state-flow/lib/snapshot.ts +305 -48
- package/node_modules/@llblab/pi-state-flow/lib/state.ts +74 -7
- package/node_modules/@llblab/pi-state-flow/lib/status.ts +125 -6
- package/node_modules/@llblab/pi-state-flow/lib/storage.ts +196 -0
- package/node_modules/@llblab/pi-state-flow/lib/temporal.ts +233 -0
- package/node_modules/@llblab/pi-state-flow/lib/terminal.ts +70 -24
- package/node_modules/@llblab/pi-state-flow/lib/transition.ts +254 -29
- package/node_modules/@llblab/pi-state-flow/package.json +8 -2
- package/node_modules/@llblab/pi-state-flow/skills/state-flow-memory/SKILL.md +128 -0
- package/node_modules/@llblab/pi-telegram/AGENTS.md +14 -9
- package/node_modules/@llblab/pi-telegram/BACKLOG.md +23 -5
- package/node_modules/@llblab/pi-telegram/CHANGELOG.md +18 -0
- package/node_modules/@llblab/pi-telegram/README.md +13 -9
- package/node_modules/@llblab/pi-telegram/docs/README.md +1 -0
- package/node_modules/@llblab/pi-telegram/docs/architecture.md +227 -23
- package/node_modules/@llblab/pi-telegram/docs/generative-apps.md +1 -1
- package/node_modules/@llblab/pi-telegram/docs/multi-instance-bus.md +70 -19
- package/node_modules/@llblab/pi-telegram/docs/outbound.md +6 -6
- package/node_modules/@llblab/pi-telegram/docs/public-api.md +13 -6
- package/node_modules/@llblab/pi-telegram/docs/ui-style.md +3 -1
- package/node_modules/@llblab/pi-telegram/index.ts +4 -1418
- package/node_modules/@llblab/pi-telegram/lib/agent-messages.ts +6 -3
- package/node_modules/@llblab/pi-telegram/lib/bindings.ts +46 -1
- package/node_modules/@llblab/pi-telegram/lib/bus-api.ts +32 -19
- package/node_modules/@llblab/pi-telegram/lib/bus-follower.ts +600 -135
- package/node_modules/@llblab/pi-telegram/lib/bus-leader.ts +962 -55
- package/node_modules/@llblab/pi-telegram/lib/bus.ts +355 -26
- package/node_modules/@llblab/pi-telegram/lib/channel-posts.ts +718 -0
- package/node_modules/@llblab/pi-telegram/lib/commands.ts +237 -11
- package/node_modules/@llblab/pi-telegram/lib/config.ts +242 -26
- package/node_modules/@llblab/pi-telegram/lib/extension.ts +1851 -0
- package/node_modules/@llblab/pi-telegram/lib/generative-apps.ts +20 -2
- package/node_modules/@llblab/pi-telegram/lib/journal.ts +2184 -126
- package/node_modules/@llblab/pi-telegram/lib/locks.ts +44 -2
- package/node_modules/@llblab/pi-telegram/lib/menu-settings.ts +152 -13
- package/node_modules/@llblab/pi-telegram/lib/outbound-attachments.ts +97 -10
- package/node_modules/@llblab/pi-telegram/lib/paths.ts +29 -0
- package/node_modules/@llblab/pi-telegram/lib/polling.ts +85 -17
- package/node_modules/@llblab/pi-telegram/lib/preview.ts +17 -0
- package/node_modules/@llblab/pi-telegram/lib/prompts.ts +6 -2
- package/node_modules/@llblab/pi-telegram/lib/queue.ts +118 -26
- package/node_modules/@llblab/pi-telegram/lib/rendering.ts +4 -1
- package/node_modules/@llblab/pi-telegram/lib/replies.ts +21 -2
- package/node_modules/@llblab/pi-telegram/lib/routing.ts +344 -112
- package/node_modules/@llblab/pi-telegram/lib/setup.ts +44 -4
- package/node_modules/@llblab/pi-telegram/lib/status.ts +51 -4
- package/node_modules/@llblab/pi-telegram/lib/sync.ts +308 -39
- package/node_modules/@llblab/pi-telegram/lib/telegram-api.ts +353 -22
- package/node_modules/@llblab/pi-telegram/lib/thread-cleanup-manager.ts +664 -0
- package/node_modules/@llblab/pi-telegram/lib/thread-display.ts +226 -0
- package/node_modules/@llblab/pi-telegram/lib/thread-naming.ts +118 -0
- package/node_modules/@llblab/pi-telegram/lib/threads.ts +1686 -129
- package/node_modules/@llblab/pi-telegram/lib/turns.ts +7 -0
- package/node_modules/@llblab/pi-telegram/lib/updates.ts +1319 -97
- package/node_modules/@llblab/pi-telegram/lib/workspace-admission.ts +1643 -0
- package/node_modules/@llblab/pi-telegram/lib/workspace-retirement.ts +968 -0
- package/node_modules/@llblab/pi-telegram/lib/workspace-slots.ts +84 -0
- package/node_modules/@llblab/pi-telegram/package.json +1 -1
- package/node_modules/@llblab/pi-telegram/screenshot.png +0 -0
- package/node_modules/@llblab/pi-telegram/scripts/measure-bus.mjs +83 -0
- package/node_modules/@llblab/pi-telegram/scripts/measure-workspace.mjs +101 -0
- package/node_modules/@llblab/{skills → pi-telegram/skills}/show-me/SKILL.md +28 -6
- package/node_modules/@llblab/pi-telegram/skills/show-me/references/telegram-surfaces.md +43 -0
- package/node_modules/@llblab/pi-telegram/skills/telegram-bridge/references/delivery-and-threads.md +1 -1
- package/node_modules/@llblab/skills/package.json +2 -3
- package/package.json +6 -5
- /package/node_modules/@llblab/pi-telegram/lib/{logs.ts → logging.ts} +0 -0
|
@@ -1108,6 +1108,7 @@ export function createTelegramDirectDeliveryOwnershipChecker<
|
|
|
1108
1108
|
export interface TelegramLockedPollingStartOptions {
|
|
1109
1109
|
force?: boolean;
|
|
1110
1110
|
forceFreshLeaderThread?: boolean;
|
|
1111
|
+
requestedThreadName?: string;
|
|
1111
1112
|
election?: { expectedOwner?: TelegramLockEntry };
|
|
1112
1113
|
onAcquired?: () => Promise<void> | void;
|
|
1113
1114
|
}
|
|
@@ -1131,6 +1132,10 @@ export interface TelegramLockedPollingRuntime<
|
|
|
1131
1132
|
ctx: TContext,
|
|
1132
1133
|
owner: TelegramLockEntry,
|
|
1133
1134
|
) => boolean | undefined | Promise<boolean | undefined>;
|
|
1135
|
+
restoreFollowerWithOwner?: (
|
|
1136
|
+
ctx: TContext,
|
|
1137
|
+
owner: TelegramLockEntry,
|
|
1138
|
+
) => boolean | undefined | Promise<boolean | undefined>;
|
|
1134
1139
|
stopFollowerRegistration?: () => void;
|
|
1135
1140
|
}
|
|
1136
1141
|
|
|
@@ -1139,6 +1144,7 @@ export interface TelegramLockedPollingRuntimeDeps<
|
|
|
1139
1144
|
> {
|
|
1140
1145
|
lock: TelegramLockRuntime<TContext>;
|
|
1141
1146
|
hasBotToken: () => boolean;
|
|
1147
|
+
getBotTokenDiagnostic?: () => string | undefined;
|
|
1142
1148
|
canStartPolling?: (ctx: TContext) => boolean;
|
|
1143
1149
|
isContextCurrent?: (ctx: TContext) => boolean;
|
|
1144
1150
|
formatStartBlockedMessage?: (ctx: TContext) => string;
|
|
@@ -1151,6 +1157,10 @@ export interface TelegramLockedPollingRuntimeDeps<
|
|
|
1151
1157
|
ctx: TContext,
|
|
1152
1158
|
owner: TelegramLockEntry,
|
|
1153
1159
|
) => boolean | undefined | Promise<boolean | undefined>;
|
|
1160
|
+
restoreFollowerWithOwner?: (
|
|
1161
|
+
ctx: TContext,
|
|
1162
|
+
owner: TelegramLockEntry,
|
|
1163
|
+
) => boolean | undefined | Promise<boolean | undefined>;
|
|
1154
1164
|
stopFollowerRegistration?: () => void;
|
|
1155
1165
|
onTransportAvailabilityChanged?: () => void;
|
|
1156
1166
|
transportMonitor?: { start: (ctx: TContext) => void; stop: () => void };
|
|
@@ -1198,6 +1208,7 @@ export function createTelegramLockedPollingRuntime<
|
|
|
1198
1208
|
stopOwnershipWatcher();
|
|
1199
1209
|
if (sessionAutoStartRun) {
|
|
1200
1210
|
await sessionAutoStartRun;
|
|
1211
|
+
deps.stopFollowerRegistration?.();
|
|
1201
1212
|
}
|
|
1202
1213
|
if (ownershipStop) {
|
|
1203
1214
|
await ownershipStop;
|
|
@@ -1295,7 +1306,11 @@ export function createTelegramLockedPollingRuntime<
|
|
|
1295
1306
|
return {
|
|
1296
1307
|
start: async (ctx, options = {}) => {
|
|
1297
1308
|
if (!deps.hasBotToken()) {
|
|
1298
|
-
return {
|
|
1309
|
+
return {
|
|
1310
|
+
ok: false,
|
|
1311
|
+
message:
|
|
1312
|
+
deps.getBotTokenDiagnostic?.() ?? "Telegram bot is not configured.",
|
|
1313
|
+
};
|
|
1299
1314
|
}
|
|
1300
1315
|
if (!canStartPolling(ctx)) {
|
|
1301
1316
|
return { ok: false, message: formatStartBlockedMessage(ctx) };
|
|
@@ -1452,10 +1467,14 @@ export function createTelegramLockedPollingRuntime<
|
|
|
1452
1467
|
const canHandoffSameProcess =
|
|
1453
1468
|
state?.kind === "active-here" &&
|
|
1454
1469
|
(!state.lock.cwd || state.lock.cwd === ctx.cwd);
|
|
1470
|
+
const canRestoreRememberedFollower =
|
|
1471
|
+
state?.kind === "active-elsewhere" &&
|
|
1472
|
+
deps.restoreFollowerWithOwner !== undefined;
|
|
1455
1473
|
if (
|
|
1456
1474
|
!ownsCurrentLock &&
|
|
1457
1475
|
!canResumeStaleSameCwd &&
|
|
1458
|
-
!canHandoffSameProcess
|
|
1476
|
+
!canHandoffSameProcess &&
|
|
1477
|
+
!canRestoreRememberedFollower
|
|
1459
1478
|
) {
|
|
1460
1479
|
return;
|
|
1461
1480
|
}
|
|
@@ -1466,11 +1485,27 @@ export function createTelegramLockedPollingRuntime<
|
|
|
1466
1485
|
const startedAtMs = Date.now();
|
|
1467
1486
|
deps.recordRuntimeEvent?.("lock", "Telegram auto-start scheduled", {
|
|
1468
1487
|
phase: "auto-start-scheduled",
|
|
1488
|
+
mode: canRestoreRememberedFollower ? "follower-restore" : "leader",
|
|
1469
1489
|
});
|
|
1470
1490
|
const run = (async () => {
|
|
1471
1491
|
await new Promise((resolve) => setTimeout(resolve, 0));
|
|
1472
1492
|
if (ownershipStop) await ownershipStop;
|
|
1473
1493
|
if (!isCurrent()) return;
|
|
1494
|
+
if (canRestoreRememberedFollower && state?.kind === "active-elsewhere") {
|
|
1495
|
+
const restored = await deps.restoreFollowerWithOwner?.(
|
|
1496
|
+
ctx,
|
|
1497
|
+
state.lock,
|
|
1498
|
+
);
|
|
1499
|
+
if (!isCurrent() || !restored) return;
|
|
1500
|
+
deps.onTransportAvailabilityChanged?.();
|
|
1501
|
+
deps.updateStatus(ctx);
|
|
1502
|
+
deps.recordRuntimeEvent?.(
|
|
1503
|
+
"bus",
|
|
1504
|
+
"Telegram follower auto-connect completed",
|
|
1505
|
+
{ phase: "follower-auto-connect" },
|
|
1506
|
+
);
|
|
1507
|
+
return;
|
|
1508
|
+
}
|
|
1474
1509
|
if (canResumeStaleSameCwd || canHandoffSameProcess) {
|
|
1475
1510
|
const acquired = deps.lock.acquire(
|
|
1476
1511
|
ctx,
|
|
@@ -1505,6 +1540,13 @@ export function createTelegramLockedPollingRuntime<
|
|
|
1505
1540
|
return registered === true;
|
|
1506
1541
|
}
|
|
1507
1542
|
: undefined,
|
|
1543
|
+
restoreFollowerWithOwner: deps.restoreFollowerWithOwner
|
|
1544
|
+
? async (ctx, owner) => {
|
|
1545
|
+
const restored = await deps.restoreFollowerWithOwner?.(ctx, owner);
|
|
1546
|
+
if (restored) deps.updateStatus(ctx);
|
|
1547
|
+
return restored === true;
|
|
1548
|
+
}
|
|
1549
|
+
: undefined,
|
|
1508
1550
|
stopFollowerRegistration: deps.stopFollowerRegistration,
|
|
1509
1551
|
};
|
|
1510
1552
|
}
|
|
@@ -8,6 +8,7 @@ import type {
|
|
|
8
8
|
TelegramActivityVerbosity,
|
|
9
9
|
TelegramAssistantRenderingMode,
|
|
10
10
|
TelegramTimeMode,
|
|
11
|
+
TelegramThreadDisplayMode,
|
|
11
12
|
} from "./config.ts";
|
|
12
13
|
import type { TelegramInlineKeyboardMarkup } from "./keyboard.ts";
|
|
13
14
|
import type { TelegramModelMenuState } from "./menu-model.ts";
|
|
@@ -21,6 +22,7 @@ import type { TelegramVoiceReplyMode } from "./voice.ts";
|
|
|
21
22
|
export type TelegramSettingsMenuReplyMarkup = TelegramInlineKeyboardMarkup;
|
|
22
23
|
|
|
23
24
|
export interface TelegramSettingsStateDeps {
|
|
25
|
+
getThreadDisplayMode?: () => TelegramThreadDisplayMode | undefined;
|
|
24
26
|
areDraftPreviewsEnabled: () => boolean;
|
|
25
27
|
getAssistantRenderingMode: () => TelegramAssistantRenderingMode;
|
|
26
28
|
getActivityVerbosity: () => TelegramActivityVerbosity;
|
|
@@ -31,6 +33,7 @@ export interface TelegramSettingsStateDeps {
|
|
|
31
33
|
}
|
|
32
34
|
|
|
33
35
|
export interface TelegramSettingsMutationDeps extends TelegramSettingsStateDeps {
|
|
36
|
+
setThreadDisplayMode?: (mode: TelegramThreadDisplayMode) => Promise<void>;
|
|
34
37
|
setDraftPreviewsEnabled: (enabled: boolean) => Promise<void>;
|
|
35
38
|
setAssistantRenderingMode: (
|
|
36
39
|
mode: TelegramAssistantRenderingMode,
|
|
@@ -43,6 +46,11 @@ export interface TelegramSettingsMutationDeps extends TelegramSettingsStateDeps
|
|
|
43
46
|
) => Promise<void>;
|
|
44
47
|
setTimeInjectionMode: (mode: TelegramTimeMode) => Promise<void>;
|
|
45
48
|
setAutomaticThreadCleanupEnabled: (enabled: boolean) => Promise<void>;
|
|
49
|
+
reviewInactiveThreads?: () => Promise<{ count: number; operationId?: string }>;
|
|
50
|
+
cleanInactiveThreads?: (operationId: string) => Promise<{
|
|
51
|
+
deleted: number; outcomeUnknown: number; blocked?: number;
|
|
52
|
+
recovery?: "commit-ready" | "deletion-outcome-unknown" | "authority-blocked";
|
|
53
|
+
}>;
|
|
46
54
|
}
|
|
47
55
|
|
|
48
56
|
export interface TelegramSettingsMenuOpenDeps<
|
|
@@ -137,6 +145,7 @@ export interface TelegramSettingsMenuRuntimeDeps<
|
|
|
137
145
|
export const SETTINGS_MENU_TITLE = "<b>⚙️ Settings:</b>";
|
|
138
146
|
export const AUTOMATIC_THREAD_CLEANUP_SETTINGS_TITLE =
|
|
139
147
|
"<b>🧹 Thread cleanup:</b>";
|
|
148
|
+
export const INACTIVE_THREAD_REVIEW_TITLE = "<b>🔎 Inactive tabs review:</b>";
|
|
140
149
|
export const DRAFT_PREVIEWS_SETTINGS_TITLE = "<b>📝 Draft previews:</b>";
|
|
141
150
|
export const ASSISTANT_RENDERING_SETTINGS_TITLE =
|
|
142
151
|
"<b>🧾 Assistant rendering:</b>";
|
|
@@ -145,6 +154,7 @@ export const ACTIVITY_VERBOSITY_SETTINGS_TITLE =
|
|
|
145
154
|
export const TIME_INJECTION_MODE_SETTINGS_TITLE =
|
|
146
155
|
"<b>🕒 Time injection mode:</b>";
|
|
147
156
|
export const VOICE_REPLY_MODE_SETTINGS_TITLE = "<b>👄 Voice reply mode:</b>";
|
|
157
|
+
export const THREAD_DISPLAY_SETTINGS_TITLE = "<b>🧵 Thread display:</b>";
|
|
148
158
|
|
|
149
159
|
function getVoiceReplyModeLabel(mode: TelegramVoiceReplyMode): string {
|
|
150
160
|
return mode;
|
|
@@ -165,6 +175,18 @@ export function buildTelegramSettingsMenuText(): string {
|
|
|
165
175
|
return SETTINGS_MENU_TITLE;
|
|
166
176
|
}
|
|
167
177
|
|
|
178
|
+
export function buildThreadDisplaySettingsText(mode: TelegramThreadDisplayMode): string {
|
|
179
|
+
return [
|
|
180
|
+
`${THREAD_DISPLAY_SETTINGS_TITLE} <code>${mode}</code>`,
|
|
181
|
+
"",
|
|
182
|
+
"Choose how this bot profile labels Telegram tabs and Pi terminal status. Each slot is unique across this bot profile.",
|
|
183
|
+
"",
|
|
184
|
+
"<code>-</code> <code>letters</code> (default): show the unique slot, such as <b><i>A</i></b> or <b><i>B</i></b>.",
|
|
185
|
+
"<code>-</code> <code>directories</code>: show the directory, such as <b><i>extensions</i></b>; shared Workspaces keep slot suffixes, such as <b><i>extensions_a</i></b> and <b><i>extensions_c</i></b>.",
|
|
186
|
+
"A manual <code>/name Name</code> overrides this Thread display name until reset.",
|
|
187
|
+
].join("\n");
|
|
188
|
+
}
|
|
189
|
+
|
|
168
190
|
export function buildAutomaticThreadCleanupSettingsText(
|
|
169
191
|
enabled: boolean,
|
|
170
192
|
): string {
|
|
@@ -175,9 +197,35 @@ export function buildAutomaticThreadCleanupSettingsText(
|
|
|
175
197
|
"",
|
|
176
198
|
"<code>-</code> <code>on</code> (default): delete the bound thread and release Telegram authority on graceful quit.",
|
|
177
199
|
"<code>-</code> <code>off</code>: preserve the tab as a restart hint; manual <code>/telegram-disconnect</code> still confirms and deletes it.",
|
|
200
|
+
"",
|
|
201
|
+
"Review inactive tabs checks current owner and work evidence. Review never deletes tabs.",
|
|
178
202
|
].join("\n");
|
|
179
203
|
}
|
|
180
204
|
|
|
205
|
+
export function buildInactiveThreadReviewText(count: number): string {
|
|
206
|
+
return [INACTIVE_THREAD_REVIEW_TITLE, "",
|
|
207
|
+
`${count} proven inactive tab${count === 1 ? "" : "s"}.`,
|
|
208
|
+
"No tabs were deleted.",
|
|
209
|
+
"Deletion requires a separate confirmed Clean action.",
|
|
210
|
+
].join("\n");
|
|
211
|
+
}
|
|
212
|
+
|
|
213
|
+
export function buildInactiveThreadReviewReplyMarkup(
|
|
214
|
+
operationId?: string,
|
|
215
|
+
canCleanInactiveThreads = false,
|
|
216
|
+
): TelegramSettingsMenuReplyMarkup {
|
|
217
|
+
const validOperationId = typeof operationId === "string" &&
|
|
218
|
+
/^thread-cleanup:[a-f0-9]{32}$/u.test(operationId);
|
|
219
|
+
return { inline_keyboard: [
|
|
220
|
+
[{ text: "⬆️ Back to Thread cleanup",
|
|
221
|
+
callback_data: "settings:open:automatic-thread-cleanup" }],
|
|
222
|
+
...(canCleanInactiveThreads && validOperationId ? [[{
|
|
223
|
+
text: "🧹 Clean inactive tabs",
|
|
224
|
+
callback_data: `settings:clean:${operationId}`,
|
|
225
|
+
}]] : []),
|
|
226
|
+
] };
|
|
227
|
+
}
|
|
228
|
+
|
|
181
229
|
export function buildDraftPreviewsSettingsText(enabled: boolean): string {
|
|
182
230
|
return [
|
|
183
231
|
`${DRAFT_PREVIEWS_SETTINGS_TITLE} <code>${enabled ? "on" : "off"}</code>`,
|
|
@@ -259,6 +307,7 @@ export function buildTelegramSettingsMenuReplyMarkup(
|
|
|
259
307
|
voiceReplyModeConfigured = true,
|
|
260
308
|
automaticThreadCleanupEnabled = true,
|
|
261
309
|
activityVerbosity: TelegramActivityVerbosity = "quiet",
|
|
310
|
+
threadDisplayMode?: TelegramThreadDisplayMode,
|
|
262
311
|
): TelegramSettingsMenuReplyMarkup {
|
|
263
312
|
const hasRenderingMode =
|
|
264
313
|
assistantRenderingModeOrVoiceReplyMode === "rich" ||
|
|
@@ -317,6 +366,10 @@ export function buildTelegramSettingsMenuReplyMarkup(
|
|
|
317
366
|
callback_data: "settings:open:automatic-thread-cleanup",
|
|
318
367
|
},
|
|
319
368
|
];
|
|
369
|
+
if (threadDisplayMode) settingsButtons.push({
|
|
370
|
+
text: `🧵 Thread display: ${threadDisplayMode}`,
|
|
371
|
+
callback_data: "settings:open:thread-display",
|
|
372
|
+
});
|
|
320
373
|
if (sectionRegistry) {
|
|
321
374
|
const extensionRows = getTelegramExtensionSettingsRows(sectionRegistry);
|
|
322
375
|
settingsButtons.push(
|
|
@@ -349,6 +402,7 @@ export async function openTelegramSettingsMenu<
|
|
|
349
402
|
deps.isVoiceReplyModeConfigured(),
|
|
350
403
|
deps.isAutomaticThreadCleanupEnabled(),
|
|
351
404
|
deps.getActivityVerbosity(),
|
|
405
|
+
deps.getThreadDisplayMode?.(),
|
|
352
406
|
),
|
|
353
407
|
);
|
|
354
408
|
if (messageId === undefined) return;
|
|
@@ -357,8 +411,19 @@ export async function openTelegramSettingsMenu<
|
|
|
357
411
|
deps.storeModelMenuState(state);
|
|
358
412
|
}
|
|
359
413
|
|
|
414
|
+
export function buildThreadDisplaySettingsReplyMarkup(mode: TelegramThreadDisplayMode): TelegramSettingsMenuReplyMarkup {
|
|
415
|
+
return { inline_keyboard: [
|
|
416
|
+
[{ text: "⬆️ Back", callback_data: "settings:list" }],
|
|
417
|
+
...(["letters", "directories"] as const).map((value) => [{
|
|
418
|
+
text: `${mode === value ? "🟢 " : ""}${value}`,
|
|
419
|
+
callback_data: `settings:set:thread-display:${value}`,
|
|
420
|
+
}]),
|
|
421
|
+
] };
|
|
422
|
+
}
|
|
423
|
+
|
|
360
424
|
export function buildAutomaticThreadCleanupSettingsReplyMarkup(
|
|
361
425
|
enabled: boolean,
|
|
426
|
+
canReviewInactiveThreads = false,
|
|
362
427
|
): TelegramSettingsMenuReplyMarkup {
|
|
363
428
|
return {
|
|
364
429
|
inline_keyboard: [
|
|
@@ -373,6 +438,10 @@ export function buildAutomaticThreadCleanupSettingsReplyMarkup(
|
|
|
373
438
|
callback_data: "settings:set:automatic-thread-cleanup:off",
|
|
374
439
|
},
|
|
375
440
|
],
|
|
441
|
+
...(canReviewInactiveThreads ? [[{
|
|
442
|
+
text: "🔎 Review inactive tabs",
|
|
443
|
+
callback_data: "settings:review:inactive-threads",
|
|
444
|
+
}]] : []),
|
|
376
445
|
],
|
|
377
446
|
};
|
|
378
447
|
}
|
|
@@ -417,21 +486,16 @@ export function buildAssistantRenderingSettingsReplyMarkup(
|
|
|
417
486
|
export function buildActivityVerbositySettingsReplyMarkup(
|
|
418
487
|
verbosity: TelegramActivityVerbosity,
|
|
419
488
|
): TelegramSettingsMenuReplyMarkup {
|
|
420
|
-
const
|
|
421
|
-
"
|
|
422
|
-
|
|
423
|
-
|
|
424
|
-
"verbose",
|
|
425
|
-
];
|
|
489
|
+
const button = (value: TelegramActivityVerbosity) => ({
|
|
490
|
+
text: `${value === verbosity ? "🟢 " : ""}${value}`,
|
|
491
|
+
callback_data: `settings:set:activity-verbosity:${value}`,
|
|
492
|
+
});
|
|
426
493
|
return {
|
|
427
494
|
inline_keyboard: [
|
|
428
495
|
[{ text: "⬆️ Back", callback_data: "settings:list" }],
|
|
429
|
-
|
|
430
|
-
|
|
431
|
-
|
|
432
|
-
callback_data: `settings:set:activity-verbosity:${value}`,
|
|
433
|
-
},
|
|
434
|
-
]),
|
|
496
|
+
[button("quiet")],
|
|
497
|
+
[button("thinking"), button("tools")],
|
|
498
|
+
[button("verbose")],
|
|
435
499
|
],
|
|
436
500
|
};
|
|
437
501
|
}
|
|
@@ -487,6 +551,7 @@ export async function updateTelegramSettingsMenuMessage(
|
|
|
487
551
|
deps.isVoiceReplyModeConfigured(),
|
|
488
552
|
deps.isAutomaticThreadCleanupEnabled(),
|
|
489
553
|
deps.getActivityVerbosity(),
|
|
554
|
+
deps.getThreadDisplayMode?.(),
|
|
490
555
|
),
|
|
491
556
|
);
|
|
492
557
|
}
|
|
@@ -497,7 +562,7 @@ export async function updateAutomaticThreadCleanupSettingsMessage(
|
|
|
497
562
|
const enabled = deps.isAutomaticThreadCleanupEnabled();
|
|
498
563
|
await deps.updateSettingsMessage(
|
|
499
564
|
buildAutomaticThreadCleanupSettingsText(enabled),
|
|
500
|
-
buildAutomaticThreadCleanupSettingsReplyMarkup(enabled),
|
|
565
|
+
buildAutomaticThreadCleanupSettingsReplyMarkup(enabled, !!deps.reviewInactiveThreads),
|
|
501
566
|
);
|
|
502
567
|
}
|
|
503
568
|
|
|
@@ -558,6 +623,36 @@ export async function handleTelegramSettingsMenuCallbackAction(
|
|
|
558
623
|
deps: TelegramSettingsMenuCallbackDeps,
|
|
559
624
|
): Promise<boolean> {
|
|
560
625
|
if (!data?.startsWith("settings:")) return false;
|
|
626
|
+
if (data === "settings:open:thread-display" || data.startsWith("settings:set:thread-display:")) {
|
|
627
|
+
if (!deps.getThreadDisplayMode?.() || !deps.setThreadDisplayMode) {
|
|
628
|
+
await deps.answerCallbackQuery(callbackQueryId, "Thread display requires Threaded Mode and a connected instance.");
|
|
629
|
+
return true;
|
|
630
|
+
}
|
|
631
|
+
if (data.startsWith("settings:set:thread-display:")) {
|
|
632
|
+
const mode = data.slice("settings:set:thread-display:".length);
|
|
633
|
+
if (mode !== "letters" && mode !== "names" && mode !== "directories") {
|
|
634
|
+
await deps.answerCallbackQuery(callbackQueryId, "Unknown Thread display mode.");
|
|
635
|
+
return true;
|
|
636
|
+
}
|
|
637
|
+
try {
|
|
638
|
+
await deps.setThreadDisplayMode(mode);
|
|
639
|
+
} catch {
|
|
640
|
+
await deps.answerCallbackQuery(callbackQueryId, "Thread display was not fully applied. The preference may be saved; retry after checking the leader.");
|
|
641
|
+
return true;
|
|
642
|
+
}
|
|
643
|
+
}
|
|
644
|
+
const mode = deps.getThreadDisplayMode();
|
|
645
|
+
if (!mode) {
|
|
646
|
+
await deps.answerCallbackQuery(callbackQueryId, "Threaded Mode is no longer available.");
|
|
647
|
+
return true;
|
|
648
|
+
}
|
|
649
|
+
await deps.updateSettingsMessage(
|
|
650
|
+
buildThreadDisplaySettingsText(mode),
|
|
651
|
+
buildThreadDisplaySettingsReplyMarkup(mode),
|
|
652
|
+
);
|
|
653
|
+
await deps.answerCallbackQuery(callbackQueryId);
|
|
654
|
+
return true;
|
|
655
|
+
}
|
|
561
656
|
if (data === "settings:list") {
|
|
562
657
|
await updateTelegramSettingsMenuMessage(deps, deps.sectionRegistry);
|
|
563
658
|
await deps.answerCallbackQuery(callbackQueryId);
|
|
@@ -692,6 +787,44 @@ export async function handleTelegramSettingsMenuCallbackAction(
|
|
|
692
787
|
return true;
|
|
693
788
|
}
|
|
694
789
|
}
|
|
790
|
+
if (data === "settings:review:inactive-threads") {
|
|
791
|
+
if (!deps.reviewInactiveThreads) {
|
|
792
|
+
await deps.answerCallbackQuery(callbackQueryId, "Inactive tab review is unavailable.");
|
|
793
|
+
return true;
|
|
794
|
+
}
|
|
795
|
+
try {
|
|
796
|
+
const review = await deps.reviewInactiveThreads();
|
|
797
|
+
if (review.count === 0) {
|
|
798
|
+
await deps.answerCallbackQuery(callbackQueryId, "No proven inactive tabs.");
|
|
799
|
+
} else {
|
|
800
|
+
await deps.updateSettingsMessage(buildInactiveThreadReviewText(review.count),
|
|
801
|
+
buildInactiveThreadReviewReplyMarkup(review.operationId, !!deps.cleanInactiveThreads));
|
|
802
|
+
await deps.answerCallbackQuery(callbackQueryId, "Review prepared. No tabs were deleted.");
|
|
803
|
+
}
|
|
804
|
+
} catch {
|
|
805
|
+
await deps.answerCallbackQuery(callbackQueryId, "Could not safely review inactive tabs.");
|
|
806
|
+
}
|
|
807
|
+
return true;
|
|
808
|
+
}
|
|
809
|
+
if (data.startsWith("settings:clean:")) {
|
|
810
|
+
const operationId = data.slice("settings:clean:".length);
|
|
811
|
+
if (!/^thread-cleanup:[a-f0-9]{32}$/u.test(operationId) || !deps.cleanInactiveThreads) {
|
|
812
|
+
await deps.answerCallbackQuery(callbackQueryId, "Cleanup confirmation is unavailable or stale.");
|
|
813
|
+
return true;
|
|
814
|
+
}
|
|
815
|
+
try {
|
|
816
|
+
const result = await deps.cleanInactiveThreads(operationId);
|
|
817
|
+
await deps.answerCallbackQuery(callbackQueryId,
|
|
818
|
+
`Deleted: ${result.deleted}. Outcome unknown: ${result.outcomeUnknown}.` +
|
|
819
|
+
(result.blocked ? ` Blocked: ${result.blocked}.` : "") +
|
|
820
|
+
(result.recovery === "commit-ready" ? " Recovery: safe local commit pending." :
|
|
821
|
+
result.recovery === "deletion-outcome-unknown" ? " Recovery: deletion outcome unknown; no retry." :
|
|
822
|
+
result.recovery === "authority-blocked" ? " Recovery: cleanup authority unavailable." : ""));
|
|
823
|
+
} catch {
|
|
824
|
+
await deps.answerCallbackQuery(callbackQueryId, "Cleanup could not be safely completed.");
|
|
825
|
+
}
|
|
826
|
+
return true;
|
|
827
|
+
}
|
|
695
828
|
if (
|
|
696
829
|
data === "settings:set:automatic-thread-cleanup:on" ||
|
|
697
830
|
data === "settings:set:automatic-thread-cleanup:off"
|
|
@@ -729,6 +862,7 @@ export function createTelegramSettingsMenuRuntime<
|
|
|
729
862
|
isVoiceReplyModeConfigured: deps.isVoiceReplyModeConfigured,
|
|
730
863
|
getTimeInjectionMode: deps.getTimeInjectionMode,
|
|
731
864
|
isAutomaticThreadCleanupEnabled: deps.isAutomaticThreadCleanupEnabled,
|
|
865
|
+
getThreadDisplayMode: deps.getThreadDisplayMode,
|
|
732
866
|
sendSettingsMenu: (state, text, replyMarkup) =>
|
|
733
867
|
deps.sendInteractiveMessage(
|
|
734
868
|
state.chatId,
|
|
@@ -752,6 +886,7 @@ export function createTelegramSettingsMenuRuntime<
|
|
|
752
886
|
isVoiceReplyModeConfigured: deps.isVoiceReplyModeConfigured,
|
|
753
887
|
getTimeInjectionMode: deps.getTimeInjectionMode,
|
|
754
888
|
isAutomaticThreadCleanupEnabled: deps.isAutomaticThreadCleanupEnabled,
|
|
889
|
+
getThreadDisplayMode: deps.getThreadDisplayMode,
|
|
755
890
|
updateSettingsMessage: (text, replyMarkup) =>
|
|
756
891
|
deps.editInteractiveMessage(
|
|
757
892
|
state.chatId,
|
|
@@ -795,12 +930,16 @@ export function createTelegramSettingsMenuRuntime<
|
|
|
795
930
|
isVoiceReplyModeConfigured: deps.isVoiceReplyModeConfigured,
|
|
796
931
|
getTimeInjectionMode: deps.getTimeInjectionMode,
|
|
797
932
|
isAutomaticThreadCleanupEnabled: deps.isAutomaticThreadCleanupEnabled,
|
|
933
|
+
getThreadDisplayMode: deps.getThreadDisplayMode,
|
|
934
|
+
setThreadDisplayMode: deps.setThreadDisplayMode,
|
|
798
935
|
setDraftPreviewsEnabled: deps.setDraftPreviewsEnabled,
|
|
799
936
|
setAssistantRenderingMode: deps.setAssistantRenderingMode,
|
|
800
937
|
setActivityVerbosity: deps.setActivityVerbosity,
|
|
801
938
|
setVoiceReplyMode: deps.setVoiceReplyMode,
|
|
802
939
|
setTimeInjectionMode: deps.setTimeInjectionMode,
|
|
803
940
|
setAutomaticThreadCleanupEnabled: deps.setAutomaticThreadCleanupEnabled,
|
|
941
|
+
...(deps.reviewInactiveThreads ? { reviewInactiveThreads: deps.reviewInactiveThreads } : {}),
|
|
942
|
+
...(deps.cleanInactiveThreads ? { cleanInactiveThreads: deps.cleanInactiveThreads } : {}),
|
|
804
943
|
updateSettingsMessage: (text, replyMarkup) =>
|
|
805
944
|
deps.editInteractiveMessage(
|
|
806
945
|
state.chatId,
|
|
@@ -13,6 +13,10 @@ import type {
|
|
|
13
13
|
TelegramBusAgentMessage,
|
|
14
14
|
TelegramBusAgentTargetSelector,
|
|
15
15
|
} from "./bus.ts";
|
|
16
|
+
import {
|
|
17
|
+
isTelegramChannelPostValidationError,
|
|
18
|
+
TelegramChannelPostValidationError,
|
|
19
|
+
} from "./channel-posts.ts";
|
|
16
20
|
import type { ExtensionAPI } from "./pi.ts";
|
|
17
21
|
import {
|
|
18
22
|
TELEGRAM_ATTACH_PROMPT_GUIDELINES,
|
|
@@ -99,6 +103,17 @@ export interface TelegramOutboundMessageToolRegistrationDeps extends TelegramOut
|
|
|
99
103
|
markdown: string,
|
|
100
104
|
options?: { replyMarkup?: unknown; target?: TelegramTarget },
|
|
101
105
|
) => Promise<number | undefined>;
|
|
106
|
+
sendChannelMarkdownMessage?: (
|
|
107
|
+
channel: number | string,
|
|
108
|
+
markdown: string,
|
|
109
|
+
options: { operationId: string; replyMarkup?: unknown },
|
|
110
|
+
) => Promise<number | undefined>;
|
|
111
|
+
sendChannelMediaMessage?: (
|
|
112
|
+
channel: number | string,
|
|
113
|
+
mediaPath: string,
|
|
114
|
+
markdown: string,
|
|
115
|
+
options: { operationId: string; replyMarkup?: unknown },
|
|
116
|
+
) => Promise<number | undefined>;
|
|
102
117
|
}
|
|
103
118
|
|
|
104
119
|
export interface TelegramQueuedOutboundAttachmentView {
|
|
@@ -177,7 +192,7 @@ export function planTelegramRichOutboundAttachment(options: {
|
|
|
177
192
|
if (!mediaType) return undefined;
|
|
178
193
|
const mediaId = "artifact";
|
|
179
194
|
const richMessage = {
|
|
180
|
-
markdown:
|
|
195
|
+
markdown: `\n\n${normalizeTelegramNativeMarkdown(options.markdown)}`,
|
|
181
196
|
media: [
|
|
182
197
|
{
|
|
183
198
|
id: mediaId,
|
|
@@ -187,7 +202,6 @@ export function planTelegramRichOutboundAttachment(options: {
|
|
|
187
202
|
},
|
|
188
203
|
},
|
|
189
204
|
],
|
|
190
|
-
skip_entity_detection: true,
|
|
191
205
|
};
|
|
192
206
|
return {
|
|
193
207
|
method: "sendRichMessage",
|
|
@@ -347,7 +361,7 @@ function formatTelegramOutboundAttachmentToolResultText(
|
|
|
347
361
|
return ["", `${verb} ${count} Telegram attachment(s).`].join("\n");
|
|
348
362
|
}
|
|
349
363
|
|
|
350
|
-
function formatTelegramOutboundMessageToolResultText(chatId: number): string {
|
|
364
|
+
function formatTelegramOutboundMessageToolResultText(chatId: number | string): string {
|
|
351
365
|
return ["", `Sent Telegram message to ${chatId}.`].join("\n");
|
|
352
366
|
}
|
|
353
367
|
|
|
@@ -513,19 +527,32 @@ export function registerTelegramOutboundMessageTool(
|
|
|
513
527
|
name: "telegram_message",
|
|
514
528
|
label: "Telegram Message",
|
|
515
529
|
description:
|
|
516
|
-
"Send
|
|
530
|
+
"Send Markdown text directly to the paired/default Telegram chat, an exact channel chat_id, or an explicit live target. Channel delivery supports one optional local photo or video upload with the text as its caption. Channel posting requires Telegram-granted bot permission. Hidden telegram_button comments become inline prompt buttons.",
|
|
517
531
|
promptSnippet: TELEGRAM_MESSAGE_PROMPT_SNIPPET,
|
|
518
532
|
promptGuidelines: [...TELEGRAM_MESSAGE_PROMPT_GUIDELINES],
|
|
519
533
|
parameters: Type.Object({
|
|
520
534
|
text: Type.String({ description: "Message text to send" }),
|
|
535
|
+
media: Type.Optional(
|
|
536
|
+
Type.String({
|
|
537
|
+
minLength: 1,
|
|
538
|
+
description:
|
|
539
|
+
"Local single-file channel upload: .jpg/.jpeg/.png/.webp photo or .mp4 video; the text becomes its caption (max 1024 characters). Albums and other media types are rejected.",
|
|
540
|
+
}),
|
|
541
|
+
),
|
|
521
542
|
chat_id: Type.Optional(
|
|
522
|
-
Type.
|
|
543
|
+
Type.Union([
|
|
544
|
+
Type.Number(),
|
|
545
|
+
Type.String({ pattern: "^@[A-Za-z0-9_]{5,32}$" }),
|
|
546
|
+
], { description: "Optional exact Telegram chat id or public channel @username" }),
|
|
523
547
|
),
|
|
524
548
|
thread_id: Type.Optional(
|
|
525
549
|
Type.Number({
|
|
526
550
|
description: "Optional Telegram topic thread id with chat_id",
|
|
527
551
|
}),
|
|
528
552
|
),
|
|
553
|
+
channel: Type.Optional(Type.Boolean({
|
|
554
|
+
description: "Set true only when a numeric chat_id is explicitly intended as a channel",
|
|
555
|
+
})),
|
|
529
556
|
thread: Type.Optional(
|
|
530
557
|
Type.Union([Type.String(), Type.Number()], {
|
|
531
558
|
description:
|
|
@@ -533,10 +560,13 @@ export function registerTelegramOutboundMessageTool(
|
|
|
533
560
|
}),
|
|
534
561
|
),
|
|
535
562
|
}),
|
|
536
|
-
async execute(
|
|
563
|
+
async execute(toolCallId, params) {
|
|
537
564
|
try {
|
|
538
565
|
return await sendTelegramOutboundMessage({
|
|
539
566
|
text: params.text,
|
|
567
|
+
media: params.media,
|
|
568
|
+
operationId: toolCallId,
|
|
569
|
+
channel: params.channel,
|
|
540
570
|
chatId: params.chat_id,
|
|
541
571
|
threadId: params.thread_id,
|
|
542
572
|
agentThread: params.thread,
|
|
@@ -548,10 +578,17 @@ export function registerTelegramOutboundMessageTool(
|
|
|
548
578
|
canSendDirect: deps.canSendDirect,
|
|
549
579
|
planMessage: deps.planMessage,
|
|
550
580
|
sendMarkdownMessage: deps.sendMarkdownMessage,
|
|
581
|
+
sendChannelMarkdownMessage: deps.sendChannelMarkdownMessage,
|
|
582
|
+
sendChannelMediaMessage: deps.sendChannelMediaMessage,
|
|
551
583
|
});
|
|
552
584
|
} catch (error) {
|
|
553
|
-
|
|
554
|
-
|
|
585
|
+
const isChannelTarget = typeof params.chat_id === "string" || params.channel === true;
|
|
586
|
+
const reportableError = isChannelTarget &&
|
|
587
|
+
!isTelegramChannelPostValidationError(error)
|
|
588
|
+
? new Error("Telegram channel publication failed; inspect the retained local record before retrying.")
|
|
589
|
+
: error;
|
|
590
|
+
deps.recordRuntimeEvent?.("message", reportableError, { phase: "direct" });
|
|
591
|
+
throw formatTelegramOutboundToolError(reportableError);
|
|
555
592
|
}
|
|
556
593
|
},
|
|
557
594
|
});
|
|
@@ -755,7 +792,10 @@ export async function deliverTelegramGuestCachedAttachment(options: {
|
|
|
755
792
|
|
|
756
793
|
export async function sendTelegramOutboundMessage(options: {
|
|
757
794
|
text: string;
|
|
758
|
-
|
|
795
|
+
media?: string;
|
|
796
|
+
operationId?: string;
|
|
797
|
+
channel?: boolean;
|
|
798
|
+
chatId?: number | string;
|
|
759
799
|
threadId?: number;
|
|
760
800
|
agentThread?: string | number;
|
|
761
801
|
target?: TelegramTarget;
|
|
@@ -775,12 +815,59 @@ export async function sendTelegramOutboundMessage(options: {
|
|
|
775
815
|
markdown: string,
|
|
776
816
|
options?: { replyMarkup?: unknown; target?: TelegramTarget },
|
|
777
817
|
) => Promise<number | undefined>;
|
|
818
|
+
sendChannelMarkdownMessage?: (
|
|
819
|
+
channel: number | string,
|
|
820
|
+
markdown: string,
|
|
821
|
+
options: { operationId: string; replyMarkup?: unknown },
|
|
822
|
+
) => Promise<number | undefined>;
|
|
823
|
+
sendChannelMediaMessage?: (
|
|
824
|
+
channel: number | string,
|
|
825
|
+
mediaPath: string,
|
|
826
|
+
markdown: string,
|
|
827
|
+
options: { operationId: string; replyMarkup?: unknown },
|
|
828
|
+
) => Promise<number | undefined>;
|
|
778
829
|
}): Promise<{
|
|
779
830
|
content: Array<{ type: "text"; text: string }>;
|
|
780
|
-
details: { chatId: number; messageId?: number };
|
|
831
|
+
details: { chatId: number | string; messageId?: number };
|
|
781
832
|
}> {
|
|
782
833
|
assertTelegramDirectDeliveryAllowed(options.canSendDirect);
|
|
783
834
|
const activeTurn = options.getActiveTurn?.();
|
|
835
|
+
if (typeof options.chatId === "string" || options.channel === true) {
|
|
836
|
+
if (!((typeof options.chatId === "string" && /^@[A-Za-z0-9_]{5,32}$/u.test(options.chatId)) ||
|
|
837
|
+
(typeof options.chatId === "number" && Number.isSafeInteger(options.chatId) && options.chatId < 0)) ||
|
|
838
|
+
options.threadId !== undefined ||
|
|
839
|
+
options.target !== undefined || options.agentThread !== undefined) {
|
|
840
|
+
throw new Error("Telegram channel delivery requires one exact @username or negative numeric channel ID without a thread target.");
|
|
841
|
+
}
|
|
842
|
+
const plan = options.planMessage(options.text);
|
|
843
|
+
if (options.media !== undefined) {
|
|
844
|
+
if (!options.sendChannelMediaMessage || !options.operationId) {
|
|
845
|
+
throw new TelegramChannelPostValidationError(
|
|
846
|
+
"Telegram channel media delivery requires direct leader transport ownership and operation identity.",
|
|
847
|
+
);
|
|
848
|
+
}
|
|
849
|
+
const messageId = await options.sendChannelMediaMessage(
|
|
850
|
+
options.chatId, options.media, plan.markdown,
|
|
851
|
+
{ operationId: options.operationId, replyMarkup: plan.replyMarkup });
|
|
852
|
+
return { content: [{ type: "text",
|
|
853
|
+
text: formatTelegramOutboundMessageToolResultText(options.chatId) }],
|
|
854
|
+
details: { chatId: options.chatId, messageId } };
|
|
855
|
+
}
|
|
856
|
+
if (!options.sendChannelMarkdownMessage || !options.operationId) {
|
|
857
|
+
throw new Error("Telegram channel delivery requires direct leader transport ownership and operation identity.");
|
|
858
|
+
}
|
|
859
|
+
const messageId = await options.sendChannelMarkdownMessage(
|
|
860
|
+
options.chatId, plan.markdown, { operationId: options.operationId,
|
|
861
|
+
replyMarkup: plan.replyMarkup });
|
|
862
|
+
return { content: [{ type: "text",
|
|
863
|
+
text: formatTelegramOutboundMessageToolResultText(options.chatId) }],
|
|
864
|
+
details: { chatId: options.chatId, messageId } };
|
|
865
|
+
}
|
|
866
|
+
if (options.media !== undefined) {
|
|
867
|
+
throw new TelegramChannelPostValidationError(
|
|
868
|
+
"telegram_message media uploads require channel delivery with an exact @username or negative numeric channel ID and channel: true.",
|
|
869
|
+
);
|
|
870
|
+
}
|
|
784
871
|
const requestedAgentSelector: TelegramBusAgentTargetSelector | undefined =
|
|
785
872
|
options.agentThread !== undefined
|
|
786
873
|
? typeof options.agentThread === "number"
|
|
@@ -87,6 +87,35 @@ export function getTelegramDiagnosticsDisplayPaths(profileName?: string): {
|
|
|
87
87
|
};
|
|
88
88
|
}
|
|
89
89
|
|
|
90
|
+
/** Durable Workspace admission ledger (<agentDir>/tmp/telegram/workspace-admission[.<profile>].json). */
|
|
91
|
+
export function resolveTelegramWorkspaceAdmissionPath(
|
|
92
|
+
agentDir = resolveAgentDir(),
|
|
93
|
+
profileName?: string,
|
|
94
|
+
): string {
|
|
95
|
+
return resolveTelegramProfileTempFilePath(
|
|
96
|
+
"workspace-admission",
|
|
97
|
+
"json",
|
|
98
|
+
agentDir,
|
|
99
|
+
profileName,
|
|
100
|
+
);
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
/** Durable inactive Thread cleanup work-set journal. */
|
|
104
|
+
export function resolveTelegramThreadCleanupWorkPath(
|
|
105
|
+
agentDir = resolveAgentDir(),
|
|
106
|
+
profileName?: string,
|
|
107
|
+
): string {
|
|
108
|
+
return resolveTelegramProfileTempFilePath("thread-cleanup", "json", agentDir, profileName);
|
|
109
|
+
}
|
|
110
|
+
|
|
111
|
+
/** Durable agent-authored channel post journal. */
|
|
112
|
+
export function resolveTelegramChannelPostJournalPath(
|
|
113
|
+
agentDir = resolveAgentDir(),
|
|
114
|
+
profileName?: string,
|
|
115
|
+
): string {
|
|
116
|
+
return resolveTelegramProfileTempFilePath("channel-posts", "json", agentDir, profileName);
|
|
117
|
+
}
|
|
118
|
+
|
|
90
119
|
/** Durable inbound update journal (<agentDir>/tmp/telegram/inbox[.<profile>].json). */
|
|
91
120
|
export function resolveTelegramUpdateJournalPath(
|
|
92
121
|
agentDir = resolveAgentDir(),
|