@ai-agent-forge/agent-forge 0.88.1 → 0.88.2

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.
Files changed (143) hide show
  1. package/CHANGELOG.md +44 -0
  2. package/bundled-plugins/agent-loop-policy/agent-forge.json +1 -1
  3. package/bundled-plugins/agent-loop-policy/plugin.json +1 -1
  4. package/bundled-plugins/coding-tools/agent-forge.json +1 -1
  5. package/bundled-plugins/coding-tools/plugin.json +1 -1
  6. package/bundled-plugins/compaction-policy/agent-forge.json +1 -1
  7. package/bundled-plugins/compaction-policy/plugin.json +1 -1
  8. package/bundled-plugins/overflow-policy/agent-forge.json +1 -1
  9. package/bundled-plugins/overflow-policy/plugin.json +1 -1
  10. package/bundled-plugins/provider-failover/agent-forge.json +1 -1
  11. package/bundled-plugins/provider-failover/plugin.json +1 -1
  12. package/bundled-plugins/retry-policy/agent-forge.json +1 -1
  13. package/bundled-plugins/retry-policy/plugin.json +1 -1
  14. package/dist/bundle/.build-manifest.json +68 -58
  15. package/dist/bundle/chunks/{chunk-D3ARGZFR.js → chunk-44I47ACN.js} +3 -3
  16. package/dist/bundle/chunks/{chunk-Z72JWUZ4.js → chunk-6F5JWGKE.js} +1 -1
  17. package/dist/bundle/chunks/{chunk-LZOEQITZ.js → chunk-HTC5K5SM.js} +56 -45
  18. package/dist/bundle/chunks/{chunk-AQLNTW2B.js → chunk-LSP24WNH.js} +8 -7
  19. package/dist/bundle/chunks/image-resize-worker.js +3 -3
  20. package/dist/bundle/chunks/{virtual-modules-WF6WRHP7.js → virtual-modules-MRXD3RWG.js} +1 -1
  21. package/dist/bundle/cli.js +1 -1
  22. package/dist/bundle/client.js +1 -1
  23. package/dist/bundle/index.js +1 -1
  24. package/dist/bundle/rpc-entry.js +1 -1
  25. package/dist/cli/plugin-command.d.ts.map +1 -1
  26. package/dist/cli/plugin-command.js +8 -2
  27. package/dist/cli/plugin-command.js.map +1 -1
  28. package/dist/core/agent-session-internal.d.ts +2 -0
  29. package/dist/core/agent-session-internal.d.ts.map +1 -1
  30. package/dist/core/agent-session-internal.js.map +1 -1
  31. package/dist/core/agent-session-services.d.ts +2 -0
  32. package/dist/core/agent-session-services.d.ts.map +1 -1
  33. package/dist/core/agent-session-services.js +1 -0
  34. package/dist/core/agent-session-services.js.map +1 -1
  35. package/dist/core/agent-session-store.d.ts.map +1 -1
  36. package/dist/core/agent-session-store.js +6 -1
  37. package/dist/core/agent-session-store.js.map +1 -1
  38. package/dist/core/agent-session-tools.d.ts.map +1 -1
  39. package/dist/core/agent-session-tools.js +13 -0
  40. package/dist/core/agent-session-tools.js.map +1 -1
  41. package/dist/core/agent-session.d.ts +24 -0
  42. package/dist/core/agent-session.d.ts.map +1 -1
  43. package/dist/core/agent-session.js +58 -4
  44. package/dist/core/agent-session.js.map +1 -1
  45. package/dist/core/i18n/locales/zh-cn/cli.d.ts.map +1 -1
  46. package/dist/core/i18n/locales/zh-cn/cli.js +2 -0
  47. package/dist/core/i18n/locales/zh-cn/cli.js.map +1 -1
  48. package/dist/core/i18n/locales/zh-cn/index.d.ts.map +1 -1
  49. package/dist/core/i18n/locales/zh-cn/index.js +2 -0
  50. package/dist/core/i18n/locales/zh-cn/index.js.map +1 -1
  51. package/dist/core/i18n/locales/zh-cn/interactive-mode.d.ts.map +1 -1
  52. package/dist/core/i18n/locales/zh-cn/interactive-mode.js +5 -0
  53. package/dist/core/i18n/locales/zh-cn/interactive-mode.js.map +1 -1
  54. package/dist/core/i18n/locales/zh-cn/session-cleanup.d.ts +7 -0
  55. package/dist/core/i18n/locales/zh-cn/session-cleanup.d.ts.map +1 -0
  56. package/dist/core/i18n/locales/zh-cn/session-cleanup.js +15 -0
  57. package/dist/core/i18n/locales/zh-cn/session-cleanup.js.map +1 -0
  58. package/dist/core/i18n/locales/zh-cn/slash-commands.d.ts.map +1 -1
  59. package/dist/core/i18n/locales/zh-cn/slash-commands.js +1 -0
  60. package/dist/core/i18n/locales/zh-cn/slash-commands.js.map +1 -1
  61. package/dist/core/sdk.d.ts.map +1 -1
  62. package/dist/core/sdk.js +24 -1
  63. package/dist/core/sdk.js.map +1 -1
  64. package/dist/core/session-cleanup.d.ts +49 -0
  65. package/dist/core/session-cleanup.d.ts.map +1 -0
  66. package/dist/core/session-cleanup.js +159 -0
  67. package/dist/core/session-cleanup.js.map +1 -0
  68. package/dist/core/session-index.d.ts +75 -0
  69. package/dist/core/session-index.d.ts.map +1 -0
  70. package/dist/core/session-index.js +455 -0
  71. package/dist/core/session-index.js.map +1 -0
  72. package/dist/core/session-manager.d.ts +9 -0
  73. package/dist/core/session-manager.d.ts.map +1 -1
  74. package/dist/core/session-manager.js +202 -47
  75. package/dist/core/session-manager.js.map +1 -1
  76. package/dist/core/slash-commands.d.ts.map +1 -1
  77. package/dist/core/slash-commands.js +5 -0
  78. package/dist/core/slash-commands.js.map +1 -1
  79. package/dist/core/system-prompt.d.ts.map +1 -1
  80. package/dist/core/system-prompt.js +12 -1
  81. package/dist/core/system-prompt.js.map +1 -1
  82. package/dist/core/tools/capability-mount.d.ts +40 -0
  83. package/dist/core/tools/capability-mount.d.ts.map +1 -0
  84. package/dist/core/tools/capability-mount.js +112 -0
  85. package/dist/core/tools/capability-mount.js.map +1 -0
  86. package/dist/main.d.ts.map +1 -1
  87. package/dist/main.js +1 -0
  88. package/dist/main.js.map +1 -1
  89. package/dist/modes/interactive/components/assistant-message.d.ts +3 -0
  90. package/dist/modes/interactive/components/assistant-message.d.ts.map +1 -1
  91. package/dist/modes/interactive/components/assistant-message.js +27 -5
  92. package/dist/modes/interactive/components/assistant-message.js.map +1 -1
  93. package/dist/modes/interactive/components/session-selector-search.d.ts.map +1 -1
  94. package/dist/modes/interactive/components/session-selector-search.js +5 -1
  95. package/dist/modes/interactive/components/session-selector-search.js.map +1 -1
  96. package/dist/modes/interactive/components/session-selector.d.ts.map +1 -1
  97. package/dist/modes/interactive/components/session-selector.js +9 -0
  98. package/dist/modes/interactive/components/session-selector.js.map +1 -1
  99. package/dist/modes/interactive/components/tool-execution.d.ts +17 -0
  100. package/dist/modes/interactive/components/tool-execution.d.ts.map +1 -1
  101. package/dist/modes/interactive/components/tool-execution.js +43 -0
  102. package/dist/modes/interactive/components/tool-execution.js.map +1 -1
  103. package/dist/modes/interactive/components/tool-run-group.d.ts +44 -0
  104. package/dist/modes/interactive/components/tool-run-group.d.ts.map +1 -0
  105. package/dist/modes/interactive/components/tool-run-group.js +121 -0
  106. package/dist/modes/interactive/components/tool-run-group.js.map +1 -0
  107. package/dist/modes/interactive/interactive-commands.d.ts +2 -0
  108. package/dist/modes/interactive/interactive-commands.d.ts.map +1 -1
  109. package/dist/modes/interactive/interactive-commands.js +7 -0
  110. package/dist/modes/interactive/interactive-commands.js.map +1 -1
  111. package/dist/modes/interactive/interactive-mode.d.ts +18 -0
  112. package/dist/modes/interactive/interactive-mode.d.ts.map +1 -1
  113. package/dist/modes/interactive/interactive-mode.js +85 -3
  114. package/dist/modes/interactive/interactive-mode.js.map +1 -1
  115. package/dist/plugins/ecosystem/cli.d.ts.map +1 -1
  116. package/dist/plugins/ecosystem/cli.js +6 -3
  117. package/dist/plugins/ecosystem/cli.js.map +1 -1
  118. package/dist/plugins/ecosystem/node-plugin-bootstrap.d.ts +7 -1
  119. package/dist/plugins/ecosystem/node-plugin-bootstrap.d.ts.map +1 -1
  120. package/dist/plugins/ecosystem/node-plugin-bootstrap.js +50 -3
  121. package/dist/plugins/ecosystem/node-plugin-bootstrap.js.map +1 -1
  122. package/dist/plugins/ecosystem/plugin-bootstrap.d.ts +28 -16
  123. package/dist/plugins/ecosystem/plugin-bootstrap.d.ts.map +1 -1
  124. package/dist/plugins/ecosystem/plugin-bootstrap.js +35 -33
  125. package/dist/plugins/ecosystem/plugin-bootstrap.js.map +1 -1
  126. package/dist/profile/plugin-profiles.d.ts +1 -1
  127. package/dist/profile/plugin-profiles.d.ts.map +1 -1
  128. package/dist/profile/plugin-profiles.js +18 -0
  129. package/dist/profile/plugin-profiles.js.map +1 -1
  130. package/dist/profiles/builtin-suites.d.ts +14 -4
  131. package/dist/profiles/builtin-suites.d.ts.map +1 -1
  132. package/dist/profiles/builtin-suites.js +20 -5
  133. package/dist/profiles/builtin-suites.js.map +1 -1
  134. package/docs/settings.md +354 -354
  135. package/docs/usage.md +292 -292
  136. package/examples/extensions/with-deps/package-lock.json +2 -2
  137. package/examples/extensions/with-deps/package.json +1 -1
  138. package/examples/extensions/with-deps/plugin.json +1 -1
  139. package/package.json +1 -1
  140. package/examples/extensions/mcp-stdio/index.d.ts +0 -64
  141. package/examples/extensions/mcp-stdio/index.d.ts.map +0 -1
  142. package/examples/extensions/mcp-stdio/index.js +0 -292
  143. package/examples/extensions/mcp-stdio/index.js.map +0 -1
