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.
Files changed (113) hide show
  1. package/.next/standalone/.next/app-path-routes-manifest.json +2 -0
  2. package/.next/standalone/.next/build-manifest.json +3 -3
  3. package/.next/standalone/.next/prerender-manifest.json +3 -3
  4. package/.next/standalone/.next/routes-manifest.json +12 -0
  5. package/.next/standalone/.next/server/app/_global-error/page/build-manifest.json +3 -3
  6. package/.next/standalone/.next/server/app/_global-error/page.js +2 -2
  7. package/.next/standalone/.next/server/app/_global-error/page.js.nft.json +1 -1
  8. package/.next/standalone/.next/server/app/_global-error.html +2 -2
  9. package/.next/standalone/.next/server/app/_not-found/page/build-manifest.json +3 -3
  10. package/.next/standalone/.next/server/app/_not-found/page.js +5 -6
  11. package/.next/standalone/.next/server/app/_not-found/page.js.nft.json +1 -1
  12. package/.next/standalone/.next/server/app/_not-found/page_client-reference-manifest.js +1 -1
  13. package/.next/standalone/.next/server/app/api/v1/artifacts/active/route.js +5 -1
  14. package/.next/standalone/.next/server/app/api/v1/artifacts/active/route.js.nft.json +1 -1
  15. package/.next/standalone/.next/server/app/api/v1/changes/active/route/app-paths-manifest.json +3 -0
  16. package/.next/standalone/.next/server/app/api/v1/changes/active/route/build-manifest.json +11 -0
  17. package/.next/standalone/.next/server/app/api/v1/changes/active/route/server-reference-manifest.json +4 -0
  18. package/.next/standalone/.next/server/app/api/v1/changes/active/route.js +11 -0
  19. package/.next/standalone/.next/server/app/api/v1/changes/active/route.js.map +5 -0
  20. package/.next/standalone/.next/server/app/api/v1/changes/active/route.js.nft.json +1 -0
  21. package/.next/standalone/.next/server/app/api/v1/changes/active/route_client-reference-manifest.js +2 -0
  22. package/.next/standalone/.next/server/app/api/v1/changes/diff/route/app-paths-manifest.json +3 -0
  23. package/.next/standalone/.next/server/app/api/v1/changes/diff/route/build-manifest.json +11 -0
  24. package/.next/standalone/.next/server/app/api/v1/changes/diff/route/server-reference-manifest.json +4 -0
  25. package/.next/standalone/.next/server/app/api/v1/changes/diff/route.js +7 -0
  26. package/.next/standalone/.next/server/app/api/v1/changes/diff/route.js.map +5 -0
  27. package/.next/standalone/.next/server/app/api/v1/changes/diff/route.js.nft.json +1 -0
  28. package/.next/standalone/.next/server/app/api/v1/changes/diff/route_client-reference-manifest.js +2 -0
  29. package/.next/standalone/.next/server/app/api/v1/chat/stream/route.js +1 -1
  30. package/.next/standalone/.next/server/app/api/v1/chat/stream/route.js.nft.json +1 -1
  31. package/.next/standalone/.next/server/app/api/v1/windows/cdp-start/route.js.nft.json +1 -1
  32. package/.next/standalone/.next/server/app/api/v1/windows/cdp-status/route.js.nft.json +1 -1
  33. package/.next/standalone/.next/server/app/api/v1/windows/close/route.js +1 -1
  34. package/.next/standalone/.next/server/app/api/v1/windows/close/route.js.nft.json +1 -1
  35. package/.next/standalone/.next/server/app/api/v1/windows/open/route.js +2 -2
  36. package/.next/standalone/.next/server/app/api/v1/windows/open/route.js.nft.json +1 -1
  37. package/.next/standalone/.next/server/app/debug/page/build-manifest.json +3 -3
  38. package/.next/standalone/.next/server/app/debug/page.js +5 -5
  39. package/.next/standalone/.next/server/app/debug/page.js.nft.json +1 -1
  40. package/.next/standalone/.next/server/app/debug/page_client-reference-manifest.js +1 -1
  41. package/.next/standalone/.next/server/app/page/build-manifest.json +3 -3
  42. package/.next/standalone/.next/server/app/page.js +5 -5
  43. package/.next/standalone/.next/server/app/page.js.nft.json +1 -1
  44. package/.next/standalone/.next/server/app/page_client-reference-manifest.js +1 -1
  45. package/.next/standalone/.next/server/app-paths-manifest.json +2 -0
  46. package/.next/standalone/.next/server/chunks/[root-of-the-server]__26662154._.js +1 -1
  47. package/.next/standalone/.next/server/chunks/[root-of-the-server]__53c4f34d._.js +1 -1
  48. package/.next/standalone/.next/server/chunks/[root-of-the-server]__851f6b5a._.js +3 -0
  49. package/.next/standalone/.next/server/chunks/[root-of-the-server]__94275f7f._.js +1 -1
  50. package/.next/standalone/.next/server/chunks/[root-of-the-server]__9a1969e6._.js +3 -0
  51. package/.next/standalone/.next/server/chunks/[root-of-the-server]__c34d50c8._.js +1 -1
  52. package/.next/standalone/.next/server/chunks/[root-of-the-server]__c696771d._.js +1 -1
  53. package/.next/standalone/.next/server/chunks/[root-of-the-server]__d13bbe3c._.js +3 -0
  54. package/.next/standalone/.next/server/chunks/[root-of-the-server]__d172e6aa._.js +3 -0
  55. package/.next/standalone/.next/server/chunks/_next-internal_server_app_api_v1_changes_active_route_actions_1bb9fc18.js +3 -0
  56. package/.next/standalone/.next/server/chunks/_next-internal_server_app_api_v1_changes_diff_route_actions_65d9ee16.js +3 -0
  57. package/.next/standalone/.next/server/chunks/ssr/{[root-of-the-server]__d080cb3d._.js → [root-of-the-server]__012405ac._.js} +2 -2
  58. package/.next/standalone/.next/server/chunks/ssr/[root-of-the-server]__a457c799._.js +3 -0
  59. package/.next/standalone/.next/server/chunks/ssr/{[root-of-the-server]__f62d412e._.js → [root-of-the-server]__b9356576._.js} +2 -2
  60. package/.next/standalone/.next/server/chunks/ssr/{[root-of-the-server]__52af585c._.js → [root-of-the-server]__ce78239f._.js} +2 -2
  61. package/.next/standalone/.next/server/chunks/ssr/{[root-of-the-server]__ae6d24d9._.js → [root-of-the-server]__f47dc36d._.js} +2 -2
  62. package/.next/standalone/.next/server/chunks/ssr/{_524b2348._.js → _657ecbe9._.js} +3 -3
  63. package/.next/standalone/.next/server/chunks/ssr/{_fe4475aa._.js → _939145a4._.js} +3 -3
  64. package/.next/standalone/.next/server/chunks/ssr/app_layout_tsx_271801d7._.js +1 -1
  65. package/.next/standalone/.next/server/chunks/ssr/app_not-found_tsx_ef35050a._.js +1 -1
  66. package/.next/standalone/.next/server/chunks/ssr/components_chat-container_tsx_fcbc457f._.js +1 -1
  67. package/.next/standalone/.next/server/chunks/ssr/node_modules_next_dist_client_components_builtin_global-error_ece394eb.js +3 -0
  68. 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
  69. package/.next/standalone/.next/server/chunks/ssr/{node_modules_next_dist_f183c70b._.js → node_modules_next_dist_f21d913a._.js} +2 -2
  70. package/.next/standalone/.next/server/middleware-build-manifest.js +3 -3
  71. package/.next/standalone/.next/server/pages/500.html +2 -2
  72. package/.next/standalone/.next/server/server-reference-manifest.js +1 -1
  73. package/.next/standalone/.next/server/server-reference-manifest.json +1 -1
  74. package/.next/standalone/app/api/v1/artifacts/active/[filename]/route.ts +73 -23
  75. package/.next/standalone/app/api/v1/artifacts/active/route.ts +103 -52
  76. package/.next/standalone/app/api/v1/changes/active/route.ts +27 -0
  77. package/.next/standalone/app/api/v1/changes/diff/route.ts +119 -0
  78. package/.next/standalone/app/globals.css +424 -0
  79. package/.next/standalone/app/layout.tsx +3 -3
  80. package/.next/standalone/app/not-found.tsx +14 -23
  81. package/.next/standalone/components/artifact-panel.tsx +57 -13
  82. package/.next/standalone/components/changes-panel.tsx +178 -0
  83. package/.next/standalone/components/chat-container.tsx +44 -3
  84. package/.next/standalone/components/chat-input.tsx +44 -0
  85. package/.next/standalone/components/header.tsx +1 -13
  86. package/.next/standalone/hooks/use-changes.ts +61 -0
  87. package/.next/standalone/hooks/use-chat.ts +21 -3
  88. package/.next/standalone/hooks/use-conversations.ts +19 -11
  89. package/.next/standalone/lib/scraper/agent-state.ts +89 -54
  90. package/.next/standalone/lib/scraper/chat-history.ts +215 -85
  91. package/.next/standalone/lib/scraper/ide-artifacts.ts +212 -0
  92. package/.next/standalone/lib/scraper/ide-changes.ts +172 -0
  93. package/.next/standalone/lib/types.ts +11 -0
  94. package/.next/standalone/package-lock.json +2 -2
  95. package/.next/standalone/package.json +2 -2
  96. package/.next/standalone/scripts/check-send-button.js +126 -0
  97. package/.next/standalone/scripts/find-send-btn.js +65 -0
  98. package/.next/standalone/tsconfig.tsbuildinfo +1 -1
  99. package/.next/static/chunks/09dc2aa5c698c324.css +1 -0
  100. package/.next/static/chunks/{f7c83373e6561461.js → b9a0fabf54a78ef2.js} +1 -1
  101. package/.next/static/chunks/e2ccf5908cad5a88.js +5 -0
  102. package/.next/static/chunks/f7cc8fe5822bbc01.js +1 -0
  103. package/.next/static/chunks/{turbopack-3f34081d758747ed.js → turbopack-7b5dc393c5d3964b.js} +1 -1
  104. package/package.json +2 -2
  105. package/.next/standalone/.next/server/chunks/[root-of-the-server]__151eca3a._.js +0 -3
  106. package/.next/standalone/.next/server/chunks/[root-of-the-server]__ec32b318._.js +0 -3
  107. package/.next/standalone/.next/server/chunks/ssr/[root-of-the-server]__f77eb371._.js +0 -3
  108. package/.next/standalone/.next/server/chunks/ssr/app_global-error_tsx_9170b7a0._.js +0 -3
  109. package/.next/standalone/app/global-error.tsx +0 -42
  110. package/.next/static/chunks/2317ab948a7d90a4.js +0 -5
  111. package/.next/static/chunks/2d277a81099566c3.js +0 -1
  112. package/.next/static/chunks/ad1121f40e497811.css +0 -1
  113. package/.next/static/chunks/d5d4abede4bc89fd.js +0 -1
