@code-yeongyu/senpi 2026.9.7 → 2026.9.8
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.
- package/CHANGELOG.md +79 -0
- package/dist/core/agent-session.d.ts +40 -3
- package/dist/core/agent-session.d.ts.map +1 -1
- package/dist/core/agent-session.js +159 -40
- package/dist/core/agent-session.js.map +1 -1
- package/dist/core/compaction/compaction.d.ts.map +1 -1
- package/dist/core/compaction/compaction.js +2 -1
- package/dist/core/compaction/compaction.js.map +1 -1
- package/dist/core/dynamic-prompt/working-task.js +1 -1
- package/dist/core/dynamic-prompt/working-task.js.map +1 -1
- package/dist/core/extensions/builtin/cache-keepalive/index.d.ts.map +1 -1
- package/dist/core/extensions/builtin/cache-keepalive/index.js +4 -21
- package/dist/core/extensions/builtin/cache-keepalive/index.js.map +1 -1
- package/dist/core/extensions/builtin/claude-sdk-oauth/account-command.d.ts +2 -0
- package/dist/core/extensions/builtin/claude-sdk-oauth/account-command.d.ts.map +1 -1
- package/dist/core/extensions/builtin/claude-sdk-oauth/account-command.js +10 -25
- package/dist/core/extensions/builtin/claude-sdk-oauth/account-command.js.map +1 -1
- package/dist/core/extensions/builtin/claude-sdk-oauth/session-binding.d.ts.map +1 -1
- package/dist/core/extensions/builtin/claude-sdk-oauth/session-binding.js +36 -19
- package/dist/core/extensions/builtin/claude-sdk-oauth/session-binding.js.map +1 -1
- package/dist/core/extensions/builtin/claude-sdk-oauth/session-commit-boundary.d.ts.map +1 -1
- package/dist/core/extensions/builtin/claude-sdk-oauth/session-commit-boundary.js +21 -10
- package/dist/core/extensions/builtin/claude-sdk-oauth/session-commit-boundary.js.map +1 -1
- package/dist/core/extensions/builtin/claude-sdk-oauth/session-continuity.d.ts +4 -17
- package/dist/core/extensions/builtin/claude-sdk-oauth/session-continuity.d.ts.map +1 -1
- package/dist/core/extensions/builtin/claude-sdk-oauth/session-continuity.js +23 -33
- package/dist/core/extensions/builtin/claude-sdk-oauth/session-continuity.js.map +1 -1
- package/dist/core/extensions/builtin/claude-sdk-oauth/session-stream.d.ts.map +1 -1
- package/dist/core/extensions/builtin/claude-sdk-oauth/session-stream.js +1 -0
- package/dist/core/extensions/builtin/claude-sdk-oauth/session-stream.js.map +1 -1
- package/dist/core/extensions/builtin/claude-sdk-oauth/session-sync.d.ts.map +1 -1
- package/dist/core/extensions/builtin/claude-sdk-oauth/session-sync.js +5 -3
- package/dist/core/extensions/builtin/claude-sdk-oauth/session-sync.js.map +1 -1
- package/dist/core/extensions/builtin/claude-sdk-oauth/session-turn-attempt.d.ts.map +1 -1
- package/dist/core/extensions/builtin/claude-sdk-oauth/session-turn-attempt.js +8 -3
- package/dist/core/extensions/builtin/claude-sdk-oauth/session-turn-attempt.js.map +1 -1
- package/dist/core/extensions/builtin/claude-sdk-oauth/tools.d.ts +6 -5
- package/dist/core/extensions/builtin/claude-sdk-oauth/tools.d.ts.map +1 -1
- package/dist/core/extensions/builtin/claude-sdk-oauth/tools.js +6 -5
- package/dist/core/extensions/builtin/claude-sdk-oauth/tools.js.map +1 -1
- package/dist/core/extensions/builtin/compaction/circuit-breaker.d.ts +8 -0
- package/dist/core/extensions/builtin/compaction/circuit-breaker.d.ts.map +1 -1
- package/dist/core/extensions/builtin/compaction/circuit-breaker.js +8 -0
- package/dist/core/extensions/builtin/compaction/circuit-breaker.js.map +1 -1
- package/dist/core/extensions/builtin/compaction/deterministic-fallback.d.ts +8 -0
- package/dist/core/extensions/builtin/compaction/deterministic-fallback.d.ts.map +1 -1
- package/dist/core/extensions/builtin/compaction/deterministic-fallback.js +125 -20
- package/dist/core/extensions/builtin/compaction/deterministic-fallback.js.map +1 -1
- package/dist/core/extensions/builtin/compaction/fallback-failed-turn-normalization.d.ts +18 -0
- package/dist/core/extensions/builtin/compaction/fallback-failed-turn-normalization.d.ts.map +1 -0
- package/dist/core/extensions/builtin/compaction/fallback-failed-turn-normalization.js +20 -0
- package/dist/core/extensions/builtin/compaction/fallback-failed-turn-normalization.js.map +1 -0
- package/dist/core/extensions/builtin/compaction/index.d.ts.map +1 -1
- package/dist/core/extensions/builtin/compaction/index.js +54 -11
- package/dist/core/extensions/builtin/compaction/index.js.map +1 -1
- package/dist/core/extensions/builtin/compaction/lane-policy.d.ts +3 -0
- package/dist/core/extensions/builtin/compaction/lane-policy.d.ts.map +1 -1
- package/dist/core/extensions/builtin/compaction/lane-policy.js +47 -23
- package/dist/core/extensions/builtin/compaction/lane-policy.js.map +1 -1
- package/dist/core/extensions/builtin/goal/agent-end-continuation.d.ts.map +1 -1
- package/dist/core/extensions/builtin/goal/agent-end-continuation.js +11 -0
- package/dist/core/extensions/builtin/goal/agent-end-continuation.js.map +1 -1
- package/dist/core/extensions/builtin/goal/cache-warm-renderer.js +6 -4
- package/dist/core/extensions/builtin/goal/cache-warm-renderer.js.map +1 -1
- package/dist/core/extensions/builtin/goal/cache-warm.d.ts +25 -1
- package/dist/core/extensions/builtin/goal/cache-warm.d.ts.map +1 -1
- package/dist/core/extensions/builtin/goal/cache-warm.js +29 -10
- package/dist/core/extensions/builtin/goal/cache-warm.js.map +1 -1
- package/dist/core/extensions/builtin/goal/continuation.d.ts +3 -1
- package/dist/core/extensions/builtin/goal/continuation.d.ts.map +1 -1
- package/dist/core/extensions/builtin/goal/continuation.js +17 -2
- package/dist/core/extensions/builtin/goal/continuation.js.map +1 -1
- package/dist/core/extensions/builtin/goal/lifecycle-helpers.d.ts.map +1 -1
- package/dist/core/extensions/builtin/goal/lifecycle-helpers.js +1 -0
- package/dist/core/extensions/builtin/goal/lifecycle-helpers.js.map +1 -1
- package/dist/core/extensions/builtin/goal/monitor-continuation.d.ts +1 -1
- package/dist/core/extensions/builtin/goal/monitor-continuation.d.ts.map +1 -1
- package/dist/core/extensions/builtin/goal/monitor-continuation.js +8 -3
- package/dist/core/extensions/builtin/goal/monitor-continuation.js.map +1 -1
- package/dist/core/extensions/builtin/gpt-account.d.ts +5 -1
- package/dist/core/extensions/builtin/gpt-account.d.ts.map +1 -1
- package/dist/core/extensions/builtin/gpt-account.js +9 -26
- package/dist/core/extensions/builtin/gpt-account.js.map +1 -1
- package/dist/core/extensions/builtin/hooks/plugin-manifest.js +2 -2
- package/dist/core/extensions/builtin/hooks/plugin-manifest.js.map +1 -1
- package/dist/core/extensions/builtin/hooks/safety.js +2 -2
- package/dist/core/extensions/builtin/hooks/safety.js.map +1 -1
- package/dist/core/extensions/builtin/monitor-state-event.d.ts +18 -0
- package/dist/core/extensions/builtin/monitor-state-event.d.ts.map +1 -1
- package/dist/core/extensions/builtin/monitor-state-event.js +23 -0
- package/dist/core/extensions/builtin/monitor-state-event.js.map +1 -1
- package/dist/core/extensions/builtin/oauth-login-interaction.d.ts +24 -0
- package/dist/core/extensions/builtin/oauth-login-interaction.d.ts.map +1 -0
- package/dist/core/extensions/builtin/oauth-login-interaction.js +77 -0
- package/dist/core/extensions/builtin/oauth-login-interaction.js.map +1 -0
- package/dist/core/extensions/builtin/permission-system/external-dir.d.ts.map +1 -1
- package/dist/core/extensions/builtin/permission-system/external-dir.js +5 -51
- package/dist/core/extensions/builtin/permission-system/external-dir.js.map +1 -1
- package/dist/core/extensions/builtin/permission-system/parsers.d.ts.map +1 -1
- package/dist/core/extensions/builtin/permission-system/parsers.js +5 -7
- package/dist/core/extensions/builtin/permission-system/parsers.js.map +1 -1
- package/dist/core/extensions/builtin/prompt-preset/claude-fable-5-1.js +1 -1
- package/dist/core/extensions/builtin/prompt-preset/claude-fable-5-1.js.map +1 -1
- package/dist/core/extensions/builtin/prompt-preset/claude-fable-5.js +1 -1
- package/dist/core/extensions/builtin/prompt-preset/claude-fable-5.js.map +1 -1
- package/dist/core/extensions/builtin/prompt-preset/execution-tooling.d.ts +14 -7
- package/dist/core/extensions/builtin/prompt-preset/execution-tooling.d.ts.map +1 -1
- package/dist/core/extensions/builtin/prompt-preset/execution-tooling.js +34 -13
- package/dist/core/extensions/builtin/prompt-preset/execution-tooling.js.map +1 -1
- package/dist/core/extensions/builtin/prompt-preset/gpt-5.6.d.ts +6 -10
- package/dist/core/extensions/builtin/prompt-preset/gpt-5.6.d.ts.map +1 -1
- package/dist/core/extensions/builtin/prompt-preset/gpt-5.6.js +14 -9
- package/dist/core/extensions/builtin/prompt-preset/gpt-5.6.js.map +1 -1
- package/dist/core/extensions/builtin/prompt-preset/gpt-6-astra.d.ts +15 -15
- package/dist/core/extensions/builtin/prompt-preset/gpt-6-astra.d.ts.map +1 -1
- package/dist/core/extensions/builtin/prompt-preset/gpt-6-astra.js +49 -21
- package/dist/core/extensions/builtin/prompt-preset/gpt-6-astra.js.map +1 -1
- package/dist/core/extensions/builtin/prompt-preset/kimi-k3.js +1 -1
- package/dist/core/extensions/builtin/prompt-preset/kimi-k3.js.map +1 -1
- package/dist/core/extensions/builtin/service-tier.d.ts +2 -0
- package/dist/core/extensions/builtin/service-tier.d.ts.map +1 -1
- package/dist/core/extensions/builtin/service-tier.js +17 -1
- package/dist/core/extensions/builtin/service-tier.js.map +1 -1
- package/dist/core/extensions/builtin/terminal/durable-command.d.ts.map +1 -1
- package/dist/core/extensions/builtin/terminal/durable-command.js +3 -0
- package/dist/core/extensions/builtin/terminal/durable-command.js.map +1 -1
- package/dist/core/extensions/builtin/terminal/durable-file.d.ts.map +1 -1
- package/dist/core/extensions/builtin/terminal/durable-file.js +2 -0
- package/dist/core/extensions/builtin/terminal/durable-file.js.map +1 -1
- package/dist/core/extensions/builtin/terminal/extension.d.ts.map +1 -1
- package/dist/core/extensions/builtin/terminal/extension.js +14 -2
- package/dist/core/extensions/builtin/terminal/extension.js.map +1 -1
- package/dist/core/extensions/builtin/terminal/monitor-notify.d.ts.map +1 -1
- package/dist/core/extensions/builtin/terminal/monitor-notify.js +27 -5
- package/dist/core/extensions/builtin/terminal/monitor-notify.js.map +1 -1
- package/dist/core/extensions/builtin/terminal/monitor-registry.d.ts +17 -0
- package/dist/core/extensions/builtin/terminal/monitor-registry.d.ts.map +1 -1
- package/dist/core/extensions/builtin/terminal/monitor-registry.js +76 -14
- package/dist/core/extensions/builtin/terminal/monitor-registry.js.map +1 -1
- package/dist/core/extensions/builtin/terminal/notify.d.ts +2 -0
- package/dist/core/extensions/builtin/terminal/notify.d.ts.map +1 -1
- package/dist/core/extensions/builtin/terminal/notify.js +6 -1
- package/dist/core/extensions/builtin/terminal/notify.js.map +1 -1
- package/dist/core/extensions/builtin/terminal/session-bundle.d.ts +3 -1
- package/dist/core/extensions/builtin/terminal/session-bundle.d.ts.map +1 -1
- package/dist/core/extensions/builtin/terminal/session-bundle.js +13 -0
- package/dist/core/extensions/builtin/terminal/session-bundle.js.map +1 -1
- package/dist/core/extensions/builtin/terminal/shared.d.ts +13 -2
- package/dist/core/extensions/builtin/terminal/shared.d.ts.map +1 -1
- package/dist/core/extensions/builtin/terminal/shared.js +15 -2
- package/dist/core/extensions/builtin/terminal/shared.js.map +1 -1
- package/dist/core/extensions/builtin/terminal/tools/monitor.d.ts.map +1 -1
- package/dist/core/extensions/builtin/terminal/tools/monitor.js +8 -1
- package/dist/core/extensions/builtin/terminal/tools/monitor.js.map +1 -1
- package/dist/core/extensions/builtin/tool-search/index.d.ts.map +1 -1
- package/dist/core/extensions/builtin/tool-search/index.js +1 -0
- package/dist/core/extensions/builtin/tool-search/index.js.map +1 -1
- package/dist/core/extensions/builtin/tool-search/service.d.ts +4 -0
- package/dist/core/extensions/builtin/tool-search/service.d.ts.map +1 -1
- package/dist/core/extensions/builtin/tool-search/service.js +11 -0
- package/dist/core/extensions/builtin/tool-search/service.js.map +1 -1
- package/dist/core/extensions/runner.d.ts +1 -0
- package/dist/core/extensions/runner.d.ts.map +1 -1
- package/dist/core/extensions/runner.js +8 -1
- package/dist/core/extensions/runner.js.map +1 -1
- package/dist/core/extensions/types.d.ts +9 -0
- package/dist/core/extensions/types.d.ts.map +1 -1
- package/dist/core/extensions/types.js.map +1 -1
- package/dist/core/high-reasoning-warning.d.ts.map +1 -1
- package/dist/core/high-reasoning-warning.js +4 -6
- package/dist/core/high-reasoning-warning.js.map +1 -1
- package/dist/core/sdk.d.ts +1 -1
- package/dist/core/sdk.d.ts.map +1 -1
- package/dist/core/sdk.js +25 -6
- package/dist/core/sdk.js.map +1 -1
- package/dist/core/settings-manager.js +1 -1
- package/dist/core/settings-manager.js.map +1 -1
- package/dist/core/settings-shapes.js.map +1 -1
- package/dist/core/tools/bounded-realpath.d.ts +28 -0
- package/dist/core/tools/bounded-realpath.d.ts.map +1 -0
- package/dist/core/tools/bounded-realpath.js +53 -0
- package/dist/core/tools/bounded-realpath.js.map +1 -0
- package/dist/core/tools/file-mutation-queue.d.ts.map +1 -1
- package/dist/core/tools/file-mutation-queue.js +15 -13
- package/dist/core/tools/file-mutation-queue.js.map +1 -1
- package/dist/core/tools/filesystem-policy.d.ts +7 -0
- package/dist/core/tools/filesystem-policy.d.ts.map +1 -1
- package/dist/core/tools/filesystem-policy.js +25 -15
- package/dist/core/tools/filesystem-policy.js.map +1 -1
- package/dist/core/trust-manager.d.ts.map +1 -1
- package/dist/core/trust-manager.js +13 -2
- package/dist/core/trust-manager.js.map +1 -1
- package/dist/modes/interactive/interactive-mode.d.ts.map +1 -1
- package/dist/modes/interactive/interactive-mode.js +1 -0
- package/dist/modes/interactive/interactive-mode.js.map +1 -1
- package/dist/modes/interactive/tips/catalog/memory-tips.d.ts.map +1 -1
- package/dist/modes/interactive/tips/catalog/memory-tips.js +6 -0
- package/dist/modes/interactive/tips/catalog/memory-tips.js.map +1 -1
- package/dist/modes/interactive/tips/catalog/subagent-tips.d.ts.map +1 -1
- package/dist/modes/interactive/tips/catalog/subagent-tips.js +6 -0
- package/dist/modes/interactive/tips/catalog/subagent-tips.js.map +1 -1
- package/dist/modes/rpc/host-lifecycle.d.ts.map +1 -1
- package/dist/modes/rpc/host-lifecycle.js +31 -5
- package/dist/modes/rpc/host-lifecycle.js.map +1 -1
- package/dist/modes/rpc/host-watchdog.d.ts +11 -0
- package/dist/modes/rpc/host-watchdog.d.ts.map +1 -1
- package/dist/modes/rpc/host-watchdog.js +16 -2
- package/dist/modes/rpc/host-watchdog.js.map +1 -1
- package/dist/modes/rpc/multi-session-host.d.ts.map +1 -1
- package/dist/modes/rpc/multi-session-host.js +54 -16
- package/dist/modes/rpc/multi-session-host.js.map +1 -1
- package/dist/modes/rpc/socket-ownership.d.ts +46 -0
- package/dist/modes/rpc/socket-ownership.d.ts.map +1 -0
- package/dist/modes/rpc/socket-ownership.js +178 -0
- package/dist/modes/rpc/socket-ownership.js.map +1 -0
- package/dist/utils/fs-watch.d.ts +17 -0
- package/dist/utils/fs-watch.d.ts.map +1 -1
- package/dist/utils/fs-watch.js +38 -10
- package/dist/utils/fs-watch.js.map +1 -1
- package/dist/utils/paths.d.ts +33 -0
- package/dist/utils/paths.d.ts.map +1 -1
- package/dist/utils/paths.js +109 -3
- package/dist/utils/paths.js.map +1 -1
- package/docs/development.md +3 -2
- package/docs/providers.md +5 -1
- package/docs/settings.md +1 -0
- package/node_modules/@code-yeongyu/senpi-codemode/CHANGELOG.md +31 -0
- package/node_modules/@code-yeongyu/senpi-codemode/README.md +14 -1
- package/node_modules/@code-yeongyu/senpi-codemode/package.json +4 -4
- package/node_modules/@code-yeongyu/senpi-codemode/src/bridge/protocol.ts +1 -0
- package/node_modules/@code-yeongyu/senpi-codemode/src/extension/runtime-factory.ts +3 -0
- package/node_modules/@code-yeongyu/senpi-codemode/src/extension/session-manager.ts +7 -0
- package/node_modules/@code-yeongyu/senpi-codemode/src/kernels/AGENTS.md +7 -0
- package/node_modules/@code-yeongyu/senpi-codemode/src/kernels/jl/kernel.ts +4 -0
- package/node_modules/@code-yeongyu/senpi-codemode/src/kernels/js/kernel-contract.ts +3 -0
- package/node_modules/@code-yeongyu/senpi-codemode/src/kernels/js/worker-core.js +29 -0
- package/node_modules/@code-yeongyu/senpi-codemode/src/kernels/js/worker-indirect-eval.js +10 -6
- package/node_modules/@code-yeongyu/senpi-codemode/src/kernels/js/worker-shell-capture.d.ts +16 -0
- package/node_modules/@code-yeongyu/senpi-codemode/src/kernels/js/worker-shell-capture.js +44 -8
- package/node_modules/@code-yeongyu/senpi-codemode/src/kernels/js/worker-startup.ts +1 -0
- package/node_modules/@code-yeongyu/senpi-codemode/src/kernels/py/kernel-contract.ts +3 -0
- package/node_modules/@code-yeongyu/senpi-codemode/src/kernels/py/transport.ts +9 -1
- package/node_modules/@code-yeongyu/senpi-codemode/src/kernels/rb/kernel.ts +4 -0
- package/node_modules/@code-yeongyu/senpi-codemode/src/kernels/session-env.ts +56 -0
- package/node_modules/@code-yeongyu/senpi-codemode/src/kernels/shared/subprocess-contract.ts +3 -0
- package/node_modules/@code-yeongyu/senpi-codemode/src/kernels/shared/subprocess-kernel.ts +9 -1
- package/node_modules/@code-yeongyu/senpi-codemode/src/output/output-meta.ts +26 -2
- package/node_modules/@code-yeongyu/senpi-codemode/src/prompt/eval-prompt.ts +17 -24
- package/node_modules/@code-yeongyu/senpi-codemode/src/tool/image.ts +12 -3
- package/node_modules/@earendil-works/pi-agent-core/dist/agent-loop.d.ts.map +1 -1
- package/node_modules/@earendil-works/pi-agent-core/dist/agent-loop.js +2 -2
- package/node_modules/@earendil-works/pi-agent-core/dist/agent-loop.js.map +1 -1
- package/node_modules/@earendil-works/pi-agent-core/dist/assistant-terminal-state.d.ts +7 -0
- package/node_modules/@earendil-works/pi-agent-core/dist/assistant-terminal-state.d.ts.map +1 -1
- package/node_modules/@earendil-works/pi-agent-core/dist/assistant-terminal-state.js +19 -0
- package/node_modules/@earendil-works/pi-agent-core/dist/assistant-terminal-state.js.map +1 -1
- package/node_modules/@earendil-works/pi-agent-core/dist/empty-assistant-recovery.d.ts.map +1 -1
- package/node_modules/@earendil-works/pi-agent-core/dist/empty-assistant-recovery.js +24 -16
- package/node_modules/@earendil-works/pi-agent-core/dist/empty-assistant-recovery.js.map +1 -1
- package/node_modules/@earendil-works/pi-agent-core/dist/index.d.ts +1 -1
- package/node_modules/@earendil-works/pi-agent-core/dist/index.d.ts.map +1 -1
- package/node_modules/@earendil-works/pi-agent-core/dist/index.js +1 -1
- package/node_modules/@earendil-works/pi-agent-core/dist/index.js.map +1 -1
- package/node_modules/@earendil-works/pi-agent-core/package.json +3 -3
- package/node_modules/@earendil-works/pi-ai/dist/api/anthropic-messages.d.ts.map +1 -1
- package/node_modules/@earendil-works/pi-ai/dist/api/anthropic-messages.js +47 -178
- package/node_modules/@earendil-works/pi-ai/dist/api/anthropic-messages.js.map +1 -1
- package/node_modules/@earendil-works/pi-ai/dist/api/anthropic-tool-references.d.ts +3 -0
- package/node_modules/@earendil-works/pi-ai/dist/api/anthropic-tool-references.d.ts.map +1 -0
- package/node_modules/@earendil-works/pi-ai/dist/api/anthropic-tool-references.js +260 -0
- package/node_modules/@earendil-works/pi-ai/dist/api/anthropic-tool-references.js.map +1 -0
- package/node_modules/@earendil-works/pi-ai/dist/api/openai-codex-responses.d.ts.map +1 -1
- package/node_modules/@earendil-works/pi-ai/dist/api/openai-codex-responses.js +1 -0
- package/node_modules/@earendil-works/pi-ai/dist/api/openai-codex-responses.js.map +1 -1
- package/node_modules/@earendil-works/pi-ai/dist/api/openai-responses.d.ts.map +1 -1
- package/node_modules/@earendil-works/pi-ai/dist/api/openai-responses.js +1 -0
- package/node_modules/@earendil-works/pi-ai/dist/api/openai-responses.js.map +1 -1
- package/node_modules/@earendil-works/pi-ai/dist/auth/oauth/anthropic-callback-listener.d.ts +40 -0
- package/node_modules/@earendil-works/pi-ai/dist/auth/oauth/anthropic-callback-listener.d.ts.map +1 -0
- package/node_modules/@earendil-works/pi-ai/dist/auth/oauth/anthropic-callback-listener.js +138 -0
- package/node_modules/@earendil-works/pi-ai/dist/auth/oauth/anthropic-callback-listener.js.map +1 -0
- package/node_modules/@earendil-works/pi-ai/dist/auth/oauth/anthropic.d.ts +2 -3
- package/node_modules/@earendil-works/pi-ai/dist/auth/oauth/anthropic.d.ts.map +1 -1
- package/node_modules/@earendil-works/pi-ai/dist/auth/oauth/anthropic.js +42 -139
- package/node_modules/@earendil-works/pi-ai/dist/auth/oauth/anthropic.js.map +1 -1
- package/node_modules/@earendil-works/pi-ai/dist/auth/oauth/authorization-input.d.ts +6 -0
- package/node_modules/@earendil-works/pi-ai/dist/auth/oauth/authorization-input.d.ts.map +1 -0
- package/node_modules/@earendil-works/pi-ai/dist/auth/oauth/authorization-input.js +29 -0
- package/node_modules/@earendil-works/pi-ai/dist/auth/oauth/authorization-input.js.map +1 -0
- package/node_modules/@earendil-works/pi-ai/dist/auth/oauth/error-details.d.ts +3 -0
- package/node_modules/@earendil-works/pi-ai/dist/auth/oauth/error-details.d.ts.map +1 -0
- package/node_modules/@earendil-works/pi-ai/dist/auth/oauth/error-details.js +20 -0
- package/node_modules/@earendil-works/pi-ai/dist/auth/oauth/error-details.js.map +1 -0
- package/node_modules/@earendil-works/pi-ai/dist/providers/data/.manifest.json +1 -1
- package/node_modules/@earendil-works/pi-ai/dist/providers/data/amazon-bedrock.json +1 -1
- package/node_modules/@earendil-works/pi-ai/dist/providers/data/azure-openai-responses.json +1 -1
- package/node_modules/@earendil-works/pi-ai/dist/providers/data/fireworks.json +1 -1
- package/node_modules/@earendil-works/pi-ai/dist/providers/data/github-copilot.json +1 -1
- package/node_modules/@earendil-works/pi-ai/dist/providers/data/openai-codex.json +1 -1
- package/node_modules/@earendil-works/pi-ai/dist/providers/data/openai.json +1 -1
- package/node_modules/@earendil-works/pi-ai/dist/providers/data/opencode.json +1 -1
- package/node_modules/@earendil-works/pi-ai/dist/providers/data/opengateway.json +1 -1
- package/node_modules/@earendil-works/pi-ai/dist/providers/data/openrouter.json +1 -1
- package/node_modules/@earendil-works/pi-ai/dist/providers/data/vercel-ai-gateway.json +1 -1
- package/node_modules/@earendil-works/pi-ai/dist/tool-call-middleware/recovery-stream-terminal.d.ts.map +1 -1
- package/node_modules/@earendil-works/pi-ai/dist/tool-call-middleware/recovery-stream-terminal.js +16 -1
- package/node_modules/@earendil-works/pi-ai/dist/tool-call-middleware/recovery-stream-terminal.js.map +1 -1
- package/node_modules/@earendil-works/pi-ai/dist/types.d.ts +7 -0
- package/node_modules/@earendil-works/pi-ai/dist/types.d.ts.map +1 -1
- package/node_modules/@earendil-works/pi-ai/dist/types.js.map +1 -1
- package/node_modules/@earendil-works/pi-ai/dist/utils/overflow.d.ts +1 -1
- package/node_modules/@earendil-works/pi-ai/dist/utils/overflow.d.ts.map +1 -1
- package/node_modules/@earendil-works/pi-ai/dist/utils/overflow.js +14 -3
- package/node_modules/@earendil-works/pi-ai/dist/utils/overflow.js.map +1 -1
- package/node_modules/@earendil-works/pi-ai/dist/utils/server-fallback-receipt.d.ts +2 -1
- package/node_modules/@earendil-works/pi-ai/dist/utils/server-fallback-receipt.d.ts.map +1 -1
- package/node_modules/@earendil-works/pi-ai/dist/utils/server-fallback-receipt.js +16 -0
- package/node_modules/@earendil-works/pi-ai/dist/utils/server-fallback-receipt.js.map +1 -1
- package/node_modules/@earendil-works/pi-ai/package.json +2 -2
- package/node_modules/@earendil-works/pi-pty/package.json +1 -1
- package/node_modules/@earendil-works/pi-telemetry/package.json +1 -1
- package/node_modules/@earendil-works/pi-tui/package.json +1 -1
- package/package.json +7 -7
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"socket-ownership.js","sourceRoot":"","sources":["../../../src/modes/rpc/socket-ownership.ts"],"names":[],"mappings":"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","sourcesContent":["/**\n * Ownership-checked removal for Unix domain socket path entries.\n *\n * A socket path is a rendezvous name, not a capability: once a host unlinks\n * its entry, any other process may bind the same path. Teardown that removes\n * \"its\" socket by path alone can therefore delete a NEWER host's freshly\n * published entry after a takeover renamed a replacement over that path. The\n * startup path already refuses to touch a socket owned by a live server\n * (`prepareSocketPath`); these helpers give every teardown path the same rule:\n * capture the bound entry's filesystem identity (dev + ino) at listen time and\n * unlink only while the path still stat-matches that identity.\n */\nimport { mkdir, readFile, rename, stat, unlink, writeFile } from \"node:fs/promises\";\nimport { dirname } from \"node:path\";\n\n/** Filesystem identity of one bound socket path entry. */\nexport interface SocketFileIdentity {\n\treadonly dev: number;\n\treadonly ino: number;\n}\n\n/**\n * Name of the sidecar, inside the lifecycle supervisor's private scratch\n * directory, that records which public socket entry that supervisor bound. The\n * directory is per-supervisor, so a replacement supervisor's token can never be\n * mistaken for the old one's.\n */\nexport const PUBLIC_SOCKET_IDENTITY_FILE = \"public-socket.owner\";\n\n/** Default bound on waiting for a supervisor to publish its ownership token. */\nexport const SOCKET_IDENTITY_WAIT_MS = 30_000;\n\nfunction sameSocketIdentity(a: SocketFileIdentity, b: SocketFileIdentity): boolean {\n\treturn a.dev === b.dev && a.ino === b.ino;\n}\n\n/** Reads the current identity of a socket path entry; `undefined` once absent. */\nexport async function statSocketIdentity(socketPath: string): Promise<SocketFileIdentity | undefined> {\n\ttry {\n\t\tconst { dev, ino } = await stat(socketPath);\n\t\treturn { dev, ino };\n\t} catch (cause) {\n\t\tif (isNodeErrorCode(cause, \"ENOENT\")) return undefined;\n\t\tthrow cause;\n\t}\n}\n\n/** Records a bound socket identity for teardown paths that cannot hold it in memory. */\nexport async function writeSocketIdentityFile(path: string, identity: SocketFileIdentity): Promise<void> {\n\tawait mkdir(dirname(path), { recursive: true, mode: 0o700 });\n\tconst tmpPath = `${path}.${process.pid}.tmp`;\n\tawait writeFile(tmpPath, `${JSON.stringify(identity)}\\n`, { mode: 0o600 });\n\tawait rename(tmpPath, path);\n}\n\nexport async function readSocketIdentityFile(path: string): Promise<SocketFileIdentity | undefined> {\n\tlet raw: string;\n\ttry {\n\t\traw = await readFile(path, \"utf8\");\n\t} catch (cause) {\n\t\tif (isNodeErrorCode(cause, \"ENOENT\")) return undefined;\n\t\tthrow cause;\n\t}\n\tlet parsed: unknown;\n\ttry {\n\t\tparsed = JSON.parse(raw);\n\t} catch {\n\t\t// Truncated or in-progress JSON is not a token: callers wait or leave the path.\n\t\treturn undefined;\n\t}\n\tif (\n\t\ttypeof parsed !== \"object\" ||\n\t\tparsed === null ||\n\t\ttypeof (parsed as { dev?: unknown }).dev !== \"number\" ||\n\t\ttypeof (parsed as { ino?: unknown }).ino !== \"number\"\n\t) {\n\t\treturn undefined;\n\t}\n\treturn { dev: (parsed as { dev: number }).dev, ino: (parsed as { ino: number }).ino };\n}\n\n/**\n * Waits, bounded, for an identity file to appear. The lifecycle supervisor\n * publishes its public-socket token after this host's internal listener is\n * ready, so supervised hosts poll briefly instead of assuming ordering.\n */\nexport async function waitForSocketIdentityFile(\n\tpath: string,\n\ttimeoutMs: number = SOCKET_IDENTITY_WAIT_MS,\n\tintervalMs = 25,\n): Promise<SocketFileIdentity | undefined> {\n\tconst deadline = Date.now() + timeoutMs;\n\tfor (;;) {\n\t\tconst identity = await readSocketIdentityFile(path);\n\t\tif (identity !== undefined) return identity;\n\t\tif (Date.now() >= deadline) return undefined;\n\t\tawait delayUnrefed(intervalMs);\n\t}\n}\n\n/**\n * Unlinks a socket path only while it still refers to the entry `identity`\n * describes. An absent path is nothing to do; a mismatched identity means a\n * newer host owns the name now and is left alone; an unknown identity can\n * never be proven ours, so the path is left and the decision is logged.\n */\nexport async function unlinkOwnedSocket(\n\tsocketPath: string,\n\tidentity: SocketFileIdentity | undefined,\n\tlog: (message: string) => void,\n): Promise<void> {\n\tif (process.platform === \"win32\" || socketPath.startsWith(\"\\0\")) return;\n\tlet current: SocketFileIdentity | undefined;\n\ttry {\n\t\tcurrent = await statSocketIdentity(socketPath);\n\t} catch (cause) {\n\t\tlog(`socket ${socketPath} ownership could not be verified (${errorMessage(cause)}); leaving it`);\n\t\treturn;\n\t}\n\tif (current === undefined) return;\n\tif (identity === undefined) {\n\t\tlog(`socket path ${socketPath} ownership unknown; leaving it`);\n\t\treturn;\n\t}\n\tif (!sameSocketIdentity(current, identity)) {\n\t\tlog(`socket path ${socketPath} now owned by another host; leaving it`);\n\t\treturn;\n\t}\n\ttry {\n\t\tawait unlink(socketPath);\n\t} catch (cause) {\n\t\tif (!isNodeErrorCode(cause, \"ENOENT\")) {\n\t\t\tlog(`socket ${socketPath} removal failed (${errorMessage(cause)})`);\n\t\t}\n\t}\n}\n\n/**\n * Runs `close` with the socket path's current entry shielded from libuv's\n * close-time unlink.\n *\n * libuv unlinks the NAME a pipe was bound to when its listening handle\n * closes - even when a takeover has renamed a DIFFERENT socket over that\n * name, so an ordinary `server.close()` silently deletes the newer host's\n * freshly published entry. Moving the current entry aside for the duration\n * of the close (libuv's unlink then hits ENOENT) and restoring it afterwards\n * keeps the filesystem exactly as it was; ownership-checked removal via\n * `unlinkOwnedSocket` decides afterwards what may actually be deleted.\n */\nexport async function shieldSocketDuringClose(socketPath: string, close: () => Promise<void>): Promise<void> {\n\tif (process.platform === \"win32\" || socketPath.startsWith(\"\\0\")) return close();\n\tconst shieldPath = `${socketPath}.shield-${process.pid}`;\n\tlet shielded = false;\n\ttry {\n\t\tif ((await statSocketIdentity(socketPath)) !== undefined) {\n\t\t\tawait rename(socketPath, shieldPath);\n\t\t\tshielded = true;\n\t\t}\n\t} catch {\n\t\t// Without a shield the close degrades to today's behavior; the\n\t\t// ownership-checked unlink still runs afterwards.\n\t}\n\ttry {\n\t\tawait close();\n\t} finally {\n\t\tif (shielded) {\n\t\t\ttry {\n\t\t\t\tawait rename(shieldPath, socketPath);\n\t\t\t} catch {\n\t\t\t\t// Best effort: a failed restore leaves the entry under the shield\n\t\t\t\t// name; startup probes treat a dead entry as replaceable.\n\t\t\t}\n\t\t}\n\t}\n}\n\nfunction delayUnrefed(ms: number): Promise<void> {\n\treturn new Promise((resolve) => {\n\t\tconst timer = setTimeout(resolve, ms);\n\t\ttimer.unref?.();\n\t});\n}\n\nfunction errorMessage(cause: unknown): string {\n\treturn cause instanceof Error ? cause.message : String(cause);\n}\n\nfunction isNodeErrorCode(cause: unknown, code: string): boolean {\n\treturn cause instanceof Error && \"code\" in cause && cause.code === code;\n}\n"]}
|
package/dist/utils/fs-watch.d.ts
CHANGED
|
@@ -1,5 +1,22 @@
|
|
|
1
1
|
import { type FSWatcher, type WatchListener, type WatchOptions } from "node:fs";
|
|
2
2
|
export declare const FS_WATCH_RETRY_DELAY_MS = 5000;
|
|
3
3
|
export declare function closeWatcher(watcher: FSWatcher | null | undefined): void;
|
|
4
|
+
/**
|
|
5
|
+
* The path to hand to `fs.watch`. On Windows libuv aborts the whole process when a directory
|
|
6
|
+
* watch is created through a non-canonical (8.3 short-name) path (issue #1229), so the canonical
|
|
7
|
+
* form is looked up there. Elsewhere the caller's path is used as-is: POSIX realpath opens every
|
|
8
|
+
* directory it resolves, which blocks forever on a wedged autofs trigger, and fs.watch does not
|
|
9
|
+
* need the canonical form on those platforms.
|
|
10
|
+
*/
|
|
11
|
+
export declare function canonicalWatchPath(path: string): string;
|
|
12
|
+
/**
|
|
13
|
+
* Prove that a directory can be opened before a synchronous `fs.watch` touches it. The watcher
|
|
14
|
+
* opens the directory on the calling thread; on a wedged mount (a macOS autofs trigger whose
|
|
15
|
+
* automounter never answers) that open never returns and would block the host main thread. This
|
|
16
|
+
* probe performs the same open on the async pool, so a caller can bound it with a deadline. It
|
|
17
|
+
* rejects with the open error (for example EACCES on an execute-only directory, which fs.watch
|
|
18
|
+
* would refuse as well).
|
|
19
|
+
*/
|
|
20
|
+
export declare function probeDirectoryOpenable(directory: string): Promise<void>;
|
|
4
21
|
export declare function watchWithErrorHandler(path: string, listener: WatchListener<string>, onError: () => void, options?: WatchOptions): FSWatcher | null;
|
|
5
22
|
//# sourceMappingURL=fs-watch.d.ts.map
|
|
@@ -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;
|
|
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"}
|
package/dist/utils/fs-watch.js
CHANGED
|
@@ -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
|
-
|
|
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;
|
|
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"]}
|
package/dist/utils/paths.d.ts
CHANGED
|
@@ -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"}
|
package/dist/utils/paths.js
CHANGED
|
@@ -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 });
|
package/dist/utils/paths.js.map
CHANGED
|
@@ -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"]}
|
package/docs/development.md
CHANGED
|
@@ -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
|
|
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
|
@@ -36,6 +36,10 @@ Use `/logout` to clear credentials. Tokens are stored in `~/.senpi/agent/auth.js
|
|
|
36
36
|
|
|
37
37
|
Anthropic subscription auth is active for Claude Pro/Max accounts. Third-party harness usage draws from [extra usage](https://claude.ai/settings/usage) and is billed per token, not against Claude plan limits.
|
|
38
38
|
|
|
39
|
+
- Run `/login anthropic` to open the browser flow. The login listens on loopback port 53692 when it is free and on an ephemeral port otherwise, so a second session on the same machine (another TUI, an RPC host, an abandoned login) can log in at the same time; the auth URL always names the port that is actually listening.
|
|
40
|
+
- If the browser lands on a page saying the login belongs to a different session or an earlier attempt, that page's address was sent to another login's listener: paste the full address from the address bar into the session whose prompt is still waiting, or run the login again from that session.
|
|
41
|
+
- A login that receives neither the browser callback nor a pasted redirect URL for 10 minutes fails with a timeout and releases its port; run `/login anthropic` again.
|
|
42
|
+
|
|
39
43
|
### Claude SDK OAuth
|
|
40
44
|
|
|
41
45
|
The `claude-sdk-oauth` provider routes LLM calls through the official [Claude Agent SDK](https://www.npmjs.com/package/@anthropic-ai/claude-agent-sdk) - it spawns the real Claude Code engine - while senpi executes every tool itself. Subscription usage flows through Anthropic's official Claude Code surface.
|
|
@@ -101,7 +105,7 @@ If your Claude Pro/Max subscription usage through `claude-sdk-oauth` feels unexp
|
|
|
101
105
|
| 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
106
|
|
|
103
107
|
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.**
|
|
108
|
+
5. **Goal-monitor timing.** While at least one wake source is live, the goal monitor arms one periodic backstop of `promptCache.goalBackstopMaxSeconds` (default 270, so 4m30s: the 5-minute Anthropic prompt-cache TTL minus the 30s safety buffer; hard-capped at 1h). The normal resumption is event-driven: a monitor event or a finished background job starts a turn, and when the last wake source drains, exactly one continuation is queued about a second later. The backstop is the floor underneath that: a wake source can be misconfigured (a filter that never matches, a stream that never ends), so the goal re-checks at least once per backstop interval instead of waiting an hour on a source that will never deliver. Each backstop firing is a full main-model turn that re-sends the accumulated context; at the default it lands inside the prompt-cache TTL, so the cache stays warm. Raise `goalBackstopMaxSeconds` (up to 3600) to trade that re-check frequency for cost on a wait you trust, and use `promptCache.keepAlive` (opt-in, off by default) to keep the cache warm across such a long backstop. Cache-warm notices show which warm iteration you are on.
|
|
105
109
|
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
110
|
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
111
|
|
package/docs/settings.md
CHANGED
|
@@ -364,6 +364,7 @@ unknown (e.g. Google models) or caching is off, no budget applies and timeout be
|
|
|
364
364
|
|---------|------|---------|-------------|
|
|
365
365
|
| `promptCache.cacheAwareTimeouts` | boolean | `true` | Cap foreground tool waits at the model's prompt-cache TTL minus the safety buffer; `false` restores the fixed legacy ceilings |
|
|
366
366
|
| `promptCache.safetyBufferSeconds` | number | `30` | Headroom subtracted from the cache TTL (a 5m TTL yields a 270s ceiling). If it consumes the whole TTL, no budget applies |
|
|
367
|
+
| `promptCache.goalBackstopMaxSeconds` | number | `270` | Longest a goal parked on live wake sources (terminal monitors, background sessions, detached `eval` cells, task children) waits before it re-checks with a full turn, clamped to 1..3600. The default lands inside the 5m cache TTL; raise it toward `3570` to trade re-check frequency for cost on a wait you trust |
|
|
367
368
|
|
|
368
369
|
A foreground `bash` command still running at the budget is handed to a live background session
|
|
369
370
|
instead of being killed; its explicit `timeout` remains the kill deadline. See
|
|
@@ -12,6 +12,37 @@
|
|
|
12
12
|
|
|
13
13
|
### Removed
|
|
14
14
|
|
|
15
|
+
## [2026.9.8] - 2026-09-08
|
|
16
|
+
|
|
17
|
+
### Breaking Changes
|
|
18
|
+
|
|
19
|
+
### Added
|
|
20
|
+
|
|
21
|
+
### Changed
|
|
22
|
+
|
|
23
|
+
- The eval tool description teaches cell mechanics only (batch independent calls, real code, failures kept verbatim, truncated output re-read) and drops the "default execution surface / never a chain / distilled facts only" wording; routing lives in the model's prompt preset.
|
|
24
|
+
|
|
25
|
+
### Fixed
|
|
26
|
+
|
|
27
|
+
### Removed
|
|
28
|
+
|
|
29
|
+
## [2026.9.7-2] - 2026-09-07
|
|
30
|
+
|
|
31
|
+
### Breaking Changes
|
|
32
|
+
|
|
33
|
+
### Added
|
|
34
|
+
|
|
35
|
+
### Changed
|
|
36
|
+
|
|
37
|
+
### Fixed
|
|
38
|
+
|
|
39
|
+
- 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.
|
|
40
|
+
- 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.
|
|
41
|
+
- 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).
|
|
42
|
+
- 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.
|
|
43
|
+
|
|
44
|
+
### Removed
|
|
45
|
+
|
|
15
46
|
## [2026.9.7] - 2026-09-07
|
|
16
47
|
|
|
17
48
|
### 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. |
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@code-yeongyu/senpi-codemode",
|
|
3
|
-
"version": "2026.9.
|
|
3
|
+
"version": "2026.9.8",
|
|
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.
|
|
34
|
+
"@earendil-works/pi-ai": "npm:@code-yeongyu/senpi-ai@2026.9.8",
|
|
35
35
|
"typebox": "1.3.18"
|
|
36
36
|
},
|
|
37
37
|
"peerDependencies": {
|
|
38
|
-
"@code-yeongyu/senpi": "2026.9.
|
|
38
|
+
"@code-yeongyu/senpi": "2026.9.8"
|
|
39
39
|
},
|
|
40
40
|
"devDependencies": {
|
|
41
|
-
"@code-yeongyu/senpi": "2026.9.
|
|
41
|
+
"@code-yeongyu/senpi": "2026.9.8"
|
|
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"),
|
|
@@ -13,6 +13,7 @@ import {
|
|
|
13
13
|
getInterpreterAvailability,
|
|
14
14
|
type InterpreterAvailability,
|
|
15
15
|
} from "../interpreters/detect.ts";
|
|
16
|
+
import { sessionEnvironmentFrom } from "../kernels/session-env.ts";
|
|
16
17
|
import { resolveSessionArtifactsDir } from "../output/streaming-output.ts";
|
|
17
18
|
import type { EnabledEvalLanguages, EvalLanguage, EvalRuntimes } from "../tool/types.ts";
|
|
18
19
|
import { jsRuntimeInfo, runtimesFromAvailability } from "./runtime-info.ts";
|
|
@@ -66,11 +67,13 @@ export async function createRuntime(
|
|
|
66
67
|
const executeTool = createExecuteTool(pi, activeTools);
|
|
67
68
|
const create = options.createSessionManager ?? createCodemodeSessionManager;
|
|
68
69
|
const sessionId = sessionIdFrom(event);
|
|
70
|
+
const sessionEnv = sessionEnvironmentFrom(ctx);
|
|
69
71
|
const configuredPoolWidth = settings.parallelPoolWidth;
|
|
70
72
|
const parallelPoolWidth = Number.isFinite(configuredPoolWidth) ? Math.max(1, Math.trunc(configuredPoolWidth)) : 1;
|
|
71
73
|
const manager = await create({
|
|
72
74
|
sessionId,
|
|
73
75
|
cwd: ctx.cwd,
|
|
76
|
+
sessionEnv,
|
|
74
77
|
settings,
|
|
75
78
|
availability,
|
|
76
79
|
artifactsDir: artifacts.dir,
|
|
@@ -11,6 +11,7 @@ import { JuliaKernel } from "../kernels/jl/kernel.ts";
|
|
|
11
11
|
import { JavaScriptKernel } from "../kernels/js/context-manager.ts";
|
|
12
12
|
import { PythonKernel } from "../kernels/py/kernel.ts";
|
|
13
13
|
import { RubyKernel } from "../kernels/rb/kernel.ts";
|
|
14
|
+
import type { SessionEnvironment } from "../kernels/session-env.ts";
|
|
14
15
|
import { marshalToolResult } from "../tool/image.ts";
|
|
15
16
|
import type { EvalKernel, EvalKernelManager, EvalLanguage, ExecuteTool } from "../tool/types.ts";
|
|
16
17
|
|
|
@@ -40,6 +41,8 @@ export interface CreateCodemodeSessionManagerOptions {
|
|
|
40
41
|
readonly localRoots?: Readonly<Record<string, string>>;
|
|
41
42
|
/** Session-adjacent directory used for persisted eval artifacts. */
|
|
42
43
|
readonly artifactsDir?: string;
|
|
44
|
+
/** Per-session PI_* values exposed to every kernel and the children it spawns. */
|
|
45
|
+
readonly sessionEnv?: SessionEnvironment;
|
|
43
46
|
readonly executeTool: ExecuteTool;
|
|
44
47
|
readonly listTools?: () => readonly EvalSchemaToolInfo[];
|
|
45
48
|
readonly complete: (request: CompletionRequest, ctx: ExtensionContext) => Promise<CompletionResult>;
|
|
@@ -212,6 +215,7 @@ class DefaultCodemodeSessionManager implements CodemodeSessionManager {
|
|
|
212
215
|
cwd: this.#options.cwd,
|
|
213
216
|
parallelPoolWidth,
|
|
214
217
|
onMessage,
|
|
218
|
+
...(this.#options.sessionEnv ? { sessionEnv: this.#options.sessionEnv } : {}),
|
|
215
219
|
...(localRoots ? { localRoots: { ...localRoots } } : {}),
|
|
216
220
|
...(this.#options.artifactsDir ? { artifactsDir: this.#options.artifactsDir } : {}),
|
|
217
221
|
});
|
|
@@ -230,6 +234,7 @@ class DefaultCodemodeSessionManager implements CodemodeSessionManager {
|
|
|
230
234
|
interpreterPath: detected.path,
|
|
231
235
|
sessionId: this.#options.sessionId,
|
|
232
236
|
cwd: this.#options.cwd,
|
|
237
|
+
...(this.#options.sessionEnv ? { sessionEnv: this.#options.sessionEnv } : {}),
|
|
233
238
|
connection,
|
|
234
239
|
onMessage,
|
|
235
240
|
});
|
|
@@ -239,6 +244,7 @@ class DefaultCodemodeSessionManager implements CodemodeSessionManager {
|
|
|
239
244
|
command: detected.path,
|
|
240
245
|
sessionId: this.#options.sessionId,
|
|
241
246
|
cwd: this.#options.cwd,
|
|
247
|
+
...(this.#options.sessionEnv ? { sessionEnv: this.#options.sessionEnv } : {}),
|
|
242
248
|
connection,
|
|
243
249
|
onMessage,
|
|
244
250
|
});
|
|
@@ -247,6 +253,7 @@ class DefaultCodemodeSessionManager implements CodemodeSessionManager {
|
|
|
247
253
|
command: detected.path,
|
|
248
254
|
sessionId: this.#options.sessionId,
|
|
249
255
|
cwd: this.#options.cwd,
|
|
256
|
+
...(this.#options.sessionEnv ? { sessionEnv: this.#options.sessionEnv } : {}),
|
|
250
257
|
connection,
|
|
251
258
|
onMessage,
|
|
252
259
|
});
|