@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,568 @@
|
|
|
1
|
+
import path from 'node:path';
|
|
2
|
+
import { t } from '../i18n.js';
|
|
3
|
+
import { resolveFastTierForModel } from './service_tier.js';
|
|
4
|
+
export function formatThreadsMessage(locale, threads, currentThreadId, searchTerm, listState) {
|
|
5
|
+
if (threads.length === 0) {
|
|
6
|
+
return searchTerm
|
|
7
|
+
? t(locale, 'threads_no_matches', { searchTerm: escapeTelegramHtml(searchTerm) })
|
|
8
|
+
: t(locale, 'threads_no_recent');
|
|
9
|
+
}
|
|
10
|
+
const currentThread = currentThreadId
|
|
11
|
+
? threads.find(thread => thread.threadId === currentThreadId) ?? null
|
|
12
|
+
: null;
|
|
13
|
+
const headerLines = [
|
|
14
|
+
t(locale, listState?.archived ? 'threads_archived_title' : 'threads_recent_title'),
|
|
15
|
+
t(locale, 'threads_tap_to_open'),
|
|
16
|
+
];
|
|
17
|
+
if (searchTerm) {
|
|
18
|
+
headerLines.push(t(locale, 'threads_filter', { searchTerm: escapeTelegramHtml(searchTerm) }));
|
|
19
|
+
}
|
|
20
|
+
if (listState && threads.length > 0) {
|
|
21
|
+
headerLines.push(t(locale, 'threads_range', {
|
|
22
|
+
start: listState.offset + 1,
|
|
23
|
+
end: listState.offset + threads.length,
|
|
24
|
+
}));
|
|
25
|
+
}
|
|
26
|
+
if (currentThread) {
|
|
27
|
+
const currentTitle = truncate(compactWhitespace(currentThread.name || currentThread.preview || t(locale, 'empty')), 40);
|
|
28
|
+
headerLines.push(t(locale, 'threads_current', { title: escapeTelegramHtml(currentTitle) }));
|
|
29
|
+
headerLines.push(escapeTelegramHtml([
|
|
30
|
+
formatCwd(locale, currentThread.cwd),
|
|
31
|
+
formatRelativeTime(locale, currentThread.updatedAt),
|
|
32
|
+
formatStatusLabel(locale, currentThread.status),
|
|
33
|
+
].filter(Boolean).join(' | ')));
|
|
34
|
+
}
|
|
35
|
+
return headerLines.join('\n');
|
|
36
|
+
}
|
|
37
|
+
export function buildThreadsKeyboard(locale, threads) {
|
|
38
|
+
return threads.flatMap((thread, index) => {
|
|
39
|
+
const ordinal = typeof thread.index === 'number' ? thread.index : index + 1;
|
|
40
|
+
const openRow = [{
|
|
41
|
+
text: `${ordinal}. ${truncate(compactWhitespace(thread.name || thread.preview || t(locale, 'empty')), 28)}`,
|
|
42
|
+
callback_data: `thread:open:${thread.threadId}`,
|
|
43
|
+
}];
|
|
44
|
+
const actionRow = thread.archived
|
|
45
|
+
? [{ text: t(locale, 'button_thread_unarchive'), callback_data: `thread:unarchive:${thread.threadId}` }]
|
|
46
|
+
: [
|
|
47
|
+
{ text: t(locale, 'button_thread_rename'), callback_data: `thread:rename:${thread.threadId}` },
|
|
48
|
+
{ text: t(locale, 'button_thread_watch'), callback_data: `thread:watch:${thread.threadId}` },
|
|
49
|
+
{ text: t(locale, 'button_thread_archive'), callback_data: `thread:archive:${thread.threadId}` },
|
|
50
|
+
];
|
|
51
|
+
return [openRow, actionRow];
|
|
52
|
+
});
|
|
53
|
+
}
|
|
54
|
+
/** Threads keyboard plus Prev/Next and optional clear-filter row (Telegram only). */
|
|
55
|
+
export function buildThreadListKeyboard(locale, threads, listState) {
|
|
56
|
+
const rows = buildThreadsKeyboard(locale, threads);
|
|
57
|
+
rows.push([{ text: t(locale, 'button_new_thread'), callback_data: 'thread:new' }]);
|
|
58
|
+
const navigationRow = [];
|
|
59
|
+
if (listState.hasPreviousPage) {
|
|
60
|
+
navigationRow.push({ text: t(locale, 'button_prev_page'), callback_data: 'thread:list:prev' });
|
|
61
|
+
}
|
|
62
|
+
if (listState.hasNextPage) {
|
|
63
|
+
navigationRow.push({ text: t(locale, 'button_next_page'), callback_data: 'thread:list:next' });
|
|
64
|
+
}
|
|
65
|
+
if (navigationRow.length > 0) {
|
|
66
|
+
rows.push(navigationRow);
|
|
67
|
+
}
|
|
68
|
+
if (listState.searchTerm?.trim()) {
|
|
69
|
+
rows.push([{ text: t(locale, 'button_clear_filter'), callback_data: 'thread:list:clear' }]);
|
|
70
|
+
}
|
|
71
|
+
rows.push([{
|
|
72
|
+
text: t(locale, listState.archived ? 'button_recent_threads' : 'button_archived_threads'),
|
|
73
|
+
callback_data: listState.archived ? 'thread:list:recent' : 'thread:list:archived',
|
|
74
|
+
}]);
|
|
75
|
+
return rows;
|
|
76
|
+
}
|
|
77
|
+
export function formatWhereMessage(locale, thread, settings, defaultCwd, access, fastLabel = t(locale, 'unknown')) {
|
|
78
|
+
return [
|
|
79
|
+
t(locale, 'where_thread', { value: thread.threadId }),
|
|
80
|
+
t(locale, 'where_title', { value: thread.name || t(locale, 'untitled') }),
|
|
81
|
+
t(locale, 'where_preview', { value: thread.preview || t(locale, 'empty') }),
|
|
82
|
+
t(locale, 'where_configured_model', { value: settings?.model ?? t(locale, 'server_default') }),
|
|
83
|
+
t(locale, 'where_configured_effort', { value: settings?.reasoningEffort ?? t(locale, 'server_default') }),
|
|
84
|
+
t(locale, 'where_fast', { value: fastLabel }),
|
|
85
|
+
t(locale, 'where_collaboration_mode', { value: formatCollaborationModeLabel(locale, settings?.collaborationMode ?? null) }),
|
|
86
|
+
t(locale, 'active_current', { value: formatActiveTurnMessageModeLabel(locale, settings?.activeTurnMessageMode ?? null) }),
|
|
87
|
+
t(locale, 'where_access_preset', { value: formatAccessPresetLabel(locale, access.preset) }),
|
|
88
|
+
t(locale, 'where_approval_policy', { value: formatApprovalPolicyLabel(locale, access.approvalPolicy) }),
|
|
89
|
+
t(locale, 'where_sandbox_mode', { value: formatSandboxModeLabel(locale, access.sandboxMode) }),
|
|
90
|
+
t(locale, 'where_provider', { value: thread.modelProvider ?? t(locale, 'unknown') }),
|
|
91
|
+
t(locale, 'where_status', { value: formatStatus(locale, thread.status) }),
|
|
92
|
+
t(locale, 'where_cwd', { value: thread.cwd ?? defaultCwd }),
|
|
93
|
+
t(locale, 'where_updated', { value: formatIsoTime(locale, thread.updatedAt) }),
|
|
94
|
+
].join('\n');
|
|
95
|
+
}
|
|
96
|
+
export function formatCollaborationModeLabel(locale, mode) {
|
|
97
|
+
return t(locale, mode === 'plan' ? 'collaboration_mode_plan' : 'collaboration_mode_default');
|
|
98
|
+
}
|
|
99
|
+
export function formatAccessSettingsMessage(locale, access) {
|
|
100
|
+
return [
|
|
101
|
+
t(locale, 'permissions_title'),
|
|
102
|
+
t(locale, 'permissions_tap_to_change'),
|
|
103
|
+
'',
|
|
104
|
+
t(locale, 'permissions_preset', { value: escapeTelegramHtml(formatAccessPresetLabel(locale, access.preset)) }),
|
|
105
|
+
t(locale, 'permissions_approval_policy', { value: escapeTelegramHtml(formatApprovalPolicyLabel(locale, access.approvalPolicy)) }),
|
|
106
|
+
t(locale, 'permissions_sandbox_mode', { value: escapeTelegramHtml(formatSandboxModeLabel(locale, access.sandboxMode)) }),
|
|
107
|
+
].join('\n');
|
|
108
|
+
}
|
|
109
|
+
export function buildAccessSettingsKeyboard(locale, access) {
|
|
110
|
+
const currentPreset = access.preset;
|
|
111
|
+
const buttons = [
|
|
112
|
+
{
|
|
113
|
+
text: `${currentPreset === 'read-only' ? '• ' : ''}${t(locale, 'access_preset_read_only')}`,
|
|
114
|
+
callback_data: 'settings:access:read-only',
|
|
115
|
+
},
|
|
116
|
+
{
|
|
117
|
+
text: `${currentPreset === 'default' ? '• ' : ''}${t(locale, 'access_preset_default')}`,
|
|
118
|
+
callback_data: 'settings:access:default',
|
|
119
|
+
},
|
|
120
|
+
{
|
|
121
|
+
text: `${currentPreset === 'full-access' ? '• ' : ''}${t(locale, 'access_preset_full_access')}`,
|
|
122
|
+
callback_data: 'settings:access:full-access',
|
|
123
|
+
},
|
|
124
|
+
];
|
|
125
|
+
return [buttons];
|
|
126
|
+
}
|
|
127
|
+
export function formatWeixinThreadsCopyPaste(locale, threads, searchTerm,
|
|
128
|
+
/** 0-based offset so /open numbers align with cached global indices when paginating. */
|
|
129
|
+
pageOffset = 0) {
|
|
130
|
+
const lines = [
|
|
131
|
+
t(locale, 'weixin_copy_paste_divider'),
|
|
132
|
+
t(locale, 'weixin_copy_threads_title'),
|
|
133
|
+
];
|
|
134
|
+
if (searchTerm?.trim()) {
|
|
135
|
+
lines.push(t(locale, 'weixin_copy_threads_filter', { term: searchTerm.trim() }));
|
|
136
|
+
}
|
|
137
|
+
if (threads.length === 0) {
|
|
138
|
+
lines.push(t(locale, 'weixin_copy_threads_empty'));
|
|
139
|
+
}
|
|
140
|
+
else {
|
|
141
|
+
for (let i = 0; i < threads.length; i += 1) {
|
|
142
|
+
const index = pageOffset + i + 1;
|
|
143
|
+
const thread = threads[i];
|
|
144
|
+
const title = truncate(compactWhitespace(thread.name || thread.preview || t(locale, 'empty')), 40);
|
|
145
|
+
lines.push(`${index}. ${title}`);
|
|
146
|
+
if (thread.archived) {
|
|
147
|
+
lines.push(`/thread_unarchive ${index}`);
|
|
148
|
+
}
|
|
149
|
+
else {
|
|
150
|
+
lines.push(`/open ${index}`);
|
|
151
|
+
lines.push(`/watch ${index}`);
|
|
152
|
+
lines.push(`/thread_rename ${index} <name>`);
|
|
153
|
+
lines.push(`/thread_archive ${index}`);
|
|
154
|
+
}
|
|
155
|
+
}
|
|
156
|
+
}
|
|
157
|
+
lines.push('/new <PWD>', '/threads archived [query]', '/threads [query]', t(locale, 'weixin_copy_threads_filter_hint'));
|
|
158
|
+
return lines.join('\n');
|
|
159
|
+
}
|
|
160
|
+
export function formatWeixinModelCopyPaste(locale, models, settings) {
|
|
161
|
+
const effective = resolveCurrentModel(models, settings?.model ?? null);
|
|
162
|
+
const efforts = effective?.supportedReasoningEfforts.length
|
|
163
|
+
? effective.supportedReasoningEfforts
|
|
164
|
+
: effective
|
|
165
|
+
? [effective.defaultReasoningEffort]
|
|
166
|
+
: ['medium'];
|
|
167
|
+
const lines = [
|
|
168
|
+
t(locale, 'weixin_copy_paste_divider'),
|
|
169
|
+
t(locale, 'weixin_copy_models_title'),
|
|
170
|
+
'/model default',
|
|
171
|
+
...models.map((m) => `/model ${m.model}`),
|
|
172
|
+
'',
|
|
173
|
+
t(locale, 'weixin_copy_efforts_title'),
|
|
174
|
+
'/effort default',
|
|
175
|
+
...efforts.map((e) => `/effort ${e}`),
|
|
176
|
+
'',
|
|
177
|
+
'/fast on',
|
|
178
|
+
'/fast off',
|
|
179
|
+
];
|
|
180
|
+
return lines.join('\n');
|
|
181
|
+
}
|
|
182
|
+
export function formatWeixinAccessCopyPaste(locale) {
|
|
183
|
+
return [
|
|
184
|
+
t(locale, 'weixin_copy_paste_divider'),
|
|
185
|
+
t(locale, 'weixin_copy_access_title'),
|
|
186
|
+
'/access read-only',
|
|
187
|
+
'/access default',
|
|
188
|
+
'/access full-access',
|
|
189
|
+
].join('\n');
|
|
190
|
+
}
|
|
191
|
+
export function formatWeixinWhereNavCopyPaste(locale, hasBinding, defaultCwd) {
|
|
192
|
+
const lines = [
|
|
193
|
+
t(locale, 'weixin_copy_paste_divider'),
|
|
194
|
+
t(locale, 'weixin_copy_where_nav_title'),
|
|
195
|
+
'/status',
|
|
196
|
+
'/setup',
|
|
197
|
+
'/where',
|
|
198
|
+
'/threads',
|
|
199
|
+
'/auth',
|
|
200
|
+
'/mode default',
|
|
201
|
+
'/mode plan',
|
|
202
|
+
'/active steer',
|
|
203
|
+
'/active queue',
|
|
204
|
+
`/new ${defaultCwd || '<PWD>'}`,
|
|
205
|
+
'/interrupt',
|
|
206
|
+
'/goal',
|
|
207
|
+
'/history',
|
|
208
|
+
'/files <query>',
|
|
209
|
+
'/permissions',
|
|
210
|
+
'/models',
|
|
211
|
+
'/threads',
|
|
212
|
+
];
|
|
213
|
+
if (hasBinding) {
|
|
214
|
+
lines.push('/reveal');
|
|
215
|
+
}
|
|
216
|
+
return lines.join('\n');
|
|
217
|
+
}
|
|
218
|
+
export function formatSetupPanelMessage(locale, ctx) {
|
|
219
|
+
const currentModel = resolveCurrentModel(ctx.models, ctx.settings?.model ?? null);
|
|
220
|
+
const fastTier = resolveFastTierForModel(currentModel);
|
|
221
|
+
const fastSupported = fastTier !== null;
|
|
222
|
+
const fastLabel = formatFastSetupLabel(locale, fastSupported, fastTier !== null && ctx.settings?.serviceTier === fastTier.id, fastTier?.name ?? null);
|
|
223
|
+
const mode = resolveCollaborationMode(ctx.settings?.collaborationMode ?? null);
|
|
224
|
+
const activeTurnMessageMode = resolveActiveTurnMessageMode(ctx.settings?.activeTurnMessageMode ?? null);
|
|
225
|
+
return [
|
|
226
|
+
t(locale, 'setup_title'),
|
|
227
|
+
t(locale, 'setup_summary', { value: escapeTelegramHtml(resolveSetupSummaryLine(ctx, locale)) }),
|
|
228
|
+
setupFocusLabel(locale, ctx.focus),
|
|
229
|
+
'',
|
|
230
|
+
t(locale, 'setup_row_model', { value: escapeTelegramHtml(ctx.settings?.model ?? t(locale, 'server_default')) }),
|
|
231
|
+
t(locale, 'setup_row_effort', { value: escapeTelegramHtml(ctx.settings?.reasoningEffort ?? t(locale, 'server_default')) }),
|
|
232
|
+
t(locale, 'setup_row_fast', { value: escapeTelegramHtml(fastLabel) }),
|
|
233
|
+
t(locale, 'setup_row_access', {
|
|
234
|
+
value: escapeTelegramHtml(`${formatAccessPresetLabel(locale, ctx.access.preset)} (${ctx.access.approvalPolicy} / ${ctx.access.sandboxMode})`),
|
|
235
|
+
}),
|
|
236
|
+
t(locale, 'setup_row_mode', { value: escapeTelegramHtml(formatCollaborationModeLabel(locale, mode)) }),
|
|
237
|
+
t(locale, 'setup_row_active', { value: escapeTelegramHtml(formatActiveTurnMessageModeLabel(locale, activeTurnMessageMode)) }),
|
|
238
|
+
].join('\n');
|
|
239
|
+
}
|
|
240
|
+
export function buildSetupPanelKeyboard(locale, ctx) {
|
|
241
|
+
const currentModel = ctx.settings?.model ?? null;
|
|
242
|
+
const effectiveModel = resolveCurrentModel(ctx.models, currentModel);
|
|
243
|
+
const efforts = effectiveModel?.supportedReasoningEfforts.length
|
|
244
|
+
? effectiveModel.supportedReasoningEfforts
|
|
245
|
+
: effectiveModel
|
|
246
|
+
? [effectiveModel.defaultReasoningEffort]
|
|
247
|
+
: ['medium'];
|
|
248
|
+
const fastTier = resolveFastTierForModel(effectiveModel);
|
|
249
|
+
const serviceTier = ctx.settings?.serviceTier ?? null;
|
|
250
|
+
const currentMode = resolveCollaborationMode(ctx.settings?.collaborationMode ?? null);
|
|
251
|
+
const activeTurnMessageMode = resolveActiveTurnMessageMode(ctx.settings?.activeTurnMessageMode ?? null);
|
|
252
|
+
const rows = [];
|
|
253
|
+
rows.push(...chunkButtons([
|
|
254
|
+
{
|
|
255
|
+
text: currentModel === null ? `• ${t(locale, 'button_auto')}` : t(locale, 'button_auto'),
|
|
256
|
+
callback_data: 'setup:model:default',
|
|
257
|
+
},
|
|
258
|
+
...ctx.models.map((model) => ({
|
|
259
|
+
text: `${currentModel === model.model ? '• ' : ''}${truncate(model.model, 14)}`,
|
|
260
|
+
callback_data: `setup:model:${encodeURIComponent(model.model)}`,
|
|
261
|
+
})),
|
|
262
|
+
], 2));
|
|
263
|
+
rows.push(...chunkButtons([
|
|
264
|
+
{
|
|
265
|
+
text: (ctx.settings?.reasoningEffort ?? null) === null ? `• ${t(locale, 'button_auto')}` : t(locale, 'button_auto'),
|
|
266
|
+
callback_data: 'setup:effort:default',
|
|
267
|
+
},
|
|
268
|
+
...efforts.map((effort) => ({
|
|
269
|
+
text: `${ctx.settings?.reasoningEffort === effort ? '• ' : ''}${effort}`,
|
|
270
|
+
callback_data: `setup:effort:${effort}`,
|
|
271
|
+
})),
|
|
272
|
+
], 3));
|
|
273
|
+
if (fastTier) {
|
|
274
|
+
rows.push([
|
|
275
|
+
{
|
|
276
|
+
text: `${serviceTier === fastTier.id ? '• ' : ''}${t(locale, 'button_fast_on')}`,
|
|
277
|
+
callback_data: 'setup:fast:on',
|
|
278
|
+
},
|
|
279
|
+
{
|
|
280
|
+
text: `${serviceTier === null ? '• ' : ''}${t(locale, 'button_fast_off')}`,
|
|
281
|
+
callback_data: 'setup:fast:off',
|
|
282
|
+
},
|
|
283
|
+
]);
|
|
284
|
+
}
|
|
285
|
+
else {
|
|
286
|
+
rows.push([{ text: t(locale, 'button_fast_unsupported'), callback_data: 'setup:fast:unsupported' }]);
|
|
287
|
+
}
|
|
288
|
+
rows.push([
|
|
289
|
+
{
|
|
290
|
+
text: `${ctx.access.preset === 'read-only' ? '• ' : ''}${t(locale, 'access_preset_read_only')}`,
|
|
291
|
+
callback_data: 'setup:access:read-only',
|
|
292
|
+
},
|
|
293
|
+
{
|
|
294
|
+
text: `${ctx.access.preset === 'default' ? '• ' : ''}${t(locale, 'access_preset_default')}`,
|
|
295
|
+
callback_data: 'setup:access:default',
|
|
296
|
+
},
|
|
297
|
+
{
|
|
298
|
+
text: `${ctx.access.preset === 'full-access' ? '• ' : ''}${t(locale, 'access_preset_full_access')}`,
|
|
299
|
+
callback_data: 'setup:access:full-access',
|
|
300
|
+
},
|
|
301
|
+
]);
|
|
302
|
+
rows.push([
|
|
303
|
+
{
|
|
304
|
+
text: `${currentMode === 'default' ? '• ' : ''}${t(locale, 'collaboration_mode_default')}`,
|
|
305
|
+
callback_data: 'setup:mode:default',
|
|
306
|
+
},
|
|
307
|
+
{
|
|
308
|
+
text: `${currentMode === 'plan' ? '• ' : ''}${t(locale, 'collaboration_mode_plan')}`,
|
|
309
|
+
callback_data: 'setup:mode:plan',
|
|
310
|
+
},
|
|
311
|
+
]);
|
|
312
|
+
rows.push([
|
|
313
|
+
{
|
|
314
|
+
text: `${activeTurnMessageMode === 'steer' ? '• ' : ''}${t(locale, 'active_turn_message_mode_steer')}`,
|
|
315
|
+
callback_data: 'setup:active:steer',
|
|
316
|
+
},
|
|
317
|
+
{
|
|
318
|
+
text: `${activeTurnMessageMode === 'queue' ? '• ' : ''}${t(locale, 'active_turn_message_mode_queue')}`,
|
|
319
|
+
callback_data: 'setup:active:queue',
|
|
320
|
+
},
|
|
321
|
+
]);
|
|
322
|
+
return rows;
|
|
323
|
+
}
|
|
324
|
+
export function resolveSetupSummaryLine(ctx, locale = 'en') {
|
|
325
|
+
const currentModel = ctx.settings?.model ?? t(locale, 'server_default');
|
|
326
|
+
const currentEffort = ctx.settings?.reasoningEffort ?? t(locale, 'server_default');
|
|
327
|
+
const model = resolveCurrentModel(ctx.models, ctx.settings?.model ?? null);
|
|
328
|
+
const fastTier = resolveFastTierForModel(model);
|
|
329
|
+
const fast = fastTier ? (ctx.settings?.serviceTier === fastTier.id ? 'fast=on' : 'fast=off') : 'fast=unsupported';
|
|
330
|
+
return [
|
|
331
|
+
currentModel,
|
|
332
|
+
currentEffort,
|
|
333
|
+
fast,
|
|
334
|
+
ctx.access.preset,
|
|
335
|
+
resolveCollaborationMode(ctx.settings?.collaborationMode ?? null),
|
|
336
|
+
resolveActiveTurnMessageMode(ctx.settings?.activeTurnMessageMode ?? null),
|
|
337
|
+
].join(' · ');
|
|
338
|
+
}
|
|
339
|
+
export function formatServiceTierStatusLabel(locale, model, serviceTier) {
|
|
340
|
+
const fastTier = resolveFastTierForModel(model);
|
|
341
|
+
if (!fastTier) {
|
|
342
|
+
return t(locale, 'fast_unsupported');
|
|
343
|
+
}
|
|
344
|
+
if (serviceTier === fastTier.id) {
|
|
345
|
+
return t(locale, 'fast_enabled', { tier: fastTier.name || fastTier.id });
|
|
346
|
+
}
|
|
347
|
+
return t(locale, 'fast_disabled');
|
|
348
|
+
}
|
|
349
|
+
export function formatActiveTurnMessageModeLabel(locale, mode) {
|
|
350
|
+
return t(locale, resolveActiveTurnMessageMode(mode) === 'queue' ? 'active_turn_message_mode_queue' : 'active_turn_message_mode_steer');
|
|
351
|
+
}
|
|
352
|
+
function setupFocusLabel(locale, focus) {
|
|
353
|
+
switch (focus) {
|
|
354
|
+
case 'model':
|
|
355
|
+
return t(locale, 'setup_focus_model');
|
|
356
|
+
case 'effort':
|
|
357
|
+
return t(locale, 'setup_focus_effort');
|
|
358
|
+
case 'fast':
|
|
359
|
+
return t(locale, 'setup_focus_fast');
|
|
360
|
+
case 'access':
|
|
361
|
+
return t(locale, 'setup_focus_access');
|
|
362
|
+
case 'mode':
|
|
363
|
+
return t(locale, 'setup_focus_mode');
|
|
364
|
+
case 'active':
|
|
365
|
+
return t(locale, 'setup_focus_active');
|
|
366
|
+
default:
|
|
367
|
+
return t(locale, 'setup_focus_overview');
|
|
368
|
+
}
|
|
369
|
+
}
|
|
370
|
+
export function resolveActiveTurnMessageMode(mode) {
|
|
371
|
+
return mode === 'queue' ? 'queue' : 'steer';
|
|
372
|
+
}
|
|
373
|
+
function formatFastSetupLabel(locale, supported, enabled, tierName) {
|
|
374
|
+
if (!supported) {
|
|
375
|
+
return t(locale, 'fast_unsupported');
|
|
376
|
+
}
|
|
377
|
+
if (enabled) {
|
|
378
|
+
return t(locale, 'fast_enabled', { tier: tierName ?? 'fast' });
|
|
379
|
+
}
|
|
380
|
+
return t(locale, 'fast_disabled');
|
|
381
|
+
}
|
|
382
|
+
function resolveCollaborationMode(mode) {
|
|
383
|
+
return mode === 'plan' ? 'plan' : 'default';
|
|
384
|
+
}
|
|
385
|
+
export function formatModelSettingsMessage(locale, models, settings) {
|
|
386
|
+
const selectedModel = resolveCurrentModel(models, settings?.model ?? null);
|
|
387
|
+
const selectedModelLabel = settings?.model ?? t(locale, 'server_default');
|
|
388
|
+
const selectedEffort = settings?.reasoningEffort ?? null;
|
|
389
|
+
const supportedEfforts = selectedModel?.supportedReasoningEfforts.length
|
|
390
|
+
? selectedModel.supportedReasoningEfforts
|
|
391
|
+
: selectedModel
|
|
392
|
+
? [selectedModel.defaultReasoningEffort]
|
|
393
|
+
: [];
|
|
394
|
+
return [
|
|
395
|
+
t(locale, 'models_title'),
|
|
396
|
+
t(locale, 'models_tap_to_change'),
|
|
397
|
+
'',
|
|
398
|
+
t(locale, 'models_model', { value: escapeTelegramHtml(selectedModelLabel) }),
|
|
399
|
+
t(locale, 'models_effort', { value: escapeTelegramHtml(selectedEffort ?? t(locale, 'server_default')) }),
|
|
400
|
+
selectedModel ? t(locale, 'models_current_default_target', { value: escapeTelegramHtml(selectedModel.model) }) : null,
|
|
401
|
+
supportedEfforts.length > 0
|
|
402
|
+
? t(locale, 'models_supported_efforts', { value: escapeTelegramHtml(supportedEfforts.join(', ')) })
|
|
403
|
+
: t(locale, 'models_supported_efforts_unknown'),
|
|
404
|
+
].filter(Boolean).join('\n');
|
|
405
|
+
}
|
|
406
|
+
export function buildModelSettingsKeyboard(locale, models, settings) {
|
|
407
|
+
const currentModel = settings?.model ?? null;
|
|
408
|
+
const effectiveModel = resolveCurrentModel(models, currentModel);
|
|
409
|
+
const efforts = effectiveModel?.supportedReasoningEfforts.length
|
|
410
|
+
? effectiveModel.supportedReasoningEfforts
|
|
411
|
+
: effectiveModel
|
|
412
|
+
? [effectiveModel.defaultReasoningEffort]
|
|
413
|
+
: ['medium'];
|
|
414
|
+
const modelButtons = [
|
|
415
|
+
{
|
|
416
|
+
text: currentModel === null ? `• ${t(locale, 'button_auto')}` : t(locale, 'button_auto'),
|
|
417
|
+
callback_data: 'settings:model:default',
|
|
418
|
+
},
|
|
419
|
+
...models.map((model) => ({
|
|
420
|
+
text: `${currentModel === model.model ? '• ' : ''}${truncate(model.model, 14)}`,
|
|
421
|
+
callback_data: `settings:model:${encodeURIComponent(model.model)}`,
|
|
422
|
+
})),
|
|
423
|
+
];
|
|
424
|
+
const effortButtons = [
|
|
425
|
+
{
|
|
426
|
+
text: settings?.reasoningEffort === null ? `• ${t(locale, 'button_auto')}` : t(locale, 'button_auto'),
|
|
427
|
+
callback_data: 'settings:effort:default',
|
|
428
|
+
},
|
|
429
|
+
...efforts.map((effort) => ({
|
|
430
|
+
text: `${settings?.reasoningEffort === effort ? '• ' : ''}${effort}`,
|
|
431
|
+
callback_data: `settings:effort:${effort}`,
|
|
432
|
+
})),
|
|
433
|
+
];
|
|
434
|
+
return [
|
|
435
|
+
...chunkButtons(modelButtons, 2),
|
|
436
|
+
...chunkButtons(effortButtons, 3),
|
|
437
|
+
];
|
|
438
|
+
}
|
|
439
|
+
export function resolveRequestedModel(models, requested) {
|
|
440
|
+
const normalized = requested.trim().toLowerCase();
|
|
441
|
+
return models.find(model => (model.model.toLowerCase() === normalized
|
|
442
|
+
|| model.id.toLowerCase() === normalized
|
|
443
|
+
|| model.displayName.toLowerCase() === normalized)) ?? null;
|
|
444
|
+
}
|
|
445
|
+
export function resolveCurrentModel(models, currentModel) {
|
|
446
|
+
if (currentModel) {
|
|
447
|
+
const current = resolveRequestedModel(models, currentModel);
|
|
448
|
+
if (current)
|
|
449
|
+
return current;
|
|
450
|
+
}
|
|
451
|
+
return models.find(model => model.isDefault) ?? null;
|
|
452
|
+
}
|
|
453
|
+
export function normalizeRequestedEffort(value) {
|
|
454
|
+
const normalized = value.trim().toLowerCase();
|
|
455
|
+
if (normalized === 'none'
|
|
456
|
+
|| normalized === 'minimal'
|
|
457
|
+
|| normalized === 'low'
|
|
458
|
+
|| normalized === 'medium'
|
|
459
|
+
|| normalized === 'high'
|
|
460
|
+
|| normalized === 'xhigh') {
|
|
461
|
+
return normalized;
|
|
462
|
+
}
|
|
463
|
+
return null;
|
|
464
|
+
}
|
|
465
|
+
export function clampEffortToModel(model, effort) {
|
|
466
|
+
if (!model || !effort) {
|
|
467
|
+
return { effort, adjustedFrom: null };
|
|
468
|
+
}
|
|
469
|
+
if (model.supportedReasoningEfforts.includes(effort)) {
|
|
470
|
+
return { effort, adjustedFrom: null };
|
|
471
|
+
}
|
|
472
|
+
return { effort: model.defaultReasoningEffort, adjustedFrom: effort };
|
|
473
|
+
}
|
|
474
|
+
export function formatAccessPresetLabel(locale, preset) {
|
|
475
|
+
if (preset === 'read-only')
|
|
476
|
+
return t(locale, 'access_preset_read_only');
|
|
477
|
+
if (preset === 'full-access')
|
|
478
|
+
return t(locale, 'access_preset_full_access');
|
|
479
|
+
return t(locale, 'access_preset_default');
|
|
480
|
+
}
|
|
481
|
+
export function formatApprovalPolicyLabel(locale, policy) {
|
|
482
|
+
if (policy === 'never')
|
|
483
|
+
return t(locale, 'approval_policy_never');
|
|
484
|
+
if (policy === 'untrusted')
|
|
485
|
+
return t(locale, 'approval_policy_untrusted');
|
|
486
|
+
if (policy === 'on-failure')
|
|
487
|
+
return t(locale, 'approval_policy_on_failure');
|
|
488
|
+
return t(locale, 'approval_policy_on_request');
|
|
489
|
+
}
|
|
490
|
+
export function formatSandboxModeLabel(locale, mode) {
|
|
491
|
+
if (mode === 'danger-full-access')
|
|
492
|
+
return t(locale, 'sandbox_mode_danger_full_access');
|
|
493
|
+
if (mode === 'read-only')
|
|
494
|
+
return t(locale, 'sandbox_mode_read_only');
|
|
495
|
+
return t(locale, 'sandbox_mode_workspace_write');
|
|
496
|
+
}
|
|
497
|
+
function formatStatus(locale, status) {
|
|
498
|
+
switch (status) {
|
|
499
|
+
case 'active':
|
|
500
|
+
return t(locale, 'status_active');
|
|
501
|
+
case 'notLoaded':
|
|
502
|
+
return t(locale, 'status_not_loaded');
|
|
503
|
+
case 'systemError':
|
|
504
|
+
return t(locale, 'status_error');
|
|
505
|
+
default:
|
|
506
|
+
return t(locale, 'status_idle');
|
|
507
|
+
}
|
|
508
|
+
}
|
|
509
|
+
function formatStatusLabel(locale, status) {
|
|
510
|
+
if (status === 'active')
|
|
511
|
+
return t(locale, 'status_active');
|
|
512
|
+
if (status === 'systemError')
|
|
513
|
+
return t(locale, 'status_error');
|
|
514
|
+
return '';
|
|
515
|
+
}
|
|
516
|
+
function formatCwd(locale, cwd) {
|
|
517
|
+
if (!cwd)
|
|
518
|
+
return t(locale, 'no_cwd');
|
|
519
|
+
const base = path.basename(cwd);
|
|
520
|
+
return base || cwd;
|
|
521
|
+
}
|
|
522
|
+
function compactWhitespace(value) {
|
|
523
|
+
return value.replace(/\s+/g, ' ').trim();
|
|
524
|
+
}
|
|
525
|
+
function truncate(value, maxLength) {
|
|
526
|
+
if (value.length <= maxLength)
|
|
527
|
+
return value;
|
|
528
|
+
return `${value.slice(0, maxLength - 3)}...`;
|
|
529
|
+
}
|
|
530
|
+
function formatRelativeTime(locale, unixSeconds) {
|
|
531
|
+
if (!Number.isFinite(unixSeconds) || unixSeconds <= 0)
|
|
532
|
+
return t(locale, 'unknown');
|
|
533
|
+
const deltaSeconds = Math.max(0, Math.floor(Date.now() / 1000) - Math.floor(unixSeconds));
|
|
534
|
+
if (locale === 'zh') {
|
|
535
|
+
if (deltaSeconds < 60)
|
|
536
|
+
return `${deltaSeconds}秒前`;
|
|
537
|
+
if (deltaSeconds < 3600)
|
|
538
|
+
return `${Math.floor(deltaSeconds / 60)}分钟前`;
|
|
539
|
+
if (deltaSeconds < 86_400)
|
|
540
|
+
return `${Math.floor(deltaSeconds / 3600)}小时前`;
|
|
541
|
+
return `${Math.floor(deltaSeconds / 86_400)}天前`;
|
|
542
|
+
}
|
|
543
|
+
if (deltaSeconds < 60)
|
|
544
|
+
return `${deltaSeconds}s ago`;
|
|
545
|
+
if (deltaSeconds < 3600)
|
|
546
|
+
return `${Math.floor(deltaSeconds / 60)}m ago`;
|
|
547
|
+
if (deltaSeconds < 86_400)
|
|
548
|
+
return `${Math.floor(deltaSeconds / 3600)}h ago`;
|
|
549
|
+
return `${Math.floor(deltaSeconds / 86_400)}d ago`;
|
|
550
|
+
}
|
|
551
|
+
function formatIsoTime(locale, unixSeconds) {
|
|
552
|
+
if (!Number.isFinite(unixSeconds) || unixSeconds <= 0)
|
|
553
|
+
return t(locale, 'unknown');
|
|
554
|
+
return new Date(unixSeconds * 1000).toISOString();
|
|
555
|
+
}
|
|
556
|
+
function escapeTelegramHtml(value) {
|
|
557
|
+
return value
|
|
558
|
+
.replaceAll('&', '&')
|
|
559
|
+
.replaceAll('<', '<')
|
|
560
|
+
.replaceAll('>', '>');
|
|
561
|
+
}
|
|
562
|
+
function chunkButtons(buttons, width) {
|
|
563
|
+
const rows = [];
|
|
564
|
+
for (let index = 0; index < buttons.length; index += width) {
|
|
565
|
+
rows.push(buttons.slice(index, index + width));
|
|
566
|
+
}
|
|
567
|
+
return rows;
|
|
568
|
+
}
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
import type { ModelInfo, ModelServiceTier } from '../types.js';
|
|
2
|
+
export declare const FAST_TIER_NAME = "fast";
|
|
3
|
+
export declare const FAST_TIER_ID = "priority";
|
|
4
|
+
export declare function resolveFastTierForModel(model: ModelInfo | null): ModelServiceTier | null;
|
|
5
|
+
export declare function isServiceTierSupportedByModel(model: ModelInfo | null, tierId: string | null | undefined): boolean;
|
|
6
|
+
export declare function clampServiceTierToModel(model: ModelInfo | null, currentTierId: string | null): {
|
|
7
|
+
tier: string | null;
|
|
8
|
+
adjusted: boolean;
|
|
9
|
+
};
|
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
export const FAST_TIER_NAME = 'fast';
|
|
2
|
+
export const FAST_TIER_ID = 'priority';
|
|
3
|
+
const DEFAULT_SERVICE_TIER_IDS = new Set(['auto', 'default', 'standard']);
|
|
4
|
+
export function resolveFastTierForModel(model) {
|
|
5
|
+
if (!model || model.serviceTiers.length === 0) {
|
|
6
|
+
return null;
|
|
7
|
+
}
|
|
8
|
+
const byName = model.serviceTiers.find(tier => tier.name.trim().toLowerCase() === FAST_TIER_NAME);
|
|
9
|
+
if (byName) {
|
|
10
|
+
return byName;
|
|
11
|
+
}
|
|
12
|
+
const byId = model.serviceTiers.find(tier => tier.id.trim().toLowerCase() === FAST_TIER_ID);
|
|
13
|
+
if (byId) {
|
|
14
|
+
return byId;
|
|
15
|
+
}
|
|
16
|
+
return model.serviceTiers.find(tier => !DEFAULT_SERVICE_TIER_IDS.has(tier.id.trim().toLowerCase())) ?? null;
|
|
17
|
+
}
|
|
18
|
+
export function isServiceTierSupportedByModel(model, tierId) {
|
|
19
|
+
if (!model || !tierId) {
|
|
20
|
+
return false;
|
|
21
|
+
}
|
|
22
|
+
return model.serviceTiers.some(tier => tier.id === tierId);
|
|
23
|
+
}
|
|
24
|
+
export function clampServiceTierToModel(model, currentTierId) {
|
|
25
|
+
if (!currentTierId) {
|
|
26
|
+
return { tier: null, adjusted: false };
|
|
27
|
+
}
|
|
28
|
+
if (isServiceTierSupportedByModel(model, currentTierId)) {
|
|
29
|
+
return { tier: currentTierId, adjusted: false };
|
|
30
|
+
}
|
|
31
|
+
return { tier: null, adjusted: true };
|
|
32
|
+
}
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
import { type TurnActivityEvent } from './activity.js';
|
|
2
|
+
export interface SessionLogCursor {
|
|
3
|
+
activeTurnId: string | null;
|
|
4
|
+
nextMessageIndex: number;
|
|
5
|
+
}
|
|
6
|
+
export interface SessionLogBootstrap {
|
|
7
|
+
cursor: SessionLogCursor;
|
|
8
|
+
events: TurnActivityEvent[];
|
|
9
|
+
startedTurnId: string | null;
|
|
10
|
+
}
|
|
11
|
+
export interface SessionLogDiff {
|
|
12
|
+
cursor: SessionLogCursor;
|
|
13
|
+
events: TurnActivityEvent[];
|
|
14
|
+
startedTurnIds: string[];
|
|
15
|
+
}
|
|
16
|
+
export interface SplitJsonlChunk {
|
|
17
|
+
lines: string[];
|
|
18
|
+
remainder: string;
|
|
19
|
+
}
|
|
20
|
+
export declare function splitJsonlChunk(remainder: string, chunk: string): SplitJsonlChunk;
|
|
21
|
+
export declare function bootstrapSessionLog(lines: string[]): SessionLogBootstrap;
|
|
22
|
+
export declare function applySessionLog(lines: string[], cursor: SessionLogCursor): SessionLogDiff;
|