@parall/daemon 1.28.0 → 1.28.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.
@@ -0,0 +1,220 @@
1
+ #!/usr/bin/env node
2
+
3
+ // ts/openclaw-agent/dist/index.js
4
+ import { execFileSync, spawn } from "node:child_process";
5
+ import * as fs from "node:fs";
6
+ import * as path from "node:path";
7
+ var PRLL_API_URL = env("PRLL_API_URL");
8
+ var PRLL_API_KEY = env("PRLL_API_KEY");
9
+ var PRLL_ORG_ID = env("PRLL_ORG_ID");
10
+ var stateDir = env("PRLL_OPENCLAW_STATE_DIR");
11
+ var PRLL_WS_URL = process.env.PRLL_WS_URL?.trim() || "";
12
+ var PRLL_SWIMLANE_NAME = process.env.PRLL_SWIMLANE_NAME?.trim() || "";
13
+ var gatewayPort = process.env.OPENCLAW_GATEWAY_PORT?.trim() || "0";
14
+ var pluginArchive = process.env.PRLL_OPENCLAW_PLUGIN_ARCHIVE?.trim() || "/opt/parall-plugin/parall-plugin.tgz";
15
+ function env(name) {
16
+ const v = process.env[name]?.trim();
17
+ if (!v) {
18
+ console.error(`ERROR: Missing required environment variable: ${name}`);
19
+ process.exit(1);
20
+ }
21
+ return v;
22
+ }
23
+ var openclawStateDir = path.join(stateDir, ".openclaw");
24
+ var configPath = path.join(openclawStateDir, "openclaw.json");
25
+ fs.mkdirSync(path.join(openclawStateDir, "sessions"), { recursive: true });
26
+ fs.mkdirSync(path.join(openclawStateDir, "workspace"), { recursive: true });
27
+ if (fs.existsSync(pluginArchive)) {
28
+ const legacyExtDir = path.join(openclawStateDir, "extensions", "parall");
29
+ fs.rmSync(legacyExtDir, { recursive: true, force: true });
30
+ console.log(`Installing Parall plugin from ${pluginArchive}...`);
31
+ try {
32
+ execFileSync("openclaw", [
33
+ "plugins",
34
+ "install",
35
+ pluginArchive,
36
+ "--force",
37
+ "--dangerously-force-unsafe-install"
38
+ ], {
39
+ env: { ...process.env, OPENCLAW_STATE_DIR: openclawStateDir },
40
+ stdio: "inherit",
41
+ timeout: 6e4
42
+ });
43
+ } catch (err) {
44
+ console.error(`ERROR: Failed to install Parall plugin: ${String(err)}`);
45
+ process.exit(1);
46
+ }
47
+ } else {
48
+ console.warn(`Plugin archive not found at ${pluginArchive} \u2014 assuming plugin is already installed.`);
49
+ }
50
+ writeOpenclawConfig();
51
+ function writeOpenclawConfig() {
52
+ let cfg = {};
53
+ try {
54
+ cfg = JSON.parse(fs.readFileSync(configPath, "utf8"));
55
+ } catch {
56
+ }
57
+ const gateway = cfg.gateway && typeof cfg.gateway === "object" ? cfg.gateway : {};
58
+ gateway.mode = "local";
59
+ cfg.gateway = gateway;
60
+ const channels = cfg.channels && typeof cfg.channels === "object" ? cfg.channels : {};
61
+ const parallChannel = {
62
+ parall_url: PRLL_API_URL,
63
+ api_key: PRLL_API_KEY,
64
+ org_id: PRLL_ORG_ID
65
+ };
66
+ if (PRLL_WS_URL)
67
+ parallChannel.ws_url = PRLL_WS_URL;
68
+ channels.parall = parallChannel;
69
+ cfg.channels = channels;
70
+ const plugins = cfg.plugins && typeof cfg.plugins === "object" ? cfg.plugins : {};
71
+ const entries = plugins.entries && typeof plugins.entries === "object" ? plugins.entries : {};
72
+ const parallPluginConfig = {
73
+ parall_url: PRLL_API_URL,
74
+ api_key: PRLL_API_KEY,
75
+ org_id: PRLL_ORG_ID
76
+ };
77
+ if (PRLL_WS_URL)
78
+ parallPluginConfig.ws_url = PRLL_WS_URL;
79
+ const existingParall = entries.parall && typeof entries.parall === "object" ? entries.parall : {};
80
+ entries.parall = {
81
+ ...existingParall,
82
+ enabled: true,
83
+ hooks: { allowPromptInjection: true, allowConversationAccess: true },
84
+ config: parallPluginConfig
85
+ };
86
+ plugins.entries = entries;
87
+ cfg.plugins = plugins;
88
+ const agents = cfg.agents && typeof cfg.agents === "object" ? cfg.agents : {};
89
+ const defaults = agents.defaults && typeof agents.defaults === "object" ? agents.defaults : {};
90
+ const ms = defaults.memorySearch && typeof defaults.memorySearch === "object" ? defaults.memorySearch : {};
91
+ const store = ms.store && typeof ms.store === "object" ? ms.store : {};
92
+ const vector = store.vector && typeof store.vector === "object" ? store.vector : {};
93
+ if (vector.enabled === void 0)
94
+ vector.enabled = true;
95
+ store.vector = vector;
96
+ ms.store = store;
97
+ defaults.memorySearch = ms;
98
+ agents.defaults = defaults;
99
+ cfg.agents = agents;
100
+ const tools = cfg.tools && typeof cfg.tools === "object" ? cfg.tools : {};
101
+ const alsoAllow = new Set(Array.isArray(tools.alsoAllow) ? tools.alsoAllow : []);
102
+ alsoAllow.add("group:plugins");
103
+ tools.alsoAllow = Array.from(alsoAllow);
104
+ cfg.tools = tools;
105
+ const tmp = configPath + ".tmp";
106
+ fs.mkdirSync(path.dirname(configPath), { recursive: true });
107
+ fs.writeFileSync(tmp, JSON.stringify(cfg, null, 2));
108
+ fs.renameSync(tmp, configPath);
109
+ }
110
+ await preseedPlatformConfig();
111
+ async function preseedPlatformConfig() {
112
+ try {
113
+ const headers = { Authorization: `Bearer ${PRLL_API_KEY}` };
114
+ if (PRLL_SWIMLANE_NAME)
115
+ headers["X-Prll-Swimlane"] = PRLL_SWIMLANE_NAME;
116
+ const resp = await fetch(`${PRLL_API_URL}/api/v1/agents/platform-config`, {
117
+ headers,
118
+ signal: AbortSignal.timeout(15e3)
119
+ });
120
+ if (!resp.ok)
121
+ throw new Error(`platform-config ${resp.status}`);
122
+ const data = await resp.json();
123
+ const pc = data.config ?? data;
124
+ let cfg = {};
125
+ try {
126
+ cfg = JSON.parse(fs.readFileSync(configPath, "utf8"));
127
+ } catch {
128
+ }
129
+ const ALLOWED_DEFAULTS = /* @__PURE__ */ new Set(["model", "compaction", "memorySearch"]);
130
+ const platformDefaults = pc.agents?.defaults;
131
+ if (platformDefaults && typeof platformDefaults === "object") {
132
+ const agents = cfg.agents && typeof cfg.agents === "object" ? cfg.agents : {};
133
+ const existing = agents.defaults && typeof agents.defaults === "object" ? agents.defaults : {};
134
+ for (const [k, v] of Object.entries(platformDefaults)) {
135
+ if (ALLOWED_DEFAULTS.has(k))
136
+ existing[k] = v;
137
+ }
138
+ agents.defaults = existing;
139
+ cfg.agents = agents;
140
+ }
141
+ const ALLOWED_MODEL_KEYS = /* @__PURE__ */ new Set(["id", "name", "contextWindow", "maxTokens"]);
142
+ const platformModels = pc.models?.providers;
143
+ const platformParall = platformModels?.parall;
144
+ if (platformParall && typeof platformParall === "object") {
145
+ const models = cfg.models && typeof cfg.models === "object" ? cfg.models : {};
146
+ const providers = models.providers && typeof models.providers === "object" ? models.providers : {};
147
+ const existingParall = providers.parall && typeof providers.parall === "object" ? providers.parall : {};
148
+ const merged = { ...existingParall, ...platformParall };
149
+ if (Array.isArray(merged.models)) {
150
+ merged.models = merged.models.filter((m) => m && typeof m === "object").map((m) => {
151
+ const clean = {};
152
+ for (const [k, v] of Object.entries(m)) {
153
+ if (ALLOWED_MODEL_KEYS.has(k))
154
+ clean[k] = v;
155
+ }
156
+ return clean;
157
+ });
158
+ }
159
+ merged.apiKey = PRLL_API_KEY;
160
+ providers.parall = merged;
161
+ models.providers = providers;
162
+ cfg.models = models;
163
+ }
164
+ const tmp = configPath + ".tmp";
165
+ fs.writeFileSync(tmp, JSON.stringify(cfg, null, 2));
166
+ fs.renameSync(tmp, configPath);
167
+ const model = cfg.agents?.defaults?.model ?? "none";
168
+ console.log(`Platform config pre-seeded (model: ${String(model)}).`);
169
+ } catch (err) {
170
+ console.warn(`Platform config pre-seed skipped: ${String(err)}`);
171
+ }
172
+ }
173
+ try {
174
+ execFileSync("openclaw", ["doctor", "--fix"], {
175
+ env: { ...process.env, OPENCLAW_STATE_DIR: openclawStateDir },
176
+ stdio: "inherit",
177
+ timeout: 3e4
178
+ });
179
+ } catch {
180
+ }
181
+ console.log("Starting OpenClaw gateway...");
182
+ var workspaceDir = process.env.PRLL_OPENCLAW_WORKSPACE_DIR?.trim() || "";
183
+ var gatewayEnv = {
184
+ ...process.env,
185
+ OPENCLAW_STATE_DIR: openclawStateDir
186
+ };
187
+ if (workspaceDir) {
188
+ gatewayEnv.PRLL_WIKI_MOUNT_ROOT = workspaceDir;
189
+ }
190
+ if (PRLL_SWIMLANE_NAME) {
191
+ gatewayEnv.PRLL_SWIMLANE_NAME = PRLL_SWIMLANE_NAME;
192
+ }
193
+ var gatewayArgs = ["gateway", "run"];
194
+ if (gatewayPort !== "0") {
195
+ gatewayArgs.push("--port", gatewayPort);
196
+ gatewayEnv.OPENCLAW_GATEWAY_PORT = gatewayPort;
197
+ }
198
+ var cwd = workspaceDir || path.join(openclawStateDir, "workspace");
199
+ fs.mkdirSync(cwd, { recursive: true });
200
+ var child = spawn("openclaw", gatewayArgs, {
201
+ env: gatewayEnv,
202
+ cwd,
203
+ stdio: "inherit",
204
+ detached: false
205
+ });
206
+ function forwardSignal(sig) {
207
+ try {
208
+ child.kill(sig);
209
+ } catch {
210
+ }
211
+ }
212
+ process.on("SIGTERM", () => forwardSignal("SIGTERM"));
213
+ process.on("SIGINT", () => forwardSignal("SIGINT"));
214
+ child.on("close", (code, signal) => {
215
+ if (signal === "SIGTERM")
216
+ process.exit(143);
217
+ if (signal === "SIGINT")
218
+ process.exit(130);
219
+ process.exit(code ?? 1);
220
+ });
package/dist/cli.d.ts ADDED
@@ -0,0 +1,2 @@
1
+ export declare function runCLI(args: string[]): Promise<"run-daemon" | "handled">;
2
+ //# sourceMappingURL=cli.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"cli.d.ts","sourceRoot":"","sources":["../src/cli.ts"],"names":[],"mappings":"AAiPA,wBAAsB,MAAM,CAAC,IAAI,EAAE,MAAM,EAAE,GAAG,OAAO,CAAC,YAAY,GAAG,SAAS,CAAC,CAoD9E"}
package/dist/cli.js ADDED
@@ -0,0 +1,277 @@
1
+ import * as fs from "node:fs";
2
+ import * as path from "node:path";
3
+ import * as os from "node:os";
4
+ import * as readline from "node:readline";
5
+ import { spawn, execSync } from "node:child_process";
6
+ import { daemonConfigDir, daemonConfigPath } from "./config.js";
7
+ const CONFIG_DIR = daemonConfigDir();
8
+ const CONFIG_PATH = daemonConfigPath();
9
+ function readConfig() {
10
+ try {
11
+ return JSON.parse(fs.readFileSync(CONFIG_PATH, "utf-8"));
12
+ }
13
+ catch {
14
+ return null;
15
+ }
16
+ }
17
+ function writeConfig(config) {
18
+ fs.mkdirSync(CONFIG_DIR, { recursive: true, mode: 0o700 });
19
+ fs.writeFileSync(CONFIG_PATH, JSON.stringify(config, null, 2), { mode: 0o600 });
20
+ fs.chmodSync(CONFIG_PATH, 0o600);
21
+ }
22
+ function prompt(question) {
23
+ const rl = readline.createInterface({ input: process.stdin, output: process.stdout });
24
+ return new Promise((resolve) => {
25
+ rl.question(question, (answer) => {
26
+ rl.close();
27
+ resolve(answer.trim());
28
+ });
29
+ });
30
+ }
31
+ function isMacOS() {
32
+ return process.platform === "darwin";
33
+ }
34
+ function isLinux() {
35
+ return process.platform === "linux";
36
+ }
37
+ const PLIST_LABEL = "com.parall.daemon";
38
+ function plistPath() {
39
+ return path.join(os.homedir(), "Library", "LaunchAgents", `${PLIST_LABEL}.plist`);
40
+ }
41
+ function systemdUnitPath() {
42
+ return path.join(os.homedir(), ".config", "systemd", "user", "parall-daemon.service");
43
+ }
44
+ function getDaemonBin() {
45
+ try {
46
+ return execSync("which parall-daemon", { encoding: "utf-8", stdio: "pipe" }).trim();
47
+ }
48
+ catch {
49
+ return process.argv[1] ?? "parall-daemon";
50
+ }
51
+ }
52
+ function generatePlist(daemonBin) {
53
+ const logPath = path.join(os.homedir(), "Library", "Logs", "parall-daemon.log");
54
+ return `<?xml version="1.0" encoding="UTF-8"?>
55
+ <!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
56
+ <plist version="1.0">
57
+ <dict>
58
+ <key>Label</key>
59
+ <string>${PLIST_LABEL}</string>
60
+ <key>ProgramArguments</key>
61
+ <array>
62
+ <string>${daemonBin}</string>
63
+ </array>
64
+ <key>RunAtLoad</key>
65
+ <true/>
66
+ <key>KeepAlive</key>
67
+ <true/>
68
+ <key>ThrottleInterval</key>
69
+ <integer>5</integer>
70
+ <key>StandardOutPath</key>
71
+ <string>${logPath}</string>
72
+ <key>StandardErrorPath</key>
73
+ <string>${logPath}</string>
74
+ </dict>
75
+ </plist>`;
76
+ }
77
+ function generateSystemdUnit(daemonBin) {
78
+ return `[Unit]
79
+ Description=Parall Daemon
80
+ After=network-online.target
81
+ Wants=network-online.target
82
+
83
+ [Service]
84
+ Type=simple
85
+ ExecStart=${daemonBin}
86
+ Restart=always
87
+ RestartSec=5
88
+
89
+ [Install]
90
+ WantedBy=default.target`;
91
+ }
92
+ function installService() {
93
+ const config = readConfig();
94
+ if (!config) {
95
+ console.error("No config found. Run `parall-daemon init` first.");
96
+ process.exit(1);
97
+ }
98
+ const bin = getDaemonBin();
99
+ if (isMacOS()) {
100
+ const dir = path.dirname(plistPath());
101
+ fs.mkdirSync(dir, { recursive: true });
102
+ fs.writeFileSync(plistPath(), generatePlist(bin));
103
+ execSync(`launchctl bootout gui/$(id -u) ${plistPath()} 2>/dev/null || true`);
104
+ execSync(`launchctl bootstrap gui/$(id -u) ${plistPath()}`);
105
+ console.log(`launchd agent installed: ${plistPath()}`);
106
+ }
107
+ else if (isLinux()) {
108
+ const dir = path.dirname(systemdUnitPath());
109
+ fs.mkdirSync(dir, { recursive: true });
110
+ fs.writeFileSync(systemdUnitPath(), generateSystemdUnit(bin));
111
+ execSync("systemctl --user daemon-reload");
112
+ execSync("systemctl --user enable --now parall-daemon");
113
+ console.log(`systemd service installed: ${systemdUnitPath()}`);
114
+ }
115
+ else {
116
+ console.log("Unsupported platform. Run `parall-daemon` manually.");
117
+ }
118
+ }
119
+ async function cmdInit() {
120
+ const existing = readConfig();
121
+ const defaultUrl = existing?.api_url || "https://api.parall.com";
122
+ const apiUrl = (await prompt(`API URL [${defaultUrl}]: `)) || defaultUrl;
123
+ const apiKey = await prompt("Machine key (mck_...): ");
124
+ if (!apiKey.startsWith("mck_")) {
125
+ console.error('Error: Machine key must start with "mck_"');
126
+ process.exit(1);
127
+ }
128
+ writeConfig({ api_url: apiUrl, api_key: apiKey });
129
+ console.log(`Config written to ${CONFIG_PATH}`);
130
+ const install = await prompt("Install as background service? (Y/n): ");
131
+ if (install.toLowerCase() !== "n") {
132
+ installService();
133
+ }
134
+ }
135
+ function cmdStatus() {
136
+ const config = readConfig();
137
+ console.log(`Config: ${config ? CONFIG_PATH : "not configured"}`);
138
+ if (isMacOS()) {
139
+ try {
140
+ const output = execSync(`launchctl print gui/$(id -u)/${PLIST_LABEL} 2>&1`, {
141
+ encoding: "utf-8",
142
+ });
143
+ const running = output.includes("state = running");
144
+ console.log(`Service: ${running ? "running" : "stopped"}`);
145
+ const pidMatch = output.match(/pid\s*=\s*(\d+)/);
146
+ if (pidMatch)
147
+ console.log(`PID: ${pidMatch[1]}`);
148
+ }
149
+ catch {
150
+ console.log("Service: not installed");
151
+ }
152
+ }
153
+ else if (isLinux()) {
154
+ try {
155
+ execSync("systemctl --user is-active parall-daemon", { encoding: "utf-8", stdio: "pipe" });
156
+ console.log("Service: running");
157
+ }
158
+ catch {
159
+ console.log("Service: stopped or not installed");
160
+ }
161
+ }
162
+ }
163
+ function cmdStop() {
164
+ if (isMacOS()) {
165
+ execSync(`launchctl bootout gui/$(id -u) ${plistPath()} 2>/dev/null || true`, {
166
+ stdio: "inherit",
167
+ });
168
+ }
169
+ else if (isLinux()) {
170
+ execSync("systemctl --user stop parall-daemon", { stdio: "inherit" });
171
+ }
172
+ console.log("Daemon stopped.");
173
+ }
174
+ function cmdLogs(lines) {
175
+ if (isLinux()) {
176
+ const child = spawn("journalctl", ["--user-unit", "parall-daemon", "-n", lines, "-f"], {
177
+ stdio: "inherit",
178
+ });
179
+ child.on("exit", (code) => process.exit(code ?? 0));
180
+ return;
181
+ }
182
+ const logPath = path.join(os.homedir(), "Library", "Logs", "parall-daemon.log");
183
+ if (!fs.existsSync(logPath)) {
184
+ console.log("No log file found at", logPath);
185
+ return;
186
+ }
187
+ const child = spawn("tail", ["-n", lines, "-f", logPath], { stdio: "inherit" });
188
+ child.on("exit", (code) => process.exit(code ?? 0));
189
+ }
190
+ function cmdServiceUninstall() {
191
+ if (isMacOS()) {
192
+ execSync(`launchctl bootout gui/$(id -u) ${plistPath()} 2>/dev/null || true`);
193
+ if (fs.existsSync(plistPath()))
194
+ fs.unlinkSync(plistPath());
195
+ console.log("launchd agent uninstalled.");
196
+ }
197
+ else if (isLinux()) {
198
+ execSync("systemctl --user stop parall-daemon 2>/dev/null || true");
199
+ execSync("systemctl --user disable parall-daemon 2>/dev/null || true");
200
+ if (fs.existsSync(systemdUnitPath()))
201
+ fs.unlinkSync(systemdUnitPath());
202
+ execSync("systemctl --user daemon-reload");
203
+ console.log("systemd service uninstalled.");
204
+ }
205
+ else {
206
+ console.log("Unsupported platform.");
207
+ }
208
+ }
209
+ function printUsage() {
210
+ console.log(`
211
+ parall-daemon — Parall local agent runtime
212
+
213
+ Usage:
214
+ parall-daemon Run the daemon (default, foreground)
215
+ parall-daemon init Configure the daemon (interactive)
216
+ parall-daemon status Show daemon service status
217
+ parall-daemon stop Stop the background service
218
+ parall-daemon logs [-n LINES] Tail daemon logs
219
+ parall-daemon service install Install as background service (launchd/systemd)
220
+ parall-daemon service uninstall Uninstall background service
221
+ parall-daemon help Show this help
222
+ `.trim());
223
+ }
224
+ export async function runCLI(args) {
225
+ const cmd = args[0];
226
+ switch (cmd) {
227
+ case "init":
228
+ await cmdInit();
229
+ return "handled";
230
+ case "status":
231
+ cmdStatus();
232
+ return "handled";
233
+ case "stop":
234
+ cmdStop();
235
+ return "handled";
236
+ case "logs": {
237
+ let lines = "50";
238
+ if (args[1] === "-n") {
239
+ const n = Number(args[2]);
240
+ if (!Number.isInteger(n) || n <= 0) {
241
+ console.error("Error: -n requires a positive integer");
242
+ process.exit(1);
243
+ }
244
+ lines = String(n);
245
+ }
246
+ cmdLogs(lines);
247
+ return "handled";
248
+ }
249
+ case "service": {
250
+ const sub = args[1];
251
+ if (sub === "install") {
252
+ installService();
253
+ }
254
+ else if (sub === "uninstall") {
255
+ cmdServiceUninstall();
256
+ }
257
+ else {
258
+ console.error(`Unknown service command: ${sub ?? "(none)"}`);
259
+ console.log("Usage: parall-daemon service [install|uninstall]");
260
+ process.exit(1);
261
+ }
262
+ return "handled";
263
+ }
264
+ case "help":
265
+ case "--help":
266
+ case "-h":
267
+ printUsage();
268
+ return "handled";
269
+ default:
270
+ if (cmd) {
271
+ console.error(`Unknown command: ${cmd}`);
272
+ printUsage();
273
+ process.exit(1);
274
+ }
275
+ return "run-daemon";
276
+ }
277
+ }
package/dist/config.d.ts CHANGED
@@ -46,6 +46,8 @@ export type ClaudeDaemonConfig = {
46
46
  supervisorRestartBackoffMs: number;
47
47
  supervisorRestartBackoffMaxMs: number;
48
48
  };
