@parall/daemon 1.31.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 (52) hide show
  1. package/bundle/manifest.json +16 -11
  2. package/bundle/package.json +1 -0
  3. package/bundle/parall-claude-agent.js +27556 -339
  4. package/bundle/parall-codex-agent.js +27603 -372
  5. package/bundle/parall-daemon.js +30204 -1760
  6. package/bundle/parall-openclaw-agent.js +49 -24
  7. package/dist/cli.d.ts +1 -1
  8. package/dist/cli.d.ts.map +1 -1
  9. package/dist/cli.js +83 -83
  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.map +1 -1
  32. package/dist/config.js +24 -24
  33. package/dist/filesystem.d.ts +1 -1
  34. package/dist/filesystem.d.ts.map +1 -1
  35. package/dist/filesystem.js +51 -53
  36. package/dist/index.js +20 -15
  37. package/dist/runtimes.d.ts +3 -2
  38. package/dist/runtimes.d.ts.map +1 -1
  39. package/dist/runtimes.js +24 -20
  40. package/dist/supervisor.d.ts +9 -4
  41. package/dist/supervisor.d.ts.map +1 -1
  42. package/dist/supervisor.js +244 -76
  43. package/dist/updater-manifest.d.ts +2 -0
  44. package/dist/updater-manifest.d.ts.map +1 -1
  45. package/dist/updater-manifest.js +6 -6
  46. package/dist/updater.d.ts +2 -2
  47. package/dist/updater.d.ts.map +1 -1
  48. package/dist/updater.js +55 -37
  49. package/dist/workspace.d.ts +2 -2
  50. package/dist/workspace.d.ts.map +1 -1
  51. package/dist/workspace.js +112 -112
  52. 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
+ }
@@ -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;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,CAAC,GAAG,GAAE,MAAM,CAAC,UAAwB,GAAG,kBAAkB,CAmDlG;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,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,
@@ -92,11 +92,11 @@ export function resolveClaudeDaemonConfig(env = process.env) {
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
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"),
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
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,
99
+ updateDisabled: env.PRLL_DAEMON_UPDATE_DISABLED === 'true' || !!env.KUBERNETES_SERVICE_HOST,
100
100
  };
101
101
  }
