sandbox-agent 0.3.0 → 0.3.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/index.d.ts CHANGED
@@ -1,4 +1,4 @@
1
- import { NewSessionRequest, SessionConfigOption, SessionModeState, AnyMessage, AcpEnvelopeDirection, NewSessionResponse, PromptRequest, PromptResponse, SetSessionModeResponse, SetSessionConfigOptionResponse } from 'acp-http-client';
1
+ import { NewSessionRequest, SessionConfigOption, SessionModeState, AnyMessage, AcpEnvelopeDirection, RequestPermissionRequest, RequestPermissionResponse, NewSessionResponse, PermissionOptionKind, PromptRequest, PromptResponse, SetSessionModeResponse, SetSessionConfigOptionResponse } from 'acp-http-client';
2
2
  export { AcpRpcError } from 'acp-http-client';
3
3
 
4
4
  type SandboxAgentSpawnLogMode = "inherit" | "pipe" | "silent";
@@ -1488,8 +1488,25 @@ interface SessionSendOptions {
1488
1488
  notification?: boolean;
1489
1489
  }
1490
1490
  type SessionEventListener = (event: SessionEvent) => void;
1491
+ type PermissionReply = "once" | "always" | "reject";
1492
+ type PermissionRequestListener = (request: SessionPermissionRequest) => void;
1491
1493
  type ProcessLogListener = (entry: ProcessLogEntry) => void;
1492
1494
  type ProcessLogFollowQuery = Omit<ProcessLogsQuery, "follow">;
