@spencerboucher/switchyard 0.0.0-stage → 0.3.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/LICENSE +21 -0
- package/README.md +161 -2
- package/dist/bridge/switchyard-bridge.js +105 -0
- package/dist/switchyard.mjs +18207 -0
- package/package.json +52 -4
- package/web-dist/apple-touch-icon.png +0 -0
- package/web-dist/assets/AutomationView-Bt_v8JDx.js +2 -0
- package/web-dist/assets/AutomationView-Bt_v8JDx.js.map +1 -0
- package/web-dist/assets/InteractionCard-BRI9KgrO.js +4 -0
- package/web-dist/assets/InteractionCard-BRI9KgrO.js.map +1 -0
- package/web-dist/assets/Markdown-jXkvZeRp.js +31 -0
- package/web-dist/assets/Markdown-jXkvZeRp.js.map +1 -0
- package/web-dist/assets/Menu-Cqil5qVF.js +2 -0
- package/web-dist/assets/Menu-Cqil5qVF.js.map +1 -0
- package/web-dist/assets/Overlay-B9h5oAe6.js +2 -0
- package/web-dist/assets/Overlay-B9h5oAe6.js.map +1 -0
- package/web-dist/assets/SegmentedControl-ChdVilXb.js +2 -0
- package/web-dist/assets/SegmentedControl-ChdVilXb.js.map +1 -0
- package/web-dist/assets/TextArea-BsQHOQMe.js +2 -0
- package/web-dist/assets/TextArea-BsQHOQMe.js.map +1 -0
- package/web-dist/assets/arrow-left-C6YWM6y6.js +2 -0
- package/web-dist/assets/arrow-left-C6YWM6y6.js.map +1 -0
- package/web-dist/assets/attention-VkuGuoSf.js +2 -0
- package/web-dist/assets/attention-VkuGuoSf.js.map +1 -0
- package/web-dist/assets/automations-BY88rXFv.js +2 -0
- package/web-dist/assets/automations-BY88rXFv.js.map +1 -0
- package/web-dist/assets/board-OzxtnPgh.js +10 -0
- package/web-dist/assets/board-OzxtnPgh.js.map +1 -0
- package/web-dist/assets/calendar-clock-B8zE4JsX.js +2 -0
- package/web-dist/assets/calendar-clock-B8zE4JsX.js.map +1 -0
- package/web-dist/assets/chevron-right-evVLS6hq.js +2 -0
- package/web-dist/assets/chevron-right-evVLS6hq.js.map +1 -0
- package/web-dist/assets/code-BR4PKt8F.js +8 -0
- package/web-dist/assets/code-BR4PKt8F.js.map +1 -0
- package/web-dist/assets/copy-oWgGHmkS.js +2 -0
- package/web-dist/assets/copy-oWgGHmkS.js.map +1 -0
- package/web-dist/assets/describe-CRXLn3O6.js +5 -0
- package/web-dist/assets/describe-CRXLn3O6.js.map +1 -0
- package/web-dist/assets/dist-C4YmDHPG.js +56 -0
- package/web-dist/assets/dist-C4YmDHPG.js.map +1 -0
- package/web-dist/assets/enroll-C9-fBIKO.js +2 -0
- package/web-dist/assets/enroll-C9-fBIKO.js.map +1 -0
- package/web-dist/assets/external-link-3jjTxo92.js +2 -0
- package/web-dist/assets/external-link-3jjTxo92.js.map +1 -0
- package/web-dist/assets/hand-UPoHamrn.js +2 -0
- package/web-dist/assets/hand-UPoHamrn.js.map +1 -0
- package/web-dist/assets/index-B11DJFHW.js +11 -0
- package/web-dist/assets/index-B11DJFHW.js.map +1 -0
- package/web-dist/assets/index-DGGOgcLZ.css +2 -0
- package/web-dist/assets/jsx-runtime-D13aM9HY.js +2 -0
- package/web-dist/assets/jsx-runtime-D13aM9HY.js.map +1 -0
- package/web-dist/assets/key-round-VDbE9E4X.js +2 -0
- package/web-dist/assets/key-round-VDbE9E4X.js.map +1 -0
- package/web-dist/assets/link-2-CaMlUDRG.js +2 -0
- package/web-dist/assets/link-2-CaMlUDRG.js.map +1 -0
- package/web-dist/assets/logic-D_Eq9DMW.js +2 -0
- package/web-dist/assets/logic-D_Eq9DMW.js.map +1 -0
- package/web-dist/assets/more-BUvq82hI.js +2 -0
- package/web-dist/assets/more-BUvq82hI.js.map +1 -0
- package/web-dist/assets/now-DR2wt1DG.js +2 -0
- package/web-dist/assets/now-DR2wt1DG.js.map +1 -0
- package/web-dist/assets/pencil-DI8QbCN8.js +2 -0
- package/web-dist/assets/pencil-DI8QbCN8.js.map +1 -0
- package/web-dist/assets/play-CjoDg3ws.js +2 -0
- package/web-dist/assets/play-CjoDg3ws.js.map +1 -0
- package/web-dist/assets/plus-BLP-wIYD.js +2 -0
- package/web-dist/assets/plus-BLP-wIYD.js.map +1 -0
- package/web-dist/assets/presence-05bJD5G3.js +2 -0
- package/web-dist/assets/presence-05bJD5G3.js.map +1 -0
- package/web-dist/assets/save-DEvZPWlS.js +2 -0
- package/web-dist/assets/save-DEvZPWlS.js.map +1 -0
- package/web-dist/assets/search-C6cn5WOo.js +2 -0
- package/web-dist/assets/search-C6cn5WOo.js.map +1 -0
- package/web-dist/assets/send-CFwekPNi.js +2 -0
- package/web-dist/assets/send-CFwekPNi.js.map +1 -0
- package/web-dist/assets/server-SxFxIScj.js +2 -0
- package/web-dist/assets/server-SxFxIScj.js.map +1 -0
- package/web-dist/assets/settings-C4tDpSCU.js +7 -0
- package/web-dist/assets/settings-C4tDpSCU.js.map +1 -0
- package/web-dist/assets/states-DrTQuYXw.js +2 -0
- package/web-dist/assets/states-DrTQuYXw.js.map +1 -0
- package/web-dist/assets/trash-C6X2OLrI.js +2 -0
- package/web-dist/assets/trash-C6X2OLrI.js.map +1 -0
- package/web-dist/assets/useNow-ChB9z-N6.js +2 -0
- package/web-dist/assets/useNow-ChB9z-N6.js.map +1 -0
- package/web-dist/icon-192.png +0 -0
- package/web-dist/icon-512.png +0 -0
- package/web-dist/icon.svg +12 -0
- package/web-dist/index.html +25 -0
- package/web-dist/manifest.webmanifest +1 -0
- package/web-dist/maskable-512.png +0 -0
- package/web-dist/registerSW.js +1 -0
- package/web-dist/sw.js +2 -0
- package/web-dist/sw.js.map +1 -0
package/LICENSE
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026 Spencer Boucher
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
package/README.md
CHANGED
|
@@ -1,3 +1,162 @@
|
|
|
1
|
-
#
|
|
1
|
+
# Switchyard
|
|
2
2
|
|
|
3
|
-
|
|
3
|
+
**Run [Pi](https://github.com/earendil-works/pi) coding agents on your own machines, and drive them from your phone, your browser or your terminal.**
|
|
4
|
+
|
|
5
|
+
A coding agent has to run where your repositories, toolchains and logins are: your laptop, a workstation, a home server. You, on the other hand, are not always sitting in front of that machine. Switchyard keeps the agents where the work is and lets you follow along from anywhere: start a session from your phone, answer the question it asks while you are on your laptop, take over from a terminal, and get a notification when the result is ready for review. Every client looks at the same live session, and closing one never stops the work.
|
|
6
|
+
|
|
7
|
+
On top of interactive sessions it adds tasks, a board, scheduled automations and a review flow, so an agent can work through a backlog while you are away and hand everything back for you to accept.
|
|
8
|
+
|
|
9
|
+
## How it fits together
|
|
10
|
+
|
|
11
|
+
```mermaid
|
|
12
|
+
flowchart TB
|
|
13
|
+
subgraph clients["Clients: windows onto the same work"]
|
|
14
|
+
phone["Phone<br/>(installable web app)"]
|
|
15
|
+
browser["Desktop browser"]
|
|
16
|
+
term["Terminal client and CLI"]
|
|
17
|
+
end
|
|
18
|
+
|
|
19
|
+
coord["<b>Coordinator</b><br/>tasks, schedules, notifications,<br/>sign-in, sync<br/><i>never runs code</i>"]
|
|
20
|
+
|
|
21
|
+
subgraph hostA["Host: your laptop"]
|
|
22
|
+
agentA["Host agent"] --> piA["Pi sessions"]
|
|
23
|
+
piA --> repoA[("Repositories,<br/>worktrees, logins")]
|
|
24
|
+
end
|
|
25
|
+
|
|
26
|
+
subgraph hostB["Host: your server"]
|
|
27
|
+
agentB["Host agent"] --> piB["Pi sessions"]
|
|
28
|
+
piB --> repoB[("Repositories,<br/>worktrees, logins")]
|
|
29
|
+
end
|
|
30
|
+
|
|
31
|
+
phone & browser & term <-->|"HTTPS + WebSocket"| coord
|
|
32
|
+
agentA -->|"dials out"| coord
|
|
33
|
+
agentB -->|"dials out"| coord
|
|
34
|
+
```
|
|
35
|
+
|
|
36
|
+
There are three kinds of parts:
|
|
37
|
+
|
|
38
|
+
- **The coordinator** is the one always-on service. It keeps tasks, schedules, notifications, devices and the shared view of every session, and it decides what runs where. It never runs an agent and never holds your provider credentials, so it can live anywhere: a container, a small server, or your laptop.
|
|
39
|
+
- **Hosts** are your machines. A host agent runs Pi as your OS user, with your repositories, worktrees, Pi packages and model logins, and connects *outbound* to the coordinator. A host needs no open ports, and a laptop can come and go: its sessions keep running while the coordinator is down, and the events catch up when it returns.
|
|
40
|
+
- **Clients** are views. The web app works on desktop and as an installed app on a phone; the terminal client and the scriptable CLI work over the same API. Any client can watch, answer and steer a session; none of them owns it.
|
|
41
|
+
|
|
42
|
+
For a single machine, `switchyard up` runs the coordinator and a host in one process. For more, run the coordinator as a server and enroll each machine as a host. The protocol is the same either way.
|
|
43
|
+
|
|
44
|
+
## A piece of work, start to finish
|
|
45
|
+
|
|
46
|
+
```mermaid
|
|
47
|
+
sequenceDiagram
|
|
48
|
+
actor You
|
|
49
|
+
participant Phone
|
|
50
|
+
participant Laptop as Laptop browser
|
|
51
|
+
participant Coord as Coordinator
|
|
52
|
+
participant Host as Host (Pi)
|
|
53
|
+
|
|
54
|
+
You->>Phone: Create a task, make it ready
|
|
55
|
+
Phone->>Coord: Task: Ready
|
|
56
|
+
Coord->>Host: Dispatch (once, even across restarts)
|
|
57
|
+
Host->>Host: Create a git worktree, start Pi
|
|
58
|
+
Host-->>Coord: A plugin asks a question
|
|
59
|
+
Coord-->>Phone: Notification: needs input
|
|
60
|
+
You->>Laptop: Answer
|
|
61
|
+
Laptop->>Coord: Answer
|
|
62
|
+
Coord->>Host: Deliver the answer to Pi
|
|
63
|
+
Host-->>Coord: Turn finished, checks pass
|
|
64
|
+
Coord-->>Phone: Ready for review
|
|
65
|
+
You->>Phone: Review the diff, accept
|
|
66
|
+
```
|
|
67
|
+
|
|
68
|
+
1. **You describe the work.** A task has an objective, acceptance criteria, dependencies and a priority. Moving it to Ready lets Switchyard start it.
|
|
69
|
+
2. **Switchyard starts it on a host.** The coordinator picks an eligible host, the host creates a git worktree for the task inside the repository (`<repo>/.worktrees/<branch>` by default), and Pi starts there with your normal extensions. A launch is recorded before it is sent, so a lost connection or a coordinator restart never starts it twice.
|
|
70
|
+
3. **The agent asks, you answer anywhere.** Questions and approvals from Pi and its plugins appear as cards in every client and as a notification on the device you are most likely to see. Answer once; every client shows the result.
|
|
71
|
+
4. **You can step in.** Join from any client to watch and send messages, steer the current turn or queue a follow-up. *Take over* pauses automation (no automatic continuations or retries) until you *return it*. *Stop* aborts the run. Detaching never stops anything.
|
|
72
|
+
5. **It ends in Review, not in Done.** When the agent finishes, your configured checks run (tests, linters). If they pass, the task waits in Review with its diff; you can comment line by line, send all comments as one follow-up, request changes or accept. Accepting never merges or pushes for you.
|
|
73
|
+
6. **Background work uses the same path.** An automation can open a task on a schedule, from a webhook or when another task finishes, then continue it, append to it, or report only when something changed.
|
|
74
|
+
|
|
75
|
+
## Features
|
|
76
|
+
|
|
77
|
+
| Area | What you get |
|
|
78
|
+
|---|---|
|
|
79
|
+
| **Sessions** | Persistent Pi conversations on any host; the same live session in every client; steer or queue follow-ups; take over and return to automation; resume after crashes with the same conversation (never a silent new one); import existing Pi sessions |
|
|
80
|
+
| **Workspaces** | A git worktree per task or session inside the repository, discovery of worktrees you made yourself, setup commands, and removal that refuses to lose uncommitted or unmerged work |
|
|
81
|
+
| **Tasks and board** | Backlog, Ready, In progress, Review, Done; dependencies with cycle detection; priorities; per-project concurrency limits; a Now view explaining why queued work waits |
|
|
82
|
+
| **Review** | Changed files and diffs on desktop and phone, line comments sent as one follow-up, request changes, accept |
|
|
83
|
+
| **Bounded continuation** | Optional: keep the agent working toward an objective, within a maximum number of continuations, a time limit and token or cost caps, ending in your checks and your review |
|
|
84
|
+
| **Automations** | Cron (time-zone aware), one-time, webhook and dependency triggers; fresh, continue or watch modes; skip, queue or append when work overlaps; skip or catch up missed runs; a preview of the resulting task |
|
|
85
|
+
| **Plugins** | Pi dialogs (select, confirm, input, editor) and extension status and widgets shown in every client; adapters turn specific plugins into structured forms, cards and panels, and plugin packages can ship their own. Terminal-only plugin UI is reported honestly instead of silently dropped |
|
|
86
|
+
| **Attention and notifications** | One queue of what needs you (input, failures, reviews); Web Push routed to the device you are using, quiet hours, private previews, one notification per session |
|
|
87
|
+
| **Sign-in and devices** | Pair devices by QR code, sign in with passkeys or any OpenID Connect provider; every device has its own revocable credential |
|
|
88
|
+
| **Operations** | One container image, SQLite with migrations, backup and restore, health checks, diagnostics, `switchyard doctor` |
|
|
89
|
+
|
|
90
|
+
## Words used everywhere
|
|
91
|
+
|
|
92
|
+
| Term | Meaning |
|
|
93
|
+
|---|---|
|
|
94
|
+
| **Host** | A machine that runs Pi for Switchyard, through its host agent |
|
|
95
|
+
| **Project** | A repository Switchyard knows about; a project can have a checkout on several hosts |
|
|
96
|
+
| **Workspace** | A working directory for a project: the main checkout or a git worktree |
|
|
97
|
+
| **Session** | One persistent Pi conversation in a workspace on a host |
|
|
98
|
+
| **Task** | A unit of work on the board, with an objective and acceptance criteria |
|
|
99
|
+
| **Run** | One attempt at a task, carried out in a session |
|
|
100
|
+
| **Device** | A signed-in client: a browser, an installed web app or a terminal |
|
|
101
|
+
| **Adapter** | Code that renders a specific Pi plugin's questions and state as structured UI |
|
|
102
|
+
|
|
103
|
+
## Quick start
|
|
104
|
+
|
|
105
|
+
**Try it on one machine** (Node 24 or newer, git, and Pi 1.4 or 1.5 on your PATH):
|
|
106
|
+
|
|
107
|
+
```sh
|
|
108
|
+
npm install -g @spencerboucher/switchyard
|
|
109
|
+
switchyard up
|
|
110
|
+
```
|
|
111
|
+
|
|
112
|
+
`up` runs the coordinator and a host in one process at `http://localhost:7400`, sets the server up on first start and opens a single-use sign-in link in your browser. Add a project in Settings › Projects (a path to a git repository), then start a session or create a task.
|
|
113
|
+
|
|
114
|
+
**Run it as a server** with the container image (it runs only the coordinator):
|
|
115
|
+
|
|
116
|
+
```sh
|
|
117
|
+
docker run -d --name switchyard -p 127.0.0.1:7400:7400 -v switchyard-data:/data \
|
|
118
|
+
-e SWITCHYARD_PUBLIC_URL=https://switchyard.example.com \
|
|
119
|
+
ghcr.io/justmytwospence/switchyard:0.3.0
|
|
120
|
+
docker exec switchyard switchyard init # prints a single-use sign-in link for your first device
|
|
121
|
+
```
|
|
122
|
+
|
|
123
|
+
Put HTTPS in front of it (a reverse proxy, or a VPN or tailnet; the installed phone app, notifications and passkeys need HTTPS). Then, on each machine that should run agents, create a host token in Settings › Hosts › Add host and run:
|
|
124
|
+
|
|
125
|
+
```sh
|
|
126
|
+
npm install -g @spencerboucher/switchyard
|
|
127
|
+
switchyard host enroll --coordinator https://switchyard.example.com --token <token>
|
|
128
|
+
switchyard host install-service # launchd on macOS, a systemd user service on Linux
|
|
129
|
+
```
|
|
130
|
+
|
|
131
|
+
Settings › Hosts › Add host shows these three commands with your token filled in. To build from source instead, see [docs/development.md](docs/development.md).
|
|
132
|
+
|
|
133
|
+
[docs/deployment.md](docs/deployment.md) has recipes for Caddy, Traefik, nginx and Tailscale, a compose file, and every setting.
|
|
134
|
+
|
|
135
|
+
## Security model
|
|
136
|
+
|
|
137
|
+
- **A server is owned before it serves anything.** Ownership comes from `switchyard init` on the server, or from an owner declared in an OpenID Connect configuration. There is never an "unclaimed" web page.
|
|
138
|
+
- **Only you add devices.** A new device gets in by a pairing link or QR code made from a device you already use, by a passkey, or by single sign-on that matches the configured owner. Each device has its own credential that you can revoke; signing out of an identity provider never breaks an installed app.
|
|
139
|
+
- **Hosts dial out** with their own revocable credentials; the coordinator holds no provider credentials and needs no access to your home directory or Docker socket.
|
|
140
|
+
- **Agents run with the host user's privileges.** Running the coordinator in a container does not sandbox the agents; run hosts as a user whose access you are comfortable giving an agent.
|
|
141
|
+
|
|
142
|
+
Details: [docs/auth.md](docs/auth.md) and the [privilege model](docs/deployment.md#privilege-model).
|
|
143
|
+
|
|
144
|
+
## Status
|
|
145
|
+
|
|
146
|
+
License: [MIT](LICENSE).
|
|
147
|
+
|
|
148
|
+
|
|
149
|
+
Switchyard is young and moving quickly. It is tested against Pi `>=1.4.0 <1.6.0` (see [docs/compatibility.md](docs/compatibility.md) for supported and unsupported Pi capabilities) and runs on macOS and Linux hosts. What has and has not been verified is recorded in [docs/acceptance-report.md](docs/acceptance-report.md).
|
|
150
|
+
|
|
151
|
+
## Documentation
|
|
152
|
+
|
|
153
|
+
| Guide | Covers |
|
|
154
|
+
|---|---|
|
|
155
|
+
| [Deployment](docs/deployment.md) | Single machine, server, reverse proxies, VPNs, hosts, environment reference, privilege model |
|
|
156
|
+
| [Sign-in and devices](docs/auth.md) | The owner model, pairing, passkeys, single sign-on, host enrollment, revocation |
|
|
157
|
+
| [CLI and terminal client](docs/cli.md) | Every `switchyard` command, credentials, terminal key bindings |
|
|
158
|
+
| [Operations](docs/operations.md) | Backup, restore, migrations, retention, logs, health, diagnostics |
|
|
159
|
+
| [Adapters](docs/adapters.md) | Rendering a Pi plugin's UI in every client; shipping an adapter with your Pi package |
|
|
160
|
+
| [Architecture](docs/architecture.md) | Components, data ownership, the host and client protocols, packages |
|
|
161
|
+
| [Development](docs/development.md) | Building, testing (including against real Pi), contributing |
|
|
162
|
+
| [Specification](docs/spec.md) | The product specification Switchyard is built to |
|
|
@@ -0,0 +1,105 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Switchyard bridge extension, loaded into every managed Pi with `-e`.
|
|
3
|
+
*
|
|
4
|
+
* Forwards `ui_prompt_start` / `ui_prompt_end` (kinds select|confirm|input|editor|custom) as JSON
|
|
5
|
+
* lines over the Unix socket named by `SWITCHYARD_BRIDGE_SOCKET`. This reveals terminal-only
|
|
6
|
+
* `ctx.ui.custom()` calls (which RPC mode answers with `undefined`) and dialogs that Pi resolved
|
|
7
|
+
* itself (timeout or abort). It imports only Node built-ins, is a no-op without the variable, and
|
|
8
|
+
* never throws: any socket error disables forwarding for the rest of the process.
|
|
9
|
+
*/
|
|
10
|
+
import { createConnection } from "node:net";
|
|
11
|
+
export default function switchyardBridge(pi) {
|
|
12
|
+
const socketPath = process.env.SWITCHYARD_BRIDGE_SOCKET;
|
|
13
|
+
if (!socketPath)
|
|
14
|
+
return;
|
|
15
|
+
let socket = null;
|
|
16
|
+
let disabled = false;
|
|
17
|
+
let seq = 0;
|
|
18
|
+
const ensureSocket = () => {
|
|
19
|
+
if (disabled)
|
|
20
|
+
return null;
|
|
21
|
+
if (socket)
|
|
22
|
+
return socket;
|
|
23
|
+
try {
|
|
24
|
+
const s = createConnection(socketPath);
|
|
25
|
+
s.on("error", () => {
|
|
26
|
+
disabled = true;
|
|
27
|
+
try {
|
|
28
|
+
s.destroy();
|
|
29
|
+
}
|
|
30
|
+
catch {
|
|
31
|
+
// ignore
|
|
32
|
+
}
|
|
33
|
+
});
|
|
34
|
+
s.on("close", () => {
|
|
35
|
+
// A clean close (session_shutdown) allows a later reconnect; errors disabled us above.
|
|
36
|
+
if (socket === s)
|
|
37
|
+
socket = null;
|
|
38
|
+
});
|
|
39
|
+
s.unref();
|
|
40
|
+
socket = s;
|
|
41
|
+
send(s, { type: "hello" });
|
|
42
|
+
return s;
|
|
43
|
+
}
|
|
44
|
+
catch {
|
|
45
|
+
disabled = true;
|
|
46
|
+
return null;
|
|
47
|
+
}
|
|
48
|
+
};
|
|
49
|
+
const send = (s, msg) => {
|
|
50
|
+
const full = {
|
|
51
|
+
type: msg.type,
|
|
52
|
+
...(msg.kind !== undefined ? { kind: msg.kind } : {}),
|
|
53
|
+
title: msg.title ?? null,
|
|
54
|
+
seq: ++seq,
|
|
55
|
+
at: new Date().toISOString(),
|
|
56
|
+
pid: process.pid,
|
|
57
|
+
};
|
|
58
|
+
try {
|
|
59
|
+
s.write(`${JSON.stringify(full)}\n`);
|
|
60
|
+
}
|
|
61
|
+
catch {
|
|
62
|
+
disabled = true;
|
|
63
|
+
}
|
|
64
|
+
};
|
|
65
|
+
const forward = (event) => {
|
|
66
|
+
try {
|
|
67
|
+
const s = ensureSocket();
|
|
68
|
+
if (!s)
|
|
69
|
+
return;
|
|
70
|
+
send(s, {
|
|
71
|
+
type: event?.type === "ui_prompt_end" ? "ui_prompt_end" : "ui_prompt_start",
|
|
72
|
+
kind: typeof event?.kind === "string" ? event.kind : "unknown",
|
|
73
|
+
title: typeof event?.title === "string" ? event.title : undefined,
|
|
74
|
+
});
|
|
75
|
+
}
|
|
76
|
+
catch {
|
|
77
|
+
disabled = true;
|
|
78
|
+
}
|
|
79
|
+
};
|
|
80
|
+
try {
|
|
81
|
+
pi.on("session_start", () => {
|
|
82
|
+
try {
|
|
83
|
+
ensureSocket();
|
|
84
|
+
}
|
|
85
|
+
catch {
|
|
86
|
+
disabled = true;
|
|
87
|
+
}
|
|
88
|
+
});
|
|
89
|
+
pi.on("ui_prompt_start", forward);
|
|
90
|
+
pi.on("ui_prompt_end", forward);
|
|
91
|
+
pi.on("session_shutdown", () => {
|
|
92
|
+
try {
|
|
93
|
+
socket?.end();
|
|
94
|
+
}
|
|
95
|
+
catch {
|
|
96
|
+
// ignore
|
|
97
|
+
}
|
|
98
|
+
socket = null;
|
|
99
|
+
});
|
|
100
|
+
}
|
|
101
|
+
catch {
|
|
102
|
+
disabled = true;
|
|
103
|
+
}
|
|
104
|
+
}
|
|
105
|
+
//# sourceMappingURL=switchyard-bridge.js.map
|