remote-pi 0.1.3 → 0.2.1

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 (99) hide show
  1. package/README.md +160 -40
  2. package/dist/bin/supervisord.d.ts +2 -0
  3. package/dist/bin/supervisord.js +44 -0
  4. package/dist/bin/supervisord.js.map +1 -0
  5. package/dist/config.d.ts +44 -13
  6. package/dist/config.js +61 -22
  7. package/dist/config.js.map +1 -1
  8. package/dist/daemon/client.d.ts +20 -0
  9. package/dist/daemon/client.js +128 -0
  10. package/dist/daemon/client.js.map +1 -0
  11. package/dist/daemon/control_protocol.d.ts +100 -0
  12. package/dist/daemon/control_protocol.js +63 -0
  13. package/dist/daemon/control_protocol.js.map +1 -0
  14. package/dist/daemon/id.d.ts +18 -0
  15. package/dist/daemon/id.js +30 -0
  16. package/dist/daemon/id.js.map +1 -0
  17. package/dist/daemon/install.d.ts +132 -0
  18. package/dist/daemon/install.js +312 -0
  19. package/dist/daemon/install.js.map +1 -0
  20. package/dist/daemon/registry.d.ts +47 -0
  21. package/dist/daemon/registry.js +123 -0
  22. package/dist/daemon/registry.js.map +1 -0
  23. package/dist/daemon/rpc_child.d.ts +76 -0
  24. package/dist/daemon/rpc_child.js +130 -0
  25. package/dist/daemon/rpc_child.js.map +1 -0
  26. package/dist/daemon/supervisor.d.ts +38 -0
  27. package/dist/daemon/supervisor.js +301 -0
  28. package/dist/daemon/supervisor.js.map +1 -0
  29. package/dist/index.d.ts +62 -8
  30. package/dist/index.js +1231 -303
  31. package/dist/index.js.map +1 -1
  32. package/dist/mesh/canonical.d.ts +30 -0
  33. package/dist/mesh/canonical.js +61 -0
  34. package/dist/mesh/canonical.js.map +1 -0
  35. package/dist/mesh/client.d.ts +31 -0
  36. package/dist/mesh/client.js +56 -0
  37. package/dist/mesh/client.js.map +1 -0
  38. package/dist/mesh/encoding.d.ts +36 -0
  39. package/dist/mesh/encoding.js +53 -0
  40. package/dist/mesh/encoding.js.map +1 -0
  41. package/dist/mesh/self_revoke.d.ts +111 -0
  42. package/dist/mesh/self_revoke.js +182 -0
  43. package/dist/mesh/self_revoke.js.map +1 -0
  44. package/dist/mesh/siblings.d.ts +62 -0
  45. package/dist/mesh/siblings.js +95 -0
  46. package/dist/mesh/siblings.js.map +1 -0
  47. package/dist/mesh/types.d.ts +34 -0
  48. package/dist/mesh/types.js +11 -0
  49. package/dist/mesh/types.js.map +1 -0
  50. package/dist/mesh/verify.d.ts +17 -0
  51. package/dist/mesh/verify.js +77 -0
  52. package/dist/mesh/verify.js.map +1 -0
  53. package/dist/pairing/qr.d.ts +16 -5
  54. package/dist/pairing/qr.js +27 -8
  55. package/dist/pairing/qr.js.map +1 -1
  56. package/dist/pairing/storage.d.ts +41 -0
  57. package/dist/pairing/storage.js +160 -21
  58. package/dist/pairing/storage.js.map +1 -1
  59. package/dist/protocol/types.d.ts +23 -0
  60. package/dist/session/broker.d.ts +74 -0
  61. package/dist/session/broker.js +142 -4
  62. package/dist/session/broker.js.map +1 -1
  63. package/dist/session/broker_remote.d.ts +110 -0
  64. package/dist/session/broker_remote.js +397 -0
  65. package/dist/session/broker_remote.js.map +1 -0
  66. package/dist/session/cwd_lock.d.ts +28 -0
  67. package/dist/session/cwd_lock.js +89 -0
  68. package/dist/session/cwd_lock.js.map +1 -0
  69. package/dist/session/global_config.d.ts +9 -0
  70. package/dist/session/global_config.js +9 -0
  71. package/dist/session/global_config.js.map +1 -1
  72. package/dist/session/leader_election.d.ts +16 -0
  73. package/dist/session/leader_election.js +22 -0
  74. package/dist/session/leader_election.js.map +1 -1
  75. package/dist/session/local_config.d.ts +12 -5
  76. package/dist/session/local_config.js +24 -3
  77. package/dist/session/local_config.js.map +1 -1
  78. package/dist/session/peer.d.ts +28 -1
  79. package/dist/session/peer.js +69 -2
  80. package/dist/session/peer.js.map +1 -1
  81. package/dist/session/peer_inventory.d.ts +13 -0
  82. package/dist/session/peer_inventory.js +48 -0
  83. package/dist/session/peer_inventory.js.map +1 -0
  84. package/dist/session/setup_wizard.d.ts +32 -8
  85. package/dist/session/setup_wizard.js +45 -33
  86. package/dist/session/setup_wizard.js.map +1 -1
  87. package/dist/session/tools.d.ts +15 -7
  88. package/dist/session/tools.js +139 -31
  89. package/dist/session/tools.js.map +1 -1
  90. package/dist/transport/pi_forward_client.d.ts +29 -0
  91. package/dist/transport/pi_forward_client.js +62 -0
  92. package/dist/transport/pi_forward_client.js.map +1 -0
  93. package/dist/ui/footer.js +8 -6
  94. package/dist/ui/footer.js.map +1 -1
  95. package/docs/daemon.md +289 -0
  96. package/package.json +8 -2
  97. package/service-templates/launchd.plist.template +35 -0
  98. package/service-templates/systemd.service.template +19 -0
  99. package/skills/agent-network/SKILL.md +273 -294
