@zixt/host 0.0.96 → 0.0.98

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 (2) hide show
  1. package/dist/index.js +179 -83
  2. package/package.json +1 -1
package/dist/index.js CHANGED
@@ -28,7 +28,7 @@ import { homedir as homedir3 } from "node:os";
28
28
  // package.json
29
29
  var package_default = {
30
30
  name: "@zixt/host",
31
- version: "0.0.96",
31
+ version: "0.0.98",
32
32
  type: "module",
33
33
  exports: {
34
34
  ".": "./src/client.ts",
@@ -14764,6 +14764,7 @@ var BrowserTab = external_exports.object({
14764
14764
  var BROWSER_MAX_TABS = 12;
14765
14765
  var BrowserSessionState = external_exports.object({
14766
14766
  browserSessionId: BrowserSessionId,
14767
+ taskId: TaskId,
14767
14768
  agentId: AgentId,
14768
14769
  /** Mirrors the active tab so a viewer can render before tabs arrive. */
14769
14770
  url: external_exports.string().max(2e3),
@@ -14796,6 +14797,7 @@ var BrowserCommand = external_exports.discriminatedUnion("action", [
14796
14797
  ]);
14797
14798
  var BrowserFramePayload = external_exports.object({
14798
14799
  browserSessionId: BrowserSessionId,
14800
+ taskId: TaskId,
14799
14801
  agentId: AgentId,
14800
14802
  /** Per-session monotonic counter so late frames can be dropped. */
14801
14803
  frameSeq: external_exports.number().int().min(0),
@@ -17315,7 +17317,7 @@ var ListTasksResponse = external_exports.object({
17315
17317
  }).strict();
17316
17318
 
17317
17319
  // ../../packages/contracts/src/protocol.ts
17318
- var PROTOCOL_VERSION = 9;
17320
+ var PROTOCOL_VERSION = 10;
17319
17321
  var BROWSER_PROFILE_INVENTORY_PAGE_SIZE = 200;
17320
17322
  var HELLO_UNWOUND_ASSIGNMENT_LIMIT = 1e3;
17321
17323
  var TASK_CANCEL_ACK_EVENT = "zixt.task.cancel.acknowledged";
@@ -18305,6 +18307,7 @@ var BrowserScreencastFrame = external_exports.object({
18305
18307
  var BrowserSessionEndedFrame = external_exports.object({
18306
18308
  type: external_exports.literal("browser.session.ended"),
18307
18309
  browserSessionId: external_exports.string().min(1).max(200),
18310
+ taskId: TaskId,
18308
18311
  agentId: AgentId,
18309
18312
  reason: BrowserSessionEndReason
18310
18313
  });
@@ -18340,6 +18343,7 @@ var BrowserCredentialResultFrame = external_exports.discriminatedUnion("ok", [
18340
18343
  var BrowserOpenFrame = external_exports.object({
18341
18344
  type: external_exports.literal("browser.open"),
18342
18345
  requestId: external_exports.string().min(1).max(200),
18346
+ taskId: TaskId,
18343
18347
  agentId: AgentId,
18344
18348
  /**
18345
18349
  * Cloud-owned browser-profile lifecycle revision. A Host persists the last
@@ -18353,11 +18357,13 @@ var BrowserOpenFrame = external_exports.object({
18353
18357
  });
18354
18358
  var BrowserCloseFrame = external_exports.object({
18355
18359
  type: external_exports.literal("browser.close"),
18360
+ taskId: TaskId,
18356
18361
  agentId: AgentId,
18357
18362
  browserSessionId: external_exports.string().min(1).max(200)
18358
18363
  });
18359
18364
  var BrowserViewFrame = external_exports.object({
18360
18365
  type: external_exports.literal("browser.view"),
18366
+ taskId: TaskId,
18361
18367
  agentId: AgentId,
18362
18368
  browserSessionId: external_exports.string().min(1).max(200),
18363
18369
  active: external_exports.boolean(),
@@ -18365,12 +18371,14 @@ var BrowserViewFrame = external_exports.object({
18365
18371
  });
18366
18372
  var BrowserInputFrame = external_exports.object({
18367
18373
  type: external_exports.literal("browser.input"),
18374
+ taskId: TaskId,
18368
18375
  agentId: AgentId,
18369
18376
  browserSessionId: external_exports.string().min(1).max(200),
18370
18377
  events: external_exports.array(BrowserInputEvent).min(1).max(60)
18371
18378
  });
18372
18379
  var BrowserCommandFrame = external_exports.object({
18373
18380
  type: external_exports.literal("browser.command"),
18381
+ taskId: TaskId,
18374
18382
  agentId: AgentId,
18375
18383
  browserSessionId: external_exports.string().min(1).max(200),
18376
18384
  command: BrowserCommand
@@ -18885,6 +18893,11 @@ var ConversationEventProjection = external_exports.discriminatedUnion("kind", [
18885
18893
  eventKind: external_exports.enum(["response", "error", "status", "question", "message"]),
18886
18894
  summary: external_exports.string(),
18887
18895
  taskStatus: TaskStatus.nullable(),
18896
+ /** Authorship of a role-neutral `message`; null for legacy/unknown rows. */
18897
+ messageAuthor: external_exports.object({
18898
+ role: external_exports.enum(["member", "teammate"]),
18899
+ displayName: external_exports.string().nullable()
18900
+ }).strict().nullable().optional(),
18888
18901
  /**
18889
18902
  * Opaque presentation key shared by the useful report and its synthetic
18890
18903
  * terminal wake-up. It is never shown to people; clients use it only to
@@ -22975,9 +22988,10 @@ var HostClient = class _HostClient {
22975
22988
  ...openRequestId !== void 0 ? { openRequestId } : {}
22976
22989
  }),
22977
22990
  frame: (payload) => this.sendBrowserFrame({ type: "browser.frame", frame: payload }),
22978
- ended: (browserSessionId, agentId, reason) => this.sendBrowserFrame({
22991
+ ended: (browserSessionId, taskId, agentId, reason) => this.sendBrowserFrame({
22979
22992
  type: "browser.session.ended",
22980
22993
  browserSessionId,
22994
+ taskId,
22981
22995
  agentId,
22982
22996
  reason
22983
22997
  })
@@ -23078,6 +23092,7 @@ var HostClient = class _HostClient {
23078
23092
  const bridge = this.opts.browser;
23079
23093
  if (!bridge) return;
23080
23094
  void bridge.open(
23095
+ frame.taskId,
23081
23096
  frame.agentId,
23082
23097
  frame.browserSessionId,
23083
23098
  frame.viewport,
@@ -23087,6 +23102,7 @@ var HostClient = class _HostClient {
23087
23102
  this.sendBrowserFrame({
23088
23103
  type: "browser.session.ended",
23089
23104
  browserSessionId: frame.browserSessionId,
23105
+ taskId: frame.taskId,
23090
23106
  agentId: frame.agentId,
23091
23107
  reason: "error"
23092
23108
  });
@@ -23094,17 +23110,33 @@ var HostClient = class _HostClient {
23094
23110
  return;
23095
23111
  }
23096
23112
  case "browser.close":
23097
- void this.opts.browser?.close(frame.agentId, "stopped").catch(() => {
23113
+ void this.opts.browser?.close(frame.taskId, frame.agentId, frame.browserSessionId, "stopped").catch(() => {
23098
23114
  });
23099
23115
  return;
23100
23116
  case "browser.view":
23101
- void this.opts.browser?.setViewers(frame.agentId, frame.active, frame.viewerCount);
23117
+ void this.opts.browser?.setViewers(
23118
+ frame.taskId,
23119
+ frame.agentId,
23120
+ frame.browserSessionId,
23121
+ frame.active,
23122
+ frame.viewerCount
23123
+ );
23102
23124
  return;
23103
23125
  case "browser.input":
23104
- void this.opts.browser?.applyInput(frame.agentId, frame.events);
23126
+ void this.opts.browser?.applyInput(
23127
+ frame.taskId,
23128
+ frame.agentId,
23129
+ frame.browserSessionId,
23130
+ frame.events
23131
+ );
23105
23132
  return;
23106
23133
  case "browser.command":
23107
- void this.opts.browser?.command(frame.agentId, frame.command);
23134
+ void this.opts.browser?.command(
23135
+ frame.taskId,
23136
+ frame.agentId,
23137
+ frame.browserSessionId,
23138
+ frame.command
23139
+ );
23108
23140
  return;
23109
23141
  case "browser.credential.result": {
23110
23142
  const waiter = this.browserCredentialWaiters.get(frame.requestId);
@@ -28507,7 +28539,7 @@ var BrowserManager = class {
28507
28539
  * open until a later trusted revision is successfully authorized.
28508
28540
  */
28509
28541
  deniedAuthorizations = /* @__PURE__ */ new Map();
28510
- /** Serializes open/close per teammate: one profile allows one live browser. */
28542
+ /** Serializes profile lifecycle and session open/close per teammate. */
28511
28543
  locks = /* @__PURE__ */ new Map();
28512
28544
  events = null;
28513
28545
  profileRoot;
@@ -28557,9 +28589,9 @@ var BrowserManager = class {
28557
28589
  const first = await this.profileInventoryPages().next();
28558
28590
  return first.value ?? { agentIds: [], complete: true };
28559
28591
  }
28560
- sessionState(agentId) {
28561
- const session = this.sessions.get(agentId);
28562
- return session && !session.closed ? this.stateOf(session) : null;
28592
+ sessionState(taskId, agentId) {
28593
+ const session = this.sessions.get(taskId);
28594
+ return session && !session.closed && (!agentId || session.agentId === agentId) ? this.stateOf(session) : null;
28563
28595
  }
28564
28596
  /** Serialized per agent so concurrent tool calls/frames can't double-launch. */
28565
28597
  withLock(agentId, work) {
@@ -28577,6 +28609,11 @@ var BrowserManager = class {
28577
28609
  throw new Error("invalid agent id for a browser profile path");
28578
28610
  }
28579
28611
  }
28612
+ validateTaskId(taskId) {
28613
+ if (!TaskId.safeParse(taskId).success) {
28614
+ throw new Error("invalid task id for a browser session");
28615
+ }
28616
+ }
28580
28617
  exactChild(root, child) {
28581
28618
  const canonicalRoot = resolve7(root);
28582
28619
  const target = resolve7(canonicalRoot, child);
@@ -28689,13 +28726,14 @@ var BrowserManager = class {
28689
28726
  }
28690
28727
  }
28691
28728
  /**
28692
- * Ensure a live session for this teammate. An existing session is adopted
28729
+ * Ensure a live session for this Task. An existing session is adopted
28693
28730
  * as-is (the cloud-minted id loses to the live one — the cloud reconciles
28694
28731
  * from the announced state). Missing Chromium surfaces as a thrown error
28695
28732
  * with the install remedy.
28696
28733
  */
28697
- ensure(agentId, browserSessionId) {
28734
+ ensure(taskId, agentId, browserSessionId) {
28698
28735
  try {
28736
+ this.validateTaskId(taskId);
28699
28737
  this.validateAgentId(agentId);
28700
28738
  } catch (error52) {
28701
28739
  return Promise.reject(error52);
@@ -28710,12 +28748,15 @@ var BrowserManager = class {
28710
28748
  if (lifecycle?.state === "purged") {
28711
28749
  throw new Error("this browser profile was retired and cannot be recreated locally");
28712
28750
  }
28713
- const existing = this.sessions.get(agentId);
28751
+ const existing = this.sessions.get(taskId);
28752
+ if (existing && existing.agentId !== agentId) {
28753
+ throw new Error("browser session teammate does not match its Task");
28754
+ }
28714
28755
  if (existing?.closeFailed) {
28715
28756
  throw new Error("the previous browser termination is not yet confirmed");
28716
28757
  }
28717
28758
  if (existing && !existing.closed) {
28718
- this.markActivity(agentId);
28759
+ this.markActivity(taskId);
28719
28760
  return this.stateOf(existing);
28720
28761
  }
28721
28762
  await this.ensureOwnedDirectory(this.profileRoot);
@@ -28730,12 +28771,14 @@ var BrowserManager = class {
28730
28771
  }
28731
28772
  await mkdir6(profileDir, { recursive: true, mode: 448 });
28732
28773
  const adapter = await this.opts.factory.open({
28774
+ taskId,
28733
28775
  agentId,
28734
28776
  profileDir,
28735
28777
  viewport: this.viewport
28736
28778
  });
28737
28779
  const session = {
28738
28780
  browserSessionId: browserSessionId ?? newId("brs"),
28781
+ taskId,
28739
28782
  agentId,
28740
28783
  viewport: this.viewport,
28741
28784
  adapter,
@@ -28751,7 +28794,7 @@ var BrowserManager = class {
28751
28794
  closed: false,
28752
28795
  closeFailed: false
28753
28796
  };
28754
- this.sessions.set(agentId, session);
28797
+ this.sessions.set(taskId, session);
28755
28798
  adapter.onStateChanged(() => {
28756
28799
  if (!session.closed) this.events?.state(this.stateOf(session));
28757
28800
  });
@@ -28761,17 +28804,23 @@ var BrowserManager = class {
28761
28804
  });
28762
28805
  }
28763
28806
  /** browser.open handler: ensure, then announce with the request correlation. */
28764
- async open(agentId, browserSessionId, _viewport, openRequestId, profileRevision) {
28807
+ async open(taskId, agentId, browserSessionId, _viewport, openRequestId, profileRevision) {
28765
28808
  if (profileRevision !== void 0) await this.authorizeProfile(agentId, profileRevision);
28766
- const state = await this.ensure(agentId, browserSessionId);
28809
+ const state = await this.ensure(taskId, agentId, browserSessionId);
28767
28810
  this.events?.state(state, openRequestId);
28768
28811
  return state;
28769
28812
  }
28770
- close(agentId, reason) {
28771
- return this.withLock(agentId, () => this.closeLocked(agentId, reason));
28813
+ close(taskId, agentId, browserSessionId, reason) {
28814
+ return this.withLock(
28815
+ agentId,
28816
+ () => this.closeLocked(taskId, reason, { agentId, browserSessionId })
28817
+ );
28772
28818
  }
28773
- async closeLocked(agentId, reason) {
28774
- const session = this.sessions.get(agentId);
28819
+ async closeLocked(taskId, reason, expected) {
28820
+ const session = this.sessions.get(taskId);
28821
+ if (expected && session && (session.agentId !== expected.agentId || session.browserSessionId !== expected.browserSessionId)) {
28822
+ return;
28823
+ }
28775
28824
  if (!session || session.closed && !session.closeFailed) return;
28776
28825
  session.closed = true;
28777
28826
  session.closeFailed = false;
@@ -28785,8 +28834,8 @@ var BrowserManager = class {
28785
28834
  session.closeFailed = true;
28786
28835
  throw error52;
28787
28836
  }
28788
- this.sessions.delete(agentId);
28789
- this.events?.ended(session.browserSessionId, agentId, reason);
28837
+ this.sessions.delete(taskId);
28838
+ this.events?.ended(session.browserSessionId, taskId, session.agentId, reason);
28790
28839
  }
28791
28840
  /** Persist retirement, close a live Chromium, then delete only this exact profile. */
28792
28841
  purge(agentId, purgeId, profileRevision) {
@@ -28806,22 +28855,25 @@ var BrowserManager = class {
28806
28855
  state: "purged",
28807
28856
  purgeId
28808
28857
  });
28809
- await this.closeLocked(agentId, "stopped");
28858
+ const sessionTaskIds = [...this.sessions.values()].filter((session) => session.agentId === agentId).map((session) => session.taskId);
28859
+ for (const taskId of sessionTaskIds) await this.closeLocked(taskId, "stopped");
28810
28860
  await rm6(this.profilePath(agentId), { recursive: true, force: true, maxRetries: 3 });
28811
28861
  await this.syncDirectory(this.profileRoot);
28812
28862
  });
28813
28863
  }
28814
28864
  async closeAll(reason) {
28815
28865
  await Promise.allSettled(
28816
- [...this.sessions.keys()].map((agentId) => this.close(agentId, reason))
28866
+ [...this.sessions.values()].map(
28867
+ (session) => this.close(session.taskId, session.agentId, session.browserSessionId, reason)
28868
+ )
28817
28869
  );
28818
28870
  }
28819
28871
  /** Streaming is viewer-driven: no watcher, no capture cost. */
28820
- async setViewers(agentId, active, viewerCount) {
28821
- const session = this.sessions.get(agentId);
28822
- if (!session || session.closed) return;
28872
+ async setViewers(taskId, agentId, browserSessionId, active, viewerCount) {
28873
+ const session = this.exactSession(taskId, agentId, browserSessionId);
28874
+ if (!session) return;
28823
28875
  session.viewerCount = active ? Math.max(1, viewerCount) : 0;
28824
- this.markActivity(agentId);
28876
+ this.markActivity(taskId);
28825
28877
  if (session.viewerCount > 0) {
28826
28878
  await session.adapter.setFrameSink((frame) => this.enqueueFrame(session, frame));
28827
28879
  try {
@@ -28835,17 +28887,17 @@ var BrowserManager = class {
28835
28887
  session.pendingFrame = null;
28836
28888
  }
28837
28889
  }
28838
- applyInput(agentId, events) {
28839
- const session = this.sessions.get(agentId);
28840
- if (!session || session.closed) return Promise.resolve();
28841
- this.markActivity(agentId);
28890
+ applyInput(taskId, agentId, browserSessionId, events) {
28891
+ const session = this.exactSession(taskId, agentId, browserSessionId);
28892
+ if (!session) return Promise.resolve();
28893
+ this.markActivity(taskId);
28842
28894
  return this.enqueue(session, () => session.adapter.applyInput(events));
28843
28895
  }
28844
28896
  /** Chrome-level action from a human: tabs, reload, history, address bar. */
28845
- command(agentId, command) {
28846
- const session = this.sessions.get(agentId);
28847
- if (!session || session.closed) return Promise.resolve();
28848
- this.markActivity(agentId);
28897
+ command(taskId, agentId, browserSessionId, command) {
28898
+ const session = this.exactSession(taskId, agentId, browserSessionId);
28899
+ if (!session) return Promise.resolve();
28900
+ this.markActivity(taskId);
28849
28901
  const resizeGeneration = command.action === "resize" ? ++session.resizeGeneration : null;
28850
28902
  return this.enqueue(session, async () => {
28851
28903
  if (resizeGeneration !== null && resizeGeneration !== session.resizeGeneration) return;
@@ -28860,9 +28912,9 @@ var BrowserManager = class {
28860
28912
  });
28861
28913
  }
28862
28914
  /**
28863
- * One page, one queue. Everything that touches the live page goes through
28864
- * here in arrival order, including tool calls, so an agent action and a
28865
- * human's click cannot interleave halfway through each other.
28915
+ * One Task session, one queue. Everything that touches its pages goes
28916
+ * through here in arrival order, including tool calls, so an agent action
28917
+ * and a human's click cannot interleave halfway through each other.
28866
28918
  */
28867
28919
  enqueue(session, work) {
28868
28920
  const next = session.inputQueue.then(work, work);
@@ -28871,11 +28923,11 @@ var BrowserManager = class {
28871
28923
  return next;
28872
28924
  }
28873
28925
  /** Tool surface: every call ensures the session and counts as activity. */
28874
- async tool(agentId) {
28875
- await this.ensure(agentId);
28876
- const session = this.sessions.get(agentId);
28926
+ async tool(taskId, agentId) {
28927
+ await this.ensure(taskId, agentId);
28928
+ const session = this.sessions.get(taskId);
28877
28929
  if (!session || session.closed) throw new Error("browser session is not available");
28878
- const touch = () => this.markActivity(agentId);
28930
+ const touch = () => this.markActivity(taskId);
28879
28931
  const queued = (work) => {
28880
28932
  touch();
28881
28933
  let result;
@@ -28912,6 +28964,7 @@ var BrowserManager = class {
28912
28964
  const page2 = session.adapter.state();
28913
28965
  return {
28914
28966
  browserSessionId: session.browserSessionId,
28967
+ taskId: session.taskId,
28915
28968
  agentId: session.agentId,
28916
28969
  url: page2.url.slice(0, 2e3),
28917
28970
  title: page2.title.slice(0, 300),
@@ -28936,6 +28989,7 @@ var BrowserManager = class {
28936
28989
  session.lastFrameSentAt = Date.now();
28937
28990
  this.events?.frame({
28938
28991
  browserSessionId: session.browserSessionId,
28992
+ taskId: session.taskId,
28939
28993
  agentId: session.agentId,
28940
28994
  frameSeq: session.frameSeq++,
28941
28995
  jpegBase64: pending.jpegBase64,
@@ -28945,8 +28999,12 @@ var BrowserManager = class {
28945
28999
  }, delay4);
28946
29000
  session.frameTimer.unref?.();
28947
29001
  }
28948
- markActivity(agentId) {
28949
- const session = this.sessions.get(agentId);
29002
+ exactSession(taskId, agentId, browserSessionId) {
29003
+ const session = this.sessions.get(taskId);
29004
+ return session && !session.closed && session.agentId === agentId && session.browserSessionId === browserSessionId ? session : null;
29005
+ }
29006
+ markActivity(taskId) {
29007
+ const session = this.sessions.get(taskId);
28950
29008
  if (!session || session.closed) return;
28951
29009
  this.armIdleTimer(session);
28952
29010
  }
@@ -28957,7 +29015,12 @@ var BrowserManager = class {
28957
29015
  this.armIdleTimer(session);
28958
29016
  return;
28959
29017
  }
28960
- void this.close(session.agentId, "idle_timeout").catch(() => {
29018
+ void this.close(
29019
+ session.taskId,
29020
+ session.agentId,
29021
+ session.browserSessionId,
29022
+ "idle_timeout"
29023
+ ).catch(() => {
28961
29024
  });
28962
29025
  }, this.idleTimeoutMs);
28963
29026
  session.idleTimer.unref?.();
@@ -28977,6 +29040,49 @@ async function loadPlaywright() {
28977
29040
  var KEY_ALIASES = { " ": "Space" };
28978
29041
  function createPlaywrightBrowserAdapterFactory(dependencies = {}) {
28979
29042
  const load = dependencies.loadPlaywright ?? loadPlaywright;
29043
+ const runtimes = /* @__PURE__ */ new Map();
29044
+ const runtimeLocks = /* @__PURE__ */ new Map();
29045
+ const withRuntimeLock = (profileDir, work) => {
29046
+ const tail = runtimeLocks.get(profileDir) ?? Promise.resolve();
29047
+ const next = tail.then(work, work);
29048
+ runtimeLocks.set(
29049
+ profileDir,
29050
+ next.catch(() => {
29051
+ })
29052
+ );
29053
+ return next;
29054
+ };
29055
+ const acquireRuntime = (profileDir, viewport) => withRuntimeLock(profileDir, async () => {
29056
+ let runtime = runtimes.get(profileDir);
29057
+ if (!runtime) {
29058
+ const playwright = await load();
29059
+ const context = await playwright.chromium.launchPersistentContext(profileDir, {
29060
+ headless: true,
29061
+ // Use the full Chromium build so real sign-in pages see an ordinary
29062
+ // browser shape rather than the headless-shell fingerprint.
29063
+ channel: "chromium",
29064
+ viewport,
29065
+ args: ["--disable-blink-features=AutomationControlled"]
29066
+ });
29067
+ await Promise.all(context.pages().map((page2) => page2.close()));
29068
+ runtime = { context, references: 0 };
29069
+ runtimes.set(profileDir, runtime);
29070
+ }
29071
+ runtime.references += 1;
29072
+ return runtime;
29073
+ });
29074
+ const releaseRuntime = (profileDir, runtime) => withRuntimeLock(profileDir, async () => {
29075
+ if (runtimes.get(profileDir) !== runtime || runtime.references <= 0) return;
29076
+ runtime.references -= 1;
29077
+ if (runtime.references > 0) return;
29078
+ try {
29079
+ await runtime.context.close();
29080
+ runtimes.delete(profileDir);
29081
+ } catch (error52) {
29082
+ runtime.references = 1;
29083
+ throw error52;
29084
+ }
29085
+ });
28980
29086
  return {
28981
29087
  kind: "playwright",
28982
29088
  async capability() {
@@ -28991,28 +29097,8 @@ function createPlaywrightBrowserAdapterFactory(dependencies = {}) {
28991
29097
  }
28992
29098
  },
28993
29099
  async open({ profileDir, viewport }) {
28994
- const playwright = await load();
28995
- const context = await playwright.chromium.launchPersistentContext(profileDir, {
28996
- headless: true,
28997
- // Without a channel, Playwright resolves `headless: true` to its
28998
- // `chromium_headless_shell` build — a stripped binary missing pieces
28999
- // a browser is expected to have (no `window.chrome`, no plugins, a
29000
- // single-entry `navigator.languages`). Google Accounts reads that
29001
- // shape as "this browser or app may not be secure" and refuses the
29002
- // sign-in outright, which made every site behind a Google login a
29003
- // dead end for the person watching the panel. `chromium` selects the
29004
- // full browser this adapter's capability probe already checks for
29005
- // (`executablePath()` resolves to it), so the two now agree.
29006
- channel: "chromium",
29007
- viewport,
29008
- // Headless Chromium announces itself in two ways that bot protection
29009
- // reads as "not a person": `HeadlessChrome` in the user agent, and
29010
- // `navigator.webdriver`. This is a real Chrome rendering a real page
29011
- // for a real person watching it, so neither claim is informative —
29012
- // sites that act on them simply refuse to load. The user agent is
29013
- // corrected below, once the browser can tell us its own version.
29014
- args: ["--disable-blink-features=AutomationControlled"]
29015
- });
29100
+ const runtime = await acquireRuntime(profileDir, viewport);
29101
+ const context = runtime.context;
29016
29102
  let liveViewport = { ...viewport };
29017
29103
  const tabs = [];
29018
29104
  let activeTabId = null;
@@ -29170,6 +29256,13 @@ function createPlaywrightBrowserAdapterFactory(dependencies = {}) {
29170
29256
  });
29171
29257
  page2.on("domcontentloaded", () => settle4(tab.navigation));
29172
29258
  page2.on("load", () => settle4(tab.navigation));
29259
+ page2.on("popup", (popup) => {
29260
+ void adopt(popup).then(async (created) => {
29261
+ activeTabId = created.tabId;
29262
+ await syncScreencast();
29263
+ emit();
29264
+ });
29265
+ });
29173
29266
  page2.on("close", () => {
29174
29267
  if (tab.loadingTimer) clearTimeout(tab.loadingTimer);
29175
29268
  tab.loadingTimer = null;
@@ -29190,16 +29283,15 @@ function createPlaywrightBrowserAdapterFactory(dependencies = {}) {
29190
29283
  });
29191
29284
  return tab;
29192
29285
  };
29193
- for (const page2 of context.pages()) await adopt(page2);
29194
- if (tabs.length === 0) await adopt(await context.newPage());
29195
- activeTabId = tabs[0].tabId;
29196
- context.on("page", (popup) => {
29197
- void adopt(popup).then(async (created) => {
29198
- activeTabId = created.tabId;
29199
- await syncScreencast();
29200
- emit();
29286
+ try {
29287
+ await adopt(await context.newPage());
29288
+ } catch (error52) {
29289
+ closed = true;
29290
+ await releaseRuntime(profileDir, runtime).catch(() => {
29201
29291
  });
29202
- });
29292
+ throw error52;
29293
+ }
29294
+ activeTabId = tabs[0].tabId;
29203
29295
  for (const tab of tabs) {
29204
29296
  tab.loading = false;
29205
29297
  await refreshTitle(tab);
@@ -29497,8 +29589,11 @@ function createPlaywrightBrowserAdapterFactory(dependencies = {}) {
29497
29589
  },
29498
29590
  async close() {
29499
29591
  closed = true;
29500
- for (const tab of tabs) await detachSession(tab);
29501
- await context.close();
29592
+ for (const tab of [...tabs]) {
29593
+ await detachSession(tab);
29594
+ if (!tab.page.isClosed()) await tab.page.close();
29595
+ }
29596
+ await releaseRuntime(profileDir, runtime);
29502
29597
  }
29503
29598
  };
29504
29599
  }
@@ -29675,7 +29770,7 @@ function externalPage(page2, data, truncated = false) {
29675
29770
  var asString = (value) => typeof value === "string" && value.length > 0 ? value : void 0;
29676
29771
  var asNumber = (value) => typeof value === "number" && Number.isFinite(value) ? value : void 0;
29677
29772
  function createBrowserToolPack(deps) {
29678
- const { manager, agentId } = deps;
29773
+ const { manager, taskId, agentId } = deps;
29679
29774
  let detached = false;
29680
29775
  const guard = () => {
29681
29776
  if (detached) return "this run has ended; the browser tool is detached";
@@ -29686,7 +29781,7 @@ function createBrowserToolPack(deps) {
29686
29781
  const refused = guard();
29687
29782
  if (refused) return { ok: false, error: refused };
29688
29783
  try {
29689
- const tool = await manager.tool(agentId);
29784
+ const tool = await manager.tool(taskId, agentId);
29690
29785
  switch (name) {
29691
29786
  case "browser_navigate": {
29692
29787
  const url3 = asString(args["url"]);
@@ -37938,6 +38033,7 @@ function createCliRunner(adapter, opts = {}) {
37938
38033
  toolPacks.push(
37939
38034
  createBrowserToolPack({
37940
38035
  manager: opts.browserManager,
38036
+ taskId: task.taskId,
37941
38037
  agentId: task.agentId,
37942
38038
  authoritySignal: task.authoritySignal,
37943
38039
  cancelledNow: task.cancelledNow,
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@zixt/host",
3
- "version": "0.0.96",
3
+ "version": "0.0.98",
4
4
  "type": "module",
5
5
  "exports": {
6
6
  ".": "./src/client.ts",