remote-pi 0.1.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.
Files changed (79) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +384 -0
  3. package/dist/config.d.ts +18 -0
  4. package/dist/config.js +51 -0
  5. package/dist/config.js.map +1 -0
  6. package/dist/index.d.ts +77 -0
  7. package/dist/index.js +1285 -0
  8. package/dist/index.js.map +1 -0
  9. package/dist/pairing/crypto.d.ts +8 -0
  10. package/dist/pairing/crypto.js +22 -0
  11. package/dist/pairing/crypto.js.map +1 -0
  12. package/dist/pairing/handshake.d.ts +28 -0
  13. package/dist/pairing/handshake.js +113 -0
  14. package/dist/pairing/handshake.js.map +1 -0
  15. package/dist/pairing/noise-sha256.d.ts +16 -0
  16. package/dist/pairing/noise-sha256.js +103 -0
  17. package/dist/pairing/noise-sha256.js.map +1 -0
  18. package/dist/pairing/qr.d.ts +41 -0
  19. package/dist/pairing/qr.js +96 -0
  20. package/dist/pairing/qr.js.map +1 -0
  21. package/dist/pairing/storage.d.ts +10 -0
  22. package/dist/pairing/storage.js +65 -0
  23. package/dist/pairing/storage.js.map +1 -0
  24. package/dist/protocol/codec.d.ts +7 -0
  25. package/dist/protocol/codec.js +46 -0
  26. package/dist/protocol/codec.js.map +1 -0
  27. package/dist/protocol/types.d.ts +119 -0
  28. package/dist/protocol/types.js +2 -0
  29. package/dist/protocol/types.js.map +1 -0
  30. package/dist/rooms.d.ts +9 -0
  31. package/dist/rooms.js +22 -0
  32. package/dist/rooms.js.map +1 -0
  33. package/dist/session/agent_bridge.d.ts +55 -0
  34. package/dist/session/agent_bridge.js +146 -0
  35. package/dist/session/agent_bridge.js.map +1 -0
  36. package/dist/session/broker.d.ts +37 -0
  37. package/dist/session/broker.js +206 -0
  38. package/dist/session/broker.js.map +1 -0
  39. package/dist/session/envelope.d.ts +24 -0
  40. package/dist/session/envelope.js +89 -0
  41. package/dist/session/envelope.js.map +1 -0
  42. package/dist/session/global_config.d.ts +14 -0
  43. package/dist/session/global_config.js +51 -0
  44. package/dist/session/global_config.js.map +1 -0
  45. package/dist/session/leader_election.d.ts +16 -0
  46. package/dist/session/leader_election.js +78 -0
  47. package/dist/session/leader_election.js.map +1 -0
  48. package/dist/session/local_config.d.ts +18 -0
  49. package/dist/session/local_config.js +47 -0
  50. package/dist/session/local_config.js.map +1 -0
  51. package/dist/session/peer.d.ts +80 -0
  52. package/dist/session/peer.js +268 -0
  53. package/dist/session/peer.js.map +1 -0
  54. package/dist/session/setup_wizard.d.ts +32 -0
  55. package/dist/session/setup_wizard.js +60 -0
  56. package/dist/session/setup_wizard.js.map +1 -0
  57. package/dist/session/tool_gate.d.ts +5 -0
  58. package/dist/session/tool_gate.js +11 -0
  59. package/dist/session/tool_gate.js.map +1 -0
  60. package/dist/session/tools.d.ts +16 -0
  61. package/dist/session/tools.js +123 -0
  62. package/dist/session/tools.js.map +1 -0
  63. package/dist/session/wizard.d.ts +13 -0
  64. package/dist/session/wizard.js +20 -0
  65. package/dist/session/wizard.js.map +1 -0
  66. package/dist/settings.d.ts +15 -0
  67. package/dist/settings.js +52 -0
  68. package/dist/settings.js.map +1 -0
  69. package/dist/transport/peer_channel.d.ts +37 -0
  70. package/dist/transport/peer_channel.js +85 -0
  71. package/dist/transport/peer_channel.js.map +1 -0
  72. package/dist/transport/relay_client.d.ts +81 -0
  73. package/dist/transport/relay_client.js +154 -0
  74. package/dist/transport/relay_client.js.map +1 -0
  75. package/dist/ui/footer.d.ts +32 -0
  76. package/dist/ui/footer.js +32 -0
  77. package/dist/ui/footer.js.map +1 -0
  78. package/package.json +77 -0
  79. package/skills/agent-network/SKILL.md +429 -0
