@parall/daemon 1.29.3 → 1.31.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/bundle/manifest.json +12 -11
- package/bundle/parall-claude-agent.js +252 -85
- package/bundle/parall-codex-agent.js +248 -94
- package/bundle/parall-daemon.js +1076 -299
- package/bundle/parall-openclaw-agent.js +2 -2
- package/dist/cli.d.ts.map +1 -1
- package/dist/cli.js +52 -2
- package/dist/config.d.ts +13 -0
- package/dist/config.d.ts.map +1 -1
- package/dist/config.js +17 -0
- package/dist/filesystem.d.ts +7 -0
- package/dist/filesystem.d.ts.map +1 -0
- package/dist/filesystem.js +118 -0
- package/dist/index.js +30 -3
- package/dist/runtimes.d.ts +9 -8
- package/dist/runtimes.d.ts.map +1 -1
- package/dist/runtimes.js +47 -83
- package/dist/supervisor.d.ts +5 -0
- package/dist/supervisor.d.ts.map +1 -1
- package/dist/supervisor.js +63 -0
- package/dist/updater-manifest.d.ts +39 -0
- package/dist/updater-manifest.d.ts.map +1 -0
- package/dist/updater-manifest.js +94 -0
- package/dist/updater.d.ts +60 -0
- package/dist/updater.d.ts.map +1 -0
- package/dist/updater.js +409 -0
- package/package.json +6 -6
package/bundle/parall-daemon.js
CHANGED
|
@@ -1,11 +1,630 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
|
+
var __defProp = Object.defineProperty;
|
|
3
|
+
var __getOwnPropNames = Object.getOwnPropertyNames;
|
|
4
|
+
var __esm = (fn, res) => function __init() {
|
|
5
|
+
return fn && (res = (0, fn[__getOwnPropNames(fn)[0]])(fn = 0)), res;
|
|
6
|
+
};
|
|
7
|
+
var __export = (target, all) => {
|
|
8
|
+
for (var name in all)
|
|
9
|
+
__defProp(target, name, { get: all[name], enumerable: true });
|
|
10
|
+
};
|
|
11
|
+
|
|
12
|
+
// ts/daemon/dist/config.js
|
|
13
|
+
var config_exports = {};
|
|
14
|
+
__export(config_exports, {
|
|
15
|
+
agentClaudeCredentialsFileFor: () => agentClaudeCredentialsFileFor,
|
|
16
|
+
agentClaudeHomeFor: () => agentClaudeHomeFor,
|
|
17
|
+
agentStateDirFor: () => agentStateDirFor,
|
|
18
|
+
agentWorkspaceDirFor: () => agentWorkspaceDirFor,
|
|
19
|
+
daemonConfigDir: () => daemonConfigDir,
|
|
20
|
+
daemonConfigPath: () => daemonConfigPath,
|
|
21
|
+
resolveBundleDir: () => resolveBundleDir,
|
|
22
|
+
resolveClaudeDaemonConfig: () => resolveClaudeDaemonConfig,
|
|
23
|
+
resolveWsUrl: () => resolveWsUrl,
|
|
24
|
+
sharedClaudeCredentialsFileFor: () => sharedClaudeCredentialsFileFor
|
|
25
|
+
});
|
|
26
|
+
import * as fs from "node:fs";
|
|
27
|
+
import * as os from "node:os";
|
|
28
|
+
import * as path from "node:path";
|
|
29
|
+
function resolvePath(value) {
|
|
30
|
+
return path.isAbsolute(value) ? value : path.resolve(process.cwd(), value);
|
|
31
|
+
}
|
|
32
|
+
function parseMs(value, fallback) {
|
|
33
|
+
if (!value)
|
|
34
|
+
return fallback;
|
|
35
|
+
const n = Number(value);
|
|
36
|
+
return Number.isFinite(n) && n > 0 ? n : fallback;
|
|
37
|
+
}
|
|
38
|
+
function parseMsAllowZero(value, fallback) {
|
|
39
|
+
if (value === void 0)
|
|
40
|
+
return fallback;
|
|
41
|
+
const n = Number(value);
|
|
42
|
+
return Number.isFinite(n) && n >= 0 ? n : fallback;
|
|
43
|
+
}
|
|
44
|
+
function daemonConfigDir(env = process.env) {
|
|
45
|
+
return path.join(env.HOME || os.homedir(), ".parall-daemon");
|
|
46
|
+
}
|
|
47
|
+
function daemonConfigPath(env = process.env) {
|
|
48
|
+
return path.join(daemonConfigDir(env), "config.json");
|
|
49
|
+
}
|
|
50
|
+
function tryLoadConfigFile(env) {
|
|
51
|
+
const cfgPath = daemonConfigPath(env);
|
|
52
|
+
let content;
|
|
53
|
+
try {
|
|
54
|
+
content = fs.readFileSync(cfgPath, "utf-8");
|
|
55
|
+
} catch (err) {
|
|
56
|
+
if (err.code === "ENOENT")
|
|
57
|
+
return null;
|
|
58
|
+
console.error(`Failed to read daemon config at ${cfgPath}: ${String(err)}`);
|
|
59
|
+
return null;
|
|
60
|
+
}
|
|
61
|
+
try {
|
|
62
|
+
return JSON.parse(content);
|
|
63
|
+
} catch (err) {
|
|
64
|
+
console.error(`Failed to parse daemon config at ${cfgPath}: ${String(err)}`);
|
|
65
|
+
return null;
|
|
66
|
+
}
|
|
67
|
+
}
|
|
68
|
+
function resolveClaudeDaemonConfig(env = process.env) {
|
|
69
|
+
let apiUrl = env.PRLL_API_URL?.trim() || "";
|
|
70
|
+
let apiKey = env.PRLL_API_KEY?.trim() || "";
|
|
71
|
+
if (!apiUrl || !apiKey) {
|
|
72
|
+
const file = tryLoadConfigFile(env);
|
|
73
|
+
if (file) {
|
|
74
|
+
if (!apiUrl && file.api_url)
|
|
75
|
+
apiUrl = file.api_url.trim();
|
|
76
|
+
if (!apiKey && file.api_key)
|
|
77
|
+
apiKey = file.api_key.trim();
|
|
78
|
+
}
|
|
79
|
+
}
|
|
80
|
+
if (!apiUrl)
|
|
81
|
+
throw new Error("Missing required env var: PRLL_API_URL");
|
|
82
|
+
if (!apiKey)
|
|
83
|
+
throw new Error("Missing required env var: PRLL_API_KEY");
|
|
84
|
+
if (!apiKey.startsWith("mck_")) {
|
|
85
|
+
throw new Error(`PRLL_API_KEY does not look like a Machine bearer (expected prefix "mck_"). Daemon mode requires a machine-scoped key issued via POST /machines/{id}/keys.`);
|
|
86
|
+
}
|
|
87
|
+
const rootClaudeHome = resolvePath(env.PRLL_CLAUDE_HOME?.trim() || env.HOME || os.homedir());
|
|
88
|
+
const rootStateDir = resolvePath(env.PRLL_CLAUDE_STATE_DIR?.trim() || path.join(rootClaudeHome, ".parall-agent"));
|
|
89
|
+
return {
|
|
90
|
+
apiUrl,
|
|
91
|
+
apiKey,
|
|
92
|
+
agentBin: env.PRLL_CLAUDE_AGENT_BIN?.trim() || "parall-claude-agent",
|
|
93
|
+
rootStateDir,
|
|
94
|
+
rootClaudeHome,
|
|
95
|
+
wsUrl: env.PRLL_WS_URL?.trim() || void 0,
|
|
96
|
+
swimlaneName: env.PRLL_SWIMLANE_NAME?.trim() || void 0,
|
|
97
|
+
pollIntervalMs: parseMs(env.PRLL_DAEMON_POLL_INTERVAL_MS, 3e4),
|
|
98
|
+
heartbeatIntervalMs: parseMs(env.PRLL_DAEMON_HEARTBEAT_INTERVAL_MS, 3e4),
|
|
99
|
+
restartBackoffMs: parseMs(env.PRLL_DAEMON_RESTART_BACKOFF_MS, 5e3),
|
|
100
|
+
restartBackoffMaxMs: parseMs(env.PRLL_DAEMON_RESTART_BACKOFF_MAX_MS, 5 * 6e4),
|
|
101
|
+
bootstrapBackoffMs: parseMsAllowZero(env.PRLL_DAEMON_BOOTSTRAP_BACKOFF_MS, 2e3),
|
|
102
|
+
bootstrapBackoffMaxMs: parseMs(env.PRLL_DAEMON_BOOTSTRAP_BACKOFF_MAX_MS, 6e4),
|
|
103
|
+
supervisorRestartBackoffMs: parseMsAllowZero(env.PRLL_DAEMON_SUPERVISOR_RESTART_BACKOFF_MS, 5e3),
|
|
104
|
+
supervisorRestartBackoffMaxMs: parseMs(env.PRLL_DAEMON_SUPERVISOR_RESTART_BACKOFF_MAX_MS, 5 * 6e4),
|
|
105
|
+
updateCdnUrl: env.PRLL_DAEMON_UPDATE_CDN_URL?.trim() || ((env.PRLL_DAEMON_UPDATE_CHANNEL?.trim() ?? "production") === "staging" ? "https://releases.staging.prll.sh/daemon/staging" : "https://releases.parall.com/daemon/production"),
|
|
106
|
+
updateIntervalMs: parseMsAllowZero(env.PRLL_DAEMON_UPDATE_INTERVAL_MS, 6 * 60 * 6e4),
|
|
107
|
+
updateDisabled: env.PRLL_DAEMON_UPDATE_DISABLED === "true" || !!env.KUBERNETES_SERVICE_HOST
|
|
108
|
+
};
|
|
109
|
+
}
|
|
110
|
+
function assertSafeAgentId(agentId) {
|
|
111
|
+
if (!/^[A-Za-z0-9_-]+$/.test(agentId)) {
|
|
112
|
+
throw new Error(`Invalid agentId for filesystem path: ${agentId}`);
|
|
113
|
+
}
|
|
114
|
+
return agentId;
|
|
115
|
+
}
|
|
116
|
+
function agentStateDirFor(rootStateDir, agentId) {
|
|
117
|
+
return path.join(rootStateDir, "agents", assertSafeAgentId(agentId));
|
|
118
|
+
}
|
|
119
|
+
function agentClaudeHomeFor(rootClaudeHome, agentId) {
|
|
120
|
+
return path.join(rootClaudeHome, "agents", assertSafeAgentId(agentId));
|
|
121
|
+
}
|
|
122
|
+
function sharedClaudeCredentialsFileFor(rootClaudeHome) {
|
|
123
|
+
return path.join(rootClaudeHome, ".claude", ".credentials.json");
|
|
124
|
+
}
|
|
125
|
+
function agentClaudeCredentialsFileFor(agentClaudeHome) {
|
|
126
|
+
return path.join(agentClaudeHome, ".claude", ".credentials.json");
|
|
127
|
+
}
|
|
128
|
+
function agentWorkspaceDirFor(rootStateDir, agentId) {
|
|
129
|
+
return path.join(rootStateDir, "agents", assertSafeAgentId(agentId), "workspace");
|
|
130
|
+
}
|
|
131
|
+
function resolveBundleDir(env = process.env) {
|
|
132
|
+
if (env.PRLL_DAEMON_BUNDLE_DIR)
|
|
133
|
+
return resolvePath(env.PRLL_DAEMON_BUNDLE_DIR);
|
|
134
|
+
return path.join(daemonConfigDir(env), "bundle");
|
|
135
|
+
}
|
|
136
|
+
function resolveWsUrl(apiUrl, explicitWsUrl, swimlaneName) {
|
|
137
|
+
const base = explicitWsUrl || `${apiUrl.replace(/\/$/, "").replace(/^http/, "ws")}/ws`;
|
|
138
|
+
if (!swimlaneName)
|
|
139
|
+
return base;
|
|
140
|
+
const url = new URL(base);
|
|
141
|
+
url.searchParams.set("swimlane", swimlaneName);
|
|
142
|
+
return url.toString();
|
|
143
|
+
}
|
|
144
|
+
var init_config = __esm({
|
|
145
|
+
"ts/daemon/dist/config.js"() {
|
|
146
|
+
"use strict";
|
|
147
|
+
}
|
|
148
|
+
});
|
|
149
|
+
|
|
150
|
+
// ts/daemon/dist/updater-manifest.js
|
|
151
|
+
import { verify } from "node:crypto";
|
|
152
|
+
function canonicalize(obj) {
|
|
153
|
+
if (Array.isArray(obj))
|
|
154
|
+
return obj.map(canonicalize);
|
|
155
|
+
if (obj !== null && typeof obj === "object") {
|
|
156
|
+
const sorted = {};
|
|
157
|
+
for (const k of Object.keys(obj).sort()) {
|
|
158
|
+
sorted[k] = canonicalize(obj[k]);
|
|
159
|
+
}
|
|
160
|
+
return sorted;
|
|
161
|
+
}
|
|
162
|
+
return obj;
|
|
163
|
+
}
|
|
164
|
+
function verifyManifestSignature(manifest, publicKey) {
|
|
165
|
+
const { signature, ...rest } = manifest;
|
|
166
|
+
const canonical = JSON.stringify(canonicalize(rest));
|
|
167
|
+
try {
|
|
168
|
+
return verify(null, Buffer.from(canonical), publicKey, Buffer.from(signature, "base64"));
|
|
169
|
+
} catch {
|
|
170
|
+
return false;
|
|
171
|
+
}
|
|
172
|
+
}
|
|
173
|
+
function semverCompare(a, b) {
|
|
174
|
+
const pa = parseSemver(a);
|
|
175
|
+
const pb = parseSemver(b);
|
|
176
|
+
if (!pa || !pb)
|
|
177
|
+
return null;
|
|
178
|
+
for (let i = 0; i < 3; i++) {
|
|
179
|
+
if (pa.nums[i] < pb.nums[i])
|
|
180
|
+
return -1;
|
|
181
|
+
if (pa.nums[i] > pb.nums[i])
|
|
182
|
+
return 1;
|
|
183
|
+
}
|
|
184
|
+
if (!pa.pre && !pb.pre)
|
|
185
|
+
return 0;
|
|
186
|
+
if (!pa.pre)
|
|
187
|
+
return 1;
|
|
188
|
+
if (!pb.pre)
|
|
189
|
+
return -1;
|
|
190
|
+
if (pa.pre < pb.pre)
|
|
191
|
+
return -1;
|
|
192
|
+
if (pa.pre > pb.pre)
|
|
193
|
+
return 1;
|
|
194
|
+
return 0;
|
|
195
|
+
}
|
|
196
|
+
function parseSemver(v) {
|
|
197
|
+
const parts = v.split(".");
|
|
198
|
+
if (parts.length < 3)
|
|
199
|
+
return null;
|
|
200
|
+
const major = Number(parts[0]);
|
|
201
|
+
const minor = Number(parts[1]);
|
|
202
|
+
const rest = parts.slice(2).join(".");
|
|
203
|
+
const hyphen = rest.indexOf("-");
|
|
204
|
+
let patchStr;
|
|
205
|
+
let pre;
|
|
206
|
+
if (hyphen >= 0) {
|
|
207
|
+
patchStr = rest.slice(0, hyphen);
|
|
208
|
+
pre = rest.slice(hyphen + 1);
|
|
209
|
+
} else {
|
|
210
|
+
patchStr = rest;
|
|
211
|
+
}
|
|
212
|
+
const patch = Number(patchStr);
|
|
213
|
+
if (isNaN(major) || isNaN(minor) || isNaN(patch))
|
|
214
|
+
return null;
|
|
215
|
+
return { nums: [major, minor, patch], pre };
|
|
216
|
+
}
|
|
217
|
+
var init_updater_manifest = __esm({
|
|
218
|
+
"ts/daemon/dist/updater-manifest.js"() {
|
|
219
|
+
"use strict";
|
|
220
|
+
}
|
|
221
|
+
});
|
|
222
|
+
|
|
223
|
+
// ts/daemon/dist/updater.js
|
|
224
|
+
var updater_exports = {};
|
|
225
|
+
__export(updater_exports, {
|
|
226
|
+
DaemonUpdater: () => DaemonUpdater
|
|
227
|
+
});
|
|
228
|
+
import * as fs6 from "node:fs";
|
|
229
|
+
import * as path6 from "node:path";
|
|
230
|
+
import * as https from "node:https";
|
|
231
|
+
import * as http from "node:http";
|
|
232
|
+
import { createHash as createHash2 } from "node:crypto";
|
|
233
|
+
var SIGNING_PUBLIC_KEY, DaemonUpdater;
|
|
234
|
+
var init_updater = __esm({
|
|
235
|
+
"ts/daemon/dist/updater.js"() {
|
|
236
|
+
"use strict";
|
|
237
|
+
init_updater_manifest();
|
|
238
|
+
SIGNING_PUBLIC_KEY = process.env.PRLL_DAEMON_SIGNING_PUBLIC_KEY ?? "";
|
|
239
|
+
DaemonUpdater = class {
|
|
240
|
+
bundleDir;
|
|
241
|
+
cdnBaseUrl;
|
|
242
|
+
log;
|
|
243
|
+
signingEnabled;
|
|
244
|
+
updating = false;
|
|
245
|
+
periodicTimer = null;
|
|
246
|
+
constructor(bundleDir, cdnBaseUrl, log2, signingEnabled) {
|
|
247
|
+
this.bundleDir = bundleDir;
|
|
248
|
+
this.cdnBaseUrl = cdnBaseUrl;
|
|
249
|
+
this.log = log2;
|
|
250
|
+
this.signingEnabled = signingEnabled;
|
|
251
|
+
}
|
|
252
|
+
// --- Public API ---
|
|
253
|
+
/**
|
|
254
|
+
* Start periodic update checks. Call once after supervisor bootstrap.
|
|
255
|
+
*/
|
|
256
|
+
startPeriodicCheck(intervalMs) {
|
|
257
|
+
if (intervalMs <= 0 || this.periodicTimer)
|
|
258
|
+
return;
|
|
259
|
+
this.periodicTimer = setInterval(() => {
|
|
260
|
+
this.checkAndApply().catch((err) => {
|
|
261
|
+
this.log.warn(`periodic update check failed: ${String(err)}`);
|
|
262
|
+
});
|
|
263
|
+
}, intervalMs);
|
|
264
|
+
this.periodicTimer.unref?.();
|
|
265
|
+
}
|
|
266
|
+
stopPeriodicCheck() {
|
|
267
|
+
if (this.periodicTimer) {
|
|
268
|
+
clearInterval(this.periodicTimer);
|
|
269
|
+
this.periodicTimer = null;
|
|
270
|
+
}
|
|
271
|
+
}
|
|
272
|
+
/**
|
|
273
|
+
* Check CDN for a newer version. Returns true if update was applied
|
|
274
|
+
* (caller should exit for service manager restart).
|
|
275
|
+
*/
|
|
276
|
+
async checkAndApply(targetVersion) {
|
|
277
|
+
if (this.updating)
|
|
278
|
+
return false;
|
|
279
|
+
this.updating = true;
|
|
280
|
+
try {
|
|
281
|
+
return await this.doUpdate(targetVersion);
|
|
282
|
+
} catch (err) {
|
|
283
|
+
this.log.warn(`update check failed: ${String(err)}`);
|
|
284
|
+
return false;
|
|
285
|
+
} finally {
|
|
286
|
+
this.updating = false;
|
|
287
|
+
}
|
|
288
|
+
}
|
|
289
|
+
/**
|
|
290
|
+
* Handle WS-triggered update. Adds jitter for non-mandatory updates.
|
|
291
|
+
*/
|
|
292
|
+
async triggerUpdate(targetVersion, mandatory) {
|
|
293
|
+
if (!mandatory) {
|
|
294
|
+
const jitter = Math.random() * 18e4;
|
|
295
|
+
await new Promise((r) => setTimeout(r, jitter));
|
|
296
|
+
}
|
|
297
|
+
return this.checkAndApply(targetVersion);
|
|
298
|
+
}
|
|
299
|
+
/**
|
|
300
|
+
* Check if we need to roll back from a failed update.
|
|
301
|
+
* Call on every daemon startup, before bootstrap.
|
|
302
|
+
* Returns true if rollback was performed (caller should exit immediately).
|
|
303
|
+
*/
|
|
304
|
+
checkRollback() {
|
|
305
|
+
const state = this.loadUpdateState();
|
|
306
|
+
const current = this.loadLocalManifest();
|
|
307
|
+
if (!state || !current)
|
|
308
|
+
return false;
|
|
309
|
+
if (!state.pending_version || state.pending_version !== current.version)
|
|
310
|
+
return false;
|
|
311
|
+
state.boot_count = (state.boot_count ?? 0) + 1;
|
|
312
|
+
this.saveUpdateState(state);
|
|
313
|
+
if (state.boot_count < 3)
|
|
314
|
+
return false;
|
|
315
|
+
if (!state.previous_version)
|
|
316
|
+
return false;
|
|
317
|
+
const previousDir = path6.join(this.bundleDir, "versions", state.previous_version);
|
|
318
|
+
if (!fs6.existsSync(previousDir))
|
|
319
|
+
return false;
|
|
320
|
+
this.log.warn(`rollback: ${current.version} failed ${state.boot_count} boots, reverting to ${state.previous_version}`);
|
|
321
|
+
if (process.platform === "win32") {
|
|
322
|
+
const currentDir = path6.join(this.bundleDir, "current");
|
|
323
|
+
if (fs6.existsSync(currentDir)) {
|
|
324
|
+
for (const file of fs6.readdirSync(currentDir)) {
|
|
325
|
+
fs6.unlinkSync(path6.join(currentDir, file));
|
|
326
|
+
}
|
|
327
|
+
}
|
|
328
|
+
fs6.mkdirSync(currentDir, { recursive: true });
|
|
329
|
+
for (const file of fs6.readdirSync(previousDir)) {
|
|
330
|
+
fs6.copyFileSync(path6.join(previousDir, file), path6.join(currentDir, file));
|
|
331
|
+
}
|
|
332
|
+
} else {
|
|
333
|
+
this.swapSymlink(state.previous_version);
|
|
334
|
+
}
|
|
335
|
+
state.confirmed_version = state.previous_version;
|
|
336
|
+
state.pending_version = void 0;
|
|
337
|
+
state.boot_count = 0;
|
|
338
|
+
state.rollback_from = current.version;
|
|
339
|
+
state.rollback_at = (/* @__PURE__ */ new Date()).toISOString();
|
|
340
|
+
this.saveUpdateState(state);
|
|
341
|
+
return true;
|
|
342
|
+
}
|
|
343
|
+
/**
|
|
344
|
+
* Confirm the current version after successful health check.
|
|
345
|
+
* Clears pending state so rollback won't trigger.
|
|
346
|
+
*/
|
|
347
|
+
confirmVersion() {
|
|
348
|
+
const current = this.loadLocalManifest();
|
|
349
|
+
if (!current)
|
|
350
|
+
return;
|
|
351
|
+
const state = this.loadUpdateState() ?? {};
|
|
352
|
+
state.confirmed_version = current.version;
|
|
353
|
+
state.confirmed_at = (/* @__PURE__ */ new Date()).toISOString();
|
|
354
|
+
state.pending_version = void 0;
|
|
355
|
+
state.boot_count = 0;
|
|
356
|
+
this.saveUpdateState(state);
|
|
357
|
+
this.log.info(`update: confirmed version ${current.version}`);
|
|
358
|
+
}
|
|
359
|
+
/**
|
|
360
|
+
* Get the current local manifest version (for heartbeat reporting).
|
|
361
|
+
*/
|
|
362
|
+
getLocalVersion() {
|
|
363
|
+
return this.loadLocalManifest()?.version;
|
|
364
|
+
}
|
|
365
|
+
/**
|
|
366
|
+
* Check CDN for available update without downloading.
|
|
367
|
+
* Returns remote version info for CLI --check display.
|
|
368
|
+
*/
|
|
369
|
+
async checkAvailable() {
|
|
370
|
+
const local = this.loadLocalManifest();
|
|
371
|
+
const manifestUrl = `${this.cdnBaseUrl}/latest/manifest.json`;
|
|
372
|
+
try {
|
|
373
|
+
const remoteJson = await this.httpGet(manifestUrl);
|
|
374
|
+
const remote = JSON.parse(remoteJson);
|
|
375
|
+
const current = local?.version;
|
|
376
|
+
if (current) {
|
|
377
|
+
const cmp = semverCompare(remote.version, current);
|
|
378
|
+
return { available: cmp !== null && cmp > 0, currentVersion: current, remoteVersion: remote.version };
|
|
379
|
+
}
|
|
380
|
+
return { available: true, remoteVersion: remote.version };
|
|
381
|
+
} catch {
|
|
382
|
+
return { available: false, currentVersion: local?.version };
|
|
383
|
+
}
|
|
384
|
+
}
|
|
385
|
+
// --- Internal ---
|
|
386
|
+
async doUpdate(targetVersion) {
|
|
387
|
+
const local = this.loadLocalManifest();
|
|
388
|
+
const manifestUrl = targetVersion ? `${this.cdnBaseUrl}/${targetVersion}/manifest.json` : `${this.cdnBaseUrl}/latest/manifest.json`;
|
|
389
|
+
let remoteJson;
|
|
390
|
+
try {
|
|
391
|
+
remoteJson = await this.httpGet(manifestUrl);
|
|
392
|
+
} catch (err) {
|
|
393
|
+
this.log.warn(`update: failed to fetch manifest from ${manifestUrl}: ${String(err)}`);
|
|
394
|
+
return false;
|
|
395
|
+
}
|
|
396
|
+
let remote;
|
|
397
|
+
try {
|
|
398
|
+
remote = JSON.parse(remoteJson);
|
|
399
|
+
} catch {
|
|
400
|
+
this.log.warn("update: failed to parse remote manifest");
|
|
401
|
+
return false;
|
|
402
|
+
}
|
|
403
|
+
if (local) {
|
|
404
|
+
const cmp = semverCompare(remote.version, local.version);
|
|
405
|
+
if (cmp === null || cmp <= 0)
|
|
406
|
+
return false;
|
|
407
|
+
}
|
|
408
|
+
const state = this.loadUpdateState();
|
|
409
|
+
if (state?.rollback_from === remote.version) {
|
|
410
|
+
this.log.info(`update: skipping ${remote.version} (previously rolled back)`);
|
|
411
|
+
return false;
|
|
412
|
+
}
|
|
413
|
+
if (this.signingEnabled) {
|
|
414
|
+
if (!SIGNING_PUBLIC_KEY) {
|
|
415
|
+
this.log.error("update: signing enabled but PRLL_DAEMON_SIGNING_PUBLIC_KEY is empty \u2014 rejecting update");
|
|
416
|
+
return false;
|
|
417
|
+
}
|
|
418
|
+
if (!verifyManifestSignature(remote, SIGNING_PUBLIC_KEY)) {
|
|
419
|
+
this.log.error(`update: signature verification failed for ${remote.version}`);
|
|
420
|
+
return false;
|
|
421
|
+
}
|
|
422
|
+
}
|
|
423
|
+
const stagingDir = path6.join(this.bundleDir, "staging");
|
|
424
|
+
this.cleanDir(stagingDir);
|
|
425
|
+
fs6.mkdirSync(stagingDir, { recursive: true });
|
|
426
|
+
for (const [filename, meta] of Object.entries(remote.files)) {
|
|
427
|
+
const filePath = path6.join(stagingDir, filename);
|
|
428
|
+
const fileUrl = `${this.cdnBaseUrl}/${remote.version}/${filename}`;
|
|
429
|
+
try {
|
|
430
|
+
await this.downloadFile(fileUrl, filePath);
|
|
431
|
+
} catch (err) {
|
|
432
|
+
this.log.error(`update: download failed for ${filename}: ${String(err)}`);
|
|
433
|
+
this.cleanDir(stagingDir);
|
|
434
|
+
return false;
|
|
435
|
+
}
|
|
436
|
+
const content = fs6.readFileSync(filePath);
|
|
437
|
+
const hash = createHash2("sha256").update(content).digest("hex");
|
|
438
|
+
if (hash !== meta.sha256) {
|
|
439
|
+
this.log.error(`update: sha256 mismatch for ${filename} (expected ${meta.sha256}, got ${hash})`);
|
|
440
|
+
this.cleanDir(stagingDir);
|
|
441
|
+
return false;
|
|
442
|
+
}
|
|
443
|
+
if (content.length !== meta.size) {
|
|
444
|
+
this.log.error(`update: size mismatch for ${filename} (expected ${meta.size}, got ${content.length})`);
|
|
445
|
+
this.cleanDir(stagingDir);
|
|
446
|
+
return false;
|
|
447
|
+
}
|
|
448
|
+
}
|
|
449
|
+
fs6.writeFileSync(path6.join(stagingDir, "manifest.json"), JSON.stringify(remote, null, 2));
|
|
450
|
+
this.atomicSwap(stagingDir, remote.version);
|
|
451
|
+
const newState = {
|
|
452
|
+
confirmed_version: local?.version ?? state?.confirmed_version,
|
|
453
|
+
previous_version: local?.version,
|
|
454
|
+
pending_version: remote.version,
|
|
455
|
+
pending_at: (/* @__PURE__ */ new Date()).toISOString(),
|
|
456
|
+
boot_count: 0
|
|
457
|
+
};
|
|
458
|
+
this.saveUpdateState(newState);
|
|
459
|
+
this.log.info(`update: ${local?.version ?? "unknown"} \u2192 ${remote.version} applied, restarting`);
|
|
460
|
+
return true;
|
|
461
|
+
}
|
|
462
|
+
atomicSwap(stagingDir, newVersion) {
|
|
463
|
+
const versionsDir = path6.join(this.bundleDir, "versions");
|
|
464
|
+
const targetDir = path6.join(versionsDir, newVersion);
|
|
465
|
+
const currentLink = path6.join(this.bundleDir, "current");
|
|
466
|
+
fs6.mkdirSync(versionsDir, { recursive: true });
|
|
467
|
+
if (fs6.existsSync(targetDir)) {
|
|
468
|
+
fs6.rmSync(targetDir, { recursive: true });
|
|
469
|
+
}
|
|
470
|
+
fs6.renameSync(stagingDir, targetDir);
|
|
471
|
+
if (process.platform === "win32") {
|
|
472
|
+
const currentDir = currentLink;
|
|
473
|
+
if (fs6.existsSync(currentDir)) {
|
|
474
|
+
for (const file of fs6.readdirSync(currentDir)) {
|
|
475
|
+
fs6.unlinkSync(path6.join(currentDir, file));
|
|
476
|
+
}
|
|
477
|
+
}
|
|
478
|
+
fs6.mkdirSync(currentDir, { recursive: true });
|
|
479
|
+
for (const file of fs6.readdirSync(targetDir)) {
|
|
480
|
+
fs6.copyFileSync(path6.join(targetDir, file), path6.join(currentDir, file));
|
|
481
|
+
}
|
|
482
|
+
} else {
|
|
483
|
+
this.swapSymlink(newVersion);
|
|
484
|
+
}
|
|
485
|
+
this.pruneOldVersions(versionsDir, newVersion);
|
|
486
|
+
}
|
|
487
|
+
swapSymlink(version) {
|
|
488
|
+
const currentLink = path6.join(this.bundleDir, "current");
|
|
489
|
+
const tmpLink = `${currentLink}.new`;
|
|
490
|
+
try {
|
|
491
|
+
fs6.unlinkSync(tmpLink);
|
|
492
|
+
} catch {
|
|
493
|
+
}
|
|
494
|
+
fs6.symlinkSync(`versions/${version}`, tmpLink);
|
|
495
|
+
fs6.renameSync(tmpLink, currentLink);
|
|
496
|
+
}
|
|
497
|
+
pruneOldVersions(versionsDir, currentVersion) {
|
|
498
|
+
const state = this.loadUpdateState();
|
|
499
|
+
const keep = /* @__PURE__ */ new Set([currentVersion]);
|
|
500
|
+
if (state?.previous_version)
|
|
501
|
+
keep.add(state.previous_version);
|
|
502
|
+
if (state?.confirmed_version)
|
|
503
|
+
keep.add(state.confirmed_version);
|
|
504
|
+
try {
|
|
505
|
+
for (const entry of fs6.readdirSync(versionsDir)) {
|
|
506
|
+
if (!keep.has(entry)) {
|
|
507
|
+
fs6.rmSync(path6.join(versionsDir, entry), { recursive: true });
|
|
508
|
+
}
|
|
509
|
+
}
|
|
510
|
+
} catch {
|
|
511
|
+
}
|
|
512
|
+
}
|
|
513
|
+
// --- Manifest & State I/O ---
|
|
514
|
+
loadLocalManifest() {
|
|
515
|
+
const currentDir = path6.join(this.bundleDir, "current");
|
|
516
|
+
const manifestPath = path6.join(currentDir, "manifest.json");
|
|
517
|
+
try {
|
|
518
|
+
return JSON.parse(fs6.readFileSync(manifestPath, "utf-8"));
|
|
519
|
+
} catch {
|
|
520
|
+
return null;
|
|
521
|
+
}
|
|
522
|
+
}
|
|
523
|
+
loadUpdateState() {
|
|
524
|
+
const statePath = path6.join(this.bundleDir, "update-state.json");
|
|
525
|
+
try {
|
|
526
|
+
return JSON.parse(fs6.readFileSync(statePath, "utf-8"));
|
|
527
|
+
} catch {
|
|
528
|
+
return null;
|
|
529
|
+
}
|
|
530
|
+
}
|
|
531
|
+
saveUpdateState(state) {
|
|
532
|
+
const statePath = path6.join(this.bundleDir, "update-state.json");
|
|
533
|
+
fs6.mkdirSync(path6.dirname(statePath), { recursive: true });
|
|
534
|
+
fs6.writeFileSync(statePath, JSON.stringify(state, null, 2));
|
|
535
|
+
}
|
|
536
|
+
// --- HTTP helpers ---
|
|
537
|
+
httpGet(url, maxRedirects = 5) {
|
|
538
|
+
return new Promise((resolve5, reject) => {
|
|
539
|
+
const mod = url.startsWith("https") ? https : http;
|
|
540
|
+
const req = mod.get(url, (res) => {
|
|
541
|
+
if (res.statusCode === 301 || res.statusCode === 302) {
|
|
542
|
+
if (res.headers.location && maxRedirects > 0) {
|
|
543
|
+
this.httpGet(res.headers.location, maxRedirects - 1).then(resolve5, reject);
|
|
544
|
+
return;
|
|
545
|
+
}
|
|
546
|
+
res.resume();
|
|
547
|
+
reject(new Error(`too many redirects or missing location for ${url}`));
|
|
548
|
+
return;
|
|
549
|
+
}
|
|
550
|
+
if (res.statusCode !== 200) {
|
|
551
|
+
res.resume();
|
|
552
|
+
reject(new Error(`HTTP ${res.statusCode} for ${url}`));
|
|
553
|
+
return;
|
|
554
|
+
}
|
|
555
|
+
const chunks = [];
|
|
556
|
+
res.on("data", (chunk) => chunks.push(chunk));
|
|
557
|
+
res.on("end", () => resolve5(Buffer.concat(chunks).toString("utf-8")));
|
|
558
|
+
res.on("error", reject);
|
|
559
|
+
});
|
|
560
|
+
req.on("error", reject);
|
|
561
|
+
req.setTimeout(3e4, () => {
|
|
562
|
+
req.destroy(new Error(`timeout fetching ${url}`));
|
|
563
|
+
});
|
|
564
|
+
});
|
|
565
|
+
}
|
|
566
|
+
downloadFile(url, dest, maxRedirects = 5) {
|
|
567
|
+
return new Promise((resolve5, reject) => {
|
|
568
|
+
const mod = url.startsWith("https") ? https : http;
|
|
569
|
+
const req = mod.get(url, (res) => {
|
|
570
|
+
if (res.statusCode === 301 || res.statusCode === 302) {
|
|
571
|
+
if (res.headers.location && maxRedirects > 0) {
|
|
572
|
+
this.downloadFile(res.headers.location, dest, maxRedirects - 1).then(resolve5, reject);
|
|
573
|
+
return;
|
|
574
|
+
}
|
|
575
|
+
res.resume();
|
|
576
|
+
reject(new Error(`too many redirects or missing location for ${url}`));
|
|
577
|
+
return;
|
|
578
|
+
}
|
|
579
|
+
if (res.statusCode !== 200) {
|
|
580
|
+
res.resume();
|
|
581
|
+
reject(new Error(`HTTP ${res.statusCode} for ${url}`));
|
|
582
|
+
return;
|
|
583
|
+
}
|
|
584
|
+
const file = fs6.createWriteStream(dest);
|
|
585
|
+
res.pipe(file);
|
|
586
|
+
file.on("finish", () => {
|
|
587
|
+
file.close();
|
|
588
|
+
resolve5();
|
|
589
|
+
});
|
|
590
|
+
file.on("error", (err) => {
|
|
591
|
+
fs6.unlinkSync(dest);
|
|
592
|
+
reject(err);
|
|
593
|
+
});
|
|
594
|
+
});
|
|
595
|
+
req.on("error", reject);
|
|
596
|
+
req.setTimeout(12e4, () => {
|
|
597
|
+
req.destroy(new Error(`timeout downloading ${url}`));
|
|
598
|
+
});
|
|
599
|
+
});
|
|
600
|
+
}
|
|
601
|
+
cleanDir(dir) {
|
|
602
|
+
try {
|
|
603
|
+
fs6.rmSync(dir, { recursive: true });
|
|
604
|
+
} catch {
|
|
605
|
+
}
|
|
606
|
+
}
|
|
607
|
+
};
|
|
608
|
+
}
|
|
609
|
+
});
|
|
610
|
+
|
|
611
|
+
// ts/agent-core/dist/provider-config.js
|
|
612
|
+
function clearAllProviderCreds(env) {
|
|
613
|
+
delete env.ANTHROPIC_AUTH_TOKEN;
|
|
614
|
+
delete env.ANTHROPIC_BASE_URL;
|
|
615
|
+
delete env.ANTHROPIC_API_KEY;
|
|
616
|
+
delete env.OPENAI_API_KEY;
|
|
617
|
+
delete env.OPENAI_BASE_URL;
|
|
618
|
+
delete env.PRLL_CLAUDE_ALLOW_API_KEY;
|
|
619
|
+
}
|
|
2
620
|
|
|
3
621
|
// ts/agent-core/dist/logger.js
|
|
4
622
|
function createLogger(prefix) {
|
|
5
623
|
return {
|
|
6
624
|
info: (msg) => console.log(`${(/* @__PURE__ */ new Date()).toISOString()} [${prefix}] ${msg}`),
|
|
7
625
|
warn: (msg) => console.warn(`${(/* @__PURE__ */ new Date()).toISOString()} [${prefix}] ${msg}`),
|
|
8
|
-
error: (msg) => console.error(`${(/* @__PURE__ */ new Date()).toISOString()} [${prefix}] ${msg}`)
|
|
626
|
+
error: (msg) => console.error(`${(/* @__PURE__ */ new Date()).toISOString()} [${prefix}] ${msg}`),
|
|
627
|
+
child: (sub) => createLogger(`${prefix}:${sub}`)
|
|
9
628
|
};
|
|
10
629
|
}
|
|
11
630
|
|
|
@@ -39,6 +658,7 @@ var ENDPOINTS = {
|
|
|
39
658
|
ORG_MEMBER: (orgId, userId) => `${API_BASE}/orgs/${orgId}/members/${userId}`,
|
|
40
659
|
ORG_MEMBER_CHATS: (orgId, memberId) => `${API_BASE}/orgs/${orgId}/members/${memberId}/chats`,
|
|
41
660
|
ORG_MEMBER_TASKS: (orgId, memberId) => `${API_BASE}/orgs/${orgId}/members/${memberId}/tasks`,
|
|
661
|
+
REF_SEARCH: (orgId) => `${API_BASE}/orgs/${orgId}/refs/search`,
|
|
42
662
|
// Direct messages (org-scoped, atomic find-or-create + send)
|
|
43
663
|
DM: (orgId) => `${API_BASE}/orgs/${orgId}/dm`,
|
|
44
664
|
// Onboarding
|
|
@@ -82,6 +702,7 @@ var ENDPOINTS = {
|
|
|
82
702
|
AGENT_ACTIVITY: (orgId, agentId) => `${API_BASE}/orgs/${orgId}/agents/${agentId}/activity`,
|
|
83
703
|
AGENT_MONITOR: (orgId, agentId) => `${API_BASE}/orgs/${orgId}/agents/${agentId}/monitor`,
|
|
84
704
|
AGENT_ME: (orgId) => `${API_BASE}/orgs/${orgId}/agents/me`,
|
|
705
|
+
AGENT_NEW_SESSION: (orgId, agentId) => `${API_BASE}/orgs/${orgId}/agents/${agentId}/new-session`,
|
|
85
706
|
AGENT_SESSIONS: (orgId, agentId) => `${API_BASE}/orgs/${orgId}/agents/${agentId}/sessions`,
|
|
86
707
|
AGENT_SESSION: (orgId, agentId, sessionId) => `${API_BASE}/orgs/${orgId}/agents/${agentId}/sessions/${sessionId}`,
|
|
87
708
|
AGENT_SESSION_STEPS: (orgId, agentId, sessionId) => `${API_BASE}/orgs/${orgId}/agents/${agentId}/sessions/${sessionId}/steps`,
|
|
@@ -120,6 +741,8 @@ var ENDPOINTS = {
|
|
|
120
741
|
MACHINE_KEY: (orgId, machineId, keyId) => `${API_BASE}/orgs/${orgId}/machines/${machineId}/keys/${keyId}`,
|
|
121
742
|
MACHINE_RUNTIME_AUTH_SESSIONS: (orgId, machineId) => `${API_BASE}/orgs/${orgId}/machines/${machineId}/runtime-auth-sessions`,
|
|
122
743
|
MACHINE_RUNTIME_AUTH_SESSION_COMPLETE: (orgId, machineId, sessionId) => `${API_BASE}/orgs/${orgId}/machines/${machineId}/runtime-auth-sessions/${sessionId}/complete`,
|
|
744
|
+
MACHINE_REQUEST_UPDATE: (orgId, machineId) => `${API_BASE}/orgs/${orgId}/machines/${machineId}/request-update`,
|
|
745
|
+
MACHINE_BROWSE: (orgId, machineId) => `${API_BASE}/orgs/${orgId}/machines/${machineId}/browse`,
|
|
123
746
|
// Machine self-control-plane (mck_-scoped). The bearer token implicitly
|
|
124
747
|
// identifies the Machine, so there is no `:mid` URL parameter — these are
|
|
125
748
|
// "self" routes called by the daemon for its own host.
|
|
@@ -129,6 +752,7 @@ var ENDPOINTS = {
|
|
|
129
752
|
MACHINES_ME_AGENT_LAUNCH_CREDENTIAL: (agentId) => `${API_BASE}/machines/me/agents/${agentId}/launch-credential`,
|
|
130
753
|
MACHINES_ME_AGENT_WORKSPACE_STATE: (agentId) => `${API_BASE}/machines/me/agents/${agentId}/workspace-state`,
|
|
131
754
|
MACHINES_ME_WS_TICKET: `${API_BASE}/machines/me/ws/ticket`,
|
|
755
|
+
MACHINES_ME_BROWSE_RESPONSE: (requestId) => `${API_BASE}/machines/me/browse-response/${requestId}`,
|
|
132
756
|
// Tasks (org-scoped)
|
|
133
757
|
TASKS: (orgId) => `${API_BASE}/orgs/${orgId}/tasks`,
|
|
134
758
|
TASK: (orgId, taskId) => `${API_BASE}/orgs/${orgId}/tasks/${taskId}`,
|
|
@@ -163,6 +787,12 @@ var ENDPOINTS = {
|
|
|
163
787
|
INVITATION_ACCEPT: (id) => `${API_BASE}/invitations/${id}/accept`,
|
|
164
788
|
INVITATION_DECLINE: (id) => `${API_BASE}/invitations/${id}/decline`,
|
|
165
789
|
INVITATION_BY_TOKEN: (token) => `${API_BASE}/invitations/by-token/${token}`,
|
|
790
|
+
// Org-level shareable invite link
|
|
791
|
+
ORG_INVITE_LINK: (orgId) => `${API_BASE}/orgs/${orgId}/invite-link`,
|
|
792
|
+
ORG_INVITE_LINK_REGENERATE: (orgId) => `${API_BASE}/orgs/${orgId}/invite-link/regenerate`,
|
|
793
|
+
ORG_INVITE_LINK_JOIN_REQUESTS: (orgId) => `${API_BASE}/orgs/${orgId}/invite-link/join-requests`,
|
|
794
|
+
ORG_INVITE_LINK_JOIN_REQUEST_DECIDE: (orgId, jrId) => `${API_BASE}/orgs/${orgId}/invite-link/join-requests/${jrId}/decide`,
|
|
795
|
+
INVITE_LINK_JOIN: `${API_BASE}/invite-link/join`,
|
|
166
796
|
// Wikis (org-scoped, served by wiki-service)
|
|
167
797
|
WIKIS: (orgId) => `${WIKI_BASE}/orgs/${orgId}/wikis`,
|
|
168
798
|
WIKI: (orgId, wikiId) => `${WIKI_BASE}/orgs/${orgId}/wikis/${wikiId}`,
|
|
@@ -282,6 +912,8 @@ var WS_EVENTS = {
|
|
|
282
912
|
INVITATION_ACCEPTED: "invitation.accepted",
|
|
283
913
|
INVITATION_DECLINED: "invitation.declined",
|
|
284
914
|
INVITATION_REVOKED: "invitation.revoked",
|
|
915
|
+
ORG_JOIN_REQUEST_NEW: "org.join_request.new",
|
|
916
|
+
ORG_INVITE_LINK_JOINED: "org.invite_link.joined",
|
|
285
917
|
AGENT_CONFIG_UPDATE: "agent_config.update",
|
|
286
918
|
PRESENCE_UPDATE: "presence.update",
|
|
287
919
|
WIKI_CHANGESET_CREATED: "wiki.changeset.created",
|
|
@@ -308,7 +940,11 @@ var WS_EVENTS = {
|
|
|
308
940
|
MACHINE_AGENT_ATTACHED: "machine.agent.attached",
|
|
309
941
|
MACHINE_AGENT_DETACHED: "machine.agent.detached",
|
|
310
942
|
MACHINE_STOP: "machine.stop",
|
|
311
|
-
MACHINE_WORKSPACE_SETUP_REQUESTED: "machine.workspace.setup.requested"
|
|
943
|
+
MACHINE_WORKSPACE_SETUP_REQUESTED: "machine.workspace.setup.requested",
|
|
944
|
+
MACHINE_FILESYSTEM_BROWSE: "machine.filesystem.browse",
|
|
945
|
+
MACHINE_UPDATE: "machine.update",
|
|
946
|
+
MACHINE_CONFIG_UPDATED: "machine.config.updated",
|
|
947
|
+
AGENT_NEW_SESSION: "agent.new_session"
|
|
312
948
|
};
|
|
313
949
|
|
|
314
950
|
// ts/sdk/dist/client.js
|
|
@@ -391,10 +1027,10 @@ var ParallClient = class _ParallClient {
|
|
|
391
1027
|
* REFRESH_THRESHOLD_S, refresh it **before** sending the request.
|
|
392
1028
|
* No-op when the token is still fresh, missing, or un-parseable.
|
|
393
1029
|
*/
|
|
394
|
-
async ensureFreshToken(
|
|
1030
|
+
async ensureFreshToken(path8) {
|
|
395
1031
|
if (!this.token || !this.getRefreshToken)
|
|
396
1032
|
return;
|
|
397
|
-
const pathSuffix =
|
|
1033
|
+
const pathSuffix = path8.replace(/^\/api\/v1/, "");
|
|
398
1034
|
if (_ParallClient.AUTH_PATHS.has(pathSuffix))
|
|
399
1035
|
return;
|
|
400
1036
|
const exp = _ParallClient.decodeJwtExp(this.token);
|
|
@@ -426,11 +1062,11 @@ var ParallClient = class _ParallClient {
|
|
|
426
1062
|
this.refreshPromise = null;
|
|
427
1063
|
}
|
|
428
1064
|
}
|
|
429
|
-
async request(method,
|
|
1065
|
+
async request(method, path8, body, query, retried = false, opts) {
|
|
430
1066
|
if (!retried) {
|
|
431
|
-
await this.ensureFreshToken(
|
|
1067
|
+
await this.ensureFreshToken(path8);
|
|
432
1068
|
}
|
|
433
|
-
let url = `${this.baseUrl}${
|
|
1069
|
+
let url = `${this.baseUrl}${path8}`;
|
|
434
1070
|
if (query) {
|
|
435
1071
|
const params = new URLSearchParams();
|
|
436
1072
|
for (const [key, value] of Object.entries(query)) {
|
|
@@ -455,12 +1091,12 @@ var ParallClient = class _ParallClient {
|
|
|
455
1091
|
throw _ParallClient.normalizeFetchError(err);
|
|
456
1092
|
}
|
|
457
1093
|
if (res.status === 401) {
|
|
458
|
-
const pathSuffix =
|
|
1094
|
+
const pathSuffix = path8.replace(/^\/api\/v1/, "");
|
|
459
1095
|
const isAuthPath = _ParallClient.AUTH_PATHS.has(pathSuffix);
|
|
460
1096
|
if (!retried && !isAuthPath && this.getRefreshToken) {
|
|
461
1097
|
const refreshed = await this.tryRefresh();
|
|
462
1098
|
if (refreshed) {
|
|
463
|
-
return this.request(method,
|
|
1099
|
+
return this.request(method, path8, body, query, true, opts);
|
|
464
1100
|
}
|
|
465
1101
|
}
|
|
466
1102
|
if (this.onTokenExpired && !isAuthPath) {
|
|
@@ -497,15 +1133,15 @@ var ParallClient = class _ParallClient {
|
|
|
497
1133
|
* hit the 100 MiB cap, so a longer 5-minute timeout is used so a
|
|
498
1134
|
* 50 MiB blob on a slow connection doesn't get chopped at 15 s.
|
|
499
1135
|
*/
|
|
500
|
-
async multipartRequest(method,
|
|
1136
|
+
async multipartRequest(method, path8, body, retried = false) {
|
|
501
1137
|
if (!retried) {
|
|
502
|
-
await this.ensureFreshToken(
|
|
1138
|
+
await this.ensureFreshToken(path8);
|
|
503
1139
|
}
|
|
504
1140
|
const { "Content-Type": _drop, ...headers } = this.buildHeaders();
|
|
505
1141
|
void _drop;
|
|
506
1142
|
let res;
|
|
507
1143
|
try {
|
|
508
|
-
res = await fetch(`${this.baseUrl}${
|
|
1144
|
+
res = await fetch(`${this.baseUrl}${path8}`, {
|
|
509
1145
|
method,
|
|
510
1146
|
headers,
|
|
511
1147
|
body,
|
|
@@ -515,12 +1151,12 @@ var ParallClient = class _ParallClient {
|
|
|
515
1151
|
throw _ParallClient.normalizeFetchError(err);
|
|
516
1152
|
}
|
|
517
1153
|
if (res.status === 401) {
|
|
518
|
-
const pathSuffix =
|
|
1154
|
+
const pathSuffix = path8.replace(/^\/api\/v1/, "");
|
|
519
1155
|
const isAuthPath = _ParallClient.AUTH_PATHS.has(pathSuffix);
|
|
520
1156
|
if (!retried && !isAuthPath && this.getRefreshToken) {
|
|
521
1157
|
const refreshed = await this.tryRefresh();
|
|
522
1158
|
if (refreshed) {
|
|
523
|
-
return this.multipartRequest(method,
|
|
1159
|
+
return this.multipartRequest(method, path8, body, true);
|
|
524
1160
|
}
|
|
525
1161
|
}
|
|
526
1162
|
if (this.onTokenExpired && !isAuthPath) {
|
|
@@ -637,6 +1273,9 @@ var ParallClient = class _ParallClient {
|
|
|
637
1273
|
const res = await this.request("GET", ENDPOINTS.ORG_MEMBERS_ONLINE(orgId));
|
|
638
1274
|
return res.user_ids ?? [];
|
|
639
1275
|
}
|
|
1276
|
+
async searchRefs(orgId, params) {
|
|
1277
|
+
return this.request("GET", ENDPOINTS.REF_SEARCH(orgId), void 0, params);
|
|
1278
|
+
}
|
|
640
1279
|
async removeOrgMember(orgId, userId) {
|
|
641
1280
|
return this.request("DELETE", ENDPOINTS.ORG_MEMBER(orgId, userId));
|
|
642
1281
|
}
|
|
@@ -690,6 +1329,34 @@ var ParallClient = class _ParallClient {
|
|
|
690
1329
|
async getInvitationByToken(token) {
|
|
691
1330
|
return this.request("GET", ENDPOINTS.INVITATION_BY_TOKEN(token));
|
|
692
1331
|
}
|
|
1332
|
+
// ---- Org-level shareable invite link ----
|
|
1333
|
+
/** Fetch the org's current invite link. Auto-creates on first call
|
|
1334
|
+
* so the settings UI never sees an empty state. */
|
|
1335
|
+
async getOrgInviteLink(orgId) {
|
|
1336
|
+
return this.request("GET", ENDPOINTS.ORG_INVITE_LINK(orgId));
|
|
1337
|
+
}
|
|
1338
|
+
/** Rotate the token in place. The old token stops resolving immediately. */
|
|
1339
|
+
async regenerateOrgInviteLink(orgId) {
|
|
1340
|
+
return this.request("POST", ENDPOINTS.ORG_INVITE_LINK_REGENERATE(orgId));
|
|
1341
|
+
}
|
|
1342
|
+
/** Toggle the require-approval flag on the org's invite link. */
|
|
1343
|
+
async updateOrgInviteLink(orgId, data) {
|
|
1344
|
+
return this.request("PATCH", ENDPOINTS.ORG_INVITE_LINK(orgId), data);
|
|
1345
|
+
}
|
|
1346
|
+
/** Redeem an invite-link token. Caller must be authenticated; the
|
|
1347
|
+
* token is the lookup key so this endpoint is NOT org-scoped. */
|
|
1348
|
+
async joinByInviteLink(token) {
|
|
1349
|
+
return this.request("POST", ENDPOINTS.INVITE_LINK_JOIN, { token });
|
|
1350
|
+
}
|
|
1351
|
+
/** Admin: list pending invite-link join requests for an org. */
|
|
1352
|
+
async listOrgJoinRequests(orgId) {
|
|
1353
|
+
const res = await this.request("GET", ENDPOINTS.ORG_INVITE_LINK_JOIN_REQUESTS(orgId));
|
|
1354
|
+
return res.data;
|
|
1355
|
+
}
|
|
1356
|
+
/** Admin: approve or reject a pending invite-link join request. */
|
|
1357
|
+
async decideOrgJoinRequest(orgId, jrId, decision) {
|
|
1358
|
+
return this.request("POST", ENDPOINTS.ORG_INVITE_LINK_JOIN_REQUEST_DECIDE(orgId, jrId), { decision });
|
|
1359
|
+
}
|
|
693
1360
|
// ---- Direct Messages (org-scoped) ----
|
|
694
1361
|
async sendDirectMessage(orgId, req) {
|
|
695
1362
|
return this.request("POST", ENDPOINTS.DM(orgId), req);
|
|
@@ -854,6 +1521,9 @@ var ParallClient = class _ParallClient {
|
|
|
854
1521
|
return this.request("GET", ENDPOINTS.AGENT_ME(orgId));
|
|
855
1522
|
}
|
|
856
1523
|
// ---- Agent Sessions (org-scoped) ----
|
|
1524
|
+
async requestNewAgentSession(orgId, agentId) {
|
|
1525
|
+
return this.request("POST", ENDPOINTS.AGENT_NEW_SESSION(orgId, agentId));
|
|
1526
|
+
}
|
|
857
1527
|
async createAgentSession(orgId, agentId, req) {
|
|
858
1528
|
return this.request("POST", ENDPOINTS.AGENT_SESSIONS(orgId, agentId), req);
|
|
859
1529
|
}
|
|
@@ -956,19 +1626,20 @@ var ParallClient = class _ParallClient {
|
|
|
956
1626
|
}
|
|
957
1627
|
// ---- Daemon-mode Machine management (org-scoped, user auth) ----
|
|
958
1628
|
/**
|
|
959
|
-
* Create a new daemon-mode Machine. Returns the Machine row +
|
|
960
|
-
* mck_ token.
|
|
1629
|
+
* Create a new self-hosted daemon-mode Machine. Returns the Machine row +
|
|
1630
|
+
* one-shot mck_ token.
|
|
961
1631
|
*/
|
|
962
1632
|
async createMachine(orgId, opts) {
|
|
963
1633
|
return this.request("POST", ENDPOINTS.MACHINES(orgId), opts);
|
|
964
1634
|
}
|
|
965
1635
|
/**
|
|
966
|
-
* Returns
|
|
967
|
-
* "Workspaces" UI list. Legacy 1:1 machines are
|
|
1636
|
+
* Returns local daemon_mode=true machines (non-terminated). Backs the
|
|
1637
|
+
* "Workspaces" UI list. Legacy 1:1 and retired hosted daemon machines are
|
|
1638
|
+
* excluded.
|
|
968
1639
|
*/
|
|
969
1640
|
async getDaemonMachines(orgId) {
|
|
970
1641
|
const all = await this.getMachines(orgId);
|
|
971
|
-
return all.filter((m) => m.daemon_mode && m.status !== "terminated");
|
|
1642
|
+
return all.filter((m) => m.daemon_mode && m.compute_mode === "local" && m.status !== "terminated");
|
|
972
1643
|
}
|
|
973
1644
|
/** Attach an agent to a daemon-mode Machine. */
|
|
974
1645
|
async attachAgent(orgId, machineId, agentId, opts) {
|
|
@@ -988,8 +1659,8 @@ var ParallClient = class _ParallClient {
|
|
|
988
1659
|
async retryAgentWorkspaceSetup(orgId, machineId, agentId) {
|
|
989
1660
|
return this.request("POST", ENDPOINTS.MACHINE_AGENT_WORKSPACE_SETUP(orgId, machineId, agentId));
|
|
990
1661
|
}
|
|
991
|
-
async patchMachineLLMSource(orgId, machineId,
|
|
992
|
-
return this.request("PATCH", ENDPOINTS.MACHINE_LLM_SOURCE(orgId, machineId), { llm_source:
|
|
1662
|
+
async patchMachineLLMSource(orgId, machineId, llmSource) {
|
|
1663
|
+
return this.request("PATCH", ENDPOINTS.MACHINE_LLM_SOURCE(orgId, machineId), { llm_source: llmSource });
|
|
993
1664
|
}
|
|
994
1665
|
/** Get machine-level runtime auth state. */
|
|
995
1666
|
async getMachineRuntimeAuth(orgId, machineId) {
|
|
@@ -1030,8 +1701,9 @@ var ParallClient = class _ParallClient {
|
|
|
1030
1701
|
* daemon should call this on a fixed cadence (e.g. every 30s) so an
|
|
1031
1702
|
* external observer can detect a wedged supervisor.
|
|
1032
1703
|
*/
|
|
1033
|
-
async postMachineHeartbeat() {
|
|
1034
|
-
|
|
1704
|
+
async postMachineHeartbeat(daemonVersion) {
|
|
1705
|
+
const body = daemonVersion ? { daemon_version: daemonVersion } : void 0;
|
|
1706
|
+
return this.request("POST", ENDPOINTS.MACHINES_ME_HEALTH, body);
|
|
1035
1707
|
}
|
|
1036
1708
|
async reportAgentWorkspaceState(agentId, state) {
|
|
1037
1709
|
const res = await this.request("PUT", ENDPOINTS.MACHINES_ME_AGENT_WORKSPACE_STATE(agentId), state);
|
|
@@ -1053,9 +1725,19 @@ var ParallClient = class _ParallClient {
|
|
|
1053
1725
|
async getMachineWsTicket() {
|
|
1054
1726
|
return this.request("POST", ENDPOINTS.MACHINES_ME_WS_TICKET);
|
|
1055
1727
|
}
|
|
1728
|
+
async postBrowseResponse(requestId, response) {
|
|
1729
|
+
return this.request("POST", ENDPOINTS.MACHINES_ME_BROWSE_RESPONSE(requestId), response);
|
|
1730
|
+
}
|
|
1056
1731
|
async resizeMachine(orgId, machineId, spec) {
|
|
1057
1732
|
return this.request("PATCH", ENDPOINTS.MACHINE_SPEC(orgId, machineId), spec);
|
|
1058
1733
|
}
|
|
1734
|
+
/** Signal a local daemon-mode Machine to check for and apply an update. */
|
|
1735
|
+
async requestMachineUpdate(orgId, machineId, mandatory = false) {
|
|
1736
|
+
await this.request("POST", ENDPOINTS.MACHINE_REQUEST_UPDATE(orgId, machineId), { mandatory });
|
|
1737
|
+
}
|
|
1738
|
+
async browseMachineFilesystem(orgId, machineId, path8) {
|
|
1739
|
+
return this.request("POST", ENDPOINTS.MACHINE_BROWSE(orgId, machineId), { path: path8 }, void 0, false, { timeoutMs: 15e3 });
|
|
1740
|
+
}
|
|
1059
1741
|
/** Create a new machine key. Returns the raw key string (shown once) + metadata. */
|
|
1060
1742
|
async createMachineKey(orgId, machineId, name) {
|
|
1061
1743
|
return this.request("POST", ENDPOINTS.MACHINE_KEYS(orgId, machineId), name ? { name } : void 0);
|
|
@@ -1404,8 +2086,8 @@ var ParallClient = class _ParallClient {
|
|
|
1404
2086
|
async deleteWikiPathScope(orgId, wikiId, scopeId) {
|
|
1405
2087
|
await this.request("DELETE", ENDPOINTS.WIKI_PATH_SCOPE(orgId, wikiId, scopeId));
|
|
1406
2088
|
}
|
|
1407
|
-
async getWikiAccessStatus(orgId, wikiId,
|
|
1408
|
-
return this.request("GET", ENDPOINTS.WIKI_ACCESS_STATUS(orgId, wikiId), void 0,
|
|
2089
|
+
async getWikiAccessStatus(orgId, wikiId, path8) {
|
|
2090
|
+
return this.request("GET", ENDPOINTS.WIKI_ACCESS_STATUS(orgId, wikiId), void 0, path8 ? { path: path8 } : void 0);
|
|
1409
2091
|
}
|
|
1410
2092
|
async createWikiAccessRequest(orgId, wikiId, data) {
|
|
1411
2093
|
await this.request("POST", ENDPOINTS.WIKI_ACCESS_REQUESTS(orgId, wikiId), data);
|
|
@@ -1414,11 +2096,11 @@ var ParallClient = class _ParallClient {
|
|
|
1414
2096
|
async getWikiCommits(orgId, wikiId, params) {
|
|
1415
2097
|
return this.request("GET", ENDPOINTS.WIKI_COMMITS(orgId, wikiId), void 0, params);
|
|
1416
2098
|
}
|
|
1417
|
-
async getWikiFileCommits(orgId, wikiId,
|
|
1418
|
-
return this.request("GET", ENDPOINTS.WIKI_FILE_COMMITS(orgId, wikiId), void 0, { path:
|
|
2099
|
+
async getWikiFileCommits(orgId, wikiId, path8, params) {
|
|
2100
|
+
return this.request("GET", ENDPOINTS.WIKI_FILE_COMMITS(orgId, wikiId), void 0, { path: path8, ...params });
|
|
1419
2101
|
}
|
|
1420
|
-
async getWikiBlame(orgId, wikiId,
|
|
1421
|
-
return this.request("GET", ENDPOINTS.WIKI_BLAME(orgId, wikiId), void 0, { path:
|
|
2102
|
+
async getWikiBlame(orgId, wikiId, path8, ref) {
|
|
2103
|
+
return this.request("GET", ENDPOINTS.WIKI_BLAME(orgId, wikiId), void 0, { path: path8, ref });
|
|
1422
2104
|
}
|
|
1423
2105
|
// ---- Wiki Operations (audit log) ----
|
|
1424
2106
|
async getWikiOperations(orgId, wikiId, params) {
|
|
@@ -1902,223 +2584,174 @@ var ParallWs = class {
|
|
|
1902
2584
|
}
|
|
1903
2585
|
};
|
|
1904
2586
|
|
|
1905
|
-
// ts/daemon/dist/
|
|
1906
|
-
|
|
1907
|
-
|
|
1908
|
-
|
|
1909
|
-
|
|
1910
|
-
|
|
1911
|
-
|
|
1912
|
-
|
|
1913
|
-
|
|
1914
|
-
|
|
1915
|
-
|
|
1916
|
-
|
|
1917
|
-
|
|
1918
|
-
|
|
1919
|
-
|
|
1920
|
-
|
|
1921
|
-
|
|
1922
|
-
|
|
1923
|
-
|
|
1924
|
-
|
|
1925
|
-
|
|
1926
|
-
|
|
1927
|
-
|
|
1928
|
-
|
|
1929
|
-
|
|
1930
|
-
|
|
1931
|
-
|
|
1932
|
-
|
|
1933
|
-
|
|
1934
|
-
|
|
1935
|
-
|
|
1936
|
-
|
|
1937
|
-
|
|
1938
|
-
|
|
1939
|
-
|
|
2587
|
+
// ts/daemon/dist/index.js
|
|
2588
|
+
init_config();
|
|
2589
|
+
|
|
2590
|
+
// ts/daemon/dist/supervisor.js
|
|
2591
|
+
import { spawn as spawn2 } from "node:child_process";
|
|
2592
|
+
import * as fs5 from "node:fs";
|
|
2593
|
+
import * as path5 from "node:path";
|
|
2594
|
+
|
|
2595
|
+
// ts/daemon/dist/filesystem.js
|
|
2596
|
+
import * as fs2 from "fs";
|
|
2597
|
+
import * as path2 from "path";
|
|
2598
|
+
import * as os2 from "os";
|
|
2599
|
+
var MAX_ENTRIES = 200;
|
|
2600
|
+
var SYSTEM_DIR_PREFIXES = [
|
|
2601
|
+
"/Applications",
|
|
2602
|
+
"/bin",
|
|
2603
|
+
"/boot",
|
|
2604
|
+
"/dev",
|
|
2605
|
+
"/etc",
|
|
2606
|
+
"/Library",
|
|
2607
|
+
"/private",
|
|
2608
|
+
"/proc",
|
|
2609
|
+
"/root",
|
|
2610
|
+
"/run",
|
|
2611
|
+
"/sbin",
|
|
2612
|
+
"/System",
|
|
2613
|
+
"/sys",
|
|
2614
|
+
"/usr",
|
|
2615
|
+
"/var"
|
|
2616
|
+
];
|
|
2617
|
+
var CREDENTIAL_DIR_NAMES = /* @__PURE__ */ new Set([
|
|
2618
|
+
".aws",
|
|
2619
|
+
".azure",
|
|
2620
|
+
".claude",
|
|
2621
|
+
".codex",
|
|
2622
|
+
".config",
|
|
2623
|
+
".docker",
|
|
2624
|
+
".gnupg",
|
|
2625
|
+
".kube",
|
|
2626
|
+
".npm",
|
|
2627
|
+
".ssh",
|
|
2628
|
+
".parall-agent",
|
|
2629
|
+
".parall-daemon"
|
|
2630
|
+
]);
|
|
2631
|
+
function browseDenyReason(value) {
|
|
2632
|
+
const normalized = path2.resolve(value).split(path2.sep).join("/");
|
|
2633
|
+
if (normalized === "/")
|
|
2634
|
+
return "";
|
|
2635
|
+
for (const prefix of SYSTEM_DIR_PREFIXES) {
|
|
2636
|
+
if (normalized === prefix || normalized.startsWith(`${prefix}/`)) {
|
|
2637
|
+
return "a system directory";
|
|
2638
|
+
}
|
|
1940
2639
|
}
|
|
1941
|
-
|
|
1942
|
-
|
|
1943
|
-
|
|
1944
|
-
|
|
1945
|
-
|
|
2640
|
+
const parts = normalized.split("/").filter(Boolean);
|
|
2641
|
+
for (const part of parts) {
|
|
2642
|
+
if (CREDENTIAL_DIR_NAMES.has(part)) {
|
|
2643
|
+
return "a credential or application state directory";
|
|
2644
|
+
}
|
|
1946
2645
|
}
|
|
2646
|
+
return "";
|
|
1947
2647
|
}
|
|
1948
|
-
function
|
|
1949
|
-
|
|
1950
|
-
|
|
1951
|
-
|
|
1952
|
-
|
|
1953
|
-
|
|
1954
|
-
|
|
1955
|
-
|
|
1956
|
-
|
|
1957
|
-
apiKey = file.api_key.trim();
|
|
2648
|
+
function syntheticRoots() {
|
|
2649
|
+
const roots = [];
|
|
2650
|
+
const platform2 = os2.platform();
|
|
2651
|
+
const candidates = platform2 === "darwin" ? ["/Users", os2.homedir()] : ["/home", os2.homedir()];
|
|
2652
|
+
for (const dir of [...new Set(candidates)]) {
|
|
2653
|
+
try {
|
|
2654
|
+
fs2.accessSync(dir, fs2.constants.R_OK);
|
|
2655
|
+
roots.push({ name: dir, type: "dir" });
|
|
2656
|
+
} catch {
|
|
1958
2657
|
}
|
|
1959
2658
|
}
|
|
1960
|
-
|
|
1961
|
-
throw new Error("Missing required env var: PRLL_API_URL");
|
|
1962
|
-
if (!apiKey)
|
|
1963
|
-
throw new Error("Missing required env var: PRLL_API_KEY");
|
|
1964
|
-
if (!apiKey.startsWith("mck_")) {
|
|
1965
|
-
throw new Error(`PRLL_API_KEY does not look like a Machine bearer (expected prefix "mck_"). Daemon mode requires a machine-scoped key issued via POST /machines/{id}/keys.`);
|
|
1966
|
-
}
|
|
1967
|
-
const rootClaudeHome = resolvePath(env.PRLL_CLAUDE_HOME?.trim() || env.HOME || os.homedir());
|
|
1968
|
-
const rootStateDir = resolvePath(env.PRLL_CLAUDE_STATE_DIR?.trim() || path.join(rootClaudeHome, ".parall-agent"));
|
|
1969
|
-
return {
|
|
1970
|
-
apiUrl,
|
|
1971
|
-
apiKey,
|
|
1972
|
-
agentBin: env.PRLL_CLAUDE_AGENT_BIN?.trim() || "parall-claude-agent",
|
|
1973
|
-
rootStateDir,
|
|
1974
|
-
rootClaudeHome,
|
|
1975
|
-
wsUrl: env.PRLL_WS_URL?.trim() || void 0,
|
|
1976
|
-
swimlaneName: env.PRLL_SWIMLANE_NAME?.trim() || void 0,
|
|
1977
|
-
pollIntervalMs: parseMs(env.PRLL_DAEMON_POLL_INTERVAL_MS, 3e4),
|
|
1978
|
-
heartbeatIntervalMs: parseMs(env.PRLL_DAEMON_HEARTBEAT_INTERVAL_MS, 3e4),
|
|
1979
|
-
restartBackoffMs: parseMs(env.PRLL_DAEMON_RESTART_BACKOFF_MS, 5e3),
|
|
1980
|
-
restartBackoffMaxMs: parseMs(env.PRLL_DAEMON_RESTART_BACKOFF_MAX_MS, 5 * 6e4),
|
|
1981
|
-
bootstrapBackoffMs: parseMsAllowZero(env.PRLL_DAEMON_BOOTSTRAP_BACKOFF_MS, 2e3),
|
|
1982
|
-
bootstrapBackoffMaxMs: parseMs(env.PRLL_DAEMON_BOOTSTRAP_BACKOFF_MAX_MS, 6e4),
|
|
1983
|
-
supervisorRestartBackoffMs: parseMsAllowZero(env.PRLL_DAEMON_SUPERVISOR_RESTART_BACKOFF_MS, 5e3),
|
|
1984
|
-
supervisorRestartBackoffMaxMs: parseMs(env.PRLL_DAEMON_SUPERVISOR_RESTART_BACKOFF_MAX_MS, 5 * 6e4)
|
|
1985
|
-
};
|
|
2659
|
+
return roots;
|
|
1986
2660
|
}
|
|
1987
|
-
function
|
|
1988
|
-
|
|
1989
|
-
|
|
2661
|
+
async function listDirectory(dirPath) {
|
|
2662
|
+
const resolved = path2.resolve(dirPath);
|
|
2663
|
+
const normalized = resolved.split(path2.sep).join("/");
|
|
2664
|
+
if (normalized === "/") {
|
|
2665
|
+
return { entries: syntheticRoots() };
|
|
1990
2666
|
}
|
|
1991
|
-
|
|
1992
|
-
|
|
1993
|
-
|
|
1994
|
-
|
|
1995
|
-
|
|
1996
|
-
|
|
1997
|
-
|
|
1998
|
-
}
|
|
1999
|
-
|
|
2000
|
-
|
|
2001
|
-
}
|
|
2002
|
-
|
|
2003
|
-
|
|
2004
|
-
|
|
2005
|
-
|
|
2006
|
-
|
|
2007
|
-
}
|
|
2008
|
-
|
|
2009
|
-
|
|
2010
|
-
|
|
2011
|
-
|
|
2012
|
-
|
|
2013
|
-
|
|
2014
|
-
|
|
2667
|
+
const deny = browseDenyReason(normalized);
|
|
2668
|
+
if (deny) {
|
|
2669
|
+
return { entries: [], error: `Access denied: ${deny}` };
|
|
2670
|
+
}
|
|
2671
|
+
let realPath;
|
|
2672
|
+
try {
|
|
2673
|
+
realPath = fs2.realpathSync(resolved);
|
|
2674
|
+
} catch (err) {
|
|
2675
|
+
const code = err.code;
|
|
2676
|
+
if (code === "ENOENT")
|
|
2677
|
+
return { entries: [], error: "Directory not found" };
|
|
2678
|
+
return { entries: [], error: "Permission denied" };
|
|
2679
|
+
}
|
|
2680
|
+
const realDeny = browseDenyReason(realPath.split(path2.sep).join("/"));
|
|
2681
|
+
if (realDeny) {
|
|
2682
|
+
return { entries: [], error: `Access denied: ${realDeny}` };
|
|
2683
|
+
}
|
|
2684
|
+
let dirents;
|
|
2685
|
+
try {
|
|
2686
|
+
dirents = fs2.readdirSync(realPath, { withFileTypes: true });
|
|
2687
|
+
} catch (err) {
|
|
2688
|
+
const code = err.code;
|
|
2689
|
+
if (code === "ENOENT")
|
|
2690
|
+
return { entries: [], error: "Directory not found" };
|
|
2691
|
+
if (code === "EACCES" || code === "EPERM")
|
|
2692
|
+
return { entries: [], error: "Permission denied" };
|
|
2693
|
+
return { entries: [], error: `Failed to read directory: ${code ?? String(err)}` };
|
|
2694
|
+
}
|
|
2695
|
+
const entries = [];
|
|
2696
|
+
for (const d of dirents) {
|
|
2697
|
+
if (!d.isDirectory())
|
|
2698
|
+
continue;
|
|
2699
|
+
if (d.name.startsWith("."))
|
|
2700
|
+
continue;
|
|
2701
|
+
entries.push({ name: d.name, type: "dir" });
|
|
2702
|
+
if (entries.length >= MAX_ENTRIES)
|
|
2703
|
+
break;
|
|
2704
|
+
}
|
|
2705
|
+
entries.sort((a, b) => a.name.localeCompare(b.name));
|
|
2706
|
+
return { entries };
|
|
2015
2707
|
}
|
|
2016
2708
|
|
|
2017
2709
|
// ts/daemon/dist/supervisor.js
|
|
2018
|
-
|
|
2019
|
-
import * as fs3 from "node:fs";
|
|
2020
|
-
import * as path4 from "node:path";
|
|
2710
|
+
init_config();
|
|
2021
2711
|
|
|
2022
2712
|
// ts/daemon/dist/runtimes.js
|
|
2023
|
-
import * as
|
|
2024
|
-
|
|
2025
|
-
|
|
2026
|
-
|
|
2027
|
-
|
|
2028
|
-
|
|
2029
|
-
|
|
2030
|
-
|
|
2031
|
-
|
|
2032
|
-
|
|
2033
|
-
|
|
2034
|
-
|
|
2035
|
-
|
|
2036
|
-
|
|
2037
|
-
delete env.
|
|
2038
|
-
|
|
2713
|
+
import * as fs3 from "node:fs";
|
|
2714
|
+
import * as path3 from "node:path";
|
|
2715
|
+
init_config();
|
|
2716
|
+
function buildStandardEnv(baseEnv, agentId, orgId, apiKey, dirs, pc) {
|
|
2717
|
+
const env = { ...baseEnv };
|
|
2718
|
+
clearAllProviderCreds(env);
|
|
2719
|
+
env.PRLL_API_KEY = apiKey;
|
|
2720
|
+
env.PRLL_ORG_ID = orgId;
|
|
2721
|
+
env.AGENT_ID = agentId;
|
|
2722
|
+
env.PRLL_AGENT_ID = agentId;
|
|
2723
|
+
env.PRLL_STATE_DIR = dirs.stateDir;
|
|
2724
|
+
env.PRLL_WORKSPACE_DIR = dirs.workspaceDir;
|
|
2725
|
+
if (pc)
|
|
2726
|
+
env.PRLL_PROVIDER_CONFIG = JSON.stringify(pc);
|
|
2727
|
+
delete env.PRLL_DAEMON_MODE;
|
|
2728
|
+
return env;
|
|
2039
2729
|
}
|
|
2040
2730
|
var claudeCodeAdapter = {
|
|
2041
2731
|
bin: "parall-claude-agent",
|
|
2042
2732
|
buildEnv(baseEnv, agentId, orgId, apiKey, dirs, pc) {
|
|
2043
|
-
const env =
|
|
2044
|
-
clearAllProviderCreds(env);
|
|
2045
|
-
env.PRLL_API_KEY = apiKey;
|
|
2046
|
-
env.PRLL_ORG_ID = orgId;
|
|
2047
|
-
env.AGENT_ID = agentId;
|
|
2048
|
-
env.PRLL_AGENT_ID = agentId;
|
|
2733
|
+
const env = buildStandardEnv(baseEnv, agentId, orgId, apiKey, dirs, pc);
|
|
2049
2734
|
env.PRLL_CLAUDE_HOME = dirs.claudeHome;
|
|
2050
|
-
env.PRLL_CLAUDE_STATE_DIR = dirs.stateDir;
|
|
2051
|
-
env.PRLL_CLAUDE_WORKSPACE_DIR = dirs.workspaceDir;
|
|
2052
|
-
const source = llmSource(pc);
|
|
2053
|
-
if (source === "parall") {
|
|
2054
|
-
env.ANTHROPIC_AUTH_TOKEN = apiKey;
|
|
2055
|
-
env.ANTHROPIC_BASE_URL = `${baseEnv.PRLL_API_URL}/api/llm`;
|
|
2056
|
-
env.PRLL_CLAUDE_ALLOW_API_KEY = "1";
|
|
2057
|
-
} else if (source === "custom") {
|
|
2058
|
-
if (pc?.anthropic_auth_token) {
|
|
2059
|
-
env.ANTHROPIC_AUTH_TOKEN = pc.anthropic_auth_token;
|
|
2060
|
-
env.PRLL_CLAUDE_ALLOW_API_KEY = "1";
|
|
2061
|
-
}
|
|
2062
|
-
if (pc?.anthropic_base_url)
|
|
2063
|
-
env.ANTHROPIC_BASE_URL = pc.anthropic_base_url;
|
|
2064
|
-
}
|
|
2065
|
-
delete env.PRLL_DAEMON_MODE;
|
|
2066
2735
|
return env;
|
|
2067
2736
|
}
|
|
2068
2737
|
};
|
|
2069
2738
|
var codexAdapter = {
|
|
2070
2739
|
bin: "parall-codex-agent",
|
|
2071
2740
|
buildEnv(baseEnv, agentId, orgId, apiKey, dirs, pc) {
|
|
2072
|
-
const env =
|
|
2073
|
-
|
|
2074
|
-
env.PRLL_API_KEY = apiKey;
|
|
2075
|
-
env.PRLL_ORG_ID = orgId;
|
|
2076
|
-
env.AGENT_ID = agentId;
|
|
2077
|
-
env.PRLL_AGENT_ID = agentId;
|
|
2078
|
-
env.PRLL_CODEX_STATE_DIR = dirs.stateDir;
|
|
2079
|
-
env.PRLL_CODEX_WORKSPACE_DIR = dirs.workspaceDir;
|
|
2080
|
-
env.PRLL_CODEX_HOME = path2.join(dirs.stateDir, ".codex");
|
|
2081
|
-
const source = llmSource(pc);
|
|
2082
|
-
if (source === "parall") {
|
|
2083
|
-
env.OPENAI_API_KEY = apiKey;
|
|
2084
|
-
env.OPENAI_BASE_URL = `${baseEnv.PRLL_API_URL}/api/llm/v1`;
|
|
2085
|
-
} else if (source === "custom") {
|
|
2086
|
-
if (pc?.openai_api_key)
|
|
2087
|
-
env.OPENAI_API_KEY = pc.openai_api_key;
|
|
2088
|
-
if (pc?.openai_base_url)
|
|
2089
|
-
env.OPENAI_BASE_URL = pc.openai_base_url;
|
|
2090
|
-
}
|
|
2091
|
-
delete env.PRLL_DAEMON_MODE;
|
|
2741
|
+
const env = buildStandardEnv(baseEnv, agentId, orgId, apiKey, dirs, pc);
|
|
2742
|
+
env.PRLL_CODEX_HOME = path3.join(dirs.stateDir, ".codex");
|
|
2092
2743
|
return env;
|
|
2093
2744
|
}
|
|
2094
2745
|
};
|
|
2095
2746
|
var defaultAdapter = {
|
|
2096
2747
|
bin: "parall-agent",
|
|
2097
|
-
buildEnv
|
|
2098
|
-
const env = { ...baseEnv };
|
|
2099
|
-
env.PRLL_API_KEY = apiKey;
|
|
2100
|
-
env.PRLL_ORG_ID = orgId;
|
|
2101
|
-
env.AGENT_ID = agentId;
|
|
2102
|
-
env.PRLL_AGENT_ID = agentId;
|
|
2103
|
-
env.PRLL_STATE_DIR = dirs.stateDir;
|
|
2104
|
-
env.PRLL_WORKSPACE_DIR = dirs.workspaceDir;
|
|
2105
|
-
delete env.PRLL_DAEMON_MODE;
|
|
2106
|
-
return env;
|
|
2107
|
-
}
|
|
2748
|
+
buildEnv: buildStandardEnv
|
|
2108
2749
|
};
|
|
2109
2750
|
var openclawAdapter = {
|
|
2110
2751
|
bin: "parall-openclaw-agent",
|
|
2111
|
-
buildEnv(baseEnv, agentId, orgId, apiKey, dirs) {
|
|
2112
|
-
const env =
|
|
2113
|
-
clearAllProviderCreds(env);
|
|
2114
|
-
env.PRLL_API_KEY = apiKey;
|
|
2115
|
-
env.PRLL_ORG_ID = orgId;
|
|
2116
|
-
env.AGENT_ID = agentId;
|
|
2117
|
-
env.PRLL_AGENT_ID = agentId;
|
|
2118
|
-
env.PRLL_OPENCLAW_STATE_DIR = dirs.stateDir;
|
|
2119
|
-
env.PRLL_OPENCLAW_WORKSPACE_DIR = dirs.workspaceDir;
|
|
2752
|
+
buildEnv(baseEnv, agentId, orgId, apiKey, dirs, pc) {
|
|
2753
|
+
const env = buildStandardEnv(baseEnv, agentId, orgId, apiKey, dirs, pc);
|
|
2120
2754
|
env.OPENCLAW_GATEWAY_PORT = env.OPENCLAW_GATEWAY_PORT || "0";
|
|
2121
|
-
delete env.PRLL_DAEMON_MODE;
|
|
2122
2755
|
return env;
|
|
2123
2756
|
}
|
|
2124
2757
|
};
|
|
@@ -2127,8 +2760,25 @@ var RUNTIME_ADAPTERS = {
|
|
|
2127
2760
|
"codex": codexAdapter,
|
|
2128
2761
|
"openclaw": openclawAdapter
|
|
2129
2762
|
};
|
|
2763
|
+
var OVERLAY_BIN_NAMES = {
|
|
2764
|
+
"claude-code": "parall-claude-agent.js",
|
|
2765
|
+
"codex": "parall-codex-agent.js",
|
|
2766
|
+
"openclaw": "parall-openclaw-agent.js"
|
|
2767
|
+
};
|
|
2130
2768
|
function getRuntimeAdapter(runtimeType) {
|
|
2131
|
-
|
|
2769
|
+
const base = RUNTIME_ADAPTERS[runtimeType] ?? defaultAdapter;
|
|
2770
|
+
const overlayName = OVERLAY_BIN_NAMES[runtimeType];
|
|
2771
|
+
if (!overlayName)
|
|
2772
|
+
return base;
|
|
2773
|
+
try {
|
|
2774
|
+
const bundleDir = resolveBundleDir();
|
|
2775
|
+
const overlayBin = path3.join(bundleDir, "current", overlayName);
|
|
2776
|
+
if (fs3.existsSync(overlayBin)) {
|
|
2777
|
+
return { ...base, bin: overlayBin };
|
|
2778
|
+
}
|
|
2779
|
+
} catch {
|
|
2780
|
+
}
|
|
2781
|
+
return base;
|
|
2132
2782
|
}
|
|
2133
2783
|
function assertAgentKey(apiKey) {
|
|
2134
2784
|
if (apiKey.startsWith("mck_")) {
|
|
@@ -2139,8 +2789,8 @@ function assertAgentKey(apiKey) {
|
|
|
2139
2789
|
// ts/daemon/dist/workspace.js
|
|
2140
2790
|
import { spawn } from "node:child_process";
|
|
2141
2791
|
import { createHash } from "node:crypto";
|
|
2142
|
-
import * as
|
|
2143
|
-
import * as
|
|
2792
|
+
import * as fs4 from "node:fs";
|
|
2793
|
+
import * as path4 from "node:path";
|
|
2144
2794
|
var OUTPUT_TAIL_LIMIT = 32 * 1024;
|
|
2145
2795
|
var DEFAULT_SETUP_TIMEOUT_SEC = 600;
|
|
2146
2796
|
async function prepareWorkspace(opts) {
|
|
@@ -2257,7 +2907,7 @@ function resolveWorkspaceDir(workspace, defaultWorkspaceDir) {
|
|
|
2257
2907
|
async function ensureWorkspace(plan, log2) {
|
|
2258
2908
|
const ws = plan.workspace;
|
|
2259
2909
|
if (ws.mode === "default") {
|
|
2260
|
-
|
|
2910
|
+
fs4.mkdirSync(plan.workspaceDir, { recursive: true });
|
|
2261
2911
|
assertWritableWorkspaceDir(plan.workspaceDir);
|
|
2262
2912
|
return;
|
|
2263
2913
|
}
|
|
@@ -2265,7 +2915,7 @@ async function ensureWorkspace(plan, log2) {
|
|
|
2265
2915
|
assertSafeCustomWorkspacePath(plan);
|
|
2266
2916
|
let st;
|
|
2267
2917
|
try {
|
|
2268
|
-
st =
|
|
2918
|
+
st = fs4.statSync(plan.workspaceDir);
|
|
2269
2919
|
} catch (err) {
|
|
2270
2920
|
if (isNodeError(err) && err.code === "ENOENT") {
|
|
2271
2921
|
throw new Error(`workspace path does not exist: ${plan.workspaceDir}`);
|
|
@@ -2275,7 +2925,7 @@ async function ensureWorkspace(plan, log2) {
|
|
|
2275
2925
|
if (!st.isDirectory()) {
|
|
2276
2926
|
throw new Error(`workspace path is not a directory: ${plan.workspaceDir}`);
|
|
2277
2927
|
}
|
|
2278
|
-
assertSafeCustomWorkspacePath(plan,
|
|
2928
|
+
assertSafeCustomWorkspacePath(plan, fs4.realpathSync(plan.workspaceDir));
|
|
2279
2929
|
assertWritableWorkspaceDir(plan.workspaceDir);
|
|
2280
2930
|
return;
|
|
2281
2931
|
}
|
|
@@ -2286,17 +2936,17 @@ async function ensureWorkspace(plan, log2) {
|
|
|
2286
2936
|
if (plan.customWorkspaceField) {
|
|
2287
2937
|
assertSafeCustomWorkspacePath(plan);
|
|
2288
2938
|
}
|
|
2289
|
-
if (!
|
|
2290
|
-
|
|
2291
|
-
assertWritableWorkspaceDir(
|
|
2939
|
+
if (!fs4.existsSync(plan.workspaceDir)) {
|
|
2940
|
+
fs4.mkdirSync(path4.dirname(plan.workspaceDir), { recursive: true });
|
|
2941
|
+
assertWritableWorkspaceDir(path4.dirname(plan.workspaceDir));
|
|
2292
2942
|
await runCommand("git", ["clone", remote, plan.workspaceDir], process.cwd());
|
|
2293
2943
|
} else {
|
|
2294
|
-
const st =
|
|
2944
|
+
const st = fs4.statSync(plan.workspaceDir);
|
|
2295
2945
|
if (!st.isDirectory()) {
|
|
2296
2946
|
throw new Error(`workspace path is not a directory: ${plan.workspaceDir}`);
|
|
2297
2947
|
}
|
|
2298
2948
|
if (plan.customWorkspaceField) {
|
|
2299
|
-
assertSafeCustomWorkspacePath(plan,
|
|
2949
|
+
assertSafeCustomWorkspacePath(plan, fs4.realpathSync(plan.workspaceDir));
|
|
2300
2950
|
}
|
|
2301
2951
|
assertWritableWorkspaceDir(plan.workspaceDir);
|
|
2302
2952
|
await ensureGitWorktree(plan.workspaceDir);
|
|
@@ -2320,13 +2970,13 @@ async function verifyExistingWorkspace(plan, log2) {
|
|
|
2320
2970
|
if (plan.customWorkspaceField) {
|
|
2321
2971
|
assertSafeCustomWorkspacePath(plan);
|
|
2322
2972
|
}
|
|
2323
|
-
const st =
|
|
2973
|
+
const st = fs4.statSync(plan.workspaceDir);
|
|
2324
2974
|
if (!st.isDirectory()) {
|
|
2325
2975
|
log2.warn(`workspace ready state ignored: path is not a directory: ${plan.workspaceDir}`);
|
|
2326
2976
|
return false;
|
|
2327
2977
|
}
|
|
2328
2978
|
if (plan.customWorkspaceField) {
|
|
2329
|
-
assertSafeCustomWorkspacePath(plan,
|
|
2979
|
+
assertSafeCustomWorkspacePath(plan, fs4.realpathSync(plan.workspaceDir));
|
|
2330
2980
|
}
|
|
2331
2981
|
assertWritableWorkspaceDir(plan.workspaceDir);
|
|
2332
2982
|
if (plan.workspace.mode === "git") {
|
|
@@ -2419,7 +3069,7 @@ async function tryGitOutput(cmd, args, cwd) {
|
|
|
2419
3069
|
}
|
|
2420
3070
|
}
|
|
2421
3071
|
function runCommand(cmd, args, cwd, timeoutMs = 12e4, env = process.env) {
|
|
2422
|
-
return new Promise((
|
|
3072
|
+
return new Promise((resolve5, reject) => {
|
|
2423
3073
|
let tail = "";
|
|
2424
3074
|
let timedOut = false;
|
|
2425
3075
|
let settled = false;
|
|
@@ -2461,7 +3111,7 @@ ${tail}`)));
|
|
|
2461
3111
|
return;
|
|
2462
3112
|
}
|
|
2463
3113
|
if (code === 0) {
|
|
2464
|
-
settle(() =>
|
|
3114
|
+
settle(() => resolve5(tail));
|
|
2465
3115
|
} else {
|
|
2466
3116
|
settle(() => reject(new Error(`command failed (${code ?? signal}): ${cmd} ${args.join(" ")}
|
|
2467
3117
|
${tail}`)));
|
|
@@ -2470,10 +3120,10 @@ ${tail}`)));
|
|
|
2470
3120
|
});
|
|
2471
3121
|
}
|
|
2472
3122
|
function requireAbsolute(value, field) {
|
|
2473
|
-
if (!value || !
|
|
3123
|
+
if (!value || !path4.isAbsolute(value)) {
|
|
2474
3124
|
throw new Error(`${field} must be an absolute path`);
|
|
2475
3125
|
}
|
|
2476
|
-
return
|
|
3126
|
+
return path4.resolve(value);
|
|
2477
3127
|
}
|
|
2478
3128
|
function assertSafeCustomWorkspacePath(plan, candidate = plan.workspaceDir) {
|
|
2479
3129
|
if (!plan.customWorkspaceField)
|
|
@@ -2483,17 +3133,17 @@ function assertSafeCustomWorkspacePath(plan, candidate = plan.workspaceDir) {
|
|
|
2483
3133
|
if (reason) {
|
|
2484
3134
|
throw new Error(`${plan.customWorkspaceField} must point to a project folder, not ${reason}: ${normalized}`);
|
|
2485
3135
|
}
|
|
2486
|
-
const defaultWorkspace =
|
|
3136
|
+
const defaultWorkspace = path4.resolve(plan.defaultWorkspaceDir);
|
|
2487
3137
|
if (isAncestorPath(normalized, defaultWorkspace) || normalized === defaultWorkspace) {
|
|
2488
3138
|
throw new Error(`${plan.customWorkspaceField} must not point at daemon state directories: ${normalized}`);
|
|
2489
3139
|
}
|
|
2490
3140
|
}
|
|
2491
3141
|
function assertWritableWorkspaceDir(dir) {
|
|
2492
|
-
|
|
2493
|
-
const probe =
|
|
2494
|
-
const fd =
|
|
2495
|
-
|
|
2496
|
-
|
|
3142
|
+
fs4.accessSync(dir, fs4.constants.R_OK | fs4.constants.W_OK | fs4.constants.X_OK);
|
|
3143
|
+
const probe = path4.join(dir, `.parall-workspace-check-${process.pid}-${Date.now()}`);
|
|
3144
|
+
const fd = fs4.openSync(probe, "wx", 384);
|
|
3145
|
+
fs4.closeSync(fd);
|
|
3146
|
+
fs4.unlinkSync(probe);
|
|
2497
3147
|
}
|
|
2498
3148
|
function workspacePathDenyReason(value) {
|
|
2499
3149
|
if (value === "/")
|
|
@@ -2554,11 +3204,11 @@ function workspacePathDenyReason(value) {
|
|
|
2554
3204
|
return "";
|
|
2555
3205
|
}
|
|
2556
3206
|
function isAncestorPath(parent, child) {
|
|
2557
|
-
const relative2 =
|
|
2558
|
-
return relative2 !== "" && !relative2.startsWith("..") && !
|
|
3207
|
+
const relative2 = path4.relative(parent, child);
|
|
3208
|
+
return relative2 !== "" && !relative2.startsWith("..") && !path4.isAbsolute(relative2);
|
|
2559
3209
|
}
|
|
2560
3210
|
function toPolicyPath(value) {
|
|
2561
|
-
return
|
|
3211
|
+
return path4.resolve(value).split(path4.sep).join("/");
|
|
2562
3212
|
}
|
|
2563
3213
|
function isNodeError(err) {
|
|
2564
3214
|
return err instanceof Error && "code" in err;
|
|
@@ -2574,14 +3224,14 @@ var WORKSPACE_SETUP_RETRY_DELAY_MS = 5e3;
|
|
|
2574
3224
|
function sleepCancellable(ms, signal) {
|
|
2575
3225
|
if (signal.aborted)
|
|
2576
3226
|
return Promise.resolve(false);
|
|
2577
|
-
return new Promise((
|
|
3227
|
+
return new Promise((resolve5) => {
|
|
2578
3228
|
const timer = setTimeout(() => {
|
|
2579
3229
|
signal.removeEventListener("abort", onAbort);
|
|
2580
|
-
|
|
3230
|
+
resolve5(true);
|
|
2581
3231
|
}, ms);
|
|
2582
3232
|
const onAbort = () => {
|
|
2583
3233
|
clearTimeout(timer);
|
|
2584
|
-
|
|
3234
|
+
resolve5(false);
|
|
2585
3235
|
};
|
|
2586
3236
|
signal.addEventListener("abort", onAbort, { once: true });
|
|
2587
3237
|
});
|
|
@@ -2600,11 +3250,16 @@ var DaemonSupervisor = class {
|
|
|
2600
3250
|
machineOrgId = null;
|
|
2601
3251
|
machineLlmSource = "parall";
|
|
2602
3252
|
stopResolve = null;
|
|
3253
|
+
updater = null;
|
|
3254
|
+
healthConfirmed = false;
|
|
2603
3255
|
constructor(config, client, log2) {
|
|
2604
3256
|
this.config = config;
|
|
2605
3257
|
this.client = client;
|
|
2606
3258
|
this.log = log2;
|
|
2607
3259
|
}
|
|
3260
|
+
setUpdater(updater) {
|
|
3261
|
+
this.updater = updater;
|
|
3262
|
+
}
|
|
2608
3263
|
/** Start the supervisor. Returns a promise that resolves on `stop()`. */
|
|
2609
3264
|
async run(signal) {
|
|
2610
3265
|
if (this.running)
|
|
@@ -2626,6 +3281,10 @@ var DaemonSupervisor = class {
|
|
|
2626
3281
|
throw err;
|
|
2627
3282
|
}
|
|
2628
3283
|
this.migrateFlatLayout();
|
|
3284
|
+
const daemonVersion = this.updater?.getLocalVersion();
|
|
3285
|
+
if (daemonVersion) {
|
|
3286
|
+
this.client.postMachineHeartbeat(daemonVersion).catch((err) => this.log.warn(`daemon version report failed: ${String(err)}`));
|
|
3287
|
+
}
|
|
2629
3288
|
await this.fullReconcile();
|
|
2630
3289
|
this.ws = new ParallWs({
|
|
2631
3290
|
getTicket: () => this.client.getMachineWsTicket(),
|
|
@@ -2634,11 +3293,33 @@ var DaemonSupervisor = class {
|
|
|
2634
3293
|
});
|
|
2635
3294
|
this.ws.on("machine.hello", (_data) => {
|
|
2636
3295
|
this.log.info("machine WS connected (machine.hello)");
|
|
3296
|
+
if (!this.healthConfirmed && this.updater) {
|
|
3297
|
+
try {
|
|
3298
|
+
this.updater.confirmVersion();
|
|
3299
|
+
this.healthConfirmed = true;
|
|
3300
|
+
} catch (err) {
|
|
3301
|
+
this.log.warn(`confirmVersion failed: ${String(err)}`);
|
|
3302
|
+
}
|
|
3303
|
+
}
|
|
2637
3304
|
void (async () => {
|
|
2638
3305
|
await this.refreshMachineConfig();
|
|
2639
3306
|
await this.fullReconcile();
|
|
2640
3307
|
})();
|
|
2641
3308
|
});
|
|
3309
|
+
this.ws.on("machine.update", (data) => {
|
|
3310
|
+
this.log.info(`WS: daemon update available \u2014 version=${data.new_version} mandatory=${data.mandatory}`);
|
|
3311
|
+
if (this.updater) {
|
|
3312
|
+
void this.updater.triggerUpdate(data.new_version, data.mandatory).then(async (applied) => {
|
|
3313
|
+
if (applied) {
|
|
3314
|
+
this.log.info("daemon update applied \u2014 stopping supervisor before restart");
|
|
3315
|
+
await this.stop();
|
|
3316
|
+
process.exit(42);
|
|
3317
|
+
}
|
|
3318
|
+
}).catch((err) => {
|
|
3319
|
+
this.log.warn(`daemon update failed: ${String(err)}`);
|
|
3320
|
+
});
|
|
3321
|
+
}
|
|
3322
|
+
});
|
|
2642
3323
|
this.ws.on("machine.agent.attached", (data) => {
|
|
2643
3324
|
this.log.info(`WS: agent ${data.agent_id} attached`);
|
|
2644
3325
|
void this.handleAgentAttached(data.agent_id);
|
|
@@ -2659,6 +3340,10 @@ var DaemonSupervisor = class {
|
|
|
2659
3340
|
this.log.info(`WS: workspace setup requested for agent ${data.agent_id}`);
|
|
2660
3341
|
void this.handleWorkspaceSetupRequested(data.agent_id);
|
|
2661
3342
|
});
|
|
3343
|
+
this.ws.on("machine.filesystem.browse", (data) => {
|
|
3344
|
+
this.log.info(`WS: filesystem browse requested: ${data.path}`);
|
|
3345
|
+
void this.handleFilesystemBrowse(data.request_id, data.path);
|
|
3346
|
+
});
|
|
2662
3347
|
this.ws.on("machine.stop", (data) => {
|
|
2663
3348
|
this.log.info(`WS: machine.stop received (reason=${data.reason ?? "none"})`);
|
|
2664
3349
|
void this.stop();
|
|
@@ -2669,8 +3354,8 @@ var DaemonSupervisor = class {
|
|
|
2669
3354
|
}
|
|
2670
3355
|
});
|
|
2671
3356
|
await this.ws.connect();
|
|
2672
|
-
await new Promise((
|
|
2673
|
-
this.stopResolve =
|
|
3357
|
+
await new Promise((resolve5) => {
|
|
3358
|
+
this.stopResolve = resolve5;
|
|
2674
3359
|
});
|
|
2675
3360
|
signal.removeEventListener("abort", onAbort);
|
|
2676
3361
|
}
|
|
@@ -2789,15 +3474,15 @@ var DaemonSupervisor = class {
|
|
|
2789
3474
|
*/
|
|
2790
3475
|
migrateFlatLayout() {
|
|
2791
3476
|
const root = this.config.rootStateDir;
|
|
2792
|
-
const agentsDir =
|
|
2793
|
-
const flatWorkspace =
|
|
2794
|
-
if (!
|
|
3477
|
+
const agentsDir = path5.join(root, "agents");
|
|
3478
|
+
const flatWorkspace = path5.join(root, "workspace");
|
|
3479
|
+
if (!fs5.existsSync(flatWorkspace) || fs5.existsSync(agentsDir))
|
|
2795
3480
|
return;
|
|
2796
3481
|
let ownerAgentId;
|
|
2797
|
-
const sessionsDir =
|
|
2798
|
-
if (
|
|
3482
|
+
const sessionsDir = path5.join(root, "sessions");
|
|
3483
|
+
if (fs5.existsSync(sessionsDir)) {
|
|
2799
3484
|
try {
|
|
2800
|
-
for (const file of
|
|
3485
|
+
for (const file of fs5.readdirSync(sessionsDir)) {
|
|
2801
3486
|
if (!file.endsWith(".json"))
|
|
2802
3487
|
continue;
|
|
2803
3488
|
const decoded = Buffer.from(file.replace(".json", ""), "base64url").toString();
|
|
@@ -2811,13 +3496,13 @@ var DaemonSupervisor = class {
|
|
|
2811
3496
|
}
|
|
2812
3497
|
}
|
|
2813
3498
|
const targetId = ownerAgentId ?? "_orphan";
|
|
2814
|
-
const targetDir =
|
|
3499
|
+
const targetDir = path5.join(agentsDir, targetId);
|
|
2815
3500
|
try {
|
|
2816
|
-
|
|
3501
|
+
fs5.mkdirSync(targetDir, { recursive: true });
|
|
2817
3502
|
for (const sub of ["workspace", "sessions", "dispatch-context"]) {
|
|
2818
|
-
const src =
|
|
2819
|
-
if (
|
|
2820
|
-
|
|
3503
|
+
const src = path5.join(root, sub);
|
|
3504
|
+
if (fs5.existsSync(src)) {
|
|
3505
|
+
fs5.renameSync(src, path5.join(targetDir, sub));
|
|
2821
3506
|
}
|
|
2822
3507
|
}
|
|
2823
3508
|
this.log.info(`migrated legacy flat state \u2192 agents/${targetId}/`);
|
|
@@ -2877,6 +3562,28 @@ var DaemonSupervisor = class {
|
|
|
2877
3562
|
await this.terminateChild(state);
|
|
2878
3563
|
this.children.delete(agentId);
|
|
2879
3564
|
}
|
|
3565
|
+
async handleFilesystemBrowse(requestId, dirPath) {
|
|
3566
|
+
try {
|
|
3567
|
+
const result = await listDirectory(dirPath);
|
|
3568
|
+
await this.client.postBrowseResponse(requestId, {
|
|
3569
|
+
request_id: requestId,
|
|
3570
|
+
path: dirPath,
|
|
3571
|
+
entries: result.entries,
|
|
3572
|
+
error: result.error
|
|
3573
|
+
});
|
|
3574
|
+
} catch (err) {
|
|
3575
|
+
this.log.warn(`filesystem browse failed: ${String(err)}`);
|
|
3576
|
+
try {
|
|
3577
|
+
await this.client.postBrowseResponse(requestId, {
|
|
3578
|
+
request_id: requestId,
|
|
3579
|
+
path: dirPath,
|
|
3580
|
+
entries: [],
|
|
3581
|
+
error: String(err)
|
|
3582
|
+
});
|
|
3583
|
+
} catch {
|
|
3584
|
+
}
|
|
3585
|
+
}
|
|
3586
|
+
}
|
|
2880
3587
|
async handleWorkspaceSetupRequested(agentId) {
|
|
2881
3588
|
if (this.spawningAgents.has(agentId)) {
|
|
2882
3589
|
this.log.info(`agent ${agentId}: workspace setup already in progress; queueing one restart`);
|
|
@@ -3002,9 +3709,9 @@ var DaemonSupervisor = class {
|
|
|
3002
3709
|
const isK8s = !!process.env.KUBERNETES_SERVICE_HOST;
|
|
3003
3710
|
const claudeHome = isK8s ? agentClaudeHomeFor(this.config.rootClaudeHome, agentId) : this.config.rootClaudeHome;
|
|
3004
3711
|
try {
|
|
3005
|
-
|
|
3712
|
+
fs5.mkdirSync(stateDir, { recursive: true });
|
|
3006
3713
|
if (isK8s) {
|
|
3007
|
-
|
|
3714
|
+
fs5.mkdirSync(claudeHome, { recursive: true });
|
|
3008
3715
|
this.ensureSharedCredentialLink(claudeHome, agentId);
|
|
3009
3716
|
}
|
|
3010
3717
|
} catch (err) {
|
|
@@ -3109,15 +3816,15 @@ var DaemonSupervisor = class {
|
|
|
3109
3816
|
const child = state.child;
|
|
3110
3817
|
if (!child)
|
|
3111
3818
|
return;
|
|
3112
|
-
return new Promise((
|
|
3113
|
-
const onExit = () =>
|
|
3819
|
+
return new Promise((resolve5) => {
|
|
3820
|
+
const onExit = () => resolve5();
|
|
3114
3821
|
child.once("exit", onExit);
|
|
3115
3822
|
try {
|
|
3116
3823
|
child.kill("SIGTERM");
|
|
3117
3824
|
} catch (err) {
|
|
3118
3825
|
this.log.warn(`SIGTERM ${state.agentId} threw: ${String(err)}`);
|
|
3119
3826
|
child.off("exit", onExit);
|
|
3120
|
-
|
|
3827
|
+
resolve5();
|
|
3121
3828
|
return;
|
|
3122
3829
|
}
|
|
3123
3830
|
const hardKill = setTimeout(() => {
|
|
@@ -3130,60 +3837,64 @@ var DaemonSupervisor = class {
|
|
|
3130
3837
|
});
|
|
3131
3838
|
}
|
|
3132
3839
|
ensureSharedCredentialLink(agentClaudeHome, agentId) {
|
|
3133
|
-
const sharedCredentials =
|
|
3840
|
+
const sharedCredentials = path5.resolve(sharedClaudeCredentialsFileFor(this.config.rootClaudeHome));
|
|
3134
3841
|
const agentCredentials = agentClaudeCredentialsFileFor(agentClaudeHome);
|
|
3135
|
-
const agentCredentialsDir =
|
|
3136
|
-
|
|
3137
|
-
|
|
3842
|
+
const agentCredentialsDir = path5.dirname(agentCredentials);
|
|
3843
|
+
fs5.mkdirSync(path5.dirname(sharedCredentials), { recursive: true });
|
|
3844
|
+
fs5.mkdirSync(agentCredentialsDir, { recursive: true });
|
|
3138
3845
|
try {
|
|
3139
|
-
const existing =
|
|
3846
|
+
const existing = fs5.lstatSync(agentCredentials);
|
|
3140
3847
|
if (existing.isSymbolicLink()) {
|
|
3141
|
-
const currentTarget =
|
|
3142
|
-
if (
|
|
3848
|
+
const currentTarget = fs5.readlinkSync(agentCredentials);
|
|
3849
|
+
if (path5.resolve(agentCredentialsDir, currentTarget) === sharedCredentials) {
|
|
3143
3850
|
return;
|
|
3144
3851
|
}
|
|
3145
|
-
|
|
3852
|
+
fs5.unlinkSync(agentCredentials);
|
|
3146
3853
|
} else if (existing.isDirectory()) {
|
|
3147
3854
|
this.log.warn(`agent ${agentId}: credential path is a directory, cannot link ${agentCredentials}`);
|
|
3148
3855
|
return;
|
|
3149
3856
|
} else {
|
|
3150
|
-
|
|
3857
|
+
fs5.unlinkSync(agentCredentials);
|
|
3151
3858
|
}
|
|
3152
3859
|
} catch (err) {
|
|
3153
3860
|
if (err.code !== "ENOENT") {
|
|
3154
3861
|
throw err;
|
|
3155
3862
|
}
|
|
3156
3863
|
}
|
|
3157
|
-
|
|
3864
|
+
fs5.symlinkSync(sharedCredentials, agentCredentials);
|
|
3158
3865
|
}
|
|
3159
3866
|
};
|
|
3160
3867
|
|
|
3868
|
+
// ts/daemon/dist/index.js
|
|
3869
|
+
init_updater();
|
|
3870
|
+
|
|
3161
3871
|
// ts/daemon/dist/cli.js
|
|
3162
|
-
|
|
3163
|
-
import * as
|
|
3164
|
-
import * as
|
|
3872
|
+
init_config();
|
|
3873
|
+
import * as fs7 from "node:fs";
|
|
3874
|
+
import * as path7 from "node:path";
|
|
3875
|
+
import * as os3 from "node:os";
|
|
3165
3876
|
import * as readline from "node:readline";
|
|
3166
3877
|
import { spawn as spawn3, execSync } from "node:child_process";
|
|
3167
3878
|
var CONFIG_DIR = daemonConfigDir();
|
|
3168
3879
|
var CONFIG_PATH = daemonConfigPath();
|
|
3169
3880
|
function readConfig() {
|
|
3170
3881
|
try {
|
|
3171
|
-
return JSON.parse(
|
|
3882
|
+
return JSON.parse(fs7.readFileSync(CONFIG_PATH, "utf-8"));
|
|
3172
3883
|
} catch {
|
|
3173
3884
|
return null;
|
|
3174
3885
|
}
|
|
3175
3886
|
}
|
|
3176
3887
|
function writeConfig(config) {
|
|
3177
|
-
|
|
3178
|
-
|
|
3179
|
-
|
|
3888
|
+
fs7.mkdirSync(CONFIG_DIR, { recursive: true, mode: 448 });
|
|
3889
|
+
fs7.writeFileSync(CONFIG_PATH, JSON.stringify(config, null, 2), { mode: 384 });
|
|
3890
|
+
fs7.chmodSync(CONFIG_PATH, 384);
|
|
3180
3891
|
}
|
|
3181
3892
|
function prompt(question) {
|
|
3182
3893
|
const rl = readline.createInterface({ input: process.stdin, output: process.stdout });
|
|
3183
|
-
return new Promise((
|
|
3894
|
+
return new Promise((resolve5) => {
|
|
3184
3895
|
rl.question(question, (answer) => {
|
|
3185
3896
|
rl.close();
|
|
3186
|
-
|
|
3897
|
+
resolve5(answer.trim());
|
|
3187
3898
|
});
|
|
3188
3899
|
});
|
|
3189
3900
|
}
|
|
@@ -3195,10 +3906,10 @@ function isLinux() {
|
|
|
3195
3906
|
}
|
|
3196
3907
|
var PLIST_LABEL = "com.parall.daemon";
|
|
3197
3908
|
function plistPath() {
|
|
3198
|
-
return
|
|
3909
|
+
return path7.join(os3.homedir(), "Library", "LaunchAgents", `${PLIST_LABEL}.plist`);
|
|
3199
3910
|
}
|
|
3200
3911
|
function systemdUnitPath() {
|
|
3201
|
-
return
|
|
3912
|
+
return path7.join(os3.homedir(), ".config", "systemd", "user", "parall-daemon.service");
|
|
3202
3913
|
}
|
|
3203
3914
|
function getDaemonBin() {
|
|
3204
3915
|
try {
|
|
@@ -3208,7 +3919,7 @@ function getDaemonBin() {
|
|
|
3208
3919
|
}
|
|
3209
3920
|
}
|
|
3210
3921
|
function generatePlist(daemonBin) {
|
|
3211
|
-
const logPath =
|
|
3922
|
+
const logPath = path7.join(os3.homedir(), "Library", "Logs", "parall-daemon.log");
|
|
3212
3923
|
return `<?xml version="1.0" encoding="UTF-8"?>
|
|
3213
3924
|
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
|
|
3214
3925
|
<plist version="1.0">
|
|
@@ -3217,7 +3928,9 @@ function generatePlist(daemonBin) {
|
|
|
3217
3928
|
<string>${PLIST_LABEL}</string>
|
|
3218
3929
|
<key>ProgramArguments</key>
|
|
3219
3930
|
<array>
|
|
3220
|
-
<string
|
|
3931
|
+
<string>/bin/sh</string>
|
|
3932
|
+
<string>-c</string>
|
|
3933
|
+
<string>OVERLAY="$HOME/.parall-daemon/bundle/current/parall-daemon.js"; if [ -f "$OVERLAY" ]; then exec node "$OVERLAY"; else exec ${daemonBin}; fi</string>
|
|
3221
3934
|
</array>
|
|
3222
3935
|
<key>RunAtLoad</key>
|
|
3223
3936
|
<true/>
|
|
@@ -3240,7 +3953,7 @@ Wants=network-online.target
|
|
|
3240
3953
|
|
|
3241
3954
|
[Service]
|
|
3242
3955
|
Type=simple
|
|
3243
|
-
ExecStart
|
|
3956
|
+
ExecStart=/bin/sh -c 'OVERLAY="$HOME/.parall-daemon/bundle/current/parall-daemon.js"; if [ -f "$OVERLAY" ]; then exec node "$OVERLAY"; else exec ${daemonBin}; fi'
|
|
3244
3957
|
Restart=always
|
|
3245
3958
|
RestartSec=5
|
|
3246
3959
|
|
|
@@ -3255,16 +3968,16 @@ function installService() {
|
|
|
3255
3968
|
}
|
|
3256
3969
|
const bin = getDaemonBin();
|
|
3257
3970
|
if (isMacOS()) {
|
|
3258
|
-
const dir =
|
|
3259
|
-
|
|
3260
|
-
|
|
3971
|
+
const dir = path7.dirname(plistPath());
|
|
3972
|
+
fs7.mkdirSync(dir, { recursive: true });
|
|
3973
|
+
fs7.writeFileSync(plistPath(), generatePlist(bin));
|
|
3261
3974
|
execSync(`launchctl bootout gui/$(id -u) ${plistPath()} 2>/dev/null || true`);
|
|
3262
3975
|
execSync(`launchctl bootstrap gui/$(id -u) ${plistPath()}`);
|
|
3263
3976
|
console.log(`launchd agent installed: ${plistPath()}`);
|
|
3264
3977
|
} else if (isLinux()) {
|
|
3265
|
-
const dir =
|
|
3266
|
-
|
|
3267
|
-
|
|
3978
|
+
const dir = path7.dirname(systemdUnitPath());
|
|
3979
|
+
fs7.mkdirSync(dir, { recursive: true });
|
|
3980
|
+
fs7.writeFileSync(systemdUnitPath(), generateSystemdUnit(bin));
|
|
3268
3981
|
execSync("systemctl --user daemon-reload");
|
|
3269
3982
|
execSync("systemctl --user enable --now parall-daemon");
|
|
3270
3983
|
console.log(`systemd service installed: ${systemdUnitPath()}`);
|
|
@@ -3331,8 +4044,8 @@ function cmdLogs(lines) {
|
|
|
3331
4044
|
child2.on("exit", (code) => process.exit(code ?? 0));
|
|
3332
4045
|
return;
|
|
3333
4046
|
}
|
|
3334
|
-
const logPath =
|
|
3335
|
-
if (!
|
|
4047
|
+
const logPath = path7.join(os3.homedir(), "Library", "Logs", "parall-daemon.log");
|
|
4048
|
+
if (!fs7.existsSync(logPath)) {
|
|
3336
4049
|
console.log("No log file found at", logPath);
|
|
3337
4050
|
return;
|
|
3338
4051
|
}
|
|
@@ -3342,20 +4055,56 @@ function cmdLogs(lines) {
|
|
|
3342
4055
|
function cmdServiceUninstall() {
|
|
3343
4056
|
if (isMacOS()) {
|
|
3344
4057
|
execSync(`launchctl bootout gui/$(id -u) ${plistPath()} 2>/dev/null || true`);
|
|
3345
|
-
if (
|
|
3346
|
-
|
|
4058
|
+
if (fs7.existsSync(plistPath()))
|
|
4059
|
+
fs7.unlinkSync(plistPath());
|
|
3347
4060
|
console.log("launchd agent uninstalled.");
|
|
3348
4061
|
} else if (isLinux()) {
|
|
3349
4062
|
execSync("systemctl --user stop parall-daemon 2>/dev/null || true");
|
|
3350
4063
|
execSync("systemctl --user disable parall-daemon 2>/dev/null || true");
|
|
3351
|
-
if (
|
|
3352
|
-
|
|
4064
|
+
if (fs7.existsSync(systemdUnitPath()))
|
|
4065
|
+
fs7.unlinkSync(systemdUnitPath());
|
|
3353
4066
|
execSync("systemctl --user daemon-reload");
|
|
3354
4067
|
console.log("systemd service uninstalled.");
|
|
3355
4068
|
} else {
|
|
3356
4069
|
console.log("Unsupported platform.");
|
|
3357
4070
|
}
|
|
3358
4071
|
}
|
|
4072
|
+
async function cmdUpdate(checkOnly) {
|
|
4073
|
+
const { resolveClaudeDaemonConfig: resolveClaudeDaemonConfig2, resolveBundleDir: resolveBundleDir2 } = await Promise.resolve().then(() => (init_config(), config_exports));
|
|
4074
|
+
const { DaemonUpdater: DaemonUpdater2 } = await Promise.resolve().then(() => (init_updater(), updater_exports));
|
|
4075
|
+
const config = resolveClaudeDaemonConfig2(process.env);
|
|
4076
|
+
const bundleDir = resolveBundleDir2(process.env);
|
|
4077
|
+
const signingEnabled = !!process.env.PRLL_DAEMON_SIGNING_PUBLIC_KEY;
|
|
4078
|
+
const updater = new DaemonUpdater2(bundleDir, config.updateCdnUrl, {
|
|
4079
|
+
info: (msg) => console.log(msg),
|
|
4080
|
+
warn: (msg) => console.warn(msg),
|
|
4081
|
+
error: (msg) => console.error(msg)
|
|
4082
|
+
}, signingEnabled);
|
|
4083
|
+
const local = updater.getLocalVersion();
|
|
4084
|
+
console.log(`Current version: ${local ?? "unknown"}`);
|
|
4085
|
+
console.log(`CDN: ${config.updateCdnUrl}`);
|
|
4086
|
+
console.log(`Bundle dir: ${bundleDir}`);
|
|
4087
|
+
if (checkOnly) {
|
|
4088
|
+
console.log("\nChecking for updates...");
|
|
4089
|
+
const result = await updater.checkAvailable();
|
|
4090
|
+
if (result.available) {
|
|
4091
|
+
console.log(`Update available: ${result.currentVersion ?? "unknown"} \u2192 ${result.remoteVersion}`);
|
|
4092
|
+
} else if (result.remoteVersion) {
|
|
4093
|
+
console.log(`Already up to date (${result.currentVersion}).`);
|
|
4094
|
+
} else {
|
|
4095
|
+
console.log("Could not check for updates.");
|
|
4096
|
+
}
|
|
4097
|
+
return;
|
|
4098
|
+
} else {
|
|
4099
|
+
console.log("\nChecking and applying updates...");
|
|
4100
|
+
}
|
|
4101
|
+
const applied = await updater.checkAndApply();
|
|
4102
|
+
if (applied) {
|
|
4103
|
+
console.log("Update applied. Restart the daemon to use the new version.");
|
|
4104
|
+
} else {
|
|
4105
|
+
console.log("Already up to date.");
|
|
4106
|
+
}
|
|
4107
|
+
}
|
|
3359
4108
|
function printUsage() {
|
|
3360
4109
|
console.log(`
|
|
3361
4110
|
parall-daemon \u2014 Parall local agent runtime
|
|
@@ -3365,6 +4114,7 @@ Usage:
|
|
|
3365
4114
|
parall-daemon init Configure the daemon (interactive)
|
|
3366
4115
|
parall-daemon status Show daemon service status
|
|
3367
4116
|
parall-daemon stop Stop the background service
|
|
4117
|
+
parall-daemon update [--check] Check for / apply daemon updates
|
|
3368
4118
|
parall-daemon logs [-n LINES] Tail daemon logs
|
|
3369
4119
|
parall-daemon service install Install as background service (launchd/systemd)
|
|
3370
4120
|
parall-daemon service uninstall Uninstall background service
|
|
@@ -3396,6 +4146,9 @@ async function runCLI(args) {
|
|
|
3396
4146
|
cmdLogs(lines);
|
|
3397
4147
|
return "handled";
|
|
3398
4148
|
}
|
|
4149
|
+
case "update":
|
|
4150
|
+
await cmdUpdate(args.includes("--check"));
|
|
4151
|
+
return "handled";
|
|
3399
4152
|
case "service": {
|
|
3400
4153
|
const sub = args[1];
|
|
3401
4154
|
if (sub === "install") {
|
|
@@ -3425,6 +4178,7 @@ async function runCLI(args) {
|
|
|
3425
4178
|
}
|
|
3426
4179
|
|
|
3427
4180
|
// ts/daemon/dist/index.js
|
|
4181
|
+
var UPDATE_EXIT_CODE = 42;
|
|
3428
4182
|
var log = createLogger("daemon");
|
|
3429
4183
|
function formatError(reason) {
|
|
3430
4184
|
if (reason instanceof Error) {
|
|
@@ -3432,10 +4186,12 @@ function formatError(reason) {
|
|
|
3432
4186
|
}
|
|
3433
4187
|
return String(reason);
|
|
3434
4188
|
}
|
|
3435
|
-
async function runForever(config, client, log2, signal) {
|
|
4189
|
+
async function runForever(config, client, log2, signal, updater) {
|
|
3436
4190
|
let attempt = 0;
|
|
3437
4191
|
while (!signal.aborted) {
|
|
3438
4192
|
const supervisor = new DaemonSupervisor(config, client, log2);
|
|
4193
|
+
if (updater)
|
|
4194
|
+
supervisor.setUpdater(updater);
|
|
3439
4195
|
try {
|
|
3440
4196
|
await supervisor.run(signal);
|
|
3441
4197
|
await supervisor.stop();
|
|
@@ -3464,6 +4220,16 @@ async function main() {
|
|
|
3464
4220
|
const config = resolveClaudeDaemonConfig(process.env);
|
|
3465
4221
|
log.info(`boot: api=${config.apiUrl} pollMs=${config.pollIntervalMs} heartbeatMs=${config.heartbeatIntervalMs} agentBin=${config.agentBin}`);
|
|
3466
4222
|
log.info(`keepalive: bootstrapBackoffMs=${config.bootstrapBackoffMs} supervisorRestartBackoffMs=${config.supervisorRestartBackoffMs}`);
|
|
4223
|
+
let updater = null;
|
|
4224
|
+
if (!config.updateDisabled) {
|
|
4225
|
+
const bundleDir = resolveBundleDir(process.env);
|
|
4226
|
+
updater = new DaemonUpdater(bundleDir, config.updateCdnUrl, log, true);
|
|
4227
|
+
if (updater.checkRollback()) {
|
|
4228
|
+
log.info("rollback applied \u2014 exiting for service manager restart");
|
|
4229
|
+
process.exit(UPDATE_EXIT_CODE);
|
|
4230
|
+
}
|
|
4231
|
+
log.info(`update: bundleDir=${bundleDir} cdn=${config.updateCdnUrl} interval=${config.updateIntervalMs}ms`);
|
|
4232
|
+
}
|
|
3467
4233
|
const client = new ParallClient({
|
|
3468
4234
|
baseUrl: config.apiUrl,
|
|
3469
4235
|
token: config.apiKey,
|
|
@@ -3485,7 +4251,18 @@ async function main() {
|
|
|
3485
4251
|
process.exit(1);
|
|
3486
4252
|
});
|
|
3487
4253
|
config.wsUrl = resolveWsUrl(config.apiUrl, config.wsUrl, config.swimlaneName);
|
|
3488
|
-
|
|
4254
|
+
if (updater) {
|
|
4255
|
+
const applied = await updater.checkAndApply().catch((err) => {
|
|
4256
|
+
log.warn(`boot update check failed: ${String(err)}`);
|
|
4257
|
+
return false;
|
|
4258
|
+
});
|
|
4259
|
+
if (applied) {
|
|
4260
|
+
log.info("boot update applied \u2014 exiting for restart");
|
|
4261
|
+
process.exit(UPDATE_EXIT_CODE);
|
|
4262
|
+
}
|
|
4263
|
+
updater.startPeriodicCheck(config.updateIntervalMs);
|
|
4264
|
+
}
|
|
4265
|
+
await runForever(config, client, log, abortController.signal, updater);
|
|
3489
4266
|
}
|
|
3490
4267
|
var cliArgs = process.argv.slice(2);
|
|
3491
4268
|
runCLI(cliArgs).then((result) => {
|