sandbox-agent 0.2.1 → 0.3.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.
package/dist/index.js CHANGED
@@ -1,6 +1,7 @@
1
1
  // src/client.ts
2
2
  import {
3
3
  AcpHttpClient,
4
+ AcpRpcError,
4
5
  PROTOCOL_VERSION
5
6
  } from "acp-http-client";
6
7
 
@@ -82,7 +83,9 @@ var InMemorySessionPersistDriver = class {
82
83
  function cloneSessionRecord(session) {
83
84
  return {
84
85
  ...session,
85
- sessionInit: session.sessionInit ? JSON.parse(JSON.stringify(session.sessionInit)) : void 0
86
+ sessionInit: session.sessionInit ? JSON.parse(JSON.stringify(session.sessionInit)) : void 0,
87
+ configOptions: session.configOptions ? JSON.parse(JSON.stringify(session.configOptions)) : void 0,
88
+ modes: session.modes ? JSON.parse(JSON.stringify(session.modes)) : session.modes
86
89
  };
87
90
  }
88
91
  function cloneSessionEvent(event) {
@@ -125,6 +128,12 @@ var DEFAULT_BASE_URL = "http://sandbox-agent";
125
128
  var DEFAULT_REPLAY_MAX_EVENTS = 50;
126
129
  var DEFAULT_REPLAY_MAX_CHARS = 12e3;
127
130
  var EVENT_INDEX_SCAN_EVENTS_LIMIT = 500;
131
+ var SESSION_CANCEL_METHOD = "session/cancel";
132
+ var MANUAL_CANCEL_ERROR = "Manual session/cancel calls are not allowed. Use destroySession(sessionId) instead.";
133
+ var HEALTH_WAIT_MIN_DELAY_MS = 500;
134
+ var HEALTH_WAIT_MAX_DELAY_MS = 15e3;
135
+ var HEALTH_WAIT_LOG_AFTER_MS = 5e3;
136
+ var HEALTH_WAIT_LOG_EVERY_MS = 1e4;
128
137
  var SandboxAgentError = class extends Error {
129
138
  status;
130
139
  problem;
@@ -137,6 +146,52 @@ var SandboxAgentError = class extends Error {
137
146
  this.response = response;
138
147
  }
139
148
  };
149
+ var UnsupportedSessionCategoryError = class extends Error {
150
+ sessionId;
151
+ category;
152
+ availableCategories;
153
+ constructor(sessionId, category, availableCategories) {
154
+ super(
155
+ `Session '${sessionId}' does not support category '${category}'. Available categories: ${availableCategories.join(", ") || "(none)"}`
156
+ );
157
+ this.name = "UnsupportedSessionCategoryError";
158
+ this.sessionId = sessionId;
159
+ this.category = category;
160
+ this.availableCategories = availableCategories;
161
+ }
162
+ };
163
+ var UnsupportedSessionValueError = class extends Error {
164
+ sessionId;
165
+ category;
166
+ configId;
167
+ requestedValue;
168
+ allowedValues;
169
+ constructor(sessionId, category, configId, requestedValue, allowedValues) {
170
+ super(
171
+ `Session '${sessionId}' does not support value '${requestedValue}' for category '${category}' (configId='${configId}'). Allowed values: ${allowedValues.join(", ") || "(none)"}`
172
+ );
173
+ this.name = "UnsupportedSessionValueError";
174
+ this.sessionId = sessionId;
175
+ this.category = category;
176
+ this.configId = configId;
177
+ this.requestedValue = requestedValue;
178
+ this.allowedValues = allowedValues;
179
+ }
180
+ };
181
+ var UnsupportedSessionConfigOptionError = class extends Error {
182
+ sessionId;
183
+ configId;
184
+ availableConfigIds;
185
+ constructor(sessionId, configId, availableConfigIds) {
186
+ super(
187
+ `Session '${sessionId}' does not expose config option '${configId}'. Available configIds: ${availableConfigIds.join(", ") || "(none)"}`
188
+ );
189
+ this.name = "UnsupportedSessionConfigOptionError";
190
+ this.sessionId = sessionId;
191
+ this.configId = configId;
192
+ this.availableConfigIds = availableConfigIds;
193
+ }
194
+ };
140
195
  var Session = class {
141
196
  record;
142
197
  sandbox;
@@ -179,6 +234,32 @@ var Session = class {
179
234
  const response = await this.send("session/prompt", { prompt });
180
235
  return response;
181
236
  }
237
+ async setMode(modeId) {
238
+ const updated = await this.sandbox.setSessionMode(this.id, modeId);
239
+ this.apply(updated.session.toRecord());
240
+ return updated.response;
241
+ }
242
+ async setConfigOption(configId, value) {
243
+ const updated = await this.sandbox.setSessionConfigOption(this.id, configId, value);
244
+ this.apply(updated.session.toRecord());
245
+ return updated.response;
246
+ }
247
+ async setModel(model) {
248
+ const updated = await this.sandbox.setSessionModel(this.id, model);
249
+ this.apply(updated.session.toRecord());
250
+ return updated.response;
251
+ }
252
+ async setThoughtLevel(thoughtLevel) {
253
+ const updated = await this.sandbox.setSessionThoughtLevel(this.id, thoughtLevel);
254
+ this.apply(updated.session.toRecord());
255
+ return updated.response;
256
+ }
257
+ async getConfigOptions() {
258
+ return this.sandbox.getSessionConfigOptions(this.id);
259
+ }
260
+ async getModes() {
261
+ return this.sandbox.getSessionModes(this.id);
262
+ }
182
263
  onEvent(listener) {
183
264
  return this.sandbox.onSessionEvent(this.id, listener);
184
265
  }
@@ -377,15 +458,163 @@ var LiveAcpConnection = class _LiveAcpConnection {
377
458
  return this.localByAgentSessionId.get(agentSessionId) ?? null;
378
459
  }
379
460
  };
461
+ var ProcessTerminalSession = class {
462
+ socket;
463
+ closed;
464
+ readyListeners = /* @__PURE__ */ new Set();
465
+ dataListeners = /* @__PURE__ */ new Set();
466
+ exitListeners = /* @__PURE__ */ new Set();
467
+ errorListeners = /* @__PURE__ */ new Set();
468
+ closeListeners = /* @__PURE__ */ new Set();
469
+ closeSignalSent = false;
470
+ closedResolve;
471
+ constructor(socket) {
472
+ this.socket = socket;
473
+ this.socket.binaryType = "arraybuffer";
474
+ this.closed = new Promise((resolve) => {
475
+ this.closedResolve = resolve;
476
+ });
477
+ this.socket.addEventListener("message", (event) => {
478
+ void this.handleMessage(event.data);
479
+ });
480
+ this.socket.addEventListener("error", () => {
481
+ this.emitError(new Error("Terminal websocket connection failed."));
482
+ });
483
+ this.socket.addEventListener("close", () => {
484
+ this.closedResolve();
485
+ for (const listener of this.closeListeners) {
486
+ listener();
487
+ }
488
+ });
489
+ }
490
+ onReady(listener) {
491
+ this.readyListeners.add(listener);
492
+ return () => {
493
+ this.readyListeners.delete(listener);
494
+ };
495
+ }
496
+ onData(listener) {
497
+ this.dataListeners.add(listener);
498
+ return () => {
499
+ this.dataListeners.delete(listener);
500
+ };
501
+ }
502
+ onExit(listener) {
503
+ this.exitListeners.add(listener);
504
+ return () => {
505
+ this.exitListeners.delete(listener);
506
+ };
507
+ }
508
+ onError(listener) {
509
+ this.errorListeners.add(listener);
510
+ return () => {
511
+ this.errorListeners.delete(listener);
512
+ };
513
+ }
514
+ onClose(listener) {
515
+ this.closeListeners.add(listener);
516
+ return () => {
517
+ this.closeListeners.delete(listener);
518
+ };
519
+ }
520
+ sendInput(data) {
521
+ const payload = encodeTerminalInput(data);
522
+ this.sendFrame({
523
+ type: "input",
524
+ data: payload.data,
525
+ encoding: payload.encoding
526
+ });
527
+ }
528
+ resize(payload) {
529
+ this.sendFrame({
530
+ type: "resize",
531
+ cols: payload.cols,
532
+ rows: payload.rows
533
+ });
534
+ }
535
+ close() {
536
+ if (this.socket.readyState === WS_READY_STATE_CONNECTING) {
537
+ this.socket.addEventListener(
538
+ "open",
539
+ () => {
540
+ this.close();
541
+ },
542
+ { once: true }
543
+ );
544
+ return;
545
+ }
546
+ if (this.socket.readyState === WS_READY_STATE_OPEN) {
547
+ if (!this.closeSignalSent) {
548
+ this.closeSignalSent = true;
549
+ this.sendFrame({ type: "close" });
550
+ }
551
+ this.socket.close();
552
+ return;
553
+ }
554
+ if (this.socket.readyState !== WS_READY_STATE_CLOSED) {
555
+ this.socket.close();
556
+ }
557
+ }
558
+ async handleMessage(data) {
559
+ try {
560
+ if (typeof data === "string") {
561
+ const frame = parseProcessTerminalServerFrame(data);
562
+ if (!frame) {
563
+ this.emitError(new Error("Received invalid terminal control frame."));
564
+ return;
565
+ }
566
+ if (frame.type === "ready") {
567
+ for (const listener of this.readyListeners) {
568
+ listener(frame);
569
+ }
570
+ return;
571
+ }
572
+ if (frame.type === "exit") {
573
+ for (const listener of this.exitListeners) {
574
+ listener(frame);
575
+ }
576
+ return;
577
+ }
578
+ this.emitError(frame);
579
+ return;
580
+ }
581
+ const bytes = await decodeTerminalBytes(data);
582
+ for (const listener of this.dataListeners) {
583
+ listener(bytes);
584
+ }
585
+ } catch (error) {
586
+ this.emitError(error instanceof Error ? error : new Error(String(error)));
587
+ }
588
+ }
589
+ sendFrame(frame) {
590
+ if (this.socket.readyState !== WS_READY_STATE_OPEN) {
591
+ return;
592
+ }
593
+ this.socket.send(JSON.stringify(frame));
594
+ }
595
+ emitError(error) {
596
+ for (const listener of this.errorListeners) {
597
+ listener(error);
598
+ }
599
+ }
600
+ };
601
+ var WS_READY_STATE_CONNECTING = 0;
602
+ var WS_READY_STATE_OPEN = 1;
603
+ var WS_READY_STATE_CLOSED = 3;
380
604
  var SandboxAgent = class _SandboxAgent {
381
605
  baseUrl;
382
606
  token;
383
607
  fetcher;
384
608
  defaultHeaders;
609
+ healthWait;
610
+ healthWaitAbortController = new AbortController();
385
611
  persist;
386
612
  replayMaxEvents;
387
613
  replayMaxChars;
388
614
  spawnHandle;
615
+ healthPromise;
616
+ healthError;
617
+ disposed = false;
389
618
  liveConnections = /* @__PURE__ */ new Map();
390
619
  pendingLiveConnections = /* @__PURE__ */ new Map();
391
620
  sessionHandles = /* @__PURE__ */ new Map();
@@ -405,9 +634,11 @@ var SandboxAgent = class _SandboxAgent {
405
634
  }
406
635
  this.fetcher = resolvedFetch;
407
636
  this.defaultHeaders = options.headers;
637
+ this.healthWait = normalizeHealthWaitOptions(options.waitForHealth, options.signal);
408
638
  this.persist = options.persist ?? new InMemorySessionPersistDriver();
409
639
  this.replayMaxEvents = normalizePositiveInt(options.replayMaxEvents, DEFAULT_REPLAY_MAX_EVENTS);
410
640
  this.replayMaxChars = normalizePositiveInt(options.replayMaxChars, DEFAULT_REPLAY_MAX_CHARS);
641
+ this.startHealthWait();
411
642
  }
412
643
  static async connect(options) {
413
644
  return new _SandboxAgent(options);
@@ -425,6 +656,7 @@ var SandboxAgent = class _SandboxAgent {
425
656
  token: handle.token,
426
657
  fetch: options.fetch,
427
658
  headers: options.headers,
659
+ waitForHealth: false,
428
660
  persist: options.persist,
429
661
  replayMaxEvents: options.replayMaxEvents,
430
662
  replayMaxChars: options.replayMaxChars
@@ -433,6 +665,8 @@ var SandboxAgent = class _SandboxAgent {
433
665
  return client;
434
666
  }
435
667
  async dispose() {
668
+ this.disposed = true;
669
+ this.healthWaitAbortController.abort(createAbortError("SandboxAgent was disposed."));
436
670
  const connections = [...this.liveConnections.values()];
437
671
  this.liveConnections.clear();
438
672
  const pending = [...this.pendingLiveConnections.values()];
@@ -484,12 +718,32 @@ var SandboxAgent = class _SandboxAgent {
484
718
  agentSessionId: response.sessionId,
485
719
  lastConnectionId: live.connectionId,
486
720
  createdAt: nowMs(),
487
- sessionInit
721
+ sessionInit,
722
+ configOptions: cloneConfigOptions(response.configOptions),
723
+ modes: cloneModes(response.modes)
488
724
  };
489
725
  await this.persist.updateSession(record);
490
726
  this.nextSessionEventIndexBySession.set(record.id, 1);
491
727
  live.bindSession(record.id, record.agentSessionId);
492
- return this.upsertSessionHandle(record);
728
+ let session = this.upsertSessionHandle(record);
729
+ try {
730
+ if (request.mode) {
731
+ session = (await this.setSessionMode(session.id, request.mode)).session;
732
+ }
733
+ if (request.model) {
734
+ session = (await this.setSessionModel(session.id, request.model)).session;
735
+ }
736
+ if (request.thoughtLevel) {
737
+ session = (await this.setSessionThoughtLevel(session.id, request.thoughtLevel)).session;
738
+ }
739
+ } catch (err) {
740
+ try {
741
+ await this.destroySession(session.id);
742
+ } catch {
743
+ }
744
+ throw err;
745
+ }
746
+ return session;
493
747
  }
494
748
  async resumeSession(id) {
495
749
  const existing = await this.persist.getSession(id);
@@ -507,7 +761,9 @@ var SandboxAgent = class _SandboxAgent {
507
761
  ...existing,
508
762
  agentSessionId: recreated.sessionId,
509
763
  lastConnectionId: live.connectionId,
510
- destroyedAt: void 0
764
+ destroyedAt: void 0,
765
+ configOptions: cloneConfigOptions(recreated.configOptions),
766
+ modes: cloneModes(recreated.modes)
511
767
  };
512
768
  await this.persist.updateSession(updated);
513
769
  live.bindSession(updated.id, updated.agentSessionId);
@@ -517,15 +773,26 @@ var SandboxAgent = class _SandboxAgent {
517
773
  async resumeOrCreateSession(request) {
518
774
  const existing = await this.persist.getSession(request.id);
519
775
  if (existing) {
520
- return this.resumeSession(existing.id);
776
+ let session = await this.resumeSession(existing.id);
777
+ if (request.mode) {
778
+ session = (await this.setSessionMode(session.id, request.mode)).session;
779
+ }
780
+ if (request.model) {
781
+ session = (await this.setSessionModel(session.id, request.model)).session;
782
+ }
783
+ if (request.thoughtLevel) {
784
+ session = (await this.setSessionThoughtLevel(session.id, request.thoughtLevel)).session;
785
+ }
786
+ return session;
521
787
  }
522
788
  return this.createSession(request);
523
789
  }
524
790
  async destroySession(id) {
525
- const existing = await this.persist.getSession(id);
526
- if (!existing) {
527
- throw new Error(`session '${id}' not found`);
791
+ try {
792
+ await this.sendSessionMethodInternal(id, SESSION_CANCEL_METHOD, {}, {}, true);
793
+ } catch {
528
794
  }
795
+ const existing = await this.requireSessionRecord(id);
529
796
  const updated = {
530
797
  ...existing,
531
798
  destroyedAt: nowMs()
@@ -533,7 +800,132 @@ var SandboxAgent = class _SandboxAgent {
533
800
  await this.persist.updateSession(updated);
534
801
  return this.upsertSessionHandle(updated);
535
802
  }
803
+ async setSessionMode(sessionId, modeId) {
804
+ const mode = modeId.trim();
805
+ if (!mode) {
806
+ throw new Error("setSessionMode requires a non-empty modeId");
807
+ }
808
+ const record = await this.requireSessionRecord(sessionId);
809
+ const knownModeIds = extractKnownModeIds(record.modes);
810
+ if (knownModeIds.length > 0 && !knownModeIds.includes(mode)) {
811
+ throw new UnsupportedSessionValueError(sessionId, "mode", "mode", mode, knownModeIds);
812
+ }
813
+ try {
814
+ return await this.sendSessionMethodInternal(
815
+ sessionId,
816
+ "session/set_mode",
817
+ { modeId: mode },
818
+ {},
819
+ false
820
+ );
821
+ } catch (error) {
822
+ if (!(error instanceof AcpRpcError) || error.code !== -32601) {
823
+ throw error;
824
+ }
825
+ return this.setSessionCategoryValue(sessionId, "mode", mode);
826
+ }
827
+ }
828
+ async setSessionConfigOption(sessionId, configId, value) {
829
+ const resolvedConfigId = configId.trim();
830
+ if (!resolvedConfigId) {
831
+ throw new Error("setSessionConfigOption requires a non-empty configId");
832
+ }
833
+ const resolvedValue = value.trim();
834
+ if (!resolvedValue) {
835
+ throw new Error("setSessionConfigOption requires a non-empty value");
836
+ }
837
+ const options = await this.getSessionConfigOptions(sessionId);
838
+ const option = findConfigOptionById(options, resolvedConfigId);
839
+ if (!option) {
840
+ throw new UnsupportedSessionConfigOptionError(
841
+ sessionId,
842
+ resolvedConfigId,
843
+ options.map((item) => item.id)
844
+ );
845
+ }
846
+ const allowedValues = extractConfigValues(option);
847
+ if (allowedValues.length > 0 && !allowedValues.includes(resolvedValue)) {
848
+ throw new UnsupportedSessionValueError(
849
+ sessionId,
850
+ option.category ?? "uncategorized",
851
+ option.id,
852
+ resolvedValue,
853
+ allowedValues
854
+ );
855
+ }
856
+ return await this.sendSessionMethodInternal(
857
+ sessionId,
858
+ "session/set_config_option",
859
+ {
860
+ configId: resolvedConfigId,
861
+ value: resolvedValue
862
+ },
863
+ {},
864
+ false
865
+ );
866
+ }
867
+ async setSessionModel(sessionId, model) {
868
+ return this.setSessionCategoryValue(sessionId, "model", model);
869
+ }
870
+ async setSessionThoughtLevel(sessionId, thoughtLevel) {
871
+ return this.setSessionCategoryValue(sessionId, "thought_level", thoughtLevel);
872
+ }
873
+ async getSessionConfigOptions(sessionId) {
874
+ const record = await this.requireSessionRecord(sessionId);
875
+ const hydrated = await this.hydrateSessionConfigOptions(record.id, record);
876
+ return cloneConfigOptions(hydrated.configOptions) ?? [];
877
+ }
878
+ async getSessionModes(sessionId) {
879
+ const record = await this.requireSessionRecord(sessionId);
880
+ return cloneModes(record.modes);
881
+ }
882
+ async setSessionCategoryValue(sessionId, category, value) {
883
+ const resolvedValue = value.trim();
884
+ if (!resolvedValue) {
885
+ throw new Error(`setSession${toTitleCase(category)} requires a non-empty value`);
886
+ }
887
+ const options = await this.getSessionConfigOptions(sessionId);
888
+ const option = findConfigOptionByCategory(options, category);
889
+ if (!option) {
890
+ const categories = uniqueCategories(options);
891
+ throw new UnsupportedSessionCategoryError(sessionId, category, categories);
892
+ }
893
+ const allowedValues = extractConfigValues(option);
894
+ if (allowedValues.length > 0 && !allowedValues.includes(resolvedValue)) {
895
+ throw new UnsupportedSessionValueError(
896
+ sessionId,
897
+ category,
898
+ option.id,
899
+ resolvedValue,
900
+ allowedValues
901
+ );
902
+ }
903
+ return this.setSessionConfigOption(sessionId, option.id, resolvedValue);
904
+ }
905
+ async hydrateSessionConfigOptions(sessionId, snapshot) {
906
+ if (snapshot.configOptions !== void 0) {
907
+ return snapshot;
908
+ }
909
+ const info = await this.getAgent(snapshot.agent, { config: true });
910
+ const configOptions = normalizeSessionConfigOptions(info.configOptions) ?? [];
911
+ const record = await this.persist.getSession(sessionId);
912
+ if (!record) {
913
+ return { ...snapshot, configOptions };
914
+ }
915
+ const updated = {
916
+ ...record,
917
+ configOptions
918
+ };
919
+ await this.persist.updateSession(updated);
920
+ return updated;
921
+ }
536
922
  async sendSessionMethod(sessionId, method, params, options = {}) {
923
+ return this.sendSessionMethodInternal(sessionId, method, params, options, false);
924
+ }
925
+ async sendSessionMethodInternal(sessionId, method, params, options, allowManagedCancel) {
926
+ if (method === SESSION_CANCEL_METHOD && !allowManagedCancel) {
927
+ throw new Error(MANUAL_CANCEL_ERROR);
928
+ }
537
929
  const record = await this.persist.getSession(sessionId);
538
930
  if (!record) {
539
931
  throw new Error(`session '${sessionId}' not found`);
@@ -541,15 +933,73 @@ var SandboxAgent = class _SandboxAgent {
541
933
  const live = await this.getLiveConnection(record.agent);
542
934
  if (!live.hasBoundSession(record.id, record.agentSessionId)) {
543
935
  const restored = await this.resumeSession(record.id);
544
- return this.sendSessionMethod(restored.id, method, params, options);
936
+ return this.sendSessionMethodInternal(restored.id, method, params, options, allowManagedCancel);
545
937
  }
546
938
  const response = await live.sendSessionMethod(record.id, method, params, options);
939
+ await this.persistSessionStateFromMethod(record.id, method, params, response);
547
940
  const refreshed = await this.requireSessionRecord(record.id);
548
941
  return {
549
942
  session: this.upsertSessionHandle(refreshed),
550
943
  response
551
944
  };
552
945
  }
946
+ async persistSessionStateFromMethod(sessionId, method, params, response) {
947
+ const record = await this.persist.getSession(sessionId);
948
+ if (!record) {
949
+ return;
950
+ }
951
+ if (method === "session/set_config_option") {
952
+ const configId = typeof params.configId === "string" ? params.configId : null;
953
+ const value = typeof params.value === "string" ? params.value : null;
954
+ const updates = {};
955
+ const serverConfigOptions = extractConfigOptionsFromSetResponse(response);
956
+ if (serverConfigOptions) {
957
+ updates.configOptions = cloneConfigOptions(serverConfigOptions);
958
+ } else if (record.configOptions && configId && value) {
959
+ const updated = applyConfigOptionValue(record.configOptions, configId, value);
960
+ if (updated) {
961
+ updates.configOptions = updated;
962
+ }
963
+ }
964
+ if (configId && value) {
965
+ const source = updates.configOptions ?? record.configOptions;
966
+ const option = source ? findConfigOptionById(source, configId) : null;
967
+ if (option?.category === "mode") {
968
+ const nextModes = applyCurrentMode(record.modes, value);
969
+ if (nextModes) {
970
+ updates.modes = nextModes;
971
+ }
972
+ }
973
+ }
974
+ if (Object.keys(updates).length > 0) {
975
+ await this.persist.updateSession({ ...record, ...updates });
976
+ }
977
+ return;
978
+ }
979
+ if (method === "session/set_mode") {
980
+ const modeId = typeof params.modeId === "string" ? params.modeId : null;
981
+ if (!modeId) {
982
+ return;
983
+ }
984
+ const updates = {};
985
+ const nextModes = applyCurrentMode(record.modes, modeId);
986
+ if (nextModes) {
987
+ updates.modes = nextModes;
988
+ }
989
+ if (record.configOptions) {
990
+ const modeOption = findConfigOptionByCategory(record.configOptions, "mode");
991
+ if (modeOption) {
992
+ const updated = applyConfigOptionValue(record.configOptions, modeOption.id, modeId);
993
+ if (updated) {
994
+ updates.configOptions = updated;
995
+ }
996
+ }
997
+ }
998
+ if (Object.keys(updates).length > 0) {
999
+ await this.persist.updateSession({ ...record, ...updates });
1000
+ }
1001
+ }
1002
+ }
553
1003
  onSessionEvent(sessionId, listener) {
554
1004
  const listeners = this.eventListeners.get(sessionId) ?? /* @__PURE__ */ new Set();
555
1005
  listeners.add(listener);
@@ -566,16 +1016,16 @@ var SandboxAgent = class _SandboxAgent {
566
1016
  };
567
1017
  }
568
1018
  async getHealth() {
569
- return this.requestJson("GET", `${API_PREFIX}/health`);
1019
+ return this.requestHealth();
570
1020
  }
571
1021
  async listAgents(options) {
572
1022
  return this.requestJson("GET", `${API_PREFIX}/agents`, {
573
- query: options?.config ? { config: "true" } : void 0
1023
+ query: toAgentQuery(options)
574
1024
  });
575
1025
  }
576
1026
  async getAgent(agent, options) {
577
1027
  return this.requestJson("GET", `${API_PREFIX}/agents/${encodeURIComponent(agent)}`, {
578
- query: options?.config ? { config: "true" } : void 0
1028
+ query: toAgentQuery(options)
579
1029
  });
580
1030
  }
581
1031
  async installAgent(agent, request = {}) {
@@ -647,7 +1097,107 @@ var SandboxAgent = class _SandboxAgent {
647
1097
  async deleteSkillsConfig(query) {
648
1098
  await this.requestRaw("DELETE", `${API_PREFIX}/config/skills`, { query });
649
1099
  }
1100
+ async getProcessConfig() {
1101
+ return this.requestJson("GET", `${API_PREFIX}/processes/config`);
1102
+ }
1103
+ async setProcessConfig(config) {
1104
+ return this.requestJson("POST", `${API_PREFIX}/processes/config`, {
1105
+ body: config
1106
+ });
1107
+ }
1108
+ async createProcess(request) {
1109
+ return this.requestJson("POST", `${API_PREFIX}/processes`, {
1110
+ body: request
1111
+ });
1112
+ }
1113
+ async runProcess(request) {
1114
+ return this.requestJson("POST", `${API_PREFIX}/processes/run`, {
1115
+ body: request
1116
+ });
1117
+ }
1118
+ async listProcesses() {
1119
+ return this.requestJson("GET", `${API_PREFIX}/processes`);
1120
+ }
1121
+ async getProcess(id) {
1122
+ return this.requestJson("GET", `${API_PREFIX}/processes/${encodeURIComponent(id)}`);
1123
+ }
1124
+ async stopProcess(id, query) {
1125
+ return this.requestJson("POST", `${API_PREFIX}/processes/${encodeURIComponent(id)}/stop`, {
1126
+ query
1127
+ });
1128
+ }
1129
+ async killProcess(id, query) {
1130
+ return this.requestJson("POST", `${API_PREFIX}/processes/${encodeURIComponent(id)}/kill`, {
1131
+ query
1132
+ });
1133
+ }
1134
+ async deleteProcess(id) {
1135
+ await this.requestRaw("DELETE", `${API_PREFIX}/processes/${encodeURIComponent(id)}`);
1136
+ }
1137
+ async getProcessLogs(id, query = {}) {
1138
+ return this.requestJson("GET", `${API_PREFIX}/processes/${encodeURIComponent(id)}/logs`, {
1139
+ query
1140
+ });
1141
+ }
1142
+ async followProcessLogs(id, listener, query = {}) {
1143
+ const abortController = new AbortController();
1144
+ const response = await this.requestRaw(
1145
+ "GET",
1146
+ `${API_PREFIX}/processes/${encodeURIComponent(id)}/logs`,
1147
+ {
1148
+ query: { ...query, follow: true },
1149
+ accept: "text/event-stream",
1150
+ signal: abortController.signal
1151
+ }
1152
+ );
1153
+ if (!response.body) {
1154
+ abortController.abort();
1155
+ throw new Error("SSE stream is not readable in this environment.");
1156
+ }
1157
+ const closed = consumeProcessLogSse(response.body, listener, abortController.signal);
1158
+ return {
1159
+ close: () => abortController.abort(),
1160
+ closed
1161
+ };
1162
+ }
1163
+ async sendProcessInput(id, request) {
1164
+ return this.requestJson("POST", `${API_PREFIX}/processes/${encodeURIComponent(id)}/input`, {
1165
+ body: request
1166
+ });
1167
+ }
1168
+ async resizeProcessTerminal(id, request) {
1169
+ return this.requestJson(
1170
+ "POST",
1171
+ `${API_PREFIX}/processes/${encodeURIComponent(id)}/terminal/resize`,
1172
+ {
1173
+ body: request
1174
+ }
1175
+ );
1176
+ }
1177
+ buildProcessTerminalWebSocketUrl(id, options = {}) {
1178
+ return toWebSocketUrl(
1179
+ this.buildUrl(`${API_PREFIX}/processes/${encodeURIComponent(id)}/terminal/ws`, {
1180
+ access_token: options.accessToken ?? this.token
1181
+ })
1182
+ );
1183
+ }
1184
+ connectProcessTerminalWebSocket(id, options = {}) {
1185
+ const WebSocketCtor = options.WebSocket ?? globalThis.WebSocket;
1186
+ if (!WebSocketCtor) {
1187
+ throw new Error("WebSocket API is not available; provide a WebSocket implementation.");
1188
+ }
1189
+ return new WebSocketCtor(
1190
+ this.buildProcessTerminalWebSocketUrl(id, {
1191
+ accessToken: options.accessToken
1192
+ }),
1193
+ options.protocols
1194
+ );
1195
+ }
1196
+ connectProcessTerminal(id, options = {}) {
1197
+ return new ProcessTerminalSession(this.connectProcessTerminalWebSocket(id, options));
1198
+ }
650
1199
  async getLiveConnection(agent) {
1200
+ await this.awaitHealthy();
651
1201
  const existing = this.liveConnections.get(agent);
652
1202
  if (existing) {
653
1203
  return existing;
@@ -700,6 +1250,7 @@ var SandboxAgent = class _SandboxAgent {
700
1250
  payload: cloneEnvelope(envelope)
701
1251
  };
702
1252
  await this.persist.insertEvent(event);
1253
+ await this.persistSessionStateFromEvent(localSessionId, envelope, direction);
703
1254
  const listeners = this.eventListeners.get(localSessionId);
704
1255
  if (!listeners || listeners.size === 0) {
705
1256
  return;
@@ -708,6 +1259,46 @@ var SandboxAgent = class _SandboxAgent {
708
1259
  listener(event);
709
1260
  }
710
1261
  }
1262
+ async persistSessionStateFromEvent(sessionId, envelope, direction) {
1263
+ if (direction !== "inbound") {
1264
+ return;
1265
+ }
1266
+ if (envelopeMethod(envelope) !== "session/update") {
1267
+ return;
1268
+ }
1269
+ const update = envelopeSessionUpdate(envelope);
1270
+ if (!update || typeof update.sessionUpdate !== "string") {
1271
+ return;
1272
+ }
1273
+ const record = await this.persist.getSession(sessionId);
1274
+ if (!record) {
1275
+ return;
1276
+ }
1277
+ if (update.sessionUpdate === "config_option_update") {
1278
+ const configOptions = normalizeSessionConfigOptions(update.configOptions);
1279
+ if (configOptions) {
1280
+ await this.persist.updateSession({
1281
+ ...record,
1282
+ configOptions
1283
+ });
1284
+ }
1285
+ return;
1286
+ }
1287
+ if (update.sessionUpdate === "current_mode_update") {
1288
+ const modeId = typeof update.currentModeId === "string" ? update.currentModeId : null;
1289
+ if (!modeId) {
1290
+ return;
1291
+ }
1292
+ const nextModes = applyCurrentMode(record.modes, modeId);
1293
+ if (!nextModes) {
1294
+ return;
1295
+ }
1296
+ await this.persist.updateSession({
1297
+ ...record,
1298
+ modes: nextModes
1299
+ });
1300
+ }
1301
+ }
711
1302
  async allocateSessionEventIndex(sessionId) {
712
1303
  await this.ensureSessionEventIndexSeeded(sessionId);
713
1304
  const nextIndex = this.nextSessionEventIndexBySession.get(sessionId) ?? 1;
@@ -793,7 +1384,8 @@ var SandboxAgent = class _SandboxAgent {
793
1384
  body: options.body,
794
1385
  headers: options.headers,
795
1386
  accept: options.accept ?? "application/json",
796
- signal: options.signal
1387
+ signal: options.signal,
1388
+ skipReadyWait: options.skipReadyWait
797
1389
  });
798
1390
  if (response.status === 204) {
799
1391
  return void 0;
@@ -801,6 +1393,9 @@ var SandboxAgent = class _SandboxAgent {
801
1393
  return await response.json();
802
1394
  }
803
1395
  async requestRaw(method, path, options = {}) {
1396
+ if (!options.skipReadyWait) {
1397
+ await this.awaitHealthy(options.signal);
1398
+ }
804
1399
  const url = this.buildUrl(path, options.query);
805
1400
  const headers = this.buildHeaders(options.headers);
806
1401
  if (options.accept) {
@@ -830,6 +1425,64 @@ var SandboxAgent = class _SandboxAgent {
830
1425
  }
831
1426
  return response;
832
1427
  }
1428
+ startHealthWait() {
1429
+ if (!this.healthWait.enabled || this.healthPromise) {
1430
+ return;
1431
+ }
1432
+ this.healthPromise = this.runHealthWait().catch((error) => {
1433
+ this.healthError = error instanceof Error ? error : new Error(String(error));
1434
+ });
1435
+ }
1436
+ async awaitHealthy(signal) {
1437
+ if (!this.healthPromise) {
1438
+ throwIfAborted(signal);
1439
+ return;
1440
+ }
1441
+ await waitForAbortable(this.healthPromise, signal);
1442
+ throwIfAborted(signal);
1443
+ if (this.healthError) {
1444
+ throw this.healthError;
1445
+ }
1446
+ }
1447
+ async runHealthWait() {
1448
+ const signal = this.healthWait.enabled ? anyAbortSignal([this.healthWait.signal, this.healthWaitAbortController.signal]) : void 0;
1449
+ const startedAt = Date.now();
1450
+ const deadline = typeof this.healthWait.timeoutMs === "number" ? startedAt + this.healthWait.timeoutMs : void 0;
1451
+ let delayMs = HEALTH_WAIT_MIN_DELAY_MS;
1452
+ let nextLogAt = startedAt + HEALTH_WAIT_LOG_AFTER_MS;
1453
+ let lastError;
1454
+ while (!this.disposed && (deadline === void 0 || Date.now() < deadline)) {
1455
+ throwIfAborted(signal);
1456
+ try {
1457
+ const health = await this.requestHealth({ signal });
1458
+ if (health.status === "ok") {
1459
+ return;
1460
+ }
1461
+ lastError = new Error(`Unexpected health response: ${JSON.stringify(health)}`);
1462
+ } catch (error) {
1463
+ if (isAbortError(error)) {
1464
+ throw error;
1465
+ }
1466
+ lastError = error;
1467
+ }
1468
+ const now = Date.now();
1469
+ if (now >= nextLogAt) {
1470
+ const details = formatHealthWaitError(lastError);
1471
+ console.warn(
1472
+ `sandbox-agent at ${this.baseUrl} is not healthy after ${now - startedAt}ms; still waiting (${details})`
1473
+ );
1474
+ nextLogAt = now + HEALTH_WAIT_LOG_EVERY_MS;
1475
+ }
1476
+ await sleep(delayMs, signal);
1477
+ delayMs = Math.min(HEALTH_WAIT_MAX_DELAY_MS, delayMs * 2);
1478
+ }
1479
+ if (this.disposed) {
1480
+ return;
1481
+ }
1482
+ throw new Error(
1483
+ `Timed out waiting for sandbox-agent health after ${this.healthWait.timeoutMs}ms (${formatHealthWaitError(lastError)})`
1484
+ );
1485
+ }
833
1486
  buildHeaders(extra) {
834
1487
  const headers = new Headers(this.defaultHeaders ?? void 0);
835
1488
  if (this.token) {
@@ -853,7 +1506,75 @@ var SandboxAgent = class _SandboxAgent {
853
1506
  }
854
1507
  return url.toString();
855
1508
  }
1509
+ async requestHealth(options = {}) {
1510
+ return this.requestJson("GET", `${API_PREFIX}/health`, {
1511
+ signal: options.signal,
1512
+ skipReadyWait: true
1513
+ });
1514
+ }
856
1515
  };
1516
+ function parseProcessTerminalServerFrame(payload) {
1517
+ try {
1518
+ const parsed = JSON.parse(payload);
1519
+ if (!isRecord(parsed) || typeof parsed.type !== "string") {
1520
+ return null;
1521
+ }
1522
+ if (parsed.type === "ready" && typeof parsed.processId === "string") {
1523
+ return parsed;
1524
+ }
1525
+ if (parsed.type === "exit" && (parsed.exitCode === void 0 || parsed.exitCode === null || typeof parsed.exitCode === "number")) {
1526
+ return parsed;
1527
+ }
1528
+ if (parsed.type === "error" && typeof parsed.message === "string") {
1529
+ return parsed;
1530
+ }
1531
+ } catch {
1532
+ return null;
1533
+ }
1534
+ return null;
1535
+ }
1536
+ function encodeTerminalInput(data) {
1537
+ if (typeof data === "string") {
1538
+ return { data };
1539
+ }
1540
+ const bytes = encodeTerminalBytes(data);
1541
+ return {
1542
+ data: bytesToBase64(bytes),
1543
+ encoding: "base64"
1544
+ };
1545
+ }
1546
+ function encodeTerminalBytes(data) {
1547
+ if (data instanceof ArrayBuffer) {
1548
+ return new Uint8Array(data);
1549
+ }
1550
+ return new Uint8Array(data.buffer, data.byteOffset, data.byteLength).slice();
1551
+ }
1552
+ async function decodeTerminalBytes(data) {
1553
+ if (data instanceof ArrayBuffer) {
1554
+ return new Uint8Array(data);
1555
+ }
1556
+ if (ArrayBuffer.isView(data)) {
1557
+ return new Uint8Array(data.buffer, data.byteOffset, data.byteLength).slice();
1558
+ }
1559
+ if (typeof Blob !== "undefined" && data instanceof Blob) {
1560
+ return new Uint8Array(await data.arrayBuffer());
1561
+ }
1562
+ throw new Error(`Unsupported terminal frame payload: ${String(data)}`);
1563
+ }
1564
+ function bytesToBase64(bytes) {
1565
+ if (typeof Buffer !== "undefined") {
1566
+ return Buffer.from(bytes).toString("base64");
1567
+ }
1568
+ if (typeof btoa === "function") {
1569
+ let binary = "";
1570
+ const chunkSize = 32768;
1571
+ for (let index = 0; index < bytes.length; index += chunkSize) {
1572
+ binary += String.fromCharCode(...bytes.subarray(index, index + chunkSize));
1573
+ }
1574
+ return btoa(binary);
1575
+ }
1576
+ throw new Error("Base64 encoding is not available in this environment.");
1577
+ }
857
1578
  async function autoAuthenticate(acp, methods) {
858
1579
  const envBased = methods.find(
859
1580
  (m) => m.id === "codex-api-key" || m.id === "openai-api-key" || m.id === "anthropic-api-key"
@@ -866,6 +1587,15 @@ async function autoAuthenticate(acp, methods) {
866
1587
  } catch {
867
1588
  }
868
1589
  }
1590
+ function toAgentQuery(options) {
1591
+ if (!options) {
1592
+ return void 0;
1593
+ }
1594
+ return {
1595
+ config: options.config,
1596
+ no_cache: options.noCache
1597
+ };
1598
+ }
869
1599
  function normalizeSessionInit(value) {
870
1600
  if (!value) {
871
1601
  return {
@@ -973,6 +1703,20 @@ function normalizePositiveInt(value, fallback) {
973
1703
  }
974
1704
  return Math.floor(value);
975
1705
  }
1706
+ function normalizeHealthWaitOptions(value, signal) {
1707
+ if (value === false) {
1708
+ return { enabled: false };
1709
+ }
1710
+ if (value === true || value === void 0) {
1711
+ return { enabled: true, signal };
1712
+ }
1713
+ const timeoutMs = typeof value.timeoutMs === "number" && Number.isFinite(value.timeoutMs) && value.timeoutMs > 0 ? Math.floor(value.timeoutMs) : void 0;
1714
+ return {
1715
+ enabled: true,
1716
+ signal,
1717
+ timeoutMs
1718
+ };
1719
+ }
976
1720
  function normalizeSpawnOptions(spawn, defaultEnabled) {
977
1721
  if (spawn === false) {
978
1722
  return { enabled: false };
@@ -996,9 +1740,282 @@ async function readProblem(response) {
996
1740
  return void 0;
997
1741
  }
998
1742
  }
1743
+ function normalizeSessionConfigOptions(value) {
1744
+ if (!Array.isArray(value)) {
1745
+ return void 0;
1746
+ }
1747
+ const normalized = value.filter(isSessionConfigOption);
1748
+ return cloneConfigOptions(normalized) ?? [];
1749
+ }
1750
+ function extractConfigOptionsFromSetResponse(response) {
1751
+ if (!isRecord(response)) {
1752
+ return void 0;
1753
+ }
1754
+ return normalizeSessionConfigOptions(response.configOptions);
1755
+ }
1756
+ function findConfigOptionByCategory(options, category) {
1757
+ return options.find((option) => option.category === category);
1758
+ }
1759
+ function findConfigOptionById(options, configId) {
1760
+ return options.find((option) => option.id === configId);
1761
+ }
1762
+ function uniqueCategories(options) {
1763
+ return [...new Set(options.map((option) => option.category).filter((value) => !!value))].sort();
1764
+ }
1765
+ function extractConfigValues(option) {
1766
+ if (!isRecord(option) || option.type !== "select" || !Array.isArray(option.options)) {
1767
+ return [];
1768
+ }
1769
+ const values = [];
1770
+ for (const entry of option.options) {
1771
+ if (isRecord(entry) && typeof entry.value === "string") {
1772
+ values.push(entry.value);
1773
+ continue;
1774
+ }
1775
+ if (isRecord(entry) && Array.isArray(entry.options)) {
1776
+ for (const nested of entry.options) {
1777
+ if (isRecord(nested) && typeof nested.value === "string") {
1778
+ values.push(nested.value);
1779
+ }
1780
+ }
1781
+ }
1782
+ }
1783
+ return [...new Set(values)];
1784
+ }
1785
+ function extractKnownModeIds(modes) {
1786
+ if (!modes || !Array.isArray(modes.availableModes)) {
1787
+ return [];
1788
+ }
1789
+ return modes.availableModes.map((mode) => typeof mode.id === "string" ? mode.id : null).filter((value) => !!value);
1790
+ }
1791
+ function applyCurrentMode(modes, currentModeId) {
1792
+ if (modes && Array.isArray(modes.availableModes)) {
1793
+ return {
1794
+ ...modes,
1795
+ currentModeId
1796
+ };
1797
+ }
1798
+ return {
1799
+ currentModeId,
1800
+ availableModes: []
1801
+ };
1802
+ }
1803
+ function applyConfigOptionValue(configOptions, configId, value) {
1804
+ const idx = configOptions.findIndex((o) => o.id === configId);
1805
+ if (idx === -1) {
1806
+ return null;
1807
+ }
1808
+ const updated = cloneConfigOptions(configOptions) ?? [];
1809
+ updated[idx] = { ...updated[idx], currentValue: value };
1810
+ return updated;
1811
+ }
1812
+ function envelopeSessionUpdate(message) {
1813
+ if (!isRecord(message) || !("params" in message) || !isRecord(message.params)) {
1814
+ return null;
1815
+ }
1816
+ if (!("update" in message.params) || !isRecord(message.params.update)) {
1817
+ return null;
1818
+ }
1819
+ return message.params.update;
1820
+ }
1821
+ function cloneConfigOptions(value) {
1822
+ if (!value) {
1823
+ return void 0;
1824
+ }
1825
+ return JSON.parse(JSON.stringify(value));
1826
+ }
1827
+ function cloneModes(value) {
1828
+ if (!value) {
1829
+ return null;
1830
+ }
1831
+ return JSON.parse(JSON.stringify(value));
1832
+ }
1833
+ function isSessionConfigOption(value) {
1834
+ return isRecord(value) && typeof value.id === "string" && typeof value.name === "string" && typeof value.type === "string";
1835
+ }
1836
+ function toTitleCase(input) {
1837
+ if (!input) {
1838
+ return "";
1839
+ }
1840
+ return input.split(/[_\s-]+/).filter(Boolean).map((part) => part[0].toUpperCase() + part.slice(1)).join("");
1841
+ }
1842
+ function formatHealthWaitError(error) {
1843
+ if (error instanceof Error && error.message) {
1844
+ return error.message;
1845
+ }
1846
+ if (error === void 0 || error === null) {
1847
+ return "unknown error";
1848
+ }
1849
+ return String(error);
1850
+ }
1851
+ function anyAbortSignal(signals) {
1852
+ const active = signals.filter((signal) => Boolean(signal));
1853
+ if (active.length === 0) {
1854
+ return void 0;
1855
+ }
1856
+ if (active.length === 1) {
1857
+ return active[0];
1858
+ }
1859
+ const controller = new AbortController();
1860
+ const onAbort = (event) => {
1861
+ cleanup();
1862
+ const signal = event.target;
1863
+ controller.abort(signal.reason ?? createAbortError());
1864
+ };
1865
+ const cleanup = () => {
1866
+ for (const signal of active) {
1867
+ signal.removeEventListener("abort", onAbort);
1868
+ }
1869
+ };
1870
+ for (const signal of active) {
1871
+ if (signal.aborted) {
1872
+ controller.abort(signal.reason ?? createAbortError());
1873
+ return controller.signal;
1874
+ }
1875
+ }
1876
+ for (const signal of active) {
1877
+ signal.addEventListener("abort", onAbort, { once: true });
1878
+ }
1879
+ return controller.signal;
1880
+ }
1881
+ function throwIfAborted(signal) {
1882
+ if (!signal?.aborted) {
1883
+ return;
1884
+ }
1885
+ throw signal.reason instanceof Error ? signal.reason : createAbortError(signal.reason);
1886
+ }
1887
+ async function waitForAbortable(promise, signal) {
1888
+ if (!signal) {
1889
+ return promise;
1890
+ }
1891
+ throwIfAborted(signal);
1892
+ return new Promise((resolve, reject) => {
1893
+ const onAbort = () => {
1894
+ cleanup();
1895
+ reject(signal.reason instanceof Error ? signal.reason : createAbortError(signal.reason));
1896
+ };
1897
+ const cleanup = () => {
1898
+ signal.removeEventListener("abort", onAbort);
1899
+ };
1900
+ signal.addEventListener("abort", onAbort, { once: true });
1901
+ promise.then(
1902
+ (value) => {
1903
+ cleanup();
1904
+ resolve(value);
1905
+ },
1906
+ (error) => {
1907
+ cleanup();
1908
+ reject(error);
1909
+ }
1910
+ );
1911
+ });
1912
+ }
1913
+ async function consumeProcessLogSse(body, listener, signal) {
1914
+ const reader = body.getReader();
1915
+ const decoder = new TextDecoder();
1916
+ let buffer = "";
1917
+ try {
1918
+ while (!signal.aborted) {
1919
+ const { done, value } = await reader.read();
1920
+ if (done) {
1921
+ return;
1922
+ }
1923
+ buffer += decoder.decode(value, { stream: true }).replace(/\r\n/g, "\n");
1924
+ let separatorIndex = buffer.indexOf("\n\n");
1925
+ while (separatorIndex !== -1) {
1926
+ const chunk = buffer.slice(0, separatorIndex);
1927
+ buffer = buffer.slice(separatorIndex + 2);
1928
+ const entry = parseProcessLogSseChunk(chunk);
1929
+ if (entry) {
1930
+ listener(entry);
1931
+ }
1932
+ separatorIndex = buffer.indexOf("\n\n");
1933
+ }
1934
+ }
1935
+ } catch (error) {
1936
+ if (signal.aborted || isAbortError(error)) {
1937
+ return;
1938
+ }
1939
+ throw error;
1940
+ } finally {
1941
+ reader.releaseLock();
1942
+ }
1943
+ }
1944
+ function parseProcessLogSseChunk(chunk) {
1945
+ if (!chunk.trim()) {
1946
+ return null;
1947
+ }
1948
+ let eventName = "message";
1949
+ const dataLines = [];
1950
+ for (const line of chunk.split("\n")) {
1951
+ if (!line || line.startsWith(":")) {
1952
+ continue;
1953
+ }
1954
+ if (line.startsWith("event:")) {
1955
+ eventName = line.slice(6).trim();
1956
+ continue;
1957
+ }
1958
+ if (line.startsWith("data:")) {
1959
+ dataLines.push(line.slice(5).trimStart());
1960
+ }
1961
+ }
1962
+ if (eventName !== "log") {
1963
+ return null;
1964
+ }
1965
+ const data = dataLines.join("\n");
1966
+ if (!data.trim()) {
1967
+ return null;
1968
+ }
1969
+ return JSON.parse(data);
1970
+ }
1971
+ function toWebSocketUrl(url) {
1972
+ const parsed = new URL(url);
1973
+ if (parsed.protocol === "http:") {
1974
+ parsed.protocol = "ws:";
1975
+ } else if (parsed.protocol === "https:") {
1976
+ parsed.protocol = "wss:";
1977
+ }
1978
+ return parsed.toString();
1979
+ }
1980
+ function isAbortError(error) {
1981
+ return error instanceof Error && error.name === "AbortError";
1982
+ }
1983
+ function createAbortError(reason) {
1984
+ if (reason instanceof Error) {
1985
+ return reason;
1986
+ }
1987
+ const message = typeof reason === "string" ? reason : "This operation was aborted.";
1988
+ if (typeof DOMException !== "undefined") {
1989
+ return new DOMException(message, "AbortError");
1990
+ }
1991
+ const error = new Error(message);
1992
+ error.name = "AbortError";
1993
+ return error;
1994
+ }
1995
+ function sleep(ms, signal) {
1996
+ if (!signal) {
1997
+ return new Promise((resolve) => setTimeout(resolve, ms));
1998
+ }
1999
+ throwIfAborted(signal);
2000
+ return new Promise((resolve, reject) => {
2001
+ const timer = setTimeout(() => {
2002
+ cleanup();
2003
+ resolve();
2004
+ }, ms);
2005
+ const onAbort = () => {
2006
+ cleanup();
2007
+ reject(signal.reason instanceof Error ? signal.reason : createAbortError(signal.reason));
2008
+ };
2009
+ const cleanup = () => {
2010
+ clearTimeout(timer);
2011
+ signal.removeEventListener("abort", onAbort);
2012
+ };
2013
+ signal.addEventListener("abort", onAbort, { once: true });
2014
+ });
2015
+ }
999
2016
 
1000
2017
  // src/index.ts
1001
- import { AcpRpcError } from "acp-http-client";
2018
+ import { AcpRpcError as AcpRpcError2 } from "acp-http-client";
1002
2019
 
1003
2020
  // src/inspector.ts
1004
2021
  function buildInspectorUrl(options) {
@@ -1014,12 +2031,16 @@ function buildInspectorUrl(options) {
1014
2031
  return `${normalized}/ui/${queryString ? `?${queryString}` : ""}`;
1015
2032
  }
1016
2033
  export {
1017
- AcpRpcError,
2034
+ AcpRpcError2 as AcpRpcError,
1018
2035
  InMemorySessionPersistDriver,
1019
2036
  LiveAcpConnection,
2037
+ ProcessTerminalSession,
1020
2038
  SandboxAgent,
1021
2039
  SandboxAgentError,
1022
2040
  Session,
2041
+ UnsupportedSessionCategoryError,
2042
+ UnsupportedSessionConfigOptionError,
2043
+ UnsupportedSessionValueError,
1023
2044
  buildInspectorUrl
1024
2045
  };
1025
2046
  //# sourceMappingURL=index.js.map