@agentorg/cli 0.0.5 → 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.
Files changed (206) hide show
  1. package/README.md +18 -102
  2. package/dist/node_modules/@agentorg/hub/dist/src/hub-actor.d.ts +2 -7
  3. package/dist/node_modules/@agentorg/hub/dist/src/hub-actor.js +4 -144
  4. package/dist/node_modules/@agentorg/hub/dist/src/index.d.ts +1 -3
  5. package/dist/node_modules/@agentorg/hub/dist/src/index.js +1 -3
  6. package/dist/node_modules/@agentorg/hub/dist/src/router.d.ts +6 -57
  7. package/dist/node_modules/@agentorg/hub/dist/src/router.js +38 -413
  8. package/dist/node_modules/@agentorg/hub/dist/src/state.d.ts +17 -317
  9. package/dist/node_modules/@agentorg/hub/dist/src/state.js +42 -536
  10. package/dist/node_modules/@agentorg/hub/dist/src/validate.d.ts +1 -3
  11. package/dist/node_modules/@agentorg/hub/dist/src/validate.js +3 -15
  12. package/dist/node_modules/@agentorg/hub/package.json +0 -3
  13. package/dist/node_modules/@agentorg/interfaces/dist/src/agent-provider.d.ts +132 -0
  14. package/dist/node_modules/@agentorg/interfaces/dist/src/agent-provider.js +105 -0
  15. package/dist/node_modules/@agentorg/interfaces/dist/src/agent-settings.d.ts +42 -0
  16. package/dist/node_modules/@agentorg/interfaces/dist/src/agent-settings.js +53 -0
  17. package/dist/node_modules/@agentorg/interfaces/dist/src/chat.d.ts +15 -0
  18. package/dist/node_modules/@agentorg/interfaces/dist/src/chat.js +14 -0
  19. package/dist/node_modules/@agentorg/interfaces/dist/src/hub.d.ts +16 -286
  20. package/dist/node_modules/@agentorg/interfaces/dist/src/hub.js +17 -159
  21. package/dist/node_modules/@agentorg/interfaces/dist/src/index.d.ts +6 -8
  22. package/dist/node_modules/@agentorg/interfaces/dist/src/index.js +6 -8
  23. package/dist/node_modules/@agentorg/interfaces/dist/src/kv.d.ts +2 -2
  24. package/dist/node_modules/@agentorg/interfaces/dist/src/kv.js +2 -2
  25. package/dist/node_modules/@agentorg/interfaces/dist/src/manager.d.ts +272 -0
  26. package/dist/node_modules/@agentorg/interfaces/dist/src/manager.js +185 -0
  27. package/dist/node_modules/@agentorg/interfaces/dist/src/mcp-provider.d.ts +88 -0
  28. package/dist/node_modules/@agentorg/interfaces/dist/src/mcp-provider.js +78 -0
  29. package/dist/node_modules/@agentorg/interfaces/dist/src/tools.d.ts +39 -0
  30. package/dist/node_modules/@agentorg/interfaces/dist/src/tools.js +43 -0
  31. package/dist/node_modules/@agentorg/manager/dist/src/index.d.ts +1 -0
  32. package/dist/node_modules/@agentorg/manager/dist/src/index.js +1 -0
  33. package/dist/node_modules/@agentorg/manager/dist/src/manager.d.ts +21 -0
  34. package/dist/node_modules/@agentorg/manager/dist/src/manager.js +528 -0
  35. package/dist/node_modules/@agentorg/manager/dist/src/skills.d.ts +25 -0
  36. package/dist/node_modules/@agentorg/manager/dist/src/skills.js +145 -0
  37. package/dist/node_modules/@agentorg/manager/package.json +30 -0
  38. package/dist/node_modules/@agentorg/mcp/dist/src/connection.d.ts +33 -0
  39. package/dist/node_modules/@agentorg/mcp/dist/src/connection.js +290 -0
  40. package/dist/node_modules/@agentorg/mcp/dist/src/index.d.ts +1 -0
  41. package/dist/node_modules/@agentorg/mcp/dist/src/index.js +1 -0
  42. package/dist/node_modules/@agentorg/mcp/dist/src/kinds.d.ts +50 -0
  43. package/dist/node_modules/@agentorg/mcp/dist/src/kinds.js +69 -0
  44. package/dist/node_modules/@agentorg/mcp/dist/src/provider.d.ts +22 -0
  45. package/dist/node_modules/@agentorg/mcp/dist/src/provider.js +167 -0
  46. package/dist/node_modules/@agentorg/mcp/package.json +31 -0
  47. package/dist/node_modules/@agentorg/sdk/dist/src/cli.js +3 -23
  48. package/dist/node_modules/@agentorg/sdk/dist/src/client.d.ts +4 -25
  49. package/dist/node_modules/@agentorg/sdk/dist/src/client.js +5 -55
  50. package/dist/node_modules/@agentorg/sdk/dist/src/errors.d.ts +1 -1
  51. package/dist/node_modules/@agentorg/sdk/dist/src/errors.js +3 -3
  52. package/dist/node_modules/@agentorg/sdk/dist/src/index.d.ts +2 -2
  53. package/dist/node_modules/@agentorg/sdk/dist/src/index.js +1 -1
  54. package/dist/node_modules/@agentorg/sdk/dist/src/interface.d.ts +6 -34
  55. package/dist/node_modules/@agentorg/sdk/dist/src/interface.js +3 -24
  56. package/dist/node_modules/@agentorg/sdk/dist/src/module-host.d.ts +8 -17
  57. package/dist/node_modules/@agentorg/sdk/dist/src/module-host.js +15 -87
  58. package/dist/node_modules/@agentorg/sdk/dist/src/module.d.ts +10 -76
  59. package/dist/node_modules/@agentorg/sdk/dist/src/module.js +3 -23
  60. package/dist/node_modules/@agentorg/sdk/dist/src/person-commands.js +0 -5
  61. package/dist/node_modules/@agentorg/sdk/dist/src/runtime.d.ts +3 -27
  62. package/dist/node_modules/@agentorg/sdk/dist/src/runtime.js +5 -100
  63. package/dist/node_modules/@agentorg/sdk/dist/src/testing.d.ts +7 -8
  64. package/dist/node_modules/@agentorg/sdk/dist/src/testing.js +12 -24
  65. package/dist/node_modules/@agentorg/sdk/package.json +4 -37
  66. package/dist/node_modules/@agentorg/shell/dist/app.css +3435 -739
  67. package/dist/node_modules/@agentorg/shell/dist/app.js +204 -202
  68. package/dist/node_modules/@agentorg/shell/package.json +18 -10
  69. package/dist/node_modules/@agentorg/wire/dist/src/frames.d.ts +2 -3
  70. package/dist/node_modules/@agentorg/wire/dist/src/frames.js +2 -3
  71. package/dist/node_modules/@agentorg/wire/dist/src/ops.d.ts +5 -34
  72. package/dist/node_modules/@agentorg/wire/dist/src/ops.js +2 -17
  73. package/dist/node_modules/assistant/dist/src/agent.d.ts +62 -0
  74. package/dist/node_modules/assistant/dist/src/agent.js +226 -0
  75. package/dist/node_modules/assistant/dist/src/engine.d.ts +23 -0
  76. package/dist/node_modules/assistant/dist/src/engine.js +88 -0
  77. package/dist/node_modules/assistant/dist/src/history.d.ts +97 -0
  78. package/dist/node_modules/assistant/dist/src/history.js +235 -0
  79. package/dist/node_modules/assistant/dist/src/local-store.d.ts +3 -0
  80. package/dist/node_modules/assistant/dist/src/local-store.js +58 -0
  81. package/dist/node_modules/assistant/dist/src/provider.d.ts +21 -0
  82. package/dist/node_modules/assistant/dist/src/provider.js +231 -0
  83. package/dist/node_modules/assistant/dist/src/settings.d.ts +17 -0
  84. package/dist/node_modules/assistant/dist/src/settings.js +43 -0
  85. package/dist/node_modules/assistant/dist/src/tools.d.ts +74 -0
  86. package/dist/node_modules/assistant/dist/src/tools.js +123 -0
  87. package/dist/node_modules/assistant/package.json +29 -0
  88. package/dist/node_modules/chatgpt/dist/src/account.d.ts +52 -0
  89. package/dist/node_modules/chatgpt/dist/src/account.js +270 -0
  90. package/dist/node_modules/chatgpt/dist/src/encoding.d.ts +7 -0
  91. package/dist/node_modules/chatgpt/dist/src/encoding.js +20 -0
  92. package/dist/node_modules/chatgpt/dist/src/file-store.d.ts +9 -0
  93. package/dist/node_modules/chatgpt/dist/src/file-store.js +68 -0
  94. package/dist/node_modules/chatgpt/dist/src/index.d.ts +1 -0
  95. package/dist/node_modules/chatgpt/dist/src/index.js +1 -0
  96. package/dist/node_modules/chatgpt/dist/src/jwt.d.ts +15 -0
  97. package/dist/node_modules/chatgpt/dist/src/jwt.js +37 -0
  98. package/dist/node_modules/chatgpt/dist/src/oauth.d.ts +43 -0
  99. package/dist/node_modules/chatgpt/dist/src/oauth.js +104 -0
  100. package/dist/node_modules/chatgpt/dist/src/program.d.ts +23 -0
  101. package/dist/node_modules/chatgpt/dist/src/program.js +41 -0
  102. package/dist/node_modules/chatgpt/dist/src/responses.d.ts +54 -0
  103. package/dist/node_modules/chatgpt/dist/src/responses.js +300 -0
  104. package/dist/node_modules/chatgpt/dist/src/serve.d.ts +65 -0
  105. package/dist/node_modules/chatgpt/dist/src/serve.js +46 -0
  106. package/dist/node_modules/chatgpt/package.json +33 -0
  107. package/dist/src/api.d.ts +5 -34
  108. package/dist/src/api.js +6 -68
  109. package/dist/src/cli.js +3 -7
  110. package/dist/src/gateway.d.ts +6 -7
  111. package/dist/src/gateway.js +39 -285
  112. package/dist/src/index.d.ts +4 -5
  113. package/dist/src/index.js +3 -4
  114. package/dist/src/notes.d.ts +11 -0
  115. package/dist/src/notes.js +64 -0
  116. package/dist/src/oauth.d.ts +16 -0
  117. package/dist/src/oauth.js +46 -0
  118. package/dist/src/programs.d.ts +29 -0
  119. package/dist/src/programs.js +171 -0
  120. package/package.json +3 -11
  121. package/dist/bundled/index.json +0 -6
  122. package/dist/bundled/sha256-0e91cc3d0e9762b81abc2427b5496b20f383abcd6fb97d928a8d50e956b54d1f.agentorg +0 -0
  123. package/dist/bundled/sha256-1f64ca62a0209d767d6aa6c4feb50b0653d60f10bebbc301f79bd3a37e0153c1.agentorg +0 -0
  124. package/dist/bundled/sha256-4055f8fddf6b90f3d356543f554b2a9e9a2c4a8827a8774f8f87952dc1692331.agentorg +0 -0
  125. package/dist/bundled/sha256-e356ea3c1899138a8494baf521556831006f38534fa9e92ae873e7e6956c3a46.agentorg +0 -0
  126. package/dist/node_modules/@agentorg/hub/dist/src/build.d.ts +0 -17
  127. package/dist/node_modules/@agentorg/hub/dist/src/build.js +0 -120
  128. package/dist/node_modules/@agentorg/hub/dist/src/logs.d.ts +0 -33
  129. package/dist/node_modules/@agentorg/hub/dist/src/logs.js +0 -162
  130. package/dist/node_modules/@agentorg/hub/dist/src/net.d.ts +0 -41
  131. package/dist/node_modules/@agentorg/hub/dist/src/net.js +0 -265
  132. package/dist/node_modules/@agentorg/hub/dist/src/processes.d.ts +0 -78
  133. package/dist/node_modules/@agentorg/hub/dist/src/processes.js +0 -485
  134. package/dist/node_modules/@agentorg/hub/dist/src/storage.d.ts +0 -38
  135. package/dist/node_modules/@agentorg/hub/dist/src/storage.js +0 -194
  136. package/dist/node_modules/@agentorg/interfaces/dist/src/http-egress.d.ts +0 -6
  137. package/dist/node_modules/@agentorg/interfaces/dist/src/http-egress.js +0 -7
  138. package/dist/node_modules/@agentorg/interfaces/dist/src/installer.d.ts +0 -191
  139. package/dist/node_modules/@agentorg/interfaces/dist/src/installer.js +0 -136
  140. package/dist/node_modules/@agentorg/interfaces/dist/src/layout.d.ts +0 -47
  141. package/dist/node_modules/@agentorg/interfaces/dist/src/layout.js +0 -52
  142. package/dist/node_modules/@agentorg/interfaces/dist/src/rules.d.ts +0 -35
  143. package/dist/node_modules/@agentorg/interfaces/dist/src/rules.js +0 -37
  144. package/dist/node_modules/@agentorg/interfaces/dist/src/storage-admin.d.ts +0 -23
  145. package/dist/node_modules/@agentorg/interfaces/dist/src/storage-admin.js +0 -31
  146. package/dist/node_modules/@agentorg/interfaces/dist/src/ui-shell.d.ts +0 -14
  147. package/dist/node_modules/@agentorg/interfaces/dist/src/ui-shell.js +0 -26
  148. package/dist/node_modules/@agentorg/interfaces/dist/src/ui-view.d.ts +0 -27
  149. package/dist/node_modules/@agentorg/interfaces/dist/src/ui-view.js +0 -26
  150. package/dist/node_modules/@agentorg/kit/dist/src/chat.d.ts +0 -37
  151. package/dist/node_modules/@agentorg/kit/dist/src/chat.js +0 -131
  152. package/dist/node_modules/@agentorg/kit/dist/src/components.d.ts +0 -94
  153. package/dist/node_modules/@agentorg/kit/dist/src/components.js +0 -66
  154. package/dist/node_modules/@agentorg/kit/dist/src/icons.d.ts +0 -17
  155. package/dist/node_modules/@agentorg/kit/dist/src/icons.js +0 -30
  156. package/dist/node_modules/@agentorg/kit/dist/src/index.d.ts +0 -6
  157. package/dist/node_modules/@agentorg/kit/dist/src/index.js +0 -10
  158. package/dist/node_modules/@agentorg/kit/dist/src/markdown.d.ts +0 -9
  159. package/dist/node_modules/@agentorg/kit/dist/src/markdown.js +0 -14
  160. package/dist/node_modules/@agentorg/kit/dist/src/resizable.d.ts +0 -5
  161. package/dist/node_modules/@agentorg/kit/dist/src/resizable.js +0 -8
  162. package/dist/node_modules/@agentorg/kit/dist/src/styles.d.ts +0 -11
  163. package/dist/node_modules/@agentorg/kit/dist/src/styles.js +0 -166
  164. package/dist/node_modules/@agentorg/kit/package.json +0 -44
  165. package/dist/node_modules/@agentorg/sdk/dist/frame/runtime.js +0 -753
  166. package/dist/node_modules/@agentorg/sdk/dist/host/runner.js +0 -21963
  167. package/dist/node_modules/@agentorg/sdk/dist/src/build.d.ts +0 -2
  168. package/dist/node_modules/@agentorg/sdk/dist/src/build.js +0 -2
  169. package/dist/node_modules/@agentorg/sdk/dist/src/client-http.d.ts +0 -24
  170. package/dist/node_modules/@agentorg/sdk/dist/src/client-http.js +0 -247
  171. package/dist/node_modules/@agentorg/sdk/dist/src/describe.d.ts +0 -13
  172. package/dist/node_modules/@agentorg/sdk/dist/src/describe.js +0 -16
  173. package/dist/node_modules/@agentorg/sdk/dist/src/frame-base.d.ts +0 -8
  174. package/dist/node_modules/@agentorg/sdk/dist/src/frame-base.js +0 -64
  175. package/dist/node_modules/@agentorg/sdk/dist/src/frame-entry.d.ts +0 -1
  176. package/dist/node_modules/@agentorg/sdk/dist/src/frame-entry.js +0 -35
  177. package/dist/node_modules/@agentorg/sdk/dist/src/frame-host.d.ts +0 -57
  178. package/dist/node_modules/@agentorg/sdk/dist/src/frame-host.js +0 -293
  179. package/dist/node_modules/@agentorg/sdk/dist/src/frame-protocol.d.ts +0 -124
  180. package/dist/node_modules/@agentorg/sdk/dist/src/frame-protocol.js +0 -16
  181. package/dist/node_modules/@agentorg/sdk/dist/src/frame.d.ts +0 -25
  182. package/dist/node_modules/@agentorg/sdk/dist/src/frame.js +0 -248
  183. package/dist/node_modules/@agentorg/sdk/dist/src/host-main.d.ts +0 -1
  184. package/dist/node_modules/@agentorg/sdk/dist/src/host-main.js +0 -60
  185. package/dist/node_modules/@agentorg/sdk/dist/src/host.d.ts +0 -25
  186. package/dist/node_modules/@agentorg/sdk/dist/src/host.js +0 -92
  187. package/dist/node_modules/@agentorg/sdk/dist/src/manifest-api.d.ts +0 -2
  188. package/dist/node_modules/@agentorg/sdk/dist/src/manifest-api.js +0 -2
  189. package/dist/node_modules/@agentorg/sdk/dist/src/manifest.d.ts +0 -56
  190. package/dist/node_modules/@agentorg/sdk/dist/src/manifest.js +0 -165
  191. package/dist/node_modules/@agentorg/sdk/dist/src/package.d.ts +0 -60
  192. package/dist/node_modules/@agentorg/sdk/dist/src/package.js +0 -538
  193. package/dist/node_modules/@agentorg/sdk/dist/src/tar.d.ts +0 -7
  194. package/dist/node_modules/@agentorg/sdk/dist/src/tar.js +0 -71
  195. package/dist/node_modules/@agentorg/shell/dist/agent-bindings.js +0 -59
  196. package/dist/node_modules/@agentorg/shell/src/agent-bindings.ts +0 -107
  197. package/dist/src/frames.d.ts +0 -23
  198. package/dist/src/frames.js +0 -78
  199. package/dist/src/layout.d.ts +0 -44
  200. package/dist/src/layout.js +0 -90
  201. package/dist/src/model.d.ts +0 -54
  202. package/dist/src/model.js +0 -299
  203. package/dist/src/rules.d.ts +0 -71
  204. package/dist/src/rules.js +0 -157
  205. package/dist/src/transfer.d.ts +0 -30
  206. 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 { basename, dirname, join, relative, resolve, sep } from "node:path";