@@ -0,0 +1,130 @@
1
+ import { spawn } from "node:child_process";
2
+ import { EventEmitter } from "node:events";
3
+ export class RpcChild extends EventEmitter {
4
+ opts;
5
+ child = null;
6
+ _state = "stopped";
7
+ _startedAt = null;
8
+ _restartCount = 0;
9
+ /** Accumulates partial stdout lines while waiting for `\n`. */
10
+ stdoutBuf = "";
11
+ constructor(opts) {
12
+ super();
13
+ this.opts = opts;
14
+ }
15
+ get state() { return this._state; }
16
+ get pid() { return this.child?.pid; }
17
+ get restartCount() { return this._restartCount; }
18
+ get uptimeMs() {
19
+ return this._startedAt !== null ? Date.now() - this._startedAt : undefined;
20
+ }
21
+ /**
22
+ * Spawn the child process. Idempotent for the same instance: a second
23
+ * call while already running is a no-op.
24
+ */
25
+ spawn() {
26
+ if (this.child)
27
+ return;
28
+ this._state = "starting";
29
+ const piBin = this.opts.piBin ?? "pi";
30
+ const args = ["--mode", "rpc", "-e", this.opts.extensionPath];
31
+ const env = {
32
+ ...process.env,
33
+ ...this.opts.env,
34
+ // Mandatory daemon marker — `_cmdRoot` in index.ts can use this to
35
+ // bail early if local config is missing (no wizard in RPC mode).
36
+ REMOTE_PI_DAEMON: "1",
37
+ };
38
+ const child = spawn(piBin, args, {
39
+ cwd: this.opts.cwd,
40
+ env,
41
+ stdio: ["pipe", "pipe", "pipe"],
42
+ });
43
+ this.child = child;
44
+ this._startedAt = Date.now();
45
+ this._state = "running";
46
+ child.stdout?.on("data", (chunk) => this._onStdout(chunk));
47
+ child.stderr?.on("data", (chunk) => {
48
+ // Forward stderr to our own stderr so `journalctl --user -u remote-pi-supervisord`
49
+ // sees daemon logs (with cwd prefix for disambiguation).
50
+ process.stderr.write(`[${this.opts.cwd}] ${chunk.toString()}`);
51
+ });
52
+ child.on("exit", (code, signal) => this._onExit(code, signal));
53
+ child.on("error", (err) => {
54
+ // spawn() itself failed (e.g. `pi` binary not found).
55
+ this._state = "crashed";
56
+ process.stderr.write(`[remote-pi-supervisord] spawn failed for ${this.opts.cwd}: ${String(err)}\n`);
57
+ this.emit("exit", { code: null, signal: null, isCrash: true });
58
+ });
59
+ this.emit("spawn", { pid: child.pid });
60
+ }
61
+ /**
62
+ * Sends a Pi RPC `prompt` command to the child's stdin. Fire-and-forget
63
+ * — we don't wait for the response (response would be the success ack;
64
+ * the actual agent output streams via the relay/UDS, not back through
65
+ * stdout).
66
+ *
67
+ * Returns false if the child isn't running (caller decides how to report).
68
+ */
69
+ sendPrompt(text, requestId) {
70
+ if (!this.child || !this.child.stdin || this._state !== "running")
71
+ return false;
72
+ const cmd = { id: requestId ?? `sv-${Date.now()}`, type: "prompt", message: text };
73
+ try {
74
+ this.child.stdin.write(JSON.stringify(cmd) + "\n");
75
+ return true;
76
+ }
77
+ catch {
78
+ return false;
79
+ }
80
+ }
81
+ /**
82
+ * Asks the child to exit gracefully. Sends SIGTERM; if the child doesn't
83
+ * exit within `timeoutMs`, escalates to SIGKILL. Resolves when the
84
+ * `exit` event fires.
85
+ */
86
+ async stop(timeoutMs = 5000) {
87
+ if (!this.child)
88
+ return;
89
+ const child = this.child;
90
+ return new Promise((resolve) => {
91
+ const onExit = () => { resolve(); };
92
+ this.once("exit", onExit);
93
+ try {
94
+ child.kill("SIGTERM");
95
+ }
96
+ catch { /* already dead */ }
97
+ const t = setTimeout(() => {
98
+ try {
99
+ child.kill("SIGKILL");
100
+ }
101
+ catch { /* race — already dead */ }
102
+ }, timeoutMs);
103
+ this.once("exit", () => clearTimeout(t));
104
+ });
105
+ }
106
+ _onStdout(chunk) {
107
+ this.stdoutBuf += chunk.toString();
108
+ let nl;
109
+ while ((nl = this.stdoutBuf.indexOf("\n")) >= 0) {
110
+ const line = this.stdoutBuf.slice(0, nl);
111
+ this.stdoutBuf = this.stdoutBuf.slice(nl + 1);
112
+ if (!line.trim())
113
+ continue;
114
+ this.emit("stdout", line);
115
+ }
116
+ }
117
+ _onExit(code, signal) {
118
+ const isCrash = code !== 0 || signal !== null;
119
+ this._state = isCrash ? "crashed" : "stopped";
120
+ this.child = null;
121
+ this._startedAt = null;
122
+ this.emit("exit", { code, signal, isCrash });
123
+ }
124
+ /** Bumps the restart counter — called by the supervisor when it
125
+ * decides to re-spawn after a crash. Exposed so tests can drive it. */
126
+ noteRestart() {
127
+ this._restartCount += 1;
128
+ }
129
+ }
130
+ //# sourceMappingURL=rpc_child.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"rpc_child.js","sourceRoot":"","sources":["../../src/daemon/rpc_child.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,38 @@
1
+ export interface SupervisorOptions {
2
+ /** Absolute path to remote-pi's dist/index.js — passed as -e to each
3
+ * spawned `pi`. Defaults to the location relative to where this file
4
+ * is bundled (so the supervisor finds itself). */
5
+ extensionPath: string;
6
+ /** Override the `pi` binary path. Defaults to "pi" on PATH. */
7
+ piBin?: string;
8
+ }
9
+ export declare class Supervisor {
10
+ private readonly opts;
11
+ private server;
12
+ private readonly children;
13
+ private shuttingDown;
14
+ constructor(opts: SupervisorOptions);
15
+ /** Bind the control UDS + spawn all registered daemons. */
16
+ start(): Promise<void>;
17
+ /** Graceful shutdown: stop all children, close UDS. */
18
+ stop(): Promise<void>;
19
+ private _mkdirParent;
20
+ private _bindUds;
21
+ private _onConnection;
22
+ private _handleRequest;
23
+ private _listInfo;
24
+ private _opStartAll;
25
+ private _opStopAll;
26
+ private _opRestartAll;
27
+ private _opSend;
28
+ private _opRegister;
29
+ private _opUnregister;
30
+ private _spawnAllFromRegistry;
31
+ private _spawnEntry;
32
+ private _onChildExit;
33
+ }
34
+ /** Test helper: derive id from cwd without going through the registry. */
35
+ export declare function _idForCwdForTest(cwd: string): string;
36
+ /** Exported for the bin/supervisord entry + tests to know where the
37
+ * supervisor will bind. */
38
+ export declare function getSupervisorSockPath(): string;
@@ -0,0 +1,301 @@
1
+ import { existsSync, mkdirSync, unlinkSync } from "node:fs";
2
+ import { createServer } from "node:net";
3
+ import { homedir } from "node:os";
4
+ import { dirname, join } from "node:path";
5
+ import { addDaemon, listDaemons, removeDaemon } from "./registry.js";
6
+ import { daemonIdForCwd } from "./id.js";
7
+ import { loadLocalConfig, defaultAgentName } from "../session/local_config.js";
8
+ import { RpcChild } from "./rpc_child.js";
9
+ import { encodeReply, parseRequest, } from "./control_protocol.js";
10
+ /**
11
+ * Central process that owns the daemon fleet (plan/26).
12
+ *
13
+ * Responsibilities:
14
+ * - Spawn one `pi --mode rpc` child per registry entry. Track them in
15
+ * `_children: Map<id, RpcChild>`.
16
+ * - Auto-restart crashed children with exponential backoff
17
+ * (1s, 5s, 30s, 5min). Give up after 4 attempts to avoid log spam
18
+ * when the agent is misconfigured.
19
+ * - Listen on `~/.pi/remote/supervisor.sock` for `ControlRequest`s from
20
+ * the `remote-pi` CLI. Each connection: 1 request → 1 reply → close.
21
+ * - Graceful shutdown on SIGTERM/SIGINT: stop all children + unlink
22
+ * the UDS file so a next supervisor can bind cleanly.
23
+ *
24
+ * The supervisor itself is the only long-running process the user
25
+ * installs as a system service (plan/26 W3 will generate the unit/plist).
26
+ * If it crashes, systemd/launchd restarts it; on restart it re-reads
27
+ * the registry and re-spawns everything.
28
+ */
29
+ const SUPERVISOR_SOCK_NAME = "supervisor.sock";
30
+ /** Backoff schedule for auto-restart after a crash. After exhausting, the
31
+ * child stays in `crashed` state until manual `restart_all` or fresh
32
+ * registry add. Keeps logs sane when the agent dies on every boot. */
33
+ const RESTART_BACKOFFS_MS = [1_000, 5_000, 30_000, 5 * 60_000];
34
+ function supervisorSockPath() {
35
+ const root = process.env["REMOTE_PI_HOME"] || homedir();
36
+ return join(root, ".pi", "remote", SUPERVISOR_SOCK_NAME);
37
+ }
38
+ export class Supervisor {
39
+ opts;
40
+ server = null;
41
+ children = new Map();
42
+ shuttingDown = false;
43
+ constructor(opts) {
44
+ this.opts = opts;
45
+ }
46
+ /** Bind the control UDS + spawn all registered daemons. */
47
+ async start() {
48
+ this._mkdirParent();
49
+ await this._bindUds();
50
+ this._spawnAllFromRegistry();
51
+ }
52
+ /** Graceful shutdown: stop all children, close UDS. */
53
+ async stop() {
54
+ this.shuttingDown = true;
55
+ // Cancel pending restart timers first so they don't race with stop().
56
+ for (const slot of this.children.values()) {
57
+ if (slot.restartTimer !== null) {
58
+ clearTimeout(slot.restartTimer);
59
+ slot.restartTimer = null;
60
+ }
61
+ }
62
+ await Promise.all([...this.children.values()].map((s) => s.child.stop()));
63
+ this.children.clear();
64
+ await new Promise((resolve) => {
65
+ if (!this.server)
66
+ return resolve();
67
+ this.server.close(() => resolve());
68
+ });
69
+ this.server = null;
70
+ // Best-effort: clear the socket file so a next supervisor bind succeeds.
71
+ try {
72
+ unlinkSync(supervisorSockPath());
73
+ }
74
+ catch { /* ignored */ }
75
+ }
76
+ // ── UDS binding ──────────────────────────────────────────────────────────
77
+ _mkdirParent() {
78
+ mkdirSync(dirname(supervisorSockPath()), { recursive: true });
79
+ }
80
+ async _bindUds() {
81
+ const path = supervisorSockPath();
82
+ // If a stale socket file exists from a previous crashed supervisor,
83
+ // try to connect first — if that fails, unlink and bind. Same self-
84
+ // healing pattern as cwd_lock / leader_election.
85
+ if (existsSync(path)) {
86
+ try {
87
+ unlinkSync(path);
88
+ }
89
+ catch { /* will throw on bind if still held */ }
90
+ }
91
+ const server = createServer((socket) => this._onConnection(socket));
92
+ await new Promise((resolve, reject) => {
93
+ server.once("error", reject);
94
+ server.listen(path, () => resolve());
95
+ });
96
+ this.server = server;
97
+ }
98
+ _onConnection(socket) {
99
+ let buf = "";
100
+ socket.setEncoding("utf8");
101
+ socket.on("data", (chunk) => {
102
+ buf += chunk;
103
+ const nl = buf.indexOf("\n");
104
+ if (nl < 0)
105
+ return;
106
+ const line = buf.slice(0, nl);
107
+ // Single request per connection; ignore anything past the newline.
108
+ void this._handleRequest(line)
109
+ .then((reply) => socket.end(encodeReply(reply)))
110
+ .catch((err) => socket.end(encodeReply({ ok: false, error: String(err) })));
111
+ });
112
+ socket.on("error", () => { });
113
+ }
114
+ // ── Request dispatch ─────────────────────────────────────────────────────
115
+ async _handleRequest(line) {
116
+ let req;
117
+ try {
118
+ req = parseRequest(line);
119
+ }
120
+ catch (e) {
121
+ return { ok: false, error: e.message };
122
+ }
123
+ switch (req.op) {
124
+ case "list": return { ok: true, data: { daemons: this._listInfo() } };
125
+ case "status": return { ok: true, data: { daemons: this._listInfo() } };
126
+ case "start_all": return this._opStartAll();
127
+ case "stop_all": return this._opStopAll();
128
+ case "restart_all": return this._opRestartAll();
129
+ case "send": return this._opSend(req.id, req.text);
130
+ case "register": return this._opRegister(req.cwd);
131
+ case "unregister": return this._opUnregister(req.id);
132
+ default: {
133
+ const unknown = req.op;
134
+ return { ok: false, error: `unknown op: ${unknown}` };
135
+ }
136
+ }
137
+ }
138
+ // ── Op handlers ──────────────────────────────────────────────────────────
139
+ _listInfo() {
140
+ const registry = listDaemons();
141
+ return registry.map((entry) => {
142
+ const slot = this.children.get(entry.id);
143
+ const cfg = loadLocalConfig(entry.cwd);
144
+ const name = cfg.agent_name ?? defaultAgentName(entry.cwd);
145
+ const info = {
146
+ id: entry.id,
147
+ cwd: entry.cwd,
148
+ name,
149
+ state: slot?.child.state ?? "stopped",
150
+ };
151
+ if (slot) {
152
+ if (slot.child.pid !== undefined)
153
+ info.pid = slot.child.pid;
154
+ if (slot.child.uptimeMs !== undefined)
155
+ info.uptime_s = Math.floor(slot.child.uptimeMs / 1000);
156
+ info.restart_count = slot.child.restartCount;
157
+ }
158
+ return info;
159
+ });
160
+ }
161
+ _opStartAll() {
162
+ const started = [];
163
+ const already = [];
164
+ for (const entry of listDaemons()) {
165
+ const slot = this.children.get(entry.id);
166
+ if (slot && slot.child.state === "running") {
167
+ already.push(entry.id);
168
+ continue;
169
+ }
170
+ this._spawnEntry(entry.id, entry.cwd);
171
+ started.push(entry.id);
172
+ }
173
+ return { ok: true, data: { started, already_running: already } };
174
+ }
175
+ async _opStopAll() {
176
+ const stopped = [];
177
+ const already = [];
178
+ for (const [id, slot] of this.children) {
179
+ if (slot.child.state !== "running") {
180
+ already.push(id);
181
+ continue;
182
+ }
183
+ if (slot.restartTimer !== null) {
184
+ clearTimeout(slot.restartTimer);
185
+ slot.restartTimer = null;
186
+ }
187
+ await slot.child.stop();
188
+ stopped.push(id);
189
+ }
190
+ return { ok: true, data: { stopped, already_stopped: already } };
191
+ }
192
+ async _opRestartAll() {
193
+ const stopReply = await this._opStopAll();
194
+ if (!stopReply.ok)
195
+ return stopReply;
196
+ const startReply = this._opStartAll();
197
+ if (!startReply.ok)
198
+ return startReply;
199
+ const restarted = startReply.data.started;
200
+ return { ok: true, data: { restarted } };
201
+ }
202
+ _opSend(id, text) {
203
+ const slot = this.children.get(id);
204
+ if (!slot)
205
+ return { ok: false, error: `daemon ${id} not running` };
206
+ if (slot.child.state !== "running") {
207
+ return { ok: false, error: `daemon ${id} state is ${slot.child.state}` };
208
+ }
209
+ const ok = slot.child.sendPrompt(text);
210
+ return { ok: true, data: { id, delivered: ok } };
211
+ }
212
+ _opRegister(rawCwd) {
213
+ try {
214
+ const { id, cwd } = addDaemon(rawCwd);
215
+ return { ok: true, data: { id, cwd } };
216
+ }
217
+ catch (e) {
218
+ return { ok: false, error: e.message };
219
+ }
220
+ }
221
+ async _opUnregister(id) {
222
+ // Stop the child first so we don't leave an orphan when the registry
223
+ // entry is gone.
224
+ const slot = this.children.get(id);
225
+ if (slot) {
226
+ if (slot.restartTimer !== null) {
227
+ clearTimeout(slot.restartTimer);
228
+ slot.restartTimer = null;
229
+ }
230
+ await slot.child.stop();
231
+ this.children.delete(id);
232
+ }
233
+ try {
234
+ const result = removeDaemon(id);
235
+ return { ok: true, data: result };
236
+ }
237
+ catch (e) {
238
+ return { ok: false, error: e.message };
239
+ }
240
+ }
241
+ // ── Child lifecycle ──────────────────────────────────────────────────────
242
+ _spawnAllFromRegistry() {
243
+ for (const entry of listDaemons()) {
244
+ this._spawnEntry(entry.id, entry.cwd);
245
+ }
246
+ }
247
+ _spawnEntry(id, cwd) {
248
+ // Clean up any prior slot (e.g. crashed + waiting for backoff).
249
+ const existing = this.children.get(id);
250
+ if (existing) {
251
+ if (existing.restartTimer !== null)
252
+ clearTimeout(existing.restartTimer);
253
+ // If somehow the child is still alive, stop it first so we don't
254
+ // leak. Fire-and-forget — caller doesn't await.
255
+ if (existing.child.state === "running")
256
+ void existing.child.stop();
257
+ }
258
+ const childOpts = {
259
+ extensionPath: this.opts.extensionPath,
260
+ cwd,
261
+ };
262
+ if (this.opts.piBin !== undefined)
263
+ childOpts.piBin = this.opts.piBin;
264
+ const child = new RpcChild(childOpts);
265
+ const slot = { id, cwd, child, restartTimer: null, restartAttempt: 0 };
266
+ this.children.set(id, slot);
267
+ child.on("exit", (evt) => this._onChildExit(id, evt));
268
+ child.spawn();
269
+ }
270
+ _onChildExit(id, evt) {
271
+ if (this.shuttingDown)
272
+ return;
273
+ const slot = this.children.get(id);
274
+ if (!slot)
275
+ return;
276
+ if (!evt.isCrash) {
277
+ // Clean shutdown (e.g. via `stop_all`). Don't auto-restart.
278
+ return;
279
+ }
280
+ // Crash: schedule restart with backoff. After exhausting the schedule
281
+ // we give up and stay in `crashed`.
282
+ if (slot.restartAttempt >= RESTART_BACKOFFS_MS.length) {
283
+ process.stderr.write(`[remote-pi-supervisord] giving up restart for ${id} after ${slot.restartAttempt} attempts\n`);
284
+ return;
285
+ }
286
+ const delay = RESTART_BACKOFFS_MS[slot.restartAttempt];
287
+ process.stderr.write(`[remote-pi-supervisord] scheduling restart of ${id} in ${delay}ms (attempt ${slot.restartAttempt + 1})\n`);
288
+ slot.restartTimer = setTimeout(() => {
289
+ slot.restartTimer = null;
290
+ slot.restartAttempt += 1;
291
+ slot.child.noteRestart();
292
+ slot.child.spawn();
293
+ }, delay);
294
+ }
295
+ }
296
+ /** Test helper: derive id from cwd without going through the registry. */
297
+ export function _idForCwdForTest(cwd) { return daemonIdForCwd(cwd); }
298
+ /** Exported for the bin/supervisord entry + tests to know where the
299
+ * supervisor will bind. */
300
+ export function getSupervisorSockPath() { return supervisorSockPath(); }
301
+ //# sourceMappingURL=supervisor.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"supervisor.js","sourceRoot":"","sources":["../../src/daemon/supervisor.ts"],"names":[],"mappings":"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"}
package/dist/index.d.ts CHANGED
@@ -1,3 +1,4 @@
1
+ #!/usr/bin/env node
1
2
  /**
2
3
  * pi-extension — remote-pi slash commands + AgentBridge wiring
3
4
  *
@@ -30,7 +31,8 @@
30
31
  */
