@orion-agents/orion-code 0.2.2 → 0.3.1

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 (217) hide show
  1. package/CHANGELOG.md +82 -0
  2. package/README.md +89 -14
  3. package/README.zh-CN.md +77 -14
  4. package/bin/orion +15 -4
  5. package/dist/cli.js +51 -172
  6. package/dist/cli.js.map +1 -1
  7. package/dist/commands/model-command-handlers.d.ts +2 -2
  8. package/dist/commands/model-command-handlers.d.ts.map +1 -1
  9. package/dist/commands/model-command-handlers.js +113 -32
  10. package/dist/commands/model-command-handlers.js.map +1 -1
  11. package/dist/commands/session-command-handlers.d.ts.map +1 -1
  12. package/dist/commands/session-command-handlers.js +106 -15
  13. package/dist/commands/session-command-handlers.js.map +1 -1
  14. package/dist/commands/types.d.ts +13 -3
  15. package/dist/commands/types.d.ts.map +1 -1
  16. package/dist/commands/types.js +1 -1
  17. package/dist/commands/types.js.map +1 -1
  18. package/dist/core/tool-artifacts.d.ts +2 -0
  19. package/dist/core/tool-artifacts.d.ts.map +1 -1
  20. package/dist/core/tool-artifacts.js +27 -1
  21. package/dist/core/tool-artifacts.js.map +1 -1
  22. package/dist/index.d.ts +1 -1
  23. package/dist/index.js +1 -1
  24. package/dist/runtime/agent-loop.d.ts.map +1 -1
  25. package/dist/runtime/agent-loop.js +3 -3
  26. package/dist/runtime/agent-loop.js.map +1 -1
  27. package/dist/runtime/agent-runtime-controller.d.ts +32 -1
  28. package/dist/runtime/agent-runtime-controller.d.ts.map +1 -1
  29. package/dist/runtime/agent-runtime-controller.js +241 -27
  30. package/dist/runtime/agent-runtime-controller.js.map +1 -1
  31. package/dist/runtime/agent-runtime-protocol.d.ts +4 -0
  32. package/dist/runtime/agent-runtime-protocol.d.ts.map +1 -1
  33. package/dist/runtime/agent-runtime-protocol.js.map +1 -1
  34. package/dist/runtime/agent-runtime-runner.d.ts +2 -1
  35. package/dist/runtime/agent-runtime-runner.d.ts.map +1 -1
  36. package/dist/runtime/durable-tool-receipt-reader.d.ts +32 -0
  37. package/dist/runtime/durable-tool-receipt-reader.d.ts.map +1 -0
  38. package/dist/runtime/durable-tool-receipt-reader.js +130 -0
  39. package/dist/runtime/durable-tool-receipt-reader.js.map +1 -0
  40. package/dist/runtime/legacy-thread-materializer.d.ts +31 -0
  41. package/dist/runtime/legacy-thread-materializer.d.ts.map +1 -1
  42. package/dist/runtime/legacy-thread-materializer.js +71 -17
  43. package/dist/runtime/legacy-thread-materializer.js.map +1 -1
  44. package/dist/runtime/orion-runtime-v1.d.ts +2 -0
  45. package/dist/runtime/orion-runtime-v1.d.ts.map +1 -1
  46. package/dist/runtime/orion-runtime-v1.js +7 -1
  47. package/dist/runtime/orion-runtime-v1.js.map +1 -1
  48. package/dist/runtime/orion-session-runner.d.ts +7 -2
  49. package/dist/runtime/orion-session-runner.d.ts.map +1 -1
  50. package/dist/runtime/orion-session-runner.js +16 -6
  51. package/dist/runtime/orion-session-runner.js.map +1 -1
  52. package/dist/runtime/product-bootstrap.d.ts +22 -0
  53. package/dist/runtime/product-bootstrap.d.ts.map +1 -0
  54. package/dist/runtime/product-bootstrap.js +374 -0
  55. package/dist/runtime/product-bootstrap.js.map +1 -0
  56. package/dist/runtime/product-orion-runtime.d.ts +2 -1
  57. package/dist/runtime/product-orion-runtime.d.ts.map +1 -1
  58. package/dist/runtime/product-orion-runtime.js +83 -13
  59. package/dist/runtime/product-orion-runtime.js.map +1 -1
  60. package/dist/runtime/protocol/runtime-protocol-v1.d.ts +1 -1
  61. package/dist/runtime/protocol/runtime-protocol-v1.js +1 -1
  62. package/dist/runtime/protocol/runtime-protocol-v1.js.map +1 -1
  63. package/dist/runtime/release-receipts.d.ts +66 -3
  64. package/dist/runtime/release-receipts.d.ts.map +1 -1
  65. package/dist/runtime/release-receipts.js +340 -7
  66. package/dist/runtime/release-receipts.js.map +1 -1
  67. package/dist/runtime/step-snapshot.d.ts.map +1 -1
  68. package/dist/runtime/step-snapshot.js +22 -2
  69. package/dist/runtime/step-snapshot.js.map +1 -1
  70. package/dist/runtime/subagents/runtime-integration.d.ts +1 -1
  71. package/dist/runtime/subagents/runtime-integration.d.ts.map +1 -1
  72. package/dist/runtime/subagents/runtime-integration.js +5 -2
  73. package/dist/runtime/subagents/runtime-integration.js.map +1 -1
  74. package/dist/runtime/thread-event-store.d.ts +80 -0
  75. package/dist/runtime/thread-event-store.d.ts.map +1 -1
  76. package/dist/runtime/thread-event-store.js +456 -22
  77. package/dist/runtime/thread-event-store.js.map +1 -1
  78. package/dist/runtime/thread-projection.d.ts +10 -0
  79. package/dist/runtime/thread-projection.d.ts.map +1 -1
  80. package/dist/runtime/thread-projection.js +40 -0
  81. package/dist/runtime/thread-projection.js.map +1 -1
  82. package/dist/runtime/thread-runtime.d.ts +3 -1
  83. package/dist/runtime/thread-runtime.d.ts.map +1 -1
  84. package/dist/runtime/thread-runtime.js +6 -0
  85. package/dist/runtime/thread-runtime.js.map +1 -1
  86. package/dist/runtime/thread-session-index.d.ts +84 -0
  87. package/dist/runtime/thread-session-index.d.ts.map +1 -0
  88. package/dist/runtime/thread-session-index.js +503 -0
  89. package/dist/runtime/thread-session-index.js.map +1 -0
  90. package/dist/runtime/thread-session-view.d.ts +70 -9
  91. package/dist/runtime/thread-session-view.d.ts.map +1 -1
  92. package/dist/runtime/thread-session-view.js +200 -82
  93. package/dist/runtime/thread-session-view.js.map +1 -1
  94. package/dist/runtime/thread-ui-adapter.d.ts +2 -0
  95. package/dist/runtime/thread-ui-adapter.d.ts.map +1 -1
  96. package/dist/runtime/thread-ui-adapter.js +86 -4
  97. package/dist/runtime/thread-ui-adapter.js.map +1 -1
  98. package/dist/runtime/tool-detail-repository.d.ts.map +1 -1
  99. package/dist/runtime/tool-detail-repository.js +23 -19
  100. package/dist/runtime/tool-detail-repository.js.map +1 -1
  101. package/dist/runtime/tool-receipt-validator.d.ts +21 -0
  102. package/dist/runtime/tool-receipt-validator.d.ts.map +1 -0
  103. package/dist/runtime/tool-receipt-validator.js +157 -0
  104. package/dist/runtime/tool-receipt-validator.js.map +1 -0
  105. package/dist/runtime/ui-events.d.ts +35 -0
  106. package/dist/runtime/ui-events.d.ts.map +1 -1
  107. package/dist/runtime/ui-events.js +1 -0
  108. package/dist/runtime/ui-events.js.map +1 -1
  109. package/dist/runtime/ui-view-model.d.ts +2 -0
  110. package/dist/runtime/ui-view-model.d.ts.map +1 -1
  111. package/dist/runtime/ui-view-model.js +7 -3
  112. package/dist/runtime/ui-view-model.js.map +1 -1
  113. package/dist/services/global-config.d.ts +25 -0
  114. package/dist/services/global-config.d.ts.map +1 -1
  115. package/dist/services/global-config.js +373 -5
  116. package/dist/services/global-config.js.map +1 -1
  117. package/dist/services/redaction.d.ts +4 -0
  118. package/dist/services/redaction.d.ts.map +1 -1
  119. package/dist/services/redaction.js +63 -0
  120. package/dist/services/redaction.js.map +1 -1
  121. package/dist/services/session-index.d.ts +16 -0
  122. package/dist/services/session-index.d.ts.map +1 -1
  123. package/dist/services/session-index.js +45 -36
  124. package/dist/services/session-index.js.map +1 -1
  125. package/dist/services/session-storage.d.ts +59 -0
  126. package/dist/services/session-storage.d.ts.map +1 -1
  127. package/dist/services/session-storage.js +306 -107
  128. package/dist/services/session-storage.js.map +1 -1
  129. package/dist/services/settings-coordinator.d.ts +174 -0
  130. package/dist/services/settings-coordinator.d.ts.map +1 -0
  131. package/dist/services/settings-coordinator.js +605 -0
  132. package/dist/services/settings-coordinator.js.map +1 -0
  133. package/dist/services/settings-document-repository.d.ts +138 -0
  134. package/dist/services/settings-document-repository.d.ts.map +1 -0
  135. package/dist/services/settings-document-repository.js +551 -0
  136. package/dist/services/settings-document-repository.js.map +1 -0
  137. package/dist/services/workspace-registry.d.ts +42 -0
  138. package/dist/services/workspace-registry.d.ts.map +1 -0
  139. package/dist/services/workspace-registry.js +222 -0
  140. package/dist/services/workspace-registry.js.map +1 -0
  141. package/dist/terminal-ui/launch.d.ts.map +1 -1
  142. package/dist/terminal-ui/launch.js +5 -0
  143. package/dist/terminal-ui/launch.js.map +1 -1
  144. package/dist/tui-ui/state.d.ts.map +1 -1
  145. package/dist/tui-ui/state.js +5 -0
  146. package/dist/tui-ui/state.js.map +1 -1
  147. package/dist/web/errors.d.ts +6 -0
  148. package/dist/web/errors.d.ts.map +1 -0
  149. package/dist/web/errors.js +13 -0
  150. package/dist/web/errors.js.map +1 -0
  151. package/dist/web/event-hub.d.ts +43 -0
  152. package/dist/web/event-hub.d.ts.map +1 -0
  153. package/dist/web/event-hub.js +323 -0
  154. package/dist/web/event-hub.js.map +1 -0
  155. package/dist/web/file-read-service.d.ts +55 -0
  156. package/dist/web/file-read-service.d.ts.map +1 -0
  157. package/dist/web/file-read-service.js +399 -0
  158. package/dist/web/file-read-service.js.map +1 -0
  159. package/dist/web/git-read-model-service.d.ts +77 -0
  160. package/dist/web/git-read-model-service.d.ts.map +1 -0
  161. package/dist/web/git-read-model-service.js +631 -0
  162. package/dist/web/git-read-model-service.js.map +1 -0
  163. package/dist/web/index.d.ts +6 -0
  164. package/dist/web/index.d.ts.map +1 -0
  165. package/dist/web/index.js +28 -0
  166. package/dist/web/index.js.map +1 -0
  167. package/dist/web/launch.d.ts +9 -0
  168. package/dist/web/launch.d.ts.map +1 -0
  169. package/dist/web/launch.js +64 -0
  170. package/dist/web/launch.js.map +1 -0
  171. package/dist/web/protocol.d.ts +408 -0
  172. package/dist/web/protocol.d.ts.map +1 -0
  173. package/dist/web/protocol.js +369 -0
  174. package/dist/web/protocol.js.map +1 -0
  175. package/dist/web/review-service.d.ts +38 -0
  176. package/dist/web/review-service.d.ts.map +1 -0
  177. package/dist/web/review-service.js +74 -0
  178. package/dist/web/review-service.js.map +1 -0
  179. package/dist/web/server.d.ts +20 -0
  180. package/dist/web/server.d.ts.map +1 -0
  181. package/dist/web/server.js +744 -0
  182. package/dist/web/server.js.map +1 -0
  183. package/dist/web/terminal-manager.d.ts +155 -0
  184. package/dist/web/terminal-manager.d.ts.map +1 -0
  185. package/dist/web/terminal-manager.js +728 -0
  186. package/dist/web/terminal-manager.js.map +1 -0
  187. package/dist/web/terminal-server.d.ts +8 -0
  188. package/dist/web/terminal-server.d.ts.map +1 -0
  189. package/dist/web/terminal-server.js +275 -0
  190. package/dist/web/terminal-server.js.map +1 -0
  191. package/dist/web/workbench-controller.d.ts +99 -0
  192. package/dist/web/workbench-controller.d.ts.map +1 -0
  193. package/dist/web/workbench-controller.js +1267 -0
  194. package/dist/web/workbench-controller.js.map +1 -0
  195. package/dist/web-client/assets/DiffViewer-DpKoD07C.js +5 -0
  196. package/dist/web-client/assets/FilesPanel-BkoLmgjR.js +2 -0
  197. package/dist/web-client/assets/GitPanel-D20StcdG.js +1 -0
  198. package/dist/web-client/assets/ReviewPanel-3MVWbMQV.js +1 -0
  199. package/dist/web-client/assets/TerminalPanel-BLQ592Fb.js +21 -0
  200. package/dist/web-client/assets/TerminalPanel-DLuoa74B.css +1 -0
  201. package/dist/web-client/assets/index-BkIDl_xk.js +16 -0
  202. package/dist/web-client/assets/index-DXMpnWE8.css +1 -0
  203. package/dist/web-client/index.html +16 -0
  204. package/docs/architecture/v0.3.0-web-api.yaml +1340 -0
  205. package/docs/architecture/v0.3.1-web-api.yaml +2290 -0
  206. package/docs/migration/v0.2.2-to-v0.3.0-settings.md +114 -0
  207. package/docs/migration/v0.2.2-to-v0.3.0.md +55 -0
  208. package/docs/migration/v0.3.0-to-v0.3.1.md +88 -0
  209. package/docs/orion.example.json +9 -2
  210. package/docs/plan/v0.3.0-node-runtime-compatibility-plan.md +57 -0
  211. package/docs/plan/v0.3.0-settings-integration-plan.md +740 -0
  212. package/docs/plan/v0.3.0-web-workbench-plan.md +376 -0
  213. package/docs/plan/v0.3.1-web-workbench-professional-shell-plan.md +848 -0
  214. package/docs/readme.md +22 -1
  215. package/docs/test/v0.3.1-web-workbench-e2e-plan.md +140 -0
  216. package/npm-shrinkwrap.json +1229 -45
  217. package/package.json +47 -11
