@foxden-app/foxclaw 0.7.3 → 0.11.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.
Files changed (63) hide show
  1. package/.env.example +22 -8
  2. package/CHANGELOG.md +134 -0
  3. package/README.md +21 -1
  4. package/README_EN.md +21 -1
  5. package/dist/antigravity/adapter.d.ts +18 -0
  6. package/dist/antigravity/adapter.js +394 -0
  7. package/dist/antigravity/auth.d.ts +126 -0
  8. package/dist/antigravity/auth.js +933 -0
  9. package/dist/antigravity/client.d.ts +32 -0
  10. package/dist/antigravity/client.js +244 -0
  11. package/dist/antigravity/controller.d.ts +101 -0
  12. package/dist/antigravity/controller.js +2007 -0
  13. package/dist/antigravity/conversations.d.ts +29 -0
  14. package/dist/antigravity/conversations.js +289 -0
  15. package/dist/antigravity/events.d.ts +102 -0
  16. package/dist/antigravity/events.js +143 -0
  17. package/dist/antigravity/runtime.d.ts +49 -0
  18. package/dist/antigravity/runtime.js +82 -0
  19. package/dist/antigravity/subagents.d.ts +66 -0
  20. package/dist/antigravity/subagents.js +459 -0
  21. package/dist/channels/telegram/telegram_channel_adapter.d.ts +10 -3
  22. package/dist/channels/telegram/telegram_channel_adapter.js +2 -1
  23. package/dist/channels/weixin/weixin_channel_adapter.d.ts +5 -1
  24. package/dist/codex_app/adapter.d.ts +20 -0
  25. package/dist/codex_app/adapter.js +213 -0
  26. package/dist/codex_app/client.d.ts +1 -1
  27. package/dist/codex_app/client.js +12 -0
  28. package/dist/config.d.ts +14 -0
  29. package/dist/config.js +65 -16
  30. package/dist/controller/controller.d.ts +12 -5
  31. package/dist/controller/controller.js +109 -8
  32. package/dist/core/attachments.d.ts +11 -0
  33. package/dist/core/attachments.js +56 -0
  34. package/dist/core/engine_spi.d.ts +77 -0
  35. package/dist/core/engine_spi.js +1 -0
  36. package/dist/core/orchestrator.d.ts +106 -0
  37. package/dist/core/orchestrator.js +1556 -0
  38. package/dist/core/stream_preview.d.ts +29 -0
  39. package/dist/core/stream_preview.js +146 -0
  40. package/dist/core/turn_queue.d.ts +19 -0
  41. package/dist/core/turn_queue.js +41 -0
  42. package/dist/i18n.d.ts +6 -0
  43. package/dist/i18n.js +49 -0
  44. package/dist/main.js +375 -446
  45. package/dist/opencode/adapter.d.ts +11 -0
  46. package/dist/opencode/adapter.js +169 -0
  47. package/dist/opencode/controller.js +2 -2
  48. package/dist/store/database.d.ts +44 -0
  49. package/dist/store/database.js +275 -43
  50. package/dist/store/token_usage.d.ts +23 -0
  51. package/dist/store/token_usage.js +61 -0
  52. package/dist/telegram/api.d.ts +1 -0
  53. package/dist/telegram/api.js +80 -3
  54. package/dist/telegram/bot_home.js +5 -4
  55. package/dist/telegram/gateway.d.ts +4 -0
  56. package/dist/telegram/gateway.js +11 -0
  57. package/dist/types.d.ts +3 -0
  58. package/dist/update.d.ts +6 -0
  59. package/dist/update.js +95 -7
  60. package/dist/voice/tts.js +4 -2
  61. package/docs/devlogs//346/212/200/346/234/257/351/200/232/350/256/257/02./347/273/237/344/270/200/351/200/232/351/201/223/350/260/203/345/272/246/345/231/250/344/270/216/345/217/257/346/217/222/346/213/224/345/274/225/346/223/216SPI/357/274/232FoxClaw/344/273/216/345/215/225/344/275/223/350/265/260/345/220/221/345/244/232Agent/345/272/225/345/272/247.md +192 -0
  62. package/docs/devlogs//347/254/254/344/270/200/344/272/272/347/247/260/347/211/210/02./347/257/207/344/272/214_/345/275/223/344/270/200/344/270/252/346/241/245/346/216/245/345/231/250/345/274/200/345/247/213/345/255/246/344/274/232/345/220/254/346/207/202/346/211/200/346/234/211Agent.md +45 -0
  63. package/package.json +2 -2
