@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.
Files changed (53) hide show
  1. package/bundle/manifest.json +17 -11
  2. package/bundle/package.json +1 -0
  3. package/bundle/parall-claude-agent.js +27671 -337
  4. package/bundle/parall-codex-agent.js +27715 -375
  5. package/bundle/parall-daemon.js +30421 -1355
  6. package/bundle/parall-openclaw-agent.js +51 -26
  7. package/dist/cli.d.ts +1 -1
  8. package/dist/cli.d.ts.map +1 -1
  9. package/dist/cli.js +122 -72
  10. package/dist/clip-runtime/clip-installer.d.ts +44 -0
  11. package/dist/clip-runtime/clip-installer.d.ts.map +1 -0
  12. package/dist/clip-runtime/clip-installer.js +501 -0
  13. package/dist/clip-runtime/clip-provider.d.ts +76 -0
  14. package/dist/clip-runtime/clip-provider.d.ts.map +1 -0
  15. package/dist/clip-runtime/clip-provider.js +402 -0
  16. package/dist/clip-runtime/index.d.ts +7 -0
  17. package/dist/clip-runtime/index.d.ts.map +1 -0
  18. package/dist/clip-runtime/index.js +5 -0
  19. package/dist/clip-runtime/ipc.d.ts +94 -0
  20. package/dist/clip-runtime/ipc.d.ts.map +1 -0
  21. package/dist/clip-runtime/ipc.js +98 -0
  22. package/dist/clip-runtime/manifest.d.ts +74 -0
  23. package/dist/clip-runtime/manifest.d.ts.map +1 -0
  24. package/dist/clip-runtime/manifest.js +181 -0
  25. package/dist/clip-runtime/process-manager.d.ts +57 -0
  26. package/dist/clip-runtime/process-manager.d.ts.map +1 -0
  27. package/dist/clip-runtime/process-manager.js +354 -0
  28. package/dist/clip-runtime/process.d.ts +59 -0
  29. package/dist/clip-runtime/process.d.ts.map +1 -0
  30. package/dist/clip-runtime/process.js +350 -0
  31. package/dist/config.d.ts +13 -0
  32. package/dist/config.d.ts.map +1 -1
  33. package/dist/config.js +36 -19
  34. package/dist/filesystem.d.ts +1 -1
  35. package/dist/filesystem.d.ts.map +1 -1
  36. package/dist/filesystem.js +51 -53
  37. package/dist/index.js +46 -14
  38. package/dist/runtimes.d.ts +10 -8
  39. package/dist/runtimes.d.ts.map +1 -1
  40. package/dist/runtimes.js +63 -95
  41. package/dist/supervisor.d.ts +12 -3
  42. package/dist/supervisor.d.ts.map +1 -1
  43. package/dist/supervisor.js +272 -71
  44. package/dist/updater-manifest.d.ts +41 -0
  45. package/dist/updater-manifest.d.ts.map +1 -0
  46. package/dist/updater-manifest.js +94 -0
  47. package/dist/updater.d.ts +60 -0
  48. package/dist/updater.d.ts.map +1 -0
  49. package/dist/updater.js +427 -0
  50. package/dist/workspace.d.ts +2 -2
  51. package/dist/workspace.d.ts.map +1 -1
  52. package/dist/workspace.js +112 -112
  53. 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
@@ -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;CACvC,CAAC;AA2BF,wBAAgB,eAAe,CAAC,GAAG,GAAE,MAAM,CAAC,UAAwB,GAAG,MAAM,CAE5E;AAED,wBAAgB,gBAAgB,CAAC,GAAG,GAAE,MAAM,CAAC,UAAwB,GAAG,MAAM,CAE7E;AAyBD,wBAAgB,yBAAyB,CAAC,GAAG,GAAE,MAAM,CAAC,UAAwB,GAAG,kBAAkB,CA6ClG;AASD,wDAAwD;AACxD,wBAAgB,gBAAgB,CAAC,YAAY,EAAE,MAAM,EAAE,OAAO,EAAE,MAAM,GAAG,MAAM,CAE9E;AAED;;mEAEmE;AACnE,wBAAgB,kBAAkB,CAAC,cAAc,EAAE,MAAM,EAAE,OAAO,EAAE,MAAM,GAAG,MAAM,CAElF;AAED,+EAA+E;AAC/E,wBAAgB,8BAA8B,CAAC,cAAc,EAAE,MAAM,GAAG,MAAM,CAE7E;AAED,uEAAuE;AACvE,wBAAgB,6BAA6B,CAAC,eAAe,EAAE,MAAM,GAAG,MAAM,CAE7E;AAED,iEAAiE;AACjE,wBAAgB,oBAAoB,CAAC,YAAY,EAAE,MAAM,EAAE,OAAO,EAAE,MAAM,GAAG,MAAM,CAElF;AAED,wBAAgB,YAAY,CAAC,MAAM,EAAE,MAAM,EAAE,aAAa,CAAC,EAAE,MAAM,EAAE,YAAY,CAAC,EAAE,MAAM,GAAG,MAAM,CAMlG"}
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 "node:fs";
2
- import * as os from "node:os";
3
- import * as path from "node:path";
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(), ".parall-daemon");
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), "config.json");
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("Missing required env var: PRLL_API_URL");
67
+ throw new Error('Missing required env var: PRLL_API_URL');
68
68
  if (!apiKey)
69
- throw new Error("Missing required env var: PRLL_API_KEY");
70
- if (!apiKey.startsWith("mck_")) {
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, ".parall-agent"));
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() || "parall-claude-agent",
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, "agents", assertSafeAgentId(agentId));
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, "agents", assertSafeAgentId(agentId));
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, ".claude", ".credentials.json");
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, ".claude", ".credentials.json");
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, "agents", assertSafeAgentId(agentId), "workspace");
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(/\/$/, "").replace(/^http/, "ws")}/ws`;
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("swimlane", swimlaneName);
146
+ url.searchParams.set('swimlane', swimlaneName);
130
147
  return url.toString();
131
148
  }
@@ -1,4 +1,4 @@
1
- import type { FilesystemEntry } from "@parall/sdk";
1
+ import type { FilesystemEntry } from '@parall/sdk';
2
2
  export declare function browseDenyReason(value: string): string;
3
3
  export declare function listDirectory(dirPath: string): Promise<{
4
4
  entries: FilesystemEntry[];
@@ -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;AAoBD,wBAAsB,aAAa,CAAC,OAAO,EAAE,MAAM,GAAG,OAAO,CAAC;IAAE,OAAO,EAAE,eAAe,EAAE,CAAC;IAAC,KAAK,CAAC,EAAE,MAAM,CAAA;CAAE,CAAC,CA8C5G"}
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"}