@code-yeongyu/senpi 2026.9.17-2 → 2026.9.17-3
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 +35 -0
- package/dist/bundle/chunks/{app-server-command-XSPV6BRF.js → app-server-command-HDTNYVSW.js} +1 -1
- package/dist/bundle/chunks/{chunk-PLUQUK3T.js → chunk-CUBEOOBJ.js} +15 -15
- package/dist/bundle/chunks/{chunk-ESZHT7IE.js → chunk-IMTGM3RJ.js} +1 -1
- package/dist/bundle/chunks/chunk-OQGI752B.js +2 -0
- package/dist/bundle/chunks/{chunk-Z56RXBL6.js → chunk-QDR3GME3.js} +2 -2
- package/dist/bundle/chunks/{chunk-ZPO5FWHM.js → chunk-QILSJ5PQ.js} +2 -2
- package/dist/bundle/chunks/chunk-V6IBT2QS.js +8 -0
- package/dist/bundle/chunks/{chunk-IGKWKAMN.js → chunk-XHTF6VKZ.js} +4 -3
- package/dist/bundle/chunks/{cli-main-L7WQP5V6.js → cli-main-IPY5CCRP.js} +1 -1
- package/dist/bundle/chunks/{help-fast-path-O6MBSNMK.js → help-fast-path-LFGHCIWO.js} +1 -1
- package/dist/bundle/chunks/{host-lifecycle-AI7NH6ZO.js → host-lifecycle-YPVGQSGS.js} +1 -1
- package/dist/bundle/chunks/{interactive-host-runtime-DKJKH5DU.js → interactive-host-runtime-KZLF3AZD.js} +1 -1
- package/dist/bundle/chunks/{interactive-mode-FUW5LNKL.js → interactive-mode-HFE7ROP2.js} +1 -1
- package/dist/bundle/chunks/multi-session-host-VMVADJFM.js +20 -0
- package/dist/bundle/chunks/{package-manager-cli-J26DB6GM.js → package-manager-cli-AX4WQCCG.js} +1 -1
- package/dist/bundle/chunks/rotation-stream-FSZVBG2T.js +2 -0
- package/dist/bundle/chunks/{rpc-mode-A3U63XX7.js → rpc-mode-PDYLQ5BZ.js} +1 -1
- package/dist/bundle/chunks/session-worker.js +71 -68
- 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 +19 -1
- package/dist/cli/args.d.ts.map +1 -1
- package/dist/cli/args.js +27 -1
- package/dist/cli/args.js.map +1 -1
- package/dist/core/agent-session-runtime.d.ts +14 -1
- package/dist/core/agent-session-runtime.d.ts.map +1 -1
- package/dist/core/agent-session-runtime.js.map +1 -1
- package/dist/core/auth-storage.js +2 -2
- package/dist/core/auth-storage.js.map +1 -1
- package/dist/core/credential-pool/rotation-stream.d.ts.map +1 -1
- package/dist/core/credential-pool/rotation-stream.js +10 -5
- package/dist/core/credential-pool/rotation-stream.js.map +1 -1
- package/dist/core/extensions/builtin/mcp/index.d.ts.map +1 -1
- package/dist/core/extensions/builtin/mcp/index.js +11 -0
- package/dist/core/extensions/builtin/mcp/index.js.map +1 -1
- package/dist/core/extensions/builtin/mcp/service.d.ts +9 -0
- package/dist/core/extensions/builtin/mcp/service.d.ts.map +1 -1
- package/dist/core/extensions/builtin/mcp/service.js +14 -1
- package/dist/core/extensions/builtin/mcp/service.js.map +1 -1
- package/dist/core/extensions/builtin/mcp/startup-race.d.ts +20 -0
- package/dist/core/extensions/builtin/mcp/startup-race.d.ts.map +1 -1
- package/dist/core/extensions/builtin/mcp/startup-race.js +38 -1
- package/dist/core/extensions/builtin/mcp/startup-race.js.map +1 -1
- package/dist/core/extensions/index.d.ts +2 -2
- package/dist/core/extensions/index.d.ts.map +1 -1
- package/dist/core/extensions/index.js +1 -1
- package/dist/core/extensions/index.js.map +1 -1
- package/dist/core/extensions/loader.d.ts +9 -4
- package/dist/core/extensions/loader.d.ts.map +1 -1
- package/dist/core/extensions/loader.js +20 -9
- package/dist/core/extensions/loader.js.map +1 -1
- package/dist/core/extensions/types.d.ts +37 -0
- package/dist/core/extensions/types.d.ts.map +1 -1
- package/dist/core/extensions/types.js +8 -0
- package/dist/core/extensions/types.js.map +1 -1
- package/dist/core/footer-data-provider.d.ts +2 -1
- package/dist/core/footer-data-provider.d.ts.map +1 -1
- package/dist/core/footer-data-provider.js +21 -35
- package/dist/core/footer-data-provider.js.map +1 -1
- package/dist/core/model-registry.d.ts.map +1 -1
- package/dist/core/model-registry.js +3 -2
- package/dist/core/model-registry.js.map +1 -1
- package/dist/core/model-runtime.d.ts +4 -2
- package/dist/core/model-runtime.d.ts.map +1 -1
- package/dist/core/model-runtime.js +9 -5
- package/dist/core/model-runtime.js.map +1 -1
- package/dist/core/provider-api-key-auth.js +3 -3
- package/dist/core/provider-api-key-auth.js.map +1 -1
- package/dist/core/provider-composer.d.ts +9 -3
- package/dist/core/provider-composer.d.ts.map +1 -1
- package/dist/core/provider-composer.js +7 -4
- package/dist/core/provider-composer.js.map +1 -1
- package/dist/core/resolve-config-command.d.ts +10 -0
- package/dist/core/resolve-config-command.d.ts.map +1 -0
- package/dist/core/resolve-config-command.js +106 -0
- package/dist/core/resolve-config-command.js.map +1 -0
- package/dist/core/resolve-config-value.d.ts +4 -8
- package/dist/core/resolve-config-value.d.ts.map +1 -1
- package/dist/core/resolve-config-value.js +16 -105
- package/dist/core/resolve-config-value.js.map +1 -1
- package/dist/core/resource-loader.d.ts +7 -1
- package/dist/core/resource-loader.d.ts.map +1 -1
- package/dist/core/resource-loader.js +13 -9
- package/dist/core/resource-loader.js.map +1 -1
- package/dist/main.d.ts +5 -3
- package/dist/main.d.ts.map +1 -1
- package/dist/main.js +25 -11
- package/dist/main.js.map +1 -1
- package/dist/modes/rpc/child-reaper-syscalls.d.ts +18 -0
- package/dist/modes/rpc/child-reaper-syscalls.d.ts.map +1 -0
- package/dist/modes/rpc/child-reaper-syscalls.js +136 -0
- package/dist/modes/rpc/child-reaper-syscalls.js.map +1 -0
- package/dist/modes/rpc/child-reaper.d.ts +56 -0
- package/dist/modes/rpc/child-reaper.d.ts.map +1 -0
- package/dist/modes/rpc/child-reaper.js +140 -0
- package/dist/modes/rpc/child-reaper.js.map +1 -0
- package/dist/modes/rpc/custom-capability.d.ts +16 -0
- package/dist/modes/rpc/custom-capability.d.ts.map +1 -1
- package/dist/modes/rpc/custom-capability.js +16 -0
- package/dist/modes/rpc/custom-capability.js.map +1 -1
- package/dist/modes/rpc/host-memory-sampler.d.ts +48 -0
- package/dist/modes/rpc/host-memory-sampler.d.ts.map +1 -0
- package/dist/modes/rpc/host-memory-sampler.js +72 -0
- package/dist/modes/rpc/host-memory-sampler.js.map +1 -0
- package/dist/modes/rpc/loop-lag-watchdog.d.ts +51 -0
- package/dist/modes/rpc/loop-lag-watchdog.d.ts.map +1 -0
- package/dist/modes/rpc/loop-lag-watchdog.js +78 -0
- package/dist/modes/rpc/loop-lag-watchdog.js.map +1 -0
- package/dist/modes/rpc/multi-session-host.d.ts.map +1 -1
- package/dist/modes/rpc/multi-session-host.js +34 -0
- package/dist/modes/rpc/multi-session-host.js.map +1 -1
- package/dist/modes/rpc/rpc-client.d.ts +4 -2
- package/dist/modes/rpc/rpc-client.d.ts.map +1 -1
- package/dist/modes/rpc/rpc-client.js +2 -1
- package/dist/modes/rpc/rpc-client.js.map +1 -1
- package/dist/modes/rpc/rpc-input-validation.d.ts +28 -0
- package/dist/modes/rpc/rpc-input-validation.d.ts.map +1 -1
- package/dist/modes/rpc/rpc-input-validation.js +63 -0
- package/dist/modes/rpc/rpc-input-validation.js.map +1 -1
- package/dist/modes/rpc/rpc-mode.d.ts +18 -5
- package/dist/modes/rpc/rpc-mode.d.ts.map +1 -1
- package/dist/modes/rpc/rpc-mode.js +18 -5
- package/dist/modes/rpc/rpc-mode.js.map +1 -1
- package/dist/modes/rpc/rpc-types.d.ts +79 -2
- package/dist/modes/rpc/rpc-types.d.ts.map +1 -1
- package/dist/modes/rpc/rpc-types.js +6 -0
- package/dist/modes/rpc/rpc-types.js.map +1 -1
- package/dist/modes/rpc/session-attribution.d.ts +57 -0
- package/dist/modes/rpc/session-attribution.d.ts.map +1 -0
- package/dist/modes/rpc/session-attribution.js +132 -0
- package/dist/modes/rpc/session-attribution.js.map +1 -0
- package/dist/modes/rpc/session-binding.d.ts.map +1 -1
- package/dist/modes/rpc/session-binding.js +19 -8
- package/dist/modes/rpc/session-binding.js.map +1 -1
- package/dist/modes/rpc/session-command-router.d.ts +26 -3
- package/dist/modes/rpc/session-command-router.d.ts.map +1 -1
- package/dist/modes/rpc/session-command-router.js +95 -19
- package/dist/modes/rpc/session-command-router.js.map +1 -1
- package/dist/modes/rpc/session-event-fanout.d.ts +2 -0
- package/dist/modes/rpc/session-event-fanout.d.ts.map +1 -1
- package/dist/modes/rpc/session-event-fanout.js +6 -0
- package/dist/modes/rpc/session-event-fanout.js.map +1 -1
- package/dist/modes/rpc/session-event-writer.d.ts +29 -0
- package/dist/modes/rpc/session-event-writer.d.ts.map +1 -1
- package/dist/modes/rpc/session-event-writer.js +57 -0
- package/dist/modes/rpc/session-event-writer.js.map +1 -1
- package/dist/modes/rpc/session-registry.d.ts +42 -9
- package/dist/modes/rpc/session-registry.d.ts.map +1 -1
- package/dist/modes/rpc/session-registry.js +49 -3
- package/dist/modes/rpc/session-registry.js.map +1 -1
- package/dist/modes/rpc/worker-session-registry.d.ts +2 -10
- package/dist/modes/rpc/worker-session-registry.d.ts.map +1 -1
- package/dist/modes/rpc/worker-session-registry.js +6 -2
- package/dist/modes/rpc/worker-session-registry.js.map +1 -1
- package/docs/rpc.md +126 -20
- package/node_modules/@code-yeongyu/senpi-codemode/CHANGELOG.md +12 -0
- package/node_modules/@code-yeongyu/senpi-codemode/package.json +4 -4
- package/node_modules/@earendil-works/pi-agent-core/assets/tree-sitter/javascript.wasm +0 -0
- package/node_modules/@earendil-works/pi-agent-core/assets/tree-sitter/provenance.json +22 -0
- package/node_modules/@earendil-works/pi-agent-core/assets/tree-sitter/web-tree-sitter.wasm +0 -0
- 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/cloudflare-ai-gateway.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/zai-coding-cn.json +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
- package/dist/bundle/chunks/chunk-3Q5PD4KW.js +0 -8
- package/dist/bundle/chunks/chunk-YLYLKL7X.js +0 -2
- package/dist/bundle/chunks/multi-session-host-SMNZXFPT.js +0 -18
- package/dist/bundle/chunks/rotation-stream-SCVFIQ2O.js +0 -2
package/CHANGELOG.md
CHANGED
|
@@ -12,6 +12,34 @@
|
|
|
12
12
|
|
|
13
13
|
### Removed
|
|
14
14
|
|
|
15
|
+
## [2026.9.17-3] - 2026-09-17
|
|
16
|
+
|
|
17
|
+
### Breaking Changes
|
|
18
|
+
|
|
19
|
+
### Added
|
|
20
|
+
|
|
21
|
+
- Shared RPC hosts decide session auto-titling per `open_session`. `auto_title: true` titles that session from its first prompt, `auto_title: false` leaves it untitled, and an omitted field keeps the host-wide default (`--auto-title-sessions` or the `auto_title_sessions` client capability) so existing clients stay byte-identical. A non-boolean is refused with `invalid_launch_profile`. Hosts advertise `auto_title_per_session` in `get_protocol_info`. Interactive TUI launches still auto-title by default. `--auto-title-sessions` is not removed; it is deprecated for shared hosts because a host-wide flag collides when two clients share one daemon. ([#1782](https://github.com/code-yeongyu/senpi/issues/1782))
|
|
22
|
+
|
|
23
|
+
- A shared RPC host now tells its clients when it PARKS a retained session instead of closing one. A session opened with `retain_on_disconnect` that reaches the idle-eviction window is released with a `session_parked { sessionId, sessionPath }` record rather than `session_closed`, so a client can reopen it with `open_session { sessionPath }` instead of forgetting it — the session itself is untouched on disk, only its routing handle ends. The teardown and the windows are unchanged: retention still never outlives idle eviction, and a parked session no longer counts as occupancy, so a host holding nothing but parked sessions still exits on its empty-host window. ([#1782](https://github.com/code-yeongyu/senpi/issues/1782))
|
|
24
|
+
|
|
25
|
+
- Shared RPC hosts (`--mode rpc --multi-session`) accept two additive `open_session` fields for machine-driven work. `kind: "worker"` (default `"interactive"`) marks a session as a subagent-style worker: it no longer appears in `list_sessions` unless the caller passes `include_workers: true`, and its `session_closed` record reaches only the connections attached to it, so a client that never opened it is not told about its lifecycle. `context` is an opaque `Record<string,string>` the host stores with the session and hands to that session's extensions as `pi.sessionContext` (with `pi.sessionKind`), so one host with one extension set can serve many sessions and each extension instance still knows which session it is serving; it is republished only on `list_sessions { include_workers: true }`, never on a default listing. The host never interprets `context` — it takes no part in authentication, model selection or resource loading — and refuses anything past its caps with `invalid_session_context` (at most 32 keys matching `^[a-z][a-z0-9_]*$`, each value at most 16 KiB, at most 32 KiB of JSON in total); an unrecognised `kind` is refused with `invalid_session_kind` instead of being treated as interactive. Every `list_sessions` row now carries `kind`, interactive sessions keep today's broadcast lifecycle records, and hosts advertise `session_kind` and `session_context` in `get_protocol_info` so a client can probe before relying on either. ([#1782](https://github.com/code-yeongyu/senpi/issues/1782))
|
|
26
|
+
|
|
27
|
+
- A shared RPC host now reports when one session freezes it. Its own 200ms timer measures how late it is invoked, and that lateness is the time the host could serve nobody: past 500ms it writes one stderr line per 10 seconds naming the drift, the session and the tool that held the loop, and past 5 seconds it also sends every connected client a `host_stalled` record (`{ driftMs, sessionId?, tool? }`) so a UI can show which conversation is blocking the rest. The host also samples its own memory every 30 seconds: above 4096 MB resident it sends `host_memory_pressure` (`{ rssMb, sessions }`), writes one stderr line per five minutes, and parks idle sessions at half the usual window until memory falls back - it still never refuses, caps, or kills a session, since capacity is memory rather than a refusal. Thresholds are tunable with `SENPI_RPC_LOOP_LAG_WARN_MS`, `SENPI_RPC_LOOP_LAG_ERROR_MS` and `SENPI_RPC_HOST_RSS_WARN_MB`; both records are additive and older clients ignore them. ([#1782](https://github.com/code-yeongyu/senpi/issues/1782))
|
|
28
|
+
|
|
29
|
+
- Shared RPC hosts accept `--session-runtime in-process|worker`, selecting where the host runs its sessions. A socket host (`--mode rpc --listen …`) now defaults to `in-process`: every session runs in the host process, so one shared host serves as many sessions as its memory allows and never answers `open_session` with `too_many_sessions`. Stdio hosts (`--mode rpc --multi-session`, `--listen stdio://`) and embedders keep the worker runtime, where each session owns a worker isolate and the existing 20-worker capacity applies; passing `--session-runtime worker` restores that runtime for a socket host too. The wire protocol, attachment semantics and lifecycle windows are identical on both runtimes; an in-process session shares the host event loop and has no per-session opening deadline. ([#1782](https://github.com/code-yeongyu/senpi/issues/1782))
|
|
30
|
+
|
|
31
|
+
### Changed
|
|
32
|
+
|
|
33
|
+
### Fixed
|
|
34
|
+
- The z.ai Coding Plan tests follow the provider's current catalog. Upstream retired `glm-4.7`, `glm-5.1`, `glm-5.2`, `glm-5.2-highspeed`, `glm-5-turbo` and `glm-5v-turbo` from `zai-coding-cn` in favour of the `glm-5.3` family, so assertions naming the retired ids stopped type-checking the moment the release regenerated the catalog, which blocked publishing. The suites now assert models both providers actually carry, and the `glm-5.3` reasoning map (`off: null`, `low: "low"`) rather than the `5.2` shape.
|
|
35
|
+
|
|
36
|
+
- The published package now ships `@earendil-works/pi-agent-core`'s tree-sitter assets. Its `dist` reaches those grammars through compile-time `type: "file"` imports, but publish staging copied only `dist`, `native` and the manifest files, so the tarball carried specifiers pointing five directories above the package at files that were never published. Any consumer bundling senpi with `bun build --compile` failed to resolve them. Staging now copies a bundled workspace's `assets` as well, and the pack gate requires those two grammar files so a future drop fails the release instead of the consumer. ([#1800](https://github.com/code-yeongyu/senpi/issues/1800))
|
|
37
|
+
|
|
38
|
+
|
|
39
|
+
- A new session's first turn no longer goes out without an MCP server's instructions and tools. Taking attach off the first-paint path means `session_start` returns while a cold server is still booting, and the first turn's system prompt was assembled from whatever had landed by then - on a loaded machine that is an empty catalog, so the turn carried no `<mcp_instructions>` block and none of that server's tools, and the instructions stay missing for the rest of the session because they are captured once per session. The prompt build now awaits the attach's own completion signal, bounded at 5 s; a server still connecting past that no longer blocks the turn, and its catalog lands on a later turn. ([#1797](https://github.com/code-yeongyu/senpi/issues/1797))
|
|
40
|
+
|
|
41
|
+
### Removed
|
|
42
|
+
|
|
15
43
|
## [2026.9.17-2] - 2026-09-17
|
|
16
44
|
|
|
17
45
|
### Breaking Changes
|
|
@@ -46,6 +74,13 @@
|
|
|
46
74
|
- An `auto` theme no longer repaints on every launch. Making theme detection non-blocking meant the first frame is painted from a guess, and for `light/dark` that guess came from `COLORFGBG` alone - unset by most terminals - while the detected answer was never remembered. A user on a light terminal therefore got a dark first frame on every start, corrected one OSC round trip later. The detected terminal background is now remembered in `<agentDir>/cache/terminal-theme.json` and seeds the next launch, so the repaint happens at most once after install; the file is written atomically and a missing or malformed one simply falls back to the environment guess. ([#1781](https://github.com/code-yeongyu/senpi/issues/1781))
|
|
47
75
|
|
|
48
76
|
|
|
77
|
+
- Reopening a session path right after it was closed no longer fails on a shared RPC host. `open_session { sessionPath }` was refused with `session_path_in_use` while the previous session was still being disposed, so "close this session, then open its file again" — and the same reopen after the last client of a closed session dropped — succeeded or failed depending on how long that disposal happened to take, which no client can time (measured on a loaded host: the path stayed refused for 300 ms to 1 s after the close). This is a long-standing behaviour of shared hosts, present since multi-session hosts shipped, not something introduced this release. The open now waits for a teardown already in flight and then opens the file fresh, bounded by the same close-grace window that bounds the teardown, so a wedged disposal still ends in the old refusal instead of an open that never answers. Attaching to a live session, retained sessions, parked sessions and the duplicate-open refusal while a session is still opening are all unchanged. ([#1782](https://github.com/code-yeongyu/senpi/issues/1782))
|
|
78
|
+
|
|
79
|
+
- A shared RPC host no longer lets one client close a session it never opened. Routing handles are published to every client through `list_sessions`, and `close_session` released an attachment for whoever asked — so a client that had never attached could release another client's attachment and tear down a single-client session it did not own. Such a close is now refused with `unknown_session` and leaves the session untouched; a connection that opened or attached to the session closes it exactly as before. ([#1782](https://github.com/code-yeongyu/senpi/issues/1782))
|
|
80
|
+
|
|
81
|
+
- One conversation can no longer freeze a shared RPC host while a credential is fetched. A stored API key written as a `!command` (an auth broker, a keychain helper) was executed synchronously while a session started, and a repository whose HEAD needs `git` to name the branch ran that probe synchronously too - on a host that runs its sessions in one process, every other session stopped for the duration. Both now run off the event loop: measured on a live host, a credential helper that takes three seconds used to make an unrelated session's `get_state` take 15.3 seconds and now answers in 1 millisecond, with the same 10-second per-attempt deadline, the same three attempts and the same backoff as before. Two callers of the same command now share one execution instead of running it twice. In a reftable repository the footer shows `detached` for the instant it takes `git` to answer and then updates itself. ([#1782](https://github.com/code-yeongyu/senpi/issues/1782))
|
|
82
|
+
- A shared RPC host over a socket no longer accumulates zombie processes. When a session's worker thread is terminated while it still has running commands — the quarantine a stuck session close performs — the exited children of that thread had nobody left to wait on them and stayed in the process table for the life of the host, which on a long-running machine-wide host meant they never went away (measured: every spawn API leaks 20 of 20 such children, under the source runtime, the compiled binary and Node alike). A socket host now watches its own direct children once a second and claims only those that have been finished and unclaimed for at least 30 seconds, so a command's own exit status is never taken from the code waiting for it; commands that finish normally are unaffected, and no child of any other process is ever touched. Set `SENPI_RPC_HOST_REAPER=0` to turn it off, or `SENPI_RPC_HOST_REAPER_MIN_WAITABLE_MS` to widen the window. Hosts running under Node instead of the shipped binary log one warning at startup and keep the old behaviour. ([#1782](https://github.com/code-yeongyu/senpi/issues/1782))
|
|
83
|
+
|
|
49
84
|
- Fixed `claude-sdk-oauth` re-login never refreshing the existing account: a successful login now replaces the same-name slot in place (clearing its `auth_error` block and preserving its display name) instead of appending `account-N+1` or failing on a duplicate name, so "blocked until re-login" actually lifts on re-login. A blank or headless re-login only ever targets a lone slot or the pool's one auth-blocked slot, never the newest working slot of a multi-account pool. ([#7084](https://github.com/code-yeongyu/oh-my-openagent/issues/7084))
|
|
50
85
|
- Fixed the Anthropic OAuth import forking one single-use refresh token into two stores: accepting the import now moves the grant out of the `anthropic` provider. ([#7084](https://github.com/code-yeongyu/oh-my-openagent/issues/7084))
|
|
51
86
|
- Fixed stored credential-pool blocks outliving the credential that earned them: stored-lane sidecar health is bound to a credential revision (an HMAC of the slot material, never the material itself), so a re-login or token refresh retires stale `auth_error` and cooldown blocks the way env-key rotation already did. ([#7084](https://github.com/code-yeongyu/oh-my-openagent/issues/7084), [#8383](https://github.com/code-yeongyu/oh-my-openagent/issues/8383))
|
package/dist/bundle/chunks/{app-server-command-XSPV6BRF.js → app-server-command-HDTNYVSW.js}
RENAMED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { createRequire as __piCreateRequire } from "node:module"; const require = __piCreateRequire(import.meta.url);
|
|
2
|
-
import{import_websocket_server,wrapper_default}from"./chunk-HSIJ6RLW.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-HSIJ6RLW.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-CUBEOOBJ.js";import"./chunk-OZINB5JY.js";import{SessionManager,loadEntriesFromFile}from"./chunk-QDR3GME3.js";import{acquireOwnershipSafeLock,parseDaemonPidFile,processMatchesPidFile,readProcessStartTime,stopValidatedPid,waitForGone,waitForStartTime}from"./chunk-IMTGM3RJ.js";import{getCredentialAccounts,getModels,getProviders,getSupportedThinkingLevels,pinCredentialAccount,removeCredentialAccount,subscribeProviderAccountEvents}from"./chunk-V6IBT2QS.js";import{attachJsonlLineReader}from"./chunk-QILSJ5PQ.js";import{require_ignore}from"./chunk-B5GQRD32.js";import"./chunk-2WKZYYAR.js";import{getAvailableThemesWithPaths,getThemeByName,takeOverStdout,theme,waitForRawStdoutBackpressure,writeRawStdout}from"./chunk-Z7IRPMTE.js";import{x}from"./chunk-WINYWZE2.js";import{SettingsManager}from"./chunk-4QHFEW27.js";import"./chunk-U7N2NMGI.js";import"./chunk-SGTGQNNP.js";import"./chunk-ZQNNIPKX.js";import"./chunk-EAAHPEGN.js";import"./chunk-YAMKKQV7.js";import"./chunk-ESYNSFBY.js";import"./chunk-OQGI752B.js";import"./chunk-34UXOYIZ.js";import"./chunk-KPSJKRO6.js";import"./chunk-WHS3V662.js";import"./chunk-EN5HLCZK.js";import"./chunk-VYQOYVDV.js";import"./chunk-QL422WQ7.js";import"./chunk-K7HIYM6P.js";import"./chunk-HHQCPUMK.js";import"./chunk-ZG74NES2.js";import"./chunk-JKCCH24R.js";import"./chunk-T6SHTZ3M.js";import"./chunk-EFXPIEEC.js";import"./chunk-7DDKXS2Q.js";import"./chunk-U456RL4N.js";import"./chunk-TEPMHNKQ.js";import"./chunk-B2F5G2RY.js";import"./chunk-7DGJYBYJ.js";import"./chunk-L2ZPNLYD.js";import"./chunk-D2D2LIVO.js";import"./chunk-6R7UI4IY.js";import"./chunk-AHUSODXB.js";import"./chunk-PVXEILUA.js";import"./chunk-HMUSFTXZ.js";import"./chunk-4G2VXEY3.js";import"./chunk-XHTF6VKZ.js";import"./chunk-GXONGRGB.js";import"./chunk-6QGV3IWD.js";import"./chunk-K22YTJFL.js";import{APP_NAME,CONFIG_DIR_NAME,ENV_SESSION_DIR,VERSION,getAgentDir,getSessionsDir,resolvePath}from"./chunk-PW6XRLS6.js";import"./chunk-KZ4SIPGM.js";import{__toESM}from"./chunk-JXWJOIH4.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(`
|