3
+ import { join } from "node:path";
4
4
  import { DatabaseSync } from "node:sqlite";
5
- import { httpEgress, hubActors, hubAudit, hubBindings, hubExports, hubGrants, hubPackages, kv, storageAdmin, } from "@agentorg/interfaces";
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,33 +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
- /** What a package's Actor declares: each module's slots and exports, by `module.name`. */
147
- function packageDeclarations(manifest) {
148
- return Object.entries(manifest.modules).flatMap(([module, m]) => [
149
- ["slot", m.slots],
150
- ["export", m.exports],
151
- ].flatMap(([side, members]) => Object.entries(members).map(([member, id]) => {
152
- const entry = manifest.interfaces[id];
153
- if (!entry)
154
- throw new Refused("invalid", `The package doesn't record ${id}.`);
155
- return { side, name: `${module}.${member}`, iface: { id, ...entry } };
156
- })));
157
- }
158
130
  /**
159
- * The core's state in its home folder: Actors, packages, declarations, bindings, binding requests
131
+ * The core's state in its home folder: Actors, declarations, bindings
160
132
  * and the audit trail, in `node:sqlite`. Every method that changes something takes its caller, and
161
133
  * each is checked against bindings: there is no other authority.
162
134
  */
@@ -169,7 +141,6 @@ export class HubState {
169
141
  constructor(home, events) {
170
142
  this.home = home;
171
143
  this.#events = events;
172
- mkdirSync(join(home, "packages"), { recursive: true, mode: 0o700 });
173
144
  chmodSync(home, 0o700);
174
145
  const file = join(home, "hub.db");
175
146
  this.#db = new DatabaseSync(file);
@@ -246,17 +217,6 @@ export class HubState {
246
217
  }
247
218
  this.#audit(HUB, "bootstrap", { person: PERSON });
248
219
  }
249
- if (!this.#actor(STORAGE))
250
- this.#db
251
- .prepare("INSERT INTO actors (id, name, package, state, credential, created_at) VALUES (?, 'storage', 'builtin', 'running', NULL, ?)")
252
- .run(STORAGE, now());
253
- this.#declare(STORAGE, "export", "kv", describeInterface(kv), false);
254
- this.#declare(STORAGE, "export", "admin", describeInterface(storageAdmin), false);
255
- if (!this.#actor(NET))
256
- this.#db
257
- .prepare("INSERT INTO actors (id, name, package, state, credential, created_at) VALUES (?, 'net', 'builtin', 'running', NULL, ?)")
258
- .run(NET, now());
259
- this.#declare(NET, "export", "egress", describeInterface(httpEgress), false);
260
220
  for (const [name, iface] of Object.entries(HUB_EXPORTS)) {
261
221
  const described = describeInterface(iface);
262
222
  this.#declare(HUB, "export", name, described, false);
@@ -323,17 +283,6 @@ export class HubState {
323
283
  const entry = { seq: Number(lastInsertRowid), at, actor, kind, details };
324
284
  this.#emit(() => this.#events.audited?.(entry));
325
285
  }
326
- /** Record something the Hub itself saw happen, such as the host sandbox's status changing. */
327
- auditHub(kind, details, actor = HUB) {
328
- this.#transaction(() => this.#audit(actor, kind, details));
329
- }
330
- /** A package Actor's process exited: kept as its `lastExit`, and counted when it is restarted. */
331
- recordExit(actorId, exit, restarted) {
332
- this.#db
333
- .prepare("UPDATE actors SET last_exit = ?, restarts = restarts + ? WHERE id = ? AND package NOT IN ('external', 'builtin')")
334
- .run(JSON.stringify(exit), restarted ? 1 : 0, actorId);
335
- this.#emit(() => this.#events.actorChanged?.(actorId));
336
- }
337
286
  /** Record a call's metadata: who, through which binding, which method, and how it ended. */
338
287
  auditCall(caller,
339
288
  /** The binding the call went through; an unbound slot has only its consumer and slot. */
@@ -343,16 +292,13 @@ export class HubState {
343
292
  // ── Actors ──────────────────────────────────────────────────────────────────
344
293
  #actor(actorId) {
345
294
  const row = this.#db
346
- .prepare("SELECT id, name, package, state, restarts, last_exit, installed_for FROM actors WHERE id = ?")
295
+ .prepare("SELECT id, name, package, state FROM actors WHERE id = ?")
347
296
  .get(actorId);
348
297
  return (row && {
349
298
  actorId: String(row.id),
350
299
  name: String(row.name),
351
300
  package: String(row.package),
352
301
  state: row.state,
353
- restarts: Number(row.restarts),
354
- ...(row.last_exit ? { lastExit: JSON.parse(String(row.last_exit)) } : {}),
355
- ...(row.installed_for ? { installedFor: String(row.installed_for) } : {}),
356
302
  });
357
303
  }
358
304
  /** An Actor as a refusal names it: "Name (id)", or the id alone when there's no such Actor. */
@@ -366,12 +312,6 @@ export class HubState {
366
312
  throw new Refused("unknown", `No Actor ${actorId}; hub.actors.list lists them.`);
367
313
  return actor;
368
314
  }
369
- /** The host sandbox's status changed: every package Actor's record (its `host`) differs. */
370
- hostChanged() {
371
- for (const actor of this.actors())
372
- if (actor.package !== "external" && actor.package !== "builtin")
373
- this.#emit(() => this.#events.actorChanged?.(actor.actorId));
374
- }
375
315
  actors() {
376
316
  const rows = this.#db.prepare("SELECT id FROM actors ORDER BY created_at, id").all();
377
317
  return rows.map((row) => this.actor(String(row.id)));
@@ -386,42 +326,24 @@ export class HubState {
386
326
  if (!this.holds(caller, hubExport))
387
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.`);
388
328
  }
389
- /** Create an Actor: one that runs a package, or an external one, whose credential comes back once. */
329
+ /** Create an Actor: a program that connects itself, with a credential that comes back once. */
390
330
  createActor(caller, input) {
391
331
  this.#require(caller, "actors");
392
- if (input.for !== undefined) {
393
- if (!this.isManager(caller))
394
- throw new Refused("forbidden", `Only a manager says whom an Actor is made for; ${this.named(caller)} isn't one. Create it without for.`);
395
- this.#open(input.for);
396
- }
397
332
  const actorId = `act_${randomBytes(8).toString("hex")}`;
398
- const credential = input.package ? undefined : randomBytes(32).toString("base64url");
399
- const insert = this.#db.prepare("INSERT INTO actors (id, name, package, state, credential, created_at, installed_for) VALUES (?, ?, ?, 'created', ?, ?, ?)");
400
- 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', ?, ?)");
401
335
  this.#transaction(() => {
402
- if (input.package) {
403
- const { manifest } = this.package(input.package);
404
- insert.run(actorId, input.name, input.package, null, now(), madeFor);
405
- for (const { side, name, iface } of packageDeclarations(manifest))
406
- this.#declare(actorId, side, name, iface, false);
407
- // A package's own wiring is applied as it is installed.
408
- for (const [slot, exported] of manifest.connections)
409
- this.#insertBinding({ consumer: actorId, slot, provider: actorId, export: exported }, HUB);
410
- }
411
- else
412
- insert.run(actorId, input.name, "external", digest(credential), now(), madeFor);
336
+ insert.run(actorId, input.name, digest(credential), now());
413
337
  this.#audit(caller, "actor.create", {
414
338
  actorId,
415
339
  name: input.name,
416
- package: input.package ?? "external",
417
- ...(input.for === undefined ? {} : { for: input.for }),
418
340
  });
419
341
  this.#emit(() => this.#events.actorChanged?.(actorId));
420
342
  for (const initial of input.bindings ?? []) {
421
343
  const wiring = { consumer: actorId, ...initial };
422
344
  if (!this.sides(caller, wiring).export)
423
- throw new Refused("forbidden", `${this.named(caller)} can't grant ${this.named(initial.provider)}'s ${initial.export}. Create it without that binding, then it can ask for it with hub.grants.request, or the provider or a manager can bind it.`);
424
- if (!input.package && !this.declaration(actorId, "slot", initial.slot)) {
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)) {
425
347
  if (!NAME.test(initial.slot))
426
348
  throw new Refused("invalid", `${JSON.stringify(initial.slot)} isn't a valid slot name.`);
427
349
  const offered = this.declaration(initial.provider, "export", initial.export);
@@ -432,7 +354,7 @@ export class HubState {
432
354
  this.#insertBinding(wiring, caller);
433
355
  }
434
356
  });
435
- return credential ? { actorId, credential } : { actorId };
357
+ return { actorId, credential };
436
358
  }
437
359
  /** Whether an external Actor's credential is right, compared in constant time. */
438
360
  authenticate(actorId, credential) {
@@ -444,53 +366,18 @@ export class HubState {
444
366
  // Both sides are SHA-256 digests, so the lengths always match.
445
367
  return timingSafeEqual(Buffer.from(row.credential), digest(credential));
446
368
  }
447
- #packageActor(caller, actorId) {
448
- this.#require(caller, "actors");
449
- const actor = this.actor(actorId);
450
- if (actor.package === "external" || actor.package === "builtin")
451
- throw new Refused("state", `${this.named(actorId)} isn't a package Actor, so the Hub doesn't run its process.`);
452
- return actor;
453
- }
454
- /** Refused unless the caller may start the Actor and it can be started now; changes nothing. */
455
- checkStart(caller, actorId) {
456
- const actor = this.#packageActor(caller, actorId);
457
- if (actor.state !== "created" && actor.state !== "stopped")
458
- throw new Refused("state", `${this.named(actorId)} is ${actor.state === "running" ? "already running" : "closed and can't start again; make another"}.`);
459
- }
460
- /** Start a package Actor: recorded as running; the Hub spawns its process. */
461
- start(caller, actorId) {
462
- this.checkStart(caller, actorId);
463
- this.#transaction(() => {
464
- this.#db.prepare("UPDATE actors SET state = 'running' WHERE id = ?").run(actorId);
465
- this.#audit(caller, "actor.start", { actorId });
466
- this.#emit(() => this.#events.actorChanged?.(actorId));
467
- });
468
- }
469
- stop(caller, actorId) {
470
- const actor = this.#packageActor(caller, actorId);
471
- if (actor.state !== "running")
472
- throw new Refused("state", `${this.named(actorId)} is ${actor.state}, not running.`);
473
- this.#transaction(() => {
474
- this.#db.prepare("UPDATE actors SET state = 'stopped' WHERE id = ?").run(actorId);
475
- this.#audit(caller, "actor.stop", { actorId });
476
- this.#emit(() => this.#events.actorChanged?.(actorId));
477
- });
478
- }
479
- /** 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. */
480
370
  closeActor(caller, actorId) {
481
371
  this.#require(caller, "actors");
482
372
  const actor = this.actor(actorId);
483
- if (actorId === HUB || actorId === PERSON || actorId === STORAGE || actorId === NET)
373
+ if (actorId === HUB || actorId === PERSON)
484
374
  throw new Refused("forbidden", `${this.named(actorId)} is built in and can't be closed.`);
485
375
  if (actor.state === "closed")
486
376
  return;
487
377
  this.#transaction(() => {
488
- const pending = this.#pending("consumer = ? OR provider = ? OR requested_by = ?", actorId, actorId, actorId);
489
378
  this.#db.prepare("UPDATE actors SET state = 'closed' WHERE id = ?").run(actorId);
490
379
  this.#removeBindings("consumer = ? OR provider = ?", actorId, actorId);
491
380
  this.#db.prepare("DELETE FROM declarations WHERE actor = ?").run(actorId);
492
- for (const open of pending)
493
- this.#decide(open, "closed");
494
381
  this.#audit(caller, "actor.close", { actorId });
495
382
  this.#emit(() => this.#events.exportsChanged?.(actorId));
496
383
  this.#emit(() => this.#events.actorChanged?.(actorId));
@@ -518,160 +405,6 @@ export class HubState {
518
405
  this.#audit(by, "binding.drop", { ...wiring, why: "its interface changed" });
519
406
  return wirings;
520
407
  }
521
- /** Close the open requests to bind through one of an Actor's slots or exports. */
522
- #closeRequestsThrough(actorId, side, name) {
523
- const where = side === "slot" ? "consumer = ? AND slot = ?" : "provider = ? AND export = ?";
524
- for (const open of this.#pending(where, actorId, name))
525
- this.#decide(open, "closed");
526
- }
527
- /** Refused unless the caller may upgrade the Actor to the package now; changes nothing. */
528
- checkUpgrade(caller, actorId, hash) {
529
- this.#require(caller, "actors");
530
- // An upgrade runs new code as an Actor that keeps its bindings and its store: whatever it was
531
- // granted, the new code holds. Only a manager, who could grant all of it anyway, may.
532
- if (!this.isManager(caller))
533
- throw new Refused("forbidden", `Only a manager may upgrade an Actor, since the new package keeps its bindings and its store; ${this.named(caller)} isn't one.`);
534
- const actor = this.actor(actorId);
535
- if (actorId === HUB || actorId === PERSON || actorId === STORAGE || actorId === NET)
536
- throw new Refused("forbidden", `${this.named(actorId)} is built in and can't be upgraded.`);
537
- if (actor.package === "external")
538
- throw new Refused("state", `${this.named(actorId)} isn't a package Actor; it declares what it takes when it connects.`);
539
- if (actor.state === "closed")
540
- throw new Refused("state", `${this.named(actorId)} is closed for good; make another.`);
541
- packageDeclarations(this.package(hash).manifest);
542
- return actor;
543
- }
544
- /**
545
- * Run another package as the same Actor: its id, its stores and every binding whose interfaces
546
- * still match stay; a binding through a slot or export the new package drops or declares as
547
- * another interface is dropped, audited and returned. The new package's own wiring is applied
548
- * where its slot is unbound. The process isn't touched here: a running Actor is stopped first
549
- * and started after, by whoever runs processes.
550
- */
551
- upgrade(caller, actorId, hash) {
552
- const actor = this.checkUpgrade(caller, actorId, hash);
553
- if (actor.package === hash)
554
- return [];
555
- const { manifest } = this.package(hash);
556
- const next = packageDeclarations(manifest);
557
- const declared = (side, name) => next.find((n) => n.side === side && n.name === name)?.iface;
558
- const dropped = [];
559
- this.#transaction(() => {
560
- // What it offered at run time was its process's, which this restarts: it goes, with the
561
- // bindings to it, which are returned with the rest.
562
- for (const row of this.#db
563
- .prepare("SELECT name FROM declarations WHERE actor = ? AND side = 'export' AND dynamic = 1")
564
- .all(actorId)) {
565
- dropped.push(...this.#dropBindingsOf(actorId, "export", String(row.name), caller));
566
- this.#drop(actorId, "export", String(row.name));
567
- }
568
- // A binding stays while its two ends declare the same interface: this Actor's end as the
569
- // new package declares it, the other end as it is declared now.
570
- const keeps = (b) => {
571
- const slot = b.consumer === actorId
572
- ? declared("slot", b.slot)
573
- : this.declaration(b.consumer, "slot", b.slot)?.iface;
574
- const exported = b.provider === actorId
575
- ? declared("export", b.export)
576
- : this.declaration(b.provider, "export", b.export)?.iface;
577
- return !!slot && !!exported && compareInterfaces(slot, exported) === "same";
578
- };
579
- const old = this.#db
580
- .prepare("SELECT side, name, iface FROM declarations WHERE actor = ? AND dynamic = 0")
581
- .all(actorId).map((row) => ({
582
- side: row.side,
583
- name: String(row.name),
584
- iface: JSON.parse(String(row.iface)),
585
- }));
586
- for (const was of old) {
587
- dropped.push(...this.#dropBindingsOf(actorId, was.side, was.name, caller, keeps));
588
- // A request through a name the package no longer declares as it was can't be made.
589
- const fresh = declared(was.side, was.name);
590
- if (!fresh || compareInterfaces(was.iface, fresh) !== "same")
591
- this.#closeRequestsThrough(actorId, was.side, was.name);
592
- }
593
- this.#db.prepare("DELETE FROM declarations WHERE actor = ? AND dynamic = 0").run(actorId);
594
- for (const { side, name, iface } of next)
595
- this.#declare(actorId, side, name, iface, false);
596
- this.#db.prepare("UPDATE actors SET package = ? WHERE id = ?").run(hash, actorId);
597
- for (const [slot, exported] of manifest.connections)
598
- if (!this.binding(actorId, slot))
599
- this.#insertBinding({ consumer: actorId, slot, provider: actorId, export: exported }, HUB);
600
- this.#audit(caller, "actor.upgrade", { actorId, from: actor.package, to: hash, dropped });
601
- this.#emit(() => this.#events.exportsChanged?.(actorId));
602
- this.#emit(() => this.#events.actorChanged?.(actorId));
603
- });
604
- return dropped;
605
- }
606
- // ── Packages ────────────────────────────────────────────────────────────────
607
- /** Add a `.agentorg` archive from a path on this machine, verified; adding it again is a no-op. */
608
- addPackage(caller, path) {
609
- this.#require(caller, "packages");
610
- let bytes;
611
- try {
612
- bytes = new Uint8Array(readFileSync(path));
613
- }
614
- catch {
615
- throw new Refused("unknown", `No package file at ${path}.`);
616
- }
617
- let expected = /^(sha256-[0-9a-f]{64})\.agentorg$/.exec(basename(path))?.[1];
618
- if (!expected)
619
- try {
620
- expected = treeHash(readTar(bytes));
621
- }
622
- catch (error) {
623
- throw new Refused("invalid", error instanceof Error ? error.message : String(error));
624
- }
625
- const verified = verifyArchive(bytes, expected);
626
- if (!verified.ok)
627
- throw new Refused("invalid", verified.problems.join("\n"));
628
- const { hash, manifest, files } = verified;
629
- if (this.#db.prepare("SELECT 1 FROM packages WHERE hash = ?").get(hash))
630
- return { hash, manifest };
631
- const dir = join(this.home, "packages", hash);
632
- for (const [file, data] of files) {
633
- const target = resolve(dir, file);
634
- // verifyArchive allows only agentorg.json and dist/<module>.js; this keeps it so.
635
- if (relative(dir, target).startsWith(`..${sep}`))
636
- throw new Refused("invalid", `Unsafe path ${file}.`);
637
- mkdirSync(dirname(target), { recursive: true, mode: 0o700 });
638
- writeFileSync(target, data, { mode: 0o600 });
639
- }
640
- this.#transaction(() => {
641
- this.#db
642
- .prepare("INSERT INTO packages (hash, name, version, manifest, added_by, added_at) VALUES (?, ?, ?, ?, ?, ?)")
643
- .run(hash, manifest.name, manifest.version, JSON.stringify(manifest), caller, now());
644
- this.#audit(caller, "package.add", { hash, name: manifest.name, version: manifest.version });
645
- });
646
- return { hash, manifest };
647
- }
648
- packages() {
649
- const rows = this.#db
650
- .prepare("SELECT hash, name, version FROM packages ORDER BY added_at, hash")
651
- .all();
652
- return rows.map((row) => ({
653
- hash: String(row.hash),
654
- name: String(row.name),
655
- version: String(row.version),
656
- }));
657
- }
658
- package(hash) {
659
- const row = this.#db.prepare("SELECT manifest FROM packages WHERE hash = ?").get(hash);
660
- if (!row)
661
- throw new Refused("unknown", `No package ${hash}.`);
662
- return { hash, manifest: JSON.parse(String(row.manifest)) };
663
- }
664
- /** Remove a package no live Actor runs. Its files stay until a later clean-up. */
665
- removePackage(caller, hash) {
666
- this.#require(caller, "packages");
667
- this.package(hash);
668
- if (this.#db.prepare("SELECT 1 FROM actors WHERE package = ? AND state != 'closed'").get(hash))
669
- throw new Refused("in_use", `An Actor runs ${hash}.`);
670
- this.#transaction(() => {
671
- this.#db.prepare("DELETE FROM packages WHERE hash = ?").run(hash);
672
- this.#audit(caller, "package.remove", { hash });
673
- });
674
- }
675
408
  // ── Declarations: what each Actor takes and offers ──────────────────────────
