@junctionpanel/server 0.1.33 → 0.1.35

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 (75) hide show
  1. package/dist/server/client/daemon-client.d.ts +2 -0
  2. package/dist/server/client/daemon-client.d.ts.map +1 -1
  3. package/dist/server/client/daemon-client.js +2 -0
  4. package/dist/server/client/daemon-client.js.map +1 -1
  5. package/dist/server/server/agent/agent-management-mcp.d.ts.map +1 -1
  6. package/dist/server/server/agent/agent-management-mcp.js +44 -7
  7. package/dist/server/server/agent/agent-management-mcp.js.map +1 -1
  8. package/dist/server/server/agent/agent-manager.d.ts +5 -0
  9. package/dist/server/server/agent/agent-manager.d.ts.map +1 -1
  10. package/dist/server/server/agent/agent-manager.js +66 -12
  11. package/dist/server/server/agent/agent-manager.js.map +1 -1
  12. package/dist/server/server/agent/agent-permission-fingerprint.d.ts +6 -0
  13. package/dist/server/server/agent/agent-permission-fingerprint.d.ts.map +1 -0
  14. package/dist/server/server/agent/agent-permission-fingerprint.js +28 -0
  15. package/dist/server/server/agent/agent-permission-fingerprint.js.map +1 -0
  16. package/dist/server/server/agent/agent-projections.d.ts +1 -0
  17. package/dist/server/server/agent/agent-projections.d.ts.map +1 -1
  18. package/dist/server/server/agent/agent-projections.js +61 -3
  19. package/dist/server/server/agent/agent-projections.js.map +1 -1
  20. package/dist/server/server/agent/agent-sdk-types.d.ts +5 -0
  21. package/dist/server/server/agent/agent-sdk-types.d.ts.map +1 -1
  22. package/dist/server/server/agent/agent-storage.d.ts +13 -0
  23. package/dist/server/server/agent/agent-storage.d.ts.map +1 -1
  24. package/dist/server/server/agent/agent-storage.js +4 -2
  25. package/dist/server/server/agent/agent-storage.js.map +1 -1
  26. package/dist/server/server/agent/mcp-server.d.ts.map +1 -1
  27. package/dist/server/server/agent/mcp-server.js +44 -7
  28. package/dist/server/server/agent/mcp-server.js.map +1 -1
  29. package/dist/server/server/agent/providers/claude-agent.d.ts.map +1 -1
  30. package/dist/server/server/agent/providers/claude-agent.js +38 -90
  31. package/dist/server/server/agent/providers/claude-agent.js.map +1 -1
  32. package/dist/server/server/agent/providers/claude-cli-capabilities.d.ts +50 -0
  33. package/dist/server/server/agent/providers/claude-cli-capabilities.d.ts.map +1 -0
  34. package/dist/server/server/agent/providers/claude-cli-capabilities.js +247 -0
  35. package/dist/server/server/agent/providers/claude-cli-capabilities.js.map +1 -0
  36. package/dist/server/server/agent/providers/codex-app-server-agent.d.ts.map +1 -1
  37. package/dist/server/server/agent/providers/codex-app-server-agent.js +4 -0
  38. package/dist/server/server/agent/providers/codex-app-server-agent.js.map +1 -1
  39. package/dist/server/server/agent/providers/gemini-agent.d.ts +287 -1
  40. package/dist/server/server/agent/providers/gemini-agent.d.ts.map +1 -1
  41. package/dist/server/server/agent/providers/gemini-agent.js +255 -16
  42. package/dist/server/server/agent/providers/gemini-agent.js.map +1 -1
  43. package/dist/server/server/daemon-doctor.d.ts +5 -0
  44. package/dist/server/server/daemon-doctor.d.ts.map +1 -1
  45. package/dist/server/server/daemon-doctor.js +26 -0
  46. package/dist/server/server/daemon-doctor.js.map +1 -1
  47. package/dist/server/server/file-explorer/service.d.ts +1 -0
  48. package/dist/server/server/file-explorer/service.d.ts.map +1 -1
  49. package/dist/server/server/file-explorer/service.js +36 -0
  50. package/dist/server/server/file-explorer/service.js.map +1 -1
  51. package/dist/server/server/session.d.ts +1 -0
  52. package/dist/server/server/session.d.ts.map +1 -1
  53. package/dist/server/server/session.js +82 -71
  54. package/dist/server/server/session.js.map +1 -1
  55. package/dist/server/server/worktree-bootstrap.d.ts +2 -1
  56. package/dist/server/server/worktree-bootstrap.d.ts.map +1 -1
  57. package/dist/server/server/worktree-bootstrap.js +1 -0
  58. package/dist/server/server/worktree-bootstrap.js.map +1 -1
  59. package/dist/server/shared/messages.d.ts +424 -218
  60. package/dist/server/shared/messages.d.ts.map +1 -1
  61. package/dist/server/shared/messages.js +9 -0
  62. package/dist/server/shared/messages.js.map +1 -1
  63. package/dist/server/utils/checkout-git.d.ts +14 -0
  64. package/dist/server/utils/checkout-git.d.ts.map +1 -1
  65. package/dist/server/utils/checkout-git.js +73 -44
  66. package/dist/server/utils/checkout-git.js.map +1 -1
  67. package/dist/server/utils/worktree-metadata.d.ts +30 -0
  68. package/dist/server/utils/worktree-metadata.d.ts.map +1 -1
  69. package/dist/server/utils/worktree-metadata.js +38 -9
  70. package/dist/server/utils/worktree-metadata.js.map +1 -1
  71. package/dist/server/utils/worktree.d.ts +7 -3
  72. package/dist/server/utils/worktree.d.ts.map +1 -1
  73. package/dist/server/utils/worktree.js +91 -47
  74. package/dist/server/utils/worktree.js.map +1 -1
  75. package/package.json +2 -2
