@foxden-app/foxclaw 0.12.0 → 0.13.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +20 -0
- package/dist/antigravity/account_ui.d.ts +34 -0
- package/dist/antigravity/account_ui.js +354 -0
- package/dist/antigravity/adapter.js +1 -1
- package/dist/antigravity/auth.d.ts +4 -1
- package/dist/antigravity/auth.js +17 -5
- package/dist/antigravity/controller.d.ts +3 -108
- package/dist/antigravity/controller.js +2 -2129
- package/dist/antigravity/conversation_ui.d.ts +56 -0
- package/dist/antigravity/conversation_ui.js +509 -0
- package/dist/antigravity/ui_callbacks.d.ts +6 -0
- package/dist/antigravity/ui_callbacks.js +6 -0
- package/dist/bridge/unified_bridge.d.ts +114 -0
- package/dist/bridge/unified_bridge.js +1343 -0
- package/dist/channels/bridge_messaging_router.d.ts +8 -4
- package/dist/channels/bridge_messaging_router.js +15 -4
- package/dist/channels/telegram/telegram_messaging_port.d.ts +16 -0
- package/dist/channels/telegram/telegram_messaging_port.js +22 -2
- package/dist/channels/text_actions.d.ts +14 -0
- package/dist/channels/text_actions.js +48 -0
- package/dist/channels/weixin/weixin_channel_adapter.d.ts +3 -4
- package/dist/channels/weixin/weixin_channel_adapter.js +2 -2
- package/dist/channels/weixin/weixin_messaging_port.d.ts +16 -1
- package/dist/channels/weixin/weixin_messaging_port.js +34 -7
- package/dist/codex_app/activity.d.ts +62 -0
- package/dist/codex_app/activity.js +418 -0
- package/dist/codex_app/adapter.d.ts +2 -2
- package/dist/codex_app/adapter.js +55 -25
- package/dist/codex_app/backend_ui.d.ts +4 -0
- package/dist/codex_app/backend_ui.js +48 -0
- package/dist/codex_app/client.js +0 -12
- package/dist/controller/access.d.ts +1 -11
- package/dist/controller/access.js +1 -33
- package/dist/controller/activity.d.ts +1 -62
- package/dist/controller/activity.js +2 -418
- package/dist/controller/auth_errors.d.ts +11 -0
- package/dist/controller/auth_errors.js +124 -0
- package/dist/controller/auth_files.d.ts +13 -0
- package/dist/controller/auth_files.js +156 -0
- package/dist/controller/auth_presentation.d.ts +77 -0
- package/dist/controller/auth_presentation.js +697 -0
- package/dist/controller/auth_quota_service.d.ts +27 -0
- package/dist/controller/auth_quota_service.js +151 -0
- package/dist/controller/controller.d.ts +30 -56
- package/dist/controller/controller.js +181 -3497
- package/dist/controller/interactions.d.ts +74 -0
- package/dist/controller/interactions.js +560 -0
- package/dist/controller/local_usage_service.d.ts +19 -0
- package/dist/controller/local_usage_service.js +62 -0
- package/dist/controller/native_panel_service.d.ts +55 -0
- package/dist/controller/native_panel_service.js +214 -0
- package/dist/controller/native_panels.d.ts +61 -0
- package/dist/controller/native_panels.js +784 -0
- package/dist/controller/shared_helpers.d.ts +53 -0
- package/dist/controller/shared_helpers.js +399 -0
- package/dist/controller/state_types.d.ts +390 -0
- package/dist/controller/state_types.js +82 -0
- package/dist/controller/turn_rendering.d.ts +22 -0
- package/dist/controller/turn_rendering.js +288 -0
- package/dist/core/access.d.ts +11 -0
- package/dist/core/access.js +33 -0
- package/dist/core/attachment_files.d.ts +13 -0
- package/dist/core/attachment_files.js +180 -0
- package/dist/core/attachment_types.d.ts +22 -0
- package/dist/core/attachment_types.js +1 -0
- package/dist/core/attachments.d.ts +5 -3
- package/dist/core/attachments.js +2 -2
- package/dist/core/backend_registry.d.ts +22 -0
- package/dist/core/backend_registry.js +50 -0
- package/dist/core/backend_ui.d.ts +28 -0
- package/dist/core/backend_ui.js +1 -0
- package/dist/core/channel_events.d.ts +48 -0
- package/dist/core/channel_events.js +1 -0
- package/dist/core/channel_port.d.ts +52 -8
- package/dist/core/engine_spi.d.ts +19 -4
- package/dist/core/listener_scope.d.ts +11 -0
- package/dist/core/listener_scope.js +10 -0
- package/dist/core/message_text.d.ts +2 -0
- package/dist/core/message_text.js +28 -0
- package/dist/core/network_errors.d.ts +1 -0
- package/dist/core/network_errors.js +23 -0
- package/dist/core/orchestrator.d.ts +53 -24
- package/dist/core/orchestrator.js +855 -507
- package/dist/core/queued_engine_input.d.ts +12 -0
- package/dist/core/queued_engine_input.js +16 -0
- package/dist/core/runtime_supervisor.d.ts +19 -0
- package/dist/core/runtime_supervisor.js +45 -0
- package/dist/core/scope_operations.d.ts +7 -0
- package/dist/core/scope_operations.js +19 -0
- package/dist/core/stream_preview.js +21 -9
- package/dist/dsh/adapter.js +18 -5
- package/dist/dsh/backend.d.ts +7 -0
- package/dist/dsh/backend.js +21 -0
- package/dist/dsh/runtime.d.ts +0 -1
- package/dist/dsh/runtime.js +4 -13
- package/dist/dsh/ui.d.ts +8 -8
- package/dist/dsh/ui.js +1 -1
- package/dist/main.js +55 -43
- package/dist/opencode/adapter.js +28 -9
- package/dist/opencode/controller.js +1 -1
- package/dist/store/channel_inbox.d.ts +14 -0
- package/dist/store/channel_inbox.js +21 -0
- package/dist/store/database.d.ts +7 -3
- package/dist/store/database.js +7 -2
- package/dist/store/task_journal.d.ts +32 -0
- package/dist/store/task_journal.js +70 -0
- package/dist/telegram/api.d.ts +2 -2
- package/dist/telegram/api.js +10 -28
- package/dist/telegram/gateway.d.ts +13 -27
- package/dist/telegram/gateway.js +86 -43
- package/dist/telegram/media.d.ts +2 -34
- package/dist/telegram/media.js +1 -180
- package/dist/telegram/task_preview.d.ts +13 -0
- package/dist/telegram/task_preview.js +92 -0
- package/dist/telegram/task_result.d.ts +2 -0
- package/dist/telegram/task_result.js +12 -0
- package/dist/telegram/text.d.ts +1 -1
- package/dist/telegram/text.js +2 -27
- package/dist/types.d.ts +1 -1
- package/docs/release.md +13 -11
- package/docs/zh/architecture-review.md +106 -0
- package/docs/zh/release.md +13 -11
- package/docs/zh/telegram-experience.md +40 -0
- package/package.json +5 -4
|
@@ -0,0 +1,784 @@
|
|
|
1
|
+
import { t } from '../i18n.js';
|
|
2
|
+
import { escapeTelegramHtml, telegramBold, telegramDetails, telegramExpandableBlockquote, telegramPreCode } from '../telegram/html.js';
|
|
3
|
+
import { TELEGRAM_RICH_MESSAGE_TEXT_LIMIT } from '../telegram/rich.js';
|
|
4
|
+
import fs from 'node:fs/promises';
|
|
5
|
+
import path from 'node:path';
|
|
6
|
+
import { truncateInline } from './turn_rendering.js';
|
|
7
|
+
import { CODEX_AUTH_STALE_CREDENTIAL_DAYS, AUTH_DELETE_REASON_NEEDS_REPAIR, } from './state_types.js';
|
|
8
|
+
import { formatConfigValue, formatTokenCount, formatCompactNumber, formatLocalTimestamp, summarizeTurnItems } from './shared_helpers.js';
|
|
9
|
+
export function findSkill(entries, name) {
|
|
10
|
+
const normalized = name.trim().toLowerCase();
|
|
11
|
+
for (const entry of entries) {
|
|
12
|
+
const skill = entry.skills.find(candidate => candidate.name.toLowerCase() === normalized
|
|
13
|
+
|| candidate.displayName?.toLowerCase() === normalized);
|
|
14
|
+
if (skill)
|
|
15
|
+
return skill;
|
|
16
|
+
}
|
|
17
|
+
return null;
|
|
18
|
+
}
|
|
19
|
+
export function formatSkillsMessage(locale, entries, query, forceReload) {
|
|
20
|
+
const skills = entries.flatMap(entry => entry.skills.map(skill => ({ cwd: entry.cwd, skill })))
|
|
21
|
+
.filter(entry => !query
|
|
22
|
+
|| entry.skill.name.toLowerCase().includes(query.toLowerCase())
|
|
23
|
+
|| entry.skill.description.toLowerCase().includes(query.toLowerCase()));
|
|
24
|
+
const lines = [
|
|
25
|
+
t(locale, 'skills_title'),
|
|
26
|
+
forceReload ? t(locale, 'skills_reloaded') : null,
|
|
27
|
+
query ? t(locale, 'skills_filter', { value: query }) : null,
|
|
28
|
+
].filter((line) => Boolean(line));
|
|
29
|
+
if (skills.length === 0) {
|
|
30
|
+
lines.push(t(locale, 'skills_empty'));
|
|
31
|
+
}
|
|
32
|
+
else {
|
|
33
|
+
for (const { skill } of skills.slice(0, 30)) {
|
|
34
|
+
const enabled = skill.enabled ? 'on' : 'off';
|
|
35
|
+
const label = skill.displayName || skill.name;
|
|
36
|
+
lines.push(`${skill.enabled ? '*' : '-'} ${label} (${enabled})`);
|
|
37
|
+
const desc = skill.shortDescription || skill.description;
|
|
38
|
+
if (desc) {
|
|
39
|
+
lines.push(` ${truncateInline(desc, 120)}`);
|
|
40
|
+
}
|
|
41
|
+
}
|
|
42
|
+
if (skills.length > 30) {
|
|
43
|
+
lines.push(t(locale, 'list_truncated', { count: skills.length - 30 }));
|
|
44
|
+
}
|
|
45
|
+
}
|
|
46
|
+
const errors = entries.flatMap(entry => entry.errors);
|
|
47
|
+
if (errors.length > 0) {
|
|
48
|
+
lines.push('', t(locale, 'skills_errors'));
|
|
49
|
+
lines.push(...errors.slice(0, 5).map(error => `- ${truncateInline(error, 160)}`));
|
|
50
|
+
}
|
|
51
|
+
return lines.join('\n');
|
|
52
|
+
}
|
|
53
|
+
export function formatSkillDetailMessage(locale, skill) {
|
|
54
|
+
return [
|
|
55
|
+
t(locale, 'skill_title', { name: skill.displayName || skill.name }),
|
|
56
|
+
t(locale, 'skill_name', { value: skill.name }),
|
|
57
|
+
t(locale, 'skill_enabled_state', { value: skill.enabled ? t(locale, 'yes') : t(locale, 'no') }),
|
|
58
|
+
t(locale, 'skill_scope', { value: skill.scope || t(locale, 'unknown') }),
|
|
59
|
+
t(locale, 'skill_path', { value: skill.path || t(locale, 'unknown') }),
|
|
60
|
+
'',
|
|
61
|
+
skill.description || skill.shortDescription || t(locale, 'empty'),
|
|
62
|
+
skill.defaultPrompt ? `\n${t(locale, 'skill_default_prompt', { value: skill.defaultPrompt })}` : '',
|
|
63
|
+
].filter(Boolean).join('\n');
|
|
64
|
+
}
|
|
65
|
+
export function formatMcpStatusMessage(locale, statuses) {
|
|
66
|
+
const lines = [t(locale, 'mcp_title')];
|
|
67
|
+
if (statuses.length === 0) {
|
|
68
|
+
lines.push(t(locale, 'mcp_empty'));
|
|
69
|
+
return lines.join('\n');
|
|
70
|
+
}
|
|
71
|
+
for (const status of statuses) {
|
|
72
|
+
lines.push(`${status.name}: ${status.authStatus}`);
|
|
73
|
+
lines.push(` tools: ${status.toolNames.length ? status.toolNames.slice(0, 12).join(', ') : '-'}`);
|
|
74
|
+
if (status.resourceUris.length > 0) {
|
|
75
|
+
lines.push(` resources: ${status.resourceUris.slice(0, 5).join(', ')}`);
|
|
76
|
+
}
|
|
77
|
+
if (status.resourceTemplateUris.length > 0) {
|
|
78
|
+
lines.push(` templates: ${status.resourceTemplateUris.slice(0, 5).join(', ')}`);
|
|
79
|
+
}
|
|
80
|
+
}
|
|
81
|
+
return lines.join('\n');
|
|
82
|
+
}
|
|
83
|
+
export function formatMcpResourceMessage(locale, server, uri, contents) {
|
|
84
|
+
const lines = [t(locale, 'mcp_resource_title', { server, uri })];
|
|
85
|
+
if (contents.length === 0) {
|
|
86
|
+
lines.push(t(locale, 'mcp_resource_empty'));
|
|
87
|
+
return lines.join('\n');
|
|
88
|
+
}
|
|
89
|
+
for (const content of contents.slice(0, 5)) {
|
|
90
|
+
lines.push(`- ${content.type}${content.mimeType ? ` (${content.mimeType})` : ''}${content.uri ? ` ${content.uri}` : ''}`);
|
|
91
|
+
if (content.text) {
|
|
92
|
+
lines.push(truncateInline(content.text, 1500));
|
|
93
|
+
}
|
|
94
|
+
else if (content.blob) {
|
|
95
|
+
lines.push(t(locale, 'mcp_resource_blob', { size: content.blob.length }));
|
|
96
|
+
}
|
|
97
|
+
}
|
|
98
|
+
return lines.join('\n');
|
|
99
|
+
}
|
|
100
|
+
export function formatDiffMessage(locale, diff) {
|
|
101
|
+
const clipped = diff.length > 3500 ? `${diff.slice(0, 3500)}\n...` : diff;
|
|
102
|
+
return [
|
|
103
|
+
telegramBold(t(locale, 'diff_title')),
|
|
104
|
+
telegramExpandableBlockquote(clipped),
|
|
105
|
+
].join('\n');
|
|
106
|
+
}
|
|
107
|
+
export function formatRichDiffMessage(locale, diff) {
|
|
108
|
+
const clipped = clipRichMessageText(diff, Math.min(24_000, TELEGRAM_RICH_MESSAGE_TEXT_LIMIT - 1024));
|
|
109
|
+
const summary = locale === 'zh' ? '展开 diff' : 'Expand diff';
|
|
110
|
+
const footer = locale === 'zh'
|
|
111
|
+
? 'FoxClaw · sendRichMessage · details/pre/code'
|
|
112
|
+
: 'FoxClaw · sendRichMessage · details/pre/code';
|
|
113
|
+
return [
|
|
114
|
+
`<h3>${escapeTelegramHtml(t(locale, 'diff_title'))}</h3>`,
|
|
115
|
+
telegramDetails(summary, telegramPreCode(clipped, 'diff')),
|
|
116
|
+
`<footer>${escapeTelegramHtml(footer)}</footer>`,
|
|
117
|
+
].join('\n');
|
|
118
|
+
}
|
|
119
|
+
export function formatRichDemoMessage(locale) {
|
|
120
|
+
const title = 'FoxClaw RichMessage';
|
|
121
|
+
const intro = locale === 'zh'
|
|
122
|
+
? '这条消息通过 Telegram Bot API sendRichMessage 发送,用来验证 Rich Message 在真实客户端里的渲染。'
|
|
123
|
+
: 'This message is sent through Telegram Bot API sendRichMessage to verify Rich Message rendering in a real client.';
|
|
124
|
+
const detailsSummary = locale === 'zh' ? '展开 details + pre 示例' : 'Open details + pre sample';
|
|
125
|
+
const diffSample = [
|
|
126
|
+
'diff --git a/src/telegram/rich.ts b/src/telegram/rich.ts',
|
|
127
|
+
'+ sendRichMessage({ rich_message: { html } })',
|
|
128
|
+
'+ <details><summary>Expandable</summary>...</details>',
|
|
129
|
+
'+ <table bordered striped>...</table>',
|
|
130
|
+
].join('\n');
|
|
131
|
+
const tableCaption = locale === 'zh' ? 'FoxClaw 可用 rich 面' : 'FoxClaw rich surfaces';
|
|
132
|
+
const surfaceHeader = locale === 'zh' ? '功能面' : 'Surface';
|
|
133
|
+
const richHeader = locale === 'zh' ? 'Rich 用法' : 'Rich usage';
|
|
134
|
+
const tableRows = locale === 'zh'
|
|
135
|
+
? [
|
|
136
|
+
['`/diff`', 'details + pre/code'],
|
|
137
|
+
['工具状态', 'details/list'],
|
|
138
|
+
['`/status`', 'table'],
|
|
139
|
+
]
|
|
140
|
+
: [
|
|
141
|
+
['`/diff`', 'details + pre/code'],
|
|
142
|
+
['Tool status', 'details/list'],
|
|
143
|
+
['`/status`', 'table'],
|
|
144
|
+
];
|
|
145
|
+
const listItems = locale === 'zh'
|
|
146
|
+
? ['Gateway 已接入 sendRichMessage', '/diff 已优先使用 RichMessage', '失败会回退到 Telegram HTML']
|
|
147
|
+
: ['Gateway now supports sendRichMessage', '/diff prefers RichMessage', 'Failures fall back to Telegram HTML'];
|
|
148
|
+
const detailBody = [
|
|
149
|
+
`<p>${escapeTelegramHtml(locale === 'zh' ? '下面是 rich pre/code block,客户端支持时会按代码块渲染。' : 'This is a rich pre/code block rendered as code by supported clients.')}</p>`,
|
|
150
|
+
telegramPreCode(diffSample, 'diff'),
|
|
151
|
+
].join('\n');
|
|
152
|
+
return [
|
|
153
|
+
`<h2>${escapeTelegramHtml(title)}</h2>`,
|
|
154
|
+
`<p><b>Bot API 10.1</b> ${escapeTelegramHtml(intro)}</p>`,
|
|
155
|
+
'<hr/>',
|
|
156
|
+
'<table bordered striped>',
|
|
157
|
+
`<caption>${escapeTelegramHtml(tableCaption)}</caption>`,
|
|
158
|
+
`<tr><th>${escapeTelegramHtml(surfaceHeader)}</th><th>${escapeTelegramHtml(richHeader)}</th></tr>`,
|
|
159
|
+
...tableRows.map(([surface, usage]) => `<tr><td>${escapeTelegramHtml(surface)}</td><td>${escapeTelegramHtml(usage)}</td></tr>`),
|
|
160
|
+
'</table>',
|
|
161
|
+
telegramDetails(detailsSummary, detailBody, true),
|
|
162
|
+
'<ul>',
|
|
163
|
+
...listItems.map(item => `<li>${escapeTelegramHtml(item)}</li>`),
|
|
164
|
+
'</ul>',
|
|
165
|
+
'<footer>FoxClaw · sendRichMessage</footer>',
|
|
166
|
+
].join('\n');
|
|
167
|
+
}
|
|
168
|
+
export function formatRichDemoFallbackMessage(locale) {
|
|
169
|
+
const lines = locale === 'zh'
|
|
170
|
+
? [
|
|
171
|
+
'RichMessage fallback 预览',
|
|
172
|
+
'Gateway 已接入 sendRichMessage',
|
|
173
|
+
'/diff 已优先使用 RichMessage',
|
|
174
|
+
'如果你看到这条 HTML fallback,说明 Telegram rich API 返回了失败,正常功能仍可用。',
|
|
175
|
+
]
|
|
176
|
+
: [
|
|
177
|
+
'RichMessage fallback preview',
|
|
178
|
+
'Gateway now supports sendRichMessage',
|
|
179
|
+
'/diff prefers RichMessage',
|
|
180
|
+
'If you see this HTML fallback, Telegram rich API failed but normal messaging still works.',
|
|
181
|
+
];
|
|
182
|
+
return [
|
|
183
|
+
telegramBold('FoxClaw RichMessage'),
|
|
184
|
+
telegramExpandableBlockquote(lines.join('\n')),
|
|
185
|
+
telegramPreCode('sendRichMessage({ rich_message: { html } })', 'typescript'),
|
|
186
|
+
].join('\n');
|
|
187
|
+
}
|
|
188
|
+
export function formatRichInternalMessage(title, text) {
|
|
189
|
+
const sections = splitRichInternalSections(text);
|
|
190
|
+
const body = sections.map(formatRichInternalSection).filter(Boolean).join('\n');
|
|
191
|
+
return [
|
|
192
|
+
`<h3>${escapeTelegramHtml(title)}</h3>`,
|
|
193
|
+
body || `<p>${escapeTelegramHtml(text)}</p>`,
|
|
194
|
+
telegramDetails('Plain text', telegramPreCode(text, 'text')),
|
|
195
|
+
'<footer>FoxClaw · RichMessage</footer>',
|
|
196
|
+
].join('\n');
|
|
197
|
+
}
|
|
198
|
+
export function splitRichInternalSections(text) {
|
|
199
|
+
const sections = [];
|
|
200
|
+
let current = [];
|
|
201
|
+
for (const rawLine of text.split('\n')) {
|
|
202
|
+
const line = rawLine.trimEnd();
|
|
203
|
+
if (!line.trim()) {
|
|
204
|
+
if (current.length > 0) {
|
|
205
|
+
sections.push(current);
|
|
206
|
+
current = [];
|
|
207
|
+
}
|
|
208
|
+
continue;
|
|
209
|
+
}
|
|
210
|
+
current.push(line);
|
|
211
|
+
}
|
|
212
|
+
if (current.length > 0) {
|
|
213
|
+
sections.push(current);
|
|
214
|
+
}
|
|
215
|
+
return sections;
|
|
216
|
+
}
|
|
217
|
+
export function formatRichInternalSection(lines) {
|
|
218
|
+
const candidateRows = lines
|
|
219
|
+
.map(parseRichAuthCandidateRow)
|
|
220
|
+
.filter((row) => row !== null);
|
|
221
|
+
if (candidateRows.length > 0) {
|
|
222
|
+
const nonCandidateLines = lines.filter(line => parseRichAuthCandidateRow(line) === null);
|
|
223
|
+
return [
|
|
224
|
+
nonCandidateLines.length > 0 ? formatRichInternalSectionWithoutCandidates(nonCandidateLines) : '',
|
|
225
|
+
formatRichAuthCandidateTable(candidateRows),
|
|
226
|
+
].filter(Boolean).join('\n');
|
|
227
|
+
}
|
|
228
|
+
return formatRichInternalSectionWithoutCandidates(lines);
|
|
229
|
+
}
|
|
230
|
+
export function formatRichInternalSectionWithoutCandidates(lines) {
|
|
231
|
+
const rows = lines
|
|
232
|
+
.map(parseRichInternalKeyValue)
|
|
233
|
+
.filter((row) => row !== null);
|
|
234
|
+
if (rows.length >= Math.max(2, Math.floor(lines.length * 0.6))) {
|
|
235
|
+
return [
|
|
236
|
+
'<table bordered striped>',
|
|
237
|
+
...rows.map(([key, value]) => (`<tr><td>${escapeTelegramHtml(key)}</td><td>${formatRichInternalValue(value)}</td></tr>`)),
|
|
238
|
+
'</table>',
|
|
239
|
+
].join('\n');
|
|
240
|
+
}
|
|
241
|
+
return [
|
|
242
|
+
'<ul>',
|
|
243
|
+
...lines.map(line => `<li>${formatRichInternalValue(line.replace(/^\s*[-*]\s+/, '').replace(/^\s*\d+[.)]\s+/, ''))}</li>`),
|
|
244
|
+
'</ul>',
|
|
245
|
+
].join('\n');
|
|
246
|
+
}
|
|
247
|
+
export function parseRichAuthCandidateRow(line) {
|
|
248
|
+
const match = line.match(/^\s*(\d+)\.\s+(.+)$/);
|
|
249
|
+
if (!match) {
|
|
250
|
+
return null;
|
|
251
|
+
}
|
|
252
|
+
let body = match[2].trim();
|
|
253
|
+
let status = '';
|
|
254
|
+
const statusMatch = body.match(/^(.*?)\s+(\[[^\]]+\])$/);
|
|
255
|
+
if (statusMatch) {
|
|
256
|
+
body = statusMatch[1].trim();
|
|
257
|
+
status = statusMatch[2].replace(/^\[|\]$/g, '');
|
|
258
|
+
}
|
|
259
|
+
const current = body.endsWith(' *');
|
|
260
|
+
if (current) {
|
|
261
|
+
body = body.slice(0, -2).trimEnd();
|
|
262
|
+
}
|
|
263
|
+
const parts = body.split('|');
|
|
264
|
+
const name = parts.pop()?.trim() ?? '';
|
|
265
|
+
if (!name) {
|
|
266
|
+
return null;
|
|
267
|
+
}
|
|
268
|
+
const quotas = parts.map(parseRichAuthQuotaCell);
|
|
269
|
+
const statusParts = splitRichAuthStatus(status);
|
|
270
|
+
return {
|
|
271
|
+
index: match[1],
|
|
272
|
+
quotaA: quotas[0]?.value ?? '-',
|
|
273
|
+
quotaAReset: quotas[0]?.reset ?? '-',
|
|
274
|
+
quotaB: quotas[1]?.value ?? '-',
|
|
275
|
+
quotaBReset: quotas[1]?.reset ?? '-',
|
|
276
|
+
name,
|
|
277
|
+
current,
|
|
278
|
+
enabled: statusParts.health === 'disabled' || statusParts.health === '已禁用'
|
|
279
|
+
? false
|
|
280
|
+
: statusParts.health === '-' ? null : true,
|
|
281
|
+
...statusParts,
|
|
282
|
+
};
|
|
283
|
+
}
|
|
284
|
+
export function formatRichAuthCandidateTable(rows) {
|
|
285
|
+
return [
|
|
286
|
+
'<table bordered striped>',
|
|
287
|
+
'<tr><th>#</th><th>Quota A</th><th>A reset</th><th>Quota B</th><th>B reset</th><th>Auth</th><th>Current</th><th>Plan</th><th>Health</th><th>Last refresh</th><th>Expiry</th><th>Risk</th><th>Command</th></tr>',
|
|
288
|
+
...rows.map(row => [
|
|
289
|
+
'<tr>',
|
|
290
|
+
`<td>${escapeTelegramHtml(row.index)}</td>`,
|
|
291
|
+
`<td>${escapeTelegramHtml(row.quotaA)}</td>`,
|
|
292
|
+
`<td>${escapeTelegramHtml(row.quotaAReset)}</td>`,
|
|
293
|
+
`<td>${escapeTelegramHtml(row.quotaB)}</td>`,
|
|
294
|
+
`<td>${escapeTelegramHtml(row.quotaBReset)}</td>`,
|
|
295
|
+
`<td>${formatRichAuthCommandLink(`/auth use ${row.index}`, row.name)}</td>`,
|
|
296
|
+
`<td>${row.current ? 'yes' : '-'}</td>`,
|
|
297
|
+
`<td>${escapeTelegramHtml(row.plan)}</td>`,
|
|
298
|
+
`<td>${escapeTelegramHtml(row.health)}</td>`,
|
|
299
|
+
`<td>${escapeTelegramHtml(row.refresh)}</td>`,
|
|
300
|
+
`<td>${escapeTelegramHtml(row.expiry)}</td>`,
|
|
301
|
+
`<td>${escapeTelegramHtml(row.risk)}</td>`,
|
|
302
|
+
`<td>${formatRichAuthCommandCell(row)}</td>`,
|
|
303
|
+
'</tr>',
|
|
304
|
+
].join('')),
|
|
305
|
+
'</table>',
|
|
306
|
+
].join('\n');
|
|
307
|
+
}
|
|
308
|
+
export function splitRichAuthStatus(status) {
|
|
309
|
+
const parts = status.split(' · ').map(part => part.trim()).filter(Boolean);
|
|
310
|
+
const refreshIndex = parts.findIndex(part => /^(refreshed|刷新于)\b/i.test(part));
|
|
311
|
+
const refresh = refreshIndex >= 0 ? parts.splice(refreshIndex, 1)[0] : '-';
|
|
312
|
+
const expiryIndex = parts.findIndex(part => /^(expires|过期于)\b/i.test(part));
|
|
313
|
+
const expiry = expiryIndex >= 0 ? parts.splice(expiryIndex, 1)[0] : '-';
|
|
314
|
+
const plan = parts.length > 1 ? parts.shift() : '-';
|
|
315
|
+
const health = parts.shift() ?? '-';
|
|
316
|
+
return {
|
|
317
|
+
plan,
|
|
318
|
+
health,
|
|
319
|
+
refresh,
|
|
320
|
+
expiry,
|
|
321
|
+
risk: formatRichAuthRisk(health),
|
|
322
|
+
};
|
|
323
|
+
}
|
|
324
|
+
export function formatRichAuthRisk(health) {
|
|
325
|
+
if (/not recently refreshed|长期未刷新/i.test(health)) {
|
|
326
|
+
return `stale >${CODEX_AUTH_STALE_CREDENTIAL_DAYS}d`;
|
|
327
|
+
}
|
|
328
|
+
if (/needs login repair|需要登录修复/i.test(health)) {
|
|
329
|
+
return 'repair';
|
|
330
|
+
}
|
|
331
|
+
if (/quota exhausted|额度耗尽/i.test(health)) {
|
|
332
|
+
return 'quota exhausted';
|
|
333
|
+
}
|
|
334
|
+
if (/invalid|无效/i.test(health)) {
|
|
335
|
+
return 'invalid';
|
|
336
|
+
}
|
|
337
|
+
if (/\blow\b|偏低|低/i.test(health)) {
|
|
338
|
+
return 'low quota';
|
|
339
|
+
}
|
|
340
|
+
if (/unknown|未知/i.test(health)) {
|
|
341
|
+
return 'unknown';
|
|
342
|
+
}
|
|
343
|
+
return '-';
|
|
344
|
+
}
|
|
345
|
+
export function parseRichAuthQuotaCell(value) {
|
|
346
|
+
const trimmed = value.trim();
|
|
347
|
+
if (!trimmed || trimmed === '--' || trimmed === '—') {
|
|
348
|
+
return { value: '-', reset: '-' };
|
|
349
|
+
}
|
|
350
|
+
const [quotaPart, reset = '-'] = trimmed.split('@', 2);
|
|
351
|
+
const [windowLabel, percent] = quotaPart.split(':');
|
|
352
|
+
if (!windowLabel || percent === undefined) {
|
|
353
|
+
return { value: quotaPart, reset };
|
|
354
|
+
}
|
|
355
|
+
return { value: `${percent}%`, reset };
|
|
356
|
+
}
|
|
357
|
+
export function formatRichAuthCommandCell(row) {
|
|
358
|
+
const commands = [formatRichAuthCommandLink(`/auth use ${row.index}`, 'use')];
|
|
359
|
+
if (row.enabled === true) {
|
|
360
|
+
commands.push(formatRichAuthCommandLink(`/auth disable ${row.index}`, 'disable'));
|
|
361
|
+
}
|
|
362
|
+
else if (row.enabled === false) {
|
|
363
|
+
commands.push(formatRichAuthCommandLink(`/auth enable ${row.index}`, 'enable'));
|
|
364
|
+
}
|
|
365
|
+
return commands.join(' ');
|
|
366
|
+
}
|
|
367
|
+
export function formatRichAuthCommandLink(command, label) {
|
|
368
|
+
const url = `tg://msg?text=${encodeURIComponent(command)}`;
|
|
369
|
+
return `<a href="${escapeTelegramHtml(url)}">${escapeTelegramHtml(label)}</a>`;
|
|
370
|
+
}
|
|
371
|
+
export function parseRichInternalKeyValue(line) {
|
|
372
|
+
const separator = line.includes(':') ? ':' : ':';
|
|
373
|
+
const index = line.indexOf(separator);
|
|
374
|
+
if (index <= 0) {
|
|
375
|
+
return null;
|
|
376
|
+
}
|
|
377
|
+
const key = line.slice(0, index).trim();
|
|
378
|
+
const value = line.slice(index + separator.length).trim();
|
|
379
|
+
if (!key || !value || key.length > 80) {
|
|
380
|
+
return null;
|
|
381
|
+
}
|
|
382
|
+
return [key, value];
|
|
383
|
+
}
|
|
384
|
+
export function formatRichInternalValue(value) {
|
|
385
|
+
const escaped = escapeTelegramHtml(value);
|
|
386
|
+
return escaped.replace(/`([^`]+)`/g, '<code>$1</code>');
|
|
387
|
+
}
|
|
388
|
+
export function renderTelegramTable(headers, rows) {
|
|
389
|
+
return [
|
|
390
|
+
'<table bordered striped>',
|
|
391
|
+
`<thead><tr>${headers.map(header => `<th>${escapeTelegramHtml(header)}</th>`).join('')}</tr></thead>`,
|
|
392
|
+
`<tbody>${rows.map(row => `<tr>${row.map(cell => `<td>${escapeTelegramHtml(cell)}</td>`).join('')}</tr>`).join('')}</tbody>`,
|
|
393
|
+
'</table>',
|
|
394
|
+
].join('');
|
|
395
|
+
}
|
|
396
|
+
export function formatSelfUpdateVersionTransition(fromVersion, toVersion, locale) {
|
|
397
|
+
if (!fromVersion && !toVersion) {
|
|
398
|
+
return locale === 'zh' ? '未检测' : 'Not detected';
|
|
399
|
+
}
|
|
400
|
+
return `${fromVersion ?? '?'} -> ${toVersion ?? '?'}`;
|
|
401
|
+
}
|
|
402
|
+
export function formatCodexUpdateState(status) {
|
|
403
|
+
if (status.codexFromVersion && status.codexToVersion) {
|
|
404
|
+
if (status.codexFromVersion === status.codexToVersion) {
|
|
405
|
+
return status.locale === 'zh' ? '已是最新版本' : 'Already current';
|
|
406
|
+
}
|
|
407
|
+
return status.locale === 'zh' ? '升级完成' : 'Updated';
|
|
408
|
+
}
|
|
409
|
+
return status.codexUpdate ?? (status.locale === 'zh' ? '未执行升级' : 'Not updated');
|
|
410
|
+
}
|
|
411
|
+
export function formatSelfUpdateBroadcastLine(locale, targetVersion, broadcast) {
|
|
412
|
+
if (broadcast.state === 'disabled') {
|
|
413
|
+
return locale === 'zh' ? '未启用跨节点同步' : 'Cross-node sync is disabled';
|
|
414
|
+
}
|
|
415
|
+
if (broadcast.state === 'pending') {
|
|
416
|
+
return locale === 'zh'
|
|
417
|
+
? `目标 ${targetVersion ?? '?'},等待广播结果`
|
|
418
|
+
: `Target ${targetVersion ?? '?'}; waiting for broadcast result`;
|
|
419
|
+
}
|
|
420
|
+
if (broadcast.peers.length === 0) {
|
|
421
|
+
return locale === 'zh' ? '广播完成,无已配置 peer' : 'Broadcast completed; no configured peers';
|
|
422
|
+
}
|
|
423
|
+
return locale === 'zh'
|
|
424
|
+
? `已发送 ${broadcast.sent} 个 peer:${broadcast.peers.join('、')}`
|
|
425
|
+
: `Sent to ${broadcast.sent} peers: ${broadcast.peers.join(', ')}`;
|
|
426
|
+
}
|
|
427
|
+
export function clipRichMessageText(value, limit) {
|
|
428
|
+
if (value.length <= limit) {
|
|
429
|
+
return value;
|
|
430
|
+
}
|
|
431
|
+
return `${value.slice(0, Math.max(0, limit - 4))}\n...`;
|
|
432
|
+
}
|
|
433
|
+
export function formatLoadedThreadsMessage(locale, threadIds) {
|
|
434
|
+
const lines = [t(locale, 'loaded_title')];
|
|
435
|
+
if (threadIds.length === 0) {
|
|
436
|
+
lines.push(t(locale, 'loaded_empty'));
|
|
437
|
+
return lines.join('\n');
|
|
438
|
+
}
|
|
439
|
+
for (const threadId of threadIds.slice(0, 30)) {
|
|
440
|
+
lines.push(`- ${threadId}`);
|
|
441
|
+
}
|
|
442
|
+
if (threadIds.length > 30) {
|
|
443
|
+
lines.push(t(locale, 'list_truncated', { count: threadIds.length - 30 }));
|
|
444
|
+
}
|
|
445
|
+
return lines.join('\n');
|
|
446
|
+
}
|
|
447
|
+
export function formatHooksMessage(locale, entries) {
|
|
448
|
+
const hooks = entries.flatMap(entry => entry.hooks.map(hook => ({ cwd: entry.cwd, hook })));
|
|
449
|
+
const lines = [t(locale, 'hooks_title')];
|
|
450
|
+
if (hooks.length === 0) {
|
|
451
|
+
lines.push(t(locale, 'hooks_empty'));
|
|
452
|
+
}
|
|
453
|
+
else {
|
|
454
|
+
for (const { hook } of hooks.slice(0, 30)) {
|
|
455
|
+
lines.push(`${hook.enabled ? '*' : '-'} ${hook.key} (${hook.eventName}, ${hook.trustStatus})`);
|
|
456
|
+
const detail = [hook.pluginId ? `plugin=${hook.pluginId}` : null, hook.statusMessage, hook.command].filter(Boolean).join(' · ');
|
|
457
|
+
if (detail) {
|
|
458
|
+
lines.push(` ${truncateInline(detail, 140)}`);
|
|
459
|
+
}
|
|
460
|
+
}
|
|
461
|
+
if (hooks.length > 30) {
|
|
462
|
+
lines.push(t(locale, 'list_truncated', { count: hooks.length - 30 }));
|
|
463
|
+
}
|
|
464
|
+
}
|
|
465
|
+
const warnings = entries.flatMap(entry => entry.warnings);
|
|
466
|
+
const errors = entries.flatMap(entry => entry.errors);
|
|
467
|
+
if (warnings.length > 0) {
|
|
468
|
+
lines.push('', t(locale, 'hooks_warnings'));
|
|
469
|
+
lines.push(...warnings.slice(0, 5).map(warning => `- ${truncateInline(warning, 160)}`));
|
|
470
|
+
}
|
|
471
|
+
if (errors.length > 0) {
|
|
472
|
+
lines.push('', t(locale, 'hooks_errors'));
|
|
473
|
+
lines.push(...errors.slice(0, 5).map(error => `- ${truncateInline(`${error.path}: ${error.message}`, 180)}`));
|
|
474
|
+
}
|
|
475
|
+
return lines.join('\n');
|
|
476
|
+
}
|
|
477
|
+
export function formatPluginsMessage(locale, marketplaces, query) {
|
|
478
|
+
const plugins = marketplaces.flatMap(marketplace => marketplace.plugins.map(plugin => ({ marketplace, plugin })))
|
|
479
|
+
.filter(entry => !query
|
|
480
|
+
|| entry.plugin.name.toLowerCase().includes(query.toLowerCase())
|
|
481
|
+
|| entry.plugin.id.toLowerCase().includes(query.toLowerCase()));
|
|
482
|
+
const lines = [t(locale, 'plugins_title')];
|
|
483
|
+
if (query)
|
|
484
|
+
lines.push(t(locale, 'plugins_filter', { value: query }));
|
|
485
|
+
if (plugins.length === 0) {
|
|
486
|
+
lines.push(t(locale, 'plugins_empty'));
|
|
487
|
+
}
|
|
488
|
+
else {
|
|
489
|
+
for (const { marketplace, plugin } of plugins.slice(0, 30)) {
|
|
490
|
+
const state = `${plugin.installed ? 'installed' : 'not-installed'}, ${plugin.enabled ? 'on' : 'off'}`;
|
|
491
|
+
lines.push(`${plugin.enabled ? '*' : '-'} ${plugin.name} (${state})`);
|
|
492
|
+
lines.push(` ${plugin.id} · ${marketplace.displayName || marketplace.name}`);
|
|
493
|
+
}
|
|
494
|
+
if (plugins.length > 30) {
|
|
495
|
+
lines.push(t(locale, 'list_truncated', { count: plugins.length - 30 }));
|
|
496
|
+
}
|
|
497
|
+
}
|
|
498
|
+
return lines.join('\n');
|
|
499
|
+
}
|
|
500
|
+
export function formatPluginDetailMessage(locale, plugin) {
|
|
501
|
+
const lines = [
|
|
502
|
+
t(locale, 'plugin_title', { name: plugin.summary.name || plugin.summary.id }),
|
|
503
|
+
`id: ${plugin.summary.id}`,
|
|
504
|
+
`marketplace: ${plugin.marketplaceName}`,
|
|
505
|
+
`state: ${plugin.summary.installed ? 'installed' : 'not-installed'}, ${plugin.summary.enabled ? 'on' : 'off'}`,
|
|
506
|
+
plugin.description ? truncateInline(plugin.description, 400) : null,
|
|
507
|
+
].filter((line) => Boolean(line));
|
|
508
|
+
if (plugin.skills.length > 0) {
|
|
509
|
+
lines.push('', `skills: ${plugin.skills.slice(0, 12).map(skill => skill.name).join(', ')}`);
|
|
510
|
+
}
|
|
511
|
+
if (plugin.hooks.length > 0) {
|
|
512
|
+
lines.push(`hooks: ${plugin.hooks.slice(0, 12).map(hook => `${hook.eventName}:${hook.key}`).join(', ')}`);
|
|
513
|
+
}
|
|
514
|
+
if (plugin.apps.length > 0) {
|
|
515
|
+
lines.push(`apps: ${plugin.apps.slice(0, 12).map(app => app.name || app.id).join(', ')}`);
|
|
516
|
+
}
|
|
517
|
+
if (plugin.mcpServers.length > 0) {
|
|
518
|
+
lines.push(`mcp: ${plugin.mcpServers.slice(0, 12).join(', ')}`);
|
|
519
|
+
}
|
|
520
|
+
return lines.join('\n');
|
|
521
|
+
}
|
|
522
|
+
export function formatPluginSkillMessage(locale, marketplace, plugin, skill, contents) {
|
|
523
|
+
const lines = [t(locale, 'plugin_skill_title', { marketplace, plugin, skill })];
|
|
524
|
+
if (!contents) {
|
|
525
|
+
lines.push(t(locale, 'plugin_skill_empty'));
|
|
526
|
+
return lines.join('\n');
|
|
527
|
+
}
|
|
528
|
+
lines.push(truncateInline(contents, 3500));
|
|
529
|
+
return lines.join('\n');
|
|
530
|
+
}
|
|
531
|
+
export function formatAppsMessage(locale, apps, forceRefetch) {
|
|
532
|
+
const lines = [t(locale, 'apps_title')];
|
|
533
|
+
if (forceRefetch)
|
|
534
|
+
lines.push(t(locale, 'apps_reloaded'));
|
|
535
|
+
if (apps.length === 0) {
|
|
536
|
+
lines.push(t(locale, 'apps_empty'));
|
|
537
|
+
}
|
|
538
|
+
else {
|
|
539
|
+
for (const app of apps.slice(0, 30)) {
|
|
540
|
+
const state = `${app.isEnabled ? 'on' : 'off'}, ${app.isAccessible ? 'accessible' : 'blocked'}`;
|
|
541
|
+
lines.push(`${app.isEnabled ? '*' : '-'} ${app.name} (${state})`);
|
|
542
|
+
if (app.description)
|
|
543
|
+
lines.push(` ${truncateInline(app.description, 120)}`);
|
|
544
|
+
}
|
|
545
|
+
if (apps.length > 30) {
|
|
546
|
+
lines.push(t(locale, 'list_truncated', { count: apps.length - 30 }));
|
|
547
|
+
}
|
|
548
|
+
}
|
|
549
|
+
return lines.join('\n');
|
|
550
|
+
}
|
|
551
|
+
export function formatFeaturesMessage(locale, features) {
|
|
552
|
+
const lines = [t(locale, 'features_title')];
|
|
553
|
+
if (features.length === 0) {
|
|
554
|
+
lines.push(t(locale, 'features_empty'));
|
|
555
|
+
return lines.join('\n');
|
|
556
|
+
}
|
|
557
|
+
for (const feature of features.slice(0, 40)) {
|
|
558
|
+
lines.push(`${feature.enabled ? '*' : '-'} ${feature.displayName || feature.name} (${feature.stage})`);
|
|
559
|
+
if (feature.description)
|
|
560
|
+
lines.push(` ${truncateInline(feature.description, 120)}`);
|
|
561
|
+
}
|
|
562
|
+
if (features.length > 40) {
|
|
563
|
+
lines.push(t(locale, 'list_truncated', { count: features.length - 40 }));
|
|
564
|
+
}
|
|
565
|
+
return lines.join('\n');
|
|
566
|
+
}
|
|
567
|
+
export function formatConfigMessage(locale, result, appConfig, authPoolStats) {
|
|
568
|
+
const config = result.config && typeof result.config === 'object' ? result.config : {};
|
|
569
|
+
const layers = Array.isArray(result.layers) ? result.layers : [];
|
|
570
|
+
const keys = ['model', 'model_provider', 'approval_policy', 'sandbox_mode', 'web_search', 'service_tier', 'profile', 'review_model'];
|
|
571
|
+
const lines = [t(locale, 'config_title')];
|
|
572
|
+
for (const key of keys) {
|
|
573
|
+
const value = config[key];
|
|
574
|
+
lines.push(`${key}: ${value === null || value === undefined ? '-' : formatConfigValue(value)}`);
|
|
575
|
+
}
|
|
576
|
+
lines.push(t(locale, 'config_layers', { count: layers.length }));
|
|
577
|
+
lines.push('');
|
|
578
|
+
lines.push(t(locale, 'config_foxclaw_title'));
|
|
579
|
+
lines.push(t(locale, 'config_auth_auto_delete_needs_repair', {
|
|
580
|
+
value: t(locale, appConfig.authAutoDeleteNeedsRepair ? 'yes' : 'no'),
|
|
581
|
+
}));
|
|
582
|
+
lines.push(`AUTH_AUTO_DELETE_NEEDS_REPAIR=${appConfig.authAutoDeleteNeedsRepair ? 'true' : 'false'}`);
|
|
583
|
+
lines.push(t(locale, 'config_delete_tool_details_after_final', {
|
|
584
|
+
value: t(locale, appConfig.telegramDeleteToolDetailsAfterFinal ? 'yes' : 'no'),
|
|
585
|
+
}));
|
|
586
|
+
lines.push(`TELEGRAM_DELETE_TOOL_DETAILS_AFTER_FINAL=${appConfig.telegramDeleteToolDetailsAfterFinal ? 'true' : 'false'}`);
|
|
587
|
+
lines.push(t(locale, 'config_panel_ttl', { value: formatDurationMs(locale, appConfig.telegramPanelTtlMs) }));
|
|
588
|
+
lines.push(`TELEGRAM_PANEL_TTL_MS=${appConfig.telegramPanelTtlMs}`);
|
|
589
|
+
lines.push(formatCodexAuthPoolSummary(locale, authPoolStats));
|
|
590
|
+
lines.push(...formatCodexApiProviderConfigLines(locale, appConfig));
|
|
591
|
+
return lines.join('\n');
|
|
592
|
+
}
|
|
593
|
+
export function formatCodexApiProviderConfigLines(locale, appConfig) {
|
|
594
|
+
if (appConfig.codexApiProviders.length === 0) {
|
|
595
|
+
return [t(locale, 'config_api_providers_none')];
|
|
596
|
+
}
|
|
597
|
+
const lines = [t(locale, 'config_api_providers_title')];
|
|
598
|
+
lines.push(t(locale, 'config_api_default_provider', { value: appConfig.codexApiDefaultProvider ?? '-' }));
|
|
599
|
+
for (const provider of appConfig.codexApiProviders) {
|
|
600
|
+
lines.push(t(locale, 'config_api_provider_line', {
|
|
601
|
+
id: provider.id,
|
|
602
|
+
model: provider.model ?? '-',
|
|
603
|
+
base: provider.baseUrl,
|
|
604
|
+
env: provider.apiKeyEnv,
|
|
605
|
+
key: process.env[provider.apiKeyEnv]?.trim() ? t(locale, 'yes') : t(locale, 'no'),
|
|
606
|
+
}));
|
|
607
|
+
if (provider.chatCompletionsOnly) {
|
|
608
|
+
lines.push(t(locale, 'config_api_provider_chat_warning', { id: provider.id }));
|
|
609
|
+
}
|
|
610
|
+
}
|
|
611
|
+
return lines;
|
|
612
|
+
}
|
|
613
|
+
export function formatCodexAuthPoolSummary(locale, stats) {
|
|
614
|
+
return t(locale, 'auth_pool_summary', {
|
|
615
|
+
total: stats.totalSeen,
|
|
616
|
+
alive: stats.alive,
|
|
617
|
+
deleted: stats.deletedInvalid,
|
|
618
|
+
});
|
|
619
|
+
}
|
|
620
|
+
export function isInvalidCodexAuthDeleteReason(reason) {
|
|
621
|
+
return reason === AUTH_DELETE_REASON_NEEDS_REPAIR;
|
|
622
|
+
}
|
|
623
|
+
export function configKeyboard(locale, appConfig) {
|
|
624
|
+
const authAutoDeleteEnabled = appConfig.authAutoDeleteNeedsRepair;
|
|
625
|
+
const deleteToolDetailsEnabled = appConfig.telegramDeleteToolDetailsAfterFinal;
|
|
626
|
+
return [
|
|
627
|
+
[{
|
|
628
|
+
text: t(locale, authAutoDeleteEnabled ? 'button_config_auth_auto_delete_off' : 'button_config_auth_auto_delete_on'),
|
|
629
|
+
callback_data: `config:auth_auto_delete:${authAutoDeleteEnabled ? 'off' : 'on'}`,
|
|
630
|
+
}],
|
|
631
|
+
[{
|
|
632
|
+
text: t(locale, deleteToolDetailsEnabled ? 'button_config_delete_tool_details_off' : 'button_config_delete_tool_details_on'),
|
|
633
|
+
callback_data: `config:delete_tool_details:${deleteToolDetailsEnabled ? 'off' : 'on'}`,
|
|
634
|
+
}],
|
|
635
|
+
];
|
|
636
|
+
}
|
|
637
|
+
export function formatDurationMs(locale, value) {
|
|
638
|
+
if (value <= 0) {
|
|
639
|
+
return locale === 'zh' ? '关闭' : 'disabled';
|
|
640
|
+
}
|
|
641
|
+
if (value % 60_000 === 0) {
|
|
642
|
+
const minutes = value / 60_000;
|
|
643
|
+
return locale === 'zh' ? `${minutes} 分钟` : `${minutes} min`;
|
|
644
|
+
}
|
|
645
|
+
if (value % 1000 === 0) {
|
|
646
|
+
const seconds = value / 1000;
|
|
647
|
+
return locale === 'zh' ? `${seconds} 秒` : `${seconds} sec`;
|
|
648
|
+
}
|
|
649
|
+
return `${value}ms`;
|
|
650
|
+
}
|
|
651
|
+
export function parseConfigBooleanArg(value) {
|
|
652
|
+
const normalized = value?.trim().toLowerCase();
|
|
653
|
+
if (!normalized)
|
|
654
|
+
return null;
|
|
655
|
+
if (['1', 'true', 'yes', 'on', 'enable', 'enabled'].includes(normalized))
|
|
656
|
+
return true;
|
|
657
|
+
if (['0', 'false', 'no', 'off', 'disable', 'disabled'].includes(normalized))
|
|
658
|
+
return false;
|
|
659
|
+
return null;
|
|
660
|
+
}
|
|
661
|
+
export async function writeEnvBoolean(envPath, key, enabled) {
|
|
662
|
+
await fs.mkdir(path.dirname(envPath), { recursive: true });
|
|
663
|
+
const nextLine = `${key}=${enabled ? 'true' : 'false'}`;
|
|
664
|
+
let contents = '';
|
|
665
|
+
try {
|
|
666
|
+
contents = await fs.readFile(envPath, 'utf8');
|
|
667
|
+
}
|
|
668
|
+
catch {
|
|
669
|
+
await fs.writeFile(envPath, `${nextLine}\n`, { encoding: 'utf8', mode: 0o600 });
|
|
670
|
+
return;
|
|
671
|
+
}
|
|
672
|
+
const pattern = new RegExp(`(^|\\n)[ \\t#]*${escapeRegExp(key)}\\s*=.*(?=\\r?\\n|$)`);
|
|
673
|
+
const nextContents = pattern.test(contents)
|
|
674
|
+
? contents.replace(pattern, (_match, prefix) => `${prefix}${nextLine}`)
|
|
675
|
+
: `${contents}${contents.endsWith('\n') || contents.length === 0 ? '' : '\n'}${nextLine}\n`;
|
|
676
|
+
await fs.writeFile(envPath, nextContents, { encoding: 'utf8', mode: 0o600 });
|
|
677
|
+
}
|
|
678
|
+
export function escapeRegExp(value) {
|
|
679
|
+
return value.replace(/[.*+?^${}()|[\]\\]/g, '\\$&');
|
|
680
|
+
}
|
|
681
|
+
export function formatRequirementsMessage(locale, requirements) {
|
|
682
|
+
const lines = [t(locale, 'requirements_title')];
|
|
683
|
+
if (!requirements) {
|
|
684
|
+
lines.push(t(locale, 'requirements_empty'));
|
|
685
|
+
return lines.join('\n');
|
|
686
|
+
}
|
|
687
|
+
lines.push(`approval: ${requirements.allowedApprovalPolicies?.join(', ') ?? '-'}`);
|
|
688
|
+
lines.push(`sandbox: ${requirements.allowedSandboxModes?.join(', ') ?? '-'}`);
|
|
689
|
+
lines.push(`web_search: ${requirements.allowedWebSearchModes?.join(', ') ?? '-'}`);
|
|
690
|
+
lines.push(`residency: ${requirements.enforceResidency ?? '-'}`);
|
|
691
|
+
if (requirements.featureRequirements) {
|
|
692
|
+
const features = Object.entries(requirements.featureRequirements)
|
|
693
|
+
.map(([key, value]) => `${key}=${value ? 'on' : 'off'}`)
|
|
694
|
+
.join(', ');
|
|
695
|
+
lines.push(`features: ${truncateInline(features, 500)}`);
|
|
696
|
+
}
|
|
697
|
+
return lines.join('\n');
|
|
698
|
+
}
|
|
699
|
+
export function formatProviderMessage(locale, capabilities) {
|
|
700
|
+
return [
|
|
701
|
+
t(locale, 'provider_title'),
|
|
702
|
+
`webSearch: ${t(locale, capabilities.webSearch ? 'yes' : 'no')}`,
|
|
703
|
+
`imageGeneration: ${t(locale, capabilities.imageGeneration ? 'yes' : 'no')}`,
|
|
704
|
+
`namespaceTools: ${t(locale, capabilities.namespaceTools ? 'yes' : 'no')}`,
|
|
705
|
+
].join('\n');
|
|
706
|
+
}
|
|
707
|
+
export function formatGoalMessage(locale, goal, prefix) {
|
|
708
|
+
const lines = [t(locale, 'goal_title')];
|
|
709
|
+
if (prefix) {
|
|
710
|
+
lines.push(prefix);
|
|
711
|
+
}
|
|
712
|
+
if (!goal) {
|
|
713
|
+
lines.push(t(locale, 'goal_empty'));
|
|
714
|
+
return lines.join('\n');
|
|
715
|
+
}
|
|
716
|
+
lines.push(t(locale, 'goal_status', { value: formatGoalStatus(locale, goal.status) }));
|
|
717
|
+
lines.push(t(locale, 'goal_objective', { value: goal.objective || t(locale, 'empty') }));
|
|
718
|
+
lines.push(t(locale, 'goal_budget', {
|
|
719
|
+
value: goal.tokenBudget === null ? t(locale, 'none') : t(locale, 'goal_tokens', { value: formatTokenCount(goal.tokenBudget) }),
|
|
720
|
+
}));
|
|
721
|
+
lines.push(t(locale, 'goal_usage', {
|
|
722
|
+
tokens: formatTokenCount(goal.tokensUsed),
|
|
723
|
+
seconds: formatCompactNumber(goal.timeUsedSeconds),
|
|
724
|
+
}));
|
|
725
|
+
if (goal.updatedAt > 0) {
|
|
726
|
+
lines.push(t(locale, 'goal_updated_at', { value: formatLocalTimestamp(goal.updatedAt) }));
|
|
727
|
+
}
|
|
728
|
+
return lines.join('\n');
|
|
729
|
+
}
|
|
730
|
+
export function formatHistoryMessage(locale, threadId, turns) {
|
|
731
|
+
const lines = [t(locale, 'history_title', { threadId })];
|
|
732
|
+
if (turns.length === 0) {
|
|
733
|
+
lines.push(t(locale, 'history_empty'));
|
|
734
|
+
return lines.join('\n');
|
|
735
|
+
}
|
|
736
|
+
for (const turn of turns.slice(0, 30)) {
|
|
737
|
+
const time = turn.startedAt ? formatLocalTimestamp(turn.startedAt) : t(locale, 'unknown');
|
|
738
|
+
const itemSummary = summarizeTurnItems(turn.items);
|
|
739
|
+
const error = turn.error ? ` · ${truncateInline(turn.error, 80)}` : '';
|
|
740
|
+
lines.push(`- ${turn.turnId} · ${turn.status} · ${time}${error}`);
|
|
741
|
+
if (itemSummary) {
|
|
742
|
+
lines.push(` ${itemSummary}`);
|
|
743
|
+
}
|
|
744
|
+
}
|
|
745
|
+
return lines.join('\n');
|
|
746
|
+
}
|
|
747
|
+
export function formatFuzzyFilesMessage(locale, query, root, files) {
|
|
748
|
+
const lines = [t(locale, 'files_title', { query, root })];
|
|
749
|
+
if (files.length === 0) {
|
|
750
|
+
lines.push(t(locale, 'files_empty'));
|
|
751
|
+
return lines.join('\n');
|
|
752
|
+
}
|
|
753
|
+
for (const file of files.slice(0, 25)) {
|
|
754
|
+
const displayPath = file.path || file.fileName || '(unknown)';
|
|
755
|
+
lines.push(`- ${displayPath}${file.matchType ? ` (${file.matchType})` : ''}`);
|
|
756
|
+
}
|
|
757
|
+
if (files.length > 25) {
|
|
758
|
+
lines.push(t(locale, 'list_truncated', { count: files.length - 25 }));
|
|
759
|
+
}
|
|
760
|
+
return lines.join('\n');
|
|
761
|
+
}
|
|
762
|
+
export function formatRemoteStatusMessage(locale, status) {
|
|
763
|
+
const lines = [t(locale, 'remote_title')];
|
|
764
|
+
if (!status) {
|
|
765
|
+
lines.push(t(locale, 'remote_unknown'));
|
|
766
|
+
return lines.join('\n');
|
|
767
|
+
}
|
|
768
|
+
lines.push(t(locale, 'remote_status', { value: status.status }));
|
|
769
|
+
lines.push(t(locale, 'remote_environment', { value: status.environmentId ?? t(locale, 'none') }));
|
|
770
|
+
lines.push(t(locale, 'remote_installation', { value: status.installationId ?? t(locale, 'none') }));
|
|
771
|
+
return lines.join('\n');
|
|
772
|
+
}
|
|
773
|
+
export function formatGoalStatus(locale, status) {
|
|
774
|
+
switch (status) {
|
|
775
|
+
case 'paused':
|
|
776
|
+
return t(locale, 'goal_status_paused');
|
|
777
|
+
case 'budgetLimited':
|
|
778
|
+
return t(locale, 'goal_status_budget_limited');
|
|
779
|
+
case 'complete':
|
|
780
|
+
return t(locale, 'goal_status_complete');
|
|
781
|
+
default:
|
|
782
|
+
return t(locale, 'goal_status_active');
|
|
783
|
+
}
|
|
784
|
+
}
|