676
409
  #declare(actor, side, name, iface, dynamic) {
677
410
  this.#db
@@ -718,9 +451,7 @@ export class HubState {
718
451
  * A name it no longer declares is dropped, with its bindings.
719
452
  */
720
453
  declareExternal(actorId, declares) {
721
- const actor = this.#open(actorId);
722
- if (actor.package !== "external")
723
- throw new Refused("forbidden", `${this.named(actorId)} declares through its package; an Actor of another package declares otherwise.`);
454
+ this.#open(actorId);
724
455
  this.#transaction(() => {
725
456
  for (const [side, members] of [
726
457
  ["slot", declares.slots],
@@ -732,15 +463,12 @@ export class HubState {
732
463
  this.#checked(iface);
733
464
  if (side === "slot") {
734
465
  // The Actor's own slots are its to name: an exposure under the same name yields,
735
- // with its binding, and requests to make one under it close.
466
+ // with its binding.
736
467
  if (this.declaration(actorId, side, name)?.dynamic)
737
468
  this.#drop(actorId, side, name);
738
- for (const open of this.#pending("consumer = ? AND slot = ? AND exposed = 1", actorId, name))
739
- this.#decide(open, "closed");
740
469
  }
741
470
  // A binding holds while both ends declare the same interface: redeclared as another,
742
- // the name's bindings whose other end doesn't declare it are dropped, and its requests
743
- // close.
471
+ // the name's bindings whose other end doesn't declare it are dropped.
744
472
  const known = this.declaration(actorId, side, name);
745
473
  if (known && compareInterfaces(known.iface, iface) !== "same") {
746
474
  this.#dropBindingsOf(actorId, side, name, actorId, (b) => {
@@ -749,7 +477,6 @@ export class HubState {
749
477
  : this.declaration(b.consumer, "slot", b.slot)?.iface;
750
478
  return !!other && compareInterfaces(iface, other) === "same";
751
479
  });
752
- this.#closeRequestsThrough(actorId, side, name);
753
480
  }
754
481
  this.#declare(actorId, side, name, iface, false);
755
482
  }
@@ -772,51 +499,11 @@ export class HubState {
772
499
  .prepare("DELETE FROM declarations WHERE actor = ? AND side = ? AND name = ?")
773
500
  .run(actor, side, name);
774
501
  const where = side === "slot" ? "consumer = ? AND slot = ?" : "provider = ? AND export = ?";
775
- const pending = this.#pending(where, actor, name);
776
502
  this.#removeBindings(where, actor, name);
777
- for (const open of pending)
778
- this.#decide(open, "closed");
779
- }
780
- /** Offer an export at run time; it lasts until withdrawn or until the Actor disconnects. */
781
- offer(actorId, name, iface) {
782
- this.#open(actorId);
783
- if (!MEMBER.test(name))
784
- throw new Refused("invalid", `${JSON.stringify(name)} isn't a valid export name.`);
785
- if (this.declaration(actorId, "export", name))
786
- 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" : ""}.`);
787
- this.#checked(iface);
788
- this.#transaction(() => {
789
- this.#declare(actorId, "export", name, iface, true);
790
- this.#audit(actorId, "export.offer", { export: name, interface: iface.id });
791
- this.#emit(() => this.#events.exportsChanged?.(actorId));
792
- });
793
- }
794
- /** Withdraw an export offered at run time: the bindings to it end. */
795
- withdraw(actorId, name) {
796
- const declared = this.declaration(actorId, "export", name);
797
- if (!declared?.dynamic)
798
- throw new Refused("unknown", `${this.named(actorId)} offers no ${name} at run time to withdraw; only exports offered at run time can be.`);
799
- this.#transaction(() => {
800
- this.#drop(actorId, "export", name);
801
- this.#audit(actorId, "export.withdraw", { export: name });
802
- this.#emit(() => this.#events.exportsChanged?.(actorId));
803
- });
804
- }
805
- /** An Actor's connection ended: what it offered at run time goes, with the bindings to it. */
806
- disconnected(actorId) {
807
- this.#transaction(() => {
808
- const dynamic = this.#db
809
- .prepare("SELECT name FROM declarations WHERE actor = ? AND side = 'export' AND dynamic = 1")
810
- .all(actorId);
811
- for (const row of dynamic)
812
- this.#drop(actorId, "export", String(row.name));
813
- if (dynamic.length)
814
- this.#emit(() => this.#events.exportsChanged?.(actorId));
815
- });
816
503
  }
