@parall/daemon 1.32.0 → 1.32.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/bundle/manifest.json +9 -9
- package/bundle/parall-claude-agent.js +54 -36
- package/bundle/parall-codex-agent.js +54 -36
- package/bundle/parall-daemon.js +319 -251
- package/dist/clip-runtime/bun-resolver.d.ts +24 -0
- package/dist/clip-runtime/bun-resolver.d.ts.map +1 -0
- package/dist/clip-runtime/bun-resolver.js +58 -0
- package/dist/clip-runtime/clip-installer.d.ts.map +1 -1
- package/dist/clip-runtime/clip-installer.js +59 -18
- package/dist/clip-runtime/clip-provider.d.ts +3 -1
- package/dist/clip-runtime/clip-provider.d.ts.map +1 -1
- package/dist/clip-runtime/clip-provider.js +50 -22
- package/dist/clip-runtime/process-manager.d.ts.map +1 -1
- package/dist/clip-runtime/process-manager.js +8 -29
- package/package.json +6 -6
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Bun binary resolution — shared by the clip process manager (which spawns
|
|
3
|
+
* `bun run <entry> --ipc`) and the clip installer (which runs `bun install`).
|
|
4
|
+
*
|
|
5
|
+
* Resolution order, embedded-first:
|
|
6
|
+
* 1. Embedded bun shipped alongside the daemon's node binary. In a packaged
|
|
7
|
+
* desktop app the daemon is launched via `Frameworks/parall-node`, so
|
|
8
|
+
* `dirname(process.execPath)` is the `Frameworks/` dir and the embedded
|
|
9
|
+
* bun sits right beside it. electron-builder stages it under the CANONICAL
|
|
10
|
+
* name `Frameworks/bun` (node stays `parall-node` since the daemon only
|
|
11
|
+
* invokes node by absolute path, whereas bun must also be discoverable by
|
|
12
|
+
* name on the child PATH for clip postinstall hooks / clips that shell out
|
|
13
|
+
* to `bun`). This is the reliable path for users who never installed Bun.
|
|
14
|
+
* 2. User-level installs (`~/.bun/bin`, Homebrew) — for dev / CLI daemons.
|
|
15
|
+
* 3. PATH lookup (`which` / `where.exe`).
|
|
16
|
+
*
|
|
17
|
+
* A non-packaged or npm-CLI daemon runs from the system node, so the sibling
|
|
18
|
+
* `bun` does not exist and resolution falls through to the install candidates.
|
|
19
|
+
* The launchd-managed daemon inherits a minimal PATH
|
|
20
|
+
* (`/usr/bin:/bin:/usr/sbin:/sbin`) that contains neither `bun` nor `npm`,
|
|
21
|
+
* which is exactly why resolving an absolute path (not relying on PATH) matters.
|
|
22
|
+
*/
|
|
23
|
+
export declare function findBunBinary(): string;
|
|
24
|
+
//# sourceMappingURL=bun-resolver.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"bun-resolver.d.ts","sourceRoot":"","sources":["../../src/clip-runtime/bun-resolver.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;GAqBG;AAMH,wBAAgB,aAAa,IAAI,MAAM,CAgCtC"}
|
|
@@ -0,0 +1,58 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Bun binary resolution — shared by the clip process manager (which spawns
|
|
3
|
+
* `bun run <entry> --ipc`) and the clip installer (which runs `bun install`).
|
|
4
|
+
*
|
|
5
|
+
* Resolution order, embedded-first:
|
|
6
|
+
* 1. Embedded bun shipped alongside the daemon's node binary. In a packaged
|
|
7
|
+
* desktop app the daemon is launched via `Frameworks/parall-node`, so
|
|
8
|
+
* `dirname(process.execPath)` is the `Frameworks/` dir and the embedded
|
|
9
|
+
* bun sits right beside it. electron-builder stages it under the CANONICAL
|
|
10
|
+
* name `Frameworks/bun` (node stays `parall-node` since the daemon only
|
|
11
|
+
* invokes node by absolute path, whereas bun must also be discoverable by
|
|
12
|
+
* name on the child PATH for clip postinstall hooks / clips that shell out
|
|
13
|
+
* to `bun`). This is the reliable path for users who never installed Bun.
|
|
14
|
+
* 2. User-level installs (`~/.bun/bin`, Homebrew) — for dev / CLI daemons.
|
|
15
|
+
* 3. PATH lookup (`which` / `where.exe`).
|
|
16
|
+
*
|
|
17
|
+
* A non-packaged or npm-CLI daemon runs from the system node, so the sibling
|
|
18
|
+
* `bun` does not exist and resolution falls through to the install candidates.
|
|
19
|
+
* The launchd-managed daemon inherits a minimal PATH
|
|
20
|
+
* (`/usr/bin:/bin:/usr/sbin:/sbin`) that contains neither `bun` nor `npm`,
|
|
21
|
+
* which is exactly why resolving an absolute path (not relying on PATH) matters.
|
|
22
|
+
*/
|
|
23
|
+
import * as fs from 'node:fs';
|
|
24
|
+
import * as path from 'node:path';
|
|
25
|
+
import { execFileSync } from 'node:child_process';
|
|
26
|
+
export function findBunBinary() {
|
|
27
|
+
const isWindows = process.platform === 'win32';
|
|
28
|
+
const binName = isWindows ? 'bun.exe' : 'bun';
|
|
29
|
+
// 1. Embedded bun — sibling of the daemon's node binary in a packaged app
|
|
30
|
+
// (staged as parall-bun-${arch}, landed as Frameworks/bun).
|
|
31
|
+
const embedded = path.join(path.dirname(process.execPath), binName);
|
|
32
|
+
if (fs.existsSync(embedded))
|
|
33
|
+
return embedded;
|
|
34
|
+
if (!isWindows) {
|
|
35
|
+
const candidates = [
|
|
36
|
+
path.join(process.env.HOME || '', '.bun', 'bin', 'bun'),
|
|
37
|
+
'/usr/local/bin/bun',
|
|
38
|
+
'/opt/homebrew/bin/bun',
|
|
39
|
+
];
|
|
40
|
+
for (const candidate of candidates) {
|
|
41
|
+
if (fs.existsSync(candidate))
|
|
42
|
+
return candidate;
|
|
43
|
+
}
|
|
44
|
+
}
|
|
45
|
+
// PATH lookup — platform-aware
|
|
46
|
+
const lookupCmd = isWindows ? 'where.exe' : 'which';
|
|
47
|
+
const lookupArg = isWindows ? 'bun.exe' : 'bun';
|
|
48
|
+
try {
|
|
49
|
+
const result = execFileSync(lookupCmd, [lookupArg], { encoding: 'utf-8' }).trim();
|
|
50
|
+
const firstLine = result.split('\n')[0]?.trim();
|
|
51
|
+
if (firstLine)
|
|
52
|
+
return firstLine;
|
|
53
|
+
}
|
|
54
|
+
catch {
|
|
55
|
+
// not found in PATH
|
|
56
|
+
}
|
|
57
|
+
throw new Error('bun binary not found — install Bun (https://bun.sh) or set bunPath option');
|
|
58
|
+
}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"clip-installer.d.ts","sourceRoot":"","sources":["../../src/clip-runtime/clip-installer.ts"],"names":[],"mappings":"AAAA;;;;;;;GAOG;
|
|
1
|
+
{"version":3,"file":"clip-installer.d.ts","sourceRoot":"","sources":["../../src/clip-runtime/clip-installer.ts"],"names":[],"mappings":"AAAA;;;;;;;GAOG;AAeH,MAAM,WAAW,cAAc;IAC7B,iEAAiE;IACjE,MAAM,EAAE,MAAM,CAAC;IACf,0DAA0D;IAC1D,KAAK,CAAC,EAAE,MAAM,CAAC;IACf,sCAAsC;IACtC,QAAQ,EAAE,MAAM,CAAC;IACjB,8HAA8H;IAC9H,WAAW,CAAC,EAAE,MAAM,CAAC;CACtB;AAED,MAAM,WAAW,aAAa;IAC5B,KAAK,EAAE,MAAM,CAAC;IACd,IAAI,EAAE,MAAM,CAAC;IACb,OAAO,EAAE,MAAM,CAAC;IAChB,4CAA4C;IAC5C,IAAI,EAAE,MAAM,CAAC;CACd;AAMD,UAAU,YAAY;IACpB,KAAK,EAAE,MAAM,GAAG,IAAI,CAAC;IACrB,IAAI,EAAE,MAAM,CAAC;IACb,oEAAoE;IACpE,QAAQ,EAAE,MAAM,CAAC;IACjB,OAAO,EAAE,MAAM,GAAG,IAAI,CAAC;CACxB;AAcD;;;;;;GAMG;AACH,wBAAgB,WAAW,CAAC,MAAM,EAAE,MAAM,GAAG,YAAY,CAgCxD;AAyeD,wBAAsB,WAAW,CAAC,IAAI,EAAE,cAAc,GAAG,OAAO,CAAC,aAAa,CAAC,CAiE9E;AAED,wBAAsB,UAAU,CAAC,QAAQ,EAAE,MAAM,EAAE,KAAK,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC,CAM/E"}
|
|
@@ -11,6 +11,7 @@ import * as path from 'node:path';
|
|
|
11
11
|
import * as crypto from 'node:crypto';
|
|
12
12
|
import { execFileSync } from 'node:child_process';
|
|
13
13
|
import { pipeline } from 'node:stream/promises';
|
|
14
|
+
import { findBunBinary } from './bun-resolver.js';
|
|
14
15
|
const DEFAULT_REGISTRY_URL = 'https://api.pinixai.com';
|
|
15
16
|
// ---------------------------------------------------------------------------
|
|
16
17
|
// Source parsing
|
|
@@ -406,28 +407,68 @@ function installDeps(clipDir) {
|
|
|
406
407
|
catch {
|
|
407
408
|
/* ignore parse error, try install anyway */
|
|
408
409
|
}
|
|
409
|
-
|
|
410
|
-
|
|
411
|
-
|
|
412
|
-
|
|
413
|
-
|
|
414
|
-
|
|
415
|
-
|
|
416
|
-
|
|
417
|
-
|
|
418
|
-
|
|
419
|
-
|
|
420
|
-
|
|
421
|
-
|
|
422
|
-
|
|
423
|
-
|
|
424
|
-
|
|
410
|
+
// Reasons each attempt failed — surfaced in the thrown error so a clip that
|
|
411
|
+
// genuinely needs deps reports *why* installation failed, not just "no
|
|
412
|
+
// package manager succeeded".
|
|
413
|
+
const failures = [];
|
|
414
|
+
// Bun is the reliable path. Resolve it the same way the process manager
|
|
415
|
+
// resolves the runtime bun (embedded-first, then ~/.bun / Homebrew / PATH) —
|
|
416
|
+
// a bare `bun` would not be found under the daemon's minimal launchd PATH
|
|
417
|
+
// (/usr/bin:/bin:/usr/sbin:/sbin). We invoke bun by absolute path below, and
|
|
418
|
+
// also prepend its directory to the child PATH so a dependency's postinstall
|
|
419
|
+
// hook that shells out to `bun` by name resolves it. The embedded binary is
|
|
420
|
+
// landed under the canonical name `bun` (Frameworks/bun) precisely so this
|
|
421
|
+
// by-name lookup works — a `parall-bun` filename would defeat it.
|
|
422
|
+
let bunPath = null;
|
|
423
|
+
try {
|
|
424
|
+
bunPath = findBunBinary();
|
|
425
|
+
}
|
|
426
|
+
catch (err) {
|
|
427
|
+
failures.push(`resolve bun: ${err instanceof Error ? err.message : String(err)}`);
|
|
428
|
+
}
|
|
429
|
+
if (bunPath) {
|
|
430
|
+
const bunEnv = {
|
|
431
|
+
...process.env,
|
|
432
|
+
PATH: `${path.dirname(bunPath)}${path.delimiter}${process.env.PATH ?? ''}`,
|
|
433
|
+
};
|
|
434
|
+
// Prefer a reproducible install when a lockfile is present, then fall back
|
|
435
|
+
// to a plain install (no lockfile / lockfile drift).
|
|
436
|
+
for (const args of [['install', '--frozen-lockfile'], ['install']]) {
|
|
437
|
+
try {
|
|
438
|
+
execFileSync(bunPath, args, {
|
|
439
|
+
cwd: clipDir,
|
|
440
|
+
stdio: 'pipe',
|
|
441
|
+
timeout: 120_000,
|
|
442
|
+
env: bunEnv,
|
|
443
|
+
});
|
|
444
|
+
return;
|
|
445
|
+
}
|
|
446
|
+
catch (err) {
|
|
447
|
+
failures.push(`bun ${args.join(' ')}: ${err instanceof Error ? err.message : String(err)}`);
|
|
448
|
+
}
|
|
425
449
|
}
|
|
426
450
|
}
|
|
451
|
+
// Best-effort npm fallback. The packaged desktop app embeds bun but NOT npm,
|
|
452
|
+
// so this only succeeds on dev/CLI hosts that already have npm on PATH; it is
|
|
453
|
+
// never the reliable path. Kept so a developer running the bare daemon with
|
|
454
|
+
// npm-but-no-bun still resolves dependencies.
|
|
455
|
+
try {
|
|
456
|
+
execFileSync('npm', ['install', '--production'], {
|
|
457
|
+
cwd: clipDir,
|
|
458
|
+
stdio: 'pipe',
|
|
459
|
+
timeout: 120_000,
|
|
460
|
+
});
|
|
461
|
+
return;
|
|
462
|
+
}
|
|
463
|
+
catch (err) {
|
|
464
|
+
failures.push(`npm install: ${err instanceof Error ? err.message : String(err)}`);
|
|
465
|
+
}
|
|
427
466
|
if (hasDeps) {
|
|
428
|
-
throw new Error(`failed to install dependencies in ${clipDir} — clip declares dependencies but no
|
|
467
|
+
throw new Error(`failed to install dependencies in ${clipDir} — clip declares dependencies but no ` +
|
|
468
|
+
`package manager succeeded:\n ${failures.join('\n ')}`);
|
|
429
469
|
}
|
|
430
|
-
console.warn(`[clip-installer] could not run package manager in ${clipDir}
|
|
470
|
+
console.warn(`[clip-installer] could not run a package manager in ${clipDir} ` +
|
|
471
|
+
`(no dependencies declared, continuing): ${failures.join('; ')}`);
|
|
431
472
|
}
|
|
432
473
|
// ---------------------------------------------------------------------------
|
|
433
474
|
// Public API
|
|
@@ -19,7 +19,7 @@ export interface ClipProviderOptions {
|
|
|
19
19
|
serviceUrl: string;
|
|
20
20
|
/** mck_xxx machine key for authorization. */
|
|
21
21
|
authKey: string;
|
|
22
|
-
/**
|
|
22
|
+
/** Machine org, used for local logging; Hub derives org from auth. */
|
|
23
23
|
orgId: string;
|
|
24
24
|
/** Unique provider name (typically machine ID). */
|
|
25
25
|
providerName: string;
|
|
@@ -72,5 +72,7 @@ export declare class ClipProvider {
|
|
|
72
72
|
private buildClipRegistrations;
|
|
73
73
|
private clipConfigToRegistration;
|
|
74
74
|
private commandDetailToInfo;
|
|
75
|
+
private dependencyNames;
|
|
76
|
+
private stringEntities;
|
|
75
77
|
}
|
|
76
78
|
//# sourceMappingURL=clip-provider.d.ts.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"clip-provider.d.ts","sourceRoot":"","sources":["../../src/clip-runtime/clip-provider.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;GAcG;AAGH,OAAO,KAAK,EAAE,kBAAkB,EAAE,MAAM,sBAAsB,CAAC;
|
|
1
|
+
{"version":3,"file":"clip-provider.d.ts","sourceRoot":"","sources":["../../src/clip-runtime/clip-provider.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;GAcG;AAGH,OAAO,KAAK,EAAE,kBAAkB,EAAE,MAAM,sBAAsB,CAAC;AA0I/D,MAAM,WAAW,mBAAmB;IAClC,sDAAsD;IACtD,UAAU,EAAE,MAAM,CAAC;IACnB,6CAA6C;IAC7C,OAAO,EAAE,MAAM,CAAC;IAChB,sEAAsE;IACtE,KAAK,EAAE,MAAM,CAAC;IACd,mDAAmD;IACnD,YAAY,EAAE,MAAM,CAAC;IACrB,yDAAyD;IACzD,WAAW,EAAE,kBAAkB,CAAC;IAChC,mBAAmB;IACnB,GAAG,EAAE;QAAE,IAAI,CAAC,GAAG,EAAE,MAAM,GAAG,IAAI,CAAC;QAAC,IAAI,CAAC,GAAG,EAAE,MAAM,GAAG,IAAI,CAAC;QAAC,KAAK,CAAC,GAAG,EAAE,MAAM,GAAG,IAAI,CAAA;KAAE,CAAC;CACrF;AAED,qBAAa,YAAY;IAiBX,OAAO,CAAC,QAAQ,CAAC,IAAI;IAhBjC,OAAO,CAAC,OAAO,CAAyC;IACxD,OAAO,CAAC,MAAM,CAAwC;IACtD,OAAO,CAAC,OAAO,CAAS;IACxB,OAAO,CAAC,SAAS,CAAS;IAC1B,OAAO,CAAC,gBAAgB,CAAK;IAC7B,OAAO,CAAC,cAAc,CAA+B;IACrD,OAAO,CAAC,cAAc,CAA+B;IACrD,OAAO,CAAC,iBAAiB,CAA6B;IACtD,OAAO,CAAC,mBAAmB,CAA6B;IACxD,OAAO,CAAC,eAAe,CAAS;IAChC,OAAO,CAAC,gBAAgB,CAAS;IAEjC,OAAO,CAAC,MAAM,CAAC,iBAAiB,CAAS;IACzC,OAAO,CAAC,MAAM,CAAC,gBAAgB,CAAU;IACzC,OAAO,CAAC,MAAM,CAAC,qBAAqB,CAAU;gBAEjB,IAAI,EAAE,mBAAmB;IAEtD,mFAAmF;IAC7E,OAAO,IAAI,OAAO,CAAC,IAAI,CAAC;IAS9B,wCAAwC;IAClC,UAAU,IAAI,OAAO,CAAC,IAAI,CAAC;IAUjC,WAAW,IAAI,OAAO;YAQR,UAAU;IAoExB,OAAO,CAAC,gBAAgB;IAQxB,OAAO,CAAC,iBAAiB;IAiBzB,OAAO,CAAC,mBAAmB;IAO3B,OAAO,CAAC,mBAAmB;IAO3B,OAAO,CAAC,cAAc;IAyBtB,OAAO,CAAC,WAAW;IAWnB,OAAO,CAAC,YAAY;IAepB,OAAO,CAAC,sBAAsB;IAyB9B,OAAO,CAAC,wBAAwB;IAWhC,OAAO,CAAC,gBAAgB;IAaxB,OAAO,CAAC,gBAAgB;YAeV,mBAAmB;YAyDnB,YAAY;YAUZ,mBAAmB;IAiBjC,OAAO,CAAC,sBAAsB;IAK9B,OAAO,CAAC,wBAAwB;IAuBhC,OAAO,CAAC,mBAAmB;IAS3B,OAAO,CAAC,eAAe;IAUvB,OAAO,CAAC,cAAc;CAUvB"}
|
|
@@ -149,7 +149,7 @@ export class ClipProvider {
|
|
|
149
149
|
this.stream.on('close', () => {
|
|
150
150
|
this.handleDisconnect();
|
|
151
151
|
});
|
|
152
|
-
// Send
|
|
152
|
+
// Send RegisterRequest as the first message.
|
|
153
153
|
await this.sendRegister();
|
|
154
154
|
}
|
|
155
155
|
catch (err) {
|
|
@@ -208,7 +208,7 @@ export class ClipProvider {
|
|
|
208
208
|
void this.openStream();
|
|
209
209
|
return;
|
|
210
210
|
}
|
|
211
|
-
this.sendProviderMessage({
|
|
211
|
+
this.sendProviderMessage({ ping: { sentAtUnixMs: Date.now() } }).catch((err) => {
|
|
212
212
|
this.opts.log.warn(`[clip-provider] heartbeat send failed: ${String(err)}`);
|
|
213
213
|
});
|
|
214
214
|
}, ClipProvider.HEARTBEAT_INTERVAL_MS);
|
|
@@ -242,16 +242,16 @@ export class ClipProvider {
|
|
|
242
242
|
subscribeStatusChanges() {
|
|
243
243
|
const mgr = this.opts.clipManager;
|
|
244
244
|
// Forward clip status changes to the Hub
|
|
245
|
-
this.statusUnsubscribe = mgr.addStatusListener((name, status,
|
|
245
|
+
this.statusUnsubscribe = mgr.addStatusListener((name, status, message) => {
|
|
246
246
|
if (!this.connected)
|
|
247
247
|
return;
|
|
248
248
|
const protoStatus = status === 'running'
|
|
249
|
-
? '
|
|
249
|
+
? 'CLIP_STATUS_RUNNING'
|
|
250
250
|
: status === 'error'
|
|
251
|
-
? '
|
|
252
|
-
: '
|
|
251
|
+
? 'CLIP_STATUS_ERROR'
|
|
252
|
+
: 'CLIP_STATUS_SLEEPING';
|
|
253
253
|
this.sendProviderMessage({
|
|
254
|
-
clipStatusChanged: {
|
|
254
|
+
clipStatusChanged: { name, status: protoStatus, message },
|
|
255
255
|
}).catch((err) => {
|
|
256
256
|
this.opts.log.warn(`[clip-provider] status change send failed: ${String(err)}`);
|
|
257
257
|
});
|
|
@@ -271,20 +271,22 @@ export class ClipProvider {
|
|
|
271
271
|
// Message handling
|
|
272
272
|
// ---------------------------------------------------------------------------
|
|
273
273
|
handleHubMessage(msg) {
|
|
274
|
-
if (msg.
|
|
275
|
-
this.handleRegistered(msg.
|
|
274
|
+
if (msg.registerResponse) {
|
|
275
|
+
this.handleRegistered(msg.registerResponse);
|
|
276
276
|
}
|
|
277
277
|
else if (msg.invokeCommand) {
|
|
278
278
|
void this.handleInvokeCommand(msg.invokeCommand);
|
|
279
279
|
}
|
|
280
|
-
else if (msg.
|
|
281
|
-
//
|
|
282
|
-
|
|
280
|
+
else if (msg.pong !== undefined) {
|
|
281
|
+
// Pong is the hub's keepalive ack. Do NOT echo a ping here — the
|
|
282
|
+
// heartbeat timer (startHeartbeat) is the sole ping driver. Replying to
|
|
283
|
+
// every pong with a ping turns the 30s interval into an unbounded
|
|
284
|
+
// ping↔pong loop the moment the first timer tick fires.
|
|
283
285
|
}
|
|
284
286
|
}
|
|
285
287
|
handleRegistered(reg) {
|
|
286
288
|
if (reg.accepted) {
|
|
287
|
-
this.opts.log.info(`[clip-provider] registered with hub (org=${
|
|
289
|
+
this.opts.log.info(`[clip-provider] registered with hub (org=${this.opts.orgId})`);
|
|
288
290
|
this.connected = true;
|
|
289
291
|
this.reconnectAttempt = 0;
|
|
290
292
|
this.startHeartbeat();
|
|
@@ -298,7 +300,7 @@ export class ClipProvider {
|
|
|
298
300
|
}
|
|
299
301
|
}
|
|
300
302
|
async handleInvokeCommand(cmd) {
|
|
301
|
-
const { requestId,
|
|
303
|
+
const { requestId, clipName, command } = cmd;
|
|
302
304
|
const timeoutMs = cmd.timeoutMs ?? 30_000;
|
|
303
305
|
try {
|
|
304
306
|
let input;
|
|
@@ -313,7 +315,7 @@ export class ClipProvider {
|
|
|
313
315
|
}
|
|
314
316
|
let timer;
|
|
315
317
|
const output = await Promise.race([
|
|
316
|
-
this.opts.clipManager.invoke(
|
|
318
|
+
this.opts.clipManager.invoke(clipName, command, input),
|
|
317
319
|
new Promise((_, reject) => {
|
|
318
320
|
timer = setTimeout(() => reject(new Error('invoke timeout')), timeoutMs);
|
|
319
321
|
}),
|
|
@@ -336,7 +338,10 @@ export class ClipProvider {
|
|
|
336
338
|
await this.sendProviderMessage({
|
|
337
339
|
invokeResult: {
|
|
338
340
|
requestId,
|
|
339
|
-
error:
|
|
341
|
+
error: {
|
|
342
|
+
code: isCommandError ? 'CLIP_COMMAND_ERROR' : 'INVOKE_FAILED',
|
|
343
|
+
message: isCommandError ? err.message : String(err),
|
|
344
|
+
},
|
|
340
345
|
errorIsCommand: isCommandError,
|
|
341
346
|
done: true,
|
|
342
347
|
},
|
|
@@ -352,7 +357,6 @@ export class ClipProvider {
|
|
|
352
357
|
register: {
|
|
353
358
|
providerName: this.opts.providerName,
|
|
354
359
|
clips,
|
|
355
|
-
orgId: this.opts.orgId,
|
|
356
360
|
},
|
|
357
361
|
});
|
|
358
362
|
}
|
|
@@ -382,21 +386,45 @@ export class ClipProvider {
|
|
|
382
386
|
const commands = manifest
|
|
383
387
|
? manifest.commandDetails.map((cmd) => this.commandDetailToInfo(cmd))
|
|
384
388
|
: [];
|
|
389
|
+
const pkg = manifest?.package ?? clip.package ?? clip.name;
|
|
385
390
|
return {
|
|
386
391
|
alias: clip.name,
|
|
387
|
-
|
|
392
|
+
package: pkg,
|
|
388
393
|
version: manifest?.version ?? clip.version ?? '',
|
|
394
|
+
domain: manifest?.domain,
|
|
389
395
|
commands,
|
|
396
|
+
hasWeb: manifest?.hasWeb ?? false,
|
|
397
|
+
dependencies: this.dependencyNames(manifest?.dependencies),
|
|
398
|
+
tokenProtected: Boolean(clip.token),
|
|
399
|
+
name: pkg,
|
|
400
|
+
patterns: manifest?.patterns,
|
|
401
|
+
entities: this.stringEntities(manifest?.entities),
|
|
402
|
+
description: manifest?.description,
|
|
390
403
|
};
|
|
391
404
|
}
|
|
392
405
|
commandDetailToInfo(detail) {
|
|
393
406
|
return {
|
|
394
407
|
name: detail.name,
|
|
395
408
|
description: detail.description ?? '',
|
|
396
|
-
|
|
397
|
-
|
|
398
|
-
? Buffer.from(detail.output, 'utf-8').toString('base64')
|
|
399
|
-
: undefined,
|
|
409
|
+
input: detail.input,
|
|
410
|
+
output: detail.output,
|
|
400
411
|
};
|
|
401
412
|
}
|
|
413
|
+
dependencyNames(deps) {
|
|
414
|
+
if (!deps)
|
|
415
|
+
return undefined;
|
|
416
|
+
const names = Object.entries(deps)
|
|
417
|
+
.map(([slot, dep]) => dep.package ?? slot)
|
|
418
|
+
.filter((name) => name.length > 0);
|
|
419
|
+
return names.length > 0 ? names : undefined;
|
|
420
|
+
}
|
|
421
|
+
stringEntities(entities) {
|
|
422
|
+
if (!entities)
|
|
423
|
+
return undefined;
|
|
424
|
+
const out = {};
|
|
425
|
+
for (const [key, value] of Object.entries(entities)) {
|
|
426
|
+
out[key] = typeof value === 'string' ? value : JSON.stringify(value);
|
|
427
|
+
}
|
|
428
|
+
return out;
|
|
429
|
+
}
|
|
402
430
|
}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"process-manager.d.ts","sourceRoot":"","sources":["../../src/clip-runtime/process-manager.ts"],"names":[],"mappings":"
|
|
1
|
+
{"version":3,"file":"process-manager.d.ts","sourceRoot":"","sources":["../../src/clip-runtime/process-manager.ts"],"names":[],"mappings":"AAEA,OAAO,EAGL,KAAK,iBAAiB,EAEvB,MAAM,cAAc,CAAC;AAEtB,OAAO,KAAK,EAAE,UAAU,EAAE,aAAa,EAAE,MAAM,eAAe,CAAC;AAQ/D,MAAM,WAAW,yBAAyB;IACxC,OAAO,CAAC,EAAE,MAAM,CAAC;IACjB,QAAQ,EAAE,MAAM,CAAC;IACjB,OAAO,EAAE,MAAM,CAAC;CACjB;AAED,qBAAa,kBAAkB;IAC7B,OAAO,CAAC,OAAO,CAAS;IACxB,OAAO,CAAC,QAAQ,CAAS;IACzB,OAAO,CAAC,OAAO,CAAS;IACxB,OAAO,CAAC,SAAS,CAAkC;IACnD,OAAO,CAAC,UAAU,CAA2C;IAC7D,OAAO,CAAC,QAAQ,CAAsC;IACtD,OAAO,CAAC,WAAW,CAAiC;IACpD,OAAO,CAAC,YAAY,CAAqB;IACzC,OAAO,CAAC,eAAe,CAEhB;IACP,OAAO,CAAC,iBAAiB,CAAyC;gBAEtD,IAAI,EAAE,yBAAyB;IAM3C,+EAA+E;IAC/E,IAAI,cAAc,CAAC,EAAE,EACjB,CAAC,CAAC,IAAI,EAAE,MAAM,EAAE,MAAM,EAAE,iBAAiB,EAAE,OAAO,EAAE,MAAM,KAAK,IAAI,CAAC,GACpE,SAAS,EAQZ;IAED,yEAAyE;IACzE,iBAAiB,CACf,EAAE,EAAE,CAAC,IAAI,EAAE,MAAM,EAAE,MAAM,EAAE,iBAAiB,EAAE,OAAO,EAAE,MAAM,KAAK,IAAI,GACrE,MAAM,IAAI;IAQb,mBAAmB,CAAC,EAAE,EAAE,CAAC,IAAI,EAAE,UAAU,KAAK,IAAI,GAAG,MAAM,IAAI;IAQ/D,OAAO,CAAC,oBAAoB;IAU5B,OAAO,CAAC,UAAU;IAOlB,YAAY,CAAC,MAAM,EAAE,UAAU,GAAG,IAAI;IAOhC,cAAc,CAAC,IAAI,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC;IAkB3C,SAAS,CAAC,IAAI,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC;IAItC,QAAQ,CAAC,IAAI,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC;IAarC,OAAO,IAAI,OAAO,CAAC,IAAI,CAAC;IAkB9B;;;;OAIG;IACG,MAAM,CAAC,IAAI,EAAE,MAAM,EAAE,OAAO,EAAE,MAAM,EAAE,KAAK,CAAC,EAAE,OAAO,GAAG,OAAO,CAAC,OAAO,CAAC;IAgBxE,YAAY,CAChB,IAAI,EAAE,MAAM,EACZ,OAAO,EAAE,MAAM,EACf,KAAK,CAAC,EAAE,OAAO,EACf,OAAO,CAAC,EAAE,CAAC,KAAK,EAAE,OAAO,KAAK,IAAI,GACjC,OAAO,CAAC,OAAO,CAAC;IAgBb,YAAY,CAAC,IAAI,EAAE,MAAM,GAAG,OAAO,CAAC,aAAa,CAAC;IAOxD,UAAU,CAAC,IAAI,EAAE,MAAM,GAAG;QAAE,MAAM,EAAE,iBAAiB,CAAC;QAAC,OAAO,EAAE,MAAM,CAAA;KAAE;IAMxE,SAAS,CAAC,IAAI,EAAE,MAAM,GAAG,OAAO;IAKhC,kBAAkB,IAAI,UAAU,EAAE;IAIlC;;;OAGG;IACG,kBAAkB,IAAI,OAAO,CAAC,IAAI,CAAC;YA0D3B,aAAa;YAwBb,YAAY;IAgD1B,OAAO,CAAC,iBAAiB;IAMzB,OAAO,CAAC,YAAY;IAMpB,OAAO,CAAC,SAAS;CAUlB"}
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import * as fs from 'node:fs';
|
|
2
2
|
import * as path from 'node:path';
|
|
3
|
-
import { execFileSync } from 'node:child_process';
|
|
4
3
|
import { ClipProcess, ClipCommandError, } from './process.js';
|
|
4
|
+
import { findBunBinary } from './bun-resolver.js';
|
|
5
5
|
export class ClipProcessManager {
|
|
6
6
|
bunPath;
|
|
7
7
|
clipsDir;
|
|
@@ -269,8 +269,14 @@ export class ClipProcessManager {
|
|
|
269
269
|
async spawnProcess(config) {
|
|
270
270
|
const proc = new ClipProcess(config);
|
|
271
271
|
const clipDataDir = this.ensureClipDataDir(config);
|
|
272
|
-
|
|
272
|
+
const bunPath = this.resolveBun();
|
|
273
|
+
proc.spawn(bunPath, {
|
|
273
274
|
PINIX_DATA_DIR: clipDataDir,
|
|
275
|
+
// Expose the embedded bun's directory on the clip's PATH so a clip that
|
|
276
|
+
// shells out to `bun` by name resolves it — the daemon's launchd PATH
|
|
277
|
+
// does not include Frameworks/. Symmetric with installDeps(); relies on
|
|
278
|
+
// the embedded binary being named `bun` (Frameworks/bun).
|
|
279
|
+
PATH: `${path.dirname(bunPath)}${path.delimiter}${process.env.PATH ?? ''}`,
|
|
274
280
|
});
|
|
275
281
|
this.setStatus(config.name, 'running', 'starting');
|
|
276
282
|
try {
|
|
@@ -325,30 +331,3 @@ export class ClipProcessManager {
|
|
|
325
331
|
}
|
|
326
332
|
}
|
|
327
333
|
}
|
|
328
|
-
function findBunBinary() {
|
|
329
|
-
const isWindows = process.platform === 'win32';
|
|
330
|
-
if (!isWindows) {
|
|
331
|
-
const candidates = [
|
|
332
|
-
path.join(process.env.HOME || '', '.bun', 'bin', 'bun'),
|
|
333
|
-
'/usr/local/bin/bun',
|
|
334
|
-
'/opt/homebrew/bin/bun',
|
|
335
|
-
];
|
|
336
|
-
for (const candidate of candidates) {
|
|
337
|
-
if (fs.existsSync(candidate))
|
|
338
|
-
return candidate;
|
|
339
|
-
}
|
|
340
|
-
}
|
|
341
|
-
// PATH lookup — platform-aware
|
|
342
|
-
const lookupCmd = isWindows ? 'where.exe' : 'which';
|
|
343
|
-
const lookupArg = isWindows ? 'bun.exe' : 'bun';
|
|
344
|
-
try {
|
|
345
|
-
const result = execFileSync(lookupCmd, [lookupArg], { encoding: 'utf-8' }).trim();
|
|
346
|
-
const firstLine = result.split('\n')[0]?.trim();
|
|
347
|
-
if (firstLine)
|
|
348
|
-
return firstLine;
|
|
349
|
-
}
|
|
350
|
-
catch {
|
|
351
|
-
// not found in PATH
|
|
352
|
-
}
|
|
353
|
-
throw new Error('bun binary not found — install Bun (https://bun.sh) or set bunPath option');
|
|
354
|
-
}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@parall/daemon",
|
|
3
|
-
"version": "1.32.
|
|
3
|
+
"version": "1.32.1",
|
|
4
4
|
"description": "Parall local agent runtime — daemon supervisor + bridge runtimes, bundled as standalone JS files",
|
|
5
5
|
"license": "MIT",
|
|
6
6
|
"repository": {
|
|
@@ -29,11 +29,11 @@
|
|
|
29
29
|
"dist"
|
|
30
30
|
],
|
|
31
31
|
"dependencies": {
|
|
32
|
-
"@parall/agent
|
|
33
|
-
"@parall/claude-agent": "1.32.
|
|
34
|
-
"@parall/
|
|
35
|
-
"@parall/openclaw-agent": "1.32.
|
|
36
|
-
"@parall/
|
|
32
|
+
"@parall/codex-agent": "1.32.1",
|
|
33
|
+
"@parall/claude-agent": "1.32.1",
|
|
34
|
+
"@parall/sdk": "1.32.1",
|
|
35
|
+
"@parall/openclaw-agent": "1.32.1",
|
|
36
|
+
"@parall/agent-core": "1.32.1"
|
|
37
37
|
},
|
|
38
38
|
"devDependencies": {
|
|
39
39
|
"@types/node": "^22.0.0",
|