@yefengr/remote-pi 0.7.4 → 0.7.6
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/README.md +81 -586
- package/dist/bin/supervisord.js +4 -5
- package/dist/bin/supervisord.js.map +1 -1
- package/dist/config.d.ts +1 -1
- package/dist/config.js +2 -2
- package/dist/daemon/cli-links.d.ts +30 -0
- package/dist/daemon/cli-links.js +134 -0
- package/dist/daemon/cli-links.js.map +1 -0
- package/dist/daemon/commands.d.ts +27 -0
- package/dist/daemon/commands.js +295 -0
- package/dist/daemon/commands.js.map +1 -0
- package/dist/daemon/control_protocol.d.ts +53 -31
- package/dist/daemon/control_protocol.js +8 -31
- package/dist/daemon/control_protocol.js.map +1 -1
- package/dist/daemon/cron_log.d.ts +2 -23
- package/dist/daemon/cron_log.js +10 -25
- package/dist/daemon/cron_log.js.map +1 -1
- package/dist/daemon/cron_registry.d.ts +3 -40
- package/dist/daemon/cron_registry.js +58 -103
- package/dist/daemon/cron_registry.js.map +1 -1
- package/dist/daemon/install-runtime.d.ts +10 -0
- package/dist/daemon/install-runtime.js +81 -0
- package/dist/daemon/install-runtime.js.map +1 -0
- package/dist/daemon/install.d.ts +3 -63
- package/dist/daemon/install.js +5 -289
- package/dist/daemon/install.js.map +1 -1
- package/dist/daemon/preflight.d.ts +51 -0
- package/dist/daemon/preflight.js +136 -0
- package/dist/daemon/preflight.js.map +1 -0
- package/dist/daemon/registry.d.ts +33 -45
- package/dist/daemon/registry.js +171 -117
- package/dist/daemon/registry.js.map +1 -1
- package/dist/daemon/rpc_child.d.ts +92 -146
- package/dist/daemon/rpc_child.js +336 -263
- package/dist/daemon/rpc_child.js.map +1 -1
- package/dist/daemon/status.d.ts +45 -0
- package/dist/daemon/status.js +87 -0
- package/dist/daemon/status.js.map +1 -0
- package/dist/daemon/supervisor-ipc.d.ts +2 -0
- package/dist/daemon/supervisor-ipc.js +20 -0
- package/dist/daemon/supervisor-ipc.js.map +1 -0
- package/dist/daemon/supervisor.d.ts +48 -71
- package/dist/daemon/supervisor.js +430 -420
- package/dist/daemon/supervisor.js.map +1 -1
- package/dist/extension_ui_bridge.d.ts +4 -3
- package/dist/extension_ui_bridge.js +10 -0
- package/dist/extension_ui_bridge.js.map +1 -1
- package/dist/index.d.ts +25 -230
- package/dist/index.js +515 -5553
- package/dist/index.js.map +1 -1
- package/dist/pairing/crypto.d.ts +8 -1
- package/dist/pairing/crypto.js +56 -6
- package/dist/pairing/crypto.js.map +1 -1
- package/dist/pairing/owner_storage.d.ts +42 -0
- package/dist/pairing/owner_storage.js +207 -0
- package/dist/pairing/owner_storage.js.map +1 -0
- package/dist/pairing/qr.d.ts +3 -42
- package/dist/pairing/qr.js +9 -59
- package/dist/pairing/qr.js.map +1 -1
- package/dist/pairing/storage.d.ts +3 -45
- package/dist/pairing/storage.js +10 -223
- package/dist/pairing/storage.js.map +1 -1
- package/dist/protocol/types.d.ts +1 -1
- package/dist/protocol/v2/schemas.d.ts +1 -1
- package/dist/protocol/v2/schemas.js +1 -1
- package/dist/protocol/v2/schemas.js.map +1 -1
- package/dist/runtime/owner_router.d.ts +16 -0
- package/dist/runtime/owner_router.js +37 -0
- package/dist/runtime/owner_router.js.map +1 -0
- package/dist/session/ipc.d.ts +1 -22
- package/dist/session/ipc.js +4 -11
- package/dist/session/ipc.js.map +1 -1
- package/dist/session/local_config.d.ts +4 -47
- package/dist/session/local_config.js +19 -101
- package/dist/session/local_config.js.map +1 -1
- package/dist/transport/peer_channel.d.ts +25 -39
- package/dist/transport/peer_channel.js +74 -131
- package/dist/transport/peer_channel.js.map +1 -1
- package/dist/transport/relay_client.d.ts +26 -70
- package/dist/transport/relay_client.js +37 -106
- package/dist/transport/relay_client.js.map +1 -1
- package/docs/daemon.md +91 -207
- package/package.json +21 -17
- package/dist/daemon/id.d.ts +0 -24
- package/dist/daemon/id.js +0 -36
- package/dist/daemon/id.js.map +0 -1
- package/dist/mcp/mesh_result.d.ts +0 -10
- package/dist/mcp/mesh_result.js +0 -42
- package/dist/mcp/mesh_result.js.map +0 -1
- package/dist/mcp/mesh_server.d.ts +0 -16
- package/dist/mcp/mesh_server.js +0 -327
- package/dist/mcp/mesh_server.js.map +0 -1
- package/dist/mesh/canonical.d.ts +0 -30
- package/dist/mesh/canonical.js +0 -61
- package/dist/mesh/canonical.js.map +0 -1
- package/dist/mesh/client.d.ts +0 -18
- package/dist/mesh/client.js +0 -125
- package/dist/mesh/client.js.map +0 -1
- package/dist/mesh/encoding.d.ts +0 -38
- package/dist/mesh/encoding.js +0 -216
- package/dist/mesh/encoding.js.map +0 -1
- package/dist/mesh/self_revoke.d.ts +0 -73
- package/dist/mesh/self_revoke.js +0 -401
- package/dist/mesh/self_revoke.js.map +0 -1
- package/dist/mesh/siblings.d.ts +0 -49
- package/dist/mesh/siblings.js +0 -159
- package/dist/mesh/siblings.js.map +0 -1
- package/dist/mesh/types.d.ts +0 -34
- package/dist/mesh/types.js +0 -11
- package/dist/mesh/types.js.map +0 -1
- package/dist/mesh/verify.d.ts +0 -17
- package/dist/mesh/verify.js +0 -86
- package/dist/mesh/verify.js.map +0 -1
- package/dist/rooms.d.ts +0 -29
- package/dist/rooms.js +0 -57
- package/dist/rooms.js.map +0 -1
- package/dist/session/bridge.d.ts +0 -37
- package/dist/session/bridge.js +0 -130
- package/dist/session/bridge.js.map +0 -1
- package/dist/session/broker.d.ts +0 -189
- package/dist/session/broker.js +0 -507
- package/dist/session/broker.js.map +0 -1
- package/dist/session/broker_remote.d.ts +0 -74
- package/dist/session/broker_remote.js +0 -583
- package/dist/session/broker_remote.js.map +0 -1
- package/dist/session/cwd_lock.d.ts +0 -33
- package/dist/session/cwd_lock.js +0 -126
- package/dist/session/cwd_lock.js.map +0 -1
- package/dist/session/envelope.d.ts +0 -33
- package/dist/session/envelope.js +0 -117
- package/dist/session/envelope.js.map +0 -1
- package/dist/session/global_config.d.ts +0 -33
- package/dist/session/global_config.js +0 -73
- package/dist/session/global_config.js.map +0 -1
- package/dist/session/leader_election.d.ts +0 -32
- package/dist/session/leader_election.js +0 -106
- package/dist/session/leader_election.js.map +0 -1
- package/dist/session/mesh_node.d.ts +0 -116
- package/dist/session/mesh_node.js +0 -573
- package/dist/session/mesh_node.js.map +0 -1
- package/dist/session/peer.d.ts +0 -139
- package/dist/session/peer.js +0 -451
- package/dist/session/peer.js.map +0 -1
- package/dist/session/peer_inventory.d.ts +0 -13
- package/dist/session/peer_inventory.js +0 -48
- package/dist/session/peer_inventory.js.map +0 -1
- package/dist/session/peer_limits.d.ts +0 -9
- package/dist/session/peer_limits.js +0 -21
- package/dist/session/peer_limits.js.map +0 -1
- package/dist/session/setup_wizard.d.ts +0 -39
- package/dist/session/setup_wizard.js +0 -63
- package/dist/session/setup_wizard.js.map +0 -1
- package/dist/session/tools.d.ts +0 -24
- package/dist/session/tools.js +0 -252
- package/dist/session/tools.js.map +0 -1
- package/dist/session/wizard.d.ts +0 -13
- package/dist/session/wizard.js +0 -20
- package/dist/session/wizard.js.map +0 -1
- package/dist/transport/pi_forward_client.d.ts +0 -29
- package/dist/transport/pi_forward_client.js +0 -62
- package/dist/transport/pi_forward_client.js.map +0 -1
- package/dist/ui/footer.d.ts +0 -32
- package/dist/ui/footer.js +0 -34
- package/dist/ui/footer.js.map +0 -1
- package/skills/agent-network/SKILL.md +0 -310
package/dist/daemon/registry.js
CHANGED
|
@@ -1,152 +1,206 @@
|
|
|
1
|
-
import { existsSync, mkdirSync, readFileSync, realpathSync, writeFileSync } from "node:fs";
|
|
1
|
+
import { existsSync, mkdirSync, readFileSync, realpathSync, renameSync, writeFileSync } from "node:fs";
|
|
2
|
+
import { randomUUID } from "node:crypto";
|
|
2
3
|
import { homedir } from "node:os";
|
|
3
4
|
import { dirname, isAbsolute, join, resolve as resolvePath } from "node:path";
|
|
4
|
-
import { daemonIdForCwd } from "./id.js";
|
|
5
5
|
import { defaultAgentName } from "../session/local_config.js";
|
|
6
6
|
/**
|
|
7
|
-
*
|
|
8
|
-
*
|
|
9
|
-
*
|
|
10
|
-
* Schema rationale (decision Q in plan/26): the registry stores **only**
|
|
11
|
-
* `cwd` per entry. Everything else (agent_name, auto_start_relay, etc.)
|
|
12
|
-
* is the cwd's local config at `<cwd>/.pi/remote-pi/config.json` — single
|
|
13
|
-
* source of truth, no duplication. The daemon `id` is *derived* from cwd
|
|
14
|
-
* via `daemonIdForCwd`, never persisted.
|
|
15
|
-
*
|
|
16
|
-
* Cwds are always normalized to an **absolute realpath** before storage.
|
|
17
|
-
* A user typing `/remote-pi create ~/Movies` or `/remote-pi create .`
|
|
18
|
-
* results in the same entry as `/remote-pi create /Users/x/Movies` — no
|
|
19
|
-
* surprise duplicates, symlinks collapse correctly.
|
|
7
|
+
* A malformed or old registry is an operator-visible error, not an empty
|
|
8
|
+
* registry. Treating it as empty could overwrite valid registrations on the
|
|
9
|
+
* next save, which is destructive.
|
|
20
10
|
*/
|
|
21
|
-
|
|
22
|
-
|
|
11
|
+
export class RegistrySchemaError extends Error {
|
|
12
|
+
code;
|
|
13
|
+
constructor(code, message) {
|
|
14
|
+
super(message);
|
|
15
|
+
this.code = code;
|
|
16
|
+
this.name = "RegistrySchemaError";
|
|
17
|
+
}
|
|
18
|
+
}
|
|
23
19
|
function registryPathInternal() {
|
|
24
20
|
const root = process.env["REMOTE_PI_HOME"] || homedir();
|
|
25
21
|
return join(root, ".pi", "remote", "daemons.json");
|
|
26
22
|
}
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
*
|
|
32
|
-
* `/remote-pi create` always stores normalized paths so two registrations
|
|
33
|
-
* of the same logical folder via different aliases produce a single entry.
|
|
34
|
-
*/
|
|
23
|
+
function emptyRegistry() {
|
|
24
|
+
return { version: 2, daemons: [] };
|
|
25
|
+
}
|
|
26
|
+
/** Canonicalizes a supplied cwd and rejects nonexistent paths. */
|
|
35
27
|
export function normalizeCwd(input) {
|
|
36
|
-
if (!input || !input.trim())
|
|
28
|
+
if (!input || !input.trim())
|
|
37
29
|
throw new Error("cwd is required");
|
|
30
|
+
let path = input.trim();
|
|
31
|
+
if (path === "~")
|
|
32
|
+
path = homedir();
|
|
33
|
+
else if (path.startsWith("~/"))
|
|
34
|
+
path = join(homedir(), path.slice(2));
|
|
35
|
+
if (!isAbsolute(path))
|
|
36
|
+
path = resolvePath(process.cwd(), path);
|
|
37
|
+
return realpathSync(path);
|
|
38
|
+
}
|
|
39
|
+
function registryError(message, code = "registry_schema_invalid") {
|
|
40
|
+
throw new RegistrySchemaError(code, message);
|
|
41
|
+
}
|
|
42
|
+
function isOpaqueId(value) {
|
|
43
|
+
// UUID is generated by this registry. Accept UUID variants to keep the
|
|
44
|
+
// persisted identifier opaque without coupling validation to a display form.
|
|
45
|
+
return typeof value === "string" && /^[0-9a-f]{8}-[0-9a-f]{4}-[1-8][0-9a-f]{3}-[89ab][0-9a-f]{3}-[0-9a-f]{12}$/i.test(value);
|
|
46
|
+
}
|
|
47
|
+
function parseEntry(value, index) {
|
|
48
|
+
if (!value || typeof value !== "object")
|
|
49
|
+
registryError(`registry daemon[${index}] must be an object`);
|
|
50
|
+
const entry = value;
|
|
51
|
+
if (!isOpaqueId(entry["id"]))
|
|
52
|
+
registryError(`registry daemon[${index}].id must be an opaque UUID`);
|
|
53
|
+
if (typeof entry["cwd"] !== "string" || !isAbsolute(entry["cwd"]) || entry["cwd"].length === 0) {
|
|
54
|
+
registryError(`registry daemon[${index}].cwd must be an absolute path`);
|
|
55
|
+
}
|
|
56
|
+
// A vanished cwd stays loadable long enough for supervisor reconcile to
|
|
57
|
+
// remove it. Existing paths must already be their canonical realpath.
|
|
58
|
+
if (existsSync(entry["cwd"]) && realpathSync(entry["cwd"]) !== entry["cwd"]) {
|
|
59
|
+
registryError(`registry daemon[${index}].cwd must be a filesystem realpath`);
|
|
38
60
|
}
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
if (!
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
return
|
|
49
|
-
|
|
50
|
-
|
|
61
|
+
if (typeof entry["name"] !== "string" || entry["name"].trim().length === 0) {
|
|
62
|
+
registryError(`registry daemon[${index}].name must be a non-empty string`);
|
|
63
|
+
}
|
|
64
|
+
if (entry["desired_state"] !== "running" && entry["desired_state"] !== "stopped") {
|
|
65
|
+
registryError(`registry daemon[${index}].desired_state must be running or stopped`);
|
|
66
|
+
}
|
|
67
|
+
if (typeof entry["created_at"] !== "number" || !Number.isFinite(entry["created_at"]) || entry["created_at"] < 0) {
|
|
68
|
+
registryError(`registry daemon[${index}].created_at must be an epoch timestamp`);
|
|
69
|
+
}
|
|
70
|
+
return {
|
|
71
|
+
id: entry["id"],
|
|
72
|
+
cwd: entry["cwd"],
|
|
73
|
+
name: entry["name"].trim(),
|
|
74
|
+
desired_state: entry["desired_state"],
|
|
75
|
+
created_at: entry["created_at"],
|
|
76
|
+
};
|
|
77
|
+
}
|
|
78
|
+
function validateRegistry(value) {
|
|
79
|
+
if (!value || typeof value !== "object" || Array.isArray(value)) {
|
|
80
|
+
registryError("registry root must be an object");
|
|
81
|
+
}
|
|
82
|
+
const root = value;
|
|
83
|
+
if (root["version"] !== 2) {
|
|
84
|
+
const code = root["version"] === undefined || root["version"] === 1
|
|
85
|
+
? "registry_schema_unsupported"
|
|
86
|
+
: "registry_schema_invalid";
|
|
87
|
+
registryError(`unsupported daemon registry version ${String(root["version"])}; remove or replace ~/.pi/remote/daemons.json before registering daemons`, code);
|
|
88
|
+
}
|
|
89
|
+
if (!Array.isArray(root["daemons"]))
|
|
90
|
+
registryError("registry.daemons must be an array");
|
|
91
|
+
const daemons = root["daemons"].map((entry, index) => parseEntry(entry, index));
|
|
92
|
+
const ids = new Set();
|
|
93
|
+
const cwds = new Set();
|
|
94
|
+
for (const entry of daemons) {
|
|
95
|
+
if (ids.has(entry.id))
|
|
96
|
+
registryError(`registry contains duplicate id ${entry.id}`);
|
|
97
|
+
if (cwds.has(entry.cwd))
|
|
98
|
+
registryError(`registry contains duplicate cwd ${entry.cwd}`);
|
|
99
|
+
ids.add(entry.id);
|
|
100
|
+
cwds.add(entry.cwd);
|
|
101
|
+
}
|
|
102
|
+
return { version: 2, daemons };
|
|
103
|
+
}
|
|
104
|
+
/** Missing file is empty v2; every existing file must be valid v2. */
|
|
51
105
|
export function loadRegistry() {
|
|
52
|
-
|
|
53
|
-
|
|
106
|
+
const path = registryPathInternal();
|
|
107
|
+
if (!existsSync(path))
|
|
108
|
+
return emptyRegistry();
|
|
109
|
+
let raw;
|
|
54
110
|
try {
|
|
55
|
-
|
|
56
|
-
const parsed = JSON.parse(raw);
|
|
57
|
-
if (!parsed || typeof parsed !== "object")
|
|
58
|
-
return { daemons: [] };
|
|
59
|
-
const arr = parsed.daemons;
|
|
60
|
-
if (!Array.isArray(arr))
|
|
61
|
-
return { daemons: [] };
|
|
62
|
-
const daemons = [];
|
|
63
|
-
for (const item of arr) {
|
|
64
|
-
if (!item || typeof item !== "object")
|
|
65
|
-
continue;
|
|
66
|
-
const cwd = item.cwd;
|
|
67
|
-
if (typeof cwd === "string" && cwd.length > 0) {
|
|
68
|
-
const rawName = item.name;
|
|
69
|
-
const entry = { cwd };
|
|
70
|
-
if (typeof rawName === "string" && rawName.length > 0)
|
|
71
|
-
entry.name = rawName;
|
|
72
|
-
daemons.push(entry);
|
|
73
|
-
}
|
|
74
|
-
}
|
|
75
|
-
return { daemons };
|
|
111
|
+
raw = readFileSync(path, "utf8");
|
|
76
112
|
}
|
|
77
|
-
catch {
|
|
78
|
-
|
|
113
|
+
catch (error) {
|
|
114
|
+
throw new RegistrySchemaError("registry_schema_invalid", `cannot read daemon registry: ${String(error)}`);
|
|
79
115
|
}
|
|
116
|
+
let parsed;
|
|
117
|
+
try {
|
|
118
|
+
parsed = JSON.parse(raw);
|
|
119
|
+
}
|
|
120
|
+
catch (error) {
|
|
121
|
+
throw new RegistrySchemaError("registry_schema_invalid", `invalid daemon registry JSON: ${String(error)}`);
|
|
122
|
+
}
|
|
123
|
+
return validateRegistry(parsed);
|
|
80
124
|
}
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
125
|
+
/** Validates before atomically replacing the on-disk registry. */
|
|
126
|
+
export function saveRegistry(registry) {
|
|
127
|
+
const valid = validateRegistry(registry);
|
|
128
|
+
const path = registryPathInternal();
|
|
129
|
+
mkdirSync(dirname(path), { recursive: true });
|
|
130
|
+
const temp = `${path}.${process.pid}.${randomUUID()}.tmp`;
|
|
131
|
+
try {
|
|
132
|
+
writeFileSync(temp, JSON.stringify(valid, null, 2) + "\n", "utf8");
|
|
133
|
+
renameSync(temp, path);
|
|
134
|
+
}
|
|
135
|
+
catch (error) {
|
|
136
|
+
throw new Error(`failed to save daemon registry: ${String(error)}`);
|
|
137
|
+
}
|
|
84
138
|
}
|
|
85
|
-
/**
|
|
86
|
-
* Adds a daemon entry. Refuses duplicates (same normalized cwd already
|
|
87
|
-
* present). Returns the derived id + normalized cwd so the caller can
|
|
88
|
-
* report it back to the user.
|
|
89
|
-
*/
|
|
139
|
+
/** Explicit registration always starts in the desired running state. */
|
|
90
140
|
export function addDaemon(rawCwd, name) {
|
|
91
141
|
const cwd = normalizeCwd(rawCwd);
|
|
92
|
-
const
|
|
93
|
-
if (
|
|
142
|
+
const registry = loadRegistry();
|
|
143
|
+
if (registry.daemons.some((entry) => entry.cwd === cwd)) {
|
|
94
144
|
throw new Error(`Daemon already registered for cwd: ${cwd}`);
|
|
95
145
|
}
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
146
|
+
const entry = {
|
|
147
|
+
id: randomUUID(),
|
|
148
|
+
cwd,
|
|
149
|
+
name: name?.trim() || defaultAgentName(cwd),
|
|
150
|
+
desired_state: "running",
|
|
151
|
+
created_at: Date.now(),
|
|
152
|
+
};
|
|
153
|
+
registry.daemons.push(entry);
|
|
154
|
+
saveRegistry(registry);
|
|
155
|
+
return entry;
|
|
102
156
|
}
|
|
103
|
-
/**
|
|
104
|
-
* Removes the daemon entry whose derived id matches `id`. Returns the
|
|
105
|
-
* removed cwd (if any). Does NOT touch the cwd's local config — `create`
|
|
106
|
-
* the same cwd later restores the registration idempotently.
|
|
107
|
-
*/
|
|
108
157
|
export function removeDaemon(id) {
|
|
109
|
-
const
|
|
110
|
-
const
|
|
111
|
-
if (
|
|
158
|
+
const registry = loadRegistry();
|
|
159
|
+
const index = registry.daemons.findIndex((entry) => entry.id === id);
|
|
160
|
+
if (index === -1)
|
|
112
161
|
return { removed: false };
|
|
113
|
-
const [removed] =
|
|
114
|
-
saveRegistry(
|
|
162
|
+
const [removed] = registry.daemons.splice(index, 1);
|
|
163
|
+
saveRegistry(registry);
|
|
115
164
|
return { removed: true, cwd: removed.cwd };
|
|
116
165
|
}
|
|
117
|
-
/**
|
|
118
|
-
|
|
166
|
+
/** Finds a registration by canonical cwd for external lifecycle integrations. */
|
|
167
|
+
export function findDaemonByCwd(rawCwd) {
|
|
168
|
+
let cwd;
|
|
169
|
+
try {
|
|
170
|
+
cwd = normalizeCwd(rawCwd);
|
|
171
|
+
}
|
|
172
|
+
catch {
|
|
173
|
+
let path = rawCwd.trim();
|
|
174
|
+
if (path === "~")
|
|
175
|
+
path = homedir();
|
|
176
|
+
else if (path.startsWith("~/"))
|
|
177
|
+
path = join(homedir(), path.slice(2));
|
|
178
|
+
cwd = isAbsolute(path) ? resolvePath(path) : resolvePath(process.cwd(), path);
|
|
179
|
+
}
|
|
180
|
+
return loadRegistry().daemons.find((entry) => entry.cwd === cwd);
|
|
181
|
+
}
|
|
182
|
+
/** Persists intended lifecycle independently from process state. */
|
|
183
|
+
export function setDaemonDesiredState(id, desiredState) {
|
|
184
|
+
const registry = loadRegistry();
|
|
185
|
+
const entry = registry.daemons.find((item) => item.id === id);
|
|
186
|
+
if (!entry)
|
|
187
|
+
return undefined;
|
|
188
|
+
entry.desired_state = desiredState;
|
|
189
|
+
saveRegistry(registry);
|
|
190
|
+
return entry;
|
|
191
|
+
}
|
|
192
|
+
/** Returns v2 entries in insertion order. */
|
|
119
193
|
export function listDaemons() {
|
|
120
|
-
return loadRegistry().daemons
|
|
121
|
-
id: daemonIdForCwd(d.cwd),
|
|
122
|
-
cwd: d.cwd,
|
|
123
|
-
// Legacy entries (cwd only) fall back to the folder-derived name so a
|
|
124
|
-
// daemon is never nameless. New entries already carry an explicit name.
|
|
125
|
-
name: d.name ?? defaultAgentName(d.cwd),
|
|
126
|
-
}));
|
|
194
|
+
return loadRegistry().daemons;
|
|
127
195
|
}
|
|
128
196
|
/**
|
|
129
|
-
*
|
|
130
|
-
*
|
|
131
|
-
* Idempotent — writes only when something was missing. Returns how many
|
|
132
|
-
* entries were backfilled. The supervisor runs this on start.
|
|
197
|
+
* Backward compatible export name only. v2 deliberately has no in-place
|
|
198
|
+
* migration: an old registry must be explicitly replaced by its operator.
|
|
133
199
|
*/
|
|
134
200
|
export function migrateRegistryNames() {
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
if (!d.name) {
|
|
139
|
-
d.name = defaultAgentName(d.cwd);
|
|
140
|
-
changed++;
|
|
141
|
-
}
|
|
142
|
-
}
|
|
143
|
-
if (changed > 0)
|
|
144
|
-
saveRegistry(reg);
|
|
145
|
-
return changed;
|
|
146
|
-
}
|
|
147
|
-
/** Test/diag-only: returns the on-disk path. Exported so tests can poke
|
|
148
|
-
* at it (e.g. tmpdir override is done via env, but for now the path is
|
|
149
|
-
* hardcoded to ~/.pi/remote/daemons.json). */
|
|
201
|
+
loadRegistry();
|
|
202
|
+
return 0;
|
|
203
|
+
}
|
|
150
204
|
export function registryPath() {
|
|
151
205
|
return registryPathInternal();
|
|
152
206
|
}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"registry.js","sourceRoot":"","sources":["../../src/daemon/registry.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,UAAU,EAAE,SAAS,EAAE,YAAY,EAAE,YAAY,EAAE,aAAa,EAAE,MAAM,SAAS,CAAC;
|
|
1
|
+
{"version":3,"file":"registry.js","sourceRoot":"","sources":["../../src/daemon/registry.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,UAAU,EAAE,SAAS,EAAE,YAAY,EAAE,YAAY,EAAE,UAAU,EAAE,aAAa,EAAE,MAAM,SAAS,CAAC;AACvG,OAAO,EAAE,UAAU,EAAE,MAAM,aAAa,CAAC;AACzC,OAAO,EAAE,OAAO,EAAE,MAAM,SAAS,CAAC;AAClC,OAAO,EAAE,OAAO,EAAE,UAAU,EAAE,IAAI,EAAE,OAAO,IAAI,WAAW,EAAE,MAAM,WAAW,CAAC;AAC9E,OAAO,EAAE,gBAAgB,EAAE,MAAM,4BAA4B,CAAC;AAwB9D;;;;GAIG;AACH,MAAM,OAAO,mBAAoB,SAAQ,KAAK;IAE1B;IADlB,YACkB,IAAuB,EACvC,OAAe;QAEf,KAAK,CAAC,OAAO,CAAC,CAAC;QAHC,SAAI,GAAJ,IAAI,CAAmB;QAIvC,IAAI,CAAC,IAAI,GAAG,qBAAqB,CAAC;IACpC,CAAC;CACF;AAED,SAAS,oBAAoB;IAC3B,MAAM,IAAI,GAAG,OAAO,CAAC,GAAG,CAAC,gBAAgB,CAAC,IAAI,OAAO,EAAE,CAAC;IACxD,OAAO,IAAI,CAAC,IAAI,EAAE,KAAK,EAAE,QAAQ,EAAE,cAAc,CAAC,CAAC;AACrD,CAAC;AAED,SAAS,aAAa;IACpB,OAAO,EAAE,OAAO,EAAE,CAAC,EAAE,OAAO,EAAE,EAAE,EAAE,CAAC;AACrC,CAAC;AAED,kEAAkE;AAClE,MAAM,UAAU,YAAY,CAAC,KAAa;IACxC,IAAI,CAAC,KAAK,IAAI,CAAC,KAAK,CAAC,IAAI,EAAE;QAAE,MAAM,IAAI,KAAK,CAAC,iBAAiB,CAAC,CAAC;IAChE,IAAI,IAAI,GAAG,KAAK,CAAC,IAAI,EAAE,CAAC;IACxB,IAAI,IAAI,KAAK,GAAG;QAAE,IAAI,GAAG,OAAO,EAAE,CAAC;SAC9B,IAAI,IAAI,CAAC,UAAU,CAAC,IAAI,CAAC;QAAE,IAAI,GAAG,IAAI,CAAC,OAAO,EAAE,EAAE,IAAI,CAAC,KAAK,CAAC,CAAC,CAAC,CAAC,CAAC;IACtE,IAAI,CAAC,UAAU,CAAC,IAAI,CAAC;QAAE,IAAI,GAAG,WAAW,CAAC,OAAO,CAAC,GAAG,EAAE,EAAE,IAAI,CAAC,CAAC;IAC/D,OAAO,YAAY,CAAC,IAAI,CAAC,CAAC;AAC5B,CAAC;AAED,SAAS,aAAa,CAAC,OAAe,EAAE,OAA0B,yBAAyB;IACzF,MAAM,IAAI,mBAAmB,CAAC,IAAI,EAAE,OAAO,CAAC,CAAC;AAC/C,CAAC;AAED,SAAS,UAAU,CAAC,KAAc;IAChC,uEAAuE;IACvE,6EAA6E;IAC7E,OAAO,OAAO,KAAK,KAAK,QAAQ,IAAI,4EAA4E,CAAC,IAAI,CAAC,KAAK,CAAC,CAAC;AAC/H,CAAC;AAED,SAAS,UAAU,CAAC,KAAc,EAAE,KAAa;IAC/C,IAAI,CAAC,KAAK,IAAI,OAAO,KAAK,KAAK,QAAQ;QAAE,aAAa,CAAC,mBAAmB,KAAK,qBAAqB,CAAC,CAAC;IACtG,MAAM,KAAK,GAAG,KAAgC,CAAC;IAC/C,IAAI,CAAC,UAAU,CAAC,KAAK,CAAC,IAAI,CAAC,CAAC;QAAE,aAAa,CAAC,mBAAmB,KAAK,6BAA6B,CAAC,CAAC;IACnG,IAAI,OAAO,KAAK,CAAC,KAAK,CAAC,KAAK,QAAQ,IAAI,CAAC,UAAU,CAAC,KAAK,CAAC,KAAK,CAAC,CAAC,IAAI,KAAK,CAAC,KAAK,CAAC,CAAC,MAAM,KAAK,CAAC,EAAE,CAAC;QAC/F,aAAa,CAAC,mBAAmB,KAAK,gCAAgC,CAAC,CAAC;IAC1E,CAAC;IACD,wEAAwE;IACxE,sEAAsE;IACtE,IAAI,UAAU,CAAC,KAAK,CAAC,KAAK,CAAC,CAAC,IAAI,YAAY,CAAC,KAAK,CAAC,KAAK,CAAC,CAAC,KAAK,KAAK,CAAC,KAAK,CAAC,EAAE,CAAC;QAC5E,aAAa,CAAC,mBAAmB,KAAK,qCAAqC,CAAC,CAAC;IAC/E,CAAC;IACD,IAAI,OAAO,KAAK,CAAC,MAAM,CAAC,KAAK,QAAQ,IAAI,KAAK,CAAC,MAAM,CAAC,CAAC,IAAI,EAAE,CAAC,MAAM,KAAK,CAAC,EAAE,CAAC;QAC3E,aAAa,CAAC,mBAAmB,KAAK,mCAAmC,CAAC,CAAC;IAC7E,CAAC;IACD,IAAI,KAAK,CAAC,eAAe,CAAC,KAAK,SAAS,IAAI,KAAK,CAAC,eAAe,CAAC,KAAK,SAAS,EAAE,CAAC;QACjF,aAAa,CAAC,mBAAmB,KAAK,4CAA4C,CAAC,CAAC;IACtF,CAAC;IACD,IAAI,OAAO,KAAK,CAAC,YAAY,CAAC,KAAK,QAAQ,IAAI,CAAC,MAAM,CAAC,QAAQ,CAAC,KAAK,CAAC,YAAY,CAAC,CAAC,IAAI,KAAK,CAAC,YAAY,CAAC,GAAG,CAAC,EAAE,CAAC;QAChH,aAAa,CAAC,mBAAmB,KAAK,yCAAyC,CAAC,CAAC;IACnF,CAAC;IACD,OAAO;QACL,EAAE,EAAE,KAAK,CAAC,IAAI,CAAC;QACf,GAAG,EAAE,KAAK,CAAC,KAAK,CAAC;QACjB,IAAI,EAAE,KAAK,CAAC,MAAM,CAAC,CAAC,IAAI,EAAE;QAC1B,aAAa,EAAE,KAAK,CAAC,eAAe,CAAC;QACrC,UAAU,EAAE,KAAK,CAAC,YAAY,CAAC;KAChC,CAAC;AACJ,CAAC;AAED,SAAS,gBAAgB,CAAC,KAAc;IACtC,IAAI,CAAC,KAAK,IAAI,OAAO,KAAK,KAAK,QAAQ,IAAI,KAAK,CAAC,OAAO,CAAC,KAAK,CAAC,EAAE,CAAC;QAChE,aAAa,CAAC,iCAAiC,CAAC,CAAC;IACnD,CAAC;IACD,MAAM,IAAI,GAAG,KAAgC,CAAC;IAC9C,IAAI,IAAI,CAAC,SAAS,CAAC,KAAK,CAAC,EAAE,CAAC;QAC1B,MAAM,IAAI,GAAsB,IAAI,CAAC,SAAS,CAAC,KAAK,SAAS,IAAI,IAAI,CAAC,SAAS,CAAC,KAAK,CAAC;YACpF,CAAC,CAAC,6BAA6B;YAC/B,CAAC,CAAC,yBAAyB,CAAC;QAC9B,aAAa,CACX,uCAAuC,MAAM,CAAC,IAAI,CAAC,SAAS,CAAC,CAAC,0EAA0E,EACxI,IAAI,CACL,CAAC;IACJ,CAAC;IACD,IAAI,CAAC,KAAK,CAAC,OAAO,CAAC,IAAI,CAAC,SAAS,CAAC,CAAC;QAAE,aAAa,CAAC,mCAAmC,CAAC,CAAC;IACxF,MAAM,OAAO,GAAG,IAAI,CAAC,SAAS,CAAC,CAAC,GAAG,CAAC,CAAC,KAAK,EAAE,KAAK,EAAE,EAAE,CAAC,UAAU,CAAC,KAAK,EAAE,KAAK,CAAC,CAAC,CAAC;IAChF,MAAM,GAAG,GAAG,IAAI,GAAG,EAAU,CAAC;IAC9B,MAAM,IAAI,GAAG,IAAI,GAAG,EAAU,CAAC;IAC/B,KAAK,MAAM,KAAK,IAAI,OAAO,EAAE,CAAC;QAC5B,IAAI,GAAG,CAAC,GAAG,CAAC,KAAK,CAAC,EAAE,CAAC;YAAE,aAAa,CAAC,kCAAkC,KAAK,CAAC,EAAE,EAAE,CAAC,CAAC;QACnF,IAAI,IAAI,CAAC,GAAG,CAAC,KAAK,CAAC,GAAG,CAAC;YAAE,aAAa,CAAC,mCAAmC,KAAK,CAAC,GAAG,EAAE,CAAC,CAAC;QACvF,GAAG,CAAC,GAAG,CAAC,KAAK,CAAC,EAAE,CAAC,CAAC;QAClB,IAAI,CAAC,GAAG,CAAC,KAAK,CAAC,GAAG,CAAC,CAAC;IACtB,CAAC;IACD,OAAO,EAAE,OAAO,EAAE,CAAC,EAAE,OAAO,EAAE,CAAC;AACjC,CAAC;AAED,sEAAsE;AACtE,MAAM,UAAU,YAAY;IAC1B,MAAM,IAAI,GAAG,oBAAoB,EAAE,CAAC;IACpC,IAAI,CAAC,UAAU,CAAC,IAAI,CAAC;QAAE,OAAO,aAAa,EAAE,CAAC;IAC9C,IAAI,GAAW,CAAC;IAChB,IAAI,CAAC;QACH,GAAG,GAAG,YAAY,CAAC,IAAI,EAAE,MAAM,CAAC,CAAC;IACnC,CAAC;IAAC,OAAO,KAAK,EAAE,CAAC;QACf,MAAM,IAAI,mBAAmB,CAAC,yBAAyB,EAAE,gCAAgC,MAAM,CAAC,KAAK,CAAC,EAAE,CAAC,CAAC;IAC5G,CAAC;IACD,IAAI,MAAe,CAAC;IACpB,IAAI,CAAC;QACH,MAAM,GAAG,IAAI,CAAC,KAAK,CAAC,GAAG,CAAC,CAAC;IAC3B,CAAC;IAAC,OAAO,KAAK,EAAE,CAAC;QACf,MAAM,IAAI,mBAAmB,CAAC,yBAAyB,EAAE,iCAAiC,MAAM,CAAC,KAAK,CAAC,EAAE,CAAC,CAAC;IAC7G,CAAC;IACD,OAAO,gBAAgB,CAAC,MAAM,CAAC,CAAC;AAClC,CAAC;AAED,kEAAkE;AAClE,MAAM,UAAU,YAAY,CAAC,QAAwB;IACnD,MAAM,KAAK,GAAG,gBAAgB,CAAC,QAAQ,CAAC,CAAC;IACzC,MAAM,IAAI,GAAG,oBAAoB,EAAE,CAAC;IACpC,SAAS,CAAC,OAAO,CAAC,IAAI,CAAC,EAAE,EAAE,SAAS,EAAE,IAAI,EAAE,CAAC,CAAC;IAC9C,MAAM,IAAI,GAAG,GAAG,IAAI,IAAI,OAAO,CAAC,GAAG,IAAI,UAAU,EAAE,MAAM,CAAC;IAC1D,IAAI,CAAC;QACH,aAAa,CAAC,IAAI,EAAE,IAAI,CAAC,SAAS,CAAC,KAAK,EAAE,IAAI,EAAE,CAAC,CAAC,GAAG,IAAI,EAAE,MAAM,CAAC,CAAC;QACnE,UAAU,CAAC,IAAI,EAAE,IAAI,CAAC,CAAC;IACzB,CAAC;IAAC,OAAO,KAAK,EAAE,CAAC;QACf,MAAM,IAAI,KAAK,CAAC,mCAAmC,MAAM,CAAC,KAAK,CAAC,EAAE,CAAC,CAAC;IACtE,CAAC;AACH,CAAC;AAED,wEAAwE;AACxE,MAAM,UAAU,SAAS,CAAC,MAAc,EAAE,IAAa;IACrD,MAAM,GAAG,GAAG,YAAY,CAAC,MAAM,CAAC,CAAC;IACjC,MAAM,QAAQ,GAAG,YAAY,EAAE,CAAC;IAChC,IAAI,QAAQ,CAAC,OAAO,CAAC,IAAI,CAAC,CAAC,KAAK,EAAE,EAAE,CAAC,KAAK,CAAC,GAAG,KAAK,GAAG,CAAC,EAAE,CAAC;QACxD,MAAM,IAAI,KAAK,CAAC,sCAAsC,GAAG,EAAE,CAAC,CAAC;IAC/D,CAAC;IACD,MAAM,KAAK,GAAgB;QACzB,EAAE,EAAE,UAAU,EAAE;QAChB,GAAG;QACH,IAAI,EAAE,IAAI,EAAE,IAAI,EAAE,IAAI,gBAAgB,CAAC,GAAG,CAAC;QAC3C,aAAa,EAAE,SAAS;QACxB,UAAU,EAAE,IAAI,CAAC,GAAG,EAAE;KACvB,CAAC;IACF,QAAQ,CAAC,OAAO,CAAC,IAAI,CAAC,KAAK,CAAC,CAAC;IAC7B,YAAY,CAAC,QAAQ,CAAC,CAAC;IACvB,OAAO,KAAK,CAAC;AACf,CAAC;AAED,MAAM,UAAU,YAAY,CAAC,EAAU;IACrC,MAAM,QAAQ,GAAG,YAAY,EAAE,CAAC;IAChC,MAAM,KAAK,GAAG,QAAQ,CAAC,OAAO,CAAC,SAAS,CAAC,CAAC,KAAK,EAAE,EAAE,CAAC,KAAK,CAAC,EAAE,KAAK,EAAE,CAAC,CAAC;IACrE,IAAI,KAAK,KAAK,CAAC,CAAC;QAAE,OAAO,EAAE,OAAO,EAAE,KAAK,EAAE,CAAC;IAC5C,MAAM,CAAC,OAAO,CAAC,GAAG,QAAQ,CAAC,OAAO,CAAC,MAAM,CAAC,KAAK,EAAE,CAAC,CAAC,CAAC;IACpD,YAAY,CAAC,QAAQ,CAAC,CAAC;IACvB,OAAO,EAAE,OAAO,EAAE,IAAI,EAAE,GAAG,EAAE,OAAQ,CAAC,GAAG,EAAE,CAAC;AAC9C,CAAC;AAED,iFAAiF;AACjF,MAAM,UAAU,eAAe,CAAC,MAAc;IAC5C,IAAI,GAAW,CAAC;IAChB,IAAI,CAAC;QACH,GAAG,GAAG,YAAY,CAAC,MAAM,CAAC,CAAC;IAC7B,CAAC;IAAC,MAAM,CAAC;QACP,IAAI,IAAI,GAAG,MAAM,CAAC,IAAI,EAAE,CAAC;QACzB,IAAI,IAAI,KAAK,GAAG;YAAE,IAAI,GAAG,OAAO,EAAE,CAAC;aAC9B,IAAI,IAAI,CAAC,UAAU,CAAC,IAAI,CAAC;YAAE,IAAI,GAAG,IAAI,CAAC,OAAO,EAAE,EAAE,IAAI,CAAC,KAAK,CAAC,CAAC,CAAC,CAAC,CAAC;QACtE,GAAG,GAAG,UAAU,CAAC,IAAI,CAAC,CAAC,CAAC,CAAC,WAAW,CAAC,IAAI,CAAC,CAAC,CAAC,CAAC,WAAW,CAAC,OAAO,CAAC,GAAG,EAAE,EAAE,IAAI,CAAC,CAAC;IAChF,CAAC;IACD,OAAO,YAAY,EAAE,CAAC,OAAO,CAAC,IAAI,CAAC,CAAC,KAAK,EAAE,EAAE,CAAC,KAAK,CAAC,GAAG,KAAK,GAAG,CAAC,CAAC;AACnE,CAAC;AAED,oEAAoE;AACpE,MAAM,UAAU,qBAAqB,CAAC,EAAU,EAAE,YAA0B;IAC1E,MAAM,QAAQ,GAAG,YAAY,EAAE,CAAC;IAChC,MAAM,KAAK,GAAG,QAAQ,CAAC,OAAO,CAAC,IAAI,CAAC,CAAC,IAAI,EAAE,EAAE,CAAC,IAAI,CAAC,EAAE,KAAK,EAAE,CAAC,CAAC;IAC9D,IAAI,CAAC,KAAK;QAAE,OAAO,SAAS,CAAC;IAC7B,KAAK,CAAC,aAAa,GAAG,YAAY,CAAC;IACnC,YAAY,CAAC,QAAQ,CAAC,CAAC;IACvB,OAAO,KAAK,CAAC;AACf,CAAC;AAED,6CAA6C;AAC7C,MAAM,UAAU,WAAW;IACzB,OAAO,YAAY,EAAE,CAAC,OAAO,CAAC;AAChC,CAAC;AAED;;;GAGG;AACH,MAAM,UAAU,oBAAoB;IAClC,YAAY,EAAE,CAAC;IACf,OAAO,CAAC,CAAC;AACX,CAAC;AAED,MAAM,UAAU,YAAY;IAC1B,OAAO,oBAAoB,EAAE,CAAC;AAChC,CAAC"}
|
|
@@ -1,194 +1,140 @@
|
|
|
1
1
|
import { EventEmitter } from "node:events";
|
|
2
|
-
import type { DaemonState } from "./control_protocol.js";
|
|
2
|
+
import type { DaemonState, ProcessState, RelayState, RuntimeState } from "./control_protocol.js";
|
|
3
3
|
import { type LocalConfig } from "../session/local_config.js";
|
|
4
|
-
/**
|
|
5
|
-
*
|
|
6
|
-
* supervisor.
|
|
7
|
-
*
|
|
8
|
-
* Lifecycle:
|
|
9
|
-
* - `spawn()` boots the child with the daemon's cwd + `REMOTE_PI_DAEMON=1`
|
|
10
|
-
* env so the extension knows to skip the interactive wizard.
|
|
11
|
-
* - `sendPrompt(text)` writes a Pi RPC `prompt` command to stdin.
|
|
12
|
-
* - The child's stdout (Pi RPC events) is currently consumed line-by-line
|
|
13
|
-
* and ignored — wave 2 only needs fire-and-forget. Later waves can
|
|
14
|
-
* surface the events back through the supervisor's status op.
|
|
15
|
-
* - Exit/crash fires `exit` event with `{code, signal, isCrash}` so the
|
|
16
|
-
* supervisor can decide whether to auto-restart.
|
|
17
|
-
*
|
|
18
|
-
* Each `RpcChild` instance maps 1:1 to a registry entry (single cwd).
|
|
19
|
-
* The supervisor owns the map of these and addresses them by `id`.
|
|
20
|
-
*/
|
|
4
|
+
/** RPC child wrapper. PID creation is not readiness: only structured stdout
|
|
5
|
+
* responses/events can move runtime from pending to ready. */
|
|
21
6
|
export interface RpcChildOptions {
|
|
22
|
-
/** Path to the `pi` binary. Defaults to "pi" (must be on PATH). */
|
|
23
7
|
piBin?: string;
|
|
24
|
-
/**
|
|
8
|
+
/** Retained until bin/supervisor wiring is removed; daemon spawn deliberately
|
|
9
|
+
* does not pass it as `-e` because Pi's configured extension environment is
|
|
10
|
+
* the sole extension source. */
|
|
25
11
|
extensionPath: string;
|
|
26
|
-
/** Working directory for the spawned process. Determines which local
|
|
27
|
-
* config the extension reads. */
|
|
28
12
|
cwd: string;
|
|
29
|
-
/**
|
|
30
|
-
|
|
13
|
+
/** Stable daemon endpoint identity supplied by the supervisor. */
|
|
14
|
+
endpointId?: string;
|
|
31
15
|
env?: NodeJS.ProcessEnv;
|
|
32
|
-
/**
|
|
33
|
-
* Daemon config injected into the child via `REMOTE_PI_DIRECT_CONFIG`
|
|
34
|
-
* (JSON inline) instead of a per-cwd `.pi/remote-pi/config.json` file. The
|
|
35
|
-
* supervisor builds this from the registry. When set, the child reads it
|
|
36
|
-
* env-first (see `loadLocalConfig`) and no config file is needed. Also the
|
|
37
|
-
* source of the `--name` for the session. Falls back to the on-disk config
|
|
38
|
-
* when omitted.
|
|
39
|
-
*/
|
|
40
16
|
config?: LocalConfig;
|
|
17
|
+
readinessTimeoutMs?: number;
|
|
18
|
+
promptTimeoutMs?: number;
|
|
41
19
|
}
|
|
42
20
|
export interface RpcChildExitEvent {
|
|
43
21
|
code: number | null;
|
|
44
22
|
signal: NodeJS.Signals | null;
|
|
45
|
-
/** True
|
|
23
|
+
/** True for every exit not initiated through stop(), including code 0. */
|
|
46
24
|
isCrash: boolean;
|
|
47
25
|
}
|
|
26
|
+
export interface PromptAcceptance {
|
|
27
|
+
accepted: boolean;
|
|
28
|
+
code?: string;
|
|
29
|
+
error?: string;
|
|
30
|
+
}
|
|
31
|
+
export interface RuntimeReadyEvent {
|
|
32
|
+
control_protocol_version?: number;
|
|
33
|
+
extension_version?: string;
|
|
34
|
+
endpoint_id?: string;
|
|
35
|
+
runtime_instance_id?: string;
|
|
36
|
+
session_id?: string;
|
|
37
|
+
}
|
|
38
|
+
export interface RuntimeFailedEvent {
|
|
39
|
+
stage?: string;
|
|
40
|
+
code: string;
|
|
41
|
+
message?: string;
|
|
42
|
+
retryable: boolean;
|
|
43
|
+
}
|
|
44
|
+
export interface RelayStateChangedEvent {
|
|
45
|
+
state: RelayState;
|
|
46
|
+
}
|
|
47
|
+
export interface SessionChangedEvent {
|
|
48
|
+
session_id?: string;
|
|
49
|
+
}
|
|
48
50
|
export declare const EXIT_DAEMON_FRESH_SESSION = 42;
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
* bare `pi` is actually `pi.cmd`/`pi.ps1`, and `spawn` won't find it without an
|
|
52
|
-
* extension → resolve the real path via `where` (rather than `shell:true`,
|
|
53
|
-
* which risks shell injection). An explicit path or an already-suffixed name is
|
|
54
|
-
* used as-is. POSIX returns the name unchanged. Best-effort: if `where` fails,
|
|
55
|
-
* fall back to the bare name (spawn will surface ENOENT honestly).
|
|
56
|
-
*
|
|
57
|
-
* `where` lists every match in PATH order, which on pi-node installs puts the
|
|
58
|
-
* extensionless `pi` (a shell script) BEFORE `pi.cmd`. Naively taking the first
|
|
59
|
-
* line spawns the unrunnable script → ENOENT → "crashed". So we prefer a result
|
|
60
|
-
* with a real Windows executable extension and only fall back to the first line.
|
|
61
|
-
*/
|
|
51
|
+
export declare const DEFAULT_READINESS_TIMEOUT_MS = 10000;
|
|
52
|
+
export declare const DEFAULT_PROMPT_TIMEOUT_MS = 5000;
|
|
62
53
|
export declare function resolvePiBin(piBin: string, plat?: NodeJS.Platform): string;
|
|
63
|
-
/** What to pass to `spawn`: the executable and any args to prepend before the
|
|
64
|
-
* caller's args. On POSIX (and for `.exe`) `prefixArgs` is empty. */
|
|
65
54
|
export interface PiSpawnTarget {
|
|
66
55
|
command: string;
|
|
67
56
|
prefixArgs: string[];
|
|
68
57
|
}
|
|
69
|
-
/**
|
|
70
|
-
* Resolve `pi` to a directly-spawnable target (plan/40). On Windows `pi` is an
|
|
71
|
-
* npm `.cmd` shim, which modern Node refuses to `spawn` without `shell:true`
|
|
72
|
-
* (EINVAL, post CVE-2024-27980). Wrapping it in `cmd.exe` would (a) add a layer
|
|
73
|
-
* that orphans the real `node` on kill and (b) complicate stdio — both fatal for
|
|
74
|
-
* a long-lived RPC daemon the supervisor must stop/restart cleanly. Instead we
|
|
75
|
-
* parse the shim to recover the underlying `node <cli.js>` and spawn that
|
|
76
|
-
* directly: one process, clean signals, clean stdio.
|
|
77
|
-
*
|
|
78
|
-
* Falls back to the bare resolved path when not a parseable shim (e.g. a real
|
|
79
|
-
* `pi.exe`, or POSIX) so `spawn` surfaces any real error honestly.
|
|
80
|
-
*/
|
|
81
58
|
export declare function resolvePiSpawn(piBin: string, plat?: NodeJS.Platform, nodeExe?: string): PiSpawnTarget;
|
|
82
|
-
/**
|
|
83
|
-
* Extract the JS entry an npm `.cmd` shim launches. npm shims invoke
|
|
84
|
-
* `"%_prog%" "%dp0%\<relative>\entry.js" %*`; we recover `<dir>\<relative>\entry.js`
|
|
85
|
-
* (dir = the shim's own folder, i.e. `%dp0%`). Returns null when the shim
|
|
86
|
-
* doesn't match the expected shape or the target is missing.
|
|
87
|
-
*/
|
|
88
59
|
export declare function _npmShimTarget(cmdPath: string): string | null;
|
|
60
|
+
export declare function busyTransition(line: string): boolean | null;
|
|
89
61
|
/**
|
|
90
|
-
*
|
|
91
|
-
*
|
|
92
|
-
* change). Pure + exported for tests.
|
|
93
|
-
*
|
|
94
|
-
* NOTE (plan/39 detail 1): the Pi RPC stream has NO turn-level event — only
|
|
95
|
-
* per-message start/end (and `response{command:"prompt"}` is emitted at
|
|
96
|
-
* PREFLIGHT, i.e. turn START, not end). So this passive flag reflects
|
|
97
|
-
* "a message is being produced right now"; the authoritative turn-busy signal
|
|
98
|
-
* is `get_state.isStreaming` (see `RpcChild.refreshBusy`).
|
|
62
|
+
* The installed Pi RPC starts configured extensions itself. In particular this
|
|
63
|
+
* must not attach Remote Pi with `-e`, which could load it a second time.
|
|
99
64
|
*/
|
|
100
|
-
export declare function
|
|
65
|
+
export declare function rpcSpawnArgs(_extensionPath: string, sessionName?: string, useContinue?: boolean): string[];
|
|
66
|
+
type ParsedRuntimeEvent = {
|
|
67
|
+
type: "runtime_ready";
|
|
68
|
+
event: RuntimeReadyEvent;
|
|
69
|
+
} | {
|
|
70
|
+
type: "runtime_failed";
|
|
71
|
+
event: RuntimeFailedEvent;
|
|
72
|
+
} | {
|
|
73
|
+
type: "relay_state_changed";
|
|
74
|
+
event: RelayStateChangedEvent;
|
|
75
|
+
} | {
|
|
76
|
+
type: "session_changed";
|
|
77
|
+
event: SessionChangedEvent;
|
|
78
|
+
};
|
|
101
79
|
/**
|
|
102
|
-
*
|
|
103
|
-
*
|
|
104
|
-
* `--continue` is the key bit: it resumes the **most recent** session for the
|
|
105
|
-
* cwd (`SessionManager.continueRecent`, non-interactive — unlike `--resume`
|
|
106
|
-
* which opens a picker). Without it every supervisor restart spun up a brand
|
|
107
|
-
* new session file, piling up thousands of JSONLs per folder. With it, a
|
|
108
|
-
* restart REUSES the one session; the app's `/new` (session_new) still rolls
|
|
109
|
-
* it over to a fresh one, which the next restart then continues. First boot
|
|
110
|
-
* (no prior session) just creates the first one.
|
|
111
|
-
*
|
|
112
|
-
* `--name <sessionName>`, when given, pins the session's display name to the
|
|
113
|
-
* daemon's identity (its `agent_name`) so every restart shows up under the
|
|
114
|
-
* same stable name in the picker/app instead of an auto-generated one. The
|
|
115
|
-
* daemon's name is set at registration (`remote-pi create <cwd> --name "…"`).
|
|
116
|
-
* Omitted when no name resolves, so the arg list stays minimal.
|
|
117
|
-
*
|
|
118
|
-
* `--approve` is mandatory for a daemon (pi ≥0.79 project trust): RPC mode is
|
|
119
|
-
* non-interactive, so without an override Pi resolves an untrusted project
|
|
120
|
-
* folder (any folder with `.pi/` or CLAUDE.md/AGENTS.md) to NOT trusted and
|
|
121
|
-
* silently skips its `.pi/settings.json` (model/provider/keys), instructions,
|
|
122
|
-
* resources and project extensions — the daemon then comes up with no model
|
|
123
|
-
* and fails on the first turn. The operator already authorized this folder by
|
|
124
|
-
* registering/launching a daemon in it, so `--approve` (trust-for-this-run) is
|
|
125
|
-
* the correct non-interactive stance. (Does NOT affect the separate "extension
|
|
126
|
-
* loaded twice" conflict, which comes from the extension being BOTH installed
|
|
127
|
-
* in ~/.pi/agent/extensions or cwd/.pi/extensions AND passed via `-e`.)
|
|
80
|
+
* Parses direct daemon-control events and the current extension's custom
|
|
81
|
+
* relay-state message shape. Natural-language stderr is intentionally absent.
|
|
128
82
|
*/
|
|
129
|
-
export declare function
|
|
83
|
+
export declare function parseRuntimeEvent(line: string): ParsedRuntimeEvent | null;
|
|
130
84
|
export declare class RpcChild extends EventEmitter {
|
|
131
85
|
private readonly opts;
|
|
132
86
|
private child;
|
|
133
87
|
private _state;
|
|
88
|
+
private _process;
|
|
89
|
+
private _runtime;
|
|
90
|
+
private _relay;
|
|
134
91
|
private _startedAt;
|
|
92
|
+
private _runtimeInstanceId;
|
|
93
|
+
private _sessionId;
|
|
135
94
|
private _restartCount;
|
|
136
|
-
/** True while a deliberate `stop()` is in flight. The process dies by
|
|
137
|
-
* signal (SIGTERM/SIGKILL), which would otherwise look like a crash and
|
|
138
|
-
* trip the supervisor's auto-restart — re-spawning a daemon the operator
|
|
139
|
-
* just stopped/removed. Gating `isCrash` on this makes a deliberate stop a
|
|
140
|
-
* clean exit (no restart). Reset on every `spawn()`. */
|
|
141
95
|
private _stopping;
|
|
142
|
-
/** Next spawn should create a fresh session instead of --continue. */
|
|
143
96
|
private forceFreshSessionOnNextSpawn;
|
|
144
|
-
/** Accumulates partial stdout lines while waiting for `\n`. */
|
|
145
97
|
private stdoutBuf;
|
|
146
|
-
/** Passive busy flag derived from the RPC stream (message_start/end). Hint
|
|
147
|
-
* only; `refreshBusy` syncs it authoritatively via get_state.isStreaming. */
|
|
148
98
|
private _busy;
|
|
149
|
-
|
|
150
|
-
private
|
|
99
|
+
private rpcReady;
|
|
100
|
+
private extensionReady;
|
|
101
|
+
private extensionReadyEvent;
|
|
102
|
+
private readinessTimer;
|
|
103
|
+
private readyPromise;
|
|
104
|
+
private readyResolve;
|
|
105
|
+
private readonly statePending;
|
|
106
|
+
private readonly promptPending;
|
|
151
107
|
constructor(opts: RpcChildOptions);
|
|
152
108
|
get state(): DaemonState;
|
|
153
|
-
|
|
154
|
-
|
|
109
|
+
get processState(): ProcessState;
|
|
110
|
+
get runtimeState(): RuntimeState;
|
|
111
|
+
get relayState(): RelayState;
|
|
155
112
|
get isBusy(): boolean;
|
|
156
113
|
get pid(): number | undefined;
|
|
157
114
|
get restartCount(): number;
|
|
115
|
+
get startedAt(): number | undefined;
|
|
116
|
+
get runtimeInstanceId(): string | undefined;
|
|
117
|
+
get sessionId(): string | undefined;
|
|
158
118
|
get uptimeMs(): number | undefined;
|
|
159
|
-
/**
|
|
160
|
-
|
|
161
|
-
* call while already running is a no-op.
|
|
162
|
-
*/
|
|
119
|
+
/** Resolves true only after the readiness probe/event, false on timeout/exit. */
|
|
120
|
+
ready(): Promise<boolean>;
|
|
163
121
|
spawn(): void;
|
|
164
|
-
/**
|
|
165
|
-
|
|
166
|
-
* — we don't wait for the response (response would be the success ack;
|
|
167
|
-
* the actual agent output streams via the relay/UDS, not back through
|
|
168
|
-
* stdout).
|
|
169
|
-
*
|
|
170
|
-
* Returns false if the child isn't running (caller decides how to report).
|
|
171
|
-
*/
|
|
172
|
-
sendPrompt(text: string, requestId?: string): boolean;
|
|
173
|
-
/**
|
|
174
|
-
* Asks the child to exit gracefully. Sends SIGTERM; if the child doesn't
|
|
175
|
-
* exit within `timeoutMs`, escalates to SIGKILL. Resolves when the
|
|
176
|
-
* `exit` event fires.
|
|
177
|
-
*/
|
|
122
|
+
/** Waits for Pi's correlated prompt preflight response, not merely stdin write. */
|
|
123
|
+
sendPrompt(text: string, requestId?: string, timeoutMs?: number): Promise<PromptAcceptance>;
|
|
178
124
|
stop(timeoutMs?: number): Promise<void>;
|
|
179
|
-
private _onStdout;
|
|
180
|
-
private _handleStdoutLine;
|
|
181
|
-
/**
|
|
182
|
-
* Authoritative busy check: queries the child's `get_state` and syncs
|
|
183
|
-
* `_busy` to `isStreaming`. Resolves the passive flag on timeout (no
|
|
184
|
-
* response) or when the child isn't running. Used by the cron `skip_if_busy`
|
|
185
|
-
* gate, where a false "not busy" would pile a prompt onto a live turn.
|
|
186
|
-
*/
|
|
187
125
|
refreshBusy(timeoutMs?: number): Promise<boolean>;
|
|
188
|
-
/** Test
|
|
126
|
+
/** Test injection point for deterministic RPC event/response coverage. */
|
|
189
127
|
_ingestStdoutForTest(line: string): void;
|
|
190
|
-
private
|
|
191
|
-
|
|
192
|
-
|
|
128
|
+
private startReadinessProbe;
|
|
129
|
+
private onStdout;
|
|
130
|
+
private handleStdoutLine;
|
|
131
|
+
private parseResponse;
|
|
132
|
+
private handleResponse;
|
|
133
|
+
private handleRuntimeEvent;
|
|
134
|
+
private acceptExtensionReady;
|
|
135
|
+
private maybeMarkReady;
|
|
136
|
+
private failRuntime;
|
|
137
|
+
private onExit;
|
|
193
138
|
noteRestart(): void;
|
|
194
139
|
}
|
|
140
|
+
export {};
|