@jingyi0605/codingns 0.2.5 → 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 (227) hide show
  1. package/bin/codingns.mjs +278 -2
  2. package/dist/public/assets/{TerminalPage-BkjqU9NG.js → TerminalPage-CgrfstRm.js} +19 -19
  3. package/dist/public/assets/index-Cek6u0b9.css +1 -0
  4. package/dist/public/assets/index-THHY79si.js +122 -0
  5. package/dist/public/index.html +2 -2
  6. package/dist/server/config/env.d.ts +4 -0
  7. package/dist/server/config/env.js +67 -3
  8. package/dist/server/config/env.js.map +1 -1
  9. package/dist/server/modules/auth/auth-service.d.ts +18 -1
  10. package/dist/server/modules/auth/auth-service.js +168 -7
  11. package/dist/server/modules/auth/auth-service.js.map +1 -1
  12. package/dist/server/modules/butler/butler-codex-model-policy.d.ts +1 -0
  13. package/dist/server/modules/butler/butler-codex-model-policy.js +36 -0
  14. package/dist/server/modules/butler/butler-codex-model-policy.js.map +1 -0
  15. package/dist/server/modules/butler/butler-control-session-service.d.ts +16 -2
  16. package/dist/server/modules/butler/butler-control-session-service.js +58 -210
  17. package/dist/server/modules/butler/butler-control-session-service.js.map +1 -1
  18. package/dist/server/modules/butler/butler-controller.d.ts +17 -0
  19. package/dist/server/modules/butler/butler-controller.js +20 -1
  20. package/dist/server/modules/butler/butler-controller.js.map +1 -1
  21. package/dist/server/modules/butler/butler-follow-up-service.js +7 -3
  22. package/dist/server/modules/butler/butler-follow-up-service.js.map +1 -1
  23. package/dist/server/modules/butler/butler-inbox-analysis-service.d.ts +36 -0
  24. package/dist/server/modules/butler/butler-inbox-analysis-service.js +375 -0
  25. package/dist/server/modules/butler/butler-inbox-analysis-service.js.map +1 -0
  26. package/dist/server/modules/butler/butler-inbox-instruction-adapter.d.ts +23 -0
  27. package/dist/server/modules/butler/butler-inbox-instruction-adapter.js +96 -0
  28. package/dist/server/modules/butler/butler-inbox-instruction-adapter.js.map +1 -0
  29. package/dist/server/modules/butler/butler-inbox-service.d.ts +39 -2
  30. package/dist/server/modules/butler/butler-inbox-service.js +392 -2
  31. package/dist/server/modules/butler/butler-inbox-service.js.map +1 -1
  32. package/dist/server/modules/butler/butler-session-service.d.ts +9 -0
  33. package/dist/server/modules/butler/butler-session-service.js +277 -68
  34. package/dist/server/modules/butler/butler-session-service.js.map +1 -1
  35. package/dist/server/modules/butler/butler-session-summary-service.d.ts +1 -0
  36. package/dist/server/modules/butler/butler-session-summary-service.js +48 -23
  37. package/dist/server/modules/butler/butler-session-summary-service.js.map +1 -1
  38. package/dist/server/modules/butler/butler-workspace-context.d.ts +13 -0
  39. package/dist/server/modules/butler/butler-workspace-context.js +223 -0
  40. package/dist/server/modules/butler/butler-workspace-context.js.map +1 -0
  41. package/dist/server/modules/client/client-controller.d.ts +6 -0
  42. package/dist/server/modules/client/client-controller.js +30 -8
  43. package/dist/server/modules/client/client-controller.js.map +1 -1
  44. package/dist/server/modules/client/client-service.d.ts +22 -10
  45. package/dist/server/modules/client/client-service.js +77 -100
  46. package/dist/server/modules/client/client-service.js.map +1 -1
  47. package/dist/server/modules/client/npm-global-package-service.d.ts +21 -0
  48. package/dist/server/modules/client/npm-global-package-service.js +210 -0
  49. package/dist/server/modules/client/npm-global-package-service.js.map +1 -0
  50. package/dist/server/modules/client/service-update-task-service.d.ts +15 -0
  51. package/dist/server/modules/client/service-update-task-service.js +147 -0
  52. package/dist/server/modules/client/service-update-task-service.js.map +1 -0
  53. package/dist/server/modules/client/service-update-types.d.ts +30 -0
  54. package/dist/server/modules/client/service-update-types.js +2 -0
  55. package/dist/server/modules/client/service-update-types.js.map +1 -0
  56. package/dist/server/modules/debug-target/debug-target-service.d.ts +5 -2
  57. package/dist/server/modules/debug-target/debug-target-service.js +170 -10
  58. package/dist/server/modules/debug-target/debug-target-service.js.map +1 -1
  59. package/dist/server/modules/file/file-constants.d.ts +1 -0
  60. package/dist/server/modules/file/file-constants.js +1 -0
  61. package/dist/server/modules/file/file-constants.js.map +1 -1
  62. package/dist/server/modules/file/file-controller.js +12 -3
  63. package/dist/server/modules/file/file-controller.js.map +1 -1
  64. package/dist/server/modules/file/file-preview-link-service.js +6 -37
  65. package/dist/server/modules/file/file-preview-link-service.js.map +1 -1
  66. package/dist/server/modules/file/file-preview-service.d.ts +6 -12
  67. package/dist/server/modules/file/file-preview-service.js +114 -28
  68. package/dist/server/modules/file/file-preview-service.js.map +1 -1
  69. package/dist/server/modules/file/file-preview-types.d.ts +37 -0
  70. package/dist/server/modules/file/file-preview-types.js +84 -0
  71. package/dist/server/modules/file/file-preview-types.js.map +1 -0
  72. package/dist/server/modules/git/git-controller.d.ts +6 -0
  73. package/dist/server/modules/git/git-controller.js +13 -0
  74. package/dist/server/modules/git/git-controller.js.map +1 -1
  75. package/dist/server/modules/git/git-read-service.d.ts +2 -1
  76. package/dist/server/modules/git/git-read-service.js +100 -0
  77. package/dist/server/modules/git/git-read-service.js.map +1 -1
  78. package/dist/server/modules/git/types.d.ts +23 -0
  79. package/dist/server/modules/model-switch/cc-switch-adapter.d.ts +36 -0
  80. package/dist/server/modules/model-switch/cc-switch-adapter.js +317 -0
  81. package/dist/server/modules/model-switch/cc-switch-adapter.js.map +1 -0
  82. package/dist/server/modules/model-switch/model-switch-controller.d.ts +11 -0
  83. package/dist/server/modules/model-switch/model-switch-controller.js +30 -0
  84. package/dist/server/modules/model-switch/model-switch-controller.js.map +1 -0
  85. package/dist/server/modules/model-switch/model-switch-service.d.ts +16 -0
  86. package/dist/server/modules/model-switch/model-switch-service.js +29 -0
  87. package/dist/server/modules/model-switch/model-switch-service.js.map +1 -0
  88. package/dist/server/modules/preferences/profile-service.d.ts +1 -0
  89. package/dist/server/modules/preferences/profile-service.js +9 -0
  90. package/dist/server/modules/preferences/profile-service.js.map +1 -1
  91. package/dist/server/modules/sessions/codex-app-server-helper-process.js +3 -0
  92. package/dist/server/modules/sessions/codex-app-server-helper-process.js.map +1 -1
  93. package/dist/server/modules/sessions/session-activity-authority-service.d.ts +3 -1
  94. package/dist/server/modules/sessions/session-activity-authority-service.js +16 -1
  95. package/dist/server/modules/sessions/session-activity-authority-service.js.map +1 -1
  96. package/dist/server/modules/sessions/session-activity-inspector.d.ts +1 -1
  97. package/dist/server/modules/sessions/session-activity-inspector.js +52 -9
  98. package/dist/server/modules/sessions/session-activity-inspector.js.map +1 -1
  99. package/dist/server/modules/sessions/session-controller.d.ts +3 -3
  100. package/dist/server/modules/sessions/session-controller.js +3 -3
  101. package/dist/server/modules/sessions/session-controller.js.map +1 -1
  102. package/dist/server/modules/sessions/session-history-service.d.ts +11 -2
  103. package/dist/server/modules/sessions/session-history-service.js +373 -78
  104. package/dist/server/modules/sessions/session-history-service.js.map +1 -1
  105. package/dist/server/modules/sessions/session-live-runtime-service.d.ts +9 -4
  106. package/dist/server/modules/sessions/session-live-runtime-service.js +59 -11
  107. package/dist/server/modules/sessions/session-live-runtime-service.js.map +1 -1
  108. package/dist/server/modules/sessions/session-message-attachment-service.d.ts +8 -8
  109. package/dist/server/modules/sessions/session-message-attachment-service.js +25 -34
  110. package/dist/server/modules/sessions/session-message-attachment-service.js.map +1 -1
  111. package/dist/server/modules/sessions/session-provider-error-mapper.js +7 -0
  112. package/dist/server/modules/sessions/session-provider-error-mapper.js.map +1 -1
  113. package/dist/server/modules/skills/skill-controller.d.ts +23 -0
  114. package/dist/server/modules/skills/skill-controller.js +35 -0
  115. package/dist/server/modules/skills/skill-controller.js.map +1 -0
  116. package/dist/server/modules/skills/skill-manager-service.d.ts +86 -0
  117. package/dist/server/modules/skills/skill-manager-service.js +557 -0
  118. package/dist/server/modules/skills/skill-manager-service.js.map +1 -0
  119. package/dist/server/modules/skills/skill-reconciler.d.ts +21 -0
  120. package/dist/server/modules/skills/skill-reconciler.js +99 -0
  121. package/dist/server/modules/skills/skill-reconciler.js.map +1 -0
  122. package/dist/server/modules/skills/skill-sync-planner.d.ts +8 -0
  123. package/dist/server/modules/skills/skill-sync-planner.js +20 -0
  124. package/dist/server/modules/skills/skill-sync-planner.js.map +1 -0
  125. package/dist/server/modules/skills/skill-target-adapter.d.ts +34 -0
  126. package/dist/server/modules/skills/skill-target-adapter.js +65 -0
  127. package/dist/server/modules/skills/skill-target-adapter.js.map +1 -0
  128. package/dist/server/modules/tailscale/tailscale-controller.d.ts +15 -0
  129. package/dist/server/modules/tailscale/tailscale-controller.js +33 -0
  130. package/dist/server/modules/tailscale/tailscale-controller.js.map +1 -0
  131. package/dist/server/modules/tailscale/tailscale-helper-client.d.ts +41 -0
  132. package/dist/server/modules/tailscale/tailscale-helper-client.js +135 -0
  133. package/dist/server/modules/tailscale/tailscale-helper-client.js.map +1 -0
  134. package/dist/server/modules/tailscale/tailscale-helper-process.d.ts +1 -0
  135. package/dist/server/modules/tailscale/tailscale-helper-process.js +327 -0
  136. package/dist/server/modules/tailscale/tailscale-helper-process.js.map +1 -0
  137. package/dist/server/modules/tailscale/tailscale-manager.d.ts +41 -0
  138. package/dist/server/modules/tailscale/tailscale-manager.js +263 -0
  139. package/dist/server/modules/tailscale/tailscale-manager.js.map +1 -0
  140. package/dist/server/modules/tailscale/tailscale-service.d.ts +43 -0
  141. package/dist/server/modules/tailscale/tailscale-service.js +201 -0
  142. package/dist/server/modules/tailscale/tailscale-service.js.map +1 -0
  143. package/dist/server/modules/tasks/task-lane-executors.js +3 -0
  144. package/dist/server/modules/tasks/task-lane-executors.js.map +1 -1
  145. package/dist/server/modules/tasks/task-types.d.ts +2 -0
  146. package/dist/server/modules/tasks/task-types.js +3 -1
  147. package/dist/server/modules/tasks/task-types.js.map +1 -1
  148. package/dist/server/modules/terminal/command-template-service.js +10 -1
  149. package/dist/server/modules/terminal/command-template-service.js.map +1 -1
  150. package/dist/server/modules/terminal/template-port-runtime.d.ts +12 -1
  151. package/dist/server/modules/terminal/template-port-runtime.js +189 -17
  152. package/dist/server/modules/terminal/template-port-runtime.js.map +1 -1
  153. package/dist/server/modules/terminal/terminal-service.js +30 -3
  154. package/dist/server/modules/terminal/terminal-service.js.map +1 -1
  155. package/dist/server/modules/workspace/workspace-code-composition.js +95 -2
  156. package/dist/server/modules/workspace/workspace-code-composition.js.map +1 -1
  157. package/dist/server/routes/butler.js +4 -0
  158. package/dist/server/routes/butler.js.map +1 -1
  159. package/dist/server/routes/client.js +2 -0
  160. package/dist/server/routes/client.js.map +1 -1
  161. package/dist/server/routes/git.js +1 -0
  162. package/dist/server/routes/git.js.map +1 -1
  163. package/dist/server/routes/sessions.js +1 -1
  164. package/dist/server/routes/sessions.js.map +1 -1
  165. package/dist/server/routes/skills.d.ts +3 -0
  166. package/dist/server/routes/skills.js +7 -0
  167. package/dist/server/routes/skills.js.map +1 -0
  168. package/dist/server/routes/system.d.ts +4 -0
  169. package/dist/server/routes/system.js +11 -0
  170. package/dist/server/routes/system.js.map +1 -0
  171. package/dist/server/server/create-server.d.ts +16 -0
  172. package/dist/server/server/create-server.js +91 -8
  173. package/dist/server/server/create-server.js.map +1 -1
  174. package/dist/server/shared/errors/app-error.d.ts +2 -0
  175. package/dist/server/shared/errors/app-error.js +2 -0
  176. package/dist/server/shared/errors/app-error.js.map +1 -1
  177. package/dist/server/shared/http/error-handler.d.ts +2 -1
  178. package/dist/server/shared/http/error-handler.js +3 -2
  179. package/dist/server/shared/http/error-handler.js.map +1 -1
  180. package/dist/server/shared/utils/command-availability.d.ts +1 -0
  181. package/dist/server/shared/utils/command-availability.js +26 -3
  182. package/dist/server/shared/utils/command-availability.js.map +1 -1
  183. package/dist/server/storage/repositories/auth-login-attempt-repository.d.ts +9 -0
  184. package/dist/server/storage/repositories/auth-login-attempt-repository.js +59 -0
  185. package/dist/server/storage/repositories/auth-login-attempt-repository.js.map +1 -0
  186. package/dist/server/storage/repositories/butler-control-session-repository.d.ts +3 -0
  187. package/dist/server/storage/repositories/butler-control-session-repository.js +80 -4
  188. package/dist/server/storage/repositories/butler-control-session-repository.js.map +1 -1
  189. package/dist/server/storage/repositories/butler-inbox-item-repository.js +54 -3
  190. package/dist/server/storage/repositories/butler-inbox-item-repository.js.map +1 -1
  191. package/dist/server/storage/repositories/instance-tailscale-repository.d.ts +10 -0
  192. package/dist/server/storage/repositories/instance-tailscale-repository.js +112 -0
  193. package/dist/server/storage/repositories/instance-tailscale-repository.js.map +1 -0
  194. package/dist/server/storage/repositories/managed-skill-repository.d.ts +11 -0
  195. package/dist/server/storage/repositories/managed-skill-repository.js +102 -0
  196. package/dist/server/storage/repositories/managed-skill-repository.js.map +1 -0
  197. package/dist/server/storage/repositories/skill-target-binding-repository.d.ts +10 -0
  198. package/dist/server/storage/repositories/skill-target-binding-repository.js +77 -0
  199. package/dist/server/storage/repositories/skill-target-binding-repository.js.map +1 -0
  200. package/dist/server/storage/repositories/terminal-instance-repository.js +1 -1
  201. package/dist/server/storage/repositories/terminal-instance-repository.js.map +1 -1
  202. package/dist/server/storage/repositories/user-preference-profile-repository.js +6 -3
  203. package/dist/server/storage/repositories/user-preference-profile-repository.js.map +1 -1
  204. package/dist/server/storage/sqlite/client.js +137 -0
  205. package/dist/server/storage/sqlite/client.js.map +1 -1
  206. package/dist/server/storage/sqlite/schema.sql +84 -1
  207. package/dist/server/types/domain.d.ts +109 -1
  208. package/dist/server/ws/ws-server.js +4 -4
  209. package/dist/server/ws/ws-server.js.map +1 -1
  210. package/node_modules/@codingns/session-sync-core/dist/patch-builder.d.ts +23 -0
  211. package/node_modules/@codingns/session-sync-core/dist/patch-builder.js +162 -0
  212. package/node_modules/@codingns/session-sync-core/dist/patch-builder.js.map +1 -1
  213. package/node_modules/@codingns/session-sync-core/dist/providers/codex.d.ts +1 -0
  214. package/node_modules/@codingns/session-sync-core/dist/providers/codex.js +89 -33
  215. package/node_modules/@codingns/session-sync-core/dist/providers/codex.js.map +1 -1
  216. package/node_modules/@codingns/session-sync-core/dist/runtime/active-run-registry.js +18 -2
  217. package/node_modules/@codingns/session-sync-core/dist/runtime/active-run-registry.js.map +1 -1
  218. package/node_modules/@codingns/session-sync-core/dist/runtime/codex-runtime.d.ts +3 -1
  219. package/node_modules/@codingns/session-sync-core/dist/runtime/codex-runtime.js +238 -53
  220. package/node_modules/@codingns/session-sync-core/dist/runtime/codex-runtime.js.map +1 -1
  221. package/node_modules/@codingns/session-sync-core/dist/runtime/provider-runtime-service.js +1 -0
  222. package/node_modules/@codingns/session-sync-core/dist/runtime/provider-runtime-service.js.map +1 -1
  223. package/node_modules/@codingns/session-sync-core/dist/runtime/types.d.ts +6 -2
  224. package/node_modules/@codingns/session-sync-core/dist/types.d.ts +1 -1
  225. package/package.json +1 -1
  226. package/dist/public/assets/index-C6U8-9jg.css +0 -1
  227. package/dist/public/assets/index-CKSumuV2.js +0 -109
