@jingyi0605/codingns 0.1.1 → 0.1.3

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 (251) hide show
  1. package/README.md +14 -0
  2. package/dist/public/assets/{TerminalPage-CVG1cGAJ.js → TerminalPage-Nq5sPc5c.js} +19 -19
  3. package/dist/public/assets/index-9hnprhO7.css +1 -0
  4. package/dist/public/assets/index-BTpmuKhG.js +108 -0
  5. package/dist/public/index.html +2 -2
  6. package/dist/server/config/env.js +24 -3
  7. package/dist/server/config/env.js.map +1 -1
  8. package/dist/server/config/opencode-base-url-resolver.d.ts +13 -8
  9. package/dist/server/config/opencode-base-url-resolver.js +117 -147
  10. package/dist/server/config/opencode-base-url-resolver.js.map +1 -1
  11. package/dist/server/config/opencode-system-probe-helper-client.d.ts +18 -0
  12. package/dist/server/config/opencode-system-probe-helper-client.js +127 -0
  13. package/dist/server/config/opencode-system-probe-helper-client.js.map +1 -0
  14. package/dist/server/config/opencode-system-probe-helper-process.d.ts +1 -0
  15. package/dist/server/config/opencode-system-probe-helper-process.js +208 -0
  16. package/dist/server/config/opencode-system-probe-helper-process.js.map +1 -0
  17. package/dist/server/modules/git/git-command-helper-client.d.ts +25 -0
  18. package/dist/server/modules/git/git-command-helper-client.js +143 -0
  19. package/dist/server/modules/git/git-command-helper-client.js.map +1 -0
  20. package/dist/server/modules/git/git-command-helper-process.d.ts +1 -0
  21. package/dist/server/modules/git/git-command-helper-process.js +237 -0
  22. package/dist/server/modules/git/git-command-helper-process.js.map +1 -0
  23. package/dist/server/modules/git/git-command-runner.d.ts +8 -0
  24. package/dist/server/modules/git/git-command-runner.js +77 -6
  25. package/dist/server/modules/git/git-command-runner.js.map +1 -1
  26. package/dist/server/modules/git/git-controller.d.ts +4 -0
  27. package/dist/server/modules/git/git-controller.js +4 -1
  28. package/dist/server/modules/git/git-controller.js.map +1 -1
  29. package/dist/server/modules/git/git-read-service.d.ts +2 -1
  30. package/dist/server/modules/git/git-read-service.js +30 -0
  31. package/dist/server/modules/git/git-read-service.js.map +1 -1
  32. package/dist/server/modules/git/git-write-service.d.ts +1 -1
  33. package/dist/server/modules/git/git-write-service.js +8 -7
  34. package/dist/server/modules/git/git-write-service.js.map +1 -1
  35. package/dist/server/modules/git/types.d.ts +5 -0
  36. package/dist/server/modules/preferences/common.d.ts +2 -0
  37. package/dist/server/modules/preferences/common.js +13 -0
  38. package/dist/server/modules/preferences/common.js.map +1 -0
  39. package/dist/server/modules/preferences/profile-controller.d.ts +11 -0
  40. package/dist/server/modules/preferences/profile-controller.js +14 -0
  41. package/dist/server/modules/preferences/profile-controller.js.map +1 -0
  42. package/dist/server/modules/preferences/profile-service.d.ts +17 -0
  43. package/dist/server/modules/preferences/profile-service.js +213 -0
  44. package/dist/server/modules/preferences/profile-service.js.map +1 -0
  45. package/dist/server/modules/preferences/quick-phrase-controller.js +2 -12
  46. package/dist/server/modules/preferences/quick-phrase-controller.js.map +1 -1
  47. package/dist/server/modules/provider/codex-model-options.js +26 -165
  48. package/dist/server/modules/provider/codex-model-options.js.map +1 -1
  49. package/dist/server/modules/provider/opencode-model-options.js +6 -71
  50. package/dist/server/modules/provider/opencode-model-options.js.map +1 -1
  51. package/dist/server/modules/provider/provider-controller.d.ts +2 -0
  52. package/dist/server/modules/provider/provider-controller.js +21 -1
  53. package/dist/server/modules/provider/provider-controller.js.map +1 -1
  54. package/dist/server/modules/provider/provider-discovery-helper-client.d.ts +25 -0
  55. package/dist/server/modules/provider/provider-discovery-helper-client.js +114 -0
  56. package/dist/server/modules/provider/provider-discovery-helper-client.js.map +1 -0
  57. package/dist/server/modules/provider/provider-discovery-helper-process.d.ts +1 -0
  58. package/dist/server/modules/provider/provider-discovery-helper-process.js +296 -0
  59. package/dist/server/modules/provider/provider-discovery-helper-process.js.map +1 -0
  60. package/dist/server/modules/sessions/claude-runtime-helper-client.d.ts +28 -0
  61. package/dist/server/modules/sessions/claude-runtime-helper-client.js +221 -0
  62. package/dist/server/modules/sessions/claude-runtime-helper-client.js.map +1 -0
  63. package/dist/server/modules/sessions/claude-runtime-helper-process.d.ts +1 -0
  64. package/dist/server/modules/sessions/claude-runtime-helper-process.js +146 -0
  65. package/dist/server/modules/sessions/claude-runtime-helper-process.js.map +1 -0
  66. package/dist/server/modules/sessions/codex-app-server-helper-client.d.ts +17 -0
  67. package/dist/server/modules/sessions/codex-app-server-helper-client.js +260 -0
  68. package/dist/server/modules/sessions/codex-app-server-helper-client.js.map +1 -0
  69. package/dist/server/modules/sessions/codex-app-server-helper-process.d.ts +1 -0
  70. package/dist/server/modules/sessions/codex-app-server-helper-process.js +484 -0
  71. package/dist/server/modules/sessions/codex-app-server-helper-process.js.map +1 -0
  72. package/dist/server/modules/sessions/session-activity-authority-service.d.ts +52 -0
  73. package/dist/server/modules/sessions/session-activity-authority-service.js +377 -0
  74. package/dist/server/modules/sessions/session-activity-authority-service.js.map +1 -0
  75. package/dist/server/modules/sessions/session-activity-inspector.js +80 -40
  76. package/dist/server/modules/sessions/session-activity-inspector.js.map +1 -1
  77. package/dist/server/modules/sessions/session-controller.d.ts +15 -1
  78. package/dist/server/modules/sessions/session-controller.js +14 -1
  79. package/dist/server/modules/sessions/session-controller.js.map +1 -1
  80. package/dist/server/modules/sessions/session-history-service.d.ts +4 -2
  81. package/dist/server/modules/sessions/session-history-service.js +167 -44
  82. package/dist/server/modules/sessions/session-history-service.js.map +1 -1
  83. package/dist/server/modules/sessions/session-live-runtime-service.d.ts +42 -4
  84. package/dist/server/modules/sessions/session-live-runtime-service.js +343 -51
  85. package/dist/server/modules/sessions/session-live-runtime-service.js.map +1 -1
  86. package/dist/server/modules/sessions/session-permission-request-service.d.ts +175 -0
  87. package/dist/server/modules/sessions/session-permission-request-service.js +1615 -0
  88. package/dist/server/modules/sessions/session-permission-request-service.js.map +1 -0
  89. package/dist/server/modules/sessions/session-provider-error-mapper.js +14 -0
  90. package/dist/server/modules/sessions/session-provider-error-mapper.js.map +1 -1
  91. package/dist/server/modules/terminal/command-template-service.d.ts +6 -2
  92. package/dist/server/modules/terminal/command-template-service.js +91 -5
  93. package/dist/server/modules/terminal/command-template-service.js.map +1 -1
  94. package/dist/server/modules/terminal/runtime/adapters/conpty-control-helper-client.d.ts +24 -0
  95. package/dist/server/modules/terminal/runtime/adapters/conpty-control-helper-client.js +104 -0
  96. package/dist/server/modules/terminal/runtime/adapters/conpty-control-helper-client.js.map +1 -0
  97. package/dist/server/modules/terminal/runtime/adapters/conpty-control-helper-process.d.ts +1 -0
  98. package/dist/server/modules/terminal/runtime/adapters/conpty-control-helper-process.js +96 -0
  99. package/dist/server/modules/terminal/runtime/adapters/conpty-control-helper-process.js.map +1 -0
  100. package/dist/server/modules/terminal/runtime/adapters/conpty-runtime-adapter.d.ts +37 -0
  101. package/dist/server/modules/terminal/runtime/adapters/conpty-runtime-adapter.js +123 -0
  102. package/dist/server/modules/terminal/runtime/adapters/conpty-runtime-adapter.js.map +1 -0
  103. package/dist/server/modules/terminal/runtime/adapters/embedded-pty-runtime-adapter.d.ts +12 -5
  104. package/dist/server/modules/terminal/runtime/adapters/embedded-pty-runtime-adapter.js +44 -4
  105. package/dist/server/modules/terminal/runtime/adapters/embedded-pty-runtime-adapter.js.map +1 -1
  106. package/dist/server/modules/terminal/runtime/adapters/tmux-helper-client.d.ts +14 -0
  107. package/dist/server/modules/terminal/runtime/adapters/tmux-helper-client.js +105 -0
  108. package/dist/server/modules/terminal/runtime/adapters/tmux-helper-client.js.map +1 -0
  109. package/dist/server/modules/terminal/runtime/adapters/tmux-helper-process.d.ts +1 -0
  110. package/dist/server/modules/terminal/runtime/adapters/tmux-helper-process.js +67 -0
  111. package/dist/server/modules/terminal/runtime/adapters/tmux-helper-process.js.map +1 -0
  112. package/dist/server/modules/terminal/runtime/adapters/tmux-runtime-adapter.d.ts +8 -12
  113. package/dist/server/modules/terminal/runtime/adapters/tmux-runtime-adapter.js +112 -21
  114. package/dist/server/modules/terminal/runtime/adapters/tmux-runtime-adapter.js.map +1 -1
  115. package/dist/server/modules/terminal/runtime/conpty-runtime-shared.d.ts +25 -0
  116. package/dist/server/modules/terminal/runtime/conpty-runtime-shared.js +124 -0
  117. package/dist/server/modules/terminal/runtime/conpty-runtime-shared.js.map +1 -0
  118. package/dist/server/modules/terminal/runtime/conpty-session-agent-process.d.ts +1 -0
  119. package/dist/server/modules/terminal/runtime/conpty-session-agent-process.js +205 -0
  120. package/dist/server/modules/terminal/runtime/conpty-session-agent-process.js.map +1 -0
  121. package/dist/server/modules/terminal/runtime/conpty-session-attach-client.d.ts +1 -0
  122. package/dist/server/modules/terminal/runtime/conpty-session-attach-client.js +108 -0
  123. package/dist/server/modules/terminal/runtime/conpty-session-attach-client.js.map +1 -0
  124. package/dist/server/modules/terminal/runtime/conpty-session-control-client.d.ts +1 -0
  125. package/dist/server/modules/terminal/runtime/conpty-session-control-client.js +90 -0
  126. package/dist/server/modules/terminal/runtime/conpty-session-control-client.js.map +1 -0
  127. package/dist/server/modules/terminal/runtime/pty-broker-agent-process.d.ts +1 -0
  128. package/dist/server/modules/terminal/runtime/pty-broker-agent-process.js +179 -0
  129. package/dist/server/modules/terminal/runtime/pty-broker-agent-process.js.map +1 -0
  130. package/dist/server/modules/terminal/runtime/pty-broker-client.d.ts +29 -0
  131. package/dist/server/modules/terminal/runtime/pty-broker-client.js +169 -0
  132. package/dist/server/modules/terminal/runtime/pty-broker-client.js.map +1 -0
  133. package/dist/server/modules/terminal/runtime/pty-broker-shared.d.ts +22 -0
  134. package/dist/server/modules/terminal/runtime/pty-broker-shared.js +123 -0
  135. package/dist/server/modules/terminal/runtime/pty-broker-shared.js.map +1 -0
  136. package/dist/server/modules/terminal/runtime/pty-host-attachment-manager.d.ts +1 -0
  137. package/dist/server/modules/terminal/runtime/pty-host-attachment-manager.js +11 -1
  138. package/dist/server/modules/terminal/runtime/pty-host-attachment-manager.js.map +1 -1
  139. package/dist/server/modules/terminal/runtime/terminal-log-file-store.d.ts +1 -0
  140. package/dist/server/modules/terminal/runtime/terminal-log-file-store.js +7 -1
  141. package/dist/server/modules/terminal/runtime/terminal-log-file-store.js.map +1 -1
  142. package/dist/server/modules/terminal/runtime/terminal-log-spooler.js +3 -2
  143. package/dist/server/modules/terminal/runtime/terminal-log-spooler.js.map +1 -1
  144. package/dist/server/modules/terminal/runtime/terminal-runtime-adapter.d.ts +5 -3
  145. package/dist/server/modules/terminal/runtime/terminal-runtime-manager.d.ts +11 -5
  146. package/dist/server/modules/terminal/runtime/terminal-runtime-manager.js +110 -13
  147. package/dist/server/modules/terminal/runtime/terminal-runtime-manager.js.map +1 -1
  148. package/dist/server/modules/terminal/template-reverse-proxy-service.d.ts +10 -0
  149. package/dist/server/modules/terminal/template-reverse-proxy-service.js +320 -0
  150. package/dist/server/modules/terminal/template-reverse-proxy-service.js.map +1 -0
  151. package/dist/server/modules/terminal/terminal-controller.d.ts +1 -0
  152. package/dist/server/modules/terminal/terminal-controller.js +23 -7
  153. package/dist/server/modules/terminal/terminal-controller.js.map +1 -1
  154. package/dist/server/modules/terminal/terminal-service.d.ts +30 -14
  155. package/dist/server/modules/terminal/terminal-service.js +260 -51
  156. package/dist/server/modules/terminal/terminal-service.js.map +1 -1
  157. package/dist/server/modules/terminal/terminal-shell.d.ts +4 -0
  158. package/dist/server/modules/terminal/terminal-shell.js +165 -18
  159. package/dist/server/modules/terminal/terminal-shell.js.map +1 -1
  160. package/dist/server/modules/workbench/workspace-file-watcher.d.ts +30 -0
  161. package/dist/server/modules/workbench/workspace-file-watcher.js +137 -0
  162. package/dist/server/modules/workbench/workspace-file-watcher.js.map +1 -0
  163. package/dist/server/modules/workbench/workspace-panel-snapshot-service.js +48 -7
  164. package/dist/server/modules/workbench/workspace-panel-snapshot-service.js.map +1 -1
  165. package/dist/server/routes/git.js +1 -0
  166. package/dist/server/routes/git.js.map +1 -1
  167. package/dist/server/routes/preferences.d.ts +2 -1
  168. package/dist/server/routes/preferences.js +3 -1
  169. package/dist/server/routes/preferences.js.map +1 -1
  170. package/dist/server/routes/proxy.d.ts +3 -0
  171. package/dist/server/routes/proxy.js +24 -0
  172. package/dist/server/routes/proxy.js.map +1 -0
  173. package/dist/server/routes/sessions.js +2 -0
  174. package/dist/server/routes/sessions.js.map +1 -1
  175. package/dist/server/server/create-server.d.ts +4 -0
  176. package/dist/server/server/create-server.js +29 -5
  177. package/dist/server/server/create-server.js.map +1 -1
  178. package/dist/server/shared/utils/command-launch.d.ts +6 -0
  179. package/dist/server/shared/utils/command-launch.js +39 -0
  180. package/dist/server/shared/utils/command-launch.js.map +1 -0
  181. package/dist/server/shared/utils/perf-log.d.ts +1 -0
  182. package/dist/server/shared/utils/perf-log.js +8 -2
  183. package/dist/server/shared/utils/perf-log.js.map +1 -1
  184. package/dist/server/shared/utils/permission-debug-log.d.ts +2 -0
  185. package/dist/server/shared/utils/permission-debug-log.js +40 -0
  186. package/dist/server/shared/utils/permission-debug-log.js.map +1 -0
  187. package/dist/server/shared/utils/terminal-debug-log.d.ts +4 -0
  188. package/dist/server/shared/utils/terminal-debug-log.js +71 -0
  189. package/dist/server/shared/utils/terminal-debug-log.js.map +1 -0
  190. package/dist/server/storage/repositories/terminal-command-template-repository.d.ts +1 -0
  191. package/dist/server/storage/repositories/terminal-command-template-repository.js +34 -3
  192. package/dist/server/storage/repositories/terminal-command-template-repository.js.map +1 -1
  193. package/dist/server/storage/repositories/user-preference-profile-repository.d.ts +8 -0
  194. package/dist/server/storage/repositories/user-preference-profile-repository.js +46 -0
  195. package/dist/server/storage/repositories/user-preference-profile-repository.js.map +1 -0
  196. package/dist/server/storage/sqlite/client.js +27 -0
  197. package/dist/server/storage/sqlite/client.js.map +1 -1
  198. package/dist/server/storage/sqlite/schema.sql +15 -0
  199. package/dist/server/types/domain.d.ts +30 -1
  200. package/dist/server/ws/terminal-ws-hub.d.ts +2 -0
  201. package/dist/server/ws/terminal-ws-hub.js +42 -5
  202. package/dist/server/ws/terminal-ws-hub.js.map +1 -1
  203. package/dist/server/ws/workbench-ws-hub.d.ts +9 -1
  204. package/dist/server/ws/workbench-ws-hub.js +132 -21
  205. package/dist/server/ws/workbench-ws-hub.js.map +1 -1
  206. package/dist/server/ws/ws-server.d.ts +22 -1
  207. package/dist/server/ws/ws-server.js +82 -44
  208. package/dist/server/ws/ws-server.js.map +1 -1
  209. package/node_modules/@codingns/session-sync-core/dist/claude-message-utils.js +42 -0
  210. package/node_modules/@codingns/session-sync-core/dist/claude-message-utils.js.map +1 -1
  211. package/node_modules/@codingns/session-sync-core/dist/patch-builder.d.ts +30 -0
  212. package/node_modules/@codingns/session-sync-core/dist/patch-builder.js +103 -0
  213. package/node_modules/@codingns/session-sync-core/dist/patch-builder.js.map +1 -0
  214. package/node_modules/@codingns/session-sync-core/dist/providers/claude-code.d.ts +1 -1
  215. package/node_modules/@codingns/session-sync-core/dist/providers/claude-code.js +167 -7
  216. package/node_modules/@codingns/session-sync-core/dist/providers/claude-code.js.map +1 -1
  217. package/node_modules/@codingns/session-sync-core/dist/providers/codex.d.ts +1 -1
  218. package/node_modules/@codingns/session-sync-core/dist/providers/codex.js +1 -1
  219. package/node_modules/@codingns/session-sync-core/dist/providers/codex.js.map +1 -1
  220. package/node_modules/@codingns/session-sync-core/dist/providers/opencode-permissions.d.ts +1 -0
  221. package/node_modules/@codingns/session-sync-core/dist/providers/opencode-permissions.js +8 -0
  222. package/node_modules/@codingns/session-sync-core/dist/providers/opencode-permissions.js.map +1 -0
  223. package/node_modules/@codingns/session-sync-core/dist/providers/opencode-shared.js +31 -0
  224. package/node_modules/@codingns/session-sync-core/dist/providers/opencode-shared.js.map +1 -1
  225. package/node_modules/@codingns/session-sync-core/dist/providers/opencode.d.ts +1 -1
  226. package/node_modules/@codingns/session-sync-core/dist/providers/opencode.js +8 -4
  227. package/node_modules/@codingns/session-sync-core/dist/providers/opencode.js.map +1 -1
  228. package/node_modules/@codingns/session-sync-core/dist/runtime/active-run-registry.d.ts +1 -0
  229. package/node_modules/@codingns/session-sync-core/dist/runtime/active-run-registry.js +15 -0
  230. package/node_modules/@codingns/session-sync-core/dist/runtime/active-run-registry.js.map +1 -1
  231. package/node_modules/@codingns/session-sync-core/dist/runtime/claude-runtime.d.ts +5 -0
  232. package/node_modules/@codingns/session-sync-core/dist/runtime/claude-runtime.js +85 -1
  233. package/node_modules/@codingns/session-sync-core/dist/runtime/claude-runtime.js.map +1 -1
  234. package/node_modules/@codingns/session-sync-core/dist/runtime/codex-runtime.d.ts +25 -0
  235. package/node_modules/@codingns/session-sync-core/dist/runtime/codex-runtime.js +718 -17
  236. package/node_modules/@codingns/session-sync-core/dist/runtime/codex-runtime.js.map +1 -1
  237. package/node_modules/@codingns/session-sync-core/dist/runtime/opencode-runtime.d.ts +3 -0
  238. package/node_modules/@codingns/session-sync-core/dist/runtime/opencode-runtime.js +106 -23
  239. package/node_modules/@codingns/session-sync-core/dist/runtime/opencode-runtime.js.map +1 -1
  240. package/node_modules/@codingns/session-sync-core/dist/runtime/provider-runtime-service.d.ts +3 -0
  241. package/node_modules/@codingns/session-sync-core/dist/runtime/provider-runtime-service.js +19 -0
  242. package/node_modules/@codingns/session-sync-core/dist/runtime/provider-runtime-service.js.map +1 -1
  243. package/node_modules/@codingns/session-sync-core/dist/runtime/types.d.ts +3 -0
  244. package/node_modules/@codingns/session-sync-core/dist/services.d.ts +1 -1
  245. package/node_modules/@codingns/session-sync-core/dist/services.js +2 -2
  246. package/node_modules/@codingns/session-sync-core/dist/services.js.map +1 -1
  247. package/node_modules/@codingns/session-sync-core/dist/types.d.ts +1 -1
  248. package/package.json +3 -2
  249. package/scripts/postinstall.mjs +15 -8
  250. package/dist/public/assets/index-BUPByQPG.css +0 -1
  251. package/dist/public/assets/index-D1CwTkI2.js +0 -106