@@ -0,0 +1,212 @@
1
+ /**
2
+ * Scrapes the artifact list directly from the Antigravity IDE's conversation panel.
3
+ *
4
+ * The IDE maintains a "tooltip-artifacts" toggle button in the input toolbar.
5
+ * Clicking it reveals a section headed "Artifacts (N Files for Conversation)"
6
+ * with rows showing artifact names and last-updated timestamps.
7
+ *
8
+ * By scraping this section we get the EXACT artifacts for the current
9
+ * conversation — no more cross-conversation mixing.
10
+ */
11
+
12
+ import type { ProxyContext } from '../types';
13
+ import { logger } from '../logger';
14
+
15
+ export interface IdeArtifact {
16
+ /** Display name, e.g. "task.md" or "Pricing Blue Cards" */
17
+ name: string;
18
+ /** Raw timestamp text from the IDE, e.g. "Mar 10 11:21 PM" */
19
+ lastUpdated: string | null;
20
+ /** Whether this looks like a file (has an extension) */
21
+ isFile: boolean;
22
+ }
23
+
24
+ export interface IdeArtifactResult {
25
+ /** Total count stated in the section header */
26
+ totalCount: number;
27
+ /** Scraped artifact entries */
28
+ artifacts: IdeArtifact[];
29
+ /** The conversation title at the time of scraping */
30
+ conversationTitle: string | null;
31
+ }
32
+
33
+ /**
34
+ * Get the list of artifacts from the IDE's conversation panel.
35
+ *
36
+ * Strategy:
37
+ * 1. Check whether the artifact section is already visible.
38
+ * 2. If not, click `[data-tooltip-id="tooltip-artifacts"]` to toggle it open.
39
+ * 3. Parse the rows to extract artifact names and timestamps.
40
+ * 4. Close the section again (toggle) to leave the IDE clean.
41
+ */
42
+ export async function getIdeArtifacts(ctx: ProxyContext): Promise<IdeArtifactResult> {
43
+ if (!ctx.workbenchPage) {
44
+ logger.info('[IdeArtifacts] No active workbench page.');
45
+ return { totalCount: 0, artifacts: [], conversationTitle: null };
46
+ }
47
+
48
+ try {
49
+ const result = await ctx.workbenchPage.evaluate(async () => {
50
+ const panel = document.querySelector('.antigravity-agent-side-panel');
51
+ if (!panel) return { error: 'No agent panel found' };
52
+
53
+ // Get current conversation title
54
+ const titleEl = panel.querySelector('span.font-semibold.text-ide-text-color');
55
+ const conversationTitle = titleEl?.textContent?.trim() || null;
56
+
57
+ // Check whether the artifact section is already open
58
+ const findArtifactHeader = (): Element | null => {
59
+ for (const el of panel.querySelectorAll('*')) {
60
+ const t = (el.textContent || '').trim();
61
+ if (
62
+ t.startsWith('Artifacts (') &&
63
+ t.includes('Files') &&
64
+ el.children.length <= 3
65
+ ) {
66
+ return el;
67
+ }
68
+ }
69
+ return null;
70
+ };
71
+
72
+ let header = findArtifactHeader();
73
+ let didOpen = false;
74
+
75
+ if (!header) {
76
+ // Click the artifact button to open the section
77
+ const btn = panel.querySelector('[data-tooltip-id="tooltip-artifacts"]');
78
+ if (!btn) return { error: 'No artifact button (tooltip-artifacts) found' };
79
+
80
+ (btn as HTMLElement).click();
81
+ await new Promise(r => setTimeout(r, 1500));
82
+ header = findArtifactHeader();
83
+ didOpen = true;
84
+ }
85
+
86
+ if (!header) {
87
+ return { error: 'Artifact section did not appear after clicking' };
88
+ }
89
+
90
+ // Parse total count from header like "Artifacts (15 Files for Conversation)"
91
+ const headerText = header.textContent?.trim() || '';
92
+ const countMatch = headerText.match(/Artifacts\s*\((\d+)\s*Files?/);
93
+ const totalCount = countMatch ? parseInt(countMatch[1], 10) : 0;
94
+
95
+ // Find the section container — walk up until we find the container
96
+ // that holds the header AND the file rows
97
+ let section = header as HTMLElement;
98
+ for (let i = 0; i < 8; i++) {
99
+ if (!section.parentElement) break;
100
+ section = section.parentElement;
101
+ const text = section.textContent || '';
102
+ if (text.includes('Artifact Name') && text.includes('Last Updated')) {
103
+ break;
104
+ }
105
+ }
106
+
107
+ // Extract artifact rows — each row has a name and a date
108
+ // Strategy: find all visible row-like flex containers within the section
109
+ const rows = section.querySelectorAll(
110
+ '.flex.w-full.flex-row.items-center.justify-between'
111
+ );
112
+
113
+ const artifacts: Array<{ name: string; lastUpdated: string | null }> = [];
114
+
115
+ for (const row of rows) {
116
+ const cells = row.children;
117
+ if (cells.length < 2) continue;
118
+
119
+ const nameText = (cells[0].textContent || '').trim();
120
+ const dateText = (cells[1].textContent || '').trim();
121
+
122
+ // Skip the header row itself
123
+ if (nameText === 'Artifact Name' || !nameText) continue;
124
+
125
+ artifacts.push({ name: nameText, lastUpdated: dateText || null });
126
+ }
127
+
128
+ // Fallback: if no rows found via flex layout, try extracting from text nodes
129
+ if (artifacts.length === 0) {
130
+ // Use TreeWalker to find file-name and date text nodes
131
+ const walker = document.createTreeWalker(section, NodeFilter.SHOW_TEXT);
132
+ const allTexts: string[] = [];
133
+ while (walker.nextNode()) {
134
+ const t = walker.currentNode.textContent?.trim();
135
+ if (t && t.length > 0) allTexts.push(t);
136
+ }
137
+
138
+ // Parse pairs: artifact name followed by date
139
+ for (let i = 0; i < allTexts.length; i++) {
140
+ const text = allTexts[i];
141
+ // Skip header/control texts
142
+ if (
143
+ text === 'Artifact Name' ||
144
+ text === 'Last Updated' ||
145
+ text.startsWith('Artifacts (') ||
146
+ text === 'Review Changes'
147
+ ) {
148
+ continue;
149
+ }
150
+
151
+ // Check if this has a date pattern like "Mar 10 11:21 PM"
152
+ const dateMatch = text.match(
153
+ /\(([A-Z][a-z]{2}\s+\d{1,2}\s+\d{1,2}:\d{2}\s*(?:AM|PM)?)\)$/
154
+ );
155
+ if (dateMatch) {
156
+ const name = text.replace(dateMatch[0], '').trim();
157
+ artifacts.push({ name, lastUpdated: dateMatch[1] });
158
+ } else if (
159
+ text.match(
160
+ /\.(md|json|txt|ts|tsx|js|jsx|css|html|yaml|yml|py|sh)$/
161
+ )
162
+ ) {
163
+ // It's a file name — check if next text is a date
164
+ const nextText = allTexts[i + 1] || '';
165
+ const nextDate = nextText.match(
166
+ /^[A-Z][a-z]{2}\s+\d{1,2}\s+\d{1,2}:\d{2}/
167
+ );
168
+ artifacts.push({
169
+ name: text,
170
+ lastUpdated: nextDate ? nextText : null,
171
+ });
172
+ if (nextDate) i++; // Skip the date we already consumed
173
+ }
174
+ }
175
+ }
176
+
177
+ // Close the section if we opened it (toggle back)
178
+ if (didOpen) {
179
+ const btn = panel.querySelector('[data-tooltip-id="tooltip-artifacts"]');
180
+ if (btn) (btn as HTMLElement).click();
181
+ }
182
+
183
+ return { conversationTitle, totalCount, artifacts };
184
+ });
185
+
186
+ if (result && 'error' in result) {
187
+ logger.error(`[IdeArtifacts] ${result.error}`);
188
+ return { totalCount: 0, artifacts: [], conversationTitle: null };
189
+ }
190
+
191
+ const artifacts: IdeArtifact[] = (result?.artifacts || []).map(
192
+ (a: { name: string; lastUpdated: string | null }) => ({
193
+ name: a.name,
194
+ lastUpdated: a.lastUpdated,
195
+ isFile: /\.\w{1,5}$/.test(a.name),
196
+ })
197
+ );
198
+
199
+ logger.info(
200
+ `[IdeArtifacts] Scraped ${artifacts.length}/${result?.totalCount || 0} artifacts for "${result?.conversationTitle}"`
201
+ );
202
+
203
+ return {
204
+ totalCount: result?.totalCount || 0,
205
+ artifacts,
206
+ conversationTitle: result?.conversationTitle || null,
207
+ };
208
+ } catch (err: any) {
209
+ logger.error(`[IdeArtifacts] Error scraping: ${err.message}`);
210
+ return { totalCount: 0, artifacts: [], conversationTitle: null };
211
+ }
212
+ }
@@ -0,0 +1,172 @@
1
+ /**
2
+ * IDE Changes Overview Scraper
3
+ *
4
+ * Scrapes the "Changes Overview" section from the IDE's agent panel.
5
+ * This section lists files that have been modified/created/deleted
6
+ * during the current conversation, with +additions/-deletions counts.
7
+ *
8
+ * DOM structure (when open):
9
+ * .flex.grow.flex-col.justify-start.gap-8
10
+ * └ div.relative.flex.flex-col.mb-2
11
+ * └ div.px-2 (header bar)
12
+ * └ div (the outline container)
13
+ * ├ span "N Files With Changes"
14
+ * ├ "Reject all" / "Accept all"
15
+ * └ div.pointer-events-none.absolute.bottom-full (dropdown)
16
+ * └ div.flex.flex-col (rows container)
17
+ * └ each row: [+][N][-][M] [filename] [filepath]
18
+ */
19
+
20
+ import type { ProxyContext } from '../types';
21
+
22
+ export interface ChangeFile {
23
+ filename: string;
24
+ filepath: string;
25
+ additions: number;
26
+ deletions: number;
27
+ }
28
+
29
+ export interface IdeChangesResult {
30
+ changes: ChangeFile[];
31
+ totalCount: number;
32
+ }
33
+
34
+ export async function getIdeChanges(ctx: ProxyContext): Promise<IdeChangesResult> {
35
+ const page = ctx.workbenchPage;
36
+ if (!page) return { changes: [], totalCount: 0 };
37
+
38
+ try {
39
+ const result = await page.evaluate(async () => {
40
+ const panel = document.querySelector('.antigravity-agent-side-panel');
41
+ if (!panel) return { changes: [] as any[], totalCount: 0, error: 'no panel' };
42
+
43
+ // Find the gap-8 container that holds the changes section
44
+ const gapContainer = panel.querySelector('.flex.grow.flex-col.justify-start.gap-8');
45
+ if (!gapContainer) return { changes: [] as any[], totalCount: 0, error: 'no gap container' };
46
+
47
+ // Check if the changes section is already visible
48
+ let section = Array.from(gapContainer.children).find(c =>
49
+ (c.textContent || '').includes('Files With Changes')
50
+ ) as HTMLElement | undefined;
51
+
52
+ let didOpen = false;
53
+
54
+ if (!section) {
55
+ // Toggle the changesOverview button to open it
56
+ const btn = panel.querySelector('[data-tooltip-id="tooltip-changesOverview"]') as HTMLElement;
57
+ if (!btn) return { changes: [] as any[], totalCount: 0, error: 'no changesOverview button' };
58
+
59
+ btn.click();
60
+ // Wait for the section to appear
61
+ for (let i = 0; i < 10; i++) {
62
+ await new Promise(r => setTimeout(r, 300));
63
+ section = Array.from(gapContainer.children).find(c =>
64
+ (c.textContent || '').includes('Files With Changes')
65
+ ) as HTMLElement | undefined;
66
+ if (section) break;
67
+ }
68
+ didOpen = true;
69
+ }
70
+
71
+ if (!section) {
72
+ // Close if we opened but nothing appeared
73
+ if (didOpen) {
74
+ const btn = panel.querySelector('[data-tooltip-id="tooltip-changesOverview"]') as HTMLElement;
75
+ if (btn) btn.click();
76
+ }
77
+ return { changes: [] as any[], totalCount: 0 };
78
+ }
79
+
80
+ // Find the dropdown containing file rows
81
+ const dropdown = section.querySelector(
82
+ '.pointer-events-none.absolute.bottom-full .flex.flex-col'
83
+ );
84
+
85
+ const changes: any[] = [];
86
+
87
+ if (dropdown) {
88
+ const rows = Array.from(dropdown.children);
89
+ for (const row of rows) {
90
+ const walker = document.createTreeWalker(row, NodeFilter.SHOW_TEXT);
91
+ const texts: string[] = [];
92
+ while (walker.nextNode()) {
93
+ const t = (walker.currentNode.textContent || '').trim();
94
+ if (t) texts.push(t);
95
+ }
96
+
97
+ // Parse: ["+"] ["N"] ["-"] ["M"] ["filename.ext"] ["path/to/filename.ext"]
98
+ // The + and number may be separate text nodes
99
+ let additions = 0, deletions = 0;
100
+ let filename = '', filepath = '';
101
+
102
+ let plusSeen = false, minusSeen = false;
103
+ for (let i = 0; i < texts.length; i++) {
104
+ const t = texts[i];
105
+
106
+ // Handle "+N" as single token
107
+ const addSingle = t.match(/^\+(\d+)$/);
108
+ if (addSingle) { additions = parseInt(addSingle[1]); continue; }
109
+
110
+ // Handle "-N" as single token
111
+ const delSingle = t.match(/^-(\d+)$/);
112
+ if (delSingle) { deletions = parseInt(delSingle[1]); continue; }
113
+
114
+ // Handle "+" and number as separate tokens
115
+ if (t === '+') { plusSeen = true; continue; }
116
+ if (t === '-') { minusSeen = true; continue; }
117
+
118
+ // Number after +/-
119
+ if (plusSeen && t.match(/^\d+$/)) {
120
+ additions = parseInt(t);
121
+ plusSeen = false;
122
+ continue;
123
+ }
124
+ if (minusSeen && t.match(/^\d+$/)) {
125
+ deletions = parseInt(t);
126
+ minusSeen = false;
127
+ continue;
128
+ }
129
+
130
+ // File name (has extension)
131
+ if (!filename && t.match(/\.\w{1,5}$/) && t.length < 100) {
132
+ filename = t;
133
+ continue;
134
+ }
135
+
136
+ // File path (contains /)
137
+ if (filename && !filepath && t.includes('/')) {
138
+ filepath = t;
139
+ }
140
+ }
141
+
142
+ if (filename) {
143
+ changes.push({ filename, filepath: filepath || filename, additions, deletions });
144
+ }
145
+ }
146
+ }
147
+
148
+ // Get total count from header
149
+ const headerSpan = section.querySelector('span');
150
+ const headerText = headerSpan ? (headerSpan.textContent || '').trim() : '';
151
+ const totalMatch = headerText.match(/^(\d+)/);
152
+ const totalCount = totalMatch ? parseInt(totalMatch[1]) : changes.length;
153
+
154
+ // Close the section if we opened it
155
+ if (didOpen) {
156
+ const btn = panel.querySelector('[data-tooltip-id="tooltip-changesOverview"]') as HTMLElement;
157
+ if (btn) btn.click();
158
+ }
159
+
160
+ return { changes, totalCount };
161
+ });
162
+
163
+ if (result.changes.length > 0) {
164
+ console.log(`[INFO] [IdeChanges] Scraped ${result.changes.length}/${result.totalCount} file changes`);
165
+ }
166
+
167
+ return result;
168
+ } catch (err: any) {
169
+ console.error(`[ERROR] [IdeChanges] ${err.message}`);
170
+ return { changes: [], totalCount: 0 };
171
+ }
172
+ }
@@ -132,6 +132,17 @@ export interface ArtifactFile {
132
132
  name: string;
133
133
  size: number;
134
134
  mtime: string;
135
+ /** Whether this is a file with an extension (can be opened/viewed) or a named IDE artifact */
136
+ isFile?: boolean;
137
+ /** Where this artifact was detected from */
138
+ source?: 'ide' | 'brain' | 'none';
139
+ }
140
+
141
+ export interface ChangeFile {
142
+ filename: string;
143
+ filepath: string;
144
+ additions: number;
145
+ deletions: number;
135
146
  }
136
147
 
137
148
  export interface ChatMessage {
@@ -1,12 +1,12 @@
1
1
  {
2
2
  "name": "antigravity-mobile-proxy",
3
- "version": "0.1.6",
3
+ "version": "0.1.7",
4
4
  "lockfileVersion": 3,
5
5
  "requires": true,
6
6
  "packages": {
7
7
  "": {
8
8
  "name": "antigravity-mobile-proxy",
9
- "version": "0.1.6",
9
+ "version": "0.1.7",
10
10
  "hasInstallScript": true,
11
11
  "license": "MIT",
12
12
  "dependencies": {
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "antigravity-mobile-proxy",
3
- "version": "0.1.6",
3
+ "version": "0.1.7",
4
4
  "description": "A web chat proxy for Antigravity IDE with ngrok OAuth tunnel support",
5
5
  "keywords": [
6
6
  "antigravity",
@@ -22,7 +22,7 @@
22
22
  ],
23
23
  "scripts": {
24
24
  "dev": "next dev -p 5555",
25
- "build": "next build",
25
+ "build": "NODE_ENV=production next build",
26
26
  "postbuild": "node scripts/patch-next.js",
27
27
  "start": "next start -p 5555",
28
28
  "tunnel": "node bin/cli.js",
@@ -0,0 +1,126 @@
1
+ /**
2
+ * Diagnostic: Check the send/stop button state in both idle and running states.
3
+ * Run: node scripts/check-send-button.js
4
+ */
5
+ const puppeteer = require('puppeteer-core');
6
+ const CDP_PORT = process.env.CDP_PORT || '9223';
7
+
8
+ async function run() {
9
+ const browser = await puppeteer.connect({ browserURL: `http://127.0.0.1:${CDP_PORT}` });
10
+ const pages = await browser.pages();
11
+ const page = pages.find(p => p.url().includes('workbench.html'));
12
+
13
+ if (!page) {
14
+ console.log('No workbench page found.');
15
+ process.exit(1);
16
+ }
17
+
18
+ const result = await page.evaluate(() => {
19
+ const panel = document.querySelector('.antigravity-agent-side-panel');
20
+ if (!panel) return { error: 'No panel' };
21
+
22
+ const inputArea = document.querySelector('#antigravity\\.agentSidePanelInputBox') ||
23
+ panel.querySelector('[id*="InputBox"]');
24
+ if (!inputArea) return { error: 'No input box' };
25
+
26
+ const wrapper = inputArea.closest('.flex') || inputArea.parentElement?.parentElement || inputArea.parentElement;
27
+ if (!wrapper) return { error: 'No wrapper' };
28
+
29
+ const inputBtns = wrapper.querySelectorAll('button');
30
+ const buttons = [];
31
+
32
+ for (const btn of inputBtns) {
33
+ const html = btn.innerHTML || '';
34
+ const ariaLabel = (btn.getAttribute('aria-label') || '');
35
+ const text = (btn.textContent || '').trim();
36
+ const tooltipId = (btn.getAttribute('data-tooltip-id') || '');
37
+ const cls = (btn.className || '');
38
+
39
+ // Check what icons are present
40
+ const svgClasses = [...btn.querySelectorAll('svg')].map(s => s.getAttribute('class') || '');
41
+
42
+ buttons.push({
43
+ text,
44
+ ariaLabel,
45
+ tooltipId,
46
+ cls: cls.substring(0, 200),
47
+ svgClasses,
48
+ htmlSnippet: html.substring(0, 300),
49
+ });
50
+ }
51
+
52
+ // Also check the spinner state
53
+ const spinners = panel.querySelectorAll('.animate-spin');
54
+ let visibleSpinners = 0;
55
+ for (const s of spinners) {
56
+ let el = s, hidden = false;
57
+ while (el) {
58
+ const c = (el.getAttribute && el.getAttribute('class')) || '';
59
+ if (c.includes('invisible') || c.includes('opacity-0')) { hidden = true; break; }
60
+ el = el.parentElement;
61
+ }
62
+ if (!hidden) visibleSpinners++;
63
+ }
64
+
65
+ return {
66
+ totalButtons: inputBtns.length,
67
+ buttons,
68
+ visibleSpinners,
69
+ wrapperTag: wrapper.tagName,
70
+ wrapperCls: (wrapper.className || '').substring(0, 200),
71
+ };
72
+ });
73
+
74
+ console.log(JSON.stringify(result, null, 2));
75
+
76
+ // Analyze
77
+ if (result.buttons) {
78
+ console.log('\n--- ANALYSIS ---');
79
+ let hasStop = false, hasSend = false;
80
+ for (const btn of result.buttons) {
81
+ const tooltipLower = btn.tooltipId.toLowerCase();
82
+ const ariaLower = btn.ariaLabel.toLowerCase();
83
+ const textLower = btn.text.toLowerCase();
84
+ const html = btn.htmlSnippet.toLowerCase();
85
+
86
+ const isStopIcon = html.match(/lucide-square(?:[^a-z0-9-]|$)/i) ||
87
+ html.includes('lucide-circle-stop') ||
88
+ html.includes('lucide-octagon');
89
+
90
+ const isStop = isStopIcon ||
91
+ ariaLower.includes('stop') ||
92
+ ariaLower.includes('cancel') ||
93
+ textLower === 'stop' ||
94
+ tooltipLower.includes('stop');
95
+
96
+ const isSend = html.includes('lucide-send') ||
97
+ html.includes('lucide-arrow-up') ||
98
+ html.includes('lucide-arrow-right') ||
99
+ html.includes('codicon-send') ||
100
+ html.includes('lucide-corner-down-left') ||
101
+ ariaLower.includes('send') ||
102
+ ariaLower.includes('submit') ||
103
+ textLower === 'send' ||
104
+ tooltipLower.includes('send');
105
+
106
+ if (isStop) hasStop = true;
107
+ if (isSend) hasSend = true;
108
+
109
+ console.log(` Button "${btn.text || btn.ariaLabel || btn.tooltipId}": stop=${isStop}, send=${isSend}`);
110
+ }
111
+
112
+ console.log(`\n hasStop=${hasStop}, hasSend=${hasSend}`);
113
+ if (hasStop) {
114
+ console.log(' => isRunning = TRUE (stop found)');
115
+ } else if (hasSend) {
116
+ console.log(' => isRunning = FALSE (send found, no stop)');
117
+ } else {
118
+ console.log(' => isRunning = TRUE (no send or stop — send button removed from DOM)');
119
+ }
120
+ console.log(` Visible spinners: ${result.visibleSpinners}`);
121
+ }
122
+
123
+ await browser.disconnect();
124
+ }
125
+
126
+ run().catch(e => { console.error('Error:', e.message); process.exit(1); });
@@ -0,0 +1,65 @@
1
+ /**
2
+ * Find exactly where the send button lives relative to the input box.
3
+ */
4
+ const puppeteer = require('puppeteer-core');
5
+
6
+ async function run() {
7
+ const browser = await puppeteer.connect({ browserURL: 'http://127.0.0.1:9223' });
8
+ const pages = await browser.pages();
9
+ const page = pages.find(p => p.url().includes('workbench.html'));
10
+ if (!page) { console.log('No workbench'); process.exit(1); }
11
+
12
+ const result = await page.evaluate(() => {
13
+ const panel = document.querySelector('.antigravity-agent-side-panel');
14
+ if (!panel) return { error: 'No panel' };
15
+
16
+ // Find the send/cancel button by tooltip
17
+ const sendBtn = panel.querySelector('[data-tooltip-id*="send"]') ||
18
+ panel.querySelector('[data-tooltip-id*="cancel"]');
19
+
20
+ // Find all buttons with send-like tooltips
21
+ const sendBtns = [...panel.querySelectorAll('[data-tooltip-id]')]
22
+ .filter(el => {
23
+ const tip = el.getAttribute('data-tooltip-id') || '';
24
+ return tip.includes('send') || tip.includes('cancel');
25
+ })
26
+ .map(el => ({
27
+ tag: el.tagName,
28
+ tooltipId: el.getAttribute('data-tooltip-id'),
29
+ innerHTML: el.innerHTML?.substring(0, 300) || '',
30
+ cls: (el.className || '').substring(0, 200),
31
+ }));
32
+
33
+ // The input box
34
+ const inputBox = document.querySelector('#antigravity\\.agentSidePanelInputBox');
35
+ if (!inputBox) return { error: 'No input box', sendBtns };
36
+
37
+ // Current wrapper that `.closest('.flex')` finds
38
+ const currentWrapper = inputBox.closest('.flex');
39
+
40
+ // Walk up and list all ancestors with their button counts
41
+ const ancestors = [];
42
+ let el = inputBox;
43
+ for (let i = 0; i < 10 && el; i++) {
44
+ const btns = el.querySelectorAll('button');
45
+ const sendInside = el.querySelector('[data-tooltip-id*="send"]');
46
+ ancestors.push({
47
+ level: i,
48
+ tag: el.tagName,
49
+ id: el.id || '',
50
+ cls: (el.className || '').substring(0, 150),
51
+ buttonCount: btns.length,
52
+ hasSendBtn: !!sendInside,
53
+ isCurrentWrapper: el === currentWrapper,
54
+ });
55
+ el = el.parentElement;
56
+ }
57
+
58
+ return { sendBtns, ancestors };
59
+ });
60
+
61
+ console.log(JSON.stringify(result, null, 2));
62
+ await browser.disconnect();
63
+ }
64
+
65
+ run().catch(e => { console.error(e.message); process.exit(1); });