@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.
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 -111
  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 +20 -298
  9. package/dist/node_modules/@agentorg/hub/dist/src/state.js +73 -456
  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 -267
  20. package/dist/node_modules/@agentorg/interfaces/dist/src/hub.js +17 -145
  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-01d69bc3888c2a53963f4413d47de909d129ad27c78e9af6d830bb50c3478419.agentorg +0 -0
  123. package/dist/bundled/sha256-84165e3be5c904fc971d983dab31102e07cca725d8629a6fd7d118fa3a0a88b8.agentorg +0 -0
  124. package/dist/bundled/sha256-ade9132b3f34b4c39db92adeadc7e540dc745e4a81b5b54f5d45bf9ec92bca75.agentorg +0 -0
  125. package/dist/bundled/sha256-f4330f5987f37d8cf3aeb44d2f831b573641a87186daa2f811f0c35ba04804f0.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 -51
  202. package/dist/src/model.js +0 -230
  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,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, packages, declarations, bindings, binding requests
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, restarts, last_exit, installed_for FROM actors WHERE id = ?")
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: 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. */
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 = input.package ? undefined : randomBytes(32).toString("base64url");
387
- const insert = this.#db.prepare("INSERT INTO actors (id, name, package, state, credential, created_at, installed_for) VALUES (?, ?, ?, 'created', ?, ?, ?)");
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
- if (input.package) {
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}. Create it without that binding, then it can ask for it with hub.grants.request, or the provider or a manager can bind it.`);
421
- 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)) {
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 credential ? { actorId, credential } : { actorId };
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
- #packageActor(caller, actorId) {
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 || actorId === STORAGE || actorId === NET)
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
- // ── Packages ────────────────────────────────────────────────────────────────
498
- /** Add a `.agentorg` archive from a path on this machine, verified; adding it again is a no-op. */
499
- addPackage(caller, path) {
500
- this.#require(caller, "packages");
501
- let bytes;
502
- try {
503
- bytes = new Uint8Array(readFileSync(path));
504
- }
505
- catch {
506
- throw new Refused("unknown", `No package file at ${path}.`);
507
- }
508
- let expected = /^(sha256-[0-9a-f]{64})\.agentorg$/.exec(basename(path))?.[1];
509
- if (!expected)
510
- try {
511
- expected = treeHash(readTar(bytes));
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
- package(hash) {
550
- const row = this.#db.prepare("SELECT manifest FROM packages WHERE hash = ?").get(hash);
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 bound name keeps its interface: redeclaring it as another is
608
- * refused. A name it no longer declares is dropped, with its bindings.
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
- const actor = this.#open(actorId);
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, and requests to make one under it close.
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.#isBound(actorId, side, name) &&
634
- compareInterfaces(known.iface, iface) !== "same")
635
- throw new Refused("conflict", `${name} is bound as ${known.iface.id}; it can't change while bound.`);
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, 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
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 && !sides.slot)
841
- 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.`);
842
- if (!(sides.export && sides.slot)) {
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 authority is a binding's, with `to` as the
879
- * consumer: with both ends it is made (approving any open request for it); with one, it is a
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, as bind's: a caller with neither end learns nothing more.
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 && !sides.slot)
890
- 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.`);
891
- 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)
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
- if (offered.iface.kind !== "rpc")
897
- throw new Refused("unsupported", `Only an rpc export can be exposed; ${input.export} is ${offered.iface.kind}.`);
898
- const same = (row) => row.provider === provider.actorId && row.export === input.export;
899
- 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);
900
656
  if (already && (input.as === undefined || input.as === already.slot))
901
657
  return { exposed: true, slot: already.slot };
902
- const requested = this.#db
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, requested) {
939
- 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);
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