@ccpocket/bridge 1.43.0 → 1.44.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
@@ -1,3 +1,4 @@
1
+ import { randomUUID } from "node:crypto";
1
2
  import { execFile, execFileSync } from "node:child_process";
2
3
  import { existsSync } from "node:fs";
3
4
  import { lstat, readFile, readlink, stat, unlink } from "node:fs/promises";
@@ -667,16 +668,8 @@ export class BridgeWebSocketServer {
667
668
  return;
668
669
  }
669
670
  const text = msg.text;
670
- // Codex: reject if the process is not waiting for input (turn-based, no internal queue)
671
- if (session.provider === "codex" &&
672
- !session.process.isWaitingForInput) {
673
- this.send(ws, {
674
- type: "input_rejected",
675
- sessionId: session.id,
676
- reason: "Process is busy",
677
- });
678
- break;
679
- }
671
+ const codexSkills = msg.skills ?? (msg.skill ? [msg.skill] : []);
672
+ const codexMentions = msg.mentions ?? [];
680
673
  // Snapshot busy state before dispatch. We prefer the actual enqueue
681
674
  // result returned by SdkProcess sendInput* below, but keep this as a
682
675
  // fallback for test doubles and async paths.
@@ -709,6 +702,50 @@ export class BridgeWebSocketServer {
709
702
  if (imageRefs.length === 0)
710
703
  imageRefs = undefined;
711
704
  }
705
+ if (session.provider === "codex" &&
706
+ !session.process.isWaitingForInput) {
707
+ if (session.codexQueuedInput) {
708
+ this.send(ws, {
709
+ type: "input_rejected",
710
+ sessionId: session.id,
711
+ reason: "Queue is full",
712
+ });
713
+ break;
714
+ }
715
+ const queued = this.sessionManager.queueCodexInput(session.id, {
716
+ itemId: randomUUID(),
717
+ text,
718
+ createdAt: new Date().toISOString(),
719
+ ...(images.length > 0 ? { imageCount: images.length, images } : {}),
720
+ ...(imageRefs ? { imageRefs } : {}),
721
+ ...(codexSkills.length > 0 ? { skills: codexSkills } : {}),
722
+ ...(codexMentions.length > 0 ? { mentions: codexMentions } : {}),
723
+ });
724
+ if (!queued) {
725
+ this.send(ws, {
726
+ type: "input_rejected",
727
+ sessionId: session.id,
728
+ reason: "Queue is full",
729
+ });
730
+ break;
731
+ }
732
+ if (images.length > 0 && this.galleryStore && session.projectPath) {
733
+ for (const img of images) {
734
+ this.galleryStore
735
+ .addImageFromBase64(img.base64, img.mimeType, session.projectPath, msg.sessionId)
736
+ .catch((err) => {
737
+ console.warn(`[ws] Failed to persist queued image to gallery: ${err}`);
738
+ });
739
+ }
740
+ }
741
+ this.send(ws, {
742
+ type: "input_ack",
743
+ sessionId: session.id,
744
+ queued: true,
745
+ });
746
+ this.broadcastSessionList();
747
+ break;
748
+ }
712
749
  session.history.push({
713
750
  type: "user_input",
714
751
  text,
@@ -734,13 +771,11 @@ export class BridgeWebSocketServer {
734
771
  queued: false,
735
772
  });
736
773
  const codexProc = session.process;
737
- const skills = msg.skills ?? (msg.skill ? [msg.skill] : []);
738
- const mentions = msg.mentions ?? [];
739
774
  if (images.length > 0) {
740
775
  codexProc.sendInputStructured(text, {
741
776
  images,
742
- skills,
743
- mentions,
777
+ skills: codexSkills,
778
+ mentions: codexMentions,
744
779
  });
745
780
  }
746
781
  else if (msg.imageId && this.galleryStore) {
@@ -750,22 +785,31 @@ export class BridgeWebSocketServer {
750
785
  if (imageData) {
751
786
  codexProc.sendInputStructured(text, {
752
787
  images: [imageData],
753
- skills,
754
- mentions,
788
+ skills: codexSkills,
789
+ mentions: codexMentions,
755
790
  });
756
791
  }
757
792
  else {
758
793
  console.warn(`[ws] Image not found: ${msg.imageId}`);
759
- codexProc.sendInputStructured(text, { skills, mentions });
794
+ codexProc.sendInputStructured(text, {
795
+ skills: codexSkills,
796
+ mentions: codexMentions,
797
+ });
760
798
  }
761
799
  })
762
800
  .catch((err) => {
763
801
  console.error(`[ws] Failed to load image: ${err}`);
764
- codexProc.sendInputStructured(text, { skills, mentions });
802
+ codexProc.sendInputStructured(text, {
803
+ skills: codexSkills,
804
+ mentions: codexMentions,
805
+ });
765
806
  });
766
807
  }
