@jingyi0605/codingns 0.9.8 → 1.0.0-beta.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.
Files changed (169) hide show
  1. package/bin/codingns.mjs +14 -0
  2. package/dist/public/assets/{AdaptiveButlerPage-D-gXre7Y.js → AdaptiveButlerPage-BYETYaIe.js} +2 -2
  3. package/dist/public/assets/App-CHsm-VrM.js +30 -0
  4. package/dist/public/assets/App-Z0Zd7gXR.css +1 -0
  5. package/dist/public/assets/{BootstrapPage-B-yMdfpQ.js → BootstrapPage-Bp0KfySv.js} +1 -1
  6. package/dist/public/assets/ConversationPage-BCrNml1k.js +9 -0
  7. package/dist/public/assets/{DesktopDetachPreviewPage-D1DMaGcy.js → DesktopDetachPreviewPage-DlDSb_tf.js} +1 -1
  8. package/dist/public/assets/{DesktopModal-BnfGW2gk.js → DesktopModal-Csj5AdAN.js} +1 -1
  9. package/dist/public/assets/DesktopWindowPage-CacXiWjV.js +2 -0
  10. package/dist/public/assets/FileContextPanel-DzMUQvE5.js +1 -0
  11. package/dist/public/assets/GitSidebar-C8IC18GH.js +6 -0
  12. package/dist/public/assets/MobileCreateSessionSheet-BtqR_hKc.js +1 -0
  13. package/dist/public/assets/{MobileSheet-rkn_CUOY.js → MobileSheet-Co-qPMBD.js} +1 -1
  14. package/dist/public/assets/PluginAccessOverview-D-KXkcyj.js +1 -0
  15. package/dist/public/assets/{PluginContainerPage-D-ly3i3H.js → PluginContainerPage-BKwCIs4r.js} +1 -1
  16. package/dist/public/assets/{PluginDetailPage-CWAHYyyG.js → PluginDetailPage-Djv_swcO.js} +1 -1
  17. package/dist/public/assets/{PluginsListPage-Cte3vBgR.js → PluginsListPage-DAECNoN6.js} +1 -1
  18. package/dist/public/assets/PureConversationPage-DP1X7Hix.js +1 -0
  19. package/dist/public/assets/{RelayConnectEntryPage-sRJlstx9.js → RelayConnectEntryPage-xL_IPH8a.js} +1 -1
  20. package/dist/public/assets/{ServerSettingsModal-BBft9KEC.js → ServerSettingsModal-CyhMgk10.js} +1 -1
  21. package/dist/public/assets/SessionIndexPage-CVEz50tc.js +1 -0
  22. package/dist/public/assets/SettingsPage-CQNCrgaj.js +2 -0
  23. package/dist/public/assets/TerminalManagerPanel-wVnoRA8u.js +1 -0
  24. package/dist/public/assets/ToolFilesPage-BospXumK.js +1 -0
  25. package/dist/public/assets/ToolGitPage-B2zOeCAD.js +1 -0
  26. package/dist/public/assets/ToolProcessesPage-BDShao4b.js +1 -0
  27. package/dist/public/assets/{ToolsHomePage-D1n4FU1s.js → ToolsHomePage-s4zH7D9M.js} +1 -1
  28. package/dist/public/assets/{WorkbenchLandingPage-BaU_dXls.js → WorkbenchLandingPage-D51QCU_u.js} +1 -1
  29. package/dist/public/assets/{WorkbenchLayout-BksVkkFF.css → WorkbenchLayout-BZdfVest.css} +32 -1
  30. package/dist/public/assets/WorkbenchLayout-Bo2BbMY6.js +1081 -0
  31. package/dist/public/assets/{WorkbenchModal-DWsNm2B2.js → WorkbenchModal-YsyEdJ_m.js} +1 -1
  32. package/dist/public/assets/WorkbenchShellRoute-D3l4aWJS.js +1 -0
  33. package/dist/public/assets/WorkbenchShellRoute-D5fnyF8z.css +1 -0
  34. package/dist/public/assets/WorkspaceDebugDetailPage-CMjNLqFq.js +1 -0
  35. package/dist/public/assets/WorkspaceDetailPage-CKxTbPKh.js +1 -0
  36. package/dist/public/assets/WorkspaceHomePage-CEy4ShCu.js +1 -0
  37. package/dist/public/assets/{client-runtime-manager-D9VbgJZ_.js → client-runtime-manager-BFXU9DmS.js} +1 -1
  38. package/dist/public/assets/host-alias-Zb2xyVrf.js +1 -0
  39. package/dist/public/assets/index-BilHJjYU.js +50 -0
  40. package/dist/public/assets/index-OR7OITpQ.css +1 -0
  41. package/dist/public/assets/{login-direct-candidate-resolver-17wEvjhh.js → login-direct-candidate-resolver-DL8DS-Si.js} +1 -1
  42. package/dist/public/assets/peer-host-config-sync-d2ZcPC5P.js +1 -0
  43. package/dist/public/assets/{plugin-permission-copy-apDn8EWG.js → plugin-permission-copy-DrLk22m5.js} +1 -1
  44. package/dist/public/assets/{plugins-api-CnZYRKoS.js → plugins-api-COF4oh24.js} +1 -1
  45. package/dist/public/assets/{preferences-service-PZlLLAWH.js → preferences-service-CEWNV1w9.js} +1 -1
  46. package/dist/public/assets/{relay-entry-DhHwflXl.js → relay-entry-Cmc8vTlE.js} +1 -1
  47. package/dist/public/assets/useRegisteredDebugTemplates-FdmHG2S4.js +1 -0
  48. package/dist/public/assets/workbench-navigation-ED0157V-.js +1 -0
  49. package/dist/public/index.html +2 -2
  50. package/dist/server/config/env.js +5 -3
  51. package/dist/server/config/env.js.map +1 -1
  52. package/dist/server/modules/assistant-capability/assistant-capability-controller.d.ts +3 -0
  53. package/dist/server/modules/assistant-capability/assistant-capability-controller.js +9 -0
  54. package/dist/server/modules/assistant-capability/assistant-capability-controller.js.map +1 -1
  55. package/dist/server/modules/assistant-capability/assistant-capability-service.d.ts +3 -0
  56. package/dist/server/modules/assistant-capability/assistant-capability-service.js +4 -1
  57. package/dist/server/modules/assistant-capability/assistant-capability-service.js.map +1 -1
  58. package/dist/server/modules/butler/butler-session-service.d.ts +1 -0
  59. package/dist/server/modules/butler/butler-session-service.js +2 -1
  60. package/dist/server/modules/butler/butler-session-service.js.map +1 -1
  61. package/dist/server/modules/client/npm-global-package-service.js +3 -0
  62. package/dist/server/modules/client/npm-global-package-service.js.map +1 -1
  63. package/dist/server/modules/client/service-update-types.d.ts +3 -0
  64. package/dist/server/modules/peer-host/peer-host-controller.d.ts +5 -0
  65. package/dist/server/modules/peer-host/peer-host-controller.js +3 -0
  66. package/dist/server/modules/peer-host/peer-host-controller.js.map +1 -1
  67. package/dist/server/modules/peer-host/peer-host-service.d.ts +1 -0
  68. package/dist/server/modules/peer-host/peer-host-service.js +44 -0
  69. package/dist/server/modules/peer-host/peer-host-service.js.map +1 -1
  70. package/dist/server/modules/sessions/session-controller.d.ts +10 -0
  71. package/dist/server/modules/sessions/session-controller.js +11 -0
  72. package/dist/server/modules/sessions/session-controller.js.map +1 -1
  73. package/dist/server/modules/sessions/session-history-service.d.ts +43 -1
  74. package/dist/server/modules/sessions/session-history-service.js +362 -27
  75. package/dist/server/modules/sessions/session-history-service.js.map +1 -1
  76. package/dist/server/modules/tasks/observability-controller.d.ts +2 -0
  77. package/dist/server/modules/tasks/observability-controller.js +16 -1
  78. package/dist/server/modules/tasks/observability-controller.js.map +1 -1
  79. package/dist/server/modules/tasks/observability-service.d.ts +12 -2
  80. package/dist/server/modules/tasks/observability-service.js +16 -3
  81. package/dist/server/modules/tasks/observability-service.js.map +1 -1
  82. package/dist/server/modules/terminal/runtime/terminal-log-spooler.d.ts +5 -0
  83. package/dist/server/modules/terminal/runtime/terminal-log-spooler.js +54 -7
  84. package/dist/server/modules/terminal/runtime/terminal-log-spooler.js.map +1 -1
  85. package/dist/server/modules/workbench/affairs-assistant-session-snapshot-service.js +2 -1
  86. package/dist/server/modules/workbench/affairs-assistant-session-snapshot-service.js.map +1 -1
  87. package/dist/server/modules/workbench/workbench-service.d.ts +2 -0
  88. package/dist/server/modules/workbench/workbench-service.js +173 -20
  89. package/dist/server/modules/workbench/workbench-service.js.map +1 -1
  90. package/dist/server/modules/workspace/affairs-library-controller.d.ts +1 -0
  91. package/dist/server/modules/workspace/affairs-library-controller.js +3 -0
  92. package/dist/server/modules/workspace/affairs-library-controller.js.map +1 -1
  93. package/dist/server/modules/workspace/affairs-library-service.d.ts +4 -0
  94. package/dist/server/modules/workspace/affairs-library-service.js +23 -4
  95. package/dist/server/modules/workspace/affairs-library-service.js.map +1 -1
  96. package/dist/server/modules/workspace/affairs-lightweight-session-controller.d.ts +4 -0
  97. package/dist/server/modules/workspace/affairs-lightweight-session-controller.js +26 -0
  98. package/dist/server/modules/workspace/affairs-lightweight-session-controller.js.map +1 -1
  99. package/dist/server/modules/workspace/affairs-lightweight-session-service.d.ts +15 -1
  100. package/dist/server/modules/workspace/affairs-lightweight-session-service.js +201 -22
  101. package/dist/server/modules/workspace/affairs-lightweight-session-service.js.map +1 -1
  102. package/dist/server/modules/workspace/workspace-controller.d.ts +7 -1
  103. package/dist/server/modules/workspace/workspace-controller.js +11 -1
  104. package/dist/server/modules/workspace/workspace-controller.js.map +1 -1
  105. package/dist/server/modules/workspace/workspace-service.d.ts +9 -2
  106. package/dist/server/modules/workspace/workspace-service.js +45 -10
  107. package/dist/server/modules/workspace/workspace-service.js.map +1 -1
  108. package/dist/server/routes/affairs.js +4 -0
  109. package/dist/server/routes/affairs.js.map +1 -1
  110. package/dist/server/routes/peer-hosts.js +1 -0
  111. package/dist/server/routes/peer-hosts.js.map +1 -1
  112. package/dist/server/routes/sessions.js +2 -0
  113. package/dist/server/routes/sessions.js.map +1 -1
  114. package/dist/server/server/create-server.js +2 -2
  115. package/dist/server/server/create-server.js.map +1 -1
  116. package/dist/server/storage/repositories/session-discovery-diagnostics-repository.d.ts +10 -0
  117. package/dist/server/storage/repositories/session-discovery-diagnostics-repository.js +68 -0
  118. package/dist/server/storage/repositories/session-discovery-diagnostics-repository.js.map +1 -0
  119. package/dist/server/storage/repositories/session-source-index-repository.d.ts +14 -0
  120. package/dist/server/storage/repositories/session-source-index-repository.js +164 -0
  121. package/dist/server/storage/repositories/session-source-index-repository.js.map +1 -0
  122. package/dist/server/storage/repositories/workspace-navigation-state-repository.js +16 -7
  123. package/dist/server/storage/repositories/workspace-navigation-state-repository.js.map +1 -1
  124. package/dist/server/storage/sqlite/client.js +26 -0
  125. package/dist/server/storage/sqlite/client.js.map +1 -1
  126. package/dist/server/storage/sqlite/schema.sql +57 -0
  127. package/dist/server/types/domain.d.ts +45 -0
  128. package/node_modules/@codingns/session-sync-core/dist/providers/claude-session-store.js +19 -2
  129. package/node_modules/@codingns/session-sync-core/dist/providers/claude-session-store.js.map +1 -1
  130. package/node_modules/@codingns/session-sync-core/dist/providers/codex.d.ts +1 -0
  131. package/node_modules/@codingns/session-sync-core/dist/providers/codex.js +45 -1
  132. package/node_modules/@codingns/session-sync-core/dist/providers/codex.js.map +1 -1
  133. package/node_modules/@codingns/session-sync-core/dist/providers/opencode.js +11 -4
  134. package/node_modules/@codingns/session-sync-core/dist/providers/opencode.js.map +1 -1
  135. package/package.json +3 -2
  136. package/scripts/node22-runtime.mjs +176 -0
  137. package/scripts/postinstall.mjs +6 -0
  138. package/dist/public/assets/App-7zrCMhE-.css +0 -1
  139. package/dist/public/assets/App-Dl-mcdqy.js +0 -30
  140. package/dist/public/assets/ConversationPage-DRQ5Sg_d.js +0 -9
  141. package/dist/public/assets/DesktopWindowPage-2SWAi0xz.js +0 -2
  142. package/dist/public/assets/FileContextPanel-fbPuE9dO.js +0 -1
  143. package/dist/public/assets/GitSidebar-BkmesJJR.js +0 -6
  144. package/dist/public/assets/MobileCreateSessionSheet-CEJcDBZJ.js +0 -1
  145. package/dist/public/assets/MobileTopHeaderFrame-CU0wsYSS.js +0 -1
  146. package/dist/public/assets/MobileWorkspaceSwitcherHeader-idl8o1OB.js +0 -1
  147. package/dist/public/assets/PluginAccessOverview-BBgM6tb0.js +0 -1
  148. package/dist/public/assets/SessionIndexPage-CN7cEdl9.js +0 -1
  149. package/dist/public/assets/SettingsPage-BGT-YqG2.js +0 -2
  150. package/dist/public/assets/TerminalManagerPanel-6-ZJ8vGn.js +0 -1
  151. package/dist/public/assets/TerminalPage-6GBZ9nXN.css +0 -32
  152. package/dist/public/assets/TerminalPage-CUXXQYU2.js +0 -55
  153. package/dist/public/assets/TerminalRuntimeFallbackModal-zc3qqMKJ.js +0 -1
  154. package/dist/public/assets/ToolFilesPage-QzsZyr0F.js +0 -1
  155. package/dist/public/assets/ToolGitPage-CXg4ncuT.js +0 -1
  156. package/dist/public/assets/ToolProcessesPage-BPsOsg4w.js +0 -1
  157. package/dist/public/assets/WorkbenchLayout-DViAJhHz.js +0 -1027
  158. package/dist/public/assets/WorkbenchShellRoute-BGfRqBUa.js +0 -1
  159. package/dist/public/assets/WorkbenchShellRoute-f2jWjHWu.css +0 -1
  160. package/dist/public/assets/WorkspaceDebugDetailPage-BX0zVSsI.js +0 -1
  161. package/dist/public/assets/WorkspaceDetailPage-Dx6JX4jx.js +0 -1
  162. package/dist/public/assets/WorkspaceHomePage-DQVJ042Z.js +0 -1
  163. package/dist/public/assets/host-alias-0TfFnYxR.js +0 -1
  164. package/dist/public/assets/index-DREvg1Yu.css +0 -1
  165. package/dist/public/assets/index-FOhyOpGY.js +0 -50
  166. package/dist/public/assets/peer-host-config-sync-vYkmqzNz.js +0 -1
  167. package/dist/public/assets/terminal-runtime-meta-2zvacxvM.js +0 -1
  168. package/dist/public/assets/useRegisteredDebugTemplates-CJ-o4tFl.js +0 -1
  169. package/dist/public/assets/workbench-navigation-aqJ1ay4M.js +0 -1
