@code-yeongyu/senpi 2026.9.24-3 → 2026.9.25

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 (256) hide show
  1. package/CHANGELOG.md +26 -0
  2. package/dist/bundle/chunks/{app-server-command-OTRHYRIB.js → app-server-command-RB6QUPRI.js} +1 -1
  3. package/dist/bundle/chunks/{chunk-UM43CBL4.js → chunk-JJ3NJZBD.js} +118 -111
  4. package/dist/bundle/chunks/chunk-SGYEOIOJ.js +8 -0
  5. package/dist/bundle/chunks/chunk-YS5YEX4S.js +2 -0
  6. package/dist/bundle/chunks/{cli-main-TX6I4X4F.js → cli-main-KO53KYDN.js} +1 -1
  7. package/dist/bundle/chunks/{interactive-mode-UI5Q6FGY.js → interactive-mode-ABDNLJSI.js} +1 -1
  8. package/dist/bundle/chunks/{multi-session-host-D6HMI3RQ.js → multi-session-host-JV3MNWXZ.js} +1 -1
  9. package/dist/bundle/chunks/{package-manager-cli-3UWH3ZZS.js → package-manager-cli-ZYYAVCXU.js} +1 -1
  10. package/dist/bundle/chunks/{rpc-mode-LXIZHJSJ.js → rpc-mode-Q2OUJURB.js} +1 -1
  11. package/dist/bundle/chunks/runtime-session-XBT646O5.js +2 -0
  12. package/dist/bundle/chunks/session-worker.js +184 -177
  13. package/dist/bundle/cli.js +1 -1
  14. package/dist/bundle/index.js +1 -1
  15. package/dist/bundle/rpc-entry.js +1 -1
  16. package/dist/core/agent-session.d.ts.map +1 -1
  17. package/dist/core/agent-session.js +1 -1
  18. package/dist/core/agent-session.js.map +1 -1
  19. package/dist/core/extensions/builtin/cursor-cli-oauth/stream.d.ts +7 -0
  20. package/dist/core/extensions/builtin/cursor-cli-oauth/stream.d.ts.map +1 -1
  21. package/dist/core/extensions/builtin/cursor-cli-oauth/stream.js +23 -4
  22. package/dist/core/extensions/builtin/cursor-cli-oauth/stream.js.map +1 -1
  23. package/dist/core/extensions/builtin/terminal/durable-command.d.ts +11 -16
  24. package/dist/core/extensions/builtin/terminal/durable-command.d.ts.map +1 -1
  25. package/dist/core/extensions/builtin/terminal/durable-command.js +97 -43
  26. package/dist/core/extensions/builtin/terminal/durable-command.js.map +1 -1
  27. package/dist/core/extensions/builtin/terminal/durable-file.d.ts.map +1 -1
  28. package/dist/core/extensions/builtin/terminal/durable-file.js +11 -4
  29. package/dist/core/extensions/builtin/terminal/durable-file.js.map +1 -1
  30. package/dist/core/extensions/builtin/terminal/extension-state.d.ts +26 -0
  31. package/dist/core/extensions/builtin/terminal/extension-state.d.ts.map +1 -0
  32. package/dist/core/extensions/builtin/terminal/extension-state.js +151 -0
  33. package/dist/core/extensions/builtin/terminal/extension-state.js.map +1 -0
  34. package/dist/core/extensions/builtin/terminal/extension.d.ts.map +1 -1
  35. package/dist/core/extensions/builtin/terminal/extension.js +32 -308
  36. package/dist/core/extensions/builtin/terminal/extension.js.map +1 -1
  37. package/dist/core/extensions/builtin/terminal/lease-file.d.ts +52 -0
  38. package/dist/core/extensions/builtin/terminal/lease-file.d.ts.map +1 -0
  39. package/dist/core/extensions/builtin/terminal/lease-file.js +205 -0
  40. package/dist/core/extensions/builtin/terminal/lease-file.js.map +1 -0
  41. package/dist/core/extensions/builtin/terminal/lease-keeper.d.ts +33 -0
  42. package/dist/core/extensions/builtin/terminal/lease-keeper.d.ts.map +1 -0
  43. package/dist/core/extensions/builtin/terminal/lease-keeper.js +110 -0
  44. package/dist/core/extensions/builtin/terminal/lease-keeper.js.map +1 -0
  45. package/dist/core/extensions/builtin/terminal/manager.d.ts +1 -0
  46. package/dist/core/extensions/builtin/terminal/manager.d.ts.map +1 -1
  47. package/dist/core/extensions/builtin/terminal/manager.js +6 -0
  48. package/dist/core/extensions/builtin/terminal/manager.js.map +1 -1
  49. package/dist/core/extensions/builtin/terminal/manifest-lease.d.ts +48 -4
  50. package/dist/core/extensions/builtin/terminal/manifest-lease.d.ts.map +1 -1
  51. package/dist/core/extensions/builtin/terminal/manifest-lease.js +141 -59
  52. package/dist/core/extensions/builtin/terminal/manifest-lease.js.map +1 -1
  53. package/dist/core/extensions/builtin/terminal/monitor-registry.d.ts +2 -0
  54. package/dist/core/extensions/builtin/terminal/monitor-registry.d.ts.map +1 -1
  55. package/dist/core/extensions/builtin/terminal/monitor-registry.js +15 -10
  56. package/dist/core/extensions/builtin/terminal/monitor-registry.js.map +1 -1
  57. package/dist/core/extensions/builtin/terminal/monitor-state-dir.d.ts +11 -0
  58. package/dist/core/extensions/builtin/terminal/monitor-state-dir.d.ts.map +1 -0
  59. package/dist/core/extensions/builtin/terminal/monitor-state-dir.js +28 -0
  60. package/dist/core/extensions/builtin/terminal/monitor-state-dir.js.map +1 -0
  61. package/dist/core/extensions/builtin/terminal/orphan-reaper.d.ts +41 -0
  62. package/dist/core/extensions/builtin/terminal/orphan-reaper.d.ts.map +1 -0
  63. package/dist/core/extensions/builtin/terminal/orphan-reaper.js +131 -0
  64. package/dist/core/extensions/builtin/terminal/orphan-reaper.js.map +1 -0
  65. package/dist/core/extensions/builtin/terminal/process-identity.d.ts +21 -0
  66. package/dist/core/extensions/builtin/terminal/process-identity.d.ts.map +1 -0
  67. package/dist/core/extensions/builtin/terminal/process-identity.js +34 -0
  68. package/dist/core/extensions/builtin/terminal/process-identity.js.map +1 -0
  69. package/dist/core/extensions/builtin/terminal/process-start-probe.d.ts +6 -0
  70. package/dist/core/extensions/builtin/terminal/process-start-probe.d.ts.map +1 -0
  71. package/dist/core/extensions/builtin/terminal/process-start-probe.js +60 -0
  72. package/dist/core/extensions/builtin/terminal/process-start-probe.js.map +1 -0
  73. package/dist/core/extensions/builtin/terminal/prompt.d.ts.map +1 -1
  74. package/dist/core/extensions/builtin/terminal/prompt.js +8 -4
  75. package/dist/core/extensions/builtin/terminal/prompt.js.map +1 -1
  76. package/dist/core/extensions/builtin/terminal/restore-digest.d.ts +54 -0
  77. package/dist/core/extensions/builtin/terminal/restore-digest.d.ts.map +1 -0
  78. package/dist/core/extensions/builtin/terminal/restore-digest.js +133 -0
  79. package/dist/core/extensions/builtin/terminal/restore-digest.js.map +1 -0
  80. package/dist/core/extensions/builtin/terminal/restore-session.d.ts +56 -0
  81. package/dist/core/extensions/builtin/terminal/restore-session.d.ts.map +1 -0
  82. package/dist/core/extensions/builtin/terminal/restore-session.js +238 -0
  83. package/dist/core/extensions/builtin/terminal/restore-session.js.map +1 -0
  84. package/dist/core/extensions/builtin/terminal/restore.d.ts +52 -19
  85. package/dist/core/extensions/builtin/terminal/restore.d.ts.map +1 -1
  86. package/dist/core/extensions/builtin/terminal/restore.js +63 -138
  87. package/dist/core/extensions/builtin/terminal/restore.js.map +1 -1
  88. package/dist/core/extensions/builtin/terminal/runtime-session.d.ts +5 -0
  89. package/dist/core/extensions/builtin/terminal/runtime-session.d.ts.map +1 -1
  90. package/dist/core/extensions/builtin/terminal/runtime-session.js +17 -0
  91. package/dist/core/extensions/builtin/terminal/runtime-session.js.map +1 -1
  92. package/dist/core/extensions/builtin/terminal/session-activity.d.ts +8 -0
  93. package/dist/core/extensions/builtin/terminal/session-activity.d.ts.map +1 -0
  94. package/dist/core/extensions/builtin/terminal/session-activity.js +41 -0
  95. package/dist/core/extensions/builtin/terminal/session-activity.js.map +1 -0
  96. package/dist/core/extensions/builtin/terminal/shared.d.ts +14 -0
  97. package/dist/core/extensions/builtin/terminal/shared.d.ts.map +1 -1
  98. package/dist/core/extensions/builtin/terminal/shared.js +14 -0
  99. package/dist/core/extensions/builtin/terminal/shared.js.map +1 -1
  100. package/dist/core/extensions/builtin/terminal/terminal-manifest-model.d.ts +8 -0
  101. package/dist/core/extensions/builtin/terminal/terminal-manifest-model.d.ts.map +1 -1
  102. package/dist/core/extensions/builtin/terminal/terminal-manifest-model.js.map +1 -1
  103. package/dist/core/extensions/builtin/terminal/terminal-manifest-parse.d.ts +15 -0
  104. package/dist/core/extensions/builtin/terminal/terminal-manifest-parse.d.ts.map +1 -0
  105. package/dist/core/extensions/builtin/terminal/terminal-manifest-parse.js +148 -0
  106. package/dist/core/extensions/builtin/terminal/terminal-manifest-parse.js.map +1 -0
  107. package/dist/core/extensions/builtin/terminal/terminal-manifest.d.ts +15 -2
  108. package/dist/core/extensions/builtin/terminal/terminal-manifest.d.ts.map +1 -1
  109. package/dist/core/extensions/builtin/terminal/terminal-manifest.js +62 -11
  110. package/dist/core/extensions/builtin/terminal/terminal-manifest.js.map +1 -1
  111. package/dist/core/extensions/builtin/terminal/terminal-state-gc.d.ts +17 -0
  112. package/dist/core/extensions/builtin/terminal/terminal-state-gc.d.ts.map +1 -0
  113. package/dist/core/extensions/builtin/terminal/terminal-state-gc.js +81 -0
  114. package/dist/core/extensions/builtin/terminal/terminal-state-gc.js.map +1 -0
  115. package/dist/core/extensions/builtin/terminal/tools/context.d.ts +5 -0
  116. package/dist/core/extensions/builtin/terminal/tools/context.d.ts.map +1 -1
  117. package/dist/core/extensions/builtin/terminal/tools/context.js.map +1 -1
  118. package/dist/core/extensions/builtin/terminal/tools/kill-bash.d.ts.map +1 -1
  119. package/dist/core/extensions/builtin/terminal/tools/kill-bash.js +16 -1
  120. package/dist/core/extensions/builtin/terminal/tools/kill-bash.js.map +1 -1
  121. package/dist/core/extensions/builtin/terminal/tools/monitor-manifest-binding.d.ts +27 -0
  122. package/dist/core/extensions/builtin/terminal/tools/monitor-manifest-binding.d.ts.map +1 -0
  123. package/dist/core/extensions/builtin/terminal/tools/monitor-manifest-binding.js +75 -0
  124. package/dist/core/extensions/builtin/terminal/tools/monitor-manifest-binding.js.map +1 -0
  125. package/dist/core/extensions/builtin/terminal/tools/monitor-schema.d.ts +35 -0
  126. package/dist/core/extensions/builtin/terminal/tools/monitor-schema.d.ts.map +1 -0
  127. package/dist/core/extensions/builtin/terminal/tools/monitor-schema.js +67 -0
  128. package/dist/core/extensions/builtin/terminal/tools/monitor-schema.js.map +1 -0
  129. package/dist/core/extensions/builtin/terminal/tools/monitor.d.ts +14 -35
  130. package/dist/core/extensions/builtin/terminal/tools/monitor.d.ts.map +1 -1
  131. package/dist/core/extensions/builtin/terminal/tools/monitor.js +30 -110
  132. package/dist/core/extensions/builtin/terminal/tools/monitor.js.map +1 -1
  133. package/dist/core/extensions/builtin/todotools/first-turn.d.ts +7 -6
  134. package/dist/core/extensions/builtin/todotools/first-turn.d.ts.map +1 -1
  135. package/dist/core/extensions/builtin/todotools/first-turn.js +8 -8
  136. package/dist/core/extensions/builtin/todotools/first-turn.js.map +1 -1
  137. package/dist/core/extensions/builtin/todotools/index.js +1 -1
  138. package/dist/core/extensions/builtin/todotools/index.js.map +1 -1
  139. package/dist/core/extensions/builtin/todotools/todo-format.js +1 -1
  140. package/dist/core/extensions/builtin/todotools/todo-format.js.map +1 -1
  141. package/dist/core/extensions/builtin/ttsr/detectors/repetitive-turns.d.ts.map +1 -1
  142. package/dist/core/extensions/builtin/ttsr/detectors/repetitive-turns.js +4 -0
  143. package/dist/core/extensions/builtin/ttsr/detectors/repetitive-turns.js.map +1 -1
  144. package/dist/core/extensions/builtin/ttsr/prompts.js +1 -1
  145. package/dist/core/extensions/builtin/ttsr/prompts.js.map +1 -1
  146. package/dist/core/extensions/runner.d.ts +1 -0
  147. package/dist/core/extensions/runner.d.ts.map +1 -1
  148. package/dist/core/extensions/runner.js +1 -0
  149. package/dist/core/extensions/runner.js.map +1 -1
  150. package/dist/core/extensions/types.d.ts +6 -0
  151. package/dist/core/extensions/types.d.ts.map +1 -1
  152. package/dist/core/extensions/types.js.map +1 -1
  153. package/docs/extensions.md +2 -0
  154. package/node_modules/@code-yeongyu/senpi-codemode/CHANGELOG.md +12 -0
  155. package/node_modules/@code-yeongyu/senpi-codemode/package.json +4 -4
  156. package/node_modules/@code-yeongyu/senpi-desktop-engine/README.md +31 -0
  157. package/node_modules/@code-yeongyu/senpi-desktop-engine/dist/handshake.d.ts +38 -0
  158. package/node_modules/@code-yeongyu/senpi-desktop-engine/dist/handshake.d.ts.map +1 -0
  159. package/node_modules/@code-yeongyu/senpi-desktop-engine/dist/handshake.js +117 -0
  160. package/node_modules/@code-yeongyu/senpi-desktop-engine/dist/handshake.js.map +1 -0
  161. package/node_modules/@code-yeongyu/senpi-desktop-engine/dist/index.d.ts +3 -0
  162. package/node_modules/@code-yeongyu/senpi-desktop-engine/dist/index.d.ts.map +1 -0
  163. package/node_modules/@code-yeongyu/senpi-desktop-engine/dist/index.js +3 -0
  164. package/node_modules/@code-yeongyu/senpi-desktop-engine/dist/index.js.map +1 -0
  165. package/node_modules/@code-yeongyu/senpi-desktop-engine/dist/locator.d.ts +42 -0
  166. package/node_modules/@code-yeongyu/senpi-desktop-engine/dist/locator.d.ts.map +1 -0
  167. package/node_modules/@code-yeongyu/senpi-desktop-engine/dist/locator.js +79 -0
  168. package/node_modules/@code-yeongyu/senpi-desktop-engine/dist/locator.js.map +1 -0
  169. package/node_modules/@code-yeongyu/senpi-desktop-engine/native/check-prebuild-fresh.mjs +160 -0
  170. package/node_modules/@code-yeongyu/senpi-desktop-engine/native/index.d.ts +20 -0
  171. package/node_modules/@code-yeongyu/senpi-desktop-engine/native/index.js +23 -0
  172. package/node_modules/@code-yeongyu/senpi-desktop-engine/native/prebuilds/darwin-arm64/senpi-desktop-engine +0 -0
  173. package/node_modules/@code-yeongyu/senpi-desktop-engine/package.json +53 -0
  174. package/node_modules/@code-yeongyu/senpi-desktop-prelude/README.md +20 -0
  175. package/node_modules/@code-yeongyu/senpi-desktop-prelude/dist/index.d.ts +2 -0
  176. package/node_modules/@code-yeongyu/senpi-desktop-prelude/dist/index.d.ts.map +1 -0
  177. package/node_modules/@code-yeongyu/senpi-desktop-prelude/dist/index.js +2 -0
  178. package/node_modules/@code-yeongyu/senpi-desktop-prelude/dist/index.js.map +1 -0
  179. package/node_modules/@code-yeongyu/senpi-desktop-prelude/package.json +44 -0
  180. package/node_modules/@code-yeongyu/senpi-desktop-protocol/README.md +20 -0
  181. package/node_modules/@code-yeongyu/senpi-desktop-protocol/dist/audit.d.ts +38 -0
  182. package/node_modules/@code-yeongyu/senpi-desktop-protocol/dist/audit.d.ts.map +1 -0
  183. package/node_modules/@code-yeongyu/senpi-desktop-protocol/dist/audit.js +2 -0
  184. package/node_modules/@code-yeongyu/senpi-desktop-protocol/dist/audit.js.map +1 -0
  185. package/node_modules/@code-yeongyu/senpi-desktop-protocol/dist/call.d.ts +28 -0
  186. package/node_modules/@code-yeongyu/senpi-desktop-protocol/dist/call.d.ts.map +1 -0
  187. package/node_modules/@code-yeongyu/senpi-desktop-protocol/dist/call.js +102 -0
  188. package/node_modules/@code-yeongyu/senpi-desktop-protocol/dist/call.js.map +1 -0
  189. package/node_modules/@code-yeongyu/senpi-desktop-protocol/dist/engine-schema.generated.d.ts +14 -0
  190. package/node_modules/@code-yeongyu/senpi-desktop-protocol/dist/engine-schema.generated.d.ts.map +1 -0
  191. package/node_modules/@code-yeongyu/senpi-desktop-protocol/dist/engine-schema.generated.js +71 -0
  192. package/node_modules/@code-yeongyu/senpi-desktop-protocol/dist/engine-schema.generated.js.map +1 -0
  193. package/node_modules/@code-yeongyu/senpi-desktop-protocol/dist/index.d.ts +7 -0
  194. package/node_modules/@code-yeongyu/senpi-desktop-protocol/dist/index.d.ts.map +1 -0
  195. package/node_modules/@code-yeongyu/senpi-desktop-protocol/dist/index.js +4 -0
  196. package/node_modules/@code-yeongyu/senpi-desktop-protocol/dist/index.js.map +1 -0
  197. package/node_modules/@code-yeongyu/senpi-desktop-protocol/dist/json-rpc.d.ts +66 -0
  198. package/node_modules/@code-yeongyu/senpi-desktop-protocol/dist/json-rpc.d.ts.map +1 -0
  199. package/node_modules/@code-yeongyu/senpi-desktop-protocol/dist/json-rpc.js +8 -0
  200. package/node_modules/@code-yeongyu/senpi-desktop-protocol/dist/json-rpc.js.map +1 -0
  201. package/node_modules/@code-yeongyu/senpi-desktop-protocol/dist/session.d.ts +44 -0
  202. package/node_modules/@code-yeongyu/senpi-desktop-protocol/dist/session.d.ts.map +1 -0
  203. package/node_modules/@code-yeongyu/senpi-desktop-protocol/dist/session.js +2 -0
  204. package/node_modules/@code-yeongyu/senpi-desktop-protocol/dist/session.js.map +1 -0
  205. package/node_modules/@code-yeongyu/senpi-desktop-protocol/dist/wire.d.ts +59 -0
  206. package/node_modules/@code-yeongyu/senpi-desktop-protocol/dist/wire.d.ts.map +1 -0
  207. package/node_modules/@code-yeongyu/senpi-desktop-protocol/dist/wire.js +2 -0
  208. package/node_modules/@code-yeongyu/senpi-desktop-protocol/dist/wire.js.map +1 -0
  209. package/node_modules/@code-yeongyu/senpi-desktop-protocol/package.json +45 -0
  210. package/node_modules/@code-yeongyu/senpi-desktop-service/README.md +27 -0
  211. package/node_modules/@code-yeongyu/senpi-desktop-service/dist/index.d.ts +2 -0
  212. package/node_modules/@code-yeongyu/senpi-desktop-service/dist/index.d.ts.map +1 -0
  213. package/node_modules/@code-yeongyu/senpi-desktop-service/dist/index.js +2 -0
  214. package/node_modules/@code-yeongyu/senpi-desktop-service/dist/index.js.map +1 -0
  215. package/node_modules/@code-yeongyu/senpi-desktop-service/package.json +44 -0
  216. package/node_modules/@code-yeongyu/senpi-desktop-tool/README.md +20 -0
  217. package/node_modules/@code-yeongyu/senpi-desktop-tool/dist/index.d.ts +2 -0
  218. package/node_modules/@code-yeongyu/senpi-desktop-tool/dist/index.d.ts.map +1 -0
  219. package/node_modules/@code-yeongyu/senpi-desktop-tool/dist/index.js +2 -0
  220. package/node_modules/@code-yeongyu/senpi-desktop-tool/dist/index.js.map +1 -0
  221. package/node_modules/@code-yeongyu/senpi-desktop-tool/package.json +44 -0
  222. package/node_modules/@earendil-works/pi-agent-core/package.json +3 -3
  223. package/node_modules/@earendil-works/pi-ai/dist/providers/data/.manifest.json +1 -1
  224. package/node_modules/@earendil-works/pi-ai/dist/providers/data/cerebras.json +1 -1
  225. package/node_modules/@earendil-works/pi-ai/dist/providers/data/openrouter.json +1 -1
  226. package/node_modules/@earendil-works/pi-ai/dist/providers/data/vercel-ai-gateway.json +1 -1
  227. package/node_modules/@earendil-works/pi-ai/package.json +2 -2
  228. package/node_modules/@earendil-works/pi-pty/dist/pipe-fallback.d.ts +2 -0
  229. package/node_modules/@earendil-works/pi-pty/dist/pipe-fallback.d.ts.map +1 -1
  230. package/node_modules/@earendil-works/pi-pty/dist/pipe-fallback.js +6 -0
  231. package/node_modules/@earendil-works/pi-pty/dist/pipe-fallback.js.map +1 -1
  232. package/node_modules/@earendil-works/pi-pty/dist/session-bun.d.ts +1 -0
  233. package/node_modules/@earendil-works/pi-pty/dist/session-bun.d.ts.map +1 -1
  234. package/node_modules/@earendil-works/pi-pty/dist/session-bun.js +1 -0
  235. package/node_modules/@earendil-works/pi-pty/dist/session-bun.js.map +1 -1
  236. package/node_modules/@earendil-works/pi-pty/dist/session-native.js +5 -0
  237. package/node_modules/@earendil-works/pi-pty/dist/session-native.js.map +1 -1
  238. package/node_modules/@earendil-works/pi-pty/dist/session-raw-tail.d.ts +11 -0
  239. package/node_modules/@earendil-works/pi-pty/dist/session-raw-tail.d.ts.map +1 -0
  240. package/node_modules/@earendil-works/pi-pty/dist/session-raw-tail.js +24 -0
  241. package/node_modules/@earendil-works/pi-pty/dist/session-raw-tail.js.map +1 -0
  242. package/node_modules/@earendil-works/pi-pty/dist/session-types.d.ts +2 -0
  243. package/node_modules/@earendil-works/pi-pty/dist/session-types.d.ts.map +1 -1
  244. package/node_modules/@earendil-works/pi-pty/dist/session-types.js.map +1 -1
  245. package/node_modules/@earendil-works/pi-pty/dist/session.d.ts +4 -5
  246. package/node_modules/@earendil-works/pi-pty/dist/session.d.ts.map +1 -1
  247. package/node_modules/@earendil-works/pi-pty/dist/session.js +11 -16
  248. package/node_modules/@earendil-works/pi-pty/dist/session.js.map +1 -1
  249. package/node_modules/@earendil-works/pi-pty/native/prebuilds/darwin-arm64/senpi_pty.darwin-arm64.node +0 -0
  250. package/node_modules/@earendil-works/pi-pty/package.json +1 -1
  251. package/node_modules/@earendil-works/pi-telemetry/package.json +1 -1
  252. package/node_modules/@earendil-works/pi-tui/package.json +1 -1
  253. package/package.json +22 -7
  254. package/dist/bundle/chunks/chunk-GFUZPFHN.js +0 -2
  255. package/dist/bundle/chunks/chunk-JBOV6VNK.js +0 -8
  256. package/dist/bundle/chunks/runtime-session-F3EXC44D.js +0 -2
