@foxden-app/foxclaw 0.12.0 → 0.13.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (126) hide show
  1. package/CHANGELOG.md +34 -0
  2. package/dist/antigravity/account_ui.d.ts +34 -0
  3. package/dist/antigravity/account_ui.js +354 -0
  4. package/dist/antigravity/adapter.js +1 -1
  5. package/dist/antigravity/auth.d.ts +4 -1
  6. package/dist/antigravity/auth.js +17 -5
  7. package/dist/antigravity/controller.d.ts +3 -108
  8. package/dist/antigravity/controller.js +2 -2129
  9. package/dist/antigravity/conversation_ui.d.ts +56 -0
  10. package/dist/antigravity/conversation_ui.js +509 -0
  11. package/dist/antigravity/ui_callbacks.d.ts +6 -0
  12. package/dist/antigravity/ui_callbacks.js +6 -0
  13. package/dist/bridge/unified_bridge.d.ts +114 -0
  14. package/dist/bridge/unified_bridge.js +1343 -0
  15. package/dist/channels/bridge_messaging_router.d.ts +8 -4
  16. package/dist/channels/bridge_messaging_router.js +15 -4
  17. package/dist/channels/telegram/telegram_messaging_port.d.ts +16 -0
  18. package/dist/channels/telegram/telegram_messaging_port.js +22 -2
  19. package/dist/channels/text_actions.d.ts +14 -0
  20. package/dist/channels/text_actions.js +48 -0
  21. package/dist/channels/weixin/weixin_channel_adapter.d.ts +3 -4
  22. package/dist/channels/weixin/weixin_channel_adapter.js +2 -2
  23. package/dist/channels/weixin/weixin_messaging_port.d.ts +16 -1
  24. package/dist/channels/weixin/weixin_messaging_port.js +34 -7
  25. package/dist/codex_app/activity.d.ts +62 -0
  26. package/dist/codex_app/activity.js +418 -0
  27. package/dist/codex_app/adapter.d.ts +2 -2
  28. package/dist/codex_app/adapter.js +55 -25
  29. package/dist/codex_app/backend_ui.d.ts +4 -0
  30. package/dist/codex_app/backend_ui.js +48 -0
  31. package/dist/codex_app/client.js +0 -12
  32. package/dist/controller/access.d.ts +1 -11
  33. package/dist/controller/access.js +1 -33
  34. package/dist/controller/activity.d.ts +1 -62
  35. package/dist/controller/activity.js +2 -418
  36. package/dist/controller/auth_errors.d.ts +11 -0
  37. package/dist/controller/auth_errors.js +124 -0
  38. package/dist/controller/auth_files.d.ts +13 -0
  39. package/dist/controller/auth_files.js +156 -0
  40. package/dist/controller/auth_presentation.d.ts +77 -0
  41. package/dist/controller/auth_presentation.js +697 -0
  42. package/dist/controller/auth_quota_service.d.ts +27 -0
  43. package/dist/controller/auth_quota_service.js +151 -0
  44. package/dist/controller/controller.d.ts +30 -56
  45. package/dist/controller/controller.js +181 -3497
  46. package/dist/controller/interactions.d.ts +74 -0
  47. package/dist/controller/interactions.js +560 -0
  48. package/dist/controller/local_usage_service.d.ts +19 -0
  49. package/dist/controller/local_usage_service.js +62 -0
  50. package/dist/controller/native_panel_service.d.ts +55 -0
  51. package/dist/controller/native_panel_service.js +214 -0
  52. package/dist/controller/native_panels.d.ts +61 -0
  53. package/dist/controller/native_panels.js +784 -0
  54. package/dist/controller/shared_helpers.d.ts +53 -0
  55. package/dist/controller/shared_helpers.js +399 -0
  56. package/dist/controller/state_types.d.ts +390 -0
  57. package/dist/controller/state_types.js +82 -0
  58. package/dist/controller/turn_rendering.d.ts +22 -0
  59. package/dist/controller/turn_rendering.js +288 -0
  60. package/dist/core/access.d.ts +11 -0
  61. package/dist/core/access.js +33 -0
  62. package/dist/core/attachment_files.d.ts +13 -0
  63. package/dist/core/attachment_files.js +180 -0
  64. package/dist/core/attachment_types.d.ts +22 -0
  65. package/dist/core/attachment_types.js +1 -0
  66. package/dist/core/attachments.d.ts +5 -3
  67. package/dist/core/attachments.js +2 -2
  68. package/dist/core/backend_registry.d.ts +22 -0
  69. package/dist/core/backend_registry.js +50 -0
  70. package/dist/core/backend_ui.d.ts +28 -0
  71. package/dist/core/backend_ui.js +1 -0
  72. package/dist/core/channel_events.d.ts +48 -0
  73. package/dist/core/channel_events.js +1 -0
  74. package/dist/core/channel_port.d.ts +52 -8
  75. package/dist/core/engine_spi.d.ts +19 -4
  76. package/dist/core/listener_scope.d.ts +11 -0
  77. package/dist/core/listener_scope.js +10 -0
  78. package/dist/core/message_text.d.ts +2 -0
  79. package/dist/core/message_text.js +28 -0
  80. package/dist/core/network_errors.d.ts +1 -0
  81. package/dist/core/network_errors.js +23 -0
  82. package/dist/core/orchestrator.d.ts +53 -24
  83. package/dist/core/orchestrator.js +855 -507
  84. package/dist/core/queued_engine_input.d.ts +12 -0
  85. package/dist/core/queued_engine_input.js +16 -0
  86. package/dist/core/runtime_supervisor.d.ts +19 -0
  87. package/dist/core/runtime_supervisor.js +45 -0
  88. package/dist/core/scope_operations.d.ts +7 -0
  89. package/dist/core/scope_operations.js +19 -0
  90. package/dist/core/stream_preview.js +21 -9
  91. package/dist/dsh/adapter.js +18 -5
  92. package/dist/dsh/backend.d.ts +7 -0
  93. package/dist/dsh/backend.js +21 -0
  94. package/dist/dsh/runtime.d.ts +0 -1
  95. package/dist/dsh/runtime.js +4 -13
  96. package/dist/dsh/ui.d.ts +8 -8
  97. package/dist/dsh/ui.js +1 -1
  98. package/dist/main.js +55 -43
  99. package/dist/opencode/adapter.js +28 -9
  100. package/dist/opencode/controller.js +1 -1
  101. package/dist/store/channel_inbox.d.ts +14 -0
  102. package/dist/store/channel_inbox.js +21 -0
  103. package/dist/store/database.d.ts +7 -3
  104. package/dist/store/database.js +7 -2
  105. package/dist/store/task_journal.d.ts +32 -0
  106. package/dist/store/task_journal.js +70 -0
  107. package/dist/telegram/api.d.ts +2 -2
  108. package/dist/telegram/api.js +10 -28
  109. package/dist/telegram/gateway.d.ts +13 -27
  110. package/dist/telegram/gateway.js +86 -43
  111. package/dist/telegram/media.d.ts +2 -34
  112. package/dist/telegram/media.js +1 -180
  113. package/dist/telegram/task_preview.d.ts +13 -0
  114. package/dist/telegram/task_preview.js +92 -0
  115. package/dist/telegram/task_result.d.ts +2 -0
  116. package/dist/telegram/task_result.js +12 -0
  117. package/dist/telegram/text.d.ts +1 -1
  118. package/dist/telegram/text.js +2 -27
  119. package/dist/types.d.ts +1 -1
  120. package/dist/update.d.ts +27 -2
  121. package/dist/update.js +81 -28
  122. package/docs/release.md +13 -11
  123. package/docs/zh/architecture-review.md +106 -0
  124. package/docs/zh/release.md +13 -11
  125. package/docs/zh/telegram-experience.md +40 -0
  126. package/package.json +8 -5
