ragent-cli 1.11.2 → 1.11.4

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.2",
34
+ version: "1.11.4",
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: {
@@ -708,9 +708,146 @@ var CodexCliParserV2 = class {
708
708
  return [];
709
709
  }
710
710
  };
711
+ function isGeminiHistoryItem(value) {
712
+ if (!value || typeof value !== "object") return false;
713
+ const item = value;
714
+ return Boolean(item.role && (item.parts || item.content || item.message));
715
+ }
716
+ function findGeminiHistory(value, depth = 0) {
717
+ if (depth > 6 || !value || typeof value !== "object") return null;
718
+ if (Array.isArray(value)) {
719
+ if (value.some(isGeminiHistoryItem)) {
720
+ return value.filter(isGeminiHistoryItem);
721
+ }
722
+ for (const entry of value) {
723
+ const found = findGeminiHistory(entry, depth + 1);
724
+ if (found) return found;
725
+ }
726
+ return null;
727
+ }
728
+ const obj = value;
729
+ for (const key of ["history", "messages", "turns", "contents", "conversation"]) {
730
+ const found = findGeminiHistory(obj[key], depth + 1);
731
+ if (found) return found;
732
+ }
733
+ for (const entry of Object.values(obj)) {
734
+ const found = findGeminiHistory(entry, depth + 1);
735
+ if (found) return found;
736
+ }
737
+ return null;
738
+ }
739
+ function geminiPartsFromItem(item) {
740
+ if (Array.isArray(item.parts)) return item.parts;
741
+ const source = item.content ?? item.message;
742
+ if (typeof source === "string") return [{ text: source }];
743
+ if (source && typeof source === "object" && Array.isArray(source.parts)) {
744
+ return source.parts;
745
+ }
746
+ return [];
747
+ }
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;
753
+ }
754
+ var GeminiCliParserV2 = class {
755
+ name = "gemini-cli-v2";
756
+ parseLine(line) {
757
+ return this.parseDocument(line);
758
+ }
759
+ parseDocument(document) {
760
+ let root;
761
+ try {
762
+ root = JSON.parse(document);
763
+ } catch (error) {
764
+ debugTranscriptParse("gemini", document, error);
765
+ return [];
766
+ }
767
+ const history = findGeminiHistory(root);
768
+ if (!history) return [];
769
+ const ops = [];
770
+ 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) {
780
+ blocks.push({
781
+ op: "insert_block",
782
+ turnId,
783
+ block: makeTextBlock(genBlockId("txt"), text, true)
784
+ });
785
+ }
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
+ });
799
+ }
800
+ if (part.functionResponse?.name) {
801
+ const output = part.functionResponse.response;
802
+ const blockId = genBlockId("tool");
803
+ blocks.push({
804
+ op: "insert_block",
805
+ turnId,
806
+ block: makeToolBlock(
807
+ blockId,
808
+ genBlockId("call"),
809
+ part.functionResponse.name,
810
+ "completed",
811
+ void 0,
812
+ true
813
+ )
814
+ });
815
+ if (output !== void 0) {
816
+ blocks.push({
817
+ op: "append_tool_output",
818
+ turnId,
819
+ blockId,
820
+ text: typeof output === "string" ? output : JSON.stringify(output, null, 2)
821
+ });
822
+ }
823
+ }
824
+ }
825
+ if (blocks.length === 0) return;
826
+ ops.push({
827
+ op: "upsert_turn",
828
+ turn: {
829
+ turnId,
830
+ role,
831
+ startedAt: timestamp,
832
+ updatedAt: timestamp,
833
+ isComplete: false
834
+ }
835
+ });
836
+ ops.push(...blocks);
837
+ ops.push({
838
+ op: "complete_turn",
839
+ turnId,
840
+ endedAt: timestamp,
841
+ updatedAt: timestamp
842
+ });
843
+ });
844
+ return ops;
845
+ }
846
+ };
711
847
  var V2_FACTORIES = {
712
848
  "Claude Code": () => new ClaudeCodeParserV2(),
713
- "Codex CLI": () => new CodexCliParserV2()
849
+ "Codex CLI": () => new CodexCliParserV2(),
850
+ "Gemini CLI": () => new GeminiCliParserV2()
714
851
  };
715
852
  function getParserV2(agentType) {
716
853
  if (!agentType) return void 0;
@@ -1046,18 +1183,110 @@ var CodexCliParser = class {
1046
1183
  return null;
1047
1184
  }
1048
1185
  };