817
504
  exports(actor) {
818
505
  const rows = this.#db
819
- .prepare(`SELECT d.actor, d.name, d.iface, d.dynamic FROM declarations d JOIN actors a ON a.id = d.actor
506
+ .prepare(`SELECT d.actor, d.name, d.iface FROM declarations d JOIN actors a ON a.id = d.actor
820
507
  WHERE d.side = 'export' AND a.state != 'closed' ${actor ? "AND d.actor = ?" : ""}
821
508
  ORDER BY d.actor, d.name`)
822
509
  .all(...(actor ? [actor] : []));
@@ -827,7 +514,6 @@ export class HubState {
827
514
  export: String(row.name),
828
515
  interface: iface.id,
829
516
  hash: iface.hash,
830
- dynamic: !!row.dynamic,
831
517
  };
832
518
  });
833
519
  }
@@ -877,21 +563,6 @@ export class HubState {
877
563
  slot: manager || caller === wiring.consumer,
878
564
  };
879
565
  }
880
- /** Who can give the side a request needs: exactly the Actors `sides` grants it to. */
881
- approvers(request) {
882
- const managers = this.#db
883
- .prepare("SELECT DISTINCT consumer FROM bindings WHERE provider = ? AND export = 'bindings'")
884
- .all(HUB).map((m) => String(m.consumer));
885
- const own = request.needs === "slot"
886
- ? [request.consumer]
887
- : [
888
- request.provider,
889
- ...this.#db
890
- .prepare("SELECT DISTINCT consumer FROM bindings WHERE provider = ? AND export = ?")
891
- .all(request.provider, request.export).map((h) => String(h.consumer)),
892
- ];
893
- return [...new Set([...own, ...managers])];
894
- }
895
566
  /** The two ends exist, are live, and take and offer the same interface. */
896
567
  #ends(wiring) {
897
568
  this.#open(wiring.consumer);
@@ -927,8 +598,6 @@ export class HubState {
927
598
  return;
928
599
  if (removed.some((b) => b.consumer === PERSON && b.provider === HUB && !b.exposed))
929
600
  throw new Refused("forbidden", "The person's hub bindings stay, so someone can always manage the Hub.");
930
- // Requests whose requester held its end through these bindings can't be approved any more.
931
- const open = this.#pending("1 = 1");
932
601
  this.#db.prepare(`DELETE FROM bindings WHERE ${where}`).run(...params);
933
602
  // A slot made at run time goes with its binding.
934
603
  const dropExposed = this.#db.prepare("DELETE FROM declarations WHERE actor = ? AND side = 'slot' AND name = ? AND dynamic = 1");
@@ -936,35 +605,13 @@ export class HubState {
936
605
  if (binding.exposed)
937
606
  dropExposed.run(binding.consumer, binding.slot);
938
607
  this.#emit(() => this.#events.unbound?.(removed));
939
- for (const pending of open)
940
- if (!this.#standing(pending.request))
941
- this.#decide(pending, "closed");
942
608
  }
943
- /**
944
- * Bind a slot to an export. A caller with both ends binds. A caller with one end approves an
945
- * open request for this binding from the other end, if its requester still holds that end;
946
- * otherwise its call becomes a request to the other end's approvers. A caller with neither end
947
- * is refused.
948
- */
609
+ /** Bind a slot to an export: the caller must hold both ends. */
949
610
  bind(caller, wiring) {
950
611
  const sides = this.sides(caller, wiring);
951
- if (!sides.export && !sides.slot)
952
- 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.`);
953
- if (!(sides.export && sides.slot)) {
954
- const has = sides.export ? "export" : "slot";
955
- if (!this.#asked(wiring, has))
956
- return { bound: false, requestId: this.#ask(caller, wiring, "", other(has)).requestId };
957
- }
958
- this.#transaction(() => {
959
- // Every open request for this slot is settled: the ones for this binding are approved, and
960
- // the rest can't be made any more.
961
- const pending = this.#pending("consumer = ? AND slot = ?", wiring.consumer, wiring.slot);
962
- this.#insertBinding(wiring, caller);
963
- for (const open of pending)
964
- this.#decide(open, open.request.provider === wiring.provider && open.request.export === wiring.export
965
- ? "approved"
966
- : "closed");
967
- });
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));
968
615
  return { bound: true };