102
102
  function assertSafeAgentId(agentId) {
@@ -107,25 +107,25 @@ function assertSafeAgentId(agentId) {
107
107
  }
108
108
  /** Per-agent state dir under the shared host volume. */
109
109
  export function agentStateDirFor(rootStateDir, agentId) {
110
- return path.join(rootStateDir, "agents", assertSafeAgentId(agentId));
110
+ return path.join(rootStateDir, 'agents', assertSafeAgentId(agentId));
111
111
  }
112
112
  /** Per-agent HOME dir. Claude Code stores project/session state under
113
113
  * `${HOME}/.claude`, so each agent gets its own HOME root while the daemon
114
114
  * links shared OAuth credentials into that `.claude` directory. */
115
115
  export function agentClaudeHomeFor(rootClaudeHome, agentId) {
116
- return path.join(rootClaudeHome, "agents", assertSafeAgentId(agentId));
116
+ return path.join(rootClaudeHome, 'agents', assertSafeAgentId(agentId));
117
117
  }
118
118
  /** Shared Claude Code OAuth credential written by server-side runtime auth. */
119
119
  export function sharedClaudeCredentialsFileFor(rootClaudeHome) {
120
- return path.join(rootClaudeHome, ".claude", ".credentials.json");
120
+ return path.join(rootClaudeHome, '.claude', '.credentials.json');
121
121
  }
122
122
  /** Per-agent credential location inside that agent's isolated HOME. */
123
123
  export function agentClaudeCredentialsFileFor(agentClaudeHome) {
124
- return path.join(agentClaudeHome, ".claude", ".credentials.json");
124
+ return path.join(agentClaudeHome, '.claude', '.credentials.json');
125
125
  }
126
126
  /** Per-agent workspace dir where the agent runs git commands. */
127
127
  export function agentWorkspaceDirFor(rootStateDir, agentId) {
128
- return path.join(rootStateDir, "agents", assertSafeAgentId(agentId), "workspace");
128
+ return path.join(rootStateDir, 'agents', assertSafeAgentId(agentId), 'workspace');
129
129
  }
130
130
  /**
131
131
  * Resolve the bundle directory for self-update storage.
@@ -136,13 +136,13 @@ export function agentWorkspaceDirFor(rootStateDir, agentId) {
136
136
  export function resolveBundleDir(env = process.env) {
137
137
  if (env.PRLL_DAEMON_BUNDLE_DIR)
138
138
  return resolvePath(env.PRLL_DAEMON_BUNDLE_DIR);
139
- return path.join(daemonConfigDir(env), "bundle");
139
+ return path.join(daemonConfigDir(env), 'bundle');
140
140
  }
141
141
  export function resolveWsUrl(apiUrl, explicitWsUrl, swimlaneName) {
142
- const base = explicitWsUrl || `${apiUrl.replace(/\/$/, "").replace(/^http/, "ws")}/ws`;
142
+ const base = explicitWsUrl || `${apiUrl.replace(/\/$/, '').replace(/^http/, 'ws')}/ws`;
143
143
  if (!swimlaneName)
144
144
  return base;
145
145
  const url = new URL(base);
146
- url.searchParams.set("swimlane", swimlaneName);
146
+ url.searchParams.set('swimlane', swimlaneName);
147
147
  return url.toString();
148
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"}
@@ -1,65 +1,63 @@
1
- import * as fs from "fs";
2
- import * as path from "path";
3
- import * as os from "os";
1
+ import * as fs from 'fs';
2
+ import * as path from 'path';
3
+ import * as os from 'os';
4
4
  const MAX_ENTRIES = 200;
5
5
  const SYSTEM_DIR_PREFIXES = [
6
- "/Applications",
7
- "/bin",
8
- "/boot",
9
- "/dev",
10
- "/etc",
11
- "/Library",
12
- "/private",
13
- "/proc",
14
- "/root",
15
- "/run",
16
- "/sbin",
17
- "/System",
18
- "/sys",
19
- "/usr",
20
- "/var",
6
+ '/Applications',
7
+ '/bin',
8
+ '/boot',
9
+ '/dev',
10
+ '/etc',
11
+ '/Library',
12
+ '/private',
13
+ '/proc',
14
+ '/root',
15
+ '/run',
16
+ '/sbin',
17
+ '/System',
18
+ '/sys',
19
+ '/usr',
20
+ '/var',
21
21
  ];
22
22
  const CREDENTIAL_DIR_NAMES = new Set([
23
- ".aws",
24
- ".azure",
25
- ".claude",
26
- ".codex",
27
- ".config",
28
- ".docker",
29
- ".gnupg",
30
- ".kube",
31
- ".npm",
32
- ".ssh",
33
- ".parall-agent",
34
- ".parall-daemon",
23
+ '.aws',
24
+ '.azure',
25
+ '.claude',
26
+ '.codex',
27
+ '.config',
28
+ '.docker',
29
+ '.gnupg',
30
+ '.kube',
31
+ '.npm',
32
+ '.ssh',
33
+ '.parall-agent',
34
+ '.parall-daemon',
35
35
  ]);
36
36
  export function browseDenyReason(value) {
37
- const normalized = path.resolve(value).split(path.sep).join("/");
38
- if (normalized === "/")
39
- return "";
37
+ const normalized = path.resolve(value).split(path.sep).join('/');
38
+ if (normalized === '/')
39
+ return '';
40
40
  for (const prefix of SYSTEM_DIR_PREFIXES) {
41
41
  if (normalized === prefix || normalized.startsWith(`${prefix}/`)) {
42
- return "a system directory";
42
+ return 'a system directory';
43
43
  }
44
44
  }
45
- const parts = normalized.split("/").filter(Boolean);
45
+ const parts = normalized.split('/').filter(Boolean);
46
46
  for (const part of parts) {
47
47
  if (CREDENTIAL_DIR_NAMES.has(part)) {
48
- return "a credential or application state directory";
48
+ return 'a credential or application state directory';
49
49
  }
50
50
  }
51
- return "";
51
+ return '';
52
52
  }
53
53
  function syntheticRoots() {
54
54
  const roots = [];
55
55
  const platform = os.platform();
56
- const candidates = platform === "darwin"
57
- ? ["/Users", os.homedir()]
58
- : ["/home", os.homedir()];
56
+ const candidates = platform === 'darwin' ? ['/Users', os.homedir()] : ['/home', os.homedir()];
59
57
  for (const dir of [...new Set(candidates)]) {
60
58
  try {
61
59
  fs.accessSync(dir, fs.constants.R_OK);
62
- roots.push({ name: dir, type: "dir" });
60
+ roots.push({ name: dir, type: 'dir' });
63
61
  }
64
62
  catch {
65
63
  // not accessible
@@ -69,8 +67,8 @@ function syntheticRoots() {
69
67
  }
70
68
  export async function listDirectory(dirPath) {
71
69
  const resolved = path.resolve(dirPath);
72
- const normalized = resolved.split(path.sep).join("/");
73
- if (normalized === "/") {
70
+ const normalized = resolved.split(path.sep).join('/');
71
+ if (normalized === '/') {
74
72
  return { entries: syntheticRoots() };
75
73
  }
76
74
  const deny = browseDenyReason(normalized);
@@ -83,11 +81,11 @@ export async function listDirectory(dirPath) {
83
81
  }
84
82
  catch (err) {
85
83
  const code = err.code;
86
- if (code === "ENOENT")
87
- return { entries: [], error: "Directory not found" };
88
- return { entries: [], error: "Permission denied" };
84
+ if (code === 'ENOENT')
85
+ return { entries: [], error: 'Directory not found' };
86
+ return { entries: [], error: 'Permission denied' };
89
87
  }
90
- const realDeny = browseDenyReason(realPath.split(path.sep).join("/"));
88
+ const realDeny = browseDenyReason(realPath.split(path.sep).join('/'));
91
89
  if (realDeny) {
92
90
  return { entries: [], error: `Access denied: ${realDeny}` };
93
91
  }
@@ -97,19 +95,19 @@ export async function listDirectory(dirPath) {
97
95
  }
98
96
  catch (err) {
99
97
  const code = err.code;
100
- if (code === "ENOENT")
101
- return { entries: [], error: "Directory not found" };
102
- if (code === "EACCES" || code === "EPERM")
103
- return { entries: [], error: "Permission denied" };
98
+ if (code === 'ENOENT')
99
+ return { entries: [], error: 'Directory not found' };
100
+ if (code === 'EACCES' || code === 'EPERM')
101
+ return { entries: [], error: 'Permission denied' };
104
102
  return { entries: [], error: `Failed to read directory: ${code ?? String(err)}` };
105
103
  }
106
104
  const entries = [];
107
105
  for (const d of dirents) {
108
106
  if (!d.isDirectory())
109
107
  continue;
110
- if (d.name.startsWith("."))
108
+ if (d.name.startsWith('.'))
111
109
  continue;
112
- entries.push({ name: d.name, type: "dir" });
110
+ entries.push({ name: d.name, type: 'dir' });
113
111
  if (entries.length >= MAX_ENTRIES)
114
112
  break;
115
113
  }