@jingyi0605/codingns 0.5.1 → 0.6.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 (196) hide show
  1. package/dist/public/assets/{AdaptiveButlerPage-SffCV4Vb.js → AdaptiveButlerPage-uFwDdN-F.js} +3 -3
  2. package/dist/public/assets/App-BZvapsi8.js +30 -0
  3. package/dist/public/assets/App-CcDXqFl1.css +1 -0
  4. package/dist/public/assets/{BootstrapPage--zExdgfM.js → BootstrapPage-gHSoa4JN.js} +1 -1
  5. package/dist/public/assets/ConversationPage-z3sXtKZ7.js +4 -0
  6. package/dist/public/assets/{DesktopDetachPreviewPage-DvI9CIKi.js → DesktopDetachPreviewPage-4eMRxiBW.js} +1 -1
  7. package/dist/public/assets/DesktopWindowPage-CZcoGApB.js +2 -0
  8. package/dist/public/assets/FileContextPanel-C3qex8bb.js +1 -0
  9. package/dist/public/assets/GitSidebar-BK6H16XU.js +6 -0
  10. package/dist/public/assets/{MobileCreateSessionSheet-CXSKMnYn.js → MobileCreateSessionSheet-BYfbvK8o.js} +1 -1
  11. package/dist/public/assets/MobileSheet-Ckug8hTb.js +1 -0
  12. package/dist/public/assets/{MobileTopHeaderFrame-BWorAJ1C.js → MobileTopHeaderFrame-Bwv8Ovm_.js} +1 -1
  13. package/dist/public/assets/MobileWorkspaceSwitcherHeader-RqWrBdn2.js +1 -0
  14. package/dist/public/assets/RelayConnectEntryPage-D_4YL-YH.js +1 -0
  15. package/dist/public/assets/ServerSettingsModal-CMSm3BZU.js +1 -0
  16. package/dist/public/assets/SessionIndexPage-DuK10DL5.js +1 -0
  17. package/dist/public/assets/SettingsPage-fyD-xaHL.js +1 -0
  18. package/dist/public/assets/TerminalManagerPanel-CCLr1Ypk.js +1 -0
  19. package/dist/public/assets/{TerminalPage-CvnHXBhw.js → TerminalPage-DaooFaJ4.js} +19 -19
  20. package/dist/public/assets/{TerminalRuntimeFallbackModal-D7Aq186N.js → TerminalRuntimeFallbackModal-aUzjEBwP.js} +1 -1
  21. package/dist/public/assets/ToolFilesPage-CGxBvYG0.js +1 -0
  22. package/dist/public/assets/ToolGitPage-C264yjS9.js +1 -0
  23. package/dist/public/assets/ToolProcessesPage-BOP4A1cb.js +1 -0
  24. package/dist/public/assets/ToolsHomePage-CQxGiKQA.js +1 -0
  25. package/dist/public/assets/WorkbenchLandingPage-CvAY68ca.js +1 -0
  26. package/dist/public/assets/WorkbenchLayout-DGm8Tc5M.js +3 -0
  27. package/dist/public/assets/{WorkbenchModal-B09hC9b5.js → WorkbenchModal-0tPIIhca.js} +1 -1
  28. package/dist/public/assets/{WorkbenchShellRoute-DsW4mBTX.css → WorkbenchShellRoute-BF0nHWOk.css} +1 -1
  29. package/dist/public/assets/WorkbenchShellRoute-DBBOsJo9.js +1 -0
  30. package/dist/public/assets/WorkspaceDebugDetailPage-CDerFYd2.js +1 -0
  31. package/dist/public/assets/WorkspaceDetailPage-BlJc1CHE.js +1 -0
  32. package/dist/public/assets/WorkspaceHomePage-BUsKJ3lv.js +1 -0
  33. package/dist/public/assets/client-runtime-manager-BZpL17fc.js +1 -0
  34. package/dist/public/assets/{default-session-permission-mode-D0wZ9Jek.js → default-session-permission-mode-DT4SGiwp.js} +1 -1
  35. package/dist/public/assets/file-tree-icon-Db5LXC8h.js +31 -0
  36. package/dist/public/assets/index-BZLcEHW3.js +42 -0
  37. package/dist/public/assets/index-BbspQPC2.css +1 -0
  38. package/dist/public/assets/login-direct-candidate-resolver-1mxe_Oh8.js +1 -0
  39. package/dist/public/assets/{preferences-service-gOt2ZjKZ.js → preferences-service-DWnzl5a0.js} +1 -1
  40. package/dist/public/assets/relay-entry-C5_Iay0I.js +1 -0
  41. package/dist/public/assets/session-runtime-machine-DdLeDqQr.js +17 -0
  42. package/dist/public/assets/{styles-BWPBZvze.css → styles-CsEMfdaS.css} +1 -1
  43. package/dist/public/assets/{terminal-runtime-meta-BMT-rSEe.js → terminal-runtime-meta-cdtWVfCm.js} +1 -1
  44. package/dist/public/assets/{useRegisteredDebugTemplates-zMcEOGca.js → useRegisteredDebugTemplates-oFAQNIqh.js} +1 -1
  45. package/dist/public/assets/window-BVUB8gMK.js +1 -0
  46. package/dist/public/index.html +2 -2
  47. package/dist/server/config/env.d.ts +2 -0
  48. package/dist/server/config/env.js +39 -0
  49. package/dist/server/config/env.js.map +1 -1
  50. package/dist/server/modules/client/npm-global-package-service.d.ts +7 -1
  51. package/dist/server/modules/client/npm-global-package-service.js +149 -43
  52. package/dist/server/modules/client/npm-global-package-service.js.map +1 -1
  53. package/dist/server/modules/client/service-update-task-service.js +6 -2
  54. package/dist/server/modules/client/service-update-task-service.js.map +1 -1
  55. package/dist/server/modules/client/service-update-types.d.ts +2 -0
  56. package/dist/server/modules/git/git-controller.d.ts +3 -0
  57. package/dist/server/modules/git/git-controller.js +3 -0
  58. package/dist/server/modules/git/git-controller.js.map +1 -1
  59. package/dist/server/modules/git/git-read-service.js +47 -1
  60. package/dist/server/modules/git/git-read-service.js.map +1 -1
  61. package/dist/server/modules/git/git-write-service.d.ts +4 -0
  62. package/dist/server/modules/git/git-write-service.js +24 -0
  63. package/dist/server/modules/git/git-write-service.js.map +1 -1
  64. package/dist/server/modules/git/types.d.ts +1 -0
  65. package/dist/server/modules/git/workspace-repo-guard.d.ts +2 -0
  66. package/dist/server/modules/git/workspace-repo-guard.js +24 -10
  67. package/dist/server/modules/git/workspace-repo-guard.js.map +1 -1
  68. package/dist/server/modules/parallel-sessions/parallel-session-controller.d.ts +53 -0
  69. package/dist/server/modules/parallel-sessions/parallel-session-controller.js +70 -0
  70. package/dist/server/modules/parallel-sessions/parallel-session-controller.js.map +1 -0
  71. package/dist/server/modules/parallel-sessions/parallel-session-group-service.d.ts +83 -0
  72. package/dist/server/modules/parallel-sessions/parallel-session-group-service.js +591 -0
  73. package/dist/server/modules/parallel-sessions/parallel-session-group-service.js.map +1 -0
  74. package/dist/server/modules/parallel-sessions/session-isolated-workspace-service.d.ts +56 -0
  75. package/dist/server/modules/parallel-sessions/session-isolated-workspace-service.js +483 -0
  76. package/dist/server/modules/parallel-sessions/session-isolated-workspace-service.js.map +1 -0
  77. package/dist/server/modules/relay-tunnel/crypto/relay-tunnel-packets.d.ts +16 -1
  78. package/dist/server/modules/relay-tunnel/crypto/relay-tunnel-packets.js.map +1 -1
  79. package/dist/server/modules/relay-tunnel/crypto/relay-tunnel-protocol.d.ts +2 -1
  80. package/dist/server/modules/relay-tunnel/crypto/relay-tunnel-protocol.js +18 -0
  81. package/dist/server/modules/relay-tunnel/crypto/relay-tunnel-protocol.js.map +1 -1
  82. package/dist/server/modules/relay-tunnel/relay-tunnel-candidate-endpoints.d.ts +2 -0
  83. package/dist/server/modules/relay-tunnel/relay-tunnel-candidate-endpoints.js +129 -0
  84. package/dist/server/modules/relay-tunnel/relay-tunnel-candidate-endpoints.js.map +1 -0
  85. package/dist/server/modules/relay-tunnel/relay-tunnel-client-context.d.ts +13 -0
  86. package/dist/server/modules/relay-tunnel/relay-tunnel-client-context.js +2 -0
  87. package/dist/server/modules/relay-tunnel/relay-tunnel-client-context.js.map +1 -0
  88. package/dist/server/modules/relay-tunnel/relay-tunnel-gateway-service.d.ts +6 -0
  89. package/dist/server/modules/relay-tunnel/relay-tunnel-gateway-service.js +110 -10
  90. package/dist/server/modules/relay-tunnel/relay-tunnel-gateway-service.js.map +1 -1
  91. package/dist/server/modules/relay-tunnel/relay-tunnel-runtime-adapter.d.ts +16 -4
  92. package/dist/server/modules/relay-tunnel/relay-tunnel-runtime-adapter.js +220 -102
  93. package/dist/server/modules/relay-tunnel/relay-tunnel-runtime-adapter.js.map +1 -1
  94. package/dist/server/modules/relay-tunnel/relay-tunnel-service.d.ts +4 -1
  95. package/dist/server/modules/relay-tunnel/relay-tunnel-service.js +257 -162
  96. package/dist/server/modules/relay-tunnel/relay-tunnel-service.js.map +1 -1
  97. package/dist/server/modules/sessions/codex-app-server-helper-client.d.ts +3 -0
  98. package/dist/server/modules/sessions/codex-app-server-helper-client.js +56 -45
  99. package/dist/server/modules/sessions/codex-app-server-helper-client.js.map +1 -1
  100. package/dist/server/modules/sessions/codex-app-server-helper-process.js +21 -2
  101. package/dist/server/modules/sessions/codex-app-server-helper-process.js.map +1 -1
  102. package/dist/server/modules/sessions/session-activity-inspector.js +6 -8
  103. package/dist/server/modules/sessions/session-activity-inspector.js.map +1 -1
  104. package/dist/server/modules/sessions/session-history-service.d.ts +11 -1
  105. package/dist/server/modules/sessions/session-history-service.js +204 -21
  106. package/dist/server/modules/sessions/session-history-service.js.map +1 -1
  107. package/dist/server/modules/sessions/session-live-runtime-service.d.ts +8 -0
  108. package/dist/server/modules/sessions/session-live-runtime-service.js +208 -25
  109. package/dist/server/modules/sessions/session-live-runtime-service.js.map +1 -1
  110. package/dist/server/modules/workbench/codex-archive-watcher.d.ts +16 -0
  111. package/dist/server/modules/workbench/codex-archive-watcher.js +50 -0
  112. package/dist/server/modules/workbench/codex-archive-watcher.js.map +1 -0
  113. package/dist/server/modules/workbench/workbench-service.d.ts +43 -4
  114. package/dist/server/modules/workbench/workbench-service.js +72 -9
  115. package/dist/server/modules/workbench/workbench-service.js.map +1 -1
  116. package/dist/server/modules/workbench/workspace-panel-snapshot-service.d.ts +1 -1
  117. package/dist/server/modules/workbench/workspace-panel-snapshot-service.js +26 -3
  118. package/dist/server/modules/workbench/workspace-panel-snapshot-service.js.map +1 -1
  119. package/dist/server/modules/workspace/workspace-service.d.ts +3 -1
  120. package/dist/server/modules/workspace/workspace-service.js +10 -2
  121. package/dist/server/modules/workspace/workspace-service.js.map +1 -1
  122. package/dist/server/modules/worktree/worktree-base-ref-resolver.d.ts +20 -0
  123. package/dist/server/modules/worktree/worktree-base-ref-resolver.js +111 -0
  124. package/dist/server/modules/worktree/worktree-base-ref-resolver.js.map +1 -0
  125. package/dist/server/modules/worktree/worktree-cleanup-service.js +9 -3
  126. package/dist/server/modules/worktree/worktree-cleanup-service.js.map +1 -1
  127. package/dist/server/modules/worktree/worktree-manager.d.ts +0 -1
  128. package/dist/server/modules/worktree/worktree-manager.js +14 -20
  129. package/dist/server/modules/worktree/worktree-manager.js.map +1 -1
  130. package/dist/server/routes/git.js +1 -0
  131. package/dist/server/routes/git.js.map +1 -1
  132. package/dist/server/routes/parallel-groups.d.ts +3 -0
  133. package/dist/server/routes/parallel-groups.js +9 -0
  134. package/dist/server/routes/parallel-groups.js.map +1 -0
  135. package/dist/server/server/create-server.d.ts +8 -0
  136. package/dist/server/server/create-server.js +48 -9
  137. package/dist/server/server/create-server.js.map +1 -1
  138. package/dist/server/server/workbench-runtime-terminal-sync.d.ts +14 -0
  139. package/dist/server/server/workbench-runtime-terminal-sync.js +17 -0
  140. package/dist/server/server/workbench-runtime-terminal-sync.js.map +1 -0
  141. package/dist/server/storage/repositories/parallel-session-group-repository.d.ts +11 -0
  142. package/dist/server/storage/repositories/parallel-session-group-repository.js +131 -0
  143. package/dist/server/storage/repositories/parallel-session-group-repository.js.map +1 -0
  144. package/dist/server/storage/repositories/parallel-session-member-repository.d.ts +12 -0
  145. package/dist/server/storage/repositories/parallel-session-member-repository.js +150 -0
  146. package/dist/server/storage/repositories/parallel-session-member-repository.js.map +1 -0
  147. package/dist/server/storage/repositories/session-isolated-workspace-repository.d.ts +15 -0
  148. package/dist/server/storage/repositories/session-isolated-workspace-repository.js +230 -0
  149. package/dist/server/storage/repositories/session-isolated-workspace-repository.js.map +1 -0
  150. package/dist/server/storage/sqlite/schema.sql +73 -0
  151. package/dist/server/types/domain.d.ts +72 -0
  152. package/dist/server/ws/workbench-ws-hub.d.ts +3 -1
  153. package/dist/server/ws/workbench-ws-hub.js +189 -20
  154. package/dist/server/ws/workbench-ws-hub.js.map +1 -1
  155. package/dist/server/ws/ws-server.js +141 -8
  156. package/dist/server/ws/ws-server.js.map +1 -1
  157. package/node_modules/@codingns/session-sync-core/dist/providers/codex.d.ts +1 -0
  158. package/node_modules/@codingns/session-sync-core/dist/providers/codex.js +67 -6
  159. package/node_modules/@codingns/session-sync-core/dist/providers/codex.js.map +1 -1
  160. package/node_modules/@codingns/session-sync-core/dist/runtime/active-run-registry.d.ts +3 -0
  161. package/node_modules/@codingns/session-sync-core/dist/runtime/active-run-registry.js +46 -22
  162. package/node_modules/@codingns/session-sync-core/dist/runtime/active-run-registry.js.map +1 -1
  163. package/node_modules/@codingns/session-sync-core/dist/runtime/codex-runtime.js +558 -309
  164. package/node_modules/@codingns/session-sync-core/dist/runtime/codex-runtime.js.map +1 -1
  165. package/node_modules/@codingns/session-sync-core/dist/runtime/opencode-runtime.js +29 -5
  166. package/node_modules/@codingns/session-sync-core/dist/runtime/opencode-runtime.js.map +1 -1
  167. package/package.json +1 -1
  168. package/dist/public/assets/App-DUAg5urj.css +0 -1
  169. package/dist/public/assets/App-WOLwMld_.js +0 -30
  170. package/dist/public/assets/ConversationPage-D9pzRmOg.js +0 -2
  171. package/dist/public/assets/DesktopWindowPage-D8FpOSLE.js +0 -2
  172. package/dist/public/assets/FileContextPanel-C8T7oqRN.js +0 -1
  173. package/dist/public/assets/GitSidebar-Bze7DNnc.js +0 -6
  174. package/dist/public/assets/MobileSheet-Gzc14EpR.js +0 -1
  175. package/dist/public/assets/MobileWorkspaceSwitcherHeader-DOr4pTUq.js +0 -1
  176. package/dist/public/assets/ServerSettingsModal-BYB0GvTl.js +0 -1
  177. package/dist/public/assets/SessionIndexPage-CR3IARXX.js +0 -1
  178. package/dist/public/assets/SettingsPage-B_BQtnwE.js +0 -1
  179. package/dist/public/assets/TerminalManagerPanel-PQ-EM64j.js +0 -1
  180. package/dist/public/assets/ToolFilesPage-Qzkc6K2I.js +0 -1
  181. package/dist/public/assets/ToolGitPage-BdNDN-cV.js +0 -1
  182. package/dist/public/assets/ToolProcessesPage-EXJ9DHWI.js +0 -1
  183. package/dist/public/assets/ToolsHomePage-CjF3CWzR.js +0 -1
  184. package/dist/public/assets/WorkbenchLandingPage-DZPk4SmX.js +0 -1
  185. package/dist/public/assets/WorkbenchLayout-rwQib5In.js +0 -3
  186. package/dist/public/assets/WorkbenchShellRoute-Cerk5uK7.js +0 -1
  187. package/dist/public/assets/WorkspaceDebugDetailPage-Bcq8s-Ma.js +0 -1
  188. package/dist/public/assets/WorkspaceDetailPage-DNAa8pKr.js +0 -1
  189. package/dist/public/assets/WorkspaceHomePage-BoiLuACV.js +0 -1
  190. package/dist/public/assets/client-runtime-manager-CRQ-F5d2.js +0 -1
  191. package/dist/public/assets/file-tree-icon-Dp_xhVfD.js +0 -31
  192. package/dist/public/assets/index-C2G8Gmf1.js +0 -42
  193. package/dist/public/assets/index-CpPTUeA3.css +0 -1
  194. package/dist/public/assets/session-runtime-machine-Dq3pW-UF.js +0 -17
  195. package/dist/public/assets/window-BWqRixxq.js +0 -1
  196. /package/dist/public/assets/{styles-CSUx5LGe.js → styles-DRVvx_kv.js} +0 -0