@@ -7,6 +7,8 @@ import { createId } from "../../shared/utils/id.js";
7
7
  import { logPerformance } from "../../shared/utils/perf-log.js";
8
8
  import { isTerminalDebugEnabled, logTerminalDebug, terminalDebugNowMs } from "../../shared/utils/terminal-debug-log.js";
9
9
  import { nowIso } from "../../shared/utils/time.js";
10
+ import { SessionDiscoveryDiagnosticsRepository } from "../../storage/repositories/session-discovery-diagnostics-repository.js";
11
+ import { SessionSourceIndexRepository } from "../../storage/repositories/session-source-index-repository.js";
10
12
  import { inspectSessionActivity } from "./session-activity-inspector.js";
11
13
  import { SessionActivityAuthorityService } from "./session-activity-authority-service.js";
12
14
  import { buildSessionTitleFromContent, normalizeRuntimePromptTitle } from "./session-title-utils.js";
@@ -72,6 +74,8 @@ export class SessionHistoryService {
72
74
  capabilityService;
73
75
  sessionActivityAuthorityService;
74
76
  sessionForkRepository;
77
+ sessionSourceIndexRepository;
78
+ sessionDiscoveryDiagnosticsRepository;
75
79
  providerSessionDeleteCli;
76
80
  claudeCodeHomeDir;
77
81
  codexModelOptionsService;
@@ -87,6 +91,7 @@ export class SessionHistoryService {
87
91
  providerControlRepository;
88
92
  taskManager;
89
93
  workspaceDiscoveryStatuses = new Map();
94
+ sessionSourceIndexRepairScopes = new Map();
90
95
  workspaceStateRefreshStatuses = new Map();
91
96
  providerCapabilityCache = new Map();
92
97
  codexDirtyBindingRepairStates = new Map();
@@ -108,6 +113,8 @@ export class SessionHistoryService {
108
113
  this.sessionMessageOriginRepository = sessionMessageOriginRepository;
109
114
  this.sessionActivityAuthorityService = sessionActivityAuthorityService;
110
115
  this.sessionForkRepository = sessionForkRepository ?? new SessionForkRepository(db);
116
+ this.sessionSourceIndexRepository = new SessionSourceIndexRepository(db);
117
+ this.sessionDiscoveryDiagnosticsRepository = new SessionDiscoveryDiagnosticsRepository(db);
111
118
  this.providerSessionDeleteCli =
112
119
  adapterOverrides.providerSessionDeleteCli ?? new CodingnsProviderSessionDeleteCli(config);
113
120
  this.taskManager = taskManager;
@@ -185,6 +192,73 @@ export class SessionHistoryService {
185
192
  observeBackgroundTaskMetrics() {
186
193
  return this.taskManager.observe();
187
194
  }
195
+ listWorkspaceDiscoveryDiagnostics(workspaceId, userId, limit = 20) {
196
+ this.getDiscoverableWorkspaceForUserOrThrow(workspaceId, userId);
197
+ const normalizedLimit = Number.isFinite(limit) ? Math.max(1, Math.floor(limit)) : 20;
198
+ return this.sessionDiscoveryDiagnosticsRepository.listByWorkspaceId(workspaceId, normalizedLimit);
199
+ }
200
+ getWorkspaceDiscoveryStatusSummary(workspaceId) {
201
+ const status = this.workspaceDiscoveryStatuses.get(workspaceId);
202
+ if (!status) {
203
+ return null;
204
+ }
205
+ return {
206
+ phase: status.phase,
207
+ dirtyReasonCount: status.dirtyReasons.size,
208
+ isComplete: status.isComplete,
209
+ lastRequestedAt: status.lastRequestedAt,
210
+ lastStartedAt: status.lastStartedAt,
211
+ lastCompletedAt: status.lastCompletedAt,
212
+ lastFailedAt: status.lastFailedAt,
213
+ nextAllowedAt: status.nextAllowedAt
214
+ };
215
+ }
216
+ async repairSessionSourceIndex(input) {
217
+ const workspaceId = input.workspaceId.trim();
218
+ const userId = input.userId.trim();
219
+ this.getDiscoverableWorkspaceForUserOrThrow(workspaceId, userId);
220
+ const provider = normalizeOptionalText(input.provider);
221
+ if (provider && !this.providerRegistry.list().some((adapter) => adapter.providerId === provider)) {
222
+ throw new AppError({
223
+ statusCode: 400,
224
+ errorCode: "INVALID_INPUT",
225
+ detail: "provider 不存在或当前不受支持",
226
+ field: "provider"
227
+ });
228
+ }
229
+ const sourceKeys = normalizeDistinctTexts(input.sourceKeys);
230
+ const rawStoreRefs = normalizeDistinctTexts(input.rawStoreRefs);
231
+ const existingRecords = this.sessionSourceIndexRepository.listByWorkspaceId(workspaceId);
232
+ const matchedRecords = existingRecords.filter((record) => matchesSessionSourceIndexRepairScope(record, provider, sourceKeys, rawStoreRefs));
233
+ const deletedSourceKeys = matchedRecords.map((record) => record.sourceKey);
234
+ const clearedSourceCount = this.sessionSourceIndexRepository.deleteBySourceKeys(deletedSourceKeys);
235
+ this.sessionSourceIndexRepairScopes.set(workspaceId, {
236
+ provider,
237
+ sourceKeys: new Set(sourceKeys),
238
+ rawStoreRefs: new Set(rawStoreRefs)
239
+ });
240
+ const awaitDiscovery = input.awaitDiscovery === true;
241
+ if (awaitDiscovery) {
242
+ await this.discoverWorkspaceSessions(workspaceId, userId, {
243
+ force: true,
244
+ refreshStateMode: "deferred"
245
+ });
246
+ }
247
+ else {
248
+ this.requestWorkspaceDiscovery(workspaceId, userId, {
249
+ force: true,
250
+ refreshStateMode: "deferred"
251
+ });
252
+ }
253
+ return {
254
+ workspaceId,
255
+ provider,
256
+ sourceKeyCount: sourceKeys.length,
257
+ rawStoreRefCount: rawStoreRefs.length,
258
+ clearedSourceCount,
259
+ awaitDiscovery
260
+ };
261
+ }
188
262
  registerLiveActivityObservationResolver(resolver) {
189
263
  this.liveActivityObservationResolvers.add(resolver);
190
264
  let closed = false;
@@ -219,7 +293,7 @@ export class SessionHistoryService {
219
293
  this.taskManager.register({
220
294
  taskType: HOST_TASK_TYPES.workspaceDiscovery,
221
295
  executionLane: "host_background",
222
- run: async ({ workspaceId, userId, refreshStateMode }, context) => this.runDiscoverWorkspaceSessions(workspaceId, userId, refreshStateMode, context.signal)
296
+ run: async ({ workspaceId, userId, refreshStateMode }, context) => this.runDiscoverWorkspaceSessions(workspaceId, userId, refreshStateMode, context.signal, context.taskId)
223
297
  });
224
298
  }
225
299
  if (!this.taskManager.has(HOST_TASK_TYPES.workspaceDiscoveryScan)) {
@@ -255,24 +329,21 @@ export class SessionHistoryService {
255
329
  }
256
330
  }
257
331
  async discoverWorkspaceSessions(workspaceId, userId, options) {
332
+ this.getDiscoverableWorkspaceForUserOrThrow(workspaceId, userId);
333
+ this.markWorkspaceDiscoveryRequested(workspaceId, "session_history.discover_workspace_sessions");
258
334
  const maxAgeMs = options?.maxAgeMs ?? 0;
259
335
  const force = options?.force ?? false;
260
336
  const discoveryStatus = this.workspaceDiscoveryStatuses.get(workspaceId);
261
- const lastRefreshedAt = discoveryStatus?.refreshedAt ?? 0;
262
337
  const now = Date.now();
263
- const isPartialCoolingDown = discoveryStatus?.isComplete === false &&
264
- discoveryStatus.partialCooldownUntil !== null &&
265
- now < discoveryStatus.partialCooldownUntil;
266
- const isCompleteAndFresh = discoveryStatus?.isComplete === true &&
267
- maxAgeMs > 0 &&
268
- now - lastRefreshedAt <= maxAgeMs;
338
+ const isPartialCoolingDown = this.isWorkspaceDiscoveryPartialCoolingDown(discoveryStatus, now);
339
+ const isCompleteAndFresh = this.isWorkspaceDiscoveryCompleteAndFresh(discoveryStatus, maxAgeMs, now);
269
340
  if (!force &&
270
341
  discoveryStatus &&
271
342
  (isPartialCoolingDown || isCompleteAndFresh)) {
272
343
  this.taskManager.recordCacheHit(HOST_TASK_TYPES.workspaceDiscovery, workspaceId);
273
344
  return this.listWorkspaceSessions(workspaceId, userId);
274
345
  }
275
- return this.taskManager.enqueue(HOST_TASK_TYPES.workspaceDiscovery, {
346
+ const handle = this.taskManager.enqueue(HOST_TASK_TYPES.workspaceDiscovery, {
276
347
  key: workspaceId,
277
348
  source: "session_history.discover_workspace_sessions",
278
349
  input: {
@@ -280,11 +351,22 @@ export class SessionHistoryService {
280
351
  userId,
281
352
  refreshStateMode: options?.refreshStateMode ?? "inline"
282
353
  }
283
- }).promise;
354
+ });
355
+ if (options?.signal) {
356
+ return await awaitTaskHandleWithSignal(handle, options.signal);
357
+ }
358
+ return await handle.promise;
284
359
  }
285
360
  requestWorkspaceDiscovery(workspaceId, userId, options) {
286
361
  const maxAgeMs = options?.maxAgeMs ?? WORKSPACE_DISCOVERY_BACKGROUND_MAX_AGE_MS;
287
362
  const force = options?.force ?? false;
363
+ if (!this.isWorkspaceDiscoverableForUser(workspaceId, userId)) {
364
+ return;
365
+ }
366
+ const discoveryStatus = this.markWorkspaceDiscoveryRequested(workspaceId, "session_history.request_workspace_discovery");
367
+ if (!force && discoveryStatus.phase === "running") {
368
+ return;
369
+ }
288
370
  if (!force && !this.needsWorkspaceDiscovery(workspaceId, maxAgeMs)) {
289
371
  return;
290
372
  }
@@ -312,19 +394,27 @@ export class SessionHistoryService {
312
394
  });
313
395
  }
314
396
  needsWorkspaceDiscovery(workspaceId, maxAgeMs) {
315
- if (maxAgeMs <= 0) {
316
- return true;
317
- }
318
397
  const discoveryStatus = this.workspaceDiscoveryStatuses.get(workspaceId);
319
398
  if (!discoveryStatus) {
320
399
  return true;
321
400
  }
401
+ if (discoveryStatus.phase === "running") {
402
+ return false;
403
+ }
404
+ if (discoveryStatus.phase === "failed") {
405
+ return true;
406
+ }
407
+ if (discoveryStatus.dirtyReasons.size > 0) {
408
+ return true;
409
+ }
322
410
  if (!discoveryStatus.isComplete &&
323
- (discoveryStatus.partialCooldownUntil === null ||
324
- Date.now() >= discoveryStatus.partialCooldownUntil)) {
411
+ !this.isWorkspaceDiscoveryPartialCoolingDown(discoveryStatus, Date.now())) {
412
+ return true;
413
+ }
414
+ if (maxAgeMs <= 0) {
325
415
  return true;
326
416
  }
327
- return Date.now() - discoveryStatus.refreshedAt > maxAgeMs;
417
+ return !this.isWorkspaceDiscoveryCompleteAndFresh(discoveryStatus, maxAgeMs, Date.now());
328
418
  }
329
419
  async readSessionHistory(sessionId, cursor, limit, direction = "forward", userId) {
330
420
  const startedAt = Date.now();
@@ -1433,10 +1523,14 @@ export class SessionHistoryService {
1433
1523
  }
1434
1524
  }
1435
1525
  }
1436
- async runDiscoverWorkspaceSessions(workspaceId, userId, refreshStateMode = "inline", signal) {
1526
+ async runDiscoverWorkspaceSessions(workspaceId, userId, refreshStateMode = "inline", signal, taskId) {
1437
1527
  const startedAt = Date.now();
1438
1528
  const debugStartedAtMs = terminalDebugNowMs();
1439
- const workspace = this.getWorkspaceForUserOrThrow(workspaceId, userId);
1529
+ const workspace = this.getDiscoverableWorkspaceForUserOrThrow(workspaceId, userId);
1530
+ const discoveryStatus = this.getOrCreateWorkspaceDiscoveryStatus(workspaceId);
1531
+ discoveryStatus.phase = "running";
1532
+ discoveryStatus.lastStartedAt = startedAt;
1533
+ discoveryStatus.runningTaskId = taskId ?? `workspace.discovery:${workspaceId}:${startedAt}`;
1440
1534
  let discoverDurationMs = 0;
1441
1535
  let persistDurationMs = 0;
1442
1536
  let persistPass1DurationMs = 0;
@@ -1450,14 +1544,16 @@ export class SessionHistoryService {
1450
1544
  let listItemsDurationMs = 0;
1451
1545
  let refreshStateDurationMs = 0;
1452
1546
  const refreshStateCount = 10;
1547
+ const activeRepairScope = this.sessionSourceIndexRepairScopes.get(workspaceId) ?? null;
1453
1548
  try {
1454
1549
  const discoverStartedAt = Date.now();
1455
1550
  const existingWorkspaceSessions = this.sessionIndexRepository.listByWorkspace(workspaceId, userId);
1551
+ const existingWorkspaceSourceIndexes = this.sessionSourceIndexRepository.listByWorkspaceId(workspaceId);
1456
1552
  const enabledProviders = this.providerRegistry
1457
1553
  .list()
1458
1554
  .map((adapter) => adapter.providerId)
1459
1555
  .filter((providerId) => this.isProviderEnabled(providerId));
1460
- const knownSessions = this.buildKnownSessionSummaries(existingWorkspaceSessions.filter((session) => enabledProviders.includes(session.provider)), workspace.path);
1556
+ const knownSessions = this.buildKnownSessionSummaries(existingWorkspaceSessions.filter((session) => enabledProviders.includes(session.provider)), existingWorkspaceSourceIndexes.filter((record) => enabledProviders.includes(record.provider)), workspace.path, activeRepairScope);
1461
1557
  const discoveryHandle = this.taskManager.enqueue(HOST_TASK_TYPES.workspaceDiscoveryScan, {
1462
1558
  key: workspaceId,
1463
1559
  source: "session_history.workspace_discovery.scan",
@@ -1474,6 +1570,7 @@ export class SessionHistoryService {
1474
1570
  const sessions = discovery.sessions;
1475
1571
  discoverDurationMs = Date.now() - discoverStartedAt;
1476
1572
  const timestamp = nowIso();
1573
+ this.persistDiscoveryDiagnostics(workspaceId, "session_history.workspace_discovery.scan", discovery, timestamp);
1477
1574
  const discoveredSessionIds = new Map();
1478
1575
  const persistedSessions = [];
1479
1576
  const claimedPendingSessionIds = new Set();
@@ -1642,6 +1739,7 @@ export class SessionHistoryService {
1642
1739
  persistPass2DurationMs = Date.now() - persistPass2StartedAt;
1643
1740
  persistPass2BatchCount = persistPass2Stats.batchCount;
1644
1741
  persistPass2MaxBatchMs = persistPass2Stats.maxBatchMs;
1742
+ this.persistSessionSourceIndexRecords(workspaceId, workspace.path, sessions, existingWorkspaceSourceIndexes, timestamp);
1645
1743
  persistDurationMs = persistPass1DurationMs + relationMapDurationMs + persistPass2DurationMs;
1646
1744
  if (discovery.isComplete) {
1647
1745
  const cleanupStartedAt = Date.now();
@@ -1655,11 +1753,19 @@ export class SessionHistoryService {
1655
1753
  listItemsDurationMs = Date.now() - listItemsStartedAt;
1656
1754
  const refreshCandidates = buildSessionStateRefreshCandidates(items, refreshStateCount);
1657
1755
  this.workspaceDiscoveryStatuses.set(workspaceId, {
1756
+ ...discoveryStatus,
1757
+ phase: discovery.isComplete ? "fresh" : "cooldown",
1758
+ dirtyReasons: new Set(),
1658
1759
  refreshedAt: Date.now(),
1659
1760
  isComplete: discovery.isComplete,
1660
1761
  partialCooldownUntil: discovery.isComplete
1661
1762
  ? null
1662
- : Date.now() + WORKSPACE_DISCOVERY_PARTIAL_COOLDOWN_MS
1763
+ : Date.now() + WORKSPACE_DISCOVERY_PARTIAL_COOLDOWN_MS,
1764
+ lastCompletedAt: Date.now(),
1765
+ nextAllowedAt: discovery.isComplete
1766
+ ? null
1767
+ : Date.now() + WORKSPACE_DISCOVERY_PARTIAL_COOLDOWN_MS,
1768
+ runningTaskId: null
1663
1769
  });
1664
1770
  const refreshStateStartedAt = Date.now();
1665
1771
  if (refreshStateMode === "inline") {
@@ -1734,6 +1840,13 @@ export class SessionHistoryService {
1734
1840
  return nextItems;
1735
1841
  }
1736
1842
  catch (error) {
1843
+ const failedAt = Date.now();
1844
+ this.workspaceDiscoveryStatuses.set(workspaceId, {
1845
+ ...discoveryStatus,
1846
+ phase: "failed",
1847
+ lastFailedAt: failedAt,
1848
+ runningTaskId: null
1849
+ });
1737
1850
  logPerformance("workspace.discover_sessions.failed", Date.now() - startedAt, {
1738
1851
  workspaceId,
1739
1852
  workspacePath: workspace.path,
@@ -1757,6 +1870,11 @@ export class SessionHistoryService {
1757
1870
  });
1758
1871
  throw error;
1759
1872
  }
1873
+ finally {
1874
+ if (activeRepairScope) {
1875
+ this.sessionSourceIndexRepairScopes.delete(workspaceId);
1876
+ }
1877
+ }
1760
1878
  }
1761
1879
  async readPage(sessionId, provider, providerSessionId, rawStoreRef, cursor, limit, direction = "forward", knownTotalMessageCount = null) {
1762
1880
  if (shouldShortCircuitClaudePendingHistory(provider, providerSessionId, rawStoreRef)) {
@@ -2450,6 +2568,65 @@ export class SessionHistoryService {
2450
2568
  }
2451
2569
  return workspace;
2452
2570
  }
2571
+ getOrCreateWorkspaceDiscoveryStatus(workspaceId) {
2572
+ const existing = this.workspaceDiscoveryStatuses.get(workspaceId);
2573
+ if (existing) {
2574
+ return existing;
2575
+ }
2576
+ const created = {
2577
+ phase: "fresh",
2578
+ dirtyReasons: new Set(),
2579
+ refreshedAt: 0,
2580
+ isComplete: false,
2581
+ partialCooldownUntil: null,
2582
+ lastRequestedAt: null,
2583
+ lastStartedAt: null,
2584
+ lastCompletedAt: null,
2585
+ lastFailedAt: null,
2586
+ nextAllowedAt: null,
2587
+ runningTaskId: null
2588
+ };
2589
+ this.workspaceDiscoveryStatuses.set(workspaceId, created);
2590
+ return created;
2591
+ }
2592
+ markWorkspaceDiscoveryRequested(workspaceId, reason) {
2593
+ const status = this.getOrCreateWorkspaceDiscoveryStatus(workspaceId);
2594
+ status.lastRequestedAt = Date.now();
2595
+ if (status.phase === "running") {
2596
+ status.dirtyReasons.add(reason);
2597
+ }
2598
+ return status;
2599
+ }
2600
+ isWorkspaceDiscoveryPartialCoolingDown(status, now) {
2601
+ return Boolean(status
2602
+ && status.isComplete === false
2603
+ && status.partialCooldownUntil !== null
2604
+ && now < status.partialCooldownUntil);
2605
+ }
2606
+ isWorkspaceDiscoveryCompleteAndFresh(status, maxAgeMs, now) {
2607
+ if (!status || !status.isComplete || maxAgeMs <= 0) {
2608
+ return false;
2609
+ }
2610
+ return now - status.refreshedAt <= maxAgeMs;
2611
+ }
2612
+ isWorkspaceDiscoverableForUser(workspaceId, userId) {
2613
+ const workspace = this.workspaceRepository.findById(workspaceId);
2614
+ if (!workspace || workspace.removedAt || workspace.ownerUserId !== userId) {
2615
+ return false;
2616
+ }
2617
+ return true;
2618
+ }
2619
+ getDiscoverableWorkspaceForUserOrThrow(workspaceId, userId) {
2620
+ const workspace = this.getWorkspaceForUserOrThrow(workspaceId, userId);
2621
+ if (workspace.removedAt) {
2622
+ throw new AppError({
2623
+ statusCode: 404,
2624
+ errorCode: "WORKSPACE_NOT_FOUND",
2625
+ detail: "工作区不存在"
2626
+ });
2627
+ }
2628
+ return workspace;
2629
+ }
2453
2630
  getBindingForUserOrThrow(sessionId, userId) {
2454
2631
  const binding = this.sessionBindingRepository.findBySessionIdForUser(sessionId, userId);
2455
2632
  if (!binding) {
@@ -3189,24 +3366,121 @@ export class SessionHistoryService {
3189
3366
  }
3190
3367
  }
3191
3368
  }
3192
- buildKnownSessionSummaries(sessions, workspacePath) {
3193
- return sessions
3194
- .filter((session) => !this.isPendingSessionAlias(session))
3195
- .filter((session) => !shouldSkipClaudePendingBinding(session))
3196
- .map((session) => {
3369
+ buildKnownSessionSummaries(sessions, sourceIndexes, workspacePath, repairScope = null) {
3370
+ const merged = new Map();
3371
+ for (const session of sessions) {
3372
+ if (this.isPendingSessionAlias(session) || shouldSkipClaudePendingBinding(session)) {
3373
+ continue;
3374
+ }
3375
+ const sourceKey = buildSessionSourceKey(session.provider, session.providerSessionId, session.rawStoreRef);
3376
+ if (shouldExcludeKnownSessionFromRepairScope(repairScope, session.provider, sourceKey, session.rawStoreRef)) {
3377
+ continue;
3378
+ }
3197
3379
  const stats = safeStat(session.rawStoreRef);
3198
- return {
3380
+ const summary = {
3199
3381
  provider: session.provider,
3200
3382
  providerSessionId: session.providerSessionId,
3201
3383
  title: session.title,
3202
3384
  workspacePath,
3203
3385
  rawStoreRef: session.rawStoreRef,
3386
+ isArchived: session.isArchived,
3204
3387
  lastMessageAt: session.lastMessageAt,
3205
3388
  messageCount: session.messageCount,
3206
3389
  sourceMtimeMs: stats?.mtimeMs,
3207
3390
  sourceSizeBytes: stats?.size
3208
3391
  };
3209
- });
3392
+ merged.set(buildKnownSessionSummaryKey(summary.provider, summary.providerSessionId, summary.rawStoreRef), summary);
3393
+ }
3394
+ for (const record of sourceIndexes) {
3395
+ if (record.deletedAt) {
3396
+ continue;
3397
+ }
3398
+ if (!record.providerSessionId || !record.rawStoreRef) {
3399
+ continue;
3400
+ }
3401
+ if (shouldExcludeKnownSessionFromRepairScope(repairScope, record.provider, record.sourceKey, record.rawStoreRef)) {
3402
+ continue;
3403
+ }
3404
+ const key = buildKnownSessionSummaryKey(record.provider, record.providerSessionId, record.rawStoreRef);
3405
+ if (merged.has(key)) {
3406
+ continue;
3407
+ }
3408
+ merged.set(key, {
3409
+ provider: record.provider,
3410
+ providerSessionId: record.providerSessionId,
3411
+ title: record.title?.trim() || record.providerSessionId,
3412
+ workspacePath: record.workspacePath?.trim() || workspacePath,
3413
+ rawStoreRef: record.rawStoreRef,
3414
+ isArchived: record.isArchivedHint ?? undefined,
3415
+ lastMessageAt: record.lastMessageAt,
3416
+ messageCount: Math.max(0, record.messageCount ?? 0),
3417
+ sourceMtimeMs: record.fingerprintMtimeMs ?? undefined,
3418
+ sourceSizeBytes: record.fingerprintSizeBytes ?? undefined
3419
+ });
3420
+ }
3421
+ return [...merged.values()];
3422
+ }
3423
+ persistSessionSourceIndexRecords(workspaceId, workspacePath, sessions, existingSourceIndexes, timestamp) {
3424
+ const existingByKey = new Map(existingSourceIndexes.map((record) => [record.sourceKey, record]));
3425
+ for (const session of sessions) {
3426
+ const sourceKind = inferSessionSourceKind(session.rawStoreRef);
3427
+ const sourceKey = buildSessionSourceKey(session.provider, session.providerSessionId, session.rawStoreRef);
3428
+ const stats = safeStat(session.rawStoreRef);
3429
+ const existing = existingByKey.get(sourceKey);
3430
+ this.sessionSourceIndexRepository.upsert({
3431
+ sourceKey,
3432
+ provider: session.provider,
3433
+ sourceKind,
3434
+ workspaceId,
3435
+ providerSessionId: session.providerSessionId,
3436
+ rawStoreRef: session.rawStoreRef,
3437
+ workspacePath,
3438
+ fingerprintMtimeMs: stats?.mtimeMs ?? existing?.fingerprintMtimeMs ?? null,
3439
+ fingerprintSizeBytes: stats?.size ?? existing?.fingerprintSizeBytes ?? null,
3440
+ fingerprintInode: existing?.fingerprintInode ?? null,
3441
+ fingerprintVersion: existing?.fingerprintVersion ?? null,
3442
+ title: session.title,
3443
+ messageCount: session.messageCount,
3444
+ lastMessageAt: session.lastMessageAt,
3445
+ isArchivedHint: session.isArchived ?? existing?.isArchivedHint ?? null,
3446
+ lastParsedAt: timestamp,
3447
+ lastVerifiedAt: timestamp,
3448
+ sampleDueAt: existing?.sampleDueAt ?? null,
3449
+ deletedAt: null,
3450
+ createdAt: existing?.createdAt ?? timestamp,
3451
+ updatedAt: timestamp
3452
+ });
3453
+ }
3454
+ }
3455
+ persistDiscoveryDiagnostics(workspaceId, triggerSource, discovery, timestamp) {
3456
+ try {
3457
+ for (const entry of discovery.providerDiagnostics ?? []) {
3458
+ const record = {
3459
+ id: createId(),
3460
+ workspaceId,
3461
+ triggerSource,
3462
+ provider: entry.provider,
3463
+ isComplete: entry.isComplete,
3464
+ status: entry.status,
3465
+ durationMs: Math.max(0, Math.round(entry.durationMs)),
3466
+ sessionCount: Math.max(0, entry.sessionCount),
3467
+ scannedFiles: Math.max(0, entry.scannedFiles ?? 0),
3468
+ skippedByFingerprint: Math.max(0, entry.skippedByMtimeSize ?? 0),
3469
+ parsedFiles: Math.max(0, entry.parsedFiles ?? 0),
3470
+ bytesRead: Math.max(0, entry.bytesRead ?? 0),
3471
+ createdAt: timestamp
3472
+ };
3473
+ this.sessionDiscoveryDiagnosticsRepository.insert(record);
3474
+ }
3475
+ }
3476
+ catch (error) {
3477
+ console.warn("[session-discovery-diagnostics-persist-failed]", {
3478
+ workspaceId,
3479
+ triggerSource,
3480
+ providerCount: discovery.providerDiagnostics?.length ?? 0,
3481
+ error
3482
+ });
3483
+ }
3210
3484
  }
3211
3485
  async refreshSessionState(sessionId, userId) {
3212
3486
  const binding = this.getBindingOrThrow(sessionId);
@@ -3783,6 +4057,67 @@ function pickLaterIso(left, right) {
3783
4057
  function buildProviderSessionKey(provider, providerSessionId) {
3784
4058
  return `${provider}::${providerSessionId}`;
3785
4059
  }
4060
+ function normalizeOptionalText(value) {
4061
+ const normalized = value?.trim();
4062
+ return normalized ? normalized : null;
4063
+ }
4064
+ function normalizeDistinctTexts(values) {
4065
+ if (!Array.isArray(values)) {
4066
+ return [];
4067
+ }
4068
+ const unique = new Set();
4069
+ for (const value of values) {
4070
+ const normalized = value?.trim();
4071
+ if (normalized) {
4072
+ unique.add(normalized);
4073
+ }
4074
+ }
4075
+ return [...unique];
4076
+ }
4077
+ function matchesSessionSourceIndexRepairScope(record, provider, sourceKeys, rawStoreRefs) {
4078
+ if (provider && record.provider !== provider) {
4079
+ return false;
4080
+ }
4081
+ if (sourceKeys.length === 0 && rawStoreRefs.length === 0) {
4082
+ return true;
4083
+ }
4084
+ return sourceKeys.includes(record.sourceKey)
4085
+ || (record.rawStoreRef ? rawStoreRefs.includes(record.rawStoreRef) : false);
4086
+ }
4087
+ function shouldExcludeKnownSessionFromRepairScope(scope, provider, sourceKey, rawStoreRef) {
4088
+ if (!scope) {
4089
+ return false;
4090
+ }
4091
+ if (scope.provider && scope.provider !== provider) {
4092
+ return false;
4093
+ }
4094
+ if (scope.sourceKeys.size === 0 && scope.rawStoreRefs.size === 0) {
4095
+ return true;
4096
+ }
4097
+ return scope.sourceKeys.has(sourceKey) || scope.rawStoreRefs.has(rawStoreRef);
4098
+ }
4099
+ function buildKnownSessionSummaryKey(provider, providerSessionId, rawStoreRef) {
4100
+ return `${provider}::${providerSessionId}::${rawStoreRef}`;
4101
+ }
4102
+ function buildSessionSourceKey(provider, providerSessionId, rawStoreRef) {
4103
+ const normalizedRawStoreRef = rawStoreRef.trim();
4104
+ if (normalizedRawStoreRef.length > 0) {
4105
+ return `${provider}:raw:${normalizedRawStoreRef}`;
4106
+ }
4107
+ return `${provider}:session:${providerSessionId}`;
4108
+ }
4109
+ function inferSessionSourceKind(rawStoreRef) {
4110
+ if (rawStoreRef.endsWith(".jsonl")) {
4111
+ return "jsonl";
4112
+ }
4113
+ if (rawStoreRef.startsWith("opencode://") || rawStoreRef.startsWith("server://")) {
4114
+ return "server_session";
4115
+ }
4116
+ if (rawStoreRef.endsWith(".sqlite") || rawStoreRef.includes(".sqlite:")) {
4117
+ return "sqlite_row";
4118
+ }
4119
+ return "index_entry";
4120
+ }
3786
4121
  function normalizeSessionBindingSnapshot(sessionId, snapshot) {
3787
4122
  if (snapshot.provider !== "claude-code" ||
3788
4123
  !(isPendingBindingValue(snapshot.providerSessionId) ||