@jingyi0605/codingns 0.3.6 → 0.4.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (185) hide show
  1. package/README.md +3 -0
  2. package/bin/codingns.mjs +489 -1
  3. package/dist/public/assets/{TerminalPage-D00S4KM6.js → TerminalPage-6jHZV9Mh.js} +17 -17
  4. package/dist/public/assets/index-CSVhg7I8.js +123 -0
  5. package/dist/public/assets/index-Ce1VX19m.css +1 -0
  6. package/dist/public/index.html +2 -2
  7. package/dist/server/modules/assistant-capability/assistant-capability-controller.d.ts +173 -0
  8. package/dist/server/modules/assistant-capability/assistant-capability-controller.js +307 -0
  9. package/dist/server/modules/assistant-capability/assistant-capability-controller.js.map +1 -1
  10. package/dist/server/modules/assistant-capability/assistant-capability-service.d.ts +199 -2
  11. package/dist/server/modules/assistant-capability/assistant-capability-service.js +565 -3
  12. package/dist/server/modules/assistant-capability/assistant-capability-service.js.map +1 -1
  13. package/dist/server/modules/butler/assistant-automation-service.d.ts +110 -0
  14. package/dist/server/modules/butler/assistant-automation-service.js +786 -0
  15. package/dist/server/modules/butler/assistant-automation-service.js.map +1 -0
  16. package/dist/server/modules/butler/assistant-automation-trigger.d.ts +94 -0
  17. package/dist/server/modules/butler/assistant-automation-trigger.js +400 -0
  18. package/dist/server/modules/butler/assistant-automation-trigger.js.map +1 -0
  19. package/dist/server/modules/butler/assistant-sandbox-service.d.ts +55 -0
  20. package/dist/server/modules/butler/assistant-sandbox-service.js +266 -0
  21. package/dist/server/modules/butler/assistant-sandbox-service.js.map +1 -0
  22. package/dist/server/modules/butler/butler-action-context-service.d.ts +4 -1
  23. package/dist/server/modules/butler/butler-action-context-service.js +8 -2
  24. package/dist/server/modules/butler/butler-action-context-service.js.map +1 -1
  25. package/dist/server/modules/butler/butler-control-session-service.d.ts +8 -1
  26. package/dist/server/modules/butler/butler-control-session-service.js +154 -40
  27. package/dist/server/modules/butler/butler-control-session-service.js.map +1 -1
  28. package/dist/server/modules/butler/butler-control-timer-scheduler.d.ts +32 -0
  29. package/dist/server/modules/butler/butler-control-timer-scheduler.js +93 -0
  30. package/dist/server/modules/butler/butler-control-timer-scheduler.js.map +1 -0
  31. package/dist/server/modules/butler/butler-control-timer-service.d.ts +42 -0
  32. package/dist/server/modules/butler/butler-control-timer-service.js +132 -0
  33. package/dist/server/modules/butler/butler-control-timer-service.js.map +1 -0
  34. package/dist/server/modules/butler/butler-controller.d.ts +42 -2
  35. package/dist/server/modules/butler/butler-controller.js +79 -12
  36. package/dist/server/modules/butler/butler-controller.js.map +1 -1
  37. package/dist/server/modules/butler/butler-follow-up-service.d.ts +9 -1
  38. package/dist/server/modules/butler/butler-follow-up-service.js +273 -181
  39. package/dist/server/modules/butler/butler-follow-up-service.js.map +1 -1
  40. package/dist/server/modules/butler/butler-inbox-analysis-service.d.ts +4 -1
  41. package/dist/server/modules/butler/butler-inbox-analysis-service.js +18 -4
  42. package/dist/server/modules/butler/butler-inbox-analysis-service.js.map +1 -1
  43. package/dist/server/modules/butler/butler-profile-service.js +2 -5
  44. package/dist/server/modules/butler/butler-profile-service.js.map +1 -1
  45. package/dist/server/modules/butler/butler-project-service.d.ts +3 -1
  46. package/dist/server/modules/butler/butler-project-service.js +7 -1
  47. package/dist/server/modules/butler/butler-project-service.js.map +1 -1
  48. package/dist/server/modules/butler/butler-session-service.d.ts +3 -1
  49. package/dist/server/modules/butler/butler-session-service.js +12 -1
  50. package/dist/server/modules/butler/butler-session-service.js.map +1 -1
  51. package/dist/server/modules/butler/butler-session-summary-service.js +2 -1
  52. package/dist/server/modules/butler/butler-session-summary-service.js.map +1 -1
  53. package/dist/server/modules/butler/butler-workspace-context.d.ts +3 -0
  54. package/dist/server/modules/butler/butler-workspace-context.js +164 -44
  55. package/dist/server/modules/butler/butler-workspace-context.js.map +1 -1
  56. package/dist/server/modules/butler/patrol-execution-service.js +2 -1
  57. package/dist/server/modules/butler/patrol-execution-service.js.map +1 -1
  58. package/dist/server/modules/butler/provider-adapter-registry.d.ts +3 -0
  59. package/dist/server/modules/butler/provider-adapter-registry.js +18 -1
  60. package/dist/server/modules/butler/provider-adapter-registry.js.map +1 -1
  61. package/dist/server/modules/butler/verification-run-service.d.ts +9 -2
  62. package/dist/server/modules/butler/verification-run-service.js +188 -34
  63. package/dist/server/modules/butler/verification-run-service.js.map +1 -1
  64. package/dist/server/modules/debug-target/debug-target-controller.js +1 -1
  65. package/dist/server/modules/debug-target/debug-target-controller.js.map +1 -1
  66. package/dist/server/modules/debug-target/debug-target-service.d.ts +7 -2
  67. package/dist/server/modules/debug-target/debug-target-service.js +563 -100
  68. package/dist/server/modules/debug-target/debug-target-service.js.map +1 -1
  69. package/dist/server/modules/git/git-command-helper-client.d.ts +1 -0
  70. package/dist/server/modules/git/git-command-helper-client.js +19 -26
  71. package/dist/server/modules/git/git-command-helper-client.js.map +1 -1
  72. package/dist/server/modules/git/git-command-runner.js +19 -1
  73. package/dist/server/modules/git/git-command-runner.js.map +1 -1
  74. package/dist/server/modules/preferences/profile-service.d.ts +3 -1
  75. package/dist/server/modules/preferences/profile-service.js +74 -3
  76. package/dist/server/modules/preferences/profile-service.js.map +1 -1
  77. package/dist/server/modules/provider/provider-discovery-helper-client.d.ts +5 -3
  78. package/dist/server/modules/provider/provider-discovery-helper-client.js +129 -43
  79. package/dist/server/modules/provider/provider-discovery-helper-client.js.map +1 -1
  80. package/dist/server/modules/provider/provider-discovery-helper-process.js +44 -0
  81. package/dist/server/modules/provider/provider-discovery-helper-process.js.map +1 -1
  82. package/dist/server/modules/provider/provider-discovery-runtime.js +83 -3
  83. package/dist/server/modules/provider/provider-discovery-runtime.js.map +1 -1
  84. package/dist/server/modules/sessions/claude-runtime-helper-client.js +23 -1
  85. package/dist/server/modules/sessions/claude-runtime-helper-client.js.map +1 -1
  86. package/dist/server/modules/sessions/session-history-service.d.ts +7 -1
  87. package/dist/server/modules/sessions/session-history-service.js +251 -41
  88. package/dist/server/modules/sessions/session-history-service.js.map +1 -1
  89. package/dist/server/modules/sessions/session-live-runtime-service.d.ts +6 -0
  90. package/dist/server/modules/sessions/session-live-runtime-service.js +97 -11
  91. package/dist/server/modules/sessions/session-live-runtime-service.js.map +1 -1
  92. package/dist/server/modules/sessions/session-message-origin-utils.d.ts +12 -0
  93. package/dist/server/modules/sessions/session-message-origin-utils.js +45 -0
  94. package/dist/server/modules/sessions/session-message-origin-utils.js.map +1 -0
  95. package/dist/server/modules/sessions/session-permission-request-service.js +167 -0
  96. package/dist/server/modules/sessions/session-permission-request-service.js.map +1 -1
  97. package/dist/server/modules/skills/builtin-skill-service.js +1 -1
  98. package/dist/server/modules/skills/builtin-skill-service.js.map +1 -1
  99. package/dist/server/modules/skills/builtin-skills/codingns-assistant/SKILL.md +19 -12
  100. package/dist/server/modules/skills/builtin-skills/codingns-assistant/references/cli-workflow.md +9 -3
  101. package/dist/server/modules/tasks/task-helper-client.d.ts +5 -2
  102. package/dist/server/modules/tasks/task-helper-client.js +118 -38
  103. package/dist/server/modules/tasks/task-helper-client.js.map +1 -1
  104. package/dist/server/modules/tasks/task-helper-process.js +94 -3
  105. package/dist/server/modules/tasks/task-helper-process.js.map +1 -1
  106. package/dist/server/modules/tasks/task-types.d.ts +3 -0
  107. package/dist/server/modules/tasks/task-types.js +4 -1
  108. package/dist/server/modules/tasks/task-types.js.map +1 -1
  109. package/dist/server/modules/terminal/command-template-service.d.ts +9 -0
  110. package/dist/server/modules/terminal/command-template-service.js +87 -5
  111. package/dist/server/modules/terminal/command-template-service.js.map +1 -1
  112. package/dist/server/modules/terminal/terminal-controller.d.ts +3 -0
  113. package/dist/server/modules/terminal/terminal-controller.js +41 -0
  114. package/dist/server/modules/terminal/terminal-controller.js.map +1 -1
  115. package/dist/server/modules/workbench/workbench-service.d.ts +3 -0
  116. package/dist/server/modules/workbench/workbench-service.js +4 -3
  117. package/dist/server/modules/workbench/workbench-service.js.map +1 -1
  118. package/dist/server/modules/workbench/workspace-file-watcher.d.ts +14 -6
  119. package/dist/server/modules/workbench/workspace-file-watcher.js +267 -57
  120. package/dist/server/modules/workbench/workspace-file-watcher.js.map +1 -1
  121. package/dist/server/modules/workbench/workspace-panel-snapshot-service.d.ts +2 -0
  122. package/dist/server/modules/workbench/workspace-panel-snapshot-service.js +32 -3
  123. package/dist/server/modules/workbench/workspace-panel-snapshot-service.js.map +1 -1
  124. package/dist/server/modules/worktree/worktree-manager.d.ts +9 -1
  125. package/dist/server/modules/worktree/worktree-manager.js +9 -1
  126. package/dist/server/modules/worktree/worktree-manager.js.map +1 -1
  127. package/dist/server/routes/assistant.js +19 -0
  128. package/dist/server/routes/assistant.js.map +1 -1
  129. package/dist/server/routes/butler.js +5 -0
  130. package/dist/server/routes/butler.js.map +1 -1
  131. package/dist/server/server/create-server.d.ts +8 -0
  132. package/dist/server/server/create-server.js +36 -13
  133. package/dist/server/server/create-server.js.map +1 -1
  134. package/dist/server/storage/repositories/assistant-automation-run-repository.d.ts +12 -0
  135. package/dist/server/storage/repositories/assistant-automation-run-repository.js +139 -0
  136. package/dist/server/storage/repositories/assistant-automation-run-repository.js.map +1 -0
  137. package/dist/server/storage/repositories/assistant-automation-task-repository.d.ts +15 -0
  138. package/dist/server/storage/repositories/assistant-automation-task-repository.js +173 -0
  139. package/dist/server/storage/repositories/assistant-automation-task-repository.js.map +1 -0
  140. package/dist/server/storage/repositories/assistant-sandbox-workspace-repository.d.ts +17 -0
  141. package/dist/server/storage/repositories/assistant-sandbox-workspace-repository.js +164 -0
  142. package/dist/server/storage/repositories/assistant-sandbox-workspace-repository.js.map +1 -0
  143. package/dist/server/storage/repositories/butler-control-session-repository.js +27 -3
  144. package/dist/server/storage/repositories/butler-control-session-repository.js.map +1 -1
  145. package/dist/server/storage/repositories/butler-control-timer-repository.d.ts +15 -0
  146. package/dist/server/storage/repositories/butler-control-timer-repository.js +157 -0
  147. package/dist/server/storage/repositories/butler-control-timer-repository.js.map +1 -0
  148. package/dist/server/storage/repositories/user-preference-profile-repository.js +6 -3
  149. package/dist/server/storage/repositories/user-preference-profile-repository.js.map +1 -1
  150. package/dist/server/storage/sqlite/client.js +239 -2
  151. package/dist/server/storage/sqlite/client.js.map +1 -1
  152. package/dist/server/storage/sqlite/schema.sql +107 -1
  153. package/dist/server/types/domain.d.ts +89 -2
  154. package/dist/server/ws/workbench-ws-hub.d.ts +14 -8
  155. package/dist/server/ws/workbench-ws-hub.js +299 -163
  156. package/dist/server/ws/workbench-ws-hub.js.map +1 -1
  157. package/node_modules/@codingns/session-sync-core/dist/providers/claude-code.d.ts +4 -1
  158. package/node_modules/@codingns/session-sync-core/dist/providers/claude-code.js +111 -3
  159. package/node_modules/@codingns/session-sync-core/dist/providers/claude-code.js.map +1 -1
  160. package/node_modules/@codingns/session-sync-core/dist/providers/codex.d.ts +6 -1
  161. package/node_modules/@codingns/session-sync-core/dist/providers/codex.js +306 -31
  162. package/node_modules/@codingns/session-sync-core/dist/providers/codex.js.map +1 -1
  163. package/node_modules/@codingns/session-sync-core/dist/providers/gemini.d.ts +5 -1
  164. package/node_modules/@codingns/session-sync-core/dist/providers/gemini.js +187 -26
  165. package/node_modules/@codingns/session-sync-core/dist/providers/gemini.js.map +1 -1
  166. package/node_modules/@codingns/session-sync-core/dist/providers/kimi.d.ts +4 -1
  167. package/node_modules/@codingns/session-sync-core/dist/providers/kimi.js +98 -1
  168. package/node_modules/@codingns/session-sync-core/dist/providers/kimi.js.map +1 -1
  169. package/node_modules/@codingns/session-sync-core/dist/providers/opencode.d.ts +2 -0
  170. package/node_modules/@codingns/session-sync-core/dist/providers/opencode.js +71 -8
  171. package/node_modules/@codingns/session-sync-core/dist/providers/opencode.js.map +1 -1
  172. package/node_modules/@codingns/session-sync-core/dist/providers/utils.d.ts +1 -0
  173. package/node_modules/@codingns/session-sync-core/dist/providers/utils.js +4 -1
  174. package/node_modules/@codingns/session-sync-core/dist/providers/utils.js.map +1 -1
  175. package/node_modules/@codingns/session-sync-core/dist/runtime/claude-runtime.js +44 -0
  176. package/node_modules/@codingns/session-sync-core/dist/runtime/claude-runtime.js.map +1 -1
  177. package/node_modules/@codingns/session-sync-core/dist/runtime/codex-runtime.js +9 -3
  178. package/node_modules/@codingns/session-sync-core/dist/runtime/codex-runtime.js.map +1 -1
  179. package/node_modules/@codingns/session-sync-core/dist/runtime/types.d.ts +1 -0
  180. package/node_modules/@codingns/session-sync-core/dist/services.js +17 -8
  181. package/node_modules/@codingns/session-sync-core/dist/services.js.map +1 -1
  182. package/node_modules/@codingns/session-sync-core/dist/types.d.ts +4 -0
  183. package/package.json +1 -1
  184. package/dist/public/assets/index-BlOinYqR.js +0 -122
  185. package/dist/public/assets/index-Dg_7g6lA.css +0 -1