@@ -0,0 +1,29 @@
1
+ import type { AppLocale } from '../types.js';
2
+ export interface StreamPreviewState {
3
+ toolLines: string[];
4
+ accumulatedText: string;
5
+ isBoost?: boolean | undefined;
6
+ engineName?: string | undefined;
7
+ stepIndex?: number | undefined;
8
+ toolCount?: number | undefined;
9
+ currentTool?: string | null | undefined;
10
+ elapsedSeconds?: number | undefined;
11
+ }
12
+ export interface FoldedToolsSummaryOptions {
13
+ stepIndex?: number | undefined;
14
+ toolLines?: string[] | undefined;
15
+ toolCount?: number | undefined;
16
+ startTime?: number | undefined;
17
+ durationSeconds?: number | undefined;
18
+ usage?: {
19
+ inputTokens?: number | undefined;
20
+ outputTokens?: number | undefined;
21
+ cachedTokens?: number | undefined;
22
+ totalTokens?: number | undefined;
23
+ } | undefined;
24
+ locale?: AppLocale | undefined;
25
+ }
26
+ export declare function formatElapsedDuration(seconds: number, locale?: AppLocale): string;
27
+ export declare function buildFoldedToolsSummary(options: FoldedToolsSummaryOptions): string;
28
+ export declare function combineSummaryAndResponse(foldedSummary: string, responseText: string, limit?: number): string[];
29
+ export declare function renderStreamPreviewContent(state: StreamPreviewState): string;
@@ -0,0 +1,146 @@
1
+ import { formatMetricTokenCount } from '../store/token_usage.js';
2
+ import { chunkTelegramMessage } from '../telegram/text.js';
3
+ export function formatElapsedDuration(seconds, locale = 'zh') {
4
+ if (!Number.isFinite(seconds) || seconds <= 0)
5
+ return '0s';
6
+ if (seconds < 60)
7
+ return locale === 'zh' ? `${seconds}秒` : `${seconds}s`;
8
+ const mins = Math.floor(seconds / 60);
9
+ const secs = seconds % 60;
10
+ if (mins < 60) {
11
+ return locale === 'zh'
12
+ ? `${mins}分${secs > 0 ? `${secs}秒` : ''}`
13
+ : `${mins}m${secs > 0 ? ` ${secs}s` : ''}`;
14
+ }
15
+ const hours = Math.floor(mins / 60);
16
+ const remMins = mins % 60;
17
+ return locale === 'zh'
18
+ ? `${hours}小时${remMins > 0 ? `${remMins}分` : ''}`
19
+ : `${hours}h${remMins > 0 ? ` ${remMins}m` : ''}`;
20
+ }
21
+ export function buildFoldedToolsSummary(options) {
22
+ const { stepIndex, toolLines = [], toolCount = toolLines.length, startTime, durationSeconds, usage, locale = 'zh', } = options;
23
+ if (toolCount === 0 && (!stepIndex || stepIndex <= 1) && !usage) {
24
+ return '';
25
+ }
26
+ const roundText = stepIndex && stepIndex > 1
27
+ ? (locale === 'zh' ? ` · 共 ${stepIndex} 轮` : ` · ${stepIndex} turns`)
28
+ : '';
29
+ const toolText = toolCount > 0
30
+ ? (locale === 'zh' ? ` · 累计执行 ${toolCount} 次工具` : ` · ${toolCount} tools`)
31
+ : '';
32
+ let durationText = '';
33
+ const elapsedSec = typeof durationSeconds === 'number' && durationSeconds > 0
34
+ ? durationSeconds
35
+ : startTime
36
+ ? Math.max(1, Math.round((Date.now() - startTime) / 1000))
37
+ : 0;
38
+ if (elapsedSec > 0) {
39
+ durationText = locale === 'zh'
40
+ ? ` · 耗时 ${formatElapsedDuration(elapsedSec, 'zh')}`
41
+ : ` · in ${formatElapsedDuration(elapsedSec, 'en')}`;
42
+ }
43
+ // Header line
44
+ const header = `🛠️ <b>${locale === 'zh' ? '执行小结' : 'Execution Summary'}${roundText}${toolText}${durationText}</b>`;
45
+ // Token line
46
+ let usageLine = '';
47
+ if (usage && (usage.totalTokens || usage.inputTokens || usage.outputTokens || usage.cachedTokens)) {
48
+ const totalVal = usage.totalTokens || ((usage.inputTokens || 0) + (usage.outputTokens || 0));
49
+ const total = formatMetricTokenCount(totalVal);
50
+ const input = formatMetricTokenCount(usage.inputTokens || 0);
51
+ const output = formatMetricTokenCount(usage.outputTokens || 0);
52
+ const cached = formatMetricTokenCount(usage.cachedTokens || 0);
53
+ if (locale === 'zh') {
54
+ usageLine = `\n🪙 <b>Token 消耗</b>: 总计 ${total} (输入 ${input} · 输出 ${output} · 缓存 ${cached})`;
55
+ }
56
+ else {
57
+ usageLine = `\n🪙 <b>Token Usage</b>: Total ${total} (Input ${input} · Output ${output} · Cached ${cached})`;
58
+ }
59
+ }
60
+ // Tool detail section:
61
+ // If toolLines <= 25 (e.g. 10-20 step summaries in Codex/AGY): preserve ALL of them!
62
+ // If toolLines > 25 (e.g. 100+ raw tool calls): keep first 3 + omitted notice + last 15.
63
+ let toolDetailSection = '';
64
+ if (toolLines.length > 0) {
65
+ let previewLines = [];
66
+ if (toolLines.length <= 25) {
67
+ previewLines = toolLines;
68
+ }
69
+ else {
70
+ const head = toolLines.slice(0, 3);
71
+ const tail = toolLines.slice(-15);
72
+ const omitted = toolCount - (head.length + tail.length);
73
+ const omittedNote = omitted > 0
74
+ ? `<i>${locale === 'zh' ? `(中间 ${omitted} 项历史工具调用已折叠收起)` : `(${omitted} intermediate tools collapsed)`}</i>`
75
+ : '';
76
+ previewLines = [...head, omittedNote, ...tail].filter(Boolean);
77
+ }
78
+ toolDetailSection = `\n\n<i>${locale === 'zh' ? '工具调用记录:' : 'Tool calls:'}</i>\n${previewLines.join('\n')}`;
79
+ }
80
+ return `<blockquote expandable>${header}${usageLine}${toolDetailSection}</blockquote>\n\n`;
81
+ }
82
+ export function combineSummaryAndResponse(foldedSummary, responseText, limit = 4000) {
83
+ const content = (responseText || '(无输出 / No output)').trim();
84
+ if (!foldedSummary) {
85
+ return chunkTelegramMessage(content, limit);
86
+ }
87
+ const combined = `${foldedSummary}${content}`;
88
+ if (combined.length <= limit) {
89
+ return [combined];
90
+ }
91
+ // Combined exceeds limit: chunk responseText cleanly without tearing foldedSummary apart!
92
+ const remainingInFirstChunk = limit - foldedSummary.length;
93
+ if (remainingInFirstChunk >= 300) {
94
+ const window = content.slice(0, remainingInFirstChunk);
95
+ const splitAt = Math.max(window.lastIndexOf('\n\n'), window.lastIndexOf('\n'));
96
+ if (splitAt >= Math.floor(remainingInFirstChunk / 3)) {
97
+ const firstChunkText = content.slice(0, splitAt + 1).trim();
98
+ const rest = content.slice(splitAt + 1).trim();
99
+ const restChunks = chunkTelegramMessage(rest, limit);
100
+ return [`${foldedSummary}${firstChunkText}`, ...restChunks];
101
+ }
102
+ }
103
+ // If foldedSummary takes up most of the message or no clean paragraph break was found,
104
+ // foldedSummary gets its own clean message, and responseText is chunked independently.
105
+ const responseChunks = chunkTelegramMessage(content, limit);
106
+ return [foldedSummary.trim(), ...responseChunks];
107
+ }
108
+ export function renderStreamPreviewContent(state) {
109
+ const parts = [];
110
+ const name = state.engineName || 'AI';
111
+ const roundText = state.stepIndex && state.stepIndex > 0 ? `第 ${state.stepIndex} 轮` : '';
112
+ const toolText = typeof state.toolCount === 'number' && state.toolCount > 0 ? `累计执行 ${state.toolCount} 次工具` : '';
113
+ const timeText = typeof state.elapsedSeconds === 'number' && state.elapsedSeconds > 0 ? `已耗时 ${state.elapsedSeconds}s` : '';
114
+ const meta = [roundText, toolText, timeText].filter(Boolean).join(' · ');
115
+ if (meta || state.currentTool || state.toolLines.length > 0) {
116
+ if (state.isBoost) {
117
+ parts.push(`🚀 <b>${name} (Boost 模式) 正在执行中</b>${meta ? ` (${meta})` : ''}`);
118
+ }
119
+ else {
120
+ parts.push(`🔄 <b>${name} 正在执行中</b>${meta ? ` (${meta})` : ''}`);
121
+ }
122
+ }
123
+ if (state.currentTool) {
124
+ parts.push(`⚙️ <b>当前正在运行</b>: <code>${escapeTelegramHtml(state.currentTool)}</code>`);
125
+ parts.push('');
126
+ }
127
+ if (state.toolLines.length > 0) {
128
+ const recent = state.toolLines.slice(-6);
129
+ parts.push(`<blockquote expandable>🛠️ <b>正在调用工具 (${state.toolLines.length} 项)</b>\n${recent.join('\n')}</blockquote>`);
130
+ parts.push('');
131
+ }
132
+ if (state.accumulatedText) {
133
+ const preview = state.accumulatedText.slice(-3000);
134
+ parts.push(preview);
135
+ }
136
+ else {
137
+ parts.push(state.isBoost ? `🚀 ${name} (Boost 模式) 正在深度思考中…` : `⏳ ${name} 正在思考中…`);
138
+ }
139
+ return parts.join('\n');
140
+ }
141
+ function escapeTelegramHtml(value) {
142
+ return value
143
+ .replace(/&/g, '&amp;')
144
+ .replace(/</g, '&lt;')
145
+ .replace(/>/g, '&gt;');
146
+ }
@@ -0,0 +1,19 @@
1
+ export type TurnMessageMode = 'steer' | 'queue';
2
+ export interface QueuedTurnItem<T = unknown> {
3
+ id: string;
4
+ scopeId: string;
5
+ prompt: string;
6
+ payload: T;
7
+ enqueuedAt: number;
8
+ }
9
+ export declare class TurnQueueManager<T = unknown> {
10
+ private readonly queues;
11
+ private readonly activeModes;
12
+ getMode(scopeId: string, defaultMode?: TurnMessageMode): TurnMessageMode;
13
+ setMode(scopeId: string, mode: TurnMessageMode): void;
14
+ enqueue(scopeId: string, item: Omit<QueuedTurnItem<T>, 'id' | 'enqueuedAt'>): QueuedTurnItem<T>;
15
+ dequeue(scopeId: string): QueuedTurnItem<T> | undefined;
16
+ peek(scopeId: string): QueuedTurnItem<T> | undefined;
17
+ getQueueLength(scopeId: string): number;
18
+ clearQueue(scopeId: string): number;
19
+ }
@@ -0,0 +1,41 @@
1
+ export class TurnQueueManager {
2
+ queues = new Map();
3
+ activeModes = new Map();
4
+ getMode(scopeId, defaultMode = 'queue') {
5
+ return this.activeModes.get(scopeId) ?? defaultMode;
6
+ }
7
+ setMode(scopeId, mode) {
8
+ this.activeModes.set(scopeId, mode);
9
+ }
10
+ enqueue(scopeId, item) {
11
+ const fullItem = {
12
+ ...item,
13
+ id: `q_${Date.now()}_${Math.random().toString(36).slice(2, 7)}`,
14
+ enqueuedAt: Date.now(),
15
+ };
16
+ const list = this.queues.get(scopeId) || [];
17
+ list.push(fullItem);
18
+ this.queues.set(scopeId, list);
19
+ return fullItem;
20
+ }
21
+ dequeue(scopeId) {
22
+ const list = this.queues.get(scopeId);
23
+ if (!list || list.length === 0)
24
+ return undefined;
25
+ const item = list.shift();
26
+ if (list.length === 0)
27
+ this.queues.delete(scopeId);
28
+ return item;
29
+ }
30
+ peek(scopeId) {
31
+ return this.queues.get(scopeId)?.[0];
32
+ }
33
+ getQueueLength(scopeId) {
34
+ return this.queues.get(scopeId)?.length ?? 0;
35
+ }
36
+ clearQueue(scopeId) {
37
+ const count = this.getQueueLength(scopeId);
38
+ this.queues.delete(scopeId);
39
+ return count;
40
+ }
41
+ }
package/dist/i18n.d.ts CHANGED
@@ -43,6 +43,7 @@ declare const MESSAGES: {
43
43
  readonly usage_files: "Usage: /files <query>";
44
44
  readonly cmd_desc_help: "Show commands";
45
45
  readonly cmd_desc_setup: "Unified preference panel";
46
+ readonly cmd_desc_backend: "Switch backend engine and account";
46
47
  readonly cmd_desc_fast: "Toggle Fast mode";
47
48
  readonly cmd_desc_active: "Active-turn message behavior";
48
49
  readonly cmd_desc_rich: "Telegram RichMessage demo";
@@ -783,6 +784,7 @@ declare const MESSAGES: {
783
784
  readonly usage_files: "用法:/files <关键词>";
784
785
  readonly cmd_desc_help: "查看命令";
785
786
  readonly cmd_desc_setup: "统一偏好面板";
787
+ readonly cmd_desc_backend: "切换后端运行环境与账号";
786
788
  readonly cmd_desc_fast: "切换 Fast 模式";
787
789
  readonly cmd_desc_active: "运行中新消息处理方式";
788
790
  readonly cmd_desc_rich: "Telegram RichMessage 演示";
@@ -1493,4 +1495,8 @@ export declare function getOpencodeTelegramCommands(locale: AppLocale): Array<{
1493
1495
  command: string;
1494
1496
  description: string;
1495
1497
  }>;
1498
+ export declare function getAntigravityTelegramCommands(locale: AppLocale): Array<{
1499
+ command: string;
1500
+ description: string;
1501
+ }>;
1496
1502
  export {};
package/dist/i18n.js CHANGED
@@ -41,6 +41,7 @@ const MESSAGES = {
41
41
  usage_files: 'Usage: /files <query>',
42
42
  cmd_desc_help: 'Show commands',
43
43
  cmd_desc_setup: 'Unified preference panel',
44
+ cmd_desc_backend: 'Switch backend engine and account',
44
45
  cmd_desc_fast: 'Toggle Fast mode',
45
46
  cmd_desc_active: 'Active-turn message behavior',
46
47
  cmd_desc_rich: 'Telegram RichMessage demo',
@@ -781,6 +782,7 @@ const MESSAGES = {
781
782
  usage_files: '用法:/files <关键词>',
782
783
  cmd_desc_help: '查看命令',
783
784
  cmd_desc_setup: '统一偏好面板',
785
+ cmd_desc_backend: '切换后端运行环境与账号',
784
786
  cmd_desc_fast: '切换 Fast 模式',
785
787
  cmd_desc_active: '运行中新消息处理方式',
786
788
  cmd_desc_rich: 'Telegram RichMessage 演示',
@@ -1492,10 +1494,12 @@ export function getTelegramCommands(locale) {
1492
1494
  return [
1493
1495
  { command: 'help', description: t(locale, 'cmd_desc_help') },
1494
1496
  { command: 'setup', description: t(locale, 'cmd_desc_setup') },
1497
+ { command: 'backend', description: t(locale, 'cmd_desc_backend') },
1495
1498
  { command: 'status', description: t(locale, 'cmd_desc_status') },
1496
1499
  { command: 'update', description: t(locale, 'cmd_desc_update') },
1497
1500
  { command: 'threads', description: t(locale, 'cmd_desc_threads') },
1498
1501
  { command: 'auth', description: t(locale, 'cmd_desc_auth') },
1502
+ { command: 'boost', description: locale === 'zh' ? '切换 Boost 深度推理模式' : 'Toggle Boost reasoning mode' },
1499
1503
  { command: 'fast', description: t(locale, 'cmd_desc_fast') },
1500
1504
  { command: 'active', description: t(locale, 'cmd_desc_active') },
1501
1505
  { command: 'rich', description: t(locale, 'cmd_desc_rich') },
@@ -1578,3 +1582,48 @@ export function getOpencodeTelegramCommands(locale) {
1578
1582
  .map(([command, description]) => ({ command, description })),
1579
1583
  ];
1580
1584
  }
1585
+ export function getAntigravityTelegramCommands(locale) {
1586
+ return locale === 'zh'
1587
+ ? [
1588
+ { command: 'help', description: '查看帮助' },
1589
+ { command: 'setup', description: '控制面板与设置' },
1590
+ { command: 'backend', description: '切换后端运行环境与账号' },
1591
+ { command: 'threads', description: '查看会话列表' },
1592
+ { command: 'open', description: '打开指定会话' },
1593
+ { command: 'watch', description: '只读观察会话' },
1594
+ { command: 'unwatch', description: '停止观察' },
1595
+ { command: 'new', description: '新建会话' },
1596
+ { command: 'boost', description: '切换 Boost 深度推理模式' },
1597
+ { command: 'models', description: '切换模型' },
1598
+ { command: 'auth', description: '账号管理与切换' },
1599
+ { command: 'login', description: '网页授权登录新账号' },
1600
+ { command: 'active', description: '设置运行中消息模式 (插话/排队)' },
1601
+ { command: 'queue', description: '排队下一轮回复' },
1602
+ { command: 'steer', description: '插话并中断当前回复' },
1603
+ { command: 'effort', description: '设置思考深度' },
1604
+ { command: 'status', description: '运行状态' },
1605
+ { command: 'update', description: '自升级服务及后端 (Codex/AGY)' },
1606
+ { command: 'interrupt', description: '打断当前任务' },
1607
+ ]
1608
+ : [
1609
+ { command: 'help', description: 'Show commands' },
1610
+ { command: 'setup', description: 'Control panel & settings' },
1611
+ { command: 'backend', description: 'Switch backend and account' },
1612
+ { command: 'threads', description: 'List conversations' },
1613
+ { command: 'open', description: 'Open conversation' },
1614
+ { command: 'watch', description: 'Watch conversation' },
1615
+ { command: 'unwatch', description: 'Stop watching' },
1616
+ { command: 'new', description: 'New session' },
1617
+ { command: 'boost', description: 'Toggle Boost reasoning mode' },
1618
+ { command: 'models', description: 'Select model' },
1619
+ { command: 'auth', description: 'Manage accounts' },
1620
+ { command: 'login', description: 'Sign in new Google account' },
1621
+ { command: 'active', description: 'Active-turn message mode' },
1622
+ { command: 'queue', description: 'Queue next prompt' },
1623
+ { command: 'steer', description: 'Interrupt and steer turn' },
1624
+ { command: 'effort', description: 'Reasoning effort' },
1625
+ { command: 'status', description: 'Bridge status' },
1626
+ { command: 'update', description: 'Self-update service & backends' },
1627
+ { command: 'interrupt', description: 'Stop active turn' },
1628
+ ];
1629
+ }