@parall/daemon 1.45.0 → 1.47.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 (55) hide show
  1. package/bundle/manifest.json +15 -15
  2. package/bundle/parall-browser-pod.js +29796 -379
  3. package/bundle/parall-channel-exec.js +2 -0
  4. package/bundle/parall-claude-agent.js +26161 -277
  5. package/bundle/parall-codex-agent.js +26720 -335
  6. package/bundle/parall-daemon.js +31760 -2001
  7. package/bundle/parall-openclaw-agent.js +1 -0
  8. package/dist/browser-pod.d.ts +23 -1
  9. package/dist/browser-pod.d.ts.map +1 -1
  10. package/dist/browser-pod.js +104 -34
  11. package/dist/browser-profile-reconcile.d.ts +21 -0
  12. package/dist/browser-profile-reconcile.d.ts.map +1 -0
  13. package/dist/browser-profile-reconcile.js +188 -0
  14. package/dist/clip-runtime/browser-cdp.d.ts +40 -0
  15. package/dist/clip-runtime/browser-cdp.d.ts.map +1 -0
  16. package/dist/clip-runtime/browser-cdp.js +218 -0
  17. package/dist/clip-runtime/browser-profile-manager.d.ts +97 -24
  18. package/dist/clip-runtime/browser-profile-manager.d.ts.map +1 -1
  19. package/dist/clip-runtime/browser-profile-manager.js +316 -182
  20. package/dist/clip-runtime/browser-profile-pool.d.ts +3 -0
  21. package/dist/clip-runtime/browser-profile-pool.d.ts.map +1 -1
  22. package/dist/clip-runtime/browser-profile-pool.js +18 -1
  23. package/dist/clip-runtime/browser-proxy-reconcile.d.ts +77 -0
  24. package/dist/clip-runtime/browser-proxy-reconcile.d.ts.map +1 -0
  25. package/dist/clip-runtime/browser-proxy-reconcile.js +139 -0
  26. package/dist/clip-runtime/browser-proxy-state.d.ts +55 -0
  27. package/dist/clip-runtime/browser-proxy-state.d.ts.map +1 -0
  28. package/dist/clip-runtime/browser-proxy-state.js +149 -0
  29. package/dist/clip-runtime/browser-quiescence.d.ts +71 -0
  30. package/dist/clip-runtime/browser-quiescence.d.ts.map +1 -0
  31. package/dist/clip-runtime/browser-quiescence.js +136 -0
  32. package/dist/clip-runtime/browser-readiness.d.ts +64 -0
  33. package/dist/clip-runtime/browser-readiness.d.ts.map +1 -0
  34. package/dist/clip-runtime/browser-readiness.js +161 -0
  35. package/dist/clip-runtime/browser-state-store.d.ts +13 -2
  36. package/dist/clip-runtime/browser-state-store.d.ts.map +1 -1
  37. package/dist/clip-runtime/browser-state-store.js +15 -6
  38. package/dist/clip-runtime/browser-target-registry.d.ts +143 -0
  39. package/dist/clip-runtime/browser-target-registry.d.ts.map +1 -0
  40. package/dist/clip-runtime/browser-target-registry.js +297 -0
  41. package/dist/clip-runtime/browser-viewer-streamer.d.ts +13 -14
  42. package/dist/clip-runtime/browser-viewer-streamer.d.ts.map +1 -1
  43. package/dist/clip-runtime/browser-viewer-streamer.js +11 -63
  44. package/dist/daemon-main.d.ts.map +1 -1
  45. package/dist/daemon-main.js +3 -1
  46. package/dist/local-control.d.ts +59 -0
  47. package/dist/local-control.d.ts.map +1 -0
  48. package/dist/local-control.js +230 -0
  49. package/dist/local-profile-control.d.ts +77 -0
  50. package/dist/local-profile-control.d.ts.map +1 -0
  51. package/dist/local-profile-control.js +108 -0
  52. package/dist/supervisor.d.ts +19 -0
  53. package/dist/supervisor.d.ts.map +1 -1
  54. package/dist/supervisor.js +90 -187
  55. package/package.json +8 -6
