ragent-cli 1.11.8 → 1.11.10

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/index.js CHANGED
@@ -31,7 +31,7 @@ var require_package = __commonJS({
31
31
  "package.json"(exports2, module2) {
32
32
  module2.exports = {
33
33
  name: "ragent-cli",
34
- version: "1.11.8",
34
+ version: "1.11.10",
35
35
  description: "CLI agent for rAgent Live \u2014 browser-first terminal control plane for AI coding agents",
36
36
  main: "dist/index.js",
37
37
  bin: {
@@ -662,8 +662,30 @@ var ClaudeCodeParserV2 = class {
662
662
  return "";
663
663
  }
664
664
  };
665
+ function mapCodexRole(role) {
666
+ const r = role?.toLowerCase();
667
+ if (r === "user") return "user";
668
+ if (r === "assistant") return "assistant";
669
+ return null;
670
+ }
671
+ function extractCodexText(content) {
672
+ if (!Array.isArray(content)) return "";
673
+ return content.filter(
674
+ (c) => (c.type === "input_text" || c.type === "output_text" || c.type === "text") && typeof c.text === "string"
675
+ ).map((c) => c.text).join("\n").trim();
676
+ }
677
+ function parseCodexArguments(args) {
678
+ if (typeof args !== "string" || !args) return void 0;
679
+ try {
680
+ return JSON.parse(args);
681
+ } catch {
682
+ return args;
683
+ }
684
+ }
665
685
  var CodexCliParserV2 = class {
666
686
  name = "codex-cli-v2";
687
+ pendingTools = /* @__PURE__ */ new Map();
688
+ turnCounter = 0;
667
689
  parseLine(line) {
668
690
  let obj;
669
691
  try {
@@ -672,21 +694,62 @@ var CodexCliParserV2 = class {
672
694
  debugTranscriptParse("codex", line, error);
673
695
  return [];
674
696
  }
697
+ if (obj.response_item) {
698
+ return this.parseLegacy(obj);
699
+ }
700
+ if (obj.type === "response_item" && obj.payload) {
701
+ return this.parseNewEnvelope(obj.payload, obj.timestamp);
702
+ }
703
+ return [];
704
+ }
705
+ parseLegacy(obj) {
675
706
  const item = obj.response_item;
676
- if (!item) return [];
677
- if (item.type === "response.output_item.done" && item.item?.type === "message") {
678
- const textParts = (item.item.content ?? []).filter((c) => c.type === "text" && typeof c.text === "string").map((c) => c.text);
679
- const text = textParts.join("\n").trim();
707
+ if (item.type !== "response.output_item.done") return [];
708
+ if (item.item?.type !== "message") return [];
709
+ const text = extractCodexText(item.item.content);
710
+ if (!text) return [];
711
+ const turnId = item.id ?? this.genTurnId(obj.timestamp);
712
+ const timestamp = obj.timestamp ?? now();
713
+ const blockId = genBlockId("txt");
714
+ return [
715
+ {
716
+ op: "upsert_turn",
717
+ turn: {
718
+ turnId,
719
+ role: "assistant",
720
+ startedAt: timestamp,
721
+ updatedAt: timestamp,
722
+ isComplete: false
723
+ }
724
+ },
725
+ {
726
+ op: "insert_block",
727
+ turnId,
728
+ block: makeTextBlock(blockId, text, true)
729
+ },
730
+ {
731
+ op: "complete_turn",
732
+ turnId,
733
+ endedAt: timestamp,
734
+ updatedAt: timestamp
735
+ }
736
+ ];
737
+ }
738
+ parseNewEnvelope(payload, lineTimestamp) {
739
+ const timestamp = lineTimestamp ?? now();
740
+ if (payload.type === "message") {
741
+ const role = mapCodexRole(payload.role);
742
+ if (!role) return [];
743
+ const text = extractCodexText(payload.content);
680
744
  if (!text) return [];
681
- const turnId = item.id ?? `codex-${Date.now()}-${Math.random().toString(36).slice(2, 8)}`;
682
- const timestamp = obj.timestamp ?? now();
745
+ const turnId = this.genTurnId(lineTimestamp);
683
746
  const blockId = genBlockId("txt");
684
747
  return [
685
748
  {
686
749
  op: "upsert_turn",
687
750
  turn: {
688
751
  turnId,
689
- role: "assistant",
752
+ role,
690
753
  startedAt: timestamp,
691
754
  updatedAt: timestamp,
692
755
  isComplete: false
@@ -705,13 +768,140 @@ var CodexCliParserV2 = class {
705
768
  }
706
769
  ];
707
770
  }
771
+ if (payload.type === "function_call") {
772
+ if (!payload.name) return [];
773
+ const callId = payload.call_id ?? genBlockId("call");
774
+ const blockId = genBlockId("tool");
775
+ const turnId = this.genTurnId(lineTimestamp);
776
+ this.pendingTools.set(callId, {
777
+ blockId,
778
+ turnId,
779
+ toolName: payload.name
780
+ });
781
+ this.capPendingTools();
782
+ const input = parseCodexArguments(payload.arguments);
783
+ return [
784
+ {
785
+ op: "upsert_turn",
786
+ turn: {
787
+ turnId,
788
+ role: "assistant",
789
+ startedAt: timestamp,
790
+ updatedAt: timestamp,
791
+ isComplete: false
792
+ }
793
+ },
794
+ {
795
+ op: "insert_block",
796
+ turnId,
797
+ block: makeToolBlock(
798
+ blockId,
799
+ callId,
800
+ payload.name,
801
+ "running",
802
+ input
803
+ )
804
+ }
805
+ ];
806
+ }
807
+ if (payload.type === "function_call_output") {
808
+ if (!payload.call_id) return [];
809
+ const pending = this.pendingTools.get(payload.call_id);
810
+ const output = payload.output ?? "";
811
+ if (pending) {
812
+ this.pendingTools.delete(payload.call_id);
813
+ const ops2 = [
814
+ {
815
+ op: "set_tool_state",
816
+ turnId: pending.turnId,
817
+ blockId: pending.blockId,
818
+ state: "completed",
819
+ isComplete: true
820
+ }
821
+ ];
822
+ if (output) {
823
+ ops2.push({
824
+ op: "append_tool_output",
825
+ turnId: pending.turnId,
826
+ blockId: pending.blockId,
827
+ text: output
828
+ });
829
+ }
830
+ ops2.push({
831
+ op: "complete_turn",
832
+ turnId: pending.turnId,
833
+ endedAt: timestamp,
834
+ updatedAt: timestamp
835
+ });
836
+ return ops2;
837
+ }
838
+ const turnId = this.genTurnId(lineTimestamp);
839
+ const blockId = genBlockId("tool");
840
+ const ops = [
841
+ {
842
+ op: "upsert_turn",
843
+ turn: {
844
+ turnId,
845
+ role: "user",
846
+ startedAt: timestamp,
847
+ updatedAt: timestamp,
848
+ isComplete: false
849
+ }
850
+ },
851
+ {
852
+ op: "insert_block",
853
+ turnId,
854
+ block: makeToolBlock(
855
+ blockId,
856
+ payload.call_id,
857
+ "tool",
858
+ "completed",
859
+ void 0,
860
+ true
861
+ )
862
+ }
863
+ ];
864
+ if (output) {
865
+ ops.push({
866
+ op: "append_tool_output",
867
+ turnId,
868
+ blockId,
869
+ text: output
870
+ });
871
+ }
872
+ ops.push({
873
+ op: "complete_turn",
874
+ turnId,
875
+ endedAt: timestamp,
876
+ updatedAt: timestamp
877
+ });
878
+ return ops;
879
+ }
708
880
  return [];
709
881
  }
882
+ genTurnId(timestamp) {
883
+ this.turnCounter += 1;
884
+ const ts = timestamp ?? (/* @__PURE__ */ new Date()).toISOString();
885
+ return `codex-${ts}-${this.turnCounter.toString(36)}`;
886
+ }
887
+ capPendingTools() {
888
+ if (this.pendingTools.size <= 500) return;
889
+ const keys = [...this.pendingTools.keys()];
890
+ for (let i = 0; i < 250; i += 1) {
891
+ this.pendingTools.delete(keys[i]);
892
+ }
893
+ }
710
894
  };
711
895
  function isGeminiHistoryItem(value) {
712
896
  if (!value || typeof value !== "object") return false;
713
897
  const item = value;
714
- return Boolean(item.role && (item.parts || item.content || item.message));
898
+ if (typeof item.role === "string" && (item.parts !== void 0 || item.content !== void 0 || item.message !== void 0)) {
899
+ return true;
900
+ }
901
+ if (typeof item.type === "string" && (item.content !== void 0 || Array.isArray(item.toolCalls))) {
902
+ return true;
903
+ }
904
+ return false;
715
905
  }
716
906
  function findGeminiHistory(value, depth = 0) {
717
907
  if (depth > 6 || !value || typeof value !== "object") return null;
@@ -736,20 +926,76 @@ function findGeminiHistory(value, depth = 0) {
736
926
  }
737
927
  return null;
738
928
  }
739
- function geminiPartsFromItem(item) {
929
+ function geminiRoleFromLegacy(role) {
930
+ const normalized = role?.toLowerCase();
931
+ if (normalized === "user") return "user";
932
+ if (normalized === "model" || normalized === "assistant") return "assistant";
933
+ return null;
934
+ }
935
+ function geminiRoleFromType(type) {
936
+ if (type === "user") return "user";
937
+ if (type === "gemini" || type === "model" || type === "assistant") {
938
+ return "assistant";
939
+ }
940
+ return null;
941
+ }
942
+ function geminiPartsFromLegacyItem(item) {
740
943
  if (Array.isArray(item.parts)) return item.parts;
741
944
  const source = item.content ?? item.message;
742
945
  if (typeof source === "string") return [{ text: source }];
743
- if (source && typeof source === "object" && Array.isArray(source.parts)) {
744
- return source.parts;
946
+ if (source && !Array.isArray(source) && typeof source === "object") {
947
+ const objSource = source;
948
+ if (Array.isArray(objSource.parts)) return objSource.parts;
745
949
  }
746
950
  return [];
747
951
  }
748
- function geminiRole(role) {
749
- const normalized = role?.toLowerCase();
750
- if (normalized === "user") return "user";
751
- if (normalized === "model" || normalized === "assistant") return "assistant";
752
- return null;
952
+ function geminiTextFromNewItem(item) {
953
+ const c = item.content;
954
+ if (typeof c === "string") return c.trim();
955
+ if (Array.isArray(c)) {
956
+ return c.map((p) => p && typeof p.text === "string" ? p.text : "").filter(Boolean).join("\n").trim();
957
+ }
958
+ return "";
959
+ }
960
+ function mapGeminiToolStatus(status) {
961
+ switch (status?.toLowerCase()) {
962
+ case "success":
963
+ case "completed":
964
+ case "ok":
965
+ return "completed";
966
+ case "error":
967
+ case "failed":
968
+ case "failure":
969
+ return "error";
970
+ case "cancelled":
971
+ case "canceled":
972
+ return "cancelled";
973
+ case "pending":
974
+ return "pending";
975
+ case "running":
976
+ case "in_progress":
977
+ return "running";
978
+ default:
979
+ return status ? "completed" : "running";
980
+ }
981
+ }
982
+ function extractGeminiToolOutput(tc) {
983
+ if (Array.isArray(tc.result)) {
984
+ for (const r of tc.result) {
985
+ const output = r?.functionResponse?.response?.output;
986
+ if (typeof output === "string" && output) return output;
987
+ if (output !== void 0 && output !== null) {
988
+ try {
989
+ return JSON.stringify(output, null, 2);
990
+ } catch {
991
+ }
992
+ }
993
+ }
994
+ }
995
+ if (typeof tc.resultDisplay === "string" && tc.resultDisplay) {
996
+ return tc.resultDisplay;
997
+ }
998
+ return "";
753
999
  }
754
1000
  var GeminiCliParserV2 = class {
755
1001
  name = "gemini-cli-v2";
@@ -766,41 +1012,134 @@ var GeminiCliParserV2 = class {
766
1012
  }
767
1013
  const history = findGeminiHistory(root);
768
1014
  if (!history) return [];
1015
+ const pendingLegacyCalls = /* @__PURE__ */ new Map();
769
1016
  const ops = [];
770
1017
  history.forEach((item, index) => {
771
- const role = geminiRole(item.role);
772
- if (!role) return;
773
- const parts = geminiPartsFromItem(item);
774
- const timestamp = item.timestamp ?? item.createdAt ?? item.createTime ?? now();
775
- const turnId = item.id ?? `gemini-${index}`;
776
- const blocks = [];
777
- for (const part of parts) {
778
- const text = typeof part.text === "string" ? part.text.trim() : "";
779
- if (text) {
1018
+ const itemOps = this.parseHistoryItem(item, index, pendingLegacyCalls);
1019
+ ops.push(...itemOps);
1020
+ });
1021
+ return ops;
1022
+ }
1023
+ parseHistoryItem(item, index, pendingLegacyCalls) {
1024
+ const timestamp = item.timestamp ?? item.createdAt ?? item.createTime ?? now();
1025
+ const fallbackTurnId = `gemini-${index}`;
1026
+ const turnId = item.id ?? fallbackTurnId;
1027
+ if (typeof item.type === "string") {
1028
+ return this.parseNewSchemaItem(item, turnId, timestamp);
1029
+ }
1030
+ return this.parseLegacySchemaItem(item, turnId, timestamp, pendingLegacyCalls);
1031
+ }
1032
+ parseNewSchemaItem(item, turnId, timestamp) {
1033
+ const role = geminiRoleFromType(item.type);
1034
+ if (!role) return [];
1035
+ const text = geminiTextFromNewItem(item);
1036
+ const blocks = [];
1037
+ if (text) {
1038
+ blocks.push({
1039
+ op: "insert_block",
1040
+ turnId,
1041
+ block: makeTextBlock(genBlockId("txt"), text, true)
1042
+ });
1043
+ }
1044
+ if (role === "assistant" && Array.isArray(item.toolCalls)) {
1045
+ for (const tc of item.toolCalls) {
1046
+ if (!tc.name) continue;
1047
+ const callId = tc.id ?? genBlockId("call");
1048
+ const blockId = genBlockId("tool");
1049
+ const state = mapGeminiToolStatus(tc.status);
1050
+ const isComplete = state === "completed" || state === "error" || state === "cancelled";
1051
+ blocks.push({
1052
+ op: "insert_block",
1053
+ turnId,
1054
+ block: makeToolBlock(blockId, callId, tc.name, state, tc.args, isComplete)
1055
+ });
1056
+ const output = extractGeminiToolOutput(tc);
1057
+ if (output) {
780
1058
  blocks.push({
781
- op: "insert_block",
1059
+ op: "append_tool_output",
782
1060
  turnId,
783
- block: makeTextBlock(genBlockId("txt"), text, true)
1061
+ blockId,
1062
+ text: output
784
1063
  });
785
1064
  }
786
- if (part.functionCall?.name) {
787
- const callId = genBlockId("call");
788
- blocks.push({
789
- op: "insert_block",
790
- turnId,
791
- block: makeToolBlock(
792
- genBlockId("tool"),
793
- callId,
794
- part.functionCall.name,
795
- "running",
796
- part.functionCall.args
797
- )
798
- });
1065
+ }
1066
+ }
1067
+ if (blocks.length === 0) return [];
1068
+ return [
1069
+ {
1070
+ op: "upsert_turn",
1071
+ turn: {
1072
+ turnId,
1073
+ role,
1074
+ startedAt: timestamp,
1075
+ updatedAt: timestamp,
1076
+ isComplete: false
799
1077
  }
800
- if (part.functionResponse?.name) {
801
- const output = part.functionResponse.response;
1078
+ },
1079
+ ...blocks,
1080
+ {
1081
+ op: "complete_turn",
1082
+ turnId,
1083
+ endedAt: timestamp,
1084
+ updatedAt: timestamp
1085
+ }
1086
+ ];
1087
+ }
1088
+ parseLegacySchemaItem(item, turnId, timestamp, pendingLegacyCalls) {
1089
+ const role = geminiRoleFromLegacy(item.role);
1090
+ if (!role) return [];
1091
+ const parts = geminiPartsFromLegacyItem(item);
1092
+ const currentTurnBlocks = [];
1093
+ const priorTurnUpdates = [];
1094
+ for (const part of parts) {
1095
+ const text = typeof part.text === "string" ? part.text.trim() : "";
1096
+ if (text) {
1097
+ currentTurnBlocks.push({
1098
+ op: "insert_block",
1099
+ turnId,
1100
+ block: makeTextBlock(genBlockId("txt"), text, true)
1101
+ });
1102
+ }
1103
+ if (part.functionCall?.name) {
1104
+ const callId = genBlockId("call");
1105
+ const blockId = genBlockId("tool");
1106
+ pendingLegacyCalls.set(part.functionCall.name, { turnId, blockId });
1107
+ currentTurnBlocks.push({
1108
+ op: "insert_block",
1109
+ turnId,
1110
+ block: makeToolBlock(
1111
+ blockId,
1112
+ callId,
1113
+ part.functionCall.name,
1114
+ "running",
1115
+ part.functionCall.args
1116
+ )
1117
+ });
1118
+ }
1119
+ if (part.functionResponse?.name) {
1120
+ const output = part.functionResponse.response;
1121
+ const outputText = output === void 0 || output === null ? "" : typeof output === "string" ? output : JSON.stringify(output, null, 2);
1122
+ const existing = pendingLegacyCalls.get(part.functionResponse.name);
1123
+ if (existing) {
1124
+ pendingLegacyCalls.delete(part.functionResponse.name);
1125
+ priorTurnUpdates.push({
1126
+ op: "set_tool_state",
1127
+ turnId: existing.turnId,
1128
+ blockId: existing.blockId,
1129
+ state: "completed",
1130
+ isComplete: true
1131
+ });
1132
+ if (outputText) {
1133
+ priorTurnUpdates.push({
1134
+ op: "append_tool_output",
1135
+ turnId: existing.turnId,
1136
+ blockId: existing.blockId,
1137
+ text: outputText
1138
+ });
1139
+ }
1140
+ } else {
802
1141
  const blockId = genBlockId("tool");
803
- blocks.push({
1142
+ currentTurnBlocks.push({
804
1143
  op: "insert_block",
805
1144
  turnId,
806
1145
  block: makeToolBlock(
@@ -812,17 +1151,20 @@ var GeminiCliParserV2 = class {
812
1151
  true
813
1152
  )
814
1153
  });
815
- if (output !== void 0) {
816
- blocks.push({
1154
+ if (outputText) {
1155
+ currentTurnBlocks.push({
817
1156
  op: "append_tool_output",
818
1157
  turnId,
819
1158
  blockId,
820
- text: typeof output === "string" ? output : JSON.stringify(output, null, 2)
1159
+ text: outputText
821
1160
  });
822
1161
  }
823
1162
  }
824
1163
  }
825
- if (blocks.length === 0) return;
1164
+ }
1165
+ const ops = [];
1166
+ ops.push(...priorTurnUpdates);
1167
+ if (currentTurnBlocks.length > 0) {
826
1168
  ops.push({
827
1169
  op: "upsert_turn",
828
1170
  turn: {
@@ -833,14 +1175,14 @@ var GeminiCliParserV2 = class {
833
1175
  isComplete: false
834
1176
  }
835
1177
  });
836
- ops.push(...blocks);
1178
+ ops.push(...currentTurnBlocks);
837
1179
  ops.push({
838
1180
  op: "complete_turn",
839
1181
  turnId,
840
1182
  endedAt: timestamp,
841
1183
  updatedAt: timestamp
842
1184
  });
843
- });
1185
+ }
844
1186
  return ops;
845
1187
  }
846
1188
  };
@@ -1158,8 +1500,17 @@ var ClaudeCodeParser = class {
1158
1500
  return "";
1159
1501
  }
1160
1502
  };
1503
+ function extractCodexLineText(content) {
1504
+ if (!Array.isArray(content)) return "";
1505
+ return content.filter(
1506
+ (c) => (c.type === "input_text" || c.type === "output_text" || c.type === "text") && typeof c.text === "string"
1507
+ ).map((c) => c.text).join("\n").trim();
1508
+ }
1161
1509
  var CodexCliParser = class {
1162
1510
  name = "codex-cli";
1511
+ /** call_id → tool name, populated by function_call and consumed by function_call_output. */
1512
+ pendingTools = /* @__PURE__ */ new Map();
1513
+ turnCounter = 0;
1163
1514
  parseLine(line) {
1164
1515
  let obj;
1165
1516
  try {
@@ -1167,26 +1518,106 @@ var CodexCliParser = class {
1167
1518
  } catch {
1168
1519
  return null;
1169
1520
  }
1521
+ if (obj.response_item) {
1522
+ return this.parseLegacy(obj);
1523
+ }
1524
+ if (obj.type === "response_item" && obj.payload) {
1525
+ return this.parseNewEnvelope(obj.payload, obj.timestamp);
1526
+ }
1527
+ return null;
1528
+ }
1529
+ parseLegacy(obj) {
1170
1530
  const item = obj.response_item;
1171
- if (!item) return null;
1172
- if (item.type === "response.output_item.done" && item.item?.type === "message") {
1173
- const textParts = (item.item.content ?? []).filter((c) => c.type === "text" && typeof c.text === "string").map((c) => c.text);
1174
- const text = textParts.join("\n").trim();
1531
+ if (item.type !== "response.output_item.done") return null;
1532
+ if (item.item?.type !== "message") return null;
1533
+ const text = extractCodexLineText(item.item.content);
1534
+ if (!text) return null;
1535
+ return {
1536
+ turnId: item.id ?? this.genTurnId(obj.timestamp),
1537
+ role: "assistant",
1538
+ content: text,
1539
+ timestamp: obj.timestamp ?? (/* @__PURE__ */ new Date()).toISOString()
1540
+ };
1541
+ }
1542
+ parseNewEnvelope(payload, lineTimestamp) {
1543
+ const timestamp = lineTimestamp ?? (/* @__PURE__ */ new Date()).toISOString();
1544
+ if (payload.type === "message") {
1545
+ const role = payload.role === "user" ? "user" : payload.role === "assistant" ? "assistant" : null;
1546
+ if (!role) return null;
1547
+ const text = extractCodexLineText(payload.content);
1175
1548
  if (!text) return null;
1176
1549
  return {
1177
- turnId: item.id ?? `codex-${Date.now()}-${Math.random().toString(36).slice(2, 8)}`,
1178
- role: "assistant",
1550
+ turnId: this.genTurnId(lineTimestamp),
1551
+ role,
1179
1552
  content: text,
1180
- timestamp: obj.timestamp ?? (/* @__PURE__ */ new Date()).toISOString()
1553
+ timestamp
1554
+ };
1555
+ }
1556
+ if (payload.type === "function_call") {
1557
+ if (!payload.name) return null;
1558
+ if (payload.call_id) {
1559
+ this.pendingTools.set(payload.call_id, payload.name);
1560
+ this.capPendingTools();
1561
+ }
1562
+ let input;
1563
+ if (typeof payload.arguments === "string" && payload.arguments) {
1564
+ try {
1565
+ input = JSON.parse(payload.arguments);
1566
+ } catch {
1567
+ input = void 0;
1568
+ }
1569
+ }
1570
+ return {
1571
+ turnId: this.genTurnId(lineTimestamp),
1572
+ role: "assistant",
1573
+ content: "",
1574
+ tools: [{ name: payload.name, input, status: "started" }],
1575
+ timestamp
1576
+ };
1577
+ }
1578
+ if (payload.type === "function_call_output") {
1579
+ if (!payload.call_id) return null;
1580
+ const toolName = this.pendingTools.get(payload.call_id) ?? "tool";
1581
+ this.pendingTools.delete(payload.call_id);
1582
+ return {
1583
+ turnId: this.genTurnId(lineTimestamp),
1584
+ role: "user",
1585
+ content: "",
1586
+ tools: [
1587
+ {
1588
+ name: toolName,
1589
+ status: "completed",
1590
+ result: payload.output || void 0
1591
+ }
1592
+ ],
1593
+ timestamp
1181
1594
  };
1182
1595
  }
1183
1596
  return null;
1184
1597
  }
1598
+ genTurnId(timestamp) {
1599
+ this.turnCounter += 1;
1600
+ const ts = timestamp ?? (/* @__PURE__ */ new Date()).toISOString();
1601
+ return `codex-${ts}-${this.turnCounter.toString(36)}`;
1602
+ }
1603
+ capPendingTools() {
1604
+ if (this.pendingTools.size <= 500) return;
1605
+ const keys = [...this.pendingTools.keys()];
1606
+ for (let i = 0; i < 250; i += 1) {
1607
+ this.pendingTools.delete(keys[i]);
1608
+ }
1609
+ }
1185
1610
  };
1186
1611
  function isGeminiHistoryItem2(value) {
1187
1612
  if (!value || typeof value !== "object") return false;
1188
1613
  const item = value;
1189
- return Boolean(item.role && (item.parts || item.content || item.message));
1614
+ if (typeof item.role === "string" && (item.parts !== void 0 || item.content !== void 0 || item.message !== void 0)) {
1615
+ return true;
1616
+ }
1617
+ if (typeof item.type === "string" && (item.content !== void 0 || Array.isArray(item.toolCalls))) {
1618
+ return true;
1619
+ }
1620
+ return false;
1190
1621
  }
1191
1622
  function findGeminiHistory2(value, depth = 0) {
1192
1623
  if (depth > 6 || !value || typeof value !== "object") return null;
@@ -1209,15 +1640,63 @@ function findGeminiHistory2(value, depth = 0) {
1209
1640
  }
1210
1641
  return null;
1211
1642
  }
1212
- function geminiPartsFromItem2(item) {
1643
+ function geminiPartsFromLegacyItem2(item) {
1213
1644
  if (Array.isArray(item.parts)) return item.parts;
1214
1645
  const source = item.content ?? item.message;
1215
1646
  if (typeof source === "string") return [{ text: source }];
1216
- if (source && typeof source === "object" && Array.isArray(source.parts)) {
1217
- return source.parts;
1647
+ if (source && !Array.isArray(source) && typeof source === "object") {
1648
+ const objSource = source;
1649
+ if (Array.isArray(objSource.parts)) return objSource.parts;
1218
1650
  }
1219
1651
  return [];
1220
1652
  }
1653
+ function geminiTextFromNewItem2(item) {
1654
+ const c = item.content;
1655
+ if (typeof c === "string") return c.trim();
1656
+ if (Array.isArray(c)) {
1657
+ return c.map((p) => p && typeof p.text === "string" ? p.text : "").filter(Boolean).join("\n").trim();
1658
+ }
1659
+ return "";
1660
+ }
1661
+ function geminiToolStatusToEvent(status) {
1662
+ switch (status?.toLowerCase()) {
1663
+ case "success":
1664
+ case "completed":
1665
+ case "ok":
1666
+ return "completed";
1667
+ case "error":
1668
+ case "failed":
1669
+ case "failure":
1670
+ return "error";
1671
+ case "cancelled":
1672
+ case "canceled":
1673
+ return "completed";
1674
+ case "pending":
1675
+ case "running":
1676
+ case "in_progress":
1677
+ return "started";
1678
+ default:
1679
+ return status ? "completed" : "started";
1680
+ }
1681
+ }
1682
+ function geminiToolResult(tc) {
1683
+ if (Array.isArray(tc.result)) {
1684
+ for (const r of tc.result) {
1685
+ const output = r?.functionResponse?.response?.output;
1686
+ if (typeof output === "string" && output) return output;
1687
+ if (output !== void 0 && output !== null) {
1688
+ try {
1689
+ return JSON.stringify(output, null, 2);
1690
+ } catch {
1691
+ }
1692
+ }
1693
+ }
1694
+ }
1695
+ if (typeof tc.resultDisplay === "string" && tc.resultDisplay) {
1696
+ return tc.resultDisplay;
1697
+ }
1698
+ return void 0;
1699
+ }
1221
1700
  var GeminiCliParser = class {
1222
1701
  name = "gemini-cli";
1223
1702
  parseLine(line) {
@@ -1235,43 +1714,79 @@ var GeminiCliParser = class {
1235
1714
  if (!history) return [];
1236
1715
  const turns = [];
1237
1716
  history.forEach((item, index) => {
1238
- const normalizedRole = item.role?.toLowerCase();
1239
- const role = normalizedRole === "model" || normalizedRole === "assistant" ? "assistant" : normalizedRole === "user" ? "user" : null;
1240
- if (!role) return;
1241
- const textBlocks = [];
1242
- const tools = [];
1243
- for (const part of geminiPartsFromItem2(item)) {
1244
- if (typeof part.text === "string" && part.text.trim()) {
1245
- textBlocks.push(part.text.trim());
1246
- }
1247
- if (part.functionCall?.name) {
1248
- tools.push({
1249
- name: part.functionCall.name,
1250
- input: part.functionCall.args,
1251
- status: "started"
1252
- });
1253
- }
1254
- if (part.functionResponse?.name) {
1255
- const response = part.functionResponse.response;
1256
- tools.push({
1257
- name: part.functionResponse.name,
1258
- status: "completed",
1259
- result: response === void 0 ? void 0 : typeof response === "string" ? response : JSON.stringify(response, null, 2)
1260
- });
1261
- }
1262
- }
1263
- const content = textBlocks.join("\n").trim();
1264
- if (!content && tools.length === 0) return;
1265
- turns.push({
1266
- turnId: item.id ?? `gemini-${index}`,
1267
- role,
1268
- content,
1269
- tools: tools.length > 0 ? tools : void 0,
1270
- timestamp: item.timestamp ?? item.createdAt ?? item.createTime ?? (/* @__PURE__ */ new Date()).toISOString()
1271
- });
1717
+ const parsed = this.parseHistoryItem(item, index);
1718
+ if (parsed) turns.push(parsed);
1272
1719
  });
1273
1720
  return turns;
1274
1721
  }
1722
+ parseHistoryItem(item, index) {
1723
+ if (typeof item.type === "string") {
1724
+ return this.parseNewSchemaItem(item, index);
1725
+ }
1726
+ return this.parseLegacySchemaItem(item, index);
1727
+ }
1728
+ parseNewSchemaItem(item, index) {
1729
+ const t = item.type;
1730
+ const role = t === "user" ? "user" : t === "gemini" || t === "model" || t === "assistant" ? "assistant" : null;
1731
+ if (!role) return null;
1732
+ const content = geminiTextFromNewItem2(item);
1733
+ const tools = [];
1734
+ if (role === "assistant" && Array.isArray(item.toolCalls)) {
1735
+ for (const tc of item.toolCalls) {
1736
+ if (!tc.name) continue;
1737
+ tools.push({
1738
+ name: tc.name,
1739
+ input: tc.args,
1740
+ status: geminiToolStatusToEvent(tc.status),
1741
+ result: geminiToolResult(tc)
1742
+ });
1743
+ }
1744
+ }
1745
+ if (!content && tools.length === 0) return null;
1746
+ return {
1747
+ turnId: item.id ?? `gemini-${index}`,
1748
+ role,
1749
+ content,
1750
+ tools: tools.length > 0 ? tools : void 0,
1751
+ timestamp: item.timestamp ?? item.createdAt ?? item.createTime ?? (/* @__PURE__ */ new Date()).toISOString()
1752
+ };
1753
+ }
1754
+ parseLegacySchemaItem(item, index) {
1755
+ const normalizedRole = item.role?.toLowerCase();
1756
+ const role = normalizedRole === "model" || normalizedRole === "assistant" ? "assistant" : normalizedRole === "user" ? "user" : null;
1757
+ if (!role) return null;
1758
+ const textBlocks = [];
1759
+ const tools = [];
1760
+ for (const part of geminiPartsFromLegacyItem2(item)) {
1761
+ if (typeof part.text === "string" && part.text.trim()) {
1762
+ textBlocks.push(part.text.trim());
1763
+ }
1764
+ if (part.functionCall?.name) {
1765
+ tools.push({
1766
+ name: part.functionCall.name,
1767
+ input: part.functionCall.args,
1768
+ status: "started"
1769
+ });
1770
+ }
1771
+ if (part.functionResponse?.name) {
1772
+ const response = part.functionResponse.response;
1773
+ tools.push({
1774
+ name: part.functionResponse.name,
1775
+ status: "completed",
1776
+ result: response === void 0 ? void 0 : typeof response === "string" ? response : JSON.stringify(response, null, 2)
1777
+ });
1778
+ }
1779
+ }
1780
+ const content = textBlocks.join("\n").trim();
1781
+ if (!content && tools.length === 0) return null;
1782
+ return {
1783
+ turnId: item.id ?? `gemini-${index}`,
1784
+ role,
1785
+ content,
1786
+ tools: tools.length > 0 ? tools : void 0,
1787
+ timestamp: item.timestamp ?? item.createdAt ?? item.createTime ?? (/* @__PURE__ */ new Date()).toISOString()
1788
+ };
1789
+ }
1275
1790
  };
1276
1791
  function parseProcessPid(sessionId) {
1277
1792
  if (!sessionId.startsWith("process:")) return null;
@@ -2930,10 +3445,39 @@ var BACKPRESSURE_RESUME_CHECK_MS = 100;
2930
3445
  var STOP_DEBOUNCE_MS = 2e3;
2931
3446
  var MAX_CONCURRENT_STREAMS = 20;
2932
3447
  var MAX_SESSION_BUFFER_BYTES = 64 * 1024;
2933
- var ALT_SCREEN_MODE_RE = /\x1b\[\?(?=[0-9;]*(?:47|1047|1048|1049)(?:;|[hl]))[0-9;]*[hl]/g;
3448
+ var PRIVATE_MODE_SEQUENCE_RE = /\x1b\[\?([0-9;]*)([hl])/g;
3449
+ var ALT_SCREEN_PARAMS = /* @__PURE__ */ new Set(["47", "1047", "1048", "1049"]);
2934
3450
  function stripAlternateScreenModeSwitches(data) {
2935
- return data.replace(ALT_SCREEN_MODE_RE, "");
3451
+ return data.replace(
3452
+ PRIVATE_MODE_SEQUENCE_RE,
3453
+ (full, params, action) => {
3454
+ if (!params) return full;
3455
+ const parts = params.split(";");
3456
+ const filtered = parts.filter((p) => !ALT_SCREEN_PARAMS.has(p));
3457
+ if (filtered.length === parts.length) return full;
3458
+ if (filtered.length === 0) return "";
3459
+ return `\x1B[?${filtered.join(";")}${action}`;
3460
+ }
3461
+ );
2936
3462
  }
3463
+ var AlternateScreenModeStripper = class {
3464
+ pending = "";
3465
+ write(data) {
3466
+ const combined = this.pending + data;
3467
+ this.pending = "";
3468
+ const match = combined.match(/\x1b(?:\[(?:\?(?:[0-9;]*)?)?)?$/);
3469
+ if (!match || match.index === void 0) {
3470
+ return stripAlternateScreenModeSwitches(combined);
3471
+ }
3472
+ this.pending = combined.slice(match.index);
3473
+ return stripAlternateScreenModeSwitches(combined.slice(0, match.index));
3474
+ }
3475
+ end() {
3476
+ const pending = this.pending;
3477
+ this.pending = "";
3478
+ return stripAlternateScreenModeSwitches(pending);
3479
+ }
3480
+ };
2937
3481
  function parsePaneTarget(sessionId) {
2938
3482
  if (!sessionId.startsWith("tmux:")) return null;
2939
3483
  const rest = sessionId.slice("tmux:".length);
@@ -3172,6 +3716,11 @@ var SessionStreamer = class {
3172
3716
  isStreaming(sessionId) {
3173
3717
  return this.active.has(sessionId);
3174
3718
  }
3719
+ hasViewer(sessionId, viewerId) {
3720
+ const viewerKey = viewerId?.trim() || "";
3721
+ if (!viewerKey) return false;
3722
+ return this.active.get(sessionId)?.viewerIds.has(viewerKey) ?? false;
3723
+ }
3175
3724
  /**
3176
3725
  * Stop all active streams immediately (no debounce).
3177
3726
  */
@@ -3253,11 +3802,19 @@ var SessionStreamer = class {
3253
3802
  * Used when a viewer missed the initial burst (e.g. joinGroup race)
3254
3803
  * or reconnected ("Retry stream"). Does NOT restart pipe-pane.
3255
3804
  */
3256
- resyncStream(sessionId) {
3805
+ resyncStream(sessionId, viewerId) {
3257
3806
  const stream = this.active.get(sessionId);
3258
3807
  if (!stream || stream.stopped || stream.streamType !== "tmux-pipe") return false;
3259
3808
  const { paneTarget, cleanEnv } = stream;
3260
3809
  if (!paneTarget || !cleanEnv) return false;
3810
+ const targetViewerId = viewerId?.trim() || void 0;
3811
+ const send = (data) => {
3812
+ if (targetViewerId) {
3813
+ this.sendFn(sessionId, data, targetViewerId);
3814
+ } else {
3815
+ this.sendFn(sessionId, data);
3816
+ }
3817
+ };
3261
3818
  stream.initializing = true;
3262
3819
  try {
3263
3820
  let alternateScreen = false;
@@ -3277,11 +3834,11 @@ var SessionStreamer = class {
3277
3834
  { env: cleanEnv, timeout: 1e4, encoding: "utf-8" }
3278
3835
  );
3279
3836
  if (scrollback && scrollback.length > 0) {
3280
- this.sendFn(sessionId, scrollback.replace(/\n/g, "\r\n"));
3837
+ send(scrollback.replace(/\n/g, "\r\n"));
3281
3838
  }
3282
3839
  } catch {
3283
3840
  }
3284
- this.sendFn(sessionId, "\x1B[?1049l\x1B[0m\x1B[2J\x1B[H");
3841
+ send("\x1B[?1049l\x1B[0m\x1B[2J\x1B[H");
3285
3842
  try {
3286
3843
  const captureArgs = alternateScreen ? ["capture-pane", "-a", "-t", paneTarget, "-p", "-e"] : ["capture-pane", "-t", paneTarget, "-p", "-e"];
3287
3844
  const initial = (0, import_node_child_process2.execFileSync)(
@@ -3290,7 +3847,7 @@ var SessionStreamer = class {
3290
3847
  { env: cleanEnv, timeout: 5e3, encoding: "utf-8" }
3291
3848
  );
3292
3849
  if (initial) {
3293
- this.sendFn(sessionId, stripAlternateScreenModeSwitches(initial.replace(/\n/g, "\r\n").replace(/\r\n$/, "")));
3850
+ send(stripAlternateScreenModeSwitches(initial.replace(/\n/g, "\r\n").replace(/\r\n$/, "")));
3294
3851
  }
3295
3852
  } catch {
3296
3853
  }
@@ -3305,7 +3862,7 @@ var SessionStreamer = class {
3305
3862
  const x = parseInt(parts[0], 10);
3306
3863
  const y = parseInt(parts[1], 10);
3307
3864
  if (!isNaN(x) && !isNaN(y)) {
3308
- this.sendFn(sessionId, `\x1B[${y + 1};${x + 1}H`);
3865
+ send(`\x1B[${y + 1};${x + 1}H`);
3309
3866
  }
3310
3867
  }
3311
3868
  } catch {
@@ -3359,7 +3916,8 @@ var SessionStreamer = class {
3359
3916
  cleanEnv,
3360
3917
  ptyProc: null,
3361
3918
  straceProc: null,
3362
- utf8Decoder: new import_node_string_decoder.StringDecoder("utf8")
3919
+ utf8Decoder: new import_node_string_decoder.StringDecoder("utf8"),
3920
+ altScreenStripper: new AlternateScreenModeStripper()
3363
3921
  };
3364
3922
  this.active.set(sessionId, stream);
3365
3923
  try {
@@ -3383,13 +3941,19 @@ var SessionStreamer = class {
3383
3941
  if (stream.initializing) {
3384
3942
  stream.initBuffer.push(data);
3385
3943
  } else {
3386
- this.sendFn(sessionId, stripAlternateScreenModeSwitches(data));
3944
+ const filtered = stream.altScreenStripper.write(data);
3945
+ if (filtered) this.sendFn(sessionId, filtered);
3387
3946
  }
3388
3947
  });
3389
3948
  catProc.on("exit", () => {
3390
3949
  if (!stream.stopped) {
3391
3950
  const remaining = stream.utf8Decoder.end();
3392
- if (remaining) this.sendFn(sessionId, stripAlternateScreenModeSwitches(remaining));
3951
+ if (remaining) {
3952
+ const filtered = stream.altScreenStripper.write(remaining);
3953
+ if (filtered) this.sendFn(sessionId, filtered);
3954
+ }
3955
+ const pending = stream.altScreenStripper.end();
3956
+ if (pending) this.sendFn(sessionId, pending);
3393
3957
  this.cleanupStream(stream);
3394
3958
  this.active.delete(sessionId);
3395
3959
  this.pendingStops.delete(sessionId);
@@ -3485,7 +4049,8 @@ var SessionStreamer = class {
3485
4049
  initBuffer: [],
3486
4050
  ptyProc: proc,
3487
4051
  straceProc: null,
3488
- utf8Decoder: new import_node_string_decoder.StringDecoder("utf8")
4052
+ utf8Decoder: new import_node_string_decoder.StringDecoder("utf8"),
4053
+ altScreenStripper: new AlternateScreenModeStripper()
3489
4054
  };
3490
4055
  this.active.set(sessionId, stream);
3491
4056
  proc.onData((data) => {
@@ -3538,7 +4103,8 @@ var SessionStreamer = class {
3538
4103
  initBuffer: [],
3539
4104
  ptyProc: proc,
3540
4105
  straceProc: null,
3541
- utf8Decoder: new import_node_string_decoder.StringDecoder("utf8")
4106
+ utf8Decoder: new import_node_string_decoder.StringDecoder("utf8"),
4107
+ altScreenStripper: new AlternateScreenModeStripper()
3542
4108
  };
3543
4109
  this.active.set(sessionId, stream);
3544
4110
  proc.onData((data) => {
@@ -3603,7 +4169,8 @@ var SessionStreamer = class {
3603
4169
  initBuffer: [],
3604
4170
  ptyProc: null,
3605
4171
  straceProc,
3606
- utf8Decoder: new import_node_string_decoder.StringDecoder("utf8")
4172
+ utf8Decoder: new import_node_string_decoder.StringDecoder("utf8"),
4173
+ altScreenStripper: new AlternateScreenModeStripper()
3607
4174
  };
3608
4175
  this.active.set(sessionId, stream);
3609
4176
  const writeRegex = /^write\(([12]),\s*"((?:[^"\\]|\\.)*)"/;
@@ -3805,6 +4372,7 @@ var ANSI_KEY_SEQUENCES = /* @__PURE__ */ new Map([
3805
4372
  ["\x1B[5~", "PageUp"],
3806
4373
  ["\x1B[6~", "PageDown"]
3807
4374
  ]);
4375
+ var ANSI_KEY_ENTRIES = Array.from(ANSI_KEY_SEQUENCES.entries());
3808
4376
  function controlKeyName(charCode) {
3809
4377
  if (charCode >= 1 && charCode <= 26) {
3810
4378
  return `C-${String.fromCharCode(charCode + 96)}`;
@@ -3843,8 +4411,12 @@ function encodeTmuxInput(data) {
3843
4411
  literal = "";
3844
4412
  };
3845
4413
  for (let i = 0; i < data.length; i++) {
3846
- const remaining = data.slice(i);
3847
- const matchedSequence = Array.from(ANSI_KEY_SEQUENCES.entries()).find(([sequence]) => remaining.startsWith(sequence));
4414
+ let matchedSequence;
4415
+ if (data.charCodeAt(i) === 27) {
4416
+ matchedSequence = ANSI_KEY_ENTRIES.find(
4417
+ ([sequence]) => data.startsWith(sequence, i)
4418
+ );
4419
+ }
3848
4420
  if (matchedSequence) {
3849
4421
  flushLiteral();
3850
4422
  const [sequence, key] = matchedSequence;
@@ -3932,12 +4504,30 @@ async function sendInputToTmux(sessionId, data) {
3932
4504
  return;
3933
4505
  }
3934
4506
  try {
3935
- for (const segment of encodeTmuxInput(data)) {
3936
- if (segment.kind === "literal") {
3937
- await execTmuxSendKeys(["send-keys", "-t", target, "-l", "--", segment.value]);
3938
- } else {
3939
- await execTmuxSendKeys(["send-keys", "-t", target, "--", segment.value]);
4507
+ const segments = encodeTmuxInput(data);
4508
+ let i = 0;
4509
+ while (i < segments.length) {
4510
+ const seg = segments[i];
4511
+ if (seg.kind === "literal") {
4512
+ await execTmuxSendKeys([
4513
+ "send-keys",
4514
+ "-t",
4515
+ target,
4516
+ "-l",
4517
+ "--",
4518
+ seg.value
4519
+ ]);
4520
+ i += 1;
4521
+ continue;
3940
4522
  }
4523
+ const keys = [seg.value];
4524
+ let j = i + 1;
4525
+ while (j < segments.length && segments[j].kind === "key") {
4526
+ keys.push(segments[j].value);
4527
+ j += 1;
4528
+ }
4529
+ await execTmuxSendKeys(["send-keys", "-t", target, "--", ...keys]);
4530
+ i = j;
3941
4531
  }
3942
4532
  } catch (error) {
3943
4533
  const message = error instanceof Error ? error.message : String(error);
@@ -4186,11 +4776,15 @@ var ConnectionManager = class {
4186
4776
  * the field.
4187
4777
  */
4188
4778
  getRxSeqForViewer(viewerId) {
4189
- const key = typeof viewerId === "string" && viewerId.length > 0 ? viewerId : "_legacy";
4779
+ const isValid = typeof viewerId === "string" && viewerId.length > 0;
4780
+ const key = isValid ? viewerId : "_legacy";
4190
4781
  let tracker = this.rxSeqByViewer.get(key);
4191
4782
  if (!tracker) {
4192
4783
  tracker = new SequenceTracker();
4193
4784
  this.rxSeqByViewer.set(key, tracker);
4785
+ if (!isValid) {
4786
+ log11.warn("rxSeq fell back to _legacy bucket \u2014 portal sent empty viewerId", { typeofViewerId: typeof viewerId });
4787
+ }
4194
4788
  }
4195
4789
  return tracker;
4196
4790
  }
@@ -5025,6 +5619,7 @@ async function installLatestCliFromNpm() {
5025
5619
  // src/control-dispatcher.ts
5026
5620
  var log13 = createLogger("control");
5027
5621
  var TMUX_RESYNC_DEBOUNCE_MS = 150;
5622
+ var TMUX_INPUT_COALESCE_MS = 4;
5028
5623
  function collectProcessTreePids(rootPid, visited = /* @__PURE__ */ new Set()) {
5029
5624
  if (visited.has(rootPid)) return [];
5030
5625
  visited.add(rootPid);
@@ -5067,6 +5662,7 @@ var ControlDispatcher = class {
5067
5662
  options;
5068
5663
  _approvalEnforcer = null;
5069
5664
  tmuxResizeState = /* @__PURE__ */ new Map();
5665
+ tmuxInputBuffers = /* @__PURE__ */ new Map();
5070
5666
  /** Set to true when a reconnect was requested (restart-agent, disconnect). */
5071
5667
  reconnectRequested = false;
5072
5668
  /** Set to false to stop the agent. */
@@ -5318,14 +5914,32 @@ var ControlDispatcher = class {
5318
5914
  if (!sessionId || sessionId.startsWith("pty:")) {
5319
5915
  this.shell.write(data);
5320
5916
  } else if (sessionId.startsWith("tmux:")) {
5321
- sendInputToTmux(sessionId, data).catch(() => {
5322
- });
5917
+ this.queueTmuxInput(sessionId, data);
5323
5918
  } else if (sessionId.startsWith("screen:") || sessionId.startsWith("zellij:")) {
5324
5919
  this.streamer.writeInput(sessionId, data);
5325
5920
  }
5326
5921
  }
5922
+ /** Coalesce per-keystroke input into one `tmux send-keys` spawn. */
5923
+ queueTmuxInput(sessionId, data) {
5924
+ const existing = this.tmuxInputBuffers.get(sessionId);
5925
+ if (existing) {
5926
+ existing.data += data;
5927
+ return;
5928
+ }
5929
+ const buffer = { data, timer: null };
5930
+ buffer.timer = setTimeout(() => this.flushTmuxInput(sessionId), TMUX_INPUT_COALESCE_MS);
5931
+ this.tmuxInputBuffers.set(sessionId, buffer);
5932
+ }
5933
+ flushTmuxInput(sessionId) {
5934
+ const buffer = this.tmuxInputBuffers.get(sessionId);
5935
+ if (!buffer) return;
5936
+ this.tmuxInputBuffers.delete(sessionId);
5937
+ if (buffer.timer) clearTimeout(buffer.timer);
5938
+ sendInputToTmux(sessionId, buffer.data).catch(() => {
5939
+ });
5940
+ }
5327
5941
  /** Handle resize routing. */
5328
- handleResize(cols, rows, sessionId) {
5942
+ handleResize(cols, rows, sessionId, viewerId) {
5329
5943
  if (!sessionId || sessionId.startsWith("pty:")) {
5330
5944
  this.shell.resize(cols, rows);
5331
5945
  } else if (sessionId.startsWith("tmux:")) {
@@ -5347,7 +5961,7 @@ var ControlDispatcher = class {
5347
5961
  const latest = this.tmuxResizeState.get(sessionId);
5348
5962
  if (latest === current) {
5349
5963
  latest.timer = null;
5350
- this.streamer.resyncStream(sessionId);
5964
+ this.streamer.resyncStream(sessionId, viewerId);
5351
5965
  }
5352
5966
  }, TMUX_RESYNC_DEBOUNCE_MS);
5353
5967
  }).catch((error) => {
@@ -5558,13 +6172,15 @@ var ControlDispatcher = class {
5558
6172
  if (!sessionId) return;
5559
6173
  const ws = this.connection.activeSocket;
5560
6174
  const group = this.connection.activeGroups.privateGroup;
6175
+ const viewerKey = viewerId?.trim() || "";
5561
6176
  if (sessionId.startsWith("tmux:") || sessionId.startsWith("screen:") || sessionId.startsWith("zellij:") || sessionId.startsWith("process:")) {
5562
6177
  const alreadyStreaming = this.streamer.isStreaming(sessionId);
6178
+ const viewerAlreadyAttached = alreadyStreaming && this.streamer.hasViewer(sessionId, viewerKey);
5563
6179
  const started = this.streamer.startStream(sessionId, viewerId ?? void 0);
5564
6180
  if (ws && ws.readyState === import_ws4.default.OPEN && group) {
5565
6181
  if (started) {
5566
- if (alreadyStreaming) {
5567
- this.streamer.resyncStream(sessionId);
6182
+ if (alreadyStreaming && !viewerAlreadyAttached) {
6183
+ this.streamer.resyncStream(sessionId, viewerKey);
5568
6184
  }
5569
6185
  const dims = this.queryPaneDimensions(sessionId);
5570
6186
  sendToGroup(ws, group, { type: "stream-started", sessionId, ...dims });
@@ -6033,7 +6649,7 @@ async function runAgent(rawOptions) {
6033
6649
  const outputBuffer = new OutputBuffer();
6034
6650
  const ptySessionId = `pty:${options.hostId}`;
6035
6651
  const sessionStreamer = new SessionStreamer(
6036
- (sessionId, data) => {
6652
+ (sessionId, data, viewerId) => {
6037
6653
  conn.requestInventorySync();
6038
6654
  if (!conn.isReady()) {
6039
6655
  sessionStreamer.bufferOutput(sessionId, data);
@@ -6041,11 +6657,12 @@ async function runAgent(rawOptions) {
6041
6657
  }
6042
6658
  const redactedData = redactStreamingChunk(sessionId, data);
6043
6659
  for (const chunk of splitTransportChunks(redactedData)) {
6660
+ const outputPayload = viewerId ? { type: "output", sessionId, viewerId } : { type: "output", sessionId };
6044
6661
  if (conn.sessionKey) {
6045
6662
  const { enc, iv, seq } = encryptPayload(chunk, conn.sessionKey, conn.txSeq.next());
6046
- sendToGroup(conn.activeSocket, conn.activeGroups.privateGroup, { type: "output", enc, iv, seq, sessionId });
6663
+ sendToGroup(conn.activeSocket, conn.activeGroups.privateGroup, { ...outputPayload, enc, iv, seq });
6047
6664
  } else {
6048
- sendToGroup(conn.activeSocket, conn.activeGroups.privateGroup, { type: "output", data: chunk, sessionId });
6665
+ sendToGroup(conn.activeSocket, conn.activeGroups.privateGroup, { ...outputPayload, data: chunk });
6049
6666
  }
6050
6667
  }
6051
6668
  },
@@ -6308,7 +6925,8 @@ async function runAgent(rawOptions) {
6308
6925
  return;
6309
6926
  }
6310
6927
  const sid = typeof payload.sessionId === "string" ? payload.sessionId.trim() : "";
6311
- dispatcher.handleResize(payload.cols, payload.rows, sid);
6928
+ const resizeViewerId = typeof payload.viewerId === "string" ? payload.viewerId : void 0;
6929
+ dispatcher.handleResize(payload.cols, payload.rows, sid, resizeViewerId);
6312
6930
  } else if (payload.type === "control" && typeof payload.action === "string") {
6313
6931
  let controlPayload = payload;
6314
6932
  if (typeof payload.enc === "string" && typeof payload.iv === "string" && conn.sessionKey) {
package/dist/sbom.json CHANGED
@@ -1166,8 +1166,8 @@
1166
1166
  {
1167
1167
  "type": "library",
1168
1168
  "name": "ragent-cli",
1169
- "version": "1.11.8",
1170
- "bom-ref": "ragent-live|ragent-cli@1.11.8",
1169
+ "version": "1.11.10",
1170
+ "bom-ref": "ragent-live|ragent-cli@1.11.10",
1171
1171
  "author": "Intellimetrics",
1172
1172
  "description": "CLI agent for rAgent Live — browser-first terminal control plane for AI coding agents",
1173
1173
  "licenses": [
@@ -1178,7 +1178,7 @@
1178
1178
  }
1179
1179
  }
1180
1180
  ],
1181
- "purl": "pkg:npm/ragent-cli@1.11.8",
1181
+ "purl": "pkg:npm/ragent-cli@1.11.10",
1182
1182
  "externalReferences": [
1183
1183
  {
1184
1184
  "url": "https://github.com/chadlindell/ragent-live/issues",
@@ -1303,7 +1303,7 @@
1303
1303
  "ragent-live|@emnapi/wasi-threads@1.2.1",
1304
1304
  "ragent-live|@pkgjs/parseargs@0.11.0",
1305
1305
  "ragent-live|@tybys/wasm-util@0.10.1",
1306
- "ragent-live|ragent-cli@1.11.8"
1306
+ "ragent-live|ragent-cli@1.11.10"
1307
1307
  ]
1308
1308
  },
1309
1309
  {
@@ -1436,7 +1436,7 @@
1436
1436
  ]
1437
1437
  },
1438
1438
  {
1439
- "ref": "ragent-live|ragent-cli@1.11.8",
1439
+ "ref": "ragent-live|ragent-cli@1.11.10",
1440
1440
  "dependsOn": [
1441
1441
  "ragent-live|@azure/web-pubsub-client@1.0.4",
1442
1442
  "ragent-live|commander@14.0.3",
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "ragent-cli",
3
- "version": "1.11.8",
3
+ "version": "1.11.10",
4
4
  "description": "CLI agent for rAgent Live — browser-first terminal control plane for AI coding agents",
5
5
  "main": "dist/index.js",
6
6
  "bin": {