happy-imou-cloud 2.0.18 → 2.0.19

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