@foxden-app/foxclaw 0.5.57 → 0.5.58

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 CHANGED
@@ -81,7 +81,7 @@ CODEX_CLI_BIN=/absolute/path/to/codex
81
81
  # VOICE_SUMMARY_BUTTON_ENABLED=true
82
82
  # VOICE_SUMMARY_TEXT_LIMIT=180
83
83
  # VOICE_TEXT_LIMIT=2800
84
- # VOICE_TTS_TIMEOUT_MS=60000
84
+ # VOICE_TTS_TIMEOUT_MS=300000
85
85
 
86
86
  # Optional: standard HTTP(S) proxy for Telegram and ChatGPT/Codex backend requests.
87
87
  # Put these in the same env file that `foxclaw start` installs into systemd/launchd.
package/CHANGELOG.md CHANGED
@@ -2,6 +2,16 @@
2
2
 
3
3
  All notable FoxClaw changes are listed here. Each release note is bilingual so GitHub Releases and the npm package are useful to both Chinese and English readers.
4
4
 
5
+ ## 0.5.58 - 2026-06-21
6
+
7
+ ### 中文
8
+ - 修复重启恢复中的操作明细清理:FoxClaw 现在会把已折叠工具明细的 Telegram message id 持久化到 active turn preview,重启后最终答复完成时仍能按配置删除这些明细消息。
9
+ - 将 `VOICE_TTS_TIMEOUT_MS` 默认值和示例配置改为 300000ms,适配 SoulX 等较慢语音后端;本机配置也已同步为 5 分钟。
10
+
11
+ ### English
12
+ - Fixed operation-detail cleanup after restart recovery. FoxClaw now persists archived tool-detail Telegram message ids with active turn previews, so final-answer cleanup can still delete them after a service restart.
13
+ - Changed the default and example `VOICE_TTS_TIMEOUT_MS` to 300000ms for slower voice backends such as SoulX; the local deployment config has been updated to five minutes as well.
14
+
5
15
  ## 0.5.57 - 2026-06-21
6
16
 
7
17
  ### 中文
package/dist/config.js CHANGED
@@ -110,7 +110,7 @@ export function loadConfig() {
110
110
  voiceSummaryButtonEnabled: boolEnv('VOICE_SUMMARY_BUTTON_ENABLED', true),
111
111
  voiceSummaryTextLimit: intEnv('VOICE_SUMMARY_TEXT_LIMIT', 180),
112
112
  voiceTextLimit: intEnv('VOICE_TEXT_LIMIT', 2800),
113
- voiceTtsTimeoutMs: intEnv('VOICE_TTS_TIMEOUT_MS', 60_000),
113
+ voiceTtsTimeoutMs: intEnv('VOICE_TTS_TIMEOUT_MS', 300_000),
114
114
  };
115
115
  ensureAppDirs(config);
116
116
  return config;
@@ -2909,8 +2909,9 @@ export class BridgeSessionCore {
2909
2909
  const messageId = await this.sendMessage(record.scopeId, text, keyboard);
2910
2910
  this.store.updatePendingAttachmentBatchMessage(record.batchId, messageId);
2911
2911
  }
2912
- async registerActiveTurn(scopeId, chatId, chatType, topicId, threadId, turnId, previewMessageId, authRetry = null, collaborationMode = DEFAULT_COLLABORATION_MODE, queuedInputId = null) {
2912
+ async registerActiveTurn(scopeId, chatId, chatType, topicId, threadId, turnId, previewMessageId, authRetry = null, collaborationMode = DEFAULT_COLLABORATION_MODE, queuedInputId = null, archivedMessageIds = []) {
2913
2913
  const active = this.createActiveTurnState(scopeId, chatId, chatType, topicId, threadId, turnId, previewMessageId, false, collaborationMode, queuedInputId);
2914
+ active.archivedMessageIds = [...archivedMessageIds];
2914
2915
  active.authRetry = authRetry;
2915
2916
  this.setActiveTurn(scopeId, turnId, active);
2916
2917
  const pendingError = this.pendingTurnErrors.get(turnId);
@@ -2925,6 +2926,7 @@ export class BridgeSessionCore {
2925
2926
  threadId,
2926
2927
  messageId: previewMessageId,
2927
2928
  isObserved: active.isObserved,
2929
+ archivedMessageIds: active.archivedMessageIds,
2928
2930
  });
2929
2931
  }