@@ -1,6 +1,6 @@
1
1
  import { EventEmitter } from 'node:events';
2
2
  import { formatSdkError } from './client.js';
3
- import { buildAttachmentPrompt } from '../telegram/media.js';
3
+ import { buildAttachmentPrompt } from '../core/attachment_files.js';
4
4
  export class OpencodeEngineAdapter {
5
5
  client;
6
6
  defaultModel;
@@ -49,6 +49,8 @@ export class OpencodeEngineAdapter {
49
49
  }
50
50
  let finalResult = null;
51
51
  let turnError = null;
52
+ let accumulatedResponse = '';
53
+ let cleanup = () => { };
52
54
  const resultWaiters = [];
53
55
  const resolveWaiters = (res) => {
54
56
  while (resultWaiters.length > 0) {
@@ -68,8 +70,9 @@ export class OpencodeEngineAdapter {
68
70
  }
69
71
  if (cancelled)
70
72
  return;
71
- let accumulatedResponse = '';
72
73
  const onEvent = (ev) => {
74
+ if (cancelled)
75
+ return;
73
76
  if ('sessionId' in ev && ev.sessionId === sessionId) {
74
77
  if (ev.kind === 'text') {
75
78
  if (ev.delta) {
@@ -111,7 +114,7 @@ export class OpencodeEngineAdapter {
111
114
  }
112
115
  }
113
116
  };
114
- const cleanup = () => {
117
+ cleanup = () => {
115
118
  this.client.off('event', onEvent);
116
119
  };
117
120
  this.client.on('event', onEvent);
@@ -138,19 +141,35 @@ export class OpencodeEngineAdapter {
138
141
  }
139
142
  }
140
143
  catch (err) {
144
+ cleanup();
141
145
  turnError = err instanceof Error ? err : new Error(String(err));
142
146
  emitter.emit('error', turnError);
143
147
  resolveWaiters(null);
144
148
  }
145
149
  };
146
- void run();
150
+ const running = run();
151
+ let cancelling;
147
152
  return {
148
- turnId: sessionId ?? undefined,
153
+ get turnId() { return sessionId ?? undefined; },
149
154
  cancel: () => {
150
- cancelled = true;
151
- if (sessionId) {
152
- void this.client.getClient().session.abort({ sessionID: sessionId }).catch(() => { });
153
- }
155
+ if (finalResult)
156
+ return Promise.resolve();
157
+ cancelling ??= (async () => {
158
+ cancelled = true;
159
+ await running;
160
+ if (finalResult)
161
+ return;
162
+ if (sessionId) {
163
+ const result = await this.client.getClient().session.abort({ sessionID: sessionId });
164
+ if (result.error)
165
+ throw new Error(formatSdkError(result.error));
166
+ }
167
+ cleanup();
168
+ finalResult = { kind: 'result', status: 'INTERRUPTED', response: accumulatedResponse, conversationId: sessionId };
169
+ emitter.emit('result', finalResult);
170
+ resolveWaiters(finalResult);
171
+ })();
172
+ return cancelling.catch(error => { cancelling = undefined; throw error; });
154
173
  },
155
174
  waitForResult: async () => {
156
175
  if (finalResult)
@@ -91,7 +91,7 @@ export class OpencodeBridgeCore {
91
91
  async stop() {
92
92
  if (!this.started)
93
93
  return;
94
- this.bot.stop();
94
+ await this.bot.stop();
95
95
  for (const turn of this.activeTurns.values())
96
96
  this.clearTurnTimers(turn);
97
97
  for (const watch of this.watchers.values()) {
@@ -0,0 +1,14 @@
1
+ import type { DatabaseSync } from 'node:sqlite';
2
+ import type { ChannelInbound } from '../core/channel_events.js';
3
+ export interface InboxEntry {
4
+ id: string;
5
+ inbound: ChannelInbound;
6
+ }
7
+ /** Durable intake precedes business classification and acknowledgement to the transport. */
8
+ export declare class ChannelInbox {
9
+ private readonly db;
10
+ constructor(db: DatabaseSync);
11
+ accept(id: string, inbound: ChannelInbound): boolean;
12
+ complete(id: string): void;
13
+ pending(): InboxEntry[];
14
+ }
@@ -0,0 +1,21 @@
1
+ /** Durable intake precedes business classification and acknowledgement to the transport. */
2
+ export class ChannelInbox {
3
+ db;
4
+ constructor(db) {
5
+ this.db = db;
6
+ db.exec(`CREATE TABLE IF NOT EXISTS channel_inbox (
7
+ receipt_id TEXT PRIMARY KEY, scope_id TEXT NOT NULL, payload TEXT,
8
+ completed_at INTEGER, created_at INTEGER NOT NULL);
9
+ CREATE INDEX IF NOT EXISTS channel_inbox_pending ON channel_inbox(completed_at, created_at);`);
10
+ db.prepare('DELETE FROM channel_inbox WHERE completed_at < ?').run(Date.now() - 90 * 86400_000);
11
+ }
12
+ accept(id, inbound) {
13
+ this.db.prepare('INSERT OR IGNORE INTO channel_inbox VALUES (?, ?, ?, NULL, ?)').run(id, inbound.event.scopeId, JSON.stringify(inbound), Date.now());
14
+ return this.db.prepare('SELECT completed_at FROM channel_inbox WHERE receipt_id = ?').get(id)?.completed_at === null;
15
+ }
16
+ complete(id) { this.db.prepare('UPDATE channel_inbox SET payload = NULL, completed_at = ? WHERE receipt_id = ?').run(Date.now(), id); }
17
+ pending() {
18
+ return this.db.prepare('SELECT receipt_id, payload FROM channel_inbox WHERE completed_at IS NULL ORDER BY created_at, receipt_id').all()
19
+ .map(row => ({ id: String(row.receipt_id), inbound: JSON.parse(String(row.payload)) }));
20
+ }
21
+ }
@@ -1,4 +1,6 @@
1
- import type { AccessPresetValue, ActiveTurnMessageMode, AppLocale, CachedThread, ChatSessionSettings, CollaborationModeValue, GuidedPlanSessionRecord, GuidedPlanSessionState, PendingAttachmentBatchRecord, PendingAttachmentBatchStatus, PendingApprovalRecord, QueuedTurnInputRecord, QueuedTurnInputStatus, ReasoningEffortValue, ThreadBinding } from '../types.js';
1
+ import { ChannelInbox } from './channel_inbox.js';
2
+ import { TaskJournal } from './task_journal.js';
3
+ import type { AccessPresetValue, ActiveTurnMessageMode, AppLocale, CachedThread, ChatSessionSettings, CollaborationModeValue, GuidedPlanSessionRecord, GuidedPlanSessionState, PendingAttachmentBatchRecord, PendingAttachmentBatchStatus, PendingApprovalRecord, QueuedTurnInputRecord, QueuedTurnInputStatus, ThreadBinding } from '../types.js';
2
4
  import type { CumulativeTokenTotals, CumulativeTokenUsageRecord, TokenUsageDelta } from './token_usage.js';
3
5
  export interface ActiveTurnPreviewRecord {
4
6
  turnId: string;
@@ -50,6 +52,8 @@ export interface CodexAuthPoolStats {
50
52
  export type CodexAuthCandidateState = 'active' | 'needs_repair';
51
53
  export declare class BridgeStore {
52
54
  private db;
55
+ readonly taskJournal: TaskJournal;
56
+ readonly channelInbox: ChannelInbox;
53
57
  constructor(dbPath: string);
54
58
  getTelegramOffset(botKey: string): number;
55
59
  setTelegramOffset(botKey: string, updateId: number): void;
@@ -71,9 +75,9 @@ export declare class BridgeStore {
71
75
  threadId: string;
72
76
  }>;
73
77
  getChatSettings(chatId: string): ChatSessionSettings | null;
74
- setChatSettings(chatId: string, model: string | null, reasoningEffort: ReasoningEffortValue | null, locale?: AppLocale | null): void;
78
+ setChatSettings(chatId: string, model: string | null, reasoningEffort: string | null, locale?: AppLocale | null): void;
75
79
  setChatModel(chatId: string, model: string | null): void;
76
- setChatEffort(chatId: string, effort: ReasoningEffortValue | null): void;
80
+ setChatEffort(chatId: string, effort: string | null): void;
77
81
  /** Persist an adapter-advertised reasoning id without imposing Codex's vocabulary. */
78
82
  setChatEngineEffort(chatId: string, effort: string | null): void;
79
83
  setChatLocale(chatId: string, locale: AppLocale): void;
@@ -1,12 +1,18 @@
1
1
  import path from 'node:path';
2
2
  import fs from 'node:fs';
3
3
  import { DatabaseSync } from 'node:sqlite';
4
+ import { ChannelInbox } from './channel_inbox.js';
5
+ import { TaskJournal } from './task_journal.js';
4
6
  import { migrateLegacyBridgeScopeIds } from './migrate_bridge_scope.js';
5
7
  export class BridgeStore {
6
8
  db;
9
+ taskJournal;
10
+ channelInbox;
7
11
  constructor(dbPath) {
8
12
  fs.mkdirSync(path.dirname(dbPath), { recursive: true });
9
13
  this.db = new DatabaseSync(dbPath);
14
+ this.taskJournal = new TaskJournal(this.db);
15
+ this.channelInbox = new ChannelInbox(this.db);
10
16
  this.db.exec(`
11
17
  CREATE TABLE IF NOT EXISTS telegram_offsets (
12
18
  bot_key TEXT PRIMARY KEY,
@@ -405,8 +411,7 @@ export class BridgeStore {
405
411
  }
406
412
  /** Persist an adapter-advertised reasoning id without imposing Codex's vocabulary. */
407
413
  setChatEngineEffort(chatId, effort) {
408
- const current = this.getChatSettings(chatId);
409
- this.writeChatSettings(chatId, current?.model ?? null, effort, current?.locale ?? null, current?.accessPreset ?? null, current?.collaborationMode ?? null, current?.serviceTier ?? null, current?.activeTurnMessageMode ?? null, current?.activeBackendId ?? null);
414
+ this.setChatEffort(chatId, effort);
410
415
  }
411
416
  setChatLocale(chatId, locale) {
412
417
  const current = this.getChatSettings(chatId);
@@ -0,0 +1,32 @@
1
+ import type { DatabaseSync } from 'node:sqlite';
2
+ import type { ChannelTextEvent } from '../core/channel_events.js';
3
+ import type { EngineTurnRequest, EngineTurnResult } from '../core/engine_spi.js';
4
+ export type TaskState = 'accepted' | 'queued' | 'running' | 'awaiting_confirmation' | 'delivery_pending' | 'completed' | 'failed' | 'cancelled';
5
+ export interface JournalTask {
6
+ id: string;
7
+ backendId: string;
8
+ state: TaskState;
9
+ event: ChannelTextEvent;
10
+ sourcePrompt: string;
11
+ request: EngineTurnRequest;
12
+ queueId: string | null;
13
+ previewMessageId: number;
14
+ result: EngineTurnResult | null;
15
+ delivery: string[];
16
+ deliveredChunks: number;
17
+ error: string | null;
18
+ createdAt: number;
19
+ updatedAt: number;
20
+ }
21
+ /** Original inputs, native outcome and delivery progress are distinct durable facts. */
22
+ export declare class TaskJournal {
23
+ private readonly db;
24
+ constructor(db: DatabaseSync);
25
+ atomic<T>(operation: () => T): T;
26
+ insert(task: JournalTask): void;
27
+ get(id: string): JournalTask | null;
28
+ listUnfinished(scopeId?: string): JournalTask[];
29
+ findReceipt(event: ChannelTextEvent, prompt: string): JournalTask | null;
30
+ forQueue(queueId: string): JournalTask | null;
31
+ update(id: string, state: TaskState, patch?: Partial<Omit<JournalTask, 'id' | 'backendId' | 'createdAt' | 'state'>>): JournalTask;
32
+ }
@@ -0,0 +1,70 @@
1
+ const transitions = {
2
+ accepted: ['queued', 'running', 'awaiting_confirmation', 'failed', 'cancelled'],
3
+ queued: ['running', 'failed', 'cancelled'],
4
+ running: ['running', 'awaiting_confirmation', 'delivery_pending', 'failed', 'cancelled'],
5
+ awaiting_confirmation: ['accepted', 'running', 'cancelled'],
6
+ delivery_pending: ['delivery_pending', 'completed', 'failed', 'cancelled'],
7
+ completed: [], failed: [], cancelled: [],
8
+ };
9
+ const unfinished = ['accepted', 'queued', 'running', 'awaiting_confirmation', 'delivery_pending'];
10
+ /** Original inputs, native outcome and delivery progress are distinct durable facts. */
11
+ export class TaskJournal {
12
+ db;
13
+ constructor(db) {
14
+ this.db = db;
15
+ db.exec(`CREATE TABLE IF NOT EXISTS engine_task_journal (
16
+ task_id TEXT PRIMARY KEY, scope_id TEXT NOT NULL, backend_id TEXT NOT NULL,
17
+ state TEXT NOT NULL, queue_id TEXT, record_json TEXT NOT NULL,
18
+ created_at INTEGER NOT NULL, updated_at INTEGER NOT NULL);
19
+ CREATE INDEX IF NOT EXISTS engine_task_journal_scope_state ON engine_task_journal(scope_id, state, created_at);
20
+ CREATE INDEX IF NOT EXISTS engine_task_journal_receipt ON engine_task_journal(scope_id, json_extract(record_json, '$.event.messageId'));`);
21
+ }
22
+ atomic(operation) {
23
+ this.db.exec('SAVEPOINT task_journal_change');
24
+ try {
25
+ const result = operation();
26
+ this.db.exec('RELEASE task_journal_change');
27
+ return result;
28
+ }
29
+ catch (error) {
30
+ this.db.exec('ROLLBACK TO task_journal_change; RELEASE task_journal_change');
31
+ throw error;
32
+ }
33
+ }
34
+ insert(task) {
35
+ this.db.prepare(`INSERT INTO engine_task_journal VALUES (?, ?, ?, ?, ?, ?, ?, ?)`).run(task.id, task.event.scopeId, task.backendId, task.state, task.queueId, JSON.stringify(task), task.createdAt, task.updatedAt);
36
+ }
37
+ get(id) {
38
+ const row = this.db.prepare('SELECT record_json FROM engine_task_journal WHERE task_id = ?').get(id);
39
+ return row ? JSON.parse(String(row.record_json)) : null;
40
+ }
41
+ listUnfinished(scopeId) {
42
+ const placeholders = unfinished.map(() => '?').join(',');
43
+ const rows = this.db.prepare(`SELECT record_json FROM engine_task_journal WHERE state IN (${placeholders})${scopeId ? ' AND scope_id = ?' : ''} ORDER BY created_at, task_id`).all(...unfinished, ...(scopeId ? [scopeId] : []));
44
+ return rows.map(row => JSON.parse(String(row.record_json)));
45
+ }
46
+ findReceipt(event, prompt) {
47
+ if (event.messageId <= 0 || event.userId === 'system')
48
+ return null;
49
+ const row = this.db.prepare(`SELECT record_json FROM engine_task_journal WHERE scope_id = ?
50
+ AND json_extract(record_json, '$.event.messageId') = ? AND json_extract(record_json, '$.event.chatId') = ?
51
+ AND json_extract(record_json, '$.sourcePrompt') = ? LIMIT 1`).get(event.scopeId, event.messageId, event.chatId, prompt);
52
+ return row ? JSON.parse(String(row.record_json)) : null;
53
+ }
54
+ forQueue(queueId) {
55
+ const row = this.db.prepare('SELECT record_json FROM engine_task_journal WHERE queue_id = ?').get(queueId);
56
+ return row ? JSON.parse(String(row.record_json)) : null;
57
+ }
58
+ update(id, state, patch = {}) {
59
+ const previous = this.get(id);
60
+ if (!previous)
61
+ throw new Error(`Unknown task: ${id}`);
62
+ if (previous.state !== state && !transitions[previous.state].includes(state))
63
+ throw new Error(`Invalid task transition: ${previous.state} -> ${state}`);
64
+ if (['completed', 'failed', 'cancelled'].includes(previous.state))
65
+ throw new Error(`Task already resolved: ${id}`);
66
+ const next = { ...previous, ...patch, state, updatedAt: Date.now() };
67
+ this.db.prepare(`UPDATE engine_task_journal SET state = ?, queue_id = ?, record_json = ?, updated_at = ? WHERE task_id = ? AND state = ?`).run(state, next.queueId, JSON.stringify(next), next.updatedAt, id, previous.state);
68
+ return next;
69
+ }
70
+ }
@@ -9,8 +9,8 @@ export interface TelegramRemoteFile {
9
9
  file_size?: number;
10
10
  file_path?: string;
11
11
  }
12
- export declare function isTransientNetworkError(err: unknown): boolean;
13
- export declare function callTelegramApi<T>(botToken: string, method: string, body: Record<string, unknown>): Promise<TelegramApiResult<T>>;
12
+ export { isTransientNetworkError } from '../core/network_errors.js';
13
+ export declare function callTelegramApi<T>(botToken: string, method: string, body: Record<string, unknown>, signal?: AbortSignal): Promise<TelegramApiResult<T>>;
14
14
  export declare function callTelegramMultipartApi<T>(botToken: string, method: string, fields: Record<string, string>, files: Array<{
15
15
  fieldName: string;
16
16
  filename: string;
@@ -1,33 +1,13 @@
1
+ import { setTimeout as delay } from 'node:timers/promises';
1
2
  import fs from 'node:fs';
2
3
  import http from 'node:http';
3
4
  import https from 'node:https';
4
5
  import path from 'node:path';
5
6
  import { pipeline } from 'node:stream/promises';
6
7
  const DEFAULT_API_BASE_URL = 'https://api.telegram.org';
7
- export function isTransientNetworkError(err) {
8
- if (!err)
9
- return false;
10
- const msg = String(err.message || err).toLowerCase();
11
- return (msg.includes('client network socket disconnected') ||
12
- msg.includes('econnreset') ||
13
- msg.includes('etimedout') ||
14
- msg.includes('eai_again') ||
15
- msg.includes('enotfound') ||
16
- msg.includes('econnrefused') ||
17
- msg.includes('socket hang up') ||
18
- msg.includes('timed out') ||
19
- msg.includes('timeout') ||
20
- msg.includes('network socket disconnected') ||
21
- msg.includes('network error') ||
22
- msg.includes('fetch failed') ||
23
- msg.includes('und_err_') ||
24
- msg.includes('read tcp') ||
25
- msg.includes('connection timed out') ||
26
- msg.includes('broken pipe') ||
27
- msg.includes('ehostunreach') ||
28
- msg.includes('enetunreach'));
29
- }
30
- function callTelegramApiOnce(botToken, method, body) {
8
+ export { isTransientNetworkError } from '../core/network_errors.js';
9
+ import { isTransientNetworkError } from '../core/network_errors.js';
10
+ function callTelegramApiOnce(botToken, method, body, signal) {
31
11
  const payload = JSON.stringify(body);
32
12
  const endpoint = telegramApiUrl(botToken, method);
33
13
  return new Promise((resolve, reject) => {
@@ -37,7 +17,7 @@ function callTelegramApiOnce(botToken, method, body) {
37
17
  'content-type': 'application/json',
38
18
  'content-length': Buffer.byteLength(payload),
39
19
  },
40
- signal: AbortSignal.timeout(telegramApiTimeoutMs(false)),
20
+ signal: signal ? AbortSignal.any([signal, AbortSignal.timeout(telegramApiTimeoutMs(false))]) : AbortSignal.timeout(telegramApiTimeoutMs(false)),
41
21
  }, (response) => {
42
22
  response.on('error', reject);
43
23
  const chunks = [];
@@ -62,17 +42,19 @@ function callTelegramApiOnce(botToken, method, body) {
62
42
  request.end();
63
43
  });
64
44
  }
65
- export async function callTelegramApi(botToken, method, body) {
45
+ export async function callTelegramApi(botToken, method, body, signal) {
66
46
  const maxAttempts = method === 'getUpdates' ? 1 : 4;
67
47
  let lastError;
68
48
  for (let attempt = 1; attempt <= maxAttempts; attempt++) {
69
49
  try {
70
- return await callTelegramApiOnce(botToken, method, body);
50
+ signal?.throwIfAborted();
51
+ return await callTelegramApiOnce(botToken, method, body, signal);
71
52
  }
72
53
  catch (err) {
54
+ signal?.throwIfAborted();
73
55
  lastError = err;
74
56
  if (attempt < maxAttempts && isTransientNetworkError(err)) {
75
- await new Promise((r) => setTimeout(r, Math.min(3000, 500 * Math.pow(2, attempt - 1))));
57
+ await delay(Math.min(3000, 500 * Math.pow(2, attempt - 1)), undefined, { signal });
76
58
  continue;
77
59
  }
78
60
  throw err;
@@ -1,28 +1,15 @@
1
+ import type { ChannelInbound } from '../core/channel_events.js';
1
2
  import { EventEmitter } from 'node:events';
2
3
  import { type TelegramRemoteFile } from './api.js';
3
4
  import type { BridgeStore } from '../store/database.js';
4
5
  import type { Logger } from '../logger.js';
5
- import type { TelegramMessageEntity } from './addressing.js';
6
6
  import type { TelegramInboundAttachment } from './media.js';
7
7
  import type { TelegramInputRichMessage } from './rich.js';
8
8
  export type TelegramCommandProvider = (locale: 'en' | 'zh') => Array<{
9
9
  command: string;
10
10
  description: string;
11
11
  }>;
12
- export interface TelegramTextEvent {
13
- chatId: string;
14
- topicId: number | null;
15
- scopeId: string;
16
- chatType: string;
17
- userId: string;
18
- text: string;
19
- messageId: number;
20
- mediaGroupId?: string | null;
21
- attachments: TelegramInboundAttachment[];
22
- entities: TelegramMessageEntity[];
23
- replyToBot: boolean;
24
- languageCode?: string;
25
- }
12
+ export type { ChannelTextEvent as TelegramTextEvent } from '../core/channel_events.js';
26
13
  export interface TelegramPeerDocumentEvent {
27
14
  chatId: string;
28
15
  userId: string;
@@ -31,16 +18,7 @@ export interface TelegramPeerDocumentEvent {
31
18
  text: string;
32
19
  attachment: TelegramInboundAttachment;
33
20
  }
34
- export interface TelegramCallbackEvent {
35
- chatId: string;
36
- topicId: number | null;
37
- scopeId: string;
38
- userId: string;
39
- data: string;
40
- callbackQueryId: string;
41
- messageId: number;
42
- languageCode?: string;
43
- }
21
+ export type { ChannelCallbackEvent as TelegramCallbackEvent } from '../core/channel_events.js';
44
22
  export declare class TelegramGateway extends EventEmitter {
45
23
  private readonly botToken;
46
24
  private readonly allowedUserId;
@@ -50,7 +28,15 @@ export declare class TelegramGateway extends EventEmitter {
50
28
  private readonly logger;
51
29
  private readonly namespacedScopes;
52
30
  private readonly commandProvider;
31
+ private inboundConsumer;
32
+ private readonly generations;
33
+ setInboundConsumer(consumer: (id: string, inbound: ChannelInbound) => Promise<void>): () => void;
34
+ registerGeneration(draftId: number, scopeId: string, taskId: string, chatId: string, topicId: number | null): void;
35
+ releaseGeneration(draftId: number): void;
36
+ private dispatchInbound;
53
37
  private running;
38
+ private polling;
39
+ private pollAbort;
54
40
  private botKey;
55
41
  private botUsername;
56
42
  private botUserId;
@@ -60,7 +46,7 @@ export declare class TelegramGateway extends EventEmitter {
60
46
  initializeIdentity(): Promise<string>;
61
47
  resolveUsername(): Promise<string | null>;
62
48
  start(): Promise<void>;
63
- stop(): void;
49
+ stop(): Promise<void>;
64
50
  sendMessage(chatId: string, text: string, inlineKeyboard?: Array<Array<{
65
51
  text: string;
66
52
  callback_data: string;
@@ -76,7 +62,7 @@ export declare class TelegramGateway extends EventEmitter {
76
62
  sendDocument(chatId: string, filename: string, contents: Buffer, caption?: string): Promise<number>;
77
63
  sendVoice(chatId: string, filename: string, contents: Buffer, caption?: string, messageThreadId?: number | null, contentType?: string): Promise<number>;
78
64
  sendMessageDraft(chatId: string, draftId: number, text: string, messageThreadId?: number | null): Promise<void>;
79
- sendRichMessageDraft(chatId: string, draftId: number, richMessage: TelegramInputRichMessage, messageThreadId?: number | null): Promise<void>;
65
+ sendRichMessageDraft(chatId: string, draftId: number, richMessage: TelegramInputRichMessage, messageThreadId?: number | null, canStop?: boolean): Promise<void>;
80
66
  editMessage(chatId: string, messageId: number, text: string, inlineKeyboard?: Array<Array<{
81
67
  text: string;
82
68
  callback_data: string;