@openagt/dashboard 0.10.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 +1 -6
- package/dist/built-in.d.ts +8 -1
- package/dist/built-in.d.ts.map +1 -1
- package/dist/built-in.js +12 -3
- 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 +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/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/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-gKz-ZvkG.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 +1 -1
- package/dist/dashboard-rpc/index.d.ts.map +1 -1
- package/dist/dashboard-rpc/index.js +3 -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 +3 -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/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/lib/draft-handoff.d.ts +5 -9
- package/dist/module-types/dashboard/module/index.d.ts +45 -0
- package/dist/module-types/src/built-in.d.ts +8 -1
- 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/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 +1 -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/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/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/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 +13 -10
- 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
|
@@ -3,8 +3,9 @@ import { type PublishPick } from './publish-levels.js';
|
|
|
3
3
|
* The multi-project registry (#390): the list of projects the user has
|
|
4
4
|
* installed OpenAgent into, kept as a single JSON file `.bashrc`-style —
|
|
5
5
|
* `$HOME/.openagent.json` — so it is the user's responsibility to re-create
|
|
6
|
-
* per machine. The same file also holds the user's dashboard preferences (#410)
|
|
7
|
-
* so the daemon owns one user file and the UI never
|
|
6
|
+
* per machine. The same file also holds the user's dashboard preferences (#410)
|
|
7
|
+
* and the machines they saved, so the daemon owns one user file and the UI never
|
|
8
|
+
* needs localStorage.
|
|
8
9
|
*/
|
|
9
10
|
/** One registered project. */
|
|
10
11
|
export interface ProjectRecord {
|
|
@@ -15,6 +16,25 @@ export interface ProjectRecord {
|
|
|
15
16
|
/** ISO timestamp the project was added. */
|
|
16
17
|
addedAt: string;
|
|
17
18
|
}
|
|
19
|
+
/**
|
|
20
|
+
* A saved machine: another computer running OpenAgent that a run can be sent to. Kept in this
|
|
21
|
+
* file, the person's own, so every browser on this computer shows the same machines and the
|
|
22
|
+
* key that reaches one never leaves the two machines.
|
|
23
|
+
*/
|
|
24
|
+
export interface MachineRecord {
|
|
25
|
+
/** The machine's address, which is also what names it: saving the same address again replaces it. */
|
|
26
|
+
id: string;
|
|
27
|
+
/** The name the person gave it, or its host when they gave none. */
|
|
28
|
+
label: string;
|
|
29
|
+
/** Where its OpenAgent answers, as an origin (`http://studio.local:4201`). */
|
|
30
|
+
url: string;
|
|
31
|
+
/** This computer's own key on that machine. Never handed to a browser. */
|
|
32
|
+
key: string;
|
|
33
|
+
/** Where this computer was let in there, and where it takes itself out when the machine is removed. */
|
|
34
|
+
door: string;
|
|
35
|
+
/** What the machine's key is known by, when its link said: every call goes only to a machine that shows that key. */
|
|
36
|
+
pin?: string;
|
|
37
|
+
}
|
|
18
38
|
/**
|
|
19
39
|
* The dashboard's Global options (#410), persisted next to the project list so they
|
|
20
40
|
* survive restarts without localStorage — the daemon reads/writes them, the SPA reads
|
|
@@ -104,11 +124,16 @@ export interface Registry {
|
|
|
104
124
|
projects: ProjectRecord[];
|
|
105
125
|
preferences: Preferences;
|
|
106
126
|
/**
|
|
107
|
-
* The
|
|
108
|
-
* A top-level field, deliberately not a {@link Preferences} one, so it is never shipped
|
|
109
|
-
* browser bundle. Absent on a
|
|
127
|
+
* The browser bridge's key (#1237): generated when the bridge is first switched on and reused
|
|
128
|
+
* after. A top-level field, deliberately not a {@link Preferences} one, so it is never shipped
|
|
129
|
+
* to the browser bundle. Absent on a machine that never had the bridge on.
|
|
110
130
|
*/
|
|
111
|
-
|
|
131
|
+
bridgeToken?: string;
|
|
132
|
+
/**
|
|
133
|
+
* The saved machines. Top-level like {@link bridgeToken}, and for the same reason: each one
|
|
134
|
+
* carries a key, so the list is never part of what the browser is handed.
|
|
135
|
+
*/
|
|
136
|
+
machines?: MachineRecord[];
|
|
112
137
|
}
|
|
113
138
|
/** A read/write handle for the user preferences, wired into the dashboard's context by the daemon. */
|
|
114
139
|
export interface PreferencesStore {
|
|
@@ -122,7 +147,7 @@ export interface PreferencesStore {
|
|
|
122
147
|
}
|
|
123
148
|
/** The registry file name: a single file under `$XDG_CONFIG_HOME` (dotted under `$HOME`). */
|
|
124
149
|
export declare const REGISTRY_FILE = "openagent.json";
|
|
125
|
-
/** Owner read/write only: the file holds the
|
|
150
|
+
/** Owner read/write only: the file holds the bridge's key and the saved machines' keys. */
|
|
126
151
|
export declare const REGISTRY_FILE_MODE = 384;
|
|
127
152
|
/**
|
|
128
153
|
* Deterministic, URL-safe id for a project path: the sanitized basename plus a
|
|
@@ -151,7 +176,7 @@ export interface RegistryFs {
|
|
|
151
176
|
rename?(from: string, to: string): Promise<void>;
|
|
152
177
|
/**
|
|
153
178
|
* Narrow a file's permissions. Optional, and best-effort at the call site: this file holds the
|
|
154
|
-
*
|
|
179
|
+
* bridge's key and the saved machines' keys, so it is written owner-only — but a
|
|
155
180
|
* filesystem that cannot express that (Windows, a FAT volume) must not fail the write.
|
|
156
181
|
*/
|
|
157
182
|
chmod?(path: string, mode: number): Promise<void>;
|
|
@@ -205,15 +230,33 @@ export declare function writePreferences(preferences: Preferences, fs?: Registry
|
|
|
205
230
|
*/
|
|
206
231
|
export declare function patchPreferences(patch: Preferences, fs?: RegistryFs, env?: NodeJS.ProcessEnv): Promise<Preferences>;
|
|
207
232
|
/**
|
|
208
|
-
* The
|
|
209
|
-
* only
|
|
210
|
-
* other mutators so two concurrent
|
|
211
|
-
|
|
233
|
+
* The browser bridge's key (#1237): read the persisted one, or generate + persist it now. Called
|
|
234
|
+
* only with the bridge switched on, so a machine that never had it on never grows one. Serialized
|
|
235
|
+
* with the other mutators so two concurrent starts can't each write a different key.
|
|
236
|
+
*/
|
|
237
|
+
export declare function ensureBridgeToken(fs?: RegistryFs, env?: NodeJS.ProcessEnv): Promise<string>;
|
|
238
|
+
/** The persisted bridge key, or `undefined` when none exists. A pure read: it never generates one. */
|
|
239
|
+
export declare function readBridgeToken(fs?: RegistryFs, env?: NodeJS.ProcessEnv): Promise<string | undefined>;
|
|
240
|
+
/** The saved machines, newest first. Forgiving like every read of this file: none on a bad file. */
|
|
241
|
+
export declare function listMachines(fs?: RegistryFs, env?: NodeJS.ProcessEnv): Promise<MachineRecord[]>;
|
|
242
|
+
/**
|
|
243
|
+
* Save a machine, newest first. One per address: saving an address again replaces its name and
|
|
244
|
+
* its key, which is how a machine whose key changed is fixed. Answers the stored record, and the
|
|
245
|
+
* records it took the place of (the one saved at that address before, the oldest past the cap):
|
|
246
|
+
* each still holds a key the caller takes out of its machine.
|
|
212
247
|
*/
|
|
213
|
-
export declare function
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
|
|
248
|
+
export declare function addMachine(machine: {
|
|
249
|
+
url: string;
|
|
250
|
+
key: string;
|
|
251
|
+
door: string;
|
|
252
|
+
pin?: string;
|
|
253
|
+
label?: string;
|
|
254
|
+
}, fs?: RegistryFs, env?: NodeJS.ProcessEnv): Promise<{
|
|
255
|
+
record: MachineRecord;
|
|
256
|
+
displaced: MachineRecord[];
|
|
257
|
+
}>;
|
|
258
|
+
/** Take a saved machine off the list, by id. Answers the one that was there. */
|
|
259
|
+
export declare function removeMachine(id: string, fs?: RegistryFs, env?: NodeJS.ProcessEnv): Promise<MachineRecord | undefined>;
|
|
217
260
|
/** A {@link PreferencesStore} bound to the real registry file, wired by the daemon so the
|
|
218
261
|
* dashboard's preferences RPCs read/write the user's home file.
|
|
219
262
|
*
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
import { type GitRunner } from '@openagt/agent-data';
|
|
2
|
+
/**
|
|
3
|
+
* The one name a project has on every machine: where its repository was cloned from, as
|
|
4
|
+
* `host/path` (`github.com/me/shop`). A project's id is made from its folder's path, which is
|
|
5
|
+
* another path on another machine, so a run sent to a machine names its project by this.
|
|
6
|
+
*
|
|
7
|
+
* The same repository reads the same whichever way it was cloned: the scheme, a user before the
|
|
8
|
+
* host, a port, a trailing `.git` or slash are dropped, and letters are lowered, since the git
|
|
9
|
+
* hosts match names without regard to case. Not an address at all (a path on this disk, a
|
|
10
|
+
* `file:` address) is none.
|
|
11
|
+
*
|
|
12
|
+
* What it does not see through: a host that serves the two ways under two names or two paths
|
|
13
|
+
* (`ssh.github.com`, Azure DevOps). Such a project reads as two, and the other machine says it
|
|
14
|
+
* does not have it.
|
|
15
|
+
*/
|
|
16
|
+
export declare function addressOfRemote(remote: string): string | undefined;
|
|
17
|
+
/**
|
|
18
|
+
* The address of the repository at `cwd`: its `origin` remote, the one every push goes to. A
|
|
19
|
+
* project with no `origin`, or one that is a folder on this disk, has none and runs on this
|
|
20
|
+
* machine only.
|
|
21
|
+
*/
|
|
22
|
+
export declare function repositoryAddress(cwd: string, git?: GitRunner): Promise<string | undefined>;
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
import type { OpenAgentEvent } from './events.js';
|
|
2
|
+
/**
|
|
3
|
+
* Which of a run's `screen` lines are live and which are hidden. Live: the newest line at its
|
|
4
|
+
* address with no `ended` line for it after, and no run `end` after it other than one waiting on
|
|
5
|
+
* an answer. Every `ended` line is hidden: the screen going is said by its line no longer being
|
|
6
|
+
* live.
|
|
7
|
+
*/
|
|
8
|
+
export declare function foldScreenRows(events: readonly OpenAgentEvent[]): {
|
|
9
|
+
live: Set<OpenAgentEvent>;
|
|
10
|
+
hidden: Set<OpenAgentEvent>;
|
|
11
|
+
};
|
|
12
|
+
/** Where a run's live screen answers: the address of its newest live `screen` line, or nothing. */
|
|
13
|
+
export declare function liveScreen(events: readonly OpenAgentEvent[]): string | undefined;
|
|
@@ -157,12 +157,12 @@ export interface AgentMeta {
|
|
|
157
157
|
browserStreamPort?: number;
|
|
158
158
|
/**
|
|
159
159
|
* Where this run executes (#1050/#1053/#610): `actions` for a GitHub Actions run, `web` for a
|
|
160
|
-
* Claude Code cloud session, `remote` when relayed to a
|
|
160
|
+
* Claude Code cloud session, `remote` when relayed to a saved machine (#1067), absent for a
|
|
161
161
|
* local run. Persisted so the agent view can tell a burst-mode Actions run from a stalled live
|
|
162
162
|
* feed, show a cloud agent's session link after a reload, and gate the browser pane off (#1053).
|
|
163
163
|
*/
|
|
164
164
|
target?: 'local' | 'actions' | 'remote' | 'web';
|
|
165
|
-
/** The
|
|
165
|
+
/** The saved machine a remote agent (#1067) executes on, for the session list + notice after a reload. */
|
|
166
166
|
remoteLabel?: string;
|
|
167
167
|
/**
|
|
168
168
|
* The flow this agent started under (#1467): `build` for the scope→build orchestration, `prompt`
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export declare function openagentVersion(): string;
|
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
import { type ChildProcess } from 'node:child_process';
|
|
2
|
+
import type { ProvidedCommand } from '@openagt/agent-data';
|
|
3
|
+
/**
|
|
4
|
+
* The services of OpenAgent's own packages: each declared command's `serve`, run as a process of
|
|
5
|
+
* its own for as long as OpenAgent runs, and told where OpenAgent is (`OPENAGENT_URL`). OpenAgent
|
|
6
|
+
* knows nothing of what a service does. One that ends by itself is started again after a
|
|
7
|
+
* while; all of them end with OpenAgent. Each is started in a process group of its own, so a
|
|
8
|
+
* Ctrl-C in OpenAgent's terminal reaches OpenAgent alone, which then ends them in its own order.
|
|
9
|
+
*/
|
|
10
|
+
export interface PackageServicesOptions {
|
|
11
|
+
/** The commands to run, each a package's declared service. */
|
|
12
|
+
commands: readonly ProvidedCommand[];
|
|
13
|
+
/** OpenAgent's own address on this computer. */
|
|
14
|
+
url: string;
|
|
15
|
+
env: NodeJS.ProcessEnv;
|
|
16
|
+
log: (line: string) => void;
|
|
17
|
+
/** How a service's process is started; the real one when unsaid. */
|
|
18
|
+
spawn?: (bin: string, env: NodeJS.ProcessEnv) => ChildProcess;
|
|
19
|
+
/** After how long a service that ended by itself is started again. */
|
|
20
|
+
restartMs?: number;
|
|
21
|
+
}
|
|
22
|
+
export interface PackageServices {
|
|
23
|
+
/** End every service, and wait until each is gone. */
|
|
24
|
+
stop(): Promise<void>;
|
|
25
|
+
}
|
|
26
|
+
export declare function startPackageServices(opts: PackageServicesOptions): PackageServices;
|
|
27
|
+
//# sourceMappingURL=package-services.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"package-services.d.ts","sourceRoot":"","sources":["../src/package-services.ts"],"names":[],"mappings":"AAAA,OAAO,EAAsB,KAAK,YAAY,EAAE,MAAM,oBAAoB,CAAA;AAC1E,OAAO,KAAK,EAAE,eAAe,EAAE,MAAM,qBAAqB,CAAA;AAE1D;;;;;;GAMG;AAEH,MAAM,WAAW,sBAAsB;IACrC,8DAA8D;IAC9D,QAAQ,EAAE,SAAS,eAAe,EAAE,CAAA;IACpC,gDAAgD;IAChD,GAAG,EAAE,MAAM,CAAA;IACX,GAAG,EAAE,MAAM,CAAC,UAAU,CAAA;IACtB,GAAG,EAAE,CAAC,IAAI,EAAE,MAAM,KAAK,IAAI,CAAA;IAC3B,oEAAoE;IACpE,KAAK,CAAC,EAAE,CAAC,GAAG,EAAE,MAAM,EAAE,GAAG,EAAE,MAAM,CAAC,UAAU,KAAK,YAAY,CAAA;IAC7D,sEAAsE;IACtE,SAAS,CAAC,EAAE,MAAM,CAAA;CACnB;AAED,MAAM,WAAW,eAAe;IAC9B,sDAAsD;IACtD,IAAI,IAAI,OAAO,CAAC,IAAI,CAAC,CAAA;CACtB;AAKD,wBAAgB,oBAAoB,CAAC,IAAI,EAAE,sBAAsB,GAAG,eAAe,CAwDlF"}
|
|
@@ -0,0 +1,62 @@
|
|
|
1
|
+
import { spawn as nodeSpawn } from 'node:child_process';
|
|
2
|
+
/** How long a service gets to end by itself once asked, before it is killed. */
|
|
3
|
+
const STOP_GRACE_MS = 3_000;
|
|
4
|
+
export function startPackageServices(opts) {
|
|
5
|
+
const spawn = opts.spawn ?? ((bin, env) => nodeSpawn(process.execPath, [bin, 'serve'], { env, stdio: ['ignore', 'ignore', 'pipe'], detached: true }));
|
|
6
|
+
const env = { ...opts.env, OPENAGENT_URL: opts.url };
|
|
7
|
+
let stopping = false;
|
|
8
|
+
const running = new Map();
|
|
9
|
+
const timers = new Set();
|
|
10
|
+
const start = (command) => {
|
|
11
|
+
if (stopping)
|
|
12
|
+
return;
|
|
13
|
+
const child = spawn(command.bin, env);
|
|
14
|
+
running.set(command.package, child);
|
|
15
|
+
child.stderr?.setEncoding('utf8').on('data', (said) => {
|
|
16
|
+
for (const line of said.split('\n'))
|
|
17
|
+
if (line.trim())
|
|
18
|
+
opts.log(line.trim());
|
|
19
|
+
});
|
|
20
|
+
const ended = (why) => {
|
|
21
|
+
if (running.get(command.package) !== child)
|
|
22
|
+
return;
|
|
23
|
+
running.delete(command.package);
|
|
24
|
+
if (stopping)
|
|
25
|
+
return;
|
|
26
|
+
opts.log(`${command.package}: its service ended (${why}); it is started again shortly`);
|
|
27
|
+
const timer = setTimeout(() => {
|
|
28
|
+
timers.delete(timer);
|
|
29
|
+
start(command);
|
|
30
|
+
}, opts.restartMs ?? 5_000);
|
|
31
|
+
timers.add(timer);
|
|
32
|
+
};
|
|
33
|
+
child.once('error', err => ended(err.message));
|
|
34
|
+
child.once('exit', (code, signal) => ended(signal ?? `exit ${code}`));
|
|
35
|
+
};
|
|
36
|
+
for (const command of opts.commands)
|
|
37
|
+
start(command);
|
|
38
|
+
return {
|
|
39
|
+
async stop() {
|
|
40
|
+
stopping = true;
|
|
41
|
+
for (const timer of timers)
|
|
42
|
+
clearTimeout(timer);
|
|
43
|
+
timers.clear();
|
|
44
|
+
await Promise.all([...running.values()].map(child => new Promise(resolve => {
|
|
45
|
+
if (child.exitCode !== null || child.signalCode !== null)
|
|
46
|
+
return resolve();
|
|
47
|
+
const kill = setTimeout(() => child.kill('SIGKILL'), STOP_GRACE_MS);
|
|
48
|
+
child.once('exit', () => {
|
|
49
|
+
clearTimeout(kill);
|
|
50
|
+
resolve();
|
|
51
|
+
});
|
|
52
|
+
child.once('error', () => {
|
|
53
|
+
clearTimeout(kill);
|
|
54
|
+
resolve();
|
|
55
|
+
});
|
|
56
|
+
child.kill('SIGTERM');
|
|
57
|
+
})));
|
|
58
|
+
running.clear();
|
|
59
|
+
},
|
|
60
|
+
};
|
|
61
|
+
}
|
|
62
|
+
//# sourceMappingURL=package-services.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"package-services.js","sourceRoot":"","sources":["../src/package-services.ts"],"names":[],"mappings":"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"}
|
package/dist/registry.d.ts
CHANGED
|
@@ -3,8 +3,9 @@ import { type PublishPick } from './publish-levels.js';
|
|
|
3
3
|
* The multi-project registry (#390): the list of projects the user has
|
|
4
4
|
* installed OpenAgent into, kept as a single JSON file `.bashrc`-style —
|
|
5
5
|
* `$HOME/.openagent.json` — so it is the user's responsibility to re-create
|
|
6
|
-
* per machine. The same file also holds the user's dashboard preferences (#410)
|
|
7
|
-
* so the daemon owns one user file and the UI never
|
|
6
|
+
* per machine. The same file also holds the user's dashboard preferences (#410)
|
|
7
|
+
* and the machines they saved, so the daemon owns one user file and the UI never
|
|
8
|
+
* needs localStorage.
|
|
8
9
|
*/
|
|
9
10
|
/** One registered project. */
|
|
10
11
|
export interface ProjectRecord {
|
|
@@ -15,6 +16,25 @@ export interface ProjectRecord {
|
|
|
15
16
|
/** ISO timestamp the project was added. */
|
|
16
17
|
addedAt: string;
|
|
17
18
|
}
|
|
19
|
+
/**
|
|
20
|
+
* A saved machine: another computer running OpenAgent that a run can be sent to. Kept in this
|
|
21
|
+
* file, the person's own, so every browser on this computer shows the same machines and the
|
|
22
|
+
* key that reaches one never leaves the two machines.
|
|
23
|
+
*/
|
|
24
|
+
export interface MachineRecord {
|
|
25
|
+
/** The machine's address, which is also what names it: saving the same address again replaces it. */
|
|
26
|
+
id: string;
|
|
27
|
+
/** The name the person gave it, or its host when they gave none. */
|
|
28
|
+
label: string;
|
|
29
|
+
/** Where its OpenAgent answers, as an origin (`http://studio.local:4201`). */
|
|
30
|
+
url: string;
|
|
31
|
+
/** This computer's own key on that machine. Never handed to a browser. */
|
|
32
|
+
key: string;
|
|
33
|
+
/** Where this computer was let in there, and where it takes itself out when the machine is removed. */
|
|
34
|
+
door: string;
|
|
35
|
+
/** What the machine's key is known by, when its link said: every call goes only to a machine that shows that key. */
|
|
36
|
+
pin?: string;
|
|
37
|
+
}
|
|
18
38
|
/**
|
|
19
39
|
* The dashboard's Global options (#410), persisted next to the project list so they
|
|
20
40
|
* survive restarts without localStorage — the daemon reads/writes them, the SPA reads
|
|
@@ -104,11 +124,16 @@ export interface Registry {
|
|
|
104
124
|
projects: ProjectRecord[];
|
|
105
125
|
preferences: Preferences;
|
|
106
126
|
/**
|
|
107
|
-
* The
|
|
108
|
-
* A top-level field, deliberately not a {@link Preferences} one, so it is never shipped
|
|
109
|
-
* browser bundle. Absent on a
|
|
127
|
+
* The browser bridge's key (#1237): generated when the bridge is first switched on and reused
|
|
128
|
+
* after. A top-level field, deliberately not a {@link Preferences} one, so it is never shipped
|
|
129
|
+
* to the browser bundle. Absent on a machine that never had the bridge on.
|
|
110
130
|
*/
|
|
111
|
-
|
|
131
|
+
bridgeToken?: string;
|
|
132
|
+
/**
|
|
133
|
+
* The saved machines. Top-level like {@link bridgeToken}, and for the same reason: each one
|
|
134
|
+
* carries a key, so the list is never part of what the browser is handed.
|
|
135
|
+
*/
|
|
136
|
+
machines?: MachineRecord[];
|
|
112
137
|
}
|
|
113
138
|
/** A read/write handle for the user preferences, wired into the dashboard's context by the daemon. */
|
|
114
139
|
export interface PreferencesStore {
|
|
@@ -122,7 +147,7 @@ export interface PreferencesStore {
|
|
|
122
147
|
}
|
|
123
148
|
/** The registry file name: a single file under `$XDG_CONFIG_HOME` (dotted under `$HOME`). */
|
|
124
149
|
export declare const REGISTRY_FILE = "openagent.json";
|
|
125
|
-
/** Owner read/write only: the file holds the
|
|
150
|
+
/** Owner read/write only: the file holds the bridge's key and the saved machines' keys. */
|
|
126
151
|
export declare const REGISTRY_FILE_MODE = 384;
|
|
127
152
|
/**
|
|
128
153
|
* Deterministic, URL-safe id for a project path: the sanitized basename plus a
|
|
@@ -151,7 +176,7 @@ export interface RegistryFs {
|
|
|
151
176
|
rename?(from: string, to: string): Promise<void>;
|
|
152
177
|
/**
|
|
153
178
|
* Narrow a file's permissions. Optional, and best-effort at the call site: this file holds the
|
|
154
|
-
*
|
|
179
|
+
* bridge's key and the saved machines' keys, so it is written owner-only — but a
|
|
155
180
|
* filesystem that cannot express that (Windows, a FAT volume) must not fail the write.
|
|
156
181
|
*/
|
|
157
182
|
chmod?(path: string, mode: number): Promise<void>;
|
|
@@ -205,15 +230,33 @@ export declare function writePreferences(preferences: Preferences, fs?: Registry
|
|
|
205
230
|
*/
|
|
206
231
|
export declare function patchPreferences(patch: Preferences, fs?: RegistryFs, env?: NodeJS.ProcessEnv): Promise<Preferences>;
|
|
207
232
|
/**
|
|
208
|
-
* The
|
|
209
|
-
* only
|
|
210
|
-
* other mutators so two concurrent
|
|
211
|
-
|
|
233
|
+
* The browser bridge's key (#1237): read the persisted one, or generate + persist it now. Called
|
|
234
|
+
* only with the bridge switched on, so a machine that never had it on never grows one. Serialized
|
|
235
|
+
* with the other mutators so two concurrent starts can't each write a different key.
|
|
236
|
+
*/
|
|
237
|
+
export declare function ensureBridgeToken(fs?: RegistryFs, env?: NodeJS.ProcessEnv): Promise<string>;
|
|
238
|
+
/** The persisted bridge key, or `undefined` when none exists. A pure read: it never generates one. */
|
|
239
|
+
export declare function readBridgeToken(fs?: RegistryFs, env?: NodeJS.ProcessEnv): Promise<string | undefined>;
|
|
240
|
+
/** The saved machines, newest first. Forgiving like every read of this file: none on a bad file. */
|
|
241
|
+
export declare function listMachines(fs?: RegistryFs, env?: NodeJS.ProcessEnv): Promise<MachineRecord[]>;
|
|
242
|
+
/**
|
|
243
|
+
* Save a machine, newest first. One per address: saving an address again replaces its name and
|
|
244
|
+
* its key, which is how a machine whose key changed is fixed. Answers the stored record, and the
|
|
245
|
+
* records it took the place of (the one saved at that address before, the oldest past the cap):
|
|
246
|
+
* each still holds a key the caller takes out of its machine.
|
|
212
247
|
*/
|
|
213
|
-
export declare function
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
|
|
248
|
+
export declare function addMachine(machine: {
|
|
249
|
+
url: string;
|
|
250
|
+
key: string;
|
|
251
|
+
door: string;
|
|
252
|
+
pin?: string;
|
|
253
|
+
label?: string;
|
|
254
|
+
}, fs?: RegistryFs, env?: NodeJS.ProcessEnv): Promise<{
|
|
255
|
+
record: MachineRecord;
|
|
256
|
+
displaced: MachineRecord[];
|
|
257
|
+
}>;
|
|
258
|
+
/** Take a saved machine off the list, by id. Answers the one that was there. */
|
|
259
|
+
export declare function removeMachine(id: string, fs?: RegistryFs, env?: NodeJS.ProcessEnv): Promise<MachineRecord | undefined>;
|
|
217
260
|
/** A {@link PreferencesStore} bound to the real registry file, wired by the daemon so the
|
|
218
261
|
* dashboard's preferences RPCs read/write the user's home file.
|
|
219
262
|
*
|
package/dist/registry.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"registry.d.ts","sourceRoot":"","sources":["../src/registry.ts"],"names":[],"mappings":"AAGA,OAAO,EAAiB,KAAK,WAAW,EAAE,MAAM,qBAAqB,CAAA;AAGrE
|
|
1
|
+
{"version":3,"file":"registry.d.ts","sourceRoot":"","sources":["../src/registry.ts"],"names":[],"mappings":"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"}
|
package/dist/registry.js
CHANGED
|
@@ -3,6 +3,8 @@ import { randomBytes } from 'node:crypto';
|
|
|
3
3
|
import { isDriverName } from './driver-names.js';
|
|
4
4
|
import { isPublishPick } from './publish-levels.js';
|
|
5
5
|
import { nodeFs } from './node-fs.js';
|
|
6
|
+
/** The cap on saved machines and on a name's length, so a hand-edited file cannot bloat. */
|
|
7
|
+
const MACHINE_LIMITS = { count: 50, label: 60 };
|
|
6
8
|
/** The cap on saved custom presets, and the per-field lengths — enough for real prompts, bounded
|
|
7
9
|
* so a hand-edited or hostile registry can't bloat the home file. */
|
|
8
10
|
const CUSTOM_PRESET_LIMITS = { count: 30, label: 80, prompt: 20_000 };
|
|
@@ -10,7 +12,7 @@ const CUSTOM_PRESET_LIMITS = { count: 30, label: 80, prompt: 20_000 };
|
|
|
10
12
|
const START_FROM_LIMIT = 200;
|
|
11
13
|
/** The registry file name: a single file under `$XDG_CONFIG_HOME` (dotted under `$HOME`). */
|
|
12
14
|
export const REGISTRY_FILE = 'openagent.json';
|
|
13
|
-
/** Owner read/write only: the file holds the
|
|
15
|
+
/** Owner read/write only: the file holds the bridge's key and the saved machines' keys. */
|
|
14
16
|
export const REGISTRY_FILE_MODE = 0o600;
|
|
15
17
|
/**
|
|
16
18
|
* Deterministic, URL-safe id for a project path: the sanitized basename plus a
|
|
@@ -166,6 +168,27 @@ export function sanitizeCustomPresets(value) {
|
|
|
166
168
|
}
|
|
167
169
|
return out;
|
|
168
170
|
}
|
|
171
|
+
/** Keep well-formed machines, one per address (first wins), names and count capped. */
|
|
172
|
+
function sanitizeMachines(value) {
|
|
173
|
+
if (!Array.isArray(value))
|
|
174
|
+
return [];
|
|
175
|
+
const machines = [];
|
|
176
|
+
const seen = new Set();
|
|
177
|
+
for (const raw of value) {
|
|
178
|
+
if (machines.length >= MACHINE_LIMITS.count)
|
|
179
|
+
break;
|
|
180
|
+
if (typeof raw !== 'object' || raw === null)
|
|
181
|
+
continue;
|
|
182
|
+
const { label, url, key, door, pin } = raw;
|
|
183
|
+
if (typeof label !== 'string' || typeof url !== 'string' || typeof key !== 'string' || typeof door !== 'string')
|
|
184
|
+
continue;
|
|
185
|
+
if (!url || !key || !door || seen.has(url))
|
|
186
|
+
continue;
|
|
187
|
+
seen.add(url);
|
|
188
|
+
machines.push({ id: url, label: label.trim().slice(0, MACHINE_LIMITS.label) || url, url, key, door, ...(typeof pin === 'string' && pin ? { pin } : {}) });
|
|
189
|
+
}
|
|
190
|
+
return machines;
|
|
191
|
+
}
|
|
169
192
|
/**
|
|
170
193
|
* Read the whole registry. Forgiving: a missing / unreadable / malformed file — or one in a shape
|
|
171
194
|
* this no longer writes — yields an empty registry, never throws. Projects are deduped by resolved
|
|
@@ -184,11 +207,13 @@ export async function readRegistry(fs = nodeRegistryFs(), env = process.env) {
|
|
|
184
207
|
return empty;
|
|
185
208
|
const obj = parsed;
|
|
186
209
|
const projects = Array.isArray(obj.projects) ? dedupeProjects(obj.projects) : [];
|
|
210
|
+
const machines = sanitizeMachines(obj.machines);
|
|
187
211
|
return {
|
|
188
212
|
projects,
|
|
189
213
|
preferences: sanitizePreferences(obj.preferences),
|
|
190
|
-
//
|
|
191
|
-
...(typeof obj.
|
|
214
|
+
// Kept only as a non-empty string, so a hand-edited registry can't smuggle a junk key.
|
|
215
|
+
...(typeof obj.bridgeToken === 'string' && obj.bridgeToken ? { bridgeToken: obj.bridgeToken } : {}),
|
|
216
|
+
...(machines.length ? { machines } : {}),
|
|
192
217
|
};
|
|
193
218
|
}
|
|
194
219
|
/**
|
|
@@ -201,18 +226,19 @@ export async function readRegistry(fs = nodeRegistryFs(), env = process.env) {
|
|
|
201
226
|
* now only ever damages the temp file. The temp is left behind on failure rather than swept up:
|
|
202
227
|
* one stray file is the cheaper half of that trade.
|
|
203
228
|
*
|
|
204
|
-
* Written owner-only (#1095): the file carries the
|
|
229
|
+
* Written owner-only (#1095): the file carries the bridge's key and the saved machines' keys, so a default-umask 0644 in a
|
|
205
230
|
* shared home would hand it to every other account on the machine.
|
|
206
231
|
* The mode is set on the temp file, before the rename — narrowing after it would leave a window
|
|
207
232
|
* where the real path is readable. Best-effort: a filesystem with no permission bits still writes.
|
|
208
233
|
*/
|
|
209
234
|
async function writeRegistry(registry, fs, env) {
|
|
210
235
|
const file = registryPath(env);
|
|
211
|
-
const { projects, preferences,
|
|
236
|
+
const { projects, preferences, bridgeToken, machines } = registry;
|
|
212
237
|
const contents = {
|
|
213
238
|
projects,
|
|
214
239
|
preferences,
|
|
215
|
-
...(
|
|
240
|
+
...(bridgeToken ? { bridgeToken } : {}),
|
|
241
|
+
...(machines?.length ? { machines } : {}),
|
|
216
242
|
};
|
|
217
243
|
const json = JSON.stringify(contents, null, 2);
|
|
218
244
|
await fs.mkdir(dirname(file));
|
|
@@ -316,25 +342,70 @@ export async function patchPreferences(patch, fs = nodeRegistryFs(), env = proce
|
|
|
316
342
|
});
|
|
317
343
|
}
|
|
318
344
|
/**
|
|
319
|
-
* The
|
|
320
|
-
* only
|
|
321
|
-
* other mutators so two concurrent
|
|
322
|
-
|
|
345
|
+
* The browser bridge's key (#1237): read the persisted one, or generate + persist it now. Called
|
|
346
|
+
* only with the bridge switched on, so a machine that never had it on never grows one. Serialized
|
|
347
|
+
* with the other mutators so two concurrent starts can't each write a different key.
|
|
348
|
+
*/
|
|
349
|
+
export async function ensureBridgeToken(fs = nodeRegistryFs(), env = process.env) {
|
|
350
|
+
return serialize(async () => {
|
|
351
|
+
const registry = await readRegistry(fs, env);
|
|
352
|
+
if (registry.bridgeToken)
|
|
353
|
+
return registry.bridgeToken;
|
|
354
|
+
const bridgeToken = randomBytes(32).toString('base64url');
|
|
355
|
+
await writeRegistry({ ...registry, bridgeToken }, fs, env);
|
|
356
|
+
return bridgeToken;
|
|
357
|
+
});
|
|
358
|
+
}
|
|
359
|
+
/** The persisted bridge key, or `undefined` when none exists. A pure read: it never generates one. */
|
|
360
|
+
export async function readBridgeToken(fs = nodeRegistryFs(), env = process.env) {
|
|
361
|
+
return (await readRegistry(fs, env)).bridgeToken;
|
|
362
|
+
}
|
|
363
|
+
/** The saved machines, newest first. Forgiving like every read of this file: none on a bad file. */
|
|
364
|
+
export async function listMachines(fs = nodeRegistryFs(), env = process.env) {
|
|
365
|
+
return (await readRegistry(fs, env)).machines ?? [];
|
|
366
|
+
}
|
|
367
|
+
/**
|
|
368
|
+
* Save a machine, newest first. One per address: saving an address again replaces its name and
|
|
369
|
+
* its key, which is how a machine whose key changed is fixed. Answers the stored record, and the
|
|
370
|
+
* records it took the place of (the one saved at that address before, the oldest past the cap):
|
|
371
|
+
* each still holds a key the caller takes out of its machine.
|
|
323
372
|
*/
|
|
324
|
-
export async function
|
|
373
|
+
export async function addMachine(machine, fs = nodeRegistryFs(), env = process.env) {
|
|
325
374
|
return serialize(async () => {
|
|
326
375
|
const registry = await readRegistry(fs, env);
|
|
327
|
-
|
|
328
|
-
|
|
329
|
-
|
|
330
|
-
|
|
331
|
-
|
|
376
|
+
const record = {
|
|
377
|
+
id: machine.url,
|
|
378
|
+
label: machine.label?.trim().slice(0, MACHINE_LIMITS.label) || hostOf(machine.url),
|
|
379
|
+
url: machine.url,
|
|
380
|
+
key: machine.key,
|
|
381
|
+
door: machine.door,
|
|
382
|
+
...(machine.pin ? { pin: machine.pin } : {}),
|
|
383
|
+
};
|
|
384
|
+
const before = registry.machines ?? [];
|
|
385
|
+
const machines = [record, ...before.filter(saved => saved.id !== record.id)].slice(0, MACHINE_LIMITS.count);
|
|
386
|
+
await writeRegistry({ ...registry, machines }, fs, env);
|
|
387
|
+
return { record, displaced: before.filter(saved => !machines.includes(saved)) };
|
|
332
388
|
});
|
|
333
389
|
}
|
|
334
|
-
/**
|
|
335
|
-
|
|
336
|
-
|
|
337
|
-
|
|
390
|
+
/** Take a saved machine off the list, by id. Answers the one that was there. */
|
|
391
|
+
export async function removeMachine(id, fs = nodeRegistryFs(), env = process.env) {
|
|
392
|
+
return serialize(async () => {
|
|
393
|
+
const registry = await readRegistry(fs, env);
|
|
394
|
+
const removed = (registry.machines ?? []).find(saved => saved.id === id);
|
|
395
|
+
if (!removed)
|
|
396
|
+
return undefined;
|
|
397
|
+
await writeRegistry({ ...registry, machines: (registry.machines ?? []).filter(saved => saved.id !== id) }, fs, env);
|
|
398
|
+
return removed;
|
|
399
|
+
});
|
|
400
|
+
}
|
|
401
|
+
/** An address's host and port (`192.168.1.5:4200`): a machine's name when the person gave none. */
|
|
402
|
+
function hostOf(url) {
|
|
403
|
+
try {
|
|
404
|
+
return new URL(url).host;
|
|
405
|
+
}
|
|
406
|
+
catch {
|
|
407
|
+
return url;
|
|
408
|
+
}
|
|
338
409
|
}
|
|
339
410
|
/** A {@link PreferencesStore} bound to the real registry file, wired by the daemon so the
|
|
340
411
|
* dashboard's preferences RPCs read/write the user's home file.
|