@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,180 @@
|
|
|
1
|
+
import path from 'node:path';
|
|
2
|
+
export const TELEGRAM_INBOX_DIR = '.telegram-inbox';
|
|
3
|
+
export const TELEGRAM_BOT_API_DOWNLOAD_LIMIT_BYTES = 20 * 1024 * 1024;
|
|
4
|
+
const FALLBACK_EXTENSION_BY_KIND = {
|
|
5
|
+
photo: '.jpg',
|
|
6
|
+
document: '',
|
|
7
|
+
audio: '.mp3',
|
|
8
|
+
voice: '.ogg',
|
|
9
|
+
video: '.mp4',
|
|
10
|
+
animation: '.mp4',
|
|
11
|
+
sticker: '.webp',
|
|
12
|
+
videoNote: '.mp4',
|
|
13
|
+
};
|
|
14
|
+
const MIME_EXTENSION_MAP = {
|
|
15
|
+
'application/gzip': '.gz',
|
|
16
|
+
'application/json': '.json',
|
|
17
|
+
'application/pdf': '.pdf',
|
|
18
|
+
'application/zip': '.zip',
|
|
19
|
+
'audio/mpeg': '.mp3',
|
|
20
|
+
'audio/mp4': '.m4a',
|
|
21
|
+
'audio/ogg': '.ogg',
|
|
22
|
+
'audio/wav': '.wav',
|
|
23
|
+
'image/gif': '.gif',
|
|
24
|
+
'image/jpeg': '.jpg',
|
|
25
|
+
'image/png': '.png',
|
|
26
|
+
'image/webp': '.webp',
|
|
27
|
+
'text/csv': '.csv',
|
|
28
|
+
'text/html': '.html',
|
|
29
|
+
'text/markdown': '.md',
|
|
30
|
+
'text/plain': '.txt',
|
|
31
|
+
'video/mp4': '.mp4',
|
|
32
|
+
'video/quicktime': '.mov',
|
|
33
|
+
};
|
|
34
|
+
export function isNativeImageAttachment(attachment) {
|
|
35
|
+
if (attachment.kind === 'photo') {
|
|
36
|
+
return true;
|
|
37
|
+
}
|
|
38
|
+
if (attachment.kind === 'sticker') {
|
|
39
|
+
return !attachment.isAnimated && !attachment.isVideo;
|
|
40
|
+
}
|
|
41
|
+
if (attachment.kind !== 'document') {
|
|
42
|
+
return false;
|
|
43
|
+
}
|
|
44
|
+
const lowerMime = attachment.mimeType?.toLowerCase() ?? '';
|
|
45
|
+
if (lowerMime === 'image/jpeg' || lowerMime === 'image/png' || lowerMime === 'image/webp' || lowerMime === 'image/gif') {
|
|
46
|
+
return true;
|
|
47
|
+
}
|
|
48
|
+
const extension = extensionFromName(attachment.fileName);
|
|
49
|
+
return extension === '.jpg' || extension === '.jpeg' || extension === '.png' || extension === '.webp' || extension === '.gif';
|
|
50
|
+
}
|
|
51
|
+
export function planAttachmentStoragePath(cwd, threadId, attachment, remoteFilePath, now = new Date()) {
|
|
52
|
+
const dateSegment = now.toISOString().slice(0, 10);
|
|
53
|
+
const threadSegment = sanitizePathSegment(threadId);
|
|
54
|
+
const timestamp = now.toISOString().slice(0, 19).replace(/[-:]/g, '').replace('T', '-');
|
|
55
|
+
const resolvedFileName = resolveAttachmentFileName(attachment, remoteFilePath);
|
|
56
|
+
const uniquePrefix = `${timestamp}-${sanitizePathSegment(attachment.fileUniqueId).slice(0, 16)}`;
|
|
57
|
+
const joinedName = truncateFileName(`${uniquePrefix}-${resolvedFileName}`, 160);
|
|
58
|
+
const relativePath = path.join(TELEGRAM_INBOX_DIR, dateSegment, threadSegment, joinedName);
|
|
59
|
+
return {
|
|
60
|
+
fileName: resolvedFileName,
|
|
61
|
+
localPath: path.resolve(cwd, relativePath),
|
|
62
|
+
relativePath,
|
|
63
|
+
};
|
|
64
|
+
}
|
|
65
|
+
export function buildAttachmentPrompt(userText, attachments) {
|
|
66
|
+
const normalizedText = userText.trim();
|
|
67
|
+
const lines = [];
|
|
68
|
+
if (normalizedText) {
|
|
69
|
+
lines.push(normalizedText, '');
|
|
70
|
+
}
|
|
71
|
+
else {
|
|
72
|
+
lines.push('User sent Telegram attachments without a caption.', '');
|
|
73
|
+
}
|
|
74
|
+
lines.push('Telegram attachments:');
|
|
75
|
+
attachments.forEach((attachment, index) => {
|
|
76
|
+
lines.push(`${index + 1}. ${describeAttachment(attachment)}`);
|
|
77
|
+
lines.push(` filename: ${attachment.fileName}`);
|
|
78
|
+
lines.push(` path: ${attachment.localPath}`);
|
|
79
|
+
if (attachment.mimeType)
|
|
80
|
+
lines.push(` mime: ${attachment.mimeType}`);
|
|
81
|
+
if (attachment.fileSize !== null)
|
|
82
|
+
lines.push(` size_bytes: ${attachment.fileSize}`);
|
|
83
|
+
if (attachment.width !== null && attachment.height !== null) {
|
|
84
|
+
lines.push(` dimensions: ${attachment.width}x${attachment.height}`);
|
|
85
|
+
}
|
|
86
|
+
if (attachment.durationSeconds !== null) {
|
|
87
|
+
lines.push(` duration_seconds: ${attachment.durationSeconds}`);
|
|
88
|
+
}
|
|
89
|
+
if (attachment.nativeImage) {
|
|
90
|
+
lines.push(' attached_as: localImage');
|
|
91
|
+
}
|
|
92
|
+
});
|
|
93
|
+
lines.push('', 'Use the local file paths above when you inspect these attachments.');
|
|
94
|
+
return lines.join('\n');
|
|
95
|
+
}
|
|
96
|
+
export function summarizeTelegramInput(text, attachments) {
|
|
97
|
+
const lines = [];
|
|
98
|
+
const normalizedText = text.trim();
|
|
99
|
+
if (normalizedText) {
|
|
100
|
+
lines.push(normalizedText);
|
|
101
|
+
}
|
|
102
|
+
if (attachments.length > 0) {
|
|
103
|
+
lines.push(`[attachments: ${attachments.map((attachment) => `${attachment.kind}:${attachment.fileName ?? attachment.fileUniqueId}`).join(', ')}]`);
|
|
104
|
+
}
|
|
105
|
+
return truncateSummary(lines.join(' '), 500);
|
|
106
|
+
}
|
|
107
|
+
function resolveAttachmentFileName(attachment, remoteFilePath) {
|
|
108
|
+
const providedName = attachment.fileName ? path.basename(attachment.fileName) : '';
|
|
109
|
+
const remoteName = remoteFilePath ? path.basename(remoteFilePath) : '';
|
|
110
|
+
const baseCandidate = providedName || remoteName || `${attachment.kind}-${attachment.fileUniqueId}`;
|
|
111
|
+
const sanitizedBase = sanitizeFileName(baseCandidate);
|
|
112
|
+
const extension = extensionFromName(sanitizedBase)
|
|
113
|
+
|| extensionFromName(remoteName)
|
|
114
|
+
|| extensionFromMimeType(attachment.mimeType)
|
|
115
|
+
|| fallbackExtensionByKind(attachment.kind);
|
|
116
|
+
if (!extension) {
|
|
117
|
+
return sanitizedBase;
|
|
118
|
+
}
|
|
119
|
+
const withoutExtension = sanitizedBase.slice(0, sanitizedBase.length - extensionFromName(sanitizedBase).length) || `${attachment.kind}-${attachment.fileUniqueId}`;
|
|
120
|
+
return truncateFileName(`${withoutExtension}${extension}`, 120);
|
|
121
|
+
}
|
|
122
|
+
function describeAttachment(attachment) {
|
|
123
|
+
switch (attachment.kind) {
|
|
124
|
+
case 'photo':
|
|
125
|
+
return 'photo';
|
|
126
|
+
case 'document':
|
|
127
|
+
return 'document';
|
|
128
|
+
case 'audio':
|
|
129
|
+
return 'audio file';
|
|
130
|
+
case 'voice':
|
|
131
|
+
return 'voice message';
|
|
132
|
+
case 'video':
|
|
133
|
+
return 'video';
|
|
134
|
+
case 'animation':
|
|
135
|
+
return 'animation';
|
|
136
|
+
case 'sticker':
|
|
137
|
+
return attachment.isAnimated || attachment.isVideo ? 'animated sticker' : 'sticker';
|
|
138
|
+
case 'videoNote':
|
|
139
|
+
return 'video note';
|
|
140
|
+
}
|
|
141
|
+
}
|
|
142
|
+
function sanitizeFileName(fileName) {
|
|
143
|
+
const trimmed = fileName.trim().replace(/[\\/]+/g, '-');
|
|
144
|
+
const sanitized = trimmed
|
|
145
|
+
.replace(/[^\w.\-()+@]+/g, '-')
|
|
146
|
+
.replace(/-+/g, '-')
|
|
147
|
+
.replace(/^\.+/, '')
|
|
148
|
+
.replace(/^-+/, '')
|
|
149
|
+
.replace(/[.-]+$/, '');
|
|
150
|
+
return sanitized || 'attachment';
|
|
151
|
+
}
|
|
152
|
+
function sanitizePathSegment(value) {
|
|
153
|
+
return value.replace(/[^\w.-]+/g, '-').replace(/-+/g, '-').replace(/^-+|-+$/g, '') || 'thread';
|
|
154
|
+
}
|
|
155
|
+
function extensionFromMimeType(mimeType) {
|
|
156
|
+
if (!mimeType)
|
|
157
|
+
return '';
|
|
158
|
+
return MIME_EXTENSION_MAP[mimeType.toLowerCase()] ?? '';
|
|
159
|
+
}
|
|
160
|
+
function extensionFromName(fileName) {
|
|
161
|
+
if (!fileName)
|
|
162
|
+
return '';
|
|
163
|
+
const extension = path.extname(fileName).toLowerCase();
|
|
164
|
+
return extension === '.' ? '' : extension;
|
|
165
|
+
}
|
|
166
|
+
function truncateFileName(fileName, maxLength) {
|
|
167
|
+
if (fileName.length <= maxLength)
|
|
168
|
+
return fileName;
|
|
169
|
+
const extension = extensionFromName(fileName);
|
|
170
|
+
const base = extension ? fileName.slice(0, -extension.length) : fileName;
|
|
171
|
+
return `${base.slice(0, Math.max(1, maxLength - extension.length))}${extension}`;
|
|
172
|
+
}
|
|
173
|
+
function truncateSummary(text, maxLength) {
|
|
174
|
+
if (text.length <= maxLength)
|
|
175
|
+
return text;
|
|
176
|
+
return `${text.slice(0, maxLength - 3)}...`;
|
|
177
|
+
}
|
|
178
|
+
function fallbackExtensionByKind(kind) {
|
|
179
|
+
return FALLBACK_EXTENSION_BY_KIND[kind] ?? '';
|
|
180
|
+
}
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
export type TelegramConversationKind = 'private_chat' | 'private_topic' | 'group_chat' | 'group_topic';
|
|
2
|
+
export type TelegramRendererKind = 'draft_stream' | 'segmented_stream';
|
|
3
|
+
export interface TelegramRenderRoute {
|
|
4
|
+
conversationKind: TelegramConversationKind;
|
|
5
|
+
preferredRenderer: TelegramRendererKind;
|
|
6
|
+
currentRenderer: TelegramRendererKind;
|
|
7
|
+
supportsDraftStreaming: boolean;
|
|
8
|
+
usesMessageThread: boolean;
|
|
9
|
+
}
|
|
10
|
+
export declare function resolveTelegramRenderRoute(chatType: string, topicId: number | null): TelegramRenderRoute;
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
export function resolveTelegramRenderRoute(chatType, topicId) {
|
|
2
|
+
const conversationKind = resolveTelegramConversationKind(chatType, topicId);
|
|
3
|
+
const supportsDraftStreaming = chatType === 'private';
|
|
4
|
+
const preferredRenderer = 'segmented_stream';
|
|
5
|
+
return {
|
|
6
|
+
conversationKind,
|
|
7
|
+
// Telegram draft updates are available in private chats, but segmented live messages
|
|
8
|
+
// are the more stable default because they do not overwrite visible partial output.
|
|
9
|
+
preferredRenderer,
|
|
10
|
+
currentRenderer: preferredRenderer,
|
|
11
|
+
supportsDraftStreaming,
|
|
12
|
+
usesMessageThread: topicId !== null,
|
|
13
|
+
};
|
|
14
|
+
}
|
|
15
|
+
function resolveTelegramConversationKind(chatType, topicId) {
|
|
16
|
+
const isPrivate = chatType === 'private';
|
|
17
|
+
if (isPrivate) {
|
|
18
|
+
return topicId === null ? 'private_chat' : 'private_topic';
|
|
19
|
+
}
|
|
20
|
+
return topicId === null ? 'group_chat' : 'group_topic';
|
|
21
|
+
}
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
const ROOT_TOPIC_KEY = 'root';
|
|
2
|
+
const SCOPE_SEPARATOR = '::';
|
|
3
|
+
export function createTelegramScopeId(chatId, topicId) {
|
|
4
|
+
return `${chatId}${SCOPE_SEPARATOR}${topicId ?? ROOT_TOPIC_KEY}`;
|
|
5
|
+
}
|
|
6
|
+
export function parseTelegramScopeId(scopeId) {
|
|
7
|
+
const separatorIndex = scopeId.lastIndexOf(SCOPE_SEPARATOR);
|
|
8
|
+
if (separatorIndex === -1) {
|
|
9
|
+
return { chatId: scopeId, topicId: null };
|
|
10
|
+
}
|
|
11
|
+
const chatId = scopeId.slice(0, separatorIndex);
|
|
12
|
+
const topicPart = scopeId.slice(separatorIndex + SCOPE_SEPARATOR.length);
|
|
13
|
+
if (!chatId) {
|
|
14
|
+
throw new Error(`Invalid Telegram scope id: ${scopeId}`);
|
|
15
|
+
}
|
|
16
|
+
if (topicPart === ROOT_TOPIC_KEY || topicPart === '') {
|
|
17
|
+
return { chatId, topicId: null };
|
|
18
|
+
}
|
|
19
|
+
const topicId = Number.parseInt(topicPart, 10);
|
|
20
|
+
if (!Number.isFinite(topicId)) {
|
|
21
|
+
throw new Error(`Invalid Telegram topic id in scope: ${scopeId}`);
|
|
22
|
+
}
|
|
23
|
+
return { chatId, topicId };
|
|
24
|
+
}
|
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
export declare const TELEGRAM_MESSAGE_LIMIT = 4000;
|
|
2
|
+
export declare const TELEGRAM_STREAM_MESSAGE_LIMIT = 1200;
|
|
3
|
+
export declare const TELEGRAM_DRAFT_LIMIT = 4000;
|
|
4
|
+
export declare function sanitizeTelegramPreview(text: string): string;
|
|
5
|
+
export declare function chunkTelegramMessage(text: string, limit?: number, fallbackText?: string): string[];
|
|
6
|
+
export declare function chunkTelegramStreamMessage(text: string, limit?: number): string[];
|
|
7
|
+
export declare function clipTelegramDraftMessage(text: string, fallbackText?: string): string;
|
|
@@ -0,0 +1,47 @@
|
|
|
1
|
+
export const TELEGRAM_MESSAGE_LIMIT = 4000;
|
|
2
|
+
export const TELEGRAM_STREAM_MESSAGE_LIMIT = 1200;
|
|
3
|
+
export const TELEGRAM_DRAFT_LIMIT = 4000;
|
|
4
|
+
export function sanitizeTelegramPreview(text) {
|
|
5
|
+
if (!text.trim())
|
|
6
|
+
return 'Working...';
|
|
7
|
+
return text.length > TELEGRAM_MESSAGE_LIMIT
|
|
8
|
+
? `${text.slice(0, TELEGRAM_MESSAGE_LIMIT - 3)}...`
|
|
9
|
+
: text;
|
|
10
|
+
}
|
|
11
|
+
export function chunkTelegramMessage(text, limit = TELEGRAM_MESSAGE_LIMIT, fallbackText = 'Completed.') {
|
|
12
|
+
const source = text.trim() ? text : fallbackText;
|
|
13
|
+
if (!source) {
|
|
14
|
+
return [];
|
|
15
|
+
}
|
|
16
|
+
if (source.length <= limit) {
|
|
17
|
+
return [source];
|
|
18
|
+
}
|
|
19
|
+
const chunks = [];
|
|
20
|
+
let start = 0;
|
|
21
|
+
while (start < source.length) {
|
|
22
|
+
const remaining = source.length - start;
|
|
23
|
+
if (remaining <= limit) {
|
|
24
|
+
chunks.push(source.slice(start));
|
|
25
|
+
break;
|
|
26
|
+
}
|
|
27
|
+
const tentativeEnd = start + limit;
|
|
28
|
+
const window = source.slice(start, tentativeEnd);
|
|
29
|
+
const splitAt = Math.max(window.lastIndexOf('\n\n'), window.lastIndexOf('\n'));
|
|
30
|
+
const end = splitAt >= Math.floor(limit / 2)
|
|
31
|
+
? start + splitAt + 1
|
|
32
|
+
: tentativeEnd;
|
|
33
|
+
chunks.push(source.slice(start, end));
|
|
34
|
+
start = end;
|
|
35
|
+
}
|
|
36
|
+
return chunks.filter(chunk => chunk.length > 0);
|
|
37
|
+
}
|
|
38
|
+
export function chunkTelegramStreamMessage(text, limit = TELEGRAM_STREAM_MESSAGE_LIMIT) {
|
|
39
|
+
return chunkTelegramMessage(text, limit, '');
|
|
40
|
+
}
|
|
41
|
+
export function clipTelegramDraftMessage(text, fallbackText = 'Thinking...') {
|
|
42
|
+
const source = text.trim() ? text : fallbackText;
|
|
43
|
+
if (source.length <= TELEGRAM_DRAFT_LIMIT) {
|
|
44
|
+
return source;
|
|
45
|
+
}
|
|
46
|
+
return `${source.slice(0, TELEGRAM_DRAFT_LIMIT - 1)}…`;
|
|
47
|
+
}
|
package/dist/types.d.ts
ADDED
|
@@ -0,0 +1,343 @@
|
|
|
1
|
+
/** Session row: `chatId` holds the bridge scope id (e.g. `telegram:…`). */
|
|
2
|
+
export interface ThreadBinding {
|
|
3
|
+
chatId: string;
|
|
4
|
+
threadId: string;
|
|
5
|
+
cwd: string | null;
|
|
6
|
+
updatedAt: number;
|
|
7
|
+
}
|
|
8
|
+
export type AppLocale = 'en' | 'zh';
|
|
9
|
+
export type ApprovalPolicyValue = 'on-request' | 'on-failure' | 'never' | 'untrusted';
|
|
10
|
+
export type SandboxModeValue = 'read-only' | 'workspace-write' | 'danger-full-access';
|
|
11
|
+
export type AccessPresetValue = 'read-only' | 'default' | 'full-access';
|
|
12
|
+
export type CollaborationModeValue = 'default' | 'plan';
|
|
13
|
+
export type ActiveTurnMessageMode = 'steer' | 'queue';
|
|
14
|
+
export type ReasoningEffortValue = 'none' | 'minimal' | 'low' | 'medium' | 'high' | 'xhigh';
|
|
15
|
+
export type ThreadStatusKind = 'active' | 'idle' | 'notLoaded' | 'systemError';
|
|
16
|
+
export interface ChatSessionSettings {
|
|
17
|
+
/** Bridge scope id (e.g. `telegram:…`). */
|
|
18
|
+
chatId: string;
|
|
19
|
+
model: string | null;
|
|
20
|
+
reasoningEffort: ReasoningEffortValue | null;
|
|
21
|
+
locale: AppLocale | null;
|
|
22
|
+
accessPreset: AccessPresetValue | null;
|
|
23
|
+
collaborationMode: CollaborationModeValue | null;
|
|
24
|
+
serviceTier: string | null;
|
|
25
|
+
activeTurnMessageMode: ActiveTurnMessageMode | null;
|
|
26
|
+
updatedAt: number;
|
|
27
|
+
}
|
|
28
|
+
export interface CachedThread {
|
|
29
|
+
index: number;
|
|
30
|
+
threadId: string;
|
|
31
|
+
name: string | null;
|
|
32
|
+
preview: string;
|
|
33
|
+
cwd: string | null;
|
|
34
|
+
modelProvider: string | null;
|
|
35
|
+
status: ThreadStatusKind;
|
|
36
|
+
archived: boolean;
|
|
37
|
+
updatedAt: number;
|
|
38
|
+
}
|
|
39
|
+
export interface AppThread {
|
|
40
|
+
threadId: string;
|
|
41
|
+
name: string | null;
|
|
42
|
+
preview: string;
|
|
43
|
+
cwd: string | null;
|
|
44
|
+
modelProvider: string | null;
|
|
45
|
+
source: string | null;
|
|
46
|
+
path: string | null;
|
|
47
|
+
status: ThreadStatusKind;
|
|
48
|
+
updatedAt: number;
|
|
49
|
+
}
|
|
50
|
+
export interface AppTurnItemSnapshot {
|
|
51
|
+
itemId: string;
|
|
52
|
+
type: string;
|
|
53
|
+
phase: string | null;
|
|
54
|
+
text: string | null;
|
|
55
|
+
command: string | null;
|
|
56
|
+
status: string | null;
|
|
57
|
+
aggregatedOutput: string | null;
|
|
58
|
+
}
|
|
59
|
+
export interface AppTurnSnapshot {
|
|
60
|
+
turnId: string;
|
|
61
|
+
status: string;
|
|
62
|
+
error: string | null;
|
|
63
|
+
items: AppTurnItemSnapshot[];
|
|
64
|
+
itemsView?: string | null;
|
|
65
|
+
startedAt?: number | null;
|
|
66
|
+
completedAt?: number | null;
|
|
67
|
+
durationMs?: number | null;
|
|
68
|
+
}
|
|
69
|
+
export interface AppThreadSnapshot extends AppThread {
|
|
70
|
+
activeFlags: string[];
|
|
71
|
+
turns: AppTurnSnapshot[];
|
|
72
|
+
}
|
|
73
|
+
export interface ThreadSessionState {
|
|
74
|
+
thread: AppThread;
|
|
75
|
+
model: string;
|
|
76
|
+
modelProvider: string;
|
|
77
|
+
reasoningEffort: ReasoningEffortValue | null;
|
|
78
|
+
cwd: string;
|
|
79
|
+
}
|
|
80
|
+
export interface CodexAccountInfo {
|
|
81
|
+
type: string;
|
|
82
|
+
email: string | null;
|
|
83
|
+
planType: string | null;
|
|
84
|
+
requiresOpenaiAuth: boolean;
|
|
85
|
+
}
|
|
86
|
+
export interface CodexRateLimitWindow {
|
|
87
|
+
usedPercent: number;
|
|
88
|
+
windowDurationMins: number | null;
|
|
89
|
+
resetsAt: number | null;
|
|
90
|
+
}
|
|
91
|
+
export interface CodexCreditsSnapshot {
|
|
92
|
+
hasCredits: boolean;
|
|
93
|
+
unlimited: boolean;
|
|
94
|
+
balance: string | null;
|
|
95
|
+
}
|
|
96
|
+
export interface CodexRateLimitSnapshot {
|
|
97
|
+
limitId: string | null;
|
|
98
|
+
limitName: string | null;
|
|
99
|
+
primary: CodexRateLimitWindow | null;
|
|
100
|
+
secondary: CodexRateLimitWindow | null;
|
|
101
|
+
credits: CodexCreditsSnapshot | null;
|
|
102
|
+
planType: string | null;
|
|
103
|
+
rateLimitReachedType: string | null;
|
|
104
|
+
}
|
|
105
|
+
export interface CodexAccountRateLimits {
|
|
106
|
+
rateLimits: CodexRateLimitSnapshot | null;
|
|
107
|
+
rateLimitsByLimitId: Record<string, CodexRateLimitSnapshot> | null;
|
|
108
|
+
}
|
|
109
|
+
export interface CodexLoginDeviceCode {
|
|
110
|
+
type: 'chatgptDeviceCode';
|
|
111
|
+
loginId: string;
|
|
112
|
+
verificationUrl: string;
|
|
113
|
+
userCode: string;
|
|
114
|
+
}
|
|
115
|
+
export interface CodexSkillMetadata {
|
|
116
|
+
name: string;
|
|
117
|
+
description: string;
|
|
118
|
+
shortDescription: string | null;
|
|
119
|
+
path: string;
|
|
120
|
+
scope: string;
|
|
121
|
+
enabled: boolean;
|
|
122
|
+
displayName: string | null;
|
|
123
|
+
defaultPrompt: string | null;
|
|
124
|
+
}
|
|
125
|
+
export interface CodexSkillsListEntry {
|
|
126
|
+
cwd: string;
|
|
127
|
+
skills: CodexSkillMetadata[];
|
|
128
|
+
errors: string[];
|
|
129
|
+
}
|
|
130
|
+
export interface CodexMcpServerStatus {
|
|
131
|
+
name: string;
|
|
132
|
+
authStatus: string;
|
|
133
|
+
toolNames: string[];
|
|
134
|
+
resourceUris: string[];
|
|
135
|
+
resourceTemplateUris: string[];
|
|
136
|
+
}
|
|
137
|
+
export interface CodexMcpResourceContent {
|
|
138
|
+
type: string;
|
|
139
|
+
text: string | null;
|
|
140
|
+
blob: string | null;
|
|
141
|
+
mimeType: string | null;
|
|
142
|
+
uri: string | null;
|
|
143
|
+
}
|
|
144
|
+
export interface CodexHookMetadata {
|
|
145
|
+
key: string;
|
|
146
|
+
eventName: string;
|
|
147
|
+
handlerType: string;
|
|
148
|
+
enabled: boolean;
|
|
149
|
+
trustStatus: string;
|
|
150
|
+
sourcePath: string;
|
|
151
|
+
pluginId: string | null;
|
|
152
|
+
command: string | null;
|
|
153
|
+
statusMessage: string | null;
|
|
154
|
+
}
|
|
155
|
+
export interface CodexHooksListEntry {
|
|
156
|
+
cwd: string;
|
|
157
|
+
hooks: CodexHookMetadata[];
|
|
158
|
+
errors: Array<{
|
|
159
|
+
path: string;
|
|
160
|
+
message: string;
|
|
161
|
+
}>;
|
|
162
|
+
warnings: string[];
|
|
163
|
+
}
|
|
164
|
+
export interface CodexPluginSummary {
|
|
165
|
+
id: string;
|
|
166
|
+
name: string;
|
|
167
|
+
enabled: boolean;
|
|
168
|
+
installed: boolean;
|
|
169
|
+
source: string;
|
|
170
|
+
availability: string;
|
|
171
|
+
authPolicy: string;
|
|
172
|
+
installPolicy: string;
|
|
173
|
+
keywords: string[];
|
|
174
|
+
}
|
|
175
|
+
export interface CodexPluginMarketplace {
|
|
176
|
+
name: string;
|
|
177
|
+
displayName: string | null;
|
|
178
|
+
path: string | null;
|
|
179
|
+
plugins: CodexPluginSummary[];
|
|
180
|
+
}
|
|
181
|
+
export interface CodexPluginSkillSummary {
|
|
182
|
+
name: string;
|
|
183
|
+
description: string;
|
|
184
|
+
shortDescription: string | null;
|
|
185
|
+
enabled: boolean;
|
|
186
|
+
path: string | null;
|
|
187
|
+
}
|
|
188
|
+
export interface CodexPluginDetail {
|
|
189
|
+
marketplaceName: string;
|
|
190
|
+
marketplacePath: string | null;
|
|
191
|
+
summary: CodexPluginSummary;
|
|
192
|
+
description: string | null;
|
|
193
|
+
skills: CodexPluginSkillSummary[];
|
|
194
|
+
hooks: Array<{
|
|
195
|
+
key: string;
|
|
196
|
+
eventName: string;
|
|
197
|
+
}>;
|
|
198
|
+
apps: Array<{
|
|
199
|
+
id: string;
|
|
200
|
+
name: string;
|
|
201
|
+
description: string | null;
|
|
202
|
+
needsAuth: boolean;
|
|
203
|
+
}>;
|
|
204
|
+
mcpServers: string[];
|
|
205
|
+
}
|
|
206
|
+
export interface CodexAppInfo {
|
|
207
|
+
id: string;
|
|
208
|
+
name: string;
|
|
209
|
+
description: string | null;
|
|
210
|
+
isEnabled: boolean;
|
|
211
|
+
isAccessible: boolean;
|
|
212
|
+
installUrl: string | null;
|
|
213
|
+
distributionChannel: string | null;
|
|
214
|
+
pluginDisplayNames: string[];
|
|
215
|
+
}
|
|
216
|
+
export interface CodexExperimentalFeature {
|
|
217
|
+
name: string;
|
|
218
|
+
displayName: string | null;
|
|
219
|
+
enabled: boolean;
|
|
220
|
+
defaultEnabled: boolean;
|
|
221
|
+
stage: string;
|
|
222
|
+
description: string | null;
|
|
223
|
+
}
|
|
224
|
+
export interface CodexConfigRequirements {
|
|
225
|
+
allowedApprovalPolicies: string[] | null;
|
|
226
|
+
allowedSandboxModes: string[] | null;
|
|
227
|
+
allowedWebSearchModes: string[] | null;
|
|
228
|
+
enforceResidency: string | null;
|
|
229
|
+
featureRequirements: Record<string, boolean> | null;
|
|
230
|
+
}
|
|
231
|
+
export interface CodexModelProviderCapabilities {
|
|
232
|
+
webSearch: boolean;
|
|
233
|
+
imageGeneration: boolean;
|
|
234
|
+
namespaceTools: boolean;
|
|
235
|
+
}
|
|
236
|
+
export type ThreadGoalStatusValue = 'active' | 'paused' | 'budgetLimited' | 'complete';
|
|
237
|
+
export interface CodexThreadGoal {
|
|
238
|
+
threadId: string;
|
|
239
|
+
objective: string;
|
|
240
|
+
status: ThreadGoalStatusValue;
|
|
241
|
+
tokenBudget: number | null;
|
|
242
|
+
tokensUsed: number;
|
|
243
|
+
timeUsedSeconds: number;
|
|
244
|
+
createdAt: number;
|
|
245
|
+
updatedAt: number;
|
|
246
|
+
}
|
|
247
|
+
export interface CodexFuzzyFileResult {
|
|
248
|
+
root: string;
|
|
249
|
+
path: string;
|
|
250
|
+
matchType: string;
|
|
251
|
+
fileName: string;
|
|
252
|
+
score: number;
|
|
253
|
+
}
|
|
254
|
+
export type ReviewTarget = {
|
|
255
|
+
type: 'uncommittedChanges';
|
|
256
|
+
} | {
|
|
257
|
+
type: 'baseBranch';
|
|
258
|
+
branch: string;
|
|
259
|
+
} | {
|
|
260
|
+
type: 'commit';
|
|
261
|
+
sha: string;
|
|
262
|
+
title: string | null;
|
|
263
|
+
} | {
|
|
264
|
+
type: 'custom';
|
|
265
|
+
instructions: string;
|
|
266
|
+
};
|
|
267
|
+
export interface CodexEffectiveConfig {
|
|
268
|
+
model: string | null;
|
|
269
|
+
modelReasoningEffort: ReasoningEffortValue | null;
|
|
270
|
+
planModeReasoningEffort: ReasoningEffortValue | null;
|
|
271
|
+
developerInstructions: string | null;
|
|
272
|
+
}
|
|
273
|
+
export interface CodexCollaborationModePreset {
|
|
274
|
+
name: string;
|
|
275
|
+
mode: CollaborationModeValue | null;
|
|
276
|
+
model: string | null;
|
|
277
|
+
reasoningEffort: ReasoningEffortValue | null;
|
|
278
|
+
}
|
|
279
|
+
export interface CodexCollaborationMode {
|
|
280
|
+
mode: CollaborationModeValue;
|
|
281
|
+
settings: {
|
|
282
|
+
model: string;
|
|
283
|
+
reasoning_effort: ReasoningEffortValue | null;
|
|
284
|
+
developer_instructions: string | null;
|
|
285
|
+
};
|
|
286
|
+
}
|
|
287
|
+
export interface ModelInfo {
|
|
288
|
+
id: string;
|
|
289
|
+
model: string;
|
|
290
|
+
displayName: string;
|
|
291
|
+
description: string;
|
|
292
|
+
isDefault: boolean;
|
|
293
|
+
supportedReasoningEfforts: ReasoningEffortValue[];
|
|
294
|
+
defaultReasoningEffort: ReasoningEffortValue;
|
|
295
|
+
serviceTiers: ModelServiceTier[];
|
|
296
|
+
}
|
|
297
|
+
export interface ModelServiceTier {
|
|
298
|
+
id: string;
|
|
299
|
+
name: string;
|
|
300
|
+
description: string;
|
|
301
|
+
}
|
|
302
|
+
export type ApprovalKind = 'command' | 'fileChange' | 'permissions';
|
|
303
|
+
export interface PendingApprovalRecord {
|
|
304
|
+
localId: string;
|
|
305
|
+
serverRequestId: string;
|
|
306
|
+
kind: ApprovalKind;
|
|
307
|
+
/** Bridge scope id (e.g. `telegram:…`). */
|
|
308
|
+
chatId: string;
|
|
309
|
+
threadId: string;
|
|
310
|
+
turnId: string;
|
|
311
|
+
itemId: string;
|
|
312
|
+
approvalId: string | null;
|
|
313
|
+
reason: string | null;
|
|
314
|
+
command: string | null;
|
|
315
|
+
cwd: string | null;
|
|
316
|
+
payloadJson: string | null;
|
|
317
|
+
messageId: number | null;
|
|
318
|
+
createdAt: number;
|
|
319
|
+
resolvedAt: number | null;
|
|
320
|
+
}
|
|
321
|
+
export interface RuntimeStatus {
|
|
322
|
+
running: boolean;
|
|
323
|
+
connected: boolean;
|
|
324
|
+
userAgent: string | null;
|
|
325
|
+
codexAppServer?: {
|
|
326
|
+
pid: number | null;
|
|
327
|
+
port: number | null;
|
|
328
|
+
running: boolean;
|
|
329
|
+
managed: boolean;
|
|
330
|
+
};
|
|
331
|
+
botUsername: string | null;
|
|
332
|
+
currentBindings: number;
|
|
333
|
+
pendingApprovals: number;
|
|
334
|
+
pendingUserInputs: number;
|
|
335
|
+
activeTurns: number;
|
|
336
|
+
lastError: string | null;
|
|
337
|
+
updatedAt: string;
|
|
338
|
+
/** Which messaging transports are expected active in this process. */
|
|
339
|
+
channels?: {
|
|
340
|
+
telegram: boolean;
|
|
341
|
+
weixin: boolean;
|
|
342
|
+
};
|
|
343
|
+
}
|
package/dist/types.js
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export {};
|