@jingyi0605/codingns 0.3.0 → 0.3.5

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 (156) hide show
  1. package/dist/public/assets/{TerminalPage-Dfw1QUqW.js → TerminalPage-CgrfstRm.js} +19 -19
  2. package/dist/public/assets/index-Cek6u0b9.css +1 -0
  3. package/dist/public/assets/index-THHY79si.js +122 -0
  4. package/dist/public/index.html +2 -2
  5. package/dist/server/config/env.d.ts +2 -0
  6. package/dist/server/config/env.js +35 -0
  7. package/dist/server/config/env.js.map +1 -1
  8. package/dist/server/modules/auth/auth-service.d.ts +18 -1
  9. package/dist/server/modules/auth/auth-service.js +168 -7
  10. package/dist/server/modules/auth/auth-service.js.map +1 -1
  11. package/dist/server/modules/butler/butler-codex-model-policy.d.ts +1 -0
  12. package/dist/server/modules/butler/butler-codex-model-policy.js +36 -0
  13. package/dist/server/modules/butler/butler-codex-model-policy.js.map +1 -0
  14. package/dist/server/modules/butler/butler-control-session-service.d.ts +13 -1
  15. package/dist/server/modules/butler/butler-control-session-service.js +55 -231
  16. package/dist/server/modules/butler/butler-control-session-service.js.map +1 -1
  17. package/dist/server/modules/butler/butler-controller.d.ts +17 -0
  18. package/dist/server/modules/butler/butler-controller.js +20 -1
  19. package/dist/server/modules/butler/butler-controller.js.map +1 -1
  20. package/dist/server/modules/butler/butler-follow-up-service.js +7 -3
  21. package/dist/server/modules/butler/butler-follow-up-service.js.map +1 -1
  22. package/dist/server/modules/butler/butler-inbox-analysis-service.d.ts +36 -0
  23. package/dist/server/modules/butler/butler-inbox-analysis-service.js +375 -0
  24. package/dist/server/modules/butler/butler-inbox-analysis-service.js.map +1 -0
  25. package/dist/server/modules/butler/butler-inbox-instruction-adapter.d.ts +23 -0
  26. package/dist/server/modules/butler/butler-inbox-instruction-adapter.js +96 -0
  27. package/dist/server/modules/butler/butler-inbox-instruction-adapter.js.map +1 -0
  28. package/dist/server/modules/butler/butler-inbox-service.d.ts +39 -2
  29. package/dist/server/modules/butler/butler-inbox-service.js +392 -2
  30. package/dist/server/modules/butler/butler-inbox-service.js.map +1 -1
  31. package/dist/server/modules/butler/butler-session-service.d.ts +8 -0
  32. package/dist/server/modules/butler/butler-session-service.js +205 -53
  33. package/dist/server/modules/butler/butler-session-service.js.map +1 -1
  34. package/dist/server/modules/butler/butler-session-summary-service.d.ts +1 -0
  35. package/dist/server/modules/butler/butler-session-summary-service.js +48 -23
  36. package/dist/server/modules/butler/butler-session-summary-service.js.map +1 -1
  37. package/dist/server/modules/butler/butler-workspace-context.d.ts +13 -0
  38. package/dist/server/modules/butler/butler-workspace-context.js +223 -0
  39. package/dist/server/modules/butler/butler-workspace-context.js.map +1 -0
  40. package/dist/server/modules/client/client-controller.d.ts +6 -0
  41. package/dist/server/modules/client/client-controller.js +30 -8
  42. package/dist/server/modules/client/client-controller.js.map +1 -1
  43. package/dist/server/modules/client/client-service.d.ts +22 -10
  44. package/dist/server/modules/client/client-service.js +77 -100
  45. package/dist/server/modules/client/client-service.js.map +1 -1
  46. package/dist/server/modules/client/npm-global-package-service.d.ts +21 -0
  47. package/dist/server/modules/client/npm-global-package-service.js +210 -0
  48. package/dist/server/modules/client/npm-global-package-service.js.map +1 -0
  49. package/dist/server/modules/client/service-update-task-service.d.ts +15 -0
  50. package/dist/server/modules/client/service-update-task-service.js +147 -0
  51. package/dist/server/modules/client/service-update-task-service.js.map +1 -0
  52. package/dist/server/modules/client/service-update-types.d.ts +30 -0
  53. package/dist/server/modules/client/service-update-types.js +2 -0
  54. package/dist/server/modules/client/service-update-types.js.map +1 -0
  55. package/dist/server/modules/model-switch/cc-switch-adapter.d.ts +36 -0
  56. package/dist/server/modules/model-switch/cc-switch-adapter.js +317 -0
  57. package/dist/server/modules/model-switch/cc-switch-adapter.js.map +1 -0
  58. package/dist/server/modules/model-switch/model-switch-controller.d.ts +11 -0
  59. package/dist/server/modules/model-switch/model-switch-controller.js +30 -0
  60. package/dist/server/modules/model-switch/model-switch-controller.js.map +1 -0
  61. package/dist/server/modules/model-switch/model-switch-service.d.ts +16 -0
  62. package/dist/server/modules/model-switch/model-switch-service.js +29 -0
  63. package/dist/server/modules/model-switch/model-switch-service.js.map +1 -0
  64. package/dist/server/modules/preferences/profile-service.d.ts +1 -0
  65. package/dist/server/modules/preferences/profile-service.js +9 -0
  66. package/dist/server/modules/preferences/profile-service.js.map +1 -1
  67. package/dist/server/modules/sessions/codex-app-server-helper-process.js +3 -0
  68. package/dist/server/modules/sessions/codex-app-server-helper-process.js.map +1 -1
  69. package/dist/server/modules/sessions/session-activity-authority-service.d.ts +3 -1
  70. package/dist/server/modules/sessions/session-activity-authority-service.js +3 -0
  71. package/dist/server/modules/sessions/session-activity-authority-service.js.map +1 -1
  72. package/dist/server/modules/sessions/session-activity-inspector.d.ts +1 -1
  73. package/dist/server/modules/sessions/session-activity-inspector.js +43 -16
  74. package/dist/server/modules/sessions/session-activity-inspector.js.map +1 -1
  75. package/dist/server/modules/sessions/session-controller.d.ts +3 -3
  76. package/dist/server/modules/sessions/session-controller.js +3 -3
  77. package/dist/server/modules/sessions/session-controller.js.map +1 -1
  78. package/dist/server/modules/sessions/session-history-service.d.ts +3 -0
  79. package/dist/server/modules/sessions/session-history-service.js +259 -39
  80. package/dist/server/modules/sessions/session-history-service.js.map +1 -1
  81. package/dist/server/modules/sessions/session-live-runtime-service.d.ts +7 -3
  82. package/dist/server/modules/sessions/session-live-runtime-service.js +47 -11
  83. package/dist/server/modules/sessions/session-live-runtime-service.js.map +1 -1
  84. package/dist/server/modules/sessions/session-message-attachment-service.d.ts +8 -8
  85. package/dist/server/modules/sessions/session-message-attachment-service.js +25 -34
  86. package/dist/server/modules/sessions/session-message-attachment-service.js.map +1 -1
  87. package/dist/server/modules/sessions/session-provider-error-mapper.js +7 -0
  88. package/dist/server/modules/sessions/session-provider-error-mapper.js.map +1 -1
  89. package/dist/server/modules/tailscale/tailscale-manager.js +5 -1
  90. package/dist/server/modules/tailscale/tailscale-manager.js.map +1 -1
  91. package/dist/server/modules/tasks/task-lane-executors.js +3 -0
  92. package/dist/server/modules/tasks/task-lane-executors.js.map +1 -1
  93. package/dist/server/modules/tasks/task-types.d.ts +2 -0
  94. package/dist/server/modules/tasks/task-types.js +3 -1
  95. package/dist/server/modules/tasks/task-types.js.map +1 -1
  96. package/dist/server/modules/terminal/command-template-service.js +10 -1
  97. package/dist/server/modules/terminal/command-template-service.js.map +1 -1
  98. package/dist/server/modules/terminal/template-port-runtime.d.ts +12 -1
  99. package/dist/server/modules/terminal/template-port-runtime.js +189 -17
  100. package/dist/server/modules/terminal/template-port-runtime.js.map +1 -1
  101. package/dist/server/modules/terminal/terminal-service.js +30 -3
  102. package/dist/server/modules/terminal/terminal-service.js.map +1 -1
  103. package/dist/server/modules/workspace/workspace-code-composition.js +95 -2
  104. package/dist/server/modules/workspace/workspace-code-composition.js.map +1 -1
  105. package/dist/server/routes/butler.js +4 -0
  106. package/dist/server/routes/butler.js.map +1 -1
  107. package/dist/server/routes/client.js +2 -0
  108. package/dist/server/routes/client.js.map +1 -1
  109. package/dist/server/routes/sessions.js +1 -1
  110. package/dist/server/routes/sessions.js.map +1 -1
  111. package/dist/server/routes/system.d.ts +2 -1
  112. package/dist/server/routes/system.js +3 -1
  113. package/dist/server/routes/system.js.map +1 -1
  114. package/dist/server/server/create-server.d.ts +4 -0
  115. package/dist/server/server/create-server.js +51 -7
  116. package/dist/server/server/create-server.js.map +1 -1
  117. package/dist/server/shared/errors/app-error.d.ts +2 -0
  118. package/dist/server/shared/errors/app-error.js +2 -0
  119. package/dist/server/shared/errors/app-error.js.map +1 -1
  120. package/dist/server/shared/http/error-handler.d.ts +2 -1
  121. package/dist/server/shared/http/error-handler.js +3 -2
  122. package/dist/server/shared/http/error-handler.js.map +1 -1
  123. package/dist/server/storage/repositories/auth-login-attempt-repository.d.ts +9 -0
  124. package/dist/server/storage/repositories/auth-login-attempt-repository.js +59 -0
  125. package/dist/server/storage/repositories/auth-login-attempt-repository.js.map +1 -0
  126. package/dist/server/storage/repositories/butler-control-session-repository.d.ts +3 -0
  127. package/dist/server/storage/repositories/butler-control-session-repository.js +80 -4
  128. package/dist/server/storage/repositories/butler-control-session-repository.js.map +1 -1
  129. package/dist/server/storage/repositories/butler-inbox-item-repository.js +54 -3
  130. package/dist/server/storage/repositories/butler-inbox-item-repository.js.map +1 -1
  131. package/dist/server/storage/repositories/terminal-instance-repository.js +1 -1
  132. package/dist/server/storage/repositories/terminal-instance-repository.js.map +1 -1
  133. package/dist/server/storage/repositories/user-preference-profile-repository.js +6 -3
  134. package/dist/server/storage/repositories/user-preference-profile-repository.js.map +1 -1
  135. package/dist/server/storage/sqlite/client.js +127 -0
  136. package/dist/server/storage/sqlite/client.js.map +1 -1
  137. package/dist/server/storage/sqlite/schema.sql +19 -1
  138. package/dist/server/types/domain.d.ts +37 -1
  139. package/node_modules/@codingns/session-sync-core/dist/patch-builder.d.ts +23 -0
  140. package/node_modules/@codingns/session-sync-core/dist/patch-builder.js +162 -0
  141. package/node_modules/@codingns/session-sync-core/dist/patch-builder.js.map +1 -1
  142. package/node_modules/@codingns/session-sync-core/dist/providers/codex.d.ts +1 -0
  143. package/node_modules/@codingns/session-sync-core/dist/providers/codex.js +89 -33
  144. package/node_modules/@codingns/session-sync-core/dist/providers/codex.js.map +1 -1
  145. package/node_modules/@codingns/session-sync-core/dist/runtime/active-run-registry.js +18 -2
  146. package/node_modules/@codingns/session-sync-core/dist/runtime/active-run-registry.js.map +1 -1
  147. package/node_modules/@codingns/session-sync-core/dist/runtime/codex-runtime.d.ts +3 -1
  148. package/node_modules/@codingns/session-sync-core/dist/runtime/codex-runtime.js +238 -53
  149. package/node_modules/@codingns/session-sync-core/dist/runtime/codex-runtime.js.map +1 -1
  150. package/node_modules/@codingns/session-sync-core/dist/runtime/provider-runtime-service.js +1 -0
  151. package/node_modules/@codingns/session-sync-core/dist/runtime/provider-runtime-service.js.map +1 -1
  152. package/node_modules/@codingns/session-sync-core/dist/runtime/types.d.ts +6 -2
  153. package/node_modules/@codingns/session-sync-core/dist/types.d.ts +1 -1
  154. package/package.json +1 -1
  155. package/dist/public/assets/index-DR2rPNi7.css +0 -1
  156. package/dist/public/assets/index-DTOruahn.js +0 -114
