@jingyi0605/codingns 0.3.6 → 0.4.0

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 (185) hide show
  1. package/README.md +3 -0
  2. package/bin/codingns.mjs +489 -1
  3. package/dist/public/assets/{TerminalPage-D00S4KM6.js → TerminalPage-6jHZV9Mh.js} +17 -17
  4. package/dist/public/assets/index-CSVhg7I8.js +123 -0
  5. package/dist/public/assets/index-Ce1VX19m.css +1 -0
  6. package/dist/public/index.html +2 -2
  7. package/dist/server/modules/assistant-capability/assistant-capability-controller.d.ts +173 -0
  8. package/dist/server/modules/assistant-capability/assistant-capability-controller.js +307 -0
  9. package/dist/server/modules/assistant-capability/assistant-capability-controller.js.map +1 -1
  10. package/dist/server/modules/assistant-capability/assistant-capability-service.d.ts +199 -2
  11. package/dist/server/modules/assistant-capability/assistant-capability-service.js +565 -3
  12. package/dist/server/modules/assistant-capability/assistant-capability-service.js.map +1 -1
  13. package/dist/server/modules/butler/assistant-automation-service.d.ts +110 -0
  14. package/dist/server/modules/butler/assistant-automation-service.js +786 -0
  15. package/dist/server/modules/butler/assistant-automation-service.js.map +1 -0
  16. package/dist/server/modules/butler/assistant-automation-trigger.d.ts +94 -0
  17. package/dist/server/modules/butler/assistant-automation-trigger.js +400 -0
  18. package/dist/server/modules/butler/assistant-automation-trigger.js.map +1 -0
  19. package/dist/server/modules/butler/assistant-sandbox-service.d.ts +55 -0
  20. package/dist/server/modules/butler/assistant-sandbox-service.js +266 -0
  21. package/dist/server/modules/butler/assistant-sandbox-service.js.map +1 -0
  22. package/dist/server/modules/butler/butler-action-context-service.d.ts +4 -1
  23. package/dist/server/modules/butler/butler-action-context-service.js +8 -2
  24. package/dist/server/modules/butler/butler-action-context-service.js.map +1 -1
  25. package/dist/server/modules/butler/butler-control-session-service.d.ts +8 -1
  26. package/dist/server/modules/butler/butler-control-session-service.js +154 -40
  27. package/dist/server/modules/butler/butler-control-session-service.js.map +1 -1
  28. package/dist/server/modules/butler/butler-control-timer-scheduler.d.ts +32 -0
  29. package/dist/server/modules/butler/butler-control-timer-scheduler.js +93 -0
  30. package/dist/server/modules/butler/butler-control-timer-scheduler.js.map +1 -0
  31. package/dist/server/modules/butler/butler-control-timer-service.d.ts +42 -0
  32. package/dist/server/modules/butler/butler-control-timer-service.js +132 -0
  33. package/dist/server/modules/butler/butler-control-timer-service.js.map +1 -0
  34. package/dist/server/modules/butler/butler-controller.d.ts +42 -2
  35. package/dist/server/modules/butler/butler-controller.js +79 -12
  36. package/dist/server/modules/butler/butler-controller.js.map +1 -1
  37. package/dist/server/modules/butler/butler-follow-up-service.d.ts +9 -1
  38. package/dist/server/modules/butler/butler-follow-up-service.js +273 -181
  39. package/dist/server/modules/butler/butler-follow-up-service.js.map +1 -1
  40. package/dist/server/modules/butler/butler-inbox-analysis-service.d.ts +4 -1
  41. package/dist/server/modules/butler/butler-inbox-analysis-service.js +18 -4
  42. package/dist/server/modules/butler/butler-inbox-analysis-service.js.map +1 -1
  43. package/dist/server/modules/butler/butler-profile-service.js +2 -5
  44. package/dist/server/modules/butler/butler-profile-service.js.map +1 -1
  45. package/dist/server/modules/butler/butler-project-service.d.ts +3 -1
  46. package/dist/server/modules/butler/butler-project-service.js +7 -1
  47. package/dist/server/modules/butler/butler-project-service.js.map +1 -1
  48. package/dist/server/modules/butler/butler-session-service.d.ts +3 -1
  49. package/dist/server/modules/butler/butler-session-service.js +12 -1
  50. package/dist/server/modules/butler/butler-session-service.js.map +1 -1
  51. package/dist/server/modules/butler/butler-session-summary-service.js +2 -1
  52. package/dist/server/modules/butler/butler-session-summary-service.js.map +1 -1
  53. package/dist/server/modules/butler/butler-workspace-context.d.ts +3 -0
  54. package/dist/server/modules/butler/butler-workspace-context.js +164 -44
  55. package/dist/server/modules/butler/butler-workspace-context.js.map +1 -1
  56. package/dist/server/modules/butler/patrol-execution-service.js +2 -1
  57. package/dist/server/modules/butler/patrol-execution-service.js.map +1 -1
  58. package/dist/server/modules/butler/provider-adapter-registry.d.ts +3 -0
  59. package/dist/server/modules/butler/provider-adapter-registry.js +18 -1
  60. package/dist/server/modules/butler/provider-adapter-registry.js.map +1 -1
  61. package/dist/server/modules/butler/verification-run-service.d.ts +9 -2
  62. package/dist/server/modules/butler/verification-run-service.js +188 -34
  63. package/dist/server/modules/butler/verification-run-service.js.map +1 -1
  64. package/dist/server/modules/debug-target/debug-target-controller.js +1 -1
  65. package/dist/server/modules/debug-target/debug-target-controller.js.map +1 -1
  66. package/dist/server/modules/debug-target/debug-target-service.d.ts +7 -2
  67. package/dist/server/modules/debug-target/debug-target-service.js +563 -100
  68. package/dist/server/modules/debug-target/debug-target-service.js.map +1 -1
  69. package/dist/server/modules/git/git-command-helper-client.d.ts +1 -0
  70. package/dist/server/modules/git/git-command-helper-client.js +19 -26
  71. package/dist/server/modules/git/git-command-helper-client.js.map +1 -1
  72. package/dist/server/modules/git/git-command-runner.js +19 -1
  73. package/dist/server/modules/git/git-command-runner.js.map +1 -1
  74. package/dist/server/modules/preferences/profile-service.d.ts +3 -1
  75. package/dist/server/modules/preferences/profile-service.js +74 -3
  76. package/dist/server/modules/preferences/profile-service.js.map +1 -1
  77. package/dist/server/modules/provider/provider-discovery-helper-client.d.ts +5 -3
  78. package/dist/server/modules/provider/provider-discovery-helper-client.js +129 -43
  79. package/dist/server/modules/provider/provider-discovery-helper-client.js.map +1 -1
  80. package/dist/server/modules/provider/provider-discovery-helper-process.js +44 -0
  81. package/dist/server/modules/provider/provider-discovery-helper-process.js.map +1 -1
  82. package/dist/server/modules/provider/provider-discovery-runtime.js +83 -3
  83. package/dist/server/modules/provider/provider-discovery-runtime.js.map +1 -1
  84. package/dist/server/modules/sessions/claude-runtime-helper-client.js +23 -1
  85. package/dist/server/modules/sessions/claude-runtime-helper-client.js.map +1 -1
  86. package/dist/server/modules/sessions/session-history-service.d.ts +7 -1
  87. package/dist/server/modules/sessions/session-history-service.js +251 -41
  88. package/dist/server/modules/sessions/session-history-service.js.map +1 -1
  89. package/dist/server/modules/sessions/session-live-runtime-service.d.ts +6 -0
  90. package/dist/server/modules/sessions/session-live-runtime-service.js +97 -11
  91. package/dist/server/modules/sessions/session-live-runtime-service.js.map +1 -1
  92. package/dist/server/modules/sessions/session-message-origin-utils.d.ts +12 -0
  93. package/dist/server/modules/sessions/session-message-origin-utils.js +45 -0
  94. package/dist/server/modules/sessions/session-message-origin-utils.js.map +1 -0
  95. package/dist/server/modules/sessions/session-permission-request-service.js +167 -0
  96. package/dist/server/modules/sessions/session-permission-request-service.js.map +1 -1
  97. package/dist/server/modules/skills/builtin-skill-service.js +1 -1
  98. package/dist/server/modules/skills/builtin-skill-service.js.map +1 -1
  99. package/dist/server/modules/skills/builtin-skills/codingns-assistant/SKILL.md +19 -12
  100. package/dist/server/modules/skills/builtin-skills/codingns-assistant/references/cli-workflow.md +9 -3
  101. package/dist/server/modules/tasks/task-helper-client.d.ts +5 -2
  102. package/dist/server/modules/tasks/task-helper-client.js +118 -38
  103. package/dist/server/modules/tasks/task-helper-client.js.map +1 -1
  104. package/dist/server/modules/tasks/task-helper-process.js +94 -3
  105. package/dist/server/modules/tasks/task-helper-process.js.map +1 -1
  106. package/dist/server/modules/tasks/task-types.d.ts +3 -0
  107. package/dist/server/modules/tasks/task-types.js +4 -1
  108. package/dist/server/modules/tasks/task-types.js.map +1 -1
  109. package/dist/server/modules/terminal/command-template-service.d.ts +9 -0
  110. package/dist/server/modules/terminal/command-template-service.js +87 -5
  111. package/dist/server/modules/terminal/command-template-service.js.map +1 -1
  112. package/dist/server/modules/terminal/terminal-controller.d.ts +3 -0
  113. package/dist/server/modules/terminal/terminal-controller.js +41 -0
  114. package/dist/server/modules/terminal/terminal-controller.js.map +1 -1
  115. package/dist/server/modules/workbench/workbench-service.d.ts +3 -0
  116. package/dist/server/modules/workbench/workbench-service.js +4 -3
  117. package/dist/server/modules/workbench/workbench-service.js.map +1 -1
  118. package/dist/server/modules/workbench/workspace-file-watcher.d.ts +14 -6
  119. package/dist/server/modules/workbench/workspace-file-watcher.js +267 -57
  120. package/dist/server/modules/workbench/workspace-file-watcher.js.map +1 -1
  121. package/dist/server/modules/workbench/workspace-panel-snapshot-service.d.ts +2 -0
  122. package/dist/server/modules/workbench/workspace-panel-snapshot-service.js +32 -3
  123. package/dist/server/modules/workbench/workspace-panel-snapshot-service.js.map +1 -1
  124. package/dist/server/modules/worktree/worktree-manager.d.ts +9 -1
  125. package/dist/server/modules/worktree/worktree-manager.js +9 -1
  126. package/dist/server/modules/worktree/worktree-manager.js.map +1 -1
  127. package/dist/server/routes/assistant.js +19 -0
  128. package/dist/server/routes/assistant.js.map +1 -1
  129. package/dist/server/routes/butler.js +5 -0
  130. package/dist/server/routes/butler.js.map +1 -1
  131. package/dist/server/server/create-server.d.ts +8 -0
  132. package/dist/server/server/create-server.js +36 -13
  133. package/dist/server/server/create-server.js.map +1 -1
  134. package/dist/server/storage/repositories/assistant-automation-run-repository.d.ts +12 -0
  135. package/dist/server/storage/repositories/assistant-automation-run-repository.js +139 -0
  136. package/dist/server/storage/repositories/assistant-automation-run-repository.js.map +1 -0
  137. package/dist/server/storage/repositories/assistant-automation-task-repository.d.ts +15 -0
  138. package/dist/server/storage/repositories/assistant-automation-task-repository.js +173 -0
  139. package/dist/server/storage/repositories/assistant-automation-task-repository.js.map +1 -0
  140. package/dist/server/storage/repositories/assistant-sandbox-workspace-repository.d.ts +17 -0
  141. package/dist/server/storage/repositories/assistant-sandbox-workspace-repository.js +164 -0
  142. package/dist/server/storage/repositories/assistant-sandbox-workspace-repository.js.map +1 -0
  143. package/dist/server/storage/repositories/butler-control-session-repository.js +27 -3
  144. package/dist/server/storage/repositories/butler-control-session-repository.js.map +1 -1
  145. package/dist/server/storage/repositories/butler-control-timer-repository.d.ts +15 -0
  146. package/dist/server/storage/repositories/butler-control-timer-repository.js +157 -0
  147. package/dist/server/storage/repositories/butler-control-timer-repository.js.map +1 -0
  148. package/dist/server/storage/repositories/user-preference-profile-repository.js +6 -3
  149. package/dist/server/storage/repositories/user-preference-profile-repository.js.map +1 -1
  150. package/dist/server/storage/sqlite/client.js +239 -2
  151. package/dist/server/storage/sqlite/client.js.map +1 -1
  152. package/dist/server/storage/sqlite/schema.sql +107 -1
  153. package/dist/server/types/domain.d.ts +89 -2
  154. package/dist/server/ws/workbench-ws-hub.d.ts +14 -8
  155. package/dist/server/ws/workbench-ws-hub.js +299 -163
  156. package/dist/server/ws/workbench-ws-hub.js.map +1 -1
  157. package/node_modules/@codingns/session-sync-core/dist/providers/claude-code.d.ts +4 -1
  158. package/node_modules/@codingns/session-sync-core/dist/providers/claude-code.js +111 -3
  159. package/node_modules/@codingns/session-sync-core/dist/providers/claude-code.js.map +1 -1
  160. package/node_modules/@codingns/session-sync-core/dist/providers/codex.d.ts +6 -1
  161. package/node_modules/@codingns/session-sync-core/dist/providers/codex.js +306 -31
  162. package/node_modules/@codingns/session-sync-core/dist/providers/codex.js.map +1 -1
  163. package/node_modules/@codingns/session-sync-core/dist/providers/gemini.d.ts +5 -1
  164. package/node_modules/@codingns/session-sync-core/dist/providers/gemini.js +187 -26
  165. package/node_modules/@codingns/session-sync-core/dist/providers/gemini.js.map +1 -1
  166. package/node_modules/@codingns/session-sync-core/dist/providers/kimi.d.ts +4 -1
  167. package/node_modules/@codingns/session-sync-core/dist/providers/kimi.js +98 -1
  168. package/node_modules/@codingns/session-sync-core/dist/providers/kimi.js.map +1 -1
  169. package/node_modules/@codingns/session-sync-core/dist/providers/opencode.d.ts +2 -0
  170. package/node_modules/@codingns/session-sync-core/dist/providers/opencode.js +71 -8
  171. package/node_modules/@codingns/session-sync-core/dist/providers/opencode.js.map +1 -1
  172. package/node_modules/@codingns/session-sync-core/dist/providers/utils.d.ts +1 -0
  173. package/node_modules/@codingns/session-sync-core/dist/providers/utils.js +4 -1
  174. package/node_modules/@codingns/session-sync-core/dist/providers/utils.js.map +1 -1
  175. package/node_modules/@codingns/session-sync-core/dist/runtime/claude-runtime.js +44 -0
  176. package/node_modules/@codingns/session-sync-core/dist/runtime/claude-runtime.js.map +1 -1
  177. package/node_modules/@codingns/session-sync-core/dist/runtime/codex-runtime.js +9 -3
  178. package/node_modules/@codingns/session-sync-core/dist/runtime/codex-runtime.js.map +1 -1
  179. package/node_modules/@codingns/session-sync-core/dist/runtime/types.d.ts +1 -0
  180. package/node_modules/@codingns/session-sync-core/dist/services.js +17 -8
  181. package/node_modules/@codingns/session-sync-core/dist/services.js.map +1 -1
  182. package/node_modules/@codingns/session-sync-core/dist/types.d.ts +4 -0
  183. package/package.json +1 -1
  184. package/dist/public/assets/index-BlOinYqR.js +0 -122
  185. package/dist/public/assets/index-Dg_7g6lA.css +0 -1