@@ -15,6 +15,8 @@ exports.advanceSessionCompactSourceReceipt = advanceSessionCompactSourceReceipt;
15
15
  exports.createSession = createSession;
16
16
  exports.saveSessionMeta = saveSessionMeta;
17
17
  exports.updateSessionEffort = updateSessionEffort;
18
+ exports.updateSessionModel = updateSessionModel;
19
+ exports.updateSessionThreadReadModel = updateSessionThreadReadModel;
18
20
  exports.loadSessionMeta = loadSessionMeta;
19
21
  exports.updateSessionStats = updateSessionStats;
20
22
  exports.resumeSession = resumeSession;
@@ -32,6 +34,7 @@ exports.loadSessionTranscriptMessages = loadSessionTranscriptMessages;
32
34
  exports.endSession = endSession;
33
35
  exports.updateSessionSummary = updateSessionSummary;
34
36
  exports.getLastSession = getLastSession;
37
+ exports.filterSessionsWithRestorableHistory = filterSessionsWithRestorableHistory;
35
38
  exports.appendHistory = appendHistory;
36
39
  exports.readHistory = readHistory;
37
40
  exports.readProjectHistory = readProjectHistory;
@@ -45,10 +48,12 @@ exports.appendSessionTraceEvent = appendSessionTraceEvent;
45
48
  exports.readSessionTraceEvents = readSessionTraceEvents;
