@foxden-app/foxclaw 0.2.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/.env.example +36 -0
- package/LICENSE +22 -0
- package/README.md +244 -0
- package/README_EN.md +244 -0
- package/dist/channels/bridge_messaging_router.d.ts +27 -0
- package/dist/channels/bridge_messaging_router.js +85 -0
- package/dist/channels/telegram/telegram_channel_adapter.d.ts +12 -0
- package/dist/channels/telegram/telegram_channel_adapter.js +21 -0
- package/dist/channels/telegram/telegram_messaging_port.d.ts +25 -0
- package/dist/channels/telegram/telegram_messaging_port.js +51 -0
- package/dist/channels/weixin/account_store.d.ts +15 -0
- package/dist/channels/weixin/account_store.js +54 -0
- package/dist/channels/weixin/ilink/aes_ecb.d.ts +3 -0
- package/dist/channels/weixin/ilink/aes_ecb.js +12 -0
- package/dist/channels/weixin/ilink/api.d.ts +44 -0
- package/dist/channels/weixin/ilink/api.js +187 -0
- package/dist/channels/weixin/ilink/cdn_upload.d.ts +11 -0
- package/dist/channels/weixin/ilink/cdn_upload.js +60 -0
- package/dist/channels/weixin/ilink/cdn_url.d.ts +7 -0
- package/dist/channels/weixin/ilink/cdn_url.js +7 -0
- package/dist/channels/weixin/ilink/constants.d.ts +7 -0
- package/dist/channels/weixin/ilink/constants.js +27 -0
- package/dist/channels/weixin/ilink/context.d.ts +13 -0
- package/dist/channels/weixin/ilink/context.js +13 -0
- package/dist/channels/weixin/ilink/login_qr.d.ts +34 -0
- package/dist/channels/weixin/ilink/login_qr.js +233 -0
- package/dist/channels/weixin/ilink/media_image.d.ts +11 -0
- package/dist/channels/weixin/ilink/media_image.js +44 -0
- package/dist/channels/weixin/ilink/mime.d.ts +3 -0
- package/dist/channels/weixin/ilink/mime.js +36 -0
- package/dist/channels/weixin/ilink/pic_decrypt.d.ts +2 -0
- package/dist/channels/weixin/ilink/pic_decrypt.js +56 -0
- package/dist/channels/weixin/ilink/random.d.ts +2 -0
- package/dist/channels/weixin/ilink/random.js +7 -0
- package/dist/channels/weixin/ilink/redact.d.ts +4 -0
- package/dist/channels/weixin/ilink/redact.js +34 -0
- package/dist/channels/weixin/ilink/runtime_attach.d.ts +3 -0
- package/dist/channels/weixin/ilink/runtime_attach.js +13 -0
- package/dist/channels/weixin/ilink/send.d.ts +21 -0
- package/dist/channels/weixin/ilink/send.js +108 -0
- package/dist/channels/weixin/ilink/session_guard.d.ts +6 -0
- package/dist/channels/weixin/ilink/session_guard.js +39 -0
- package/dist/channels/weixin/ilink/types.d.ts +155 -0
- package/dist/channels/weixin/ilink/types.js +10 -0
- package/dist/channels/weixin/ilink/upload.d.ts +15 -0
- package/dist/channels/weixin/ilink/upload.js +75 -0
- package/dist/channels/weixin/sync_buf_store.d.ts +3 -0
- package/dist/channels/weixin/sync_buf_store.js +19 -0
- package/dist/channels/weixin/weixin_channel_adapter.d.ts +18 -0
- package/dist/channels/weixin/weixin_channel_adapter.js +273 -0
- package/dist/channels/weixin/weixin_messaging_port.d.ts +29 -0
- package/dist/channels/weixin/weixin_messaging_port.js +113 -0
- package/dist/codex_app/client.d.ts +176 -0
- package/dist/codex_app/client.js +1230 -0
- package/dist/codex_app/deeplink.d.ts +7 -0
- package/dist/codex_app/deeplink.js +29 -0
- package/dist/codex_app/local_usage.d.ts +16 -0
- package/dist/codex_app/local_usage.js +123 -0
- package/dist/config.d.ts +44 -0
- package/dist/config.js +131 -0
- package/dist/controller/access.d.ts +11 -0
- package/dist/controller/access.js +33 -0
- package/dist/controller/activity.d.ts +62 -0
- package/dist/controller/activity.js +330 -0
- package/dist/controller/commands.d.ts +6 -0
- package/dist/controller/commands.js +17 -0
- package/dist/controller/controller.d.ts +326 -0
- package/dist/controller/controller.js +7503 -0
- package/dist/controller/observer.d.ts +16 -0
- package/dist/controller/observer.js +98 -0
- package/dist/controller/presentation.d.ts +80 -0
- package/dist/controller/presentation.js +568 -0
- package/dist/controller/service_tier.d.ts +9 -0
- package/dist/controller/service_tier.js +32 -0
- package/dist/controller/session_observer.d.ts +22 -0
- package/dist/controller/session_observer.js +259 -0
- package/dist/controller/status.d.ts +10 -0
- package/dist/controller/status.js +28 -0
- package/dist/core/bridge_scope.d.ts +18 -0
- package/dist/core/bridge_scope.js +46 -0
- package/dist/core/channel_port.d.ts +15 -0
- package/dist/core/channel_port.js +1 -0
- package/dist/i18n.d.ts +1108 -0
- package/dist/i18n.js +1154 -0
- package/dist/lock.d.ts +7 -0
- package/dist/lock.js +80 -0
- package/dist/logger.d.ts +12 -0
- package/dist/logger.js +57 -0
- package/dist/main.d.ts +2 -0
- package/dist/main.js +236 -0
- package/dist/runtime.d.ts +3 -0
- package/dist/runtime.js +14 -0
- package/dist/store/database.d.ts +79 -0
- package/dist/store/database.js +489 -0
- package/dist/store/migrate_bridge_scope.d.ts +6 -0
- package/dist/store/migrate_bridge_scope.js +59 -0
- package/dist/telegram/addressing.d.ts +33 -0
- package/dist/telegram/addressing.js +57 -0
- package/dist/telegram/api.d.ts +14 -0
- package/dist/telegram/api.js +89 -0
- package/dist/telegram/gateway.d.ts +76 -0
- package/dist/telegram/gateway.js +383 -0
- package/dist/telegram/media.d.ts +34 -0
- package/dist/telegram/media.js +180 -0
- package/dist/telegram/rendering.d.ts +10 -0
- package/dist/telegram/rendering.js +21 -0
- package/dist/telegram/scope.d.ts +6 -0
- package/dist/telegram/scope.js +24 -0
- package/dist/telegram/text.d.ts +7 -0
- package/dist/telegram/text.js +47 -0
- package/dist/types.d.ts +343 -0
- package/dist/types.js +1 -0
- package/docs/agent-assisted-install.md +84 -0
- package/docs/install-for-beginners.md +287 -0
- package/docs/troubleshooting.md +239 -0
- package/package.json +62 -0
- package/scripts/doctor.sh +3 -0
- package/scripts/launchd/install.sh +54 -0
- package/scripts/status.sh +3 -0
- package/scripts/systemd/install.sh +83 -0
- package/scripts/systemd/uninstall.sh +15 -0
- package/skills/foxclaw/SKILL.md +167 -0
- package/skills/foxclaw/agents/openai.yaml +4 -0
- package/skills/foxclaw/references/telegram-setup.md +93 -0
- package/skills/foxclaw/scripts/bootstrap_host.py +350 -0
- package/skills/foxclaw/scripts/bootstrap_remote.py +67 -0
|
@@ -0,0 +1,259 @@
|
|
|
1
|
+
import { classifyAgentOutput, inferToolActivityState, } from './activity.js';
|
|
2
|
+
export function splitJsonlChunk(remainder, chunk) {
|
|
3
|
+
const text = `${remainder}${chunk}`;
|
|
4
|
+
if (text.length === 0) {
|
|
5
|
+
return { lines: [], remainder: '' };
|
|
6
|
+
}
|
|
7
|
+
const parts = text.split('\n');
|
|
8
|
+
const nextRemainder = text.endsWith('\n') ? '' : parts.pop() ?? '';
|
|
9
|
+
return {
|
|
10
|
+
lines: parts.filter(line => line.trim().length > 0),
|
|
11
|
+
remainder: nextRemainder,
|
|
12
|
+
};
|
|
13
|
+
}
|
|
14
|
+
export function bootstrapSessionLog(lines) {
|
|
15
|
+
const records = parseRecords(lines);
|
|
16
|
+
let activeTurnId = null;
|
|
17
|
+
let nextMessageIndex = 0;
|
|
18
|
+
let events = [];
|
|
19
|
+
for (const record of records) {
|
|
20
|
+
const next = applySessionRecord(record, { activeTurnId, nextMessageIndex });
|
|
21
|
+
if (next.startedTurnId) {
|
|
22
|
+
activeTurnId = next.startedTurnId;
|
|
23
|
+
nextMessageIndex = 0;
|
|
24
|
+
events = [];
|
|
25
|
+
continue;
|
|
26
|
+
}
|
|
27
|
+
if (!activeTurnId) {
|
|
28
|
+
continue;
|
|
29
|
+
}
|
|
30
|
+
if (next.turnCompleted) {
|
|
31
|
+
activeTurnId = null;
|
|
32
|
+
nextMessageIndex = 0;
|
|
33
|
+
events = [];
|
|
34
|
+
continue;
|
|
35
|
+
}
|
|
36
|
+
events.push(...next.events);
|
|
37
|
+
activeTurnId = next.cursor.activeTurnId;
|
|
38
|
+
nextMessageIndex = next.cursor.nextMessageIndex;
|
|
39
|
+
}
|
|
40
|
+
return {
|
|
41
|
+
cursor: { activeTurnId, nextMessageIndex },
|
|
42
|
+
events,
|
|
43
|
+
startedTurnId: activeTurnId,
|
|
44
|
+
};
|
|
45
|
+
}
|
|
46
|
+
export function applySessionLog(lines, cursor) {
|
|
47
|
+
const records = parseRecords(lines);
|
|
48
|
+
let state = { ...cursor };
|
|
49
|
+
const events = [];
|
|
50
|
+
const startedTurnIds = [];
|
|
51
|
+
for (const record of records) {
|
|
52
|
+
const next = applySessionRecord(record, state);
|
|
53
|
+
if (next.startedTurnId) {
|
|
54
|
+
state = {
|
|
55
|
+
activeTurnId: next.startedTurnId,
|
|
56
|
+
nextMessageIndex: 0,
|
|
57
|
+
};
|
|
58
|
+
startedTurnIds.push(next.startedTurnId);
|
|
59
|
+
continue;
|
|
60
|
+
}
|
|
61
|
+
events.push(...next.events);
|
|
62
|
+
state = next.cursor;
|
|
63
|
+
}
|
|
64
|
+
return {
|
|
65
|
+
cursor: state,
|
|
66
|
+
events,
|
|
67
|
+
startedTurnIds,
|
|
68
|
+
};
|
|
69
|
+
}
|
|
70
|
+
function parseRecords(lines) {
|
|
71
|
+
const records = [];
|
|
72
|
+
for (const line of lines) {
|
|
73
|
+
try {
|
|
74
|
+
const parsed = JSON.parse(line);
|
|
75
|
+
if (parsed && typeof parsed.type === 'string') {
|
|
76
|
+
records.push(parsed);
|
|
77
|
+
}
|
|
78
|
+
}
|
|
79
|
+
catch {
|
|
80
|
+
continue;
|
|
81
|
+
}
|
|
82
|
+
}
|
|
83
|
+
return records;
|
|
84
|
+
}
|
|
85
|
+
function applySessionRecord(record, cursor) {
|
|
86
|
+
const { type, payload } = record;
|
|
87
|
+
if (type === 'event_msg' && payload?.type === 'task_started' && typeof payload.turn_id === 'string') {
|
|
88
|
+
return {
|
|
89
|
+
cursor: { activeTurnId: payload.turn_id, nextMessageIndex: 0 },
|
|
90
|
+
events: [],
|
|
91
|
+
startedTurnId: payload.turn_id,
|
|
92
|
+
turnCompleted: false,
|
|
93
|
+
};
|
|
94
|
+
}
|
|
95
|
+
const activeTurnId = cursor.activeTurnId;
|
|
96
|
+
if (!activeTurnId) {
|
|
97
|
+
return {
|
|
98
|
+
cursor,
|
|
99
|
+
events: [],
|
|
100
|
+
startedTurnId: null,
|
|
101
|
+
turnCompleted: false,
|
|
102
|
+
};
|
|
103
|
+
}
|
|
104
|
+
if (type === 'event_msg' && payload?.type === 'agent_message' && typeof payload.message === 'string') {
|
|
105
|
+
return createSessionTextEvents(activeTurnId, cursor, payload.message, typeof payload.phase === 'string' ? payload.phase : null, false);
|
|
106
|
+
}
|
|
107
|
+
if (type === 'response_item' && payload?.type === 'plan' && typeof payload.text === 'string') {
|
|
108
|
+
return createSessionTextEvents(activeTurnId, cursor, payload.text, 'commentary', true, true);
|
|
109
|
+
}
|
|
110
|
+
if (type === 'event_msg' && payload?.type === 'user_message' && typeof payload.message === 'string') {
|
|
111
|
+
const text = payload.message.trim();
|
|
112
|
+
if (!text) {
|
|
113
|
+
return { cursor, events: [], startedTurnId: null, turnCompleted: false };
|
|
114
|
+
}
|
|
115
|
+
return {
|
|
116
|
+
cursor,
|
|
117
|
+
events: [{
|
|
118
|
+
kind: 'user_message',
|
|
119
|
+
turnId: activeTurnId,
|
|
120
|
+
text,
|
|
121
|
+
}],
|
|
122
|
+
startedTurnId: null,
|
|
123
|
+
turnCompleted: false,
|
|
124
|
+
};
|
|
125
|
+
}
|
|
126
|
+
if (type === 'response_item' && payload?.type === 'function_call' && payload?.name === 'exec_command') {
|
|
127
|
+
const exec = createExecStartEvent(activeTurnId, payload);
|
|
128
|
+
if (!exec) {
|
|
129
|
+
return { cursor, events: [], startedTurnId: null, turnCompleted: false };
|
|
130
|
+
}
|
|
131
|
+
return {
|
|
132
|
+
cursor,
|
|
133
|
+
events: [{
|
|
134
|
+
kind: 'tool_started',
|
|
135
|
+
turnId: activeTurnId,
|
|
136
|
+
exec,
|
|
137
|
+
state: inferToolActivityState(exec),
|
|
138
|
+
}],
|
|
139
|
+
startedTurnId: null,
|
|
140
|
+
turnCompleted: false,
|
|
141
|
+
};
|
|
142
|
+
}
|
|
143
|
+
if (type === 'event_msg' && payload?.type === 'exec_command_end' && payload?.turn_id === activeTurnId) {
|
|
144
|
+
const exec = createExecEndEvent(payload);
|
|
145
|
+
if (!exec) {
|
|
146
|
+
return { cursor, events: [], startedTurnId: null, turnCompleted: false };
|
|
147
|
+
}
|
|
148
|
+
return {
|
|
149
|
+
cursor,
|
|
150
|
+
events: [{
|
|
151
|
+
kind: 'tool_completed',
|
|
152
|
+
turnId: activeTurnId,
|
|
153
|
+
exec,
|
|
154
|
+
state: inferToolActivityState(exec),
|
|
155
|
+
}],
|
|
156
|
+
startedTurnId: null,
|
|
157
|
+
turnCompleted: false,
|
|
158
|
+
};
|
|
159
|
+
}
|
|
160
|
+
if (type === 'event_msg' && payload?.type === 'task_complete' && payload?.turn_id === activeTurnId) {
|
|
161
|
+
return {
|
|
162
|
+
cursor: { activeTurnId: null, nextMessageIndex: 0 },
|
|
163
|
+
events: [{
|
|
164
|
+
kind: 'turn_completed',
|
|
165
|
+
turnId: activeTurnId,
|
|
166
|
+
state: 'completed',
|
|
167
|
+
}],
|
|
168
|
+
startedTurnId: null,
|
|
169
|
+
turnCompleted: true,
|
|
170
|
+
};
|
|
171
|
+
}
|
|
172
|
+
return {
|
|
173
|
+
cursor,
|
|
174
|
+
events: [],
|
|
175
|
+
startedTurnId: null,
|
|
176
|
+
turnCompleted: false,
|
|
177
|
+
};
|
|
178
|
+
}
|
|
179
|
+
function buildSessionItemId(turnId, index) {
|
|
180
|
+
return `${turnId}:session:${index}`;
|
|
181
|
+
}
|
|
182
|
+
function createSessionTextEvents(activeTurnId, cursor, text, phase, forceCommentary, isPlan = false) {
|
|
183
|
+
const itemId = buildSessionItemId(activeTurnId, cursor.nextMessageIndex + 1);
|
|
184
|
+
const outputKind = forceCommentary ? 'commentary' : classifyAgentOutput(phase, true);
|
|
185
|
+
const streamOutputKind = forceCommentary ? 'commentary' : classifyAgentOutput(phase, false);
|
|
186
|
+
return {
|
|
187
|
+
cursor: {
|
|
188
|
+
activeTurnId,
|
|
189
|
+
nextMessageIndex: cursor.nextMessageIndex + 1,
|
|
190
|
+
},
|
|
191
|
+
events: [
|
|
192
|
+
{
|
|
193
|
+
kind: 'agent_message_started',
|
|
194
|
+
turnId: activeTurnId,
|
|
195
|
+
itemId,
|
|
196
|
+
phase,
|
|
197
|
+
outputKind: streamOutputKind,
|
|
198
|
+
isPlan,
|
|
199
|
+
},
|
|
200
|
+
{
|
|
201
|
+
kind: 'agent_message_delta',
|
|
202
|
+
turnId: activeTurnId,
|
|
203
|
+
itemId,
|
|
204
|
+
delta: text,
|
|
205
|
+
outputKind: streamOutputKind,
|
|
206
|
+
isPlan,
|
|
207
|
+
},
|
|
208
|
+
{
|
|
209
|
+
kind: 'agent_message_completed',
|
|
210
|
+
turnId: activeTurnId,
|
|
211
|
+
itemId,
|
|
212
|
+
phase,
|
|
213
|
+
text,
|
|
214
|
+
outputKind,
|
|
215
|
+
isPlan,
|
|
216
|
+
},
|
|
217
|
+
],
|
|
218
|
+
startedTurnId: null,
|
|
219
|
+
turnCompleted: false,
|
|
220
|
+
};
|
|
221
|
+
}
|
|
222
|
+
function createExecStartEvent(turnId, payload) {
|
|
223
|
+
const callId = typeof payload.call_id === 'string' ? payload.call_id : null;
|
|
224
|
+
if (!callId) {
|
|
225
|
+
return null;
|
|
226
|
+
}
|
|
227
|
+
let args = null;
|
|
228
|
+
if (typeof payload.arguments === 'string') {
|
|
229
|
+
try {
|
|
230
|
+
args = JSON.parse(payload.arguments);
|
|
231
|
+
}
|
|
232
|
+
catch {
|
|
233
|
+
args = null;
|
|
234
|
+
}
|
|
235
|
+
}
|
|
236
|
+
const commandText = typeof args?.cmd === 'string' ? args.cmd : null;
|
|
237
|
+
const cwd = typeof args?.workdir === 'string' ? args.workdir : null;
|
|
238
|
+
return {
|
|
239
|
+
callId,
|
|
240
|
+
turnId,
|
|
241
|
+
command: commandText ? ['/bin/bash', '-lc', commandText] : [],
|
|
242
|
+
cwd,
|
|
243
|
+
parsedCmd: [],
|
|
244
|
+
};
|
|
245
|
+
}
|
|
246
|
+
function createExecEndEvent(payload) {
|
|
247
|
+
const callId = typeof payload.call_id === 'string' ? payload.call_id : null;
|
|
248
|
+
const turnId = typeof payload.turn_id === 'string' ? payload.turn_id : null;
|
|
249
|
+
if (!callId || !turnId) {
|
|
250
|
+
return null;
|
|
251
|
+
}
|
|
252
|
+
return {
|
|
253
|
+
callId,
|
|
254
|
+
turnId,
|
|
255
|
+
command: Array.isArray(payload.command) ? payload.command.map((entry) => String(entry)) : [],
|
|
256
|
+
cwd: typeof payload.cwd === 'string' ? payload.cwd : null,
|
|
257
|
+
parsedCmd: Array.isArray(payload.parsed_cmd) ? payload.parsed_cmd : [],
|
|
258
|
+
};
|
|
259
|
+
}
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
import type { AppLocale, PendingApprovalRecord } from '../types.js';
|
|
2
|
+
export interface ActiveTurnStatusSnapshot {
|
|
3
|
+
interruptRequested: boolean;
|
|
4
|
+
pendingApprovalKinds: ReadonlySet<PendingApprovalRecord['kind']>;
|
|
5
|
+
toolStatusText: string | null;
|
|
6
|
+
reasoningActive: boolean;
|
|
7
|
+
hasStreamingReply: boolean;
|
|
8
|
+
}
|
|
9
|
+
export declare function renderActiveTurnStatus(locale: AppLocale, snapshot: ActiveTurnStatusSnapshot): string;
|
|
10
|
+
export declare function formatApprovalKinds(locale: AppLocale, kinds: ReadonlySet<PendingApprovalRecord['kind']>): string;
|
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
import { t } from '../i18n.js';
|
|
2
|
+
export function renderActiveTurnStatus(locale, snapshot) {
|
|
3
|
+
if (snapshot.interruptRequested) {
|
|
4
|
+
return t(locale, 'interrupt_requested_waiting');
|
|
5
|
+
}
|
|
6
|
+
if (snapshot.pendingApprovalKinds.size > 0) {
|
|
7
|
+
return t(locale, 'approval_requested', { kind: formatApprovalKinds(locale, snapshot.pendingApprovalKinds) });
|
|
8
|
+
}
|
|
9
|
+
if (snapshot.toolStatusText) {
|
|
10
|
+
return snapshot.toolStatusText;
|
|
11
|
+
}
|
|
12
|
+
if (snapshot.reasoningActive) {
|
|
13
|
+
return locale === 'zh' ? '正在思考...' : 'Thinking...';
|
|
14
|
+
}
|
|
15
|
+
if (snapshot.hasStreamingReply) {
|
|
16
|
+
return locale === 'zh' ? '正在回复...' : 'Streaming reply...';
|
|
17
|
+
}
|
|
18
|
+
return locale === 'zh' ? '正在思考...' : 'Thinking...';
|
|
19
|
+
}
|
|
20
|
+
export function formatApprovalKinds(locale, kinds) {
|
|
21
|
+
const values = [...kinds].map(kind => locale === 'zh'
|
|
22
|
+
? kind === 'fileChange' ? '文件修改' : kind === 'permissions' ? '权限扩展' : '命令执行'
|
|
23
|
+
: kind === 'fileChange' ? 'file change' : kind === 'permissions' ? 'permissions' : 'command');
|
|
24
|
+
if (values.length === 0) {
|
|
25
|
+
return locale === 'zh' ? '审批' : 'approval';
|
|
26
|
+
}
|
|
27
|
+
return values.join(locale === 'zh' ? '、' : ', ');
|
|
28
|
+
}
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
import { type TelegramScope } from '../telegram/scope.js';
|
|
2
|
+
/** Prefix for Telegram-derived bridge session keys (stored in SQLite `chat_id` / `scope_id` columns). */
|
|
3
|
+
export declare const BRIDGE_SCOPE_TELEGRAM_PREFIX = "telegram:";
|
|
4
|
+
/** Weixin iLink scopes: `weixin:<accountId>:<from_user_id>`. */
|
|
5
|
+
export declare const BRIDGE_SCOPE_WEIXIN_PREFIX = "weixin:";
|
|
6
|
+
export interface WeixinBridgeScope {
|
|
7
|
+
accountId: string;
|
|
8
|
+
fromUserId: string;
|
|
9
|
+
}
|
|
10
|
+
export declare function isBridgeScopedKey(key: string): boolean;
|
|
11
|
+
/** Wrap legacy Telegram inner scope (`chat::topic`) for storage and routing. */
|
|
12
|
+
export declare function toTelegramBridgeScopeId(telegramInnerScopeId: string): string;
|
|
13
|
+
/** Strip `telegram:` prefix; returns `null` if not a Telegram bridge scope. */
|
|
14
|
+
export declare function telegramInnerScopeFromBridge(bridgeScopeId: string): string | null;
|
|
15
|
+
export declare function parseTelegramTargetFromBridgeScope(bridgeScopeId: string): TelegramScope;
|
|
16
|
+
/** Parse `weixin:<accountId>:<from_user_id>`; returns `null` if not a Weixin scope. */
|
|
17
|
+
export declare function parseWeixinBridgeScope(bridgeScopeId: string): WeixinBridgeScope | null;
|
|
18
|
+
export declare function toWeixinBridgeScopeId(accountId: string, fromUserId: string): string;
|
|
@@ -0,0 +1,46 @@
|
|
|
1
|
+
import { parseTelegramScopeId } from '../telegram/scope.js';
|
|
2
|
+
/** Prefix for Telegram-derived bridge session keys (stored in SQLite `chat_id` / `scope_id` columns). */
|
|
3
|
+
export const BRIDGE_SCOPE_TELEGRAM_PREFIX = 'telegram:';
|
|
4
|
+
/** Weixin iLink scopes: `weixin:<accountId>:<from_user_id>`. */
|
|
5
|
+
export const BRIDGE_SCOPE_WEIXIN_PREFIX = 'weixin:';
|
|
6
|
+
export function isBridgeScopedKey(key) {
|
|
7
|
+
return key.startsWith(BRIDGE_SCOPE_TELEGRAM_PREFIX) || key.startsWith(BRIDGE_SCOPE_WEIXIN_PREFIX);
|
|
8
|
+
}
|
|
9
|
+
/** Wrap legacy Telegram inner scope (`chat::topic`) for storage and routing. */
|
|
10
|
+
export function toTelegramBridgeScopeId(telegramInnerScopeId) {
|
|
11
|
+
return `${BRIDGE_SCOPE_TELEGRAM_PREFIX}${telegramInnerScopeId}`;
|
|
12
|
+
}
|
|
13
|
+
/** Strip `telegram:` prefix; returns `null` if not a Telegram bridge scope. */
|
|
14
|
+
export function telegramInnerScopeFromBridge(bridgeScopeId) {
|
|
15
|
+
if (!bridgeScopeId.startsWith(BRIDGE_SCOPE_TELEGRAM_PREFIX)) {
|
|
16
|
+
return null;
|
|
17
|
+
}
|
|
18
|
+
return bridgeScopeId.slice(BRIDGE_SCOPE_TELEGRAM_PREFIX.length);
|
|
19
|
+
}
|
|
20
|
+
export function parseTelegramTargetFromBridgeScope(bridgeScopeId) {
|
|
21
|
+
const inner = telegramInnerScopeFromBridge(bridgeScopeId);
|
|
22
|
+
if (inner === null) {
|
|
23
|
+
throw new Error(`Expected ${BRIDGE_SCOPE_TELEGRAM_PREFIX} scope, got: ${bridgeScopeId}`);
|
|
24
|
+
}
|
|
25
|
+
return parseTelegramScopeId(inner);
|
|
26
|
+
}
|
|
27
|
+
/** Parse `weixin:<accountId>:<from_user_id>`; returns `null` if not a Weixin scope. */
|
|
28
|
+
export function parseWeixinBridgeScope(bridgeScopeId) {
|
|
29
|
+
if (!bridgeScopeId.startsWith(BRIDGE_SCOPE_WEIXIN_PREFIX)) {
|
|
30
|
+
return null;
|
|
31
|
+
}
|
|
32
|
+
const rest = bridgeScopeId.slice(BRIDGE_SCOPE_WEIXIN_PREFIX.length);
|
|
33
|
+
const firstColon = rest.indexOf(':');
|
|
34
|
+
if (firstColon === -1) {
|
|
35
|
+
return null;
|
|
36
|
+
}
|
|
37
|
+
const accountId = rest.slice(0, firstColon);
|
|
38
|
+
const fromUserId = rest.slice(firstColon + 1);
|
|
39
|
+
if (!accountId || !fromUserId) {
|
|
40
|
+
return null;
|
|
41
|
+
}
|
|
42
|
+
return { accountId, fromUserId };
|
|
43
|
+
}
|
|
44
|
+
export function toWeixinBridgeScopeId(accountId, fromUserId) {
|
|
45
|
+
return `${BRIDGE_SCOPE_WEIXIN_PREFIX}${accountId}:${fromUserId}`;
|
|
46
|
+
}
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Opaque outbound message handle for the active UI surface (Telegram message id, Weixin id, etc.).
|
|
3
|
+
*/
|
|
4
|
+
export type ChannelMessageRef = number | string;
|
|
5
|
+
export type ChannelInlineKeyboard = Array<Array<{
|
|
6
|
+
text: string;
|
|
7
|
+
callback_data: string;
|
|
8
|
+
}>>;
|
|
9
|
+
/**
|
|
10
|
+
* Future: channel-agnostic outbound surface. Telegram uses {@link TelegramMessagingPort} today.
|
|
11
|
+
*/
|
|
12
|
+
export interface ChannelPort {
|
|
13
|
+
sendPlain(scopeId: string, text: string, keyboard?: ChannelInlineKeyboard): Promise<ChannelMessageRef>;
|
|
14
|
+
sendHtml(scopeId: string, text: string, keyboard?: ChannelInlineKeyboard): Promise<ChannelMessageRef>;
|
|
15
|
+
}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export {};
|