@llblab/pi-telegram 0.13.2 → 0.14.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/AGENTS.md +5 -3
- package/CHANGELOG.md +14 -12
- package/README.md +6 -6
- package/docs/architecture.md +6 -5
- package/docs/locks.md +4 -2
- package/docs/outbound.md +6 -3
- package/docs/public-api.md +7 -6
- package/docs/sections.md +4 -4
- package/index.ts +33 -18
- package/lib/bindings.ts +32 -3
- package/lib/command-templates.ts +155 -10
- package/lib/commands.ts +14 -12
- package/lib/lifecycle.ts +34 -0
- package/lib/locks.ts +16 -2
- package/lib/outbound-attachments.ts +253 -31
- package/lib/prompts.ts +4 -4
- package/lib/queue.ts +33 -32
- package/lib/routing.ts +7 -7
- package/lib/runtime.ts +45 -17
- package/lib/sections.ts +95 -31
- package/lib/status.ts +1 -1
- package/package.json +1 -1
package/lib/command-templates.ts
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
/**
|
|
2
|
-
* Command-template standard
|
|
3
|
-
* Zones:
|
|
4
|
-
* Owns
|
|
2
|
+
* Command-template execution standard.
|
|
3
|
+
* Zones: shell-free command parsing, placeholder expansion, local process execution, composition semantics
|
|
4
|
+
* Owns portable command-template parsing, expansion, risk checks, retries, timeouts, and direct execution.
|
|
5
5
|
*/
|
|
6
6
|
|
|
7
7
|
import { spawn } from "node:child_process";
|
|
@@ -10,7 +10,16 @@ import { isAbsolute, resolve } from "node:path";
|
|
|
10
10
|
|
|
11
11
|
export type CommandTemplateFailureScope = "continue" | "branch" | "root";
|
|
12
12
|
|
|
13
|
+
export interface CommandTemplateActorRecipeContext {
|
|
14
|
+
alias?: string;
|
|
15
|
+
file?: string;
|
|
16
|
+
name?: string;
|
|
17
|
+
path?: string;
|
|
18
|
+
role?: string;
|
|
19
|
+
}
|
|
20
|
+
|
|
13
21
|
export interface CommandTemplateObjectConfig {
|
|
22
|
+
actorRecipeContext?: CommandTemplateActorRecipeContext;
|
|
14
23
|
label?: string;
|
|
15
24
|
parallel?: boolean;
|
|
16
25
|
when?: boolean | string;
|
|
@@ -58,6 +67,29 @@ export interface CommandTemplateExecResult {
|
|
|
58
67
|
killed: boolean;
|
|
59
68
|
}
|
|
60
69
|
|
|
70
|
+
export type CommandTemplateRiskLabel =
|
|
71
|
+
| "risk.shell"
|
|
72
|
+
| "risk.eval"
|
|
73
|
+
| "risk.broad_fs_write"
|
|
74
|
+
| "risk.destructive_fs"
|
|
75
|
+
| "risk.network"
|
|
76
|
+
| "risk.external_side_effect"
|
|
77
|
+
| "risk.long_running"
|
|
78
|
+
| "risk.platform_specific"
|
|
79
|
+
| "risk.secret_touching";
|
|
80
|
+
|
|
81
|
+
const COMMAND_TEMPLATE_RISK_LABEL_ORDER: CommandTemplateRiskLabel[] = [
|
|
82
|
+
"risk.shell",
|
|
83
|
+
"risk.eval",
|
|
84
|
+
"risk.destructive_fs",
|
|
85
|
+
"risk.broad_fs_write",
|
|
86
|
+
"risk.external_side_effect",
|
|
87
|
+
"risk.secret_touching",
|
|
88
|
+
"risk.network",
|
|
89
|
+
"risk.long_running",
|
|
90
|
+
"risk.platform_specific",
|
|
91
|
+
];
|
|
92
|
+
|
|
61
93
|
export type CommandTemplateExecCommand = (
|
|
62
94
|
command: string,
|
|
63
95
|
args: string[],
|
|
@@ -148,8 +180,15 @@ function getExecutableName(command: string | undefined): string {
|
|
|
148
180
|
return command.split(/[\\/]/).pop()?.toLowerCase() ?? "";
|
|
149
181
|
}
|
|
150
182
|
|
|
183
|
+
function matchesFlag(arg: string, flag: string): boolean {
|
|
184
|
+
if (arg === flag) return true;
|
|
185
|
+
if (/^-[A-Za-z]$/.test(flag) && /^-[A-Za-z]+$/.test(arg))
|
|
186
|
+
return arg.slice(1).includes(flag.slice(1));
|
|
187
|
+
return false;
|
|
188
|
+
}
|
|
189
|
+
|
|
151
190
|
function hasAnyFlag(args: string[], flags: string[]): boolean {
|
|
152
|
-
return args.some((arg) => flags.
|
|
191
|
+
return args.some((arg) => flags.some((flag) => matchesFlag(arg, flag)));
|
|
153
192
|
}
|
|
154
193
|
|
|
155
194
|
function hasRiskyPathArg(args: string[]): boolean {
|
|
@@ -165,6 +204,97 @@ function hasRiskyPathArg(args: string[]): boolean {
|
|
|
165
204
|
);
|
|
166
205
|
}
|
|
167
206
|
|
|
207
|
+
function sortRiskLabels(
|
|
208
|
+
labels: Iterable<CommandTemplateRiskLabel>,
|
|
209
|
+
): CommandTemplateRiskLabel[] {
|
|
210
|
+
const unique = new Set(labels);
|
|
211
|
+
return COMMAND_TEMPLATE_RISK_LABEL_ORDER.filter((label) => unique.has(label));
|
|
212
|
+
}
|
|
213
|
+
|
|
214
|
+
function hasAnyArg(args: string[], values: string[]): boolean {
|
|
215
|
+
return args.some((arg) => values.includes(arg.toLowerCase()));
|
|
216
|
+
}
|
|
217
|
+
|
|
218
|
+
function hasSecretTouchingText(parts: string[]): boolean {
|
|
219
|
+
return parts.some((part) =>
|
|
220
|
+
/(^|[{}._\-\s/])(?:secret|token|password|passwd|credential|api[_-]?key|private[_-]?key|\.env|ssh[_-]?key)(?:[{}._\-\s/]|$)/i.test(
|
|
221
|
+
part,
|
|
222
|
+
),
|
|
223
|
+
);
|
|
224
|
+
}
|
|
225
|
+
|
|
226
|
+
function getLeafCommandTemplateRiskLabels(
|
|
227
|
+
config: CommandTemplateLeafConfig,
|
|
228
|
+
): CommandTemplateRiskLabel[] {
|
|
229
|
+
const parts = splitCommandTemplate(config.template);
|
|
230
|
+
const command = getExecutableName(parts[0]);
|
|
231
|
+
const args = parts.slice(1);
|
|
232
|
+
const labels = new Set<CommandTemplateRiskLabel>();
|
|
233
|
+
if (["bash", "sh", "zsh", "fish"].includes(command)) {
|
|
234
|
+
labels.add("risk.shell");
|
|
235
|
+
if (hasAnyFlag(args, ["-c"])) labels.add("risk.eval");
|
|
236
|
+
}
|
|
237
|
+
if (
|
|
238
|
+
["node", "deno", "bun"].includes(command) &&
|
|
239
|
+
hasAnyFlag(args, ["-e", "--eval"])
|
|
240
|
+
) {
|
|
241
|
+
labels.add("risk.eval");
|
|
242
|
+
}
|
|
243
|
+
if (
|
|
244
|
+
["python", "python3", "perl", "ruby"].includes(command) &&
|
|
245
|
+
hasAnyFlag(args, ["-c", "-e"])
|
|
246
|
+
) {
|
|
247
|
+
labels.add("risk.eval");
|
|
248
|
+
}
|
|
249
|
+
if (
|
|
250
|
+
command === "rm" &&
|
|
251
|
+
(args.some((arg) => /^-[^-]*r/.test(arg) || /^-[^-]*f/.test(arg)) ||
|
|
252
|
+
hasRiskyPathArg(args))
|
|
253
|
+
) {
|
|
254
|
+
labels.add("risk.destructive_fs");
|
|
255
|
+
}
|
|
256
|
+
if (["mv", "cp", "rsync"].includes(command) && hasRiskyPathArg(args)) {
|
|
257
|
+
labels.add("risk.broad_fs_write");
|
|
258
|
+
}
|
|
259
|
+
if (
|
|
260
|
+
["curl", "wget", "ssh", "scp", "sftp", "rsync", "nc", "ncat", "telnet", "ftp"].includes(
|
|
261
|
+
command,
|
|
262
|
+
) ||
|
|
263
|
+
(command === "git" &&
|
|
264
|
+
hasAnyArg(args, ["clone", "fetch", "pull", "push", "ls-remote"])) ||
|
|
265
|
+
["npm", "pnpm", "yarn", "pip", "cargo"].includes(command)
|
|
266
|
+
) {
|
|
267
|
+
labels.add("risk.network");
|
|
268
|
+
}
|
|
269
|
+
if (
|
|
270
|
+
["gh", "glab", "hub", "kubectl", "terraform"].includes(command) ||
|
|
271
|
+
(command === "git" && hasAnyArg(args, ["push"])) ||
|
|
272
|
+
(["npm", "pnpm", "yarn"].includes(command) &&
|
|
273
|
+
hasAnyArg(args, ["publish", "login", "logout", "deprecate"]))
|
|
274
|
+
) {
|
|
275
|
+
labels.add("risk.external_side_effect");
|
|
276
|
+
}
|
|
277
|
+
if (
|
|
278
|
+
command === "sleep" ||
|
|
279
|
+
command === "watch" ||
|
|
280
|
+
(command === "tail" && hasAnyFlag(args, ["-f"])) ||
|
|
281
|
+
hasAnyArg(args, ["--watch", "--serve", "serve"])
|
|
282
|
+
) {
|
|
283
|
+
labels.add("risk.long_running");
|
|
284
|
+
}
|
|
285
|
+
if (
|
|
286
|
+
["systemctl", "launchctl", "osascript", "open", "xdg-open", "powershell", "pwsh", "cmd.exe", "apt", "apt-get", "dnf", "yum", "brew", "pacman", "apk", "xclip", "wl-copy"].includes(
|
|
287
|
+
command,
|
|
288
|
+
)
|
|
289
|
+
) {
|
|
290
|
+
labels.add("risk.platform_specific");
|
|
291
|
+
}
|
|
292
|
+
if (["pass", "gpg", "ssh-add"].includes(command) || hasSecretTouchingText(parts)) {
|
|
293
|
+
labels.add("risk.secret_touching");
|
|
294
|
+
}
|
|
295
|
+
return sortRiskLabels(labels);
|
|
296
|
+
}
|
|
297
|
+
|
|
168
298
|
function getLeafCommandTemplateWarnings(
|
|
169
299
|
config: CommandTemplateLeafConfig,
|
|
170
300
|
): string[] {
|
|
@@ -177,7 +307,7 @@ function getLeafCommandTemplateWarnings(
|
|
|
177
307
|
? "shell command strings"
|
|
178
308
|
: "shell scripts";
|
|
179
309
|
warnings.push(
|
|
180
|
-
`${config.label ?? command}: invokes ${command}; ${shellContent} are trusted executable content and are not sandboxed by command-template argv splitting.`,
|
|
310
|
+
`${config.label ?? command}: invokes ${command}; ${shellContent} are trusted executable content and are not sandboxed by command-template argv splitting. Mitigation: keep scripts local, reviewed, and parameterized with explicit placeholders.`,
|
|
181
311
|
);
|
|
182
312
|
}
|
|
183
313
|
if (
|
|
@@ -185,7 +315,7 @@ function getLeafCommandTemplateWarnings(
|
|
|
185
315
|
hasAnyFlag(args, ["-e", "--eval"])
|
|
186
316
|
) {
|
|
187
317
|
warnings.push(
|
|
188
|
-
`${config.label ?? command}: invokes ${command} eval mode; code strings are trusted executable content and are not sandboxed.`,
|
|
318
|
+
`${config.label ?? command}: invokes ${command} eval mode; code strings are trusted executable content and are not sandboxed. Mitigation: prefer a checked-in script file or keep eval input fixed and reviewed.`,
|
|
189
319
|
);
|
|
190
320
|
}
|
|
191
321
|
if (
|
|
@@ -193,7 +323,7 @@ function getLeafCommandTemplateWarnings(
|
|
|
193
323
|
hasAnyFlag(args, ["-c", "-e"])
|
|
194
324
|
) {
|
|
195
325
|
warnings.push(
|
|
196
|
-
`${config.label ?? command}: invokes ${command} code-eval mode; code strings are trusted executable content and are not sandboxed.`,
|
|
326
|
+
`${config.label ?? command}: invokes ${command} code-eval mode; code strings are trusted executable content and are not sandboxed. Mitigation: prefer a checked-in script file or keep eval input fixed and reviewed.`,
|
|
197
327
|
);
|
|
198
328
|
}
|
|
199
329
|
if (
|
|
@@ -202,12 +332,12 @@ function getLeafCommandTemplateWarnings(
|
|
|
202
332
|
hasRiskyPathArg(args))
|
|
203
333
|
) {
|
|
204
334
|
warnings.push(
|
|
205
|
-
`${config.label ?? command}: removes filesystem paths; verify placeholders and paths before running trusted destructive commands.`,
|
|
335
|
+
`${config.label ?? command}: removes filesystem paths; verify placeholders and paths before running trusted destructive commands. Mitigation: constrain path placeholders and consider dry-run or explicit confirmation.`,
|
|
206
336
|
);
|
|
207
337
|
}
|
|
208
338
|
if (["mv", "cp", "rsync"].includes(command) && hasRiskyPathArg(args)) {
|
|
209
339
|
warnings.push(
|
|
210
|
-
`${config.label ?? command}: mutates broad filesystem paths; verify placeholders and paths before running trusted commands.`,
|
|
340
|
+
`${config.label ?? command}: mutates broad filesystem paths; verify placeholders and paths before running trusted commands. Mitigation: constrain path placeholders and prefer narrow source/destination paths.`,
|
|
211
341
|
);
|
|
212
342
|
}
|
|
213
343
|
return warnings;
|
|
@@ -331,6 +461,16 @@ export function getCommandTemplateWarnings(
|
|
|
331
461
|
];
|
|
332
462
|
}
|
|
333
463
|
|
|
464
|
+
export function getCommandTemplateRiskLabels(
|
|
465
|
+
config: CommandTemplateConfig,
|
|
466
|
+
): CommandTemplateRiskLabel[] {
|
|
467
|
+
return sortRiskLabels(
|
|
468
|
+
expandCommandTemplateConfigs(config).flatMap((leaf) =>
|
|
469
|
+
getLeafCommandTemplateRiskLabels(leaf),
|
|
470
|
+
),
|
|
471
|
+
);
|
|
472
|
+
}
|
|
473
|
+
|
|
334
474
|
function parseCommandTemplateArgToken(value: string): {
|
|
335
475
|
name: string;
|
|
336
476
|
defaultValue?: string;
|
|
@@ -541,7 +681,12 @@ function shouldResolveEmbeddedCommandTemplateToken(
|
|
|
541
681
|
function isFalsyCommandTemplateValue(value: unknown): boolean {
|
|
542
682
|
if (value === undefined || value === null || value === false) return true;
|
|
543
683
|
const normalized = String(value).trim().toLowerCase();
|
|
544
|
-
return
|
|
684
|
+
return (
|
|
685
|
+
normalized === "" ||
|
|
686
|
+
normalized === "0" ||
|
|
687
|
+
normalized === "false" ||
|
|
688
|
+
normalized === "no"
|
|
689
|
+
);
|
|
545
690
|
}
|
|
546
691
|
|
|
547
692
|
function resolveCommandTemplateCondition(
|
package/lib/commands.ts
CHANGED
|
@@ -293,7 +293,7 @@ export interface TelegramStopCommandDeps {
|
|
|
293
293
|
hasAbortHandler: () => boolean;
|
|
294
294
|
clearPendingModelSwitch: () => void;
|
|
295
295
|
clearQueuedTelegramItems: () => number;
|
|
296
|
-
|
|
296
|
+
setFoldQueuedPromptsIntoHistory: (fold: boolean) => void;
|
|
297
297
|
abortCurrentTurn: () => void;
|
|
298
298
|
updateStatus: () => void;
|
|
299
299
|
sendTextReply: (text: string) => Promise<void>;
|
|
@@ -582,7 +582,7 @@ export interface TelegramCommandRuntimeDeps<
|
|
|
582
582
|
clearPendingModelSwitch: () => void;
|
|
583
583
|
hasQueuedTelegramItems: () => boolean;
|
|
584
584
|
clearQueuedTelegramItems: (ctx: TContext) => number;
|
|
585
|
-
|
|
585
|
+
setFoldQueuedPromptsIntoHistory: (fold: boolean) => void;
|
|
586
586
|
abortCurrentTurn: () => void;
|
|
587
587
|
isIdle: (ctx: TContext) => boolean;
|
|
588
588
|
hasPendingMessages: (ctx: TContext) => boolean;
|
|
@@ -727,7 +727,7 @@ export async function handleTelegramStopCommand(
|
|
|
727
727
|
): Promise<void> {
|
|
728
728
|
deps.clearPendingModelSwitch();
|
|
729
729
|
const clearedCount = deps.clearQueuedTelegramItems();
|
|
730
|
-
deps.
|
|
730
|
+
deps.setFoldQueuedPromptsIntoHistory(false);
|
|
731
731
|
if (!deps.hasAbortHandler()) {
|
|
732
732
|
const clearedSuffix =
|
|
733
733
|
clearedCount > 0
|
|
@@ -748,9 +748,10 @@ export async function handleTelegramStopCommand(
|
|
|
748
748
|
|
|
749
749
|
export async function handleTelegramAbortCommand(deps: {
|
|
750
750
|
hasAbortHandler: () => boolean;
|
|
751
|
+
hasActiveTelegramTurn: () => boolean;
|
|
751
752
|
clearPendingModelSwitch: () => void;
|
|
752
753
|
abortCurrentTurn: () => void;
|
|
753
|
-
|
|
754
|
+
setFoldQueuedPromptsIntoHistory: (fold: boolean) => void;
|
|
754
755
|
updateStatus: () => void;
|
|
755
756
|
sendTextReply: (text: string) => Promise<void>;
|
|
756
757
|
}): Promise<void> {
|
|
@@ -759,7 +760,7 @@ export async function handleTelegramAbortCommand(deps: {
|
|
|
759
760
|
await deps.sendTextReply("No active turn.");
|
|
760
761
|
return;
|
|
761
762
|
}
|
|
762
|
-
deps.
|
|
763
|
+
deps.setFoldQueuedPromptsIntoHistory(deps.hasActiveTelegramTurn());
|
|
763
764
|
deps.abortCurrentTurn();
|
|
764
765
|
deps.updateStatus();
|
|
765
766
|
await deps.sendTextReply("Aborted current turn.");
|
|
@@ -772,7 +773,7 @@ export async function handleTelegramNextCommand(deps: {
|
|
|
772
773
|
clearPendingModelSwitch: () => void;
|
|
773
774
|
abortCurrentTurn: () => void;
|
|
774
775
|
dispatchNextQueuedTurn: () => void;
|
|
775
|
-
|
|
776
|
+
clearFoldForDispatch: () => void;
|
|
776
777
|
updateStatus: () => void;
|
|
777
778
|
sendTextReply: (text: string) => Promise<void>;
|
|
778
779
|
}): Promise<void> {
|
|
@@ -782,7 +783,7 @@ export async function handleTelegramNextCommand(deps: {
|
|
|
782
783
|
return;
|
|
783
784
|
}
|
|
784
785
|
if (!deps.isIdle() && deps.hasAbortHandler()) {
|
|
785
|
-
deps.
|
|
786
|
+
deps.clearFoldForDispatch();
|
|
786
787
|
deps.abortCurrentTurn();
|
|
787
788
|
deps.updateStatus();
|
|
788
789
|
await deps.sendTextReply(
|
|
@@ -1070,7 +1071,7 @@ export function createTelegramCommandHandlerTargetRuntime<
|
|
|
1070
1071
|
clearPendingModelSwitch: deps.clearPendingModelSwitch,
|
|
1071
1072
|
hasQueuedTelegramItems: deps.hasQueuedTelegramItems,
|
|
1072
1073
|
clearQueuedTelegramItems: deps.clearQueuedTelegramItems,
|
|
1073
|
-
|
|
1074
|
+
setFoldQueuedPromptsIntoHistory: deps.setFoldQueuedPromptsIntoHistory,
|
|
1074
1075
|
abortCurrentTurn: deps.abortCurrentTurn,
|
|
1075
1076
|
isIdle: deps.isIdle,
|
|
1076
1077
|
hasPendingMessages: deps.hasPendingMessages,
|
|
@@ -1177,7 +1178,7 @@ async function handleTelegramCommandRuntime<
|
|
|
1177
1178
|
clearPendingModelSwitch: deps.clearPendingModelSwitch,
|
|
1178
1179
|
clearQueuedTelegramItems: () =>
|
|
1179
1180
|
deps.clearQueuedTelegramItems(commandCtx),
|
|
1180
|
-
|
|
1181
|
+
setFoldQueuedPromptsIntoHistory: deps.setFoldQueuedPromptsIntoHistory,
|
|
1181
1182
|
abortCurrentTurn: deps.abortCurrentTurn,
|
|
1182
1183
|
updateStatus: updateStatusFor(commandCtx),
|
|
1183
1184
|
sendTextReply: sendReplyFor(nextMessage),
|
|
@@ -1186,9 +1187,10 @@ async function handleTelegramCommandRuntime<
|
|
|
1186
1187
|
handleAbort: async (nextMessage, commandCtx) => {
|
|
1187
1188
|
await handleTelegramAbortCommand({
|
|
1188
1189
|
hasAbortHandler: deps.hasAbortHandler,
|
|
1190
|
+
hasActiveTelegramTurn: deps.hasActiveTelegramTurn,
|
|
1189
1191
|
clearPendingModelSwitch: deps.clearPendingModelSwitch,
|
|
1190
1192
|
abortCurrentTurn: deps.abortCurrentTurn,
|
|
1191
|
-
|
|
1193
|
+
setFoldQueuedPromptsIntoHistory: deps.setFoldQueuedPromptsIntoHistory,
|
|
1192
1194
|
updateStatus: updateStatusFor(commandCtx),
|
|
1193
1195
|
sendTextReply: sendReplyFor(nextMessage),
|
|
1194
1196
|
});
|
|
@@ -1202,8 +1204,8 @@ async function handleTelegramCommandRuntime<
|
|
|
1202
1204
|
abortCurrentTurn: deps.abortCurrentTurn,
|
|
1203
1205
|
dispatchNextQueuedTurn: () =>
|
|
1204
1206
|
deps.dispatchNextQueuedTelegramTurn(commandCtx),
|
|
1205
|
-
|
|
1206
|
-
deps.
|
|
1207
|
+
clearFoldForDispatch: () =>
|
|
1208
|
+
deps.setFoldQueuedPromptsIntoHistory(false),
|
|
1207
1209
|
updateStatus: updateStatusFor(commandCtx),
|
|
1208
1210
|
sendTextReply: sendReplyFor(nextMessage),
|
|
1209
1211
|
});
|
package/lib/lifecycle.ts
CHANGED
|
@@ -102,6 +102,40 @@ export interface TelegramSessionLifecycleHooks {
|
|
|
102
102
|
) => Promise<void>;
|
|
103
103
|
}
|
|
104
104
|
|
|
105
|
+
export interface TelegramSessionContextStore<TContext> {
|
|
106
|
+
get: () => TContext | undefined;
|
|
107
|
+
set: (ctx: TContext) => void;
|
|
108
|
+
clear: () => void;
|
|
109
|
+
}
|
|
110
|
+
|
|
111
|
+
export function createTelegramSessionContextStore<
|
|
112
|
+
TContext,
|
|
113
|
+
>(): TelegramSessionContextStore<TContext> {
|
|
114
|
+
let currentContext: TContext | undefined;
|
|
115
|
+
return {
|
|
116
|
+
get: () => currentContext,
|
|
117
|
+
set: (ctx) => {
|
|
118
|
+
currentContext = ctx;
|
|
119
|
+
},
|
|
120
|
+
clear: () => {
|
|
121
|
+
currentContext = undefined;
|
|
122
|
+
},
|
|
123
|
+
};
|
|
124
|
+
}
|
|
125
|
+
|
|
126
|
+
export function createTelegramSessionContextTracker(
|
|
127
|
+
store: Pick<TelegramSessionContextStore<ExtensionContext>, "set" | "clear">,
|
|
128
|
+
): TelegramSessionLifecycleHooks {
|
|
129
|
+
return {
|
|
130
|
+
onSessionStart: async (_event, ctx) => {
|
|
131
|
+
store.set(ctx);
|
|
132
|
+
},
|
|
133
|
+
onSessionShutdown: async () => {
|
|
134
|
+
store.clear();
|
|
135
|
+
},
|
|
136
|
+
};
|
|
137
|
+
}
|
|
138
|
+
|
|
105
139
|
type TelegramLifecycleTimer = number | ReturnType<typeof setTimeout>;
|
|
106
140
|
|
|
107
141
|
export interface TelegramCompactionObserverRuntimeDeps<TContext> {
|
package/lib/locks.ts
CHANGED
|
@@ -62,10 +62,13 @@ export interface TelegramLockRuntime<TContext extends TelegramLockContext> {
|
|
|
62
62
|
}
|
|
63
63
|
|
|
64
64
|
export interface TelegramLockOwnershipGuard<TContext extends TelegramLockContext> {
|
|
65
|
-
ownsCurrentProcess: () => boolean;
|
|
66
65
|
ownsContext: (ctx: TContext) => boolean;
|
|
67
66
|
}
|
|
68
67
|
|
|
68
|
+
export interface TelegramLockContextStore<TContext extends TelegramLockContext> {
|
|
69
|
+
get: () => TContext | undefined;
|
|
70
|
+
}
|
|
71
|
+
|
|
69
72
|
export interface TelegramLockRuntimeOptions {
|
|
70
73
|
key?: string;
|
|
71
74
|
locksPath?: string;
|
|
@@ -248,11 +251,22 @@ export function createTelegramLockOwnershipGuard<
|
|
|
248
251
|
lock: TelegramLockRuntime<TContext>,
|
|
249
252
|
): TelegramLockOwnershipGuard<TContext> {
|
|
250
253
|
return {
|
|
251
|
-
ownsCurrentProcess: () => lock.owns(),
|
|
252
254
|
ownsContext: (ctx) => lock.owns(ctx),
|
|
253
255
|
};
|
|
254
256
|
}
|
|
255
257
|
|
|
258
|
+
export function createTelegramDirectDeliveryOwnershipChecker<
|
|
259
|
+
TContext extends TelegramLockContext,
|
|
260
|
+
>(deps: {
|
|
261
|
+
lock: TelegramLockRuntime<TContext>;
|
|
262
|
+
contextStore: TelegramLockContextStore<TContext>;
|
|
263
|
+
}): () => boolean {
|
|
264
|
+
return () => {
|
|
265
|
+
const ctx = deps.contextStore.get();
|
|
266
|
+
return ctx ? deps.lock.owns(ctx) : false;
|
|
267
|
+
};
|
|
268
|
+
}
|
|
269
|
+
|
|
256
270
|
export function createTelegramLockedPollingRuntime<
|
|
257
271
|
TContext extends TelegramLockContext,
|
|
258
272
|
>(
|