@agentorg/cli 0.0.4 → 0.0.5
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +2 -2
- package/dist/bundled/index.json +4 -4
- package/dist/bundled/{sha256-01d69bc3888c2a53963f4413d47de909d129ad27c78e9af6d830bb50c3478419.agentorg → sha256-0e91cc3d0e9762b81abc2427b5496b20f383abcd6fb97d928a8d50e956b54d1f.agentorg} +0 -0
- package/dist/bundled/{sha256-ade9132b3f34b4c39db92adeadc7e540dc745e4a81b5b54f5d45bf9ec92bca75.agentorg → sha256-1f64ca62a0209d767d6aa6c4feb50b0653d60f10bebbc301f79bd3a37e0153c1.agentorg} +0 -0
- package/dist/bundled/{sha256-84165e3be5c904fc971d983dab31102e07cca725d8629a6fd7d118fa3a0a88b8.agentorg → sha256-4055f8fddf6b90f3d356543f554b2a9e9a2c4a8827a8774f8f87952dc1692331.agentorg} +0 -0
- package/dist/bundled/{sha256-f4330f5987f37d8cf3aeb44d2f831b573641a87186daa2f811f0c35ba04804f0.agentorg → sha256-e356ea3c1899138a8494baf521556831006f38534fa9e92ae873e7e6956c3a46.agentorg} +0 -0
- package/dist/node_modules/@agentorg/hub/dist/src/hub-actor.js +33 -0
- package/dist/node_modules/@agentorg/hub/dist/src/state.d.ts +24 -2
- package/dist/node_modules/@agentorg/hub/dist/src/state.js +135 -24
- package/dist/node_modules/@agentorg/interfaces/dist/src/hub.d.ts +19 -0
- package/dist/node_modules/@agentorg/interfaces/dist/src/hub.js +14 -0
- package/dist/src/model.d.ts +7 -4
- package/dist/src/model.js +83 -14
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -17,7 +17,7 @@ AgentOrg as a dynamic system: Actors and widgets are modules in content-addresse
|
|
|
17
17
|
- `@agentorg/kit`: the shell's own components, for the shell and for any view, drawn in the theme's tokens by the kit's styles (`KIT_CSS`, every class `kit-`, put first in the document's head when the kit is imported, so a view's own styles win). Accessible primitives on Radix: `Dialog` (focus held inside; Escape and a click outside close it), `Menu` with `MenuItem`, `Switch` and `Tabs`; plain elements for the rest: `Button` (`primary`, `quiet`), `IconButton` (its label is its name), `Field`, `Select` (the browser's own), `Card`, `List`, `Table`, `Meter` and `Actions`; layout pieces that fill and adapt: `Stack`, `Row`, `Fill`, `Scroll`, and `ResizableGroup`, `ResizablePanel` and `ResizeHandle` from react-resizable-panels. `Markdown` renders an agent's GitHub-flavored markdown with react-markdown, safe as it streams: remend first closes what's still open, and raw HTML is shown as text. `Chat` is a conversation on assistant-ui's primitives over an external store: the caller keeps the messages (`ChatEntry`, as `chat@1` has them) and runs the turns (`onSend`, `onCancel`, `onRetry`, `onEarlier`). The person's messages, each run of replies and tool messages as one reply in markdown, a tool message as a card (its name, how it ended, what went in and came back) when it says which tool ran and as its line otherwise; Enter sends, never mid-composition or during a turn, when Send is Stop; a refused send puts the text back; it fills its parent and goes compact below 460 px wide. Its tests run on happy-dom.
|
|
18
18
|
- `@agentorg/wire`: the channel: yamux streams, length-prefixed JSON frames, stream headers and transports.
|
|
19
19
|
- `installer`: a bundled host package that turns packages into working Actors, exporting `installer@1`. Bound to the Hub's management (`hub.packages`, `hub.actors`, `hub.grants`, `hub.exports` and `hub.bindings`, which makes it the person's delegated manager) and to `storage.admin@1`, it proposes and installs. `plan({package})` lists each slot the package doesn't wire itself, with the exports that could serve it now: exactly its interface and schema hash, on Actors that aren't stopped or closed. `install({package, name, connections, start?})` makes the Actor with exactly those connections, all or none. It grants nothing its caller couldn't: each connection's export side must be the caller's (the provider, bound to that export, or a manager), else the whole install is refused (`forbidden`, listing every such connection) and nothing is made. It seeds each store bound to `storage.kv` from the package's `seed` (an object of key to value per `module.slot`), starts the Actor if asked (a seed or start that fails closes it), and lists the slots left unbound and the seeds that couldn't land (`unseeded`). It never picks a connection itself. `install` records the new Actor as made for its caller (`installedFor`, which grants nothing), and takes `expose: [{export, to}]`: the new Actor's exports to expose, each to one Actor, all or none like the connections. A manager's are made at once (`granted`, with the slot); one to the caller itself is left for the caller to file, its result giving the exact `hub.grants.expose` call, which makes it the caller's own request (`request`); any other is refused `forbidden`, saying a manager could, and nothing is created. Only `rpc` exports of the package's host modules can be listed, and the Hub's own Actors take no slots. `plan({package, expose})` lists each entry with how it would settle, and refuses one `install` would refuse. Nothing is exposed that isn't listed. Bound to the person's `layout@1` too, it carries a system between homes as data: `exportSystem()`, for a manager only (it lists every binding and the person's layout; any other caller is refused `forbidden`), gives an `agentorg.system/1` composition (every package Actor that isn't closed, by a name of its own, with its package as `name@version#hash` and whether it runs; the connections among them and to the built-in Actors, as `{consumer, slot, provider, export}` by names; and the layout's placements), never credentials, tokens, stores or logs, and lists what it leaves out (external Actors and what connects to them). `importSystem({composition})` makes it again with the caller's authority (connecting the new Actors to each other takes a manager): it creates the Actors whose packages are here (a taken name gets a suffix), binds the connections, seeds, starts what ran and places the layout, all or nothing, and lists what it couldn't: a package that isn't here or is another version (whoever holds the composition's folder adds its archives first) and what depends on it, an Actor that needs a sign-in, a seed that can't land, and the placements unless the caller holds the person's layout. The exporting home's installer stays out: every home has its own. An import never grants management: a connection to `hub` or to `storage.admin` is recorded by an export but never made by an import, even a manager's (a composition may come from anyone); it comes back in `ungranted`, for the person to grant one by one.
|
|
20
|
-
- `@agentorg/hub`: the core. Its state, in `node:sqlite` in a private home: Actors, a verified package store (`hub.packages.add` takes an archive by path; `hub.packages.build` takes a package's source as data, `package.json` and files under `src/` up to 768 KiB, builds it as `agentorg build` does, but reading no tsconfig, in a scratch folder of the home that is always removed: its files import only `@agentorg/sdk`, `@agentorg/interfaces`, `zod`, and for views `react`, `react-dom` and `@agentorg/kit` (JSX compiles to `react/jsx-runtime`), by name, each only within its own package, nothing outside the package's folder, and nothing by a pattern; a library's own imports are its own, and only the files resolved for it are read; a library's wrapped modules are named in the bundle by their library and version (`agentorg-library:react@19.3.1/cjs/react.production.js`), never by where it is installed, in `agentorg build` and Hub builds alike, so a package names no machine's folders and builds the same on any install; a Hub build resolves each allowed package's entry file from the Hub's own install, so in that file esbuild omits Node's ESM-to-CommonJS interop flag, which `agentorg build` keeps: no package allowed now behaves differently for it, but a Hub build's bytes can differ from `agentorg build`'s for packages that use them; at most 16 modules, 16 MiB built and 60 s; one build per caller at a time; and it adds the result. Reading each module's definition runs the source's code, so the Hub runs it in the same OS sandbox as host processes, and where those can't start, `build` is refused `unsupported` with the same reason. A failed build is `Refused("build")` with the build's message, which names no path outside the package), what each Actor takes and offers, bindings, binding requests and an audit trail. A binding is the grant, and it needs both ends: the export side (the provider, an Actor already bound to that export, or a manager) and the slot side (the consumer itself, or a manager). Through `hub.grants@1`, a caller with both ends binds; one with a single end approves the other end's open request or asks that end's approvers; one with neither is refused. `hub.grants.expose({provider, export, to, as?})` exposes an `rpc` export to one Actor: a slot made on `to` at run time, typed by the export's interface and bound to it in one step, named `as` (one word of at most 48 characters) or by default the provider's name camelCased, `_` and the export's last part (`Pocket Side Quests`' `logic.quests` is `pocketSideQuests_quests`), made unique on `to` with `_2`, `_3`…. Its authority is a binding's, `to` being the consumer: with both ends it is made; with one it is a request (`exposed: true` on the request, its `slot` the name it will have), which the other end approves by calling `expose` with the request's fields; with neither it is refused, naming who could. Exposing the same export to the same Actor again gives its slot back. A request reserves no name: the first exposure made under it has it, and the requests it settles close; one Actor has at most 32 exposure requests open toward another. An Actor that declares a slot of its own under an exposed slot's name takes the name, and the exposure goes. The slot goes with its binding: `unexpose({to, slot})`, `unbind`, the export withdrawn or either end closed; `hub.bindings` marks such a binding `exposed: true`. The Hub's own Actors take no slots. A manager creating an Actor for another (the installer, for its caller) passes `for`, shown as `installedFor` on the Actor: provenance only, from which the Hub grants nothing. A decision goes to the request's approvers and its requester, which can also read its requests' outcomes with `mine`. A manager is any Actor bound to `hub.bindings@1`. `hub.actors.create` can bind the new Actor's slots at once, with the creator's export side. The router serves it over WebSocket (`listen`), one yamux channel per Actor: a client's first stream is `hello` with its credential, then every `call`, `subscribe`, `open` and `http` stream is resolved through its binding, validated against the interface's JSON Schemas in both directions (ajv, 2020-12, in a pool of worker threads, each check under a time budget, since schemas come from Actors), forwarded to the provider with the caller named, and audited. A provider accepts or refuses an `open` or `http` stream with one body before any raw bytes. An Actor offers and withdraws exports at run time on its own channel (`offer`, `withdraw`). An Actor learns which of its slots are bound from hello's result, then from a `slot` notice on each change. A pipe the Hub makes for a package Actor's process says hello as that Actor without a credential (the pipe is the proof) and declares nothing (the manifest does). Every `serve` stream names the caller and the caller's slot, so a provider can keep state per consumer slot. `hub.actors.start` runs a package Actor's host modules in their own process: `node` under its permission model (reading only the runner and the package's folder, no child processes or workers), with an empty environment, a 256 MB heap and the channel on fd 3, inside the OS sandbox (`sandbox-exec` on macOS, a user, network and PID namespace with `unshare` on Linux), so it has no network, no DNS and no other process to signal. The Actor is running once its process says hello, within 30 s. `stop` sends SIGTERM, then SIGKILL after 5 s. A process that exits otherwise has crashed: it is restarted after a backoff from 1 s doubling to 60 s (starting over after 10 minutes up), and `hub.actors.get` counts `restarts` and keeps the `lastExit`. Every Actor's record in `hub.actors` says whether it is `connected`, holding a channel to the Hub now: an external Actor while its program is connected (the person while the app runs: its gateway holds the person's channel), a package Actor while its process is, so a running one isn't while its process starts or restarts; the built-in Actors are the Hub itself and have none. `hub.actors`' `changed` event names an Actor whose record may differ: made, started, stopped, closed, its process exited, or connected or gone; the audit trail records no connections. Actors running when the Hub stopped are started again when it opens. Where the sandbox can't run (stock Ubuntu restricts unprivileged user namespaces), `start` is refused with `unsupported` and every package Actor's `host` says why and what to change; the Hub probes again at each start. `hub.actors.logs` reads an Actor's log lines: its `ctx.log` entries and anything else its process writes (stdout as `info`, stderr as `error`). For the person's shell, `Hub.viewCall` and `Hub.viewSubscribe` run a package Actor's view module's calls and events in the Hub's process, as that Actor, through `<module>.<slot>`, and `Hub.viewSlots` tells the gateway a view module's slots: those bound now, then each change, and each bound again when it is usable again: its provider's channel comes back, or its Actor's own, since a view calls only while its Actor runs; `hub.actors.get` lists a package Actor's `views`. `Hub.viewLog` writes a view module's log line to its Actor's log, marked with the module; the Hub bounds each line itself (a known level, 4 KiB of text, 16 fields of 1 KiB) and rotates each Actor's log once at 1 MiB. The built-in `storage` Actor exports `kv@1`: one store per consumer slot (keyed by the consumer and slot the router names), in `<home>/storage.db`; values up to 256 KiB and 64 MiB per Actor, past which `Refused("quota")`; stores outlive unbinding and go when their Actor is closed. Its `admin` export (`storage.admin@1`, bound only to the installer) seeds a new Actor's store from its package's `seed`: all entries or none, into an empty store only, within the same limits, and audited as `storage.seed` with the consumer, the slot and how many entries. The built-in `net` Actor exports `egress` (`http.egress@1`): a consumer's request, made from the Hub's process to the public internet over HTTPS only. Its host is resolved and every address checked (loopback, private, shared, link-local including the cloud metadata address, multicast, reserved and documentation ranges, and their IPv4-mapped forms are refused), and the connection goes to the checked address with the name for SNI and the certificate, so a second resolution can't move it. Plain `http:`, a refused address and a name that resolves to one throw `Refused("forbidden")` at the consumer's `fetch`; a host that can't be reached before anything is sent throws `Refused("unreachable")`, and a failure after, or 5 minutes without a byte, throws `OutcomeUnknown`. Redirects come back as they are. Each request's method, host and status are audited as `net`'s; paths, queries and bodies never are. The `hub` Actor's six exports are served through the same path. A refusal that names an Actor names it as "Name (id)", and says what the caller can do instead: who could grant it, what to call, or what to do first.
|
|
20
|
+
- `@agentorg/hub`: the core. Its state, in `node:sqlite` in a private home: Actors, a verified package store (`hub.packages.add` takes an archive by path; `hub.packages.build` takes a package's source as data, `package.json` and files under `src/` up to 768 KiB, builds it as `agentorg build` does, but reading no tsconfig, in a scratch folder of the home that is always removed: its files import only `@agentorg/sdk`, `@agentorg/interfaces`, `zod`, and for views `react`, `react-dom` and `@agentorg/kit` (JSX compiles to `react/jsx-runtime`), by name, each only within its own package, nothing outside the package's folder, and nothing by a pattern; a library's own imports are its own, and only the files resolved for it are read; a library's wrapped modules are named in the bundle by their library and version (`agentorg-library:react@19.3.1/cjs/react.production.js`), never by where it is installed, in `agentorg build` and Hub builds alike, so a package names no machine's folders and builds the same on any install; a Hub build resolves each allowed package's entry file from the Hub's own install, so in that file esbuild omits Node's ESM-to-CommonJS interop flag, which `agentorg build` keeps: no package allowed now behaves differently for it, but a Hub build's bytes can differ from `agentorg build`'s for packages that use them; at most 16 modules, 16 MiB built and 60 s; one build per caller at a time; and it adds the result. Reading each module's definition runs the source's code, so the Hub runs it in the same OS sandbox as host processes, and where those can't start, `build` is refused `unsupported` with the same reason. A failed build is `Refused("build")` with the build's message, which names no path outside the package), what each Actor takes and offers, bindings, binding requests and an audit trail. A binding is the grant, and it needs both ends: the export side (the provider, an Actor already bound to that export, or a manager) and the slot side (the consumer itself, or a manager). Through `hub.grants@1`, a caller with both ends binds; one with a single end approves the other end's open request or asks that end's approvers; one with neither is refused. `hub.grants.expose({provider, export, to, as?})` exposes an `rpc` export to one Actor: a slot made on `to` at run time, typed by the export's interface and bound to it in one step, named `as` (one word of at most 48 characters) or by default the provider's name camelCased, `_` and the export's last part (`Pocket Side Quests`' `logic.quests` is `pocketSideQuests_quests`), made unique on `to` with `_2`, `_3`…. Its authority is a binding's, `to` being the consumer: with both ends it is made; with one it is a request (`exposed: true` on the request, its `slot` the name it will have), which the other end approves by calling `expose` with the request's fields; with neither it is refused, naming who could. Exposing the same export to the same Actor again gives its slot back. A request reserves no name: the first exposure made under it has it, and the requests it settles close; one Actor has at most 32 exposure requests open toward another. An Actor that declares a slot of its own under an exposed slot's name takes the name, and the exposure goes. The slot goes with its binding: `unexpose({to, slot})`, `unbind`, the export withdrawn or either end closed; `hub.bindings` marks such a binding `exposed: true`. The Hub's own Actors take no slots. A manager creating an Actor for another (the installer, for its caller) passes `for`, shown as `installedFor` on the Actor: provenance only, from which the Hub grants nothing. A decision goes to the request's approvers and its requester, which can also read its requests' outcomes with `mine`. A manager is any Actor bound to `hub.bindings@1`. `hub.actors.create` can bind the new Actor's slots at once, with the creator's export side. The router serves it over WebSocket (`listen`), one yamux channel per Actor: a client's first stream is `hello` with its credential, then every `call`, `subscribe`, `open` and `http` stream is resolved through its binding, validated against the interface's JSON Schemas in both directions (ajv, 2020-12, in a pool of worker threads, each check under a time budget, since schemas come from Actors), forwarded to the provider with the caller named, and audited. A provider accepts or refuses an `open` or `http` stream with one body before any raw bytes. An Actor offers and withdraws exports at run time on its own channel (`offer`, `withdraw`). An Actor learns which of its slots are bound from hello's result, then from a `slot` notice on each change. A pipe the Hub makes for a package Actor's process says hello as that Actor without a credential (the pipe is the proof) and declares nothing (the manifest does). Every `serve` stream names the caller and the caller's slot, so a provider can keep state per consumer slot. `hub.actors.start` runs a package Actor's host modules in their own process: `node` under its permission model (reading only the runner and the package's folder, no child processes or workers), with an empty environment, a 256 MB heap and the channel on fd 3, inside the OS sandbox (`sandbox-exec` on macOS, a user, network and PID namespace with `unshare` on Linux), so it has no network, no DNS and no other process to signal. The Actor is running once its process says hello, within 30 s. `stop` sends SIGTERM, then SIGKILL after 5 s. A process that exits otherwise has crashed: it is restarted after a backoff from 1 s doubling to 60 s (starting over after 10 minutes up), and `hub.actors.get` counts `restarts` and keeps the `lastExit`. Every Actor's record in `hub.actors` says whether it is `connected`, holding a channel to the Hub now: an external Actor while its program is connected (the person while the app runs: its gateway holds the person's channel), a package Actor while its process is, so a running one isn't while its process starts or restarts; the built-in Actors are the Hub itself and have none. `hub.actors`' `changed` event names an Actor whose record may differ: made, started, stopped, closed, its process exited, or connected or gone; the audit trail records no connections. Actors running when the Hub stopped are started again when it opens. `hub.actors.upgrade({actorId, package})` runs another package as the same package Actor, for a manager only, since the new package's code then holds everything the Actor was granted: its id, its stores and every binding whose interfaces still match stay, the new package's own wiring is made where its slot is unbound, and a running Actor restarts on it; a binding through a slot or export the new package drops or declares as another interface is dropped, audited and returned in `dropped`. When a redeclaration or an upgrade changes the interface of a bound name, the bindings whose other end doesn't declare the new one are dropped (audited as `binding.drop`) and the requests through it close: an external Actor's hello that redeclares a bound name as another is no longer refused for it. Dropping only takes access away. Where the sandbox can't run (stock Ubuntu restricts unprivileged user namespaces), `start` is refused with `unsupported` and every package Actor's `host` says why and what to change; the Hub probes again at each start. `hub.actors.logs` reads an Actor's log lines: its `ctx.log` entries and anything else its process writes (stdout as `info`, stderr as `error`). For the person's shell, `Hub.viewCall` and `Hub.viewSubscribe` run a package Actor's view module's calls and events in the Hub's process, as that Actor, through `<module>.<slot>`, and `Hub.viewSlots` tells the gateway a view module's slots: those bound now, then each change, and each bound again when it is usable again: its provider's channel comes back, or its Actor's own, since a view calls only while its Actor runs; `hub.actors.get` lists a package Actor's `views`. `Hub.viewLog` writes a view module's log line to its Actor's log, marked with the module; the Hub bounds each line itself (a known level, 4 KiB of text, 16 fields of 1 KiB) and rotates each Actor's log once at 1 MiB. The built-in `storage` Actor exports `kv@1`: one store per consumer slot (keyed by the consumer and slot the router names), in `<home>/storage.db`; values up to 256 KiB and 64 MiB per Actor, past which `Refused("quota")`; stores outlive unbinding and go when their Actor is closed. Its `admin` export (`storage.admin@1`, bound only to the installer) seeds a new Actor's store from its package's `seed`: all entries or none, into an empty store only, within the same limits, and audited as `storage.seed` with the consumer, the slot and how many entries. The built-in `net` Actor exports `egress` (`http.egress@1`): a consumer's request, made from the Hub's process to the public internet over HTTPS only. Its host is resolved and every address checked (loopback, private, shared, link-local including the cloud metadata address, multicast, reserved and documentation ranges, and their IPv4-mapped forms are refused), and the connection goes to the checked address with the name for SNI and the certificate, so a second resolution can't move it. Plain `http:`, a refused address and a name that resolves to one throw `Refused("forbidden")` at the consumer's `fetch`; a host that can't be reached before anything is sent throws `Refused("unreachable")`, and a failure after, or 5 minutes without a byte, throws `OutcomeUnknown`. Redirects come back as they are. Each request's method, host and status are audited as `net`'s; paths, queries and bodies never are. The `hub` Actor's six exports are served through the same path. A refusal that names an Actor names it as "Name (id)", and says what the caller can do instead: who could grant it, what to call, or what to do first.
|
|
21
21
|
- `@agentorg/cli`: AgentOrg as one app. `agentorg` runs a Hub, the person's gateway and the shell on loopback; see "The app: the person's gateway".
|
|
22
22
|
- `chatgpt`: the model Actor, a package with one host module. It signs in with ChatGPT for the person's plan (`account.login@1`: OpenAI's OAuth for open-source apps, with dynamic registration, PKCE and a verified id_token; refresh tokens rotate, one refresh at a time) and serves `llm.session@1` on that plan through the Responses API, streamed and stored nowhere. Its whole network is its `web: http.egress@1` slot and its sign-in lives in its `store: kv@1` slot; the tokens never leave it except to that store: never in a reply, an event, a log or a refusal. It never uses an API key or API billing: a usage limit is refused `usage_limit`, with where to manage it. Its tests run against an in-process fake of OpenAI, so none reaches OpenAI.
|
|
23
23
|
- `assistant`: an agent, a package whose host module `logic` exports `chat@1` and runs the agent loop itself, with the model behind its `model: llm.session@1` slot. Its tools are its other bound slots (`layout`, `packages`, `actors`, `grants`, `exports` and `installer`), and every slot exposed to it at run time (`ctx.exposed()`): each method becomes a function tool named `<slot>__<method>` (a dot in the slot's name as `_`; a name past 64 characters is left out) with the method's input JSON Schema, so it can do exactly what it is connected to, and a call runs through the slot, checked and audited by the Hub. With `packages` and `installer: installer@1` bound, it can extend the system: its instructions then give the package format (views may use React and `@agentorg/kit`'s components), with a whole counter package and a view on the kit as the examples (`EXAMPLE` in `src/logic.ts`, built by its tests as the Hub builds), and the steps: `packages__build` with the files, fixing errors from the build's message; `installer__plan`; `installer__install` with explicit connections (a store to `storage.kv`); then `layout__place` with the new Actor's view. A turn makes at most 20 tool calls, and a function's output is cut at 16 KiB. Its chat is kept in its `store: kv@1` slot: each message once, its text and a call's arguments cut to fit a stored value, and the newest 2,000 messages (16 MiB) kept. The model is sent the newest whole exchanges that fit 400 KB of JSON (instructions and tool schemas aside), and always the latest one; if that alone is over, its oldest function outputs are left out, each call keeping an output. Its view module `chat` is the chat itself, the kit's `Chat` over its `chat: chat@1` slot: the record (the newest 100, earlier pages on request), the replies as they stream, in markdown, and a composer, with "Using ChatGPT plan" and "Manage usage" beside it, Retry under the last reply, and the action each refusal calls for (Manage usage, Continue with ChatGPT, Retry), which it asks of the shell through its `shell: ui.shell@1` slot.
|
|
@@ -137,7 +137,7 @@ From a clone, after `npm ci` and `npm run build`, with `agentorg` being `node pa
|
|
|
137
137
|
- it makes the installer as the person, with its seven bindings (`hub.packages`, `hub.actors`, `hub.grants`, `hub.exports`, `hub.bindings`, `storage.admin` and `person.layout`), starts it, and binds the person's `installer` to it;
|
|
138
138
|
- once the installer's process is connected, it installs ChatGPT through `installer.install`, with `model.web` to `net.egress` and `model.store` to `storage.kv`, started, and binds the person's `login` to its `model.login`.
|
|
139
139
|
|
|
140
|
-
It records the Actors it made, and the bundled assistant's hash, in the person's store (`app/first-run`), and trusts only those, never an Actor because of the name its package gives itself. Each step reads what is there first, so a restart, or a first run that stopped half way, converges and makes nothing twice: a recorded Actor that lost one of its slots' bindings gets it again (of the slots its package declares)
|
|
140
|
+
It records the Actors it made, and the bundled assistant's hash, in the person's store (`app/first-run`), and trusts only those, never an Actor because of the name its package gives itself. Each step reads what is there first, so a restart, or a first run that stopped half way, converges and makes nothing twice: a recorded Actor that lost one of its slots' bindings gets it again (of the slots its package declares). A recorded Actor of an older build is upgraded in place to the bundled one (`hub.actors.upgrade`: the same id, store and matching bindings), and so is an agent on an assistant build an earlier run bundled, known by the hashes the first run records, never by a package's name; the bindings an upgrade drops are made again where their ends now match, and each upgrade is logged. A recorded Actor that was closed is made again. A step that fails is logged and tried again at the next start.
|
|
141
141
|
- **`/oauth/chatgpt`:** where ChatGPT sends the browser back after sign-in. It takes `GET` only and hands the whole address, which holds the sign-in's code, to `login.complete`. Then it sends the tab on (303) to `/oauth/chatgpt?result=<outcome>`, so the code leaves the tab's address and history; that page says how it went and links back to the shell. The code is never logged or shown, and no referrer is sent.
|
|
142
142
|
- **Named rules, `app.rules@1`:** conveniences the person turns on, served by the gateway on `/api` as `rules` (`list`, `set`, and `changed`). Each is named and off until the person turns it on, and the ones that are on are kept in the person's store under `app/rules`. The gateway acts on a rule that is on as the person, a manager, so only within what the person could grant: it hears each request through `grants.requested`, and goes through the open ones at start and whenever a rule is turned on. It approves a match the way an approver does, with the call the request asked for, and never declines; whatever no rule matches stays for the person.
|
|
143
143
|
- **"Let an agent use what it installs"** (`agents-use-what-they-install`) approves a request to expose an Actor's export to the agent it was installed for (its `installedFor`), while the Actor isn't closed. `installedFor` is provenance only: nothing but a rule that is on acts on it.
|
package/dist/bundled/index.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
|
-
"installer": "sha256-
|
|
3
|
-
"chatgpt": "sha256-
|
|
4
|
-
"assistant": "sha256-
|
|
5
|
-
"board": "sha256-
|
|
2
|
+
"installer": "sha256-4055f8fddf6b90f3d356543f554b2a9e9a2c4a8827a8774f8f87952dc1692331.agentorg",
|
|
3
|
+
"chatgpt": "sha256-e356ea3c1899138a8494baf521556831006f38534fa9e92ae873e7e6956c3a46.agentorg",
|
|
4
|
+
"assistant": "sha256-1f64ca62a0209d767d6aa6c4feb50b0653d60f10bebbc301f79bd3a37e0153c1.agentorg",
|
|
5
|
+
"board": "sha256-0e91cc3d0e9762b81abc2427b5496b20f383abcd6fb97d928a8d50e956b54d1f.agentorg"
|
|
6
6
|
}
|
|
@@ -116,6 +116,39 @@ export class HubActor {
|
|
|
116
116
|
await processes.stop(actorId);
|
|
117
117
|
state.closeActor(caller, actorId);
|
|
118
118
|
},
|
|
119
|
+
// A running Actor is stopped as `stop` does it, upgraded, and started again: on the new
|
|
120
|
+
// package, or on its own if the upgrade is refused. The machine's check comes first, so
|
|
121
|
+
// one that can't run it never has it stopped by an upgrade.
|
|
122
|
+
upgrade: async (caller, input) => {
|
|
123
|
+
const actorId = String(input.actorId);
|
|
124
|
+
const hash = String(input.package);
|
|
125
|
+
const actor = state.checkUpgrade(caller, actorId, hash);
|
|
126
|
+
if (actor.package === hash)
|
|
127
|
+
return { dropped: [] };
|
|
128
|
+
const running = actor.state === "running";
|
|
129
|
+
if (running) {
|
|
130
|
+
await processes.ready();
|
|
131
|
+
state.stop(caller, actorId);
|
|
132
|
+
await processes.stop(actorId);
|
|
133
|
+
}
|
|
134
|
+
const restart = () => {
|
|
135
|
+
// Started meanwhile, by another call: it runs already.
|
|
136
|
+
if (!running || state.actor(actorId).state === "running")
|
|
137
|
+
return;
|
|
138
|
+
state.start(caller, actorId);
|
|
139
|
+
processes.start(actorId);
|
|
140
|
+
};
|
|
141
|
+
let dropped;
|
|
142
|
+
try {
|
|
143
|
+
dropped = state.upgrade(caller, actorId, hash);
|
|
144
|
+
}
|
|
145
|
+
catch (error) {
|
|
146
|
+
restart();
|
|
147
|
+
throw error;
|
|
148
|
+
}
|
|
149
|
+
restart();
|
|
150
|
+
return { dropped };
|
|
151
|
+
},
|
|
119
152
|
logs: (_, input) => {
|
|
120
153
|
// The Actor must exist; its lines are its process's output and its views' lines.
|
|
121
154
|
const { actorId } = state.actor(String(input.actorId));
|
|
@@ -115,6 +115,17 @@ declare const HUB_EXPORTS: {
|
|
|
115
115
|
close: import("@agentorg/sdk").MethodSpec<import("zod").ZodObject<{
|
|
116
116
|
actorId: import("zod").ZodString;
|
|
117
117
|
}, import("zod/v4/core").$strip>, import("zod").ZodObject<{}, import("zod/v4/core").$strip>>;
|
|
118
|
+
upgrade: import("@agentorg/sdk").MethodSpec<import("zod").ZodObject<{
|
|
119
|
+
actorId: import("zod").ZodString;
|
|
120
|
+
package: import("zod").ZodString;
|
|
121
|
+
}, import("zod/v4/core").$strip>, import("zod").ZodObject<{
|
|
122
|
+
dropped: import("zod").ZodArray<import("zod").ZodObject<{
|
|
123
|
+
consumer: import("zod").ZodString;
|
|
124
|
+
slot: import("zod").ZodString;
|
|
125
|
+
provider: import("zod").ZodString;
|
|
126
|
+
export: import("zod").ZodString;
|
|
127
|
+
}, import("zod/v4/core").$strip>>;
|
|
128
|
+
}, import("zod/v4/core").$strip>>;
|
|
118
129
|
logs: import("@agentorg/sdk").MethodSpec<import("zod").ZodObject<{
|
|
119
130
|
actorId: import("zod").ZodString;
|
|
120
131
|
limit: import("zod").ZodOptional<import("zod").ZodNumber>;
|
|
@@ -472,6 +483,16 @@ export declare class HubState {
|
|
|
472
483
|
stop(caller: string, actorId: string): void;
|
|
473
484
|
/** Close an Actor for good: its bindings both ways, its declarations and its open requests go. */
|
|
474
485
|
closeActor(caller: string, actorId: string): void;
|
|
486
|
+
/** Refused unless the caller may upgrade the Actor to the package now; changes nothing. */
|
|
487
|
+
checkUpgrade(caller: string, actorId: string, hash: string): ActorRecord;
|
|
488
|
+
/**
|
|
489
|
+
* Run another package as the same Actor: its id, its stores and every binding whose interfaces
|
|
490
|
+
* still match stay; a binding through a slot or export the new package drops or declares as
|
|
491
|
+
* another interface is dropped, audited and returned. The new package's own wiring is applied
|
|
492
|
+
* where its slot is unbound. The process isn't touched here: a running Actor is stopped first
|
|
493
|
+
* and started after, by whoever runs processes.
|
|
494
|
+
*/
|
|
495
|
+
upgrade(caller: string, actorId: string, hash: string): Wiring[];
|
|
475
496
|
/** Add a `.agentorg` archive from a path on this machine, verified; adding it again is a no-op. */
|
|
476
497
|
addPackage(caller: string, path: string): {
|
|
477
498
|
hash: string;
|
|
@@ -493,8 +514,9 @@ export declare class HubState {
|
|
|
493
514
|
declaration(actor: string, side: "slot" | "export", name: string): Declaration | undefined;
|
|
494
515
|
/**
|
|
495
516
|
* An external Actor's slots and static exports, as it declares them when it connects; they
|
|
496
|
-
* replace what it declared before. A
|
|
497
|
-
*
|
|
517
|
+
* replace what it declared before. A name redeclared as another interface loses its bindings
|
|
518
|
+
* (audited as `binding.drop`), since a binding holds only while both ends declare the same one.
|
|
519
|
+
* A name it no longer declares is dropped, with its bindings.
|
|
498
520
|
*/
|
|
499
521
|
declareExternal(actorId: string, declares: {
|
|
500
522
|
slots: Record<string, DescribedInterface>;
|
|
@@ -143,6 +143,18 @@ const requestOf = (row) => ({
|
|
|
143
143
|
...(row.exposed ? { exposed: true } : {}),
|
|
144
144
|
});
|
|
145
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
|
+
}
|
|
146
158
|
/**
|
|
147
159
|
* The core's state in its home folder: Actors, packages, declarations, bindings, binding requests
|
|
148
160
|
* and the audit trail, in `node:sqlite`. Every method that changes something takes its caller, and
|
|
@@ -390,17 +402,8 @@ export class HubState {
|
|
|
390
402
|
if (input.package) {
|
|
391
403
|
const { manifest } = this.package(input.package);
|
|
392
404
|
insert.run(actorId, input.name, input.package, null, now(), madeFor);
|
|
393
|
-
for (const
|
|
394
|
-
|
|
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
|
-
}
|
|
405
|
+
for (const { side, name, iface } of packageDeclarations(manifest))
|
|
406
|
+
this.#declare(actorId, side, name, iface, false);
|
|
404
407
|
// A package's own wiring is applied as it is installed.
|
|
405
408
|
for (const [slot, exported] of manifest.connections)
|
|
406
409
|
this.#insertBinding({ consumer: actorId, slot, provider: actorId, export: exported }, HUB);
|
|
@@ -494,6 +497,112 @@ export class HubState {
|
|
|
494
497
|
this.#emit(() => this.#events.closed?.(actorId));
|
|
495
498
|
});
|
|
496
499
|
}
|
|
500
|
+
/**
|
|
501
|
+
* Drop the bindings through one of an Actor's slots or exports, its interface having changed,
|
|
502
|
+
* but those `keeps` says still match: each is audited as `binding.drop`. Dropping only takes
|
|
503
|
+
* access away. Returns what was dropped.
|
|
504
|
+
*/
|
|
505
|
+
#dropBindingsOf(actorId, side, name, by, keeps = () => false) {
|
|
506
|
+
const dropped = this.bindings(actorId).filter((b) => (side === "slot"
|
|
507
|
+
? b.consumer === actorId && b.slot === name
|
|
508
|
+
: b.provider === actorId && b.export === name) && !keeps(b));
|
|
509
|
+
for (const b of dropped)
|
|
510
|
+
this.#removeBindings("consumer = ? AND slot = ?", b.consumer, b.slot);
|
|
511
|
+
const wirings = dropped.map(({ consumer, slot, provider, export: exported }) => ({
|
|
512
|
+
consumer,
|
|
513
|
+
slot,
|
|
514
|
+
provider,
|
|
515
|
+
export: exported,
|
|
516
|
+
}));
|
|
517
|
+
for (const wiring of wirings)
|
|
518
|
+
this.#audit(by, "binding.drop", { ...wiring, why: "its interface changed" });
|
|
519
|
+
return wirings;
|
|
520
|
+
}
|
|
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
|
+
}
|
|
497
606
|
// ── Packages ────────────────────────────────────────────────────────────────
|
|
498
607
|
/** Add a `.agentorg` archive from a path on this machine, verified; adding it again is a no-op. */
|
|
499
608
|
addPackage(caller, path) {
|
|
@@ -604,8 +713,9 @@ export class HubState {
|
|
|
604
713
|
}
|
|
605
714
|
/**
|
|
606
715
|
* An external Actor's slots and static exports, as it declares them when it connects; they
|
|
607
|
-
* replace what it declared before. A
|
|
608
|
-
*
|
|
716
|
+
* replace what it declared before. A name redeclared as another interface loses its bindings
|
|
717
|
+
* (audited as `binding.drop`), since a binding holds only while both ends declare the same one.
|
|
718
|
+
* A name it no longer declares is dropped, with its bindings.
|
|
609
719
|
*/
|
|
610
720
|
declareExternal(actorId, declares) {
|
|
611
721
|
const actor = this.#open(actorId);
|
|
@@ -628,11 +738,19 @@ export class HubState {
|
|
|
628
738
|
for (const open of this.#pending("consumer = ? AND slot = ? AND exposed = 1", actorId, name))
|
|
629
739
|
this.#decide(open, "closed");
|
|
630
740
|
}
|
|
741
|
+
// 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.
|
|
631
744
|
const known = this.declaration(actorId, side, name);
|
|
632
|
-
if (known &&
|
|
633
|
-
this.#
|
|
634
|
-
|
|
635
|
-
|
|
745
|
+
if (known && compareInterfaces(known.iface, iface) !== "same") {
|
|
746
|
+
this.#dropBindingsOf(actorId, side, name, actorId, (b) => {
|
|
747
|
+
const other = side === "slot"
|
|
748
|
+
? this.declaration(b.provider, "export", b.export)?.iface
|
|
749
|
+
: this.declaration(b.consumer, "slot", b.slot)?.iface;
|
|
750
|
+
return !!other && compareInterfaces(iface, other) === "same";
|
|
751
|
+
});
|
|
752
|
+
this.#closeRequestsThrough(actorId, side, name);
|
|
753
|
+
}
|
|
636
754
|
this.#declare(actorId, side, name, iface, false);
|
|
637
755
|
}
|
|
638
756
|
const kept = Object.keys(members);
|
|
@@ -648,13 +766,6 @@ export class HubState {
|
|
|
648
766
|
this.#emit(() => this.#events.exportsChanged?.(actorId));
|
|
649
767
|
});
|
|
650
768
|
}
|
|
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
769
|
/** A declaration gone, and the bindings through it. */
|
|
659
770
|
#drop(actor, side, name) {
|
|
660
771
|
this.#db
|
|
@@ -172,6 +172,25 @@ export declare const hubActors: import("@agentorg/sdk").RpcInterface<"hub.actors
|
|
|
172
172
|
close: import("@agentorg/sdk").MethodSpec<z.ZodObject<{
|
|
173
173
|
actorId: z.ZodString;
|
|
174
174
|
}, z.core.$strip>, z.ZodObject<{}, z.core.$strip>>;
|
|
175
|
+
/**
|
|
176
|
+
* Run another package as the same package Actor: its id, its stores and every binding whose
|
|
177
|
+
* interfaces still match stay. A binding through a slot or export the new package drops, or
|
|
178
|
+
* declares as another interface, is dropped and listed in `dropped`; the new package's own
|
|
179
|
+
* wiring is made where its slot is unbound. A running Actor restarts on the new package.
|
|
180
|
+
* Only a manager may upgrade: the new package runs as the Actor and keeps what it was granted
|
|
181
|
+
* (its bindings and its store), which only a manager could grant anyway.
|
|
182
|
+
*/
|
|
183
|
+
upgrade: import("@agentorg/sdk").MethodSpec<z.ZodObject<{
|
|
184
|
+
actorId: z.ZodString;
|
|
185
|
+
package: z.ZodString;
|
|
186
|
+
}, z.core.$strip>, z.ZodObject<{
|
|
187
|
+
dropped: z.ZodArray<z.ZodObject<{
|
|
188
|
+
consumer: z.ZodString;
|
|
189
|
+
slot: z.ZodString;
|
|
190
|
+
provider: z.ZodString;
|
|
191
|
+
export: z.ZodString;
|
|
192
|
+
}, z.core.$strip>>;
|
|
193
|
+
}, z.core.$strip>>;
|
|
175
194
|
/** Its newest log lines, oldest first. */
|
|
176
195
|
logs: import("@agentorg/sdk").MethodSpec<z.ZodObject<{
|
|
177
196
|
actorId: z.ZodString;
|
|
@@ -120,6 +120,20 @@ export const hubActors = defineInterface({
|
|
|
120
120
|
stop: method({ input: z.object({ actorId: Id }), output: Empty }),
|
|
121
121
|
/** Final: its bindings, both ways, are removed. */
|
|
122
122
|
close: method({ input: z.object({ actorId: Id }), output: Empty }),
|
|
123
|
+
/**
|
|
124
|
+
* Run another package as the same package Actor: its id, its stores and every binding whose
|
|
125
|
+
* interfaces still match stay. A binding through a slot or export the new package drops, or
|
|
126
|
+
* declares as another interface, is dropped and listed in `dropped`; the new package's own
|
|
127
|
+
* wiring is made where its slot is unbound. A running Actor restarts on the new package.
|
|
128
|
+
* Only a manager may upgrade: the new package runs as the Actor and keeps what it was granted
|
|
129
|
+
* (its bindings and its store), which only a manager could grant anyway.
|
|
130
|
+
*/
|
|
131
|
+
upgrade: method({
|
|
132
|
+
input: z.object({ actorId: Id, package: Hash }),
|
|
133
|
+
output: z.object({
|
|
134
|
+
dropped: z.array(z.object({ consumer: Id, slot: Name, provider: Id, export: Name })),
|
|
135
|
+
}),
|
|
136
|
+
}),
|
|
123
137
|
/** Its newest log lines, oldest first. */
|
|
124
138
|
logs: method({
|
|
125
139
|
input: z.object({ actorId: Id, limit: z.number().int().min(1).max(1000).optional() }),
|
package/dist/src/model.d.ts
CHANGED
|
@@ -35,10 +35,13 @@ export interface FirstRun {
|
|
|
35
35
|
* The first run, at every start after the person's store is bound: add the bundled packages,
|
|
36
36
|
* ensure the installer (made by the person, with its bindings, started), bind the person's
|
|
37
37
|
* `installer` to it, ensure the model Actor installed through it (`web` to `egress`, `store` to
|
|
38
|
-
* storage's `kv`, started), and bind the person's `login` to the model's. What
|
|
39
|
-
*
|
|
40
|
-
*
|
|
41
|
-
*
|
|
38
|
+
* storage's `kv`, started), and bind the person's `login` to the model's. What earlier runs made
|
|
39
|
+
* (recorded in the person's store) is kept and brought to this build: an installer or model Actor
|
|
40
|
+
* on another package is upgraded in place, with its id, its store and its bindings; so is an agent
|
|
41
|
+
* on an assistant build an earlier run bundled (by the hashes recorded, never by a package's
|
|
42
|
+
* name). A binding an upgrade drops, its interface having changed, is made again where its ends
|
|
43
|
+
* match now. A step that fails is logged and the rest still runs where it can; the next start
|
|
44
|
+
* tries again.
|
|
42
45
|
*/
|
|
43
46
|
export declare function firstRun(person: PersonCalls, options?: FirstRunOptions): Promise<FirstRun>;
|
|
44
47
|
/**
|
package/dist/src/model.js
CHANGED
|
@@ -35,10 +35,13 @@ const INSTALLER_BINDINGS = [
|
|
|
35
35
|
* The first run, at every start after the person's store is bound: add the bundled packages,
|
|
36
36
|
* ensure the installer (made by the person, with its bindings, started), bind the person's
|
|
37
37
|
* `installer` to it, ensure the model Actor installed through it (`web` to `egress`, `store` to
|
|
38
|
-
* storage's `kv`, started), and bind the person's `login` to the model's. What
|
|
39
|
-
*
|
|
40
|
-
*
|
|
41
|
-
*
|
|
38
|
+
* storage's `kv`, started), and bind the person's `login` to the model's. What earlier runs made
|
|
39
|
+
* (recorded in the person's store) is kept and brought to this build: an installer or model Actor
|
|
40
|
+
* on another package is upgraded in place, with its id, its store and its bindings; so is an agent
|
|
41
|
+
* on an assistant build an earlier run bundled (by the hashes recorded, never by a package's
|
|
42
|
+
* name). A binding an upgrade drops, its interface having changed, is made again where its ends
|
|
43
|
+
* match now. A step that fails is logged and the rest still runs where it can; the next start
|
|
44
|
+
* tries again.
|
|
42
45
|
*/
|
|
43
46
|
export async function firstRun(person, options = {}) {
|
|
44
47
|
const log = options.log ?? (() => { });
|
|
@@ -63,17 +66,48 @@ export async function firstRun(person, options = {}) {
|
|
|
63
66
|
// What earlier runs made, by role, kept while it isn't closed. Unread, nothing is made: a run that
|
|
64
67
|
// couldn't see its record would make a second installer.
|
|
65
68
|
const read = (await call("store", "get", { key: RECORD })).value;
|
|
66
|
-
const
|
|
69
|
+
const kept = (read ?? {});
|
|
70
|
+
// Every assistant build a run has bundled, by hash: an agent on one of them is this app's to
|
|
71
|
+
// bring to this build. Never a package by its name, which an agent's own package could take.
|
|
72
|
+
const earlier = [
|
|
73
|
+
...(Array.isArray(kept.assistants) ? kept.assistants : []),
|
|
74
|
+
kept.assistant,
|
|
75
|
+
].filter((hash) => typeof hash === "string");
|
|
76
|
+
const isWiring = (wiring) => typeof wiring === "object" &&
|
|
77
|
+
wiring !== null &&
|
|
78
|
+
["consumer", "slot", "provider", "export"].every((key) => typeof wiring[key] === "string");
|
|
79
|
+
const record = {
|
|
80
|
+
...Object.fromEntries(Object.entries(kept).filter(([role, id]) => (role === "installer" || role === "chatgpt" || role === "assistant") &&
|
|
81
|
+
typeof id === "string")),
|
|
82
|
+
...(earlier.length ? { assistants: [...new Set(earlier)] } : {}),
|
|
83
|
+
...(Array.isArray(kept.rebind) && kept.rebind.some(isWiring)
|
|
84
|
+
? { rebind: kept.rebind.filter(isWiring) }
|
|
85
|
+
: {}),
|
|
86
|
+
};
|
|
87
|
+
const save = () => call("store", "set", { key: RECORD, value: { ...record } });
|
|
67
88
|
const remember = (role, actorId) => {
|
|
68
89
|
record[role] = actorId;
|
|
69
|
-
return
|
|
90
|
+
return save();
|
|
70
91
|
};
|
|
71
|
-
// The assistant New agent installs is this build's, by hash
|
|
72
|
-
// an agent's own package could take.
|
|
92
|
+
// The assistant New agent installs is this build's, by hash.
|
|
73
93
|
if (packages.assistant) {
|
|
74
94
|
record.assistant = packages.assistant;
|
|
75
|
-
|
|
95
|
+
record.assistants = [...new Set([...earlier, packages.assistant])];
|
|
96
|
+
await attempt("recording the assistant", save);
|
|
76
97
|
}
|
|
98
|
+
/** Run this build's package as an Actor an earlier run made, keeping its id and its store. */
|
|
99
|
+
const upgrade = async (actor, hash, what) => {
|
|
100
|
+
const { dropped: lost } = await call("actors", "upgrade", {
|
|
101
|
+
actorId: actor.actorId,
|
|
102
|
+
package: hash,
|
|
103
|
+
});
|
|
104
|
+
if (lost.length) {
|
|
105
|
+
record.rebind = [...(record.rebind ?? []), ...lost];
|
|
106
|
+
await attempt("recording the bindings to make again", save);
|
|
107
|
+
}
|
|
108
|
+
log(`First run: upgraded ${what} (${actor.actorId}) to this build${lost.length ? `; ${lost.length} of its bindings to make again` : ""}.`);
|
|
109
|
+
return { ...actor, package: hash };
|
|
110
|
+
};
|
|
77
111
|
/** The Actor an earlier run made for `role`, unless it is gone or closed. */
|
|
78
112
|
const madeBefore = async (role) => {
|
|
79
113
|
const id = record[role];
|
|
@@ -129,9 +163,12 @@ export async function firstRun(person, options = {}) {
|
|
|
129
163
|
actor = { actorId, name: "Installer", package: hash, state: "created" };
|
|
130
164
|
}
|
|
131
165
|
else {
|
|
166
|
+
const hash = packages[INSTALLER];
|
|
167
|
+
const before = actor;
|
|
168
|
+
if (hash && actor.package !== hash)
|
|
169
|
+
actor =
|
|
170
|
+
(await attempt("upgrading the installer", () => upgrade(before, hash, "the installer"))) ?? actor;
|
|
132
171
|
await rebind(actor, INSTALLER_BINDINGS);
|
|
133
|
-
if (actor.package !== packages[INSTALLER])
|
|
134
|
-
log("First run: kept the installer; the bundled build is added, not used.");
|
|
135
172
|
}
|
|
136
173
|
await personSlot("installer", { actor: actor.actorId, export: "installer.installer" });
|
|
137
174
|
await running(actor);
|
|
@@ -143,11 +180,14 @@ export async function firstRun(person, options = {}) {
|
|
|
143
180
|
{ slot: "model.store", provider: "storage", export: "kv" },
|
|
144
181
|
];
|
|
145
182
|
const chatgpt = await attempt("ChatGPT", async () => {
|
|
146
|
-
|
|
183
|
+
let actor = await madeBefore("chatgpt");
|
|
147
184
|
if (actor) {
|
|
185
|
+
const hash = packages[CHATGPT];
|
|
186
|
+
const before = actor;
|
|
187
|
+
if (hash && actor.package !== hash)
|
|
188
|
+
actor =
|
|
189
|
+
(await attempt("upgrading ChatGPT", () => upgrade(before, hash, "ChatGPT"))) ?? actor;
|
|
148
190
|
await rebind(actor, chatgptBindings);
|
|
149
|
-
if (actor.package !== packages[CHATGPT])
|
|
150
|
-
log("First run: kept ChatGPT; the bundled build is added, not used.");
|
|
151
191
|
await running(actor);
|
|
152
192
|
return actor.actorId;
|
|
153
193
|
}
|
|
@@ -176,6 +216,35 @@ export async function firstRun(person, options = {}) {
|
|
|
176
216
|
});
|
|
177
217
|
if (chatgpt)
|
|
178
218
|
await attempt("binding the login", () => personSlot("login", { actor: chatgpt, export: "model.login" }));
|
|
219
|
+
// Agents on an assistant build an earlier run bundled: brought to this build's assistant.
|
|
220
|
+
const assistant = packages.assistant;
|
|
221
|
+
if (assistant) {
|
|
222
|
+
const agents = await attempt("finding agents on earlier builds", async () => (await call("actors", "list")).actors.filter((a) => a.state !== "closed" && a.package !== assistant && earlier.includes(a.package)));
|
|
223
|
+
for (const agent of agents ?? [])
|
|
224
|
+
await attempt(`upgrading ${agent.name}`, () => upgrade(agent, assistant, `the agent ${agent.name}`));
|
|
225
|
+
}
|
|
226
|
+
// What upgrades dropped, this run or one that stopped before this, made again where its slot is
|
|
227
|
+
// still unbound: the ends now match, both on this build. One that can't be yet (an upgrade
|
|
228
|
+
// failed, or its ends still differ) is logged and kept for the next start.
|
|
229
|
+
if (record.rebind) {
|
|
230
|
+
const left = [];
|
|
231
|
+
for (const wiring of record.rebind) {
|
|
232
|
+
const made = await attempt(`binding ${wiring.slot} again`, async () => {
|
|
233
|
+
if ((await bindingsOf(wiring.consumer)).some((b) => b.slot === wiring.slot))
|
|
234
|
+
return true;
|
|
235
|
+
await call("grants", "bind", wiring);
|
|
236
|
+
log(`First run: bound ${wiring.consumer}'s ${wiring.slot} again.`);
|
|
237
|
+
return true;
|
|
238
|
+
});
|
|
239
|
+
if (!made)
|
|
240
|
+
left.push(wiring);
|
|
241
|
+
}
|
|
242
|
+
if (left.length)
|
|
243
|
+
record.rebind = left;
|
|
244
|
+
else
|
|
245
|
+
delete record.rebind;
|
|
246
|
+
await attempt("recording the bindings made again", save);
|
|
247
|
+
}
|
|
179
248
|
return {
|
|
180
249
|
packages,
|
|
181
250
|
...(installer ? { installer } : {}),
|