antigravity-mobile-proxy 0.1.6 → 0.1.7
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/.next/standalone/.next/app-path-routes-manifest.json +2 -0
- package/.next/standalone/.next/build-manifest.json +3 -3
- package/.next/standalone/.next/prerender-manifest.json +3 -3
- package/.next/standalone/.next/routes-manifest.json +12 -0
- package/.next/standalone/.next/server/app/_global-error/page/build-manifest.json +3 -3
- package/.next/standalone/.next/server/app/_global-error/page.js +2 -2
- package/.next/standalone/.next/server/app/_global-error/page.js.nft.json +1 -1
- package/.next/standalone/.next/server/app/_global-error.html +2 -2
- package/.next/standalone/.next/server/app/_not-found/page/build-manifest.json +3 -3
- package/.next/standalone/.next/server/app/_not-found/page.js +5 -6
- package/.next/standalone/.next/server/app/_not-found/page.js.nft.json +1 -1
- package/.next/standalone/.next/server/app/_not-found/page_client-reference-manifest.js +1 -1
- package/.next/standalone/.next/server/app/api/v1/artifacts/active/route.js +5 -1
- package/.next/standalone/.next/server/app/api/v1/artifacts/active/route.js.nft.json +1 -1
- package/.next/standalone/.next/server/app/api/v1/changes/active/route/app-paths-manifest.json +3 -0
- package/.next/standalone/.next/server/app/api/v1/changes/active/route/build-manifest.json +11 -0
- package/.next/standalone/.next/server/app/api/v1/changes/active/route/server-reference-manifest.json +4 -0
- package/.next/standalone/.next/server/app/api/v1/changes/active/route.js +11 -0
- package/.next/standalone/.next/server/app/api/v1/changes/active/route.js.map +5 -0
- package/.next/standalone/.next/server/app/api/v1/changes/active/route.js.nft.json +1 -0
- package/.next/standalone/.next/server/app/api/v1/changes/active/route_client-reference-manifest.js +2 -0
- package/.next/standalone/.next/server/app/api/v1/changes/diff/route/app-paths-manifest.json +3 -0
- package/.next/standalone/.next/server/app/api/v1/changes/diff/route/build-manifest.json +11 -0
- package/.next/standalone/.next/server/app/api/v1/changes/diff/route/server-reference-manifest.json +4 -0
- package/.next/standalone/.next/server/app/api/v1/changes/diff/route.js +7 -0
- package/.next/standalone/.next/server/app/api/v1/changes/diff/route.js.map +5 -0
- package/.next/standalone/.next/server/app/api/v1/changes/diff/route.js.nft.json +1 -0
- package/.next/standalone/.next/server/app/api/v1/changes/diff/route_client-reference-manifest.js +2 -0
- package/.next/standalone/.next/server/app/api/v1/chat/stream/route.js +1 -1
- package/.next/standalone/.next/server/app/api/v1/chat/stream/route.js.nft.json +1 -1
- package/.next/standalone/.next/server/app/api/v1/windows/cdp-start/route.js.nft.json +1 -1
- package/.next/standalone/.next/server/app/api/v1/windows/cdp-status/route.js.nft.json +1 -1
- package/.next/standalone/.next/server/app/api/v1/windows/close/route.js +1 -1
- package/.next/standalone/.next/server/app/api/v1/windows/close/route.js.nft.json +1 -1
- package/.next/standalone/.next/server/app/api/v1/windows/open/route.js +2 -2
- package/.next/standalone/.next/server/app/api/v1/windows/open/route.js.nft.json +1 -1
- package/.next/standalone/.next/server/app/debug/page/build-manifest.json +3 -3
- package/.next/standalone/.next/server/app/debug/page.js +5 -5
- package/.next/standalone/.next/server/app/debug/page.js.nft.json +1 -1
- package/.next/standalone/.next/server/app/debug/page_client-reference-manifest.js +1 -1
- package/.next/standalone/.next/server/app/page/build-manifest.json +3 -3
- package/.next/standalone/.next/server/app/page.js +5 -5
- package/.next/standalone/.next/server/app/page.js.nft.json +1 -1
- package/.next/standalone/.next/server/app/page_client-reference-manifest.js +1 -1
- package/.next/standalone/.next/server/app-paths-manifest.json +2 -0
- package/.next/standalone/.next/server/chunks/[root-of-the-server]__26662154._.js +1 -1
- package/.next/standalone/.next/server/chunks/[root-of-the-server]__53c4f34d._.js +1 -1
- package/.next/standalone/.next/server/chunks/[root-of-the-server]__851f6b5a._.js +3 -0
- package/.next/standalone/.next/server/chunks/[root-of-the-server]__94275f7f._.js +1 -1
- package/.next/standalone/.next/server/chunks/[root-of-the-server]__9a1969e6._.js +3 -0
- package/.next/standalone/.next/server/chunks/[root-of-the-server]__c34d50c8._.js +1 -1
- package/.next/standalone/.next/server/chunks/[root-of-the-server]__c696771d._.js +1 -1
- package/.next/standalone/.next/server/chunks/[root-of-the-server]__d13bbe3c._.js +3 -0
- package/.next/standalone/.next/server/chunks/[root-of-the-server]__d172e6aa._.js +3 -0
- package/.next/standalone/.next/server/chunks/_next-internal_server_app_api_v1_changes_active_route_actions_1bb9fc18.js +3 -0
- package/.next/standalone/.next/server/chunks/_next-internal_server_app_api_v1_changes_diff_route_actions_65d9ee16.js +3 -0
- package/.next/standalone/.next/server/chunks/ssr/{[root-of-the-server]__d080cb3d._.js → [root-of-the-server]__012405ac._.js} +2 -2
- package/.next/standalone/.next/server/chunks/ssr/[root-of-the-server]__a457c799._.js +3 -0
- package/.next/standalone/.next/server/chunks/ssr/{[root-of-the-server]__f62d412e._.js → [root-of-the-server]__b9356576._.js} +2 -2
- package/.next/standalone/.next/server/chunks/ssr/{[root-of-the-server]__52af585c._.js → [root-of-the-server]__ce78239f._.js} +2 -2
- package/.next/standalone/.next/server/chunks/ssr/{[root-of-the-server]__ae6d24d9._.js → [root-of-the-server]__f47dc36d._.js} +2 -2
- package/.next/standalone/.next/server/chunks/ssr/{_524b2348._.js → _657ecbe9._.js} +3 -3
- package/.next/standalone/.next/server/chunks/ssr/{_fe4475aa._.js → _939145a4._.js} +3 -3
- package/.next/standalone/.next/server/chunks/ssr/app_layout_tsx_271801d7._.js +1 -1
- package/.next/standalone/.next/server/chunks/ssr/app_not-found_tsx_ef35050a._.js +1 -1
- package/.next/standalone/.next/server/chunks/ssr/components_chat-container_tsx_fcbc457f._.js +1 -1
- package/.next/standalone/.next/server/chunks/ssr/node_modules_next_dist_client_components_builtin_global-error_ece394eb.js +3 -0
- package/.next/standalone/.next/server/chunks/ssr/{node_modules_next_dist_esm_build_templates_app-page_39f173ba.js → node_modules_next_dist_esm_build_templates_app-page_7f45f9bf.js} +3 -3
- package/.next/standalone/.next/server/chunks/ssr/{node_modules_next_dist_f183c70b._.js → node_modules_next_dist_f21d913a._.js} +2 -2
- package/.next/standalone/.next/server/middleware-build-manifest.js +3 -3
- package/.next/standalone/.next/server/pages/500.html +2 -2
- package/.next/standalone/.next/server/server-reference-manifest.js +1 -1
- package/.next/standalone/.next/server/server-reference-manifest.json +1 -1
- package/.next/standalone/app/api/v1/artifacts/active/[filename]/route.ts +73 -23
- package/.next/standalone/app/api/v1/artifacts/active/route.ts +103 -52
- package/.next/standalone/app/api/v1/changes/active/route.ts +27 -0
- package/.next/standalone/app/api/v1/changes/diff/route.ts +119 -0
- package/.next/standalone/app/globals.css +424 -0
- package/.next/standalone/app/layout.tsx +3 -3
- package/.next/standalone/app/not-found.tsx +14 -23
- package/.next/standalone/components/artifact-panel.tsx +57 -13
- package/.next/standalone/components/changes-panel.tsx +178 -0
- package/.next/standalone/components/chat-container.tsx +44 -3
- package/.next/standalone/components/chat-input.tsx +44 -0
- package/.next/standalone/components/header.tsx +1 -13
- package/.next/standalone/hooks/use-changes.ts +61 -0
- package/.next/standalone/hooks/use-chat.ts +21 -3
- package/.next/standalone/hooks/use-conversations.ts +19 -11
- package/.next/standalone/lib/scraper/agent-state.ts +89 -54
- package/.next/standalone/lib/scraper/chat-history.ts +215 -85
- package/.next/standalone/lib/scraper/ide-artifacts.ts +212 -0
- package/.next/standalone/lib/scraper/ide-changes.ts +172 -0
- package/.next/standalone/lib/types.ts +11 -0
- package/.next/standalone/package-lock.json +2 -2
- package/.next/standalone/package.json +2 -2
- package/.next/standalone/scripts/check-send-button.js +126 -0
- package/.next/standalone/scripts/find-send-btn.js +65 -0
- package/.next/standalone/tsconfig.tsbuildinfo +1 -1
- package/.next/static/chunks/09dc2aa5c698c324.css +1 -0
- package/.next/static/chunks/{f7c83373e6561461.js → b9a0fabf54a78ef2.js} +1 -1
- package/.next/static/chunks/e2ccf5908cad5a88.js +5 -0
- package/.next/static/chunks/f7cc8fe5822bbc01.js +1 -0
- package/.next/static/chunks/{turbopack-3f34081d758747ed.js → turbopack-7b5dc393c5d3964b.js} +1 -1
- package/package.json +2 -2
- package/.next/standalone/.next/server/chunks/[root-of-the-server]__151eca3a._.js +0 -3
- package/.next/standalone/.next/server/chunks/[root-of-the-server]__ec32b318._.js +0 -3
- package/.next/standalone/.next/server/chunks/ssr/[root-of-the-server]__f77eb371._.js +0 -3
- package/.next/standalone/.next/server/chunks/ssr/app_global-error_tsx_9170b7a0._.js +0 -3
- package/.next/standalone/app/global-error.tsx +0 -42
- package/.next/static/chunks/2317ab948a7d90a4.js +0 -5
- package/.next/static/chunks/2d277a81099566c3.js +0 -1
- package/.next/static/chunks/ad1121f40e497811.css +0 -1
- package/.next/static/chunks/d5d4abede4bc89fd.js +0 -1
|
@@ -80,68 +80,103 @@ export async function getFullAgentState(ctx: ProxyContext): Promise<AgentState>
|
|
|
80
80
|
const wrapper = inputArea.closest('.flex') || inputArea.parentElement?.parentElement || inputArea.parentElement;
|
|
81
81
|
if (wrapper) {
|
|
82
82
|
(window as any).__proxyInputBoxHTML = wrapper.outerHTML;
|
|
83
|
-
const inputBtns = wrapper.querySelectorAll('button');
|
|
84
83
|
|
|
85
|
-
|
|
86
|
-
|
|
84
|
+
// ── Priority check: look for the send/cancel element by data-tooltip-id.
|
|
85
|
+
// In current Antigravity versions, the send/cancel control is a <div>,
|
|
86
|
+
// NOT a <button>, with tooltip like "input-send-button-cancel-tooltip"
|
|
87
|
+
// or "input-send-button-tooltip".
|
|
88
|
+
const sendCancelEl = wrapper.querySelector('[data-tooltip-id*="send"]') ||
|
|
89
|
+
wrapper.querySelector('[data-tooltip-id*="cancel"]');
|
|
87
90
|
|
|
88
|
-
|
|
89
|
-
const
|
|
90
|
-
const
|
|
91
|
-
const text = (btn.textContent || '').trim().toLowerCase();
|
|
92
|
-
const tooltipId = (btn.getAttribute('data-tooltip-id') || '').toLowerCase();
|
|
91
|
+
if (sendCancelEl) {
|
|
92
|
+
const tooltipId = (sendCancelEl.getAttribute('data-tooltip-id') || '').toLowerCase();
|
|
93
|
+
const innerHtml = sendCancelEl.innerHTML || '';
|
|
93
94
|
|
|
94
|
-
//
|
|
95
|
-
//
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
95
|
+
// When running: tooltip contains "cancel", inner HTML has a red/colored
|
|
96
|
+
// square (stop icon) like <div class="bg-red-500 ...rounded-xs">
|
|
97
|
+
const isCancelMode = tooltipId.includes('cancel') ||
|
|
98
|
+
innerHtml.includes('bg-red') ||
|
|
99
|
+
innerHtml.includes('rounded-xs') ||
|
|
100
|
+
innerHtml.match(/lucide-square(?:[^a-z0-9-]|$)/i);
|
|
101
|
+
|
|
102
|
+
// When idle: tooltip is just "send" without "cancel", inner HTML has
|
|
103
|
+
// an arrow-up or send icon
|
|
104
|
+
const isSendMode = !isCancelMode && (
|
|
105
|
+
tooltipId.includes('send') ||
|
|
106
|
+
innerHtml.includes('lucide-arrow-up') ||
|
|
107
|
+
innerHtml.includes('lucide-send')
|
|
108
|
+
);
|
|
109
|
+
|
|
110
|
+
if (isCancelMode) {
|
|
111
|
+
isRunning = true;
|
|
112
|
+
buttonStateDefinitive = true;
|
|
113
|
+
} else if (isSendMode) {
|
|
114
|
+
isRunning = false;
|
|
115
|
+
buttonStateDefinitive = true;
|
|
116
|
+
}
|
|
117
|
+
}
|
|
101
118
|
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
119
|
+
// ── Fallback: scan <button> elements in the wrapper
|
|
120
|
+
if (!buttonStateDefinitive) {
|
|
121
|
+
const inputBtns = wrapper.querySelectorAll('button');
|
|
122
|
+
|
|
123
|
+
let hasStop = false;
|
|
124
|
+
let hasSend = false;
|
|
125
|
+
|
|
126
|
+
for (const btn of inputBtns) {
|
|
127
|
+
const html = btn.innerHTML || '';
|
|
128
|
+
const ariaLabel = (btn.getAttribute('aria-label') || '').toLowerCase();
|
|
129
|
+
const text = (btn.textContent || '').trim().toLowerCase();
|
|
130
|
+
const tooltipId = (btn.getAttribute('data-tooltip-id') || '').toLowerCase();
|
|
131
|
+
|
|
132
|
+
const isStopIcon =
|
|
133
|
+
html.match(/lucide-square(?:[^a-z0-9-]|$)/i) ||
|
|
134
|
+
html.includes('lucide-circle-stop') ||
|
|
135
|
+
html.includes('lucide-octagon');
|
|
136
|
+
|
|
137
|
+
if (
|
|
138
|
+
isStopIcon ||
|
|
139
|
+
ariaLabel.includes('stop') ||
|
|
140
|
+
ariaLabel.includes('cancel') ||
|
|
141
|
+
text === 'stop' ||
|
|
142
|
+
tooltipId.includes('stop')
|
|
143
|
+
) {
|
|
144
|
+
hasStop = true;
|
|
145
|
+
}
|
|
146
|
+
|
|
147
|
+
if (
|
|
148
|
+
html.includes('lucide-send') ||
|
|
149
|
+
html.includes('lucide-arrow-up') ||
|
|
150
|
+
html.includes('lucide-arrow-right') ||
|
|
151
|
+
html.includes('codicon-send') ||
|
|
152
|
+
html.includes('lucide-corner-down-left') ||
|
|
153
|
+
ariaLabel.includes('send') ||
|
|
154
|
+
ariaLabel.includes('submit') ||
|
|
155
|
+
text === 'send' ||
|
|
156
|
+
tooltipId.includes('send')
|
|
157
|
+
) {
|
|
158
|
+
hasSend = true;
|
|
159
|
+
}
|
|
110
160
|
}
|
|
111
161
|
|
|
112
|
-
if (
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
162
|
+
if (hasStop) {
|
|
163
|
+
isRunning = true;
|
|
164
|
+
buttonStateDefinitive = true;
|
|
165
|
+
} else if (hasSend) {
|
|
166
|
+
isRunning = false;
|
|
167
|
+
buttonStateDefinitive = true;
|
|
168
|
+
} else {
|
|
169
|
+
// Send button absent — check for div-based send/cancel element
|
|
170
|
+
const altSendEl = inputArea?.querySelector('[data-tooltip-id*="send"]');
|
|
171
|
+
if (altSendEl) {
|
|
172
|
+
const tip = (altSendEl.getAttribute('data-tooltip-id') || '').toLowerCase();
|
|
173
|
+
isRunning = tip.includes('cancel');
|
|
174
|
+
} else {
|
|
175
|
+
isRunning = true;
|
|
176
|
+
}
|
|
177
|
+
buttonStateDefinitive = true;
|
|
124
178
|
}
|
|
125
179
|
}
|
|
126
|
-
|
|
127
|
-
// Detection logic:
|
|
128
|
-
// - If stop button found → definitely running
|
|
129
|
-
// - If send button found → definitely idle
|
|
130
|
-
// - If NEITHER found (send button removed from DOM) → running
|
|
131
|
-
// Antigravity removes the send button entirely while the agent is
|
|
132
|
-
// active; no stop icon replaces it. The wrapper drops from 4 to 3
|
|
133
|
-
// buttons (Plus, Mode, Mic — no Send).
|
|
134
|
-
if (hasStop) {
|
|
135
|
-
isRunning = true;
|
|
136
|
-
buttonStateDefinitive = true;
|
|
137
|
-
} else if (hasSend) {
|
|
138
|
-
isRunning = false;
|
|
139
|
-
buttonStateDefinitive = true;
|
|
140
|
-
} else {
|
|
141
|
-
// Send button absent from wrapper — agent is running
|
|
142
|
-
isRunning = true;
|
|
143
|
-
buttonStateDefinitive = true;
|
|
144
|
-
}
|
|
145
180
|
}
|
|
146
181
|
}
|
|
147
182
|
|
|
@@ -1,7 +1,21 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* Chat history scraper.
|
|
3
|
-
* Scrolls the Antigravity conversation view
|
|
4
|
-
*
|
|
3
|
+
* Scrolls the Antigravity conversation view viewport-by-viewport,
|
|
4
|
+
* waiting for content to load at each position, then extracts
|
|
5
|
+
* user/agent messages in document order.
|
|
6
|
+
*
|
|
7
|
+
* Key design decisions:
|
|
8
|
+
* - Viewport-pinned extraction: collect content at each scroll position
|
|
9
|
+
* rather than scrolling everything first, because Antigravity re-virtualizes
|
|
10
|
+
* content that scrolls out of view.
|
|
11
|
+
* - Wait for skeleton resolution: skeleton blocks (.bg-gray-500/10) appear
|
|
12
|
+
* temporarily while content loads; we wait for them to clear.
|
|
13
|
+
* - Clean deduplication: dedup keys are built from cleaned text (without
|
|
14
|
+
* injected <style> content) to avoid false collisions.
|
|
15
|
+
* - User message filtering: excludes CODE tags, tool containers, and
|
|
16
|
+
* notify blocks that incorrectly match .whitespace-pre-wrap.
|
|
17
|
+
* - Agent response categorization: distinguishes between thinking summaries,
|
|
18
|
+
* tool descriptions, notify messages, and final responses.
|
|
5
19
|
*/
|
|
6
20
|
|
|
7
21
|
import type { ProxyContext, ChatHistory } from '../types';
|
|
@@ -22,110 +36,226 @@ export async function getChatHistory(ctx: ProxyContext): Promise<ChatHistory> {
|
|
|
22
36
|
if (!scrollArea)
|
|
23
37
|
return { isRunning: false, turnCount: 0, turns: [] };
|
|
24
38
|
|
|
25
|
-
// Step 1: Scroll to top to force older content to render
|
|
26
|
-
scrollArea.scrollTop = 0;
|
|
27
|
-
await new Promise((r) => setTimeout(r, 300));
|
|
28
|
-
|
|
29
|
-
// Step 2: Incrementally scroll down to de-virtualize all content
|
|
30
|
-
const scrollHeight = scrollArea.scrollHeight;
|
|
31
|
-
const viewportHeight = scrollArea.clientHeight;
|
|
32
|
-
const scrollStep = viewportHeight * 0.8;
|
|
33
|
-
let pos = 0;
|
|
34
|
-
while (pos < scrollHeight) {
|
|
35
|
-
scrollArea.scrollTop = pos;
|
|
36
|
-
await new Promise((r) => setTimeout(r, 100));
|
|
37
|
-
pos += scrollStep;
|
|
38
|
-
}
|
|
39
|
-
scrollArea.scrollTop = scrollArea.scrollHeight;
|
|
40
|
-
await new Promise((r) => setTimeout(r, 200));
|
|
41
|
-
|
|
42
|
-
// Step 3: Walk the DOM to find messages in document order
|
|
43
|
-
const turns: { role: 'user' | 'agent'; content: string }[] = [];
|
|
44
|
-
const seen = new Set<string>();
|
|
45
|
-
|
|
46
39
|
const msgList =
|
|
47
40
|
(scrollArea as Element).querySelector('.mx-auto') || scrollArea;
|
|
48
41
|
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
) {
|
|
69
|
-
isInsideAgentResponse = true;
|
|
70
|
-
break;
|
|
42
|
+
// ── Helper: wait for skeleton blocks in current viewport to resolve ──
|
|
43
|
+
async function waitForLoad(maxWait = 3000) {
|
|
44
|
+
const start = Date.now();
|
|
45
|
+
while (Date.now() - start < maxWait) {
|
|
46
|
+
const skeletons = Array.from(msgList.querySelectorAll(
|
|
47
|
+
'.rounded-lg.bg-gray-500\\/10'
|
|
48
|
+
));
|
|
49
|
+
let visibleSkeletons = 0;
|
|
50
|
+
const panelRect = scrollArea!.getBoundingClientRect();
|
|
51
|
+
for (const sk of skeletons) {
|
|
52
|
+
const rect = sk.getBoundingClientRect();
|
|
53
|
+
const relTop = rect.top - panelRect.top;
|
|
54
|
+
const relBottom = rect.bottom - panelRect.top;
|
|
55
|
+
if (
|
|
56
|
+
relBottom > -50 &&
|
|
57
|
+
relTop < scrollArea!.clientHeight + 50
|
|
58
|
+
) {
|
|
59
|
+
visibleSkeletons++;
|
|
60
|
+
}
|
|
71
61
|
}
|
|
72
|
-
|
|
62
|
+
if (visibleSkeletons === 0) break;
|
|
63
|
+
await new Promise((r) => setTimeout(r, 100));
|
|
73
64
|
}
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
if (el.closest('[data-lexical-editor]')) continue;
|
|
77
|
-
if (el.closest('#antigravity\\.agentSidePanelInputBox')) continue;
|
|
65
|
+
}
|
|
78
66
|
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
67
|
+
// ── Helper: extract clean text from an element (strips <style>) ──
|
|
68
|
+
function getCleanText(el: Element): string {
|
|
69
|
+
const clone = el.cloneNode(true) as Element;
|
|
70
|
+
clone.querySelectorAll('style, script').forEach((n) => n.remove());
|
|
71
|
+
return (clone as HTMLElement).textContent?.trim() || '';
|
|
72
|
+
}
|
|
82
73
|
|
|
83
|
-
|
|
74
|
+
// ── Helper: extract clean HTML from an element ──
|
|
75
|
+
function getCleanHTML(el: Element): string {
|
|
76
|
+
const clone = el.cloneNode(true) as Element;
|
|
77
|
+
clone.querySelectorAll('style, script').forEach((n) => n.remove());
|
|
78
|
+
// Remove Antigravity interactive UI chrome
|
|
79
|
+
clone
|
|
80
|
+
.querySelectorAll(
|
|
81
|
+
'svg.cursor-pointer, [class*="cursor-pointer"][class*="opacity-70"], button[class*="opacity-70"]'
|
|
82
|
+
)
|
|
83
|
+
.forEach((n) => n.remove());
|
|
84
|
+
return (clone as HTMLElement).innerHTML?.trim() || '';
|
|
84
85
|
}
|
|
85
86
|
|
|
86
|
-
//
|
|
87
|
-
|
|
88
|
-
'.leading-relaxed.select-text'
|
|
89
|
-
);
|
|
90
|
-
for (const el of allResponses) {
|
|
91
|
-
let hidden = false;
|
|
87
|
+
// ── Helper: check if element is visually hidden ──
|
|
88
|
+
function isHidden(el: Element, root: Element): boolean {
|
|
92
89
|
let ancestor = el.parentElement;
|
|
93
90
|
let depth = 0;
|
|
94
|
-
while (ancestor && ancestor !==
|
|
91
|
+
while (ancestor && ancestor !== root && depth < 15) {
|
|
95
92
|
const cls = ancestor.getAttribute('class') || '';
|
|
96
93
|
if (cls.includes('max-h-0') || cls.includes('hidden')) {
|
|
97
|
-
|
|
98
|
-
|
|
94
|
+
return true;
|
|
95
|
+
}
|
|
96
|
+
// Check inline styles too
|
|
97
|
+
const style = (ancestor as HTMLElement).style;
|
|
98
|
+
if (
|
|
99
|
+
style &&
|
|
100
|
+
(style.display === 'none' || style.maxHeight === '0px')
|
|
101
|
+
) {
|
|
102
|
+
return true;
|
|
103
|
+
}
|
|
104
|
+
// Check if the element has zero height (collapsed)
|
|
105
|
+
if ((ancestor as HTMLElement).offsetHeight === 0) {
|
|
106
|
+
return true;
|
|
99
107
|
}
|
|
100
108
|
ancestor = ancestor.parentElement;
|
|
101
109
|
depth++;
|
|
102
110
|
}
|
|
103
|
-
|
|
111
|
+
return false;
|
|
112
|
+
}
|
|
104
113
|
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
114
|
+
// ── Viewport-by-viewport extraction ──
|
|
115
|
+
// Items are tagged with their absolute Y position at collection time
|
|
116
|
+
// so we can sort chronologically even after elements get re-virtualized.
|
|
117
|
+
const seen = new Set<string>();
|
|
118
|
+
const candidates: {
|
|
119
|
+
absY: number; // scrollTop + element offset — for chronological sorting
|
|
120
|
+
role: 'user' | 'agent';
|
|
121
|
+
content: string;
|
|
122
|
+
contentType?: string;
|
|
123
|
+
}[] = [];
|
|
124
|
+
|
|
125
|
+
function collectAtCurrentPosition() {
|
|
126
|
+
const panelRect = scrollArea!.getBoundingClientRect();
|
|
127
|
+
const currentScroll = scrollArea!.scrollTop;
|
|
128
|
+
|
|
129
|
+
// ── Collect user messages ──
|
|
130
|
+
for (const el of Array.from(msgList.querySelectorAll('.whitespace-pre-wrap'))) {
|
|
131
|
+
const text = el.textContent?.trim();
|
|
132
|
+
if (!text || text.length < 2) continue;
|
|
133
|
+
|
|
134
|
+
// Skip if inside agent response block
|
|
135
|
+
if (el.closest('.leading-relaxed.select-text')) continue;
|
|
136
|
+
// Skip editor / input box
|
|
137
|
+
if (el.closest('[data-lexical-editor]')) continue;
|
|
138
|
+
if (
|
|
139
|
+
el.closest('#antigravity\\.agentSidePanelInputBox')
|
|
140
|
+
)
|
|
141
|
+
continue;
|
|
142
|
+
// Skip CODE tags (inline code references like `file.ts`)
|
|
143
|
+
if (el.tagName === 'CODE') continue;
|
|
144
|
+
// Skip elements inside tool containers
|
|
145
|
+
if (
|
|
146
|
+
el.closest(
|
|
147
|
+
'.flex.flex-col.gap-2.border.rounded-lg'
|
|
148
|
+
)
|
|
149
|
+
)
|
|
150
|
+
continue;
|
|
151
|
+
// Skip elements inside notify containers (these are agent content)
|
|
152
|
+
if (el.closest('.notify-user-container')) continue;
|
|
153
|
+
|
|
154
|
+
// Check if in current viewport
|
|
155
|
+
const rect = el.getBoundingClientRect();
|
|
156
|
+
const relTop = rect.top - panelRect.top;
|
|
157
|
+
if (
|
|
158
|
+
relTop < -100 ||
|
|
159
|
+
relTop > scrollArea!.clientHeight + 100
|
|
160
|
+
)
|
|
161
|
+
continue;
|
|
162
|
+
|
|
163
|
+
const key = 'user:' + text.substring(0, 300);
|
|
164
|
+
if (seen.has(key)) continue;
|
|
165
|
+
seen.add(key);
|
|
166
|
+
|
|
167
|
+
// Absolute Y position = scroll offset + element's position relative to scroll container
|
|
168
|
+
const absY = currentScroll + relTop;
|
|
169
|
+
candidates.push({ absY, role: 'user', content: text });
|
|
170
|
+
}
|
|
171
|
+
|
|
172
|
+
// ── Collect agent response blocks ──
|
|
173
|
+
for (const el of Array.from(msgList.querySelectorAll(
|
|
174
|
+
'.leading-relaxed.select-text'
|
|
175
|
+
))) {
|
|
176
|
+
if (isHidden(el, msgList)) continue;
|
|
177
|
+
|
|
178
|
+
// Check if in current viewport
|
|
179
|
+
const rect = el.getBoundingClientRect();
|
|
180
|
+
const relTop = rect.top - panelRect.top;
|
|
181
|
+
if (
|
|
182
|
+
relTop < -100 ||
|
|
183
|
+
relTop > scrollArea!.clientHeight + 100
|
|
184
|
+
)
|
|
185
|
+
continue;
|
|
186
|
+
|
|
187
|
+
// Get clean HTML (strips <style>, <script>, UI chrome)
|
|
188
|
+
const html = getCleanHTML(el);
|
|
189
|
+
if (!html) continue;
|
|
190
|
+
|
|
191
|
+
// Build dedup key from CLEAN text (not raw textContent which includes <style>)
|
|
192
|
+
const cleanText = getCleanText(el);
|
|
193
|
+
if (!cleanText) continue;
|
|
194
|
+
|
|
195
|
+
const key = 'agent:' + cleanText.substring(0, 300);
|
|
196
|
+
if (seen.has(key)) continue;
|
|
197
|
+
seen.add(key);
|
|
198
|
+
|
|
199
|
+
// Categorize the response
|
|
200
|
+
const inNotify = !!el.closest('.notify-user-container');
|
|
201
|
+
const parentClass =
|
|
202
|
+
el.parentElement?.getAttribute('class') || '';
|
|
203
|
+
const isThinkingSummary =
|
|
204
|
+
parentClass.includes('font-medium') &&
|
|
205
|
+
parentClass.includes('pb-0');
|
|
206
|
+
|
|
207
|
+
// Skip thinking summaries (these are short descriptions of agent thoughts,
|
|
208
|
+
// not the actual response to the user)
|
|
209
|
+
if (isThinkingSummary) continue;
|
|
210
|
+
|
|
211
|
+
const contentType = inNotify ? 'notify' : 'response';
|
|
212
|
+
const absY = currentScroll + relTop;
|
|
213
|
+
|
|
214
|
+
candidates.push({
|
|
215
|
+
absY,
|
|
216
|
+
role: 'agent',
|
|
217
|
+
content: html,
|
|
218
|
+
contentType,
|
|
219
|
+
});
|
|
220
|
+
}
|
|
119
221
|
}
|
|
120
222
|
|
|
121
|
-
//
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
223
|
+
// ── Scroll through entire conversation, collecting at each stop ──
|
|
224
|
+
const overallStart = Date.now();
|
|
225
|
+
const OVERALL_TIMEOUT = 30000; // 30s max for the entire scroll
|
|
226
|
+
|
|
227
|
+
scrollArea.scrollTop = 0;
|
|
228
|
+
await waitForLoad(2000);
|
|
229
|
+
await new Promise((r) => setTimeout(r, 200));
|
|
230
|
+
collectAtCurrentPosition();
|
|
231
|
+
|
|
232
|
+
const step = scrollArea.clientHeight * 0.8; // 80% viewport step
|
|
233
|
+
let pos = step;
|
|
234
|
+
let scrollSteps = 0;
|
|
235
|
+
const maxScrollSteps = 200; // Safety limit
|
|
128
236
|
|
|
237
|
+
while (pos < scrollArea.scrollHeight + scrollArea.clientHeight) {
|
|
238
|
+
if (Date.now() - overallStart > OVERALL_TIMEOUT) break;
|
|
239
|
+
scrollArea.scrollTop = pos;
|
|
240
|
+
await waitForLoad(1500);
|
|
241
|
+
await new Promise((r) => setTimeout(r, 50));
|
|
242
|
+
collectAtCurrentPosition();
|
|
243
|
+
pos += step;
|
|
244
|
+
scrollSteps++;
|
|
245
|
+
if (scrollSteps > maxScrollSteps) break;
|
|
246
|
+
}
|
|
247
|
+
|
|
248
|
+
// Final collection at bottom
|
|
249
|
+
scrollArea.scrollTop = scrollArea.scrollHeight;
|
|
250
|
+
await waitForLoad(1500);
|
|
251
|
+
await new Promise((r) => setTimeout(r, 200));
|
|
252
|
+
collectAtCurrentPosition();
|
|
253
|
+
|
|
254
|
+
// ── Sort by absolute Y position (chronological order) ──
|
|
255
|
+
candidates.sort((a, b) => a.absY - b.absY);
|
|
256
|
+
|
|
257
|
+
// ── Build final turn list ──
|
|
258
|
+
const turns: { role: 'user' | 'agent'; content: string }[] = [];
|
|
129
259
|
for (const c of candidates) {
|
|
130
260
|
turns.push({ role: c.role, content: c.content });
|
|
131
261
|
}
|