@loompad/cli 0.1.0
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/ARCHITECTURE.md +211 -0
- package/CHANGELOG.md +470 -0
- package/LICENSE +21 -0
- package/README.md +630 -0
- package/dist/adapters/base.d.ts +71 -0
- package/dist/adapters/base.js +187 -0
- package/dist/adapters/base.js.map +1 -0
- package/dist/adapters/bridges/antigravity.d.ts +53 -0
- package/dist/adapters/bridges/antigravity.js +106 -0
- package/dist/adapters/bridges/antigravity.js.map +1 -0
- package/dist/adapters/bridges/cdp.d.ts +82 -0
- package/dist/adapters/bridges/cdp.js +222 -0
- package/dist/adapters/bridges/cdp.js.map +1 -0
- package/dist/adapters/bridges/gui-chat.d.ts +113 -0
- package/dist/adapters/bridges/gui-chat.js +498 -0
- package/dist/adapters/bridges/gui-chat.js.map +1 -0
- package/dist/adapters/bridges/kiro.d.ts +19 -0
- package/dist/adapters/bridges/kiro.js +22 -0
- package/dist/adapters/bridges/kiro.js.map +1 -0
- package/dist/adapters/bridges/profiles.d.ts +82 -0
- package/dist/adapters/bridges/profiles.js +125 -0
- package/dist/adapters/bridges/profiles.js.map +1 -0
- package/dist/adapters/claude-code.d.ts +26 -0
- package/dist/adapters/claude-code.js +209 -0
- package/dist/adapters/claude-code.js.map +1 -0
- package/dist/adapters/codex.d.ts +51 -0
- package/dist/adapters/codex.js +291 -0
- package/dist/adapters/codex.js.map +1 -0
- package/dist/adapters/echo.d.ts +20 -0
- package/dist/adapters/echo.js +79 -0
- package/dist/adapters/echo.js.map +1 -0
- package/dist/adapters/grok.d.ts +61 -0
- package/dist/adapters/grok.js +217 -0
- package/dist/adapters/grok.js.map +1 -0
- package/dist/adapters/index.d.ts +9 -0
- package/dist/adapters/index.js +33 -0
- package/dist/adapters/index.js.map +1 -0
- package/dist/adapters/opencode.d.ts +64 -0
- package/dist/adapters/opencode.js +426 -0
- package/dist/adapters/opencode.js.map +1 -0
- package/dist/cli/common.d.ts +11 -0
- package/dist/cli/common.js +40 -0
- package/dist/cli/common.js.map +1 -0
- package/dist/cli/doctor.d.ts +12 -0
- package/dist/cli/doctor.js +181 -0
- package/dist/cli/doctor.js.map +1 -0
- package/dist/cli/index.d.ts +8 -0
- package/dist/cli/index.js +641 -0
- package/dist/cli/index.js.map +1 -0
- package/dist/cli/tui-model.d.ts +73 -0
- package/dist/cli/tui-model.js +312 -0
- package/dist/cli/tui-model.js.map +1 -0
- package/dist/cli/tui.d.ts +15 -0
- package/dist/cli/tui.js +451 -0
- package/dist/cli/tui.js.map +1 -0
- package/dist/cli/ui.d.ts +10 -0
- package/dist/cli/ui.js +135 -0
- package/dist/cli/ui.js.map +1 -0
- package/dist/core/ades.d.ts +79 -0
- package/dist/core/ades.js +130 -0
- package/dist/core/ades.js.map +1 -0
- package/dist/core/baton.d.ts +37 -0
- package/dist/core/baton.js +92 -0
- package/dist/core/baton.js.map +1 -0
- package/dist/core/brain-extract.d.ts +111 -0
- package/dist/core/brain-extract.js +248 -0
- package/dist/core/brain-extract.js.map +1 -0
- package/dist/core/brain-index.d.ts +128 -0
- package/dist/core/brain-index.js +338 -0
- package/dist/core/brain-index.js.map +1 -0
- package/dist/core/brain.d.ts +214 -0
- package/dist/core/brain.js +425 -0
- package/dist/core/brain.js.map +1 -0
- package/dist/core/claude-cli.d.ts +12 -0
- package/dist/core/claude-cli.js +47 -0
- package/dist/core/claude-cli.js.map +1 -0
- package/dist/core/distill.d.ts +19 -0
- package/dist/core/distill.js +88 -0
- package/dist/core/distill.js.map +1 -0
- package/dist/core/eventlog.d.ts +43 -0
- package/dist/core/eventlog.js +211 -0
- package/dist/core/eventlog.js.map +1 -0
- package/dist/core/git.d.ts +203 -0
- package/dist/core/git.js +460 -0
- package/dist/core/git.js.map +1 -0
- package/dist/core/logbook.d.ts +59 -0
- package/dist/core/logbook.js +118 -0
- package/dist/core/logbook.js.map +1 -0
- package/dist/core/memory.d.ts +34 -0
- package/dist/core/memory.js +124 -0
- package/dist/core/memory.js.map +1 -0
- package/dist/core/notify.d.ts +11 -0
- package/dist/core/notify.js +30 -0
- package/dist/core/notify.js.map +1 -0
- package/dist/core/projection.d.ts +20 -0
- package/dist/core/projection.js +96 -0
- package/dist/core/projection.js.map +1 -0
- package/dist/core/registry.d.ts +76 -0
- package/dist/core/registry.js +147 -0
- package/dist/core/registry.js.map +1 -0
- package/dist/core/router.d.ts +42 -0
- package/dist/core/router.js +99 -0
- package/dist/core/router.js.map +1 -0
- package/dist/core/routes.d.ts +93 -0
- package/dist/core/routes.js +399 -0
- package/dist/core/routes.js.map +1 -0
- package/dist/core/search.d.ts +63 -0
- package/dist/core/search.js +0 -0
- package/dist/core/search.js.map +1 -0
- package/dist/core/setup.d.ts +70 -0
- package/dist/core/setup.js +250 -0
- package/dist/core/setup.js.map +1 -0
- package/dist/core/suggestions.d.ts +15 -0
- package/dist/core/suggestions.js +38 -0
- package/dist/core/suggestions.js.map +1 -0
- package/dist/core/worktree.d.ts +32 -0
- package/dist/core/worktree.js +83 -0
- package/dist/core/worktree.js.map +1 -0
- package/dist/daemon/app-page.d.ts +32 -0
- package/dist/daemon/app-page.js +6363 -0
- package/dist/daemon/app-page.js.map +1 -0
- package/dist/daemon/auth.d.ts +51 -0
- package/dist/daemon/auth.js +135 -0
- package/dist/daemon/auth.js.map +1 -0
- package/dist/daemon/board.d.ts +117 -0
- package/dist/daemon/board.js +202 -0
- package/dist/daemon/board.js.map +1 -0
- package/dist/daemon/brand-icons.d.ts +29 -0
- package/dist/daemon/brand-icons.js +44 -0
- package/dist/daemon/brand-icons.js.map +1 -0
- package/dist/daemon/client.d.ts +127 -0
- package/dist/daemon/client.js +237 -0
- package/dist/daemon/client.js.map +1 -0
- package/dist/daemon/geist-font.d.ts +10 -0
- package/dist/daemon/geist-font.js +783 -0
- package/dist/daemon/geist-font.js.map +1 -0
- package/dist/daemon/guards.d.ts +18 -0
- package/dist/daemon/guards.js +40 -0
- package/dist/daemon/guards.js.map +1 -0
- package/dist/daemon/linear.d.ts +59 -0
- package/dist/daemon/linear.js +117 -0
- package/dist/daemon/linear.js.map +1 -0
- package/dist/daemon/push.d.ts +22 -0
- package/dist/daemon/push.js +58 -0
- package/dist/daemon/push.js.map +1 -0
- package/dist/daemon/runtime.d.ts +299 -0
- package/dist/daemon/runtime.js +1016 -0
- package/dist/daemon/runtime.js.map +1 -0
- package/dist/daemon/server.d.ts +125 -0
- package/dist/daemon/server.js +1728 -0
- package/dist/daemon/server.js.map +1 -0
- package/dist/daemon/tasks.d.ts +153 -0
- package/dist/daemon/tasks.js +264 -0
- package/dist/daemon/tasks.js.map +1 -0
- package/dist/daemon/terminals.d.ts +82 -0
- package/dist/daemon/terminals.js +372 -0
- package/dist/daemon/terminals.js.map +1 -0
- package/dist/index.d.ts +20 -0
- package/dist/index.js +20 -0
- package/dist/index.js.map +1 -0
- package/dist/sdk.d.ts +17 -0
- package/dist/sdk.js +17 -0
- package/dist/sdk.js.map +1 -0
- package/dist/types.d.ts +236 -0
- package/dist/types.js +18 -0
- package/dist/types.js.map +1 -0
- package/docs/SETUP.md +265 -0
- package/docs/adapters.md +132 -0
- package/docs/design-system.md +245 -0
- package/docs/img/board-linear.png +0 -0
- package/docs/img/board-projects.png +0 -0
- package/docs/img/brain.png +0 -0
- package/docs/img/settings.png +0 -0
- package/docs/img/workspace.png +0 -0
- package/docs/integration-notes.md +76 -0
- package/docs/proposals/brain.md +262 -0
- package/package.json +84 -0
|
@@ -0,0 +1,498 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Driving a GUI agent's chat box over CDP.
|
|
3
|
+
*
|
|
4
|
+
* Shared by the Antigravity and Kiro bridges, because the problem is identical:
|
|
5
|
+
* an Electron app with a chat panel, no API, and a DOM we can reach.
|
|
6
|
+
*
|
|
7
|
+
* The approach — and most of the hard-won detail — comes from two projects that
|
|
8
|
+
* do this for real every day: krishnakanthb13/antigravity_phone_chat for the
|
|
9
|
+
* shape of the chat, yazanbaker94/AntiGravity-AutoAccept for how to attach.
|
|
10
|
+
* Both MIT. Connect over CDP, find the composer inside the chat panel, type
|
|
11
|
+
* through the browser's own input pipeline, click the app's own submit button.
|
|
12
|
+
* Never touch the provider APIs, never lift a token: drive the app that's
|
|
13
|
+
* already signed in, the way a person would.
|
|
14
|
+
*
|
|
15
|
+
* ## Why Input.insertText and not `el.textContent = …`
|
|
16
|
+
*
|
|
17
|
+
* Antigravity's composer is a Lexical editor; Kiro's is React. Both keep their
|
|
18
|
+
* own model of the text and only update it when the browser tells them about an
|
|
19
|
+
* edit. Assigning to the DOM changes what you see and nothing else — the app
|
|
20
|
+
* still believes the box is empty and submits nothing.
|
|
21
|
+
*
|
|
22
|
+
* CDP's `Input.insertText` is what's used, and the choice was measured rather
|
|
23
|
+
* than assumed. In a real Chromium:
|
|
24
|
+
*
|
|
25
|
+
* document.execCommand("insertText") → fires `input` only
|
|
26
|
+
* Input.insertText (CDP) → fires `beforeinput` AND `input`
|
|
27
|
+
*
|
|
28
|
+
* `beforeinput` is the one an editor builds its model from, so execCommand is
|
|
29
|
+
* the worse tool despite being the obvious one — it fills the box while leaving
|
|
30
|
+
* the app convinced it's empty, which then looks like a send bug. It survives
|
|
31
|
+
* only as a fallback, after synthesising the events by hand.
|
|
32
|
+
*
|
|
33
|
+
* ## Why it refuses
|
|
34
|
+
*
|
|
35
|
+
* Both apps are VS Code family, and Monaco — the editor holding YOUR SOURCE
|
|
36
|
+
* FILE — is a `contenteditable`. A driver that hunts the document for something
|
|
37
|
+
* editable will eventually find that one, type a prompt into your code and press
|
|
38
|
+
* Enter. No error message repairs that afterwards.
|
|
39
|
+
*
|
|
40
|
+
* So the composer is only ever looked for *inside* the chat panel
|
|
41
|
+
* (profiles.ts), never in the document at large; anything under `.monaco-editor`
|
|
42
|
+
* is excluded even then; and no match, or an ambiguous one, is a refusal that
|
|
43
|
+
* names the fix. Not sending is a bad outcome. Sending into the wrong box is a
|
|
44
|
+
* worse one.
|
|
45
|
+
*/
|
|
46
|
+
import { CdpSession, cdpTargets, cdpUp, chatTargets } from "./cdp.js";
|
|
47
|
+
import { CHATTY, FORBIDDEN_ANCESTORS, GENERIC, profileFor } from "./profiles.js";
|
|
48
|
+
const MARK = "data-loom-composer";
|
|
49
|
+
/**
|
|
50
|
+
* Every document this target can reach: its own, plus any same-origin child
|
|
51
|
+
* frame's.
|
|
52
|
+
*
|
|
53
|
+
* Kiro is why. Its chat panel is a `vscode-webview` target whose top document
|
|
54
|
+
* holds eight nodes and an iframe — VS Code nests the extension's real HTML one
|
|
55
|
+
* frame deeper, and that inner frame is not a CDP target of its own. Searching
|
|
56
|
+
* only the top document found nothing and reported "the chat panel has no input
|
|
57
|
+
* in it", which was true of the document I was looking at and false about Kiro.
|
|
58
|
+
*
|
|
59
|
+
* Cross-origin frames throw on contentDocument; they're skipped rather than
|
|
60
|
+
* fataled, because one unreachable ad frame shouldn't hide a chat box.
|
|
61
|
+
*/
|
|
62
|
+
const DOCS = `
|
|
63
|
+
const docs = [document];
|
|
64
|
+
for (const f of document.querySelectorAll("iframe, frame")) {
|
|
65
|
+
try { if (f.contentDocument) docs.push(f.contentDocument); } catch (e) { /* cross-origin */ }
|
|
66
|
+
}
|
|
67
|
+
const findIn = (sel) => {
|
|
68
|
+
for (const d of docs) {
|
|
69
|
+
let el = null;
|
|
70
|
+
try { el = d.querySelector(sel); } catch (e) {}
|
|
71
|
+
if (el) return el;
|
|
72
|
+
}
|
|
73
|
+
return null;
|
|
74
|
+
};
|
|
75
|
+
const allIn = (sel) => {
|
|
76
|
+
const out = [];
|
|
77
|
+
for (const d of docs) {
|
|
78
|
+
try { out.push(...d.querySelectorAll(sel)); } catch (e) {}
|
|
79
|
+
}
|
|
80
|
+
return out;
|
|
81
|
+
};
|
|
82
|
+
`;
|
|
83
|
+
/**
|
|
84
|
+
* Find the composer and stamp it.
|
|
85
|
+
*
|
|
86
|
+
* Returns a marker attribute rather than an element handle: the page is a live
|
|
87
|
+
* React/Lexical tree that re-renders between calls, so a handle goes stale while
|
|
88
|
+
* an attribute we wrote survives.
|
|
89
|
+
*/
|
|
90
|
+
function discoverExpr(p, explicit) {
|
|
91
|
+
return `(() => {
|
|
92
|
+
${DOCS}
|
|
93
|
+
const MARK = ${JSON.stringify(MARK)};
|
|
94
|
+
const forbidden = ${JSON.stringify(FORBIDDEN_ANCESTORS)};
|
|
95
|
+
const vis = (el) => !!(el.offsetParent || el.getClientRects().length);
|
|
96
|
+
const banned = (el) => forbidden.some((sel) => el.closest(sel));
|
|
97
|
+
allIn('[' + MARK + ']').forEach((el) => el.removeAttribute(MARK));
|
|
98
|
+
|
|
99
|
+
const explicit = ${explicit ? JSON.stringify(explicit) : "null"};
|
|
100
|
+
if (explicit) {
|
|
101
|
+
const el = findIn(explicit);
|
|
102
|
+
if (!el) return { ok: false, reason: "nothing matches the selector you set: " + explicit };
|
|
103
|
+
if (banned(el)) return { ok: false, reason: "that selector points inside the code editor" };
|
|
104
|
+
el.setAttribute(MARK, "1");
|
|
105
|
+
return { ok: true, how: "your selector" };
|
|
106
|
+
}
|
|
107
|
+
|
|
108
|
+
// The composer is only ever looked for inside the chat panel. This is what
|
|
109
|
+
// keeps the source editor out of reach — it isn't in here.
|
|
110
|
+
const roots = ${JSON.stringify(p.chatRoots)};
|
|
111
|
+
let root = null;
|
|
112
|
+
for (const sel of roots) {
|
|
113
|
+
root = findIn(sel);
|
|
114
|
+
if (root) break;
|
|
115
|
+
}
|
|
116
|
+
// No named panel anywhere? A webview whose whole document IS the chat has
|
|
117
|
+
// nothing to scope to — fall back to its body, which is still not the
|
|
118
|
+
// workbench and so still can't reach Monaco.
|
|
119
|
+
if (!root && docs.length > 1) root = docs[docs.length - 1].body;
|
|
120
|
+
if (!root) {
|
|
121
|
+
return { ok: false, reason: ${JSON.stringify(`no chat panel in ${p.name} — sign in, and open a chat`)} };
|
|
122
|
+
}
|
|
123
|
+
|
|
124
|
+
${p.signedOut ? `
|
|
125
|
+
// A signed-out app is the cruellest failure here: the chat renders, the box
|
|
126
|
+
// takes text, and the send button is disabled forever. Say so plainly.
|
|
127
|
+
const panel = (root.innerText || "").replace(/\\s+/g, " ");
|
|
128
|
+
if (new RegExp(${JSON.stringify(p.signedOut.source)}, "i").test(panel)) {
|
|
129
|
+
return { ok: false, reason: ${JSON.stringify(`${p.name} is signed out — log in from its window, then try again`)} };
|
|
130
|
+
}` : ""}
|
|
131
|
+
|
|
132
|
+
const found = [...root.querySelectorAll(${JSON.stringify(p.composer)})].filter((el) => vis(el) && !banned(el));
|
|
133
|
+
if (!found.length) {
|
|
134
|
+
return { ok: false, reason: "the chat panel has no input in it — is a conversation open?" };
|
|
135
|
+
}
|
|
136
|
+
|
|
137
|
+
// Last visible wins: when a page has several, the live composer is the one at
|
|
138
|
+
// the bottom of the panel. (antigravity_phone_chat takes .at(-1) too.)
|
|
139
|
+
let pick = found.at(-1);
|
|
140
|
+
|
|
141
|
+
// Generic profile only: with no real chat panel to scope to, the box has to
|
|
142
|
+
// look like a chat box before we'll type in it.
|
|
143
|
+
const generic = ${JSON.stringify(p.chatRoots)}.length === 1 && ${JSON.stringify(p.chatRoots[0])} === "body";
|
|
144
|
+
if (generic) {
|
|
145
|
+
const chatty = new RegExp(${JSON.stringify(CHATTY.source)}, "i");
|
|
146
|
+
const labelled = found.filter((el) => chatty.test([
|
|
147
|
+
el.getAttribute("placeholder"),
|
|
148
|
+
el.getAttribute("data-placeholder"),
|
|
149
|
+
el.getAttribute("aria-label"),
|
|
150
|
+
el.getAttribute("title"),
|
|
151
|
+
].filter(Boolean).join(" ")));
|
|
152
|
+
if (labelled.length !== 1) {
|
|
153
|
+
return {
|
|
154
|
+
ok: false,
|
|
155
|
+
reason: labelled.length
|
|
156
|
+
? labelled.length + " things look like a chat box; name one with options.selectors.composer"
|
|
157
|
+
: found.length + " editable areas, none labelled like a chat box; name one with options.selectors.composer",
|
|
158
|
+
};
|
|
159
|
+
}
|
|
160
|
+
pick = labelled[0];
|
|
161
|
+
}
|
|
162
|
+
|
|
163
|
+
pick.setAttribute(MARK, "1");
|
|
164
|
+
return { ok: true, how: "profile" };
|
|
165
|
+
})()`;
|
|
166
|
+
}
|
|
167
|
+
/**
|
|
168
|
+
* Focus the composer and select whatever is in it, so the next keystroke
|
|
169
|
+
* replaces it.
|
|
170
|
+
*
|
|
171
|
+
* Selecting the node's *contents* rather than calling execCommand("selectAll")
|
|
172
|
+
* is deliberate: selectAll acts on whatever the document considers selected,
|
|
173
|
+
* and if that isn't scoped to the box it's scoped to the page. A range over the
|
|
174
|
+
* editor's contents can only ever affect the editor. It doubles as the clear —
|
|
175
|
+
* inserting replaces a selection, so there's nothing to delete first.
|
|
176
|
+
*/
|
|
177
|
+
function focusExpr(p) {
|
|
178
|
+
return `(() => {
|
|
179
|
+
${DOCS}
|
|
180
|
+
const MARK = ${JSON.stringify(MARK)};
|
|
181
|
+
const vis = (el) => !!(el && (el.offsetParent || el.getClientRects().length));
|
|
182
|
+
|
|
183
|
+
${p.busy ? `const busy = findIn(${JSON.stringify(p.busy)});
|
|
184
|
+
if (vis(busy)) return { ok: false, reason: "busy" };` : ""}
|
|
185
|
+
|
|
186
|
+
const editor = findIn('[' + MARK + ']');
|
|
187
|
+
if (!editor) return { ok: false, reason: "the composer went away" };
|
|
188
|
+
|
|
189
|
+
editor.scrollIntoView({ block: "center" });
|
|
190
|
+
editor.focus();
|
|
191
|
+
try {
|
|
192
|
+
const range = document.createRange();
|
|
193
|
+
range.selectNodeContents(editor);
|
|
194
|
+
const sel = window.getSelection();
|
|
195
|
+
sel.removeAllRanges();
|
|
196
|
+
sel.addRange(range);
|
|
197
|
+
} catch {}
|
|
198
|
+
|
|
199
|
+
// Hand back where it is, so a caller that finds focus didn't take can click
|
|
200
|
+
// it like a person would.
|
|
201
|
+
const r = editor.getBoundingClientRect();
|
|
202
|
+
return {
|
|
203
|
+
ok: true,
|
|
204
|
+
focused: document.activeElement === editor || editor.contains(document.activeElement),
|
|
205
|
+
x: Math.round(r.left + r.width / 2),
|
|
206
|
+
y: Math.round(r.top + r.height / 2),
|
|
207
|
+
};
|
|
208
|
+
})()`;
|
|
209
|
+
}
|
|
210
|
+
/**
|
|
211
|
+
* Submit, having typed.
|
|
212
|
+
*
|
|
213
|
+
* Also the fallback path: if CDP's typing didn't land (the box is still empty),
|
|
214
|
+
* try the page's own editing command, and failing that assign and synthesise
|
|
215
|
+
* the events by hand. Each rung down is less like real typing and less likely
|
|
216
|
+
* to convince a framework, which is why they're in this order.
|
|
217
|
+
*/
|
|
218
|
+
function submitExpr(p, text, sendSel) {
|
|
219
|
+
// JSON.stringify handles quotes, newlines, backslashes and unicode. This is a
|
|
220
|
+
// string being pasted into a program being pasted into a browser; nothing else
|
|
221
|
+
// is safe enough.
|
|
222
|
+
const safe = JSON.stringify(text);
|
|
223
|
+
// Deliberately NOT async, and with no requestAnimationFrame in it.
|
|
224
|
+
//
|
|
225
|
+
// This used to await two rAFs to let the framework commit the edit before
|
|
226
|
+
// clicking submit. rAF does not fire in a window that is occluded or
|
|
227
|
+
// backgrounded — so against a real Antigravity sitting behind other windows
|
|
228
|
+
// the promise never settled and Runtime.evaluate hung until it timed out.
|
|
229
|
+
// Which is the whole use case: the app is in the background and you're on
|
|
230
|
+
// your phone. The settle wait happens on the Node side now, where a timer is
|
|
231
|
+
// a timer.
|
|
232
|
+
return `(() => {
|
|
233
|
+
${DOCS}
|
|
234
|
+
const MARK = ${JSON.stringify(MARK)};
|
|
235
|
+
const editor = findIn('[' + MARK + ']');
|
|
236
|
+
if (!editor) return { ok: false, reason: "the composer went away" };
|
|
237
|
+
const text = ${safe};
|
|
238
|
+
|
|
239
|
+
// If the typing didn't land, synthesise the same events by hand — including
|
|
240
|
+
// beforeinput, which is the one that matters. execCommand("insertText") is
|
|
241
|
+
// deliberately NOT the fallback: measured here, it fires only \`input\`, so it
|
|
242
|
+
// fills the box while leaving a beforeinput-driven editor (Lexical, and this
|
|
243
|
+
// is what Antigravity uses) still believing it's empty. That's worse than
|
|
244
|
+
// failing, because the box then LOOKS full and every check downstream passes
|
|
245
|
+
// while the send button quietly does nothing.
|
|
246
|
+
let how = "typed";
|
|
247
|
+
if (!(editor.innerText || editor.value || "").trim()) {
|
|
248
|
+
how = "synthesised";
|
|
249
|
+
if ("value" in editor) editor.value = text; else editor.textContent = text;
|
|
250
|
+
editor.dispatchEvent(new InputEvent("beforeinput", { bubbles: true, inputType: "insertText", data: text }));
|
|
251
|
+
editor.dispatchEvent(new InputEvent("input", { bubbles: true, inputType: "insertText", data: text }));
|
|
252
|
+
}
|
|
253
|
+
|
|
254
|
+
const explicit = ${sendSel ? JSON.stringify(sendSel) : "null"};
|
|
255
|
+
const candidates = explicit ? [explicit] : ${JSON.stringify(p.send)};
|
|
256
|
+
let sawDisabled = false;
|
|
257
|
+
for (const sel of candidates) {
|
|
258
|
+
const el = findIn(sel);
|
|
259
|
+
if (!el) continue;
|
|
260
|
+
const btn = el.closest("button") || (el.tagName === "BUTTON" ? el : null) || el;
|
|
261
|
+
if (btn.disabled) { sawDisabled = true; continue; }
|
|
262
|
+
btn.click();
|
|
263
|
+
return { ok: true, how: how + " → clicked " + sel };
|
|
264
|
+
}
|
|
265
|
+
|
|
266
|
+
// The app's own send button, disabled with text in the box, is the app
|
|
267
|
+
// telling us it won't send — a dead account, a missing model, a turn already
|
|
268
|
+
// running. Pressing Enter at that point pretends we didn't hear.
|
|
269
|
+
if (sawDisabled) {
|
|
270
|
+
return { ok: false, reason: "the send button is disabled — is it signed in, with a model selected?" };
|
|
271
|
+
}
|
|
272
|
+
|
|
273
|
+
// Most of them submit on Enter anyway.
|
|
274
|
+
editor.dispatchEvent(new KeyboardEvent("keydown", { bubbles: true, key: "Enter", code: "Enter" }));
|
|
275
|
+
editor.dispatchEvent(new KeyboardEvent("keyup", { bubbles: true, key: "Enter", code: "Enter" }));
|
|
276
|
+
return { ok: true, how: how + " → enter" };
|
|
277
|
+
})()`;
|
|
278
|
+
}
|
|
279
|
+
/**
|
|
280
|
+
* Read the conversation, minus the box you type into.
|
|
281
|
+
*
|
|
282
|
+
* The composer lives *inside* the transcript container in Antigravity
|
|
283
|
+
* (`#conversation` holds both), so a naive innerText includes whatever is
|
|
284
|
+
* currently typed. The reply is computed as a diff of this text, which meant
|
|
285
|
+
* the "reply" was your own prompt sitting in the box — every ask returned what
|
|
286
|
+
* you just said. The composer is cloned out before reading.
|
|
287
|
+
*/
|
|
288
|
+
function transcriptExpr(p, explicit) {
|
|
289
|
+
const roots = explicit ? [explicit] : p.transcript;
|
|
290
|
+
return `(() => {
|
|
291
|
+
${DOCS}
|
|
292
|
+
const roots = ${JSON.stringify(roots)};
|
|
293
|
+
const composerish = ['[data-lexical-editor]', '[contenteditable="true"]', 'textarea', '[data-loom-composer]'];
|
|
294
|
+
for (const sel of roots) {
|
|
295
|
+
const el = findIn(sel);
|
|
296
|
+
if (!el) continue;
|
|
297
|
+
const doc = el.ownerDocument;
|
|
298
|
+
const clone = el.cloneNode(true);
|
|
299
|
+
composerish.forEach((c) => clone.querySelectorAll(c).forEach((n) => n.remove()));
|
|
300
|
+
// innerText needs layout, which a detached node has none of; attach it
|
|
301
|
+
// offscreen for the read rather than settling for textContent, which would
|
|
302
|
+
// glue every word together and make the diff meaningless.
|
|
303
|
+
clone.style.position = 'fixed';
|
|
304
|
+
clone.style.left = '-99999px';
|
|
305
|
+
clone.style.top = '0';
|
|
306
|
+
doc.body.appendChild(clone);
|
|
307
|
+
const text = (clone.innerText || "").slice(-20000);
|
|
308
|
+
clone.remove();
|
|
309
|
+
return text;
|
|
310
|
+
}
|
|
311
|
+
const last = docs[docs.length - 1];
|
|
312
|
+
return (last.body ? last.body.innerText || "" : "").slice(-20000);
|
|
313
|
+
})()`;
|
|
314
|
+
}
|
|
315
|
+
export class GuiChatDriver {
|
|
316
|
+
options;
|
|
317
|
+
profile;
|
|
318
|
+
appName;
|
|
319
|
+
/** The target that had the chat last time; tried first, never trusted. */
|
|
320
|
+
lastTargetId = null;
|
|
321
|
+
constructor(appName, options, kind) {
|
|
322
|
+
this.options = options;
|
|
323
|
+
this.profile = profileFor(kind ?? appName.toLowerCase());
|
|
324
|
+
// The profile knows the app's real name; "Antigravity" is the other program.
|
|
325
|
+
this.appName = this.profile === GENERIC ? appName : this.profile.name;
|
|
326
|
+
}
|
|
327
|
+
get endpoint() {
|
|
328
|
+
const host = this.options.host ?? "127.0.0.1";
|
|
329
|
+
const port = this.options.debugPort ?? this.profile.defaultPort;
|
|
330
|
+
return `http://${host}:${port}`;
|
|
331
|
+
}
|
|
332
|
+
/** How to start this app with a debugger, on this OS. */
|
|
333
|
+
get launchHint() {
|
|
334
|
+
const os = process.platform;
|
|
335
|
+
const how = this.profile.launch[os === "win32" ? "win32" : os === "darwin" ? "darwin" : "linux"];
|
|
336
|
+
return `${this.appName} isn't listening — ${how}`;
|
|
337
|
+
}
|
|
338
|
+
reachable() {
|
|
339
|
+
return cdpUp(this.endpoint);
|
|
340
|
+
}
|
|
341
|
+
/**
|
|
342
|
+
* A session on the target that actually has the chat in it.
|
|
343
|
+
*
|
|
344
|
+
* There is usually more than one candidate and the right one isn't
|
|
345
|
+
* predictable from the app: Antigravity's chat is in its page, Kiro's is in a
|
|
346
|
+
* webview target. So every candidate is asked "is there a composer here?" and
|
|
347
|
+
* the first that says yes wins. The answer is remembered — the targets don't
|
|
348
|
+
* move around between two calls a second apart — but it's re-derived whenever
|
|
349
|
+
* that target has gone.
|
|
350
|
+
*/
|
|
351
|
+
async session() {
|
|
352
|
+
const targets = chatTargets(await cdpTargets(this.endpoint));
|
|
353
|
+
if (!targets.length)
|
|
354
|
+
throw new Error(`${this.appName} is running but has no window to talk to`);
|
|
355
|
+
const ordered = this.lastTargetId
|
|
356
|
+
? [...targets].sort((a, b) => (a.id === this.lastTargetId ? -1 : b.id === this.lastTargetId ? 1 : 0))
|
|
357
|
+
: targets;
|
|
358
|
+
let firstReason;
|
|
359
|
+
for (const target of ordered) {
|
|
360
|
+
let session;
|
|
361
|
+
try {
|
|
362
|
+
session = await CdpSession.open(target, 4000);
|
|
363
|
+
}
|
|
364
|
+
catch {
|
|
365
|
+
continue; // a target that won't attach isn't the one
|
|
366
|
+
}
|
|
367
|
+
try {
|
|
368
|
+
const found = await session.evaluate(discoverExpr(this.profile, this.options.selectors?.composer), 8000);
|
|
369
|
+
if (found.ok) {
|
|
370
|
+
this.lastTargetId = target.id;
|
|
371
|
+
return { session, found };
|
|
372
|
+
}
|
|
373
|
+
firstReason ??= found.reason;
|
|
374
|
+
}
|
|
375
|
+
catch {
|
|
376
|
+
/* this target can't answer; try the next */
|
|
377
|
+
}
|
|
378
|
+
session.close();
|
|
379
|
+
}
|
|
380
|
+
throw new Error(firstReason ?? `no chat panel in ${this.appName}`);
|
|
381
|
+
}
|
|
382
|
+
/**
|
|
383
|
+
* Can we actually drive it right now? Reachable is not enough: a signed-out
|
|
384
|
+
* Antigravity answers CDP happily and has no chat in it at all.
|
|
385
|
+
*/
|
|
386
|
+
async driveable() {
|
|
387
|
+
if (!(await this.reachable()))
|
|
388
|
+
return { ok: false, reason: this.launchHint };
|
|
389
|
+
let session = null;
|
|
390
|
+
try {
|
|
391
|
+
({ session } = await this.session());
|
|
392
|
+
return { ok: true };
|
|
393
|
+
}
|
|
394
|
+
catch (err) {
|
|
395
|
+
return { ok: false, reason: err instanceof Error ? err.message : String(err) };
|
|
396
|
+
}
|
|
397
|
+
finally {
|
|
398
|
+
session?.close();
|
|
399
|
+
}
|
|
400
|
+
}
|
|
401
|
+
/**
|
|
402
|
+
* Type a prompt into the app, submit it, and wait for the panel to stop
|
|
403
|
+
* growing.
|
|
404
|
+
*
|
|
405
|
+
* What comes back is the panel's text, not the model's response object —
|
|
406
|
+
* these apps render their conversation, they don't hand it out. Same trade
|
|
407
|
+
* the phone-chat project makes: mirror what's on screen rather than pretend
|
|
408
|
+
* to have something structured.
|
|
409
|
+
*/
|
|
410
|
+
async ask(text, onProgress) {
|
|
411
|
+
// session() finds the target whose chat exists and marks the composer in it
|
|
412
|
+
const { session } = await this.session();
|
|
413
|
+
try {
|
|
414
|
+
const before = await session.evaluate(transcriptExpr(this.profile, this.options.selectors?.transcript));
|
|
415
|
+
const focused = await session.evaluate(focusExpr(this.profile));
|
|
416
|
+
if (!focused.ok) {
|
|
417
|
+
throw new Error(focused.reason === "busy"
|
|
418
|
+
? `${this.appName} is already working on something — let it finish or stop it there`
|
|
419
|
+
: `can't focus ${this.appName}'s chat box: ${focused.reason}`);
|
|
420
|
+
}
|
|
421
|
+
// focus() is a request, and sometimes the page says no — a re-render, a
|
|
422
|
+
// framework that owns focus. When it does, click the box like a person
|
|
423
|
+
// and re-select. This is what made the second ask in a session flaky:
|
|
424
|
+
// typing went somewhere else, silently.
|
|
425
|
+
if (!focused.focused && focused.x !== undefined && focused.y !== undefined) {
|
|
426
|
+
await session.clickAt(focused.x, focused.y);
|
|
427
|
+
await session.evaluate(focusExpr(this.profile));
|
|
428
|
+
}
|
|
429
|
+
// Type through the browser's own input pipeline. This matters more than
|
|
430
|
+
// it looks: measured in a real Chromium, execCommand("insertText") fires
|
|
431
|
+
// only `input`, while Input.insertText fires `beforeinput` AND `input` —
|
|
432
|
+
// which is what a keyboard does, and what an editor like Lexical builds
|
|
433
|
+
// its model from. Typing with execCommand filled the box visually and
|
|
434
|
+
// left the app believing it was empty, so the send button did nothing.
|
|
435
|
+
await session.insertText(text);
|
|
436
|
+
// Let the framework commit the edit before asking it to submit; without
|
|
437
|
+
// this the send button is still disabled from when the box was empty.
|
|
438
|
+
// The wait lives here rather than inside the page because the page may be
|
|
439
|
+
// in a background window, where rAF never fires and an awaited evaluate
|
|
440
|
+
// hangs forever. A setTimeout out here always fires.
|
|
441
|
+
await new Promise((r) => setTimeout(r, 120));
|
|
442
|
+
const sent = await session.evaluate(submitExpr(this.profile, text, this.options.selectors?.send));
|
|
443
|
+
if (!sent.ok)
|
|
444
|
+
throw new Error(`couldn't send to ${this.appName}: ${sent.reason}`);
|
|
445
|
+
return await this.waitForReply(session, before, onProgress);
|
|
446
|
+
}
|
|
447
|
+
finally {
|
|
448
|
+
session.close();
|
|
449
|
+
}
|
|
450
|
+
}
|
|
451
|
+
/** Poll until the transcript stops changing, or we run out of patience. */
|
|
452
|
+
async waitForReply(session, before, onProgress) {
|
|
453
|
+
const timeout = this.options.replyTimeoutMs ?? 300_000;
|
|
454
|
+
const settle = this.options.settleMs ?? 2500;
|
|
455
|
+
const deadline = Date.now() + timeout;
|
|
456
|
+
const expr = transcriptExpr(this.profile, this.options.selectors?.transcript);
|
|
457
|
+
let last = before;
|
|
458
|
+
let lastChange = Date.now();
|
|
459
|
+
while (Date.now() < deadline) {
|
|
460
|
+
await new Promise((r) => setTimeout(r, 500));
|
|
461
|
+
let now;
|
|
462
|
+
try {
|
|
463
|
+
now = await session.evaluate(expr);
|
|
464
|
+
}
|
|
465
|
+
catch {
|
|
466
|
+
break; // the window went away mid-answer
|
|
467
|
+
}
|
|
468
|
+
if (now !== last) {
|
|
469
|
+
last = now;
|
|
470
|
+
lastChange = Date.now();
|
|
471
|
+
onProgress?.(delta(before, now));
|
|
472
|
+
}
|
|
473
|
+
else if (last !== before && Date.now() - lastChange > settle) {
|
|
474
|
+
return delta(before, last);
|
|
475
|
+
}
|
|
476
|
+
}
|
|
477
|
+
const out = delta(before, last);
|
|
478
|
+
if (out)
|
|
479
|
+
return out;
|
|
480
|
+
throw new Error(`${this.appName} didn't answer within ${Math.round(timeout / 1000)}s`);
|
|
481
|
+
}
|
|
482
|
+
}
|
|
483
|
+
/**
|
|
484
|
+
* What the panel gained.
|
|
485
|
+
*
|
|
486
|
+
* Not a real diff: the transcript is a rendered panel that scrolls, collapses
|
|
487
|
+
* and reflows. When `after` simply grew, the tail is the new part; when it
|
|
488
|
+
* changed shape, the common prefix is the best we can honestly do.
|
|
489
|
+
*/
|
|
490
|
+
export function delta(before, after) {
|
|
491
|
+
if (after.startsWith(before))
|
|
492
|
+
return after.slice(before.length).trim();
|
|
493
|
+
let i = 0;
|
|
494
|
+
while (i < before.length && i < after.length && before[i] === after[i])
|
|
495
|
+
i++;
|
|
496
|
+
return after.slice(i).trim();
|
|
497
|
+
}
|
|
498
|
+
//# sourceMappingURL=gui-chat.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"gui-chat.js","sourceRoot":"","sources":["../../../src/adapters/bridges/gui-chat.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Kiro bridge — the same shape as Antigravity, because the situation is the
|
|
3
|
+
* same shape: an Electron app (a VS Code fork) whose agent lives in a panel and
|
|
4
|
+
* offers nothing to talk to but its DevTools port.
|
|
5
|
+
*
|
|
6
|
+
* The `code` CLI inside Kiro.app is VS Code's, not an agent interface — it
|
|
7
|
+
* opens files and installs extensions. It cannot ask Kiro anything, so it isn't
|
|
8
|
+
* used here.
|
|
9
|
+
*
|
|
10
|
+
* The Monaco hazard is at its sharpest in this one. Kiro's window is a code
|
|
11
|
+
* editor first: on launch its only contenteditable IS your source file. The
|
|
12
|
+
* driver refuses to type into anything under .monaco-editor for exactly this
|
|
13
|
+
* reason — see gui-chat.ts. Open the Kiro chat panel before expecting this to
|
|
14
|
+
* find anything.
|
|
15
|
+
*/
|
|
16
|
+
import { AntigravityBridge } from "./antigravity.js";
|
|
17
|
+
export declare class KiroBridge extends AntigravityBridge {
|
|
18
|
+
constructor(id: string, projectDir: string, options?: Record<string, unknown>);
|
|
19
|
+
}
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Kiro bridge — the same shape as Antigravity, because the situation is the
|
|
3
|
+
* same shape: an Electron app (a VS Code fork) whose agent lives in a panel and
|
|
4
|
+
* offers nothing to talk to but its DevTools port.
|
|
5
|
+
*
|
|
6
|
+
* The `code` CLI inside Kiro.app is VS Code's, not an agent interface — it
|
|
7
|
+
* opens files and installs extensions. It cannot ask Kiro anything, so it isn't
|
|
8
|
+
* used here.
|
|
9
|
+
*
|
|
10
|
+
* The Monaco hazard is at its sharpest in this one. Kiro's window is a code
|
|
11
|
+
* editor first: on launch its only contenteditable IS your source file. The
|
|
12
|
+
* driver refuses to type into anything under .monaco-editor for exactly this
|
|
13
|
+
* reason — see gui-chat.ts. Open the Kiro chat panel before expecting this to
|
|
14
|
+
* find anything.
|
|
15
|
+
*/
|
|
16
|
+
import { AntigravityBridge } from "./antigravity.js";
|
|
17
|
+
export class KiroBridge extends AntigravityBridge {
|
|
18
|
+
constructor(id, projectDir, options = {}) {
|
|
19
|
+
super(id, projectDir, options, "kiro", "Kiro");
|
|
20
|
+
}
|
|
21
|
+
}
|
|
22
|
+
//# sourceMappingURL=kiro.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"kiro.js","sourceRoot":"","sources":["../../../src/adapters/bridges/kiro.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;GAcG;AAEH,OAAO,EAAE,iBAAiB,EAAE,MAAM,kBAAkB,CAAC;AAErD,MAAM,OAAO,UAAW,SAAQ,iBAAiB;IAC/C,YAAY,EAAU,EAAE,UAAkB,EAAE,UAAmC,EAAE;QAC/E,KAAK,CAAC,EAAE,EAAE,UAAU,EAAE,OAAO,EAAE,MAAM,EAAE,MAAM,CAAC,CAAC;IACjD,CAAC;CACF"}
|
|
@@ -0,0 +1,82 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* What each GUI agent's chat is made of.
|
|
3
|
+
*
|
|
4
|
+
* Read off the running apps, with two projects' work behind them:
|
|
5
|
+
* krishnakanthb13/antigravity_phone_chat for the shape of Antigravity's chat,
|
|
6
|
+
* and yazanbaker94/AntiGravity-AutoAccept for how to attach to it at all. Both
|
|
7
|
+
* MIT, both doing this daily against real installs.
|
|
8
|
+
*
|
|
9
|
+
* Everything here was then checked against Antigravity IDE 1.107.0 on this
|
|
10
|
+
* machine, and the checking mattered — two of the borrowed selectors are gone
|
|
11
|
+
* in this build, and the app that matters isn't the one with the obvious name.
|
|
12
|
+
*
|
|
13
|
+
* The scoping is the important idea, and it's from the phone-chat project. The
|
|
14
|
+
* composer is looked for *inside* `#conversation` — never in the document at
|
|
15
|
+
* large. These are VS Code-family apps and Monaco, the editor holding your
|
|
16
|
+
* source file, is a `contenteditable`. Hunt the document and you will
|
|
17
|
+
* eventually find it and type a prompt into someone's code. Search inside the
|
|
18
|
+
* chat panel and you cannot.
|
|
19
|
+
*
|
|
20
|
+
* Selectors rot; two already did. When they stop matching, the bridge says so
|
|
21
|
+
* rather than improvising, and `options.selectors` overrides any of it without
|
|
22
|
+
* waiting for a release.
|
|
23
|
+
*/
|
|
24
|
+
export interface AppProfile {
|
|
25
|
+
/** For messages. */
|
|
26
|
+
name: string;
|
|
27
|
+
/**
|
|
28
|
+
* The debugging port to expect by default.
|
|
29
|
+
*
|
|
30
|
+
* 9333 for Antigravity, not the usual 9222: Antigravity's own Browser Control
|
|
31
|
+
* (the Chrome button in its toolbar) already uses 9222, and asking for it
|
|
32
|
+
* gives you EADDRINUSE and an IDE with no debugger. That's the AutoAccept
|
|
33
|
+
* extension's finding, and it's worth every word of this comment — it cost an
|
|
34
|
+
* hour here, launching an IDE that silently never got a port while a stale
|
|
35
|
+
* process held it.
|
|
36
|
+
*/
|
|
37
|
+
defaultPort: number;
|
|
38
|
+
/** How to start it with a debugger, per platform. */
|
|
39
|
+
launch: {
|
|
40
|
+
darwin: string;
|
|
41
|
+
win32: string;
|
|
42
|
+
linux: string;
|
|
43
|
+
};
|
|
44
|
+
/**
|
|
45
|
+
* The chat panel. The composer is only ever looked for inside one of these,
|
|
46
|
+
* which is what keeps Monaco out of reach.
|
|
47
|
+
*/
|
|
48
|
+
chatRoots: string[];
|
|
49
|
+
/** The composer, within the root. */
|
|
50
|
+
composer: string;
|
|
51
|
+
/**
|
|
52
|
+
* The submit control, tried in order. Each is matched then walked up to its
|
|
53
|
+
* closest button — Antigravity's arrow is an svg inside one.
|
|
54
|
+
*/
|
|
55
|
+
send: string[];
|
|
56
|
+
/** Visible ⇒ a turn is already running and the composer isn't yours. */
|
|
57
|
+
busy?: string;
|
|
58
|
+
/** Where the conversation is rendered, for reading replies back. */
|
|
59
|
+
transcript: string[];
|
|
60
|
+
/**
|
|
61
|
+
* Text the panel shows when the app is signed out.
|
|
62
|
+
*
|
|
63
|
+
* Worth a field of its own, because a signed-out app fails in a way that
|
|
64
|
+
* mimics a broken driver: the chat renders, the composer takes text happily,
|
|
65
|
+
* and the send button is simply disabled forever. Without this, the honest
|
|
66
|
+
* report is "we typed and nothing happened", which sends you debugging the
|
|
67
|
+
* wrong thing — as it did here for an hour. With it, the report is "log in".
|
|
68
|
+
*/
|
|
69
|
+
signedOut?: RegExp;
|
|
70
|
+
}
|
|
71
|
+
/** Anything under one of these is never a composer, whatever else it looks like. */
|
|
72
|
+
export declare const FORBIDDEN_ANCESTORS: string[];
|
|
73
|
+
export declare const PROFILES: Record<string, AppProfile>;
|
|
74
|
+
/**
|
|
75
|
+
* When we don't know the app: no chat root to scope to, so the composer has to
|
|
76
|
+
* earn it by being labelled like one, and Monaco is excluded explicitly rather
|
|
77
|
+
* than structurally. Strictly worse than a profile — that's why profiles exist.
|
|
78
|
+
*/
|
|
79
|
+
export declare const GENERIC: AppProfile;
|
|
80
|
+
/** What a chat box calls itself, for the generic path only. */
|
|
81
|
+
export declare const CHATTY: RegExp;
|
|
82
|
+
export declare function profileFor(kind: string): AppProfile;
|