@earendil-works/pi-coding-agent 0.84.4 → 0.85.0
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 +57 -5
- package/README.md +3 -1
- package/dist/bun/cli.d.ts.map +1 -1
- package/dist/bun/cli.js +10 -7
- package/dist/bun/cli.js.map +1 -1
- package/dist/bun/runtime-setup.d.ts +2 -0
- package/dist/bun/runtime-setup.d.ts.map +1 -0
- package/dist/bun/runtime-setup.js +9 -0
- package/dist/bun/runtime-setup.js.map +1 -0
- package/dist/bundle/chunks/anthropic-messages-QAQLEQIY.js +6 -0
- package/dist/bundle/chunks/{azure-openai-responses-5ENVCQ7U.js → azure-openai-responses-Q7MJBRE6.js} +1 -1
- package/dist/bundle/chunks/bedrock-converse-stream.js +1 -1
- package/dist/bundle/chunks/chunk-7ZC4UXSL.js +2 -0
- package/dist/bundle/chunks/chunk-EQ6DR6XK.js +5 -0
- package/dist/bundle/chunks/{chunk-MNAIPA3J.js → chunk-IDDQWTHI.js} +1 -1
- package/dist/bundle/chunks/chunk-LZKJTKCT.js +2 -0
- package/dist/bundle/chunks/{chunk-NBBFIJUL.js → chunk-OUXLLA64.js} +2 -2
- package/dist/bundle/chunks/chunk-R7UUJ4C2.js +42 -0
- package/dist/bundle/chunks/chunk-USJZMAIR.js +2 -0
- package/dist/bundle/chunks/chunk-WBE4J4AU.js +9 -0
- package/dist/bundle/chunks/chunk-WZB2R5YO.js +1558 -0
- package/dist/bundle/chunks/github-copilot.js +1 -1
- package/dist/bundle/chunks/{google-generative-ai-YMUPJBKR.js → google-generative-ai-BSMTYDAM.js} +1 -1
- package/dist/bundle/chunks/{google-vertex-MPWMV4OF.js → google-vertex-5C3HASOJ.js} +1 -1
- package/dist/bundle/chunks/image-resize-worker.js +1 -1
- package/dist/bundle/chunks/{mistral-conversations-Q3AWZJAZ.js → mistral-conversations-4M6BZBX6.js} +1 -1
- package/dist/bundle/chunks/node-EM5FIVUN.js +16 -0
- package/dist/bundle/chunks/node-TWVVAGPO.js +2 -0
- package/dist/bundle/chunks/openai-codex-responses-YJOO52RW.js +10 -0
- package/dist/bundle/chunks/{openai-completions-ERMU2SS7.js → openai-completions-QOT3QX7D.js} +3 -3
- package/dist/bundle/chunks/openai-responses-U7LNPQN4.js +2 -0
- package/dist/bundle/chunks/{pi-messages-TPFH44NP.js → pi-messages-EGZIFQGG.js} +1 -1
- package/dist/bundle/cli.js +1 -1
- package/dist/bundle/client.js +1 -1
- package/dist/bundle/coordinator.js +3 -0
- package/dist/bundle/index.js +1 -1
- package/dist/bundle/rpc-entry.js +1 -1
- package/dist/cli/args.d.ts.map +1 -1
- package/dist/cli/args.js +2 -0
- package/dist/cli/args.js.map +1 -1
- package/dist/cli/config-selector.d.ts.map +1 -1
- package/dist/cli/config-selector.js +2 -1
- package/dist/cli/config-selector.js.map +1 -1
- package/dist/cli/experimental/cli.d.ts +7 -3
- package/dist/cli/experimental/cli.d.ts.map +1 -1
- package/dist/cli/experimental/cli.js +6 -2
- package/dist/cli/experimental/cli.js.map +1 -1
- package/dist/cli/experimental/command-options.d.ts +23 -12
- package/dist/cli/experimental/command-options.d.ts.map +1 -1
- package/dist/cli/experimental/command-options.js +69 -20
- package/dist/cli/experimental/command-options.js.map +1 -1
- package/dist/cli/experimental/command.d.ts +10 -3
- package/dist/cli/experimental/command.d.ts.map +1 -1
- package/dist/cli/experimental/command.js +28 -14
- package/dist/cli/experimental/command.js.map +1 -1
- package/dist/cli/experimental/commands/client.d.ts +8 -2
- package/dist/cli/experimental/commands/client.d.ts.map +1 -1
- package/dist/cli/experimental/commands/client.js +43 -5
- package/dist/cli/experimental/commands/client.js.map +1 -1
- package/dist/cli/experimental/commands/server.d.ts +7 -3
- package/dist/cli/experimental/commands/server.d.ts.map +1 -1
- package/dist/cli/experimental/commands/server.js +27 -8
- package/dist/cli/experimental/commands/server.js.map +1 -1
- package/dist/cli.d.ts.map +1 -1
- package/dist/cli.js +23 -14
- package/dist/cli.js.map +1 -1
- package/dist/client/index.d.ts +1 -2
- package/dist/client/index.d.ts.map +1 -1
- package/dist/client/index.js +1 -2
- package/dist/client/index.js.map +1 -1
- package/dist/config.d.ts +1 -0
- package/dist/config.d.ts.map +1 -1
- package/dist/config.js +1 -0
- package/dist/config.js.map +1 -1
- package/dist/core/agent-session-runtime.d.ts.map +1 -1
- package/dist/core/agent-session-runtime.js +15 -7
- package/dist/core/agent-session-runtime.js.map +1 -1
- package/dist/core/agent-session.d.ts +2 -6
- package/dist/core/agent-session.d.ts.map +1 -1
- package/dist/core/agent-session.js +14 -6
- package/dist/core/agent-session.js.map +1 -1
- package/dist/core/compaction/branch-summarization.d.ts +1 -1
- package/dist/core/compaction/branch-summarization.d.ts.map +1 -1
- package/dist/core/compaction/branch-summarization.js +2 -1
- package/dist/core/compaction/branch-summarization.js.map +1 -1
- package/dist/core/extensions/loader.d.ts.map +1 -1
- package/dist/core/extensions/loader.js +1 -2
- package/dist/core/extensions/loader.js.map +1 -1
- package/dist/core/extensions/types.d.ts +8 -2
- package/dist/core/extensions/types.d.ts.map +1 -1
- package/dist/core/extensions/types.js.map +1 -1
- package/dist/core/http-dispatcher.d.ts.map +1 -1
- package/dist/core/http-dispatcher.js +2 -0
- package/dist/core/http-dispatcher.js.map +1 -1
- package/dist/core/messages.d.ts +1 -1
- package/dist/core/messages.d.ts.map +1 -1
- package/dist/core/messages.js.map +1 -1
- package/dist/core/model-config.d.ts +15 -0
- package/dist/core/model-config.d.ts.map +1 -1
- package/dist/core/model-config.js +3 -0
- package/dist/core/model-config.js.map +1 -1
- package/dist/core/model-runtime.d.ts +1 -0
- package/dist/core/model-runtime.d.ts.map +1 -1
- package/dist/core/model-runtime.js +5 -2
- package/dist/core/model-runtime.js.map +1 -1
- package/dist/core/provider-composer.d.ts.map +1 -1
- package/dist/core/provider-composer.js +8 -2
- package/dist/core/provider-composer.js.map +1 -1
- package/dist/core/session-manager.d.ts +3 -2
- package/dist/core/session-manager.d.ts.map +1 -1
- package/dist/core/session-manager.js +40 -13
- package/dist/core/session-manager.js.map +1 -1
- package/dist/core/settings-manager.d.ts.map +1 -1
- package/dist/core/settings-manager.js.map +1 -1
- package/dist/core/skills.d.ts +1 -1
- package/dist/core/skills.d.ts.map +1 -1
- package/dist/core/skills.js +4 -2
- package/dist/core/skills.js.map +1 -1
- package/dist/core/system-prompt.d.ts.map +1 -1
- package/dist/core/system-prompt.js +8 -9
- package/dist/core/system-prompt.js.map +1 -1
- package/dist/core/tools/bash.d.ts.map +1 -1
- package/dist/core/tools/bash.js +3 -117
- package/dist/core/tools/bash.js.map +1 -1
- package/dist/core/tools/edit.d.ts +1 -12
- package/dist/core/tools/edit.d.ts.map +1 -1
- package/dist/core/tools/edit.js +5 -163
- package/dist/core/tools/edit.js.map +1 -1
- package/dist/core/tools/find.d.ts.map +1 -1
- package/dist/core/tools/find.js +4 -55
- package/dist/core/tools/find.js.map +1 -1
- package/dist/core/tools/grep.d.ts.map +1 -1
- package/dist/core/tools/grep.js +4 -60
- package/dist/core/tools/grep.js.map +1 -1
- package/dist/core/tools/ls.d.ts.map +1 -1
- package/dist/core/tools/ls.js +4 -49
- package/dist/core/tools/ls.js.map +1 -1
- package/dist/core/tools/read.d.ts.map +1 -1
- package/dist/core/tools/read.js +4 -120
- package/dist/core/tools/read.js.map +1 -1
- package/dist/core/tools/renderers/bash.d.ts +12 -0
- package/dist/core/tools/renderers/bash.d.ts.map +1 -0
- package/dist/core/tools/renderers/bash.js +130 -0
- package/dist/core/tools/renderers/bash.js.map +1 -0
- package/dist/core/tools/renderers/edit.d.ts +23 -0
- package/dist/core/tools/renderers/edit.d.ts.map +1 -0
- package/dist/core/tools/renderers/edit.js +167 -0
- package/dist/core/tools/renderers/edit.js.map +1 -0
- package/dist/core/tools/renderers/find.d.ts +10 -0
- package/dist/core/tools/renderers/find.d.ts.map +1 -0
- package/dist/core/tools/renderers/find.js +64 -0
- package/dist/core/tools/renderers/find.js.map +1 -0
- package/dist/core/tools/renderers/grep.d.ts +10 -0
- package/dist/core/tools/renderers/grep.d.ts.map +1 -0
- package/dist/core/tools/renderers/grep.js +69 -0
- package/dist/core/tools/renderers/grep.js.map +1 -0
- package/dist/core/tools/renderers/index.d.ts +28 -0
- package/dist/core/tools/renderers/index.d.ts.map +1 -0
- package/dist/core/tools/renderers/index.js +47 -0
- package/dist/core/tools/renderers/index.js.map +1 -0
- package/dist/core/tools/renderers/ls.d.ts +10 -0
- package/dist/core/tools/renderers/ls.d.ts.map +1 -0
- package/dist/core/tools/renderers/ls.js +58 -0
- package/dist/core/tools/renderers/ls.js.map +1 -0
- package/dist/core/tools/renderers/read.d.ts +11 -0
- package/dist/core/tools/renderers/read.d.ts.map +1 -0
- package/dist/core/tools/renderers/read.js +129 -0
- package/dist/core/tools/renderers/read.js.map +1 -0
- package/dist/core/tools/renderers/write.d.ts +10 -0
- package/dist/core/tools/renderers/write.d.ts.map +1 -0
- package/dist/core/tools/renderers/write.js +153 -0
- package/dist/core/tools/renderers/write.js.map +1 -0
- package/dist/core/tools/write.d.ts.map +1 -1
- package/dist/core/tools/write.js +5 -146
- package/dist/core/tools/write.js.map +1 -1
- package/dist/experimental/client-runtime.d.ts +43 -0
- package/dist/experimental/client-runtime.d.ts.map +1 -0
- package/dist/experimental/client-runtime.js +183 -0
- package/dist/experimental/client-runtime.js.map +1 -0
- package/dist/experimental/client-tui-chat.d.ts +14 -0
- package/dist/experimental/client-tui-chat.d.ts.map +1 -0
- package/dist/experimental/client-tui-chat.js +171 -0
- package/dist/experimental/client-tui-chat.js.map +1 -0
- package/dist/experimental/client-tui.d.ts +37 -0
- package/dist/experimental/client-tui.d.ts.map +1 -0
- package/dist/experimental/client-tui.js +702 -0
- package/dist/experimental/client-tui.js.map +1 -0
- package/dist/experimental/client.d.ts +25 -0
- package/dist/experimental/client.d.ts.map +1 -0
- package/dist/experimental/client.js +99 -0
- package/dist/experimental/client.js.map +1 -0
- package/dist/experimental/coordinator-entry.d.ts +3 -0
- package/dist/experimental/coordinator-entry.d.ts.map +1 -0
- package/dist/experimental/coordinator-entry.js +11 -0
- package/dist/experimental/coordinator-entry.js.map +1 -0
- package/dist/experimental/coordinator.d.ts +38 -0
- package/dist/experimental/coordinator.d.ts.map +1 -0
- package/dist/experimental/coordinator.js +570 -0
- package/dist/experimental/coordinator.js.map +1 -0
- package/dist/experimental/mini/main.d.ts +7 -0
- package/dist/experimental/mini/main.d.ts.map +1 -0
- package/dist/experimental/mini/main.js +21 -0
- package/dist/experimental/mini/main.js.map +1 -0
- package/dist/experimental/mini/server/entry.d.ts +3 -0
- package/dist/experimental/mini/server/entry.d.ts.map +1 -0
- package/dist/experimental/mini/server/entry.js +11 -0
- package/dist/experimental/mini/server/entry.js.map +1 -0
- package/dist/experimental/mini/server/run.d.ts +13 -0
- package/dist/experimental/mini/server/run.d.ts.map +1 -0
- package/dist/experimental/mini/server/run.js +150 -0
- package/dist/experimental/mini/server/run.js.map +1 -0
- package/dist/experimental/mini/shared/protocol.d.ts +127 -0
- package/dist/experimental/mini/shared/protocol.d.ts.map +1 -0
- package/dist/experimental/mini/shared/protocol.js +13 -0
- package/dist/experimental/mini/shared/protocol.js.map +1 -0
- package/dist/experimental/mini/shared/rpc.d.ts +49 -0
- package/dist/experimental/mini/shared/rpc.d.ts.map +1 -0
- package/dist/experimental/mini/shared/rpc.js +171 -0
- package/dist/experimental/mini/shared/rpc.js.map +1 -0
- package/dist/experimental/mini/shared/transport.d.ts +35 -0
- package/dist/experimental/mini/shared/transport.d.ts.map +1 -0
- package/dist/experimental/mini/shared/transport.js +94 -0
- package/dist/experimental/mini/shared/transport.js.map +1 -0
- package/dist/experimental/mini/tui/run.d.ts +9 -0
- package/dist/experimental/mini/tui/run.d.ts.map +1 -0
- package/dist/experimental/mini/tui/run.js +62 -0
- package/dist/experimental/mini/tui/run.js.map +1 -0
- package/dist/experimental/mini/tui/session.d.ts +26 -0
- package/dist/experimental/mini/tui/session.d.ts.map +1 -0
- package/dist/experimental/mini/tui/session.js +94 -0
- package/dist/experimental/mini/tui/session.js.map +1 -0
- package/dist/experimental/mini/tui/view.d.ts +10 -0
- package/dist/experimental/mini/tui/view.d.ts.map +1 -0
- package/dist/experimental/mini/tui/view.js +459 -0
- package/dist/experimental/mini/tui/view.js.map +1 -0
- package/dist/experimental/mini/worker/entry.d.ts +3 -0
- package/dist/experimental/mini/worker/entry.d.ts.map +1 -0
- package/dist/experimental/mini/worker/entry.js +10 -0
- package/dist/experimental/mini/worker/entry.js.map +1 -0
- package/dist/experimental/mini/worker/lane-service.d.ts +44 -0
- package/dist/experimental/mini/worker/lane-service.d.ts.map +1 -0
- package/dist/experimental/mini/worker/lane-service.js +97 -0
- package/dist/experimental/mini/worker/lane-service.js.map +1 -0
- package/dist/experimental/mini/worker/models-service.d.ts +19 -0
- package/dist/experimental/mini/worker/models-service.d.ts.map +1 -0
- package/dist/experimental/mini/worker/models-service.js +136 -0
- package/dist/experimental/mini/worker/models-service.js.map +1 -0
- package/dist/experimental/mini/worker/run.d.ts +14 -0
- package/dist/experimental/mini/worker/run.d.ts.map +1 -0
- package/dist/experimental/mini/worker/run.js +88 -0
- package/dist/experimental/mini/worker/run.js.map +1 -0
- package/dist/experimental/plugin.d.ts +4 -0
- package/dist/experimental/plugin.d.ts.map +1 -0
- package/dist/experimental/plugin.js +4 -0
- package/dist/experimental/plugin.js.map +1 -0
- package/dist/experimental/plugins/bundled.d.ts +7 -0
- package/dist/experimental/plugins/bundled.d.ts.map +1 -0
- package/dist/experimental/plugins/bundled.js +48 -0
- package/dist/experimental/plugins/bundled.js.map +1 -0
- package/dist/experimental/plugins/package.d.ts +18 -0
- package/dist/experimental/plugins/package.d.ts.map +1 -0
- package/dist/experimental/plugins/package.js +113 -0
- package/dist/experimental/plugins/package.js.map +1 -0
- package/dist/experimental/process.d.ts +20 -0
- package/dist/experimental/process.d.ts.map +1 -0
- package/dist/experimental/process.js +85 -0
- package/dist/experimental/process.js.map +1 -0
- package/dist/experimental/radius-auth.d.ts +17 -0
- package/dist/experimental/radius-auth.d.ts.map +1 -0
- package/dist/experimental/radius-auth.js +52 -0
- package/dist/experimental/radius-auth.js.map +1 -0
- package/dist/experimental/radius-relay.d.ts +90 -0
- package/dist/experimental/radius-relay.d.ts.map +1 -0
- package/dist/experimental/radius-relay.js +582 -0
- package/dist/experimental/radius-relay.js.map +1 -0
- package/dist/experimental/server.d.ts +75 -0
- package/dist/experimental/server.d.ts.map +1 -0
- package/dist/experimental/server.js +682 -0
- package/dist/experimental/server.js.map +1 -0
- package/dist/experimental/services/agent-controller-provider.d.ts +4 -0
- package/dist/experimental/services/agent-controller-provider.d.ts.map +1 -0
- package/dist/experimental/services/agent-controller-provider.js +84 -0
- package/dist/experimental/services/agent-controller-provider.js.map +1 -0
- package/dist/experimental/services/agent-controller.d.ts +57 -0
- package/dist/experimental/services/agent-controller.d.ts.map +1 -0
- package/dist/experimental/services/agent-controller.js +3 -0
- package/dist/experimental/services/agent-controller.js.map +1 -0
- package/dist/experimental/services/connection.d.ts +40 -0
- package/dist/experimental/services/connection.d.ts.map +1 -0
- package/dist/experimental/services/connection.js +300 -0
- package/dist/experimental/services/connection.js.map +1 -0
- package/dist/experimental/services/models-provider.d.ts +16 -0
- package/dist/experimental/services/models-provider.d.ts.map +1 -0
- package/dist/experimental/services/models-provider.js +116 -0
- package/dist/experimental/services/models-provider.js.map +1 -0
- package/dist/experimental/services/models.d.ts +36 -0
- package/dist/experimental/services/models.d.ts.map +1 -0
- package/dist/experimental/services/models.js +3 -0
- package/dist/experimental/services/models.js.map +1 -0
- package/dist/experimental/services/plugins.d.ts +16 -0
- package/dist/experimental/services/plugins.d.ts.map +1 -0
- package/dist/experimental/services/plugins.js +4 -0
- package/dist/experimental/services/plugins.js.map +1 -0
- package/dist/experimental/services/presentation-ui.d.ts +13 -0
- package/dist/experimental/services/presentation-ui.d.ts.map +1 -0
- package/dist/experimental/services/presentation-ui.js +3 -0
- package/dist/experimental/services/presentation-ui.js.map +1 -0
- package/dist/experimental/services/server.d.ts +19 -0
- package/dist/experimental/services/server.d.ts.map +1 -0
- package/dist/experimental/services/server.js +104 -0
- package/dist/experimental/services/server.js.map +1 -0
- package/dist/experimental/services/sessions.d.ts +28 -0
- package/dist/experimental/services/sessions.d.ts.map +1 -0
- package/dist/experimental/services/sessions.js +4 -0
- package/dist/experimental/services/sessions.js.map +1 -0
- package/dist/experimental/services/slash-commands-provider.d.ts +14 -0
- package/dist/experimental/services/slash-commands-provider.d.ts.map +1 -0
- package/dist/experimental/services/slash-commands-provider.js +200 -0
- package/dist/experimental/services/slash-commands-provider.js.map +1 -0
- package/dist/experimental/services/slash-commands.d.ts +24 -0
- package/dist/experimental/services/slash-commands.d.ts.map +1 -0
- package/dist/experimental/services/slash-commands.js +3 -0
- package/dist/experimental/services/slash-commands.js.map +1 -0
- package/dist/experimental/services/transcript-provider.d.ts +12 -0
- package/dist/experimental/services/transcript-provider.d.ts.map +1 -0
- package/dist/experimental/services/transcript-provider.js +109 -0
- package/dist/experimental/services/transcript-provider.js.map +1 -0
- package/dist/experimental/services/transcript.d.ts +13 -0
- package/dist/experimental/services/transcript.d.ts.map +1 -0
- package/dist/experimental/services/transcript.js +3 -0
- package/dist/experimental/services/transcript.js.map +1 -0
- package/dist/experimental/services/worker.d.ts +28 -0
- package/dist/experimental/services/worker.d.ts.map +1 -0
- package/dist/experimental/services/worker.js +107 -0
- package/dist/experimental/services/worker.js.map +1 -0
- package/dist/experimental/session-worker-manager.d.ts +24 -0
- package/dist/experimental/session-worker-manager.d.ts.map +1 -0
- package/dist/experimental/session-worker-manager.js +691 -0
- package/dist/experimental/session-worker-manager.js.map +1 -0
- package/dist/experimental/session-worker.d.ts +188 -0
- package/dist/experimental/session-worker.d.ts.map +1 -0
- package/dist/experimental/session-worker.js +785 -0
- package/dist/experimental/session-worker.js.map +1 -0
- package/dist/experimental/source-resolver.d.ts +2 -0
- package/dist/experimental/source-resolver.d.ts.map +1 -0
- package/dist/experimental/source-resolver.js +71 -0
- package/dist/experimental/source-resolver.js.map +1 -0
- package/dist/index.d.ts +1 -1
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js.map +1 -1
- package/dist/main.d.ts.map +1 -1
- package/dist/main.js +126 -1
- package/dist/main.js.map +1 -1
- package/dist/modes/interactive/chat-viewport.d.ts +20 -0
- package/dist/modes/interactive/chat-viewport.d.ts.map +1 -0
- package/dist/modes/interactive/chat-viewport.js +28 -0
- package/dist/modes/interactive/chat-viewport.js.map +1 -0
- package/dist/modes/interactive/components/assistant-message.d.ts +1 -0
- package/dist/modes/interactive/components/assistant-message.d.ts.map +1 -1
- package/dist/modes/interactive/components/assistant-message.js +18 -10
- package/dist/modes/interactive/components/assistant-message.js.map +1 -1
- package/dist/modes/interactive/components/custom-editor.d.ts +10 -1
- package/dist/modes/interactive/components/custom-editor.d.ts.map +1 -1
- package/dist/modes/interactive/components/custom-editor.js +39 -1
- package/dist/modes/interactive/components/custom-editor.js.map +1 -1
- package/dist/modes/interactive/components/index.d.ts +1 -1
- package/dist/modes/interactive/components/index.d.ts.map +1 -1
- package/dist/modes/interactive/components/index.js.map +1 -1
- package/dist/modes/interactive/components/model-selector.d.ts.map +1 -1
- package/dist/modes/interactive/components/model-selector.js +5 -14
- package/dist/modes/interactive/components/model-selector.js.map +1 -1
- package/dist/modes/interactive/components/scoped-models-selector.d.ts.map +1 -1
- package/dist/modes/interactive/components/scoped-models-selector.js +13 -15
- package/dist/modes/interactive/components/scoped-models-selector.js.map +1 -1
- package/dist/modes/interactive/components/settings-selector.d.ts.map +1 -1
- package/dist/modes/interactive/components/settings-selector.js +14 -10
- package/dist/modes/interactive/components/settings-selector.js.map +1 -1
- package/dist/modes/interactive/components/status-indicator.d.ts +3 -1
- package/dist/modes/interactive/components/status-indicator.d.ts.map +1 -1
- package/dist/modes/interactive/components/status-indicator.js +10 -3
- package/dist/modes/interactive/components/status-indicator.js.map +1 -1
- package/dist/modes/interactive/components/thinking-selector.d.ts.map +1 -1
- package/dist/modes/interactive/components/thinking-selector.js +2 -2
- package/dist/modes/interactive/components/thinking-selector.js.map +1 -1
- package/dist/modes/interactive/components/tool-execution.d.ts +21 -4
- package/dist/modes/interactive/components/tool-execution.d.ts.map +1 -1
- package/dist/modes/interactive/components/tool-execution.js +35 -33
- package/dist/modes/interactive/components/tool-execution.js.map +1 -1
- package/dist/modes/interactive/components/trust-selector.d.ts.map +1 -1
- package/dist/modes/interactive/components/trust-selector.js +2 -2
- package/dist/modes/interactive/components/trust-selector.js.map +1 -1
- package/dist/modes/interactive/interactive-mode.d.ts +8 -15
- package/dist/modes/interactive/interactive-mode.d.ts.map +1 -1
- package/dist/modes/interactive/interactive-mode.js +95 -80
- package/dist/modes/interactive/interactive-mode.js.map +1 -1
- package/dist/modes/interactive/session-share.d.ts.map +1 -1
- package/dist/modes/interactive/session-share.js +8 -12
- package/dist/modes/interactive/session-share.js.map +1 -1
- package/dist/modes/interactive/theme/dark.json +2 -1
- package/dist/modes/interactive/theme/light.json +2 -1
- package/dist/modes/interactive/theme/theme-controller.d.ts +1 -0
- package/dist/modes/interactive/theme/theme-controller.d.ts.map +1 -1
- package/dist/modes/interactive/theme/theme-controller.js +5 -0
- package/dist/modes/interactive/theme/theme-controller.js.map +1 -1
- package/dist/modes/interactive/theme/theme-json.d.ts +84 -0
- package/dist/modes/interactive/theme/theme-json.d.ts.map +1 -0
- package/dist/modes/interactive/theme/theme-json.js +130 -0
- package/dist/modes/interactive/theme/theme-json.js.map +1 -0
- package/dist/modes/interactive/theme/theme-schema.json +6 -2
- package/dist/modes/interactive/theme/theme.d.ts +14 -5
- package/dist/modes/interactive/theme/theme.d.ts.map +1 -1
- package/dist/modes/interactive/theme/theme.js +18 -119
- package/dist/modes/interactive/theme/theme.js.map +1 -1
- package/dist/modes/interactive/tui-renderer.d.ts +21 -0
- package/dist/modes/interactive/tui-renderer.d.ts.map +1 -0
- package/dist/modes/interactive/tui-renderer.js +66 -0
- package/dist/modes/interactive/tui-renderer.js.map +1 -0
- package/dist/utils/exif-orientation.d.ts.map +1 -1
- package/dist/utils/exif-orientation.js +2 -3
- package/dist/utils/exif-orientation.js.map +1 -1
- package/dist/utils/tools-manager.d.ts +1 -0
- package/dist/utils/tools-manager.d.ts.map +1 -1
- package/dist/utils/tools-manager.js +41 -19
- package/dist/utils/tools-manager.js.map +1 -1
- package/docs/environment-variables.md +2 -0
- package/docs/extensions.md +5 -2
- package/docs/keybindings.md +3 -1
- package/docs/models.md +3 -0
- package/docs/rpc.md +1 -1
- package/docs/sdk.md +5 -0
- package/docs/settings.md +5 -5
- package/docs/skills.md +1 -1
- package/docs/terminal-setup.md +15 -0
- package/docs/themes.md +8 -6
- package/docs/tui.md +20 -1
- package/examples/README.md +3 -0
- package/examples/extensions/custom-provider-anthropic/package-lock.json +2 -2
- package/examples/extensions/custom-provider-anthropic/package.json +1 -1
- package/examples/extensions/custom-provider-gitlab-duo/package.json +1 -1
- package/examples/extensions/dynamic-resources/dynamic.json +2 -0
- package/examples/extensions/gondolin/package-lock.json +2 -2
- package/examples/extensions/gondolin/package.json +1 -1
- package/examples/extensions/sandbox/package-lock.json +2 -2
- package/examples/extensions/sandbox/package.json +1 -1
- package/examples/extensions/with-deps/package-lock.json +2 -2
- package/examples/extensions/with-deps/package.json +1 -1
- package/examples/plugins/pi-example-plugin/README.md +38 -0
- package/examples/plugins/pi-example-plugin/package.json +10 -0
- package/examples/plugins/pi-example-plugin/src/contract.ts +13 -0
- package/examples/plugins/pi-example-plugin/src/session.ts +23 -0
- package/examples/plugins/pi-example-plugin/src/tui.ts +31 -0
- package/npm-shrinkwrap.json +523 -28
- package/package.json +12 -6
- package/dist/bun/register-bedrock.d.ts +0 -2
- package/dist/bun/register-bedrock.d.ts.map +0 -1
- package/dist/bun/register-bedrock.js +0 -4
- package/dist/bun/register-bedrock.js.map +0 -1
- package/dist/bundle/chunks/anthropic-messages-JWX2WP65.js +0 -45
- package/dist/bundle/chunks/chunk-JUUC5AZY.js +0 -2
- package/dist/bundle/chunks/chunk-OMWWHBTG.js +0 -1548
- package/dist/bundle/chunks/openai-codex-responses-GJVBJXLB.js +0 -8
- package/dist/bundle/chunks/openai-responses-BSOJMSEV.js +0 -2
- package/dist/cli/experimental/auth.d.ts +0 -16
- package/dist/cli/experimental/auth.d.ts.map +0 -1
- package/dist/cli/experimental/auth.js +0 -13
- package/dist/cli/experimental/auth.js.map +0 -1
- package/dist/cli/experimental/commands/pi.d.ts +0 -15
- package/dist/cli/experimental/commands/pi.d.ts.map +0 -1
- package/dist/cli/experimental/commands/pi.js +0 -28
- package/dist/cli/experimental/commands/pi.js.map +0 -1
- package/dist/cli/experimental/transport-address.d.ts +0 -10
- package/dist/cli/experimental/transport-address.d.ts.map +0 -1
- package/dist/cli/experimental/transport-address.js +0 -38
- package/dist/cli/experimental/transport-address.js.map +0 -1
- package/dist/client/remote-session.d.ts +0 -53
- package/dist/client/remote-session.d.ts.map +0 -1
- package/dist/client/remote-session.js +0 -340
- package/dist/client/remote-session.js.map +0 -1
- package/dist/client/transcript.d.ts +0 -12
- package/dist/client/transcript.d.ts.map +0 -1
- package/dist/client/transcript.js +0 -98
- package/dist/client/transcript.js.map +0 -1
- package/dist/server/create-harness.d.ts +0 -26
- package/dist/server/create-harness.d.ts.map +0 -1
- package/dist/server/create-harness.js +0 -106
- package/dist/server/create-harness.js.map +0 -1
|
@@ -0,0 +1,171 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The whole protocol: call, result, error, cancel, event, ping. Plus named services and one routing
|
|
3
|
+
* rule.
|
|
4
|
+
*
|
|
5
|
+
* A peer provides any number of services and uses the other side's. A call for a service this peer
|
|
6
|
+
* does not provide goes to `forward`, which is what makes the server transparent: a TUI uses
|
|
7
|
+
* `lane.prompt`, the server does not provide `lane`, so it hands the call to the attached worker.
|
|
8
|
+
* The same rule lets a worker use `sessions.list` back through the server.
|
|
9
|
+
*/
|
|
10
|
+
const DEFAULT_DEAD_MS = 15_000;
|
|
11
|
+
/** A bidirectional peer on one connection. */
|
|
12
|
+
export function createPeer(connection, options = {}) {
|
|
13
|
+
const services = new Map();
|
|
14
|
+
const provided = new Set();
|
|
15
|
+
/** What the other side told us it provides, so routing is a lookup rather than a guess. */
|
|
16
|
+
const announced = new Set();
|
|
17
|
+
const pending = new Map();
|
|
18
|
+
/** Controllers for calls this peer is currently answering, so a `cancel` frame can stop them. */
|
|
19
|
+
const inflight = new Map();
|
|
20
|
+
const eventHandlers = [];
|
|
21
|
+
let nextId = 1;
|
|
22
|
+
let lastFrameAt = Date.now();
|
|
23
|
+
// The signal is appended to every handler call: services that care declare a trailing
|
|
24
|
+
// `AbortSignal` parameter, the rest ignore an extra argument.
|
|
25
|
+
const dispatch = async (method, args, signal) => {
|
|
26
|
+
const dot = method.indexOf(".");
|
|
27
|
+
const local = dot === -1 ? undefined : services.get(method.slice(0, dot));
|
|
28
|
+
if (!local) {
|
|
29
|
+
if (!options.forward)
|
|
30
|
+
throw new Error(`No service provides ${method}`);
|
|
31
|
+
return options.forward(method, args);
|
|
32
|
+
}
|
|
33
|
+
const handler = local[method.slice(dot + 1)];
|
|
34
|
+
if (typeof handler !== "function")
|
|
35
|
+
throw new Error(`Unknown method: ${method}`);
|
|
36
|
+
return handler.apply(local, [...args, signal]);
|
|
37
|
+
};
|
|
38
|
+
connection.onMessage((frameValue) => {
|
|
39
|
+
const frame = frameValue;
|
|
40
|
+
lastFrameAt = Date.now();
|
|
41
|
+
switch (frame.kind) {
|
|
42
|
+
case "event": {
|
|
43
|
+
for (const handler of eventHandlers)
|
|
44
|
+
handler(frame.service, frame.payload, frame.to);
|
|
45
|
+
return;
|
|
46
|
+
}
|
|
47
|
+
case "call": {
|
|
48
|
+
const controller = new AbortController();
|
|
49
|
+
inflight.set(frame.id, controller);
|
|
50
|
+
void dispatch(frame.method, frame.args, controller.signal)
|
|
51
|
+
.then(
|
|
52
|
+
// `undefined` vanishes through JSON, so an absent result is sent as null.
|
|
53
|
+
(result) => connection.send({ kind: "result", id: frame.id, result: result ?? null }), (error) => connection.send({ kind: "error", id: frame.id, error: message(error) }))
|
|
54
|
+
.finally(() => inflight.delete(frame.id));
|
|
55
|
+
return;
|
|
56
|
+
}
|
|
57
|
+
case "cancel": {
|
|
58
|
+
inflight.get(frame.id)?.abort(new Error("Cancelled by caller"));
|
|
59
|
+
inflight.delete(frame.id);
|
|
60
|
+
return;
|
|
61
|
+
}
|
|
62
|
+
case "result":
|
|
63
|
+
case "error": {
|
|
64
|
+
const waiter = pending.get(frame.id);
|
|
65
|
+
pending.delete(frame.id);
|
|
66
|
+
if (frame.kind === "error")
|
|
67
|
+
waiter?.reject(new Error(frame.error));
|
|
68
|
+
else
|
|
69
|
+
waiter?.resolve(frame.result);
|
|
70
|
+
return;
|
|
71
|
+
}
|
|
72
|
+
case "announce": {
|
|
73
|
+
announced.clear();
|
|
74
|
+
for (const service of frame.services)
|
|
75
|
+
announced.add(service);
|
|
76
|
+
return;
|
|
77
|
+
}
|
|
78
|
+
case "ping":
|
|
79
|
+
return;
|
|
80
|
+
default: {
|
|
81
|
+
const unknownFrame = frame;
|
|
82
|
+
throw new Error(`Unknown frame: ${JSON.stringify(unknownFrame)}`);
|
|
83
|
+
}
|
|
84
|
+
}
|
|
85
|
+
});
|
|
86
|
+
connection.onClose(() => {
|
|
87
|
+
if (liveness)
|
|
88
|
+
clearInterval(liveness);
|
|
89
|
+
for (const waiter of pending.values())
|
|
90
|
+
waiter.reject(new Error("Connection closed"));
|
|
91
|
+
pending.clear();
|
|
92
|
+
for (const controller of inflight.values())
|
|
93
|
+
controller.abort(new Error("Connection closed"));
|
|
94
|
+
inflight.clear();
|
|
95
|
+
});
|
|
96
|
+
/**
|
|
97
|
+
* A peer can vanish without closing: a killed machine, a wedged event loop. Any frame counts as
|
|
98
|
+
* proof of life, and pings keep an idle connection proving it.
|
|
99
|
+
*/
|
|
100
|
+
const deadMs = options.deadMs ?? DEFAULT_DEAD_MS;
|
|
101
|
+
const liveness = deadMs > 0
|
|
102
|
+
? setInterval(() => {
|
|
103
|
+
if (Date.now() - lastFrameAt > deadMs)
|
|
104
|
+
connection.close();
|
|
105
|
+
else
|
|
106
|
+
connection.send({ kind: "ping" });
|
|
107
|
+
}, Math.floor(deadMs / 3))
|
|
108
|
+
: undefined;
|
|
109
|
+
liveness?.unref();
|
|
110
|
+
const callWith = (callOptions, method, ...args) => new Promise((resolve, reject) => {
|
|
111
|
+
const id = nextId++;
|
|
112
|
+
let timer;
|
|
113
|
+
const abandon = (error) => {
|
|
114
|
+
if (!pending.delete(id))
|
|
115
|
+
return;
|
|
116
|
+
if (timer)
|
|
117
|
+
clearTimeout(timer);
|
|
118
|
+
callOptions.signal?.removeEventListener("abort", onAbort);
|
|
119
|
+
// Tell the peer to stop; it may already be gone, in which case this is a no-op.
|
|
120
|
+
connection.send({ kind: "cancel", id });
|
|
121
|
+
reject(error);
|
|
122
|
+
};
|
|
123
|
+
const onAbort = () => abandon(new Error("Call cancelled"));
|
|
124
|
+
const settle = (handler) => (value) => {
|
|
125
|
+
if (timer)
|
|
126
|
+
clearTimeout(timer);
|
|
127
|
+
callOptions.signal?.removeEventListener("abort", onAbort);
|
|
128
|
+
handler(value);
|
|
129
|
+
};
|
|
130
|
+
if (callOptions.signal?.aborted) {
|
|
131
|
+
reject(new Error("Call cancelled"));
|
|
132
|
+
return;
|
|
133
|
+
}
|
|
134
|
+
pending.set(id, { resolve: settle(resolve), reject: settle(reject) });
|
|
135
|
+
callOptions.signal?.addEventListener("abort", onAbort, { once: true });
|
|
136
|
+
if (callOptions.timeoutMs !== undefined) {
|
|
137
|
+
timer = setTimeout(() => abandon(new Error(`${method} timed out after ${callOptions.timeoutMs}ms`)), callOptions.timeoutMs);
|
|
138
|
+
timer.unref();
|
|
139
|
+
}
|
|
140
|
+
connection.send({ kind: "call", id, method, args });
|
|
141
|
+
});
|
|
142
|
+
const peer = {
|
|
143
|
+
provide: (token, implementation) => {
|
|
144
|
+
services.set(token.name, implementation);
|
|
145
|
+
provided.add(token.name);
|
|
146
|
+
connection.send({ kind: "announce", services: [...provided] });
|
|
147
|
+
},
|
|
148
|
+
provided,
|
|
149
|
+
announced,
|
|
150
|
+
use: (token, callOptions = {}) => new Proxy({}, {
|
|
151
|
+
get: (_target, method) => (...args) => callWith(callOptions, `${token.name}.${String(method)}`, ...args),
|
|
152
|
+
}),
|
|
153
|
+
emit: (token, event) => connection.send({ kind: "event", service: token.name, payload: event }),
|
|
154
|
+
emitTo: (token, event, to) => connection.send({ kind: "event", service: token.name, payload: event, to }),
|
|
155
|
+
emitRaw: (service, payload, to) => connection.send({ kind: "event", service, payload, ...(to === undefined ? {} : { to }) }),
|
|
156
|
+
on: (token, handler) => eventHandlers.push((name, payload) => {
|
|
157
|
+
if (name === token.name)
|
|
158
|
+
handler(payload);
|
|
159
|
+
}),
|
|
160
|
+
onEvent: (handler) => eventHandlers.push(handler),
|
|
161
|
+
call: (method, ...args) => callWith({}, method, ...args),
|
|
162
|
+
callWith,
|
|
163
|
+
onClose: (handler) => connection.onClose(handler),
|
|
164
|
+
close: () => connection.close(),
|
|
165
|
+
};
|
|
166
|
+
return peer;
|
|
167
|
+
}
|
|
168
|
+
function message(error) {
|
|
169
|
+
return error instanceof Error ? error.message : String(error);
|
|
170
|
+
}
|
|
171
|
+
//# sourceMappingURL=rpc.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"rpc.js","sourceRoot":"","sources":["../../../../src/experimental/mini/shared/rpc.ts"],"names":[],"mappings":"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","sourcesContent":["/**\n * The whole protocol: call, result, error, cancel, event, ping. Plus named services and one routing\n * rule.\n *\n * A peer provides any number of services and uses the other side's. A call for a service this peer\n * does not provide goes to `forward`, which is what makes the server transparent: a TUI uses\n * `lane.prompt`, the server does not provide `lane`, so it hands the call to the attached worker.\n * The same rule lets a worker use `sessions.list` back through the server.\n */\n\nimport type { Remote, ServiceToken } from \"./protocol.ts\";\nimport type { Connection } from \"./transport.ts\";\n\ntype Frame =\n\t| { kind: \"call\"; id: number; method: string; args: unknown[] }\n\t| { kind: \"result\"; id: number; result: unknown }\n\t| { kind: \"error\"; id: number; error: string }\n\t| { kind: \"cancel\"; id: number }\n\t| { kind: \"event\"; service: string; payload: unknown; to?: string }\n\t| { kind: \"announce\"; services: string[] }\n\t| { kind: \"ping\" };\n\nexport type Forward = (method: string, args: unknown[]) => Promise<unknown>;\n\nexport interface CallOptions {\n\t/** Abandon the call and tell the peer to stop. */\n\tsignal?: AbortSignal;\n\t/** Reject if the peer has not answered in time. Omit for calls with no bounded duration. */\n\ttimeoutMs?: number;\n}\n\nexport interface PeerOptions {\n\t/** Handles calls for services this peer does not provide. */\n\tforward?: Forward;\n\t/** Silence tolerated before the peer is declared gone. Default 15s; 0 disables liveness. */\n\tdeadMs?: number;\n}\n\nconst DEFAULT_DEAD_MS = 15_000;\n\nexport interface RpcPeer {\n\t/** Register an implementation and announce the name to the other side. */\n\tprovide<TApi extends object, TEvent>(token: ServiceToken<TApi, TEvent>, implementation: TApi): void;\n\t/** Services this peer provides. */\n\treadonly provided: ReadonlySet<string>;\n\t/** Services the other side announced. */\n\treadonly announced: ReadonlySet<string>;\n\t/** Use a service, wherever it is provided: this peer's other side, or its next hop. */\n\tuse<TApi extends object, TEvent>(token: ServiceToken<TApi, TEvent>, options?: CallOptions): Remote<TApi>;\n\t/** Publish to everyone listening on the other side. */\n\temit<TApi extends object, TEvent>(token: ServiceToken<TApi, TEvent>, event: TEvent): void;\n\t/** Publish for one destination. A router delivers it there instead of broadcasting. */\n\temitTo<TApi extends object, TEvent>(token: ServiceToken<TApi, TEvent>, event: TEvent, to: string): void;\n\ton<TApi extends object, TEvent>(token: ServiceToken<TApi, TEvent>, handler: (event: TEvent) => void): void;\n\t/** Router half of the event channel: observe and republish without knowing the service. */\n\tonEvent(handler: (service: string, payload: unknown, to: string | undefined) => void): void;\n\temitRaw(service: string, payload: unknown, to?: string): void;\n\tcall(method: string, ...args: unknown[]): Promise<unknown>;\n\tcallWith(options: CallOptions, method: string, ...args: unknown[]): Promise<unknown>;\n\tonClose(handler: () => void): void;\n\tclose(): void;\n}\n\n/** A bidirectional peer on one connection. */\nexport function createPeer(connection: Connection, options: PeerOptions = {}): RpcPeer {\n\tconst services = new Map<string, object>();\n\tconst provided = new Set<string>();\n\t/** What the other side told us it provides, so routing is a lookup rather than a guess. */\n\tconst announced = new Set<string>();\n\tconst pending = new Map<number, { resolve: (value: unknown) => void; reject: (error: Error) => void }>();\n\t/** Controllers for calls this peer is currently answering, so a `cancel` frame can stop them. */\n\tconst inflight = new Map<number, AbortController>();\n\tconst eventHandlers: ((service: string, payload: unknown, to: string | undefined) => void)[] = [];\n\tlet nextId = 1;\n\tlet lastFrameAt = Date.now();\n\n\t// The signal is appended to every handler call: services that care declare a trailing\n\t// `AbortSignal` parameter, the rest ignore an extra argument.\n\tconst dispatch = async (method: string, args: unknown[], signal: AbortSignal): Promise<unknown> => {\n\t\tconst dot = method.indexOf(\".\");\n\t\tconst local = dot === -1 ? undefined : services.get(method.slice(0, dot));\n\t\tif (!local) {\n\t\t\tif (!options.forward) throw new Error(`No service provides ${method}`);\n\t\t\treturn options.forward(method, args);\n\t\t}\n\t\tconst handler = (local as Record<string, unknown>)[method.slice(dot + 1)];\n\t\tif (typeof handler !== \"function\") throw new Error(`Unknown method: ${method}`);\n\t\treturn (handler as (...args: unknown[]) => unknown).apply(local, [...args, signal]);\n\t};\n\n\tconnection.onMessage((frameValue) => {\n\t\tconst frame = frameValue as Frame;\n\t\tlastFrameAt = Date.now();\n\t\tswitch (frame.kind) {\n\t\t\tcase \"event\": {\n\t\t\t\tfor (const handler of eventHandlers) handler(frame.service, frame.payload, frame.to);\n\t\t\t\treturn;\n\t\t\t}\n\t\t\tcase \"call\": {\n\t\t\t\tconst controller = new AbortController();\n\t\t\t\tinflight.set(frame.id, controller);\n\t\t\t\tvoid dispatch(frame.method, frame.args, controller.signal)\n\t\t\t\t\t.then(\n\t\t\t\t\t\t// `undefined` vanishes through JSON, so an absent result is sent as null.\n\t\t\t\t\t\t(result) => connection.send({ kind: \"result\", id: frame.id, result: result ?? null }),\n\t\t\t\t\t\t(error: unknown) => connection.send({ kind: \"error\", id: frame.id, error: message(error) }),\n\t\t\t\t\t)\n\t\t\t\t\t.finally(() => inflight.delete(frame.id));\n\t\t\t\treturn;\n\t\t\t}\n\t\t\tcase \"cancel\": {\n\t\t\t\tinflight.get(frame.id)?.abort(new Error(\"Cancelled by caller\"));\n\t\t\t\tinflight.delete(frame.id);\n\t\t\t\treturn;\n\t\t\t}\n\t\t\tcase \"result\":\n\t\t\tcase \"error\": {\n\t\t\t\tconst waiter = pending.get(frame.id);\n\t\t\t\tpending.delete(frame.id);\n\t\t\t\tif (frame.kind === \"error\") waiter?.reject(new Error(frame.error));\n\t\t\t\telse waiter?.resolve(frame.result);\n\t\t\t\treturn;\n\t\t\t}\n\t\t\tcase \"announce\": {\n\t\t\t\tannounced.clear();\n\t\t\t\tfor (const service of frame.services) announced.add(service);\n\t\t\t\treturn;\n\t\t\t}\n\t\t\tcase \"ping\":\n\t\t\t\treturn;\n\t\t\tdefault: {\n\t\t\t\tconst unknownFrame: never = frame;\n\t\t\t\tthrow new Error(`Unknown frame: ${JSON.stringify(unknownFrame)}`);\n\t\t\t}\n\t\t}\n\t});\n\n\tconnection.onClose(() => {\n\t\tif (liveness) clearInterval(liveness);\n\t\tfor (const waiter of pending.values()) waiter.reject(new Error(\"Connection closed\"));\n\t\tpending.clear();\n\t\tfor (const controller of inflight.values()) controller.abort(new Error(\"Connection closed\"));\n\t\tinflight.clear();\n\t});\n\n\t/**\n\t * A peer can vanish without closing: a killed machine, a wedged event loop. Any frame counts as\n\t * proof of life, and pings keep an idle connection proving it.\n\t */\n\tconst deadMs = options.deadMs ?? DEFAULT_DEAD_MS;\n\tconst liveness =\n\t\tdeadMs > 0\n\t\t\t? setInterval(\n\t\t\t\t\t() => {\n\t\t\t\t\t\tif (Date.now() - lastFrameAt > deadMs) connection.close();\n\t\t\t\t\t\telse connection.send({ kind: \"ping\" });\n\t\t\t\t\t},\n\t\t\t\t\tMath.floor(deadMs / 3),\n\t\t\t\t)\n\t\t\t: undefined;\n\tliveness?.unref();\n\n\tconst callWith = (callOptions: CallOptions, method: string, ...args: unknown[]): Promise<unknown> =>\n\t\tnew Promise((resolve, reject) => {\n\t\t\tconst id = nextId++;\n\t\t\tlet timer: NodeJS.Timeout | undefined;\n\t\t\tconst abandon = (error: Error): void => {\n\t\t\t\tif (!pending.delete(id)) return;\n\t\t\t\tif (timer) clearTimeout(timer);\n\t\t\t\tcallOptions.signal?.removeEventListener(\"abort\", onAbort);\n\t\t\t\t// Tell the peer to stop; it may already be gone, in which case this is a no-op.\n\t\t\t\tconnection.send({ kind: \"cancel\", id });\n\t\t\t\treject(error);\n\t\t\t};\n\t\t\tconst onAbort = (): void => abandon(new Error(\"Call cancelled\"));\n\t\t\tconst settle =\n\t\t\t\t<T>(handler: (value: T) => void) =>\n\t\t\t\t(value: T) => {\n\t\t\t\t\tif (timer) clearTimeout(timer);\n\t\t\t\t\tcallOptions.signal?.removeEventListener(\"abort\", onAbort);\n\t\t\t\t\thandler(value);\n\t\t\t\t};\n\t\t\tif (callOptions.signal?.aborted) {\n\t\t\t\treject(new Error(\"Call cancelled\"));\n\t\t\t\treturn;\n\t\t\t}\n\t\t\tpending.set(id, { resolve: settle(resolve), reject: settle(reject) });\n\t\t\tcallOptions.signal?.addEventListener(\"abort\", onAbort, { once: true });\n\t\t\tif (callOptions.timeoutMs !== undefined) {\n\t\t\t\ttimer = setTimeout(\n\t\t\t\t\t() => abandon(new Error(`${method} timed out after ${callOptions.timeoutMs}ms`)),\n\t\t\t\t\tcallOptions.timeoutMs,\n\t\t\t\t);\n\t\t\t\ttimer.unref();\n\t\t\t}\n\t\t\tconnection.send({ kind: \"call\", id, method, args });\n\t\t});\n\n\tconst peer: RpcPeer = {\n\t\tprovide: (token, implementation) => {\n\t\t\tservices.set(token.name, implementation);\n\t\t\tprovided.add(token.name);\n\t\t\tconnection.send({ kind: \"announce\", services: [...provided] });\n\t\t},\n\t\tprovided,\n\t\tannounced,\n\t\tuse: (token, callOptions = {}) =>\n\t\t\tnew Proxy({} as Remote<typeof token extends ServiceToken<infer TApi, never> ? TApi : never>, {\n\t\t\t\tget:\n\t\t\t\t\t(_target, method) =>\n\t\t\t\t\t(...args: unknown[]) =>\n\t\t\t\t\t\tcallWith(callOptions, `${token.name}.${String(method)}`, ...args),\n\t\t\t}) as never,\n\t\temit: (token, event) => connection.send({ kind: \"event\", service: token.name, payload: event }),\n\t\temitTo: (token, event, to) => connection.send({ kind: \"event\", service: token.name, payload: event, to }),\n\t\temitRaw: (service, payload, to) =>\n\t\t\tconnection.send({ kind: \"event\", service, payload, ...(to === undefined ? {} : { to }) }),\n\t\ton: (token, handler) =>\n\t\t\teventHandlers.push((name, payload) => {\n\t\t\t\tif (name === token.name) handler(payload as never);\n\t\t\t}),\n\t\tonEvent: (handler) => eventHandlers.push(handler),\n\t\tcall: (method, ...args) => callWith({}, method, ...args),\n\t\tcallWith,\n\t\tonClose: (handler) => connection.onClose(handler),\n\t\tclose: () => connection.close(),\n\t};\n\treturn peer;\n}\n\nfunction message(error: unknown): string {\n\treturn error instanceof Error ? error.message : String(error);\n}\n"]}
|
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Pluggable transport: newline-delimited JSON over any duplex pair.
|
|
3
|
+
*
|
|
4
|
+
* `Connection` is the shared abstraction and every host uses it. `Transport` is only for hops that
|
|
5
|
+
* have an address to negotiate: the unix socket between presentations and the server. A spawned
|
|
6
|
+
* worker needs none, because the pipes exist before it does.
|
|
7
|
+
*/
|
|
8
|
+
export interface Connection {
|
|
9
|
+
send(message: unknown): void;
|
|
10
|
+
onMessage(handler: (message: unknown) => void): void;
|
|
11
|
+
onClose(handler: () => void): void;
|
|
12
|
+
close(): void;
|
|
13
|
+
}
|
|
14
|
+
export interface Listener {
|
|
15
|
+
close(): Promise<void>;
|
|
16
|
+
}
|
|
17
|
+
export interface Transport {
|
|
18
|
+
listen(onConnection: (connection: Connection) => void): Promise<Listener>;
|
|
19
|
+
connect(): Promise<Connection>;
|
|
20
|
+
}
|
|
21
|
+
/** Frame JSON messages as one line each over a readable/writable pair. */
|
|
22
|
+
export declare function jsonConnection(input: NodeJS.ReadableStream, output: NodeJS.WritableStream, close: () => void): Connection;
|
|
23
|
+
/**
|
|
24
|
+
* A spawned child is connected at birth, so there is no address to dial and no `Transport`: the
|
|
25
|
+
* parent reads the child's stdout and writes its stdin, and the child sees the same pipes reversed.
|
|
26
|
+
*/
|
|
27
|
+
export declare function childConnection(child: {
|
|
28
|
+
stdin: NodeJS.WritableStream | null;
|
|
29
|
+
stdout: NodeJS.ReadableStream | null;
|
|
30
|
+
kill(): unknown;
|
|
31
|
+
}): Connection;
|
|
32
|
+
/** The child's own view of the pipes its parent created. */
|
|
33
|
+
export declare function parentConnection(): Connection;
|
|
34
|
+
export declare function socketTransport(path: string): Transport;
|
|
35
|
+
//# sourceMappingURL=transport.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"transport.d.ts","sourceRoot":"","sources":["../../../../src/experimental/mini/shared/transport.ts"],"names":[],"mappings":"AAAA;;;;;;GAMG;AAKH,MAAM,WAAW,UAAU;IAC1B,IAAI,CAAC,OAAO,EAAE,OAAO,GAAG,IAAI,CAAC;IAC7B,SAAS,CAAC,OAAO,EAAE,CAAC,OAAO,EAAE,OAAO,KAAK,IAAI,GAAG,IAAI,CAAC;IACrD,OAAO,CAAC,OAAO,EAAE,MAAM,IAAI,GAAG,IAAI,CAAC;IACnC,KAAK,IAAI,IAAI,CAAC;CACd;AAED,MAAM,WAAW,QAAQ;IACxB,KAAK,IAAI,OAAO,CAAC,IAAI,CAAC,CAAC;CACvB;AAED,MAAM,WAAW,SAAS;IACzB,MAAM,CAAC,YAAY,EAAE,CAAC,UAAU,EAAE,UAAU,KAAK,IAAI,GAAG,OAAO,CAAC,QAAQ,CAAC,CAAC;IAC1E,OAAO,IAAI,OAAO,CAAC,UAAU,CAAC,CAAC;CAC/B;AAED,0EAA0E;AAC1E,wBAAgB,cAAc,CAC7B,KAAK,EAAE,MAAM,CAAC,cAAc,EAC5B,MAAM,EAAE,MAAM,CAAC,cAAc,EAC7B,KAAK,EAAE,MAAM,IAAI,GACf,UAAU,CAsCZ;AAMD;;;GAGG;AACH,wBAAgB,eAAe,CAAC,KAAK,EAAE;IACtC,KAAK,EAAE,MAAM,CAAC,cAAc,GAAG,IAAI,CAAC;IACpC,MAAM,EAAE,MAAM,CAAC,cAAc,GAAG,IAAI,CAAC;IACrC,IAAI,IAAI,OAAO,CAAC;CAChB,GAAG,UAAU,CAGb;AAED,4DAA4D;AAC5D,wBAAgB,gBAAgB,IAAI,UAAU,CAE7C;AAED,wBAAgB,eAAe,CAAC,IAAI,EAAE,MAAM,GAAG,SAAS,CAwBvD","sourcesContent":["/**\n * Pluggable transport: newline-delimited JSON over any duplex pair.\n *\n * `Connection` is the shared abstraction and every host uses it. `Transport` is only for hops that\n * have an address to negotiate: the unix socket between presentations and the server. A spawned\n * worker needs none, because the pipes exist before it does.\n */\n\nimport { rm } from \"node:fs/promises\";\nimport { createConnection as connectSocket, createServer, type Socket } from \"node:net\";\n\nexport interface Connection {\n\tsend(message: unknown): void;\n\tonMessage(handler: (message: unknown) => void): void;\n\tonClose(handler: () => void): void;\n\tclose(): void;\n}\n\nexport interface Listener {\n\tclose(): Promise<void>;\n}\n\nexport interface Transport {\n\tlisten(onConnection: (connection: Connection) => void): Promise<Listener>;\n\tconnect(): Promise<Connection>;\n}\n\n/** Frame JSON messages as one line each over a readable/writable pair. */\nexport function jsonConnection(\n\tinput: NodeJS.ReadableStream,\n\toutput: NodeJS.WritableStream,\n\tclose: () => void,\n): Connection {\n\tconst messageHandlers: ((message: unknown) => void)[] = [];\n\tconst closeHandlers: (() => void)[] = [];\n\tlet buffered = \"\";\n\tlet closed = false;\n\tconst notifyClosed = (): void => {\n\t\tif (closed) return;\n\t\tclosed = true;\n\t\tfor (const handler of closeHandlers) handler();\n\t};\n\tinput.setEncoding(\"utf8\");\n\tinput.on(\"data\", (chunk: string) => {\n\t\tbuffered += chunk;\n\t\tlet newline = buffered.indexOf(\"\\n\");\n\t\twhile (newline !== -1) {\n\t\t\tconst line = buffered.slice(0, newline);\n\t\t\tbuffered = buffered.slice(newline + 1);\n\t\t\tif (line.length > 0) {\n\t\t\t\tconst message: unknown = JSON.parse(line);\n\t\t\t\tfor (const handler of messageHandlers) handler(message);\n\t\t\t}\n\t\t\tnewline = buffered.indexOf(\"\\n\");\n\t\t}\n\t});\n\tinput.on(\"end\", notifyClosed);\n\tinput.on(\"error\", notifyClosed);\n\toutput.on(\"error\", notifyClosed);\n\treturn {\n\t\tsend: (message) => {\n\t\t\tif (!closed) output.write(`${JSON.stringify(message)}\\n`);\n\t\t},\n\t\tonMessage: (handler) => messageHandlers.push(handler),\n\t\tonClose: (handler) => closeHandlers.push(handler),\n\t\tclose: () => {\n\t\t\tnotifyClosed();\n\t\t\tclose();\n\t\t},\n\t};\n}\n\nfunction socketConnection(socket: Socket): Connection {\n\treturn jsonConnection(socket, socket, () => socket.destroy());\n}\n\n/**\n * A spawned child is connected at birth, so there is no address to dial and no `Transport`: the\n * parent reads the child's stdout and writes its stdin, and the child sees the same pipes reversed.\n */\nexport function childConnection(child: {\n\tstdin: NodeJS.WritableStream | null;\n\tstdout: NodeJS.ReadableStream | null;\n\tkill(): unknown;\n}): Connection {\n\tif (!child.stdin || !child.stdout) throw new Error(\"Child process was spawned without pipes\");\n\treturn jsonConnection(child.stdout, child.stdin, () => child.kill());\n}\n\n/** The child's own view of the pipes its parent created. */\nexport function parentConnection(): Connection {\n\treturn jsonConnection(process.stdin, process.stdout, () => process.stdin.pause());\n}\n\nexport function socketTransport(path: string): Transport {\n\treturn {\n\t\tasync listen(onConnection) {\n\t\t\tawait rm(path, { force: true });\n\t\t\tconst server = createServer((socket) => onConnection(socketConnection(socket)));\n\t\t\tawait new Promise<void>((resolve, reject) => {\n\t\t\t\tserver.once(\"error\", reject);\n\t\t\t\tserver.listen(path, resolve);\n\t\t\t});\n\t\t\treturn {\n\t\t\t\tclose: () =>\n\t\t\t\t\tnew Promise<void>((resolve) => {\n\t\t\t\t\t\tserver.close(() => resolve());\n\t\t\t\t\t}),\n\t\t\t};\n\t\t},\n\t\tconnect() {\n\t\t\treturn new Promise<Connection>((resolve, reject) => {\n\t\t\t\tconst socket = connectSocket(path);\n\t\t\t\tsocket.once(\"connect\", () => resolve(socketConnection(socket)));\n\t\t\t\tsocket.once(\"error\", reject);\n\t\t\t});\n\t\t},\n\t};\n}\n"]}
|
|
@@ -0,0 +1,94 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Pluggable transport: newline-delimited JSON over any duplex pair.
|
|
3
|
+
*
|
|
4
|
+
* `Connection` is the shared abstraction and every host uses it. `Transport` is only for hops that
|
|
5
|
+
* have an address to negotiate: the unix socket between presentations and the server. A spawned
|
|
6
|
+
* worker needs none, because the pipes exist before it does.
|
|
7
|
+
*/
|
|
8
|
+
import { rm } from "node:fs/promises";
|
|
9
|
+
import { createConnection as connectSocket, createServer } from "node:net";
|
|
10
|
+
/** Frame JSON messages as one line each over a readable/writable pair. */
|
|
11
|
+
export function jsonConnection(input, output, close) {
|
|
12
|
+
const messageHandlers = [];
|
|
13
|
+
const closeHandlers = [];
|
|
14
|
+
let buffered = "";
|
|
15
|
+
let closed = false;
|
|
16
|
+
const notifyClosed = () => {
|
|
17
|
+
if (closed)
|
|
18
|
+
return;
|
|
19
|
+
closed = true;
|
|
20
|
+
for (const handler of closeHandlers)
|
|
21
|
+
handler();
|
|
22
|
+
};
|
|
23
|
+
input.setEncoding("utf8");
|
|
24
|
+
input.on("data", (chunk) => {
|
|
25
|
+
buffered += chunk;
|
|
26
|
+
let newline = buffered.indexOf("\n");
|
|
27
|
+
while (newline !== -1) {
|
|
28
|
+
const line = buffered.slice(0, newline);
|
|
29
|
+
buffered = buffered.slice(newline + 1);
|
|
30
|
+
if (line.length > 0) {
|
|
31
|
+
const message = JSON.parse(line);
|
|
32
|
+
for (const handler of messageHandlers)
|
|
33
|
+
handler(message);
|
|
34
|
+
}
|
|
35
|
+
newline = buffered.indexOf("\n");
|
|
36
|
+
}
|
|
37
|
+
});
|
|
38
|
+
input.on("end", notifyClosed);
|
|
39
|
+
input.on("error", notifyClosed);
|
|
40
|
+
output.on("error", notifyClosed);
|
|
41
|
+
return {
|
|
42
|
+
send: (message) => {
|
|
43
|
+
if (!closed)
|
|
44
|
+
output.write(`${JSON.stringify(message)}\n`);
|
|
45
|
+
},
|
|
46
|
+
onMessage: (handler) => messageHandlers.push(handler),
|
|
47
|
+
onClose: (handler) => closeHandlers.push(handler),
|
|
48
|
+
close: () => {
|
|
49
|
+
notifyClosed();
|
|
50
|
+
close();
|
|
51
|
+
},
|
|
52
|
+
};
|
|
53
|
+
}
|
|
54
|
+
function socketConnection(socket) {
|
|
55
|
+
return jsonConnection(socket, socket, () => socket.destroy());
|
|
56
|
+
}
|
|
57
|
+
/**
|
|
58
|
+
* A spawned child is connected at birth, so there is no address to dial and no `Transport`: the
|
|
59
|
+
* parent reads the child's stdout and writes its stdin, and the child sees the same pipes reversed.
|
|
60
|
+
*/
|
|
61
|
+
export function childConnection(child) {
|
|
62
|
+
if (!child.stdin || !child.stdout)
|
|
63
|
+
throw new Error("Child process was spawned without pipes");
|
|
64
|
+
return jsonConnection(child.stdout, child.stdin, () => child.kill());
|
|
65
|
+
}
|
|
66
|
+
/** The child's own view of the pipes its parent created. */
|
|
67
|
+
export function parentConnection() {
|
|
68
|
+
return jsonConnection(process.stdin, process.stdout, () => process.stdin.pause());
|
|
69
|
+
}
|
|
70
|
+
export function socketTransport(path) {
|
|
71
|
+
return {
|
|
72
|
+
async listen(onConnection) {
|
|
73
|
+
await rm(path, { force: true });
|
|
74
|
+
const server = createServer((socket) => onConnection(socketConnection(socket)));
|
|
75
|
+
await new Promise((resolve, reject) => {
|
|
76
|
+
server.once("error", reject);
|
|
77
|
+
server.listen(path, resolve);
|
|
78
|
+
});
|
|
79
|
+
return {
|
|
80
|
+
close: () => new Promise((resolve) => {
|
|
81
|
+
server.close(() => resolve());
|
|
82
|
+
}),
|
|
83
|
+
};
|
|
84
|
+
},
|
|
85
|
+
connect() {
|
|
86
|
+
return new Promise((resolve, reject) => {
|
|
87
|
+
const socket = connectSocket(path);
|
|
88
|
+
socket.once("connect", () => resolve(socketConnection(socket)));
|
|
89
|
+
socket.once("error", reject);
|
|
90
|
+
});
|
|
91
|
+
},
|
|
92
|
+
};
|
|
93
|
+
}
|
|
94
|
+
//# sourceMappingURL=transport.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"transport.js","sourceRoot":"","sources":["../../../../src/experimental/mini/shared/transport.ts"],"names":[],"mappings":"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","sourcesContent":["/**\n * Pluggable transport: newline-delimited JSON over any duplex pair.\n *\n * `Connection` is the shared abstraction and every host uses it. `Transport` is only for hops that\n * have an address to negotiate: the unix socket between presentations and the server. A spawned\n * worker needs none, because the pipes exist before it does.\n */\n\nimport { rm } from \"node:fs/promises\";\nimport { createConnection as connectSocket, createServer, type Socket } from \"node:net\";\n\nexport interface Connection {\n\tsend(message: unknown): void;\n\tonMessage(handler: (message: unknown) => void): void;\n\tonClose(handler: () => void): void;\n\tclose(): void;\n}\n\nexport interface Listener {\n\tclose(): Promise<void>;\n}\n\nexport interface Transport {\n\tlisten(onConnection: (connection: Connection) => void): Promise<Listener>;\n\tconnect(): Promise<Connection>;\n}\n\n/** Frame JSON messages as one line each over a readable/writable pair. */\nexport function jsonConnection(\n\tinput: NodeJS.ReadableStream,\n\toutput: NodeJS.WritableStream,\n\tclose: () => void,\n): Connection {\n\tconst messageHandlers: ((message: unknown) => void)[] = [];\n\tconst closeHandlers: (() => void)[] = [];\n\tlet buffered = \"\";\n\tlet closed = false;\n\tconst notifyClosed = (): void => {\n\t\tif (closed) return;\n\t\tclosed = true;\n\t\tfor (const handler of closeHandlers) handler();\n\t};\n\tinput.setEncoding(\"utf8\");\n\tinput.on(\"data\", (chunk: string) => {\n\t\tbuffered += chunk;\n\t\tlet newline = buffered.indexOf(\"\\n\");\n\t\twhile (newline !== -1) {\n\t\t\tconst line = buffered.slice(0, newline);\n\t\t\tbuffered = buffered.slice(newline + 1);\n\t\t\tif (line.length > 0) {\n\t\t\t\tconst message: unknown = JSON.parse(line);\n\t\t\t\tfor (const handler of messageHandlers) handler(message);\n\t\t\t}\n\t\t\tnewline = buffered.indexOf(\"\\n\");\n\t\t}\n\t});\n\tinput.on(\"end\", notifyClosed);\n\tinput.on(\"error\", notifyClosed);\n\toutput.on(\"error\", notifyClosed);\n\treturn {\n\t\tsend: (message) => {\n\t\t\tif (!closed) output.write(`${JSON.stringify(message)}\\n`);\n\t\t},\n\t\tonMessage: (handler) => messageHandlers.push(handler),\n\t\tonClose: (handler) => closeHandlers.push(handler),\n\t\tclose: () => {\n\t\t\tnotifyClosed();\n\t\t\tclose();\n\t\t},\n\t};\n}\n\nfunction socketConnection(socket: Socket): Connection {\n\treturn jsonConnection(socket, socket, () => socket.destroy());\n}\n\n/**\n * A spawned child is connected at birth, so there is no address to dial and no `Transport`: the\n * parent reads the child's stdout and writes its stdin, and the child sees the same pipes reversed.\n */\nexport function childConnection(child: {\n\tstdin: NodeJS.WritableStream | null;\n\tstdout: NodeJS.ReadableStream | null;\n\tkill(): unknown;\n}): Connection {\n\tif (!child.stdin || !child.stdout) throw new Error(\"Child process was spawned without pipes\");\n\treturn jsonConnection(child.stdout, child.stdin, () => child.kill());\n}\n\n/** The child's own view of the pipes its parent created. */\nexport function parentConnection(): Connection {\n\treturn jsonConnection(process.stdin, process.stdout, () => process.stdin.pause());\n}\n\nexport function socketTransport(path: string): Transport {\n\treturn {\n\t\tasync listen(onConnection) {\n\t\t\tawait rm(path, { force: true });\n\t\t\tconst server = createServer((socket) => onConnection(socketConnection(socket)));\n\t\t\tawait new Promise<void>((resolve, reject) => {\n\t\t\t\tserver.once(\"error\", reject);\n\t\t\t\tserver.listen(path, resolve);\n\t\t\t});\n\t\t\treturn {\n\t\t\t\tclose: () =>\n\t\t\t\t\tnew Promise<void>((resolve) => {\n\t\t\t\t\t\tserver.close(() => resolve());\n\t\t\t\t\t}),\n\t\t\t};\n\t\t},\n\t\tconnect() {\n\t\t\treturn new Promise<Connection>((resolve, reject) => {\n\t\t\t\tconst socket = connectSocket(path);\n\t\t\t\tsocket.once(\"connect\", () => resolve(socketConnection(socket)));\n\t\t\t\tsocket.once(\"error\", reject);\n\t\t\t});\n\t\t},\n\t};\n}\n"]}
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The presentation host: find or start the session server, attach to a session, run the view.
|
|
3
|
+
*/
|
|
4
|
+
export interface TuiOptions {
|
|
5
|
+
cwd?: string;
|
|
6
|
+
continueSession?: boolean;
|
|
7
|
+
}
|
|
8
|
+
export declare function runTui(options?: TuiOptions): Promise<void>;
|
|
9
|
+
//# sourceMappingURL=run.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"run.d.ts","sourceRoot":"","sources":["../../../../src/experimental/mini/tui/run.ts"],"names":[],"mappings":"AAAA;;GAEG;AAeH,MAAM,WAAW,UAAU;IAC1B,GAAG,CAAC,EAAE,MAAM,CAAC;IACb,eAAe,CAAC,EAAE,OAAO,CAAC;CAC1B;AA2BD,wBAAsB,MAAM,CAAC,OAAO,GAAE,UAAe,GAAG,OAAO,CAAC,IAAI,CAAC,CAoBpE","sourcesContent":["/**\n * The presentation host: find or start the session server, attach to a session, run the view.\n */\n\nimport { spawn } from \"node:child_process\";\nimport { mkdir } from \"node:fs/promises\";\nimport { extname, join } from \"node:path\";\nimport { fileURLToPath } from \"node:url\";\nimport { getAgentDir } from \"../../../config.ts\";\nimport { socketTransport, type Transport } from \"../shared/transport.ts\";\nimport { connect, listSessions } from \"./session.ts\";\nimport { runView } from \"./view.ts\";\n\nconst SELF_EXTENSION = extname(fileURLToPath(import.meta.url));\nconst SERVER_ENTRY = fileURLToPath(new URL(`../server/entry${SELF_EXTENSION}`, import.meta.url));\nconst SERVER_START_TIMEOUT_MS = 10_000;\n\nexport interface TuiOptions {\n\tcwd?: string;\n\tcontinueSession?: boolean;\n}\n\nasync function ensureServer(transport: Transport, socketPath: string, sessionsRoot: string): Promise<void> {\n\ttry {\n\t\t(await transport.connect()).close();\n\t\treturn;\n\t} catch {\n\t\t// No server yet; start one.\n\t}\n\t// execArgv is forwarded so a parent running under a TypeScript loader produces children that do too.\n\tconst child = spawn(process.execPath, [...process.execArgv, SERVER_ENTRY, socketPath, sessionsRoot], {\n\t\tdetached: true,\n\t\tstdio: \"ignore\",\n\t});\n\tchild.unref();\n\tconst deadline = Date.now() + SERVER_START_TIMEOUT_MS;\n\twhile (Date.now() < deadline) {\n\t\ttry {\n\t\t\t(await transport.connect()).close();\n\t\t\treturn;\n\t\t} catch {\n\t\t\tawait new Promise((resolve) => setTimeout(resolve, 50));\n\t\t}\n\t}\n\tthrow new Error(\"Timed out waiting for the mini session server\");\n}\n\nexport async function runTui(options: TuiOptions = {}): Promise<void> {\n\tconst cwd = options.cwd ?? process.cwd();\n\tconst root = join(getAgentDir(), \"experimental\");\n\tawait mkdir(root, { recursive: true });\n\tconst socketPath = join(root, \"mini.sock\");\n\tconst sessionsRoot = join(root, \"mini-sessions\");\n\tconst transport = socketTransport(socketPath);\n\tawait ensureServer(transport, socketPath, sessionsRoot);\n\n\tlet sessionId: string | null = null;\n\tif (options.continueSession) {\n\t\tconst sessions = (await listSessions(transport)).filter((session) => session.cwd === cwd);\n\t\tsessionId = sessions.sort((left, right) => left.createdAt - right.createdAt).at(-1)?.id ?? null;\n\t}\n\tconst client = await connect(transport, sessionId, cwd);\n\ttry {\n\t\tawait runView(client);\n\t} finally {\n\t\tclient.close();\n\t}\n}\n"]}
|
|
@@ -0,0 +1,62 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The presentation host: find or start the session server, attach to a session, run the view.
|
|
3
|
+
*/
|
|
4
|
+
import { spawn } from "node:child_process";
|
|
5
|
+
import { mkdir } from "node:fs/promises";
|
|
6
|
+
import { extname, join } from "node:path";
|
|
7
|
+
import { fileURLToPath } from "node:url";
|
|
8
|
+
import { getAgentDir } from "../../../config.js";
|
|
9
|
+
import { socketTransport } from "../shared/transport.js";
|
|
10
|
+
import { connect, listSessions } from "./session.js";
|
|
11
|
+
import { runView } from "./view.js";
|
|
12
|
+
const SELF_EXTENSION = extname(fileURLToPath(import.meta.url));
|
|
13
|
+
const SERVER_ENTRY = fileURLToPath(new URL(`../server/entry${SELF_EXTENSION}`, import.meta.url));
|
|
14
|
+
const SERVER_START_TIMEOUT_MS = 10_000;
|
|
15
|
+
async function ensureServer(transport, socketPath, sessionsRoot) {
|
|
16
|
+
try {
|
|
17
|
+
(await transport.connect()).close();
|
|
18
|
+
return;
|
|
19
|
+
}
|
|
20
|
+
catch {
|
|
21
|
+
// No server yet; start one.
|
|
22
|
+
}
|
|
23
|
+
// execArgv is forwarded so a parent running under a TypeScript loader produces children that do too.
|
|
24
|
+
const child = spawn(process.execPath, [...process.execArgv, SERVER_ENTRY, socketPath, sessionsRoot], {
|
|
25
|
+
detached: true,
|
|
26
|
+
stdio: "ignore",
|
|
27
|
+
});
|
|
28
|
+
child.unref();
|
|
29
|
+
const deadline = Date.now() + SERVER_START_TIMEOUT_MS;
|
|
30
|
+
while (Date.now() < deadline) {
|
|
31
|
+
try {
|
|
32
|
+
(await transport.connect()).close();
|
|
33
|
+
return;
|
|
34
|
+
}
|
|
35
|
+
catch {
|
|
36
|
+
await new Promise((resolve) => setTimeout(resolve, 50));
|
|
37
|
+
}
|
|
38
|
+
}
|
|
39
|
+
throw new Error("Timed out waiting for the mini session server");
|
|
40
|
+
}
|
|
41
|
+
export async function runTui(options = {}) {
|
|
42
|
+
const cwd = options.cwd ?? process.cwd();
|
|
43
|
+
const root = join(getAgentDir(), "experimental");
|
|
44
|
+
await mkdir(root, { recursive: true });
|
|
45
|
+
const socketPath = join(root, "mini.sock");
|
|
46
|
+
const sessionsRoot = join(root, "mini-sessions");
|
|
47
|
+
const transport = socketTransport(socketPath);
|
|
48
|
+
await ensureServer(transport, socketPath, sessionsRoot);
|
|
49
|
+
let sessionId = null;
|
|
50
|
+
if (options.continueSession) {
|
|
51
|
+
const sessions = (await listSessions(transport)).filter((session) => session.cwd === cwd);
|
|
52
|
+
sessionId = sessions.sort((left, right) => left.createdAt - right.createdAt).at(-1)?.id ?? null;
|
|
53
|
+
}
|
|
54
|
+
const client = await connect(transport, sessionId, cwd);
|
|
55
|
+
try {
|
|
56
|
+
await runView(client);
|
|
57
|
+
}
|
|
58
|
+
finally {
|
|
59
|
+
client.close();
|
|
60
|
+
}
|
|
61
|
+
}
|
|
62
|
+
//# sourceMappingURL=run.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"run.js","sourceRoot":"","sources":["../../../../src/experimental/mini/tui/run.ts"],"names":[],"mappings":"AAAA;;GAEG;AAEH,OAAO,EAAE,KAAK,EAAE,MAAM,oBAAoB,CAAC;AAC3C,OAAO,EAAE,KAAK,EAAE,MAAM,kBAAkB,CAAC;AACzC,OAAO,EAAE,OAAO,EAAE,IAAI,EAAE,MAAM,WAAW,CAAC;AAC1C,OAAO,EAAE,aAAa,EAAE,MAAM,UAAU,CAAC;AACzC,OAAO,EAAE,WAAW,EAAE,MAAM,oBAAoB,CAAC;AACjD,OAAO,EAAE,eAAe,EAAkB,MAAM,wBAAwB,CAAC;AACzE,OAAO,EAAE,OAAO,EAAE,YAAY,EAAE,MAAM,cAAc,CAAC;AACrD,OAAO,EAAE,OAAO,EAAE,MAAM,WAAW,CAAC;AAEpC,MAAM,cAAc,GAAG,OAAO,CAAC,aAAa,CAAC,OAAO,IAAI,CAAC,GAAG,CAAC,CAAC,CAAC;AAC/D,MAAM,YAAY,GAAG,aAAa,CAAC,IAAI,GAAG,CAAC,kBAAkB,cAAc,EAAE,EAAE,OAAO,IAAI,CAAC,GAAG,CAAC,CAAC,CAAC;AACjG,MAAM,uBAAuB,GAAG,MAAM,CAAC;AAOvC,KAAK,UAAU,YAAY,CAAC,SAAoB,EAAE,UAAkB,EAAE,YAAoB,EAAiB;IAC1G,IAAI,CAAC;QACJ,CAAC,MAAM,SAAS,CAAC,OAAO,EAAE,CAAC,CAAC,KAAK,EAAE,CAAC;QACpC,OAAO;IACR,CAAC;IAAC,MAAM,CAAC;QACR,4BAA4B;IAC7B,CAAC;IACD,qGAAqG;IACrG,MAAM,KAAK,GAAG,KAAK,CAAC,OAAO,CAAC,QAAQ,EAAE,CAAC,GAAG,OAAO,CAAC,QAAQ,EAAE,YAAY,EAAE,UAAU,EAAE,YAAY,CAAC,EAAE;QACpG,QAAQ,EAAE,IAAI;QACd,KAAK,EAAE,QAAQ;KACf,CAAC,CAAC;IACH,KAAK,CAAC,KAAK,EAAE,CAAC;IACd,MAAM,QAAQ,GAAG,IAAI,CAAC,GAAG,EAAE,GAAG,uBAAuB,CAAC;IACtD,OAAO,IAAI,CAAC,GAAG,EAAE,GAAG,QAAQ,EAAE,CAAC;QAC9B,IAAI,CAAC;YACJ,CAAC,MAAM,SAAS,CAAC,OAAO,EAAE,CAAC,CAAC,KAAK,EAAE,CAAC;YACpC,OAAO;QACR,CAAC;QAAC,MAAM,CAAC;YACR,MAAM,IAAI,OAAO,CAAC,CAAC,OAAO,EAAE,EAAE,CAAC,UAAU,CAAC,OAAO,EAAE,EAAE,CAAC,CAAC,CAAC;QACzD,CAAC;IACF,CAAC;IACD,MAAM,IAAI,KAAK,CAAC,+CAA+C,CAAC,CAAC;AAAA,CACjE;AAED,MAAM,CAAC,KAAK,UAAU,MAAM,CAAC,OAAO,GAAe,EAAE,EAAiB;IACrE,MAAM,GAAG,GAAG,OAAO,CAAC,GAAG,IAAI,OAAO,CAAC,GAAG,EAAE,CAAC;IACzC,MAAM,IAAI,GAAG,IAAI,CAAC,WAAW,EAAE,EAAE,cAAc,CAAC,CAAC;IACjD,MAAM,KAAK,CAAC,IAAI,EAAE,EAAE,SAAS,EAAE,IAAI,EAAE,CAAC,CAAC;IACvC,MAAM,UAAU,GAAG,IAAI,CAAC,IAAI,EAAE,WAAW,CAAC,CAAC;IAC3C,MAAM,YAAY,GAAG,IAAI,CAAC,IAAI,EAAE,eAAe,CAAC,CAAC;IACjD,MAAM,SAAS,GAAG,eAAe,CAAC,UAAU,CAAC,CAAC;IAC9C,MAAM,YAAY,CAAC,SAAS,EAAE,UAAU,EAAE,YAAY,CAAC,CAAC;IAExD,IAAI,SAAS,GAAkB,IAAI,CAAC;IACpC,IAAI,OAAO,CAAC,eAAe,EAAE,CAAC;QAC7B,MAAM,QAAQ,GAAG,CAAC,MAAM,YAAY,CAAC,SAAS,CAAC,CAAC,CAAC,MAAM,CAAC,CAAC,OAAO,EAAE,EAAE,CAAC,OAAO,CAAC,GAAG,KAAK,GAAG,CAAC,CAAC;QAC1F,SAAS,GAAG,QAAQ,CAAC,IAAI,CAAC,CAAC,IAAI,EAAE,KAAK,EAAE,EAAE,CAAC,IAAI,CAAC,SAAS,GAAG,KAAK,CAAC,SAAS,CAAC,CAAC,EAAE,CAAC,CAAC,CAAC,CAAC,EAAE,EAAE,IAAI,IAAI,CAAC;IACjG,CAAC;IACD,MAAM,MAAM,GAAG,MAAM,OAAO,CAAC,SAAS,EAAE,SAAS,EAAE,GAAG,CAAC,CAAC;IACxD,IAAI,CAAC;QACJ,MAAM,OAAO,CAAC,MAAM,CAAC,CAAC;IACvB,CAAC;YAAS,CAAC;QACV,MAAM,CAAC,KAAK,EAAE,CAAC;IAChB,CAAC;AAAA,CACD","sourcesContent":["/**\n * The presentation host: find or start the session server, attach to a session, run the view.\n */\n\nimport { spawn } from \"node:child_process\";\nimport { mkdir } from \"node:fs/promises\";\nimport { extname, join } from \"node:path\";\nimport { fileURLToPath } from \"node:url\";\nimport { getAgentDir } from \"../../../config.ts\";\nimport { socketTransport, type Transport } from \"../shared/transport.ts\";\nimport { connect, listSessions } from \"./session.ts\";\nimport { runView } from \"./view.ts\";\n\nconst SELF_EXTENSION = extname(fileURLToPath(import.meta.url));\nconst SERVER_ENTRY = fileURLToPath(new URL(`../server/entry${SELF_EXTENSION}`, import.meta.url));\nconst SERVER_START_TIMEOUT_MS = 10_000;\n\nexport interface TuiOptions {\n\tcwd?: string;\n\tcontinueSession?: boolean;\n}\n\nasync function ensureServer(transport: Transport, socketPath: string, sessionsRoot: string): Promise<void> {\n\ttry {\n\t\t(await transport.connect()).close();\n\t\treturn;\n\t} catch {\n\t\t// No server yet; start one.\n\t}\n\t// execArgv is forwarded so a parent running under a TypeScript loader produces children that do too.\n\tconst child = spawn(process.execPath, [...process.execArgv, SERVER_ENTRY, socketPath, sessionsRoot], {\n\t\tdetached: true,\n\t\tstdio: \"ignore\",\n\t});\n\tchild.unref();\n\tconst deadline = Date.now() + SERVER_START_TIMEOUT_MS;\n\twhile (Date.now() < deadline) {\n\t\ttry {\n\t\t\t(await transport.connect()).close();\n\t\t\treturn;\n\t\t} catch {\n\t\t\tawait new Promise((resolve) => setTimeout(resolve, 50));\n\t\t}\n\t}\n\tthrow new Error(\"Timed out waiting for the mini session server\");\n}\n\nexport async function runTui(options: TuiOptions = {}): Promise<void> {\n\tconst cwd = options.cwd ?? process.cwd();\n\tconst root = join(getAgentDir(), \"experimental\");\n\tawait mkdir(root, { recursive: true });\n\tconst socketPath = join(root, \"mini.sock\");\n\tconst sessionsRoot = join(root, \"mini-sessions\");\n\tconst transport = socketTransport(socketPath);\n\tawait ensureServer(transport, socketPath, sessionsRoot);\n\n\tlet sessionId: string | null = null;\n\tif (options.continueSession) {\n\t\tconst sessions = (await listSessions(transport)).filter((session) => session.cwd === cwd);\n\t\tsessionId = sessions.sort((left, right) => left.createdAt - right.createdAt).at(-1)?.id ?? null;\n\t}\n\tconst client = await connect(transport, sessionId, cwd);\n\ttry {\n\t\tawait runView(client);\n\t} finally {\n\t\tclient.close();\n\t}\n}\n"]}
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The presentation's half of a session: one connection, services by token, one replicated snapshot.
|
|
3
|
+
*
|
|
4
|
+
* `Sessions` is answered by the server and `Lane`/`Models` by the worker, but both are reached
|
|
5
|
+
* through this one peer, so the view never learns which host provides what.
|
|
6
|
+
*
|
|
7
|
+
* Alignment is not this file's problem. `lane.watch()` captures a snapshot in the worker and buffers
|
|
8
|
+
* that subscription's events there; `lane.start()` drains them. Nothing can arrive before this
|
|
9
|
+
* presentation holds the snapshot and knows its subscription id, so there is nothing to buffer here.
|
|
10
|
+
*
|
|
11
|
+
* The fold is the harness's `reduceLaneSnapshot`: a replica must not have a second opinion.
|
|
12
|
+
*/
|
|
13
|
+
import { type AuthEventPayload, type LaneServiceApi, type ModelsServiceApi, type Remote, type SessionSnapshot, type SessionSummary } from "../shared/protocol.ts";
|
|
14
|
+
import type { Transport } from "../shared/transport.ts";
|
|
15
|
+
export interface AttachedSession {
|
|
16
|
+
state(): SessionSnapshot;
|
|
17
|
+
subscribe(listener: () => void): () => void;
|
|
18
|
+
onAuth(handler: (event: AuthEventPayload) => void): void;
|
|
19
|
+
readonly lane: Remote<LaneServiceApi>;
|
|
20
|
+
readonly models: Remote<ModelsServiceApi>;
|
|
21
|
+
close(): void;
|
|
22
|
+
}
|
|
23
|
+
export declare function listSessions(transport: Transport): Promise<SessionSummary[]>;
|
|
24
|
+
/** Attach to `sessionId`, or to a new session when it is null. */
|
|
25
|
+
export declare function connect(transport: Transport, sessionId: string | null, cwd: string): Promise<AttachedSession>;
|
|
26
|
+
//# sourceMappingURL=session.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"session.d.ts","sourceRoot":"","sources":["../../../../src/experimental/mini/tui/session.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;GAWG;AAMH,OAAO,EACN,KAAK,gBAAgB,EAErB,KAAK,cAAc,EAEnB,KAAK,gBAAgB,EACrB,KAAK,MAAM,EACX,KAAK,eAAe,EACpB,KAAK,cAAc,EAEnB,MAAM,uBAAuB,CAAC;AAE/B,OAAO,KAAK,EAAE,SAAS,EAAE,MAAM,wBAAwB,CAAC;AAExD,MAAM,WAAW,eAAe;IAC/B,KAAK,IAAI,eAAe,CAAC;IACzB,SAAS,CAAC,QAAQ,EAAE,MAAM,IAAI,GAAG,MAAM,IAAI,CAAC;IAC5C,MAAM,CAAC,OAAO,EAAE,CAAC,KAAK,EAAE,gBAAgB,KAAK,IAAI,GAAG,IAAI,CAAC;IACzD,QAAQ,CAAC,IAAI,EAAE,MAAM,CAAC,cAAc,CAAC,CAAC;IACtC,QAAQ,CAAC,MAAM,EAAE,MAAM,CAAC,gBAAgB,CAAC,CAAC;IAC1C,KAAK,IAAI,IAAI,CAAC;CACd;AAKD,wBAAsB,YAAY,CAAC,SAAS,EAAE,SAAS,GAAG,OAAO,CAAC,cAAc,EAAE,CAAC,CAOlF;AAED,kEAAkE;AAClE,wBAAsB,OAAO,CAAC,SAAS,EAAE,SAAS,EAAE,SAAS,EAAE,MAAM,GAAG,IAAI,EAAE,GAAG,EAAE,MAAM,GAAG,OAAO,CAAC,eAAe,CAAC,CA+DnH","sourcesContent":["/**\n * The presentation's half of a session: one connection, services by token, one replicated snapshot.\n *\n * `Sessions` is answered by the server and `Lane`/`Models` by the worker, but both are reached\n * through this one peer, so the view never learns which host provides what.\n *\n * Alignment is not this file's problem. `lane.watch()` captures a snapshot in the worker and buffers\n * that subscription's events there; `lane.start()` drains them. Nothing can arrive before this\n * presentation holds the snapshot and knows its subscription id, so there is nothing to buffer here.\n *\n * The fold is the harness's `reduceLaneSnapshot`: a replica must not have a second opinion.\n */\n\nimport { randomUUID } from \"node:crypto\";\nimport type { HarnessEvent } from \"@earendil-works/pi-agent-core\";\n// Narrow entry: the fold is one file, so a presentation never evaluates the harness barrel.\nimport { reduceLaneSnapshot } from \"@earendil-works/pi-agent-core/harness/runtime/reducer\";\nimport {\n\ttype AuthEventPayload,\n\tLane,\n\ttype LaneServiceApi,\n\tModels,\n\ttype ModelsServiceApi,\n\ttype Remote,\n\ttype SessionSnapshot,\n\ttype SessionSummary,\n\tSessions,\n} from \"../shared/protocol.ts\";\nimport { createPeer } from \"../shared/rpc.ts\";\nimport type { Transport } from \"../shared/transport.ts\";\n\nexport interface AttachedSession {\n\tstate(): SessionSnapshot;\n\tsubscribe(listener: () => void): () => void;\n\tonAuth(handler: (event: AuthEventPayload) => void): void;\n\treadonly lane: Remote<LaneServiceApi>;\n\treadonly models: Remote<ModelsServiceApi>;\n\tclose(): void;\n}\n\n/** Attach spawns a worker when none is running; runs and other lane calls stay unbounded. */\nconst ATTACH_TIMEOUT_MS = 60_000;\n\nexport async function listSessions(transport: Transport): Promise<SessionSummary[]> {\n\tconst peer = createPeer(await transport.connect());\n\ttry {\n\t\treturn await peer.use(Sessions).list();\n\t} finally {\n\t\tpeer.close();\n\t}\n}\n\n/** Attach to `sessionId`, or to a new session when it is null. */\nexport async function connect(transport: Transport, sessionId: string | null, cwd: string): Promise<AttachedSession> {\n\tconst peer = createPeer(await transport.connect());\n\tconst lane = peer.use(Lane);\n\t/** This presentation's identity: the server routes our lane events by it. */\n\tconst presentationId = randomUUID();\n\tconst listeners = new Set<() => void>();\n\tconst authHandlers = new Set<(event: AuthEventPayload) => void>();\n\tconst publish = (): void => {\n\t\tfor (const listener of listeners) listener();\n\t};\n\n\tlet snapshot: SessionSnapshot | undefined;\n\tlet subscriptionId: string | undefined;\n\n\tconst fold = (event: HarnessEvent): void => {\n\t\tif (!snapshot) return;\n\t\tif (reduceLaneSnapshot(snapshot.lane, event) === \"rebase\") {\n\t\t\tvoid resubscribe();\n\t\t\treturn;\n\t\t}\n\t\tpublish();\n\t};\n\n\t/** First attach and rebase are the same operation: take a new subscription, drop the old one. */\n\tconst resubscribe = async (): Promise<void> => {\n\t\tconst previous = subscriptionId;\n\t\tconst opened = await lane.watch(presentationId);\n\t\tsnapshot = opened.snapshot;\n\t\tsubscriptionId = opened.subscriptionId;\n\t\tpublish();\n\t\tawait lane.start(opened.subscriptionId);\n\t\tif (previous) void lane.unwatch(previous);\n\t};\n\n\tpeer.on(Lane, (event) => {\n\t\t// Addressed to us by the server; the id check discards a superseded subscription after a rebase.\n\t\tif (event.subscriptionId === subscriptionId) fold(event.event);\n\t});\n\tpeer.on(Models, (event) => {\n\t\tif (event.type !== \"state\") {\n\t\t\t// Login prompts and notices are not state; they drive the dialog.\n\t\t\tfor (const handler of authHandlers) handler(event);\n\t\t\treturn;\n\t\t}\n\t\tif (!snapshot) return;\n\t\tsnapshot = { ...snapshot, models: event.state };\n\t\tpublish();\n\t});\n\n\tawait peer.use(Sessions, { timeoutMs: ATTACH_TIMEOUT_MS }).attach(sessionId, cwd, presentationId);\n\tawait resubscribe();\n\n\treturn {\n\t\tstate: () => snapshot as SessionSnapshot,\n\t\tsubscribe: (listener) => {\n\t\t\tlisteners.add(listener);\n\t\t\treturn () => listeners.delete(listener);\n\t\t},\n\t\tonAuth: (handler) => authHandlers.add(handler),\n\t\tlane,\n\t\tmodels: peer.use(Models),\n\t\tclose: () => peer.close(),\n\t};\n}\n"]}
|
|
@@ -0,0 +1,94 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The presentation's half of a session: one connection, services by token, one replicated snapshot.
|
|
3
|
+
*
|
|
4
|
+
* `Sessions` is answered by the server and `Lane`/`Models` by the worker, but both are reached
|
|
5
|
+
* through this one peer, so the view never learns which host provides what.
|
|
6
|
+
*
|
|
7
|
+
* Alignment is not this file's problem. `lane.watch()` captures a snapshot in the worker and buffers
|
|
8
|
+
* that subscription's events there; `lane.start()` drains them. Nothing can arrive before this
|
|
9
|
+
* presentation holds the snapshot and knows its subscription id, so there is nothing to buffer here.
|
|
10
|
+
*
|
|
11
|
+
* The fold is the harness's `reduceLaneSnapshot`: a replica must not have a second opinion.
|
|
12
|
+
*/
|
|
13
|
+
import { randomUUID } from "node:crypto";
|
|
14
|
+
// Narrow entry: the fold is one file, so a presentation never evaluates the harness barrel.
|
|
15
|
+
import { reduceLaneSnapshot } from "@earendil-works/pi-agent-core/harness/runtime/reducer";
|
|
16
|
+
import { Lane, Models, Sessions, } from "../shared/protocol.js";
|
|
17
|
+
import { createPeer } from "../shared/rpc.js";
|
|
18
|
+
/** Attach spawns a worker when none is running; runs and other lane calls stay unbounded. */
|
|
19
|
+
const ATTACH_TIMEOUT_MS = 60_000;
|
|
20
|
+
export async function listSessions(transport) {
|
|
21
|
+
const peer = createPeer(await transport.connect());
|
|
22
|
+
try {
|
|
23
|
+
return await peer.use(Sessions).list();
|
|
24
|
+
}
|
|
25
|
+
finally {
|
|
26
|
+
peer.close();
|
|
27
|
+
}
|
|
28
|
+
}
|
|
29
|
+
/** Attach to `sessionId`, or to a new session when it is null. */
|
|
30
|
+
export async function connect(transport, sessionId, cwd) {
|
|
31
|
+
const peer = createPeer(await transport.connect());
|
|
32
|
+
const lane = peer.use(Lane);
|
|
33
|
+
/** This presentation's identity: the server routes our lane events by it. */
|
|
34
|
+
const presentationId = randomUUID();
|
|
35
|
+
const listeners = new Set();
|
|
36
|
+
const authHandlers = new Set();
|
|
37
|
+
const publish = () => {
|
|
38
|
+
for (const listener of listeners)
|
|
39
|
+
listener();
|
|
40
|
+
};
|
|
41
|
+
let snapshot;
|
|
42
|
+
let subscriptionId;
|
|
43
|
+
const fold = (event) => {
|
|
44
|
+
if (!snapshot)
|
|
45
|
+
return;
|
|
46
|
+
if (reduceLaneSnapshot(snapshot.lane, event) === "rebase") {
|
|
47
|
+
void resubscribe();
|
|
48
|
+
return;
|
|
49
|
+
}
|
|
50
|
+
publish();
|
|
51
|
+
};
|
|
52
|
+
/** First attach and rebase are the same operation: take a new subscription, drop the old one. */
|
|
53
|
+
const resubscribe = async () => {
|
|
54
|
+
const previous = subscriptionId;
|
|
55
|
+
const opened = await lane.watch(presentationId);
|
|
56
|
+
snapshot = opened.snapshot;
|
|
57
|
+
subscriptionId = opened.subscriptionId;
|
|
58
|
+
publish();
|
|
59
|
+
await lane.start(opened.subscriptionId);
|
|
60
|
+
if (previous)
|
|
61
|
+
void lane.unwatch(previous);
|
|
62
|
+
};
|
|
63
|
+
peer.on(Lane, (event) => {
|
|
64
|
+
// Addressed to us by the server; the id check discards a superseded subscription after a rebase.
|
|
65
|
+
if (event.subscriptionId === subscriptionId)
|
|
66
|
+
fold(event.event);
|
|
67
|
+
});
|
|
68
|
+
peer.on(Models, (event) => {
|
|
69
|
+
if (event.type !== "state") {
|
|
70
|
+
// Login prompts and notices are not state; they drive the dialog.
|
|
71
|
+
for (const handler of authHandlers)
|
|
72
|
+
handler(event);
|
|
73
|
+
return;
|
|
74
|
+
}
|
|
75
|
+
if (!snapshot)
|
|
76
|
+
return;
|
|
77
|
+
snapshot = { ...snapshot, models: event.state };
|
|
78
|
+
publish();
|
|
79
|
+
});
|
|
80
|
+
await peer.use(Sessions, { timeoutMs: ATTACH_TIMEOUT_MS }).attach(sessionId, cwd, presentationId);
|
|
81
|
+
await resubscribe();
|
|
82
|
+
return {
|
|
83
|
+
state: () => snapshot,
|
|
84
|
+
subscribe: (listener) => {
|
|
85
|
+
listeners.add(listener);
|
|
86
|
+
return () => listeners.delete(listener);
|
|
87
|
+
},
|
|
88
|
+
onAuth: (handler) => authHandlers.add(handler),
|
|
89
|
+
lane,
|
|
90
|
+
models: peer.use(Models),
|
|
91
|
+
close: () => peer.close(),
|
|
92
|
+
};
|
|
93
|
+
}
|
|
94
|
+
//# sourceMappingURL=session.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"session.js","sourceRoot":"","sources":["../../../../src/experimental/mini/tui/session.ts"],"names":[],"mappings":"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","sourcesContent":["/**\n * The presentation's half of a session: one connection, services by token, one replicated snapshot.\n *\n * `Sessions` is answered by the server and `Lane`/`Models` by the worker, but both are reached\n * through this one peer, so the view never learns which host provides what.\n *\n * Alignment is not this file's problem. `lane.watch()` captures a snapshot in the worker and buffers\n * that subscription's events there; `lane.start()` drains them. Nothing can arrive before this\n * presentation holds the snapshot and knows its subscription id, so there is nothing to buffer here.\n *\n * The fold is the harness's `reduceLaneSnapshot`: a replica must not have a second opinion.\n */\n\nimport { randomUUID } from \"node:crypto\";\nimport type { HarnessEvent } from \"@earendil-works/pi-agent-core\";\n// Narrow entry: the fold is one file, so a presentation never evaluates the harness barrel.\nimport { reduceLaneSnapshot } from \"@earendil-works/pi-agent-core/harness/runtime/reducer\";\nimport {\n\ttype AuthEventPayload,\n\tLane,\n\ttype LaneServiceApi,\n\tModels,\n\ttype ModelsServiceApi,\n\ttype Remote,\n\ttype SessionSnapshot,\n\ttype SessionSummary,\n\tSessions,\n} from \"../shared/protocol.ts\";\nimport { createPeer } from \"../shared/rpc.ts\";\nimport type { Transport } from \"../shared/transport.ts\";\n\nexport interface AttachedSession {\n\tstate(): SessionSnapshot;\n\tsubscribe(listener: () => void): () => void;\n\tonAuth(handler: (event: AuthEventPayload) => void): void;\n\treadonly lane: Remote<LaneServiceApi>;\n\treadonly models: Remote<ModelsServiceApi>;\n\tclose(): void;\n}\n\n/** Attach spawns a worker when none is running; runs and other lane calls stay unbounded. */\nconst ATTACH_TIMEOUT_MS = 60_000;\n\nexport async function listSessions(transport: Transport): Promise<SessionSummary[]> {\n\tconst peer = createPeer(await transport.connect());\n\ttry {\n\t\treturn await peer.use(Sessions).list();\n\t} finally {\n\t\tpeer.close();\n\t}\n}\n\n/** Attach to `sessionId`, or to a new session when it is null. */\nexport async function connect(transport: Transport, sessionId: string | null, cwd: string): Promise<AttachedSession> {\n\tconst peer = createPeer(await transport.connect());\n\tconst lane = peer.use(Lane);\n\t/** This presentation's identity: the server routes our lane events by it. */\n\tconst presentationId = randomUUID();\n\tconst listeners = new Set<() => void>();\n\tconst authHandlers = new Set<(event: AuthEventPayload) => void>();\n\tconst publish = (): void => {\n\t\tfor (const listener of listeners) listener();\n\t};\n\n\tlet snapshot: SessionSnapshot | undefined;\n\tlet subscriptionId: string | undefined;\n\n\tconst fold = (event: HarnessEvent): void => {\n\t\tif (!snapshot) return;\n\t\tif (reduceLaneSnapshot(snapshot.lane, event) === \"rebase\") {\n\t\t\tvoid resubscribe();\n\t\t\treturn;\n\t\t}\n\t\tpublish();\n\t};\n\n\t/** First attach and rebase are the same operation: take a new subscription, drop the old one. */\n\tconst resubscribe = async (): Promise<void> => {\n\t\tconst previous = subscriptionId;\n\t\tconst opened = await lane.watch(presentationId);\n\t\tsnapshot = opened.snapshot;\n\t\tsubscriptionId = opened.subscriptionId;\n\t\tpublish();\n\t\tawait lane.start(opened.subscriptionId);\n\t\tif (previous) void lane.unwatch(previous);\n\t};\n\n\tpeer.on(Lane, (event) => {\n\t\t// Addressed to us by the server; the id check discards a superseded subscription after a rebase.\n\t\tif (event.subscriptionId === subscriptionId) fold(event.event);\n\t});\n\tpeer.on(Models, (event) => {\n\t\tif (event.type !== \"state\") {\n\t\t\t// Login prompts and notices are not state; they drive the dialog.\n\t\t\tfor (const handler of authHandlers) handler(event);\n\t\t\treturn;\n\t\t}\n\t\tif (!snapshot) return;\n\t\tsnapshot = { ...snapshot, models: event.state };\n\t\tpublish();\n\t});\n\n\tawait peer.use(Sessions, { timeoutMs: ATTACH_TIMEOUT_MS }).attach(sessionId, cwd, presentationId);\n\tawait resubscribe();\n\n\treturn {\n\t\tstate: () => snapshot as SessionSnapshot,\n\t\tsubscribe: (listener) => {\n\t\t\tlisteners.add(listener);\n\t\t\treturn () => listeners.delete(listener);\n\t\t},\n\t\tonAuth: (handler) => authHandlers.add(handler),\n\t\tlane,\n\t\tmodels: peer.use(Models),\n\t\tclose: () => peer.close(),\n\t};\n}\n"]}
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The view. It holds no live objects: no harness, lane, session, or model runtime.
|
|
3
|
+
*
|
|
4
|
+
* Everything it renders comes from a replicated `SessionView` snapshot, and everything it does is a
|
|
5
|
+
* command that answers with data. Whether that view is in-process or a socket away is invisible here.
|
|
6
|
+
*/
|
|
7
|
+
import type { AttachedSession } from "./session.ts";
|
|
8
|
+
/** Run the view against one attached session until the user exits. */
|
|
9
|
+
export declare function runView(client: AttachedSession): Promise<void>;
|
|
10
|
+
//# sourceMappingURL=view.d.ts.map
|