969
616
  }
970
617
  /** Unbind a slot: its consumer, its provider or a manager may. */
@@ -986,68 +633,44 @@ export class HubState {
986
633
  }
987
634
  /**
988
635
  * Expose an export to one Actor: a slot made on `to` at run time, typed by the export's
989
- * interface and bound to it, in one step. The authority is a binding's, with `to` as the
990
- * consumer: with both ends it is made (approving any open request for it); with one, it is a
991
- * request to the other end's approvers, who approve by exposing the same; with neither, it is
992
- * 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.
993
638
  */
994
639
  expose(caller, input) {
995
640
  const to = this.#open(input.to);
996
641
  const provider = this.#open(input.provider);
997
- // Authority first, as bind's: a caller with neither end learns nothing more.
642
+ // Authority first: a caller without it learns nothing more.
998
643
  const ends = { consumer: to.actorId, provider: provider.actorId, export: input.export };
999
644
  const sides = this.sides(caller, { ...ends, slot: "" });
1000
- if (!sides.export && !sides.slot)
1001
- throw new Refused("forbidden", `${this.named(caller)} can't expose ${input.export} of ${this.named(provider.actorId)} to ${this.named(to.actorId)}: that takes the provider's side (it, an Actor bound to that export, or a manager) or ${this.named(to.actorId)}'s (it, or a manager). Ask one of them to expose it.`);
1002
- if (BUILTIN.has(to.actorId))
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)
1003
648
  throw new Refused("forbidden", `${this.named(to.actorId)} is the Hub itself and takes no slots.`);
