@orion-agents/orion-code 0.2.0 → 0.3.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 (170) hide show
  1. package/CHANGELOG.md +75 -0
  2. package/README.md +73 -14
  3. package/README.zh-CN.md +63 -13
  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 +1 -1
  12. package/dist/commands/session-command-handlers.d.ts.map +1 -1
  13. package/dist/commands/session-command-handlers.js +121 -16
  14. package/dist/commands/session-command-handlers.js.map +1 -1
  15. package/dist/commands/types.d.ts +14 -2
  16. package/dist/commands/types.d.ts.map +1 -1
  17. package/dist/commands/types.js +1 -1
  18. package/dist/commands/types.js.map +1 -1
  19. package/dist/index.d.ts +1 -1
  20. package/dist/index.js +1 -1
  21. package/dist/runtime/agent-loop.d.ts +2 -0
  22. package/dist/runtime/agent-loop.d.ts.map +1 -1
  23. package/dist/runtime/agent-loop.js +4 -3
  24. package/dist/runtime/agent-loop.js.map +1 -1
  25. package/dist/runtime/agent-runtime-controller.d.ts +32 -1
  26. package/dist/runtime/agent-runtime-controller.d.ts.map +1 -1
  27. package/dist/runtime/agent-runtime-controller.js +250 -31
  28. package/dist/runtime/agent-runtime-controller.js.map +1 -1
  29. package/dist/runtime/agent-runtime-runner.d.ts +3 -0
  30. package/dist/runtime/agent-runtime-runner.d.ts.map +1 -1
  31. package/dist/runtime/goal-runtime-coordinator.d.ts +6 -0
  32. package/dist/runtime/goal-runtime-coordinator.d.ts.map +1 -1
  33. package/dist/runtime/goal-runtime-coordinator.js +5 -0
  34. package/dist/runtime/goal-runtime-coordinator.js.map +1 -1
  35. package/dist/runtime/legacy-thread-materializer.d.ts +18 -0
  36. package/dist/runtime/legacy-thread-materializer.d.ts.map +1 -1
  37. package/dist/runtime/legacy-thread-materializer.js +27 -12
  38. package/dist/runtime/legacy-thread-materializer.js.map +1 -1
  39. package/dist/runtime/orion-runtime-v1.d.ts +3 -0
  40. package/dist/runtime/orion-runtime-v1.d.ts.map +1 -1
  41. package/dist/runtime/orion-runtime-v1.js +8 -1
  42. package/dist/runtime/orion-runtime-v1.js.map +1 -1
  43. package/dist/runtime/orion-session-runner.d.ts +7 -1
  44. package/dist/runtime/orion-session-runner.d.ts.map +1 -1
  45. package/dist/runtime/orion-session-runner.js +35 -3
  46. package/dist/runtime/orion-session-runner.js.map +1 -1
  47. package/dist/runtime/product-bootstrap.d.ts +22 -0
  48. package/dist/runtime/product-bootstrap.d.ts.map +1 -0
  49. package/dist/runtime/product-bootstrap.js +374 -0
  50. package/dist/runtime/product-bootstrap.js.map +1 -0
  51. package/dist/runtime/product-orion-runtime.d.ts +2 -1
  52. package/dist/runtime/product-orion-runtime.d.ts.map +1 -1
  53. package/dist/runtime/product-orion-runtime.js +114 -16
  54. package/dist/runtime/product-orion-runtime.js.map +1 -1
  55. package/dist/runtime/protocol/runtime-protocol-v1.d.ts +1 -1
  56. package/dist/runtime/protocol/runtime-protocol-v1.js +1 -1
  57. package/dist/runtime/protocol/runtime-protocol-v1.js.map +1 -1
  58. package/dist/runtime/release-receipts.d.ts +63 -2
  59. package/dist/runtime/release-receipts.d.ts.map +1 -1
  60. package/dist/runtime/release-receipts.js +298 -7
  61. package/dist/runtime/release-receipts.js.map +1 -1
  62. package/dist/runtime/session-history-recovery.d.ts +19 -0
  63. package/dist/runtime/session-history-recovery.d.ts.map +1 -0
  64. package/dist/runtime/session-history-recovery.js +45 -0
  65. package/dist/runtime/session-history-recovery.js.map +1 -0
  66. package/dist/runtime/step-snapshot.d.ts.map +1 -1
  67. package/dist/runtime/step-snapshot.js +22 -2
  68. package/dist/runtime/step-snapshot.js.map +1 -1
  69. package/dist/runtime/subagents/runtime-integration.d.ts +1 -1
  70. package/dist/runtime/subagents/runtime-integration.d.ts.map +1 -1
  71. package/dist/runtime/subagents/runtime-integration.js +5 -2
  72. package/dist/runtime/subagents/runtime-integration.js.map +1 -1
  73. package/dist/runtime/thread-event-store.d.ts +27 -0
  74. package/dist/runtime/thread-event-store.d.ts.map +1 -1
  75. package/dist/runtime/thread-event-store.js +119 -17
  76. package/dist/runtime/thread-event-store.js.map +1 -1
  77. package/dist/runtime/thread-projection.d.ts +10 -0
  78. package/dist/runtime/thread-projection.d.ts.map +1 -1
  79. package/dist/runtime/thread-projection.js +40 -0
  80. package/dist/runtime/thread-projection.js.map +1 -1
  81. package/dist/runtime/thread-runtime.d.ts +3 -1
  82. package/dist/runtime/thread-runtime.d.ts.map +1 -1
  83. package/dist/runtime/thread-runtime.js +6 -0
  84. package/dist/runtime/thread-runtime.js.map +1 -1
  85. package/dist/runtime/thread-session-view.d.ts +88 -0
  86. package/dist/runtime/thread-session-view.d.ts.map +1 -0
  87. package/dist/runtime/thread-session-view.js +327 -0
  88. package/dist/runtime/thread-session-view.js.map +1 -0
  89. package/dist/runtime/thread-ui-adapter.d.ts +4 -0
  90. package/dist/runtime/thread-ui-adapter.d.ts.map +1 -1
  91. package/dist/runtime/thread-ui-adapter.js +193 -5
  92. package/dist/runtime/thread-ui-adapter.js.map +1 -1
  93. package/dist/runtime/ui-events.d.ts +37 -0
  94. package/dist/runtime/ui-events.d.ts.map +1 -1
  95. package/dist/runtime/ui-events.js +1 -0
  96. package/dist/runtime/ui-events.js.map +1 -1
  97. package/dist/runtime/ui-view-model.d.ts +3 -0
  98. package/dist/runtime/ui-view-model.d.ts.map +1 -1
  99. package/dist/runtime/ui-view-model.js +10 -3
  100. package/dist/runtime/ui-view-model.js.map +1 -1
  101. package/dist/services/file-lock.d.ts.map +1 -1
  102. package/dist/services/file-lock.js +42 -17
  103. package/dist/services/file-lock.js.map +1 -1
  104. package/dist/services/global-config.d.ts +25 -0
  105. package/dist/services/global-config.d.ts.map +1 -1
  106. package/dist/services/global-config.js +373 -5
  107. package/dist/services/global-config.js.map +1 -1
  108. package/dist/services/session-storage.d.ts +67 -0
  109. package/dist/services/session-storage.d.ts.map +1 -1
  110. package/dist/services/session-storage.js +327 -61
  111. package/dist/services/session-storage.js.map +1 -1
  112. package/dist/services/settings-coordinator.d.ts +174 -0
  113. package/dist/services/settings-coordinator.d.ts.map +1 -0
  114. package/dist/services/settings-coordinator.js +605 -0
  115. package/dist/services/settings-coordinator.js.map +1 -0
  116. package/dist/services/settings-document-repository.d.ts +138 -0
  117. package/dist/services/settings-document-repository.d.ts.map +1 -0
  118. package/dist/services/settings-document-repository.js +551 -0
  119. package/dist/services/settings-document-repository.js.map +1 -0
  120. package/dist/terminal-ui/launch.d.ts.map +1 -1
  121. package/dist/terminal-ui/launch.js +7 -0
  122. package/dist/terminal-ui/launch.js.map +1 -1
  123. package/dist/tools/web.js +1 -1
  124. package/dist/tools/web.js.map +1 -1
  125. package/dist/tui-ui/chrome-view-model.d.ts.map +1 -1
  126. package/dist/tui-ui/chrome-view-model.js +21 -7
  127. package/dist/tui-ui/chrome-view-model.js.map +1 -1
  128. package/dist/tui-ui/launch.d.ts.map +1 -1
  129. package/dist/tui-ui/launch.js +13 -0
  130. package/dist/tui-ui/launch.js.map +1 -1
  131. package/dist/tui-ui/state.d.ts.map +1 -1
  132. package/dist/tui-ui/state.js +7 -0
  133. package/dist/tui-ui/state.js.map +1 -1
  134. package/dist/web/event-hub.d.ts +39 -0
  135. package/dist/web/event-hub.d.ts.map +1 -0
  136. package/dist/web/event-hub.js +263 -0
  137. package/dist/web/event-hub.js.map +1 -0
  138. package/dist/web/index.d.ts +6 -0
  139. package/dist/web/index.d.ts.map +1 -0
  140. package/dist/web/index.js +28 -0
  141. package/dist/web/index.js.map +1 -0
  142. package/dist/web/launch.d.ts +9 -0
  143. package/dist/web/launch.d.ts.map +1 -0
  144. package/dist/web/launch.js +64 -0
  145. package/dist/web/launch.js.map +1 -0
  146. package/dist/web/protocol.d.ts +346 -0
  147. package/dist/web/protocol.d.ts.map +1 -0
  148. package/dist/web/protocol.js +348 -0
  149. package/dist/web/protocol.js.map +1 -0
  150. package/dist/web/server.d.ts +20 -0
  151. package/dist/web/server.d.ts.map +1 -0
  152. package/dist/web/server.js +498 -0
  153. package/dist/web/server.js.map +1 -0
  154. package/dist/web/workbench-controller.d.ts +63 -0
  155. package/dist/web/workbench-controller.d.ts.map +1 -0
  156. package/dist/web/workbench-controller.js +774 -0
  157. package/dist/web/workbench-controller.js.map +1 -0
  158. package/dist/web-client/assets/index-BAEO71Gp.css +1 -0
  159. package/dist/web-client/assets/index-DSkFy7gM.js +13 -0
  160. package/dist/web-client/index.html +16 -0
  161. package/docs/architecture/v0.3.0-web-api.yaml +1309 -0
  162. package/docs/migration/v0.2.2-to-v0.3.0-settings.md +114 -0
  163. package/docs/migration/v0.2.2-to-v0.3.0.md +55 -0
  164. package/docs/orion.example.json +9 -2
  165. package/docs/plan/v0.3.0-node-runtime-compatibility-plan.md +57 -0
  166. package/docs/plan/v0.3.0-settings-integration-plan.md +740 -0
  167. package/docs/plan/v0.3.0-web-workbench-plan.md +325 -0
  168. package/docs/readme.md +12 -1
  169. package/npm-shrinkwrap.json +1777 -424
  170. package/package.json +33 -9