@@ -10,6 +10,7 @@ import { normalizePath, resolvePath } from "../utils/paths.js";
10
10
  import { AGENT_SESSIONS_DIR_NAME } from "./agent-session-paths.js";
11
11
  import { t } from "./i18n/index.js";
12
12
  import { createBranchSummaryMessage, createCompactionSummaryMessage, createCustomMessage, createInterruptedToolResultMessage, } from "./messages.js";
13
+ import { listSessionsViaIndex, recordSessionUpdate } from "./session-index.js";
13
14
  import { FileSessionStorageProvider, SessionHeaderScanLimitError, } from "./session-storage.js";
14
15
  export const CURRENT_SESSION_VERSION = 3;
15
16
  function sameSessionRevision(left, right) {
@@ -564,6 +565,73 @@ async function buildSessionInfo(filePath, signal, fileStats) {
564
565
  return null;
565
566
  }
566
567
  }
568
+ /**
569
+ * 从内存 fileEntries 派生会话索引字段(设计 docs/design/会话索引.md §4)。
570
+ *
571
+ * 与 buildSessionInfo() 逐语义对应(该函数从磁盘重读同一文件应得到相同值):
572
+ * - name = 最新一条 session_info(显式空串清除);
573
+ * - messageCount = 全部 message 条目数(不限角色);
574
+ * - modifiedMs = 最后一条 user/assistant 消息活动时间(getMessageActivityTime),
575
+ * 缺失时回退 header 时间戳,再缺失(畸形 header)时回退当前时间——对应
576
+ * buildSessionInfo 的 stats.mtime 兜底,钩子侧文件刚写完,mtime≈now;
577
+ * - firstMessage = 首条有正文的 user 消息文本,无则 "(no messages)";
578
+ * - createdMs = header 时间戳;
579
+ * - profileId/profileVersion = header 的 profile 锁(与 buildSessionInfo 读回一致)。
580
+ * 返回 null 表示无会话头(无可索引字段)。
581
+ */
582
+ function deriveSessionIndexFields(entries) {
583
+ const header = entries.find((e) => e.type === "session");
584
+ if (!header)
585
+ return null;
586
+ let name;
587
+ let messageCount = 0;
588
+ let firstMessage = "";
589
+ let lastActivityTime;
590
+ for (const entry of entries) {
591
+ if (entry.type === "session")
592
+ continue;
593
+ if (entry.type === "session_info") {
594
+ name = entry.name?.trim() || undefined;
595
+ continue;
596
+ }
597
+ if (entry.type !== "message")
598
+ continue;
599
+ messageCount++;
600
+ const activityTime = getMessageActivityTime(entry);
601
+ if (typeof activityTime === "number") {
602
+ lastActivityTime = Math.max(lastActivityTime ?? 0, activityTime);
603
+ }
604
+ const message = entry.message;
605
+ if (!isMessageWithContent(message))
606
+ continue;
607
+ if (message.role !== "user" && message.role !== "assistant")
608
+ continue;
609
+ const textContent = extractTextContent(message);
610
+ if (!textContent)
611
+ continue;
612
+ if (!firstMessage && message.role === "user") {
613
+ firstMessage = textContent;
614
+ }
615
+ }
616
+ const headerTime = typeof header.timestamp === "string" ? new Date(header.timestamp).getTime() : NaN;
617
+ return {
618
+ sessionId: header.id,
619
+ cwd: typeof header.cwd === "string" ? header.cwd : "",
620
+ name,
621
+ parentSessionPath: header.parentSession,
622
+ // 与 buildSessionInfo 相同的守卫:畸形 header 的非字符串值读作缺席。
623
+ profileId: typeof header.profileId === "string" ? header.profileId : undefined,
624
+ profileVersion: typeof header.profileVersion === "string" ? header.profileVersion : undefined,
625
+ createdMs: headerTime,
626
+ modifiedMs: typeof lastActivityTime === "number" && lastActivityTime > 0
627
+ ? lastActivityTime
628
+ : Number.isNaN(headerTime)
629
+ ? Date.now()
630
+ : headerTime,
631
+ messageCount,
632
+ firstMessage: firstMessage || "(no messages)",
633
+ };
634
+ }
567
635
  const MAX_CONCURRENT_SESSION_INFO_LOADS = 10;
