@foxden-app/foxclaw 0.10.0 → 0.11.1

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.
@@ -10,6 +10,7 @@ import { formatQuotaResetTime, AntigravityAuthManager } from './auth.js';
10
10
  import { AntigravityConversationManager, formatAge } from './conversations.js';
11
11
  import { UnifiedChannelOrchestrator } from '../core/orchestrator.js';
12
12
  import { AntigravityEngineAdapter } from './adapter.js';
13
+ import { AntigravitySubagentTracker } from './subagents.js';
13
14
  import { CodexEngineAdapter } from '../codex_app/adapter.js';
14
15
  import { OpencodeEngineAdapter } from '../opencode/adapter.js';
15
16
  import { BridgeSessionCore } from '../controller/controller.js';
@@ -64,6 +65,7 @@ export class UnifiedBridgeCore {
64
65
  codexCore;
65
66
  opencodeAdapter;
66
67
  defaultBackendId;
68
+ selfUpdater;
67
69
  orchestrator;
68
70
  watchers = new Map();
69
71
  pendingAgyRenames = new Map();
@@ -80,6 +82,7 @@ export class UnifiedBridgeCore {
80
82
  this.messaging = messaging ?? new TelegramMessagingPort(bot);
81
83
  this.codexApp = options?.codexApp;
82
84
  this.defaultBackendId = options?.defaultBackendId ?? 'antigravity';
85
+ this.selfUpdater = options?.selfUpdater;
83
86
  this.codexCore = options?.codexCore;
84
87
  if (!this.codexCore && options?.codexApp) {
85
88
  try {
@@ -288,8 +291,12 @@ export class UnifiedBridgeCore {
288
291
  catch (err) {
289
292
  this.logger.warn('antigravity.restore_watchers_error', { error: String(err) });
290
293
  }
294
+ if (this.selfUpdater) {
295
+ this.scheduleSelfUpdateStatusPoll(500);
296
+ }
291
297
  }
292
298
  async stop() {
299
+ this.clearSelfUpdateStatusPoll();
293
300
  await this.orchestrator.stop();
294
301
  this.auth.stopKeepAlive();
295
302
  for (const [scopeId, watcher] of this.watchers.entries()) {
@@ -299,6 +306,73 @@ export class UnifiedBridgeCore {
299
306
  this.watchers.delete(scopeId);
300
307
  }
301
308
  }
309
+ selfUpdatePollTimer = null;
310
+ scheduleSelfUpdateStatusPoll(delay = 1000) {
311
+ if (!this.selfUpdater || this.selfUpdatePollTimer) {
312
+ return;
313
+ }
314
+ this.selfUpdatePollTimer = setTimeout(() => {
315
+ this.selfUpdatePollTimer = null;
316
+ void this.pollSelfUpdateStatus().catch((error) => {
317
+ this.logger.error('antigravity.self_update_poll_failed', { error: String(error) });
318
+ this.scheduleSelfUpdateStatusPoll();
319
+ });
320
+ }, delay);
321
+ this.selfUpdatePollTimer.unref?.();
322
+ }
323
+ clearSelfUpdateStatusPoll() {
324
+ if (!this.selfUpdatePollTimer)
325
+ return;
326
+ clearTimeout(this.selfUpdatePollTimer);
327
+ this.selfUpdatePollTimer = null;
328
+ }
329
+ ownsScope(scopeId) {
330
+ if (this.bot.identity) {
331
+ return scopeId.includes(`:${this.bot.identity}:`);
332
+ }
333
+ return !scopeId.includes(':bot');
334
+ }
335
+ async pollSelfUpdateStatus() {
336
+ const status = await this.selfUpdater?.readStatus();
337
+ if (!status)
338
+ return;
339
+ if (status.state === 'pending') {
340
+ this.scheduleSelfUpdateStatusPoll();
341
+ return;
342
+ }
343
+ if (status.scopeId.startsWith('cluster:')) {
344
+ await this.selfUpdater?.clearStatus();
345
+ return;
346
+ }
347
+ if (!this.ownsScope(status.scopeId)) {
348
+ this.scheduleSelfUpdateStatusPoll();
349
+ return;
350
+ }
351
+ const isZh = status.locale === 'zh';
352
+ if (status.state === 'succeeded') {
353
+ const notes = status.releaseNotes?.filter((n) => n.trim()) ?? [];
354
+ const title = isZh ? '🎉 FoxClaw 全链路升级完成' : '🎉 FoxClaw Update Completed';
355
+ const lines = [
356
+ `**${title}**`,
357
+ `• FoxClaw: \`${status.fromVersion}\` ➔ \`${status.toVersion ?? '0.11.0'}\``,
358
+ `• Codex CLI: \`${status.codexFromVersion ?? '0.157.1'}\` ➔ \`${status.codexToVersion ?? '0.159.2'}\``,
359
+ `• Antigravity CLI: \`${status.agyFromVersion ?? '1.2.14'}\` (最新)`,
360
+ ];
361
+ if (notes.length > 0) {
362
+ lines.push('', isZh ? '**更新日志**:' : '**Release Notes**:');
363
+ for (const note of notes) {
364
+ lines.push(`• ${note}`);
365
+ }
366
+ }
367
+ lines.push('', isZh ? '服务已自动重载并恢复就绪。' : 'Service reloaded and ready.');
368
+ await this.sendMessage(status.scopeId, lines.join('\n')).catch(() => { });
369
+ }
370
+ else {
371
+ const errorMsg = status.error ?? (isZh ? '未知错误' : 'Unknown error');
372
+ await this.sendMessage(status.scopeId, `❌ **FoxClaw 升级失败**: ${errorMsg}`).catch(() => { });
373
+ }
374
+ await this.selfUpdater?.clearStatus();
375
+ }
302
376
  getRuntimeStatus() {
303
377
  const isConnected = this.codexApp && typeof this.codexApp.isConnected === 'function'
304
378
  ? this.codexApp.isConnected()
@@ -612,6 +686,25 @@ export class UnifiedBridgeCore {
612
686
  const activeBackend = this.orchestrator.getBackendDescriptorForScope(scopeId);
613
687
  const isCodex = activeBackend.engineType === 'codex';
614
688
  if (cmd.toLowerCase() === 'update') {
689
+ if (this.selfUpdater) {
690
+ const status = await this.selfUpdater.readStatus();
691
+ if (status?.state === 'pending') {
692
+ await this.sendMessage(scopeId, locale === 'zh'
693
+ ? '⏳ 升级任务已在后台执行中,请稍候…'
694
+ : '⏳ Self-update is already running, please wait…');
695
+ return true;
696
+ }
697
+ await this.sendMessage(scopeId, locale === 'zh'
698
+ ? '🚀 正在启动全链路自升级(FoxClaw + Codex + Antigravity CLI),服务将自动更新并重载…'
699
+ : '🚀 Starting full-stack self-update (FoxClaw + Codex + Antigravity CLI). Service will restart automatically…');
700
+ try {
701
+ await this.selfUpdater.launch(scopeId, locale);
702
+ }
703
+ catch (err) {
704
+ await this.sendMessage(scopeId, `❌ 自升级启动失败: ${String(err)}`);
705
+ }
706
+ return true;
707
+ }
615
708
  if (this.codexCore) {
616
709
  if (_event) {
617
710
  this.codexCore.dispatchInboundLikeTelegramText(_event);
@@ -858,7 +951,7 @@ export class UnifiedBridgeCore {
858
951
  }
859
952
  if (data.startsWith(AGY_MODEL_CALLBACK_PREFIX)) {
860
953
  const model = data.slice(AGY_MODEL_CALLBACK_PREFIX.length);
861
- this.store.setChatSettings(scopeId, model, null);
954
+ this.store.setChatModel(scopeId, model === 'default' ? null : model);
862
955
  this.orchestrator.syncCurrentBackendSettings(scopeId);
863
956
  await this.messaging.answerCallback(data, `Model: ${model}`);
864
957
  await this.orchestrator.sendModelsMenu(scopeId, locale, messageId);
@@ -870,7 +963,7 @@ export class UnifiedBridgeCore {
870
963
  if (effort !== 'high' && settings?.serviceTier === 'boost') {
871
964
  this.store.setChatServiceTier(scopeId, null);
872
965
  }
873
- this.store.setChatSettings(scopeId, null, effort);
966
+ this.store.setChatEffort(scopeId, effort);
874
967
  this.orchestrator.syncCurrentBackendSettings(scopeId);
875
968
  await this.messaging.answerCallback(data, `Effort: ${effort}`);
876
969
  await this.sendEffortMenu(scopeId, '', locale, messageId);
@@ -1401,6 +1494,7 @@ export class UnifiedBridgeCore {
1401
1494
  messageId: null,
1402
1495
  currentToolLines: [],
1403
1496
  lastContentPreview: '',
1497
+ subagentTracker: new AntigravitySubagentTracker(conv.conversationId, undefined, this.logger),
1404
1498
  };
1405
1499
  this.watchers.set(scopeId, watcher);
1406
1500
  this.store.setWatchedThread(scopeId, conv.conversationId);
@@ -1472,9 +1566,45 @@ export class UnifiedBridgeCore {
1472
1566
  }, 1500);
1473
1567
  watcher.timer?.unref?.();
1474
1568
  }
1569
+ getScopeLocale(scopeId) {
1570
+ return this.store.getChatSettings(scopeId)?.locale || 'zh';
1571
+ }
1572
+ async renderWatcherProgressUpdate(watcher) {
1573
+ const locale = this.getScopeLocale(watcher.scopeId);
1574
+ let messageText = `👁 <b>[Antigravity 观察中]</b>\n\n`;
1575
+ if (watcher.currentToolLines.length > 0) {
1576
+ const recent = watcher.currentToolLines.slice(-8);
1577
+ messageText += `<blockquote expandable>🛠️ <b>主会话工具 (${watcher.currentToolLines.length} 项)</b>\n${recent.join('\n')}</blockquote>\n\n`;
1578
+ }
1579
+ const subBlock = watcher.subagentTracker.renderTelegramBlock(locale);
1580
+ if (subBlock) {
1581
+ messageText += `${subBlock}\n\n`;
1582
+ }
1583
+ const activeSubSummary = watcher.subagentTracker.getActiveSummaryLine(locale);
1584
+ if (activeSubSummary) {
1585
+ messageText += `⏳ <b>${escapeTelegramHtml(activeSubSummary)}</b>`;
1586
+ }
1587
+ else if (watcher.lastContentPreview) {
1588
+ messageText += watcher.lastContentPreview.slice(-3000);
1589
+ }
1590
+ else {
1591
+ messageText += `⏳ 正在执行中…`;
1592
+ }
1593
+ if (watcher.messageId) {
1594
+ await this.editMessage(watcher.scopeId, watcher.messageId, messageText).catch(() => { });
1595
+ }
1596
+ else {
1597
+ watcher.messageId = await this.sendMessage(watcher.scopeId, messageText).catch(() => null);
1598
+ }
1599
+ }
1475
1600
  async pollWatcher(watcher) {
1476
1601
  if (watcher.stopped)
1477
1602
  return;
1603
+ const currentBinding = this.store.getBinding(watcher.scopeId);
1604
+ if (currentBinding?.threadId && currentBinding.threadId !== watcher.conversationId) {
1605
+ await this.unwatchConversation(watcher.scopeId, this.getScopeLocale(watcher.scopeId), false);
1606
+ return;
1607
+ }
1478
1608
  if (this.orchestrator.hasActiveTurn(watcher.scopeId)) {
1479
1609
  try {
1480
1610
  if (fs.existsSync(watcher.transcriptPath)) {
@@ -1487,31 +1617,32 @@ export class UnifiedBridgeCore {
1487
1617
  return;
1488
1618
  }
1489
1619
  try {
1490
- if (!fs.existsSync(watcher.transcriptPath)) {
1491
- return;
1492
- }
1493
- const stats = fs.statSync(watcher.transcriptPath);
1494
- if (stats.size < watcher.fileOffset) {
1495
- watcher.fileOffset = 0;
1496
- watcher.remainder = '';
1497
- return;
1498
- }
1499
- if (stats.size === watcher.fileOffset) {
1500
- return;
1501
- }
1502
- const bytesToRead = stats.size - watcher.fileOffset;
1503
- const buffer = Buffer.alloc(bytesToRead);
1504
- const fd = fs.openSync(watcher.transcriptPath, 'r');
1505
- try {
1506
- fs.readSync(fd, buffer, 0, bytesToRead, watcher.fileOffset);
1507
- }
1508
- finally {
1509
- fs.closeSync(fd);
1620
+ let hasParentLines = false;
1621
+ const lines = [];
1622
+ if (fs.existsSync(watcher.transcriptPath)) {
1623
+ const stats = fs.statSync(watcher.transcriptPath);
1624
+ if (stats.size < watcher.fileOffset) {
1625
+ watcher.fileOffset = 0;
1626
+ watcher.remainder = '';
1627
+ }
1628
+ else if (stats.size > watcher.fileOffset) {
1629
+ const bytesToRead = stats.size - watcher.fileOffset;
1630
+ const buffer = Buffer.alloc(bytesToRead);
1631
+ const fd = fs.openSync(watcher.transcriptPath, 'r');
1632
+ try {
1633
+ fs.readSync(fd, buffer, 0, bytesToRead, watcher.fileOffset);
1634
+ }
1635
+ finally {
1636
+ fs.closeSync(fd);
1637
+ }
1638
+ watcher.fileOffset = stats.size;
1639
+ const chunk = watcher.remainder + buffer.toString('utf8');
1640
+ const splitLines = chunk.split('\n');
1641
+ watcher.remainder = splitLines.pop() ?? '';
1642
+ lines.push(...splitLines);
1643
+ hasParentLines = splitLines.length > 0;
1644
+ }
1510
1645
  }
1511
- watcher.fileOffset = stats.size;
1512
- const chunk = watcher.remainder + buffer.toString('utf8');
1513
- const lines = chunk.split('\n');
1514
- watcher.remainder = lines.pop() ?? '';
1515
1646
  for (const line of lines) {
1516
1647
  const trimmed = line.trim();
1517
1648
  if (!trimmed)
@@ -1525,12 +1656,19 @@ export class UnifiedBridgeCore {
1525
1656
  }
1526
1657
  await this.handleWatcherTranscriptEntry(watcher, entry);
1527
1658
  }
1659
+ // Poll subagents
1660
+ const subResult = await watcher.subagentTracker.poll();
1661
+ // If subagents had new tool events or status updates while the parent has no new lines
1662
+ if (subResult.hasUpdates && !hasParentLines) {
1663
+ await this.renderWatcherProgressUpdate(watcher);
1664
+ }
1528
1665
  }
1529
1666
  catch (err) {
1530
1667
  this.logger.debug('antigravity.poll_watcher_error', { error: String(err) });
1531
1668
  }
1532
1669
  }
1533
1670
  async handleWatcherTranscriptEntry(watcher, entry) {
1671
+ const locale = this.getScopeLocale(watcher.scopeId);
1534
1672
  if (entry.type === 'USER_INPUT') {
1535
1673
  const text = typeof entry.content === 'string' ? entry.content : '';
1536
1674
  const preview = text.length > 300 ? text.slice(0, 300) + '…' : text;
@@ -1540,6 +1678,16 @@ export class UnifiedBridgeCore {
1540
1678
  watcher.lastContentPreview = '';
1541
1679
  return;
1542
1680
  }
1681
+ if (entry.type === 'GENERIC') {
1682
+ const content = typeof entry.content === 'string' ? entry.content : '';
1683
+ if (content.includes('Created the following subagents') || content.includes('conversationId')) {
1684
+ const subResult = await watcher.subagentTracker.poll();
1685
+ if (subResult.hasUpdates) {
1686
+ await this.renderWatcherProgressUpdate(watcher);
1687
+ }
1688
+ }
1689
+ return;
1690
+ }
1543
1691
  if (entry.type === 'PLANNER_RESPONSE') {
1544
1692
  let hasUpdate = false;
1545
1693
  if (Array.isArray(entry.tool_calls) && entry.tool_calls.length > 0) {
@@ -1573,6 +1721,10 @@ export class UnifiedBridgeCore {
1573
1721
  : '';
1574
1722
  progressFinal += `\n\n<blockquote expandable>🛠️ <b>已调用工具 (${watcher.currentToolLines.length} 项)</b>\n${recent.join('\n')}${hiddenNote}</blockquote>`;
1575
1723
  }
1724
+ const subBlock = watcher.subagentTracker.renderTelegramBlock(locale);
1725
+ if (subBlock) {
1726
+ progressFinal += `\n\n<blockquote expandable>${subBlock}</blockquote>`;
1727
+ }
1576
1728
  await this.editMessage(watcher.scopeId, watcher.messageId, progressFinal).catch(() => { });
1577
1729
  }
1578
1730
  const chunks = chunkTelegramMessage(entry.content.trim(), 4000);
@@ -1582,6 +1734,7 @@ export class UnifiedBridgeCore {
1582
1734
  watcher.messageId = null;
1583
1735
  watcher.currentToolLines = [];
1584
1736
  watcher.lastContentPreview = '';
1737
+ watcher.subagentTracker = new AntigravitySubagentTracker(watcher.conversationId, undefined, this.logger);
1585
1738
  return;
1586
1739
  }
1587
1740
  if (entry.content) {
@@ -1594,7 +1747,15 @@ export class UnifiedBridgeCore {
1594
1747
  const recent = watcher.currentToolLines.slice(-8);
1595
1748
  messageText += `<blockquote expandable>🛠️ <b>已调用工具 (${watcher.currentToolLines.length} 项)</b>\n${recent.join('\n')}</blockquote>\n\n`;
1596
1749
  }
1597
- if (watcher.lastContentPreview) {
1750
+ const subBlock = watcher.subagentTracker.renderTelegramBlock(locale);
1751
+ if (subBlock) {
1752
+ messageText += `${subBlock}\n\n`;
1753
+ }
1754
+ const activeSubSummary = watcher.subagentTracker.getActiveSummaryLine(locale);
1755
+ if (activeSubSummary) {
1756
+ messageText += `⏳ <b>${escapeTelegramHtml(activeSubSummary)}</b>`;
1757
+ }
1758
+ else if (watcher.lastContentPreview) {
1598
1759
  messageText += watcher.lastContentPreview.slice(-3000);
1599
1760
  }
1600
1761
  else {
@@ -1877,7 +2038,7 @@ export class UnifiedBridgeCore {
1877
2038
  if (target !== 'high' && isBoost) {
1878
2039
  this.store.setChatServiceTier(scopeId, null);
1879
2040
  }
1880
- this.store.setChatSettings(scopeId, null, target);
2041
+ this.store.setChatEffort(scopeId, target);
1881
2042
  this.orchestrator.syncCurrentBackendSettings(scopeId);
1882
2043
  await this.sendMessage(scopeId, locale === 'zh' ? `✅ 思考深度已切换为: \`${target}\`` : `✅ Effort switched to: \`${target}\``);
1883
2044
  return;
@@ -90,6 +90,7 @@ export type AntigravityBridgeEvent = AgInitBridgeEvent | AgTextEvent | AgToolEve
90
90
  export declare function isVerificationError(errorMessage: string): boolean;
91
91
  export declare function isQuotaOrAuthError(errorMessage: string): boolean;
92
92
  export declare function isCapacityOrUnavailableError(errorMessage: string): boolean;
93
+ export declare function isTransientNetworkError(errorMessage: string): boolean;
93
94
  /**
94
95
  * Normalizes raw NDJSON stream events from `agy` into clean bridge events.
95
96
  */
@@ -28,6 +28,35 @@ export function isCapacityOrUnavailableError(errorMessage) {
28
28
  lower.includes('overloaded') ||
29
29
  lower.includes('high load'));
30
30
  }
31
+ export function isTransientNetworkError(errorMessage) {
32
+ if (!errorMessage)
33
+ return false;
34
+ const lower = errorMessage.toLowerCase();
35
+ return (lower.includes('client network socket disconnected') ||
36
+ lower.includes('connection timed out') ||
37
+ lower.includes('timed out') ||
38
+ lower.includes('timeout') ||
39
+ lower.includes('econnreset') ||
40
+ lower.includes('etimedout') ||
41
+ lower.includes('enotfound') ||
42
+ lower.includes('eai_again') ||
43
+ lower.includes('econnrefused') ||
44
+ lower.includes('socket hang up') ||
45
+ lower.includes('network socket disconnected') ||
46
+ lower.includes('retryable') ||
47
+ lower.includes('grpc') ||
48
+ lower.includes('network error') ||
49
+ lower.includes('fetch failed') ||
50
+ lower.includes('502 bad gateway') ||
51
+ lower.includes('504 gateway timeout') ||
52
+ lower.includes('proxy error') ||
53
+ lower.includes('read tcp') ||
54
+ lower.includes('und_err_') ||
55
+ lower.includes('broken pipe') ||
56
+ lower.includes('ehostunreach') ||
57
+ lower.includes('enetunreach') ||
58
+ lower.includes('exited unexpectedly'));
59
+ }
31
60
  /**
32
61
  * Normalizes raw NDJSON stream events from `agy` into clean bridge events.
33
62
  */
@@ -6,6 +6,7 @@ import { AntigravityAppClient } from './client.js';
6
6
  import { AntigravityAuthManager } from './auth.js';
7
7
  import { AntigravityBridgeCore } from './controller.js';
8
8
  import type { CodexAppClient } from '../codex_app/client.js';
9
+ import type { SelfUpdateRuntime } from '../update.js';
9
10
  export interface AntigravityRuntimeOptions {
10
11
  botToken?: string | undefined;
11
12
  botId?: string | undefined;
@@ -16,6 +17,7 @@ export interface AntigravityRuntimeOptions {
16
17
  codexApp?: CodexAppClient | undefined;
17
18
  app?: AntigravityAppClient | undefined;
18
19
  auth?: AntigravityAuthManager | undefined;
20
+ selfUpdater?: SelfUpdateRuntime | undefined;
19
21
  }
20
22
  /** Keeps the optional Antigravity Telegram bot lifecycle decoupled from Codex and OpenCode runtimes. */
21
23
  export declare class AntigravityTelegramRuntime {
@@ -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
+ }