31
32
  import type { ExtensionContext, ExtensionFactory } from "@mariozechner/pi-coding-agent";
32
33
  import type { ClientMessage, SessionHistoryEvent } from "./protocol/types.js";
33
- export type RemoteState = "idle" | "started" | "paired";
34
+ import { PlainPeerChannel } from "./transport/peer_channel.js";
35
+ export type RemoteState = "idle" | "started";
34
36
  type BufferMsg = {
35
37
  role: "user" | "assistant" | "toolResult" | string;
36
38
  content?: unknown;
@@ -44,6 +46,26 @@ type BufferMsg = {
44
46
  };
45
47
  };
46
48
  /** Test-only override of the message buffer. */
49
+ /**
50
+ * Test-only: emulate what `/remote-pi` does on the returning-user path
51
+ * (join the local mesh, then start the relay) without touching the FS for
52
+ * a `localConfigExists()` lookup. Lets tests bring the relay up without
53
+ * mocking the wizard or the local config storage.
54
+ *
55
+ * Typed loosely to accept any ctx shape with `ui.notify` + `cwd` — the
56
+ * unit tests use minimal mocks that don't satisfy the full
57
+ * `ExtensionContext` interface.
58
+ */
59
+ export declare function _connectForTest(ctx: unknown): Promise<void>;
60
+ /** Test-only: tear everything down (mirrors `/remote-pi stop`). */
61
+ export declare function _stopForTest(ctx: unknown): Promise<void>;
62
+ /**
63
+ * Test-only: relay-only startup, no UDS mesh join. Replaces the old
64
+ * `remote-pi relay start` handler that some tests captured to bring up
65
+ * the relay in isolation (e.g. ping/pong tests that don't care about the
66
+ * agent-network broker).
67
+ */
68
+ export declare function _startRelayForTest(ctx: unknown): Promise<void>;
47
69
  export declare function _setMessageBufferForTest(msgs: unknown[]): void;