@@ -2,14 +2,18 @@ import { EventEmitter } from "node:events";
2
2
  import path from "node:path";
3
3
  import { AppError } from "../../shared/errors/app-error.js";
4
4
  import { createId } from "../../shared/utils/id.js";
5
+ import { isTerminalDebugEnabled, logTerminalDebug, terminalDebugNowMs } from "../../shared/utils/terminal-debug-log.js";
5
6
  import { nowIso } from "../../shared/utils/time.js";
6
7
  import { resolveWorkspaceCwd } from "./terminal-paths.js";
7
- import { getDefaultShell, resolveRequestedShell } from "./terminal-shell.js";
8
+ import { getDefaultShell, resolveRequestedShell, resolveWindowsPersistentRuntimeType } from "./terminal-shell.js";
8
9
  import { captureTmuxPaneContent } from "./runtime/adapters/tmux-runtime-adapter.js";
10
+ import { isConptyRuntimeType } from "./runtime/conpty-runtime-shared.js";
9
11
  import { TerminalLogFileStore } from "./runtime/terminal-log-file-store.js";
10
12
  import { TerminalLogSpooler } from "./runtime/terminal-log-spooler.js";
11
13
  import { TerminalOutputBuffer } from "./runtime/terminal-output-buffer.js";
12
14
  import { TerminalRuntimeManager } from "./runtime/terminal-runtime-manager.js";