@@ -1,9 +1,10 @@
1
1
  import * as acp from "@agentclientprotocol/sdk";
2
2
  import { execSync, spawn, spawnSync } from "node:child_process";
3
- import { readdir, readFile } from "node:fs/promises";
3
+ import { access, readdir, readFile } from "node:fs/promises";
4
4
  import { homedir } from "node:os";
5
5
  import path from "node:path";
6
6
  import { Readable as NodeReadable, Writable as NodeWritable } from "node:stream";
7
+ import { setTimeout as delay } from "node:timers/promises";
7
8
  import { z } from "zod";
8
9
  import { applyProviderEnv, isProviderCommandAvailable, resolveProviderCommandPrefix, } from "../provider-launch-config.js";
9
10
  import { ToolEditInputSchema, ToolEditOutputSchema, ToolReadInputSchema, ToolReadOutputSchema, ToolSearchInputSchema, ToolShellInputSchema, ToolShellOutputSchema, ToolWriteInputSchema, ToolWriteOutputSchema, toEditToolDetail, toReadToolDetail, toSearchToolDetail, toShellToolDetail, toWriteToolDetail, } from "./tool-call-detail-primitives.js";
@@ -13,6 +14,8 @@ const GEMINI_MODES = ["default", "auto_edit", "yolo", "plan"];
13
14
  const GEMINI_GLOBAL_DIR = path.join(homedir(), ".gemini");
14
15
  const GEMINI_TMP_DIR = path.join(GEMINI_GLOBAL_DIR, "tmp");
15
16
  const GEMINI_HISTORY_FALLBACK_IDLE_MS = 1000;
17
+ const GEMINI_RECORDED_SESSION_POLL_INTERVAL_MS = 100;
18
+ const GEMINI_RECORDED_SESSION_DISCOVERY_TIMEOUT_MS = 200;
16
19
  const GEMINI_CAPABILITIES = {
17
20
  supportsStreaming: true,
18
21
  supportsSessionPersistence: true,
@@ -98,6 +101,22 @@ const GeminiRecordedThoughtSchema = z
98
101
  text: z.string().optional(),
99
102
  })
100
103
  .passthrough();
104
+ const GeminiRecordedTokensSchema = z.preprocess((value) => {
105
+ if (!value || typeof value !== "object" || Array.isArray(value)) {
106
+ return undefined;
107
+ }
108
+ return value;
109
+ }, z
110
+ .object({
111
+ input: z.number().nonnegative().optional(),
112
+ output: z.number().nonnegative().optional(),
113
+ cached: z.number().nonnegative().optional(),
114
+ thoughts: z.number().nonnegative().optional(),
115
+ tool: z.number().nonnegative().optional(),
116
+ total: z.number().nonnegative().optional(),
117
+ })
118
+ .passthrough()
119
+ .optional());
101
120
  const GeminiRecordedToolCallSchema = z
