@llblab/pi-telegram 0.23.3 → 0.24.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/AGENTS.md +4 -4
- package/BACKLOG.md +6 -8
- package/CHANGELOG.md +131 -274
- package/README.md +4 -5
- package/docs/README.md +0 -1
- package/docs/architecture.md +26 -15
- package/docs/multi-instance-bus.md +18 -12
- package/docs/public-api.md +10 -2
- package/docs/sections.md +1 -1
- package/docs/updates.md +1 -1
- package/lib/bindings.ts +1 -9
- package/lib/bus-transport.ts +7 -14
- package/lib/bus.ts +48 -19
- package/lib/command-templates.ts +36 -20
- package/lib/commands.ts +5 -2
- package/lib/config.ts +157 -65
- package/lib/inbound.ts +22 -26
- package/lib/lifecycle.ts +0 -13
- package/lib/locks.ts +59 -40
- package/lib/logs.ts +76 -18
- package/lib/media.ts +0 -9
- package/lib/menu-model.ts +5 -12
- package/lib/menu.ts +0 -19
- package/lib/outbound.ts +2 -12
- package/lib/paths.ts +9 -6
- package/lib/pi.ts +0 -4
- package/lib/queue.ts +0 -5
- package/lib/replies.ts +0 -23
- package/lib/runtime.ts +1 -2
- package/lib/sections.ts +1 -48
- package/lib/status.ts +26 -13
- package/lib/telegram-api.ts +0 -4
- package/lib/threads.ts +41 -8
- package/package.json +1 -1
- package/docs/locks.md +0 -156
package/lib/command-templates.ts
CHANGED
|
@@ -36,9 +36,7 @@ export interface CommandTemplateObjectConfig {
|
|
|
36
36
|
}
|
|
37
37
|
|
|
38
38
|
export type CommandTemplateValue =
|
|
39
|
-
|
|
|
40
|
-
| CommandTemplateConfig[]
|
|
41
|
-
| CommandTemplateObjectConfig;
|
|
39
|
+
string | CommandTemplateConfig[] | CommandTemplateObjectConfig;
|
|
42
40
|
|
|
43
41
|
export type CommandTemplateConfig = string | CommandTemplateObjectConfig;
|
|
44
42
|
|
|
@@ -90,12 +88,6 @@ const COMMAND_TEMPLATE_RISK_LABEL_ORDER: CommandTemplateRiskLabel[] = [
|
|
|
90
88
|
"risk.platform_specific",
|
|
91
89
|
];
|
|
92
90
|
|
|
93
|
-
export type CommandTemplateExecCommand = (
|
|
94
|
-
command: string,
|
|
95
|
-
args: string[],
|
|
96
|
-
options?: CommandTemplateExecOptions,
|
|
97
|
-
) => Promise<CommandTemplateExecResult>;
|
|
98
|
-
|
|
99
91
|
function normalizeCommandTemplateArgs(value: string[] | undefined): string[] {
|
|
100
92
|
if (!Array.isArray(value)) return [];
|
|
101
93
|
return value.map(String).map((item) => item.trim());
|
|
@@ -257,9 +249,18 @@ function getLeafCommandTemplateRiskLabels(
|
|
|
257
249
|
labels.add("risk.broad_fs_write");
|
|
258
250
|
}
|
|
259
251
|
if (
|
|
260
|
-
[
|
|
261
|
-
|
|
262
|
-
|
|
252
|
+
[
|
|
253
|
+
"curl",
|
|
254
|
+
"wget",
|
|
255
|
+
"ssh",
|
|
256
|
+
"scp",
|
|
257
|
+
"sftp",
|
|
258
|
+
"rsync",
|
|
259
|
+
"nc",
|
|
260
|
+
"ncat",
|
|
261
|
+
"telnet",
|
|
262
|
+
"ftp",
|
|
263
|
+
].includes(command) ||
|
|
263
264
|
(command === "git" &&
|
|
264
265
|
hasAnyArg(args, ["clone", "fetch", "pull", "push", "ls-remote"])) ||
|
|
265
266
|
["npm", "pnpm", "yarn", "pip", "cargo"].includes(command)
|
|
@@ -283,13 +284,32 @@ function getLeafCommandTemplateRiskLabels(
|
|
|
283
284
|
labels.add("risk.long_running");
|
|
284
285
|
}
|
|
285
286
|
if (
|
|
286
|
-
[
|
|
287
|
-
|
|
288
|
-
|
|
287
|
+
[
|
|
288
|
+
"systemctl",
|
|
289
|
+
"launchctl",
|
|
290
|
+
"osascript",
|
|
291
|
+
"open",
|
|
292
|
+
"xdg-open",
|
|
293
|
+
"powershell",
|
|
294
|
+
"pwsh",
|
|
295
|
+
"cmd.exe",
|
|
296
|
+
"apt",
|
|
297
|
+
"apt-get",
|
|
298
|
+
"dnf",
|
|
299
|
+
"yum",
|
|
300
|
+
"brew",
|
|
301
|
+
"pacman",
|
|
302
|
+
"apk",
|
|
303
|
+
"xclip",
|
|
304
|
+
"wl-copy",
|
|
305
|
+
].includes(command)
|
|
289
306
|
) {
|
|
290
307
|
labels.add("risk.platform_specific");
|
|
291
308
|
}
|
|
292
|
-
if (
|
|
309
|
+
if (
|
|
310
|
+
["pass", "gpg", "ssh-add"].includes(command) ||
|
|
311
|
+
hasSecretTouchingText(parts)
|
|
312
|
+
) {
|
|
293
313
|
labels.add("risk.secret_touching");
|
|
294
314
|
}
|
|
295
315
|
return sortRiskLabels(labels);
|
|
@@ -347,10 +367,6 @@ function pad(value: number, width: number): string {
|
|
|
347
367
|
return String(value).padStart(width, "0");
|
|
348
368
|
}
|
|
349
369
|
|
|
350
|
-
export function isCommandTemplateRepeatPlaceholder(name: string): boolean {
|
|
351
|
-
return /^_{0,6}(?:index|prev|next|repeat)$/.test(name);
|
|
352
|
-
}
|
|
353
|
-
|
|
354
370
|
export function getCommandTemplateRepeatDefaults(
|
|
355
371
|
index: number,
|
|
356
372
|
repeat: number,
|
package/lib/commands.ts
CHANGED
|
@@ -4,7 +4,10 @@
|
|
|
4
4
|
* Owns Telegram slash-command normalization, bot command metadata, and pi-side command registration behind runtime ports
|
|
5
5
|
*/
|
|
6
6
|
|
|
7
|
-
import {
|
|
7
|
+
import {
|
|
8
|
+
pairTelegramUserIfNeeded,
|
|
9
|
+
TELEGRAM_DEFAULT_PROFILE_NAME,
|
|
10
|
+
} from "./config.ts";
|
|
8
11
|
import type { ExtensionAPI, ExtensionCommandContext } from "./pi.ts";
|
|
9
12
|
import type { TelegramBridgeStatusLineOptions } from "./status.ts";
|
|
10
13
|
import {
|
|
@@ -321,7 +324,7 @@ function parseTelegramProfileArg(args: string): string | undefined {
|
|
|
321
324
|
const word = args.trim().split(/\s+/)[0];
|
|
322
325
|
if (!word || word.length === 0) return undefined;
|
|
323
326
|
if (word.startsWith("-")) return undefined;
|
|
324
|
-
return word;
|
|
327
|
+
return word === TELEGRAM_DEFAULT_PROFILE_NAME ? undefined : word;
|
|
325
328
|
}
|
|
326
329
|
|
|
327
330
|
function formatTelegramTakeoverTitle(ctx: ExtensionCommandContext): string {
|
package/lib/config.ts
CHANGED
|
@@ -15,7 +15,12 @@ import {
|
|
|
15
15
|
writeFileSync,
|
|
16
16
|
} from "node:fs";
|
|
17
17
|
import { chmod, mkdir, rename, writeFile } from "node:fs/promises";
|
|
18
|
-
import {
|
|
18
|
+
import {
|
|
19
|
+
resolveAgentDir,
|
|
20
|
+
resolveTelegramConfigPath,
|
|
21
|
+
TELEGRAM_DEFAULT_PROFILE_NAME,
|
|
22
|
+
} from "./paths.ts";
|
|
23
|
+
export { TELEGRAM_DEFAULT_PROFILE_NAME } from "./paths.ts";
|
|
19
24
|
|
|
20
25
|
import type { CommandTemplateObjectConfig } from "./command-templates.ts";
|
|
21
26
|
import type { TelegramInboundHandlerConfig } from "./inbound.ts";
|
|
@@ -52,10 +57,15 @@ export interface ResolvedTelegramTimeConfig {
|
|
|
52
57
|
export type TelegramAssistantRenderingMode = "rich" | "html";
|
|
53
58
|
|
|
54
59
|
export interface TelegramConfig {
|
|
60
|
+
/** @deprecated persisted identity belongs in profiles.default; retained for effective/legacy views */
|
|
55
61
|
botToken?: string;
|
|
62
|
+
/** @deprecated persisted identity belongs in profiles.default; retained for effective/legacy views */
|
|
56
63
|
botUsername?: string;
|
|
64
|
+
/** @deprecated persisted identity belongs in profiles.default; retained for effective/legacy views */
|
|
57
65
|
botId?: number;
|
|
66
|
+
/** @deprecated persisted identity belongs in profiles.default; retained for effective/legacy views */
|
|
58
67
|
allowedUserId?: number;
|
|
68
|
+
/** @deprecated persisted identity belongs in profiles.default; retained for effective/legacy views */
|
|
59
69
|
lastUpdateId?: number;
|
|
60
70
|
inboundHandlers?: TelegramInboundHandlerConfig[];
|
|
61
71
|
attachmentHandlers?: TelegramInboundHandlerConfig[];
|
|
@@ -77,7 +87,7 @@ export interface TelegramConfig {
|
|
|
77
87
|
sendTranscript?: boolean;
|
|
78
88
|
};
|
|
79
89
|
time?: TelegramTimeConfig;
|
|
80
|
-
/**
|
|
90
|
+
/** Canonical bot/session profiles, including profiles.default. */
|
|
81
91
|
profiles?: Record<string, TelegramBotProfile>;
|
|
82
92
|
}
|
|
83
93
|
|
|
@@ -98,7 +108,6 @@ export interface TelegramBotProfile {
|
|
|
98
108
|
/** Profile names must contain only lowercase ASCII letters and digits; max 32 chars. */
|
|
99
109
|
const TELEGRAM_PROFILE_NAME_PATTERN = /^[a-z0-9]{1,32}$/;
|
|
100
110
|
const TELEGRAM_RESERVED_PROFILE_NAMES: ReadonlySet<string> = new Set([
|
|
101
|
-
"default",
|
|
102
111
|
"main",
|
|
103
112
|
"active",
|
|
104
113
|
]);
|
|
@@ -110,41 +119,6 @@ export function isValidTelegramProfileName(name: string): boolean {
|
|
|
110
119
|
);
|
|
111
120
|
}
|
|
112
121
|
|
|
113
|
-
/**
|
|
114
|
-
* Resolve the effective config for a named (or default) profile.
|
|
115
|
-
* Returns bot/session fields from the named profile, falling back to
|
|
116
|
-
* top-level fields for the default profile. Shared bridge settings
|
|
117
|
-
* always come from the top level.
|
|
118
|
-
*/
|
|
119
|
-
export function resolveTelegramActiveProfile(
|
|
120
|
-
config: TelegramConfig,
|
|
121
|
-
profileName?: string,
|
|
122
|
-
): {
|
|
123
|
-
botToken?: string;
|
|
124
|
-
botUsername?: string;
|
|
125
|
-
botId?: number;
|
|
126
|
-
allowedUserId?: number;
|
|
127
|
-
lastUpdateId?: number;
|
|
128
|
-
} {
|
|
129
|
-
if (!profileName || !config.profiles?.[profileName]) {
|
|
130
|
-
return {
|
|
131
|
-
botToken: config.botToken,
|
|
132
|
-
botUsername: config.botUsername,
|
|
133
|
-
botId: config.botId,
|
|
134
|
-
allowedUserId: config.allowedUserId,
|
|
135
|
-
lastUpdateId: config.lastUpdateId,
|
|
136
|
-
};
|
|
137
|
-
}
|
|
138
|
-
const profile = config.profiles[profileName];
|
|
139
|
-
return {
|
|
140
|
-
botToken: profile.botToken,
|
|
141
|
-
botUsername: profile.botUsername,
|
|
142
|
-
botId: profile.botId,
|
|
143
|
-
allowedUserId: profile.allowedUserId,
|
|
144
|
-
lastUpdateId: profile.lastUpdateId,
|
|
145
|
-
};
|
|
146
|
-
}
|
|
147
|
-
|
|
148
122
|
/** List defined profile names. */
|
|
149
123
|
export function getTelegramProfileNames(config: TelegramConfig): string[] {
|
|
150
124
|
return Object.keys(config.profiles ?? {}).sort();
|
|
@@ -154,6 +128,7 @@ export interface TelegramConfigStore {
|
|
|
154
128
|
get: () => TelegramConfig;
|
|
155
129
|
getStoredConfig: () => TelegramConfig;
|
|
156
130
|
set: (config: TelegramConfig) => void;
|
|
131
|
+
setProfile: (profileName: string, profile: TelegramBotProfile) => void;
|
|
157
132
|
update: (mutate: (config: TelegramConfig) => void) => void;
|
|
158
133
|
activateProfile: (profileName: string | undefined) => boolean;
|
|
159
134
|
getActiveProfileName: () => string | undefined;
|
|
@@ -396,10 +371,91 @@ export function getTelegramProfileFields(
|
|
|
396
371
|
if (!token) return undefined;
|
|
397
372
|
return {
|
|
398
373
|
botToken: token,
|
|
399
|
-
|
|
400
|
-
|
|
401
|
-
|
|
402
|
-
|
|
374
|
+
...(config.botUsername !== undefined
|
|
375
|
+
? { botUsername: config.botUsername }
|
|
376
|
+
: {}),
|
|
377
|
+
...(config.botId !== undefined ? { botId: config.botId } : {}),
|
|
378
|
+
...(config.allowedUserId !== undefined
|
|
379
|
+
? { allowedUserId: config.allowedUserId }
|
|
380
|
+
: {}),
|
|
381
|
+
...(config.lastUpdateId !== undefined
|
|
382
|
+
? { lastUpdateId: config.lastUpdateId }
|
|
383
|
+
: {}),
|
|
384
|
+
};
|
|
385
|
+
}
|
|
386
|
+
|
|
387
|
+
function omitTelegramRootProfileFields(config: TelegramConfig): TelegramConfig {
|
|
388
|
+
const {
|
|
389
|
+
botToken: _botToken,
|
|
390
|
+
botUsername: _botUsername,
|
|
391
|
+
botId: _botId,
|
|
392
|
+
allowedUserId: _allowedUserId,
|
|
393
|
+
lastUpdateId: _lastUpdateId,
|
|
394
|
+
...sharedConfig
|
|
395
|
+
} = config;
|
|
396
|
+
return sharedConfig;
|
|
397
|
+
}
|
|
398
|
+
|
|
399
|
+
export function normalizeTelegramDefaultProfileConfig(config: TelegramConfig): {
|
|
400
|
+
config: TelegramConfig;
|
|
401
|
+
changed: boolean;
|
|
402
|
+
} {
|
|
403
|
+
const hasLegacyRootProfile = [
|
|
404
|
+
"botToken",
|
|
405
|
+
"botUsername",
|
|
406
|
+
"botId",
|
|
407
|
+
"allowedUserId",
|
|
408
|
+
"lastUpdateId",
|
|
409
|
+
].some((field) => Object.hasOwn(config, field));
|
|
410
|
+
if (!hasLegacyRootProfile) return { config, changed: false };
|
|
411
|
+
const canonicalProfile = config.profiles?.[TELEGRAM_DEFAULT_PROFILE_NAME];
|
|
412
|
+
const legacyToken = config.botToken?.trim();
|
|
413
|
+
if (Object.hasOwn(config, "botToken") && !legacyToken) {
|
|
414
|
+
throw new Error("Legacy Telegram default profile has no bot token");
|
|
415
|
+
}
|
|
416
|
+
const legacyProfile: Partial<TelegramBotProfile> = {
|
|
417
|
+
...(legacyToken ? { botToken: legacyToken } : {}),
|
|
418
|
+
...(config.botUsername !== undefined
|
|
419
|
+
? { botUsername: config.botUsername }
|
|
420
|
+
: {}),
|
|
421
|
+
...(config.botId !== undefined ? { botId: config.botId } : {}),
|
|
422
|
+
...(config.allowedUserId !== undefined
|
|
423
|
+
? { allowedUserId: config.allowedUserId }
|
|
424
|
+
: {}),
|
|
425
|
+
...(config.lastUpdateId !== undefined
|
|
426
|
+
? { lastUpdateId: config.lastUpdateId }
|
|
427
|
+
: {}),
|
|
428
|
+
};
|
|
429
|
+
if (!canonicalProfile && !legacyToken) {
|
|
430
|
+
throw new Error("Legacy Telegram default profile has no bot token");
|
|
431
|
+
}
|
|
432
|
+
const hasConflict = canonicalProfile
|
|
433
|
+
? Object.entries(legacyProfile).some(
|
|
434
|
+
([field, value]) =>
|
|
435
|
+
Object.hasOwn(canonicalProfile, field) &&
|
|
436
|
+
!configValuesEqual(
|
|
437
|
+
canonicalProfile[field as keyof TelegramBotProfile],
|
|
438
|
+
value,
|
|
439
|
+
),
|
|
440
|
+
)
|
|
441
|
+
: false;
|
|
442
|
+
if (hasConflict) {
|
|
443
|
+
throw new Error(
|
|
444
|
+
"Conflicting Telegram default profile identity at root and profiles.default",
|
|
445
|
+
);
|
|
446
|
+
}
|
|
447
|
+
const normalizedProfile: TelegramBotProfile = canonicalProfile
|
|
448
|
+
? { ...legacyProfile, ...canonicalProfile }
|
|
449
|
+
: (legacyProfile as TelegramBotProfile);
|
|
450
|
+
return {
|
|
451
|
+
config: {
|
|
452
|
+
...omitTelegramRootProfileFields(config),
|
|
453
|
+
profiles: {
|
|
454
|
+
...(config.profiles ?? {}),
|
|
455
|
+
[TELEGRAM_DEFAULT_PROFILE_NAME]: normalizedProfile,
|
|
456
|
+
},
|
|
457
|
+
},
|
|
458
|
+
changed: true,
|
|
403
459
|
};
|
|
404
460
|
}
|
|
405
461
|
|
|
@@ -407,16 +463,12 @@ function applyTelegramProfile(
|
|
|
407
463
|
config: TelegramConfig,
|
|
408
464
|
profileName: string | undefined,
|
|
409
465
|
): TelegramConfig {
|
|
410
|
-
|
|
411
|
-
const profile = config.profiles?.[
|
|
412
|
-
if (!profile) return config;
|
|
466
|
+
const effectiveProfileName = profileName ?? TELEGRAM_DEFAULT_PROFILE_NAME;
|
|
467
|
+
const profile = config.profiles?.[effectiveProfileName];
|
|
468
|
+
if (!profile) return omitTelegramRootProfileFields(config);
|
|
413
469
|
return {
|
|
414
|
-
...config,
|
|
415
|
-
|
|
416
|
-
botUsername: profile.botUsername,
|
|
417
|
-
botId: profile.botId,
|
|
418
|
-
allowedUserId: profile.allowedUserId,
|
|
419
|
-
lastUpdateId: profile.lastUpdateId,
|
|
470
|
+
...omitTelegramRootProfileFields(config),
|
|
471
|
+
...profile,
|
|
420
472
|
};
|
|
421
473
|
}
|
|
422
474
|
|
|
@@ -425,18 +477,13 @@ function storeTelegramEffectiveConfig(
|
|
|
425
477
|
nextConfig: TelegramConfig,
|
|
426
478
|
profileName: string | undefined,
|
|
427
479
|
): TelegramConfig {
|
|
428
|
-
|
|
480
|
+
const effectiveProfileName = profileName ?? TELEGRAM_DEFAULT_PROFILE_NAME;
|
|
429
481
|
const profile = getTelegramProfileFields(nextConfig);
|
|
430
482
|
const profiles = { ...(baseConfig.profiles ?? {}) };
|
|
431
|
-
if (profile) profiles[
|
|
432
|
-
else delete profiles[
|
|
483
|
+
if (profile) profiles[effectiveProfileName] = profile;
|
|
484
|
+
else delete profiles[effectiveProfileName];
|
|
433
485
|
return {
|
|
434
|
-
...nextConfig,
|
|
435
|
-
botToken: baseConfig.botToken,
|
|
436
|
-
botUsername: baseConfig.botUsername,
|
|
437
|
-
botId: baseConfig.botId,
|
|
438
|
-
allowedUserId: baseConfig.allowedUserId,
|
|
439
|
-
lastUpdateId: baseConfig.lastUpdateId,
|
|
486
|
+
...omitTelegramRootProfileFields(nextConfig),
|
|
440
487
|
profiles: Object.keys(profiles).length > 0 ? profiles : undefined,
|
|
441
488
|
};
|
|
442
489
|
}
|
|
@@ -444,7 +491,9 @@ function storeTelegramEffectiveConfig(
|
|
|
444
491
|
export function createTelegramConfigStore(
|
|
445
492
|
options: TelegramConfigStoreOptions = {},
|
|
446
493
|
): TelegramConfigStore {
|
|
447
|
-
let config: TelegramConfig =
|
|
494
|
+
let config: TelegramConfig = normalizeTelegramDefaultProfileConfig(
|
|
495
|
+
cloneTelegramConfig(options.initialConfig ?? {}),
|
|
496
|
+
).config;
|
|
448
497
|
let persistedConfig: TelegramConfig = {};
|
|
449
498
|
let mutationVersion = 0;
|
|
450
499
|
let persistQueue: Promise<void> = Promise.resolve();
|
|
@@ -465,14 +514,30 @@ export function createTelegramConfigStore(
|
|
|
465
514
|
get: getEffectiveConfig,
|
|
466
515
|
getStoredConfig: () => config,
|
|
467
516
|
set: setEffectiveConfig,
|
|
517
|
+
setProfile: (profileName, profile) => {
|
|
518
|
+
config = {
|
|
519
|
+
...omitTelegramRootProfileFields(config),
|
|
520
|
+
profiles: {
|
|
521
|
+
...(config.profiles ?? {}),
|
|
522
|
+
[profileName]: cloneTelegramConfig(profile),
|
|
523
|
+
},
|
|
524
|
+
};
|
|
525
|
+
mutationVersion += 1;
|
|
526
|
+
},
|
|
468
527
|
update: (mutate) => {
|
|
469
528
|
const nextConfig = getEffectiveConfig();
|
|
470
529
|
mutate(nextConfig);
|
|
471
530
|
setEffectiveConfig(nextConfig);
|
|
472
531
|
},
|
|
473
532
|
activateProfile: (profileName) => {
|
|
474
|
-
|
|
475
|
-
|
|
533
|
+
const normalizedProfileName =
|
|
534
|
+
!profileName || profileName === TELEGRAM_DEFAULT_PROFILE_NAME
|
|
535
|
+
? undefined
|
|
536
|
+
: profileName;
|
|
537
|
+
if (normalizedProfileName && !config.profiles?.[normalizedProfileName]) {
|
|
538
|
+
return false;
|
|
539
|
+
}
|
|
540
|
+
activeProfileName = normalizedProfileName;
|
|
476
541
|
return true;
|
|
477
542
|
},
|
|
478
543
|
getActiveProfileName: () => activeProfileName,
|
|
@@ -491,7 +556,7 @@ export function createTelegramConfigStore(
|
|
|
491
556
|
setEffectiveConfig(nextConfig);
|
|
492
557
|
},
|
|
493
558
|
load: async () => {
|
|
494
|
-
|
|
559
|
+
const loadedConfig = await readTelegramConfig(configPath, {
|
|
495
560
|
onInvalidConfig: (recovery) => {
|
|
496
561
|
options.recordRuntimeEvent?.("config", recovery.error, {
|
|
497
562
|
phase: "load",
|
|
@@ -500,6 +565,31 @@ export function createTelegramConfigStore(
|
|
|
500
565
|
});
|
|
501
566
|
},
|
|
502
567
|
});
|
|
568
|
+
let normalized: ReturnType<typeof normalizeTelegramDefaultProfileConfig>;
|
|
569
|
+
try {
|
|
570
|
+
normalized = normalizeTelegramDefaultProfileConfig(loadedConfig);
|
|
571
|
+
} catch (error) {
|
|
572
|
+
options.recordRuntimeEvent?.("config", error, {
|
|
573
|
+
phase: "default-profile-normalize",
|
|
574
|
+
configPath,
|
|
575
|
+
});
|
|
576
|
+
throw error;
|
|
577
|
+
}
|
|
578
|
+
config = normalized.changed
|
|
579
|
+
? withTelegramFileTransaction(`${configPath}.transaction`, () => {
|
|
580
|
+
const latestConfig = readTelegramConfigForTransaction(configPath);
|
|
581
|
+
const latestNormalized =
|
|
582
|
+
normalizeTelegramDefaultProfileConfig(latestConfig);
|
|
583
|
+
if (latestNormalized.changed) {
|
|
584
|
+
writeTelegramConfigInTransaction(
|
|
585
|
+
agentDir,
|
|
586
|
+
configPath,
|
|
587
|
+
latestNormalized.config,
|
|
588
|
+
);
|
|
589
|
+
}
|
|
590
|
+
return latestNormalized.config;
|
|
591
|
+
})
|
|
592
|
+
: normalized.config;
|
|
503
593
|
persistedConfig = cloneTelegramConfig(config);
|
|
504
594
|
mutationVersion += 1;
|
|
505
595
|
},
|
|
@@ -522,7 +612,9 @@ export function createTelegramConfigStore(
|
|
|
522
612
|
desiredConfig as Record<string, unknown>,
|
|
523
613
|
latestConfig as Record<string, unknown>,
|
|
524
614
|
) as TelegramConfig;
|
|
525
|
-
|
|
615
|
+
if (!configValuesEqual(latestConfig, merged)) {
|
|
616
|
+
writeTelegramConfigInTransaction(agentDir, configPath, merged);
|
|
617
|
+
}
|
|
526
618
|
return merged;
|
|
527
619
|
},
|
|
528
620
|
);
|
package/lib/inbound.ts
CHANGED
|
@@ -23,8 +23,7 @@ const MAX_INBOUND_HANDLER_OUTPUT_LENGTH = 12_000;
|
|
|
23
23
|
const MAX_INBOUND_HANDLER_FAILURE_STREAM_LENGTH = 4_000;
|
|
24
24
|
|
|
25
25
|
type TelegramInboundCommandTemplateConfig =
|
|
26
|
-
|
|
|
27
|
-
| CommandTemplateObjectConfig;
|
|
26
|
+
string | CommandTemplateObjectConfig;
|
|
28
27
|
|
|
29
28
|
export interface TelegramInboundHandlerConfig {
|
|
30
29
|
match?: string | string[];
|
|
@@ -74,10 +73,6 @@ export interface TelegramInboundHandlerExecResult {
|
|
|
74
73
|
killed: boolean;
|
|
75
74
|
}
|
|
76
75
|
|
|
77
|
-
export interface TelegramInboundHandlerRuntimeContext {
|
|
78
|
-
cwd: string;
|
|
79
|
-
}
|
|
80
|
-
|
|
81
76
|
export interface TelegramInboundHandlerRuntimeDeps<TContext> {
|
|
82
77
|
getHandlers: () => TelegramInboundHandlerConfig[] | undefined;
|
|
83
78
|
execCommand: (
|
|
@@ -102,9 +97,7 @@ export interface TelegramInboundHandlerRuntime<TContext> {
|
|
|
102
97
|
}
|
|
103
98
|
|
|
104
99
|
export type TelegramInboundProgrammaticHandlerResult =
|
|
105
|
-
| string
|
|
106
|
-
| { text: string }
|
|
107
|
-
| undefined;
|
|
100
|
+
string | { text: string } | undefined;
|
|
108
101
|
|
|
109
102
|
export interface TelegramInboundProgrammaticHandlerInput {
|
|
110
103
|
kind: string;
|
|
@@ -179,12 +172,6 @@ export function getTelegramInboundProgrammaticHandlers(
|
|
|
179
172
|
];
|
|
180
173
|
}
|
|
181
174
|
|
|
182
|
-
export function hasTelegramInboundHandler(kind?: string): boolean {
|
|
183
|
-
const registry = getOrCreateInboundHandlerRegistry();
|
|
184
|
-
if (kind !== undefined) return (registry.handlers.get(kind)?.length ?? 0) > 0;
|
|
185
|
-
return Array.from(registry.handlers.values()).some((list) => list.length > 0);
|
|
186
|
-
}
|
|
187
|
-
|
|
188
175
|
export function clearTelegramInboundHandlers(): void {
|
|
189
176
|
getOrCreateInboundHandlerRegistry().handlers.clear();
|
|
190
177
|
}
|
|
@@ -281,13 +268,6 @@ export function findTelegramInboundHandlers(
|
|
|
281
268
|
);
|
|
282
269
|
}
|
|
283
270
|
|
|
284
|
-
export function findTelegramInboundHandler(
|
|
285
|
-
handlers: TelegramInboundHandlerConfig[] | undefined,
|
|
286
|
-
file: TelegramInboundHandlerFile,
|
|
287
|
-
): TelegramInboundHandlerConfig | undefined {
|
|
288
|
-
return findTelegramInboundHandlers(handlers, file)[0];
|
|
289
|
-
}
|
|
290
|
-
|
|
291
271
|
function hasInboundFilePlaceholder(value: string): boolean {
|
|
292
272
|
return /\{file\}/.test(value);
|
|
293
273
|
}
|
|
@@ -420,9 +400,13 @@ function formatTelegramInboundHandlerFailure(
|
|
|
420
400
|
`Inbound handler exited with code ${result.code}${result.killed ? " (killed)" : ""}`,
|
|
421
401
|
];
|
|
422
402
|
if (result.stderr.trim())
|
|
423
|
-
parts.push(
|
|
403
|
+
parts.push(
|
|
404
|
+
`stderr:\n${truncateTelegramInboundFailureStream(result.stderr)}`,
|
|
405
|
+
);
|
|
424
406
|
if (result.stdout.trim())
|
|
425
|
-
parts.push(
|
|
407
|
+
parts.push(
|
|
408
|
+
`stdout:\n${truncateTelegramInboundFailureStream(result.stdout)}`,
|
|
409
|
+
);
|
|
426
410
|
return parts.join("\n\n");
|
|
427
411
|
}
|
|
428
412
|
|
|
@@ -445,7 +429,13 @@ async function executeTelegramInboundHandlerInvocation(
|
|
|
445
429
|
cwd,
|
|
446
430
|
timeout,
|
|
447
431
|
...(typeof handler === "object" && handler.retry !== undefined
|
|
448
|
-
? {
|
|
432
|
+
? {
|
|
433
|
+
retry: resolveTelegramInboundNumericControlField(
|
|
434
|
+
handler.retry,
|
|
435
|
+
{},
|
|
436
|
+
"retry",
|
|
437
|
+
),
|
|
438
|
+
}
|
|
449
439
|
: {}),
|
|
450
440
|
...(stdin !== undefined ? { stdin } : {}),
|
|
451
441
|
});
|
|
@@ -519,7 +509,13 @@ async function executeTelegramTextHandlerInvocation(
|
|
|
519
509
|
timeout,
|
|
520
510
|
stdin: text,
|
|
521
511
|
...(typeof handler === "object" && handler.retry !== undefined
|
|
522
|
-
? {
|
|
512
|
+
? {
|
|
513
|
+
retry: resolveTelegramInboundNumericControlField(
|
|
514
|
+
handler.retry,
|
|
515
|
+
{},
|
|
516
|
+
"retry",
|
|
517
|
+
),
|
|
518
|
+
}
|
|
523
519
|
: {}),
|
|
524
520
|
});
|
|
525
521
|
if (result.code !== 0)
|
package/lib/lifecycle.ts
CHANGED
|
@@ -196,19 +196,6 @@ export function createTelegramSessionContextStore<TContext>(
|
|
|
196
196
|
};
|
|
197
197
|
}
|
|
198
198
|
|
|
199
|
-
export function createTelegramSessionContextTracker(
|
|
200
|
-
store: Pick<TelegramSessionContextStore<ExtensionContext>, "set" | "clear">,
|
|
201
|
-
): TelegramSessionLifecycleHooks {
|
|
202
|
-
return {
|
|
203
|
-
onSessionStart: async (_event, ctx) => {
|
|
204
|
-
store.set(ctx);
|
|
205
|
-
},
|
|
206
|
-
onSessionShutdown: async (_event, ctx) => {
|
|
207
|
-
store.clear(ctx);
|
|
208
|
-
},
|
|
209
|
-
};
|
|
210
|
-
}
|
|
211
|
-
|
|
212
199
|
export function createTelegramSessionGenerationFence(
|
|
213
200
|
store: TelegramSessionContextStore<ExtensionContext>,
|
|
214
201
|
hooks: TelegramSessionLifecycleHooks,
|