@@ -58,6 +58,7 @@ export class SessionHistoryService {
58
58
  workspaceDiscoveryStatuses = new Map();
59
59
  workspaceStateRefreshInflight = new Map();
60
60
  providerCapabilityCache = new Map();
61
+ liveActivityObservationResolvers = new Set();
61
62
  workspaceSessionRelations = new Map();
62
63
  constructor(db, workspaceRepository, sessionBindingRepository, sessionChangedFileService, sessionIndexRepository, sessionMessageAttachmentService, sessionStateRepository, sessionStatusSnapshotRepository, config, sessionActivityAuthorityService = new SessionActivityAuthorityService(), sessionMessageOriginRepository = null, sessionForkRepository = null, adapterOverrides = {}, taskManager = createTaskManager()) {
63
64
  this.db = db;
@@ -130,6 +131,19 @@ export class SessionHistoryService {
130
131
  observeBackgroundTaskMetrics() {
131
132
  return this.taskManager.observe();
132
133
  }
134
+ registerLiveActivityObservationResolver(resolver) {
135
+ this.liveActivityObservationResolvers.add(resolver);
136
+ let closed = false;
137
+ return {
138
+ close: () => {
139
+ if (closed) {
140
+ return;
141
+ }
142
+ closed = true;
143
+ this.liveActivityObservationResolvers.delete(resolver);
144
+ }
145
+ };
146
+ }
133
147
  registerBackgroundTasks() {
134
148
  if (!this.taskManager.has(HOST_TASK_TYPES.workspaceDiscovery)) {
135
149
  this.taskManager.register({
@@ -1076,39 +1090,52 @@ export class SessionHistoryService {
1076
1090
  providerSessionId: snapshot.providerSessionId,
1077
1091
  rawStoreRef: snapshot.rawStoreRef
1078
1092
  });
1079
- const currentBinding = this.sessionBindingRepository.findBySessionId(sessionId);
1080
- const timestamp = nowIso();
1081
- const duplicateBinding = this.findPendingBindingDuplicate(sessionId, workspaceId, currentBinding, resolvedSnapshot);
1082
- this.db.transaction(() => {
1083
- if (duplicateBinding) {
1084
- // 新建运行时会话会先写入 pending 绑定,后台发现链路可能在真 ID 回填前先落一条重复记录。
1085
- // 这里保留当前 runtime session,把扫描出的重复会话并回当前会话,避免 provider_session_id 撞唯一键。
1086
- this.mergeSessionIntoTarget({
1087
- workspaceId,
1088
- targetSessionId: sessionId,
1089
- sourceSessionId: duplicateBinding.sessionId,
1090
- provider: resolvedSnapshot.provider,
1091
- timestamp
1092
- });
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;
1093
1131
  }
1094
- const currentIndex = this.sessionIndexRepository.findIndexRecordBySessionId(sessionId);
1095
- this.sessionBindingRepository.upsert({
1096
- sessionId,
1097
- workspaceId,
1098
- provider: resolvedSnapshot.provider,
1099
- providerSessionId: resolvedSnapshot.providerSessionId,
1100
- rawStoreRef: resolvedSnapshot.rawStoreRef,
1101
- createdAt: pickEarlierIso(currentBinding?.createdAt ?? null, duplicateBinding?.createdAt ?? null)
1102
- ?? timestamp,
1103
- updatedAt: timestamp
1104
- });
1105
- if (currentIndex) {
1106
- this.sessionIndexRepository.upsert({
1107
- ...currentIndex,
1108
- updatedAt: timestamp
1109
- });
1132
+ catch (error) {
1133
+ if (attempt === 0 && isSessionBindingProviderUniqueConflict(error)) {
1134
+ continue;
1135
+ }
1136
+ throw error;
1110
1137
  }
1111
- })();
1138
+ }
1112
1139
  }
1113
1140
  async runDiscoverWorkspaceSessions(workspaceId, userId, refreshStateMode = "inline") {
1114
1141
  const startedAt = Date.now();
@@ -1148,7 +1175,9 @@ export class SessionHistoryService {
1148
1175
  const claimedPendingSessionIds = new Set();
1149
1176
  const persistPass1Transaction = this.db.transaction((batch) => {
1150
1177
  for (const session of batch) {
1151
- 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);
1152
1181
  if (exactExisting && exactExisting.workspaceId !== workspaceId) {
1153
1182
  continue;
1154
1183
  }
@@ -1391,12 +1420,13 @@ export class SessionHistoryService {
1391
1420
  ? this.sessionSyncService.readRecentHistory(provider, providerSessionId, rawStoreRef, knownTotalMessageCount, limit)
1392
1421
  : this.sessionSyncService.readHistory(provider, providerSessionId, rawStoreRef, cursor, limit, direction);
1393
1422
  return historyTask
1394
- .then((page) => {
1395
- 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);
1396
1426
  const messages = this.enrichMessagesWithOrigin(sessionId, messagesWithAttachments);
1397
1427
  this.persistSessionChangedFiles(sessionId, messages);
1398
1428
  return {
1399
- ...page,
1429
+ ...sanitizedPage,
1400
1430
  messages
1401
1431
  };
1402
1432
  })
@@ -1415,6 +1445,56 @@ export class SessionHistoryService {
1415
1445
  enrichMessagesWithOrigin(sessionId, messages) {
1416
1446
  return this.resolveMessageOrigins(sessionId, messages);
1417
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
+ }
1418
1498
  resolveMessageOrigins(sessionId, messages) {
1419
1499
  const originRepository = this.sessionMessageOriginRepository;
1420
1500
  if (!originRepository || messages.length === 0) {
@@ -1669,7 +1749,13 @@ export class SessionHistoryService {
1669
1749
  return workspace;
1670
1750
  }
1671
1751
  getSessionListItemOrThrow(sessionId, userId) {
1672
- 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);
1673
1759
  if (!item) {
1674
1760
  throw new AppError({
1675
1761
  statusCode: 500,
@@ -1683,6 +1769,75 @@ export class SessionHistoryService {
1683
1769
  }
1684
1770
  return this.sessionIndexRepository.findBySessionId(aliasTargetSessionId, userId) ?? item;
1685
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
+ }
1686
1841
  resolveCanonicalSessionId(sessionId, userId) {
1687
1842
  if (userId) {
1688
1843
  const item = this.sessionIndexRepository.findBySessionId(sessionId, userId);
@@ -1705,11 +1860,11 @@ export class SessionHistoryService {
1705
1860
  return this.sessionBindingRepository.findBySessionId(aliasTargetSessionId);
1706
1861
  }
1707
1862
  findPendingSessionAliasTargetSessionId(descriptor) {
1708
- if (!descriptor || descriptor.provider !== "gemini") {
1863
+ if (!descriptor) {
1709
1864
  return null;
1710
1865
  }
1711
- const aliasTargetSessionId = extractPendingBindingTargetSessionId(descriptor.providerSessionId)
1712
- ?? extractPendingBindingTargetSessionId(descriptor.rawStoreRef);
1866
+ const aliasTargetSessionId = extractSessionAliasTargetSessionId(descriptor.providerSessionId)
1867
+ ?? extractSessionAliasTargetSessionId(descriptor.rawStoreRef);
1713
1868
  if (!aliasTargetSessionId || aliasTargetSessionId === descriptor.sessionId) {
1714
1869
  return null;
1715
1870
  }
@@ -1794,14 +1949,13 @@ export class SessionHistoryService {
1794
1949
  });
1795
1950
  await runBatchedTransactions(staleHiddenSessions.map((session) => session.sessionId), WORKSPACE_DISCOVERY_PERSIST_BATCH_SIZE, deleteTransaction);
1796
1951
  }
1797
- findPendingBindingDuplicate(sessionId, workspaceId, currentBinding, snapshot) {
1798
- if (!currentBinding || !isPendingBindingValue(currentBinding.providerSessionId)) {
1799
- return null;
1800
- }
1952
+ findSameWorkspaceBindingDuplicate(sessionId, workspaceId, snapshot) {
1801
1953
  if (isPendingBindingValue(snapshot.providerSessionId)) {
1802
1954
  return null;
1803
1955
  }
1804
- 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);
1805
1959
  if (!existing || existing.sessionId === sessionId) {
1806
1960
  return null;
1807
1961
  }
@@ -1814,11 +1968,22 @@ export class SessionHistoryService {
1814
1968
  if (input.targetSessionId === input.sourceSessionId) {
1815
1969
  return;
1816
1970
  }
1817
- const targetBinding = this.sessionBindingRepository.findBySessionId(input.targetSessionId);
1818
1971
  const sourceBinding = this.sessionBindingRepository.findBySessionId(input.sourceSessionId);
1819
- if (!targetBinding || !sourceBinding) {
1972
+ if (!sourceBinding) {
1820
1973
  return;
1821
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
+ }
1822
1987
  const targetIndex = this.sessionIndexRepository.findIndexRecordBySessionId(input.targetSessionId);
1823
1988
  const sourceIndex = this.sessionIndexRepository.findIndexRecordBySessionId(input.sourceSessionId);
1824
1989
  const targetSnapshot = this.sessionStatusSnapshotRepository.findBySessionId(input.targetSessionId);
@@ -1889,12 +2054,22 @@ export class SessionHistoryService {
1889
2054
  this.db
1890
2055
  .prepare("DELETE FROM session_forks WHERE session_id = ?")
1891
2056
  .run(input.sourceSessionId);
1892
- this.db
1893
- .prepare("DELETE FROM session_indices WHERE session_id = ?")
1894
- .run(input.sourceSessionId);
1895
- this.db
1896
- .prepare("DELETE FROM session_bindings WHERE session_id = ?")
1897
- .run(input.sourceSessionId);
2057
+ // 保留旧 session_id 作为 alias,避免前端或 Butler 还拿着旧 id 时直接炸成 SESSION_NOT_FOUND。
2058
+ this.sessionBindingRepository.upsert({
2059
+ sessionId: input.sourceSessionId,
2060
+ workspaceId: sourceBinding.workspaceId,
2061
+ provider: sourceBinding.provider,
2062
+ providerSessionId: buildAliasBindingValue(input.provider, input.targetSessionId, input.sourceSessionId),
2063
+ rawStoreRef: buildAliasBindingValue(input.provider, input.targetSessionId, input.sourceSessionId),
2064
+ createdAt: sourceBinding.createdAt,
2065
+ updatedAt: input.timestamp
2066
+ });
2067
+ if (sourceIndex) {
2068
+ this.sessionIndexRepository.upsert({
2069
+ ...sourceIndex,
2070
+ updatedAt: input.timestamp
2071
+ });
2072
+ }
1898
2073
  this.rewriteWorkspaceSessionRelations(input.workspaceId, input.targetSessionId, input.sourceSessionId, targetIndex, sourceIndex);
1899
2074
  }
1900
2075
  listSessionStatesBySessionId(sessionId) {
@@ -2020,6 +2195,7 @@ export class SessionHistoryService {
2020
2195
  }
2021
2196
  buildKnownSessionSummaries(sessions, workspacePath) {
2022
2197
  return sessions
2198
+ .filter((session) => !this.isPendingSessionAlias(session))
2023
2199
  .filter((session) => !shouldSkipClaudePendingBinding(session))
2024
2200
  .map((session) => {
2025
2201
  const stats = safeStat(session.rawStoreRef);
@@ -2039,19 +2215,28 @@ export class SessionHistoryService {
2039
2215
  async refreshSessionState(sessionId, userId) {
2040
2216
  const binding = this.getBindingOrThrow(sessionId);
2041
2217
  const current = this.sessionStateRepository.findBySessionAndUser(sessionId, userId);
2042
- const inspection = inspectSessionActivity(binding.provider, binding.rawStoreRef);
2043
2218
  const timestamp = nowIso();
2044
- const nowMs = Date.parse(timestamp);
2045
- if (shouldClearStaleRuntimeWithoutInspection(current, inspection, nowMs)) {
2046
- this.sessionActivityAuthorityService.clearSession(sessionId);
2047
- }
2048
- if (shouldPreserveRuntimeTerminalState(current, inspection)) {
2049
- return current;
2219
+ const liveObservation = this.resolveLiveActivityObservation(sessionId);
2220
+ const inspection = liveObservation
2221
+ ? null
2222
+ : inspectSessionActivity(binding.provider, binding.rawStoreRef);
2223
+ if (inspection) {
2224
+ const nowMs = Date.parse(timestamp);
2225
+ if (shouldClearStaleRuntimeWithoutInspection(current, inspection, nowMs)) {
2226
+ this.sessionActivityAuthorityService.clearSession(sessionId);
2227
+ }
2228
+ if (shouldPreserveRuntimeTerminalState(current, inspection)) {
2229
+ return current;
2230
+ }
2050
2231
  }
2051
- const resolution = this.sessionActivityAuthorityService.observe(buildInspectionActivityObservation(sessionId, inspection, timestamp));
2052
- const resolvedLastEventAt = hasInspectionEvidence(inspection)
2053
- ? resolution.lastObservedAt ?? inspection.lastEventAt ?? current?.lastEventAt ?? null
2054
- : current?.lastEventAt ?? null;
2232
+ const resolution = liveObservation
2233
+ ? this.sessionActivityAuthorityService.observe(liveObservation)
2234
+ : this.sessionActivityAuthorityService.observe(buildInspectionActivityObservation(sessionId, inspection, timestamp));
2235
+ const resolvedLastEventAt = liveObservation
2236
+ ? resolution.lastObservedAt ?? current?.lastEventAt ?? null
2237
+ : inspection && hasInspectionEvidence(inspection)
2238
+ ? resolution.lastObservedAt ?? inspection.lastEventAt ?? current?.lastEventAt ?? null
2239
+ : current?.lastEventAt ?? null;
2055
2240
  const nextRecord = {
2056
2241
  sessionId,
2057
2242
  userId,
@@ -2060,7 +2245,7 @@ export class SessionHistoryService {
2060
2245
  favorite: current?.favorite ?? false,
2061
2246
  lastEventAt: resolvedLastEventAt,
2062
2247
  completedAt: isTerminalResolvedRunningState(resolution.runningState)
2063
- ? resolution.terminalAt ?? inspection.completedAtCandidate ?? current?.completedAt ?? null
2248
+ ? resolution.terminalAt ?? inspection?.completedAtCandidate ?? current?.completedAt ?? null
2064
2249
  : null,
2065
2250
  lastSeenAt: current?.lastSeenAt ?? null,
2066
2251
  updatedAt: timestamp
@@ -2078,8 +2263,8 @@ export class SessionHistoryService {
2078
2263
  syncCursor: currentSnapshot?.syncCursor ?? null,
2079
2264
  lastSyncAt: resolution.lastObservedAt
2080
2265
  ?? resolution.terminalAt
2081
- ?? inspection.lastEventAt
2082
- ?? inspection.completedAtCandidate
2266
+ ?? inspection?.lastEventAt
2267
+ ?? inspection?.completedAtCandidate
2083
2268
  ?? currentSnapshot?.lastSyncAt
2084
2269
  ?? null,
2085
2270
  lastErrorCode: resolution.runningState === "failed"
@@ -2097,17 +2282,34 @@ export class SessionHistoryService {
2097
2282
  });
2098
2283
  return nextRecord;
2099
2284
  }
2285
+ resolveLiveActivityObservation(sessionId) {
2286
+ for (const resolver of this.liveActivityObservationResolvers) {
2287
+ const observation = resolver(sessionId);
2288
+ if (observation) {
2289
+ return observation;
2290
+ }
2291
+ }
2292
+ return null;
2293
+ }
2100
2294
  upsertSnapshot(sessionId, input) {
2295
+ const resolvedSessionId = this.resolveCanonicalSessionId(sessionId);
2296
+ if (!this.sessionBindingRepository.findBySessionId(resolvedSessionId)) {
2297
+ return;
2298
+ }
2101
2299
  this.sessionStatusSnapshotRepository.upsert({
2102
- sessionId,
2300
+ sessionId: resolvedSessionId,
2103
2301
  ...input,
2104
2302
  updatedAt: nowIso()
2105
2303
  });
2106
2304
  }
2107
2305
  markSessionError(sessionId, errorCode, error) {
2108
- const current = this.sessionStatusSnapshotRepository.findBySessionId(sessionId);
2306
+ const resolvedSessionId = this.resolveCanonicalSessionId(sessionId);
2307
+ if (!this.sessionBindingRepository.findBySessionId(resolvedSessionId)) {
2308
+ return;
2309
+ }
2310
+ const current = this.sessionStatusSnapshotRepository.findBySessionId(resolvedSessionId);
2109
2311
  this.sessionStatusSnapshotRepository.upsert({
2110
- sessionId,
2312
+ sessionId: resolvedSessionId,
2111
2313
  syncStatus: "error",
2112
2314
  syncCursor: current?.syncCursor ?? null,
2113
2315
  lastSyncAt: current?.lastSyncAt ?? null,
@@ -2167,6 +2369,7 @@ function buildInspectionActivityObservation(sessionId, inspection, observedAt) {
2167
2369
  source: hasInspectionEvidence(inspection) ? "inferred_log" : "unknown",
2168
2370
  confidence: "weak",
2169
2371
  detail: inspection.errorDetail,
2372
+ interruptSource: null,
2170
2373
  errorCode: inspection.errorCode,
2171
2374
  observedAt: inspection.completedAtCandidate ?? inspection.lastEventAt ?? observedAt
2172
2375
  };
@@ -2177,23 +2380,28 @@ function hasInspectionEvidence(inspection) {
2177
2380
  || !!inspection.completedAtCandidate;
2178
2381
  }
2179
2382
  function applySessionActivityResolution(item, resolution) {
2180
- const runningState = resolution.runningState;
2181
- const shouldClearResolvedFailure = runningState !== "failed" && item.runningState === "failed";
2383
+ const rawResolvedRunningState = resolution.runningState === "unknown" && item.runningState === null
2384
+ ? null
2385
+ : resolution.runningState;
2386
+ const resolvedRunningState = resolution.activityResolutionSource === "inferred_log" && rawResolvedRunningState === "completed"
2387
+ ? "idle"
2388
+ : rawResolvedRunningState;
2389
+ const shouldClearResolvedFailure = resolvedRunningState !== "failed" && item.runningState === "failed";
2182
2390
  const lastEventAt = resolution.lastObservedAt ?? item.lastEventAt;
2183
- const completedAt = isTerminalResolvedRunningState(runningState)
2391
+ const completedAt = rawResolvedRunningState && isTerminalResolvedRunningState(rawResolvedRunningState)
2184
2392
  ? resolution.terminalAt ?? item.completedAt
2185
2393
  : null;
2186
- const lastErrorCode = runningState === "failed"
2394
+ const lastErrorCode = resolvedRunningState === "failed"
2187
2395
  ? resolution.errorCode ?? item.lastErrorCode
2188
2396
  : shouldClearResolvedFailure
2189
2397
  ? null
2190
2398
  : item.lastErrorCode;
2191
- const lastErrorDetail = runningState === "failed"
2399
+ const lastErrorDetail = resolvedRunningState === "failed"
2192
2400
  ? resolution.detail ?? item.lastErrorDetail
2193
2401
  : shouldClearResolvedFailure
2194
2402
  ? null
2195
2403
  : item.lastErrorDetail;
2196
- const syncStatus = runningState === "failed"
2404
+ const syncStatus = resolvedRunningState === "failed"
2197
2405
  ? "error"
2198
2406
  : shouldClearResolvedFailure && item.syncStatus === "error"
2199
2407
  ? "idle"
@@ -2201,7 +2409,7 @@ function applySessionActivityResolution(item, resolution) {
2201
2409
  return {
2202
2410
  ...item,
2203
2411
  syncStatus,
2204
- runningState,
2412
+ runningState: resolvedRunningState,
2205
2413
  activitySource: mapResolutionSourceToCompatibilitySource(resolution.activityResolutionSource),
2206
2414
  activityResolutionSource: resolution.activityResolutionSource,
2207
2415
  activityConfidence: resolution.activityConfidence,
@@ -2211,7 +2419,7 @@ function applySessionActivityResolution(item, resolution) {
2211
2419
  lastErrorCode,
2212
2420
  lastErrorDetail,
2213
2421
  watchdogTriggeredAt: resolution.watchdogTriggeredAt,
2214
- activityState: resolveActivityState(runningState, completedAt, item.lastSeenAt)
2422
+ activityState: resolveActivityState(resolvedRunningState, completedAt, item.lastSeenAt)
2215
2423
  };
2216
2424
  }
2217
2425
  function clampLimit(limit) {
@@ -2395,6 +2603,35 @@ function normalizeSessionBindingSnapshot(sessionId, snapshot) {
2395
2603
  rawStoreRef: buildPendingBindingValue("claude-code", sessionId)
2396
2604
  };
2397
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
+ }
2398
2635
  function shouldSkipClaudePendingBinding(binding) {
2399
2636
  if (binding.provider !== "claude-code") {
2400
2637
  return false;
@@ -2407,9 +2644,27 @@ function shouldSkipClaudePendingBinding(binding) {
2407
2644
  function isPendingBindingValue(value) {
2408
2645
  return value.trim().toLowerCase().startsWith("pending://");
2409
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
+ }
2410
2653
  function buildPendingBindingValue(provider, sessionId) {
2411
2654
  return `pending://${provider}/${sessionId}`;
2412
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
+ }
2665
+ function buildAliasBindingValue(provider, targetSessionId, sourceSessionId) {
2666
+ return `alias://${provider}/${targetSessionId}/${sourceSessionId}`;
2667
+ }
2413
2668
  function extractPendingBindingTargetSessionId(value) {
2414
2669
  if (!isPendingBindingValue(value)) {
2415
2670
  return null;
@@ -2418,6 +2673,25 @@ function extractPendingBindingTargetSessionId(value) {
2418
2673
  const targetSessionId = normalizedValue.slice(normalizedValue.indexOf("/", "pending://".length) + 1).trim();
2419
2674
  return targetSessionId || null;
2420
2675
  }
2676
+ function extractAliasBindingTargetSessionId(value) {
2677
+ const normalizedValue = value.trim();
2678
+ if (!normalizedValue.toLowerCase().startsWith("alias://")) {
2679
+ return null;
2680
+ }
2681
+ const pathStart = normalizedValue.indexOf("/", "alias://".length);
2682
+ if (pathStart < 0) {
2683
+ return null;
2684
+ }
2685
+ const targetAndSource = normalizedValue.slice(pathStart + 1).trim();
2686
+ if (targetAndSource.length === 0) {
2687
+ return null;
2688
+ }
2689
+ const [targetSessionId] = targetAndSource.split("/", 1);
2690
+ return targetSessionId?.trim() || null;
2691
+ }
2692
+ function extractSessionAliasTargetSessionId(value) {
2693
+ return extractAliasBindingTargetSessionId(value) ?? extractPendingBindingTargetSessionId(value);
2694
+ }
2421
2695
  function isClaudePendingRuntimeRawStoreRef(rawStoreRef) {
2422
2696
  const normalizedRawStoreRef = rawStoreRef.replaceAll("\\", "/").toLowerCase();
2423
2697
  return normalizedRawStoreRef.includes("/.pending-");
@@ -2671,6 +2945,9 @@ function isLegacyCodingNsRolloutSession(providerSessionId, rawStoreRef) {
2671
2945
  function shouldRemoveMissingSyntheticCodexSession(rawStoreRef) {
2672
2946
  return isSyntheticCodexRawStoreRef(rawStoreRef) && !existsSync(rawStoreRef);
2673
2947
  }
2948
+ function shouldMatchSessionBindingByRawStoreRef(provider) {
2949
+ return provider !== "codex";
2950
+ }
2674
2951
  function resolveSessionListTitle(provider, existingTitle, fallbackContent, parentTitle = null) {
2675
2952
  const normalizedExistingTitle = existingTitle?.trim() ?? "";
2676
2953
  const normalizedParentTitle = parentTitle?.trim() ?? "";
@@ -2693,6 +2970,24 @@ function buildUserMessageTitle(content, fallbackTitle) {
2693
2970
  const title = content.trim().replace(/\s+/g, " ");
2694
2971
  return title.slice(0, 48) || fallbackTitle;
2695
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
+ }
2696
2991
  function resolvePersistedSessionTitle(provider, discoveredTitle, existingTitle, parentTitle = null) {
2697
2992
  const nextTitle = discoveredTitle.trim();
2698
2993
  const currentTitle = existingTitle?.trim() ?? "";
@@ -2786,7 +3081,7 @@ function mapResolutionSourceToLegacyActivitySource(source, inspection) {
2786
3081
  if (source === "authoritative_runtime" || source === "authoritative_provider_event") {
2787
3082
  return "runtime";
2788
3083
  }
2789
- if (inspection.lastEventAt || inspection.completedAtCandidate) {
3084
+ if (inspection && (inspection.lastEventAt || inspection.completedAtCandidate)) {
2790
3085
  return "inferred";
2791
3086
  }
2792
3087
  return "none";