@foxden-app/foxclaw 0.5.76 → 0.5.78

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/CHANGELOG.md CHANGED
@@ -2,6 +2,28 @@
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.78 - 2026-07-12
6
+
7
+ ### 中文
8
+ - `/auth` 面板收敛为“设备登录”和“安全同步”两个维护入口,移除权限、重载 auth 和单独的全节点审计按钮。“安全同步”现在直接执行完整的全节点验证、最新有效副本协商、8 天临期刷新和最终分发;`/auth sync audit` 继续作为兼容别名。
9
+ - 全节点审计现在也覆盖只存在于单个节点的旧候选:通过现有 AES-256-GCM 通道发送给其他节点做只验证、不导入的独立复核。任一节点验证有效就恢复并分发;没有有效结果且至少两个节点确认无效才标记 `?`。
10
+ - Telegram `/status` 新增当前 bot 实际使用的 Codex Home,并与 `/auth`、`/threads` 等时效面板一样按 `TELEGRAM_PANEL_TTL_MS` 自动撤回,默认 5 分钟。
11
+ - 完整支持 Codex 新增的 `max`、`ultra` 推理强度,包括模型能力读取、命令解析、设置持久化和下一轮请求透传,不再提示“未知推理强度”。
12
+
13
+ ### English
14
+ - Simplified the `/auth` panel to Device login and Safe sync, removing the Permissions, Reload auth, and separate cluster-audit buttons. Safe sync now runs the complete all-node validation, newest-valid-copy reconciliation, 8-day stale refresh, and final distribution flow; `/auth sync audit` remains a compatibility alias.
15
+ - All-node audits now cover legacy candidates found on only one node. The candidate is sent through the existing AES-256-GCM channel for independent validation without import. Any valid result restores and distributes it; it is marked `?` only when no valid result exists and at least two nodes reject it.
16
+ - Telegram `/status` now shows the effective Codex Home for the current bot and expires through `TELEGRAM_PANEL_TTL_MS`, like `/auth` and `/threads`, with a five-minute default.
17
+ - Added end-to-end support for the new Codex `max` and `ultra` reasoning efforts across model capability parsing, commands, persisted settings, and turn requests.
18
+
19
+ ## 0.5.77 - 2026-07-11
20
+
21
+ ### 中文
22
+ - 修复跨节点升级广播触发的后台升级完成后,旧服务把内部 `cluster:*` scope 当成 Telegram scope 解析并反复记录 `Expected telegram: scope` 的问题。集群升级现在只记录完成状态并清理轮询文件,不再尝试向不存在的聊天发送回报。
23
+
24
+ ### English
25
+ - Fixed cluster-broadcast updates repeatedly logging `Expected telegram: scope` after completion because the old service tried to parse the internal `cluster:*` scope as a Telegram destination. Cluster updates now record completion and clear the poll state without trying to send a chat reply to a non-chat scope.
26
+
5
27
  ## 0.5.76 - 2026-07-11
6
28
 
7
29
  ### 中文