@@ -4,11 +4,14 @@ import * as os from 'node:os';
4
4
  import * as path from 'node:path';
5
5
  import { effectiveLLMSourceExplicit, llmSource } from '@parall/agent-core';
6
6
  import { ParallWs, } from '@parall/sdk';
7
+ import { PUBLIC_PROXY_PROBE_FALLBACK_URL } from './clip-runtime/browser-readiness.js';
7
8
  import { installClip, parseSource } from './clip-runtime/clip-installer.js';
8
9
  import { parseIpcCommands } from './clip-runtime/manifest.js';
9
10
  import { BrowserProfilePool, ClipProcessManager, ClipProvider, HubClient, } from './clip-runtime/index.js';
10
11
  import { agentClaudeHomeFor, agentHomeDirFor, agentStateDirFor, agentWorkspaceDirFor, } from './config.js';
12
+ import { reconcileBrowserProfiles as runBrowserProfileReconcile } from './browser-profile-reconcile.js';
11
13
  import { listDirectory } from './filesystem.js';
14
+ import { startLocalProfileControl, } from './local-profile-control.js';
12
15
  import { ensureIsolatedHome, ensureSharedCredentialLink, } from './home-isolation.js';
13
16
  import { assertAgentKey, getRuntimeAdapter } from './runtimes.js';
14
17
  import { applyRuntimeBinaryEnv } from './runtime-bin-resolver.js';
@@ -97,6 +100,11 @@ export class DaemonSupervisor {
97
100
  running = false;
98
101
  machineId = null;
99
102
  machineOrgId = null;
103
+ // machines.created_by — the machine owner. The local control socket's profile
104
+ // commands are authorized against it (requester must BE the owner); kept
105
+ // fresh via bootstrap + refreshMachineConfig. Null until bootstrap → the
106
+ // local control plane fails closed.
107
+ machineCreatedBy = null;
100
108
  machineLlmSource = 'parall';
101
109
  // Whether this machine contributes its local clips as a hub provider. The
102
110
  // org admin toggles it via PATCH /machines/{id}/provider-enabled; default true.
@@ -115,6 +123,7 @@ export class DaemonSupervisor {
115
123
  // browserProfilePool replaces the old single browserProfileManager.
116
124
  healthGate = null;
117
125
  browserProfilePool = null;
126
+ localControl = null;
118
127
  clipManager = null;
119
128
  clipProvider = null;
120
129
  clipReconcileTimer = null;
@@ -178,6 +187,7 @@ export class DaemonSupervisor {
178
187
  .catch((err) => this.log.warn(`browser profile status report failed: ${String(err)}`));
179
188
  },
180
189
  resolveProxy: (profileId) => this.resolveBrowserProfileProxy(profileId),
190
+ proxyProbeUrl: this.resolveBrowserProxyProbeUrl(),
181
191
  });
