@orion-agents/orion-code 0.1.4-2 → 0.1.5

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (206) hide show
  1. package/README.md +50 -20
  2. package/README.zh-CN.md +6 -5
  3. package/dist/cli.js +4 -0
  4. package/dist/cli.js.map +1 -1
  5. package/dist/commands/context-commands.d.ts.map +1 -1
  6. package/dist/commands/context-commands.js +22 -0
  7. package/dist/commands/context-commands.js.map +1 -1
  8. package/dist/commands/context-tool-command-handlers.d.ts.map +1 -1
  9. package/dist/commands/context-tool-command-handlers.js +10 -3
  10. package/dist/commands/context-tool-command-handlers.js.map +1 -1
  11. package/dist/commands/core-command-handlers.d.ts +2 -2
  12. package/dist/commands/core-command-handlers.d.ts.map +1 -1
  13. package/dist/commands/core-command-handlers.js +65 -58
  14. package/dist/commands/core-command-handlers.js.map +1 -1
  15. package/dist/commands/diagnostic-command-handlers.d.ts.map +1 -1
  16. package/dist/commands/diagnostic-command-handlers.js +5 -5
  17. package/dist/commands/diagnostic-command-handlers.js.map +1 -1
  18. package/dist/commands/diagnostic-commands.d.ts.map +1 -1
  19. package/dist/commands/diagnostic-commands.js +43 -0
  20. package/dist/commands/diagnostic-commands.js.map +1 -1
  21. package/dist/commands/index.d.ts +4 -4
  22. package/dist/commands/index.d.ts.map +1 -1
  23. package/dist/commands/index.js +9 -2
  24. package/dist/commands/index.js.map +1 -1
  25. package/dist/commands/legacy-commands.js +2 -2
  26. package/dist/commands/legacy-commands.js.map +1 -1
  27. package/dist/commands/model-command-handlers.d.ts +3 -1
  28. package/dist/commands/model-command-handlers.d.ts.map +1 -1
  29. package/dist/commands/model-command-handlers.js +293 -81
  30. package/dist/commands/model-command-handlers.js.map +1 -1
  31. package/dist/commands/model-commands.d.ts.map +1 -1
  32. package/dist/commands/model-commands.js +36 -3
  33. package/dist/commands/model-commands.js.map +1 -1
  34. package/dist/commands/parser.d.ts +12 -0
  35. package/dist/commands/parser.d.ts.map +1 -1
  36. package/dist/commands/parser.js +48 -7
  37. package/dist/commands/parser.js.map +1 -1
  38. package/dist/commands/registry.d.ts +4 -0
  39. package/dist/commands/registry.d.ts.map +1 -0
  40. package/dist/commands/registry.js +566 -0
  41. package/dist/commands/registry.js.map +1 -0
  42. package/dist/commands/session-command-handlers.d.ts +2 -1
  43. package/dist/commands/session-command-handlers.d.ts.map +1 -1
  44. package/dist/commands/session-command-handlers.js +119 -59
  45. package/dist/commands/session-command-handlers.js.map +1 -1
  46. package/dist/commands/session-commands.d.ts.map +1 -1
  47. package/dist/commands/session-commands.js +24 -0
  48. package/dist/commands/session-commands.js.map +1 -1
  49. package/dist/commands/system-commands.d.ts.map +1 -1
  50. package/dist/commands/system-commands.js +1 -13
  51. package/dist/commands/system-commands.js.map +1 -1
  52. package/dist/commands/types.d.ts +65 -1
  53. package/dist/commands/types.d.ts.map +1 -1
  54. package/dist/commands/types.js +3 -4
  55. package/dist/commands/types.js.map +1 -1
  56. package/dist/commands/workflow-command-handlers.d.ts.map +1 -1
  57. package/dist/commands/workflow-command-handlers.js +5 -8
  58. package/dist/commands/workflow-command-handlers.js.map +1 -1
  59. package/dist/commands/workflow-commands.d.ts.map +1 -1
  60. package/dist/commands/workflow-commands.js +36 -4
  61. package/dist/commands/workflow-commands.js.map +1 -1
  62. package/dist/core/cost-tracker.d.ts +12 -0
  63. package/dist/core/cost-tracker.d.ts.map +1 -1
  64. package/dist/core/cost-tracker.js +12 -6
  65. package/dist/core/cost-tracker.js.map +1 -1
  66. package/dist/framework/store.d.ts +11 -2
  67. package/dist/framework/store.d.ts.map +1 -1
  68. package/dist/framework/store.js +18 -0
  69. package/dist/framework/store.js.map +1 -1
  70. package/dist/framework/tool-scheduler.d.ts +1 -1
  71. package/dist/framework/tool-scheduler.d.ts.map +1 -1
  72. package/dist/framework/tool-scheduler.js +41 -27
  73. package/dist/framework/tool-scheduler.js.map +1 -1
  74. package/dist/framework/tool.d.ts +12 -0
  75. package/dist/framework/tool.d.ts.map +1 -1
  76. package/dist/framework/tool.js.map +1 -1
  77. package/dist/ink-ui/screens/ReplScreen.d.ts +2 -1
  78. package/dist/ink-ui/screens/ReplScreen.d.ts.map +1 -1
  79. package/dist/ink-ui/screens/ReplScreen.js +103 -31
  80. package/dist/ink-ui/screens/ReplScreen.js.map +1 -1
  81. package/dist/memory/storage.d.ts.map +1 -1
  82. package/dist/memory/storage.js +7 -1
  83. package/dist/memory/storage.js.map +1 -1
  84. package/dist/memory/vector-store.d.ts +18 -0
  85. package/dist/memory/vector-store.d.ts.map +1 -1
  86. package/dist/memory/vector-store.js +62 -40
  87. package/dist/memory/vector-store.js.map +1 -1
  88. package/dist/migration/migrate.d.ts.map +1 -1
  89. package/dist/migration/migrate.js +11 -5
  90. package/dist/migration/migrate.js.map +1 -1
  91. package/dist/print-ui/launch.d.ts +3 -0
  92. package/dist/print-ui/launch.d.ts.map +1 -1
  93. package/dist/print-ui/launch.js +10 -3
  94. package/dist/print-ui/launch.js.map +1 -1
  95. package/dist/product/paths.d.ts.map +1 -1
  96. package/dist/product/paths.js +4 -11
  97. package/dist/product/paths.js.map +1 -1
  98. package/dist/runtime/agent-runtime-controller.d.ts +2 -0
  99. package/dist/runtime/agent-runtime-controller.d.ts.map +1 -1
  100. package/dist/runtime/agent-runtime-controller.js +127 -14
  101. package/dist/runtime/agent-runtime-controller.js.map +1 -1
  102. package/dist/runtime/agent-runtime-protocol.d.ts +15 -0
  103. package/dist/runtime/agent-runtime-protocol.d.ts.map +1 -1
  104. package/dist/runtime/agent-runtime-protocol.js +6 -0
  105. package/dist/runtime/agent-runtime-protocol.js.map +1 -1
  106. package/dist/runtime/chat-controller.d.ts +0 -5
  107. package/dist/runtime/chat-controller.d.ts.map +1 -1
  108. package/dist/runtime/chat-controller.js +102 -14
  109. package/dist/runtime/chat-controller.js.map +1 -1
  110. package/dist/runtime/chat-effort.d.ts +5 -0
  111. package/dist/runtime/chat-effort.d.ts.map +1 -0
  112. package/dist/runtime/chat-effort.js +47 -0
  113. package/dist/runtime/chat-effort.js.map +1 -0
  114. package/dist/runtime/goals/prompt.d.ts.map +1 -1
  115. package/dist/runtime/goals/prompt.js +2 -2
  116. package/dist/runtime/goals/prompt.js.map +1 -1
  117. package/dist/runtime/rich-text/markdown-parser.d.ts +2 -0
  118. package/dist/runtime/rich-text/markdown-parser.d.ts.map +1 -1
  119. package/dist/runtime/rich-text/markdown-parser.js +50 -10
  120. package/dist/runtime/rich-text/markdown-parser.js.map +1 -1
  121. package/dist/runtime/ui-events.d.ts +32 -0
  122. package/dist/runtime/ui-events.d.ts.map +1 -1
  123. package/dist/runtime/ui-events.js.map +1 -1
  124. package/dist/runtime/ui-view-model.d.ts +13 -4
  125. package/dist/runtime/ui-view-model.d.ts.map +1 -1
  126. package/dist/runtime/ui-view-model.js +64 -10
  127. package/dist/runtime/ui-view-model.js.map +1 -1
  128. package/dist/services/concurrent-sessions.d.ts.map +1 -1
  129. package/dist/services/concurrent-sessions.js +28 -20
  130. package/dist/services/concurrent-sessions.js.map +1 -1
  131. package/dist/services/config.d.ts +2 -0
  132. package/dist/services/config.d.ts.map +1 -1
  133. package/dist/services/config.js +1 -0
  134. package/dist/services/config.js.map +1 -1
  135. package/dist/services/effort.d.ts +40 -0
  136. package/dist/services/effort.d.ts.map +1 -0
  137. package/dist/services/effort.js +88 -0
  138. package/dist/services/effort.js.map +1 -0
  139. package/dist/services/file-lock.d.ts.map +1 -1
  140. package/dist/services/file-lock.js +83 -29
  141. package/dist/services/file-lock.js.map +1 -1
  142. package/dist/services/global-config.d.ts +7 -0
  143. package/dist/services/global-config.d.ts.map +1 -1
  144. package/dist/services/global-config.js +12 -0
  145. package/dist/services/global-config.js.map +1 -1
  146. package/dist/services/llm.d.ts +23 -0
  147. package/dist/services/llm.d.ts.map +1 -1
  148. package/dist/services/llm.js +99 -3
  149. package/dist/services/llm.js.map +1 -1
  150. package/dist/services/model-registry.d.ts +3 -0
  151. package/dist/services/model-registry.d.ts.map +1 -1
  152. package/dist/services/model-registry.js +59 -2
  153. package/dist/services/model-registry.js.map +1 -1
  154. package/dist/services/provider-diagnostics.d.ts +1 -1
  155. package/dist/services/provider-diagnostics.d.ts.map +1 -1
  156. package/dist/services/provider-diagnostics.js +30 -16
  157. package/dist/services/provider-diagnostics.js.map +1 -1
  158. package/dist/services/provider-resilience/error-classifier.d.ts.map +1 -1
  159. package/dist/services/provider-resilience/error-classifier.js +15 -0
  160. package/dist/services/provider-resilience/error-classifier.js.map +1 -1
  161. package/dist/services/provider-resilience/types.d.ts +1 -1
  162. package/dist/services/provider-resilience/types.d.ts.map +1 -1
  163. package/dist/services/provider-resilience/types.js.map +1 -1
  164. package/dist/services/session-storage.d.ts +4 -0
  165. package/dist/services/session-storage.d.ts.map +1 -1
  166. package/dist/services/session-storage.js +252 -228
  167. package/dist/services/session-storage.js.map +1 -1
  168. package/dist/services/storage-maintenance.d.ts.map +1 -1
  169. package/dist/services/storage-maintenance.js +7 -7
  170. package/dist/services/storage-maintenance.js.map +1 -1
  171. package/dist/services/tool-allowlist.d.ts +20 -10
  172. package/dist/services/tool-allowlist.d.ts.map +1 -1
  173. package/dist/services/tool-allowlist.js +64 -15
  174. package/dist/services/tool-allowlist.js.map +1 -1
  175. package/dist/services/usage-state.d.ts +9 -0
  176. package/dist/services/usage-state.d.ts.map +1 -1
  177. package/dist/services/usage-state.js +35 -0
  178. package/dist/services/usage-state.js.map +1 -1
  179. package/dist/terminal-ui/launch.d.ts +10 -0
  180. package/dist/terminal-ui/launch.d.ts.map +1 -1
  181. package/dist/terminal-ui/launch.js +58 -37
  182. package/dist/terminal-ui/launch.js.map +1 -1
  183. package/dist/tools/bash_security.d.ts +9 -0
  184. package/dist/tools/bash_security.d.ts.map +1 -1
  185. package/dist/tools/bash_security.js +32 -0
  186. package/dist/tools/bash_security.js.map +1 -1
  187. package/dist/tools/index.d.ts.map +1 -1
  188. package/dist/tools/index.js +40 -1
  189. package/dist/tools/index.js.map +1 -1
  190. package/dist/tui-ui/launch.d.ts.map +1 -1
  191. package/dist/tui-ui/launch.js +3 -3
  192. package/dist/tui-ui/launch.js.map +1 -1
  193. package/dist/tui-ui/layout.js +7 -3
  194. package/dist/tui-ui/layout.js.map +1 -1
  195. package/dist/tui-ui/pickers.d.ts.map +1 -1
  196. package/dist/tui-ui/pickers.js +1 -0
  197. package/dist/tui-ui/pickers.js.map +1 -1
  198. package/dist/tui-ui/runner.d.ts +2 -1
  199. package/dist/tui-ui/runner.d.ts.map +1 -1
  200. package/dist/tui-ui/runner.js +17 -29
  201. package/dist/tui-ui/runner.js.map +1 -1
  202. package/dist/tui-ui/state.d.ts +4 -0
  203. package/dist/tui-ui/state.d.ts.map +1 -1
  204. package/dist/tui-ui/state.js +15 -1
  205. package/dist/tui-ui/state.js.map +1 -1
  206. package/package.json +3 -3
