@foxden-app/foxclaw 0.10.0 → 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 +9 -8
- package/CHANGELOG.md +25 -0
- package/dist/antigravity/adapter.d.ts +2 -1
- package/dist/antigravity/adapter.js +180 -42
- package/dist/antigravity/auth.d.ts +5 -0
- package/dist/antigravity/auth.js +37 -0
- package/dist/antigravity/client.js +8 -2
- package/dist/antigravity/controller.d.ts +5 -0
- package/dist/antigravity/controller.js +113 -28
- package/dist/antigravity/events.d.ts +1 -0
- package/dist/antigravity/events.js +29 -0
- package/dist/antigravity/runtime.d.ts +2 -0
- package/dist/antigravity/subagents.d.ts +66 -0
- package/dist/antigravity/subagents.js +459 -0
- package/dist/config.d.ts +3 -1
- package/dist/config.js +36 -29
- package/dist/controller/controller.d.ts +4 -0
- package/dist/controller/controller.js +71 -0
- package/dist/core/engine_spi.d.ts +1 -0
- package/dist/core/orchestrator.d.ts +6 -1
- package/dist/core/orchestrator.js +291 -139
- package/dist/main.js +346 -551
- package/dist/opencode/adapter.js +1 -0
- package/dist/opencode/controller.js +2 -2
- package/dist/store/database.d.ts +3 -0
- package/dist/store/database.js +33 -0
- package/dist/telegram/api.d.ts +1 -0
- package/dist/telegram/api.js +60 -12
- package/dist/telegram/bot_home.js +5 -4
- package/dist/types.d.ts +2 -0
- package/dist/update.js +17 -1
- package/package.json +1 -1
|
@@ -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
|
+
}
|
package/dist/config.d.ts
CHANGED
|
@@ -19,6 +19,8 @@ export declare function resolveEnvPath(): string;
|
|
|
19
19
|
export declare function getLoadedEnvPath(): string | null;
|
|
20
20
|
export declare function loadEnv(): void;
|
|
21
21
|
export interface AppConfig {
|
|
22
|
+
codexBotTokens: string[];
|
|
23
|
+
antigravityBotTokens: string[];
|
|
22
24
|
tgBotToken: string;
|
|
23
25
|
tgBotTokens: string[];
|
|
24
26
|
tgMultiBotMode: boolean;
|
|
@@ -45,7 +47,6 @@ export interface AppConfig {
|
|
|
45
47
|
opencodeServerStatePath: string;
|
|
46
48
|
opencodeServerLogPath: string;
|
|
47
49
|
antigravityBotToken: string | null;
|
|
48
|
-
antigravityBotTokens: string[];
|
|
49
50
|
antigravityMultiBotMode: boolean;
|
|
50
51
|
antigravityDefaultRuntimeBotToken: string | null;
|
|
51
52
|
antigravityHome: string | null;
|
|
@@ -110,6 +111,7 @@ export interface CodexApiProviderConfig {
|
|
|
110
111
|
}
|
|
111
112
|
export declare function loadConfig(): AppConfig;
|
|
112
113
|
export declare function buildCodexApiProviderOverrides(providers: readonly CodexApiProviderConfig[], defaultProviderId?: string | null): string[];
|
|
114
|
+
export declare function validateCodexBotTokens(tokens: readonly string[]): void;
|
|
113
115
|
export declare function validateOpencodeBotToken(token: string | null, codexTokens: readonly string[]): void;
|
|
114
116
|
export declare function validateAntigravityBotTokens(tokens: readonly string[], codexTokens: readonly string[], opencodeToken: string | null): void;
|
|
115
117
|
export declare function validateAntigravityBotToken(token: string | null, codexTokens: readonly string[], opencodeToken: string | null): void;
|
package/dist/config.js
CHANGED
|
@@ -44,36 +44,35 @@ export function loadEnv() {
|
|
|
44
44
|
}
|
|
45
45
|
export function loadConfig() {
|
|
46
46
|
loadEnv();
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
const tgDefaultRuntimeBotToken = selectDefaultRuntimeBotToken(configuredTokens, legacyToken);
|
|
50
|
-
const tgBotTokens = configuredTokens.length > 0
|
|
51
|
-
? configuredTokens
|
|
52
|
-
: legacyToken
|
|
53
|
-
? [legacyToken]
|
|
54
|
-
: [];
|
|
55
|
-
if (tgBotTokens.length === 0) {
|
|
56
|
-
throw new Error('TG_BOT_TOKENS or TG_BOT_TOKEN is required');
|
|
47
|
+
if (process.env.TG_BOT_TOKEN?.trim() || process.env.TG_BOT_TOKENS?.trim()) {
|
|
48
|
+
throw new Error('Configuration error: TG_BOT_TOKEN and TG_BOT_TOKENS have been removed. Please update .env to use CODEX_BOT_TOKENS and/or ANTIGRAVITY_BOT_TOKENS.');
|
|
57
49
|
}
|
|
50
|
+
const codexBotTokens = parseCommaSeparatedIds(process.env.CODEX_BOT_TOKENS || process.env.CODEX_BOT_TOKEN);
|
|
51
|
+
const antigravityBotTokens = parseCommaSeparatedIds(process.env.ANTIGRAVITY_BOT_TOKENS || process.env.ANTIGRAVITY_BOT_TOKEN);
|
|
58
52
|
const opencodeBotToken = optional('OPENCODE_BOT_TOKEN');
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
53
|
+
if (codexBotTokens.length === 0 && antigravityBotTokens.length === 0) {
|
|
54
|
+
throw new Error('At least one Telegram bot token must be configured via CODEX_BOT_TOKENS or ANTIGRAVITY_BOT_TOKENS');
|
|
55
|
+
}
|
|
56
|
+
validateCodexBotTokens(codexBotTokens);
|
|
57
|
+
validateAntigravityBotTokens(antigravityBotTokens, codexBotTokens, opencodeBotToken);
|
|
58
|
+
validateOpencodeBotToken(opencodeBotToken, [...codexBotTokens, ...antigravityBotTokens]);
|
|
59
|
+
const codexDefaultRuntimeBotToken = selectDefaultRuntimeBotToken(codexBotTokens, optional('CODEX_DEFAULT_RUNTIME_BOT_TOKEN')) || codexBotTokens[0] || null;
|
|
60
|
+
const antigravityDefaultRuntimeBotToken = selectDefaultRuntimeBotToken(antigravityBotTokens, optional('ANTIGRAVITY_DEFAULT_RUNTIME_BOT_TOKEN')) ||
|
|
61
|
+
antigravityBotTokens[0] ||
|
|
62
|
+
null;
|
|
63
|
+
const totalBots = codexBotTokens.length + antigravityBotTokens.length;
|
|
64
|
+
const tgBotTokens = codexBotTokens.length > 0 ? codexBotTokens : antigravityBotTokens;
|
|
65
|
+
const tgBotToken = (codexBotTokens[0] || antigravityBotTokens[0]);
|
|
68
66
|
const antigravityBotToken = antigravityBotTokens[0] || null;
|
|
69
|
-
validateAntigravityBotTokens(antigravityBotTokens, tgBotTokens, opencodeBotToken);
|
|
70
67
|
const config = {
|
|
71
|
-
|
|
68
|
+
codexBotTokens,
|
|
69
|
+
antigravityBotTokens,
|
|
70
|
+
tgBotToken,
|
|
72
71
|
tgBotTokens,
|
|
73
|
-
tgMultiBotMode:
|
|
74
|
-
tgDefaultRuntimeBotToken,
|
|
72
|
+
tgMultiBotMode: totalBots > 1,
|
|
73
|
+
tgDefaultRuntimeBotToken: codexDefaultRuntimeBotToken,
|
|
75
74
|
tgScopeBotId: null,
|
|
76
|
-
tgRequireExplicitGroupAddressing:
|
|
75
|
+
tgRequireExplicitGroupAddressing: totalBots > 1,
|
|
77
76
|
tgAllowedUserId: required('TG_ALLOWED_USER_ID'),
|
|
78
77
|
tgAllowedChatId: optional('TG_ALLOWED_CHAT_ID'),
|
|
79
78
|
tgAllowedTopicId: nullableIntEnv('TG_ALLOWED_TOPIC_ID'),
|
|
@@ -94,8 +93,7 @@ export function loadConfig() {
|
|
|
94
93
|
opencodeServerStatePath: process.env.OPENCODE_SERVER_STATE_PATH || DEFAULT_OPENCODE_SERVER_STATE_PATH,
|
|
95
94
|
opencodeServerLogPath: process.env.OPENCODE_SERVER_LOG_PATH || DEFAULT_OPENCODE_SERVER_LOG_PATH,
|
|
96
95
|
antigravityBotToken,
|
|
97
|
-
antigravityBotTokens,
|
|
98
|
-
antigravityMultiBotMode: configuredAntigravityTokens.length > 0,
|
|
96
|
+
antigravityMultiBotMode: antigravityBotTokens.length > 0,
|
|
99
97
|
antigravityDefaultRuntimeBotToken,
|
|
100
98
|
antigravityHome: process.env.ANTIGRAVITY_HOME?.trim() || null,
|
|
101
99
|
antigravityCliBin: process.env.ANTIGRAVITY_CLI_BIN || resolveCommand('agy') || '/home/wuya/.local/bin/agy',
|
|
@@ -169,11 +167,20 @@ export function buildCodexApiProviderOverrides(providers, defaultProviderId = nu
|
|
|
169
167
|
}
|
|
170
168
|
return overrides;
|
|
171
169
|
}
|
|
170
|
+
export function validateCodexBotTokens(tokens) {
|
|
171
|
+
const seen = new Set();
|
|
172
|
+
for (const token of tokens) {
|
|
173
|
+
if (seen.has(token)) {
|
|
174
|
+
throw new Error(`CODEX_BOT_TOKENS contains duplicate Telegram bot token: ${token}`);
|
|
175
|
+
}
|
|
176
|
+
seen.add(token);
|
|
177
|
+
}
|
|
178
|
+
}
|
|
172
179
|
export function validateOpencodeBotToken(token, codexTokens) {
|
|
173
180
|
if (!token)
|
|
174
181
|
return;
|
|
175
182
|
if (codexTokens.includes(token)) {
|
|
176
|
-
throw new Error('OPENCODE_BOT_TOKEN must use a different bot from
|
|
183
|
+
throw new Error('OPENCODE_BOT_TOKEN must use a different bot from CODEX_BOT_TOKENS');
|
|
177
184
|
}
|
|
178
185
|
}
|
|
179
186
|
export function validateAntigravityBotTokens(tokens, codexTokens, opencodeToken) {
|
|
@@ -186,10 +193,10 @@ export function validateAntigravityBotTokens(tokens, codexTokens, opencodeToken)
|
|
|
186
193
|
}
|
|
187
194
|
seen.add(token);
|
|
188
195
|
if (codexTokens.includes(token)) {
|
|
189
|
-
throw new Error('ANTIGRAVITY_BOT_TOKENS
|
|
196
|
+
throw new Error('ANTIGRAVITY_BOT_TOKENS must use a different bot from CODEX_BOT_TOKENS');
|
|
190
197
|
}
|
|
191
198
|
if (opencodeToken && token === opencodeToken) {
|
|
192
|
-
throw new Error('ANTIGRAVITY_BOT_TOKENS
|
|
199
|
+
throw new Error('ANTIGRAVITY_BOT_TOKENS must use a different bot from OPENCODE_BOT_TOKEN');
|
|
193
200
|
}
|
|
194
201
|
}
|
|
195
202
|
}
|
|
@@ -71,6 +71,7 @@ export declare class BridgeSessionCore {
|
|
|
71
71
|
private pendingForceTakeovers;
|
|
72
72
|
private pendingLoginScopesById;
|
|
73
73
|
private pendingAuthAddsByLoginId;
|
|
74
|
+
private authLoginRecoveryTimers;
|
|
74
75
|
private latestTurnDiffs;
|
|
75
76
|
private threadTokenUsageAlerts;
|
|
76
77
|
private pendingAuthChoiceLists;
|
|
@@ -156,6 +157,9 @@ export declare class BridgeSessionCore {
|
|
|
156
157
|
private handleServerRequestResolved;
|
|
157
158
|
private handleAccountLoginCompleted;
|
|
158
159
|
private restorePendingAuthAdd;
|
|
160
|
+
private scheduleAuthLoginRecovery;
|
|
161
|
+
private recoverAuthLogin;
|
|
162
|
+
private reconcileAuthLoginCredential;
|
|
159
163
|
private restoreAuthAfterAddFailure;
|
|
160
164
|
private handleAccountUpdated;
|
|
161
165
|
private handleRateLimitsUpdated;
|