@@ -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;
@@ -44,10 +47,13 @@ exports.readSessionMessages = readSessionMessages;
44
47
  exports.appendSessionTraceEvent = appendSessionTraceEvent;
45
48
  exports.readSessionTraceEvents = readSessionTraceEvents;
46
49
  exports.loadSessionHistory = loadSessionHistory;
50
+ exports.loadSessionHistoryWithDiagnostics = loadSessionHistoryWithDiagnostics;
51
+ exports.loadSessionRestoreBundle = loadSessionRestoreBundle;
47
52
  exports.listSessions = listSessions;
48
53
  exports.listProjectSessions = listProjectSessions;
49
54
  exports.findSession = findSession;
50
55
  exports.lookupSessionRef = lookupSessionRef;
56
+ exports.lookupSessionRefInSessions = lookupSessionRefInSessions;
51
57
  exports.renameSession = renameSession;
52
58
  exports.deleteSession = deleteSession;
53
59
  const fs_1 = require("fs");
@@ -64,6 +70,10 @@ const debug_log_1 = require("../utils/debug-log");
64
70
  const goal_storage_1 = require("./goal-storage");
65
71
  const fingerprint_1 = require("./compact/fingerprint");
66
72
  const token_estimate_1 = require("../utils/token-estimate");
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");
67
77
  const harness_1 = require("../harness");