@@ -32,9 +32,13 @@ const SESSION_START_DEFERRED_PROVIDERS = new Set([
32
32
  ]);
33
33
  const MUTABLE_HISTORY_TAIL_REFRESH_INTERVAL_MS = 1_200;
34
34
  const WORKSPACE_DISCOVERY_BACKGROUND_MAX_AGE_MS = 15_000;
35
+ const WORKSPACE_DISCOVERY_SCAN_CONCURRENCY = 2;
35
36
  const PROVIDER_CAPABILITY_CACHE_MAX_AGE_MS = 5_000;
36
37
  const WORKSPACE_DISCOVERY_PERSIST_BATCH_SIZE = 25;
37
38
  const SESSION_TRANSACTION_HOTSPOT_THRESHOLD_MS = 150;
39
+ const WORKSPACE_STATE_REFRESH_COOLDOWN_MS = 1_500;
40
+ const SQLITE_BUSY_RETRY_LIMIT = 3;
41
+ const SQLITE_BUSY_RETRY_DELAY_MS = 100;
38
42
  export class SessionHistoryService {
39
43
  db;
40
44
  workspaceRepository;
@@ -59,10 +63,11 @@ export class SessionHistoryService {
59
63
  providerSessionDiscoveryConfig;
60
64
  taskManager;
61
65
  workspaceDiscoveryStatuses = new Map();
62
- workspaceStateRefreshInflight = new Map();
66
+ workspaceStateRefreshStatuses = new Map();
63
67
  providerCapabilityCache = new Map();
64
68
  liveActivityObservationResolvers = new Set();
65
69
  workspaceSessionRelations = new Map();
70
+ workspaceStateRefreshTaskSequence = 0;
66
71
  constructor(db, workspaceRepository, sessionBindingRepository, sessionChangedFileService, sessionIndexRepository, sessionMessageAttachmentService, sessionStateRepository, sessionStatusSnapshotRepository, config, sessionActivityAuthorityService = new SessionActivityAuthorityService(), sessionMessageOriginRepository = null, sessionForkRepository = null, adapterOverrides = {}, taskManager = createTaskManager()) {
67
72
  this.db = db;
68
73
  this.workspaceRepository = workspaceRepository;
@@ -159,6 +164,7 @@ export class SessionHistoryService {
159
164
  this.taskManager.register({
160
165
  taskType: HOST_TASK_TYPES.workspaceDiscoveryScan,
161
166
  executionLane: "helper_process",
167
+ concurrency: WORKSPACE_DISCOVERY_SCAN_CONCURRENCY,
162
168
  helperProcessHandler: "session.workspace_discovery",
163
169
  run: async ({ config, workspacePath, knownSessions }, context) => await discoverWorkspaceSessionsInRuntime(config, workspacePath, knownSessions, context.signal)
164
170
  });
@@ -319,6 +325,12 @@ export class SessionHistoryService {
319
325
  originRef: null
320
326
  };
321
327
  }
328
+ resolveMessageOriginByClientRequestId(sessionId, clientRequestId, messageId, updatedAt) {
329
+ if (!this.sessionMessageOriginRepository || !clientRequestId || !messageId) {
330
+ return;
331
+ }
332
+ this.sessionMessageOriginRepository.resolveMessageId(sessionId, clientRequestId, messageId, updatedAt);
333
+ }
322
334
  async findLatestUserMessage(sessionId, content, maxAttempts = 12, minTimestamp = null) {
323
335
  const binding = this.getBindingOrThrow(sessionId);
324
336
  const acceptedContents = new Set((Array.isArray(content) ? content : [content]).filter((value) => value.trim().length > 0));
@@ -1215,7 +1227,7 @@ export class SessionHistoryService {
1215
1227
  const existingIndex = existing
1216
1228
  ? this.sessionIndexRepository.findIndexRecordBySessionId(existing.sessionId)
1217
1229
  : null;
1218
- this.sessionBindingRepository.upsert({
1230
+ const nextBinding = {
1219
1231
  sessionId,
1220
1232
  workspaceId: workspace.id,
1221
1233
  provider: session.provider,
@@ -1223,7 +1235,10 @@ export class SessionHistoryService {
1223
1235
  rawStoreRef: session.rawStoreRef,
1224
1236
  createdAt,
1225
1237
  updatedAt: timestamp
1226
- });
1238
+ };
1239
+ if (!areEquivalentSessionBindings(existing, nextBinding)) {
1240
+ this.sessionBindingRepository.upsert(nextBinding);
1241
+ }
1227
1242
  const preservedParentSessionId = existingIndex?.parentSessionId
1228
1243
  ?? this.sessionForkRepository.findBySessionId(sessionId)?.parentSessionId
1229
1244
  ?? null;
@@ -1231,7 +1246,7 @@ export class SessionHistoryService {
1231
1246
  ? this.sessionIndexRepository.findIndexRecordBySessionId(preservedParentSessionId)?.title ?? null
1232
1247
  : null;
1233
1248
  const preservedTitle = resolvePersistedSessionTitle(session.provider, session.title, existingIndex?.title ?? null, preservedParentTitle);
1234
- this.sessionIndexRepository.upsert({
1249
+ const nextIndex = {
1235
1250
  sessionId,
1236
1251
  workspaceId: workspace.id,
1237
1252
  provider: session.provider,
@@ -1243,12 +1258,15 @@ export class SessionHistoryService {
1243
1258
  subagentLabel: existingIndex?.subagentLabel ?? null,
1244
1259
  title: preservedTitle,
1245
1260
  messageCount: session.messageCount,
1246
- isArchived: resolveDiscoveredArchiveState(existingIndex?.isArchived ?? false, session.isArchived),
1261
+ isArchived: resolveDiscoveredArchiveState(session.provider, existingIndex?.isArchived ?? false, session.isArchived),
1247
1262
  lastMessageAt: session.lastMessageAt,
1248
1263
  createdAt,
1249
1264
  updatedAt: timestamp
1250
- });
1251
- this.sessionStatusSnapshotRepository.upsert({
1265
+ };
1266
+ if (!areEquivalentSessionIndexRecords(existingIndex, nextIndex)) {
1267
+ this.sessionIndexRepository.upsert(nextIndex);
1268
+ }
1269
+ const nextSnapshot = {
1252
1270
  sessionId,
1253
1271
  syncStatus: currentSnapshot?.syncStatus ?? "idle",
1254
1272
  syncCursor: currentSnapshot?.syncCursor ?? null,
@@ -1257,13 +1275,17 @@ export class SessionHistoryService {
1257
1275
  lastErrorDetail: currentSnapshot?.lastErrorDetail ?? null,
1258
1276
  resumedAt: currentSnapshot?.resumedAt ?? null,
1259
1277
  updatedAt: timestamp
1260
- });
1278
+ };
1279
+ if (!areEquivalentSessionStatusSnapshots(currentSnapshot, nextSnapshot)) {
1280
+ this.sessionStatusSnapshotRepository.upsert(nextSnapshot);
1281
+ }
1261
1282
  discoveredSessionIds.set(buildProviderSessionKey(session.provider, session.providerSessionId), sessionId);
1262
1283
  persistedSessions.push({
1263
1284
  session,
1264
1285
  sessionId,
1265
1286
  createdAt,
1266
- existingIndex
1287
+ existingIndex,
1288
+ pass1Index: nextIndex
1267
1289
  });
1268
1290
  }
1269
1291
  });
@@ -1293,7 +1315,7 @@ export class SessionHistoryService {
1293
1315
  const resolvedParentTitle = resolvedParentSessionId
1294
1316
  ? this.sessionIndexRepository.findIndexRecordBySessionId(resolvedParentSessionId)?.title ?? null
1295
1317
  : null;
1296
- this.sessionIndexRepository.upsert({
1318
+ const nextIndex = {
1297
1319
  sessionId: persistedSession.sessionId,
1298
1320
  workspaceId: workspace.id,
1299
1321
  provider: persistedSession.session.provider,
@@ -1315,11 +1337,14 @@ export class SessionHistoryService {
1315
1337
  ?? null,
1316
1338
  title: resolvePersistedSessionTitle(persistedSession.session.provider, persistedSession.session.title, persistedSession.existingIndex?.title ?? null, resolvedParentTitle),
1317
1339
  messageCount: persistedSession.session.messageCount,
1318
- isArchived: resolveDiscoveredArchiveState(persistedSession.existingIndex?.isArchived ?? false, persistedSession.session.isArchived),
1340
+ isArchived: resolveDiscoveredArchiveState(persistedSession.session.provider, persistedSession.existingIndex?.isArchived ?? false, persistedSession.session.isArchived),
1319
1341
  lastMessageAt: persistedSession.session.lastMessageAt,
1320
1342
  createdAt: persistedSession.createdAt,
1321
1343
  updatedAt: timestamp
1322
- });
1344
+ };
1345
+ if (!areEquivalentSessionIndexRecords(persistedSession.pass1Index, nextIndex)) {
1346
+ this.sessionIndexRepository.upsert(nextIndex);
1347
+ }
1323
1348
  }
1324
1349
  });
1325
1350
  const persistPass2StartedAt = Date.now();
@@ -1387,7 +1412,22 @@ export class SessionHistoryService {
1387
1412
  discoveredSessions: sessions.length,
1388
1413
  returnedSessions: nextItems.length,
1389
1414
  discoveryComplete: discovery.isComplete,
1390
- providerDiagnostics: (discovery.providerDiagnostics ?? []).map((entry) => `${entry.provider}:${entry.status}:${Math.round(entry.durationMs)}ms`),
1415
+ providerDiagnostics: (discovery.providerDiagnostics ?? []).map((entry) => {
1416
+ const scannedFiles = entry.scannedFiles ?? 0;
1417
+ const skippedByMtimeSize = entry.skippedByMtimeSize ?? 0;
1418
+ const parsedFiles = entry.parsedFiles ?? 0;
1419
+ const bytesRead = entry.bytesRead ?? 0;
1420
+ return [
1421
+ entry.provider,
1422
+ entry.status,
1423
+ `${Math.round(entry.durationMs)}ms`,
1424
+ `sessions=${entry.sessionCount}`,
1425
+ `scanned=${scannedFiles}`,
1426
+ `skipped=${skippedByMtimeSize}`,
1427
+ `parsed=${parsedFiles}`,
1428
+ `bytes=${bytesRead}`
1429
+ ].join(":");
1430
+ }),
1391
1431
  refreshedStates: refreshCandidates.length,
1392
1432
  discoverMs: discoverDurationMs,
1393
1433
  persistMs: persistDurationMs,
@@ -1542,13 +1582,6 @@ export class SessionHistoryService {
1542
1582
  const originByMessageId = new Map(originRows
1543
1583
  .filter((row) => row.messageId)
1544
1584
  .map((row) => [row.messageId, row]));
1545
- const unresolvedRows = originRepository.listUnresolvedBySessionAndContents(sessionId, [...new Set(messages.map((message) => message.content).filter((content) => content.trim().length > 0))]);
1546
- const unresolvedByContent = new Map();
1547
- for (const row of unresolvedRows) {
1548
- const current = unresolvedByContent.get(row.content) ?? [];
1549
- current.push(row);
1550
- unresolvedByContent.set(row.content, current);
1551
- }
1552
1585
  return messages.map((message) => {
1553
1586
  const resolved = originByMessageId.get(message.messageId) ?? null;
1554
1587
  if (resolved) {
@@ -1565,21 +1598,10 @@ export class SessionHistoryService {
1565
1598
  originRef: null
1566
1599
  };
1567
1600
  }
1568
- const candidates = unresolvedByContent.get(message.content) ?? [];
1569
- const matched = candidates.find((row) => isMessageAtOrAfter(message.timestamp, row.createdAt)) ?? null;
1570
- if (!matched) {
1571
- return {
1572
- ...message,
1573
- origin: null,
1574
- originRef: null
1575
- };
1576
- }
1577
- originRepository.resolveMessageId(sessionId, matched.clientRequestId, message.messageId, message.timestamp);
1578
- unresolvedByContent.set(message.content, candidates.filter((candidate) => candidate.clientRequestId !== matched.clientRequestId));
1579
1601
  return {
1580
1602
  ...message,
1581
- origin: matched.origin,
1582
- originRef: matched.originRef
1603
+ origin: null,
1604
+ originRef: null
1583
1605
  };
1584
1606
  });
1585
1607
  }
@@ -1718,6 +1740,9 @@ export class SessionHistoryService {
1718
1740
  if (shouldSkipClaudePendingBinding(binding)) {
1719
1741
  return;
1720
1742
  }
1743
+ if (!shouldSyncSessionTitleFromProvider(binding.provider, currentIndex.title)) {
1744
+ return;
1745
+ }
1721
1746
  const nextTitle = (await this.providerDiscoveryHelperClient.readSessionTitle({
1722
1747
  config: this.providerSessionDiscoveryConfig,
1723
1748
  provider: binding.provider,
@@ -1926,13 +1951,71 @@ export class SessionHistoryService {
1926
1951
  return;
1927
1952
  }
1928
1953
  const inflightKey = `${workspaceId}:${userId}`;
1929
- if (this.workspaceStateRefreshInflight.has(inflightKey)) {
1954
+ const refreshState = this.getOrCreateWorkspaceStateRefreshStatus(inflightKey);
1955
+ const now = Date.now();
1956
+ refreshState.lastRequestedAt = now;
1957
+ refreshState.phase = refreshState.phase === "fresh" ? "stale" : refreshState.phase;
1958
+ refreshState.dirtyReasons.add("workspace.discovery.deferred_state_refresh");
1959
+ mergeWorkspaceStateRefreshSessions(refreshState.pendingSessions, sessions);
1960
+ if (refreshState.phase === "running" && refreshState.runningPromise) {
1961
+ return;
1962
+ }
1963
+ if (this.isWorkspaceStateRefreshCoolingDown(refreshState, now)) {
1964
+ refreshState.phase = "stale";
1965
+ this.ensureWorkspaceStateRefreshCooldownTimer(inflightKey, workspaceId, userId, refreshState);
1930
1966
  return;
1931
1967
  }
1968
+ this.startWorkspaceStateRefreshTask(inflightKey, workspaceId, userId, refreshState);
1969
+ }
1970
+ getOrCreateWorkspaceStateRefreshStatus(key) {
1971
+ const existing = this.workspaceStateRefreshStatuses.get(key);
1972
+ if (existing) {
1973
+ return existing;
1974
+ }
1975
+ const created = {
1976
+ phase: "fresh",
1977
+ dirtyReasons: new Set(),
1978
+ pendingSessions: new Map(),
1979
+ runningPromise: null,
1980
+ cooldownTimer: null,
1981
+ lastRequestedAt: null,
1982
+ lastStartedAt: null,
1983
+ lastCompletedAt: null,
1984
+ lastFailedAt: null,
1985
+ nextAllowedAt: null,
1986
+ runningTaskId: null
1987
+ };
1988
+ this.workspaceStateRefreshStatuses.set(key, created);
1989
+ return created;
1990
+ }
1991
+ isWorkspaceStateRefreshCoolingDown(state, now) {
1992
+ if (state.nextAllowedAt === null || now >= state.nextAllowedAt) {
1993
+ return false;
1994
+ }
1995
+ return state.phase === "cooldown" || state.phase === "failed";
1996
+ }
1997
+ startWorkspaceStateRefreshTask(key, workspaceId, userId, state) {
1998
+ if (state.runningPromise) {
1999
+ return;
2000
+ }
2001
+ const sessions = [...state.pendingSessions.values()];
2002
+ if (sessions.length === 0) {
2003
+ state.phase = "fresh";
2004
+ state.dirtyReasons.clear();
2005
+ return;
2006
+ }
2007
+ state.pendingSessions.clear();
2008
+ state.phase = "running";
2009
+ state.lastStartedAt = Date.now();
2010
+ state.runningTaskId = `${key}:${++this.workspaceStateRefreshTaskSequence}`;
1932
2011
  const startedAt = Date.now();
1933
2012
  const task = delay(0)
1934
2013
  .then(() => this.refreshRecentSessionStates(sessions, userId))
1935
2014
  .then(() => {
2015
+ state.lastCompletedAt = Date.now();
2016
+ state.phase = "cooldown";
2017
+ state.nextAllowedAt = state.lastCompletedAt + WORKSPACE_STATE_REFRESH_COOLDOWN_MS;
2018
+ state.dirtyReasons.clear();
1936
2019
  logPerformance("workspace.refresh_recent_session_states", Date.now() - startedAt, {
1937
2020
  workspaceId,
1938
2021
  refreshedStates: sessions.length
@@ -1941,6 +2024,9 @@ export class SessionHistoryService {
1941
2024
  });
1942
2025
  })
1943
2026
  .catch((error) => {
2027
+ state.lastFailedAt = Date.now();
2028
+ state.phase = "failed";
2029
+ state.nextAllowedAt = state.lastFailedAt + WORKSPACE_STATE_REFRESH_COOLDOWN_MS;
1944
2030
  logPerformance("workspace.refresh_recent_session_states.failed", Date.now() - startedAt, {
1945
2031
  workspaceId,
1946
2032
  refreshedStates: sessions.length,
@@ -1951,9 +2037,42 @@ export class SessionHistoryService {
1951
2037
  });
1952
2038
  })
1953
2039
  .finally(() => {
1954
- this.workspaceStateRefreshInflight.delete(inflightKey);
2040
+ state.runningPromise = null;
2041
+ state.runningTaskId = null;
2042
+ if (state.pendingSessions.size === 0) {
2043
+ if (state.phase === "cooldown") {
2044
+ this.ensureWorkspaceStateRefreshCooldownTimer(key, workspaceId, userId, state);
2045
+ return;
2046
+ }
2047
+ if (state.phase === "failed") {
2048
+ this.ensureWorkspaceStateRefreshCooldownTimer(key, workspaceId, userId, state);
2049
+ return;
2050
+ }
2051
+ state.phase = "fresh";
2052
+ return;
2053
+ }
2054
+ state.phase = "stale";
2055
+ this.ensureWorkspaceStateRefreshCooldownTimer(key, workspaceId, userId, state);
1955
2056
  });
1956
- this.workspaceStateRefreshInflight.set(inflightKey, task);
2057
+ state.runningPromise = task;
2058
+ }
2059
+ ensureWorkspaceStateRefreshCooldownTimer(key, workspaceId, userId, state) {
2060
+ if (state.cooldownTimer) {
2061
+ return;
2062
+ }
2063
+ const now = Date.now();
2064
+ const delayMs = Math.max(0, (state.nextAllowedAt ?? now) - now);
2065
+ state.cooldownTimer = setTimeout(() => {
2066
+ state.cooldownTimer = null;
2067
+ if (state.pendingSessions.size === 0) {
2068
+ state.phase = "fresh";
2069
+ state.dirtyReasons.clear();
2070
+ state.nextAllowedAt = null;
2071
+ return;
2072
+ }
2073
+ state.phase = "stale";
2074
+ this.startWorkspaceStateRefreshTask(key, workspaceId, userId, state);
2075
+ }, delayMs);
1957
2076
  }
1958
2077
  async cleanupStaleHiddenSessions(workspaceId, userId, sessions) {
1959
2078
  const discoveredProviderSessionIds = new Set(sessions.map((session) => buildProviderSessionKey(session.provider, session.providerSessionId)));
@@ -2532,6 +2651,11 @@ function buildSessionStateRefreshCandidates(items, recentCount) {
2532
2651
  }
2533
2652
  return Array.from(deduped.values());
2534
2653
  }
2654
+ function mergeWorkspaceStateRefreshSessions(target, sessions) {
2655
+ for (const session of sessions) {
2656
+ target.set(session.sessionId, session);
2657
+ }
2658
+ }
2535
2659
  function isSessionStateRefreshCandidate(item) {
2536
2660
  return item.activityState === "running"
2537
2661
  || item.runningState === "starting"
@@ -2609,12 +2733,20 @@ function mergeSessionIndexRecord(input) {
2609
2733
  subagentLabel: input.target?.subagentLabel ?? input.source?.subagentLabel ?? null,
2610
2734
  title: pickPreferredSessionTitle(input.target?.title ?? null, input.source?.title ?? null),
2611
2735
  messageCount: Math.max(input.target?.messageCount ?? 0, input.source?.messageCount ?? 0),
2612
- isArchived: Boolean(input.target?.isArchived || input.source?.isArchived),
2736
+ isArchived: mergePersistedArchiveState(input.provider, input.target?.isArchived, input.source?.isArchived),
2613
2737
  lastMessageAt: pickLaterIso(input.target?.lastMessageAt ?? null, input.source?.lastMessageAt ?? null),
2614
2738
  createdAt: pickEarlierIso(input.target?.createdAt ?? null, input.source?.createdAt ?? null) ?? input.timestamp,
2615
2739
  updatedAt: input.timestamp
2616
2740
  };
2617
2741
  }
2742
+ function mergePersistedArchiveState(provider, targetArchived, sourceArchived) {
2743
+ // 只有 Codex 这类真实支持归档的 provider 才认底层归档真相;
2744
+ // 其他 provider 的归档完全由 Host 本地索引维护,不能让旧副本把恢复状态再刷回去。
2745
+ if (shouldUseProviderDiscoveredArchiveState(provider)) {
2746
+ return Boolean(targetArchived || sourceArchived);
2747
+ }
2748
+ return targetArchived ?? sourceArchived ?? false;
2749
+ }
2618
2750
  function mergeSessionStatusSnapshot(input) {
2619
2751
  if (!input.target && !input.source) {
2620
2752
  return null;
@@ -2915,12 +3047,16 @@ function isCloseClaudeSessionTimestamp(left, right, maxGapMs = 2 * 60 * 1000) {
2915
3047
  }
2916
3048
  return Math.abs(leftAt - rightAt) <= maxGapMs;
2917
3049
  }
2918
- function resolveDiscoveredArchiveState(existingArchived, discoveredArchived) {
2919
- if (existingArchived) {
2920
- return true;
3050
+ function resolveDiscoveredArchiveState(provider, existingArchived, discoveredArchived) {
3051
+ if (!shouldUseProviderDiscoveredArchiveState(provider)) {
3052
+ return existingArchived;
2921
3053
  }
2922
3054
  return discoveredArchived === true;
2923
3055
  }
3056
+ function shouldUseProviderDiscoveredArchiveState(provider) {
3057
+ // 当前只有 Codex 的归档能稳定映射到底层文件位置;其余 provider 一律信本地 session_indices。
3058
+ return provider === "codex";
3059
+ }
2924
3060
  function isMessageAtOrAfter(timestamp, minTimestamp) {
2925
3061
  if (!minTimestamp) {
2926
3062
  return true;
@@ -3153,6 +3289,16 @@ function isSyntheticCodexSessionTitle(title) {
3153
3289
  return (/^rollout-\d{4}-\d{2}-\d{2}t/i.test(title) ||
3154
3290
  /^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$/i.test(title));
3155
3291
  }
3292
+ function shouldSyncSessionTitleFromProvider(provider, currentTitle) {
3293
+ const normalizedTitle = currentTitle?.trim() ?? "";
3294
+ if (normalizedTitle.length === 0) {
3295
+ return true;
3296
+ }
3297
+ if (provider === "codex" && isSyntheticCodexSessionTitle(normalizedTitle)) {
3298
+ return true;
3299
+ }
3300
+ return false;
3301
+ }
3156
3302
  function shouldRemoveHiddenClaudeDebugSession(session) {
3157
3303
  const normalizedRawStoreRef = session.rawStoreRef.replaceAll("\\", "/");
3158
3304
  if (normalizedRawStoreRef.includes("/subagents/")) {
@@ -3325,6 +3471,56 @@ function getAbortMessage(reason) {
3325
3471
  }
3326
3472
  return "任务已取消";
3327
3473
  }
3474
+ function areEquivalentSessionBindings(current, next) {
3475
+ if (!current) {
3476
+ return false;
3477
+ }
3478
+ return (current.sessionId === next.sessionId &&
3479
+ current.workspaceId === next.workspaceId &&
3480
+ current.provider === next.provider &&
3481
+ current.providerSessionId === next.providerSessionId &&
3482
+ current.rawStoreRef === next.rawStoreRef &&
3483
+ current.createdAt === next.createdAt);
3484
+ }
3485
+ function areEquivalentSessionIndexRecords(current, next) {
3486
+ if (!current) {
3487
+ return false;
3488
+ }
3489
+ return (current.sessionId === next.sessionId &&
3490
+ current.workspaceId === next.workspaceId &&
3491
+ current.provider === next.provider &&
3492
+ (current.parentSessionId ?? null) === (next.parentSessionId ?? null) &&
3493
+ (current.sessionKind ?? "default") === (next.sessionKind ?? "default") &&
3494
+ (current.annotationSourceMessageId ?? null) === (next.annotationSourceMessageId ?? null) &&
3495
+ (current.annotationSourceText ?? null) === (next.annotationSourceText ?? null) &&
3496
+ (current.isSubagent ?? false) === (next.isSubagent ?? false) &&
3497
+ (current.subagentLabel ?? null) === (next.subagentLabel ?? null) &&
3498
+ current.title === next.title &&
3499
+ current.messageCount === next.messageCount &&
3500
+ current.isArchived === next.isArchived &&
3501
+ (current.lastMessageAt ?? null) === (next.lastMessageAt ?? null) &&
3502
+ current.createdAt === next.createdAt);
3503
+ }
3504
+ function areEquivalentSessionStatusSnapshots(current, next) {
3505
+ if (!current) {
3506
+ return false;
3507
+ }
3508
+ return (current.sessionId === next.sessionId &&
3509
+ current.syncStatus === next.syncStatus &&
3510
+ (current.syncCursor ?? null) === (next.syncCursor ?? null) &&
3511
+ (current.lastSyncAt ?? null) === (next.lastSyncAt ?? null) &&
3512
+ (current.lastErrorCode ?? null) === (next.lastErrorCode ?? null) &&
3513
+ (current.lastErrorDetail ?? null) === (next.lastErrorDetail ?? null) &&
3514
+ (current.resumedAt ?? null) === (next.resumedAt ?? null));
3515
+ }
3516
+ function isSqliteBusyError(error) {
3517
+ if (!error || typeof error !== "object") {
3518
+ return false;
3519
+ }
3520
+ const sqliteCode = "code" in error ? error.code : null;
3521
+ const message = error instanceof Error ? error.message : String(error);
3522
+ return sqliteCode === "SQLITE_BUSY" || message.includes("database is locked");
3523
+ }
3328
3524
  async function runBatchedTransactions(items, batchSize, transaction, logOptions) {
3329
3525
  const normalizedBatchSize = Math.max(1, Math.floor(batchSize) || 1);
3330
3526
  let batchCount = 0;
@@ -3332,7 +3528,20 @@ async function runBatchedTransactions(items, batchSize, transaction, logOptions)
3332
3528
  for (let index = 0; index < items.length; index += normalizedBatchSize) {
3333
3529
  const batch = items.slice(index, index + normalizedBatchSize);
3334
3530
  const batchStartedAt = Date.now();
3335
- transaction(batch);
3531
+ let retryCount = 0;
3532
+ while (true) {
3533
+ try {
3534
+ transaction(batch);
3535
+ break;
3536
+ }
3537
+ catch (error) {
3538
+ if (!isSqliteBusyError(error) || retryCount >= SQLITE_BUSY_RETRY_LIMIT) {
3539
+ throw error;
3540
+ }
3541
+ retryCount += 1;
3542
+ await delay(SQLITE_BUSY_RETRY_DELAY_MS * retryCount);
3543
+ }
3544
+ }
3336
3545
  const batchDurationMs = Date.now() - batchStartedAt;
3337
3546
  const nextBatchIndex = batchCount + 1;
3338
3547
  if (logOptions) {
@@ -3341,6 +3550,7 @@ async function runBatchedTransactions(items, batchSize, transaction, logOptions)
3341
3550
  batchIndex: nextBatchIndex,
3342
3551
  batchSize: batch.length,
3343
3552
  batchStartIndex: index,
3553
+ retryCount,
3344
3554
  totalItems: items.length,
3345
3555
  configuredBatchSize: normalizedBatchSize
3346
3556
  }, {