767
- else if (skills.length > 0 || mentions.length > 0) {
768
- codexProc.sendInputStructured(text, { skills, mentions });
808
+ else if (codexSkills.length > 0 || codexMentions.length > 0) {
809
+ codexProc.sendInputStructured(text, {
810
+ skills: codexSkills,
811
+ mentions: codexMentions,
812
+ });
769
813
  }
770
814
  else {
771
815
  codexProc.sendInput(text);
@@ -841,6 +885,69 @@ export class BridgeWebSocketServer {
841
885
  }
842
886
  break;
843
887
  }
888
+ case "update_queued_input": {
889
+ const session = this.resolveSession(msg.sessionId);
890
+ if (!session || session.provider !== "codex") {
891
+ this.send(ws, { type: "error", message: "No active Codex session." });
892
+ return;
893
+ }
894
+ if (!msg.text.trim()) {
895
+ this.send(ws, {
896
+ type: "error",
897
+ message: "Queued message cannot be empty.",
898
+ });
899
+ return;
900
+ }
901
+ const success = this.sessionManager.updateCodexQueuedInput(session.id, msg.itemId, msg.text, { skills: msg.skills ?? [], mentions: msg.mentions ?? [] });
902
+ if (!success) {
903
+ this.send(ws, {
904
+ type: "error",
905
+ message: "Queued message not found.",
906
+ errorCode: "queued_input_not_found",
907
+ });
908
+ return;
909
+ }
910
+ this.broadcastSessionList();
911
+ break;
912
+ }
913
+ case "cancel_queued_input": {
914
+ const session = this.resolveSession(msg.sessionId);
915
+ if (!session || session.provider !== "codex") {
916
+ this.send(ws, { type: "error", message: "No active Codex session." });
917
+ return;
918
+ }
919
+ const success = this.sessionManager.cancelCodexQueuedInput(session.id, msg.itemId);
920
+ if (!success) {
921
+ this.send(ws, {
922
+ type: "error",
923
+ message: "Queued message not found.",
924
+ errorCode: "queued_input_not_found",
925
+ });
926
+ return;
927
+ }
928
+ this.broadcastSessionList();
929
+ break;
930
+ }
931
+ case "steer_queued_input": {
932
+ const session = this.resolveSession(msg.sessionId);
933
+ if (!session || session.provider !== "codex") {
934
+ this.send(ws, { type: "error", message: "No active Codex session." });
935
+ return;
936
+ }
937
+ const result = await this.sessionManager.steerCodexQueuedInput(session.id, msg.itemId);
938
+ if (!result.ok) {
939
+ this.send(ws, {
940
+ type: "error",
941
+ message: result.error,
942
+ errorCode: result.error === "Queued message not found."
943
+ ? "queued_input_not_found"
944
+ : "queued_input_steer_failed",
945
+ });
946
+ return;
947
+ }
948
+ this.broadcastSessionList();
949
+ break;
950
+ }
844
951
  case "push_register": {
845
952
  const locale = normalizePushLocale(msg.locale);
846
953
  const privacyMode = msg.privacyMode === true;
@@ -958,6 +1065,11 @@ export class BridgeWebSocketServer {
958
1065
  if (newCollaboration !== currentCollaboration) {
959
1066
  process.setCollaborationMode(newCollaboration);
960
1067
  }
1068
+ session.codexSettings = {
1069
+ ...(session.codexSettings ?? {}),
1070
+ approvalPolicy: newApproval,
1071
+ approvalsReviewer: newReviewer,
1072
+ };
961
1073
  session.lastActivityAt = new Date();
962
1074
  this.broadcast({
963
1075
  type: "system",
@@ -1473,6 +1585,31 @@ export class BridgeWebSocketServer {
1473
1585
  status: session.status,
1474
1586
  sessionId: msg.sessionId,
1475
1587
  });
1588
+ if (session.provider === "codex") {
1589
+ const item = session.codexQueuedInput;
1590
+ this.send(ws, {
1591
+ type: "conversation_queue",
1592
+ sessionId: msg.sessionId,
1593
+ limit: 1,
1594
+ items: item
1595
+ ? [
1596
+ {
1597
+ itemId: item.itemId,
1598
+ text: item.text,
1599
+ createdAt: item.createdAt,
1600
+ ...(item.updatedAt ? { updatedAt: item.updatedAt } : {}),
1601
+ ...(item.imageCount
1602
+ ? { imageCount: item.imageCount }
1603
+ : {}),
1604
+ ...(item.skills?.length ? { skills: item.skills } : {}),
1605
+ ...(item.mentions?.length
1606
+ ? { mentions: item.mentions }
1607
+ : {}),
1608
+ },
1609
+ ]
1610
+ : [],
1611
+ });
1612
+ }
1476
1613
  // Send cached slash commands so the client can restore them even when
1477
1614
  // the original init/supported_commands message was evicted from the
1478
1615
  // in-memory history (MAX_HISTORY_PER_SESSION overflow).