@code-yeongyu/senpi 2026.9.25 → 2026.9.27-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.
- package/CHANGELOG.md +58 -0
- package/dist/bun-runtime.d.ts +18 -2
- package/dist/bun-runtime.d.ts.map +1 -1
- package/dist/bun-runtime.js +60 -8
- package/dist/bun-runtime.js.map +1 -1
- package/dist/bundle/chunks/{app-server-command-RB6QUPRI.js → app-server-command-ATWHNOOO.js} +1 -1
- package/dist/bundle/chunks/bun-extension-importer-R4HXF3GD.js +14 -0
- package/dist/bundle/chunks/chunk-2L26KPZ6.js +8 -0
- package/dist/bundle/chunks/{chunk-3WBSBDBX.js → chunk-3CC5ZOBV.js} +1 -1
- package/dist/bundle/chunks/{chunk-OZCB6BP3.js → chunk-67BC7VZV.js} +4 -4
- package/dist/bundle/chunks/{chunk-JJ3NJZBD.js → chunk-6SLQ5HFO.js} +48 -45
- package/dist/bundle/chunks/{chunk-MKAM7IRH.js → chunk-6W2DB2GC.js} +1 -1
- package/dist/bundle/chunks/{chunk-BSOJHK4W.js → chunk-7FXXRPB3.js} +2 -1
- package/dist/bundle/chunks/{chunk-Y54V65MS.js → chunk-HPY47AVX.js} +1 -1
- package/dist/bundle/chunks/{chunk-CLZVNQW3.js → chunk-LPFZGWG2.js} +1 -1
- package/dist/bundle/chunks/chunk-W3D3ZYDS.js +2 -0
- package/dist/bundle/chunks/{chunk-UBPMXBOJ.js → chunk-XKLR7C5B.js} +1 -1
- package/dist/bundle/chunks/{cli-main-KO53KYDN.js → cli-main-HOM6UH3H.js} +1 -1
- package/dist/bundle/chunks/{help-fast-path-ZGKPSDFO.js → help-fast-path-YGYHMV2W.js} +1 -1
- package/dist/bundle/chunks/{host-command-BMHJ4YUD.js → host-command-XXSAFWBW.js} +1 -1
- package/dist/bundle/chunks/{host-lifecycle-2GZAGSGW.js → host-lifecycle-GOMGJ25O.js} +1 -1
- package/dist/bundle/chunks/{interactive-host-runtime-YI75GFD5.js → interactive-host-runtime-IYCZWJGX.js} +1 -1
- package/dist/bundle/chunks/{interactive-mode-ABDNLJSI.js → interactive-mode-ZMO7YVYU.js} +1 -1
- package/dist/bundle/chunks/{multi-session-host-JV3MNWXZ.js → multi-session-host-CE3W6COI.js} +1 -1
- package/dist/bundle/chunks/{package-manager-cli-ZYYAVCXU.js → package-manager-cli-2PHRL2SX.js} +1 -1
- package/dist/bundle/chunks/{rpc-mode-Q2OUJURB.js → rpc-mode-WCETTMFV.js} +1 -1
- package/dist/bundle/chunks/session-worker.js +135 -131
- package/dist/bundle/cli.js +1 -1
- package/dist/bundle/index.js +1 -1
- package/dist/bundle/rpc-entry.js +1 -1
- package/dist/cli/args.d.ts +1 -0
- package/dist/cli/args.d.ts.map +1 -1
- package/dist/cli/args.js +4 -0
- package/dist/cli/args.js.map +1 -1
- package/dist/cli/cross-project-session.d.ts +22 -0
- package/dist/cli/cross-project-session.d.ts.map +1 -0
- package/dist/cli/cross-project-session.js +38 -0
- package/dist/cli/cross-project-session.js.map +1 -0
- package/dist/core/agent-session.d.ts.map +1 -1
- package/dist/core/agent-session.js +19 -5
- package/dist/core/agent-session.js.map +1 -1
- package/dist/core/dynamic-prompt/handoff.js +1 -1
- package/dist/core/dynamic-prompt/handoff.js.map +1 -1
- package/dist/core/extensions/builtin/index.d.ts.map +1 -1
- package/dist/core/extensions/builtin/index.js +2 -0
- package/dist/core/extensions/builtin/index.js.map +1 -1
- package/dist/core/extensions/builtin/permission-system/index.d.ts.map +1 -1
- package/dist/core/extensions/builtin/permission-system/index.js +5 -2
- package/dist/core/extensions/builtin/permission-system/index.js.map +1 -1
- package/dist/core/extensions/builtin/permission-system/parsers.d.ts +5 -0
- package/dist/core/extensions/builtin/permission-system/parsers.d.ts.map +1 -1
- package/dist/core/extensions/builtin/permission-system/parsers.js +13 -0
- package/dist/core/extensions/builtin/permission-system/parsers.js.map +1 -1
- package/dist/core/extensions/builtin/repository-identity.d.ts +6 -0
- package/dist/core/extensions/builtin/repository-identity.d.ts.map +1 -0
- package/dist/core/extensions/builtin/repository-identity.js +33 -0
- package/dist/core/extensions/builtin/repository-identity.js.map +1 -0
- package/dist/core/extensions/builtin/todotools/state.d.ts +1 -1
- package/dist/core/extensions/builtin/todotools/state.d.ts.map +1 -1
- package/dist/core/extensions/builtin/todotools/state.js +1 -1
- package/dist/core/extensions/builtin/todotools/state.js.map +1 -1
- package/dist/core/extensions/builtin/ttsr/detectors/repetitive-turns.d.ts.map +1 -1
- package/dist/core/extensions/builtin/ttsr/detectors/repetitive-turns.js +3 -1
- package/dist/core/extensions/builtin/ttsr/detectors/repetitive-turns.js.map +1 -1
- package/dist/core/extensions/bun-extension-importer.d.ts.map +1 -1
- package/dist/core/extensions/bun-extension-importer.js +10 -4
- package/dist/core/extensions/bun-extension-importer.js.map +1 -1
- package/dist/core/extensions/bun-extension-registry.js +6 -5
- package/dist/core/extensions/bun-extension-registry.js.map +1 -1
- package/dist/core/extensions/index.d.ts +1 -1
- package/dist/core/extensions/index.d.ts.map +1 -1
- package/dist/core/extensions/index.js.map +1 -1
- package/dist/core/extensions/kernel-prelude.d.ts +13 -0
- package/dist/core/extensions/kernel-prelude.d.ts.map +1 -0
- package/dist/core/extensions/kernel-prelude.js +38 -0
- package/dist/core/extensions/kernel-prelude.js.map +1 -0
- package/dist/core/extensions/types.d.ts +44 -2
- package/dist/core/extensions/types.d.ts.map +1 -1
- package/dist/core/extensions/types.js.map +1 -1
- package/dist/core/messages.d.ts.map +1 -1
- package/dist/core/messages.js +4 -2
- package/dist/core/messages.js.map +1 -1
- package/dist/core/model-resolver.js +1 -1
- package/dist/core/model-resolver.js.map +1 -1
- package/dist/core/provider-rejected-images.d.ts +18 -0
- package/dist/core/provider-rejected-images.d.ts.map +1 -0
- package/dist/core/provider-rejected-images.js +107 -0
- package/dist/core/provider-rejected-images.js.map +1 -0
- package/dist/core/repository-identity.d.ts +18 -0
- package/dist/core/repository-identity.d.ts.map +1 -0
- package/dist/core/repository-identity.js +75 -0
- package/dist/core/repository-identity.js.map +1 -0
- package/dist/core/session-rebind.d.ts +17 -0
- package/dist/core/session-rebind.d.ts.map +1 -0
- package/dist/core/session-rebind.js +111 -0
- package/dist/core/session-rebind.js.map +1 -0
- package/dist/core/session-title-generator.d.ts.map +1 -1
- package/dist/core/session-title-generator.js +21 -3
- package/dist/core/session-title-generator.js.map +1 -1
- package/dist/core/session-title-reasoning.d.ts +14 -0
- package/dist/core/session-title-reasoning.d.ts.map +1 -0
- package/dist/core/session-title-reasoning.js +31 -0
- package/dist/core/session-title-reasoning.js.map +1 -0
- package/dist/core/tools/path-utils.d.ts.map +1 -1
- package/dist/core/tools/path-utils.js +13 -0
- package/dist/core/tools/path-utils.js.map +1 -1
- package/dist/index.d.ts +1 -1
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js.map +1 -1
- package/dist/main.d.ts +6 -0
- package/dist/main.d.ts.map +1 -1
- package/dist/main.js +100 -19
- package/dist/main.js.map +1 -1
- package/dist/modes/interactive/interactive-mode.d.ts.map +1 -1
- package/dist/modes/interactive/interactive-mode.js +2 -0
- package/dist/modes/interactive/interactive-mode.js.map +1 -1
- package/dist/modes/interactive/runtime-notice-presenter.d.ts +8 -0
- package/dist/modes/interactive/runtime-notice-presenter.d.ts.map +1 -0
- package/dist/modes/interactive/runtime-notice-presenter.js +69 -0
- package/dist/modes/interactive/runtime-notice-presenter.js.map +1 -0
- package/dist/modes/interactive/runtime-notice.d.ts +50 -0
- package/dist/modes/interactive/runtime-notice.d.ts.map +1 -0
- package/dist/modes/interactive/runtime-notice.js +104 -0
- package/dist/modes/interactive/runtime-notice.js.map +1 -0
- package/docs/environment-variables.md +2 -0
- package/docs/extensions.md +27 -1
- package/docs/quickstart.md +7 -1
- package/docs/sessions.md +9 -0
- package/docs/usage.md +1 -0
- package/node_modules/@code-yeongyu/senpi-codemode/CHANGELOG.md +38 -0
- package/node_modules/@code-yeongyu/senpi-codemode/README.md +7 -0
- package/node_modules/@code-yeongyu/senpi-codemode/package.json +4 -4
- package/node_modules/@code-yeongyu/senpi-codemode/src/bridge/protocol.ts +9 -0
- package/node_modules/@code-yeongyu/senpi-codemode/src/bridges/schema-bridge.ts +2 -0
- package/node_modules/@code-yeongyu/senpi-codemode/src/extension/kernel-preludes.ts +30 -0
- package/node_modules/@code-yeongyu/senpi-codemode/src/index.ts +15 -1
- package/node_modules/@code-yeongyu/senpi-codemode/src/kernels/js/context-manager.ts +1 -1
- package/node_modules/@code-yeongyu/senpi-codemode/src/kernels/js/local-module-loader.ts +16 -2
- package/node_modules/@code-yeongyu/senpi-codemode/src/kernels/py/kernel.ts +6 -1
- package/node_modules/@code-yeongyu/senpi-codemode/src/kernels/py/prelude.py +15 -2
- package/node_modules/@code-yeongyu/senpi-codemode/src/kernels/py/transport.ts +7 -1
- package/node_modules/@code-yeongyu/senpi-codemode/src/kernels/shared/kernel-prelude-plan.ts +19 -0
- package/node_modules/@code-yeongyu/senpi-codemode/src/prompt/eval-prompt-template.ts +2 -1
- package/node_modules/@code-yeongyu/senpi-codemode/src/prompt/eval-prompt.ts +4 -0
- package/node_modules/@code-yeongyu/senpi-codemode/src/tool/eval-tool-options.ts +11 -1
- package/node_modules/@code-yeongyu/senpi-codemode/src/tool/eval-tool.ts +1 -0
- package/node_modules/@code-yeongyu/senpi-codemode/src/tool/run-eval-cell.ts +1 -0
- package/node_modules/@code-yeongyu/senpi-codemode/src/tool/types.ts +3 -1
- package/node_modules/@earendil-works/pi-agent-core/package.json +3 -3
- package/node_modules/@earendil-works/pi-ai/dist/providers/data/.manifest.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/opencode-go.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/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/package.json +2 -2
- package/node_modules/@earendil-works/pi-pty/native/prebuilds/darwin-arm64/senpi_pty.darwin-arm64.node +0 -0
- 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 -22
- package/dist/bundle/chunks/bun-extension-importer-H4IJCWWQ.js +0 -14
- package/dist/bundle/chunks/chunk-34HMVB33.js +0 -2
- package/dist/bundle/chunks/chunk-SGYEOIOJ.js +0 -8
- package/node_modules/@code-yeongyu/senpi-desktop-engine/README.md +0 -31
- package/node_modules/@code-yeongyu/senpi-desktop-engine/dist/handshake.d.ts +0 -38
- package/node_modules/@code-yeongyu/senpi-desktop-engine/dist/handshake.d.ts.map +0 -1
- package/node_modules/@code-yeongyu/senpi-desktop-engine/dist/handshake.js +0 -117
- package/node_modules/@code-yeongyu/senpi-desktop-engine/dist/handshake.js.map +0 -1
- package/node_modules/@code-yeongyu/senpi-desktop-engine/dist/index.d.ts +0 -3
- package/node_modules/@code-yeongyu/senpi-desktop-engine/dist/index.d.ts.map +0 -1
- package/node_modules/@code-yeongyu/senpi-desktop-engine/dist/index.js +0 -3
- package/node_modules/@code-yeongyu/senpi-desktop-engine/dist/index.js.map +0 -1
- package/node_modules/@code-yeongyu/senpi-desktop-engine/dist/locator.d.ts +0 -42
- package/node_modules/@code-yeongyu/senpi-desktop-engine/dist/locator.d.ts.map +0 -1
- package/node_modules/@code-yeongyu/senpi-desktop-engine/dist/locator.js +0 -79
- package/node_modules/@code-yeongyu/senpi-desktop-engine/dist/locator.js.map +0 -1
- package/node_modules/@code-yeongyu/senpi-desktop-engine/native/check-prebuild-fresh.mjs +0 -160
- package/node_modules/@code-yeongyu/senpi-desktop-engine/native/index.d.ts +0 -20
- package/node_modules/@code-yeongyu/senpi-desktop-engine/native/index.js +0 -23
- package/node_modules/@code-yeongyu/senpi-desktop-engine/native/prebuilds/darwin-arm64/senpi-desktop-engine +0 -0
- package/node_modules/@code-yeongyu/senpi-desktop-engine/package.json +0 -53
- package/node_modules/@code-yeongyu/senpi-desktop-prelude/README.md +0 -20
- package/node_modules/@code-yeongyu/senpi-desktop-prelude/dist/index.d.ts +0 -2
- package/node_modules/@code-yeongyu/senpi-desktop-prelude/dist/index.d.ts.map +0 -1
- package/node_modules/@code-yeongyu/senpi-desktop-prelude/dist/index.js +0 -2
- package/node_modules/@code-yeongyu/senpi-desktop-prelude/dist/index.js.map +0 -1
- package/node_modules/@code-yeongyu/senpi-desktop-prelude/package.json +0 -44
- package/node_modules/@code-yeongyu/senpi-desktop-protocol/README.md +0 -20
- package/node_modules/@code-yeongyu/senpi-desktop-protocol/dist/audit.d.ts +0 -38
- package/node_modules/@code-yeongyu/senpi-desktop-protocol/dist/audit.d.ts.map +0 -1
- package/node_modules/@code-yeongyu/senpi-desktop-protocol/dist/audit.js +0 -2
- package/node_modules/@code-yeongyu/senpi-desktop-protocol/dist/audit.js.map +0 -1
- package/node_modules/@code-yeongyu/senpi-desktop-protocol/dist/call.d.ts +0 -28
- package/node_modules/@code-yeongyu/senpi-desktop-protocol/dist/call.d.ts.map +0 -1
- package/node_modules/@code-yeongyu/senpi-desktop-protocol/dist/call.js +0 -102
- package/node_modules/@code-yeongyu/senpi-desktop-protocol/dist/call.js.map +0 -1
- package/node_modules/@code-yeongyu/senpi-desktop-protocol/dist/engine-schema.generated.d.ts +0 -14
- package/node_modules/@code-yeongyu/senpi-desktop-protocol/dist/engine-schema.generated.d.ts.map +0 -1
- package/node_modules/@code-yeongyu/senpi-desktop-protocol/dist/engine-schema.generated.js +0 -71
- package/node_modules/@code-yeongyu/senpi-desktop-protocol/dist/engine-schema.generated.js.map +0 -1
- package/node_modules/@code-yeongyu/senpi-desktop-protocol/dist/index.d.ts +0 -7
- package/node_modules/@code-yeongyu/senpi-desktop-protocol/dist/index.d.ts.map +0 -1
- package/node_modules/@code-yeongyu/senpi-desktop-protocol/dist/index.js +0 -4
- package/node_modules/@code-yeongyu/senpi-desktop-protocol/dist/index.js.map +0 -1
- package/node_modules/@code-yeongyu/senpi-desktop-protocol/dist/json-rpc.d.ts +0 -66
- package/node_modules/@code-yeongyu/senpi-desktop-protocol/dist/json-rpc.d.ts.map +0 -1
- package/node_modules/@code-yeongyu/senpi-desktop-protocol/dist/json-rpc.js +0 -8
- package/node_modules/@code-yeongyu/senpi-desktop-protocol/dist/json-rpc.js.map +0 -1
- package/node_modules/@code-yeongyu/senpi-desktop-protocol/dist/session.d.ts +0 -44
- package/node_modules/@code-yeongyu/senpi-desktop-protocol/dist/session.d.ts.map +0 -1
- package/node_modules/@code-yeongyu/senpi-desktop-protocol/dist/session.js +0 -2
- package/node_modules/@code-yeongyu/senpi-desktop-protocol/dist/session.js.map +0 -1
- package/node_modules/@code-yeongyu/senpi-desktop-protocol/dist/wire.d.ts +0 -59
- package/node_modules/@code-yeongyu/senpi-desktop-protocol/dist/wire.d.ts.map +0 -1
- package/node_modules/@code-yeongyu/senpi-desktop-protocol/dist/wire.js +0 -2
- package/node_modules/@code-yeongyu/senpi-desktop-protocol/dist/wire.js.map +0 -1
- package/node_modules/@code-yeongyu/senpi-desktop-protocol/package.json +0 -45
- package/node_modules/@code-yeongyu/senpi-desktop-service/README.md +0 -27
- package/node_modules/@code-yeongyu/senpi-desktop-service/dist/index.d.ts +0 -2
- package/node_modules/@code-yeongyu/senpi-desktop-service/dist/index.d.ts.map +0 -1
- package/node_modules/@code-yeongyu/senpi-desktop-service/dist/index.js +0 -2
- package/node_modules/@code-yeongyu/senpi-desktop-service/dist/index.js.map +0 -1
- package/node_modules/@code-yeongyu/senpi-desktop-service/package.json +0 -44
- package/node_modules/@code-yeongyu/senpi-desktop-tool/README.md +0 -20
- package/node_modules/@code-yeongyu/senpi-desktop-tool/dist/index.d.ts +0 -2
- package/node_modules/@code-yeongyu/senpi-desktop-tool/dist/index.d.ts.map +0 -1
- package/node_modules/@code-yeongyu/senpi-desktop-tool/dist/index.js +0 -2
- package/node_modules/@code-yeongyu/senpi-desktop-tool/dist/index.js.map +0 -1
- package/node_modules/@code-yeongyu/senpi-desktop-tool/package.json +0 -44
package/CHANGELOG.md
CHANGED
|
@@ -12,6 +12,64 @@
|
|
|
12
12
|
|
|
13
13
|
### Removed
|
|
14
14
|
|
|
15
|
+
## [2026.9.27-2] - 2026-09-27
|
|
16
|
+
|
|
17
|
+
### Breaking Changes
|
|
18
|
+
|
|
19
|
+
### Added
|
|
20
|
+
|
|
21
|
+
- Extension tools can declare `kernelPrelude` (JavaScript/Python snippets, one documentation line, exported names) to add globals to eval kernels while the tool is active; `getAllTools()` projects it and rejects exports that shadow built-in kernel helpers. ([#2128](https://github.com/code-yeongyu/senpi/issues/2128))
|
|
22
|
+
- Extension tools can declare `permissionParser(input, cwd)`, so permission rules can gate their calls by tier (for example `my_tool:exec=deny`). A built-in parser for the same tool name always wins.
|
|
23
|
+
- New `tool_activated` extension event, fired when the active tool set gains tools through `pi.setActiveTools()`, `tool_search` promotion, or a lazy by-name activation.
|
|
24
|
+
|
|
25
|
+
- Sessions follow a moved or re-cloned repository: when `--session <id>` (or a `--resume` pick) finds a session filed under another path of the same git repository, senpi shows both paths and offers to move it here, keeping its id, history, goal, loops, and monitors; `--rebind <path|id>` does it without asking, and a different repository keeps the fork prompt. Sessions now record their repository (root commit and `origin` remote) so this works after the old path is gone. ([#2181](https://github.com/code-yeongyu/senpi/issues/2181))
|
|
26
|
+
|
|
27
|
+
### Changed
|
|
28
|
+
|
|
29
|
+
### Fixed
|
|
30
|
+
|
|
31
|
+
- The built-in Fireworks default is `accounts/fireworks/models/kimi-k3`; the previous default `kimi-k2p6` is no longer in the Fireworks catalog. ([#2175](https://github.com/code-yeongyu/senpi/issues/2175))
|
|
32
|
+
|
|
33
|
+
- Answering `y` or `yes` to "Fork this session into current directory?" (shown when `--session` finds the session in another project, for example after moving the repository) now forks the session instead of printing `Aborted.`; closing the input without an answer still declines. ([#2180](https://github.com/code-yeongyu/senpi/issues/2180), [#1042](https://github.com/code-yeongyu/senpi/pull/1042) by [@beengineer500](https://github.com/beengineer500))
|
|
34
|
+
|
|
35
|
+
### Removed
|
|
36
|
+
|
|
37
|
+
## [2026.9.27] - 2026-09-27
|
|
38
|
+
|
|
39
|
+
### Breaking Changes
|
|
40
|
+
|
|
41
|
+
### Added
|
|
42
|
+
|
|
43
|
+
- The interactive UI shows a one-time "Running on Node.js" notice (once per version) when senpi or OmO Native starts on Node.js. It says how to install or upgrade Bun and gives the clean reinstall command for the package manager that installed it, for example `npm uninstall -g @code-yeongyu/senpi && bun add -g --ignore-scripts @code-yeongyu/senpi`. Set `SENPI_RUNTIME=node` (`OMO_RUNTIME=node` for OmO Native) to stay on Node.js without it, or `SENPI_SKIP_RUNTIME_NOTICE=1` to hide it. ([#2157](https://github.com/code-yeongyu/senpi/issues/2157))
|
|
44
|
+
|
|
45
|
+
### Changed
|
|
46
|
+
|
|
47
|
+
- npm, pnpm, Yarn, and npx installs now run on Bun when Bun 1.4.0 or newer is installed, like `bun add -g` installs already did. `SENPI_RUNTIME=node` keeps them on Node.js. ([#2157](https://github.com/code-yeongyu/senpi/issues/2157))
|
|
48
|
+
|
|
49
|
+
### Fixed
|
|
50
|
+
|
|
51
|
+
- A session recovers after the provider rejects an image. It used to fail every later turn, text-only ones included, with `Codex error: The image data you provided does not represent a valid image`. The rejected image is now replaced in later requests by a note naming its file, the error says which file was left out, and the next message goes through, also after a restart. ([#2170](https://github.com/code-yeongyu/senpi/issues/2170))
|
|
52
|
+
- The read tool finds a file whose path is wrapped in quotes, such as the `"C:\Users\<user>\Pictures\Screenshots\aaa.png"` that Windows Explorer's "Copy as path" produces, instead of failing with ENOENT. ([#2170](https://github.com/code-yeongyu/senpi/issues/2170))
|
|
53
|
+
- On Bun 1.3.x, extensions now load when they declare a package directory as their entry (`"pi": { "extensions": ["."] }`) or depend on a package that requires a JSON file, such as ajv. Both used to fail with `Cannot find module 'file:/…'` or `ENOENT reading "file:/…"` even though Bun itself loads them. ([#2164](https://github.com/code-yeongyu/senpi/issues/2164))
|
|
54
|
+
- Automatic session titles work on models that cannot turn reasoning off, such as `openrouter/meta/muse-spark-1.3-contributor` and Z.ai GLM 5.3. The title request now asks for the model's lowest supported reasoning level and no longer fails with `Reasoning is mandatory for this endpoint and cannot be disabled. (HTTP 400)`. A title that still fails is recorded in `logs/session.log` instead of showing a runtime error. ([#2163](https://github.com/code-yeongyu/senpi/issues/2163))
|
|
55
|
+
|
|
56
|
+
### Removed
|
|
57
|
+
|
|
58
|
+
## [2026.9.26] - 2026-09-26
|
|
59
|
+
|
|
60
|
+
### Breaking Changes
|
|
61
|
+
|
|
62
|
+
### Added
|
|
63
|
+
|
|
64
|
+
### Changed
|
|
65
|
+
|
|
66
|
+
### Fixed
|
|
67
|
+
|
|
68
|
+
- `bun add @code-yeongyu/senpi` works again. 2026.9.25 declared five unpublished desktop packages as dependencies, which npm installs from the bundle but bun looks up on the registry and fails; the package no longer ships them until a released feature uses them. ([#2141](https://github.com/code-yeongyu/senpi/issues/2141))
|
|
69
|
+
|
|
70
|
+
- A turn that keeps repeating is caught again when its text contains `Ask:` outside a handoff block; only the restated request inside a handoff block is ignored. ([#2143](https://github.com/code-yeongyu/senpi/issues/2143))
|
|
71
|
+
### Removed
|
|
72
|
+
|
|
15
73
|
## [2026.9.25] - 2026-09-25
|
|
16
74
|
|
|
17
75
|
### Breaking Changes
|
package/dist/bun-runtime.d.ts
CHANGED
|
@@ -9,9 +9,13 @@ export interface BunRuntimeOptions {
|
|
|
9
9
|
readonly platform: NodeJS.Platform;
|
|
10
10
|
readonly exists: (path: string) => boolean;
|
|
11
11
|
readonly realpath: (path: string) => string;
|
|
12
|
+
/** `<bun> --version` output, or `undefined` when the binary cannot be run. */
|
|
13
|
+
readonly bunVersion: (bunPath: string) => string | undefined;
|
|
12
14
|
}
|
|
15
|
+
/** Oldest Bun release a non-Bun install is handed to; older releases miss runtime APIs senpi uses. */
|
|
16
|
+
export declare const MIN_BUN_VERSION = "1.4.0";
|
|
13
17
|
/** Why the process stayed on its current runtime. Surfaced for tests and diagnostics. */
|
|
14
|
-
export type StayReason = "already-bun" | "runtime-pinned-node" | "inspector" | "bun-not-found" | "not-bun-install";
|
|
18
|
+
export type StayReason = "already-bun" | "runtime-pinned-node" | "inspector" | "bun-not-found" | "bun-too-old" | "not-bun-install";
|
|
15
19
|
export type BunReexecDecision = {
|
|
16
20
|
readonly action: "stay";
|
|
17
21
|
readonly reason: StayReason;
|
|
@@ -36,6 +40,14 @@ export interface BunReexecInput {
|
|
|
36
40
|
* is compared both as configured and as resolved, so a symlinked install root still matches.
|
|
37
41
|
*/
|
|
38
42
|
export declare function isUnderBunGlobalTree(scriptRealPath: string, options: BunRuntimeOptions): boolean;
|
|
43
|
+
/**
|
|
44
|
+
* Report whether the executed script belongs to an installed package (npm, pnpm, yarn, npx, or a
|
|
45
|
+
* project-local install), as opposed to a source checkout. Only installed packages are handed to a
|
|
46
|
+
* discovered Bun: a checkout run on Node is a developer's deliberate choice.
|
|
47
|
+
*/
|
|
48
|
+
export declare function isInstalledPackageScript(scriptRealPath: string): boolean;
|
|
49
|
+
/** True when `version` (e.g. `1.4.2`, `1.5.0-canary.3`) is at least {@link MIN_BUN_VERSION}. */
|
|
50
|
+
export declare function bunVersionSatisfies(version: string | undefined): boolean;
|
|
39
51
|
/**
|
|
40
52
|
* Locate a Bun binary: the configured install root first, then the default `~/.bun` root, then
|
|
41
53
|
* a PATH scan. Returns `undefined` when Bun is not installed — a missing Bun is never an error,
|
|
@@ -50,9 +62,13 @@ export declare function findBunBinary(options: BunRuntimeOptions): string | unde
|
|
|
50
62
|
* 3. an inherited Inspector option — a debugger session owns a Node socket, keep it;
|
|
51
63
|
* 4. `SENPI_RUNTIME=bun` with Bun installed — explicit opt-in;
|
|
52
64
|
* 5. the script lives in Bun's global install tree with Bun installed — the install implies it;
|
|
53
|
-
* 6.
|
|
65
|
+
* 6. any other installed package with Bun >= {@link MIN_BUN_VERSION} — a machine that has a
|
|
66
|
+
* current Bun runs senpi on it, whichever package manager installed it;
|
|
67
|
+
* 7. otherwise stay (source checkouts, no Bun, or an older Bun).
|
|
54
68
|
*/
|
|
55
69
|
export declare function resolveBunReexec(input: BunReexecInput): BunReexecDecision;
|
|
70
|
+
/** Run `<bun> --version`; any failure (missing, not executable, timeout) means "unknown". */
|
|
71
|
+
export declare function readBunVersion(bunPath: string): string | undefined;
|
|
56
72
|
/** Build the default, process-backed options for the real CLI entry point. */
|
|
57
73
|
export declare function processBunRuntimeOptions(exists: (path: string) => boolean, realpath: (path: string) => string): BunRuntimeOptions;
|
|
58
74
|
//# sourceMappingURL=bun-runtime.d.ts.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"bun-runtime.d.ts","sourceRoot":"","sources":["../src/bun-runtime.ts"],"names":[],"mappings":"
|
|
1
|
+
{"version":3,"file":"bun-runtime.d.ts","sourceRoot":"","sources":["../src/bun-runtime.ts"],"names":[],"mappings":"AAiBA,8DAA8D;AAC9D,MAAM,MAAM,WAAW,GAAG,MAAM,GAAG,KAAK,CAAC;AAEzC,kFAAkF;AAClF,eAAO,MAAM,eAAe,kBAAkB,CAAC;AAE/C,sFAAsF;AACtF,MAAM,WAAW,iBAAiB;IACjC,QAAQ,CAAC,GAAG,EAAE,MAAM,CAAC,UAAU,CAAC;IAChC,QAAQ,CAAC,OAAO,EAAE,MAAM,CAAC;IACzB,QAAQ,CAAC,QAAQ,EAAE,MAAM,CAAC,QAAQ,CAAC;IACnC,QAAQ,CAAC,MAAM,EAAE,CAAC,IAAI,EAAE,MAAM,KAAK,OAAO,CAAC;IAC3C,QAAQ,CAAC,QAAQ,EAAE,CAAC,IAAI,EAAE,MAAM,KAAK,MAAM,CAAC;IAC5C,8EAA8E;IAC9E,QAAQ,CAAC,UAAU,EAAE,CAAC,OAAO,EAAE,MAAM,KAAK,MAAM,GAAG,SAAS,CAAC;CAC7D;AAED,sGAAsG;AACtG,eAAO,MAAM,eAAe,UAAU,CAAC;AAEvC,yFAAyF;AACzF,MAAM,MAAM,UAAU,GACnB,aAAa,GACb,qBAAqB,GACrB,WAAW,GACX,eAAe,GACf,aAAa,GACb,iBAAiB,CAAC;AAErB,MAAM,MAAM,iBAAiB,GAC1B;IAAE,QAAQ,CAAC,MAAM,EAAE,MAAM,CAAC;IAAC,QAAQ,CAAC,MAAM,EAAE,UAAU,CAAA;CAAE,GACxD;IAAE,QAAQ,CAAC,MAAM,EAAE,QAAQ,CAAC;IAAC,QAAQ,CAAC,OAAO,EAAE,MAAM,CAAA;CAAE,CAAC;AAE3D,MAAM,WAAW,cAAc;IAC9B,iFAAiF;IACjF,QAAQ,CAAC,cAAc,EAAE,MAAM,CAAC;IAChC,2EAA2E;IAC3E,QAAQ,CAAC,QAAQ,EAAE,QAAQ,CAAC,MAAM,CAAC,MAAM,EAAE,MAAM,GAAG,SAAS,CAAC,CAAC,CAAC;IAChE,4DAA4D;IAC5D,QAAQ,CAAC,2BAA2B,EAAE,OAAO,CAAC;IAC9C,QAAQ,CAAC,OAAO,EAAE,iBAAiB,CAAC;CACpC;AA0CD;;;;;;GAMG;AACH,wBAAgB,oBAAoB,CAAC,cAAc,EAAE,MAAM,EAAE,OAAO,EAAE,iBAAiB,GAAG,OAAO,CAShG;AAED;;;;GAIG;AACH,wBAAgB,wBAAwB,CAAC,cAAc,EAAE,MAAM,GAAG,OAAO,CAExE;AAED,gGAAgG;AAChG,wBAAgB,mBAAmB,CAAC,OAAO,EAAE,MAAM,GAAG,SAAS,GAAG,OAAO,CASxE;AAoBD;;;;GAIG;AACH,wBAAgB,aAAa,CAAC,OAAO,EAAE,iBAAiB,GAAG,MAAM,GAAG,SAAS,CA4B5E;AAUD;;;;;;;;;;;GAWG;AACH,wBAAgB,gBAAgB,CAAC,KAAK,EAAE,cAAc,GAAG,iBAAiB,CAwBzE;AAED,6FAA6F;AAC7F,wBAAgB,cAAc,CAAC,OAAO,EAAE,MAAM,GAAG,MAAM,GAAG,SAAS,CAUlE;AAED,8EAA8E;AAC9E,wBAAgB,wBAAwB,CACvC,MAAM,EAAE,CAAC,IAAI,EAAE,MAAM,KAAK,OAAO,EACjC,QAAQ,EAAE,CAAC,IAAI,EAAE,MAAM,KAAK,MAAM,GAChC,iBAAiB,CASnB"}
|
package/dist/bun-runtime.js
CHANGED
|
@@ -1,19 +1,23 @@
|
|
|
1
1
|
/**
|
|
2
|
-
* Runtime selection for
|
|
2
|
+
* Runtime selection for an installed CLI.
|
|
3
3
|
*
|
|
4
4
|
* `bun install -g` writes `~/.bun/bin/senpi` as a symlink into
|
|
5
5
|
* `<BUN_ROOT>/install/global/node_modules/...`, but the launcher still starts the script with
|
|
6
|
-
* whatever interpreter resolves the shebang — Node.
|
|
7
|
-
* Node,
|
|
8
|
-
* facts only, whether the process should
|
|
6
|
+
* whatever interpreter resolves the shebang — Node. npm, pnpm, yarn, and npx installs start on
|
|
7
|
+
* Node too, even on a machine that has Bun. Either way the user silently loses Bun's startup time
|
|
8
|
+
* and its runtime APIs. This module decides, from injected facts only, whether the process should
|
|
9
|
+
* hand itself to Bun.
|
|
9
10
|
*
|
|
10
11
|
* Everything here is pure and injectable so the decision can be tested without touching the
|
|
11
12
|
* host PATH or the real `~/.bun` tree; `cli.ts` owns the single impure call site.
|
|
12
13
|
*/
|
|
14
|
+
import { spawnSync } from "node:child_process";
|
|
13
15
|
import { homedir as osHomedir } from "node:os";
|
|
14
16
|
import { posix, win32 } from "node:path";
|
|
15
17
|
/** Environment variable that pins the runtime explicitly, bypassing detection. */
|
|
16
18
|
export const RUNTIME_ENV_VAR = "SENPI_RUNTIME";
|
|
19
|
+
/** Oldest Bun release a non-Bun install is handed to; older releases miss runtime APIs senpi uses. */
|
|
20
|
+
export const MIN_BUN_VERSION = "1.4.0";
|
|
17
21
|
/** Path separators differ per platform; compare on a single normalized form. */
|
|
18
22
|
function normalize(path) {
|
|
19
23
|
return path.replaceAll("\\", "/");
|
|
@@ -68,6 +72,27 @@ export function isUnderBunGlobalTree(scriptRealPath, options) {
|
|
|
68
72
|
}
|
|
69
73
|
return false;
|
|
70
74
|
}
|
|
75
|
+
/**
|
|
76
|
+
* Report whether the executed script belongs to an installed package (npm, pnpm, yarn, npx, or a
|
|
77
|
+
* project-local install), as opposed to a source checkout. Only installed packages are handed to a
|
|
78
|
+
* discovered Bun: a checkout run on Node is a developer's deliberate choice.
|
|
79
|
+
*/
|
|
80
|
+
export function isInstalledPackageScript(scriptRealPath) {
|
|
81
|
+
return normalize(scriptRealPath).includes("/node_modules/");
|
|
82
|
+
}
|
|
83
|
+
/** True when `version` (e.g. `1.4.2`, `1.5.0-canary.3`) is at least {@link MIN_BUN_VERSION}. */
|
|
84
|
+
export function bunVersionSatisfies(version) {
|
|
85
|
+
const actual = /^v?(\d+)\.(\d+)\.(\d+)/.exec(version?.trim() ?? "");
|
|
86
|
+
const minimum = /^(\d+)\.(\d+)\.(\d+)/.exec(MIN_BUN_VERSION);
|
|
87
|
+
if (!actual || !minimum)
|
|
88
|
+
return false;
|
|
89
|
+
for (let index = 1; index <= 3; index++) {
|
|
90
|
+
const difference = Number(actual[index]) - Number(minimum[index]);
|
|
91
|
+
if (difference !== 0)
|
|
92
|
+
return difference > 0;
|
|
93
|
+
}
|
|
94
|
+
return true;
|
|
95
|
+
}
|
|
71
96
|
/** Executable name for the platform, e.g. `bun` or `bun.exe`. */
|
|
72
97
|
function bunExecutableName(options) {
|
|
73
98
|
return options.platform === "win32" ? "bun.exe" : "bun";
|
|
@@ -133,7 +158,9 @@ function readRuntimePin(options) {
|
|
|
133
158
|
* 3. an inherited Inspector option — a debugger session owns a Node socket, keep it;
|
|
134
159
|
* 4. `SENPI_RUNTIME=bun` with Bun installed — explicit opt-in;
|
|
135
160
|
* 5. the script lives in Bun's global install tree with Bun installed — the install implies it;
|
|
136
|
-
* 6.
|
|
161
|
+
* 6. any other installed package with Bun >= {@link MIN_BUN_VERSION} — a machine that has a
|
|
162
|
+
* current Bun runs senpi on it, whichever package manager installed it;
|
|
163
|
+
* 7. otherwise stay (source checkouts, no Bun, or an older Bun).
|
|
137
164
|
*/
|
|
138
165
|
export function resolveBunReexec(input) {
|
|
139
166
|
if (input.versions.bun !== undefined) {
|
|
@@ -146,18 +173,43 @@ export function resolveBunReexec(input) {
|
|
|
146
173
|
if (input.hasInheritedInspectorOption) {
|
|
147
174
|
return { action: "stay", reason: "inspector" };
|
|
148
175
|
}
|
|
149
|
-
const
|
|
150
|
-
if (!
|
|
176
|
+
const trustsInstalledBun = pinned === "bun" || isUnderBunGlobalTree(input.scriptRealPath, input.options);
|
|
177
|
+
if (!trustsInstalledBun && !isInstalledPackageScript(input.scriptRealPath)) {
|
|
151
178
|
return { action: "stay", reason: "not-bun-install" };
|
|
152
179
|
}
|
|
153
180
|
const bunPath = findBunBinary(input.options);
|
|
154
181
|
if (bunPath === undefined) {
|
|
155
182
|
return { action: "stay", reason: "bun-not-found" };
|
|
156
183
|
}
|
|
184
|
+
// A pin or a bun-global install already proved its Bun; any other install probes it once.
|
|
185
|
+
if (!trustsInstalledBun && !bunVersionSatisfies(input.options.bunVersion(bunPath))) {
|
|
186
|
+
return { action: "stay", reason: "bun-too-old" };
|
|
187
|
+
}
|
|
157
188
|
return { action: "reexec", bunPath };
|
|
158
189
|
}
|
|
190
|
+
/** Run `<bun> --version`; any failure (missing, not executable, timeout) means "unknown". */
|
|
191
|
+
export function readBunVersion(bunPath) {
|
|
192
|
+
try {
|
|
193
|
+
const result = spawnSync(bunPath, ["--version"], { encoding: "utf8", timeout: 5_000, windowsHide: true });
|
|
194
|
+
if (result.status !== 0)
|
|
195
|
+
return undefined;
|
|
196
|
+
const version = result.stdout.trim();
|
|
197
|
+
return version === "" ? undefined : version;
|
|
198
|
+
}
|
|
199
|
+
catch {
|
|
200
|
+
// Node throws synchronously for a batch file spawned without a shell (spawn EINVAL).
|
|
201
|
+
return undefined;
|
|
202
|
+
}
|
|
203
|
+
}
|
|
159
204
|
/** Build the default, process-backed options for the real CLI entry point. */
|
|
160
205
|
export function processBunRuntimeOptions(exists, realpath) {
|
|
161
|
-
return {
|
|
206
|
+
return {
|
|
207
|
+
env: process.env,
|
|
208
|
+
homedir: osHomedir(),
|
|
209
|
+
platform: process.platform,
|
|
210
|
+
exists,
|
|
211
|
+
realpath,
|
|
212
|
+
bunVersion: readBunVersion,
|
|
213
|
+
};
|
|
162
214
|
}
|
|
163
215
|
//# sourceMappingURL=bun-runtime.js.map
|
package/dist/bun-runtime.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"bun-runtime.js","sourceRoot":"","sources":["../src/bun-runtime.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;GAWG;AACH,OAAO,EAAE,OAAO,IAAI,SAAS,EAAE,MAAM,SAAS,CAAC;AAC/C,OAAO,EAAE,KAAK,EAAE,KAAK,EAAE,MAAM,WAAW,CAAC;AAKzC,kFAAkF;AAClF,MAAM,CAAC,MAAM,eAAe,GAAG,eAAe,CAAC;AA4B/C,gFAAgF;AAChF,SAAS,SAAS,CAAC,IAAY;IAC9B,OAAO,IAAI,CAAC,UAAU,CAAC,IAAI,EAAE,GAAG,CAAC,CAAC;AACnC,CAAC;AAED;;;;GAIG;AACH,SAAS,KAAK,CAAC,OAA0B;IACxC,OAAO,OAAO,CAAC,QAAQ,KAAK,OAAO,CAAC,CAAC,CAAC,KAAK,CAAC,CAAC,CAAC,KAAK,CAAC;AACrD,CAAC;AAED;;;GAGG;AACH,SAAS,OAAO,CAAC,OAA0B;IAC1C,MAAM,UAAU,GAAG,OAAO,CAAC,GAAG,CAAC,WAAW,CAAC;IAC3C,IAAI,UAAU,KAAK,SAAS,IAAI,UAAU,CAAC,IAAI,EAAE,KAAK,EAAE,EAAE,CAAC;QAC1D,OAAO,UAAU,CAAC;IACnB,CAAC;IACD,OAAO,KAAK,CAAC,OAAO,CAAC,CAAC,IAAI,CAAC,OAAO,CAAC,OAAO,EAAE,MAAM,CAAC,CAAC;AACrD,CAAC;AAED;;;;;GAKG;AACH,SAAS,aAAa,CAAC,IAAY,EAAE,OAA0B;IAC9D,IAAI,CAAC;QACJ,OAAO,OAAO,CAAC,QAAQ,CAAC,IAAI,CAAC,CAAC;IAC/B,CAAC;IAAC,MAAM,CAAC;QACR,OAAO,IAAI,CAAC;IACb,CAAC;AACF,CAAC;AAED;;;;;;GAMG;AACH,MAAM,UAAU,oBAAoB,CAAC,cAAsB,EAAE,OAA0B;IACtF,MAAM,UAAU,GAAG,KAAK,CAAC,OAAO,CAAC,CAAC,IAAI,CAAC,OAAO,CAAC,OAAO,CAAC,EAAE,SAAS,EAAE,QAAQ,CAAC,CAAC;IAC9E,MAAM,MAAM,GAAG,SAAS,CAAC,cAAc,CAAC,CAAC;IACzC,KAAK,MAAM,SAAS,IAAI,IAAI,GAAG,CAAC,CAAC,UAAU,EAAE,aAAa,CAAC,UAAU,EAAE,OAAO,CAAC,CAAC,CAAC,EAAE,CAAC;QACnF,IAAI,MAAM,CAAC,UAAU,CAAC,GAAG,SAAS,CAAC,SAAS,CAAC,GAAG,CAAC,EAAE,CAAC;YACnD,OAAO,IAAI,CAAC;QACb,CAAC;IACF,CAAC;IACD,OAAO,KAAK,CAAC;AACd,CAAC;AAED,iEAAiE;AACjE,SAAS,iBAAiB,CAAC,OAA0B;IACpD,OAAO,OAAO,CAAC,QAAQ,KAAK,OAAO,CAAC,CAAC,CAAC,SAAS,CAAC,CAAC,CAAC,KAAK,CAAC;AACzD,CAAC;AAED,wFAAwF;AACxF,SAAS,kBAAkB,CAAC,OAA0B;IACrD,IAAI,OAAO,CAAC,QAAQ,KAAK,OAAO,EAAE,CAAC;QAClC,OAAO,CAAC,KAAK,CAAC,CAAC;IAChB,CAAC;IACD,MAAM,OAAO,GAAG,OAAO,CAAC,GAAG,CAAC,OAAO,IAAI,qBAAqB,CAAC;IAC7D,MAAM,UAAU,GAAG,OAAO;SACxB,KAAK,CAAC,GAAG,CAAC;SACV,GAAG,CAAC,CAAC,SAAS,EAAE,EAAE,CAAC,SAAS,CAAC,IAAI,EAAE,CAAC;SACpC,MAAM,CAAC,CAAC,SAAS,EAAE,EAAE,CAAC,SAAS,CAAC,MAAM,GAAG,CAAC,CAAC,CAAC;IAC9C,OAAO,UAAU,CAAC,MAAM,GAAG,CAAC,CAAC,CAAC,CAAC,UAAU,CAAC,GAAG,CAAC,CAAC,SAAS,EAAE,EAAE,CAAC,MAAM,SAAS,EAAE,CAAC,CAAC,CAAC,CAAC,CAAC,SAAS,CAAC,CAAC;AAC/F,CAAC;AAED;;;;GAIG;AACH,MAAM,UAAU,aAAa,CAAC,OAA0B;IACvD,MAAM,IAAI,GAAG,KAAK,CAAC,OAAO,CAAC,CAAC;IAC5B,MAAM,UAAU,GAAG,iBAAiB,CAAC,OAAO,CAAC,CAAC;IAC9C,MAAM,cAAc,GAAG;QACtB,IAAI,CAAC,IAAI,CAAC,OAAO,CAAC,OAAO,CAAC,EAAE,KAAK,EAAE,UAAU,CAAC;QAC9C,IAAI,CAAC,IAAI,CAAC,OAAO,CAAC,OAAO,EAAE,MAAM,EAAE,KAAK,EAAE,UAAU,CAAC;KACrD,CAAC;IACF,KAAK,MAAM,SAAS,IAAI,cAAc,EAAE,CAAC;QACxC,IAAI,OAAO,CAAC,MAAM,CAAC,SAAS,CAAC,EAAE,CAAC;YAC/B,OAAO,SAAS,CAAC;QAClB,CAAC;IACF,CAAC;IAED,MAAM,SAAS,GAAG,OAAO,CAAC,GAAG,CAAC,IAAI,IAAI,OAAO,CAAC,GAAG,CAAC,IAAI,CAAC;IACvD,IAAI,SAAS,KAAK,SAAS,IAAI,SAAS,KAAK,EAAE,EAAE,CAAC;QACjD,OAAO,SAAS,CAAC;IAClB,CAAC;IACD,MAAM,KAAK,GAAG,kBAAkB,CAAC,OAAO,CAAC,CAAC;IAC1C,KAAK,MAAM,SAAS,IAAI,SAAS,CAAC,KAAK,CAAC,IAAI,CAAC,SAAS,CAAC,EAAE,CAAC;QACzD,IAAI,SAAS,KAAK,EAAE;YAAE,SAAS;QAC/B,KAAK,MAAM,IAAI,IAAI,KAAK,EAAE,CAAC;YAC1B,MAAM,SAAS,GAAG,IAAI,CAAC,IAAI,CAAC,SAAS,EAAE,IAAI,CAAC,CAAC;YAC7C,IAAI,OAAO,CAAC,MAAM,CAAC,SAAS,CAAC,EAAE,CAAC;gBAC/B,OAAO,SAAS,CAAC;YAClB,CAAC;QACF,CAAC;IACF,CAAC;IACD,OAAO,SAAS,CAAC;AAClB,CAAC;AAED,SAAS,cAAc,CAAC,OAA0B;IACjD,MAAM,KAAK,GAAG,OAAO,CAAC,GAAG,CAAC,eAAe,CAAC,CAAC;IAC3C,IAAI,KAAK,KAAK,MAAM,IAAI,KAAK,KAAK,KAAK,EAAE,CAAC;QACzC,OAAO,KAAK,CAAC;IACd,CAAC;IACD,OAAO,SAAS,CAAC;AAClB,CAAC;AAED;;;;;;;;;GASG;AACH,MAAM,UAAU,gBAAgB,CAAC,KAAqB;IACrD,IAAI,KAAK,CAAC,QAAQ,CAAC,GAAG,KAAK,SAAS,EAAE,CAAC;QACtC,OAAO,EAAE,MAAM,EAAE,MAAM,EAAE,MAAM,EAAE,aAAa,EAAE,CAAC;IAClD,CAAC;IACD,MAAM,MAAM,GAAG,cAAc,CAAC,KAAK,CAAC,OAAO,CAAC,CAAC;IAC7C,IAAI,MAAM,KAAK,MAAM,EAAE,CAAC;QACvB,OAAO,EAAE,MAAM,EAAE,MAAM,EAAE,MAAM,EAAE,qBAAqB,EAAE,CAAC;IAC1D,CAAC;IACD,IAAI,KAAK,CAAC,2BAA2B,EAAE,CAAC;QACvC,OAAO,EAAE,MAAM,EAAE,MAAM,EAAE,MAAM,EAAE,WAAW,EAAE,CAAC;IAChD,CAAC;IACD,MAAM,QAAQ,GAAG,MAAM,KAAK,KAAK,IAAI,oBAAoB,CAAC,KAAK,CAAC,cAAc,EAAE,KAAK,CAAC,OAAO,CAAC,CAAC;IAC/F,IAAI,CAAC,QAAQ,EAAE,CAAC;QACf,OAAO,EAAE,MAAM,EAAE,MAAM,EAAE,MAAM,EAAE,iBAAiB,EAAE,CAAC;IACtD,CAAC;IACD,MAAM,OAAO,GAAG,aAAa,CAAC,KAAK,CAAC,OAAO,CAAC,CAAC;IAC7C,IAAI,OAAO,KAAK,SAAS,EAAE,CAAC;QAC3B,OAAO,EAAE,MAAM,EAAE,MAAM,EAAE,MAAM,EAAE,eAAe,EAAE,CAAC;IACpD,CAAC;IACD,OAAO,EAAE,MAAM,EAAE,QAAQ,EAAE,OAAO,EAAE,CAAC;AACtC,CAAC;AAED,8EAA8E;AAC9E,MAAM,UAAU,wBAAwB,CACvC,MAAiC,EACjC,QAAkC;IAElC,OAAO,EAAE,GAAG,EAAE,OAAO,CAAC,GAAG,EAAE,OAAO,EAAE,SAAS,EAAE,EAAE,QAAQ,EAAE,OAAO,CAAC,QAAQ,EAAE,MAAM,EAAE,QAAQ,EAAE,CAAC;AACjG,CAAC","sourcesContent":["/**\n * Runtime selection for a CLI that was installed with `bun install -g`.\n *\n * `bun install -g` writes `~/.bun/bin/senpi` as a symlink into\n * `<BUN_ROOT>/install/global/node_modules/...`, but the launcher still starts the script with\n * whatever interpreter resolves the shebang — Node. A user who chose Bun then silently runs on\n * Node, losing Bun's startup time and its runtime APIs. This module decides, from injected\n * facts only, whether the process should hand itself to Bun.\n *\n * Everything here is pure and injectable so the decision can be tested without touching the\n * host PATH or the real `~/.bun` tree; `cli.ts` owns the single impure call site.\n */\nimport { homedir as osHomedir } from \"node:os\";\nimport { posix, win32 } from \"node:path\";\n\n/** Value accepted by the runtime pin environment variable. */\nexport type RuntimeName = \"node\" | \"bun\";\n\n/** Environment variable that pins the runtime explicitly, bypassing detection. */\nexport const RUNTIME_ENV_VAR = \"SENPI_RUNTIME\";\n\n/** Injected view of the world. Defaults read the real process, tests inject fakes. */\nexport interface BunRuntimeOptions {\n\treadonly env: NodeJS.ProcessEnv;\n\treadonly homedir: string;\n\treadonly platform: NodeJS.Platform;\n\treadonly exists: (path: string) => boolean;\n\treadonly realpath: (path: string) => string;\n}\n\n/** Why the process stayed on its current runtime. Surfaced for tests and diagnostics. */\nexport type StayReason = \"already-bun\" | \"runtime-pinned-node\" | \"inspector\" | \"bun-not-found\" | \"not-bun-install\";\n\nexport type BunReexecDecision =\n\t| { readonly action: \"stay\"; readonly reason: StayReason }\n\t| { readonly action: \"reexec\"; readonly bunPath: string };\n\nexport interface BunReexecInput {\n\t/** Real path (symlinks resolved) of the script this process was started with. */\n\treadonly scriptRealPath: string;\n\t/** `process.versions`; a `bun` entry means this process already IS Bun. */\n\treadonly versions: Readonly<Record<string, string | undefined>>;\n\t/** Whether this process inherited a `--inspect*` option. */\n\treadonly hasInheritedInspectorOption: boolean;\n\treadonly options: BunRuntimeOptions;\n}\n\n/** Path separators differ per platform; compare on a single normalized form. */\nfunction normalize(path: string): string {\n\treturn path.replaceAll(\"\\\\\", \"/\");\n}\n\n/**\n * Path semantics of the TARGET platform, not of the host running the code. Joining and PATH\n * splitting must follow the injected platform so the decision is reproducible from a Windows\n * fixture on a POSIX machine and vice versa.\n */\nfunction paths(options: BunRuntimeOptions): typeof posix {\n\treturn options.platform === \"win32\" ? win32 : posix;\n}\n\n/**\n * Root of the Bun installation: `BUN_INSTALL` when set, otherwise `~/.bun`. This is the same\n * resolution order Bun itself uses to place `bin/` and `install/global/`.\n */\nfunction bunRoot(options: BunRuntimeOptions): string {\n\tconst configured = options.env.BUN_INSTALL;\n\tif (configured !== undefined && configured.trim() !== \"\") {\n\t\treturn configured;\n\t}\n\treturn paths(options).join(options.homedir, \".bun\");\n}\n\n/**\n * Resolve a directory through the injected `realpath`, falling back to the input. The bun root\n * can be a symlinked or aliased path (`/tmp` -> `/private/tmp` on macOS, a symlinked `$HOME`),\n * and the script path is compared in its resolved form, so both sides must be resolvable.\n * `realpath` throws on a path that does not exist, which simply means \"no match here\".\n */\nfunction resolveOrSelf(path: string, options: BunRuntimeOptions): string {\n\ttry {\n\t\treturn options.realpath(path);\n\t} catch {\n\t\treturn path;\n\t}\n}\n\n/**\n * Report whether the executed script lives inside Bun's global install tree.\n *\n * The caller must pass an already-resolved real path: `~/.bun/bin/<name>` is a symlink, and the\n * link itself is not under `install/global/`, so an unresolved path never matches. The tree root\n * is compared both as configured and as resolved, so a symlinked install root still matches.\n */\nexport function isUnderBunGlobalTree(scriptRealPath: string, options: BunRuntimeOptions): boolean {\n\tconst globalRoot = paths(options).join(bunRoot(options), \"install\", \"global\");\n\tconst script = normalize(scriptRealPath);\n\tfor (const candidate of new Set([globalRoot, resolveOrSelf(globalRoot, options)])) {\n\t\tif (script.startsWith(`${normalize(candidate)}/`)) {\n\t\t\treturn true;\n\t\t}\n\t}\n\treturn false;\n}\n\n/** Executable name for the platform, e.g. `bun` or `bun.exe`. */\nfunction bunExecutableName(options: BunRuntimeOptions): string {\n\treturn options.platform === \"win32\" ? \"bun.exe\" : \"bun\";\n}\n\n/** Candidate file names to probe in a PATH directory, honoring `PATHEXT` on Windows. */\nfunction pathCandidateNames(options: BunRuntimeOptions): string[] {\n\tif (options.platform !== \"win32\") {\n\t\treturn [\"bun\"];\n\t}\n\tconst pathExt = options.env.PATHEXT ?? \".COM;.EXE;.BAT;.CMD\";\n\tconst extensions = pathExt\n\t\t.split(\";\")\n\t\t.map((extension) => extension.trim())\n\t\t.filter((extension) => extension.length > 0);\n\treturn extensions.length > 0 ? extensions.map((extension) => `bun${extension}`) : [\"bun.exe\"];\n}\n\n/**\n * Locate a Bun binary: the configured install root first, then the default `~/.bun` root, then\n * a PATH scan. Returns `undefined` when Bun is not installed — a missing Bun is never an error,\n * the process simply stays on Node.\n */\nexport function findBunBinary(options: BunRuntimeOptions): string | undefined {\n\tconst path = paths(options);\n\tconst executable = bunExecutableName(options);\n\tconst rootCandidates = [\n\t\tpath.join(bunRoot(options), \"bin\", executable),\n\t\tpath.join(options.homedir, \".bun\", \"bin\", executable),\n\t];\n\tfor (const candidate of rootCandidates) {\n\t\tif (options.exists(candidate)) {\n\t\t\treturn candidate;\n\t\t}\n\t}\n\n\tconst pathValue = options.env.PATH ?? options.env.Path;\n\tif (pathValue === undefined || pathValue === \"\") {\n\t\treturn undefined;\n\t}\n\tconst names = pathCandidateNames(options);\n\tfor (const directory of pathValue.split(path.delimiter)) {\n\t\tif (directory === \"\") continue;\n\t\tfor (const name of names) {\n\t\t\tconst candidate = path.join(directory, name);\n\t\t\tif (options.exists(candidate)) {\n\t\t\t\treturn candidate;\n\t\t\t}\n\t\t}\n\t}\n\treturn undefined;\n}\n\nfunction readRuntimePin(options: BunRuntimeOptions): RuntimeName | undefined {\n\tconst value = options.env[RUNTIME_ENV_VAR];\n\tif (value === \"node\" || value === \"bun\") {\n\t\treturn value;\n\t}\n\treturn undefined;\n}\n\n/**\n * Decide whether this process should re-exec itself under Bun. First match wins:\n *\n * 1. already running Bun — never re-exec, that would loop forever;\n * 2. `SENPI_RUNTIME=node` — an explicit opt-out always wins;\n * 3. an inherited Inspector option — a debugger session owns a Node socket, keep it;\n * 4. `SENPI_RUNTIME=bun` with Bun installed — explicit opt-in;\n * 5. the script lives in Bun's global install tree with Bun installed — the install implies it;\n * 6. otherwise stay.\n */\nexport function resolveBunReexec(input: BunReexecInput): BunReexecDecision {\n\tif (input.versions.bun !== undefined) {\n\t\treturn { action: \"stay\", reason: \"already-bun\" };\n\t}\n\tconst pinned = readRuntimePin(input.options);\n\tif (pinned === \"node\") {\n\t\treturn { action: \"stay\", reason: \"runtime-pinned-node\" };\n\t}\n\tif (input.hasInheritedInspectorOption) {\n\t\treturn { action: \"stay\", reason: \"inspector\" };\n\t}\n\tconst wantsBun = pinned === \"bun\" || isUnderBunGlobalTree(input.scriptRealPath, input.options);\n\tif (!wantsBun) {\n\t\treturn { action: \"stay\", reason: \"not-bun-install\" };\n\t}\n\tconst bunPath = findBunBinary(input.options);\n\tif (bunPath === undefined) {\n\t\treturn { action: \"stay\", reason: \"bun-not-found\" };\n\t}\n\treturn { action: \"reexec\", bunPath };\n}\n\n/** Build the default, process-backed options for the real CLI entry point. */\nexport function processBunRuntimeOptions(\n\texists: (path: string) => boolean,\n\trealpath: (path: string) => string,\n): BunRuntimeOptions {\n\treturn { env: process.env, homedir: osHomedir(), platform: process.platform, exists, realpath };\n}\n"]}
|
|
1
|
+
{"version":3,"file":"bun-runtime.js","sourceRoot":"","sources":["../src/bun-runtime.ts"],"names":[],"mappings":"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","sourcesContent":["/**\n * Runtime selection for an installed CLI.\n *\n * `bun install -g` writes `~/.bun/bin/senpi` as a symlink into\n * `<BUN_ROOT>/install/global/node_modules/...`, but the launcher still starts the script with\n * whatever interpreter resolves the shebang — Node. npm, pnpm, yarn, and npx installs start on\n * Node too, even on a machine that has Bun. Either way the user silently loses Bun's startup time\n * and its runtime APIs. This module decides, from injected facts only, whether the process should\n * hand itself to Bun.\n *\n * Everything here is pure and injectable so the decision can be tested without touching the\n * host PATH or the real `~/.bun` tree; `cli.ts` owns the single impure call site.\n */\nimport { spawnSync } from \"node:child_process\";\nimport { homedir as osHomedir } from \"node:os\";\nimport { posix, win32 } from \"node:path\";\n\n/** Value accepted by the runtime pin environment variable. */\nexport type RuntimeName = \"node\" | \"bun\";\n\n/** Environment variable that pins the runtime explicitly, bypassing detection. */\nexport const RUNTIME_ENV_VAR = \"SENPI_RUNTIME\";\n\n/** Injected view of the world. Defaults read the real process, tests inject fakes. */\nexport interface BunRuntimeOptions {\n\treadonly env: NodeJS.ProcessEnv;\n\treadonly homedir: string;\n\treadonly platform: NodeJS.Platform;\n\treadonly exists: (path: string) => boolean;\n\treadonly realpath: (path: string) => string;\n\t/** `<bun> --version` output, or `undefined` when the binary cannot be run. */\n\treadonly bunVersion: (bunPath: string) => string | undefined;\n}\n\n/** Oldest Bun release a non-Bun install is handed to; older releases miss runtime APIs senpi uses. */\nexport const MIN_BUN_VERSION = \"1.4.0\";\n\n/** Why the process stayed on its current runtime. Surfaced for tests and diagnostics. */\nexport type StayReason =\n\t| \"already-bun\"\n\t| \"runtime-pinned-node\"\n\t| \"inspector\"\n\t| \"bun-not-found\"\n\t| \"bun-too-old\"\n\t| \"not-bun-install\";\n\nexport type BunReexecDecision =\n\t| { readonly action: \"stay\"; readonly reason: StayReason }\n\t| { readonly action: \"reexec\"; readonly bunPath: string };\n\nexport interface BunReexecInput {\n\t/** Real path (symlinks resolved) of the script this process was started with. */\n\treadonly scriptRealPath: string;\n\t/** `process.versions`; a `bun` entry means this process already IS Bun. */\n\treadonly versions: Readonly<Record<string, string | undefined>>;\n\t/** Whether this process inherited a `--inspect*` option. */\n\treadonly hasInheritedInspectorOption: boolean;\n\treadonly options: BunRuntimeOptions;\n}\n\n/** Path separators differ per platform; compare on a single normalized form. */\nfunction normalize(path: string): string {\n\treturn path.replaceAll(\"\\\\\", \"/\");\n}\n\n/**\n * Path semantics of the TARGET platform, not of the host running the code. Joining and PATH\n * splitting must follow the injected platform so the decision is reproducible from a Windows\n * fixture on a POSIX machine and vice versa.\n */\nfunction paths(options: BunRuntimeOptions): typeof posix {\n\treturn options.platform === \"win32\" ? win32 : posix;\n}\n\n/**\n * Root of the Bun installation: `BUN_INSTALL` when set, otherwise `~/.bun`. This is the same\n * resolution order Bun itself uses to place `bin/` and `install/global/`.\n */\nfunction bunRoot(options: BunRuntimeOptions): string {\n\tconst configured = options.env.BUN_INSTALL;\n\tif (configured !== undefined && configured.trim() !== \"\") {\n\t\treturn configured;\n\t}\n\treturn paths(options).join(options.homedir, \".bun\");\n}\n\n/**\n * Resolve a directory through the injected `realpath`, falling back to the input. The bun root\n * can be a symlinked or aliased path (`/tmp` -> `/private/tmp` on macOS, a symlinked `$HOME`),\n * and the script path is compared in its resolved form, so both sides must be resolvable.\n * `realpath` throws on a path that does not exist, which simply means \"no match here\".\n */\nfunction resolveOrSelf(path: string, options: BunRuntimeOptions): string {\n\ttry {\n\t\treturn options.realpath(path);\n\t} catch {\n\t\treturn path;\n\t}\n}\n\n/**\n * Report whether the executed script lives inside Bun's global install tree.\n *\n * The caller must pass an already-resolved real path: `~/.bun/bin/<name>` is a symlink, and the\n * link itself is not under `install/global/`, so an unresolved path never matches. The tree root\n * is compared both as configured and as resolved, so a symlinked install root still matches.\n */\nexport function isUnderBunGlobalTree(scriptRealPath: string, options: BunRuntimeOptions): boolean {\n\tconst globalRoot = paths(options).join(bunRoot(options), \"install\", \"global\");\n\tconst script = normalize(scriptRealPath);\n\tfor (const candidate of new Set([globalRoot, resolveOrSelf(globalRoot, options)])) {\n\t\tif (script.startsWith(`${normalize(candidate)}/`)) {\n\t\t\treturn true;\n\t\t}\n\t}\n\treturn false;\n}\n\n/**\n * Report whether the executed script belongs to an installed package (npm, pnpm, yarn, npx, or a\n * project-local install), as opposed to a source checkout. Only installed packages are handed to a\n * discovered Bun: a checkout run on Node is a developer's deliberate choice.\n */\nexport function isInstalledPackageScript(scriptRealPath: string): boolean {\n\treturn normalize(scriptRealPath).includes(\"/node_modules/\");\n}\n\n/** True when `version` (e.g. `1.4.2`, `1.5.0-canary.3`) is at least {@link MIN_BUN_VERSION}. */\nexport function bunVersionSatisfies(version: string | undefined): boolean {\n\tconst actual = /^v?(\\d+)\\.(\\d+)\\.(\\d+)/.exec(version?.trim() ?? \"\");\n\tconst minimum = /^(\\d+)\\.(\\d+)\\.(\\d+)/.exec(MIN_BUN_VERSION);\n\tif (!actual || !minimum) return false;\n\tfor (let index = 1; index <= 3; index++) {\n\t\tconst difference = Number(actual[index]) - Number(minimum[index]);\n\t\tif (difference !== 0) return difference > 0;\n\t}\n\treturn true;\n}\n\n/** Executable name for the platform, e.g. `bun` or `bun.exe`. */\nfunction bunExecutableName(options: BunRuntimeOptions): string {\n\treturn options.platform === \"win32\" ? \"bun.exe\" : \"bun\";\n}\n\n/** Candidate file names to probe in a PATH directory, honoring `PATHEXT` on Windows. */\nfunction pathCandidateNames(options: BunRuntimeOptions): string[] {\n\tif (options.platform !== \"win32\") {\n\t\treturn [\"bun\"];\n\t}\n\tconst pathExt = options.env.PATHEXT ?? \".COM;.EXE;.BAT;.CMD\";\n\tconst extensions = pathExt\n\t\t.split(\";\")\n\t\t.map((extension) => extension.trim())\n\t\t.filter((extension) => extension.length > 0);\n\treturn extensions.length > 0 ? extensions.map((extension) => `bun${extension}`) : [\"bun.exe\"];\n}\n\n/**\n * Locate a Bun binary: the configured install root first, then the default `~/.bun` root, then\n * a PATH scan. Returns `undefined` when Bun is not installed — a missing Bun is never an error,\n * the process simply stays on Node.\n */\nexport function findBunBinary(options: BunRuntimeOptions): string | undefined {\n\tconst path = paths(options);\n\tconst executable = bunExecutableName(options);\n\tconst rootCandidates = [\n\t\tpath.join(bunRoot(options), \"bin\", executable),\n\t\tpath.join(options.homedir, \".bun\", \"bin\", executable),\n\t];\n\tfor (const candidate of rootCandidates) {\n\t\tif (options.exists(candidate)) {\n\t\t\treturn candidate;\n\t\t}\n\t}\n\n\tconst pathValue = options.env.PATH ?? options.env.Path;\n\tif (pathValue === undefined || pathValue === \"\") {\n\t\treturn undefined;\n\t}\n\tconst names = pathCandidateNames(options);\n\tfor (const directory of pathValue.split(path.delimiter)) {\n\t\tif (directory === \"\") continue;\n\t\tfor (const name of names) {\n\t\t\tconst candidate = path.join(directory, name);\n\t\t\tif (options.exists(candidate)) {\n\t\t\t\treturn candidate;\n\t\t\t}\n\t\t}\n\t}\n\treturn undefined;\n}\n\nfunction readRuntimePin(options: BunRuntimeOptions): RuntimeName | undefined {\n\tconst value = options.env[RUNTIME_ENV_VAR];\n\tif (value === \"node\" || value === \"bun\") {\n\t\treturn value;\n\t}\n\treturn undefined;\n}\n\n/**\n * Decide whether this process should re-exec itself under Bun. First match wins:\n *\n * 1. already running Bun — never re-exec, that would loop forever;\n * 2. `SENPI_RUNTIME=node` — an explicit opt-out always wins;\n * 3. an inherited Inspector option — a debugger session owns a Node socket, keep it;\n * 4. `SENPI_RUNTIME=bun` with Bun installed — explicit opt-in;\n * 5. the script lives in Bun's global install tree with Bun installed — the install implies it;\n * 6. any other installed package with Bun >= {@link MIN_BUN_VERSION} — a machine that has a\n * current Bun runs senpi on it, whichever package manager installed it;\n * 7. otherwise stay (source checkouts, no Bun, or an older Bun).\n */\nexport function resolveBunReexec(input: BunReexecInput): BunReexecDecision {\n\tif (input.versions.bun !== undefined) {\n\t\treturn { action: \"stay\", reason: \"already-bun\" };\n\t}\n\tconst pinned = readRuntimePin(input.options);\n\tif (pinned === \"node\") {\n\t\treturn { action: \"stay\", reason: \"runtime-pinned-node\" };\n\t}\n\tif (input.hasInheritedInspectorOption) {\n\t\treturn { action: \"stay\", reason: \"inspector\" };\n\t}\n\tconst trustsInstalledBun = pinned === \"bun\" || isUnderBunGlobalTree(input.scriptRealPath, input.options);\n\tif (!trustsInstalledBun && !isInstalledPackageScript(input.scriptRealPath)) {\n\t\treturn { action: \"stay\", reason: \"not-bun-install\" };\n\t}\n\tconst bunPath = findBunBinary(input.options);\n\tif (bunPath === undefined) {\n\t\treturn { action: \"stay\", reason: \"bun-not-found\" };\n\t}\n\t// A pin or a bun-global install already proved its Bun; any other install probes it once.\n\tif (!trustsInstalledBun && !bunVersionSatisfies(input.options.bunVersion(bunPath))) {\n\t\treturn { action: \"stay\", reason: \"bun-too-old\" };\n\t}\n\treturn { action: \"reexec\", bunPath };\n}\n\n/** Run `<bun> --version`; any failure (missing, not executable, timeout) means \"unknown\". */\nexport function readBunVersion(bunPath: string): string | undefined {\n\ttry {\n\t\tconst result = spawnSync(bunPath, [\"--version\"], { encoding: \"utf8\", timeout: 5_000, windowsHide: true });\n\t\tif (result.status !== 0) return undefined;\n\t\tconst version = result.stdout.trim();\n\t\treturn version === \"\" ? undefined : version;\n\t} catch {\n\t\t// Node throws synchronously for a batch file spawned without a shell (spawn EINVAL).\n\t\treturn undefined;\n\t}\n}\n\n/** Build the default, process-backed options for the real CLI entry point. */\nexport function processBunRuntimeOptions(\n\texists: (path: string) => boolean,\n\trealpath: (path: string) => string,\n): BunRuntimeOptions {\n\treturn {\n\t\tenv: process.env,\n\t\thomedir: osHomedir(),\n\t\tplatform: process.platform,\n\t\texists,\n\t\trealpath,\n\t\tbunVersion: readBunVersion,\n\t};\n}\n"]}
|
package/dist/bundle/chunks/{app-server-command-RB6QUPRI.js → app-server-command-ATWHNOOO.js}
RENAMED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { createRequire as __piCreateRequire } from "node:module"; const require = __piCreateRequire(import.meta.url); { const __piWorkerThreads = require("node:worker_threads"); if (typeof __piWorkerThreads.markAsUncloneable !== "function") { __piWorkerThreads.markAsUncloneable = () => {}; } }
|
|
2
|
-
import{import_websocket_server,wrapper_default}from"./chunk-YBZDAABT.js";import{AuthStorage,DefaultResourceLoader,GOAL_STORE_CHANGED_EVENT,ModelRegistry,clearGoal,createAgentSession,createGoal,createPendingQuestion,defaultModelPerProvider,formatProviderNativeBody,formatProviderNativeSummary,getMcpService,goalStoreRef,loadMcpConfig,loadSkills,readGoal,resolveAuthMode,updateGoal}from"./chunk-
|
|
2
|
+
import{import_websocket_server,wrapper_default}from"./chunk-YBZDAABT.js";import{AuthStorage,DefaultResourceLoader,GOAL_STORE_CHANGED_EVENT,ModelRegistry,clearGoal,createAgentSession,createGoal,createPendingQuestion,defaultModelPerProvider,formatProviderNativeBody,formatProviderNativeSummary,getMcpService,goalStoreRef,loadMcpConfig,loadSkills,readGoal,resolveAuthMode,updateGoal}from"./chunk-6SLQ5HFO.js";import{require_ignore}from"./chunk-ZKZAATFM.js";import"./chunk-UYCGOK6M.js";import"./chunk-6WBYI25N.js";import{SessionManager,loadEntriesFromFile}from"./chunk-67BC7VZV.js";import"./chunk-YS5YEX4S.js";import{getCredentialAccounts,getModels,getProviders,getSupportedThinkingLevels,pinCredentialAccount,removeCredentialAccount,subscribeProviderAccountEvents}from"./chunk-2L26KPZ6.js";import{getAvailableThemesWithPaths,getThemeByName,takeOverStdout,theme,waitForRawStdoutBackpressure,writeRawStdout}from"./chunk-77ACEMYL.js";import{x}from"./chunk-EJKCBRYK.js";import"./chunk-6Q6DGME2.js";import{SettingsManager}from"./chunk-WM5FVFAP.js";import"./chunk-YU2P3CAR.js";import"./chunk-BMX2U22F.js";import"./chunk-6W2DB2GC.js";import{acquireOwnershipSafeLock}from"./chunk-LPFZGWG2.js";import"./chunk-HH3KJDWA.js";import{parseDaemonPidFile,processMatchesPidFile,readProcessStartTime,stopValidatedPid,waitForGone,waitForStartTime}from"./chunk-3CC5ZOBV.js";import{attachJsonlLineReader}from"./chunk-XKLR7C5B.js";import"./chunk-FIJJKSPL.js";import"./chunk-H4QOEVS5.js";import"./chunk-HB35RBP4.js";import"./chunk-HZAKOVHN.js";import"./chunk-TTY5BHCG.js";import"./chunk-FHXKBENX.js";import"./chunk-X3T45623.js";import"./chunk-7X72IACK.js";import"./chunk-KUPQO7MC.js";import"./chunk-HPY47AVX.js";import"./chunk-SUQDU5KM.js";import"./chunk-7FXXRPB3.js";import"./chunk-26UBFF6N.js";import"./chunk-KMJXSXRX.js";import"./chunk-L6JO4M6J.js";import"./chunk-RFB5CBW7.js";import"./chunk-2VZJTDUX.js";import"./chunk-ELFCNCBY.js";import"./chunk-PRC5XIIH.js";import"./chunk-W3D3ZYDS.js";import"./chunk-ZOFZRJSL.js";import"./chunk-2SOVDETB.js";import{APP_NAME,CONFIG_DIR_NAME,ENV_SESSION_DIR,VERSION,getAgentDir,getSessionsDir,resolvePath}from"./chunk-Y65WFI5H.js";import"./chunk-ATQFN5HI.js";import"./chunk-RBCQOIAD.js";import"./chunk-WASCMT6Y.js";import"./chunk-X4Q5YSTE.js";import"./chunk-IXAUDPOK.js";import"./chunk-TTHMOL5G.js";import"./chunk-WVGCQOLJ.js";import"./chunk-BPX6QWPD.js";import{normalizeProviderId}from"./chunk-WL5EQYNL.js";import"./chunk-WDTJ6JKE.js";import"./chunk-5ROMKDLK.js";import"./chunk-3W47LTXQ.js";import"./chunk-DMI55M2N.js";import"./chunk-BQVVQ2LQ.js";import{__toESM}from"./chunk-IKOFCODM.js";import{isIP}from"node:net";var APP_SERVER_LISTEN_USAGE="Invalid --listen value. Use stdio://, unix://, unix:///abs/path, or ws://IP:PORT.";function formatAppServerUsage(){let listenForms="stdio://|unix://|unix:///abs/path|ws://IP:PORT";return[`Usage: ${APP_NAME} app-server [--listen <${listenForms}>] [--ws-auth <token-file|off>] [--json-logs]`,` ${APP_NAME} app-server daemon <start|stop|status|restart> [--listen <${listenForms}>]`].join(`
|
|
3
3
|
`)}function parseListen(value){if(value==="stdio://")return{kind:"stdio",url:"stdio://"};if(value==="unix://")return{kind:"unix",url:"unix://"};if(value.startsWith("unix:///")){let path=value.slice(7);return path.startsWith("/")?{kind:"unix",url:value,path}:void 0}if(!value.startsWith("ws://"))return;let parsed;try{parsed=new URL(value)}catch(error){if(error instanceof TypeError)return;throw error}let port=Number(parsed.port);if(!(parsed.protocol!=="ws:"||parsed.username!==""||parsed.password!==""||parsed.pathname!=="/"||parsed.search!==""||parsed.hash!==""||parsed.port===""||!Number.isInteger(port)||port<1||port>65535||isIP(parsed.hostname)===0))return{kind:"ws",url:value,host:parsed.hostname,port}}function parseDaemonVerb(value){switch(value){case"start":case"stop":case"status":case"restart":return value;default:return}}function parseWsAuth(value){return value==="off"?{kind:"off"}:{kind:"token-file",path:value}}function parseServerArgs(args){let listen3={kind:"stdio",url:"stdio://"},wsAuth,jsonLogs=!1;for(let index=0;index<args.length;index++){let arg=args[index];if(arg==="--listen"){let value=args[index+1];if(value===void 0)return{kind:"usage-error",message:APP_SERVER_LISTEN_USAGE};let parsed=parseListen(value);if(parsed===void 0)return{kind:"usage-error",message:APP_SERVER_LISTEN_USAGE};listen3=parsed,index++;continue}if(arg==="--ws-auth"){let value=args[index+1];if(value===void 0)return{kind:"usage-error",message:"--ws-auth requires <token-file|off>."};wsAuth=parseWsAuth(value),index++;continue}if(arg==="--json-logs"){jsonLogs=!0;continue}return{kind:"usage-error",message:`Unexpected app-server argument: ${arg}`}}return{kind:"server",listen:listen3,wsAuth,jsonLogs}}function parseDaemonArgs(args){let verb=parseDaemonVerb(args[0]);if(verb===void 0)return{kind:"usage-error",message:"Usage: app-server daemon <start|stop|status|restart>."};let listen3={kind:"ws",url:"ws://127.0.0.1:18800",host:"127.0.0.1",port:18800};for(let index=1;index<args.length;index++){let arg=args[index];if(arg==="--listen"){let value=args[index+1];if(value===void 0)return{kind:"usage-error",message:APP_SERVER_LISTEN_USAGE};let parsed=parseListen(value);if(parsed===void 0)return{kind:"usage-error",message:APP_SERVER_LISTEN_USAGE};listen3=parsed,index++;continue}return{kind:"usage-error",message:`Unexpected app-server daemon argument: ${arg}`}}return{kind:"daemon",verb,listen:listen3}}function parseAppServerCliArgs(args){return args[0]==="daemon"?parseDaemonArgs(args.slice(1)):parseServerArgs(args)}var RpcHandlerError=class extends Error{constructor(error){super(error.message),this.name="RpcHandlerError",this.rpcError=error}};function invalidRequestError(){return{code:-32600,message:"Invalid request"}}function methodNotFoundError(method){return{code:-32601,message:`Method not found: ${method}`}}function invalidParamsError(){return{code:-32602,message:"Invalid params"}}function internalError(message="Internal error"){return{code:-32603,message}}function notInitializedError(){return{code:-32600,message:"Not initialized"}}function alreadyInitializedError(){return{code:-32600,message:"Already initialized"}}function experimentalCapabilityError(method){return{code:-32600,message:`${method} requires experimentalApi capability`}}function registerExtensionRequestMethod(registry,getThread){registry.register("extension_request",{scope:"thread",handler:async({request})=>{let params=request.params;if(typeof params!="object"||params===null||Array.isArray(params))throw new RpcHandlerError({code:-32602,message:"Invalid params"});let threadId=Reflect.get(params,"threadId"),name=Reflect.get(params,"name");if(typeof threadId!="string"||threadId.length===0||typeof name!="string"||name.length===0)throw new RpcHandlerError({code:-32602,message:"Invalid params"});try{return await getThread(threadId).extensionRunner.requestRpc(name,Reflect.get(params,"data"))}catch(error){throw new RpcHandlerError(internalError(error instanceof Error?error.message:String(error)))}}})}function createRegistry(){return new InMemoryMethodRegistry}var InMemoryMethodRegistry=class{constructor(){this.methods=new Map}register(method,registration){this.methods.set(method,registration)}async dispatch(connection,request){if(!connection.initialized){let registration2=this.methods.get(request.method);if(request.method!=="initialize"||registration2?.requiresInit!==!1)return{id:request.id,error:notInitializedError()}}if(connection.initialized&&request.method==="initialize")return{id:request.id,error:alreadyInitializedError()};let registration=this.methods.get(request.method);if(!registration)return{id:request.id,error:methodNotFoundError(request.method)};if(registration.experimental===!0&&connection.capabilities?.experimentalApi!==!0)return{id:request.id,error:experimentalCapabilityError(request.method)};try{let result=await registration.handler({connection,request});return{id:request.id,result}}catch(error){return error instanceof RpcHandlerError?{id:request.id,error:error.rpcError}:{id:request.id,error:internalError(error instanceof Error?error.message:String(error))}}}};function registerFuzzyFileSearchMethods(registry,service){registry.register("fuzzyFileSearch",{scope:"global",handler:({request})=>service.search(parseOneShotParams(request.params))}),registry.register("fuzzyFileSearch/sessionStart",{experimental:!0,scope:"global",handler:({request})=>service.startSession(parseSessionStartParams(request.params))}),registry.register("fuzzyFileSearch/sessionUpdate",{experimental:!0,scope:"global",handler:({request})=>service.updateSession(parseSessionUpdateParams(request.params))}),registry.register("fuzzyFileSearch/sessionStop",{experimental:!0,scope:"global",handler:({request})=>service.stopSession(parseSessionStopParams(request.params))})}function parseOneShotParams(value){let params=objectParams(value,"fuzzyFileSearch");return{query:requiredString(params.query,"fuzzyFileSearch query"),roots:requiredStringArray(params.roots,"fuzzyFileSearch roots"),cancellationToken:optionalString(params.cancellationToken,"fuzzyFileSearch cancellationToken")}}function parseSessionStartParams(value){let params=objectParams(value,"fuzzyFileSearch/sessionStart");return{sessionId:requiredString(params.sessionId,"fuzzyFileSearch/sessionStart sessionId"),roots:requiredStringArray(params.roots,"fuzzyFileSearch/sessionStart roots")}}function parseSessionUpdateParams(value){let params=objectParams(value,"fuzzyFileSearch/sessionUpdate");return{sessionId:requiredString(params.sessionId,"fuzzyFileSearch/sessionUpdate sessionId"),query:requiredString(params.query,"fuzzyFileSearch/sessionUpdate query")}}function parseSessionStopParams(value){let params=objectParams(value,"fuzzyFileSearch/sessionStop");return{sessionId:requiredString(params.sessionId,"fuzzyFileSearch/sessionStop sessionId")}}function objectParams(value,method){if(!isRecord(value))throw invalidRequest(`${method} params must be an object`);return value}function requiredString(value,field){if(typeof value!="string")throw invalidRequest(`${field} must be a string`);return value}function requiredStringArray(value,field){if(!Array.isArray(value)||value.some(entry=>typeof entry!="string"))throw invalidRequest(`${field} must be an array of strings`);return value}function optionalString(value,field){if(value==null)return null;if(typeof value!="string")throw invalidRequest(`${field} must be a string or null`);return value}function invalidRequest(message){return new RpcHandlerError({code:-32600,message})}function isRecord(value){return typeof value=="object"&&value!==null&&!Array.isArray(value)}import{lstat,readdir}from"node:fs/promises";import{basename,join as join2,relative as relative2,resolve,sep as sep2}from"node:path";var import_ignore=__toESM(require_ignore(),1);import{readFile}from"node:fs/promises";import{join,relative,sep}from"node:path";async function addGitignoreScope(matchers,directory){let gitignore=await readableText(join(directory,".gitignore"));return gitignore===void 0?matchers:[...matchers,{directory,matcher:(0,import_ignore.default)().add(gitignore)}]}function isIgnored(matchers,path,isDirectory){let ignored=!1;for(let scope of matchers){let relativePath=relative(scope.directory,path).split(sep).join("/"),result=scope.matcher.test(isDirectory?`${relativePath}/`:relativePath);result.ignored&&(ignored=!0),result.unignored&&(ignored=!1)}return ignored}async function readableText(path){try{return await readFile(path,"utf8")}catch(error){if(error instanceof Error)return;throw error}}var FUZZY_SEARCH_MAX_DEPTH=12,FUZZY_SEARCH_MAX_VISITED_ENTRIES=5e4,FUZZY_SEARCH_RESULT_LIMIT=50,FILENAME_MATCH_BONUS=1e6,CHARACTER_MATCH_SCORE=10,CONTIGUOUS_MATCH_BONUS=20,BOUNDARY_MATCH_BONUS=5,CANCELLATION_YIELD_INTERVAL=256;async function collectFuzzyFileEntries(roots,options={}){let entries=[],state={visitedEntries:0,stopped:!1},maxDepth=options.maxDepth??FUZZY_SEARCH_MAX_DEPTH,maxVisitedEntries=options.maxVisitedEntries??FUZZY_SEARCH_MAX_VISITED_ENTRIES;for(let root of roots){if(options.signal?.aborted||state.stopped)break;let absoluteRoot=resolve(root),rootStat=await readableStat(absoluteRoot);!rootStat?.isDirectory()||rootStat.isSymbolicLink()||await walkDirectory({wireRoot:root,absoluteRoot,absoluteDirectory:absoluteRoot,depth:0,matchers:[],entries,state,signal:options.signal,maxDepth,maxVisitedEntries})}return entries}function rankFuzzyFileEntries(query,entries){if(query.length===0)return[];let results=[];for(let entry of entries){let filenameMatch=scoreSubsequence(entry.fileName,query),pathMatch=filenameMatch??scoreSubsequence(entry.path,query);if(!pathMatch)continue;let filenameOffset=Array.from(entry.path).length-Array.from(entry.fileName).length,indices=filenameMatch?filenameMatch.indices.map(index=>index+filenameOffset):pathMatch.indices;results.push({root:entry.root,path:entry.path,match_type:entry.matchType,file_name:entry.fileName,score:pathMatch.score+(filenameMatch?FILENAME_MATCH_BONUS:0),indices})}return results.sort(compareResults),results.slice(0,FUZZY_SEARCH_RESULT_LIMIT)}async function walkDirectory(input){let matchers=await addGitignoreScope(input.matchers,input.absoluteDirectory),directoryEntries=await readableDirectory(input.absoluteDirectory);directoryEntries.sort((left,right)=>compareText(left.name,right.name));for(let directoryEntry of directoryEntries){if(input.signal?.aborted||input.state.stopped)return;if(input.state.visitedEntries>=input.maxVisitedEntries){input.state.stopped=!0;return}if(input.state.visitedEntries+=1,directoryEntry.name===".git"||directoryEntry.name==="node_modules"||directoryEntry.isSymbolicLink())continue;let isDirectory=directoryEntry.isDirectory();if(!isDirectory&&!directoryEntry.isFile())continue;let absolutePath=join2(input.absoluteDirectory,directoryEntry.name),relativePath=toPosixPath(relative2(input.absoluteRoot,absolutePath));if(isIgnored(matchers,absolutePath,isDirectory))continue;input.entries.push({root:input.wireRoot,path:relativePath,matchType:isDirectory?"directory":"file",fileName:basename(relativePath)});let entryDepth=input.depth+1;isDirectory&&entryDepth<input.maxDepth&&await walkDirectory({...input,absoluteDirectory:absolutePath,depth:entryDepth,matchers}),input.state.visitedEntries%CANCELLATION_YIELD_INTERVAL===0&&await yieldToEventLoop()}}function scoreSubsequence(candidate,query){let normalizedCandidate=normalizeText(candidate),normalizedQuery=query.toLowerCase(),contiguousStart=normalizedCandidate.value.indexOf(normalizedQuery),normalizedIndices=[];if(contiguousStart>=0){let cursor=contiguousStart;for(let character of normalizedQuery)normalizedIndices.push(cursor),cursor+=character.length}else{let cursor=0;for(let character of normalizedQuery){let matchIndex=normalizedCandidate.value.indexOf(character,cursor);if(matchIndex<0)return;normalizedIndices.push(matchIndex),cursor=matchIndex+character.length}}let indices=[];for(let normalizedIndex of normalizedIndices){let sourceIndex=normalizedCandidate.sourceIndices[normalizedIndex];if(sourceIndex===void 0)return;indices.at(-1)!==sourceIndex&&indices.push(sourceIndex)}let candidateCharacters=Array.from(candidate),score=indices.length*CHARACTER_MATCH_SCORE;for(let index=0;index<indices.length;index++){let current=indices[index];if(current===void 0)continue;let previous=indices[index-1];previous!==void 0&¤t===previous+1&&(score+=CONTIGUOUS_MATCH_BONUS),(current===0||isBoundary(candidateCharacters[current-1]))&&(score+=BOUNDARY_MATCH_BONUS)}return{score,indices}}function normalizeText(value){let sourceIndices=[];for(let[sourceIndex,character]of Array.from(value).entries())for(let index=0;index<character.toLowerCase().length;index++)sourceIndices.push(sourceIndex);return{value:value.toLowerCase(),sourceIndices}}function compareResults(left,right){return left.score!==right.score?right.score-left.score:compareText(left.path,right.path)}function compareText(left,right){return left<right?-1:left>right?1:0}function isBoundary(character){return character==="/"||character==="-"||character==="_"||character==="."||character===" "}async function readableDirectory(path){try{return await readdir(path,{withFileTypes:!0})}catch(error){if(error instanceof Error)return[];throw error}}async function readableStat(path){try{return await lstat(path)}catch(error){if(error instanceof Error)return;throw error}}function toPosixPath(path){return path.split(sep2).join("/")}function yieldToEventLoop(){return new Promise(resolve4=>setImmediate(resolve4))}var FuzzyFileSearchService=class{constructor(options){this.activeSearches=new Set,this.pendingSearches=new Map,this.sessions=new Map,this.disposed=!1,this.broadcast=options.broadcast,this.collectEntries=options.collectEntries??((roots,signal)=>collectFuzzyFileEntries(roots,{signal})),this.rankEntries=options.rankEntries??rankFuzzyFileEntries}async search(params){if(this.disposed)return{files:[]};let controller=new AbortController;this.activeSearches.add(controller);let token=params.cancellationToken;token!==null&&(this.pendingSearches.get(token)?.abort(),this.pendingSearches.set(token,controller));try{if(params.query.length===0)return{files:[]};let entries=await this.collectEntries(params.roots,controller.signal);return controller.signal.aborted||this.disposed?{files:[]}:{files:this.rankEntries(params.query,entries)}}finally{this.activeSearches.delete(controller),token!==null&&this.pendingSearches.get(token)===controller&&this.pendingSearches.delete(token)}}startSession(params){if(params.sessionId.length===0)throw invalidRequest2("sessionId must not be empty");this.cancelSession(params.sessionId);let session={id:params.sessionId,roots:params.roots,controller:new AbortController,query:"",version:0,entries:void 0,active:!this.disposed,completedVersion:void 0};return this.sessions.set(session.id,session),this.runTraversal(session),{}}updateSession(params){let session=this.sessions.get(params.sessionId);if(!session?.active)throw invalidRequest2(`fuzzy file search session not found: ${params.sessionId}`);return session.query=params.query,session.version+=1,session.entries!==void 0&&this.emitVersion(session,session.version),{}}stopSession(params){return this.cancelSession(params.sessionId),{}}dispose(){if(!this.disposed){this.disposed=!0;for(let controller of this.activeSearches)controller.abort();this.activeSearches.clear(),this.pendingSearches.clear();for(let session of this.sessions.values())this.deactivate(session);this.sessions.clear()}}async runTraversal(session){let entries;try{entries=await this.collectEntries(session.roots,session.controller.signal)}catch(error){if(!(error instanceof Error))throw error;entries=[]}if(this.isLive(session))for(session.entries=entries;this.isLive(session);){let version=session.version;if(await this.emitVersion(session,version))break}}async emitVersion(session,version){if(await Promise.resolve(),!this.isLive(session)||session.version!==version||session.entries===void 0)return!1;if(session.completedVersion===version)return!0;let query=session.query,files=query.length===0?[]:this.rankEntries(query,session.entries);return!this.isLive(session)||session.version!==version||(this.broadcast({method:"fuzzyFileSearch/sessionUpdated",params:{sessionId:session.id,query,files}}),!this.isLive(session)||session.version!==version)?!1:(session.completedVersion=version,this.broadcast({method:"fuzzyFileSearch/sessionCompleted",params:{sessionId:session.id}}),!0)}cancelSession(sessionId){let session=this.sessions.get(sessionId);session&&(this.deactivate(session),this.sessions.get(sessionId)===session&&this.sessions.delete(sessionId))}deactivate(session){session.active=!1,session.controller.abort()}isLive(session){return!this.disposed&&session.active&&this.sessions.get(session.id)===session}};function invalidRequest2(message){return new RpcHandlerError({code:-32600,message})}var APPROVAL_DECISIONS=["accept","acceptForSession","decline","cancel"],PERMISSION_OPTIONS=["Allow once","Allow always","Deny","Deny with feedback"],CANCEL_REASON="approval request was cancelled because the turn ended",NO_SUBSCRIBER_REASON="no client connected to approve";var ApprovalBridge=class{constructor(sendToThreadSubscribers){this.nextServerRequestId=0,this.pending=new Map,this.sessionAllows=new Set,this.sendToThreadSubscribers=sendToThreadSubscribers}get pendingCount(){return this.pending.size}requestApproval(threadId,kind,payload){let allowKey=createAllowKey(threadId,kind,payload);if(allowKey&&this.sessionAllows.has(allowKey))return Promise.resolve({allow:!0,decision:"acceptForSession"});let requestId=this.nextServerRequestId;this.nextServerRequestId+=1;let request=buildApprovalRequest(threadId,requestId,kind,payload);return new Promise(resolve4=>{this.pending.set(requestId,{threadId,request,allowKey,resolve:resolve4}),!(this.sendToThreadSubscribers(threadId,request)>0)&&(this.pending.delete(requestId),resolve4({allow:!1,decision:"decline",reason:NO_SUBSCRIBER_REASON}))})}resolveResponse(response){let pending=this.pending.get(response.id);if(!pending)return!1;let decision=readDecision(response);return this.pending.delete(response.id),decision==="acceptForSession"&&pending.allowKey&&this.sessionAllows.add(pending.allowKey),this.emitResolved(pending.threadId,response.id),pending.resolve(decisionToOutcome(decision,readReason(response))),!0}replayPendingForThread(threadId){let replayed=0;for(let pending of this.pending.values())pending.threadId===threadId&&(this.sendToThreadSubscribers(threadId,pending.request),replayed+=1);return replayed}cancelPendingForThread(threadId){let cancelled=0;for(let[requestId,pending]of Array.from(this.pending.entries()))pending.threadId===threadId&&(this.pending.delete(requestId),this.emitResolved(threadId,requestId),pending.resolve({allow:!1,decision:"cancel",reason:CANCEL_REASON}),cancelled+=1);return cancelled}emitResolved(threadId,requestId){this.sendToThreadSubscribers(threadId,{method:"serverRequest/resolved",params:{threadId,requestId}})}};function buildApprovalRequest(threadId,requestId,kind,payload){let base={threadId,turnId:payload.turnId??"turn-approval",itemId:payload.itemId??(kind==="commandExecution"?"approval-command":"approval-file-change"),startedAtMs:Date.now()};return kind==="fileChange"?{id:requestId,method:"item/fileChange/requestApproval",params:{...base,reason:payload.reason??null,grantRoot:payload.grantRoot??null}}:{id:requestId,method:"item/commandExecution/requestApproval",params:{...base,approvalId:payload.approvalId??null,environmentId:payload.environmentId??null,reason:payload.reason??null,command:payload.command??null,cwd:payload.cwd??null,availableDecisions:[...APPROVAL_DECISIONS]}}}function decisionToOutcome(decision,reason){return decision==="accept"||decision==="acceptForSession"?{allow:!0,decision}:reason?{allow:!1,decision,reason}:{allow:!1,decision}}function readDecision(response){let decision=isRecord2(response.result)?response.result.decision:void 0;return isApprovalDecision(decision)?decision:"cancel"}function readReason(response){let resultReason=isRecord2(response.result)?response.result.reason??response.result.message:void 0,errorReason=isRecord2(response.error)?response.error.message:void 0,reason=typeof resultReason=="string"?resultReason:errorReason;return typeof reason=="string"&&reason.length>0?reason:void 0}function createAllowKey(threadId,kind,payload){if(!(!payload.toolName||typeof payload.command!="string"))return`${threadId}\0${kind}\0${payload.toolName}\0${payload.command}`}function isApprovalDecision(value){return value==="accept"||value==="acceptForSession"||value==="decline"||value==="cancel"}function isRecord2(value){return typeof value=="object"&&value!==null&&!Array.isArray(value)}function createAppServerUIContext(bridge,threadId,userInput,getTurnId=()=>"turn-user-input"){let pendingInput,editorText="",editorFactory,toolsExpanded=!1;return{question(request,opts){return userInput?.requestUserInput(threadId,getTurnId(),request.requestId,request,opts)??Promise.resolve({status:"unavailable",answers:{},unanswered:request.questions.map(question=>question.id)})},async select(title,options){let prompt=parsePermissionPrompt(title);if(!prompt||!isPermissionOptions(options))return;let outcome=await bridge.requestApproval(threadId,prompt.kind,{turnId:"turn-approval",itemId:`approval-${prompt.toolName}`,toolName:prompt.toolName,command:prompt.command,reason:prompt.reason});return outcome.decision==="accept"?"Allow once":outcome.decision==="acceptForSession"?"Allow always":outcome.reason&&outcome.reason!==NO_SUBSCRIBER_REASON&&outcome.reason!==CANCEL_REASON?(pendingInput=outcome.reason,"Deny with feedback"):"Deny"},async confirm(title,message){return(await bridge.requestApproval(threadId,"commandExecution",{turnId:"turn-approval",itemId:"approval-confirm",toolName:"confirm",command:title,reason:message})).allow},async input(){let value=pendingInput;return pendingInput=void 0,value},notify(){},onTerminalInput(){return()=>{}},setStatus(){},setWorkingMessage(){},setWorkingVisible(){},setWorkingIndicator(){},setHiddenThinkingLabel(){},setWidget(){},setFooter(){},setHeader(){},setTitle(){},custom(){return Promise.reject(new Error("Custom UI is not available in app-server mode."))},pasteToEditor(text){editorText=text},setEditorText(text){editorText=text},getEditorText(){return editorText},async editor(_title,prefill){return prefill},addAutocompleteProvider(){},setEditorComponent(factory){editorFactory=factory},getEditorComponent(){return editorFactory},theme,getAllThemes:getAvailableThemesWithPaths,getTheme(name){return getThemeByName(name)},setTheme(_nextTheme){return{success:!1,error:"Theme switching is not available in app-server mode."}},getToolsExpanded(){return toolsExpanded},setToolsExpanded(expanded){toolsExpanded=expanded}}}function parsePermissionPrompt(title){let[firstLine="",...rest]=title.split(`
|
|
4
4
|
`),prefix="Permission required: ";if(!firstLine.startsWith(prefix))return;let toolName=firstLine.slice(prefix.length).trim(),reason=rest.join(`
|
|
5
5
|
`).trim();return{kind:permissionKind(toolName),toolName,command:readLineValue(reason,"Command: $ ")??readLineValue(reason,"File: ")??readLineValue(reason,"Path: ")??null,reason}}function permissionKind(toolName){return toolName==="edit"||toolName==="write"||toolName==="apply_patch"||toolName==="multiedit"?"fileChange":"commandExecution"}function readLineValue(text,prefix){return text.split(`
|