@@ -1,11 +1,12 @@
1
1
  import { basename, dirname, join } from "node:path";
2
2
  import { existsSync, readFileSync, readdirSync, renameSync, statSync } from "node:fs";
3
3
  import crypto from "node:crypto";
4
- import { appendJsonLine, createRawRef, encodeCursor, ensureDirectory, extractTextBlocks, ensureText, messageIdFromRawRef, nextTimestamp, normalizeWorkspacePath, readFirstNonEmptyLine, readJsonLines, readTrailingJsonLines, safeDate, sliceHistory, stringifyStructuredValue, walkJsonlFiles } from "./utils.js";
4
+ import { appendJsonLine, createRawRef, encodeCursor, ensureDirectory, extractTextBlocks, ensureText, messageIdFromRawRef, messageIdFromStableKey, nextTimestamp, normalizeWorkspacePath, readFirstNonEmptyLine, readJsonLines, readTrailingJsonLines, safeDate, sliceHistory, stringifyStructuredValue, walkJsonlFiles } from "./utils.js";
5
5
  import { loadDatabaseSync } from "../sqlite/node-sqlite.js";
6
6
  const CODEX_SESSION_TITLE_MAX_LENGTH = 48;
7
7
  const HISTORY_CACHE_LIMIT = 6;
8
8
  const SESSION_SUMMARY_CACHE_LIMIT = 512;
