@jungjaehoon/mama-os 0.75.0 → 0.77.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/CHANGELOG.md +112 -0
- package/README.md +1 -1
- package/dist/api/attachment-actions.d.ts.map +1 -1
- package/dist/api/attachment-actions.js +8 -7
- package/dist/api/attachment-actions.js.map +1 -1
- package/dist/api/code-act-actions.d.ts +3 -1
- package/dist/api/code-act-actions.d.ts.map +1 -1
- package/dist/api/code-act-actions.js +21 -9
- package/dist/api/code-act-actions.js.map +1 -1
- package/dist/api/document-actions.d.ts +13 -0
- package/dist/api/document-actions.d.ts.map +1 -0
- package/dist/api/document-actions.js +55 -0
- package/dist/api/document-actions.js.map +1 -0
- package/dist/api/drive-actions.d.ts +4 -0
- package/dist/api/drive-actions.d.ts.map +1 -1
- package/dist/api/drive-actions.js +15 -7
- package/dist/api/drive-actions.js.map +1 -1
- package/dist/api/drive-delivery.d.ts.map +1 -1
- package/dist/api/drive-delivery.js +8 -3
- package/dist/api/drive-delivery.js.map +1 -1
- package/dist/api/member-enrollment.d.ts +9 -3
- package/dist/api/member-enrollment.d.ts.map +1 -1
- package/dist/api/member-enrollment.js +64 -16
- package/dist/api/member-enrollment.js.map +1 -1
- package/dist/api/member-export.d.ts.map +1 -1
- package/dist/api/member-export.js +14 -6
- package/dist/api/member-export.js.map +1 -1
- package/dist/api/member-lifecycle.d.ts +25 -4
- package/dist/api/member-lifecycle.d.ts.map +1 -1
- package/dist/api/member-lifecycle.js +88 -21
- package/dist/api/member-lifecycle.js.map +1 -1
- package/dist/api/member-notice-actions.d.ts +18 -0
- package/dist/api/member-notice-actions.d.ts.map +1 -0
- package/dist/api/member-notice-actions.js +184 -0
- package/dist/api/member-notice-actions.js.map +1 -0
- package/dist/api/member-records.d.ts +17 -2
- package/dist/api/member-records.d.ts.map +1 -1
- package/dist/api/member-records.js +440 -82
- package/dist/api/member-records.js.map +1 -1
- package/dist/api/member-share-actions.d.ts +3 -3
- package/dist/api/member-share-actions.d.ts.map +1 -1
- package/dist/api/member-share-actions.js +47 -20
- package/dist/api/member-share-actions.js.map +1 -1
- package/dist/api/owner-policy-actions.d.ts +1 -0
- package/dist/api/owner-policy-actions.d.ts.map +1 -1
- package/dist/api/owner-policy-actions.js +8 -4
- package/dist/api/owner-policy-actions.js.map +1 -1
- package/dist/api/owner-timezone-actions.d.ts +5 -2
- package/dist/api/owner-timezone-actions.d.ts.map +1 -1
- package/dist/api/owner-timezone-actions.js +4 -6
- package/dist/api/owner-timezone-actions.js.map +1 -1
- package/dist/api/personal-job-actions.d.ts +6 -0
- package/dist/api/personal-job-actions.d.ts.map +1 -0
- package/dist/api/personal-job-actions.js +72 -0
- package/dist/api/personal-job-actions.js.map +1 -0
- package/dist/api/security-events.d.ts +6 -1
- package/dist/api/security-events.d.ts.map +1 -1
- package/dist/api/security-events.js +34 -15
- package/dist/api/security-events.js.map +1 -1
- package/dist/api/skill-actions.d.ts +5 -0
- package/dist/api/skill-actions.d.ts.map +1 -0
- package/dist/api/skill-actions.js +66 -0
- package/dist/api/skill-actions.js.map +1 -0
- package/dist/api/source-actions.d.ts.map +1 -1
- package/dist/api/source-actions.js +4 -2
- package/dist/api/source-actions.js.map +1 -1
- package/dist/api/stored-source-reader.d.ts.map +1 -1
- package/dist/api/stored-source-reader.js +9 -2
- package/dist/api/stored-source-reader.js.map +1 -1
- package/dist/api/trello-actions.d.ts.map +1 -1
- package/dist/api/trello-actions.js +8 -2
- package/dist/api/trello-actions.js.map +1 -1
- package/dist/api/work-actions.d.ts +12 -2
- package/dist/api/work-actions.d.ts.map +1 -1
- package/dist/api/work-actions.js +57 -10
- package/dist/api/work-actions.js.map +1 -1
- package/dist/cli/commands/daemon.d.ts +12 -2
- package/dist/cli/commands/daemon.d.ts.map +1 -1
- package/dist/cli/commands/daemon.js +85 -13
- package/dist/cli/commands/daemon.js.map +1 -1
- package/dist/cli/commands/init.d.ts +4 -0
- package/dist/cli/commands/init.d.ts.map +1 -1
- package/dist/cli/commands/init.js +23 -0
- package/dist/cli/commands/init.js.map +1 -1
- package/dist/cli/commands/tools.d.ts +12 -0
- package/dist/cli/commands/tools.d.ts.map +1 -0
- package/dist/cli/commands/tools.js +109 -0
- package/dist/cli/commands/tools.js.map +1 -0
- package/dist/cli/index.js +6 -1
- package/dist/cli/index.js.map +1 -1
- package/dist/cli/runtime/claude-caller-config.d.ts +3 -1
- package/dist/cli/runtime/claude-caller-config.d.ts.map +1 -1
- package/dist/cli/runtime/claude-caller-config.js +14 -2
- package/dist/cli/runtime/claude-caller-config.js.map +1 -1
- package/dist/connectors/framework/polling-scheduler.d.ts +6 -1
- package/dist/connectors/framework/polling-scheduler.d.ts.map +1 -1
- package/dist/connectors/framework/polling-scheduler.js +25 -3
- package/dist/connectors/framework/polling-scheduler.js.map +1 -1
- package/dist/connectors/framework/stored-index-read.d.ts +2 -2
- package/dist/connectors/framework/stored-index-read.d.ts.map +1 -1
- package/dist/connectors/framework/stored-index-read.js +10 -3
- package/dist/connectors/framework/stored-index-read.js.map +1 -1
- package/dist/gateways/base-gateway.d.ts +7 -0
- package/dist/gateways/base-gateway.d.ts.map +1 -1
- package/dist/gateways/base-gateway.js +24 -0
- package/dist/gateways/base-gateway.js.map +1 -1
- package/dist/gateways/discord.d.ts +4 -0
- package/dist/gateways/discord.d.ts.map +1 -1
- package/dist/gateways/discord.js +24 -6
- package/dist/gateways/discord.js.map +1 -1
- package/dist/gateways/slack.d.ts +3 -0
- package/dist/gateways/slack.d.ts.map +1 -1
- package/dist/gateways/slack.js +15 -2
- package/dist/gateways/slack.js.map +1 -1
- package/dist/gateways/telegram-errors.d.ts +3 -0
- package/dist/gateways/telegram-errors.d.ts.map +1 -1
- package/dist/gateways/telegram-errors.js +5 -0
- package/dist/gateways/telegram-errors.js.map +1 -1
- package/dist/gateways/telegram-message-ledger.d.ts +7 -0
- package/dist/gateways/telegram-message-ledger.d.ts.map +1 -1
- package/dist/gateways/telegram-message-ledger.js +27 -2
- package/dist/gateways/telegram-message-ledger.js.map +1 -1
- package/dist/gateways/telegram.d.ts +24 -6
- package/dist/gateways/telegram.d.ts.map +1 -1
- package/dist/gateways/telegram.js +258 -44
- package/dist/gateways/telegram.js.map +1 -1
- package/dist/gateways/turn-contract.d.ts +6 -0
- package/dist/gateways/turn-contract.d.ts.map +1 -1
- package/dist/runtime/action-surface.d.ts +28 -3
- package/dist/runtime/action-surface.d.ts.map +1 -1
- package/dist/runtime/action-surface.js +316 -34
- package/dist/runtime/action-surface.js.map +1 -1
- package/dist/runtime/backend-security.d.ts +4 -0
- package/dist/runtime/backend-security.d.ts.map +1 -1
- package/dist/runtime/backend-security.js +8 -1
- package/dist/runtime/backend-security.js.map +1 -1
- package/dist/runtime/claude-caller-hook.d.ts +14 -0
- package/dist/runtime/claude-caller-hook.d.ts.map +1 -1
- package/dist/runtime/claude-caller-hook.js +21 -1
- package/dist/runtime/claude-caller-hook.js.map +1 -1
- package/dist/runtime/config.d.ts +6 -0
- package/dist/runtime/config.d.ts.map +1 -1
- package/dist/runtime/config.js +24 -0
- package/dist/runtime/config.js.map +1 -1
- package/dist/runtime/connectors.js +1 -1
- package/dist/runtime/connectors.js.map +1 -1
- package/dist/runtime/egress-proxy.d.ts +3 -3
- package/dist/runtime/egress-proxy.d.ts.map +1 -1
- package/dist/runtime/egress-proxy.js +16 -3
- package/dist/runtime/egress-proxy.js.map +1 -1
- package/dist/runtime/member-paths.d.ts +9 -1
- package/dist/runtime/member-paths.d.ts.map +1 -1
- package/dist/runtime/member-paths.js +67 -5
- package/dist/runtime/member-paths.js.map +1 -1
- package/dist/runtime/member-session.d.ts +21 -21
- package/dist/runtime/member-session.d.ts.map +1 -1
- package/dist/runtime/member-session.js +297 -34
- package/dist/runtime/member-session.js.map +1 -1
- package/dist/runtime/member-system-prompt.d.ts +3 -0
- package/dist/runtime/member-system-prompt.d.ts.map +1 -1
- package/dist/runtime/member-system-prompt.js +30 -4
- package/dist/runtime/member-system-prompt.js.map +1 -1
- package/dist/runtime/native-session.d.ts +8 -0
- package/dist/runtime/native-session.d.ts.map +1 -1
- package/dist/runtime/native-session.js +105 -17
- package/dist/runtime/native-session.js.map +1 -1
- package/dist/runtime/outbound-attempts.d.ts +5 -2
- package/dist/runtime/outbound-attempts.d.ts.map +1 -1
- package/dist/runtime/outbound-attempts.js +3 -2
- package/dist/runtime/outbound-attempts.js.map +1 -1
- package/dist/runtime/owner-authority.d.ts +16 -14
- package/dist/runtime/owner-authority.d.ts.map +1 -1
- package/dist/runtime/owner-authority.js +163 -7
- package/dist/runtime/owner-authority.js.map +1 -1
- package/dist/runtime/owner-runtime.d.ts +18 -5
- package/dist/runtime/owner-runtime.d.ts.map +1 -1
- package/dist/runtime/owner-runtime.js +429 -43
- package/dist/runtime/owner-runtime.js.map +1 -1
- package/dist/runtime/owner-system-prompt.d.ts +3 -1
- package/dist/runtime/owner-system-prompt.d.ts.map +1 -1
- package/dist/runtime/owner-system-prompt.js +16 -6
- package/dist/runtime/owner-system-prompt.js.map +1 -1
- package/dist/runtime/personal-job-cron.d.ts +6 -0
- package/dist/runtime/personal-job-cron.d.ts.map +1 -0
- package/dist/runtime/personal-job-cron.js +154 -0
- package/dist/runtime/personal-job-cron.js.map +1 -0
- package/dist/runtime/personal-jobs.d.ts +107 -0
- package/dist/runtime/personal-jobs.d.ts.map +1 -0
- package/dist/runtime/personal-jobs.js +353 -0
- package/dist/runtime/personal-jobs.js.map +1 -0
- package/dist/runtime/personal-notices.d.ts +38 -0
- package/dist/runtime/personal-notices.d.ts.map +1 -0
- package/dist/runtime/personal-notices.js +137 -0
- package/dist/runtime/personal-notices.js.map +1 -0
- package/dist/runtime/personal-turns.d.ts +23 -0
- package/dist/runtime/personal-turns.d.ts.map +1 -0
- package/dist/runtime/personal-turns.js +51 -0
- package/dist/runtime/personal-turns.js.map +1 -0
- package/dist/runtime/principal-access.d.ts +8 -0
- package/dist/runtime/principal-access.d.ts.map +1 -1
- package/dist/runtime/principal-access.js +70 -2
- package/dist/runtime/principal-access.js.map +1 -1
- package/dist/runtime/principal-sessions.d.ts +16 -2
- package/dist/runtime/principal-sessions.d.ts.map +1 -1
- package/dist/runtime/principal-sessions.js +104 -21
- package/dist/runtime/principal-sessions.js.map +1 -1
- package/dist/runtime/skills.d.ts +86 -0
- package/dist/runtime/skills.d.ts.map +1 -0
- package/dist/runtime/skills.js +560 -0
- package/dist/runtime/skills.js.map +1 -0
- package/dist/runtime/stimulus-delivery.d.ts +21 -1
- package/dist/runtime/stimulus-delivery.d.ts.map +1 -1
- package/dist/runtime/stimulus-delivery.js +185 -18
- package/dist/runtime/stimulus-delivery.js.map +1 -1
- package/dist/runtime/tools-paths.d.ts +9 -0
- package/dist/runtime/tools-paths.d.ts.map +1 -0
- package/dist/runtime/tools-paths.js +98 -0
- package/dist/runtime/tools-paths.js.map +1 -0
- package/dist/runtime/turn-orders.d.ts +22 -2
- package/dist/runtime/turn-orders.d.ts.map +1 -1
- package/dist/runtime/turn-orders.js +60 -6
- package/dist/runtime/turn-orders.js.map +1 -1
- package/dist/runtime/web-fetch-scope.d.ts +20 -0
- package/dist/runtime/web-fetch-scope.d.ts.map +1 -0
- package/dist/runtime/web-fetch-scope.js +68 -0
- package/dist/runtime/web-fetch-scope.js.map +1 -0
- package/dist/storage/chat-sources.d.ts +21 -0
- package/dist/storage/chat-sources.d.ts.map +1 -1
- package/dist/storage/chat-sources.js +65 -3
- package/dist/storage/chat-sources.js.map +1 -1
- package/dist/tools/command-runner.d.ts +12 -0
- package/dist/tools/command-runner.d.ts.map +1 -0
- package/dist/tools/command-runner.js +65 -0
- package/dist/tools/command-runner.js.map +1 -0
- package/dist/tools/document-render.d.ts +24 -0
- package/dist/tools/document-render.d.ts.map +1 -0
- package/dist/tools/document-render.js +208 -0
- package/dist/tools/document-render.js.map +1 -0
- package/dist/tools/document-toolkit.d.ts +15 -0
- package/dist/tools/document-toolkit.d.ts.map +1 -0
- package/dist/tools/document-toolkit.js +501 -0
- package/dist/tools/document-toolkit.js.map +1 -0
- package/dist/tools/office-render.d.ts +28 -0
- package/dist/tools/office-render.d.ts.map +1 -0
- package/dist/tools/office-render.js +101 -0
- package/dist/tools/office-render.js.map +1 -0
- package/dist/tools/toolkit-process.d.ts +9 -0
- package/dist/tools/toolkit-process.d.ts.map +1 -0
- package/dist/tools/toolkit-process.js +148 -0
- package/dist/tools/toolkit-process.js.map +1 -0
- package/dist/tools/toolkit-state.d.ts +22 -0
- package/dist/tools/toolkit-state.d.ts.map +1 -0
- package/dist/tools/toolkit-state.js +116 -0
- package/dist/tools/toolkit-state.js.map +1 -0
- package/package.json +3 -2
- package/toolkit/inspect-office-zip.py +16 -0
- package/toolkit/rasterize.py +29 -0
- package/toolkit/requirements.in +11 -0
- package/toolkit/requirements.lock +1643 -0
- package/toolkit/smoke.py +32 -0
|
@@ -1,9 +1,11 @@
|
|
|
1
1
|
"use strict";
|
|
2
2
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
3
|
exports.TelegramGateway = void 0;
|
|
4
|
+
const personal_turns_js_1 = require("../runtime/personal-turns.js");
|
|
4
5
|
const node_async_hooks_1 = require("node:async_hooks");
|
|
5
6
|
const node_crypto_1 = require("node:crypto");
|
|
6
7
|
const owner_authority_js_1 = require("../runtime/owner-authority.js");
|
|
8
|
+
const principal_access_js_1 = require("../runtime/principal-access.js");
|
|
7
9
|
const node_fs_1 = require("node:fs");
|
|
8
10
|
const node_path_1 = require("node:path");
|
|
9
11
|
const grammy_1 = require("grammy");
|
|
@@ -66,9 +68,6 @@ function chatIdFromSourceMessageRef(value) {
|
|
|
66
68
|
}
|
|
67
69
|
return rest.slice(0, separator);
|
|
68
70
|
}
|
|
69
|
-
function outboundLedgerKey(idempotencyKey) {
|
|
70
|
-
return `outbound:${(0, node_crypto_1.createHash)('sha256').update(`text\0${idempotencyKey}`).digest('hex')}`;
|
|
71
|
-
}
|
|
72
71
|
/** Telegram personal/common ingress and its durable response transport. */
|
|
73
72
|
class TelegramGateway extends base_gateway_js_1.BaseGateway {
|
|
74
73
|
source = 'telegram';
|
|
@@ -84,11 +83,17 @@ class TelegramGateway extends base_gateway_js_1.BaseGateway {
|
|
|
84
83
|
onFatalError;
|
|
85
84
|
messageLedger;
|
|
86
85
|
chatTails = new Map();
|
|
86
|
+
pendingMemberIntakes = new Map();
|
|
87
|
+
stopIntake;
|
|
88
|
+
intakeStopped = new Promise((resolve) => {
|
|
89
|
+
this.stopIntake = () => resolve(undefined);
|
|
90
|
+
});
|
|
91
|
+
enrollmentCompletions = new Set();
|
|
87
92
|
activePresenters = new Map();
|
|
88
93
|
recentMessageIds = new Map();
|
|
89
94
|
activeChat = new node_async_hooks_1.AsyncLocalStorage();
|
|
90
95
|
nextEnrollmentRequestId = (0, node_crypto_1.randomInt)(1, 0x7fffffff);
|
|
91
|
-
|
|
96
|
+
pendingEnrollments = new Map();
|
|
92
97
|
onMemberSelection;
|
|
93
98
|
recordMemberSelection;
|
|
94
99
|
bot = null;
|
|
@@ -134,6 +139,10 @@ class TelegramGateway extends base_gateway_js_1.BaseGateway {
|
|
|
134
139
|
async start() {
|
|
135
140
|
if (this.connected)
|
|
136
141
|
return;
|
|
142
|
+
this.accepting = true;
|
|
143
|
+
this.intakeStopped = new Promise((resolve) => {
|
|
144
|
+
this.stopIntake = () => resolve(undefined);
|
|
145
|
+
});
|
|
137
146
|
if (!this.config.allowedChats?.some((chatId) => chatId.trim().length > 0)) {
|
|
138
147
|
throw new Error('telegram gateway disabled: allowed_chats is not set. Run: mama status');
|
|
139
148
|
}
|
|
@@ -178,10 +187,21 @@ class TelegramGateway extends base_gateway_js_1.BaseGateway {
|
|
|
178
187
|
throw error;
|
|
179
188
|
}
|
|
180
189
|
}
|
|
181
|
-
async
|
|
182
|
-
this.
|
|
190
|
+
async beginStop() {
|
|
191
|
+
this.accepting = false;
|
|
192
|
+
this.stopIntake();
|
|
183
193
|
if (this.bot)
|
|
184
194
|
await this.bot.stop().catch(() => { });
|
|
195
|
+
}
|
|
196
|
+
async drain() {
|
|
197
|
+
await super.drain();
|
|
198
|
+
await Promise.allSettled([...this.enrollmentCompletions]);
|
|
199
|
+
await Promise.allSettled([...this.chatTails.values()]);
|
|
200
|
+
}
|
|
201
|
+
async stop() {
|
|
202
|
+
await this.beginStop();
|
|
203
|
+
await this.drain();
|
|
204
|
+
this.pendingEnrollments.clear();
|
|
185
205
|
this.bot = null;
|
|
186
206
|
this.connected = false;
|
|
187
207
|
this.recentMessageIds.clear();
|
|
@@ -202,7 +222,7 @@ class TelegramGateway extends base_gateway_js_1.BaseGateway {
|
|
|
202
222
|
}
|
|
203
223
|
if (existing.state === 'delivered')
|
|
204
224
|
return;
|
|
205
|
-
this.responseIntake(sourceRef);
|
|
225
|
+
await this.responseIntake(sourceRef);
|
|
206
226
|
if (existing.state === 'ready' && existing.response !== response) {
|
|
207
227
|
throw new Error('Telegram response conflicts with its durable ledger entry');
|
|
208
228
|
}
|
|
@@ -212,6 +232,18 @@ class TelegramGateway extends base_gateway_js_1.BaseGateway {
|
|
|
212
232
|
return;
|
|
213
233
|
await this.deliverReadyEntry(sourceRef);
|
|
214
234
|
}
|
|
235
|
+
/** Close one accepted input with the configured host notice, preserving any ready reply. */
|
|
236
|
+
async interruptResponse(sourceRef) {
|
|
237
|
+
const entry = this.messageLedger.get(sourceRef);
|
|
238
|
+
if (!entry)
|
|
239
|
+
throw new Error(`Telegram interruption has no accepted message ${sourceRef}`);
|
|
240
|
+
if (entry.state === 'delivered')
|
|
241
|
+
return;
|
|
242
|
+
await this.responseIntake(sourceRef);
|
|
243
|
+
if (entry.state === 'processing')
|
|
244
|
+
this.messageLedger.markInterrupted(sourceRef, this.interruptedNotice, 'html-v1');
|
|
245
|
+
await this.deliverReadyEntry(sourceRef);
|
|
246
|
+
}
|
|
215
247
|
async sendMessage(chatId, text, idempotencyKey) {
|
|
216
248
|
if (!this.bot || !this.connected)
|
|
217
249
|
throw new Error('Telegram gateway not connected');
|
|
@@ -228,6 +260,19 @@ class TelegramGateway extends base_gateway_js_1.BaseGateway {
|
|
|
228
260
|
await this.sendMessage(ownerChatId, text, idempotencyKey);
|
|
229
261
|
}
|
|
230
262
|
/** Host receipt only. The dispatcher never exposes arbitrary member text/destination input. */
|
|
263
|
+
memberTextDelivered(access, operationId) {
|
|
264
|
+
const destinations = access.destinations?.filter((target) => target.kind === 'telegram');
|
|
265
|
+
if (destinations?.length !== 1 || !destinations[0].id.trim())
|
|
266
|
+
throw new Error('Member text delivery requires exactly one Telegram destination');
|
|
267
|
+
if (!operationId.trim())
|
|
268
|
+
throw new Error('Member text operation id is required');
|
|
269
|
+
const entry = this.messageLedger.get((0, telegram_message_ledger_js_1.outboundLedgerKey)(operationId));
|
|
270
|
+
return (entry?.state === 'delivered' && entry.deliveryTarget === `telegram:${destinations[0].id}`);
|
|
271
|
+
}
|
|
272
|
+
/** Host-only retirement after the mailbox occurrence closes, including inactive recipients. */
|
|
273
|
+
retireMemberJobText(operationId) {
|
|
274
|
+
(0, telegram_message_ledger_js_1.retirePersonalTurnReceipt)(this.messageLedger, operationId);
|
|
275
|
+
}
|
|
231
276
|
async sendMemberText(access, text, operationId) {
|
|
232
277
|
const destinations = access.destinations?.filter((target) => target.kind === 'telegram');
|
|
233
278
|
if (destinations?.length !== 1 || !destinations[0].id.trim())
|
|
@@ -238,9 +283,12 @@ class TelegramGateway extends base_gateway_js_1.BaseGateway {
|
|
|
238
283
|
throw new Error('Member text operation id is required');
|
|
239
284
|
const chatId = destinations[0].id;
|
|
240
285
|
await this.runInChatQueue(chatId, () => this.sendMessageNow(chatId, text, operationId));
|
|
241
|
-
return this.
|
|
286
|
+
return this.memberTextDelivered(access, operationId);
|
|
242
287
|
}
|
|
243
|
-
|
|
288
|
+
sendFile(path, caption, operationId, member) {
|
|
289
|
+
return this.trackDelivery(() => this.sendFileNow(path, caption, operationId, member));
|
|
290
|
+
}
|
|
291
|
+
async sendFileNow(path, caption, operationId, member) {
|
|
244
292
|
if (!this.bot || !this.connected)
|
|
245
293
|
throw new Error('Telegram gateway not connected');
|
|
246
294
|
if (operationId.trim() === '')
|
|
@@ -253,6 +301,8 @@ class TelegramGateway extends base_gateway_js_1.BaseGateway {
|
|
|
253
301
|
if (destinations?.length !== 1 || !destinations[0].id.trim())
|
|
254
302
|
throw new Error('Member file delivery requires exactly one Telegram destination');
|
|
255
303
|
chatId = destinations[0].id;
|
|
304
|
+
if ((0, principal_access_js_1.roomChannel)(member.access) !== undefined)
|
|
305
|
+
this.requireAllowedChat(chatId);
|
|
256
306
|
filesRoot = member.filesRoot;
|
|
257
307
|
}
|
|
258
308
|
else {
|
|
@@ -304,12 +354,12 @@ class TelegramGateway extends base_gateway_js_1.BaseGateway {
|
|
|
304
354
|
}
|
|
305
355
|
}
|
|
306
356
|
/** The source ref was verified as an owner turn by the runtime; only an owner DM can select. */
|
|
307
|
-
async requestMemberEnrollment(sourceRef) {
|
|
357
|
+
async requestMemberEnrollment(sourceRef, requestingOwnerUserId, name) {
|
|
308
358
|
if (!sourceRef.startsWith('telegram:'))
|
|
309
359
|
throw Object.assign(new Error("Enrollment requires the owner's own Telegram DM"), {
|
|
310
360
|
name: 'denied',
|
|
311
361
|
});
|
|
312
|
-
const ownerUserId =
|
|
362
|
+
const ownerUserId = requestingOwnerUserId;
|
|
313
363
|
const chatId = Number(ownerUserId);
|
|
314
364
|
if (!this.bot || !this.onMemberSelection || !this.ownerAllowed(ownerUserId)) {
|
|
315
365
|
throw Object.assign(new Error("Enrollment requires the owner's own Telegram DM"), {
|
|
@@ -318,7 +368,12 @@ class TelegramGateway extends base_gateway_js_1.BaseGateway {
|
|
|
318
368
|
}
|
|
319
369
|
const requestId = this.nextEnrollmentRequestId;
|
|
320
370
|
this.nextEnrollmentRequestId = requestId === 0x7fffffff ? 1 : requestId + 1;
|
|
321
|
-
this.
|
|
371
|
+
this.pendingEnrollments.set(ownerUserId, {
|
|
372
|
+
requestId,
|
|
373
|
+
sourceMessageRef: sourceRef,
|
|
374
|
+
ownerUserId,
|
|
375
|
+
...(name === undefined ? {} : { name }),
|
|
376
|
+
});
|
|
322
377
|
// Telegram shows a request_users button only on the reply keyboard, under the message box.
|
|
323
378
|
const prompt = 'Choose one member to enroll with the Choose member button under the message box, not in the chat. If it is hidden, open it with the keyboard icon in the message box.';
|
|
324
379
|
try {
|
|
@@ -338,15 +393,24 @@ class TelegramGateway extends base_gateway_js_1.BaseGateway {
|
|
|
338
393
|
});
|
|
339
394
|
}
|
|
340
395
|
catch (error) {
|
|
341
|
-
if (this.
|
|
342
|
-
this.
|
|
396
|
+
if (this.pendingEnrollments.get(ownerUserId)?.requestId === requestId)
|
|
397
|
+
this.pendingEnrollments.delete(ownerUserId);
|
|
343
398
|
throw error;
|
|
344
399
|
}
|
|
345
400
|
}
|
|
401
|
+
cancelMemberEnrollment(principalId) {
|
|
402
|
+
for (const ownerUserId of this.pendingEnrollments.keys()) {
|
|
403
|
+
if (this.registry.resolveByExternal('telegram', 'private', ownerUserId)?.principalId ===
|
|
404
|
+
principalId)
|
|
405
|
+
this.pendingEnrollments.delete(ownerUserId);
|
|
406
|
+
}
|
|
407
|
+
}
|
|
346
408
|
async completeMemberSelection(message, senderPrincipalId) {
|
|
347
409
|
const chatId = String(message.chat.id);
|
|
348
410
|
const ownerUserId = String(message.from.id);
|
|
349
|
-
if (message.chat.type !== 'private' ||
|
|
411
|
+
if (message.chat.type !== 'private' ||
|
|
412
|
+
chatId !== ownerUserId ||
|
|
413
|
+
!this.ownerAllowed(ownerUserId))
|
|
350
414
|
return;
|
|
351
415
|
let receiptText = 'Enrollment selection received.';
|
|
352
416
|
let deliveryVerified = false;
|
|
@@ -359,7 +423,7 @@ class TelegramGateway extends base_gateway_js_1.BaseGateway {
|
|
|
359
423
|
deliveryVerified = true;
|
|
360
424
|
};
|
|
361
425
|
try {
|
|
362
|
-
const pending = this.
|
|
426
|
+
const pending = this.pendingEnrollments.get(ownerUserId);
|
|
363
427
|
const shared = message.users_shared;
|
|
364
428
|
if (!pending ||
|
|
365
429
|
shared.request_id !== pending.requestId ||
|
|
@@ -368,7 +432,7 @@ class TelegramGateway extends base_gateway_js_1.BaseGateway {
|
|
|
368
432
|
return;
|
|
369
433
|
}
|
|
370
434
|
// Consume before awaiting the serial host chain, so concurrent duplicate updates cannot enroll.
|
|
371
|
-
this.
|
|
435
|
+
this.pendingEnrollments.delete(ownerUserId);
|
|
372
436
|
if (shared.users.length !== 1 ||
|
|
373
437
|
!Number.isSafeInteger(shared.users[0].user_id) ||
|
|
374
438
|
shared.users[0].user_id <= 0) {
|
|
@@ -382,6 +446,7 @@ class TelegramGateway extends base_gateway_js_1.BaseGateway {
|
|
|
382
446
|
sourceMessageRef: pending.sourceMessageRef,
|
|
383
447
|
ownerUserId,
|
|
384
448
|
userId: String(shared.users[0].user_id),
|
|
449
|
+
...(pending.name === undefined ? {} : { name: pending.name }),
|
|
385
450
|
});
|
|
386
451
|
}
|
|
387
452
|
catch (error) {
|
|
@@ -442,7 +507,7 @@ class TelegramGateway extends base_gateway_js_1.BaseGateway {
|
|
|
442
507
|
principal.status === 'active' &&
|
|
443
508
|
(0, owner_authority_js_1.holdsOwnerRole)(this.registry, principal.principalId));
|
|
444
509
|
}
|
|
445
|
-
responseIntake(sourceRef) {
|
|
510
|
+
async responseIntake(sourceRef) {
|
|
446
511
|
const chatId = chatIdFromSourceMessageRef(sourceRef);
|
|
447
512
|
const recipient = this.registry.resolveByExternal('telegram', 'private', chatId);
|
|
448
513
|
if (recipient) {
|
|
@@ -452,6 +517,15 @@ class TelegramGateway extends base_gateway_js_1.BaseGateway {
|
|
|
452
517
|
return this.memberIntake(recipient.principalId);
|
|
453
518
|
}
|
|
454
519
|
this.requireAllowedChat(chatId);
|
|
520
|
+
// A room turn records its member on the ledger; a common message handed to a member's room
|
|
521
|
+
// is found through the durable room input, so a crash between the two cannot misroute it.
|
|
522
|
+
const principalId = this.messageLedger.get(sourceRef)?.principalId ?? this.intake.handedOffTo?.(sourceRef);
|
|
523
|
+
if (principalId !== undefined) {
|
|
524
|
+
const member = this.registry.findById(principalId);
|
|
525
|
+
if (member?.kind !== 'member' || member.status !== 'active')
|
|
526
|
+
throw new Error('Telegram room response requires its recorded active member');
|
|
527
|
+
return this.memberIntake(principalId, chatId);
|
|
528
|
+
}
|
|
455
529
|
return this.intake;
|
|
456
530
|
}
|
|
457
531
|
requireAllowedChat(chatId) {
|
|
@@ -459,7 +533,10 @@ class TelegramGateway extends base_gateway_js_1.BaseGateway {
|
|
|
459
533
|
throw new Error('Telegram destination is not allowlisted');
|
|
460
534
|
}
|
|
461
535
|
}
|
|
462
|
-
|
|
536
|
+
handleMessage(message) {
|
|
537
|
+
return this.trackIntake(() => this.handleMessageInput(message));
|
|
538
|
+
}
|
|
539
|
+
async handleMessageInput(message) {
|
|
463
540
|
if (!message.chat || !message.from)
|
|
464
541
|
return;
|
|
465
542
|
const chatId = String(message.chat.id);
|
|
@@ -468,19 +545,84 @@ class TelegramGateway extends base_gateway_js_1.BaseGateway {
|
|
|
468
545
|
const privateChat = message.chat.type === 'private';
|
|
469
546
|
const admitted = principal?.kind === 'member' &&
|
|
470
547
|
principal.status === 'active' &&
|
|
471
|
-
(privateChat ||
|
|
548
|
+
(privateChat || this.config.allowedChats?.includes(chatId));
|
|
472
549
|
if (message.from.is_bot || !admitted) {
|
|
550
|
+
if (principal?.status === 'suspended')
|
|
551
|
+
this.cancelMemberEnrollment(principal.principalId);
|
|
473
552
|
const chatHash = (0, node_crypto_1.createHash)('sha256').update(`chat\0${chatId}`).digest('hex');
|
|
474
553
|
const senderHash = (0, node_crypto_1.createHash)('sha256').update(`sender\0${userId}`).digest('hex');
|
|
475
554
|
console.warn(`telegram message dropped reason=non_owner chat_hash=${chatHash} sender_hash=${senderHash}`);
|
|
476
555
|
return;
|
|
477
556
|
}
|
|
478
557
|
if (message.users_shared) {
|
|
479
|
-
|
|
558
|
+
// Enrollment can wait for running member turns. Let polling admit other updates while
|
|
559
|
+
// the host completion sends and records its own final receipt.
|
|
560
|
+
const completion = this.completeMemberSelection(message, principal.principalId).catch((error) => this.log(`telegram enrollment completion failed error=${telegramErrorMessage(error)}`));
|
|
561
|
+
this.enrollmentCompletions.add(completion);
|
|
562
|
+
void completion.finally(() => this.enrollmentCompletions.delete(completion));
|
|
563
|
+
return;
|
|
564
|
+
}
|
|
565
|
+
const principalId = principal.principalId;
|
|
566
|
+
const recorded = this.messageLedger.get(sourceMessageRef(chatId, message.message_id));
|
|
567
|
+
const memberRoom = !privateChat && (recorded ? recorded.principalId !== undefined : !this.ownerAllowed(userId));
|
|
568
|
+
const selectedIntake = () => privateChat || memberRoom
|
|
569
|
+
? this.memberIntake(principalId, memberRoom ? chatId : undefined)
|
|
570
|
+
: this.intake;
|
|
571
|
+
const queued = (intake) => {
|
|
572
|
+
const selected = (0, telegram_text_entities_js_1.selectTelegramTextEntities)(message);
|
|
573
|
+
const files = (0, telegram_attachments_js_1.telegramFiles)(message);
|
|
574
|
+
if (!selected.text.trim() && files.length === 0)
|
|
575
|
+
return;
|
|
576
|
+
const ref = sourceMessageRef(chatId, message.message_id);
|
|
577
|
+
const claim = this.claimMessage(message, privateChat || memberRoom ? principalId : undefined);
|
|
578
|
+
this.pendingMemberIntakes.set(chatId, (this.pendingMemberIntakes.get(chatId) ?? 0) + 1);
|
|
579
|
+
void this.runInChatQueue(chatId, async () => {
|
|
580
|
+
const target = await Promise.race([
|
|
581
|
+
Promise.resolve(intake ?? selectedIntake()),
|
|
582
|
+
this.intakeStopped,
|
|
583
|
+
]);
|
|
584
|
+
// The claimed inbound ledger already preserves this wait. Stop leaves it for startup
|
|
585
|
+
// interruption recovery rather than waiting for a directory/model slot to open.
|
|
586
|
+
if (!target || !this.accepting)
|
|
587
|
+
return;
|
|
588
|
+
await this.acceptMessage(message, principalId, target, claim.claimed ? ref : undefined);
|
|
589
|
+
})
|
|
590
|
+
.catch((error) => {
|
|
591
|
+
this.lastError = telegramErrorMessage(error);
|
|
592
|
+
this.log(`telegram deferred intake failed error=${this.lastError}`);
|
|
593
|
+
})
|
|
594
|
+
.finally(() => {
|
|
595
|
+
const pending = this.pendingMemberIntakes.get(chatId) - 1;
|
|
596
|
+
if (pending)
|
|
597
|
+
this.pendingMemberIntakes.set(chatId, pending);
|
|
598
|
+
else
|
|
599
|
+
this.pendingMemberIntakes.delete(chatId);
|
|
600
|
+
});
|
|
601
|
+
};
|
|
602
|
+
// While an intake for this chat waits, every later message queues behind it, whichever
|
|
603
|
+
// session it selects, so the group's order holds.
|
|
604
|
+
if (this.pendingMemberIntakes.has(chatId)) {
|
|
605
|
+
queued();
|
|
606
|
+
return;
|
|
607
|
+
}
|
|
608
|
+
const intake = selectedIntake();
|
|
609
|
+
if ('then' in intake) {
|
|
610
|
+
queued(intake);
|
|
611
|
+
return;
|
|
612
|
+
}
|
|
613
|
+
await this.acceptMessage(message, principalId, intake);
|
|
614
|
+
}
|
|
615
|
+
async acceptMessage(message, principalId, intake, queuedClaim) {
|
|
616
|
+
const chatId = String(message.chat.id);
|
|
617
|
+
const privateChat = message.chat.type === 'private';
|
|
618
|
+
const principal = this.registry.resolveByExternal('telegram', 'private', String(message.from.id));
|
|
619
|
+
if (principal?.principalId !== principalId || principal.status !== 'active') {
|
|
620
|
+
const chatHash = (0, node_crypto_1.createHash)('sha256').update(`chat\0${chatId}`).digest('hex');
|
|
621
|
+
const senderHash = (0, node_crypto_1.createHash)('sha256').update(`sender\0${message.from.id}`).digest('hex');
|
|
622
|
+
console.warn(`telegram message dropped reason=principal_inactive chat_hash=${chatHash} sender_hash=${senderHash}`);
|
|
480
623
|
return;
|
|
481
624
|
}
|
|
482
625
|
const ref = sourceMessageRef(chatId, message.message_id);
|
|
483
|
-
const intake = privateChat ? this.memberIntake(principal.principalId) : this.intake;
|
|
484
626
|
const now = Date.now();
|
|
485
627
|
const previous = this.recentMessageIds.get(ref);
|
|
486
628
|
if (previous !== undefined && now - previous <= MESSAGE_DEDUP_TTL_MS)
|
|
@@ -501,9 +643,8 @@ class TelegramGateway extends base_gateway_js_1.BaseGateway {
|
|
|
501
643
|
return;
|
|
502
644
|
if (durable?.state === 'processing' && intake.isPending?.(ref))
|
|
503
645
|
return;
|
|
504
|
-
if (durable?.state === 'processing') {
|
|
505
|
-
this.
|
|
506
|
-
await this.deliverReadyEntry(ref);
|
|
646
|
+
if (durable?.state === 'processing' && queuedClaim !== ref) {
|
|
647
|
+
await this.interruptResponse(ref);
|
|
507
648
|
return;
|
|
508
649
|
}
|
|
509
650
|
const selected = (0, telegram_text_entities_js_1.selectTelegramTextEntities)(message);
|
|
@@ -511,13 +652,23 @@ class TelegramGateway extends base_gateway_js_1.BaseGateway {
|
|
|
511
652
|
if (!selected.text.trim() && files.length === 0)
|
|
512
653
|
return;
|
|
513
654
|
const formatting = (0, telegram_text_entities_js_1.captureTelegramTextFormatting)(selected.field, selected.text, selected.entities);
|
|
514
|
-
const
|
|
515
|
-
|
|
516
|
-
|
|
517
|
-
|
|
518
|
-
|
|
519
|
-
|
|
520
|
-
|
|
655
|
+
const memberRoom = !privateChat &&
|
|
656
|
+
(durable ? durable.principalId !== undefined : !(0, owner_authority_js_1.holdsOwnerRole)(this.registry, principalId));
|
|
657
|
+
const ledgerEntry = this.claimMessage(message, privateChat || memberRoom ? principalId : undefined).entry;
|
|
658
|
+
if (intake.deferIncoming?.()) {
|
|
659
|
+
intake.acceptOwnerMessage({
|
|
660
|
+
id: ref,
|
|
661
|
+
channelKey: chatId,
|
|
662
|
+
senderPrincipalId: principalId,
|
|
663
|
+
occurredAt: message.date * 1000,
|
|
664
|
+
text: selected.text,
|
|
665
|
+
payload: { pendingTelegramMessage: JSON.parse(JSON.stringify(message)) },
|
|
666
|
+
});
|
|
667
|
+
const pendingPresenter = this.createResponsePresenter(ref, Number(message.chat.id));
|
|
668
|
+
this.activePresenters.set(ref, pendingPresenter);
|
|
669
|
+
await pendingPresenter.start();
|
|
670
|
+
return;
|
|
671
|
+
}
|
|
521
672
|
const presenter = this.createResponsePresenter(ref, Number(message.chat.id));
|
|
522
673
|
this.activePresenters.set(ref, presenter);
|
|
523
674
|
try {
|
|
@@ -526,8 +677,9 @@ class TelegramGateway extends base_gateway_js_1.BaseGateway {
|
|
|
526
677
|
const attachments = await (0, telegram_attachments_js_1.downloadTelegramFiles)(files, {
|
|
527
678
|
api: this.bot.api,
|
|
528
679
|
token: this.token,
|
|
529
|
-
downloadsDir: privateChat
|
|
530
|
-
? this.principalPaths(principal.principalId)
|
|
680
|
+
downloadsDir: privateChat || memberRoom
|
|
681
|
+
? this.principalPaths(principal.principalId, memberRoom ? chatId : undefined)
|
|
682
|
+
.downloadsDir
|
|
531
683
|
: this.downloadsDir,
|
|
532
684
|
messageId: message.message_id,
|
|
533
685
|
});
|
|
@@ -561,6 +713,48 @@ class TelegramGateway extends base_gateway_js_1.BaseGateway {
|
|
|
561
713
|
throw error;
|
|
562
714
|
}
|
|
563
715
|
}
|
|
716
|
+
/** Host preparation of a transport-verified envelope accepted while the member was retired. */
|
|
717
|
+
async prepareDeferredMemberInput(principalId, input) {
|
|
718
|
+
const payload = input.payload;
|
|
719
|
+
const message = payload.pendingTelegramMessage;
|
|
720
|
+
const person = this.registry.resolveByExternal('telegram', 'private', String(message.from.id));
|
|
721
|
+
if (person?.principalId !== principalId ||
|
|
722
|
+
person.status !== 'active' ||
|
|
723
|
+
sourceMessageRef(String(message.chat.id), message.message_id) !== input.id)
|
|
724
|
+
throw new Error('Deferred Telegram input does not belong to this active person');
|
|
725
|
+
const selected = (0, telegram_text_entities_js_1.selectTelegramTextEntities)(message);
|
|
726
|
+
const formatting = (0, telegram_text_entities_js_1.captureTelegramTextFormatting)(selected.field, selected.text, selected.entities);
|
|
727
|
+
const attachments = await (0, telegram_attachments_js_1.downloadTelegramFiles)((0, telegram_attachments_js_1.telegramFiles)(message), {
|
|
728
|
+
api: this.bot.api,
|
|
729
|
+
token: this.token,
|
|
730
|
+
// A member's room message downloads into that room session's paths.
|
|
731
|
+
downloadsDir: this.principalPaths(principalId, message.chat.type === 'private' ? undefined : String(message.chat.id)).downloadsDir,
|
|
732
|
+
messageId: message.message_id,
|
|
733
|
+
});
|
|
734
|
+
return {
|
|
735
|
+
...input,
|
|
736
|
+
text: selected.text.trim()
|
|
737
|
+
? selected.text
|
|
738
|
+
: attachments.map(({ name }) => `[file: ${name}]`).join('\n'),
|
|
739
|
+
payload: {
|
|
740
|
+
...(formatting === undefined ? {} : { telegramFormatting: formatting }),
|
|
741
|
+
...(attachments.length ? { attachments } : {}),
|
|
742
|
+
},
|
|
743
|
+
};
|
|
744
|
+
}
|
|
745
|
+
claimMessage(message, principalId) {
|
|
746
|
+
const selected = (0, telegram_text_entities_js_1.selectTelegramTextEntities)(message);
|
|
747
|
+
const files = (0, telegram_attachments_js_1.telegramFiles)(message);
|
|
748
|
+
const inputIdentity = (0, node_crypto_1.createHash)('sha256')
|
|
749
|
+
.update(`${selected.text}\0${files.map(({ file }) => file.file_unique_id).join(',')}`)
|
|
750
|
+
.digest('hex');
|
|
751
|
+
const chatId = String(message.chat.id);
|
|
752
|
+
return this.messageLedger.claim(sourceMessageRef(chatId, message.message_id), {
|
|
753
|
+
deliveryTarget: `telegram:${chatId}`,
|
|
754
|
+
payloadIdentity: inputIdentity,
|
|
755
|
+
...(principalId === undefined ? {} : { principalId }),
|
|
756
|
+
});
|
|
757
|
+
}
|
|
564
758
|
createResponsePresenter(sourceRef, chatId) {
|
|
565
759
|
if (!this.bot)
|
|
566
760
|
throw new Error('Telegram gateway not connected');
|
|
@@ -596,7 +790,7 @@ class TelegramGateway extends base_gateway_js_1.BaseGateway {
|
|
|
596
790
|
return;
|
|
597
791
|
if (current.deliveryUncertain)
|
|
598
792
|
throw new Error('Telegram response delivery is uncertain');
|
|
599
|
-
const intake = this.responseIntake(sourceRef);
|
|
793
|
+
const intake = await this.responseIntake(sourceRef);
|
|
600
794
|
const presenter = this.activePresenters.get(sourceRef) ?? this.createResponsePresenter(sourceRef, chatId);
|
|
601
795
|
try {
|
|
602
796
|
await presenter.finalize(current.response);
|
|
@@ -609,6 +803,11 @@ class TelegramGateway extends base_gateway_js_1.BaseGateway {
|
|
|
609
803
|
if (current.responseAuthor === undefined) {
|
|
610
804
|
this.log(`telegram reply archive skipped key=${sourceRef} reason=missing_response_author`);
|
|
611
805
|
}
|
|
806
|
+
else if (current.responseAuthor === 'host' &&
|
|
807
|
+
intake.hasOwnerMessage?.(sourceRef) === false) {
|
|
808
|
+
// An intake interrupted before storage has no original to attach its notice to.
|
|
809
|
+
this.log(`telegram reply archive skipped key=${sourceRef} reason=missing_owner_message`);
|
|
810
|
+
}
|
|
612
811
|
else {
|
|
613
812
|
intake.recordOwnerReply({
|
|
614
813
|
messageRef: sourceRef,
|
|
@@ -626,7 +825,7 @@ class TelegramGateway extends base_gateway_js_1.BaseGateway {
|
|
|
626
825
|
timestamp: new Date(),
|
|
627
826
|
data: { sourceMessageRef: sourceRef },
|
|
628
827
|
});
|
|
629
|
-
});
|
|
828
|
+
}, true);
|
|
630
829
|
}
|
|
631
830
|
async recoverPendingResponses() {
|
|
632
831
|
for (const entry of this.messageLedger.listUndelivered()) {
|
|
@@ -636,25 +835,31 @@ class TelegramGateway extends base_gateway_js_1.BaseGateway {
|
|
|
636
835
|
continue;
|
|
637
836
|
}
|
|
638
837
|
if (entry.key.startsWith('outbound:') && entry.state === 'ready') {
|
|
838
|
+
// Personal turn recovery belongs to mailbox reconciliation, which checks the current person
|
|
839
|
+
// and input before sending. Gateway startup cannot establish that authority.
|
|
840
|
+
if (entry.idempotencyKey && (0, personal_turns_js_1.isPersonalTurnTextKey)(entry.idempotencyKey))
|
|
841
|
+
continue;
|
|
639
842
|
const chatId = entry.deliveryTarget?.slice('telegram:'.length);
|
|
640
843
|
if (!entry.deliveryTarget?.startsWith('telegram:') || !chatId) {
|
|
641
844
|
throw new Error('Telegram outbound entry has no valid destination');
|
|
642
845
|
}
|
|
643
|
-
if (!this.config.allowedChats?.includes(chatId))
|
|
644
|
-
|
|
846
|
+
if (!this.config.allowedChats?.includes(chatId)) {
|
|
847
|
+
const recipient = this.registry.resolveByExternal('telegram', 'private', chatId);
|
|
848
|
+
if (recipient?.kind !== 'member' || recipient.status !== 'active')
|
|
849
|
+
continue;
|
|
850
|
+
}
|
|
645
851
|
await this.runInChatQueue(chatId, () => this.deliverOutboundEntry(entry.key, chatId));
|
|
646
852
|
continue;
|
|
647
853
|
}
|
|
648
854
|
if (!entry.key.startsWith('telegram:'))
|
|
649
855
|
continue;
|
|
650
|
-
const intake = this.responseIntake(entry.key);
|
|
856
|
+
const intake = await this.responseIntake(entry.key);
|
|
651
857
|
if (entry.state === 'ready' && entry.response !== undefined) {
|
|
652
858
|
await this.deliverReadyEntry(entry.key);
|
|
653
859
|
continue;
|
|
654
860
|
}
|
|
655
861
|
if (entry.state === 'processing' && !intake.isPending?.(entry.key)) {
|
|
656
|
-
this.
|
|
657
|
-
await this.deliverReadyEntry(entry.key);
|
|
862
|
+
await this.interruptResponse(entry.key);
|
|
658
863
|
}
|
|
659
864
|
}
|
|
660
865
|
catch (error) {
|
|
@@ -672,7 +877,7 @@ class TelegramGateway extends base_gateway_js_1.BaseGateway {
|
|
|
672
877
|
await sendFormattedMessage(this.bot.api, Number(chatId), chunk);
|
|
673
878
|
return;
|
|
674
879
|
}
|
|
675
|
-
const key = outboundLedgerKey(idempotencyKey);
|
|
880
|
+
const key = (0, telegram_message_ledger_js_1.outboundLedgerKey)(idempotencyKey);
|
|
676
881
|
const binding = {
|
|
677
882
|
deliveryTarget: `telegram:${chatId}`,
|
|
678
883
|
payloadIdentity: (0, node_crypto_1.createHash)('sha256').update(text).digest('hex'),
|
|
@@ -682,6 +887,9 @@ class TelegramGateway extends base_gateway_js_1.BaseGateway {
|
|
|
682
887
|
const existing = this.messageLedger.claim(key, binding).entry;
|
|
683
888
|
if (existing.state === 'delivered')
|
|
684
889
|
return;
|
|
890
|
+
// Keep the rejection until the occurrence closes. A restart in between must not send it.
|
|
891
|
+
if ((0, personal_turns_js_1.isPersonalTurnTextKey)(idempotencyKey) && existing.state === 'failed')
|
|
892
|
+
throw new telegram_errors_js_1.TelegramJobDeliveryRejected('Personal job DM delivery was already rejected');
|
|
685
893
|
if (existing.state !== 'ready')
|
|
686
894
|
this.messageLedger.markReady(key, text, 'html-v1');
|
|
687
895
|
await this.deliverOutboundEntry(key, chatId);
|
|
@@ -712,7 +920,11 @@ class TelegramGateway extends base_gateway_js_1.BaseGateway {
|
|
|
712
920
|
}
|
|
713
921
|
catch (error) {
|
|
714
922
|
if ((0, telegram_errors_js_1.isDefinitiveTelegramRejection)(error)) {
|
|
715
|
-
|
|
923
|
+
// A personal turn refusal is final. Never persist a retryable ready receipt before its failure.
|
|
924
|
+
if (entry.idempotencyKey && (0, personal_turns_js_1.isPersonalTurnTextKey)(entry.idempotencyKey))
|
|
925
|
+
this.messageLedger.markFailed(key);
|
|
926
|
+
else
|
|
927
|
+
this.messageLedger.markDeliveryProgress(key, index, false);
|
|
716
928
|
}
|
|
717
929
|
throw error;
|
|
718
930
|
}
|
|
@@ -731,12 +943,14 @@ class TelegramGateway extends base_gateway_js_1.BaseGateway {
|
|
|
731
943
|
});
|
|
732
944
|
const current = (previous ?? Promise.resolve()).catch(() => { }).then(() => gate);
|
|
733
945
|
this.chatTails.set(chatId, current);
|
|
946
|
+
const context = { chatId, active: true };
|
|
734
947
|
try {
|
|
735
948
|
if (previous)
|
|
736
949
|
await previous.catch(() => { });
|
|
737
|
-
return await this.activeChat.run(
|
|
950
|
+
return await this.activeChat.run(context, work);
|
|
738
951
|
}
|
|
739
952
|
finally {
|
|
953
|
+
context.active = false;
|
|
740
954
|
release();
|
|
741
955
|
if (this.chatTails.get(chatId) === current)
|
|
742
956
|
this.chatTails.delete(chatId);
|