1186
+ function isGeminiHistoryItem2(value) {
1187
+ if (!value || typeof value !== "object") return false;
1188
+ const item = value;
1189
+ return Boolean(item.role && (item.parts || item.content || item.message));
1190
+ }
1191
+ function findGeminiHistory2(value, depth = 0) {
1192
+ if (depth > 6 || !value || typeof value !== "object") return null;
1193
+ if (Array.isArray(value)) {
1194
+ if (value.some(isGeminiHistoryItem2)) return value.filter(isGeminiHistoryItem2);
1195
+ for (const entry of value) {
1196
+ const found = findGeminiHistory2(entry, depth + 1);
1197
+ if (found) return found;
1198
+ }
1199
+ return null;
1200
+ }
1201
+ const obj = value;
1202
+ for (const key of ["history", "messages", "turns", "contents", "conversation"]) {
1203
+ const found = findGeminiHistory2(obj[key], depth + 1);
1204
+ if (found) return found;
1205
+ }
1206
+ for (const entry of Object.values(obj)) {
1207
+ const found = findGeminiHistory2(entry, depth + 1);
1208
+ if (found) return found;
1209
+ }
1210
+ return null;
1211
+ }
1212
+ function geminiPartsFromItem2(item) {
1213
+ if (Array.isArray(item.parts)) return item.parts;
1214
+ const source = item.content ?? item.message;
1215
+ if (typeof source === "string") return [{ text: source }];
1216
+ if (source && typeof source === "object" && Array.isArray(source.parts)) {
1217
+ return source.parts;
1218
+ }
1219
+ return [];
1220
+ }
1221
+ var GeminiCliParser = class {
1222
+ name = "gemini-cli";
1223
+ parseLine(line) {
1224
+ const turns = this.parseDocument(line);
1225
+ return turns[0] ?? null;
1226
+ }
1227
+ parseDocument(document) {
1228
+ let root;
1229
+ try {
1230
+ root = JSON.parse(document);
1231
+ } catch {
1232
+ return [];
1233
+ }
1234
+ const history = findGeminiHistory2(root);
1235
+ if (!history) return [];
1236
+ const turns = [];
1237
+ 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
+ });
1272
+ });
1273
+ return turns;
1274
+ }
1275
+ };
1049
1276
  function parseProcessPid(sessionId) {
1050
1277
  if (!sessionId.startsWith("process:")) return null;
1051
1278
  const pid = parseInt(sessionId.slice("process:".length).split(":")[0] ?? "", 10);
1052
1279
  return Number.isFinite(pid) && pid > 0 ? pid : null;
1053
1280
  }
1054
1281
  function pathMatchesAgentTranscript(target, agentType) {
1055
- if (!target.endsWith(".jsonl")) return false;
1282
+ if (!target.endsWith(".jsonl") && !target.endsWith(".json")) return false;
1056
1283
  const isClaudeTranscript = target.includes("/.claude/");
1057
1284
  const isCodexTranscript = target.includes("/.codex/sessions/");
1285
+ const isGeminiTranscript = target.includes("/.gemini/tmp/") && target.includes("/chats/session-") && target.endsWith(".json");
1058
1286
  if (agentType === "Claude Code") return isClaudeTranscript;
1059
1287
  if (agentType === "Codex CLI") return isCodexTranscript;
1060
- return isClaudeTranscript || isCodexTranscript;
1288
+ if (agentType === "Gemini CLI") return isGeminiTranscript;
1289
+ return isClaudeTranscript || isCodexTranscript || isGeminiTranscript;
1061
1290
  }
