@factory/droid-sdk 0.9.1 → 0.11.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (41) hide show
  1. package/README.md +53 -0
  2. package/dist/api.d.mts +15058 -0
  3. package/dist/api.d.ts +15058 -0
  4. package/dist/api.js +253 -0
  5. package/dist/api.js.map +1 -0
  6. package/dist/api.mjs +253 -0
  7. package/dist/api.mjs.map +1 -0
  8. package/dist/chunk-7ZD7MMNA.mjs +2268 -0
  9. package/dist/chunk-7ZD7MMNA.mjs.map +1 -0
  10. package/dist/{chunk-YSKO6SPT.js → chunk-KAAWCHO4.js} +32033 -17088
  11. package/dist/chunk-KAAWCHO4.js.map +1 -0
  12. package/dist/chunk-NWFX4MPJ.mjs +41630 -0
  13. package/dist/chunk-NWFX4MPJ.mjs.map +1 -0
  14. package/dist/chunk-ZQ7TS73S.js +2268 -0
  15. package/dist/chunk-ZQ7TS73S.js.map +1 -0
  16. package/dist/{index-C06W9tbN.d.mts → index-Cf2WtJqJ.d.mts} +134169 -84330
  17. package/dist/{index-C06W9tbN.d.ts → index-Cf2WtJqJ.d.ts} +134169 -84330
  18. package/dist/index.d.mts +1 -1
  19. package/dist/index.d.ts +1 -1
  20. package/dist/index.js +91 -2
  21. package/dist/index.js.map +1 -1
  22. package/dist/index.mjs +99 -10
  23. package/dist/node.d.mts +223 -43
  24. package/dist/node.d.ts +223 -43
  25. package/dist/node.js +2033 -776
  26. package/dist/node.js.map +1 -1
  27. package/dist/node.mjs +1756 -499
  28. package/dist/node.mjs.map +1 -1
  29. package/examples/browser/daemon-lifecycle.ts +4 -6
  30. package/examples/node/custom-model-editing-disposable.sh +88 -0
  31. package/examples/node/custom-model-editing-disposable.ts +171 -0
  32. package/examples/node/custom-model-editing.ts +55 -0
  33. package/examples/node/hook-execution.ts +3 -2
  34. package/examples/node/rest-api.ts +55 -0
  35. package/examples/node/sdk-mcp-tool.ts +5 -3
  36. package/examples/node/start-workstream.ts +76 -0
  37. package/examples/node/tool-controls.ts +7 -5
  38. package/package.json +14 -3
  39. package/dist/chunk-TWSTJMV7.mjs +0 -26685
  40. package/dist/chunk-TWSTJMV7.mjs.map +0 -1
  41. package/dist/chunk-YSKO6SPT.js.map +0 -1