1004
649
  const offered = this.declaration(provider.actorId, "export", input.export);
1005
650
  if (!offered)
1006
651
  throw new Refused("unknown", `${this.named(provider.actorId)} offers no ${input.export}.`);
1007
- if (offered.iface.kind !== "rpc")
1008
- throw new Refused("unsupported", `Only an rpc export can be exposed; ${input.export} is ${offered.iface.kind}.`);
1009
- const same = (row) => row.provider === provider.actorId && row.export === input.export;
1010
- const already = this.bindings(to.actorId).find((b) => b.consumer === to.actorId && b.exposed && same(b));
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);
1011
656
  if (already && (input.as === undefined || input.as === already.slot))
1012
657
  return { exposed: true, slot: already.slot };
1013
- const requested = this.#db
1014
- .prepare("SELECT * FROM requests WHERE status = 'open' AND exposed = 1 AND consumer = ?")
1015
- .all(to.actorId).map(requestOf);
1016
- // An open request's name for this export is taken again, so approving needs no `as`.
1017
- const slot = input.as ??
1018
- requested.find(same)?.slot ??
1019
- this.#freeName(to.actorId, exposedName(provider.name, input.export), requested);
658
+ const slot = input.as ?? this.#freeName(to.actorId, exposedName(provider.name, input.export));
1020
659
  if (!NAME.test(slot) || slot.length > EXPOSED_MAX)
