@ccpocket/bridge 1.46.1 → 1.47.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/websocket.js CHANGED
@@ -395,7 +395,9 @@ export class BridgeWebSocketServer {
395
395
  tipCode,
396
396
  sessionId,
397
397
  };
398
- session?.history.push(tipMsg);
398
+ if (session) {
399
+ this.sessionManager.appendHistory(session.id, tipMsg);
400
+ }
399
401
  this.send(ws, tipMsg);
400
402
  }
401
403
  async splitPastHistoryMessages(session) {
@@ -738,6 +740,8 @@ export class BridgeWebSocketServer {
738
740
  return;
739
741
  }
740
742
  const text = msg.text;
743
+ const clientMessageId = msg.clientMessageId;
744
+ const baseSeq = msg.baseSeq;
741
745
  const codexSkills = msg.skills ?? (msg.skill ? [msg.skill] : []);
742
746
  const codexMentions = msg.mentions ?? [];
743
747
  // Snapshot busy state before dispatch. We prefer the actual enqueue
@@ -772,12 +776,24 @@ export class BridgeWebSocketServer {
772
776
  if (imageRefs.length === 0)
773
777
  imageRefs = undefined;
774
778
  }
779
+ if (clientMessageId &&
780
+ baseSeq !== undefined &&
781
+ this.hasInputConflictSince(session.id, baseSeq)) {
782
+ this.send(ws, {
783
+ type: "input_rejected",
784
+ sessionId: session.id,
785
+ clientMessageId,
786
+ reason: "conflict",
787
+ });
788
+ break;
789
+ }
775
790
  if (session.provider === "codex" &&
776
791
  !session.process.isWaitingForInput) {
777
792
  if (session.codexQueuedInput) {
778
793
  this.send(ws, {
779
794
  type: "input_rejected",
780
795
  sessionId: session.id,
796
+ ...(clientMessageId ? { clientMessageId } : {}),
781
797
  reason: "Queue is full",
782
798
  });
783
799
  break;
@@ -795,6 +811,7 @@ export class BridgeWebSocketServer {
795
811
  this.send(ws, {
796
812
  type: "input_rejected",
797
813
  sessionId: session.id,
814
+ ...(clientMessageId ? { clientMessageId } : {}),
798
815
  reason: "Queue is full",
799
816
  });
800
817
  break;
@@ -811,18 +828,22 @@ export class BridgeWebSocketServer {
811
828
  this.send(ws, {
812
829
  type: "input_ack",
813
830
  sessionId: session.id,
831
+ ...(clientMessageId ? { clientMessageId } : {}),
832
+ acceptedSeq: session.historyRevision,
814
833
  queued: true,
815
834
  });
816
835
  this.broadcastSessionList();
817
836
  break;
818
837
  }
819
- session.history.push({
838
+ const userEntry = this.sessionManager.appendHistory(session.id, {
820
839
  type: "user_input",
821
840
  text,
841
+ ...(clientMessageId ? { clientMessageId } : {}),
822
842
  timestamp: new Date().toISOString(),
823
843
  ...(images.length > 0 ? { imageCount: images.length } : {}),
824
844
  ...(imageRefs ? { images: imageRefs } : {}),
825
845
  });
846
+ const acceptedSeq = userEntry?.seq ?? session.historyRevision;
826
847
  // Persist images to Gallery Store asynchronously (fire-and-forget)
827
848
  if (images.length > 0 && this.galleryStore && session.projectPath) {
828
849
  for (const img of images) {
@@ -838,6 +859,8 @@ export class BridgeWebSocketServer {
838
859
  this.send(ws, {
839
860
  type: "input_ack",
840
861
  sessionId: session.id,
862
+ ...(clientMessageId ? { clientMessageId } : {}),
863
+ acceptedSeq,
841
864
  queued: false,
842
865
  });
843
866
  const codexProc = session.process;
@@ -900,6 +923,8 @@ export class BridgeWebSocketServer {
900
923
  this.send(ws, {
901
924
  type: "input_ack",
902
925
  sessionId: session.id,
926
+ ...(clientMessageId ? { clientMessageId } : {}),
927
+ acceptedSeq,
903
928
  queued: isAgentBusySnapshot,
904
929
  });
905
930
  this.galleryStore
@@ -947,6 +972,8 @@ export class BridgeWebSocketServer {
947
972
  this.send(ws, {
948
973
  type: "input_ack",
949
974
  sessionId: session.id,
975
+ ...(clientMessageId ? { clientMessageId } : {}),
976
+ acceptedSeq,
950
977
  queued: wasQueued,
951
978
  });
952
979
  if (wasQueued) {
@@ -1623,7 +1650,7 @@ export class BridgeWebSocketServer {
1623
1650
  });
1624
1651
  this.debugEvents.delete(msg.sessionId);
1625
1652
  this.notifiedPermissionToolUses.delete(msg.sessionId);
1626
- this.sendSessionList(ws);
1653
+ this.broadcastSessionList();
1627
1654
  }
1628
1655
  else {
1629
1656
  this.send(ws, {
@@ -1718,6 +1745,66 @@ export class BridgeWebSocketServer {
1718
1745
  }
1719
1746
  break;
1720
1747
  }
1748
+ case "get_history_delta": {
1749
+ const session = this.sessionManager.get(msg.sessionId);
1750
+ const result = this.sessionManager.getHistorySince(msg.sessionId, msg.sinceSeq);
1751
+ if (session && result) {
1752
+ if (session.pastMessages && session.pastMessages.length > 0) {
1753
+ const splitPastHistory = await this.splitPastHistoryMessages(session);
1754
+ if (splitPastHistory.pastMessages.length > 0) {
1755
+ this.send(ws, {
1756
+ type: "past_history",
1757
+ claudeSessionId: session.claudeSessionId ?? msg.sessionId,
1758
+ sessionId: msg.sessionId,
1759
+ messages: splitPastHistory.pastMessages,
1760
+ });
1761
+ }
1762
+ }
1763
+ this.send(ws, {
1764
+ type: result.kind === "snapshot"
1765
+ ? "history_snapshot"
1766
+ : "history_delta",
1767
+ sessionId: msg.sessionId,
1768
+ fromSeq: result.fromSeq,
1769
+ toSeq: result.toSeq,
1770
+ messages: result.entries,
1771
+ status: session.status,
1772
+ ...(result.kind === "snapshot" ? { reason: result.reason } : {}),
1773
+ });
1774
+ if (session.provider === "codex") {
1775
+ const item = session.codexQueuedInput;
1776
+ this.sendConversationQueue(ws, {
1777
+ type: "conversation_queue",
1778
+ sessionId: msg.sessionId,
1779
+ limit: 1,
1780
+ items: item
1781
+ ? [
1782
+ {
1783
+ itemId: item.itemId,
1784
+ text: item.text,
1785
+ createdAt: item.createdAt,
1786
+ ...(item.updatedAt ? { updatedAt: item.updatedAt } : {}),
1787
+ ...(item.imageCount
1788
+ ? { imageCount: item.imageCount }
1789
+ : {}),
1790
+ ...(item.skills?.length ? { skills: item.skills } : {}),
1791
+ ...(item.mentions?.length
1792
+ ? { mentions: item.mentions }
1793
+ : {}),
1794
+ },
1795
+ ]
1796
+ : [],
1797
+ });
1798
+ }
1799
+ }
1800
+ else {
1801
+ this.send(ws, {
1802
+ type: "error",
1803
+ message: `Session ${msg.sessionId} not found`,
1804
+ });
1805
+ }
1806
+ break;
1807
+ }
1721
1808
  case "refresh_branch": {
1722
1809
  const session = this.sessionManager.get(msg.sessionId);
1723
1810
  if (session) {
@@ -3600,6 +3687,26 @@ export class BridgeWebSocketServer {
3600
3687
  .get(ws)
3601
3688
  ?.has("conversation_queue") ?? false);
3602
3689
  }
3690
+ hasInputConflictSince(sessionId, baseSeq) {
3691
+ const delta = this.sessionManager.getHistorySince(sessionId, baseSeq);
3692
+ if (!delta)
3693
+ return true;
3694
+ if (delta.kind === "snapshot")
3695
+ return true;
3696
+ return delta.entries.some((entry) => {
3697
+ const msg = entry.message;
3698
+ if (msg.type === "user_input" || msg.type === "result")
3699
+ return true;
3700
+ if (msg.type === "system") {
3701
+ const subtype = msg.subtype;
3702
+ return (subtype === "session_switched" ||
3703
+ subtype === "session_rewound" ||
3704
+ subtype === "sandbox_restarted" ||
3705
+ subtype === "session_stopped");
3706
+ }
3707
+ return false;
3708
+ });
3709
+ }
3603
3710
  sendConversationQueue(ws, msg) {
3604
3711
  if (!this.shouldSendToClient(ws, msg))
3605
3712
  return;