@@ -1090,39 +1090,52 @@ export class SessionHistoryService {
1090
1090
  providerSessionId: snapshot.providerSessionId,
1091
1091
  rawStoreRef: snapshot.rawStoreRef
1092
1092
  });
1093
- const currentBinding = this.sessionBindingRepository.findBySessionId(sessionId);
1094
- const timestamp = nowIso();
1095
- const duplicateBinding = this.findSameWorkspaceBindingDuplicate(sessionId, workspaceId, resolvedSnapshot);
1096
- this.db.transaction(() => {
1097
- if (duplicateBinding) {
1098
- // 运行时链路显式指定了当前 sessionId,就应该由当前会话接管同工作区里的重复底层会话。
1099
- // 否则后续事件重放或后台发现补录都会持续撞 UNIQUE(provider, provider_session_id)
1100
- this.mergeSessionIntoTarget({
1101
- workspaceId,
1102
- targetSessionId: sessionId,
1103
- sourceSessionId: duplicateBinding.sessionId,
1104
- provider: resolvedSnapshot.provider,
1105
- timestamp
1106
- });
1093
+ // discovery runtime 回填会并发命中这里;如果在事务外先看重复,再事务内写入,
1094
+ // 中间就会留下一个竞态窗口,最后直接撞 UNIQUE(provider, provider_session_id)
1095
+ for (let attempt = 0; attempt < 2; attempt += 1) {
1096
+ try {
1097
+ this.db.transaction(() => {
1098
+ const currentBinding = this.sessionBindingRepository.findBySessionId(sessionId);
1099
+ const timestamp = nowIso();
1100
+ const duplicateBinding = this.findSameWorkspaceBindingDuplicate(sessionId, workspaceId, resolvedSnapshot);
1101
+ if (duplicateBinding) {
1102
+ // 运行时链路显式指定了当前 sessionId,就应该由当前会话接管同工作区里的重复底层会话。
1103
+ // 否则后续事件重放或后台发现补录都会持续撞 UNIQUE(provider, provider_session_id)。
1104
+ this.mergeSessionIntoTarget({
1105
+ workspaceId,
1106
+ targetSessionId: sessionId,
1107
+ sourceSessionId: duplicateBinding.sessionId,
1108
+ provider: resolvedSnapshot.provider,
1109
+ timestamp
1110
+ });
1111
+ }
1112
+ const currentIndex = this.sessionIndexRepository.findIndexRecordBySessionId(sessionId);
1113
+ this.sessionBindingRepository.upsert({
1114
+ sessionId,
1115
+ workspaceId,
1116
+ provider: resolvedSnapshot.provider,
1117
+ providerSessionId: resolvedSnapshot.providerSessionId,
1118
+ rawStoreRef: resolvedSnapshot.rawStoreRef,
1119
+ createdAt: pickEarlierIso(currentBinding?.createdAt ?? null, duplicateBinding?.createdAt ?? null)
1120
+ ?? timestamp,
1121
+ updatedAt: timestamp
1122
+ });
1123
+ if (currentIndex) {
1124
+ this.sessionIndexRepository.upsert({
1125
+ ...currentIndex,
1126
+ updatedAt: timestamp
1127
+ });
1128
+ }
1129
+ })();
1130
+ return;
1107
1131
  }
1108
- const currentIndex = this.sessionIndexRepository.findIndexRecordBySessionId(sessionId);
1109
- this.sessionBindingRepository.upsert({
1110
- sessionId,
1111
- workspaceId,
1112
- provider: resolvedSnapshot.provider,
1113
- providerSessionId: resolvedSnapshot.providerSessionId,
1114
- rawStoreRef: resolvedSnapshot.rawStoreRef,
1115
- createdAt: pickEarlierIso(currentBinding?.createdAt ?? null, duplicateBinding?.createdAt ?? null)
1116
- ?? timestamp,
1117
- updatedAt: timestamp
1118
- });
1119
- if (currentIndex) {
1120
- this.sessionIndexRepository.upsert({
1121
- ...currentIndex,
1122
- updatedAt: timestamp
1123
- });
1132
+ catch (error) {
1133
+ if (attempt === 0 && isSessionBindingProviderUniqueConflict(error)) {
1134
+ continue;
1135
+ }
1136
+ throw error;
1124
1137
  }
1125
- })();
1138
+ }
1126
1139
  }
1127
1140
  async runDiscoverWorkspaceSessions(workspaceId, userId, refreshStateMode = "inline") {
1128
1141
  const startedAt = Date.now();
@@ -1162,7 +1175,9 @@ export class SessionHistoryService {
1162
1175
  const claimedPendingSessionIds = new Set();
1163
1176
  const persistPass1Transaction = this.db.transaction((batch) => {
1164
1177
  for (const session of batch) {
1165
- const exactExisting = this.sessionBindingRepository.findByProviderSession(session.provider, session.providerSessionId) ?? this.sessionBindingRepository.findByRawStoreRef(session.provider, session.rawStoreRef);
1178
+ const exactExisting = this.sessionBindingRepository.findByProviderSession(session.provider, session.providerSessionId) ?? (shouldMatchSessionBindingByRawStoreRef(session.provider)
1179
+ ? this.sessionBindingRepository.findByRawStoreRef(session.provider, session.rawStoreRef)
1180
+ : null);
1166
1181
  if (exactExisting && exactExisting.workspaceId !== workspaceId) {
1167
1182
  continue;
1168
1183
  }
@@ -1405,12 +1420,13 @@ export class SessionHistoryService {
1405
1420
  ? this.sessionSyncService.readRecentHistory(provider, providerSessionId, rawStoreRef, knownTotalMessageCount, limit)
1406
1421
  : this.sessionSyncService.readHistory(provider, providerSessionId, rawStoreRef, cursor, limit, direction);
1407
1422
  return historyTask
1408
- .then((page) => {
1409
- const messagesWithAttachments = this.sessionMessageAttachmentService.enrichMessages(sessionId, page.messages);
1423
+ .then(async (page) => {
1424
+ const sanitizedPage = await this.sanitizeForkHistoryPage(sessionId, page, cursor, direction);
1425
+ const messagesWithAttachments = this.sessionMessageAttachmentService.enrichMessages(sessionId, sanitizedPage.messages);
1410
1426
  const messages = this.enrichMessagesWithOrigin(sessionId, messagesWithAttachments);
1411
1427
  this.persistSessionChangedFiles(sessionId, messages);
1412
1428
  return {
1413
- ...page,
1429
+ ...sanitizedPage,
1414
1430
  messages
1415
1431
  };
1416
1432
  })
@@ -1429,6 +1445,56 @@ export class SessionHistoryService {
1429
1445
  enrichMessagesWithOrigin(sessionId, messages) {
1430
1446
  return this.resolveMessageOrigins(sessionId, messages);
1431
1447
  }
1448
+ async sanitizeForkHistoryPage(sessionId, page, cursor, direction) {
1449
+ if (direction !== "forward" || cursor !== null || page.messages.length === 0) {
1450
+ return page;
1451
+ }
1452
+ const forkRecord = this.sessionForkRepository.findBySessionId(sessionId);
1453
+ if (!forkRecord
1454
+ || forkRecord.forkSourceType !== "message"
1455
+ || !forkRecord.forkSourceMessageId) {
1456
+ return page;
1457
+ }
1458
+ const childSession = this.sessionIndexRepository.findIndexRecordBySessionId(sessionId);
1459
+ const childCreatedAt = childSession?.createdAt?.trim() || null;
1460
+ if (!childCreatedAt) {
1461
+ return page;
1462
+ }
1463
+ const parentBinding = this.getBindingOrThrow(forkRecord.forkSourceSessionId);
1464
+ const inheritedMessages = await this.readForkSourceMessages(forkRecord.forkSourceSessionId, parentBinding, "message", forkRecord.forkSourceMessageId, null);
1465
+ const expectedInheritedCount = inheritedMessages.length;
1466
+ if (expectedInheritedCount <= 0) {
1467
+ return page;
1468
+ }
1469
+ const parentMessages = await this.readForkSourceMessages(forkRecord.forkSourceSessionId, parentBinding, "session", null, null);
1470
+ let leakedInheritedCount = countCommonHistoryPrefixLength(page.messages.slice(expectedInheritedCount), parentMessages.slice(expectedInheritedCount));
1471
+ if (leakedInheritedCount <= 0) {
1472
+ for (let index = expectedInheritedCount; index < page.messages.length; index += 1) {
1473
+ const message = page.messages[index];
1474
+ if (!message || message.timestamp > childCreatedAt) {
1475
+ break;
1476
+ }
1477
+ leakedInheritedCount += 1;
1478
+ }
1479
+ }
1480
+ if (forkRecord.inheritedPrefixMessageCount !== expectedInheritedCount) {
1481
+ this.sessionForkRepository.upsert({
1482
+ ...forkRecord,
1483
+ inheritedPrefixMessageCount: expectedInheritedCount
1484
+ });
1485
+ }
1486
+ if (leakedInheritedCount <= 0) {
1487
+ return page;
1488
+ }
1489
+ return {
1490
+ ...page,
1491
+ messages: [
1492
+ ...page.messages.slice(0, expectedInheritedCount),
1493
+ ...page.messages.slice(expectedInheritedCount + leakedInheritedCount)
1494
+ ],
1495
+ total: Math.max(0, page.total - leakedInheritedCount)
1496
+ };
1497
+ }
1432
1498
  resolveMessageOrigins(sessionId, messages) {
1433
1499
  const originRepository = this.sessionMessageOriginRepository;
1434
1500
  if (!originRepository || messages.length === 0) {
@@ -1683,7 +1749,13 @@ export class SessionHistoryService {
1683
1749
  return workspace;
1684
1750
  }
1685
1751
  getSessionListItemOrThrow(sessionId, userId) {
1686
- const item = this.sessionIndexRepository.findBySessionId(sessionId, userId);
1752
+ const canonicalSessionId = this.resolveCanonicalSessionId(sessionId, userId);
1753
+ const item = this.findSessionListItem(canonicalSessionId, sessionId, userId)
1754
+ ?? this.repairMissingSessionListItem(canonicalSessionId, userId)
1755
+ ?? (canonicalSessionId === sessionId
1756
+ ? null
1757
+ : this.repairMissingSessionListItem(sessionId, userId))
1758
+ ?? this.findSessionListItem(canonicalSessionId, sessionId, userId);
1687
1759
  if (!item) {
1688
1760
  throw new AppError({
1689
1761
  statusCode: 500,
@@ -1697,6 +1769,75 @@ export class SessionHistoryService {
1697
1769
  }
1698
1770
  return this.sessionIndexRepository.findBySessionId(aliasTargetSessionId, userId) ?? item;
1699
1771
  }
1772
+ findSessionListItem(canonicalSessionId, sessionId, userId) {
1773
+ return (this.sessionIndexRepository.findBySessionId(canonicalSessionId, userId)
1774
+ ?? (canonicalSessionId === sessionId
1775
+ ? null
1776
+ : this.sessionIndexRepository.findBySessionId(sessionId, userId)));
1777
+ }
1778
+ repairMissingSessionListItem(sessionId, userId) {
1779
+ const binding = this.sessionBindingRepository.findBySessionId(sessionId);
1780
+ if (!binding) {
1781
+ return null;
1782
+ }
1783
+ const existingIndex = this.sessionIndexRepository.findIndexRecordBySessionId(sessionId);
1784
+ const existingSnapshot = this.sessionStatusSnapshotRepository.findBySessionId(sessionId);
1785
+ const existingState = this.sessionStateRepository.findBySessionAndUser(sessionId, userId);
1786
+ const timestamp = nowIso();
1787
+ const fallbackLastMessageAt = existingIndex?.lastMessageAt
1788
+ ?? existingState?.lastEventAt
1789
+ ?? existingSnapshot?.lastSyncAt
1790
+ ?? null;
1791
+ const fallbackCreatedAt = pickEarlierIso(binding.createdAt, existingIndex?.createdAt ?? null)
1792
+ ?? timestamp;
1793
+ this.db.transaction(() => {
1794
+ this.sessionIndexRepository.upsert({
1795
+ sessionId,
1796
+ workspaceId: binding.workspaceId,
1797
+ provider: binding.provider,
1798
+ parentSessionId: existingIndex?.parentSessionId ?? this.sessionForkRepository.findBySessionId(sessionId)?.parentSessionId ?? null,
1799
+ sessionKind: existingIndex?.sessionKind ?? "default",
1800
+ annotationSourceMessageId: existingIndex?.annotationSourceMessageId ?? null,
1801
+ annotationSourceText: existingIndex?.annotationSourceText ?? null,
1802
+ isSubagent: existingIndex?.isSubagent ?? false,
1803
+ subagentLabel: existingIndex?.subagentLabel ?? null,
1804
+ title: existingIndex?.title?.trim()
1805
+ || buildRecoveredSessionTitle(binding.provider, binding.providerSessionId),
1806
+ messageCount: existingIndex?.messageCount ?? 0,
1807
+ isArchived: existingIndex?.isArchived ?? false,
1808
+ lastMessageAt: fallbackLastMessageAt,
1809
+ createdAt: fallbackCreatedAt,
1810
+ updatedAt: timestamp
1811
+ });
1812
+ if (!existingSnapshot) {
1813
+ this.sessionStatusSnapshotRepository.upsert({
1814
+ sessionId,
1815
+ syncStatus: "idle",
1816
+ syncCursor: null,
1817
+ lastSyncAt: fallbackLastMessageAt,
1818
+ lastErrorCode: null,
1819
+ lastErrorDetail: null,
1820
+ resumedAt: null,
1821
+ updatedAt: timestamp
1822
+ });
1823
+ }
1824
+ if (!existingState) {
1825
+ this.sessionStateRepository.upsert({
1826
+ sessionId,
1827
+ userId,
1828
+ runningState: inferRecoveredSessionRunningState(binding),
1829
+ activitySource: inferRecoveredSessionActivitySource(binding),
1830
+ favorite: false,
1831
+ lastEventAt: shouldRecoverSessionAsActive(binding) ? (binding.updatedAt || timestamp) : fallbackLastMessageAt,
1832
+ completedAt: null,
1833
+ lastSeenAt: null,
1834
+ updatedAt: timestamp
1835
+ });
1836
+ }
1837
+ })();
1838
+ console.warn(`[session-history] repaired missing session index for ${sessionId} (${binding.provider})`);
1839
+ return this.sessionIndexRepository.findBySessionId(sessionId, userId);
1840
+ }
1700
1841
  resolveCanonicalSessionId(sessionId, userId) {
1701
1842
  if (userId) {
1702
1843
  const item = this.sessionIndexRepository.findBySessionId(sessionId, userId);
@@ -1812,7 +1953,9 @@ export class SessionHistoryService {
1812
1953
  if (isPendingBindingValue(snapshot.providerSessionId)) {
1813
1954
  return null;
1814
1955
  }
1815
- const existing = this.sessionBindingRepository.findByProviderSession(snapshot.provider, snapshot.providerSessionId) ?? this.sessionBindingRepository.findByRawStoreRef(snapshot.provider, snapshot.rawStoreRef);
1956
+ const existing = this.sessionBindingRepository.findByProviderSession(snapshot.provider, snapshot.providerSessionId) ?? (shouldMatchSessionBindingByRawStoreRef(snapshot.provider)
1957
+ ? this.sessionBindingRepository.findByRawStoreRef(snapshot.provider, snapshot.rawStoreRef)
1958
+ : null);
1816
1959
  if (!existing || existing.sessionId === sessionId) {
1817
1960
  return null;
1818
1961
  }
@@ -1825,11 +1968,22 @@ export class SessionHistoryService {
1825
1968
  if (input.targetSessionId === input.sourceSessionId) {
1826
1969
  return;
1827
1970
  }
1828
- const targetBinding = this.sessionBindingRepository.findBySessionId(input.targetSessionId);
1829
1971
  const sourceBinding = this.sessionBindingRepository.findBySessionId(input.sourceSessionId);
1830
- if (!targetBinding || !sourceBinding) {
1972
+ if (!sourceBinding) {
1831
1973
  return;
1832
1974
  }
1975
+ const targetBinding = this.sessionBindingRepository.findBySessionId(input.targetSessionId);
1976
+ if (!targetBinding) {
1977
+ this.sessionBindingRepository.upsert({
1978
+ sessionId: input.targetSessionId,
1979
+ workspaceId: input.workspaceId,
1980
+ provider: input.provider,
1981
+ providerSessionId: buildPendingBindingValue(input.provider, input.targetSessionId),
1982
+ rawStoreRef: buildPendingBindingValue(input.provider, input.targetSessionId),
1983
+ createdAt: sourceBinding.createdAt,
1984
+ updatedAt: input.timestamp
1985
+ });
1986
+ }
1833
1987
  const targetIndex = this.sessionIndexRepository.findIndexRecordBySessionId(input.targetSessionId);
1834
1988
  const sourceIndex = this.sessionIndexRepository.findIndexRecordBySessionId(input.sourceSessionId);
1835
1989
  const targetSnapshot = this.sessionStatusSnapshotRepository.findBySessionId(input.targetSessionId);
@@ -2215,6 +2369,7 @@ function buildInspectionActivityObservation(sessionId, inspection, observedAt) {
2215
2369
  source: hasInspectionEvidence(inspection) ? "inferred_log" : "unknown",
2216
2370
  confidence: "weak",
2217
2371
  detail: inspection.errorDetail,
2372
+ interruptSource: null,
2218
2373
  errorCode: inspection.errorCode,
2219
2374
  observedAt: inspection.completedAtCandidate ?? inspection.lastEventAt ?? observedAt
2220
2375
  };
@@ -2448,6 +2603,35 @@ function normalizeSessionBindingSnapshot(sessionId, snapshot) {
2448
2603
  rawStoreRef: buildPendingBindingValue("claude-code", sessionId)
2449
2604
  };
2450
2605
  }
2606
+ function countCommonHistoryPrefixLength(left, right) {
2607
+ const maxLength = Math.min(left.length, right.length);
2608
+ let count = 0;
2609
+ for (; count < maxLength; count += 1) {
2610
+ if (!areHistoryMessagesEquivalent(left[count], right[count])) {
2611
+ break;
2612
+ }
2613
+ }
2614
+ return count;
2615
+ }
2616
+ function areHistoryMessagesEquivalent(left, right) {
2617
+ if (!left || !right) {
2618
+ return false;
2619
+ }
2620
+ if (left.messageId && right.messageId) {
2621
+ if (left.messageId === right.messageId) {
2622
+ return true;
2623
+ }
2624
+ }
2625
+ if (left.rawRef && right.rawRef) {
2626
+ if (left.rawRef === right.rawRef) {
2627
+ return true;
2628
+ }
2629
+ }
2630
+ return left.role === right.role
2631
+ && left.kind === right.kind
2632
+ && left.content === right.content
2633
+ && left.timestamp === right.timestamp;
2634
+ }
2451
2635
  function shouldSkipClaudePendingBinding(binding) {
2452
2636
  if (binding.provider !== "claude-code") {
2453
2637
  return false;
@@ -2460,9 +2644,24 @@ function shouldSkipClaudePendingBinding(binding) {
2460
2644
  function isPendingBindingValue(value) {
2461
2645
  return value.trim().toLowerCase().startsWith("pending://");
2462
2646
  }
2647
+ function isSessionBindingProviderUniqueConflict(error) {
2648
+ if (!(error instanceof Error)) {
2649
+ return false;
2650
+ }
2651
+ return error.message.includes("UNIQUE constraint failed: session_bindings.provider, session_bindings.provider_session_id");
2652
+ }
2463
2653
  function buildPendingBindingValue(provider, sessionId) {
2464
2654
  return `pending://${provider}/${sessionId}`;
2465
2655
  }
2656
+ function shouldRecoverSessionAsActive(binding) {
2657
+ return isPendingBindingValue(binding.providerSessionId) || isPendingBindingValue(binding.rawStoreRef);
2658
+ }
2659
+ function inferRecoveredSessionRunningState(binding) {
2660
+ return shouldRecoverSessionAsActive(binding) ? "starting" : "idle";
2661
+ }
2662
+ function inferRecoveredSessionActivitySource(binding) {
2663
+ return shouldRecoverSessionAsActive(binding) ? "runtime" : "none";
2664
+ }
2466
2665
  function buildAliasBindingValue(provider, targetSessionId, sourceSessionId) {
2467
2666
  return `alias://${provider}/${targetSessionId}/${sourceSessionId}`;
2468
2667
  }
@@ -2746,6 +2945,9 @@ function isLegacyCodingNsRolloutSession(providerSessionId, rawStoreRef) {
2746
2945
  function shouldRemoveMissingSyntheticCodexSession(rawStoreRef) {
2747
2946
  return isSyntheticCodexRawStoreRef(rawStoreRef) && !existsSync(rawStoreRef);
2748
2947
  }
2948
+ function shouldMatchSessionBindingByRawStoreRef(provider) {
2949
+ return provider !== "codex";
2950
+ }
2749
2951
  function resolveSessionListTitle(provider, existingTitle, fallbackContent, parentTitle = null) {
2750
2952
  const normalizedExistingTitle = existingTitle?.trim() ?? "";
2751
2953
  const normalizedParentTitle = parentTitle?.trim() ?? "";
@@ -2768,6 +2970,24 @@ function buildUserMessageTitle(content, fallbackTitle) {
2768
2970
  const title = content.trim().replace(/\s+/g, " ");
2769
2971
  return title.slice(0, 48) || fallbackTitle;
2770
2972
  }
2973
+ function buildRecoveredSessionTitle(provider, providerSessionId) {
2974
+ if (isPendingBindingValue(providerSessionId)) {
2975
+ return "新会话";
2976
+ }
2977
+ const normalizedProvider = provider.trim().toLowerCase();
2978
+ const providerLabel = normalizedProvider === "claude-code"
2979
+ ? "Claude"
2980
+ : normalizedProvider === "codex"
2981
+ ? "Codex"
2982
+ : normalizedProvider === "gemini"
2983
+ ? "Gemini"
2984
+ : normalizedProvider === "kimi"
2985
+ ? "Kimi"
2986
+ : normalizedProvider === "opencode"
2987
+ ? "OpenCode"
2988
+ : provider;
2989
+ return `${providerLabel} 会话 ${providerSessionId.slice(0, 8)}`;
2990
+ }
2771
2991
  function resolvePersistedSessionTitle(provider, discoveredTitle, existingTitle, parentTitle = null) {
2772
2992
  const nextTitle = discoveredTitle.trim();
2773
2993
  const currentTitle = existingTitle?.trim() ?? "";