1021
660
  throw new Refused("invalid", `${JSON.stringify(slot)} isn't a slot name: one word of letters, digits and _, at most ${EXPOSED_MAX}.`);
1022
- // A request reserves no name: the first exposure made under it has it, and the rest close.
1023
661
  if (this.declaration(to.actorId, "slot", slot))
1024
662
  throw new Refused("conflict", `${this.named(to.actorId)} already has a slot ${slot}; name another with as.`);
1025
663
  const wiring = { ...ends, slot };
1026
- if (!(sides.export && sides.slot)) {
1027
- const has = sides.export ? "export" : "slot";
1028
- if (!this.#asked(wiring, has)) {
1029
- const mine = requested.filter((r) => r.requestedBy === caller && !(same(r) && r.slot === slot));
1030
- if (mine.length >= MAX_OPEN_EXPOSURES)
1031
- 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.`);
1032
- const request = this.#ask(caller, wiring, "", other(has), true);
1033
- return { exposed: false, slot, requestId: request.requestId };
1034
- }
1035
- }
1036
664
  this.#transaction(() => {
1037
- // Settled: this slot's requests (approved if for this export), and this export's requests
1038
- // to `to` under other names, which can't be made any more.
1039
- const pending = this.#pending("consumer = ? AND (slot = ? OR (exposed = 1 AND provider = ? AND export = ?))", wiring.consumer, wiring.slot, wiring.provider, wiring.export);
1040
665
  this.#declare(to.actorId, "slot", slot, offered.iface, true);
1041
666
  this.#insertBinding(wiring, caller);
1042
- for (const open of pending)
1043
- this.#decide(open, same(open.request) && open.request.slot === slot ? "approved" : "closed");
1044
667
  this.#audit(caller, "expose", { ...wiring });
1045
668
  });
1046
669
  return { exposed: true, slot };
1047
670
  }
1048
671
  /** A name free on `to`: `base`, else `base_2`, `base_3`…, each within the longest name. */
1049
- #freeName(to, base, requested) {
1050
- const taken = (name) => !!this.declaration(to, "slot", name) || requested.some((r) => r.slot === name);
672
+ #freeName(to, base) {
673
+ const taken = (name) => !!this.declaration(to, "slot", name);
1051
674
  for (let n = 1;; n++) {
1052
675
  const suffix = n === 1 ? "" : `_${n}`;
1053
676
  const name = base.slice(0, EXPOSED_MAX - suffix.length) + suffix;
@@ -1062,123 +685,6 @@ export class HubState {
1062
685
  throw new Refused("unknown", `${this.named(to)} has no slot ${slot} made by expose; unbind a declared slot instead.`);
1063
686
  this.unbind(caller, to, slot);
1064
687
  }
1065
- /**
1066
- * Ask for a binding the caller holds one end of; it goes to the other end's approvers. Asking
1067
- * again for the same wiring while a request is open returns that request.
1068
- */
1069
- request(caller, wiring, reason) {
1070
- const sides = this.sides(caller, wiring);
1071
- if (!sides.export && !sides.slot)
1072
- 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.`);
1073
- if (sides.export && sides.slot)
1074
- throw new Refused("state", `${this.named(caller)} holds both ends: bind it instead.`);
1075
- const has = sides.export ? "export" : "slot";
1076
- if (this.#asked(wiring, has))
1077
- throw new Refused("state", "The other end has asked already: bind to approve.");
1078
- return this.#ask(caller, wiring, reason, other(has));
1079
- }
1080
- /** Whether the other end has an open request for this binding that `has` would complete. */
1081
- #asked(wiring, has) {
1082
- return this.#db
1083
- .prepare("SELECT * FROM requests WHERE status = 'open' AND needs = ? AND consumer = ? AND slot = ? AND provider = ? AND export = ?")
1084
- .all(has, wiring.consumer, wiring.slot, wiring.provider, wiring.export)
1085
- .map(requestOf)
1086
- .some((request) => this.#standing(request));
1087
- }
1088
- #ask(caller, wiring, reason, needs, exposed = false) {
1089
- // An exposure's slot doesn't exist until it is approved; `expose` checked its ends.
1090
- if (!exposed)
1091
- this.#ends(wiring);
1092
- if (this.binding(wiring.consumer, wiring.slot))
1093
- throw new Refused("bound", `${this.named(wiring.consumer)}'s ${wiring.slot} is already bound; unbind it first.`);
1094
- const open = this.#db
1095
- .prepare("SELECT * FROM requests WHERE status = 'open' AND requested_by = ? AND consumer = ? AND slot = ? AND provider = ? AND export = ?")
1096
- .get(caller, wiring.consumer, wiring.slot, wiring.provider, wiring.export);
1097
- if (open)
1098
- return requestOf(open);
1099
- const request = {
1100
- requestId: `req_${randomBytes(8).toString("hex")}`,
1101
- ...wiring,
1102
- needs,
1103
- reason,
1104
- requestedBy: caller,
1105
- at: now(),
1106
- ...(exposed ? { exposed: true } : {}),
1107
- };
1108
- this.#transaction(() => {
1109
- this.#db
1110
- .prepare("INSERT INTO requests (id, consumer, slot, provider, export, reason, requested_by, at, status, needs, exposed) VALUES (?, ?, ?, ?, ?, ?, ?, ?, 'open', ?, ?)")
1111
- .run(request.requestId, wiring.consumer, wiring.slot, wiring.provider, wiring.export, reason, caller, request.at, needs, exposed ? 1 : 0);
1112
- this.#audit(caller, "request", {
1113
- ...wiring,
1114
- needs,
1115
- requestId: request.requestId,
1116
- ...(exposed ? { exposed: true } : {}),
1117
- });
1118
- const approvers = this.approvers(request).filter((a) => a !== caller);
1119
- this.#emit(() => this.#events.requested?.(request, approvers));
1120
- });
1121
- return request;
1122
- }
1123
- /** The open requests the caller could approve. */
1124
- requests(caller) {
1125
- const rows = this.#db
1126
- .prepare("SELECT * FROM requests WHERE status = 'open' ORDER BY at")
1127
- .all();
1128
- return rows
1129
- .map(requestOf)
1130
- .filter((request) => request.requestedBy !== caller &&
1131
- this.#standing(request) &&
1132
- this.approvers(request).includes(caller));
1133
- }
1134
- /** The caller's own requests with how each stands, newest first. */
1135
- mine(caller, limit = 100) {
1136
- const rows = this.#db
1137
- .prepare("SELECT * FROM requests WHERE requested_by = ? ORDER BY at DESC, rowid DESC LIMIT ?")
1138
- .all(caller, limit);
1139
- return rows.map((row) => ({ ...requestOf(row), status: row.status }));
1140
- }
1141
- /** A request can still be approved only while its requester holds the end it brought. */
1142
- #standing(request) {
1143
- return this.sides(request.requestedBy, request)[other(request.needs)];
1144
- }
1145
- /**
1146
- * Open requests matching a condition, each with the approvers it went to. Taken before bindings
1147
- * change, so the decision reaches the same Actors as the request.
1148
- */
1149
- #pending(where, ...params) {
1150
- return this.#db
1151
- .prepare(`SELECT * FROM requests WHERE status = 'open' AND (${where})`)
1152
- .all(...params).map((row) => {
1153
- const request = requestOf(row);
1154
- return {
1155
- request,
1156
- approvers: this.approvers(request).filter((a) => a !== request.requestedBy),
1157
- };
1158
- });
1159
- }
1160
- #decide({ request, approvers }, status) {
1161
- // A request is decided once; a later path that finds it again leaves it be.
1162
- const { changes } = this.#db
1163
- .prepare("UPDATE requests SET status = ? WHERE id = ? AND status = 'open'")
1164
- .run(status, request.requestId);
1165
- if (!changes)
1166
- return;
1167
- // The requester hears too; events are at most once, so it can also read the outcome in `mine`.
1168
- const to = [...new Set([...approvers, request.requestedBy])];
1169
- this.#emit(() => this.#events.decided?.(request, status, to));
1170
- }
1171
- decline(caller, requestId) {
1172
- const request = this.requests(caller).find((r) => r.requestId === requestId);
1173
- if (!request)
1174
- throw new Refused("unknown", `No open request ${requestId} for ${this.named(caller)} to decide; hub.grants.requests lists those it can.`);
1175
- this.#transaction(() => {
1176
- const [pending] = this.#pending("id = ?", requestId);
1177
- if (pending)
1178
- this.#decide(pending, "declined");
1179
- this.#audit(caller, "decline", { requestId });
1180
- });
1181
- }
1182
688
  // ── Audit ───────────────────────────────────────────────────────────────────
1183
689
  audit(since = 0, limit = 100) {
1184
690
  const rows = this.#db