happy-imou-cloud 2.0.18 → 2.0.20

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 (33) hide show
  1. package/bin/happy-cloud.mjs +1 -1
  2. package/dist/{BaseReasoningProcessor-DrAN-2bw.cjs → BaseReasoningProcessor-B9z785Pi.cjs} +5 -3
  3. package/dist/{BaseReasoningProcessor-DajL_jtA.mjs → BaseReasoningProcessor-iTk5q5Vy.mjs} +5 -3
  4. package/dist/{ProviderSelectionHandler-BOAcP_CE.cjs → ProviderSelectionHandler-DUBEXkmo.cjs} +2 -2
  5. package/dist/{ProviderSelectionHandler-DiHuEbf_.mjs → ProviderSelectionHandler-s79sTquD.mjs} +2 -2
  6. package/dist/{api-eDp10nsY.cjs → api-BKnzORe4.cjs} +748 -41
  7. package/dist/{api-DLI3Zloa.mjs → api-_Y2kzqZL.mjs} +748 -41
  8. package/dist/{command-BJu-MaCp.cjs → command-BBNzMWEG.cjs} +4 -4
  9. package/dist/{command-BYxsn0_c.mjs → command-BDkgHdQU.mjs} +4 -4
  10. package/dist/{index-bzGCZJLB.cjs → index-0TIyXMQu.cjs} +246 -27
  11. package/dist/{index-CjWtQyJu.mjs → index-Bg4KLXYY.mjs} +243 -24
  12. package/dist/index.cjs +3 -3
  13. package/dist/index.mjs +3 -3
  14. package/dist/lib.cjs +2 -2
  15. package/dist/lib.d.cts +1054 -0
  16. package/dist/lib.d.mts +1054 -0
  17. package/dist/lib.mjs +2 -2
  18. package/dist/{persistence-DicQpgl6.mjs → persistence-BCkHc68b.mjs} +1 -1
  19. package/dist/{persistence-CSaoFYvt.cjs → persistence-D5uolhHo.cjs} +1 -1
  20. package/dist/{registerKillSessionHandler-BeMsOt9D.cjs → registerKillSessionHandler-BMUE5Oaj.cjs} +3 -3
  21. package/dist/{registerKillSessionHandler-BWCjaf5S.mjs → registerKillSessionHandler-DJMH-gar.mjs} +3 -3
  22. package/dist/{runClaude-BaDVGV4k.mjs → runClaude-BMv-eao6.mjs} +5 -5
  23. package/dist/{runClaude-CERpGT0L.cjs → runClaude-BUhD2jR2.cjs} +5 -5
  24. package/dist/{runCodex-Bik9wE7F.mjs → runCodex-DwnLnXWK.mjs} +129 -15
  25. package/dist/{runCodex-Z12g3g5P.cjs → runCodex-DyNSDN6X.cjs} +129 -15
  26. package/dist/{runGemini-Chas-o_m.cjs → runGemini-UKpRbyNY.cjs} +6 -6
  27. package/dist/{runGemini-Bh59hP_q.mjs → runGemini-uVHDcJ09.mjs} +6 -6
  28. package/package.json +1 -1
  29. package/scripts/build.mjs +66 -66
  30. package/scripts/devtools/README.md +9 -9
  31. package/scripts/e2e/fake-codex-acp-agent.mjs +139 -139
  32. package/scripts/e2e/local-server-session-roundtrip.mjs +1063 -1063
  33. package/scripts/release-smoke.mjs +28 -4
@@ -6,9 +6,9 @@ var fs$1 = require('fs');
6
6
  var fs = require('node:fs');
7
7
  var os = require('node:os');
8
8
  var path = require('node:path');
9
+ var z = require('zod');
9
10
  var node_events = require('node:events');
10
11
  var socket_ioClient = require('socket.io-client');
11
- var z = require('zod');
12
12
  var node_crypto = require('node:crypto');
13
13
  var tweetnacl = require('tweetnacl');
14
14
  var fs$2 = require('fs/promises');
@@ -18,7 +18,7 @@ var node_child_process = require('node:child_process');
18
18
  var expoServerSdk = require('expo-server-sdk');
19
19
 
20
20
  var name = "happy-imou-cloud";
21
- var version = "2.0.18";
21
+ var version = "2.0.20";
22
22
  var description = "hicloud - Imou 企业定制版。关键是 happy!移动端远程 AI 编程工具,支持 Claude Code、Codex 和 Gemini CLI";
23
23
  var author = "long.zhu";
24
24
  var license = "MIT";
@@ -432,7 +432,7 @@ async function listDaemonLogFiles(limit = 50) {
432
432
  return { file, path: fullPath, modified: stats.mtime };
433
433
  }).sort((a, b) => b.modified.getTime() - a.modified.getTime());
