remote-pi-app 0.7.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.
- package/LICENSE +21 -0
- package/README.md +675 -0
- package/dist/actions/handlers.d.ts +138 -0
- package/dist/actions/handlers.js +189 -0
- package/dist/actions/handlers.js.map +1 -0
- package/dist/actions/registry.d.ts +39 -0
- package/dist/actions/registry.js +58 -0
- package/dist/actions/registry.js.map +1 -0
- package/dist/bin/supervisord.d.ts +2 -0
- package/dist/bin/supervisord.js +86 -0
- package/dist/bin/supervisord.js.map +1 -0
- package/dist/config.d.ts +62 -0
- package/dist/config.js +115 -0
- package/dist/config.js.map +1 -0
- package/dist/daemon/client.d.ts +20 -0
- package/dist/daemon/client.js +131 -0
- package/dist/daemon/client.js.map +1 -0
- package/dist/daemon/control_protocol.d.ts +176 -0
- package/dist/daemon/control_protocol.js +63 -0
- package/dist/daemon/control_protocol.js.map +1 -0
- package/dist/daemon/cron_log.d.ts +45 -0
- package/dist/daemon/cron_log.js +72 -0
- package/dist/daemon/cron_log.js.map +1 -0
- package/dist/daemon/cron_registry.d.ts +80 -0
- package/dist/daemon/cron_registry.js +194 -0
- package/dist/daemon/cron_registry.js.map +1 -0
- package/dist/daemon/id.d.ts +24 -0
- package/dist/daemon/id.js +36 -0
- package/dist/daemon/id.js.map +1 -0
- package/dist/daemon/install.d.ts +170 -0
- package/dist/daemon/install.js +547 -0
- package/dist/daemon/install.js.map +1 -0
- package/dist/daemon/registry.d.ts +63 -0
- package/dist/daemon/registry.js +153 -0
- package/dist/daemon/registry.js.map +1 -0
- package/dist/daemon/rpc_child.d.ts +194 -0
- package/dist/daemon/rpc_child.js +389 -0
- package/dist/daemon/rpc_child.js.map +1 -0
- package/dist/daemon/supervisor.d.ts +93 -0
- package/dist/daemon/supervisor.js +596 -0
- package/dist/daemon/supervisor.js.map +1 -0
- package/dist/extension_ui_bridge.d.ts +26 -0
- package/dist/extension_ui_bridge.js +341 -0
- package/dist/extension_ui_bridge.js.map +1 -0
- package/dist/index.d.ts +213 -0
- package/dist/index.js +5090 -0
- package/dist/index.js.map +1 -0
- package/dist/mcp/mesh_result.d.ts +10 -0
- package/dist/mcp/mesh_result.js +42 -0
- package/dist/mcp/mesh_result.js.map +1 -0
- package/dist/mcp/mesh_server.d.ts +16 -0
- package/dist/mcp/mesh_server.js +327 -0
- package/dist/mcp/mesh_server.js.map +1 -0
- package/dist/mesh/canonical.d.ts +30 -0
- package/dist/mesh/canonical.js +61 -0
- package/dist/mesh/canonical.js.map +1 -0
- package/dist/mesh/client.d.ts +18 -0
- package/dist/mesh/client.js +125 -0
- package/dist/mesh/client.js.map +1 -0
- package/dist/mesh/encoding.d.ts +38 -0
- package/dist/mesh/encoding.js +216 -0
- package/dist/mesh/encoding.js.map +1 -0
- package/dist/mesh/self_revoke.d.ts +73 -0
- package/dist/mesh/self_revoke.js +401 -0
- package/dist/mesh/self_revoke.js.map +1 -0
- package/dist/mesh/siblings.d.ts +49 -0
- package/dist/mesh/siblings.js +159 -0
- package/dist/mesh/siblings.js.map +1 -0
- package/dist/mesh/types.d.ts +34 -0
- package/dist/mesh/types.js +11 -0
- package/dist/mesh/types.js.map +1 -0
- package/dist/mesh/verify.d.ts +17 -0
- package/dist/mesh/verify.js +86 -0
- package/dist/mesh/verify.js.map +1 -0
- package/dist/pairing/crypto.d.ts +8 -0
- package/dist/pairing/crypto.js +22 -0
- package/dist/pairing/crypto.js.map +1 -0
- package/dist/pairing/qr.d.ts +59 -0
- package/dist/pairing/qr.js +125 -0
- package/dist/pairing/qr.js.map +1 -0
- package/dist/pairing/storage.d.ts +120 -0
- package/dist/pairing/storage.js +604 -0
- package/dist/pairing/storage.js.map +1 -0
- package/dist/protocol/codec.d.ts +7 -0
- package/dist/protocol/codec.js +53 -0
- package/dist/protocol/codec.js.map +1 -0
- package/dist/protocol/types.d.ts +432 -0
- package/dist/protocol/types.js +2 -0
- package/dist/protocol/types.js.map +1 -0
- package/dist/rooms.d.ts +29 -0
- package/dist/rooms.js +57 -0
- package/dist/rooms.js.map +1 -0
- package/dist/session/bridge.d.ts +37 -0
- package/dist/session/bridge.js +130 -0
- package/dist/session/bridge.js.map +1 -0
- package/dist/session/broker.d.ts +189 -0
- package/dist/session/broker.js +507 -0
- package/dist/session/broker.js.map +1 -0
- package/dist/session/broker_remote.d.ts +74 -0
- package/dist/session/broker_remote.js +583 -0
- package/dist/session/broker_remote.js.map +1 -0
- package/dist/session/cwd_lock.d.ts +33 -0
- package/dist/session/cwd_lock.js +126 -0
- package/dist/session/cwd_lock.js.map +1 -0
- package/dist/session/envelope.d.ts +33 -0
- package/dist/session/envelope.js +117 -0
- package/dist/session/envelope.js.map +1 -0
- package/dist/session/global_config.d.ts +33 -0
- package/dist/session/global_config.js +73 -0
- package/dist/session/global_config.js.map +1 -0
- package/dist/session/ipc.d.ts +27 -0
- package/dist/session/ipc.js +22 -0
- package/dist/session/ipc.js.map +1 -0
- package/dist/session/leader_election.d.ts +32 -0
- package/dist/session/leader_election.js +106 -0
- package/dist/session/leader_election.js.map +1 -0
- package/dist/session/local_config.d.ts +55 -0
- package/dist/session/local_config.js +157 -0
- package/dist/session/local_config.js.map +1 -0
- package/dist/session/mesh_node.d.ts +116 -0
- package/dist/session/mesh_node.js +573 -0
- package/dist/session/mesh_node.js.map +1 -0
- package/dist/session/peer.d.ts +139 -0
- package/dist/session/peer.js +451 -0
- package/dist/session/peer.js.map +1 -0
- package/dist/session/peer_inventory.d.ts +13 -0
- package/dist/session/peer_inventory.js +48 -0
- package/dist/session/peer_inventory.js.map +1 -0
- package/dist/session/peer_limits.d.ts +9 -0
- package/dist/session/peer_limits.js +21 -0
- package/dist/session/peer_limits.js.map +1 -0
- package/dist/session/setup_wizard.d.ts +39 -0
- package/dist/session/setup_wizard.js +63 -0
- package/dist/session/setup_wizard.js.map +1 -0
- package/dist/session/tools.d.ts +24 -0
- package/dist/session/tools.js +252 -0
- package/dist/session/tools.js.map +1 -0
- package/dist/session/wizard.d.ts +13 -0
- package/dist/session/wizard.js +20 -0
- package/dist/session/wizard.js.map +1 -0
- package/dist/transport/peer_channel.d.ts +37 -0
- package/dist/transport/peer_channel.js +98 -0
- package/dist/transport/peer_channel.js.map +1 -0
- package/dist/transport/pi_forward_client.d.ts +29 -0
- package/dist/transport/pi_forward_client.js +62 -0
- package/dist/transport/pi_forward_client.js.map +1 -0
- package/dist/transport/relay_client.d.ts +91 -0
- package/dist/transport/relay_client.js +206 -0
- package/dist/transport/relay_client.js.map +1 -0
- package/dist/ui/footer.d.ts +32 -0
- package/dist/ui/footer.js +34 -0
- package/dist/ui/footer.js.map +1 -0
- package/docs/daemon.md +289 -0
- package/package.json +90 -0
- package/service-templates/launchd.plist.template +35 -0
- package/service-templates/systemd.service.template +19 -0
- package/service-templates/task-launcher.vbs.template +10 -0
- package/service-templates/task-scheduler.xml.template +38 -0
- package/skills/agent-network/SKILL.md +310 -0
package/dist/config.js
ADDED
|
@@ -0,0 +1,115 @@
|
|
|
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
|
+
/**
|
|
7
|
+
* Default community relay. Stored in canonical http(s):// form — conversion
|
|
8
|
+
* to ws(s):// happens at the transport layer (see `toWebSocketUrl`). The
|
|
9
|
+
* community relay's reverse proxy maps `:443 → :3000` (the WS port), so the
|
|
10
|
+
* URL has no explicit port and the WebSocket upgrade rides on the same TLS
|
|
11
|
+
* connection as the HTTPS endpoints used by the mesh client.
|
|
12
|
+
*/
|
|
13
|
+
export const kDefaultRelayUrl = "https://relay-rp1.jacobmoura.work";
|
|
14
|
+
export function loadConfig() {
|
|
15
|
+
try {
|
|
16
|
+
const raw = fs.readFileSync(CONFIG_FILE, "utf8");
|
|
17
|
+
const parsed = JSON.parse(raw);
|
|
18
|
+
if (!parsed || typeof parsed !== "object")
|
|
19
|
+
return {};
|
|
20
|
+
return parsed;
|
|
21
|
+
}
|
|
22
|
+
catch {
|
|
23
|
+
return {};
|
|
24
|
+
}
|
|
25
|
+
}
|
|
26
|
+
export function saveConfig(patch) {
|
|
27
|
+
fs.mkdirSync(CONFIG_DIR, { recursive: true });
|
|
28
|
+
const current = loadConfig();
|
|
29
|
+
const next = { ...current, ...patch };
|
|
30
|
+
fs.writeFileSync(CONFIG_FILE, JSON.stringify(next, null, 2));
|
|
31
|
+
}
|
|
32
|
+
/**
|
|
33
|
+
* Resolves the effective relay URL in **canonical http(s):// form**.
|
|
34
|
+
*
|
|
35
|
+
* Precedence:
|
|
36
|
+
* 1. `REMOTE_PI_RELAY` env var (ops/CI escape hatch)
|
|
37
|
+
* 2. `~/.pi/remote/config.json` `relay` field (set via /remote-pi set-relay)
|
|
38
|
+
* 3. `kDefaultRelayUrl` (community default)
|
|
39
|
+
*
|
|
40
|
+
* Any ws(s):// values found (legacy configs or env overrides) are coerced
|
|
41
|
+
* to http(s):// defensively — the canonical form across the codebase is
|
|
42
|
+
* http(s)://, and the transport layer converts to ws(s):// at WS-open time.
|
|
43
|
+
*/
|
|
44
|
+
export function resolveRelayUrl() {
|
|
45
|
+
const env = process.env["REMOTE_PI_RELAY"];
|
|
46
|
+
if (env && env.length > 0)
|
|
47
|
+
return { url: toHttpUrl(env), source: "env" };
|
|
48
|
+
const cfg = loadConfig();
|
|
49
|
+
if (cfg.relay && cfg.relay.length > 0)
|
|
50
|
+
return { url: toHttpUrl(cfg.relay), source: "config" };
|
|
51
|
+
return { url: toHttpUrl(kDefaultRelayUrl), source: "default" };
|
|
52
|
+
}
|
|
53
|
+
/**
|
|
54
|
+
* Strict validator for **user-provided** relay URLs (via `/remote-pi
|
|
55
|
+
* set-relay` or `/remote-pi relay url`).
|
|
56
|
+
*
|
|
57
|
+
* Only accepts `http://` and `https://`. `ws://`/`wss://` are deliberately
|
|
58
|
+
* **rejected** — the canonical form stored in config is http(s):// and the
|
|
59
|
+
* extension converts to ws(s):// internally when opening the WebSocket.
|
|
60
|
+
* Forcing a single scheme at the user boundary avoids two-form drift.
|
|
61
|
+
*/
|
|
62
|
+
export function isValidRelayUrl(url) {
|
|
63
|
+
if (!url)
|
|
64
|
+
return false;
|
|
65
|
+
const lower = url.toLowerCase();
|
|
66
|
+
if (!lower.startsWith("http://") && !lower.startsWith("https://"))
|
|
67
|
+
return false;
|
|
68
|
+
try {
|
|
69
|
+
new URL(url);
|
|
70
|
+
return true;
|
|
71
|
+
}
|
|
72
|
+
catch {
|
|
73
|
+
return false;
|
|
74
|
+
}
|
|
75
|
+
}
|
|
76
|
+
/**
|
|
77
|
+
* Returns true if the URL uses ws:// or wss:// scheme — for emitting a
|
|
78
|
+
* targeted error message when the user pastes a WebSocket URL by mistake.
|
|
79
|
+
*/
|
|
80
|
+
export function isWebSocketScheme(url) {
|
|
81
|
+
const lower = url.toLowerCase();
|
|
82
|
+
return lower.startsWith("ws://") || lower.startsWith("wss://");
|
|
83
|
+
}
|
|
84
|
+
/**
|
|
85
|
+
* Converts an http(s):// URL to the corresponding ws(s):// form. Used by
|
|
86
|
+
* the transport layer right before opening the WebSocket — config storage
|
|
87
|
+
* and the mesh HTTP client both stay on http(s)://.
|
|
88
|
+
*
|
|
89
|
+
* https://host → wss://host
|
|
90
|
+
* http://host → ws://host
|
|
91
|
+
* ws(s)://host → pass-through (defensive — env overrides or legacy
|
|
92
|
+
* configs may still carry ws(s)://)
|
|
93
|
+
*/
|
|
94
|
+
export function toWebSocketUrl(url) {
|
|
95
|
+
const lower = url.toLowerCase();
|
|
96
|
+
if (lower.startsWith("https://"))
|
|
97
|
+
return "wss://" + url.slice("https://".length);
|
|
98
|
+
if (lower.startsWith("http://"))
|
|
99
|
+
return "ws://" + url.slice("http://".length);
|
|
100
|
+
return url;
|
|
101
|
+
}
|
|
102
|
+
/**
|
|
103
|
+
* Inverse of `toWebSocketUrl`. Used by `resolveRelayUrl` to coerce any
|
|
104
|
+
* ws(s):// values back to canonical http(s):// before returning them to
|
|
105
|
+
* the rest of the codebase.
|
|
106
|
+
*/
|
|
107
|
+
export function toHttpUrl(url) {
|
|
108
|
+
const lower = url.toLowerCase();
|
|
109
|
+
if (lower.startsWith("wss://"))
|
|
110
|
+
return "https://" + url.slice("wss://".length);
|
|
111
|
+
if (lower.startsWith("ws://"))
|
|
112
|
+
return "http://" + url.slice("ws://".length);
|
|
113
|
+
return url;
|
|
114
|
+
}
|
|
115
|
+
//# sourceMappingURL=config.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"config.js","sourceRoot":"","sources":["../src/config.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
import { type ControlReplyFor, type ControlRequest } from "./control_protocol.js";
|
|
2
|
+
export declare class SupervisorOfflineError extends Error {
|
|
3
|
+
readonly sockPath: string;
|
|
4
|
+
constructor(sockPath: string);
|
|
5
|
+
}
|
|
6
|
+
/**
|
|
7
|
+
* Sends a single request and returns the typed reply data.
|
|
8
|
+
*
|
|
9
|
+
* Throws:
|
|
10
|
+
* - `SupervisorOfflineError` when the supervisor isn't reachable.
|
|
11
|
+
* - `Error` from `parseReply` when the reply line is malformed.
|
|
12
|
+
* - The supervisor's own error string when `ok: false`.
|
|
13
|
+
*/
|
|
14
|
+
export declare function callSupervisor<Op extends ControlRequest["op"]>(req: Extract<ControlRequest, {
|
|
15
|
+
op: Op;
|
|
16
|
+
}>): Promise<ControlReplyFor<Op>>;
|
|
17
|
+
/** Returns true when the supervisor is reachable. Used by `/remote-pi
|
|
18
|
+
* daemons` to decide whether to query runtime state or fall back to
|
|
19
|
+
* registry-only listing. */
|
|
20
|
+
export declare function supervisorOnline(): Promise<boolean>;
|
|
@@ -0,0 +1,131 @@
|
|
|
1
|
+
import { existsSync } from "node:fs";
|
|
2
|
+
import { createConnection } from "node:net";
|
|
3
|
+
import { encodeRequest, parseReply, } from "./control_protocol.js";
|
|
4
|
+
import { getSupervisorSockPath } from "./supervisor.js";
|
|
5
|
+
import { usesNamedPipe } from "../session/ipc.js";
|
|
6
|
+
/**
|
|
7
|
+
* Tiny client for the `remote-pi` CLI to call the supervisor over the
|
|
8
|
+
* `~/.pi/remote/supervisor.sock` UDS.
|
|
9
|
+
*
|
|
10
|
+
* Each call opens a fresh connection, sends one request line, reads one
|
|
11
|
+
* reply line, closes. No connection pooling — the CLI is short-lived,
|
|
12
|
+
* latency is dominated by the socket round-trip (<1ms).
|
|
13
|
+
*
|
|
14
|
+
* `SupervisorOfflineError` is the common error: thrown when the socket
|
|
15
|
+
* file is missing OR the connect fails (no listener). The CLI handler
|
|
16
|
+
* formats it as a friendly "Run `remote-pi install` first" message.
|
|
17
|
+
*/
|
|
18
|
+
const CONNECT_TIMEOUT_MS = 1000;
|
|
19
|
+
const REPLY_TIMEOUT_MS = 5000;
|
|
20
|
+
export class SupervisorOfflineError extends Error {
|
|
21
|
+
sockPath;
|
|
22
|
+
constructor(sockPath) {
|
|
23
|
+
super(`Supervisor is not running. UDS not responding at ${sockPath}.\n` +
|
|
24
|
+
"Run `remote-pi install` to set it up, or start it manually with `pi-supervisord`.");
|
|
25
|
+
this.sockPath = sockPath;
|
|
26
|
+
this.name = "SupervisorOfflineError";
|
|
27
|
+
}
|
|
28
|
+
}
|
|
29
|
+
/**
|
|
30
|
+
* Sends a single request and returns the typed reply data.
|
|
31
|
+
*
|
|
32
|
+
* Throws:
|
|
33
|
+
* - `SupervisorOfflineError` when the supervisor isn't reachable.
|
|
34
|
+
* - `Error` from `parseReply` when the reply line is malformed.
|
|
35
|
+
* - The supervisor's own error string when `ok: false`.
|
|
36
|
+
*/
|
|
37
|
+
export async function callSupervisor(req) {
|
|
38
|
+
const sockPath = getSupervisorSockPath();
|
|
39
|
+
// POSIX fast-fail on a missing socket file. Windows pipes have no file —
|
|
40
|
+
// skip the check and let `_connect` fail fast if the pipe isn't there.
|
|
41
|
+
if (!usesNamedPipe() && !existsSync(sockPath))
|
|
42
|
+
throw new SupervisorOfflineError(sockPath);
|
|
43
|
+
const sock = await _connect(sockPath);
|
|
44
|
+
try {
|
|
45
|
+
sock.write(encodeRequest(req));
|
|
46
|
+
const line = await _readLine(sock);
|
|
47
|
+
const reply = parseReply(line);
|
|
48
|
+
if (!reply.ok)
|
|
49
|
+
throw new Error(reply.error);
|
|
50
|
+
return reply.data;
|
|
51
|
+
}
|
|
52
|
+
finally {
|
|
53
|
+
sock.destroy();
|
|
54
|
+
}
|
|
55
|
+
}
|
|
56
|
+
/** Returns true when the supervisor is reachable. Used by `/remote-pi
|
|
57
|
+
* daemons` to decide whether to query runtime state or fall back to
|
|
58
|
+
* registry-only listing. */
|
|
59
|
+
export async function supervisorOnline() {
|
|
60
|
+
const sockPath = getSupervisorSockPath();
|
|
61
|
+
if (!usesNamedPipe() && !existsSync(sockPath))
|
|
62
|
+
return false;
|
|
63
|
+
try {
|
|
64
|
+
const sock = await _connect(sockPath);
|
|
65
|
+
sock.destroy();
|
|
66
|
+
return true;
|
|
67
|
+
}
|
|
68
|
+
catch {
|
|
69
|
+
return false;
|
|
70
|
+
}
|
|
71
|
+
}
|
|
72
|
+
// ── internals ───────────────────────────────────────────────────────────────
|
|
73
|
+
function _connect(sockPath) {
|
|
74
|
+
return new Promise((resolve, reject) => {
|
|
75
|
+
const sock = createConnection({ path: sockPath });
|
|
76
|
+
let settled = false;
|
|
77
|
+
const timer = setTimeout(() => {
|
|
78
|
+
if (settled)
|
|
79
|
+
return;
|
|
80
|
+
settled = true;
|
|
81
|
+
sock.destroy();
|
|
82
|
+
reject(new SupervisorOfflineError(sockPath));
|
|
83
|
+
}, CONNECT_TIMEOUT_MS);
|
|
84
|
+
sock.once("connect", () => {
|
|
85
|
+
if (settled)
|
|
86
|
+
return;
|
|
87
|
+
settled = true;
|
|
88
|
+
clearTimeout(timer);
|
|
89
|
+
sock.setEncoding("utf8");
|
|
90
|
+
resolve(sock);
|
|
91
|
+
});
|
|
92
|
+
sock.once("error", () => {
|
|
93
|
+
if (settled)
|
|
94
|
+
return;
|
|
95
|
+
settled = true;
|
|
96
|
+
clearTimeout(timer);
|
|
97
|
+
reject(new SupervisorOfflineError(sockPath));
|
|
98
|
+
});
|
|
99
|
+
});
|
|
100
|
+
}
|
|
101
|
+
function _readLine(sock) {
|
|
102
|
+
return new Promise((resolve, reject) => {
|
|
103
|
+
let buf = "";
|
|
104
|
+
const timer = setTimeout(() => {
|
|
105
|
+
reject(new Error("supervisor reply timeout"));
|
|
106
|
+
sock.destroy();
|
|
107
|
+
}, REPLY_TIMEOUT_MS);
|
|
108
|
+
sock.on("data", (chunk) => {
|
|
109
|
+
buf += chunk;
|
|
110
|
+
const nl = buf.indexOf("\n");
|
|
111
|
+
if (nl >= 0) {
|
|
112
|
+
clearTimeout(timer);
|
|
113
|
+
resolve(buf.slice(0, nl));
|
|
114
|
+
}
|
|
115
|
+
});
|
|
116
|
+
sock.on("end", () => {
|
|
117
|
+
clearTimeout(timer);
|
|
118
|
+
const nl = buf.indexOf("\n");
|
|
119
|
+
if (nl >= 0)
|
|
120
|
+
return resolve(buf.slice(0, nl));
|
|
121
|
+
if (buf.length > 0)
|
|
122
|
+
return resolve(buf);
|
|
123
|
+
reject(new Error("supervisor closed connection without replying"));
|
|
124
|
+
});
|
|
125
|
+
sock.on("error", (err) => {
|
|
126
|
+
clearTimeout(timer);
|
|
127
|
+
reject(err);
|
|
128
|
+
});
|
|
129
|
+
});
|
|
130
|
+
}
|
|
131
|
+
//# sourceMappingURL=client.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"client.js","sourceRoot":"","sources":["../../src/daemon/client.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,176 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* CLI ↔ supervisor IPC contract for `~/.pi/remote/supervisor.sock`.
|
|
3
|
+
*
|
|
4
|
+
* Framing: one JSON object per line, newline-terminated. The CLI sends a
|
|
5
|
+
* single `ControlRequest`, the supervisor sends a single `ControlReply`,
|
|
6
|
+
* both close the connection. No multiplexing, no streaming — each command
|
|
7
|
+
* is a short round-trip.
|
|
8
|
+
*
|
|
9
|
+
* Plan/26 W2. The Pi RPC protocol (`pi --mode rpc`) used by the daemon
|
|
10
|
+
* children themselves is a separate contract — see
|
|
11
|
+
* `node_modules/@earendil-works/pi-coding-agent/dist/modes/rpc/rpc-types.d.ts`.
|
|
12
|
+
* This file is strictly the supervisor's own control plane.
|
|
13
|
+
*/
|
|
14
|
+
import type { CronJob } from "./cron_registry.js";
|
|
15
|
+
import type { CronLogEntry } from "./cron_log.js";
|
|
16
|
+
/** Per-daemon runtime state observable through the supervisor. */
|
|
17
|
+
export type DaemonState = "running" | "stopped" | "starting" | "crashed";
|
|
18
|
+
export interface DaemonInfo {
|
|
19
|
+
id: string;
|
|
20
|
+
cwd: string;
|
|
21
|
+
name: string;
|
|
22
|
+
state: DaemonState;
|
|
23
|
+
pid?: number;
|
|
24
|
+
uptime_s?: number;
|
|
25
|
+
restart_count?: number;
|
|
26
|
+
}
|
|
27
|
+
/** Requests sent CLI → supervisor. */
|
|
28
|
+
export type ControlRequest = {
|
|
29
|
+
op: "list";
|
|
30
|
+
} | {
|
|
31
|
+
op: "status";
|
|
32
|
+
} | {
|
|
33
|
+
op: "start_all";
|
|
34
|
+
} | {
|
|
35
|
+
op: "start";
|
|
36
|
+
id: string;
|
|
37
|
+
} | {
|
|
38
|
+
op: "stop_all";
|
|
39
|
+
} | {
|
|
40
|
+
op: "stop";
|
|
41
|
+
id: string;
|
|
42
|
+
} | {
|
|
43
|
+
op: "restart_all";
|
|
44
|
+
} | {
|
|
45
|
+
op: "restart";
|
|
46
|
+
id: string;
|
|
47
|
+
} | {
|
|
48
|
+
op: "send";
|
|
49
|
+
id: string;
|
|
50
|
+
text: string;
|
|
51
|
+
} | {
|
|
52
|
+
op: "register";
|
|
53
|
+
cwd: string;
|
|
54
|
+
} | {
|
|
55
|
+
op: "unregister";
|
|
56
|
+
id: string;
|
|
57
|
+
} | {
|
|
58
|
+
op: "cron_add";
|
|
59
|
+
daemon_id: string;
|
|
60
|
+
schedule: string;
|
|
61
|
+
prompt: string;
|
|
62
|
+
tz?: string;
|
|
63
|
+
skip_if_busy?: boolean;
|
|
64
|
+
wake?: boolean;
|
|
65
|
+
catchup?: boolean;
|
|
66
|
+
} | {
|
|
67
|
+
op: "cron_list";
|
|
68
|
+
} | {
|
|
69
|
+
op: "cron_remove";
|
|
70
|
+
job_id: string;
|
|
71
|
+
} | {
|
|
72
|
+
op: "cron_enable";
|
|
73
|
+
job_id: string;
|
|
74
|
+
enabled: boolean;
|
|
75
|
+
} | {
|
|
76
|
+
op: "cron_run";
|
|
77
|
+
job_id: string;
|
|
78
|
+
} | {
|
|
79
|
+
op: "cron_log";
|
|
80
|
+
job_id?: string;
|
|
81
|
+
tail?: number;
|
|
82
|
+
};
|
|
83
|
+
/** Replies sent supervisor → CLI. Tagged by `ok` boolean. */
|
|
84
|
+
export type ControlReply<T = unknown> = {
|
|
85
|
+
ok: true;
|
|
86
|
+
data?: T;
|
|
87
|
+
} | {
|
|
88
|
+
ok: false;
|
|
89
|
+
error: string;
|
|
90
|
+
};
|
|
91
|
+
/**
|
|
92
|
+
* Response shapes per op. Keep in sync with the supervisor handlers in
|
|
93
|
+
* `daemon/supervisor.ts`. Used for typed client calls.
|
|
94
|
+
*/
|
|
95
|
+
export interface ControlReplyShapes {
|
|
96
|
+
list: {
|
|
97
|
+
daemons: DaemonInfo[];
|
|
98
|
+
};
|
|
99
|
+
status: {
|
|
100
|
+
daemons: DaemonInfo[];
|
|
101
|
+
};
|
|
102
|
+
start_all: {
|
|
103
|
+
started: string[];
|
|
104
|
+
already_running: string[];
|
|
105
|
+
};
|
|
106
|
+
start: {
|
|
107
|
+
id: string;
|
|
108
|
+
state: DaemonState;
|
|
109
|
+
started: boolean;
|
|
110
|
+
};
|
|
111
|
+
stop_all: {
|
|
112
|
+
stopped: string[];
|
|
113
|
+
already_stopped: string[];
|
|
114
|
+
};
|
|
115
|
+
stop: {
|
|
116
|
+
id: string;
|
|
117
|
+
state: DaemonState;
|
|
118
|
+
stopped: boolean;
|
|
119
|
+
};
|
|
120
|
+
restart_all: {
|
|
121
|
+
restarted: string[];
|
|
122
|
+
};
|
|
123
|
+
restart: {
|
|
124
|
+
id: string;
|
|
125
|
+
state: DaemonState;
|
|
126
|
+
restarted: boolean;
|
|
127
|
+
};
|
|
128
|
+
send: {
|
|
129
|
+
id: string;
|
|
130
|
+
delivered: boolean;
|
|
131
|
+
};
|
|
132
|
+
register: {
|
|
133
|
+
id: string;
|
|
134
|
+
cwd: string;
|
|
135
|
+
};
|
|
136
|
+
unregister: {
|
|
137
|
+
removed: boolean;
|
|
138
|
+
cwd?: string;
|
|
139
|
+
};
|
|
140
|
+
cron_add: {
|
|
141
|
+
job: CronJobView;
|
|
142
|
+
};
|
|
143
|
+
cron_list: {
|
|
144
|
+
jobs: CronJobView[];
|
|
145
|
+
};
|
|
146
|
+
cron_remove: {
|
|
147
|
+
removed: boolean;
|
|
148
|
+
};
|
|
149
|
+
cron_enable: {
|
|
150
|
+
job_id: string;
|
|
151
|
+
enabled: boolean;
|
|
152
|
+
updated: boolean;
|
|
153
|
+
};
|
|
154
|
+
cron_run: {
|
|
155
|
+
job_id: string;
|
|
156
|
+
result: string;
|
|
157
|
+
};
|
|
158
|
+
cron_log: {
|
|
159
|
+
entries: CronLogEntry[];
|
|
160
|
+
};
|
|
161
|
+
}
|
|
162
|
+
/** A cron job plus its computed `next_run` (ISO), for `cron list`. */
|
|
163
|
+
export type CronJobView = CronJob & {
|
|
164
|
+
next_run?: string | null;
|
|
165
|
+
};
|
|
166
|
+
/** Convenience for typed `Client.request<...>("op")` calls. */
|
|
167
|
+
export type ControlReplyFor<Op extends ControlRequest["op"]> = Op extends keyof ControlReplyShapes ? ControlReplyShapes[Op] : never;
|
|
168
|
+
export declare function encodeRequest(req: ControlRequest): string;
|
|
169
|
+
export declare function encodeReply<T>(reply: ControlReply<T>): string;
|
|
170
|
+
/**
|
|
171
|
+
* Parses a single JSON line into a request. Throws on malformed input —
|
|
172
|
+
* the supervisor catches and replies `{ok:false, error}` so the client
|
|
173
|
+
* gets a clean error rather than an unframed disconnect.
|
|
174
|
+
*/
|
|
175
|
+
export declare function parseRequest(line: string): ControlRequest;
|
|
176
|
+
export declare function parseReply(line: string): ControlReply<unknown>;
|
|
@@ -0,0 +1,63 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* CLI ↔ supervisor IPC contract for `~/.pi/remote/supervisor.sock`.
|
|
3
|
+
*
|
|
4
|
+
* Framing: one JSON object per line, newline-terminated. The CLI sends a
|
|
5
|
+
* single `ControlRequest`, the supervisor sends a single `ControlReply`,
|
|
6
|
+
* both close the connection. No multiplexing, no streaming — each command
|
|
7
|
+
* is a short round-trip.
|
|
8
|
+
*
|
|
9
|
+
* Plan/26 W2. The Pi RPC protocol (`pi --mode rpc`) used by the daemon
|
|
10
|
+
* children themselves is a separate contract — see
|
|
11
|
+
* `node_modules/@earendil-works/pi-coding-agent/dist/modes/rpc/rpc-types.d.ts`.
|
|
12
|
+
* This file is strictly the supervisor's own control plane.
|
|
13
|
+
*/
|
|
14
|
+
// ── Serialization helpers ────────────────────────────────────────────────────
|
|
15
|
+
const TRAILING_NEWLINE = "\n";
|
|
16
|
+
export function encodeRequest(req) {
|
|
17
|
+
return JSON.stringify(req) + TRAILING_NEWLINE;
|
|
18
|
+
}
|
|
19
|
+
export function encodeReply(reply) {
|
|
20
|
+
return JSON.stringify(reply) + TRAILING_NEWLINE;
|
|
21
|
+
}
|
|
22
|
+
/**
|
|
23
|
+
* Parses a single JSON line into a request. Throws on malformed input —
|
|
24
|
+
* the supervisor catches and replies `{ok:false, error}` so the client
|
|
25
|
+
* gets a clean error rather than an unframed disconnect.
|
|
26
|
+
*/
|
|
27
|
+
export function parseRequest(line) {
|
|
28
|
+
let obj;
|
|
29
|
+
try {
|
|
30
|
+
obj = JSON.parse(line);
|
|
31
|
+
}
|
|
32
|
+
catch (e) {
|
|
33
|
+
throw new Error(`malformed control request: ${e.message}`);
|
|
34
|
+
}
|
|
35
|
+
if (!obj || typeof obj !== "object") {
|
|
36
|
+
throw new Error("control request must be a JSON object");
|
|
37
|
+
}
|
|
38
|
+
const op = obj.op;
|
|
39
|
+
if (typeof op !== "string") {
|
|
40
|
+
throw new Error("control request missing string `op` field");
|
|
41
|
+
}
|
|
42
|
+
// We don't validate every field shape here — supervisor handlers do it
|
|
43
|
+
// per-op since the error messages are more specific that way.
|
|
44
|
+
return obj;
|
|
45
|
+
}
|
|
46
|
+
export function parseReply(line) {
|
|
47
|
+
let obj;
|
|
48
|
+
try {
|
|
49
|
+
obj = JSON.parse(line);
|
|
50
|
+
}
|
|
51
|
+
catch (e) {
|
|
52
|
+
throw new Error(`malformed control reply: ${e.message}`);
|
|
53
|
+
}
|
|
54
|
+
if (!obj || typeof obj !== "object") {
|
|
55
|
+
throw new Error("control reply must be a JSON object");
|
|
56
|
+
}
|
|
57
|
+
const ok = obj.ok;
|
|
58
|
+
if (typeof ok !== "boolean") {
|
|
59
|
+
throw new Error("control reply missing boolean `ok` field");
|
|
60
|
+
}
|
|
61
|
+
return obj;
|
|
62
|
+
}
|
|
63
|
+
//# sourceMappingURL=control_protocol.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"control_protocol.js","sourceRoot":"","sources":["../../src/daemon/control_protocol.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;GAYG;AA4EH,gFAAgF;AAEhF,MAAM,gBAAgB,GAAG,IAAI,CAAC;AAE9B,MAAM,UAAU,aAAa,CAAC,GAAmB;IAC/C,OAAO,IAAI,CAAC,SAAS,CAAC,GAAG,CAAC,GAAG,gBAAgB,CAAC;AAChD,CAAC;AAED,MAAM,UAAU,WAAW,CAAI,KAAsB;IACnD,OAAO,IAAI,CAAC,SAAS,CAAC,KAAK,CAAC,GAAG,gBAAgB,CAAC;AAClD,CAAC;AAED;;;;GAIG;AACH,MAAM,UAAU,YAAY,CAAC,IAAY;IACvC,IAAI,GAAY,CAAC;IACjB,IAAI,CAAC;QAAC,GAAG,GAAG,IAAI,CAAC,KAAK,CAAC,IAAI,CAAC,CAAC;IAAC,CAAC;IAC/B,OAAO,CAAC,EAAE,CAAC;QAAC,MAAM,IAAI,KAAK,CAAC,8BAA+B,CAAW,CAAC,OAAO,EAAE,CAAC,CAAC;IAAC,CAAC;IACpF,IAAI,CAAC,GAAG,IAAI,OAAO,GAAG,KAAK,QAAQ,EAAE,CAAC;QACpC,MAAM,IAAI,KAAK,CAAC,uCAAuC,CAAC,CAAC;IAC3D,CAAC;IACD,MAAM,EAAE,GAAI,GAAwB,CAAC,EAAE,CAAC;IACxC,IAAI,OAAO,EAAE,KAAK,QAAQ,EAAE,CAAC;QAC3B,MAAM,IAAI,KAAK,CAAC,2CAA2C,CAAC,CAAC;IAC/D,CAAC;IACD,uEAAuE;IACvE,8DAA8D;IAC9D,OAAO,GAAqB,CAAC;AAC/B,CAAC;AAED,MAAM,UAAU,UAAU,CAAC,IAAY;IACrC,IAAI,GAAY,CAAC;IACjB,IAAI,CAAC;QAAC,GAAG,GAAG,IAAI,CAAC,KAAK,CAAC,IAAI,CAAC,CAAC;IAAC,CAAC;IAC/B,OAAO,CAAC,EAAE,CAAC;QAAC,MAAM,IAAI,KAAK,CAAC,4BAA6B,CAAW,CAAC,OAAO,EAAE,CAAC,CAAC;IAAC,CAAC;IAClF,IAAI,CAAC,GAAG,IAAI,OAAO,GAAG,KAAK,QAAQ,EAAE,CAAC;QACpC,MAAM,IAAI,KAAK,CAAC,qCAAqC,CAAC,CAAC;IACzD,CAAC;IACD,MAAM,EAAE,GAAI,GAAwB,CAAC,EAAE,CAAC;IACxC,IAAI,OAAO,EAAE,KAAK,SAAS,EAAE,CAAC;QAC5B,MAAM,IAAI,KAAK,CAAC,0CAA0C,CAAC,CAAC;IAC9D,CAAC;IACD,OAAO,GAA4B,CAAC;AACtC,CAAC"}
|
|
@@ -0,0 +1,45 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Append-only audit trail for cron fires at `~/.pi/remote/cron.jsonl`.
|
|
3
|
+
*
|
|
4
|
+
* One JSON line per scheduler decision — **every fire AND every skip** — so an
|
|
5
|
+
* operator can see exactly what ran and what didn't (the agent's output goes
|
|
6
|
+
* fire-and-forget to the relay/mesh, so the dispatch itself needs its own
|
|
7
|
+
* trail). Plan/39 decision E.
|
|
8
|
+
*/
|
|
9
|
+
/** Outcome of a single `fireJob` decision. */
|
|
10
|
+
export type CronResult = "delivered" | "deliver_failed" | "woke_and_delivered" | "skipped_busy" | "skipped_down" | "skipped_disabled";
|
|
11
|
+
export interface CronLogEntry {
|
|
12
|
+
/** epoch ms */
|
|
13
|
+
ts: number;
|
|
14
|
+
job_id: string;
|
|
15
|
+
daemon_id: string;
|
|
16
|
+
schedule: string;
|
|
17
|
+
/** true when a prompt was actually sent (delivered / woke_and_delivered). */
|
|
18
|
+
fired: boolean;
|
|
19
|
+
result: CronResult;
|
|
20
|
+
/** First chars of the prompt, for at-a-glance log reading. */
|
|
21
|
+
prompt_preview: string;
|
|
22
|
+
}
|
|
23
|
+
/** Test/diag-only: the on-disk path. */
|
|
24
|
+
export declare function cronLogPath(): string;
|
|
25
|
+
/** Maps a result to whether a prompt was actually delivered. */
|
|
26
|
+
export declare function firedFor(result: CronResult): boolean;
|
|
27
|
+
/**
|
|
28
|
+
* Appends one entry. Best-effort: creates the parent dir + file when absent;
|
|
29
|
+
* never throws into the scheduler (a logging failure must not abort a fire).
|
|
30
|
+
*/
|
|
31
|
+
export declare function appendCronLog(entry: {
|
|
32
|
+
job_id: string;
|
|
33
|
+
daemon_id: string;
|
|
34
|
+
schedule: string;
|
|
35
|
+
result: CronResult;
|
|
36
|
+
prompt: string;
|
|
37
|
+
}): void;
|
|
38
|
+
/**
|
|
39
|
+
* Reads the log, newest-last. Optional `jobId` filter and `tail` (last N).
|
|
40
|
+
* Missing file → []. Malformed lines are skipped.
|
|
41
|
+
*/
|
|
42
|
+
export declare function readCronLog(opts?: {
|
|
43
|
+
jobId?: string;
|
|
44
|
+
tail?: number;
|
|
45
|
+
}): CronLogEntry[];
|
|
@@ -0,0 +1,72 @@
|
|
|
1
|
+
import { appendFileSync, existsSync, mkdirSync, readFileSync } from "node:fs";
|
|
2
|
+
import { homedir } from "node:os";
|
|
3
|
+
import { dirname, join } from "node:path";
|
|
4
|
+
const PREVIEW_LEN = 80;
|
|
5
|
+
function logPath() {
|
|
6
|
+
const root = process.env["REMOTE_PI_HOME"] || homedir();
|
|
7
|
+
return join(root, ".pi", "remote", "cron.jsonl");
|
|
8
|
+
}
|
|
9
|
+
/** Test/diag-only: the on-disk path. */
|
|
10
|
+
export function cronLogPath() {
|
|
11
|
+
return logPath();
|
|
12
|
+
}
|
|
13
|
+
/** Maps a result to whether a prompt was actually delivered. */
|
|
14
|
+
export function firedFor(result) {
|
|
15
|
+
return result === "delivered" || result === "woke_and_delivered";
|
|
16
|
+
}
|
|
17
|
+
/**
|
|
18
|
+
* Appends one entry. Best-effort: creates the parent dir + file when absent;
|
|
19
|
+
* never throws into the scheduler (a logging failure must not abort a fire).
|
|
20
|
+
*/
|
|
21
|
+
export function appendCronLog(entry) {
|
|
22
|
+
const line = JSON.stringify({
|
|
23
|
+
ts: Date.now(),
|
|
24
|
+
job_id: entry.job_id,
|
|
25
|
+
daemon_id: entry.daemon_id,
|
|
26
|
+
schedule: entry.schedule,
|
|
27
|
+
fired: firedFor(entry.result),
|
|
28
|
+
result: entry.result,
|
|
29
|
+
prompt_preview: entry.prompt.slice(0, PREVIEW_LEN),
|
|
30
|
+
}) + "\n";
|
|
31
|
+
try {
|
|
32
|
+
mkdirSync(dirname(logPath()), { recursive: true });
|
|
33
|
+
appendFileSync(logPath(), line, "utf8");
|
|
34
|
+
}
|
|
35
|
+
catch {
|
|
36
|
+
/* audit is best-effort — don't break the scheduler on a write error */
|
|
37
|
+
}
|
|
38
|
+
}
|
|
39
|
+
/**
|
|
40
|
+
* Reads the log, newest-last. Optional `jobId` filter and `tail` (last N).
|
|
41
|
+
* Missing file → []. Malformed lines are skipped.
|
|
42
|
+
*/
|
|
43
|
+
export function readCronLog(opts = {}) {
|
|
44
|
+
if (!existsSync(logPath()))
|
|
45
|
+
return [];
|
|
46
|
+
let raw;
|
|
47
|
+
try {
|
|
48
|
+
raw = readFileSync(logPath(), "utf8");
|
|
49
|
+
}
|
|
50
|
+
catch {
|
|
51
|
+
return [];
|
|
52
|
+
}
|
|
53
|
+
const entries = [];
|
|
54
|
+
for (const line of raw.split("\n")) {
|
|
55
|
+
if (!line.trim())
|
|
56
|
+
continue;
|
|
57
|
+
try {
|
|
58
|
+
const e = JSON.parse(line);
|
|
59
|
+
if (opts.jobId && e.job_id !== opts.jobId)
|
|
60
|
+
continue;
|
|
61
|
+
entries.push(e);
|
|
62
|
+
}
|
|
63
|
+
catch {
|
|
64
|
+
/* skip malformed */
|
|
65
|
+
}
|
|
66
|
+
}
|
|
67
|
+
if (opts.tail !== undefined && opts.tail >= 0 && entries.length > opts.tail) {
|
|
68
|
+
return entries.slice(entries.length - opts.tail);
|
|
69
|
+
}
|
|
70
|
+
return entries;
|
|
71
|
+
}
|
|
72
|
+
//# sourceMappingURL=cron_log.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"cron_log.js","sourceRoot":"","sources":["../../src/daemon/cron_log.ts"],"names":[],"mappings":"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"}
|