@jungjaehoon/mama-os 0.74.0 → 0.76.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 +84 -0
- package/README.md +6 -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 +16 -7
- 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 +3 -1
- package/dist/api/drive-actions.d.ts.map +1 -1
- package/dist/api/drive-actions.js +6 -1
- package/dist/api/drive-actions.js.map +1 -1
- package/dist/api/member-enrollment.d.ts +7 -4
- package/dist/api/member-enrollment.d.ts.map +1 -1
- package/dist/api/member-enrollment.js +65 -33
- 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 +23 -4
- package/dist/api/member-lifecycle.d.ts.map +1 -1
- package/dist/api/member-lifecycle.js +90 -19
- 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 +49 -22
- package/dist/api/member-share-actions.js.map +1 -1
- package/dist/api/member-smoke-state.d.ts +3 -0
- package/dist/api/member-smoke-state.d.ts.map +1 -0
- package/dist/api/member-smoke-state.js +20 -0
- package/dist/api/member-smoke-state.js.map +1 -0
- 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 +3 -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 +1 -1
- package/dist/api/trello-actions.d.ts.map +1 -1
- package/dist/api/trello-actions.js +19 -4
- 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 +80 -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 +27 -5
- package/dist/cli/commands/init.js.map +1 -1
- package/dist/cli/commands/register-owner.d.ts +14 -5
- package/dist/cli/commands/register-owner.d.ts.map +1 -1
- package/dist/cli/commands/register-owner.js +145 -42
- package/dist/cli/commands/register-owner.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 +8 -4
- package/dist/cli/index.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 +33 -7
- package/dist/gateways/telegram.d.ts.map +1 -1
- package/dist/gateways/telegram.js +297 -59
- package/dist/gateways/telegram.js.map +1 -1
- package/dist/gateways/turn-contract.d.ts +8 -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 -39
- 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/config.d.ts +1 -1
- package/dist/runtime/config.d.ts.map +1 -1
- package/dist/runtime/config.js +18 -11
- 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 +289 -34
- package/dist/runtime/member-session.js.map +1 -1
- package/dist/runtime/member-system-prompt.d.ts +1 -0
- package/dist/runtime/member-system-prompt.d.ts.map +1 -1
- package/dist/runtime/member-system-prompt.js +26 -4
- package/dist/runtime/member-system-prompt.js.map +1 -1
- package/dist/runtime/native-session.d.ts +6 -0
- package/dist/runtime/native-session.d.ts.map +1 -1
- package/dist/runtime/native-session.js +80 -13
- 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 +15 -13
- package/dist/runtime/owner-authority.d.ts.map +1 -1
- package/dist/runtime/owner-authority.js +164 -8
- package/dist/runtime/owner-authority.js.map +1 -1
- package/dist/runtime/owner-runtime.d.ts +19 -4
- package/dist/runtime/owner-runtime.d.ts.map +1 -1
- package/dist/runtime/owner-runtime.js +446 -44
- 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 +15 -5
- 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 +99 -4
- 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 +22 -1
- package/dist/runtime/stimulus-delivery.d.ts.map +1 -1
- package/dist/runtime/stimulus-delivery.js +192 -19
- 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/storage/chat-sources.d.ts +21 -0
- package/dist/storage/chat-sources.d.ts.map +1 -1
- package/dist/storage/chat-sources.js +66 -4
- package/dist/storage/chat-sources.js.map +1 -1
- package/dist/storage/source-archive.d.ts +13 -0
- package/dist/storage/source-archive.d.ts.map +1 -1
- package/dist/storage/source-archive.js +103 -0
- package/dist/storage/source-archive.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 +98 -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.lock +664 -0
- package/toolkit/smoke.py +32 -0
|
@@ -1,8 +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");
|
|
7
|
+
const owner_authority_js_1 = require("../runtime/owner-authority.js");
|
|
8
|
+
const principal_access_js_1 = require("../runtime/principal-access.js");
|
|
6
9
|
const node_fs_1 = require("node:fs");
|
|
7
10
|
const node_path_1 = require("node:path");
|
|
8
11
|
const grammy_1 = require("grammy");
|
|
@@ -65,13 +68,13 @@ function chatIdFromSourceMessageRef(value) {
|
|
|
65
68
|
}
|
|
66
69
|
return rest.slice(0, separator);
|
|
67
70
|
}
|
|
68
|
-
|
|
69
|
-
return `outbound:${(0, node_crypto_1.createHash)('sha256').update(`text\0${idempotencyKey}`).digest('hex')}`;
|
|
70
|
-
}
|
|
71
|
-
/** Telegram owner ingress and its durable response transport. */
|
|
71
|
+
/** Telegram personal/common ingress and its durable response transport. */
|
|
72
72
|
class TelegramGateway extends base_gateway_js_1.BaseGateway {
|
|
73
73
|
source = 'telegram';
|
|
74
74
|
token;
|
|
75
|
+
registry;
|
|
76
|
+
memberIntake;
|
|
77
|
+
principalPaths;
|
|
75
78
|
config;
|
|
76
79
|
filesRoot;
|
|
77
80
|
downloadsDir;
|
|
@@ -80,11 +83,17 @@ class TelegramGateway extends base_gateway_js_1.BaseGateway {
|
|
|
80
83
|
onFatalError;
|
|
81
84
|
messageLedger;
|
|
82
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();
|
|
83
92
|
activePresenters = new Map();
|
|
84
93
|
recentMessageIds = new Map();
|
|
85
94
|
activeChat = new node_async_hooks_1.AsyncLocalStorage();
|
|
86
95
|
nextEnrollmentRequestId = (0, node_crypto_1.randomInt)(1, 0x7fffffff);
|
|
87
|
-
|
|
96
|
+
pendingEnrollments = new Map();
|
|
88
97
|
onMemberSelection;
|
|
89
98
|
recordMemberSelection;
|
|
90
99
|
bot = null;
|
|
@@ -96,14 +105,14 @@ class TelegramGateway extends base_gateway_js_1.BaseGateway {
|
|
|
96
105
|
constructor(options) {
|
|
97
106
|
super({ intake: options.intake });
|
|
98
107
|
this.token = options.token;
|
|
108
|
+
this.registry = options.registry;
|
|
109
|
+
this.memberIntake = options.memberIntake;
|
|
110
|
+
this.principalPaths = options.principalPaths;
|
|
99
111
|
this.onMemberSelection = options.onMemberSelection;
|
|
100
112
|
this.recordMemberSelection = options.recordMemberSelection;
|
|
101
113
|
this.config = {
|
|
102
114
|
enabled: options.config?.enabled ?? true,
|
|
103
115
|
allowedChats: options.config?.allowedChats ?? [],
|
|
104
|
-
...(options.config?.ownerUserIds === undefined
|
|
105
|
-
? {}
|
|
106
|
-
: { ownerUserIds: options.config.ownerUserIds }),
|
|
107
116
|
...(options.config?.ownerChatId === undefined
|
|
108
117
|
? {}
|
|
109
118
|
: { ownerChatId: options.config.ownerChatId }),
|
|
@@ -130,6 +139,10 @@ class TelegramGateway extends base_gateway_js_1.BaseGateway {
|
|
|
130
139
|
async start() {
|
|
131
140
|
if (this.connected)
|
|
132
141
|
return;
|
|
142
|
+
this.accepting = true;
|
|
143
|
+
this.intakeStopped = new Promise((resolve) => {
|
|
144
|
+
this.stopIntake = () => resolve(undefined);
|
|
145
|
+
});
|
|
133
146
|
if (!this.config.allowedChats?.some((chatId) => chatId.trim().length > 0)) {
|
|
134
147
|
throw new Error('telegram gateway disabled: allowed_chats is not set. Run: mama status');
|
|
135
148
|
}
|
|
@@ -174,10 +187,21 @@ class TelegramGateway extends base_gateway_js_1.BaseGateway {
|
|
|
174
187
|
throw error;
|
|
175
188
|
}
|
|
176
189
|
}
|
|
177
|
-
async
|
|
178
|
-
this.
|
|
190
|
+
async beginStop() {
|
|
191
|
+
this.accepting = false;
|
|
192
|
+
this.stopIntake();
|
|
179
193
|
if (this.bot)
|
|
180
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();
|
|
181
205
|
this.bot = null;
|
|
182
206
|
this.connected = false;
|
|
183
207
|
this.recentMessageIds.clear();
|
|
@@ -187,10 +211,9 @@ class TelegramGateway extends base_gateway_js_1.BaseGateway {
|
|
|
187
211
|
answered(sourceRef) {
|
|
188
212
|
return this.messageLedger.get(sourceRef)?.state === 'delivered';
|
|
189
213
|
}
|
|
190
|
-
/** Final response callback used
|
|
214
|
+
/** Final response callback used after a common or personal native turn settles. */
|
|
191
215
|
async deliverResponse(sourceRef, response) {
|
|
192
216
|
const chatId = chatIdFromSourceMessageRef(sourceRef);
|
|
193
|
-
this.requireAllowedChat(chatId);
|
|
194
217
|
const existing = this.messageLedger.get(sourceRef);
|
|
195
218
|
if (!existing)
|
|
196
219
|
throw new Error(`Telegram response has no accepted message ${sourceRef}`);
|
|
@@ -199,6 +222,7 @@ class TelegramGateway extends base_gateway_js_1.BaseGateway {
|
|
|
199
222
|
}
|
|
200
223
|
if (existing.state === 'delivered')
|
|
201
224
|
return;
|
|
225
|
+
await this.responseIntake(sourceRef);
|
|
202
226
|
if (existing.state === 'ready' && existing.response !== response) {
|
|
203
227
|
throw new Error('Telegram response conflicts with its durable ledger entry');
|
|
204
228
|
}
|
|
@@ -208,6 +232,18 @@ class TelegramGateway extends base_gateway_js_1.BaseGateway {
|
|
|
208
232
|
return;
|
|
209
233
|
await this.deliverReadyEntry(sourceRef);
|
|
210
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
|
+
}
|
|
211
247
|
async sendMessage(chatId, text, idempotencyKey) {
|
|
212
248
|
if (!this.bot || !this.connected)
|
|
213
249
|
throw new Error('Telegram gateway not connected');
|
|
@@ -224,6 +260,19 @@ class TelegramGateway extends base_gateway_js_1.BaseGateway {
|
|
|
224
260
|
await this.sendMessage(ownerChatId, text, idempotencyKey);
|
|
225
261
|
}
|
|
226
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
|
+
}
|
|
227
276
|
async sendMemberText(access, text, operationId) {
|
|
228
277
|
const destinations = access.destinations?.filter((target) => target.kind === 'telegram');
|
|
229
278
|
if (destinations?.length !== 1 || !destinations[0].id.trim())
|
|
@@ -234,9 +283,12 @@ class TelegramGateway extends base_gateway_js_1.BaseGateway {
|
|
|
234
283
|
throw new Error('Member text operation id is required');
|
|
235
284
|
const chatId = destinations[0].id;
|
|
236
285
|
await this.runInChatQueue(chatId, () => this.sendMessageNow(chatId, text, operationId));
|
|
237
|
-
return this.
|
|
286
|
+
return this.memberTextDelivered(access, operationId);
|
|
287
|
+
}
|
|
288
|
+
sendFile(path, caption, operationId, member) {
|
|
289
|
+
return this.trackDelivery(() => this.sendFileNow(path, caption, operationId, member));
|
|
238
290
|
}
|
|
239
|
-
async
|
|
291
|
+
async sendFileNow(path, caption, operationId, member) {
|
|
240
292
|
if (!this.bot || !this.connected)
|
|
241
293
|
throw new Error('Telegram gateway not connected');
|
|
242
294
|
if (operationId.trim() === '')
|
|
@@ -249,6 +301,8 @@ class TelegramGateway extends base_gateway_js_1.BaseGateway {
|
|
|
249
301
|
if (destinations?.length !== 1 || !destinations[0].id.trim())
|
|
250
302
|
throw new Error('Member file delivery requires exactly one Telegram destination');
|
|
251
303
|
chatId = destinations[0].id;
|
|
304
|
+
if ((0, principal_access_js_1.roomChannel)(member.access) !== undefined)
|
|
305
|
+
this.requireAllowedChat(chatId);
|
|
252
306
|
filesRoot = member.filesRoot;
|
|
253
307
|
}
|
|
254
308
|
else {
|
|
@@ -300,21 +354,26 @@ class TelegramGateway extends base_gateway_js_1.BaseGateway {
|
|
|
300
354
|
}
|
|
301
355
|
}
|
|
302
356
|
/** The source ref was verified as an owner turn by the runtime; only an owner DM can select. */
|
|
303
|
-
async requestMemberEnrollment(sourceRef) {
|
|
357
|
+
async requestMemberEnrollment(sourceRef, requestingOwnerUserId, name) {
|
|
304
358
|
if (!sourceRef.startsWith('telegram:'))
|
|
305
359
|
throw Object.assign(new Error("Enrollment requires the owner's own Telegram DM"), {
|
|
306
360
|
name: 'denied',
|
|
307
361
|
});
|
|
308
|
-
const ownerUserId =
|
|
362
|
+
const ownerUserId = requestingOwnerUserId;
|
|
309
363
|
const chatId = Number(ownerUserId);
|
|
310
|
-
if (!this.bot || !this.onMemberSelection || !this.ownerAllowed(ownerUserId
|
|
364
|
+
if (!this.bot || !this.onMemberSelection || !this.ownerAllowed(ownerUserId)) {
|
|
311
365
|
throw Object.assign(new Error("Enrollment requires the owner's own Telegram DM"), {
|
|
312
366
|
name: 'denied',
|
|
313
367
|
});
|
|
314
368
|
}
|
|
315
369
|
const requestId = this.nextEnrollmentRequestId;
|
|
316
370
|
this.nextEnrollmentRequestId = requestId === 0x7fffffff ? 1 : requestId + 1;
|
|
317
|
-
this.
|
|
371
|
+
this.pendingEnrollments.set(ownerUserId, {
|
|
372
|
+
requestId,
|
|
373
|
+
sourceMessageRef: sourceRef,
|
|
374
|
+
ownerUserId,
|
|
375
|
+
...(name === undefined ? {} : { name }),
|
|
376
|
+
});
|
|
318
377
|
// Telegram shows a request_users button only on the reply keyboard, under the message box.
|
|
319
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.';
|
|
320
379
|
try {
|
|
@@ -334,15 +393,24 @@ class TelegramGateway extends base_gateway_js_1.BaseGateway {
|
|
|
334
393
|
});
|
|
335
394
|
}
|
|
336
395
|
catch (error) {
|
|
337
|
-
if (this.
|
|
338
|
-
this.
|
|
396
|
+
if (this.pendingEnrollments.get(ownerUserId)?.requestId === requestId)
|
|
397
|
+
this.pendingEnrollments.delete(ownerUserId);
|
|
339
398
|
throw error;
|
|
340
399
|
}
|
|
341
400
|
}
|
|
342
|
-
|
|
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
|
+
}
|
|
408
|
+
async completeMemberSelection(message, senderPrincipalId) {
|
|
343
409
|
const chatId = String(message.chat.id);
|
|
344
410
|
const ownerUserId = String(message.from.id);
|
|
345
|
-
if (message.chat.type !== 'private' ||
|
|
411
|
+
if (message.chat.type !== 'private' ||
|
|
412
|
+
chatId !== ownerUserId ||
|
|
413
|
+
!this.ownerAllowed(ownerUserId))
|
|
346
414
|
return;
|
|
347
415
|
let receiptText = 'Enrollment selection received.';
|
|
348
416
|
let deliveryVerified = false;
|
|
@@ -355,7 +423,7 @@ class TelegramGateway extends base_gateway_js_1.BaseGateway {
|
|
|
355
423
|
deliveryVerified = true;
|
|
356
424
|
};
|
|
357
425
|
try {
|
|
358
|
-
const pending = this.
|
|
426
|
+
const pending = this.pendingEnrollments.get(ownerUserId);
|
|
359
427
|
const shared = message.users_shared;
|
|
360
428
|
if (!pending ||
|
|
361
429
|
shared.request_id !== pending.requestId ||
|
|
@@ -364,7 +432,7 @@ class TelegramGateway extends base_gateway_js_1.BaseGateway {
|
|
|
364
432
|
return;
|
|
365
433
|
}
|
|
366
434
|
// Consume before awaiting the serial host chain, so concurrent duplicate updates cannot enroll.
|
|
367
|
-
this.
|
|
435
|
+
this.pendingEnrollments.delete(ownerUserId);
|
|
368
436
|
if (shared.users.length !== 1 ||
|
|
369
437
|
!Number.isSafeInteger(shared.users[0].user_id) ||
|
|
370
438
|
shared.users[0].user_id <= 0) {
|
|
@@ -378,6 +446,7 @@ class TelegramGateway extends base_gateway_js_1.BaseGateway {
|
|
|
378
446
|
sourceMessageRef: pending.sourceMessageRef,
|
|
379
447
|
ownerUserId,
|
|
380
448
|
userId: String(shared.users[0].user_id),
|
|
449
|
+
...(pending.name === undefined ? {} : { name: pending.name }),
|
|
381
450
|
});
|
|
382
451
|
}
|
|
383
452
|
catch (error) {
|
|
@@ -411,6 +480,7 @@ class TelegramGateway extends base_gateway_js_1.BaseGateway {
|
|
|
411
480
|
message: {
|
|
412
481
|
id: sourceMessageRef(chatId, message.message_id),
|
|
413
482
|
channelKey: chatId,
|
|
483
|
+
senderPrincipalId,
|
|
414
484
|
occurredAt: message.date * 1000,
|
|
415
485
|
text: 'Member selection for enrollment (Choose member button)',
|
|
416
486
|
// manage.member.list finds the receipts by this marker, not by the text.
|
|
@@ -431,31 +501,125 @@ class TelegramGateway extends base_gateway_js_1.BaseGateway {
|
|
|
431
501
|
getLastMessageAt() {
|
|
432
502
|
return this.lastMessageAt;
|
|
433
503
|
}
|
|
434
|
-
ownerAllowed(
|
|
435
|
-
|
|
436
|
-
|
|
437
|
-
|
|
438
|
-
|
|
439
|
-
|
|
504
|
+
ownerAllowed(userId) {
|
|
505
|
+
const principal = this.registry.resolveByExternal('telegram', 'private', userId);
|
|
506
|
+
return (principal?.kind === 'member' &&
|
|
507
|
+
principal.status === 'active' &&
|
|
508
|
+
(0, owner_authority_js_1.holdsOwnerRole)(this.registry, principal.principalId));
|
|
509
|
+
}
|
|
510
|
+
async responseIntake(sourceRef) {
|
|
511
|
+
const chatId = chatIdFromSourceMessageRef(sourceRef);
|
|
512
|
+
const recipient = this.registry.resolveByExternal('telegram', 'private', chatId);
|
|
513
|
+
if (recipient) {
|
|
514
|
+
if (recipient.kind !== 'member' || recipient.status !== 'active') {
|
|
515
|
+
throw new Error('Telegram DM response requires its registered active member');
|
|
516
|
+
}
|
|
517
|
+
return this.memberIntake(recipient.principalId);
|
|
518
|
+
}
|
|
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
|
+
}
|
|
529
|
+
return this.intake;
|
|
440
530
|
}
|
|
441
531
|
requireAllowedChat(chatId) {
|
|
442
532
|
if (!this.config.allowedChats?.includes(chatId)) {
|
|
443
533
|
throw new Error('Telegram destination is not allowlisted');
|
|
444
534
|
}
|
|
445
535
|
}
|
|
446
|
-
|
|
536
|
+
handleMessage(message) {
|
|
537
|
+
return this.trackIntake(() => this.handleMessageInput(message));
|
|
538
|
+
}
|
|
539
|
+
async handleMessageInput(message) {
|
|
447
540
|
if (!message.chat || !message.from)
|
|
448
541
|
return;
|
|
449
542
|
const chatId = String(message.chat.id);
|
|
450
543
|
const userId = String(message.from.id);
|
|
451
|
-
|
|
544
|
+
const principal = this.registry.resolveByExternal('telegram', 'private', userId);
|
|
545
|
+
const privateChat = message.chat.type === 'private';
|
|
546
|
+
const admitted = principal?.kind === 'member' &&
|
|
547
|
+
principal.status === 'active' &&
|
|
548
|
+
(privateChat || this.config.allowedChats?.includes(chatId));
|
|
549
|
+
if (message.from.is_bot || !admitted) {
|
|
550
|
+
if (principal?.status === 'suspended')
|
|
551
|
+
this.cancelMemberEnrollment(principal.principalId);
|
|
452
552
|
const chatHash = (0, node_crypto_1.createHash)('sha256').update(`chat\0${chatId}`).digest('hex');
|
|
453
553
|
const senderHash = (0, node_crypto_1.createHash)('sha256').update(`sender\0${userId}`).digest('hex');
|
|
454
554
|
console.warn(`telegram message dropped reason=non_owner chat_hash=${chatHash} sender_hash=${senderHash}`);
|
|
455
555
|
return;
|
|
456
556
|
}
|
|
457
557
|
if (message.users_shared) {
|
|
458
|
-
|
|
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}`);
|
|
459
623
|
return;
|
|
460
624
|
}
|
|
461
625
|
const ref = sourceMessageRef(chatId, message.message_id);
|
|
@@ -477,11 +641,10 @@ class TelegramGateway extends base_gateway_js_1.BaseGateway {
|
|
|
477
641
|
}
|
|
478
642
|
if (durable?.state === 'processing' && this.activePresenters.has(ref))
|
|
479
643
|
return;
|
|
480
|
-
if (durable?.state === 'processing' &&
|
|
644
|
+
if (durable?.state === 'processing' && intake.isPending?.(ref))
|
|
481
645
|
return;
|
|
482
|
-
if (durable?.state === 'processing') {
|
|
483
|
-
this.
|
|
484
|
-
await this.deliverReadyEntry(ref);
|
|
646
|
+
if (durable?.state === 'processing' && queuedClaim !== ref) {
|
|
647
|
+
await this.interruptResponse(ref);
|
|
485
648
|
return;
|
|
486
649
|
}
|
|
487
650
|
const selected = (0, telegram_text_entities_js_1.selectTelegramTextEntities)(message);
|
|
@@ -489,13 +652,23 @@ class TelegramGateway extends base_gateway_js_1.BaseGateway {
|
|
|
489
652
|
if (!selected.text.trim() && files.length === 0)
|
|
490
653
|
return;
|
|
491
654
|
const formatting = (0, telegram_text_entities_js_1.captureTelegramTextFormatting)(selected.field, selected.text, selected.entities);
|
|
492
|
-
const
|
|
493
|
-
|
|
494
|
-
|
|
495
|
-
|
|
496
|
-
|
|
497
|
-
|
|
498
|
-
|
|
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
|
+
}
|
|
499
672
|
const presenter = this.createResponsePresenter(ref, Number(message.chat.id));
|
|
500
673
|
this.activePresenters.set(ref, presenter);
|
|
501
674
|
try {
|
|
@@ -504,12 +677,16 @@ class TelegramGateway extends base_gateway_js_1.BaseGateway {
|
|
|
504
677
|
const attachments = await (0, telegram_attachments_js_1.downloadTelegramFiles)(files, {
|
|
505
678
|
api: this.bot.api,
|
|
506
679
|
token: this.token,
|
|
507
|
-
downloadsDir:
|
|
680
|
+
downloadsDir: privateChat || memberRoom
|
|
681
|
+
? this.principalPaths(principal.principalId, memberRoom ? chatId : undefined)
|
|
682
|
+
.downloadsDir
|
|
683
|
+
: this.downloadsDir,
|
|
508
684
|
messageId: message.message_id,
|
|
509
685
|
});
|
|
510
686
|
const input = {
|
|
511
687
|
id: ref,
|
|
512
688
|
channelKey: chatId,
|
|
689
|
+
senderPrincipalId: principal.principalId,
|
|
513
690
|
occurredAt: message.date * 1000,
|
|
514
691
|
text: selected.text.trim()
|
|
515
692
|
? selected.text
|
|
@@ -523,7 +700,7 @@ class TelegramGateway extends base_gateway_js_1.BaseGateway {
|
|
|
523
700
|
},
|
|
524
701
|
}),
|
|
525
702
|
};
|
|
526
|
-
|
|
703
|
+
intake.acceptOwnerMessage(input);
|
|
527
704
|
this.emitEvent({
|
|
528
705
|
type: 'message_received',
|
|
529
706
|
source: 'telegram',
|
|
@@ -536,6 +713,48 @@ class TelegramGateway extends base_gateway_js_1.BaseGateway {
|
|
|
536
713
|
throw error;
|
|
537
714
|
}
|
|
538
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
|
+
}
|
|
539
758
|
createResponsePresenter(sourceRef, chatId) {
|
|
540
759
|
if (!this.bot)
|
|
541
760
|
throw new Error('Telegram gateway not connected');
|
|
@@ -571,6 +790,7 @@ class TelegramGateway extends base_gateway_js_1.BaseGateway {
|
|
|
571
790
|
return;
|
|
572
791
|
if (current.deliveryUncertain)
|
|
573
792
|
throw new Error('Telegram response delivery is uncertain');
|
|
793
|
+
const intake = await this.responseIntake(sourceRef);
|
|
574
794
|
const presenter = this.activePresenters.get(sourceRef) ?? this.createResponsePresenter(sourceRef, chatId);
|
|
575
795
|
try {
|
|
576
796
|
await presenter.finalize(current.response);
|
|
@@ -583,8 +803,13 @@ class TelegramGateway extends base_gateway_js_1.BaseGateway {
|
|
|
583
803
|
if (current.responseAuthor === undefined) {
|
|
584
804
|
this.log(`telegram reply archive skipped key=${sourceRef} reason=missing_response_author`);
|
|
585
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
|
+
}
|
|
586
811
|
else {
|
|
587
|
-
|
|
812
|
+
intake.recordOwnerReply({
|
|
588
813
|
messageRef: sourceRef,
|
|
589
814
|
text: current.response,
|
|
590
815
|
occurredAt: this.messageLedger.get(sourceRef).updatedAt,
|
|
@@ -600,7 +825,7 @@ class TelegramGateway extends base_gateway_js_1.BaseGateway {
|
|
|
600
825
|
timestamp: new Date(),
|
|
601
826
|
data: { sourceMessageRef: sourceRef },
|
|
602
827
|
});
|
|
603
|
-
});
|
|
828
|
+
}, true);
|
|
604
829
|
}
|
|
605
830
|
async recoverPendingResponses() {
|
|
606
831
|
for (const entry of this.messageLedger.listUndelivered()) {
|
|
@@ -610,27 +835,31 @@ class TelegramGateway extends base_gateway_js_1.BaseGateway {
|
|
|
610
835
|
continue;
|
|
611
836
|
}
|
|
612
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;
|
|
613
842
|
const chatId = entry.deliveryTarget?.slice('telegram:'.length);
|
|
614
843
|
if (!entry.deliveryTarget?.startsWith('telegram:') || !chatId) {
|
|
615
844
|
throw new Error('Telegram outbound entry has no valid destination');
|
|
616
845
|
}
|
|
617
|
-
if (!this.config.allowedChats?.includes(chatId))
|
|
618
|
-
|
|
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
|
+
}
|
|
619
851
|
await this.runInChatQueue(chatId, () => this.deliverOutboundEntry(entry.key, chatId));
|
|
620
852
|
continue;
|
|
621
853
|
}
|
|
622
854
|
if (!entry.key.startsWith('telegram:'))
|
|
623
855
|
continue;
|
|
624
|
-
const
|
|
625
|
-
if (!this.config.allowedChats?.includes(chatId))
|
|
626
|
-
continue;
|
|
856
|
+
const intake = await this.responseIntake(entry.key);
|
|
627
857
|
if (entry.state === 'ready' && entry.response !== undefined) {
|
|
628
858
|
await this.deliverReadyEntry(entry.key);
|
|
629
859
|
continue;
|
|
630
860
|
}
|
|
631
|
-
if (entry.state === 'processing' && !
|
|
632
|
-
this.
|
|
633
|
-
await this.deliverReadyEntry(entry.key);
|
|
861
|
+
if (entry.state === 'processing' && !intake.isPending?.(entry.key)) {
|
|
862
|
+
await this.interruptResponse(entry.key);
|
|
634
863
|
}
|
|
635
864
|
}
|
|
636
865
|
catch (error) {
|
|
@@ -648,7 +877,7 @@ class TelegramGateway extends base_gateway_js_1.BaseGateway {
|
|
|
648
877
|
await sendFormattedMessage(this.bot.api, Number(chatId), chunk);
|
|
649
878
|
return;
|
|
650
879
|
}
|
|
651
|
-
const key = outboundLedgerKey(idempotencyKey);
|
|
880
|
+
const key = (0, telegram_message_ledger_js_1.outboundLedgerKey)(idempotencyKey);
|
|
652
881
|
const binding = {
|
|
653
882
|
deliveryTarget: `telegram:${chatId}`,
|
|
654
883
|
payloadIdentity: (0, node_crypto_1.createHash)('sha256').update(text).digest('hex'),
|
|
@@ -658,6 +887,9 @@ class TelegramGateway extends base_gateway_js_1.BaseGateway {
|
|
|
658
887
|
const existing = this.messageLedger.claim(key, binding).entry;
|
|
659
888
|
if (existing.state === 'delivered')
|
|
660
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');
|
|
661
893
|
if (existing.state !== 'ready')
|
|
662
894
|
this.messageLedger.markReady(key, text, 'html-v1');
|
|
663
895
|
await this.deliverOutboundEntry(key, chatId);
|
|
@@ -688,7 +920,11 @@ class TelegramGateway extends base_gateway_js_1.BaseGateway {
|
|
|
688
920
|
}
|
|
689
921
|
catch (error) {
|
|
690
922
|
if ((0, telegram_errors_js_1.isDefinitiveTelegramRejection)(error)) {
|
|
691
|
-
|
|
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);
|
|
692
928
|
}
|
|
693
929
|
throw error;
|
|
694
930
|
}
|
|
@@ -707,12 +943,14 @@ class TelegramGateway extends base_gateway_js_1.BaseGateway {
|
|
|
707
943
|
});
|
|
708
944
|
const current = (previous ?? Promise.resolve()).catch(() => { }).then(() => gate);
|
|
709
945
|
this.chatTails.set(chatId, current);
|
|
946
|
+
const context = { chatId, active: true };
|
|
710
947
|
try {
|
|
711
948
|
if (previous)
|
|
712
949
|
await previous.catch(() => { });
|
|
713
|
-
return await this.activeChat.run(
|
|
950
|
+
return await this.activeChat.run(context, work);
|
|
714
951
|
}
|
|
715
952
|
finally {
|
|
953
|
+
context.active = false;
|
|
716
954
|
release();
|
|
717
955
|
if (this.chatTails.get(chatId) === current)
|
|
718
956
|
this.chatTails.delete(chatId);
|