@factory/droid-sdk 0.9.0 → 0.10.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/node.mjs CHANGED
@@ -17,6 +17,7 @@ import {
17
17
  AskUserRequestSchema,
18
18
  AskUserResponseSchema,
19
19
  AskUserResultSchema,
20
+ AssistantMessageRetractedNotificationSchema,
20
21
  AssistantTextCompleteNotificationSchema,
21
22
  AssistantTextDeltaNotificationSchema,
22
23
  AuthenticateMcpServerRequestParamsSchema,
@@ -67,6 +68,7 @@ import {
67
68
  ConnectionError,
68
69
  ConnectionFailureReason,
69
70
  ContentBlockSchema,
71
+ ContextApiError,
70
72
  ContextBreakdownCategorySchema,
71
73
  ContextStatsAccuracy,
72
74
  ContextStatsSchema,
@@ -103,6 +105,7 @@ import {
103
105
  ErrorDetailSchema,
104
106
  ErrorNotificationSchema,
105
107
  ExecToolInfoSchema,
108
+ ExecToolInfoWithSchemasSchema,
106
109
  ExecuteRewindRequestParamsSchema,
107
110
  ExecuteRewindRequestSchema,
108
111
  ExecuteRewindResponseSchema,
@@ -182,6 +185,7 @@ import {
182
185
  LEGACY_FACTORY_API_VERSION,
183
186
  LEGACY_FACTORY_API_VERSION2,
184
187
  LOCAL_MACHINE_ID,
188
+ LfLineDecoder,
185
189
  ListCommandsResponseSchema,
186
190
  ListMcpRegistryRequestParamsSchema,
187
191
  ListMcpRegistryRequestSchema,
@@ -205,7 +209,9 @@ import {
205
209
  ListToolsRequestParamsSchema,
206
210
  ListToolsRequestSchema,
207
211
  ListToolsResponseSchema,
212
+ ListToolsResponseWithSchemasSchema,
208
213
  ListToolsResultSchema,
214
+ ListToolsResultWithSchemasSchema,
209
215
  LoadSessionRequestParamsSchema,
210
216
  LoadSessionRequestSchema,
211
217
  LoadSessionResponseSchema,
@@ -260,6 +266,12 @@ import {
260
266
  OutputFormatSchema,
261
267
  OutputFormatType,
262
268
  PermissionResolvedNotificationSchema,
269
+ PickOptionsRequestParamsSchema,
270
+ PickOptionsRequestSchema,
271
+ PickOptionsResponseSchema,
272
+ PickOptionsResultSchema,
273
+ PickOptionsRowSchema,
274
+ PickOptionsUnavailableError,
263
275
  ProcessExitError,
264
276
  ProgressLogEntrySchema,
265
277
  ProgressLogEntryType,
@@ -271,6 +283,7 @@ import {
271
283
  ReasoningEffort,
272
284
  RedactedThinkingBlockSchema,
273
285
  RefreshComputerResponseSchema,
286
+ RefreshConnectorToolCatalogResponseSchema,
274
287
  RelayConnectionError,
275
288
  RemoteSessionListResponseSchema,
276
289
  RemoteSessionSchema,
@@ -290,13 +303,19 @@ import {
290
303
  RewindEvictedFileSchema,
291
304
  RewindFileCreationSchema,
292
305
  RewindFileSnapshotSchema,
293
- SDK_CLIENT_METADATA,
306
+ SDK_IDENTITY,
294
307
  SDK_REQUEST_ATTRIBUTION,
295
308
  SDK_TAG,
296
309
  SDK_VERSION,
297
310
  SESSION_INIT_TIMEOUT,
298
311
  SLACK_DELEGATION_MAX_RETRIES,
299
312
  ServerRequestHandlerType,
313
+ SessionArtifactAction,
314
+ SessionArtifactKind,
315
+ SessionArtifactProvider,
316
+ SessionArtifactSchema,
317
+ SessionArtifactSource,
318
+ SessionArtifactsSchema,
300
319
  SessionError,
301
320
  SessionMetadataSchema,
302
321
  SessionNotFoundError,
@@ -370,11 +389,15 @@ import {
370
389
  ToolConfirmationListItemSchema,
371
390
  ToolConfirmationOutcome,
372
391
  ToolConfirmationType,
392
+ ToolJsonSchemaSchema,
373
393
  ToolProgressUpdateNotificationSchema,
374
394
  ToolProgressUpdateSchema,
375
395
  ToolResultNotificationSchema,
376
396
  ToolResultSchema,
397
+ ToolResultSchemaDescriptorSchema,
398
+ ToolSchemasSchema,
377
399
  ToolSelectionOverridesSchema,
400
+ ToolSourceSchema,
378
401
  ToolUseSchema,
379
402
  ToolUseSchema2,
380
403
  TraceContextMetaSchema,
@@ -394,6 +417,7 @@ import {
394
417
  WorkerSelectedFeatureEntrySchema,
395
418
  WorkerStartedEntrySchema,
396
419
  WorkerStateInfoSchema,
420
+ appendContextSession,
397
421
  buildUserMessageContentBlocks,
398
422
  configureDaemonCoreDeps,
399
423
  connectAndAuthenticate,
@@ -401,16 +425,26 @@ import {
401
425
  connectWithRetry,
402
426
  convertNotificationToStreamMessage,
403
427
  createComputer,
428
+ createContextDocument,
429
+ createContextFile,
430
+ createContextSpace,
404
431
  createResponseSchema,
405
432
  createWebSocketDaemonClient,
406
433
  defaultDaemonCoreDeps,
407
434
  deleteComputer,
435
+ deleteContextDocument,
436
+ deleteContextFile,
437
+ emitDroidLog,
438
+ escapeJsonLineSeparators,
408
439
  extractInnerNotification,
409
440
  filterMessagesForUI,
410
- formatSdkClientMetadata,
411
441
  getComputer,
412
442
  getComputerByName,
413
443
  getComputerMetrics,
444
+ getContextDocument,
445
+ getContextDocumentByPath,
446
+ getContextSession,
447
+ getContextSpace,
414
448
  getMachineTemplate,
415
449
  getPermissionToolInputForDisplay,
416
450
  getQueuedUserMessageDisplayGroup,
@@ -420,20 +454,27 @@ import {
420
454
  getToolResultToolUseId,
421
455
  hasDecoupledInteractionSettings,
422
456
  hasSubagentSessionTag,
457
+ injectDroidTraceContext,
423
458
  inspectJsonRpcEnvelope,
424
459
  isDaemonQueuedMessageKind,
425
460
  isPendingToolResultMarker,
426
461
  isPersistedHookMessage,
462
+ isProtocolVersionAtLeast,
427
463
  isReviewableQueuedMessageKind,
428
464
  listComputers,
465
+ listContextDocuments,
466
+ listContextSessions,
467
+ listContextSpaces,
429
468
  listMachineTemplates,
430
469
  listRemoteSessions,
431
470
  machineTypeToMachineConnectionType,
432
471
  mergeSessionSettings,
433
- orderMessagesByParentChain,
472
+ readContextFilePart,
473
+ readContextSessionRecords,
434
474
  recordComputerConnectSli,
475
+ recordDroidMetric,
435
476
  refreshComputer,
436
- repairParentChainCorruption,
477
+ registerContextSession,
437
478
  restartComputer,
438
479
  retryInstallDeps,
439
480
  retryWithLogger,
@@ -443,9 +484,11 @@ import {
443
484
  toError,
444
485
  unwrapResponseResult,
445
486
  updateComputer,
487
+ updateContextDocument,
488
+ uploadContextFilePart,
446
489
  wireAbortSignal,
447
490
  withSdkTag
448
- } from "./chunk-KXDG5ZWS.mjs";
491
+ } from "./chunk-TWAVPIPS.mjs";
449
492
 
450
493
  // src/exec-core/client.ts
451
494
  import EventEmitter from "events";
@@ -517,6 +560,9 @@ var defaultExecCoreDeps = {
517
560
  },
518
561
  sanitizeSpawnEnv(env) {
519
562
  return env;
563
+ },
564
+ buildRuntimeSettingsAuthorityEnv() {
565
+ return {};
520
566
  }
521
567
  }
522
568
  };
@@ -526,6 +572,7 @@ var DroidClientEvent = /* @__PURE__ */ ((DroidClientEvent2) => {
526
572
  DroidClientEvent2["SESSION_NOTIFICATION"] = "session_notification";
527
573
  DroidClientEvent2["REQUEST_PERMISSION"] = "request_permission";
528
574
  DroidClientEvent2["ASK_USER"] = "ask_user";
575
+ DroidClientEvent2["PICK_OPTIONS"] = "pick_options";
529
576
  DroidClientEvent2["ERROR"] = "error";
530
577
  return DroidClientEvent2;
531
578
  })(DroidClientEvent || {});
@@ -541,90 +588,6 @@ var TransportDirection = /* @__PURE__ */ ((TransportDirection2) => {
541
588
  return TransportDirection2;
542
589
  })(TransportDirection || {});
543
590
 
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
591
  // src/exec-core/deps.ts
629
592
  function logWarn(logger, message, metadata) {
630
593
  logger.warn(message, metadata);
@@ -788,6 +751,7 @@ var JsonRpcEnvelopePeekSchema = JsonRpcEnvelopeSchema.partial().extend({
788
751
  method: z.string().optional(),
789
752
  id: z.string().nullable().optional()
790
753
  }).passthrough();
754
+ var MCP_OAUTH_OPT_OUT_PROTOCOL_VERSION = "1.78.0";
791
755
  function inspectJsonRpcEnvelope2(message) {
792
756
  const result = JsonRpcEnvelopePeekSchema.safeParse(message);
793
757
  if (!result.success) {
@@ -811,8 +775,10 @@ var DroidClient = class _DroidClient extends EventEmitter {
811
775
  pendingRequests = /* @__PURE__ */ new Map();
812
776
  permissionHandlers = /* @__PURE__ */ new Map();
813
777
  askUserHandlers = /* @__PURE__ */ new Map();
778
+ pickOptionsHandlers = /* @__PURE__ */ new Map();
814
779
  pendingPermissionRequests = /* @__PURE__ */ new Map();
815
780
  pendingAskUserRequests = /* @__PURE__ */ new Map();
781
+ pendingPickOptionsRequests = /* @__PURE__ */ new Map();
816
782
  currentSessionId = null;
817
783
  peerProtocolVersion;
818
784
  /**
@@ -828,8 +794,8 @@ var DroidClient = class _DroidClient extends EventEmitter {
828
794
  getRequestAttribution;
829
795
  constructor(options, deps = defaultExecCoreDeps) {
830
796
  super();
831
- this.transport = options.transport;
832
797
  const resolved = resolveExecCoreDeps(options.observability, deps);
798
+ this.transport = options.transport;
833
799
  this.logger = resolved.logger;
834
800
  this.tracing = resolved.tracing;
835
801
  this.requestTimeout = options.requestTimeout ?? DEFAULT_REQUEST_TIMEOUT2;
@@ -849,13 +815,28 @@ var DroidClient = class _DroidClient extends EventEmitter {
849
815
  });
850
816
  this.transport.onError((error) => {
851
817
  const err = error instanceof Error ? error : new Error(String(error));
818
+ const pendingSessionIds = /* @__PURE__ */ new Set();
819
+ let hasPendingIdlessSessionStart = false;
820
+ for (const pendingRequest of this.pendingRequests.values()) {
821
+ if (pendingRequest.method === "droid.initialize_session" /* INITIALIZE_SESSION */ && pendingRequest.sessionId === void 0) {
822
+ hasPendingIdlessSessionStart = true;
823
+ } else if (pendingRequest.sessionId !== void 0) {
824
+ pendingSessionIds.add(pendingRequest.sessionId);
825
+ }
826
+ }
827
+ const pendingSessionId = pendingSessionIds.size === 1 ? pendingSessionIds.values().next().value : void 0;
828
+ const sessionId = hasPendingIdlessSessionStart || pendingSessionIds.size > 1 ? void 0 : pendingSessionId ?? this.currentSessionId ?? void 0;
852
829
  const expectedCleanExit = err instanceof ProcessExitError && err.exitCode === 0 && this.completedTurnObserved;
853
830
  if (expectedCleanExit) {
854
831
  this.logger.info("[DroidClient] Process exited after completed turn", {
855
- exitCode: err.exitCode
832
+ sessionId,
833
+ exitCode: err.exitCode,
834
+ signal: err.signal
856
835
  });
857
836
  } else {
858
- this.logger.exception(err, "Transport error");
837
+ this.logger.exception(err, "Transport error", {
838
+ sessionId
839
+ });
859
840
  }
860
841
  this.transportError = err;
861
842
  this.rejectAllPending(err);
@@ -877,14 +858,18 @@ var DroidClient = class _DroidClient extends EventEmitter {
877
858
  return this.transport.isConnected;
878
859
  }
879
860
  requireMcpOAuthOptOutSupport() {
880
- if (this.peerProtocolVersion === FACTORY_PROTOCOL_VERSION) {
861
+ if (isProtocolVersionAtLeast(
862
+ this.peerProtocolVersion,
863
+ MCP_OAUTH_OPT_OUT_PROTOCOL_VERSION
864
+ )) {
881
865
  return;
882
866
  }
883
867
  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.",
868
+ `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
869
  {
886
870
  value: {
887
871
  localFactoryProtocolVersion: FACTORY_PROTOCOL_VERSION,
872
+ minimumFactoryProtocolVersion: MCP_OAUTH_OPT_OUT_PROTOCOL_VERSION,
888
873
  peerFactoryProtocolVersion: this.peerProtocolVersion
889
874
  }
890
875
  }
@@ -907,7 +892,8 @@ var DroidClient = class _DroidClient extends EventEmitter {
907
892
  };
908
893
  const rawResponse = await this.sendRequest(
909
894
  request,
910
- this.sessionInitTimeout
895
+ this.sessionInitTimeout,
896
+ params.sessionId
911
897
  );
912
898
  const response = InitializeSessionResponseSchema.parse(rawResponse);
913
899
  this.peerProtocolVersion = response.factoryProtocolVersion;
@@ -936,7 +922,8 @@ var DroidClient = class _DroidClient extends EventEmitter {
936
922
  };
937
923
  const rawResponse = await this.sendRequest(
938
924
  request,
939
- this.sessionInitTimeout
925
+ this.sessionInitTimeout,
926
+ params.sessionId
940
927
  );
941
928
  const response = LoadSessionResponseSchema.parse(rawResponse);
942
929
  this.peerProtocolVersion = response.factoryProtocolVersion;
@@ -1358,6 +1345,24 @@ var DroidClient = class _DroidClient extends EventEmitter {
1358
1345
  const response = ListMcpRegistryResponseSchema.parse(rawResponse);
1359
1346
  return response;
1360
1347
  }
1348
+ async refreshConnectorToolCatalog(params = {}) {
1349
+ if (!this.currentSessionId) {
1350
+ throw new SessionError(
1351
+ "No active session. Call initializeSession or loadSession first."
1352
+ );
1353
+ }
1354
+ const request = {
1355
+ factoryApiVersion: LEGACY_FACTORY_API_VERSION,
1356
+ factoryProtocolVersion: FACTORY_PROTOCOL_VERSION,
1357
+ type: "request",
1358
+ jsonrpc: JSONRPC_VERSION2,
1359
+ id: uuidv4(),
1360
+ method: "droid.refresh_connector_tool_catalog" /* REFRESH_CONNECTOR_TOOL_CATALOG */,
1361
+ params
1362
+ };
1363
+ const response = await this.sendRequest(request, this.requestTimeout);
1364
+ return RefreshConnectorToolCatalogResponseSchema.parse(response);
1365
+ }
1361
1366
  async listMcpTools() {
1362
1367
  if (!this.currentSessionId) {
1363
1368
  throw new SessionError(
@@ -1378,6 +1383,7 @@ var DroidClient = class _DroidClient extends EventEmitter {
1378
1383
  return response;
1379
1384
  }
1380
1385
  async listTools(params = {}) {
1386
+ const schemasRequested = params.includeSchemas === true;
1381
1387
  const request = {
1382
1388
  factoryApiVersion: LEGACY_FACTORY_API_VERSION,
1383
1389
  factoryProtocolVersion: FACTORY_PROTOCOL_VERSION,
@@ -1389,7 +1395,28 @@ var DroidClient = class _DroidClient extends EventEmitter {
1389
1395
  };
1390
1396
  const rawResponse = await this.sendRequest(request, this.requestTimeout);
1391
1397
  const response = ListToolsResponseSchema.parse(rawResponse);
1392
- return response;
1398
+ if (!schemasRequested || !("result" in response)) {
1399
+ return response;
1400
+ }
1401
+ const strictResult = ListToolsResultWithSchemasSchema.safeParse(
1402
+ response.result
1403
+ );
1404
+ if (strictResult.success) {
1405
+ return response;
1406
+ }
1407
+ const result = ListToolsResultSchema.parse(response.result);
1408
+ const missingSchemaIds = [];
1409
+ for (const tool2 of result.tools) {
1410
+ if (tool2.schemas === void 0) {
1411
+ missingSchemaIds.push(tool2.llmId);
1412
+ }
1413
+ }
1414
+ if (missingSchemaIds.length > 0) {
1415
+ throw new ProtocolError(
1416
+ `The installed Droid version did not return requested schemas for tool IDs: ${missingSchemaIds.join(", ")}. Update Droid and try again.`
1417
+ );
1418
+ }
1419
+ throw strictResult.error;
1393
1420
  }
1394
1421
  async listMcpServers() {
1395
1422
  if (!this.currentSessionId) {
@@ -1467,7 +1494,7 @@ var DroidClient = class _DroidClient extends EventEmitter {
1467
1494
  const response = SubmitMcpAuthErrorResponseSchema.parse(rawResponse);
1468
1495
  return response;
1469
1496
  }
1470
- async listSkills() {
1497
+ async listSkills(params = {}) {
1471
1498
  if (!this.currentSessionId) {
1472
1499
  throw new SessionError(
1473
1500
  "No active session. Call initializeSession or loadSession first."
@@ -1480,7 +1507,7 @@ var DroidClient = class _DroidClient extends EventEmitter {
1480
1507
  jsonrpc: JSONRPC_VERSION2,
1481
1508
  id: uuidv4(),
1482
1509
  method: "droid.list_skills" /* LIST_SKILLS */,
1483
- params: {}
1510
+ params
1484
1511
  };
1485
1512
  const rawResponse = await this.sendRequest(request, this.requestTimeout);
1486
1513
  const response = ListSkillsResponseSchema.parse(rawResponse);
@@ -1642,7 +1669,8 @@ var DroidClient = class _DroidClient extends EventEmitter {
1642
1669
  id: uuidv4(),
1643
1670
  method: "droid.compact_session" /* COMPACT_SESSION */,
1644
1671
  params: {
1645
- customInstructions: params.customInstructions
1672
+ customInstructions: params.customInstructions,
1673
+ mode: params.mode
1646
1674
  }
1647
1675
  };
1648
1676
  const rawResponse = await this.sendRequest(request, COMPACTION_TIMEOUT2);
@@ -1738,13 +1766,34 @@ var DroidClient = class _DroidClient extends EventEmitter {
1738
1766
  clearAskUserHandler() {
1739
1767
  this.askUserHandlers.delete("default");
1740
1768
  }
1741
- async close() {
1769
+ setPickOptionsHandler(handler) {
1770
+ this.pickOptionsHandlers.set(
1771
+ "default",
1772
+ async (event) => await handler(event)
1773
+ );
1774
+ }
1775
+ clearPickOptionsHandler() {
1776
+ this.pickOptionsHandlers.delete("default");
1777
+ }
1778
+ async close(options) {
1742
1779
  const closeError = new ConnectionError(
1743
1780
  "Droid client closed: pending requests cancelled"
1744
1781
  );
1782
+ if (options?.closeTransportFirst) {
1783
+ try {
1784
+ await this.transport.close();
1785
+ } finally {
1786
+ this.rejectAllPending(closeError);
1787
+ this.permissionHandlers.clear();
1788
+ this.askUserHandlers.clear();
1789
+ this.removeAllListeners();
1790
+ }
1791
+ return;
1792
+ }
1745
1793
  this.rejectAllPending(closeError);
1746
1794
  this.permissionHandlers.clear();
1747
1795
  this.askUserHandlers.clear();
1796
+ this.pickOptionsHandlers.clear();
1748
1797
  this.removeAllListeners();
1749
1798
  await this.transport.close();
1750
1799
  }
@@ -1765,6 +1814,10 @@ var DroidClient = class _DroidClient extends EventEmitter {
1765
1814
  reject(error);
1766
1815
  });
1767
1816
  this.pendingAskUserRequests.clear();
1817
+ this.pendingPickOptionsRequests.forEach(({ reject }) => {
1818
+ reject(error);
1819
+ });
1820
+ this.pendingPickOptionsRequests.clear();
1768
1821
  }
1769
1822
  settleRequest(requestId) {
1770
1823
  this.pendingRequests.delete(requestId);
@@ -1776,10 +1829,16 @@ var DroidClient = class _DroidClient extends EventEmitter {
1776
1829
  } catch {
1777
1830
  }
1778
1831
  }
1779
- async sendRequest(request, timeout) {
1832
+ async sendRequest(request, timeout, sessionId) {
1780
1833
  if (this.transportError) {
1781
1834
  throw this.transportError;
1782
1835
  }
1836
+ const requestAttribution = this.getRequestAttribution?.();
1837
+ const _meta = {
1838
+ ...requestAttribution && { requestAttribution }
1839
+ };
1840
+ this.tracing.injectContext(_meta, this.tracing.getCurrentContext());
1841
+ const requestWithMeta = _meta.traceparent || _meta.requestAttribution ? { ...request, _meta } : request;
1783
1842
  const responsePromise = new Promise(
1784
1843
  (resolve2, reject) => {
1785
1844
  const timeoutId = timeout ? setTimeout(() => {
@@ -1796,22 +1855,19 @@ var DroidClient = class _DroidClient extends EventEmitter {
1796
1855
  method: request.method,
1797
1856
  resolve: resolve2,
1798
1857
  reject,
1799
- timeoutId
1858
+ timeoutId,
1859
+ sessionId
1800
1860
  });
1801
1861
  }
1802
1862
  );
1803
- const requestAttribution = this.getRequestAttribution?.();
1804
- const _meta = {
1805
- ...requestAttribution && { requestAttribution }
1806
- };
1807
- this.tracing.injectContext(_meta, this.tracing.getCurrentContext());
1808
- const requestWithMeta = _meta.traceparent || _meta.requestAttribution ? { ...request, _meta } : request;
1863
+ void responsePromise.catch(() => {
1864
+ });
1809
1865
  try {
1810
1866
  this.logger.info("[DroidClient] [daemon -> droid] sending request", {
1811
1867
  requestId: request.id,
1812
1868
  method: request.method
1813
1869
  });
1814
- await this.transport.send(JSON.stringify(requestWithMeta));
1870
+ await this.transport.send(requestWithMeta);
1815
1871
  } catch (error) {
1816
1872
  const pending = this.pendingRequests.get(request.id);
1817
1873
  if (pending) {
@@ -1831,20 +1887,13 @@ var DroidClient = class _DroidClient extends EventEmitter {
1831
1887
  }
1832
1888
  async handleMessage(message) {
1833
1889
  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);
1890
+ if (message.type === "response") {
1891
+ this.handleResponse(message);
1839
1892
  } else {
1840
- throw new ProtocolError("Invalid message format", {
1841
- message
1842
- });
1893
+ await this.handleEvent(message);
1843
1894
  }
1844
1895
  } catch {
1845
- this.logger.warn("[DroidClient] Failed to handle message", {
1846
- messageByteLength: new TextEncoder().encode(message).byteLength
1847
- });
1896
+ this.logger.warn("[DroidClient] Failed to handle message");
1848
1897
  }
1849
1898
  }
1850
1899
  handleResponse(response) {
@@ -1912,6 +1961,9 @@ var DroidClient = class _DroidClient extends EventEmitter {
1912
1961
  case "droid.list_tools" /* LIST_TOOLS */:
1913
1962
  validatedResponse = ListToolsResponseSchema.parse(response);
1914
1963
  break;
1964
+ case "droid.refresh_connector_tool_catalog" /* REFRESH_CONNECTOR_TOOL_CATALOG */:
1965
+ validatedResponse = RefreshConnectorToolCatalogResponseSchema.parse(response);
1966
+ break;
1915
1967
  case "droid.toggle_mcp_tool" /* TOGGLE_MCP_TOOL */:
1916
1968
  validatedResponse = ToggleMcpToolResponseSchema.parse(response);
1917
1969
  break;
@@ -2052,6 +2104,8 @@ var DroidClient = class _DroidClient extends EventEmitter {
2052
2104
  await this.handlePermissionRequest(validatedEvent);
2053
2105
  } else if (validatedEvent.method === "droid.ask_user" /* ASK_USER */) {
2054
2106
  await this.handleAskUserRequest(validatedEvent);
2107
+ } else if (validatedEvent.method === "droid.pick_options" /* PICK_OPTIONS */) {
2108
+ await this.handlePickOptionsRequest(validatedEvent);
2055
2109
  }
2056
2110
  } catch (error) {
2057
2111
  this.logDroppedEventParseError(error, event);
@@ -2086,7 +2140,7 @@ var DroidClient = class _DroidClient extends EventEmitter {
2086
2140
  }
2087
2141
  };
2088
2142
  try {
2089
- await this.transport.send(JSON.stringify(errorResponse));
2143
+ await this.transport.send(errorResponse);
2090
2144
  } catch (sendError) {
2091
2145
  this.logger.exception(
2092
2146
  sendError,
@@ -2149,6 +2203,8 @@ var DroidClient = class _DroidClient extends EventEmitter {
2149
2203
  let selectedOption = "cancel" /* Cancel */;
2150
2204
  let comment;
2151
2205
  let editedSpecContent;
2206
+ let clientStartsHandoffSession;
2207
+ let specHandoffId;
2152
2208
  const handler = this.permissionHandlers.get("default");
2153
2209
  if (handler) {
2154
2210
  try {
@@ -2179,6 +2235,8 @@ var DroidClient = class _DroidClient extends EventEmitter {
2179
2235
  selectedOption = parsedHandlerResult.data.selectedOption;
2180
2236
  comment = parsedHandlerResult.data.comment;
2181
2237
  editedSpecContent = parsedHandlerResult.data.editedSpecContent;
2238
+ clientStartsHandoffSession = parsedHandlerResult.data.clientStartsHandoffSession;
2239
+ specHandoffId = parsedHandlerResult.data.specHandoffId;
2182
2240
  } else {
2183
2241
  throw new DroidClientError(
2184
2242
  "Permission handler returned invalid result",
@@ -2222,7 +2280,7 @@ var DroidClient = class _DroidClient extends EventEmitter {
2222
2280
  }
2223
2281
  };
2224
2282
  try {
2225
- await this.transport.send(JSON.stringify(cancelResponse));
2283
+ await this.transport.send(cancelResponse);
2226
2284
  } catch (sendError) {
2227
2285
  this.logger.exception(
2228
2286
  sendError,
@@ -2248,14 +2306,18 @@ var DroidClient = class _DroidClient extends EventEmitter {
2248
2306
  result: {
2249
2307
  selectedOption,
2250
2308
  ...comment !== void 0 && { comment },
2251
- ...editedSpecContent !== void 0 && { editedSpecContent }
2309
+ ...editedSpecContent !== void 0 && { editedSpecContent },
2310
+ ...clientStartsHandoffSession !== void 0 && {
2311
+ clientStartsHandoffSession
2312
+ },
2313
+ ...specHandoffId !== void 0 && { specHandoffId }
2252
2314
  }
2253
2315
  };
2254
2316
  this.logger.info("[DroidClient] Sending permission response back to CLI", {
2255
2317
  requestId: event.id,
2256
2318
  result: String(selectedOption)
2257
2319
  });
2258
- await this.transport.send(JSON.stringify(response));
2320
+ await this.transport.send(response);
2259
2321
  }
2260
2322
  async handleAskUserRequest(event) {
2261
2323
  this.logger.info("[DroidClient] Handling ask-user request", {
@@ -2319,7 +2381,7 @@ var DroidClient = class _DroidClient extends EventEmitter {
2319
2381
  }
2320
2382
  };
2321
2383
  try {
2322
- await this.transport.send(JSON.stringify(cancelResponse));
2384
+ await this.transport.send(cancelResponse);
2323
2385
  } catch (sendError) {
2324
2386
  this.logger.exception(
2325
2387
  sendError,
@@ -2341,7 +2403,105 @@ var DroidClient = class _DroidClient extends EventEmitter {
2341
2403
  requestId: event.id,
2342
2404
  cancelled: result.cancelled ?? false
2343
2405
  });
2344
- await this.transport.send(JSON.stringify(response));
2406
+ await this.transport.send(response);
2407
+ }
2408
+ /**
2409
+ * Same shape as the ask-user round trip: the handler (a UI) resolves with
2410
+ * the user's selection, and any failure or missing handler answers the CLI
2411
+ * with a cancellation so its tool call never hangs. A handler that rejects
2412
+ * with {@link PickOptionsUnavailableError} is relayed as that JSON-RPC
2413
+ * error instead, which the CLI does not read as the user's cancel.
2414
+ */
2415
+ async handlePickOptionsRequest(event) {
2416
+ this.logger.info("[DroidClient] Handling pick-options request", {
2417
+ requestId: event.id,
2418
+ toolCallId: event.params.toolCallId,
2419
+ count: event.params.rows.length
2420
+ });
2421
+ this.emit("pick_options" /* PICK_OPTIONS */, event);
2422
+ let result = { cancelled: true, selectedIds: [] };
2423
+ const handler = this.pickOptionsHandlers.get("default");
2424
+ if (handler) {
2425
+ try {
2426
+ const handlerPromise = new Promise(
2427
+ (resolve2, reject) => {
2428
+ this.pendingPickOptionsRequests.set(event.id, { reject });
2429
+ handler(event).then(resolve2).catch(reject).finally(() => {
2430
+ this.pendingPickOptionsRequests.delete(event.id);
2431
+ });
2432
+ }
2433
+ );
2434
+ const parsedResult = PickOptionsResultSchema.safeParse(
2435
+ await handlerPromise
2436
+ );
2437
+ if (!parsedResult.success) {
2438
+ throw new DroidClientError(
2439
+ "PickOptions handler returned invalid result",
2440
+ { cause: parsedResult.error }
2441
+ );
2442
+ }
2443
+ result = parsedResult.data;
2444
+ } catch (error) {
2445
+ this.pendingPickOptionsRequests.delete(event.id);
2446
+ if (error instanceof PickOptionsUnavailableError) {
2447
+ this.logger.info("[DroidClient] Pick-options request unavailable", {
2448
+ requestId: event.id,
2449
+ code: error.rpcError.code
2450
+ });
2451
+ const failure = {
2452
+ jsonrpc: JSONRPC_VERSION2,
2453
+ factoryApiVersion: LEGACY_FACTORY_API_VERSION,
2454
+ factoryProtocolVersion: FACTORY_PROTOCOL_VERSION,
2455
+ type: "response",
2456
+ id: event.id,
2457
+ error: error.rpcError
2458
+ };
2459
+ try {
2460
+ await this.transport.send(failure);
2461
+ } catch (sendError) {
2462
+ this.logger.exception(
2463
+ sendError,
2464
+ "Failed to send pick-options failure"
2465
+ );
2466
+ }
2467
+ return;
2468
+ }
2469
+ this.logger.exception(error, "Failed to handle pick-options request");
2470
+ const err = error instanceof Error ? error : new Error(String(error));
2471
+ this.emit(
2472
+ "session_notification" /* SESSION_NOTIFICATION */,
2473
+ _DroidClient.createNotificationEvent(
2474
+ _DroidClient.createErrorNotification(err)
2475
+ )
2476
+ );
2477
+ const cancelResponse = {
2478
+ jsonrpc: JSONRPC_VERSION2,
2479
+ factoryApiVersion: LEGACY_FACTORY_API_VERSION,
2480
+ factoryProtocolVersion: FACTORY_PROTOCOL_VERSION,
2481
+ type: "response",
2482
+ id: event.id,
2483
+ result: { cancelled: true, selectedIds: [] }
2484
+ };
2485
+ try {
2486
+ await this.transport.send(cancelResponse);
2487
+ } catch (sendError) {
2488
+ this.logger.exception(
2489
+ sendError,
2490
+ "Failed to send pick-options cancellation"
2491
+ );
2492
+ }
2493
+ return;
2494
+ }
2495
+ }
2496
+ const response = {
2497
+ type: "response",
2498
+ jsonrpc: JSONRPC_VERSION2,
2499
+ factoryApiVersion: LEGACY_FACTORY_API_VERSION,
2500
+ factoryProtocolVersion: FACTORY_PROTOCOL_VERSION,
2501
+ id: event.id,
2502
+ result
2503
+ };
2504
+ await this.transport.send(response);
2345
2505
  }
2346
2506
  static getErrorType(error) {
2347
2507
  switch (error.constructor) {
@@ -2403,10 +2563,52 @@ var DroidClient = class _DroidClient extends EventEmitter {
2403
2563
  }
2404
2564
  };
2405
2565
 
2566
+ // src/exec-core/message-transport.ts
2567
+ var JsonRpcCodecTransport = class {
2568
+ transport;
2569
+ logger;
2570
+ constructor(transport, logger) {
2571
+ this.transport = transport;
2572
+ this.logger = logger;
2573
+ }
2574
+ send(message) {
2575
+ return this.transport.send(JSON.stringify(message));
2576
+ }
2577
+ onMessage(handler) {
2578
+ this.transport.onMessage((frame) => {
2579
+ const message = decodeDroidMessage(frame, this.logger);
2580
+ if (message) handler(message);
2581
+ });
2582
+ }
2583
+ onError(handler) {
2584
+ this.transport.onError(handler);
2585
+ }
2586
+ close() {
2587
+ return this.transport.close();
2588
+ }
2589
+ get isConnected() {
2590
+ return this.transport.isConnected;
2591
+ }
2592
+ };
2593
+ function withJsonRpcCodec(transport, deps = defaultExecCoreDeps) {
2594
+ const { logger } = resolveExecCoreDeps(void 0, deps);
2595
+ return new JsonRpcCodecTransport(transport, logger);
2596
+ }
2597
+ function decodeDroidMessage(frame, logger) {
2598
+ try {
2599
+ return JsonRpcMessageSchema.parse(JSON.parse(frame));
2600
+ } catch (error) {
2601
+ logWarn(logger, "[DroidClient] Failed to handle message", {
2602
+ errorName: error instanceof Error ? error.name : typeof error,
2603
+ messageByteLength: new TextEncoder().encode(frame).byteLength
2604
+ });
2605
+ return void 0;
2606
+ }
2607
+ }
2608
+
2406
2609
  // src/exec-core/managed-process.ts
2407
2610
  import { spawnSync } from "child_process";
2408
2611
  import fs2 from "fs";
2409
- import * as readline from "readline";
2410
2612
 
2411
2613
  // src/exec-core/runtime-utils.ts
2412
2614
  import { homedir } from "os";
@@ -2637,7 +2839,13 @@ var ManagedProcessImpl = class {
2637
2839
  _childProcess;
2638
2840
  messageHandler = null;
2639
2841
  errorHandler = null;
2640
- readlineInterface = null;
2842
+ stdoutDecoder = new LfLineDecoder();
2843
+ stdoutDataHandler = (chunk) => {
2844
+ const text = typeof chunk === "string" ? chunk : chunk.toString("utf8");
2845
+ for (const line of this.stdoutDecoder.push(text)) {
2846
+ this.handleStdoutLine(line);
2847
+ }
2848
+ };
2641
2849
  isClosing = false;
2642
2850
  /** Latch so exit teardown/classification runs exactly once per process. */
2643
2851
  exitFinalized = false;
@@ -2660,31 +2868,12 @@ var ManagedProcessImpl = class {
2660
2868
  if (!this.childProcess.stdout) {
2661
2869
  throw new ConnectionError("Failed to get process stdout");
2662
2870
  }
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
- }
2685
- }
2686
- });
2871
+ this.childProcess.stdout.setEncoding("utf8");
2872
+ this.childProcess.stdout.on("data", this.stdoutDataHandler);
2687
2873
  this.childProcess.on("error", (error) => {
2874
+ if (this.processError || this.isClosing || this.exitFinalized) {
2875
+ return;
2876
+ }
2688
2877
  this.processError = new ConnectionError(
2689
2878
  describeSpawnError(error, this.spawnContext),
2690
2879
  {
@@ -2693,9 +2882,16 @@ var ManagedProcessImpl = class {
2693
2882
  execPath: this.spawnContext?.execPath
2694
2883
  }
2695
2884
  );
2696
- if (!this.isClosing && this.errorHandler) {
2697
- this.errorHandler(this.processError);
2885
+ this.errorHandler?.(this.processError);
2886
+ });
2887
+ this.childProcess.stdin?.on("error", (error) => {
2888
+ if (this.processError || this.isClosing || this.exitFinalized) {
2889
+ return;
2698
2890
  }
2891
+ this.processError = new ConnectionError("Process stdin failed", {
2892
+ error
2893
+ });
2894
+ this.errorHandler?.(this.processError);
2699
2895
  });
2700
2896
  this.childProcess.on("exit", (code, signal) => {
2701
2897
  this.exitDrainTimer = setTimeout(() => {
@@ -2704,6 +2900,9 @@ var ManagedProcessImpl = class {
2704
2900
  this.exitDrainTimer.unref?.();
2705
2901
  });
2706
2902
  this.childProcess.on("close", (code, signal) => {
2903
+ for (const line of this.stdoutDecoder.finish()) {
2904
+ this.handleStdoutLine(line);
2905
+ }
2707
2906
  this.finalizeExit(code, signal);
2708
2907
  });
2709
2908
  if (this.childProcess.stderr) {
@@ -2715,6 +2914,27 @@ var ManagedProcessImpl = class {
2715
2914
  });
2716
2915
  }
2717
2916
  }
2917
+ handleStdoutLine(line) {
2918
+ const trimmedLine = line.trim();
2919
+ if (!this.messageHandler || !trimmedLine) {
2920
+ return;
2921
+ }
2922
+ if (trimmedLine.startsWith("{") || trimmedLine.startsWith("[")) {
2923
+ logTransportMessage(
2924
+ "in" /* In */,
2925
+ trimmedLine,
2926
+ this._childProcess?.pid,
2927
+ this.transportLoggerDeps
2928
+ );
2929
+ this.messageHandler(trimmedLine);
2930
+ return;
2931
+ }
2932
+ const truncated = trimmedLine.length > MAX_NON_JSON_LOG_LENGTH ? trimmedLine.substring(0, MAX_NON_JSON_LOG_LENGTH) : trimmedLine;
2933
+ this.logger.warn("[droid process] non-JSON output", {
2934
+ state: this.logPrefix,
2935
+ output: truncated
2936
+ });
2937
+ }
2718
2938
  finalizeExit(code, signal) {
2719
2939
  if (this.exitFinalized) {
2720
2940
  return;
@@ -2724,12 +2944,10 @@ var ManagedProcessImpl = class {
2724
2944
  clearTimeout(this.exitDrainTimer);
2725
2945
  this.exitDrainTimer = null;
2726
2946
  }
2947
+ this._childProcess?.stdout?.off("data", this.stdoutDataHandler);
2727
2948
  this._childProcess = null;
2728
- if (this.readlineInterface) {
2729
- this.readlineInterface.close();
2730
- this.readlineInterface = null;
2731
- }
2732
- if (!this.isClosing) {
2949
+ this.stdoutDecoder.reset();
2950
+ if (!this.isClosing && !this.processError) {
2733
2951
  this.processError = new ProcessExitError(
2734
2952
  describeExitError(code, signal),
2735
2953
  {
@@ -2768,7 +2986,7 @@ var ManagedProcessImpl = class {
2768
2986
  return;
2769
2987
  }
2770
2988
  const childPid = this._childProcess?.pid;
2771
- stdin.write(`${message}
2989
+ stdin.write(`${escapeJsonLineSeparators(message)}
2772
2990
  `, (error) => {
2773
2991
  if (error) {
2774
2992
  logTransportMessage(
@@ -2858,11 +3076,9 @@ var ManagedProcessImpl = class {
2858
3076
  }
2859
3077
  });
2860
3078
  }
3079
+ processToClose.stdout?.off("data", this.stdoutDataHandler);
2861
3080
  }
2862
- if (this.readlineInterface) {
2863
- this.readlineInterface.close();
2864
- this.readlineInterface = null;
2865
- }
3081
+ this.stdoutDecoder.reset();
2866
3082
  this.exitFinalized = true;
2867
3083
  if (this.exitDrainTimer) {
2868
3084
  clearTimeout(this.exitDrainTimer);
@@ -3011,6 +3227,14 @@ async function spawnWithWindowsJobObjectRetry(spawnFactory, options = {}) {
3011
3227
  }
3012
3228
 
3013
3229
  // src/exec-core/process-manager.ts
3230
+ var NATIVE_PROCESS_LIFECYCLE_MESSAGE = "[droid process] native lifecycle";
3231
+ var MAX_SPAWN_ERROR_CODE_LENGTH = 64;
3232
+ function getSpawnErrorCode(error) {
3233
+ if (typeof error !== "object" || error === null || !("code" in error) || typeof error.code !== "string" && typeof error.code !== "number") {
3234
+ return void 0;
3235
+ }
3236
+ return String(error.code).slice(0, MAX_SPAWN_ERROR_CODE_LENGTH);
3237
+ }
3014
3238
  function buildArgs(config) {
3015
3239
  const args = [];
3016
3240
  if (config.mode === "acp" /* Acp */) {
@@ -3029,6 +3253,13 @@ function buildArgs(config) {
3029
3253
  }
3030
3254
  return args;
3031
3255
  }
3256
+ function ensureIpcStdio(stdio) {
3257
+ if (Array.isArray(stdio)) {
3258
+ return stdio.includes("ipc") ? stdio : [...stdio, "ipc"];
3259
+ }
3260
+ const standardChannel = stdio ?? "pipe";
3261
+ return [standardChannel, standardChannel, standardChannel, "ipc"];
3262
+ }
3032
3263
  var DroidProcessManager = class {
3033
3264
  deps;
3034
3265
  transportLoggerDeps;
@@ -3109,6 +3340,7 @@ var DroidProcessManager = class {
3109
3340
  }
3110
3341
  );
3111
3342
  if (!childProcess.stdout || !childProcess.stdin) {
3343
+ childProcess.kill();
3112
3344
  throw new ConnectionError("Failed to get process stdio");
3113
3345
  }
3114
3346
  return new ManagedProcessImpl(
@@ -3155,20 +3387,87 @@ var DroidProcessManager = class {
3155
3387
  };
3156
3388
  }
3157
3389
  invokeNodeSpawn(planned) {
3390
+ const stdio = planned.enableIpc ? ensureIpcStdio(planned.stdio) : planned.stdio ?? ["pipe", "pipe", "pipe"];
3158
3391
  const options = {
3159
3392
  cwd: planned.expandedCwd,
3160
3393
  env: planned.env,
3161
- stdio: planned.stdio ?? (planned.enableIpc ? ["pipe", "pipe", "pipe", "ipc"] : ["pipe", "pipe", "pipe"])
3394
+ stdio
3162
3395
  };
3163
3396
  if (planned.enableIpc) {
3164
3397
  options.serialization = "json";
3165
3398
  }
3166
- return spawn(planned.execPath, planned.args, options);
3399
+ const spawnedAt = Date.now();
3400
+ let childProcess;
3401
+ try {
3402
+ childProcess = spawn(planned.execPath, planned.args, options);
3403
+ } catch (error) {
3404
+ const failedAt = Date.now();
3405
+ this.logNativeProcessLifecycle({
3406
+ state: "spawn_error",
3407
+ pid: null,
3408
+ createdAt: spawnedAt,
3409
+ eventDate: new Date(failedAt).toISOString(),
3410
+ durationMs: failedAt - spawnedAt,
3411
+ errnoCode: getSpawnErrorCode(error)
3412
+ });
3413
+ throw error;
3414
+ }
3415
+ const pid = childProcess.pid;
3416
+ if (pid === void 0) {
3417
+ childProcess.once("error", (error) => {
3418
+ const failedAt = Date.now();
3419
+ this.logNativeProcessLifecycle({
3420
+ state: "spawn_error",
3421
+ pid: null,
3422
+ createdAt: spawnedAt,
3423
+ eventDate: new Date(failedAt).toISOString(),
3424
+ durationMs: failedAt - spawnedAt,
3425
+ errnoCode: getSpawnErrorCode(error)
3426
+ });
3427
+ });
3428
+ } else {
3429
+ childProcess.once("exit", (exitCode, signal) => {
3430
+ const exitedAt = Date.now();
3431
+ this.logNativeProcessLifecycle({
3432
+ state: "exited",
3433
+ pid,
3434
+ createdAt: spawnedAt,
3435
+ eventDate: new Date(exitedAt).toISOString(),
3436
+ durationMs: exitedAt - spawnedAt,
3437
+ exitCode,
3438
+ signal
3439
+ });
3440
+ });
3441
+ this.logNativeProcessLifecycle({
3442
+ state: "spawned",
3443
+ pid,
3444
+ createdAt: spawnedAt,
3445
+ eventDate: new Date(spawnedAt).toISOString()
3446
+ });
3447
+ }
3448
+ return childProcess;
3449
+ }
3450
+ /**
3451
+ * Emits the content-free native process record consumed by lifecycle
3452
+ * measurement harnesses. `createdAt` is the wall-clock lower bound captured
3453
+ * immediately before spawn; `eventDate` is the wall-clock observation time.
3454
+ */
3455
+ logNativeProcessLifecycle(record) {
3456
+ try {
3457
+ this.logger.info(NATIVE_PROCESS_LIFECYCLE_MESSAGE, record);
3458
+ } catch (loggingError) {
3459
+ void loggingError;
3460
+ logWarn(
3461
+ defaultExecCoreDeps.logger,
3462
+ "[droid process] native lifecycle logger failed"
3463
+ );
3464
+ }
3167
3465
  }
3168
3466
  spawnChildProcess(config) {
3169
3467
  const planned = this.prepareSpawn(config);
3170
3468
  const childProcess = this.invokeNodeSpawn(planned);
3171
3469
  if (!childProcess.stdout || !childProcess.stdin) {
3470
+ childProcess.kill();
3172
3471
  throw new ConnectionError("Failed to get process stdio");
3173
3472
  }
3174
3473
  return {
@@ -3187,12 +3486,20 @@ var ProcessTransport = class {
3187
3486
  options;
3188
3487
  // Store handlers that may be registered before connect() is called
3189
3488
  messageHandler = null;
3489
+ logger;
3490
+ handleMessage = (frame) => {
3491
+ const message = decodeDroidMessage(frame, this.logger);
3492
+ if (message) this.messageHandler?.(message);
3493
+ };
3190
3494
  errorHandler = null;
3191
3495
  metrics;
3496
+ environmentResolver;
3192
3497
  constructor(options = {}, deps = defaultExecCoreDeps) {
3193
3498
  this.options = options;
3194
3499
  const resolved = resolveExecCoreDeps(options.observability, deps);
3500
+ this.logger = resolved.logger;
3195
3501
  this.metrics = resolved.metrics;
3502
+ this.environmentResolver = resolved.environmentResolver;
3196
3503
  this.processManager = new DroidProcessManager(
3197
3504
  {
3198
3505
  observability: options.observability
@@ -3210,13 +3517,21 @@ var ProcessTransport = class {
3210
3517
  mode: "stream-jsonrpc" /* StreamJsonRpc */,
3211
3518
  cwd: this.options.cwd,
3212
3519
  env: this.options.env,
3520
+ trustedEnv: {
3521
+ ...this.options.runtimeSettingsAuthority ? this.environmentResolver.buildRuntimeSettingsAuthorityEnv(
3522
+ this.options.runtimeSettingsAuthority
3523
+ ) : {},
3524
+ ...this.options.trustedEnv
3525
+ },
3213
3526
  extraArgs: this.options.droidExecExtraArgs,
3214
- enableIpc: this.options.enableIpc
3527
+ enableIpc: this.options.enableIpc,
3528
+ stdio: this.options.stdio
3215
3529
  };
3216
3530
  const spawnStart = performance.now();
3217
3531
  let spawnOutcome = "error";
3532
+ let managedProcess;
3218
3533
  try {
3219
- this.managedProcess = process.platform === "win32" ? await this.processManager.spawnWithWindowsRetry(config) : this.processManager.spawn(config);
3534
+ managedProcess = process.platform === "win32" ? await this.processManager.spawnWithWindowsRetry(config) : this.processManager.spawn(config);
3220
3535
  spawnOutcome = "success";
3221
3536
  } finally {
3222
3537
  this.metrics.recordDuration(
@@ -3225,8 +3540,15 @@ var ProcessTransport = class {
3225
3540
  { outcome: spawnOutcome, source: "process_transport" }
3226
3541
  );
3227
3542
  }
3543
+ try {
3544
+ this.options.onSpawned?.(managedProcess.childProcess);
3545
+ } catch (error) {
3546
+ await managedProcess.close();
3547
+ throw error;
3548
+ }
3549
+ this.managedProcess = managedProcess;
3228
3550
  if (this.messageHandler) {
3229
- this.managedProcess.onMessage(this.messageHandler);
3551
+ this.managedProcess.onMessage(this.handleMessage);
3230
3552
  }
3231
3553
  if (this.errorHandler) {
3232
3554
  this.managedProcess.onError(this.errorHandler);
@@ -3236,12 +3558,12 @@ var ProcessTransport = class {
3236
3558
  if (!this.managedProcess) {
3237
3559
  throw new ConnectionError("Transport not connected");
3238
3560
  }
3239
- return this.managedProcess.send(message);
3561
+ return await this.managedProcess.send(JSON.stringify(message));
3240
3562
  }
3241
3563
  onMessage(handler) {
3242
3564
  this.messageHandler = handler;
3243
3565
  if (this.managedProcess) {
3244
- this.managedProcess.onMessage(handler);
3566
+ this.managedProcess.onMessage(this.handleMessage);
3245
3567
  }
3246
3568
  }
3247
3569
  onError(handler) {
@@ -3286,7 +3608,7 @@ async function createTransport(options) {
3286
3608
  ...options.env,
3287
3609
  FACTORY_API_KEY: resolveApiKey(options.apiKey),
3288
3610
  FACTORY_UPSTREAM_CLIENT_TYPE: "sdk" /* SDK */,
3289
- FACTORY_UPSTREAM_SDK: formatSdkClientMetadata(SDK_CLIENT_METADATA)
3611
+ FACTORY_UPSTREAM_SDK: SDK_IDENTITY
3290
3612
  },
3291
3613
  observability: options.observability
3292
3614
  });
@@ -3372,15 +3694,21 @@ import http from "http";
3372
3694
  import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
3373
3695
  import { StreamableHTTPServerTransport } from "@modelcontextprotocol/sdk/server/streamableHttp.js";
3374
3696
  import { z as z2 } from "zod";
3375
- function tool(name, description, inputSchemaOrHandler, handler) {
3697
+ function tool(name, description, inputSchemaOrHandler, handlerOrOptions, maybeOptions) {
3698
+ const options = typeof handlerOrOptions === "function" ? maybeOptions : handlerOrOptions;
3699
+ const annotationFields = options?.annotations && {
3700
+ annotations: options.annotations
3701
+ };
3376
3702
  if (typeof inputSchemaOrHandler === "function") {
3377
3703
  const actualHandler = inputSchemaOrHandler;
3378
3704
  return {
3379
3705
  name,
3380
3706
  description,
3381
- handler: (input) => actualHandler(input)
3707
+ handler: (input) => actualHandler(input),
3708
+ ...annotationFields
3382
3709
  };
3383
3710
  }
3711
+ const handler = typeof handlerOrOptions === "function" ? handlerOrOptions : void 0;
3384
3712
  if (!handler) {
3385
3713
  throw new Error(`Missing handler for SDK MCP tool "${name}"`);
3386
3714
  }
@@ -3389,7 +3717,8 @@ function tool(name, description, inputSchemaOrHandler, handler) {
3389
3717
  name,
3390
3718
  description,
3391
3719
  inputSchema: inputSchemaOrHandler,
3392
- handler: (input) => handler(inputObjectSchema.parse(input))
3720
+ handler: (input) => handler(inputObjectSchema.parse(input)),
3721
+ ...annotationFields
3393
3722
  };
3394
3723
  }
3395
3724
  function isSdkMcpServer(server) {
@@ -3515,7 +3844,8 @@ var SdkMcpServer = class {
3515
3844
  description: sdkTool.description,
3516
3845
  ...sdkTool.inputSchema && {
3517
3846
  inputSchema: sdkTool.inputSchema
3518
- }
3847
+ },
3848
+ ...sdkTool.annotations && { annotations: sdkTool.annotations }
3519
3849
  },
3520
3850
  async (input) => toCallToolResult(await sdkTool.handler(input))
3521
3851
  );
@@ -3717,6 +4047,11 @@ var DroidSession = class _DroidSession {
3717
4047
  this._ensureNotClosed();
3718
4048
  return unwrapResponseResult(await this._client.listMcpTools()).tools;
3719
4049
  }
4050
+ /** Refresh connector discovery. MCP configuration has its own lifecycle. */
4051
+ async refreshConnectorToolCatalog() {
4052
+ this._ensureNotClosed();
4053
+ unwrapResponseResult(await this._client.refreshConnectorToolCatalog());
4054
+ }
3720
4055
  async listTools(params = {}) {
3721
4056
  this._ensureNotClosed();
3722
4057
  return unwrapResponseResult(await this._client.listTools(params)).tools.map(
@@ -3733,9 +4068,9 @@ var DroidSession = class _DroidSession {
3733
4068
  await this._client.authenticateMcpServer(params)
3734
4069
  );
3735
4070
  }
3736
- async listSkills() {
4071
+ async listSkills(params) {
3737
4072
  this._ensureNotClosed();
3738
- return unwrapResponseResult(await this._client.listSkills());
4073
+ return unwrapResponseResult(await this._client.listSkills(params));
3739
4074
  }
3740
4075
  async setSkillDisabled(params) {
3741
4076
  this._ensureNotClosed();
@@ -3760,7 +4095,14 @@ var DroidSession = class _DroidSession {
3760
4095
  }
3761
4096
  async compact(params) {
3762
4097
  const { session, result } = await this._runReplacement(
3763
- () => this._client.compactSession(params ?? {}).then(unwrapResponseResult)
4098
+ () => this._client.compactSession(params ?? {}).then(unwrapResponseResult),
4099
+ { retainSourceWhenSameId: true }
4100
+ );
4101
+ return { session, removedCount: result.removedCount };
4102
+ }
4103
+ async handoff(params) {
4104
+ const { session, result } = await this._runReplacement(
4105
+ () => this._client.compactSession({ ...params, mode: "handoff" }).then(unwrapResponseResult)
3764
4106
  );
3765
4107
  return { session, removedCount: result.removedCount };
3766
4108
  }
@@ -3808,7 +4150,7 @@ var DroidSession = class _DroidSession {
3808
4150
  }
3809
4151
  this._status = "replacing";
3810
4152
  }
3811
- async _runReplacement(operation) {
4153
+ async _runReplacement(operation, options) {
3812
4154
  this._beginReplacement();
3813
4155
  let result;
3814
4156
  try {
@@ -3829,6 +4171,10 @@ var DroidSession = class _DroidSession {
3829
4171
  )
3830
4172
  });
3831
4173
  }
4174
+ if (options?.retainSourceWhenSameId && result.newSessionId === this._sessionId) {
4175
+ this._status = "active";
4176
+ return { session: this, result };
4177
+ }
3832
4178
  return {
3833
4179
  session: await this._loadReplacement(result.newSessionId),
3834
4180
  result
@@ -4308,6 +4654,7 @@ export {
4308
4654
  AskUserRequestSchema,
4309
4655
  AskUserResponseSchema,
4310
4656
  AskUserResultSchema,
4657
+ AssistantMessageRetractedNotificationSchema,
4311
4658
  AssistantTextCompleteNotificationSchema,
4312
4659
  AssistantTextDeltaNotificationSchema,
4313
4660
  AuthenticateMcpServerRequestParamsSchema,
@@ -4359,6 +4706,7 @@ export {
4359
4706
  ConnectionError,
4360
4707
  ConnectionFailureReason,
4361
4708
  ContentBlockSchema,
4709
+ ContextApiError,
4362
4710
  ContextBreakdownCategorySchema,
4363
4711
  GetContextBreakdownResultSchema as ContextBreakdownResultSchema,
4364
4712
  ContextStatsAccuracy,
@@ -4402,6 +4750,7 @@ export {
4402
4750
  ErrorDetailSchema,
4403
4751
  ErrorNotificationSchema,
4404
4752
  ExecToolInfoSchema,
4753
+ ExecToolInfoWithSchemasSchema,
4405
4754
  ExecuteRewindRequestParamsSchema,
4406
4755
  ExecuteRewindRequestSchema,
4407
4756
  ExecuteRewindResponseSchema,
@@ -4495,7 +4844,9 @@ export {
4495
4844
  ListToolsRequestParamsSchema,
4496
4845
  ListToolsRequestSchema,
4497
4846
  ListToolsResponseSchema,
4847
+ ListToolsResponseWithSchemasSchema,
4498
4848
  ListToolsResultSchema,
4849
+ ListToolsResultWithSchemasSchema,
4499
4850
  LoadSessionRequestParamsSchema,
4500
4851
  LoadSessionRequestSchema,
4501
4852
  LoadSessionResponseSchema,
@@ -4550,6 +4901,12 @@ export {
4550
4901
  OutputFormatSchema,
4551
4902
  OutputFormatType,
4552
4903
  PermissionResolvedNotificationSchema,
4904
+ PickOptionsRequestParamsSchema,
4905
+ PickOptionsRequestSchema,
4906
+ PickOptionsResponseSchema,
4907
+ PickOptionsResultSchema,
4908
+ PickOptionsRowSchema,
4909
+ PickOptionsUnavailableError,
4553
4910
  ProcessExitError,
4554
4911
  ProcessTransport,
4555
4912
  ProgressLogEntrySchema,
@@ -4586,6 +4943,12 @@ export {
4586
4943
  SLACK_DELEGATION_MAX_RETRIES,
4587
4944
  SdkMcpServer,
4588
4945
  ServerRequestHandlerType,
4946
+ SessionArtifactAction,
4947
+ SessionArtifactKind,
4948
+ SessionArtifactProvider,
4949
+ SessionArtifactSchema,
4950
+ SessionArtifactSource,
4951
+ SessionArtifactsSchema,
4589
4952
  SessionError,
4590
4953
  SessionMetadataSchema,
4591
4954
  SessionNotFoundError,
@@ -4657,11 +5020,15 @@ export {
4657
5020
  ToolConfirmationListItemSchema,
4658
5021
  ToolConfirmationOutcome,
4659
5022
  ToolConfirmationType,
5023
+ ToolJsonSchemaSchema,
4660
5024
  ToolProgressUpdateNotificationSchema,
4661
5025
  ToolProgressUpdateSchema,
4662
5026
  ToolResultSchema as ToolResultBlockSchema,
4663
5027
  ToolResultNotificationSchema,
5028
+ ToolResultSchemaDescriptorSchema,
5029
+ ToolSchemasSchema,
4664
5030
  ToolSelectionOverridesSchema,
5031
+ ToolSourceSchema,
4665
5032
  ToolUseSchema as ToolUseBlockSchema,
4666
5033
  ToolUseSchema2 as ToolUseSchema,
4667
5034
  TraceContextMetaSchema,
@@ -4683,6 +5050,7 @@ export {
4683
5050
  WorkerStartedEntrySchema,
4684
5051
  WorkerStateInfoSchema,
4685
5052
  __resetTransportLoggerForTests,
5053
+ appendContextSession,
4686
5054
  buildUserMessageContentBlocks,
4687
5055
  configureDaemonCoreDeps,
4688
5056
  connectAndAuthenticate,
@@ -4690,16 +5058,25 @@ export {
4690
5058
  connectWithRetry,
4691
5059
  convertNotificationToStreamMessage,
4692
5060
  createComputer,
5061
+ createContextDocument,
5062
+ createContextFile,
5063
+ createContextSpace,
4693
5064
  createResponseSchema,
4694
5065
  createSdkMcpServer,
4695
5066
  createSession,
4696
5067
  createWebSocketDaemonClient,
4697
5068
  defaultDaemonCoreDeps,
4698
5069
  deleteComputer,
5070
+ deleteContextDocument,
5071
+ deleteContextFile,
4699
5072
  filterMessagesForUI,
4700
5073
  getComputer,
4701
5074
  getComputerByName,
4702
5075
  getComputerMetrics,
5076
+ getContextDocument,
5077
+ getContextDocumentByPath,
5078
+ getContextSession,
5079
+ getContextSpace,
4703
5080
  getMachineTemplate,
4704
5081
  getPermissionToolInputForDisplay,
4705
5082
  getQueuedUserMessageDisplayGroup,
@@ -4716,6 +5093,9 @@ export {
4716
5093
  isReviewableQueuedMessageKind,
4717
5094
  isTransportLoggingEnabled,
4718
5095
  listComputers,
5096
+ listContextDocuments,
5097
+ listContextSessions,
5098
+ listContextSpaces,
4719
5099
  listMachineTemplates,
4720
5100
  listModels,
4721
5101
  listRemoteSessions,
@@ -4723,10 +5103,11 @@ export {
4723
5103
  logTransportMessage,
4724
5104
  machineTypeToMachineConnectionType,
4725
5105
  noopLogger,
4726
- orderMessagesByParentChain,
5106
+ readContextFilePart,
5107
+ readContextSessionRecords,
4727
5108
  recordComputerConnectSli,
4728
5109
  refreshComputer,
4729
- repairParentChainCorruption,
5110
+ registerContextSession,
4730
5111
  resolveExecCoreDeps,
4731
5112
  restartComputer,
4732
5113
  resumeSession,
@@ -4738,6 +5119,9 @@ export {
4738
5119
  summarizeSessionToolUsage,
4739
5120
  toError,
4740
5121
  tool,
4741
- updateComputer
5122
+ updateComputer,
5123
+ updateContextDocument,
5124
+ uploadContextFilePart,
5125
+ withJsonRpcCodec
4742
5126
  };
4743
5127
  //# sourceMappingURL=node.mjs.map