@@ -12,6 +12,7 @@ exports.getProjectKey = getProjectKey;
12
12
  exports.isSessionMetaFile = isSessionMetaFile;
13
13
  exports.createSession = createSession;
14
14
  exports.saveSessionMeta = saveSessionMeta;
15
+ exports.updateSessionEffort = updateSessionEffort;
15
16
  exports.loadSessionMeta = loadSessionMeta;
16
17
  exports.updateSessionStats = updateSessionStats;
17
18
  exports.resumeSession = resumeSession;
@@ -51,6 +52,7 @@ const child_process_1 = require("child_process");
51
52
  const path_1 = require("path");
52
53
  const config_dir_1 = require("./config-dir");
53
54
  const atomic_write_1 = require("./atomic-write");
55
+ const file_lock_1 = require("./file-lock");
54
56
  const session_index_1 = require("./session-index");
55
57
  const tool_call_groups_1 = require("./compact/tool-call-groups");
56
58
  const redaction_1 = require("./redaction");
@@ -377,105 +379,129 @@ function createSession(projectPath, model) {
377
379
  function saveSessionMeta(session) {
378
380
  (0, config_dir_1.ensureConfigDir)();
379
381
  const normalized = normalizeSessionMeta(session);
380
- const payload = JSON.stringify(normalized, null, 2);
381
382
  (0, config_dir_1.ensureProjectDir)(normalized.projectPath);
382
- (0, atomic_write_1.atomicWriteFileSync)((0, config_dir_1.getProjectSessionMetaPath)(normalized.projectPath, normalized.id), payload, {
383
- mode: 0o600,
383
+ const metaPath = (0, config_dir_1.getProjectSessionMetaPath)(normalized.projectPath, normalized.id);
384
+ (0, file_lock_1.withFileLockSync)(metaPath, () => {
385
+ writeSessionMetaUnlocked(normalized);
386
+ });
387
+ }
388
+ function writeSessionMetaUnlocked(session) {
389
+ const normalized = normalizeSessionMeta(session);
390
+ (0, config_dir_1.ensureProjectDir)(normalized.projectPath);
391
+ (0, atomic_write_1.atomicWriteFileSync)((0, config_dir_1.getProjectSessionMetaPath)(normalized.projectPath, normalized.id), JSON.stringify(normalized, null, 2), { mode: 0o600 });
392
+ }
393
+ function findSessionMetaPath(sessionId) {
394
+ const projectsDir = (0, config_dir_1.getProjectsDir)();
395
+ if (!(0, fs_1.existsSync)(projectsDir))
396
+ return null;
397
+ for (const projectKey of (0, fs_1.readdirSync)(projectsDir)) {
398
+ const path = (0, path_1.join)(projectsDir, projectKey, 'sessions', `${sessionId}.json`);
399
+ if ((0, fs_1.existsSync)(path) && readSessionMetaAtPath(path))
400
+ return path;
401
+ }
402
+ return null;
403
+ }
404
+ function readSessionMetaAtPath(path) {
405
+ const session = parseSessionMetaFile(path);
406
+ return session ? tryNormalizeSessionMeta(session, path) : null;
407
+ }
408
+ /**
409
+ * Serialize a session read-modify-write transaction across Orion processes.
410
+ * The metadata path is rediscovered before each transaction and re-read only
411
+ * after the lock is held, so a stale snapshot can never overwrite a concurrent
412
+ * message/stat update.
413
+ */
414
+ function withLockedSession(sessionId, operation) {
415
+ const metaPath = findSessionMetaPath(sessionId);
416
+ if (!metaPath)
417
+ return null;
418
+ return (0, file_lock_1.withFileLockSync)(metaPath, () => {
419
+ const session = readSessionMetaAtPath(metaPath);
420
+ if (!session)
421
+ return null;
422
+ return operation(session);
423
+ }, { waitMs: 10000 });
424
+ }
425
+ function mutateSessionMeta(sessionId, mutation) {
426
+ return withLockedSession(sessionId, session => {
427
+ mutation(session);
428
+ writeSessionMetaUnlocked(session);
429
+ return session;
430
+ });
431
+ }
432
+ function updateSessionEffort(sessionId, effortPreference) {
433
+ return mutateSessionMeta(sessionId, session => {
434
+ if (effortPreference === undefined || effortPreference === 'auto') {
435
+ delete session.effortPreference;
436
+ }
437
+ else {
438
+ session.effortPreference = effortPreference;
439
+ }
384
440
  });
385
441
  }
386
442
  /**
387
443
  * 加载会话元数据
388
444
  */
389
445
  function loadSessionMeta(sessionId) {
390
- const projectsDir = (0, config_dir_1.getProjectsDir)();
391
- if ((0, fs_1.existsSync)(projectsDir)) {
392
- for (const projectKey of (0, fs_1.readdirSync)(projectsDir)) {
393
- const path = (0, path_1.join)(projectsDir, projectKey, 'sessions', `${sessionId}.json`);
394
- if (!(0, fs_1.existsSync)(path))
395
- continue;
396
- const session = parseSessionMetaFile(path);
397
- if (session) {
398
- const normalized = tryNormalizeSessionMeta(session, path);
399
- if (normalized)
400
- return normalized;
401
- }
402
- }
403
- }
404
- return null;
446
+ const path = findSessionMetaPath(sessionId);
447
+ return path ? readSessionMetaAtPath(path) : null;
405
448
  }
406
449
  /**
407
450
  * 更新会话统计
408
451
  */
409
452
  function updateSessionStats(sessionId, tokens, cost) {
410
- const session = loadSessionMeta(sessionId);
411
- if (!session)
412
- return;
413
- session.tokenCount += tokens;
414
- session.cost += cost;
415
- session.updatedAt = Date.now();
416
- session.updatedAtIso = new Date(session.updatedAt).toISOString();
417
- saveSessionMeta(session);
418
- }
419
- function touchSession(sessionId, messageDelta = 0) {
420
- const session = loadSessionMeta(sessionId);
421
- if (!session)
422
- return null;
423
- session.updatedAt = Date.now();
424
- session.updatedAtIso = new Date(session.updatedAt).toISOString();
425
- session.messageCount = (session.messageCount ?? 0) + messageDelta;
426
- saveSessionMeta(session);
427
- return session;
453
+ mutateSessionMeta(sessionId, session => {
454
+ session.tokenCount += tokens;
455
+ session.cost += cost;
456
+ session.updatedAt = Date.now();
457
+ session.updatedAtIso = new Date(session.updatedAt).toISOString();
458
+ });
428
459
  }
429
460
  /**
430
461
  * Mark a saved session as active again and refresh project metadata.
431
462
  */
432
463
  function resumeSession(sessionId) {
433
- const session = loadSessionMeta(sessionId);
434
- if (!session)
435
- return null;
436
- session.endTime = undefined;
437
- session.gitBranch = getGitBranch(session.projectPath);
438
- session.updatedAt = Date.now();
439
- session.updatedAtIso = new Date(session.updatedAt).toISOString();
440
- saveSessionMeta(session);
441
- return session;
464
+ return mutateSessionMeta(sessionId, session => {
465
+ session.endTime = undefined;
466
+ session.gitBranch = getGitBranch(session.projectPath);
467
+ session.updatedAt = Date.now();
468
+ session.updatedAtIso = new Date(session.updatedAt).toISOString();
469
+ });
442
470
  }
443
471
  /** Keep the session metadata's additive Goal binding in sync with its sidecar. */
444
472
  function updateSessionGoalBinding(sessionId, goal) {
445
- const session = loadSessionMeta(sessionId);
446
- if (!session)
447
- return null;
448
- session.activeGoalId = goal?.goalId;
449
- session.activeGoalObjective = goal?.objective;
450
- session.updatedAt = Date.now();
451
- session.updatedAtIso = new Date(session.updatedAt).toISOString();
452
- saveSessionMeta(session);
453
- return session;
473
+ return mutateSessionMeta(sessionId, session => {
474
+ session.activeGoalId = goal?.goalId;
475
+ session.activeGoalObjective = goal?.objective;
476
+ session.updatedAt = Date.now();
477
+ session.updatedAtIso = new Date(session.updatedAt).toISOString();
478
+ });
454
479
  }
455
480
  /**
456
481
  * 更新会话 Harness 状态。
457
482
  */
458
483
  function updateSessionHarnessState(sessionId, harnessState) {
459
- const session = loadSessionMeta(sessionId);
460
- if (!session)
461
- return;
462
- const fullState = (0, harness_1.upgradeHarnessState)(harnessState, { cwd: session.cwd ?? session.projectPath });
463
- const sidecar = {
464
- version: 2,
465
- sessionId,
466
- projectPath: session.projectPath,
467
- state: fullState,
468
- contextCapsule: fullState.capsule,
469
- updatedAt: Date.now(),
470
- diagnostics: fullState.diagnostics,
471
- };
472
- (0, config_dir_1.ensureProjectDir)(session.projectPath);
473
- (0, atomic_write_1.atomicWriteFileSync)((0, config_dir_1.getProjectSessionHarnessPath)(session.projectPath, sessionId), JSON.stringify(sidecar, null, 2), { mode: 0o600 });
474
- session.harnessState = (0, harness_1.summarizeHarnessStateForMeta)(fullState);
475
- session.contextCapsule = fullState.capsule;
476
- session.updatedAt = Date.now();
477
- session.updatedAtIso = new Date(session.updatedAt).toISOString();
478
- saveSessionMeta(session);
484
+ withLockedSession(sessionId, session => {
485
+ const fullState = (0, harness_1.upgradeHarnessState)(harnessState, {
486
+ cwd: session.cwd ?? session.projectPath,
487
+ });
488
+ const sidecar = {
489
+ version: 2,
490
+ sessionId,
491
+ projectPath: session.projectPath,
492
+ state: fullState,
493
+ contextCapsule: fullState.capsule,
494
+ updatedAt: Date.now(),
495
+ diagnostics: fullState.diagnostics,
496
+ };
497
+ (0, config_dir_1.ensureProjectDir)(session.projectPath);
498
+ (0, atomic_write_1.atomicWriteFileSync)((0, config_dir_1.getProjectSessionHarnessPath)(session.projectPath, sessionId), JSON.stringify(sidecar, null, 2), { mode: 0o600 });
499
+ session.harnessState = (0, harness_1.summarizeHarnessStateForMeta)(fullState);
500
+ session.contextCapsule = fullState.capsule;
501
+ session.updatedAt = Date.now();
502
+ session.updatedAtIso = new Date(session.updatedAt).toISOString();
503
+ writeSessionMetaUnlocked(session);
504
+ });
479
505
  }
480
506
  function loadSessionHarnessState(sessionId) {
481
507
  const session = loadSessionMeta(sessionId);
@@ -509,23 +535,18 @@ function loadSessionHarnessState(sessionId) {
509
535
  function updateSessionSkills(sessionId, skills) {
510
536
  if (skills.length === 0)
511
537
  return;
512
- const session = loadSessionMeta(sessionId);
513
- if (!session)
514
- return;
515
- session.skillsUsed = [...new Set([...(session.skillsUsed || []), ...skills])];
516
- session.updatedAt = Date.now();
517
- session.updatedAtIso = new Date(session.updatedAt).toISOString();
518
- saveSessionMeta(session);
538
+ mutateSessionMeta(sessionId, session => {
539
+ session.skillsUsed = [...new Set([...(session.skillsUsed || []), ...skills])];
540
+ session.updatedAt = Date.now();
541
+ session.updatedAtIso = new Date(session.updatedAt).toISOString();
542
+ });
519
543
  }
520
544
  function markSessionTranscriptDisplayStart(sessionId, timestamp = Date.now()) {
521
- const session = loadSessionMeta(sessionId);
522
- if (!session)
523
- return null;
524
- session.transcriptDisplayStartTime = timestamp;
525
- session.updatedAt = timestamp;
526
- session.updatedAtIso = new Date(timestamp).toISOString();
527
- saveSessionMeta(session);
528
- return session;
545
+ return mutateSessionMeta(sessionId, session => {
546
+ session.transcriptDisplayStartTime = timestamp;
547
+ session.updatedAt = timestamp;
548
+ session.updatedAtIso = new Date(timestamp).toISOString();
549
+ });
529
550
  }
530
551
  function persistSessionCompactHistory(sessionId, messages, timestamp = Date.now()) {
531
552
  const session = loadSessionMeta(sessionId);
@@ -557,51 +578,53 @@ function loadSessionCompactCheckpoint(sessionId) {
557
578
  return checkpoint;
558
579
  }
559
580
  function commitSessionCompactCheckpoint(input) {
560
- const session = loadSessionMeta(input.sessionId);
561
- if (!session)
562
- throw new Error(`Session not found: ${input.sessionId}`);
563
- const rawCount = readSessionMessages(input.sessionId).length;
564
- if (input.sourceMessageCount < 0 || input.sourceMessageCount > rawCount) {
565
- throw new Error(`Invalid compact source boundary ${input.sourceMessageCount}; transcript has ${rawCount} messages`);
566
- }
567
- const createdAt = input.createdAt ?? Date.now();
568
- const checkpoint = {
569
- version: 1,
570
- checkpointId: (0, crypto_1.randomUUID)(),
571
- sessionId: input.sessionId,
572
- createdAt,
573
- mode: input.mode,
574
- modelId: input.modelId,
575
- sourceMessageCount: input.sourceMessageCount,
576
- transcriptStartMessageIndex: Math.max(0, Math.min(input.sourceMessageCount, input.transcriptStartMessageIndex)),
577
- modelHistory: input.modelHistory
578
- .filter(message => message.role !== 'system')
579
- .map(message => ({ ...message })),
580
- summary: {
581
- ...input.summary,
581
+ const checkpoint = withLockedSession(input.sessionId, session => {
582
+ const rawCount = readSessionMessagesForSession(session).length;
583
+ if (input.sourceMessageCount < 0 || input.sourceMessageCount > rawCount) {
584
+ throw new Error(`Invalid compact source boundary ${input.sourceMessageCount}; transcript has ${rawCount} messages`);
585
+ }
586
+ const createdAt = input.createdAt ?? Date.now();
587
+ const nextCheckpoint = {
588
+ version: 1,
589
+ checkpointId: (0, crypto_1.randomUUID)(),
590
+ sessionId: input.sessionId,
591
+ createdAt,
592
+ mode: input.mode,
593
+ modelId: input.modelId,
582
594
  sourceMessageCount: input.sourceMessageCount,
583
- },
584
- beforeUsage: { ...input.beforeUsage },
585
- afterUsage: { ...input.afterUsage },
586
- };
587
- const previousId = session.activeCompactCheckpointId;
588
- const previousTime = session.lastCompactAt;
589
- session.activeCompactCheckpointId = checkpoint.checkpointId;
590
- session.lastCompactAt = createdAt;
591
- session.updatedAt = createdAt;
592
- session.updatedAtIso = new Date(createdAt).toISOString();
593
- // Commit the pointer before replacing the atomic sidecar. If the process
594
- // crashes between writes, the loader safely uses the last valid sidecar.
595
- saveSessionMeta(session);
596
- try {
597
- (0, atomic_write_1.atomicWriteFileSync)((0, config_dir_1.getProjectSessionCompactPath)(session.projectPath, input.sessionId), JSON.stringify(checkpoint, null, 2), { mode: 0o600 });
598
- }
599
- catch (error) {
600
- session.activeCompactCheckpointId = previousId;
601
- session.lastCompactAt = previousTime;
602
- saveSessionMeta(session);
603
- throw error;
604
- }
595
+ transcriptStartMessageIndex: Math.max(0, Math.min(input.sourceMessageCount, input.transcriptStartMessageIndex)),
596
+ modelHistory: input.modelHistory
597
+ .filter(message => message.role !== 'system')
598
+ .map(message => ({ ...message })),
599
+ summary: {
600
+ ...input.summary,
601
+ sourceMessageCount: input.sourceMessageCount,
602
+ },
603
+ beforeUsage: { ...input.beforeUsage },
604
+ afterUsage: { ...input.afterUsage },
605
+ };
606
+ const previousId = session.activeCompactCheckpointId;
607
+ const previousTime = session.lastCompactAt;
608
+ session.activeCompactCheckpointId = nextCheckpoint.checkpointId;
609
+ session.lastCompactAt = createdAt;
610
+ session.updatedAt = createdAt;
611
+ session.updatedAtIso = new Date(createdAt).toISOString();
612
+ // Commit the pointer before replacing the atomic sidecar. If the process
613
+ // crashes between writes, the loader safely uses the last valid sidecar.
614
+ writeSessionMetaUnlocked(session);
615
+ try {
616
+ (0, atomic_write_1.atomicWriteFileSync)((0, config_dir_1.getProjectSessionCompactPath)(session.projectPath, input.sessionId), JSON.stringify(nextCheckpoint, null, 2), { mode: 0o600 });
617
+ }
618
+ catch (error) {
619
+ session.activeCompactCheckpointId = previousId;
620
+ session.lastCompactAt = previousTime;
621
+ writeSessionMetaUnlocked(session);
622
+ throw error;
623
+ }
624
+ return nextCheckpoint;
625
+ });
626
+ if (!checkpoint)
627
+ throw new Error(`Session not found: ${input.sessionId}`);
605
628
  return checkpoint;
606
629
  }
607
630
  function loadSessionTranscriptMessages(sessionId) {
@@ -625,22 +648,17 @@ function hasPersistedCompactContext(messages) {
625
648
  * 结束会话
626
649
  */
627
650
  function endSession(sessionId) {
628
- const session = loadSessionMeta(sessionId);
629
- if (!session)
630
- return;
631
- session.endTime = Date.now();
632
- session.updatedAt = session.endTime;
633
- session.updatedAtIso = new Date(session.updatedAt).toISOString();
634
- saveSessionMeta(session);
651
+ mutateSessionMeta(sessionId, session => {
652
+ session.endTime = Date.now();
653
+ session.updatedAt = session.endTime;
654
+ session.updatedAtIso = new Date(session.updatedAt).toISOString();
655
+ });
635
656
  }
636
657
  /**
637
658
  * 更新会话任务摘要
638
659
  * 从会话消息中提取关键信息并更新元数据
639
660
  */
640
661
  function updateSessionSummary(sessionId, messages) {
641
- const session = loadSessionMeta(sessionId);
642
- if (!session)
643
- return;
644
662
  // 提取工具使用列表
645
663
  const toolsUsed = [];
646
664
  const filesModified = [];
@@ -668,14 +686,15 @@ function updateSessionSummary(sessionId, messages) {
668
686
  // 提取任务摘要(从第一个用户消息)
669
687
  const firstUserMsg = messages.find(m => m.role === 'user' && m.content);
670
688
  const taskSummary = (0, redaction_1.redactTraceText)(firstUserMsg?.content ?? '').slice(0, 100);
671
- // 更新 session
672
- session.toolsUsed = [...new Set(toolsUsed)]; // unique
673
- session.filesModified = [...new Set(filesModified)]; // unique
674
- session.taskSummary = taskSummary.length > 100 ? taskSummary.slice(0, 100) + '...' : taskSummary;
675
- session.messageCount = messages.length;
676
- session.updatedAt = Date.now();
677
- session.updatedAtIso = new Date(session.updatedAt).toISOString();
678
- saveSessionMeta(session);
689
+ mutateSessionMeta(sessionId, session => {
690
+ session.toolsUsed = [...new Set(toolsUsed)]; // unique
691
+ session.filesModified = [...new Set(filesModified)]; // unique
692
+ session.taskSummary =
693
+ taskSummary.length > 100 ? taskSummary.slice(0, 100) + '...' : taskSummary;
694
+ session.messageCount = messages.length;
695
+ session.updatedAt = Date.now();
696
+ session.updatedAtIso = new Date(session.updatedAt).toISOString();
697
+ });
679
698
  }
680
699
  /**
681
700
  * 获取项目最近的会话
@@ -747,15 +766,19 @@ function readProjectHistory(projectPath, limit) {
747
766
  function appendSessionMessage(sessionId, message) {
748
767
  (0, config_dir_1.ensureConfigDir)();
749
768
  const line = JSON.stringify(message) + '\n';
750
- const session = touchSession(sessionId, 1);
751
- if (!session)
752
- return;
753
- (0, config_dir_1.ensureProjectDir)(session.projectPath);
754
- (0, fs_1.appendFileSync)((0, config_dir_1.getProjectSessionMessagesPath)(session.projectPath, sessionId), line, {
755
- mode: 0o600,
769
+ withLockedSession(sessionId, session => {
770
+ (0, config_dir_1.ensureProjectDir)(session.projectPath);
771
+ (0, fs_1.appendFileSync)((0, config_dir_1.getProjectSessionMessagesPath)(session.projectPath, sessionId), line, {
772
+ mode: 0o600,
773
+ });
774
+ // Keep transcript, metadata, and search index inside the same session
775
+ // critical section so concurrent processes cannot lose counters.
776
+ (0, session_index_1.updateSessionIndex)(sessionId, session.projectPath, message);
777
+ session.updatedAt = Date.now();
778
+ session.updatedAtIso = new Date(session.updatedAt).toISOString();
779
+ session.messageCount = (session.messageCount ?? 0) + 1;
780
+ writeSessionMetaUnlocked(session);
756
781
  });
757
- // Update session index for fast search
758
- (0, session_index_1.updateSessionIndex)(sessionId, session.projectPath, message);
759
782
  }
760
783
  /**
761
784
  * 追加多条会话消息
@@ -765,16 +788,19 @@ function appendSessionMessages(sessionId, messages) {
765
788
  return;
766
789
  (0, config_dir_1.ensureConfigDir)();
767
790
  const lines = messages.map(m => JSON.stringify(m)).join('\n') + '\n';
768
- const session = touchSession(sessionId, messages.length);
769
- if (!session)
770
- return;
771
- (0, config_dir_1.ensureProjectDir)(session.projectPath);
772
- (0, fs_1.appendFileSync)((0, config_dir_1.getProjectSessionMessagesPath)(session.projectPath, sessionId), lines, {
773
- mode: 0o600,
791
+ withLockedSession(sessionId, session => {
792
+ (0, config_dir_1.ensureProjectDir)(session.projectPath);
793
+ (0, fs_1.appendFileSync)((0, config_dir_1.getProjectSessionMessagesPath)(session.projectPath, sessionId), lines, {
794
+ mode: 0o600,
795
+ });
796
+ for (const message of messages) {
797
+ (0, session_index_1.updateSessionIndex)(sessionId, session.projectPath, message);
798
+ }
799
+ session.updatedAt = Date.now();
800
+ session.updatedAtIso = new Date(session.updatedAt).toISOString();
801
+ session.messageCount = (session.messageCount ?? 0) + messages.length;
802
+ writeSessionMetaUnlocked(session);
774
803
  });
775
- for (const message of messages) {
776
- (0, session_index_1.updateSessionIndex)(sessionId, session.projectPath, message);
777
- }
778
804
  }
779
805
  function isFinalAssistantMessage(message) {
780
806
  return message.role === 'assistant' && (!message.tool_calls || message.tool_calls.length === 0);
@@ -795,24 +821,20 @@ function findLastCompleteBoundary(messages) {
795
821
  // sliced the prompt away).
796
822
  return lastUserIndex + 1;
797
823
  }
798
- function overwriteSessionMessages(sessionId, messages) {
799
- (0, config_dir_1.ensureConfigDir)();
800
- const session = loadSessionMeta(sessionId);
801
- if (!session)
802
- return;
824
+ function overwriteSessionMessagesUnlocked(session, messages) {
803
825
  const content = messages.length > 0 ? messages.map(message => JSON.stringify(message)).join('\n') + '\n' : '';
804
826
  (0, config_dir_1.ensureProjectDir)(session.projectPath);
805
- (0, atomic_write_1.atomicWriteFileSync)((0, config_dir_1.getProjectSessionMessagesPath)(session.projectPath, sessionId), content, {
827
+ (0, atomic_write_1.atomicWriteFileSync)((0, config_dir_1.getProjectSessionMessagesPath)(session.projectPath, session.id), content, {
806
828
  mode: 0o600,
807
829
  });
808
- (0, session_index_1.deleteSessionIndex)(sessionId, session.projectPath);
830
+ (0, session_index_1.deleteSessionIndex)(session.id, session.projectPath);
809
831
  for (const message of messages) {
810
- (0, session_index_1.updateSessionIndex)(sessionId, session.projectPath, message);
832
+ (0, session_index_1.updateSessionIndex)(session.id, session.projectPath, message);
811
833
  }
812
834
  session.messageCount = messages.length;
813
835
  session.updatedAt = Date.now();
814
836
  session.updatedAtIso = new Date(session.updatedAt).toISOString();
815
- saveSessionMeta(session);
837
+ writeSessionMetaUnlocked(session);
816
838
  }
817
839
  /**
818
840
  * Remove a trailing incomplete turn from the persisted session transcript.
@@ -823,14 +845,15 @@ function overwriteSessionMessages(sessionId, messages) {
823
845
  * resurrect partial state.
824
846
  */
825
847
  function truncateSessionToLastComplete(sessionId) {
826
- const messages = readSessionMessages(sessionId);
827
- const boundary = findLastCompleteBoundary(messages);
828
- if (boundary === messages.length) {
829
- return messages;
830
- }
831
- const truncated = messages.slice(0, boundary);
832
- overwriteSessionMessages(sessionId, truncated);
833
- return truncated;
848
+ return (withLockedSession(sessionId, session => {
849
+ const messages = readSessionMessagesForSession(session);
850
+ const boundary = findLastCompleteBoundary(messages);
851
+ if (boundary === messages.length)
852
+ return messages;
853
+ const truncated = messages.slice(0, boundary);
854
+ overwriteSessionMessagesUnlocked(session, truncated);
855
+ return truncated;
856
+ }) ?? []);
834
857
  }
835
858
  function removeLastIncompleteAssistantMessage(sessionId) {
836
859
  return truncateSessionToLastComplete(sessionId);
@@ -843,14 +866,16 @@ function removeLastIncompleteAssistantMessage(sessionId) {
843
866
  * this is a no-op when the last message is not a user message.
844
867
  */
845
868
  function removeTrailingSessionUserMessage(sessionId) {
846
- const messages = readSessionMessages(sessionId);
847
- if (messages.length === 0)
848
- return messages;
849
- if (messages[messages.length - 1].role !== 'user')
850
- return messages;
851
- const truncated = messages.slice(0, -1);
852
- overwriteSessionMessages(sessionId, truncated);
853
- return truncated;
869
+ return (withLockedSession(sessionId, session => {
870
+ const messages = readSessionMessagesForSession(session);
871
+ if (messages.length === 0)
872
+ return messages;
873
+ if (messages[messages.length - 1].role !== 'user')
874
+ return messages;
875
+ const truncated = messages.slice(0, -1);
876
+ overwriteSessionMessagesUnlocked(session, truncated);
877
+ return truncated;
878
+ }) ?? []);
854
879
  }
855
880
  /**
856
881
  * 读取会话消息
@@ -859,6 +884,10 @@ function readSessionMessages(sessionId) {
859
884
  const session = loadSessionMeta(sessionId);
860
885
  if (!session)
861
886
  return [];
887
+ return readSessionMessagesForSession(session);
888
+ }
889
+ function readSessionMessagesForSession(session) {
890
+ const sessionId = session.id;
862
891
  const path = (0, config_dir_1.getProjectSessionMessagesPath)(session.projectPath, sessionId);
863
892
  if (!(0, fs_1.existsSync)(path))
864
893
  return [];
@@ -1073,48 +1102,43 @@ function lookupSessionRef(ref, projectPath, options = {}) {
1073
1102
  * Rename a session for easier picker/resume targeting.
1074
1103
  */
1075
1104
  function renameSession(sessionId, name) {
1076
- const session = loadSessionMeta(sessionId);
1077
- if (!session)
1078
- return null;
1079
1105
  const trimmed = name.trim();
1080
- session.name = trimmed || undefined;
1081
- session.updatedAt = Date.now();
1082
- session.updatedAtIso = new Date(session.updatedAt).toISOString();
1083
- saveSessionMeta(session);
1084
- return session;
1106
+ return mutateSessionMeta(sessionId, session => {
1107
+ session.name = trimmed || undefined;
1108
+ session.updatedAt = Date.now();
1109
+ session.updatedAtIso = new Date(session.updatedAt).toISOString();
1110
+ });
1085
1111
  }
1086
1112
  /**
1087
1113
  * 删除会话
1088
1114
  */
1089
1115
  function deleteSession(sessionId) {
1090
- const session = loadSessionMeta(sessionId);
1091
- if (!session)
1092
- return false;
1093
- let deleted = false;
1094
- const paths = [
1095
- (0, config_dir_1.getProjectSessionMetaPath)(session.projectPath, sessionId),
1096
- (0, config_dir_1.getProjectSessionMessagesPath)(session.projectPath, sessionId),
1097
- (0, config_dir_1.getProjectSessionHarnessPath)(session.projectPath, sessionId),
1098
- (0, config_dir_1.getProjectSessionCompactPath)(session.projectPath, sessionId),
1099
- (0, config_dir_1.getProjectSessionTracePath)(session.projectPath, sessionId),
1100
- ];
1101
- // Preserve a durable deletion fence so a stale writer in another process
1102
- // cannot recreate the Goal after the rest of the session is removed.
1103
- const goalPath = (0, path_1.join)((0, config_dir_1.getProjectSessionsDir)(session.projectPath), `${sessionId}.goal.json`);
1104
- const hadGoal = (0, fs_1.existsSync)(goalPath);
1105
- const goalDeletion = (0, goal_storage_1.deleteGoal)(session.projectPath, sessionId);
1106
- if (!goalDeletion.ok) {
1107
- return false;
1108
- }
1109
- if (hadGoal)
1110
- deleted = true;
1111
- (0, session_index_1.deleteSessionIndex)(sessionId, session.projectPath);
1112
- for (const path of uniquePaths(paths)) {
1113
- if ((0, fs_1.existsSync)(path)) {
1114
- (0, fs_1.unlinkSync)(path);
1116
+ return (withLockedSession(sessionId, session => {
1117
+ let deleted = false;
1118
+ const paths = [
1119
+ (0, config_dir_1.getProjectSessionMetaPath)(session.projectPath, sessionId),
1120
+ (0, config_dir_1.getProjectSessionMessagesPath)(session.projectPath, sessionId),
1121
+ (0, config_dir_1.getProjectSessionHarnessPath)(session.projectPath, sessionId),
1122
+ (0, config_dir_1.getProjectSessionCompactPath)(session.projectPath, sessionId),
1123
+ (0, config_dir_1.getProjectSessionTracePath)(session.projectPath, sessionId),
1124
+ ];
1125
+ // Preserve a durable deletion fence so a stale writer in another process
1126
+ // cannot recreate the Goal after the rest of the session is removed.
1127
+ const goalPath = (0, path_1.join)((0, config_dir_1.getProjectSessionsDir)(session.projectPath), `${sessionId}.goal.json`);
1128
+ const hadGoal = (0, fs_1.existsSync)(goalPath);
1129
+ const goalDeletion = (0, goal_storage_1.deleteGoal)(session.projectPath, sessionId);
1130
+ if (!goalDeletion.ok)
1131
+ return false;
1132
+ if (hadGoal)
1115
1133
  deleted = true;
1134
+ (0, session_index_1.deleteSessionIndex)(sessionId, session.projectPath);
1135
+ for (const path of uniquePaths(paths)) {
1136
+ if ((0, fs_1.existsSync)(path)) {
1137
+ (0, fs_1.unlinkSync)(path);
1138
+ deleted = true;
1139
+ }
1116
1140
  }
1117
- }
1118
- return deleted;
1141
+ return deleted;
1142
+ }) ?? false);
1119
1143
  }
1120
1144
  //# sourceMappingURL=session-storage.js.map