@onepointfour-packs/flaredeck-agent 0.2.37 → 0.2.39
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/agent.d.ts +42 -3
- package/dist/agent.js +84 -6
- package/dist/agent.js.map +1 -1
- package/dist/automode.js +43 -3
- package/dist/automode.js.map +1 -1
- package/dist/boards.d.ts +72 -0
- package/dist/boards.js +136 -0
- package/dist/boards.js.map +1 -0
- package/dist/branches.d.ts +12 -1
- package/dist/branches.js +13 -3
- package/dist/branches.js.map +1 -1
- package/dist/browser.d.ts +59 -0
- package/dist/browser.js +260 -26
- package/dist/browser.js.map +1 -1
- package/dist/config.d.ts +22 -0
- package/dist/config.js +37 -2
- package/dist/config.js.map +1 -1
- package/dist/credentials.d.ts +217 -0
- package/dist/credentials.js +332 -0
- package/dist/credentials.js.map +1 -0
- package/dist/engine-antigravity.js +2 -0
- package/dist/engine-antigravity.js.map +1 -1
- package/dist/engine-claude.js +14 -2
- package/dist/engine-claude.js.map +1 -1
- package/dist/engine.d.ts +19 -0
- package/dist/engine.js.map +1 -1
- package/dist/environment.d.ts +15 -3
- package/dist/environment.js +79 -9
- package/dist/environment.js.map +1 -1
- package/dist/envtools.d.ts +58 -0
- package/dist/envtools.js +151 -0
- package/dist/envtools.js.map +1 -0
- package/dist/flaredeck-proxy.d.ts +11 -1
- package/dist/flaredeck-proxy.js +27 -1
- package/dist/flaredeck-proxy.js.map +1 -1
- package/dist/flaredeck.d.ts +24 -0
- package/dist/flaredeck.js +20 -0
- package/dist/flaredeck.js.map +1 -1
- package/dist/github.d.ts +2 -0
- package/dist/github.js +4 -0
- package/dist/github.js.map +1 -1
- package/dist/githubauth.d.ts +11 -1
- package/dist/githubauth.js +82 -16
- package/dist/githubauth.js.map +1 -1
- package/dist/hydrate.d.ts +76 -0
- package/dist/hydrate.js +77 -0
- package/dist/hydrate.js.map +1 -0
- package/dist/limits.d.ts +184 -0
- package/dist/limits.js +251 -0
- package/dist/limits.js.map +1 -0
- package/dist/main.js +177 -33
- package/dist/main.js.map +1 -1
- package/dist/picker.d.ts +10 -0
- package/dist/picker.js +150 -0
- package/dist/picker.js.map +1 -0
- package/dist/preflight.d.ts +43 -0
- package/dist/preflight.js +133 -19
- package/dist/preflight.js.map +1 -1
- package/dist/repopicker.js +8 -96
- package/dist/repopicker.js.map +1 -1
- package/dist/runs.d.ts +19 -4
- package/dist/runs.js +74 -9
- package/dist/runs.js.map +1 -1
- package/dist/serve.d.ts +6 -0
- package/dist/serve.js +1152 -138
- package/dist/serve.js.map +1 -1
- package/dist/session.d.ts +51 -0
- package/dist/session.js +145 -0
- package/dist/session.js.map +1 -0
- package/dist/settle.d.ts +18 -0
- package/dist/settle.js +39 -0
- package/dist/settle.js.map +1 -0
- package/dist/signin.d.ts +47 -0
- package/dist/signin.js +331 -0
- package/dist/signin.js.map +1 -0
- package/dist/stream.d.ts +8 -0
- package/dist/stream.js +44 -3
- package/dist/stream.js.map +1 -1
- package/dist/subscriptions.d.ts +115 -0
- package/dist/subscriptions.js +140 -0
- package/dist/subscriptions.js.map +1 -0
- package/dist/team.js +7 -1
- package/dist/team.js.map +1 -1
- package/dist/tmux.d.ts +1 -0
- package/dist/tmux.js +47 -0
- package/dist/tmux.js.map +1 -0
- package/dist/ui.d.ts +13 -0
- package/dist/ui.js +7 -2
- package/dist/ui.js.map +1 -1
- package/dist/usage.d.ts +105 -0
- package/dist/usage.js +348 -0
- package/dist/usage.js.map +1 -0
- package/dist/wall.d.ts +35 -0
- package/dist/wall.js +94 -0
- package/dist/wall.js.map +1 -0
- package/dist/workspace.d.ts +15 -1
- package/dist/workspace.js +25 -1
- package/dist/workspace.js.map +1 -1
- package/dist/worktree.d.ts +19 -0
- package/dist/worktree.js +179 -4
- package/dist/worktree.js.map +1 -1
- package/package.json +1 -1
package/dist/settle.d.ts
ADDED
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Wait for a page to stop changing before looking at it.
|
|
3
|
+
*
|
|
4
|
+
* `domcontentloaded` means the HTML arrived, which on a single-page app is the moment BEFORE
|
|
5
|
+
* anything exists. FlareDeck's own login page proved it: the sign-in wall check ran, found no
|
|
6
|
+
* sign-in buttons because Angular had not drawn them yet, and reported an ordinary page — while
|
|
7
|
+
* the same check a few seconds later said "sign-in wall" with complete confidence.
|
|
8
|
+
*
|
|
9
|
+
* That affects everything the agent sees, not just the wall check. A snapshot taken too early is
|
|
10
|
+
* an empty shell, and an agent reading one concludes the feature it came for is missing.
|
|
11
|
+
*
|
|
12
|
+
* Quiet is measured by mutations rather than by the network, because an app holding a websocket
|
|
13
|
+
* open — Firebase keeps one — is never network-idle, and waiting for that would simply time out
|
|
14
|
+
* on every page.
|
|
15
|
+
*/
|
|
16
|
+
import type { Page } from 'playwright-core';
|
|
17
|
+
/** Resolves when the DOM has been still for a moment, or when the time is up. Never throws. */
|
|
18
|
+
export declare function settle(page: Page, timeoutMs?: number): Promise<void>;
|
package/dist/settle.js
ADDED
|
@@ -0,0 +1,39 @@
|
|
|
1
|
+
const QUIET_MS = 450;
|
|
2
|
+
const GRACE_MS = 250;
|
|
3
|
+
/** Resolves when the DOM has been still for a moment, or when the time is up. Never throws. */
|
|
4
|
+
export async function settle(page, timeoutMs = 6000) {
|
|
5
|
+
const started = Date.now();
|
|
6
|
+
try {
|
|
7
|
+
/* One observer per document, re-installed after each navigation, so it must be idempotent —
|
|
8
|
+
a second would double-count and the page would never read as quiet. No named functions: a
|
|
9
|
+
bundler wraps them in a `__name` helper the page does not have. */
|
|
10
|
+
await page.evaluate(() => {
|
|
11
|
+
const w = window;
|
|
12
|
+
if (w.__fdObserver)
|
|
13
|
+
return;
|
|
14
|
+
w.__fdLastMutation = Date.now();
|
|
15
|
+
w.__fdObserver = new MutationObserver(() => { w.__fdLastMutation = Date.now(); });
|
|
16
|
+
w.__fdObserver.observe(document.documentElement, {
|
|
17
|
+
childList: true, subtree: true, attributes: true, characterData: true,
|
|
18
|
+
});
|
|
19
|
+
});
|
|
20
|
+
}
|
|
21
|
+
catch {
|
|
22
|
+
// A page that will not run script yet is one we cannot watch; fall back to a plain pause
|
|
23
|
+
// rather than returning instantly and snapshotting nothing.
|
|
24
|
+
await new Promise((r) => setTimeout(r, 800));
|
|
25
|
+
return;
|
|
26
|
+
}
|
|
27
|
+
while (Date.now() - started < timeoutMs) {
|
|
28
|
+
const since = await page
|
|
29
|
+
.evaluate(() => Date.now() - (window.__fdLastMutation ?? 0))
|
|
30
|
+
.catch(() => Number.POSITIVE_INFINITY);
|
|
31
|
+
if (since >= QUIET_MS)
|
|
32
|
+
break;
|
|
33
|
+
await new Promise((r) => setTimeout(r, 120));
|
|
34
|
+
}
|
|
35
|
+
/* One more beat after it goes quiet: a panel that debounces and THEN fetches is briefly still,
|
|
36
|
+
and that gap is exactly where a snapshot catches placeholders. */
|
|
37
|
+
await new Promise((r) => setTimeout(r, GRACE_MS));
|
|
38
|
+
}
|
|
39
|
+
//# sourceMappingURL=settle.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"settle.js","sourceRoot":"","sources":["../src/settle.ts"],"names":[],"mappings":"AA0BA,MAAM,QAAQ,GAAG,GAAG,CAAC;AACrB,MAAM,QAAQ,GAAG,GAAG,CAAC;AAErB,+FAA+F;AAC/F,MAAM,CAAC,KAAK,UAAU,MAAM,CAAC,IAAU,EAAE,SAAS,GAAG,IAAI;IACvD,MAAM,OAAO,GAAG,IAAI,CAAC,GAAG,EAAE,CAAC;IAC3B,IAAI,CAAC;QACH;;6EAEqE;QACrE,MAAM,IAAI,CAAC,QAAQ,CAAC,GAAG,EAAE;YACvB,MAAM,CAAC,GAAG,MAAa,CAAC;YACxB,IAAI,CAAC,CAAC,YAAY;gBAAE,OAAO;YAC3B,CAAC,CAAC,gBAAgB,GAAG,IAAI,CAAC,GAAG,EAAE,CAAC;YAChC,CAAC,CAAC,YAAY,GAAG,IAAI,gBAAgB,CAAC,GAAG,EAAE,GAAG,CAAC,CAAC,gBAAgB,GAAG,IAAI,CAAC,GAAG,EAAE,CAAC,CAAC,CAAC,CAAC,CAAC;YAClF,CAAC,CAAC,YAAY,CAAC,OAAO,CAAC,QAAQ,CAAC,eAAe,EAAE;gBAC/C,SAAS,EAAE,IAAI,EAAE,OAAO,EAAE,IAAI,EAAE,UAAU,EAAE,IAAI,EAAE,aAAa,EAAE,IAAI;aACtE,CAAC,CAAC;QACL,CAAC,CAAC,CAAC;IACL,CAAC;IAAC,MAAM,CAAC;QACP,yFAAyF;QACzF,4DAA4D;QAC5D,MAAM,IAAI,OAAO,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,UAAU,CAAC,CAAC,EAAE,GAAG,CAAC,CAAC,CAAC;QAC7C,OAAO;IACT,CAAC;IAED,OAAO,IAAI,CAAC,GAAG,EAAE,GAAG,OAAO,GAAG,SAAS,EAAE,CAAC;QACxC,MAAM,KAAK,GAAG,MAAM,IAAI;aACrB,QAAQ,CAAC,GAAG,EAAE,CAAC,IAAI,CAAC,GAAG,EAAE,GAAG,CAAE,MAAc,CAAC,gBAAgB,IAAI,CAAC,CAAC,CAAC;aACpE,KAAK,CAAC,GAAG,EAAE,CAAC,MAAM,CAAC,iBAAiB,CAAC,CAAC;QACzC,IAAI,KAAK,IAAI,QAAQ;YAAE,MAAM;QAC7B,MAAM,IAAI,OAAO,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,UAAU,CAAC,CAAC,EAAE,GAAG,CAAC,CAAC,CAAC;IAC/C,CAAC;IACD;wEACoE;IACpE,MAAM,IAAI,OAAO,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,UAAU,CAAC,CAAC,EAAE,QAAQ,CAAC,CAAC,CAAC;AACpD,CAAC"}
|
package/dist/signin.d.ts
ADDED
|
@@ -0,0 +1,47 @@
|
|
|
1
|
+
import type { Browser } from 'playwright-core';
|
|
2
|
+
import { type SavedSession } from './session.js';
|
|
3
|
+
export type SignInResult = {
|
|
4
|
+
ok: boolean;
|
|
5
|
+
why?: string;
|
|
6
|
+
session?: SavedSession;
|
|
7
|
+
};
|
|
8
|
+
/**
|
|
9
|
+
* Open `url` in a real browser and serve a viewer for it on loopback. Resolves when the person
|
|
10
|
+
* presses Save, or when `timeoutMs` passes with nothing saved.
|
|
11
|
+
*
|
|
12
|
+
* `onReady` is given the address to show them. Loopback only: this drives a browser mid-sign-in,
|
|
13
|
+
* so it is not something to put on a public port.
|
|
14
|
+
*/
|
|
15
|
+
export declare function signInSession(opts: {
|
|
16
|
+
url: string;
|
|
17
|
+
launch: () => Promise<Browser>;
|
|
18
|
+
onReady: (viewerUrl: string) => void;
|
|
19
|
+
onNote?: (text: string) => void;
|
|
20
|
+
timeoutMs?: number;
|
|
21
|
+
/**
|
|
22
|
+
* Reach the person through FlareDeck instead of a port on this machine.
|
|
23
|
+
*
|
|
24
|
+
* Loopback was never the answer for the case this exists for: the agent may be on a cloud box
|
|
25
|
+
* and the person is on a phone. With this set the harness DIALS OUT to the relay and listens
|
|
26
|
+
* on nothing — which is also why it is safe, since a page that drives a browser mid-sign-in
|
|
27
|
+
* should not be reachable at an address. What crosses the wire is pixels and clicks; the
|
|
28
|
+
* sign-in itself happens in this browser, against the real site, so the origin and the OAuth
|
|
29
|
+
* redirect URIs are exactly what they would be with somebody sitting here.
|
|
30
|
+
*/
|
|
31
|
+
relay?: {
|
|
32
|
+
/** The relay's base, e.g. wss://mcp.flaredeck.app */
|
|
33
|
+
url: string;
|
|
34
|
+
/** This harness's FlareDeck bearer token — the agent half is not open to anyone else. */
|
|
35
|
+
token: string;
|
|
36
|
+
/** Pairing id, `sgn_…`, minted by the caller and put on the card. */
|
|
37
|
+
id: string;
|
|
38
|
+
/**
|
|
39
|
+
* Where to send them, given the token the RELAY mints.
|
|
40
|
+
*
|
|
41
|
+
* The relay signs it, not us: it holds the signing secret, the token expires on its own and
|
|
42
|
+
* it is bound to this tenant and this pairing. A random string invented here would have had
|
|
43
|
+
* to sit on a card, unexpiring, until somebody noticed.
|
|
44
|
+
*/
|
|
45
|
+
viewerUrl: (viewerToken: string) => string;
|
|
46
|
+
};
|
|
47
|
+
}): Promise<SignInResult>;
|
package/dist/signin.js
ADDED
|
@@ -0,0 +1,331 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Letting a person sign in, once, for an agent that cannot.
|
|
3
|
+
*
|
|
4
|
+
* An agent meets a sign-in wall and stops — see wall.ts. This is the other half: a real browser
|
|
5
|
+
* is opened here, the person is given a link, and they drive that browser from their own one and
|
|
6
|
+
* complete the login themselves. Nothing about the login is automated, deliberately: identity
|
|
7
|
+
* providers refuse browsers they can tell are scripted, and no password should pass through a
|
|
8
|
+
* model or a harness in the first place. The only thing automated is showing the screen and
|
|
9
|
+
* forwarding a mouse and keyboard.
|
|
10
|
+
*
|
|
11
|
+
* The session is then kept against the PROJECT, so every later card opens straight into the app.
|
|
12
|
+
*
|
|
13
|
+
* Ported from Sceneflo's live-auth.ts (BrowserAgent), with four corrections found by driving a
|
|
14
|
+
* real Google sign-in into FlareDeck on 2026-10-09 — all four noted at the point they apply.
|
|
15
|
+
*/
|
|
16
|
+
import { createServer } from 'node:http';
|
|
17
|
+
import { captureSession } from './session.js';
|
|
18
|
+
const KEYS = {
|
|
19
|
+
Enter: { code: 'Enter', vk: 13 }, Tab: { code: 'Tab', vk: 9 },
|
|
20
|
+
Backspace: { code: 'Backspace', vk: 8 }, Delete: { code: 'Delete', vk: 46 },
|
|
21
|
+
ArrowLeft: { code: 'ArrowLeft', vk: 37 }, ArrowRight: { code: 'ArrowRight', vk: 39 },
|
|
22
|
+
ArrowUp: { code: 'ArrowUp', vk: 38 }, ArrowDown: { code: 'ArrowDown', vk: 40 },
|
|
23
|
+
Escape: { code: 'Escape', vk: 27 },
|
|
24
|
+
};
|
|
25
|
+
const VIEWER = `<!doctype html>
|
|
26
|
+
<meta charset="utf-8"><title>Sign in</title>
|
|
27
|
+
<style>
|
|
28
|
+
:root { color-scheme: dark }
|
|
29
|
+
body { margin:0; background:#111317; color:#e8e8ea; font:14px -apple-system,system-ui,sans-serif }
|
|
30
|
+
header { display:flex; gap:12px; align-items:center; padding:8px 14px; background:#1b1e24; border-bottom:1px solid #2a2e36 }
|
|
31
|
+
strong { font-weight:600 } #url { color:#9aa1ad; font-size:12px; overflow:hidden; text-overflow:ellipsis; white-space:nowrap; flex:1 }
|
|
32
|
+
button { font:inherit; padding:6px 14px; border-radius:7px; border:1px solid #39404d; background:#272c35; color:#e8e8ea; cursor:pointer }
|
|
33
|
+
button.go { background:#3b6fd4; border-color:#3b6fd4; font-weight:600 }
|
|
34
|
+
#msg { padding:8px 14px; color:#9aa1ad; font-size:12px }
|
|
35
|
+
canvas { display:block; margin:0 auto; max-width:100%; box-shadow:0 0 40px #0008 }
|
|
36
|
+
</style>
|
|
37
|
+
<header>
|
|
38
|
+
<strong>Sign in</strong><span id="url"></span><span id="fps" style="color:#6b7280;font-size:12px"></span>
|
|
39
|
+
<button onclick="send({t:'reload'})">Reload</button>
|
|
40
|
+
<button class="go" onclick="send({t:'save'})">Save & finish</button>
|
|
41
|
+
</header>
|
|
42
|
+
<div id="msg">Click and type in the picture — it is a real browser. Sign in, then press Save & finish.</div>
|
|
43
|
+
<canvas id="c"></canvas>
|
|
44
|
+
<script>
|
|
45
|
+
const c = document.getElementById('c'), ctx = c.getContext('2d');
|
|
46
|
+
const ws = new WebSocket((location.protocol === 'https:' ? 'wss://' : 'ws://') + location.host);
|
|
47
|
+
ws.binaryType = 'blob';
|
|
48
|
+
const send = (o) => ws.readyState === 1 && ws.send(JSON.stringify(o));
|
|
49
|
+
let pending = null, drawing = false, frames = 0;
|
|
50
|
+
/* The size of the page being STREAMED. It changes when an OAuth popup is followed — Google
|
|
51
|
+
opens one far smaller than the app — and a mapping that assumes the app's own size puts
|
|
52
|
+
every click in the popup tens of pixels adrift, which reads as "the button does nothing". */
|
|
53
|
+
let VW = 1280, VH = 900;
|
|
54
|
+
|
|
55
|
+
function draw() {
|
|
56
|
+
if (!pending || drawing) return;
|
|
57
|
+
drawing = true;
|
|
58
|
+
const blob = pending; pending = null;
|
|
59
|
+
createImageBitmap(blob).then((b) => {
|
|
60
|
+
if (c.width !== b.width || c.height !== b.height) { c.width = b.width; c.height = b.height; }
|
|
61
|
+
ctx.drawImage(b, 0, 0); b.close(); frames++; drawing = false;
|
|
62
|
+
if (pending) requestAnimationFrame(draw);
|
|
63
|
+
}).catch(() => { drawing = false; });
|
|
64
|
+
}
|
|
65
|
+
setInterval(() => { document.getElementById('fps').textContent = frames + ' fps'; frames = 0; }, 1000);
|
|
66
|
+
|
|
67
|
+
ws.onmessage = (e) => {
|
|
68
|
+
if (typeof e.data !== 'string') { pending = e.data; requestAnimationFrame(draw); return; }
|
|
69
|
+
const m = JSON.parse(e.data);
|
|
70
|
+
if (m.t === 'url') document.getElementById('url').textContent = m.d;
|
|
71
|
+
if (m.t === 'msg') document.getElementById('msg').textContent = m.d;
|
|
72
|
+
if (m.t === 'size' && m.d) { VW = m.d.w; VH = m.d.h; }
|
|
73
|
+
};
|
|
74
|
+
|
|
75
|
+
const at = (e) => { const r = c.getBoundingClientRect();
|
|
76
|
+
return { x: (e.clientX - r.left) / r.width * VW, y: (e.clientY - r.top) / r.height * VH }; };
|
|
77
|
+
c.addEventListener('mousedown', (e) => send({ t: 'down', ...at(e) }));
|
|
78
|
+
c.addEventListener('mouseup', (e) => send({ t: 'up', ...at(e) }));
|
|
79
|
+
let lastMove = 0;
|
|
80
|
+
c.addEventListener('mousemove', (e) => {
|
|
81
|
+
const now = performance.now();
|
|
82
|
+
if (now - lastMove < 80 || ws.bufferedAmount > 20000) return;
|
|
83
|
+
lastMove = now; send({ t: 'move', ...at(e) });
|
|
84
|
+
});
|
|
85
|
+
c.addEventListener('wheel', (e) => { e.preventDefault(); send({ t: 'wheel', ...at(e), dy: e.deltaY }); }, { passive: false });
|
|
86
|
+
addEventListener('keydown', (e) => {
|
|
87
|
+
if (e.metaKey || e.ctrlKey) return;
|
|
88
|
+
if (e.key.length === 1) { e.preventDefault(); send({ t: 'text', d: e.key }); }
|
|
89
|
+
else { e.preventDefault(); send({ t: 'key', d: e.key }); }
|
|
90
|
+
});
|
|
91
|
+
</script>`;
|
|
92
|
+
/**
|
|
93
|
+
* Open `url` in a real browser and serve a viewer for it on loopback. Resolves when the person
|
|
94
|
+
* presses Save, or when `timeoutMs` passes with nothing saved.
|
|
95
|
+
*
|
|
96
|
+
* `onReady` is given the address to show them. Loopback only: this drives a browser mid-sign-in,
|
|
97
|
+
* so it is not something to put on a public port.
|
|
98
|
+
*/
|
|
99
|
+
export async function signInSession(opts) {
|
|
100
|
+
const note = opts.onNote ?? (() => { });
|
|
101
|
+
let browser;
|
|
102
|
+
try {
|
|
103
|
+
browser = await opts.launch();
|
|
104
|
+
}
|
|
105
|
+
catch (e) {
|
|
106
|
+
return { ok: false, why: `could not start a browser: ${e instanceof Error ? e.message : String(e)}` };
|
|
107
|
+
}
|
|
108
|
+
const ctx = await browser.newContext({ viewport: { width: 1280, height: 900 } });
|
|
109
|
+
/* What the app itself said. A sign-in that fails usually fails with a precise reason the page
|
|
110
|
+
prints and nobody reads — "popup blocked", "unauthorized domain", "web storage unsupported" —
|
|
111
|
+
and without this the only evidence is a toast that has already faded. */
|
|
112
|
+
ctx.on('page', (p) => {
|
|
113
|
+
p.on('console', (m) => {
|
|
114
|
+
const t = m.text();
|
|
115
|
+
if (m.type() === 'error' || /auth|sign|popup|storage|cookie|denied|blocked/i.test(t)) {
|
|
116
|
+
note(`page [${m.type()}] ${t.slice(0, 300)}`);
|
|
117
|
+
}
|
|
118
|
+
});
|
|
119
|
+
p.on('pageerror', (e) => note(`page error: ${String(e).slice(0, 300)}`));
|
|
120
|
+
});
|
|
121
|
+
let page = await ctx.newPage();
|
|
122
|
+
await page.goto(opts.url, { waitUntil: 'domcontentloaded' }).catch(() => { });
|
|
123
|
+
const http = createServer((_req, res) => {
|
|
124
|
+
res.writeHead(200, { 'content-type': 'text/html; charset=utf-8' });
|
|
125
|
+
res.end(VIEWER);
|
|
126
|
+
});
|
|
127
|
+
const { WebSocketServer } = await import('ws');
|
|
128
|
+
const wss = new WebSocketServer({ server: http });
|
|
129
|
+
return new Promise((resolve) => {
|
|
130
|
+
let done = false;
|
|
131
|
+
const finish = async (r) => {
|
|
132
|
+
if (done)
|
|
133
|
+
return;
|
|
134
|
+
done = true;
|
|
135
|
+
clearTimeout(timer);
|
|
136
|
+
try {
|
|
137
|
+
wss.close();
|
|
138
|
+
}
|
|
139
|
+
catch { /* going away */ }
|
|
140
|
+
try {
|
|
141
|
+
http.close();
|
|
142
|
+
}
|
|
143
|
+
catch { /* going away */ }
|
|
144
|
+
try {
|
|
145
|
+
await browser.close();
|
|
146
|
+
}
|
|
147
|
+
catch { /* going away */ }
|
|
148
|
+
resolve(r);
|
|
149
|
+
};
|
|
150
|
+
/* An hour, not the twenty minutes this started with. That figure was chosen when the viewer
|
|
151
|
+
was loopback and the person was by definition at the machine; through the relay the ask
|
|
152
|
+
reaches a phone, and "I will do it when I am back at my desk" is an ordinary answer rather
|
|
153
|
+
than a failure. The cost of waiting is one idle browser on a disposable machine. */
|
|
154
|
+
const timer = setTimeout(() => void finish({ ok: false, why: 'nobody signed in before the time was up' }), opts.timeoutMs ?? 60 * 60_000);
|
|
155
|
+
/* One body, two transports: a loopback viewer connecting in, or the relay we dialled out
|
|
156
|
+
to. Everything below only needs send/on('message')/bufferedAmount, which both provide. */
|
|
157
|
+
const drive = async (socket) => {
|
|
158
|
+
const send = (t, d) => socket.readyState === 1 && socket.send(JSON.stringify({ t, d }));
|
|
159
|
+
let cdp = await ctx.newCDPSession(page);
|
|
160
|
+
/**
|
|
161
|
+
* Start the picture again for somebody who has just begun watching.
|
|
162
|
+
*
|
|
163
|
+
* Chrome emits a screencast frame when the page CHANGES, so a sign-in screen that is
|
|
164
|
+
* simply sitting there produces nothing after its first frames — and in relay mode those
|
|
165
|
+
* are gone before anyone connects. Stopping and restarting forces a fresh one, and the
|
|
166
|
+
* size goes again because the viewer needs it to map a click.
|
|
167
|
+
*/
|
|
168
|
+
let reprime = async () => { };
|
|
169
|
+
const attach = async (p) => {
|
|
170
|
+
page = p;
|
|
171
|
+
cdp = await ctx.newCDPSession(p);
|
|
172
|
+
let told = '';
|
|
173
|
+
cdp.on('Page.screencastFrame', (f) => {
|
|
174
|
+
/* Ack FIRST: Chrome produces no further frame until this returns, so acking after a
|
|
175
|
+
slow socket write makes the round trip the frame rate. */
|
|
176
|
+
void cdp.send('Page.screencastFrameAck', { sessionId: f.sessionId }).catch(() => { });
|
|
177
|
+
const w = Math.round(f.metadata?.deviceWidth ?? 0), h = Math.round(f.metadata?.deviceHeight ?? 0);
|
|
178
|
+
if (w && h && `${w}x${h}` !== told) {
|
|
179
|
+
told = `${w}x${h}`;
|
|
180
|
+
send('size', { w, h });
|
|
181
|
+
}
|
|
182
|
+
/* Binary rather than base64-in-JSON, and dropped outright when the socket is behind:
|
|
183
|
+
a stale frame helps nobody. */
|
|
184
|
+
if (socket.readyState === 1 && socket.bufferedAmount < 300_000) {
|
|
185
|
+
socket.send(Buffer.from(f.data, 'base64'), { binary: true });
|
|
186
|
+
}
|
|
187
|
+
});
|
|
188
|
+
const start = () => cdp.send('Page.startScreencast', { format: 'jpeg', quality: 60, maxWidth: 1280, maxHeight: 900, everyNthFrame: 1 });
|
|
189
|
+
await start();
|
|
190
|
+
send('url', p.url());
|
|
191
|
+
p.on('framenavigated', () => send('url', p.url()));
|
|
192
|
+
reprime = async () => {
|
|
193
|
+
told = '';
|
|
194
|
+
send('url', p.url());
|
|
195
|
+
await cdp.send('Page.stopScreencast').catch(() => { });
|
|
196
|
+
await start().catch(() => { });
|
|
197
|
+
};
|
|
198
|
+
};
|
|
199
|
+
/* An OAuth flow opens a popup, which is a separate page — the stream has to follow it, and
|
|
200
|
+
follow it BACK. Without the return the viewer sits on a closed page at 0 fps while the
|
|
201
|
+
sign-in has already finished behind it. */
|
|
202
|
+
ctx.on('page', async (popup) => {
|
|
203
|
+
await popup.waitForLoadState('domcontentloaded').catch(() => { });
|
|
204
|
+
await attach(popup);
|
|
205
|
+
send('msg', 'A popup opened — you are looking at it now.');
|
|
206
|
+
popup.on('close', async () => {
|
|
207
|
+
const back = ctx.pages().find((q) => !q.isClosed() && q !== popup);
|
|
208
|
+
if (back) {
|
|
209
|
+
await attach(back);
|
|
210
|
+
send('msg', 'The popup closed — back on the app.');
|
|
211
|
+
}
|
|
212
|
+
});
|
|
213
|
+
});
|
|
214
|
+
await attach(page);
|
|
215
|
+
socket.on('message', async (raw) => {
|
|
216
|
+
let m;
|
|
217
|
+
try {
|
|
218
|
+
m = JSON.parse(String(raw));
|
|
219
|
+
}
|
|
220
|
+
catch {
|
|
221
|
+
return;
|
|
222
|
+
}
|
|
223
|
+
try {
|
|
224
|
+
if (m.t === 'down' || m.t === 'up' || m.t === 'move') {
|
|
225
|
+
await cdp.send('Input.dispatchMouseEvent', {
|
|
226
|
+
type: m.t === 'down' ? 'mousePressed' : m.t === 'up' ? 'mouseReleased' : 'mouseMoved',
|
|
227
|
+
x: m.x ?? 0, y: m.y ?? 0,
|
|
228
|
+
button: m.t === 'move' ? 'none' : 'left',
|
|
229
|
+
clickCount: m.t === 'move' ? 0 : 1,
|
|
230
|
+
});
|
|
231
|
+
}
|
|
232
|
+
else if (m.t === 'wheel') {
|
|
233
|
+
await cdp.send('Input.dispatchMouseEvent', { type: 'mouseWheel', x: m.x ?? 0, y: m.y ?? 0, deltaX: 0, deltaY: m.dy ?? 0 });
|
|
234
|
+
}
|
|
235
|
+
else if (m.t === 'text') {
|
|
236
|
+
await cdp.send('Input.insertText', { text: m.d ?? '' });
|
|
237
|
+
}
|
|
238
|
+
else if (m.t === 'key') {
|
|
239
|
+
const k = KEYS[m.d ?? ''];
|
|
240
|
+
if (k) {
|
|
241
|
+
await cdp.send('Input.dispatchKeyEvent', { type: 'rawKeyDown', code: k.code, key: m.d, windowsVirtualKeyCode: k.vk });
|
|
242
|
+
await cdp.send('Input.dispatchKeyEvent', { type: 'keyUp', code: k.code, key: m.d, windowsVirtualKeyCode: k.vk });
|
|
243
|
+
}
|
|
244
|
+
}
|
|
245
|
+
else if (m.t === 'viewer') {
|
|
246
|
+
// The relay says somebody has just started (or stopped) watching.
|
|
247
|
+
if (m.on)
|
|
248
|
+
await reprime();
|
|
249
|
+
}
|
|
250
|
+
else if (m.t === 'reload') {
|
|
251
|
+
await page.reload({ waitUntil: 'domcontentloaded' }).catch(() => { });
|
|
252
|
+
}
|
|
253
|
+
else if (m.t === 'save') {
|
|
254
|
+
/* Capture from the APP page, whatever the stream happens to be showing. Reading
|
|
255
|
+
storage off a closed popup yields nothing — which produces a session carrying only
|
|
256
|
+
cookies, and that was measured signing in as nobody. */
|
|
257
|
+
const livePages = ctx.pages().filter((q) => !q.isClosed());
|
|
258
|
+
const app = livePages.find((q) => {
|
|
259
|
+
try {
|
|
260
|
+
return new URL(q.url()).origin === new URL(opts.url).origin;
|
|
261
|
+
}
|
|
262
|
+
catch {
|
|
263
|
+
return false;
|
|
264
|
+
}
|
|
265
|
+
}) ?? livePages[0] ?? page;
|
|
266
|
+
const session = await captureSession(ctx, app);
|
|
267
|
+
const dbs = (session._idb ?? []).length;
|
|
268
|
+
note(`sign-in captured: ${(session.cookies ?? []).length} cookies, ${dbs} database(s)`);
|
|
269
|
+
send('msg', 'Saved — you can close this tab.');
|
|
270
|
+
await finish({ ok: true, session });
|
|
271
|
+
}
|
|
272
|
+
}
|
|
273
|
+
catch (e) {
|
|
274
|
+
note(`sign-in input: ${e instanceof Error ? e.message.split('\n')[0] : String(e)}`);
|
|
275
|
+
}
|
|
276
|
+
});
|
|
277
|
+
};
|
|
278
|
+
wss.on('connection', drive);
|
|
279
|
+
if (opts.relay) {
|
|
280
|
+
/* Dial out. Nothing listens on this machine in relay mode — not even loopback — so there
|
|
281
|
+
is no port to find and none to leave open afterwards. */
|
|
282
|
+
const { url, token, id, viewerUrl } = opts.relay;
|
|
283
|
+
void (async () => {
|
|
284
|
+
try {
|
|
285
|
+
const { WebSocket } = await import('ws');
|
|
286
|
+
const socket = new WebSocket(`${url.replace(/\/+$/, '')}/signin/agent?id=${encodeURIComponent(id)}`, {
|
|
287
|
+
headers: { authorization: `Bearer ${token}` },
|
|
288
|
+
});
|
|
289
|
+
/* The relay answers with the token the person will present. Waiting for it before
|
|
290
|
+
announcing readiness is what stops a link being put on a card that cannot yet be
|
|
291
|
+
opened — and `once` hands the socket over to drive() cleanly, because every message
|
|
292
|
+
after this one is a frame or an input. */
|
|
293
|
+
socket.once('message', async (raw) => {
|
|
294
|
+
let m;
|
|
295
|
+
try {
|
|
296
|
+
m = JSON.parse(String(raw));
|
|
297
|
+
}
|
|
298
|
+
catch {
|
|
299
|
+
m = null;
|
|
300
|
+
}
|
|
301
|
+
if (m?.t !== 'paired' || !m?.viewerToken) {
|
|
302
|
+
return void finish({ ok: false, why: 'the sign-in relay did not pair this request' });
|
|
303
|
+
}
|
|
304
|
+
opts.onReady(viewerUrl(String(m.viewerToken)));
|
|
305
|
+
await drive(socket);
|
|
306
|
+
});
|
|
307
|
+
socket.on('error', (e) => {
|
|
308
|
+
void finish({ ok: false, why: `could not reach the sign-in relay: ${e?.message ?? String(e)}` });
|
|
309
|
+
});
|
|
310
|
+
socket.on('close', () => {
|
|
311
|
+
// Only meaningful before a save; afterwards `finish` has already run and ignores this.
|
|
312
|
+
void finish({ ok: false, why: 'the sign-in relay closed before anyone signed in' });
|
|
313
|
+
});
|
|
314
|
+
}
|
|
315
|
+
catch (e) {
|
|
316
|
+
void finish({ ok: false, why: `could not start the relay connection: ${e instanceof Error ? e.message : String(e)}` });
|
|
317
|
+
}
|
|
318
|
+
})();
|
|
319
|
+
return;
|
|
320
|
+
}
|
|
321
|
+
/* Port 0 and loopback: several agents share a machine, and this drives a live sign-in.
|
|
322
|
+
Kept as the fallback for when FlareDeck itself cannot be reached — it is the thing that
|
|
323
|
+
still works when the relay does not. */
|
|
324
|
+
http.listen(0, '127.0.0.1', () => {
|
|
325
|
+
const addr = http.address();
|
|
326
|
+
const port = typeof addr === 'object' && addr ? addr.port : 0;
|
|
327
|
+
opts.onReady(`http://127.0.0.1:${port}`);
|
|
328
|
+
});
|
|
329
|
+
});
|
|
330
|
+
}
|
|
331
|
+
//# sourceMappingURL=signin.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"signin.js","sourceRoot":"","sources":["../src/signin.ts"],"names":[],"mappings":"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"}
|
package/dist/stream.d.ts
CHANGED
|
@@ -52,6 +52,8 @@ export type StreamEvent = {
|
|
|
52
52
|
environmentScript?: string;
|
|
53
53
|
/** A script named for ONE agent, keyed by its lower-cased name. Beats the shared one. */
|
|
54
54
|
environmentScriptByAgent?: Record<string, string>;
|
|
55
|
+
/** Deployed addresses for this repository, by name — the dev or UAT site, never production. */
|
|
56
|
+
sites?: Record<string, string>;
|
|
55
57
|
role: 'feature' | 'fix' | string;
|
|
56
58
|
}[];
|
|
57
59
|
assignee?: string;
|
|
@@ -67,6 +69,12 @@ export type StreamEvent = {
|
|
|
67
69
|
prompt?: string;
|
|
68
70
|
validUntil?: string;
|
|
69
71
|
};
|
|
72
|
+
/**
|
|
73
|
+
* Why a fetch failed, in words worth reading. Node's fetch reports every network-level failure as
|
|
74
|
+
* the same bare `TypeError: fetch failed` and keeps the real reason — ENOTFOUND, ECONNRESET, a
|
|
75
|
+
* timeout, a TLS error — on `cause`, so printing the error alone says nothing about what happened.
|
|
76
|
+
*/
|
|
77
|
+
export declare function describeFailure(e: unknown): string;
|
|
70
78
|
/** The connection failed in a way that waiting cannot fix. */
|
|
71
79
|
export declare class StreamRejected extends Error {
|
|
72
80
|
}
|
package/dist/stream.js
CHANGED
|
@@ -1,4 +1,23 @@
|
|
|
1
1
|
const RETRY_MS = 2000;
|
|
2
|
+
/** A long outage backs off to this, so a dead network is not hammered every two seconds. */
|
|
3
|
+
const MAX_RETRY_MS = 30_000;
|
|
4
|
+
/** While still down, say so this often rather than once per attempt. */
|
|
5
|
+
const STILL_DOWN_EVERY_MS = 60_000;
|
|
6
|
+
/**
|
|
7
|
+
* Why a fetch failed, in words worth reading. Node's fetch reports every network-level failure as
|
|
8
|
+
* the same bare `TypeError: fetch failed` and keeps the real reason — ENOTFOUND, ECONNRESET, a
|
|
9
|
+
* timeout, a TLS error — on `cause`, so printing the error alone says nothing about what happened.
|
|
10
|
+
*/
|
|
11
|
+
export function describeFailure(e) {
|
|
12
|
+
if (!(e instanceof Error))
|
|
13
|
+
return String(e);
|
|
14
|
+
const cause = e.cause;
|
|
15
|
+
if (!(cause instanceof Error))
|
|
16
|
+
return e.message;
|
|
17
|
+
const code = cause.code;
|
|
18
|
+
const detail = code && !cause.message.includes(code) ? `${code} ${cause.message}` : cause.message;
|
|
19
|
+
return `${e.message}: ${detail}`;
|
|
20
|
+
}
|
|
2
21
|
/** The connection failed in a way that waiting cannot fix. */
|
|
3
22
|
export class StreamRejected extends Error {
|
|
4
23
|
}
|
|
@@ -27,6 +46,12 @@ export class EventStream {
|
|
|
27
46
|
* and never has to know the socket went away.
|
|
28
47
|
*/
|
|
29
48
|
async *events(onStatus) {
|
|
49
|
+
// One outage is one story — when it started, how many attempts, the last reason — not a line
|
|
50
|
+
// per two-second retry, which buried everything else in the pane.
|
|
51
|
+
let downSince = 0;
|
|
52
|
+
let attempts = 0;
|
|
53
|
+
let lastReason = '';
|
|
54
|
+
let lastNotice = 0;
|
|
30
55
|
while (!this.stop) {
|
|
31
56
|
try {
|
|
32
57
|
const url = `${this.baseUrl}/events/stream?format=json&ack=manual`;
|
|
@@ -44,7 +69,10 @@ export class EventStream {
|
|
|
44
69
|
}
|
|
45
70
|
if (!res.ok || !res.body)
|
|
46
71
|
throw new Error(`stream ${res.status}`);
|
|
47
|
-
onStatus('listening');
|
|
72
|
+
onStatus(downSince ? `listening (back after ${elapsed(Date.now() - downSince)}, ${attempts} retries)` : 'listening');
|
|
73
|
+
downSince = 0;
|
|
74
|
+
attempts = 0;
|
|
75
|
+
lastReason = '';
|
|
48
76
|
for await (const event of readSse(res.body)) {
|
|
49
77
|
if (this.stop)
|
|
50
78
|
return;
|
|
@@ -58,10 +86,22 @@ export class EventStream {
|
|
|
58
86
|
throw e;
|
|
59
87
|
if (this.stop)
|
|
60
88
|
return; // an abort we asked for is not a fault
|
|
61
|
-
|
|
89
|
+
const reason = describeFailure(e);
|
|
90
|
+
const now = Date.now();
|
|
91
|
+
if (!downSince) {
|
|
92
|
+
downSince = now;
|
|
93
|
+
lastNotice = now;
|
|
94
|
+
onStatus(`stream dropped (${reason}) — retrying`);
|
|
95
|
+
}
|
|
96
|
+
else if (reason !== lastReason || now - lastNotice >= STILL_DOWN_EVERY_MS) {
|
|
97
|
+
lastNotice = now;
|
|
98
|
+
onStatus(`still offline ${elapsed(now - downSince)} (${reason}) — retrying`);
|
|
99
|
+
}
|
|
100
|
+
lastReason = reason;
|
|
101
|
+
attempts++;
|
|
62
102
|
}
|
|
63
103
|
if (!this.stop)
|
|
64
|
-
await sleep(RETRY_MS);
|
|
104
|
+
await sleep(downSince ? Math.min(RETRY_MS * 2 ** Math.max(0, attempts - 1), MAX_RETRY_MS) : RETRY_MS);
|
|
65
105
|
}
|
|
66
106
|
}
|
|
67
107
|
/** Only once the work is done: an event acknowledged early is an event lost on a crash. */
|
|
@@ -99,5 +139,6 @@ async function* readSse(body) {
|
|
|
99
139
|
}
|
|
100
140
|
}
|
|
101
141
|
}
|
|
142
|
+
const elapsed = (ms) => (ms < 60_000 ? `${Math.round(ms / 1000)}s` : `${Math.floor(ms / 60_000)}m${Math.round((ms % 60_000) / 1000)}s`);
|
|
102
143
|
const sleep = (ms) => new Promise((r) => setTimeout(r, ms));
|
|
103
144
|
//# sourceMappingURL=stream.js.map
|
package/dist/stream.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"stream.js","sourceRoot":"","sources":["../src/stream.ts"],"names":[],"mappings":"
|
|
1
|
+
{"version":3,"file":"stream.js","sourceRoot":"","sources":["../src/stream.ts"],"names":[],"mappings":"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"}
|