@geohar/un-bien 0.7.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/LICENSE +21 -0
- package/README.md +726 -0
- package/dist/actions/handlers.d.ts +77 -0
- package/dist/actions/handlers.js +26 -0
- package/dist/actions/handlers.js.map +1 -0
- package/dist/actions/registry.d.ts +39 -0
- package/dist/actions/registry.js +62 -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 +84 -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 +71 -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 +198 -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 +603 -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 +419 -0
- package/dist/daemon/rpc_child.js.map +1 -0
- package/dist/daemon/supervisor.d.ts +93 -0
- package/dist/daemon/supervisor.js +666 -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 +197 -0
- package/dist/index.js +5750 -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 +373 -0
- package/dist/mcp/mesh_server.js.map +1 -0
- package/dist/mesh/canonical.d.ts +30 -0
- package/dist/mesh/canonical.js +63 -0
- package/dist/mesh/canonical.js.map +1 -0
- package/dist/mesh/client.d.ts +18 -0
- package/dist/mesh/client.js +127 -0
- package/dist/mesh/client.js.map +1 -0
- package/dist/mesh/encoding.d.ts +38 -0
- package/dist/mesh/encoding.js +217 -0
- package/dist/mesh/encoding.js.map +1 -0
- package/dist/mesh/self_revoke.d.ts +73 -0
- package/dist/mesh/self_revoke.js +407 -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 +127 -0
- package/dist/pairing/qr.js.map +1 -0
- package/dist/pairing/storage.d.ts +120 -0
- package/dist/pairing/storage.js +614 -0
- package/dist/pairing/storage.js.map +1 -0
- package/dist/panel_bridge.d.ts +15 -0
- package/dist/panel_bridge.js +409 -0
- package/dist/panel_bridge.js.map +1 -0
- package/dist/paths.d.ts +39 -0
- package/dist/paths.js +51 -0
- package/dist/paths.js.map +1 -0
- package/dist/protocol/codec.d.ts +7 -0
- package/dist/protocol/codec.js +48 -0
- package/dist/protocol/codec.js.map +1 -0
- package/dist/protocol/types.d.ts +404 -0
- package/dist/protocol/types.js +2 -0
- package/dist/protocol/types.js.map +1 -0
- package/dist/rooms.d.ts +42 -0
- package/dist/rooms.js +78 -0
- package/dist/rooms.js.map +1 -0
- package/dist/session/bridge.d.ts +37 -0
- package/dist/session/bridge.js +134 -0
- package/dist/session/bridge.js.map +1 -0
- package/dist/session/broker.d.ts +189 -0
- package/dist/session/broker.js +527 -0
- package/dist/session/broker.js.map +1 -0
- package/dist/session/broker_remote.d.ts +74 -0
- package/dist/session/broker_remote.js +581 -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 +127 -0
- package/dist/session/cwd_lock.js.map +1 -0
- package/dist/session/debug_log.d.ts +3 -0
- package/dist/session/debug_log.js +32 -0
- package/dist/session/debug_log.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 +108 -0
- package/dist/session/leader_election.js.map +1 -0
- package/dist/session/local_config.d.ts +67 -0
- package/dist/session/local_config.js +193 -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 +596 -0
- package/dist/session/mesh_node.js.map +1 -0
- package/dist/session/peer.d.ts +139 -0
- package/dist/session/peer.js +474 -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 +24 -0
- package/dist/session/peer_limits.js.map +1 -0
- package/dist/session/rpc_envelope.d.ts +108 -0
- package/dist/session/rpc_envelope.js +221 -0
- package/dist/session/rpc_envelope.js.map +1 -0
- package/dist/session/rpc_inbound.d.ts +55 -0
- package/dist/session/rpc_inbound.js +136 -0
- package/dist/session/rpc_inbound.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 +272 -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 +52 -0
- package/dist/transport/peer_channel.js +145 -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 +68 -0
- package/dist/transport/pi_forward_client.js.map +1 -0
- package/dist/transport/relay_client.d.ts +96 -0
- package/dist/transport/relay_client.js +210 -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 +92 -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
|
@@ -0,0 +1,419 @@
|
|
|
1
|
+
import { execFileSync, spawn } from "node:child_process";
|
|
2
|
+
import { EventEmitter } from "node:events";
|
|
3
|
+
import { existsSync, readFileSync } from "node:fs";
|
|
4
|
+
import { dirname, join } from "node:path";
|
|
5
|
+
import { defaultAgentName, loadLocalConfig, } from "../session/local_config.js";
|
|
6
|
+
export const EXIT_DAEMON_FRESH_SESSION = 42;
|
|
7
|
+
/** Windows extensions `spawn` (without `shell`) can actually launch, in
|
|
8
|
+
* preference order: native `.exe` first, then the `.cmd`/`.bat` shims (run via
|
|
9
|
+
* cmd.exe). An extensionless `pi` is a Unix shell script Node CANNOT spawn on
|
|
10
|
+
* Windows — picking it yields `spawn … ENOENT` and the daemon shows "crashed". */
|
|
11
|
+
const WIN_EXECUTABLE_EXTS = [".exe", ".cmd", ".bat", ".com"];
|
|
12
|
+
/**
|
|
13
|
+
* Resolve the `pi` executable for `spawn` (plan/40, decision C). On Windows a
|
|
14
|
+
* bare `pi` is actually `pi.cmd`/`pi.ps1`, and `spawn` won't find it without an
|
|
15
|
+
* extension → resolve the real path via `where` (rather than `shell:true`,
|
|
16
|
+
* which risks shell injection). An explicit path or an already-suffixed name is
|
|
17
|
+
* used as-is. POSIX returns the name unchanged. Best-effort: if `where` fails,
|
|
18
|
+
* fall back to the bare name (spawn will surface ENOENT honestly).
|
|
19
|
+
*
|
|
20
|
+
* `where` lists every match in PATH order, which on pi-node installs puts the
|
|
21
|
+
* extensionless `pi` (a shell script) BEFORE `pi.cmd`. Naively taking the first
|
|
22
|
+
* line spawns the unrunnable script → ENOENT → "crashed". So we prefer a result
|
|
23
|
+
* with a real Windows executable extension and only fall back to the first line.
|
|
24
|
+
*/
|
|
25
|
+
export function resolvePiBin(piBin, plat = process.platform) {
|
|
26
|
+
if (plat !== "win32")
|
|
27
|
+
return piBin;
|
|
28
|
+
if (piBin.includes("\\") ||
|
|
29
|
+
piBin.includes("/") ||
|
|
30
|
+
/\.[a-z0-9]+$/i.test(piBin))
|
|
31
|
+
return piBin;
|
|
32
|
+
try {
|
|
33
|
+
const out = execFileSync("where", [piBin], { encoding: "utf8" });
|
|
34
|
+
const lines = out
|
|
35
|
+
.split(/\r?\n/)
|
|
36
|
+
.map((l) => l.trim())
|
|
37
|
+
.filter(Boolean);
|
|
38
|
+
const runnable = lines.find((l) => WIN_EXECUTABLE_EXTS.some((ext) => l.toLowerCase().endsWith(ext)));
|
|
39
|
+
const chosen = runnable ?? lines[0];
|
|
40
|
+
if (chosen)
|
|
41
|
+
return chosen;
|
|
42
|
+
}
|
|
43
|
+
catch {
|
|
44
|
+
/* `where` unavailable or `pi` not found — fall back to the bare name */
|
|
45
|
+
}
|
|
46
|
+
return piBin;
|
|
47
|
+
}
|
|
48
|
+
/**
|
|
49
|
+
* Resolve `pi` to a directly-spawnable target (plan/40). On Windows `pi` is an
|
|
50
|
+
* npm `.cmd` shim, which modern Node refuses to `spawn` without `shell:true`
|
|
51
|
+
* (EINVAL, post CVE-2024-27980). Wrapping it in `cmd.exe` would (a) add a layer
|
|
52
|
+
* that orphans the real `node` on kill and (b) complicate stdio — both fatal for
|
|
53
|
+
* a long-lived RPC daemon the supervisor must stop/restart cleanly. Instead we
|
|
54
|
+
* parse the shim to recover the underlying `node <cli.js>` and spawn that
|
|
55
|
+
* directly: one process, clean signals, clean stdio.
|
|
56
|
+
*
|
|
57
|
+
* Falls back to the bare resolved path when not a parseable shim (e.g. a real
|
|
58
|
+
* `pi.exe`, or POSIX) so `spawn` surfaces any real error honestly.
|
|
59
|
+
*/
|
|
60
|
+
export function resolvePiSpawn(piBin, plat = process.platform, nodeExe = process.execPath) {
|
|
61
|
+
const resolved = resolvePiBin(piBin, plat);
|
|
62
|
+
if (plat !== "win32")
|
|
63
|
+
return { command: resolved, prefixArgs: [] };
|
|
64
|
+
if (/\.(cmd|bat)$/i.test(resolved)) {
|
|
65
|
+
const target = _npmShimTarget(resolved);
|
|
66
|
+
if (target)
|
|
67
|
+
return { command: nodeExe, prefixArgs: [target] };
|
|
68
|
+
}
|
|
69
|
+
return { command: resolved, prefixArgs: [] };
|
|
70
|
+
}
|
|
71
|
+
/**
|
|
72
|
+
* Extract the JS entry an npm `.cmd` shim launches. npm shims invoke
|
|
73
|
+
* `"%_prog%" "%dp0%\<relative>\entry.js" %*`; we recover `<dir>\<relative>\entry.js`
|
|
74
|
+
* (dir = the shim's own folder, i.e. `%dp0%`). Returns null when the shim
|
|
75
|
+
* doesn't match the expected shape or the target is missing.
|
|
76
|
+
*/
|
|
77
|
+
export function _npmShimTarget(cmdPath) {
|
|
78
|
+
let content;
|
|
79
|
+
try {
|
|
80
|
+
content = readFileSync(cmdPath, "utf8");
|
|
81
|
+
}
|
|
82
|
+
catch {
|
|
83
|
+
return null;
|
|
84
|
+
}
|
|
85
|
+
const m = content.match(/"%dp0%\\([^"]+\.[cm]?js)"/i);
|
|
86
|
+
if (!m)
|
|
87
|
+
return null;
|
|
88
|
+
const target = join(dirname(cmdPath), m[1]);
|
|
89
|
+
return existsSync(target) ? target : null;
|
|
90
|
+
}
|
|
91
|
+
/**
|
|
92
|
+
* Maps an RPC stdout line to a busy-state transition: `message_start` → true
|
|
93
|
+
* (a message is streaming), `message_end` → false. Other lines → null (no
|
|
94
|
+
* change). Pure + exported for tests.
|
|
95
|
+
*
|
|
96
|
+
* NOTE (plan/39 detail 1): the Pi RPC stream has NO turn-level event — only
|
|
97
|
+
* per-message start/end (and `response{command:"prompt"}` is emitted at
|
|
98
|
+
* PREFLIGHT, i.e. turn START, not end). So this passive flag reflects
|
|
99
|
+
* "a message is being produced right now"; the authoritative turn-busy signal
|
|
100
|
+
* is `get_state.isStreaming` (see `RpcChild.refreshBusy`).
|
|
101
|
+
*/
|
|
102
|
+
export function busyTransition(line) {
|
|
103
|
+
let obj;
|
|
104
|
+
try {
|
|
105
|
+
obj = JSON.parse(line);
|
|
106
|
+
}
|
|
107
|
+
catch {
|
|
108
|
+
return null;
|
|
109
|
+
}
|
|
110
|
+
const t = obj?.type;
|
|
111
|
+
if (t === "message_start")
|
|
112
|
+
return true;
|
|
113
|
+
if (t === "message_end")
|
|
114
|
+
return false;
|
|
115
|
+
return null;
|
|
116
|
+
}
|
|
117
|
+
/** Parses a `get_state` RPC response line, returning its id + isStreaming. */
|
|
118
|
+
function parseGetStateResponse(line) {
|
|
119
|
+
let obj;
|
|
120
|
+
try {
|
|
121
|
+
obj = JSON.parse(line);
|
|
122
|
+
}
|
|
123
|
+
catch {
|
|
124
|
+
return null;
|
|
125
|
+
}
|
|
126
|
+
const o = obj;
|
|
127
|
+
if (o.type !== "response" || o.command !== "get_state")
|
|
128
|
+
return null;
|
|
129
|
+
const data = o.data;
|
|
130
|
+
return {
|
|
131
|
+
id: typeof o.id === "string" ? o.id : undefined,
|
|
132
|
+
isStreaming: typeof data?.isStreaming === "boolean" ? data.isStreaming : undefined,
|
|
133
|
+
};
|
|
134
|
+
}
|
|
135
|
+
/**
|
|
136
|
+
* CLI args for the daemon's `pi --mode rpc` child.
|
|
137
|
+
*
|
|
138
|
+
* `--continue` is the key bit: it resumes the **most recent** session for the
|
|
139
|
+
* cwd (`SessionManager.continueRecent`, non-interactive — unlike `--resume`
|
|
140
|
+
* which opens a picker). Without it every supervisor restart spun up a brand
|
|
141
|
+
* new session file, piling up thousands of JSONLs per folder. With it, a
|
|
142
|
+
* restart REUSES the one session; the app's `/new` (session_new) still rolls
|
|
143
|
+
* it over to a fresh one, which the next restart then continues. First boot
|
|
144
|
+
* (no prior session) just creates the first one.
|
|
145
|
+
*
|
|
146
|
+
* `--name <sessionName>`, when given, pins the session's display name to the
|
|
147
|
+
* daemon's identity (its `agent_name`) so every restart shows up under the
|
|
148
|
+
* same stable name in the picker/app instead of an auto-generated one. The
|
|
149
|
+
* daemon's name is set at registration (`unbien create <cwd> --name "…"`).
|
|
150
|
+
* Omitted when no name resolves, so the arg list stays minimal.
|
|
151
|
+
*
|
|
152
|
+
* `--approve` is mandatory for a daemon (pi ≥0.79 project trust): RPC mode is
|
|
153
|
+
* non-interactive, so without an override Pi resolves an untrusted project
|
|
154
|
+
* folder (any folder with `.pi/` or CLAUDE.md/AGENTS.md) to NOT trusted and
|
|
155
|
+
* silently skips its `.pi/settings.json` (model/provider/keys), instructions,
|
|
156
|
+
* resources and project extensions — the daemon then comes up with no model
|
|
157
|
+
* and fails on the first turn. The operator already authorized this folder by
|
|
158
|
+
* registering/launching a daemon in it, so `--approve` (trust-for-this-run) is
|
|
159
|
+
* the correct non-interactive stance. (Does NOT affect the separate "extension
|
|
160
|
+
* loaded twice" conflict, which comes from the extension being BOTH installed
|
|
161
|
+
* in ~/.pi/agent/extensions or cwd/.pi/extensions AND passed via `-e`.)
|
|
162
|
+
*/
|
|
163
|
+
export function rpcSpawnArgs(extensionPath, sessionName, useContinue = true) {
|
|
164
|
+
return [
|
|
165
|
+
"--mode",
|
|
166
|
+
"rpc",
|
|
167
|
+
"--approve",
|
|
168
|
+
...(useContinue ? ["--continue"] : []),
|
|
169
|
+
...(sessionName ? ["--name", sessionName] : []),
|
|
170
|
+
"-e",
|
|
171
|
+
extensionPath,
|
|
172
|
+
];
|
|
173
|
+
}
|
|
174
|
+
export class RpcChild extends EventEmitter {
|
|
175
|
+
opts;
|
|
176
|
+
child = null;
|
|
177
|
+
_state = "stopped";
|
|
178
|
+
_startedAt = null;
|
|
179
|
+
_restartCount = 0;
|
|
180
|
+
/** True while a deliberate `stop()` is in flight. The process dies by
|
|
181
|
+
* signal (SIGTERM/SIGKILL), which would otherwise look like a crash and
|
|
182
|
+
* trip the supervisor's auto-restart — re-spawning a daemon the operator
|
|
183
|
+
* just stopped/removed. Gating `isCrash` on this makes a deliberate stop a
|
|
184
|
+
* clean exit (no restart). Reset on every `spawn()`. */
|
|
185
|
+
_stopping = false;
|
|
186
|
+
/** Next spawn should create a fresh session instead of --continue. */
|
|
187
|
+
forceFreshSessionOnNextSpawn = false;
|
|
188
|
+
/** Accumulates partial stdout lines while waiting for `\n`. */
|
|
189
|
+
stdoutBuf = "";
|
|
190
|
+
/** Passive busy flag derived from the RPC stream (message_start/end). Hint
|
|
191
|
+
* only; `refreshBusy` syncs it authoritatively via get_state.isStreaming. */
|
|
192
|
+
_busy = false;
|
|
193
|
+
/** In-flight `get_state` requests, keyed by request id. */
|
|
194
|
+
_statePending = new Map();
|
|
195
|
+
constructor(opts) {
|
|
196
|
+
super();
|
|
197
|
+
this.opts = opts;
|
|
198
|
+
}
|
|
199
|
+
get state() {
|
|
200
|
+
return this._state;
|
|
201
|
+
}
|
|
202
|
+
/** Passive busy hint from the stream. Prefer `refreshBusy()` for an
|
|
203
|
+
* authoritative check before acting on it (cron skip_if_busy). */
|
|
204
|
+
get isBusy() {
|
|
205
|
+
return this._busy;
|
|
206
|
+
}
|
|
207
|
+
get pid() {
|
|
208
|
+
return this.child?.pid;
|
|
209
|
+
}
|
|
210
|
+
get restartCount() {
|
|
211
|
+
return this._restartCount;
|
|
212
|
+
}
|
|
213
|
+
get uptimeMs() {
|
|
214
|
+
return this._startedAt === null ? undefined : Date.now() - this._startedAt;
|
|
215
|
+
}
|
|
216
|
+
/**
|
|
217
|
+
* Spawn the child process. Idempotent for the same instance: a second
|
|
218
|
+
* call while already running is a no-op.
|
|
219
|
+
*/
|
|
220
|
+
spawn() {
|
|
221
|
+
if (this.child)
|
|
222
|
+
return;
|
|
223
|
+
this._stopping = false; // fresh start — a later signal IS a real crash
|
|
224
|
+
this._busy = false;
|
|
225
|
+
this._state = "starting";
|
|
226
|
+
const piTarget = resolvePiSpawn(this.opts.piBin ?? "pi");
|
|
227
|
+
// Name the (single) daemon session after the daemon's configured identity,
|
|
228
|
+
// so it shows up stably instead of an auto-generated name on each restart.
|
|
229
|
+
// Prefer the supervisor-injected config; fall back to the on-disk file.
|
|
230
|
+
const cfg = this.opts.config ?? loadLocalConfig(this.opts.cwd);
|
|
231
|
+
const sessionName = cfg.agent_name ?? defaultAgentName(this.opts.cwd);
|
|
232
|
+
const useContinue = !this.forceFreshSessionOnNextSpawn;
|
|
233
|
+
this.forceFreshSessionOnNextSpawn = false;
|
|
234
|
+
// On Windows `prefixArgs` carries pi's cli.js (we spawn node directly); on
|
|
235
|
+
// POSIX it's empty and `command` is `pi` itself.
|
|
236
|
+
const args = [
|
|
237
|
+
...piTarget.prefixArgs,
|
|
238
|
+
...rpcSpawnArgs(this.opts.extensionPath, sessionName, useContinue),
|
|
239
|
+
];
|
|
240
|
+
const env = {
|
|
241
|
+
...process.env,
|
|
242
|
+
...this.opts.env,
|
|
243
|
+
// Mandatory daemon marker — `_cmdRoot` in index.ts can use this to
|
|
244
|
+
// bail early if local config is missing (no wizard in RPC mode).
|
|
245
|
+
UNBIEN_DAEMON: "1",
|
|
246
|
+
// Inject the daemon config inline so the child needs no config file.
|
|
247
|
+
...(this.opts.config
|
|
248
|
+
? { UNBIEN_DIRECT_CONFIG: JSON.stringify(this.opts.config) }
|
|
249
|
+
: {}),
|
|
250
|
+
};
|
|
251
|
+
const child = spawn(piTarget.command, args, {
|
|
252
|
+
cwd: this.opts.cwd,
|
|
253
|
+
env,
|
|
254
|
+
stdio: ["pipe", "pipe", "pipe"],
|
|
255
|
+
// Windows: suppress any console window the child might allocate. With the
|
|
256
|
+
// node+cli.js path (resolvePiSpawn) there's no cmd.exe layer, but keep this
|
|
257
|
+
// as belt-and-suspenders alongside the supervisor's hidden wscript launcher
|
|
258
|
+
// (service-templates/task-launcher.vbs.template). No-op on POSIX.
|
|
259
|
+
windowsHide: process.platform === "win32",
|
|
260
|
+
});
|
|
261
|
+
this.child = child;
|
|
262
|
+
this._startedAt = Date.now();
|
|
263
|
+
this._state = "running";
|
|
264
|
+
child.stdout?.on("data", (chunk) => this._onStdout(chunk));
|
|
265
|
+
child.stderr?.on("data", (chunk) => {
|
|
266
|
+
// Forward stderr to our own stderr so `journalctl --user -u unbien-supervisord`
|
|
267
|
+
// sees daemon logs (with cwd prefix for disambiguation).
|
|
268
|
+
process.stderr.write(`[${this.opts.cwd}] ${chunk.toString()}`);
|
|
269
|
+
});
|
|
270
|
+
child.on("exit", (code, signal) => this._onExit(code, signal));
|
|
271
|
+
child.on("error", (err) => {
|
|
272
|
+
// spawn() itself failed (e.g. `pi` binary not found).
|
|
273
|
+
this._state = "crashed";
|
|
274
|
+
process.stderr.write(`[unbien-supervisord] spawn failed for ${this.opts.cwd}: ${String(err)}\n`);
|
|
275
|
+
this.emit("exit", { code: null, signal: null, isCrash: true });
|
|
276
|
+
});
|
|
277
|
+
this.emit("spawn", { pid: child.pid });
|
|
278
|
+
}
|
|
279
|
+
/**
|
|
280
|
+
* Sends a Pi RPC `prompt` command to the child's stdin. Fire-and-forget
|
|
281
|
+
* — we don't wait for the response (response would be the success ack;
|
|
282
|
+
* the actual agent output streams via the relay/UDS, not back through
|
|
283
|
+
* stdout).
|
|
284
|
+
*
|
|
285
|
+
* Returns false if the child isn't running (caller decides how to report).
|
|
286
|
+
*/
|
|
287
|
+
sendPrompt(text, requestId) {
|
|
288
|
+
if (!this.child || !this.child.stdin || this._state !== "running")
|
|
289
|
+
return false;
|
|
290
|
+
const cmd = {
|
|
291
|
+
id: requestId ?? `sv-${Date.now()}`,
|
|
292
|
+
type: "prompt",
|
|
293
|
+
message: text,
|
|
294
|
+
};
|
|
295
|
+
try {
|
|
296
|
+
this.child.stdin.write(JSON.stringify(cmd) + "\n");
|
|
297
|
+
return true;
|
|
298
|
+
}
|
|
299
|
+
catch {
|
|
300
|
+
return false;
|
|
301
|
+
}
|
|
302
|
+
}
|
|
303
|
+
/**
|
|
304
|
+
* Asks the child to exit gracefully. Sends SIGTERM; if the child doesn't
|
|
305
|
+
* exit within `timeoutMs`, escalates to SIGKILL. Resolves when the
|
|
306
|
+
* `exit` event fires.
|
|
307
|
+
*/
|
|
308
|
+
async stop(timeoutMs = 5000) {
|
|
309
|
+
if (!this.child)
|
|
310
|
+
return;
|
|
311
|
+
this._stopping = true; // deliberate — the upcoming signal-exit is NOT a crash
|
|
312
|
+
const child = this.child;
|
|
313
|
+
return new Promise((resolve) => {
|
|
314
|
+
const onExit = () => {
|
|
315
|
+
resolve();
|
|
316
|
+
};
|
|
317
|
+
this.once("exit", onExit);
|
|
318
|
+
try {
|
|
319
|
+
child.kill("SIGTERM");
|
|
320
|
+
}
|
|
321
|
+
catch {
|
|
322
|
+
/* already dead */
|
|
323
|
+
}
|
|
324
|
+
const t = setTimeout(() => {
|
|
325
|
+
try {
|
|
326
|
+
child.kill("SIGKILL");
|
|
327
|
+
}
|
|
328
|
+
catch {
|
|
329
|
+
/* race — already dead */
|
|
330
|
+
}
|
|
331
|
+
}, timeoutMs);
|
|
332
|
+
this.once("exit", () => clearTimeout(t));
|
|
333
|
+
});
|
|
334
|
+
}
|
|
335
|
+
_onStdout(chunk) {
|
|
336
|
+
this.stdoutBuf += chunk.toString();
|
|
337
|
+
let nl;
|
|
338
|
+
while ((nl = this.stdoutBuf.indexOf("\n")) >= 0) {
|
|
339
|
+
const line = this.stdoutBuf.slice(0, nl);
|
|
340
|
+
this.stdoutBuf = this.stdoutBuf.slice(nl + 1);
|
|
341
|
+
if (!line.trim())
|
|
342
|
+
continue;
|
|
343
|
+
this._handleStdoutLine(line);
|
|
344
|
+
}
|
|
345
|
+
}
|
|
346
|
+
_handleStdoutLine(line) {
|
|
347
|
+
const t = busyTransition(line);
|
|
348
|
+
if (t !== null)
|
|
349
|
+
this._busy = t;
|
|
350
|
+
const gs = parseGetStateResponse(line);
|
|
351
|
+
if (gs && gs.id) {
|
|
352
|
+
const pending = this._statePending.get(gs.id);
|
|
353
|
+
if (pending) {
|
|
354
|
+
clearTimeout(pending.timer);
|
|
355
|
+
this._statePending.delete(gs.id);
|
|
356
|
+
if (typeof gs.isStreaming === "boolean")
|
|
357
|
+
this._busy = gs.isStreaming;
|
|
358
|
+
pending.resolve(this._busy);
|
|
359
|
+
}
|
|
360
|
+
}
|
|
361
|
+
this.emit("stdout", line);
|
|
362
|
+
}
|
|
363
|
+
/**
|
|
364
|
+
* Authoritative busy check: queries the child's `get_state` and syncs
|
|
365
|
+
* `_busy` to `isStreaming`. Resolves the passive flag on timeout (no
|
|
366
|
+
* response) or when the child isn't running. Used by the cron `skip_if_busy`
|
|
367
|
+
* gate, where a false "not busy" would pile a prompt onto a live turn.
|
|
368
|
+
*/
|
|
369
|
+
async refreshBusy(timeoutMs = 1500) {
|
|
370
|
+
if (this._state !== "running" || !this.child?.stdin)
|
|
371
|
+
return this._busy;
|
|
372
|
+
const id = `gs-${Date.now()}-${Math.round(Math.random() * 1e6)}`;
|
|
373
|
+
return new Promise((resolve) => {
|
|
374
|
+
const timer = setTimeout(() => {
|
|
375
|
+
this._statePending.delete(id);
|
|
376
|
+
resolve(this._busy);
|
|
377
|
+
}, timeoutMs);
|
|
378
|
+
this._statePending.set(id, { resolve, timer });
|
|
379
|
+
try {
|
|
380
|
+
this.child.stdin.write(JSON.stringify({ id, type: "get_state" }) + "\n");
|
|
381
|
+
}
|
|
382
|
+
catch {
|
|
383
|
+
clearTimeout(timer);
|
|
384
|
+
this._statePending.delete(id);
|
|
385
|
+
resolve(this._busy);
|
|
386
|
+
}
|
|
387
|
+
});
|
|
388
|
+
}
|
|
389
|
+
/** Test-only: feed a raw stdout line through the same handler. */
|
|
390
|
+
_ingestStdoutForTest(line) {
|
|
391
|
+
this._handleStdoutLine(line);
|
|
392
|
+
}
|
|
393
|
+
_onExit(code, signal) {
|
|
394
|
+
// Daemon app-action `/new`: child exits with a private code, supervisor
|
|
395
|
+
// restarts it, and the next spawn omits --continue once to create a fresh
|
|
396
|
+
// session. Later restarts go back to --continue.
|
|
397
|
+
if (code === EXIT_DAEMON_FRESH_SESSION)
|
|
398
|
+
this.forceFreshSessionOnNextSpawn = true;
|
|
399
|
+
// A deliberate stop() kills by signal — not a crash, so the supervisor
|
|
400
|
+
// must NOT auto-restart it. Only an UNexpected exit counts as a crash.
|
|
401
|
+
const isCrash = !this._stopping && (code !== 0 || signal !== null);
|
|
402
|
+
this._state = isCrash ? "crashed" : "stopped";
|
|
403
|
+
this.child = null;
|
|
404
|
+
this._startedAt = null;
|
|
405
|
+
this._busy = false;
|
|
406
|
+
for (const p of this._statePending.values()) {
|
|
407
|
+
clearTimeout(p.timer);
|
|
408
|
+
p.resolve(false);
|
|
409
|
+
}
|
|
410
|
+
this._statePending.clear();
|
|
411
|
+
this.emit("exit", { code, signal, isCrash });
|
|
412
|
+
}
|
|
413
|
+
/** Bumps the restart counter — called by the supervisor when it
|
|
414
|
+
* decides to re-spawn after a crash. Exposed so tests can drive it. */
|
|
415
|
+
noteRestart() {
|
|
416
|
+
this._restartCount += 1;
|
|
417
|
+
}
|
|
418
|
+
}
|
|
419
|
+
//# 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,93 @@
|
|
|
1
|
+
import { type CronResult } from "./cron_log.js";
|
|
2
|
+
/** Thrown by `start()` when another live supervisor already holds the UDS.
|
|
3
|
+
* Prevents a second supervisor from orphaning the first's children. */
|
|
4
|
+
export declare class SupervisorAlreadyRunningError extends Error {
|
|
5
|
+
readonly sockPath: string;
|
|
6
|
+
constructor(sockPath: string);
|
|
7
|
+
}
|
|
8
|
+
export interface SupervisorOptions {
|
|
9
|
+
/** Absolute path to un-bien's dist/index.js — passed as -e to each
|
|
10
|
+
* spawned `pi`. Defaults to the location relative to where this file
|
|
11
|
+
* is bundled (so the supervisor finds itself). */
|
|
12
|
+
extensionPath: string;
|
|
13
|
+
/** Override the `pi` binary path. Defaults to "pi" on PATH. */
|
|
14
|
+
piBin?: string;
|
|
15
|
+
}
|
|
16
|
+
/** Pure decision for `fireJob` (plan/39) — picks the action from the daemon's
|
|
17
|
+
* liveness/busy state + the job's flags. Tested in isolation for all 4 ramos. */
|
|
18
|
+
export type FireAction = "send" | "wake_and_send" | "skip_down" | "skip_busy";
|
|
19
|
+
export declare function decideFireAction(o: {
|
|
20
|
+
running: boolean;
|
|
21
|
+
busy: boolean;
|
|
22
|
+
wake: boolean;
|
|
23
|
+
skipIfBusy: boolean;
|
|
24
|
+
}): FireAction;
|
|
25
|
+
export declare class Supervisor {
|
|
26
|
+
private readonly opts;
|
|
27
|
+
private server;
|
|
28
|
+
private readonly children;
|
|
29
|
+
/** Live croner schedules, keyed by cron job id (plan/39). */
|
|
30
|
+
private readonly cronJobs;
|
|
31
|
+
private shuttingDown;
|
|
32
|
+
constructor(opts: SupervisorOptions);
|
|
33
|
+
/** Bind the control UDS + spawn all registered daemons. */
|
|
34
|
+
start(): Promise<void>;
|
|
35
|
+
/** Graceful shutdown: stop all children, close UDS. */
|
|
36
|
+
stop(): Promise<void>;
|
|
37
|
+
private _mkdirParent;
|
|
38
|
+
private _bindUds;
|
|
39
|
+
private _onConnection;
|
|
40
|
+
private _handleRequest;
|
|
41
|
+
private _listInfo;
|
|
42
|
+
private _opStartAll;
|
|
43
|
+
/** Spawn a single registered daemon by id. Idempotent: a daemon already
|
|
44
|
+
* running returns `started: false`. Unknown id → ok:false. This is what
|
|
45
|
+
* `/unbien create` calls so a freshly-registered folder boots its Pi
|
|
46
|
+
* immediately instead of waiting for the next supervisor restart. */
|
|
47
|
+
private _opStart;
|
|
48
|
+
private _opStopAll;
|
|
49
|
+
/** Stop a single registered daemon by id. Idempotent: a daemon that isn't
|
|
50
|
+
* running returns `stopped: false`. Unknown id → ok:false. Mirrors the
|
|
51
|
+
* per-id semantics of `_opStart`. Cancels any pending restart backoff so a
|
|
52
|
+
* deliberate stop stays stopped. */
|
|
53
|
+
private _opStop;
|
|
54
|
+
/** Restart a single registered daemon by id (stop-if-running, then spawn).
|
|
55
|
+
* Unknown id → ok:false. Resets the crash backoff. */
|
|
56
|
+
private _opRestart;
|
|
57
|
+
private _opRestartAll;
|
|
58
|
+
private _opSend;
|
|
59
|
+
private _opRegister;
|
|
60
|
+
private _opUnregister;
|
|
61
|
+
private _opCronAdd;
|
|
62
|
+
private _opCronList;
|
|
63
|
+
private _opCronRemove;
|
|
64
|
+
private _opCronEnable;
|
|
65
|
+
private _opCronRun;
|
|
66
|
+
private _opCronLog;
|
|
67
|
+
private _jobView;
|
|
68
|
+
/** Rebuild all live `Cron` schedules from the registry (enabled jobs only).
|
|
69
|
+
* Called on start; mutations reconcile incrementally via _scheduleCron/_stopCron. */
|
|
70
|
+
private _reconcileCron;
|
|
71
|
+
private _scheduleCron;
|
|
72
|
+
private _stopCron;
|
|
73
|
+
/** Detail 2: on start, run a catchup job once if its previous scheduled run
|
|
74
|
+
* was missed while the supervisor was down. Opt-in (`catchup`), at most 1×. */
|
|
75
|
+
private _runCatchup;
|
|
76
|
+
/**
|
|
77
|
+
* Fires a cron job: resolves the daemon, decides the action (decideFireAction),
|
|
78
|
+
* acts, and records the outcome — ALWAYS one `last_status` update + one JSONL
|
|
79
|
+
* line, for both fires and skips. Returns the result. `manual` bypasses the
|
|
80
|
+
* disabled-skip (used by `cron run` + catchup).
|
|
81
|
+
*/
|
|
82
|
+
fireJob(jobId: string, opts?: {
|
|
83
|
+
manual?: boolean;
|
|
84
|
+
}): Promise<CronResult | "missing">;
|
|
85
|
+
private _spawnAllFromRegistry;
|
|
86
|
+
private _spawnEntry;
|
|
87
|
+
private _onChildExit;
|
|
88
|
+
}
|
|
89
|
+
/** Test helper: derive id from cwd without going through the registry. */
|
|
90
|
+
export declare function _idForCwdForTest(cwd: string): string;
|
|
91
|
+
/** Exported for the bin/supervisord entry + tests to know where the
|
|
92
|
+
* supervisor will bind. */
|
|
93
|
+
export declare function getSupervisorSockPath(): string;
|