@agentorg/cli 0.0.4 → 0.0.6
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 +18 -102
- package/dist/node_modules/@agentorg/hub/dist/src/hub-actor.d.ts +2 -7
- package/dist/node_modules/@agentorg/hub/dist/src/hub-actor.js +4 -111
- package/dist/node_modules/@agentorg/hub/dist/src/index.d.ts +1 -3
- package/dist/node_modules/@agentorg/hub/dist/src/index.js +1 -3
- package/dist/node_modules/@agentorg/hub/dist/src/router.d.ts +6 -57
- package/dist/node_modules/@agentorg/hub/dist/src/router.js +38 -413
- package/dist/node_modules/@agentorg/hub/dist/src/state.d.ts +20 -298
- package/dist/node_modules/@agentorg/hub/dist/src/state.js +73 -456
- package/dist/node_modules/@agentorg/hub/dist/src/validate.d.ts +1 -3
- package/dist/node_modules/@agentorg/hub/dist/src/validate.js +3 -15
- package/dist/node_modules/@agentorg/hub/package.json +0 -3
- package/dist/node_modules/@agentorg/interfaces/dist/src/agent-provider.d.ts +132 -0
- package/dist/node_modules/@agentorg/interfaces/dist/src/agent-provider.js +105 -0
- package/dist/node_modules/@agentorg/interfaces/dist/src/agent-settings.d.ts +42 -0
- package/dist/node_modules/@agentorg/interfaces/dist/src/agent-settings.js +53 -0
- package/dist/node_modules/@agentorg/interfaces/dist/src/chat.d.ts +15 -0
- package/dist/node_modules/@agentorg/interfaces/dist/src/chat.js +14 -0
- package/dist/node_modules/@agentorg/interfaces/dist/src/hub.d.ts +16 -267
- package/dist/node_modules/@agentorg/interfaces/dist/src/hub.js +17 -145
- package/dist/node_modules/@agentorg/interfaces/dist/src/index.d.ts +6 -8
- package/dist/node_modules/@agentorg/interfaces/dist/src/index.js +6 -8
- package/dist/node_modules/@agentorg/interfaces/dist/src/kv.d.ts +2 -2
- package/dist/node_modules/@agentorg/interfaces/dist/src/kv.js +2 -2
- package/dist/node_modules/@agentorg/interfaces/dist/src/manager.d.ts +272 -0
- package/dist/node_modules/@agentorg/interfaces/dist/src/manager.js +185 -0
- package/dist/node_modules/@agentorg/interfaces/dist/src/mcp-provider.d.ts +88 -0
- package/dist/node_modules/@agentorg/interfaces/dist/src/mcp-provider.js +78 -0
- package/dist/node_modules/@agentorg/interfaces/dist/src/tools.d.ts +39 -0
- package/dist/node_modules/@agentorg/interfaces/dist/src/tools.js +43 -0
- package/dist/node_modules/@agentorg/manager/dist/src/index.d.ts +1 -0
- package/dist/node_modules/@agentorg/manager/dist/src/index.js +1 -0
- package/dist/node_modules/@agentorg/manager/dist/src/manager.d.ts +21 -0
- package/dist/node_modules/@agentorg/manager/dist/src/manager.js +528 -0
- package/dist/node_modules/@agentorg/manager/dist/src/skills.d.ts +25 -0
- package/dist/node_modules/@agentorg/manager/dist/src/skills.js +145 -0
- package/dist/node_modules/@agentorg/manager/package.json +30 -0
- package/dist/node_modules/@agentorg/mcp/dist/src/connection.d.ts +33 -0
- package/dist/node_modules/@agentorg/mcp/dist/src/connection.js +290 -0
- package/dist/node_modules/@agentorg/mcp/dist/src/index.d.ts +1 -0
- package/dist/node_modules/@agentorg/mcp/dist/src/index.js +1 -0
- package/dist/node_modules/@agentorg/mcp/dist/src/kinds.d.ts +50 -0
- package/dist/node_modules/@agentorg/mcp/dist/src/kinds.js +69 -0
- package/dist/node_modules/@agentorg/mcp/dist/src/provider.d.ts +22 -0
- package/dist/node_modules/@agentorg/mcp/dist/src/provider.js +167 -0
- package/dist/node_modules/@agentorg/mcp/package.json +31 -0
- package/dist/node_modules/@agentorg/sdk/dist/src/cli.js +3 -23
- package/dist/node_modules/@agentorg/sdk/dist/src/client.d.ts +4 -25
- package/dist/node_modules/@agentorg/sdk/dist/src/client.js +5 -55
- package/dist/node_modules/@agentorg/sdk/dist/src/errors.d.ts +1 -1
- package/dist/node_modules/@agentorg/sdk/dist/src/errors.js +3 -3
- package/dist/node_modules/@agentorg/sdk/dist/src/index.d.ts +2 -2
- package/dist/node_modules/@agentorg/sdk/dist/src/index.js +1 -1
- package/dist/node_modules/@agentorg/sdk/dist/src/interface.d.ts +6 -34
- package/dist/node_modules/@agentorg/sdk/dist/src/interface.js +3 -24
- package/dist/node_modules/@agentorg/sdk/dist/src/module-host.d.ts +8 -17
- package/dist/node_modules/@agentorg/sdk/dist/src/module-host.js +15 -87
- package/dist/node_modules/@agentorg/sdk/dist/src/module.d.ts +10 -76
- package/dist/node_modules/@agentorg/sdk/dist/src/module.js +3 -23
- package/dist/node_modules/@agentorg/sdk/dist/src/person-commands.js +0 -5
- package/dist/node_modules/@agentorg/sdk/dist/src/runtime.d.ts +3 -27
- package/dist/node_modules/@agentorg/sdk/dist/src/runtime.js +5 -100
- package/dist/node_modules/@agentorg/sdk/dist/src/testing.d.ts +7 -8
- package/dist/node_modules/@agentorg/sdk/dist/src/testing.js +12 -24
- package/dist/node_modules/@agentorg/sdk/package.json +4 -37
- package/dist/node_modules/@agentorg/shell/dist/app.css +3435 -739
- package/dist/node_modules/@agentorg/shell/dist/app.js +204 -202
- package/dist/node_modules/@agentorg/shell/package.json +18 -10
- package/dist/node_modules/@agentorg/wire/dist/src/frames.d.ts +2 -3
- package/dist/node_modules/@agentorg/wire/dist/src/frames.js +2 -3
- package/dist/node_modules/@agentorg/wire/dist/src/ops.d.ts +5 -34
- package/dist/node_modules/@agentorg/wire/dist/src/ops.js +2 -17
- package/dist/node_modules/assistant/dist/src/agent.d.ts +62 -0
- package/dist/node_modules/assistant/dist/src/agent.js +226 -0
- package/dist/node_modules/assistant/dist/src/engine.d.ts +23 -0
- package/dist/node_modules/assistant/dist/src/engine.js +88 -0
- package/dist/node_modules/assistant/dist/src/history.d.ts +97 -0
- package/dist/node_modules/assistant/dist/src/history.js +235 -0
- package/dist/node_modules/assistant/dist/src/local-store.d.ts +3 -0
- package/dist/node_modules/assistant/dist/src/local-store.js +58 -0
- package/dist/node_modules/assistant/dist/src/provider.d.ts +21 -0
- package/dist/node_modules/assistant/dist/src/provider.js +231 -0
- package/dist/node_modules/assistant/dist/src/settings.d.ts +17 -0
- package/dist/node_modules/assistant/dist/src/settings.js +43 -0
- package/dist/node_modules/assistant/dist/src/tools.d.ts +74 -0
- package/dist/node_modules/assistant/dist/src/tools.js +123 -0
- package/dist/node_modules/assistant/package.json +29 -0
- package/dist/node_modules/chatgpt/dist/src/account.d.ts +52 -0
- package/dist/node_modules/chatgpt/dist/src/account.js +270 -0
- package/dist/node_modules/chatgpt/dist/src/encoding.d.ts +7 -0
- package/dist/node_modules/chatgpt/dist/src/encoding.js +20 -0
- package/dist/node_modules/chatgpt/dist/src/file-store.d.ts +9 -0
- package/dist/node_modules/chatgpt/dist/src/file-store.js +68 -0
- package/dist/node_modules/chatgpt/dist/src/index.d.ts +1 -0
- package/dist/node_modules/chatgpt/dist/src/index.js +1 -0
- package/dist/node_modules/chatgpt/dist/src/jwt.d.ts +15 -0
- package/dist/node_modules/chatgpt/dist/src/jwt.js +37 -0
- package/dist/node_modules/chatgpt/dist/src/oauth.d.ts +43 -0
- package/dist/node_modules/chatgpt/dist/src/oauth.js +104 -0
- package/dist/node_modules/chatgpt/dist/src/program.d.ts +23 -0
- package/dist/node_modules/chatgpt/dist/src/program.js +41 -0
- package/dist/node_modules/chatgpt/dist/src/responses.d.ts +54 -0
- package/dist/node_modules/chatgpt/dist/src/responses.js +300 -0
- package/dist/node_modules/chatgpt/dist/src/serve.d.ts +65 -0
- package/dist/node_modules/chatgpt/dist/src/serve.js +46 -0
- package/dist/node_modules/chatgpt/package.json +33 -0
- package/dist/src/api.d.ts +5 -34
- package/dist/src/api.js +6 -68
- package/dist/src/cli.js +3 -7
- package/dist/src/gateway.d.ts +6 -7
- package/dist/src/gateway.js +39 -285
- package/dist/src/index.d.ts +4 -5
- package/dist/src/index.js +3 -4
- package/dist/src/notes.d.ts +11 -0
- package/dist/src/notes.js +64 -0
- package/dist/src/oauth.d.ts +16 -0
- package/dist/src/oauth.js +46 -0
- package/dist/src/programs.d.ts +29 -0
- package/dist/src/programs.js +171 -0
- package/package.json +3 -11
- package/dist/bundled/index.json +0 -6
- package/dist/bundled/sha256-01d69bc3888c2a53963f4413d47de909d129ad27c78e9af6d830bb50c3478419.agentorg +0 -0
- package/dist/bundled/sha256-84165e3be5c904fc971d983dab31102e07cca725d8629a6fd7d118fa3a0a88b8.agentorg +0 -0
- package/dist/bundled/sha256-ade9132b3f34b4c39db92adeadc7e540dc745e4a81b5b54f5d45bf9ec92bca75.agentorg +0 -0
- package/dist/bundled/sha256-f4330f5987f37d8cf3aeb44d2f831b573641a87186daa2f811f0c35ba04804f0.agentorg +0 -0
- package/dist/node_modules/@agentorg/hub/dist/src/build.d.ts +0 -17
- package/dist/node_modules/@agentorg/hub/dist/src/build.js +0 -120
- package/dist/node_modules/@agentorg/hub/dist/src/logs.d.ts +0 -33
- package/dist/node_modules/@agentorg/hub/dist/src/logs.js +0 -162
- package/dist/node_modules/@agentorg/hub/dist/src/net.d.ts +0 -41
- package/dist/node_modules/@agentorg/hub/dist/src/net.js +0 -265
- package/dist/node_modules/@agentorg/hub/dist/src/processes.d.ts +0 -78
- package/dist/node_modules/@agentorg/hub/dist/src/processes.js +0 -485
- package/dist/node_modules/@agentorg/hub/dist/src/storage.d.ts +0 -38
- package/dist/node_modules/@agentorg/hub/dist/src/storage.js +0 -194
- package/dist/node_modules/@agentorg/interfaces/dist/src/http-egress.d.ts +0 -6
- package/dist/node_modules/@agentorg/interfaces/dist/src/http-egress.js +0 -7
- package/dist/node_modules/@agentorg/interfaces/dist/src/installer.d.ts +0 -191
- package/dist/node_modules/@agentorg/interfaces/dist/src/installer.js +0 -136
- package/dist/node_modules/@agentorg/interfaces/dist/src/layout.d.ts +0 -47
- package/dist/node_modules/@agentorg/interfaces/dist/src/layout.js +0 -52
- package/dist/node_modules/@agentorg/interfaces/dist/src/rules.d.ts +0 -35
- package/dist/node_modules/@agentorg/interfaces/dist/src/rules.js +0 -37
- package/dist/node_modules/@agentorg/interfaces/dist/src/storage-admin.d.ts +0 -23
- package/dist/node_modules/@agentorg/interfaces/dist/src/storage-admin.js +0 -31
- package/dist/node_modules/@agentorg/interfaces/dist/src/ui-shell.d.ts +0 -14
- package/dist/node_modules/@agentorg/interfaces/dist/src/ui-shell.js +0 -26
- package/dist/node_modules/@agentorg/interfaces/dist/src/ui-view.d.ts +0 -27
- package/dist/node_modules/@agentorg/interfaces/dist/src/ui-view.js +0 -26
- package/dist/node_modules/@agentorg/kit/dist/src/chat.d.ts +0 -37
- package/dist/node_modules/@agentorg/kit/dist/src/chat.js +0 -131
- package/dist/node_modules/@agentorg/kit/dist/src/components.d.ts +0 -94
- package/dist/node_modules/@agentorg/kit/dist/src/components.js +0 -66
- package/dist/node_modules/@agentorg/kit/dist/src/icons.d.ts +0 -17
- package/dist/node_modules/@agentorg/kit/dist/src/icons.js +0 -30
- package/dist/node_modules/@agentorg/kit/dist/src/index.d.ts +0 -6
- package/dist/node_modules/@agentorg/kit/dist/src/index.js +0 -10
- package/dist/node_modules/@agentorg/kit/dist/src/markdown.d.ts +0 -9
- package/dist/node_modules/@agentorg/kit/dist/src/markdown.js +0 -14
- package/dist/node_modules/@agentorg/kit/dist/src/resizable.d.ts +0 -5
- package/dist/node_modules/@agentorg/kit/dist/src/resizable.js +0 -8
- package/dist/node_modules/@agentorg/kit/dist/src/styles.d.ts +0 -11
- package/dist/node_modules/@agentorg/kit/dist/src/styles.js +0 -166
- package/dist/node_modules/@agentorg/kit/package.json +0 -44
- package/dist/node_modules/@agentorg/sdk/dist/frame/runtime.js +0 -753
- package/dist/node_modules/@agentorg/sdk/dist/host/runner.js +0 -21963
- package/dist/node_modules/@agentorg/sdk/dist/src/build.d.ts +0 -2
- package/dist/node_modules/@agentorg/sdk/dist/src/build.js +0 -2
- package/dist/node_modules/@agentorg/sdk/dist/src/client-http.d.ts +0 -24
- package/dist/node_modules/@agentorg/sdk/dist/src/client-http.js +0 -247
- package/dist/node_modules/@agentorg/sdk/dist/src/describe.d.ts +0 -13
- package/dist/node_modules/@agentorg/sdk/dist/src/describe.js +0 -16
- package/dist/node_modules/@agentorg/sdk/dist/src/frame-base.d.ts +0 -8
- package/dist/node_modules/@agentorg/sdk/dist/src/frame-base.js +0 -64
- package/dist/node_modules/@agentorg/sdk/dist/src/frame-entry.d.ts +0 -1
- package/dist/node_modules/@agentorg/sdk/dist/src/frame-entry.js +0 -35
- package/dist/node_modules/@agentorg/sdk/dist/src/frame-host.d.ts +0 -57
- package/dist/node_modules/@agentorg/sdk/dist/src/frame-host.js +0 -293
- package/dist/node_modules/@agentorg/sdk/dist/src/frame-protocol.d.ts +0 -124
- package/dist/node_modules/@agentorg/sdk/dist/src/frame-protocol.js +0 -16
- package/dist/node_modules/@agentorg/sdk/dist/src/frame.d.ts +0 -25
- package/dist/node_modules/@agentorg/sdk/dist/src/frame.js +0 -248
- package/dist/node_modules/@agentorg/sdk/dist/src/host-main.d.ts +0 -1
- package/dist/node_modules/@agentorg/sdk/dist/src/host-main.js +0 -60
- package/dist/node_modules/@agentorg/sdk/dist/src/host.d.ts +0 -25
- package/dist/node_modules/@agentorg/sdk/dist/src/host.js +0 -92
- package/dist/node_modules/@agentorg/sdk/dist/src/manifest-api.d.ts +0 -2
- package/dist/node_modules/@agentorg/sdk/dist/src/manifest-api.js +0 -2
- package/dist/node_modules/@agentorg/sdk/dist/src/manifest.d.ts +0 -56
- package/dist/node_modules/@agentorg/sdk/dist/src/manifest.js +0 -165
- package/dist/node_modules/@agentorg/sdk/dist/src/package.d.ts +0 -60
- package/dist/node_modules/@agentorg/sdk/dist/src/package.js +0 -538
- package/dist/node_modules/@agentorg/sdk/dist/src/tar.d.ts +0 -7
- package/dist/node_modules/@agentorg/sdk/dist/src/tar.js +0 -71
- package/dist/node_modules/@agentorg/shell/dist/agent-bindings.js +0 -59
- package/dist/node_modules/@agentorg/shell/src/agent-bindings.ts +0 -107
- package/dist/src/frames.d.ts +0 -23
- package/dist/src/frames.js +0 -78
- package/dist/src/layout.d.ts +0 -44
- package/dist/src/layout.js +0 -90
- package/dist/src/model.d.ts +0 -51
- package/dist/src/model.js +0 -230
- package/dist/src/rules.d.ts +0 -71
- package/dist/src/rules.js +0 -157
- package/dist/src/transfer.d.ts +0 -30
- package/dist/src/transfer.js +0 -110
|
@@ -1,21 +1,15 @@
|
|
|
1
1
|
import { createHash, randomBytes, timingSafeEqual } from "node:crypto";
|
|
2
2
|
import { chmodSync, closeSync, fsyncSync, mkdirSync, openSync, readFileSync, renameSync, rmSync, writeFileSync, writeSync, } from "node:fs";
|
|
3
|
-
import {
|
|
3
|
+
import { join } from "node:path";
|
|
4
4
|
import { DatabaseSync } from "node:sqlite";
|
|
5
|
-
import {
|
|
5
|
+
import { hubActors, hubAudit, hubBindings, hubExports, hubGrants } from "@agentorg/interfaces";
|
|
6
6
|
import { compareInterfaces, describeInterface, hashInterface, Refused, } from "@agentorg/sdk";
|
|
7
|
-
import { readTar, treeHash, verifyArchive } from "@agentorg/sdk/manifest";
|
|
8
7
|
/** The built-in Actor that exports management. */
|
|
9
8
|
export const HUB = "hub";
|
|
10
|
-
/** The built-in Actor that keeps state: `kv@1`, one store per consumer slot. */
|
|
11
|
-
export const STORAGE = "storage";
|
|
12
|
-
/** The built-in Actor that reaches the public internet: `http.egress@1`, over HTTPS only. */
|
|
13
|
-
export const NET = "net";
|
|
14
9
|
/** The person's Actor, created at first start with every `hub.*` binding. */
|
|
15
10
|
export const PERSON = "person";
|
|
16
11
|
/** The hub's exports, each the authority for its own part of management. */
|
|
17
12
|
const HUB_EXPORTS = {
|
|
18
|
-
packages: hubPackages,
|
|
19
13
|
actors: hubActors,
|
|
20
14
|
bindings: hubBindings,
|
|
21
15
|
grants: hubGrants,
|
|
@@ -25,10 +19,6 @@ const HUB_EXPORTS = {
|
|
|
25
19
|
const NAME = /^[a-z][A-Za-z0-9_]*$/;
|
|
26
20
|
/** The longest name of a slot made at run time, so a tool named `<slot>__<method>` fits. */
|
|
27
21
|
const EXPOSED_MAX = 48;
|
|
28
|
-
/** Exposure requests one Actor may have open toward one other at a time. */
|
|
29
|
-
const MAX_OPEN_EXPOSURES = 32;
|
|
30
|
-
/** The Actors that are the Hub itself: they take no slots. */
|
|
31
|
-
const BUILTIN = new Set(["hub", "storage", "net"]);
|
|
32
22
|
/**
|
|
33
23
|
* The default name of a slot exposing `exportName` of the Actor named `providerName`: the name's
|
|
34
24
|
* ASCII words camelCased, `_`, and the export's last part; the export's part alone when the name
|
|
@@ -44,7 +34,6 @@ export function exposedName(providerName, exportName) {
|
|
|
44
34
|
const part = exportName.slice(exportName.lastIndexOf(".") + 1);
|
|
45
35
|
return (camel ? `${camel}_${part}` : part).slice(0, EXPOSED_MAX);
|
|
46
36
|
}
|
|
47
|
-
const MEMBER = /^[a-z][A-Za-z0-9_]*(\.[a-z][A-Za-z0-9_]*)?$/;
|
|
48
37
|
const now = () => new Date().toISOString();
|
|
49
38
|
const digest = (secret) => createHash("sha256").update(secret).digest();
|
|
50
39
|
/** Each entry moves the home's database up one version; `user_version` records where it is. */
|
|
@@ -76,6 +65,14 @@ const MIGRATIONS = [
|
|
|
76
65
|
// Exposure: an Actor made for another (provenance only), and requests to expose.
|
|
77
66
|
`ALTER TABLE actors ADD COLUMN installed_for TEXT;
|
|
78
67
|
ALTER TABLE requests ADD COLUMN exposed INTEGER NOT NULL DEFAULT 0;`,
|
|
68
|
+
// The built-in storage and network and the package store are gone; an earlier home's records
|
|
69
|
+
// of them go too. The packages table stays, unread.
|
|
70
|
+
`DELETE FROM bindings WHERE provider IN ('storage', 'net');
|
|
71
|
+
DELETE FROM bindings WHERE provider = 'hub' AND export = 'packages';
|
|
72
|
+
DELETE FROM declarations WHERE actor = 'hub' AND side = 'export' AND name = 'packages';
|
|
73
|
+
DELETE FROM declarations WHERE actor = 'person' AND side = 'slot' AND name = 'packages';
|
|
74
|
+
DELETE FROM declarations WHERE actor IN ('storage', 'net');
|
|
75
|
+
UPDATE actors SET state = 'closed' WHERE id IN ('storage', 'net');`,
|
|
79
76
|
];
|
|
80
77
|
/** Whether a process with this pid runs now. */
|
|
81
78
|
function alive(pid) {
|
|
@@ -130,21 +127,8 @@ const bindingOf = (row) => ({
|
|
|
130
127
|
/** A binding's columns, with whether its slot was made at run time. */
|
|
131
128
|
const BINDING = `b.*, (SELECT d.dynamic FROM declarations d
|
|
132
129
|
WHERE d.actor = b.consumer AND d.side = 'slot' AND d.name = b.slot) AS exposed`;
|
|
133
|
-
const requestOf = (row) => ({
|
|
134
|
-
requestId: String(row.id),
|
|
135
|
-
consumer: String(row.consumer),
|
|
136
|
-
slot: String(row.slot),
|
|
137
|
-
provider: String(row.provider),
|
|
138
|
-
export: String(row.export),
|
|
139
|
-
reason: String(row.reason),
|
|
140
|
-
requestedBy: String(row.requested_by),
|
|
141
|
-
at: String(row.at),
|
|
142
|
-
needs: row.needs === "slot" ? "slot" : "export",
|
|
143
|
-
...(row.exposed ? { exposed: true } : {}),
|
|
144
|
-
});
|
|
145
|
-
const other = (side) => (side === "export" ? "slot" : "export");
|
|
146
130
|
/**
|
|
147
|
-
* The core's state in its home folder: Actors,
|
|
131
|
+
* The core's state in its home folder: Actors, declarations, bindings
|
|
148
132
|
* and the audit trail, in `node:sqlite`. Every method that changes something takes its caller, and
|
|
149
133
|
* each is checked against bindings: there is no other authority.
|
|
150
134
|
*/
|
|
@@ -157,7 +141,6 @@ export class HubState {
|
|
|
157
141
|
constructor(home, events) {
|
|
158
142
|
this.home = home;
|
|
159
143
|
this.#events = events;
|
|
160
|
-
mkdirSync(join(home, "packages"), { recursive: true, mode: 0o700 });
|
|
161
144
|
chmodSync(home, 0o700);
|
|
162
145
|
const file = join(home, "hub.db");
|
|
163
146
|
this.#db = new DatabaseSync(file);
|
|
@@ -234,17 +217,6 @@ export class HubState {
|
|
|
234
217
|
}
|
|
235
218
|
this.#audit(HUB, "bootstrap", { person: PERSON });
|
|
236
219
|
}
|
|
237
|
-
if (!this.#actor(STORAGE))
|
|
238
|
-
this.#db
|
|
239
|
-
.prepare("INSERT INTO actors (id, name, package, state, credential, created_at) VALUES (?, 'storage', 'builtin', 'running', NULL, ?)")
|
|
240
|
-
.run(STORAGE, now());
|
|
241
|
-
this.#declare(STORAGE, "export", "kv", describeInterface(kv), false);
|
|
242
|
-
this.#declare(STORAGE, "export", "admin", describeInterface(storageAdmin), false);
|
|
243
|
-
if (!this.#actor(NET))
|
|
244
|
-
this.#db
|
|
245
|
-
.prepare("INSERT INTO actors (id, name, package, state, credential, created_at) VALUES (?, 'net', 'builtin', 'running', NULL, ?)")
|
|
246
|
-
.run(NET, now());
|
|
247
|
-
this.#declare(NET, "export", "egress", describeInterface(httpEgress), false);
|
|
248
220
|
for (const [name, iface] of Object.entries(HUB_EXPORTS)) {
|
|
249
221
|
const described = describeInterface(iface);
|
|
250
222
|
this.#declare(HUB, "export", name, described, false);
|
|
@@ -311,17 +283,6 @@ export class HubState {
|
|
|
311
283
|
const entry = { seq: Number(lastInsertRowid), at, actor, kind, details };
|
|
312
284
|
this.#emit(() => this.#events.audited?.(entry));
|
|
313
285
|
}
|
|
314
|
-
/** Record something the Hub itself saw happen, such as the host sandbox's status changing. */
|
|
315
|
-
auditHub(kind, details, actor = HUB) {
|
|
316
|
-
this.#transaction(() => this.#audit(actor, kind, details));
|
|
317
|
-
}
|
|
318
|
-
/** A package Actor's process exited: kept as its `lastExit`, and counted when it is restarted. */
|
|
319
|
-
recordExit(actorId, exit, restarted) {
|
|
320
|
-
this.#db
|
|
321
|
-
.prepare("UPDATE actors SET last_exit = ?, restarts = restarts + ? WHERE id = ? AND package NOT IN ('external', 'builtin')")
|
|
322
|
-
.run(JSON.stringify(exit), restarted ? 1 : 0, actorId);
|
|
323
|
-
this.#emit(() => this.#events.actorChanged?.(actorId));
|
|
324
|
-
}
|
|
325
286
|
/** Record a call's metadata: who, through which binding, which method, and how it ended. */
|
|
326
287
|
auditCall(caller,
|
|
327
288
|
/** The binding the call went through; an unbound slot has only its consumer and slot. */
|
|
@@ -331,16 +292,13 @@ export class HubState {
|
|
|
331
292
|
// ── Actors ──────────────────────────────────────────────────────────────────
|
|
332
293
|
#actor(actorId) {
|
|
333
294
|
const row = this.#db
|
|
334
|
-
.prepare("SELECT id, name, package, state
|
|
295
|
+
.prepare("SELECT id, name, package, state FROM actors WHERE id = ?")
|
|
335
296
|
.get(actorId);
|
|
336
297
|
return (row && {
|
|
337
298
|
actorId: String(row.id),
|
|
338
299
|
name: String(row.name),
|
|
339
300
|
package: String(row.package),
|
|
340
301
|
state: row.state,
|
|
341
|
-
restarts: Number(row.restarts),
|
|
342
|
-
...(row.last_exit ? { lastExit: JSON.parse(String(row.last_exit)) } : {}),
|
|
343
|
-
...(row.installed_for ? { installedFor: String(row.installed_for) } : {}),
|
|
344
302
|
});
|
|
345
303
|
}
|
|
346
304
|
/** An Actor as a refusal names it: "Name (id)", or the id alone when there's no such Actor. */
|
|
@@ -354,12 +312,6 @@ export class HubState {
|
|
|
354
312
|
throw new Refused("unknown", `No Actor ${actorId}; hub.actors.list lists them.`);
|
|
355
313
|
return actor;
|
|
356
314
|
}
|
|
357
|
-
/** The host sandbox's status changed: every package Actor's record (its `host`) differs. */
|
|
358
|
-
hostChanged() {
|
|
359
|
-
for (const actor of this.actors())
|
|
360
|
-
if (actor.package !== "external" && actor.package !== "builtin")
|
|
361
|
-
this.#emit(() => this.#events.actorChanged?.(actor.actorId));
|
|
362
|
-
}
|
|
363
315
|
actors() {
|
|
364
316
|
const rows = this.#db.prepare("SELECT id FROM actors ORDER BY created_at, id").all();
|
|
365
317
|
return rows.map((row) => this.actor(String(row.id)));
|
|
@@ -374,51 +326,24 @@ export class HubState {
|
|
|
374
326
|
if (!this.holds(caller, hubExport))
|
|
375
327
|
throw new Refused("forbidden", `${this.named(caller)} isn't bound to hub.${hubExport}@1; a manager (the person) can bind it, or do this for it.`);
|
|
376
328
|
}
|
|
377
|
-
/** Create an Actor:
|
|
329
|
+
/** Create an Actor: a program that connects itself, with a credential that comes back once. */
|
|
378
330
|
createActor(caller, input) {
|
|
379
331
|
this.#require(caller, "actors");
|
|
380
|
-
if (input.for !== undefined) {
|
|
381
|
-
if (!this.isManager(caller))
|
|
382
|
-
throw new Refused("forbidden", `Only a manager says whom an Actor is made for; ${this.named(caller)} isn't one. Create it without for.`);
|
|
383
|
-
this.#open(input.for);
|
|
384
|
-
}
|
|
385
332
|
const actorId = `act_${randomBytes(8).toString("hex")}`;
|
|
386
|
-
const credential =
|
|
387
|
-
const insert = this.#db.prepare("INSERT INTO actors (id, name, package, state, credential, created_at
|
|
388
|
-
const madeFor = input.for ?? null;
|
|
333
|
+
const credential = randomBytes(32).toString("base64url");
|
|
334
|
+
const insert = this.#db.prepare("INSERT INTO actors (id, name, package, state, credential, created_at) VALUES (?, ?, 'external', 'created', ?, ?)");
|
|
389
335
|
this.#transaction(() => {
|
|
390
|
-
|
|
391
|
-
const { manifest } = this.package(input.package);
|
|
392
|
-
insert.run(actorId, input.name, input.package, null, now(), madeFor);
|
|
393
|
-
for (const [module, m] of Object.entries(manifest.modules))
|
|
394
|
-
for (const [side, members] of [
|
|
395
|
-
["slot", m.slots],
|
|
396
|
-
["export", m.exports],
|
|
397
|
-
])
|
|
398
|
-
for (const [member, id] of Object.entries(members)) {
|
|
399
|
-
const entry = manifest.interfaces[id];
|
|
400
|
-
if (!entry)
|
|
401
|
-
throw new Refused("invalid", `The package doesn't record ${id}.`);
|
|
402
|
-
this.#declare(actorId, side, `${module}.${member}`, { id, ...entry }, false);
|
|
403
|
-
}
|
|
404
|
-
// A package's own wiring is applied as it is installed.
|
|
405
|
-
for (const [slot, exported] of manifest.connections)
|
|
406
|
-
this.#insertBinding({ consumer: actorId, slot, provider: actorId, export: exported }, HUB);
|
|
407
|
-
}
|
|
408
|
-
else
|
|
409
|
-
insert.run(actorId, input.name, "external", digest(credential), now(), madeFor);
|
|
336
|
+
insert.run(actorId, input.name, digest(credential), now());
|
|
410
337
|
this.#audit(caller, "actor.create", {
|
|
411
338
|
actorId,
|
|
412
339
|
name: input.name,
|
|
413
|
-
package: input.package ?? "external",
|
|
414
|
-
...(input.for === undefined ? {} : { for: input.for }),
|
|
415
340
|
});
|
|
416
341
|
this.#emit(() => this.#events.actorChanged?.(actorId));
|
|
417
342
|
for (const initial of input.bindings ?? []) {
|
|
418
343
|
const wiring = { consumer: actorId, ...initial };
|
|
419
344
|
if (!this.sides(caller, wiring).export)
|
|
420
|
-
throw new Refused("forbidden", `${this.named(caller)} can't grant ${this.named(initial.provider)}'s ${initial.export}
|
|
421
|
-
if (!
|
|
345
|
+
throw new Refused("forbidden", `${this.named(caller)} can't grant ${this.named(initial.provider)}'s ${initial.export}: only a manager, the provider or an Actor bound to that export can.`);
|
|
346
|
+
if (!this.declaration(actorId, "slot", initial.slot)) {
|
|
422
347
|
if (!NAME.test(initial.slot))
|
|
423
348
|
throw new Refused("invalid", `${JSON.stringify(initial.slot)} isn't a valid slot name.`);
|
|
424
349
|
const offered = this.declaration(initial.provider, "export", initial.export);
|
|
@@ -429,7 +354,7 @@ export class HubState {
|
|
|
429
354
|
this.#insertBinding(wiring, caller);
|
|
430
355
|
}
|
|
431
356
|
});
|
|
432
|
-
return
|
|
357
|
+
return { actorId, credential };
|
|
433
358
|
}
|
|
434
359
|
/** Whether an external Actor's credential is right, compared in constant time. */
|
|
435
360
|
authenticate(actorId, credential) {
|
|
@@ -441,127 +366,44 @@ export class HubState {
|
|
|
441
366
|
// Both sides are SHA-256 digests, so the lengths always match.
|
|
442
367
|
return timingSafeEqual(Buffer.from(row.credential), digest(credential));
|
|
443
368
|
}
|
|
444
|
-
|
|
445
|
-
this.#require(caller, "actors");
|
|
446
|
-
const actor = this.actor(actorId);
|
|
447
|
-
if (actor.package === "external" || actor.package === "builtin")
|
|
448
|
-
throw new Refused("state", `${this.named(actorId)} isn't a package Actor, so the Hub doesn't run its process.`);
|
|
449
|
-
return actor;
|
|
450
|
-
}
|
|
451
|
-
/** Refused unless the caller may start the Actor and it can be started now; changes nothing. */
|
|
452
|
-
checkStart(caller, actorId) {
|
|
453
|
-
const actor = this.#packageActor(caller, actorId);
|
|
454
|
-
if (actor.state !== "created" && actor.state !== "stopped")
|
|
455
|
-
throw new Refused("state", `${this.named(actorId)} is ${actor.state === "running" ? "already running" : "closed and can't start again; make another"}.`);
|
|
456
|
-
}
|
|
457
|
-
/** Start a package Actor: recorded as running; the Hub spawns its process. */
|
|
458
|
-
start(caller, actorId) {
|
|
459
|
-
this.checkStart(caller, actorId);
|
|
460
|
-
this.#transaction(() => {
|
|
461
|
-
this.#db.prepare("UPDATE actors SET state = 'running' WHERE id = ?").run(actorId);
|
|
462
|
-
this.#audit(caller, "actor.start", { actorId });
|
|
463
|
-
this.#emit(() => this.#events.actorChanged?.(actorId));
|
|
464
|
-
});
|
|
465
|
-
}
|
|
466
|
-
stop(caller, actorId) {
|
|
467
|
-
const actor = this.#packageActor(caller, actorId);
|
|
468
|
-
if (actor.state !== "running")
|
|
469
|
-
throw new Refused("state", `${this.named(actorId)} is ${actor.state}, not running.`);
|
|
470
|
-
this.#transaction(() => {
|
|
471
|
-
this.#db.prepare("UPDATE actors SET state = 'stopped' WHERE id = ?").run(actorId);
|
|
472
|
-
this.#audit(caller, "actor.stop", { actorId });
|
|
473
|
-
this.#emit(() => this.#events.actorChanged?.(actorId));
|
|
474
|
-
});
|
|
475
|
-
}
|
|
476
|
-
/** Close an Actor for good: its bindings both ways, its declarations and its open requests go. */
|
|
369
|
+
/** Close an Actor for good: its bindings both ways and its declarations go. */
|
|
477
370
|
closeActor(caller, actorId) {
|
|
478
371
|
this.#require(caller, "actors");
|
|
479
372
|
const actor = this.actor(actorId);
|
|
480
|
-
if (actorId === HUB || actorId === PERSON
|
|
373
|
+
if (actorId === HUB || actorId === PERSON)
|
|
481
374
|
throw new Refused("forbidden", `${this.named(actorId)} is built in and can't be closed.`);
|
|
482
375
|
if (actor.state === "closed")
|
|
483
376
|
return;
|
|
484
377
|
this.#transaction(() => {
|
|
485
|
-
const pending = this.#pending("consumer = ? OR provider = ? OR requested_by = ?", actorId, actorId, actorId);
|
|
486
378
|
this.#db.prepare("UPDATE actors SET state = 'closed' WHERE id = ?").run(actorId);
|
|
487
379
|
this.#removeBindings("consumer = ? OR provider = ?", actorId, actorId);
|
|
488
380
|
this.#db.prepare("DELETE FROM declarations WHERE actor = ?").run(actorId);
|
|
489
|
-
for (const open of pending)
|
|
490
|
-
this.#decide(open, "closed");
|
|
491
381
|
this.#audit(caller, "actor.close", { actorId });
|
|
492
382
|
this.#emit(() => this.#events.exportsChanged?.(actorId));
|
|
493
383
|
this.#emit(() => this.#events.actorChanged?.(actorId));
|
|
494
384
|
this.#emit(() => this.#events.closed?.(actorId));
|
|
495
385
|
});
|
|
496
386
|
}
|
|
497
|
-
|
|
498
|
-
|
|
499
|
-
|
|
500
|
-
|
|
501
|
-
|
|
502
|
-
|
|
503
|
-
|
|
504
|
-
|
|
505
|
-
|
|
506
|
-
|
|
507
|
-
|
|
508
|
-
|
|
509
|
-
|
|
510
|
-
|
|
511
|
-
|
|
512
|
-
|
|
513
|
-
catch (error) {
|
|
514
|
-
throw new Refused("invalid", error instanceof Error ? error.message : String(error));
|
|
515
|
-
}
|
|
516
|
-
const verified = verifyArchive(bytes, expected);
|
|
517
|
-
if (!verified.ok)
|
|
518
|
-
throw new Refused("invalid", verified.problems.join("\n"));
|
|
519
|
-
const { hash, manifest, files } = verified;
|
|
520
|
-
if (this.#db.prepare("SELECT 1 FROM packages WHERE hash = ?").get(hash))
|
|
521
|
-
return { hash, manifest };
|
|
522
|
-
const dir = join(this.home, "packages", hash);
|
|
523
|
-
for (const [file, data] of files) {
|
|
524
|
-
const target = resolve(dir, file);
|
|
525
|
-
// verifyArchive allows only agentorg.json and dist/<module>.js; this keeps it so.
|
|
526
|
-
if (relative(dir, target).startsWith(`..${sep}`))
|
|
527
|
-
throw new Refused("invalid", `Unsafe path ${file}.`);
|
|
528
|
-
mkdirSync(dirname(target), { recursive: true, mode: 0o700 });
|
|
529
|
-
writeFileSync(target, data, { mode: 0o600 });
|
|
530
|
-
}
|
|
531
|
-
this.#transaction(() => {
|
|
532
|
-
this.#db
|
|
533
|
-
.prepare("INSERT INTO packages (hash, name, version, manifest, added_by, added_at) VALUES (?, ?, ?, ?, ?, ?)")
|
|
534
|
-
.run(hash, manifest.name, manifest.version, JSON.stringify(manifest), caller, now());
|
|
535
|
-
this.#audit(caller, "package.add", { hash, name: manifest.name, version: manifest.version });
|
|
536
|
-
});
|
|
537
|
-
return { hash, manifest };
|
|
538
|
-
}
|
|
539
|
-
packages() {
|
|
540
|
-
const rows = this.#db
|
|
541
|
-
.prepare("SELECT hash, name, version FROM packages ORDER BY added_at, hash")
|
|
542
|
-
.all();
|
|
543
|
-
return rows.map((row) => ({
|
|
544
|
-
hash: String(row.hash),
|
|
545
|
-
name: String(row.name),
|
|
546
|
-
version: String(row.version),
|
|
387
|
+
/**
|
|
388
|
+
* Drop the bindings through one of an Actor's slots or exports, its interface having changed,
|
|
389
|
+
* but those `keeps` says still match: each is audited as `binding.drop`. Dropping only takes
|
|
390
|
+
* access away. Returns what was dropped.
|
|
391
|
+
*/
|
|
392
|
+
#dropBindingsOf(actorId, side, name, by, keeps = () => false) {
|
|
393
|
+
const dropped = this.bindings(actorId).filter((b) => (side === "slot"
|
|
394
|
+
? b.consumer === actorId && b.slot === name
|
|
395
|
+
: b.provider === actorId && b.export === name) && !keeps(b));
|
|
396
|
+
for (const b of dropped)
|
|
397
|
+
this.#removeBindings("consumer = ? AND slot = ?", b.consumer, b.slot);
|
|
398
|
+
const wirings = dropped.map(({ consumer, slot, provider, export: exported }) => ({
|
|
399
|
+
consumer,
|
|
400
|
+
slot,
|
|
401
|
+
provider,
|
|
402
|
+
export: exported,
|
|
547
403
|
}));
|
|
548
|
-
|
|
549
|
-
|
|
550
|
-
|
|
551
|
-
if (!row)
|
|
552
|
-
throw new Refused("unknown", `No package ${hash}.`);
|
|
553
|
-
return { hash, manifest: JSON.parse(String(row.manifest)) };
|
|
554
|
-
}
|
|
555
|
-
/** Remove a package no live Actor runs. Its files stay until a later clean-up. */
|
|
556
|
-
removePackage(caller, hash) {
|
|
557
|
-
this.#require(caller, "packages");
|
|
558
|
-
this.package(hash);
|
|
559
|
-
if (this.#db.prepare("SELECT 1 FROM actors WHERE package = ? AND state != 'closed'").get(hash))
|
|
560
|
-
throw new Refused("in_use", `An Actor runs ${hash}.`);
|
|
561
|
-
this.#transaction(() => {
|
|
562
|
-
this.#db.prepare("DELETE FROM packages WHERE hash = ?").run(hash);
|
|
563
|
-
this.#audit(caller, "package.remove", { hash });
|
|
564
|
-
});
|
|
404
|
+
for (const wiring of wirings)
|
|
405
|
+
this.#audit(by, "binding.drop", { ...wiring, why: "its interface changed" });
|
|
406
|
+
return wirings;
|
|
565
407
|
}
|
|
566
408
|
// ── Declarations: what each Actor takes and offers ──────────────────────────
|
|
567
409
|
#declare(actor, side, name, iface, dynamic) {
|
|
@@ -604,13 +446,12 @@ export class HubState {
|
|
|
604
446
|
}
|
|
605
447
|
/**
|
|
606
448
|
* An external Actor's slots and static exports, as it declares them when it connects; they
|
|
607
|
-
* replace what it declared before. A
|
|
608
|
-
*
|
|
449
|
+
* replace what it declared before. A name redeclared as another interface loses its bindings
|
|
450
|
+
* (audited as `binding.drop`), since a binding holds only while both ends declare the same one.
|
|
451
|
+
* A name it no longer declares is dropped, with its bindings.
|
|
609
452
|
*/
|
|
610
453
|
declareExternal(actorId, declares) {
|
|
611
|
-
|
|
612
|
-
if (actor.package !== "external")
|
|
613
|
-
throw new Refused("forbidden", `${this.named(actorId)} declares through its package; an Actor of another package declares otherwise.`);
|
|
454
|
+
this.#open(actorId);
|
|
614
455
|
this.#transaction(() => {
|
|
615
456
|
for (const [side, members] of [
|
|
616
457
|
["slot", declares.slots],
|
|
@@ -622,17 +463,21 @@ export class HubState {
|
|
|
622
463
|
this.#checked(iface);
|
|
623
464
|
if (side === "slot") {
|
|
624
465
|
// The Actor's own slots are its to name: an exposure under the same name yields,
|
|
625
|
-
// with its binding
|
|
466
|
+
// with its binding.
|
|
626
467
|
if (this.declaration(actorId, side, name)?.dynamic)
|
|
627
468
|
this.#drop(actorId, side, name);
|
|
628
|
-
for (const open of this.#pending("consumer = ? AND slot = ? AND exposed = 1", actorId, name))
|
|
629
|
-
this.#decide(open, "closed");
|
|
630
469
|
}
|
|
470
|
+
// A binding holds while both ends declare the same interface: redeclared as another,
|
|
471
|
+
// the name's bindings whose other end doesn't declare it are dropped.
|
|
631
472
|
const known = this.declaration(actorId, side, name);
|
|
632
|
-
if (known &&
|
|
633
|
-
this.#
|
|
634
|
-
|
|
635
|
-
|
|
473
|
+
if (known && compareInterfaces(known.iface, iface) !== "same") {
|
|
474
|
+
this.#dropBindingsOf(actorId, side, name, actorId, (b) => {
|
|
475
|
+
const other = side === "slot"
|
|
476
|
+
? this.declaration(b.provider, "export", b.export)?.iface
|
|
477
|
+
: this.declaration(b.consumer, "slot", b.slot)?.iface;
|
|
478
|
+
return !!other && compareInterfaces(iface, other) === "same";
|
|
479
|
+
});
|
|
480
|
+
}
|
|
636
481
|
this.#declare(actorId, side, name, iface, false);
|
|
637
482
|
}
|
|
638
483
|
const kept = Object.keys(members);
|
|
@@ -648,64 +493,17 @@ export class HubState {
|
|
|
648
493
|
this.#emit(() => this.#events.exportsChanged?.(actorId));
|
|
649
494
|
});
|
|
650
495
|
}
|
|
651
|
-
#isBound(actor, side, name) {
|
|
652
|
-
return !!this.#db
|
|
653
|
-
.prepare(side === "slot"
|
|
654
|
-
? "SELECT 1 FROM bindings WHERE consumer = ? AND slot = ?"
|
|
655
|
-
: "SELECT 1 FROM bindings WHERE provider = ? AND export = ?")
|
|
656
|
-
.get(actor, name);
|
|
657
|
-
}
|
|
658
496
|
/** A declaration gone, and the bindings through it. */
|
|
659
497
|
#drop(actor, side, name) {
|
|
660
498
|
this.#db
|
|
661
499
|
.prepare("DELETE FROM declarations WHERE actor = ? AND side = ? AND name = ?")
|
|
662
500
|
.run(actor, side, name);
|
|
663
501
|
const where = side === "slot" ? "consumer = ? AND slot = ?" : "provider = ? AND export = ?";
|
|
664
|
-
const pending = this.#pending(where, actor, name);
|
|
665
502
|
this.#removeBindings(where, actor, name);
|
|
666
|
-
for (const open of pending)
|
|
667
|
-
this.#decide(open, "closed");
|
|
668
|
-
}
|
|
669
|
-
/** Offer an export at run time; it lasts until withdrawn or until the Actor disconnects. */
|
|
670
|
-
offer(actorId, name, iface) {
|
|
671
|
-
this.#open(actorId);
|
|
672
|
-
if (!MEMBER.test(name))
|
|
673
|
-
throw new Refused("invalid", `${JSON.stringify(name)} isn't a valid export name.`);
|
|
674
|
-
if (this.declaration(actorId, "export", name))
|
|
675
|
-
throw new Refused("conflict", `${this.named(actorId)} already offers ${name}; offer it under another name${this.declaration(actorId, "export", name)?.dynamic ? ", or withdraw it first" : ""}.`);
|
|
676
|
-
this.#checked(iface);
|
|
677
|
-
this.#transaction(() => {
|
|
678
|
-
this.#declare(actorId, "export", name, iface, true);
|
|
679
|
-
this.#audit(actorId, "export.offer", { export: name, interface: iface.id });
|
|
680
|
-
this.#emit(() => this.#events.exportsChanged?.(actorId));
|
|
681
|
-
});
|
|
682
|
-
}
|
|
683
|
-
/** Withdraw an export offered at run time: the bindings to it end. */
|
|
684
|
-
withdraw(actorId, name) {
|
|
685
|
-
const declared = this.declaration(actorId, "export", name);
|
|
686
|
-
if (!declared?.dynamic)
|
|
687
|
-
throw new Refused("unknown", `${this.named(actorId)} offers no ${name} at run time to withdraw; only exports offered at run time can be.`);
|
|
688
|
-
this.#transaction(() => {
|
|
689
|
-
this.#drop(actorId, "export", name);
|
|
690
|
-
this.#audit(actorId, "export.withdraw", { export: name });
|
|
691
|
-
this.#emit(() => this.#events.exportsChanged?.(actorId));
|
|
692
|
-
});
|
|
693
|
-
}
|
|
694
|
-
/** An Actor's connection ended: what it offered at run time goes, with the bindings to it. */
|
|
695
|
-
disconnected(actorId) {
|
|
696
|
-
this.#transaction(() => {
|
|
697
|
-
const dynamic = this.#db
|
|
698
|
-
.prepare("SELECT name FROM declarations WHERE actor = ? AND side = 'export' AND dynamic = 1")
|
|
699
|
-
.all(actorId);
|
|
700
|
-
for (const row of dynamic)
|
|
701
|
-
this.#drop(actorId, "export", String(row.name));
|
|
702
|
-
if (dynamic.length)
|
|
703
|
-
this.#emit(() => this.#events.exportsChanged?.(actorId));
|
|
704
|
-
});
|
|
705
503
|
}
|
|
706
504
|
exports(actor) {
|
|
707
505
|
const rows = this.#db
|
|
708
|
-
.prepare(`SELECT d.actor, d.name, d.iface
|
|
506
|
+
.prepare(`SELECT d.actor, d.name, d.iface FROM declarations d JOIN actors a ON a.id = d.actor
|
|
709
507
|
WHERE d.side = 'export' AND a.state != 'closed' ${actor ? "AND d.actor = ?" : ""}
|
|
710
508
|
ORDER BY d.actor, d.name`)
|
|
711
509
|
.all(...(actor ? [actor] : []));
|
|
@@ -716,7 +514,6 @@ export class HubState {
|
|
|
716
514
|
export: String(row.name),
|
|
717
515
|
interface: iface.id,
|
|
718
516
|
hash: iface.hash,
|
|
719
|
-
dynamic: !!row.dynamic,
|
|
720
517
|
};
|
|
721
518
|
});
|
|
722
519
|
}
|
|
@@ -766,21 +563,6 @@ export class HubState {
|
|
|
766
563
|
slot: manager || caller === wiring.consumer,
|
|
767
564
|
};
|
|
768
565
|
}
|
|
769
|
-
/** Who can give the side a request needs: exactly the Actors `sides` grants it to. */
|
|
770
|
-
approvers(request) {
|
|
771
|
-
const managers = this.#db
|
|
772
|
-
.prepare("SELECT DISTINCT consumer FROM bindings WHERE provider = ? AND export = 'bindings'")
|
|
773
|
-
.all(HUB).map((m) => String(m.consumer));
|
|
774
|
-
const own = request.needs === "slot"
|
|
775
|
-
? [request.consumer]
|
|
776
|
-
: [
|
|
777
|
-
request.provider,
|
|
778
|
-
...this.#db
|
|
779
|
-
.prepare("SELECT DISTINCT consumer FROM bindings WHERE provider = ? AND export = ?")
|
|
780
|
-
.all(request.provider, request.export).map((h) => String(h.consumer)),
|
|
781
|
-
];
|
|
782
|
-
return [...new Set([...own, ...managers])];
|
|
783
|
-
}
|
|
784
566
|
/** The two ends exist, are live, and take and offer the same interface. */
|
|
785
567
|
#ends(wiring) {
|
|
786
568
|
this.#open(wiring.consumer);
|
|
@@ -816,8 +598,6 @@ export class HubState {
|
|
|
816
598
|
return;
|
|
817
599
|
if (removed.some((b) => b.consumer === PERSON && b.provider === HUB && !b.exposed))
|
|
818
600
|
throw new Refused("forbidden", "The person's hub bindings stay, so someone can always manage the Hub.");
|
|
819
|
-
// Requests whose requester held its end through these bindings can't be approved any more.
|
|
820
|
-
const open = this.#pending("1 = 1");
|
|
821
601
|
this.#db.prepare(`DELETE FROM bindings WHERE ${where}`).run(...params);
|
|
822
602
|
// A slot made at run time goes with its binding.
|
|
823
603
|
const dropExposed = this.#db.prepare("DELETE FROM declarations WHERE actor = ? AND side = 'slot' AND name = ? AND dynamic = 1");
|
|
@@ -825,35 +605,13 @@ export class HubState {
|
|
|
825
605
|
if (binding.exposed)
|
|
826
606
|
dropExposed.run(binding.consumer, binding.slot);
|
|
827
607
|
this.#emit(() => this.#events.unbound?.(removed));
|
|
828
|
-
for (const pending of open)
|
|
829
|
-
if (!this.#standing(pending.request))
|
|
830
|
-
this.#decide(pending, "closed");
|
|
831
608
|
}
|
|
832
|
-
/**
|
|
833
|
-
* Bind a slot to an export. A caller with both ends binds. A caller with one end approves an
|
|
834
|
-
* open request for this binding from the other end, if its requester still holds that end;
|
|
835
|
-
* otherwise its call becomes a request to the other end's approvers. A caller with neither end
|
|
836
|
-
* is refused.
|
|
837
|
-
*/
|
|
609
|
+
/** Bind a slot to an export: the caller must hold both ends. */
|
|
838
610
|
bind(caller, wiring) {
|
|
839
611
|
const sides = this.sides(caller, wiring);
|
|
840
|
-
if (!sides.export &&
|
|
841
|
-
throw new Refused("forbidden", `${this.named(caller)}
|
|
842
|
-
|
|
843
|
-
const has = sides.export ? "export" : "slot";
|
|
844
|
-
if (!this.#asked(wiring, has))
|
|
845
|
-
return { bound: false, requestId: this.#ask(caller, wiring, "", other(has)).requestId };
|
|
846
|
-
}
|
|
847
|
-
this.#transaction(() => {
|
|
848
|
-
// Every open request for this slot is settled: the ones for this binding are approved, and
|
|
849
|
-
// the rest can't be made any more.
|
|
850
|
-
const pending = this.#pending("consumer = ? AND slot = ?", wiring.consumer, wiring.slot);
|
|
851
|
-
this.#insertBinding(wiring, caller);
|
|
852
|
-
for (const open of pending)
|
|
853
|
-
this.#decide(open, open.request.provider === wiring.provider && open.request.export === wiring.export
|
|
854
|
-
? "approved"
|
|
855
|
-
: "closed");
|
|
856
|
-
});
|
|
612
|
+
if (!(sides.export && sides.slot))
|
|
613
|
+
throw new Refused("forbidden", `${this.named(caller)} doesn't hold both ends of this binding: the export side is ${this.named(wiring.provider)}, the slot side ${this.named(wiring.consumer)}. A manager can bind it.`);
|
|
614
|
+
this.#transaction(() => this.#insertBinding(wiring, caller));
|
|
857
615
|
return { bound: true };
|
|
858
616
|
}
|
|
859
617
|
/** Unbind a slot: its consumer, its provider or a manager may. */
|
|
@@ -875,68 +633,44 @@ export class HubState {
|
|
|
875
633
|
}
|
|
876
634
|
/**
|
|
877
635
|
* Expose an export to one Actor: a slot made on `to` at run time, typed by the export's
|
|
878
|
-
* interface and bound to it, in one step. The
|
|
879
|
-
* consumer
|
|
880
|
-
* request to the other end's approvers, who approve by exposing the same; with neither, it is
|
|
881
|
-
* refused. The same export already exposed to `to` gives that slot back.
|
|
636
|
+
* interface and bound to it, in one step. The caller must hold both ends, with `to` as the
|
|
637
|
+
* consumer. The same export already exposed to `to` gives that slot back.
|
|
882
638
|
*/
|
|
883
639
|
expose(caller, input) {
|
|
884
640
|
const to = this.#open(input.to);
|
|
885
641
|
const provider = this.#open(input.provider);
|
|
886
|
-
// Authority first
|
|
642
|
+
// Authority first: a caller without it learns nothing more.
|
|
887
643
|
const ends = { consumer: to.actorId, provider: provider.actorId, export: input.export };
|
|
888
644
|
const sides = this.sides(caller, { ...ends, slot: "" });
|
|
889
|
-
if (!sides.export &&
|
|
890
|
-
throw new Refused("forbidden", `${this.named(caller)} can't expose ${input.export} of ${this.named(provider.actorId)} to ${this.named(to.actorId)}:
|
|
891
|
-
if (
|
|
645
|
+
if (!(sides.export && sides.slot))
|
|
646
|
+
throw new Refused("forbidden", `${this.named(caller)} can't expose ${input.export} of ${this.named(provider.actorId)} to ${this.named(to.actorId)}: a manager can.`);
|
|
647
|
+
if (to.actorId === HUB)
|
|
892
648
|
throw new Refused("forbidden", `${this.named(to.actorId)} is the Hub itself and takes no slots.`);
|
|
893
649
|
const offered = this.declaration(provider.actorId, "export", input.export);
|
|
894
650
|
if (!offered)
|
|
895
651
|
throw new Refused("unknown", `${this.named(provider.actorId)} offers no ${input.export}.`);
|
|
896
|
-
|
|
897
|
-
|
|
898
|
-
|
|
899
|
-
|
|
652
|
+
const already = this.bindings(to.actorId).find((b) => b.consumer === to.actorId &&
|
|
653
|
+
b.exposed &&
|
|
654
|
+
b.provider === provider.actorId &&
|
|
655
|
+
b.export === input.export);
|
|
900
656
|
if (already && (input.as === undefined || input.as === already.slot))
|
|
901
657
|
return { exposed: true, slot: already.slot };
|
|
902
|
-
const
|
|
903
|
-
.prepare("SELECT * FROM requests WHERE status = 'open' AND exposed = 1 AND consumer = ?")
|
|
904
|
-
.all(to.actorId).map(requestOf);
|
|
905
|
-
// An open request's name for this export is taken again, so approving needs no `as`.
|
|
906
|
-
const slot = input.as ??
|
|
907
|
-
requested.find(same)?.slot ??
|
|
908
|
-
this.#freeName(to.actorId, exposedName(provider.name, input.export), requested);
|
|
658
|
+
const slot = input.as ?? this.#freeName(to.actorId, exposedName(provider.name, input.export));
|
|
909
659
|
if (!NAME.test(slot) || slot.length > EXPOSED_MAX)
|
|
910
660
|
throw new Refused("invalid", `${JSON.stringify(slot)} isn't a slot name: one word of letters, digits and _, at most ${EXPOSED_MAX}.`);
|
|
911
|
-
// A request reserves no name: the first exposure made under it has it, and the rest close.
|
|
912
661
|
if (this.declaration(to.actorId, "slot", slot))
|
|
913
662
|
throw new Refused("conflict", `${this.named(to.actorId)} already has a slot ${slot}; name another with as.`);
|
|
914
663
|
const wiring = { ...ends, slot };
|
|
915
|
-
if (!(sides.export && sides.slot)) {
|
|
916
|
-
const has = sides.export ? "export" : "slot";
|
|
917
|
-
if (!this.#asked(wiring, has)) {
|
|
918
|
-
const mine = requested.filter((r) => r.requestedBy === caller && !(same(r) && r.slot === slot));
|
|
919
|
-
if (mine.length >= MAX_OPEN_EXPOSURES)
|
|
920
|
-
throw new Refused("limit", `${this.named(caller)} has ${MAX_OPEN_EXPOSURES} exposures to ${this.named(to.actorId)} waiting; wait for them to be decided, or withdraw one by exposing it.`);
|
|
921
|
-
const request = this.#ask(caller, wiring, "", other(has), true);
|
|
922
|
-
return { exposed: false, slot, requestId: request.requestId };
|
|
923
|
-
}
|
|
924
|
-
}
|
|
925
664
|
this.#transaction(() => {
|
|
926
|
-
// Settled: this slot's requests (approved if for this export), and this export's requests
|
|
927
|
-
// to `to` under other names, which can't be made any more.
|
|
928
|
-
const pending = this.#pending("consumer = ? AND (slot = ? OR (exposed = 1 AND provider = ? AND export = ?))", wiring.consumer, wiring.slot, wiring.provider, wiring.export);
|
|
929
665
|
this.#declare(to.actorId, "slot", slot, offered.iface, true);
|
|
930
666
|
this.#insertBinding(wiring, caller);
|
|
931
|
-
for (const open of pending)
|
|
932
|
-
this.#decide(open, same(open.request) && open.request.slot === slot ? "approved" : "closed");
|
|
933
667
|
this.#audit(caller, "expose", { ...wiring });
|
|
934
668
|
});
|
|
935
669
|
return { exposed: true, slot };
|
|
936
670
|
}
|
|
937
671
|
/** A name free on `to`: `base`, else `base_2`, `base_3`…, each within the longest name. */
|
|
938
|
-
#freeName(to, base
|
|
939
|
-
const taken = (name) => !!this.declaration(to, "slot", name)
|
|
672
|
+
#freeName(to, base) {
|
|
673
|
+
const taken = (name) => !!this.declaration(to, "slot", name);
|
|
940
674
|
for (let n = 1;; n++) {
|
|
941
675
|
const suffix = n === 1 ? "" : `_${n}`;
|
|
942
676
|
const name = base.slice(0, EXPOSED_MAX - suffix.length) + suffix;
|
|
@@ -951,123 +685,6 @@ export class HubState {
|
|
|
951
685
|
throw new Refused("unknown", `${this.named(to)} has no slot ${slot} made by expose; unbind a declared slot instead.`);
|
|
952
686
|
this.unbind(caller, to, slot);
|
|
953
687
|
}
|
|
954
|
-
/**
|
|
955
|
-
* Ask for a binding the caller holds one end of; it goes to the other end's approvers. Asking
|
|
956
|
-
* again for the same wiring while a request is open returns that request.
|
|
957
|
-
*/
|
|
958
|
-
request(caller, wiring, reason) {
|
|
959
|
-
const sides = this.sides(caller, wiring);
|
|
960
|
-
if (!sides.export && !sides.slot)
|
|
961
|
-
throw new Refused("forbidden", `${this.named(caller)} holds neither end of this binding: the export side is ${this.named(wiring.provider)}, an Actor bound to that export, or a manager; the slot side is ${this.named(wiring.consumer)} or a manager. Ask one of them.`);
|
|
962
|
-
if (sides.export && sides.slot)
|
|
963
|
-
throw new Refused("state", `${this.named(caller)} holds both ends: bind it instead.`);
|
|
964
|
-
const has = sides.export ? "export" : "slot";
|
|
965
|
-
if (this.#asked(wiring, has))
|
|
966
|
-
throw new Refused("state", "The other end has asked already: bind to approve.");
|
|
967
|
-
return this.#ask(caller, wiring, reason, other(has));
|
|
968
|
-
}
|
|
969
|
-
/** Whether the other end has an open request for this binding that `has` would complete. */
|
|
970
|
-
#asked(wiring, has) {
|
|
971
|
-
return this.#db
|
|
972
|
-
.prepare("SELECT * FROM requests WHERE status = 'open' AND needs = ? AND consumer = ? AND slot = ? AND provider = ? AND export = ?")
|
|
973
|
-
.all(has, wiring.consumer, wiring.slot, wiring.provider, wiring.export)
|
|
974
|
-
.map(requestOf)
|
|
975
|
-
.some((request) => this.#standing(request));
|
|
976
|
-
}
|
|
977
|
-
#ask(caller, wiring, reason, needs, exposed = false) {
|
|
978
|
-
// An exposure's slot doesn't exist until it is approved; `expose` checked its ends.
|
|
979
|
-
if (!exposed)
|
|
980
|
-
this.#ends(wiring);
|
|
981
|
-
if (this.binding(wiring.consumer, wiring.slot))
|
|
982
|
-
throw new Refused("bound", `${this.named(wiring.consumer)}'s ${wiring.slot} is already bound; unbind it first.`);
|
|
983
|
-
const open = this.#db
|
|
984
|
-
.prepare("SELECT * FROM requests WHERE status = 'open' AND requested_by = ? AND consumer = ? AND slot = ? AND provider = ? AND export = ?")
|
|
985
|
-
.get(caller, wiring.consumer, wiring.slot, wiring.provider, wiring.export);
|
|
986
|
-
if (open)
|
|
987
|
-
return requestOf(open);
|
|
988
|
-
const request = {
|
|
989
|
-
requestId: `req_${randomBytes(8).toString("hex")}`,
|
|
990
|
-
...wiring,
|
|
991
|
-
needs,
|
|
992
|
-
reason,
|
|
993
|
-
requestedBy: caller,
|
|
994
|
-
at: now(),
|
|
995
|
-
...(exposed ? { exposed: true } : {}),
|
|
996
|
-
};
|
|
997
|
-
this.#transaction(() => {
|
|
998
|
-
this.#db
|
|
999
|
-
.prepare("INSERT INTO requests (id, consumer, slot, provider, export, reason, requested_by, at, status, needs, exposed) VALUES (?, ?, ?, ?, ?, ?, ?, ?, 'open', ?, ?)")
|
|
1000
|
-
.run(request.requestId, wiring.consumer, wiring.slot, wiring.provider, wiring.export, reason, caller, request.at, needs, exposed ? 1 : 0);
|
|
1001
|
-
this.#audit(caller, "request", {
|
|
1002
|
-
...wiring,
|
|
1003
|
-
needs,
|
|
1004
|
-
requestId: request.requestId,
|
|
1005
|
-
...(exposed ? { exposed: true } : {}),
|
|
1006
|
-
});
|
|
1007
|
-
const approvers = this.approvers(request).filter((a) => a !== caller);
|
|
1008
|
-
this.#emit(() => this.#events.requested?.(request, approvers));
|
|
1009
|
-
});
|
|
1010
|
-
return request;
|
|
1011
|
-
}
|
|
1012
|
-
/** The open requests the caller could approve. */
|
|
1013
|
-
requests(caller) {
|
|
1014
|
-
const rows = this.#db
|
|
1015
|
-
.prepare("SELECT * FROM requests WHERE status = 'open' ORDER BY at")
|
|
1016
|
-
.all();
|
|
1017
|
-
return rows
|
|
1018
|
-
.map(requestOf)
|
|
1019
|
-
.filter((request) => request.requestedBy !== caller &&
|
|
1020
|
-
this.#standing(request) &&
|
|
1021
|
-
this.approvers(request).includes(caller));
|
|
1022
|
-
}
|
|
1023
|
-
/** The caller's own requests with how each stands, newest first. */
|
|
1024
|
-
mine(caller, limit = 100) {
|
|
1025
|
-
const rows = this.#db
|
|
1026
|
-
.prepare("SELECT * FROM requests WHERE requested_by = ? ORDER BY at DESC, rowid DESC LIMIT ?")
|
|
1027
|
-
.all(caller, limit);
|
|
1028
|
-
return rows.map((row) => ({ ...requestOf(row), status: row.status }));
|
|
1029
|
-
}
|
|
1030
|
-
/** A request can still be approved only while its requester holds the end it brought. */
|
|
1031
|
-
#standing(request) {
|
|
1032
|
-
return this.sides(request.requestedBy, request)[other(request.needs)];
|
|
1033
|
-
}
|
|
1034
|
-
/**
|
|
1035
|
-
* Open requests matching a condition, each with the approvers it went to. Taken before bindings
|
|
1036
|
-
* change, so the decision reaches the same Actors as the request.
|
|
1037
|
-
*/
|
|
1038
|
-
#pending(where, ...params) {
|
|
1039
|
-
return this.#db
|
|
1040
|
-
.prepare(`SELECT * FROM requests WHERE status = 'open' AND (${where})`)
|
|
1041
|
-
.all(...params).map((row) => {
|
|
1042
|
-
const request = requestOf(row);
|
|
1043
|
-
return {
|
|
1044
|
-
request,
|
|
1045
|
-
approvers: this.approvers(request).filter((a) => a !== request.requestedBy),
|
|
1046
|
-
};
|
|
1047
|
-
});
|
|
1048
|
-
}
|
|
1049
|
-
#decide({ request, approvers }, status) {
|
|
1050
|
-
// A request is decided once; a later path that finds it again leaves it be.
|
|
1051
|
-
const { changes } = this.#db
|
|
1052
|
-
.prepare("UPDATE requests SET status = ? WHERE id = ? AND status = 'open'")
|
|
1053
|
-
.run(status, request.requestId);
|
|
1054
|
-
if (!changes)
|
|
1055
|
-
return;
|
|
1056
|
-
// The requester hears too; events are at most once, so it can also read the outcome in `mine`.
|
|
1057
|
-
const to = [...new Set([...approvers, request.requestedBy])];
|
|
1058
|
-
this.#emit(() => this.#events.decided?.(request, status, to));
|
|
1059
|
-
}
|
|
1060
|
-
decline(caller, requestId) {
|
|
1061
|
-
const request = this.requests(caller).find((r) => r.requestId === requestId);
|
|
1062
|
-
if (!request)
|
|
1063
|
-
throw new Refused("unknown", `No open request ${requestId} for ${this.named(caller)} to decide; hub.grants.requests lists those it can.`);
|
|
1064
|
-
this.#transaction(() => {
|
|
1065
|
-
const [pending] = this.#pending("id = ?", requestId);
|
|
1066
|
-
if (pending)
|
|
1067
|
-
this.#decide(pending, "declined");
|
|
1068
|
-
this.#audit(caller, "decline", { requestId });
|
|
1069
|
-
});
|
|
1070
|
-
}
|
|
1071
688
|
// ── Audit ───────────────────────────────────────────────────────────────────
|
|
1072
689
|
audit(since = 0, limit = 100) {
|
|
1073
690
|
const rows = this.#db
|