@@ -299,6 +299,7 @@ export declare class CrossNodeAuthSync {
299
299
  private readonly pendingLeases;
300
300
  private readonly pendingTests;
301
301
  private readonly pendingAudits;
302
+ private readonly pendingAuditVerifications;
302
303
  private seenNonces;
303
304
  private lastPeerActivityAt;
304
305
  private timer;
@@ -338,11 +339,17 @@ export declare class CrossNodeAuthSync {
338
339
  private handleMessage;
339
340
  private handleAuditRequest;
340
341
  private handleAuditResponse;
342
+ private handleAuditVerificationRequest;
343
+ private handleAuditVerificationResponse;
341
344
  private handleAuditApply;
342
345
  private applyAuditState;
343
346
  private buildAuditReport;
347
+ private verifySingleNodeInvalidCandidates;
348
+ private verifyBundlesIntoAuditReport;
349
+ private validateAuditBundle;
344
350
  private reconcileAuditReports;
345
351
  private finishPendingAudit;
352
+ private finishPendingAuditVerification;
346
353
  private handleServiceUpdateRequest;
347
354
  private handlePullRequest;
348
355
  private handlePullResponse;
@@ -27,6 +27,7 @@ export class CrossNodeAuthSync {
27
27
  pendingLeases = new Map();
28
28
  pendingTests = new Map();
29
29
  pendingAudits = new Map();
30
+ pendingAuditVerifications = new Map();
30
31
  seenNonces = new Map();
31
32
  lastPeerActivityAt = new Map();
32
33
  timer = null;
@@ -523,6 +524,7 @@ export class CrossNodeAuthSync {
523
524
  }
524
525
  const [localReport, remote] = await Promise.all([localReportPromise, remoteReportsPromise]);
525
526
  const reports = [localReport, ...remote.reports.values()];
527
+ await this.verifySingleNodeInvalidCandidates(requestId, reports);
526
528
  const result = await this.reconcileAuditReports(requestId, reports, remote.missing);
527
529
  this.recordEvent({
528
530
  direction: 'local',
@@ -641,6 +643,12 @@ export class CrossNodeAuthSync {
641
643
  case 'audit.response':
642
644
  this.handleAuditResponse(message, normalizePeerIdentity(peer));
643
645
  return;
646
+ case 'audit.verify.request':
647
+ await this.handleAuditVerificationRequest(message, normalizePeerIdentity(peer));
648
+ return;
649
+ case 'audit.verify.response':
650
+ this.handleAuditVerificationResponse(message, normalizePeerIdentity(peer));
651
+ return;
644
652
  case 'audit.apply':
645
653
  await this.handleAuditApply(message, senderNodeId, sourceLabel, normalizePeerIdentity(peer));
646
654
  return;
@@ -686,6 +694,62 @@ export class CrossNodeAuthSync {
686
694
  this.finishPendingAudit(message.requestId);
687
695
  }
688
696
  }
697
+ async handleAuditVerificationRequest(message, peer) {
698
+ let report;
699
+ if (!this.callbacks.isIdle()) {
700
+ report = { nodeId: this.nodeId ?? 'local', status: 'busy', records: [], reason: 'runtime is not idle' };
701
+ }
702
+ else {
703
+ const records = [];
704
+ const bundles = Array.isArray(message.bundles)
705
+ ? message.bundles.filter(bundle => isValidBundle(bundle) && sha256(bundle.rawAuth) === bundle.authSha256)
706
+ : [];
707
+ for (const bundle of bundles) {
708
+ const validation = await this.validateAuditBundle(bundle);
709
+ records.push({
710
+ candidateName: bundle.candidateName,
711
+ authSha256: bundle.authSha256,
712
+ status: validation.ok ? 'valid' : 'invalid',
713
+ reason: validation.ok ? null : validation.reason ?? 'usage validation failed',
714
+ });
715
+ }
716
+ report = { nodeId: this.nodeId ?? 'local', status: 'completed', records, reason: null };
717
+ }
718
+ await this.sendToPeer(peer, {
719
+ kind: 'audit.verify.response',
720
+ requestId: message.requestId,
721
+ verificationId: message.verificationId,
722
+ report,
723
+ });
724
+ this.recordEvent({
725
+ direction: 'local',
726
+ kind: 'audit.verify.response',
727
+ stage: 'sent',
728
+ peer,
729
+ requestId: message.requestId,
730
+ candidateName: null,
731
+ detail: `${report.status}; records=${report.records.length}`,
732
+ });
733
+ }
734
+ handleAuditVerificationResponse(message, peer) {
735
+ const pending = this.pendingAuditVerifications.get(message.verificationId);
736
+ if (!pending || pending.finished || !isValidAuditVerificationReport(message.report))
737
+ return;
738
+ const matchedPeer = this.matchConfiguredPeer(peer) ?? peer;
739
+ pending.reports.set(matchedPeer, message.report);
740
+ this.recordEvent({
741
+ direction: 'local',
742
+ kind: 'audit.verify.response',
743
+ stage: 'received',
744
+ peer: matchedPeer,
745
+ requestId: message.requestId,
746
+ candidateName: null,
747
+ detail: `${message.report.status}; records=${message.report.records.length}; node=${message.report.nodeId}`,
748
+ });
749
+ if (pending.peers.every(peerName => pending.reports.has(peerName))) {
750
+ this.finishPendingAuditVerification(message.verificationId);
751
+ }
752
+ }
689
753
  async handleAuditApply(message, senderNodeId, sourceLabel, peer) {
690
754
  const outcome = await this.validateAndImport({ ...message.bundle, requestId: message.requestId }, senderNodeId, sourceLabel, peer, 'push', message.replaceExisting);
691
755
  if (!outcome.ok)
@@ -747,7 +811,7 @@ export class CrossNodeAuthSync {
747
811
  lastRefreshMs: candidate.lastRefreshMs,
748
812
  status: validation.ok ? 'valid' : 'invalid',
749
813
  reason: validation.ok ? null : validation.reason ?? 'usage validation failed',
750
- bundle: validation.ok ? bundle : null,
814
+ bundle,
751
815
  });
752
816
  }
753
817
  return {
@@ -757,6 +821,107 @@ export class CrossNodeAuthSync {
757
821
  reason: null,
758
822
  };
759
823
  }
824
+ async verifySingleNodeInvalidCandidates(requestId, reports) {
825
+ const completedReports = reports.filter(report => report.status === 'completed');
826
+ const entries = new Map();
827
+ for (const report of completedReports) {
828
+ for (const record of report.records) {
829
+ const candidateEntries = entries.get(record.candidateName) ?? [];
830
+ candidateEntries.push({ report, record });
831
+ entries.set(record.candidateName, candidateEntries);
832
+ }
833
+ }
834
+ const bundles = [];
835
+ for (const candidateEntries of entries.values()) {
836
+ if (candidateEntries.some(entry => entry.record.status === 'valid'))
837
+ continue;
838
+ const invalidNodes = new Set(candidateEntries.map(entry => entry.report.nodeId));
839
+ if (invalidNodes.size >= 2)
840
+ continue;
841
+ const latest = candidateEntries
842
+ .filter(entry => entry.record.bundle !== null)
843
+ .sort((left, right) => right.record.lastRefreshMs - left.record.lastRefreshMs)[0];
844
+ if (latest?.record.bundle)
845
+ bundles.push(latest.record.bundle);
846
+ }
847
+ if (bundles.length === 0)
848
+ return;
849
+ const localReport = completedReports.find(report => report.nodeId === this.nodeId);
850
+ if (localReport) {
851
+ await this.verifyBundlesIntoAuditReport(localReport, bundles);
852
+ }
853
+ if (!this.isReady())
854
+ return;
855
+ const verificationId = crypto.randomUUID();
856
+ const remoteReports = await new Promise((resolve) => {
857
+ const timer = setTimeout(() => this.finishPendingAuditVerification(verificationId), AUDIT_TIMEOUT_MS);
858
+ timer.unref();
859
+ this.pendingAuditVerifications.set(verificationId, {
860
+ peers: [...this.peers],
861
+ reports: new Map(),
862
+ resolve,
863
+ timer,
864
+ finished: false,
865
+ });
866
+ void this.sendToAll({ kind: 'audit.verify.request', requestId, verificationId, bundles }).catch(() => {
867
+ this.finishPendingAuditVerification(verificationId);
868
+ });
869
+ });
870
+ const bundlesByHash = new Map(bundles.map(bundle => [bundle.authSha256, bundle]));
871
+ for (const verification of remoteReports.values()) {
872
+ if (verification.status !== 'completed')
873
+ continue;
874
+ let target = completedReports.find(report => report.nodeId === verification.nodeId);
875
+ if (!target) {
876
+ target = { nodeId: verification.nodeId, status: 'completed', records: [], reason: null };
877
+ reports.push(target);
878
+ completedReports.push(target);
879
+ }
880
+ for (const record of verification.records) {
881
+ const bundle = bundlesByHash.get(record.authSha256);
882
+ if (!bundle || bundle.candidateName !== record.candidateName)
883
+ continue;
884
+ if (record.status === 'valid') {
885
+ target.records = target.records.filter(existing => existing.candidateName !== record.candidateName);
886
+ }
887
+ else if (target.records.some(existing => existing.candidateName === record.candidateName)) {
888
+ continue;
889
+ }
890
+ target.records.push({
891
+ candidateName: bundle.candidateName,
892
+ accountId: bundle.accountId,
893
+ quotaIdentityId: bundle.quotaIdentityId ?? null,
894
+ lastRefreshMs: bundle.lastRefreshMs,
895
+ status: record.status,
896
+ reason: record.reason,
897
+ bundle,
898
+ });
899
+ }
900
+ }
901
+ }
902
+ async verifyBundlesIntoAuditReport(report, bundles) {
903
+ for (const bundle of bundles) {
904
+ if (report.records.some(record => record.candidateName === bundle.candidateName))
905
+ continue;
906
+ const validation = await this.validateAuditBundle(bundle);
907
+ report.records.push({
908
+ candidateName: bundle.candidateName,
909
+ accountId: bundle.accountId,
910
+ quotaIdentityId: bundle.quotaIdentityId ?? null,
911
+ lastRefreshMs: bundle.lastRefreshMs,
912
+ status: validation.ok ? 'valid' : 'invalid',
913
+ reason: validation.ok ? null : validation.reason ?? 'usage validation failed',
914
+ bundle,
915
+ });
916
+ }
917
+ }
918
+ async validateAuditBundle(bundle) {
919
+ const expiresAt = readAccessTokenExpiresAtMs(bundle.rawAuth);
920
+ if (expiresAt === null || expiresAt <= Date.now() + REMOTE_ACCESS_TOKEN_MIN_TTL_MS) {
921
+ return { ok: false, reason: 'access token is expired or missing exp' };
922
+ }
923
+ return this.callbacks.validateCandidate(bundle.candidateName, bundle.rawAuth, bundle.accountId);
924
+ }
760
925
  async reconcileAuditReports(requestId, reports, missingPeers) {
761
926
  const completedReports = reports.filter(report => report.status === 'completed');
762
927
  const busyNodes = reports.filter(report => report.status === 'busy').map(report => report.nodeId);
@@ -872,6 +1037,15 @@ export class CrossNodeAuthSync {
872
1037
  const missing = pending.peers.filter(peer => !pending.reports.has(peer));
873
1038
  pending.resolve({ reports: pending.reports, missing });
874
1039
  }
1040
+ finishPendingAuditVerification(verificationId) {
1041
+ const pending = this.pendingAuditVerifications.get(verificationId);
1042
+ if (!pending || pending.finished)
1043
+ return;
1044
+ pending.finished = true;
1045
+ clearTimeout(pending.timer);
1046
+ this.pendingAuditVerifications.delete(verificationId);
1047
+ pending.resolve(pending.reports);
1048
+ }
875
1049
  async handleServiceUpdateRequest(message, senderNodeId, sourceLabel, peer) {
876
1050
  let result = {
877
1051
  accepted: false,
@@ -1722,6 +1896,18 @@ function isValidAuditReport(value) {
1722
1896
  && (record.bundle === null || isValidBundle(record.bundle))
1723
1897
  && (record.status !== 'valid' || record.bundle !== null)));
1724
1898
  }
1899
+ function isValidAuditVerificationReport(value) {
1900
+ return Boolean(value)
1901
+ && typeof value.nodeId === 'string'
1902
+ && (value.status === 'completed' || value.status === 'busy')
1903
+ && Array.isArray(value.records)
1904
+ && (typeof value.reason === 'string' || value.reason === null)
1905
+ && value.records.every(record => (typeof record.candidateName === 'string'
1906
+ && isAuthCandidateName(record.candidateName)
1907
+ && typeof record.authSha256 === 'string'
1908
+ && (record.status === 'valid' || record.status === 'invalid')
1909
+ && (typeof record.reason === 'string' || record.reason === null)));
1910
+ }
1725
1911
  function auditIdentityKey(accountId, quotaIdentityId) {
1726
1912
  return `${accountId}\u0000${quotaIdentityId || accountId}`;
1727
1913
  }
@@ -1835,6 +2021,8 @@ function requestIdFromMessage(message) {
1835
2021
  case 'test.pong':
1836
2022
  case 'audit.request':
1837
2023
  case 'audit.response':
2024
+ case 'audit.verify.request':
2025
+ case 'audit.verify.response':
1838
2026
  case 'audit.apply':
1839
2027
  case 'audit.state':
1840
2028
  case 'service.update.request':
@@ -1859,6 +2047,8 @@ function candidateNameFromMessage(message) {
1859
2047
  return message.bundle?.candidateName ?? null;
1860
2048
  case 'audit.apply':
1861
2049
  return message.bundle.candidateName;
2050
+ case 'audit.verify.request':
2051
+ return message.bundles.length === 1 ? message.bundles[0]?.candidateName ?? null : null;
1862
2052
  case 'audit.state':
1863
2053
  return message.candidateName;
1864
2054
  default:
@@ -1199,7 +1199,7 @@ function normalizeCollaborationMode(value) {
1199
1199
  return value === 'default' || value === 'plan' ? value : null;
1200
1200
  }
1201
1201
  function normalizeReasoningEffort(value) {
1202
- return typeof value === 'string' && ['none', 'minimal', 'low', 'medium', 'high', 'xhigh'].includes(value)
1202
+ return typeof value === 'string' && ['none', 'minimal', 'low', 'medium', 'high', 'xhigh', 'max', 'ultra'].includes(value)
1203
1203
  ? value
1204
1204
  : null;
1205
1205
  }
@@ -523,7 +523,11 @@ export class BridgeSessionCore {
523
523
  }
524
524
  lines.push(...codexUsageLines);
525
525
  lines.push(...codexLocalUsageLines);
526
- await this.sendRichInternalMessage(scopeId, '/status', lines.join('\n'));
526
+ lines.splice(5, 0, t(locale, 'status_codex_home', {
527
+ value: this.config.codexHome ?? path.join(os.homedir(), '.codex'),
528
+ }));
529
+ const messageId = await this.sendRichInternalMessage(scopeId, '/status', lines.join('\n'));
530
+ this.scheduleStalePanelDeletion(scopeId, messageId);
527
531
  return;
528
532
  }
529
533
  case 'account': {
@@ -4655,6 +4659,11 @@ export class BridgeSessionCore {
4655
4659
  this.scheduleSelfUpdateStatusPoll();
4656
4660
  return;
4657
4661
  }
4662
+ if (status.scopeId.startsWith('cluster:')) {
4663
+ this.coordinator?.selfUpdateCompleted?.(status);
4664
+ await this.selfUpdater?.clearStatus();
4665
+ return;
4666
+ }
4658
4667
  if (!this.ownsScope(status.scopeId)) {
4659
4668
  this.scheduleSelfUpdateStatusPoll();
4660
4669
  return;
@@ -4943,7 +4952,7 @@ export class BridgeSessionCore {
4943
4952
  await this.sendMessage(scopeId, message);
4944
4953
  return;
4945
4954
  }
4946
- if (action === 'audit' || action === 'check') {
4955
+ if (action === 'audit' || action === 'check' || action === 'safe') {
4947
4956
  if (!this.canRunGlobalAuthRefresh()) {
4948
4957
  await this.sendMessage(scopeId, t(locale, 'auth_cluster_audit_blocked_active'));
4949
4958
  return;
@@ -4954,10 +4963,10 @@ export class BridgeSessionCore {
4954
4963
  await this.sendMessage(scopeId, t(locale, 'auth_sync_disabled'));
4955
4964
  return;
4956
4965
  }
4957
- await this.sendRichInternalMessage(scopeId, '/auth sync audit', formatAuthClusterAuditResult(locale, outcome));
4966
+ await this.sendRichInternalMessage(scopeId, '/auth sync safe', formatAuthClusterAuditResult(locale, outcome));
4958
4967
  return;
4959
4968
  }
4960
- if (action === 'safe' || (action === 'push' && args[1]?.toLowerCase() === 'all')) {
4969
+ if (action === 'push' && args[1]?.toLowerCase() === 'all') {
4961
4970
  if (!this.canRunGlobalAuthRefresh()) {
4962
4971
  await this.sendMessage(scopeId, t(locale, 'auth_sync_push_blocked_active'));
4963
4972
  return;
@@ -5798,8 +5807,20 @@ export class BridgeSessionCore {
5798
5807
  if (record.messageId !== null) {
5799
5808
  await this.editAuthPanelMessage(event.scopeId, record.messageId, t(locale, 'auth_sync_safe_starting'), []);
5800
5809
  }
5801
- const result = await this.runAuthSafeSyncAll();
5802
- if (!result) {
5810
+ if (record.messageId !== null) {
5811
+ this.pauseStalePanelDeletion(event.scopeId, record.messageId);
5812
+ }
5813
+ let outcome;
5814
+ try {
5815
+ outcome = await this.runAuthClusterAudit();
5816
+ }
5817
+ catch (error) {
5818
+ if (record.messageId !== null) {
5819
+ await this.editAuthPanelMessage(event.scopeId, record.messageId, t(locale, 'auth_cluster_audit_failed', { error: formatUserError(error) }), authChoiceKeyboard(locale, record));
5820
+ }
5821
+ return;
5822
+ }
5823
+ if (!outcome) {
5803
5824
  if (record.messageId !== null) {
5804
5825
  await this.editAuthPanelMessage(event.scopeId, record.messageId, t(locale, 'auth_sync_disabled'), authChoiceKeyboard(locale, record));
5805
5826
  }
@@ -5811,12 +5832,7 @@ export class BridgeSessionCore {
5811
5832
  record.createdAt = Date.now();
5812
5833
  clampCodexAuthListOffset(record);
5813
5834
  if (record.messageId !== null) {
5814
- await this.editAuthPanelMessage(event.scopeId, record.messageId, `${t(locale, 'auth_sync_safe_done', {
5815
- localSynced: result.localSynced,
5816
- localSkipped: result.localSkipped,
5817
- sent: result.sent,
5818
- skipped: result.skipped,
5819
- })}\n\n${renderAuthListMessage(locale, state, this.authDisplayBotLabel(), parseWeixinBridgeScope(event.scopeId) !== null, record)}`, authChoiceKeyboard(locale, record));
5835
+ await this.editAuthPanelMessage(event.scopeId, record.messageId, `${formatAuthClusterAuditResult(locale, outcome)}\n\n${renderAuthListMessage(locale, state, this.authDisplayBotLabel(), parseWeixinBridgeScope(event.scopeId) !== null, record)}`, authChoiceKeyboard(locale, record));
5820
5836
  }
5821
5837
  return;
5822
5838
  }
@@ -10488,17 +10504,8 @@ function authChoiceKeyboard(locale, record) {
10488
10504
  if (record.searchTerm) {
10489
10505
  rows.push([{ text: t(locale, 'button_clear_filter'), callback_data: `auth:${record.localId}:clear_search` }]);
10490
10506
  }
10491
- rows.push([
10492
- { text: t(locale, 'button_permissions'), callback_data: 'nav:permissions' },
10493
- { text: t(locale, 'button_login_device'), callback_data: `auth:${record.localId}:login_device` },
10494
- ]);
10495
- rows.push([
10496
- { text: t(locale, 'button_auth_cluster_audit'), callback_data: `auth:${record.localId}:cluster_audit` },
10497
- ]);
10498
- rows.push([
10499
- { text: t(locale, 'button_auth_safe_sync'), callback_data: `auth:${record.localId}:safe_sync` },
10500
- { text: t(locale, 'button_auth_reload'), callback_data: `auth:${record.localId}:reload` },
10501
- ]);
10507
+ rows.push([{ text: t(locale, 'button_login_device'), callback_data: `auth:${record.localId}:login_device` }]);
10508
+ rows.push([{ text: t(locale, 'button_auth_safe_sync'), callback_data: `auth:${record.localId}:safe_sync` }]);
10502
10509
  return rows;
10503
10510
  }
10504
10511
  function formatCodexAuthCandidateDisplayName(name) {
@@ -542,7 +542,9 @@ export function normalizeRequestedEffort(value) {
542
542
  || normalized === 'low'
543
543
  || normalized === 'medium'
544
544
  || normalized === 'high'
545
- || normalized === 'xhigh') {
545
+ || normalized === 'xhigh'
546
+ || normalized === 'max'
547
+ || normalized === 'ultra') {
546
548
  return normalized;
547
549
  }
548
550
  return null;
package/dist/i18n.d.ts CHANGED
@@ -95,6 +95,7 @@ declare const MESSAGES: {
95
95
  readonly status_user_agent: "Codex/FoxClaw agent: {value}";
96
96
  readonly status_current_thread: "Current thread: {value}";
97
97
  readonly status_configured_model: "Configured model: {value}";
98
+ readonly status_codex_home: "Codex home: {value}";
98
99
  readonly status_configured_effort: "Configured effort: {value}";
99
100
  readonly status_fast: "Fast: {value}";
100
101
  readonly status_collaboration_mode: "Mode: {value}";
@@ -245,7 +246,7 @@ declare const MESSAGES: {
245
246
  readonly auth_sync_test_sent: "Auth sync test complete: sent {sent}, replies {replied}.";
246
247
  readonly auth_sync_test_missing: "Missing replies: {value}";
247
248
  readonly auth_sync_push_blocked_active: "Cannot push auth sync while any runtime, approval, input, login, or auth mirror write is active.";
248
- readonly auth_sync_safe_starting: "Safely syncing auth across local bot runtimes and cross-node peers...";
249
+ readonly auth_sync_safe_starting: "Auditing every node, selecting the newest valid auth, and safely synchronizing the cluster...";
249
250
  readonly auth_sync_safe_done: "Safe auth sync complete: local synced {localSynced}, local skipped {localSkipped}; cross-node sent {sent}, skipped {skipped}.";
250
251
  readonly auth_cluster_audit_starting: "Asking all auth-sync peers to validate accounts, reconcile the newest usable credentials, and refresh credentials at least 8 days old...";
251
252
  readonly auth_cluster_audit_blocked_active: "Cluster auth check requires every local runtime, approval, input, login, and auth mirror write to be idle.";
@@ -282,7 +283,7 @@ declare const MESSAGES: {
282
283
  readonly auth_sync_trace_missing: "Usage: /auth sync trace <requestId>";
283
284
  readonly button_login_device: "🔑 Login";
284
285
  readonly button_auth_reload: "🔄 Reload auth";
285
- readonly button_auth_safe_sync: "🧷 Safe sync";
286
+ readonly button_auth_safe_sync: "🩺 Safe sync";
286
287
  readonly button_auth_cluster_audit: "🩺 Check all nodes and reconcile auth";
287
288
  readonly button_auth_refresh_all_confirm: "⚠️ Accept risk & refresh";
288
289
  readonly button_auth_repair_login: "🔑 Login repair";
@@ -394,7 +395,7 @@ declare const MESSAGES: {
394
395
  readonly effort_adjusted_model: "Effort {effort} is not supported by {model}, so it was adjusted.";
395
396
  readonly effort_change_blocked: "Cannot change reasoning effort while a turn is active. Use /interrupt or wait.";
396
397
  readonly effort_reset: "Configured effort reset to server default.";
397
- readonly usage_effort: "Usage: /effort <none|minimal|low|medium|high|xhigh|default>\nOr just use /models and tap buttons.";
398
+ readonly usage_effort: "Usage: /effort <none|minimal|low|medium|high|xhigh|max|ultra|default>\nOr just use /models and tap buttons.";
398
399
  readonly model_does_not_support_effort: "{model} does not support {effort}.\nSupported: {supported}";
399
400
  readonly effort_configured: "Configured effort: {effort}";
400
401
  readonly usage_fast: "Usage: /fast <on|off|toggle>";
@@ -818,6 +819,7 @@ declare const MESSAGES: {
818
819
  readonly status_user_agent: "Codex/FoxClaw 标识:{value}";
819
820
  readonly status_current_thread: "当前线程:{value}";
820
821
  readonly status_configured_model: "已配置模型:{value}";
822
+ readonly status_codex_home: "Codex Home:{value}";
821
823
  readonly status_configured_effort: "已配置推理强度:{value}";
822
824
  readonly status_fast: "Fast:{value}";
823
825
  readonly status_collaboration_mode: "模式:{value}";
@@ -968,7 +970,7 @@ declare const MESSAGES: {
968
970
  readonly auth_sync_test_sent: "auth sync 测试完成:已发送 {sent},收到回应 {replied}。";
969
971
  readonly auth_sync_test_missing: "未回应:{value}";
970
972
  readonly auth_sync_push_blocked_active: "当前有任一 runtime、审批、待输入、登录或 auth 镜像写入在进行中,不能推送 auth 同步。";
971
- readonly auth_sync_safe_starting: "正在安全同步本机多 bot runtime 和跨节点 auth...";
973
+ readonly auth_sync_safe_starting: "正在自检全部节点、选择最新有效 auth,并安全同步整个集群...";
972
974
  readonly auth_sync_safe_done: "安全 auth 同步完成:本机同步 {localSynced},本机跳过 {localSkipped};跨节点发送 {sent},跳过 {skipped}。";
973
975
  readonly auth_cluster_audit_starting: "正在通知所有 auth sync 节点逐账号自检,协商最新有效凭据,并由本节点刷新已满 8 天的 auth...";
974
976
  readonly auth_cluster_audit_blocked_active: "集群 auth 自检要求本机所有 runtime、审批、待输入、登录和 auth 镜像写入均为空闲。";
@@ -1005,7 +1007,7 @@ declare const MESSAGES: {
1005
1007
  readonly auth_sync_trace_missing: "用法:/auth sync trace <requestId>";
1006
1008
  readonly button_login_device: "🔑 设备登录";
1007
1009
  readonly button_auth_reload: "🔄 重载 auth";
1008
- readonly button_auth_safe_sync: "🧷 安全同步";
1010
+ readonly button_auth_safe_sync: "🩺 安全同步";
1009
1011
  readonly button_auth_cluster_audit: "🩺 全节点自检并同步";
1010
1012
  readonly button_auth_refresh_all_confirm: "⚠️ 接受风险并刷新";
1011
1013
  readonly button_auth_repair_login: "🔑 登录修复";
@@ -1117,7 +1119,7 @@ declare const MESSAGES: {
1117
1119
  readonly effort_adjusted_model: "{model} 不支持 {effort},所以已自动调整推理强度。";
1118
1120
  readonly effort_change_blocked: "当前有回复在进行中,暂时不能切换推理强度。请先等待,或使用 /interrupt。";
1119
1121
  readonly effort_reset: "推理强度已重置为服务端默认值。";
1120
- readonly usage_effort: "用法:/effort <none|minimal|low|medium|high|xhigh|default>\n或者直接使用 /models 点按钮。";
1122
+ readonly usage_effort: "用法:/effort <none|minimal|low|medium|high|xhigh|max|ultra|default>\n或者直接使用 /models 点按钮。";
1121
1123
  readonly model_does_not_support_effort: "{model} 不支持 {effort}。\n支持的强度:{supported}";
1122
1124
  readonly effort_configured: "已配置推理强度:{effort}";
1123
1125
  readonly usage_fast: "用法:/fast <on|off|toggle>";
package/dist/i18n.js CHANGED
@@ -93,6 +93,7 @@ const MESSAGES = {
93
93
  status_user_agent: 'Codex/FoxClaw agent: {value}',
94
94
  status_current_thread: 'Current thread: {value}',
95
95
  status_configured_model: 'Configured model: {value}',
96
+ status_codex_home: 'Codex home: {value}',
96
97
  status_configured_effort: 'Configured effort: {value}',
97
98
  status_fast: 'Fast: {value}',
98
99
  status_collaboration_mode: 'Mode: {value}',
@@ -243,7 +244,7 @@ const MESSAGES = {
243
244
  auth_sync_test_sent: 'Auth sync test complete: sent {sent}, replies {replied}.',
244
245
  auth_sync_test_missing: 'Missing replies: {value}',
245
246
  auth_sync_push_blocked_active: 'Cannot push auth sync while any runtime, approval, input, login, or auth mirror write is active.',
246
- auth_sync_safe_starting: 'Safely syncing auth across local bot runtimes and cross-node peers...',
247
+ auth_sync_safe_starting: 'Auditing every node, selecting the newest valid auth, and safely synchronizing the cluster...',
247
248
  auth_sync_safe_done: 'Safe auth sync complete: local synced {localSynced}, local skipped {localSkipped}; cross-node sent {sent}, skipped {skipped}.',
248
249
  auth_cluster_audit_starting: 'Asking all auth-sync peers to validate accounts, reconcile the newest usable credentials, and refresh credentials at least 8 days old...',
249
250
  auth_cluster_audit_blocked_active: 'Cluster auth check requires every local runtime, approval, input, login, and auth mirror write to be idle.',
@@ -280,7 +281,7 @@ const MESSAGES = {
280
281
  auth_sync_trace_missing: 'Usage: /auth sync trace <requestId>',
281
282
  button_login_device: '🔑 Login',
282
283
  button_auth_reload: '🔄 Reload auth',
283
- button_auth_safe_sync: '🧷 Safe sync',
284
+ button_auth_safe_sync: '🩺 Safe sync',
284
285
  button_auth_cluster_audit: '🩺 Check all nodes and reconcile auth',
285
286
  button_auth_refresh_all_confirm: '⚠️ Accept risk & refresh',
286
287
  button_auth_repair_login: '🔑 Login repair',
@@ -392,7 +393,7 @@ const MESSAGES = {
392
393
  effort_adjusted_model: 'Effort {effort} is not supported by {model}, so it was adjusted.',
393
394
  effort_change_blocked: 'Cannot change reasoning effort while a turn is active. Use /interrupt or wait.',
394
395
  effort_reset: 'Configured effort reset to server default.',
395
- usage_effort: 'Usage: /effort <none|minimal|low|medium|high|xhigh|default>\nOr just use /models and tap buttons.',
396
+ usage_effort: 'Usage: /effort <none|minimal|low|medium|high|xhigh|max|ultra|default>\nOr just use /models and tap buttons.',
396
397
  model_does_not_support_effort: '{model} does not support {effort}.\nSupported: {supported}',
397
398
  effort_configured: 'Configured effort: {effort}',
398
399
  usage_fast: 'Usage: /fast <on|off|toggle>',
@@ -816,6 +817,7 @@ const MESSAGES = {
816
817
  status_user_agent: 'Codex/FoxClaw 标识:{value}',
817
818
  status_current_thread: '当前线程:{value}',
818
819
  status_configured_model: '已配置模型:{value}',
820
+ status_codex_home: 'Codex Home:{value}',
819
821
  status_configured_effort: '已配置推理强度:{value}',
820
822
  status_fast: 'Fast:{value}',
821
823
  status_collaboration_mode: '模式:{value}',
@@ -966,7 +968,7 @@ const MESSAGES = {
966
968
  auth_sync_test_sent: 'auth sync 测试完成:已发送 {sent},收到回应 {replied}。',
967
969
  auth_sync_test_missing: '未回应:{value}',
968
970
  auth_sync_push_blocked_active: '当前有任一 runtime、审批、待输入、登录或 auth 镜像写入在进行中,不能推送 auth 同步。',
969
- auth_sync_safe_starting: '正在安全同步本机多 bot runtime 和跨节点 auth...',
971
+ auth_sync_safe_starting: '正在自检全部节点、选择最新有效 auth,并安全同步整个集群...',
970
972
  auth_sync_safe_done: '安全 auth 同步完成:本机同步 {localSynced},本机跳过 {localSkipped};跨节点发送 {sent},跳过 {skipped}。',
971
973
  auth_cluster_audit_starting: '正在通知所有 auth sync 节点逐账号自检,协商最新有效凭据,并由本节点刷新已满 8 天的 auth...',
972
974
  auth_cluster_audit_blocked_active: '集群 auth 自检要求本机所有 runtime、审批、待输入、登录和 auth 镜像写入均为空闲。',
@@ -1003,7 +1005,7 @@ const MESSAGES = {
1003
1005
  auth_sync_trace_missing: '用法:/auth sync trace <requestId>',
1004
1006
  button_login_device: '🔑 设备登录',
1005
1007
  button_auth_reload: '🔄 重载 auth',
1006
- button_auth_safe_sync: '🧷 安全同步',
1008
+ button_auth_safe_sync: '🩺 安全同步',
1007
1009
  button_auth_cluster_audit: '🩺 全节点自检并同步',
1008
1010
  button_auth_refresh_all_confirm: '⚠️ 接受风险并刷新',
1009
1011
  button_auth_repair_login: '🔑 登录修复',
@@ -1115,7 +1117,7 @@ const MESSAGES = {
1115
1117
  effort_adjusted_model: '{model} 不支持 {effort},所以已自动调整推理强度。',
1116
1118
  effort_change_blocked: '当前有回复在进行中,暂时不能切换推理强度。请先等待,或使用 /interrupt。',
1117
1119
  effort_reset: '推理强度已重置为服务端默认值。',
1118
- usage_effort: '用法:/effort <none|minimal|low|medium|high|xhigh|default>\n或者直接使用 /models 点按钮。',
1120
+ usage_effort: '用法:/effort <none|minimal|low|medium|high|xhigh|max|ultra|default>\n或者直接使用 /models 点按钮。',
1119
1121
  model_does_not_support_effort: '{model} 不支持 {effort}。\n支持的强度:{supported}',
1120
1122
  effort_configured: '已配置推理强度:{effort}',
1121
1123
  usage_fast: '用法:/fast <on|off|toggle>',
package/dist/types.d.ts CHANGED
@@ -11,7 +11,7 @@ export type SandboxModeValue = 'read-only' | 'workspace-write' | 'danger-full-ac
11
11
  export type AccessPresetValue = 'read-only' | 'default' | 'full-access';
12
12
  export type CollaborationModeValue = 'default' | 'plan';
13
13
  export type ActiveTurnMessageMode = 'steer' | 'queue';
14
- export type ReasoningEffortValue = 'none' | 'minimal' | 'low' | 'medium' | 'high' | 'xhigh';
14
+ export type ReasoningEffortValue = 'none' | 'minimal' | 'low' | 'medium' | 'high' | 'xhigh' | 'max' | 'ultra';
15
15
  export type ThreadStatusKind = 'active' | 'idle' | 'notLoaded' | 'systemError';
16
16
  export interface ChatSessionSettings {
17
17
  /** Bridge scope id (e.g. `telegram:…`). */
@@ -153,11 +153,11 @@ Auth sync test complete: sent 1, replies 1.
153
153
 
154
154
  If it shows `Missing replies: @peer_bot`, Telegram delivery may have succeeded, but the peer did not receive, decrypt, pass allowlist validation, or run the same auth sync configuration.
155
155
 
156
- The `/auth` panel also provides **Check all nodes and reconcile auth** (`/auth sync audit`). It asks every configured peer to validate every local candidate against the usage endpoint, then:
156
+ The `/auth` panel's **Safe sync** action (`/auth sync safe`; `/auth sync audit` remains a compatibility alias) performs the complete all-node audit and reconciliation flow. It asks every configured peer to validate every local candidate against the usage endpoint, then:
157
157
 
158
158
  - selects the newest usage-validated copy for each same-name, same-account and same-user identity and distributes it to every peer;
159
159
  - allows a validated older copy to replace a newer timestamped copy only when the newer copy failed this explicit audit;
160
- - marks a candidate `?` only when every peer responded, no node has a valid copy, and at least two nodes independently reported it invalid;
160
+ - sends a single-node invalid candidate through the encrypted channel for validation-only checks on other nodes, without importing it, and marks it `?` only when no valid result exists and at least two nodes independently report it invalid;
161
161
  - leaves candidates unchanged when any peer is missing or busy, or when account/user identities conflict;
162
162
  - refreshes enabled ChatGPT candidates whose `last_refresh` is at least 8 days old on the initiating node, then distributes the refreshed copies.
163
163
 
@@ -268,7 +268,7 @@ foxclaw send-media /absolute/path/output.mp4 "caption"
268
268
  It controls:
269
269
 
270
270
  - Model: server default, or a model returned by app-server.
271
- - Reasoning effort: for example `low`, `medium`, `high`, or `xhigh`, depending on model support.
271
+ - Reasoning effort: for example `low`, `medium`, `high`, `xhigh`, `max`, or `ultra`, depending on model support.
272
272
  - Fast tier: available when supported by the selected model.
273
273
  - Access: `read-only`, `default`, or `full-access`.
274
274
  - Mode: `Agent` or `Plan`.
@@ -433,13 +433,13 @@ Quota remaining: window:percent|auth
433
433
  [✅ 20|25|personal] [✅]
434
434
  [🔐 —|—|team] [✅]
435
435
  [☑️ All] [Enabled] [Attention]
436
- [🛡️ Access] [🔑 Login]
437
- [🔄 Reload auth]
436
+ [🔑 Login]
437
+ [🩺 Safe sync]
438
438
  ```
439
439
 
440
440
  The right-side `✅` / `⏸️` button controls whether the candidate participates in auto-rotation. Tapping it toggles enabled/disabled, and the refreshed list shows the new state. Tapping a candidate switches auth, restarts that runtime, and refreshes the same panel with its buttons intact so you can switch again immediately. `--` means no quota snapshot has been observed for that candidate yet. Health summaries distinguish ready, low quota, quota exhausted, quota unknown, not recently refreshed, API key, invalid auth file, and needs login repair states.
441
441
 
442
- The **Check all nodes and reconcile auth** button asks every configured auth-sync peer to validate its candidates. FoxClaw adopts the newest valid copy, distributes it, marks an account `?` only after complete multi-node invalid consensus, and has the initiating node refresh enabled ChatGPT credentials last refreshed at least 8 days ago. Missing, busy, or identity-conflicting peers prevent invalid marking and stale refresh for that run.
442
+ The **Safe sync** button asks every configured auth-sync peer to validate candidates, adopts the newest valid same-account copy, and distributes it. A candidate that exists on only one node is sent to other nodes for validation only, without being imported; it is marked `?` only when no node validates it and at least two nodes independently reject it. The initiating node also refreshes enabled ChatGPT credentials last refreshed at least 8 days ago. Missing, busy, or identity-conflicting peers prevent stale refresh for that run.
443
443
 
444
444
  When an auth candidate has already failed while in use and FoxClaw cannot recover a newer same-account credential from local mirror or cross-node sync, it is marked as `needs login repair`. These candidates are skipped by auto-rotation and proactive refresh, and are hidden from the `Enabled` filter. Their row shows a `?` action. Tapping it opens two choices: Login repair starts device-code login with that candidate selected; Delete removes the candidate from canonical storage and all local bot runtimes, and clears cached quota for it.
445
445
 
@@ -497,7 +497,7 @@ Commands:
497
497
  - `/auth sync events [filter]`: show recent sync event records, optionally filtered by candidate, peer, request id, kind, stage, or detail.
498
498
  - `/auth sync trace <requestId>`: show recent records for one request id or event id.
499
499
  - `/auth sync test`: send an encrypted ping and wait for peer pong replies to verify peer config, shared key, and Bot-to-Bot private messages.
500
- - `/auth sync audit`: run the same validate, reconcile, consensus, stale-refresh, and distribution flow as the `/auth` cluster-check button.
500
+ - `/auth sync safe`: run the same all-node validation, reconciliation, invalid verification, stale-refresh, and distribution flow as the `/auth` Safe sync button. `/auth sync audit` remains a compatibility alias.
501
501
  - `/auth sync push all`: manually broadcast all locally verified candidates without refreshing tokens. “Sent” does not mean the peer imported files; check `/auth sync status` and `/auth` on the peer.
502
502
 
503
503
  Equivalent commands:
@@ -153,11 +153,11 @@ auth sync 测试完成:已发送 1,收到回应 1。
153
153
 
154
154
  如果显示 `未回应:@peer_bot`,说明 Telegram 发送可能成功,但对方没有成功接收、解密、通过 allowlist,或没有运行同一组 auth sync 配置。
155
155
 
156
- `/auth` 面板还提供 **全节点自检并同步**(命令等价入口是 `/auth sync audit`)。它会让所有已配置 peer 用 usage 接口逐个验证本机候选,然后:
156
+ `/auth` 面板的 **安全同步**(命令等价入口是 `/auth sync safe`,旧的 `/auth sync audit` 仍兼容)会执行完整的全节点自检与同步。它会让所有已配置 peer 用 usage 接口逐个验证本机候选,然后:
157
157
 
158
158
  - 对同名、同 account、同 ChatGPT 用户身份的候选,选择最新且验证有效的副本并分发给所有 peer;
159
159
  - 只有显式审计已经证明时间戳更晚的副本无效时,才允许经过验证的较旧有效副本替换它;
160
- - 仅当所有 peer 都回应、没有任何有效副本,且至少两个节点独立判定无效时,才把候选标为 `?`;
160
+ - 对只存在于单个节点的无效候选,会通过加密通道交给其他节点做只验证、不导入的复核;没有任何有效结果且至少两个节点独立判定无效时,才把候选标为 `?`;
161
161
  - 任一 peer 未回应、忙碌或账号/用户身份冲突时,不做无效裁决;
162
162
  - 由发起节点刷新 `last_refresh` 已满 8 天的已启用 ChatGPT 候选,再把刷新结果分发给所有 peer。
163
163
 
@@ -268,7 +268,7 @@ foxclaw send-media /absolute/path/output.mp4 "说明"
268
268
  它能配置:
269
269
 
270
270
  - 模型:使用服务端默认模型,或选择 app-server 返回的模型。
271
- - reasoning effort:例如 `low`、`medium`、`high`、`xhigh`,取决于模型支持情况。
271
+ - reasoning effort:例如 `low`、`medium`、`high`、`xhigh`、`max` 或 `ultra`,取决于模型支持情况。
272
272
  - Fast tier:模型支持时可开关 fast 服务档。
273
273
  - Access:`read-only`、`default`、`full-access`。
274
274
  - Mode:`Agent` 或 `Plan`。
@@ -433,13 +433,13 @@ Candidates: 2
433
433
  [✅ 20|25|personal] [✅]
434
434
  [🔐 —|—|team] [✅]
435
435
  [☑️ 全部] [已启用] [需关注]
436
- [🛡️ Access] [🔑 设备登录]
437
- [🔄 Reload auth]
436
+ [🔑 设备登录]
437
+ [🩺 安全同步]
438
438
  ```
439
439
 
440
440
  右侧 `✅` / `⏸️` 表示当前是否参与自动轮转。点一下会切换启用/禁用,列表刷新后图标会随状态变化。点击候选会切换 auth、重启对应 runtime,并在原消息上刷新面板且保留按钮,因此可以立即连续切换。`--` 表示该候选还没有额度历史快照。健康摘要会区分正常、额度偏低、额度耗尽、额度未知、长期未刷新、API key、无效 auth 文件和需要登录修复。
441
441
 
442
- **全节点自检并同步** 按钮会通知所有已配置 auth-sync peer 验证本机候选。FoxClaw 会采用并分发最新有效副本;只有完整的多节点无效共识才会标记 `?`;`last_refresh` 已满 8 天的已启用 ChatGPT auth 由发起节点刷新后再分发。存在未回应、忙碌或身份冲突节点时,本轮不会做无效裁决和临期刷新。
442
+ **安全同步** 按钮会通知所有已配置 auth-sync peer 验证候选,采用并分发同账号最新有效副本。只存在于一台机器的无效候选也会交给其他节点做只验证、不导入的复核;没有任何节点验证有效且至少两个节点独立确认无效后才标记 `?`。`last_refresh` 已满 8 天的已启用 ChatGPT auth 由发起节点刷新后再分发。存在未回应、忙碌或身份冲突节点时,本轮不会做临期刷新。
443
443
 
444
444
  当某个候选在实际使用中已经失败,并且 FoxClaw 无法从本机 mirror 或跨节点同步恢复同账号较新凭据时,会标为“需要登录修复”。这类候选不会参与自动轮转和后台主动刷新,也不会出现在“已启用”筛选里。它的按钮会显示 `?`。点击后有两个选择:`登录修复` 会在选中该候选的状态下启动设备码登录;`删除` 会从 canonical 和所有本机 bot runtime 中删除这个候选,并清理它的额度缓存。
445
445
 
@@ -497,7 +497,7 @@ AUTH_AUTO_DELETE_NEEDS_REPAIR=false
497
497
  - `/auth sync events [过滤]`:查看最近同步事件,可按候选名、peer、request id、事件类型、阶段或详情过滤。
498
498
  - `/auth sync trace <requestId>`:查看某个 request id 或事件 id 的最近流水。
499
499
  - `/auth sync test`:发送加密 ping 并等待 peer 返回 pong,确认 peer、共享密钥和 Bot-to-Bot 私聊可用。
500
- - `/auth sync audit`:执行与 `/auth` 面板“全节点自检并同步”按钮相同的验证、协商、无效共识、临期刷新和分发流程。
500
+ - `/auth sync safe`:执行与 `/auth` 面板“安全同步”按钮相同的全节点验证、协商、无效复核、临期刷新和分发流程。`/auth sync audit` 保留为兼容别名。
501
501
  - `/auth sync push all`:手动广播当前节点已验证的全部候选,不刷新 token;“已发送”不等于对端已经导入,需要在 peer 上看 `/auth sync status` 和 `/auth`。
502
502
 
503
503
  命令等价用法:
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@foxden-app/foxclaw",
3
- "version": "0.5.76",
3
+ "version": "0.5.78",
4
4
  "description": "Foxden local execution claw for controlling Codex from trusted chat interfaces.",
5
5
  "type": "module",
6
6
  "main": "dist/main.js",