@parall/daemon 1.30.0 → 1.32.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 +17 -11
- package/bundle/package.json +1 -0
- package/bundle/parall-claude-agent.js +27671 -337
- package/bundle/parall-codex-agent.js +27715 -375
- package/bundle/parall-daemon.js +30421 -1355
- package/bundle/parall-openclaw-agent.js +51 -26
- package/dist/cli.d.ts +1 -1
- package/dist/cli.d.ts.map +1 -1
- package/dist/cli.js +122 -72
- package/dist/clip-runtime/clip-installer.d.ts +44 -0
- package/dist/clip-runtime/clip-installer.d.ts.map +1 -0
- package/dist/clip-runtime/clip-installer.js +501 -0
- package/dist/clip-runtime/clip-provider.d.ts +76 -0
- package/dist/clip-runtime/clip-provider.d.ts.map +1 -0
- package/dist/clip-runtime/clip-provider.js +402 -0
- package/dist/clip-runtime/index.d.ts +7 -0
- package/dist/clip-runtime/index.d.ts.map +1 -0
- package/dist/clip-runtime/index.js +5 -0
- package/dist/clip-runtime/ipc.d.ts +94 -0
- package/dist/clip-runtime/ipc.d.ts.map +1 -0
- package/dist/clip-runtime/ipc.js +98 -0
- package/dist/clip-runtime/manifest.d.ts +74 -0
- package/dist/clip-runtime/manifest.d.ts.map +1 -0
- package/dist/clip-runtime/manifest.js +181 -0
- package/dist/clip-runtime/process-manager.d.ts +57 -0
- package/dist/clip-runtime/process-manager.d.ts.map +1 -0
- package/dist/clip-runtime/process-manager.js +354 -0
- package/dist/clip-runtime/process.d.ts +59 -0
- package/dist/clip-runtime/process.d.ts.map +1 -0
- package/dist/clip-runtime/process.js +350 -0
- package/dist/config.d.ts +13 -0
- package/dist/config.d.ts.map +1 -1
- package/dist/config.js +36 -19
- package/dist/filesystem.d.ts +1 -1
- package/dist/filesystem.d.ts.map +1 -1
- package/dist/filesystem.js +51 -53
- package/dist/index.js +46 -14
- package/dist/runtimes.d.ts +10 -8
- package/dist/runtimes.d.ts.map +1 -1
- package/dist/runtimes.js +63 -95
- package/dist/supervisor.d.ts +12 -3
- package/dist/supervisor.d.ts.map +1 -1
- package/dist/supervisor.js +272 -71
- package/dist/updater-manifest.d.ts +41 -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 +427 -0
- package/dist/workspace.d.ts +2 -2
- package/dist/workspace.d.ts.map +1 -1
- package/dist/workspace.js +112 -112
- package/package.json +6 -6
|
@@ -0,0 +1,350 @@
|
|
|
1
|
+
import { spawn } from 'node:child_process';
|
|
2
|
+
import { createInterface } from 'node:readline';
|
|
3
|
+
import { clearAllProviderCreds } from '../runtimes.js';
|
|
4
|
+
import { NdjsonReader, NdjsonWriter, MessageType } from './ipc.js';
|
|
5
|
+
import { manifestFromIpc, enrichManifest, resolveEntrypoint, } from './manifest.js';
|
|
6
|
+
const CLIP_REGISTER_TIMEOUT_MS = 10_000;
|
|
7
|
+
const CLIP_STOP_TIMEOUT_MS = 5_000;
|
|
8
|
+
function sanitizeEnvForClip(extra) {
|
|
9
|
+
const env = { ...process.env };
|
|
10
|
+
clearAllProviderCreds(env);
|
|
11
|
+
delete env.PRLL_API_KEY;
|
|
12
|
+
delete env.PRLL_PROVIDER_CONFIG;
|
|
13
|
+
delete env.PRLL_DAEMON_MODE;
|
|
14
|
+
Object.assign(env, extra);
|
|
15
|
+
return env;
|
|
16
|
+
}
|
|
17
|
+
export class ClipCommandError extends Error {
|
|
18
|
+
code;
|
|
19
|
+
constructor(message, code) {
|
|
20
|
+
super(message);
|
|
21
|
+
this.name = 'ClipCommandError';
|
|
22
|
+
this.code = code;
|
|
23
|
+
}
|
|
24
|
+
}
|
|
25
|
+
function makeDeferred() {
|
|
26
|
+
let resolve;
|
|
27
|
+
let reject;
|
|
28
|
+
const promise = new Promise((res, rej) => {
|
|
29
|
+
resolve = res;
|
|
30
|
+
reject = rej;
|
|
31
|
+
});
|
|
32
|
+
return { promise, resolve, reject };
|
|
33
|
+
}
|
|
34
|
+
export class ClipProcess {
|
|
35
|
+
clip;
|
|
36
|
+
child = null;
|
|
37
|
+
reader = null;
|
|
38
|
+
writer = null;
|
|
39
|
+
registered = false;
|
|
40
|
+
manifest = null;
|
|
41
|
+
pending = new Map();
|
|
42
|
+
nextId = 0;
|
|
43
|
+
aborted = false;
|
|
44
|
+
stopping = false;
|
|
45
|
+
readyDeferred = makeDeferred();
|
|
46
|
+
readyResolved = false;
|
|
47
|
+
doneDeferred = makeDeferred();
|
|
48
|
+
doneResolved = false;
|
|
49
|
+
exitError;
|
|
50
|
+
constructor(clip) {
|
|
51
|
+
this.clip = clip;
|
|
52
|
+
}
|
|
53
|
+
/**
|
|
54
|
+
* Spawn the Bun subprocess and start the IPC read loop.
|
|
55
|
+
* Corresponds to Pinix startLocked().
|
|
56
|
+
*/
|
|
57
|
+
spawn(bunPath, env) {
|
|
58
|
+
const entrypoint = resolveEntrypoint(this.clip);
|
|
59
|
+
this.child = spawn(bunPath, ['run', entrypoint, '--ipc'], {
|
|
60
|
+
cwd: this.clip.path,
|
|
61
|
+
env: sanitizeEnvForClip({
|
|
62
|
+
...env,
|
|
63
|
+
PINIX_URL: env.PINIX_URL || 'http://127.0.0.1:9000',
|
|
64
|
+
PINIX_DATA_DIR: env.PINIX_DATA_DIR || '',
|
|
65
|
+
}),
|
|
66
|
+
stdio: ['pipe', 'pipe', 'pipe'],
|
|
67
|
+
});
|
|
68
|
+
this.writer = new NdjsonWriter(this.child.stdin);
|
|
69
|
+
this.reader = new NdjsonReader(this.child.stdout);
|
|
70
|
+
// stderr → daemon log with clip name prefix
|
|
71
|
+
const stderrRl = createInterface({ input: this.child.stderr, crlfDelay: Infinity });
|
|
72
|
+
stderrRl.on('line', (line) => {
|
|
73
|
+
console.log(`[clip:${this.clip.name}] ${line}`);
|
|
74
|
+
});
|
|
75
|
+
this.readLoop().catch((err) => this.finish(err));
|
|
76
|
+
this.child.on('exit', (code, signal) => {
|
|
77
|
+
const err = this.stopping
|
|
78
|
+
? undefined
|
|
79
|
+
: signal
|
|
80
|
+
? new Error(`clip "${this.clip.name}" killed by ${signal}`)
|
|
81
|
+
: code !== 0
|
|
82
|
+
? new Error(`clip "${this.clip.name}" exited with code ${code}`)
|
|
83
|
+
: undefined;
|
|
84
|
+
this.finish(err);
|
|
85
|
+
});
|
|
86
|
+
this.child.on('error', (err) => {
|
|
87
|
+
this.finish(err);
|
|
88
|
+
});
|
|
89
|
+
}
|
|
90
|
+
async waitReady(timeoutMs = CLIP_REGISTER_TIMEOUT_MS) {
|
|
91
|
+
let timer;
|
|
92
|
+
const timeout = new Promise((_, reject) => {
|
|
93
|
+
timer = setTimeout(() => {
|
|
94
|
+
this.abort(new Error(`clip "${this.clip.name}" did not register within ${timeoutMs}ms`));
|
|
95
|
+
reject(new Error(`clip "${this.clip.name}" did not register within ${timeoutMs}ms`));
|
|
96
|
+
}, timeoutMs);
|
|
97
|
+
});
|
|
98
|
+
try {
|
|
99
|
+
await Promise.race([this.readyDeferred.promise, timeout]);
|
|
100
|
+
}
|
|
101
|
+
finally {
|
|
102
|
+
clearTimeout(timer);
|
|
103
|
+
}
|
|
104
|
+
}
|
|
105
|
+
waitDone() {
|
|
106
|
+
return this.doneDeferred.promise;
|
|
107
|
+
}
|
|
108
|
+
async invoke(command, input) {
|
|
109
|
+
return this.invokeStream(command, input);
|
|
110
|
+
}
|
|
111
|
+
async invokeStream(command, input, onChunk) {
|
|
112
|
+
if (!this.alive())
|
|
113
|
+
throw new Error(`clip "${this.clip.name}" is not running`);
|
|
114
|
+
const requestId = String(this.nextId++);
|
|
115
|
+
const events = [];
|
|
116
|
+
const resultPromise = new Promise((resolve, reject) => {
|
|
117
|
+
this.pending.set(requestId, (event) => {
|
|
118
|
+
switch (event.type) {
|
|
119
|
+
case MessageType.Result: {
|
|
120
|
+
this.pending.delete(requestId);
|
|
121
|
+
resolve({ output: event.output });
|
|
122
|
+
break;
|
|
123
|
+
}
|
|
124
|
+
case MessageType.Error: {
|
|
125
|
+
this.pending.delete(requestId);
|
|
126
|
+
const msg = event.error?.message ?? 'unknown clip error';
|
|
127
|
+
if (event.processExit) {
|
|
128
|
+
reject(new Error(msg));
|
|
129
|
+
}
|
|
130
|
+
else {
|
|
131
|
+
reject(new ClipCommandError(msg, event.error?.code));
|
|
132
|
+
}
|
|
133
|
+
break;
|
|
134
|
+
}
|
|
135
|
+
case MessageType.Chunk: {
|
|
136
|
+
if (onChunk)
|
|
137
|
+
onChunk(event.output);
|
|
138
|
+
events.push(event);
|
|
139
|
+
break;
|
|
140
|
+
}
|
|
141
|
+
case MessageType.Done: {
|
|
142
|
+
this.pending.delete(requestId);
|
|
143
|
+
let output = event.output;
|
|
144
|
+
if (output === undefined && events.length > 0) {
|
|
145
|
+
output = events.map((e) => e.output);
|
|
146
|
+
}
|
|
147
|
+
resolve({ output });
|
|
148
|
+
break;
|
|
149
|
+
}
|
|
150
|
+
}
|
|
151
|
+
});
|
|
152
|
+
});
|
|
153
|
+
const invokeMsg = {
|
|
154
|
+
id: requestId,
|
|
155
|
+
type: MessageType.Invoke,
|
|
156
|
+
command,
|
|
157
|
+
input: input ?? {},
|
|
158
|
+
};
|
|
159
|
+
try {
|
|
160
|
+
await this.send(invokeMsg);
|
|
161
|
+
}
|
|
162
|
+
catch (err) {
|
|
163
|
+
this.pending.delete(requestId);
|
|
164
|
+
throw err;
|
|
165
|
+
}
|
|
166
|
+
return resultPromise;
|
|
167
|
+
}
|
|
168
|
+
async stop(timeoutMs = CLIP_STOP_TIMEOUT_MS) {
|
|
169
|
+
if (!this.child || !this.alive())
|
|
170
|
+
return;
|
|
171
|
+
this.stopping = true;
|
|
172
|
+
this.child.kill('SIGTERM');
|
|
173
|
+
let timer;
|
|
174
|
+
const timeout = new Promise((resolve) => {
|
|
175
|
+
timer = setTimeout(() => {
|
|
176
|
+
if (this.child && this.alive()) {
|
|
177
|
+
this.child.kill('SIGKILL');
|
|
178
|
+
}
|
|
179
|
+
resolve();
|
|
180
|
+
}, timeoutMs);
|
|
181
|
+
});
|
|
182
|
+
try {
|
|
183
|
+
await Promise.race([this.doneDeferred.promise, timeout]);
|
|
184
|
+
}
|
|
185
|
+
finally {
|
|
186
|
+
clearTimeout(timer);
|
|
187
|
+
}
|
|
188
|
+
}
|
|
189
|
+
alive() {
|
|
190
|
+
return (this.child !== null &&
|
|
191
|
+
!this.aborted &&
|
|
192
|
+
this.child.exitCode === null &&
|
|
193
|
+
this.child.signalCode === null);
|
|
194
|
+
}
|
|
195
|
+
getManifest() {
|
|
196
|
+
return this.manifest;
|
|
197
|
+
}
|
|
198
|
+
getError() {
|
|
199
|
+
return this.exitError;
|
|
200
|
+
}
|
|
201
|
+
// --- internal ---
|
|
202
|
+
async readLoop() {
|
|
203
|
+
if (!this.reader)
|
|
204
|
+
return;
|
|
205
|
+
for await (const msg of this.reader) {
|
|
206
|
+
try {
|
|
207
|
+
this.handleMessage(msg);
|
|
208
|
+
}
|
|
209
|
+
catch (err) {
|
|
210
|
+
console.warn(`[clip:${this.clip.name}] error handling message:`, err);
|
|
211
|
+
}
|
|
212
|
+
}
|
|
213
|
+
}
|
|
214
|
+
handleMessage(msg) {
|
|
215
|
+
// First message must be register
|
|
216
|
+
if (!this.registered && msg.type !== MessageType.Register) {
|
|
217
|
+
console.warn(`[clip:${this.clip.name}] expected register as first message, got "${msg.type}"`);
|
|
218
|
+
return;
|
|
219
|
+
}
|
|
220
|
+
switch (msg.type) {
|
|
221
|
+
case MessageType.Register:
|
|
222
|
+
this.handleRegister(msg);
|
|
223
|
+
break;
|
|
224
|
+
case MessageType.Result:
|
|
225
|
+
case MessageType.Error:
|
|
226
|
+
case MessageType.Chunk:
|
|
227
|
+
case MessageType.Done:
|
|
228
|
+
this.dispatchInvokeEvent(msg);
|
|
229
|
+
break;
|
|
230
|
+
case MessageType.Data:
|
|
231
|
+
this.handleData(msg);
|
|
232
|
+
break;
|
|
233
|
+
case MessageType.ListClips:
|
|
234
|
+
// Clip-to-clip list request — respond with empty for now (Phase 2)
|
|
235
|
+
this.send({
|
|
236
|
+
id: msg.id,
|
|
237
|
+
type: MessageType.ListClipsResult,
|
|
238
|
+
clips: [],
|
|
239
|
+
}).catch(() => { });
|
|
240
|
+
break;
|
|
241
|
+
case MessageType.Invoke:
|
|
242
|
+
// Clip-to-clip invoke — not routed in Phase 1
|
|
243
|
+
this.send({
|
|
244
|
+
id: msg.id,
|
|
245
|
+
type: MessageType.Error,
|
|
246
|
+
error: 'clip-to-clip invoke not supported yet',
|
|
247
|
+
}).catch(() => { });
|
|
248
|
+
break;
|
|
249
|
+
case MessageType.Heartbeat:
|
|
250
|
+
break;
|
|
251
|
+
default:
|
|
252
|
+
console.warn(`[clip:${this.clip.name}] unknown message type: ${msg.type}`);
|
|
253
|
+
}
|
|
254
|
+
}
|
|
255
|
+
handleRegister(msg) {
|
|
256
|
+
if (this.registered) {
|
|
257
|
+
console.warn(`[clip:${this.clip.name}] duplicate register message, ignoring`);
|
|
258
|
+
return;
|
|
259
|
+
}
|
|
260
|
+
let manifest;
|
|
261
|
+
if (msg.manifest) {
|
|
262
|
+
manifest = manifestFromIpc(msg.manifest);
|
|
263
|
+
}
|
|
264
|
+
else {
|
|
265
|
+
manifest = {
|
|
266
|
+
name: this.clip.name,
|
|
267
|
+
commands: [],
|
|
268
|
+
commandDetails: [],
|
|
269
|
+
};
|
|
270
|
+
}
|
|
271
|
+
manifest = enrichManifest(this.clip, manifest);
|
|
272
|
+
this.manifest = manifest;
|
|
273
|
+
this.registered = true;
|
|
274
|
+
this.send({ type: MessageType.Registered }).catch((err) => {
|
|
275
|
+
console.error(`[clip:${this.clip.name}] failed to send registered:`, err);
|
|
276
|
+
});
|
|
277
|
+
this.signalReady();
|
|
278
|
+
}
|
|
279
|
+
dispatchInvokeEvent(msg) {
|
|
280
|
+
if (!msg.id) {
|
|
281
|
+
console.warn(`[clip:${this.clip.name}] invoke response missing id`);
|
|
282
|
+
return;
|
|
283
|
+
}
|
|
284
|
+
const callback = this.pending.get(msg.id);
|
|
285
|
+
if (!callback) {
|
|
286
|
+
console.warn(`[clip:${this.clip.name}] no pending invoke for id ${msg.id}`);
|
|
287
|
+
return;
|
|
288
|
+
}
|
|
289
|
+
const event = { type: msg.type };
|
|
290
|
+
if (msg.output !== undefined)
|
|
291
|
+
event.output = msg.output;
|
|
292
|
+
if (msg.error)
|
|
293
|
+
event.error = { message: msg.error };
|
|
294
|
+
callback(event);
|
|
295
|
+
}
|
|
296
|
+
handleData(msg) {
|
|
297
|
+
// Data operations (file I/O) — stub for Phase 1
|
|
298
|
+
this.send({
|
|
299
|
+
id: msg.id,
|
|
300
|
+
type: MessageType.DataResult,
|
|
301
|
+
error: 'data operations not implemented yet',
|
|
302
|
+
}).catch(() => { });
|
|
303
|
+
}
|
|
304
|
+
async send(msg) {
|
|
305
|
+
if (!this.writer)
|
|
306
|
+
throw new Error('writer not initialized');
|
|
307
|
+
return this.writer.send(msg);
|
|
308
|
+
}
|
|
309
|
+
signalReady() {
|
|
310
|
+
if (this.readyResolved)
|
|
311
|
+
return;
|
|
312
|
+
this.readyResolved = true;
|
|
313
|
+
this.readyDeferred.resolve();
|
|
314
|
+
}
|
|
315
|
+
finish(err) {
|
|
316
|
+
if (this.doneResolved)
|
|
317
|
+
return;
|
|
318
|
+
this.doneResolved = true;
|
|
319
|
+
this.exitError = err;
|
|
320
|
+
// Close stdin to signal the child
|
|
321
|
+
if (this.child?.stdin && !this.child.stdin.destroyed) {
|
|
322
|
+
this.child.stdin.end();
|
|
323
|
+
}
|
|
324
|
+
this.reader?.close();
|
|
325
|
+
this.writer?.close();
|
|
326
|
+
// Reject all pending invokes with process-exit flag (not ClipCommandError)
|
|
327
|
+
const errorEvent = {
|
|
328
|
+
type: MessageType.Error,
|
|
329
|
+
error: { message: err?.message ?? 'clip process exited' },
|
|
330
|
+
processExit: true,
|
|
331
|
+
};
|
|
332
|
+
for (const [id, callback] of this.pending) {
|
|
333
|
+
callback(errorEvent);
|
|
334
|
+
}
|
|
335
|
+
this.pending.clear();
|
|
336
|
+
// If not yet registered, reject the ready promise
|
|
337
|
+
if (!this.readyResolved) {
|
|
338
|
+
this.readyResolved = true;
|
|
339
|
+
this.readyDeferred.reject(err ?? new Error('clip process exited before registering'));
|
|
340
|
+
}
|
|
341
|
+
this.doneDeferred.resolve();
|
|
342
|
+
}
|
|
343
|
+
abort(err) {
|
|
344
|
+
this.aborted = true;
|
|
345
|
+
this.finish(err);
|
|
346
|
+
if (this.child && this.child.exitCode === null && !this.child.killed) {
|
|
347
|
+
this.child.kill('SIGKILL');
|
|
348
|
+
}
|
|
349
|
+
}
|
|
350
|
+
}
|
package/dist/config.d.ts
CHANGED
|
@@ -47,6 +47,12 @@ export type ClaudeDaemonConfig = {
|
|
|
47
47
|
*/
|
|
48
48
|
supervisorRestartBackoffMs: number;
|
|
49
49
|
supervisorRestartBackoffMaxMs: number;
|
|
50
|
+
/** CDN base URL for daemon bundle manifest (includes channel prefix). */
|
|
51
|
+
updateCdnUrl: string;
|
|
52
|
+
/** Update check interval in ms. Default 6h. Set 0 to disable periodic check. */
|
|
53
|
+
updateIntervalMs: number;
|
|
54
|
+
/** Disable self-update entirely (K8s env auto-disables). */
|
|
55
|
+
updateDisabled: boolean;
|
|
50
56
|
};
|
|
51
57
|
export declare function daemonConfigDir(env?: NodeJS.ProcessEnv): string;
|
|
52
58
|
export declare function daemonConfigPath(env?: NodeJS.ProcessEnv): string;
|
|
@@ -63,5 +69,12 @@ export declare function sharedClaudeCredentialsFileFor(rootClaudeHome: string):
|
|
|
63
69
|
export declare function agentClaudeCredentialsFileFor(agentClaudeHome: string): string;
|
|
64
70
|
/** Per-agent workspace dir where the agent runs git commands. */
|
|
65
71
|
export declare function agentWorkspaceDirFor(rootStateDir: string, agentId: string): string;
|
|
72
|
+
/**
|
|
73
|
+
* Resolve the bundle directory for self-update storage.
|
|
74
|
+
* Desktop: ~/Library/Application Support/Parall/daemon/ (set via env)
|
|
75
|
+
* Default: ~/.parall-daemon/bundle/ — always a writable overlay directory,
|
|
76
|
+
* never the npm global install dir (which may be root-owned or read-only).
|
|
77
|
+
*/
|
|
78
|
+
export declare function resolveBundleDir(env?: NodeJS.ProcessEnv): string;
|
|
66
79
|
export declare function resolveWsUrl(apiUrl: string, explicitWsUrl?: string, swimlaneName?: string): string;
|
|
67
80
|
//# sourceMappingURL=config.d.ts.map
|
package/dist/config.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
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,oDAAoD;IACpD,YAAY,CAAC,EAAE,MAAM,CAAC;IACtB,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;
|
|
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,oDAAoD;IACpD,YAAY,CAAC,EAAE,MAAM,CAAC;IACtB,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;IACtC,yEAAyE;IACzE,YAAY,EAAE,MAAM,CAAC;IACrB,gFAAgF;IAChF,gBAAgB,EAAE,MAAM,CAAC;IACzB,4DAA4D;IAC5D,cAAc,EAAE,OAAO,CAAC;CACzB,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,CACvC,GAAG,GAAE,MAAM,CAAC,UAAwB,GACnC,kBAAkB,CA0DpB;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;;;;;GAKG;AACH,wBAAgB,gBAAgB,CAAC,GAAG,GAAE,MAAM,CAAC,UAAwB,GAAG,MAAM,CAG7E;AAED,wBAAgB,YAAY,CAC1B,MAAM,EAAE,MAAM,EACd,aAAa,CAAC,EAAE,MAAM,EACtB,YAAY,CAAC,EAAE,MAAM,GACpB,MAAM,CAMR"}
|
package/dist/config.js
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
|
-
import * as fs from
|
|
2
|
-
import * as os from
|
|
3
|
-
import * as path from
|
|
1
|
+
import * as fs from 'node:fs';
|
|
2
|
+
import * as os from 'node:os';
|
|
3
|
+
import * as path from 'node:path';
|
|
4
4
|
function requireEnv(env, name) {
|
|
5
5
|
const value = env[name]?.trim();
|
|
6
6
|
if (!value) {
|
|
@@ -25,10 +25,10 @@ function parseMsAllowZero(value, fallback) {
|
|
|
25
25
|
return Number.isFinite(n) && n >= 0 ? n : fallback;
|
|
26
26
|
}
|
|
27
27
|
export function daemonConfigDir(env = process.env) {
|
|
28
|
-
return path.join(env.HOME || os.homedir(),
|
|
28
|
+
return path.join(env.HOME || os.homedir(), '.parall-daemon');
|
|
29
29
|
}
|
|
30
30
|
export function daemonConfigPath(env = process.env) {
|
|
31
|
-
return path.join(daemonConfigDir(env),
|
|
31
|
+
return path.join(daemonConfigDir(env), 'config.json');
|
|
32
32
|
}
|
|
33
33
|
function tryLoadConfigFile(env) {
|
|
34
34
|
const cfgPath = daemonConfigPath(env);
|
|
@@ -51,8 +51,8 @@ function tryLoadConfigFile(env) {
|
|
|
51
51
|
}
|
|
52
52
|
}
|
|
53
53
|
export function resolveClaudeDaemonConfig(env = process.env) {
|
|
54
|
-
let apiUrl = env.PRLL_API_URL?.trim() ||
|
|
55
|
-
let apiKey = env.PRLL_API_KEY?.trim() ||
|
|
54
|
+
let apiUrl = env.PRLL_API_URL?.trim() || '';
|
|
55
|
+
let apiKey = env.PRLL_API_KEY?.trim() || '';
|
|
56
56
|
// Fall back to config file for values not provided via env.
|
|
57
57
|
if (!apiUrl || !apiKey) {
|
|
58
58
|
const file = tryLoadConfigFile(env);
|
|
@@ -64,21 +64,21 @@ export function resolveClaudeDaemonConfig(env = process.env) {
|
|
|
64
64
|
}
|
|
65
65
|
}
|
|
66
66
|
if (!apiUrl)
|
|
67
|
-
throw new Error(
|
|
67
|
+
throw new Error('Missing required env var: PRLL_API_URL');
|
|
68
68
|
if (!apiKey)
|
|
69
|
-
throw new Error(
|
|
70
|
-
if (!apiKey.startsWith(
|
|
69
|
+
throw new Error('Missing required env var: PRLL_API_KEY');
|
|
70
|
+
if (!apiKey.startsWith('mck_')) {
|
|
71
71
|
// Fatal startup validation: the daemon must never run with an agent or
|
|
72
72
|
// human key because child launch credentials are minted from this bearer.
|
|
73
73
|
throw new Error(`PRLL_API_KEY does not look like a Machine bearer (expected prefix "mck_"). ` +
|
|
74
74
|
`Daemon mode requires a machine-scoped key issued via POST /machines/{id}/keys.`);
|
|
75
75
|
}
|
|
76
76
|
const rootClaudeHome = resolvePath(env.PRLL_CLAUDE_HOME?.trim() || env.HOME || os.homedir());
|
|
77
|
-
const rootStateDir = resolvePath(env.PRLL_CLAUDE_STATE_DIR?.trim() || path.join(rootClaudeHome,
|
|
77
|
+
const rootStateDir = resolvePath(env.PRLL_CLAUDE_STATE_DIR?.trim() || path.join(rootClaudeHome, '.parall-agent'));
|
|
78
78
|
return {
|
|
79
79
|
apiUrl,
|
|
80
80
|
apiKey,
|
|
81
|
-
agentBin: env.PRLL_CLAUDE_AGENT_BIN?.trim() ||
|
|
81
|
+
agentBin: env.PRLL_CLAUDE_AGENT_BIN?.trim() || 'parall-claude-agent',
|
|
82
82
|
rootStateDir,
|
|
83
83
|
rootClaudeHome,
|
|
84
84
|
wsUrl: env.PRLL_WS_URL?.trim() || undefined,
|
|
@@ -91,6 +91,12 @@ export function resolveClaudeDaemonConfig(env = process.env) {
|
|
|
91
91
|
bootstrapBackoffMaxMs: parseMs(env.PRLL_DAEMON_BOOTSTRAP_BACKOFF_MAX_MS, 60_000),
|
|
92
92
|
supervisorRestartBackoffMs: parseMsAllowZero(env.PRLL_DAEMON_SUPERVISOR_RESTART_BACKOFF_MS, 5_000),
|
|
93
93
|
supervisorRestartBackoffMaxMs: parseMs(env.PRLL_DAEMON_SUPERVISOR_RESTART_BACKOFF_MAX_MS, 5 * 60_000),
|
|
94
|
+
updateCdnUrl: env.PRLL_DAEMON_UPDATE_CDN_URL?.trim() ||
|
|
95
|
+
((env.PRLL_DAEMON_UPDATE_CHANNEL?.trim() ?? 'production') === 'staging'
|
|
96
|
+
? 'https://releases.staging.prll.sh/daemon/staging'
|
|
97
|
+
: 'https://releases.parall.com/daemon/production'),
|
|
98
|
+
updateIntervalMs: parseMsAllowZero(env.PRLL_DAEMON_UPDATE_INTERVAL_MS, 6 * 60 * 60_000),
|
|
99
|
+
updateDisabled: env.PRLL_DAEMON_UPDATE_DISABLED === 'true' || !!env.KUBERNETES_SERVICE_HOST,
|
|
94
100
|
};
|
|
95
101
|
}
|
|
96
102
|
function assertSafeAgentId(agentId) {
|
|
@@ -101,31 +107,42 @@ function assertSafeAgentId(agentId) {
|
|
|
101
107
|
}
|
|
102
108
|
/** Per-agent state dir under the shared host volume. */
|
|
103
109
|
export function agentStateDirFor(rootStateDir, agentId) {
|
|
104
|
-
return path.join(rootStateDir,
|
|
110
|
+
return path.join(rootStateDir, 'agents', assertSafeAgentId(agentId));
|
|
105
111
|
}
|
|
106
112
|
/** Per-agent HOME dir. Claude Code stores project/session state under
|
|
107
113
|
* `${HOME}/.claude`, so each agent gets its own HOME root while the daemon
|
|
108
114
|
* links shared OAuth credentials into that `.claude` directory. */
|
|
109
115
|
export function agentClaudeHomeFor(rootClaudeHome, agentId) {
|
|
110
|
-
return path.join(rootClaudeHome,
|
|
116
|
+
return path.join(rootClaudeHome, 'agents', assertSafeAgentId(agentId));
|
|
111
117
|
}
|
|
112
118
|
/** Shared Claude Code OAuth credential written by server-side runtime auth. */
|
|
113
119
|
export function sharedClaudeCredentialsFileFor(rootClaudeHome) {
|
|
114
|
-
return path.join(rootClaudeHome,
|
|
120
|
+
return path.join(rootClaudeHome, '.claude', '.credentials.json');
|
|
115
121
|
}
|
|
116
122
|
/** Per-agent credential location inside that agent's isolated HOME. */
|
|
117
123
|
export function agentClaudeCredentialsFileFor(agentClaudeHome) {
|
|
118
|
-
return path.join(agentClaudeHome,
|
|
124
|
+
return path.join(agentClaudeHome, '.claude', '.credentials.json');
|
|
119
125
|
}
|
|
120
126
|
/** Per-agent workspace dir where the agent runs git commands. */
|
|
121
127
|
export function agentWorkspaceDirFor(rootStateDir, agentId) {
|
|
122
|
-
return path.join(rootStateDir,
|
|
128
|
+
return path.join(rootStateDir, 'agents', assertSafeAgentId(agentId), 'workspace');
|
|
129
|
+
}
|
|
130
|
+
/**
|
|
131
|
+
* Resolve the bundle directory for self-update storage.
|
|
132
|
+
* Desktop: ~/Library/Application Support/Parall/daemon/ (set via env)
|
|
133
|
+
* Default: ~/.parall-daemon/bundle/ — always a writable overlay directory,
|
|
134
|
+
* never the npm global install dir (which may be root-owned or read-only).
|
|
135
|
+
*/
|
|
136
|
+
export function resolveBundleDir(env = process.env) {
|
|
137
|
+
if (env.PRLL_DAEMON_BUNDLE_DIR)
|
|
138
|
+
return resolvePath(env.PRLL_DAEMON_BUNDLE_DIR);
|
|
139
|
+
return path.join(daemonConfigDir(env), 'bundle');
|
|
123
140
|
}
|
|
124
141
|
export function resolveWsUrl(apiUrl, explicitWsUrl, swimlaneName) {
|
|
125
|
-
const base = explicitWsUrl || `${apiUrl.replace(/\/$/,
|
|
142
|
+
const base = explicitWsUrl || `${apiUrl.replace(/\/$/, '').replace(/^http/, 'ws')}/ws`;
|
|
126
143
|
if (!swimlaneName)
|
|
127
144
|
return base;
|
|
128
145
|
const url = new URL(base);
|
|
129
|
-
url.searchParams.set(
|
|
146
|
+
url.searchParams.set('swimlane', swimlaneName);
|
|
130
147
|
return url.toString();
|
|
131
148
|
}
|
package/dist/filesystem.d.ts
CHANGED
package/dist/filesystem.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"filesystem.d.ts","sourceRoot":"","sources":["../src/filesystem.ts"],"names":[],"mappings":"AAGA,OAAO,KAAK,EAAE,eAAe,EAAE,MAAM,aAAa,CAAC;AAqCnD,wBAAgB,gBAAgB,CAAC,KAAK,EAAE,MAAM,GAAG,MAAM,CAkBtD;
|
|
1
|
+
{"version":3,"file":"filesystem.d.ts","sourceRoot":"","sources":["../src/filesystem.ts"],"names":[],"mappings":"AAGA,OAAO,KAAK,EAAE,eAAe,EAAE,MAAM,aAAa,CAAC;AAqCnD,wBAAgB,gBAAgB,CAAC,KAAK,EAAE,MAAM,GAAG,MAAM,CAkBtD;AAkBD,wBAAsB,aAAa,CACjC,OAAO,EAAE,MAAM,GACd,OAAO,CAAC;IAAE,OAAO,EAAE,eAAe,EAAE,CAAC;IAAC,KAAK,CAAC,EAAE,MAAM,CAAA;CAAE,CAAC,CA8CzD"}
|