@@ -187,6 +187,7 @@ export declare class SessionLiveRuntimeService {
187
187
  private readonly sessionPermissionRequestService;
188
188
  private readonly runtimeAdapterDisposables;
189
189
  private readonly externalRuntimeSnapshots;
190
+ private readonly externalRuntimeInterruptSuppressions;
190
191
  private readonly runtimeListeners;
191
192
  private readonly terminalStateListeners;
192
193
  private readonly deadRuntimeReconciliationSessions;
@@ -246,6 +247,8 @@ export declare class SessionLiveRuntimeService {
246
247
  private buildBindingSnapshot;
247
248
  private persistRuntimeEvent;
248
249
  private emitTerminalStateEvent;
250
+ private reconcileTerminalRuntimeSnapshot;
251
+ private forceInterruptInactiveSession;
249
252
  private beginPendingSendDebugTrace;
250
253
  private logSendDebugStep;
251
254
  private markSendDebugResponseReady;
@@ -265,6 +268,11 @@ export declare class SessionLiveRuntimeService {
265
268
  private upsertSnapshot;
266
269
  private shouldIgnoreClaudeExternalRuntimeUpdate;
267
270
  private clearExternalRuntimeSnapshot;
271
+ private getFreshExternalRuntimeSnapshot;
272
+ private suppressInterruptedExternalRuntime;
273
+ private clearExternalRuntimeInterruptSuppression;
274
+ private shouldIgnoreInterruptedExternalRuntime;
275
+ private forceInterruptExternalSession;
268
276
  private resolveActiveClaudePermissionSession;
269
277
  }
270
278
  export {};
@@ -14,6 +14,8 @@ import { ClaudeRuntimeHelperAdapter } from "./claude-runtime-helper-client.js";
14
14
  import { CodexAppServerHelperClient } from "./codex-app-server-helper-client.js";
15
15
  const RUNTIME_START_BINDING_WAIT_TIMEOUT_MS = 10_000;
16
16
  const START_BINDING_POLL_INTERVAL_MS = 50;
17
+ const EXTERNAL_RUNTIME_INTERRUPT_SUPPRESSION_MS = 30_000;
18
+ const EXTERNAL_RUNTIME_SNAPSHOT_MAX_AGE_MS = 90_000;
17
19
  export class SessionLiveRuntimeService {
18
20
  sessionHistoryService;
19
21
  sessionMessageAttachmentService;
@@ -31,6 +33,7 @@ export class SessionLiveRuntimeService {
31
33
  sessionPermissionRequestService;
32
34
  runtimeAdapterDisposables;
33
35
  externalRuntimeSnapshots = new Map();
36
+ externalRuntimeInterruptSuppressions = new Map();
34
37
  runtimeListeners = new Map();
35
38
  terminalStateListeners = new Set();
36
39
  deadRuntimeReconciliationSessions = new Set();
@@ -435,7 +438,7 @@ export class SessionLiveRuntimeService {
435
438
  async getSessionRuntime(sessionId, userId) {
436
439
  const runtimeSessionId = this.resolveRuntimeSessionId(sessionId);
437
440
  const runtimeSnapshot = this.getLiveRuntimeSnapshot(runtimeSessionId);
438
- const externalRuntimeSnapshot = this.externalRuntimeSnapshots.get(runtimeSessionId) ?? null;
441
+ const externalRuntimeSnapshot = this.getFreshExternalRuntimeSnapshot(runtimeSessionId);
439
442
  const runtimeHasActiveRun = runtimeSnapshot ? isActiveRuntimeState(runtimeSnapshot.runningState) : false;
440
443
  const externalHasActiveRun = externalRuntimeSnapshot
441
444
  ? isActiveRuntimeState(externalRuntimeSnapshot.runningState)
@@ -485,7 +488,7 @@ export class SessionLiveRuntimeService {
485
488
  runningState: resolution.runningState,
486
489
  hasActiveRun: externalHasActiveRun,
487
490
  canAttach: false,
488
- canInterrupt: false,
491
+ canInterrupt: externalHasActiveRun,
489
492
  inRunInputMode: capabilities.inRunInputMode,
490
493
  activityResolutionSource: resolution.activityResolutionSource,
491
494
  activityConfidence: resolution.activityConfidence,
@@ -528,32 +531,59 @@ export class SessionLiveRuntimeService {
528
531
  if (runtimeSnapshot) {
529
532
  return createRuntimeActivityObservation(runtimeSessionId, runtimeSnapshot);
530
533
  }
531
- const externalRuntimeSnapshot = this.externalRuntimeSnapshots.get(runtimeSessionId) ?? null;
534
+ const externalRuntimeSnapshot = this.getFreshExternalRuntimeSnapshot(runtimeSessionId);
532
535
  if (externalRuntimeSnapshot) {
533
536
  return createExternalRuntimeActivityObservation(runtimeSessionId, externalRuntimeSnapshot);
534
537
  }
535
538
  return null;
536
539
  }
537
540
  async interruptSession(sessionId, userId) {
538
- this.sessionHistoryService.getSession(sessionId, userId);
541
+ const session = this.sessionHistoryService.getSession(sessionId, userId);
539
542
  const runtimeSessionId = this.resolveRuntimeSessionId(sessionId);
540
543
  const runtime = this.getLiveRuntimeSnapshot(runtimeSessionId);
541
- const externalRuntimeSnapshot = this.externalRuntimeSnapshots.get(runtimeSessionId) ?? null;
544
+ const externalRuntimeSnapshot = this.getFreshExternalRuntimeSnapshot(runtimeSessionId);
542
545
  if (!runtime || (runtime.runningState !== "running" && runtime.runningState !== "starting")) {
546
+ if (runtime && !isActiveRuntimeState(runtime.runningState)) {
547
+ await this.reconcileTerminalRuntimeSnapshot(sessionId, runtime);
548
+ return {
549
+ sessionId,
550
+ interrupted: true,
551
+ detail: runtime.detail ?? "当前会话已结束,已自动同步状态"
552
+ };
553
+ }
543
554
  if (externalRuntimeSnapshot && isActiveRuntimeState(externalRuntimeSnapshot.runningState)) {
544
- throw new AppError({
545
- statusCode: 409,
546
- errorCode: "CAPABILITY_NOT_SUPPORTED",
547
- detail: "当前 Claude 外部运行仍在进行,但现有链路不支持中断",
548
- field: "sessionId"
549
- });
555
+ const refreshedSession = await Promise.resolve(this.sessionHistoryService.refreshRuntimeFallbackSession(sessionId, userId)).catch(() => null);
556
+ if (refreshedSession && !isPendingSessionRunningState(refreshedSession.runningState)) {
557
+ await this.forceInterruptInactiveSession(sessionId);
558
+ return {
559
+ sessionId,
560
+ interrupted: true,
561
+ detail: "当前会话已停止,已自动同步状态"
562
+ };
563
+ }
564
+ await this.forceInterruptExternalSession(sessionId);
565
+ return {
566
+ sessionId,
567
+ interrupted: true,
568
+ detail: "Claude 外部运行当前无法直接中断,已强制清理本地运行状态"
569
+ };
550
570
  }
551
- throw new AppError({
552
- statusCode: 409,
553
- errorCode: "SESSION_NOT_RUNNING",
554
- detail: "当前会话不在运行中,无法中断",
555
- field: "sessionId"
556
- });
571
+ const refreshedSession = isPendingSessionRunningState(session.runningState)
572
+ ? await Promise.resolve(this.sessionHistoryService.refreshRuntimeFallbackSession(sessionId, userId)).catch(() => null)
573
+ : session;
574
+ if (refreshedSession && !isPendingSessionRunningState(refreshedSession.runningState)) {
575
+ return {
576
+ sessionId,
577
+ interrupted: true,
578
+ detail: "当前会话已停止,已自动同步状态"
579
+ };
580
+ }
581
+ await this.forceInterruptInactiveSession(sessionId);
582
+ return {
583
+ sessionId,
584
+ interrupted: true,
585
+ detail: "当前会话已停止,已自动修正残留运行状态"
586
+ };
557
587
  }
558
588
  const interrupted = await this.providerRuntimeService.interrupt(runtimeSessionId).catch((error) => {
559
589
  if (error instanceof Error && error.message === "INTERRUPT_NOT_SUPPORTED") {
@@ -589,7 +619,7 @@ export class SessionLiveRuntimeService {
589
619
  subscribeRuntime(sessionId, onEnvelope) {
590
620
  const runtimeSessionId = this.resolveRuntimeSessionId(sessionId);
591
621
  const runtimeSnapshot = this.getLiveRuntimeSnapshot(runtimeSessionId);
592
- const externalRuntimeSnapshot = this.externalRuntimeSnapshots.get(runtimeSessionId) ?? null;
622
+ const externalRuntimeSnapshot = this.getFreshExternalRuntimeSnapshot(runtimeSessionId);
593
623
  const initialActivityEnvelope = this.buildSessionActivityEnvelope(sessionId, runtimeSessionId);
594
624
  if (runtimeSnapshot) {
595
625
  void onEnvelope({
@@ -767,13 +797,13 @@ export class SessionLiveRuntimeService {
767
797
  sessionId
768
798
  };
769
799
  }
770
- const externalRuntimeSnapshot = this.externalRuntimeSnapshots.get(runtimeSessionId) ?? null;
800
+ const externalRuntimeSnapshot = this.getFreshExternalRuntimeSnapshot(runtimeSessionId);
771
801
  if (externalRuntimeSnapshot) {
772
802
  const resolution = this.sessionActivityAuthorityService.observe(createExternalRuntimeActivityObservation(runtimeSessionId, externalRuntimeSnapshot));
773
803
  return {
774
804
  ...this.mapResolutionToActivityEnvelope(resolution, {
775
805
  hasActiveRun: isActiveRuntimeState(externalRuntimeSnapshot.runningState),
776
- canInterrupt: false
806
+ canInterrupt: isActiveRuntimeState(externalRuntimeSnapshot.runningState)
777
807
  }),
778
808
  sessionId
779
809
  };
@@ -792,7 +822,7 @@ export class SessionLiveRuntimeService {
792
822
  }
793
823
  resolveRuntimeSessionId(sessionId) {
794
824
  if (this.providerRuntimeService.getSnapshot(sessionId)
795
- || this.externalRuntimeSnapshots.has(sessionId)) {
825
+ || this.getFreshExternalRuntimeSnapshot(sessionId)) {
796
826
  return sessionId;
797
827
  }
798
828
  const listSnapshots = "listSnapshots" in this.providerRuntimeService
@@ -898,6 +928,12 @@ export class SessionLiveRuntimeService {
898
928
  };
899
929
  }
900
930
  async applyExternalRuntimeUpdate(input) {
931
+ if (input.runningState === "running" && this.shouldIgnoreInterruptedExternalRuntime(input.sessionId)) {
932
+ return;
933
+ }
934
+ if (input.runningState !== "running") {
935
+ this.clearExternalRuntimeInterruptSuppression(input.sessionId);
936
+ }
901
937
  const userIds = this.authUserRepository.listIds();
902
938
  if (userIds.length === 0) {
903
939
  return;
@@ -994,6 +1030,7 @@ export class SessionLiveRuntimeService {
994
1030
  async startRuntimeRun(request, userId, mode) {
995
1031
  this.runtimeMessageSeenSessions.delete(request.sessionId);
996
1032
  this.runtimeHistoryFallbackSentSessions.delete(request.sessionId);
1033
+ this.clearExternalRuntimeInterruptSuppression(request.sessionId);
997
1034
  if (request.provider === "claude-code") {
998
1035
  this.clearExternalRuntimeSnapshot(request.sessionId);
999
1036
  }
@@ -1061,7 +1098,7 @@ export class SessionLiveRuntimeService {
1061
1098
  };
1062
1099
  const runtimeSessionId = this.resolveRuntimeSessionId(input.sessionId);
1063
1100
  const activeRun = this.getLiveRuntimeSnapshot(runtimeSessionId);
1064
- const externalRuntimeSnapshot = this.externalRuntimeSnapshots.get(runtimeSessionId);
1101
+ const externalRuntimeSnapshot = this.getFreshExternalRuntimeSnapshot(runtimeSessionId);
1065
1102
  if (activeRun &&
1066
1103
  activeRun.provider === "claude-code" &&
1067
1104
  isActiveRuntimeState(activeRun.runningState)) {
@@ -1156,7 +1193,7 @@ export class SessionLiveRuntimeService {
1156
1193
  this.queueDispatchSessions.add(sessionId);
1157
1194
  try {
1158
1195
  const runtimeSnapshot = this.getLiveRuntimeSnapshot(sessionId);
1159
- const externalRuntimeSnapshot = this.externalRuntimeSnapshots.get(sessionId);
1196
+ const externalRuntimeSnapshot = this.getFreshExternalRuntimeSnapshot(sessionId);
1160
1197
  if ((runtimeSnapshot && isActiveRuntimeState(runtimeSnapshot.runningState))
1161
1198
  || (externalRuntimeSnapshot && isActiveRuntimeState(externalRuntimeSnapshot.runningState))) {
1162
1199
  return;
@@ -1222,7 +1259,7 @@ export class SessionLiveRuntimeService {
1222
1259
  if (runtimeSnapshot && isActiveRuntimeState(runtimeSnapshot.runningState)) {
1223
1260
  return true;
1224
1261
  }
1225
- const externalRuntimeSnapshot = this.externalRuntimeSnapshots.get(session.sessionId);
1262
+ const externalRuntimeSnapshot = this.getFreshExternalRuntimeSnapshot(session.sessionId);
1226
1263
  if (externalRuntimeSnapshot && isActiveRuntimeState(externalRuntimeSnapshot.runningState)) {
1227
1264
  return true;
1228
1265
  }
@@ -1259,7 +1296,7 @@ export class SessionLiveRuntimeService {
1259
1296
  return session;
1260
1297
  }
1261
1298
  const runtimeSnapshot = this.getLiveRuntimeSnapshot(sessionId);
1262
- const externalRuntimeSnapshot = this.externalRuntimeSnapshots.get(sessionId);
1299
+ const externalRuntimeSnapshot = this.getFreshExternalRuntimeSnapshot(sessionId);
1263
1300
  if ((runtimeSnapshot && isActiveRuntimeState(runtimeSnapshot.runningState))
1264
1301
  || (externalRuntimeSnapshot && isActiveRuntimeState(externalRuntimeSnapshot.runningState))) {
1265
1302
  return session;
@@ -1482,6 +1519,112 @@ export class SessionLiveRuntimeService {
1482
1519
  await listener(event);
1483
1520
  }
1484
1521
  }
1522
+ async reconcileTerminalRuntimeSnapshot(sessionId, runtime) {
1523
+ const timestamp = runtime.lastEventAt ?? runtime.completedAt ?? runtime.startedAt;
1524
+ const runningState = toStoredRunningState(runtime.runningState);
1525
+ const currentSnapshot = this.sessionStatusSnapshotRepository.findBySessionId(sessionId);
1526
+ for (const userId of this.authUserRepository.listIds()) {
1527
+ const current = this.sessionStateRepository.findBySessionAndUser(sessionId, userId);
1528
+ if (current?.lastEventAt
1529
+ && current.lastEventAt.localeCompare(timestamp) > 0
1530
+ && isTerminalSessionRunningState(current.runningState)) {
1531
+ continue;
1532
+ }
1533
+ this.sessionStateRepository.upsert({
1534
+ sessionId,
1535
+ userId,
1536
+ runningState,
1537
+ activitySource: "runtime",
1538
+ favorite: current?.favorite ?? false,
1539
+ lastEventAt: timestamp,
1540
+ completedAt: isTerminalSessionRunningState(runningState)
1541
+ ? (runtime.completedAt ?? timestamp)
1542
+ : current?.completedAt ?? null,
1543
+ lastSeenAt: current?.lastSeenAt ?? null,
1544
+ updatedAt: nowIso()
1545
+ });
1546
+ }
1547
+ this.upsertSnapshot(sessionId, {
1548
+ syncStatus: runningState === "failed" ? "error" : "idle",
1549
+ syncCursor: currentSnapshot?.syncCursor ?? null,
1550
+ lastSyncAt: timestamp,
1551
+ lastErrorCode: runningState === "failed" ? runtime.errorCode ?? null : null,
1552
+ lastErrorDetail: runningState === "failed" ? runtime.detail ?? null : null,
1553
+ resumedAt: currentSnapshot?.resumedAt ?? null
1554
+ });
1555
+ this.sessionActivityAuthorityService.observe(createRuntimeActivityObservation(sessionId, runtime));
1556
+ await this.emitExternalRuntimeEnvelope({
1557
+ type: "session.runtime_status",
1558
+ sessionId,
1559
+ status: runtime.runningState,
1560
+ detail: runtime.detail,
1561
+ interruptSource: runtime.interruptSource,
1562
+ timestamp
1563
+ });
1564
+ if (isTerminalSessionRunningState(runningState)) {
1565
+ await this.emitTerminalStateEvent({
1566
+ sessionId,
1567
+ status: runningState,
1568
+ timestamp,
1569
+ detail: runtime.detail,
1570
+ source: "runtime"
1571
+ });
1572
+ void this.dispatchNextQueuedMessage(sessionId);
1573
+ }
1574
+ }
1575
+ async forceInterruptInactiveSession(sessionId) {
1576
+ const timestamp = nowIso();
1577
+ const currentSnapshot = this.sessionStatusSnapshotRepository.findBySessionId(sessionId);
1578
+ for (const userId of this.authUserRepository.listIds()) {
1579
+ const current = this.sessionStateRepository.findBySessionAndUser(sessionId, userId);
1580
+ if (!current || !isPendingSessionRunningState(current.runningState)) {
1581
+ continue;
1582
+ }
1583
+ this.sessionStateRepository.upsert({
1584
+ ...current,
1585
+ runningState: "interrupted",
1586
+ activitySource: "runtime",
1587
+ completedAt: timestamp,
1588
+ updatedAt: timestamp
1589
+ });
1590
+ }
1591
+ this.clearExternalRuntimeSnapshot(sessionId);
1592
+ this.upsertSnapshot(sessionId, {
1593
+ syncStatus: "idle",
1594
+ syncCursor: currentSnapshot?.syncCursor ?? null,
1595
+ lastSyncAt: timestamp,
1596
+ lastErrorCode: null,
1597
+ lastErrorDetail: null,
1598
+ resumedAt: currentSnapshot?.resumedAt ?? null
1599
+ });
1600
+ this.sessionActivityAuthorityService.observe({
1601
+ sessionId,
1602
+ runId: null,
1603
+ runningState: "interrupted",
1604
+ source: "authoritative_runtime",
1605
+ confidence: "strong",
1606
+ detail: "检测到会话实际已停止,已自动修正残留运行状态",
1607
+ interruptSource: "runtime",
1608
+ errorCode: null,
1609
+ observedAt: timestamp
1610
+ });
1611
+ await this.emitExternalRuntimeEnvelope({
1612
+ type: "session.runtime_status",
1613
+ sessionId,
1614
+ status: "interrupted",
1615
+ detail: "检测到会话实际已停止,已自动修正残留运行状态",
1616
+ interruptSource: "runtime",
1617
+ timestamp
1618
+ });
1619
+ await this.emitTerminalStateEvent({
1620
+ sessionId,
1621
+ status: "interrupted",
1622
+ timestamp,
1623
+ detail: "检测到会话实际已停止,已自动修正残留运行状态",
1624
+ source: "runtime"
1625
+ });
1626
+ void this.dispatchNextQueuedMessage(sessionId);
1627
+ }
1485
1628
  beginPendingSendDebugTrace(input) {
1486
1629
  if (!isPerfDebugEnabled()) {
1487
1630
  return null;
@@ -1764,6 +1907,39 @@ export class SessionLiveRuntimeService {
1764
1907
  clearExternalRuntimeSnapshot(sessionId) {
1765
1908
  this.externalRuntimeSnapshots.delete(sessionId);
1766
1909
  }
1910
+ getFreshExternalRuntimeSnapshot(sessionId) {
1911
+ const snapshot = this.externalRuntimeSnapshots.get(sessionId) ?? null;
1912
+ if (!snapshot) {
1913
+ return null;
1914
+ }
1915
+ if (!isExternalRuntimeSnapshotExpired(snapshot)) {
1916
+ return snapshot;
1917
+ }
1918
+ this.clearExternalRuntimeSnapshot(sessionId);
1919
+ this.sessionActivityAuthorityService.clearSession(sessionId);
1920
+ return null;
1921
+ }
1922
+ suppressInterruptedExternalRuntime(sessionId) {
1923
+ this.externalRuntimeInterruptSuppressions.set(sessionId, Date.now() + EXTERNAL_RUNTIME_INTERRUPT_SUPPRESSION_MS);
1924
+ }
1925
+ clearExternalRuntimeInterruptSuppression(sessionId) {
1926
+ this.externalRuntimeInterruptSuppressions.delete(sessionId);
1927
+ }
1928
+ shouldIgnoreInterruptedExternalRuntime(sessionId) {
1929
+ const expiresAt = this.externalRuntimeInterruptSuppressions.get(sessionId);
1930
+ if (!expiresAt) {
1931
+ return false;
1932
+ }
1933
+ if (Date.now() >= expiresAt) {
1934
+ this.externalRuntimeInterruptSuppressions.delete(sessionId);
1935
+ return false;
1936
+ }
1937
+ return true;
1938
+ }
1939
+ async forceInterruptExternalSession(sessionId) {
1940
+ this.suppressInterruptedExternalRuntime(sessionId);
1941
+ await this.forceInterruptInactiveSession(sessionId);
1942
+ }
1767
1943
  async resolveActiveClaudePermissionSession(input) {
1768
1944
  const activeSnapshots = this.providerRuntimeService
1769
1945
  .listSnapshots()
@@ -1835,6 +2011,13 @@ function createExternalRuntimeActivityObservation(sessionId, snapshot) {
1835
2011
  observedAt: snapshot.updatedAt
1836
2012
  };
1837
2013
  }
2014
+ function isExternalRuntimeSnapshotExpired(snapshot) {
2015
+ const updatedAtMs = Date.parse(snapshot.updatedAt);
2016
+ if (!Number.isFinite(updatedAtMs)) {
2017
+ return true;
2018
+ }
2019
+ return Date.now() - updatedAtMs > EXTERNAL_RUNTIME_SNAPSHOT_MAX_AGE_MS;
2020
+ }
1838
2021
  function createRuntimeEventObservation(sessionId, event, startedAt) {
1839
2022
  return {
1840
2023
  sessionId,