@code-yeongyu/senpi 2026.9.5 → 2026.9.7-2

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 (307) hide show
  1. package/CHANGELOG.md +104 -0
  2. package/dist/core/agent-session.d.ts +60 -5
  3. package/dist/core/agent-session.d.ts.map +1 -1
  4. package/dist/core/agent-session.js +262 -78
  5. package/dist/core/agent-session.js.map +1 -1
  6. package/dist/core/compaction/compaction.d.ts +6 -2
  7. package/dist/core/compaction/compaction.d.ts.map +1 -1
  8. package/dist/core/compaction/compaction.js +6 -1
  9. package/dist/core/compaction/compaction.js.map +1 -1
  10. package/dist/core/compaction/stuck-overflow.d.ts +11 -0
  11. package/dist/core/compaction/stuck-overflow.d.ts.map +1 -0
  12. package/dist/core/compaction/stuck-overflow.js +17 -0
  13. package/dist/core/compaction/stuck-overflow.js.map +1 -0
  14. package/dist/core/extensions/builtin/cache-keepalive/index.d.ts.map +1 -1
  15. package/dist/core/extensions/builtin/cache-keepalive/index.js +3 -21
  16. package/dist/core/extensions/builtin/cache-keepalive/index.js.map +1 -1
  17. package/dist/core/extensions/builtin/claude-sdk-oauth/prompt-bridge.d.ts.map +1 -1
  18. package/dist/core/extensions/builtin/claude-sdk-oauth/prompt-bridge.js +2 -0
  19. package/dist/core/extensions/builtin/claude-sdk-oauth/prompt-bridge.js.map +1 -1
  20. package/dist/core/extensions/builtin/claude-sdk-oauth/session-binding.d.ts.map +1 -1
  21. package/dist/core/extensions/builtin/claude-sdk-oauth/session-binding.js +36 -19
  22. package/dist/core/extensions/builtin/claude-sdk-oauth/session-binding.js.map +1 -1
  23. package/dist/core/extensions/builtin/claude-sdk-oauth/session-commit-boundary.d.ts.map +1 -1
  24. package/dist/core/extensions/builtin/claude-sdk-oauth/session-commit-boundary.js +21 -10
  25. package/dist/core/extensions/builtin/claude-sdk-oauth/session-commit-boundary.js.map +1 -1
  26. package/dist/core/extensions/builtin/claude-sdk-oauth/session-continuity.d.ts +4 -17
  27. package/dist/core/extensions/builtin/claude-sdk-oauth/session-continuity.d.ts.map +1 -1
  28. package/dist/core/extensions/builtin/claude-sdk-oauth/session-continuity.js +23 -33
  29. package/dist/core/extensions/builtin/claude-sdk-oauth/session-continuity.js.map +1 -1
  30. package/dist/core/extensions/builtin/claude-sdk-oauth/session-registry-pump.d.ts.map +1 -1
  31. package/dist/core/extensions/builtin/claude-sdk-oauth/session-registry-pump.js +15 -2
  32. package/dist/core/extensions/builtin/claude-sdk-oauth/session-registry-pump.js.map +1 -1
  33. package/dist/core/extensions/builtin/claude-sdk-oauth/session-stream.d.ts.map +1 -1
  34. package/dist/core/extensions/builtin/claude-sdk-oauth/session-stream.js +1 -0
  35. package/dist/core/extensions/builtin/claude-sdk-oauth/session-stream.js.map +1 -1
  36. package/dist/core/extensions/builtin/claude-sdk-oauth/session-sync.d.ts.map +1 -1
  37. package/dist/core/extensions/builtin/claude-sdk-oauth/session-sync.js +5 -3
  38. package/dist/core/extensions/builtin/claude-sdk-oauth/session-sync.js.map +1 -1
  39. package/dist/core/extensions/builtin/claude-sdk-oauth/session-turn-attempt.d.ts.map +1 -1
  40. package/dist/core/extensions/builtin/claude-sdk-oauth/session-turn-attempt.js +8 -3
  41. package/dist/core/extensions/builtin/claude-sdk-oauth/session-turn-attempt.js.map +1 -1
  42. package/dist/core/extensions/builtin/claude-sdk-oauth/session-turn-claim.d.ts +1 -1
  43. package/dist/core/extensions/builtin/claude-sdk-oauth/session-turn-claim.d.ts.map +1 -1
  44. package/dist/core/extensions/builtin/claude-sdk-oauth/session-turn-claim.js +2 -3
  45. package/dist/core/extensions/builtin/claude-sdk-oauth/session-turn-claim.js.map +1 -1
  46. package/dist/core/extensions/builtin/claude-sdk-oauth/tools.d.ts +6 -5
  47. package/dist/core/extensions/builtin/claude-sdk-oauth/tools.d.ts.map +1 -1
  48. package/dist/core/extensions/builtin/claude-sdk-oauth/tools.js +6 -5
  49. package/dist/core/extensions/builtin/claude-sdk-oauth/tools.js.map +1 -1
  50. package/dist/core/extensions/builtin/compaction/circuit-breaker.d.ts +8 -0
  51. package/dist/core/extensions/builtin/compaction/circuit-breaker.d.ts.map +1 -1
  52. package/dist/core/extensions/builtin/compaction/circuit-breaker.js +8 -0
  53. package/dist/core/extensions/builtin/compaction/circuit-breaker.js.map +1 -1
  54. package/dist/core/extensions/builtin/compaction/deterministic-fallback.d.ts +4 -0
  55. package/dist/core/extensions/builtin/compaction/deterministic-fallback.d.ts.map +1 -1
  56. package/dist/core/extensions/builtin/compaction/deterministic-fallback.js +40 -20
  57. package/dist/core/extensions/builtin/compaction/deterministic-fallback.js.map +1 -1
  58. package/dist/core/extensions/builtin/compaction/fallback-failed-turn-normalization.d.ts +18 -0
  59. package/dist/core/extensions/builtin/compaction/fallback-failed-turn-normalization.d.ts.map +1 -0
  60. package/dist/core/extensions/builtin/compaction/fallback-failed-turn-normalization.js +20 -0
  61. package/dist/core/extensions/builtin/compaction/fallback-failed-turn-normalization.js.map +1 -0
  62. package/dist/core/extensions/builtin/compaction/index.js +2 -2
  63. package/dist/core/extensions/builtin/compaction/index.js.map +1 -1
  64. package/dist/core/extensions/builtin/compaction/lane-policy.d.ts +12 -0
  65. package/dist/core/extensions/builtin/compaction/lane-policy.d.ts.map +1 -1
  66. package/dist/core/extensions/builtin/compaction/lane-policy.js +47 -18
  67. package/dist/core/extensions/builtin/compaction/lane-policy.js.map +1 -1
  68. package/dist/core/extensions/builtin/compaction/openai-remote-convert.d.ts.map +1 -1
  69. package/dist/core/extensions/builtin/compaction/openai-remote-convert.js +2 -0
  70. package/dist/core/extensions/builtin/compaction/openai-remote-convert.js.map +1 -1
  71. package/dist/core/extensions/builtin/compaction/retained-message-safety.d.ts.map +1 -1
  72. package/dist/core/extensions/builtin/compaction/retained-message-safety.js +14 -1
  73. package/dist/core/extensions/builtin/compaction/retained-message-safety.js.map +1 -1
  74. package/dist/core/extensions/builtin/goal/cache-warm-renderer.js +6 -4
  75. package/dist/core/extensions/builtin/goal/cache-warm-renderer.js.map +1 -1
  76. package/dist/core/extensions/builtin/goal/cache-warm.d.ts +19 -1
  77. package/dist/core/extensions/builtin/goal/cache-warm.d.ts.map +1 -1
  78. package/dist/core/extensions/builtin/goal/cache-warm.js +23 -10
  79. package/dist/core/extensions/builtin/goal/cache-warm.js.map +1 -1
  80. package/dist/core/extensions/builtin/goal/continuation-recovery.d.ts +1 -0
  81. package/dist/core/extensions/builtin/goal/continuation-recovery.d.ts.map +1 -1
  82. package/dist/core/extensions/builtin/goal/continuation-recovery.js +2 -0
  83. package/dist/core/extensions/builtin/goal/continuation-recovery.js.map +1 -1
  84. package/dist/core/extensions/builtin/goal/continuation.d.ts +3 -1
  85. package/dist/core/extensions/builtin/goal/continuation.d.ts.map +1 -1
  86. package/dist/core/extensions/builtin/goal/continuation.js +4 -0
  87. package/dist/core/extensions/builtin/goal/continuation.js.map +1 -1
  88. package/dist/core/extensions/builtin/goal/lifecycle-helpers.d.ts +3 -0
  89. package/dist/core/extensions/builtin/goal/lifecycle-helpers.d.ts.map +1 -1
  90. package/dist/core/extensions/builtin/goal/lifecycle-helpers.js +14 -3
  91. package/dist/core/extensions/builtin/goal/lifecycle-helpers.js.map +1 -1
  92. package/dist/core/extensions/builtin/goal/monitor-continuation.d.ts +1 -1
  93. package/dist/core/extensions/builtin/goal/monitor-continuation.d.ts.map +1 -1
  94. package/dist/core/extensions/builtin/goal/monitor-continuation.js +7 -3
  95. package/dist/core/extensions/builtin/goal/monitor-continuation.js.map +1 -1
  96. package/dist/core/extensions/builtin/hooks/plugin-manifest.js +2 -2
  97. package/dist/core/extensions/builtin/hooks/plugin-manifest.js.map +1 -1
  98. package/dist/core/extensions/builtin/hooks/safety.js +2 -2
  99. package/dist/core/extensions/builtin/hooks/safety.js.map +1 -1
  100. package/dist/core/extensions/builtin/permission-system/external-dir.d.ts.map +1 -1
  101. package/dist/core/extensions/builtin/permission-system/external-dir.js +5 -22
  102. package/dist/core/extensions/builtin/permission-system/external-dir.js.map +1 -1
  103. package/dist/core/extensions/builtin/permission-system/parsers.d.ts.map +1 -1
  104. package/dist/core/extensions/builtin/permission-system/parsers.js +5 -7
  105. package/dist/core/extensions/builtin/permission-system/parsers.js.map +1 -1
  106. package/dist/core/extensions/builtin/prompt-preset/gpt-6-astra.d.ts +2 -2
  107. package/dist/core/extensions/builtin/prompt-preset/gpt-6-astra.d.ts.map +1 -1
  108. package/dist/core/extensions/builtin/prompt-preset/gpt-6-astra.js +8 -2
  109. package/dist/core/extensions/builtin/prompt-preset/gpt-6-astra.js.map +1 -1
  110. package/dist/core/extensions/builtin/terminal/monitor-registry.d.ts.map +1 -1
  111. package/dist/core/extensions/builtin/terminal/monitor-registry.js +14 -7
  112. package/dist/core/extensions/builtin/terminal/monitor-registry.js.map +1 -1
  113. package/dist/core/messages.d.ts +7 -0
  114. package/dist/core/messages.d.ts.map +1 -1
  115. package/dist/core/messages.js +2 -0
  116. package/dist/core/messages.js.map +1 -1
  117. package/dist/core/package-identity.d.ts +12 -0
  118. package/dist/core/package-identity.d.ts.map +1 -0
  119. package/dist/core/package-identity.js +57 -0
  120. package/dist/core/package-identity.js.map +1 -0
  121. package/dist/core/resource-loader.d.ts +0 -2
  122. package/dist/core/resource-loader.d.ts.map +1 -1
  123. package/dist/core/resource-loader.js +8 -60
  124. package/dist/core/resource-loader.js.map +1 -1
  125. package/dist/core/retry-fallback/controller.d.ts.map +1 -1
  126. package/dist/core/retry-fallback/controller.js +5 -4
  127. package/dist/core/retry-fallback/controller.js.map +1 -1
  128. package/dist/core/sdk.d.ts.map +1 -1
  129. package/dist/core/sdk.js +5 -0
  130. package/dist/core/sdk.js.map +1 -1
  131. package/dist/core/session-manager.d.ts +12 -1
  132. package/dist/core/session-manager.d.ts.map +1 -1
  133. package/dist/core/session-manager.js +29 -3
  134. package/dist/core/session-manager.js.map +1 -1
  135. package/dist/core/settings-manager.d.ts +6 -2
  136. package/dist/core/settings-manager.d.ts.map +1 -1
  137. package/dist/core/settings-manager.js +4 -1
  138. package/dist/core/settings-manager.js.map +1 -1
  139. package/dist/core/tools/bounded-realpath.d.ts +28 -0
  140. package/dist/core/tools/bounded-realpath.d.ts.map +1 -0
  141. package/dist/core/tools/bounded-realpath.js +53 -0
  142. package/dist/core/tools/bounded-realpath.js.map +1 -0
  143. package/dist/core/tools/file-mutation-queue.d.ts.map +1 -1
  144. package/dist/core/tools/file-mutation-queue.js +15 -13
  145. package/dist/core/tools/file-mutation-queue.js.map +1 -1
  146. package/dist/core/tools/filesystem-policy.d.ts +7 -0
  147. package/dist/core/tools/filesystem-policy.d.ts.map +1 -1
  148. package/dist/core/tools/filesystem-policy.js +25 -15
  149. package/dist/core/tools/filesystem-policy.js.map +1 -1
  150. package/dist/core/trust-manager.d.ts.map +1 -1
  151. package/dist/core/trust-manager.js +13 -2
  152. package/dist/core/trust-manager.js.map +1 -1
  153. package/dist/modes/interactive/interactive-mode.d.ts.map +1 -1
  154. package/dist/modes/interactive/interactive-mode.js +5 -1
  155. package/dist/modes/interactive/interactive-mode.js.map +1 -1
  156. package/dist/modes/interactive/tips/catalog/memory-tips.d.ts.map +1 -1
  157. package/dist/modes/interactive/tips/catalog/memory-tips.js +6 -0
  158. package/dist/modes/interactive/tips/catalog/memory-tips.js.map +1 -1
  159. package/dist/modes/interactive/tips/catalog/subagent-tips.d.ts.map +1 -1
  160. package/dist/modes/interactive/tips/catalog/subagent-tips.js +6 -0
  161. package/dist/modes/interactive/tips/catalog/subagent-tips.js.map +1 -1
  162. package/dist/modes/rpc/host-lifecycle.d.ts.map +1 -1
  163. package/dist/modes/rpc/host-lifecycle.js +31 -5
  164. package/dist/modes/rpc/host-lifecycle.js.map +1 -1
  165. package/dist/modes/rpc/host-watchdog.d.ts +11 -0
  166. package/dist/modes/rpc/host-watchdog.d.ts.map +1 -1
  167. package/dist/modes/rpc/host-watchdog.js +16 -2
  168. package/dist/modes/rpc/host-watchdog.js.map +1 -1
  169. package/dist/modes/rpc/multi-session-host.d.ts +3 -7
  170. package/dist/modes/rpc/multi-session-host.d.ts.map +1 -1
  171. package/dist/modes/rpc/multi-session-host.js +57 -29
  172. package/dist/modes/rpc/multi-session-host.js.map +1 -1
  173. package/dist/modes/rpc/rpc-types.d.ts +1 -2
  174. package/dist/modes/rpc/rpc-types.d.ts.map +1 -1
  175. package/dist/modes/rpc/rpc-types.js +0 -1
  176. package/dist/modes/rpc/rpc-types.js.map +1 -1
  177. package/dist/modes/rpc/session-registry.d.ts +1 -4
  178. package/dist/modes/rpc/session-registry.d.ts.map +1 -1
  179. package/dist/modes/rpc/session-registry.js +0 -15
  180. package/dist/modes/rpc/session-registry.js.map +1 -1
  181. package/dist/modes/rpc/socket-ownership.d.ts +46 -0
  182. package/dist/modes/rpc/socket-ownership.d.ts.map +1 -0
  183. package/dist/modes/rpc/socket-ownership.js +178 -0
  184. package/dist/modes/rpc/socket-ownership.js.map +1 -0
  185. package/dist/utils/fs-watch.d.ts +17 -0
  186. package/dist/utils/fs-watch.d.ts.map +1 -1
  187. package/dist/utils/fs-watch.js +38 -10
  188. package/dist/utils/fs-watch.js.map +1 -1
  189. package/dist/utils/paths.d.ts +33 -0
  190. package/dist/utils/paths.d.ts.map +1 -1
  191. package/dist/utils/paths.js +109 -3
  192. package/dist/utils/paths.js.map +1 -1
  193. package/docs/compaction-guide.md +2 -2
  194. package/docs/compaction.md +2 -2
  195. package/docs/development.md +3 -2
  196. package/docs/providers.md +1 -1
  197. package/docs/rpc.md +6 -7
  198. package/node_modules/@code-yeongyu/senpi-codemode/CHANGELOG.md +74 -0
  199. package/node_modules/@code-yeongyu/senpi-codemode/README.md +34 -5
  200. package/node_modules/@code-yeongyu/senpi-codemode/package.json +4 -4
  201. package/node_modules/@code-yeongyu/senpi-codemode/src/bridge/protocol.ts +1 -0
  202. package/node_modules/@code-yeongyu/senpi-codemode/src/bridge/reserved.ts +2 -0
  203. package/node_modules/@code-yeongyu/senpi-codemode/src/extension/runtime-factory.ts +3 -0
  204. package/node_modules/@code-yeongyu/senpi-codemode/src/extension/session-manager.ts +7 -0
  205. package/node_modules/@code-yeongyu/senpi-codemode/src/kernels/AGENTS.md +7 -0
  206. package/node_modules/@code-yeongyu/senpi-codemode/src/kernels/jl/kernel.ts +6 -2
  207. package/node_modules/@code-yeongyu/senpi-codemode/src/kernels/js/context-manager.ts +73 -129
  208. package/node_modules/@code-yeongyu/senpi-codemode/src/kernels/js/inline-worker.ts +2 -2
  209. package/node_modules/@code-yeongyu/senpi-codemode/src/kernels/js/interrupt-bounds.ts +66 -0
  210. package/node_modules/@code-yeongyu/senpi-codemode/src/kernels/js/kernel-contract.ts +3 -0
  211. package/node_modules/@code-yeongyu/senpi-codemode/src/kernels/js/run-queue.ts +18 -3
  212. package/node_modules/@code-yeongyu/senpi-codemode/src/kernels/js/worker-core.js +59 -0
  213. package/node_modules/@code-yeongyu/senpi-codemode/src/kernels/js/worker-indirect-eval.js +10 -6
  214. package/node_modules/@code-yeongyu/senpi-codemode/src/kernels/js/worker-runtime.js +16 -0
  215. package/node_modules/@code-yeongyu/senpi-codemode/src/kernels/js/worker-shell-capture.d.ts +25 -0
  216. package/node_modules/@code-yeongyu/senpi-codemode/src/kernels/js/worker-shell-capture.js +70 -8
  217. package/node_modules/@code-yeongyu/senpi-codemode/src/kernels/js/worker-slot.ts +106 -0
  218. package/node_modules/@code-yeongyu/senpi-codemode/src/kernels/js/worker-startup.ts +70 -0
  219. package/node_modules/@code-yeongyu/senpi-codemode/src/kernels/py/kernel-contract.ts +3 -0
  220. package/node_modules/@code-yeongyu/senpi-codemode/src/kernels/py/transport.ts +11 -3
  221. package/node_modules/@code-yeongyu/senpi-codemode/src/kernels/rb/kernel.ts +6 -2
  222. package/node_modules/@code-yeongyu/senpi-codemode/src/kernels/session-env.ts +56 -0
  223. package/node_modules/@code-yeongyu/senpi-codemode/src/kernels/shared/runtime-asset.ts +40 -14
  224. package/node_modules/@code-yeongyu/senpi-codemode/src/kernels/shared/subprocess-contract.ts +3 -0
  225. package/node_modules/@code-yeongyu/senpi-codemode/src/kernels/shared/subprocess-kernel.ts +9 -1
  226. package/node_modules/@code-yeongyu/senpi-codemode/src/output/output-meta.ts +26 -2
  227. package/node_modules/@code-yeongyu/senpi-codemode/src/prompt/eval-prompt.ts +10 -5
  228. package/node_modules/@code-yeongyu/senpi-codemode/src/tool/cell-execution.ts +9 -11
  229. package/node_modules/@code-yeongyu/senpi-codemode/src/tool/detached-cell-contract.ts +45 -0
  230. package/node_modules/@code-yeongyu/senpi-codemode/src/tool/detached-cell-manager.ts +43 -71
  231. package/node_modules/@code-yeongyu/senpi-codemode/src/tool/detached-cell-notification.ts +4 -5
  232. package/node_modules/@code-yeongyu/senpi-codemode/src/tool/detached-cell-snapshot.ts +2 -0
  233. package/node_modules/@code-yeongyu/senpi-codemode/src/tool/detached-cell-status.ts +30 -0
  234. package/node_modules/@code-yeongyu/senpi-codemode/src/tool/detached-eval-result.ts +1 -0
  235. package/node_modules/@code-yeongyu/senpi-codemode/src/tool/detached-notification-queue.ts +2 -2
  236. package/node_modules/@code-yeongyu/senpi-codemode/src/tool/image.ts +12 -3
  237. package/node_modules/@code-yeongyu/senpi-codemode/src/tool/interrupt-note.ts +29 -15
  238. package/node_modules/@code-yeongyu/senpi-codemode/src/tool/types.ts +2 -0
  239. package/node_modules/@earendil-works/pi-agent-core/dist/agent.d.ts.map +1 -1
  240. package/node_modules/@earendil-works/pi-agent-core/dist/agent.js +3 -1
  241. package/node_modules/@earendil-works/pi-agent-core/dist/agent.js.map +1 -1
  242. package/node_modules/@earendil-works/pi-agent-core/dist/harness/agent-harness.d.ts.map +1 -1
  243. package/node_modules/@earendil-works/pi-agent-core/dist/harness/agent-harness.js +17 -0
  244. package/node_modules/@earendil-works/pi-agent-core/dist/harness/agent-harness.js.map +1 -1
  245. package/node_modules/@earendil-works/pi-agent-core/dist/harness/compaction/branch-summarization.d.ts.map +1 -1
  246. package/node_modules/@earendil-works/pi-agent-core/dist/harness/compaction/branch-summarization.js +1 -0
  247. package/node_modules/@earendil-works/pi-agent-core/dist/harness/compaction/branch-summarization.js.map +1 -1
  248. package/node_modules/@earendil-works/pi-agent-core/dist/harness/compaction/compaction.d.ts.map +1 -1
  249. package/node_modules/@earendil-works/pi-agent-core/dist/harness/compaction/compaction.js +1 -0
  250. package/node_modules/@earendil-works/pi-agent-core/dist/harness/compaction/compaction.js.map +1 -1
  251. package/node_modules/@earendil-works/pi-agent-core/dist/harness/reducer.d.ts.map +1 -1
  252. package/node_modules/@earendil-works/pi-agent-core/dist/harness/reducer.js +6 -0
  253. package/node_modules/@earendil-works/pi-agent-core/dist/harness/reducer.js.map +1 -1
  254. package/node_modules/@earendil-works/pi-agent-core/dist/harness/session/context.d.ts +2 -1
  255. package/node_modules/@earendil-works/pi-agent-core/dist/harness/session/context.d.ts.map +1 -1
  256. package/node_modules/@earendil-works/pi-agent-core/dist/harness/session/context.js +5 -1
  257. package/node_modules/@earendil-works/pi-agent-core/dist/harness/session/context.js.map +1 -1
  258. package/node_modules/@earendil-works/pi-agent-core/dist/harness/session/jsonl/codec.d.ts.map +1 -1
  259. package/node_modules/@earendil-works/pi-agent-core/dist/harness/session/jsonl/codec.js +1 -0
  260. package/node_modules/@earendil-works/pi-agent-core/dist/harness/session/jsonl/codec.js.map +1 -1
  261. package/node_modules/@earendil-works/pi-agent-core/dist/harness/session/types.d.ts +7 -1
  262. package/node_modules/@earendil-works/pi-agent-core/dist/harness/session/types.d.ts.map +1 -1
  263. package/node_modules/@earendil-works/pi-agent-core/dist/harness/session/types.js.map +1 -1
  264. package/node_modules/@earendil-works/pi-agent-core/dist/types.d.ts +2 -0
  265. package/node_modules/@earendil-works/pi-agent-core/dist/types.d.ts.map +1 -1
  266. package/node_modules/@earendil-works/pi-agent-core/dist/types.js.map +1 -1
  267. package/node_modules/@earendil-works/pi-agent-core/package.json +3 -3
  268. package/node_modules/@earendil-works/pi-ai/dist/api/mistral-conversations.js +2 -0
  269. package/node_modules/@earendil-works/pi-ai/dist/api/mistral-conversations.js.map +1 -1
  270. package/node_modules/@earendil-works/pi-ai/dist/api/openai-responses-shared.d.ts.map +1 -1
  271. package/node_modules/@earendil-works/pi-ai/dist/api/openai-responses-shared.js +15 -0
  272. package/node_modules/@earendil-works/pi-ai/dist/api/openai-responses-shared.js.map +1 -1
  273. package/node_modules/@earendil-works/pi-ai/dist/image-models.generated.js +4 -4
  274. package/node_modules/@earendil-works/pi-ai/dist/image-models.generated.js.map +1 -1
  275. package/node_modules/@earendil-works/pi-ai/dist/providers/data/.manifest.json +1 -1
  276. package/node_modules/@earendil-works/pi-ai/dist/providers/data/amazon-bedrock.json +1 -1
  277. package/node_modules/@earendil-works/pi-ai/dist/providers/data/azure-openai-responses.json +1 -1
  278. package/node_modules/@earendil-works/pi-ai/dist/providers/data/cerebras.json +1 -1
  279. package/node_modules/@earendil-works/pi-ai/dist/providers/data/cloudflare-ai-gateway.json +1 -1
  280. package/node_modules/@earendil-works/pi-ai/dist/providers/data/github-copilot.json +1 -1
  281. package/node_modules/@earendil-works/pi-ai/dist/providers/data/moonshotai-cn.json +1 -1
  282. package/node_modules/@earendil-works/pi-ai/dist/providers/data/moonshotai.json +1 -1
  283. package/node_modules/@earendil-works/pi-ai/dist/providers/data/openai-codex.json +1 -1
  284. package/node_modules/@earendil-works/pi-ai/dist/providers/data/openai.json +1 -1
  285. package/node_modules/@earendil-works/pi-ai/dist/providers/data/opencode-go.json +1 -1
  286. package/node_modules/@earendil-works/pi-ai/dist/providers/data/opencode.json +1 -1
  287. package/node_modules/@earendil-works/pi-ai/dist/providers/data/opengateway.json +1 -1
  288. package/node_modules/@earendil-works/pi-ai/dist/providers/data/openrouter.json +1 -1
  289. package/node_modules/@earendil-works/pi-ai/dist/providers/data/vercel-ai-gateway.json +1 -1
  290. package/node_modules/@earendil-works/pi-ai/dist/providers/faux.d.ts.map +1 -1
  291. package/node_modules/@earendil-works/pi-ai/dist/providers/faux.js +2 -0
  292. package/node_modules/@earendil-works/pi-ai/dist/providers/faux.js.map +1 -1
  293. package/node_modules/@earendil-works/pi-ai/dist/types.d.ts +8 -1
  294. package/node_modules/@earendil-works/pi-ai/dist/types.d.ts.map +1 -1
  295. package/node_modules/@earendil-works/pi-ai/dist/types.js.map +1 -1
  296. package/node_modules/@earendil-works/pi-ai/dist/utils/estimate.d.ts.map +1 -1
  297. package/node_modules/@earendil-works/pi-ai/dist/utils/estimate.js +2 -0
  298. package/node_modules/@earendil-works/pi-ai/dist/utils/estimate.js.map +1 -1
  299. package/node_modules/@earendil-works/pi-ai/dist/utils/overflow.d.ts +1 -1
  300. package/node_modules/@earendil-works/pi-ai/dist/utils/overflow.d.ts.map +1 -1
  301. package/node_modules/@earendil-works/pi-ai/dist/utils/overflow.js +14 -3
  302. package/node_modules/@earendil-works/pi-ai/dist/utils/overflow.js.map +1 -1
  303. package/node_modules/@earendil-works/pi-ai/package.json +2 -2
  304. package/node_modules/@earendil-works/pi-pty/package.json +1 -1
  305. package/node_modules/@earendil-works/pi-telemetry/package.json +1 -1
  306. package/node_modules/@earendil-works/pi-tui/package.json +1 -1
  307. package/package.json +7 -7