48
70
  /** Test-only accessor: returns a defensive copy of the buffer. */
49
71
  export declare function _getMessageBufferForTest(): unknown[];
@@ -53,17 +75,49 @@ export declare function _setSessionStartedAtForTest(ts: number | null): void;
53
75
  export declare function _setCurrentModelForTest(name: string | undefined): void;
54
76
  /** Test-only: exposes pending reconnect timer state. */
55
77
  export declare function _hasPendingReconnect(): boolean;
56
- /** Exported for tests. */
57
- export declare function _getState(): RemoteState;
58
78
  /**
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).
79
+ * Public state-snapshot helper. Returns the derived UX state, not the raw
80
+ * `_state` enum: the W2D refactor collapsed the internal machine to
81
+ * `idle | started` and made `paired` a derived metric
82
+ * (`_activePeers.size > 0`). Tests and the footer keep the three-state
83
+ * mental model via this getter.
84
+ */
85
+ export declare function _getState(): "idle" | "started" | "paired";
86
+ /** Test-only: number of owners currently attached via PlainPeerChannel. */
87
+ export declare function _getActivePeerCountForTest(): number;
88
+ /** Test-only: true if a specific peer (base64 std) has an attached channel. */
89
+ export declare function _hasActivePeerForTest(appPeerIdStd: string): boolean;
90
+ /**
91
+ * Per-owner disconnect callback. Fires when one specific owner's channel
92
+ * detaches (e.g. relay told us the peer is gone). Other owners' channels
93
+ * keep running — relay stays "started".
94
+ *
95
+ * Exported so tests can trigger the disconnect path for a specific peer.
96
+ *
97
+ * Backward-compat: a no-arg call (legacy tests / pre-W2D callers) falls
98
+ * back to detaching the most recently attached peer, mirroring the old
99
+ * singleton semantics.
63
100
  */
