@openagt/dashboard 0.9.0 → 0.11.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 +9 -17
- package/dist/built-in.d.ts +45 -16
- package/dist/built-in.d.ts.map +1 -1
- package/dist/built-in.js +95 -32
- 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/daemon-runtime.d.ts +12 -12
- package/dist/daemon-runtime.d.ts.map +1 -1
- package/dist/daemon-runtime.js +97 -37
- 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/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 +19 -25
- package/dist/dashboard/rpc-serve.d.ts.map +1 -1
- package/dist/dashboard/rpc-serve.js +11 -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 +52 -81
- package/dist/dashboard/server.js.map +1 -1
- package/dist/dashboard/start-branch.d.ts +32 -0
- package/dist/dashboard/start-branch.d.ts.map +1 -0
- package/dist/dashboard/start-branch.js +47 -0
- package/dist/dashboard/start-branch.js.map +1 -0
- package/dist/dashboard/types.d.ts +6 -14
- 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-9Xb8Ao4p.css +2 -0
- package/dist/dashboard-bundle/assets/main-DTyyQ0jM.js +140 -0
- package/dist/dashboard-bundle/assets/{module-v213KWfH.js → module-DKd-4BTU.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 +2 -1
- package/dist/dashboard-rpc/index.d.ts.map +1 -1
- package/dist/dashboard-rpc/index.js +5 -3
- 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 +1 -1
- package/dist/dashboard-rpc/modules.js +2 -2
- package/dist/dashboard-rpc/modules.js.map +1 -1
- package/dist/dashboard-rpc/projects.d.ts +2 -0
- package/dist/dashboard-rpc/projects.d.ts.map +1 -1
- package/dist/dashboard-rpc/projects.js +10 -2
- 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/skills.d.ts +80 -0
- package/dist/dashboard-rpc/skills.d.ts.map +1 -0
- package/dist/dashboard-rpc/skills.js +84 -0
- package/dist/dashboard-rpc/skills.js.map +1 -0
- 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/ui/switch.d.ts +7 -0
- package/dist/module-types/dashboard/lib/draft-handoff.d.ts +5 -9
- package/dist/module-types/dashboard/module/index.d.ts +46 -0
- package/dist/module-types/src/built-in.d.ts +45 -16
- package/dist/module-types/src/client.d.ts +2 -1
- 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/remote-run.d.ts +77 -32
- package/dist/module-types/src/dashboard/rpc-serve.d.ts +19 -25
- package/dist/module-types/src/dashboard/start-branch.d.ts +31 -0
- package/dist/module-types/src/dashboard/types.d.ts +6 -14
- 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 +2 -1
- package/dist/module-types/src/dashboard-rpc/machines.d.ts +51 -0
- package/dist/module-types/src/dashboard-rpc/modules.d.ts +1 -1
- package/dist/module-types/src/dashboard-rpc/projects.d.ts +2 -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/dashboard-rpc/skills.d.ts +79 -0
- 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/openagent-dir.d.ts +3 -5
- package/dist/module-types/src/project-commands.d.ts +4 -0
- package/dist/module-types/src/project-hooks.d.ts +8 -5
- package/dist/module-types/src/project-modules.d.ts +8 -6
- 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/store/branches.d.ts +1 -1
- package/dist/module-types/src/store/git-host.d.ts +1 -1
- package/dist/module-types/src/store/queue.d.ts +1 -1
- package/dist/module-types/src/store/runs.d.ts +1 -1
- package/dist/module-types/src/store/tickets.d.ts +1 -1
- package/dist/module-types/src/version.d.ts +1 -0
- package/dist/openagent-dir.d.ts +3 -5
- package/dist/openagent-dir.d.ts.map +1 -1
- package/dist/openagent-dir.js +3 -5
- package/dist/openagent-dir.js.map +1 -1
- 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/project-commands.d.ts +4 -0
- package/dist/project-commands.d.ts.map +1 -1
- package/dist/project-commands.js.map +1 -1
- package/dist/project-hooks.d.ts +8 -5
- package/dist/project-hooks.d.ts.map +1 -1
- package/dist/project-hooks.js +9 -6
- package/dist/project-hooks.js.map +1 -1
- package/dist/project-modules.d.ts +8 -6
- package/dist/project-modules.d.ts.map +1 -1
- package/dist/project-modules.js +7 -13
- package/dist/project-modules.js.map +1 -1
- package/dist/project.d.ts.map +1 -1
- package/dist/project.js +1 -2
- package/dist/project.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/store/branches.d.ts +1 -1
- package/dist/store/branches.js +1 -1
- package/dist/store/git-host.d.ts +1 -1
- package/dist/store/git-host.js +2 -2
- package/dist/store/git-host.js.map +1 -1
- package/dist/store/queue.d.ts +1 -1
- package/dist/store/queue.js +1 -1
- package/dist/store/runs.d.ts +1 -1
- package/dist/store/runs.js +1 -1
- package/dist/store/test-branches.d.ts +0 -8
- package/dist/store/test-branches.d.ts.map +1 -1
- package/dist/store/test-branches.js +0 -24
- package/dist/store/test-branches.js.map +1 -1
- package/dist/store/tickets.d.ts +1 -1
- package/dist/store/tickets.js +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 +19 -13
- package/dist/dashboard-bundle/assets/main-B98UOA3_.js +0 -140
- package/dist/dashboard-bundle/assets/main-ByRjbCiA.css +0 -2
- 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
|
@@ -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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"}
|
package/dist/dashboard/server.js
CHANGED
|
@@ -1,10 +1,10 @@
|
|
|
1
1
|
import { createServer } from 'node:http';
|
|
2
|
-
import { timingSafeEqual } from 'node:crypto';
|
|
3
2
|
import { serveClientBundle } from './static.js';
|
|
4
3
|
import { makeRpcMount, RPC_PREFIX, isSameOriginRequest, isExpectedHost } from './rpc-serve.js';
|
|
5
4
|
import { requestPathname } from '../request-path.js';
|
|
6
5
|
import { MODULES_PREFIX, serveModuleFile } from './module-serve.js';
|
|
7
6
|
import { handleRelayRequest, RELAY_PREFIX } from './relay-endpoints.js';
|
|
7
|
+
import { handleScreenRequest, kept, SCREEN_PREFIX } from './screen.js';
|
|
8
8
|
import { BRIDGE_PREFIX, EXPECTED_EXTENSION_VERSION, handleBridgeRequest } from './bridge-endpoints.js';
|
|
9
9
|
import { bridgeQuestions } from './bridge-store.js';
|
|
10
10
|
import { bridgeStarts } from './bridge-starts.js';
|
|
@@ -18,7 +18,6 @@ import { WEB_START_PREFIX, handleWebStartRequest } from './web-start-endpoints.j
|
|
|
18
18
|
* {@link DashboardOptions.onStart} / {@link DashboardOptions.onAddProject}.
|
|
19
19
|
*/
|
|
20
20
|
export function startDashboard(opts) {
|
|
21
|
-
const host = opts.host ?? '127.0.0.1';
|
|
22
21
|
const port = opts.port ?? 4200;
|
|
23
22
|
const clientBundleDir = opts.clientBundleDir;
|
|
24
23
|
// A broken install ships no built bundle (the published package always does), so serve
|
|
@@ -29,7 +28,7 @@ export function startDashboard(opts) {
|
|
|
29
28
|
res.writeHead(503, { 'content-type': 'text/plain' });
|
|
30
29
|
res.end('the dashboard bundle is not installed');
|
|
31
30
|
});
|
|
32
|
-
return listenDashboard(server,
|
|
31
|
+
return listenDashboard(server, port, () => closeServer(server));
|
|
33
32
|
}
|
|
34
33
|
// The usage panel polls for the dashboard's whole life, not just during an agent:
|
|
35
34
|
// it has to show where the account stands while nothing is running (#533).
|
|
@@ -45,17 +44,14 @@ export function startDashboard(opts) {
|
|
|
45
44
|
bridgeBrowser: opts.bridgeBrowser,
|
|
46
45
|
quota,
|
|
47
46
|
models: opts.models,
|
|
48
|
-
}
|
|
49
|
-
// The
|
|
50
|
-
|
|
51
|
-
{ host });
|
|
52
|
-
// The device-to-daemon relay endpoints (#1067): wired only when an events tail is supplied.
|
|
53
|
-
// Fronted by the same token guard as every other route below.
|
|
47
|
+
});
|
|
48
|
+
// The machine-to-daemon relay endpoints (#1067): wired only when an events tail is supplied.
|
|
49
|
+
const localScreen = opts.screen ? kept(opts.screen) : undefined;
|
|
54
50
|
const relayHandlers = opts.relay
|
|
55
|
-
? { start: opts.onStart, tailEvents: opts.relay.tailEvents, ...(opts.relay.rpc ? { rpc: opts.relay.rpc } : {}) }
|
|
51
|
+
? { project: opts.relay.project, start: opts.onStart, tailEvents: opts.relay.tailEvents, ...(opts.relay.rpc ? { rpc: opts.relay.rpc } : {}), ...(localScreen ? { screen: localScreen } : {}) }
|
|
56
52
|
: undefined;
|
|
57
|
-
|
|
58
|
-
//
|
|
53
|
+
const screenHandlers = localScreen ? { local: localScreen, remote: agentId => opts.remote.target(agentId) } : undefined;
|
|
54
|
+
// The browser bridge (#1237). Off unless a token was supplied, and it carries that token itself.
|
|
59
55
|
const bridgeHandlers = opts.bridgeToken
|
|
60
56
|
? {
|
|
61
57
|
token: opts.bridgeToken,
|
|
@@ -95,17 +91,13 @@ export function startDashboard(opts) {
|
|
|
95
91
|
get: id => bridgeStarts().get(id),
|
|
96
92
|
}
|
|
97
93
|
: undefined;
|
|
98
|
-
const token = opts.token;
|
|
99
94
|
const server = createServer((req, res) => {
|
|
100
95
|
const pathname = requestPathname(req);
|
|
101
96
|
if (pathname === undefined) {
|
|
102
97
|
res.writeHead(400, { 'content-type': 'text/plain' }).end('bad request');
|
|
103
98
|
return;
|
|
104
99
|
}
|
|
105
|
-
// The browser bridge (#1237)
|
|
106
|
-
// That guard's browser affordance is a `?token=` redirect meant for a human following a link,
|
|
107
|
-
// which is meaningless to an extension posting JSON; the bridge presents its own bearer token
|
|
108
|
-
// instead, so letting it past here costs nothing and skips a 302 it could not follow.
|
|
100
|
+
// The browser bridge (#1237) presents its own bearer token.
|
|
109
101
|
if (pathname === BRIDGE_PREFIX || pathname.startsWith(`${BRIDGE_PREFIX}/`)) {
|
|
110
102
|
void handleBridgeRequest(req, res, pathname, bridgeHandlers);
|
|
111
103
|
return;
|
|
@@ -114,20 +106,30 @@ export function startDashboard(opts) {
|
|
|
114
106
|
void handleWebStartRequest(req, res, pathname, webStartHandlers);
|
|
115
107
|
return;
|
|
116
108
|
}
|
|
117
|
-
//
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
//
|
|
121
|
-
//
|
|
122
|
-
// the relay carries its own CSRF + DNS-rebinding guard, or a page the user merely visited could
|
|
123
|
-
// POST /_relay/start to start a run (the real device caller sends no Origin and a loopback
|
|
124
|
-
// Host, so both checks pass it; only a browser's cross-origin/rebound request is turned away).
|
|
109
|
+
// The machine relay (#1067): another daemon posts a start here and streams the run's events back.
|
|
110
|
+
// Exactly like the RPC mount below, the relay carries its own CSRF + DNS-rebinding guard, or a
|
|
111
|
+
// page the user merely visited could POST /_relay/start to start a run (a machine's call
|
|
112
|
+
// arrives with no Origin and a loopback Host, so both checks pass it; only a browser's
|
|
113
|
+
// cross-origin/rebound request is turned away).
|
|
125
114
|
if (pathname === RELAY_PREFIX || pathname.startsWith(`${RELAY_PREFIX}/`)) {
|
|
126
|
-
if (!guardBrowserOrigin(req, res
|
|
115
|
+
if (!guardBrowserOrigin(req, res))
|
|
127
116
|
return;
|
|
128
117
|
void handleRelayRequest(req, res, pathname, relayHandlers);
|
|
129
118
|
return;
|
|
130
119
|
}
|
|
120
|
+
// A run's live screen (screen.ts): a click on it acts in the agent's browser, so it carries
|
|
121
|
+
// the same guard as the RPC mount.
|
|
122
|
+
if (pathname === SCREEN_PREFIX || pathname.startsWith(`${SCREEN_PREFIX}/`)) {
|
|
123
|
+
if (!guardBrowserOrigin(req, res) || !guardOwnPageFetch(req, res))
|
|
124
|
+
return;
|
|
125
|
+
void handleScreenRequest(req, res, pathname, screenHandlers).catch(() => {
|
|
126
|
+
if (!res.headersSent)
|
|
127
|
+
res.writeHead(500, { 'content-type': 'text/plain' }).end('the screen failed');
|
|
128
|
+
else
|
|
129
|
+
res.destroy();
|
|
130
|
+
});
|
|
131
|
+
return;
|
|
132
|
+
}
|
|
131
133
|
if (pathname === RPC_PREFIX || pathname.startsWith(`${RPC_PREFIX}/`)) {
|
|
132
134
|
void rpcMount(req, res);
|
|
133
135
|
return;
|
|
@@ -143,21 +145,26 @@ export function startDashboard(opts) {
|
|
|
143
145
|
}
|
|
144
146
|
void serveClientBundle(req, res, clientBundleDir);
|
|
145
147
|
});
|
|
146
|
-
return listenDashboard(server,
|
|
148
|
+
return listenDashboard(server, port, async () => {
|
|
147
149
|
// Stop polling with the server: the poller outlives every agent by design,
|
|
148
150
|
// so nothing else would ever end it.
|
|
149
151
|
quota.stop();
|
|
150
152
|
await closeServer(server);
|
|
151
153
|
});
|
|
152
154
|
}
|
|
155
|
+
/**
|
|
156
|
+
* The one address the dashboard listens on: this computer's own. Whatever opens OpenAgent to a
|
|
157
|
+
* network is a package of its own, which listens there and passes what it lets in on to here.
|
|
158
|
+
*/
|
|
159
|
+
const DASHBOARD_HOST = '127.0.0.1';
|
|
153
160
|
/** Bind the server and resolve a {@link Dashboard} handle; rejects if the port is already taken. */
|
|
154
|
-
function listenDashboard(server,
|
|
161
|
+
function listenDashboard(server, port, close) {
|
|
155
162
|
return new Promise((resolvePromise, rejectPromise) => {
|
|
156
163
|
server.once('error', rejectPromise);
|
|
157
|
-
server.listen(port,
|
|
164
|
+
server.listen(port, DASHBOARD_HOST, () => {
|
|
158
165
|
server.removeListener('error', rejectPromise);
|
|
159
166
|
const address = server.address();
|
|
160
|
-
resolvePromise({ url: `http://${
|
|
167
|
+
resolvePromise({ url: `http://${DASHBOARD_HOST}:${address.port}`, close });
|
|
161
168
|
});
|
|
162
169
|
});
|
|
163
170
|
}
|
|
@@ -167,65 +174,29 @@ function closeServer(server) {
|
|
|
167
174
|
return new Promise(resolvePromise => server.close(() => resolvePromise()));
|
|
168
175
|
}
|
|
169
176
|
/**
|
|
170
|
-
*
|
|
171
|
-
*
|
|
172
|
-
*
|
|
173
|
-
*
|
|
174
|
-
* it has
|
|
177
|
+
* A watched screen makes the run's computer draw pictures, and watching is a plain GET, which a
|
|
178
|
+
* browser sends with no `Origin`. So a screen's routes also read what a browser says of where a
|
|
179
|
+
* request comes from: one another site's page made, and one that is the address bar's own (a
|
|
180
|
+
* download with no end), is turned away. A browser that says nothing (a page reached over plain
|
|
181
|
+
* HTTP on the Wi-Fi) is let by, as before. Returns true to admit; on rejection it has answered 403.
|
|
175
182
|
*/
|
|
176
|
-
function
|
|
177
|
-
|
|
183
|
+
function guardOwnPageFetch(req, res) {
|
|
184
|
+
const site = req.headers['sec-fetch-site'];
|
|
185
|
+
if ((site === undefined || site === 'same-origin') && req.headers['sec-fetch-mode'] !== 'navigate')
|
|
178
186
|
return true;
|
|
179
187
|
res.writeHead(403, { 'content-type': 'text/plain' }).end('forbidden');
|
|
180
188
|
return false;
|
|
181
189
|
}
|
|
182
|
-
/** The cookie a bootstrapped browser carries on every same-origin request (#1051). */
|
|
183
|
-
const DAEMON_COOKIE = 'oa_daemon';
|
|
184
190
|
/**
|
|
185
|
-
* The
|
|
186
|
-
*
|
|
187
|
-
*
|
|
188
|
-
*
|
|
189
|
-
* reach. Returns true to admit the request, false once it has answered (401 or the redirect).
|
|
191
|
+
* The CSRF + DNS-rebinding guard the RPC mount applies, lifted to the machine relay, which
|
|
192
|
+
* dispatches outside it. The relay is state-changing (it starts a run on this machine), so
|
|
193
|
+
* without this a page the user merely visited reaches it. Returns true to admit the request; on rejection
|
|
194
|
+
* it has already answered 403.
|
|
190
195
|
*/
|
|
191
|
-
function
|
|
192
|
-
|
|
193
|
-
const url = new URL(req.url ?? '/', 'http://localhost');
|
|
194
|
-
const queryToken = url.searchParams.get('token');
|
|
195
|
-
if (queryToken !== null && tokensMatch(queryToken, token)) {
|
|
196
|
-
url.searchParams.delete('token');
|
|
197
|
-
const query = url.searchParams.toString();
|
|
198
|
-
res.writeHead(302, {
|
|
199
|
-
// Lax, not Strict: the device-hop (#1052) is a cross-origin top-level nav, and a Strict cookie set on it is withheld from the redirect right after, so the clean path 401s. Lax still rides top-level GET navs; CSRF stays covered by the same-origin check on /_rpc.
|
|
200
|
-
'set-cookie': `${DAEMON_COOKIE}=${token}; HttpOnly; SameSite=Lax; Path=/`,
|
|
201
|
-
location: url.pathname + (query ? `?${query}` : ''),
|
|
202
|
-
});
|
|
203
|
-
res.end();
|
|
204
|
-
return false;
|
|
205
|
-
}
|
|
206
|
-
const cookieToken = readCookie(req.headers.cookie, DAEMON_COOKIE);
|
|
207
|
-
if (cookieToken !== undefined && tokensMatch(cookieToken, token))
|
|
196
|
+
function guardBrowserOrigin(req, res) {
|
|
197
|
+
if (isSameOriginRequest(req) && isExpectedHost(req))
|
|
208
198
|
return true;
|
|
209
|
-
res.writeHead(
|
|
210
|
-
res.end('unauthorized');
|
|
199
|
+
res.writeHead(403, { 'content-type': 'text/plain' }).end('forbidden');
|
|
211
200
|
return false;
|
|
212
201
|
}
|
|
213
|
-
/** Constant-time token compare (#1051). The length check first, since `timingSafeEqual` throws on
|
|
214
|
-
* unequal-length buffers and a length mismatch cannot be a match anyway. */
|
|
215
|
-
function tokensMatch(a, b) {
|
|
216
|
-
const ab = Buffer.from(a);
|
|
217
|
-
const bb = Buffer.from(b);
|
|
218
|
-
return ab.length === bb.length && timingSafeEqual(ab, bb);
|
|
219
|
-
}
|
|
220
|
-
/** One cookie's value out of a `Cookie` header, or `undefined`. */
|
|
221
|
-
function readCookie(header, name) {
|
|
222
|
-
if (!header)
|
|
223
|
-
return undefined;
|
|
224
|
-
for (const part of header.split(';')) {
|
|
225
|
-
const eq = part.indexOf('=');
|
|
226
|
-
if (eq !== -1 && part.slice(0, eq).trim() === name)
|
|
227
|
-
return part.slice(eq + 1).trim();
|
|
228
|
-
}
|
|
229
|
-
return undefined;
|
|
230
|
-
}
|
|
231
202
|
//# sourceMappingURL=server.js.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"server.js","sourceRoot":"","sources":["../../src/dashboard/server.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,YAAY,EAA0D,MAAM,WAAW,CAAA;
|
|
1
|
+
{"version":3,"file":"server.js","sourceRoot":"","sources":["../../src/dashboard/server.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The branch agents start from in the project at `cwd`: origin's default branch as this clone has
|
|
3
|
+
* it, else the branch the folder is on (a project with no remote starts an agent from its last
|
|
4
|
+
* local commit). `ref` is what git is asked, `name` what a person reads. None where the folder is
|
|
5
|
+
* on no branch, or is no repository.
|
|
6
|
+
*/
|
|
7
|
+
export declare function startBranch(cwd: string): Promise<{
|
|
8
|
+
ref: string;
|
|
9
|
+
name: string;
|
|
10
|
+
} | undefined>;
|
|
11
|
+
/** A skill that is in the folder and not on the branch agents start from. */
|
|
12
|
+
export interface Waiting {
|
|
13
|
+
/** The branch it has yet to reach, by name. */
|
|
14
|
+
branch: string;
|
|
15
|
+
/** Whether the branch the folder is on has it: an agent started from that branch does. */
|
|
16
|
+
here: boolean;
|
|
17
|
+
}
|
|
18
|
+
/**
|
|
19
|
+
* Which of the skills `names`, all in the project's folder, are waiting to reach the branch agents
|
|
20
|
+
* start from. A skill is said to be waiting only when that is known: an agent's checkout fetches
|
|
21
|
+
* the remote before it starts, so before a skill is called waiting against a remote branch, this
|
|
22
|
+
* clone's copy of that branch is fetched too (at most once a minute per project), or a pull
|
|
23
|
+
* request merged a moment ago would keep its skill waiting here.
|
|
24
|
+
*/
|
|
25
|
+
export declare function waitingSkills(cwd: string, names: readonly string[]): Promise<{
|
|
26
|
+
start?: {
|
|
27
|
+
ref: string;
|
|
28
|
+
name: string;
|
|
29
|
+
};
|
|
30
|
+
waiting: Map<string, Waiting>;
|
|
31
|
+
}>;
|
|
32
|
+
//# sourceMappingURL=start-branch.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"start-branch.d.ts","sourceRoot":"","sources":["../../src/dashboard/start-branch.ts"],"names":[],"mappings":"AAIA;;;;;GAKG;AACH,wBAAsB,WAAW,CAAC,GAAG,EAAE,MAAM,GAAG,OAAO,CAAC;IAAE,GAAG,EAAE,MAAM,CAAC;IAAC,IAAI,EAAE,MAAM,CAAA;CAAE,GAAG,SAAS,CAAC,CAKjG;AAED,6EAA6E;AAC7E,MAAM,WAAW,OAAO;IACtB,+CAA+C;IAC/C,MAAM,EAAE,MAAM,CAAA;IACd,0FAA0F;IAC1F,IAAI,EAAE,OAAO,CAAA;CACd;AAQD;;;;;;GAMG;AACH,wBAAsB,aAAa,CAAC,GAAG,EAAE,MAAM,EAAE,KAAK,EAAE,SAAS,MAAM,EAAE,GAAG,OAAO,CAAC;IAAE,KAAK,CAAC,EAAE;QAAE,GAAG,EAAE,MAAM,CAAC;QAAC,IAAI,EAAE,MAAM,CAAA;KAAE,CAAC;IAAC,OAAO,EAAE,GAAG,CAAC,MAAM,EAAE,OAAO,CAAC,CAAA;CAAE,CAAC,CAe5J"}
|
|
@@ -0,0 +1,47 @@
|
|
|
1
|
+
import { nodeGitRunner, originDefaultBranch } from '@openagt/agent-data';
|
|
2
|
+
import { skillsOn } from '@openagt/init';
|
|
3
|
+
import { currentBranch } from './git-status.js';
|
|
4
|
+
/**
|
|
5
|
+
* The branch agents start from in the project at `cwd`: origin's default branch as this clone has
|
|
6
|
+
* it, else the branch the folder is on (a project with no remote starts an agent from its last
|
|
7
|
+
* local commit). `ref` is what git is asked, `name` what a person reads. None where the folder is
|
|
8
|
+
* on no branch, or is no repository.
|
|
9
|
+
*/
|
|
10
|
+
export async function startBranch(cwd) {
|
|
11
|
+
const main = await originDefaultBranch(cwd).catch(() => undefined);
|
|
12
|
+
if (main !== undefined)
|
|
13
|
+
return { ref: main, name: main.slice('origin/'.length) };
|
|
14
|
+
const local = await currentBranch(cwd).catch(() => undefined);
|
|
15
|
+
return local !== undefined && local !== 'HEAD' ? { ref: 'HEAD', name: local } : undefined;
|
|
16
|
+
}
|
|
17
|
+
/** How long this clone's copy of the remote's default branch counts as fresh, per project. */
|
|
18
|
+
const FRESH_MS = 60_000;
|
|
19
|
+
/** How long a read waits for the fetch that freshens it; the fetch itself goes on. */
|
|
20
|
+
const FETCH_WAIT_MS = 5_000;
|
|
21
|
+
const fetched = new Map();
|
|
22
|
+
/**
|
|
23
|
+
* Which of the skills `names`, all in the project's folder, are waiting to reach the branch agents
|
|
24
|
+
* start from. A skill is said to be waiting only when that is known: an agent's checkout fetches
|
|
25
|
+
* the remote before it starts, so before a skill is called waiting against a remote branch, this
|
|
26
|
+
* clone's copy of that branch is fetched too (at most once a minute per project), or a pull
|
|
27
|
+
* request merged a moment ago would keep its skill waiting here.
|
|
28
|
+
*/
|
|
29
|
+
export async function waitingSkills(cwd, names) {
|
|
30
|
+
const start = await startBranch(cwd);
|
|
31
|
+
if (!start || names.length === 0)
|
|
32
|
+
return { ...(start ? { start } : {}), waiting: new Map() };
|
|
33
|
+
let on = await skillsOn(cwd, start.ref);
|
|
34
|
+
let missing = names.filter(name => !on.has(name));
|
|
35
|
+
if (missing.length > 0 && start.ref !== 'HEAD' && Date.now() - (fetched.get(cwd) ?? 0) > FRESH_MS) {
|
|
36
|
+
fetched.set(cwd, Date.now());
|
|
37
|
+
const fetch = nodeGitRunner()(['fetch', '--quiet', 'origin', start.name], cwd).catch(() => undefined);
|
|
38
|
+
await Promise.race([fetch, new Promise(resolve => setTimeout(resolve, FETCH_WAIT_MS))]);
|
|
39
|
+
on = await skillsOn(cwd, start.ref);
|
|
40
|
+
missing = names.filter(name => !on.has(name));
|
|
41
|
+
}
|
|
42
|
+
if (missing.length === 0)
|
|
43
|
+
return { start, waiting: new Map() };
|
|
44
|
+
const here = start.ref === 'HEAD' ? { has: () => false } : await skillsOn(cwd, 'HEAD');
|
|
45
|
+
return { start, waiting: new Map(missing.map(name => [name, { branch: start.name, here: here.has(name) }])) };
|
|
46
|
+
}
|
|
47
|
+
//# sourceMappingURL=start-branch.js.map
|