@llblab/pi-telegram 0.52.2 → 0.53.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 +9 -7
- package/BACKLOG.md +1 -18
- package/CHANGELOG.md +11 -0
- package/README.md +7 -11
- package/api/commands.ts +3 -0
- package/dist/api/commands.d.ts +1 -1
- package/dist/lib/activity-verbosity.d.ts +1 -5
- package/dist/lib/activity-verbosity.js +15 -42
- package/dist/lib/activity.d.ts +9 -0
- package/dist/lib/activity.js +39 -10
- package/dist/lib/agent-messages.js +7 -3
- package/dist/lib/bindings.d.ts +43 -5
- package/dist/lib/bindings.js +163 -20
- package/dist/lib/bus-api.d.ts +12 -0
- package/dist/lib/bus-api.js +182 -79
- package/dist/lib/bus-follower.d.ts +111 -31
- package/dist/lib/bus-follower.js +1324 -359
- package/dist/lib/bus-leader.d.ts +41 -91
- package/dist/lib/bus-leader.js +969 -637
- package/dist/lib/bus-transport.js +16 -9
- package/dist/lib/bus.d.ts +215 -18
- package/dist/lib/bus.js +1048 -327
- package/dist/lib/channel-posts.d.ts +5 -0
- package/dist/lib/channel-posts.js +434 -261
- package/dist/lib/command-templates.d.ts +26 -5
- package/dist/lib/command-templates.js +92 -176
- package/dist/lib/commands.d.ts +168 -82
- package/dist/lib/commands.js +1409 -291
- package/dist/lib/config.d.ts +1 -8
- package/dist/lib/config.js +34 -76
- package/dist/lib/delivery.d.ts +2 -2
- package/dist/lib/delivery.js +16 -16
- package/dist/lib/extension.js +639 -171
- package/dist/lib/generative-app-worker.mjs +19 -7
- package/dist/lib/generative-apps.d.ts +0 -5
- package/dist/lib/generative-apps.js +61 -49
- package/dist/lib/inbound.d.ts +5 -0
- package/dist/lib/inbound.js +35 -59
- package/dist/lib/journal.d.ts +33 -3
- package/dist/lib/journal.js +2063 -782
- package/dist/lib/lifecycle.d.ts +2 -6
- package/dist/lib/lifecycle.js +75 -58
- package/dist/lib/locks.d.ts +29 -3
- package/dist/lib/locks.js +289 -108
- package/dist/lib/logging.js +50 -14
- package/dist/lib/media.d.ts +22 -1
- package/dist/lib/media.js +46 -28
- package/dist/lib/menu-model.d.ts +19 -5
- package/dist/lib/menu-model.js +53 -11
- package/dist/lib/menu-queue.d.ts +5 -4
- package/dist/lib/menu-queue.js +45 -63
- package/dist/lib/menu-settings.d.ts +5 -4
- package/dist/lib/menu-settings.js +114 -78
- package/dist/lib/menu-status.d.ts +1 -1
- package/dist/lib/menu-status.js +8 -25
- package/dist/lib/menu-thinking.d.ts +6 -1
- package/dist/lib/menu-thinking.js +14 -21
- package/dist/lib/menu.d.ts +8 -61
- package/dist/lib/menu.js +92 -164
- package/dist/lib/model.d.ts +1 -1
- package/dist/lib/model.js +2 -16
- package/dist/lib/outbound-attachments.d.ts +6 -27
- package/dist/lib/outbound-attachments.js +45 -38
- package/dist/lib/outbound-buttons.js +22 -16
- package/dist/lib/outbound-markup.d.ts +0 -2
- package/dist/lib/outbound-markup.js +51 -64
- package/dist/lib/outbound-voice.js +6 -3
- package/dist/lib/outbound.d.ts +6 -6
- package/dist/lib/outbound.js +61 -51
- package/dist/lib/ownership.js +6 -2
- package/dist/lib/paths.d.ts +5 -0
- package/dist/lib/paths.js +37 -21
- package/dist/lib/pi.d.ts +2 -0
- package/dist/lib/pi.js +8 -1
- package/dist/lib/polling.d.ts +21 -37
- package/dist/lib/polling.js +59 -50
- package/dist/lib/preview.js +39 -19
- package/dist/lib/process-identity.d.ts +24 -0
- package/dist/lib/process-identity.js +133 -0
- package/dist/lib/prompts.js +2 -7
- package/dist/lib/queue.d.ts +14 -16
- package/dist/lib/queue.js +249 -126
- package/dist/lib/recovery.d.ts +2 -83
- package/dist/lib/recovery.js +20 -274
- package/dist/lib/replies.d.ts +16 -24
- package/dist/lib/replies.js +99 -116
- package/dist/lib/routing.d.ts +120 -7
- package/dist/lib/routing.js +4667 -1152
- package/dist/lib/runtime.d.ts +1 -11
- package/dist/lib/sections.d.ts +4 -29
- package/dist/lib/sections.js +7 -20
- package/dist/lib/skills.js +4 -3
- package/dist/lib/status.js +86 -57
- package/dist/lib/sync.d.ts +1 -3
- package/dist/lib/sync.js +67 -43
- package/dist/lib/target.d.ts +4 -0
- package/dist/lib/target.js +18 -0
- package/dist/lib/telegram-api.d.ts +18 -13
- package/dist/lib/telegram-api.js +148 -53
- package/dist/lib/text-groups.js +4 -39
- package/dist/lib/thread-cleanup-manager.d.ts +6 -27
- package/dist/lib/thread-cleanup-manager.js +369 -186
- package/dist/lib/thread-display.d.ts +1 -2
- package/dist/lib/thread-display.js +58 -38
- package/dist/lib/thread-naming.d.ts +28 -8
- package/dist/lib/thread-naming.js +175 -66
- package/dist/lib/thread-reconciler.d.ts +42 -16
- package/dist/lib/thread-reconciler.js +90 -50
- package/dist/lib/threads.d.ts +82 -20
- package/dist/lib/threads.js +2247 -855
- package/dist/lib/time-injection.js +1 -2
- package/dist/lib/turns.js +1 -19
- package/dist/lib/updates.d.ts +169 -80
- package/dist/lib/updates.js +2418 -641
- package/dist/lib/voice.d.ts +7 -22
- package/dist/lib/voice.js +7 -22
- package/dist/lib/wire.js +1 -1
- package/dist/lib/workspace-admission.d.ts +3 -1
- package/dist/lib/workspace-admission.js +425 -211
- package/dist/lib/workspace-identity.js +8 -13
- package/dist/lib/workspace-retirement.d.ts +1 -1
- package/dist/lib/workspace-retirement.js +328 -140
- package/dist/lib/workspace-slots.js +8 -4
- package/dist/package.json +1 -1
- package/docs/architecture.md +31 -29
- package/docs/inbound.md +1 -1
- package/docs/multi-instance-bus.md +15 -3
- package/docs/outbound.md +1 -1
- package/docs/public-api.md +29 -1
- package/docs/ui-style.md +25 -8
- package/lib/activity-verbosity.ts +59 -82
- package/lib/activity.ts +146 -102
- package/lib/agent-messages.ts +12 -5
- package/lib/bindings.ts +389 -82
- package/lib/bus-api.ts +263 -102
- package/lib/bus-follower.ts +2440 -601
- package/lib/bus-leader.ts +2209 -1290
- package/lib/bus-transport.ts +46 -12
- package/lib/bus.ts +1673 -444
- package/lib/channel-posts.ts +848 -327
- package/lib/command-templates.ts +152 -265
- package/lib/commands.ts +2398 -555
- package/lib/config.ts +177 -170
- package/lib/delivery.ts +41 -29
- package/lib/extension.ts +1206 -433
- package/lib/generative-app-worker.mjs +19 -7
- package/lib/generative-apps.ts +324 -211
- package/lib/inbound.ts +95 -97
- package/lib/journal.ts +5311 -1763
- package/lib/keyboard.ts +1 -3
- package/lib/lifecycle.ts +128 -78
- package/lib/locks.ts +677 -203
- package/lib/logging.ts +147 -58
- package/lib/media.ts +82 -43
- package/lib/menu-model.ts +85 -18
- package/lib/menu-queue.ts +88 -129
- package/lib/menu-settings.ts +260 -155
- package/lib/menu-status.ts +20 -60
- package/lib/menu-thinking.ts +41 -56
- package/lib/menu.ts +112 -285
- package/lib/model.ts +8 -22
- package/lib/outbound-attachments.ts +156 -125
- package/lib/outbound-buttons.ts +76 -49
- package/lib/outbound-markup.ts +96 -94
- package/lib/outbound-voice.ts +33 -27
- package/lib/outbound.ts +125 -125
- package/lib/ownership.ts +19 -10
- package/lib/paths.ts +148 -49
- package/lib/pi.ts +35 -15
- package/lib/polling.ts +144 -147
- package/lib/preview.ts +93 -40
- package/lib/process-identity.ts +164 -0
- package/lib/prompts.ts +4 -11
- package/lib/queue.ts +511 -300
- package/lib/recovery.ts +50 -454
- package/lib/rendering.ts +1 -3
- package/lib/replies.ts +248 -209
- package/lib/routing.ts +8509 -2186
- package/lib/runtime.ts +1 -12
- package/lib/sections.ts +24 -89
- package/lib/setup.ts +1 -3
- package/lib/skills.ts +9 -4
- package/lib/status.ts +215 -110
- package/lib/sync.ts +448 -361
- package/lib/target.ts +22 -3
- package/lib/telegram-api.ts +302 -127
- package/lib/text-groups.ts +5 -39
- package/lib/thread-cleanup-manager.ts +833 -331
- package/lib/thread-display.ts +213 -97
- package/lib/thread-naming.ts +232 -87
- package/lib/thread-reconciler.ts +155 -72
- package/lib/threads.ts +5318 -1845
- package/lib/time-injection.ts +1 -4
- package/lib/turns.ts +15 -28
- package/lib/updates.ts +5184 -1612
- package/lib/voice.ts +7 -35
- package/lib/wire.ts +1 -1
- package/lib/workspace-admission.ts +996 -416
- package/lib/workspace-identity.ts +14 -17
- package/lib/workspace-retirement.ts +878 -376
- package/lib/workspace-slots.ts +22 -13
- package/package.json +1 -1
- package/scripts/check-downgrade.mjs +0 -472
- package/scripts/measure-workspace.mjs +0 -108
package/dist/lib/recovery.d.ts
CHANGED
|
@@ -1,87 +1,8 @@
|
|
|
1
1
|
/**
|
|
2
|
-
* Telegram
|
|
2
|
+
* Telegram runtime storage housekeeping
|
|
3
3
|
* Zones: filesystem diagnostics, unclean-shutdown recovery
|
|
4
|
-
* Owns
|
|
4
|
+
* Owns best-effort removal of obsolete recovery folders and orphaned session journal families
|
|
5
5
|
*/
|
|
6
|
-
import { type TelegramFileTransactionOptions } from "./locks.ts";
|
|
7
|
-
export type TelegramRuntimeArtifactKind = "owners" | "state" | "transaction";
|
|
8
|
-
export interface TelegramRuntimeCorruptArtifact {
|
|
9
|
-
kind: TelegramRuntimeArtifactKind;
|
|
10
|
-
path: string;
|
|
11
|
-
reason: string;
|
|
12
|
-
}
|
|
13
|
-
export type TelegramRuntimeRecoveryClassification = {
|
|
14
|
-
kind: "clean";
|
|
15
|
-
} | {
|
|
16
|
-
kind: "recoverable-corruption";
|
|
17
|
-
artifacts: TelegramRuntimeCorruptArtifact[];
|
|
18
|
-
} | {
|
|
19
|
-
kind: "blocked-live-owner";
|
|
20
|
-
artifacts: TelegramRuntimeCorruptArtifact[];
|
|
21
|
-
livePids: number[];
|
|
22
|
-
};
|
|
23
|
-
export interface TelegramRuntimeRecoveryClassificationOptions {
|
|
24
|
-
ownersPath: string;
|
|
25
|
-
statePaths?: readonly string[];
|
|
26
|
-
transactionPath?: string;
|
|
27
|
-
isProcessAlive?: (pid: number) => boolean;
|
|
28
|
-
nowMs?: number;
|
|
29
|
-
staleHeartbeatMs?: number;
|
|
30
|
-
ignoredTransactionPids?: readonly number[];
|
|
31
|
-
}
|
|
32
|
-
export type TelegramRuntimeRecoveryResult = {
|
|
33
|
-
kind: "not-needed";
|
|
34
|
-
} | {
|
|
35
|
-
kind: "blocked-live-owner";
|
|
36
|
-
livePids: number[];
|
|
37
|
-
deletedPaths?: string[];
|
|
38
|
-
} | {
|
|
39
|
-
kind: "recovered";
|
|
40
|
-
artifacts: TelegramRuntimeCorruptArtifact[];
|
|
41
|
-
/** Damaged disposable artifacts deleted per the approved corruption policy; nothing is quarantined. */
|
|
42
|
-
deletedPaths: string[];
|
|
43
|
-
};
|
|
44
|
-
export interface TelegramRuntimeRecoveryOptions extends TelegramRuntimeRecoveryClassificationOptions {
|
|
45
|
-
recoveryTransactionPath?: string;
|
|
46
|
-
pid?: number;
|
|
47
|
-
getNowMs?: () => number;
|
|
48
|
-
/** Fault-injection seam; production uses bounded-retry recursive removal. */
|
|
49
|
-
removePath?: (path: string) => void;
|
|
50
|
-
transactionOptions?: TelegramFileTransactionOptions;
|
|
51
|
-
}
|
|
52
|
-
export type TelegramPollingStartRecoveryDecision = {
|
|
53
|
-
kind: "unhandled";
|
|
54
|
-
} | {
|
|
55
|
-
kind: "retry";
|
|
56
|
-
message: string;
|
|
57
|
-
} | {
|
|
58
|
-
kind: "blocked";
|
|
59
|
-
message: string;
|
|
60
|
-
};
|
|
61
|
-
export interface TelegramPollingStartRecoveryHandlerDeps {
|
|
62
|
-
getOwnersPath: () => string;
|
|
63
|
-
getStatePaths: () => readonly string[];
|
|
64
|
-
suspendPolling: () => Promise<unknown>;
|
|
65
|
-
releaseOwnership?: () => unknown | Promise<unknown>;
|
|
66
|
-
recordRuntimeEvent?: (category: string, error: unknown, details?: Record<string, unknown>) => void;
|
|
67
|
-
}
|
|
68
|
-
/**
|
|
69
|
-
* Classify disposable runtime corruption without mutating any artifact.
|
|
70
|
-
*
|
|
71
|
-
* Corruption remains recoverable only when neither owners.json nor a
|
|
72
|
-
* verifiable transaction marker identifies a process that is still alive.
|
|
73
|
-
*/
|
|
74
|
-
export declare function classifyTelegramRuntimeRecovery(options: TelegramRuntimeRecoveryClassificationOptions): TelegramRuntimeRecoveryClassification;
|
|
75
|
-
/**
|
|
76
|
-
* Delete classifier-approved disposable corruption under two guards.
|
|
77
|
-
*
|
|
78
|
-
* A dedicated recovery transaction serializes recoverers. The ownership
|
|
79
|
-
* transaction then prevents a new Telegram owner from appearing between the
|
|
80
|
-
* final classification and mutation. Damaged ownership debris and canonical
|
|
81
|
-
* state are deleted (operator policy: unfinished Restores in unreadable state
|
|
82
|
-
* are acceptable loss). Durable config and diagnostics never enter the set.
|
|
83
|
-
*/
|
|
84
|
-
export declare function recoverTelegramRuntimeState(options: TelegramRuntimeRecoveryOptions): TelegramRuntimeRecoveryResult;
|
|
85
6
|
/**
|
|
86
7
|
* Remove recovery folders written by earlier releases (runtime root and session folders).
|
|
87
8
|
* Current releases delete damaged files instead of quarantining them; nothing reads these copies.
|
|
@@ -102,5 +23,3 @@ export interface TelegramSessionFolderSweeperDeps {
|
|
|
102
23
|
export declare function createTelegramSessionFolderSweeper(deps: TelegramSessionFolderSweeperDeps): {
|
|
103
24
|
sweep: () => string[];
|
|
104
25
|
};
|
|
105
|
-
/** Build the `/telegram-connect` recovery boundary around runtime artifacts. */
|
|
106
|
-
export declare function createTelegramPollingStartRecoveryHandler(deps: TelegramPollingStartRecoveryHandlerDeps): () => Promise<TelegramPollingStartRecoveryDecision>;
|
package/dist/lib/recovery.js
CHANGED
|
@@ -1,216 +1,11 @@
|
|
|
1
1
|
/**
|
|
2
|
-
* Telegram
|
|
2
|
+
* Telegram runtime storage housekeeping
|
|
3
3
|
* Zones: filesystem diagnostics, unclean-shutdown recovery
|
|
4
|
-
* Owns
|
|
4
|
+
* Owns best-effort removal of obsolete recovery folders and orphaned session journal families
|
|
5
5
|
*/
|
|
6
|
-
import {
|
|
7
|
-
import {
|
|
8
|
-
import { decodeTelegramSessionDirectoryName, getTelegramProfilePathSuffix } from "./paths.js";
|
|
9
|
-
import { isProcessAlive as defaultIsProcessAlive, parseTelegramLockEntry, TELEGRAM_BUS_LEADER_STALE_HEARTBEAT_MS, withTelegramFileTransaction, } from "./locks.js";
|
|
10
|
-
const OWNER_FILE_PATTERN = /^owner\.([A-Za-z0-9-]+)\.json$/u;
|
|
11
|
-
const RECLAIM_FILE_PATTERN = /^owner\.reclaim\.(\d+)\.([0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12})\.json$/u;
|
|
12
|
-
function corruption(kind, path, reason) {
|
|
13
|
-
return { source: kind, corrupt: { kind, path, reason }, ownerPids: [] };
|
|
14
|
-
}
|
|
15
|
-
function inspectOwners(path, nowMs, staleHeartbeatMs) {
|
|
16
|
-
if (!existsSync(path))
|
|
17
|
-
return { source: "owners", ownerPids: [] };
|
|
18
|
-
let value;
|
|
19
|
-
try {
|
|
20
|
-
value = JSON.parse(readFileSync(path, "utf8"));
|
|
21
|
-
}
|
|
22
|
-
catch {
|
|
23
|
-
return corruption("owners", path, "owners.json is not valid JSON");
|
|
24
|
-
}
|
|
25
|
-
if (!value || typeof value !== "object" || Array.isArray(value)) {
|
|
26
|
-
return corruption("owners", path, "owners.json is not an object");
|
|
27
|
-
}
|
|
28
|
-
const ownerPids = Object.values(value).flatMap((candidate) => {
|
|
29
|
-
const entry = parseTelegramLockEntry(candidate);
|
|
30
|
-
if (!entry ||
|
|
31
|
-
(typeof entry.heartbeatMs === "number" &&
|
|
32
|
-
nowMs - entry.heartbeatMs > staleHeartbeatMs)) {
|
|
33
|
-
return [];
|
|
34
|
-
}
|
|
35
|
-
return [entry.pid];
|
|
36
|
-
});
|
|
37
|
-
return { source: "owners", ownerPids };
|
|
38
|
-
}
|
|
39
|
-
function inspectState(path) {
|
|
40
|
-
if (!existsSync(path))
|
|
41
|
-
return { source: "state", ownerPids: [] };
|
|
42
|
-
try {
|
|
43
|
-
const stat = lstatSync(path);
|
|
44
|
-
if (!stat.isFile() || stat.isSymbolicLink() || stat.nlink !== 1 || stat.size > 8 * 1024 * 1024)
|
|
45
|
-
return corruption("state", path, `${basename(path)} cannot be safely inspected`);
|
|
46
|
-
JSON.parse(readFileSync(path, "utf8"));
|
|
47
|
-
return { source: "state", ownerPids: [] };
|
|
48
|
-
}
|
|
49
|
-
catch {
|
|
50
|
-
return corruption("state", path, `${basename(path)} is not valid JSON`);
|
|
51
|
-
}
|
|
52
|
-
}
|
|
53
|
-
function parseTransactionOwner(path) {
|
|
54
|
-
const value = JSON.parse(readFileSync(path, "utf8"));
|
|
55
|
-
if (!value || typeof value !== "object" || Array.isArray(value)) {
|
|
56
|
-
return undefined;
|
|
57
|
-
}
|
|
58
|
-
const pid = value.pid;
|
|
59
|
-
const acquiredAtMs = value.acquiredAtMs;
|
|
60
|
-
const generation = value.generation;
|
|
61
|
-
return typeof pid === "number" &&
|
|
62
|
-
typeof acquiredAtMs === "number" &&
|
|
63
|
-
typeof generation === "string"
|
|
64
|
-
? pid
|
|
65
|
-
: undefined;
|
|
66
|
-
}
|
|
67
|
-
function inspectTransaction(path) {
|
|
68
|
-
if (!existsSync(path))
|
|
69
|
-
return { source: "transaction", ownerPids: [] };
|
|
70
|
-
try {
|
|
71
|
-
const stat = lstatSync(path);
|
|
72
|
-
let ownerPath;
|
|
73
|
-
let expectedOwnerPid;
|
|
74
|
-
if (stat.isDirectory()) {
|
|
75
|
-
const entries = readdirSync(path);
|
|
76
|
-
if (entries.length !== 1) {
|
|
77
|
-
return corruption("transaction", path, "transaction guard has an unverifiable directory shape");
|
|
78
|
-
}
|
|
79
|
-
const entry = entries[0];
|
|
80
|
-
const ownerMatch = OWNER_FILE_PATTERN.exec(entry);
|
|
81
|
-
const reclaimMatch = RECLAIM_FILE_PATTERN.exec(entry);
|
|
82
|
-
if (!ownerMatch && !reclaimMatch) {
|
|
83
|
-
return corruption("transaction", path, "transaction guard has an unrecognized owner marker");
|
|
84
|
-
}
|
|
85
|
-
ownerPath = join(path, entry);
|
|
86
|
-
expectedOwnerPid = reclaimMatch ? Number(reclaimMatch[1]) : undefined;
|
|
87
|
-
const ownerPid = parseTransactionOwner(ownerPath);
|
|
88
|
-
if (ownerPid === undefined) {
|
|
89
|
-
return corruption("transaction", path, "transaction guard owner metadata is invalid");
|
|
90
|
-
}
|
|
91
|
-
if (ownerMatch) {
|
|
92
|
-
const value = JSON.parse(readFileSync(ownerPath, "utf8"));
|
|
93
|
-
if (value.generation !== ownerMatch[1]) {
|
|
94
|
-
return corruption("transaction", path, "transaction guard generation does not match its owner marker");
|
|
95
|
-
}
|
|
96
|
-
}
|
|
97
|
-
return {
|
|
98
|
-
source: "transaction",
|
|
99
|
-
ownerPids: [expectedOwnerPid ?? ownerPid],
|
|
100
|
-
};
|
|
101
|
-
}
|
|
102
|
-
if (!stat.isFile()) {
|
|
103
|
-
return corruption("transaction", path, "transaction guard has an unsupported filesystem type");
|
|
104
|
-
}
|
|
105
|
-
ownerPath = path;
|
|
106
|
-
const ownerPid = parseTransactionOwner(ownerPath);
|
|
107
|
-
return ownerPid === undefined
|
|
108
|
-
? corruption("transaction", path, "legacy transaction guard owner metadata is invalid")
|
|
109
|
-
: { source: "transaction", ownerPids: [ownerPid] };
|
|
110
|
-
}
|
|
111
|
-
catch {
|
|
112
|
-
return corruption("transaction", path, "transaction guard cannot be inspected");
|
|
113
|
-
}
|
|
114
|
-
}
|
|
115
|
-
/**
|
|
116
|
-
* Classify disposable runtime corruption without mutating any artifact.
|
|
117
|
-
*
|
|
118
|
-
* Corruption remains recoverable only when neither owners.json nor a
|
|
119
|
-
* verifiable transaction marker identifies a process that is still alive.
|
|
120
|
-
*/
|
|
121
|
-
export function classifyTelegramRuntimeRecovery(options) {
|
|
122
|
-
const transactionPath = options.transactionPath ?? `${options.ownersPath}.transaction`;
|
|
123
|
-
const inspections = [
|
|
124
|
-
inspectOwners(options.ownersPath, options.nowMs ?? Date.now(), options.staleHeartbeatMs ?? TELEGRAM_BUS_LEADER_STALE_HEARTBEAT_MS),
|
|
125
|
-
...(options.statePaths ?? []).map(inspectState),
|
|
126
|
-
inspectTransaction(transactionPath),
|
|
127
|
-
];
|
|
128
|
-
const artifacts = inspections.flatMap((inspection) => inspection.corrupt ? [inspection.corrupt] : []);
|
|
129
|
-
if (artifacts.length === 0)
|
|
130
|
-
return { kind: "clean" };
|
|
131
|
-
const processAlive = options.isProcessAlive ?? defaultIsProcessAlive;
|
|
132
|
-
const ignoredTransactionPids = new Set(options.ignoredTransactionPids ?? []);
|
|
133
|
-
const livePids = [
|
|
134
|
-
...new Set(inspections.flatMap((inspection) => inspection.ownerPids.filter((pid) => !(inspection.source === "transaction" &&
|
|
135
|
-
ignoredTransactionPids.has(pid)) && processAlive(pid)))),
|
|
136
|
-
].sort((left, right) => left - right);
|
|
137
|
-
return livePids.length > 0
|
|
138
|
-
? { kind: "blocked-live-owner", artifacts, livePids }
|
|
139
|
-
: { kind: "recoverable-corruption", artifacts };
|
|
140
|
-
}
|
|
141
|
-
/**
|
|
142
|
-
* Delete classifier-approved disposable corruption under two guards.
|
|
143
|
-
*
|
|
144
|
-
* A dedicated recovery transaction serializes recoverers. The ownership
|
|
145
|
-
* transaction then prevents a new Telegram owner from appearing between the
|
|
146
|
-
* final classification and mutation. Damaged ownership debris and canonical
|
|
147
|
-
* state are deleted (operator policy: unfinished Restores in unreadable state
|
|
148
|
-
* are acceptable loss). Durable config and diagnostics never enter the set.
|
|
149
|
-
*/
|
|
150
|
-
export function recoverTelegramRuntimeState(options) {
|
|
151
|
-
const transactionPath = options.transactionPath ?? `${options.ownersPath}.transaction`;
|
|
152
|
-
const recoveryTransactionPath = options.recoveryTransactionPath ??
|
|
153
|
-
join(dirname(options.ownersPath), "runtime-recovery.transaction");
|
|
154
|
-
const removePath = options.removePath ?? ((path) => rmSync(path, { recursive: true, force: true, maxRetries: 3, retryDelay: 50 }));
|
|
155
|
-
const classificationOptions = {
|
|
156
|
-
ownersPath: options.ownersPath,
|
|
157
|
-
statePaths: options.statePaths,
|
|
158
|
-
transactionPath,
|
|
159
|
-
isProcessAlive: options.isProcessAlive,
|
|
160
|
-
nowMs: options.nowMs ?? options.getNowMs?.(),
|
|
161
|
-
staleHeartbeatMs: options.staleHeartbeatMs,
|
|
162
|
-
};
|
|
163
|
-
return withTelegramFileTransaction(recoveryTransactionPath, () => {
|
|
164
|
-
const initial = classifyTelegramRuntimeRecovery(classificationOptions);
|
|
165
|
-
if (initial.kind === "clean")
|
|
166
|
-
return { kind: "not-needed" };
|
|
167
|
-
if (initial.kind === "blocked-live-owner") {
|
|
168
|
-
return {
|
|
169
|
-
kind: "blocked-live-owner",
|
|
170
|
-
livePids: initial.livePids,
|
|
171
|
-
};
|
|
172
|
-
}
|
|
173
|
-
const deletedPaths = [];
|
|
174
|
-
const recoveredArtifacts = [];
|
|
175
|
-
const deleteArtifact = (artifact) => {
|
|
176
|
-
if (!existsSync(artifact.path))
|
|
177
|
-
return;
|
|
178
|
-
removePath(artifact.path);
|
|
179
|
-
deletedPaths.push(artifact.path);
|
|
180
|
-
recoveredArtifacts.push(artifact);
|
|
181
|
-
};
|
|
182
|
-
for (const artifact of initial.artifacts) {
|
|
183
|
-
if (artifact.kind === "transaction")
|
|
184
|
-
deleteArtifact(artifact);
|
|
185
|
-
}
|
|
186
|
-
return withTelegramFileTransaction(transactionPath, () => {
|
|
187
|
-
const current = classifyTelegramRuntimeRecovery({
|
|
188
|
-
...classificationOptions,
|
|
189
|
-
ignoredTransactionPids: [process.pid],
|
|
190
|
-
});
|
|
191
|
-
if (current.kind === "blocked-live-owner") {
|
|
192
|
-
return {
|
|
193
|
-
kind: "blocked-live-owner",
|
|
194
|
-
livePids: current.livePids,
|
|
195
|
-
...(deletedPaths.length > 0 ? { deletedPaths } : {}),
|
|
196
|
-
};
|
|
197
|
-
}
|
|
198
|
-
if (current.kind === "recoverable-corruption") {
|
|
199
|
-
for (const artifact of current.artifacts) {
|
|
200
|
-
if (artifact.kind !== "transaction")
|
|
201
|
-
deleteArtifact(artifact);
|
|
202
|
-
}
|
|
203
|
-
}
|
|
204
|
-
return recoveredArtifacts.length > 0
|
|
205
|
-
? {
|
|
206
|
-
kind: "recovered",
|
|
207
|
-
artifacts: recoveredArtifacts,
|
|
208
|
-
deletedPaths,
|
|
209
|
-
}
|
|
210
|
-
: { kind: "not-needed" };
|
|
211
|
-
}, options.transactionOptions);
|
|
212
|
-
}, options.transactionOptions);
|
|
213
|
-
}
|
|
6
|
+
import { lstatSync, readdirSync, rmdirSync, rmSync } from "node:fs";
|
|
7
|
+
import { join } from "node:path";
|
|
8
|
+
import { decodeTelegramSessionDirectoryName, getTelegramProfilePathSuffix, } from "./paths.js";
|
|
214
9
|
/**
|
|
215
10
|
* Remove recovery folders written by earlier releases (runtime root and session folders).
|
|
216
11
|
* Current releases delete damaged files instead of quarantining them; nothing reads these copies.
|
|
@@ -221,7 +16,12 @@ export function removeTelegramLegacyRecoveryStorage(runtimeDir) {
|
|
|
221
16
|
try {
|
|
222
17
|
if (!lstatSync(path).isDirectory())
|
|
223
18
|
return;
|
|
224
|
-
rmSync(path, {
|
|
19
|
+
rmSync(path, {
|
|
20
|
+
recursive: true,
|
|
21
|
+
force: true,
|
|
22
|
+
maxRetries: 3,
|
|
23
|
+
retryDelay: 50,
|
|
24
|
+
});
|
|
225
25
|
removed.push(path);
|
|
226
26
|
}
|
|
227
27
|
catch {
|
|
@@ -233,7 +33,9 @@ export function removeTelegramLegacyRecoveryStorage(runtimeDir) {
|
|
|
233
33
|
try {
|
|
234
34
|
sessions = readdirSync(join(runtimeDir, "sessions"));
|
|
235
35
|
}
|
|
236
|
-
catch {
|
|
36
|
+
catch {
|
|
37
|
+
/* no sessions yet */
|
|
38
|
+
}
|
|
237
39
|
for (const session of sessions)
|
|
238
40
|
remove(join(runtimeDir, "sessions", session, "recovery"));
|
|
239
41
|
return removed;
|
|
@@ -277,7 +79,12 @@ export function createTelegramSessionFolderSweeper(deps) {
|
|
|
277
79
|
if (!family.test(entry))
|
|
278
80
|
continue;
|
|
279
81
|
const path = join(folder, entry);
|
|
280
|
-
rmSync(path, {
|
|
82
|
+
rmSync(path, {
|
|
83
|
+
recursive: true,
|
|
84
|
+
force: true,
|
|
85
|
+
maxRetries: 3,
|
|
86
|
+
retryDelay: 50,
|
|
87
|
+
});
|
|
281
88
|
removed.push(path);
|
|
282
89
|
}
|
|
283
90
|
if (readdirSync(folder).length === 0) {
|
|
@@ -293,64 +100,3 @@ export function createTelegramSessionFolderSweeper(deps) {
|
|
|
293
100
|
},
|
|
294
101
|
};
|
|
295
102
|
}
|
|
296
|
-
/** Build the `/telegram-connect` recovery boundary around runtime artifacts. */
|
|
297
|
-
export function createTelegramPollingStartRecoveryHandler(deps) {
|
|
298
|
-
return async () => {
|
|
299
|
-
const ownersPath = deps.getOwnersPath();
|
|
300
|
-
const statePaths = deps.getStatePaths();
|
|
301
|
-
const classification = classifyTelegramRuntimeRecovery({
|
|
302
|
-
ownersPath,
|
|
303
|
-
statePaths,
|
|
304
|
-
});
|
|
305
|
-
if (classification.kind === "clean")
|
|
306
|
-
return { kind: "unhandled" };
|
|
307
|
-
if (classification.kind === "blocked-live-owner") {
|
|
308
|
-
return {
|
|
309
|
-
kind: "blocked",
|
|
310
|
-
message: `Telegram temporary state is damaged, but owner process ${classification.livePids.join(", ")} is still live. Restart that Pi instance, then run /telegram-connect again.`,
|
|
311
|
-
};
|
|
312
|
-
}
|
|
313
|
-
try {
|
|
314
|
-
await deps.suspendPolling();
|
|
315
|
-
}
|
|
316
|
-
catch (error) {
|
|
317
|
-
deps.recordRuntimeEvent?.("recovery", error, {
|
|
318
|
-
phase: "suspend-before-reset",
|
|
319
|
-
});
|
|
320
|
-
return {
|
|
321
|
-
kind: "blocked",
|
|
322
|
-
message: "Telegram polling could not stop safely, so temporary state was not reset. Restart this Pi instance and run /telegram-connect again.",
|
|
323
|
-
};
|
|
324
|
-
}
|
|
325
|
-
try {
|
|
326
|
-
await deps.releaseOwnership?.();
|
|
327
|
-
}
|
|
328
|
-
catch (error) {
|
|
329
|
-
deps.recordRuntimeEvent?.("recovery", error, {
|
|
330
|
-
phase: "release-before-reset",
|
|
331
|
-
});
|
|
332
|
-
}
|
|
333
|
-
try {
|
|
334
|
-
const recovery = recoverTelegramRuntimeState({ ownersPath, statePaths });
|
|
335
|
-
if (recovery.kind === "blocked-live-owner") {
|
|
336
|
-
return {
|
|
337
|
-
kind: "blocked",
|
|
338
|
-
message: `Telegram temporary state changed during recovery and is now protected by owner process ${recovery.livePids.join(", ")}. Restart that Pi instance, then run /telegram-connect again.`,
|
|
339
|
-
};
|
|
340
|
-
}
|
|
341
|
-
return {
|
|
342
|
-
kind: "retry",
|
|
343
|
-
message: recovery.kind === "recovered"
|
|
344
|
-
? "Telegram temporary state was damaged after an unclean shutdown and has been reset."
|
|
345
|
-
: "Telegram temporary state was recovered by another Pi instance.",
|
|
346
|
-
};
|
|
347
|
-
}
|
|
348
|
-
catch (error) {
|
|
349
|
-
deps.recordRuntimeEvent?.("recovery", error, { phase: "runtime-reset" });
|
|
350
|
-
return {
|
|
351
|
-
kind: "blocked",
|
|
352
|
-
message: "Telegram temporary-state recovery failed. Restart this Pi instance and run /telegram-connect again.",
|
|
353
|
-
};
|
|
354
|
-
}
|
|
355
|
-
};
|
|
356
|
-
}
|
package/dist/lib/replies.d.ts
CHANGED
|
@@ -3,25 +3,13 @@
|
|
|
3
3
|
* Zones: telegram outbound, native rich markdown, UI/compat rendering transport
|
|
4
4
|
* Owns native assistant replies, rendered UI delivery, guest placeholder rotation, reply transport wiring, and plain text replies
|
|
5
5
|
*/
|
|
6
|
-
import { type TelegramTarget } from "./target.ts";
|
|
7
|
-
import type { TelegramInputRichMessage, TelegramReplyParameters, TelegramSendRichMessageBody, TelegramSentMessage } from "./telegram-api.ts";
|
|
8
6
|
import { renderTelegramMessage, type TelegramRenderedChunk, type TelegramRenderMode } from "./rendering.ts";
|
|
7
|
+
import { type TelegramTarget } from "./target.ts";
|
|
8
|
+
import type { TelegramApiCallOptions, TelegramInputRichMessage, TelegramReplyParameters, TelegramSendRichMessageBody, TelegramSentMessage } from "./telegram-api.ts";
|
|
9
9
|
export { renderTelegramMessage, type TelegramRenderedChunk, type TelegramRenderMode, };
|
|
10
10
|
export declare function renderTelegramMarkdownToHtmlDraft(markdown: string): string;
|
|
11
11
|
export declare const TELEGRAM_RICH_MESSAGE_MAX_CHARS = 32768;
|
|
12
12
|
export declare const TELEGRAM_RICH_MESSAGE_MAX_BLOCKS = 500;
|
|
13
|
-
/** Non-persistent reply deduplication for a single agent turn.
|
|
14
|
-
* First reply to a prompt gets `reply_parameters.message_id`;
|
|
15
|
-
* subsequent replies in the same turn skip it to avoid stacking
|
|
16
|
-
* duplicate reply headers in the chat viewport. */
|
|
17
|
-
export interface ReplyDedupRuntime {
|
|
18
|
-
/** Returns true if this is the first reply for the given prompt
|
|
19
|
-
* message id in the current turn. Side-effect: marks it replied. */
|
|
20
|
-
shouldReply(promptMessageId: number): boolean;
|
|
21
|
-
/** Reset the tracker when a new prompt enters the queue. */
|
|
22
|
-
reset(): void;
|
|
23
|
-
}
|
|
24
|
-
export declare function createReplyDedupRuntime(): ReplyDedupRuntime;
|
|
25
13
|
export declare function resetTransportReplyDedup(): void;
|
|
26
14
|
/** Keeps a successfully published transition notice as the first reply of the
|
|
27
15
|
* next agent turn. The following agent-start reset consumes this one-shot
|
|
@@ -67,7 +55,7 @@ export interface TelegramReplyDeliveryDeps<TReplyMarkup> {
|
|
|
67
55
|
reply_parameters?: TelegramReplyParameters;
|
|
68
56
|
reply_to_message_id?: number;
|
|
69
57
|
message_thread_id?: number;
|
|
70
|
-
}) => Promise<TelegramSentMessage>;
|
|
58
|
+
}, options?: Pick<TelegramApiCallOptions, "assertAuthority">) => Promise<TelegramSentMessage>;
|
|
71
59
|
editMessage: (body: {
|
|
72
60
|
chat_id: number;
|
|
73
61
|
message_id: number;
|
|
@@ -76,9 +64,9 @@ export interface TelegramReplyDeliveryDeps<TReplyMarkup> {
|
|
|
76
64
|
parse_mode?: "HTML";
|
|
77
65
|
reply_markup?: TReplyMarkup;
|
|
78
66
|
message_thread_id?: number;
|
|
79
|
-
}) => Promise<unknown>;
|
|
67
|
+
}, options?: Pick<TelegramApiCallOptions, "assertAuthority">) => Promise<unknown>;
|
|
80
68
|
}
|
|
81
|
-
export interface TelegramReplyTargetOptions {
|
|
69
|
+
export interface TelegramReplyTargetOptions extends Pick<TelegramApiCallOptions, "assertAuthority"> {
|
|
82
70
|
target?: TelegramTarget;
|
|
83
71
|
replyToMessageId?: number;
|
|
84
72
|
}
|
|
@@ -109,11 +97,18 @@ export interface TelegramReplyRuntimeDeps<TReplyMarkup = unknown> {
|
|
|
109
97
|
} & TelegramReplyTargetOptions) => Promise<number | undefined>;
|
|
110
98
|
}
|
|
111
99
|
export declare function sendTelegramPlainReply(text: string, deps: TelegramReplyRuntimeDeps, options?: TelegramTextReplyOptions): Promise<number | undefined>;
|
|
100
|
+
/** An open fenced code block: its marker character and the minimum closing run length. */
|
|
101
|
+
export interface TelegramMarkdownFence {
|
|
102
|
+
marker: "`" | "~";
|
|
103
|
+
length: number;
|
|
104
|
+
}
|
|
105
|
+
export declare function parseTelegramMarkdownFenceOpening(line: string): TelegramMarkdownFence | undefined;
|
|
106
|
+
export declare function isTelegramMarkdownFenceClosing(line: string, fence: TelegramMarkdownFence): boolean;
|
|
112
107
|
export declare function normalizeTelegramNativeMarkdown(markdown: string): string;
|
|
113
108
|
export declare function splitTelegramNativeMarkdown(markdown: string): string[];
|
|
114
109
|
export declare function sendTelegramNativeMarkdownReply<TReplyMarkup = unknown>(chatId: number, replyToMessageId: number | undefined, markdown: string, deps: {
|
|
115
110
|
recordOwnership?: TelegramReplyOwnershipRecorder["record"];
|
|
116
|
-
sendRichMessage: (body: TelegramSendRichMessageBody) => Promise<TelegramSentMessage>;
|
|
111
|
+
sendRichMessage: (body: TelegramSendRichMessageBody, options?: Pick<TelegramApiCallOptions, "assertAuthority">) => Promise<TelegramSentMessage>;
|
|
117
112
|
}, options?: TelegramReplyTargetOptions & {
|
|
118
113
|
replyMarkup?: TReplyMarkup;
|
|
119
114
|
}): Promise<number | undefined>;
|
|
@@ -125,14 +120,14 @@ export interface TelegramRenderedMessageRuntimeDeps<TReplyMarkup> {
|
|
|
125
120
|
replyTransport: TelegramReplyTransport<TReplyMarkup>;
|
|
126
121
|
recordOwnership?: TelegramReplyOwnershipRecorder["record"];
|
|
127
122
|
getAssistantRenderingMode?: () => TelegramAssistantRenderingMode;
|
|
128
|
-
sendRichMessage: (body: TelegramSendRichMessageBody) => Promise<TelegramSentMessage>;
|
|
123
|
+
sendRichMessage: (body: TelegramSendRichMessageBody, options?: Pick<TelegramApiCallOptions, "assertAuthority">) => Promise<TelegramSentMessage>;
|
|
129
124
|
}
|
|
130
125
|
export interface TelegramRenderedMessageRuntime<TReplyMarkup> {
|
|
131
126
|
sendTextReply: (chatId: number, replyToMessageId: number | undefined, text: string, options?: TelegramTextReplyOptions) => Promise<number | undefined>;
|
|
132
127
|
sendMarkdownReply: (chatId: number, replyToMessageId: number | undefined, markdown: string, options?: TelegramReplyTargetOptions & {
|
|
133
128
|
replyMarkup?: TReplyMarkup;
|
|
134
129
|
}) => Promise<number | undefined>;
|
|
135
|
-
editInteractiveMessage: (chatId: number, messageId: number, text: string, mode: TelegramRenderMode, replyMarkup: TReplyMarkup) => Promise<void>;
|
|
130
|
+
editInteractiveMessage: (chatId: number, messageId: number, text: string, mode: TelegramRenderMode, replyMarkup: TReplyMarkup, options?: TelegramReplyTargetOptions) => Promise<void>;
|
|
136
131
|
sendInteractiveMessage: (chatId: number, text: string, mode: TelegramRenderMode, replyMarkup: TReplyMarkup, options?: TelegramReplyTargetOptions) => Promise<number | undefined>;
|
|
137
132
|
sendSectionRichMessage: (chatId: number, message: TelegramInputRichMessage, options?: TelegramReplyTargetOptions) => Promise<number>;
|
|
138
133
|
}
|
|
@@ -144,13 +139,10 @@ export interface TelegramRenderedMessageDeliveryRuntimeDeps<TReplyMarkup> extend
|
|
|
144
139
|
mode?: TelegramRenderMode;
|
|
145
140
|
}) => TelegramRenderedChunk[];
|
|
146
141
|
getAssistantRenderingMode?: () => TelegramAssistantRenderingMode;
|
|
147
|
-
sendRichMessage: (body: TelegramSendRichMessageBody) => Promise<TelegramSentMessage>;
|
|
142
|
+
sendRichMessage: (body: TelegramSendRichMessageBody, options?: Pick<TelegramApiCallOptions, "assertAuthority">) => Promise<TelegramSentMessage>;
|
|
148
143
|
}
|
|
149
144
|
export declare function createTelegramRenderedMessageDeliveryRuntime<TReplyMarkup>(deps: TelegramRenderedMessageDeliveryRuntimeDeps<TReplyMarkup>): TelegramRenderedMessageDeliveryRuntime<TReplyMarkup>;
|
|
150
145
|
export declare function createTelegramRenderedMessageRuntime<TReplyMarkup>(deps: TelegramRenderedMessageRuntimeDeps<TReplyMarkup>): TelegramRenderedMessageRuntime<TReplyMarkup>;
|
|
151
|
-
/** Wrap a sendTextReply with reply dedup so only the first message
|
|
152
|
-
* in a turn carries reply metadata. */
|
|
153
|
-
export declare function dedupSendTextReply(dedup: ReplyDedupRuntime, inner: (chatId: number, replyToMessageId: number | undefined, text: string, options?: TelegramTextReplyOptions) => Promise<number | undefined>): (chatId: number, replyToMessageId: number, text: string, options?: TelegramTextReplyOptions) => Promise<number | undefined>;
|
|
154
146
|
/**
|
|
155
147
|
* Guest reply sender: answers guest queries with native Rich Markdown content.
|
|
156
148
|
* Guest queries use InlineQueryResult input_message_content rather than chat
|