@code-yeongyu/senpi 2026.8.29 → 2026.8.30

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 (200) hide show
  1. package/CHANGELOG.md +66 -0
  2. package/dist/core/agent-session.d.ts +45 -1
  3. package/dist/core/agent-session.d.ts.map +1 -1
  4. package/dist/core/agent-session.js +312 -25
  5. package/dist/core/agent-session.js.map +1 -1
  6. package/dist/core/bash-executor.d.ts.map +1 -1
  7. package/dist/core/bash-executor.js +155 -37
  8. package/dist/core/bash-executor.js.map +1 -1
  9. package/dist/core/credential-accounts.d.ts.map +1 -1
  10. package/dist/core/credential-accounts.js +23 -9
  11. package/dist/core/credential-accounts.js.map +1 -1
  12. package/dist/core/credential-pool/env-slots.d.ts.map +1 -1
  13. package/dist/core/credential-pool/env-slots.js +2 -0
  14. package/dist/core/credential-pool/env-slots.js.map +1 -1
  15. package/dist/core/extensions/builtin/compaction/deterministic-fallback.d.ts +8 -2
  16. package/dist/core/extensions/builtin/compaction/deterministic-fallback.d.ts.map +1 -1
  17. package/dist/core/extensions/builtin/compaction/deterministic-fallback.js +160 -47
  18. package/dist/core/extensions/builtin/compaction/deterministic-fallback.js.map +1 -1
  19. package/dist/core/extensions/builtin/compaction/retained-message-safety.d.ts.map +1 -1
  20. package/dist/core/extensions/builtin/compaction/retained-message-safety.js +31 -8
  21. package/dist/core/extensions/builtin/compaction/retained-message-safety.js.map +1 -1
  22. package/dist/core/extensions/builtin/cursor-cli-oauth/stream.d.ts.map +1 -1
  23. package/dist/core/extensions/builtin/cursor-cli-oauth/stream.js +8 -28
  24. package/dist/core/extensions/builtin/cursor-cli-oauth/stream.js.map +1 -1
  25. package/dist/core/extensions/builtin/permission-system/external-dir.d.ts.map +1 -1
  26. package/dist/core/extensions/builtin/permission-system/external-dir.js +15 -5
  27. package/dist/core/extensions/builtin/permission-system/external-dir.js.map +1 -1
  28. package/dist/core/extensions/builtin/permission-system/parsers.d.ts.map +1 -1
  29. package/dist/core/extensions/builtin/permission-system/parsers.js +16 -1
  30. package/dist/core/extensions/builtin/permission-system/parsers.js.map +1 -1
  31. package/dist/core/extensions/builtin/terminal/manager.d.ts +5 -0
  32. package/dist/core/extensions/builtin/terminal/manager.d.ts.map +1 -1
  33. package/dist/core/extensions/builtin/terminal/manager.js +31 -3
  34. package/dist/core/extensions/builtin/terminal/manager.js.map +1 -1
  35. package/dist/core/extensions/builtin/terminal/monitor-permission.d.ts +8 -0
  36. package/dist/core/extensions/builtin/terminal/monitor-permission.d.ts.map +1 -0
  37. package/dist/core/extensions/builtin/terminal/monitor-permission.js +16 -0
  38. package/dist/core/extensions/builtin/terminal/monitor-permission.js.map +1 -0
  39. package/dist/core/extensions/builtin/terminal/monitor-registry.d.ts +13 -0
  40. package/dist/core/extensions/builtin/terminal/monitor-registry.d.ts.map +1 -1
  41. package/dist/core/extensions/builtin/terminal/monitor-registry.js +403 -3
  42. package/dist/core/extensions/builtin/terminal/monitor-registry.js.map +1 -1
  43. package/dist/core/extensions/builtin/terminal/session-bundle.d.ts.map +1 -1
  44. package/dist/core/extensions/builtin/terminal/session-bundle.js +1 -0
  45. package/dist/core/extensions/builtin/terminal/session-bundle.js.map +1 -1
  46. package/dist/core/extensions/builtin/terminal/tools/kill-bash.d.ts.map +1 -1
  47. package/dist/core/extensions/builtin/terminal/tools/kill-bash.js +5 -2
  48. package/dist/core/extensions/builtin/terminal/tools/kill-bash.js.map +1 -1
  49. package/dist/core/extensions/builtin/terminal/tools/monitor.d.ts +4 -0
  50. package/dist/core/extensions/builtin/terminal/tools/monitor.d.ts.map +1 -1
  51. package/dist/core/extensions/builtin/terminal/tools/monitor.js +40 -2
  52. package/dist/core/extensions/builtin/terminal/tools/monitor.js.map +1 -1
  53. package/dist/core/model-resolver.js +2 -2
  54. package/dist/core/model-resolver.js.map +1 -1
  55. package/dist/core/session-manager.d.ts.map +1 -1
  56. package/dist/core/session-manager.js +10 -1
  57. package/dist/core/session-manager.js.map +1 -1
  58. package/dist/core/settings-manager.d.ts +7 -0
  59. package/dist/core/settings-manager.d.ts.map +1 -1
  60. package/dist/core/settings-manager.js +6 -0
  61. package/dist/core/settings-manager.js.map +1 -1
  62. package/dist/core/tools/bash.d.ts.map +1 -1
  63. package/dist/core/tools/bash.js +97 -15
  64. package/dist/core/tools/bash.js.map +1 -1
  65. package/dist/core/tools/index.d.ts +1 -0
  66. package/dist/core/tools/index.d.ts.map +1 -1
  67. package/dist/core/tools/index.js +8 -0
  68. package/dist/core/tools/index.js.map +1 -1
  69. package/dist/core/tools/output-accumulator.d.ts +1 -1
  70. package/dist/core/tools/output-accumulator.d.ts.map +1 -1
  71. package/dist/core/tools/output-accumulator.js +76 -34
  72. package/dist/core/tools/output-accumulator.js.map +1 -1
  73. package/dist/modes/interactive/components/footer.d.ts +7 -0
  74. package/dist/modes/interactive/components/footer.d.ts.map +1 -1
  75. package/dist/modes/interactive/components/footer.js +34 -8
  76. package/dist/modes/interactive/components/footer.js.map +1 -1
  77. package/dist/modes/interactive/grok/chrome.d.ts +2 -0
  78. package/dist/modes/interactive/grok/chrome.d.ts.map +1 -1
  79. package/dist/modes/interactive/grok/chrome.js.map +1 -1
  80. package/dist/modes/interactive/interactive-host-runtime.d.ts +51 -1
  81. package/dist/modes/interactive/interactive-host-runtime.d.ts.map +1 -1
  82. package/dist/modes/interactive/interactive-host-runtime.js +235 -20
  83. package/dist/modes/interactive/interactive-host-runtime.js.map +1 -1
  84. package/dist/modes/interactive/interactive-mode.d.ts +7 -0
  85. package/dist/modes/interactive/interactive-mode.d.ts.map +1 -1
  86. package/dist/modes/interactive/interactive-mode.js +114 -2
  87. package/dist/modes/interactive/interactive-mode.js.map +1 -1
  88. package/dist/modes/rpc/connection-handler.d.ts +8 -1
  89. package/dist/modes/rpc/connection-handler.d.ts.map +1 -1
  90. package/dist/modes/rpc/connection-handler.js +145 -49
  91. package/dist/modes/rpc/connection-handler.js.map +1 -1
  92. package/dist/modes/rpc/host-ensure.d.ts +2 -0
  93. package/dist/modes/rpc/host-ensure.d.ts.map +1 -1
  94. package/dist/modes/rpc/host-ensure.js +15 -2
  95. package/dist/modes/rpc/host-ensure.js.map +1 -1
  96. package/dist/modes/rpc/host-lifecycle.d.ts.map +1 -1
  97. package/dist/modes/rpc/host-lifecycle.js +31 -12
  98. package/dist/modes/rpc/host-lifecycle.js.map +1 -1
  99. package/dist/modes/rpc/host-watchdog.d.ts +2 -0
  100. package/dist/modes/rpc/host-watchdog.d.ts.map +1 -1
  101. package/dist/modes/rpc/host-watchdog.js +23 -12
  102. package/dist/modes/rpc/host-watchdog.js.map +1 -1
  103. package/dist/modes/rpc/multi-session-host.js +6 -4
  104. package/dist/modes/rpc/multi-session-host.js.map +1 -1
  105. package/dist/modes/rpc/rpc-client.d.ts +10 -4
  106. package/dist/modes/rpc/rpc-client.d.ts.map +1 -1
  107. package/dist/modes/rpc/rpc-client.js +45 -10
  108. package/dist/modes/rpc/rpc-client.js.map +1 -1
  109. package/dist/modes/rpc/rpc-input-validation.d.ts +1 -0
  110. package/dist/modes/rpc/rpc-input-validation.d.ts.map +1 -1
  111. package/dist/modes/rpc/rpc-input-validation.js +66 -0
  112. package/dist/modes/rpc/rpc-input-validation.js.map +1 -1
  113. package/dist/modes/rpc/rpc-types.d.ts +79 -1
  114. package/dist/modes/rpc/rpc-types.d.ts.map +1 -1
  115. package/dist/modes/rpc/rpc-types.js.map +1 -1
  116. package/dist/modes/rpc/session-binding.d.ts +1 -0
  117. package/dist/modes/rpc/session-binding.d.ts.map +1 -1
  118. package/dist/modes/rpc/session-binding.js +21 -1
  119. package/dist/modes/rpc/session-binding.js.map +1 -1
  120. package/dist/modes/rpc/session-command-router.d.ts.map +1 -1
  121. package/dist/modes/rpc/session-command-router.js +1 -0
  122. package/dist/modes/rpc/session-command-router.js.map +1 -1
  123. package/dist/modes/rpc/session-event-writer.d.ts +2 -0
  124. package/dist/modes/rpc/session-event-writer.d.ts.map +1 -1
  125. package/dist/modes/rpc/session-event-writer.js +54 -7
  126. package/dist/modes/rpc/session-event-writer.js.map +1 -1
  127. package/dist/modes/rpc/session-extension-ui-requests.d.ts +1 -0
  128. package/dist/modes/rpc/session-extension-ui-requests.d.ts.map +1 -1
  129. package/dist/modes/rpc/session-extension-ui-requests.js +5 -0
  130. package/dist/modes/rpc/session-extension-ui-requests.js.map +1 -1
  131. package/dist/modes/rpc/session-registry.d.ts +10 -2
  132. package/dist/modes/rpc/session-registry.d.ts.map +1 -1
  133. package/dist/modes/rpc/session-registry.js +58 -4
  134. package/dist/modes/rpc/session-registry.js.map +1 -1
  135. package/dist/modes/rpc/socket-event-fanout.d.ts +22 -0
  136. package/dist/modes/rpc/socket-event-fanout.d.ts.map +1 -0
  137. package/dist/modes/rpc/socket-event-fanout.js +84 -0
  138. package/dist/modes/rpc/socket-event-fanout.js.map +1 -0
  139. package/docs/rpc.md +3 -1
  140. package/docs/settings.md +34 -0
  141. package/docs/windows.md +2 -0
  142. package/node_modules/@code-yeongyu/senpi-codemode/CHANGELOG.md +38 -0
  143. package/node_modules/@code-yeongyu/senpi-codemode/package.json +4 -4
  144. package/node_modules/@code-yeongyu/senpi-codemode/src/kernels/js/worker-indirect-eval.js +139 -90
  145. package/node_modules/@code-yeongyu/senpi-codemode/src/prompt/eval-prompt.ts +4 -4
  146. package/node_modules/@earendil-works/pi-agent-core/dist/harness/env/nodejs.d.ts.map +1 -1
  147. package/node_modules/@earendil-works/pi-agent-core/dist/harness/env/nodejs.js +40 -20
  148. package/node_modules/@earendil-works/pi-agent-core/dist/harness/env/nodejs.js.map +1 -1
  149. package/node_modules/@earendil-works/pi-agent-core/dist/harness/types.d.ts +3 -3
  150. package/node_modules/@earendil-works/pi-agent-core/dist/harness/types.d.ts.map +1 -1
  151. package/node_modules/@earendil-works/pi-agent-core/dist/harness/types.js.map +1 -1
  152. package/node_modules/@earendil-works/pi-agent-core/dist/harness/utils/shell-output.d.ts +1 -1
  153. package/node_modules/@earendil-works/pi-agent-core/dist/harness/utils/shell-output.d.ts.map +1 -1
  154. package/node_modules/@earendil-works/pi-agent-core/dist/harness/utils/shell-output.js +28 -9
  155. package/node_modules/@earendil-works/pi-agent-core/dist/harness/utils/shell-output.js.map +1 -1
  156. package/node_modules/@earendil-works/pi-agent-core/package.json +3 -3
  157. package/node_modules/@earendil-works/pi-ai/dist/api/anthropic-messages.d.ts.map +1 -1
  158. package/node_modules/@earendil-works/pi-ai/dist/api/anthropic-messages.js +58 -31
  159. package/node_modules/@earendil-works/pi-ai/dist/api/anthropic-messages.js.map +1 -1
  160. package/node_modules/@earendil-works/pi-ai/dist/api/cursor-agent/measure.d.ts +20 -0
  161. package/node_modules/@earendil-works/pi-ai/dist/api/cursor-agent/measure.d.ts.map +1 -0
  162. package/node_modules/@earendil-works/pi-ai/dist/api/cursor-agent/measure.js +443 -0
  163. package/node_modules/@earendil-works/pi-ai/dist/api/cursor-agent/measure.js.map +1 -0
  164. package/node_modules/@earendil-works/pi-ai/dist/api/cursor-agent.d.ts +2 -0
  165. package/node_modules/@earendil-works/pi-ai/dist/api/cursor-agent.d.ts.map +1 -1
  166. package/node_modules/@earendil-works/pi-ai/dist/api/cursor-agent.js +2 -0
  167. package/node_modules/@earendil-works/pi-ai/dist/api/cursor-agent.js.map +1 -1
  168. package/node_modules/@earendil-works/pi-ai/dist/api/openai-completions.js +1 -1
  169. package/node_modules/@earendil-works/pi-ai/dist/api/openai-completions.js.map +1 -1
  170. package/node_modules/@earendil-works/pi-ai/dist/index.d.ts +1 -0
  171. package/node_modules/@earendil-works/pi-ai/dist/index.d.ts.map +1 -1
  172. package/node_modules/@earendil-works/pi-ai/dist/index.js +1 -0
  173. package/node_modules/@earendil-works/pi-ai/dist/index.js.map +1 -1
  174. package/node_modules/@earendil-works/pi-ai/dist/providers/data/.manifest.json +1 -1
  175. package/node_modules/@earendil-works/pi-ai/dist/providers/data/cloudflare-ai-gateway.json +1 -1
  176. package/node_modules/@earendil-works/pi-ai/dist/providers/data/cloudflare-workers-ai.json +1 -1
  177. package/node_modules/@earendil-works/pi-ai/dist/providers/data/fireworks.json +1 -1
  178. package/node_modules/@earendil-works/pi-ai/dist/providers/data/opencode-go.json +1 -1
  179. package/node_modules/@earendil-works/pi-ai/dist/providers/data/opencode.json +1 -1
  180. package/node_modules/@earendil-works/pi-ai/dist/providers/data/openrouter.json +1 -1
  181. package/node_modules/@earendil-works/pi-ai/dist/providers/data/together.json +1 -1
  182. package/node_modules/@earendil-works/pi-ai/dist/providers/data/zai-coding-cn.json +1 -1
  183. package/node_modules/@earendil-works/pi-ai/dist/providers/data/zai.json +1 -1
  184. package/node_modules/@earendil-works/pi-ai/package.json +2 -2
  185. package/node_modules/@earendil-works/pi-pty/package.json +1 -1
  186. package/node_modules/@earendil-works/pi-telemetry/package.json +1 -1
  187. package/node_modules/@earendil-works/pi-tui/dist/components/editor.d.ts +7 -0
  188. package/node_modules/@earendil-works/pi-tui/dist/components/editor.d.ts.map +1 -1
  189. package/node_modules/@earendil-works/pi-tui/dist/components/editor.js +5 -0
  190. package/node_modules/@earendil-works/pi-tui/dist/components/editor.js.map +1 -1
  191. package/node_modules/@earendil-works/pi-tui/dist/mux.d.ts +1 -1
  192. package/node_modules/@earendil-works/pi-tui/dist/mux.d.ts.map +1 -1
  193. package/node_modules/@earendil-works/pi-tui/dist/mux.js +2 -2
  194. package/node_modules/@earendil-works/pi-tui/dist/mux.js.map +1 -1
  195. package/node_modules/@earendil-works/pi-tui/dist/terminal.d.ts +1 -0
  196. package/node_modules/@earendil-works/pi-tui/dist/terminal.d.ts.map +1 -1
  197. package/node_modules/@earendil-works/pi-tui/dist/terminal.js +19 -0
  198. package/node_modules/@earendil-works/pi-tui/dist/terminal.js.map +1 -1
  199. package/node_modules/@earendil-works/pi-tui/package.json +1 -1
  200. package/package.json +7 -7