package/LICENSE ADDED
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2026 Jacob Moura
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 ADDED
@@ -0,0 +1,384 @@
1
+ # Remote Pi
2
+
3
+ > Extend the [Pi coding agent](https://github.com/earendil-works/pi) with two
4
+ > superpowers: agents that talk to each other on the same machine, and a mobile
5
+ > app that drives Pi from your phone.
6
+
7
+ `/remote-pi` is a single slash command that wires both at once. Run it; the
8
+ first time it asks a couple of questions and you are done.
9
+
10
+ ---
11
+
12
+ ## Quick start
13
+
14
+ Install the extension (one-time):
15
+
16
+ ```bash
17
+ pi install npm:@jacobmoura7/remote-pi
18
+ ```
19
+
20
+ Then in any Pi terminal:
21
+
22
+ ```text
23
+ /remote-pi
24
+ ```
25
+
26
+ The first run shows a short interactive wizard (agent name, default session,
27
+ whether to auto-start the relay). On every following run, `/remote-pi` joins
28
+ the local agent session and starts the relay automatically — no extra typing.
29
+
30
+ ### Try the agent network in 30 seconds
31
+
32
+ Open **two** Pi terminals in the same directory and run `/remote-pi` in each.
33
+ Both join the same session. Now just talk to the LLM — it has the tools.
34
+
35
+ In terminal A (say it ended up named `agent-A`):
36
+
37
+ ```text
38
+ Who else is connected in our agent session? List them.
39
+ ```
40
+
41
+ The LLM calls `agent_send` to `broker` with `{ type: "list_peers" }` and
42
+ replies with the names it sees.
43
+
44
+ Then, still in terminal A:
45
+
46
+ ```text
47
+ Send a ping to agent-B and wait for a reply.
48
+ ```
49
+
50
+ Pi calls `agent_request({ to: "agent-B", body: { type: "ping" } })`. The
51
+ message arrives in terminal B as a user-facing turn — terminal B's LLM
52
+ answers, and the reply lands back in terminal A. Two agents, one prompt
53
+ each, full round trip.
54
+
55
+ (Replace `agent-B` with whatever name terminal B reports for itself — the
56
+ wizard's default is the directory name plus a `#N` suffix on collision.)
57
+
58
+ ---
59
+
60
+ ## What it does
61
+
62
+ Remote Pi adds two independent layers on top of Pi. You can use either, or
63
+ both:
64
+
65
+ ### 1) Agent network (local, same machine)
66
+
67
+ Several Pi instances running side-by-side in different terminals can discover
68
+ each other and exchange messages. Each instance is a peer in a named
69
+ *session* and gets two tools the LLM can call directly:
70
+
71
+ - `agent_send` — fire-and-forget message to another agent
72
+ - `agent_request` — send and await a reply (correlated by message id)
73
+
74
+ This is purely local: the agents talk over a Unix domain socket at
75
+ `~/.pi/remote/sessions/<session-name>/broker.sock`. No network involved.
76
+ Useful for splitting work across roles (`backend`, `frontend`, `tests`,
77
+ `orchestrator`, …) and letting them coordinate.
78
+
79
+ The first agent to enter a session becomes the *leader* (hosts the broker);
80
+ the rest are *followers*. If the leader exits, a follower automatically takes
81
+ over — the failover is invisible to the LLMs.
82
+
83
+ ### 2) Mobile app (over the relay)
84
+
85
+ The companion mobile app lets you send prompts to Pi and read its responses
86
+ from your phone. The phone and the Pi process find each other through a
87
+ **relay**: a small WebSocket server that ferries end-to-end encrypted
88
+ messages between them. Pairing is one-time and per device, via QR code.
89
+
90
+ Encryption uses Curve25519 key agreement + ChaCha20-Poly1305 (libsodium).
91
+ The relay sees only ciphertext.
92
+
93
+ App downloads:
94
+
95
+ - **Google Play** — *coming soon*
96
+ - **App Store** — *coming soon*
97
+
98
+ Until the public releases land, follow
99
+ [the repo](https://github.com/jacobmoura7/remote-pi) for build/beta info.
100
+
101
+ ---
102
+
103
+ ## Install
104
+
105
+ Requirements: Node 20+, Pi (the host coding agent).
106
+
107
+ ```bash
108
+ pi install npm:@jacobmoura7/remote-pi
109
+ ```
110
+
111
+ The extension self-registers the `/remote-pi` slash command and deploys an
112
+ agent skill that teaches the LLM how to use `agent_send` / `agent_request`.
113
+
114
+ To verify:
115
+
116
+ ```text
117
+ /remote-pi config
118
+ ```
119
+
120
+ It should print the effective relay URL and where it came from
121
+ (`env` / `config` / `default`).
122
+
123
+ ---
124
+
125
+ ## Using `/remote-pi`
126
+
127
+ The bare command is the everyday entry point:
128
+
129
+ ```text
130
+ /remote-pi
131
+ ```
132
+
133
+ Behavior depends on whether there's a local config for this directory:
134
+
135
+ | State | What happens |
136
+ |---|---|
137
+ | First run (no `.pi/remote-pi/config.json`) | Interactive wizard → saves config → joins agent session → starts relay (if you opted in) |
138
+ | Returning user, auto-start enabled | Joins agent session + starts relay automatically, then prints status |
139
+ | Returning user, auto-start disabled | Prints status only; join/relay must be run manually |
140
+
141
+ The wizard asks three questions:
142
+
143
+ 1. **Agent name** — how other agents will address you in `agent_send` /
144
+ `agent_request`. Defaults to the directory name.
145
+ 2. **Default session** — the name of the agent-network room for this
146
+ directory. Multiple terminals in the same directory join the same session.
147
+ 3. **Auto-start relay (for mobile app access)?** — `Yes` if you want
148
+ `/remote-pi` to also connect to the relay so the mobile app can reach this
149
+ Pi. `No` for local-only use (agent network without mobile access).
150
+
151
+ Re-run the wizard later with `/remote-pi setup`.
152
+
153
+ ---
154
+
155
+ ## Pairing a mobile device
156
+
157
+ Once the relay is up (`/remote-pi relay status` shows `started` or `paired`):
158
+
159
+ ```text
160
+ /remote-pi pair
161
+ ```
162
+
163
+ A QR code is printed in the terminal. Scan it with the Remote Pi mobile app.
164
+ Pairing is **per machine** — once a device is paired, every Pi process on
165
+ this machine accepts it (it lives in `~/.pi/remote/peers.json`).
166
+
167
+ To list paired devices:
168
+
169
+ ```text
170
+ /remote-pi devices
171
+ ```
172
+
173
+ To remove one:
174
+
175
+ ```text
176
+ /remote-pi revoke <shortid>
177
+ ```
178
+
179
+ The shortid is the first 8 chars shown by `devices`.
180
+
181
+ ---
182
+
183
+ ## The relay
184
+
185
+ The relay is the only network-touching piece of Remote Pi. It does **not**
186
+ read messages — payloads are end-to-end encrypted between the Pi and the
187
+ paired device — but it sees connection metadata: which keypair is online,
188
+ which room/cwd identifiers exist, message timing, sizes.
189
+
190
+ You have two options:
191
+
192
+ ### Option A — Use the community relay
193
+
194
+ `wss://relay.remote-pi.dev` (default). Zero setup. Good for trying things
195
+ out or for casual use.
196
+
197
+ Caveats:
198
+
199
+ - Shared infrastructure — availability is best-effort.
200
+ - The operator could observe connection metadata as described above.
201
+ - TLS + per-message encryption is the only protection; **there is no IP
202
+ allow-listing or VPN gating**.
203
+
204
+ ### Option B — Self-host (recommended for privacy)
205
+
206
+ Run the relay yourself in Docker and put it behind a VPN like
207
+ [Tailscale](https://tailscale.com), [WireGuard](https://www.wireguard.com),
208
+ or your own VPC. Because the relay's network-level protection is just TLS +
209
+ keypair authentication, layering a VPN on top means **only your devices** can
210
+ even reach the WebSocket port — defense in depth.
211
+
212
+ Quick Docker outline (see the
213
+ [relay README](https://github.com/jacobmoura7/remote-pi/blob/main/relay/README.md#self-hosted-relay-recommended-for-privacy)
214
+ for the full setup, environment variables, and reverse-proxy guidance):
215
+
216
+ ```bash
217
+ docker run -d \
218
+ --name remote-pi-relay \
219
+ -p 3000:3000 \
220
+ --restart unless-stopped \
221
+ ghcr.io/jacobmoura7/remote-pi-relay:latest
222
+ ```
223
+
224
+ Bind the container to your VPN interface, terminate TLS in a reverse proxy,
225
+ and point both your Pi and your phone at the resulting `wss://…` URL.
226
+
227
+ ### Pointing Pi at your own relay
228
+
229
+ Once your relay is reachable, tell the extension:
230
+
231
+ ```text
232
+ /remote-pi relay url wss://relay.yourdomain.tld
233
+ ```
234
+
235
+ This writes `~/.pi/remote/config.json` with `{ "relay": "..." }`. Resolution
236
+ order (highest precedence first):
237
+
238
+ 1. `REMOTE_PI_RELAY` environment variable (CI / one-off overrides)
239
+ 2. `~/.pi/remote/config.json`
240
+ 3. The built-in default (`wss://relay.remote-pi.dev`)
241
+
242
+ Verify the active URL and its source with:
243
+
244
+ ```text
245
+ /remote-pi config
246
+ ```
247
+
248
+ If you change the URL while connected, run `/remote-pi relay stop` then
249
+ `/remote-pi relay start` (or `/remote-pi relay` to toggle).
250
+
251
+ The mobile app has its own relay-URL setting in its preferences pane — keep
252
+ both pointing at the same relay.
253
+
254
+ ---
255
+
256
+ ## Agent network: deeper look
257
+
258
+ Each session is one Unix-domain-socket broker plus N peers. The broker
259
+ multiplexes messages by `to` name and broadcasts system events
260
+ (`peer_joined`, `peer_left`).
261
+
262
+ Inside the LLM, the agent skill registers two tools:
263
+
264
+ ```jsonc
265
+ // Fire-and-forget
266
+ agent_send({
267
+ to: "backend", // peer name (or array for multicast)
268
+ body: { task: "add /healthz endpoint" },
269
+ re: "<id>" // optional — set when replying to a previous request
270
+ })
271
+
272
+ // Send + await reply (default 30s timeout)
273
+ agent_request({
274
+ to: "backend",
275
+ body: { question: "is the migration applied?" }
276
+ })
277
+ ```
278
+
279
+ The wire format is a 5-field envelope `{ from, to, id, re, body }` serialized
280
+ as one JSON line per message. The leader's broker writes an `audit.jsonl`
281
+ log at `~/.pi/remote/sessions/<name>/audit.jsonl` for postmortem inspection.
282
+
283
+ Useful commands:
284
+
285
+ | Command | What it does |
286
+ |---|---|
287
+ | `/remote-pi join [name]` | Join (or create) a session — only needed manually if `auto_start_relay=false` |
288
+ | `/remote-pi leave` | Leave the current session |
289
+ | `/remote-pi sessions` | List local sessions and which are live |
290
+ | `/remote-pi rename <new>` | Rename this agent in the current session |
291
+
292
+ Name collisions inside a session get a numeric suffix automatically
293
+ (`backend`, `backend#2`, `backend#3`). The broker assigns it and returns the
294
+ real name to the peer.
295
+
296
+ ---
297
+
298
+ ## Command reference
299
+
300
+ | Command | Description |
301
+ |---|---|
302
+ | `/remote-pi` | Connect (join session + start relay), or run setup on first use |
303
+ | `/remote-pi setup` | Run the setup wizard and update local config |
304
+ | `/remote-pi join [name]` | Join (or create) a local agent session |
305
+ | `/remote-pi leave` | Leave the current agent session |
306
+ | `/remote-pi rename <name>` | Rename this agent in the current session |
307
+ | `/remote-pi sessions` | List local agent sessions |
308
+ | `/remote-pi relay` | Toggle the relay connection on/off |
309
+ | `/remote-pi relay start` | Connect to the relay |
310
+ | `/remote-pi relay stop` | Disconnect from the relay |
311
+ | `/remote-pi relay status` | Show current relay status |
312
+ | `/remote-pi relay url <url>` | Set the relay URL (alias of `/remote-pi set-relay`) |
313
+ | `/remote-pi pair` | Show a QR code to pair a new mobile device |
314
+ | `/remote-pi devices` | List paired mobile devices |
315
+ | `/remote-pi revoke <shortid>` | Revoke a paired device by its shortid |
316
+ | `/remote-pi set-relay <url>` | Persist a new relay URL to user config |
317
+ | `/remote-pi config` | Show the effective relay URL and its source |
318
+
319
+ The footer in the Pi TUI reflects state live:
320
+
321
+ - `📡 <session> (N)` — current agent session and peer count
322
+ - `🟢 relay` — relay connected, at least one device paired
323
+ - `🟡 relay waiting for pairing` — relay connected, no device paired yet
324
+ - `📱 <shortid>` — a mobile device is actively connected right now
325
+
326
+ The window title becomes `<agent-name> · <session> · relay` so you can tell
327
+ your terminals apart at a glance.
328
+
329
+ ---
330
+
331
+ ## Configuration files
332
+
333
+ | Path | Scope | What's in it |
334
+ |---|---|---|
335
+ | `<cwd>/.pi/remote-pi/config.json` | Per-directory | `agent_name`, `session_name`, `auto_start_relay` |
336
+ | `~/.pi/remote/config.json` | Per-user | `relay` URL |
337
+ | `~/.pi/remote/peers.json` | Per-machine | Paired mobile devices |
338
+ | `~/.pi/remote/sessions/<name>/` | Per-session | Broker socket + `audit.jsonl` |
339
+ | `~/.pi/remote/skills/agent-network/SKILL.md` | Per-user | Agent skill the LLM reads |
340
+
341
+ Override the relay for a single run without persisting:
342
+
343
+ ```bash
344
+ REMOTE_PI_RELAY=wss://staging.example.tld pi
345
+ ```
346
+
347
+ ---
348
+
349
+ ## Troubleshooting
350
+
351
+ **Footer says `🟡 relay waiting for pairing` even though I paired a device.**
352
+ The icon reflects whether *any* device has been paired on this machine, not
353
+ whether one is connected right now. If you really have a paired device in
354
+ `/remote-pi devices`, restart Pi — the cache may be stale (fixed in current
355
+ release; report a bug if it recurs).
356
+
357
+ **Mobile app times out connecting.** Verify the same relay URL is configured
358
+ on both sides. If you self-host behind a VPN, your phone must also be on the
359
+ VPN (Tailscale on iOS/Android works fine).
360
+
361
+ **`agent_request` keeps timing out.** Default timeout is 30 s. For tasks
362
+ that legitimately take longer, the receiver should reply with `agent_send`
363
+ including `re: "<original-id>"` so the requester can correlate. The skill
364
+ explains this to the LLM automatically.
365
+
366
+ **Multiple terminals in the same directory.** Supported. They share the same
367
+ agent-network session (UDS broker) and the relay handles each Pi process
368
+ independently. If the relay refuses with `RoomAlreadyOpenError`, stop the
369
+ other terminal first.
370
+
371
+ ---
372
+
373
+ ## Links
374
+
375
+ - Source: <https://github.com/jacobmoura7/remote-pi>
376
+ - Pi coding agent: <https://github.com/earendil-works/pi>
377
+ - Relay (self-hosting guide): <https://github.com/jacobmoura7/remote-pi/blob/main/relay/README.md>
378
+ - Issues / bugs: <https://github.com/jacobmoura7/remote-pi/issues>
379
+
380
+ ---
381
+
382
+ ## License
383
+
384
+ MIT
@@ -0,0 +1,18 @@
1
+ export declare const kDefaultRelayUrl = "wss://relay.remote-pi.dev";
2
+ export type RemotePiConfig = {
3
+ relay?: string;
4
+ };
5
+ export declare function loadConfig(): RemotePiConfig;
6
+ export declare function saveConfig(patch: Partial<RemotePiConfig>): void;
7
+ export type RelayResolution = {
8
+ url: string;
9
+ source: "env" | "config" | "default";
10
+ };
11
+ /**
12
+ * Resolves the effective relay URL. Precedence:
13
+ * 1. process.env.REMOTE_PI_RELAY (escape hatch for ops/CI)
14
+ * 2. ~/.pi/remote/config.json `relay` field (set via /remote-pi set-relay)
15
+ * 3. kDefaultRelayUrl (production default)
16
+ */
17
+ export declare function resolveRelayUrl(): RelayResolution;
18
+ export declare function isValidRelayUrl(url: string): boolean;
package/dist/config.js ADDED
@@ -0,0 +1,51 @@
1
+ import fs from "node:fs";
2
+ import path from "node:path";
3
+ import os from "node:os";
4
+ const CONFIG_DIR = path.join(os.homedir(), ".pi", "remote");
5
+ const CONFIG_FILE = path.join(CONFIG_DIR, "config.json");
6
+ export const kDefaultRelayUrl = "wss://relay.remote-pi.dev";
7
+ export function loadConfig() {
8
+ try {
9
+ const raw = fs.readFileSync(CONFIG_FILE, "utf8");
10
+ const parsed = JSON.parse(raw);
11
+ if (!parsed || typeof parsed !== "object")
12
+ return {};
13
+ return parsed;
14
+ }
15
+ catch {
16
+ return {};
17
+ }
18
+ }
19
+ export function saveConfig(patch) {
20
+ fs.mkdirSync(CONFIG_DIR, { recursive: true });
21
+ const current = loadConfig();
22
+ const next = { ...current, ...patch };
23
+ fs.writeFileSync(CONFIG_FILE, JSON.stringify(next, null, 2));
24
+ }
25
+ /**
26
+ * Resolves the effective relay URL. Precedence:
27
+ * 1. process.env.REMOTE_PI_RELAY (escape hatch for ops/CI)
28
+ * 2. ~/.pi/remote/config.json `relay` field (set via /remote-pi set-relay)
29
+ * 3. kDefaultRelayUrl (production default)
30
+ */
31
+ export function resolveRelayUrl() {
32
+ const env = process.env["REMOTE_PI_RELAY"];
33
+ if (env && env.length > 0)
34
+ return { url: env, source: "env" };
35
+ const cfg = loadConfig();
36
+ if (cfg.relay && cfg.relay.length > 0)
37
+ return { url: cfg.relay, source: "config" };
38
+ return { url: kDefaultRelayUrl, source: "default" };
39
+ }
40
+ export function isValidRelayUrl(url) {
41
+ if (!url || (!url.startsWith("ws://") && !url.startsWith("wss://")))
42
+ return false;
43
+ try {
44
+ new URL(url);
45
+ return true;
46
+ }
47
+ catch {
48
+ return false;
49
+ }
50
+ }
51
+ //# sourceMappingURL=config.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"config.js","sourceRoot":"","sources":["../src/config.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,MAAM,SAAS,CAAC;AACzB,OAAO,IAAI,MAAM,WAAW,CAAC;AAC7B,OAAO,EAAE,MAAM,SAAS,CAAC;AAEzB,MAAM,UAAU,GAAG,IAAI,CAAC,IAAI,CAAC,EAAE,CAAC,OAAO,EAAE,EAAE,KAAK,EAAE,QAAQ,CAAC,CAAC;AAC5D,MAAM,WAAW,GAAG,IAAI,CAAC,IAAI,CAAC,UAAU,EAAE,aAAa,CAAC,CAAC;AAEzD,MAAM,CAAC,MAAM,gBAAgB,GAAG,2BAA2B,CAAC;AAI5D,MAAM,UAAU,UAAU;IACxB,IAAI,CAAC;QACH,MAAM,GAAG,GAAG,EAAE,CAAC,YAAY,CAAC,WAAW,EAAE,MAAM,CAAC,CAAC;QACjD,MAAM,MAAM,GAAG,IAAI,CAAC,KAAK,CAAC,GAAG,CAAY,CAAC;QAC1C,IAAI,CAAC,MAAM,IAAI,OAAO,MAAM,KAAK,QAAQ;YAAE,OAAO,EAAE,CAAC;QACrD,OAAO,MAAwB,CAAC;IAClC,CAAC;IAAC,MAAM,CAAC;QACP,OAAO,EAAE,CAAC;IACZ,CAAC;AACH,CAAC;AAED,MAAM,UAAU,UAAU,CAAC,KAA8B;IACvD,EAAE,CAAC,SAAS,CAAC,UAAU,EAAE,EAAE,SAAS,EAAE,IAAI,EAAE,CAAC,CAAC;IAC9C,MAAM,OAAO,GAAG,UAAU,EAAE,CAAC;IAC7B,MAAM,IAAI,GAAG,EAAE,GAAG,OAAO,EAAE,GAAG,KAAK,EAAE,CAAC;IACtC,EAAE,CAAC,aAAa,CAAC,WAAW,EAAE,IAAI,CAAC,SAAS,CAAC,IAAI,EAAE,IAAI,EAAE,CAAC,CAAC,CAAC,CAAC;AAC/D,CAAC;AAID;;;;;GAKG;AACH,MAAM,UAAU,eAAe;IAC7B,MAAM,GAAG,GAAG,OAAO,CAAC,GAAG,CAAC,iBAAiB,CAAC,CAAC;IAC3C,IAAI,GAAG,IAAI,GAAG,CAAC,MAAM,GAAG,CAAC;QAAE,OAAO,EAAE,GAAG,EAAE,GAAG,EAAE,MAAM,EAAE,KAAK,EAAE,CAAC;IAC9D,MAAM,GAAG,GAAG,UAAU,EAAE,CAAC;IACzB,IAAI,GAAG,CAAC,KAAK,IAAI,GAAG,CAAC,KAAK,CAAC,MAAM,GAAG,CAAC;QAAE,OAAO,EAAE,GAAG,EAAE,GAAG,CAAC,KAAK,EAAE,MAAM,EAAE,QAAQ,EAAE,CAAC;IACnF,OAAO,EAAE,GAAG,EAAE,gBAAgB,EAAE,MAAM,EAAE,SAAS,EAAE,CAAC;AACtD,CAAC;AAED,MAAM,UAAU,eAAe,CAAC,GAAW;IACzC,IAAI,CAAC,GAAG,IAAI,CAAC,CAAC,GAAG,CAAC,UAAU,CAAC,OAAO,CAAC,IAAI,CAAC,GAAG,CAAC,UAAU,CAAC,QAAQ,CAAC,CAAC;QAAE,OAAO,KAAK,CAAC;IAClF,IAAI,CAAC;QAAC,IAAI,GAAG,CAAC,GAAG,CAAC,CAAC;QAAC,OAAO,IAAI,CAAC;IAAC,CAAC;IAAC,MAAM,CAAC;QAAC,OAAO,KAAK,CAAC;IAAC,CAAC;AAC5D,CAAC"}
@@ -0,0 +1,77 @@
1
+ /**
2
+ * pi-extension — remote-pi slash commands + AgentBridge wiring
3
+ *
4
+ * Exported as ExtensionFactory (default export) to be loaded by Pi SDK:
5
+ * pi -e $(pwd)/dist/index.js
6
+ *
7
+ * State machine: idle → started → paired
8
+ * /remote-pi start connects to relay (idle → started)
9
+ * /remote-pi pair shows QR for new peers (started, async → paired via auto-listener)
10
+ * /remote-pi stop closes everything (any → idle)
11
+ *
12
+ * Pairing (post plano 06 — sem Noise XX):
13
+ * App envia inner `pair_request` (id, token, device_name) sobre canal opaco.
14
+ * Pi valida o token via qrSession.consumeToken, salva peer em peers.json
15
+ * {name, remote_epk, paired_at} e responde com `pair_ok` (ou `pair_error`).
16
+ * `ct` é base64(JSON.stringify(inner)) — sem cifra, sem MAC.
17
+ *
18
+ * Reconexão de peer conhecido:
19
+ * Se uma mensagem chega em estado `started` vinda de um epk presente em
20
+ * peers.json, o auto-listener promove direto pra `paired` sem novo
21
+ * pair_request, criando o PlainPeerChannel e roteando a mensagem.
22
+ *
23
+ * Architecture note — why we don't use AgentBridge directly here:
24
+ * AgentBridge.beforeToolCallHook is designed to be passed to createAgentSession().
25
+ * Inside an extension Pi already owns the AgentSession, so we can't re-bind
26
+ * beforeToolCall after the fact. The equivalent is pi.on("tool_call", …) which
27
+ * fires BEFORE execution and supports { block: true }.
28
+ * AgentBridge (src/session/agent_bridge.ts) remains the tested, mockable unit
29
+ * for integration tests.
30
+ */
31
+ import type { ExtensionContext, ExtensionFactory } from "@mariozechner/pi-coding-agent";
32
+ import type { ClientMessage, SessionHistoryEvent } from "./protocol/types.js";
33
+ export type RemoteState = "idle" | "started" | "paired";
34
+ type BufferMsg = {
35
+ role: "user" | "assistant" | "toolResult" | string;
36
+ content?: unknown;
37
+ timestamp?: number;
38
+ toolCallId?: string;
39
+ toolName?: string;
40
+ isError?: boolean;
41
+ usage?: {
42
+ input?: number;
43
+ output?: number;
44
+ };
45
+ };
46
+ /** Test-only override of the message buffer. */
47
+ export declare function _setMessageBufferForTest(msgs: unknown[]): void;
48
+ /** Test-only accessor: returns a defensive copy of the buffer. */
49
+ export declare function _getMessageBufferForTest(): unknown[];
50
+ /** Test-only override of session started timestamp. */
51
+ export declare function _setSessionStartedAtForTest(ts: number | null): void;
52
+ /** Test-only: reset the cached model name (between tests). */
53
+ export declare function _setCurrentModelForTest(name: string | undefined): void;
54
+ /** Test-only: exposes pending reconnect timer state. */
55
+ export declare function _hasPendingReconnect(): boolean;
56
+ /** Exported for tests. */
57
+ export declare function _getState(): RemoteState;
58
+ /**
59
+ * App-level peer disconnect (relay still up).
60
+ * Transitions paired → started and re-installs the auto-listener.
61
+ * Exported so tests can trigger it directly; in production it will be
62
+ * called when the relay sends a peer-disconnect notification (future).
63
+ */
64
+ export declare function _onPeerDisconnect(): void;
65
+ declare const extension: ExtensionFactory;
66
+ export default extension;
67
+ export declare function routeClientMessage(msg: ClientMessage, ctx: Pick<ExtensionContext, "abort">): void;
68
+ /**
69
+ * Maps SDK AgentMessage[] (UserMessage / AssistantMessage / ToolResultMessage)
70
+ * into the flat SessionHistoryEvent[] shape consumed by the app.
71
+ *
72
+ * Caveats (see report): in_reply_to of agent_message is the *last* user_input
73
+ * id seen in a linear scan — fine for typical conversational flow but not
74
+ * a perfect reconstruction of multi-turn ordering when tools interleave.
75
+ * Stable id for user_input is `sync_<timestamp>`.
76
+ */
77
+ export declare function _mapAgentMessagesToEvents(messages: BufferMsg[]): SessionHistoryEvent[];