9
+ const SPAWN_RELATION_SCAN_CACHE_LIMIT = 512;
9
10
  const RECENT_HISTORY_INITIAL_BYTES = 256 * 1024;
10
11
  const RECENT_HISTORY_MAX_BYTES = 4 * 1024 * 1024;
11
12
  const RECENT_HISTORY_BUFFER_MESSAGES = 24;
@@ -19,20 +20,32 @@ export class CodexAdapter {
19
20
  providerId = "codex";
20
21
  historyCache = new Map();
21
22
  sessionSummaryCache = new Map();
23
+ spawnRelationScanCache = new Map();
22
24
  threadMetadataIndexCache = null;
23
25
  constructor(options) {
24
26
  this.options = options;
25
27
  }
26
28
  async detectSessions(workspacePath, options) {
29
+ const discovery = await this.detectSessionsDetailed(workspacePath, options);
30
+ return discovery.sessions;
31
+ }
32
+ async detectSessionsDetailed(workspacePath, options) {
33
+ const startedAt = Date.now();
27
34
  const targetPath = normalizeWorkspacePath(workspacePath);
28
- const files = this.listSessionFiles();
29
35
  const knownSessions = (options?.knownSessions ?? []).filter((session) => session.provider === this.providerId);
36
+ const threadMetadataIndex = this.readThreadMetadataIndex();
37
+ const files = this.listSessionFiles(targetPath, threadMetadataIndex, knownSessions);
30
38
  const knownByRawStoreRef = new Map(knownSessions.map((session) => [session.rawStoreRef, session]));
31
39
  const knownByProviderSessionId = new Map(knownSessions.map((session) => [session.providerSessionId, session]));
32
40
  const sessionsByProviderSessionId = new Map();
33
41
  const retainedSummaries = [];
34
42
  const pendingFiles = [];
43
+ let scannedFiles = 0;
44
+ let skippedByMtimeSize = 0;
45
+ let parsedFiles = 0;
46
+ let bytesRead = 0;
35
47
  for (const filePath of files) {
48
+ scannedFiles += 1;
36
49
  const stats = statSync(filePath);
37
50
  const cachedSummary = this.sessionSummaryCache.get(filePath);
38
51
  const fileSessionId = basename(filePath, ".jsonl");
@@ -47,6 +60,7 @@ export class CodexAdapter {
47
60
  if (cachedSummary.summary
48
61
  && hasUsableCodexTitle(cachedSummary.summary.title)
49
62
  && normalizeWorkspacePath(cachedSummary.summary.workspacePath) === targetPath) {
63
+ skippedByMtimeSize += 1;
50
64
  retainedSummaries.push({
51
65
  filePath,
52
66
  stats,
@@ -67,6 +81,7 @@ export class CodexAdapter {
67
81
  && hasUsableCodexTitle(knownByPath.title)
68
82
  && (!sessionIdentity
69
83
  || knownByPath.providerSessionId === sessionIdentity.threadId)) {
84
+ skippedByMtimeSize += 1;
70
85
  if (normalizeWorkspacePath(knownByPath.workspacePath) === targetPath) {
71
86
  retainedSummaries.push({
72
87
  filePath,
@@ -81,6 +96,8 @@ export class CodexAdapter {
81
96
  mtimeMs: stats.mtimeMs,
82
97
  size: stats.size,
83
98
  workspacePath: knownByPath.workspacePath,
99
+ providerSessionId: knownByPath.providerSessionId,
100
+ title: null,
84
101
  summary: null
85
102
  });
86
103
  continue;
@@ -92,6 +109,8 @@ export class CodexAdapter {
92
109
  mtimeMs: stats.mtimeMs,
93
110
  size: stats.size,
94
111
  workspacePath: sessionIdentity.cwd,
112
+ providerSessionId: sessionIdentity.threadId,
113
+ title: null,
95
114
  summary: null
96
115
  });
97
116
  continue;
@@ -107,13 +126,33 @@ export class CodexAdapter {
107
126
  });
108
127
  }
109
128
  if (pendingFiles.length === 0 && retainedSummaries.length === 0) {
110
- return [...sessionsByProviderSessionId.values()].sort((left, right) => (left.lastMessageAt ?? "").localeCompare(right.lastMessageAt ?? ""));
129
+ const sessions = [...sessionsByProviderSessionId.values()].sort((left, right) => (left.lastMessageAt ?? "").localeCompare(right.lastMessageAt ?? ""));
130
+ const diagnostic = {
131
+ provider: this.providerId,
132
+ status: "success",
133
+ durationMs: Date.now() - startedAt,
134
+ sessionCount: sessions.length,
135
+ isComplete: true,
136
+ errorMessage: null,
137
+ scannedFiles,
138
+ skippedByMtimeSize,
139
+ parsedFiles,
140
+ bytesRead
141
+ };
142
+ return {
143
+ sessions,
144
+ isComplete: true,
145
+ providerDiagnostics: [diagnostic]
146
+ };
111
147
  }
112
- const threadMetadataIndex = this.readThreadMetadataIndex();
113
148
  const pendingThreadIds = new Set([...pendingFiles, ...retainedSummaries]
114
149
  .map((entry) => entry.sessionIdentity?.threadId ?? null)
115
150
  .filter((value) => value !== null));
116
- const spawnedAgentRelationIndex = this.readSpawnedAgentRelationIndex([...pendingFiles, ...retainedSummaries].map((entry) => entry.filePath), targetPath, threadMetadataIndex, pendingThreadIds);
151
+ const spawnedAgentRelationIndex = this.readSpawnedAgentRelationIndex([...pendingFiles, ...retainedSummaries].map((entry) => ({
152
+ filePath: entry.filePath,
153
+ stats: entry.stats,
154
+ sessionIdentity: entry.sessionIdentity
155
+ })), targetPath, threadMetadataIndex, pendingThreadIds);
117
156
  for (const entry of retainedSummaries) {
118
157
  const currentThreadId = entry.sessionIdentity?.threadId ?? entry.summary.providerSessionId;
119
158
  const currentThreadMetadata = threadMetadataIndex.get(currentThreadId) ?? null;
@@ -127,21 +166,28 @@ export class CodexAdapter {
127
166
  mtimeMs: entry.stats.mtimeMs,
128
167
  size: entry.stats.size,
129
168
  workspacePath: summary.workspacePath,
169
+ providerSessionId: summary.providerSessionId,
170
+ title: summary.title,
130
171
  summary
131
172
  });
132
173
  sessionsByProviderSessionId.set(summary.providerSessionId, summary);
133
174
  }
134
175
  for (const entry of pendingFiles) {
135
176
  const { filePath, fileSessionId, stats, sessionIdentity } = entry;
177
+ parsedFiles += 1;
178
+ bytesRead += stats.size;
136
179
  const records = readJsonLines(filePath);
137
180
  const meta = records.find((record) => record.data.type === "session_meta")?.data;
138
181
  const metaPayload = (meta?.payload ?? {});
182
+ const codexSessionId = this.resolveCodexSessionId(metaPayload, fileSessionId);
139
183
  if (shouldIgnoreCodingNsDraftSession(metaPayload)) {
140
184
  this.touchSessionSummaryCache(filePath, {
141
185
  filePath,
142
186
  mtimeMs: stats.mtimeMs,
143
187
  size: stats.size,
144
188
  workspacePath: null,
189
+ providerSessionId: codexSessionId,
190
+ title: null,
145
191
  summary: null
146
192
  });
147
193
  continue;
@@ -155,11 +201,12 @@ export class CodexAdapter {
155
201
  mtimeMs: stats.mtimeMs,
156
202
  size: stats.size,
157
203
  workspacePath: cachedWorkspacePath,
204
+ providerSessionId: codexSessionId,
205
+ title: null,
158
206
  summary: null
159
207
  });
160
208
  continue;
161
209
  }
162
- const codexSessionId = this.resolveCodexSessionId(metaPayload, fileSessionId);
163
210
  const currentThreadMetadata = threadMetadataIndex.get(codexSessionId) ??
164
211
  (sessionIdentity ? threadMetadataIndex.get(sessionIdentity.threadId) : null);
165
212
  const currentSpawnRelation = spawnedAgentRelationIndex.get(codexSessionId) ??
@@ -178,6 +225,8 @@ export class CodexAdapter {
178
225
  mtimeMs: stats.mtimeMs,
179
226
  size: stats.size,
180
227
  workspacePath: sessionWorkspacePath,
228
+ providerSessionId: summary.providerSessionId,
229
+ title: summary.title,
181
230
  summary
182
231
  });
183
232
  sessionsByProviderSessionId.set(codexSessionId, summary);
@@ -204,10 +253,29 @@ export class CodexAdapter {
204
253
  mtimeMs: stats.mtimeMs,
205
254
  size: stats.size,
206
255
  workspacePath: sessionWorkspacePath,
256
+ providerSessionId: summary.providerSessionId,
257
+ title: summary.title,
207
258
  summary
208
259
  });
209
260
  }
210
- return [...sessionsByProviderSessionId.values()].sort((left, right) => (left.lastMessageAt ?? "").localeCompare(right.lastMessageAt ?? ""));
261
+ const sessions = [...sessionsByProviderSessionId.values()].sort((left, right) => (left.lastMessageAt ?? "").localeCompare(right.lastMessageAt ?? ""));
262
+ const diagnostic = {
263
+ provider: this.providerId,
264
+ status: "success",
265
+ durationMs: Date.now() - startedAt,
266
+ sessionCount: sessions.length,
267
+ isComplete: true,
268
+ errorMessage: null,
269
+ scannedFiles,
270
+ skippedByMtimeSize,
271
+ parsedFiles,
272
+ bytesRead
273
+ };
274
+ return {
275
+ sessions,
276
+ isComplete: true,
277
+ providerDiagnostics: [diagnostic]
278
+ };
211
279
  }
212
280
  async readSessionHistory(providerSessionId, rawStoreRef, cursor, limit, direction = "forward") {
213
281
  const resolvedStoreRef = this.resolveSessionFilePath(rawStoreRef, providerSessionId);
@@ -439,20 +507,50 @@ export class CodexAdapter {
439
507
  }
440
508
  async readSessionTitle(providerSessionId, rawStoreRef) {
441
509
  const resolvedStoreRef = this.resolveSessionFilePath(rawStoreRef, providerSessionId);
442
- const records = readJsonLines(resolvedStoreRef);
443
510
  const fileSessionId = basename(resolvedStoreRef, ".jsonl");
444
- const meta = records.find((record) => record.data.type === "session_meta")?.data;
445
- const metaPayload = (meta?.payload ?? {});
446
511
  const sessionIdentity = this.readSessionIdentity(resolvedStoreRef, fileSessionId);
447
- const codexSessionId = this.resolveCodexSessionId(metaPayload, providerSessionId || fileSessionId);
448
512
  const threadMetadataIndex = this.readThreadMetadataIndex();
449
- const messages = this.parseMessagesFromEntries(resolvedStoreRef, records, codexSessionId);
450
- return (this.resolveIndexedTitle(threadMetadataIndex, codexSessionId) ??
513
+ const indexedTitle = this.resolveIndexedTitle(threadMetadataIndex, providerSessionId) ??
451
514
  (sessionIdentity
452
515
  ? this.resolveIndexedTitle(threadMetadataIndex, sessionIdentity.threadId)
453
- : null) ??
516
+ : null);
517
+ if (indexedTitle) {
518
+ return indexedTitle;
519
+ }
520
+ const stats = statSync(resolvedStoreRef);
521
+ const cachedSummary = this.sessionSummaryCache.get(resolvedStoreRef);
522
+ if (cachedSummary
523
+ && cachedSummary.mtimeMs === stats.mtimeMs
524
+ && cachedSummary.size === stats.size
525
+ && (cachedSummary.providerSessionId === providerSessionId
526
+ || (sessionIdentity
527
+ && cachedSummary.providerSessionId === sessionIdentity.threadId))) {
528
+ this.touchSessionSummaryCache(resolvedStoreRef, cachedSummary);
529
+ if (cachedSummary.title) {
530
+ return cachedSummary.title;
531
+ }
532
+ if (cachedSummary.summary) {
533
+ return cachedSummary.summary.title;
534
+ }
535
+ }
536
+ const records = readJsonLines(resolvedStoreRef);
537
+ const meta = records.find((record) => record.data.type === "session_meta")?.data;
538
+ const metaPayload = (meta?.payload ?? {});
539
+ const codexSessionId = this.resolveCodexSessionId(metaPayload, providerSessionId || fileSessionId);
540
+ const messages = this.parseMessagesFromEntries(resolvedStoreRef, records, codexSessionId);
541
+ const resolvedTitle = (this.resolveIndexedTitle(threadMetadataIndex, codexSessionId) ??
454
542
  resolveCodexFallbackTitle(messages) ??
455
543
  fileSessionId);
544
+ this.touchSessionSummaryCache(resolvedStoreRef, {
545
+ filePath: resolvedStoreRef,
546
+ mtimeMs: stats.mtimeMs,
547
+ size: stats.size,
548
+ workspacePath: (sessionIdentity?.cwd ?? ensureText(metaPayload.cwd)) || null,
549
+ providerSessionId: codexSessionId,
550
+ title: resolvedTitle,
551
+ summary: null
552
+ });
553
+ return resolvedTitle;
456
554
  }
457
555
  async renameSessionTitle(providerSessionId, rawStoreRef, title) {
458
556
  const nextTitle = title.trim();
@@ -508,6 +606,8 @@ export class CodexAdapter {
508
606
  }
509
607
  this.sessionSummaryCache.delete(resolvedStoreRef);
510
608
  this.sessionSummaryCache.delete(nextStoreRef);
609
+ // 归档切换后线程索引的 archived / rollout_path 也变了,不能继续赌文件系统 mtime 一定会跳。
610
+ this.invalidateThreadMetadataIndexCache();
511
611
  return {
512
612
  rawStoreRef: nextStoreRef,
513
613
  isArchived
@@ -617,7 +717,8 @@ export class CodexAdapter {
617
717
  firstUserMessage: null,
618
718
  agentNickname: null,
619
719
  agentRole: null,
620
- isArchived: null
720
+ isArchived: null,
721
+ rolloutPath: null
621
722
  });
622
723
  }
623
724
  catch {
@@ -666,6 +767,7 @@ export class CodexAdapter {
666
767
  firstUserMessage: ensureText(row.first_user_message).trim() || (current?.firstUserMessage ?? null),
667
768
  agentNickname: ensureText(row.agent_nickname).trim() || (current?.agentNickname ?? null),
668
769
  agentRole: ensureText(row.agent_role).trim() || (current?.agentRole ?? null),
770
+ rolloutPath: ensureText(row.rollout_path).trim() || (current?.rolloutPath ?? null),
669
771
  isArchived: typeof row.archived === "number"
670
772
  ? row.archived === 1
671
773
  : ensureText(row.rollout_path).includes("archived_sessions")
@@ -694,11 +796,40 @@ export class CodexAdapter {
694
796
  };
695
797
  return index;
696
798
  }
697
- listSessionFiles() {
799
+ listSessionFiles(targetPath, threadMetadataIndex, knownSessions) {
698
800
  const activeFiles = walkJsonlFiles(join(this.options.homeDir, "sessions"));
699
- const archivedFiles = walkJsonlFiles(join(this.options.homeDir, "archived_sessions"));
801
+ const archivedFiles = this.listArchivedSessionFiles(targetPath, threadMetadataIndex, knownSessions);
700
802
  return [...activeFiles, ...archivedFiles];
701
803
  }
804
+ listArchivedSessionFiles(targetPath, threadMetadataIndex, knownSessions) {
805
+ const archivedFiles = new Set();
806
+ for (const metadata of threadMetadataIndex.values()) {
807
+ if (metadata.isArchived !== true
808
+ || !metadata.rolloutPath) {
809
+ continue;
810
+ }
811
+ if (targetPath.length > 0
812
+ && normalizeWorkspacePath(metadata.cwd ?? "") !== targetPath) {
813
+ continue;
814
+ }
815
+ archivedFiles.add(metadata.rolloutPath);
816
+ }
817
+ for (const session of knownSessions) {
818
+ if (session.isArchived !== true
819
+ || normalizeWorkspacePath(session.workspacePath) !== targetPath
820
+ || !isCodexArchivedFilePath(session.rawStoreRef)) {
821
+ continue;
822
+ }
823
+ archivedFiles.add(session.rawStoreRef);
824
+ }
825
+ if (archivedFiles.size > 0) {
826
+ return [...archivedFiles].filter((filePath) => existsSync(filePath));
827
+ }
828
+ if (threadMetadataIndex.size === 0) {
829
+ return walkJsonlFiles(join(this.options.homeDir, "archived_sessions"));
830
+ }
831
+ return [];
832
+ }
702
833
  resolveSessionFilePath(rawStoreRef, providerSessionId) {
703
834
  const matchedByThreadId = this.findSessionFileByThreadId(providerSessionId);
704
835
  if (existsSync(rawStoreRef)) {
@@ -732,7 +863,10 @@ export class CodexAdapter {
732
863
  return buildSyntheticCodexHistoryPath(this.options.homeDir, providerSessionId);
733
864
  }
734
865
  findSessionFileByThreadId(providerSessionId) {
735
- for (const filePath of this.listSessionFiles()) {
866
+ const threadMetadataIndex = this.readThreadMetadataIndex();
867
+ const activeFiles = walkJsonlFiles(join(this.options.homeDir, "sessions"));
868
+ const archivedFiles = this.listArchivedSessionFiles("", threadMetadataIndex, []);
869
+ for (const filePath of [...activeFiles, ...archivedFiles]) {
736
870
  const threadId = this.readThreadIdFromRawStore(filePath);
737
871
  if (threadId === providerSessionId) {
738
872
  return filePath;
@@ -834,6 +968,20 @@ export class CodexAdapter {
834
968
  this.sessionSummaryCache.delete(oldestKey);
835
969
  }
836
970
  }
971
+ invalidateThreadMetadataIndexCache() {
972
+ this.threadMetadataIndexCache = null;
973
+ }
974
+ touchSpawnRelationScanCache(filePath, entry) {
975
+ this.spawnRelationScanCache.delete(filePath);
976
+ this.spawnRelationScanCache.set(filePath, entry);
977
+ while (this.spawnRelationScanCache.size > SPAWN_RELATION_SCAN_CACHE_LIMIT) {
978
+ const oldestKey = this.spawnRelationScanCache.keys().next().value;
979
+ if (!oldestKey) {
980
+ break;
981
+ }
982
+ this.spawnRelationScanCache.delete(oldestKey);
983
+ }
984
+ }
837
985
  isForkedChildHistoryAligned(childThreadReadResult, expectedHistory) {
838
986
  const expectedSignatures = collectCodexForkComparableSignatures(expectedHistory);
839
987
  if (expectedSignatures.length === 0) {
@@ -880,22 +1028,65 @@ export class CodexAdapter {
880
1028
  readSpawnedAgentRelationIndex(files, targetPath, threadMetadataIndex, candidateThreadIds) {
881
1029
  const directRelations = new Map();
882
1030
  const spawnRecords = [];
883
- for (const filePath of files) {
884
- const sessionIdentity = this.readSessionIdentity(filePath, basename(filePath, ".jsonl"));
1031
+ for (const entry of files) {
1032
+ const { filePath, stats } = entry;
1033
+ const cached = this.spawnRelationScanCache.get(filePath);
1034
+ if (cached
1035
+ && cached.mtimeMs === stats.mtimeMs
1036
+ && cached.size === stats.size) {
1037
+ this.touchSpawnRelationScanCache(filePath, cached);
1038
+ if (cached.workspacePath !== targetPath) {
1039
+ continue;
1040
+ }
1041
+ for (const [threadId, relation] of cached.directRelations) {
1042
+ directRelations.set(threadId, relation);
1043
+ }
1044
+ spawnRecords.push(...cached.spawnRecords);
1045
+ continue;
1046
+ }
1047
+ const sessionIdentity = entry.sessionIdentity ?? this.readSessionIdentity(filePath, basename(filePath, ".jsonl"));
885
1048
  if (!sessionIdentity) {
1049
+ this.touchSpawnRelationScanCache(filePath, {
1050
+ filePath,
1051
+ mtimeMs: stats.mtimeMs,
1052
+ size: stats.size,
1053
+ workspacePath: null,
1054
+ directRelations: [],
1055
+ spawnRecords: []
1056
+ });
886
1057
  continue;
887
1058
  }
888
- if (normalizeWorkspacePath(sessionIdentity.cwd) !== targetPath) {
1059
+ const workspacePath = normalizeWorkspacePath(sessionIdentity.cwd);
1060
+ if (workspacePath !== targetPath) {
1061
+ this.touchSpawnRelationScanCache(filePath, {
1062
+ filePath,
1063
+ mtimeMs: stats.mtimeMs,
1064
+ size: stats.size,
1065
+ workspacePath,
1066
+ directRelations: [],
1067
+ spawnRecords: []
1068
+ });
889
1069
  continue;
890
1070
  }
891
1071
  if (sessionIdentity.parentThreadId) {
892
- directRelations.set(sessionIdentity.threadId, {
1072
+ const relation = {
893
1073
  parentProviderSessionId: sessionIdentity.parentThreadId
1074
+ };
1075
+ directRelations.set(sessionIdentity.threadId, relation);
1076
+ this.touchSpawnRelationScanCache(filePath, {
1077
+ filePath,
1078
+ mtimeMs: stats.mtimeMs,
1079
+ size: stats.size,
1080
+ workspacePath,
1081
+ directRelations: [[sessionIdentity.threadId, relation]],
1082
+ spawnRecords: []
894
1083
  });
895
1084
  continue;
896
1085
  }
897
1086
  const records = readJsonLines(filePath).map((record) => record.data);
898
1087
  const spawnCallById = new Map();
1088
+ const fileSpawnRecords = [];
1089
+ const fileDirectRelations = [];
899
1090
  for (const record of records) {
900
1091
  if (record.type !== "response_item") {
901
1092
  continue;
@@ -916,6 +1107,7 @@ export class CodexAdapter {
916
1107
  timestampMs: toTimestampMs(record.timestamp)
917
1108
  };
918
1109
  spawnCallById.set(callId, spawnRecord);
1110
+ fileSpawnRecords.push(spawnRecord);
919
1111
  spawnRecords.push(spawnRecord);
920
1112
  continue;
921
1113
  }
@@ -931,10 +1123,20 @@ export class CodexAdapter {
931
1123
  if (!agentId) {
932
1124
  continue;
933
1125
  }
934
- directRelations.set(agentId, {
1126
+ const relation = {
935
1127
  parentProviderSessionId: spawnRecord.parentProviderSessionId
936
- });
1128
+ };
1129
+ directRelations.set(agentId, relation);
1130
+ fileDirectRelations.push([agentId, relation]);
937
1131
  }
1132
+ this.touchSpawnRelationScanCache(filePath, {
1133
+ filePath,
1134
+ mtimeMs: stats.mtimeMs,
1135
+ size: stats.size,
1136
+ workspacePath,
1137
+ directRelations: fileDirectRelations,
1138
+ spawnRecords: fileSpawnRecords
1139
+ });
938
1140
  }
939
1141
  const relationCandidates = candidateThreadIds === undefined
940
1142
  ? [...threadMetadataIndex.entries()]
@@ -1058,7 +1260,13 @@ export class CodexAdapter {
1058
1260
  const content = ensureText(payload.message);
1059
1261
  if (content.length > 0) {
1060
1262
  pushMessage("event_msg", {
1061
- messageId: messageIdFromRawRef(rawRef),
1263
+ messageId: resolveCodexParsedMessageId({
1264
+ providerSessionId,
1265
+ rawRef,
1266
+ role: "user",
1267
+ kind: "text",
1268
+ payloadId: payload.id
1269
+ }),
1062
1270
  provider: this.providerId,
1063
1271
  providerSessionId,
1064
1272
  role: "user",
@@ -1074,7 +1282,13 @@ export class CodexAdapter {
1074
1282
  const content = ensureText(payload.message);
1075
1283
  if (content.length > 0) {
1076
1284
  pushMessage("event_msg", {
1077
- messageId: messageIdFromRawRef(rawRef),
1285
+ messageId: resolveCodexParsedMessageId({
1286
+ providerSessionId,
1287
+ rawRef,
1288
+ role: "assistant",
1289
+ kind: "text",
1290
+ payloadId: payload.id
1291
+ }),
1078
1292
  provider: this.providerId,
1079
1293
  providerSessionId,
1080
1294
  role: "assistant",
@@ -1090,7 +1304,13 @@ export class CodexAdapter {
1090
1304
  const content = extractTextBlocks(payload.text ?? payload.message).trim();
1091
1305
  if (content.length > 0) {
1092
1306
  pushMessage("event_msg", {
1093
- messageId: messageIdFromRawRef(rawRef),
1307
+ messageId: resolveCodexParsedMessageId({
1308
+ providerSessionId,
1309
+ rawRef,
1310
+ role: "assistant",
1311
+ kind: "thinking",
1312
+ payloadId: payload.id
1313
+ }),
1094
1314
  provider: this.providerId,
1095
1315
  providerSessionId,
1096
1316
  role: "assistant",
@@ -1112,7 +1332,13 @@ export class CodexAdapter {
1112
1332
  return;
1113
1333
  }
1114
1334
  pushMessage("response_item", {
1115
- messageId: messageIdFromRawRef(rawRef),
1335
+ messageId: resolveCodexParsedMessageId({
1336
+ providerSessionId,
1337
+ rawRef,
1338
+ role: "assistant",
1339
+ kind: "thinking",
1340
+ payloadId: payload.id
1341
+ }),
1116
1342
  provider: this.providerId,
1117
1343
  providerSessionId,
1118
1344
  role: "assistant",
@@ -1131,7 +1357,13 @@ export class CodexAdapter {
1131
1357
  const input = stringifyStructuredValue(inputSource);
1132
1358
  toolNameById.set(callId, name);
1133
1359
  pushMessage("response_item", {
1134
- messageId: messageIdFromRawRef(rawRef),
1360
+ messageId: resolveCodexParsedMessageId({
1361
+ providerSessionId,
1362
+ rawRef,
1363
+ role: "tool",
1364
+ kind: "tool_call",
1365
+ callId
1366
+ }),
1135
1367
  provider: this.providerId,
1136
1368
  providerSessionId,
1137
1369
  role: "tool",
@@ -1156,7 +1388,13 @@ export class CodexAdapter {
1156
1388
  const output = extractTextBlocks(payload.output).trim() || stringifyStructuredValue(payload.output);
1157
1389
  const resultState = resolveToolResultState(payload, output);
1158
1390
  pushMessage("response_item", {
1159
- messageId: messageIdFromRawRef(rawRef),
1391
+ messageId: resolveCodexParsedMessageId({
1392
+ providerSessionId,
1393
+ rawRef,
1394
+ role: "tool",
1395
+ kind: "tool_result",
1396
+ callId
1397
+ }),
1160
1398
  provider: this.providerId,
1161
1399
  providerSessionId,
1162
1400
  role: "tool",
@@ -1184,7 +1422,13 @@ export class CodexAdapter {
1184
1422
  return;
1185
1423
  }
1186
1424
  pushMessage("response_item", {
1187
- messageId: messageIdFromRawRef(rawRef),
1425
+ messageId: resolveCodexParsedMessageId({
1426
+ providerSessionId,
1427
+ rawRef,
1428
+ role,
1429
+ kind: "text",
1430
+ payloadId: payload.id
1431
+ }),
1188
1432
  provider: this.providerId,
1189
1433
  providerSessionId,
1190
1434
  role,
@@ -1380,6 +1624,37 @@ function areCodexTimestampsNear(left, right) {
1380
1624
  }
1381
1625
  return Math.abs(leftMs - rightMs) <= 1000;
1382
1626
  }
1627
+ function resolveCodexParsedMessageId(input) {
1628
+ const stableIdentity = resolveCodexStableIdentity(input);
1629
+ if (!stableIdentity) {
1630
+ return messageIdFromRawRef(input.rawRef);
1631
+ }
1632
+ return messageIdFromStableKey(buildCodexStableMessageKey(input.providerSessionId, stableIdentity));
1633
+ }
1634
+ function resolveCodexStableIdentity(input) {
1635
+ if (input.kind === "tool_call" || input.kind === "tool_result") {
1636
+ const normalizedCallId = ensureText(input.callId).trim();
1637
+ if (!normalizedCallId) {
1638
+ return null;
1639
+ }
1640
+ return input.kind === "tool_call"
1641
+ ? `tool:call:${normalizedCallId}`
1642
+ : `tool:result:${normalizedCallId}`;
1643
+ }
1644
+ if (input.role !== "assistant"
1645
+ && input.role !== "user") {
1646
+ return null;
1647
+ }
1648
+ const normalizedPayloadId = ensureText(input.payloadId).trim();
1649
+ if (!normalizedPayloadId) {
1650
+ return null;
1651
+ }
1652
+ const identityKind = input.kind === "thinking" ? "thinking" : "text";
1653
+ return `${input.role}:${identityKind}:${normalizedPayloadId}`;
1654
+ }
1655
+ function buildCodexStableMessageKey(providerSessionId, stableIdentity) {
1656
+ return `codex:${providerSessionId}:${stableIdentity}`;
1657
+ }
1383
1658
  function extractTextFromArray(value) {
1384
1659
  if (!Array.isArray(value)) {
1385
1660
  return "";