@quo-systems/dock 0.2.0 → 0.2.2
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/beings/avatar.ts +9 -3
- package/beings/carry.ts +101 -0
- package/beings/desk.ts +2 -2
- package/beings/index.ts +3 -1
- package/beings/link.ts +54 -0
- package/beings/look.ts +96 -0
- package/beings/quo-dock.md +163 -277
- package/beings/side.ts +10 -1
- package/beings/user.ts +67 -13
- package/cli/daemon.ts +58 -251
- package/cli/http.ts +70 -0
- package/cli/quo.ts +13 -5
- package/dist/beings/avatar.js +10 -3
- package/dist/beings/carry.d.ts +10 -0
- package/dist/beings/carry.js +106 -0
- package/dist/beings/desk.d.ts +1 -0
- package/dist/beings/desk.js +1 -1
- package/dist/beings/index.d.ts +2 -0
- package/dist/beings/index.js +3 -1
- package/dist/beings/link.d.ts +7 -0
- package/dist/beings/link.js +42 -0
- package/dist/beings/look.d.ts +27 -0
- package/dist/beings/look.js +71 -0
- package/dist/beings/side.d.ts +8 -1
- package/dist/beings/user.d.ts +29 -3
- package/dist/beings/user.js +71 -14
- package/dist/cli/daemon.d.ts +7 -19
- package/dist/cli/daemon.js +51 -240
- package/dist/cli/http.d.ts +17 -0
- package/dist/cli/http.js +60 -0
- package/dist/cli/quo.js +15 -5
- package/dist/harbor/quo.d.ts +4 -0
- package/dist/harbor/quo.js +53 -0
- package/dist/human/door.d.ts +5 -0
- package/dist/human/door.js +19 -0
- package/dist/human/guest.d.ts +3 -0
- package/dist/human/guest.js +25 -0
- package/dist/human/html.d.ts +10 -2
- package/dist/human/html.js +61 -10
- package/dist/human/screen.d.ts +8 -3
- package/dist/human/screen.js +26 -6
- package/dist/human/tab.d.ts +5 -0
- package/dist/human/tab.js +161 -43
- package/dist/human/web.d.ts +9 -0
- package/dist/human/web.js +95 -0
- package/dist/human/worlds.d.ts +10 -0
- package/dist/human/worlds.js +38 -0
- package/dist/mcp/http.d.ts +4 -3
- package/dist/mcp/http.js +6 -6
- package/dist/mcp/oauth.d.ts +9 -4
- package/dist/mcp/oauth.js +15 -14
- package/dist/mcp/pilot.js +1 -1
- package/dist/mcp/route.d.ts +12 -0
- package/dist/mcp/route.js +34 -0
- package/dist/mcp/server.d.ts +5 -10
- package/dist/mcp/server.js +35 -4
- package/dist/mcp/web/exchange.d.ts +5 -2
- package/dist/mcp/web/exchange.js +21 -7
- package/harbor/quo-harbor.md +67 -30
- package/harbor/quo.ts +67 -0
- package/human/door.ts +26 -0
- package/human/guest.ts +26 -0
- package/human/html.ts +58 -10
- package/human/quo-human.md +158 -67
- package/human/screen.ts +34 -8
- package/human/tab.ts +193 -53
- package/human/web.ts +123 -0
- package/human/worlds.ts +52 -0
- package/mcp/http.ts +10 -9
- package/mcp/oauth.ts +20 -17
- package/mcp/pilot.ts +1 -1
- package/mcp/quo-mcp.md +20 -2
- package/mcp/route.ts +39 -0
- package/mcp/server.ts +37 -18
- package/mcp/web/exchange.ts +23 -9
- package/package.json +2 -2
package/beings/quo-dock.md
CHANGED
|
@@ -102,7 +102,10 @@ that every arrival is named and judged.
|
|
|
102
102
|
- **Front desk.** A being on the user's harbor that strangers and fresh
|
|
103
103
|
clients meet first. It holds a standing to every user being and can ask
|
|
104
104
|
one for a device invitation. It is the only being that is ever public, and
|
|
105
|
-
only for the first hello.
|
|
105
|
+
only for the first hello. Public is not a kind of being: she is an ordinary
|
|
106
|
+
being who chose to describe herself to strangers and answer them, and what
|
|
107
|
+
she keeps is hers like anyone's. This one keeps little because that is the
|
|
108
|
+
desk's job, not because Quo asks it of her.
|
|
106
109
|
- **Envoy.** A being whose methods call a non-Quo thing and whose describe
|
|
107
110
|
is that thing's surface. A SaaS, an MCP server someone else runs, an
|
|
108
111
|
Arduino behind serial: each enters a world as a being like any other.
|
|
@@ -112,9 +115,40 @@ that every arrival is named and judged.
|
|
|
112
115
|
- **Client identity.** What a credential exchange establishes: this human at
|
|
113
116
|
this browser, this OAuth client for this human, this process on this
|
|
114
117
|
machine. One client identity is one avatar, for life.
|
|
118
|
+
- **Carrier.** A being who shows the asks of the standings she holds as asks
|
|
119
|
+
of her own and forwards. The user being is one: a device the human let
|
|
120
|
+
reach sees acme's asks on her, and acme sees her.
|
|
121
|
+
- **Look.** What a being may say about how she is presented, as values: a
|
|
122
|
+
name, a logo, colours, a font, and a hint per ask. One optional ask,
|
|
123
|
+
`look`, answers it, and every side reads the part it understands.
|
|
115
124
|
- **Route.** A hostname a reverse proxy sends to one process: `web.`, `quo.`,
|
|
116
125
|
`mcp.`. Routes are deployment, never boundaries.
|
|
117
126
|
|
|
127
|
+
## The ids
|
|
128
|
+
|
|
129
|
+
Every relation here is named by an id one being minted, and the dock's own
|
|
130
|
+
are these. They are conventions of this package and nothing of Quo's; a
|
|
131
|
+
different estate could choose others and change no word of the spec.
|
|
132
|
+
|
|
133
|
+
| id | who holds it | what it names |
|
|
134
|
+
| ------------------- | -------------- | ---------------------------------------- |
|
|
135
|
+
| `desk` | the user being | the front desk, her privileged occupant |
|
|
136
|
+
| `user:<name>` | the front desk | its standing to that human's user being |
|
|
137
|
+
| `<client>` | the user being | one device, by its client identity |
|
|
138
|
+
| `to:<client>` | the user being | her way back to that device |
|
|
139
|
+
| `user` | an avatar | her one standing, at the user being |
|
|
140
|
+
| `user:push` | an avatar | the user being as her occupant, to push |
|
|
141
|
+
| `avatar:<identity>` | the ward | the being key one client identity boots |
|
|
142
|
+
|
|
143
|
+
A device is two relations, because a relation in Quo is one direction: its
|
|
144
|
+
way in, the occupant, and her way back, `to:<client>`. Neither implies the
|
|
145
|
+
other and the door never joins them. The user being joins them herself, as
|
|
146
|
+
her own rule: she does not push to a device she no longer admits, and
|
|
147
|
+
`forget` drops both in one act. Her way back is handed over at hello and
|
|
148
|
+
taken when it can be: a knock back that is unreached, a tab whose pk the
|
|
149
|
+
far harbor has not bound yet or a phone in a tunnel, spends nothing, so she
|
|
150
|
+
keeps the invitation and takes it at the next push. A refusal is final.
|
|
151
|
+
|
|
118
152
|
## Architecture
|
|
119
153
|
|
|
120
154
|
One droplet, one harbor, three routes. Every other placement is a subset.
|
|
@@ -122,7 +156,7 @@ One droplet, one harbor, three routes. Every other placement is a subset.
|
|
|
122
156
|
```
|
|
123
157
|
acme.com, or razvan.com: the dialable part of an estate
|
|
124
158
|
reverse proxy
|
|
125
|
-
web.
|
|
159
|
+
web. one page per world, /<ward>, the credential exchange, ends with an invitation
|
|
126
160
|
quo. the harbor's socket door: the rendezvous for every dialer
|
|
127
161
|
mcp. the model side over HTTP, a credential exchange in front
|
|
128
162
|
quo serve: one process, one harbor, the ask pointer
|
|
@@ -164,6 +198,15 @@ standing to the user being is minted once and persists in the partition; a
|
|
|
164
198
|
side opening and closing is a reconnect, and the keys carry on. Reconnect
|
|
165
199
|
never re-invites.
|
|
166
200
|
|
|
201
|
+
An avatar is one standing, and a harbor holds as many avatars as it holds
|
|
202
|
+
relations; a ward only groups them. The droplet groups by client identity
|
|
203
|
+
per world, in the avatars ward. A tab groups by world, one local ward per
|
|
204
|
+
far world and one avatar per relation in it, so a fresh invitation is
|
|
205
|
+
always a fresh avatar and never a join on one that already holds hers: two
|
|
206
|
+
invitations into one world are two avatars, and two humans on one family
|
|
207
|
+
tablet are two relations. Which avatar a side speaks through is a switch
|
|
208
|
+
and nothing more.
|
|
209
|
+
|
|
167
210
|
One class for every side. It has:
|
|
168
211
|
|
|
169
212
|
- `answer`: the user being asks her only to push, and she forwards the push
|
|
@@ -202,8 +245,8 @@ last word. A push is the trigger of a turn, never a line smuggled into one:
|
|
|
202
245
|
a push that arrives while a turn is running waits in her cells and starts
|
|
203
246
|
the next turn when this one ends. Nothing is dropped and nothing is held;
|
|
204
247
|
the ask that carried the push was answered the moment it landed. Between
|
|
205
|
-
turns, the world reaches a model
|
|
206
|
-
run and a callback when it ends.
|
|
248
|
+
turns, the world reaches a model as `packages/dock/mcp/quo-mcp.md` says: an
|
|
249
|
+
ask that starts a run, and a callback when it ends.
|
|
207
250
|
|
|
208
251
|
One describe, many renderings. A screen is the user being's describe spoken
|
|
209
252
|
as forms; a tool list is the same describe spoken as tools. Nobody builds a
|
|
@@ -211,6 +254,63 @@ UI and then a tool layer: they build asks, both fall out, and neither side
|
|
|
211
254
|
can do a thing the other cannot, because the gate is one decision for
|
|
212
255
|
describe and for dispatch.
|
|
213
256
|
|
|
257
|
+
## Carrying
|
|
258
|
+
|
|
259
|
+
A device sees one describe, its avatar's one standing, and the human's
|
|
260
|
+
world is many standings: acme, the calendar, the house. The user being
|
|
261
|
+
carries them. Her describe for a device the human allowed to reach shows,
|
|
262
|
+
after her own asks, every ask of every standing she holds, each named
|
|
263
|
+
`<id>-<name>`, and answering one is an ask on that standing in her name. Acme
|
|
264
|
+
sees her id and never the device; the device sees acme without holding a
|
|
265
|
+
key anywhere; the human removes acme once, at her, and every device loses
|
|
266
|
+
it at once. Invariant 1 holds as written: the occupant still holds one
|
|
267
|
+
standing.
|
|
268
|
+
|
|
269
|
+
Carrying is a base, `beings/carry.ts`, and nothing in it knows what a user
|
|
270
|
+
is: a house being could carry its rooms, a company its suppliers. A subclass
|
|
271
|
+
says who may see what she carries, and the user being says a device with the
|
|
272
|
+
`reach` note, written when the human ticked the box at the exchange. The
|
|
273
|
+
notes of a carrier's describe say which asks are whose,
|
|
274
|
+
`standings: { id: { asks, look? } }`, so a screen draws one section per
|
|
275
|
+
standing and a model side keeps the flat list it needs; the mapping back
|
|
276
|
+
from a carried name to the standing is in her cells and never parsed from
|
|
277
|
+
the name. Her own asks come first and are never shadowed. She asks a
|
|
278
|
+
standing's describe again when its digest moved and its `look` once per
|
|
279
|
+
digest, so a describe of hers may cost one ask per stale standing, the price
|
|
280
|
+
of a page that is never more than one ask behind.
|
|
281
|
+
|
|
282
|
+
## The look
|
|
283
|
+
|
|
284
|
+
Three sides, one describe, and nothing hard-wired for any of them. A being
|
|
285
|
+
who wants to say how she is presented answers one more ask, `look`, with
|
|
286
|
+
values and no more, and every side reads the part it understands. The
|
|
287
|
+
vocabulary is closed, in `beings/look.ts`, and a token that fails its shape
|
|
288
|
+
is dropped, not fixed:
|
|
289
|
+
|
|
290
|
+
| token | shape | a screen | a model side |
|
|
291
|
+
| ---------------------- | ------------------------------------- | --------------------------------- | -------------------------- |
|
|
292
|
+
| name | text, no tags | her heading | nothing |
|
|
293
|
+
| logo | an image as a data URI, never a URL | beside the heading | nothing |
|
|
294
|
+
| accent, background, fg | hex colours | CSS variables on her section only | nothing |
|
|
295
|
+
| font | a font stack, plain characters | her section | nothing |
|
|
296
|
+
| radius | 0 to 40 | her corners | nothing |
|
|
297
|
+
| order | her ask names | which come first | tool order |
|
|
298
|
+
| asks.NAME.title | text | the button | the tool's title |
|
|
299
|
+
| asks.NAME.icon | one short string | on the button | nothing |
|
|
300
|
+
| asks.NAME.readOnly | boolean | run on open, nothing to type | `readOnlyHint` |
|
|
301
|
+
| asks.NAME.destructive | boolean | ask once more | `destructiveHint` |
|
|
302
|
+
| asks.NAME.idempotent | boolean | offer a retry after silence | `idempotentHint` |
|
|
303
|
+
|
|
304
|
+
A look is not capability: it is never in a blueprint or a digest, Quo never
|
|
305
|
+
sees it, and a being without one renders and lists exactly as before. The
|
|
306
|
+
`look` ask itself is presentation and is neither a form nor a tool. A
|
|
307
|
+
carrier keeps each standing's look beside her asks in the notes, so acme's
|
|
308
|
+
section on the human's page is painted as acme asked, inside that section
|
|
309
|
+
and nowhere else. No token can carry a URL, a stylesheet or code, so a far
|
|
310
|
+
being cannot paint over the page or reach a server through it. A being who
|
|
311
|
+
marks an ask read-only and then writes has lied to her own page, and nothing
|
|
312
|
+
enforces it, the way nothing enforces that her empty ask is safe to repeat.
|
|
313
|
+
|
|
214
314
|
## Asks only
|
|
215
315
|
|
|
216
316
|
There is no listening in Quo, and none under `packages/dock/`. A being who wants
|
|
@@ -219,8 +319,8 @@ whenever something happens. An event is an ask in the other direction, and
|
|
|
219
319
|
it is the same act next door and across planets; only latency and
|
|
220
320
|
reachability change. It follows that two beings never share memory, not
|
|
221
321
|
even in one ward: a callback, a stream or a bus between them is a link that
|
|
222
|
-
breaks the day one of them
|
|
223
|
-
when a being
|
|
322
|
+
breaks the day one of them is somewhere else, and Quo exists so that nothing
|
|
323
|
+
breaks when she is. Only a ward moves; a being is placed once, at boot.
|
|
224
324
|
|
|
225
325
|
Two consequences every side obeys:
|
|
226
326
|
|
|
@@ -258,22 +358,48 @@ thin as one call. Every route ends the same way:
|
|
|
258
358
|
1. The route authenticates by its own means. Password, passkey, OAuth, a
|
|
259
359
|
bearer token from a client, a nonce from a local process.
|
|
260
360
|
2. The route asks the front desk `device({ proof })` and receives an
|
|
261
|
-
invitation.
|
|
361
|
+
invitation. Which desk is which world: a harbor holds many wards, and
|
|
362
|
+
the human chooses the world at the allow page when there is more than
|
|
363
|
+
one; the grant remembers it.
|
|
262
364
|
3. The route hands the invitation to the avatar it is about to boot, or to
|
|
263
365
|
the one already booted for this client identity if it is a reconnect, in
|
|
264
366
|
which case nothing is handed and the existing standing is used.
|
|
265
367
|
4. The avatar knocks. The heir dies. From here there is no token anywhere.
|
|
266
368
|
|
|
267
|
-
The human decides
|
|
268
|
-
the identity: whether this device may **wake** the human's other devices
|
|
269
|
-
|
|
270
|
-
|
|
271
|
-
|
|
272
|
-
wake
|
|
273
|
-
|
|
274
|
-
the
|
|
275
|
-
|
|
276
|
-
|
|
369
|
+
The human decides two more things at step 1, on the same page that names
|
|
370
|
+
the identity: whether this device may **wake** the human's other devices,
|
|
371
|
+
and whether it may **reach** what she holds. Each word travels with the
|
|
372
|
+
proof, the user being writes it as a note on the occupant she mints, and her
|
|
373
|
+
gate reads the note: `push` is shown to the desk and to a device with the
|
|
374
|
+
`wake` note, and her standings are carried for a device with the `reach`
|
|
375
|
+
note, and for nobody else. A device that may wake can hand an agent an event
|
|
376
|
+
through the user being, and she sees who did; a device that may reach sees
|
|
377
|
+
acme on her, and a model sees acme only because the human said it may. The
|
|
378
|
+
notes are written when the occupant is minted; to change one, remove the
|
|
379
|
+
occupant and allow again, because reconnect mints nothing. A device the
|
|
380
|
+
root mints on her directly, with `quo invite`, carries no note: the id the
|
|
381
|
+
root chose is its client identity, and it reaches and may wake, since the
|
|
382
|
+
root owns everything she holds and chose to mint it.
|
|
383
|
+
|
|
384
|
+
Two proof kinds exist, and each arrives only the one way it is made:
|
|
385
|
+
`local`, a side that reached the daemon's side socket; `web`, a human who
|
|
386
|
+
passed the exchange pages. Each is a nonce the daemon minted for one kind
|
|
387
|
+
and honours once, under that kind alone, so a proof of any other provenance
|
|
388
|
+
names no nonce and is refused. A human at a screen passes no exchange: a
|
|
389
|
+
tab enters by a link, an invitation a user being minted, and nothing is
|
|
390
|
+
ever typed to get in. The root's first device is no exception; the root
|
|
391
|
+
mints its invitation with `quo invite` on the box and opens the link.
|
|
392
|
+
|
|
393
|
+
The route's own lifetimes, where it speaks OAuth: a pending request and a
|
|
394
|
+
code live ten minutes, an access token one hour, a refresh token thirty
|
|
395
|
+
days. A registered client lives ten minutes, and as long as its refresh
|
|
396
|
+
token once the human allowed it, since the register door is open to anyone
|
|
397
|
+
and what nobody allowed must not stay on disk. A code is spent the first
|
|
398
|
+
time it is offered, right or wrong. The client identity is chosen by the
|
|
399
|
+
human at the allow page and never taken from the client's registration: a
|
|
400
|
+
client may register afresh on every connect, and the avatar outlives that.
|
|
401
|
+
|
|
402
|
+
Tokens, cookies and grants exist for the length of step 1 above and are never
|
|
277
403
|
stored on a Quo side. Revocation is `occupants.remove(id)` on the user being:
|
|
278
404
|
the client's next ask, under the key the door bound for its avatar, hears
|
|
279
405
|
`removed`, and the side speaks that in its own vocabulary: the MCP route
|
|
@@ -283,7 +409,11 @@ answers 401 at the next session and forgets the grant.
|
|
|
283
409
|
|
|
284
410
|
One command, `quo`, under `packages/dock/cli/`. It imports the four library exports
|
|
285
411
|
and nothing else, holds no class body, and never speaks to a ward except
|
|
286
|
-
through the ask pointer or an avatar, the two doors everyone has.
|
|
412
|
+
through the ask pointer or an avatar, the two doors everyone has. The
|
|
413
|
+
daemon's folder holds the process and its doors, the two sockets and the
|
|
414
|
+
HTTP listener; each route on that listener lives in the folder whose truth
|
|
415
|
+
it is, `human/web.ts`, `mcp/route.ts`, `harbor/quo.ts`, and the daemon only
|
|
416
|
+
mounts them.
|
|
287
417
|
|
|
288
418
|
```
|
|
289
419
|
quo init [--dir D] mint a seed, boot a harbor and one ward over a disk store, write both
|
|
@@ -292,7 +422,7 @@ quo pilot [--dir D] [--via S] the owner pilot over stdio: the ward's describe as
|
|
|
292
422
|
quo side [--dir D] --as NAME an avatar over stdio for a local client; NAME is the client identity
|
|
293
423
|
quo run [--dir D] --as NAME --url URL --model NAME a human at a terminal talking to a model through an avatar; a line in, the final text out
|
|
294
424
|
quo census [--dir D] print the census once and exit
|
|
295
|
-
quo boot|public|invite|knock|remove the
|
|
425
|
+
quo boot|public|invite|knock|remove|unboot the six owner asks from a shell, for scripts and for agents under cron; --via S sends them to another ward
|
|
296
426
|
quo reach <pk> <url> a hint for the harbor's directory: that world's ward lives at that quo. route
|
|
297
427
|
quo estate <dir> --domain D an estate folder of the shape every estate has: a droplet's quo directory, its unit and Caddyfile, a package on this dock
|
|
298
428
|
```
|
|
@@ -393,8 +523,8 @@ to be decided.
|
|
|
393
523
|
|
|
394
524
|
## Generality
|
|
395
525
|
|
|
396
|
-
|
|
397
|
-
implementation of one of them. An estate implements none of them: it holds
|
|
526
|
+
Eight general pieces exist under `packages/dock/`, and everything concrete is
|
|
527
|
+
an implementation of one of them. An estate implements none of them: it holds
|
|
398
528
|
class bodies and placement, and reaches every piece through the dock.
|
|
399
529
|
|
|
400
530
|
| piece | one interface for | implementations |
|
|
@@ -405,6 +535,8 @@ class bodies and placement, and reaches every piece through the dock.
|
|
|
405
535
|
| side | an avatar speaking to someone outside | screen, MCP server, runner, agent |
|
|
406
536
|
| proof | turning a credential into an invitation | one verifier, a daemon nonce, three kinds |
|
|
407
537
|
| envoy | a non-Quo surface as a being | none built: a name until its first user |
|
|
538
|
+
| carrier | a being showing her standings as her own | the user being; any estate being that extends it |
|
|
539
|
+
| look | how a being is presented, as values | read by the screen and the model side |
|
|
408
540
|
|
|
409
541
|
Two rules keep them general:
|
|
410
542
|
|
|
@@ -415,8 +547,8 @@ Two rules keep them general:
|
|
|
415
547
|
|
|
416
548
|
Harbor core, reach and store held across three terrains and moved into
|
|
417
549
|
`packages/quo/src` with `packages/quo/SPEC.md` in the same commit on 2026-09-04;
|
|
418
|
-
what stays here of them is each terrain's store, loader, lease and listener.
|
|
419
|
-
other
|
|
550
|
+
what stays here of them is each terrain's store, loader, lease and listener.
|
|
551
|
+
The other five are dock conventions and stay here.
|
|
420
552
|
|
|
421
553
|
## Invariants
|
|
422
554
|
|
|
@@ -444,8 +576,11 @@ other three are dock conventions and stay here.
|
|
|
444
576
|
|
|
445
577
|
## Where the dock stands
|
|
446
578
|
|
|
447
|
-
The one place under `packages/dock/` where a gap between these documents and
|
|
448
|
-
folders is named. Each line is a debt to close, not a note to keep.
|
|
579
|
+
The one place under `packages/dock/` where a gap between these documents and
|
|
580
|
+
the folders is named. Each line is a debt to close, not a note to keep.
|
|
581
|
+
|
|
582
|
+
No gap today. Everything these documents describe is in the folders and on
|
|
583
|
+
the machines, and MCP is complete.
|
|
449
584
|
|
|
450
585
|
Two rules hold:
|
|
451
586
|
|
|
@@ -459,262 +594,12 @@ Two rules hold:
|
|
|
459
594
|
keep in step with every change, and the vectors under `packages/quo/vectors/`
|
|
460
595
|
are the hand that will be extended when the time comes.
|
|
461
596
|
|
|
462
|
-
|
|
463
|
-
above or sending us back to fix it:
|
|
464
|
-
|
|
465
|
-
1. Done. `packages/dock/` scaffold: its own package linking the library, the
|
|
466
|
-
four exports only, `check:dock` in the root gate, the terrain test pointed at
|
|
467
|
-
`packages/dock/beings`. The disk harbor in `packages/dock/harbor/disk.ts`:
|
|
468
|
-
one folder per ward holding seed, partition and the class source, a ward
|
|
469
|
-
record, the lease, and a save after every call. The `quo` CLI: init, serve,
|
|
470
|
-
census, boot, invite, knock, over the daemon's unix socket. Pilot and side
|
|
471
|
-
landed at steps 4 and 5.
|
|
472
|
-
2. Done. The beings in `packages/dock/beings/`: `User`, `Desk`, `Avatar`, proven
|
|
473
|
-
on the memory harbor in `packages/dock/test/beings.test.ts`. A fake proof
|
|
474
|
-
becomes an invitation the user being minted, the avatar knocks and takes
|
|
475
|
-
her, and her tool list equals the user being's describe for her;
|
|
476
|
-
reconnect mints nothing; two clients see two tool lists; a push is a
|
|
477
|
-
reverse ask that reaches the side; remove is revocation. The ids that
|
|
478
|
-
carry the conventions: the desk is the occupant the root named `desk`
|
|
479
|
-
at the user being; the desk holds each user being as `user:<name>`; the
|
|
480
|
-
user being holds each device's way back as `to:<client>`; the avatar
|
|
481
|
-
holds the user being as `user` and is pushed to as `user:push`, two ids
|
|
482
|
-
because standings and occupants share one namespace.
|
|
483
|
-
3. Done. The side in `packages/dock/beings/side.ts`: a function of the avatar
|
|
484
|
-
that runs until closed, and `word`, which tells the three words and the
|
|
485
|
-
object apart before any side renders them. The MCP server in
|
|
486
|
-
`packages/dock/mcp/server.ts`, proven over the SDK's in-memory transport in
|
|
487
|
-
`packages/dock/test/mcp.test.ts`: tools/list is her describe spoken as tools,
|
|
488
|
-
tools/call is a named ask, an object is a result, an error object and
|
|
489
|
-
the two words are error results with the text the trunk gives them, a
|
|
490
|
-
push is a logging notification, and a session closing and opening
|
|
491
|
-
reuses the standing. The SDK is the dock's first dependency, under
|
|
492
|
-
`packages/dock/` alone.
|
|
493
|
-
4. Done. `quo side --as NAME` over stdio, proven in
|
|
494
|
-
`packages/dock/test/cli.test.ts` with the SDK's stdio client as a child
|
|
495
|
-
process: the daemon finds or boots
|
|
496
|
-
`avatar:NAME`, enters her with a nonce the desk honours once, and runs
|
|
497
|
-
the model side beside her; the tool list is the user being's describe
|
|
498
|
-
for that client; a second start is a reconnect and mints nothing; a
|
|
499
|
-
`local` proof from anywhere else is refused. `quo init` now does the
|
|
500
|
-
root's setup, the desk public and one user being named by `--user`, so
|
|
501
|
-
a fresh folder is a world. Claude Code and Claude Desktop on the Mac
|
|
502
|
-
need only `quo serve` running and `quo side --as claude` as the server
|
|
503
|
-
command: no droplet, no OAuth.
|
|
504
|
-
5. Done. `quo pilot` in `packages/dock/mcp/pilot.ts`, proven in
|
|
505
|
-
`packages/dock/test/cli.test.ts` with the SDK's stdio client as a child
|
|
506
|
-
process: the model side of step 3 over the ward's owner asks, the
|
|
507
|
-
ward's describe as tools and no list of its own, each call an owner ask
|
|
508
|
-
on the daemon's root socket, and nothing logged. It is the root and not
|
|
509
|
-
an avatar: nothing is booted for it and no occupant is minted for it.
|
|
510
|
-
With no daemon it says so and exits. Claude Code as the owner is
|
|
511
|
-
`claude mcp add` with `quo pilot` as the command.
|
|
512
|
-
6. Done. `quo serve --http PORT`: the HTTP door on loopback, `/health`
|
|
513
|
-
naming the wards, routes mounting by path so a hostname on the proxy is
|
|
514
|
-
a path on the daemon; proven in `packages/dock/test/cli.test.ts`. The systemd
|
|
515
|
-
unit and the Caddyfile are in `estates/lab/droplet/`. Disk store, the
|
|
516
|
-
lease and reboot from disk with relations intact were steps 1 and 4.
|
|
517
|
-
7. Done. OAuth on the `mcp.` route in `packages/dock/mcp/oauth.ts`, mounted by
|
|
518
|
-
the daemon when `<dir>/routes.json` names the two public origins, and
|
|
519
|
-
proven over real HTTP in `packages/dock/test/oauth.test.ts`: metadata,
|
|
520
|
-
protected-resource metadata, dynamic registration, PKCE with S256, the
|
|
521
|
-
code grant, refresh with rotation, opaque tokens mapping a bearer to a
|
|
522
|
-
client identity, revoke by identity, and expiry. The conventions pinned
|
|
523
|
-
here: a pending request and a code live ten minutes, an access token
|
|
524
|
-
one hour, a refresh token thirty days; a registered client lives ten
|
|
525
|
-
minutes, and as long as its refresh token once the human allowed it,
|
|
526
|
-
since the register door is open to anyone and what nobody allowed must
|
|
527
|
-
not stay on disk; a code is spent the first time
|
|
528
|
-
it is offered, right or wrong; the client identity is chosen at the
|
|
529
|
-
allow page, not taken from the client's registration, because a client
|
|
530
|
-
like claude.ai registers a fresh client id on every connect and the
|
|
531
|
-
avatar must outlive that. The store is `<dir>/oauth.json`, the route's
|
|
532
|
-
own, mode 0600, and holds no secret of Quo's.
|
|
533
|
-
8. Done. The exchange pages in `packages/dock/mcp/web/exchange.ts`, mounted on
|
|
534
|
-
the web route, proven over real HTTP in `packages/dock/test/web.test.ts`.
|
|
535
|
-
Login checks `QUO_OWNER_PASSWORD` in constant time and sets a signed
|
|
536
|
-
session cookie that lives ten minutes, the life of a request; with no
|
|
537
|
-
password set the exchange is closed. Allow names the client, where it
|
|
538
|
-
returns to, and the identity it will be, suggested from the client's
|
|
539
|
-
name and editable, one word and not the user or the desk. Allow admits
|
|
540
|
-
her through the one path every side uses: find or boot the avatar,
|
|
541
|
-
enter with a nonce the desk honours once under the `web` proof kind,
|
|
542
|
-
then complete the OAuth request with that identity. Deny sends the
|
|
543
|
-
client `access_denied`. Two pages, hand-written, rendering no blueprint.
|
|
544
|
-
9. Done. The MCP endpoint in `packages/dock/mcp/http.ts` at `/mcp` on the mcp
|
|
545
|
-
route: streamable HTTP, a bearer names the identity, the identity names
|
|
546
|
-
her avatar through the same admit path, the side from step 3 runs
|
|
547
|
-
beside her, one session per connection, a session belonging to the
|
|
548
|
-
identity that opened it. Proven in `packages/dock/test/http.test.ts` with the
|
|
549
|
-
SDK's streamable HTTP client over the whole exchange. MCP is complete:
|
|
550
|
-
every human-assisted case on every device, with nothing of Quo's
|
|
551
|
-
crossing a machine.
|
|
552
|
-
10. Done. The runner in `packages/dock/mcp/runner.ts`: the second side, an MCP
|
|
553
|
-
client written inline for a model that speaks function calling. Her
|
|
554
|
-
describe is a tools array, a tool call is a named ask, the three words
|
|
555
|
-
are tool messages, a line from the human or a push from the user being
|
|
556
|
-
starts a turn, and turns never overlap. Proven in
|
|
557
|
-
`packages/dock/test/runner.test.ts` over a scripted endpoint on loopback, and
|
|
558
|
-
live against Qwen3 Coder 30B in LM Studio on the Mac, which read her
|
|
559
|
-
describe, called `whoami` through the gate and answered with the id she
|
|
560
|
-
minted. What it taught: LM Studio refuses a parameters schema with no
|
|
561
|
-
`properties`, so the runner is where a describe is narrowed for an
|
|
562
|
-
endpoint, and nowhere else.
|
|
563
|
-
11. Done. The agent side in `packages/dock/mcp/agent.ts`: the world is the loop.
|
|
564
|
-
A push from the user being is acknowledged at once by her `notify`,
|
|
565
|
-
lands in her inbox, which is the queue in her cells, and the side runs
|
|
566
|
-
it, one event at a time in order, then asks the user being back with
|
|
567
|
-
`report({ event, result })`, the ask this step added to her, gated to
|
|
568
|
-
devices. A run is a function of the event or a process in the agent's
|
|
569
|
-
folder with the event as its last argument, which is what `claude -p`
|
|
570
|
-
is. Proven in `packages/dock/test/agent.test.ts` with a fake run and a fake
|
|
571
|
-
process: the ask is answered before the run ends, a throw reports an
|
|
572
|
-
error object, and events that came while no side was up run when one
|
|
573
|
-
comes up. What it decided: the queue lives in the partition and not in
|
|
574
|
-
the side, so a restart loses nothing but the one run in flight. On a
|
|
575
|
-
device, `<dir>/agents.json` names each agent's identity, command and
|
|
576
|
-
folder, and the daemon admits her on the local proof and runs her side
|
|
577
|
-
beside her avatar; proven in the same suite over a disk harbor.
|
|
578
|
-
12. The `quo.` route: Done. The reach interface with request and socket, since
|
|
579
|
-
the library move `packages/quo/src/harbor/reach.ts`, the disk harbor holds
|
|
580
|
-
the directory, and the daemon mounts `/quo` and dials with `--dial`. The
|
|
581
|
-
library's conformance suite passed untouched over two disk harbors on
|
|
582
|
-
loopback under both reaches, and the rendezvous carried a knock from a third
|
|
583
|
-
harbor to a dialer through its listener; `packages/dock/test/wire.test.ts`.
|
|
584
|
-
Live: a Mac daemon dialed `quo.lab.quo.systems`, an avatar there knocked at
|
|
585
|
-
the lab's user being across the internet, the lab knocked back over the held
|
|
586
|
-
socket and took `to:mac`, and a push from the lab landed in her inbox on the
|
|
587
|
-
Mac. What it taught: the proxy rewrites the route root with a trailing
|
|
588
|
-
slash, and a ward pk is 64 bytes on the wire;
|
|
589
|
-
`packages/dock/harbor/quo-harbor.md` has both.
|
|
590
|
-
13. The browser harbor: Done. Harbor core carved out of the disk harbor
|
|
591
|
-
into a core of its own, since the library move `packages/quo/src/harbor/core.ts`,
|
|
592
|
-
the store a piece with files and IndexedDB under it, the dialer its
|
|
593
|
-
own terrain-free piece, the socket reach on the standard surface.
|
|
594
|
-
`packages/dock/harbor/browser.ts` is the core in a tab: IndexedDB, a web lock
|
|
595
|
-
for the lease, one socket per world.
|
|
596
|
-
The library's conformance suite passed untouched inside a real
|
|
597
|
-
Chromium against two browser harbors in one tab through a daemon on
|
|
598
|
-
loopback, migration between two databases included;
|
|
599
|
-
`packages/dock/test/terrain/browser.test.ts`, behind `check:terrain`. And the
|
|
600
|
-
first screen, `packages/dock/human/tab.ts`, rudimentary on purpose: a page on
|
|
601
|
-
the web route that boots a harbor in the tab, takes the owner password
|
|
602
|
-
once under the `tab` proof kind, and renders her describe as buttons,
|
|
603
|
-
with pushes landing on the page; a reload asks nothing. What it taught:
|
|
604
|
-
a tab's store keeps values through JSON because the cells guard refuses
|
|
605
|
-
structured clone; a side in a tab saves after the exchange and after
|
|
606
|
-
every call, as the daemon does; and a dialed socket is the fallback
|
|
607
|
-
from the moment it is dialed, because a ward asks the instant it boots.
|
|
608
|
-
14. Owners at the door in the CLI: Done. The root socket takes `via`, a
|
|
609
|
-
standing the user being holds at another ward, and the daemon asks on
|
|
610
|
-
it in-process and saves; `quo pilot` and the bare owner asks take
|
|
611
|
-
`--via S`. Scene 8 on two daemons over a dialed socket in
|
|
612
|
-
`packages/dock/test/cli.test.ts`: the far root invites on its ward's pk, the
|
|
613
|
-
user being here knocks and takes `lab`, census and boot land there,
|
|
614
|
-
invite on the far pk is refused at the door, the standing survives a
|
|
615
|
-
restart, and the owner removed meets silence. What it taught: the
|
|
616
|
-
library had no owner ask that removes an owner, and the test reached
|
|
617
|
-
into the ward's cells; the word is `remove` now, decided in `packages/quo/SPEC.md`
|
|
618
|
-
at step 16.
|
|
619
|
-
15. The human side, minimal: Done. Three pieces under `packages/dock/human/`:
|
|
620
|
-
`html.ts`, pure, a schema as a form, a form back as typed arguments,
|
|
621
|
-
an answer as a view, the three words with faces; `screen.ts`, the side
|
|
622
|
-
over a surface that can only show a page and hand back a form,
|
|
623
|
-
re-asking her describe after every call so the page follows the
|
|
624
|
-
digest; `dom.ts`, the surface on an element. The tab keeps its harbor
|
|
625
|
-
and exchange and hands the avatar to the side. Proven on the memory
|
|
626
|
-
harbor with a fake surface in `packages/dock/test/human.test.ts`, and in
|
|
627
|
-
Chromium behind `check:terrain`. Minimal on purpose: design, a
|
|
628
|
-
switcher, the guest's page and the native app are left to the human
|
|
629
|
-
document's own sitting, and none of them blocks a world from having
|
|
630
|
-
a screen.
|
|
631
|
-
16. Removing: Done. `packages/quo/SPEC.md` gave the owner its fourth ask, `remove`,
|
|
632
|
-
the mirror of knock: a relation out of a being by id, occupant or
|
|
633
|
-
standing, and on the ward's own pk an owner, by the root alone. In
|
|
634
|
-
`packages/quo/src/ward/owner.ts` with its test; the pilot's fourth tool; `quo
|
|
635
|
-
remove` from the shell, the same word; the step 14 test asks it
|
|
636
|
-
instead of reaching into the cells. The lab's leftovers went with it,
|
|
637
|
-
recorded in `estates/lab/lab.md`.
|
|
638
|
-
17. What the lab wants: Done. A launchd agent beside the systemd unit in
|
|
639
|
-
`estates/lab/droplet/`, the daemon as the user forever with
|
|
640
|
-
`~/.quo/dial.json` naming the routes it holds a socket to, installed
|
|
641
|
-
on the operator's Mac against the lab. And `quo run`: the runner from
|
|
642
|
-
a terminal, the same pipe as `quo side` with a model named in the
|
|
643
|
-
hello, the daemon running the runner beside the avatar, a line in a
|
|
644
|
-
turn and the final text a line out, proven with a scripted endpoint
|
|
645
|
-
as a child process in `packages/dock/test/cli.test.ts`. The gate profile per
|
|
646
|
-
client identity the road lists is defined nowhere and was not built.
|
|
647
|
-
18. The edge terrain: Done. `packages/dock/harbor/edge/`, the core over a Durable
|
|
648
|
-
Object: its storage the store with the seed sealed under a platform
|
|
649
|
-
secret, the deployed worker the code, the object the lease; reached by
|
|
650
|
-
request, holding dialers' sockets, the root a route under a platform
|
|
651
|
-
secret. The conformance suite passed untouched inside the platform's
|
|
652
|
-
runtime, behind `check:terrain`; deployed to the lab's account and
|
|
653
|
-
piloted from the Mac over a standing, `--via edge`. The third terrain
|
|
654
|
-
the library move waited for; the `ws` question answered in the harbor
|
|
655
|
-
document. `estates/lab/lab.md` keeps the record, one slip included.
|
|
656
|
-
|
|
657
|
-
19. The split: Done. The estate folder became the dock, the lab's own
|
|
658
|
-
things moved to `estates/lab/`, and the dock's package took its name.
|
|
659
|
-
Then the repository took the shape that says what ships by where it
|
|
660
|
-
lives: `packages/quo/` and `packages/dock/` are the two published
|
|
661
|
-
packages, `estates/` is private, and the root is nobody's package, the
|
|
662
|
-
workspaces and the gate. The lab is an estate folder of the shape this
|
|
663
|
-
document names, one folder per harbor, and `estates/lab/test/boot.test.ts`
|
|
664
|
-
is invariant 12 proven: a copy of the folder, the two packages installed
|
|
665
|
-
into it and nothing else, and the droplet boots. The lab redeployed
|
|
666
|
-
from it: the droplet from a clean export of HEAD with its unit copied
|
|
667
|
-
from the estate's folder, the edge from the estate's own config
|
|
668
|
-
resolving the dock's worker by name, the Mac's agent on the new path.
|
|
669
|
-
What it taught: a nested npm under `npm run` inherits the outer run's
|
|
670
|
-
local prefix by environment and installs into the repository instead
|
|
671
|
-
of the empty folder, so the stranger tests keep nothing of it; and
|
|
672
|
-
`npx` in a workspace runs at the package root, so an estate names its
|
|
673
|
-
edge config from its own folder.
|
|
674
|
-
20. The dock published: Done. `@quo-systems/dock` 0.1.0, the same shape
|
|
675
|
-
the library took, an emitted `dist/` with the `quo` command pointing
|
|
676
|
-
into it, the source and the four documents beside it, and
|
|
677
|
-
`packages/dock/test/package.test.ts` as the one suite that meets it as
|
|
678
|
-
an adopter: the library's tarball and this one installed into an empty
|
|
679
|
-
folder with nothing but Node, then `quo init`, a daemon, and
|
|
680
|
-
`quo census` answering with the desk and the user being. The library
|
|
681
|
-
went to 0.2.0 first, since the door's words moved the wire. What it
|
|
682
|
-
decided: the daemon bundles the tab from beside itself, emitted or
|
|
683
|
-
source, whichever is there, the one place the dock reads a file by
|
|
684
|
-
path at run time.
|
|
685
|
-
|
|
686
|
-
21. A second estate: Done, and live. `estates/gherghina/`,
|
|
687
|
-
staging like the lab and nothing more, one harbor on the shared droplet
|
|
688
|
-
`nervur-staging-0`, routes under `gherghina.quo.systems`, no agents and
|
|
689
|
-
no classes of its own yet, the same unit and Caddyfile shape as the
|
|
690
|
-
lab's. It is the first proof that the dock has two users: its folder
|
|
691
|
-
depends on the dock alone, and `estates/gherghina/test/boot.test.ts`
|
|
692
|
-
runs the one proof every estate here runs, `estates/proof.ts`, shared
|
|
693
|
-
between them and shipped nowhere. The three staging droplets are shared
|
|
694
|
-
by every estate in this repository, and none of them is production or
|
|
695
|
-
holds real data. Its droplet stood up the same day, its document says
|
|
696
|
-
how; what it taught went into both estates' units: the agent's folders
|
|
697
|
-
are optional to the daemon, since a harbor may have no agent.
|
|
698
|
-
|
|
699
|
-
22. The scaffold: Done. `quo estate <dir> --domain D` writes an estate
|
|
700
|
-
folder of the shape the two estates here agreed on: a package file
|
|
701
|
-
depending on this dock and nothing else, one document to fill in, and
|
|
702
|
-
`droplet/` as the first harbor, its routes under the domain, no
|
|
703
|
-
agents, no classes of its own, and that device's unit and Caddyfile
|
|
704
|
-
from templates the dock ships beside its command. The unit it writes
|
|
705
|
-
runs the installed dock from the estate's folder on the box, since an
|
|
706
|
-
adopter has no tree there; the two estates here run the tree. Proven
|
|
707
|
-
in `packages/dock/test/cli.test.ts`: the folder is written, refuses to
|
|
708
|
-
overwrite a package or take a name that is no domain, and its droplet
|
|
709
|
-
boots, `quo init` into it and a daemon answering the census.
|
|
710
|
-
|
|
711
|
-
Steps 1 to 22 are in the tree and on the machines, and MCP is complete;
|
|
712
|
-
the documents and the folders agree up to step 22. There is no gap named
|
|
713
|
-
today. `next.md` is where the next sitting starts.
|
|
597
|
+
`next.md` is where the next sitting starts.
|
|
714
598
|
|
|
715
599
|
## Glossary
|
|
716
600
|
|
|
717
601
|
- **avatar**: a being that is someone's voice from outside Quo.
|
|
602
|
+
- **carrier**: a being who shows her standings' asks as her own and forwards.
|
|
718
603
|
- **client identity**: what a credential exchange establishes; one avatar.
|
|
719
604
|
- **envoy**: a being whose methods call a non-Quo thing.
|
|
720
605
|
- **dock**: what every estate needs and nobody writes twice, `packages/dock/`,
|
|
@@ -725,6 +610,7 @@ today. `next.md` is where the next sitting starts.
|
|
|
725
610
|
- **front desk**: the being every fresh client meets; hands out invitations
|
|
726
611
|
minted by user beings.
|
|
727
612
|
- **gate**: `for(occupant, asker)`; the whole permission model.
|
|
613
|
+
- **look**: how a being is presented, as values; one optional ask.
|
|
728
614
|
- **owner pilot**: `quo pilot`, the model side over a ward's owner asks, reaching
|
|
729
615
|
the daemon's socket; with `--via`, a ward elsewhere through a standing.
|
|
730
616
|
- **daemon**: `quo serve`, the one process on a device that holds a harbor.
|
package/beings/side.ts
CHANGED
|
@@ -6,13 +6,22 @@
|
|
|
6
6
|
// a screen, an MCP server, a runner, an event-driven agent. None knows
|
|
7
7
|
// another exists, and none of this file names a platform.
|
|
8
8
|
import { isSilence, isUnreached, isWord, wordOf } from '@quo-systems/quo';
|
|
9
|
-
import type { Answer, Json, JsonObject, WordName } from '@quo-systems/quo';
|
|
9
|
+
import type { Answer, Blueprint, Json, JsonObject, Wanted, WordName } from '@quo-systems/quo';
|
|
10
10
|
import type { Avatar } from './avatar.ts';
|
|
11
11
|
|
|
12
12
|
// A side, running: it holds the avatar until closed.
|
|
13
13
|
export type Serving = { close(): Promise<void> };
|
|
14
14
|
export type Side = (avatar: Avatar) => Promise<Serving>;
|
|
15
15
|
|
|
16
|
+
// What a side speaks for: the empty ask and a named one, and the ears a
|
|
17
|
+
// push reaches. An avatar on her one standing is one; the owner's asks on a
|
|
18
|
+
// ward are another; a guest at a world's public being is a third.
|
|
19
|
+
export type Subject = {
|
|
20
|
+
tools(): Promise<Blueprint | { error: string }>;
|
|
21
|
+
call(name: string, args?: JsonObject, wanted?: Wanted): Promise<Answer>;
|
|
22
|
+
ears: Set<(object: JsonObject) => void>;
|
|
23
|
+
};
|
|
24
|
+
|
|
16
25
|
// The three words for "no object", and the object itself, as every side must
|
|
17
26
|
// tell them apart before rendering. An error object is an ordinary answer
|
|
18
27
|
// that the side may mark; silence and unreached are not objects at all.
|