49
+ export declare function daemonConfigDir(env?: NodeJS.ProcessEnv): string;
50
+ export declare function daemonConfigPath(env?: NodeJS.ProcessEnv): string;
49
51
  export declare function resolveClaudeDaemonConfig(env?: NodeJS.ProcessEnv): ClaudeDaemonConfig;
50
52
  /** Per-agent state dir under the shared host volume. */
51
53
  export declare function agentStateDirFor(rootStateDir: string, agentId: string): string;
@@ -1 +1 @@
1
- {"version":3,"file":"config.d.ts","sourceRoot":"","sources":["../src/config.ts"],"names":[],"mappings":"AAGA;;;;;;;;;;;GAWG;AACH,MAAM,MAAM,kBAAkB,GAAG;IAC/B,MAAM,EAAE,MAAM,CAAC;IACf,qCAAqC;IACrC,MAAM,EAAE,MAAM,CAAC;IACf,2GAA2G;IAC3G,QAAQ,EAAE,MAAM,CAAC;IACjB,yFAAyF;IACzF,YAAY,EAAE,MAAM,CAAC;IACrB,kGAAkG;IAClG,cAAc,EAAE,MAAM,CAAC;IACvB,wEAAwE;IACxE,KAAK,CAAC,EAAE,MAAM,CAAC;IACf,iFAAiF;IACjF,cAAc,EAAE,MAAM,CAAC;IACvB,iFAAiF;IACjF,mBAAmB,EAAE,MAAM,CAAC;IAC5B,oDAAoD;IACpD,gBAAgB,EAAE,MAAM,CAAC;IACzB,oFAAoF;IACpF,mBAAmB,EAAE,MAAM,CAAC;IAC5B;;;;;OAKG;IACH,kBAAkB,EAAE,MAAM,CAAC;IAC3B,qBAAqB,EAAE,MAAM,CAAC;IAC9B;;;;OAIG;IACH,0BAA0B,EAAE,MAAM,CAAC;IACnC,6BAA6B,EAAE,MAAM,CAAC;CACvC,CAAC;AA2BF,wBAAgB,yBAAyB,CAAC,GAAG,GAAE,MAAM,CAAC,UAAwB,GAAG,kBAAkB,CA8BlG;AASD,wDAAwD;AACxD,wBAAgB,gBAAgB,CAAC,YAAY,EAAE,MAAM,EAAE,OAAO,EAAE,MAAM,GAAG,MAAM,CAE9E;AAED;;mEAEmE;AACnE,wBAAgB,kBAAkB,CAAC,cAAc,EAAE,MAAM,EAAE,OAAO,EAAE,MAAM,GAAG,MAAM,CAElF;AAED,+EAA+E;AAC/E,wBAAgB,8BAA8B,CAAC,cAAc,EAAE,MAAM,GAAG,MAAM,CAE7E;AAED,uEAAuE;AACvE,wBAAgB,6BAA6B,CAAC,eAAe,EAAE,MAAM,GAAG,MAAM,CAE7E;AAED,iEAAiE;AACjE,wBAAgB,oBAAoB,CAAC,YAAY,EAAE,MAAM,EAAE,OAAO,EAAE,MAAM,GAAG,MAAM,CAElF;AAED,wBAAgB,YAAY,CAAC,MAAM,EAAE,MAAM,EAAE,aAAa,CAAC,EAAE,MAAM,GAAG,MAAM,CAE3E"}
1
+ {"version":3,"file":"config.d.ts","sourceRoot":"","sources":["../src/config.ts"],"names":[],"mappings":"AAIA;;;;;;;;;;;GAWG;AACH,MAAM,MAAM,kBAAkB,GAAG;IAC/B,MAAM,EAAE,MAAM,CAAC;IACf,qCAAqC;IACrC,MAAM,EAAE,MAAM,CAAC;IACf,2GAA2G;IAC3G,QAAQ,EAAE,MAAM,CAAC;IACjB,yFAAyF;IACzF,YAAY,EAAE,MAAM,CAAC;IACrB,kGAAkG;IAClG,cAAc,EAAE,MAAM,CAAC;IACvB,wEAAwE;IACxE,KAAK,CAAC,EAAE,MAAM,CAAC;IACf,iFAAiF;IACjF,cAAc,EAAE,MAAM,CAAC;IACvB,iFAAiF;IACjF,mBAAmB,EAAE,MAAM,CAAC;IAC5B,oDAAoD;IACpD,gBAAgB,EAAE,MAAM,CAAC;IACzB,oFAAoF;IACpF,mBAAmB,EAAE,MAAM,CAAC;IAC5B;;;;;OAKG;IACH,kBAAkB,EAAE,MAAM,CAAC;IAC3B,qBAAqB,EAAE,MAAM,CAAC;IAC9B;;;;OAIG;IACH,0BAA0B,EAAE,MAAM,CAAC;IACnC,6BAA6B,EAAE,MAAM,CAAC;CACvC,CAAC;AA2BF,wBAAgB,eAAe,CAAC,GAAG,GAAE,MAAM,CAAC,UAAwB,GAAG,MAAM,CAE5E;AAED,wBAAgB,gBAAgB,CAAC,GAAG,GAAE,MAAM,CAAC,UAAwB,GAAG,MAAM,CAE7E;AAyBD,wBAAgB,yBAAyB,CAAC,GAAG,GAAE,MAAM,CAAC,UAAwB,GAAG,kBAAkB,CA4ClG;AASD,wDAAwD;AACxD,wBAAgB,gBAAgB,CAAC,YAAY,EAAE,MAAM,EAAE,OAAO,EAAE,MAAM,GAAG,MAAM,CAE9E;AAED;;mEAEmE;AACnE,wBAAgB,kBAAkB,CAAC,cAAc,EAAE,MAAM,EAAE,OAAO,EAAE,MAAM,GAAG,MAAM,CAElF;AAED,+EAA+E;AAC/E,wBAAgB,8BAA8B,CAAC,cAAc,EAAE,MAAM,GAAG,MAAM,CAE7E;AAED,uEAAuE;AACvE,wBAAgB,6BAA6B,CAAC,eAAe,EAAE,MAAM,GAAG,MAAM,CAE7E;AAED,iEAAiE;AACjE,wBAAgB,oBAAoB,CAAC,YAAY,EAAE,MAAM,EAAE,OAAO,EAAE,MAAM,GAAG,MAAM,CAElF;AAED,wBAAgB,YAAY,CAAC,MAAM,EAAE,MAAM,EAAE,aAAa,CAAC,EAAE,MAAM,GAAG,MAAM,CAE3E"}
package/dist/config.js CHANGED
@@ -1,3 +1,4 @@
1
+ import * as fs from "node:fs";
1
2
  import * as os from "node:os";