434
434
  try {
435
- const { readDaemonState } = await Promise.resolve().then(function () { return require('./persistence-CSaoFYvt.cjs'); });
435
+ const { readDaemonState } = await Promise.resolve().then(function () { return require('./persistence-D5uolhHo.cjs'); });
436
436
  const state = await readDaemonState();
437
437
  if (!state) {
438
438
  return logs;
@@ -566,16 +566,54 @@ function decrypt(key, variant, data) {
566
566
  }
567
567
  }
568
568
 
569
+ const SessionRuntimeIndexSchema = z.z.object({
570
+ machineId: z.z.string().nullish(),
571
+ hostPid: z.z.number().int().nullish(),
572
+ startedBy: z.z.string().nullish(),
573
+ lifecycleState: z.z.string().nullish(),
574
+ flavor: z.z.string().nullish()
575
+ });
576
+ const ProtocolV3DescriptorSchema = z.z.object({
577
+ protocolVersion: z.z.string(),
578
+ serverVersion: z.z.string().optional(),
579
+ capabilities: z.z.array(z.z.string()),
580
+ legacyFallback: z.z.object({
581
+ http: z.z.array(z.z.string()),
582
+ websocketPath: z.z.string()
583
+ }).optional()
584
+ });
585
+ const ProtocolV3CapabilitiesResponseSchema = z.z.object({
586
+ protocol: ProtocolV3DescriptorSchema
587
+ });
569
588
  const SessionMessageContentSchema = z.z.object({
570
589
  c: z.z.string(),
571
590
  // Base64 encoded encrypted content
572
591
  t: z.z.literal("encrypted")
573
592
  });
593
+ const NewSessionBodySchema = z.z.object({
594
+ t: z.z.literal("new-session"),
595
+ id: z.z.string(),
596
+ seq: z.z.number(),
597
+ title: z.z.string().nullable().optional(),
598
+ metadata: z.z.string(),
599
+ metadataVersion: z.z.number(),
600
+ agentState: z.z.string().nullable(),
601
+ agentStateVersion: z.z.number(),
602
+ dataEncryptionKey: z.z.string().nullable().optional(),
603
+ active: z.z.boolean().optional(),
604
+ activeAt: z.z.number().optional(),
605
+ createdAt: z.z.number().optional(),
606
+ updatedAt: z.z.number().optional(),
607
+ sessionIndex: SessionRuntimeIndexSchema.nullish()
608
+ });
574
609
  const UpdateBodySchema = z.z.object({
575
610
  message: z.z.object({
576
611
  id: z.z.string(),
577
612
  seq: z.z.number(),
578
- content: SessionMessageContentSchema
613
+ content: SessionMessageContentSchema,
614
+ localId: z.z.string().nullable().optional(),
615
+ createdAt: z.z.number().optional(),
616
+ updatedAt: z.z.number().optional()
579
617
  }),
580
618
  sid: z.z.string(),
581
619
  // Session ID
@@ -583,16 +621,25 @@ const UpdateBodySchema = z.z.object({
583
621
  });
584
622
  const UpdateSessionBodySchema = z.z.object({
585
623
  t: z.z.literal("update-session"),
586
- sid: z.z.string(),
624
+ sid: z.z.string().optional(),
625
+ id: z.z.string().optional(),
626
+ changeSeq: z.z.number().optional(),
627
+ title: z.z.string().nullable().optional(),
587
628
  metadata: z.z.object({
588
629
  version: z.z.number(),
589
630
  value: z.z.string()
590
631
  }).nullish(),
632
+ sessionIndex: SessionRuntimeIndexSchema.nullish(),
591
633
  agentState: z.z.object({
592
634
  version: z.z.number(),
593
635
  value: z.z.string()
594
636
  }).nullish()
595
637
  });
638
+ const DeleteSessionBodySchema = z.z.object({
639
+ t: z.z.literal("delete-session"),
640
+ sid: z.z.string(),
641
+ changeSeq: z.z.number().optional()
642
+ });
596
643
  const UpdateMachineBodySchema = z.z.object({
597
644
  t: z.z.literal("update-machine"),
598
645
  machineId: z.z.string(),
@@ -609,9 +656,11 @@ z.z.object({
609
656
  id: z.z.string(),
610
657
  seq: z.z.number(),
611
658
  body: z.z.union([
659
+ NewSessionBodySchema,
612
660
  UpdateBodySchema,
613
661
  UpdateSessionBodySchema,
614
- UpdateMachineBodySchema
662
+ UpdateMachineBodySchema,
663
+ DeleteSessionBodySchema
615
664
  ]),
616
665
  createdAt: z.z.number()
617
666
  });
@@ -646,6 +695,87 @@ z.z.object({
646
695
  seq: z.z.number(),
647
696
  updatedAt: z.z.number()
648
697
  });
698
+ const ProtocolV3SessionSchema = z.z.object({
699
+ id: z.z.string(),
700
+ lastChangeSeq: z.z.number(),
701
+ title: z.z.string().nullable().optional(),
702
+ metadata: z.z.string().optional(),
703
+ metadataVersion: z.z.number().optional(),
704
+ agentState: z.z.string().nullable().optional(),
705
+ agentStateVersion: z.z.number().optional(),
706
+ dataEncryptionKey: z.z.string().nullable().optional(),
707
+ active: z.z.boolean().optional(),
708
+ activeAt: z.z.number().optional(),
709
+ pendingCount: z.z.number().optional(),
710
+ pendingVersion: z.z.number().optional(),
711
+ createdAt: z.z.number().optional(),
712
+ updatedAt: z.z.number().optional(),
713
+ deleted: z.z.boolean().optional()
714
+ });
715
+ const ProtocolV3SessionMessageSchema = z.z.object({
716
+ id: z.z.string(),
717
+ seq: z.z.number(),
718
+ localId: z.z.string().nullable(),
719
+ content: SessionMessageContentSchema,
720
+ createdAt: z.z.number(),
721
+ updatedAt: z.z.number()
722
+ });
723
+ const ProtocolV3SessionSnapshotSchema = z.z.object({
724
+ messages: z.z.array(ProtocolV3SessionMessageSchema),
725
+ truncated: z.z.boolean(),
726
+ oldestRetainedSeq: z.z.number().nullable()
727
+ });
728
+ const ProtocolV3SessionSnapshotResponseSchema = z.z.object({
729
+ protocol: ProtocolV3DescriptorSchema,
730
+ session: ProtocolV3SessionSchema,
731
+ snapshot: ProtocolV3SessionSnapshotSchema
732
+ });
733
+ const ProtocolV3SessionChangeSchema = z.z.object({
734
+ changeSeq: z.z.number(),
735
+ changeType: z.z.string(),
736
+ createdAt: z.z.number(),
737
+ payload: z.z.record(z.z.string(), z.z.unknown())
738
+ });
739
+ z.z.object({
740
+ protocol: ProtocolV3DescriptorSchema,
741
+ connectionType: z.z.enum(["session-scoped", "user-scoped", "machine-scoped"])
742
+ });
743
+ z.z.object({
744
+ sessionId: z.z.string(),
745
+ change: ProtocolV3SessionChangeSchema
746
+ });
747
+ const SessionListItemSchema = z.z.object({
748
+ id: z.z.string(),
749
+ seq: z.z.number(),
750
+ title: z.z.string().nullable().optional(),
751
+ createdAt: z.z.number(),
752
+ updatedAt: z.z.number(),
753
+ active: z.z.boolean(),
754
+ activeAt: z.z.number(),
755
+ metadata: z.z.string(),
756
+ metadataVersion: z.z.number(),
757
+ agentState: z.z.string().nullable(),
758
+ agentStateVersion: z.z.number(),
759
+ dataEncryptionKey: z.z.string().nullable().optional(),
760
+ pendingCount: z.z.number().optional(),
761
+ pendingVersion: z.z.number().optional(),
762
+ lastMessage: z.z.unknown().nullable().optional(),
763
+ sessionIndex: SessionRuntimeIndexSchema.nullish()
764
+ });
765
+ const SessionListResponseSchema = z.z.object({
766
+ sessions: z.z.array(SessionListItemSchema)
767
+ });
768
+ const ProtocolV3SessionChangesCursorSchema = z.z.object({
769
+ afterSeq: z.z.number(),
770
+ lastChangeSeq: z.z.number(),
771
+ hasMore: z.z.boolean()
772
+ });
773
+ const ProtocolV3SessionChangesResponseSchema = z.z.object({
774
+ protocol: ProtocolV3DescriptorSchema,
775
+ session: ProtocolV3SessionSchema,
776
+ cursor: ProtocolV3SessionChangesCursorSchema,
777
+ changes: z.z.array(ProtocolV3SessionChangeSchema)
778
+ });
649
779
  const MessageMetaSchema = z.z.object({
650
780
  sentFrom: z.z.string().optional(),
651
781
  // Source identifier
@@ -697,6 +827,27 @@ const AgentMessageSchema = z.z.object({
697
827
  });
698
828
  z.z.union([UserMessageSchema, AgentMessageSchema]);
699
829
 
830
+ function buildSessionRuntimeIndex(metadata) {
831
+ if (!metadata) {
832
+ return null;
833
+ }
834
+ const machineId = typeof metadata.machineId === "string" && metadata.machineId ? metadata.machineId : null;
835
+ const hostPid = typeof metadata.hostPid === "number" && Number.isInteger(metadata.hostPid) ? metadata.hostPid : null;
836
+ const startedBy = typeof metadata.startedBy === "string" && metadata.startedBy ? metadata.startedBy : null;
837
+ const lifecycleState = typeof metadata.lifecycleState === "string" && metadata.lifecycleState ? metadata.lifecycleState : null;
838
+ const flavor = typeof metadata.flavor === "string" && metadata.flavor ? metadata.flavor : null;
839
+ if (!machineId && hostPid === null && !startedBy && !lifecycleState && !flavor) {
840
+ return null;
841
+ }
842
+ return {
843
+ ...machineId ? { machineId } : {},
844
+ ...hostPid !== null ? { hostPid } : {},
845
+ ...startedBy ? { startedBy } : {},
846
+ ...lifecycleState ? { lifecycleState } : {},
847
+ ...flavor ? { flavor } : {}
848
+ };
849
+ }
850
+
700
851
  async function delay(ms) {
701
852
  return new Promise((resolve) => setTimeout(resolve, ms));
702
853
  }
@@ -1503,6 +1654,9 @@ function buildSocketAuth(opts) {
1503
1654
 
1504
1655
  const MAX_PENDING_RELIABLE_CODEX_MESSAGES = 200;
1505
1656
  const MAX_PENDING_RELIABLE_CODEX_MESSAGE_BYTES = 512 * 1024;
1657
+ const PROTOCOL_V3_INITIAL_SNAPSHOT_LIMIT = 150;
1658
+ const PROTOCOL_V3_CHANGES_PAGE_LIMIT = 200;
1659
+ const PROTOCOL_V3_CAPABILITIES_WAIT_MS = 250;
1506
1660
  class ApiSessionClient extends node_events.EventEmitter {
1507
1661
  credentials;
1508
1662
  sessionId;
@@ -1522,6 +1676,15 @@ class ApiSessionClient extends node_events.EventEmitter {
1522
1676
  pendingReliableCodexMessageBytes = 0;
1523
1677
  reconnectAfterServerDisconnectTimer = null;
1524
1678
  lastSocketServerError = null;
1679
+ protocolV3SessionSync = null;
1680
+ protocolV3SessionSyncGeneration = 0;
1681
+ protocolV3SessionSyncInProgress = false;
1682
+ bufferedLiveSessionEventsDuringProtocolSync = [];
1683
+ initialProtocolV3SnapshotComplete = false;
1684
+ lastChangeSeq;
1685
+ protocolV3Descriptor = null;
1686
+ protocolV3SocketCapabilities = null;
1687
+ protocolV3SessionSyncWaitTimer = null;
1525
1688
  constructor(credentials, session) {
1526
1689
  super();
1527
1690
  this.credentials = credentials;
@@ -1532,6 +1695,7 @@ class ApiSessionClient extends node_events.EventEmitter {
1532
1695
  this.agentStateVersion = session.agentStateVersion;
1533
1696
  this.encryptionKey = session.encryptionKey;
1534
1697
  this.encryptionVariant = session.encryptionVariant;
1698
+ this.lastChangeSeq = session.seq;
1535
1699
  this.rpcHandlerManager = new RpcHandlerManager({
1536
1700
  scopePrefix: this.sessionId,
1537
1701
  encryptionKey: this.encryptionKey,
@@ -1560,6 +1724,9 @@ class ApiSessionClient extends node_events.EventEmitter {
1560
1724
  this.lastSocketServerError = null;
1561
1725
  this.rpcHandlerManager.onSocketConnect(this.socket);
1562
1726
  this.flushReliableCodexMessages();
1727
+ this.protocolV3Descriptor = null;
1728
+ this.protocolV3SocketCapabilities = null;
1729
+ this.scheduleProtocolV3SessionSync();
1563
1730
  });
1564
1731
  this.socket.on("rpc-request", async (data, callback) => {
1565
1732
  callback(await this.rpcHandlerManager.handleRequest(data));
@@ -1567,6 +1734,7 @@ class ApiSessionClient extends node_events.EventEmitter {
1567
1734
  this.socket.on("disconnect", (reason) => {
1568
1735
  logger.debug("[API] Socket disconnected:", reason);
1569
1736
  this.rpcHandlerManager.onSocketDisconnect();
1737
+ this.invalidateProtocolV3SessionSync();
1570
1738
  this.retryAfterServerDisconnect(reason);
1571
1739
  });
1572
1740
  this.socket.on("connect_error", (error) => {
@@ -1576,43 +1744,42 @@ class ApiSessionClient extends node_events.EventEmitter {
1576
1744
  this.socket.on("update", (data) => {
1577
1745
  try {
1578
1746
  logger.debugLargeJson("[SOCKET] [UPDATE] Received update:", data);
1579
- if (!data.body) {
1580
- logger.debug("[SOCKET] [UPDATE] [ERROR] No body in update!");
1747
+ if (this.protocolV3SessionSyncInProgress) {
1748
+ this.bufferLiveSessionEvent({ type: "update", payload: data });
1581
1749
  return;
1582
1750
  }
1583
- if (data.body.t === "new-message" && data.body.message.content.t === "encrypted") {
1584
- const body = decrypt(this.encryptionKey, this.encryptionVariant, decodeBase64(data.body.message.content.c));
1585
- logger.debugLargeJson("[SOCKET] [UPDATE] Received update:", body);
1586
- const userResult = UserMessageSchema.safeParse(body);
1587
- if (userResult.success) {
1588
- if (this.pendingMessageCallback) {
1589
- this.pendingMessageCallback(userResult.data);
1590
- } else {
1591
- this.pendingMessages.push(userResult.data);
1592
- }
1593
- } else {
1594
- this.emit("message", body);
1595
- }
1596
- } else if (data.body.t === "update-session") {
1597
- if (data.body.metadata && data.body.metadata.version > this.metadataVersion) {
1598
- this.metadata = decrypt(this.encryptionKey, this.encryptionVariant, decodeBase64(data.body.metadata.value));
1599
- this.metadataVersion = data.body.metadata.version;
1600
- this.emit("metadata-updated", this.metadata);
1601
- }
1602
- if (data.body.agentState && data.body.agentState.version > this.agentStateVersion) {
1603
- this.agentState = data.body.agentState.value ? decrypt(this.encryptionKey, this.encryptionVariant, decodeBase64(data.body.agentState.value)) : null;
1604
- this.agentStateVersion = data.body.agentState.version;
1605
- this.emit("agent-state-updated", this.agentState);
1606
- }
1607
- } else if (data.body.t === "update-machine") {
1608
- logger.debug(`[SOCKET] WARNING: Session client received unexpected machine update - ignoring`);
1609
- } else {
1610
- this.emit("message", data.body);
1611
- }
1751
+ this.handleSocketUpdate(data);
1612
1752
  } catch (error) {
1613
1753
  logger.debug("[SOCKET] [UPDATE] [ERROR] Error handling update", { error });
1614
1754
  }
1615
1755
  });
1756
+ this.socket.on("capabilities", (data) => {
1757
+ try {
1758
+ this.protocolV3SocketCapabilities = data;
1759
+ this.protocolV3Descriptor = data.protocol;
1760
+ logger.debug("[API] Received websocket protocol capabilities", {
1761
+ sessionId: this.sessionId,
1762
+ connectionType: data.connectionType,
1763
+ capabilities: data.protocol.capabilities
1764
+ });
1765
+ this.clearProtocolV3SessionSyncWaitTimer();
1766
+ this.scheduleProtocolV3SessionSync({ waitForCapabilities: false });
1767
+ } catch (error) {
1768
+ logger.debug("[SOCKET] [CAPABILITIES] [ERROR] Error handling capabilities event", { error });
1769
+ }
1770
+ });
1771
+ this.socket.on("session-change", (data) => {
1772
+ try {
1773
+ logger.debugLargeJson("[SOCKET] [SESSION-CHANGE] Received session change:", data);
1774
+ if (this.protocolV3SessionSyncInProgress) {
1775
+ this.bufferLiveSessionEvent({ type: "session-change", payload: data });
1776
+ return;
1777
+ }
1778
+ this.handleProtocolV3SessionChangeEvent(data);
1779
+ } catch (error) {
1780
+ logger.debug("[SOCKET] [SESSION-CHANGE] [ERROR] Error handling session change", { error });
1781
+ }
1782
+ });
1616
1783
  this.socket.on("error", (error) => {
1617
1784
  logger.debug("[API] Socket error:", error);
1618
1785
  this.lastSocketServerError = this.normalizeSocketError(error);
@@ -1625,6 +1792,20 @@ class ApiSessionClient extends node_events.EventEmitter {
1625
1792
  callback(this.pendingMessages.shift());
1626
1793
  }
1627
1794
  }
1795
+ configureProtocolV3Sync(options) {
1796
+ this.invalidateProtocolV3SessionSync();
1797
+ this.protocolV3SessionSync = options;
1798
+ this.initialProtocolV3SnapshotComplete = false;
1799
+ if (this.protocolV3SessionSync && this.socket.connected) {
1800
+ this.scheduleProtocolV3SessionSync();
1801
+ }
1802
+ }
1803
+ getProtocolV3DescriptorSnapshot() {
1804
+ return this.protocolV3Descriptor;
1805
+ }
1806
+ getProtocolV3SocketCapabilitiesSnapshot() {
1807
+ return this.protocolV3SocketCapabilities;
1808
+ }
1628
1809
  getMetadataSnapshot() {
1629
1810
  return this.metadata;
1630
1811
  }
@@ -1783,11 +1964,13 @@ class ApiSessionClient extends node_events.EventEmitter {
1783
1964
  if (process.env.DEBUG) {
1784
1965
  logger.debug(`[API] Sending keep alive message: ${thinking}`);
1785
1966
  }
1967
+ const sessionIndex = buildSessionRuntimeIndex(this.metadata);
1786
1968
  this.socket.volatile.emit("session-alive", {
1787
1969
  sid: this.sessionId,
1788
1970
  time: Date.now(),
1789
1971
  thinking,
1790
- mode
1972
+ mode,
1973
+ ...sessionIndex ? { sessionIndex } : {}
1791
1974
  });
1792
1975
  }
1793
1976
  /**
@@ -1829,7 +2012,13 @@ class ApiSessionClient extends node_events.EventEmitter {
1829
2012
  this.metadataLock.inLock(async () => {
1830
2013
  await backoff(async () => {
1831
2014
  let updated = handler(this.metadata);
1832
- const answer = await this.socket.emitWithAck("update-metadata", { sid: this.sessionId, expectedVersion: this.metadataVersion, metadata: encodeBase64(encrypt(this.encryptionKey, this.encryptionVariant, updated)) });
2015
+ const sessionIndex = buildSessionRuntimeIndex(updated);
2016
+ const answer = await this.socket.emitWithAck("update-metadata", {
2017
+ sid: this.sessionId,
2018
+ expectedVersion: this.metadataVersion,
2019
+ metadata: encodeBase64(encrypt(this.encryptionKey, this.encryptionVariant, updated)),
2020
+ ...sessionIndex ? { sessionIndex } : {}
2021
+ });
1833
2022
  if (answer.result === "success") {
1834
2023
  this.metadata = decrypt(this.encryptionKey, this.encryptionVariant, decodeBase64(answer.metadata));
1835
2024
  this.metadataVersion = answer.version;
@@ -1890,8 +2079,280 @@ class ApiSessionClient extends node_events.EventEmitter {
1890
2079
  async close() {
1891
2080
  logger.debug("[API] socket.close() called");
1892
2081
  this.clearReconnectAfterServerDisconnectTimer();
2082
+ this.invalidateProtocolV3SessionSync();
1893
2083
  this.socket.close();
1894
2084
  }
2085
+ handleSocketUpdate(data) {
2086
+ if (!data.body) {
2087
+ logger.debug("[SOCKET] [UPDATE] [ERROR] No body in update!");
2088
+ return;
2089
+ }
2090
+ if (data.body.t === "new-message" && data.body.message.content.t === "encrypted") {
2091
+ const messageSeq = typeof data.body.message.seq === "number" ? data.body.message.seq : null;
2092
+ if (messageSeq !== null && messageSeq <= this.lastChangeSeq) {
2093
+ return;
2094
+ }
2095
+ this.dispatchEncryptedSessionMessage(data.body.message.content.c);
2096
+ if (messageSeq !== null) {
2097
+ this.lastChangeSeq = Math.max(this.lastChangeSeq, messageSeq);
2098
+ }
2099
+ return;
2100
+ }
2101
+ if (data.body.t === "update-session") {
2102
+ const sessionBody = data.body;
2103
+ const changeSeq = typeof sessionBody.changeSeq === "number" ? sessionBody.changeSeq : null;
2104
+ if (changeSeq !== null && changeSeq <= this.lastChangeSeq) {
2105
+ return;
2106
+ }
2107
+ if (sessionBody.metadata && sessionBody.metadata.version > this.metadataVersion) {
2108
+ this.metadata = decrypt(this.encryptionKey, this.encryptionVariant, decodeBase64(sessionBody.metadata.value));
2109
+ this.metadataVersion = sessionBody.metadata.version;
2110
+ this.emit("metadata-updated", this.metadata);
2111
+ }
2112
+ if (sessionBody.agentState && sessionBody.agentState.version > this.agentStateVersion) {
2113
+ this.agentState = sessionBody.agentState.value ? decrypt(this.encryptionKey, this.encryptionVariant, decodeBase64(sessionBody.agentState.value)) : null;
2114
+ this.agentStateVersion = sessionBody.agentState.version;
2115
+ this.emit("agent-state-updated", this.agentState);
2116
+ }
2117
+ if (changeSeq !== null) {
2118
+ this.lastChangeSeq = Math.max(this.lastChangeSeq, changeSeq);
2119
+ }
2120
+ return;
2121
+ }
2122
+ if (data.body.t === "update-machine") {
2123
+ logger.debug(`[SOCKET] WARNING: Session client received unexpected machine update - ignoring`);
2124
+ return;
2125
+ }
2126
+ if (data.body.t === "delete-session") {
2127
+ const deleteBody = data.body;
2128
+ const changeSeq = typeof deleteBody.changeSeq === "number" ? deleteBody.changeSeq : null;
2129
+ if (changeSeq !== null) {
2130
+ if (changeSeq <= this.lastChangeSeq) {
2131
+ return;
2132
+ }
2133
+ this.lastChangeSeq = Math.max(this.lastChangeSeq, changeSeq);
2134
+ }
2135
+ }
2136
+ this.emit("message", data.body);
2137
+ }
2138
+ dispatchEncryptedSessionMessage(encryptedMessage) {
2139
+ const body = decrypt(this.encryptionKey, this.encryptionVariant, decodeBase64(encryptedMessage));
2140
+ logger.debugLargeJson("[SOCKET] [UPDATE] Received update:", body);
2141
+ const userResult = UserMessageSchema.safeParse(body);
2142
+ if (userResult.success) {
2143
+ if (this.pendingMessageCallback) {
2144
+ this.pendingMessageCallback(userResult.data);
2145
+ } else {
2146
+ this.pendingMessages.push(userResult.data);
2147
+ }
2148
+ return;
2149
+ }
2150
+ this.emit("message", body);
2151
+ }
2152
+ invalidateProtocolV3SessionSync() {
2153
+ this.protocolV3SessionSyncGeneration += 1;
2154
+ this.protocolV3SessionSyncInProgress = false;
2155
+ this.bufferedLiveSessionEventsDuringProtocolSync = [];
2156
+ this.protocolV3Descriptor = null;
2157
+ this.protocolV3SocketCapabilities = null;
2158
+ this.clearProtocolV3SessionSyncWaitTimer();
2159
+ }
2160
+ scheduleProtocolV3SessionSync(options) {
2161
+ if (!this.protocolV3SessionSync || !this.socket.connected) {
2162
+ return;
2163
+ }
2164
+ const shouldWaitForCapabilities = options?.waitForCapabilities ?? (Boolean(this.credentials.signing) && this.protocolV3Descriptor === null);
2165
+ if (shouldWaitForCapabilities) {
2166
+ this.clearProtocolV3SessionSyncWaitTimer();
2167
+ this.protocolV3SessionSyncWaitTimer = setTimeout(() => {
2168
+ this.protocolV3SessionSyncWaitTimer = null;
2169
+ void this.runProtocolV3SessionSync();
2170
+ }, PROTOCOL_V3_CAPABILITIES_WAIT_MS);
2171
+ return;
2172
+ }
2173
+ void this.runProtocolV3SessionSync();
2174
+ }
2175
+ clearProtocolV3SessionSyncWaitTimer() {
2176
+ if (!this.protocolV3SessionSyncWaitTimer) {
2177
+ return;
2178
+ }
2179
+ clearTimeout(this.protocolV3SessionSyncWaitTimer);
2180
+ this.protocolV3SessionSyncWaitTimer = null;
2181
+ }
2182
+ async runProtocolV3SessionSync() {
2183
+ if (!this.protocolV3SessionSync || this.protocolV3SessionSyncInProgress || !this.socket.connected) {
2184
+ return;
2185
+ }
2186
+ if (this.protocolV3Descriptor && !this.supportsKnownProtocolCapability("session_changes_v3")) {
2187
+ logger.debug("[API] Skipping protocol v3 session catch-up because websocket capabilities do not advertise session_changes_v3", {
2188
+ sessionId: this.sessionId
2189
+ });
2190
+ return;
2191
+ }
2192
+ const generation = this.protocolV3SessionSyncGeneration + 1;
2193
+ this.protocolV3SessionSyncGeneration = generation;
2194
+ this.protocolV3SessionSyncInProgress = true;
2195
+ this.bufferedLiveSessionEventsDuringProtocolSync = [];
2196
+ try {
2197
+ logger.debug("[API] Starting protocol v3 session catch-up", {
2198
+ sessionId: this.sessionId,
2199
+ lastChangeSeq: this.lastChangeSeq
2200
+ });
2201
+ if (!this.initialProtocolV3SnapshotComplete && this.lastChangeSeq === 0 && (!this.protocolV3Descriptor || this.supportsKnownProtocolCapability("session_snapshot_v3"))) {
2202
+ const snapshot = await this.protocolV3SessionSync.getSessionSnapshot(
2203
+ this.sessionId,
2204
+ this.protocolV3SessionSync.snapshotLimit ?? PROTOCOL_V3_INITIAL_SNAPSHOT_LIMIT
2205
+ );
2206
+ if (!this.isActiveProtocolV3SessionSync(generation)) {
2207
+ return;
2208
+ }
2209
+ if (snapshot) {
2210
+ this.applyProtocolV3Snapshot(snapshot);
2211
+ this.initialProtocolV3SnapshotComplete = true;
2212
+ }
2213
+ }
2214
+ while (this.isActiveProtocolV3SessionSync(generation)) {
2215
+ const afterSeq = this.lastChangeSeq;
2216
+ const page = await this.protocolV3SessionSync.getSessionChanges(
2217
+ this.sessionId,
2218
+ afterSeq,
2219
+ this.protocolV3SessionSync.changesLimit ?? PROTOCOL_V3_CHANGES_PAGE_LIMIT
2220
+ );
2221
+ if (!this.isActiveProtocolV3SessionSync(generation)) {
2222
+ return;
2223
+ }
2224
+ if (!page) {
2225
+ break;
2226
+ }
2227
+ this.applyProtocolV3SessionOverlay(page.session);
2228
+ for (const change of page.changes) {
2229
+ this.applyProtocolV3Change(change);
2230
+ }
2231
+ if (!page.cursor.hasMore || page.changes.length === 0) {
2232
+ break;
2233
+ }
2234
+ }
2235
+ } catch (error) {
2236
+ logger.debug("[API] Protocol v3 session catch-up failed", {
2237
+ sessionId: this.sessionId,
2238
+ error
2239
+ });
2240
+ } finally {
2241
+ if (generation !== this.protocolV3SessionSyncGeneration) {
2242
+ return;
2243
+ }
2244
+ this.protocolV3SessionSyncInProgress = false;
2245
+ const bufferedLiveEvents = this.bufferedLiveSessionEventsDuringProtocolSync;
2246
+ this.bufferedLiveSessionEventsDuringProtocolSync = [];
2247
+ for (const liveEvent of bufferedLiveEvents) {
2248
+ this.replayBufferedLiveSessionEvent(liveEvent);
2249
+ }
2250
+ }
2251
+ }
2252
+ isActiveProtocolV3SessionSync(generation) {
2253
+ return generation === this.protocolV3SessionSyncGeneration && this.socket.connected;
2254
+ }
2255
+ applyProtocolV3Snapshot(snapshot) {
2256
+ this.applyProtocolV3SessionOverlay(snapshot.session);
2257
+ for (const message of snapshot.snapshot.messages) {
2258
+ if (message.seq <= this.lastChangeSeq) {
2259
+ continue;
2260
+ }
2261
+ if (message.content.t === "encrypted") {
2262
+ this.dispatchEncryptedSessionMessage(message.content.c);
2263
+ }
2264
+ this.lastChangeSeq = Math.max(this.lastChangeSeq, message.seq);
2265
+ }
2266
+ this.lastChangeSeq = Math.max(this.lastChangeSeq, snapshot.session.lastChangeSeq);
2267
+ }
2268
+ applyProtocolV3SessionOverlay(session) {
2269
+ if (typeof session.metadataVersion === "number" && typeof session.metadata === "string" && session.metadataVersion > this.metadataVersion) {
2270
+ this.metadata = decrypt(this.encryptionKey, this.encryptionVariant, decodeBase64(session.metadata));
2271
+ this.metadataVersion = session.metadataVersion;
2272
+ this.emit("metadata-updated", this.metadata);
2273
+ }
2274
+ if (typeof session.agentStateVersion === "number" && session.agentStateVersion > this.agentStateVersion) {
2275
+ this.agentState = typeof session.agentState === "string" ? decrypt(this.encryptionKey, this.encryptionVariant, decodeBase64(session.agentState)) : null;
2276
+ this.agentStateVersion = session.agentStateVersion;
2277
+ this.emit("agent-state-updated", this.agentState);
2278
+ }
2279
+ }
2280
+ applyProtocolV3Change(change) {
2281
+ if (change.changeSeq <= this.lastChangeSeq) {
2282
+ return;
2283
+ }
2284
+ const payload = change.payload && typeof change.payload === "object" ? change.payload : {};
2285
+ switch (change.changeType) {
2286
+ case "message.created": {
2287
+ const message = payload.message;
2288
+ if (message && typeof message === "object") {
2289
+ const content = message.content;
2290
+ if (content && typeof content === "object" && content.t === "encrypted" && typeof content.c === "string") {
2291
+ this.dispatchEncryptedSessionMessage(content.c);
2292
+ }
2293
+ }
2294
+ break;
2295
+ }
2296
+ case "session.metadata.updated": {
2297
+ const metadataUpdate = payload.metadata;
2298
+ if (metadataUpdate && typeof metadataUpdate === "object" && typeof metadataUpdate.version === "number" && typeof metadataUpdate.value === "string") {
2299
+ const version = metadataUpdate.version;
2300
+ if (version <= this.metadataVersion) {
2301
+ break;
2302
+ }
2303
+ this.metadata = decrypt(
2304
+ this.encryptionKey,
2305
+ this.encryptionVariant,
2306
+ decodeBase64(metadataUpdate.value)
2307
+ );
2308
+ this.metadataVersion = version;
2309
+ this.emit("metadata-updated", this.metadata);
2310
+ }
2311
+ break;
2312
+ }
2313
+ case "session.agent-state.updated": {
2314
+ const agentStateUpdate = payload.agentState;
2315
+ if (agentStateUpdate && typeof agentStateUpdate === "object" && typeof agentStateUpdate.version === "number") {
2316
+ const version = agentStateUpdate.version;
2317
+ if (version <= this.agentStateVersion) {
2318
+ break;
2319
+ }
2320
+ const encodedAgentState = agentStateUpdate.value;
2321
+ this.agentState = typeof encodedAgentState === "string" ? decrypt(this.encryptionKey, this.encryptionVariant, decodeBase64(encodedAgentState)) : null;
2322
+ this.agentStateVersion = version;
2323
+ this.emit("agent-state-updated", this.agentState);
2324
+ }
2325
+ break;
2326
+ }
2327
+ case "session.deleted":
2328
+ this.emit("message", {
2329
+ t: "delete-session",
2330
+ sid: this.sessionId,
2331
+ changeSeq: change.changeSeq
2332
+ });
2333
+ break;
2334
+ }
2335
+ this.lastChangeSeq = Math.max(this.lastChangeSeq, change.changeSeq);
2336
+ }
2337
+ handleProtocolV3SessionChangeEvent(data) {
2338
+ if (data.sessionId !== this.sessionId) {
2339
+ return;
2340
+ }
2341
+ this.applyProtocolV3Change(data.change);
2342
+ }
2343
+ replayBufferedLiveSessionEvent(event) {
2344
+ if (event.type === "update") {
2345
+ this.handleSocketUpdate(event.payload);
2346
+ return;
2347
+ }
2348
+ this.handleProtocolV3SessionChangeEvent(event.payload);
2349
+ }
2350
+ bufferLiveSessionEvent(event) {
2351
+ this.bufferedLiveSessionEventsDuringProtocolSync.push(event);
2352
+ }
2353
+ supportsKnownProtocolCapability(capability) {
2354
+ return Array.isArray(this.protocolV3Descriptor?.capabilities) && this.protocolV3Descriptor.capabilities.includes(capability);
2355
+ }
1895
2356
  emitEncryptedSessionMessage(encrypted) {
1896
2357
  this.socket.emit("message", {
1897
2358
  sid: this.sessionId,
@@ -2200,6 +2661,141 @@ class ApiMachineClient {
2200
2661
  }
2201
2662
  }
2202
2663
 
2664
+ class ApiUserObserverClient extends node_events.EventEmitter {
2665
+ constructor(credentials) {
2666
+ super();
2667
+ this.credentials = credentials;
2668
+ this.socket = socket_ioClient.io(configuration.serverUrl, {
2669
+ auth: (cb) => cb(buildSocketAuth({
2670
+ credentials: this.credentials,
2671
+ clientType: "user-scoped"
2672
+ })),
2673
+ path: "/v1/updates",
2674
+ reconnection: true,
2675
+ reconnectionAttempts: Infinity,
2676
+ reconnectionDelay: 1e3,
2677
+ reconnectionDelayMax: 5e3,
2678
+ transports: ["websocket"],
2679
+ withCredentials: true,
2680
+ autoConnect: false
2681
+ });
2682
+ this.socket.on("connect", () => {
2683
+ logger.debug("[API USER OBSERVER] Socket connected successfully");
2684
+ this.clearReconnectAfterServerDisconnectTimer();
2685
+ this.lastSocketServerError = null;
2686
+ this.protocolV3Descriptor = null;
2687
+ this.protocolV3SocketCapabilities = null;
2688
+ });
2689
+ this.socket.on("disconnect", (reason) => {
2690
+ logger.debug("[API USER OBSERVER] Socket disconnected:", reason);
2691
+ this.protocolV3Descriptor = null;
2692
+ this.protocolV3SocketCapabilities = null;
2693
+ this.retryAfterServerDisconnect(reason);
2694
+ });
2695
+ this.socket.on("connect_error", (error) => {
2696
+ logger.debug("[API USER OBSERVER] Socket connection error:", error);
2697
+ });
2698
+ this.socket.on("capabilities", (data) => {
2699
+ this.protocolV3SocketCapabilities = data;
2700
+ this.protocolV3Descriptor = data.protocol;
2701
+ this.emit("protocol-capabilities", data);
2702
+ });
2703
+ this.socket.on("update", (data) => {
2704
+ this.emit("update", data);
2705
+ });
2706
+ this.socket.on("ephemeral", (data) => {
2707
+ this.emit("ephemeral", data);
2708
+ });
2709
+ this.socket.on("error", (error) => {
2710
+ logger.debug("[API USER OBSERVER] Socket error:", error);
2711
+ this.lastSocketServerError = this.normalizeSocketError(error);
2712
+ });
2713
+ this.socket.connect();
2714
+ }
2715
+ socket;
2716
+ protocolV3Descriptor = null;
2717
+ protocolV3SocketCapabilities = null;
2718
+ reconnectAfterServerDisconnectTimer = null;
2719
+ lastSocketServerError = null;
2720
+ isConnected() {
2721
+ return this.socket.connected;
2722
+ }
2723
+ getProtocolV3DescriptorSnapshot() {
2724
+ return this.protocolV3Descriptor;
2725
+ }
2726
+ getProtocolV3SocketCapabilitiesSnapshot() {
2727
+ return this.protocolV3SocketCapabilities;
2728
+ }
2729
+ onEphemeral(callback) {
2730
+ this.on("ephemeral", callback);
2731
+ }
2732
+ offEphemeral(callback) {
2733
+ this.off("ephemeral", callback);
2734
+ }
2735
+ onUpdate(callback) {
2736
+ this.on("update", callback);
2737
+ }
2738
+ offUpdate(callback) {
2739
+ this.off("update", callback);
2740
+ }
2741
+ async close() {
2742
+ this.clearReconnectAfterServerDisconnectTimer();
2743
+ this.protocolV3Descriptor = null;
2744
+ this.protocolV3SocketCapabilities = null;
2745
+ this.socket.close();
2746
+ }
2747
+ retryAfterServerDisconnect(reason) {
2748
+ if (reason !== "io server disconnect") {
2749
+ return;
2750
+ }
2751
+ const errorCode = this.lastSocketServerError?.error;
2752
+ if (errorCode !== "REQUEST_EXPIRED" && errorCode !== "REQUEST_REPLAYED") {
2753
+ return;
2754
+ }
2755
+ if (this.reconnectAfterServerDisconnectTimer || this.socket.connected) {
2756
+ return;
2757
+ }
2758
+ logger.debug("[API USER OBSERVER] Scheduling manual reconnect after retryable server disconnect", {
2759
+ reason,
2760
+ errorCode,
2761
+ message: this.lastSocketServerError?.message
2762
+ });
2763
+ this.reconnectAfterServerDisconnectTimer = setTimeout(() => {
2764
+ this.reconnectAfterServerDisconnectTimer = null;
2765
+ if (this.socket.connected) {
2766
+ return;
2767
+ }
2768
+ logger.debug("[API USER OBSERVER] Retrying socket connection after server disconnect", {
2769
+ errorCode,
2770
+ message: this.lastSocketServerError?.message
2771
+ });
2772
+ this.socket.connect();
2773
+ }, 1e3);
2774
+ }
2775
+ clearReconnectAfterServerDisconnectTimer() {
2776
+ if (!this.reconnectAfterServerDisconnectTimer) {
2777
+ return;
2778
+ }
2779
+ clearTimeout(this.reconnectAfterServerDisconnectTimer);
2780
+ this.reconnectAfterServerDisconnectTimer = null;
2781
+ }
2782
+ normalizeSocketError(error) {
2783
+ if (!error || typeof error !== "object") {
2784
+ return null;
2785
+ }
2786
+ const candidate = error;
2787
+ const errorCode = typeof candidate.error === "string" ? candidate.error : void 0;
2788
+ const message = typeof candidate.message === "string" ? candidate.message : void 0;
2789
+ if (!errorCode && !message) {
2790
+ return null;
2791
+ }
2792
+ return {
2793
+ error: errorCode,
2794
+ message
2795
+ };
2796
+ }
2797
+ }
2798
+
2203
2799
  class PushNotificationClient {
2204
2800
  credentials;
2205
2801
  baseUrl;
@@ -2520,6 +3116,39 @@ class ApiClient {
2520
3116
  }
2521
3117
  return new AuthenticationRequiredError(AUTHENTICATION_REQUIRED_MESSAGE);
2522
3118
  }
3119
+ async requestProtocolV3Resource(opts) {
3120
+ if (!this.credential.signing) {
3121
+ logger.debug(`[API] Skipping ${opts.logLabel} because signing credentials are unavailable`);
3122
+ return null;
3123
+ }
3124
+ try {
3125
+ const response = await this.request({
3126
+ method: "GET",
3127
+ url: opts.url,
3128
+ timeout: 5e3
3129
+ });
3130
+ const parsed = opts.schema.safeParse(response.data);
3131
+ if (!parsed.success) {
3132
+ logger.debug(`[API] ${opts.logLabel} returned an unexpected payload`, response.data);
3133
+ return null;
3134
+ }
3135
+ return parsed.data;
3136
+ } catch (error) {
3137
+ if (axios.isAxiosError(error)) {
3138
+ const status = error.response?.status;
3139
+ if (status === 401 || status === 403 || status === 404) {
3140
+ logger.debug(`[API] ${opts.logLabel} unavailable (${status ?? "unknown"})`);
3141
+ return null;
3142
+ }
3143
+ if (error.code && isNetworkError(error.code)) {
3144
+ logger.debug(`[API] ${opts.logLabel} probe failed due to network error`, error.code);
3145
+ return null;
3146
+ }
3147
+ }
3148
+ logger.debug(`[API] Failed to fetch ${opts.logLabel}`, error);
3149
+ return null;
3150
+ }
3151
+ }
2523
3152
  async request(opts) {
2524
3153
  return axios.request({
2525
3154
  method: opts.method,
@@ -2538,6 +3167,72 @@ class ApiClient {
2538
3167
  timeout: opts.timeout
2539
3168
  });
2540
3169
  }
3170
+ async getProtocolV3Descriptor() {
3171
+ const url = `${configuration.serverUrl}/v3/capabilities`;
3172
+ const payload = await this.requestProtocolV3Resource({
3173
+ url,
3174
+ schema: ProtocolV3CapabilitiesResponseSchema,
3175
+ logLabel: "/v3/capabilities"
3176
+ });
3177
+ return payload?.protocol ?? null;
3178
+ }
3179
+ async getSessionSnapshotV3(sessionId, limit = 150) {
3180
+ const url = new URL(`/v3/sessions/${encodeURIComponent(sessionId)}/snapshot`, configuration.serverUrl);
3181
+ url.searchParams.set("limit", String(limit));
3182
+ return await this.requestProtocolV3Resource({
3183
+ url: url.toString(),
3184
+ schema: ProtocolV3SessionSnapshotResponseSchema,
3185
+ logLabel: `/v3/sessions/${sessionId}/snapshot`
3186
+ });
3187
+ }
3188
+ async getSessionChangesV3(sessionId, afterSeq, limit = 200) {
3189
+ const url = new URL(`/v3/sessions/${encodeURIComponent(sessionId)}/changes`, configuration.serverUrl);
3190
+ url.searchParams.set("afterSeq", String(afterSeq));
3191
+ url.searchParams.set("limit", String(limit));
3192
+ return await this.requestProtocolV3Resource({
3193
+ url: url.toString(),
3194
+ schema: ProtocolV3SessionChangesResponseSchema,
3195
+ logLabel: `/v3/sessions/${sessionId}/changes`
3196
+ });
3197
+ }
3198
+ async listSessionsIndex() {
3199
+ try {
3200
+ const response = await this.request({
3201
+ method: "GET",
3202
+ url: `${configuration.serverUrl}/v1/sessions`,
3203
+ timeout: 5e3
3204
+ });
3205
+ const parsed = SessionListResponseSchema.safeParse(response.data);
3206
+ if (!parsed.success) {
3207
+ logger.debug("[API] /v1/sessions returned an unexpected payload while listing session index", response.data);
3208
+ return [];
3209
+ }
3210
+ return parsed.data.sessions.map((session) => ({
3211
+ id: session.id,
3212
+ seq: session.seq,
3213
+ title: session.title ?? null,
3214
+ active: session.active,
3215
+ activeAt: session.activeAt,
3216
+ createdAt: session.createdAt,
3217
+ updatedAt: session.updatedAt,
3218
+ sessionIndex: session.sessionIndex ?? null
3219
+ }));
3220
+ } catch (error) {
3221
+ if (axios.isAxiosError(error)) {
3222
+ const status = error.response?.status;
3223
+ if (status === 401 || status === 403 || status === 404) {
3224
+ logger.debug(`[API] Session index unavailable (${status ?? "unknown"})`);
3225
+ return [];
3226
+ }
3227
+ if (error.code && isNetworkError(error.code)) {
3228
+ logger.debug("[API] Session index probe failed due to network error", error.code);
3229
+ return [];
3230
+ }
3231
+ }
3232
+ logger.debug("[API] Failed to list session index", error);
3233
+ return [];
3234
+ }
3235
+ }
2541
3236
  /**
2542
3237
  * Create a new session or load existing one with the given tag
2543
3238
  */
@@ -2557,6 +3252,7 @@ class ApiClient {
2557
3252
  encryptionVariant = "legacy";
2558
3253
  }
2559
3254
  try {
3255
+ const sessionIndex = buildSessionRuntimeIndex(opts.metadata);
2560
3256
  const response = await this.request({
2561
3257
  method: "POST",
2562
3258
  url: `${configuration.serverUrl}/v1/sessions`,
@@ -2564,7 +3260,8 @@ class ApiClient {
2564
3260
  tag: opts.tag,
2565
3261
  metadata: encodeBase64(encrypt(encryptionKey, encryptionVariant, opts.metadata)),
2566
3262
  agentState: opts.state ? encodeBase64(encrypt(encryptionKey, encryptionVariant, opts.state)) : null,
2567
- dataEncryptionKey: dataEncryptionKey ? encodeBase64(dataEncryptionKey) : null
3263
+ dataEncryptionKey: dataEncryptionKey ? encodeBase64(dataEncryptionKey) : null,
3264
+ ...sessionIndex ? { sessionIndex } : {}
2568
3265
  },
2569
3266
  timeout: 6e4
2570
3267
  });
@@ -2733,11 +3430,21 @@ class ApiClient {
2733
3430
  }
2734
3431
  }
2735
3432
  sessionSyncClient(session) {
2736
- return new ApiSessionClient(this.credential, session);
3433
+ const client = new ApiSessionClient(this.credential, session);
3434
+ if (this.credential.signing) {
3435
+ client.configureProtocolV3Sync({
3436
+ getSessionSnapshot: (sessionId, limit) => this.getSessionSnapshotV3(sessionId, limit),
3437
+ getSessionChanges: (sessionId, afterSeq, limit) => this.getSessionChangesV3(sessionId, afterSeq, limit)
3438
+ });
3439
+ }
3440
+ return client;
2737
3441
  }
2738
3442
  machineSyncClient(machine) {
2739
3443
  return new ApiMachineClient(this.credential, machine);
2740
3444
  }
3445
+ userScopedObserverClient() {
3446
+ return new ApiUserObserverClient(this.credential);
3447
+ }
2741
3448
  push() {
2742
3449
  return this.pushClient;
2743
3450
  }