@jingyi0605/codingns 0.1.3 → 0.1.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 (224) hide show
  1. package/bin/codingns.mjs +47 -8
  2. package/dist/public/assets/{TerminalPage-Nq5sPc5c.js → TerminalPage-4p6EBqrR.js} +19 -19
  3. package/dist/public/assets/gemini-D4G1NbrE.png +0 -0
  4. package/dist/public/assets/index-CxeghocY.css +1 -0
  5. package/dist/public/assets/index-DXusStl0.js +108 -0
  6. package/dist/public/assets/kimi-BWNNSh7e.png +0 -0
  7. package/dist/public/index.html +2 -2
  8. package/dist/server/config/env.d.ts +7 -0
  9. package/dist/server/config/env.js +147 -1
  10. package/dist/server/config/env.js.map +1 -1
  11. package/dist/server/config/opencode-system-probe-helper-process.d.ts +24 -0
  12. package/dist/server/config/opencode-system-probe-helper-process.js +70 -5
  13. package/dist/server/config/opencode-system-probe-helper-process.js.map +1 -1
  14. package/dist/server/modules/auth/auth-service.d.ts +7 -1
  15. package/dist/server/modules/auth/auth-service.js +23 -3
  16. package/dist/server/modules/auth/auth-service.js.map +1 -1
  17. package/dist/server/modules/bootstrap/bootstrap-service.d.ts +3 -1
  18. package/dist/server/modules/bootstrap/bootstrap-service.js +7 -2
  19. package/dist/server/modules/bootstrap/bootstrap-service.js.map +1 -1
  20. package/dist/server/modules/butler/butler-action-context-service.d.ts +30 -0
  21. package/dist/server/modules/butler/butler-action-context-service.js +108 -0
  22. package/dist/server/modules/butler/butler-action-context-service.js.map +1 -0
  23. package/dist/server/modules/butler/butler-auth-service.d.ts +17 -0
  24. package/dist/server/modules/butler/butler-auth-service.js +91 -0
  25. package/dist/server/modules/butler/butler-auth-service.js.map +1 -0
  26. package/dist/server/modules/butler/butler-control-action-service.d.ts +65 -0
  27. package/dist/server/modules/butler/butler-control-action-service.js +296 -0
  28. package/dist/server/modules/butler/butler-control-action-service.js.map +1 -0
  29. package/dist/server/modules/butler/butler-control-session-service.d.ts +55 -0
  30. package/dist/server/modules/butler/butler-control-session-service.js +367 -0
  31. package/dist/server/modules/butler/butler-control-session-service.js.map +1 -0
  32. package/dist/server/modules/butler/butler-controller.d.ts +367 -0
  33. package/dist/server/modules/butler/butler-controller.js +475 -0
  34. package/dist/server/modules/butler/butler-controller.js.map +1 -0
  35. package/dist/server/modules/butler/butler-follow-up-evaluation-instruction-adapter.d.ts +34 -0
  36. package/dist/server/modules/butler/butler-follow-up-evaluation-instruction-adapter.js +77 -0
  37. package/dist/server/modules/butler/butler-follow-up-evaluation-instruction-adapter.js.map +1 -0
  38. package/dist/server/modules/butler/butler-follow-up-scheduler.d.ts +23 -0
  39. package/dist/server/modules/butler/butler-follow-up-scheduler.js +57 -0
  40. package/dist/server/modules/butler/butler-follow-up-scheduler.js.map +1 -0
  41. package/dist/server/modules/butler/butler-follow-up-service.d.ts +86 -0
  42. package/dist/server/modules/butler/butler-follow-up-service.js +948 -0
  43. package/dist/server/modules/butler/butler-follow-up-service.js.map +1 -0
  44. package/dist/server/modules/butler/butler-inbox-service.d.ts +35 -0
  45. package/dist/server/modules/butler/butler-inbox-service.js +136 -0
  46. package/dist/server/modules/butler/butler-inbox-service.js.map +1 -0
  47. package/dist/server/modules/butler/butler-notification-service.d.ts +12 -0
  48. package/dist/server/modules/butler/butler-notification-service.js +45 -0
  49. package/dist/server/modules/butler/butler-notification-service.js.map +1 -0
  50. package/dist/server/modules/butler/butler-profile-service.d.ts +26 -0
  51. package/dist/server/modules/butler/butler-profile-service.js +529 -0
  52. package/dist/server/modules/butler/butler-profile-service.js.map +1 -0
  53. package/dist/server/modules/butler/butler-project-service.d.ts +48 -0
  54. package/dist/server/modules/butler/butler-project-service.js +253 -0
  55. package/dist/server/modules/butler/butler-project-service.js.map +1 -0
  56. package/dist/server/modules/butler/butler-session-service.d.ts +79 -0
  57. package/dist/server/modules/butler/butler-session-service.js +503 -0
  58. package/dist/server/modules/butler/butler-session-service.js.map +1 -0
  59. package/dist/server/modules/butler/butler-session-summary-service.d.ts +55 -0
  60. package/dist/server/modules/butler/butler-session-summary-service.js +382 -0
  61. package/dist/server/modules/butler/butler-session-summary-service.js.map +1 -0
  62. package/dist/server/modules/butler/context-aggregator.d.ts +187 -0
  63. package/dist/server/modules/butler/context-aggregator.js +807 -0
  64. package/dist/server/modules/butler/context-aggregator.js.map +1 -0
  65. package/dist/server/modules/butler/instruction-adapter.d.ts +28 -0
  66. package/dist/server/modules/butler/instruction-adapter.js +101 -0
  67. package/dist/server/modules/butler/instruction-adapter.js.map +1 -0
  68. package/dist/server/modules/butler/patrol-execution-service.d.ts +47 -0
  69. package/dist/server/modules/butler/patrol-execution-service.js +347 -0
  70. package/dist/server/modules/butler/patrol-execution-service.js.map +1 -0
  71. package/dist/server/modules/butler/patrol-plan-service.d.ts +54 -0
  72. package/dist/server/modules/butler/patrol-plan-service.js +272 -0
  73. package/dist/server/modules/butler/patrol-plan-service.js.map +1 -0
  74. package/dist/server/modules/butler/patrol-run-service.d.ts +60 -0
  75. package/dist/server/modules/butler/patrol-run-service.js +185 -0
  76. package/dist/server/modules/butler/patrol-run-service.js.map +1 -0
  77. package/dist/server/modules/butler/patrol-scheduler.d.ts +36 -0
  78. package/dist/server/modules/butler/patrol-scheduler.js +99 -0
  79. package/dist/server/modules/butler/patrol-scheduler.js.map +1 -0
  80. package/dist/server/modules/butler/project-memory-service.d.ts +30 -0
  81. package/dist/server/modules/butler/project-memory-service.js +103 -0
  82. package/dist/server/modules/butler/project-memory-service.js.map +1 -0
  83. package/dist/server/modules/butler/provider-adapter-registry.d.ts +61 -0
  84. package/dist/server/modules/butler/provider-adapter-registry.js +430 -0
  85. package/dist/server/modules/butler/provider-adapter-registry.js.map +1 -0
  86. package/dist/server/modules/butler/session-summary-instruction-adapter.d.ts +28 -0
  87. package/dist/server/modules/butler/session-summary-instruction-adapter.js +79 -0
  88. package/dist/server/modules/butler/session-summary-instruction-adapter.js.map +1 -0
  89. package/dist/server/modules/butler/session-summary-scheduler.d.ts +23 -0
  90. package/dist/server/modules/butler/session-summary-scheduler.js +57 -0
  91. package/dist/server/modules/butler/session-summary-scheduler.js.map +1 -0
  92. package/dist/server/modules/butler/verification-run-service.d.ts +73 -0
  93. package/dist/server/modules/butler/verification-run-service.js +633 -0
  94. package/dist/server/modules/butler/verification-run-service.js.map +1 -0
  95. package/dist/server/modules/demo/demo-cleanup-service.d.ts +41 -0
  96. package/dist/server/modules/demo/demo-cleanup-service.js +111 -0
  97. package/dist/server/modules/demo/demo-cleanup-service.js.map +1 -0
  98. package/dist/server/modules/preferences/profile-service.js +8 -2
  99. package/dist/server/modules/preferences/profile-service.js.map +1 -1
  100. package/dist/server/modules/sessions/claude-runtime-helper-process.js +1 -1
  101. package/dist/server/modules/sessions/claude-runtime-helper-process.js.map +1 -1
  102. package/dist/server/modules/sessions/codex-app-server-helper-client.d.ts +5 -1
  103. package/dist/server/modules/sessions/codex-app-server-helper-client.js +10 -2
  104. package/dist/server/modules/sessions/codex-app-server-helper-client.js.map +1 -1
  105. package/dist/server/modules/sessions/session-controller.d.ts +3 -1
  106. package/dist/server/modules/sessions/session-controller.js +11 -2
  107. package/dist/server/modules/sessions/session-controller.js.map +1 -1
  108. package/dist/server/modules/sessions/session-history-service.d.ts +14 -1
  109. package/dist/server/modules/sessions/session-history-service.js +291 -30
  110. package/dist/server/modules/sessions/session-history-service.js.map +1 -1
  111. package/dist/server/modules/sessions/session-live-runtime-service.d.ts +25 -2
  112. package/dist/server/modules/sessions/session-live-runtime-service.js +526 -158
  113. package/dist/server/modules/sessions/session-live-runtime-service.js.map +1 -1
  114. package/dist/server/modules/sessions/session-provider-error-mapper.js +28 -0
  115. package/dist/server/modules/sessions/session-provider-error-mapper.js.map +1 -1
  116. package/dist/server/modules/terminal/terminal-service.js +2 -2
  117. package/dist/server/modules/terminal/terminal-service.js.map +1 -1
  118. package/dist/server/modules/workbench/workbench-service.d.ts +7 -1
  119. package/dist/server/modules/workbench/workbench-service.js +31 -7
  120. package/dist/server/modules/workbench/workbench-service.js.map +1 -1
  121. package/dist/server/routes/butler.d.ts +3 -0
  122. package/dist/server/routes/butler.js +54 -0
  123. package/dist/server/routes/butler.js.map +1 -0
  124. package/dist/server/routes/sessions.d.ts +1 -0
  125. package/dist/server/routes/sessions.js +12 -3
  126. package/dist/server/routes/sessions.js.map +1 -1
  127. package/dist/server/server/create-server.d.ts +61 -0
  128. package/dist/server/server/create-server.js +180 -10
  129. package/dist/server/server/create-server.js.map +1 -1
  130. package/dist/server/storage/repositories/butler-control-event-repository.d.ts +8 -0
  131. package/dist/server/storage/repositories/butler-control-event-repository.js +78 -0
  132. package/dist/server/storage/repositories/butler-control-event-repository.js.map +1 -0
  133. package/dist/server/storage/repositories/butler-control-session-repository.d.ts +11 -0
  134. package/dist/server/storage/repositories/butler-control-session-repository.js +86 -0
  135. package/dist/server/storage/repositories/butler-control-session-repository.js.map +1 -0
  136. package/dist/server/storage/repositories/butler-follow-up-task-repository.d.ts +16 -0
  137. package/dist/server/storage/repositories/butler-follow-up-task-repository.js +252 -0
  138. package/dist/server/storage/repositories/butler-follow-up-task-repository.js.map +1 -0
  139. package/dist/server/storage/repositories/butler-inbox-item-repository.d.ts +15 -0
  140. package/dist/server/storage/repositories/butler-inbox-item-repository.js +111 -0
  141. package/dist/server/storage/repositories/butler-inbox-item-repository.js.map +1 -0
  142. package/dist/server/storage/repositories/butler-notification-archive-repository.d.ts +9 -0
  143. package/dist/server/storage/repositories/butler-notification-archive-repository.js +48 -0
  144. package/dist/server/storage/repositories/butler-notification-archive-repository.js.map +1 -0
  145. package/dist/server/storage/repositories/butler-profile-repository.d.ts +9 -0
  146. package/dist/server/storage/repositories/butler-profile-repository.js +86 -0
  147. package/dist/server/storage/repositories/butler-profile-repository.js.map +1 -0
  148. package/dist/server/storage/repositories/butler-project-repository.d.ts +14 -0
  149. package/dist/server/storage/repositories/butler-project-repository.js +140 -0
  150. package/dist/server/storage/repositories/butler-project-repository.js.map +1 -0
  151. package/dist/server/storage/repositories/butler-session-repository.d.ts +11 -0
  152. package/dist/server/storage/repositories/butler-session-repository.js +106 -0
  153. package/dist/server/storage/repositories/butler-session-repository.js.map +1 -0
  154. package/dist/server/storage/repositories/butler-session-summary-state-repository.d.ts +8 -0
  155. package/dist/server/storage/repositories/butler-session-summary-state-repository.js +62 -0
  156. package/dist/server/storage/repositories/butler-session-summary-state-repository.js.map +1 -0
  157. package/dist/server/storage/repositories/patrol-plan-repository.d.ts +27 -0
  158. package/dist/server/storage/repositories/patrol-plan-repository.js +119 -0
  159. package/dist/server/storage/repositories/patrol-plan-repository.js.map +1 -0
  160. package/dist/server/storage/repositories/patrol-run-repository.d.ts +28 -0
  161. package/dist/server/storage/repositories/patrol-run-repository.js +121 -0
  162. package/dist/server/storage/repositories/patrol-run-repository.js.map +1 -0
  163. package/dist/server/storage/repositories/project-memory-repository.d.ts +15 -0
  164. package/dist/server/storage/repositories/project-memory-repository.js +150 -0
  165. package/dist/server/storage/repositories/project-memory-repository.js.map +1 -0
  166. package/dist/server/storage/repositories/session-checkpoint-repository.d.ts +9 -0
  167. package/dist/server/storage/repositories/session-checkpoint-repository.js +72 -0
  168. package/dist/server/storage/repositories/session-checkpoint-repository.js.map +1 -0
  169. package/dist/server/storage/repositories/session-message-origin-repository.d.ts +10 -0
  170. package/dist/server/storage/repositories/session-message-origin-repository.js +93 -0
  171. package/dist/server/storage/repositories/session-message-origin-repository.js.map +1 -0
  172. package/dist/server/storage/repositories/verification-run-repository.d.ts +29 -0
  173. package/dist/server/storage/repositories/verification-run-repository.js +125 -0
  174. package/dist/server/storage/repositories/verification-run-repository.js.map +1 -0
  175. package/dist/server/storage/sqlite/client.js +39 -0
  176. package/dist/server/storage/sqlite/client.js.map +1 -1
  177. package/dist/server/storage/sqlite/schema.sql +324 -0
  178. package/dist/server/types/domain.d.ts +261 -1
  179. package/dist/server/ws/ws-server.d.ts +2 -1
  180. package/dist/server/ws/ws-server.js +2 -1
  181. package/dist/server/ws/ws-server.js.map +1 -1
  182. package/node_modules/@codingns/session-sync-core/dist/index.d.ts +4 -0
  183. package/node_modules/@codingns/session-sync-core/dist/index.js +4 -0
  184. package/node_modules/@codingns/session-sync-core/dist/index.js.map +1 -1
  185. package/node_modules/@codingns/session-sync-core/dist/kimi-message-normalizer.d.ts +18 -0
  186. package/node_modules/@codingns/session-sync-core/dist/kimi-message-normalizer.js +659 -0
  187. package/node_modules/@codingns/session-sync-core/dist/kimi-message-normalizer.js.map +1 -0
  188. package/node_modules/@codingns/session-sync-core/dist/kimi-shared.d.ts +11 -0
  189. package/node_modules/@codingns/session-sync-core/dist/kimi-shared.js +72 -0
  190. package/node_modules/@codingns/session-sync-core/dist/kimi-shared.js.map +1 -0
  191. package/node_modules/@codingns/session-sync-core/dist/patch-builder.d.ts +8 -0
  192. package/node_modules/@codingns/session-sync-core/dist/patch-builder.js +89 -0
  193. package/node_modules/@codingns/session-sync-core/dist/patch-builder.js.map +1 -1
  194. package/node_modules/@codingns/session-sync-core/dist/providers/codex.js +4 -1
  195. package/node_modules/@codingns/session-sync-core/dist/providers/codex.js.map +1 -1
  196. package/node_modules/@codingns/session-sync-core/dist/providers/gemini.d.ts +41 -0
  197. package/node_modules/@codingns/session-sync-core/dist/providers/gemini.js +1086 -0
  198. package/node_modules/@codingns/session-sync-core/dist/providers/gemini.js.map +1 -0
  199. package/node_modules/@codingns/session-sync-core/dist/providers/kimi.d.ts +29 -0
  200. package/node_modules/@codingns/session-sync-core/dist/providers/kimi.js +578 -0
  201. package/node_modules/@codingns/session-sync-core/dist/providers/kimi.js.map +1 -0
  202. package/node_modules/@codingns/session-sync-core/dist/providers/opencode.js +2 -1
  203. package/node_modules/@codingns/session-sync-core/dist/providers/opencode.js.map +1 -1
  204. package/node_modules/@codingns/session-sync-core/dist/providers/utils.js +30 -2
  205. package/node_modules/@codingns/session-sync-core/dist/providers/utils.js.map +1 -1
  206. package/node_modules/@codingns/session-sync-core/dist/runtime/active-run-registry.d.ts +2 -0
  207. package/node_modules/@codingns/session-sync-core/dist/runtime/active-run-registry.js +43 -5
  208. package/node_modules/@codingns/session-sync-core/dist/runtime/active-run-registry.js.map +1 -1
  209. package/node_modules/@codingns/session-sync-core/dist/runtime/codex-runtime.d.ts +2 -0
  210. package/node_modules/@codingns/session-sync-core/dist/runtime/codex-runtime.js +320 -69
  211. package/node_modules/@codingns/session-sync-core/dist/runtime/codex-runtime.js.map +1 -1
  212. package/node_modules/@codingns/session-sync-core/dist/runtime/gemini-runtime.d.ts +21 -0
  213. package/node_modules/@codingns/session-sync-core/dist/runtime/gemini-runtime.js +537 -0
  214. package/node_modules/@codingns/session-sync-core/dist/runtime/gemini-runtime.js.map +1 -0
  215. package/node_modules/@codingns/session-sync-core/dist/runtime/kimi-runtime.d.ts +38 -0
  216. package/node_modules/@codingns/session-sync-core/dist/runtime/kimi-runtime.js +911 -0
  217. package/node_modules/@codingns/session-sync-core/dist/runtime/kimi-runtime.js.map +1 -0
  218. package/node_modules/@codingns/session-sync-core/dist/sqlite/node-sqlite.d.ts +6 -0
  219. package/node_modules/@codingns/session-sync-core/dist/sqlite/node-sqlite.js +9 -0
  220. package/node_modules/@codingns/session-sync-core/dist/sqlite/node-sqlite.js.map +1 -0
  221. package/node_modules/@codingns/session-sync-core/package.json +8 -0
  222. package/package.json +1 -1
  223. package/dist/public/assets/index-9hnprhO7.css +0 -1
  224. package/dist/public/assets/index-BTpmuKhG.js +0 -108