46
49
  exports.loadSessionHistory = loadSessionHistory;
47
50
  exports.loadSessionHistoryWithDiagnostics = loadSessionHistoryWithDiagnostics;
51
+ exports.loadSessionRestoreBundle = loadSessionRestoreBundle;
48
52
  exports.listSessions = listSessions;
49
53
  exports.listProjectSessions = listProjectSessions;
50
54
  exports.findSession = findSession;
51
55
  exports.lookupSessionRef = lookupSessionRef;
56
+ exports.lookupSessionRefInSessions = lookupSessionRefInSessions;
52
57
  exports.renameSession = renameSession;
53
58
  exports.deleteSession = deleteSession;
54
59
  const fs_1 = require("fs");
@@ -66,6 +71,9 @@ const goal_storage_1 = require("./goal-storage");
66
71
  const fingerprint_1 = require("./compact/fingerprint");
67
72
  const token_estimate_1 = require("../utils/token-estimate");
68
73
  const thread_session_view_1 = require("../runtime/thread-session-view");
74
+ const legacy_thread_materializer_1 = require("../runtime/legacy-thread-materializer");
75
+ const paths_1 = require("../product/paths");
76
+ const turn_commit_1 = require("../runtime/turn-commit");
69
77
  const harness_1 = require("../harness");
70
78
  var redaction_2 = require("./redaction");
71
79
  Object.defineProperty(exports, "redactTraceText", { enumerable: true, get: function () { return redaction_2.redactTraceText; } });