@@ -0,0 +1,238 @@
1
+ /**
2
+ * A session's persistence lifecycle across process restarts: take the per-session lease, restore
3
+ * the persisted monitors on a detached promise (session_start never waits on a grace window), and
4
+ * deliver ONE decided digest through a slot that holds it until a model is bound. A live foreign
5
+ * holder defers everything: the footer says who holds the monitors, and a lease keeper takes the
6
+ * session over the moment that process exits, running the same restore exactly once. Monitors
7
+ * created while waiting are queued and reach the manifest after the takeover.
8
+ */
9
+ import { existsSync } from "node:fs";
10
+ import { encodedSessionId } from "../../../session-sidecar-store.js";
11
+ import { createRestartableCommandHandler } from "./durable-command.js";
12
+ import { createCheckpointedFileRestoreHandler } from "./durable-file.js";
13
+ import { createLeaseKeeper } from "./lease-keeper.js";
14
+ import { acquireTerminalLease, releaseTerminalLease, retireLeaseToken, } from "./manifest-lease.js";
15
+ import { removeMonitorStateDir, terminalStateDir } from "./monitor-state-dir.js";
16
+ import { ownProcessStartedAtMs, processBootAtMs } from "./process-identity.js";
17
+ import { reapplyPersistedMute, restoreTerminalState } from "./restore.js";
18
+ import { buildRestoreDigest, createDigestSlot, deliverRestoreDigest } from "./restore-digest.js";
19
+ import { createTerminalManifestStore, TerminalManifestWriter } from "./terminal-manifest.js";
20
+ import { sweepTerminalStateDir } from "./terminal-state-gc.js";
21
+ import { bindTerminalManifestWriter, unbindTerminalManifestWriter } from "./tools/monitor.js";
22
+ export const RESTORE_STATUS_KEY = "terminal-restore";
23
+ const errorText = (error) => (error instanceof Error ? error.message : String(error));
24
+ /** Test-only timing overrides; production uses the modules' own defaults. */
25
+ export const restoreSessionTestHooks = {};
26
+ const decisions = new Map();
27
+ function armDecision(sessionKey) {
28
+ let resolve = () => { };
29
+ const promise = new Promise((settle) => {
30
+ resolve = settle;
31
+ });
32
+ decisions.set(sessionKey, { promise, resolve });
33
+ return resolve;
34
+ }
35
+ /** Settles once this session's current generation has decided its restore (or had nothing to decide). */
36
+ export function whenRestoreDecided(sessionKey) {
37
+ return decisions.get(sessionKey)?.promise ?? Promise.resolve();
38
+ }
39
+ export function flushRestoreDigest(pi, state) {
40
+ state.digestSlot.flush((message) => {
41
+ // terminal.notify "off" keeps the model channel silent; the user still sees the digest.
42
+ if (state.settings.notify !== "off")
43
+ return deliverRestoreDigest(pi, state.ctx, message);
44
+ state.ctx?.ui?.notify?.(message.content, "info");
45
+ return true;
46
+ });
47
+ }
48
+ function emptyDigest() {
49
+ return {
50
+ restored: 0,
51
+ lost: 0,
52
+ expired: 0,
53
+ muted: 0,
54
+ completed: 0,
55
+ attachedElsewhere: 0,
56
+ storeError: false,
57
+ results: [],
58
+ backgroundSessions: [],
59
+ downtimeMs: 0,
60
+ };
61
+ }
62
+ function restoreHandlers(state, toolCtx, bundle) {
63
+ const writer = state.manifestWriter;
64
+ if (writer === null)
65
+ throw new Error("restore handlers need the bound manifest writer");
66
+ const registry = bundle.monitors;
67
+ const adopting = (handler) => async (monitor, context) => {
68
+ const result = await handler(monitor, context);
69
+ // Only a LIVE entry is re-adopted, so the next persist keeps it and drops the rest.
70
+ if (result.outcome === "restored" || result.outcome === "muted") {
71
+ writer.adoptRestored(result.runtime === undefined ? monitor : { ...monitor, runtime: result.runtime });
72
+ registry.adoptFireWindow(monitor.monitorId, monitor.fireWindow);
73
+ }
74
+ return result;
75
+ };
76
+ let freshFileRuntimeId;
77
+ const checkpointedFile = createCheckpointedFileRestoreHandler({
78
+ registry,
79
+ writer,
80
+ bindMonitorId: (monitorId, runtimeId) => {
81
+ freshFileRuntimeId = runtimeId;
82
+ bundle.manager.bindMonitorId(monitorId, runtimeId);
83
+ },
84
+ });
85
+ return {
86
+ "restartable-command": adopting(createRestartableCommandHandler({
87
+ ctx: toolCtx,
88
+ registry,
89
+ ...(restoreSessionTestHooks.graceMs !== undefined ? { graceMs: restoreSessionTestHooks.graceMs } : {}),
90
+ })),
91
+ "checkpointed-file": adopting(async (monitor, context) => {
92
+ freshFileRuntimeId = undefined;
93
+ const result = await checkpointedFile(monitor, context);
94
+ if (result.outcome !== "restored" || freshFileRuntimeId === undefined)
95
+ return result;
96
+ return { ...result, outcome: reapplyPersistedMute(registry, monitor, freshFileRuntimeId) };
97
+ }),
98
+ };
99
+ }
100
+ /** Non-reload session_start (and a reload that does not hold the lease): take ownership or wait for it. */
101
+ export async function startPersistence({ pi, state, toolCtx, sessionKey }) {
102
+ state.generation += 1;
103
+ state.digestSlot = createDigestSlot();
104
+ const decided = armDecision(sessionKey);
105
+ const ctx = state.ctx;
106
+ const dir = terminalStateDir(ctx);
107
+ const sessionManager = ctx?.sessionManager;
108
+ if (dir === undefined || sessionManager === undefined)
109
+ return decided();
110
+ const encoded = encodedSessionId(sessionKey);
111
+ const generation = state.generation;
112
+ const own = async (lease) => {
113
+ ctx?.ui?.setStatus?.(RESTORE_STATUS_KEY, undefined);
114
+ state.lease = { path: lease.path, pid: lease.pid, token: lease.token };
115
+ const writer = new TerminalManifestWriter({ session: sessionManager });
116
+ state.manifestWriter = writer;
117
+ state.recordedBackgroundIds.clear();
118
+ const bundle = state.bundle;
119
+ let restoredFromReadableManifest = false;
120
+ if (bundle !== null && existsSync(writer.store.filePath)) {
121
+ let digest;
122
+ try {
123
+ digest = await restoreTerminalState({
124
+ manifest: writer.store,
125
+ handlers: restoreHandlers(state, toolCtx, bundle),
126
+ sessionFile: sessionManager.getSessionFile?.(),
127
+ processStartedAtMs: ownProcessStartedAtMs(),
128
+ });
129
+ }
130
+ catch (error) {
131
+ // A restore that throws (an unreadable transcript, a kill error on a confirmed orphan)
132
+ // still decides: the session hears it failed, and the lease stays bound for shutdown.
133
+ ctx?.ui?.notify?.(`Terminal restore failed: ${errorText(error)}`, "warning");
134
+ digest = { ...emptyDigest(), storeError: true };
135
+ }
136
+ // A watch that did not come back never runs again: its baseline dir goes with it.
137
+ await Promise.all(digest.results
138
+ .filter((result) => result.outcome === "lost" || result.outcome === "expired" || result.outcome === "completed")
139
+ .map((result) => removeMonitorStateDir(dir, result.monitorId)));
140
+ if (generation !== state.generation)
141
+ return;
142
+ restoredFromReadableManifest = !digest.storeError;
143
+ if (digest.storeError || digest.results.length > 0 || digest.backgroundSessions.length > 0) {
144
+ state.digestSlot.set(buildRestoreDigest(digest, { generation, outcome: "decided" }));
145
+ }
146
+ }
147
+ // Bound AFTER the restore read the file: draining queued specs writes the manifest.
148
+ bindTerminalManifestWriter(sessionKey, writer);
149
+ // A corrupt manifest is left as it was (fail closed); a readable one is rewritten to the truth.
150
+ if (restoredFromReadableManifest)
151
+ await writer.persistRestored();
152
+ state.ensurePersistence = null;
153
+ const self = { pid: process.pid, bootAtMs: processBootAtMs(), processStartedAtMs: ownProcessStartedAtMs() };
154
+ // Housekeeping only: a failed sweep leaves the files for the next start to reclaim.
155
+ void sweepTerminalStateDir(dir, { self, keep: new Set([`${encoded}.lease`, `${encoded}.json`]) }).catch(() => undefined);
156
+ flushRestoreDigest(pi, state);
157
+ decided();
158
+ };
159
+ const wait = (holder) => {
160
+ ctx?.ui?.setStatus?.(RESTORE_STATUS_KEY, holder === null ? "monitors waiting for the session lease" : `monitors held by pid ${holder.pid}`);
161
+ state.digestSlot.set(buildRestoreDigest(emptyDigest(), {
162
+ generation,
163
+ outcome: "deferred",
164
+ ...(holder === null ? {} : { holderPid: holder.pid }),
165
+ }));
166
+ state.keeper?.stop();
167
+ state.keeper = createLeaseKeeper({
168
+ dir,
169
+ encodedSessionId: encoded,
170
+ ...(restoreSessionTestHooks.keeperIntervalMs !== undefined
171
+ ? { intervalMs: restoreSessionTestHooks.keeperIntervalMs }
172
+ : {}),
173
+ onTakeover: (lease) => {
174
+ state.restoreInFlight = own(lease);
175
+ return state.restoreInFlight;
176
+ },
177
+ onError: (error) => {
178
+ const waiting = state.keeper?.state === "waiting";
179
+ ctx?.ui?.notify?.(`Terminal lease ${waiting ? "check failed, still waiting" : "takeover failed"}: ${errorText(error)}`, "warning");
180
+ },
181
+ });
182
+ state.keeper.start(holder);
183
+ };
184
+ const acquire = async () => {
185
+ // The lease kept changing (or the dir failed): never leave the session undecided; the keeper retries.
186
+ const lease = await acquireTerminalLease({ dir, encodedSessionId: encoded }).catch((error) => {
187
+ ctx?.ui?.notify?.(`Terminal lease not acquired yet, retrying: ${errorText(error)}`, "warning");
188
+ return null;
189
+ });
190
+ if (lease === null)
191
+ return wait(null);
192
+ if (lease.acquired) {
193
+ state.restoreInFlight = own(lease);
194
+ return;
195
+ }
196
+ wait(lease.holder);
197
+ };
198
+ if (existsSync(createTerminalManifestStore(sessionManager).filePath))
199
+ return acquire();
200
+ // Nothing to restore: the lease and the recorder wait for the first real registration.
201
+ decided();
202
+ let binding = null;
203
+ state.ensurePersistence = () => {
204
+ binding ??= acquire().then(() => state.restoreInFlight);
205
+ return binding;
206
+ };
207
+ }
208
+ /** session_shutdown (not reload): stop waiting, finish any restore, suspend and flush, release. */
209
+ export async function stopPersistence(state, sessionKey) {
210
+ const keeper = state.keeper;
211
+ keeper?.stop();
212
+ state.keeper = null;
213
+ // A tick in flight may still be acquiring (then releases) or taking over (then restores).
214
+ await keeper?.settled();
215
+ const lease = state.lease;
216
+ state.lease = null;
217
+ if (lease !== null)
218
+ retireLeaseToken(lease.token);
219
+ await state.restoreInFlight;
220
+ const writer = state.manifestWriter;
221
+ if (writer && state.bundle)
222
+ await writer.observeMonitorState(state.bundle.monitors.snapshot());
223
+ detachPersistence(state, sessionKey);
224
+ if (writer)
225
+ await writer.recordShutdown();
226
+ if (lease !== null)
227
+ await releaseTerminalLease(lease);
228
+ decisions.get(sessionKey)?.resolve();
229
+ }
230
+ /** Detach the manifest recorder without writing (a reload keeps live state instead). */
231
+ export function detachPersistence(state, sessionKey) {
232
+ state.manifestWriter = null;
233
+ state.ensurePersistence = null;
234
+ state.recordedBackgroundIds.clear();
235
+ if (sessionKey !== undefined)
236
+ unbindTerminalManifestWriter(sessionKey);
237
+ }
238
+ //# sourceMappingURL=restore-session.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"restore-session.js","sourceRoot":"","sources":["../../../../../src/core/extensions/builtin/terminal/restore-session.ts"],"names":[],"mappings":"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","sourcesContent":["/**\n * A session's persistence lifecycle across process restarts: take the per-session lease, restore\n * the persisted monitors on a detached promise (session_start never waits on a grace window), and\n * deliver ONE decided digest through a slot that holds it until a model is bound. A live foreign\n * holder defers everything: the footer says who holds the monitors, and a lease keeper takes the\n * session over the moment that process exits, running the same restore exactly once. Monitors\n * created while waiting are queued and reach the manifest after the takeover.\n */\n\nimport { existsSync } from \"node:fs\";\nimport { encodedSessionId } from \"../../../session-sidecar-store.ts\";\nimport type { ExtensionAPI, ExtensionContext } from \"../../types.ts\";\nimport { createRestartableCommandHandler } from \"./durable-command.ts\";\nimport { createCheckpointedFileRestoreHandler } from \"./durable-file.ts\";\nimport { createLeaseKeeper, type LeaseKeeper } from \"./lease-keeper.ts\";\nimport {\n\ttype AcquireTerminalLeaseResult,\n\tacquireTerminalLease,\n\ttype LeaseHolder,\n\treleaseTerminalLease,\n\tretireLeaseToken,\n} from \"./manifest-lease.ts\";\nimport { removeMonitorStateDir, terminalStateDir } from \"./monitor-state-dir.ts\";\nimport { ownProcessStartedAtMs, processBootAtMs } from \"./process-identity.ts\";\nimport { type RestoreDigest, type RestoreHandler, reapplyPersistedMute, restoreTerminalState } from \"./restore.ts\";\nimport { buildRestoreDigest, createDigestSlot, type DigestSlot, deliverRestoreDigest } from \"./restore-digest.ts\";\nimport type { TerminalSessionBundle } from \"./session-bundle.ts\";\nimport { createTerminalManifestStore, TerminalManifestWriter } from \"./terminal-manifest.ts\";\nimport { sweepTerminalStateDir } from \"./terminal-state-gc.ts\";\nimport type { TerminalToolContext } from \"./tools/context.ts\";\nimport { bindTerminalManifestWriter, unbindTerminalManifestWriter } from \"./tools/monitor.ts\";\n\nexport const RESTORE_STATUS_KEY = \"terminal-restore\";\n\nconst errorText = (error: unknown): string => (error instanceof Error ? error.message : String(error));\n\n/** Test-only timing overrides; production uses the modules' own defaults. */\nexport const restoreSessionTestHooks: { keeperIntervalMs?: number; graceMs?: number } = {};\n\nexport interface PersistenceState {\n\tctx: ExtensionContext | undefined;\n\treadonly settings: { readonly notify: string };\n\tbundle: TerminalSessionBundle | null;\n\tlease: { path: string; pid: number; token: string } | null;\n\tmanifestWriter: TerminalManifestWriter | null;\n\tensurePersistence: (() => Promise<void>) | null;\n\trecordedBackgroundIds: Set<string>;\n\tkeeper: LeaseKeeper | null;\n\tdigestSlot: DigestSlot;\n\tgeneration: number;\n\trestoreInFlight: Promise<void>;\n}\n\nconst decisions = new Map<string, { promise: Promise<void>; resolve: () => void }>();\n\nfunction armDecision(sessionKey: string): () => void {\n\tlet resolve: () => void = () => {};\n\tconst promise = new Promise<void>((settle) => {\n\t\tresolve = settle;\n\t});\n\tdecisions.set(sessionKey, { promise, resolve });\n\treturn resolve;\n}\n\n/** Settles once this session's current generation has decided its restore (or had nothing to decide). */\nexport function whenRestoreDecided(sessionKey: string): Promise<void> {\n\treturn decisions.get(sessionKey)?.promise ?? Promise.resolve();\n}\n\nexport function flushRestoreDigest(pi: ExtensionAPI, state: PersistenceState): void {\n\tstate.digestSlot.flush((message) => {\n\t\t// terminal.notify \"off\" keeps the model channel silent; the user still sees the digest.\n\t\tif (state.settings.notify !== \"off\") return deliverRestoreDigest(pi, state.ctx, message);\n\t\tstate.ctx?.ui?.notify?.(message.content, \"info\");\n\t\treturn true;\n\t});\n}\n\nfunction emptyDigest(): RestoreDigest {\n\treturn {\n\t\trestored: 0,\n\t\tlost: 0,\n\t\texpired: 0,\n\t\tmuted: 0,\n\t\tcompleted: 0,\n\t\tattachedElsewhere: 0,\n\t\tstoreError: false,\n\t\tresults: [],\n\t\tbackgroundSessions: [],\n\t\tdowntimeMs: 0,\n\t};\n}\n\nfunction restoreHandlers(state: PersistenceState, toolCtx: TerminalToolContext, bundle: TerminalSessionBundle) {\n\tconst writer = state.manifestWriter;\n\tif (writer === null) throw new Error(\"restore handlers need the bound manifest writer\");\n\tconst registry = bundle.monitors;\n\tconst adopting =\n\t\t(handler: RestoreHandler): RestoreHandler =>\n\t\tasync (monitor, context) => {\n\t\t\tconst result = await handler(monitor, context);\n\t\t\t// Only a LIVE entry is re-adopted, so the next persist keeps it and drops the rest.\n\t\t\tif (result.outcome === \"restored\" || result.outcome === \"muted\") {\n\t\t\t\twriter.adoptRestored(result.runtime === undefined ? monitor : { ...monitor, runtime: result.runtime });\n\t\t\t\tregistry.adoptFireWindow(monitor.monitorId, monitor.fireWindow);\n\t\t\t}\n\t\t\treturn result;\n\t\t};\n\tlet freshFileRuntimeId: string | undefined;\n\tconst checkpointedFile = createCheckpointedFileRestoreHandler({\n\t\tregistry,\n\t\twriter,\n\t\tbindMonitorId: (monitorId, runtimeId) => {\n\t\t\tfreshFileRuntimeId = runtimeId;\n\t\t\tbundle.manager.bindMonitorId(monitorId, runtimeId);\n\t\t},\n\t});\n\treturn {\n\t\t\"restartable-command\": adopting(\n\t\t\tcreateRestartableCommandHandler({\n\t\t\t\tctx: toolCtx,\n\t\t\t\tregistry,\n\t\t\t\t...(restoreSessionTestHooks.graceMs !== undefined ? { graceMs: restoreSessionTestHooks.graceMs } : {}),\n\t\t\t}),\n\t\t),\n\t\t\"checkpointed-file\": adopting(async (monitor, context) => {\n\t\t\tfreshFileRuntimeId = undefined;\n\t\t\tconst result = await checkpointedFile(monitor, context);\n\t\t\tif (result.outcome !== \"restored\" || freshFileRuntimeId === undefined) return result;\n\t\t\treturn { ...result, outcome: reapplyPersistedMute(registry, monitor, freshFileRuntimeId) };\n\t\t}),\n\t};\n}\n\ninterface StartOptions {\n\treadonly pi: ExtensionAPI;\n\treadonly state: PersistenceState;\n\treadonly toolCtx: TerminalToolContext;\n\treadonly sessionKey: string;\n}\n\n/** Non-reload session_start (and a reload that does not hold the lease): take ownership or wait for it. */\nexport async function startPersistence({ pi, state, toolCtx, sessionKey }: StartOptions): Promise<void> {\n\tstate.generation += 1;\n\tstate.digestSlot = createDigestSlot();\n\tconst decided = armDecision(sessionKey);\n\tconst ctx = state.ctx;\n\tconst dir = terminalStateDir(ctx);\n\tconst sessionManager = ctx?.sessionManager;\n\tif (dir === undefined || sessionManager === undefined) return decided();\n\tconst encoded = encodedSessionId(sessionKey);\n\tconst generation = state.generation;\n\n\tconst own = async (lease: AcquireTerminalLeaseResult & { acquired: true }): Promise<void> => {\n\t\tctx?.ui?.setStatus?.(RESTORE_STATUS_KEY, undefined);\n\t\tstate.lease = { path: lease.path, pid: lease.pid, token: lease.token };\n\t\tconst writer = new TerminalManifestWriter({ session: sessionManager });\n\t\tstate.manifestWriter = writer;\n\t\tstate.recordedBackgroundIds.clear();\n\t\tconst bundle = state.bundle;\n\t\tlet restoredFromReadableManifest = false;\n\t\tif (bundle !== null && existsSync(writer.store.filePath)) {\n\t\t\tlet digest: RestoreDigest;\n\t\t\ttry {\n\t\t\t\tdigest = await restoreTerminalState({\n\t\t\t\t\tmanifest: writer.store,\n\t\t\t\t\thandlers: restoreHandlers(state, toolCtx, bundle),\n\t\t\t\t\tsessionFile: sessionManager.getSessionFile?.(),\n\t\t\t\t\tprocessStartedAtMs: ownProcessStartedAtMs(),\n\t\t\t\t});\n\t\t\t} catch (error) {\n\t\t\t\t// A restore that throws (an unreadable transcript, a kill error on a confirmed orphan)\n\t\t\t\t// still decides: the session hears it failed, and the lease stays bound for shutdown.\n\t\t\t\tctx?.ui?.notify?.(`Terminal restore failed: ${errorText(error)}`, \"warning\");\n\t\t\t\tdigest = { ...emptyDigest(), storeError: true };\n\t\t\t}\n\t\t\t// A watch that did not come back never runs again: its baseline dir goes with it.\n\t\t\tawait Promise.all(\n\t\t\t\tdigest.results\n\t\t\t\t\t.filter(\n\t\t\t\t\t\t(result) =>\n\t\t\t\t\t\t\tresult.outcome === \"lost\" || result.outcome === \"expired\" || result.outcome === \"completed\",\n\t\t\t\t\t)\n\t\t\t\t\t.map((result) => removeMonitorStateDir(dir, result.monitorId)),\n\t\t\t);\n\t\t\tif (generation !== state.generation) return;\n\t\t\trestoredFromReadableManifest = !digest.storeError;\n\t\t\tif (digest.storeError || digest.results.length > 0 || digest.backgroundSessions.length > 0) {\n\t\t\t\tstate.digestSlot.set(buildRestoreDigest(digest, { generation, outcome: \"decided\" }));\n\t\t\t}\n\t\t}\n\n\t\t// Bound AFTER the restore read the file: draining queued specs writes the manifest.\n\t\tbindTerminalManifestWriter(sessionKey, writer);\n\t\t// A corrupt manifest is left as it was (fail closed); a readable one is rewritten to the truth.\n\t\tif (restoredFromReadableManifest) await writer.persistRestored();\n\t\tstate.ensurePersistence = null;\n\t\tconst self = { pid: process.pid, bootAtMs: processBootAtMs(), processStartedAtMs: ownProcessStartedAtMs() };\n\t\t// Housekeeping only: a failed sweep leaves the files for the next start to reclaim.\n\t\tvoid sweepTerminalStateDir(dir, { self, keep: new Set([`${encoded}.lease`, `${encoded}.json`]) }).catch(\n\t\t\t() => undefined,\n\t\t);\n\t\tflushRestoreDigest(pi, state);\n\t\tdecided();\n\t};\n\n\tconst wait = (holder: LeaseHolder | null): void => {\n\t\tctx?.ui?.setStatus?.(\n\t\t\tRESTORE_STATUS_KEY,\n\t\t\tholder === null ? \"monitors waiting for the session lease\" : `monitors held by pid ${holder.pid}`,\n\t\t);\n\t\tstate.digestSlot.set(\n\t\t\tbuildRestoreDigest(emptyDigest(), {\n\t\t\t\tgeneration,\n\t\t\t\toutcome: \"deferred\",\n\t\t\t\t...(holder === null ? {} : { holderPid: holder.pid }),\n\t\t\t}),\n\t\t);\n\t\tstate.keeper?.stop();\n\t\tstate.keeper = createLeaseKeeper({\n\t\t\tdir,\n\t\t\tencodedSessionId: encoded,\n\t\t\t...(restoreSessionTestHooks.keeperIntervalMs !== undefined\n\t\t\t\t? { intervalMs: restoreSessionTestHooks.keeperIntervalMs }\n\t\t\t\t: {}),\n\t\t\tonTakeover: (lease) => {\n\t\t\t\tstate.restoreInFlight = own(lease);\n\t\t\t\treturn state.restoreInFlight;\n\t\t\t},\n\t\t\tonError: (error) => {\n\t\t\t\tconst waiting = state.keeper?.state === \"waiting\";\n\t\t\t\tctx?.ui?.notify?.(\n\t\t\t\t\t`Terminal lease ${waiting ? \"check failed, still waiting\" : \"takeover failed\"}: ${errorText(error)}`,\n\t\t\t\t\t\"warning\",\n\t\t\t\t);\n\t\t\t},\n\t\t});\n\t\tstate.keeper.start(holder);\n\t};\n\n\tconst acquire = async (): Promise<void> => {\n\t\t// The lease kept changing (or the dir failed): never leave the session undecided; the keeper retries.\n\t\tconst lease = await acquireTerminalLease({ dir, encodedSessionId: encoded }).catch((error: unknown) => {\n\t\t\tctx?.ui?.notify?.(`Terminal lease not acquired yet, retrying: ${errorText(error)}`, \"warning\");\n\t\t\treturn null;\n\t\t});\n\t\tif (lease === null) return wait(null);\n\t\tif (lease.acquired) {\n\t\t\tstate.restoreInFlight = own(lease);\n\t\t\treturn;\n\t\t}\n\t\twait(lease.holder);\n\t};\n\n\tif (existsSync(createTerminalManifestStore(sessionManager).filePath)) return acquire();\n\t// Nothing to restore: the lease and the recorder wait for the first real registration.\n\tdecided();\n\tlet binding: Promise<void> | null = null;\n\tstate.ensurePersistence = () => {\n\t\tbinding ??= acquire().then(() => state.restoreInFlight);\n\t\treturn binding;\n\t};\n}\n\n/** session_shutdown (not reload): stop waiting, finish any restore, suspend and flush, release. */\nexport async function stopPersistence(state: PersistenceState, sessionKey: string): Promise<void> {\n\tconst keeper = state.keeper;\n\tkeeper?.stop();\n\tstate.keeper = null;\n\t// A tick in flight may still be acquiring (then releases) or taking over (then restores).\n\tawait keeper?.settled();\n\tconst lease = state.lease;\n\tstate.lease = null;\n\tif (lease !== null) retireLeaseToken(lease.token);\n\tawait state.restoreInFlight;\n\tconst writer = state.manifestWriter;\n\tif (writer && state.bundle) await writer.observeMonitorState(state.bundle.monitors.snapshot());\n\tdetachPersistence(state, sessionKey);\n\tif (writer) await writer.recordShutdown();\n\tif (lease !== null) await releaseTerminalLease(lease);\n\tdecisions.get(sessionKey)?.resolve();\n}\n\n/** Detach the manifest recorder without writing (a reload keeps live state instead). */\nexport function detachPersistence(state: PersistenceState, sessionKey: string | undefined): void {\n\tstate.manifestWriter = null;\n\tstate.ensurePersistence = null;\n\tstate.recordedBackgroundIds.clear();\n\tif (sessionKey !== undefined) unbindTerminalManifestWriter(sessionKey);\n}\n"]}
@@ -1,27 +1,34 @@
1
1
  /**
2
- * The terminal manifest's read side and restore orchestrator: the strict fail-closed
3
- * parse every persisted manifest must survive, plus `restoreTerminalState`, which
4
- * replays the manifest after a restart. Each monitor is classified by its durability
5
- * class and handed to that class's handler; ephemeral monitors and background sessions
6
- * are always lost, and an expired watch never reaches its handler. Both durable
7
- * handler slots ship as stubs reporting `lost` — later phases plug the real ones in.
2
+ * The restore orchestrator: `restoreTerminalState` replays the manifest after a restart. Every
3
+ * monitor is classified up front (expired, ephemeral with or without time left, durable) and the
4
+ * durable and still-live ephemeral ones are handed to their kind's handler, all concurrently.
5
+ * The result carries per-monitor outcomes and reasons, background sessions told apart as still
6
+ * running or exited, and an upper bound on how long the session was detached.
8
7
  */