1062
1291
  function getNewestPath(paths) {
1063
1292
  const ranked = paths.map((target) => {
@@ -1144,11 +1373,40 @@ function resolveUserHome(pid) {
1144
1373
  } catch {
1145
1374
  }
1146
1375
  try {
1147
- return os3.userInfo().homedir || null;
1376
+ return process.env.HOME || os3.userInfo().homedir || null;
1148
1377
  } catch {
1149
1378
  return null;
1150
1379
  }
1151
1380
  }
1381
+ function discoverGeminiTranscriptFromHome(pid) {
1382
+ const userHome = resolveUserHome(pid);
1383
+ if (!userHome) return null;
1384
+ const tmpDir = path2.join(userHome, ".gemini", "tmp");
1385
+ if (!fs2.existsSync(tmpDir)) return null;
1386
+ const matches = [];
1387
+ const stack = [tmpDir];
1388
+ let visited = 0;
1389
+ while (stack.length > 0 && visited < 2e3) {
1390
+ const current = stack.pop();
1391
+ if (!current) continue;
1392
+ visited++;
1393
+ let entries;
1394
+ try {
1395
+ entries = fs2.readdirSync(current, { withFileTypes: true });
1396
+ } catch {
1397
+ continue;
1398
+ }
1399
+ for (const entry of entries) {
1400
+ const fullPath = path2.join(current, entry.name);
1401
+ if (entry.isDirectory()) {
1402
+ stack.push(fullPath);
1403
+ } else if (entry.isFile() && entry.name.startsWith("session-") && entry.name.endsWith(".json") && fullPath.includes(`${path2.sep}chats${path2.sep}`)) {
1404
+ matches.push(fullPath);
1405
+ }
1406
+ }
1407
+ }
1408
+ return getNewestPath(matches);
1409
+ }
1152
1410
  function discoverViaCwd(paneCwd, pid) {
1153
1411
  const userHome = resolveUserHome(pid);
1154
1412
  if (!userHome) return null;
@@ -1183,6 +1441,10 @@ function discoverTranscriptFile(sessionId, agentType) {
1183
1441
  const cwdResult = discoverViaProcessCwd(processPid);
1184
1442
  if (cwdResult) return cwdResult;
1185
1443
  }
1444
+ if (agentType === "Gemini CLI") {
1445
+ const geminiResult = discoverGeminiTranscriptFromHome(processPid);
1446
+ if (geminiResult) return geminiResult;
1447
+ }
1186
1448
  return null;
1187
1449
  }
1188
1450
  if (sessionId.startsWith("tmux:")) {
@@ -1217,6 +1479,9 @@ function discoverTranscriptFile(sessionId, agentType) {
1217
1479
  } catch {
1218
1480
  }
1219
1481
  }
1482
+ if (agentType === "Gemini CLI") {
1483
+ return discoverGeminiTranscriptFromHome(panePid ?? void 0);
1484
+ }
1220
1485
  return null;
1221
1486
  }
1222
1487
  return null;
@@ -1241,6 +1506,7 @@ var TranscriptWatcher = class {
1241
1506
  pollTimer = null;
1242
1507
  stopped = false;
1243
1508
  subscriberCount = 0;
1509
+ documentFingerprint = "";
1244
1510
  constructor(filePath, parser, callbacks, parserV2, sessionId) {
1245
1511
  this.filePath = filePath;
1246
1512
  this.parser = parser;
@@ -1310,6 +1576,10 @@ var TranscriptWatcher = class {
1310
1576
  }
1311
1577
  /** Read and replay the full transcript from the start of the file. */
1312
1578
  replayFromStart() {
1579
+ if (this.parser.parseDocument) {
1580
+ this.readDocument(true);
1581
+ return;
1582
+ }
1313
1583
  const savedOffset = this.offset;
1314
1584
  this.offset = 0;
1315
1585
  this.partialLine = "";
@@ -1351,6 +1621,10 @@ var TranscriptWatcher = class {
1351
1621
  }
1352
1622
  }
1353
1623
  readNewData() {
1624
+ if (this.parser.parseDocument) {
1625
+ this.readDocument(false);
1626
+ return;
1627
+ }
1354
1628
  let fd;
1355
1629
  try {
1356
1630
  fd = fs2.openSync(this.filePath, "r");
@@ -1371,6 +1645,38 @@ var TranscriptWatcher = class {
1371
1645
  fs2.closeSync(fd);
1372
1646
  }
1373
1647
  }
1648
+ readDocument(force) {
1649
+ let content;
1650
+ let stat;
1651
+ try {
1652
+ stat = fs2.statSync(this.filePath);
1653
+ content = fs2.readFileSync(this.filePath, "utf-8");
1654
+ } catch {
1655
+ return;
1656
+ }
1657
+ const fingerprint = `${stat.mtimeMs}:${stat.size}`;
1658
+ if (!force && fingerprint === this.documentFingerprint) return;
1659
+ this.documentFingerprint = fingerprint;
1660
+ const parsedTurns = this.parser.parseDocument?.(content) ?? [];
1661
+ this.seq++;
1662
+ this.turns = parsedTurns.slice(-MAX_REPLAY_TURNS).map((turn) => ({
1663
+ turnId: turn.turnId,
1664
+ role: turn.role,
1665
+ content: turn.content,
1666
+ tools: turn.tools,
1667
+ timestamp: turn.timestamp
1668
+ }));
1669
+ this.callbacks.onTurnsSnapshot?.(this.turns, this.seq);
1670
+ if (this.parserV2?.parseDocument && this.callbacks.onSnapshot) {
1671
+ const ops = this.parserV2.parseDocument(content);
1672
+ let nextSnapshot = createEmptySnapshot(this.snapshot.sessionId);
1673
+ if (ops.length > 0) {
1674
+ nextSnapshot = applyOps(nextSnapshot, ops);
1675
+ }
1676
+ this.snapshot = trimSnapshot(nextSnapshot);
1677
+ this.callbacks.onSnapshot(this.snapshot, this.seq);
1678
+ }
1679
+ }
1374
1680
  processChunk(chunk) {
1375
1681
  const combined = this.partialLine + chunk;
1376
1682
  const lines = combined.split("\n");
@@ -1413,7 +1719,8 @@ var TranscriptWatcher = class {
1413
1719
  };
1414
1720
  var PARSER_FACTORIES = {
1415
1721
  "Claude Code": () => new ClaudeCodeParser(),
1416
- "Codex CLI": () => new CodexCliParser()
1722
+ "Codex CLI": () => new CodexCliParser(),
1723
+ "Gemini CLI": () => new GeminiCliParser()
1417
1724
  };
1418
1725
  function getParser(agentType) {
1419
1726
  if (!agentType) return void 0;
@@ -1435,10 +1742,25 @@ var TranscriptWatcherManager = class {
1435
1742
  }
1436
1743
  /** Enable markdown streaming for a session. */
1437
1744
  enableMarkdown(sessionId, agentType) {
1745
+ const requestedAgentType = agentType ?? "";
1438
1746
  const existing = this.active.get(sessionId);
1439
1747
  if (existing) {
1440
- existing.watcher.addSubscriber();
1441
- return { ok: true };
1748
+ const newPath = discoverTranscriptFile(sessionId, agentType);
1749
+ const agentChanged = existing.agentType !== requestedAgentType;
1750
+ const pathChanged = Boolean(newPath && newPath !== existing.filePath);
1751
+ const existingPathGone = !fs2.existsSync(existing.filePath);
1752
+ if (!agentChanged && !pathChanged && !existingPathGone) {
1753
+ existing.watcher.addSubscriber();
1754
+ return { ok: true };
1755
+ }
1756
+ existing.watcher.stop();
1757
+ this.active.delete(sessionId);
1758
+ this.stopRediscovery(sessionId);
1759
+ this.clearTranscriptState(sessionId);
1760
+ if (!newPath) {
1761
+ log5.info("no replacement transcript file found", { sessionId, agentType });
1762
+ return { ok: false, reason: "no_transcript" };
1763
+ }
1442
1764
  }
1443
1765
  const parser = getParser(agentType);
1444
1766
  if (!parser) {
@@ -1462,6 +1784,12 @@ var TranscriptWatcherManager = class {
1462
1784
  onOps: this.sendOpsFn ? (ops, seq) => {
1463
1785
  this.sendOpsFn(sessionId, ops, seq);
1464
1786
  } : void 0,
1787
+ onTurnsSnapshot: (turns, seq) => {
1788
+ this.sendSnapshotFn(sessionId, turns, seq);
1789
+ },
1790
+ onSnapshot: this.sendV2SnapshotFn ? (snapshot, seq) => {
1791
+ this.sendV2SnapshotFn(sessionId, snapshot, seq);
1792
+ } : void 0,
1465
1793
  onError: (error) => {
1466
1794
  log5.warn("transcript watcher error", { sessionId, error: String(error) });
1467
1795
  }
@@ -1478,6 +1806,12 @@ var TranscriptWatcherManager = class {
1478
1806
  this.startRediscovery(sessionId, agentType);
1479
1807
  return { ok: true };
1480
1808
  }
1809
+ clearTranscriptState(sessionId) {
1810
+ this.sendSnapshotFn(sessionId, [], 0);
1811
+ if (this.sendV2SnapshotFn) {
1812
+ this.sendV2SnapshotFn(sessionId, createEmptySnapshot(sessionId), 0);
1813
+ }
1814
+ }
1481
1815
  /** Disable markdown streaming for a session. */
1482
1816
  disableMarkdown(sessionId) {
1483
1817
  const session = this.active.get(sessionId);
@@ -1520,6 +1854,12 @@ var TranscriptWatcherManager = class {
1520
1854
  onOps: this.sendOpsFn ? (ops, seq) => {
1521
1855
  this.sendOpsFn(sessionId, ops, seq);
1522
1856
  } : void 0,
1857
+ onTurnsSnapshot: (turns, seq) => {
1858
+ this.sendSnapshotFn(sessionId, turns, seq);
1859
+ },
1860
+ onSnapshot: this.sendV2SnapshotFn ? (snapshot, seq) => {
1861
+ this.sendV2SnapshotFn(sessionId, snapshot, seq);
1862
+ } : void 0,
1523
1863
  onError: (error) => {
1524
1864
  log5.warn("transcript watcher error", { sessionId, error: String(error) });
1525
1865
  }
@@ -1577,7 +1917,7 @@ var TranscriptWatcherManager = class {
1577
1917
 
1578
1918
  // src/sessions.ts
1579
1919
  var isMac = os4.platform() === "darwin";
1580
- var MARKDOWN_AGENT_TYPES = /* @__PURE__ */ new Set(["Claude Code", "Codex CLI"]);
1920
+ var MARKDOWN_AGENT_TYPES = /* @__PURE__ */ new Set(["Claude Code", "Codex CLI", "Gemini CLI"]);
1581
1921
  function getProcessStartTime(pid) {
1582
1922
  if (isMac) return null;
1583
1923
  try {
@@ -1629,6 +1969,7 @@ function classifyAgent(command, options) {
1629
1969
  ".claude": "Claude Code",
1630
1970
  "claude-code": "Claude Code",
1631
1971
  ".codex": "Codex CLI",
1972
+ ".gemini": "Gemini CLI",
1632
1973
  ".aider": "aider"
1633
1974
  };
1634
1975
  const agentType = cwdAgentMap[dirName];
@@ -2800,26 +3141,23 @@ var SessionStreamer = class {
2800
3141
  alternateScreen = altFlag === "1";
2801
3142
  } catch {
2802
3143
  }
2803
- if (alternateScreen) {
2804
- this.sendFn(sessionId, "\x1B[?1049h\x1B[0m\x1B[2J\x1B[H");
2805
- } else {
2806
- try {
2807
- const scrollback = (0, import_node_child_process2.execFileSync)(
2808
- "tmux",
2809
- ["capture-pane", "-t", paneTarget, "-p", "-e", "-S", "-5000", "-E", "-1"],
2810
- { env: cleanEnv, timeout: 1e4, encoding: "utf-8" }
2811
- );
2812
- if (scrollback && scrollback.length > 0) {
2813
- this.sendFn(sessionId, scrollback.replace(/\n/g, "\r\n"));
2814
- }
2815
- } catch {
3144
+ try {
3145
+ const scrollback = (0, import_node_child_process2.execFileSync)(
3146
+ "tmux",
3147
+ ["capture-pane", "-t", paneTarget, "-p", "-e", "-S", "-5000", "-E", "-1"],
3148
+ { env: cleanEnv, timeout: 1e4, encoding: "utf-8" }
3149
+ );
3150
+ if (scrollback && scrollback.length > 0) {
3151
+ this.sendFn(sessionId, scrollback.replace(/\n/g, "\r\n"));
2816
3152
  }
2817
- this.sendFn(sessionId, "\x1B[?1049l\x1B[0m\x1B[2J\x1B[H");
3153
+ } catch {
2818
3154
  }
3155
+ this.sendFn(sessionId, "\x1B[?1049l\x1B[0m\x1B[2J\x1B[H");
2819
3156
  try {
3157
+ const captureArgs = alternateScreen ? ["capture-pane", "-a", "-t", paneTarget, "-p", "-e"] : ["capture-pane", "-t", paneTarget, "-p", "-e"];
2820
3158
  const initial = (0, import_node_child_process2.execFileSync)(
2821
3159
  "tmux",
2822
- ["capture-pane", "-t", paneTarget, "-p", "-e"],
3160
+ captureArgs,
2823
3161
  { env: cleanEnv, timeout: 5e3, encoding: "utf-8" }
2824
3162
  );
2825
3163
  if (initial) {
@@ -2941,26 +3279,23 @@ var SessionStreamer = class {
2941
3279
  } catch {
2942
3280
  }
2943
3281
  stream.alternateScreen = alternateScreen;
2944
- if (alternateScreen) {
2945
- this.sendFn(sessionId, "\x1B[?1049h\x1B[0m\x1B[2J\x1B[H");
2946
- } else {
2947
- try {
2948
- const scrollback = (0, import_node_child_process2.execFileSync)(
2949
- "tmux",
2950
- ["capture-pane", "-t", paneTarget, "-p", "-e", "-S", "-5000", "-E", "-1"],
2951
- { env: cleanEnv, timeout: 1e4, encoding: "utf-8" }
2952
- );
2953
- if (scrollback && scrollback.length > 0) {
2954
- this.sendFn(sessionId, scrollback.replace(/\n/g, "\r\n"));
2955
- }
2956
- } catch {
3282
+ try {
3283
+ const scrollback = (0, import_node_child_process2.execFileSync)(
3284
+ "tmux",
3285
+ ["capture-pane", "-t", paneTarget, "-p", "-e", "-S", "-5000", "-E", "-1"],
3286
+ { env: cleanEnv, timeout: 1e4, encoding: "utf-8" }
3287
+ );
3288
+ if (scrollback && scrollback.length > 0) {
3289
+ this.sendFn(sessionId, scrollback.replace(/\n/g, "\r\n"));
2957
3290
  }
2958
- this.sendFn(sessionId, "\x1B[?1049l\x1B[0m\x1B[2J\x1B[H");
3291
+ } catch {
2959
3292
  }
3293
+ this.sendFn(sessionId, "\x1B[?1049l\x1B[0m\x1B[2J\x1B[H");
2960
3294
  try {
3295
+ const captureArgs = alternateScreen ? ["capture-pane", "-a", "-t", paneTarget, "-p", "-e"] : ["capture-pane", "-t", paneTarget, "-p", "-e"];
2961
3296
  const initial = (0, import_node_child_process2.execFileSync)(
2962
3297
  "tmux",
2963
- ["capture-pane", "-t", paneTarget, "-p", "-e"],
3298
+ captureArgs,
2964
3299
  { env: cleanEnv, timeout: 5e3, encoding: "utf-8" }
2965
3300
  );
2966
3301
  if (initial) {
@@ -3322,6 +3657,105 @@ var SequenceTracker = class {
3322
3657
  var import_node_child_process3 = require("child_process");
3323
3658
  var pty2 = __toESM(require("node-pty"));
3324
3659
  var log9 = createLogger("pty");
3660
+ var ANSI_KEY_SEQUENCES = /* @__PURE__ */ new Map([
3661
+ ["\x1B[A", "Up"],
3662
+ ["\x1B[B", "Down"],
3663
+ ["\x1B[C", "Right"],
3664
+ ["\x1B[D", "Left"],
3665
+ ["\x1B[H", "Home"],
3666
+ ["\x1B[F", "End"],
3667
+ ["\x1BOH", "Home"],
3668
+ ["\x1BOF", "End"],
3669
+ ["\x1B[1~", "Home"],
3670
+ ["\x1B[4~", "End"],
3671
+ ["\x1B[3~", "Delete"],
3672
+ ["\x1B[5~", "PageUp"],
3673
+ ["\x1B[6~", "PageDown"]
3674
+ ]);
3675
+ function controlKeyName(charCode) {
3676
+ if (charCode >= 1 && charCode <= 26) {
3677
+ return `C-${String.fromCharCode(charCode + 96)}`;
3678
+ }
3679
+ switch (charCode) {
3680
+ case 0:
3681
+ return "C-Space";
3682
+ case 27:
3683
+ return "Escape";
3684
+ case 28:
3685
+ return "C-\\";
3686
+ case 29:
3687
+ return "C-]";
3688
+ case 30:
3689
+ return "C-^";
3690
+ case 31:
3691
+ return "C-_";
3692
+ default:
3693
+ return null;
3694
+ }
3695
+ }
3696
+ function appendLiteralSegment(segments, value) {
3697
+ if (!value) return;
3698
+ const previous = segments[segments.length - 1];
3699
+ if (previous?.kind === "literal") {
3700
+ previous.value += value;
3701
+ } else {
3702
+ segments.push({ kind: "literal", value });
3703
+ }
3704
+ }
3705
+ function encodeTmuxInput(data) {
3706
+ const segments = [];
3707
+ let literal = "";
3708
+ const flushLiteral = () => {
3709
+ appendLiteralSegment(segments, literal);
3710
+ literal = "";
3711
+ };
3712
+ for (let i = 0; i < data.length; i++) {
3713
+ const remaining = data.slice(i);
3714
+ const matchedSequence = Array.from(ANSI_KEY_SEQUENCES.entries()).find(([sequence]) => remaining.startsWith(sequence));
3715
+ if (matchedSequence) {
3716
+ flushLiteral();
3717
+ const [sequence, key] = matchedSequence;
3718
+ segments.push({ kind: "key", value: key });
3719
+ i += sequence.length - 1;
3720
+ continue;
3721
+ }
3722
+ const charCode = data.charCodeAt(i);
3723
+ if (data[i] === "\r" || data[i] === "\n") {
3724
+ flushLiteral();
3725
+ segments.push({ kind: "key", value: "Enter" });
3726
+ continue;
3727
+ }
3728
+ if (data[i] === " ") {
3729
+ flushLiteral();
3730
+ segments.push({ kind: "key", value: "Tab" });
3731
+ continue;
3732
+ }
3733
+ if (data[i] === "\x7F" || data[i] === "\b") {
3734
+ flushLiteral();
3735
+ segments.push({ kind: "key", value: "BSpace" });
3736
+ continue;
3737
+ }
3738
+ if (charCode < 32) {
3739
+ const keyName = controlKeyName(charCode);
3740
+ if (keyName) {
3741
+ flushLiteral();
3742
+ segments.push({ kind: "key", value: keyName });
3743
+ continue;
3744
+ }
3745
+ }
3746
+ literal += data[i];
3747
+ }
3748
+ flushLiteral();
3749
+ return segments;
3750
+ }
3751
+ function execTmuxSendKeys(args) {
3752
+ return new Promise((resolve, reject) => {
3753
+ (0, import_node_child_process3.execFile)("tmux", args, { timeout: 5e3 }, (err) => {
3754
+ if (err) reject(err);
3755
+ else resolve();
3756
+ });
3757
+ });
3758
+ }
3325
3759
  function isInteractiveShell(command) {
3326
3760
  const trimmed = String(command).trim();
3327
3761
  return ["bash", "sh", "zsh", "fish"].includes(trimmed);
@@ -3358,12 +3792,13 @@ async function sendInputToTmux(sessionId, data) {
3358
3792
  return;
3359
3793
  }
3360
3794
  try {
3361
- await new Promise((resolve, reject) => {
3362
- (0, import_node_child_process3.execFile)("tmux", ["send-keys", "-t", target, "-l", "--", data], { timeout: 5e3 }, (err) => {
3363
- if (err) reject(err);
3364
- else resolve();
3365
- });
3366
- });
3795
+ for (const segment of encodeTmuxInput(data)) {
3796
+ if (segment.kind === "literal") {
3797
+ await execTmuxSendKeys(["send-keys", "-t", target, "-l", "--", segment.value]);
3798
+ } else {
3799
+ await execTmuxSendKeys(["send-keys", "-t", target, "--", segment.value]);
3800
+ }
3801
+ }
3367
3802
  } catch (error) {
3368
3803
  const message = error instanceof Error ? error.message : String(error);
3369
3804
  log9.warn("failed to send input", { sessionId, error: message });
package/dist/sbom.json CHANGED
@@ -1166,8 +1166,8 @@
1166
1166
  {
1167
1167
  "type": "library",
1168
1168
  "name": "ragent-cli",
1169
- "version": "1.11.2",
1170
- "bom-ref": "ragent-live|ragent-cli@1.11.2",
1169
+ "version": "1.11.4",
1170
+ "bom-ref": "ragent-live|ragent-cli@1.11.4",
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.2",
1181
+ "purl": "pkg:npm/ragent-cli@1.11.4",
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.2"
1306
+ "ragent-live|ragent-cli@1.11.4"
1307
1307
  ]
1308
1308
  },
1309
1309
  {
@@ -1436,7 +1436,7 @@
1436
1436
  ]
1437
1437
  },
1438
1438
  {
1439
- "ref": "ragent-live|ragent-cli@1.11.2",
1439
+ "ref": "ragent-live|ragent-cli@1.11.4",
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.2",
3
+ "version": "1.11.4",
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": {