@@ -1,6 +1,7 @@
1
1
  import { existsSync, readFileSync, statSync } from "node:fs";
2
- import { CapabilityService, ClaudeCodeAdapter, CodexAdapter, OpenCodeAdapter, ProviderRegistry, SessionSyncService } from "@codingns/session-sync-core";
2
+ import { CapabilityService, ClaudeCodeAdapter, CodexAdapter, GeminiAdapter, KimiAdapter, OpenCodeAdapter, ProviderRegistry, SessionSyncService } from "@codingns/session-sync-core";
3
3
  import { AppError } from "../../shared/errors/app-error.js";
4
+ import { hashContent } from "../../shared/utils/hash.js";
4
5
  import { createId } from "../../shared/utils/id.js";
5
6
  import { logPerformance } from "../../shared/utils/perf-log.js";
6
7
  import { nowIso } from "../../shared/utils/time.js";
@@ -10,6 +11,14 @@ import { mapSessionProviderError } from "./session-provider-error-mapper.js";
10
11
  import { enrichClaudeCapabilities } from "../provider/claude-model-options.js";
11
12
  import { CodexModelOptionsService, enrichCodexCapabilities } from "../provider/codex-model-options.js";
12
13
  import { OpenCodeModelOptionsService, enrichOpenCodeCapabilities } from "../provider/opencode-model-options.js";