568
636
  const MAX_CONCURRENT_SESSION_DISCOVERY_LOADS = 64;
569
637
  const CURRENT_SESSION_LIST_PUBLISH_INTERVAL = 10;
@@ -618,6 +686,59 @@ async function listSessionsFromDir(dir, onProgress, signal) {
618
686
  return [];
619
687
  }
620
688
  }
689
+ /**
690
+ * 旧的全量扫描:遍历 sessions 根下全部项目目录并逐文件构建 SessionInfo。
691
+ * 保留为索引缺失/损坏/版本不符时的重建源与 listAll 的 fail-open 降级路径
692
+ * (设计 docs/design/会话索引.md §5),行为与抽出前逐字节一致。
693
+ */
694
+ async function scanAllSessionsFromRoot(sessionsDir, onProgress, signal) {
695
+ try {
696
+ if (!existsSync(sessionsDir))
697
+ return [];
698
+ const entries = await readdir(sessionsDir, { withFileTypes: true });
699
+ const dirs = entries
700
+ .filter((entry) => (entry.isDirectory() || entry.isSymbolicLink()) && entry.name !== AGENT_SESSIONS_DIR_NAME)
701
+ .map((entry) => join(sessionsDir, entry.name));
702
+ const dirFiles = await mapWithConcurrency(dirs, MAX_CONCURRENT_SESSION_DISCOVERY_LOADS, async (dir) => {
703
+ try {
704
+ return (await readdir(dir)).filter((file) => file.endsWith(".jsonl")).map((file) => join(dir, file));
705
+ }
706
+ catch {
707
+ return [];
708
+ }
709
+ }, signal);
710
+ const allFiles = dirFiles.flat();
711
+ const candidates = await mapWithConcurrency(allFiles, MAX_CONCURRENT_SESSION_DISCOVERY_LOADS, async (path) => {
712
+ try {
713
+ return { path, stats: await stat(path) };
714
+ }
715
+ catch {
716
+ return { path };
717
+ }
718
+ }, signal);
719
+ candidates.sort((a, b) => (b.stats?.mtimeMs ?? Number.NEGATIVE_INFINITY) - (a.stats?.mtimeMs ?? Number.NEGATIVE_INFINITY) ||
720
+ basename(b.path).localeCompare(basename(a.path)));
721
+ const totalFiles = candidates.length;
722
+ let loaded = 0;
723
+ let firstCandidateLoaded = false;
724
+ const partialSessions = [];
725
+ const results = await buildSessionInfosWithConcurrency(candidates, (info, index) => {
726
+ loaded++;
727
+ if (index === 0)
728
+ firstCandidateLoaded = true;
729
+ if (info)
730
+ partialSessions.push(info);
731
+ const publishPartial = firstCandidateLoaded &&
732
+ (index === 0 || loaded % ALL_SESSION_LIST_PUBLISH_INTERVAL === 0 || loaded === totalFiles);
733
+ onProgress?.(loaded, totalFiles, publishPartial ? sortSessionInfos([...partialSessions]) : undefined);
734
+ }, signal);
735
+ return sortSessionInfos(results.filter((info) => info !== null));
736
+ }
737
+ catch {
738
+ signal?.throwIfAborted();
739
+ return [];
740
+ }
741
+ }
621
742
  /**
622
743
  * Manages conversation sessions as append-only trees stored in JSONL files.
623
744
  *
@@ -836,11 +957,45 @@ export class SessionManager {
836
957
  return;
837
958
  this.storage.createExclusive(this.sessionFile, this.fileEntries);
838
959
  this.flushed = true;
960
+ // 首次落盘即预热索引条目(设计 docs/design/会话索引.md §4:写钩子是预热
961
+ // 优化,不承担正确性——条目缺失/过期由读路径的 mtime 令牌对账刷新)。
962
+ this.notifySessionIndex(true);
839
963
  }
840
964
  else {
841
965
  this.storage.append(this.sessionFile, entry);
842
966
  }
843
967
  }
968
+ /**
969
+ * 会话索引写钩子(设计 docs/design/会话索引.md §4)。定位是预热优化,不承担
970
+ * 正确性:条目缺失或过期由读路径的 mtime 令牌对账刷新。仅在默认布局且已落盘
971
+ * (flushed = 会话文件已存在于磁盘)时生效;自定义 --session-dir 与 inMemory
972
+ * 会话不进索引。字段从内存 fileEntries 派生(deriveSessionIndexFields,与
973
+ * buildSessionInfo 逐语义对齐)。索引维护的任何失败都在 recordSessionUpdate
974
+ * 内部吞掉,绝不影响会话持久化。
975
+ */
976
+ notifySessionIndex(immediate) {
977
+ if (!this.persist || !this.flushed || !this.sessionFile)
978
+ return;
979
+ if (this.sessionDir !== getDefaultSessionDirPath(this.cwd))
980
+ return;
981
+ const fields = deriveSessionIndexFields(this.fileEntries);
982
+ if (!fields)
983
+ return;
984
+ recordSessionUpdate({
985
+ sessionPath: this.sessionFile,
986
+ sessionId: fields.sessionId,
987
+ cwd: fields.cwd,
988
+ name: fields.name,
989
+ parentSessionPath: fields.parentSessionPath,
990
+ profileId: fields.profileId,
991
+ profileVersion: fields.profileVersion,
992
+ createdMs: fields.createdMs,
993
+ modifiedMs: fields.modifiedMs,
994
+ messageCount: fields.messageCount,
995
+ firstMessage: fields.firstMessage,
996
+ immediate,
997
+ });
998
+ }
844
999
  _appendEntry(entry) {
845
1000
  const previousEntries = this.fileEntries.slice();
846
1001
  const previousById = new Map(this.byId);
@@ -854,6 +1009,10 @@ export class SessionManager {
854
1009
  this.leafId = entry.id;
855
1010
  try {
856
1011
  this._persist(entry);
1012
+ // 已落盘会话的每轮追加走节流路径(10s 窗口);首 flush 由 _persist 内的
1013
+ // immediate 钩子覆盖,此处不重复(previousFlushed=false)。
1014
+ if (previousFlushed)
1015
+ this.notifySessionIndex(false);
857
1016
  }
858
1017
  catch (error) {
859
1018
  this.fileEntries = previousEntries;
@@ -1061,6 +1220,10 @@ export class SessionManager {
1061
1220
  name: sanitizedName,
1062
1221
  };
1063
1222
  this._appendEntry(entry);
1223
+ // 重命名的预热同步(设计 docs/design/会话索引.md §4):文件已存在时立即
1224
+ // 落盘索引;未落盘会话由 notifySessionIndex 的 flushed 守卫拦截。
1225
+ if (this.flushed)
1226
+ this.notifySessionIndex(true);
1064
1227
  return entry.id;
1065
1228
  }
1066
1229
  /** Get the current session name from the latest session_info entry, if any. */
@@ -1394,6 +1557,7 @@ export class SessionManager {
1394
1557
  if (this._hasConversation()) {
1395
1558
  this._rewriteFile();
1396
1559
  this.flushed = true;
1560
+ this.notifySessionIndex(true);
1397
1561
  }
1398
1562
  else {
1399
1563
  this.flushed = false;
@@ -1519,11 +1683,33 @@ export class SessionManager {
1519
1683
  cwd: resolvedTargetCwd,
1520
1684
  parentSession: resolvedSourcePath,
1521
1685
  };
1522
- storage.createExclusive(newSessionFile, [
1686
+ const forkedEntries = [
1523
1687
  newHeader,
1524
1688
  // Copy all non-header entries from source
1525
1689
  ...sourceEntries.filter((entry) => entry.type !== "session"),
1526
- ]);
1690
+ ];
1691
+ storage.createExclusive(newSessionFile, forkedEntries);
1692
+ // fork 直接写文件(不经 _persist),成功后预热索引条目(设计 §4:写钩子是
1693
+ // 预热优化,不承担正确性);仅在默认布局生效,失败吞掉(对账自愈)。
1694
+ if (dir === getDefaultSessionDirPath(resolvedTargetCwd)) {
1695
+ const fields = deriveSessionIndexFields(forkedEntries);
1696
+ if (fields) {
1697
+ recordSessionUpdate({
1698
+ sessionPath: newSessionFile,
1699
+ sessionId: fields.sessionId,
1700
+ cwd: fields.cwd,
1701
+ name: fields.name,
1702
+ parentSessionPath: fields.parentSessionPath,
1703
+ profileId: fields.profileId,
1704
+ profileVersion: fields.profileVersion,
1705
+ createdMs: fields.createdMs,
1706
+ modifiedMs: fields.modifiedMs,
1707
+ messageCount: fields.messageCount,
1708
+ firstMessage: fields.firstMessage,
1709
+ immediate: true,
1710
+ });
1711
+ }
1712
+ }
1527
1713
  return new SessionManager(resolvedTargetCwd, dir, newSessionFile, true, undefined, undefined, storage);
1528
1714
  }
1529
1715
  /**
@@ -1585,52 +1771,21 @@ export class SessionManager {
1585
1771
  return sortSessionInfos(await listSessionsFromDir(customSessionDir, progress, abortSignal));
1586
1772
  }
1587
1773
  const sessionsDir = getSessionsDir();
1588
- try {
1589
- if (!existsSync(sessionsDir))
1590
- return [];
1591
- const entries = await readdir(sessionsDir, { withFileTypes: true });
1592
- const dirs = entries
1593
- .filter((entry) => (entry.isDirectory() || entry.isSymbolicLink()) && entry.name !== AGENT_SESSIONS_DIR_NAME)
1594
- .map((entry) => join(sessionsDir, entry.name));
1595
- const dirFiles = await mapWithConcurrency(dirs, MAX_CONCURRENT_SESSION_DISCOVERY_LOADS, async (dir) => {
1596
- try {
1597
- return (await readdir(dir)).filter((file) => file.endsWith(".jsonl")).map((file) => join(dir, file));
1598
- }
1599
- catch {
1600
- return [];
1601
- }
1602
- }, abortSignal);
1603
- const allFiles = dirFiles.flat();
1604
- const candidates = await mapWithConcurrency(allFiles, MAX_CONCURRENT_SESSION_DISCOVERY_LOADS, async (path) => {
1605
- try {
1606
- return { path, stats: await stat(path) };
1607
- }
1608
- catch {
1609
- return { path };
1610
- }
1611
- }, abortSignal);
1612
- candidates.sort((a, b) => (b.stats?.mtimeMs ?? Number.NEGATIVE_INFINITY) - (a.stats?.mtimeMs ?? Number.NEGATIVE_INFINITY) ||
1613
- basename(b.path).localeCompare(basename(a.path)));
1614
- const totalFiles = candidates.length;
1615
- let loaded = 0;
1616
- let firstCandidateLoaded = false;
1617
- const partialSessions = [];
1618
- const results = await buildSessionInfosWithConcurrency(candidates, (info, index) => {
1619
- loaded++;
1620
- if (index === 0)
1621
- firstCandidateLoaded = true;
1622
- if (info)
1623
- partialSessions.push(info);
1624
- const publishPartial = firstCandidateLoaded &&
1625
- (index === 0 || loaded % ALL_SESSION_LIST_PUBLISH_INTERVAL === 0 || loaded === totalFiles);
1626
- progress?.(loaded, totalFiles, publishPartial ? sortSessionInfos([...partialSessions]) : undefined);
1627
- }, abortSignal);
1628
- return sortSessionInfos(results.filter((info) => info !== null));
1629
- }
1630
- catch {
1631
- abortSignal?.throwIfAborted();
1632
- return [];
1774
+ // 默认布局走会话元数据索引(设计 docs/design/会话索引.md §5):命中时亚秒级
1775
+ // 返回;undefined(索引层意外 IO 失败等)fail-open 到旧全量扫描兜底。
1776
+ const viaIndex = await listSessionsViaIndex({
1777
+ sessionsRoot: sessionsDir,
1778
+ buildInfo: (path) => buildSessionInfo(path, abortSignal),
1779
+ scanAll: () => scanAllSessionsFromRoot(sessionsDir, progress, abortSignal),
1780
+ // 索引命中路径发布单次完整已排序快照(loaded === total);重建路径的进度
1781
+ // 由 scanAll 按旧语义发布,不重复。
1782
+ onHit: (sessions) => progress?.(sessions.length, sessions.length, sessions),
1783
+ signal: abortSignal,
1784
+ });
1785
+ if (viaIndex !== undefined) {
1786
+ return viaIndex;
1633
1787
  }
1788
+ return scanAllSessionsFromRoot(sessionsDir, progress, abortSignal);
1634
1789
  }
1635
1790
  }
1636
1791
  //# sourceMappingURL=session-manager.js.map