@@ -1 +1 @@
1
- {"version":3,"file":"fs-watch.d.ts","sourceRoot":"","sources":["../../src/utils/fs-watch.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,KAAK,SAAS,EAAgB,KAAK,aAAa,EAAE,KAAK,YAAY,EAAS,MAAM,SAAS,CAAC;AAErG,eAAO,MAAM,uBAAuB,OAAO,CAAC;AAE5C,wBAAgB,YAAY,CAAC,OAAO,EAAE,SAAS,GAAG,IAAI,GAAG,SAAS,GAAG,IAAI,CAUxE;AAED,wBAAgB,qBAAqB,CACpC,IAAI,EAAE,MAAM,EACZ,QAAQ,EAAE,aAAa,CAAC,MAAM,CAAC,EAC/B,OAAO,EAAE,MAAM,IAAI,EACnB,OAAO,CAAC,EAAE,YAAY,GACpB,SAAS,GAAG,IAAI,CAiBlB"}
1
+ {"version":3,"file":"fs-watch.d.ts","sourceRoot":"","sources":["../../src/utils/fs-watch.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,KAAK,SAAS,EAAgB,KAAK,aAAa,EAAE,KAAK,YAAY,EAAS,MAAM,SAAS,CAAC;AAGrG,eAAO,MAAM,uBAAuB,OAAO,CAAC;AAE5C,wBAAgB,YAAY,CAAC,OAAO,EAAE,SAAS,GAAG,IAAI,GAAG,SAAS,GAAG,IAAI,CAUxE;AAED;;;;;;GAMG;AACH,wBAAgB,kBAAkB,CAAC,IAAI,EAAE,MAAM,GAAG,MAAM,CAQvD;AAED;;;;;;;GAOG;AACH,wBAAsB,sBAAsB,CAAC,SAAS,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC,CAQ7E;AAED,wBAAgB,qBAAqB,CACpC,IAAI,EAAE,MAAM,EACZ,QAAQ,EAAE,aAAa,CAAC,MAAM,CAAC,EAC/B,OAAO,EAAE,MAAM,IAAI,EACnB,OAAO,CAAC,EAAE,YAAY,GACpB,SAAS,GAAG,IAAI,CASlB"}
@@ -1,4 +1,5 @@
1
1
  import { realpathSync, watch } from "node:fs";
2
+ import { opendir } from "node:fs/promises";
2
3
  export const FS_WATCH_RETRY_DELAY_MS = 5000;
3
4
  export function closeWatcher(watcher) {
4
5
  if (!watcher) {
@@ -11,18 +12,45 @@ export function closeWatcher(watcher) {
11
12
  // Ignore watcher close errors
12
13
  }
13
14
  }
15
+ /**
16
+ * The path to hand to `fs.watch`. On Windows libuv aborts the whole process when a directory
17
+ * watch is created through a non-canonical (8.3 short-name) path (issue #1229), so the canonical
18
+ * form is looked up there. Elsewhere the caller's path is used as-is: POSIX realpath opens every
19
+ * directory it resolves, which blocks forever on a wedged autofs trigger, and fs.watch does not
20
+ * need the canonical form on those platforms.
21
+ */
22
+ export function canonicalWatchPath(path) {
23
+ if (process.platform !== "win32")
24
+ return path;
25
+ try {
26
+ return realpathSync.native(path);
27
+ }
28
+ catch {
29
+ // Keep the raw path when it does not exist yet.
30
+ return path;
31
+ }
32
+ }
33
+ /**
34
+ * Prove that a directory can be opened before a synchronous `fs.watch` touches it. The watcher
35
+ * opens the directory on the calling thread; on a wedged mount (a macOS autofs trigger whose
36
+ * automounter never answers) that open never returns and would block the host main thread. This
37
+ * probe performs the same open on the async pool, so a caller can bound it with a deadline. It
38
+ * rejects with the open error (for example EACCES on an execute-only directory, which fs.watch
39
+ * would refuse as well).
40
+ */
41
+ export async function probeDirectoryOpenable(directory) {
42
+ const handle = await opendir(directory);
43
+ try {
44
+ // Bun opens the directory lazily on the first read; Node opens it in opendir itself.
45
+ await handle.read();
46
+ }
47
+ finally {
48
+ await handle.close();
49
+ }
50
+ }
14
51
  export function watchWithErrorHandler(path, listener, onError, options) {
15
52
  try {
16
- let watchPath = path;
17
- if (process.platform === "win32") {
18
- try {
19
- watchPath = realpathSync.native(path);
20
- }
21
- catch {
22
- // Keep the raw path when it does not exist yet.
23
- }
24
- }
25
- const watcher = watch(watchPath, { ...options, encoding: "utf8" }, listener);
53
+ const watcher = watch(canonicalWatchPath(path), { ...options, encoding: "utf8" }, listener);
26
54
  watcher.on("error", onError);
27
55
  return watcher;
28
56
  }
@@ -1 +1 @@
1
- {"version":3,"file":"fs-watch.js","sourceRoot":"","sources":["../../src/utils/fs-watch.ts"],"names":[],"mappings":"AAAA,OAAO,EAAkB,YAAY,EAAyC,KAAK,EAAE,MAAM,SAAS,CAAC;AAErG,MAAM,CAAC,MAAM,uBAAuB,GAAG,IAAI,CAAC;AAE5C,MAAM,UAAU,YAAY,CAAC,OAAqC;IACjE,IAAI,CAAC,OAAO,EAAE,CAAC;QACd,OAAO;IACR,CAAC;IAED,IAAI,CAAC;QACJ,OAAO,CAAC,KAAK,EAAE,CAAC;IACjB,CAAC;IAAC,MAAM,CAAC;QACR,8BAA8B;IAC/B,CAAC;AACF,CAAC;AAED,MAAM,UAAU,qBAAqB,CACpC,IAAY,EACZ,QAA+B,EAC/B,OAAmB,EACnB,OAAsB;IAEtB,IAAI,CAAC;QACJ,IAAI,SAAS,GAAG,IAAI,CAAC;QACrB,IAAI,OAAO,CAAC,QAAQ,KAAK,OAAO,EAAE,CAAC;YAClC,IAAI,CAAC;gBACJ,SAAS,GAAG,YAAY,CAAC,MAAM,CAAC,IAAI,CAAC,CAAC;YACvC,CAAC;YAAC,MAAM,CAAC;gBACR,gDAAgD;YACjD,CAAC;QACF,CAAC;QACD,MAAM,OAAO,GAAG,KAAK,CAAC,SAAS,EAAE,EAAE,GAAG,OAAO,EAAE,QAAQ,EAAE,MAAM,EAAE,EAAE,QAAQ,CAAC,CAAC;QAC7E,OAAO,CAAC,EAAE,CAAC,OAAO,EAAE,OAAO,CAAC,CAAC;QAC7B,OAAO,OAAO,CAAC;IAChB,CAAC;IAAC,MAAM,CAAC;QACR,OAAO,EAAE,CAAC;QACV,OAAO,IAAI,CAAC;IACb,CAAC;AACF,CAAC","sourcesContent":["import { type FSWatcher, realpathSync, type WatchListener, type WatchOptions, watch } from \"node:fs\";\n\nexport const FS_WATCH_RETRY_DELAY_MS = 5000;\n\nexport function closeWatcher(watcher: FSWatcher | null | undefined): void {\n\tif (!watcher) {\n\t\treturn;\n\t}\n\n\ttry {\n\t\twatcher.close();\n\t} catch {\n\t\t// Ignore watcher close errors\n\t}\n}\n\nexport function watchWithErrorHandler(\n\tpath: string,\n\tlistener: WatchListener<string>,\n\tonError: () => void,\n\toptions?: WatchOptions,\n): FSWatcher | null {\n\ttry {\n\t\tlet watchPath = path;\n\t\tif (process.platform === \"win32\") {\n\t\t\ttry {\n\t\t\t\twatchPath = realpathSync.native(path);\n\t\t\t} catch {\n\t\t\t\t// Keep the raw path when it does not exist yet.\n\t\t\t}\n\t\t}\n\t\tconst watcher = watch(watchPath, { ...options, encoding: \"utf8\" }, listener);\n\t\twatcher.on(\"error\", onError);\n\t\treturn watcher;\n\t} catch {\n\t\tonError();\n\t\treturn null;\n\t}\n}\n"]}
1
+ {"version":3,"file":"fs-watch.js","sourceRoot":"","sources":["../../src/utils/fs-watch.ts"],"names":[],"mappings":"AAAA,OAAO,EAAkB,YAAY,EAAyC,KAAK,EAAE,MAAM,SAAS,CAAC;AACrG,OAAO,EAAE,OAAO,EAAE,MAAM,kBAAkB,CAAC;AAE3C,MAAM,CAAC,MAAM,uBAAuB,GAAG,IAAI,CAAC;AAE5C,MAAM,UAAU,YAAY,CAAC,OAAqC;IACjE,IAAI,CAAC,OAAO,EAAE,CAAC;QACd,OAAO;IACR,CAAC;IAED,IAAI,CAAC;QACJ,OAAO,CAAC,KAAK,EAAE,CAAC;IACjB,CAAC;IAAC,MAAM,CAAC;QACR,8BAA8B;IAC/B,CAAC;AACF,CAAC;AAED;;;;;;GAMG;AACH,MAAM,UAAU,kBAAkB,CAAC,IAAY;IAC9C,IAAI,OAAO,CAAC,QAAQ,KAAK,OAAO;QAAE,OAAO,IAAI,CAAC;IAC9C,IAAI,CAAC;QACJ,OAAO,YAAY,CAAC,MAAM,CAAC,IAAI,CAAC,CAAC;IAClC,CAAC;IAAC,MAAM,CAAC;QACR,gDAAgD;QAChD,OAAO,IAAI,CAAC;IACb,CAAC;AACF,CAAC;AAED;;;;;;;GAOG;AACH,MAAM,CAAC,KAAK,UAAU,sBAAsB,CAAC,SAAiB;IAC7D,MAAM,MAAM,GAAG,MAAM,OAAO,CAAC,SAAS,CAAC,CAAC;IACxC,IAAI,CAAC;QACJ,qFAAqF;QACrF,MAAM,MAAM,CAAC,IAAI,EAAE,CAAC;IACrB,CAAC;YAAS,CAAC;QACV,MAAM,MAAM,CAAC,KAAK,EAAE,CAAC;IACtB,CAAC;AACF,CAAC;AAED,MAAM,UAAU,qBAAqB,CACpC,IAAY,EACZ,QAA+B,EAC/B,OAAmB,EACnB,OAAsB;IAEtB,IAAI,CAAC;QACJ,MAAM,OAAO,GAAG,KAAK,CAAC,kBAAkB,CAAC,IAAI,CAAC,EAAE,EAAE,GAAG,OAAO,EAAE,QAAQ,EAAE,MAAM,EAAE,EAAE,QAAQ,CAAC,CAAC;QAC5F,OAAO,CAAC,EAAE,CAAC,OAAO,EAAE,OAAO,CAAC,CAAC;QAC7B,OAAO,OAAO,CAAC;IAChB,CAAC;IAAC,MAAM,CAAC;QACR,OAAO,EAAE,CAAC;QACV,OAAO,IAAI,CAAC;IACb,CAAC;AACF,CAAC","sourcesContent":["import { type FSWatcher, realpathSync, type WatchListener, type WatchOptions, watch } from \"node:fs\";\nimport { opendir } from \"node:fs/promises\";\n\nexport const FS_WATCH_RETRY_DELAY_MS = 5000;\n\nexport function closeWatcher(watcher: FSWatcher | null | undefined): void {\n\tif (!watcher) {\n\t\treturn;\n\t}\n\n\ttry {\n\t\twatcher.close();\n\t} catch {\n\t\t// Ignore watcher close errors\n\t}\n}\n\n/**\n * The path to hand to `fs.watch`. On Windows libuv aborts the whole process when a directory\n * watch is created through a non-canonical (8.3 short-name) path (issue #1229), so the canonical\n * form is looked up there. Elsewhere the caller's path is used as-is: POSIX realpath opens every\n * directory it resolves, which blocks forever on a wedged autofs trigger, and fs.watch does not\n * need the canonical form on those platforms.\n */\nexport function canonicalWatchPath(path: string): string {\n\tif (process.platform !== \"win32\") return path;\n\ttry {\n\t\treturn realpathSync.native(path);\n\t} catch {\n\t\t// Keep the raw path when it does not exist yet.\n\t\treturn path;\n\t}\n}\n\n/**\n * Prove that a directory can be opened before a synchronous `fs.watch` touches it. The watcher\n * opens the directory on the calling thread; on a wedged mount (a macOS autofs trigger whose\n * automounter never answers) that open never returns and would block the host main thread. This\n * probe performs the same open on the async pool, so a caller can bound it with a deadline. It\n * rejects with the open error (for example EACCES on an execute-only directory, which fs.watch\n * would refuse as well).\n */\nexport async function probeDirectoryOpenable(directory: string): Promise<void> {\n\tconst handle = await opendir(directory);\n\ttry {\n\t\t// Bun opens the directory lazily on the first read; Node opens it in opendir itself.\n\t\tawait handle.read();\n\t} finally {\n\t\tawait handle.close();\n\t}\n}\n\nexport function watchWithErrorHandler(\n\tpath: string,\n\tlistener: WatchListener<string>,\n\tonError: () => void,\n\toptions?: WatchOptions,\n): FSWatcher | null {\n\ttry {\n\t\tconst watcher = watch(canonicalWatchPath(path), { ...options, encoding: \"utf8\" }, listener);\n\t\twatcher.on(\"error\", onError);\n\t\treturn watcher;\n\t} catch {\n\t\tonError();\n\t\treturn null;\n\t}\n}\n"]}
@@ -17,6 +17,39 @@ export interface PathInputOptions {
17
17
  * entries.
18
18
  */
19
19
  export declare function canonicalizePath(path: string): string;
20
+ /**
21
+ * {@link canonicalizePath} that refuses to guess.
22
+ *
23
+ * The convenience form hands back its input when resolution fails, which is what most callers
24
+ * want: they compare two canonical spellings and an unresolvable path simply compares unequal.
25
+ * A security decision cannot use that, because the raw input is indistinguishable from a
26
+ * canonical answer - so a path that could not be resolved would be keyed, compared, or trusted
27
+ * under a spelling the filesystem never confirmed. This form throws instead, leaving the caller
28
+ * to decide what an unresolvable path means.
29
+ */
30
+ export declare function canonicalizePathStrict(path: string): string;
31
+ /**
32
+ * Resolve symlinks without ever open(2)-ing a component, tolerating every error.
33
+ *
34
+ * Bun's `fs.realpath*` opens every directory it resolves, so an autofs trigger such as macOS
35
+ * `/home` blocks the calling thread (on the host main thread that freezes the TUI) and an
36
+ * execute-only directory fails with EACCES; lstat needs only search permission and never mounts
37
+ * anything. Components from the first unresolvable one onward are kept verbatim, so a file that
38
+ * does not exist yet still lands where its symlinked parent points.
39
+ *
40
+ * Never throws, so the resolved path may be incomplete. Use it where an approximate answer is
41
+ * better than a blocked thread — the external-path classifier and the monitor parent identity —
42
+ * and use {@link realpathWithoutOpenStrict} where an incomplete answer must not be trusted.
43
+ */
44
+ export declare function realpathWithoutOpen(inputPath: string): string;
45
+ /**
46
+ * {@link realpathWithoutOpen} that refuses to answer with a guess: a missing component is still
47
+ * tolerated (its name is appended verbatim, which is what a not-yet-created target needs), but
48
+ * EACCES, EIO, ELOOP and hop exhaustion throw instead of returning an unresolved path. Callers
49
+ * that feed a canonical path into a containment or identity decision need this contract, because
50
+ * an unresolved path can name a different file than the one the I/O will reach.
51
+ */
52
+ export declare function realpathWithoutOpenStrict(inputPath: string): string;
20
53
  export declare function getFileRevision(path: string): string | undefined;
21
54
  /**
22
55
  * Returns true if the value is NOT a package source (npm:, git:, etc.)
@@ -1 +1 @@
1
- {"version":3,"file":"paths.d.ts","sourceRoot":"","sources":["../../src/utils/paths.ts"],"names":[],"mappings":"AAQA,MAAM,WAAW,gBAAgB;IAChC,6DAA6D;IAC7D,IAAI,CAAC,EAAE,OAAO,CAAC;IACf,gEAAgE;IAChE,WAAW,CAAC,EAAE,OAAO,CAAC;IACtB,yEAAyE;IACzE,OAAO,CAAC,EAAE,MAAM,CAAC;IACjB,qDAAqD;IACrD,aAAa,CAAC,EAAE,OAAO,CAAC;IACxB,0DAA0D;IAC1D,sBAAsB,CAAC,EAAE,OAAO,CAAC;CACjC;AAED;;;;;GAKG;AACH,wBAAgB,gBAAgB,CAAC,IAAI,EAAE,MAAM,GAAG,MAAM,CAMrD;AAED,wBAAgB,eAAe,CAAC,IAAI,EAAE,MAAM,GAAG,MAAM,GAAG,SAAS,CAOhE;AAED;;;;GAIG;AACH,wBAAgB,WAAW,CAAC,KAAK,EAAE,MAAM,GAAG,OAAO,CAclD;AAED,gGAAgG;AAChG,wBAAgB,yBAAyB,CAAC,QAAQ,EAAE,MAAM,GAAG,MAAM,CAMlE;AAED,wBAAgB,aAAa,CAAC,KAAK,EAAE,MAAM,EAAE,OAAO,GAAE,gBAAqB,GAAG,MAAM,CAyBnF;AAED,wBAAgB,WAAW,CAAC,KAAK,EAAE,MAAM,EAAE,OAAO,GAAE,MAAsB,EAAE,OAAO,GAAE,gBAAqB,GAAG,MAAM,CAIlH;AAED,wBAAgB,kBAAkB,CAAC,QAAQ,EAAE,MAAM,EAAE,GAAG,EAAE,MAAM,GAAG,MAAM,GAAG,SAAS,CASpF;AAED,wBAAgB,iCAAiC,CAAC,QAAQ,EAAE,MAAM,EAAE,GAAG,EAAE,MAAM,GAAG,MAAM,CAGvF;AAED,wBAAgB,WAAW,CAAC,IAAI,EAAE,MAAM,GAAG,MAAM,CAKhD;AAED,wBAAgB,0BAA0B,CAAC,IAAI,EAAE,MAAM,GAAG,IAAI,CAe7D"}
1
+ {"version":3,"file":"paths.d.ts","sourceRoot":"","sources":["../../src/utils/paths.ts"],"names":[],"mappings":"AAQA,MAAM,WAAW,gBAAgB;IAChC,6DAA6D;IAC7D,IAAI,CAAC,EAAE,OAAO,CAAC;IACf,gEAAgE;IAChE,WAAW,CAAC,EAAE,OAAO,CAAC;IACtB,yEAAyE;IACzE,OAAO,CAAC,EAAE,MAAM,CAAC;IACjB,qDAAqD;IACrD,aAAa,CAAC,EAAE,OAAO,CAAC;IACxB,0DAA0D;IAC1D,sBAAsB,CAAC,EAAE,OAAO,CAAC;CACjC;AAED;;;;;GAKG;AACH,wBAAgB,gBAAgB,CAAC,IAAI,EAAE,MAAM,GAAG,MAAM,CAMrD;AAED;;;;;;;;;GASG;AACH,wBAAgB,sBAAsB,CAAC,IAAI,EAAE,MAAM,GAAG,MAAM,CAE3D;AAoED;;;;;;;;;;;;GAYG;AACH,wBAAgB,mBAAmB,CAAC,SAAS,EAAE,MAAM,GAAG,MAAM,CAE7D;AAED;;;;;;GAMG;AACH,wBAAgB,yBAAyB,CAAC,SAAS,EAAE,MAAM,GAAG,MAAM,CASnE;AAED,wBAAgB,eAAe,CAAC,IAAI,EAAE,MAAM,GAAG,MAAM,GAAG,SAAS,CAOhE;AAED;;;;GAIG;AACH,wBAAgB,WAAW,CAAC,KAAK,EAAE,MAAM,GAAG,OAAO,CAclD;AAED,gGAAgG;AAChG,wBAAgB,yBAAyB,CAAC,QAAQ,EAAE,MAAM,GAAG,MAAM,CAMlE;AAED,wBAAgB,aAAa,CAAC,KAAK,EAAE,MAAM,EAAE,OAAO,GAAE,gBAAqB,GAAG,MAAM,CAyBnF;AAED,wBAAgB,WAAW,CAAC,KAAK,EAAE,MAAM,EAAE,OAAO,GAAE,MAAsB,EAAE,OAAO,GAAE,gBAAqB,GAAG,MAAM,CAIlH;AAED,wBAAgB,kBAAkB,CAAC,QAAQ,EAAE,MAAM,EAAE,GAAG,EAAE,MAAM,GAAG,MAAM,GAAG,SAAS,CASpF;AAED,wBAAgB,iCAAiC,CAAC,QAAQ,EAAE,MAAM,EAAE,GAAG,EAAE,MAAM,GAAG,MAAM,CAGvF;AAED,wBAAgB,WAAW,CAAC,IAAI,EAAE,MAAM,GAAG,MAAM,CAKhD;AAED,wBAAgB,0BAA0B,CAAC,IAAI,EAAE,MAAM,GAAG,IAAI,CAe7D"}
@@ -1,6 +1,6 @@
1
- import { realpathSync, statSync } from "node:fs";
1
+ import { lstatSync, readlinkSync, realpathSync, statSync } from "node:fs";
2
2
  import { homedir } from "node:os";
3
- import { isAbsolute, join, resolve as nodeResolvePath, relative, sep } from "node:path";
3
+ import { isAbsolute, join, resolve as nodeResolvePath, parse, relative, sep } from "node:path";
4
4
  import { fileURLToPath } from "node:url";
5
5
  import { spawnProcessSync } from "./child-process.js";
6
6
  const UNICODE_SPACES = /[\u00A0\u2000-\u200A\u202F\u205F\u3000]/g;
@@ -12,12 +12,118 @@ const UNICODE_SPACES = /[\u00A0\u2000-\u200A\u202F\u205F\u3000]/g;
12
12
  */
13
13
  export function canonicalizePath(path) {
14
14
  try {
15
- return realpathSync(path);
15
+ return realpathSync.native(path);
16
16
  }
17
17
  catch {
18
18
  return path;
19
19
  }
20
20
  }
21
+ /**
22
+ * {@link canonicalizePath} that refuses to guess.
23
+ *
24
+ * The convenience form hands back its input when resolution fails, which is what most callers
25
+ * want: they compare two canonical spellings and an unresolvable path simply compares unequal.
26
+ * A security decision cannot use that, because the raw input is indistinguishable from a
27
+ * canonical answer - so a path that could not be resolved would be keyed, compared, or trusted
28
+ * under a spelling the filesystem never confirmed. This form throws instead, leaving the caller
29
+ * to decide what an unresolvable path means.
30
+ */
31
+ export function canonicalizePathStrict(path) {
32
+ return realpathSync.native(path);
33
+ }
34
+ /** Symlink hops allowed while resolving one path; realpath(3) reports ELOOP past this. */
35
+ const MAX_SYMLINK_HOPS = 40;
36
+ /** Error codes that mean "this component does not exist", which resolution tolerates. */
37
+ const MISSING_PATH_CODES = new Set(["ENOENT", "ENOTDIR"]);
38
+ function splitParts(pathSegment) {
39
+ return pathSegment.split(sep).filter((part) => part.length > 0);
40
+ }
41
+ /**
42
+ * realpath(3) semantics with one lstat/readlink per component and no open(2) anywhere.
43
+ *
44
+ * `..` is applied to the already-resolved prefix rather than collapsed lexically, because the two
45
+ * differ whenever a link target reaches back through a symlink: for `entry -> jump/../secret` with
46
+ * `jump -> /elsewhere/dir`, POSIX resolves `jump` first and lands in `/elsewhere/secret`, while a
47
+ * lexical collapse would answer `secret` next to `entry`. Handing a lexical answer to a
48
+ * containment decision would approve one directory while the I/O reached another, so neither the
49
+ * input nor a link target is normalized before traversal.
50
+ */
51
+ function resolveWithoutOpen(inputPath) {
52
+ const absolutePath = isAbsolute(inputPath) ? inputPath : nodeResolvePath(inputPath);
53
+ const { root } = parse(absolutePath);
54
+ const pending = splitParts(absolutePath.slice(root.length));
55
+ const resolved = [];
56
+ let hops = 0;
57
+ while (pending.length > 0) {
58
+ const part = pending.shift();
59
+ if (part === undefined)
60
+ break;
61
+ if (part === ".")
62
+ continue;
63
+ if (part === "..") {
64
+ // At the root `..` is the root itself, which an empty stack already represents.
65
+ resolved.pop();
66
+ continue;
67
+ }
68
+ const candidate = join(root, ...resolved, part);
69
+ let link;
70
+ try {
71
+ if (!lstatSync(candidate).isSymbolicLink()) {
72
+ resolved.push(part);
73
+ continue;
74
+ }
75
+ hops += 1;
76
+ if (hops > MAX_SYMLINK_HOPS)
77
+ return { path: join(candidate, ...pending), errorCode: "ELOOP" };
78
+ link = readlinkSync(candidate);
79
+ }
80
+ catch (error) {
81
+ const code = error?.code ?? "UNKNOWN";
82
+ return { path: join(candidate, ...pending), errorCode: code };
83
+ }
84
+ if (isAbsolute(link)) {
85
+ resolved.length = 0;
86
+ pending.unshift(...splitParts(link.slice(parse(link).root.length)));
87
+ }
88
+ else {
89
+ pending.unshift(...splitParts(link));
90
+ }
91
+ }
92
+ return { path: join(root, ...resolved) };
93
+ }
94
+ /**
95
+ * Resolve symlinks without ever open(2)-ing a component, tolerating every error.
96
+ *
97
+ * Bun's `fs.realpath*` opens every directory it resolves, so an autofs trigger such as macOS
98
+ * `/home` blocks the calling thread (on the host main thread that freezes the TUI) and an
99
+ * execute-only directory fails with EACCES; lstat needs only search permission and never mounts
100
+ * anything. Components from the first unresolvable one onward are kept verbatim, so a file that
101
+ * does not exist yet still lands where its symlinked parent points.
102
+ *
103
+ * Never throws, so the resolved path may be incomplete. Use it where an approximate answer is
104
+ * better than a blocked thread — the external-path classifier and the monitor parent identity —
105
+ * and use {@link realpathWithoutOpenStrict} where an incomplete answer must not be trusted.
106
+ */
107
+ export function realpathWithoutOpen(inputPath) {
108
+ return resolveWithoutOpen(inputPath).path;
109
+ }
110
+ /**
111
+ * {@link realpathWithoutOpen} that refuses to answer with a guess: a missing component is still
112
+ * tolerated (its name is appended verbatim, which is what a not-yet-created target needs), but
113
+ * EACCES, EIO, ELOOP and hop exhaustion throw instead of returning an unresolved path. Callers
114
+ * that feed a canonical path into a containment or identity decision need this contract, because
115
+ * an unresolved path can name a different file than the one the I/O will reach.
116
+ */
117
+ export function realpathWithoutOpenStrict(inputPath) {
118
+ const resolution = resolveWithoutOpen(inputPath);
119
+ if (resolution.errorCode !== undefined && !MISSING_PATH_CODES.has(resolution.errorCode)) {
120
+ throw Object.assign(new Error(`${resolution.errorCode}: cannot resolve path '${inputPath}' without opening it`), {
121
+ code: resolution.errorCode,
122
+ path: inputPath,
123
+ });
124
+ }
125
+ return resolution.path;
126
+ }
21
127
  export function getFileRevision(path) {
22
128
  try {
23
129
  const stats = statSync(path, { bigint: true });
@@ -1 +1 @@
1
- {"version":3,"file":"paths.js","sourceRoot":"","sources":["../../src/utils/paths.ts"],"names":[],"mappings":"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","sourcesContent":["import { realpathSync, statSync } from \"node:fs\";\nimport { homedir } from \"node:os\";\nimport { isAbsolute, join, resolve as nodeResolvePath, relative, sep } from \"node:path\";\nimport { fileURLToPath } from \"node:url\";\nimport { spawnProcessSync } from \"./child-process.ts\";\n\nconst UNICODE_SPACES = /[\\u00A0\\u2000-\\u200A\\u202F\\u205F\\u3000]/g;\n\nexport interface PathInputOptions {\n\t/** Trim leading/trailing whitespace before normalization. */\n\ttrim?: boolean;\n\t/** Expand leading `~` to a home directory. Defaults to true. */\n\texpandTilde?: boolean;\n\t/** Home directory used for `~` expansion. Defaults to `os.homedir()`. */\n\thomeDir?: string;\n\t/** Strip a leading `@`, used for CLI @file paths. */\n\tstripAtPrefix?: boolean;\n\t/** Normalize unicode space variants to regular spaces. */\n\tnormalizeUnicodeSpaces?: boolean;\n}\n\n/**\n * Resolve a path to its canonical (real) form, following symlinks.\n * Falls back to the raw path if resolution fails (e.g. the target does\n * not exist yet), so that callers never crash on missing filesystem\n * entries.\n */\nexport function canonicalizePath(path: string): string {\n\ttry {\n\t\treturn realpathSync(path);\n\t} catch {\n\t\treturn path;\n\t}\n}\n\nexport function getFileRevision(path: string): string | undefined {\n\ttry {\n\t\tconst stats = statSync(path, { bigint: true });\n\t\treturn `${stats.dev}:${stats.ino}:${stats.size}:${stats.mtimeNs}:${stats.ctimeNs}`;\n\t} catch {\n\t\treturn undefined;\n\t}\n}\n\n/**\n * Returns true if the value is NOT a package source (npm:, git:, etc.)\n * or a remote URL protocol. Bare names, relative paths, and file: URLs\n * are considered local.\n */\nexport function isLocalPath(value: string): boolean {\n\tconst trimmed = value.trim();\n\t// Known non-local prefixes. file: URLs are local paths and are intentionally resolved by resolvePath().\n\tif (\n\t\ttrimmed.startsWith(\"npm:\") ||\n\t\ttrimmed.startsWith(\"git:\") ||\n\t\ttrimmed.startsWith(\"github:\") ||\n\t\ttrimmed.startsWith(\"http:\") ||\n\t\ttrimmed.startsWith(\"https:\") ||\n\t\ttrimmed.startsWith(\"ssh:\")\n\t) {\n\t\treturn false;\n\t}\n\treturn true;\n}\n\n/** Convert Git Bash, MSYS, Cygwin, and WSL drive paths to a form native Windows APIs accept. */\nexport function normalizeWindowsShellPath(filePath: string): string {\n\tif (!filePath.startsWith(\"/\") || filePath.startsWith(\"//\") || filePath.includes(\"\\\\\")) return filePath;\n\tconst match = filePath.match(/^\\/(?:mnt\\/|cygdrive\\/)?([a-z])(?:\\/(.*))?$/i);\n\tif (!match) return filePath;\n\tconst suffix = match[2]?.replaceAll(\"/\", \"\\\\\");\n\treturn `${match[1].toUpperCase()}:\\\\${suffix ?? \"\"}`;\n}\n\nexport function normalizePath(input: string, options: PathInputOptions = {}): string {\n\tlet normalized = options.trim ? input.trim() : input;\n\tif (options.normalizeUnicodeSpaces) {\n\t\tnormalized = normalized.replace(UNICODE_SPACES, \" \");\n\t}\n\tif (options.stripAtPrefix && normalized.startsWith(\"@\")) {\n\t\tnormalized = normalized.slice(1);\n\t}\n\tif (process.platform === \"win32\") {\n\t\tnormalized = normalizeWindowsShellPath(normalized);\n\t}\n\n\tif (options.expandTilde ?? true) {\n\t\tconst home = options.homeDir ?? homedir();\n\t\tif (normalized === \"~\") return home;\n\t\tif (normalized.startsWith(\"~/\") || (process.platform === \"win32\" && normalized.startsWith(\"~\\\\\"))) {\n\t\t\treturn join(home, normalized.slice(2));\n\t\t}\n\t}\n\n\tif (/^file:\\/\\//.test(normalized)) {\n\t\treturn fileURLToPath(normalized);\n\t}\n\n\treturn normalized;\n}\n\nexport function resolvePath(input: string, baseDir: string = process.cwd(), options: PathInputOptions = {}): string {\n\tconst normalized = normalizePath(input, options);\n\tconst normalizedBaseDir = normalizePath(baseDir);\n\treturn isAbsolute(normalized) ? nodeResolvePath(normalized) : nodeResolvePath(normalizedBaseDir, normalized);\n}\n\nexport function getCwdRelativePath(filePath: string, cwd: string): string | undefined {\n\tconst resolvedCwd = resolvePath(cwd);\n\tconst resolvedPath = resolvePath(filePath, resolvedCwd);\n\tconst relativePath = relative(resolvedCwd, resolvedPath);\n\tconst isInsideCwd =\n\t\trelativePath === \"\" ||\n\t\t(relativePath !== \"..\" && !relativePath.startsWith(`..${sep}`) && !isAbsolute(relativePath));\n\n\treturn isInsideCwd ? relativePath || \".\" : undefined;\n}\n\nexport function formatPathRelativeToCwdOrAbsolute(filePath: string, cwd: string): string {\n\tconst absolutePath = resolvePath(filePath, cwd);\n\treturn (getCwdRelativePath(absolutePath, cwd) ?? absolutePath).split(sep).join(\"/\");\n}\n\nexport function shortenPath(path: string): string {\n\tif (!path) return path;\n\tconst home = homedir();\n\tif (path.startsWith(home)) return `~${path.slice(home.length)}`;\n\treturn path;\n}\n\nexport function markPathIgnoredByCloudSync(path: string): void {\n\tconst attrs =\n\t\tprocess.platform === \"darwin\"\n\t\t\t? [\"com.dropbox.ignored\", \"com.apple.fileprovider.ignore#P\"]\n\t\t\t: process.platform === \"linux\"\n\t\t\t\t? [\"user.com.dropbox.ignored\"]\n\t\t\t\t: [];\n\n\tfor (const attr of attrs) {\n\t\tif (process.platform === \"darwin\") {\n\t\t\tspawnProcessSync(\"xattr\", [\"-w\", attr, \"1\", path], { encoding: \"utf-8\", stdio: \"ignore\" });\n\t\t} else {\n\t\t\tspawnProcessSync(\"setfattr\", [\"-n\", attr, \"-v\", \"1\", path], { encoding: \"utf-8\", stdio: \"ignore\" });\n\t\t}\n\t}\n}\n"]}
1
+ {"version":3,"file":"paths.js","sourceRoot":"","sources":["../../src/utils/paths.ts"],"names":[],"mappings":"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","sourcesContent":["import { lstatSync, readlinkSync, realpathSync, statSync } from \"node:fs\";\nimport { homedir } from \"node:os\";\nimport { isAbsolute, join, resolve as nodeResolvePath, parse, relative, sep } from \"node:path\";\nimport { fileURLToPath } from \"node:url\";\nimport { spawnProcessSync } from \"./child-process.ts\";\n\nconst UNICODE_SPACES = /[\\u00A0\\u2000-\\u200A\\u202F\\u205F\\u3000]/g;\n\nexport interface PathInputOptions {\n\t/** Trim leading/trailing whitespace before normalization. */\n\ttrim?: boolean;\n\t/** Expand leading `~` to a home directory. Defaults to true. */\n\texpandTilde?: boolean;\n\t/** Home directory used for `~` expansion. Defaults to `os.homedir()`. */\n\thomeDir?: string;\n\t/** Strip a leading `@`, used for CLI @file paths. */\n\tstripAtPrefix?: boolean;\n\t/** Normalize unicode space variants to regular spaces. */\n\tnormalizeUnicodeSpaces?: boolean;\n}\n\n/**\n * Resolve a path to its canonical (real) form, following symlinks.\n * Falls back to the raw path if resolution fails (e.g. the target does\n * not exist yet), so that callers never crash on missing filesystem\n * entries.\n */\nexport function canonicalizePath(path: string): string {\n\ttry {\n\t\treturn realpathSync.native(path);\n\t} catch {\n\t\treturn path;\n\t}\n}\n\n/**\n * {@link canonicalizePath} that refuses to guess.\n *\n * The convenience form hands back its input when resolution fails, which is what most callers\n * want: they compare two canonical spellings and an unresolvable path simply compares unequal.\n * A security decision cannot use that, because the raw input is indistinguishable from a\n * canonical answer - so a path that could not be resolved would be keyed, compared, or trusted\n * under a spelling the filesystem never confirmed. This form throws instead, leaving the caller\n * to decide what an unresolvable path means.\n */\nexport function canonicalizePathStrict(path: string): string {\n\treturn realpathSync.native(path);\n}\n\n/** Symlink hops allowed while resolving one path; realpath(3) reports ELOOP past this. */\nconst MAX_SYMLINK_HOPS = 40;\n\n/** Error codes that mean \"this component does not exist\", which resolution tolerates. */\nconst MISSING_PATH_CODES = new Set([\"ENOENT\", \"ENOTDIR\"]);\n\nfunction splitParts(pathSegment: string): string[] {\n\treturn pathSegment.split(sep).filter((part) => part.length > 0);\n}\n\ninterface OpenFreeResolution {\n\t/** The resolved path; components from an unresolvable one onward are kept verbatim. */\n\treadonly path: string;\n\t/** Set when resolution stopped early: the fs error code, or ELOOP for hop exhaustion. */\n\treadonly errorCode?: string;\n}\n\n/**\n * realpath(3) semantics with one lstat/readlink per component and no open(2) anywhere.\n *\n * `..` is applied to the already-resolved prefix rather than collapsed lexically, because the two\n * differ whenever a link target reaches back through a symlink: for `entry -> jump/../secret` with\n * `jump -> /elsewhere/dir`, POSIX resolves `jump` first and lands in `/elsewhere/secret`, while a\n * lexical collapse would answer `secret` next to `entry`. Handing a lexical answer to a\n * containment decision would approve one directory while the I/O reached another, so neither the\n * input nor a link target is normalized before traversal.\n */\nfunction resolveWithoutOpen(inputPath: string): OpenFreeResolution {\n\tconst absolutePath = isAbsolute(inputPath) ? inputPath : nodeResolvePath(inputPath);\n\tconst { root } = parse(absolutePath);\n\tconst pending = splitParts(absolutePath.slice(root.length));\n\tconst resolved: string[] = [];\n\tlet hops = 0;\n\twhile (pending.length > 0) {\n\t\tconst part = pending.shift();\n\t\tif (part === undefined) break;\n\t\tif (part === \".\") continue;\n\t\tif (part === \"..\") {\n\t\t\t// At the root `..` is the root itself, which an empty stack already represents.\n\t\t\tresolved.pop();\n\t\t\tcontinue;\n\t\t}\n\t\tconst candidate = join(root, ...resolved, part);\n\t\tlet link: string;\n\t\ttry {\n\t\t\tif (!lstatSync(candidate).isSymbolicLink()) {\n\t\t\t\tresolved.push(part);\n\t\t\t\tcontinue;\n\t\t\t}\n\t\t\thops += 1;\n\t\t\tif (hops > MAX_SYMLINK_HOPS) return { path: join(candidate, ...pending), errorCode: \"ELOOP\" };\n\t\t\tlink = readlinkSync(candidate);\n\t\t} catch (error) {\n\t\t\tconst code = (error as NodeJS.ErrnoException)?.code ?? \"UNKNOWN\";\n\t\t\treturn { path: join(candidate, ...pending), errorCode: code };\n\t\t}\n\t\tif (isAbsolute(link)) {\n\t\t\tresolved.length = 0;\n\t\t\tpending.unshift(...splitParts(link.slice(parse(link).root.length)));\n\t\t} else {\n\t\t\tpending.unshift(...splitParts(link));\n\t\t}\n\t}\n\treturn { path: join(root, ...resolved) };\n}\n\n/**\n * Resolve symlinks without ever open(2)-ing a component, tolerating every error.\n *\n * Bun's `fs.realpath*` opens every directory it resolves, so an autofs trigger such as macOS\n * `/home` blocks the calling thread (on the host main thread that freezes the TUI) and an\n * execute-only directory fails with EACCES; lstat needs only search permission and never mounts\n * anything. Components from the first unresolvable one onward are kept verbatim, so a file that\n * does not exist yet still lands where its symlinked parent points.\n *\n * Never throws, so the resolved path may be incomplete. Use it where an approximate answer is\n * better than a blocked thread — the external-path classifier and the monitor parent identity —\n * and use {@link realpathWithoutOpenStrict} where an incomplete answer must not be trusted.\n */\nexport function realpathWithoutOpen(inputPath: string): string {\n\treturn resolveWithoutOpen(inputPath).path;\n}\n\n/**\n * {@link realpathWithoutOpen} that refuses to answer with a guess: a missing component is still\n * tolerated (its name is appended verbatim, which is what a not-yet-created target needs), but\n * EACCES, EIO, ELOOP and hop exhaustion throw instead of returning an unresolved path. Callers\n * that feed a canonical path into a containment or identity decision need this contract, because\n * an unresolved path can name a different file than the one the I/O will reach.\n */\nexport function realpathWithoutOpenStrict(inputPath: string): string {\n\tconst resolution = resolveWithoutOpen(inputPath);\n\tif (resolution.errorCode !== undefined && !MISSING_PATH_CODES.has(resolution.errorCode)) {\n\t\tthrow Object.assign(new Error(`${resolution.errorCode}: cannot resolve path '${inputPath}' without opening it`), {\n\t\t\tcode: resolution.errorCode,\n\t\t\tpath: inputPath,\n\t\t});\n\t}\n\treturn resolution.path;\n}\n\nexport function getFileRevision(path: string): string | undefined {\n\ttry {\n\t\tconst stats = statSync(path, { bigint: true });\n\t\treturn `${stats.dev}:${stats.ino}:${stats.size}:${stats.mtimeNs}:${stats.ctimeNs}`;\n\t} catch {\n\t\treturn undefined;\n\t}\n}\n\n/**\n * Returns true if the value is NOT a package source (npm:, git:, etc.)\n * or a remote URL protocol. Bare names, relative paths, and file: URLs\n * are considered local.\n */\nexport function isLocalPath(value: string): boolean {\n\tconst trimmed = value.trim();\n\t// Known non-local prefixes. file: URLs are local paths and are intentionally resolved by resolvePath().\n\tif (\n\t\ttrimmed.startsWith(\"npm:\") ||\n\t\ttrimmed.startsWith(\"git:\") ||\n\t\ttrimmed.startsWith(\"github:\") ||\n\t\ttrimmed.startsWith(\"http:\") ||\n\t\ttrimmed.startsWith(\"https:\") ||\n\t\ttrimmed.startsWith(\"ssh:\")\n\t) {\n\t\treturn false;\n\t}\n\treturn true;\n}\n\n/** Convert Git Bash, MSYS, Cygwin, and WSL drive paths to a form native Windows APIs accept. */\nexport function normalizeWindowsShellPath(filePath: string): string {\n\tif (!filePath.startsWith(\"/\") || filePath.startsWith(\"//\") || filePath.includes(\"\\\\\")) return filePath;\n\tconst match = filePath.match(/^\\/(?:mnt\\/|cygdrive\\/)?([a-z])(?:\\/(.*))?$/i);\n\tif (!match) return filePath;\n\tconst suffix = match[2]?.replaceAll(\"/\", \"\\\\\");\n\treturn `${match[1].toUpperCase()}:\\\\${suffix ?? \"\"}`;\n}\n\nexport function normalizePath(input: string, options: PathInputOptions = {}): string {\n\tlet normalized = options.trim ? input.trim() : input;\n\tif (options.normalizeUnicodeSpaces) {\n\t\tnormalized = normalized.replace(UNICODE_SPACES, \" \");\n\t}\n\tif (options.stripAtPrefix && normalized.startsWith(\"@\")) {\n\t\tnormalized = normalized.slice(1);\n\t}\n\tif (process.platform === \"win32\") {\n\t\tnormalized = normalizeWindowsShellPath(normalized);\n\t}\n\n\tif (options.expandTilde ?? true) {\n\t\tconst home = options.homeDir ?? homedir();\n\t\tif (normalized === \"~\") return home;\n\t\tif (normalized.startsWith(\"~/\") || (process.platform === \"win32\" && normalized.startsWith(\"~\\\\\"))) {\n\t\t\treturn join(home, normalized.slice(2));\n\t\t}\n\t}\n\n\tif (/^file:\\/\\//.test(normalized)) {\n\t\treturn fileURLToPath(normalized);\n\t}\n\n\treturn normalized;\n}\n\nexport function resolvePath(input: string, baseDir: string = process.cwd(), options: PathInputOptions = {}): string {\n\tconst normalized = normalizePath(input, options);\n\tconst normalizedBaseDir = normalizePath(baseDir);\n\treturn isAbsolute(normalized) ? nodeResolvePath(normalized) : nodeResolvePath(normalizedBaseDir, normalized);\n}\n\nexport function getCwdRelativePath(filePath: string, cwd: string): string | undefined {\n\tconst resolvedCwd = resolvePath(cwd);\n\tconst resolvedPath = resolvePath(filePath, resolvedCwd);\n\tconst relativePath = relative(resolvedCwd, resolvedPath);\n\tconst isInsideCwd =\n\t\trelativePath === \"\" ||\n\t\t(relativePath !== \"..\" && !relativePath.startsWith(`..${sep}`) && !isAbsolute(relativePath));\n\n\treturn isInsideCwd ? relativePath || \".\" : undefined;\n}\n\nexport function formatPathRelativeToCwdOrAbsolute(filePath: string, cwd: string): string {\n\tconst absolutePath = resolvePath(filePath, cwd);\n\treturn (getCwdRelativePath(absolutePath, cwd) ?? absolutePath).split(sep).join(\"/\");\n}\n\nexport function shortenPath(path: string): string {\n\tif (!path) return path;\n\tconst home = homedir();\n\tif (path.startsWith(home)) return `~${path.slice(home.length)}`;\n\treturn path;\n}\n\nexport function markPathIgnoredByCloudSync(path: string): void {\n\tconst attrs =\n\t\tprocess.platform === \"darwin\"\n\t\t\t? [\"com.dropbox.ignored\", \"com.apple.fileprovider.ignore#P\"]\n\t\t\t: process.platform === \"linux\"\n\t\t\t\t? [\"user.com.dropbox.ignored\"]\n\t\t\t\t: [];\n\n\tfor (const attr of attrs) {\n\t\tif (process.platform === \"darwin\") {\n\t\t\tspawnProcessSync(\"xattr\", [\"-w\", attr, \"1\", path], { encoding: \"utf-8\", stdio: \"ignore\" });\n\t\t} else {\n\t\t\tspawnProcessSync(\"setfattr\", [\"-n\", attr, \"-v\", \"1\", path], { encoding: \"utf-8\", stdio: \"ignore\" });\n\t\t}\n\t}\n}\n"]}
@@ -136,7 +136,7 @@ Configure compaction in `~/.senpi/agent/settings.json` (global) or `.senpi/setti
136
136
 
137
137
  | Setting | Default | Description |
138
138
  |---------|---------|-------------|
139
- | `compaction.enabled` | `true` | Enable auto-compaction. Set to `false` for manual control only. |
139
+ | `compaction.enabled` | `true` | Enable proactive auto-compaction. `false` leaves threshold compaction to you; a provider-rejected context overflow still runs the one-shot compact-and-retry recovery. |
140
140
  | `compaction.reserveTokens` | `16384` | Tokens reserved for the LLM's response. Lower values allow more context but risk truncation. |
141
141
  | `compaction.keepRecentTokens` | `20000` | How much recent conversation to keep uncompressed. Higher values preserve more context but trigger compaction sooner. |
142
142
 
@@ -221,7 +221,7 @@ For technical details, see:
221
221
 
222
222
  ### "I want to disable auto-compaction"
223
223
 
224
- Set `compaction.enabled: false` in your settings. Use `/compact` manually when you want to compact. Be aware that without auto-compaction, long sessions may hit context limits and fail.
224
+ Set `compaction.enabled: false` in your settings. Use `/compact` manually when you want to compact. Threshold compaction stays off, but a context the provider rejects as too large is still compacted once and retried so the session does not stall. The RPC `set_auto_compaction` command and OmO Desktop's per-thread toggle change only that session; they do not write this setting.
225
225
 
226
226
  ### "How do I see what was compacted?"
227
227
 
@@ -411,8 +411,8 @@ Configure compaction in `~/.senpi/agent/settings.json` or `<project-dir>/.senpi/
411
411
 
412
412
  | Setting | Default | Description |
413
413
  |---------|---------|-------------|
414
- | `enabled` | `true` | Enable auto-compaction |
414
+ | `enabled` | `true` | Enable proactive (threshold) auto-compaction; provider-overflow recovery stays armed |
415
415
  | `reserveTokens` | `16384` | Tokens to reserve for LLM response |
416
416
  | `keepRecentTokens` | `20000` | Recent tokens to keep (not summarized) |
417
417
 
418
- Disable auto-compaction with `"enabled": false`. You can still compact manually with `/compact`.
418
+ Disable proactive auto-compaction with `"enabled": false`. You can still compact manually with `/compact`, and a context the provider rejects as too large is still compacted once and retried automatically.
@@ -62,10 +62,11 @@ bun run check # Biome + tsc + browser-smoke check (pre-commit equivalent)
62
62
 
63
63
  Live-API tests are env-gated vitest tests. Set `PI_ENABLE_LIVE_API_TESTS=1` (or a per-provider flag from `packages/ai/test/live-api-gates.ts`) plus the provider API keys, then run `bun run test`.
64
64
 
65
- Run a specific test:
65
+ Run a specific test from the package, or from the repository root through the workspace runner (the root form runs the `scripts/` tests first):
66
66
 
67
67
  ```bash
68
- bun run test --workspace @code-yeongyu/senpi -- test/specific.test.ts
68
+ bun run --cwd packages/coding-agent test -- test/specific.test.ts
69
+ bun run test --workspace packages/coding-agent -- test/specific.test.ts
69
70
  ```
70
71
 
71
72
  ## Project Structure
package/docs/providers.md CHANGED
@@ -101,7 +101,7 @@ If your Claude Pro/Max subscription usage through `claude-sdk-oauth` feels unexp
101
101
  | Anthropic-compatible providers (kimi-coding, fireworks, gateways) | 5 minutes | The 1h TTL is gated on the native `api.anthropic.com` base URL, so these lanes stay short. | `cacheRetention` |
102
102
 
103
103
  Override precedence: `cacheRetention` in `models.json` / the model catalog wins over everything. `PI_CACHE_RETENTION=long` selects long; any other set value forces short; unset falls back to the lane default above.
104
- 5. **Goal-monitor timing.** The goal monitor's continuation backstop is derived from the model's cache-safe wait (TTL minus `promptCache.safetyBufferSeconds`, default 30), capped by `promptCache.goalBackstopMaxSeconds` (default 3570), instead of a fixed 4 minutes. The default 5m lanes wake every ~4m30s; a supported lane explicitly configured for 1h retention can wait up to 59m30s. Cache-warm notices show which warm iteration you are on.
104
+ 5. **Goal-monitor timing.** While at least one wake source is live, the goal monitor arms one stall backstop of `promptCache.goalBackstopMaxSeconds` (default 3570, so ~59m30s, hard-capped at 1h) and nothing else. It no longer wakes on the cache-safe wait (TTL minus `promptCache.safetyBufferSeconds`), because each of those wakes was a full main-model turn that re-sent the accumulated context every ~4m30s for the whole wait. The normal resumption is event-driven: when the last wake source drains, exactly one continuation is queued about a second later. The backstop only fires if nothing ever delivers, as a stall safety net; the prompt cache is expected to have expired by then, and `promptCache.keepAlive` (opt-in, off by default) is the only thing that refreshes it during the wait. Cache-warm notices show which warm iteration you are on.
105
105
  6. **Wake sources that hold the goal backstop.** Anything that can wake a parked session publishes a `wake_source_state` event (`{source, activeCount}`): terminal monitors (`terminal-monitors`), background bash sessions including auto-detached and killed ones (`terminal-background-sessions`), detached `eval` cells (`senpi-codemode`), and omo-senpi background task children plus owned team members (`senpi-task`). The goal extension sums every source, so a goal waits inside the prompt-cache TTL while ANY of them is on duty instead of continuing immediately. The legacy `terminal_monitor_state` event is still emitted for external consumers and is folded onto the same `terminal-monitors` count.
106
106
  7. **Directive-block deduplication.** As of v2026.8.4, the flatten serialization collapses repeated `<ultrawork-mode>` directive blocks to a single copy, preventing the issue-#494 scenario where duplicated ~17KB directive blocks consumed up to 73% of the re-sent prompt. The continuity observation reports how many were collapsed and the payload size.
107
107
 
package/docs/rpc.md CHANGED
@@ -188,16 +188,16 @@ occupancy bounds itself, independent of the supervisor and of client cooperation
188
188
  background terminal jobs and any other published wake source (terminal monitors, loop-guard holds), compaction,
189
189
  and barrier-held session work all defer eviction, and the idle clock restarts when that work settles. An evicted
190
190
  session resumes like any other: the next `open_session` with the same `sessionPath` reopens it.
191
- - **Session cap**: `open_session` beyond `SENPI_RPC_MAX_SESSIONS` concurrently opening/open sessions (default 8)
192
- fails with `too_many_sessions`. Attaching to an already-hosted session (`attached: true`) adds no runtime and never
193
- counts against the cap, so resume and second-surface flows keep working while at it.
191
+ - **Logical sessions**: `open_session` has no session-count admission limit. Every logical session remains isolated
192
+ by its routing handle and provider scope inside this one shared daemon; idle eviction and empty-host shutdown
193
+ reclaim resident resources without rejecting a new session.
194
194
  - **Empty-host exit**: when the registry holds zero sessions AND no client is connected, continuously for
195
195
  `SENPI_RPC_HOST_EMPTY_EXIT_MS` (default 15 minutes), the host exits through its clean shutdown path (flush, socket
196
196
  removal), for stdio and `--listen` hosts alike. A connected client counts as occupancy even with no session open,
197
197
  so the host never drops a live socket under itself. Supervised hosts stay clean either way: a supervisor reads a
198
198
  child exit of 0 without a signal as an intentional idle stop and exits 0 with the same cleanup, not as a crash.
199
199
 
200
- Values are positive integers; invalid values fall through to the defaults. These bounds run inside the host process,
200
+ Values are positive integers; invalid values fall through to the defaults. These lifecycle windows run inside the host process,
201
201
  so they hold even for embedders and hand-started hosts that have no supervisor.
202
202
 
203
203
  ### D1 normative table (multi-session mode)
@@ -205,7 +205,7 @@ so they hold even for embedders and hand-started hosts that have no supervisor.
205
205
  | Command | Params | Success data | Notes |
206
206
  | --- | --- | --- | --- |
207
207
  | `get_protocol_info` | - | `{ protocolVersion: 1, serverVersion: string, capabilities: string[], mode: "classic"\|"multi" }` | Answered in BOTH modes; side-effect-free; the capability probe. Multi-session hosts include `multi_session` plus the negotiated launch capabilities. |
208
- | `open_session` | `sessionPath?`, `cwd?`, `provider?`, `modelId?`, `thinkingLevel?`, `permissionPreset?` (all optional; paths MUST be absolute) | `{ sessionId, state: RpcSessionState, attached?: true }` | `sessionPath` = today's `--session` semantics (open-if-exists else create persisting there, `session-manager.ts:926-940`); `provider`/`modelId` applied only on create (resume restores the session's model — mirrors `SenpiSessionRuntime.ts:198-200`); params form the immutable launch profile (D8). When the path is already held by a fully-open session, the open ATTACHES to it: same routing handle, `attached: true`, one more attachment counted; the runtime is torn down only when the last attachment closes. Fails with `too_many_sessions` at the occupancy cap (see [Shared host occupancy](#shared-host-occupancy-idle-eviction-session-cap-empty-host-exit)); idle sessions past the eviction window are closed by the host itself. |
208
+ | `open_session` | `sessionPath?`, `cwd?`, `provider?`, `modelId?`, `thinkingLevel?`, `permissionPreset?` (all optional; paths MUST be absolute) | `{ sessionId, state: RpcSessionState, attached?: true }` | `sessionPath` = today's `--session` semantics (open-if-exists else create persisting there, `session-manager.ts:926-940`); `provider`/`modelId` applied only on create (resume restores the session's model — mirrors `SenpiSessionRuntime.ts:198-200`); params form the immutable launch profile (D8). When the path is already held by a fully-open session, the open ATTACHES to it: same routing handle, `attached: true`, one more attachment counted; the runtime is torn down only when the last attachment closes. Idle sessions past the eviction window are closed by the host itself. |
209
209
  | `close_session` | `sessionId` | `{}` | Aborts active work, awaits agent idle + settled persistence for up to the host grace window (default 10s), then force-releases the session; its response is the LAST record tagged with that handle for the first closer — no events after (test-pinned). A concurrent close joins the same teardown and receives its own successful response. |
210
210
  | `list_sessions` | - | `{ sessions: [{ sessionId, durableSessionId, sessionPath, cwd, name, status }] }` | Includes `opening`/`closing` entries with their status. |
211
211
  | every existing command | + `sessionId` (REQUIRED in multi mode) | unchanged | Routed to that session. |
@@ -224,7 +224,6 @@ In the response `error` field, machine-matchable:
224
224
  - `missing_session_id` (session-scoped command without `sessionId` in multi mode)
225
225
  - `multi_session_disabled` (`open_session` in classic mode)
226
226
  - `invalid_path` (relative `sessionPath`/`cwd`)
227
- - `too_many_sessions` (`open_session` while `SENPI_RPC_MAX_SESSIONS` sessions are already opening/open; attaching to a live session never fails this way)
228
227
  - `open_failed: <detail>`
229
228
  - `media_not_found` (`get_media` for an unknown `toolCallId`, or a `contentIndex` that does not point at an image block)
230
229
 
@@ -796,7 +795,7 @@ Response:
796
795
 
797
796
  #### set_auto_compaction
798
797
 
799
- Enable or disable automatic compaction when context is nearly full.
798
+ Enable or disable automatic compaction when context is nearly full for this session only. The persisted `compaction.enabled` setting is left untouched, and `get_state` reports the effective value. Disabling it stops proactive threshold compaction; a provider-rejected context overflow still triggers the one-shot compact-and-retry recovery.
800
799
 
801
800
  ```json
802
801
  {"type": "set_auto_compaction", "enabled": true}
@@ -12,6 +12,80 @@
12
12
 
13
13
  ### Removed
14
14
 
15
+ ## [2026.9.7-2] - 2026-09-07
16
+
17
+ ### Breaking Changes
18
+
19
+ ### Added
20
+
21
+ ### Changed
22
+
23
+ ### Fixed
24
+
25
+ - The JS kernel's shell capture now pins the worker's environment view for `Bun.spawnSync` as well as `Bun.spawn`, so a cell calling it without an explicit `env` sees the session's `PI_*` values instead of the inherited OS environ.
26
+ - Eval kernels and every child they spawn now see the active session's `PI_*` environment (`PI_SESSION_ID`, `PI_SESSION_FILE`, `PI_PROVIDER`, `PI_MODEL`, `PI_REASONING_LEVEL`) exactly as bash-tool children do: inherited `PI_*` values are dropped before the session values are applied, so subprocesses such as `omo-agent-toolkit ulw-loop` resolve the same session as the `bash` tool instead of a cwd-global one.
27
+ - JavaScript eval cells no longer lose their completion value when a nested function, callback, or try/catch helper contains `return`: the cell wrapper now skips last-expression capture only for a genuine top-level `return`, and a property named `return` no longer primes the statement scanner as the keyword (#1439).
28
+ - Eval output truncation notices now name the real cause: a width-clamped line reports `N line(s) clamped to M columns (… dropped)`, a byte-capped tail reports the actual cap, and a notice never presents the output's own size as a limit.
29
+
30
+ ### Removed
31
+
32
+ ## [2026.9.7] - 2026-09-07
33
+
34
+ ### Breaking Changes
35
+
36
+ ### Added
37
+
38
+ ### Changed
39
+
40
+ ### Fixed
41
+
42
+ ### Removed
43
+
44
+ ## [2026.9.6] - 2026-09-06
45
+
46
+ ### Breaking Changes
47
+
48
+ ### Added
49
+
50
+ ### Changed
51
+
52
+ - The Bun eval description now tells the model to shell out through `Bun.$` or `Bun.spawn` and never `Bun.spawnSync`, because a synchronous child blocks the worker and a stop or timeout then loses every variable.
53
+ - JavaScript eval cells now interrupt cooperatively: `stop` and kernel timeouts first ask the worker to settle the cell (pending bridge `tool.*` calls are rejected, `Bun.spawn` children are killed) and keep the worker VM and its globals when the cell settles within a 2 s grace; only an unsettled cell restarts the worker.
54
+
55
+ ### Fixed
56
+
57
+ - `eval({ action: "stop" })` no longer hangs when the JavaScript worker is blocked in a synchronous call such as `Bun.spawnSync`: worker termination is bounded by a 3 s deadline, a fresh worker replaces the blocked one, and the cell output names the blocked synchronous call.
58
+ - `Bun.$` commands run from a JavaScript cell no longer inherit the TUI's terminal as stdin (a stdin reader such as `cat`, an ssh or git credential prompt, or a keychain prompt blocked the cell forever); the shell wrapper isolates stdin while a cell is active without changing output, exit codes, `cwd`, `env`, or explicit stdin redirects.
59
+ - Stop results and detached-cell completion notifications report the real interrupt outcome (variables preserved, worker restarted, or outcome unknown) instead of a hardcoded per-language note.
60
+
61
+ ### Removed
62
+
63
+ ## [2026.9.5-3] - 2026-09-05
64
+
65
+ ### Breaking Changes
66
+
67
+ ### Added
68
+
69
+ ### Changed
70
+
71
+ ### Fixed
72
+
73
+ ### Removed
74
+
75
+ ## [2026.9.5-2] - 2026-09-05
76
+
77
+ ### Breaking Changes
78
+
79
+ ### Added
80
+
81
+ ### Changed
82
+
83
+ - The GPT eval dialect now routes a wait or a long run through `tool.monitor` inside the cell (the subscription line precedes the detach note, and the `## Tool Guidelines` line says so when `monitor` is reachable), so a GPT model no longer reads "long cells detach" as the way to wait on a `--watch`.
84
+
85
+ ### Fixed
86
+
87
+ ### Removed
88
+
15
89
  ## [2026.9.5] - 2026-09-05
16
90
 
17
91
  ### Breaking Changes
@@ -54,6 +54,19 @@ task-tool names are known.
54
54
  A missing optional interpreter removes that language from the session's `eval`
55
55
  schema; it is not an installation failure.
56
56
 
57
+ ### Session environment
58
+
59
+ Every kernel starts with the active session's `PI_*` environment — `PI_SESSION_ID`,
60
+ `PI_SESSION_FILE` (when the session is persistent), `PI_PROVIDER`, `PI_MODEL`, and
61
+ `PI_REASONING_LEVEL` (when set) — resolved at session start, mirroring the bash tool's
62
+ session environment contract. The values are visible to `env()`/`process.env`/`os.environ`
63
+ inside cells and are inherited by every child process a cell spawns
64
+ (`Bun.$`, `Bun.spawn`, `child_process`, `subprocess`, ...). Inherited `PI_*` values from
65
+ the launching environment are dropped first, so a child spawned from a cell sees exactly
66
+ what a child spawned from the bash tool sees. The values snapshot at kernel start, so a
67
+ mid-session model switch updates the bash tool's next command but not already-running
68
+ kernels; a new session starts fresh kernels with fresh values.
69
+
57
70
  ## Settings
58
71
 
59
72
  Configuration is loaded in this order:
@@ -116,7 +129,7 @@ options object and asynchronous helpers are `await`-able.
116
129
  | `print(value, ...)` | Emits text output. |
117
130
  | `read(path, offset?, limit?)` | Reads text with 1-indexed line slicing. `local://` paths resolve under the session artifact root. |
118
131
  | `write(path, content)` | Creates parent directories and writes text. `local://` paths persist in the session artifact root. |
119
- | `env(key?, value?)` | Reads all kernel environment values, one value, or sets one value. |
132
+ | `env(key?, value?)` | Reads all kernel environment values, one value, or sets one value. Includes the session's `PI_*` values (see [Session environment](#session-environment)). |
120
133
  | `tool.<name>(args)` | Invokes an active Senpi tool through the normal `pi.executeTool` pipeline and returns `{ text, images?, details?, hasError? }` in every kernel; image blocks arrive as `images[i] = { mimeType, dataBase64 }`. |
121
134
  | `tool_schema(name?)` | Returns a tool's parameter schema without calling it; omit `name` to list tool names. |
122
135
  | `completion(prompt, model?, system?, schema?)` | Requests a one-shot host completion; `schema` asks the host to parse structured output. |
@@ -166,10 +179,26 @@ when the call had no summary), clearing as soon as the last detached cell settle
166
179
 
167
180
  Use `eval({ action: "peek", cell_id })` for its state and buffered output, or
168
181
  `eval({ action: "stop", cell_id })` to cancel it. Python stop interrupts the
169
- existing kernel and preserves variables. JavaScript stop kills and restarts its
170
- worker, so JavaScript VM state is lost. Detached completion messages state when
171
- kernel variables are available to the next eval cell; oversized buffered output
172
- is written under the session local root and referenced as `local://…`.
182
+ existing kernel and preserves variables. JavaScript stop is cooperative first:
183
+ the worker rejects the cell's pending bridge `tool.*` calls and kills the
184
+ `Bun.spawn` children it started, and a cell that settles within the 2 s grace
185
+ keeps the worker and every global. Only a cell that stays unsettled (a
186
+ never-resolving promise, an un-abortable `fetch`, a `Bun.$` command) costs the
187
+ worker VM. A worker blocked in a synchronous call (`Bun.spawnSync`,
188
+ `child_process.spawnSync`) cannot be stopped at all; after a 3 s termination
189
+ deadline a fresh worker replaces it, the cell output gains a stderr line naming
190
+ the blocked synchronous call, and the blocked call keeps running until it
191
+ returns. Kernel-level timeouts follow the same path. Stop results and detached
192
+ completion messages report the real outcome - variables preserved, worker
193
+ restarted, or outcome unknown - never a per-language assumption; oversized
194
+ buffered output is written under the session local root and referenced as
195
+ `local://…`.
196
+
197
+ Commands a cell runs through `Bun.$` never read the host's terminal: the worker
198
+ thread shares the TUI's stdin, so the shell wrapper hands every template an
199
+ empty pipe (`true | ( … )`) while a cell is active. Output, exit codes, `cwd`,
200
+ `env`, and explicit `< ${input}` redirects are unchanged; `Bun.spawn` and
201
+ `Bun.spawnSync` already default stdin to `/dev/null`.
173
202
 
174
203
  ## Output and artifacts
175
204
 
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@code-yeongyu/senpi-codemode",
3
- "version": "2026.9.5",
3
+ "version": "2026.9.7-2",
4
4
  "private": true,
5
5
  "description": "Source-only senpi extension package for codemode evaluation tools",
6
6
  "type": "module",
@@ -31,14 +31,14 @@
31
31
  },
32
32
  "dependencies": {
33
33
  "@babel/parser": "8.0.4",
34
- "@earendil-works/pi-ai": "npm:@code-yeongyu/senpi-ai@2026.9.5",
34
+ "@earendil-works/pi-ai": "npm:@code-yeongyu/senpi-ai@2026.9.7-2",
35
35
  "typebox": "1.3.18"
36
36
  },
37
37
  "peerDependencies": {
38
- "@code-yeongyu/senpi": "2026.9.5"
38
+ "@code-yeongyu/senpi": "2026.9.7-2"
39
39
  },
40
40
  "devDependencies": {
41
- "@code-yeongyu/senpi": "2026.9.5"
41
+ "@code-yeongyu/senpi": "2026.9.7-2"
42
42
  },
43
43
  "keywords": [
44
44
  "senpi",
@@ -26,6 +26,7 @@ const hostToKernelMessageSchema = Type.Union([
26
26
  type: Type.Literal("init"),
27
27
  sessionId: Type.String({ minLength: 1 }),
28
28
  connection: connectionConfigSchema,
29
+ sessionEnv: Type.Optional(Type.Record(Type.String(), Type.String())),
29
30
  }),
30
31
  Type.Object({
31
32
  type: Type.Literal("run"),