@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.
- package/.env.example +22 -8
- package/CHANGELOG.md +134 -0
- package/README.md +21 -1
- package/README_EN.md +21 -1
- package/dist/antigravity/adapter.d.ts +18 -0
- package/dist/antigravity/adapter.js +394 -0
- package/dist/antigravity/auth.d.ts +126 -0
- package/dist/antigravity/auth.js +933 -0
- package/dist/antigravity/client.d.ts +32 -0
- package/dist/antigravity/client.js +244 -0
- package/dist/antigravity/controller.d.ts +101 -0
- package/dist/antigravity/controller.js +2007 -0
- package/dist/antigravity/conversations.d.ts +29 -0
- package/dist/antigravity/conversations.js +289 -0
- package/dist/antigravity/events.d.ts +102 -0
- package/dist/antigravity/events.js +143 -0
- package/dist/antigravity/runtime.d.ts +49 -0
- package/dist/antigravity/runtime.js +82 -0
- package/dist/antigravity/subagents.d.ts +66 -0
- package/dist/antigravity/subagents.js +459 -0
- package/dist/channels/telegram/telegram_channel_adapter.d.ts +10 -3
- package/dist/channels/telegram/telegram_channel_adapter.js +2 -1
- package/dist/channels/weixin/weixin_channel_adapter.d.ts +5 -1
- package/dist/codex_app/adapter.d.ts +20 -0
- package/dist/codex_app/adapter.js +213 -0
- package/dist/codex_app/client.d.ts +1 -1
- package/dist/codex_app/client.js +12 -0
- package/dist/config.d.ts +14 -0
- package/dist/config.js +65 -16
- package/dist/controller/controller.d.ts +12 -5
- package/dist/controller/controller.js +109 -8
- package/dist/core/attachments.d.ts +11 -0
- package/dist/core/attachments.js +56 -0
- package/dist/core/engine_spi.d.ts +77 -0
- package/dist/core/engine_spi.js +1 -0
- package/dist/core/orchestrator.d.ts +106 -0
- package/dist/core/orchestrator.js +1556 -0
- package/dist/core/stream_preview.d.ts +29 -0
- package/dist/core/stream_preview.js +146 -0
- package/dist/core/turn_queue.d.ts +19 -0
- package/dist/core/turn_queue.js +41 -0
- package/dist/i18n.d.ts +6 -0
- package/dist/i18n.js +49 -0
- package/dist/main.js +375 -446
- package/dist/opencode/adapter.d.ts +11 -0
- package/dist/opencode/adapter.js +169 -0
- package/dist/opencode/controller.js +2 -2
- package/dist/store/database.d.ts +44 -0
- package/dist/store/database.js +275 -43
- package/dist/store/token_usage.d.ts +23 -0
- package/dist/store/token_usage.js +61 -0
- package/dist/telegram/api.d.ts +1 -0
- package/dist/telegram/api.js +80 -3
- package/dist/telegram/bot_home.js +5 -4
- package/dist/telegram/gateway.d.ts +4 -0
- package/dist/telegram/gateway.js +11 -0
- package/dist/types.d.ts +3 -0
- package/dist/update.d.ts +6 -0
- package/dist/update.js +95 -7
- package/dist/voice/tts.js +4 -2
- 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
- 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
- package/package.json +2 -2
|
@@ -0,0 +1,66 @@
|
|
|
1
|
+
import type { Logger } from '../logger.js';
|
|
2
|
+
import type { AppLocale } from '../types.js';
|
|
3
|
+
export interface SubagentProgressInfo {
|
|
4
|
+
conversationId: string;
|
|
5
|
+
parentConversationId: string;
|
|
6
|
+
agentName: string;
|
|
7
|
+
role?: string | undefined;
|
|
8
|
+
typeName?: string | undefined;
|
|
9
|
+
status: 'running' | 'idle' | 'done';
|
|
10
|
+
stepIndex: number;
|
|
11
|
+
toolCount: number;
|
|
12
|
+
currentTool: string | null;
|
|
13
|
+
recentTools: string[];
|
|
14
|
+
lastAction: string;
|
|
15
|
+
lastContentPreview: string;
|
|
16
|
+
updatedAt: number;
|
|
17
|
+
completedAt?: number | undefined;
|
|
18
|
+
}
|
|
19
|
+
export interface SubagentToolEvent {
|
|
20
|
+
subagentName: string;
|
|
21
|
+
conversationId: string;
|
|
22
|
+
toolName: string;
|
|
23
|
+
args?: Record<string, unknown> | string | undefined;
|
|
24
|
+
summary?: string | undefined;
|
|
25
|
+
status: 'running' | 'completed' | 'failed';
|
|
26
|
+
stepIndex?: number | undefined;
|
|
27
|
+
}
|
|
28
|
+
export interface SubagentPollResult {
|
|
29
|
+
subagents: SubagentProgressInfo[];
|
|
30
|
+
activeSubagents: SubagentProgressInfo[];
|
|
31
|
+
newToolEvents: SubagentToolEvent[];
|
|
32
|
+
hasUpdates: boolean;
|
|
33
|
+
}
|
|
34
|
+
export declare function cleanParameterValue(val: unknown): string;
|
|
35
|
+
export declare function extractSubagentToolSummary(name: string, parameters?: Record<string, unknown> | string | undefined): string | undefined;
|
|
36
|
+
export declare function escapeTelegramHtml(value: string): string;
|
|
37
|
+
export declare class AntigravitySubagentTracker {
|
|
38
|
+
private readonly baseDir;
|
|
39
|
+
private readonly dbPath;
|
|
40
|
+
private readonly logger;
|
|
41
|
+
private readonly parentConversationId;
|
|
42
|
+
private readonly subagents;
|
|
43
|
+
private parentTranscriptOffset;
|
|
44
|
+
private parentTranscriptRemainder;
|
|
45
|
+
constructor(parentConversationId: string, baseDir?: string, logger?: Logger);
|
|
46
|
+
get trackedCount(): number;
|
|
47
|
+
getSubagents(): SubagentProgressInfo[];
|
|
48
|
+
getActiveSubagents(): SubagentProgressInfo[];
|
|
49
|
+
getTranscriptPath(conversationId: string): string;
|
|
50
|
+
/**
|
|
51
|
+
* Discovers and polls active subagents for new steps, tool calls, and completion status.
|
|
52
|
+
*/
|
|
53
|
+
poll(): Promise<SubagentPollResult>;
|
|
54
|
+
private discoverSubagentsFromDb;
|
|
55
|
+
private discoverSubagentsFromParentTranscript;
|
|
56
|
+
private extractSubagentsFromTranscriptEntry;
|
|
57
|
+
private pollSubagentTranscript;
|
|
58
|
+
/**
|
|
59
|
+
* Renders subagent progress for Telegram messages (watch mode or stream preview).
|
|
60
|
+
*/
|
|
61
|
+
renderTelegramBlock(locale?: AppLocale): string;
|
|
62
|
+
/**
|
|
63
|
+
* One-line summary of what active subagents are currently doing.
|
|
64
|
+
*/
|
|
65
|
+
getActiveSummaryLine(locale?: AppLocale): string | null;
|
|
66
|
+
}
|
|
@@ -0,0 +1,459 @@
|
|
|
1
|
+
import fs from 'node:fs';
|
|
2
|
+
import path from 'node:path';
|
|
3
|
+
import os from 'node:os';
|
|
4
|
+
import { DatabaseSync } from 'node:sqlite';
|
|
5
|
+
export function cleanParameterValue(val) {
|
|
6
|
+
if (typeof val !== 'string')
|
|
7
|
+
return '';
|
|
8
|
+
let str = val.trim();
|
|
9
|
+
if (str.startsWith('"') && str.endsWith('"') && str.length >= 2) {
|
|
10
|
+
str = str.slice(1, -1).trim();
|
|
11
|
+
}
|
|
12
|
+
return str;
|
|
13
|
+
}
|
|
14
|
+
export function extractSubagentToolSummary(name, parameters) {
|
|
15
|
+
if (!parameters)
|
|
16
|
+
return undefined;
|
|
17
|
+
let params = {};
|
|
18
|
+
if (typeof parameters === 'string') {
|
|
19
|
+
try {
|
|
20
|
+
params = JSON.parse(parameters);
|
|
21
|
+
}
|
|
22
|
+
catch {
|
|
23
|
+
return parameters.slice(0, 60);
|
|
24
|
+
}
|
|
25
|
+
}
|
|
26
|
+
else if (typeof parameters === 'object') {
|
|
27
|
+
params = parameters;
|
|
28
|
+
}
|
|
29
|
+
const summary = cleanParameterValue(params.toolSummary);
|
|
30
|
+
if (summary)
|
|
31
|
+
return summary.slice(0, 60);
|
|
32
|
+
const action = cleanParameterValue(params.toolAction);
|
|
33
|
+
if (action)
|
|
34
|
+
return action.slice(0, 60);
|
|
35
|
+
const cmd = cleanParameterValue(params.CommandLine);
|
|
36
|
+
if (cmd)
|
|
37
|
+
return `$ ${cmd.slice(0, 70)}`;
|
|
38
|
+
const targetFile = cleanParameterValue(params.TargetFile);
|
|
39
|
+
if (targetFile)
|
|
40
|
+
return path.basename(targetFile);
|
|
41
|
+
const absPath = cleanParameterValue(params.AbsolutePath);
|
|
42
|
+
if (absPath)
|
|
43
|
+
return path.basename(absPath);
|
|
44
|
+
const p = cleanParameterValue(params.path);
|
|
45
|
+
if (p)
|
|
46
|
+
return path.basename(p);
|
|
47
|
+
const q = cleanParameterValue(params.query);
|
|
48
|
+
if (q)
|
|
49
|
+
return q.slice(0, 60);
|
|
50
|
+
const url = cleanParameterValue(params.Url);
|
|
51
|
+
if (url)
|
|
52
|
+
return url.slice(0, 60);
|
|
53
|
+
const prompt = cleanParameterValue(params.Prompt);
|
|
54
|
+
if (prompt)
|
|
55
|
+
return prompt.slice(0, 50);
|
|
56
|
+
return undefined;
|
|
57
|
+
}
|
|
58
|
+
export function escapeTelegramHtml(value) {
|
|
59
|
+
return value
|
|
60
|
+
.replace(/&/g, '&')
|
|
61
|
+
.replace(/</g, '<')
|
|
62
|
+
.replace(/>/g, '>');
|
|
63
|
+
}
|
|
64
|
+
export class AntigravitySubagentTracker {
|
|
65
|
+
baseDir;
|
|
66
|
+
dbPath;
|
|
67
|
+
logger;
|
|
68
|
+
parentConversationId;
|
|
69
|
+
subagents = new Map();
|
|
70
|
+
parentTranscriptOffset = 0;
|
|
71
|
+
parentTranscriptRemainder = '';
|
|
72
|
+
constructor(parentConversationId, baseDir, logger) {
|
|
73
|
+
this.parentConversationId = parentConversationId;
|
|
74
|
+
this.baseDir = baseDir || path.join(os.homedir(), '.gemini', 'antigravity-cli');
|
|
75
|
+
this.dbPath = path.join(this.baseDir, 'conversation_summaries.db');
|
|
76
|
+
this.logger = logger;
|
|
77
|
+
}
|
|
78
|
+
get trackedCount() {
|
|
79
|
+
return this.subagents.size;
|
|
80
|
+
}
|
|
81
|
+
getSubagents() {
|
|
82
|
+
return Array.from(this.subagents.values()).map((s) => ({ ...s.info }));
|
|
83
|
+
}
|
|
84
|
+
getActiveSubagents() {
|
|
85
|
+
return Array.from(this.subagents.values())
|
|
86
|
+
.filter((s) => s.info.status === 'running')
|
|
87
|
+
.map((s) => ({ ...s.info }));
|
|
88
|
+
}
|
|
89
|
+
getTranscriptPath(conversationId) {
|
|
90
|
+
return path.join(this.baseDir, 'brain', conversationId, '.system_generated', 'logs', 'transcript.jsonl');
|
|
91
|
+
}
|
|
92
|
+
/**
|
|
93
|
+
* Discovers and polls active subagents for new steps, tool calls, and completion status.
|
|
94
|
+
*/
|
|
95
|
+
async poll() {
|
|
96
|
+
let hasUpdates = false;
|
|
97
|
+
const newToolEvents = [];
|
|
98
|
+
// 1. Discover subagents from SQLite database
|
|
99
|
+
this.discoverSubagentsFromDb();
|
|
100
|
+
// 2. Discover subagents from parent transcript.jsonl
|
|
101
|
+
this.discoverSubagentsFromParentTranscript();
|
|
102
|
+
// 3. Poll each tracked subagent's transcript
|
|
103
|
+
for (const state of this.subagents.values()) {
|
|
104
|
+
const updated = this.pollSubagentTranscript(state, newToolEvents);
|
|
105
|
+
if (updated) {
|
|
106
|
+
hasUpdates = true;
|
|
107
|
+
}
|
|
108
|
+
}
|
|
109
|
+
const all = this.getSubagents();
|
|
110
|
+
const active = all.filter((s) => s.status === 'running');
|
|
111
|
+
return {
|
|
112
|
+
subagents: all,
|
|
113
|
+
activeSubagents: active,
|
|
114
|
+
newToolEvents,
|
|
115
|
+
hasUpdates,
|
|
116
|
+
};
|
|
117
|
+
}
|
|
118
|
+
discoverSubagentsFromDb() {
|
|
119
|
+
if (!fs.existsSync(this.dbPath))
|
|
120
|
+
return;
|
|
121
|
+
try {
|
|
122
|
+
const db = new DatabaseSync(this.dbPath, { readOnly: true });
|
|
123
|
+
try {
|
|
124
|
+
db.exec('PRAGMA busy_timeout = 2000;');
|
|
125
|
+
// Discover direct subagents of parent, as well as nested subagents
|
|
126
|
+
const parentIdsToQuery = [this.parentConversationId, ...Array.from(this.subagents.keys())];
|
|
127
|
+
const placeholders = parentIdsToQuery.map(() => '?').join(',');
|
|
128
|
+
const rows = db.prepare(`
|
|
129
|
+
SELECT conversation_id, agent_name, status, not_fully_idle, killed, last_modified_time, parent_conversation_id
|
|
130
|
+
FROM conversation_summaries
|
|
131
|
+
WHERE parent_conversation_id IN (${placeholders}) AND killed = 0
|
|
132
|
+
ORDER BY last_modified_time ASC
|
|
133
|
+
`).all(...parentIdsToQuery);
|
|
134
|
+
for (const r of rows) {
|
|
135
|
+
const id = String(r.conversation_id);
|
|
136
|
+
const parentId = String(r.parent_conversation_id || this.parentConversationId);
|
|
137
|
+
const agentName = String(r.agent_name || 'Subagent');
|
|
138
|
+
const isIdle = !r.not_fully_idle || r.status === 'CASCADE_RUN_STATUS_IDLE';
|
|
139
|
+
if (!this.subagents.has(id)) {
|
|
140
|
+
const transcriptPath = this.getTranscriptPath(id);
|
|
141
|
+
this.subagents.set(id, {
|
|
142
|
+
info: {
|
|
143
|
+
conversationId: id,
|
|
144
|
+
parentConversationId: parentId,
|
|
145
|
+
agentName,
|
|
146
|
+
status: isIdle ? 'done' : 'running',
|
|
147
|
+
stepIndex: 0,
|
|
148
|
+
toolCount: 0,
|
|
149
|
+
currentTool: null,
|
|
150
|
+
recentTools: [],
|
|
151
|
+
lastAction: '',
|
|
152
|
+
lastContentPreview: '',
|
|
153
|
+
updatedAt: Date.now(),
|
|
154
|
+
},
|
|
155
|
+
transcriptPath,
|
|
156
|
+
fileOffset: 0,
|
|
157
|
+
remainder: '',
|
|
158
|
+
seenToolKeys: new Set(),
|
|
159
|
+
});
|
|
160
|
+
}
|
|
161
|
+
}
|
|
162
|
+
}
|
|
163
|
+
finally {
|
|
164
|
+
db.close();
|
|
165
|
+
}
|
|
166
|
+
}
|
|
167
|
+
catch (err) {
|
|
168
|
+
this.logger?.debug('antigravity.subagents.db_discovery_error', { error: String(err) });
|
|
169
|
+
}
|
|
170
|
+
}
|
|
171
|
+
discoverSubagentsFromParentTranscript() {
|
|
172
|
+
const parentTranscript = this.getTranscriptPath(this.parentConversationId);
|
|
173
|
+
if (!fs.existsSync(parentTranscript))
|
|
174
|
+
return;
|
|
175
|
+
try {
|
|
176
|
+
const stats = fs.statSync(parentTranscript);
|
|
177
|
+
if (stats.size < this.parentTranscriptOffset) {
|
|
178
|
+
this.parentTranscriptOffset = 0;
|
|
179
|
+
this.parentTranscriptRemainder = '';
|
|
180
|
+
}
|
|
181
|
+
if (stats.size === this.parentTranscriptOffset)
|
|
182
|
+
return;
|
|
183
|
+
const bytesToRead = stats.size - this.parentTranscriptOffset;
|
|
184
|
+
const buffer = Buffer.alloc(bytesToRead);
|
|
185
|
+
const fd = fs.openSync(parentTranscript, 'r');
|
|
186
|
+
try {
|
|
187
|
+
fs.readSync(fd, buffer, 0, bytesToRead, this.parentTranscriptOffset);
|
|
188
|
+
}
|
|
189
|
+
finally {
|
|
190
|
+
fs.closeSync(fd);
|
|
191
|
+
}
|
|
192
|
+
this.parentTranscriptOffset = stats.size;
|
|
193
|
+
const chunk = this.parentTranscriptRemainder + buffer.toString('utf8');
|
|
194
|
+
const lines = chunk.split('\n');
|
|
195
|
+
this.parentTranscriptRemainder = lines.pop() ?? '';
|
|
196
|
+
for (const line of lines) {
|
|
197
|
+
const trimmed = line.trim();
|
|
198
|
+
if (!trimmed)
|
|
199
|
+
continue;
|
|
200
|
+
try {
|
|
201
|
+
const entry = JSON.parse(trimmed);
|
|
202
|
+
this.extractSubagentsFromTranscriptEntry(entry, this.parentConversationId);
|
|
203
|
+
}
|
|
204
|
+
catch {
|
|
205
|
+
// ignore malformed lines
|
|
206
|
+
}
|
|
207
|
+
}
|
|
208
|
+
}
|
|
209
|
+
catch (err) {
|
|
210
|
+
this.logger?.debug('antigravity.subagents.parent_transcript_error', { error: String(err) });
|
|
211
|
+
}
|
|
212
|
+
}
|
|
213
|
+
extractSubagentsFromTranscriptEntry(entry, parentId) {
|
|
214
|
+
// 1. Check tool_calls for invoke_subagent
|
|
215
|
+
if (Array.isArray(entry.tool_calls)) {
|
|
216
|
+
for (const tc of entry.tool_calls) {
|
|
217
|
+
if (tc.name === 'invoke_subagent' && tc.args) {
|
|
218
|
+
let subagentsArg = tc.args.Subagents;
|
|
219
|
+
if (typeof subagentsArg === 'string') {
|
|
220
|
+
try {
|
|
221
|
+
subagentsArg = JSON.parse(subagentsArg);
|
|
222
|
+
}
|
|
223
|
+
catch {
|
|
224
|
+
// ignore
|
|
225
|
+
}
|
|
226
|
+
}
|
|
227
|
+
if (Array.isArray(subagentsArg)) {
|
|
228
|
+
for (const sub of subagentsArg) {
|
|
229
|
+
const name = sub.Role || sub.TypeName || 'Subagent';
|
|
230
|
+
void name;
|
|
231
|
+
}
|
|
232
|
+
}
|
|
233
|
+
}
|
|
234
|
+
}
|
|
235
|
+
}
|
|
236
|
+
// 2. Check generic content or output for Created the following subagents
|
|
237
|
+
const content = typeof entry.content === 'string' ? entry.content : '';
|
|
238
|
+
if (content.includes('Created the following subagents') || content.includes('conversationId')) {
|
|
239
|
+
const convMatches = content.matchAll(/"conversationId":\s*"([0-9a-fA-F-]{36})"/g);
|
|
240
|
+
for (const match of convMatches) {
|
|
241
|
+
const subId = match[1];
|
|
242
|
+
if (subId && subId !== this.parentConversationId && !this.subagents.has(subId)) {
|
|
243
|
+
const transcriptPath = this.getTranscriptPath(subId);
|
|
244
|
+
this.subagents.set(subId, {
|
|
245
|
+
info: {
|
|
246
|
+
conversationId: subId,
|
|
247
|
+
parentConversationId: parentId,
|
|
248
|
+
agentName: 'Subagent',
|
|
249
|
+
status: 'running',
|
|
250
|
+
stepIndex: 0,
|
|
251
|
+
toolCount: 0,
|
|
252
|
+
currentTool: null,
|
|
253
|
+
recentTools: [],
|
|
254
|
+
lastAction: '',
|
|
255
|
+
lastContentPreview: '',
|
|
256
|
+
updatedAt: Date.now(),
|
|
257
|
+
},
|
|
258
|
+
transcriptPath,
|
|
259
|
+
fileOffset: 0,
|
|
260
|
+
remainder: '',
|
|
261
|
+
seenToolKeys: new Set(),
|
|
262
|
+
});
|
|
263
|
+
}
|
|
264
|
+
}
|
|
265
|
+
}
|
|
266
|
+
}
|
|
267
|
+
pollSubagentTranscript(state, newToolEvents) {
|
|
268
|
+
if (!fs.existsSync(state.transcriptPath)) {
|
|
269
|
+
return false;
|
|
270
|
+
}
|
|
271
|
+
try {
|
|
272
|
+
const stats = fs.statSync(state.transcriptPath);
|
|
273
|
+
if (stats.size < state.fileOffset) {
|
|
274
|
+
state.fileOffset = 0;
|
|
275
|
+
state.remainder = '';
|
|
276
|
+
}
|
|
277
|
+
if (stats.size === state.fileOffset) {
|
|
278
|
+
return false;
|
|
279
|
+
}
|
|
280
|
+
const bytesToRead = stats.size - state.fileOffset;
|
|
281
|
+
const buffer = Buffer.alloc(bytesToRead);
|
|
282
|
+
const fd = fs.openSync(state.transcriptPath, 'r');
|
|
283
|
+
try {
|
|
284
|
+
fs.readSync(fd, buffer, 0, bytesToRead, state.fileOffset);
|
|
285
|
+
}
|
|
286
|
+
finally {
|
|
287
|
+
fs.closeSync(fd);
|
|
288
|
+
}
|
|
289
|
+
state.fileOffset = stats.size;
|
|
290
|
+
const chunk = state.remainder + buffer.toString('utf8');
|
|
291
|
+
const lines = chunk.split('\n');
|
|
292
|
+
state.remainder = lines.pop() ?? '';
|
|
293
|
+
let hasUpdates = false;
|
|
294
|
+
for (const line of lines) {
|
|
295
|
+
const trimmed = line.trim();
|
|
296
|
+
if (!trimmed)
|
|
297
|
+
continue;
|
|
298
|
+
let entry;
|
|
299
|
+
try {
|
|
300
|
+
entry = JSON.parse(trimmed);
|
|
301
|
+
}
|
|
302
|
+
catch {
|
|
303
|
+
continue;
|
|
304
|
+
}
|
|
305
|
+
// Check if subagent spawned a nested subagent
|
|
306
|
+
this.extractSubagentsFromTranscriptEntry(entry, state.info.conversationId);
|
|
307
|
+
if (typeof entry.step_index === 'number') {
|
|
308
|
+
state.info.stepIndex = entry.step_index;
|
|
309
|
+
}
|
|
310
|
+
if (entry.type === 'USER_INPUT' && typeof entry.content === 'string') {
|
|
311
|
+
// Task description received
|
|
312
|
+
hasUpdates = true;
|
|
313
|
+
state.info.updatedAt = Date.now();
|
|
314
|
+
}
|
|
315
|
+
if (entry.type === 'GENERIC') {
|
|
316
|
+
hasUpdates = true;
|
|
317
|
+
state.info.updatedAt = Date.now();
|
|
318
|
+
if (state.lastRunningTool) {
|
|
319
|
+
newToolEvents.push({
|
|
320
|
+
subagentName: state.info.agentName,
|
|
321
|
+
conversationId: state.info.conversationId,
|
|
322
|
+
toolName: state.lastRunningTool.toolName,
|
|
323
|
+
summary: state.lastRunningTool.summary,
|
|
324
|
+
status: entry.status === 'ERROR' ? 'failed' : 'completed',
|
|
325
|
+
stepIndex: state.lastRunningTool.stepIndex,
|
|
326
|
+
});
|
|
327
|
+
state.lastRunningTool = null;
|
|
328
|
+
state.info.currentTool = null;
|
|
329
|
+
}
|
|
330
|
+
}
|
|
331
|
+
if (entry.type === 'PLANNER_RESPONSE') {
|
|
332
|
+
hasUpdates = true;
|
|
333
|
+
state.info.updatedAt = Date.now();
|
|
334
|
+
if (typeof entry.content === 'string' && entry.content.trim()) {
|
|
335
|
+
state.info.lastContentPreview = entry.content.trim().slice(-300);
|
|
336
|
+
}
|
|
337
|
+
if (Array.isArray(entry.tool_calls) && entry.tool_calls.length > 0) {
|
|
338
|
+
for (const tc of entry.tool_calls) {
|
|
339
|
+
const toolName = tc.name || 'tool';
|
|
340
|
+
const toolKey = `${state.info.stepIndex}:${toolName}:${JSON.stringify(tc.args || {})}`;
|
|
341
|
+
if (!state.seenToolKeys.has(toolKey)) {
|
|
342
|
+
state.seenToolKeys.add(toolKey);
|
|
343
|
+
state.info.toolCount += 1;
|
|
344
|
+
if (state.lastRunningTool) {
|
|
345
|
+
newToolEvents.push({
|
|
346
|
+
subagentName: state.info.agentName,
|
|
347
|
+
conversationId: state.info.conversationId,
|
|
348
|
+
toolName: state.lastRunningTool.toolName,
|
|
349
|
+
summary: state.lastRunningTool.summary,
|
|
350
|
+
status: 'completed',
|
|
351
|
+
stepIndex: state.lastRunningTool.stepIndex,
|
|
352
|
+
});
|
|
353
|
+
state.lastRunningTool = null;
|
|
354
|
+
}
|
|
355
|
+
if (toolName === 'send_message') {
|
|
356
|
+
state.info.status = 'done';
|
|
357
|
+
state.info.completedAt = Date.now();
|
|
358
|
+
state.info.lastAction = '✅ 完成任务并回传结果给主 Agent';
|
|
359
|
+
state.info.currentTool = null;
|
|
360
|
+
}
|
|
361
|
+
else {
|
|
362
|
+
const summary = extractSubagentToolSummary(toolName, tc.args);
|
|
363
|
+
const displayDesc = summary ? ` · <i>${escapeTelegramHtml(summary)}</i>` : '';
|
|
364
|
+
const toolLine = `⚙️ <code>${escapeTelegramHtml(toolName)}</code>${displayDesc}`;
|
|
365
|
+
state.info.recentTools.push(toolLine);
|
|
366
|
+
state.info.currentTool = summary ? `${toolName} (${summary})` : toolName;
|
|
367
|
+
state.info.lastAction = summary ? `${toolName}: ${summary}` : toolName;
|
|
368
|
+
state.lastRunningTool = {
|
|
369
|
+
toolName,
|
|
370
|
+
summary,
|
|
371
|
+
stepIndex: state.info.stepIndex,
|
|
372
|
+
};
|
|
373
|
+
newToolEvents.push({
|
|
374
|
+
subagentName: state.info.agentName,
|
|
375
|
+
conversationId: state.info.conversationId,
|
|
376
|
+
toolName,
|
|
377
|
+
args: tc.args,
|
|
378
|
+
summary,
|
|
379
|
+
status: 'running',
|
|
380
|
+
stepIndex: state.info.stepIndex,
|
|
381
|
+
});
|
|
382
|
+
}
|
|
383
|
+
}
|
|
384
|
+
}
|
|
385
|
+
}
|
|
386
|
+
// If entry is DONE and was send_message, subagent is finished
|
|
387
|
+
if (entry.status === 'DONE' && state.info.status === 'done') {
|
|
388
|
+
newToolEvents.push({
|
|
389
|
+
subagentName: state.info.agentName,
|
|
390
|
+
conversationId: state.info.conversationId,
|
|
391
|
+
toolName: 'completed',
|
|
392
|
+
summary: 'Subagent finished',
|
|
393
|
+
status: 'completed',
|
|
394
|
+
stepIndex: state.info.stepIndex,
|
|
395
|
+
});
|
|
396
|
+
}
|
|
397
|
+
}
|
|
398
|
+
}
|
|
399
|
+
return hasUpdates;
|
|
400
|
+
}
|
|
401
|
+
catch (err) {
|
|
402
|
+
this.logger?.debug('antigravity.subagent.poll_error', {
|
|
403
|
+
conversationId: state.info.conversationId,
|
|
404
|
+
error: String(err),
|
|
405
|
+
});
|
|
406
|
+
return false;
|
|
407
|
+
}
|
|
408
|
+
}
|
|
409
|
+
/**
|
|
410
|
+
* Renders subagent progress for Telegram messages (watch mode or stream preview).
|
|
411
|
+
*/
|
|
412
|
+
renderTelegramBlock(locale = 'zh') {
|
|
413
|
+
const all = this.getSubagents();
|
|
414
|
+
if (all.length === 0)
|
|
415
|
+
return '';
|
|
416
|
+
const lines = [];
|
|
417
|
+
const activeCount = all.filter((s) => s.status === 'running').length;
|
|
418
|
+
const headerTitle = locale === 'zh'
|
|
419
|
+
? `🤖 <b>子 Agent 实时动态 (${activeCount > 0 ? `${activeCount} 个执行中` : `共 ${all.length} 个已完成`})</b>`
|
|
420
|
+
: `🤖 <b>Subagent Activity (${activeCount > 0 ? `${activeCount} running` : `${all.length} completed`})</b>`;
|
|
421
|
+
lines.push(headerTitle);
|
|
422
|
+
for (const sub of all) {
|
|
423
|
+
const isDone = sub.status === 'done';
|
|
424
|
+
const icon = isDone ? '✅' : '🔄';
|
|
425
|
+
const statusText = isDone
|
|
426
|
+
? (locale === 'zh' ? '已完成' : 'Completed')
|
|
427
|
+
: (locale === 'zh' ? `步骤 #${sub.stepIndex} · 已执行 ${sub.toolCount} 项工具` : `Step #${sub.stepIndex} · ${sub.toolCount} tools`);
|
|
428
|
+
const name = escapeTelegramHtml(sub.agentName || 'Subagent');
|
|
429
|
+
lines.push(`${icon} <b>[${name}]</b> <i>(${statusText})</i>`);
|
|
430
|
+
if (!isDone && sub.currentTool) {
|
|
431
|
+
lines.push(` ⚙️ <b>正在执行</b>: <code>${escapeTelegramHtml(sub.currentTool)}</code>`);
|
|
432
|
+
}
|
|
433
|
+
else if (isDone && sub.lastAction) {
|
|
434
|
+
lines.push(` ↳ <i>${escapeTelegramHtml(sub.lastAction)}</i>`);
|
|
435
|
+
}
|
|
436
|
+
if (sub.recentTools.length > 0) {
|
|
437
|
+
const recent = sub.recentTools.slice(-3);
|
|
438
|
+
for (const t of recent) {
|
|
439
|
+
lines.push(` ↳ ${t}`);
|
|
440
|
+
}
|
|
441
|
+
}
|
|
442
|
+
}
|
|
443
|
+
return lines.join('\n');
|
|
444
|
+
}
|
|
445
|
+
/**
|
|
446
|
+
* One-line summary of what active subagents are currently doing.
|
|
447
|
+
*/
|
|
448
|
+
getActiveSummaryLine(locale = 'zh') {
|
|
449
|
+
const active = this.getActiveSubagents();
|
|
450
|
+
if (active.length === 0)
|
|
451
|
+
return null;
|
|
452
|
+
const first = active[0];
|
|
453
|
+
const name = first.agentName || 'Subagent';
|
|
454
|
+
const action = first.currentTool || first.lastAction || (locale === 'zh' ? '正在执行' : 'Working');
|
|
455
|
+
return locale === 'zh'
|
|
456
|
+
? `🤖 子 Agent [${name}] 正在执行: ${action}…`
|
|
457
|
+
: `🤖 Subagent [${name}] running: ${action}…`;
|
|
458
|
+
}
|
|
459
|
+
}
|
|
@@ -1,12 +1,19 @@
|
|
|
1
1
|
import type { BridgeSessionCore } from '../../controller/controller.js';
|
|
2
|
+
export interface ITelegramBridgeCore {
|
|
3
|
+
registerTelegramInboundHandlers(): void;
|
|
4
|
+
startCodexApp(): Promise<void>;
|
|
5
|
+
startTelegramPolling(): Promise<void>;
|
|
6
|
+
stop(): Promise<void>;
|
|
7
|
+
}
|
|
2
8
|
/**
|
|
3
|
-
* Telegram channel: inbound subscription + transport startup ordering for {@link BridgeSessionCore}
|
|
9
|
+
* Telegram channel: inbound subscription + transport startup ordering for {@link BridgeSessionCore}
|
|
10
|
+
* or {@link UnifiedBridgeCore}.
|
|
4
11
|
* Additional channels (e.g. Weixin) can compose the same core with their own adapters.
|
|
5
12
|
*/
|
|
6
13
|
export declare class TelegramChannelAdapter {
|
|
7
14
|
private readonly core;
|
|
8
|
-
constructor(core: BridgeSessionCore);
|
|
15
|
+
constructor(core: BridgeSessionCore | ITelegramBridgeCore);
|
|
9
16
|
start(): Promise<void>;
|
|
10
17
|
stop(): Promise<void>;
|
|
11
|
-
get sessionCore(): BridgeSessionCore;
|
|
18
|
+
get sessionCore(): BridgeSessionCore | ITelegramBridgeCore;
|
|
12
19
|
}
|
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
/**
|
|
2
|
-
* Telegram channel: inbound subscription + transport startup ordering for {@link BridgeSessionCore}
|
|
2
|
+
* Telegram channel: inbound subscription + transport startup ordering for {@link BridgeSessionCore}
|
|
3
|
+
* or {@link UnifiedBridgeCore}.
|
|
3
4
|
* Additional channels (e.g. Weixin) can compose the same core with their own adapters.
|
|
4
5
|
*/
|
|
5
6
|
export class TelegramChannelAdapter {
|
|
@@ -2,6 +2,10 @@ import type { AppConfig } from '../../config.js';
|
|
|
2
2
|
import type { BridgeSessionCore } from '../../controller/controller.js';
|
|
3
3
|
import type { Logger } from '../../logger.js';
|
|
4
4
|
import type { BridgeStore } from '../../store/database.js';
|
|
5
|
+
import type { TelegramTextEvent } from '../../telegram/gateway.js';
|
|
6
|
+
export interface IWeixinBridgeCore {
|
|
7
|
+
dispatchInboundLikeTelegramText(event: TelegramTextEvent): void;
|
|
8
|
+
}
|
|
5
9
|
export declare class WeixinChannelAdapter {
|
|
6
10
|
private readonly core;
|
|
7
11
|
private readonly store;
|
|
@@ -9,7 +13,7 @@ export declare class WeixinChannelAdapter {
|
|
|
9
13
|
private readonly logger;
|
|
10
14
|
private readonly abort;
|
|
11
15
|
private loops;
|
|
12
|
-
constructor(core: BridgeSessionCore, store: BridgeStore, config: AppConfig, logger: Logger);
|
|
16
|
+
constructor(core: BridgeSessionCore | IWeixinBridgeCore, store: BridgeStore, config: AppConfig, logger: Logger);
|
|
13
17
|
start(): Promise<void>;
|
|
14
18
|
stop(): Promise<void>;
|
|
15
19
|
private listAccounts;
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
import type { IEngineAdapter, EngineModel, EngineTurnRequest, EngineTurnExecution } from '../core/engine_spi.js';
|
|
2
|
+
import type { CodexAppClient } from './client.js';
|
|
3
|
+
import type { SandboxModeValue } from '../types.js';
|
|
4
|
+
export declare class CodexEngineAdapter implements IEngineAdapter {
|
|
5
|
+
private readonly client;
|
|
6
|
+
readonly id: string;
|
|
7
|
+
readonly name: string;
|
|
8
|
+
private readonly defaultModel;
|
|
9
|
+
private readonly defaultApprovalPolicy;
|
|
10
|
+
private readonly defaultSandboxMode;
|
|
11
|
+
constructor(client: CodexAppClient, options?: {
|
|
12
|
+
id?: string;
|
|
13
|
+
name?: string;
|
|
14
|
+
defaultModel?: string | undefined;
|
|
15
|
+
defaultApprovalPolicy?: string;
|
|
16
|
+
defaultSandboxMode?: SandboxModeValue;
|
|
17
|
+
});
|
|
18
|
+
listModels(): Promise<EngineModel[]>;
|
|
19
|
+
executeTurn(request: EngineTurnRequest): EngineTurnExecution;
|
|
20
|
+
}
|