68
78
  var redaction_2 = require("./redaction");
69
79
  Object.defineProperty(exports, "redactTraceText", { enumerable: true, get: function () { return redaction_2.redactTraceText; } });
@@ -302,7 +312,7 @@ function normalizeSessionMeta(session) {
302
312
  updatedAt,
303
313
  updatedAtIso: session.updatedAtIso ?? new Date(updatedAt).toISOString(),
304
314
  messageCount: session.messageCount ?? 0,
305
- historySizeBytes: computeSessionHistorySizeBytes({ id: session.id, projectPath }),
315
+ historySizeBytes: session.historySizeBytes ?? computeSessionHistorySizeBytes({ id: session.id, projectPath }),
306
316
  tokenCount: session.tokenCount ?? 0,
307
317
  cost: session.cost ?? 0,
308
318
  gitBranch: session.gitBranch ?? getGitBranch(projectPath),
@@ -974,6 +984,10 @@ function readSessionMetaAtPath(path) {
974
984
  const session = parseSessionMetaFile(path);
975
985
  return session ? tryNormalizeSessionMeta(session, path) : null;
976
986
  }
987
+ function loadRawSessionMeta(sessionId) {
988
+ const path = findSessionMetaPath(sessionId);
989
+ return path ? readSessionMetaAtPath(path) : null;
990
+ }
977
991
  /**
978
992
  * Serialize a session read-modify-write transaction across Orion processes.
979
993
  * The metadata path is rediscovered before each transaction and re-read only
@@ -1008,12 +1022,100 @@ function updateSessionEffort(sessionId, effortPreference) {
1008
1022
  }
1009
1023
  });
1010
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
+ }
1011
1114
  /**
1012
1115
  * 加载会话元数据
1013
1116
  */
1014
1117
  function loadSessionMeta(sessionId) {
1015
- const path = findSessionMetaPath(sessionId);
1016
- return path ? readSessionMetaAtPath(path) : null;
1118
+ return loadRawSessionMeta(sessionId);
1017
1119
  }
1018
1120
  /**
1019
1121
  * 更新会话统计
@@ -1030,11 +1132,19 @@ function updateSessionStats(sessionId, tokens, cost) {
1030
1132
  * Mark a saved session as active again and refresh project metadata.
1031
1133
  */
1032
1134
  function resumeSession(sessionId) {
1033
- return mutateSessionMeta(sessionId, session => {
1034
- session.endTime = undefined;
1035
- session.gitBranch = getGitBranch(session.projectPath);
1036
- session.updatedAt = Date.now();
1037
- session.updatedAtIso = new Date(session.updatedAt).toISOString();
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;
1142
+ current.endTime = undefined;
1143
+ current.gitBranch ?? (current.gitBranch = getGitBranch(current.projectPath));
1144
+ current.updatedAt = Date.now();
1145
+ current.updatedAtIso = new Date(current.updatedAt).toISOString();
1146
+ writeSessionMetaUnlocked(current);
1147
+ return current;
1038
1148
  });
1039
1149
  }
1040
1150
  /** Keep the session metadata's additive Goal binding in sync with its sidecar. */
@@ -1110,30 +1220,7 @@ function loadSessionHarnessState(sessionId) {
1110
1220
  const session = loadSessionMeta(sessionId);
1111
1221
  if (!session)
1112
1222
  return null;
1113
- const sidecarPath = (0, config_dir_1.getProjectSessionHarnessPath)(session.projectPath, sessionId);
1114
- if ((0, fs_1.existsSync)(sidecarPath)) {
1115
- const sidecar = parseHarnessSidecarFile(sidecarPath);
1116
- if (sidecar?.state) {
1117
- return (0, harness_1.upgradeHarnessState)(sidecar.state, {
1118
- cwd: session.cwd ?? session.projectPath,
1119
- messages: readSessionMessages(sessionId),
1120
- });
1121
- }
1122
- }
1123
- if (session.harnessState) {
1124
- return (0, harness_1.upgradeHarnessState)(session.harnessState, {
1125
- cwd: session.cwd ?? session.projectPath,
1126
- messages: readSessionMessages(sessionId),
1127
- });
1128
- }
1129
- const messages = readSessionMessages(sessionId);
1130
- if (messages.length > 0) {
1131
- return (0, harness_1.upgradeHarnessState)(null, {
1132
- cwd: session.cwd ?? session.projectPath,
1133
- messages,
1134
- });
1135
- }
1136
- return null;
1223
+ return restoreLegacyHarnessState(session, readSessionMessages(sessionId));
1137
1224
  }
1138
1225
  function updateSessionSkills(sessionId, skills) {
1139
1226
  if (skills.length === 0)
@@ -1453,6 +1540,13 @@ function commitSessionCompactCheckpoint(input) {
1453
1540
  return checkpoint;
1454
1541
  }
1455
1542
  function loadSessionTranscriptMessages(sessionId) {
1543
+ const rawSession = loadRawSessionMeta(sessionId);
1544
+ if (rawSession) {
1545
+ const threadSummary = (0, thread_session_view_1.loadThreadSessionSummaryV1)(rawSession.projectPath, rawSession.id);
1546
+ if (threadSummary) {
1547
+ return threadSummary.transcriptMessages.map(threadTranscriptMessageToSessionMessage);
1548
+ }
1549
+ }
1456
1550
  return (withLockedSession(sessionId, session => {
1457
1551
  const messages = readSessionMessagesForSession(session);
1458
1552
  const checkpoint = loadCompactCheckpointForSession(session, messages);
@@ -1464,6 +1558,17 @@ function loadSessionTranscriptMessages(sessionId) {
1464
1558
  : messages;
1465
1559
  }) ?? []);
1466
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
+ }
1467
1572
  function hasPersistedCompactContext(messages) {
1468
1573
  return messages.some(message => message.content.includes('[Orion Code Context State v2]') ||
1469
1574
  message.content.includes('[Context Summary]') ||
@@ -1552,6 +1657,7 @@ function updateSessionSummary(sessionId, _messages) {
1552
1657
  session.filesModified = summary.filesModified;
1553
1658
  session.taskSummary = summary.taskSummary;
1554
1659
  session.messageCount = summary.messageCount;
1660
+ session.historySizeBytes = computeSessionHistorySizeBytes(session);
1555
1661
  session.updatedAt = Date.now();
1556
1662
  session.updatedAtIso = new Date(session.updatedAt).toISOString();
1557
1663
  writeSessionMetaUnlocked(session);
@@ -1561,9 +1667,55 @@ function updateSessionSummary(sessionId, _messages) {
1561
1667
  * 获取项目最近的会话
1562
1668
  */
1563
1669
  function getLastSession(projectPath) {
1564
- const sessions = listProjectSessions(projectPath).filter(session => (session.messageCount ?? 0) > 0);
1670
+ const sessions = filterSessionsWithRestorableHistory(listProjectSessions(projectPath));
1565
1671
  return sessions[0] ?? null;
1566
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
+ }
1567
1719
  // ============================================================================
1568
1720
  // 历史记录 (JSONL)
1569
1721
  // ============================================================================
@@ -1639,6 +1791,7 @@ function appendSessionMessage(sessionId, message) {
1639
1791
  session.updatedAt = Date.now();
1640
1792
  session.updatedAtIso = new Date(session.updatedAt).toISOString();
1641
1793
  session.messageCount = (session.messageCount ?? 0) + 1;
1794
+ session.historySizeBytes = computeSessionHistorySizeBytes(session);
1642
1795
  writeSessionMetaUnlocked(session);
1643
1796
  });
1644
1797
  }
@@ -1662,6 +1815,7 @@ function appendSessionMessages(sessionId, messages) {
1662
1815
  session.updatedAt = Date.now();
1663
1816
  session.updatedAtIso = new Date(session.updatedAt).toISOString();
1664
1817
  session.messageCount = (session.messageCount ?? 0) + messages.length;
1818
+ session.historySizeBytes = computeSessionHistorySizeBytes(session);
1665
1819
  writeSessionMetaUnlocked(session);
1666
1820
  });
1667
1821
  }
@@ -1695,6 +1849,7 @@ function overwriteSessionMessagesUnlocked(session, messages) {
1695
1849
  (0, session_index_1.updateSessionIndex)(session.id, session.projectPath, message);
1696
1850
  }
1697
1851
  session.messageCount = messages.length;
1852
+ session.historySizeBytes = computeSessionHistorySizeBytes(session);
1698
1853
  const summary = summarizeSessionMessages(messages);
1699
1854
  session.toolsUsed = summary.toolsUsed;
1700
1855
  session.filesModified = summary.filesModified;
@@ -1748,7 +1903,7 @@ function removeTrailingSessionUserMessage(sessionId) {
1748
1903
  * 读取会话消息
1749
1904
  */
1750
1905
  function readSessionMessages(sessionId) {
1751
- const session = loadSessionMeta(sessionId);
1906
+ const session = loadRawSessionMeta(sessionId);
1752
1907
  if (!session)
1753
1908
  return [];
1754
1909
  return readSessionMessagesForSession(session);
@@ -1846,37 +2001,144 @@ function readSessionTraceEvents(sessionId) {
1846
2001
  * 包含完整的 tool_calls 信息,确保 LLM 能理解之前的工具调用
1847
2002
  */
1848
2003
  function loadSessionHistory(sessionId) {
2004
+ return loadSessionHistoryWithDiagnostics(sessionId).messages.map(message => structuredClone(message));
2005
+ }
2006
+ /** Load provider-safe history together with any explicit recovery provenance. */
2007
+ function loadSessionHistoryWithDiagnostics(sessionId) {
2008
+ return loadSessionRestoreBundleInternal(sessionId, false).history;
2009
+ }
2010
+ /**
2011
+ * Capture every projection needed by /resume from one stable storage view.
2012
+ * A v2 target opens and verifies one ThreadEventStore; a legacy target reads
2013
+ * messages and its compact checkpoint under one Session lock.
2014
+ */
2015
+ function loadSessionRestoreBundle(sessionId) {
2016
+ return loadSessionRestoreBundleInternal(sessionId, true);
2017
+ }
2018
+ function loadSessionRestoreBundleInternal(sessionId, includeRuntimeActivation) {
2019
+ const rawSession = loadRawSessionMeta(sessionId);
2020
+ if (rawSession) {
2021
+ const openedThread = includeRuntimeActivation
2022
+ ? (0, thread_session_view_1.openThreadSessionViewV1)(rawSession.projectPath, rawSession.id)
2023
+ : undefined;
2024
+ const threadView = openedThread?.view ?? (0, thread_session_view_1.loadThreadSessionViewV1)(rawSession.projectPath, rawSession.id);
2025
+ if (threadView) {
2026
+ const harnessState = restoreThreadHarnessState(rawSession, threadView);
2027
+ try {
2028
+ updateSessionThreadReadModel(sessionId, {
2029
+ threadId: threadView.threadId,
2030
+ cursor: threadView.cursor,
2031
+ projectionDigest: threadView.projectionDigest,
2032
+ cutoverGeneration: threadView.readModel.cutoverGeneration,
2033
+ lastRecordHash: threadView.readModel.lastRecordHash,
2034
+ logDevice: threadView.readModel.log.device,
2035
+ logInode: threadView.readModel.log.inode,
2036
+ logMtimeNs: threadView.readModel.log.mtimeNs,
2037
+ logCtimeNs: threadView.readModel.log.ctimeNs,
2038
+ messageCount: threadView.messageCount,
2039
+ historySizeBytes: threadView.historySizeBytes,
2040
+ updatedAt: threadView.updatedAt,
2041
+ });
2042
+ }
2043
+ catch (error) {
2044
+ (0, debug_log_1.debugError)('session-storage.repairThreadReadModel', error, sessionId);
2045
+ }
2046
+ return {
2047
+ history: {
2048
+ messages: threadView.modelHistory.map(message => structuredClone(message)),
2049
+ source: threadView.modelHistorySource,
2050
+ diagnostics: threadView.diagnostics,
2051
+ },
2052
+ transcriptMessages: threadView.transcriptMessages.map(threadTranscriptMessageToSessionMessage),
2053
+ sourceMessageCount: threadView.transcriptMessages.length,
2054
+ checkpoint: null,
2055
+ ...(harnessState ? { harnessState } : {}),
2056
+ ...(openedThread ? { runtimeActivation: openedThread.runtimeActivation } : {}),
2057
+ };
2058
+ }
2059
+ }
1849
2060
  return (withLockedSession(sessionId, session => {
1850
2061
  const messages = readSessionMessagesForSession(session);
2062
+ const harnessState = restoreLegacyHarnessState(session, messages);
1851
2063
  const checkpoint = loadCompactCheckpointForSession(session, messages);
1852
- if (checkpoint) {
1853
- const restored = [
1854
- ...checkpoint.modelHistory.map(message => ({ ...message })),
1855
- ...messages.slice(checkpoint.sourceMessageCount).map(sessionMessageToModelMessage),
1856
- ];
1857
- if (checkpoint.version === 2) {
1858
- try {
1859
- (0, tool_call_groups_1.assertToolCallGroups)(restored);
1860
- return restored;
1861
- }
1862
- catch (error) {
1863
- // Never mutate or synthesize data inside a validated V2
1864
- // replacement. Fall back to the append-only transcript instead.
1865
- (0, debug_log_1.debugError)('session-storage.restoreCompactCheckpoint.toolGroups', error, sessionId);
1866
- return (0, tool_call_groups_1.sealToolCallGroups)(messages.map(sessionMessageToModelMessage));
1867
- }
1868
- }
1869
- return (0, tool_call_groups_1.sealToolCallGroups)(restored);
1870
- }
1871
- let modelVisibleMessages = messages;
1872
- if (typeof session.transcriptDisplayStartTime === 'number') {
1873
- const afterDisplayStart = messages.filter(message => (message.timestamp ?? 0) >= (session.transcriptDisplayStartTime ?? 0));
1874
- modelVisibleMessages = hasPersistedCompactContext(afterDisplayStart)
1875
- ? afterDisplayStart
2064
+ const transcriptMessages = checkpoint
2065
+ ? messages.slice(checkpoint.transcriptStartMessageIndex)
2066
+ : typeof session.transcriptDisplayStartTime === 'number'
2067
+ ? messages.filter(message => (message.timestamp ?? 0) >= (session.transcriptDisplayStartTime ?? 0))
1876
2068
  : messages;
2069
+ return {
2070
+ history: {
2071
+ messages: restoreLegacyModelHistory(sessionId, session, messages, checkpoint),
2072
+ source: 'legacy',
2073
+ diagnostics: [],
2074
+ },
2075
+ transcriptMessages,
2076
+ sourceMessageCount: messages.length,
2077
+ checkpoint,
2078
+ ...(harnessState ? { harnessState } : {}),
2079
+ };
2080
+ }) ?? {
2081
+ history: { messages: [], source: 'legacy', diagnostics: [] },
2082
+ transcriptMessages: [],
2083
+ sourceMessageCount: 0,
2084
+ checkpoint: null,
2085
+ });
2086
+ }
2087
+ function restoreThreadHarnessState(session, view) {
2088
+ if (!view.latestTurnCommit) {
2089
+ return restoreLegacyHarnessState(session, view.transcriptMessages.map(threadTranscriptMessageToSessionMessage));
2090
+ }
2091
+ const commit = (0, turn_commit_1.parseTurnCommitV1)(view.latestTurnCommit.receipt);
2092
+ let state;
2093
+ try {
2094
+ state = JSON.parse(commit.taskContext);
2095
+ }
2096
+ catch {
2097
+ throw new Error(`Session ${session.id} TurnCommit TaskContext is not valid JSON.`);
2098
+ }
2099
+ return (0, harness_1.upgradeHarnessState)(state, {
2100
+ cwd: session.cwd ?? session.projectPath,
2101
+ messages: view.modelHistory.map(message => structuredClone(message)),
2102
+ });
2103
+ }
2104
+ function restoreLegacyHarnessState(session, messages) {
2105
+ const sidecarPath = (0, config_dir_1.getProjectSessionHarnessPath)(session.projectPath, session.id);
2106
+ const sidecar = (0, fs_1.existsSync)(sidecarPath) ? parseHarnessSidecarFile(sidecarPath) : null;
2107
+ const state = sidecar?.state ?? session.harnessState ?? null;
2108
+ if (!state && messages.length === 0)
2109
+ return null;
2110
+ return (0, harness_1.upgradeHarnessState)(state, {
2111
+ cwd: session.cwd ?? session.projectPath,
2112
+ messages: messages.map(message => structuredClone(message)),
2113
+ });
2114
+ }
2115
+ function restoreLegacyModelHistory(sessionId, session, messages, checkpoint) {
2116
+ if (checkpoint) {
2117
+ const restored = [
2118
+ ...checkpoint.modelHistory.map(message => ({ ...message })),
2119
+ ...messages.slice(checkpoint.sourceMessageCount).map(sessionMessageToModelMessage),
2120
+ ];
2121
+ if (checkpoint.version === 2) {
2122
+ try {
2123
+ (0, tool_call_groups_1.assertToolCallGroups)(restored);
2124
+ return restored;
2125
+ }
2126
+ catch (error) {
2127
+ // Never mutate or synthesize data inside a validated V2 replacement.
2128
+ (0, debug_log_1.debugError)('session-storage.restoreCompactCheckpoint.toolGroups', error, sessionId);
2129
+ return (0, tool_call_groups_1.sealToolCallGroups)(messages.map(sessionMessageToModelMessage));
2130
+ }
1877
2131
  }
1878
- return (0, tool_call_groups_1.sealToolCallGroups)(modelVisibleMessages.map(sessionMessageToModelMessage));
1879
- }) ?? []);
2132
+ return (0, tool_call_groups_1.sealToolCallGroups)(restored);
2133
+ }
2134
+ let modelVisibleMessages = messages;
2135
+ if (typeof session.transcriptDisplayStartTime === 'number') {
2136
+ const afterDisplayStart = messages.filter(message => (message.timestamp ?? 0) >= (session.transcriptDisplayStartTime ?? 0));
2137
+ modelVisibleMessages = hasPersistedCompactContext(afterDisplayStart)
2138
+ ? afterDisplayStart
2139
+ : messages;
2140
+ }
2141
+ return (0, tool_call_groups_1.sealToolCallGroups)(modelVisibleMessages.map(sessionMessageToModelMessage));
1880
2142
  }
1881
2143
  function sessionMessageToModelMessage(message) {
1882
2144
  const result = {
@@ -1919,10 +2181,14 @@ function findSession(ref, projectPath, options = {}) {
1919
2181
  * conflict prompts.
1920
2182
  */
1921
2183
  function lookupSessionRef(ref, projectPath, options = {}) {
2184
+ const candidates = options.allProjects || !projectPath ? listSessions() : listProjectSessions(projectPath);
2185
+ return lookupSessionRefInSessions(ref, candidates);
2186
+ }
2187
+ /** Resolve a reference against an already-loaded picker snapshot. */
2188
+ function lookupSessionRefInSessions(ref, candidates) {
1922
2189
  const query = ref.trim();
1923
2190
  if (!query)
1924
2191
  return { status: 'not_found' };
1925
- const candidates = options.allProjects || !projectPath ? listSessions() : listProjectSessions(projectPath);
1926
2192
  const exactId = candidates.find(session => session.id === query);
1927
2193
  if (exactId)
1928
2194
  return { status: 'found', session: exactId };