64
- export declare function _onPeerDisconnect(): void;
101
+ export declare function _onPeerDisconnect(appPeerId?: string): void;
65
102
  declare const extension: ExtensionFactory;
66
103
  export default extension;
104
+ /**
105
+ * Per-channel router. Replaces the W2D-pre `routeClientMessage` which
106
+ * implicitly used the `_peerChannel` singleton for replies. Each
107
+ * PlainPeerChannel now carries its own `sender` and passes it here so
108
+ * sender-specific responses (cancelled, pong, session_history) flow back
109
+ * through the right wire instead of being broadcast.
110
+ *
111
+ * Broadcast messages (user_input mirror, agent_chunk, tool_*) still use
112
+ * `_broadcastToActive` from the SDK event handlers; this router only
113
+ * handles incoming app→pi requests.
114
+ */
115
+ export declare function _routeClientMessageFrom(sender: PlainPeerChannel, msg: ClientMessage, ctx: Pick<ExtensionContext, "abort">): void;
116
+ /**
117
+ * Backward-compatible shim for legacy callers + tests that didn't track
118
+ * a specific sender channel. Routes to the most recently attached owner,
119
+ * mirroring the pre-W2D singleton behavior.
120
+ */
67
121
  export declare function routeClientMessage(msg: ClientMessage, ctx: Pick<ExtensionContext, "abort">): void;
68
122
  /**
69
123
  * Maps SDK AgentMessage[] (UserMessage / AssistantMessage / ToolResultMessage)