9
- import { InvalidSidecarStoreError, type SidecarStore, type SidecarStoreRef } from "../../../session-sidecar-store.ts";
10
- import { type ManifestMonitor, type TerminalManifest } from "./terminal-manifest.ts";
11
- export declare class InvalidTerminalManifestError extends InvalidSidecarStoreError {
12
- constructor(message: string, options?: {
13
- cause?: unknown;
14
- });
15
- }
16
- /** Strict fail-closed domain parse; the sidecar store has already checked version and session. */
17
- export declare function parseTerminalManifest(raw: unknown, ref: SidecarStoreRef): TerminalManifest;
18
- export type RestoreOutcome = "restored" | "lost" | "muted" | "attachedElsewhere";
8
+ import type { SidecarStore } from "../../../session-sidecar-store.ts";
9
+ import type { ChildProcessIdentity } from "./process-identity.ts";
10
+ import type { ManifestBackgroundSession, ManifestMonitor, TerminalManifest } from "./terminal-manifest.ts";
11
+ export { InvalidTerminalManifestError, parseTerminalManifest } from "./terminal-manifest-parse.ts";
12
+ export type RestoreOutcome = "restored" | "lost" | "muted" | "completed" | "attachedElsewhere";
19
13
  export interface RestoreHandlerResult {
20
14
  readonly outcome: RestoreOutcome;
21
- /** Why a durable monitor could not be restored; diagnostic only, never part of the digest counts. */
15
+ /** Why a monitor could not come back (or what its restore did), shown per monitor in the digest. */
22
16
  readonly reason?: string;
17
+ /** The watcher a crash left running: killed before the respawn, or left alone when unverifiable. */
18
+ readonly orphan?: {
19
+ readonly pid: number;
20
+ readonly action: "killed" | "unverified";
21
+ };
22
+ /** The re-spawned process, so the manifest can find it after the next crash. */
23
+ readonly runtime?: ChildProcessIdentity;
23
24
  }
