@code-yeongyu/senpi 2026.8.26 → 2026.8.28
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 +76 -0
- package/README.md +3 -3
- package/dist/cli/args.d.ts +3 -1
- package/dist/cli/args.d.ts.map +1 -1
- package/dist/cli/args.js +14 -1
- package/dist/cli/args.js.map +1 -1
- package/dist/core/agent-session.d.ts.map +1 -1
- package/dist/core/agent-session.js +10 -1
- package/dist/core/agent-session.js.map +1 -1
- package/dist/core/bash-executor.d.ts.map +1 -1
- package/dist/core/bash-executor.js +67 -29
- package/dist/core/bash-executor.js.map +1 -1
- package/dist/core/credential-accounts.d.ts +31 -0
- package/dist/core/credential-accounts.d.ts.map +1 -0
- package/dist/core/credential-accounts.js +125 -0
- package/dist/core/credential-accounts.js.map +1 -0
- package/dist/core/credential-pool/classify.d.ts +42 -0
- package/dist/core/credential-pool/classify.d.ts.map +1 -0
- package/dist/core/credential-pool/classify.js +75 -0
- package/dist/core/credential-pool/classify.js.map +1 -0
- package/dist/core/credential-pool/env-slots.d.ts +22 -0
- package/dist/core/credential-pool/env-slots.d.ts.map +1 -0
- package/dist/core/credential-pool/env-slots.js +37 -0
- package/dist/core/credential-pool/env-slots.js.map +1 -0
- package/dist/core/credential-pool/failover.d.ts +54 -0
- package/dist/core/credential-pool/failover.d.ts.map +1 -0
- package/dist/core/credential-pool/failover.js +106 -0
- package/dist/core/credential-pool/failover.js.map +1 -0
- package/dist/core/credential-pool/rotation-stream.d.ts +43 -0
- package/dist/core/credential-pool/rotation-stream.d.ts.map +1 -0
- package/dist/core/credential-pool/rotation-stream.js +96 -0
- package/dist/core/credential-pool/rotation-stream.js.map +1 -0
- package/dist/core/credential-pool/state-store.d.ts +89 -0
- package/dist/core/credential-pool/state-store.d.ts.map +1 -0
- package/dist/core/credential-pool/state-store.js +166 -0
- package/dist/core/credential-pool/state-store.js.map +1 -0
- package/dist/core/extensions/builtin/account/index.d.ts +9 -0
- package/dist/core/extensions/builtin/account/index.d.ts.map +1 -0
- package/dist/core/extensions/builtin/account/index.js +69 -0
- package/dist/core/extensions/builtin/account/index.js.map +1 -0
- package/dist/core/extensions/builtin/compaction/index.d.ts.map +1 -1
- package/dist/core/extensions/builtin/compaction/index.js +67 -0
- package/dist/core/extensions/builtin/compaction/index.js.map +1 -1
- package/dist/core/extensions/builtin/compaction/log.d.ts +1 -1
- package/dist/core/extensions/builtin/compaction/log.d.ts.map +1 -1
- package/dist/core/extensions/builtin/compaction/log.js +1 -0
- package/dist/core/extensions/builtin/compaction/log.js.map +1 -1
- package/dist/core/extensions/builtin/config-reload/index.js +14 -22
- package/dist/core/extensions/builtin/config-reload/index.js.map +1 -1
- package/dist/core/extensions/builtin/goal/continuation-recovery.d.ts +1 -0
- package/dist/core/extensions/builtin/goal/continuation-recovery.d.ts.map +1 -1
- package/dist/core/extensions/builtin/goal/continuation-recovery.js +2 -0
- package/dist/core/extensions/builtin/goal/continuation-recovery.js.map +1 -1
- package/dist/core/extensions/builtin/goal/continuation.d.ts +8 -1
- package/dist/core/extensions/builtin/goal/continuation.d.ts.map +1 -1
- package/dist/core/extensions/builtin/goal/continuation.js +15 -0
- package/dist/core/extensions/builtin/goal/continuation.js.map +1 -1
- package/dist/core/extensions/builtin/goal/direct-input-lifecycle.js +2 -2
- package/dist/core/extensions/builtin/goal/direct-input-lifecycle.js.map +1 -1
- package/dist/core/extensions/builtin/goal/format.d.ts +6 -1
- package/dist/core/extensions/builtin/goal/format.d.ts.map +1 -1
- package/dist/core/extensions/builtin/goal/format.js +6 -0
- package/dist/core/extensions/builtin/goal/format.js.map +1 -1
- package/dist/core/extensions/builtin/goal/lifecycle-helpers.d.ts.map +1 -1
- package/dist/core/extensions/builtin/goal/lifecycle-helpers.js +5 -2
- package/dist/core/extensions/builtin/goal/lifecycle-helpers.js.map +1 -1
- package/dist/core/extensions/builtin/goal/monitor-continuation.d.ts.map +1 -1
- package/dist/core/extensions/builtin/goal/monitor-continuation.js +31 -7
- package/dist/core/extensions/builtin/goal/monitor-continuation.js.map +1 -1
- package/dist/core/extensions/builtin/goal/persistence.js +2 -0
- package/dist/core/extensions/builtin/goal/persistence.js.map +1 -1
- package/dist/core/extensions/builtin/goal/renderers.d.ts +14 -0
- package/dist/core/extensions/builtin/goal/renderers.d.ts.map +1 -0
- package/dist/core/extensions/builtin/goal/renderers.js +153 -0
- package/dist/core/extensions/builtin/goal/renderers.js.map +1 -0
- package/dist/core/extensions/builtin/goal/store.d.ts +6 -2
- package/dist/core/extensions/builtin/goal/store.d.ts.map +1 -1
- package/dist/core/extensions/builtin/goal/store.js +8 -2
- package/dist/core/extensions/builtin/goal/store.js.map +1 -1
- package/dist/core/extensions/builtin/goal/tool-registration.d.ts.map +1 -1
- package/dist/core/extensions/builtin/goal/tool-registration.js +16 -6
- package/dist/core/extensions/builtin/goal/tool-registration.js.map +1 -1
- package/dist/core/extensions/builtin/goal/types.d.ts +1 -0
- package/dist/core/extensions/builtin/goal/types.d.ts.map +1 -1
- package/dist/core/extensions/builtin/goal/types.js.map +1 -1
- package/dist/core/extensions/builtin/imagegen/index.d.ts.map +1 -1
- package/dist/core/extensions/builtin/imagegen/index.js +10 -3
- package/dist/core/extensions/builtin/imagegen/index.js.map +1 -1
- package/dist/core/extensions/builtin/index.d.ts.map +1 -1
- package/dist/core/extensions/builtin/index.js +4 -0
- package/dist/core/extensions/builtin/index.js.map +1 -1
- package/dist/core/extensions/builtin/todotools/index.js +1 -1
- package/dist/core/extensions/builtin/todotools/index.js.map +1 -1
- package/dist/core/extensions/builtin/todotools/native-todo-mirror.d.ts +1 -1
- package/dist/core/extensions/builtin/todotools/native-todo-mirror.d.ts.map +1 -1
- package/dist/core/extensions/builtin/todotools/native-todo-mirror.js +1 -1
- package/dist/core/extensions/builtin/todotools/native-todo-mirror.js.map +1 -1
- package/dist/core/hidden-stdout-log.d.ts +10 -0
- package/dist/core/hidden-stdout-log.d.ts.map +1 -1
- package/dist/core/hidden-stdout-log.js +36 -5
- package/dist/core/hidden-stdout-log.js.map +1 -1
- package/dist/core/model-config-schema.d.ts +85 -12
- package/dist/core/model-config-schema.d.ts.map +1 -1
- package/dist/core/model-config-schema.js +25 -0
- package/dist/core/model-config-schema.js.map +1 -1
- package/dist/core/model-runtime.d.ts +34 -1
- package/dist/core/model-runtime.d.ts.map +1 -1
- package/dist/core/model-runtime.js +98 -5
- package/dist/core/model-runtime.js.map +1 -1
- package/dist/core/tools/output-accumulator.d.ts +2 -0
- package/dist/core/tools/output-accumulator.d.ts.map +1 -1
- package/dist/core/tools/output-accumulator.js +32 -7
- package/dist/core/tools/output-accumulator.js.map +1 -1
- package/dist/index.d.ts +1 -1
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +1 -1
- package/dist/index.js.map +1 -1
- package/dist/main.d.ts.map +1 -1
- package/dist/main.js +38 -3
- package/dist/main.js.map +1 -1
- package/dist/modes/app-server/server/account.d.ts.map +1 -1
- package/dist/modes/app-server/server/account.js +5 -5
- package/dist/modes/app-server/server/account.js.map +1 -1
- package/dist/modes/index.d.ts +1 -0
- package/dist/modes/index.d.ts.map +1 -1
- package/dist/modes/index.js +1 -0
- package/dist/modes/index.js.map +1 -1
- package/dist/modes/interactive/components/footer.d.ts +11 -3
- package/dist/modes/interactive/components/footer.d.ts.map +1 -1
- package/dist/modes/interactive/components/footer.js +31 -1
- package/dist/modes/interactive/components/footer.js.map +1 -1
- package/dist/modes/interactive/components/tool-execution-fallback.d.ts.map +1 -1
- package/dist/modes/interactive/components/tool-execution-fallback.js +82 -1
- package/dist/modes/interactive/components/tool-execution-fallback.js.map +1 -1
- package/dist/modes/interactive/grok/chrome.d.ts +4 -4
- package/dist/modes/interactive/grok/chrome.d.ts.map +1 -1
- package/dist/modes/interactive/grok/chrome.js.map +1 -1
- package/dist/modes/interactive/grok/footer.d.ts +3 -3
- package/dist/modes/interactive/grok/footer.d.ts.map +1 -1
- package/dist/modes/interactive/grok/footer.js.map +1 -1
- package/dist/modes/interactive/interactive-host-runtime.d.ts +44 -0
- package/dist/modes/interactive/interactive-host-runtime.d.ts.map +1 -0
- package/dist/modes/interactive/interactive-host-runtime.js +271 -0
- package/dist/modes/interactive/interactive-host-runtime.js.map +1 -0
- package/dist/modes/interactive/interactive-mode.d.ts.map +1 -1
- package/dist/modes/interactive/interactive-mode.js +65 -21
- package/dist/modes/interactive/interactive-mode.js.map +1 -1
- package/dist/modes/rpc/connection-handler.d.ts.map +1 -1
- package/dist/modes/rpc/connection-handler.js +19 -6
- package/dist/modes/rpc/connection-handler.js.map +1 -1
- package/dist/modes/rpc/host-ensure.d.ts +41 -0
- package/dist/modes/rpc/host-ensure.d.ts.map +1 -0
- package/dist/modes/rpc/host-ensure.js +245 -0
- package/dist/modes/rpc/host-ensure.js.map +1 -0
- package/dist/modes/rpc/host-lifecycle.d.ts +57 -0
- package/dist/modes/rpc/host-lifecycle.d.ts.map +1 -0
- package/dist/modes/rpc/host-lifecycle.js +491 -0
- package/dist/modes/rpc/host-lifecycle.js.map +1 -0
- package/dist/modes/rpc/host-watchdog.d.ts +33 -0
- package/dist/modes/rpc/host-watchdog.d.ts.map +1 -0
- package/dist/modes/rpc/host-watchdog.js +142 -0
- package/dist/modes/rpc/host-watchdog.js.map +1 -0
- package/dist/modes/rpc/multi-session-host.d.ts +7 -1
- package/dist/modes/rpc/multi-session-host.d.ts.map +1 -1
- package/dist/modes/rpc/multi-session-host.js +206 -32
- package/dist/modes/rpc/multi-session-host.js.map +1 -1
- package/dist/modes/rpc/rpc-client.d.ts +41 -3
- package/dist/modes/rpc/rpc-client.d.ts.map +1 -1
- package/dist/modes/rpc/rpc-client.js +125 -14
- package/dist/modes/rpc/rpc-client.js.map +1 -1
- package/dist/modes/rpc/rpc-mode.d.ts +1 -1
- package/dist/modes/rpc/rpc-mode.js +1 -1
- package/dist/modes/rpc/rpc-mode.js.map +1 -1
- package/dist/modes/rpc/rpc-types.d.ts +6 -2
- package/dist/modes/rpc/rpc-types.d.ts.map +1 -1
- package/dist/modes/rpc/rpc-types.js.map +1 -1
- package/dist/modes/rpc/session-command-router.d.ts.map +1 -1
- package/dist/modes/rpc/session-command-router.js +24 -6
- package/dist/modes/rpc/session-command-router.js.map +1 -1
- package/dist/modes/rpc/session-event-writer.d.ts +13 -2
- package/dist/modes/rpc/session-event-writer.d.ts.map +1 -1
- package/dist/modes/rpc/session-event-writer.js +65 -25
- package/dist/modes/rpc/session-event-writer.js.map +1 -1
- package/dist/modes/rpc/session-registry.d.ts +9 -0
- package/dist/modes/rpc/session-registry.d.ts.map +1 -1
- package/dist/modes/rpc/session-registry.js +31 -3
- package/dist/modes/rpc/session-registry.js.map +1 -1
- package/docs/providers.md +48 -0
- package/docs/rpc.md +59 -5
- package/node_modules/@code-yeongyu/senpi-codemode/CHANGELOG.md +44 -0
- package/node_modules/@code-yeongyu/senpi-codemode/package.json +4 -4
- package/node_modules/@code-yeongyu/senpi-codemode/src/kernels/js/context-manager.ts +13 -1
- package/node_modules/@code-yeongyu/senpi-codemode/src/kernels/js/inline-worker.ts +15 -1
- package/node_modules/@code-yeongyu/senpi-codemode/src/kernels/js/worker-indirect-eval.js +711 -1
- package/node_modules/@code-yeongyu/senpi-codemode/src/kernels/py/transport.ts +14 -1
- package/node_modules/@code-yeongyu/senpi-codemode/src/prompt/eval-prompt.ts +10 -10
- package/node_modules/@code-yeongyu/senpi-codemode/src/tool/detached-eval-result.ts +9 -3
- package/node_modules/@code-yeongyu/senpi-codemode/src/tool/eval-tool.ts +6 -1
- package/node_modules/@earendil-works/pi-agent-core/dist/harness/tools/edit.d.ts.map +1 -1
- package/node_modules/@earendil-works/pi-agent-core/dist/harness/tools/edit.js +20 -4
- package/node_modules/@earendil-works/pi-agent-core/dist/harness/tools/edit.js.map +1 -1
- package/node_modules/@earendil-works/pi-agent-core/dist/harness/tools/index.d.ts +1 -1
- package/node_modules/@earendil-works/pi-agent-core/dist/harness/tools/index.d.ts.map +1 -1
- package/node_modules/@earendil-works/pi-agent-core/dist/harness/tools/index.js.map +1 -1
- package/node_modules/@earendil-works/pi-agent-core/dist/harness/tools/post-mutate.d.ts +21 -0
- package/node_modules/@earendil-works/pi-agent-core/dist/harness/tools/post-mutate.d.ts.map +1 -0
- package/node_modules/@earendil-works/pi-agent-core/dist/harness/tools/post-mutate.js +27 -0
- package/node_modules/@earendil-works/pi-agent-core/dist/harness/tools/post-mutate.js.map +1 -0
- package/node_modules/@earendil-works/pi-agent-core/dist/harness/tools/tool-context.d.ts +24 -0
- package/node_modules/@earendil-works/pi-agent-core/dist/harness/tools/tool-context.d.ts.map +1 -1
- package/node_modules/@earendil-works/pi-agent-core/dist/harness/tools/tool-context.js.map +1 -1
- package/node_modules/@earendil-works/pi-agent-core/dist/harness/tools/write.d.ts.map +1 -1
- package/node_modules/@earendil-works/pi-agent-core/dist/harness/tools/write.js +7 -2
- package/node_modules/@earendil-works/pi-agent-core/dist/harness/tools/write.js.map +1 -1
- package/node_modules/@earendil-works/pi-agent-core/package.json +3 -3
- package/node_modules/@earendil-works/pi-ai/dist/api/anthropic-tool-pairs.d.ts +1 -1
- package/node_modules/@earendil-works/pi-ai/dist/api/anthropic-tool-pairs.d.ts.map +1 -1
- package/node_modules/@earendil-works/pi-ai/dist/api/anthropic-tool-pairs.js +46 -14
- package/node_modules/@earendil-works/pi-ai/dist/api/anthropic-tool-pairs.js.map +1 -1
- package/node_modules/@earendil-works/pi-ai/dist/api/cursor-agent.d.ts +11 -0
- package/node_modules/@earendil-works/pi-ai/dist/api/cursor-agent.d.ts.map +1 -1
- package/node_modules/@earendil-works/pi-ai/dist/api/cursor-agent.js +30 -16
- package/node_modules/@earendil-works/pi-ai/dist/api/cursor-agent.js.map +1 -1
- package/node_modules/@earendil-works/pi-ai/dist/auth/credential-store.d.ts +2 -1
- package/node_modules/@earendil-works/pi-ai/dist/auth/credential-store.d.ts.map +1 -1
- package/node_modules/@earendil-works/pi-ai/dist/auth/credential-store.js +2 -1
- package/node_modules/@earendil-works/pi-ai/dist/auth/credential-store.js.map +1 -1
- package/node_modules/@earendil-works/pi-ai/dist/auth/pool/classify.d.ts +16 -0
- package/node_modules/@earendil-works/pi-ai/dist/auth/pool/classify.d.ts.map +1 -0
- package/node_modules/@earendil-works/pi-ai/dist/auth/pool/classify.js +75 -0
- package/node_modules/@earendil-works/pi-ai/dist/auth/pool/classify.js.map +1 -0
- package/node_modules/@earendil-works/pi-ai/dist/auth/pool/failover.d.ts +45 -0
- package/node_modules/@earendil-works/pi-ai/dist/auth/pool/failover.d.ts.map +1 -0
- package/node_modules/@earendil-works/pi-ai/dist/auth/pool/failover.js +81 -0
- package/node_modules/@earendil-works/pi-ai/dist/auth/pool/failover.js.map +1 -0
- package/node_modules/@earendil-works/pi-ai/dist/auth/pool/select.d.ts +31 -0
- package/node_modules/@earendil-works/pi-ai/dist/auth/pool/select.d.ts.map +1 -0
- package/node_modules/@earendil-works/pi-ai/dist/auth/pool/select.js +65 -0
- package/node_modules/@earendil-works/pi-ai/dist/auth/pool/select.js.map +1 -0
- package/node_modules/@earendil-works/pi-ai/dist/auth/pool/slots.d.ts +12 -0
- package/node_modules/@earendil-works/pi-ai/dist/auth/pool/slots.d.ts.map +1 -1
- package/node_modules/@earendil-works/pi-ai/dist/auth/pool/slots.js +37 -0
- package/node_modules/@earendil-works/pi-ai/dist/auth/pool/slots.js.map +1 -1
- package/node_modules/@earendil-works/pi-ai/dist/auth/resolve.d.ts +6 -0
- package/node_modules/@earendil-works/pi-ai/dist/auth/resolve.d.ts.map +1 -1
- package/node_modules/@earendil-works/pi-ai/dist/auth/resolve.js +32 -6
- package/node_modules/@earendil-works/pi-ai/dist/auth/resolve.js.map +1 -1
- package/node_modules/@earendil-works/pi-ai/dist/auth/types.d.ts +3 -2
- package/node_modules/@earendil-works/pi-ai/dist/auth/types.d.ts.map +1 -1
- package/node_modules/@earendil-works/pi-ai/dist/auth/types.js.map +1 -1
- package/node_modules/@earendil-works/pi-ai/dist/env-api-keys.d.ts +6 -0
- package/node_modules/@earendil-works/pi-ai/dist/env-api-keys.d.ts.map +1 -1
- package/node_modules/@earendil-works/pi-ai/dist/env-api-keys.js +6 -1
- package/node_modules/@earendil-works/pi-ai/dist/env-api-keys.js.map +1 -1
- package/node_modules/@earendil-works/pi-ai/dist/image-models.generated.d.ts +75 -0
- package/node_modules/@earendil-works/pi-ai/dist/image-models.generated.d.ts.map +1 -1
- package/node_modules/@earendil-works/pi-ai/dist/image-models.generated.js +75 -0
- package/node_modules/@earendil-works/pi-ai/dist/image-models.generated.js.map +1 -1
- package/node_modules/@earendil-works/pi-ai/dist/providers/data/.manifest.json +1 -1
- package/node_modules/@earendil-works/pi-ai/dist/providers/data/alibaba-token-plan.json +1 -1
- package/node_modules/@earendil-works/pi-ai/dist/providers/data/amazon-bedrock.json +1 -1
- package/node_modules/@earendil-works/pi-ai/dist/providers/data/baseten.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/cloudflare-workers-ai.json +1 -1
- package/node_modules/@earendil-works/pi-ai/dist/providers/data/fireworks.json +1 -1
- package/node_modules/@earendil-works/pi-ai/dist/providers/data/github-copilot.json +1 -1
- package/node_modules/@earendil-works/pi-ai/dist/providers/data/huggingface.json +1 -1
- package/node_modules/@earendil-works/pi-ai/dist/providers/data/minimax-cn.json +1 -1
- package/node_modules/@earendil-works/pi-ai/dist/providers/data/minimax.json +1 -1
- package/node_modules/@earendil-works/pi-ai/dist/providers/data/mistral.json +1 -1
- package/node_modules/@earendil-works/pi-ai/dist/providers/data/nvidia.json +1 -1
- package/node_modules/@earendil-works/pi-ai/dist/providers/data/openai-codex.json +1 -1
- package/node_modules/@earendil-works/pi-ai/dist/providers/data/openai.json +1 -1
- package/node_modules/@earendil-works/pi-ai/dist/providers/data/opencode-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/opengateway.json +1 -1
- package/node_modules/@earendil-works/pi-ai/dist/providers/data/openrouter.json +1 -1
- package/node_modules/@earendil-works/pi-ai/dist/providers/data/qwen-token-plan-cn.json +1 -1
- package/node_modules/@earendil-works/pi-ai/dist/providers/data/qwen-token-plan.json +1 -1
- package/node_modules/@earendil-works/pi-ai/dist/providers/data/together.json +1 -1
- package/node_modules/@earendil-works/pi-ai/dist/providers/data/vercel-ai-gateway.json +1 -1
- package/node_modules/@earendil-works/pi-ai/dist/providers/data/zai-coding-cn.json +1 -1
- package/node_modules/@earendil-works/pi-ai/dist/providers/data/zai.json +1 -1
- package/node_modules/@earendil-works/pi-ai/dist/utils/overflow.d.ts.map +1 -1
- package/node_modules/@earendil-works/pi-ai/dist/utils/overflow.js +4 -0
- package/node_modules/@earendil-works/pi-ai/dist/utils/overflow.js.map +1 -1
- package/node_modules/@earendil-works/pi-ai/package.json +5 -5
- 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/dist/terminal.d.ts +5 -0
- package/node_modules/@earendil-works/pi-tui/dist/terminal.d.ts.map +1 -1
- package/node_modules/@earendil-works/pi-tui/dist/terminal.js +90 -1
- package/node_modules/@earendil-works/pi-tui/dist/terminal.js.map +1 -1
- package/node_modules/@earendil-works/pi-tui/dist/tui.js +1 -1
- package/node_modules/@earendil-works/pi-tui/dist/tui.js.map +1 -1
- package/node_modules/@earendil-works/pi-tui/package.json +1 -1
- package/package.json +7 -7
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"session-event-writer.js","sourceRoot":"","sources":["../../../src/modes/rpc/session-event-writer.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,iBAAiB,EAAE,MAAM,YAAY,CAAC;AAyB/C,MAAM,WAAW,GAAG,SAAS,CAAC;AAC9B,MAAM,mBAAmB,GAAG,IAAI,GAAG,CAAmB,CAAC,YAAY,EAAE,gBAAgB,EAAE,gBAAgB,CAAC,CAAC,CAAC;AAE1G,SAAS,YAAY,CAAC,KAAgB;IACrC,IAAI,KAAK,CAAC,IAAI,KAAK,gBAAgB,IAAI,CAAC,MAAM,CAAC,MAAM,CAAC,KAAK,EAAE,SAAS,CAAC;QAAE,OAAO,SAAS,CAAC;IAC1F,MAAM,KAAK,GAAG,KAAK,CAAC,qBAAqB,CAAC;IAC1C,IAAI,OAAO,KAAK,KAAK,QAAQ,IAAI,KAAK,KAAK,IAAI;QAAE,OAAO,SAAS,CAAC;IAClE,MAAM,UAAU,GAAG,KAAgC,CAAC;IACpD,IACC,OAAO,UAAU,CAAC,IAAI,KAAK,QAAQ;QACnC,CAAC,mBAAmB,CAAC,GAAG,CAAC,UAAU,CAAC,IAAwB,CAAC;QAC7D,OAAO,UAAU,CAAC,YAAY,KAAK,QAAQ;QAC3C,OAAO,UAAU,CAAC,KAAK,KAAK,QAAQ,EACnC,CAAC;QACF,OAAO,SAAS,CAAC;IAClB,CAAC;IACD,OAAO;QACN,IAAI,EAAE,UAAU,CAAC,IAAwB;QACzC,YAAY,EAAE,UAAU,CAAC,YAAY;QACrC,KAAK,EAAE,UAAU,CAAC,KAAK;KACvB,CAAC;AACH,CAAC;AAED,SAAS,aAAa,CAAC,KAAgB;IACtC,OAAO,KAAK,CAAC,IAAI,KAAK,uBAAuB,IAAI,OAAO,KAAK,CAAC,UAAU,KAAK,QAAQ;QACpF,CAAC,CAAC,QAAQ,KAAK,CAAC,UAAU,EAAE;QAC5B,CAAC,CAAC,SAAS,CAAC;AACd,CAAC;AAED;;;;;;;;GAQG;AACH,MAAM,OAAO,kBAAkB;IAe9B,YACC,QAAmB,EACnB,cAAoD,EACpD,aAA8B;QAjBd,WAAM,GAAG,IAAI,GAAG,EAAuB,CAAC;QACxC,iBAAY,GAAgB,EAAE,WAAW,EAAE,IAAI,GAAG,EAAE,EAAE,KAAK,EAAE,KAAK,EAAE,CAAC;QACrE,gBAAW,GAAkB,EAAE,CAAC;QAChC,mBAAc,GAAG,IAAI,GAAG,EAAU,CAAC;QAI5C,mBAAc,GAAG,KAAK,CAAC;QAY9B,IAAI,CAAC,QAAQ,GAAG,QAAQ,CAAC;QACzB,IAAI,cAAc,IAAI,aAAa,KAAK,SAAS,IAAI,cAAc,CAAC,MAAM,GAAG,CAAC,EAAE,CAAC;YAChF,IAAI,CAAC,aAAa,GAAG,cAAgC,CAAC;QACvD,CAAC;aAAM,CAAC;YACP,IAAI,CAAC,mBAAmB,GAAG,cAAgD,CAAC;YAC5E,IAAI,CAAC,aAAa,GAAG,aAAa,IAAI,CAAC,CAAC,KAAK,EAAE,EAAE,CAAC,cAAc,CAAC,GAAG,EAAE,CAAC,KAAK,KAAK,EAAE,CAAC,CAAC,CAAC;QACvF,CAAC;IACF,CAAC;IAED,IAAI,mBAAmB;QACtB,IAAI,KAAK,GAAG,IAAI,CAAC,QAAQ,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC;QAClC,KAAK,MAAM,KAAK,IAAI,IAAI,CAAC,SAAS,EAAE,EAAE,CAAC;YACtC,KAAK,IAAI,IAAI,GAAG,KAAK,CAAC,IAAI,EAAE,IAAI,EAAE,IAAI,GAAG,IAAI,CAAC,IAAI;gBAAE,KAAK,EAAE,CAAC;QAC7D,CAAC;QACD,OAAO,KAAK,CAAC;IACd,CAAC;IAED,IAAI,kBAAkB;QACrB,IAAI,KAAK,GAAG,IAAI,CAAC,QAAQ,CAAC,CAAC,CAAC,MAAM,CAAC,UAAU,CAAC,iBAAiB,CAAC,IAAI,CAAC,QAAQ,CAAC,IAAI,CAAC,KAAK,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC;QAC/F,KAAK,MAAM,KAAK,IAAI,IAAI,CAAC,SAAS,EAAE,EAAE,CAAC;YACtC,KAAK,IAAI,IAAI,GAAG,KAAK,CAAC,IAAI,EAAE,IAAI,EAAE,IAAI,GAAG,IAAI,CAAC,IAAI,EAAE,CAAC;gBACpD,KAAK,IAAI,MAAM,CAAC,UAAU,CAAC,iBAAiB,CAAC,IAAI,CAAC,KAAK,CAAC,CAAC,CAAC;YAC3D,CAAC;QACF,CAAC;QACD,OAAO,KAAK,CAAC;IACd,CAAC;IAED,wEAAwE;IACxE,OAAO,CAAC,SAAiB,EAAE,KAAa;QACvC,IAAI,IAAI,CAAC,cAAc,CAAC,GAAG,CAAC,SAAS,CAAC;YAAE,OAAO,KAAK,CAAC;QACrD,IAAI,CAAC,mBAAmB,CAAC,SAAS,EAAE,EAAE,GAAG,KAAK,EAAE,SAAS,EAAE,CAAC,CAAC;QAC7D,IAAI,CAAC,YAAY,EAAE,CAAC;QACpB,OAAO,IAAI,CAAC;IACb,CAAC;IAED,mEAAmE;IACnE,cAAc,CAAC,KAAa;QAC3B,IAAI,IAAI,CAAC,OAAO,KAAK,SAAS;YAAE,OAAO,OAAO,CAAC,MAAM,CAAC,IAAI,CAAC,OAAO,CAAC,CAAC;QACpE,MAAM,UAAU,GAAG,IAAI,OAAO,CAAO,CAAC,OAAO,EAAE,MAAM,EAAE,EAAE;YACxD,IAAI,CAAC,MAAM,CAAC,IAAI,CAAC,YAAY,EAAE,EAAE,GAAG,KAAK,EAAE,EAAE,SAAS,EAAE,OAAO,EAAE,MAAM,CAAC,CAAC;QAC1E,CAAC,CAAC,CAAC;QACH,IAAI,CAAC,SAAS,CAAC,IAAI,CAAC,YAAY,CAAC,CAAC;QAClC,IAAI,CAAC,YAAY,EAAE,CAAC;QACpB,OAAO,UAAU,CAAC;IACnB,CAAC;IAED;;;;OAIG;IACH,YAAY,CAAC,SAAiB,EAAE,QAAgB;QAC/C,IAAI,IAAI,CAAC,cAAc,CAAC,GAAG,CAAC,SAAS,CAAC;YAAE,OAAO;QAC/C,IAAI,CAAC,cAAc,CAAC,GAAG,CAAC,SAAS,CAAC,CAAC;QACnC,IAAI,CAAC,mBAAmB,CAAC,SAAS,EAAE,EAAE,GAAG,QAAQ,EAAE,SAAS,EAAE,CAAC,CAAC;QAChE,IAAI,CAAC,YAAY,EAAE,CAAC;IACrB,CAAC;IAED,kEAAkE;IAClE,KAAK;QACJ,IAAI,IAAI,CAAC,OAAO,KAAK,SAAS;YAAE,OAAO,OAAO,CAAC,MAAM,CAAC,IAAI,CAAC,OAAO,CAAC,CAAC;QACpE,IAAI,CAAC,cAAc,GAAG,KAAK,CAAC;QAC5B,IAAI,IAAI,CAAC,YAAY;YAAE,OAAO,IAAI,CAAC,YAAY,CAAC;QAChD,IAAI,IAAI,CAAC,WAAW,CAAC,MAAM,KAAK,CAAC;YAAE,OAAO,OAAO,CAAC,OAAO,EAAE,CAAC;QAC5D,IAAI,YAAyB,CAAC;QAC9B,IAAI,WAAsC,CAAC;QAC3C,MAAM,KAAK,GAAG,IAAI,OAAO,CAAO,CAAC,OAAO,EAAE,MAAM,EAAE,EAAE;YACnD,YAAY,GAAG,OAAO,CAAC;YACvB,WAAW,GAAG,MAAM,CAAC;QACtB,CAAC,CAAC,CAAC;QACH,IAAI,CAAC,YAAY,GAAG,KAAK,CAAC;QAC1B,KAAK,IAAI,CAAC,eAAe,EAAE,CAAC,IAAI,CAAC,YAAY,EAAE,WAAW,CAAC,CAAC;QAC5D,KAAK,KAAK,CAAC,IAAI,CACd,GAAG,EAAE;YACJ,IAAI,IAAI,CAAC,YAAY,KAAK,KAAK;gBAAE,IAAI,CAAC,YAAY,GAAG,SAAS,CAAC;QAChE,CAAC,EACD,CAAC,KAAK,EAAE,EAAE;YACT,IAAI,IAAI,CAAC,YAAY,KAAK,KAAK;gBAAE,IAAI,CAAC,YAAY,GAAG,SAAS,CAAC;YAC/D,IAAI,CAAC,IAAI,CAAC,KAAK,CAAC,CAAC;QAClB,CAAC,CACD,CAAC;QACF,OAAO,KAAK,CAAC;IACd,CAAC;IAEO,KAAK,CAAC,eAAe;QAC5B,GAAG,CAAC;YACH,MAAM,IAAI,CAAC,gBAAgB,EAAE,CAAC;QAC/B,CAAC,QAAQ,IAAI,CAAC,WAAW,CAAC,MAAM,GAAG,CAAC,EAAE;IACvC,CAAC;IAEO,KAAK,CAAC,gBAAgB;QAC7B,OAAO,IAAI,CAAC,WAAW,CAAC,MAAM,GAAG,CAAC,EAAE,CAAC;YACpC,MAAM,KAAK,GAAG,IAAI,CAAC,WAAW,CAAC,KAAK,EAAG,CAAC;YACxC,KAAK,CAAC,KAAK,GAAG,KAAK,CAAC;YACpB,MAAM,IAAI,GAAG,KAAK,CAAC,IAAI,CAAC;YACxB,IAAI,CAAC,IAAI;gBAAE,SAAS;YAEpB,IAAI,CAAC,MAAM,CAAC,KAAK,EAAE,IAAI,CAAC,CAAC;YACzB,IAAI,CAAC,QAAQ,GAAG,EAAE,KAAK,EAAE,IAAI,EAAE,CAAC;YAChC,IAAI,CAAC;gBACJ,sEAAsE;gBACtE,8DAA8D;gBAC9D,IAAI,CAAC,QAAQ,CAAC,iBAAiB,CAAC,IAAI,CAAC,KAAK,CAAC,CAAC,CAAC;gBAC7C,IAAI,IAAI,CAAC,mBAAmB;oBAAE,MAAM,IAAI,CAAC,mBAAmB,EAAE,CAAC;gBAC/D,IAAI,CAAC,OAAO,EAAE,EAAE,CAAC;YAClB,CAAC;YAAC,OAAO,KAAK,EAAE,CAAC;gBAChB,IAAI,CAAC,MAAM,EAAE,CAAC,KAAK,CAAC,CAAC;gBACrB,MAAM,KAAK,CAAC;YACb,CAAC;oBAAS,CAAC;gBACV,IAAI,CAAC,QAAQ,GAAG,SAAS,CAAC;YAC3B,CAAC;YAED,IAAI,KAAK,CAAC,IAAI,EAAE,CAAC;gBAChB,IAAI,CAAC,SAAS,CAAC,KAAK,CAAC,CAAC;YACvB,CAAC;iBAAM,IAAI,KAAK,CAAC,SAAS,EAAE,CAAC;gBAC5B,IAAI,CAAC,MAAM,CAAC,MAAM,CAAC,KAAK,CAAC,SAAS,CAAC,CAAC;YACrC,CAAC;QACF,CAAC;IACF,CAAC;IAEO,mBAAmB,CAAC,SAAiB,EAAE,KAAgB;QAC9D,IAAI,KAAK,GAAG,IAAI,CAAC,MAAM,CAAC,GAAG,CAAC,SAAS,CAAC,CAAC;QACvC,IAAI,CAAC,KAAK,EAAE,CAAC;YACZ,KAAK,GAAG,EAAE,SAAS,EAAE,WAAW,EAAE,IAAI,GAAG,EAAE,EAAE,KAAK,EAAE,KAAK,EAAE,CAAC;YAC5D,IAAI,CAAC,MAAM,CAAC,GAAG,CAAC,SAAS,EAAE,KAAK,CAAC,CAAC;QACnC,CAAC;QAED,MAAM,KAAK,GAAG,YAAY,CAAC,KAAK,CAAC,CAAC;QAClC,IAAI,KAAK,EAAE,CAAC;YACX,MAAM,YAAY,GAAG,KAAK,CAAC,WAAW,CAAC,GAAG,CAAC,WAAW,CAAC,CAAC;YACxD,IAAI,YAAY;gBAAE,IAAI,CAAC,cAAc,CAAC,KAAK,EAAE,YAAY,CAAC,CAAC;YAC3D,MAAM,IAAI,GAAG,IAAI,CAAC,MAAM,CAAC,KAAK,EAAE,KAAK,EAAE,WAAW,CAAC,CAAC;YACpD,KAAK,CAAC,WAAW,CAAC,GAAG,CAAC,WAAW,EAAE,IAAI,CAAC,CAAC;YACzC,IAAI,CAAC,SAAS,CAAC,KAAK,CAAC,CAAC;YACtB,OAAO;QACR,CAAC;QAED,MAAM,OAAO,GAAG,aAAa,CAAC,KAAK,CAAC,CAAC;QACrC,IAAI,OAAO,EAAE,CAAC;YACb,MAAM,QAAQ,GAAG,KAAK,CAAC,WAAW,CAAC,GAAG,CAAC,OAAO,CAAC,CAAC;YAChD,IAAI,QAAQ;gBAAE,IAAI,CAAC,MAAM,CAAC,KAAK,EAAE,QAAQ,CAAC,CAAC;YAC3C,MAAM,IAAI,GAAG,IAAI,CAAC,MAAM,CAAC,KAAK,EAAE,KAAK,EAAE,OAAO,CAAC,CAAC;YAChD,KAAK,CAAC,WAAW,CAAC,GAAG,CAAC,OAAO,EAAE,IAAI,CAAC,CAAC;YACrC,IAAI,CAAC,SAAS,CAAC,KAAK,CAAC,CAAC;YACtB,OAAO;QACR,CAAC;QAED,wEAAwE;QACxE,0EAA0E;QAC1E,0EAA0E;QAC1E,KAAK,CAAC,WAAW,CAAC,KAAK,EAAE,CAAC;QAC1B,IAAI,CAAC,MAAM,CAAC,KAAK,EAAE,KAAK,CAAC,CAAC;QAC1B,IAAI,CAAC,SAAS,CAAC,KAAK,CAAC,CAAC;IACvB,CAAC;IAEO,cAAc,CAAC,KAAkB,EAAE,IAAe;QACzD,MAAM,KAAK,GAAG,IAAI,CAAC,KAAK,CAAC,qBAAgD,CAAC;QAC1E,IAAI,CAAC,KAAK,GAAG;YACZ,GAAG,IAAI,CAAC,KAAK;YACb,OAAO,EAAE,IAAI;YACb,qBAAqB,EAAE,EAAE,GAAG,KAAK,EAAE,OAAO,EAAE,IAAI,EAAE;SAClD,CAAC;QACF,MAAM,OAAO,GAAG,YAAY,CAAC,IAAI,CAAC,KAAK,CAAC,CAAC;QACzC,MAAM,SAAS,GAAG,IAAI,CAAC,QAAQ,CAAC;QAChC,MAAM,QAAQ,GAAG,SAAS,CAAC,CAAC,CAAC,YAAY,CAAC,SAAS,CAAC,KAAK,CAAC,CAAC,CAAC,CAAC,SAAS,CAAC;QACvE,IACC,SAAS;YACT,QAAQ;YACR,OAAO;YACP,SAAS,CAAC,KAAK,CAAC,OAAO,KAAK,IAAI;YAChC,QAAQ,CAAC,IAAI,KAAK,OAAO,CAAC,IAAI;YAC9B,QAAQ,CAAC,YAAY,KAAK,OAAO,CAAC,YAAY,EAC7C,CAAC;YACF,MAAM,cAAc,GAAG,SAAS,CAAC,KAAK,CAAC,qBAAgD,CAAC;YACxF,SAAS,CAAC,KAAK,GAAG;gBACjB,GAAG,SAAS,CAAC,KAAK;gBAClB,qBAAqB,EAAE,EAAE,GAAG,cAAc,EAAE,KAAK,EAAE,QAAQ,CAAC,KAAK,GAAG,OAAO,CAAC,KAAK,EAAE;aACnF,CAAC;YACF,IAAI,CAAC,MAAM,CAAC,KAAK,EAAE,IAAI,CAAC,CAAC;QAC1B,CAAC;IACF,CAAC;IAEO,MAAM,CACb,KAAkB,EAClB,KAAgB,EAChB,GAAY,EACZ,OAAoB,EACpB,MAAiC;QAEjC,MAAM,IAAI,GAAc,EAAE,KAAK,EAAE,GAAG,EAAE,QAAQ,EAAE,KAAK,CAAC,IAAI,EAAE,OAAO,EAAE,MAAM,EAAE,CAAC;QAC9E,IAAI,KAAK,CAAC,IAAI;YAAE,KAAK,CAAC,IAAI,CAAC,IAAI,GAAG,IAAI,CAAC;;YAClC,KAAK,CAAC,IAAI,GAAG,IAAI,CAAC;QACvB,KAAK,CAAC,IAAI,GAAG,IAAI,CAAC;QAClB,OAAO,IAAI,CAAC;IACb,CAAC;IAEO,MAAM,CAAC,KAAkB,EAAE,IAAe;QACjD,IAAI,IAAI,CAAC,QAAQ;YAAE,IAAI,CAAC,QAAQ,CAAC,IAAI,GAAG,IAAI,CAAC,IAAI,CAAC;;YAC7C,KAAK,CAAC,IAAI,GAAG,IAAI,CAAC,IAAI,CAAC;QAC5B,IAAI,IAAI,CAAC,IAAI;YAAE,IAAI,CAAC,IAAI,CAAC,QAAQ,GAAG,IAAI,CAAC,QAAQ,CAAC;;YAC7C,KAAK,CAAC,IAAI,GAAG,IAAI,CAAC,QAAQ,CAAC;QAChC,IAAI,IAAI,CAAC,GAAG,IAAI,KAAK,CAAC,WAAW,CAAC,GAAG,CAAC,IAAI,CAAC,GAAG,CAAC,KAAK,IAAI;YAAE,KAAK,CAAC,WAAW,CAAC,MAAM,CAAC,IAAI,CAAC,GAAG,CAAC,CAAC;QAC7F,IAAI,CAAC,QAAQ,GAAG,SAAS,CAAC;QAC1B,IAAI,CAAC,IAAI,GAAG,SAAS,CAAC;IACvB,CAAC;IAEO,SAAS,CAAC,KAAkB;QACnC,IAAI,KAAK,CAAC,KAAK,IAAI,IAAI,CAAC,QAAQ,EAAE,KAAK,KAAK,KAAK,IAAI,CAAC,KAAK,CAAC,IAAI;YAAE,OAAO;QACzE,KAAK,CAAC,KAAK,GAAG,IAAI,CAAC;QACnB,IAAI,CAAC,WAAW,CAAC,IAAI,CAAC,KAAK,CAAC,CAAC;IAC9B,CAAC;IAEO,YAAY;QACnB,IAAI,IAAI,CAAC,OAAO,KAAK,SAAS,IAAI,IAAI,CAAC,cAAc,IAAI,IAAI,CAAC,YAAY;YAAE,OAAO;QACnF,IAAI,CAAC,cAAc,GAAG,IAAI,CAAC;QAC3B,IAAI,CAAC,aAAa,CAAC,GAAG,EAAE,CAAC,IAAI,CAAC,KAAK,EAAE,CAAC,CAAC;IACxC,CAAC;IAEO,IAAI,CAAC,KAAc;QAC1B,IAAI,IAAI,CAAC,OAAO,KAAK,SAAS;YAAE,OAAO;QACvC,IAAI,CAAC,OAAO,GAAG,KAAK,CAAC;QACrB,KAAK,MAAM,KAAK,IAAI,IAAI,CAAC,SAAS,EAAE,EAAE,CAAC;YACtC,KAAK,IAAI,IAAI,GAAG,KAAK,CAAC,IAAI,EAAE,IAAI,EAAE,IAAI,GAAG,IAAI,CAAC,IAAI;gBAAE,IAAI,CAAC,MAAM,EAAE,CAAC,KAAK,CAAC,CAAC;QAC1E,CAAC;IACF,CAAC;IAEO,CAAC,SAAS;QACjB,KAAK,CAAC,CAAC,IAAI,CAAC,MAAM,CAAC,MAAM,EAAE,CAAC;QAC5B,MAAM,IAAI,CAAC,YAAY,CAAC;IACzB,CAAC;CACD","sourcesContent":["import { serializeJsonLine } from \"./jsonl.ts\";\n\ntype RawWriter = (chunk: string) => void;\ntype BackpressureWaiter = () => Promise<void>;\ntype FlushScheduler = (flush: () => Promise<void>) => void;\ntype RpcRecord = Record<string, unknown>;\ntype CompactDeltaType = \"text_delta\" | \"thinking_delta\" | \"toolcall_delta\";\n\ntype QueueNode = {\n\tvalue: RpcRecord;\n\tkey?: string;\n\tprevious?: QueueNode;\n\tnext?: QueueNode;\n\tresolve?: () => void;\n\treject?: (cause: unknown) => void;\n};\n\ntype RecordQueue = {\n\tsessionId?: string;\n\thead?: QueueNode;\n\ttail?: QueueNode;\n\tlatestByKey: Map<string, QueueNode>;\n\tready: boolean;\n};\n\nconst MESSAGE_KEY = \"message\";\nconst COMPACT_DELTA_TYPES = new Set<CompactDeltaType>([\"text_delta\", \"thinking_delta\", \"toolcall_delta\"]);\n\nfunction compactDelta(value: RpcRecord): { type: CompactDeltaType; contentIndex: number; delta: string } | undefined {\n\tif (value.type !== \"message_update\" || !Object.hasOwn(value, \"message\")) return undefined;\n\tconst event = value.assistantMessageEvent;\n\tif (typeof event !== \"object\" || event === null) return undefined;\n\tconst typedEvent = event as Record<string, unknown>;\n\tif (\n\t\ttypeof typedEvent.type !== \"string\" ||\n\t\t!COMPACT_DELTA_TYPES.has(typedEvent.type as CompactDeltaType) ||\n\t\ttypeof typedEvent.contentIndex !== \"number\" ||\n\t\ttypeof typedEvent.delta !== \"string\"\n\t) {\n\t\treturn undefined;\n\t}\n\treturn {\n\t\ttype: typedEvent.type as CompactDeltaType,\n\t\tcontentIndex: typedEvent.contentIndex,\n\t\tdelta: typedEvent.delta,\n\t};\n}\n\nfunction toolUpdateKey(value: RpcRecord): string | undefined {\n\treturn value.type === \"tool_execution_update\" && typeof value.toolCallId === \"string\"\n\t\t? `tool:${value.toolCallId}`\n\t\t: undefined;\n}\n\n/**\n * Process-wide stdout scheduler for multi-session RPC mode.\n *\n * Each queue contains complete structured JSONL records for one routing handle.\n * Draining takes one record per queue in round-robin order and waits for stdout\n * backpressure before selecting the next one. A record is deliberately written\n * by itself: coalescing records from different sessions would obscure the\n * scheduling boundary and violate D9.\n */\nexport class SessionEventWriter {\n\tprivate readonly queues = new Map<string, RecordQueue>();\n\tprivate readonly controlQueue: RecordQueue = { latestByKey: new Map(), ready: false };\n\tprivate readonly readyQueues: RecordQueue[] = [];\n\tprivate readonly sealedSessions = new Set<string>();\n\tprivate readonly writeRaw: RawWriter;\n\tprivate readonly waitForBackpressure?: BackpressureWaiter;\n\tprivate readonly scheduleFlush: FlushScheduler;\n\tprivate flushScheduled = false;\n\tprivate drainPromise?: Promise<void>;\n\tprivate inFlight?: { queue: RecordQueue; node: QueueNode };\n\tprivate failure?: unknown;\n\n\tconstructor(writeRaw: RawWriter, scheduleFlush?: FlushScheduler);\n\tconstructor(writeRaw: RawWriter, waitForBackpressure: BackpressureWaiter, scheduleFlush?: FlushScheduler);\n\tconstructor(\n\t\twriteRaw: RawWriter,\n\t\twaitOrSchedule?: BackpressureWaiter | FlushScheduler,\n\t\tscheduleFlush?: FlushScheduler,\n\t) {\n\t\tthis.writeRaw = writeRaw;\n\t\tif (waitOrSchedule && scheduleFlush === undefined && waitOrSchedule.length > 0) {\n\t\t\tthis.scheduleFlush = waitOrSchedule as FlushScheduler;\n\t\t} else {\n\t\t\tthis.waitForBackpressure = waitOrSchedule as BackpressureWaiter | undefined;\n\t\t\tthis.scheduleFlush = scheduleFlush ?? ((flush) => queueMicrotask(() => void flush()));\n\t\t}\n\t}\n\n\tget bufferedRecordCount(): number {\n\t\tlet count = this.inFlight ? 1 : 0;\n\t\tfor (const queue of this.allQueues()) {\n\t\t\tfor (let node = queue.head; node; node = node.next) count++;\n\t\t}\n\t\treturn count;\n\t}\n\n\tget bufferedByteLength(): number {\n\t\tlet bytes = this.inFlight ? Buffer.byteLength(serializeJsonLine(this.inFlight.node.value)) : 0;\n\t\tfor (const queue of this.allQueues()) {\n\t\t\tfor (let node = queue.head; node; node = node.next) {\n\t\t\t\tbytes += Buffer.byteLength(serializeJsonLine(node.value));\n\t\t\t}\n\t\t}\n\t\treturn bytes;\n\t}\n\n\t/** Queue one session-owned response, event, or extension UI request. */\n\tenqueue(sessionId: string, value: object): boolean {\n\t\tif (this.sealedSessions.has(sessionId)) return false;\n\t\tthis.appendSessionRecord(sessionId, { ...value, sessionId });\n\t\tthis.requestFlush();\n\t\treturn true;\n\t}\n\n\t/** Queue one untagged host-control response without compaction. */\n\tenqueueControl(value: object): Promise<void> {\n\t\tif (this.failure !== undefined) return Promise.reject(this.failure);\n\t\tconst completion = new Promise<void>((resolve, reject) => {\n\t\t\tthis.append(this.controlQueue, { ...value }, undefined, resolve, reject);\n\t\t});\n\t\tthis.markReady(this.controlQueue);\n\t\tthis.requestFlush();\n\t\treturn completion;\n\t}\n\n\t/**\n\t * Prevent subsequent records for a session and append its terminal response.\n\t * Existing records retain FIFO order; this response is therefore that\n\t * session's final stdout record.\n\t */\n\tcloseSession(sessionId: string, response: object): void {\n\t\tif (this.sealedSessions.has(sessionId)) return;\n\t\tthis.sealedSessions.add(sessionId);\n\t\tthis.appendSessionRecord(sessionId, { ...response, sessionId });\n\t\tthis.requestFlush();\n\t}\n\n\t/** Drain every retained lane and the current in-flight record. */\n\tflush(): Promise<void> {\n\t\tif (this.failure !== undefined) return Promise.reject(this.failure);\n\t\tthis.flushScheduled = false;\n\t\tif (this.drainPromise) return this.drainPromise;\n\t\tif (this.readyQueues.length === 0) return Promise.resolve();\n\t\tlet resolveDrain!: () => void;\n\t\tlet rejectDrain!: (cause: unknown) => void;\n\t\tconst drain = new Promise<void>((resolve, reject) => {\n\t\t\tresolveDrain = resolve;\n\t\t\trejectDrain = reject;\n\t\t});\n\t\tthis.drainPromise = drain;\n\t\tvoid this.drainUntilEmpty().then(resolveDrain, rejectDrain);\n\t\tvoid drain.then(\n\t\t\t() => {\n\t\t\t\tif (this.drainPromise === drain) this.drainPromise = undefined;\n\t\t\t},\n\t\t\t(cause) => {\n\t\t\t\tif (this.drainPromise === drain) this.drainPromise = undefined;\n\t\t\t\tthis.fail(cause);\n\t\t\t},\n\t\t);\n\t\treturn drain;\n\t}\n\n\tprivate async drainUntilEmpty(): Promise<void> {\n\t\tdo {\n\t\t\tawait this.drainReadyQueues();\n\t\t} while (this.readyQueues.length > 0);\n\t}\n\n\tprivate async drainReadyQueues(): Promise<void> {\n\t\twhile (this.readyQueues.length > 0) {\n\t\t\tconst queue = this.readyQueues.shift()!;\n\t\t\tqueue.ready = false;\n\t\t\tconst node = queue.head;\n\t\t\tif (!node) continue;\n\n\t\t\tthis.unlink(queue, node);\n\t\t\tthis.inFlight = { queue, node };\n\t\t\ttry {\n\t\t\t\t// D9: exactly one complete record per raw write. The next lane is not\n\t\t\t\t// selected until this record has cleared stdout backpressure.\n\t\t\t\tthis.writeRaw(serializeJsonLine(node.value));\n\t\t\t\tif (this.waitForBackpressure) await this.waitForBackpressure();\n\t\t\t\tnode.resolve?.();\n\t\t\t} catch (cause) {\n\t\t\t\tnode.reject?.(cause);\n\t\t\t\tthrow cause;\n\t\t\t} finally {\n\t\t\t\tthis.inFlight = undefined;\n\t\t\t}\n\n\t\t\tif (queue.head) {\n\t\t\t\tthis.markReady(queue);\n\t\t\t} else if (queue.sessionId) {\n\t\t\t\tthis.queues.delete(queue.sessionId);\n\t\t\t}\n\t\t}\n\t}\n\n\tprivate appendSessionRecord(sessionId: string, value: RpcRecord): void {\n\t\tlet queue = this.queues.get(sessionId);\n\t\tif (!queue) {\n\t\t\tqueue = { sessionId, latestByKey: new Map(), ready: false };\n\t\t\tthis.queues.set(sessionId, queue);\n\t\t}\n\n\t\tconst delta = compactDelta(value);\n\t\tif (delta) {\n\t\t\tconst previousFull = queue.latestByKey.get(MESSAGE_KEY);\n\t\t\tif (previousFull) this.demoteAndMerge(queue, previousFull);\n\t\t\tconst node = this.append(queue, value, MESSAGE_KEY);\n\t\t\tqueue.latestByKey.set(MESSAGE_KEY, node);\n\t\t\tthis.markReady(queue);\n\t\t\treturn;\n\t\t}\n\n\t\tconst toolKey = toolUpdateKey(value);\n\t\tif (toolKey) {\n\t\t\tconst previous = queue.latestByKey.get(toolKey);\n\t\t\tif (previous) this.unlink(queue, previous);\n\t\t\tconst node = this.append(queue, value, toolKey);\n\t\t\tqueue.latestByKey.set(toolKey, node);\n\t\t\tthis.markReady(queue);\n\t\t\treturn;\n\t\t}\n\n\t\t// All non-compactable records are ordering barriers. In particular this\n\t\t// includes delta-only/full non-delta message updates, protocol responses,\n\t\t// extension UI requests, errors, retries, lifecycle, and unknown records.\n\t\tqueue.latestByKey.clear();\n\t\tthis.append(queue, value);\n\t\tthis.markReady(queue);\n\t}\n\n\tprivate demoteAndMerge(queue: RecordQueue, node: QueueNode): void {\n\t\tconst event = node.value.assistantMessageEvent as Record<string, unknown>;\n\t\tnode.value = {\n\t\t\t...node.value,\n\t\t\tmessage: null,\n\t\t\tassistantMessageEvent: { ...event, partial: null },\n\t\t};\n\t\tconst current = compactDelta(node.value);\n\t\tconst preceding = node.previous;\n\t\tconst previous = preceding ? compactDelta(preceding.value) : undefined;\n\t\tif (\n\t\t\tpreceding &&\n\t\t\tprevious &&\n\t\t\tcurrent &&\n\t\t\tpreceding.value.message === null &&\n\t\t\tprevious.type === current.type &&\n\t\t\tprevious.contentIndex === current.contentIndex\n\t\t) {\n\t\t\tconst precedingEvent = preceding.value.assistantMessageEvent as Record<string, unknown>;\n\t\t\tpreceding.value = {\n\t\t\t\t...preceding.value,\n\t\t\t\tassistantMessageEvent: { ...precedingEvent, delta: previous.delta + current.delta },\n\t\t\t};\n\t\t\tthis.unlink(queue, node);\n\t\t}\n\t}\n\n\tprivate append(\n\t\tqueue: RecordQueue,\n\t\tvalue: RpcRecord,\n\t\tkey?: string,\n\t\tresolve?: () => void,\n\t\treject?: (cause: unknown) => void,\n\t): QueueNode {\n\t\tconst node: QueueNode = { value, key, previous: queue.tail, resolve, reject };\n\t\tif (queue.tail) queue.tail.next = node;\n\t\telse queue.head = node;\n\t\tqueue.tail = node;\n\t\treturn node;\n\t}\n\n\tprivate unlink(queue: RecordQueue, node: QueueNode): void {\n\t\tif (node.previous) node.previous.next = node.next;\n\t\telse queue.head = node.next;\n\t\tif (node.next) node.next.previous = node.previous;\n\t\telse queue.tail = node.previous;\n\t\tif (node.key && queue.latestByKey.get(node.key) === node) queue.latestByKey.delete(node.key);\n\t\tnode.previous = undefined;\n\t\tnode.next = undefined;\n\t}\n\n\tprivate markReady(queue: RecordQueue): void {\n\t\tif (queue.ready || this.inFlight?.queue === queue || !queue.head) return;\n\t\tqueue.ready = true;\n\t\tthis.readyQueues.push(queue);\n\t}\n\n\tprivate requestFlush(): void {\n\t\tif (this.failure !== undefined || this.flushScheduled || this.drainPromise) return;\n\t\tthis.flushScheduled = true;\n\t\tthis.scheduleFlush(() => this.flush());\n\t}\n\n\tprivate fail(cause: unknown): void {\n\t\tif (this.failure !== undefined) return;\n\t\tthis.failure = cause;\n\t\tfor (const queue of this.allQueues()) {\n\t\t\tfor (let node = queue.head; node; node = node.next) node.reject?.(cause);\n\t\t}\n\t}\n\n\tprivate *allQueues(): Iterable<RecordQueue> {\n\t\tyield* this.queues.values();\n\t\tyield this.controlQueue;\n\t}\n}\n"]}
|
|
1
|
+
{"version":3,"file":"session-event-writer.js","sourceRoot":"","sources":["../../../src/modes/rpc/session-event-writer.ts"],"names":[],"mappings":"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","sourcesContent":["import { AsyncLocalStorage } from \"node:async_hooks\";\nimport { serializeJsonLine } from \"./jsonl.ts\";\n\ntype RawWriter = (chunk: string) => void;\ntype BackpressureWaiter = () => Promise<void>;\ntype FlushScheduler = (flush: () => Promise<void>) => void;\ntype RpcRecord = Record<string, unknown>;\ntype CompactDeltaType = \"text_delta\" | \"thinking_delta\" | \"toolcall_delta\";\n\ntype QueueNode = {\n\tvalue: RpcRecord;\n\tkey?: string;\n\tprevious?: QueueNode;\n\tnext?: QueueNode;\n\tresolve?: () => void;\n\treject?: (cause: unknown) => void;\n};\n\ntype RecordQueue = {\n\tsessionId?: string;\n\ttargetId?: string;\n\thead?: QueueNode;\n\ttail?: QueueNode;\n\tlatestByKey: Map<string, QueueNode>;\n\tready: boolean;\n};\n\nconst MESSAGE_KEY = \"message\";\nconst COMPACT_DELTA_TYPES = new Set<CompactDeltaType>([\"text_delta\", \"thinking_delta\", \"toolcall_delta\"]);\n\nfunction compactDelta(value: RpcRecord): { type: CompactDeltaType; contentIndex: number; delta: string } | undefined {\n\tif (value.type !== \"message_update\" || !Object.hasOwn(value, \"message\")) return undefined;\n\tconst event = value.assistantMessageEvent;\n\tif (typeof event !== \"object\" || event === null) return undefined;\n\tconst typedEvent = event as Record<string, unknown>;\n\tif (\n\t\ttypeof typedEvent.type !== \"string\" ||\n\t\t!COMPACT_DELTA_TYPES.has(typedEvent.type as CompactDeltaType) ||\n\t\ttypeof typedEvent.contentIndex !== \"number\" ||\n\t\ttypeof typedEvent.delta !== \"string\"\n\t) {\n\t\treturn undefined;\n\t}\n\treturn {\n\t\ttype: typedEvent.type as CompactDeltaType,\n\t\tcontentIndex: typedEvent.contentIndex,\n\t\tdelta: typedEvent.delta,\n\t};\n}\n\nfunction toolUpdateKey(value: RpcRecord): string | undefined {\n\treturn value.type === \"tool_execution_update\" && typeof value.toolCallId === \"string\"\n\t\t? `tool:${value.toolCallId}`\n\t\t: undefined;\n}\n\n/**\n * Process-wide stdout scheduler for multi-session RPC mode.\n *\n * Each queue contains complete structured JSONL records for one routing handle.\n * Draining takes one record per queue in round-robin order and waits for stdout\n * backpressure before selecting the next one. A record is deliberately written\n * by itself: coalescing records from different sessions would obscure the\n * scheduling boundary and violate D9.\n */\nexport interface SessionEventWriterConnection {\n\treadonly writeRaw: RawWriter;\n\treadonly waitForBackpressure: BackpressureWaiter;\n}\n\nexport class SessionEventWriter {\n\tprivate readonly queues = new Map<string, RecordQueue>();\n\tprivate readonly connections = new Map<string, SessionEventWriterConnection>();\n\tprivate readonly connectionContext = new AsyncLocalStorage<string>();\n\tprivate readonly controlQueue: RecordQueue = { latestByKey: new Map(), ready: false };\n\tprivate readonly readyQueues: RecordQueue[] = [];\n\tprivate readonly sealedSessions = new Set<string>();\n\tprivate readonly writeRaw: RawWriter;\n\tprivate readonly waitForBackpressure?: BackpressureWaiter;\n\tprivate readonly scheduleFlush: FlushScheduler;\n\tprivate flushScheduled = false;\n\tprivate drainPromise?: Promise<void>;\n\tprivate inFlight?: { queue: RecordQueue; node: QueueNode };\n\tprivate failure?: unknown;\n\n\tconstructor(writeRaw: RawWriter, scheduleFlush?: FlushScheduler);\n\tconstructor(writeRaw: RawWriter, waitForBackpressure: BackpressureWaiter, scheduleFlush?: FlushScheduler);\n\tconstructor(\n\t\twriteRaw: RawWriter,\n\t\twaitOrSchedule?: BackpressureWaiter | FlushScheduler,\n\t\tscheduleFlush?: FlushScheduler,\n\t) {\n\t\tthis.writeRaw = writeRaw;\n\t\tif (waitOrSchedule && scheduleFlush === undefined && waitOrSchedule.length > 0) {\n\t\t\tthis.scheduleFlush = waitOrSchedule as FlushScheduler;\n\t\t} else {\n\t\t\tthis.waitForBackpressure = waitOrSchedule as BackpressureWaiter | undefined;\n\t\t\tthis.scheduleFlush = scheduleFlush ?? ((flush) => queueMicrotask(() => void flush()));\n\t\t}\n\t}\n\n\tget bufferedRecordCount(): number {\n\t\tlet count = this.inFlight ? 1 : 0;\n\t\tfor (const queue of this.allQueues()) {\n\t\t\tfor (let node = queue.head; node; node = node.next) count++;\n\t\t}\n\t\treturn count;\n\t}\n\n\tget bufferedByteLength(): number {\n\t\tlet bytes = this.inFlight ? Buffer.byteLength(serializeJsonLine(this.inFlight.node.value)) : 0;\n\t\tfor (const queue of this.allQueues()) {\n\t\t\tfor (let node = queue.head; node; node = node.next) {\n\t\t\t\tbytes += Buffer.byteLength(serializeJsonLine(node.value));\n\t\t\t}\n\t\t}\n\t\treturn bytes;\n\t}\n\n\tregisterConnection(id: string, connection: SessionEventWriterConnection): void {\n\t\tthis.connections.set(id, connection);\n\t}\n\n\tunregisterConnection(id: string): void {\n\t\tthis.connections.delete(id);\n\t}\n\n\t/** Execute a connection's command with its response destination in context. */\n\twithConnection<T>(id: string, task: () => T): T {\n\t\treturn this.connectionContext.run(id, task);\n\t}\n\n\t/** Queue a session record. Lifecycle/events are broadcast; responses/UI are targeted. */\n\tenqueue(sessionId: string, value: object): boolean {\n\t\tif (this.sealedSessions.has(sessionId)) return false;\n\t\tconst targetId = this.connectionContext.getStore();\n\t\tconst record = value as RpcRecord;\n\t\tconst isTargeted = record.type === \"response\" || record.type === \"extension_ui_request\";\n\t\tconst targets = isTargeted ? [targetId] : this.connections.size > 0 ? [...this.connections.keys()] : [undefined];\n\t\tfor (const target of targets) {\n\t\t\tif (target !== undefined && !this.connections.has(target)) continue;\n\t\t\tthis.appendSessionRecord(sessionId, { ...value, sessionId }, target);\n\t\t}\n\t\tthis.requestFlush();\n\t\treturn true;\n\t}\n\n\t/** Queue one untagged host-control response for the current connection. */\n\tenqueueControl(value: object): Promise<void> {\n\t\tif (this.failure !== undefined) return Promise.reject(this.failure);\n\t\tconst targetId = this.connectionContext.getStore();\n\t\tconst queue = targetId === undefined ? this.controlQueue : this.connectionQueue(targetId);\n\t\tconst completion = new Promise<void>((resolve, reject) => {\n\t\t\tthis.append(queue, { ...value }, undefined, resolve, reject);\n\t\t});\n\t\tthis.markReady(queue);\n\t\tthis.requestFlush();\n\t\treturn completion;\n\t}\n\n\t/**\n\t * Prevent subsequent records for a session and append its terminal response.\n\t * Existing records retain FIFO order; this response is therefore that\n\t * session's final stdout record.\n\t */\n\tcloseSession(sessionId: string, response: object): void {\n\t\tif (this.sealedSessions.has(sessionId)) return;\n\t\tthis.sealedSessions.add(sessionId);\n\t\tconst targetId = this.connectionContext.getStore();\n\t\tconst lifecycle = { type: \"session_closed\", sessionId };\n\t\tfor (const connectionId of this.connections.keys()) {\n\t\t\tthis.appendSessionRecord(sessionId, lifecycle, connectionId);\n\t\t}\n\t\tthis.appendSessionRecord(sessionId, { ...response, sessionId }, targetId);\n\t\tthis.requestFlush();\n\t}\n\n\t/** Drain every retained lane and the current in-flight record. */\n\tflush(): Promise<void> {\n\t\tif (this.failure !== undefined) return Promise.reject(this.failure);\n\t\tthis.flushScheduled = false;\n\t\tif (this.drainPromise) return this.drainPromise;\n\t\tif (this.readyQueues.length === 0) return Promise.resolve();\n\t\tlet resolveDrain!: () => void;\n\t\tlet rejectDrain!: (cause: unknown) => void;\n\t\tconst drain = new Promise<void>((resolve, reject) => {\n\t\t\tresolveDrain = resolve;\n\t\t\trejectDrain = reject;\n\t\t});\n\t\tthis.drainPromise = drain;\n\t\tvoid this.drainUntilEmpty().then(resolveDrain, rejectDrain);\n\t\tvoid drain.then(\n\t\t\t() => {\n\t\t\t\tif (this.drainPromise === drain) this.drainPromise = undefined;\n\t\t\t},\n\t\t\t(cause) => {\n\t\t\t\tif (this.drainPromise === drain) this.drainPromise = undefined;\n\t\t\t\tthis.fail(cause);\n\t\t\t},\n\t\t);\n\t\treturn drain;\n\t}\n\n\tprivate async drainUntilEmpty(): Promise<void> {\n\t\tdo {\n\t\t\tawait this.drainReadyQueues();\n\t\t} while (this.readyQueues.length > 0);\n\t}\n\n\tprivate async drainReadyQueues(): Promise<void> {\n\t\twhile (this.readyQueues.length > 0) {\n\t\t\tconst queue = this.readyQueues.shift()!;\n\t\t\tqueue.ready = false;\n\t\t\tconst node = queue.head;\n\t\t\tif (!node) continue;\n\n\t\t\tthis.unlink(queue, node);\n\t\t\tthis.inFlight = { queue, node };\n\t\t\ttry {\n\t\t\t\t// D9: exactly one complete record per raw write. The next lane is not\n\t\t\t\t// selected until this record has cleared stdout backpressure.\n\t\t\t\tconst connection = queue.targetId ? this.connections.get(queue.targetId) : undefined;\n\t\t\t\tconst writeRaw = connection?.writeRaw ?? this.writeRaw;\n\t\t\t\tconst waitForBackpressure = connection?.waitForBackpressure ?? this.waitForBackpressure;\n\t\t\t\tif (!connection && queue.targetId) {\n\t\t\t\t\tnode.resolve?.();\n\t\t\t\t} else {\n\t\t\t\t\twriteRaw(serializeJsonLine(node.value));\n\t\t\t\t\tif (waitForBackpressure) await waitForBackpressure();\n\t\t\t\t\tnode.resolve?.();\n\t\t\t\t}\n\t\t\t} catch (cause) {\n\t\t\t\tnode.reject?.(cause);\n\t\t\t\tthrow cause;\n\t\t\t} finally {\n\t\t\t\tthis.inFlight = undefined;\n\t\t\t}\n\n\t\t\tif (queue.head) {\n\t\t\t\tthis.markReady(queue);\n\t\t\t} else if (queue.sessionId || queue.targetId) {\n\t\t\t\tfor (const [key, candidate] of this.queues) {\n\t\t\t\t\tif (candidate === queue) this.queues.delete(key);\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\t}\n\n\tprivate connectionQueue(targetId: string): RecordQueue {\n\t\tconst key = `connection:${targetId}`;\n\t\tlet queue = this.queues.get(key);\n\t\tif (!queue) {\n\t\t\tqueue = { targetId, latestByKey: new Map(), ready: false };\n\t\t\tthis.queues.set(key, queue);\n\t\t}\n\t\treturn queue;\n\t}\n\n\tprivate appendSessionRecord(sessionId: string, value: RpcRecord, targetId?: string): void {\n\t\tconst key = `${targetId ?? \"default\"}:${sessionId}`;\n\t\tlet queue = this.queues.get(key);\n\t\tif (!queue) {\n\t\t\tqueue = { sessionId, targetId, latestByKey: new Map(), ready: false };\n\t\t\tthis.queues.set(key, queue);\n\t\t}\n\n\t\tconst delta = compactDelta(value);\n\t\tif (delta) {\n\t\t\tconst previousFull = queue.latestByKey.get(MESSAGE_KEY);\n\t\t\tif (previousFull) this.demoteAndMerge(queue, previousFull);\n\t\t\tconst node = this.append(queue, value, MESSAGE_KEY);\n\t\t\tqueue.latestByKey.set(MESSAGE_KEY, node);\n\t\t\tthis.markReady(queue);\n\t\t\treturn;\n\t\t}\n\n\t\tconst toolKey = toolUpdateKey(value);\n\t\tif (toolKey) {\n\t\t\tconst previous = queue.latestByKey.get(toolKey);\n\t\t\tif (previous) this.unlink(queue, previous);\n\t\t\tconst node = this.append(queue, value, toolKey);\n\t\t\tqueue.latestByKey.set(toolKey, node);\n\t\t\tthis.markReady(queue);\n\t\t\treturn;\n\t\t}\n\n\t\t// All non-compactable records are ordering barriers. In particular this\n\t\t// includes delta-only/full non-delta message updates, protocol responses,\n\t\t// extension UI requests, errors, retries, lifecycle, and unknown records.\n\t\tqueue.latestByKey.clear();\n\t\tthis.append(queue, value);\n\t\tthis.markReady(queue);\n\t}\n\n\tprivate demoteAndMerge(queue: RecordQueue, node: QueueNode): void {\n\t\tconst event = node.value.assistantMessageEvent as Record<string, unknown>;\n\t\tnode.value = {\n\t\t\t...node.value,\n\t\t\tmessage: null,\n\t\t\tassistantMessageEvent: { ...event, partial: null },\n\t\t};\n\t\tconst current = compactDelta(node.value);\n\t\tconst preceding = node.previous;\n\t\tconst previous = preceding ? compactDelta(preceding.value) : undefined;\n\t\tif (\n\t\t\tpreceding &&\n\t\t\tprevious &&\n\t\t\tcurrent &&\n\t\t\tpreceding.value.message === null &&\n\t\t\tprevious.type === current.type &&\n\t\t\tprevious.contentIndex === current.contentIndex\n\t\t) {\n\t\t\tconst precedingEvent = preceding.value.assistantMessageEvent as Record<string, unknown>;\n\t\t\tpreceding.value = {\n\t\t\t\t...preceding.value,\n\t\t\t\tassistantMessageEvent: { ...precedingEvent, delta: previous.delta + current.delta },\n\t\t\t};\n\t\t\tthis.unlink(queue, node);\n\t\t}\n\t}\n\n\tprivate append(\n\t\tqueue: RecordQueue,\n\t\tvalue: RpcRecord,\n\t\tkey?: string,\n\t\tresolve?: () => void,\n\t\treject?: (cause: unknown) => void,\n\t): QueueNode {\n\t\tconst node: QueueNode = { value, key, previous: queue.tail, resolve, reject };\n\t\tif (queue.tail) queue.tail.next = node;\n\t\telse queue.head = node;\n\t\tqueue.tail = node;\n\t\treturn node;\n\t}\n\n\tprivate unlink(queue: RecordQueue, node: QueueNode): void {\n\t\tif (node.previous) node.previous.next = node.next;\n\t\telse queue.head = node.next;\n\t\tif (node.next) node.next.previous = node.previous;\n\t\telse queue.tail = node.previous;\n\t\tif (node.key && queue.latestByKey.get(node.key) === node) queue.latestByKey.delete(node.key);\n\t\tnode.previous = undefined;\n\t\tnode.next = undefined;\n\t}\n\n\tprivate markReady(queue: RecordQueue): void {\n\t\tif (queue.ready || this.inFlight?.queue === queue || !queue.head) return;\n\t\tqueue.ready = true;\n\t\tthis.readyQueues.push(queue);\n\t}\n\n\tprivate requestFlush(): void {\n\t\tif (this.failure !== undefined || this.flushScheduled || this.drainPromise) return;\n\t\tthis.flushScheduled = true;\n\t\tthis.scheduleFlush(() => this.flush());\n\t}\n\n\tprivate fail(cause: unknown): void {\n\t\tif (this.failure !== undefined) return;\n\t\tthis.failure = cause;\n\t\tfor (const queue of this.allQueues()) {\n\t\t\tfor (let node = queue.head; node; node = node.next) node.reject?.(cause);\n\t\t}\n\t}\n\n\tprivate *allQueues(): Iterable<RecordQueue> {\n\t\tyield* this.queues.values();\n\t\tyield this.controlQueue;\n\t}\n}\n"]}
|
|
@@ -13,6 +13,10 @@ export interface RpcSessionEntry {
|
|
|
13
13
|
profile: Readonly<RpcSessionLaunchProfile>;
|
|
14
14
|
durableSessionId?: string;
|
|
15
15
|
sessionPath?: string;
|
|
16
|
+
/** Canonical reservation key for path-opened sessions; matches the reservations set. */
|
|
17
|
+
reservationKey?: string;
|
|
18
|
+
/** Live attachments (open + later attaches). The runtime is disposed only when the last one closes. */
|
|
19
|
+
attachments: number;
|
|
16
20
|
lifecycleMutex: Promise<void>;
|
|
17
21
|
}
|
|
18
22
|
export declare class RpcSessionRegistryError extends Error {
|
|
@@ -27,6 +31,8 @@ export interface OpenRpcSession {
|
|
|
27
31
|
sessionId: string;
|
|
28
32
|
durableSessionId: string;
|
|
29
33
|
sessionPath?: string;
|
|
34
|
+
/** True when this open attached to an already-open session instead of creating one. */
|
|
35
|
+
attached?: boolean;
|
|
30
36
|
}
|
|
31
37
|
/** Process-local lifecycle owner for multi-session RPC runtimes. */
|
|
32
38
|
export declare class RpcSessionRegistry {
|
|
@@ -40,6 +46,9 @@ export declare class RpcSessionRegistry {
|
|
|
40
46
|
/**
|
|
41
47
|
* Starts a close synchronously. Call this before disposing any session-owned
|
|
42
48
|
* binding so a concurrent command cannot reach a half-disposed handler.
|
|
49
|
+
* Each call releases one attachment; the entry transitions to "closing" only
|
|
50
|
+
* when the last attachment closes, so callers must check the returned state
|
|
51
|
+
* and skip finalization while other attachments remain.
|
|
43
52
|
*/
|
|
44
53
|
beginClose(handle: string): RpcSessionEntry;
|
|
45
54
|
close(handle: string): Promise<void>;
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"session-registry.d.ts","sourceRoot":"","sources":["../../../src/modes/rpc/session-registry.ts"],"names":[],"mappings":"AAEA,OAAO,EAAE,aAAa,EAAwB,MAAM,2CAA2C,CAAC;AAChG,OAAO,EACN,KAAK,yBAAyB,EAC9B,KAAK,mBAAmB,EACxB,KAAK,gCAAgC,EAErC,MAAM,qCAAqC,CAAC;AAG7C,qEAAqE;AACrE,MAAM,WAAW,uBAAwB,SAAQ,yBAAyB;IACzE,WAAW,CAAC,EAAE,MAAM,CAAC;CACrB;AAED,MAAM,MAAM,cAAc,GAAG,IAAI,CAAC,mBAAmB,EAAE,SAAS,GAAG,SAAS,CAAC,CAAC;AAC9E,MAAM,MAAM,eAAe,GAAG,SAAS,GAAG,MAAM,GAAG,SAAS,GAAG,QAAQ,CAAC;AAExE,MAAM,WAAW,eAAe;IAC/B,KAAK,EAAE,eAAe,CAAC;IACvB,OAAO,CAAC,EAAE,cAAc,CAAC;IACzB,KAAK,EAAE,aAAa,CAAC;IACrB,OAAO,EAAE,QAAQ,CAAC,uBAAuB,CAAC,CAAC;IAC3C,gBAAgB,CAAC,EAAE,MAAM,CAAC;IAC1B,WAAW,CAAC,EAAE,MAAM,CAAC;IACrB,cAAc,EAAE,OAAO,CAAC,IAAI,CAAC,CAAC;CAC9B;AAED,qBAAa,uBAAwB,SAAQ,KAAK;IACjD,QAAQ,CAAC,IAAI,EAAE,iBAAiB,GAAG,iBAAiB,GAAG,qBAAqB,GAAG,cAAc,GAAG,aAAa,CAAC;gBAElG,IAAI,EAAE,uBAAuB,CAAC,MAAM,CAAC,EAAE,MAAM,CAAC,EAAE,MAAM;CAKlE;AAED,MAAM,WAAW,yBAAyB;IACzC,QAAQ,EAAE,MAAM,CAAC;IACjB,aAAa,EAAE,gCAAgC,CAAC;CAChD;AAED,MAAM,WAAW,cAAc;IAC9B,SAAS,EAAE,MAAM,CAAC;IAClB,gBAAgB,EAAE,MAAM,CAAC;IACzB,WAAW,CAAC,EAAE,MAAM,CAAC;
|
|
1
|
+
{"version":3,"file":"session-registry.d.ts","sourceRoot":"","sources":["../../../src/modes/rpc/session-registry.ts"],"names":[],"mappings":"AAEA,OAAO,EAAE,aAAa,EAAwB,MAAM,2CAA2C,CAAC;AAChG,OAAO,EACN,KAAK,yBAAyB,EAC9B,KAAK,mBAAmB,EACxB,KAAK,gCAAgC,EAErC,MAAM,qCAAqC,CAAC;AAG7C,qEAAqE;AACrE,MAAM,WAAW,uBAAwB,SAAQ,yBAAyB;IACzE,WAAW,CAAC,EAAE,MAAM,CAAC;CACrB;AAED,MAAM,MAAM,cAAc,GAAG,IAAI,CAAC,mBAAmB,EAAE,SAAS,GAAG,SAAS,CAAC,CAAC;AAC9E,MAAM,MAAM,eAAe,GAAG,SAAS,GAAG,MAAM,GAAG,SAAS,GAAG,QAAQ,CAAC;AAExE,MAAM,WAAW,eAAe;IAC/B,KAAK,EAAE,eAAe,CAAC;IACvB,OAAO,CAAC,EAAE,cAAc,CAAC;IACzB,KAAK,EAAE,aAAa,CAAC;IACrB,OAAO,EAAE,QAAQ,CAAC,uBAAuB,CAAC,CAAC;IAC3C,gBAAgB,CAAC,EAAE,MAAM,CAAC;IAC1B,WAAW,CAAC,EAAE,MAAM,CAAC;IACrB,wFAAwF;IACxF,cAAc,CAAC,EAAE,MAAM,CAAC;IACxB,uGAAuG;IACvG,WAAW,EAAE,MAAM,CAAC;IACpB,cAAc,EAAE,OAAO,CAAC,IAAI,CAAC,CAAC;CAC9B;AAED,qBAAa,uBAAwB,SAAQ,KAAK;IACjD,QAAQ,CAAC,IAAI,EAAE,iBAAiB,GAAG,iBAAiB,GAAG,qBAAqB,GAAG,cAAc,GAAG,aAAa,CAAC;gBAElG,IAAI,EAAE,uBAAuB,CAAC,MAAM,CAAC,EAAE,MAAM,CAAC,EAAE,MAAM;CAKlE;AAED,MAAM,WAAW,yBAAyB;IACzC,QAAQ,EAAE,MAAM,CAAC;IACjB,aAAa,EAAE,gCAAgC,CAAC;CAChD;AAED,MAAM,WAAW,cAAc;IAC9B,SAAS,EAAE,MAAM,CAAC;IAClB,gBAAgB,EAAE,MAAM,CAAC;IACzB,WAAW,CAAC,EAAE,MAAM,CAAC;IACrB,uFAAuF;IACvF,QAAQ,CAAC,EAAE,OAAO,CAAC;CACnB;AAeD,oEAAoE;AACpE,qBAAa,kBAAkB;IAC9B,OAAO,CAAC,QAAQ,CAAC,OAAO,CAAsC;IAC9D,OAAO,CAAC,QAAQ,CAAC,YAAY,CAAqB;IAClD,OAAO,CAAC,UAAU,CAAK;IACvB,OAAO,CAAC,QAAQ,CAAC,OAAO,CAA4B;gBAExC,OAAO,EAAE,yBAAyB;IAIxC,WAAW,CAAC,OAAO,EAAE,uBAAuB,GAAG,OAAO,CAAC,cAAc,CAAC;IAmF5E,aAAa,CAAC,MAAM,EAAE,MAAM,EAAE,OAAO,EAAE,MAAM,GAAG,eAAe;IAU/D;;;;;;OAMG;IACH,UAAU,CAAC,MAAM,EAAE,MAAM,GAAG,eAAe;IASrC,KAAK,CAAC,MAAM,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC;IAO1C,iEAAiE;IAC3D,WAAW,CAAC,MAAM,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC;IAkBhD,IAAI,IAAI,KAAK,CAAC;QACb,SAAS,EAAE,MAAM,CAAC;QAClB,gBAAgB,CAAC,EAAE,MAAM,CAAC;QAC1B,WAAW,CAAC,EAAE,MAAM,CAAC;QACrB,GAAG,EAAE,MAAM,CAAC;QACZ,IAAI,CAAC,EAAE,MAAM,CAAC;QACd,MAAM,EAAE,eAAe,CAAC;KACxB,CAAC;IAWF,OAAO,CAAC,eAAe;CAKvB"}
|
|
@@ -33,8 +33,25 @@ export class RpcSessionRegistry {
|
|
|
33
33
|
async openSession(profile) {
|
|
34
34
|
this.validateProfile(profile);
|
|
35
35
|
const sessionPath = profile.sessionPath ? canonicalPath(profile.sessionPath) : undefined;
|
|
36
|
-
if (sessionPath && this.reservations.has(sessionPath))
|
|
37
|
-
|
|
36
|
+
if (sessionPath && this.reservations.has(sessionPath)) {
|
|
37
|
+
// Attach-on-open: a live session outlives individual client attachments, so a
|
|
38
|
+
// resume (or a second surface) for an already-hosted path joins the existing
|
|
39
|
+
// runtime instead of failing. Entries still opening or closing keep the
|
|
40
|
+
// exclusive reservation and reject as before.
|
|
41
|
+
const existing = [...this.entries].find(([, entry]) => entry.reservationKey === sessionPath && entry.state === "open");
|
|
42
|
+
if (!existing)
|
|
43
|
+
throw new RpcSessionRegistryError("session_path_in_use");
|
|
44
|
+
const [handle, entry] = existing;
|
|
45
|
+
entry.attachments += 1;
|
|
46
|
+
if (!entry.durableSessionId)
|
|
47
|
+
throw new RpcSessionRegistryError("session_path_in_use");
|
|
48
|
+
return {
|
|
49
|
+
sessionId: handle,
|
|
50
|
+
durableSessionId: entry.durableSessionId,
|
|
51
|
+
sessionPath: entry.sessionPath,
|
|
52
|
+
attached: true,
|
|
53
|
+
};
|
|
54
|
+
}
|
|
38
55
|
if (sessionPath)
|
|
39
56
|
this.reservations.add(sessionPath);
|
|
40
57
|
// Resume vs create parity (D1 + omo SenpiSessionRuntime.ts:198-200):
|
|
@@ -52,6 +69,8 @@ export class RpcSessionRegistry {
|
|
|
52
69
|
scope: new ProviderScope(),
|
|
53
70
|
profile: storedProfile,
|
|
54
71
|
sessionPath,
|
|
72
|
+
reservationKey: sessionPath,
|
|
73
|
+
attachments: 1,
|
|
55
74
|
lifecycleMutex: Promise.resolve(),
|
|
56
75
|
};
|
|
57
76
|
this.entries.set(handle, entry);
|
|
@@ -110,16 +129,25 @@ export class RpcSessionRegistry {
|
|
|
110
129
|
/**
|
|
111
130
|
* Starts a close synchronously. Call this before disposing any session-owned
|
|
112
131
|
* binding so a concurrent command cannot reach a half-disposed handler.
|
|
132
|
+
* Each call releases one attachment; the entry transitions to "closing" only
|
|
133
|
+
* when the last attachment closes, so callers must check the returned state
|
|
134
|
+
* and skip finalization while other attachments remain.
|
|
113
135
|
*/
|
|
114
136
|
beginClose(handle) {
|
|
115
137
|
const entry = this.entries.get(handle);
|
|
116
138
|
if (entry?.state !== "open")
|
|
117
139
|
throw new RpcSessionRegistryError("unknown_session");
|
|
140
|
+
entry.attachments -= 1;
|
|
141
|
+
if (entry.attachments > 0)
|
|
142
|
+
return entry;
|
|
118
143
|
entry.state = "closing";
|
|
119
144
|
return entry;
|
|
120
145
|
}
|
|
121
146
|
async close(handle) {
|
|
122
|
-
this.beginClose(handle);
|
|
147
|
+
const entry = this.beginClose(handle);
|
|
148
|
+
// Other attachments may still own the live session; only the last close finalizes.
|
|
149
|
+
if (entry.state !== "closing")
|
|
150
|
+
return;
|
|
123
151
|
return this.closeMarked(handle);
|
|
124
152
|
}
|
|
125
153
|
/** Completes a close previously made visible by beginClose(). */
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"session-registry.js","sourceRoot":"","sources":["../../../src/modes/rpc/session-registry.ts"],"names":[],"mappings":"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","sourcesContent":["import { existsSync, realpathSync } from \"node:fs\";\nimport { basename, dirname, isAbsolute, resolve } from \"node:path\";\nimport { ProviderScope, runWithProviderScope } from \"@earendil-works/pi-ai/node/provider-scope\";\nimport {\n\ttype AgentSessionLaunchProfile,\n\ttype AgentSessionRuntime,\n\ttype CreateAgentSessionRuntimeFactory,\n\tcreateAgentSessionRuntime,\n} from \"../../core/agent-session-runtime.ts\";\nimport { SessionManager } from \"../../core/session-manager.ts\";\n\n/** The immutable flags selected when a routing session is opened. */\nexport interface RpcSessionLaunchProfile extends AgentSessionLaunchProfile {\n\tsessionPath?: string;\n}\n\nexport type SessionRuntime = Pick<AgentSessionRuntime, \"session\" | \"dispose\">;\nexport type RpcSessionState = \"opening\" | \"open\" | \"closing\" | \"closed\";\n\nexport interface RpcSessionEntry {\n\tstate: RpcSessionState;\n\truntime?: SessionRuntime;\n\tscope: ProviderScope;\n\tprofile: Readonly<RpcSessionLaunchProfile>;\n\tdurableSessionId?: string;\n\tsessionPath?: string;\n\tlifecycleMutex: Promise<void>;\n}\n\nexport class RpcSessionRegistryError extends Error {\n\treadonly code: \"unknown_session\" | \"session_closing\" | \"session_path_in_use\" | \"invalid_path\" | \"open_failed\";\n\n\tconstructor(code: RpcSessionRegistryError[\"code\"], reason?: string) {\n\t\tsuper(code === \"open_failed\" && reason ? `${code}: ${reason}` : code);\n\t\tthis.code = code;\n\t\tthis.name = \"RpcSessionRegistryError\";\n\t}\n}\n\nexport interface RpcSessionRegistryOptions {\n\tagentDir: string;\n\tcreateRuntime: CreateAgentSessionRuntimeFactory;\n}\n\nexport interface OpenRpcSession {\n\tsessionId: string;\n\tdurableSessionId: string;\n\tsessionPath?: string;\n}\n\nfunction canonicalPath(path: string): string {\n\tconst absolutePath = resolve(path);\n\tif (existsSync(absolutePath)) return realpathSync(absolutePath);\n\treturn `${realpathSync(dirname(absolutePath))}/${basename(absolutePath)}`;\n}\n\nfunction frozenProfile(profile: RpcSessionLaunchProfile): Readonly<RpcSessionLaunchProfile> {\n\treturn Object.freeze({\n\t\t...profile,\n\t\t...(profile.creationModel ? { creationModel: Object.freeze({ ...profile.creationModel }) } : {}),\n\t});\n}\n\n/** Process-local lifecycle owner for multi-session RPC runtimes. */\nexport class RpcSessionRegistry {\n\tprivate readonly entries = new Map<string, RpcSessionEntry>();\n\tprivate readonly reservations = new Set<string>();\n\tprivate nextHandle = 0;\n\tprivate readonly options: RpcSessionRegistryOptions;\n\n\tconstructor(options: RpcSessionRegistryOptions) {\n\t\tthis.options = options;\n\t}\n\n\tasync openSession(profile: RpcSessionLaunchProfile): Promise<OpenRpcSession> {\n\t\tthis.validateProfile(profile);\n\t\tconst sessionPath = profile.sessionPath ? canonicalPath(profile.sessionPath) : undefined;\n\t\tif (sessionPath && this.reservations.has(sessionPath)) throw new RpcSessionRegistryError(\"session_path_in_use\");\n\t\tif (sessionPath) this.reservations.add(sessionPath);\n\n\t\t// Resume vs create parity (D1 + omo SenpiSessionRuntime.ts:198-200):\n\t\t// Create-only launch semantics mirror classic startup flags. A resumed\n\t\t// session restores its persisted model and thinking level instead of being\n\t\t// overridden by the new open_session request.\n\t\tconst isResume = sessionPath !== undefined && existsSync(sessionPath);\n\t\tconst storedProfile = frozenProfile({ ...profile, ...(sessionPath ? { sessionPath } : {}) });\n\t\tconst runtimeProfile = isResume\n\t\t\t? frozenProfile({ ...storedProfile, creationModel: undefined, initialThinkingLevel: undefined })\n\t\t\t: storedProfile;\n\n\t\tconst handle = `rpc-${++this.nextHandle}`;\n\t\tconst entry: RpcSessionEntry = {\n\t\t\tstate: \"opening\",\n\t\t\tscope: new ProviderScope(),\n\t\t\tprofile: storedProfile,\n\t\t\tsessionPath,\n\t\t\tlifecycleMutex: Promise.resolve(),\n\t\t};\n\t\tthis.entries.set(handle, entry);\n\t\ttry {\n\t\t\tconst manager = sessionPath\n\t\t\t\t? SessionManager.open(sessionPath, undefined, storedProfile.cwd)\n\t\t\t\t: SessionManager.create(storedProfile.cwd);\n\t\t\tentry.runtime = await runWithProviderScope(entry.scope, () =>\n\t\t\t\tcreateAgentSessionRuntime(this.options.createRuntime, {\n\t\t\t\t\tcwd: manager.getCwd(),\n\t\t\t\t\tagentDir: this.options.agentDir,\n\t\t\t\t\tsessionManager: manager,\n\t\t\t\t\tlaunchProfile: runtimeProfile,\n\t\t\t\t}),\n\t\t\t);\n\t\t\tentry.durableSessionId = manager.getSessionId();\n\t\t\tentry.sessionPath ??= manager.getSessionFile();\n\t\t\tentry.state = \"open\";\n\t\t\treturn { sessionId: handle, durableSessionId: entry.durableSessionId, sessionPath: entry.sessionPath };\n\t\t} catch (error) {\n\t\t\t// Runtime construction may have started extensions, watchers, and provider\n\t\t\t// registrations before it rejects. Keep the reservation and entry private\n\t\t\t// until all of those resources have been torn down, then release them as\n\t\t\t// one rollback so the path can immediately be opened again.\n\t\t\ttry {\n\t\t\t\tawait entry.runtime?.dispose();\n\t\t\t} catch {\n\t\t\t\t// The original construction error remains the externally visible cause.\n\t\t\t} finally {\n\t\t\t\ttry {\n\t\t\t\t\tawait entry.scope.close?.();\n\t\t\t\t} finally {\n\t\t\t\t\tthis.entries.delete(handle);\n\t\t\t\t\tif (sessionPath) this.reservations.delete(sessionPath);\n\t\t\t\t}\n\t\t\t}\n\t\t\tif (error instanceof RpcSessionRegistryError) throw error;\n\t\t\tthrow new RpcSessionRegistryError(\"open_failed\", error instanceof Error ? error.message : undefined);\n\t\t}\n\t}\n\n\tgetForCommand(handle: string, command: string): RpcSessionEntry {\n\t\tconst entry = this.entries.get(handle);\n\t\tif (!entry) throw new RpcSessionRegistryError(\"unknown_session\");\n\t\tif (entry.state === \"closing\" && ![\"abort\", \"abort_bash\", \"extension_ui_response\"].includes(command)) {\n\t\t\tthrow new RpcSessionRegistryError(\"session_closing\");\n\t\t}\n\t\tif (entry.state !== \"open\" && entry.state !== \"closing\") throw new RpcSessionRegistryError(\"unknown_session\");\n\t\treturn entry;\n\t}\n\n\t/**\n\t * Starts a close synchronously. Call this before disposing any session-owned\n\t * binding so a concurrent command cannot reach a half-disposed handler.\n\t */\n\tbeginClose(handle: string): RpcSessionEntry {\n\t\tconst entry = this.entries.get(handle);\n\t\tif (entry?.state !== \"open\") throw new RpcSessionRegistryError(\"unknown_session\");\n\t\tentry.state = \"closing\";\n\t\treturn entry;\n\t}\n\n\tasync close(handle: string): Promise<void> {\n\t\tthis.beginClose(handle);\n\t\treturn this.closeMarked(handle);\n\t}\n\n\t/** Completes a close previously made visible by beginClose(). */\n\tasync closeMarked(handle: string): Promise<void> {\n\t\tconst entry = this.entries.get(handle);\n\t\tif (entry?.state !== \"closing\") throw new RpcSessionRegistryError(\"unknown_session\");\n\t\tentry.lifecycleMutex = (async () => {\n\t\t\ttry {\n\t\t\t\tawait entry.runtime?.session.abort();\n\t\t\t\tawait entry.runtime?.session.waitForIdle();\n\t\t\t\tawait entry.runtime?.dispose();\n\t\t\t\tawait entry.scope.close?.();\n\t\t\t} finally {\n\t\t\t\tentry.state = \"closed\";\n\t\t\t\tthis.entries.delete(handle);\n\t\t\t\tif (entry.sessionPath) this.reservations.delete(entry.sessionPath);\n\t\t\t}\n\t\t})();\n\t\treturn entry.lifecycleMutex;\n\t}\n\n\tlist(): Array<{\n\t\tsessionId: string;\n\t\tdurableSessionId?: string;\n\t\tsessionPath?: string;\n\t\tcwd: string;\n\t\tname?: string;\n\t\tstatus: RpcSessionState;\n\t}> {\n\t\treturn [...this.entries].map(([sessionId, entry]) => ({\n\t\t\tsessionId,\n\t\t\tdurableSessionId: entry.durableSessionId,\n\t\t\tsessionPath: entry.sessionPath,\n\t\t\tcwd: entry.profile.cwd,\n\t\t\tname: entry.runtime?.session.sessionManager.getSessionName(),\n\t\t\tstatus: entry.state,\n\t\t}));\n\t}\n\n\tprivate validateProfile(profile: RpcSessionLaunchProfile): void {\n\t\tif (!isAbsolute(profile.cwd) || (profile.sessionPath !== undefined && !isAbsolute(profile.sessionPath))) {\n\t\t\tthrow new RpcSessionRegistryError(\"invalid_path\");\n\t\t}\n\t}\n}\n"]}
|
|
1
|
+
{"version":3,"file":"session-registry.js","sourceRoot":"","sources":["../../../src/modes/rpc/session-registry.ts"],"names":[],"mappings":"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","sourcesContent":["import { existsSync, realpathSync } from \"node:fs\";\nimport { basename, dirname, isAbsolute, resolve } from \"node:path\";\nimport { ProviderScope, runWithProviderScope } from \"@earendil-works/pi-ai/node/provider-scope\";\nimport {\n\ttype AgentSessionLaunchProfile,\n\ttype AgentSessionRuntime,\n\ttype CreateAgentSessionRuntimeFactory,\n\tcreateAgentSessionRuntime,\n} from \"../../core/agent-session-runtime.ts\";\nimport { SessionManager } from \"../../core/session-manager.ts\";\n\n/** The immutable flags selected when a routing session is opened. */\nexport interface RpcSessionLaunchProfile extends AgentSessionLaunchProfile {\n\tsessionPath?: string;\n}\n\nexport type SessionRuntime = Pick<AgentSessionRuntime, \"session\" | \"dispose\">;\nexport type RpcSessionState = \"opening\" | \"open\" | \"closing\" | \"closed\";\n\nexport interface RpcSessionEntry {\n\tstate: RpcSessionState;\n\truntime?: SessionRuntime;\n\tscope: ProviderScope;\n\tprofile: Readonly<RpcSessionLaunchProfile>;\n\tdurableSessionId?: string;\n\tsessionPath?: string;\n\t/** Canonical reservation key for path-opened sessions; matches the reservations set. */\n\treservationKey?: string;\n\t/** Live attachments (open + later attaches). The runtime is disposed only when the last one closes. */\n\tattachments: number;\n\tlifecycleMutex: Promise<void>;\n}\n\nexport class RpcSessionRegistryError extends Error {\n\treadonly code: \"unknown_session\" | \"session_closing\" | \"session_path_in_use\" | \"invalid_path\" | \"open_failed\";\n\n\tconstructor(code: RpcSessionRegistryError[\"code\"], reason?: string) {\n\t\tsuper(code === \"open_failed\" && reason ? `${code}: ${reason}` : code);\n\t\tthis.code = code;\n\t\tthis.name = \"RpcSessionRegistryError\";\n\t}\n}\n\nexport interface RpcSessionRegistryOptions {\n\tagentDir: string;\n\tcreateRuntime: CreateAgentSessionRuntimeFactory;\n}\n\nexport interface OpenRpcSession {\n\tsessionId: string;\n\tdurableSessionId: string;\n\tsessionPath?: string;\n\t/** True when this open attached to an already-open session instead of creating one. */\n\tattached?: boolean;\n}\n\nfunction canonicalPath(path: string): string {\n\tconst absolutePath = resolve(path);\n\tif (existsSync(absolutePath)) return realpathSync(absolutePath);\n\treturn `${realpathSync(dirname(absolutePath))}/${basename(absolutePath)}`;\n}\n\nfunction frozenProfile(profile: RpcSessionLaunchProfile): Readonly<RpcSessionLaunchProfile> {\n\treturn Object.freeze({\n\t\t...profile,\n\t\t...(profile.creationModel ? { creationModel: Object.freeze({ ...profile.creationModel }) } : {}),\n\t});\n}\n\n/** Process-local lifecycle owner for multi-session RPC runtimes. */\nexport class RpcSessionRegistry {\n\tprivate readonly entries = new Map<string, RpcSessionEntry>();\n\tprivate readonly reservations = new Set<string>();\n\tprivate nextHandle = 0;\n\tprivate readonly options: RpcSessionRegistryOptions;\n\n\tconstructor(options: RpcSessionRegistryOptions) {\n\t\tthis.options = options;\n\t}\n\n\tasync openSession(profile: RpcSessionLaunchProfile): Promise<OpenRpcSession> {\n\t\tthis.validateProfile(profile);\n\t\tconst sessionPath = profile.sessionPath ? canonicalPath(profile.sessionPath) : undefined;\n\t\tif (sessionPath && this.reservations.has(sessionPath)) {\n\t\t\t// Attach-on-open: a live session outlives individual client attachments, so a\n\t\t\t// resume (or a second surface) for an already-hosted path joins the existing\n\t\t\t// runtime instead of failing. Entries still opening or closing keep the\n\t\t\t// exclusive reservation and reject as before.\n\t\t\tconst existing = [...this.entries].find(\n\t\t\t\t([, entry]) => entry.reservationKey === sessionPath && entry.state === \"open\",\n\t\t\t);\n\t\t\tif (!existing) throw new RpcSessionRegistryError(\"session_path_in_use\");\n\t\t\tconst [handle, entry] = existing;\n\t\t\tentry.attachments += 1;\n\t\t\tif (!entry.durableSessionId) throw new RpcSessionRegistryError(\"session_path_in_use\");\n\t\t\treturn {\n\t\t\t\tsessionId: handle,\n\t\t\t\tdurableSessionId: entry.durableSessionId,\n\t\t\t\tsessionPath: entry.sessionPath,\n\t\t\t\tattached: true,\n\t\t\t};\n\t\t}\n\t\tif (sessionPath) this.reservations.add(sessionPath);\n\n\t\t// Resume vs create parity (D1 + omo SenpiSessionRuntime.ts:198-200):\n\t\t// Create-only launch semantics mirror classic startup flags. A resumed\n\t\t// session restores its persisted model and thinking level instead of being\n\t\t// overridden by the new open_session request.\n\t\tconst isResume = sessionPath !== undefined && existsSync(sessionPath);\n\t\tconst storedProfile = frozenProfile({ ...profile, ...(sessionPath ? { sessionPath } : {}) });\n\t\tconst runtimeProfile = isResume\n\t\t\t? frozenProfile({ ...storedProfile, creationModel: undefined, initialThinkingLevel: undefined })\n\t\t\t: storedProfile;\n\n\t\tconst handle = `rpc-${++this.nextHandle}`;\n\t\tconst entry: RpcSessionEntry = {\n\t\t\tstate: \"opening\",\n\t\t\tscope: new ProviderScope(),\n\t\t\tprofile: storedProfile,\n\t\t\tsessionPath,\n\t\t\treservationKey: sessionPath,\n\t\t\tattachments: 1,\n\t\t\tlifecycleMutex: Promise.resolve(),\n\t\t};\n\t\tthis.entries.set(handle, entry);\n\t\ttry {\n\t\t\tconst manager = sessionPath\n\t\t\t\t? SessionManager.open(sessionPath, undefined, storedProfile.cwd)\n\t\t\t\t: SessionManager.create(storedProfile.cwd);\n\t\t\tentry.runtime = await runWithProviderScope(entry.scope, () =>\n\t\t\t\tcreateAgentSessionRuntime(this.options.createRuntime, {\n\t\t\t\t\tcwd: manager.getCwd(),\n\t\t\t\t\tagentDir: this.options.agentDir,\n\t\t\t\t\tsessionManager: manager,\n\t\t\t\t\tlaunchProfile: runtimeProfile,\n\t\t\t\t}),\n\t\t\t);\n\t\t\tentry.durableSessionId = manager.getSessionId();\n\t\t\tentry.sessionPath ??= manager.getSessionFile();\n\t\t\tentry.state = \"open\";\n\t\t\treturn { sessionId: handle, durableSessionId: entry.durableSessionId, sessionPath: entry.sessionPath };\n\t\t} catch (error) {\n\t\t\t// Runtime construction may have started extensions, watchers, and provider\n\t\t\t// registrations before it rejects. Keep the reservation and entry private\n\t\t\t// until all of those resources have been torn down, then release them as\n\t\t\t// one rollback so the path can immediately be opened again.\n\t\t\ttry {\n\t\t\t\tawait entry.runtime?.dispose();\n\t\t\t} catch {\n\t\t\t\t// The original construction error remains the externally visible cause.\n\t\t\t} finally {\n\t\t\t\ttry {\n\t\t\t\t\tawait entry.scope.close?.();\n\t\t\t\t} finally {\n\t\t\t\t\tthis.entries.delete(handle);\n\t\t\t\t\tif (sessionPath) this.reservations.delete(sessionPath);\n\t\t\t\t}\n\t\t\t}\n\t\t\tif (error instanceof RpcSessionRegistryError) throw error;\n\t\t\tthrow new RpcSessionRegistryError(\"open_failed\", error instanceof Error ? error.message : undefined);\n\t\t}\n\t}\n\n\tgetForCommand(handle: string, command: string): RpcSessionEntry {\n\t\tconst entry = this.entries.get(handle);\n\t\tif (!entry) throw new RpcSessionRegistryError(\"unknown_session\");\n\t\tif (entry.state === \"closing\" && ![\"abort\", \"abort_bash\", \"extension_ui_response\"].includes(command)) {\n\t\t\tthrow new RpcSessionRegistryError(\"session_closing\");\n\t\t}\n\t\tif (entry.state !== \"open\" && entry.state !== \"closing\") throw new RpcSessionRegistryError(\"unknown_session\");\n\t\treturn entry;\n\t}\n\n\t/**\n\t * Starts a close synchronously. Call this before disposing any session-owned\n\t * binding so a concurrent command cannot reach a half-disposed handler.\n\t * Each call releases one attachment; the entry transitions to \"closing\" only\n\t * when the last attachment closes, so callers must check the returned state\n\t * and skip finalization while other attachments remain.\n\t */\n\tbeginClose(handle: string): RpcSessionEntry {\n\t\tconst entry = this.entries.get(handle);\n\t\tif (entry?.state !== \"open\") throw new RpcSessionRegistryError(\"unknown_session\");\n\t\tentry.attachments -= 1;\n\t\tif (entry.attachments > 0) return entry;\n\t\tentry.state = \"closing\";\n\t\treturn entry;\n\t}\n\n\tasync close(handle: string): Promise<void> {\n\t\tconst entry = this.beginClose(handle);\n\t\t// Other attachments may still own the live session; only the last close finalizes.\n\t\tif (entry.state !== \"closing\") return;\n\t\treturn this.closeMarked(handle);\n\t}\n\n\t/** Completes a close previously made visible by beginClose(). */\n\tasync closeMarked(handle: string): Promise<void> {\n\t\tconst entry = this.entries.get(handle);\n\t\tif (entry?.state !== \"closing\") throw new RpcSessionRegistryError(\"unknown_session\");\n\t\tentry.lifecycleMutex = (async () => {\n\t\t\ttry {\n\t\t\t\tawait entry.runtime?.session.abort();\n\t\t\t\tawait entry.runtime?.session.waitForIdle();\n\t\t\t\tawait entry.runtime?.dispose();\n\t\t\t\tawait entry.scope.close?.();\n\t\t\t} finally {\n\t\t\t\tentry.state = \"closed\";\n\t\t\t\tthis.entries.delete(handle);\n\t\t\t\tif (entry.sessionPath) this.reservations.delete(entry.sessionPath);\n\t\t\t}\n\t\t})();\n\t\treturn entry.lifecycleMutex;\n\t}\n\n\tlist(): Array<{\n\t\tsessionId: string;\n\t\tdurableSessionId?: string;\n\t\tsessionPath?: string;\n\t\tcwd: string;\n\t\tname?: string;\n\t\tstatus: RpcSessionState;\n\t}> {\n\t\treturn [...this.entries].map(([sessionId, entry]) => ({\n\t\t\tsessionId,\n\t\t\tdurableSessionId: entry.durableSessionId,\n\t\t\tsessionPath: entry.sessionPath,\n\t\t\tcwd: entry.profile.cwd,\n\t\t\tname: entry.runtime?.session.sessionManager.getSessionName(),\n\t\t\tstatus: entry.state,\n\t\t}));\n\t}\n\n\tprivate validateProfile(profile: RpcSessionLaunchProfile): void {\n\t\tif (!isAbsolute(profile.cwd) || (profile.sessionPath !== undefined && !isAbsolute(profile.sessionPath))) {\n\t\t\tthrow new RpcSessionRegistryError(\"invalid_path\");\n\t\t}\n\t}\n}\n"]}
|
package/docs/providers.md
CHANGED
|
@@ -302,6 +302,54 @@ credential under the provider you select; an environment variable is shared by b
|
|
|
302
302
|
|
|
303
303
|
The file is created with `0600` permissions (user read/write only). Auth file credentials take priority over environment variables.
|
|
304
304
|
|
|
305
|
+
#### Multiple accounts per provider
|
|
306
|
+
|
|
307
|
+
Any provider can hold more than one credential. The quickest way is numbered environment variables
|
|
308
|
+
over the provider's primary key variable - a gap in the numbering is fine:
|
|
309
|
+
|
|
310
|
+
```bash
|
|
311
|
+
export OPENAI_API_KEY="sk-first"
|
|
312
|
+
export OPENAI_API_KEY_2="sk-second" # any provider: ANTHROPIC_API_KEY_2, GEMINI_API_KEY_2, ...
|
|
313
|
+
```
|
|
314
|
+
|
|
315
|
+
Stored credentials pool through an `accounts` array. The flat top-level fields always stay a valid
|
|
316
|
+
credential (older binaries keep authenticating with them), and each slot's `name` is its stable
|
|
317
|
+
identity:
|
|
318
|
+
|
|
319
|
+
```json
|
|
320
|
+
{
|
|
321
|
+
"openai": {
|
|
322
|
+
"type": "api_key",
|
|
323
|
+
"key": "sk-first",
|
|
324
|
+
"accounts": [
|
|
325
|
+
{ "name": "default", "key": "sk-first", "source": "login" },
|
|
326
|
+
{ "name": "work", "key": "sk-second", "source": "login" }
|
|
327
|
+
],
|
|
328
|
+
"pinned": "work"
|
|
329
|
+
}
|
|
330
|
+
}
|
|
331
|
+
```
|
|
332
|
+
|
|
333
|
+
With more than one account, requests rotate automatically: a session sticks to one account
|
|
334
|
+
(session-affine selection), and a rate-limited or auth-failed account fails over to a healthy
|
|
335
|
+
sibling **within the same provider and model** before the model fallback chain is consulted -
|
|
336
|
+
credential rotation changes which account serves the request, never which model answers it.
|
|
337
|
+
Rotation never happens after a response has started streaming. Cooldowns persist in
|
|
338
|
+
`~/.senpi/agent/credential-pool-state.json` (health only - never key material).
|
|
339
|
+
|
|
340
|
+
Manage accounts with `/account <provider> [list | pin <name> | unpin | remove <name>]`, and tune
|
|
341
|
+
behavior per provider in `models.json`:
|
|
342
|
+
|
|
343
|
+
```json
|
|
344
|
+
{
|
|
345
|
+
"providers": {
|
|
346
|
+
"openai": {
|
|
347
|
+
"credentials": { "rotation": true, "affinity": true, "cooldownBaseMs": 60000 }
|
|
348
|
+
}
|
|
349
|
+
}
|
|
350
|
+
}
|
|
351
|
+
```
|
|
352
|
+
|
|
305
353
|
API key credentials can also include provider-scoped environment values. These values are used before process environment variables when resolving the credential key, provider/model headers, and provider configuration such as Cloudflare account IDs, Azure OpenAI settings, Vertex project/location, Bedrock settings, `PI_CACHE_RETENTION`, and `HTTP_PROXY`/`HTTPS_PROXY`.
|
|
306
354
|
|
|
307
355
|
```json
|
package/docs/rpc.md
CHANGED
|
@@ -1,5 +1,7 @@
|
|
|
1
1
|
# RPC Mode
|
|
2
2
|
|
|
3
|
+
The shared Unix socket host uses `<agentDir>/rpc-host-daemon/host.pid` and `settings.json` as its ownership state. Clients attach to a compatible existing host regardless of which client surface started it; only incompatible unmanaged owners are refused.
|
|
4
|
+
|
|
3
5
|
RPC mode enables headless operation of the coding agent via a JSON protocol over stdin/stdout. This is useful for embedding the agent in other applications, IDEs, or custom UIs.
|
|
4
6
|
|
|
5
7
|
**Note for Node.js/TypeScript users**: If you're building a Node.js application, consider using `AgentSession` directly from `@code-yeongyu/senpi` instead of spawning a subprocess. See [`src/core/agent-session.ts`](../src/core/agent-session.ts) for the API. For a subprocess-based TypeScript client, see [`src/modes/rpc/rpc-client.ts`](../src/modes/rpc/rpc-client.ts).
|
|
@@ -38,17 +40,67 @@ Multi-session mode lets one `senpi --mode rpc` process serve several independent
|
|
|
38
40
|
### Starting multi-session mode
|
|
39
41
|
|
|
40
42
|
```bash
|
|
43
|
+
# Shared JSONL over stdio (legacy multi-session host)
|
|
41
44
|
senpi --mode rpc --multi-session [options]
|
|
45
|
+
|
|
46
|
+
# One shared host over a Unix socket; each accepted connection has its own JSONL feed
|
|
47
|
+
senpi --mode rpc --listen unix:///tmp/senpi-rpc.sock [options]
|
|
48
|
+
senpi --mode rpc --listen /tmp/senpi-rpc.sock [options]
|
|
42
49
|
```
|
|
43
50
|
|
|
51
|
+
`--listen unix://` selects the default per-agent socket path. Unix abstract socket names may be supplied as
|
|
52
|
+
`unix://@name` where supported by the host platform. Socket mode accepts concurrent connections while retaining one
|
|
53
|
+
process-global session registry.
|
|
54
|
+
|
|
55
|
+
Socket event visibility is an all-sessions broadcast: every connected client receives lifecycle and agent events from
|
|
56
|
+
every open session, each tagged with its routing `sessionId`. Correlated responses and extension UI requests are sent
|
|
57
|
+
only to the connection that issued the command. This lets a non-owner observe a foreign turn without requiring a
|
|
58
|
+
separate subscription protocol.
|
|
59
|
+
|
|
44
60
|
Startup: `senpi --mode rpc --multi-session` → NO default session is constructed (no default `AgentSessionRuntime`, no default extension/watcher load). Classic `senpi --mode rpc` is byte-identical to today. Mode is fixed at process start; there is no runtime transition.
|
|
45
61
|
|
|
62
|
+
### Interactive sessions and shared-host opt-out
|
|
63
|
+
|
|
64
|
+
Interactive launches use the shared RPC host by default when a persisted session is available. A cold start takes approximately 1.3 seconds on the first launch; warm attachment to an existing compatible host is fast. To use the local runtime directly for a launch, set `SENPI_DISABLE_SHARED_HOST=1`. This uses the same local fallback runtime and does not change RPC socket behavior for other clients.
|
|
65
|
+
|
|
66
|
+
### Shared host lifecycle (cold start + idle exit)
|
|
67
|
+
|
|
68
|
+
The lifecycle supervisor is also available to bundled/rebranded runtimes through the hidden internal launch route `--internal-rpc-host-supervisor`. This route is wire-invisible and intended only for desktop launchers: it receives the public socket, ownership directory, and the runtime command/arguments to wrap, then runs the same `host-lifecycle.ts` implementation used by `ensureHost()`. Normal CLI modes do not use or advertise this route.
|
|
69
|
+
|
|
70
|
+
Hosts started through `ensureHost()` are wrapped by a lifecycle supervisor that owns the public socket and spawns the
|
|
71
|
+
real RPC host on a private internal hop. The policy lives in `<agentDir>/rpc-host-daemon/settings.json`:
|
|
72
|
+
|
|
73
|
+
```json
|
|
74
|
+
{ "socket": "…/rpc.sock", "capabilities": ["extension_events", "custom_unsupported"], "coldStart": "transient", "idleExitMs": 900000 }
|
|
75
|
+
```
|
|
76
|
+
|
|
77
|
+
- `coldStart` — `transient` (default): the host exists for the current login session and idle-exits. `persistent`:
|
|
78
|
+
no idle exit; the host stays until it is stopped or dies.
|
|
79
|
+
- `idleExitMs` — idle-exit window in milliseconds, default `900000` (15 minutes).
|
|
80
|
+
|
|
81
|
+
Environment overrides beat the file, and invalid values fall through to the next source: `SENPI_RPC_HOST_COLD_START`
|
|
82
|
+
(`transient`|`persistent`) and `SENPI_RPC_HOST_IDLE_EXIT_MS` (positive integer milliseconds).
|
|
83
|
+
|
|
84
|
+
The host exits only after the window elapses with NO attached client connections and NO active turns — continuously.
|
|
85
|
+
Any connection or agent turn resets the window, so a busy host never exits, and the exit itself is clean: the RPC host
|
|
86
|
+
receives SIGTERM first, flushes pending output, removes its socket, and the supervisor then removes `host.pid` and
|
|
87
|
+
`settings.json` (the stderr log stays for diagnostics). After an idle exit, the next `ensureHost()` transparently
|
|
88
|
+
starts a fresh host. `get_protocol_info` over the public socket behaves exactly as before; the supervisor is
|
|
89
|
+
wire-transparent.
|
|
90
|
+
|
|
91
|
+
The RPC host can never outlive its supervisor. It is spawned with an extra inherited pipe on fd 3 whose write end the
|
|
92
|
+
supervisor holds and never writes to; the kernel closes that end whenever the supervisor dies — including `SIGKILL`, an
|
|
93
|
+
OOM kill, or a crash, where no signal handler runs — so the host reads EOF, shuts down cleanly and removes its private
|
|
94
|
+
internal directory. The supervisor also exports `SENPI_RPC_HOST_WATCH_PPID` as a polling fallback. Both bindings are
|
|
95
|
+
set only by the supervisor: a host started any other way (plain `senpi --mode rpc --listen …`, embedders, hand-started
|
|
96
|
+
hosts) sees neither variable and is unaffected. A host whose supervisor is alive is never touched by this binding.
|
|
97
|
+
|
|
46
98
|
### D1 normative table (multi-session mode)
|
|
47
99
|
|
|
48
100
|
| Command | Params | Success data | Notes |
|
|
49
101
|
| --- | --- | --- | --- |
|
|
50
|
-
| `get_protocol_info` | - | `{ protocolVersion: 1, capabilities: [
|
|
51
|
-
| `open_session` | `sessionPath?`, `cwd?`, `provider?`, `modelId?`, `thinkingLevel?`, `permissionPreset?` (all optional; paths MUST be absolute) | `{ sessionId, state: RpcSessionState }` | `sessionPath` = today's `--session` semantics (open-if-exists else create persisting there, `session-manager.ts:926-940`); `provider`/`modelId` applied only on create (resume restores the session's model — mirrors `SenpiSessionRuntime.ts:198-200`); params form the immutable launch profile (D8). |
|
|
102
|
+
| `get_protocol_info` | - | `{ protocolVersion: 1, serverVersion: string, capabilities: string[], mode: "classic"\|"multi" }` | Answered in BOTH modes; side-effect-free; the capability probe. Multi-session hosts include `multi_session` plus the negotiated launch capabilities. |
|
|
103
|
+
| `open_session` | `sessionPath?`, `cwd?`, `provider?`, `modelId?`, `thinkingLevel?`, `permissionPreset?` (all optional; paths MUST be absolute) | `{ sessionId, state: RpcSessionState, attached?: true }` | `sessionPath` = today's `--session` semantics (open-if-exists else create persisting there, `session-manager.ts:926-940`); `provider`/`modelId` applied only on create (resume restores the session's model — mirrors `SenpiSessionRuntime.ts:198-200`); params form the immutable launch profile (D8). When the path is already held by a fully-open session, the open ATTACHES to it: same routing handle, `attached: true`, one more attachment counted; the runtime is torn down only when the last attachment closes. |
|
|
52
104
|
| `close_session` | `sessionId` | `{}` | Aborts active work, awaits agent idle + settled persistence, flushes queued events, detaches subscriptions; its response is the LAST record tagged with that handle — no events after (test-pinned). |
|
|
53
105
|
| `list_sessions` | - | `{ sessions: [{ sessionId, durableSessionId, sessionPath, cwd, name, status }] }` | Includes `opening`/`closing` entries with their status. |
|
|
54
106
|
| every existing command | + `sessionId` (REQUIRED in multi mode) | unchanged | Routed to that session. |
|
|
@@ -63,7 +115,7 @@ In the response `error` field, machine-matchable:
|
|
|
63
115
|
|
|
64
116
|
- `unknown_session`
|
|
65
117
|
- `session_closing`
|
|
66
|
-
- `session_path_in_use`
|
|
118
|
+
- `session_path_in_use` (path held by a session still `opening` or already `closing`; a fully-open session is attached instead)
|
|
67
119
|
- `missing_session_id` (session-scoped command without `sessionId` in multi mode)
|
|
68
120
|
- `multi_session_disabled` (`open_session` in classic mode)
|
|
69
121
|
- `invalid_path` (relative `sessionPath`/`cwd`)
|
|
@@ -79,7 +131,7 @@ Strict FIFO per session; one total stdout order; cross-session order unspecified
|
|
|
79
131
|
|
|
80
132
|
### Duplicate/idempotency
|
|
81
133
|
|
|
82
|
-
Duplicate `open_session` while a path reservation is held → `session_path_in_use`. `close_session` on unknown/already-closed → `unknown_session` error. Request `id`s are client-owned; the server echoes them without dedup.
|
|
134
|
+
Duplicate `open_session` while a path reservation is held by a fully-open session → ATTACH (`attached: true`, same handle); while held by an `opening`/`closing` entry → `session_path_in_use`. `close_session` releases one attachment; the runtime is disposed only when the last attachment closes. `close_session` on unknown/already-closed → `unknown_session` error. Request `id`s are client-owned; the server echoes them without dedup.
|
|
83
135
|
|
|
84
136
|
## Protocol Overview
|
|
85
137
|
|
|
@@ -145,9 +197,11 @@ sending/queueing. Bare inline dollar text remains literal.
|
|
|
145
197
|
|
|
146
198
|
Response:
|
|
147
199
|
```json
|
|
148
|
-
{"id": "req-1", "type": "response", "command": "prompt", "success": true}
|
|
200
|
+
{"id": "req-1", "type": "response", "command": "prompt", "success": true, "data": { "disposition": "started" }}
|
|
149
201
|
```
|
|
150
202
|
|
|
203
|
+
The prompt success response carries `data.disposition` (`"started"` | `"queued"` | `"handled"`), captured from the host session's own disposition callback so proxied clients can resolve optimistic-echo contracts exactly like the local path. Older hosts omit `data`; clients must then degrade to canonical-only rendering (treat the echo as rejected). The response is emitted before the user `message_start` event on the same connection, and disposition callbacks registered through the client run synchronously inside response-frame dispatch — never through the resolved promise's microtask.
|
|
204
|
+
|
|
151
205
|
`success: true` means the prompt was accepted, queued, or handled immediately. `success: false` means the prompt was rejected before acceptance. Failures after acceptance are reported through the normal event and message stream, not as a second `response` for the same request id.
|
|
152
206
|
|
|
153
207
|
The `images` field is optional. Each image uses `ImageContent` format: `{"type": "image", "data": "base64-encoded-data", "mimeType": "image/png"}`.
|
|
@@ -12,6 +12,50 @@
|
|
|
12
12
|
|
|
13
13
|
### Removed
|
|
14
14
|
|
|
15
|
+
## [2026.8.28] - 2026-08-28
|
|
16
|
+
|
|
17
|
+
### Breaking Changes
|
|
18
|
+
|
|
19
|
+
### Added
|
|
20
|
+
|
|
21
|
+
### Changed
|
|
22
|
+
|
|
23
|
+
### Fixed
|
|
24
|
+
|
|
25
|
+
- JavaScript and Python eval kernels resolve worker and prelude assets from the executable sidecar in Bun-compiled distributions instead of passing unusable `$bunfs` paths to `Worker` and `python3`.
|
|
26
|
+
|
|
27
|
+
### Removed
|
|
28
|
+
|
|
29
|
+
## [2026.8.27] - 2026-08-27
|
|
30
|
+
|
|
31
|
+
### Breaking Changes
|
|
32
|
+
|
|
33
|
+
### Added
|
|
34
|
+
|
|
35
|
+
### Changed
|
|
36
|
+
|
|
37
|
+
- Eval tool description examples are now a JS-first mixed set: set up once in JavaScript, fan out batched `Promise.all` session-tool calls in the next cell, then hop to Python when the JS kernel is busy with a detached cell. The detach paragraph now states in the same sentence that another language can continue.
|
|
38
|
+
|
|
39
|
+
### Fixed
|
|
40
|
+
|
|
41
|
+
- Detached-eval same-language busy errors now name each idle enabled kernel and tell the agent to continue the step there (`continue this step in an idle kernel: js`), instead of only pointing at peek and the output tail. A busy Python kernel no longer reads as "eval is unavailable", which previously sent agents to `bash`+`python3` while JavaScript (or another idle kernel) was free. Single-language sessions and fully-busy sessions omit the idle-kernel claim.
|
|
42
|
+
- JavaScript eval cells now persist only top-level declarations, including destructuring bindings and uninitialized variables, without rewriting declaration-shaped text inside literals or comments.
|
|
43
|
+
- Eval completion and detached-cell handling retain explicit lifecycle observability: nested tool counts, wall/kernel timing, detach state, `peek`, `stop`, hard limits, and crash recovery remain bounded and machine-readable for hosts and telemetry consumers.
|
|
44
|
+
|
|
45
|
+
### Removed
|
|
46
|
+
|
|
47
|
+
## [2026.8.26-2] - 2026-08-26
|
|
48
|
+
|
|
49
|
+
### Breaking Changes
|
|
50
|
+
|
|
51
|
+
### Added
|
|
52
|
+
|
|
53
|
+
### Changed
|
|
54
|
+
|
|
55
|
+
### Fixed
|
|
56
|
+
|
|
57
|
+
### Removed
|
|
58
|
+
|
|
15
59
|
## [2026.8.26] - 2026-08-26
|
|
16
60
|
|
|
17
61
|
### Breaking Changes
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@code-yeongyu/senpi-codemode",
|
|
3
|
-
"version": "2026.8.
|
|
3
|
+
"version": "2026.8.28",
|
|
4
4
|
"private": true,
|
|
5
5
|
"description": "Source-only senpi extension package for codemode evaluation tools",
|
|
6
6
|
"type": "module",
|
|
@@ -31,14 +31,14 @@
|
|
|
31
31
|
},
|
|
32
32
|
"dependencies": {
|
|
33
33
|
"@babel/parser": "8.0.4",
|
|
34
|
-
"@earendil-works/pi-ai": "npm:@code-yeongyu/senpi-ai@2026.8.
|
|
34
|
+
"@earendil-works/pi-ai": "npm:@code-yeongyu/senpi-ai@2026.8.28",
|
|
35
35
|
"typebox": "1.3.18"
|
|
36
36
|
},
|
|
37
37
|
"peerDependencies": {
|
|
38
|
-
"@code-yeongyu/senpi": "2026.8.
|
|
38
|
+
"@code-yeongyu/senpi": "2026.8.28"
|
|
39
39
|
},
|
|
40
40
|
"devDependencies": {
|
|
41
|
-
"@code-yeongyu/senpi": "2026.8.
|
|
41
|
+
"@code-yeongyu/senpi": "2026.8.28"
|
|
42
42
|
},
|
|
43
43
|
"keywords": [
|
|
44
44
|
"senpi",
|
|
@@ -1,5 +1,8 @@
|
|
|
1
|
+
import { dirname, join } from "node:path";
|
|
2
|
+
import { fileURLToPath, pathToFileURL } from "node:url";
|
|
1
3
|
import type { HostToKernelMessage, KernelToHostMessage } from "../../bridge/protocol.ts";
|
|
2
4
|
import type { KernelInterruptHandle } from "../../tool/types.ts";
|
|
5
|
+
import { type CodemodeRuntimeAssetEnvironment, resolveCodemodeRuntimeAsset } from "../shared/runtime-asset.ts";
|
|
3
6
|
import { createInlineWorker, type WorkerLike } from "./inline-worker.ts";
|
|
4
7
|
import {
|
|
5
8
|
assertJavaScriptKernelOpen,
|
|
@@ -16,6 +19,15 @@ import { bridgeError, spawnNodeWorker, WorkerStartupCancelledError, waitForReady
|
|
|
16
19
|
export { JavaScriptKernelClosedError, type JavaScriptKernelMode, type JavaScriptRunInput } from "./kernel-contract.ts";
|
|
17
20
|
export type { JavaScriptKernelOptions } from "./local-module-loader.ts";
|
|
18
21
|
|
|
22
|
+
export interface JavaScriptWorkerEntryUrlOptions extends CodemodeRuntimeAssetEnvironment {
|
|
23
|
+
readonly localPath?: string;
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
export function resolveJsWorkerEntryUrl(options: JavaScriptWorkerEntryUrlOptions = {}): URL {
|
|
27
|
+
const localPath = options.localPath ?? join(dirname(fileURLToPath(import.meta.url)), "worker-entry.js");
|
|
28
|
+
return pathToFileURL(resolveCodemodeRuntimeAsset(localPath, join("kernels", "js", "worker-entry.js"), options));
|
|
29
|
+
}
|
|
30
|
+
|
|
19
31
|
export class JavaScriptKernel {
|
|
20
32
|
readonly #options: JavaScriptKernelOptions;
|
|
21
33
|
readonly #moduleLoader: LocalModuleLoader;
|
|
@@ -161,7 +173,7 @@ export class JavaScriptKernel {
|
|
|
161
173
|
|
|
162
174
|
#spawnWorker(): WorkerLike {
|
|
163
175
|
try {
|
|
164
|
-
const url = this.#options.workerEntryUrl ??
|
|
176
|
+
const url = this.#options.workerEntryUrl ?? resolveJsWorkerEntryUrl();
|
|
165
177
|
return spawnNodeWorker(url, this.#options.cwd, this.#options.parallelPoolWidth);
|
|
166
178
|
} catch (error) {
|
|
167
179
|
if (!(error instanceof Error)) throw error;
|
|
@@ -1,7 +1,21 @@
|
|
|
1
|
+
import { dirname, join } from "node:path";
|
|
2
|
+
import { fileURLToPath, pathToFileURL } from "node:url";
|
|
1
3
|
import type { HostToKernelMessage, KernelToHostMessage } from "../../bridge/protocol.ts";
|
|
4
|
+
import { type CodemodeRuntimeAssetEnvironment, resolveCodemodeRuntimeAsset } from "../shared/runtime-asset.ts";
|
|
2
5
|
import type { JavaScriptKernelMode } from "./kernel-contract.ts";
|
|
3
6
|
import { spawnNodeWorker } from "./worker-host.ts";
|
|
4
7
|
|
|
8
|
+
export interface JavaScriptInlineWorkerEntryUrlOptions extends CodemodeRuntimeAssetEnvironment {
|
|
9
|
+
readonly localPath?: string;
|
|
10
|
+
}
|
|
11
|
+
|
|
12
|
+
export function resolveInlineWorkerEntryUrl(options: JavaScriptInlineWorkerEntryUrlOptions = {}): URL {
|
|
13
|
+
const localPath = options.localPath ?? join(dirname(fileURLToPath(import.meta.url)), "inline-worker-entry.js");
|
|
14
|
+
return pathToFileURL(
|
|
15
|
+
resolveCodemodeRuntimeAsset(localPath, join("kernels", "js", "inline-worker-entry.js"), options),
|
|
16
|
+
);
|
|
17
|
+
}
|
|
18
|
+
|
|
5
19
|
export interface WorkerLike {
|
|
6
20
|
readonly mode: JavaScriptKernelMode;
|
|
7
21
|
postMessage(message: HostToKernelMessage): void;
|
|
@@ -11,5 +25,5 @@ export interface WorkerLike {
|
|
|
11
25
|
}
|
|
12
26
|
|
|
13
27
|
export function createInlineWorker(cwd: string, parallelPoolWidth: number): WorkerLike {
|
|
14
|
-
return spawnNodeWorker(
|
|
28
|
+
return spawnNodeWorker(resolveInlineWorkerEntryUrl(), cwd, parallelPoolWidth, "inline");
|
|
15
29
|
}
|