1495
+ interface SessionPermissionRequestOption {
1496
+ optionId: string;
1497
+ name: string;
1498
+ kind: PermissionOptionKind;
1499
+ }
1500
+ interface SessionPermissionRequest {
1501
+ id: string;
1502
+ createdAt: number;
1503
+ sessionId: string;
1504
+ agentSessionId: string;
1505
+ availableReplies: PermissionReply[];
1506
+ options: SessionPermissionRequestOption[];
1507
+ toolCall: RequestPermissionRequest["toolCall"];
1508
+ rawRequest: RequestPermissionRequest;
1509
+ }
1493
1510
  interface AgentQueryOptions {
1494
1511
  config?: boolean;
1495
1512
  noCache?: boolean;
@@ -1532,6 +1549,12 @@ declare class UnsupportedSessionConfigOptionError extends Error {
1532
1549
  readonly availableConfigIds: string[];
1533
1550
  constructor(sessionId: string, configId: string, availableConfigIds: string[]);
1534
1551
  }
1552
+ declare class UnsupportedPermissionReplyError extends Error {
1553
+ readonly permissionId: string;
1554
+ readonly requestedReply: PermissionReply;
1555
+ readonly availableReplies: PermissionReply[];
1556
+ constructor(permissionId: string, requestedReply: PermissionReply, availableReplies: PermissionReply[]);
1557
+ }
1535
1558
  declare class Session {
1536
1559
  private record;
1537
1560
  private readonly sandbox;
@@ -1543,7 +1566,7 @@ declare class Session {
1543
1566
  get createdAt(): number;
1544
1567
  get destroyedAt(): number | undefined;
1545
1568
  refresh(): Promise<Session>;
1546
- send(method: string, params?: Record<string, unknown>, options?: SessionSendOptions): Promise<unknown>;
1569
+ rawSend(method: string, params?: Record<string, unknown>, options?: SessionSendOptions): Promise<unknown>;
1547
1570
  prompt(prompt: PromptRequest["prompt"]): Promise<PromptResponse>;
1548
1571
  setMode(modeId: string): Promise<SetSessionModeResponse | void>;
1549
1572
  setConfigOption(configId: string, value: string): Promise<SetSessionConfigOptionResponse>;
@@ -1552,6 +1575,9 @@ declare class Session {
1552
1575
  getConfigOptions(): Promise<SessionConfigOption[]>;
1553
1576
  getModes(): Promise<SessionModeState | null>;
1554
1577
  onEvent(listener: SessionEventListener): () => void;
1578
+ onPermissionRequest(listener: PermissionRequestListener): () => void;
1579
+ respondPermission(permissionId: string, reply: PermissionReply): Promise<void>;
1580
+ rawRespondPermission(permissionId: string, response: RequestPermissionResponse): Promise<void>;
1555
1581
  toRecord(): SessionRecord;
1556
1582
  apply(record: SessionRecord): void;
1557
1583
  }
@@ -1567,6 +1593,7 @@ declare class LiveAcpConnection {
1567
1593
  private lastAdapterExit;
1568
1594
  private lastAdapterExitAt;
1569
1595
  private readonly onObservedEnvelope;
1596
+ private readonly onPermissionRequest;
1570
1597
  private constructor();
1571
1598
  static create(options: {
1572
1599
  baseUrl: string;
@@ -1576,6 +1603,7 @@ declare class LiveAcpConnection {
1576
1603
  agent: string;
1577
1604
  serverId: string;
1578
1605
  onObservedEnvelope: (connection: LiveAcpConnection, envelope: AnyMessage, direction: AcpEnvelopeDirection, localSessionId: string | null) => void;
1606
+ onPermissionRequest: (connection: LiveAcpConnection, localSessionId: string, agentSessionId: string, request: RequestPermissionRequest) => Promise<RequestPermissionResponse>;
1579
1607
  }): Promise<LiveAcpConnection>;
1580
1608
  close(): Promise<void>;
1581
1609
  hasBoundSession(localSessionId: string, agentSessionId?: string): boolean;
@@ -1585,6 +1613,7 @@ declare class LiveAcpConnection {
1585
1613
  sendSessionMethod(localSessionId: string, method: string, params: Record<string, unknown>, options: SessionSendOptions): Promise<unknown>;
1586
1614
  private handleEnvelope;
1587
1615
  private handleAdapterNotification;
1616
+ private handlePermissionRequest;
1588
1617
  private resolveSessionId;
1589
1618
  private localFromEnvelopeParams;
1590
1619
  }
@@ -1629,6 +1658,8 @@ declare class SandboxAgent {
1629
1658
  private readonly pendingLiveConnections;
1630
1659
  private readonly sessionHandles;
1631
1660
  private readonly eventListeners;
1661
+ private readonly permissionListeners;
1662
+ private readonly pendingPermissionRequests;
1632
1663
  private readonly nextSessionEventIndexBySession;
1633
1664
  private readonly seedSessionEventIndexBySession;
1634
1665
  constructor(options: SandboxAgentConnectOptions);
@@ -1662,13 +1693,16 @@ declare class SandboxAgent {
1662
1693
  getSessionModes(sessionId: string): Promise<SessionModeState | null>;
1663
1694
  private setSessionCategoryValue;
1664
1695
  private hydrateSessionConfigOptions;
1665
- sendSessionMethod(sessionId: string, method: string, params: Record<string, unknown>, options?: SessionSendOptions): Promise<{
1696
+ rawSendSessionMethod(sessionId: string, method: string, params: Record<string, unknown>, options?: SessionSendOptions): Promise<{
1666
1697
  session: Session;
1667
1698
  response: unknown;
1668
1699
  }>;
1669
1700
  private sendSessionMethodInternal;
1670
1701
  private persistSessionStateFromMethod;
1671
1702
  onSessionEvent(sessionId: string, listener: SessionEventListener): () => void;
1703
+ onPermissionRequest(sessionId: string, listener: PermissionRequestListener): () => void;
1704
+ respondPermission(permissionId: string, reply: PermissionReply): Promise<void>;
1705
+ rawRespondPermission(permissionId: string, response: RequestPermissionResponse): Promise<void>;
1672
1706
  getHealth(): Promise<HealthResponse>;
1673
1707
  listAgents(options?: AgentQueryOptions): Promise<AgentListResponse>;
1674
1708
  getAgent(agent: string, options?: AgentQueryOptions): Promise<AgentInfo>;
@@ -1713,6 +1747,9 @@ declare class SandboxAgent {
1713
1747
  private collectReplayEvents;
1714
1748
  private upsertSessionHandle;
1715
1749
  private requireSessionRecord;
1750
+ private enqueuePermissionRequest;
1751
+ private resolvePendingPermission;
1752
+ private cancelPendingPermissionsForSession;
1716
1753
  private requestJson;
1717
1754
  private requestRaw;
1718
1755
  private startHealthWait;
@@ -1744,4 +1781,4 @@ interface InspectorUrlOptions {
1744
1781
  */
1745
1782
  declare function buildInspectorUrl(options: InspectorUrlOptions): string;
1746
1783
 
1747
- export { type AcpEnvelope, type AcpServerInfo, type AcpServerListResponse, type AgentInfo, type AgentInstallRequest, type AgentInstallResponse, type AgentListResponse, type AgentQuery, type AgentQueryOptions, type FsActionResponse, type FsDeleteQuery, type FsEntriesQuery, type FsEntry, type FsMoveRequest, type FsMoveResponse, type FsPathQuery, type FsStat, type FsUploadBatchQuery, type FsUploadBatchResponse, type FsWriteResponse, type HealthResponse, InMemorySessionPersistDriver, type InMemorySessionPersistDriverOptions, type InspectorUrlOptions, type ListEventsRequest, type ListPage, type ListPageRequest, LiveAcpConnection, type McpConfigQuery, type McpServerConfig, type ProblemDetails, type ProcessConfig, type ProcessCreateRequest, type ProcessInfo, type ProcessInputRequest, type ProcessInputResponse, type ProcessListResponse, type ProcessLogEntry, type ProcessLogFollowQuery, type ProcessLogListener, type ProcessLogSubscription, type ProcessLogsQuery, type ProcessLogsResponse, type ProcessLogsStream, type ProcessRunRequest, type ProcessRunResponse, type ProcessSignalQuery, type ProcessState, type ProcessTerminalClientFrame, type ProcessTerminalConnectOptions, type ProcessTerminalErrorFrame, type ProcessTerminalExitFrame, type ProcessTerminalReadyFrame, type ProcessTerminalResizeRequest, type ProcessTerminalResizeResponse, type ProcessTerminalServerFrame, ProcessTerminalSession, type ProcessTerminalSessionOptions, type ProcessTerminalWebSocketUrlOptions, SandboxAgent, type SandboxAgentConnectOptions, SandboxAgentError, type SandboxAgentHealthWaitOptions, type SandboxAgentSpawnLogMode, type SandboxAgentSpawnOptions, type SandboxAgentStartOptions, Session, type SessionCreateRequest, type SessionEvent, type SessionEventListener, type SessionPersistDriver, type SessionRecord, type SessionResumeOrCreateRequest, type SessionSendOptions, type SkillsConfig, type SkillsConfigQuery, type TerminalErrorStatus, type TerminalExitStatus, type TerminalReadyStatus, type TerminalResizePayload, type TerminalStatusMessage, UnsupportedSessionCategoryError, UnsupportedSessionConfigOptionError, UnsupportedSessionValueError, buildInspectorUrl };
1784
+ export { type AcpEnvelope, type AcpServerInfo, type AcpServerListResponse, type AgentInfo, type AgentInstallRequest, type AgentInstallResponse, type AgentListResponse, type AgentQuery, type AgentQueryOptions, type FsActionResponse, type FsDeleteQuery, type FsEntriesQuery, type FsEntry, type FsMoveRequest, type FsMoveResponse, type FsPathQuery, type FsStat, type FsUploadBatchQuery, type FsUploadBatchResponse, type FsWriteResponse, type HealthResponse, InMemorySessionPersistDriver, type InMemorySessionPersistDriverOptions, type InspectorUrlOptions, type ListEventsRequest, type ListPage, type ListPageRequest, LiveAcpConnection, type McpConfigQuery, type McpServerConfig, type PermissionReply, type PermissionRequestListener, type ProblemDetails, type ProcessConfig, type ProcessCreateRequest, type ProcessInfo, type ProcessInputRequest, type ProcessInputResponse, type ProcessListResponse, type ProcessLogEntry, type ProcessLogFollowQuery, type ProcessLogListener, type ProcessLogSubscription, type ProcessLogsQuery, type ProcessLogsResponse, type ProcessLogsStream, type ProcessRunRequest, type ProcessRunResponse, type ProcessSignalQuery, type ProcessState, type ProcessTerminalClientFrame, type ProcessTerminalConnectOptions, type ProcessTerminalErrorFrame, type ProcessTerminalExitFrame, type ProcessTerminalReadyFrame, type ProcessTerminalResizeRequest, type ProcessTerminalResizeResponse, type ProcessTerminalServerFrame, ProcessTerminalSession, type ProcessTerminalSessionOptions, type ProcessTerminalWebSocketUrlOptions, SandboxAgent, type SandboxAgentConnectOptions, SandboxAgentError, type SandboxAgentHealthWaitOptions, type SandboxAgentSpawnLogMode, type SandboxAgentSpawnOptions, type SandboxAgentStartOptions, Session, type SessionCreateRequest, type SessionEvent, type SessionEventListener, type SessionPermissionRequest, type SessionPermissionRequestOption, type SessionPersistDriver, type SessionRecord, type SessionResumeOrCreateRequest, type SessionSendOptions, type SkillsConfig, type SkillsConfigQuery, type TerminalErrorStatus, type TerminalExitStatus, type TerminalReadyStatus, type TerminalResizePayload, type TerminalStatusMessage, UnsupportedPermissionReplyError, UnsupportedSessionCategoryError, UnsupportedSessionConfigOptionError, UnsupportedSessionValueError, buildInspectorUrl };
package/dist/index.js CHANGED
@@ -192,6 +192,20 @@ var UnsupportedSessionConfigOptionError = class extends Error {
192
192
  this.availableConfigIds = availableConfigIds;
193
193
  }
194
194
  };
195
+ var UnsupportedPermissionReplyError = class extends Error {
196
+ permissionId;
197
+ requestedReply;
198
+ availableReplies;
199
+ constructor(permissionId, requestedReply, availableReplies) {
200
+ super(
201
+ `Permission '${permissionId}' does not support reply '${requestedReply}'. Available replies: ${availableReplies.join(", ") || "(none)"}`
202
+ );
203
+ this.name = "UnsupportedPermissionReplyError";
204
+ this.permissionId = permissionId;
205
+ this.requestedReply = requestedReply;
206
+ this.availableReplies = availableReplies;
207
+ }
208
+ };
195
209
  var Session = class {
196
210
  record;
197
211
  sandbox;
@@ -225,13 +239,13 @@ var Session = class {
225
239
  this.apply(latest.toRecord());
226
240
  return this;
227
241
  }
228
- async send(method, params = {}, options = {}) {
229
- const updated = await this.sandbox.sendSessionMethod(this.id, method, params, options);
242
+ async rawSend(method, params = {}, options = {}) {
243
+ const updated = await this.sandbox.rawSendSessionMethod(this.id, method, params, options);
230
244
  this.apply(updated.session.toRecord());
231
245
  return updated.response;
232
246
  }
233
247
  async prompt(prompt) {
234
- const response = await this.send("session/prompt", { prompt });
248
+ const response = await this.rawSend("session/prompt", { prompt });
235
249
  return response;
236
250
  }
237
251
  async setMode(modeId) {
@@ -263,6 +277,15 @@ var Session = class {
263
277
  onEvent(listener) {
264
278
  return this.sandbox.onSessionEvent(this.id, listener);
265
279
  }
280
+ onPermissionRequest(listener) {
281
+ return this.sandbox.onPermissionRequest(this.id, listener);
282
+ }
283
+ async respondPermission(permissionId, reply) {
284
+ await this.sandbox.respondPermission(permissionId, reply);
285
+ }
286
+ async rawRespondPermission(permissionId, response) {
287
+ await this.sandbox.rawRespondPermission(permissionId, response);
288
+ }
266
289
  toRecord() {
267
290
  return { ...this.record };
268
291
  }
@@ -282,11 +305,13 @@ var LiveAcpConnection = class _LiveAcpConnection {
282
305
  lastAdapterExit = null;
283
306
  lastAdapterExitAt = 0;
284
307
  onObservedEnvelope;
285
- constructor(agent, connectionId, acp, onObservedEnvelope) {
308
+ onPermissionRequest;
309
+ constructor(agent, connectionId, acp, onObservedEnvelope, onPermissionRequest) {
286
310
  this.agent = agent;
287
311
  this.connectionId = connectionId;
288
312
  this.acp = acp;
289
313
  this.onObservedEnvelope = onObservedEnvelope;
314
+ this.onPermissionRequest = onPermissionRequest;
290
315
  }
291
316
  static async create(options) {
292
317
  const connectionId = randomId();
@@ -301,6 +326,12 @@ var LiveAcpConnection = class _LiveAcpConnection {
301
326
  bootstrapQuery: { agent: options.agent }
302
327
  },
303
328
  client: {
329
+ requestPermission: async (request) => {
330
+ if (!live) {
331
+ return cancelledPermissionResponse();
332
+ }
333
+ return live.handlePermissionRequest(request);
334
+ },
304
335
  sessionUpdate: async (_notification) => {
305
336
  },
306
337
  extNotification: async (method, params) => {
@@ -315,7 +346,13 @@ var LiveAcpConnection = class _LiveAcpConnection {
315
346
  live.handleEnvelope(envelope, direction);
316
347
  }
317
348
  });
318
- live = new _LiveAcpConnection(options.agent, connectionId, acp, options.onObservedEnvelope);
349
+ live = new _LiveAcpConnection(
350
+ options.agent,
351
+ connectionId,
352
+ acp,
353
+ options.onObservedEnvelope,
354
+ options.onPermissionRequest
355
+ );
319
356
  const initResult = await acp.initialize({
320
357
  protocolVersion: PROTOCOL_VERSION,
321
358
  clientInfo: {
@@ -420,6 +457,19 @@ var LiveAcpConnection = class _LiveAcpConnection {
420
457
  };
421
458
  this.lastAdapterExitAt = Date.now();
422
459
  }
460
+ async handlePermissionRequest(request) {
461
+ const agentSessionId = request.sessionId;
462
+ const localSessionId = this.localByAgentSessionId.get(agentSessionId);
463
+ if (!localSessionId) {
464
+ return cancelledPermissionResponse();
465
+ }
466
+ return this.onPermissionRequest(
467
+ this,
468
+ localSessionId,
469
+ agentSessionId,
470
+ clonePermissionRequest(request)
471
+ );
472
+ }
423
473
  resolveSessionId(envelope, direction) {
424
474
  const id = envelopeId(envelope);
425
475
  const method = envelopeMethod(envelope);
@@ -619,6 +669,8 @@ var SandboxAgent = class _SandboxAgent {
619
669
  pendingLiveConnections = /* @__PURE__ */ new Map();
620
670
  sessionHandles = /* @__PURE__ */ new Map();
621
671
  eventListeners = /* @__PURE__ */ new Map();
672
+ permissionListeners = /* @__PURE__ */ new Map();
673
+ pendingPermissionRequests = /* @__PURE__ */ new Map();
622
674
  nextSessionEventIndexBySession = /* @__PURE__ */ new Map();
623
675
  seedSessionEventIndexBySession = /* @__PURE__ */ new Map();
624
676
  constructor(options) {
@@ -667,6 +719,10 @@ var SandboxAgent = class _SandboxAgent {
667
719
  async dispose() {
668
720
  this.disposed = true;
669
721
  this.healthWaitAbortController.abort(createAbortError("SandboxAgent was disposed."));
722
+ for (const [permissionId, pending2] of this.pendingPermissionRequests) {
723
+ this.pendingPermissionRequests.delete(permissionId);
724
+ pending2.resolve(cancelledPermissionResponse());
725
+ }
670
726
  const connections = [...this.liveConnections.values()];
671
727
  this.liveConnections.clear();
672
728
  const pending = [...this.pendingLiveConnections.values()];
@@ -788,6 +844,7 @@ var SandboxAgent = class _SandboxAgent {
788
844
  return this.createSession(request);
789
845
  }
790
846
  async destroySession(id) {
847
+ this.cancelPendingPermissionsForSession(id);
791
848
  try {
792
849
  await this.sendSessionMethodInternal(id, SESSION_CANCEL_METHOD, {}, {}, true);
793
850
  } catch {
@@ -877,7 +934,23 @@ var SandboxAgent = class _SandboxAgent {
877
934
  }
878
935
  async getSessionModes(sessionId) {
879
936
  const record = await this.requireSessionRecord(sessionId);
880
- return cloneModes(record.modes);
937
+ if (record.modes && record.modes.availableModes.length > 0) {
938
+ return cloneModes(record.modes);
939
+ }
940
+ const hydrated = await this.hydrateSessionConfigOptions(record.id, record);
941
+ if (hydrated.modes && hydrated.modes.availableModes.length > 0) {
942
+ return cloneModes(hydrated.modes);
943
+ }
944
+ const derived = deriveModesFromConfigOptions(hydrated.configOptions);
945
+ if (!derived) {
946
+ return cloneModes(hydrated.modes);
947
+ }
948
+ const updated = {
949
+ ...hydrated,
950
+ modes: derived
951
+ };
952
+ await this.persist.updateSession(updated);
953
+ return cloneModes(derived);
881
954
  }
882
955
  async setSessionCategoryValue(sessionId, category, value) {
883
956
  const resolvedValue = value.trim();
@@ -919,7 +992,7 @@ var SandboxAgent = class _SandboxAgent {
919
992
  await this.persist.updateSession(updated);
920
993
  return updated;
921
994
  }
922
- async sendSessionMethod(sessionId, method, params, options = {}) {
995
+ async rawSendSessionMethod(sessionId, method, params, options = {}) {
923
996
  return this.sendSessionMethodInternal(sessionId, method, params, options, false);
924
997
  }
925
998
  async sendSessionMethodInternal(sessionId, method, params, options, allowManagedCancel) {
@@ -1015,6 +1088,42 @@ var SandboxAgent = class _SandboxAgent {
1015
1088
  }
1016
1089
  };
1017
1090
  }
1091
+ onPermissionRequest(sessionId, listener) {
1092
+ const listeners = this.permissionListeners.get(sessionId) ?? /* @__PURE__ */ new Set();
1093
+ listeners.add(listener);
1094
+ this.permissionListeners.set(sessionId, listeners);
1095
+ return () => {
1096
+ const set = this.permissionListeners.get(sessionId);
1097
+ if (!set) {
1098
+ return;
1099
+ }
1100
+ set.delete(listener);
1101
+ if (set.size === 0) {
1102
+ this.permissionListeners.delete(sessionId);
1103
+ }
1104
+ };
1105
+ }
1106
+ async respondPermission(permissionId, reply) {
1107
+ const pending = this.pendingPermissionRequests.get(permissionId);
1108
+ if (!pending) {
1109
+ throw new Error(`permission '${permissionId}' not found`);
1110
+ }
1111
+ let response;
1112
+ try {
1113
+ response = permissionReplyToResponse(permissionId, pending.request, reply);
1114
+ } catch (error) {
1115
+ pending.reject(error instanceof Error ? error : new Error(String(error)));
1116
+ this.pendingPermissionRequests.delete(permissionId);
1117
+ throw error;
1118
+ }
1119
+ this.resolvePendingPermission(permissionId, response);
1120
+ }
1121
+ async rawRespondPermission(permissionId, response) {
1122
+ if (!this.pendingPermissionRequests.has(permissionId)) {
1123
+ throw new Error(`permission '${permissionId}' not found`);
1124
+ }
1125
+ this.resolvePendingPermission(permissionId, clonePermissionResponse(response));
1126
+ }
1018
1127
  async getHealth() {
1019
1128
  return this.requestHealth();
1020
1129
  }
@@ -1024,9 +1133,21 @@ var SandboxAgent = class _SandboxAgent {
1024
1133
  });
1025
1134
  }
1026
1135
  async getAgent(agent, options) {
1027
- return this.requestJson("GET", `${API_PREFIX}/agents/${encodeURIComponent(agent)}`, {
1028
- query: toAgentQuery(options)
1029
- });
1136
+ try {
1137
+ return await this.requestJson("GET", `${API_PREFIX}/agents/${encodeURIComponent(agent)}`, {
1138
+ query: toAgentQuery(options)
1139
+ });
1140
+ } catch (error) {
1141
+ if (!(error instanceof SandboxAgentError) || error.status !== 404) {
1142
+ throw error;
1143
+ }
1144
+ const listed = await this.listAgents(options);
1145
+ const match = listed.agents.find((entry) => entry.id === agent);
1146
+ if (match) {
1147
+ return match;
1148
+ }
1149
+ throw error;
1150
+ }
1030
1151
  }
1031
1152
  async installAgent(agent, request = {}) {
1032
1153
  return this.requestJson("POST", `${API_PREFIX}/agents/${encodeURIComponent(agent)}/install`, {
@@ -1217,7 +1338,8 @@ var SandboxAgent = class _SandboxAgent {
1217
1338
  serverId,
1218
1339
  onObservedEnvelope: (connection, envelope, direction, localSessionId) => {
1219
1340
  void this.persistObservedEnvelope(connection, envelope, direction, localSessionId);
1220
- }
1341
+ },
1342
+ onPermissionRequest: async (connection, localSessionId, agentSessionId, request) => this.enqueuePermissionRequest(connection, localSessionId, agentSessionId, request)
1221
1343
  });
1222
1344
  const raced = this.liveConnections.get(agent);
1223
1345
  if (raced) {
@@ -1378,6 +1500,57 @@ var SandboxAgent = class _SandboxAgent {
1378
1500
  }
1379
1501
  return record;
1380
1502
  }
1503
+ async enqueuePermissionRequest(_connection, localSessionId, agentSessionId, request) {
1504
+ const listeners = this.permissionListeners.get(localSessionId);
1505
+ if (!listeners || listeners.size === 0) {
1506
+ return cancelledPermissionResponse();
1507
+ }
1508
+ const pendingId = randomId();
1509
+ const permissionRequest = {
1510
+ id: pendingId,
1511
+ createdAt: nowMs(),
1512
+ sessionId: localSessionId,
1513
+ agentSessionId,
1514
+ availableReplies: availablePermissionReplies(request.options),
1515
+ options: request.options.map(clonePermissionOption),
1516
+ toolCall: clonePermissionToolCall(request.toolCall),
1517
+ rawRequest: clonePermissionRequest(request)
1518
+ };
1519
+ return await new Promise((resolve, reject) => {
1520
+ this.pendingPermissionRequests.set(pendingId, {
1521
+ id: pendingId,
1522
+ sessionId: localSessionId,
1523
+ request: clonePermissionRequest(request),
1524
+ resolve,
1525
+ reject
1526
+ });
1527
+ try {
1528
+ for (const listener of listeners) {
1529
+ listener(permissionRequest);
1530
+ }
1531
+ } catch (error) {
1532
+ this.pendingPermissionRequests.delete(pendingId);
1533
+ reject(error);
1534
+ }
1535
+ });
1536
+ }
1537
+ resolvePendingPermission(permissionId, response) {
1538
+ const pending = this.pendingPermissionRequests.get(permissionId);
1539
+ if (!pending) {
1540
+ throw new Error(`permission '${permissionId}' not found`);
1541
+ }
1542
+ this.pendingPermissionRequests.delete(permissionId);
1543
+ pending.resolve(response);
1544
+ }
1545
+ cancelPendingPermissionsForSession(sessionId) {
1546
+ for (const [permissionId, pending] of this.pendingPermissionRequests) {
1547
+ if (pending.sessionId !== sessionId) {
1548
+ continue;
1549
+ }
1550
+ this.pendingPermissionRequests.delete(permissionId);
1551
+ pending.resolve(cancelledPermissionResponse());
1552
+ }
1553
+ }
1381
1554
  async requestJson(method, path, options = {}) {
1382
1555
  const response = await this.requestRaw(method, path, {
1383
1556
  query: options.query,
@@ -1679,6 +1852,22 @@ function envelopeSessionIdFromResult(message) {
1679
1852
  function cloneEnvelope(envelope) {
1680
1853
  return JSON.parse(JSON.stringify(envelope));
1681
1854
  }
1855
+ function clonePermissionRequest(request) {
1856
+ return JSON.parse(JSON.stringify(request));
1857
+ }
1858
+ function clonePermissionResponse(response) {
1859
+ return JSON.parse(JSON.stringify(response));
1860
+ }
1861
+ function clonePermissionOption(option) {
1862
+ return {
1863
+ optionId: option.optionId,
1864
+ name: option.name,
1865
+ kind: option.kind
1866
+ };
1867
+ }
1868
+ function clonePermissionToolCall(toolCall) {
1869
+ return JSON.parse(JSON.stringify(toolCall));
1870
+ }
1682
1871
  function isRecord(value) {
1683
1872
  return typeof value === "object" && value !== null;
1684
1873
  }
@@ -1788,6 +1977,24 @@ function extractKnownModeIds(modes) {
1788
1977
  }
1789
1978
  return modes.availableModes.map((mode) => typeof mode.id === "string" ? mode.id : null).filter((value) => !!value);
1790
1979
  }
1980
+ function deriveModesFromConfigOptions(configOptions) {
1981
+ if (!configOptions || configOptions.length === 0) {
1982
+ return null;
1983
+ }
1984
+ const modeOption = findConfigOptionByCategory(configOptions, "mode");
1985
+ if (!modeOption || !Array.isArray(modeOption.options)) {
1986
+ return null;
1987
+ }
1988
+ const availableModes = modeOption.options.flatMap((entry) => flattenConfigOptions(entry)).map((entry) => ({
1989
+ id: entry.value,
1990
+ name: entry.name,
1991
+ description: entry.description ?? null
1992
+ }));
1993
+ return {
1994
+ currentModeId: typeof modeOption.currentValue === "string" && modeOption.currentValue.length > 0 ? modeOption.currentValue : availableModes[0]?.id ?? "",
1995
+ availableModes
1996
+ };
1997
+ }
1791
1998
  function applyCurrentMode(modes, currentModeId) {
1792
1999
  if (modes && Array.isArray(modes.availableModes)) {
1793
2000
  return {
@@ -1809,6 +2016,24 @@ function applyConfigOptionValue(configOptions, configId, value) {
1809
2016
  updated[idx] = { ...updated[idx], currentValue: value };
1810
2017
  return updated;
1811
2018
  }
2019
+ function flattenConfigOptions(entry) {
2020
+ if (!isRecord(entry)) {
2021
+ return [];
2022
+ }
2023
+ if (typeof entry.value === "string" && typeof entry.name === "string") {
2024
+ return [
2025
+ {
2026
+ value: entry.value,
2027
+ name: entry.name,
2028
+ description: typeof entry.description === "string" ? entry.description : void 0
2029
+ }
2030
+ ];
2031
+ }
2032
+ if (!Array.isArray(entry.options)) {
2033
+ return [];
2034
+ }
2035
+ return entry.options.flatMap((nested) => flattenConfigOptions(nested));
2036
+ }
1812
2037
  function envelopeSessionUpdate(message) {
1813
2038
  if (!isRecord(message) || !("params" in message) || !isRecord(message.params)) {
1814
2039
  return null;
@@ -1830,6 +2055,43 @@ function cloneModes(value) {
1830
2055
  }
1831
2056
  return JSON.parse(JSON.stringify(value));
1832
2057
  }
2058
+ function availablePermissionReplies(options) {
2059
+ const replies = /* @__PURE__ */ new Set();
2060
+ for (const option of options) {
2061
+ if (option.kind === "allow_once") {
2062
+ replies.add("once");
2063
+ } else if (option.kind === "allow_always") {
2064
+ replies.add("always");
2065
+ } else if (option.kind === "reject_once" || option.kind === "reject_always") {
2066
+ replies.add("reject");
2067
+ }
2068
+ }
2069
+ return [...replies];
2070
+ }
2071
+ function permissionReplyToResponse(permissionId, request, reply) {
2072
+ const preferredKinds = reply === "once" ? ["allow_once"] : reply === "always" ? ["allow_always", "allow_once"] : ["reject_once", "reject_always"];
2073
+ const selected = preferredKinds.map((kind) => request.options.find((option) => option.kind === kind)).find((option) => Boolean(option));
2074
+ if (!selected) {
2075
+ throw new UnsupportedPermissionReplyError(
2076
+ permissionId,
2077
+ reply,
2078
+ availablePermissionReplies(request.options)
2079
+ );
2080
+ }
2081
+ return {
2082
+ outcome: {
2083
+ outcome: "selected",
2084
+ optionId: selected.optionId
2085
+ }
2086
+ };
2087
+ }
2088
+ function cancelledPermissionResponse() {
2089
+ return {
2090
+ outcome: {
2091
+ outcome: "cancelled"
2092
+ }
2093
+ };
2094
+ }
1833
2095
  function isSessionConfigOption(value) {
1834
2096
  return isRecord(value) && typeof value.id === "string" && typeof value.name === "string" && typeof value.type === "string";
1835
2097
  }
@@ -2038,6 +2300,7 @@ export {
2038
2300
  SandboxAgent,
2039
2301
  SandboxAgentError,
2040
2302
  Session,
2303
+ UnsupportedPermissionReplyError,
2041
2304
  UnsupportedSessionCategoryError,
2042
2305
  UnsupportedSessionConfigOptionError,
2043
2306
  UnsupportedSessionValueError,