182
192
  this.clipManager = new ClipProcessManager({
183
193
  clipsDir: path.join(this.config.rootStateDir, 'clips'),
@@ -212,6 +222,9 @@ export class DaemonSupervisor {
212
222
  await this.applyClipProviderState();
213
223
  this.startClipReconcileTimer();
214
224
  }
225
+ // Same-machine control plane (design daemon-control-authorization §7.1) —
226
+ // socket lifecycle + authorization live in local-profile-control.ts.
227
+ this.localControl = await startLocalProfileControl(this.localProfileControlDeps());
215
228
  // Report daemon version + self-update capability + detected runtime CLIs
216
229
  // via heartbeat (best-effort, off the critical path — detection spawns
217
230
  // `--version` probes). Capability tracks whether a service manager
@@ -357,6 +370,10 @@ export class DaemonSupervisor {
357
370
  }
358
371
  exits.push(this.terminateChild(state));
359
372
  }
373
+ if (this.localControl) {
374
+ exits.push(this.localControl.stop());
375
+ this.localControl = null;
376
+ }
360
377
  if (this.clipProvider) {
361
378
  exits.push(this.clipProvider.disconnect());
362
379
  this.clipProvider = null;
@@ -385,6 +402,7 @@ export class DaemonSupervisor {
385
402
  const machine = await this.client.getMachineSelf();
386
403
  this.machineId = machine.id;
387
404
  this.machineOrgId = machine.org_id;
405
+ this.machineCreatedBy = machine.created_by ?? null;
388
406
  this.machineLlmSource = machine.llm_source ?? 'parall';
389
407
  this.machineProviderEnabled = machine.provider_enabled ?? true;
390
408
  this.machineClipProviderUrl = machine.clip_provider_url ?? null;
@@ -517,195 +535,23 @@ export class DaemonSupervisor {
517
535
  throw err;
518
536
  }
519
537
  }
520
- async reconcileBrowserProfiles() {
538
+ reconcileBrowserProfiles() {
539
+ // Coordination body extracted move-only to browser-profile-reconcile.ts (this
540
+ // file crossed the 2000-line ceiling). The op queue / pool lifecycle / wipe
541
+ // helper are injected so the behavior is byte-for-byte identical.
521
542
  const pool = this.browserProfilePool;
522
543
  if (!pool)
523
- return;
524
- // Snapshot the locally-live set BEFORE the server fetch. A profile opened via a
525
- // WS lifecycle event DURING the fetch is NOT in this snapshot, so the negative
526
- // convergence below (which is stale w.r.t. that open) can never release it.
527
- // There is no periodic browser reconcile to re-open a wrongly-released profile —
528
- // it only runs on startup / WS-hello — so a stale release would strand it until
529
- // the next reconnect.
530
- const activeBeforeList = new Set(pool.activeProfileIds());
531
- // Also snapshot revives (open/ensure) queued but not yet a live manager: such a
532
- // stale open is invisible to activeProfileIds() and would otherwise drain after a
533
- // missed offline stop/delete and start a runtime the server no longer wants.
534
- const pendingReviveBeforeList = new Set(this.browserProfilePendingRevives.keys());
535
- // And ALL local profile state — per-profile homes AND legacy flat account files
536
- // (the pool owns that storage layout). State with NO server row is a
537
- // deleted/reassigned profile whose cookies/storage must be wiped from this host;
538
- // a pre-upgrade profile never reopened after migration exists only as a legacy flat
539
- // file, so home-only enumeration would miss it. Pre-fetch so state created during
540
- // the fetch is left alone.
541
- const localStateBeforeList = new Set(pool.localStateProfileIds());
542
- let profiles;
543
- try {
544
- profiles = await this.client.listMachineBrowserProfiles();
545
- }
546
- catch (err) {
547
- this.log.warn(`browser profile reconcile: list failed: ${String(err)}`);
548
- return;
549
- }
550
- // The pool must still be the same instance after the await — stop() may have
551
- // nulled/replaced it during a concurrent shutdown; bail rather than act on a
552
- // torn-down pool (also avoids NPE-ing through Promise.allSettled into warnings).
553
- if (this.browserProfilePool !== pool)
554
- return;
555
- // Converge local runtimes to the server's desired-live set (the server is the
556
- // SSOT). The supervisor is the desired-state coordinator; the pool is the
557
- // per-profile runtime registry. Every op here goes through
558
- // enqueueBrowserProfileOp so all supervisor-originated profile work shares one
559
- // ordering layer (the pool's own queue is the final per-profile mutex).
560
- const desiredLive = new Set();
561
- const serverIds = new Set(); // every byoc profile the server still lists (any status)
562
- const serverResetGen = new Map();
563
- const ops = [];
564
- // reset_generation (server) is the SSOT for reset intent; the daemon's per-profile
565
- // sidecar (read via pool.appliedResetGeneration) is only a local ack of "already
566
- // wiped up to this generation". A missed reset (server newer than the local ack) must
567
- // wipe local state BEFORE any open/ensure, so a reopen starts clean and never
568
- // resurrects the cookies the reset cleared. Returns the server reset_generation to
569
- // apply, or null if nothing is missed. Pure (no side effects).
570
- const missedResetGen = (profileId) => {
571
- const gen = serverResetGen.get(profileId) ?? 0;
572
- return gen > pool.appliedResetGeneration(profileId) ? gen : null;
573
- };
574
- // Negative-path helper (a stopped profile with a missed reset): fence synchronously
575
- // then enqueue a STANDALONE wipe. No revive follows, so a wipe failure simply leaves
576
- // the profile fenced and the sidecar unwritten → the next reconcile retries (the
577
- // SSOT reset_generation is still unapplied locally). Fail-closed by construction.
578
- const enqueueResetWipeIfMissed = (profileId) => {
579
- const gen = missedResetGen(profileId);
580
- if (gen === null)
581
- return false;
582
- // Fence SYNCHRONOUSLY before the async wipe — same contract as the WS stop/reset
583
- // path. On a machine-WS reconnect whose clip-provider stream stays live, a hub
584
- // invoke could otherwise run on the stale runtime in the gap before the wipe op
585
- // starts. (In the negative loop this re-asserts an already-set fence within the same
586
- // synchronous iteration — atomic, no revive can observe an intermediate generation.)
587
- pool.fence(profileId);
588
- ops.push(this.enqueueBrowserProfileOp(profileId, () => pool.applyResetWipe(profileId, gen)));
589
- return true;
590
- };
591
- for (const profile of profiles) {
592
- // The daemon supervises only machine-bound (byoc) profiles; hosted profiles
593
- // (null machine_id) run on the platform pool and are never owned by a daemon.
594
- if (!profile.machine_id)
595
- continue;
596
- serverIds.add(profile.id);
597
- serverResetGen.set(profile.id, profile.reset_generation ?? 0);
598
- if (profile.status !== 'running' && profile.status !== 'pending')
599
- continue;
600
- desiredLive.add(profile.id);
601
- // A missed reset must wipe SUCCESSFULLY before this profile is revived. Fence
602
- // synchronously now (reject hub invokes in the gap before the queued wipe runs).
603
- const wipeGen = missedResetGen(profile.id);
604
- if (wipeGen !== null)
605
- pool.fence(profile.id);
606
- // Capture the fence generation AFTER any fence bump so a stop arriving while the op
607
- // waits makes it skip as stale; carry the lifecycle generation so the daemon's
608
- // `running` report is fenced server-side. Drive desired state through the CAPTURED
609
- // pool (not enqueueBrowserProfileLifecycle, which re-reads this.browserProfilePool
610
- // and would negative-ACK `error` for a pending profile if stop() nulled the field).
611
- const sinceSeq = pool.stopSeqOf(profile.id);
612
- const generation = profile.lifecycle_generation;
613
- // forceStatusReport ONLY for a `pending` row: the daemon's last report may already be
614
- // `running` (Chromium survived the WS drop), so a deduped repeat would leave the
615
- // server stuck at `pending`. A `running` row keeps the normal dedup.
616
- const forceStatusReport = profile.status === 'pending';
617
- ops.push(this.enqueueBrowserProfileRevive(profile.id, async () => {
618
- // Reset-before-open, FAIL-CLOSED: a missed reset must wipe successfully BEFORE
619
- // the revive un-fences and reopens. If applyResetWipe throws, the await
620
- // propagates and ensureRuntime is SKIPPED — the profile stays fenced (the wipe
621
- // fenced it and the applied-reset sidecar was NOT written), so the next reconcile
622
- // retries because the SSOT reset_generation is still unapplied locally. This
623
- // ordering is expressed locally; the enqueueBrowserProfileOp queue contract (a
624
- // rejected op does not block later ops) is intentionally unchanged. A failed
625
- // wipe reports `error` (visible) and re-throws here, so ensureRuntime is skipped.
626
- if (wipeGen !== null)
627
- await this.wipeBeforeRevive(pool, profile.id, wipeGen, generation);
628
- // Reconnect recovery is liveness-only: ensure the runtime is live (account +
629
- // tab) for both pending and running. The original open's `start_url` is NOT
630
- // replayed (it rides only the live lifecycle event, not persisted); a `pending`
631
- // profile whose open event the daemon missed comes up at the default page rather
632
- // than a targetless `about:blank`. Persisting start_url is a separate follow-up.
633
- await pool.ensureRuntime(profile.id, sinceSeq, generation, { forceStatusReport });
634
- }));
635
- }
636
- // Negative convergence over everything locally known but NOT desired — active
637
- // runtimes, queued revives (open/ensure not yet a manager), and local state (homes +
638
- // legacy flat files) — all from the PRE-FETCH snapshots, so a profile that appeared
639
- // DURING the fetch is left alone (no periodic reconcile would undo a wrong teardown).
640
- const candidates = new Set([
641
- ...activeBeforeList,
642
- ...pendingReviveBeforeList,
643
- ...localStateBeforeList,
644
- ]);
645
- for (const profileId of candidates) {
646
- if (desiredLive.has(profileId))
647
- continue;
648
- const isActive = activeBeforeList.has(profileId);
649
- const hasLocalState = localStateBeforeList.has(profileId);
650
- // Pending-only revive (queued open/ensure, no runtime, no on-disk state at the
651
- // pre-fetch snapshot): the synchronous fence alone neutralizes the stale open (it
652
- // bumps stopSeq, so the queued revive skips as stale). Do NOT AWAIT a teardown — it
653
- // would block behind the possibly-stuck queue and stall the whole reconcile (the
654
- // queued-stale-open deadlock).
655
- if (!isActive && !hasLocalState) {
656
- pool.fence(profileId);
657
- // Race: a pending-only revive can COMPLETE during the list fetch (create a manager
658
- // + on-disk state) BEFORE this fence — the snapshot is then stale, so the fence
659
- // alone leaves an orphaned runtime/disk the negative loop never converged. Converge
660
- // it FIRE-AND-FORGET (NOT in `ops`: a still-stuck revive queue must not hang the
661
- // reconcile; it runs once the queue drains — after the fenced open skips or
662
- // completes — and is a no-op if nothing was created):
663
- // - deleted/reassigned → wipeAbsent: the old host must keep no session data.
664
- // - present + missed reset → applyResetWipe: the completed open may have applied
665
- // only its event's (older) reset_generation, not the current one.
666
- // - present + stopped → releaseRuntime: free the orphaned Chromium; a plain
667
- // stop keeps its cookies on disk.
668
- let cleanup;
669
- if (!serverIds.has(profileId)) {
670
- cleanup = pool.wipeAbsent(profileId);
671
- }
672
- else {
673
- const wipeGen = missedResetGen(profileId);
674
- cleanup =
675
- wipeGen !== null
676
- ? pool.applyResetWipe(profileId, wipeGen)
677
- : pool.releaseRuntime(profileId);
678
- }
679
- void cleanup.catch((err) => this.log.warn(`browser profile pending-revive cleanup failed for ${profileId}: ${String(err)}`));
680
- continue;
681
- }
682
- if (!serverIds.has(profileId)) {
683
- // Absent from the server list → deleted/reassigned → fence + wipe local state (the
684
- // old host must not keep cookies/storage). No status report — server is the SSOT.
685
- pool.fence(profileId);
686
- ops.push(this.enqueueBrowserProfileOp(profileId, () => pool.wipeAbsent(profileId)));
687
- }
688
- else if (enqueueResetWipeIfMissed(profileId)) {
689
- // Present (stopped) with a missed reset → fenced synchronously + wipe enqueued.
690
- }
691
- else if (isActive) {
692
- // Present (stopped) but the runtime is still live → fence + release the runtime
693
- // only; a plain stop keeps its cookies on disk.
694
- pool.fence(profileId);
695
- ops.push(this.enqueueBrowserProfileOp(profileId, () => pool.releaseRuntime(profileId)));
696
- }
697
- // else: present + stopped + on-disk state + no runtime + no missed reset → LEAVE
698
- // ALONE (do NOT fence). Its stopped-fence is already held — the start-sequence
699
- // pre-fence on a fresh daemon, or the persisted pool state on a WS reconnect — so a
700
- // hub invoke is still rejected. Re-fencing here would bump stopSeq and drop an `open`
701
- // that arrived during the list fetch (it captured an older sinceSeq, and the stale
702
- // `stopped` snapshot doesn't reflect it yet), stranding the server at `pending`.
703
- }
704
- for (const result of await Promise.allSettled(ops)) {
705
- if (result.status === 'rejected') {
706
- this.log.warn(`browser profile reconcile op failed: ${String(result.reason)}`);
707
- }
708
- }
544
+ return Promise.resolve();
545
+ return runBrowserProfileReconcile({
546
+ pool,
547
+ currentPool: () => this.browserProfilePool,
548
+ client: this.client,
549
+ log: this.log,
550
+ enqueueOp: (id, op) => this.enqueueBrowserProfileOp(id, op),
551
+ enqueueRevive: (id, op) => this.enqueueBrowserProfileRevive(id, op),
552
+ wipeBeforeRevive: (p, id, rg, g) => this.wipeBeforeRevive(p, id, rg, g),
553
+ pendingReviveIds: () => new Set(this.browserProfilePendingRevives.keys()),
554
+ });
709
555
  }
710
556
  /**
711
557
  * Resolve a profile's outbound proxy for bb-browser account creation (BYOC).
@@ -731,6 +577,22 @@ export class DaemonSupervisor {
731
577
  password: profile.proxy_password ?? undefined,
732
578
  };
733
579
  }
580
+ /** Proxy readiness probe endpoint for BYOC profiles (browser-readiness.ts): an
581
+ * explicit override, else the api-server's Parall-owned public `/generate_204`.
582
+ * The probe navigates a sacrificial tab THROUGH the profile's proxy, so the
583
+ * target must be public + proxy-resolvable — `config.apiUrl` is the public API
584
+ * host for BYOC. Falls back to a well-known public 204 if apiUrl is unusable. */
585
+ resolveBrowserProxyProbeUrl() {
586
+ const override = process.env.PRLL_BROWSER_PROXY_PROBE_URL?.trim();
587
+ if (override)
588
+ return override;
589
+ try {
590
+ return new URL('/generate_204', this.config.apiUrl).toString();
591
+ }
592
+ catch {
593
+ return PUBLIC_PROXY_PROBE_FALLBACK_URL;
594
+ }
595
+ }
734
596
  // ---- Flat layout migration (self-hosted → daemon) ----
735
597
  /**
736
598
  * Detects a legacy flat state layout (no agents/ subdir) and migrates it
@@ -913,6 +775,20 @@ export class DaemonSupervisor {
913
775
  }
914
776
  catch (err) {
915
777
  this.log.warn(`browser profile lifecycle failed (profile=${data.profile_id}, action=${data.action}): ${String(err)}`);
778
+ // Negative-ACK backstop for open/reset failures that never reached the
779
+ // manager's own error report (pool queue rejection, unsafe id, disposal
780
+ // failure): without it the server row is stranded at `pending` with no
781
+ // error_msg and the user sees a silent timeout. The manager-level report
782
+ // (same status, message may differ) already carries the structured cause
783
+ // when it fired — a second update is idempotent on status. Deliberately
784
+ // NOT for `stop`: its desired state is durable and reconcile converges it.
785
+ if ((data.action === 'open' || data.action === 'reset') && this.running) {
786
+ await this.client
787
+ .reportBrowserProfileStatus(data.profile_id, 'error', err instanceof Error ? err.message : String(err), data.generation)
788
+ .catch((reportErr) => {
789
+ this.log.warn(`browser profile lifecycle negative ACK failed for ${data.profile_id}: ${String(reportErr)}`);
790
+ });
791
+ }
916
792
  }
917
793
  }
918
794
  enqueueBrowserProfileLifecycle(data) {
@@ -946,6 +822,28 @@ export class DaemonSupervisor {
946
822
  ? this.enqueueBrowserProfileRevive(data.profile_id, op)
947
823
  : this.enqueueBrowserProfileOp(data.profile_id, op);
948
824
  }
825
+ /**
826
+ * What the supervisor lends the local control plane: identity, pool, queues.
827
+ * Getters, not values — owner and pool both change over the daemon's life
828
+ * (bootstrap / refresh / provider state), so the handler must read what is
829
+ * true AT CALL TIME; a snapshot could authorize against a stale owner.
830
+ *
831
+ * One definition, used by both `run()` and the authorization tests, so the
832
+ * matrix under test is wired exactly like production.
833
+ */
834
+ localProfileControlDeps() {
835
+ return {
836
+ log: this.log,
837
+ machineId: () => this.machineId,
838
+ orgId: () => this.machineOrgId,
839
+ owner: () => this.machineCreatedBy,
840
+ pool: () => this.browserProfilePool,
841
+ listProfiles: () => this.client.listMachineBrowserProfiles(),
842
+ enqueueRevive: (id, op) => this.enqueueBrowserProfileRevive(id, op),
843
+ enqueueOp: (id, op) => this.enqueueBrowserProfileOp(id, op),
844
+ wipeBeforeRevive: (pool, id, resetGen, gen) => this.wipeBeforeRevive(pool, id, resetGen, gen),
845
+ };
846
+ }
949
847
  enqueueBrowserProfileOp(profileId, op) {
950
848
  const previous = this.browserProfileOpQueues.get(profileId) ?? Promise.resolve();
951
849
  const next = previous
@@ -1307,6 +1205,11 @@ export class DaemonSupervisor {
1307
1205
  async refreshMachineConfig() {
1308
1206
  try {
1309
1207
  const machine = await this.client.getMachineSelf();
1208
+ // Fail closed, exactly like bootstrap: an absent created_by means the
1209
+ // server no longer proves an owner, so keeping the previous one would let
1210
+ // a revoked user keep driving profiles over the local socket. No owner =>
1211
+ // no local control, until a refresh proves one again.
1212
+ this.machineCreatedBy = machine.created_by ?? null;
1310
1213
  const newSource = machine.llm_source ?? 'parall';
1311
1214
  if (newSource !== this.machineLlmSource) {
1312
1215
  this.log.info(`machine config refreshed: llm_source ${this.machineLlmSource} → ${newSource}`);
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@parall/daemon",
3
- "version": "1.45.0",
3
+ "version": "1.47.0",
4
4
  "description": "Parall local agent runtime — daemon supervisor + bridge runtimes, bundled as standalone JS files",
5
5
  "license": "MIT",
6
6
  "repository": {
@@ -32,14 +32,16 @@
32
32
  "dependencies": {
33
33
  "@aws-sdk/client-s3": "3.984.0",
34
34
  "@pinixai/bb-browser-pro": "0.15.0",
35
- "@parall/agent-core": "1.45.0",
36
- "@parall/sdk": "1.45.0",
37
- "@parall/codex-agent": "1.45.0",
38
- "@parall/claude-agent": "1.45.0",
39
- "@parall/openclaw-agent": "1.45.0"
35
+ "ws": "^8.18.0",
36
+ "@parall/agent-core": "1.47.0",
37
+ "@parall/claude-agent": "1.47.0",
38
+ "@parall/openclaw-agent": "1.47.0",
39
+ "@parall/sdk": "1.47.0",
40
+ "@parall/codex-agent": "1.47.0"
40
41
  },
41
42
  "devDependencies": {
42
43
  "@types/node": "^22.0.0",
44
+ "@types/ws": "^8.18.0",
43
45
  "esbuild": "^0.25.0",
44
46
  "typescript": "^5.7.0"
45
47
  },