@@ -304,7 +312,7 @@ function normalizeSessionMeta(session) {
304
312
  updatedAt,
305
313
  updatedAtIso: session.updatedAtIso ?? new Date(updatedAt).toISOString(),
306
314
  messageCount: session.messageCount ?? 0,
307
- historySizeBytes: computeSessionHistorySizeBytes({ id: session.id, projectPath }),
315
+ historySizeBytes: session.historySizeBytes ?? computeSessionHistorySizeBytes({ id: session.id, projectPath }),
308
316
  tokenCount: session.tokenCount ?? 0,
309
317
  cost: session.cost ?? 0,
310
318
  gitBranch: session.gitBranch ?? getGitBranch(projectPath),
@@ -980,28 +988,6 @@ function loadRawSessionMeta(sessionId) {
980
988
  const path = findSessionMetaPath(sessionId);
981
989
  return path ? readSessionMetaAtPath(path) : null;
982
990
  }
983
- function projectSessionMetaWithThread(session) {
984
- try {
985
- const threadSummary = (0, thread_session_view_1.loadThreadSessionSummaryV1)(session.projectPath, session.id);
986
- if (!threadSummary)
987
- return session;
988
- const updatedAt = Math.max(session.updatedAt ?? session.startTime, threadSummary.updatedAt);
989
- return {
990
- ...session,
991
- updatedAt,
992
- updatedAtIso: new Date(updatedAt).toISOString(),
993
- messageCount: threadSummary.messageCount,
994
- historySizeBytes: threadSummary.historySizeBytes,
995
- };
996
- }
997
- catch (error) {
998
- // A damaged historical Thread must not make every healthy Session in the
999
- // project undiscoverable. Its raw metadata remains selectable for an
1000
- // explicit, isolated recovery attempt.
1001
- (0, debug_log_1.debugError)('session-storage.projectThreadMeta', error, session.id);
1002
- return session;
1003
- }
1004
- }
1005
991
  /**
1006
992
  * Serialize a session read-modify-write transaction across Orion processes.
1007
993
  * The metadata path is rediscovered before each transaction and re-read only
@@ -1036,12 +1022,100 @@ function updateSessionEffort(sessionId, effortPreference) {
1036
1022
  }
1037
1023
  });
1038
1024
  }
1025
+ /** Persist the model selected for one Session without replacing unrelated metadata. */
1026
+ function updateSessionModel(sessionId, model) {
1027
+ const normalizedModel = model.trim();
1028
+ if (!normalizedModel)
1029
+ throw new Error('Session model must not be empty.');
1030
+ return mutateSessionMeta(sessionId, session => {
1031
+ session.model = normalizedModel;
1032
+ session.updatedAt = Date.now();
1033
+ session.updatedAtIso = new Date(session.updatedAt).toISOString();
1034
+ });
1035
+ }
1036
+ /**
1037
+ * Persist the bounded Session-list projection derived from an authoritative
1038
+ * v2 Thread. List and picker paths consume this catalog projection directly;
1039
+ * they must never replay every Thread merely to render metadata.
1040
+ */
1041
+ function updateSessionThreadReadModel(sessionId, input) {
1042
+ if (!input.threadId.trim())
1043
+ throw new Error('Session Thread id must not be empty.');
1044
+ if (!Number.isSafeInteger(input.cursor) || input.cursor < 0) {
1045
+ throw new Error('Session Thread cursor must be a non-negative safe integer.');
1046
+ }
1047
+ if (!input.projectionDigest.trim()) {
1048
+ throw new Error('Session Thread projection digest must not be empty.');
1049
+ }
1050
+ if (!Number.isSafeInteger(input.cutoverGeneration) || input.cutoverGeneration < 1) {
1051
+ throw new Error('Session Thread cutover generation must be a positive safe integer.');
1052
+ }
1053
+ if (input.lastRecordHash !== null && !/^[a-f0-9]{64}$/i.test(input.lastRecordHash)) {
1054
+ throw new Error('Session Thread last record hash must be a SHA-256 digest or null.');
1055
+ }
1056
+ for (const [name, value] of [
1057
+ ['logDevice', input.logDevice],
1058
+ ['logInode', input.logInode],
1059
+ ['logMtimeNs', input.logMtimeNs],
1060
+ ['logCtimeNs', input.logCtimeNs],
1061
+ ]) {
1062
+ if (!/^\d+$/.test(value))
1063
+ throw new Error(`Session Thread ${name} must be an integer string.`);
1064
+ }
1065
+ if (!Number.isSafeInteger(input.messageCount) || input.messageCount < 0) {
1066
+ throw new Error('Session Thread messageCount must be a non-negative safe integer.');
1067
+ }
1068
+ if (!Number.isSafeInteger(input.historySizeBytes) || input.historySizeBytes < 0) {
1069
+ throw new Error('Session Thread historySizeBytes must be a non-negative safe integer.');
1070
+ }
1071
+ if (!Number.isFinite(input.updatedAt) || input.updatedAt < 0) {
1072
+ throw new Error('Session Thread updatedAt must be a non-negative finite timestamp.');
1073
+ }
1074
+ return withLockedSession(sessionId, session => {
1075
+ const nextUpdatedAt = Math.max(session.updatedAt ?? session.startTime, input.updatedAt);
1076
+ const current = session.threadReadModel;
1077
+ const unchanged = session.messageCount === input.messageCount &&
1078
+ session.historySizeBytes === input.historySizeBytes &&
1079
+ session.updatedAt === nextUpdatedAt &&
1080
+ current?.version === 1 &&
1081
+ current.threadId === input.threadId &&
1082
+ current.cursor === input.cursor &&
1083
+ current.projectionDigest === input.projectionDigest &&
1084
+ current.cutoverGeneration === input.cutoverGeneration &&
1085
+ current.lastRecordHash === input.lastRecordHash &&
1086
+ current.logDevice === input.logDevice &&
1087
+ current.logInode === input.logInode &&
1088
+ current.logMtimeNs === input.logMtimeNs &&
1089
+ current.logCtimeNs === input.logCtimeNs;
1090
+ if (unchanged)
1091
+ return session;
1092
+ session.messageCount = input.messageCount;
1093
+ session.historySizeBytes = input.historySizeBytes;
1094
+ session.threadReadModel = {
1095
+ version: 1,
1096
+ threadId: input.threadId,
1097
+ cursor: input.cursor,
1098
+ projectionDigest: input.projectionDigest,
1099
+ cutoverGeneration: input.cutoverGeneration,
1100
+ lastRecordHash: input.lastRecordHash,
1101
+ logDevice: input.logDevice,
1102
+ logInode: input.logInode,
1103
+ logMtimeNs: input.logMtimeNs,
1104
+ logCtimeNs: input.logCtimeNs,
1105
+ };
1106
+ // Selecting a Session is itself recent activity. A background projection
1107
+ // refresh must not reorder it backwards to the timestamp of its last fact.
1108
+ session.updatedAt = nextUpdatedAt;
1109
+ session.updatedAtIso = new Date(session.updatedAt).toISOString();
1110
+ writeSessionMetaUnlocked(session);
1111
+ return session;
1112
+ });
1113
+ }
1039
1114
  /**
1040
1115
  * 加载会话元数据
1041
1116
  */
1042
1117
  function loadSessionMeta(sessionId) {
1043
- const session = loadRawSessionMeta(sessionId);
1044
- return session ? projectSessionMetaWithThread(session) : null;
1118
+ return loadRawSessionMeta(sessionId);
1045
1119
  }
1046
1120
  /**
1047
1121
  * 更新会话统计
@@ -1058,13 +1132,20 @@ function updateSessionStats(sessionId, tokens, cost) {
1058
1132
  * Mark a saved session as active again and refresh project metadata.
1059
1133
  */
1060
1134
  function resumeSession(sessionId) {
1061
- const session = mutateSessionMeta(sessionId, current => {
1135
+ return withLockedSession(sessionId, current => {
1136
+ // Session selection is not new conversation activity. Once a Session is
1137
+ // already open, do not rewrite its metadata/catalog merely because the UI
1138
+ // selected it again. This keeps /resume off the fsync path while preserving
1139
+ // the one durable ended -> active transition.
1140
+ if (current.endTime === undefined)
1141
+ return current;
1062
1142
  current.endTime = undefined;
1063
- current.gitBranch = getGitBranch(current.projectPath);
1143
+ current.gitBranch ?? (current.gitBranch = getGitBranch(current.projectPath));
1064
1144
  current.updatedAt = Date.now();
1065
1145
  current.updatedAtIso = new Date(current.updatedAt).toISOString();
1146
+ writeSessionMetaUnlocked(current);
1147
+ return current;
1066
1148
  });
1067
- return session ? projectSessionMetaWithThread(session) : null;
1068
1149
  }
1069
1150
  /** Keep the session metadata's additive Goal binding in sync with its sidecar. */
1070
1151
  function updateSessionGoalBinding(sessionId, goal) {
@@ -1139,30 +1220,7 @@ function loadSessionHarnessState(sessionId) {
1139
1220
  const session = loadSessionMeta(sessionId);
1140
1221
  if (!session)
1141
1222
  return null;
1142
- const sidecarPath = (0, config_dir_1.getProjectSessionHarnessPath)(session.projectPath, sessionId);
1143
- if ((0, fs_1.existsSync)(sidecarPath)) {
1144
- const sidecar = parseHarnessSidecarFile(sidecarPath);
1145
- if (sidecar?.state) {
1146
- return (0, harness_1.upgradeHarnessState)(sidecar.state, {
1147
- cwd: session.cwd ?? session.projectPath,
1148
- messages: readSessionMessages(sessionId),
1149
- });
1150
- }
1151
- }
1152
- if (session.harnessState) {
1153
- return (0, harness_1.upgradeHarnessState)(session.harnessState, {
1154
- cwd: session.cwd ?? session.projectPath,
1155
- messages: readSessionMessages(sessionId),
1156
- });
1157
- }
1158
- const messages = readSessionMessages(sessionId);
1159
- if (messages.length > 0) {
1160
- return (0, harness_1.upgradeHarnessState)(null, {
1161
- cwd: session.cwd ?? session.projectPath,
1162
- messages,
1163
- });
1164
- }
1165
- return null;
1223
+ return restoreLegacyHarnessState(session, readSessionMessages(sessionId));
1166
1224
  }
1167
1225
  function updateSessionSkills(sessionId, skills) {
1168
1226
  if (skills.length === 0)
@@ -1486,17 +1544,7 @@ function loadSessionTranscriptMessages(sessionId) {
1486
1544
  if (rawSession) {
1487
1545
  const threadSummary = (0, thread_session_view_1.loadThreadSessionSummaryV1)(rawSession.projectPath, rawSession.id);
1488
1546
  if (threadSummary) {
1489
- return threadSummary.transcriptMessages.map(message => ({
1490
- role: message.role,
1491
- content: message.content,
1492
- timestamp: message.timestamp,
1493
- ...(message.modelVisibleContent
1494
- ? { modelVisibleContent: message.modelVisibleContent }
1495
- : {}),
1496
- ...(message.toolCallId ? { toolCallId: message.toolCallId } : {}),
1497
- ...(message.tool_calls ? { tool_calls: structuredClone(message.tool_calls) } : {}),
1498
- ...(message.appliedSkills ? { appliedSkills: [...message.appliedSkills] } : {}),
1499
- }));
1547
+ return threadSummary.transcriptMessages.map(threadTranscriptMessageToSessionMessage);
1500
1548
  }
1501
1549
  }
1502
1550
  return (withLockedSession(sessionId, session => {
@@ -1510,6 +1558,17 @@ function loadSessionTranscriptMessages(sessionId) {
1510
1558
  : messages;
1511
1559
  }) ?? []);
1512
1560
  }
1561
+ function threadTranscriptMessageToSessionMessage(message) {
1562
+ return {
1563
+ role: message.role,
1564
+ content: message.content,
1565
+ timestamp: message.timestamp,
1566
+ ...(message.modelVisibleContent ? { modelVisibleContent: message.modelVisibleContent } : {}),
1567
+ ...(message.toolCallId ? { toolCallId: message.toolCallId } : {}),
1568
+ ...(message.tool_calls ? { tool_calls: structuredClone(message.tool_calls) } : {}),
1569
+ ...(message.appliedSkills ? { appliedSkills: [...message.appliedSkills] } : {}),
1570
+ };
1571
+ }
1513
1572
  function hasPersistedCompactContext(messages) {
1514
1573
  return messages.some(message => message.content.includes('[Orion Code Context State v2]') ||
1515
1574
  message.content.includes('[Context Summary]') ||
@@ -1598,6 +1657,7 @@ function updateSessionSummary(sessionId, _messages) {
1598
1657
  session.filesModified = summary.filesModified;
1599
1658
  session.taskSummary = summary.taskSummary;
1600
1659
  session.messageCount = summary.messageCount;
1660
+ session.historySizeBytes = computeSessionHistorySizeBytes(session);
1601
1661
  session.updatedAt = Date.now();
1602
1662
  session.updatedAtIso = new Date(session.updatedAt).toISOString();
1603
1663
  writeSessionMetaUnlocked(session);
@@ -1607,9 +1667,55 @@ function updateSessionSummary(sessionId, _messages) {
1607
1667
  * 获取项目最近的会话
1608
1668
  */
1609
1669
  function getLastSession(projectPath) {
1610
- const sessions = listProjectSessions(projectPath).filter(session => (session.messageCount ?? 0) > 0);
1670
+ const sessions = filterSessionsWithRestorableHistory(listProjectSessions(projectPath));
1611
1671
  return sessions[0] ?? null;
1612
1672
  }
1673
+ /**
1674
+ * Keep pre-read-model v2 Sessions discoverable without replaying their logs.
1675
+ * An indexed Thread with no bound read model is "unknown", not "empty"; its
1676
+ * metadata is repaired lazily when that target is actually opened.
1677
+ */
1678
+ function filterSessionsWithRestorableHistory(sessions) {
1679
+ const threadSessionsByProject = new Map();
1680
+ const threadSessions = (projectPath) => {
1681
+ const cached = threadSessionsByProject.get(projectPath);
1682
+ if (cached)
1683
+ return cached;
1684
+ let entries = {};
1685
+ try {
1686
+ entries = (0, legacy_thread_materializer_1.loadThreadCutoverIndexV1)(projectPath).sessions;
1687
+ }
1688
+ catch (error) {
1689
+ (0, debug_log_1.debugError)('session-storage.restorableThreadIndex', error, projectPath);
1690
+ }
1691
+ threadSessionsByProject.set(projectPath, entries);
1692
+ return entries;
1693
+ };
1694
+ return sessions.filter(session => {
1695
+ if ((session.messageCount ?? 0) > 0)
1696
+ return true;
1697
+ const entry = threadSessions(session.projectPath)[session.id];
1698
+ if (!entry)
1699
+ return false;
1700
+ const readModel = session.threadReadModel;
1701
+ if (!readModel || readModel.threadId !== entry.threadId)
1702
+ return true;
1703
+ try {
1704
+ const stats = (0, fs_1.statSync)((0, path_1.join)((0, paths_1.getProjectThreadsV2Dir)(session.projectPath), `${readModel.threadId}.events.v1.jsonl`), { bigint: true });
1705
+ const headStillCurrent = stats.size === BigInt(session.historySizeBytes ?? -1) &&
1706
+ stats.dev.toString() === readModel.logDevice &&
1707
+ stats.ino.toString() === readModel.logInode &&
1708
+ stats.mtimeNs.toString() === readModel.logMtimeNs &&
1709
+ stats.ctimeNs.toString() === readModel.logCtimeNs;
1710
+ return !headStillCurrent;
1711
+ }
1712
+ catch {
1713
+ // Missing or unreadable authoritative facts must remain discoverable so
1714
+ // an explicit restore can surface the isolated corruption diagnostic.
1715
+ return true;
1716
+ }
1717
+ });
1718
+ }
1613
1719
  // ============================================================================
1614
1720
  // 历史记录 (JSONL)
1615
1721
  // ============================================================================
@@ -1685,6 +1791,7 @@ function appendSessionMessage(sessionId, message) {
1685
1791
  session.updatedAt = Date.now();
1686
1792
  session.updatedAtIso = new Date(session.updatedAt).toISOString();
1687
1793
  session.messageCount = (session.messageCount ?? 0) + 1;
1794
+ session.historySizeBytes = computeSessionHistorySizeBytes(session);
1688
1795
  writeSessionMetaUnlocked(session);
1689
1796
  });
1690
1797
  }
@@ -1701,13 +1808,13 @@ function appendSessionMessages(sessionId, messages) {
1701
1808
  (0, fs_1.appendFileSync)((0, config_dir_1.getProjectSessionMessagesPath)(session.projectPath, sessionId), lines, {
1702
1809
  mode: 0o600,
1703
1810
  });
1704
- for (const message of messages) {
1705
- (0, session_index_1.updateSessionIndex)(sessionId, session.projectPath, message);
1811
+ (0, session_index_1.updateSessionIndexBatch)(sessionId, session.projectPath, messages);
1812
+ for (const message of messages)
1706
1813
  mergeMessageSummary(session, message);
1707
- }
1708
1814
  session.updatedAt = Date.now();
1709
1815
  session.updatedAtIso = new Date(session.updatedAt).toISOString();
1710
1816
  session.messageCount = (session.messageCount ?? 0) + messages.length;
1817
+ session.historySizeBytes = computeSessionHistorySizeBytes(session);
1711
1818
  writeSessionMetaUnlocked(session);
1712
1819
  });
1713
1820
  }
@@ -1737,10 +1844,9 @@ function overwriteSessionMessagesUnlocked(session, messages) {
1737
1844
  mode: 0o600,
1738
1845
  });
1739
1846
  (0, session_index_1.deleteSessionIndex)(session.id, session.projectPath);
1740
- for (const message of messages) {
1741
- (0, session_index_1.updateSessionIndex)(session.id, session.projectPath, message);
1742
- }
1847
+ (0, session_index_1.updateSessionIndexBatch)(session.id, session.projectPath, messages);
1743
1848
  session.messageCount = messages.length;
1849
+ session.historySizeBytes = computeSessionHistorySizeBytes(session);
1744
1850
  const summary = summarizeSessionMessages(messages);
1745
1851
  session.toolsUsed = summary.toolsUsed;
1746
1852
  session.filesModified = summary.filesModified;
@@ -1896,49 +2002,140 @@ function loadSessionHistory(sessionId) {
1896
2002
  }
1897
2003
  /** Load provider-safe history together with any explicit recovery provenance. */
1898
2004
  function loadSessionHistoryWithDiagnostics(sessionId) {
2005
+ return loadSessionRestoreBundleInternal(sessionId, false).history;
2006
+ }
2007
+ /**
2008
+ * Capture every projection needed by /resume from one stable storage view.
2009
+ * A v2 target opens and verifies one ThreadEventStore; a legacy target reads
2010
+ * messages and its compact checkpoint under one Session lock.
2011
+ */
2012
+ function loadSessionRestoreBundle(sessionId) {
2013
+ return loadSessionRestoreBundleInternal(sessionId, true);
2014
+ }
2015
+ function loadSessionRestoreBundleInternal(sessionId, includeRuntimeActivation) {
1899
2016
  const rawSession = loadRawSessionMeta(sessionId);
1900
2017
  if (rawSession) {
1901
- const threadView = (0, thread_session_view_1.loadThreadSessionViewV1)(rawSession.projectPath, rawSession.id);
2018
+ const openedThread = includeRuntimeActivation
2019
+ ? (0, thread_session_view_1.openThreadSessionViewV1)(rawSession.projectPath, rawSession.id)
2020
+ : undefined;
2021
+ const threadView = openedThread?.view ?? (0, thread_session_view_1.loadThreadSessionViewV1)(rawSession.projectPath, rawSession.id);
1902
2022
  if (threadView) {
2023
+ const harnessState = restoreThreadHarnessState(rawSession, threadView);
2024
+ try {
2025
+ updateSessionThreadReadModel(sessionId, {
2026
+ threadId: threadView.threadId,
2027
+ cursor: threadView.cursor,
2028
+ projectionDigest: threadView.projectionDigest,
2029
+ cutoverGeneration: threadView.readModel.cutoverGeneration,
2030
+ lastRecordHash: threadView.readModel.lastRecordHash,
2031
+ logDevice: threadView.readModel.log.device,
2032
+ logInode: threadView.readModel.log.inode,
2033
+ logMtimeNs: threadView.readModel.log.mtimeNs,
2034
+ logCtimeNs: threadView.readModel.log.ctimeNs,
2035
+ messageCount: threadView.messageCount,
2036
+ historySizeBytes: threadView.historySizeBytes,
2037
+ updatedAt: threadView.updatedAt,
2038
+ });
2039
+ }
2040
+ catch (error) {
2041
+ (0, debug_log_1.debugError)('session-storage.repairThreadReadModel', error, sessionId);
2042
+ }
1903
2043
  return {
1904
- messages: threadView.modelHistory.map(message => structuredClone(message)),
1905
- source: threadView.modelHistorySource,
1906
- diagnostics: threadView.diagnostics,
2044
+ history: {
2045
+ messages: threadView.modelHistory.map(message => structuredClone(message)),
2046
+ source: threadView.modelHistorySource,
2047
+ diagnostics: threadView.diagnostics,
2048
+ },
2049
+ transcriptMessages: threadView.transcriptMessages.map(threadTranscriptMessageToSessionMessage),
2050
+ sourceMessageCount: threadView.transcriptMessages.length,
2051
+ checkpoint: null,
2052
+ ...(harnessState ? { harnessState } : {}),
2053
+ ...(openedThread ? { runtimeActivation: openedThread.runtimeActivation } : {}),
1907
2054
  };
1908
2055
  }
1909
2056
  }
1910
- const messages = withLockedSession(sessionId, session => {
2057
+ return (withLockedSession(sessionId, session => {
1911
2058
  const messages = readSessionMessagesForSession(session);
2059
+ const harnessState = restoreLegacyHarnessState(session, messages);
1912
2060
  const checkpoint = loadCompactCheckpointForSession(session, messages);
1913
- if (checkpoint) {
1914
- const restored = [
1915
- ...checkpoint.modelHistory.map(message => ({ ...message })),
1916
- ...messages.slice(checkpoint.sourceMessageCount).map(sessionMessageToModelMessage),
1917
- ];
1918
- if (checkpoint.version === 2) {
1919
- try {
1920
- (0, tool_call_groups_1.assertToolCallGroups)(restored);
1921
- return restored;
1922
- }
1923
- catch (error) {
1924
- // Never mutate or synthesize data inside a validated V2
1925
- // replacement. Fall back to the append-only transcript instead.
1926
- (0, debug_log_1.debugError)('session-storage.restoreCompactCheckpoint.toolGroups', error, sessionId);
1927
- return (0, tool_call_groups_1.sealToolCallGroups)(messages.map(sessionMessageToModelMessage));
1928
- }
1929
- }
1930
- return (0, tool_call_groups_1.sealToolCallGroups)(restored);
1931
- }
1932
- let modelVisibleMessages = messages;
1933
- if (typeof session.transcriptDisplayStartTime === 'number') {
1934
- const afterDisplayStart = messages.filter(message => (message.timestamp ?? 0) >= (session.transcriptDisplayStartTime ?? 0));
1935
- modelVisibleMessages = hasPersistedCompactContext(afterDisplayStart)
1936
- ? afterDisplayStart
2061
+ const transcriptMessages = checkpoint
2062
+ ? messages.slice(checkpoint.transcriptStartMessageIndex)
2063
+ : typeof session.transcriptDisplayStartTime === 'number'
2064
+ ? messages.filter(message => (message.timestamp ?? 0) >= (session.transcriptDisplayStartTime ?? 0))
1937
2065
  : messages;
2066
+ return {
2067
+ history: {
2068
+ messages: restoreLegacyModelHistory(sessionId, session, messages, checkpoint),
2069
+ source: 'legacy',
2070
+ diagnostics: [],
2071
+ },
2072
+ transcriptMessages,
2073
+ sourceMessageCount: messages.length,
2074
+ checkpoint,
2075
+ ...(harnessState ? { harnessState } : {}),
2076
+ };
2077
+ }) ?? {
2078
+ history: { messages: [], source: 'legacy', diagnostics: [] },
2079
+ transcriptMessages: [],
2080
+ sourceMessageCount: 0,
2081
+ checkpoint: null,
2082
+ });
2083
+ }
2084
+ function restoreThreadHarnessState(session, view) {
2085
+ if (!view.latestTurnCommit) {
2086
+ return restoreLegacyHarnessState(session, view.transcriptMessages.map(threadTranscriptMessageToSessionMessage));
2087
+ }
2088
+ const commit = (0, turn_commit_1.parseTurnCommitV1)(view.latestTurnCommit.receipt);
2089
+ let state;
2090
+ try {
2091
+ state = JSON.parse(commit.taskContext);
2092
+ }
2093
+ catch {
2094
+ throw new Error(`Session ${session.id} TurnCommit TaskContext is not valid JSON.`);
2095
+ }
2096
+ return (0, harness_1.upgradeHarnessState)(state, {
2097
+ cwd: session.cwd ?? session.projectPath,
2098
+ messages: view.modelHistory.map(message => structuredClone(message)),
2099
+ });
2100
+ }
2101
+ function restoreLegacyHarnessState(session, messages) {
2102
+ const sidecarPath = (0, config_dir_1.getProjectSessionHarnessPath)(session.projectPath, session.id);
2103
+ const sidecar = (0, fs_1.existsSync)(sidecarPath) ? parseHarnessSidecarFile(sidecarPath) : null;
2104
+ const state = sidecar?.state ?? session.harnessState ?? null;
2105
+ if (!state && messages.length === 0)
2106
+ return null;
2107
+ return (0, harness_1.upgradeHarnessState)(state, {
2108
+ cwd: session.cwd ?? session.projectPath,
2109
+ messages: messages.map(message => structuredClone(message)),
2110
+ });
2111
+ }
2112
+ function restoreLegacyModelHistory(sessionId, session, messages, checkpoint) {
2113
+ if (checkpoint) {
2114
+ const restored = [
2115
+ ...checkpoint.modelHistory.map(message => ({ ...message })),
2116
+ ...messages.slice(checkpoint.sourceMessageCount).map(sessionMessageToModelMessage),
2117
+ ];
2118
+ if (checkpoint.version === 2) {
2119
+ try {
2120
+ (0, tool_call_groups_1.assertToolCallGroups)(restored);
2121
+ return restored;
2122
+ }
2123
+ catch (error) {
2124
+ // Never mutate or synthesize data inside a validated V2 replacement.
2125
+ (0, debug_log_1.debugError)('session-storage.restoreCompactCheckpoint.toolGroups', error, sessionId);
2126
+ return (0, tool_call_groups_1.sealToolCallGroups)(messages.map(sessionMessageToModelMessage));
2127
+ }
1938
2128
  }
1939
- return (0, tool_call_groups_1.sealToolCallGroups)(modelVisibleMessages.map(sessionMessageToModelMessage));
1940
- }) ?? [];
1941
- return { messages, source: 'legacy', diagnostics: [] };
2129
+ return (0, tool_call_groups_1.sealToolCallGroups)(restored);
2130
+ }
2131
+ let modelVisibleMessages = messages;
2132
+ if (typeof session.transcriptDisplayStartTime === 'number') {
2133
+ const afterDisplayStart = messages.filter(message => (message.timestamp ?? 0) >= (session.transcriptDisplayStartTime ?? 0));
2134
+ modelVisibleMessages = hasPersistedCompactContext(afterDisplayStart)
2135
+ ? afterDisplayStart
2136
+ : messages;
2137
+ }
2138
+ return (0, tool_call_groups_1.sealToolCallGroups)(modelVisibleMessages.map(sessionMessageToModelMessage));
1942
2139
  }
1943
2140
  function sessionMessageToModelMessage(message) {
1944
2141
  const result = {
@@ -1957,7 +2154,7 @@ function sessionMessageToModelMessage(message) {
1957
2154
  * 列出所有会话
1958
2155
  */
1959
2156
  function listSessions(limit) {
1960
- const sessions = sortSessionsNewestFirst(Object.values(loadOrRebuildSessionCatalog().sessions).map(projectSessionMetaWithThread));
2157
+ const sessions = sortSessionsNewestFirst(Object.values(loadOrRebuildSessionCatalog().sessions));
1961
2158
  return limit ? sessions.slice(0, limit) : sessions;
1962
2159
  }
1963
2160
  /**
@@ -1965,9 +2162,7 @@ function listSessions(limit) {
1965
2162
  */
1966
2163
  function listProjectSessions(projectPath, limit) {
1967
2164
  const canonicalProjectPath = resolveProjectPath(projectPath);
1968
- const sessions = sortSessionsNewestFirst(Object.values(loadOrRebuildSessionCatalog().sessions)
1969
- .filter(session => session.projectPath === canonicalProjectPath)
1970
- .map(projectSessionMetaWithThread));
2165
+ const sessions = sortSessionsNewestFirst(Object.values(loadOrRebuildSessionCatalog().sessions).filter(session => session.projectPath === canonicalProjectPath));
1971
2166
  return limit ? sessions.slice(0, limit) : sessions;
1972
2167
  }
1973
2168
  /**
@@ -1983,10 +2178,14 @@ function findSession(ref, projectPath, options = {}) {
1983
2178
  * conflict prompts.
1984
2179
  */
1985
2180
  function lookupSessionRef(ref, projectPath, options = {}) {
2181
+ const candidates = options.allProjects || !projectPath ? listSessions() : listProjectSessions(projectPath);
2182
+ return lookupSessionRefInSessions(ref, candidates);
2183
+ }
2184
+ /** Resolve a reference against an already-loaded picker snapshot. */
2185
+ function lookupSessionRefInSessions(ref, candidates) {
1986
2186
  const query = ref.trim();
1987
2187
  if (!query)
1988
2188
  return { status: 'not_found' };
1989
- const candidates = options.allProjects || !projectPath ? listSessions() : listProjectSessions(projectPath);
1990
2189
  const exactId = candidates.find(session => session.id === query);
1991
2190
  if (exactId)
1992
2191
  return { status: 'found', session: exactId };