@@ -23,6 +23,16 @@ export interface MonitorSnapshotEntry {
23
23
  export interface MonitorRegistryOptions {
24
24
  /** Observes every registry transition (register/pause/rearm/settle/dispose) with the live snapshot. */
25
25
  readonly onChange?: (snapshot: readonly MonitorSnapshotEntry[]) => void;
26
+ /** Reserves one shared terminal capacity slot for a native watch. */
27
+ readonly reserve?: () => (() => void) | null;
28
+ }
29
+ export interface RegisterFileMonitorOptions {
30
+ readonly description: string;
31
+ readonly path: string;
32
+ readonly event: "create" | "modify";
33
+ readonly timeoutMs: number;
34
+ readonly cwd: string;
35
+ readonly approvedParent?: string;
26
36
  }
27
37
  export interface RegisterMonitorOptions {
28
38
  readonly id: string;
@@ -39,6 +49,9 @@ export declare class MonitorRegistry {
39
49
  #private;
40
50
  constructor(emit: (event: MonitorEvent) => void, options?: MonitorRegistryOptions);
41
51
  snapshot(): readonly MonitorSnapshotEntry[];
52
+ registerFile(options: RegisterFileMonitorOptions): Promise<string>;
53
+ stopFile(id: string): Promise<boolean>;
54
+ stopAllFiles(): Promise<number>;
42
55
  register(options: RegisterMonitorOptions): void;
43
56
  pauseAll(): string[];
44
57
  rearm(id: string): MonitorRearmResult;
@@ -1 +1 @@
1
- {"version":3,"file":"monitor-registry.d.ts","sourceRoot":"","sources":["../../../../../src/core/extensions/builtin/terminal/monitor-registry.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,sBAAsB,EAAE,MAAM,sBAAsB,CAAC;AAGnE,MAAM,WAAW,gBAAgB;IAChC,QAAQ,CAAC,IAAI,EAAE,MAAM,CAAC;IACtB,QAAQ,CAAC,EAAE,EAAE,MAAM,CAAC;IACpB,QAAQ,CAAC,WAAW,EAAE,MAAM,CAAC;IAC7B,QAAQ,CAAC,IAAI,EAAE,MAAM,CAAC;CACtB;AAED,MAAM,WAAW,mBAAmB;IACnC,QAAQ,CAAC,IAAI,EAAE,SAAS,CAAC;IACzB,QAAQ,CAAC,EAAE,EAAE,MAAM,CAAC;IACpB,QAAQ,CAAC,WAAW,EAAE,MAAM,CAAC;IAC7B,QAAQ,CAAC,OAAO,EAAE,MAAM,CAAC;CACzB;AAED,MAAM,MAAM,YAAY,GAAG,gBAAgB,GAAG,mBAAmB,CAAC;AAElE,MAAM,MAAM,kBAAkB,GAAG,SAAS,GAAG,YAAY,GAAG,WAAW,CAAC;AAExE,MAAM,WAAW,oBAAoB;IACpC,QAAQ,CAAC,EAAE,EAAE,MAAM,CAAC;IACpB,QAAQ,CAAC,WAAW,EAAE,MAAM,CAAC;IAC7B,QAAQ,CAAC,MAAM,EAAE,OAAO,CAAC;IACzB,2FAA2F;IAC3F,QAAQ,CAAC,WAAW,EAAE,MAAM,CAAC;CAC7B;AAED,MAAM,WAAW,sBAAsB;IACtC,uGAAuG;IACvG,QAAQ,CAAC,QAAQ,CAAC,EAAE,CAAC,QAAQ,EAAE,SAAS,oBAAoB,EAAE,KAAK,IAAI,CAAC;CACxE;AAED,MAAM,WAAW,sBAAsB;IACtC,QAAQ,CAAC,EAAE,EAAE,MAAM,CAAC;IACpB,QAAQ,CAAC,WAAW,EAAE,MAAM,CAAC;IAC7B,QAAQ,CAAC,OAAO,EAAE,sBAAsB,CAAC;IACzC,QAAQ,CAAC,MAAM,CAAC,EAAE,MAAM,CAAC;CACzB;AAeD;;;;GAIG;AACH,qBAAa,eAAe;;gBAKf,IAAI,EAAE,CAAC,KAAK,EAAE,YAAY,KAAK,IAAI,EAAE,OAAO,CAAC,EAAE,sBAAsB;IAKjF,QAAQ,IAAI,SAAS,oBAAoB,EAAE;IAS3C,QAAQ,CAAC,OAAO,EAAE,sBAAsB,GAAG,IAAI;IAwB/C,QAAQ,IAAI,MAAM,EAAE;IAWpB,KAAK,CAAC,EAAE,EAAE,MAAM,GAAG,kBAAkB;IASrC,OAAO,IAAI,IAAI;CAgDf"}
1
+ {"version":3,"file":"monitor-registry.d.ts","sourceRoot":"","sources":["../../../../../src/core/extensions/builtin/terminal/monitor-registry.ts"],"names":[],"mappings":"AAIA,OAAO,KAAK,EAAE,sBAAsB,EAAE,MAAM,sBAAsB,CAAC;AAGnE,MAAM,WAAW,gBAAgB;IAChC,QAAQ,CAAC,IAAI,EAAE,MAAM,CAAC;IACtB,QAAQ,CAAC,EAAE,EAAE,MAAM,CAAC;IACpB,QAAQ,CAAC,WAAW,EAAE,MAAM,CAAC;IAC7B,QAAQ,CAAC,IAAI,EAAE,MAAM,CAAC;CACtB;AAED,MAAM,WAAW,mBAAmB;IACnC,QAAQ,CAAC,IAAI,EAAE,SAAS,CAAC;IACzB,QAAQ,CAAC,EAAE,EAAE,MAAM,CAAC;IACpB,QAAQ,CAAC,WAAW,EAAE,MAAM,CAAC;IAC7B,QAAQ,CAAC,OAAO,EAAE,MAAM,CAAC;CACzB;AAED,MAAM,MAAM,YAAY,GAAG,gBAAgB,GAAG,mBAAmB,CAAC;AAElE,MAAM,MAAM,kBAAkB,GAAG,SAAS,GAAG,YAAY,GAAG,WAAW,CAAC;AAExE,MAAM,WAAW,oBAAoB;IACpC,QAAQ,CAAC,EAAE,EAAE,MAAM,CAAC;IACpB,QAAQ,CAAC,WAAW,EAAE,MAAM,CAAC;IAC7B,QAAQ,CAAC,MAAM,EAAE,OAAO,CAAC;IACzB,2FAA2F;IAC3F,QAAQ,CAAC,WAAW,EAAE,MAAM,CAAC;CAC7B;AAED,MAAM,WAAW,sBAAsB;IACtC,uGAAuG;IACvG,QAAQ,CAAC,QAAQ,CAAC,EAAE,CAAC,QAAQ,EAAE,SAAS,oBAAoB,EAAE,KAAK,IAAI,CAAC;IACxE,qEAAqE;IACrE,QAAQ,CAAC,OAAO,CAAC,EAAE,MAAM,CAAC,MAAM,IAAI,CAAC,GAAG,IAAI,CAAC;CAC7C;AAED,MAAM,WAAW,0BAA0B;IAC1C,QAAQ,CAAC,WAAW,EAAE,MAAM,CAAC;IAC7B,QAAQ,CAAC,IAAI,EAAE,MAAM,CAAC;IACtB,QAAQ,CAAC,KAAK,EAAE,QAAQ,GAAG,QAAQ,CAAC;IACpC,QAAQ,CAAC,SAAS,EAAE,MAAM,CAAC;IAC3B,QAAQ,CAAC,GAAG,EAAE,MAAM,CAAC;IACrB,QAAQ,CAAC,cAAc,CAAC,EAAE,MAAM,CAAC;CACjC;AAED,MAAM,WAAW,sBAAsB;IACtC,QAAQ,CAAC,EAAE,EAAE,MAAM,CAAC;IACpB,QAAQ,CAAC,WAAW,EAAE,MAAM,CAAC;IAC7B,QAAQ,CAAC,OAAO,EAAE,sBAAsB,CAAC;IACzC,QAAQ,CAAC,MAAM,CAAC,EAAE,MAAM,CAAC;CACzB;AAiDD;;;;GAIG;AACH,qBAAa,eAAe;;gBAYf,IAAI,EAAE,CAAC,KAAK,EAAE,YAAY,KAAK,IAAI,EAAE,OAAO,CAAC,EAAE,sBAAsB;IAMjF,QAAQ,IAAI,SAAS,oBAAoB,EAAE;IASrC,YAAY,CAAC,OAAO,EAAE,0BAA0B,GAAG,OAAO,CAAC,MAAM,CAAC;IA+KlE,QAAQ,CAAC,EAAE,EAAE,MAAM,GAAG,OAAO,CAAC,OAAO,CAAC;IAOtC,YAAY,IAAI,OAAO,CAAC,MAAM,CAAC;IA2HrC,QAAQ,CAAC,OAAO,EAAE,sBAAsB,GAAG,IAAI;IAwB/C,QAAQ,IAAI,MAAM,EAAE;IAWpB,KAAK,CAAC,EAAE,EAAE,MAAM,GAAG,kBAAkB;IAUrC,OAAO,IAAI,IAAI;CAyHf"}
@@ -1,3 +1,7 @@
1
+ import { createHash } from "node:crypto";
2
+ import { watch } from "node:fs";
3
+ import { access, lstat, open, realpath, stat } from "node:fs/promises";
4
+ import { basename, dirname, join, resolve } from "node:path";
1
5
  import { describeExit } from "./tools/spawn.js";
2
6
  /**
3
7
  * Tracks active monitor sessions alongside the terminal manager's existing bash-id registry.
@@ -8,18 +12,351 @@ export class MonitorRegistry {
8
12
  #records = new Map();
9
13
  #emit;
10
14
  #onChange;
15
+ #reserve;
16
+ #files = new Map();
17
+ #nextFileId = 1;
18
+ #disposed = false;
19
+ #pendingRegistrations = 0;
20
+ #pending = new Set();
21
+ #lifecycle = 0;
11
22
  constructor(emit, options) {
12
23
  this.#emit = emit;
13
24
  this.#onChange = options?.onChange;
25
+ this.#reserve = options?.reserve;
14
26
  }
15
27
  snapshot() {
16
- return [...this.#records.values()].map((record) => ({
28
+ return [...this.#records.values(), ...this.#files.values()].map((record) => ({
17
29
  id: record.id,
18
30
  description: record.description,
19
31
  paused: record.paused,
20
32
  startedAtMs: record.startedAtMs,
21
33
  }));
22
34
  }
35
+ async registerFile(options) {
36
+ if (this.#disposed)
37
+ throw new Error("Cannot create file monitor: monitor registry is disposed.");
38
+ this.#pendingRegistrations += 1;
39
+ const lifecycle = this.#lifecycle;
40
+ const release = this.#reserve?.();
41
+ if (this.#reserve && !release) {
42
+ this.#pendingRegistrations -= 1;
43
+ throw new Error("Cannot create file monitor: terminal capacity is already in use.");
44
+ }
45
+ const pending = {
46
+ release: release ?? undefined,
47
+ abort: new AbortController(),
48
+ deadline: setTimeout(() => pending.abort.abort(), options.timeoutMs),
49
+ settled: false,
50
+ };
51
+ this.#pending.add(pending);
52
+ const path = resolve(options.cwd, options.path);
53
+ const parent = dirname(path);
54
+ let approvedParent;
55
+ try {
56
+ await this.#registrationAwait(pending, access(parent));
57
+ approvedParent = await this.#registrationAwait(pending, realpath(parent));
58
+ const approvedParentAtPermission = options.approvedParent;
59
+ if (approvedParentAtPermission !== undefined && approvedParent !== approvedParentAtPermission) {
60
+ throw new Error(`Cannot watch file: parent directory changed during permission approval: ${parent}`);
61
+ }
62
+ if (this.#disposed)
63
+ throw new Error("Cannot create file monitor: monitor registry is disposed.");
64
+ }
65
+ catch (error) {
66
+ this.#finishPending(pending);
67
+ this.#pendingRegistrations -= 1;
68
+ if (error instanceof Error && error.message.includes("disposed"))
69
+ throw error;
70
+ if (error?.code !== "ENOENT") {
71
+ throw new Error(`Cannot access parent directory ${parent}: ${error instanceof Error ? error.message : String(error)}`);
72
+ }
73
+ throw new Error(`Cannot watch file: parent directory does not exist: ${parent}`);
74
+ }
75
+ let initial = null;
76
+ let initialHandle;
77
+ try {
78
+ const target = await this.#registrationAwait(pending, lstat(path));
79
+ if (target.isSymbolicLink())
80
+ throw new Error(`Cannot watch file: target is a symbolic link: ${path}`);
81
+ initialHandle = await this.#registrationAwait(pending, open(path, "r"), async (handle) => {
82
+ await handle.close();
83
+ });
84
+ initial = await this.#registrationAwait(pending, initialHandle.stat());
85
+ if (!initial.isFile())
86
+ throw new Error(`Cannot watch file: target is not a regular file: ${path}`);
87
+ const resolvedTarget = await this.#registrationAwait(pending, realpath(path));
88
+ if (resolvedTarget !== join(approvedParent, basename(path)))
89
+ throw new Error(`Cannot watch file: target identity changed: ${path}`);
90
+ const rebound = await this.#registrationAwait(pending, lstat(path));
91
+ if (rebound.isSymbolicLink())
92
+ throw new Error(`Cannot watch file: target identity changed: ${path}`);
93
+ const reboundStat = await this.#registrationAwait(pending, stat(path));
94
+ if (!reboundStat.isFile() || reboundStat.dev !== initial.dev || reboundStat.ino !== initial.ino)
95
+ throw new Error(`Cannot watch file: target identity changed: ${path}`);
96
+ }
97
+ catch (error) {
98
+ await initialHandle?.close();
99
+ initialHandle = undefined;
100
+ if (error?.code !== "ENOENT") {
101
+ this.#finishPending(pending);
102
+ this.#pendingRegistrations -= 1;
103
+ throw error;
104
+ }
105
+ }
106
+ if (this.#disposed || lifecycle !== this.#lifecycle || this.#pendingRegistrations < 1) {
107
+ this.#finishPending(pending);
108
+ this.#pendingRegistrations -= 1;
109
+ throw new Error("Cannot create file monitor: monitor registry is disposed.");
110
+ }
111
+ const id = `watch_${this.#nextFileId++}`;
112
+ let watcher;
113
+ try {
114
+ const activationParent = await this.#registrationAwait(pending, realpath(parent));
115
+ if (activationParent !== approvedParent) {
116
+ throw new Error(`Cannot watch file: parent directory changed during registration: ${parent}`);
117
+ }
118
+ watcher = watch(activationParent, (_kind, name) => {
119
+ if (!name || basename(String(name)) === basename(path))
120
+ void this.#checkFile(id);
121
+ });
122
+ }
123
+ catch (error) {
124
+ this.#finishPending(pending);
125
+ this.#pendingRegistrations -= 1;
126
+ throw new Error(`Cannot watch file ${path}: ${error instanceof Error ? error.message : String(error)}`);
127
+ }
128
+ let registrationError;
129
+ watcher.on("error", (error) => {
130
+ registrationError = `watcher error: ${error instanceof Error ? error.message : String(error)}`;
131
+ const record = this.#files.get(id);
132
+ if (record)
133
+ this.#settleFile(record, registrationError);
134
+ });
135
+ let registrationCleaned = false;
136
+ const finishRegistration = () => {
137
+ if (registrationCleaned)
138
+ return;
139
+ registrationCleaned = true;
140
+ this.#pendingRegistrations -= 1;
141
+ };
142
+ const cleanupRegistration = () => {
143
+ if (registrationCleaned)
144
+ return;
145
+ watcher.close();
146
+ finishRegistration();
147
+ this.#finishPending(pending);
148
+ };
149
+ if (this.#disposed || lifecycle !== this.#lifecycle) {
150
+ cleanupRegistration();
151
+ throw new Error("Cannot create file monitor: monitor registry is disposed.");
152
+ }
153
+ const canonicalPath = join(approvedParent, basename(path));
154
+ let digest;
155
+ try {
156
+ digest =
157
+ initial && initialHandle
158
+ ? await this.#registrationAwait(pending, this.#digestHandle(initialHandle, pending.abort.signal))
159
+ : "";
160
+ }
161
+ catch (error) {
162
+ cleanupRegistration();
163
+ await initialHandle?.close();
164
+ initialHandle = undefined;
165
+ if (registrationError) {
166
+ this.#emit({ type: "summary", id, description: options.description, summary: registrationError });
167
+ throw new Error(registrationError);
168
+ }
169
+ throw error;
170
+ }
171
+ await initialHandle?.close();
172
+ initialHandle = undefined;
173
+ if (registrationError) {
174
+ cleanupRegistration();
175
+ this.#emit({ type: "summary", id, description: options.description, summary: registrationError });
176
+ throw new Error(registrationError);
177
+ }
178
+ if (this.#disposed || lifecycle !== this.#lifecycle) {
179
+ cleanupRegistration();
180
+ throw new Error("Cannot create file monitor: monitor registry is disposed.");
181
+ }
182
+ const record = {
183
+ id,
184
+ description: options.description,
185
+ startedAtMs: Date.now(),
186
+ path,
187
+ canonicalPath,
188
+ canonicalParent: approvedParent,
189
+ event: options.event,
190
+ watcher,
191
+ release: release ?? (() => { }),
192
+ poll: setInterval(() => void this.#checkFile(id), 250),
193
+ paused: false,
194
+ settled: false,
195
+ present: initial !== null,
196
+ mtimeMs: initial?.mtimeMs ?? 0,
197
+ size: initial?.size ?? 0,
198
+ digest,
199
+ device: initial?.dev ?? 0,
200
+ inode: initial?.ino ?? 0,
201
+ pendingChange: false,
202
+ dirty: false,
203
+ dirtyPasses: 0,
204
+ dirtyWindowStartedAt: 0,
205
+ checking: undefined,
206
+ deadline: setTimeout(() => {
207
+ const current = this.#files.get(id);
208
+ if (current)
209
+ this.#settleFile(current, "watcher timed_out");
210
+ }, options.timeoutMs),
211
+ };
212
+ this.#files.set(id, record);
213
+ finishRegistration();
214
+ this.#finishPending(pending, true);
215
+ if (registrationError || this.#disposed || lifecycle !== this.#lifecycle) {
216
+ this.#settleFile(record, registrationError ?? "watcher killed");
217
+ throw new Error(registrationError ?? "Cannot create file monitor: monitor registry is disposed.");
218
+ }
219
+ this.#notifyChange();
220
+ return id;
221
+ }
222
+ async stopFile(id) {
223
+ const record = this.#files.get(id);
224
+ if (!record)
225
+ return false;
226
+ this.#settleFile(record, "watcher killed");
227
+ return true;
228
+ }
229
+ async stopAllFiles() {
230
+ this.#lifecycle += 1;
231
+ const records = [...this.#files.values()];
232
+ for (const record of records)
233
+ this.#settleFile(record, "watcher killed");
234
+ for (const pending of [...this.#pending])
235
+ this.#finishPending(pending);
236
+ return records.length;
237
+ }
238
+ #settleFile(record, summary) {
239
+ if (record.settled)
240
+ return;
241
+ record.settled = true;
242
+ clearInterval(record.poll);
243
+ clearTimeout(record.deadline);
244
+ record.watcher.close();
245
+ record.release();
246
+ this.#files.delete(record.id);
247
+ this.#notifyChange();
248
+ this.#emit({ type: "summary", id: record.id, description: record.description, summary });
249
+ }
250
+ async #checkFile(id) {
251
+ const record = this.#files.get(id);
252
+ if (!record || record.settled)
253
+ return;
254
+ if (record.checking) {
255
+ record.dirty = true;
256
+ return;
257
+ }
258
+ record.checking = this.#checkFileImpl(record)
259
+ .catch((error) => {
260
+ if (!record.settled)
261
+ this.#settleFile(record, `watcher error: ${error instanceof Error ? error.message : String(error)}`);
262
+ })
263
+ .finally(() => {
264
+ record.checking = undefined;
265
+ const now = Date.now();
266
+ if (now - record.dirtyWindowStartedAt >= 1_000) {
267
+ record.dirtyWindowStartedAt = now;
268
+ record.dirtyPasses = 0;
269
+ }
270
+ if (record.dirty && !record.settled && record.dirtyPasses < 1) {
271
+ record.dirty = false;
272
+ record.dirtyPasses += 1;
273
+ void this.#checkFile(id);
274
+ }
275
+ else {
276
+ record.dirty = false;
277
+ }
278
+ });
279
+ return record.checking;
280
+ }
281
+ async #checkFileImpl(record) {
282
+ if (record.settled)
283
+ return;
284
+ let current = null;
285
+ let handle;
286
+ try {
287
+ const currentParent = await realpath(dirname(record.path));
288
+ if (currentParent !== record.canonicalParent) {
289
+ this.#settleFile(record, `watcher error: monitored parent changed: ${dirname(record.path)}`);
290
+ return;
291
+ }
292
+ const target = await lstat(record.canonicalPath);
293
+ if (target.isSymbolicLink())
294
+ throw new Error(`Cannot watch file: target identity changed: ${record.path}`);
295
+ const resolvedTarget = await realpath(record.canonicalPath);
296
+ if (resolvedTarget !== record.canonicalPath)
297
+ throw new Error(`Cannot watch file: target identity changed: ${record.path}`);
298
+ handle = await open(record.canonicalPath, "r");
299
+ const opened = await handle.stat();
300
+ if (!opened.isFile())
301
+ throw new Error(`Cannot watch file: target is not a regular file: ${record.path}`);
302
+ if ((opened.dev !== record.device || opened.ino !== record.inode) && opened.nlink > 1)
303
+ throw new Error(`Cannot watch file: target identity changed: ${record.path}`);
304
+ current = opened;
305
+ const rebound = await lstat(record.canonicalPath);
306
+ if (rebound.isSymbolicLink())
307
+ throw new Error(`Cannot watch file: target identity changed: ${record.path}`);
308
+ const reboundPath = await stat(record.canonicalPath);
309
+ if (!reboundPath.isFile() || reboundPath.dev !== opened.dev || reboundPath.ino !== opened.ino)
310
+ throw new Error(`Cannot watch file: target identity changed: ${record.path}`);
311
+ }
312
+ catch (error) {
313
+ await handle?.close();
314
+ if (error?.code !== "ENOENT") {
315
+ this.#settleFile(record, `watcher error: ${error instanceof Error ? error.message : String(error)}`);
316
+ return;
317
+ }
318
+ }
319
+ const present = current !== null;
320
+ let digest = "";
321
+ try {
322
+ digest = present && handle ? await this.#digestHandle(handle) : "";
323
+ if (present && handle) {
324
+ const afterDigest = await stat(record.canonicalPath);
325
+ const opened = await handle.stat();
326
+ if (afterDigest.dev !== opened.dev || afterDigest.ino !== opened.ino)
327
+ throw new Error(`Cannot watch file: target identity changed: ${record.path}`);
328
+ }
329
+ }
330
+ finally {
331
+ await handle?.close();
332
+ }
333
+ const changed = record.event === "create"
334
+ ? !record.present && present
335
+ : record.present &&
336
+ present &&
337
+ (current.mtimeMs !== record.mtimeMs || current.size !== record.size || digest !== record.digest);
338
+ record.pendingChange ||= changed;
339
+ if (!record.present && current) {
340
+ record.device = current.dev;
341
+ record.inode = current.ino;
342
+ }
343
+ record.present = present;
344
+ record.mtimeMs = current?.mtimeMs ?? 0;
345
+ record.size = current?.size ?? 0;
346
+ record.digest = digest;
347
+ if (!record.pendingChange || record.paused || record.settled)
348
+ return;
349
+ record.pendingChange = false;
350
+ if (record.settled)
351
+ return;
352
+ this.#emit({
353
+ type: "line",
354
+ id: record.id,
355
+ description: record.description,
356
+ line: `${record.event} ${record.path}`,
357
+ });
358
+ this.#settleFile(record, "watcher completed");
359
+ }
23
360
  register(options) {
24
361
  const record = {
25
362
  id: options.id,
@@ -45,7 +382,7 @@ export class MonitorRegistry {
45
382
  }
46
383
  pauseAll() {
47
384
  const paused = [];
48
- for (const record of this.#records.values()) {
385
+ for (const record of [...this.#records.values(), ...this.#files.values()]) {
49
386
  if (record.paused)
50
387
  continue;
51
388
  record.paused = true;
@@ -56,21 +393,84 @@ export class MonitorRegistry {
56
393
  return paused;
57
394
  }
58
395
  rearm(id) {
59
- const record = this.#records.get(id);
396
+ const record = this.#records.get(id) ?? this.#files.get(id);
60
397
  if (!record)
61
398
  return "not_found";
62
399
  if (!record.paused)
63
400
  return "not_paused";
64
401
  record.paused = false;
65
402
  this.#notifyChange();
403
+ if ("pendingChange" in record && record.pendingChange)
404
+ void this.#checkFile(record.id);
66
405
  return "rearmed";
67
406
  }
68
407
  dispose() {
408
+ this.#disposed = true;
409
+ this.#lifecycle += 1;
69
410
  for (const record of this.#records.values())
70
411
  this.#disposeRecord(record);
412
+ for (const record of this.#files.values())
413
+ this.#settleFile(record, "watcher disposed");
414
+ for (const pending of this.#pending)
415
+ this.#finishPending(pending);
71
416
  this.#records.clear();
72
417
  this.#notifyChange();
73
418
  }
419
+ #finishPending(pending, transfer = false) {
420
+ if (pending.settled)
421
+ return;
422
+ pending.settled = true;
423
+ clearTimeout(pending.deadline);
424
+ this.#pending.delete(pending);
425
+ if (transfer)
426
+ pending.release = undefined;
427
+ else {
428
+ pending.abort.abort();
429
+ pending.release?.();
430
+ }
431
+ }
432
+ async #registrationAwait(pending, operation, lateCleanup) {
433
+ if (pending.abort.signal.aborted) {
434
+ void operation.then((value) => lateCleanup?.(value), () => undefined);
435
+ throw new Error("Cannot create file monitor: registration timed out or was disposed.");
436
+ }
437
+ let aborted = false;
438
+ const abort = new Promise((_, reject) => pending.abort.signal.addEventListener("abort", () => {
439
+ aborted = true;
440
+ reject(new Error("Cannot create file monitor: registration timed out or was disposed."));
441
+ }, { once: true }));
442
+ const guarded = operation.then((value) => {
443
+ if (aborted)
444
+ void lateCleanup?.(value);
445
+ return value;
446
+ }, (error) => {
447
+ if (aborted)
448
+ return undefined;
449
+ throw error;
450
+ });
451
+ return Promise.race([guarded, abort]);
452
+ }
453
+ async #digestHandle(handle, signal) {
454
+ const SAMPLE_SIZE = 64 * 1024;
455
+ if (signal?.aborted)
456
+ throw new Error("file monitor registration cancelled");
457
+ const metadata = await handle.stat();
458
+ const hash = createHash("sha256");
459
+ const first = Buffer.alloc(Math.min(SAMPLE_SIZE, metadata.size));
460
+ if (first.length > 0) {
461
+ await handle.read(first, 0, first.length, 0);
462
+ hash.update(first);
463
+ }
464
+ if (metadata.size > SAMPLE_SIZE) {
465
+ const middle = Buffer.alloc(SAMPLE_SIZE);
466
+ await handle.read(middle, 0, middle.length, Math.floor((metadata.size - middle.length) / 2));
467
+ hash.update(middle);
468
+ const last = Buffer.alloc(SAMPLE_SIZE);
469
+ await handle.read(last, 0, last.length, metadata.size - last.length);
470
+ hash.update(last);
471
+ }
472
+ return `${metadata.size}:${hash.digest("hex")}`;
473
+ }
74
474
  #notifyChange() {
75
475
  this.#onChange?.(this.snapshot());
76
476
  }
@@ -1 +1 @@
1
- {"version":3,"file":"monitor-registry.js","sourceRoot":"","sources":["../../../../../src/core/extensions/builtin/terminal/monitor-registry.ts"],"names":[],"mappings":"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","sourcesContent":["import type { TerminalRuntimeSession } from \"./runtime-session.ts\";\nimport { describeExit } from \"./tools/spawn.ts\";\n\nexport interface MonitorLineEvent {\n\treadonly type: \"line\";\n\treadonly id: string;\n\treadonly description: string;\n\treadonly line: string;\n}\n\nexport interface MonitorSummaryEvent {\n\treadonly type: \"summary\";\n\treadonly id: string;\n\treadonly description: string;\n\treadonly summary: string;\n}\n\nexport type MonitorEvent = MonitorLineEvent | MonitorSummaryEvent;\n\nexport type MonitorRearmResult = \"rearmed\" | \"not_paused\" | \"not_found\";\n\nexport interface MonitorSnapshotEntry {\n\treadonly id: string;\n\treadonly description: string;\n\treadonly paused: boolean;\n\t/** Epoch milliseconds when the watch registered; feeds the footer's live elapsed label. */\n\treadonly startedAtMs: number;\n}\n\nexport interface MonitorRegistryOptions {\n\t/** Observes every registry transition (register/pause/rearm/settle/dispose) with the live snapshot. */\n\treadonly onChange?: (snapshot: readonly MonitorSnapshotEntry[]) => void;\n}\n\nexport interface RegisterMonitorOptions {\n\treadonly id: string;\n\treadonly description: string;\n\treadonly runtime: TerminalRuntimeSession;\n\treadonly filter?: RegExp;\n}\n\ninterface MonitorRecord {\n\treadonly id: string;\n\treadonly description: string;\n\treadonly startedAtMs: number;\n\treadonly runtime: TerminalRuntimeSession;\n\treadonly filter: RegExp | undefined;\n\tlineBuffer: string;\n\tpaused: boolean;\n\tsettled: boolean;\n\tunsubscribeOutput: (() => void) | undefined;\n\tunsubscribeExit: (() => void) | undefined;\n}\n\n/**\n * Tracks active monitor sessions alongside the terminal manager's existing bash-id registry.\n * Output is deliberately retained only by TerminalRuntimeSession's bounded history; this\n * registry holds at most one unfinished line for each live monitor.\n */\nexport class MonitorRegistry {\n\treadonly #records = new Map<string, MonitorRecord>();\n\treadonly #emit: (event: MonitorEvent) => void;\n\treadonly #onChange: ((snapshot: readonly MonitorSnapshotEntry[]) => void) | undefined;\n\n\tconstructor(emit: (event: MonitorEvent) => void, options?: MonitorRegistryOptions) {\n\t\tthis.#emit = emit;\n\t\tthis.#onChange = options?.onChange;\n\t}\n\n\tsnapshot(): readonly MonitorSnapshotEntry[] {\n\t\treturn [...this.#records.values()].map((record) => ({\n\t\t\tid: record.id,\n\t\t\tdescription: record.description,\n\t\t\tpaused: record.paused,\n\t\t\tstartedAtMs: record.startedAtMs,\n\t\t}));\n\t}\n\n\tregister(options: RegisterMonitorOptions): void {\n\t\tconst record: MonitorRecord = {\n\t\t\tid: options.id,\n\t\t\tdescription: options.description,\n\t\t\tstartedAtMs: Date.now(),\n\t\t\truntime: options.runtime,\n\t\t\tfilter: options.filter,\n\t\t\tlineBuffer: \"\",\n\t\t\tpaused: false,\n\t\t\tsettled: false,\n\t\t\tunsubscribeOutput: undefined,\n\t\t\tunsubscribeExit: undefined,\n\t\t};\n\t\tthis.#records.set(record.id, record);\n\t\tthis.#notifyChange();\n\n\t\t// Runtime output is already bounded. Read what was produced before monitor registration,\n\t\t// then subscribe synchronously so a fast watcher cannot lose its first line.\n\t\tthis.#consume(record, record.runtime.fullOutput());\n\t\trecord.unsubscribeOutput = record.runtime.onOutput((chunk) => this.#consume(record, chunk));\n\t\trecord.unsubscribeExit = record.runtime.session.onExit(() => this.#settle(record));\n\t\tif (record.runtime.exited) this.#settle(record);\n\t}\n\n\tpauseAll(): string[] {\n\t\tconst paused: string[] = [];\n\t\tfor (const record of this.#records.values()) {\n\t\t\tif (record.paused) continue;\n\t\t\trecord.paused = true;\n\t\t\tpaused.push(record.id);\n\t\t}\n\t\tif (paused.length > 0) this.#notifyChange();\n\t\treturn paused;\n\t}\n\n\trearm(id: string): MonitorRearmResult {\n\t\tconst record = this.#records.get(id);\n\t\tif (!record) return \"not_found\";\n\t\tif (!record.paused) return \"not_paused\";\n\t\trecord.paused = false;\n\t\tthis.#notifyChange();\n\t\treturn \"rearmed\";\n\t}\n\n\tdispose(): void {\n\t\tfor (const record of this.#records.values()) this.#disposeRecord(record);\n\t\tthis.#records.clear();\n\t\tthis.#notifyChange();\n\t}\n\n\t#notifyChange(): void {\n\t\tthis.#onChange?.(this.snapshot());\n\t}\n\n\t#consume(record: MonitorRecord, chunk: string): void {\n\t\tif (record.settled || chunk.length === 0) return;\n\t\tlet remaining = record.lineBuffer + chunk;\n\t\tfor (;;) {\n\t\t\tconst newline = remaining.indexOf(\"\\n\");\n\t\t\tif (newline < 0) break;\n\t\t\tconst rawLine = remaining.slice(0, newline);\n\t\t\tremaining = remaining.slice(newline + 1);\n\t\t\tconst line = rawLine.endsWith(\"\\r\") ? rawLine.slice(0, -1) : rawLine;\n\t\t\tif (record.paused || (record.filter && !record.filter.test(line))) continue;\n\t\t\tthis.#emit({ type: \"line\", id: record.id, description: record.description, line });\n\t\t}\n\t\trecord.lineBuffer = remaining;\n\t}\n\n\t#settle(record: MonitorRecord): void {\n\t\tif (record.settled) return;\n\t\trecord.settled = true;\n\t\trecord.unsubscribeOutput?.();\n\t\trecord.unsubscribeExit?.();\n\t\tthis.#records.delete(record.id);\n\t\tthis.#notifyChange();\n\t\tconst status = describeExit(record.runtime) ?? \"exited\";\n\t\tconst code = record.runtime.exitResult?.exitCode;\n\t\tconst codeText = code === null || code === undefined ? \"\" : ` (exit code ${code})`;\n\t\tthis.#emit({\n\t\t\ttype: \"summary\",\n\t\t\tid: record.id,\n\t\t\tdescription: record.description,\n\t\t\tsummary: `watcher ${status}${codeText}`,\n\t\t});\n\t}\n\n\t#disposeRecord(record: MonitorRecord): void {\n\t\trecord.settled = true;\n\t\trecord.unsubscribeOutput?.();\n\t\trecord.unsubscribeExit?.();\n\t}\n}\n"]}
1
+ {"version":3,"file":"monitor-registry.js","sourceRoot":"","sources":["../../../../../src/core/extensions/builtin/terminal/monitor-registry.ts"],"names":[],"mappings":"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","sourcesContent":["import { createHash } from \"node:crypto\";\nimport { type FSWatcher, watch } from \"node:fs\";\nimport { access, type FileHandle, lstat, open, realpath, stat } from \"node:fs/promises\";\nimport { basename, dirname, join, resolve } from \"node:path\";\nimport type { TerminalRuntimeSession } from \"./runtime-session.ts\";\nimport { describeExit } from \"./tools/spawn.ts\";\n\nexport interface MonitorLineEvent {\n\treadonly type: \"line\";\n\treadonly id: string;\n\treadonly description: string;\n\treadonly line: string;\n}\n\nexport interface MonitorSummaryEvent {\n\treadonly type: \"summary\";\n\treadonly id: string;\n\treadonly description: string;\n\treadonly summary: string;\n}\n\nexport type MonitorEvent = MonitorLineEvent | MonitorSummaryEvent;\n\nexport type MonitorRearmResult = \"rearmed\" | \"not_paused\" | \"not_found\";\n\nexport interface MonitorSnapshotEntry {\n\treadonly id: string;\n\treadonly description: string;\n\treadonly paused: boolean;\n\t/** Epoch milliseconds when the watch registered; feeds the footer's live elapsed label. */\n\treadonly startedAtMs: number;\n}\n\nexport interface MonitorRegistryOptions {\n\t/** Observes every registry transition (register/pause/rearm/settle/dispose) with the live snapshot. */\n\treadonly onChange?: (snapshot: readonly MonitorSnapshotEntry[]) => void;\n\t/** Reserves one shared terminal capacity slot for a native watch. */\n\treadonly reserve?: () => (() => void) | null;\n}\n\nexport interface RegisterFileMonitorOptions {\n\treadonly description: string;\n\treadonly path: string;\n\treadonly event: \"create\" | \"modify\";\n\treadonly timeoutMs: number;\n\treadonly cwd: string;\n\treadonly approvedParent?: string;\n}\n\nexport interface RegisterMonitorOptions {\n\treadonly id: string;\n\treadonly description: string;\n\treadonly runtime: TerminalRuntimeSession;\n\treadonly filter?: RegExp;\n}\n\ninterface PendingFileRegistration {\n\trelease: (() => void) | undefined;\n\treadonly abort: AbortController;\n\treadonly deadline: ReturnType<typeof setTimeout>;\n\tsettled: boolean;\n}\n\ninterface FileMonitorRecord {\n\treadonly id: string;\n\treadonly description: string;\n\treadonly startedAtMs: number;\n\treadonly path: string;\n\treadonly canonicalPath: string;\n\treadonly canonicalParent: string;\n\treadonly event: \"create\" | \"modify\";\n\treadonly watcher: FSWatcher;\n\treadonly release: () => void;\n\treadonly poll: ReturnType<typeof setInterval>;\n\tpaused: boolean;\n\tsettled: boolean;\n\tpresent: boolean;\n\tmtimeMs: number;\n\tsize: number;\n\tdigest: string;\n\tdevice: number;\n\tinode: number;\n\tpendingChange: boolean;\n\tdirty: boolean;\n\tdirtyPasses: number;\n\tdirtyWindowStartedAt: number;\n\tchecking: Promise<void> | undefined;\n\treadonly deadline: ReturnType<typeof setTimeout>;\n}\n\ninterface MonitorRecord {\n\treadonly id: string;\n\treadonly description: string;\n\treadonly startedAtMs: number;\n\treadonly runtime: TerminalRuntimeSession;\n\treadonly filter: RegExp | undefined;\n\tlineBuffer: string;\n\tpaused: boolean;\n\tsettled: boolean;\n\tunsubscribeOutput: (() => void) | undefined;\n\tunsubscribeExit: (() => void) | undefined;\n}\n\n/**\n * Tracks active monitor sessions alongside the terminal manager's existing bash-id registry.\n * Output is deliberately retained only by TerminalRuntimeSession's bounded history; this\n * registry holds at most one unfinished line for each live monitor.\n */\nexport class MonitorRegistry {\n\treadonly #records = new Map<string, MonitorRecord>();\n\treadonly #emit: (event: MonitorEvent) => void;\n\treadonly #onChange: ((snapshot: readonly MonitorSnapshotEntry[]) => void) | undefined;\n\treadonly #reserve: (() => (() => void) | null) | undefined;\n\treadonly #files = new Map<string, FileMonitorRecord>();\n\t#nextFileId = 1;\n\t#disposed = false;\n\t#pendingRegistrations = 0;\n\treadonly #pending = new Set<PendingFileRegistration>();\n\t#lifecycle = 0;\n\n\tconstructor(emit: (event: MonitorEvent) => void, options?: MonitorRegistryOptions) {\n\t\tthis.#emit = emit;\n\t\tthis.#onChange = options?.onChange;\n\t\tthis.#reserve = options?.reserve;\n\t}\n\n\tsnapshot(): readonly MonitorSnapshotEntry[] {\n\t\treturn [...this.#records.values(), ...this.#files.values()].map((record) => ({\n\t\t\tid: record.id,\n\t\t\tdescription: record.description,\n\t\t\tpaused: record.paused,\n\t\t\tstartedAtMs: record.startedAtMs,\n\t\t}));\n\t}\n\n\tasync registerFile(options: RegisterFileMonitorOptions): Promise<string> {\n\t\tif (this.#disposed) throw new Error(\"Cannot create file monitor: monitor registry is disposed.\");\n\t\tthis.#pendingRegistrations += 1;\n\t\tconst lifecycle = this.#lifecycle;\n\t\tconst release = this.#reserve?.();\n\t\tif (this.#reserve && !release) {\n\t\t\tthis.#pendingRegistrations -= 1;\n\t\t\tthrow new Error(\"Cannot create file monitor: terminal capacity is already in use.\");\n\t\t}\n\t\tconst pending: PendingFileRegistration = {\n\t\t\trelease: release ?? undefined,\n\t\t\tabort: new AbortController(),\n\t\t\tdeadline: setTimeout(() => pending.abort.abort(), options.timeoutMs),\n\t\t\tsettled: false,\n\t\t};\n\t\tthis.#pending.add(pending);\n\t\tconst path = resolve(options.cwd, options.path);\n\t\tconst parent = dirname(path);\n\t\tlet approvedParent: string;\n\t\ttry {\n\t\t\tawait this.#registrationAwait(pending, access(parent));\n\t\t\tapprovedParent = await this.#registrationAwait(pending, realpath(parent));\n\t\t\tconst approvedParentAtPermission = options.approvedParent;\n\t\t\tif (approvedParentAtPermission !== undefined && approvedParent !== approvedParentAtPermission) {\n\t\t\t\tthrow new Error(`Cannot watch file: parent directory changed during permission approval: ${parent}`);\n\t\t\t}\n\t\t\tif (this.#disposed) throw new Error(\"Cannot create file monitor: monitor registry is disposed.\");\n\t\t} catch (error) {\n\t\t\tthis.#finishPending(pending);\n\t\t\tthis.#pendingRegistrations -= 1;\n\t\t\tif (error instanceof Error && error.message.includes(\"disposed\")) throw error;\n\t\t\tif ((error as NodeJS.ErrnoException)?.code !== \"ENOENT\") {\n\t\t\t\tthrow new Error(\n\t\t\t\t\t`Cannot access parent directory ${parent}: ${error instanceof Error ? error.message : String(error)}`,\n\t\t\t\t);\n\t\t\t}\n\t\t\tthrow new Error(`Cannot watch file: parent directory does not exist: ${parent}`);\n\t\t}\n\t\tlet initial: Awaited<ReturnType<typeof stat>> | null = null;\n\t\tlet initialHandle: FileHandle | undefined;\n\t\ttry {\n\t\t\tconst target = await this.#registrationAwait(pending, lstat(path));\n\t\t\tif (target.isSymbolicLink()) throw new Error(`Cannot watch file: target is a symbolic link: ${path}`);\n\t\t\tinitialHandle = await this.#registrationAwait(pending, open(path, \"r\"), async (handle) => {\n\t\t\t\tawait handle.close();\n\t\t\t});\n\t\t\tinitial = await this.#registrationAwait(pending, initialHandle.stat());\n\t\t\tif (!initial.isFile()) throw new Error(`Cannot watch file: target is not a regular file: ${path}`);\n\t\t\tconst resolvedTarget = await this.#registrationAwait(pending, realpath(path));\n\t\t\tif (resolvedTarget !== join(approvedParent, basename(path)))\n\t\t\t\tthrow new Error(`Cannot watch file: target identity changed: ${path}`);\n\t\t\tconst rebound = await this.#registrationAwait(pending, lstat(path));\n\t\t\tif (rebound.isSymbolicLink()) throw new Error(`Cannot watch file: target identity changed: ${path}`);\n\t\t\tconst reboundStat = await this.#registrationAwait(pending, stat(path));\n\t\t\tif (!reboundStat.isFile() || reboundStat.dev !== initial.dev || reboundStat.ino !== initial.ino)\n\t\t\t\tthrow new Error(`Cannot watch file: target identity changed: ${path}`);\n\t\t} catch (error) {\n\t\t\tawait initialHandle?.close();\n\t\t\tinitialHandle = undefined;\n\t\t\tif ((error as NodeJS.ErrnoException)?.code !== \"ENOENT\") {\n\t\t\t\tthis.#finishPending(pending);\n\t\t\t\tthis.#pendingRegistrations -= 1;\n\t\t\t\tthrow error;\n\t\t\t}\n\t\t}\n\t\tif (this.#disposed || lifecycle !== this.#lifecycle || this.#pendingRegistrations < 1) {\n\t\t\tthis.#finishPending(pending);\n\t\t\tthis.#pendingRegistrations -= 1;\n\t\t\tthrow new Error(\"Cannot create file monitor: monitor registry is disposed.\");\n\t\t}\n\t\tconst id = `watch_${this.#nextFileId++}`;\n\t\tlet watcher: FSWatcher;\n\t\ttry {\n\t\t\tconst activationParent = await this.#registrationAwait(pending, realpath(parent));\n\t\t\tif (activationParent !== approvedParent) {\n\t\t\t\tthrow new Error(`Cannot watch file: parent directory changed during registration: ${parent}`);\n\t\t\t}\n\t\t\twatcher = watch(activationParent, (_kind, name) => {\n\t\t\t\tif (!name || basename(String(name)) === basename(path)) void this.#checkFile(id);\n\t\t\t});\n\t\t} catch (error) {\n\t\t\tthis.#finishPending(pending);\n\t\t\tthis.#pendingRegistrations -= 1;\n\t\t\tthrow new Error(`Cannot watch file ${path}: ${error instanceof Error ? error.message : String(error)}`);\n\t\t}\n\t\tlet registrationError: string | undefined;\n\t\twatcher.on(\"error\", (error) => {\n\t\t\tregistrationError = `watcher error: ${error instanceof Error ? error.message : String(error)}`;\n\t\t\tconst record = this.#files.get(id);\n\t\t\tif (record) this.#settleFile(record, registrationError);\n\t\t});\n\t\tlet registrationCleaned = false;\n\t\tconst finishRegistration = () => {\n\t\t\tif (registrationCleaned) return;\n\t\t\tregistrationCleaned = true;\n\t\t\tthis.#pendingRegistrations -= 1;\n\t\t};\n\t\tconst cleanupRegistration = () => {\n\t\t\tif (registrationCleaned) return;\n\t\t\twatcher.close();\n\t\t\tfinishRegistration();\n\t\t\tthis.#finishPending(pending);\n\t\t};\n\t\tif (this.#disposed || lifecycle !== this.#lifecycle) {\n\t\t\tcleanupRegistration();\n\t\t\tthrow new Error(\"Cannot create file monitor: monitor registry is disposed.\");\n\t\t}\n\t\tconst canonicalPath = join(approvedParent, basename(path));\n\t\tlet digest: string;\n\t\ttry {\n\t\t\tdigest =\n\t\t\t\tinitial && initialHandle\n\t\t\t\t\t? await this.#registrationAwait(pending, this.#digestHandle(initialHandle, pending.abort.signal))\n\t\t\t\t\t: \"\";\n\t\t} catch (error) {\n\t\t\tcleanupRegistration();\n\t\t\tawait initialHandle?.close();\n\t\t\tinitialHandle = undefined;\n\t\t\tif (registrationError) {\n\t\t\t\tthis.#emit({ type: \"summary\", id, description: options.description, summary: registrationError });\n\t\t\t\tthrow new Error(registrationError);\n\t\t\t}\n\t\t\tthrow error;\n\t\t}\n\t\tawait initialHandle?.close();\n\t\tinitialHandle = undefined;\n\t\tif (registrationError) {\n\t\t\tcleanupRegistration();\n\t\t\tthis.#emit({ type: \"summary\", id, description: options.description, summary: registrationError });\n\t\t\tthrow new Error(registrationError);\n\t\t}\n\t\tif (this.#disposed || lifecycle !== this.#lifecycle) {\n\t\t\tcleanupRegistration();\n\t\t\tthrow new Error(\"Cannot create file monitor: monitor registry is disposed.\");\n\t\t}\n\t\tconst record: FileMonitorRecord = {\n\t\t\tid,\n\t\t\tdescription: options.description,\n\t\t\tstartedAtMs: Date.now(),\n\t\t\tpath,\n\t\t\tcanonicalPath,\n\t\t\tcanonicalParent: approvedParent,\n\t\t\tevent: options.event,\n\t\t\twatcher,\n\t\t\trelease: release ?? (() => {}),\n\t\t\tpoll: setInterval(() => void this.#checkFile(id), 250),\n\t\t\tpaused: false,\n\t\t\tsettled: false,\n\t\t\tpresent: initial !== null,\n\t\t\tmtimeMs: initial?.mtimeMs ?? 0,\n\t\t\tsize: initial?.size ?? 0,\n\t\t\tdigest,\n\t\t\tdevice: initial?.dev ?? 0,\n\t\t\tinode: initial?.ino ?? 0,\n\t\t\tpendingChange: false,\n\t\t\tdirty: false,\n\t\t\tdirtyPasses: 0,\n\t\t\tdirtyWindowStartedAt: 0,\n\t\t\tchecking: undefined,\n\t\t\tdeadline: setTimeout(() => {\n\t\t\t\tconst current = this.#files.get(id);\n\t\t\t\tif (current) this.#settleFile(current, \"watcher timed_out\");\n\t\t\t}, options.timeoutMs),\n\t\t};\n\t\tthis.#files.set(id, record);\n\t\tfinishRegistration();\n\t\tthis.#finishPending(pending, true);\n\t\tif (registrationError || this.#disposed || lifecycle !== this.#lifecycle) {\n\t\t\tthis.#settleFile(record, registrationError ?? \"watcher killed\");\n\t\t\tthrow new Error(registrationError ?? \"Cannot create file monitor: monitor registry is disposed.\");\n\t\t}\n\t\tthis.#notifyChange();\n\t\treturn id;\n\t}\n\n\tasync stopFile(id: string): Promise<boolean> {\n\t\tconst record = this.#files.get(id);\n\t\tif (!record) return false;\n\t\tthis.#settleFile(record, \"watcher killed\");\n\t\treturn true;\n\t}\n\n\tasync stopAllFiles(): Promise<number> {\n\t\tthis.#lifecycle += 1;\n\t\tconst records = [...this.#files.values()];\n\t\tfor (const record of records) this.#settleFile(record, \"watcher killed\");\n\t\tfor (const pending of [...this.#pending]) this.#finishPending(pending);\n\t\treturn records.length;\n\t}\n\n\t#settleFile(record: FileMonitorRecord, summary: string): void {\n\t\tif (record.settled) return;\n\t\trecord.settled = true;\n\t\tclearInterval(record.poll);\n\t\tclearTimeout(record.deadline);\n\t\trecord.watcher.close();\n\t\trecord.release();\n\t\tthis.#files.delete(record.id);\n\t\tthis.#notifyChange();\n\t\tthis.#emit({ type: \"summary\", id: record.id, description: record.description, summary });\n\t}\n\n\tasync #checkFile(id: string): Promise<void> {\n\t\tconst record = this.#files.get(id);\n\t\tif (!record || record.settled) return;\n\t\tif (record.checking) {\n\t\t\trecord.dirty = true;\n\t\t\treturn;\n\t\t}\n\t\trecord.checking = this.#checkFileImpl(record)\n\t\t\t.catch((error) => {\n\t\t\t\tif (!record.settled)\n\t\t\t\t\tthis.#settleFile(record, `watcher error: ${error instanceof Error ? error.message : String(error)}`);\n\t\t\t})\n\t\t\t.finally(() => {\n\t\t\t\trecord.checking = undefined;\n\t\t\t\tconst now = Date.now();\n\t\t\t\tif (now - record.dirtyWindowStartedAt >= 1_000) {\n\t\t\t\t\trecord.dirtyWindowStartedAt = now;\n\t\t\t\t\trecord.dirtyPasses = 0;\n\t\t\t\t}\n\t\t\t\tif (record.dirty && !record.settled && record.dirtyPasses < 1) {\n\t\t\t\t\trecord.dirty = false;\n\t\t\t\t\trecord.dirtyPasses += 1;\n\t\t\t\t\tvoid this.#checkFile(id);\n\t\t\t\t} else {\n\t\t\t\t\trecord.dirty = false;\n\t\t\t\t}\n\t\t\t});\n\t\treturn record.checking;\n\t}\n\n\tasync #checkFileImpl(record: FileMonitorRecord): Promise<void> {\n\t\tif (record.settled) return;\n\t\tlet current: Awaited<ReturnType<typeof stat>> | null = null;\n\t\tlet handle: FileHandle | undefined;\n\t\ttry {\n\t\t\tconst currentParent = await realpath(dirname(record.path));\n\t\t\tif (currentParent !== record.canonicalParent) {\n\t\t\t\tthis.#settleFile(record, `watcher error: monitored parent changed: ${dirname(record.path)}`);\n\t\t\t\treturn;\n\t\t\t}\n\t\t\tconst target = await lstat(record.canonicalPath);\n\t\t\tif (target.isSymbolicLink()) throw new Error(`Cannot watch file: target identity changed: ${record.path}`);\n\t\t\tconst resolvedTarget = await realpath(record.canonicalPath);\n\t\t\tif (resolvedTarget !== record.canonicalPath)\n\t\t\t\tthrow new Error(`Cannot watch file: target identity changed: ${record.path}`);\n\t\t\thandle = await open(record.canonicalPath, \"r\");\n\t\t\tconst opened = await handle.stat();\n\t\t\tif (!opened.isFile()) throw new Error(`Cannot watch file: target is not a regular file: ${record.path}`);\n\t\t\tif ((opened.dev !== record.device || opened.ino !== record.inode) && opened.nlink > 1)\n\t\t\t\tthrow new Error(`Cannot watch file: target identity changed: ${record.path}`);\n\t\t\tcurrent = opened;\n\t\t\tconst rebound = await lstat(record.canonicalPath);\n\t\t\tif (rebound.isSymbolicLink()) throw new Error(`Cannot watch file: target identity changed: ${record.path}`);\n\t\t\tconst reboundPath = await stat(record.canonicalPath);\n\t\t\tif (!reboundPath.isFile() || reboundPath.dev !== opened.dev || reboundPath.ino !== opened.ino)\n\t\t\t\tthrow new Error(`Cannot watch file: target identity changed: ${record.path}`);\n\t\t} catch (error) {\n\t\t\tawait handle?.close();\n\t\t\tif ((error as NodeJS.ErrnoException)?.code !== \"ENOENT\") {\n\t\t\t\tthis.#settleFile(record, `watcher error: ${error instanceof Error ? error.message : String(error)}`);\n\t\t\t\treturn;\n\t\t\t}\n\t\t}\n\t\tconst present = current !== null;\n\t\tlet digest = \"\";\n\t\ttry {\n\t\t\tdigest = present && handle ? await this.#digestHandle(handle) : \"\";\n\t\t\tif (present && handle) {\n\t\t\t\tconst afterDigest = await stat(record.canonicalPath);\n\t\t\t\tconst opened = await handle.stat();\n\t\t\t\tif (afterDigest.dev !== opened.dev || afterDigest.ino !== opened.ino)\n\t\t\t\t\tthrow new Error(`Cannot watch file: target identity changed: ${record.path}`);\n\t\t\t}\n\t\t} finally {\n\t\t\tawait handle?.close();\n\t\t}\n\t\tconst changed =\n\t\t\trecord.event === \"create\"\n\t\t\t\t? !record.present && present\n\t\t\t\t: record.present &&\n\t\t\t\t\tpresent &&\n\t\t\t\t\t(current!.mtimeMs !== record.mtimeMs || current!.size !== record.size || digest !== record.digest);\n\t\trecord.pendingChange ||= changed;\n\t\tif (!record.present && current) {\n\t\t\trecord.device = current.dev;\n\t\t\trecord.inode = current.ino;\n\t\t}\n\t\trecord.present = present;\n\t\trecord.mtimeMs = current?.mtimeMs ?? 0;\n\t\trecord.size = current?.size ?? 0;\n\t\trecord.digest = digest;\n\t\tif (!record.pendingChange || record.paused || record.settled) return;\n\t\trecord.pendingChange = false;\n\t\tif (record.settled) return;\n\t\tthis.#emit({\n\t\t\ttype: \"line\",\n\t\t\tid: record.id,\n\t\t\tdescription: record.description,\n\t\t\tline: `${record.event} ${record.path}`,\n\t\t});\n\t\tthis.#settleFile(record, \"watcher completed\");\n\t}\n\n\tregister(options: RegisterMonitorOptions): void {\n\t\tconst record: MonitorRecord = {\n\t\t\tid: options.id,\n\t\t\tdescription: options.description,\n\t\t\tstartedAtMs: Date.now(),\n\t\t\truntime: options.runtime,\n\t\t\tfilter: options.filter,\n\t\t\tlineBuffer: \"\",\n\t\t\tpaused: false,\n\t\t\tsettled: false,\n\t\t\tunsubscribeOutput: undefined,\n\t\t\tunsubscribeExit: undefined,\n\t\t};\n\t\tthis.#records.set(record.id, record);\n\t\tthis.#notifyChange();\n\n\t\t// Runtime output is already bounded. Read what was produced before monitor registration,\n\t\t// then subscribe synchronously so a fast watcher cannot lose its first line.\n\t\tthis.#consume(record, record.runtime.fullOutput());\n\t\trecord.unsubscribeOutput = record.runtime.onOutput((chunk) => this.#consume(record, chunk));\n\t\trecord.unsubscribeExit = record.runtime.session.onExit(() => this.#settle(record));\n\t\tif (record.runtime.exited) this.#settle(record);\n\t}\n\n\tpauseAll(): string[] {\n\t\tconst paused: string[] = [];\n\t\tfor (const record of [...this.#records.values(), ...this.#files.values()]) {\n\t\t\tif (record.paused) continue;\n\t\t\trecord.paused = true;\n\t\t\tpaused.push(record.id);\n\t\t}\n\t\tif (paused.length > 0) this.#notifyChange();\n\t\treturn paused;\n\t}\n\n\trearm(id: string): MonitorRearmResult {\n\t\tconst record = this.#records.get(id) ?? this.#files.get(id);\n\t\tif (!record) return \"not_found\";\n\t\tif (!record.paused) return \"not_paused\";\n\t\trecord.paused = false;\n\t\tthis.#notifyChange();\n\t\tif (\"pendingChange\" in record && record.pendingChange) void this.#checkFile(record.id);\n\t\treturn \"rearmed\";\n\t}\n\n\tdispose(): void {\n\t\tthis.#disposed = true;\n\t\tthis.#lifecycle += 1;\n\t\tfor (const record of this.#records.values()) this.#disposeRecord(record);\n\t\tfor (const record of this.#files.values()) this.#settleFile(record, \"watcher disposed\");\n\t\tfor (const pending of this.#pending) this.#finishPending(pending);\n\t\tthis.#records.clear();\n\t\tthis.#notifyChange();\n\t}\n\n\t#finishPending(pending: PendingFileRegistration, transfer = false): void {\n\t\tif (pending.settled) return;\n\t\tpending.settled = true;\n\t\tclearTimeout(pending.deadline);\n\t\tthis.#pending.delete(pending);\n\t\tif (transfer) pending.release = undefined;\n\t\telse {\n\t\t\tpending.abort.abort();\n\t\t\tpending.release?.();\n\t\t}\n\t}\n\n\tasync #registrationAwait<T>(\n\t\tpending: PendingFileRegistration,\n\t\toperation: Promise<T>,\n\t\tlateCleanup?: (value: T) => Promise<void>,\n\t): Promise<T> {\n\t\tif (pending.abort.signal.aborted) {\n\t\t\tvoid operation.then(\n\t\t\t\t(value) => lateCleanup?.(value),\n\t\t\t\t() => undefined,\n\t\t\t);\n\t\t\tthrow new Error(\"Cannot create file monitor: registration timed out or was disposed.\");\n\t\t}\n\t\tlet aborted = false;\n\t\tconst abort = new Promise<never>((_, reject) =>\n\t\t\tpending.abort.signal.addEventListener(\n\t\t\t\t\"abort\",\n\t\t\t\t() => {\n\t\t\t\t\taborted = true;\n\t\t\t\t\treject(new Error(\"Cannot create file monitor: registration timed out or was disposed.\"));\n\t\t\t\t},\n\t\t\t\t{ once: true },\n\t\t\t),\n\t\t);\n\t\tconst guarded = operation.then(\n\t\t\t(value) => {\n\t\t\t\tif (aborted) void lateCleanup?.(value);\n\t\t\t\treturn value;\n\t\t\t},\n\t\t\t(error) => {\n\t\t\t\tif (aborted) return undefined as T;\n\t\t\t\tthrow error;\n\t\t\t},\n\t\t);\n\t\treturn Promise.race([guarded, abort]);\n\t}\n\n\tasync #digestHandle(handle: FileHandle, signal?: AbortSignal): Promise<string> {\n\t\tconst SAMPLE_SIZE = 64 * 1024;\n\t\tif (signal?.aborted) throw new Error(\"file monitor registration cancelled\");\n\t\tconst metadata = await handle.stat();\n\t\tconst hash = createHash(\"sha256\");\n\t\tconst first = Buffer.alloc(Math.min(SAMPLE_SIZE, metadata.size));\n\t\tif (first.length > 0) {\n\t\t\tawait handle.read(first, 0, first.length, 0);\n\t\t\thash.update(first);\n\t\t}\n\t\tif (metadata.size > SAMPLE_SIZE) {\n\t\t\tconst middle = Buffer.alloc(SAMPLE_SIZE);\n\t\t\tawait handle.read(middle, 0, middle.length, Math.floor((metadata.size - middle.length) / 2));\n\t\t\thash.update(middle);\n\t\t\tconst last = Buffer.alloc(SAMPLE_SIZE);\n\t\t\tawait handle.read(last, 0, last.length, metadata.size - last.length);\n\t\t\thash.update(last);\n\t\t}\n\t\treturn `${metadata.size}:${hash.digest(\"hex\")}`;\n\t}\n\n\t#notifyChange(): void {\n\t\tthis.#onChange?.(this.snapshot());\n\t}\n\n\t#consume(record: MonitorRecord, chunk: string): void {\n\t\tif (record.settled || chunk.length === 0) return;\n\t\tlet remaining = record.lineBuffer + chunk;\n\t\tfor (;;) {\n\t\t\tconst newline = remaining.indexOf(\"\\n\");\n\t\t\tif (newline < 0) break;\n\t\t\tconst rawLine = remaining.slice(0, newline);\n\t\t\tremaining = remaining.slice(newline + 1);\n\t\t\tconst line = rawLine.endsWith(\"\\r\") ? rawLine.slice(0, -1) : rawLine;\n\t\t\tif (record.paused || (record.filter && !record.filter.test(line))) continue;\n\t\t\tthis.#emit({ type: \"line\", id: record.id, description: record.description, line });\n\t\t}\n\t\trecord.lineBuffer = remaining;\n\t}\n\n\t#settle(record: MonitorRecord): void {\n\t\tif (record.settled) return;\n\t\trecord.settled = true;\n\t\trecord.unsubscribeOutput?.();\n\t\trecord.unsubscribeExit?.();\n\t\tthis.#records.delete(record.id);\n\t\tthis.#notifyChange();\n\t\tconst status = describeExit(record.runtime) ?? \"exited\";\n\t\tconst code = record.runtime.exitResult?.exitCode;\n\t\tconst codeText = code === null || code === undefined ? \"\" : ` (exit code ${code})`;\n\t\tthis.#emit({\n\t\t\ttype: \"summary\",\n\t\t\tid: record.id,\n\t\t\tdescription: record.description,\n\t\t\tsummary: `watcher ${status}${codeText}`,\n\t\t});\n\t}\n\n\t#disposeRecord(record: MonitorRecord): void {\n\t\trecord.settled = true;\n\t\trecord.unsubscribeOutput?.();\n\t\trecord.unsubscribeExit?.();\n\t}\n}\n"]}
@@ -1 +1 @@
1
- {"version":3,"file":"session-bundle.d.ts","sourceRoot":"","sources":["../../../../../src/core/extensions/builtin/terminal/session-bundle.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,mBAAmB,EAAE,MAAM,2BAA2B,CAAC;AACrE,OAAO,EAAE,eAAe,EAAE,KAAK,sBAAsB,EAAE,MAAM,cAAc,CAAC;AAC5E,OAAO,EAAE,KAAK,YAAY,EAAE,eAAe,EAAE,KAAK,oBAAoB,EAAE,MAAM,uBAAuB,CAAC;AACtG,OAAO,KAAK,EAAE,sBAAsB,EAAE,MAAM,sBAAsB,CAAC;AAEnE,MAAM,WAAW,kBAAkB;IAClC,QAAQ,CAAC,cAAc,EAAE,CAAC,KAAK,EAAE,YAAY,KAAK,IAAI,CAAC;IACvD,QAAQ,CAAC,cAAc,EAAE,CAAC,QAAQ,EAAE,SAAS,oBAAoB,EAAE,KAAK,IAAI,CAAC;IAC7E,QAAQ,CAAC,iBAAiB,EAAE,CAAC,QAAQ,EAAE,SAAS,mBAAmB,EAAE,KAAK,IAAI,CAAC;IAC/E,QAAQ,CAAC,gBAAgB,EAAE,CAAC,EAAE,EAAE,MAAM,EAAE,OAAO,EAAE,sBAAsB,KAAK,IAAI,CAAC;CACjF;AAKD;;;;;;GAMG;AACH,qBAAa,qBAAqB;;IACjC,QAAQ,CAAC,OAAO,EAAE,eAAe,CAAC;IAClC,QAAQ,CAAC,QAAQ,EAAE,eAAe,CAAC;gBAOvB,OAAO,EAAE,sBAAsB;IAO3C,gGAAgG;IAChG,IAAI,CAAC,KAAK,EAAE,kBAAkB,GAAG,IAAI;IAarC,IAAI,IAAI,IAAI;IAIZ,kBAAkB,IAAI,SAAS,mBAAmB,EAAE;IAIpD,qBAAqB,CAAC,EAAE,EAAE,MAAM,EAAE,WAAW,EAAE,MAAM,EAAE,WAAW,SAAa,GAAG,IAAI;IAMtF,oBAAoB,CAAC,EAAE,EAAE,MAAM,EAAE,OAAO,EAAE,sBAAsB,GAAG,IAAI;IAWjE,QAAQ,IAAI,OAAO,CAAC,IAAI,CAAC;CAuB/B;AAID,kGAAkG;AAClG,wBAAsB,UAAU,CAAC,UAAU,EAAE,MAAM,EAAE,MAAM,EAAE,qBAAqB,GAAG,OAAO,CAAC,IAAI,CAAC,CAIjG;AAED,wBAAgB,iBAAiB,CAAC,UAAU,EAAE,MAAM,GAAG,qBAAqB,GAAG,SAAS,CAIvF;AAED,wBAAsB,oBAAoB,CAAC,UAAU,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC,CAI5E"}
1
+ {"version":3,"file":"session-bundle.d.ts","sourceRoot":"","sources":["../../../../../src/core/extensions/builtin/terminal/session-bundle.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,mBAAmB,EAAE,MAAM,2BAA2B,CAAC;AACrE,OAAO,EAAE,eAAe,EAAE,KAAK,sBAAsB,EAAE,MAAM,cAAc,CAAC;AAC5E,OAAO,EAAE,KAAK,YAAY,EAAE,eAAe,EAAE,KAAK,oBAAoB,EAAE,MAAM,uBAAuB,CAAC;AACtG,OAAO,KAAK,EAAE,sBAAsB,EAAE,MAAM,sBAAsB,CAAC;AAEnE,MAAM,WAAW,kBAAkB;IAClC,QAAQ,CAAC,cAAc,EAAE,CAAC,KAAK,EAAE,YAAY,KAAK,IAAI,CAAC;IACvD,QAAQ,CAAC,cAAc,EAAE,CAAC,QAAQ,EAAE,SAAS,oBAAoB,EAAE,KAAK,IAAI,CAAC;IAC7E,QAAQ,CAAC,iBAAiB,EAAE,CAAC,QAAQ,EAAE,SAAS,mBAAmB,EAAE,KAAK,IAAI,CAAC;IAC/E,QAAQ,CAAC,gBAAgB,EAAE,CAAC,EAAE,EAAE,MAAM,EAAE,OAAO,EAAE,sBAAsB,KAAK,IAAI,CAAC;CACjF;AAKD;;;;;;GAMG;AACH,qBAAa,qBAAqB;;IACjC,QAAQ,CAAC,OAAO,EAAE,eAAe,CAAC;IAClC,QAAQ,CAAC,QAAQ,EAAE,eAAe,CAAC;gBAOvB,OAAO,EAAE,sBAAsB;IAQ3C,gGAAgG;IAChG,IAAI,CAAC,KAAK,EAAE,kBAAkB,GAAG,IAAI;IAarC,IAAI,IAAI,IAAI;IAIZ,kBAAkB,IAAI,SAAS,mBAAmB,EAAE;IAIpD,qBAAqB,CAAC,EAAE,EAAE,MAAM,EAAE,WAAW,EAAE,MAAM,EAAE,WAAW,SAAa,GAAG,IAAI;IAMtF,oBAAoB,CAAC,EAAE,EAAE,MAAM,EAAE,OAAO,EAAE,sBAAsB,GAAG,IAAI;IAWjE,QAAQ,IAAI,OAAO,CAAC,IAAI,CAAC;CAuB/B;AAID,kGAAkG;AAClG,wBAAsB,UAAU,CAAC,UAAU,EAAE,MAAM,EAAE,MAAM,EAAE,qBAAqB,GAAG,OAAO,CAAC,IAAI,CAAC,CAIjG;AAED,wBAAgB,iBAAiB,CAAC,UAAU,EAAE,MAAM,GAAG,qBAAqB,GAAG,SAAS,CAIvF;AAED,wBAAsB,oBAAoB,CAAC,UAAU,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC,CAI5E"}
@@ -19,6 +19,7 @@ export class TerminalSessionBundle {
19
19
  this.manager = new TerminalManager(options);
20
20
  this.monitors = new MonitorRegistry((event) => this.#dispatchMonitorEvent(event), {
21
21
  onChange: (snapshot) => this.#sinks?.onMonitorState(snapshot),
22
+ reserve: () => this.manager.reserve(),
22
23
  });
23
24
  }
24
25
  /** Install live sinks, re-publish channel state, and flush everything buffered while parked. */