2
3
  import * as path from "node:path";
3
4
  function requireEnv(env, name) {
@@ -23,9 +24,49 @@ function parseMsAllowZero(value, fallback) {
23
24
  const n = Number(value);
24
25
  return Number.isFinite(n) && n >= 0 ? n : fallback;
25
26
  }
27
+ export function daemonConfigDir(env = process.env) {
28
+ return path.join(env.HOME || os.homedir(), ".parall-daemon");
29
+ }
30
+ export function daemonConfigPath(env = process.env) {
31
+ return path.join(daemonConfigDir(env), "config.json");
32
+ }
33
+ function tryLoadConfigFile(env) {
34
+ const cfgPath = daemonConfigPath(env);
35
+ let content;
36
+ try {
37
+ content = fs.readFileSync(cfgPath, 'utf-8');
38
+ }
39
+ catch (err) {
40
+ if (err.code === 'ENOENT')
41
+ return null;
42
+ console.error(`Failed to read daemon config at ${cfgPath}: ${String(err)}`);
43
+ return null;
44
+ }
45
+ try {
46
+ return JSON.parse(content);
47
+ }
48
+ catch (err) {
49
+ console.error(`Failed to parse daemon config at ${cfgPath}: ${String(err)}`);
50
+ return null;
51
+ }
52
+ }
26
53
  export function resolveClaudeDaemonConfig(env = process.env) {
27
- const apiUrl = requireEnv(env, "PRLL_API_URL");
28
- const apiKey = requireEnv(env, "PRLL_API_KEY");
54
+ let apiUrl = env.PRLL_API_URL?.trim() || "";
55
+ let apiKey = env.PRLL_API_KEY?.trim() || "";
56
+ // Fall back to config file for values not provided via env.
57
+ if (!apiUrl || !apiKey) {
58
+ const file = tryLoadConfigFile(env);
59
+ if (file) {
60
+ if (!apiUrl && file.api_url)
61
+ apiUrl = file.api_url.trim();
62
+ if (!apiKey && file.api_key)
63
+ apiKey = file.api_key.trim();
64
+ }
65
+ }
66
+ if (!apiUrl)
67
+ throw new Error("Missing required env var: PRLL_API_URL");
68
+ if (!apiKey)
69
+ throw new Error("Missing required env var: PRLL_API_KEY");
29
70
  if (!apiKey.startsWith("mck_")) {
30
71
  // Fatal startup validation: the daemon must never run with an agent or
31
72
  // human key because child launch credentials are minted from this bearer.
package/dist/index.js CHANGED
@@ -2,6 +2,7 @@
2
2
  import { ParallClient } from "@parall/sdk";
3
3
  import { resolveClaudeDaemonConfig, resolveWsUrl } from "./config.js";
4
4
  import { DaemonSupervisor, sleepCancellable } from "./supervisor.js";
5
+ import { runCLI } from "./cli.js";
5
6
  function createLogger(prefix) {
6
7
  return {
7
8
  info: (msg) => console.log(`[${prefix}] ${msg}`),
@@ -103,7 +104,17 @@ async function main() {
103
104
  config.wsUrl = resolveWsUrl(config.apiUrl, config.wsUrl);
104
105
  await runForever(config, client, log, abortController.signal);
105
106
  }
106
- main().catch((err) => {
107
+ const cliArgs = process.argv.slice(2);
108
+ runCLI(cliArgs)
109
+ .then((result) => {
110
+ if (result === "handled")
111
+ return;
112
+ main().catch((err) => {
113
+ console.error(`[daemon] fatal: ${formatError(err)}`);
114
+ process.exitCode = 1;
115
+ });
116
+ })
117
+ .catch((err) => {
107
118
  console.error(`[daemon] fatal: ${formatError(err)}`);
108
119
  process.exitCode = 1;
109
120
  });
@@ -39,6 +39,12 @@ export declare class DaemonSupervisor {
39
39
  stop(): Promise<void>;
40
40
  private bootstrapWithRetry;
41
41
  private fullReconcile;
42
+ /**
43
+ * Detects a legacy flat state layout (no agents/ subdir) and migrates it
44
+ * into the per-agent directory for the owning agent. Ownership is determined
45
+ * by parsing session state files which embed the agent ID in the runtimeKey.
46
+ */
47
+ private migrateFlatLayout;
42
48
  private handleAgentAttached;
43
49
  private handleAgentDetached;
44
50
  private restartChildNow;
@@ -1 +1 @@
1
- {"version":3,"file":"supervisor.d.ts","sourceRoot":"","sources":["../src/supervisor.ts"],"names":[],"mappings":"AAGA,OAAO,EAAE,YAAY,EAAmJ,MAAM,aAAa,CAAC;AAC5L,OAAO,EACL,KAAK,kBAAkB,EAMxB,MAAM,aAAa,CAAC;AAGrB,MAAM,WAAW,YAAY;IAC3B,IAAI,CAAC,GAAG,EAAE,MAAM,GAAG,IAAI,CAAC;IACxB,IAAI,CAAC,GAAG,EAAE,MAAM,GAAG,IAAI,CAAC;IACxB,KAAK,CAAC,GAAG,EAAE,MAAM,GAAG,IAAI,CAAC;CAC1B;AAED;;;;GAIG;AACH,iBAAS,gBAAgB,CAAC,EAAE,EAAE,MAAM,EAAE,MAAM,EAAE,WAAW,GAAG,OAAO,CAAC,OAAO,CAAC,CAa3E;AAED,OAAO,EAAE,gBAAgB,EAAE,CAAC;AAmB5B;;;;;;;;;;GAUG;AACH,qBAAa,gBAAgB;IAQzB,OAAO,CAAC,QAAQ,CAAC,MAAM;IACvB,OAAO,CAAC,QAAQ,CAAC,MAAM;IACvB,OAAO,CAAC,QAAQ,CAAC,GAAG;IATtB,OAAO,CAAC,QAAQ,CAAC,QAAQ,CAAiC;IAC1D,OAAO,CAAC,EAAE,CAAyB;IACnC,OAAO,CAAC,OAAO,CAAS;IACxB,OAAO,CAAC,YAAY,CAAuB;IAC3C,OAAO,CAAC,WAAW,CAA6B;gBAG7B,MAAM,EAAE,kBAAkB,EAC1B,MAAM,EAAE,YAAY,EACpB,GAAG,EAAE,YAAY;IAGpC,yEAAyE;IACnE,GAAG,CAAC,MAAM,EAAE,WAAW,GAAG,OAAO,CAAC,IAAI,CAAC;IA+D7C,sEAAsE;IAChE,IAAI,IAAI,OAAO,CAAC,IAAI,CAAC;YA6Bb,kBAAkB;YAoClB,aAAa;YAqDb,mBAAmB;YA6BnB,mBAAmB;YAgBnB,eAAe;YAcf,UAAU;IAmCxB,OAAO,CAAC,UAAU;YAgEJ,cAAc;IAyB5B,OAAO,CAAC,0BAA0B;CA8BnC"}
1
+ {"version":3,"file":"supervisor.d.ts","sourceRoot":"","sources":["../src/supervisor.ts"],"names":[],"mappings":"AAGA,OAAO,EAAE,YAAY,EAAmJ,MAAM,aAAa,CAAC;AAC5L,OAAO,EACL,KAAK,kBAAkB,EAMxB,MAAM,aAAa,CAAC;AASrB,MAAM,WAAW,YAAY;IAC3B,IAAI,CAAC,GAAG,EAAE,MAAM,GAAG,IAAI,CAAC;IACxB,IAAI,CAAC,GAAG,EAAE,MAAM,GAAG,IAAI,CAAC;IACxB,KAAK,CAAC,GAAG,EAAE,MAAM,GAAG,IAAI,CAAC;CAC1B;AAED;;;;GAIG;AACH,iBAAS,gBAAgB,CAAC,EAAE,EAAE,MAAM,EAAE,MAAM,EAAE,WAAW,GAAG,OAAO,CAAC,OAAO,CAAC,CAa3E;AAED,OAAO,EAAE,gBAAgB,EAAE,CAAC;AAuB5B;;;;;;;;;;GAUG;AACH,qBAAa,gBAAgB;IAQzB,OAAO,CAAC,QAAQ,CAAC,MAAM;IACvB,OAAO,CAAC,QAAQ,CAAC,MAAM;IACvB,OAAO,CAAC,QAAQ,CAAC,GAAG;IATtB,OAAO,CAAC,QAAQ,CAAC,QAAQ,CAAiC;IAC1D,OAAO,CAAC,EAAE,CAAyB;IACnC,OAAO,CAAC,OAAO,CAAS;IACxB,OAAO,CAAC,YAAY,CAAuB;IAC3C,OAAO,CAAC,WAAW,CAA6B;gBAG7B,MAAM,EAAE,kBAAkB,EAC1B,MAAM,EAAE,YAAY,EACpB,GAAG,EAAE,YAAY;IAGpC,yEAAyE;IACnE,GAAG,CAAC,MAAM,EAAE,WAAW,GAAG,OAAO,CAAC,IAAI,CAAC;IAgE7C,sEAAsE;IAChE,IAAI,IAAI,OAAO,CAAC,IAAI,CAAC;YA6Bb,kBAAkB;YAoClB,aAAa;IAqD3B;;;;OAIG;IACH,OAAO,CAAC,iBAAiB;YA6CX,mBAAmB;YA6BnB,mBAAmB;YAgBnB,eAAe;YAcf,UAAU;IA+CxB,OAAO,CAAC,UAAU;YAwEJ,cAAc;IAyB5B,OAAO,CAAC,0BAA0B;CA8BnC"}