@openagt/dashboard 0.10.0 → 0.12.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/README.md +1 -6
- package/dist/built-in.d.ts +18 -2
- package/dist/built-in.d.ts.map +1 -1
- package/dist/built-in.js +29 -6
- package/dist/built-in.js.map +1 -1
- package/dist/cli.d.ts +4 -10
- package/dist/cli.d.ts.map +1 -1
- package/dist/cli.js +11 -67
- package/dist/cli.js.map +1 -1
- package/dist/client.d.ts +2 -1
- package/dist/client.d.ts.map +1 -1
- package/dist/client.js +3 -4
- package/dist/client.js.map +1 -1
- package/dist/clone-project.d.ts +69 -0
- package/dist/clone-project.d.ts.map +1 -0
- package/dist/clone-project.js +124 -0
- package/dist/clone-project.js.map +1 -0
- package/dist/daemon-runtime.d.ts +12 -12
- package/dist/daemon-runtime.d.ts.map +1 -1
- package/dist/daemon-runtime.js +96 -35
- package/dist/daemon-runtime.js.map +1 -1
- package/dist/daemon.d.ts +3 -14
- package/dist/daemon.d.ts.map +1 -1
- package/dist/daemon.js +15 -26
- package/dist/daemon.js.map +1 -1
- package/dist/dashboard/bridge-endpoints.d.ts +5 -5
- package/dist/dashboard/bridge-endpoints.js +5 -5
- package/dist/dashboard/http.d.ts +1 -1
- package/dist/dashboard/http.js +1 -1
- package/dist/dashboard/machine-call.d.ts +10 -0
- package/dist/dashboard/machine-call.d.ts.map +1 -0
- package/dist/dashboard/machine-call.js +86 -0
- package/dist/dashboard/machine-call.js.map +1 -0
- package/dist/dashboard/module-serve.d.ts +6 -0
- package/dist/dashboard/module-serve.d.ts.map +1 -1
- package/dist/dashboard/module-serve.js +24 -8
- package/dist/dashboard/module-serve.js.map +1 -1
- package/dist/dashboard/relay-endpoints.d.ts +26 -17
- package/dist/dashboard/relay-endpoints.d.ts.map +1 -1
- package/dist/dashboard/relay-endpoints.js +70 -27
- package/dist/dashboard/relay-endpoints.js.map +1 -1
- package/dist/dashboard/remote-run.d.ts +77 -32
- package/dist/dashboard/remote-run.d.ts.map +1 -1
- package/dist/dashboard/remote-run.js +120 -42
- package/dist/dashboard/remote-run.js.map +1 -1
- package/dist/dashboard/rpc-serve.d.ts +28 -25
- package/dist/dashboard/rpc-serve.d.ts.map +1 -1
- package/dist/dashboard/rpc-serve.js +26 -22
- package/dist/dashboard/rpc-serve.js.map +1 -1
- package/dist/dashboard/screen.d.ts +53 -0
- package/dist/dashboard/screen.d.ts.map +1 -0
- package/dist/dashboard/screen.js +175 -0
- package/dist/dashboard/screen.js.map +1 -0
- package/dist/dashboard/server.d.ts +18 -19
- package/dist/dashboard/server.d.ts.map +1 -1
- package/dist/dashboard/server.js +57 -84
- package/dist/dashboard/server.js.map +1 -1
- package/dist/dashboard/types.d.ts +11 -16
- package/dist/dashboard/types.d.ts.map +1 -1
- package/dist/dashboard/web-start-endpoints.d.ts +2 -2
- package/dist/dashboard/web-start-endpoints.js +1 -1
- package/dist/dashboard-bundle/assets/main-BHMlP40v.css +2 -0
- package/dist/dashboard-bundle/assets/main-D0qZC1r-.js +140 -0
- package/dist/dashboard-bundle/assets/{module-gKz-ZvkG.js → module-ZjyA5WUg.js} +4 -4
- package/dist/dashboard-bundle/host/module.js +1 -1
- package/dist/dashboard-bundle/index.html +3 -3
- package/dist/dashboard-rpc/context.d.ts +4 -4
- package/dist/dashboard-rpc/context.js +5 -5
- package/dist/dashboard-rpc/context.js.map +1 -1
- package/dist/dashboard-rpc/control.d.ts +1 -1
- package/dist/dashboard-rpc/control.js +7 -7
- package/dist/dashboard-rpc/control.js.map +1 -1
- package/dist/dashboard-rpc/events.d.ts +1 -1
- package/dist/dashboard-rpc/events.js +1 -1
- package/dist/dashboard-rpc/index.d.ts +3 -3
- package/dist/dashboard-rpc/index.d.ts.map +1 -1
- package/dist/dashboard-rpc/index.js +5 -5
- package/dist/dashboard-rpc/index.js.map +1 -1
- package/dist/dashboard-rpc/machines.d.ts +52 -0
- package/dist/dashboard-rpc/machines.d.ts.map +1 -0
- package/dist/dashboard-rpc/machines.js +82 -0
- package/dist/dashboard-rpc/machines.js.map +1 -0
- package/dist/dashboard-rpc/modules.d.ts +12 -4
- package/dist/dashboard-rpc/modules.d.ts.map +1 -1
- package/dist/dashboard-rpc/modules.js +24 -5
- package/dist/dashboard-rpc/modules.js.map +1 -1
- package/dist/dashboard-rpc/projects.d.ts +18 -0
- package/dist/dashboard-rpc/projects.d.ts.map +1 -1
- package/dist/dashboard-rpc/projects.js +27 -5
- package/dist/dashboard-rpc/projects.js.map +1 -1
- package/dist/dashboard-rpc/reads.d.ts +3 -4
- package/dist/dashboard-rpc/reads.d.ts.map +1 -1
- package/dist/dashboard-rpc/reads.js +5 -6
- package/dist/dashboard-rpc/reads.js.map +1 -1
- package/dist/dashboard-rpc/relay-agent.d.ts +3 -3
- package/dist/dashboard-rpc/relay-agent.js +3 -3
- package/dist/dashboard-rpc/relay-dispatch.d.ts +4 -4
- package/dist/dashboard-rpc/relay-dispatch.d.ts.map +1 -1
- package/dist/dashboard-rpc/relay-dispatch.js +7 -7
- package/dist/dashboard-rpc/relay-dispatch.js.map +1 -1
- package/dist/dashboard-rpc/test-context.js +1 -1
- package/dist/dashboard-rpc/test-context.js.map +1 -1
- package/dist/events.d.ts +4 -3
- package/dist/events.d.ts.map +1 -1
- package/dist/loopback-host.d.ts +3 -5
- package/dist/loopback-host.d.ts.map +1 -1
- package/dist/loopback-host.js +3 -5
- package/dist/loopback-host.js.map +1 -1
- package/dist/machine-link.d.ts +21 -0
- package/dist/machine-link.d.ts.map +1 -0
- package/dist/machine-link.js +20 -0
- package/dist/machine-link.js.map +1 -0
- package/dist/module-server.d.ts +1 -1
- package/dist/module-types/dashboard/components/QrCode.d.ts +8 -0
- package/dist/module-types/dashboard/lib/draft-handoff.d.ts +5 -9
- package/dist/module-types/dashboard/module/index.d.ts +56 -4
- package/dist/module-types/dashboard/rpc/modules.d.ts +1 -0
- package/dist/module-types/src/built-in.d.ts +18 -2
- package/dist/module-types/src/client.d.ts +2 -1
- package/dist/module-types/src/clone-project.d.ts +68 -0
- package/dist/module-types/src/dashboard/bridge-endpoints.d.ts +5 -5
- package/dist/module-types/src/dashboard/http.d.ts +1 -1
- package/dist/module-types/src/dashboard/machine-call.d.ts +9 -0
- package/dist/module-types/src/dashboard/module-serve.d.ts +6 -0
- package/dist/module-types/src/dashboard/remote-run.d.ts +77 -32
- package/dist/module-types/src/dashboard/rpc-serve.d.ts +28 -25
- package/dist/module-types/src/dashboard/types.d.ts +11 -16
- package/dist/module-types/src/dashboard-rpc/context.d.ts +4 -4
- package/dist/module-types/src/dashboard-rpc/control.d.ts +1 -1
- package/dist/module-types/src/dashboard-rpc/events.d.ts +1 -1
- package/dist/module-types/src/dashboard-rpc/index.d.ts +3 -3
- package/dist/module-types/src/dashboard-rpc/machines.d.ts +51 -0
- package/dist/module-types/src/dashboard-rpc/modules.d.ts +12 -4
- package/dist/module-types/src/dashboard-rpc/projects.d.ts +18 -0
- package/dist/module-types/src/dashboard-rpc/reads.d.ts +3 -4
- package/dist/module-types/src/dashboard-rpc/relay-agent.d.ts +3 -3
- package/dist/module-types/src/events.d.ts +4 -3
- package/dist/module-types/src/loopback-host.d.ts +3 -5
- package/dist/module-types/src/machine-link.d.ts +20 -0
- package/dist/module-types/src/pick-directory.d.ts +2 -0
- package/dist/module-types/src/project-modules.d.ts +12 -3
- package/dist/module-types/src/registry.d.ts +59 -16
- package/dist/module-types/src/repository-address.d.ts +22 -0
- package/dist/module-types/src/screens.d.ts +13 -0
- package/dist/module-types/src/store/agent-store.d.ts +2 -2
- package/dist/module-types/src/version.d.ts +1 -0
- package/dist/package-services.d.ts +27 -0
- package/dist/package-services.d.ts.map +1 -0
- package/dist/package-services.js +62 -0
- package/dist/package-services.js.map +1 -0
- package/dist/pick-directory.d.ts +2 -0
- package/dist/pick-directory.d.ts.map +1 -1
- package/dist/pick-directory.js +7 -1
- package/dist/pick-directory.js.map +1 -1
- package/dist/project-modules.d.ts +12 -3
- package/dist/project-modules.d.ts.map +1 -1
- package/dist/project-modules.js +20 -2
- package/dist/project-modules.js.map +1 -1
- package/dist/registry.d.ts +59 -16
- package/dist/registry.d.ts.map +1 -1
- package/dist/registry.js +91 -20
- package/dist/registry.js.map +1 -1
- package/dist/repository-address.d.ts +23 -0
- package/dist/repository-address.d.ts.map +1 -0
- package/dist/repository-address.js +38 -0
- package/dist/repository-address.js.map +1 -0
- package/dist/screens.d.ts +14 -0
- package/dist/screens.d.ts.map +1 -0
- package/dist/screens.js +40 -0
- package/dist/screens.js.map +1 -0
- package/dist/store/agent-store.d.ts +2 -2
- package/dist/store/agent-store.d.ts.map +1 -1
- package/dist/version.d.ts +2 -0
- package/dist/version.d.ts.map +1 -0
- package/dist/version.js +26 -0
- package/dist/version.js.map +1 -0
- package/package.json +15 -11
- package/dist/dashboard-bundle/assets/main-Q7JMdMJI.css +0 -2
- package/dist/dashboard-bundle/assets/main-fDZWGOub.js +0 -140
- package/dist/dashboard-rpc/devices.d.ts +0 -9
- package/dist/dashboard-rpc/devices.d.ts.map +0 -1
- package/dist/dashboard-rpc/devices.js +0 -12
- package/dist/dashboard-rpc/devices.js.map +0 -1
- package/dist/install.d.ts +0 -31
- package/dist/install.d.ts.map +0 -1
- package/dist/install.js +0 -54
- package/dist/install.js.map +0 -1
- package/dist/module-types/src/dashboard-rpc/devices.d.ts +0 -8
- package/dist/module-types/src/openagent-gitignore.d.ts +0 -11
- package/dist/openagent-gitignore.d.ts +0 -12
- package/dist/openagent-gitignore.d.ts.map +0 -1
- package/dist/openagent-gitignore.js +0 -19
- package/dist/openagent-gitignore.js.map +0 -1
|
@@ -26,6 +26,17 @@ export function isSameOriginRequest(req) {
|
|
|
26
26
|
}
|
|
27
27
|
return isLoopbackHost(hostname);
|
|
28
28
|
}
|
|
29
|
+
/**
|
|
30
|
+
* The third guard: a call says its body is JSON. {@link isSameOriginRequest} lets every loopback
|
|
31
|
+
* origin pass, whatever its port, so a page another program serves on this computer (an app an
|
|
32
|
+
* agent previews, a dev server) is let through it. A browser sends such a page's POST without
|
|
33
|
+
* asking only while its content type is one a plain form can have, and JSON is none of them:
|
|
34
|
+
* with JSON it asks first, this server never says yes, and the call is not sent. So a call that
|
|
35
|
+
* is not JSON is refused, and no page of another origin can make one, not even blind.
|
|
36
|
+
*/
|
|
37
|
+
export function isJsonRequest(req) {
|
|
38
|
+
return /^application\/json\s*(;|$)/i.test(req.headers['content-type'] ?? '');
|
|
39
|
+
}
|
|
29
40
|
/**
|
|
30
41
|
* DNS-rebinding guard, the other half of the CSRF check above. A page on `evil.com` whose DNS
|
|
31
42
|
* re-answers as `127.0.0.1` is *same-origin* with this server as far as the browser is concerned,
|
|
@@ -33,23 +44,14 @@ export function isSameOriginRequest(req) {
|
|
|
33
44
|
* the mount, `sendStart` included, is reachable from a page the user merely visited.
|
|
34
45
|
*
|
|
35
46
|
* The `Host` header is what still gives the attacker away: it carries the name the browser was
|
|
36
|
-
* asked for (`evil.com`), not the address it resolved to.
|
|
37
|
-
* only `Host` a real user's browser can send is a loopback
|
|
38
|
-
* anything else is a rebound name and is rejected. An absent `Host` is rejected too
|
|
39
|
-
*
|
|
40
|
-
*
|
|
41
|
-
* A non-loopback bind (`--host`, #1051) is reached by a hostname we cannot predict, so there is
|
|
42
|
-
* no allowlist to check against; that case gates behind the shared daemon token instead. Hosts
|
|
43
|
-
* that never pass a bind host at all (the relay, which serves a public domain) are unaffected.
|
|
47
|
+
* asked for (`evil.com`), not the address it resolved to. The dashboard listens on this
|
|
48
|
+
* computer's own address only, so the only `Host` a real user's browser can send is a loopback
|
|
49
|
+
* one — anything else is a rebound name and is rejected. An absent `Host` is rejected too:
|
|
50
|
+
* HTTP/1.1 requires it, and every browser sends it.
|
|
44
51
|
*/
|
|
45
|
-
export function isExpectedHost(req
|
|
46
|
-
if (boundHost === undefined || !isLoopbackHost(boundHost))
|
|
47
|
-
return true;
|
|
52
|
+
export function isExpectedHost(req) {
|
|
48
53
|
const header = req.headers.host;
|
|
49
|
-
|
|
50
|
-
return false;
|
|
51
|
-
const hostname = hostnameFromHostHeader(header);
|
|
52
|
-
return isLoopbackHost(hostname) || hostname === boundHost;
|
|
54
|
+
return header !== undefined && header !== '' && isLoopbackHost(hostnameFromHostHeader(header));
|
|
53
55
|
}
|
|
54
56
|
/** Where the dashboard's RPCs live. One prefix, so the static handler can decline it by path. */
|
|
55
57
|
export const RPC_PREFIX = '/_rpc';
|
|
@@ -87,7 +89,7 @@ async function serveEventStream(req, res, url) {
|
|
|
87
89
|
'content-type': 'text/event-stream',
|
|
88
90
|
'cache-control': 'no-store',
|
|
89
91
|
connection: 'keep-alive',
|
|
90
|
-
//
|
|
92
|
+
// What passes a device's request on to the daemon must not hold the lines back.
|
|
91
93
|
'x-accel-buffering': 'no',
|
|
92
94
|
});
|
|
93
95
|
let finished = false;
|
|
@@ -118,17 +120,15 @@ async function serveEventStream(req, res, url) {
|
|
|
118
120
|
* `sendStart` reaches the daemon's own `startAgent` through the wired {@link DashboardContext}.
|
|
119
121
|
*
|
|
120
122
|
* Cross-origin POSTs are rejected (CSRF: a page on evil.com must not steer or start a session), as
|
|
121
|
-
* are requests carrying someone else's `Host`
|
|
122
|
-
*
|
|
123
|
-
* address the server is bound to — to enable that second check. Returns whether the request was
|
|
124
|
-
* the RPC surface's.
|
|
123
|
+
* are requests carrying someone else's `Host` (DNS rebinding: the same page must not reach us by
|
|
124
|
+
* pointing its own name at `127.0.0.1`). Returns whether the request was the RPC surface's.
|
|
125
125
|
*
|
|
126
126
|
* This replaced Telefunc (F3), which required a build-time transform over every `.telefunc.ts`
|
|
127
127
|
* file, a registration table pinning each RPC to the client-baked key of the *dashboard* source
|
|
128
128
|
* path it was re-exported from, and a request-context indirection for wiring that never varied
|
|
129
129
|
* per request. What it bought over this was type-safety across a package boundary that A7 removed.
|
|
130
130
|
*/
|
|
131
|
-
export function makeRpcMount(context
|
|
131
|
+
export function makeRpcMount(context) {
|
|
132
132
|
setDashboardContext(context);
|
|
133
133
|
return async (req, res) => {
|
|
134
134
|
// A fixed base, like every other request parse here: only the path and the query are read,
|
|
@@ -142,7 +142,7 @@ export function makeRpcMount(context, opts = {}) {
|
|
|
142
142
|
res.end('cross-origin request forbidden');
|
|
143
143
|
return true;
|
|
144
144
|
}
|
|
145
|
-
if (!isExpectedHost(req
|
|
145
|
+
if (!isExpectedHost(req)) {
|
|
146
146
|
res.writeHead(403, { 'content-type': 'text/plain' });
|
|
147
147
|
res.end('unexpected Host header');
|
|
148
148
|
return true;
|
|
@@ -158,6 +158,10 @@ export function makeRpcMount(context, opts = {}) {
|
|
|
158
158
|
sendJson(res, 404, { error: `no such RPC: ${name}` });
|
|
159
159
|
return true;
|
|
160
160
|
}
|
|
161
|
+
if (!isJsonRequest(req)) {
|
|
162
|
+
sendJson(res, 415, { error: 'a call is sent as application/json' });
|
|
163
|
+
return true;
|
|
164
|
+
}
|
|
161
165
|
const raw = await readBody(req);
|
|
162
166
|
const args = raw ? JSON.parse(raw) : [];
|
|
163
167
|
if (!Array.isArray(args)) {
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"rpc-serve.js","sourceRoot":"","sources":["../../src/dashboard/rpc-serve.ts"],"names":[],"mappings":"AACA,OAAO,EAAE,sBAAsB,EAAE,cAAc,EAAE,MAAM,qBAAqB,CAAA;AAC5E,OAAO,EAAE,mBAAmB,EAAE,MAAM,6BAA6B,CAAA;AACjE,OAAO,EAAE,YAAY,EAAE,gBAAgB,EAAE,MAAM,2BAA2B,CAAA;AAC1E,OAAO,EAAE,YAAY,EAAE,MAAM,qBAAqB,CAAA;
|
|
1
|
+
{"version":3,"file":"rpc-serve.js","sourceRoot":"","sources":["../../src/dashboard/rpc-serve.ts"],"names":[],"mappings":"AACA,OAAO,EAAE,sBAAsB,EAAE,cAAc,EAAE,MAAM,qBAAqB,CAAA;AAC5E,OAAO,EAAE,mBAAmB,EAAE,MAAM,6BAA6B,CAAA;AACjE,OAAO,EAAE,YAAY,EAAE,gBAAgB,EAAE,MAAM,2BAA2B,CAAA;AAC1E,OAAO,EAAE,YAAY,EAAE,MAAM,qBAAqB,CAAA;AAuElD;;;;;;;GAOG;AACH,MAAM,UAAU,mBAAmB,CAAC,GAAoB;IACtD,MAAM,MAAM,GAAG,GAAG,CAAC,OAAO,CAAC,MAAM,CAAA;IACjC,IAAI,CAAC,MAAM;QAAE,OAAO,IAAI,CAAA;IACxB,MAAM,IAAI,GAAG,GAAG,CAAC,OAAO,CAAC,IAAI,CAAA;IAC7B,IAAI,IAAI,IAAI,CAAC,MAAM,KAAK,UAAU,IAAI,EAAE,IAAI,MAAM,KAAK,WAAW,IAAI,EAAE,CAAC;QAAE,OAAO,IAAI,CAAA;IACtF,IAAI,QAAgB,CAAA;IACpB,IAAI,CAAC;QACH,QAAQ,GAAG,IAAI,GAAG,CAAC,MAAM,CAAC,CAAC,QAAQ,CAAA;IACrC,CAAC;IAAC,MAAM,CAAC;QACP,OAAO,KAAK,CAAA,CAAC,0CAA0C;IACzD,CAAC;IACD,OAAO,cAAc,CAAC,QAAQ,CAAC,CAAA;AACjC,CAAC;AAED;;;;;;;GAOG;AACH,MAAM,UAAU,aAAa,CAAC,GAAoB;IAChD,OAAO,6BAA6B,CAAC,IAAI,CAAC,GAAG,CAAC,OAAO,CAAC,cAAc,CAAC,IAAI,EAAE,CAAC,CAAA;AAC9E,CAAC;AAED;;;;;;;;;;;GAWG;AACH,MAAM,UAAU,cAAc,CAAC,GAAoB;IACjD,MAAM,MAAM,GAAG,GAAG,CAAC,OAAO,CAAC,IAAI,CAAA;IAC/B,OAAO,MAAM,KAAK,SAAS,IAAI,MAAM,KAAK,EAAE,IAAI,cAAc,CAAC,sBAAsB,CAAC,MAAM,CAAC,CAAC,CAAA;AAChG,CAAC;AAED,iGAAiG;AACjG,MAAM,CAAC,MAAM,UAAU,GAAG,OAAO,CAAA;AAEjC,gGAAgG;AAChG,KAAK,UAAU,QAAQ,CAAC,GAAoB,EAAE,KAAK,GAAG,CAAC,GAAG,IAAI,GAAG,IAAI;IACnE,IAAI,IAAI,GAAG,CAAC,CAAA;IACZ,MAAM,MAAM,GAAa,EAAE,CAAA;IAC3B,IAAI,KAAK,EAAE,MAAM,KAAK,IAAI,GAAG,EAAE,CAAC;QAC9B,MAAM,GAAG,GAAG,KAAe,CAAA;QAC3B,IAAI,IAAI,GAAG,CAAC,MAAM,CAAA;QAClB,IAAI,IAAI,GAAG,KAAK;YAAE,MAAM,IAAI,KAAK,CAAC,wBAAwB,CAAC,CAAA;QAC3D,MAAM,CAAC,IAAI,CAAC,GAAG,CAAC,CAAA;IAClB,CAAC;IACD,OAAO,MAAM,CAAC,MAAM,CAAC,MAAM,CAAC,CAAC,QAAQ,CAAC,MAAM,CAAC,CAAA;AAC/C,CAAC;AAED,SAAS,QAAQ,CAAC,GAAmB,EAAE,MAAc,EAAE,IAAa;IAClE,MAAM,IAAI,GAAG,IAAI,CAAC,SAAS,CAAC,IAAI,CAAC,CAAA;IACjC,GAAG,CAAC,SAAS,CAAC,MAAM,EAAE,EAAE,cAAc,EAAE,kBAAkB,EAAE,eAAe,EAAE,UAAU,EAAE,CAAC,CAAA;IAC1F,GAAG,CAAC,GAAG,CAAC,IAAI,CAAC,CAAA;AACf,CAAC;AAED;;;;;;;;GAQG;AACH,KAAK,UAAU,gBAAgB,CAAC,GAAoB,EAAE,GAAmB,EAAE,GAAQ;IACjF,MAAM,SAAS,GAAG,GAAG,CAAC,YAAY,CAAC,GAAG,CAAC,WAAW,CAAC,IAAI,EAAE,CAAA;IACzD,MAAM,OAAO,GAAG,GAAG,CAAC,YAAY,CAAC,GAAG,CAAC,SAAS,CAAC,IAAI,SAAS,CAAA;IAC5D,GAAG,CAAC,SAAS,CAAC,GAAG,EAAE;QACjB,cAAc,EAAE,mBAAmB;QACnC,eAAe,EAAE,UAAU;QAC3B,UAAU,EAAE,YAAY;QACxB,gFAAgF;QAChF,mBAAmB,EAAE,IAAI;KAC1B,CAAC,CAAA;IACF,IAAI,QAAQ,GAAG,KAAK,CAAA;IACpB,MAAM,IAAI,GAAG,MAAM,gBAAgB,CACjC,SAAS,EACT,OAAO,EACP,KAAK,CAAC,EAAE;QACN,GAAG,CAAC,KAAK,CAAC,SAAS,IAAI,CAAC,SAAS,CAAC,KAAK,CAAC,MAAM,CAAC,CAAA;IACjD,CAAC,EACD,GAAG,EAAE;QACH,QAAQ,GAAG,IAAI,CAAA;QACf,GAAG,CAAC,GAAG,EAAE,CAAA;IACX,CAAC,CACF,CAAA;IACD,IAAI,CAAC,IAAI,EAAE,CAAC;QACV,0FAA0F;QAC1F,8EAA8E;QAC9E,GAAG,CAAC,GAAG,EAAE,CAAA;QACT,OAAM;IACR,CAAC;IACD,IAAI,QAAQ;QAAE,OAAM,CAAC,oDAAoD;IACzE,MAAM,MAAM,GAAG,GAAS,EAAE;QACxB,IAAI,EAAE,CAAA;QACN,GAAG,CAAC,GAAG,EAAE,CAAA;IACX,CAAC,CAAA;IACD,GAAG,CAAC,EAAE,CAAC,OAAO,EAAE,MAAM,CAAC,CAAA;IACvB,GAAG,CAAC,EAAE,CAAC,OAAO,EAAE,MAAM,CAAC,CAAA;AACzB,CAAC;AAED;;;;;;;;;;;;;GAaG;AACH,MAAM,UAAU,YAAY,CAAC,OAAyB;IACpD,mBAAmB,CAAC,OAAO,CAAC,CAAA;IAC5B,OAAO,KAAK,EAAE,GAAG,EAAE,GAAG,EAAE,EAAE;QACxB,2FAA2F;QAC3F,8FAA8F;QAC9F,4FAA4F;QAC5F,MAAM,GAAG,GAAG,IAAI,GAAG,CAAC,GAAG,CAAC,GAAG,IAAI,GAAG,EAAE,kBAAkB,CAAC,CAAA;QACvD,IAAI,GAAG,CAAC,QAAQ,KAAK,UAAU,IAAI,CAAC,GAAG,CAAC,QAAQ,CAAC,UAAU,CAAC,GAAG,UAAU,GAAG,CAAC;YAAE,OAAO,KAAK,CAAA;QAC3F,IAAI,CAAC,mBAAmB,CAAC,GAAG,CAAC,EAAE,CAAC;YAC9B,GAAG,CAAC,SAAS,CAAC,GAAG,EAAE,EAAE,cAAc,EAAE,YAAY,EAAE,CAAC,CAAA;YACpD,GAAG,CAAC,GAAG,CAAC,gCAAgC,CAAC,CAAA;YACzC,OAAO,IAAI,CAAA;QACb,CAAC;QACD,IAAI,CAAC,cAAc,CAAC,GAAG,CAAC,EAAE,CAAC;YACzB,GAAG,CAAC,SAAS,CAAC,GAAG,EAAE,EAAE,cAAc,EAAE,YAAY,EAAE,CAAC,CAAA;YACpD,GAAG,CAAC,GAAG,CAAC,wBAAwB,CAAC,CAAA;YACjC,OAAO,IAAI,CAAA;QACb,CAAC;QAED,MAAM,IAAI,GAAG,GAAG,CAAC,QAAQ,CAAC,KAAK,CAAC,UAAU,CAAC,MAAM,GAAG,CAAC,CAAC,CAAA;QACtD,IAAI,CAAC;YACH,IAAI,GAAG,CAAC,MAAM,KAAK,KAAK,IAAI,IAAI,KAAK,QAAQ,EAAE,CAAC;gBAC9C,MAAM,gBAAgB,CAAC,GAAG,EAAE,GAAG,EAAE,GAAG,CAAC,CAAA;gBACrC,OAAO,IAAI,CAAA;YACb,CAAC;YACD,MAAM,OAAO,GAAG,YAAY,CAAC,IAAI,CAAC,CAAA;YAClC,IAAI,GAAG,CAAC,MAAM,KAAK,MAAM,IAAI,CAAC,OAAO,EAAE,CAAC;gBACtC,QAAQ,CAAC,GAAG,EAAE,GAAG,EAAE,EAAE,KAAK,EAAE,gBAAgB,IAAI,EAAE,EAAE,CAAC,CAAA;gBACrD,OAAO,IAAI,CAAA;YACb,CAAC;YACD,IAAI,CAAC,aAAa,CAAC,GAAG,CAAC,EAAE,CAAC;gBACxB,QAAQ,CAAC,GAAG,EAAE,GAAG,EAAE,EAAE,KAAK,EAAE,oCAAoC,EAAE,CAAC,CAAA;gBACnE,OAAO,IAAI,CAAA;YACb,CAAC;YACD,MAAM,GAAG,GAAG,MAAM,QAAQ,CAAC,GAAG,CAAC,CAAA;YAC/B,MAAM,IAAI,GAAc,GAAG,CAAC,CAAC,CAAE,IAAI,CAAC,KAAK,CAAC,GAAG,CAAe,CAAC,CAAC,CAAC,EAAE,CAAA;YACjE,IAAI,CAAC,KAAK,CAAC,OAAO,CAAC,IAAI,CAAC,EAAE,CAAC;gBACzB,QAAQ,CAAC,GAAG,EAAE,GAAG,EAAE,EAAE,KAAK,EAAE,oDAAoD,EAAE,CAAC,CAAA;gBACnF,OAAO,IAAI,CAAA;YACb,CAAC;YACD,QAAQ,CAAC,GAAG,EAAE,GAAG,EAAE,EAAE,GAAG,EAAE,MAAM,OAAO,CAAC,GAAI,IAAgB,CAAC,EAAE,CAAC,CAAA;QAClE,CAAC;QAAC,OAAO,GAAG,EAAE,CAAC;YACb,yFAAyF;YACzF,0EAA0E;YAC1E,IAAI,CAAC,GAAG,CAAC,WAAW;gBAAE,QAAQ,CAAC,GAAG,EAAE,GAAG,EAAE,EAAE,KAAK,EAAE,YAAY,CAAC,GAAG,CAAC,EAAE,CAAC,CAAA;;gBACjE,GAAG,CAAC,GAAG,EAAE,CAAA;QAChB,CAAC;QACD,OAAO,IAAI,CAAA;IACb,CAAC,CAAA;AACH,CAAC"}
|
|
@@ -0,0 +1,53 @@
|
|
|
1
|
+
import type { IncomingMessage, ServerResponse } from 'node:http';
|
|
2
|
+
import type { RemoteTarget } from './remote-run.js';
|
|
3
|
+
/**
|
|
4
|
+
* A run's live screen, for whoever watches the run from this OpenAgent: on this computer, on a
|
|
5
|
+
* phone that got in, or on another computer that follows the run. A `screen` line in the run's
|
|
6
|
+
* diary names where the screen answers, on the loopback of the computer the run is on. That
|
|
7
|
+
* address is reached by nothing but the OpenAgent of that computer, which passes on two calls
|
|
8
|
+
* and no other:
|
|
9
|
+
*
|
|
10
|
+
* - `GET /_screen/stream?project=<id>&run=<id>`: what the screen shows, as the screen's own bytes.
|
|
11
|
+
* - `POST /_screen/input?project=<id>&run=<id>`: what the person did on it, as JSON.
|
|
12
|
+
*
|
|
13
|
+
* For a run this computer follows on another machine, both are asked of that machine
|
|
14
|
+
* (`/_relay/screen/…`), with this computer's key there.
|
|
15
|
+
*
|
|
16
|
+
* What a screen answers is written by whatever the agent ran, and for a run on another machine
|
|
17
|
+
* it comes from that machine. So it is never a page here: the bytes are answered as bytes no
|
|
18
|
+
* browser renders or runs, and the answer to an input is its status alone. What draws the screen
|
|
19
|
+
* is this OpenAgent's own.
|
|
20
|
+
*/
|
|
21
|
+
export declare const SCREEN_PREFIX = "/_screen";
|
|
22
|
+
/** The two calls a screen takes. */
|
|
23
|
+
export type ScreenCall = 'stream' | 'input';
|
|
24
|
+
export interface ScreenHandlers {
|
|
25
|
+
/** Where the live screen of a run on this computer answers; nothing when the run has none. */
|
|
26
|
+
local: (projectId: string, agentId: string) => Promise<string | undefined>;
|
|
27
|
+
/** The machine a run this computer follows is on; nothing for a run of this computer's own. */
|
|
28
|
+
remote: (agentId: string) => RemoteTarget | undefined;
|
|
29
|
+
}
|
|
30
|
+
/** The call a path names, when it names one. */
|
|
31
|
+
export declare function screenCallOf(pathname: string, prefix: string): ScreenCall | undefined;
|
|
32
|
+
/**
|
|
33
|
+
* The address one call to a screen goes to. Only a screen on this computer's own loopback is
|
|
34
|
+
* called, at its own two paths: the line that names it was written by whatever the agent ran.
|
|
35
|
+
*/
|
|
36
|
+
export declare function screenCallAddress(screen: string, call: ScreenCall): string | undefined;
|
|
37
|
+
/**
|
|
38
|
+
* Where the live screen of the run whose diary is the file at `diary` answers; nothing when it
|
|
39
|
+
* has none, or no diary. Only the lines that can say so are read as JSON, a screen's and a run's
|
|
40
|
+
* end: a diary is as long as the run, and this is asked for as long as someone looks.
|
|
41
|
+
*/
|
|
42
|
+
export declare function screenInDiary(diary: string): Promise<string | undefined>;
|
|
43
|
+
/**
|
|
44
|
+
* `find`, asked at most once in a while for the same run, and once for all who ask at the same
|
|
45
|
+
* moment: where a screen answers changes only when a browser opens or closes. Only a run's own
|
|
46
|
+
* screen is kept: an ask that found none is asked again, and keeps nothing.
|
|
47
|
+
*/
|
|
48
|
+
export declare function kept(find: ScreenHandlers['local'], now?: () => number): ScreenHandlers['local'];
|
|
49
|
+
/** Route a `/_screen/*` request of this computer's own dashboard, or of a device that got in. */
|
|
50
|
+
export declare function handleScreenRequest(req: IncomingMessage, res: ServerResponse, pathname: string, handlers: ScreenHandlers | undefined): Promise<void>;
|
|
51
|
+
/** Pass one call on to the screen at `screen`, on this computer; a 404 when there is none. */
|
|
52
|
+
export declare function passToScreen(req: IncomingMessage, res: ServerResponse, call: ScreenCall, screen: string | undefined): Promise<void>;
|
|
53
|
+
//# sourceMappingURL=screen.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"screen.d.ts","sourceRoot":"","sources":["../../src/dashboard/screen.ts"],"names":[],"mappings":"AACA,OAAO,KAAK,EAAE,eAAe,EAAE,cAAc,EAAE,MAAM,WAAW,CAAA;AAMhE,OAAO,KAAK,EAAE,YAAY,EAAE,MAAM,iBAAiB,CAAA;AAEnD;;;;;;;;;;;;;;;;;GAiBG;AACH,eAAO,MAAM,aAAa,aAAa,CAAA;AAEvC,oCAAoC;AACpC,MAAM,MAAM,UAAU,GAAG,QAAQ,GAAG,OAAO,CAAA;AAE3C,MAAM,WAAW,cAAc;IAC7B,8FAA8F;IAC9F,KAAK,EAAE,CAAC,SAAS,EAAE,MAAM,EAAE,OAAO,EAAE,MAAM,KAAK,OAAO,CAAC,MAAM,GAAG,SAAS,CAAC,CAAA;IAC1E,+FAA+F;IAC/F,MAAM,EAAE,CAAC,OAAO,EAAE,MAAM,KAAK,YAAY,GAAG,SAAS,CAAA;CACtD;AAeD,gDAAgD;AAChD,wBAAgB,YAAY,CAAC,QAAQ,EAAE,MAAM,EAAE,MAAM,EAAE,MAAM,GAAG,UAAU,GAAG,SAAS,CAGrF;AAED;;;GAGG;AACH,wBAAgB,iBAAiB,CAAC,MAAM,EAAE,MAAM,EAAE,IAAI,EAAE,UAAU,GAAG,MAAM,GAAG,SAAS,CAStF;AAED;;;;GAIG;AACH,wBAAsB,aAAa,CAAC,KAAK,EAAE,MAAM,GAAG,OAAO,CAAC,MAAM,GAAG,SAAS,CAAC,CAW9E;AAKD;;;;GAIG;AACH,wBAAgB,IAAI,CAAC,IAAI,EAAE,cAAc,CAAC,OAAO,CAAC,EAAE,GAAG,GAAE,MAAM,MAAiB,GAAG,cAAc,CAAC,OAAO,CAAC,CAmBzG;AAED,iGAAiG;AACjG,wBAAsB,mBAAmB,CAAC,GAAG,EAAE,eAAe,EAAE,GAAG,EAAE,cAAc,EAAE,QAAQ,EAAE,MAAM,EAAE,QAAQ,EAAE,cAAc,GAAG,SAAS,GAAG,OAAO,CAAC,IAAI,CAAC,CAY1J;AAED,8FAA8F;AAC9F,wBAAsB,YAAY,CAAC,GAAG,EAAE,eAAe,EAAE,GAAG,EAAE,cAAc,EAAE,IAAI,EAAE,UAAU,EAAE,MAAM,EAAE,MAAM,GAAG,SAAS,GAAG,OAAO,CAAC,IAAI,CAAC,CAIzI"}
|
|
@@ -0,0 +1,175 @@
|
|
|
1
|
+
import { readFile } from 'node:fs/promises';
|
|
2
|
+
import { Readable } from 'node:stream';
|
|
3
|
+
import { eventsOf, isRunId } from '../store/index.js';
|
|
4
|
+
import { liveScreen } from '../screens.js';
|
|
5
|
+
import { end, readJsonBody } from './http.js';
|
|
6
|
+
import { machineFetch } from './machine-call.js';
|
|
7
|
+
/**
|
|
8
|
+
* A run's live screen, for whoever watches the run from this OpenAgent: on this computer, on a
|
|
9
|
+
* phone that got in, or on another computer that follows the run. A `screen` line in the run's
|
|
10
|
+
* diary names where the screen answers, on the loopback of the computer the run is on. That
|
|
11
|
+
* address is reached by nothing but the OpenAgent of that computer, which passes on two calls
|
|
12
|
+
* and no other:
|
|
13
|
+
*
|
|
14
|
+
* - `GET /_screen/stream?project=<id>&run=<id>`: what the screen shows, as the screen's own bytes.
|
|
15
|
+
* - `POST /_screen/input?project=<id>&run=<id>`: what the person did on it, as JSON.
|
|
16
|
+
*
|
|
17
|
+
* For a run this computer follows on another machine, both are asked of that machine
|
|
18
|
+
* (`/_relay/screen/…`), with this computer's key there.
|
|
19
|
+
*
|
|
20
|
+
* What a screen answers is written by whatever the agent ran, and for a run on another machine
|
|
21
|
+
* it comes from that machine. So it is never a page here: the bytes are answered as bytes no
|
|
22
|
+
* browser renders or runs, and the answer to an input is its status alone. What draws the screen
|
|
23
|
+
* is this OpenAgent's own.
|
|
24
|
+
*/
|
|
25
|
+
export const SCREEN_PREFIX = '/_screen';
|
|
26
|
+
/** A person's click or a few typed letters, generously. */
|
|
27
|
+
const MAX_INPUT = 16 * 1024;
|
|
28
|
+
/** How long a screen gets to take an input. */
|
|
29
|
+
const INPUT_TIMEOUT_MS = 15_000;
|
|
30
|
+
/** The headers of every answer: bytes, kept by nobody, that no browser takes for a page or runs. */
|
|
31
|
+
const INERT = {
|
|
32
|
+
'content-type': 'application/octet-stream',
|
|
33
|
+
'cache-control': 'no-store',
|
|
34
|
+
'x-content-type-options': 'nosniff',
|
|
35
|
+
'content-security-policy': "sandbox; default-src 'none'",
|
|
36
|
+
};
|
|
37
|
+
/** The call a path names, when it names one. */
|
|
38
|
+
export function screenCallOf(pathname, prefix) {
|
|
39
|
+
const rest = pathname.slice(prefix.length + 1);
|
|
40
|
+
return pathname.startsWith(`${prefix}/`) && (rest === 'stream' || rest === 'input') ? rest : undefined;
|
|
41
|
+
}
|
|
42
|
+
/**
|
|
43
|
+
* The address one call to a screen goes to. Only a screen on this computer's own loopback is
|
|
44
|
+
* called, at its own two paths: the line that names it was written by whatever the agent ran.
|
|
45
|
+
*/
|
|
46
|
+
export function screenCallAddress(screen, call) {
|
|
47
|
+
let at;
|
|
48
|
+
try {
|
|
49
|
+
at = new URL(screen);
|
|
50
|
+
}
|
|
51
|
+
catch {
|
|
52
|
+
return undefined;
|
|
53
|
+
}
|
|
54
|
+
if (at.protocol !== 'http:' || at.hostname !== '127.0.0.1' || !at.port || at.pathname !== '/')
|
|
55
|
+
return undefined;
|
|
56
|
+
return `http://127.0.0.1:${at.port}/${call}${at.search}`;
|
|
57
|
+
}
|
|
58
|
+
/**
|
|
59
|
+
* Where the live screen of the run whose diary is the file at `diary` answers; nothing when it
|
|
60
|
+
* has none, or no diary. Only the lines that can say so are read as JSON, a screen's and a run's
|
|
61
|
+
* end: a diary is as long as the run, and this is asked for as long as someone looks.
|
|
62
|
+
*/
|
|
63
|
+
export async function screenInDiary(diary) {
|
|
64
|
+
const text = await readFile(diary, 'utf8').catch(() => '');
|
|
65
|
+
const lines = text.split('\n').flatMap(line => {
|
|
66
|
+
if (!line.includes('"screen"') && !line.includes('"ended"'))
|
|
67
|
+
return [];
|
|
68
|
+
try {
|
|
69
|
+
return [JSON.parse(line)];
|
|
70
|
+
}
|
|
71
|
+
catch {
|
|
72
|
+
return [];
|
|
73
|
+
}
|
|
74
|
+
});
|
|
75
|
+
return liveScreen(eventsOf(lines));
|
|
76
|
+
}
|
|
77
|
+
/** How long an answer of {@link kept} is good for: a person's clicks come in bursts, and each would read the whole diary. */
|
|
78
|
+
const KEPT_MS = 2_000;
|
|
79
|
+
/**
|
|
80
|
+
* `find`, asked at most once in a while for the same run, and once for all who ask at the same
|
|
81
|
+
* moment: where a screen answers changes only when a browser opens or closes. Only a run's own
|
|
82
|
+
* screen is kept: an ask that found none is asked again, and keeps nothing.
|
|
83
|
+
*/
|
|
84
|
+
export function kept(find, now = Date.now) {
|
|
85
|
+
const answers = new Map();
|
|
86
|
+
return (projectId, agentId) => {
|
|
87
|
+
const key = `${projectId}\n${agentId}`;
|
|
88
|
+
const answer = answers.get(key);
|
|
89
|
+
if (answer && answer.until > now())
|
|
90
|
+
return answer.screen;
|
|
91
|
+
for (const [old, { until }] of answers)
|
|
92
|
+
if (until <= now())
|
|
93
|
+
answers.delete(old);
|
|
94
|
+
const screen = find(projectId, agentId);
|
|
95
|
+
answers.set(key, { screen, until: now() + KEPT_MS });
|
|
96
|
+
void screen.then(found => {
|
|
97
|
+
if (found === undefined && answers.get(key)?.screen === screen)
|
|
98
|
+
answers.delete(key);
|
|
99
|
+
}, () => {
|
|
100
|
+
if (answers.get(key)?.screen === screen)
|
|
101
|
+
answers.delete(key);
|
|
102
|
+
});
|
|
103
|
+
return screen;
|
|
104
|
+
};
|
|
105
|
+
}
|
|
106
|
+
/** Route a `/_screen/*` request of this computer's own dashboard, or of a device that got in. */
|
|
107
|
+
export async function handleScreenRequest(req, res, pathname, handlers) {
|
|
108
|
+
const call = screenCallOf(pathname, SCREEN_PREFIX);
|
|
109
|
+
if (!handlers || !call)
|
|
110
|
+
return end(res, 404, 'not found');
|
|
111
|
+
const query = new URL(req.url ?? '/', 'http://localhost').searchParams;
|
|
112
|
+
const projectId = query.get('project');
|
|
113
|
+
const agentId = query.get('run');
|
|
114
|
+
if (!projectId || !agentId)
|
|
115
|
+
return end(res, 400, 'missing project or run');
|
|
116
|
+
if (!isRunId(agentId))
|
|
117
|
+
return end(res, 400, 'not a run');
|
|
118
|
+
const remote = handlers.remote(agentId);
|
|
119
|
+
if (!remote)
|
|
120
|
+
return passToScreen(req, res, call, await handlers.local(projectId, agentId).catch(() => undefined));
|
|
121
|
+
const at = `${remote.url.replace(/\/+$/, '')}/_relay/screen/${call}?run=${encodeURIComponent(agentId)}&project=${encodeURIComponent(remote.project)}`;
|
|
122
|
+
await pass(req, res, call, init => machineFetch(at, remote.pin, { ...init, headers: { ...init.headers, authorization: `Bearer ${remote.key}` } }));
|
|
123
|
+
}
|
|
124
|
+
/** Pass one call on to the screen at `screen`, on this computer; a 404 when there is none. */
|
|
125
|
+
export async function passToScreen(req, res, call, screen) {
|
|
126
|
+
const at = screen === undefined ? undefined : screenCallAddress(screen, call);
|
|
127
|
+
if (at === undefined)
|
|
128
|
+
return end(res, 404, 'no screen');
|
|
129
|
+
await pass(req, res, call, init => fetch(at, { ...init, redirect: 'error' }));
|
|
130
|
+
}
|
|
131
|
+
/** Make the call with `ask`, and answer what it came to: a stream as inert bytes, an input as its status alone. */
|
|
132
|
+
async function pass(req, res, call, ask) {
|
|
133
|
+
const method = call === 'stream' ? 'GET' : 'POST';
|
|
134
|
+
if (req.method !== method)
|
|
135
|
+
return end(res, 405, 'method not allowed', { allow: method });
|
|
136
|
+
// Whoever asked going away ends the call: a screen nobody watches is asked for nothing.
|
|
137
|
+
const gone = new AbortController();
|
|
138
|
+
res.once('close', () => gone.abort());
|
|
139
|
+
if (call === 'input') {
|
|
140
|
+
let body;
|
|
141
|
+
try {
|
|
142
|
+
body = await readJsonBody(req, MAX_INPUT);
|
|
143
|
+
}
|
|
144
|
+
catch {
|
|
145
|
+
return end(res, 400, 'invalid request body');
|
|
146
|
+
}
|
|
147
|
+
const answer = await ask({
|
|
148
|
+
method,
|
|
149
|
+
headers: { 'content-type': 'application/json' },
|
|
150
|
+
body: JSON.stringify(body),
|
|
151
|
+
signal: AbortSignal.any([gone.signal, AbortSignal.timeout(INPUT_TIMEOUT_MS)]),
|
|
152
|
+
}).catch(() => undefined);
|
|
153
|
+
void answer?.body?.cancel().catch(() => { });
|
|
154
|
+
if (!answer)
|
|
155
|
+
return end(res, 502, 'the screen did not answer');
|
|
156
|
+
if (answer.status === 404)
|
|
157
|
+
return end(res, 404, 'no screen');
|
|
158
|
+
return end(res, answer.ok ? 204 : answer.status === 400 ? 400 : 502, '');
|
|
159
|
+
}
|
|
160
|
+
const answer = await ask({ method, headers: {}, signal: gone.signal }).catch(() => undefined);
|
|
161
|
+
if (res.destroyed)
|
|
162
|
+
return void answer?.body?.cancel().catch(() => { });
|
|
163
|
+
if (!answer?.ok || !answer.body) {
|
|
164
|
+
void answer?.body?.cancel().catch(() => { });
|
|
165
|
+
return end(res, answer?.status === 404 ? 404 : 502, answer?.status === 404 ? 'no screen' : 'the screen did not answer');
|
|
166
|
+
}
|
|
167
|
+
res.writeHead(200, INERT);
|
|
168
|
+
// The first picture may be a while away: the headers go now.
|
|
169
|
+
res.flushHeaders();
|
|
170
|
+
const bytes = Readable.fromWeb(answer.body);
|
|
171
|
+
// A screen that went away mid-stream ends the answer, so the watcher asks again instead of waiting for ever.
|
|
172
|
+
bytes.once('error', () => res.destroy());
|
|
173
|
+
bytes.pipe(res);
|
|
174
|
+
}
|
|
175
|
+
//# sourceMappingURL=screen.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"screen.js","sourceRoot":"","sources":["../../src/dashboard/screen.ts"],"names":[],"mappings":"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"}
|
|
@@ -9,8 +9,6 @@ import type { EventsSource, RemoteAgents } from './rpc-serve.js';
|
|
|
9
9
|
export interface DashboardOptions {
|
|
10
10
|
/** Port to bind. Default `4200`; pass `0` for an ephemeral port. */
|
|
11
11
|
port?: number;
|
|
12
|
-
/** Host to bind. Default `127.0.0.1` (localhost only). */
|
|
13
|
-
host?: string;
|
|
14
12
|
/**
|
|
15
13
|
* Called when the browser starts a run (#345): the `sendStart` RPC reaches this through the
|
|
16
14
|
* wired dashboard context. Wire it to the project's start hook.
|
|
@@ -44,40 +42,41 @@ export interface DashboardOptions {
|
|
|
44
42
|
*/
|
|
45
43
|
clientBundleDir?: string;
|
|
46
44
|
/**
|
|
47
|
-
* The
|
|
48
|
-
* bundle, `/_rpc`, `/_relay`) needs a valid `oa_daemon` cookie or a matching
|
|
49
|
-
* `?token=`, else 401. Omit for a loopback bind, where the guard is a no-op and local UX is
|
|
50
|
-
* byte-identical. A separate concern from the CSRF origin check in rpc-serve.ts.
|
|
51
|
-
*/
|
|
52
|
-
token?: string;
|
|
53
|
-
/**
|
|
54
|
-
* The live-events source for a run this daemon is relaying from a connected device (#1067):
|
|
45
|
+
* The live-events source for a run this daemon is relaying from a saved machine (#1067):
|
|
55
46
|
* a stream for such a run, else undefined so `onEvents` tails the run's diary off disk.
|
|
56
47
|
*/
|
|
57
48
|
eventsSource: EventsSource;
|
|
58
49
|
/**
|
|
59
50
|
* The relayed-agent lookup the read RPCs consult (#1067 slice 2). A run-scoped RPC uses it to
|
|
60
|
-
* forward a remote agent's read/steer/handoff to the
|
|
51
|
+
* forward a remote agent's read/steer/handoff to the machine that owns it.
|
|
61
52
|
*/
|
|
62
53
|
remote: RemoteAgents;
|
|
63
54
|
/**
|
|
64
55
|
* Serve a relay-started run's events back to the daemon that relayed it here (#1067): the
|
|
65
|
-
* `/_relay/*` endpoints (start + events, plus the slice-2 `rpc`).
|
|
66
|
-
*
|
|
56
|
+
* `/_relay/*` endpoints (start + events, plus the slice-2 `rpc`). The daemon listens on its
|
|
57
|
+
* own computer only, so a call from another machine reaches them through whatever this
|
|
58
|
+
* computer opened to the network, which lets in only a machine that holds a key of its own.
|
|
67
59
|
*/
|
|
68
60
|
relay?: {
|
|
69
|
-
|
|
61
|
+
/** This machine's own id for its project cloned from an address, which is how a relayed call names one. */
|
|
62
|
+
project: (address: string) => Promise<string | undefined>;
|
|
63
|
+
tailEvents: (projectId: string, agentId: string, onEvent: (event: import('../events.js').OpenAgentEvent) => void) => () => void;
|
|
70
64
|
/** Run one whitelisted run-scoped RPC against this daemon's own checkout (#1067 slice 2). */
|
|
71
|
-
rpc?: (fn: string, args: unknown[]) => Promise<unknown>;
|
|
65
|
+
rpc?: (projectId: string, fn: string, args: unknown[]) => Promise<unknown>;
|
|
72
66
|
};
|
|
67
|
+
/**
|
|
68
|
+
* Where the live screen of a run on this computer answers (screen.ts): what the `/_screen/*`
|
|
69
|
+
* routes, and the relay's for another machine, pass a watcher's calls on to. Absent leaves
|
|
70
|
+
* every screen route 404.
|
|
71
|
+
*/
|
|
72
|
+
screen?: (projectId: string, agentId: string) => Promise<string | undefined>;
|
|
73
73
|
/**
|
|
74
74
|
* The browser bridge (#1237): the token a Claude web extension presents to report the question
|
|
75
75
|
* its cloud session is parked on. Absent leaves every `/_bridge/*` route 404, which is default.
|
|
76
76
|
*
|
|
77
|
-
*
|
|
78
|
-
*
|
|
79
|
-
*
|
|
80
|
-
* and it authenticates on its own.
|
|
77
|
+
* What keeps other origins out of `/_rpc` is the same-origin check. The bridge is the one
|
|
78
|
+
* route meant to be reached from another origin, so that check does not protect it and it
|
|
79
|
+
* authenticates on its own.
|
|
81
80
|
*/
|
|
82
81
|
bridgeToken?: string;
|
|
83
82
|
/**
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"server.d.ts","sourceRoot":"","sources":["../../src/dashboard/server.ts"],"names":[],"mappings":"
|
|
1
|
+
{"version":3,"file":"server.d.ts","sourceRoot":"","sources":["../../src/dashboard/server.ts"],"names":[],"mappings":"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"}
|