package/dist/node.mjs CHANGED
@@ -8,6 +8,7 @@ import {
8
8
  AddUserMessageRequestSchema,
9
9
  AddUserMessageResponseSchema,
10
10
  AddUserMessageResultSchema,
11
+ AnswerSessionElicitationResponseSchema,
11
12
  AppendMessagesResponseSchema,
12
13
  ApplyPatchToolConfirmationDetailsSchema,
13
14
  AskUserCollectedAnswerSchema,
@@ -62,19 +63,23 @@ import {
62
63
  ComputerListResponseSchema,
63
64
  ComputerMetricSchema,
64
65
  ComputerMetricsResponseSchema,
66
+ ComputerReadSchema,
65
67
  ComputerSchema,
66
68
  ConcurrentStreamError,
67
69
  ConnectionClosedError,
68
70
  ConnectionError,
69
71
  ConnectionFailureReason,
70
72
  ContentBlockSchema,
73
+ ContextApiError,
71
74
  ContextBreakdownCategorySchema,
72
75
  ContextStatsAccuracy,
73
76
  ContextStatsSchema,
74
77
  CreateMessageNotificationSchema,
75
78
  CreateToolConfirmationDetailsSchema,
79
+ DEFAULT_CONNECTION_CONFIG,
76
80
  DEFAULT_LOCAL_DAEMON_WS_URL,
77
81
  DEFAULT_REQUEST_TIMEOUT,
82
+ DROID_RUNNING_STATES,
78
83
  DaemonAuthenticationMode,
79
84
  DaemonClient,
80
85
  DaemonDroidEvent,
@@ -88,6 +93,7 @@ import {
88
93
  DocumentSourceSchema,
89
94
  DroidClientError,
90
95
  DroidClientMethod,
96
+ DroidConnectionEndedError,
91
97
  DroidErrorType,
92
98
  DroidEvent,
93
99
  DroidHookEventSchema,
@@ -101,9 +107,13 @@ import {
101
107
  DroidWorkingStateChangedNotificationSchema,
102
108
  EditToolConfirmationDetailsSchema,
103
109
  EmptyResultSchema,
110
+ EnvironmentComputerTimeoutError,
111
+ EnvironmentListResponseSchema,
112
+ EnvironmentSchema,
104
113
  ErrorDetailSchema,
105
114
  ErrorNotificationSchema,
106
115
  ExecToolInfoSchema,
116
+ ExecToolInfoWithSchemasSchema,
107
117
  ExecuteRewindRequestParamsSchema,
108
118
  ExecuteRewindRequestSchema,
109
119
  ExecuteRewindResponseSchema,
@@ -113,7 +123,6 @@ import {
113
123
  FACTORY_CLIENT_HEADER,
114
124
  FACTORY_CLIENT_VERSION,
115
125
  FACTORY_PROTOCOL_VERSION,
116
- FACTORY_PROTOCOL_VERSION2,
117
126
  FactoryDaemonTransport,
118
127
  FactoryDroidMessageRole,
119
128
  FactoryDroidMessageSchema,
@@ -129,6 +138,7 @@ import {
129
138
  GetContextStatsRequestParamsSchema,
130
139
  GetContextStatsRequestSchema,
131
140
  GetContextStatsResponseSchema,
141
+ GetHandoffSummaryResponseSchema,
132
142
  GetRewindInfoRequestParamsSchema,
133
143
  GetRewindInfoRequestSchema,
134
144
  GetRewindInfoResponseSchema,
@@ -160,7 +170,6 @@ import {
160
170
  IssueSeverity,
161
171
  JSONRPC_VERSION,
162
172
  JSONRPC_VERSION2,
163
- JSONRPC_VERSION3,
164
173
  JsonArraySchema,
165
174
  JsonObjectSchema,
166
175
  JsonRpcBaseResponseSchema,
@@ -176,6 +185,7 @@ import {
176
185
  JsonRpcResponseSchema,
177
186
  JsonRpcResponseSuccessSchema,
178
187
  JsonValueSchema,
188
+ KillBackgroundProcessResponseSchema,
179
189
  KillWorkerSessionRequestParamsSchema,
180
190
  KillWorkerSessionRequestSchema,
181
191
  KillWorkerSessionResponseSchema,
@@ -183,6 +193,7 @@ import {
183
193
  LEGACY_FACTORY_API_VERSION,
184
194
  LEGACY_FACTORY_API_VERSION2,
185
195
  LOCAL_MACHINE_ID,
196
+ LfLineDecoder,
186
197
  ListCommandsResponseSchema,
187
198
  ListMcpRegistryRequestParamsSchema,
188
199
  ListMcpRegistryRequestSchema,
@@ -199,6 +210,7 @@ import {
199
210
  ListModelsOptionsSchema,
200
211
  ListModelsResponseSchema,
201
212
  ListModelsResultSchema,
213
+ ListSessionElicitationsResponseSchema,
202
214
  ListSkillsRequestParamsSchema,
203
215
  ListSkillsRequestSchema,
204
216
  ListSkillsResponseSchema,
@@ -206,11 +218,14 @@ import {
206
218
  ListToolsRequestParamsSchema,
207
219
  ListToolsRequestSchema,
208
220
  ListToolsResponseSchema,
221
+ ListToolsResponseWithSchemasSchema,
209
222
  ListToolsResultSchema,
223
+ ListToolsResultWithSchemasSchema,
210
224
  LoadSessionRequestParamsSchema,
211
225
  LoadSessionRequestSchema,
212
226
  LoadSessionResponseSchema,
213
227
  LoadSessionResultSchema,
228
+ MACHINE_TYPE_CONFIG,
214
229
  MACHINE_TYPE_WEBSOCKET_OVERRIDES,
215
230
  MANAGED_COMPUTER_MAX_RETRIES,
216
231
  MCP_AUTH_TIMEOUT,
@@ -243,6 +258,7 @@ import {
243
258
  MissionFeatureSchema,
244
259
  MissionFeaturesChangedNotificationSchema,
245
260
  MissionHeartbeatNotificationSchema,
261
+ MissionMachineTargetKind,
246
262
  MissionPausedEntrySchema,
247
263
  MissionProgressEntryNotificationSchema,
248
264
  MissionResumedEntrySchema,
@@ -261,6 +277,12 @@ import {
261
277
  OutputFormatSchema,
262
278
  OutputFormatType,
263
279
  PermissionResolvedNotificationSchema,
280
+ PickOptionsRequestParamsSchema,
281
+ PickOptionsRequestSchema,
282
+ PickOptionsResponseSchema,
283
+ PickOptionsResultSchema,
284
+ PickOptionsRowSchema,
285
+ PickOptionsUnavailableError,
264
286
  ProcessExitError,
265
287
  ProgressLogEntrySchema,
266
288
  ProgressLogEntryType,
@@ -272,6 +294,7 @@ import {
272
294
  ReasoningEffort,
273
295
  RedactedThinkingBlockSchema,
274
296
  RefreshComputerResponseSchema,
297
+ RefreshConnectorToolCatalogResponseSchema,
275
298
  RelayConnectionError,
276
299
  RemoteSessionListResponseSchema,
277
300
  RemoteSessionSchema,
@@ -283,6 +306,7 @@ import {
283
306
  RenameSessionRequestSchema,
284
307
  RenameSessionResponseSchema,
285
308
  RenameSessionResultSchema,
309
+ RequestCancelledError,
286
310
  RequestPermissionRequestParamsSchema,
287
311
  RequestPermissionRequestSchema,
288
312
  RequestPermissionResponseSchema,
@@ -291,13 +315,22 @@ import {
291
315
  RewindEvictedFileSchema,
292
316
  RewindFileCreationSchema,
293
317
  RewindFileSnapshotSchema,
318
+ SDK_CLIENT_METADATA,
294
319
  SDK_IDENTITY,
295
320
  SDK_REQUEST_ATTRIBUTION,
296
321
  SDK_TAG,
297
322
  SDK_VERSION,
298
323
  SESSION_INIT_TIMEOUT,
299
324
  SLACK_DELEGATION_MAX_RETRIES,
325
+ SandboxEnvironmentSchema,
300
326
  ServerRequestHandlerType,
327
+ SessionArtifactAction,
328
+ SessionArtifactKind,
329
+ SessionArtifactProvider,
330
+ SessionArtifactSchema,
331
+ SessionArtifactSource,
332
+ SessionArtifactsSchema,
333
+ SessionElicitationSchema,
301
334
  SessionError,
302
335
  SessionMetadataSchema,
303
336
  SessionNotFoundError,
@@ -348,6 +381,7 @@ import {
348
381
  SubmitMcpAuthCodeResultSchema,
349
382
  SubmitMcpAuthErrorResponseSchema,
350
383
  SuccessResultSchema,
384
+ SummarizeForMembersResponseSchema,
351
385
  TestCaseSchema,
352
386
  TestFileSchema,
353
387
  TestsSchema,
@@ -371,11 +405,15 @@ import {
371
405
  ToolConfirmationListItemSchema,
372
406
  ToolConfirmationOutcome,
373
407
  ToolConfirmationType,
408
+ ToolJsonSchemaSchema,
374
409
  ToolProgressUpdateNotificationSchema,
375
410
  ToolProgressUpdateSchema,
376
411
  ToolResultNotificationSchema,
377
412
  ToolResultSchema,
413
+ ToolResultSchemaDescriptorSchema,
414
+ ToolSchemasSchema,
378
415
  ToolSelectionOverridesSchema,
416
+ ToolSourceSchema,
379
417
  ToolUseSchema,
380
418
  ToolUseSchema2,
381
419
  TraceContextMetaSchema,
@@ -395,57 +433,98 @@ import {
395
433
  WorkerSelectedFeatureEntrySchema,
396
434
  WorkerStartedEntrySchema,
397
435
  WorkerStateInfoSchema,
436
+ answerRemoteSessionElicitation,
437
+ appendContextSession,
398
438
  buildUserMessageContentBlocks,
399
439
  configureDaemonCoreDeps,
400
440
  connectAndAuthenticate,
401
441
  connectToDaemon,
402
442
  connectWithRetry,
403
443
  convertNotificationToStreamMessage,
444
+ createApiRequest,
404
445
  createComputer,
446
+ createContextDocument,
447
+ createContextFile,
448
+ createContextSpace,
449
+ createFactoryApi,
450
+ createOrRecoverComputer,
405
451
  createResponseSchema,
406
452
  createWebSocketDaemonClient,
407
453
  defaultDaemonCoreDeps,
408
454
  deleteComputer,
455
+ deleteContextDocument,
456
+ deleteContextFile,
457
+ emitDroidLog,
458
+ encodeLegacyStartupLoadOptions,
459
+ escapeJsonLineSeparators,
409
460
  extractInnerNotification,
410
461
  filterMessagesForUI,
411
462
  getComputer,
412
463
  getComputerByName,
413
464
  getComputerMetrics,
465
+ getContextDocument,
466
+ getContextDocumentByPath,
467
+ getContextSession,
468
+ getContextSpace,
414
469
  getMachineTemplate,
415
470
  getPermissionToolInputForDisplay,
416
471
  getQueuedUserMessageDisplayGroup,
417
472
  getQueuedUserMessageReviewPriority,
473
+ getSandboxEnvironmentLaunchRepositories,
474
+ getSandboxEnvironmentLaunchSource,
418
475
  getSubagentCallingMetadata,
419
476
  getToolResultBlocks,
420
477
  getToolResultToolUseId,
421
478
  hasDecoupledInteractionSettings,
422
479
  hasSubagentSessionTag,
480
+ injectDroidTraceContext,
423
481
  inspectJsonRpcEnvelope,
424
482
  isDaemonQueuedMessageKind,
483
+ isEnvironmentComputerReady,
484
+ isOfferedPermissionOutcome,
425
485
  isPendingToolResultMarker,
426
486
  isPersistedHookMessage,
487
+ isProtocolVersionAtLeast,
427
488
  isReviewableQueuedMessageKind,
489
+ isSandboxEnvironmentLaunchable,
428
490
  listComputers,
491
+ listContextDocuments,
492
+ listContextSessions,
493
+ listContextSpaces,
494
+ listEnvironments,
429
495
  listMachineTemplates,
496
+ listRemoteSessionElicitations,
430
497
  listRemoteSessions,
431
498
  machineTypeToMachineConnectionType,
432
499
  mergeSessionSettings,
433
- orderMessagesByParentChain,
500
+ parseResponse,
501
+ readContextFilePart,
502
+ readContextSessionRecords,
434
503
  recordComputerConnectSli,
504
+ recordDroidMetric,
435
505
  refreshComputer,
436
- repairParentChainCorruption,
506
+ registerContextSession,
507
+ resolveAskUserHandler,
508
+ resolvePermissionHandler,
437
509
  restartComputer,
438
510
  retryInstallDeps,
439
511
  retryWithLogger,
512
+ runWithAbort,
513
+ sessionTagsUpdateForPeer,
440
514
  streamFromClient,
441
515
  sumTokenUsage,
442
516
  summarizeSessionToolUsage,
443
517
  toError,
518
+ trustedIdentityFromComputerResponse,
444
519
  unwrapResponseResult,
445
520
  updateComputer,
521
+ updateContextDocument,
522
+ uploadContextFilePart,
523
+ waitForEnvironmentComputer,
446
524
  wireAbortSignal,
447
525
  withSdkTag
448
- } from "./chunk-TWSTJMV7.mjs";
526
+ } from "./chunk-NWFX4MPJ.mjs";
527
+ import "./chunk-7ZD7MMNA.mjs";
449
528
 
450
529
  // src/exec-core/client.ts
451
530
  import EventEmitter from "events";
@@ -454,7 +533,7 @@ import { z } from "zod";
454
533
 
455
534
  // src/exec-core/constants.ts
456
535
  var DEFAULT_REQUEST_TIMEOUT2 = 3e4;
457
- var SESSION_INIT_TIMEOUT2 = 6e4;
536
+ var SESSION_INIT_TIMEOUT2 = 12e4;
458
537
  var COMPACTION_TIMEOUT2 = 24e4;
459
538
  var MCP_AUTH_TIMEOUT2 = 3e5;
460
539
  var WINDOWS_JOB_OBJECT_ERROR_MARKER = "AssignProcessToJobObject";
@@ -517,6 +596,9 @@ var defaultExecCoreDeps = {
517
596
  },
518
597
  sanitizeSpawnEnv(env) {
519
598
  return env;
599
+ },
600
+ buildRuntimeSettingsAuthorityEnv() {
601
+ return {};
520
602
  }
521
603
  }
522
604
  };
@@ -526,6 +608,7 @@ var DroidClientEvent = /* @__PURE__ */ ((DroidClientEvent2) => {
526
608
  DroidClientEvent2["SESSION_NOTIFICATION"] = "session_notification";
527
609
  DroidClientEvent2["REQUEST_PERMISSION"] = "request_permission";
528
610
  DroidClientEvent2["ASK_USER"] = "ask_user";
611
+ DroidClientEvent2["PICK_OPTIONS"] = "pick_options";
529
612
  DroidClientEvent2["ERROR"] = "error";
530
613
  return DroidClientEvent2;
531
614
  })(DroidClientEvent || {});
@@ -541,90 +624,6 @@ var TransportDirection = /* @__PURE__ */ ((TransportDirection2) => {
541
624
  return TransportDirection2;
542
625
  })(TransportDirection || {});
543
626
 
544
- // ../droid-sdk-core/src/observability.ts
545
- function sanitizeAttributes(attributes) {
546
- if (attributes === void 0) {
547
- return void 0;
548
- }
549
- const sanitized = {};
550
- for (const [key, value] of Object.entries(attributes)) {
551
- if (value === null || typeof value === "string" || typeof value === "number" || typeof value === "boolean") {
552
- sanitized[key] = value;
553
- }
554
- }
555
- return Object.keys(sanitized).length > 0 ? sanitized : void 0;
556
- }
557
- function serializeError(error) {
558
- if (error instanceof Error) {
559
- const code = "code" in error && typeof error.code === "string" ? error.code : void 0;
560
- return {
561
- name: error.name || void 0,
562
- message: error.message,
563
- ...code !== void 0 && { code }
564
- };
565
- }
566
- return { message: String(error) };
567
- }
568
- function handleSinkFailure(error, options) {
569
- try {
570
- options?.onError?.(error);
571
- } catch {
572
- }
573
- }
574
- function emitDroidLog(observability, event, options) {
575
- const sink = observability?.logger;
576
- if (!sink) {
577
- return false;
578
- }
579
- try {
580
- sink.log({
581
- level: event.level,
582
- name: event.name,
583
- message: event.message,
584
- attributes: sanitizeAttributes(event.attributes),
585
- ...event.error !== void 0 && {
586
- error: serializeError(event.error)
587
- }
588
- });
589
- return true;
590
- } catch (error) {
591
- handleSinkFailure(error, options);
592
- return false;
593
- }
594
- }
595
- function recordDroidMetric(observability, event, options) {
596
- const sink = observability?.metrics;
597
- if (!sink) {
598
- return false;
599
- }
600
- try {
601
- sink.record({
602
- name: event.name,
603
- kind: event.kind,
604
- value: event.value,
605
- unit: event.unit,
606
- attributes: sanitizeAttributes(event.attributes)
607
- });
608
- return true;
609
- } catch (error) {
610
- handleSinkFailure(error, options);
611
- return false;
612
- }
613
- }
614
- function injectDroidTraceContext(observability, carrier, options) {
615
- const sink = observability?.tracing;
616
- if (!sink) {
617
- return false;
618
- }
619
- try {
620
- sink.inject(carrier);
621
- return true;
622
- } catch (error) {
623
- handleSinkFailure(error, options);
624
- return false;
625
- }
626
- }
627
-
628
627
  // src/exec-core/deps.ts
629
628
  function logWarn(logger, message, metadata) {
630
629
  logger.warn(message, metadata);
@@ -779,15 +778,21 @@ function resolveExecCoreDeps(observability, fallback = defaultExecCoreDeps) {
779
778
  }
780
779
 
781
780
  // src/exec-core/client.ts
782
- function getResponseId(response) {
783
- const parsed = JsonRpcBaseResponseSchema.safeParse(response);
784
- return parsed.success ? parsed.data.id : void 0;
781
+ function isDroidConnectionEnded(error) {
782
+ const failure = error instanceof ProtocolError ? error.cause : error;
783
+ return failure instanceof DroidConnectionEndedError || failure instanceof ProcessExitError;
785
784
  }
786
785
  var JsonRpcEnvelopePeekSchema = JsonRpcEnvelopeSchema.partial().extend({
787
786
  type: z.nativeEnum(JsonRpcMessageType).optional(),
788
787
  method: z.string().optional(),
789
788
  id: z.string().nullable().optional()
790
789
  }).passthrough();
790
+ function getResponseId(response2) {
791
+ const parsed = JsonRpcEnvelopePeekSchema.safeParse(response2);
792
+ return parsed.success ? parsed.data.id : void 0;
793
+ }
794
+ var MCP_OAUTH_OPT_OUT_PROTOCOL_VERSION = "1.78.0";
795
+ var PERMISSION_WITHDRAWN = /* @__PURE__ */ Symbol("permission withdrawn");
791
796
  function inspectJsonRpcEnvelope2(message) {
792
797
  const result = JsonRpcEnvelopePeekSchema.safeParse(message);
793
798
  if (!result.success) {
@@ -811,10 +816,21 @@ var DroidClient = class _DroidClient extends EventEmitter {
811
816
  pendingRequests = /* @__PURE__ */ new Map();
812
817
  permissionHandlers = /* @__PURE__ */ new Map();
813
818
  askUserHandlers = /* @__PURE__ */ new Map();
819
+ pickOptionsHandlers = /* @__PURE__ */ new Map();
814
820
  pendingPermissionRequests = /* @__PURE__ */ new Map();
815
821
  pendingAskUserRequests = /* @__PURE__ */ new Map();
822
+ /**
823
+ * Queued (non-blocking) AskUser requests that arrived before an AskUser
824
+ * handler was set, as when a reloaded session asks its pending queued sets
825
+ * again during the load. Answering one without a handler would record it
826
+ * as dismissed, and the agent never waits on it, so it waits for the
827
+ * handler instead.
828
+ */
829
+ heldQueuedAskUserRequests = [];
830
+ pendingPickOptionsRequests = /* @__PURE__ */ new Map();
816
831
  currentSessionId = null;
817
832
  peerProtocolVersion;
833
+ peerEnforcesQueueOwnership = false;
818
834
  /**
819
835
  * Sticky error from a transport failure. Once set, all subsequent
820
836
  * `sendRequest()` calls throw this error immediately, preventing
@@ -822,14 +838,20 @@ var DroidClient = class _DroidClient extends EventEmitter {
822
838
  */
823
839
  transportError = null;
824
840
  completedTurnObserved = false;
841
+ /**
842
+ * Set once the droid sends `droid.disconnect`. It reads no further
843
+ * requests from then on, so it no longer counts as connected even while its
844
+ * transport stays open.
845
+ */
846
+ disconnected = false;
825
847
  logger;
826
848
  tracing;
827
849
  onRequestSettled;
828
850
  getRequestAttribution;
829
851
  constructor(options, deps = defaultExecCoreDeps) {
830
852
  super();
831
- this.transport = options.transport;
832
853
  const resolved = resolveExecCoreDeps(options.observability, deps);
854
+ this.transport = options.transport;
833
855
  this.logger = resolved.logger;
834
856
  this.tracing = resolved.tracing;
835
857
  this.requestTimeout = options.requestTimeout ?? DEFAULT_REQUEST_TIMEOUT2;
@@ -849,13 +871,18 @@ var DroidClient = class _DroidClient extends EventEmitter {
849
871
  });
850
872
  this.transport.onError((error) => {
851
873
  const err = error instanceof Error ? error : new Error(String(error));
852
- const expectedCleanExit = err instanceof ProcessExitError && err.exitCode === 0 && this.completedTurnObserved;
853
- if (expectedCleanExit) {
854
- this.logger.info("[DroidClient] Process exited after completed turn", {
855
- exitCode: err.exitCode
874
+ const sessionId = this.addressedSessionId;
875
+ const expectedExit = err instanceof ProcessExitError && (err.isRequestedStop || err.exitCode === 0 && this.completedTurnObserved);
876
+ if (expectedExit) {
877
+ this.logger.info("[DroidClient] Process exited as expected", {
878
+ sessionId,
879
+ exitCode: err.exitCode,
880
+ signal: err.signal
856
881
  });
857
882
  } else {
858
- this.logger.exception(err, "Transport error");
883
+ this.logger.exception(err, "Transport error", {
884
+ sessionId
885
+ });
859
886
  }
860
887
  this.transportError = err;
861
888
  this.rejectAllPending(err);
@@ -873,18 +900,62 @@ var DroidClient = class _DroidClient extends EventEmitter {
873
900
  get sessionId() {
874
901
  return this.currentSessionId;
875
902
  }
903
+ /**
904
+ * The session that an inbound prompt or transport fault belongs to. A
905
+ * session start or load in flight owns it, because the CLI replays a loaded
906
+ * session's pending prompts before it answers the load; otherwise the
907
+ * current session does. Undefined when that is ambiguous: a start with no
908
+ * session id yet, or several starts and loads in flight.
909
+ *
910
+ * @internal
911
+ */
912
+ get addressedSessionId() {
913
+ const pendingSessionIds = /* @__PURE__ */ new Set();
914
+ for (const pendingRequest of this.pendingRequests.values()) {
915
+ if (pendingRequest.method === "droid.initialize_session" /* INITIALIZE_SESSION */ && pendingRequest.sessionId === void 0) {
916
+ return void 0;
917
+ }
918
+ if (pendingRequest.sessionId !== void 0) {
919
+ pendingSessionIds.add(pendingRequest.sessionId);
920
+ }
921
+ }
922
+ if (pendingSessionIds.size > 1) {
923
+ return void 0;
924
+ }
925
+ const [pendingSessionId] = pendingSessionIds;
926
+ return pendingSessionId ?? this.currentSessionId ?? void 0;
927
+ }
876
928
  get isConnected() {
877
- return this.transport.isConnected;
929
+ return !this.disconnected && this.transport.isConnected;
930
+ }
931
+ /**
932
+ * Whether the worker runs in a child process. A daemon's shutdown cuts such
933
+ * a worker off from its owner before closing it, and from then on it reads
934
+ * no request; a worker hosted in this process still answers.
935
+ */
936
+ get runsInChildProcess() {
937
+ return (this.transport.getManagedProcess?.() ?? null) !== null;
938
+ }
939
+ /**
940
+ * Whether the runner refuses a queue change from someone other than the
941
+ * prompt's author. Older runners ignore the author on such a change.
942
+ */
943
+ get enforcesQueueOwnership() {
944
+ return this.peerEnforcesQueueOwnership;
878
945
  }
879
946
  requireMcpOAuthOptOutSupport() {
880
- if (this.peerProtocolVersion === FACTORY_PROTOCOL_VERSION) {
947
+ if (isProtocolVersionAtLeast(
948
+ this.peerProtocolVersion,
949
+ MCP_OAUTH_OPT_OUT_PROTOCOL_VERSION
950
+ )) {
881
951
  return;
882
952
  }
883
953
  throw new ProtocolError(
884
- "MCP OAuth opt-out requires a confirmed matching Droid protocol version. The connected Droid reported an incompatible protocol version and cannot disable OAuth for MCP servers.",
954
+ `MCP OAuth opt-out requires Droid protocol version ${MCP_OAUTH_OPT_OUT_PROTOCOL_VERSION} or newer. The connected Droid reported an older or unknown protocol version and cannot disable OAuth for MCP servers.`,
885
955
  {
886
956
  value: {
887
957
  localFactoryProtocolVersion: FACTORY_PROTOCOL_VERSION,
958
+ minimumFactoryProtocolVersion: MCP_OAUTH_OPT_OUT_PROTOCOL_VERSION,
888
959
  peerFactoryProtocolVersion: this.peerProtocolVersion
889
960
  }
890
961
  }
@@ -897,36 +968,38 @@ var DroidClient = class _DroidClient extends EventEmitter {
897
968
  }
898
969
  async initializeSession(params) {
899
970
  const request = {
900
- factoryApiVersion: LEGACY_FACTORY_API_VERSION,
971
+ factoryApiVersion: LEGACY_FACTORY_API_VERSION2,
901
972
  factoryProtocolVersion: FACTORY_PROTOCOL_VERSION,
902
973
  type: "request",
903
974
  jsonrpc: JSONRPC_VERSION2,
904
975
  id: uuidv4(),
905
976
  method: "droid.initialize_session" /* INITIALIZE_SESSION */,
906
- params
977
+ params: encodeLegacyStartupLoadOptions(params)
907
978
  };
908
979
  const rawResponse = await this.sendRequest(
909
980
  request,
910
- this.sessionInitTimeout
981
+ this.sessionInitTimeout,
982
+ params.sessionId
911
983
  );
912
- const response = InitializeSessionResponseSchema.parse(rawResponse);
913
- this.peerProtocolVersion = response.factoryProtocolVersion;
914
- if (response.error) {
984
+ const response2 = InitializeSessionResponseSchema.parse(rawResponse);
985
+ this.peerProtocolVersion = response2.factoryProtocolVersion;
986
+ if (response2.error) {
915
987
  throw new ProtocolError("Initialize session request failed", {
916
988
  requestId: request.id,
917
- code: response.error.code,
918
- message: response.error.message,
919
- data: response.error.data
989
+ code: response2.error.code,
990
+ message: response2.error.message,
991
+ data: response2.error.data
920
992
  });
921
993
  }
922
994
  this.validateMcpServersProtocolSupport(params.mcpServers);
923
- const result = InitializeSessionResultSchema.parse(response.result);
995
+ const result = InitializeSessionResultSchema.parse(response2.result);
996
+ this.peerEnforcesQueueOwnership = result.enforcesQueueOwnership === true;
924
997
  this.currentSessionId = result.sessionId;
925
- return response;
998
+ return response2;
926
999
  }
927
1000
  async loadSession(params) {
928
1001
  const request = {
929
- factoryApiVersion: LEGACY_FACTORY_API_VERSION,
1002
+ factoryApiVersion: LEGACY_FACTORY_API_VERSION2,
930
1003
  factoryProtocolVersion: FACTORY_PROTOCOL_VERSION,
931
1004
  type: "request",
932
1005
  jsonrpc: JSONRPC_VERSION2,
@@ -936,30 +1009,32 @@ var DroidClient = class _DroidClient extends EventEmitter {
936
1009
  };
937
1010
  const rawResponse = await this.sendRequest(
938
1011
  request,
939
- this.sessionInitTimeout
1012
+ this.sessionInitTimeout,
1013
+ params.sessionId
940
1014
  );
941
- const response = LoadSessionResponseSchema.parse(rawResponse);
942
- this.peerProtocolVersion = response.factoryProtocolVersion;
943
- if (response.error) {
944
- const isSessionNotFound = response.error.code === -32004 /* ENTITY_NOT_FOUND */;
1015
+ const response2 = LoadSessionResponseSchema.parse(rawResponse);
1016
+ this.peerProtocolVersion = response2.factoryProtocolVersion;
1017
+ if (response2.error) {
1018
+ const isSessionNotFound = response2.error.code === -32004 /* ENTITY_NOT_FOUND */;
945
1019
  if (isSessionNotFound) {
946
1020
  throw new SessionNotFoundError(params.sessionId, {
947
1021
  requestId: request.id,
948
- code: response.error.code,
949
- message: response.error.message,
950
- data: response.error.data
1022
+ code: response2.error.code,
1023
+ message: response2.error.message,
1024
+ data: response2.error.data
951
1025
  });
952
1026
  }
953
1027
  throw new ProtocolError("Load session request failed", {
954
1028
  requestId: request.id,
955
- code: response.error.code,
956
- message: response.error.message,
957
- data: response.error.data
1029
+ code: response2.error.code,
1030
+ message: response2.error.message,
1031
+ data: response2.error.data
958
1032
  });
959
1033
  }
960
1034
  this.validateMcpServersProtocolSupport(params.mcpServers);
1035
+ this.peerEnforcesQueueOwnership = response2.result?.enforcesQueueOwnership === true;
961
1036
  this.currentSessionId = params.sessionId;
962
- return response;
1037
+ return response2;
963
1038
  }
964
1039
  async addUserMessage(params, requestId) {
965
1040
  if (!this.currentSessionId) {
@@ -969,7 +1044,7 @@ var DroidClient = class _DroidClient extends EventEmitter {
969
1044
  }
970
1045
  this.completedTurnObserved = false;
971
1046
  const request = {
972
- factoryApiVersion: LEGACY_FACTORY_API_VERSION,
1047
+ factoryApiVersion: LEGACY_FACTORY_API_VERSION2,
973
1048
  factoryProtocolVersion: FACTORY_PROTOCOL_VERSION,
974
1049
  type: "request",
975
1050
  jsonrpc: JSONRPC_VERSION2,
@@ -981,16 +1056,16 @@ var DroidClient = class _DroidClient extends EventEmitter {
981
1056
  }
982
1057
  };
983
1058
  const rawResponse = await this.sendRequest(request);
984
- const response = AddUserMessageResponseSchema.parse(rawResponse);
985
- if (response.error) {
1059
+ const response2 = AddUserMessageResponseSchema.parse(rawResponse);
1060
+ if (response2.error) {
986
1061
  throw new ProtocolError("Add user message request failed", {
987
1062
  requestId: request.id,
988
- code: response.error.code,
989
- message: response.error.message,
990
- data: response.error.data
1063
+ code: response2.error.code,
1064
+ message: response2.error.message,
1065
+ data: response2.error.data
991
1066
  });
992
1067
  }
993
- return response;
1068
+ return response2;
994
1069
  }
995
1070
  /**
996
1071
  * Persists user-only messages without starting an agent turn.
@@ -1005,7 +1080,7 @@ var DroidClient = class _DroidClient extends EventEmitter {
1005
1080
  );
1006
1081
  }
1007
1082
  const request = {
1008
- factoryApiVersion: LEGACY_FACTORY_API_VERSION,
1083
+ factoryApiVersion: LEGACY_FACTORY_API_VERSION2,
1009
1084
  factoryProtocolVersion: FACTORY_PROTOCOL_VERSION,
1010
1085
  type: "request",
1011
1086
  jsonrpc: JSONRPC_VERSION2,
@@ -1014,16 +1089,16 @@ var DroidClient = class _DroidClient extends EventEmitter {
1014
1089
  params
1015
1090
  };
1016
1091
  const rawResponse = await this.sendRequest(request);
1017
- const response = AppendMessagesResponseSchema.parse(rawResponse);
1018
- if (response.error) {
1092
+ const response2 = AppendMessagesResponseSchema.parse(rawResponse);
1093
+ if (response2.error) {
1019
1094
  throw new ProtocolError("Append messages request failed", {
1020
1095
  requestId: request.id,
1021
- code: response.error.code,
1022
- message: response.error.message,
1023
- data: response.error.data
1096
+ code: response2.error.code,
1097
+ message: response2.error.message,
1098
+ data: response2.error.data
1024
1099
  });
1025
1100
  }
1026
- return response;
1101
+ return response2;
1027
1102
  }
1028
1103
  async resolveQueuedUserMessage(params) {
1029
1104
  if (!this.currentSessionId) {
@@ -1032,7 +1107,7 @@ var DroidClient = class _DroidClient extends EventEmitter {
1032
1107
  );
1033
1108
  }
1034
1109
  const request = {
1035
- factoryApiVersion: LEGACY_FACTORY_API_VERSION,
1110
+ factoryApiVersion: LEGACY_FACTORY_API_VERSION2,
1036
1111
  factoryProtocolVersion: FACTORY_PROTOCOL_VERSION,
1037
1112
  type: "request",
1038
1113
  jsonrpc: JSONRPC_VERSION2,
@@ -1041,16 +1116,16 @@ var DroidClient = class _DroidClient extends EventEmitter {
1041
1116
  params
1042
1117
  };
1043
1118
  const rawResponse = await this.sendRequest(request, this.requestTimeout);
1044
- const response = ResolveQueuedUserMessageResponseSchema.parse(rawResponse);
1045
- if (response.error) {
1119
+ const response2 = ResolveQueuedUserMessageResponseSchema.parse(rawResponse);
1120
+ if (response2.error) {
1046
1121
  throw new ProtocolError("Resolve queued user message request failed", {
1047
1122
  requestId: request.id,
1048
- code: response.error.code,
1049
- message: response.error.message,
1050
- data: response.error.data
1123
+ code: response2.error.code,
1124
+ message: response2.error.message,
1125
+ data: response2.error.data
1051
1126
  });
1052
1127
  }
1053
- return response;
1128
+ return response2;
1054
1129
  }
1055
1130
  async interruptSession(params = {}) {
1056
1131
  if (!this.currentSessionId) {
@@ -1059,7 +1134,7 @@ var DroidClient = class _DroidClient extends EventEmitter {
1059
1134
  );
1060
1135
  }
1061
1136
  const request = {
1062
- factoryApiVersion: LEGACY_FACTORY_API_VERSION,
1137
+ factoryApiVersion: LEGACY_FACTORY_API_VERSION2,
1063
1138
  factoryProtocolVersion: FACTORY_PROTOCOL_VERSION,
1064
1139
  type: "request",
1065
1140
  jsonrpc: JSONRPC_VERSION2,
@@ -1068,16 +1143,16 @@ var DroidClient = class _DroidClient extends EventEmitter {
1068
1143
  params
1069
1144
  };
1070
1145
  const rawResponse = await this.sendRequest(request, this.requestTimeout);
1071
- const response = InterruptSessionResponseSchema.parse(rawResponse);
1072
- if (response.error) {
1146
+ const response2 = InterruptSessionResponseSchema.parse(rawResponse);
1147
+ if (response2.error) {
1073
1148
  throw new ProtocolError("Interrupt session request failed", {
1074
1149
  requestId: request.id,
1075
- code: response.error.code,
1076
- message: response.error.message,
1077
- data: response.error.data
1150
+ code: response2.error.code,
1151
+ message: response2.error.message,
1152
+ data: response2.error.data
1078
1153
  });
1079
1154
  }
1080
- return response;
1155
+ return response2;
1081
1156
  }
1082
1157
  async closeSession(params = {}) {
1083
1158
  if (!this.currentSessionId) {
@@ -1086,7 +1161,7 @@ var DroidClient = class _DroidClient extends EventEmitter {
1086
1161
  );
1087
1162
  }
1088
1163
  const request = {
1089
- factoryApiVersion: LEGACY_FACTORY_API_VERSION,
1164
+ factoryApiVersion: LEGACY_FACTORY_API_VERSION2,
1090
1165
  factoryProtocolVersion: FACTORY_PROTOCOL_VERSION,
1091
1166
  type: "request",
1092
1167
  jsonrpc: JSONRPC_VERSION2,
@@ -1095,16 +1170,16 @@ var DroidClient = class _DroidClient extends EventEmitter {
1095
1170
  params
1096
1171
  };
1097
1172
  const rawResponse = await this.sendRequest(request, this.requestTimeout);
1098
- const response = CloseSessionResponseSchema.parse(rawResponse);
1099
- if (response.error) {
1173
+ const response2 = CloseSessionResponseSchema.parse(rawResponse);
1174
+ if (response2.error) {
1100
1175
  throw new ProtocolError("Close session request failed", {
1101
1176
  requestId: request.id,
1102
- code: response.error.code,
1103
- message: response.error.message,
1104
- data: response.error.data
1177
+ code: response2.error.code,
1178
+ message: response2.error.message,
1179
+ data: response2.error.data
1105
1180
  });
1106
1181
  }
1107
- return response;
1182
+ return response2;
1108
1183
  }
1109
1184
  async killWorkerSession(params) {
1110
1185
  if (!this.currentSessionId) {
@@ -1113,7 +1188,7 @@ var DroidClient = class _DroidClient extends EventEmitter {
1113
1188
  );
1114
1189
  }
1115
1190
  const request = {
1116
- factoryApiVersion: LEGACY_FACTORY_API_VERSION,
1191
+ factoryApiVersion: LEGACY_FACTORY_API_VERSION2,
1117
1192
  factoryProtocolVersion: FACTORY_PROTOCOL_VERSION,
1118
1193
  type: "request",
1119
1194
  jsonrpc: JSONRPC_VERSION2,
@@ -1122,16 +1197,16 @@ var DroidClient = class _DroidClient extends EventEmitter {
1122
1197
  params
1123
1198
  };
1124
1199
  const rawResponse = await this.sendRequest(request, this.requestTimeout);
1125
- const response = KillWorkerSessionResponseSchema.parse(rawResponse);
1126
- if (response.error) {
1200
+ const response2 = KillWorkerSessionResponseSchema.parse(rawResponse);
1201
+ if (response2.error) {
1127
1202
  throw new ProtocolError("Kill worker session request failed", {
1128
1203
  requestId: request.id,
1129
- code: response.error.code,
1130
- message: response.error.message,
1131
- data: response.error.data
1204
+ code: response2.error.code,
1205
+ message: response2.error.message,
1206
+ data: response2.error.data
1132
1207
  });
1133
1208
  }
1134
- return response;
1209
+ return response2;
1135
1210
  }
1136
1211
  async updateSessionSettings(params, requestId) {
1137
1212
  if (!this.currentSessionId) {
@@ -1140,13 +1215,13 @@ var DroidClient = class _DroidClient extends EventEmitter {
1140
1215
  );
1141
1216
  }
1142
1217
  const request = {
1143
- factoryApiVersion: LEGACY_FACTORY_API_VERSION,
1218
+ factoryApiVersion: LEGACY_FACTORY_API_VERSION2,
1144
1219
  factoryProtocolVersion: FACTORY_PROTOCOL_VERSION,
1145
1220
  type: "request",
1146
1221
  jsonrpc: JSONRPC_VERSION2,
1147
1222
  id: requestId ?? uuidv4(),
1148
1223
  method: "droid.update_session_settings" /* UPDATE_SESSION_SETTINGS */,
1149
- params
1224
+ params: sessionTagsUpdateForPeer(params, this.peerProtocolVersion)
1150
1225
  };
1151
1226
  const rawResponse = await this.sendRequest(request, this.requestTimeout);
1152
1227
  const responseParsed = UpdateSessionSettingsResponseSchema.safeParse(rawResponse);
@@ -1161,16 +1236,16 @@ var DroidClient = class _DroidClient extends EventEmitter {
1161
1236
  }
1162
1237
  );
1163
1238
  }
1164
- const response = responseParsed.data;
1165
- if (response.error) {
1239
+ const response2 = responseParsed.data;
1240
+ if (response2.error) {
1166
1241
  throw new ProtocolError("Update session settings request failed", {
1167
1242
  requestId: request.id,
1168
- code: response.error.code,
1169
- message: response.error.message,
1170
- data: response.error.data
1243
+ code: response2.error.code,
1244
+ message: response2.error.message,
1245
+ data: response2.error.data
1171
1246
  });
1172
1247
  }
1173
- return response;
1248
+ return response2;
1174
1249
  }
1175
1250
  async changeWorkingDirectory(params) {
1176
1251
  if (!this.currentSessionId) {
@@ -1179,7 +1254,7 @@ var DroidClient = class _DroidClient extends EventEmitter {
1179
1254
  );
1180
1255
  }
1181
1256
  const request = {
1182
- factoryApiVersion: LEGACY_FACTORY_API_VERSION,
1257
+ factoryApiVersion: LEGACY_FACTORY_API_VERSION2,
1183
1258
  factoryProtocolVersion: FACTORY_PROTOCOL_VERSION,
1184
1259
  type: "request",
1185
1260
  jsonrpc: JSONRPC_VERSION2,
@@ -1188,16 +1263,16 @@ var DroidClient = class _DroidClient extends EventEmitter {
1188
1263
  params
1189
1264
  };
1190
1265
  const rawResponse = await this.sendRequest(request, this.requestTimeout);
1191
- const response = ChangeWorkingDirectoryResponseSchema.parse(rawResponse);
1192
- if (response.error) {
1266
+ const response2 = ChangeWorkingDirectoryResponseSchema.parse(rawResponse);
1267
+ if (response2.error) {
1193
1268
  throw new ProtocolError("Change working directory request failed", {
1194
1269
  requestId: request.id,
1195
- code: response.error.code,
1196
- message: response.error.message,
1197
- data: response.error.data
1270
+ code: response2.error.code,
1271
+ message: response2.error.message,
1272
+ data: response2.error.data
1198
1273
  });
1199
1274
  }
1200
- return response;
1275
+ return response2;
1201
1276
  }
1202
1277
  async toggleMcpServer(params) {
1203
1278
  if (!this.currentSessionId) {
@@ -1206,7 +1281,7 @@ var DroidClient = class _DroidClient extends EventEmitter {
1206
1281
  );
1207
1282
  }
1208
1283
  const request = {
1209
- factoryApiVersion: LEGACY_FACTORY_API_VERSION,
1284
+ factoryApiVersion: LEGACY_FACTORY_API_VERSION2,
1210
1285
  factoryProtocolVersion: FACTORY_PROTOCOL_VERSION,
1211
1286
  type: "request",
1212
1287
  jsonrpc: JSONRPC_VERSION2,
@@ -1219,8 +1294,8 @@ var DroidClient = class _DroidClient extends EventEmitter {
1219
1294
  }
1220
1295
  };
1221
1296
  const rawResponse = await this.sendRequest(request, this.requestTimeout);
1222
- const response = ToggleMcpServerResponseSchema.parse(rawResponse);
1223
- return response;
1297
+ const response2 = ToggleMcpServerResponseSchema.parse(rawResponse);
1298
+ return response2;
1224
1299
  }
1225
1300
  async authenticateMcpServer(params) {
1226
1301
  if (!this.currentSessionId) {
@@ -1229,7 +1304,7 @@ var DroidClient = class _DroidClient extends EventEmitter {
1229
1304
  );
1230
1305
  }
1231
1306
  const request = {
1232
- factoryApiVersion: LEGACY_FACTORY_API_VERSION,
1307
+ factoryApiVersion: LEGACY_FACTORY_API_VERSION2,
1233
1308
  factoryProtocolVersion: FACTORY_PROTOCOL_VERSION,
1234
1309
  type: "request",
1235
1310
  jsonrpc: JSONRPC_VERSION2,
@@ -1240,8 +1315,8 @@ var DroidClient = class _DroidClient extends EventEmitter {
1240
1315
  }
1241
1316
  };
1242
1317
  const rawResponse = await this.sendRequest(request, MCP_AUTH_TIMEOUT2);
1243
- const response = AuthenticateMcpServerResponseSchema.parse(rawResponse);
1244
- return response;
1318
+ const response2 = AuthenticateMcpServerResponseSchema.parse(rawResponse);
1319
+ return response2;
1245
1320
  }
1246
1321
  async cancelMcpAuth(params) {
1247
1322
  if (!this.currentSessionId) {
@@ -1250,7 +1325,7 @@ var DroidClient = class _DroidClient extends EventEmitter {
1250
1325
  );
1251
1326
  }
1252
1327
  const request = {
1253
- factoryApiVersion: LEGACY_FACTORY_API_VERSION,
1328
+ factoryApiVersion: LEGACY_FACTORY_API_VERSION2,
1254
1329
  factoryProtocolVersion: FACTORY_PROTOCOL_VERSION,
1255
1330
  type: "request",
1256
1331
  jsonrpc: JSONRPC_VERSION2,
@@ -1261,8 +1336,8 @@ var DroidClient = class _DroidClient extends EventEmitter {
1261
1336
  }
1262
1337
  };
1263
1338
  const rawResponse = await this.sendRequest(request, this.requestTimeout);
1264
- const response = CancelMcpAuthResponseSchema.parse(rawResponse);
1265
- return response;
1339
+ const response2 = CancelMcpAuthResponseSchema.parse(rawResponse);
1340
+ return response2;
1266
1341
  }
1267
1342
  async clearMcpAuth(params) {
1268
1343
  if (!this.currentSessionId) {
@@ -1271,7 +1346,7 @@ var DroidClient = class _DroidClient extends EventEmitter {
1271
1346
  );
1272
1347
  }
1273
1348
  const request = {
1274
- factoryApiVersion: LEGACY_FACTORY_API_VERSION,
1349
+ factoryApiVersion: LEGACY_FACTORY_API_VERSION2,
1275
1350
  factoryProtocolVersion: FACTORY_PROTOCOL_VERSION,
1276
1351
  type: "request",
1277
1352
  jsonrpc: JSONRPC_VERSION2,
@@ -1282,8 +1357,8 @@ var DroidClient = class _DroidClient extends EventEmitter {
1282
1357
  }
1283
1358
  };
1284
1359
  const rawResponse = await this.sendRequest(request, this.requestTimeout);
1285
- const response = ClearMcpAuthResponseSchema.parse(rawResponse);
1286
- return response;
1360
+ const response2 = ClearMcpAuthResponseSchema.parse(rawResponse);
1361
+ return response2;
1287
1362
  }
1288
1363
  async addMcpServer(params) {
1289
1364
  if (!this.currentSessionId) {
@@ -1295,7 +1370,7 @@ var DroidClient = class _DroidClient extends EventEmitter {
1295
1370
  this.requireMcpOAuthOptOutSupport();
1296
1371
  }
1297
1372
  const request = {
1298
- factoryApiVersion: LEGACY_FACTORY_API_VERSION,
1373
+ factoryApiVersion: LEGACY_FACTORY_API_VERSION2,
1299
1374
  factoryProtocolVersion: FACTORY_PROTOCOL_VERSION,
1300
1375
  type: "request",
1301
1376
  jsonrpc: JSONRPC_VERSION2,
@@ -1304,8 +1379,8 @@ var DroidClient = class _DroidClient extends EventEmitter {
1304
1379
  params
1305
1380
  };
1306
1381
  const rawResponse = await this.sendRequest(request, this.requestTimeout);
1307
- const response = AddMcpServerResponseSchema.parse(rawResponse);
1308
- return response;
1382
+ const response2 = AddMcpServerResponseSchema.parse(rawResponse);
1383
+ return response2;
1309
1384
  }
1310
1385
  async removeMcpServer(params) {
1311
1386
  if (!this.currentSessionId) {
@@ -1314,7 +1389,7 @@ var DroidClient = class _DroidClient extends EventEmitter {
1314
1389
  );
1315
1390
  }
1316
1391
  const request = {
1317
- factoryApiVersion: LEGACY_FACTORY_API_VERSION,
1392
+ factoryApiVersion: LEGACY_FACTORY_API_VERSION2,
1318
1393
  factoryProtocolVersion: FACTORY_PROTOCOL_VERSION,
1319
1394
  type: "request",
1320
1395
  jsonrpc: JSONRPC_VERSION2,
@@ -1323,12 +1398,12 @@ var DroidClient = class _DroidClient extends EventEmitter {
1323
1398
  params
1324
1399
  };
1325
1400
  const rawResponse = await this.sendRequest(request, this.requestTimeout);
1326
- const response = RemoveMcpServerResponseSchema.parse(rawResponse);
1327
- return response;
1401
+ const response2 = RemoveMcpServerResponseSchema.parse(rawResponse);
1402
+ return response2;
1328
1403
  }
1329
1404
  async listModels(params = {}) {
1330
1405
  const request = {
1331
- factoryApiVersion: LEGACY_FACTORY_API_VERSION,
1406
+ factoryApiVersion: LEGACY_FACTORY_API_VERSION2,
1332
1407
  factoryProtocolVersion: FACTORY_PROTOCOL_VERSION,
1333
1408
  type: "request",
1334
1409
  jsonrpc: JSONRPC_VERSION2,
@@ -1346,7 +1421,7 @@ var DroidClient = class _DroidClient extends EventEmitter {
1346
1421
  );
1347
1422
  }
1348
1423
  const request = {
1349
- factoryApiVersion: LEGACY_FACTORY_API_VERSION,
1424
+ factoryApiVersion: LEGACY_FACTORY_API_VERSION2,
1350
1425
  factoryProtocolVersion: FACTORY_PROTOCOL_VERSION,
1351
1426
  type: "request",
1352
1427
  jsonrpc: JSONRPC_VERSION2,
@@ -1355,8 +1430,26 @@ var DroidClient = class _DroidClient extends EventEmitter {
1355
1430
  params: {}
1356
1431
  };
1357
1432
  const rawResponse = await this.sendRequest(request, this.requestTimeout);
1358
- const response = ListMcpRegistryResponseSchema.parse(rawResponse);
1359
- return response;
1433
+ const response2 = ListMcpRegistryResponseSchema.parse(rawResponse);
1434
+ return response2;
1435
+ }
1436
+ async refreshConnectorToolCatalog(params = {}) {
1437
+ if (!this.currentSessionId) {
1438
+ throw new SessionError(
1439
+ "No active session. Call initializeSession or loadSession first."
1440
+ );
1441
+ }
1442
+ const request = {
1443
+ factoryApiVersion: LEGACY_FACTORY_API_VERSION2,
1444
+ factoryProtocolVersion: FACTORY_PROTOCOL_VERSION,
1445
+ type: "request",
1446
+ jsonrpc: JSONRPC_VERSION2,
1447
+ id: uuidv4(),
1448
+ method: "droid.refresh_connector_tool_catalog" /* REFRESH_CONNECTOR_TOOL_CATALOG */,
1449
+ params
1450
+ };
1451
+ const response2 = await this.sendRequest(request, this.requestTimeout);
1452
+ return RefreshConnectorToolCatalogResponseSchema.parse(response2);
1360
1453
  }
1361
1454
  async listMcpTools() {
1362
1455
  if (!this.currentSessionId) {
@@ -1365,7 +1458,7 @@ var DroidClient = class _DroidClient extends EventEmitter {
1365
1458
  );
1366
1459
  }
1367
1460
  const request = {
1368
- factoryApiVersion: LEGACY_FACTORY_API_VERSION,
1461
+ factoryApiVersion: LEGACY_FACTORY_API_VERSION2,
1369
1462
  factoryProtocolVersion: FACTORY_PROTOCOL_VERSION,
1370
1463
  type: "request",
1371
1464
  jsonrpc: JSONRPC_VERSION2,
@@ -1374,12 +1467,13 @@ var DroidClient = class _DroidClient extends EventEmitter {
1374
1467
  params: {}
1375
1468
  };
1376
1469
  const rawResponse = await this.sendRequest(request, this.requestTimeout);
1377
- const response = ListMcpToolsResponseSchema.parse(rawResponse);
1378
- return response;
1470
+ const response2 = ListMcpToolsResponseSchema.parse(rawResponse);
1471
+ return response2;
1379
1472
  }
1380
1473
  async listTools(params = {}) {
1474
+ const schemasRequested = params.includeSchemas === true;
1381
1475
  const request = {
1382
- factoryApiVersion: LEGACY_FACTORY_API_VERSION,
1476
+ factoryApiVersion: LEGACY_FACTORY_API_VERSION2,
1383
1477
  factoryProtocolVersion: FACTORY_PROTOCOL_VERSION,
1384
1478
  type: "request",
1385
1479
  jsonrpc: JSONRPC_VERSION2,
@@ -1388,8 +1482,29 @@ var DroidClient = class _DroidClient extends EventEmitter {
1388
1482
  params
1389
1483
  };
1390
1484
  const rawResponse = await this.sendRequest(request, this.requestTimeout);
1391
- const response = ListToolsResponseSchema.parse(rawResponse);
1392
- return response;
1485
+ const response2 = ListToolsResponseSchema.parse(rawResponse);
1486
+ if (!schemasRequested || !("result" in response2)) {
1487
+ return response2;
1488
+ }
1489
+ const strictResult = ListToolsResultWithSchemasSchema.safeParse(
1490
+ response2.result
1491
+ );
1492
+ if (strictResult.success) {
1493
+ return response2;
1494
+ }
1495
+ const result = ListToolsResultSchema.parse(response2.result);
1496
+ const missingSchemaIds = [];
1497
+ for (const tool2 of result.tools) {
1498
+ if (tool2.schemas === void 0) {
1499
+ missingSchemaIds.push(tool2.llmId);
1500
+ }
1501
+ }
1502
+ if (missingSchemaIds.length > 0) {
1503
+ throw new ProtocolError(
1504
+ `The installed Droid version did not return requested schemas for tool IDs: ${missingSchemaIds.join(", ")}. Update Droid and try again.`
1505
+ );
1506
+ }
1507
+ throw strictResult.error;
1393
1508
  }
1394
1509
  async listMcpServers() {
1395
1510
  if (!this.currentSessionId) {
@@ -1398,7 +1513,7 @@ var DroidClient = class _DroidClient extends EventEmitter {
1398
1513
  );
1399
1514
  }
1400
1515
  const request = {
1401
- factoryApiVersion: LEGACY_FACTORY_API_VERSION,
1516
+ factoryApiVersion: LEGACY_FACTORY_API_VERSION2,
1402
1517
  factoryProtocolVersion: FACTORY_PROTOCOL_VERSION,
1403
1518
  type: "request",
1404
1519
  jsonrpc: JSONRPC_VERSION2,
@@ -1407,8 +1522,8 @@ var DroidClient = class _DroidClient extends EventEmitter {
1407
1522
  params: {}
1408
1523
  };
1409
1524
  const rawResponse = await this.sendRequest(request, this.requestTimeout);
1410
- const response = ListMcpServersResponseSchema.parse(rawResponse);
1411
- return response;
1525
+ const response2 = ListMcpServersResponseSchema.parse(rawResponse);
1526
+ return response2;
1412
1527
  }
1413
1528
  async toggleMcpTool(serverName, toolName, enabled) {
1414
1529
  if (!this.currentSessionId) {
@@ -1417,7 +1532,7 @@ var DroidClient = class _DroidClient extends EventEmitter {
1417
1532
  );
1418
1533
  }
1419
1534
  const request = {
1420
- factoryApiVersion: LEGACY_FACTORY_API_VERSION,
1535
+ factoryApiVersion: LEGACY_FACTORY_API_VERSION2,
1421
1536
  factoryProtocolVersion: FACTORY_PROTOCOL_VERSION,
1422
1537
  type: "request",
1423
1538
  jsonrpc: JSONRPC_VERSION2,
@@ -1426,8 +1541,8 @@ var DroidClient = class _DroidClient extends EventEmitter {
1426
1541
  params: { serverName, toolName, enabled }
1427
1542
  };
1428
1543
  const rawResponse = await this.sendRequest(request, this.requestTimeout);
1429
- const response = ToggleMcpToolResponseSchema.parse(rawResponse);
1430
- return response;
1544
+ const response2 = ToggleMcpToolResponseSchema.parse(rawResponse);
1545
+ return response2;
1431
1546
  }
1432
1547
  async submitMcpAuthCode(params) {
1433
1548
  if (!this.currentSessionId) {
@@ -1436,7 +1551,7 @@ var DroidClient = class _DroidClient extends EventEmitter {
1436
1551
  );
1437
1552
  }
1438
1553
  const request = {
1439
- factoryApiVersion: LEGACY_FACTORY_API_VERSION,
1554
+ factoryApiVersion: LEGACY_FACTORY_API_VERSION2,
1440
1555
  factoryProtocolVersion: FACTORY_PROTOCOL_VERSION,
1441
1556
  type: "request",
1442
1557
  jsonrpc: JSONRPC_VERSION2,
@@ -1445,8 +1560,8 @@ var DroidClient = class _DroidClient extends EventEmitter {
1445
1560
  params
1446
1561
  };
1447
1562
  const rawResponse = await this.sendRequest(request, this.requestTimeout);
1448
- const response = SubmitMcpAuthCodeResponseSchema.parse(rawResponse);
1449
- return response;
1563
+ const response2 = SubmitMcpAuthCodeResponseSchema.parse(rawResponse);
1564
+ return response2;
1450
1565
  }
1451
1566
  async submitMcpAuthError(params) {
1452
1567
  if (!this.currentSessionId) {
@@ -1455,7 +1570,7 @@ var DroidClient = class _DroidClient extends EventEmitter {
1455
1570
  );
1456
1571
  }
1457
1572
  const request = {
1458
- factoryApiVersion: LEGACY_FACTORY_API_VERSION,
1573
+ factoryApiVersion: LEGACY_FACTORY_API_VERSION2,
1459
1574
  factoryProtocolVersion: FACTORY_PROTOCOL_VERSION,
1460
1575
  type: "request",
1461
1576
  jsonrpc: JSONRPC_VERSION2,
@@ -1464,27 +1579,27 @@ var DroidClient = class _DroidClient extends EventEmitter {
1464
1579
  params
1465
1580
  };
1466
1581
  const rawResponse = await this.sendRequest(request, this.requestTimeout);
1467
- const response = SubmitMcpAuthErrorResponseSchema.parse(rawResponse);
1468
- return response;
1582
+ const response2 = SubmitMcpAuthErrorResponseSchema.parse(rawResponse);
1583
+ return response2;
1469
1584
  }
1470
- async listSkills() {
1585
+ async listSkills(params = {}) {
1471
1586
  if (!this.currentSessionId) {
1472
1587
  throw new SessionError(
1473
1588
  "No active session. Call initializeSession or loadSession first."
1474
1589
  );
1475
1590
  }
1476
1591
  const request = {
1477
- factoryApiVersion: LEGACY_FACTORY_API_VERSION,
1592
+ factoryApiVersion: LEGACY_FACTORY_API_VERSION2,
1478
1593
  factoryProtocolVersion: FACTORY_PROTOCOL_VERSION,
1479
1594
  type: "request",
1480
1595
  jsonrpc: JSONRPC_VERSION2,
1481
1596
  id: uuidv4(),
1482
1597
  method: "droid.list_skills" /* LIST_SKILLS */,
1483
- params: {}
1598
+ params
1484
1599
  };
1485
1600
  const rawResponse = await this.sendRequest(request, this.requestTimeout);
1486
- const response = ListSkillsResponseSchema.parse(rawResponse);
1487
- return response;
1601
+ const response2 = ListSkillsResponseSchema.parse(rawResponse);
1602
+ return response2;
1488
1603
  }
1489
1604
  async setSkillDisabled(skillName, disabled, settingsLevel) {
1490
1605
  if (!this.currentSessionId) {
@@ -1493,7 +1608,7 @@ var DroidClient = class _DroidClient extends EventEmitter {
1493
1608
  );
1494
1609
  }
1495
1610
  const request = {
1496
- factoryApiVersion: LEGACY_FACTORY_API_VERSION,
1611
+ factoryApiVersion: LEGACY_FACTORY_API_VERSION2,
1497
1612
  factoryProtocolVersion: FACTORY_PROTOCOL_VERSION,
1498
1613
  type: "request",
1499
1614
  jsonrpc: JSONRPC_VERSION2,
@@ -1511,7 +1626,7 @@ var DroidClient = class _DroidClient extends EventEmitter {
1511
1626
  );
1512
1627
  }
1513
1628
  const request = {
1514
- factoryApiVersion: LEGACY_FACTORY_API_VERSION,
1629
+ factoryApiVersion: LEGACY_FACTORY_API_VERSION2,
1515
1630
  factoryProtocolVersion: FACTORY_PROTOCOL_VERSION,
1516
1631
  type: "request",
1517
1632
  jsonrpc: JSONRPC_VERSION2,
@@ -1520,8 +1635,8 @@ var DroidClient = class _DroidClient extends EventEmitter {
1520
1635
  params: {}
1521
1636
  };
1522
1637
  const rawResponse = await this.sendRequest(request, this.requestTimeout);
1523
- const response = ListCommandsResponseSchema.parse(rawResponse);
1524
- return response;
1638
+ const response2 = ListCommandsResponseSchema.parse(rawResponse);
1639
+ return response2;
1525
1640
  }
1526
1641
  async getContextStats() {
1527
1642
  if (!this.currentSessionId) {
@@ -1530,7 +1645,7 @@ var DroidClient = class _DroidClient extends EventEmitter {
1530
1645
  );
1531
1646
  }
1532
1647
  const request = {
1533
- factoryApiVersion: LEGACY_FACTORY_API_VERSION,
1648
+ factoryApiVersion: LEGACY_FACTORY_API_VERSION2,
1534
1649
  factoryProtocolVersion: FACTORY_PROTOCOL_VERSION,
1535
1650
  type: "request",
1536
1651
  jsonrpc: JSONRPC_VERSION2,
@@ -1539,8 +1654,8 @@ var DroidClient = class _DroidClient extends EventEmitter {
1539
1654
  params: {}
1540
1655
  };
1541
1656
  const rawResponse = await this.sendRequest(request, this.requestTimeout);
1542
- const response = GetContextStatsResponseSchema.parse(rawResponse);
1543
- return response;
1657
+ const response2 = GetContextStatsResponseSchema.parse(rawResponse);
1658
+ return response2;
1544
1659
  }
1545
1660
  async getContextBreakdown() {
1546
1661
  if (!this.currentSessionId) {
@@ -1549,7 +1664,7 @@ var DroidClient = class _DroidClient extends EventEmitter {
1549
1664
  );
1550
1665
  }
1551
1666
  const request = {
1552
- factoryApiVersion: LEGACY_FACTORY_API_VERSION,
1667
+ factoryApiVersion: LEGACY_FACTORY_API_VERSION2,
1553
1668
  factoryProtocolVersion: FACTORY_PROTOCOL_VERSION,
1554
1669
  type: "request",
1555
1670
  jsonrpc: JSONRPC_VERSION2,
@@ -1558,27 +1673,45 @@ var DroidClient = class _DroidClient extends EventEmitter {
1558
1673
  params: {}
1559
1674
  };
1560
1675
  const rawResponse = await this.sendRequest(request, this.requestTimeout);
1561
- const response = GetContextBreakdownResponseSchema.parse(rawResponse);
1562
- return response;
1676
+ const response2 = GetContextBreakdownResponseSchema.parse(rawResponse);
1677
+ return response2;
1563
1678
  }
1564
- async submitBugReport(userComment, clientLogs, source) {
1679
+ async killBackgroundProcess(processId) {
1565
1680
  if (!this.currentSessionId) {
1566
1681
  throw new SessionError(
1567
1682
  "No active session. Call initializeSession or loadSession first."
1568
1683
  );
1569
1684
  }
1570
1685
  const request = {
1571
- factoryApiVersion: LEGACY_FACTORY_API_VERSION,
1686
+ factoryApiVersion: LEGACY_FACTORY_API_VERSION2,
1687
+ factoryProtocolVersion: FACTORY_PROTOCOL_VERSION,
1688
+ type: "request",
1689
+ jsonrpc: JSONRPC_VERSION2,
1690
+ id: uuidv4(),
1691
+ method: "droid.kill_background_process" /* KILL_BACKGROUND_PROCESS */,
1692
+ params: { processId }
1693
+ };
1694
+ const rawResponse = await this.sendRequest(request, this.requestTimeout);
1695
+ return KillBackgroundProcessResponseSchema.parse(rawResponse);
1696
+ }
1697
+ async submitBugReport(userComment, clientLogs, source, tuiLogs) {
1698
+ if (!this.currentSessionId) {
1699
+ throw new SessionError(
1700
+ "No active session. Call initializeSession or loadSession first."
1701
+ );
1702
+ }
1703
+ const request = {
1704
+ factoryApiVersion: LEGACY_FACTORY_API_VERSION2,
1572
1705
  factoryProtocolVersion: FACTORY_PROTOCOL_VERSION,
1573
1706
  type: "request",
1574
1707
  jsonrpc: JSONRPC_VERSION2,
1575
1708
  id: uuidv4(),
1576
1709
  method: "droid.submit_bug_report" /* SUBMIT_BUG_REPORT */,
1577
- params: { userComment, clientLogs, source }
1710
+ params: { userComment, clientLogs, source, tuiLogs }
1578
1711
  };
1579
1712
  const rawResponse = await this.sendRequest(request, this.requestTimeout);
1580
- const response = SubmitBugReportResponseSchema.parse(rawResponse);
1581
- return response;
1713
+ const response2 = SubmitBugReportResponseSchema.parse(rawResponse);
1714
+ return response2;
1582
1715
  }
1583
1716
  async getRewindInfo(params, timeout = this.requestTimeout) {
1584
1717
  if (!this.currentSessionId) {
@@ -1587,7 +1720,7 @@ var DroidClient = class _DroidClient extends EventEmitter {
1587
1720
  );
1588
1721
  }
1589
1722
  const request = {
1590
- factoryApiVersion: LEGACY_FACTORY_API_VERSION,
1723
+ factoryApiVersion: LEGACY_FACTORY_API_VERSION2,
1591
1724
  factoryProtocolVersion: FACTORY_PROTOCOL_VERSION,
1592
1725
  type: "request",
1593
1726
  jsonrpc: JSONRPC_VERSION2,
@@ -1608,7 +1741,7 @@ var DroidClient = class _DroidClient extends EventEmitter {
1608
1741
  );
1609
1742
  }
1610
1743
  const request = {
1611
- factoryApiVersion: LEGACY_FACTORY_API_VERSION,
1744
+ factoryApiVersion: LEGACY_FACTORY_API_VERSION2,
1612
1745
  factoryProtocolVersion: FACTORY_PROTOCOL_VERSION,
1613
1746
  type: "request",
1614
1747
  jsonrpc: JSONRPC_VERSION2,
@@ -1635,19 +1768,58 @@ var DroidClient = class _DroidClient extends EventEmitter {
1635
1768
  );
1636
1769
  }
1637
1770
  const request = {
1638
- factoryApiVersion: LEGACY_FACTORY_API_VERSION,
1771
+ factoryApiVersion: LEGACY_FACTORY_API_VERSION2,
1639
1772
  factoryProtocolVersion: FACTORY_PROTOCOL_VERSION,
1640
1773
  type: "request",
1641
1774
  jsonrpc: JSONRPC_VERSION2,
1642
1775
  id: uuidv4(),
1643
1776
  method: "droid.compact_session" /* COMPACT_SESSION */,
1644
1777
  params: {
1645
- customInstructions: params.customInstructions
1778
+ customInstructions: params.customInstructions,
1779
+ mode: params.mode
1646
1780
  }
1647
1781
  };
1648
1782
  const rawResponse = await this.sendRequest(request, COMPACTION_TIMEOUT2);
1649
1783
  return CompactSessionResponseSchema.parse(rawResponse);
1650
1784
  }
1785
+ /** Summarizes the session the way `/handoff` does, without writing anything. */
1786
+ async getHandoffSummary() {
1787
+ if (!this.currentSessionId) {
1788
+ throw new SessionError(
1789
+ "No active session. Call initializeSession or loadSession first."
1790
+ );
1791
+ }
1792
+ const request = {
1793
+ factoryApiVersion: LEGACY_FACTORY_API_VERSION2,
1794
+ factoryProtocolVersion: FACTORY_PROTOCOL_VERSION,
1795
+ type: "request",
1796
+ jsonrpc: JSONRPC_VERSION2,
1797
+ id: uuidv4(),
1798
+ method: "droid.get_handoff_summary" /* GET_HANDOFF_SUMMARY */,
1799
+ params: {}
1800
+ };
1801
+ const rawResponse = await this.sendRequest(request, COMPACTION_TIMEOUT2);
1802
+ return GetHandoffSummaryResponseSchema.parse(rawResponse);
1803
+ }
1804
+ /** Writes the short summary people joining a shared copy read first. */
1805
+ async summarizeForMembers(params) {
1806
+ if (!this.currentSessionId) {
1807
+ throw new SessionError(
1808
+ "No active session. Call initializeSession or loadSession first."
1809
+ );
1810
+ }
1811
+ const request = {
1812
+ factoryApiVersion: LEGACY_FACTORY_API_VERSION2,
1813
+ factoryProtocolVersion: FACTORY_PROTOCOL_VERSION,
1814
+ type: "request",
1815
+ jsonrpc: JSONRPC_VERSION2,
1816
+ id: uuidv4(),
1817
+ method: "droid.summarize_for_members" /* SUMMARIZE_FOR_MEMBERS */,
1818
+ params: { handoffSummary: params.handoffSummary }
1819
+ };
1820
+ const rawResponse = await this.sendRequest(request, COMPACTION_TIMEOUT2);
1821
+ return SummarizeForMembersResponseSchema.parse(rawResponse);
1822
+ }
1651
1823
  async forkSession(params) {
1652
1824
  if (!this.currentSessionId) {
1653
1825
  throw new SessionError(
@@ -1655,7 +1827,7 @@ var DroidClient = class _DroidClient extends EventEmitter {
1655
1827
  );
1656
1828
  }
1657
1829
  const request = {
1658
- factoryApiVersion: LEGACY_FACTORY_API_VERSION,
1830
+ factoryApiVersion: LEGACY_FACTORY_API_VERSION2,
1659
1831
  factoryProtocolVersion: FACTORY_PROTOCOL_VERSION,
1660
1832
  type: "request",
1661
1833
  jsonrpc: JSONRPC_VERSION2,
@@ -1673,7 +1845,7 @@ var DroidClient = class _DroidClient extends EventEmitter {
1673
1845
  );
1674
1846
  }
1675
1847
  const request = {
1676
- factoryApiVersion: LEGACY_FACTORY_API_VERSION,
1848
+ factoryApiVersion: LEGACY_FACTORY_API_VERSION2,
1677
1849
  factoryProtocolVersion: FACTORY_PROTOCOL_VERSION,
1678
1850
  type: "request",
1679
1851
  jsonrpc: JSONRPC_VERSION2,
@@ -1687,15 +1859,15 @@ var DroidClient = class _DroidClient extends EventEmitter {
1687
1859
  return RenameSessionResponseSchema.parse(rawResponse);
1688
1860
  }
1689
1861
  async warmupCache() {
1690
- const response = {
1691
- factoryApiVersion: LEGACY_FACTORY_API_VERSION,
1862
+ const response2 = {
1863
+ factoryApiVersion: LEGACY_FACTORY_API_VERSION2,
1692
1864
  factoryProtocolVersion: FACTORY_PROTOCOL_VERSION,
1693
1865
  type: "response",
1694
1866
  jsonrpc: JSONRPC_VERSION2,
1695
1867
  id: uuidv4(),
1696
1868
  result: {}
1697
1869
  };
1698
- return WarmupCacheResponseSchema.parse(response);
1870
+ return WarmupCacheResponseSchema.parse(response2);
1699
1871
  }
1700
1872
  onNotification(callback, filter) {
1701
1873
  const handler = (notification) => {
@@ -1726,25 +1898,61 @@ var DroidClient = class _DroidClient extends EventEmitter {
1726
1898
  setPermissionHandler(handler) {
1727
1899
  this.permissionHandlers.set(
1728
1900
  "default",
1729
- async (event) => await handler(event)
1901
+ async (event, setResponseMetadata) => await handler(event, setResponseMetadata)
1730
1902
  );
1731
1903
  }
1732
1904
  clearPermissionHandler() {
1733
1905
  this.permissionHandlers.delete("default");
1734
1906
  }
1735
1907
  setAskUserHandler(handler) {
1736
- this.askUserHandlers.set("default", async (event) => await handler(event));
1908
+ this.askUserHandlers.set(
1909
+ "default",
1910
+ async (event, setResponseMetadata) => await handler(event, setResponseMetadata)
1911
+ );
1912
+ const held = this.heldQueuedAskUserRequests;
1913
+ this.heldQueuedAskUserRequests = [];
1914
+ for (const event of held) {
1915
+ void this.answerAskUserRequest(event).catch((error) => {
1916
+ this.reportHandlerFailure(
1917
+ error,
1918
+ "Failed to answer a held queued ask-user request"
1919
+ );
1920
+ });
1921
+ }
1737
1922
  }
1738
1923
  clearAskUserHandler() {
1739
1924
  this.askUserHandlers.delete("default");
1740
1925
  }
1741
- async close() {
1742
- const closeError = new ConnectionError(
1926
+ setPickOptionsHandler(handler) {
1927
+ this.pickOptionsHandlers.set(
1928
+ "default",
1929
+ async (event, setResponseMetadata) => await handler(event, setResponseMetadata)
1930
+ );
1931
+ }
1932
+ clearPickOptionsHandler() {
1933
+ this.pickOptionsHandlers.delete("default");
1934
+ }
1935
+ async close(options) {
1936
+ const closeError = new DroidConnectionEndedError(
1743
1937
  "Droid client closed: pending requests cancelled"
1744
1938
  );
1939
+ if (options?.closeTransportFirst) {
1940
+ try {
1941
+ await this.transport.close();
1942
+ } finally {
1943
+ this.rejectAllPending(closeError);
1944
+ this.permissionHandlers.clear();
1945
+ this.askUserHandlers.clear();
1946
+ this.heldQueuedAskUserRequests = [];
1947
+ this.removeAllListeners();
1948
+ }
1949
+ return;
1950
+ }
1745
1951
  this.rejectAllPending(closeError);
1746
1952
  this.permissionHandlers.clear();
1747
1953
  this.askUserHandlers.clear();
1954
+ this.heldQueuedAskUserRequests = [];
1955
+ this.pickOptionsHandlers.clear();
1748
1956
  this.removeAllListeners();
1749
1957
  await this.transport.close();
1750
1958
  }
@@ -1765,6 +1973,10 @@ var DroidClient = class _DroidClient extends EventEmitter {
1765
1973
  reject(error);
1766
1974
  });
1767
1975
  this.pendingAskUserRequests.clear();
1976
+ this.pendingPickOptionsRequests.forEach(({ reject }) => {
1977
+ reject(error);
1978
+ });
1979
+ this.pendingPickOptionsRequests.clear();
1768
1980
  }
1769
1981
  settleRequest(requestId) {
1770
1982
  this.pendingRequests.delete(requestId);
@@ -1776,16 +1988,31 @@ var DroidClient = class _DroidClient extends EventEmitter {
1776
1988
  } catch {
1777
1989
  }
1778
1990
  }
1779
- async sendRequest(request, timeout) {
1991
+ /** `_meta` for an answer this client gives itself: its caller and the current trace context. */
1992
+ responseMetadata() {
1993
+ const meta = {};
1994
+ const requestAttribution = this.getRequestAttribution?.();
1995
+ if (requestAttribution) meta.requestAttribution = requestAttribution;
1996
+ this.tracing.injectContext(meta, this.tracing.getCurrentContext());
1997
+ return meta;
1998
+ }
1999
+ async sendResponse(response2, hostMeta) {
2000
+ const meta = hostMeta ?? this.responseMetadata();
2001
+ await this.transport.send(
2002
+ Object.keys(meta).length > 0 ? { ...response2, _meta: meta } : response2
2003
+ );
2004
+ }
2005
+ async sendRequest(request, timeout, sessionId) {
1780
2006
  if (this.transportError) {
1781
2007
  throw this.transportError;
1782
2008
  }
1783
- const requestAttribution = this.getRequestAttribution?.();
1784
- const _meta = {
1785
- ...requestAttribution && { requestAttribution }
1786
- };
2009
+ const requestAttribution = request._meta?.requestAttribution ?? this.getRequestAttribution?.();
2010
+ const _meta = { ...request._meta };
2011
+ delete _meta.requestAttribution;
2012
+ if (requestAttribution) _meta.requestAttribution = requestAttribution;
1787
2013
  this.tracing.injectContext(_meta, this.tracing.getCurrentContext());
1788
- const requestWithMeta = _meta.traceparent || _meta.requestAttribution ? { ...request, _meta } : request;
2014
+ const { _meta: _originalMeta, ...requestWithoutMeta } = request;
2015
+ const requestWithMeta = Object.keys(_meta).length > 0 ? { ...request, _meta } : requestWithoutMeta;
1789
2016
  const responsePromise = new Promise(
1790
2017
  (resolve2, reject) => {
1791
2018
  const timeoutId = timeout ? setTimeout(() => {
@@ -1802,16 +2029,23 @@ var DroidClient = class _DroidClient extends EventEmitter {
1802
2029
  method: request.method,
1803
2030
  resolve: resolve2,
1804
2031
  reject,
1805
- timeoutId
2032
+ timeoutId,
2033
+ sessionId
1806
2034
  });
1807
2035
  }
1808
2036
  );
2037
+ void responsePromise.catch(() => {
2038
+ });
2039
+ void this.writeRequest(requestWithMeta);
2040
+ return responsePromise;
2041
+ }
2042
+ async writeRequest(request) {
1809
2043
  try {
1810
2044
  this.logger.info("[DroidClient] [daemon -> droid] sending request", {
1811
2045
  requestId: request.id,
1812
2046
  method: request.method
1813
2047
  });
1814
- await this.transport.send(JSON.stringify(requestWithMeta));
2048
+ await this.transport.send(request);
1815
2049
  } catch (error) {
1816
2050
  const pending = this.pendingRequests.get(request.id);
1817
2051
  if (pending) {
@@ -1827,141 +2061,145 @@ var DroidClient = class _DroidClient extends EventEmitter {
1827
2061
  );
1828
2062
  }
1829
2063
  }
1830
- return responsePromise;
1831
2064
  }
1832
2065
  async handleMessage(message) {
1833
2066
  try {
1834
- const parsed = JSON.parse(message);
1835
- if ("result" in parsed || "error" in parsed) {
1836
- this.handleResponse(parsed);
1837
- } else if ("method" in parsed) {
1838
- await this.handleEvent(parsed);
2067
+ if (message.type === "response") {
2068
+ this.handleResponse(message);
1839
2069
  } else {
1840
- throw new ProtocolError("Invalid message format", {
1841
- message
1842
- });
2070
+ await this.handleEvent(message);
1843
2071
  }
1844
2072
  } catch {
1845
- this.logger.warn("[DroidClient] Failed to handle message", {
1846
- messageByteLength: new TextEncoder().encode(message).byteLength
1847
- });
2073
+ this.logger.warn("[DroidClient] Failed to handle message");
1848
2074
  }
1849
2075
  }
1850
- handleResponse(response) {
2076
+ handleResponse(response2) {
1851
2077
  try {
1852
- const responseId = getResponseId(response);
2078
+ const responseId = getResponseId(response2);
1853
2079
  if (responseId !== null && responseId !== void 0) {
1854
2080
  const pending = this.pendingRequests.get(responseId);
1855
2081
  if (pending) {
1856
2082
  let validatedResponse;
1857
2083
  switch (pending.method) {
1858
2084
  case "droid.initialize_session" /* INITIALIZE_SESSION */:
1859
- validatedResponse = InitializeSessionResponseSchema.parse(response);
2085
+ validatedResponse = InitializeSessionResponseSchema.parse(response2);
1860
2086
  break;
1861
2087
  case "droid.load_session" /* LOAD_SESSION */:
1862
- validatedResponse = LoadSessionResponseSchema.parse(response);
2088
+ validatedResponse = LoadSessionResponseSchema.parse(response2);
1863
2089
  break;
1864
2090
  case "droid.add_user_message" /* ADD_USER_MESSAGE */:
1865
- validatedResponse = AddUserMessageResponseSchema.parse(response);
2091
+ validatedResponse = AddUserMessageResponseSchema.parse(response2);
1866
2092
  break;
1867
2093
  case "droid.append_messages" /* APPEND_MESSAGES */:
1868
- validatedResponse = AppendMessagesResponseSchema.parse(response);
2094
+ validatedResponse = AppendMessagesResponseSchema.parse(response2);
1869
2095
  break;
1870
2096
  case "droid.resolve_queued_user_message" /* RESOLVE_QUEUED_USER_MESSAGE */:
1871
- validatedResponse = ResolveQueuedUserMessageResponseSchema.parse(response);
2097
+ validatedResponse = ResolveQueuedUserMessageResponseSchema.parse(response2);
1872
2098
  break;
1873
2099
  case "droid.interrupt_session" /* INTERRUPT_SESSION */:
1874
- validatedResponse = InterruptSessionResponseSchema.parse(response);
2100
+ validatedResponse = InterruptSessionResponseSchema.parse(response2);
1875
2101
  break;
1876
2102
  case "droid.close_session" /* CLOSE_SESSION */:
1877
- validatedResponse = CloseSessionResponseSchema.parse(response);
2103
+ validatedResponse = CloseSessionResponseSchema.parse(response2);
1878
2104
  break;
1879
2105
  case "droid.update_session_settings" /* UPDATE_SESSION_SETTINGS */:
1880
- validatedResponse = UpdateSessionSettingsResponseSchema.parse(response);
2106
+ validatedResponse = UpdateSessionSettingsResponseSchema.parse(response2);
1881
2107
  break;
1882
2108
  case "droid.change_working_directory" /* CHANGE_WORKING_DIRECTORY */:
1883
- validatedResponse = ChangeWorkingDirectoryResponseSchema.parse(response);
2109
+ validatedResponse = ChangeWorkingDirectoryResponseSchema.parse(response2);
1884
2110
  break;
1885
2111
  case "droid.toggle_mcp_server" /* TOGGLE_MCP_SERVER */:
1886
- validatedResponse = ToggleMcpServerResponseSchema.parse(response);
2112
+ validatedResponse = ToggleMcpServerResponseSchema.parse(response2);
1887
2113
  break;
1888
2114
  case "droid.authenticate_mcp_server" /* AUTHENTICATE_MCP_SERVER */:
1889
- validatedResponse = AuthenticateMcpServerResponseSchema.parse(response);
2115
+ validatedResponse = AuthenticateMcpServerResponseSchema.parse(response2);
1890
2116
  break;
1891
2117
  case "droid.cancel_mcp_auth" /* CANCEL_MCP_AUTH */:
1892
- validatedResponse = CancelMcpAuthResponseSchema.parse(response);
2118
+ validatedResponse = CancelMcpAuthResponseSchema.parse(response2);
1893
2119
  break;
1894
2120
  case "droid.clear_mcp_auth" /* CLEAR_MCP_AUTH */:
1895
- validatedResponse = ClearMcpAuthResponseSchema.parse(response);
2121
+ validatedResponse = ClearMcpAuthResponseSchema.parse(response2);
1896
2122
  break;
1897
2123
  case "droid.add_mcp_server" /* ADD_MCP_SERVER */:
1898
- validatedResponse = AddMcpServerResponseSchema.parse(response);
2124
+ validatedResponse = AddMcpServerResponseSchema.parse(response2);
1899
2125
  break;
1900
2126
  case "droid.remove_mcp_server" /* REMOVE_MCP_SERVER */:
1901
- validatedResponse = RemoveMcpServerResponseSchema.parse(response);
2127
+ validatedResponse = RemoveMcpServerResponseSchema.parse(response2);
1902
2128
  break;
1903
2129
  case "droid.list_models" /* LIST_MODELS */:
1904
- validatedResponse = ListModelsResponseSchema.parse(response);
2130
+ validatedResponse = ListModelsResponseSchema.parse(response2);
1905
2131
  break;
1906
2132
  case "droid.list_mcp_registry" /* LIST_MCP_REGISTRY */:
1907
- validatedResponse = ListMcpRegistryResponseSchema.parse(response);
2133
+ validatedResponse = ListMcpRegistryResponseSchema.parse(response2);
1908
2134
  break;
1909
2135
  case "droid.list_mcp_tools" /* LIST_MCP_TOOLS */:
1910
- validatedResponse = ListMcpToolsResponseSchema.parse(response);
2136
+ validatedResponse = ListMcpToolsResponseSchema.parse(response2);
1911
2137
  break;
1912
2138
  case "droid.list_tools" /* LIST_TOOLS */:
1913
- validatedResponse = ListToolsResponseSchema.parse(response);
2139
+ validatedResponse = ListToolsResponseSchema.parse(response2);
2140
+ break;
2141
+ case "droid.refresh_connector_tool_catalog" /* REFRESH_CONNECTOR_TOOL_CATALOG */:
2142
+ validatedResponse = RefreshConnectorToolCatalogResponseSchema.parse(response2);
1914
2143
  break;
1915
2144
  case "droid.toggle_mcp_tool" /* TOGGLE_MCP_TOOL */:
1916
- validatedResponse = ToggleMcpToolResponseSchema.parse(response);
2145
+ validatedResponse = ToggleMcpToolResponseSchema.parse(response2);
1917
2146
  break;
1918
2147
  case "droid.submit_mcp_auth_code" /* SUBMIT_MCP_AUTH_CODE */:
1919
- validatedResponse = SubmitMcpAuthCodeResponseSchema.parse(response);
2148
+ validatedResponse = SubmitMcpAuthCodeResponseSchema.parse(response2);
1920
2149
  break;
1921
2150
  case "droid.submit_mcp_auth_error" /* SUBMIT_MCP_AUTH_ERROR */:
1922
- validatedResponse = SubmitMcpAuthErrorResponseSchema.parse(response);
2151
+ validatedResponse = SubmitMcpAuthErrorResponseSchema.parse(response2);
1923
2152
  break;
1924
2153
  case "droid.kill_worker_session" /* KILL_WORKER_SESSION */:
1925
- validatedResponse = KillWorkerSessionResponseSchema.parse(response);
2154
+ validatedResponse = KillWorkerSessionResponseSchema.parse(response2);
1926
2155
  break;
1927
2156
  case "droid.list_skills" /* LIST_SKILLS */:
1928
- validatedResponse = ListSkillsResponseSchema.parse(response);
2157
+ validatedResponse = ListSkillsResponseSchema.parse(response2);
1929
2158
  break;
1930
2159
  case "droid.set_skill_disabled" /* SET_SKILL_DISABLED */:
1931
- validatedResponse = SetSkillDisabledResponseSchema.parse(response);
2160
+ validatedResponse = SetSkillDisabledResponseSchema.parse(response2);
1932
2161
  break;
1933
2162
  case "droid.list_commands" /* LIST_COMMANDS */:
1934
- validatedResponse = ListCommandsResponseSchema.parse(response);
2163
+ validatedResponse = ListCommandsResponseSchema.parse(response2);
1935
2164
  break;
1936
2165
  case "droid.submit_bug_report" /* SUBMIT_BUG_REPORT */:
1937
- validatedResponse = SubmitBugReportResponseSchema.parse(response);
2166
+ validatedResponse = SubmitBugReportResponseSchema.parse(response2);
1938
2167
  break;
1939
2168
  case "droid.list_mcp_servers" /* LIST_MCP_SERVERS */:
1940
- validatedResponse = ListMcpServersResponseSchema.parse(response);
2169
+ validatedResponse = ListMcpServersResponseSchema.parse(response2);
1941
2170
  break;
1942
2171
  case "droid.get_rewind_info" /* GET_REWIND_INFO */:
1943
- validatedResponse = GetRewindInfoResponseSchema.parse(response);
2172
+ validatedResponse = GetRewindInfoResponseSchema.parse(response2);
1944
2173
  break;
1945
2174
  case "droid.execute_rewind" /* EXECUTE_REWIND */:
1946
- validatedResponse = ExecuteRewindResponseSchema.parse(response);
2175
+ validatedResponse = ExecuteRewindResponseSchema.parse(response2);
1947
2176
  break;
1948
2177
  case "droid.compact_session" /* COMPACT_SESSION */:
1949
- validatedResponse = CompactSessionResponseSchema.parse(response);
2178
+ validatedResponse = CompactSessionResponseSchema.parse(response2);
2179
+ break;
2180
+ case "droid.get_handoff_summary" /* GET_HANDOFF_SUMMARY */:
2181
+ validatedResponse = GetHandoffSummaryResponseSchema.parse(response2);
2182
+ break;
2183
+ case "droid.summarize_for_members" /* SUMMARIZE_FOR_MEMBERS */:
2184
+ validatedResponse = SummarizeForMembersResponseSchema.parse(response2);
1950
2185
  break;
1951
2186
  case "droid.fork_session" /* FORK_SESSION */:
1952
- validatedResponse = ForkSessionResponseSchema.parse(response);
2187
+ validatedResponse = ForkSessionResponseSchema.parse(response2);
1953
2188
  break;
1954
2189
  case "droid.rename_session" /* RENAME_SESSION */:
1955
- validatedResponse = RenameSessionResponseSchema.parse(response);
2190
+ validatedResponse = RenameSessionResponseSchema.parse(response2);
1956
2191
  break;
1957
2192
  case "droid.get_context_stats" /* GET_CONTEXT_STATS */:
1958
- validatedResponse = GetContextStatsResponseSchema.parse(response);
2193
+ validatedResponse = GetContextStatsResponseSchema.parse(response2);
1959
2194
  break;
1960
2195
  case "droid.get_context_breakdown" /* GET_CONTEXT_BREAKDOWN */:
1961
- validatedResponse = GetContextBreakdownResponseSchema.parse(response);
2196
+ validatedResponse = GetContextBreakdownResponseSchema.parse(response2);
1962
2197
  break;
1963
2198
  case "droid.warmup_cache" /* WARMUP_CACHE */:
1964
- validatedResponse = WarmupCacheResponseSchema.parse(response);
2199
+ validatedResponse = WarmupCacheResponseSchema.parse(response2);
2200
+ break;
2201
+ case "droid.kill_background_process" /* KILL_BACKGROUND_PROCESS */:
2202
+ validatedResponse = KillBackgroundProcessResponseSchema.parse(response2);
1965
2203
  break;
1966
2204
  default: {
1967
2205
  const exhaustiveCheck = pending.method;
@@ -1979,7 +2217,7 @@ var DroidClient = class _DroidClient extends EventEmitter {
1979
2217
  this.settleRequest(responseId);
1980
2218
  pending.resolve(validatedResponse);
1981
2219
  } else {
1982
- const validatedResponse = JsonRpcBaseResponseSchema.parse(response);
2220
+ const validatedResponse = JsonRpcBaseResponseSchema.parse(response2);
1983
2221
  this.logger.info(
1984
2222
  "[DroidClient] Received response for request not in pending map",
1985
2223
  {
@@ -1989,7 +2227,7 @@ var DroidClient = class _DroidClient extends EventEmitter {
1989
2227
  }
1990
2228
  }
1991
2229
  } catch (error) {
1992
- const responseId = getResponseId(response);
2230
+ const responseId = getResponseId(response2);
1993
2231
  const pending = responseId !== null && responseId !== void 0 ? this.pendingRequests.get(responseId) : void 0;
1994
2232
  const zodIssues = error instanceof z.ZodError ? error.issues.map((issue) => ({
1995
2233
  path: issue.path,
@@ -2025,7 +2263,7 @@ var DroidClient = class _DroidClient extends EventEmitter {
2025
2263
  const syntheticFailure = {
2026
2264
  jsonrpc: JSONRPC_VERSION2,
2027
2265
  type: "response",
2028
- factoryApiVersion: LEGACY_FACTORY_API_VERSION,
2266
+ factoryApiVersion: LEGACY_FACTORY_API_VERSION2,
2029
2267
  factoryProtocolVersion: FACTORY_PROTOCOL_VERSION,
2030
2268
  id: responseId,
2031
2269
  error: {
@@ -2044,14 +2282,20 @@ var DroidClient = class _DroidClient extends EventEmitter {
2044
2282
  const notification = validatedEvent.params.notification;
2045
2283
  if (notification.type === "agent_turn_completed" /* AGENT_TURN_COMPLETED */) {
2046
2284
  this.completedTurnObserved = true;
2047
- } else if (notification.type === "droid_working_state_changed" /* DROID_WORKING_STATE_CHANGED */ && notification.newState !== "idle" /* Idle */) {
2285
+ } else if (notification.type === "droid_working_state_changed" /* DROID_WORKING_STATE_CHANGED */ && DROID_RUNNING_STATES.has(notification.newState)) {
2048
2286
  this.completedTurnObserved = false;
2287
+ } else if (notification.type === "permission_resolved" /* PERMISSION_RESOLVED */) {
2288
+ this.pendingPermissionRequests.get(notification.requestId)?.withdraw();
2049
2289
  }
2050
2290
  this.emit("session_notification" /* SESSION_NOTIFICATION */, validatedEvent);
2051
2291
  } else if (validatedEvent.method === "droid.request_permission" /* REQUEST_PERMISSION */) {
2052
2292
  await this.handlePermissionRequest(validatedEvent);
2053
2293
  } else if (validatedEvent.method === "droid.ask_user" /* ASK_USER */) {
2054
2294
  await this.handleAskUserRequest(validatedEvent);
2295
+ } else if (validatedEvent.method === "droid.pick_options" /* PICK_OPTIONS */) {
2296
+ await this.handlePickOptionsRequest(validatedEvent);
2297
+ } else if (validatedEvent.method === "droid.disconnect" /* DISCONNECT */) {
2298
+ this.disconnected = true;
2055
2299
  }
2056
2300
  } catch (error) {
2057
2301
  this.logDroppedEventParseError(error, event);
@@ -2076,7 +2320,7 @@ var DroidClient = class _DroidClient extends EventEmitter {
2076
2320
  const errorResponse = {
2077
2321
  jsonrpc: JSONRPC_VERSION2,
2078
2322
  type: "response",
2079
- factoryApiVersion: LEGACY_FACTORY_API_VERSION,
2323
+ factoryApiVersion: LEGACY_FACTORY_API_VERSION2,
2080
2324
  factoryProtocolVersion: FACTORY_PROTOCOL_VERSION,
2081
2325
  id,
2082
2326
  error: {
@@ -2086,7 +2330,7 @@ var DroidClient = class _DroidClient extends EventEmitter {
2086
2330
  }
2087
2331
  };
2088
2332
  try {
2089
- await this.transport.send(JSON.stringify(errorResponse));
2333
+ await this.sendResponse(errorResponse, void 0);
2090
2334
  } catch (sendError) {
2091
2335
  this.logger.exception(
2092
2336
  sendError,
@@ -2131,6 +2375,13 @@ var DroidClient = class _DroidClient extends EventEmitter {
2131
2375
  });
2132
2376
  }
2133
2377
  }
2378
+ reportHandlerFailure(error, message) {
2379
+ if (error instanceof RequestCancelledError || isDroidConnectionEnded(error)) {
2380
+ this.logger.info(message, { cause: error });
2381
+ } else {
2382
+ this.logger.exception(error, message);
2383
+ }
2384
+ }
2134
2385
  async handlePermissionRequest(event) {
2135
2386
  if (this.pendingPermissionRequests.has(event.id)) {
2136
2387
  this.logger.info(
@@ -2145,10 +2396,14 @@ var DroidClient = class _DroidClient extends EventEmitter {
2145
2396
  requestId: event.id,
2146
2397
  count: event.params.toolUses.length
2147
2398
  });
2399
+ let hostMeta;
2400
+ const setResponseMetadata = (meta) => {
2401
+ hostMeta = meta;
2402
+ };
2148
2403
  this.emit("request_permission" /* REQUEST_PERMISSION */, event);
2149
- let selectedOption = "cancel" /* Cancel */;
2150
- let comment;
2151
- let editedSpecContent;
2404
+ let result = {
2405
+ selectedOption: "cancel" /* Cancel */
2406
+ };
2152
2407
  const handler = this.permissionHandlers.get("default");
2153
2408
  if (handler) {
2154
2409
  try {
@@ -2158,15 +2413,25 @@ var DroidClient = class _DroidClient extends EventEmitter {
2158
2413
  requestId: event.id
2159
2414
  }
2160
2415
  );
2161
- const handlerPromise = new Promise(
2162
- (resolve2, reject) => {
2163
- this.pendingPermissionRequests.set(event.id, { reject });
2164
- handler(event).then(resolve2).catch(reject).finally(() => {
2416
+ const handlerPromise = new Promise((resolve2, reject) => {
2417
+ this.pendingPermissionRequests.set(event.id, {
2418
+ reject,
2419
+ withdraw: () => {
2165
2420
  this.pendingPermissionRequests.delete(event.id);
2166
- });
2167
- }
2168
- );
2421
+ resolve2(PERMISSION_WITHDRAWN);
2422
+ }
2423
+ });
2424
+ handler(event, setResponseMetadata).then(resolve2).catch(reject).finally(() => {
2425
+ this.pendingPermissionRequests.delete(event.id);
2426
+ });
2427
+ });
2169
2428
  const handlerResult = await handlerPromise;
2429
+ if (handlerResult === PERMISSION_WITHDRAWN) {
2430
+ this.logger.info("[DroidClient] Permission request withdrawn", {
2431
+ requestId: event.id
2432
+ });
2433
+ return;
2434
+ }
2170
2435
  if (typeof handlerResult === "string" && handlerResult === "proceed_edit" /* ProceedEdit */) {
2171
2436
  throw new DroidClientError(
2172
2437
  "Permission handler returned proceed_edit without editedSpecContent"
@@ -2176,9 +2441,7 @@ var DroidClient = class _DroidClient extends EventEmitter {
2176
2441
  typeof handlerResult === "string" ? { selectedOption: handlerResult } : handlerResult
2177
2442
  );
2178
2443
  if (parsedHandlerResult.success) {
2179
- selectedOption = parsedHandlerResult.data.selectedOption;
2180
- comment = parsedHandlerResult.data.comment;
2181
- editedSpecContent = parsedHandlerResult.data.editedSpecContent;
2444
+ result = parsedHandlerResult.data;
2182
2445
  } else {
2183
2446
  throw new DroidClientError(
2184
2447
  "Permission handler returned invalid result",
@@ -2187,19 +2450,20 @@ var DroidClient = class _DroidClient extends EventEmitter {
2187
2450
  }
2188
2451
  );
2189
2452
  }
2190
- if (!event.params.options.some(
2191
- (option) => option.value === selectedOption
2453
+ if (!isOfferedPermissionOutcome(
2454
+ event.params.options,
2455
+ result.selectedOption
2192
2456
  )) {
2193
2457
  throw new DroidClientError(
2194
- `Permission handler selected unavailable option: ${selectedOption}`
2458
+ `Permission handler selected unavailable option: ${result.selectedOption}`
2195
2459
  );
2196
2460
  }
2197
2461
  this.logger.info("[DroidClient] Permission handler resolved", {
2198
2462
  requestId: event.id,
2199
- result: selectedOption
2463
+ result: result.selectedOption
2200
2464
  });
2201
2465
  } catch (error) {
2202
- this.logger.exception(
2466
+ this.reportHandlerFailure(
2203
2467
  error,
2204
2468
  "Failed to handle permission request (processing)"
2205
2469
  );
@@ -2214,7 +2478,7 @@ var DroidClient = class _DroidClient extends EventEmitter {
2214
2478
  const cancelResponse = {
2215
2479
  type: "response",
2216
2480
  jsonrpc: JSONRPC_VERSION2,
2217
- factoryApiVersion: LEGACY_FACTORY_API_VERSION,
2481
+ factoryApiVersion: LEGACY_FACTORY_API_VERSION2,
2218
2482
  factoryProtocolVersion: FACTORY_PROTOCOL_VERSION,
2219
2483
  id: event.id,
2220
2484
  result: {
@@ -2222,9 +2486,9 @@ var DroidClient = class _DroidClient extends EventEmitter {
2222
2486
  }
2223
2487
  };
2224
2488
  try {
2225
- await this.transport.send(JSON.stringify(cancelResponse));
2489
+ await this.sendResponse(cancelResponse, hostMeta);
2226
2490
  } catch (sendError) {
2227
- this.logger.exception(
2491
+ this.reportHandlerFailure(
2228
2492
  sendError,
2229
2493
  "Failed to handle permission request (send cancellation)"
2230
2494
  );
@@ -2239,23 +2503,19 @@ var DroidClient = class _DroidClient extends EventEmitter {
2239
2503
  return;
2240
2504
  }
2241
2505
  }
2242
- const response = {
2506
+ const response2 = {
2243
2507
  type: "response",
2244
2508
  jsonrpc: JSONRPC_VERSION2,
2245
- factoryApiVersion: LEGACY_FACTORY_API_VERSION,
2509
+ factoryApiVersion: LEGACY_FACTORY_API_VERSION2,
2246
2510
  factoryProtocolVersion: FACTORY_PROTOCOL_VERSION,
2247
2511
  id: event.id,
2248
- result: {
2249
- selectedOption,
2250
- ...comment !== void 0 && { comment },
2251
- ...editedSpecContent !== void 0 && { editedSpecContent }
2252
- }
2512
+ result
2253
2513
  };
2254
2514
  this.logger.info("[DroidClient] Sending permission response back to CLI", {
2255
2515
  requestId: event.id,
2256
- result: String(selectedOption)
2516
+ result: String(result.selectedOption)
2257
2517
  });
2258
- await this.transport.send(JSON.stringify(response));
2518
+ await this.sendResponse(response2, hostMeta);
2259
2519
  }
2260
2520
  async handleAskUserRequest(event) {
2261
2521
  this.logger.info("[DroidClient] Handling ask-user request", {
@@ -2264,6 +2524,21 @@ var DroidClient = class _DroidClient extends EventEmitter {
2264
2524
  count: event.params.questions.length
2265
2525
  });
2266
2526
  this.emit("ask_user" /* ASK_USER */, event);
2527
+ if (event.params.blocking === false && !this.askUserHandlers.has("default")) {
2528
+ this.heldQueuedAskUserRequests.push(event);
2529
+ this.logger.info(
2530
+ "[DroidClient] Holding queued ask-user request until a handler is set",
2531
+ { requestId: event.id }
2532
+ );
2533
+ return;
2534
+ }
2535
+ await this.answerAskUserRequest(event);
2536
+ }
2537
+ async answerAskUserRequest(event) {
2538
+ let hostMeta;
2539
+ const setResponseMetadata = (meta) => {
2540
+ hostMeta = meta;
2541
+ };
2267
2542
  let result = { cancelled: true, answers: [] };
2268
2543
  const handler = this.askUserHandlers.get("default");
2269
2544
  if (handler) {
@@ -2276,7 +2551,7 @@ var DroidClient = class _DroidClient extends EventEmitter {
2276
2551
  );
2277
2552
  const handlerPromise = new Promise((resolve2, reject) => {
2278
2553
  this.pendingAskUserRequests.set(event.id, { reject });
2279
- handler(event).then(resolve2).catch(reject).finally(() => {
2554
+ handler(event, setResponseMetadata).then(resolve2).catch(reject).finally(() => {
2280
2555
  this.pendingAskUserRequests.delete(event.id);
2281
2556
  });
2282
2557
  });
@@ -2298,7 +2573,7 @@ var DroidClient = class _DroidClient extends EventEmitter {
2298
2573
  count: result.answers.length
2299
2574
  });
2300
2575
  } catch (error) {
2301
- this.logger.exception(error, "Failed to handle ask-user request");
2576
+ this.reportHandlerFailure(error, "Failed to handle ask-user request");
2302
2577
  const err = error instanceof Error ? error : new Error(String(error));
2303
2578
  this.emit(
2304
2579
  "session_notification" /* SESSION_NOTIFICATION */,
@@ -2307,9 +2582,19 @@ var DroidClient = class _DroidClient extends EventEmitter {
2307
2582
  )
2308
2583
  );
2309
2584
  this.pendingAskUserRequests.delete(event.id);
2310
- const cancelResponse = {
2585
+ const cancelResponse = event.params.blocking === false ? {
2311
2586
  jsonrpc: JSONRPC_VERSION2,
2312
- factoryApiVersion: LEGACY_FACTORY_API_VERSION,
2587
+ factoryApiVersion: LEGACY_FACTORY_API_VERSION2,
2588
+ factoryProtocolVersion: FACTORY_PROTOCOL_VERSION,
2589
+ type: "response",
2590
+ id: event.id,
2591
+ error: {
2592
+ code: -32603 /* INTERNAL_ERROR */,
2593
+ message: "The queued AskUser request could not be answered"
2594
+ }
2595
+ } : {
2596
+ jsonrpc: JSONRPC_VERSION2,
2597
+ factoryApiVersion: LEGACY_FACTORY_API_VERSION2,
2313
2598
  factoryProtocolVersion: FACTORY_PROTOCOL_VERSION,
2314
2599
  type: "response",
2315
2600
  id: event.id,
@@ -2319,9 +2604,9 @@ var DroidClient = class _DroidClient extends EventEmitter {
2319
2604
  }
2320
2605
  };
2321
2606
  try {
2322
- await this.transport.send(JSON.stringify(cancelResponse));
2607
+ await this.sendResponse(cancelResponse, hostMeta);
2323
2608
  } catch (sendError) {
2324
- this.logger.exception(
2609
+ this.reportHandlerFailure(
2325
2610
  sendError,
2326
2611
  "Failed to send ask-user cancellation"
2327
2612
  );
@@ -2329,10 +2614,10 @@ var DroidClient = class _DroidClient extends EventEmitter {
2329
2614
  return;
2330
2615
  }
2331
2616
  }
2332
- const response = {
2617
+ const response2 = {
2333
2618
  type: "response",
2334
2619
  jsonrpc: JSONRPC_VERSION2,
2335
- factoryApiVersion: LEGACY_FACTORY_API_VERSION,
2620
+ factoryApiVersion: LEGACY_FACTORY_API_VERSION2,
2336
2621
  factoryProtocolVersion: FACTORY_PROTOCOL_VERSION,
2337
2622
  id: event.id,
2338
2623
  result
@@ -2341,11 +2626,117 @@ var DroidClient = class _DroidClient extends EventEmitter {
2341
2626
  requestId: event.id,
2342
2627
  cancelled: result.cancelled ?? false
2343
2628
  });
2344
- await this.transport.send(JSON.stringify(response));
2629
+ await this.sendResponse(response2, hostMeta);
2630
+ }
2631
+ /**
2632
+ * Same shape as the ask-user round trip: the handler (a UI) resolves with
2633
+ * the user's selection, and any failure or missing handler answers the CLI
2634
+ * with a cancellation so its tool call never hangs. A handler that rejects
2635
+ * with {@link PickOptionsUnavailableError} is relayed as that JSON-RPC
2636
+ * error instead, which the CLI does not read as the user's cancel.
2637
+ */
2638
+ async handlePickOptionsRequest(event) {
2639
+ this.logger.info("[DroidClient] Handling pick-options request", {
2640
+ requestId: event.id,
2641
+ toolCallId: event.params.toolCallId,
2642
+ count: event.params.rows.length
2643
+ });
2644
+ let hostMeta;
2645
+ const setResponseMetadata = (meta) => {
2646
+ hostMeta = meta;
2647
+ };
2648
+ this.emit("pick_options" /* PICK_OPTIONS */, event);
2649
+ let result = { cancelled: true, selectedIds: [] };
2650
+ const handler = this.pickOptionsHandlers.get("default");
2651
+ if (handler) {
2652
+ try {
2653
+ const handlerPromise = new Promise(
2654
+ (resolve2, reject) => {
2655
+ this.pendingPickOptionsRequests.set(event.id, { reject });
2656
+ handler(event, setResponseMetadata).then(resolve2).catch(reject).finally(() => {
2657
+ this.pendingPickOptionsRequests.delete(event.id);
2658
+ });
2659
+ }
2660
+ );
2661
+ const parsedResult = PickOptionsResultSchema.safeParse(
2662
+ await handlerPromise
2663
+ );
2664
+ if (!parsedResult.success) {
2665
+ throw new DroidClientError(
2666
+ "PickOptions handler returned invalid result",
2667
+ { cause: parsedResult.error }
2668
+ );
2669
+ }
2670
+ result = parsedResult.data;
2671
+ } catch (error) {
2672
+ this.pendingPickOptionsRequests.delete(event.id);
2673
+ if (error instanceof PickOptionsUnavailableError) {
2674
+ this.logger.info("[DroidClient] Pick-options request unavailable", {
2675
+ requestId: event.id,
2676
+ code: error.rpcError.code
2677
+ });
2678
+ const failure = {
2679
+ jsonrpc: JSONRPC_VERSION2,
2680
+ factoryApiVersion: LEGACY_FACTORY_API_VERSION2,
2681
+ factoryProtocolVersion: FACTORY_PROTOCOL_VERSION,
2682
+ type: "response",
2683
+ id: event.id,
2684
+ error: error.rpcError
2685
+ };
2686
+ try {
2687
+ await this.sendResponse(failure, hostMeta);
2688
+ } catch (sendError) {
2689
+ this.reportHandlerFailure(
2690
+ sendError,
2691
+ "Failed to send pick-options failure"
2692
+ );
2693
+ }
2694
+ return;
2695
+ }
2696
+ this.reportHandlerFailure(
2697
+ error,
2698
+ "Failed to handle pick-options request"
2699
+ );
2700
+ const err = error instanceof Error ? error : new Error(String(error));
2701
+ this.emit(
2702
+ "session_notification" /* SESSION_NOTIFICATION */,
2703
+ _DroidClient.createNotificationEvent(
2704
+ _DroidClient.createErrorNotification(err)
2705
+ )
2706
+ );
2707
+ const cancelResponse = {
2708
+ jsonrpc: JSONRPC_VERSION2,
2709
+ factoryApiVersion: LEGACY_FACTORY_API_VERSION2,
2710
+ factoryProtocolVersion: FACTORY_PROTOCOL_VERSION,
2711
+ type: "response",
2712
+ id: event.id,
2713
+ result: { cancelled: true, selectedIds: [] }
2714
+ };
2715
+ try {
2716
+ await this.sendResponse(cancelResponse, hostMeta);
2717
+ } catch (sendError) {
2718
+ this.reportHandlerFailure(
2719
+ sendError,
2720
+ "Failed to send pick-options cancellation"
2721
+ );
2722
+ }
2723
+ return;
2724
+ }
2725
+ }
2726
+ const response2 = {
2727
+ type: "response",
2728
+ jsonrpc: JSONRPC_VERSION2,
2729
+ factoryApiVersion: LEGACY_FACTORY_API_VERSION2,
2730
+ factoryProtocolVersion: FACTORY_PROTOCOL_VERSION,
2731
+ id: event.id,
2732
+ result
2733
+ };
2734
+ await this.sendResponse(response2, hostMeta);
2345
2735
  }
2346
2736
  static getErrorType(error) {
2347
2737
  switch (error.constructor) {
2348
2738
  case ConnectionError:
2739
+ case DroidConnectionEndedError:
2349
2740
  return "ConnectionError" /* CONNECTION_ERROR */;
2350
2741
  case ProtocolError:
2351
2742
  return "ProtocolError" /* PROTOCOL_ERROR */;
@@ -2395,7 +2786,7 @@ var DroidClient = class _DroidClient extends EventEmitter {
2395
2786
  return {
2396
2787
  jsonrpc: JSONRPC_VERSION2,
2397
2788
  type: "notification",
2398
- factoryApiVersion: LEGACY_FACTORY_API_VERSION,
2789
+ factoryApiVersion: LEGACY_FACTORY_API_VERSION2,
2399
2790
  factoryProtocolVersion: FACTORY_PROTOCOL_VERSION,
2400
2791
  method: "droid.session_notification" /* SESSION_NOTIFICATION */,
2401
2792
  params: { notification }
@@ -2403,10 +2794,52 @@ var DroidClient = class _DroidClient extends EventEmitter {
2403
2794
  }
2404
2795
  };
2405
2796
 
2797
+ // src/exec-core/message-transport.ts
2798
+ var JsonRpcCodecTransport = class {
2799
+ transport;
2800
+ logger;
2801
+ constructor(transport, logger) {
2802
+ this.transport = transport;
2803
+ this.logger = logger;
2804
+ }
2805
+ send(message) {
2806
+ return this.transport.send(JSON.stringify(message));
2807
+ }
2808
+ onMessage(handler) {
2809
+ this.transport.onMessage((frame) => {
2810
+ const message = decodeDroidMessage(frame, this.logger);
2811
+ if (message) handler(message);
2812
+ });
2813
+ }
2814
+ onError(handler) {
2815
+ this.transport.onError(handler);
2816
+ }
2817
+ close() {
2818
+ return this.transport.close();
2819
+ }
2820
+ get isConnected() {
2821
+ return this.transport.isConnected;
2822
+ }
2823
+ };
2824
+ function withJsonRpcCodec(transport, deps = defaultExecCoreDeps) {
2825
+ const { logger } = resolveExecCoreDeps(void 0, deps);
2826
+ return new JsonRpcCodecTransport(transport, logger);
2827
+ }
2828
+ function decodeDroidMessage(frame, logger) {
2829
+ try {
2830
+ return JsonRpcMessageSchema.parse(JSON.parse(frame));
2831
+ } catch (error) {
2832
+ logWarn(logger, "[DroidClient] Failed to handle message", {
2833
+ errorName: error instanceof Error ? error.name : typeof error,
2834
+ messageByteLength: new TextEncoder().encode(frame).byteLength
2835
+ });
2836
+ return void 0;
2837
+ }
2838
+ }
2839
+
2406
2840
  // src/exec-core/managed-process.ts
2407
2841
  import { spawnSync } from "child_process";
2408
2842
  import fs2 from "fs";
2409
- import * as readline from "readline";
2410
2843
 
2411
2844
  // src/exec-core/runtime-utils.ts
2412
2845
  import { homedir } from "os";
@@ -2613,6 +3046,13 @@ function describeSpawnError(error, context) {
2613
3046
  }
2614
3047
  return `Failed to start Droid: ${error.code}`;
2615
3048
  }
3049
+ function createSpawnError(error, context) {
3050
+ return new DroidConnectionEndedError(describeSpawnError(error, context), {
3051
+ error,
3052
+ cwd: context?.cwd,
3053
+ execPath: context?.execPath
3054
+ });
3055
+ }
2616
3056
  function describeExitError(code, signal) {
2617
3057
  if (signal) {
2618
3058
  return `Droid process was killed (${signal})`;
@@ -2637,7 +3077,13 @@ var ManagedProcessImpl = class {
2637
3077
  _childProcess;
2638
3078
  messageHandler = null;
2639
3079
  errorHandler = null;
2640
- readlineInterface = null;
3080
+ stdoutDecoder = new LfLineDecoder();
3081
+ stdoutDataHandler = (chunk) => {
3082
+ const text = typeof chunk === "string" ? chunk : chunk.toString("utf8");
3083
+ for (const line of this.stdoutDecoder.push(text)) {
3084
+ this.handleStdoutLine(line);
3085
+ }
3086
+ };
2641
3087
  isClosing = false;
2642
3088
  /** Latch so exit teardown/classification runs exactly once per process. */
2643
3089
  exitFinalized = false;
@@ -2652,50 +3098,50 @@ var ManagedProcessImpl = class {
2652
3098
  logger;
2653
3099
  get childProcess() {
2654
3100
  if (!this._childProcess) {
2655
- throw new ConnectionError("Process not connected");
3101
+ throw new DroidConnectionEndedError("Process not connected");
2656
3102
  }
2657
3103
  return this._childProcess;
2658
3104
  }
3105
+ /** Wait for the runtime's spawn result without replacing the transport's error handler. */
3106
+ waitForSpawn() {
3107
+ const child = this.childProcess;
3108
+ return new Promise((resolve2, reject) => {
3109
+ const onSpawn = () => {
3110
+ child.off("error", onError);
3111
+ resolve2();
3112
+ };
3113
+ const onError = () => {
3114
+ child.off("spawn", onSpawn);
3115
+ reject(this.processError);
3116
+ };
3117
+ child.once("spawn", onSpawn);
3118
+ child.once("error", onError);
3119
+ });
3120
+ }
2659
3121
  setupHandlers() {
2660
3122
  if (!this.childProcess.stdout) {
2661
- throw new ConnectionError("Failed to get process stdout");
3123
+ throw new DroidConnectionEndedError("Failed to get process stdout");
2662
3124
  }
2663
- this.readlineInterface = readline.createInterface({
2664
- input: this.childProcess.stdout,
2665
- crlfDelay: Infinity
2666
- });
2667
- this.readlineInterface.on("line", (line) => {
2668
- const trimmedLine = line.trim();
2669
- if (this.messageHandler && trimmedLine) {
2670
- if (trimmedLine.startsWith("{") || trimmedLine.startsWith("[")) {
2671
- logTransportMessage(
2672
- "in" /* In */,
2673
- trimmedLine,
2674
- this._childProcess?.pid,
2675
- this.transportLoggerDeps
2676
- );
2677
- this.messageHandler(trimmedLine);
2678
- } else {
2679
- const truncated = trimmedLine.length > MAX_NON_JSON_LOG_LENGTH ? trimmedLine.substring(0, MAX_NON_JSON_LOG_LENGTH) : trimmedLine;
2680
- this.logger.warn("[droid process] non-JSON output", {
2681
- state: this.logPrefix,
2682
- output: truncated
2683
- });
2684
- }
3125
+ this.childProcess.stdout.setEncoding("utf8");
3126
+ this.childProcess.stdout.on("data", this.stdoutDataHandler);
3127
+ this.childProcess.on("error", (error) => {
3128
+ if (this.processError || this.isClosing || this.exitFinalized) {
3129
+ return;
2685
3130
  }
3131
+ this.processError = createSpawnError(error, this.spawnContext);
3132
+ this.errorHandler?.(this.processError);
2686
3133
  });
2687
- this.childProcess.on("error", (error) => {
2688
- this.processError = new ConnectionError(
2689
- describeSpawnError(error, this.spawnContext),
3134
+ this.childProcess.stdin?.on("error", (error) => {
3135
+ if (this.processError || this.isClosing || this.exitFinalized) {
3136
+ return;
3137
+ }
3138
+ this.processError = new DroidConnectionEndedError(
3139
+ "Process stdin failed",
2690
3140
  {
2691
- error,
2692
- cwd: this.spawnContext?.cwd,
2693
- execPath: this.spawnContext?.execPath
3141
+ error
2694
3142
  }
2695
3143
  );
2696
- if (!this.isClosing && this.errorHandler) {
2697
- this.errorHandler(this.processError);
2698
- }
3144
+ this.errorHandler?.(this.processError);
2699
3145
  });
2700
3146
  this.childProcess.on("exit", (code, signal) => {
2701
3147
  this.exitDrainTimer = setTimeout(() => {
@@ -2704,6 +3150,9 @@ var ManagedProcessImpl = class {
2704
3150
  this.exitDrainTimer.unref?.();
2705
3151
  });
2706
3152
  this.childProcess.on("close", (code, signal) => {
3153
+ for (const line of this.stdoutDecoder.finish()) {
3154
+ this.handleStdoutLine(line);
3155
+ }
2707
3156
  this.finalizeExit(code, signal);
2708
3157
  });
2709
3158
  if (this.childProcess.stderr) {
@@ -2715,6 +3164,27 @@ var ManagedProcessImpl = class {
2715
3164
  });
2716
3165
  }
2717
3166
  }
3167
+ handleStdoutLine(line) {
3168
+ const trimmedLine = line.trim();
3169
+ if (!this.messageHandler || !trimmedLine) {
3170
+ return;
3171
+ }
3172
+ if (trimmedLine.startsWith("{") || trimmedLine.startsWith("[")) {
3173
+ logTransportMessage(
3174
+ "in" /* In */,
3175
+ trimmedLine,
3176
+ this._childProcess?.pid,
3177
+ this.transportLoggerDeps
3178
+ );
3179
+ this.messageHandler(trimmedLine);
3180
+ return;
3181
+ }
3182
+ const truncated = trimmedLine.length > MAX_NON_JSON_LOG_LENGTH ? trimmedLine.substring(0, MAX_NON_JSON_LOG_LENGTH) : trimmedLine;
3183
+ this.logger.warn("[droid process] non-JSON output", {
3184
+ state: this.logPrefix,
3185
+ output: truncated
3186
+ });
3187
+ }
2718
3188
  finalizeExit(code, signal) {
2719
3189
  if (this.exitFinalized) {
2720
3190
  return;
@@ -2724,12 +3194,10 @@ var ManagedProcessImpl = class {
2724
3194
  clearTimeout(this.exitDrainTimer);
2725
3195
  this.exitDrainTimer = null;
2726
3196
  }
3197
+ this._childProcess?.stdout?.off("data", this.stdoutDataHandler);
2727
3198
  this._childProcess = null;
2728
- if (this.readlineInterface) {
2729
- this.readlineInterface.close();
2730
- this.readlineInterface = null;
2731
- }
2732
- if (!this.isClosing) {
3199
+ this.stdoutDecoder.reset();
3200
+ if (!this.isClosing && !this.processError) {
2733
3201
  this.processError = new ProcessExitError(
2734
3202
  describeExitError(code, signal),
2735
3203
  {
@@ -2747,28 +3215,32 @@ var ManagedProcessImpl = class {
2747
3215
  throw this.processError;
2748
3216
  }
2749
3217
  if (!this._childProcess || !this._childProcess.stdin) {
2750
- throw new ConnectionError("Process not connected");
3218
+ throw new DroidConnectionEndedError("Process not connected");
2751
3219
  }
2752
3220
  const previousSends = this.queuedSendPromise;
2753
3221
  this.queuedSendPromise = (async () => {
2754
3222
  await previousSends.catch(() => {
2755
3223
  });
2756
3224
  if (!this._childProcess || !this._childProcess.stdin || this._childProcess.killed || this.isClosing) {
2757
- throw new ConnectionError("Process disconnected before write");
3225
+ throw new DroidConnectionEndedError(
3226
+ "Process disconnected before write"
3227
+ );
2758
3228
  }
2759
3229
  await new Promise((resolve2, reject) => {
2760
3230
  try {
2761
3231
  if (!this._childProcess || !this._childProcess.stdin) {
2762
- reject(new ConnectionError("Process stdin unavailable"));
3232
+ reject(new DroidConnectionEndedError("Process stdin unavailable"));
2763
3233
  return;
2764
3234
  }
2765
3235
  const stdin = this._childProcess.stdin;
2766
3236
  if ("writable" in stdin && !stdin.writable) {
2767
- reject(new ConnectionError("Process stdin is not writable"));
3237
+ reject(
3238
+ new DroidConnectionEndedError("Process stdin is not writable")
3239
+ );
2768
3240
  return;
2769
3241
  }
2770
3242
  const childPid = this._childProcess?.pid;
2771
- stdin.write(`${message}
3243
+ stdin.write(`${escapeJsonLineSeparators(message)}
2772
3244
  `, (error) => {
2773
3245
  if (error) {
2774
3246
  logTransportMessage(
@@ -2780,15 +3252,21 @@ var ManagedProcessImpl = class {
2780
3252
  const errorMessage = error.message || String(error);
2781
3253
  if (errorMessage.includes("EPIPE") || errorMessage.includes("ECONNRESET")) {
2782
3254
  reject(
2783
- new ConnectionError("Process stdin closed during write", {
2784
- error
2785
- })
3255
+ new DroidConnectionEndedError(
3256
+ "Process stdin closed during write",
3257
+ {
3258
+ error
3259
+ }
3260
+ )
2786
3261
  );
2787
3262
  } else {
2788
3263
  reject(
2789
- new ConnectionError("Failed to write to process stdin", {
2790
- error
2791
- })
3264
+ new DroidConnectionEndedError(
3265
+ "Failed to write to process stdin",
3266
+ {
3267
+ error
3268
+ }
3269
+ )
2792
3270
  );
2793
3271
  }
2794
3272
  } else {
@@ -2810,7 +3288,7 @@ var ManagedProcessImpl = class {
2810
3288
  }
2811
3289
  );
2812
3290
  reject(
2813
- new ConnectionError("Error writing to process stdin", {
3291
+ new DroidConnectionEndedError("Error writing to process stdin", {
2814
3292
  error: error instanceof Error ? error : new Error(String(error))
2815
3293
  })
2816
3294
  );
@@ -2858,11 +3336,9 @@ var ManagedProcessImpl = class {
2858
3336
  }
2859
3337
  });
2860
3338
  }
3339
+ processToClose.stdout?.off("data", this.stdoutDataHandler);
2861
3340
  }
2862
- if (this.readlineInterface) {
2863
- this.readlineInterface.close();
2864
- this.readlineInterface = null;
2865
- }
3341
+ this.stdoutDecoder.reset();
2866
3342
  this.exitFinalized = true;
2867
3343
  if (this.exitDrainTimer) {
2868
3344
  clearTimeout(this.exitDrainTimer);
@@ -2969,10 +3445,8 @@ async function spawnWithWindowsJobObjectRetry(spawnFactory, options = {}) {
2969
3445
  const sleep = options.sleep ?? defaultSleep;
2970
3446
  const logPrefix = options.logPrefix ?? "[spawnWithWindowsJobObjectRetry]";
2971
3447
  const logger = options.logger ?? noopLogger;
2972
- let lastChild = null;
2973
- for (let attempt = 0; attempt <= maxRetries; attempt++) {
3448
+ for (let attempt = 0; ; attempt++) {
2974
3449
  const child = spawnFactory();
2975
- lastChild = child;
2976
3450
  const observation = await observeEarlySpawnFailure(
2977
3451
  child,
2978
3452
  gracePeriodMs,
@@ -2982,8 +3456,28 @@ async function spawnWithWindowsJobObjectRetry(spawnFactory, options = {}) {
2982
3456
  return child;
2983
3457
  }
2984
3458
  const isRetryableJobObjectFailure = observation.stderr.includes(WINDOWS_JOB_OBJECT_ERROR_MARKER) || observation.exited && observation.code !== 0 && !observation.error;
2985
- if (attempt === maxRetries || !isRetryableJobObjectFailure) {
2986
- return child;
3459
+ try {
3460
+ await options.disposeEarlyExited?.(child);
3461
+ } catch (disposeErr) {
3462
+ logWarn(logger, "spawnWithWindowsJobObjectRetry: dispose error", {
3463
+ cause: disposeErr instanceof Error ? disposeErr : new Error(String(disposeErr)),
3464
+ name: logPrefix
3465
+ });
3466
+ }
3467
+ if (attempt >= maxRetries || !isRetryableJobObjectFailure) {
3468
+ if (observation.error) {
3469
+ throw createSpawnError(observation.error, options);
3470
+ }
3471
+ throw new DroidConnectionEndedError(
3472
+ "Droid process exited during startup",
3473
+ {
3474
+ cwd: options.cwd,
3475
+ execPath: options.execPath,
3476
+ exitCode: observation.code,
3477
+ signal: observation.signal,
3478
+ stderrTail: observation.stderr.slice(-256)
3479
+ }
3480
+ );
2987
3481
  }
2988
3482
  logger.warn("spawnWithWindowsJobObjectRetry: retrying on early failure", {
2989
3483
  name: logPrefix,
@@ -2993,24 +3487,19 @@ async function spawnWithWindowsJobObjectRetry(spawnFactory, options = {}) {
2993
3487
  signal: observation.signal,
2994
3488
  stderrTail: observation.stderr.slice(-256)
2995
3489
  });
2996
- try {
2997
- await options.disposeEarlyExited?.(child);
2998
- } catch (disposeErr) {
2999
- logWarn(logger, "spawnWithWindowsJobObjectRetry: dispose error", {
3000
- cause: disposeErr instanceof Error ? disposeErr : new Error(String(disposeErr)),
3001
- name: logPrefix
3002
- });
3003
- }
3004
3490
  await sleep(backoff[attempt] ?? backoff[backoff.length - 1] ?? 400);
3005
3491
  }
3006
- if (lastChild) return lastChild;
3007
- throw new DroidClientError(
3008
- "spawnWithWindowsJobObjectRetry: exhausted retries without child",
3009
- {}
3010
- );
3011
3492
  }
3012
3493
 
3013
3494
  // src/exec-core/process-manager.ts
3495
+ var NATIVE_PROCESS_LIFECYCLE_MESSAGE = "[droid process] native lifecycle";
3496
+ var MAX_SPAWN_ERROR_CODE_LENGTH = 64;
3497
+ function getSpawnErrorCode(error) {
3498
+ if (typeof error !== "object" || error === null || !("code" in error) || typeof error.code !== "string" && typeof error.code !== "number") {
3499
+ return void 0;
3500
+ }
3501
+ return String(error.code).slice(0, MAX_SPAWN_ERROR_CODE_LENGTH);
3502
+ }
3014
3503
  function buildArgs(config) {
3015
3504
  const args = [];
3016
3505
  if (config.mode === "acp" /* Acp */) {
@@ -3029,6 +3518,13 @@ function buildArgs(config) {
3029
3518
  }
3030
3519
  return args;
3031
3520
  }
3521
+ function ensureIpcStdio(stdio) {
3522
+ if (Array.isArray(stdio)) {
3523
+ return stdio.includes("ipc") ? stdio : [...stdio, "ipc"];
3524
+ }
3525
+ const standardChannel = stdio ?? "pipe";
3526
+ return [standardChannel, standardChannel, standardChannel, "ipc"];
3527
+ }
3032
3528
  var DroidProcessManager = class {
3033
3529
  deps;
3034
3530
  transportLoggerDeps;
@@ -3080,6 +3576,8 @@ var DroidProcessManager = class {
3080
3576
  () => this.invokeNodeSpawn(planned),
3081
3577
  {
3082
3578
  ...retryOptions,
3579
+ cwd: planned.expandedCwd,
3580
+ execPath: planned.execPath,
3083
3581
  logPrefix: "[DroidProcessManager]",
3084
3582
  logger: this.logger,
3085
3583
  disposeEarlyExited: (child) => {
@@ -3109,7 +3607,8 @@ var DroidProcessManager = class {
3109
3607
  }
3110
3608
  );
3111
3609
  if (!childProcess.stdout || !childProcess.stdin) {
3112
- throw new ConnectionError("Failed to get process stdio");
3610
+ childProcess.kill();
3611
+ throw new DroidConnectionEndedError("Failed to get process stdio");
3113
3612
  }
3114
3613
  return new ManagedProcessImpl(
3115
3614
  childProcess,
@@ -3155,21 +3654,88 @@ var DroidProcessManager = class {
3155
3654
  };
3156
3655
  }
3157
3656
  invokeNodeSpawn(planned) {
3657
+ const stdio = planned.enableIpc ? ensureIpcStdio(planned.stdio) : planned.stdio ?? ["pipe", "pipe", "pipe"];
3158
3658
  const options = {
3159
3659
  cwd: planned.expandedCwd,
3160
3660
  env: planned.env,
3161
- stdio: planned.stdio ?? (planned.enableIpc ? ["pipe", "pipe", "pipe", "ipc"] : ["pipe", "pipe", "pipe"])
3661
+ stdio
3162
3662
  };
3163
3663
  if (planned.enableIpc) {
3164
3664
  options.serialization = "json";
3165
3665
  }
3166
- return spawn(planned.execPath, planned.args, options);
3666
+ const spawnedAt = Date.now();
3667
+ let childProcess;
3668
+ try {
3669
+ childProcess = spawn(planned.execPath, planned.args, options);
3670
+ } catch (error) {
3671
+ const failedAt = Date.now();
3672
+ this.logNativeProcessLifecycle({
3673
+ state: "spawn_error",
3674
+ pid: null,
3675
+ createdAt: spawnedAt,
3676
+ eventDate: new Date(failedAt).toISOString(),
3677
+ durationMs: failedAt - spawnedAt,
3678
+ errnoCode: getSpawnErrorCode(error)
3679
+ });
3680
+ throw error;
3681
+ }
3682
+ const pid = childProcess.pid;
3683
+ if (pid === void 0) {
3684
+ childProcess.once("error", (error) => {
3685
+ const failedAt = Date.now();
3686
+ this.logNativeProcessLifecycle({
3687
+ state: "spawn_error",
3688
+ pid: null,
3689
+ createdAt: spawnedAt,
3690
+ eventDate: new Date(failedAt).toISOString(),
3691
+ durationMs: failedAt - spawnedAt,
3692
+ errnoCode: getSpawnErrorCode(error)
3693
+ });
3694
+ });
3695
+ } else {
3696
+ childProcess.once("exit", (exitCode, signal) => {
3697
+ const exitedAt = Date.now();
3698
+ this.logNativeProcessLifecycle({
3699
+ state: "exited",
3700
+ pid,
3701
+ createdAt: spawnedAt,
3702
+ eventDate: new Date(exitedAt).toISOString(),
3703
+ durationMs: exitedAt - spawnedAt,
3704
+ exitCode,
3705
+ signal
3706
+ });
3707
+ });
3708
+ this.logNativeProcessLifecycle({
3709
+ state: "spawned",
3710
+ pid,
3711
+ createdAt: spawnedAt,
3712
+ eventDate: new Date(spawnedAt).toISOString()
3713
+ });
3714
+ }
3715
+ return childProcess;
3716
+ }
3717
+ /**
3718
+ * Emits the content-free native process record consumed by lifecycle
3719
+ * measurement harnesses. `createdAt` is the wall-clock lower bound captured
3720
+ * immediately before spawn; `eventDate` is the wall-clock observation time.
3721
+ */
3722
+ logNativeProcessLifecycle(record) {
3723
+ try {
3724
+ this.logger.info(NATIVE_PROCESS_LIFECYCLE_MESSAGE, record);
3725
+ } catch (loggingError) {
3726
+ void loggingError;
3727
+ logWarn(
3728
+ defaultExecCoreDeps.logger,
3729
+ "[droid process] native lifecycle logger failed"
3730
+ );
3731
+ }
3167
3732
  }
3168
3733
  spawnChildProcess(config) {
3169
3734
  const planned = this.prepareSpawn(config);
3170
3735
  const childProcess = this.invokeNodeSpawn(planned);
3171
3736
  if (!childProcess.stdout || !childProcess.stdin) {
3172
- throw new ConnectionError("Failed to get process stdio");
3737
+ childProcess.kill();
3738
+ throw new DroidConnectionEndedError("Failed to get process stdio");
3173
3739
  }
3174
3740
  return {
3175
3741
  logPrefix: planned.logPrefix,
@@ -3187,12 +3753,20 @@ var ProcessTransport = class {
3187
3753
  options;
3188
3754
  // Store handlers that may be registered before connect() is called
3189
3755
  messageHandler = null;
3756
+ logger;
3757
+ handleMessage = (frame) => {
3758
+ const message = decodeDroidMessage(frame, this.logger);
3759
+ if (message) this.messageHandler?.(message);
3760
+ };
3190
3761
  errorHandler = null;
3191
3762
  metrics;
3763
+ environmentResolver;
3192
3764
  constructor(options = {}, deps = defaultExecCoreDeps) {
3193
3765
  this.options = options;
3194
3766
  const resolved = resolveExecCoreDeps(options.observability, deps);
3767
+ this.logger = resolved.logger;
3195
3768
  this.metrics = resolved.metrics;
3769
+ this.environmentResolver = resolved.environmentResolver;
3196
3770
  this.processManager = new DroidProcessManager(
3197
3771
  {
3198
3772
  observability: options.observability
@@ -3202,7 +3776,7 @@ var ProcessTransport = class {
3202
3776
  }
3203
3777
  async connect() {
3204
3778
  if (this.managedProcess && this.managedProcess.isConnected) {
3205
- throw new ConnectionError("Transport already connected");
3779
+ throw new DroidClientError("Transport already connected");
3206
3780
  }
3207
3781
  const config = {
3208
3782
  execPath: this.options.droidExecPath,
@@ -3210,14 +3784,36 @@ var ProcessTransport = class {
3210
3784
  mode: "stream-jsonrpc" /* StreamJsonRpc */,
3211
3785
  cwd: this.options.cwd,
3212
3786
  env: this.options.env,
3787
+ trustedEnv: {
3788
+ ...this.options.runtimeSettingsAuthority ? this.environmentResolver.buildRuntimeSettingsAuthorityEnv(
3789
+ this.options.runtimeSettingsAuthority
3790
+ ) : {},
3791
+ ...this.options.trustedEnv
3792
+ },
3213
3793
  extraArgs: this.options.droidExecExtraArgs,
3214
- enableIpc: this.options.enableIpc
3794
+ enableIpc: this.options.enableIpc,
3795
+ stdio: this.options.stdio
3215
3796
  };
3216
3797
  const spawnStart = performance.now();
3217
3798
  let spawnOutcome = "error";
3799
+ let managedProcess;
3218
3800
  try {
3219
- this.managedProcess = process.platform === "win32" ? await this.processManager.spawnWithWindowsRetry(config) : this.processManager.spawn(config);
3801
+ if (process.platform === "win32") {
3802
+ managedProcess = await this.processManager.spawnWithWindowsRetry(config);
3803
+ } else {
3804
+ const spawned = this.processManager.spawn(config);
3805
+ managedProcess = spawned;
3806
+ if (spawned.childProcess.pid === void 0) {
3807
+ await spawned.waitForSpawn();
3808
+ }
3809
+ }
3220
3810
  spawnOutcome = "success";
3811
+ } catch (error) {
3812
+ await managedProcess?.close();
3813
+ if (error instanceof DroidConnectionEndedError) {
3814
+ this.errorHandler?.(error);
3815
+ }
3816
+ throw error;
3221
3817
  } finally {
3222
3818
  this.metrics.recordDuration(
3223
3819
  CLI_JSONRPC_CHILD_SPAWN_LATENCY_METRIC,
@@ -3225,8 +3821,15 @@ var ProcessTransport = class {
3225
3821
  { outcome: spawnOutcome, source: "process_transport" }
3226
3822
  );
3227
3823
  }
3824
+ try {
3825
+ this.options.onSpawned?.(managedProcess.childProcess);
3826
+ } catch (error) {
3827
+ await managedProcess.close();
3828
+ throw error;
3829
+ }
3830
+ this.managedProcess = managedProcess;
3228
3831
  if (this.messageHandler) {
3229
- this.managedProcess.onMessage(this.messageHandler);
3832
+ this.managedProcess.onMessage(this.handleMessage);
3230
3833
  }
3231
3834
  if (this.errorHandler) {
3232
3835
  this.managedProcess.onError(this.errorHandler);
@@ -3234,14 +3837,14 @@ var ProcessTransport = class {
3234
3837
  }
3235
3838
  async send(message) {
3236
3839
  if (!this.managedProcess) {
3237
- throw new ConnectionError("Transport not connected");
3840
+ throw new DroidConnectionEndedError("Transport not connected");
3238
3841
  }
3239
- return this.managedProcess.send(message);
3842
+ return await this.managedProcess.send(JSON.stringify(message));
3240
3843
  }
3241
3844
  onMessage(handler) {
3242
3845
  this.messageHandler = handler;
3243
3846
  if (this.managedProcess) {
3244
- this.managedProcess.onMessage(handler);
3847
+ this.managedProcess.onMessage(this.handleMessage);
3245
3848
  }
3246
3849
  }
3247
3850
  onError(handler) {
@@ -3264,6 +3867,180 @@ var ProcessTransport = class {
3264
3867
  }
3265
3868
  };
3266
3869
 
3870
+ // src/environment-session.ts
3871
+ import { v4 as uuidv43 } from "uuid";
3872
+
3873
+ // src/environment-launch/index.ts
3874
+ import { v4 as uuidv42 } from "uuid";
3875
+
3876
+ // src/environment-launch/errors.ts
3877
+ var EnvironmentLaunchError = class extends Error {
3878
+ computerId;
3879
+ computerName;
3880
+ cleanupError;
3881
+ constructor(options) {
3882
+ super(
3883
+ options.cause instanceof Error ? options.cause.message : "Environment session startup failed",
3884
+ { cause: options.cause }
3885
+ );
3886
+ this.name = "EnvironmentLaunchError";
3887
+ this.computerId = options.computerId;
3888
+ this.computerName = options.computerName;
3889
+ this.cleanupError = options.cleanupError;
3890
+ }
3891
+ };
3892
+
3893
+ // src/environment-launch/index.ts
3894
+ async function launchEnvironmentSession(options) {
3895
+ const timeoutMs = options.timeoutMs ?? 18e4;
3896
+ const pollIntervalMs = options.pollIntervalMs ?? 1e3;
3897
+ const controller = new AbortController();
3898
+ const cancel = () => controller.abort(options.signal?.reason);
3899
+ if (options.signal?.aborted) cancel();
3900
+ else options.signal?.addEventListener("abort", cancel, { once: true });
3901
+ const timer = setTimeout(
3902
+ () => controller.abort(new EnvironmentComputerTimeoutError()),
3903
+ timeoutMs
3904
+ );
3905
+ const signal = controller.signal;
3906
+ let createdComputerId;
3907
+ let computerName;
3908
+ function launchError(cause, cleanupError) {
3909
+ return new EnvironmentLaunchError({
3910
+ cause,
3911
+ computerId: createdComputerId,
3912
+ computerName,
3913
+ cleanupError
3914
+ });
3915
+ }
3916
+ try {
3917
+ if (!Number.isFinite(timeoutMs) || timeoutMs <= 0 || !Number.isFinite(pollIntervalMs) || pollIntervalMs <= 0)
3918
+ throw new Error("Invalid environment launch configuration");
3919
+ const selection = await runWithAbort(
3920
+ signal,
3921
+ async () => await resolveEnvironment({ ...options, signal })
3922
+ );
3923
+ computerName = `environment-${uuidv42()}`;
3924
+ signal.throwIfAborted();
3925
+ const createPromise = createOrRecoverComputer({
3926
+ name: computerName,
3927
+ environmentId: selection.environment.id,
3928
+ keepWhenIdle: options.keepWhenIdle,
3929
+ request: options.request,
3930
+ serviceAccountId: options.serviceAccountId,
3931
+ signal: AbortSignal.timeout(timeoutMs)
3932
+ });
3933
+ const allocated = await runWithAbort(signal, async () => await createPromise).catch(
3934
+ (cause) => {
3935
+ if (signal.aborted && createdComputerId === void 0)
3936
+ void createPromise.then(
3937
+ (computer2) => createFactoryApi(options.request).deleteComputer({
3938
+ computerId: computer2.id,
3939
+ signal: AbortSignal.timeout(Math.min(timeoutMs, 5e3))
3940
+ })
3941
+ ).catch(() => {
3942
+ });
3943
+ throw cause;
3944
+ }
3945
+ );
3946
+ createdComputerId = allocated.id;
3947
+ await runWithAbort(signal, async () => await options.onComputer?.(allocated));
3948
+ const computer = await waitForEnvironmentComputer({
3949
+ computer: allocated,
3950
+ timeoutMs,
3951
+ pollIntervalMs,
3952
+ now: options.now,
3953
+ sleep: options.sleep,
3954
+ signal,
3955
+ isReady: isEnvironmentComputerReady,
3956
+ onComputer: options.onComputer,
3957
+ request: options.request,
3958
+ serviceAccountId: options.serviceAccountId
3959
+ });
3960
+ const connection = await runWithAbort(
3961
+ signal,
3962
+ async () => await options.connect({ computer, signal })
3963
+ );
3964
+ const session = await runWithAbort(
3965
+ signal,
3966
+ async () => await options.startSession({ connection, computer, selection, signal })
3967
+ );
3968
+ return { session, computer, selection };
3969
+ } catch (cause) {
3970
+ if (createdComputerId !== void 0) {
3971
+ const cleanup = new AbortController();
3972
+ const cleanupTimer = setTimeout(
3973
+ () => cleanup.abort(new EnvironmentComputerTimeoutError()),
3974
+ Math.min(timeoutMs, 5e3)
3975
+ );
3976
+ try {
3977
+ const computerId = createdComputerId;
3978
+ await runWithAbort(
3979
+ cleanup.signal,
3980
+ async () => await createFactoryApi(options.request).deleteComputer({
3981
+ computerId,
3982
+ signal: cleanup.signal
3983
+ })
3984
+ );
3985
+ } catch (failure) {
3986
+ throw launchError(cause, failure);
3987
+ } finally {
3988
+ clearTimeout(cleanupTimer);
3989
+ }
3990
+ }
3991
+ throw launchError(cause);
3992
+ } finally {
3993
+ clearTimeout(timer);
3994
+ options.signal?.removeEventListener("abort", cancel);
3995
+ }
3996
+ }
3997
+ async function resolveEnvironment(options) {
3998
+ const http2 = createFactoryApi(options.request);
3999
+ const selector = options.environment;
4000
+ if ("id" in selector ? !selector.id.trim() : !selector.name.trim())
4001
+ throw new Error("Invalid environment selector");
4002
+ const context = {
4003
+ serviceAccountId: options.serviceAccountId,
4004
+ signal: options.signal
4005
+ };
4006
+ let environment;
4007
+ if ("id" in selector && context.serviceAccountId === void 0) {
4008
+ environment = SandboxEnvironmentSchema.parse(
4009
+ await http2.getEnvironment({
4010
+ ...context,
4011
+ environmentId: selector.id
4012
+ })
4013
+ );
4014
+ if (environment.id !== selector.id)
4015
+ throw new Error("Environment lookup returned a different ID");
4016
+ } else {
4017
+ const matches = [];
4018
+ options.signal?.throwIfAborted();
4019
+ const page = await http2.listEnvironments(context);
4020
+ if (!page || typeof page !== "object" || !("environments" in page) || !Array.isArray(page.environments))
4021
+ throw new Error("Invalid environment list response");
4022
+ for (const item of page.environments) {
4023
+ const candidate = SandboxEnvironmentSchema.parse(item);
4024
+ if ("id" in selector ? candidate.id === selector.id : candidate.name === selector.name)
4025
+ matches.push(candidate);
4026
+ }
4027
+ if (matches.length !== 1)
4028
+ throw new Error(
4029
+ "id" in selector ? "Environment is not accessible" : "Environment name must match exactly one accessible environment"
4030
+ );
4031
+ environment = matches[0];
4032
+ }
4033
+ if (!environment) throw new Error("Environment is not accessible");
4034
+ if (!isSandboxEnvironmentLaunchable(environment)) {
4035
+ throw new Error("Environment is not ready to launch");
4036
+ }
4037
+ return {
4038
+ environment,
4039
+ source: getSandboxEnvironmentLaunchSource(environment),
4040
+ repositories: getSandboxEnvironmentLaunchRepositories(environment)
4041
+ };
4042
+ }
4043
+
3267
4044
  // src/helpers.ts
3268
4045
  function resolveApiKey(apiKey) {
3269
4046
  const resolved = apiKey ?? process.env.FACTORY_API_KEY;
@@ -3286,7 +4063,10 @@ async function createTransport(options) {
3286
4063
  ...options.env,
3287
4064
  FACTORY_API_KEY: resolveApiKey(options.apiKey),
3288
4065
  FACTORY_UPSTREAM_CLIENT_TYPE: "sdk" /* SDK */,
3289
- FACTORY_UPSTREAM_SDK: SDK_IDENTITY
4066
+ FACTORY_UPSTREAM_SDK: SDK_IDENTITY,
4067
+ // This helper owns the original SDK process, unlike the generic
4068
+ // ProcessTransport also used by daemon workers.
4069
+ FACTORY_EXEC_ORIGINAL_INVOCATION: "1"
3290
4070
  },
3291
4071
  observability: options.observability
3292
4072
  });
@@ -3294,13 +4074,26 @@ async function createTransport(options) {
3294
4074
  return processTransport;
3295
4075
  }
3296
4076
  function setupClientHandlers(client, options) {
4077
+ const sessionId = () => {
4078
+ const addressed = client.addressedSessionId;
4079
+ if (!addressed) {
4080
+ throw new DroidClientError(
4081
+ "Received a prompt that no single session owns"
4082
+ );
4083
+ }
4084
+ return addressed;
4085
+ };
3297
4086
  if (options.permissionHandler) {
3298
4087
  const handler = options.permissionHandler;
3299
- client.setPermissionHandler((event) => handler(event.params));
4088
+ client.setPermissionHandler(
4089
+ (event) => handler({ ...event.params, sessionId: sessionId() })
4090
+ );
3300
4091
  }
3301
4092
  if (options.askUserHandler) {
3302
4093
  const handler = options.askUserHandler;
3303
- client.setAskUserHandler((event) => handler(event.params));
4094
+ client.setAskUserHandler(
4095
+ (event) => handler({ ...event.params, sessionId: sessionId() })
4096
+ );
3304
4097
  }
3305
4098
  }
3306
4099
  async function createConfiguredClient(options) {
@@ -3372,15 +4165,21 @@ import http from "http";
3372
4165
  import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
3373
4166
  import { StreamableHTTPServerTransport } from "@modelcontextprotocol/sdk/server/streamableHttp.js";
3374
4167
  import { z as z2 } from "zod";
3375
- function tool(name, description, inputSchemaOrHandler, handler) {
4168
+ function tool(name, description, inputSchemaOrHandler, handlerOrOptions, maybeOptions) {
4169
+ const options = typeof handlerOrOptions === "function" ? maybeOptions : handlerOrOptions;
4170
+ const annotationFields = options?.annotations && {
4171
+ annotations: options.annotations
4172
+ };
3376
4173
  if (typeof inputSchemaOrHandler === "function") {
3377
4174
  const actualHandler = inputSchemaOrHandler;
3378
4175
  return {
3379
4176
  name,
3380
4177
  description,
3381
- handler: (input) => actualHandler(input)
4178
+ handler: (input) => actualHandler(input),
4179
+ ...annotationFields
3382
4180
  };
3383
4181
  }
4182
+ const handler = typeof handlerOrOptions === "function" ? handlerOrOptions : void 0;
3384
4183
  if (!handler) {
3385
4184
  throw new Error(`Missing handler for SDK MCP tool "${name}"`);
3386
4185
  }
@@ -3389,7 +4188,8 @@ function tool(name, description, inputSchemaOrHandler, handler) {
3389
4188
  name,
3390
4189
  description,
3391
4190
  inputSchema: inputSchemaOrHandler,
3392
- handler: (input) => handler(inputObjectSchema.parse(input))
4191
+ handler: (input) => handler(inputObjectSchema.parse(input)),
4192
+ ...annotationFields
3393
4193
  };
3394
4194
  }
3395
4195
  function isSdkMcpServer(server) {
@@ -3515,7 +4315,8 @@ var SdkMcpServer = class {
3515
4315
  description: sdkTool.description,
3516
4316
  ...sdkTool.inputSchema && {
3517
4317
  inputSchema: sdkTool.inputSchema
3518
- }
4318
+ },
4319
+ ...sdkTool.annotations && { annotations: sdkTool.annotations }
3519
4320
  },
3520
4321
  async (input) => toCallToolResult(await sdkTool.handler(input))
3521
4322
  );
@@ -3592,6 +4393,313 @@ async function startSdkMcpServers(servers) {
3592
4393
  };
3593
4394
  }
3594
4395
 
4396
+ // src/session-operations.ts
4397
+ function response(result) {
4398
+ return {
4399
+ type: "response",
4400
+ jsonrpc: JSONRPC_VERSION2,
4401
+ factoryApiVersion: LEGACY_FACTORY_API_VERSION2,
4402
+ factoryProtocolVersion: FACTORY_PROTOCOL_VERSION,
4403
+ id: "sdk-adapter",
4404
+ result
4405
+ };
4406
+ }
4407
+ function createDaemonSessionOperations(controller, initialSessionId, handlers) {
4408
+ let sessionId = initialSessionId;
4409
+ const ownedSessionIds = /* @__PURE__ */ new Set([initialSessionId]);
4410
+ const reportError = (error) => controller.emit("error", toError(error));
4411
+ const permission = async (request) => {
4412
+ if (request.sessionId !== sessionId && !request.associatedSessionIds.includes(sessionId))
4413
+ return;
4414
+ const selected = await resolvePermissionHandler(
4415
+ handlers.permissionHandler,
4416
+ request,
4417
+ reportError
4418
+ ) ?? {
4419
+ selectedOption: "cancel" /* Cancel */
4420
+ };
4421
+ try {
4422
+ await controller.respondToPermission({
4423
+ permissionId: request.requestId,
4424
+ sessionId,
4425
+ ...selected
4426
+ });
4427
+ } catch (error) {
4428
+ reportError(error);
4429
+ }
4430
+ };
4431
+ const askUser = async (request) => {
4432
+ if (request.sessionId !== sessionId) return;
4433
+ const result = await resolveAskUserHandler(
4434
+ handlers.askUserHandler,
4435
+ request,
4436
+ reportError
4437
+ );
4438
+ if (!result && request.blocking === false) return;
4439
+ try {
4440
+ await controller.respondToAskUser({
4441
+ requestId: request.requestId,
4442
+ sessionId,
4443
+ result: result ?? { cancelled: true, answers: [] }
4444
+ });
4445
+ } catch (error) {
4446
+ reportError(error);
4447
+ }
4448
+ };
4449
+ controller.on("permissionRequested" /* PermissionRequested */, permission);
4450
+ controller.on("askUserRequested" /* AskUserRequested */, askUser);
4451
+ const operations = {
4452
+ lifecycle: {
4453
+ shutdown: async () => {
4454
+ try {
4455
+ const results = await Promise.allSettled(
4456
+ [...ownedSessionIds].map((id) => controller.closeSession(id))
4457
+ );
4458
+ const failure = results.find(
4459
+ (result) => result.status === "rejected"
4460
+ );
4461
+ if (failure?.status === "rejected") throw toError(failure.reason);
4462
+ } finally {
4463
+ await operations.close();
4464
+ }
4465
+ },
4466
+ abort: (close) => {
4467
+ void close().catch(() => {
4468
+ });
4469
+ }
4470
+ },
4471
+ onNotification(callback, filter) {
4472
+ const listener = (event) => {
4473
+ if (event.sessionId !== sessionId || filter?.type !== void 0 && filter.type !== event.notification.type)
4474
+ return;
4475
+ try {
4476
+ callback({
4477
+ type: "notification",
4478
+ jsonrpc: JSONRPC_VERSION2,
4479
+ factoryApiVersion: LEGACY_FACTORY_API_VERSION2,
4480
+ factoryProtocolVersion: FACTORY_PROTOCOL_VERSION,
4481
+ method: "droid.session_notification" /* SESSION_NOTIFICATION */,
4482
+ params: { notification: event.notification }
4483
+ });
4484
+ } catch {
4485
+ }
4486
+ };
4487
+ controller.on("sessionNotification" /* SessionNotification */, listener);
4488
+ return () => controller.off("sessionNotification" /* SessionNotification */, listener);
4489
+ },
4490
+ onError(callback) {
4491
+ const disconnected = (code, reason) => callback(new ConnectionClosedError(code, reason));
4492
+ const exited = (event) => {
4493
+ if (event.sessionId === sessionId && event.notification.type === "session_process_exited" /* SESSION_PROCESS_EXITED */)
4494
+ callback(
4495
+ new DroidConnectionEndedError("Remote session process exited.")
4496
+ );
4497
+ };
4498
+ controller.on("error", callback);
4499
+ controller.on("disconnected" /* Disconnected */, disconnected);
4500
+ controller.on("sessionNotification" /* SessionNotification */, exited);
4501
+ return () => {
4502
+ controller.off("error", callback);
4503
+ controller.off("disconnected" /* Disconnected */, disconnected);
4504
+ controller.off("sessionNotification" /* SessionNotification */, exited);
4505
+ };
4506
+ },
4507
+ addUserMessage: async (params) => response(await controller.addUserMessage(sessionId, params)),
4508
+ interruptSession: async () => response(await controller.interruptSession(sessionId)),
4509
+ closeSession: async () => {
4510
+ await controller.closeSession(sessionId);
4511
+ return response({});
4512
+ },
4513
+ close: async () => {
4514
+ controller.off("permissionRequested" /* PermissionRequested */, permission);
4515
+ controller.off("askUserRequested" /* AskUserRequested */, askUser);
4516
+ controller.destroy();
4517
+ controller.getSessionStateManager().clear();
4518
+ },
4519
+ loadSession: async (params) => {
4520
+ ownedSessionIds.add(params.sessionId);
4521
+ const result = await controller.loadSession({
4522
+ ...params,
4523
+ sessionOriginHint: params.sessionOriginHint,
4524
+ sessionSource: params.sessionSource
4525
+ });
4526
+ sessionId = params.sessionId;
4527
+ return response(result);
4528
+ },
4529
+ updateSessionSettings: async (params) => response(await controller.updateSessionSettings(sessionId, params)),
4530
+ addMcpServer: async (params) => response(await controller.addMcpServer({ ...params, sessionId })),
4531
+ removeMcpServer: async (params) => response(await controller.removeMcpServer({ ...params, sessionId })),
4532
+ toggleMcpServer: async (params) => response(await controller.toggleMcpServer({ ...params, sessionId })),
4533
+ listMcpServers: async () => response(await controller.listMcpServers(sessionId)),
4534
+ listMcpTools: async () => response(await controller.listMcpTools(sessionId)),
4535
+ refreshConnectorToolCatalog: async () => response(await controller.refreshConnectorToolCatalog()),
4536
+ authenticateMcpServer: async (params) => response(
4537
+ await controller.authenticateMcpServer({ ...params, sessionId })
4538
+ ),
4539
+ listSkills: async (params) => response(await controller.listSkills(sessionId, params)),
4540
+ setSkillDisabled: async (skillName, disabled, settingsLevel) => response(
4541
+ await controller.setSkillDisabled({
4542
+ sessionId,
4543
+ skillName,
4544
+ disabled,
4545
+ settingsLevel
4546
+ })
4547
+ ),
4548
+ getRewindInfo: async (params) => response(await controller.getRewindInfo(sessionId, params.messageId)),
4549
+ executeRewind: async (params) => response(await controller.executeRewind(sessionId, params)),
4550
+ compactSession: async (params) => response(
4551
+ await controller.compactSession(
4552
+ sessionId,
4553
+ params.customInstructions,
4554
+ params.mode
4555
+ )
4556
+ ),
4557
+ forkSession: async (params) => response(await controller.forkSession(sessionId, params)),
4558
+ renameSession: async (params) => response(await controller.renameSession(sessionId, params.title)),
4559
+ listTools: async () => {
4560
+ throw new DroidClientError(
4561
+ "listTools is not supported by remote sessions."
4562
+ );
4563
+ },
4564
+ getContextStats: async () => {
4565
+ throw new DroidClientError(
4566
+ "getContextStats is not supported by remote sessions."
4567
+ );
4568
+ }
4569
+ };
4570
+ return operations;
4571
+ }
4572
+
4573
+ // src/environment-session.ts
4574
+ async function createEnvironmentSession(options, createHandle) {
4575
+ if (!options.environment)
4576
+ throw new DroidClientError("An environment is required.");
4577
+ for (const key of [
4578
+ "execPath",
4579
+ "execArgs",
4580
+ "env",
4581
+ "transport",
4582
+ "machineId"
4583
+ ]) {
4584
+ if (options[key] !== void 0)
4585
+ throw new DroidClientError(
4586
+ `Option "${key}" is local-only and cannot be used with an environment.`
4587
+ );
4588
+ }
4589
+ const mcpServers = options.mcpServers?.map((server) => {
4590
+ if (server instanceof SdkMcpServer)
4591
+ throw new DroidClientError(
4592
+ "In-process MCP servers cannot be used with an environment."
4593
+ );
4594
+ return server;
4595
+ });
4596
+ const apiKey = resolveApiKey(options.apiKey);
4597
+ const auth = { apiKey, baseUrl: options.baseUrl };
4598
+ const request = createApiRequest(auth);
4599
+ const api = createFactoryApi(request);
4600
+ const sessionId = uuidv43();
4601
+ let startup;
4602
+ try {
4603
+ const launched = await launchEnvironmentSession({
4604
+ environment: options.environment,
4605
+ signal: options.abortSignal,
4606
+ request,
4607
+ connect: async ({ computer, signal }) => {
4608
+ if (!computer.relayClientUrl)
4609
+ throw new DroidClientError("Computer has no relay URL.");
4610
+ const connectionConfig = {
4611
+ ...DEFAULT_CONNECTION_CONFIG,
4612
+ ...MACHINE_TYPE_CONFIG["computer" /* Computer */]
4613
+ };
4614
+ const connected = new DaemonSessionController({
4615
+ sessionStateManager: new MultiSessionStateManager(),
4616
+ config: {
4617
+ machineId: computer.id,
4618
+ machineType: "computer" /* Computer */,
4619
+ providerType: computer.providerType,
4620
+ isManaged: true,
4621
+ ensureRunning: async () => ({ wasRestarted: false }),
4622
+ trustedIdentity: trustedIdentityFromComputerResponse(
4623
+ computer,
4624
+ async () => {
4625
+ const fresh = parseResponse(
4626
+ await api.getComputer({
4627
+ computerId: computer.id,
4628
+ signal: AbortSignal.timeout(5e3)
4629
+ }),
4630
+ ComputerReadSchema
4631
+ );
4632
+ if (fresh.id !== computer.id)
4633
+ throw new DroidClientError(
4634
+ "Identity refresh returned a different Computer."
4635
+ );
4636
+ return fresh;
4637
+ }
4638
+ ),
4639
+ url: computer.relayClientUrl,
4640
+ clientType: "sdk" /* SDK */,
4641
+ sdk: SDK_CLIENT_METADATA,
4642
+ getCredential: async () => ({ apiKey }),
4643
+ connectionTimeoutMs: connectionConfig.connectionTimeoutMs,
4644
+ requestTimeout: connectionConfig.requestTimeoutMs,
4645
+ maxPollAttempts: connectionConfig.maxPollAttempts,
4646
+ maxReconnectAttempts: connectionConfig.maxReconnectAttempts,
4647
+ reconnectInterval: connectionConfig.reconnectInitialDelayMs,
4648
+ maxReconnectDelay: connectionConfig.reconnectMaxDelayMs,
4649
+ reconnectBackoffFactor: connectionConfig.reconnectBackoffFactor,
4650
+ supportsTerminalRestoreOnLoad: false
4651
+ }
4652
+ });
4653
+ const operations = createDaemonSessionOperations(
4654
+ connected,
4655
+ sessionId,
4656
+ options
4657
+ );
4658
+ const cancel = () => connected.destroy();
4659
+ signal.addEventListener("abort", cancel, { once: true });
4660
+ startup = {
4661
+ operations,
4662
+ releaseAbort: () => signal.removeEventListener("abort", cancel)
4663
+ };
4664
+ await connected.attemptInitialConnection();
4665
+ signal.throwIfAborted();
4666
+ return { controller: connected, operations };
4667
+ },
4668
+ startSession: async ({ connection, computer, signal }) => {
4669
+ const { controller, operations } = connection;
4670
+ const initParams = buildInitParams({
4671
+ ...options,
4672
+ machineId: computer.id,
4673
+ cwd: options.cwd ?? computer.clonedRepoDirectories?.[0] ?? "~",
4674
+ mcpServers
4675
+ });
4676
+ controller.getSessionStateManager().markSessionLoading(sessionId, computer.id);
4677
+ const result = await controller.initializeSession({
4678
+ ...initParams,
4679
+ sessionId,
4680
+ token: apiKey,
4681
+ sessionSource: initParams.sessionSource,
4682
+ sessionOriginHint: initParams.sessionOriginHint
4683
+ });
4684
+ signal.throwIfAborted();
4685
+ return createHandle({
4686
+ result,
4687
+ operations,
4688
+ computerId: computer.id,
4689
+ cwd: initParams.cwd,
4690
+ mcpServers
4691
+ });
4692
+ }
4693
+ });
4694
+ return launched.session;
4695
+ } catch (error) {
4696
+ await startup?.operations.close();
4697
+ throw error;
4698
+ } finally {
4699
+ startup?.releaseAbort();
4700
+ }
4701
+ }
4702
+
3595
4703
  // src/session.ts
3596
4704
  var DroidSession = class _DroidSession {
3597
4705
  /** @internal */
@@ -3602,11 +4710,22 @@ var DroidSession = class _DroidSession {
3602
4710
  this._settings = metadata.settings;
3603
4711
  this._cwd = metadata.cwd;
3604
4712
  this._cleanupCallbacks = cleanupCallbacks;
4713
+ this._lifecycle = _runtime.lifecycle ?? {
4714
+ shutdown: async () => {
4715
+ await client.closeSession({ reason: "other" }).catch(() => {
4716
+ });
4717
+ await client.close();
4718
+ },
4719
+ abort: (close) => {
4720
+ void close();
4721
+ }
4722
+ };
3605
4723
  this._bindMetadataNotifications();
3606
4724
  this._bindAbortSignal();
3607
4725
  }
3608
4726
  _runtime;
3609
4727
  _client;
4728
+ _lifecycle;
3610
4729
  _sessionId;
3611
4730
  _settings;
3612
4731
  _cwd;
@@ -3620,6 +4739,9 @@ var DroidSession = class _DroidSession {
3620
4739
  get id() {
3621
4740
  return this._sessionId;
3622
4741
  }
4742
+ get computerId() {
4743
+ return this._runtime.computerId;
4744
+ }
3623
4745
  get settings() {
3624
4746
  return this._settings;
3625
4747
  }
@@ -3664,9 +4786,7 @@ var DroidSession = class _DroidSession {
3664
4786
  bridge.release();
3665
4787
  }
3666
4788
  try {
3667
- await this._client.closeSession({ reason: "other" }).catch(() => {
3668
- });
3669
- await this._client.close();
4789
+ await this._lifecycle.shutdown();
3670
4790
  } finally {
3671
4791
  const cleanups = this._cleanupCallbacks.splice(0);
3672
4792
  await Promise.all(cleanups.map((cleanup) => cleanup()));
@@ -3717,6 +4837,11 @@ var DroidSession = class _DroidSession {
3717
4837
  this._ensureNotClosed();
3718
4838
  return unwrapResponseResult(await this._client.listMcpTools()).tools;
3719
4839
  }
4840
+ /** Refresh connector discovery. MCP configuration has its own lifecycle. */
4841
+ async refreshConnectorToolCatalog() {
4842
+ this._ensureNotClosed();
4843
+ unwrapResponseResult(await this._client.refreshConnectorToolCatalog());
4844
+ }
3720
4845
  async listTools(params = {}) {
3721
4846
  this._ensureNotClosed();
3722
4847
  return unwrapResponseResult(await this._client.listTools(params)).tools.map(
@@ -3733,9 +4858,9 @@ var DroidSession = class _DroidSession {
3733
4858
  await this._client.authenticateMcpServer(params)
3734
4859
  );
3735
4860
  }
3736
- async listSkills() {
4861
+ async listSkills(params) {
3737
4862
  this._ensureNotClosed();
3738
- return unwrapResponseResult(await this._client.listSkills());
4863
+ return unwrapResponseResult(await this._client.listSkills(params));
3739
4864
  }
3740
4865
  async setSkillDisabled(params) {
3741
4866
  this._ensureNotClosed();
@@ -3760,7 +4885,14 @@ var DroidSession = class _DroidSession {
3760
4885
  }
3761
4886
  async compact(params) {
3762
4887
  const { session, result } = await this._runReplacement(
3763
- () => this._client.compactSession(params ?? {}).then(unwrapResponseResult)
4888
+ () => this._client.compactSession(params ?? {}).then(unwrapResponseResult),
4889
+ { retainSourceWhenSameId: true }
4890
+ );
4891
+ return { session, removedCount: result.removedCount };
4892
+ }
4893
+ async handoff(params) {
4894
+ const { session, result } = await this._runReplacement(
4895
+ () => this._client.compactSession({ ...params, mode: "handoff" }).then(unwrapResponseResult)
3764
4896
  );
3765
4897
  return { session, removedCount: result.removedCount };
3766
4898
  }
@@ -3808,7 +4940,7 @@ var DroidSession = class _DroidSession {
3808
4940
  }
3809
4941
  this._status = "replacing";
3810
4942
  }
3811
- async _runReplacement(operation) {
4943
+ async _runReplacement(operation, options) {
3812
4944
  this._beginReplacement();
3813
4945
  let result;
3814
4946
  try {
@@ -3829,6 +4961,10 @@ var DroidSession = class _DroidSession {
3829
4961
  )
3830
4962
  });
3831
4963
  }
4964
+ if (options?.retainSourceWhenSameId && result.newSessionId === this._sessionId) {
4965
+ this._status = "active";
4966
+ return { session: this, result };
4967
+ }
3832
4968
  return {
3833
4969
  session: await this._loadReplacement(result.newSessionId),
3834
4970
  result
@@ -3913,7 +5049,7 @@ var DroidSession = class _DroidSession {
3913
5049
  _bindAbortSignal() {
3914
5050
  this._cleanupAbortSignal = wireAbortSignal(
3915
5051
  this._runtime.abortSignal,
3916
- () => void this.close()
5052
+ () => this._lifecycle.abort(() => this.close())
3917
5053
  );
3918
5054
  }
3919
5055
  _bindMetadataNotifications() {
@@ -3958,6 +5094,22 @@ function createReplacementLoadParams(options, mcpServers) {
3958
5094
  };
3959
5095
  }
3960
5096
  async function createSession(options = {}) {
5097
+ if (options.environment) {
5098
+ return await createEnvironmentSession(
5099
+ options,
5100
+ ({ result, operations, computerId, cwd, mcpServers }) => new DroidSession(
5101
+ operations,
5102
+ result.sessionId,
5103
+ { settings: result.settings, cwd },
5104
+ {
5105
+ computerId,
5106
+ abortSignal: options.abortSignal,
5107
+ loadParams: createReplacementLoadParams(options, mcpServers),
5108
+ lifecycle: operations.lifecycle
5109
+ }
5110
+ )
5111
+ );
5112
+ }
3961
5113
  const { client } = await createConfiguredClient(options);
3962
5114
  let cleanupInitAbortSignal = options.abortSignal?.aborted ? () => {
3963
5115
  } : wireAbortSignal(options.abortSignal, () => {
@@ -4047,12 +5199,14 @@ async function run(prompt, options = {}) {
4047
5199
  includePartialMessages: false
4048
5200
  })) {
4049
5201
  if (msg.type === DroidMessageType.Result) {
4050
- return msg;
5202
+ const computerId = session.computerId;
5203
+ return computerId === void 0 ? msg : { ...msg, computerId };
4051
5204
  }
4052
5205
  }
4053
5206
  throw new Error("Stream completed without a result message");
4054
5207
  } finally {
4055
- await session.close();
5208
+ await session.close().catch(() => {
5209
+ });
4056
5210
  }
4057
5211
  }
4058
5212
 
@@ -4072,9 +5226,14 @@ var CR = 13;
4072
5226
  var SPACE = 32;
4073
5227
  var TAB = 9;
4074
5228
  var PARSE_OVERSAMPLE = 20;
4075
- function readFileBuffer(filePath) {
5229
+ function readFileOrNull(filePath, read) {
4076
5230
  try {
4077
- return fs3.readFileSync(filePath);
5231
+ const fd = fs3.openSync(filePath, "r");
5232
+ try {
5233
+ return read(fd);
5234
+ } finally {
5235
+ fs3.closeSync(fd);
5236
+ }
4078
5237
  } catch {
4079
5238
  return null;
4080
5239
  }
@@ -4086,37 +5245,83 @@ function parseJsonBuffer(buf) {
4086
5245
  return null;
4087
5246
  }
4088
5247
  }
4089
- function scanBufferForLines(buf) {
5248
+ var SCAN_CHUNK_BYTES = 64 * 1024;
5249
+ var MAX_SCANNED_FILE_BYTES = 2 ** 31 - 1;
5250
+ function scratchChunk(scratch, size) {
5251
+ const want = Math.min(SCAN_CHUNK_BYTES, size === 0 ? 8192 : size);
5252
+ const current = scratch.chunk;
5253
+ if (current && current.length >= want) return current;
5254
+ const grown = Buffer.allocUnsafe(
5255
+ Math.min(SCAN_CHUNK_BYTES, Math.max(want, 2 * (current?.length ?? 0)))
5256
+ );
5257
+ scratch.chunk = grown;
5258
+ return grown;
5259
+ }
5260
+ function copyFirstLine(parts, chunk, end) {
5261
+ return Buffer.concat([...parts, chunk.subarray(0, end)]);
5262
+ }
5263
+ function scanSessionFile(fd, scratch) {
5264
+ const stats = fs3.fstatSync(fd);
5265
+ const size = stats.isFile() ? stats.size : 0;
5266
+ if (size > MAX_SCANNED_FILE_BYTES) return null;
5267
+ const chunk = scratchChunk(scratch, size);
4090
5268
  let nonEmptyLineCount = 0;
4091
- let firstLineEnd = -1;
4092
- let currentLineHasContent = false;
4093
- for (let i = 0; i < buf.length; i++) {
4094
- const ch = buf[i];
4095
- if (ch === LF) {
4096
- if (currentLineHasContent) nonEmptyLineCount++;
4097
- if (firstLineEnd === -1) firstLineEnd = i;
4098
- currentLineHasContent = false;
4099
- } else if (ch !== CR && ch !== SPACE && ch !== TAB) {
4100
- currentLineHasContent = true;
4101
- }
4102
- }
4103
- if (currentLineHasContent) nonEmptyLineCount++;
4104
- const end = firstLineEnd === -1 ? buf.length : firstLineEnd > 0 && buf[firstLineEnd - 1] === CR ? firstLineEnd - 1 : firstLineEnd;
4105
- return {
4106
- firstLine: buf.subarray(0, end),
4107
- nonEmptyLineCount
4108
- };
5269
+ let lineHasContent = false;
5270
+ const firstLineParts = [];
5271
+ let firstLine;
5272
+ let pos = 0;
5273
+ for (; ; ) {
5274
+ const length = size === 0 ? chunk.length : Math.min(chunk.length, size - pos);
5275
+ if (length === 0) break;
5276
+ const bytesRead = fs3.readSync(fd, chunk, 0, length, null);
5277
+ if (bytesRead === 0) break;
5278
+ pos += bytesRead;
5279
+ let i = 0;
5280
+ while (i < bytesRead) {
5281
+ let lf;
5282
+ if (lineHasContent) {
5283
+ lf = chunk.indexOf(LF, i);
5284
+ if (lf === -1 || lf >= bytesRead) break;
5285
+ nonEmptyLineCount++;
5286
+ lineHasContent = false;
5287
+ } else {
5288
+ const ch = chunk[i];
5289
+ if (ch === CR || ch === SPACE || ch === TAB) {
5290
+ i++;
5291
+ continue;
5292
+ }
5293
+ if (ch !== LF) {
5294
+ lineHasContent = true;
5295
+ continue;
5296
+ }
5297
+ lf = i;
5298
+ }
5299
+ firstLine ??= copyFirstLine(firstLineParts, chunk, lf);
5300
+ i = lf + 1;
5301
+ }
5302
+ if (firstLine === void 0) {
5303
+ firstLineParts.push(Buffer.from(chunk.subarray(0, bytesRead)));
5304
+ }
5305
+ }
5306
+ if (lineHasContent) nonEmptyLineCount++;
5307
+ const [onlyPart] = firstLineParts;
5308
+ firstLine ??= onlyPart !== void 0 && firstLineParts.length === 1 ? onlyPart : Buffer.concat(firstLineParts);
5309
+ return { firstLine, nonEmptyLineCount };
4109
5310
  }
4110
5311
  function loadFavorites(sessionsDir) {
4111
- const buf = readFileBuffer(path2.join(sessionsDir, FAVORITES_FILE));
5312
+ const buf = readFileOrNull(
5313
+ path2.join(sessionsDir, FAVORITES_FILE),
5314
+ (fd) => fs3.readFileSync(fd)
5315
+ );
4112
5316
  if (!buf) return /* @__PURE__ */ new Set();
4113
5317
  const parsed = parseJsonBuffer(buf);
4114
5318
  if (!Array.isArray(parsed)) return /* @__PURE__ */ new Set();
4115
5319
  return new Set(parsed.filter((id) => typeof id === "string"));
4116
5320
  }
4117
5321
  function loadSettingsFile(directory, sessionId) {
4118
- const buf = readFileBuffer(
4119
- path2.join(directory, `${sessionId}${SETTINGS_SUFFIX}`)
5322
+ const buf = readFileOrNull(
5323
+ path2.join(directory, `${sessionId}${SETTINGS_SUFFIX}`),
5324
+ (fd) => fs3.readFileSync(fd)
4120
5325
  );
4121
5326
  if (!buf) return null;
4122
5327
  const parsed = parseJsonBuffer(buf);
@@ -4138,9 +5343,11 @@ function getMissionMetadataFromTags(settings) {
4138
5343
  };
4139
5344
  }
4140
5345
  function parseSessionFile(candidate, ctx) {
4141
- const buf = readFileBuffer(candidate.sessionPath);
4142
- if (!buf) return null;
4143
- const scanned = scanBufferForLines(buf);
5346
+ const scanned = readFileOrNull(
5347
+ candidate.sessionPath,
5348
+ (fd) => scanSessionFile(fd, ctx.scratch)
5349
+ );
5350
+ if (!scanned) return null;
4144
5351
  const firstParsed = parseJsonBuffer(scanned.firstLine);
4145
5352
  if (firstParsed === null) return null;
4146
5353
  const firstResult = SessionStartEventSchema.safeParse(firstParsed);
@@ -4257,8 +5464,9 @@ async function listSessions(options = {}) {
4257
5464
  projectCandidates.sort(byMtimeDesc);
4258
5465
  legacyCandidates.sort(byMtimeDesc);
4259
5466
  const parseLimit = options.limit !== void 0 && options.limit >= 0 ? options.limit + PARSE_OVERSAMPLE : void 0;
4260
- const ctx = { favorites };
4261
- const legacyCtx = requiredCwd !== void 0 ? { favorites, requiredCwd } : ctx;
5467
+ const scratch = { chunk: void 0 };
5468
+ const ctx = { favorites, scratch };
5469
+ const legacyCtx = requiredCwd !== void 0 ? { favorites, scratch, requiredCwd } : ctx;
4262
5470
  const projectSessions = parseUpTo(projectCandidates, ctx, parseLimit);
4263
5471
  const legacySessions = parseUpTo(legacyCandidates, legacyCtx, parseLimit);
4264
5472
  const byId = /* @__PURE__ */ new Map();
@@ -4282,10 +5490,10 @@ async function listModels(options = {}) {
4282
5490
  const { includeDisabled, ...transportOptions } = options;
4283
5491
  const { client } = await createConfiguredClient(transportOptions);
4284
5492
  try {
4285
- const response = await client.listModels({
5493
+ const response2 = await client.listModels({
4286
5494
  ...includeDisabled !== void 0 && { includeDisabled }
4287
5495
  });
4288
- return unwrapResponseResult(response).models;
5496
+ return unwrapResponseResult(response2).models;
4289
5497
  } finally {
4290
5498
  await closeQuietly(client);
4291
5499
  }
@@ -4300,6 +5508,7 @@ export {
4300
5508
  AddUserMessageRequestSchema,
4301
5509
  AddUserMessageResponseSchema,
4302
5510
  AddUserMessageResultSchema,
5511
+ AnswerSessionElicitationResponseSchema,
4303
5512
  ApplyPatchToolConfirmationDetailsSchema,
4304
5513
  AskUserCollectedAnswerSchema,
4305
5514
  AskUserConfirmationDetailsSchema,
@@ -4360,6 +5569,7 @@ export {
4360
5569
  ConnectionError,
4361
5570
  ConnectionFailureReason,
4362
5571
  ContentBlockSchema,
5572
+ ContextApiError,
4363
5573
  ContextBreakdownCategorySchema,
4364
5574
  GetContextBreakdownResultSchema as ContextBreakdownResultSchema,
4365
5575
  ContextStatsAccuracy,
@@ -4384,6 +5594,7 @@ export {
4384
5594
  DroidClientError,
4385
5595
  DroidClientEvent,
4386
5596
  DroidClientMethod,
5597
+ DroidConnectionEndedError,
4387
5598
  DroidErrorType,
4388
5599
  DroidEvent,
4389
5600
  DroidHookEventSchema,
@@ -4400,9 +5611,12 @@ export {
4400
5611
  DroidWorkingStateChangedNotificationSchema,
4401
5612
  EditToolConfirmationDetailsSchema,
4402
5613
  EmptyResultSchema,
5614
+ EnvironmentListResponseSchema,
5615
+ EnvironmentSchema,
4403
5616
  ErrorDetailSchema,
4404
5617
  ErrorNotificationSchema,
4405
5618
  ExecToolInfoSchema,
5619
+ ExecToolInfoWithSchemasSchema,
4406
5620
  ExecuteRewindRequestParamsSchema,
4407
5621
  ExecuteRewindRequestSchema,
4408
5622
  ExecuteRewindResponseSchema,
@@ -4411,7 +5625,7 @@ export {
4411
5625
  ExitSpecModeConfirmationDetailsSchema,
4412
5626
  FACTORY_CLIENT_HEADER,
4413
5627
  FACTORY_CLIENT_VERSION,
4414
- FACTORY_PROTOCOL_VERSION2 as FACTORY_PROTOCOL_VERSION,
5628
+ FACTORY_PROTOCOL_VERSION,
4415
5629
  FactoryDaemonTransport,
4416
5630
  FactoryDroidMessageRole,
4417
5631
  FactoryDroidMessageSchema,
@@ -4454,7 +5668,7 @@ export {
4454
5668
  InvalidSessionCwdError,
4455
5669
  IpcDaemonClientTransport,
4456
5670
  IssueSeverity,
4457
- JSONRPC_VERSION3 as JSONRPC_VERSION,
5671
+ JSONRPC_VERSION,
4458
5672
  JsonArraySchema,
4459
5673
  JsonObjectSchema,
4460
5674
  JsonRpcEnvelopeSchema,
@@ -4473,7 +5687,7 @@ export {
4473
5687
  KillWorkerSessionRequestSchema,
4474
5688
  KillWorkerSessionResponseSchema,
4475
5689
  KillWorkerSessionResultSchema,
4476
- LEGACY_FACTORY_API_VERSION2 as LEGACY_FACTORY_API_VERSION,
5690
+ LEGACY_FACTORY_API_VERSION,
4477
5691
  LOCAL_MACHINE_ID,
4478
5692
  ListMcpRegistryRequestParamsSchema,
4479
5693
  ListMcpRegistryRequestSchema,
@@ -4489,6 +5703,7 @@ export {
4489
5703
  ListMcpToolsResultSchema,
4490
5704
  ListModelsOptionsSchema,
4491
5705
  ListModelsResultSchema,
5706
+ ListSessionElicitationsResponseSchema,
4492
5707
  ListSkillsRequestParamsSchema,
4493
5708
  ListSkillsRequestSchema,
4494
5709
  ListSkillsResponseSchema,
@@ -4496,7 +5711,9 @@ export {
4496
5711
  ListToolsRequestParamsSchema,
4497
5712
  ListToolsRequestSchema,
4498
5713
  ListToolsResponseSchema,
5714
+ ListToolsResponseWithSchemasSchema,
4499
5715
  ListToolsResultSchema,
5716
+ ListToolsResultWithSchemasSchema,
4500
5717
  LoadSessionRequestParamsSchema,
4501
5718
  LoadSessionRequestSchema,
4502
5719
  LoadSessionResponseSchema,
@@ -4551,6 +5768,12 @@ export {
4551
5768
  OutputFormatSchema,
4552
5769
  OutputFormatType,
4553
5770
  PermissionResolvedNotificationSchema,
5771
+ PickOptionsRequestParamsSchema,
5772
+ PickOptionsRequestSchema,
5773
+ PickOptionsResponseSchema,
5774
+ PickOptionsResultSchema,
5775
+ PickOptionsRowSchema,
5776
+ PickOptionsUnavailableError,
4554
5777
  ProcessExitError,
4555
5778
  ProcessTransport,
4556
5779
  ProgressLogEntrySchema,
@@ -4574,6 +5797,7 @@ export {
4574
5797
  RenameSessionRequestSchema,
4575
5798
  RenameSessionResponseSchema,
4576
5799
  RenameSessionResultSchema,
5800
+ RequestCancelledError,
4577
5801
  RequestPermissionRequestParamsSchema,
4578
5802
  RequestPermissionRequestSchema,
4579
5803
  RequestPermissionResponseSchema,
@@ -4587,6 +5811,13 @@ export {
4587
5811
  SLACK_DELEGATION_MAX_RETRIES,
4588
5812
  SdkMcpServer,
4589
5813
  ServerRequestHandlerType,
5814
+ SessionArtifactAction,
5815
+ SessionArtifactKind,
5816
+ SessionArtifactProvider,
5817
+ SessionArtifactSchema,
5818
+ SessionArtifactSource,
5819
+ SessionArtifactsSchema,
5820
+ SessionElicitationSchema,
4590
5821
  SessionError,
4591
5822
  SessionMetadataSchema,
4592
5823
  SessionNotFoundError,
@@ -4658,11 +5889,15 @@ export {
4658
5889
  ToolConfirmationListItemSchema,
4659
5890
  ToolConfirmationOutcome,
4660
5891
  ToolConfirmationType,
5892
+ ToolJsonSchemaSchema,
4661
5893
  ToolProgressUpdateNotificationSchema,
4662
5894
  ToolProgressUpdateSchema,
4663
5895
  ToolResultSchema as ToolResultBlockSchema,
4664
5896
  ToolResultNotificationSchema,
5897
+ ToolResultSchemaDescriptorSchema,
5898
+ ToolSchemasSchema,
4665
5899
  ToolSelectionOverridesSchema,
5900
+ ToolSourceSchema,
4666
5901
  ToolUseSchema as ToolUseBlockSchema,
4667
5902
  ToolUseSchema2 as ToolUseSchema,
4668
5903
  TraceContextMetaSchema,
@@ -4683,7 +5918,10 @@ export {
4683
5918
  WorkerSelectedFeatureEntrySchema,
4684
5919
  WorkerStartedEntrySchema,
4685
5920
  WorkerStateInfoSchema,
5921
+ MissionMachineTargetKind as WorkstreamMachineTargetKind,
4686
5922
  __resetTransportLoggerForTests,
5923
+ answerRemoteSessionElicitation,
5924
+ appendContextSession,
4687
5925
  buildUserMessageContentBlocks,
4688
5926
  configureDaemonCoreDeps,
4689
5927
  connectAndAuthenticate,
@@ -4691,16 +5929,25 @@ export {
4691
5929
  connectWithRetry,
4692
5930
  convertNotificationToStreamMessage,
4693
5931
  createComputer,
5932
+ createContextDocument,
5933
+ createContextFile,
5934
+ createContextSpace,
4694
5935
  createResponseSchema,
4695
5936
  createSdkMcpServer,
4696
5937
  createSession,
4697
5938
  createWebSocketDaemonClient,
4698
5939
  defaultDaemonCoreDeps,
4699
5940
  deleteComputer,
5941
+ deleteContextDocument,
5942
+ deleteContextFile,
4700
5943
  filterMessagesForUI,
4701
5944
  getComputer,
4702
5945
  getComputerByName,
4703
5946
  getComputerMetrics,
5947
+ getContextDocument,
5948
+ getContextDocumentByPath,
5949
+ getContextSession,
5950
+ getContextSpace,
4704
5951
  getMachineTemplate,
4705
5952
  getPermissionToolInputForDisplay,
4706
5953
  getQueuedUserMessageDisplayGroup,
@@ -4712,22 +5959,29 @@ export {
4712
5959
  hasSubagentSessionTag,
4713
5960
  inspectJsonRpcEnvelope,
4714
5961
  isDaemonQueuedMessageKind,
5962
+ isDroidConnectionEnded,
4715
5963
  isPendingToolResultMarker,
4716
5964
  isPersistedHookMessage,
4717
5965
  isReviewableQueuedMessageKind,
4718
5966
  isTransportLoggingEnabled,
4719
5967
  listComputers,
5968
+ listContextDocuments,
5969
+ listContextSessions,
5970
+ listContextSpaces,
5971
+ listEnvironments,
4720
5972
  listMachineTemplates,
4721
5973
  listModels,
5974
+ listRemoteSessionElicitations,
4722
5975
  listRemoteSessions,
4723
5976
  listSessions,
4724
5977
  logTransportMessage,
4725
5978
  machineTypeToMachineConnectionType,
4726
5979
  noopLogger,
4727
- orderMessagesByParentChain,
5980
+ readContextFilePart,
5981
+ readContextSessionRecords,
4728
5982
  recordComputerConnectSli,
4729
5983
  refreshComputer,
4730
- repairParentChainCorruption,
5984
+ registerContextSession,
4731
5985
  resolveExecCoreDeps,
4732
5986
  restartComputer,
4733
5987
  resumeSession,
@@ -4739,6 +5993,9 @@ export {
4739
5993
  summarizeSessionToolUsage,
4740
5994
  toError,
4741
5995
  tool,
4742
- updateComputer
5996
+ updateComputer,
5997
+ updateContextDocument,
5998
+ uploadContextFilePart,
5999
+ withJsonRpcCodec
4743
6000
  };
4744
6001
  //# sourceMappingURL=node.mjs.map