15
+ const TERMINAL_ACTIVITY_FLUSH_INTERVAL_MS = 2_000;
16
+ const TERMINAL_OUTPUT_FLUSH_INTERVAL_MS = 8;
13
17
  export class TerminalService extends EventEmitter {
14
18
  db;
15
19
  terminalInstanceRepository;
@@ -17,14 +21,17 @@ export class TerminalService extends EventEmitter {
17
21
  workspaceService;
18
22
  outputBuffer = new TerminalOutputBuffer();
19
23
  runtimeManager = new TerminalRuntimeManager();
20
- lastPersistedActivity = new Map();
21
24
  terminalSubscriptionCounts = new Map();
22
25
  pendingCloseReasons = new Map();
23
26
  pendingDeletedTerminalIds = new Set();
27
+ pendingActivityByTerminalId = new Map();
28
+ pendingInputTraceByTerminalId = new Map();
29
+ pendingOutputByTerminalId = new Map();
24
30
  terminalLogSpooler;
25
31
  terminalLogFileRepository;
26
32
  terminalLogSegmentRepository;
27
33
  terminalLogFileStore;
34
+ activityFlushTimer = null;
28
35
  isDisposing = false;
29
36
  constructor(db, terminalInstanceRepository, terminalRuntimeSessionRepository, workspaceService, _idleTimeoutSeconds, options = {}) {
30
37
  super();
@@ -55,12 +62,12 @@ export class TerminalService extends EventEmitter {
55
62
  });
56
63
  this.recoverRuntimeStates();
57
64
  }
58
- createTerminal(input) {
65
+ async createTerminal(input) {
59
66
  const workspace = this.workspaceService.getWorkspaceOrThrow(input.workspaceId);
60
67
  const now = nowIso();
61
68
  const shell = resolveRequestedShell(sanitizeShell(input.shell) ?? getDefaultShell());
62
69
  const cwd = resolveWorkspaceCwd(workspace.path, input.cwd);
63
- const runtimeType = resolveRequestedRuntimeType(input.runtimeType);
70
+ const runtimeType = resolveRequestedRuntimeType(input.runtimeType, shell);
64
71
  const runtimeSessionId = createId();
65
72
  const attachTarget = buildAttachTarget(runtimeType, runtimeSessionId);
66
73
  const terminal = {
@@ -104,9 +111,12 @@ export class TerminalService extends EventEmitter {
104
111
  persist();
105
112
  try {
106
113
  const env = buildTerminalEnv(input.env);
107
- const createdInspection = this.runtimeManager.createPersistentSession(terminal, runtimeSession, env);
108
- const attachmentProcessId = this.runtimeManager.ensureAttached(terminal, runtimeSession, env);
114
+ const createdInspection = await this.runtimeManager.createPersistentSession(terminal, runtimeSession, env);
115
+ runtimeSession.agentPid = createdInspection.agentPid ?? runtimeSession.agentPid;
116
+ const attachmentProcessId = await this.runtimeManager.ensureAttached(terminal, runtimeSession, env);
109
117
  const processId = createdInspection.shellPid ?? attachmentProcessId;
118
+ terminal.processId = processId;
119
+ runtimeSession.shellPid = processId;
110
120
  const runningTerminal = {
111
121
  ...terminal,
112
122
  status: "running",
@@ -123,6 +133,7 @@ export class TerminalService extends EventEmitter {
123
133
  });
124
134
  this.terminalRuntimeSessionRepository.updateState({
125
135
  id: runtimeSession.id,
136
+ agentPid: createdInspection.agentPid ?? runtimeSession.agentPid,
126
137
  shellPid: processId,
127
138
  state: "running",
128
139
  lastCheckedAt: nowIso(),
@@ -145,23 +156,32 @@ export class TerminalService extends EventEmitter {
145
156
  });
146
157
  this.terminalRuntimeSessionRepository.updateState({
147
158
  id: runtimeSession.id,
159
+ agentPid: runtimeSession.agentPid,
148
160
  shellPid: null,
149
161
  state: "error",
150
162
  lastCheckedAt: failedAt,
151
163
  lastErrorDetail: error instanceof Error ? error.message : "PTY 启动失败",
152
164
  updatedAt: failedAt
153
165
  });
166
+ if (runtimeSession.agentPid) {
167
+ try {
168
+ process.kill(runtimeSession.agentPid);
169
+ }
170
+ catch {
171
+ // agent 已退出时忽略。
172
+ }
173
+ }
154
174
  const failedTerminal = this.getTerminalOrThrow(terminal.id);
155
175
  this.emit("status", failedTerminal);
156
176
  throw error;
157
177
  }
158
178
  }
159
- listTerminals(workspaceId) {
179
+ async listTerminals(workspaceId) {
160
180
  this.workspaceService.getWorkspaceOrThrow(workspaceId);
161
181
  const terminals = this.terminalInstanceRepository.listByWorkspace(workspaceId);
162
182
  let hasLifecycleChange = false;
163
183
  for (const terminal of terminals) {
164
- if (this.reconcileTerminalRuntime(terminal)) {
184
+ if (await this.reconcileTerminalRuntime(terminal)) {
165
185
  hasLifecycleChange = true;
166
186
  }
167
187
  }
@@ -169,6 +189,10 @@ export class TerminalService extends EventEmitter {
169
189
  ? this.terminalInstanceRepository.listByWorkspace(workspaceId)
170
190
  : terminals;
171
191
  }
192
+ listTerminalSnapshotItems(workspaceId) {
193
+ this.workspaceService.getWorkspaceOrThrow(workspaceId);
194
+ return this.terminalInstanceRepository.listByWorkspace(workspaceId);
195
+ }
172
196
  getTerminalOrThrow(terminalId) {
173
197
  const terminal = this.terminalInstanceRepository.findById(terminalId);
174
198
  if (!terminal) {
@@ -191,7 +215,7 @@ export class TerminalService extends EventEmitter {
191
215
  }
192
216
  return session;
193
217
  }
194
- closeTerminal(terminalId, reason = "user_closed") {
218
+ async closeTerminal(terminalId, reason = "user_closed") {
195
219
  const terminal = this.getTerminalOrThrow(terminalId);
196
220
  const session = this.getRuntimeSessionOrThrow(terminal.runtimeSessionId);
197
221
  this.pendingCloseReasons.set(terminalId, reason);
@@ -200,7 +224,7 @@ export class TerminalService extends EventEmitter {
200
224
  return { success: true };
201
225
  }
202
226
  this.flushTerminalLogs(terminalId);
203
- const willEmitExit = this.runtimeManager.terminateSession(terminal, session);
227
+ const willEmitExit = await this.runtimeManager.terminateSession(terminal, session);
204
228
  if (!willEmitExit) {
205
229
  this.finalizeTerminalClosure(terminal, session, {
206
230
  requestedClose: true,
@@ -212,11 +236,12 @@ export class TerminalService extends EventEmitter {
212
236
  }
213
237
  return { success: true };
214
238
  }
215
- deleteTerminal(terminalId) {
239
+ async deleteTerminal(terminalId) {
216
240
  const terminal = this.getTerminalOrThrow(terminalId);
217
241
  const session = this.getRuntimeSessionOrThrow(terminal.runtimeSessionId);
218
242
  this.pendingCloseReasons.delete(terminalId);
219
- this.lastPersistedActivity.delete(terminalId);
243
+ this.pendingActivityByTerminalId.delete(terminalId);
244
+ this.clearActivityFlushTimerIfIdle();
220
245
  this.flushTerminalLogs(terminalId);
221
246
  this.pendingDeletedTerminalIds.add(terminalId);
222
247
  const deleteRecords = this.db.transaction(() => {
@@ -232,7 +257,7 @@ export class TerminalService extends EventEmitter {
232
257
  }
233
258
  let willEmitExit = false;
234
259
  try {
235
- willEmitExit = this.runtimeManager.terminateSession(terminal, session);
260
+ willEmitExit = await this.runtimeManager.terminateSession(terminal, session);
236
261
  }
237
262
  catch (error) {
238
263
  this.pendingDeletedTerminalIds.delete(terminalId);
@@ -250,7 +275,7 @@ export class TerminalService extends EventEmitter {
250
275
  }
251
276
  return { success: true };
252
277
  }
253
- writeInput(terminalId, content) {
278
+ async writeInput(terminalId, content, debugContext = {}) {
254
279
  if (!content) {
255
280
  throw new AppError({
256
281
  statusCode: 400,
@@ -259,12 +284,31 @@ export class TerminalService extends EventEmitter {
259
284
  field: "content"
260
285
  });
261
286
  }
262
- const terminal = this.ensureTerminalInteractive(terminalId);
287
+ const serviceWriteStartedAtMs = terminalDebugNowMs();
288
+ const terminal = await this.ensureTerminalInteractive(terminalId);
263
289
  this.runtimeManager.write(terminal.id, content);
290
+ const serviceWriteFinishedAtMs = terminalDebugNowMs();
291
+ this.recordPendingInputTrace(terminalId, content, {
292
+ ...debugContext,
293
+ serviceWriteStartedAtMs,
294
+ serviceWriteFinishedAtMs
295
+ });
296
+ logTerminalDebug("terminal.input.write_completed", {
297
+ terminalId,
298
+ traceId: debugContext.clientTraceId ?? null,
299
+ charCount: content.length,
300
+ ensureAndWriteMs: serviceWriteFinishedAtMs - serviceWriteStartedAtMs,
301
+ wsToWriteMs: debugContext.wsReceivedAtMs === null || debugContext.wsReceivedAtMs === undefined
302
+ ? null
303
+ : serviceWriteFinishedAtMs - debugContext.wsReceivedAtMs,
304
+ clientToWriteMs: debugContext.clientSentAtMs === null || debugContext.clientSentAtMs === undefined
305
+ ? null
306
+ : serviceWriteFinishedAtMs - debugContext.clientSentAtMs
307
+ });
264
308
  this.touchLastActiveAt(terminalId);
265
309
  return { accepted: true };
266
310
  }
267
- resizeTerminal(terminalId, cols, rows) {
311
+ async resizeTerminal(terminalId, cols, rows) {
268
312
  if (!Number.isInteger(cols) || !Number.isInteger(rows) || cols < 20 || rows < 5) {
269
313
  throw new AppError({
270
314
  statusCode: 400,
@@ -273,12 +317,12 @@ export class TerminalService extends EventEmitter {
273
317
  field: "cols"
274
318
  });
275
319
  }
276
- const terminal = this.ensureTerminalInteractive(terminalId);
320
+ const terminal = await this.ensureTerminalInteractive(terminalId);
277
321
  this.runtimeManager.resize(terminal.id, cols, rows);
278
322
  this.touchLastActiveAt(terminalId);
279
323
  return { accepted: true };
280
324
  }
281
- subscribeTerminal(terminalId, lastCursor, callbacks) {
325
+ async subscribeTerminal(terminalId, lastCursor, callbacks) {
282
326
  const outputListener = (event) => {
283
327
  if (event.terminalId !== terminalId) {
284
328
  return;
@@ -304,7 +348,7 @@ export class TerminalService extends EventEmitter {
304
348
  this.on("exit", exitListener);
305
349
  this.retainTerminalSubscription(terminalId);
306
350
  try {
307
- const current = this.ensureTerminalAttachedForSubscription(terminalId);
351
+ const current = await this.ensureTerminalAttachedForSubscription(terminalId);
308
352
  const backfill = this.outputBuffer.readSince(terminalId, lastCursor);
309
353
  void callbacks.onStatus(current);
310
354
  void callbacks.onBackfill(backfill);
@@ -332,17 +376,20 @@ export class TerminalService extends EventEmitter {
332
376
  }
333
377
  async dispose() {
334
378
  this.isDisposing = true;
379
+ this.flushPendingTerminalOutput();
380
+ this.flushPendingActivity();
335
381
  this.terminalLogSpooler?.flushAll();
336
382
  this.runtimeManager.closeAllAttachments();
337
383
  await new Promise((resolve) => setTimeout(resolve, 100));
338
384
  }
339
- readTerminalHistory(terminalId, beforeSeq, limit) {
385
+ async readTerminalHistory(terminalId, beforeSeq, limit) {
340
386
  const terminal = this.getTerminalOrThrow(terminalId);
387
+ this.flushTerminalLogs(terminalId);
341
388
  const runtimeSession = terminal.runtimeSessionId
342
389
  ? this.terminalRuntimeSessionRepository.findById(terminal.runtimeSessionId)
343
390
  : null;
344
391
  if (beforeSeq === 1 && runtimeSession?.runtimeType === "tmux") {
345
- const capturedContent = normalizeTerminalHistoryContent(captureTmuxPaneContent(runtimeSession.sessionKey));
392
+ const capturedContent = normalizeTerminalHistoryContent(await captureTmuxPaneContent(runtimeSession.sessionKey));
346
393
  const capturedLineCount = countTerminalHistoryLines(capturedContent);
347
394
  return {
348
395
  terminalId,
@@ -427,16 +474,16 @@ export class TerminalService extends EventEmitter {
427
474
  if (this.isDisposing) {
428
475
  return;
429
476
  }
430
- const chunks = this.outputBuffer.append(terminalId, content);
431
- if (chunks.length === 0) {
477
+ const batch = this.getOrCreatePendingOutputBatch(terminalId);
478
+ batch.contents.push(content);
479
+ if (batch.timer !== null) {
432
480
  return;
433
481
  }
434
- this.terminalLogSpooler?.appendChunks(terminalId, chunks);
435
- this.touchLastActiveAt(terminalId);
436
- this.emit("output", {
437
- terminalId,
438
- chunks
439
- });
482
+ batch.timer = setTimeout(() => {
483
+ batch.timer = null;
484
+ this.flushPendingTerminalOutput(terminalId);
485
+ }, TERMINAL_OUTPUT_FLUSH_INTERVAL_MS);
486
+ batch.timer.unref?.();
440
487
  }
441
488
  handleRuntimeExit(event) {
442
489
  if (this.isDisposing) {
@@ -444,7 +491,9 @@ export class TerminalService extends EventEmitter {
444
491
  }
445
492
  if (this.pendingDeletedTerminalIds.delete(event.terminalId)) {
446
493
  this.pendingCloseReasons.delete(event.terminalId);
447
- this.lastPersistedActivity.delete(event.terminalId);
494
+ this.pendingActivityByTerminalId.delete(event.terminalId);
495
+ this.clearActivityFlushTimerIfIdle();
496
+ this.pendingInputTraceByTerminalId.delete(event.terminalId);
448
497
  this.clearTerminalLogs(event.terminalId);
449
498
  return;
450
499
  }
@@ -461,17 +510,16 @@ export class TerminalService extends EventEmitter {
461
510
  if (this.isDisposing) {
462
511
  return;
463
512
  }
464
- const now = Date.now();
465
- const previous = this.lastPersistedActivity.get(terminalId) ?? 0;
466
- if (now - previous >= 500) {
467
- this.lastPersistedActivity.set(terminalId, now);
468
- this.terminalInstanceRepository.touchLastActiveAt(terminalId, nowIso(new Date(now)));
469
- }
513
+ this.pendingActivityByTerminalId.set(terminalId, nowIso());
514
+ this.scheduleActivityFlush();
470
515
  }
471
- ensureTerminalInteractive(terminalId) {
516
+ async ensureTerminalInteractive(terminalId) {
472
517
  const terminal = this.getTerminalOrThrow(terminalId);
518
+ if (terminal.status === "running" && this.runtimeManager.isAttached(terminal.id)) {
519
+ return terminal;
520
+ }
473
521
  const session = this.getRuntimeSessionOrThrow(terminal.runtimeSessionId);
474
- const nextTerminal = this.ensureTerminalRunning(terminal, session, true);
522
+ const nextTerminal = await this.ensureTerminalRunning(terminal, session, true);
475
523
  if (nextTerminal.status !== "running") {
476
524
  throw new AppError({
477
525
  statusCode: 409,
@@ -481,16 +529,16 @@ export class TerminalService extends EventEmitter {
481
529
  }
482
530
  return nextTerminal;
483
531
  }
484
- ensureTerminalAttachedForSubscription(terminalId) {
532
+ async ensureTerminalAttachedForSubscription(terminalId) {
485
533
  const terminal = this.getTerminalOrThrow(terminalId);
486
534
  const session = this.getRuntimeSessionOrThrow(terminal.runtimeSessionId);
487
535
  return this.ensureTerminalRunning(terminal, session, true);
488
536
  }
489
- ensureTerminalRunning(terminal, session, ensureAttached) {
537
+ async ensureTerminalRunning(terminal, session, ensureAttached) {
490
538
  if (terminal.status === "closed" || terminal.status === "error") {
491
539
  return terminal;
492
540
  }
493
- const inspection = this.runtimeManager.inspectPersistentSession(terminal, session);
541
+ const inspection = await this.runtimeManager.inspectPersistentSession(terminal, session);
494
542
  if (!inspection.alive) {
495
543
  if (shouldMarkRuntimeLost(terminal, inspection.detail)) {
496
544
  return this.markTerminalLost(terminal, session, inspection.detail);
@@ -498,12 +546,18 @@ export class TerminalService extends EventEmitter {
498
546
  return this.markTerminalError(terminal, session, inspection.detail ?? buildMissingProcessStatusDetail(terminal));
499
547
  }
500
548
  if (ensureAttached) {
501
- const attachmentProcessId = this.runtimeManager.ensureAttached(terminal, session, buildTerminalEnv());
549
+ if (session.runtimeType === "embedded-pty") {
550
+ if (!this.runtimeManager.isAttached(terminal.id)) {
551
+ return this.markTerminalError(terminal, session, inspection.detail ?? "EMBEDDED_RUNTIME_NOT_ATTACHED");
552
+ }
553
+ return this.markTerminalRunning(terminal, session, inspection.shellPid ?? this.runtimeManager.getProcessId(terminal.id), inspection.detail);
554
+ }
555
+ const attachmentProcessId = this.runtimeManager.ensureLegacyAttached(terminal, session, buildTerminalEnv());
502
556
  return this.markTerminalRunning(terminal, session, inspection.shellPid ?? attachmentProcessId, inspection.detail);
503
557
  }
504
558
  return this.markTerminalRunning(terminal, session, inspection.shellPid, inspection.detail);
505
559
  }
506
- reconcileTerminalRuntime(terminal) {
560
+ async reconcileTerminalRuntime(terminal) {
507
561
  if (terminal.status === "closed" || terminal.status === "error") {
508
562
  return false;
509
563
  }
@@ -513,7 +567,7 @@ export class TerminalService extends EventEmitter {
513
567
  statusDetail: terminal.statusDetail
514
568
  });
515
569
  const session = this.getRuntimeSessionOrThrow(terminal.runtimeSessionId);
516
- const nextTerminal = this.ensureTerminalRunning(terminal, session, false);
570
+ const nextTerminal = await this.ensureTerminalRunning(terminal, session, false);
517
571
  const after = JSON.stringify({
518
572
  status: nextTerminal.status,
519
573
  processId: nextTerminal.processId,
@@ -544,6 +598,7 @@ export class TerminalService extends EventEmitter {
544
598
  });
545
599
  this.terminalRuntimeSessionRepository.updateState({
546
600
  id: session.id,
601
+ agentPid: session.agentPid,
547
602
  shellPid: processId,
548
603
  state: "running",
549
604
  lastCheckedAt: updatedAt,
@@ -558,6 +613,7 @@ export class TerminalService extends EventEmitter {
558
613
  const updatedAt = nowIso();
559
614
  this.terminalRuntimeSessionRepository.updateState({
560
615
  id: session.id,
616
+ agentPid: session.agentPid,
561
617
  shellPid: terminal.processId,
562
618
  state: "lost",
563
619
  lastCheckedAt: updatedAt,
@@ -578,6 +634,9 @@ export class TerminalService extends EventEmitter {
578
634
  return updated;
579
635
  }
580
636
  markTerminalError(terminal, session, detail) {
637
+ this.pendingActivityByTerminalId.delete(terminal.id);
638
+ this.clearActivityFlushTimerIfIdle();
639
+ this.pendingInputTraceByTerminalId.delete(terminal.id);
581
640
  const closedAt = nowIso();
582
641
  this.terminalInstanceRepository.updateLifecycle({
583
642
  id: terminal.id,
@@ -590,6 +649,7 @@ export class TerminalService extends EventEmitter {
590
649
  });
591
650
  this.terminalRuntimeSessionRepository.updateState({
592
651
  id: session.id,
652
+ agentPid: session.agentPid,
593
653
  shellPid: terminal.processId,
594
654
  state: "error",
595
655
  lastCheckedAt: closedAt,
@@ -603,10 +663,13 @@ export class TerminalService extends EventEmitter {
603
663
  finalizeTerminalClosure(terminal, session, event) {
604
664
  const closeReason = this.pendingCloseReasons.get(terminal.id) ?? "user_closed";
605
665
  this.pendingCloseReasons.delete(terminal.id);
666
+ this.pendingActivityByTerminalId.delete(terminal.id);
667
+ this.clearActivityFlushTimerIfIdle();
668
+ this.pendingInputTraceByTerminalId.delete(terminal.id);
606
669
  const finishedAt = nowIso();
607
670
  const status = event.requestedClose || event.exitCode === 0 ? "closed" : "error";
608
671
  const statusDetail = status === "error"
609
- ? event.sessionDetail ?? `终端异常退出,exitCode=${event.exitCode ?? "unknown"}`
672
+ ? normalizeTerminalErrorDetail(event.sessionDetail, event.exitCode)
610
673
  : resolveClosedStatusDetail(closeReason, terminal.statusDetail);
611
674
  this.terminalInstanceRepository.updateLifecycle({
612
675
  id: terminal.id,
@@ -619,6 +682,7 @@ export class TerminalService extends EventEmitter {
619
682
  });
620
683
  this.terminalRuntimeSessionRepository.updateState({
621
684
  id: session.id,
685
+ agentPid: session.agentPid,
622
686
  shellPid: event.shellPid,
623
687
  state: status === "closed" ? "closed" : "error",
624
688
  lastCheckedAt: finishedAt,
@@ -658,7 +722,7 @@ export class TerminalService extends EventEmitter {
658
722
  }, "RUNTIME_SESSION_MISSING");
659
723
  continue;
660
724
  }
661
- this.ensureTerminalRunning(terminal, session, false);
725
+ void this.ensureTerminalRunning(terminal, session, false);
662
726
  }
663
727
  }
664
728
  retainTerminalSubscription(terminalId) {
@@ -689,14 +753,134 @@ export class TerminalService extends EventEmitter {
689
753
  this.runtimeManager.detach(terminalId);
690
754
  }
691
755
  flushTerminalLogs(terminalId) {
756
+ this.flushPendingTerminalOutput(terminalId);
692
757
  this.terminalLogSpooler?.flushTerminal(terminalId);
693
758
  }
694
759
  clearTerminalLogs(terminalId) {
760
+ const pendingOutput = this.pendingOutputByTerminalId.get(terminalId);
761
+ if (pendingOutput?.timer) {
762
+ clearTimeout(pendingOutput.timer);
763
+ }
764
+ this.pendingOutputByTerminalId.delete(terminalId);
765
+ this.pendingInputTraceByTerminalId.delete(terminalId);
695
766
  this.outputBuffer.clear(terminalId);
696
767
  this.terminalLogSegmentRepository?.deleteByTerminalId(terminalId);
697
768
  this.terminalLogFileRepository?.deleteByTerminalId(terminalId);
698
769
  this.terminalLogSpooler?.deleteTerminalLogs(terminalId);
699
770
  }
771
+ getOrCreatePendingOutputBatch(terminalId) {
772
+ let batch = this.pendingOutputByTerminalId.get(terminalId);
773
+ if (!batch) {
774
+ batch = {
775
+ contents: [],
776
+ timer: null
777
+ };
778
+ this.pendingOutputByTerminalId.set(terminalId, batch);
779
+ }
780
+ return batch;
781
+ }
782
+ flushPendingTerminalOutput(terminalId) {
783
+ if (terminalId) {
784
+ const batch = this.pendingOutputByTerminalId.get(terminalId);
785
+ if (!batch) {
786
+ return;
787
+ }
788
+ if (batch.timer) {
789
+ clearTimeout(batch.timer);
790
+ batch.timer = null;
791
+ }
792
+ const content = batch.contents.join("");
793
+ this.pendingOutputByTerminalId.delete(terminalId);
794
+ this.commitTerminalOutput(terminalId, content);
795
+ return;
796
+ }
797
+ for (const nextTerminalId of [...this.pendingOutputByTerminalId.keys()]) {
798
+ this.flushPendingTerminalOutput(nextTerminalId);
799
+ }
800
+ }
801
+ commitTerminalOutput(terminalId, content) {
802
+ const traceQueue = this.pendingInputTraceByTerminalId.get(terminalId) ?? [];
803
+ const outputReceivedAtMs = traceQueue.length > 0 ? terminalDebugNowMs() : null;
804
+ const chunks = this.outputBuffer.append(terminalId, content);
805
+ if (chunks.length === 0) {
806
+ return;
807
+ }
808
+ this.terminalLogSpooler?.appendChunks(terminalId, chunks);
809
+ this.touchLastActiveAt(terminalId);
810
+ this.emit("output", {
811
+ terminalId,
812
+ chunks
813
+ });
814
+ if (traceQueue.length > 0 && outputReceivedAtMs !== null) {
815
+ for (const trace of traceQueue) {
816
+ logTerminalDebug("terminal.output.after_input", {
817
+ terminalId,
818
+ traceId: trace.traceId,
819
+ charCount: trace.charCount,
820
+ outputBytes: Buffer.byteLength(content, "utf8"),
821
+ writeToOutputMs: outputReceivedAtMs - trace.serviceWriteFinishedAtMs,
822
+ wsToOutputMs: trace.wsReceivedAtMs === null ? null : outputReceivedAtMs - trace.wsReceivedAtMs,
823
+ clientToOutputMs: trace.clientSentAtMs === null ? null : outputReceivedAtMs - trace.clientSentAtMs,
824
+ pendingTraceCount: traceQueue.length
825
+ });
826
+ }
827
+ this.pendingInputTraceByTerminalId.delete(terminalId);
828
+ }
829
+ }
830
+ recordPendingInputTrace(terminalId, content, input) {
831
+ if (!isTerminalDebugEnabled() || !input.clientTraceId) {
832
+ return;
833
+ }
834
+ const queue = this.pendingInputTraceByTerminalId.get(terminalId) ?? [];
835
+ queue.push({
836
+ traceId: input.clientTraceId,
837
+ clientSentAtMs: input.clientSentAtMs ?? null,
838
+ wsReceivedAtMs: input.wsReceivedAtMs ?? null,
839
+ serviceWriteStartedAtMs: input.serviceWriteStartedAtMs,
840
+ serviceWriteFinishedAtMs: input.serviceWriteFinishedAtMs,
841
+ charCount: content.length
842
+ });
843
+ this.pendingInputTraceByTerminalId.set(terminalId, queue);
844
+ }
845
+ scheduleActivityFlush() {
846
+ if (this.activityFlushTimer !== null) {
847
+ return;
848
+ }
849
+ this.activityFlushTimer = setTimeout(() => {
850
+ this.activityFlushTimer = null;
851
+ this.flushPendingActivity();
852
+ }, TERMINAL_ACTIVITY_FLUSH_INTERVAL_MS);
853
+ this.activityFlushTimer.unref?.();
854
+ }
855
+ flushPendingActivity(terminalId) {
856
+ if (terminalId) {
857
+ const lastActiveAt = this.pendingActivityByTerminalId.get(terminalId);
858
+ if (!lastActiveAt) {
859
+ return;
860
+ }
861
+ this.pendingActivityByTerminalId.delete(terminalId);
862
+ this.terminalInstanceRepository.touchLastActiveAt(terminalId, lastActiveAt);
863
+ this.clearActivityFlushTimerIfIdle();
864
+ return;
865
+ }
866
+ if (this.pendingActivityByTerminalId.size === 0) {
867
+ this.clearActivityFlushTimerIfIdle();
868
+ return;
869
+ }
870
+ const entries = [...this.pendingActivityByTerminalId.entries()];
871
+ this.pendingActivityByTerminalId.clear();
872
+ for (const [nextTerminalId, lastActiveAt] of entries) {
873
+ this.terminalInstanceRepository.touchLastActiveAt(nextTerminalId, lastActiveAt);
874
+ }
875
+ this.clearActivityFlushTimerIfIdle();
876
+ }
877
+ clearActivityFlushTimerIfIdle() {
878
+ if (this.pendingActivityByTerminalId.size > 0 || this.activityFlushTimer === null) {
879
+ return;
880
+ }
881
+ clearTimeout(this.activityFlushTimer);
882
+ this.activityFlushTimer = null;
883
+ }
700
884
  }
701
885
  function normalizeTerminalHistoryContent(content) {
702
886
  return content
@@ -730,17 +914,36 @@ function sanitizeShell(shell) {
730
914
  }
731
915
  return value;
732
916
  }
733
- function resolveRequestedRuntimeType(input) {
917
+ function resolveRequestedRuntimeType(input, shell) {
734
918
  const runtimeType = input ?? (process.platform === "win32" ? "embedded-pty" : "tmux");
735
- if (runtimeType !== "embedded-pty" &&
736
- runtimeType !== "tmux") {
919
+ if (runtimeType === "embedded-pty") {
920
+ return runtimeType;
921
+ }
922
+ if (process.platform === "win32") {
923
+ if (runtimeType === "tmux" || isConptyRuntimeType(runtimeType)) {
924
+ return resolveWindowsPersistentRuntimeType(shell);
925
+ }
737
926
  throw new AppError({
738
927
  statusCode: 400,
739
928
  errorCode: "UNSUPPORTED_TERMINAL_RUNTIME",
740
- detail: `当前 Host 还未实现 runtime=${runtimeType}`
929
+ detail: `褰撳墠 Host 杩樻湭瀹炵幇 runtime=${runtimeType}`
741
930
  });
742
931
  }
743
- return runtimeType;
932
+ if (runtimeType === "tmux") {
933
+ return runtimeType;
934
+ }
935
+ if (isConptyRuntimeType(runtimeType)) {
936
+ throw new AppError({
937
+ statusCode: 400,
938
+ errorCode: "RUNTIME_UNSUPPORTED_PLATFORM",
939
+ detail: "conpty runtime 浠呮敮鎸?Windows"
940
+ });
941
+ }
942
+ throw new AppError({
943
+ statusCode: 400,
944
+ errorCode: "UNSUPPORTED_TERMINAL_RUNTIME",
945
+ detail: `褰撳墠 Host 杩樻湭瀹炵幇 runtime=${runtimeType}`
946
+ });
744
947
  }
745
948
  function buildAttachTarget(runtimeType, runtimeSessionId) {
746
949
  if (runtimeType === "embedded-pty") {
@@ -788,6 +991,12 @@ function resolveClosedStatusDetail(reason, currentStatusDetail) {
788
991
  }
789
992
  return currentStatusDetail;
790
993
  }
994
+ function normalizeTerminalErrorDetail(sessionDetail, exitCode) {
995
+ if (sessionDetail && sessionDetail !== "EMBEDDED_RUNTIME_NOT_RECOVERABLE") {
996
+ return sessionDetail;
997
+ }
998
+ return `终端异常退出,exitCode=${exitCode ?? "unknown"}`;
999
+ }
791
1000
  function shouldMarkRuntimeLost(terminal, detail) {
792
1001
  return terminal.runtimeType !== "embedded-pty" && Boolean(detail?.includes("检查失败"));
793
1002
  }