2930
2932
  this.updateStatus();
@@ -7758,6 +7760,7 @@ export class BridgeSessionCore {
7758
7760
  async attachRecoveredTurnPreview(preview, target, snapshot, liveTurn) {
7759
7761
  await this.stopWatchingScopeThread(preview.scopeId, preview.threadId);
7760
7762
  const active = this.createActiveTurnState(preview.scopeId, target.chatId, target.chatType, target.topicId, preview.threadId, liveTurn.turnId, preview.messageId, preview.isObserved);
7763
+ active.archivedMessageIds = [...preview.archivedMessageIds];
7761
7764
  this.setActiveTurn(preview.scopeId, liveTurn.turnId, active);
7762
7765
  this.store.saveActiveTurnPreview({
7763
7766
  turnId: liveTurn.turnId,
@@ -7765,6 +7768,7 @@ export class BridgeSessionCore {
7765
7768
  threadId: preview.threadId,
7766
7769
  messageId: preview.messageId,
7767
7770
  isObserved: preview.isObserved,
7771
+ archivedMessageIds: active.archivedMessageIds,
7768
7772
  });
7769
7773
  const watcher = {
7770
7774
  scopeId: preview.scopeId,
@@ -7833,7 +7837,7 @@ export class BridgeSessionCore {
7833
7837
  topicId: target.topicId,
7834
7838
  collaborationMode: turnState.collaborationMode,
7835
7839
  failedAuthTargets: new Set(),
7836
- }, turnState.collaborationMode);
7840
+ }, turnState.collaborationMode, null, preview.archivedMessageIds);
7837
7841
  this.logger.info('telegram.preview_auto_resumed_after_restart', {
7838
7842
  scopeId: preview.scopeId,
7839
7843
  threadId: preview.threadId,
@@ -8042,6 +8046,7 @@ export class BridgeSessionCore {
8042
8046
  threadId: active.threadId,
8043
8047
  messageId,
8044
8048
  isObserved: active.isObserved,
8049
+ archivedMessageIds: active.archivedMessageIds,
8045
8050
  });
8046
8051
  }
8047
8052
  catch (error) {
@@ -5,6 +5,7 @@ export interface ActiveTurnPreviewRecord {
5
5
  threadId: string;
6
6
  messageId: number;
7
7
  isObserved: boolean;
8
+ archivedMessageIds: number[];
8
9
  createdAt: number;
9
10
  updatedAt: number;
10
11
  }
@@ -82,6 +83,7 @@ export declare class BridgeStore {
82
83
  countPendingApprovals(): number;
83
84
  saveActiveTurnPreview(record: Pick<ActiveTurnPreviewRecord, 'turnId' | 'scopeId' | 'threadId' | 'messageId'> & {
84
85
  isObserved?: boolean;
86
+ archivedMessageIds?: number[];
85
87
  }): void;
86
88
  listActiveTurnPreviews(): ActiveTurnPreviewRecord[];
87
89
  removeActiveTurnPreview(turnId: string): void;
@@ -71,6 +71,7 @@ export class BridgeStore {
71
71
  thread_id TEXT NOT NULL,
72
72
  message_id INTEGER NOT NULL,
73
73
  is_observed INTEGER NOT NULL DEFAULT 0,
74
+ archived_message_ids TEXT NOT NULL DEFAULT '[]',
74
75
  created_at INTEGER NOT NULL,
75
76
  updated_at INTEGER NOT NULL
76
77
  );
@@ -219,6 +220,7 @@ export class BridgeStore {
219
220
  this.ensureColumn('pending_user_inputs', 'status', "TEXT NOT NULL DEFAULT 'pending'");
220
221
  this.ensureColumn('pending_user_inputs', 'submitted_at', 'INTEGER');
221
222
  this.ensureColumn('active_turn_previews', 'is_observed', 'INTEGER NOT NULL DEFAULT 0');
223
+ this.ensureColumn('active_turn_previews', 'archived_message_ids', "TEXT NOT NULL DEFAULT '[]'");
222
224
  this.ensureColumn('codex_auth_candidates', 'state', "TEXT NOT NULL DEFAULT 'active'");
223
225
  this.ensureColumn('codex_auth_candidate_runtime', 'state', "TEXT NOT NULL DEFAULT 'active'");
224
226
  this.ensureColumn('codex_auth_quota_snapshots', 'plan_type', 'TEXT');
@@ -426,13 +428,13 @@ export class BridgeStore {
426
428
  const now = Date.now();
427
429
  this.db.prepare('DELETE FROM active_turn_previews WHERE turn_id = ? OR scope_id = ?').run(record.turnId, record.scopeId);
428
430
  this.db.prepare(`
429
- INSERT INTO active_turn_previews (turn_id, scope_id, thread_id, message_id, is_observed, created_at, updated_at)
430
- VALUES (?, ?, ?, ?, ?, ?, ?)
431
- `).run(record.turnId, record.scopeId, record.threadId, record.messageId, record.isObserved ? 1 : 0, now, now);
431
+ INSERT INTO active_turn_previews (turn_id, scope_id, thread_id, message_id, is_observed, archived_message_ids, created_at, updated_at)
432
+ VALUES (?, ?, ?, ?, ?, ?, ?, ?)
433
+ `).run(record.turnId, record.scopeId, record.threadId, record.messageId, record.isObserved ? 1 : 0, JSON.stringify(normalizeMessageIds(record.archivedMessageIds ?? [])), now, now);
432
434
  }
433
435
  listActiveTurnPreviews() {
434
436
  const rows = this.db.prepare(`
435
- SELECT turn_id, scope_id, thread_id, message_id, is_observed, created_at, updated_at
437
+ SELECT turn_id, scope_id, thread_id, message_id, is_observed, archived_message_ids, created_at, updated_at
436
438
  FROM active_turn_previews
437
439
  ORDER BY created_at ASC
438
440
  `).all();
@@ -442,6 +444,7 @@ export class BridgeStore {
442
444
  threadId: String(row.thread_id),
443
445
  messageId: Number(row.message_id),
444
446
  isObserved: Boolean(row.is_observed),
447
+ archivedMessageIds: parseMessageIdArray(row.archived_message_ids),
445
448
  createdAt: Number(row.created_at),
446
449
  updatedAt: Number(row.updated_at),
447
450
  }));
@@ -1065,6 +1068,23 @@ function nullableNumber(value) {
1065
1068
  function nullableString(value) {
1066
1069
  return typeof value === 'string' && value.trim() ? value : null;
1067
1070
  }
1071
+ function normalizeMessageIds(values) {
1072
+ return [...new Set(values.filter((value) => Number.isSafeInteger(value) && value > 0))];
1073
+ }
1074
+ function parseMessageIdArray(value) {
1075
+ if (typeof value !== 'string' || !value.trim()) {
1076
+ return [];
1077
+ }
1078
+ try {
1079
+ const parsed = JSON.parse(value);
1080
+ return Array.isArray(parsed)
1081
+ ? normalizeMessageIds(parsed.map((entry) => Number(entry)))
1082
+ : [];
1083
+ }
1084
+ catch {
1085
+ return [];
1086
+ }
1087
+ }
1068
1088
  function normalizeCodexAuthCandidateState(value) {
1069
1089
  return value === 'needs_repair' ? 'needs_repair' : 'active';
1070
1090
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@foxden-app/foxclaw",
3
- "version": "0.5.57",
3
+ "version": "0.5.58",
4
4
  "description": "Foxden local execution claw for controlling Codex from trusted chat interfaces.",
5
5
  "type": "module",
6
6
  "main": "dist/main.js",