102
121
  .object({
103
122
  id: z.string().optional(),
@@ -112,10 +131,13 @@ const GeminiRecordedMessageSchema = z
112
131
  .object({
113
132
  id: z.string().optional(),
114
133
  type: z.string(),
134
+ timestamp: z.string().optional(),
115
135
  content: z.unknown().optional(),
116
136
  displayContent: z.unknown().optional(),
117
137
  thoughts: z.array(GeminiRecordedThoughtSchema).optional(),
118
138
  toolCalls: z.array(GeminiRecordedToolCallSchema).optional(),
139
+ tokens: GeminiRecordedTokensSchema.optional(),
140
+ model: z.string().optional(),
119
141
  })
120
142
  .passthrough();
121
143
  const GeminiRecordedSessionSchema = z
@@ -463,20 +485,22 @@ function toGeminiModelDefinitions() {
463
485
  async function readGeminiSessionFile(filePath) {
464
486
  try {
465
487
  const raw = await readFile(filePath, "utf8");
466
- const parsed = JSON.parse(raw);
467
- const session = GeminiRecordedSessionSchema.safeParse(parsed);
468
- if (!session.success) {
469
- return null;
470
- }
471
- if (session.data.kind === "subagent") {
472
- return null;
473
- }
474
- return session.data;
488
+ return parseGeminiRecordedSession(JSON.parse(raw));
475
489
  }
476
490
  catch {
477
491
  return null;
478
492
  }
479
493
  }
494
+ export function parseGeminiRecordedSession(raw) {
495
+ const session = GeminiRecordedSessionSchema.safeParse(raw);
496
+ if (!session.success) {
497
+ return null;
498
+ }
499
+ if (session.data.kind === "subagent") {
500
+ return null;
501
+ }
502
+ return session.data;
503
+ }
480
504
  function extractRecordedContentText(content) {
481
505
  if (typeof content === "string") {
482
506
  return content;
@@ -529,8 +553,22 @@ function extractRecordedThoughtText(thought) {
529
553
  }
530
554
  export function buildGeminiHistoryTimeline(session) {
531
555
  const items = [];
556
+ let pendingTurnMessages = [];
557
+ const flushPendingTurnSummary = () => {
558
+ const summary = summarizeGeminiRecordedMessages(pendingTurnMessages);
559
+ pendingTurnMessages = [];
560
+ if (!summary) {
561
+ return;
562
+ }
563
+ items.push({
564
+ type: "turn_summary",
565
+ usage: summary.usage,
566
+ modelId: summary.modelId,
567
+ });
568
+ };
532
569
  for (const message of session.messages) {
533
570
  if (message.type === "user") {
571
+ flushPendingTurnSummary();
534
572
  const text = extractRecordedContentText(message.content ?? message.displayContent).trim();
535
573
  if (text.length > 0) {
536
574
  items.push({
@@ -542,8 +580,10 @@ export function buildGeminiHistoryTimeline(session) {
542
580
  continue;
543
581
  }
544
582
  if (message.type !== "gemini") {
583
+ flushPendingTurnSummary();
545
584
  continue;
546
585
  }
586
+ pendingTurnMessages.push(message);
547
587
  const assistantText = extractRecordedContentText(message.content ?? message.displayContent).trim();
548
588
  if (assistantText.length > 0) {
549
589
  items.push({ type: "assistant_message", text: assistantText });
@@ -568,8 +608,107 @@ export function buildGeminiHistoryTimeline(session) {
568
608
  }));
569
609
  }
570
610
  }
611
+ flushPendingTurnSummary();
571
612
  return items;
572
613
  }
614
+ export function summarizeGeminiRecordedMessages(messages) {
615
+ let inputTokens = 0;
616
+ let cachedInputTokens = 0;
617
+ let outputTokens = 0;
618
+ let hasUsage = false;
619
+ let modelId = null;
620
+ for (const message of messages) {
621
+ if (message.type !== "gemini") {
622
+ continue;
623
+ }
624
+ const usage = toGeminiRecordedUsage(message);
625
+ if (usage) {
626
+ inputTokens += usage.inputTokens ?? 0;
627
+ cachedInputTokens += usage.cachedInputTokens ?? 0;
628
+ outputTokens += usage.outputTokens ?? 0;
629
+ hasUsage = true;
630
+ }
631
+ const recordedModelId = nonEmptyString(message.model);
632
+ if (recordedModelId) {
633
+ modelId = recordedModelId;
634
+ }
635
+ }
636
+ if (!hasUsage) {
637
+ return null;
638
+ }
639
+ return {
640
+ usage: {
641
+ ...(inputTokens > 0 ? { inputTokens } : {}),
642
+ ...(cachedInputTokens > 0 ? { cachedInputTokens } : {}),
643
+ ...(outputTokens > 0 ? { outputTokens } : {}),
644
+ },
645
+ modelId,
646
+ };
647
+ }
648
+ export function summarizeGeminiRecordedTurn(session, turnStartedAt) {
649
+ return summarizeGeminiRecordedMessages(getGeminiRecordedTurnMessages(session, turnStartedAt));
650
+ }
651
+ function getGeminiRecordedTurnMessages(session, turnStartedAt) {
652
+ const turnStartMs = turnStartedAt.getTime();
653
+ const turnMessages = [];
654
+ let collecting = false;
655
+ for (const message of session.messages) {
656
+ const timestampMs = typeof message.timestamp === "string" ? Date.parse(message.timestamp) : Number.NaN;
657
+ const isInTurn = Number.isFinite(timestampMs) && timestampMs >= turnStartMs;
658
+ if (isInTurn) {
659
+ collecting = true;
660
+ turnMessages.push(message);
661
+ continue;
662
+ }
663
+ if (collecting && message.type === "gemini") {
664
+ turnMessages.push(message);
665
+ }
666
+ }
667
+ return turnMessages;
668
+ }
669
+ function hasGeminiRecordedAssistantMessage(messages) {
670
+ return messages.some((message) => message.type === "gemini");
671
+ }
672
+ function countGeminiRecordedHistoryEvents(session) {
673
+ let count = 0;
674
+ for (const message of session.messages) {
675
+ if (message.type === "user") {
676
+ const text = extractRecordedContentText(message.content ?? message.displayContent).trim();
677
+ if (text.length > 0) {
678
+ count += 1;
679
+ }
680
+ continue;
681
+ }
682
+ if (message.type !== "gemini") {
683
+ continue;
684
+ }
685
+ const assistantText = extractRecordedContentText(message.content ?? message.displayContent).trim();
686
+ if (assistantText.length > 0) {
687
+ count += 1;
688
+ }
689
+ for (const thought of message.thoughts ?? []) {
690
+ const text = extractRecordedThoughtText(thought);
691
+ if (text) {
692
+ count += 1;
693
+ }
694
+ }
695
+ count += (message.toolCalls ?? []).length;
696
+ }
697
+ return count;
698
+ }
699
+ function toGeminiRecordedUsage(message) {
700
+ const inputTokens = message.tokens?.input ?? 0;
701
+ const cachedInputTokens = message.tokens?.cached ?? 0;
702
+ const outputTokens = message.tokens?.output ?? 0;
703
+ if (inputTokens === 0 && cachedInputTokens === 0 && outputTokens === 0) {
704
+ return undefined;
705
+ }
706
+ return {
707
+ ...(inputTokens > 0 ? { inputTokens } : {}),
708
+ ...(cachedInputTokens > 0 ? { cachedInputTokens } : {}),
709
+ ...(outputTokens > 0 ? { outputTokens } : {}),
710
+ };
711
+ }
573
712
  async function listKnownGeminiProjects() {
574
713
  let entries;
575
714
  try {
@@ -598,11 +737,14 @@ async function resolveGeminiProjectTempDirForCwd(cwd) {
598
737
  const projects = await listKnownGeminiProjects();
599
738
  return projects.find((project) => project.cwd === cwd)?.tempDir ?? null;
600
739
  }
601
- async function loadGeminiRecordedSessionForCwd(cwd, sessionId) {
740
+ async function findGeminiRecordedSessionForCwd(cwd, sessionId) {
602
741
  const tempDir = await resolveGeminiProjectTempDirForCwd(cwd);
603
742
  if (!tempDir) {
604
743
  return null;
605
744
  }
745
+ return findGeminiRecordedSessionForTempDir(tempDir, sessionId);
746
+ }
747
+ async function findGeminiRecordedSessionForTempDir(tempDir, sessionId) {
606
748
  const chatsDir = path.join(tempDir, "chats");
607
749
  let chatFiles;
608
750
  try {
@@ -615,13 +757,26 @@ async function loadGeminiRecordedSessionForCwd(cwd, sessionId) {
615
757
  if (!fileName.startsWith("session-") || !fileName.endsWith(".json")) {
616
758
  continue;
617
759
  }
618
- const session = await readGeminiSessionFile(path.join(chatsDir, fileName));
760
+ const filePath = path.join(chatsDir, fileName);
761
+ const session = await readGeminiSessionFile(filePath);
619
762
  if (session?.sessionId === sessionId) {
620
- return session;
763
+ return {
764
+ filePath,
765
+ session,
766
+ };
621
767
  }
622
768
  }
623
769
  return null;
624
770
  }
771
+ async function geminiSessionFileExists(filePath) {
772
+ try {
773
+ await access(filePath);
774
+ return true;
775
+ }
776
+ catch {
777
+ return false;
778
+ }
779
+ }
625
780
  async function listGeminiSessionsForProject(params) {
626
781
  const chatsDir = path.join(params.tempDir, "chats");
627
782
  let chatFiles;
@@ -1023,6 +1178,8 @@ class GeminiAgentSession {
1023
1178
  this.expectedHistoryEventCount = null;
1024
1179
  this.capturedHistoryEventCount = 0;
1025
1180
  this.currentRunInterrupted = false;
1181
+ this.hasObservedRecordedSession = false;
1182
+ this.recordedSessionFilePath = null;
1026
1183
  this.config = config;
1027
1184
  this.logger = logger.child({ sessionProvider: GEMINI_PROVIDER });
1028
1185
  this.runtimeSettings = runtimeSettings;
@@ -1049,6 +1206,7 @@ class GeminiAgentSession {
1049
1206
  const timeline = [];
1050
1207
  let finalText = "";
1051
1208
  let canceled = false;
1209
+ let usage;
1052
1210
  for await (const event of this.stream(prompt, options)) {
1053
1211
  if (event.type === "timeline") {
1054
1212
  timeline.push(event.item);
@@ -1056,6 +1214,9 @@ class GeminiAgentSession {
1056
1214
  finalText += event.item.text;
1057
1215
  }
1058
1216
  }
1217
+ else if (event.type === "turn_completed") {
1218
+ usage = event.usage;
1219
+ }
1059
1220
  else if (event.type === "turn_failed") {
1060
1221
  throw new Error(event.error);
1061
1222
  }
@@ -1066,6 +1227,7 @@ class GeminiAgentSession {
1066
1227
  return {
1067
1228
  sessionId: this.sessionId ?? "",
1068
1229
  finalText,
1230
+ ...(usage ? { usage } : {}),
1069
1231
  timeline,
1070
1232
  ...(canceled ? { canceled } : {}),
1071
1233
  };
@@ -1081,6 +1243,7 @@ class GeminiAgentSession {
1081
1243
  const queue = new AsyncEventQueue();
1082
1244
  this.activeTurnQueue = queue;
1083
1245
  this.currentRunInterrupted = false;
1246
+ const turnStartedAt = new Date();
1084
1247
  queue.push({
1085
1248
  type: "turn_started",
1086
1249
  provider: GEMINI_PROVIDER,
@@ -1090,7 +1253,7 @@ class GeminiAgentSession {
1090
1253
  sessionId: this.sessionId,
1091
1254
  prompt: toGeminiPromptBlocks(prompt),
1092
1255
  })
1093
- .then((response) => {
1256
+ .then(async (response) => {
1094
1257
  if (response.stopReason === "cancelled") {
1095
1258
  queue.push({
1096
1259
  type: "turn_canceled",
@@ -1099,9 +1262,17 @@ class GeminiAgentSession {
1099
1262
  });
1100
1263
  }
1101
1264
  else {
1265
+ // ACP does not report usage, so we synthesize it from Gemini's recorded session file.
1266
+ const recordedTurnSummary = await this.readRecordedTurnSummary(turnStartedAt);
1102
1267
  queue.push({
1103
1268
  type: "turn_completed",
1104
1269
  provider: GEMINI_PROVIDER,
1270
+ ...(recordedTurnSummary
1271
+ ? {
1272
+ usage: recordedTurnSummary.usage,
1273
+ modelId: recordedTurnSummary.modelId,
1274
+ }
1275
+ : {}),
1105
1276
  });
1106
1277
  }
1107
1278
  queue.close();
@@ -1379,8 +1550,11 @@ class GeminiAgentSession {
1379
1550
  },
1380
1551
  });
1381
1552
  if (this.sessionId) {
1382
- const recordedSession = await loadGeminiRecordedSessionForCwd(this.config.cwd, this.sessionId);
1383
- this.beginHistoryCapture(recordedSession ? buildGeminiHistoryTimeline(recordedSession).length : null);
1553
+ const recordedSessionMatch = await findGeminiRecordedSessionForCwd(this.config.cwd, this.sessionId);
1554
+ const recordedSession = recordedSessionMatch?.session ?? null;
1555
+ this.hasObservedRecordedSession = recordedSession !== null;
1556
+ this.recordedSessionFilePath = recordedSessionMatch?.filePath ?? null;
1557
+ this.beginHistoryCapture(recordedSession ? countGeminiRecordedHistoryEvents(recordedSession) : null);
1384
1558
  const response = await this.connection.loadSession({
1385
1559
  sessionId: this.sessionId,
1386
1560
  cwd: this.config.cwd,
@@ -1527,6 +1701,71 @@ class GeminiAgentSession {
1527
1701
  emitLiveEvent(event) {
1528
1702
  this.activeTurnQueue?.push(event);
1529
1703
  }
1704
+ async readRecordedTurnSummary(turnStartedAt) {
1705
+ if (!this.sessionId) {
1706
+ return null;
1707
+ }
1708
+ const tempDir = await resolveGeminiProjectTempDirForCwd(this.config.cwd);
1709
+ if (!tempDir) {
1710
+ return null;
1711
+ }
1712
+ const deadline = Date.now() + GEMINI_HISTORY_FALLBACK_IDLE_MS;
1713
+ const discoveryDeadline = Date.now() +
1714
+ (this.hasObservedRecordedSession && !this.recordedSessionFilePath
1715
+ ? GEMINI_RECORDED_SESSION_DISCOVERY_TIMEOUT_MS
1716
+ : GEMINI_HISTORY_FALLBACK_IDLE_MS);
1717
+ let latestFingerprint = null;
1718
+ let observedSummary = false;
1719
+ let latestEmptyTurnFingerprint = null;
1720
+ let observedEmptyTurn = false;
1721
+ let sawRecordedSession = false;
1722
+ while (Date.now() <= deadline) {
1723
+ let recordedSession = null;
1724
+ if (this.recordedSessionFilePath) {
1725
+ recordedSession = await readGeminiSessionFile(this.recordedSessionFilePath);
1726
+ if (!recordedSession && !(await geminiSessionFileExists(this.recordedSessionFilePath))) {
1727
+ this.recordedSessionFilePath = null;
1728
+ }
1729
+ }
1730
+ if (!recordedSession) {
1731
+ const recordedSessionMatch = await findGeminiRecordedSessionForTempDir(tempDir, this.sessionId);
1732
+ recordedSession = recordedSessionMatch?.session ?? null;
1733
+ if (recordedSessionMatch) {
1734
+ this.recordedSessionFilePath = recordedSessionMatch.filePath;
1735
+ }
1736
+ }
1737
+ if (recordedSession) {
1738
+ sawRecordedSession = true;
1739
+ this.hasObservedRecordedSession = true;
1740
+ const turnMessages = getGeminiRecordedTurnMessages(recordedSession, turnStartedAt);
1741
+ const nextSummary = summarizeGeminiRecordedMessages(turnMessages);
1742
+ if (nextSummary) {
1743
+ const nextFingerprint = JSON.stringify(nextSummary);
1744
+ if (observedSummary && nextFingerprint === latestFingerprint) {
1745
+ return nextSummary;
1746
+ }
1747
+ latestFingerprint = nextFingerprint;
1748
+ observedSummary = true;
1749
+ observedEmptyTurn = false;
1750
+ latestEmptyTurnFingerprint = null;
1751
+ }
1752
+ else if (hasGeminiRecordedAssistantMessage(turnMessages)) {
1753
+ const nextEmptyTurnFingerprint = JSON.stringify(turnMessages);
1754
+ if (observedEmptyTurn &&
1755
+ nextEmptyTurnFingerprint === latestEmptyTurnFingerprint) {
1756
+ return null;
1757
+ }
1758
+ observedEmptyTurn = true;
1759
+ latestEmptyTurnFingerprint = nextEmptyTurnFingerprint;
1760
+ }
1761
+ }
1762
+ else if (!sawRecordedSession && Date.now() >= discoveryDeadline) {
1763
+ return null;
1764
+ }
1765
+ await delay(GEMINI_RECORDED_SESSION_POLL_INTERVAL_MS);
1766
+ }
1767
+ return null;
1768
+ }
1530
1769
  async handlePermissionRequest(params) {
1531
1770
  if (this.currentRunInterrupted) {
1532
1771
  return {