14
+ const SESSION_START_DEFERRED_PROVIDERS = new Set([
15
+ "codex",
16
+ "claude-code",
17
+ "opencode",
18
+ "gemini",
19
+ "kimi"
20
+ ]);
21
+ const MUTABLE_HISTORY_TAIL_REFRESH_INTERVAL_MS = 1_200;
13
22
  export class SessionHistoryService {
14
23
  db;
15
24
  workspaceRepository;
@@ -19,6 +28,7 @@ export class SessionHistoryService {
19
28
  sessionMessageAttachmentService;
20
29
  sessionStateRepository;
21
30
  sessionStatusSnapshotRepository;
31
+ sessionMessageOriginRepository;
22
32
  providerRegistry;
23
33
  sessionSyncService;
24
34
  capabilityService;
@@ -30,7 +40,7 @@ export class SessionHistoryService {
30
40
  workspaceDiscoveryInflight = new Map();
31
41
  workspaceStateRefreshInflight = new Map();
32
42
  workspaceSessionRelations = new Map();
33
- constructor(db, workspaceRepository, sessionBindingRepository, sessionChangedFileService, sessionIndexRepository, sessionMessageAttachmentService, sessionStateRepository, sessionStatusSnapshotRepository, config, sessionActivityAuthorityService = new SessionActivityAuthorityService()) {
43
+ constructor(db, workspaceRepository, sessionBindingRepository, sessionChangedFileService, sessionIndexRepository, sessionMessageAttachmentService, sessionStateRepository, sessionStatusSnapshotRepository, config, sessionActivityAuthorityService = new SessionActivityAuthorityService(), sessionMessageOriginRepository = null) {
34
44
  this.db = db;
35
45
  this.workspaceRepository = workspaceRepository;
36
46
  this.sessionBindingRepository = sessionBindingRepository;
@@ -39,11 +49,20 @@ export class SessionHistoryService {
39
49
  this.sessionMessageAttachmentService = sessionMessageAttachmentService;
40
50
  this.sessionStateRepository = sessionStateRepository;
41
51
  this.sessionStatusSnapshotRepository = sessionStatusSnapshotRepository;
52
+ this.sessionMessageOriginRepository = sessionMessageOriginRepository;
42
53
  this.sessionActivityAuthorityService = sessionActivityAuthorityService;
43
54
  this.claudeCodeHomeDir = config.claudeCodeHomeDir;
44
55
  this.providerRegistry = new ProviderRegistry([
45
56
  new ClaudeCodeAdapter({ homeDir: config.claudeCodeHomeDir }),
46
57
  new CodexAdapter({ homeDir: config.codexHomeDir }),
58
+ new GeminiAdapter({
59
+ homeDir: config.geminiHomeDir,
60
+ commandPath: config.geminiCliPath
61
+ }),
62
+ new KimiAdapter({
63
+ homeDir: config.kimiHomeDir,
64
+ defaultModel: config.kimiDefaultModel
65
+ }),
47
66
  new OpenCodeAdapter({
48
67
  baseUrl: config.opencodeBaseUrl,
49
68
  baseUrlResolver: config.opencodeBaseUrlResolver?.resolve.bind(config.opencodeBaseUrlResolver),
@@ -98,15 +117,16 @@ export class SessionHistoryService {
98
117
  }
99
118
  async readSessionHistory(sessionId, cursor, limit, direction = "forward", userId) {
100
119
  const startedAt = Date.now();
101
- const binding = this.getBindingOrThrow(sessionId);
102
- const current = this.sessionStatusSnapshotRepository.findBySessionId(sessionId);
120
+ const resolvedSessionId = this.resolveCanonicalSessionId(sessionId, userId);
121
+ const binding = this.getBindingOrThrow(resolvedSessionId);
122
+ const current = this.sessionStatusSnapshotRepository.findBySessionId(resolvedSessionId);
103
123
  const safeLimit = clampLimit(limit);
104
124
  const knownTotalMessageCount = direction === "backward" && cursor === null
105
- ? this.sessionIndexRepository.findIndexRecordBySessionId(sessionId)?.messageCount ?? null
125
+ ? this.sessionIndexRepository.findIndexRecordBySessionId(resolvedSessionId)?.messageCount ?? null
106
126
  : null;
107
127
  let readDurationMs = 0;
108
128
  let refreshStateDurationMs = 0;
109
- this.upsertSnapshot(sessionId, {
129
+ this.upsertSnapshot(resolvedSessionId, {
110
130
  syncStatus: "syncing",
111
131
  syncCursor: current?.syncCursor ?? cursor,
112
132
  lastSyncAt: current?.lastSyncAt ?? null,
@@ -116,9 +136,9 @@ export class SessionHistoryService {
116
136
  });
117
137
  try {
118
138
  const readStartedAt = Date.now();
119
- const page = await this.readPage(sessionId, binding.provider, binding.providerSessionId, binding.rawStoreRef, cursor, safeLimit, direction, knownTotalMessageCount);
139
+ const page = await this.readPage(resolvedSessionId, binding.provider, binding.providerSessionId, binding.rawStoreRef, cursor, safeLimit, direction, knownTotalMessageCount);
120
140
  readDurationMs = Date.now() - readStartedAt;
121
- this.upsertSnapshot(sessionId, {
141
+ this.upsertSnapshot(resolvedSessionId, {
122
142
  syncStatus: "idle",
123
143
  syncCursor: direction === "backward" && cursor !== null
124
144
  ? current?.syncCursor ?? page.cursor
@@ -129,7 +149,8 @@ export class SessionHistoryService {
129
149
  resumedAt: current?.resumedAt ?? null
130
150
  });
131
151
  logPerformance("session.read_history", Date.now() - startedAt, {
132
- sessionId,
152
+ sessionId: resolvedSessionId,
153
+ requestedSessionId: sessionId,
133
154
  provider: binding.provider,
134
155
  direction,
135
156
  limit: safeLimit,
@@ -145,7 +166,8 @@ export class SessionHistoryService {
145
166
  }
146
167
  catch (error) {
147
168
  logPerformance("session.read_history.failed", Date.now() - startedAt, {
148
- sessionId,
169
+ sessionId: resolvedSessionId,
170
+ requestedSessionId: sessionId,
149
171
  provider: binding.provider,
150
172
  direction,
151
173
  limit: safeLimit,
@@ -157,10 +179,17 @@ export class SessionHistoryService {
157
179
  thresholdMs: 0,
158
180
  force: true
159
181
  });
160
- this.markSessionError(sessionId, "PROVIDER_READ_FAILED", error);
182
+ this.markSessionError(resolvedSessionId, "PROVIDER_READ_FAILED", error);
161
183
  throw mapSessionProviderError(error);
162
184
  }
163
185
  }
186
+ resolveMessageOrigin(sessionId, message) {
187
+ return this.resolveMessageOrigins(sessionId, [message])[0] ?? {
188
+ ...message,
189
+ origin: null,
190
+ originRef: null
191
+ };
192
+ }
164
193
  async findLatestUserMessage(sessionId, content, maxAttempts = 12, minTimestamp = null) {
165
194
  const binding = this.getBindingOrThrow(sessionId);
166
195
  const acceptedContents = new Set((Array.isArray(content) ? content : [content]).filter((value) => value.trim().length > 0));
@@ -171,7 +200,7 @@ export class SessionHistoryService {
171
200
  .reverse()
172
201
  .find((message) => message.role === "user" &&
173
202
  acceptedContents.has(message.content) &&
174
- isMessageAtOrAfter(message.timestamp, minTimestamp));
203
+ isAcceptedUserMessageTimestamp(binding.provider, message.timestamp, minTimestamp));
175
204
  if (matched) {
176
205
  return matched;
177
206
  }
@@ -209,7 +238,9 @@ export class SessionHistoryService {
209
238
  return this.sessionChangedFileService.listBySessionId(sessionId);
210
239
  }
211
240
  listWorkspaceSessions(workspaceId, userId) {
212
- return this.enrichSessionItems(workspaceId, this.sessionIndexRepository.listByWorkspace(workspaceId, userId));
241
+ return this.enrichSessionItems(workspaceId, this.sessionIndexRepository
242
+ .listByWorkspace(workspaceId, userId)
243
+ .filter((item) => !this.isPendingSessionAlias(item)));
213
244
  }
214
245
  getProviderCapabilitiesSnapshot(provider) {
215
246
  try {
@@ -281,7 +312,7 @@ export class SessionHistoryService {
281
312
  }
282
313
  async startSession(input) {
283
314
  const workspace = this.getWorkspaceOrThrow(input.workspaceId);
284
- if (input.provider === "codex" || input.provider === "claude-code" || input.provider === "opencode") {
315
+ if (SESSION_START_DEFERRED_PROVIDERS.has(input.provider)) {
285
316
  throw new AppError({
286
317
  statusCode: 409,
287
318
  errorCode: "SESSION_START_DEFERRED",
@@ -385,7 +416,7 @@ export class SessionHistoryService {
385
416
  };
386
417
  }
387
418
  async subscribeSession(sessionId, cursor, limit, onEnvelope) {
388
- const sentMessageIds = new Set();
419
+ const deliveredMessages = createDeliveredHistoryMessageState();
389
420
  const safeLimit = clampLimit(limit);
390
421
  let currentCursor = cursor;
391
422
  const current = this.sessionStatusSnapshotRepository.findBySessionId(sessionId);
@@ -401,10 +432,10 @@ export class SessionHistoryService {
401
432
  });
402
433
  try {
403
434
  if (currentCursor === null) {
404
- currentCursor = await this.pullRecentSessionHistory(sessionId, safeLimit, sentMessageIds, onEnvelope, "session.backfill");
435
+ currentCursor = await this.pullRecentSessionHistory(sessionId, safeLimit, deliveredMessages, onEnvelope, "session.backfill");
405
436
  }
406
437
  else {
407
- await this.pullSessionHistory(sessionId, currentCursor, safeLimit, sentMessageIds, onEnvelope, "session.backfill").then((nextCursor) => {
438
+ await this.pullSessionHistory(sessionId, currentCursor, safeLimit, deliveredMessages, onEnvelope, "session.backfill").then((nextCursor) => {
408
439
  currentCursor = nextCursor;
409
440
  });
410
441
  }
@@ -418,7 +449,7 @@ export class SessionHistoryService {
418
449
  return;
419
450
  }
420
451
  polling = true;
421
- void this.pullSessionHistory(sessionId, currentCursor, safeLimit, sentMessageIds, onEnvelope, "session.delta", () => closed)
452
+ void this.pullSessionHistory(sessionId, currentCursor, safeLimit, deliveredMessages, onEnvelope, "session.delta", () => closed)
422
453
  .then((nextCursor) => {
423
454
  currentCursor = nextCursor;
424
455
  })
@@ -567,7 +598,7 @@ export class SessionHistoryService {
567
598
  detail: "session 不存在"
568
599
  });
569
600
  }
570
- return binding;
601
+ return this.resolvePendingSessionAliasBinding(binding) ?? binding;
571
602
  }
572
603
  persistSessionBinding(sessionId, workspaceId, snapshot) {
573
604
  if (!snapshot.providerSessionId || !snapshot.rawStoreRef) {
@@ -643,7 +674,8 @@ export class SessionHistoryService {
643
674
  continue;
644
675
  }
645
676
  const pendingDuplicate = exactExisting
646
- ?? findClaudePendingDiscoveryDuplicate(session, existingWorkspaceSessions, claimedPendingSessionIds);
677
+ ?? findClaudePendingDiscoveryDuplicate(session, existingWorkspaceSessions, claimedPendingSessionIds)
678
+ ?? findKimiRuntimeDiscoveryDuplicate(session, existingWorkspaceSessions, claimedPendingSessionIds);
647
679
  const existing = exactExisting ?? (pendingDuplicate
648
680
  ? this.sessionBindingRepository.findBySessionId(pendingDuplicate.sessionId)
649
681
  : null);
@@ -788,7 +820,8 @@ export class SessionHistoryService {
788
820
  : this.sessionSyncService.readHistory(provider, providerSessionId, rawStoreRef, cursor, limit, direction);
789
821
  return historyTask
790
822
  .then((page) => {
791
- const messages = this.sessionMessageAttachmentService.enrichMessages(sessionId, page.messages);
823
+ const messagesWithAttachments = this.sessionMessageAttachmentService.enrichMessages(sessionId, page.messages);
824
+ const messages = this.enrichMessagesWithOrigin(sessionId, messagesWithAttachments);
792
825
  this.persistSessionChangedFiles(sessionId, messages);
793
826
  return {
794
827
  ...page,
@@ -807,6 +840,64 @@ export class SessionHistoryService {
807
840
  throw mapSessionProviderError(error);
808
841
  });
809
842
  }
843
+ enrichMessagesWithOrigin(sessionId, messages) {
844
+ return this.resolveMessageOrigins(sessionId, messages);
845
+ }
846
+ resolveMessageOrigins(sessionId, messages) {
847
+ const originRepository = this.sessionMessageOriginRepository;
848
+ if (!originRepository || messages.length === 0) {
849
+ return messages.map((message) => ({
850
+ ...message,
851
+ origin: null,
852
+ originRef: null
853
+ }));
854
+ }
855
+ const messageIds = [...new Set(messages.map((message) => message.messageId).filter(Boolean))];
856
+ const originRows = originRepository.listBySessionAndMessageIds(sessionId, messageIds);
857
+ const originByMessageId = new Map(originRows
858
+ .filter((row) => row.messageId)
859
+ .map((row) => [row.messageId, row]));
860
+ const unresolvedRows = originRepository.listUnresolvedBySessionAndContents(sessionId, [...new Set(messages.map((message) => message.content).filter((content) => content.trim().length > 0))]);
861
+ const unresolvedByContent = new Map();
862
+ for (const row of unresolvedRows) {
863
+ const current = unresolvedByContent.get(row.content) ?? [];
864
+ current.push(row);
865
+ unresolvedByContent.set(row.content, current);
866
+ }
867
+ return messages.map((message) => {
868
+ const resolved = originByMessageId.get(message.messageId) ?? null;
869
+ if (resolved) {
870
+ return {
871
+ ...message,
872
+ origin: resolved.origin,
873
+ originRef: resolved.originRef
874
+ };
875
+ }
876
+ if (message.role !== "user") {
877
+ return {
878
+ ...message,
879
+ origin: null,
880
+ originRef: null
881
+ };
882
+ }
883
+ const candidates = unresolvedByContent.get(message.content) ?? [];
884
+ const matched = candidates.find((row) => isMessageAtOrAfter(message.timestamp, row.createdAt)) ?? null;
885
+ if (!matched) {
886
+ return {
887
+ ...message,
888
+ origin: null,
889
+ originRef: null
890
+ };
891
+ }
892
+ originRepository.resolveMessageId(sessionId, matched.clientRequestId, message.messageId, message.timestamp);
893
+ unresolvedByContent.set(message.content, candidates.filter((candidate) => candidate.clientRequestId !== matched.clientRequestId));
894
+ return {
895
+ ...message,
896
+ origin: matched.origin,
897
+ originRef: matched.originRef
898
+ };
899
+ });
900
+ }
810
901
  buildWorkspaceSessionRelationMap(sessions, discoveredSessionIds) {
811
902
  const relationMap = new Map();
812
903
  for (const session of sessions) {
@@ -863,12 +954,18 @@ export class SessionHistoryService {
863
954
  const resolution = this.sessionActivityAuthorityService.resolvePersistedSession(nextItem);
864
955
  return applySessionActivityResolution(nextItem, resolution);
865
956
  }
866
- async pullSessionHistory(sessionId, cursor, limit, sentMessageIds, onEnvelope, envelopeType, isClosed = () => false) {
957
+ async pullSessionHistory(sessionId, cursor, limit, deliveredMessages, onEnvelope, envelopeType, isClosed = () => false) {
867
958
  let currentCursor = cursor;
868
959
  while (!isClosed()) {
869
960
  const binding = this.getBindingOrThrow(sessionId);
870
961
  const page = await this.readPage(sessionId, binding.provider, binding.providerSessionId, binding.rawStoreRef, currentCursor, limit);
871
- await this.publishHistoryEnvelope(sessionId, binding, page, sentMessageIds, onEnvelope, envelopeType);
962
+ await this.publishHistoryEnvelope(sessionId, binding, page, deliveredMessages, onEnvelope, envelopeType);
963
+ if (envelopeType === "session.delta" &&
964
+ shouldRefreshMutableHistoryTail(binding.provider, page, currentCursor, deliveredMessages)) {
965
+ const tailPage = await this.readPage(sessionId, binding.provider, binding.providerSessionId, binding.rawStoreRef, null, Math.max(limit, 20), "backward");
966
+ deliveredMessages.lastMutableTailRefreshAtMs = Date.now();
967
+ await this.publishHistoryEnvelope(sessionId, binding, tailPage, deliveredMessages, onEnvelope, envelopeType);
968
+ }
872
969
  currentCursor = page.cursor;
873
970
  if (!page.nextCursor) {
874
971
  return currentCursor;
@@ -876,19 +973,21 @@ export class SessionHistoryService {
876
973
  }
877
974
  return currentCursor;
878
975
  }
879
- async pullRecentSessionHistory(sessionId, limit, sentMessageIds, onEnvelope, envelopeType) {
976
+ async pullRecentSessionHistory(sessionId, limit, deliveredMessages, onEnvelope, envelopeType) {
880
977
  const binding = this.getBindingOrThrow(sessionId);
881
978
  const knownTotalMessageCount = this.sessionIndexRepository.findIndexRecordBySessionId(sessionId)?.messageCount ?? null;
882
979
  const page = await this.readPage(sessionId, binding.provider, binding.providerSessionId, binding.rawStoreRef, null, limit, "backward", knownTotalMessageCount);
883
- await this.publishHistoryEnvelope(sessionId, binding, page, sentMessageIds, onEnvelope, envelopeType);
980
+ await this.publishHistoryEnvelope(sessionId, binding, page, deliveredMessages, onEnvelope, envelopeType);
884
981
  return page.cursor;
885
982
  }
886
- async publishHistoryEnvelope(sessionId, binding, page, sentMessageIds, onEnvelope, envelopeType) {
983
+ async publishHistoryEnvelope(sessionId, binding, page, deliveredMessages, onEnvelope, envelopeType) {
887
984
  const messages = page.messages.filter((message) => {
888
- if (sentMessageIds.has(message.messageId)) {
985
+ const nextSignature = buildDeliveredHistoryMessageSignature(message);
986
+ const previousSignature = deliveredMessages.signaturesByMessageId.get(message.messageId);
987
+ if (previousSignature === nextSignature) {
889
988
  return false;
890
989
  }
891
- sentMessageIds.add(message.messageId);
990
+ rememberDeliveredHistoryMessage(deliveredMessages, message.messageId, nextSignature);
892
991
  return true;
893
992
  });
894
993
  if (messages.length === 0) {
@@ -980,7 +1079,53 @@ export class SessionHistoryService {
980
1079
  detail: "session 索引缺失"
981
1080
  });
982
1081
  }
983
- return item;
1082
+ const aliasTargetSessionId = this.findPendingSessionAliasTargetSessionId(item);
1083
+ if (!aliasTargetSessionId) {
1084
+ return item;
1085
+ }
1086
+ return this.sessionIndexRepository.findBySessionId(aliasTargetSessionId, userId) ?? item;
1087
+ }
1088
+ resolveCanonicalSessionId(sessionId, userId) {
1089
+ if (userId) {
1090
+ const item = this.sessionIndexRepository.findBySessionId(sessionId, userId);
1091
+ const aliasTargetSessionId = this.findPendingSessionAliasTargetSessionId(item);
1092
+ if (aliasTargetSessionId) {
1093
+ return aliasTargetSessionId;
1094
+ }
1095
+ }
1096
+ const binding = this.sessionBindingRepository.findBySessionId(sessionId);
1097
+ return this.findPendingSessionAliasTargetSessionId(binding) ?? sessionId;
1098
+ }
1099
+ isPendingSessionAlias(item) {
1100
+ return Boolean(this.findPendingSessionAliasTargetSessionId(item));
1101
+ }
1102
+ resolvePendingSessionAliasBinding(binding) {
1103
+ const aliasTargetSessionId = this.findPendingSessionAliasTargetSessionId(binding);
1104
+ if (!aliasTargetSessionId) {
1105
+ return null;
1106
+ }
1107
+ return this.sessionBindingRepository.findBySessionId(aliasTargetSessionId);
1108
+ }
1109
+ findPendingSessionAliasTargetSessionId(descriptor) {
1110
+ if (!descriptor || descriptor.provider !== "gemini") {
1111
+ return null;
1112
+ }
1113
+ const aliasTargetSessionId = extractPendingBindingTargetSessionId(descriptor.providerSessionId)
1114
+ ?? extractPendingBindingTargetSessionId(descriptor.rawStoreRef);
1115
+ if (!aliasTargetSessionId || aliasTargetSessionId === descriptor.sessionId) {
1116
+ return null;
1117
+ }
1118
+ const targetBinding = this.sessionBindingRepository.findBySessionId(aliasTargetSessionId);
1119
+ if (!targetBinding) {
1120
+ return null;
1121
+ }
1122
+ if (targetBinding.workspaceId !== descriptor.workspaceId
1123
+ || targetBinding.provider !== descriptor.provider
1124
+ || isPendingBindingValue(targetBinding.providerSessionId)
1125
+ || isPendingBindingValue(targetBinding.rawStoreRef)) {
1126
+ return null;
1127
+ }
1128
+ return aliasTargetSessionId;
984
1129
  }
985
1130
  async refreshRecentSessionStates(sessions, userId) {
986
1131
  for (let index = 0; index < sessions.length; index += 1) {
@@ -1585,12 +1730,20 @@ function isPendingBindingValue(value) {
1585
1730
  function buildPendingBindingValue(provider, sessionId) {
1586
1731
  return `pending://${provider}/${sessionId}`;
1587
1732
  }
1733
+ function extractPendingBindingTargetSessionId(value) {
1734
+ if (!isPendingBindingValue(value)) {
1735
+ return null;
1736
+ }
1737
+ const normalizedValue = value.trim();
1738
+ const targetSessionId = normalizedValue.slice(normalizedValue.indexOf("/", "pending://".length) + 1).trim();
1739
+ return targetSessionId || null;
1740
+ }
1588
1741
  function isClaudePendingRuntimeRawStoreRef(rawStoreRef) {
1589
1742
  const normalizedRawStoreRef = rawStoreRef.replaceAll("\\", "/").toLowerCase();
1590
1743
  return normalizedRawStoreRef.includes("/.pending-");
1591
1744
  }
1592
1745
  function shouldShortCircuitClaudePendingHistory(provider, providerSessionId, rawStoreRef) {
1593
- if (provider !== "claude-code") {
1746
+ if (provider !== "claude-code" && provider !== "gemini") {
1594
1747
  return false;
1595
1748
  }
1596
1749
  return isPendingBindingValue(providerSessionId) || isPendingBindingValue(rawStoreRef);
@@ -1639,6 +1792,66 @@ function findClaudePendingDiscoveryDuplicate(session, existingSessions, claimedS
1639
1792
  });
1640
1793
  return activePendingCandidates.length === 1 ? activePendingCandidates[0] : null;
1641
1794
  }
1795
+ function findKimiRuntimeDiscoveryDuplicate(session, existingSessions, claimedSessionIds) {
1796
+ if (session.provider !== "kimi" || isPendingBindingValue(session.providerSessionId)) {
1797
+ return null;
1798
+ }
1799
+ const candidates = existingSessions.filter((item) => {
1800
+ if (claimedSessionIds.has(item.sessionId)) {
1801
+ return false;
1802
+ }
1803
+ if (item.provider !== "kimi" || !shouldRecoverKimiRuntimeBinding(item)) {
1804
+ return false;
1805
+ }
1806
+ return isCloseKimiSessionTimestamp(item.lastMessageAt ?? item.createdAt, session.lastMessageAt);
1807
+ });
1808
+ if (candidates.length === 1) {
1809
+ return candidates[0];
1810
+ }
1811
+ const comparableTitle = normalizeKimiComparableTitle(session.title);
1812
+ if (!comparableTitle) {
1813
+ return null;
1814
+ }
1815
+ const titleMatchedCandidates = candidates.filter((item) => normalizeKimiComparableTitle(item.title) === comparableTitle);
1816
+ return titleMatchedCandidates.length === 1
1817
+ ? titleMatchedCandidates[0]
1818
+ : null;
1819
+ }
1820
+ function shouldRecoverKimiRuntimeBinding(item) {
1821
+ if (isPendingBindingValue(item.providerSessionId)) {
1822
+ return true;
1823
+ }
1824
+ if (item.messageCount !== 0 || item.activitySource !== "runtime") {
1825
+ return false;
1826
+ }
1827
+ if (item.runningState === "starting") {
1828
+ return true;
1829
+ }
1830
+ if (item.lastErrorCode === "PROVIDER_READ_FAILED") {
1831
+ return true;
1832
+ }
1833
+ return (item.lastErrorDetail ?? "").includes("provider 会话不存在");
1834
+ }
1835
+ function normalizeKimiComparableTitle(title) {
1836
+ const normalized = title.trim().replace(/\s+/g, " ");
1837
+ if (!normalized) {
1838
+ return null;
1839
+ }
1840
+ return /^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$/i.test(normalized)
1841
+ ? null
1842
+ : normalized;
1843
+ }
1844
+ function isCloseKimiSessionTimestamp(left, right) {
1845
+ if (!left || !right) {
1846
+ return false;
1847
+ }
1848
+ const leftAt = Date.parse(left);
1849
+ const rightAt = Date.parse(right);
1850
+ if (!Number.isFinite(leftAt) || !Number.isFinite(rightAt)) {
1851
+ return false;
1852
+ }
1853
+ return Math.abs(leftAt - rightAt) <= 2 * 60 * 1_000;
1854
+ }
1642
1855
  function normalizeClaudeComparableTitle(title) {
1643
1856
  return title?.trim().replace(/\s+/g, " ").toLowerCase() ?? "";
1644
1857
  }
@@ -1670,6 +1883,54 @@ function isMessageAtOrAfter(timestamp, minTimestamp) {
1670
1883
  }
1671
1884
  return messageAt >= minAt;
1672
1885
  }
1886
+ function isAcceptedUserMessageTimestamp(provider, timestamp, minTimestamp) {
1887
+ if (provider === "kimi"
1888
+ && isSyntheticKimiHistoryTimestamp(timestamp)) {
1889
+ return true;
1890
+ }
1891
+ return isMessageAtOrAfter(timestamp, minTimestamp);
1892
+ }
1893
+ function isSyntheticKimiHistoryTimestamp(timestamp) {
1894
+ return timestamp.startsWith("2020-01-01T00:");
1895
+ }
1896
+ function createDeliveredHistoryMessageState() {
1897
+ return {
1898
+ signaturesByMessageId: new Map(),
1899
+ lastMutableTailRefreshAtMs: 0
1900
+ };
1901
+ }
1902
+ function shouldRefreshMutableHistoryTail(provider, page, cursor, deliveredMessages) {
1903
+ if (provider !== "kimi" || cursor === null || page.messages.length > 0) {
1904
+ return false;
1905
+ }
1906
+ return Date.now() - deliveredMessages.lastMutableTailRefreshAtMs >= MUTABLE_HISTORY_TAIL_REFRESH_INTERVAL_MS;
1907
+ }
1908
+ function buildDeliveredHistoryMessageSignature(message) {
1909
+ return hashContent(JSON.stringify({
1910
+ provider: message.provider,
1911
+ providerSessionId: message.providerSessionId,
1912
+ role: message.role,
1913
+ kind: message.kind,
1914
+ content: message.content,
1915
+ toolCall: message.toolCall,
1916
+ attachments: message.attachments ?? [],
1917
+ timestamp: message.timestamp,
1918
+ rawRef: message.rawRef
1919
+ }));
1920
+ }
1921
+ function rememberDeliveredHistoryMessage(state, messageId, signature) {
1922
+ if (state.signaturesByMessageId.has(messageId)) {
1923
+ state.signaturesByMessageId.delete(messageId);
1924
+ }
1925
+ state.signaturesByMessageId.set(messageId, signature);
1926
+ while (state.signaturesByMessageId.size > 2_048) {
1927
+ const oldestMessageId = state.signaturesByMessageId.keys().next().value;
1928
+ if (typeof oldestMessageId !== "string") {
1929
+ break;
1930
+ }
1931
+ state.signaturesByMessageId.delete(oldestMessageId);
1932
+ }
1933
+ }
1673
1934
  function delay(ms) {
1674
1935
  return new Promise((resolve) => {
1675
1936
  setTimeout(resolve, ms);