24
- export type RestoreHandler = (monitor: ManifestMonitor) => RestoreHandlerResult | Promise<RestoreHandlerResult>;
25
+ export interface RestoreContext {
26
+ /** Upper bound on how long the session was detached (never an exact gap). */
27
+ readonly downtimeMs: number;
28
+ /** Time left for an ephemeral watch whose deadline has not passed; undefined for durable ones. */
29
+ readonly remainingMs?: number;
30
+ }
31
+ export type RestoreHandler = (monitor: ManifestMonitor, context: RestoreContext) => RestoreHandlerResult | Promise<RestoreHandlerResult>;
25
32
  export interface RestoreHandlers {
26
33
  readonly "restartable-command": RestoreHandler;
27
34
  readonly "checkpointed-file": RestoreHandler;
@@ -37,20 +44,46 @@ export interface PersistedMuteRegistry {
37
44
  * runtime id only, so passing the persisted `mon_` id silently no-ops and the mute is lost.
38
45
  */
39
46
  export declare function reapplyPersistedMute(registry: PersistedMuteRegistry, monitor: Pick<ManifestMonitor, "deliveryPaused">, runtimeId: string): RestoreOutcome;
40
- /** Stub durability handlers: every durable monitor is reported lost until later phases land. */
47
+ /** Stub handlers: every monitor is reported lost until a caller plugs the real ones in. */
41
48
  export declare const stubRestoreHandlers: RestoreHandlers;
49
+ export type MonitorRestoreOutcome = RestoreOutcome | "expired";
50
+ export interface MonitorRestoreResult {
51
+ readonly monitorId: string;
52
+ readonly description: string;
53
+ readonly kind: ManifestMonitor["runtimeKind"];
54
+ readonly outcome: MonitorRestoreOutcome;
55
+ readonly command?: string;
56
+ readonly path?: string;
57
+ readonly reason?: string;
58
+ readonly orphan?: RestoreHandlerResult["orphan"];
59
+ }
60
+ export interface BackgroundRestoreResult {
61
+ readonly id: string;
62
+ readonly command: string;
63
+ readonly outcome: "running" | "exited";
64
+ readonly pid?: number;
65
+ }
42
66
  export interface RestoreDigest {
43
67
  restored: number;
44
68
  lost: number;
45
69
  expired: number;
46
70
  muted: number;
71
+ completed: number;
47
72
  attachedElsewhere: number;
48
73
  storeError: boolean;
74
+ readonly results: readonly MonitorRestoreResult[];
75
+ readonly backgroundSessions: readonly BackgroundRestoreResult[];
76
+ /** Upper bound on how long the session was detached; 0 when nothing was persisted. */
77
+ readonly downtimeMs: number;
49
78
  }
50
79
  export interface RestoreTerminalStateOptions {
51
80
  readonly manifest: SidecarStore<TerminalManifest>;
52
81
  readonly handlers?: Partial<RestoreHandlers>;
53
82
  readonly now?: () => number;
83
+ /** The session transcript; its last entry written before this process started bounds the downtime. */
84
+ readonly sessionFile?: string;
85
+ readonly processStartedAtMs?: number;
86
+ readonly isBackgroundAlive?: (session: ManifestBackgroundSession) => Promise<boolean>;
54
87
  }
55
88
  export declare function restoreTerminalState(options: RestoreTerminalStateOptions): Promise<RestoreDigest>;
56
89
  //# sourceMappingURL=restore.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"restore.d.ts","sourceRoot":"","sources":["../../../../../src/core/extensions/builtin/terminal/restore.ts"],"names":[],"mappings":"AAAA;;;;;;;GAOG;AAEH,OAAO,EAAE,wBAAwB,EAAE,KAAK,YAAY,EAAE,KAAK,eAAe,EAAE,MAAM,mCAAmC,CAAC;AACtH,OAAO,EAEN,KAAK,eAAe,EAEpB,KAAK,gBAAgB,EAErB,MAAM,wBAAwB,CAAC;AAEhC,qBAAa,4BAA6B,SAAQ,wBAAwB;IACzE,YAAY,OAAO,EAAE,MAAM,EAAE,OAAO,CAAC,EAAE;QAAE,KAAK,CAAC,EAAE,OAAO,CAAA;KAAE,EAGzD;CACD;AAiGD,kGAAkG;AAClG,wBAAgB,qBAAqB,CAAC,GAAG,EAAE,OAAO,EAAE,GAAG,EAAE,eAAe,GAAG,gBAAgB,CAW1F;AAED,MAAM,MAAM,cAAc,GAAG,UAAU,GAAG,MAAM,GAAG,OAAO,GAAG,mBAAmB,CAAC;AAEjF,MAAM,WAAW,oBAAoB;IACpC,QAAQ,CAAC,OAAO,EAAE,cAAc,CAAC;IACjC,qGAAqG;IACrG,QAAQ,CAAC,MAAM,CAAC,EAAE,MAAM,CAAC;CACzB;AAED,MAAM,MAAM,cAAc,GAAG,CAAC,OAAO,EAAE,eAAe,KAAK,oBAAoB,GAAG,OAAO,CAAC,oBAAoB,CAAC,CAAC;AAEhH,MAAM,WAAW,eAAe;IAC/B,QAAQ,CAAC,qBAAqB,EAAE,cAAc,CAAC;IAC/C,QAAQ,CAAC,mBAAmB,EAAE,cAAc,CAAC;CAC7C;AAED,iFAAiF;AACjF,MAAM,WAAW,qBAAqB;IACrC,KAAK,CAAC,GAAG,EAAE,SAAS,MAAM,EAAE,GAAG,MAAM,EAAE,CAAC;CACxC;AAED;;;;;GAKG;AACH,wBAAgB,oBAAoB,CACnC,QAAQ,EAAE,qBAAqB,EAC/B,OAAO,EAAE,IAAI,CAAC,eAAe,EAAE,gBAAgB,CAAC,EAChD,SAAS,EAAE,MAAM,GACf,cAAc,CAIhB;AAED,gGAAgG;AAChG,eAAO,MAAM,mBAAmB,EAAE,eAGjC,CAAC;AAEF,MAAM,WAAW,aAAa;IAC7B,QAAQ,EAAE,MAAM,CAAC;IACjB,IAAI,EAAE,MAAM,CAAC;IACb,OAAO,EAAE,MAAM,CAAC;IAChB,KAAK,EAAE,MAAM,CAAC;IACd,iBAAiB,EAAE,MAAM,CAAC;IAC1B,UAAU,EAAE,OAAO,CAAC;CACpB;AAED,MAAM,WAAW,2BAA2B;IAC3C,QAAQ,CAAC,QAAQ,EAAE,YAAY,CAAC,gBAAgB,CAAC,CAAC;IAClD,QAAQ,CAAC,QAAQ,CAAC,EAAE,OAAO,CAAC,eAAe,CAAC,CAAC;IAC7C,QAAQ,CAAC,GAAG,CAAC,EAAE,MAAM,MAAM,CAAC;CAC5B;AAED,wBAAsB,oBAAoB,CAAC,OAAO,EAAE,2BAA2B,GAAG,OAAO,CAAC,aAAa,CAAC,CAmCvG"}
1
+ {"version":3,"file":"restore.d.ts","sourceRoot":"","sources":["../../../../../src/core/extensions/builtin/terminal/restore.ts"],"names":[],"mappings":"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"}
@@ -1,124 +1,13 @@
1
1
  /**
2
- * The terminal manifest's read side and restore orchestrator: the strict fail-closed
3
- * parse every persisted manifest must survive, plus `restoreTerminalState`, which
4
- * replays the manifest after a restart. Each monitor is classified by its durability
5
- * class and handed to that class's handler; ephemeral monitors and background sessions
6
- * are always lost, and an expired watch never reaches its handler. Both durable
7
- * handler slots ship as stubs reporting `lost` — later phases plug the real ones in.
2
+ * The restore orchestrator: `restoreTerminalState` replays the manifest after a restart. Every
3
+ * monitor is classified up front (expired, ephemeral with or without time left, durable) and the
4
+ * durable and still-live ephemeral ones are handed to their kind's handler, all concurrently.
5
+ * The result carries per-monitor outcomes and reasons, background sessions told apart as still
6
+ * running or exited, and an upper bound on how long the session was detached.
8
7
  */
9
- import { InvalidSidecarStoreError } from "../../../session-sidecar-store.js";
10
- import { TERMINAL_MANIFEST_VERSION, } from "./terminal-manifest.js";
11
- export class InvalidTerminalManifestError extends InvalidSidecarStoreError {
12
- constructor(message, options) {
13
- super(message, options);
14
- this.name = "InvalidTerminalManifestError";
15
- }
16
- }
17
- const RUNTIME_KINDS = ["command", "file"];
18
- const DURABILITY_CLASSES = ["ephemeral", "restartable-command", "checkpointed-file"];
19
- const FILE_EVENTS = ["create", "modify"];
20
- const isStr = (value) => typeof value === "string" && value.length > 0;
21
- const isNum = (value) => typeof value === "number" && Number.isFinite(value);
22
- const isBool = (value) => typeof value === "boolean";
23
- const isObj = (value) => typeof value === "object" && value !== null && !Array.isArray(value);
24
- function invalid(message) {
25
- throw new InvalidTerminalManifestError(`terminal manifest is invalid: ${message}`);
26
- }
27
- function str(raw, field) {
28
- if (!isStr(raw[field]))
29
- invalid(`field ${field} must be a non-empty string`);
30
- return raw[field];
31
- }
32
- function num(raw, field) {
33
- if (!isNum(raw[field]))
34
- invalid(`field ${field} must be a finite number`);
35
- return raw[field];
36
- }
37
- function digest(raw, field) {
38
- if (typeof raw[field] !== "string")
39
- invalid(`field ${field} must be a string`);
40
- return raw[field];
41
- }
42
- function bool(raw, field) {
43
- if (!isBool(raw[field]))
44
- invalid(`field ${field} must be a boolean`);
45
- return raw[field];
46
- }
47
- function opt(raw, field, required) {
48
- return raw[field] === undefined ? undefined : required(raw, field);
49
- }
50
- function oneOf(values, raw, field) {
51
- const value = raw[field];
52
- if (typeof value !== "string" || !values.includes(value))
53
- invalid(`field ${field} must be one of: ${values.join(", ")}`);
54
- return value;
55
- }
56
- function checkpoint(raw) {
57
- if (!isObj(raw))
58
- invalid("field lastCheckpoint must be an object");
59
- return {
60
- dev: num(raw, "dev"),
61
- ino: num(raw, "ino"),
62
- size: num(raw, "size"),
63
- mtimeMs: num(raw, "mtimeMs"),
64
- // A checkpoint without a digest cannot detect a same-size, same-mtime rewrite, so the field is
65
- // required — but an absent file legitimately checkpoints an empty digest, so "" is valid.
66
- digest: digest(raw, "digest"),
67
- present: bool(raw, "present"),
68
- };
69
- }
70
- function fireWindow(raw) {
71
- if (!isObj(raw))
72
- invalid("field fireWindow must be an object");
73
- return { startMs: num(raw, "startMs"), count: num(raw, "count") };
74
- }
75
- function parseMonitor(entry) {
76
- if (!isObj(entry))
77
- invalid("a monitor entry must be an object");
78
- return {
79
- monitorId: str(entry, "monitorId"),
80
- sessionId: str(entry, "sessionId"),
81
- description: str(entry, "description"),
82
- runtimeKind: oneOf(RUNTIME_KINDS, entry, "runtimeKind"),
83
- durabilityClass: oneOf(DURABILITY_CLASSES, entry, "durabilityClass"),
84
- command: opt(entry, "command", str),
85
- path: opt(entry, "path", str),
86
- event: opt(entry, "event", (raw, field) => oneOf(FILE_EVENTS, raw, field)),
87
- filter: opt(entry, "filter", str),
88
- cwd: opt(entry, "cwd", str),
89
- approvedParent: opt(entry, "approvedParent", str),
90
- createdAt: num(entry, "createdAt"),
91
- expiresAt: entry.expiresAt === undefined || entry.expiresAt === null ? null : num(entry, "expiresAt"),
92
- persistent: bool(entry, "persistent"),
93
- suspended: bool(entry, "suspended"),
94
- lastCheckpoint: entry.lastCheckpoint === null ? null : checkpoint(entry.lastCheckpoint),
95
- deliveryPaused: bool(entry, "deliveryPaused"),
96
- // Unknown keys are ignored by this field-by-field parse, so a manifest written before
97
- // `wakeCount` was dropped still reads back cleanly; no version bump is needed.
98
- fireWindow: fireWindow(entry.fireWindow),
99
- };
100
- }
101
- function parseBackgroundSession(entry) {
102
- if (!isObj(entry))
103
- invalid("a background session entry must be an object");
104
- return { id: str(entry, "id"), command: str(entry, "command"), startedAtMs: num(entry, "startedAtMs") };
105
- }
106
- /** Strict fail-closed domain parse; the sidecar store has already checked version and session. */
107
- export function parseTerminalManifest(raw, ref) {
108
- if (!isObj(raw))
109
- invalid("the payload must be an object");
110
- if (!Array.isArray(raw.monitors))
111
- invalid("field monitors must be an array");
112
- if (!Array.isArray(raw.backgroundSessions))
113
- invalid("field backgroundSessions must be an array");
114
- return {
115
- version: TERMINAL_MANIFEST_VERSION,
116
- sessionId: ref.sessionId,
117
- monitors: raw.monitors.map(parseMonitor),
118
- backgroundSessions: raw.backgroundSessions.map(parseBackgroundSession),
119
- updatedAt: num(raw, "updatedAt"),
120
- };
121
- }
8
+ import { confirmOwner } from "./orphan-reaper.js";
9
+ import { sessionActivityBeforeMs } from "./session-activity.js";
10
+ export { InvalidTerminalManifestError, parseTerminalManifest } from "./terminal-manifest-parse.js";
122
11
  /**
123
12
  * Re-apply a persisted `deliveryPaused` mute to a freshly restored monitor and report the
124
13
  * outcome it contributes to the digest. The mute MUST be applied by the FRESH runtime id
@@ -131,47 +20,83 @@ export function reapplyPersistedMute(registry, monitor, runtimeId) {
131
20
  registry.pause([runtimeId]);
132
21
  return "muted";
133
22
  }
134
- /** Stub durability handlers: every durable monitor is reported lost until later phases land. */
23
+ /** Stub handlers: every monitor is reported lost until a caller plugs the real ones in. */
135
24
  export const stubRestoreHandlers = {
136
- "restartable-command": () => ({ outcome: "lost" }),
137
- "checkpointed-file": () => ({ outcome: "lost" }),
25
+ "restartable-command": () => ({ outcome: "lost", reason: "no restore handler" }),
26
+ "checkpointed-file": () => ({ outcome: "lost", reason: "no restore handler" }),
138
27
  };
139
- export async function restoreTerminalState(options) {
140
- const digest = {
28
+ function emptyDigest() {
29
+ return {
141
30
  restored: 0,
142
31
  lost: 0,
143
32
  expired: 0,
144
33
  muted: 0,
34
+ completed: 0,
145
35
  attachedElsewhere: 0,
146
36
  storeError: false,
37
+ results: [],
38
+ backgroundSessions: [],
39
+ downtimeMs: 0,
147
40
  };
41
+ }
42
+ async function defaultBackgroundAlive(session) {
43
+ if (session.runtime === undefined)
44
+ return false;
45
+ return (await confirmOwner(session.runtime, undefined)) === "confirmed";
46
+ }
47
+ function resultFor(monitor, outcome, handled) {
48
+ return {
49
+ monitorId: monitor.monitorId,
50
+ description: monitor.description,
51
+ kind: monitor.runtimeKind,
52
+ outcome,
53
+ ...(monitor.command !== undefined ? { command: monitor.command } : {}),
54
+ ...(monitor.path !== undefined ? { path: monitor.path } : {}),
55
+ ...(handled?.reason !== undefined ? { reason: handled.reason } : {}),
56
+ ...(handled?.orphan !== undefined ? { orphan: handled.orphan } : {}),
57
+ };
58
+ }
59
+ export async function restoreTerminalState(options) {
148
60
  let state;
149
61
  try {
150
62
  state = await options.manifest.read();
151
63
  }
152
64
  catch {
153
65
  // Fail closed: a corrupt or foreign manifest restores nothing and reports the store error.
154
- return { ...digest, storeError: true };
66
+ return { ...emptyDigest(), storeError: true };
155
67
  }
156
68
  if (state === null)
157
- return digest;
69
+ return emptyDigest();
158
70
  const now = (options.now ?? Date.now)();
71
+ const lastSeen = Math.max(state.updatedAt, (await sessionActivityBeforeMs(options.sessionFile, options.processStartedAtMs ?? now)) ?? 0);
72
+ const downtimeMs = Math.max(0, now - lastSeen);
159
73
  const handlers = { ...stubRestoreHandlers, ...options.handlers };
160
- for (const monitor of state.monitors) {
74
+ const results = await Promise.all(state.monitors.map(async (monitor) => {
161
75
  // At the deadline the entry is already expired: the handler must not see it at all.
162
- if (monitor.expiresAt !== null && monitor.expiresAt <= now) {
163
- digest.expired += 1;
164
- continue;
165
- }
76
+ if (monitor.expiresAt !== null && monitor.expiresAt <= now)
77
+ return resultFor(monitor, "expired");
166
78
  if (monitor.durabilityClass === "ephemeral") {
167
- digest.lost += 1;
168
- continue;
79
+ if (monitor.deadlineMs === undefined || monitor.deadlineMs <= now) {
80
+ return resultFor(monitor, "lost", { outcome: "lost", reason: "deadline passed while detached" });
81
+ }
82
+ const handler = monitor.runtimeKind === "file" ? handlers["checkpointed-file"] : handlers["restartable-command"];
83
+ const handled = await handler(monitor, { downtimeMs, remainingMs: monitor.deadlineMs - now });
84
+ return resultFor(monitor, handled.outcome, handled);
169
85
  }
170
- const outcome = (await handlers[monitor.durabilityClass](monitor)).outcome;
171
- digest[outcome] += 1;
172
- }
173
- // Background sessions carry no durable identity: every one of them is lost.
174
- digest.lost += state.backgroundSessions.length;
86
+ const handled = await handlers[monitor.durabilityClass](monitor, { downtimeMs });
87
+ return resultFor(monitor, handled.outcome, handled);
88
+ }));
89
+ const isAlive = options.isBackgroundAlive ?? defaultBackgroundAlive;
90
+ const backgroundSessions = await Promise.all(state.backgroundSessions.map(async (session) => {
91
+ if (session.runtime !== undefined && (await isAlive(session))) {
92
+ return { id: session.id, command: session.command, outcome: "running", pid: session.runtime.pid };
93
+ }
94
+ return { id: session.id, command: session.command, outcome: "exited" };
95
+ }));
96
+ const digest = { ...emptyDigest(), results, backgroundSessions, downtimeMs };
97
+ for (const result of results)
98
+ digest[result.outcome] += 1;
99
+ digest.lost += backgroundSessions.length;
175
100
  return digest;
176
101
  }
177
102
  //# sourceMappingURL=restore.js.map