@nervur-org/dock 0.5.0 → 0.6.0
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 +7 -4
- package/api/route.ts +4 -72
- package/beings/avatar.ts +11 -7
- package/beings/carry.ts +129 -116
- package/beings/clock.ts +11 -7
- package/beings/desk.ts +7 -6
- package/beings/doorbell.ts +36 -7
- package/beings/index.ts +17 -4
- package/beings/join.ts +7 -4
- package/beings/link.ts +1 -1
- package/beings/porter.ts +5 -1
- package/beings/presence.ts +15 -5
- package/beings/setup.ts +27 -21
- package/beings/user.ts +26 -17
- package/beings/way.ts +1 -1
- package/{dock → box}/agents.ts +37 -44
- package/{dock → box}/apns.ts +4 -4
- package/box/box.ts +31 -0
- package/box/contract.ts +41 -0
- package/box/dialers.ts +88 -0
- package/box/dock.ts +148 -0
- package/box/envoy.ts +96 -0
- package/box/faculty.ts +111 -0
- package/{dock → box}/fcm.ts +1 -1
- package/box/grants.ts +90 -0
- package/box/http.ts +93 -0
- package/{dock → box}/http2.ts +7 -7
- package/box/index.ts +201 -0
- package/box/join.ts +78 -0
- package/box/local.ts +37 -0
- package/{dock → box}/location.ts +15 -3
- package/{dock → box}/notifications.ts +17 -5
- package/box/push.ts +85 -0
- package/box/ring.ts +85 -0
- package/{dock → box}/service.ts +2 -2
- package/box/timer.ts +150 -0
- package/box/tree.ts +22 -0
- package/{dock → box}/webpush.ts +2 -2
- package/cli/client.ts +1 -1
- package/cli/daemon.ts +101 -103
- package/cli/estate.ts +2 -3
- package/cli/nervur.ts +44 -25
- package/cli/stand.ts +1 -1
- package/dist/api/route.d.ts +1 -2
- package/dist/api/route.js +4 -76
- package/dist/beings/avatar.d.ts +4 -3
- package/dist/beings/avatar.js +9 -6
- package/dist/beings/carry.d.ts +10 -12
- package/dist/beings/carry.js +119 -127
- package/dist/beings/clock.js +10 -7
- package/dist/beings/desk.js +8 -7
- package/dist/beings/doorbell.d.ts +3 -1
- package/dist/beings/doorbell.js +37 -4
- package/dist/beings/index.d.ts +24 -4
- package/dist/beings/index.js +15 -4
- package/dist/beings/join.d.ts +1 -0
- package/dist/beings/join.js +6 -4
- package/dist/beings/link.d.ts +0 -1
- package/dist/beings/link.js +1 -1
- package/dist/beings/porter.js +5 -1
- package/dist/beings/presence.d.ts +1 -0
- package/dist/beings/presence.js +12 -3
- package/dist/beings/setup.d.ts +5 -2
- package/dist/beings/setup.js +16 -15
- package/dist/beings/user.d.ts +4 -3
- package/dist/beings/user.js +18 -11
- package/dist/{dock → box}/agents.d.ts +7 -4
- package/dist/{dock → box}/agents.js +30 -37
- package/dist/{dock → box}/apns.d.ts +1 -1
- package/dist/{dock → box}/apns.js +3 -3
- package/dist/box/box.d.ts +9 -0
- package/dist/box/box.js +18 -0
- package/dist/box/contract.d.ts +6 -0
- package/dist/box/contract.js +35 -0
- package/dist/{dock → box}/dialers.d.ts +7 -4
- package/dist/box/dialers.js +76 -0
- package/dist/box/dock.d.ts +182 -0
- package/dist/box/dock.js +128 -0
- package/dist/box/envoy.d.ts +44 -0
- package/dist/box/envoy.js +75 -0
- package/dist/box/faculty.d.ts +68 -0
- package/dist/box/faculty.js +97 -0
- package/dist/{dock → box}/fcm.d.ts +1 -1
- package/dist/{dock → box}/grants.d.ts +4 -3
- package/dist/box/grants.js +78 -0
- package/dist/box/http.d.ts +26 -0
- package/dist/box/http.js +89 -0
- package/dist/{dock → box}/http2.d.ts +1 -1
- package/dist/{dock → box}/http2.js +6 -6
- package/dist/box/index.d.ts +46 -0
- package/dist/box/index.js +179 -0
- package/dist/box/join.d.ts +19 -0
- package/dist/box/join.js +67 -0
- package/dist/{dock → box}/local.d.ts +3 -2
- package/dist/box/local.js +27 -0
- package/dist/{dock → box}/location.d.ts +3 -0
- package/dist/box/location.js +21 -0
- package/dist/{dock → box}/notifications.d.ts +3 -0
- package/dist/{dock → box}/notifications.js +14 -4
- package/dist/{dock → box}/push.d.ts +12 -18
- package/dist/box/push.js +83 -0
- package/dist/box/ring.d.ts +18 -0
- package/dist/box/ring.js +55 -0
- package/dist/{dock → box}/service.d.ts +1 -1
- package/dist/{dock → box}/service.js +1 -1
- package/dist/{dock → box}/timer.d.ts +2 -0
- package/dist/box/timer.js +127 -0
- package/dist/box/tree.d.ts +5 -0
- package/dist/box/tree.js +14 -0
- package/dist/{dock → box}/webpush.d.ts +1 -1
- package/dist/cli/client.d.ts +1 -0
- package/dist/cli/daemon.d.ts +2 -8
- package/dist/cli/daemon.js +102 -107
- package/dist/cli/estate.js +2 -3
- package/dist/cli/nervur.js +44 -24
- package/dist/cli/stand.d.ts +0 -14
- package/dist/cli/stand.js +1 -1
- package/dist/harbor/browser.d.ts +8 -5
- package/dist/harbor/browser.js +20 -21
- package/dist/harbor/capacitor.d.ts +3 -3
- package/dist/harbor/capacitor.js +9 -8
- package/dist/harbor/disk.d.ts +9 -9
- package/dist/harbor/disk.js +56 -51
- package/dist/harbor/edge/alarm.d.ts +4 -2
- package/dist/harbor/edge/alarm.js +9 -19
- package/dist/harbor/edge/edge.d.ts +7 -6
- package/dist/harbor/edge/edge.js +58 -53
- package/dist/harbor/edge/exercise.js +4 -4
- package/dist/harbor/edge/given.d.ts +0 -1
- package/dist/harbor/edge/given.js +0 -29
- package/dist/harbor/edge/storage.d.ts +0 -2
- package/dist/harbor/edge/storage.js +2 -2
- package/dist/harbor/http.js +4 -2
- package/dist/harbor/sealed.d.ts +0 -5
- package/dist/harbor/sealed.js +0 -39
- package/dist/harbor/tauri.d.ts +2 -2
- package/dist/harbor/tauri.js +13 -10
- package/dist/human/app.d.ts +1 -1
- package/dist/human/app.js +10 -12
- package/dist/human/door.d.ts +0 -1
- package/dist/human/door.js +1 -1
- package/dist/human/html.js +1 -1
- package/dist/human/move.js +1 -1
- package/dist/human/tab.bundle.txt +3629 -2824
- package/dist/human/wake.d.ts +5 -2
- package/dist/human/wake.js +23 -14
- package/dist/human/web.d.ts +0 -1
- package/dist/human/web.js +3 -3
- package/dist/human/worker.bundle.txt +2945 -2118
- package/dist/human/worlds.js +3 -3
- package/dist/mcp/agent.d.ts +0 -1
- package/dist/mcp/agent.js +1 -1
- package/dist/mcp/allow.js +3 -2
- package/dist/mcp/http.d.ts +0 -1
- package/dist/mcp/http.js +1 -1
- package/dist/mcp/oauth.d.ts +3 -4
- package/dist/mcp/oauth.js +4 -4
- package/dist/mcp/route.d.ts +1 -1
- package/dist/mcp/runner.d.ts +0 -1
- package/dist/mcp/runner.js +1 -1
- package/dist/mcp/server.d.ts +0 -1
- package/dist/mcp/server.js +1 -1
- package/harbor/browser.ts +22 -24
- package/harbor/capacitor.ts +9 -8
- package/harbor/disk.ts +53 -48
- package/harbor/edge/alarm.ts +15 -12
- package/harbor/edge/edge.ts +61 -55
- package/harbor/edge/exercise.ts +4 -4
- package/harbor/edge/given.ts +3 -33
- package/harbor/edge/storage.ts +2 -2
- package/harbor/http.ts +5 -3
- package/harbor/sealed.ts +0 -35
- package/harbor/tauri.ts +13 -10
- package/human/app.ts +12 -13
- package/human/door.ts +1 -1
- package/human/html.ts +1 -1
- package/human/local.ts +1 -1
- package/human/move.ts +1 -1
- package/human/tab.bundle.txt +3629 -2824
- package/human/wake.ts +36 -16
- package/human/web.ts +4 -4
- package/human/worker.bundle.txt +2945 -2118
- package/human/worlds.ts +3 -3
- package/mcp/agent.ts +1 -1
- package/mcp/allow.ts +2 -1
- package/mcp/http.ts +1 -1
- package/mcp/oauth.ts +4 -4
- package/mcp/route.ts +1 -1
- package/mcp/runner.ts +1 -1
- package/mcp/server.ts +1 -1
- package/package.json +9 -8
- package/beings/envoy.ts +0 -218
- package/dist/beings/envoy.d.ts +0 -54
- package/dist/beings/envoy.js +0 -211
- package/dist/dock/console.d.ts +0 -6
- package/dist/dock/console.js +0 -20
- package/dist/dock/dialers.js +0 -75
- package/dist/dock/faculty.d.ts +0 -24
- package/dist/dock/faculty.js +0 -142
- package/dist/dock/grants.js +0 -99
- package/dist/dock/index.d.ts +0 -41
- package/dist/dock/index.js +0 -113
- package/dist/dock/local.js +0 -44
- package/dist/dock/location.js +0 -11
- package/dist/dock/push.js +0 -132
- package/dist/dock/timer.js +0 -149
- package/dock/console.ts +0 -23
- package/dock/dialers.ts +0 -85
- package/dock/faculty.ts +0 -148
- package/dock/grants.ts +0 -111
- package/dock/index.ts +0 -133
- package/dock/local.ts +0 -48
- package/dock/push.ts +0 -161
- package/dock/timer.ts +0 -174
- /package/dist/{dock → box}/fcm.js +0 -0
- /package/dist/{dock → box}/webpush.js +0 -0
package/harbor/edge/edge.ts
CHANGED
|
@@ -30,16 +30,15 @@
|
|
|
30
30
|
// those pages are painted with. It also holds what this terrain lends, since
|
|
31
31
|
// a worker stands a box ward like every other terrain: the box's state has
|
|
32
32
|
// one home everywhere, and what differs is which faculties that ward lends.
|
|
33
|
-
import type {
|
|
33
|
+
import type { Stance } from 'nervur';
|
|
34
34
|
import { isSilence } from 'nervur';
|
|
35
|
-
import {
|
|
35
|
+
import { BEINGS } from '../../beings/index.ts';
|
|
36
36
|
import { setup } from '../../beings/setup.ts';
|
|
37
37
|
import { Harbor, Socket as Held } from 'nervur/harbor';
|
|
38
38
|
import { quoRoute } from '../quo.ts';
|
|
39
39
|
import { webRoute, type Folder } from '../../human/web.ts';
|
|
40
|
-
import { DOCK, TIMER, Console, facultyOf, settled, stand } from '../../dock/index.ts';
|
|
41
40
|
import { Alarm, alarmsIn } from './alarm.ts';
|
|
42
|
-
import {
|
|
41
|
+
import { BOX_SEED, GRANTS, Grants, Http, TIMER, WIRE, harborageOver, keyed, lent, settled, standHarbor, type Harborage, type Tree } from '../../box/index.ts';
|
|
43
42
|
import { find, letIn } from '../../beings/join.ts';
|
|
44
43
|
import { apiRoute } from '../../api/route.ts';
|
|
45
44
|
import { mcpRoute, type Routes } from '../../mcp/route.ts';
|
|
@@ -47,14 +46,10 @@ import { McpHttp } from '../../mcp/http.ts';
|
|
|
47
46
|
import { Exchange } from '../../mcp/allow.ts';
|
|
48
47
|
import type { Invitation } from 'nervur';
|
|
49
48
|
import { DurableStorage, danceInStorage, folderInStorage, settingsInStorage, wearInStorage } from './storage.ts';
|
|
50
|
-
import {
|
|
49
|
+
import { type Deployed, type Given, type Settings, type SettingsStore } from './given.ts';
|
|
51
50
|
import { sealKey } from '../seal.ts';
|
|
52
|
-
import type { State, Env
|
|
51
|
+
import type { State, Env } from './platform.d.ts';
|
|
53
52
|
|
|
54
|
-
// The beings a deployed worker carries. The Clock is among them because this
|
|
55
|
-
// terrain lends a timer: a box that lends one and holds no class that can ask
|
|
56
|
-
// for a wake lends a name nobody there could use.
|
|
57
|
-
export const BUILT_IN: Record<string, BeingClass> = { User, Desk, Avatar, Clock };
|
|
58
53
|
const json = (status: number, v: unknown, headers: Record<string, string> = {}) => new Response(JSON.stringify(v), { status, headers: { 'content-type': 'application/json', ...headers } });
|
|
59
54
|
|
|
60
55
|
export class EdgeHarbor extends Harbor {
|
|
@@ -69,9 +64,6 @@ export class EdgeHarbor extends Harbor {
|
|
|
69
64
|
// The model side's dance, one key in the same object: clients, pending
|
|
70
65
|
// requests and codes, all of them living minutes.
|
|
71
66
|
readonly dance: ReturnType<typeof danceInStorage>;
|
|
72
|
-
// The object's storage, unprefixed and unsealed: the alarm is the object's
|
|
73
|
-
// own and is no row.
|
|
74
|
-
readonly #storage: Storage;
|
|
75
67
|
#booted: Promise<void> | undefined;
|
|
76
68
|
// The MCP endpoint, one per instance: a session is a client's connection
|
|
77
69
|
// and belongs to the harbor, not to the request that opened it.
|
|
@@ -84,19 +76,33 @@ export class EdgeHarbor extends Harbor {
|
|
|
84
76
|
static async open(state: State, env: Env, deployed: Deployed = {}): Promise<EdgeHarbor> {
|
|
85
77
|
if (!env.NERVUR_SEED_KEY) throw new Error('no NERVUR_SEED_KEY: a seed is never kept plain on the edge');
|
|
86
78
|
const harbor = new EdgeHarbor(new DurableStorage(state.storage, await sealKey(env.NERVUR_SEED_KEY)), env.NERVUR_ROOT, deployed);
|
|
87
|
-
|
|
79
|
+
// What a worker brings to its own ward, and it is the whole of it. A
|
|
80
|
+
// bearer to an identity in a world is a record in the grants' cells and
|
|
81
|
+
// nothing else, so the grants are the same code here as on a disk. The
|
|
82
|
+
// timer is the same class too, with the one method that knows a terrain
|
|
83
|
+
// replaced: `Alarm` arms the object's own storage alarm.
|
|
84
|
+
//
|
|
85
|
+
// Nothing else is brought, each for its own reason. A worker is at no
|
|
86
|
+
// place and before no screen, so no location and no notifications. Push
|
|
87
|
+
// rings under this world's own web origin, which a worker learns from
|
|
88
|
+
// the request and not at boot. And no process stands behind a worker, so
|
|
89
|
+
// no way in over a socket, no dialers and no agents.
|
|
90
|
+
// Only the object may set its own alarm, so the two calls are handed to
|
|
91
|
+
// the timer in the class body this object brings.
|
|
92
|
+
const alarms = alarmsIn(state.storage);
|
|
93
|
+
class ObjectAlarm extends Alarm {
|
|
94
|
+
constructor(stance: Stance, box?: Harborage) {
|
|
95
|
+
super(stance, box, alarms);
|
|
96
|
+
}
|
|
97
|
+
}
|
|
98
|
+
harbor.faculties = { box: { Grants, Http }, harbor: { edge: { Alarm: ObjectAlarm } } };
|
|
88
99
|
return harbor;
|
|
89
100
|
}
|
|
90
101
|
|
|
91
102
|
constructor(store: DurableStorage, root: string | undefined, deployed: Deployed = {}) {
|
|
92
|
-
|
|
93
|
-
// ward's own front desk and the grants its routes honour. They are named
|
|
94
|
-
// here and not left to the deploy, because `lent` names classes and a
|
|
95
|
-
// name the loader cannot find is a ward that would not boot.
|
|
96
|
-
super(store, async () => ({ ...BUILT_IN, Console, Grants, Alarm, ...deployed.classes }));
|
|
103
|
+
super(store, async () => keyed({ ...BEINGS, classes: deployed.classes ?? {} }));
|
|
97
104
|
this.root = root;
|
|
98
105
|
this.deployed = deployed;
|
|
99
|
-
this.#storage = store.storage;
|
|
100
106
|
// The rows live beside the wards in the same object, under the same
|
|
101
107
|
// prefix, so an exercise standing two harbors in one object gives each
|
|
102
108
|
// its own settings and its own design.
|
|
@@ -112,11 +118,14 @@ export class EdgeHarbor extends Harbor {
|
|
|
112
118
|
return { ...this.deployed, settings: await this.settings.load(), folder: this.folder };
|
|
113
119
|
}
|
|
114
120
|
|
|
115
|
-
//
|
|
116
|
-
// the boot; a harbor that
|
|
117
|
-
//
|
|
118
|
-
//
|
|
119
|
-
|
|
121
|
+
// The classes this terrain brings to its own ward, as a tree. Set before
|
|
122
|
+
// the first request and read at the boot; a harbor that brings none stands
|
|
123
|
+
// no ward of its own and lends nothing. The deploy's own classes join them,
|
|
124
|
+
// so an estate's faculty stands beside the terrain's.
|
|
125
|
+
faculties: Tree | undefined;
|
|
126
|
+
// What this harbor is to its faculties: the box ward's save, which on this
|
|
127
|
+
// terrain is the one place the box keeps its state, and up once booted.
|
|
128
|
+
readonly box = harborageOver(() => this.wards.get(BOX_SEED)?.save() ?? Promise.resolve());
|
|
120
129
|
|
|
121
130
|
// Once per instance, on the first request: the platform may have woken a
|
|
122
131
|
// fresh instance, and every ward boots from storage before anything is
|
|
@@ -131,20 +140,13 @@ export class EdgeHarbor extends Harbor {
|
|
|
131
140
|
// keeps, then the beings who took what the box lends: a box is up when
|
|
132
141
|
// they have landed.
|
|
133
142
|
override async boot(): Promise<void> {
|
|
134
|
-
if (this.
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
// partition like every faculty's: there is no second place on this
|
|
138
|
-
// terrain for the box to keep its state.
|
|
139
|
-
const grants = facultyOf<Grants>(this, GRANTS);
|
|
140
|
-
if (grants) grants.persist = () => this.wards.get(DOCK)?.save() ?? Promise.resolve();
|
|
141
|
-
// The clock here is the object's alarm, and only the object may set its
|
|
142
|
-
// own, so the timer is handed the two calls the same way.
|
|
143
|
-
const timer = facultyOf<Alarm>(this, TIMER);
|
|
144
|
-
if (timer) timer.alarms = alarmsIn(this.#storage);
|
|
143
|
+
if (this.faculties) {
|
|
144
|
+
const tree = { ...this.faculties, classes: this.deployed.classes ?? {} };
|
|
145
|
+
await standHarbor(this, tree, this.box);
|
|
145
146
|
}
|
|
146
147
|
await super.boot();
|
|
147
|
-
await settled();
|
|
148
|
+
await settled(this);
|
|
149
|
+
await this.box.raise();
|
|
148
150
|
}
|
|
149
151
|
|
|
150
152
|
// The alarm went off: no request, no route and nobody asking, which is the
|
|
@@ -156,7 +158,7 @@ export class EdgeHarbor extends Harbor {
|
|
|
156
158
|
// saw it. A box whose alarm fired and whose partition was not written would
|
|
157
159
|
// fire the same minute again on its next wake.
|
|
158
160
|
async rang(): Promise<void> {
|
|
159
|
-
const timer =
|
|
161
|
+
const timer = await lent<Alarm>(this, TIMER);
|
|
160
162
|
if (!timer) return;
|
|
161
163
|
try {
|
|
162
164
|
await timer.rang();
|
|
@@ -169,8 +171,8 @@ export class EdgeHarbor extends Harbor {
|
|
|
169
171
|
// The box's grants, for the routes that honour a bearer. Undefined is a
|
|
170
172
|
// harbor that stands no box ward, which is the exercise and nothing a
|
|
171
173
|
// deploy does.
|
|
172
|
-
|
|
173
|
-
return
|
|
174
|
+
grants(): Promise<Grants | undefined> {
|
|
175
|
+
return lent<Grants>(this, GRANTS);
|
|
174
176
|
}
|
|
175
177
|
|
|
176
178
|
// The web side, built per request because its whole configuration is rows
|
|
@@ -211,7 +213,7 @@ export class EdgeHarbor extends Harbor {
|
|
|
211
213
|
// as a folder with several wards and no mark does. The box's own ward is
|
|
212
214
|
// furniture and never a candidate.
|
|
213
215
|
here(): string {
|
|
214
|
-
const worlds = [...this.wards.keys()].filter((n) => n !==
|
|
216
|
+
const worlds = [...this.wards.keys()].filter((n) => n !== BOX_SEED);
|
|
215
217
|
return worlds.length === 1 ? worlds[0] : '';
|
|
216
218
|
}
|
|
217
219
|
|
|
@@ -231,17 +233,16 @@ export class EdgeHarbor extends Harbor {
|
|
|
231
233
|
// The worlds a client may be let into: every ward here but the box's own.
|
|
232
234
|
#worlds = () =>
|
|
233
235
|
[...this.wards]
|
|
234
|
-
.filter(([ward]) => ward !==
|
|
236
|
+
.filter(([ward]) => ward !== BOX_SEED)
|
|
235
237
|
.map(([ward, h]) => ({ ward, pk: h.pk, user: h.record.user }));
|
|
236
238
|
|
|
237
239
|
// The program side. The same `apiRoute` the daemon mounts, on the grants of
|
|
238
240
|
// this box's own ward: a request with no bearer is the stranger every route
|
|
239
241
|
// knows, and one with a bearer this box granted is an occupant.
|
|
240
|
-
async api(request: Request, path: string
|
|
241
|
-
const grants = this.grants;
|
|
242
|
+
async api(request: Request, path: string): Promise<Response> {
|
|
243
|
+
const grants = await this.grants();
|
|
242
244
|
if (!grants) return json(503, { error: 'this box stands no ward of its own, so it grants nothing' });
|
|
243
|
-
const
|
|
244
|
-
const route = apiRoute({ wards: this.wards, here: () => this.here() }, { grants, resolve: this.#resolve, join: this.#join, web: at.web });
|
|
245
|
+
const route = apiRoute({ wards: this.wards, here: () => this.here() }, { grants, resolve: this.#resolve, join: this.#join });
|
|
245
246
|
return (await route(request, path.slice('/api'.length))) ?? json(404, { error: 'no such route' });
|
|
246
247
|
}
|
|
247
248
|
|
|
@@ -252,9 +253,9 @@ export class EdgeHarbor extends Harbor {
|
|
|
252
253
|
// while one is held and a client that comes back to a woken instance
|
|
253
254
|
// reconnects, which is what a reconnect is for.
|
|
254
255
|
async mcp(request: Request, path: string, base: string): Promise<Response> {
|
|
255
|
-
const grants = this.grants;
|
|
256
|
+
const grants = await this.grants();
|
|
256
257
|
if (!grants) return json(503, { error: 'this box stands no ward of its own, so it grants nothing' });
|
|
257
|
-
const endpoint =
|
|
258
|
+
const endpoint =(this.#mcp ??= new McpHttp(this.#resolve, async () => {
|
|
258
259
|
for (const h of this.wards.values()) await h.save();
|
|
259
260
|
}));
|
|
260
261
|
endpoint.gone = (identity, ward) => grants.revoke(identity, ward); // removal at the ward ends the grant at the box
|
|
@@ -266,7 +267,7 @@ export class EdgeHarbor extends Harbor {
|
|
|
266
267
|
// The dance's discovery under this prefix, for a caller that reaches the
|
|
267
268
|
// harbor at a path rather than at an origin of its own.
|
|
268
269
|
async wellKnown(request: Request, path: string, base: string): Promise<Response> {
|
|
269
|
-
const grants = this.grants;
|
|
270
|
+
const grants = await this.grants();
|
|
270
271
|
if (!grants) return json(404, { error: 'no such route' });
|
|
271
272
|
const route = await mcpRoute(this.dance, await this.routes(base), null, grants);
|
|
272
273
|
return (await route.wellKnown(request, path.slice('/.well-known'.length))) ?? json(404, { error: 'no such route' });
|
|
@@ -275,7 +276,7 @@ export class EdgeHarbor extends Harbor {
|
|
|
275
276
|
// The exchange, for the allow page on the web side: the dance it finishes
|
|
276
277
|
// is the same one the model side keeps.
|
|
277
278
|
async exchange(base: string): Promise<Exchange | undefined> {
|
|
278
|
-
const grants = this.grants;
|
|
279
|
+
const grants = await this.grants();
|
|
279
280
|
if (!grants) return undefined;
|
|
280
281
|
const route = await mcpRoute(this.dance, await this.routes(base), null, grants);
|
|
281
282
|
return new Exchange({ oauth: route.oauth, worlds: this.#worlds, join: this.#join });
|
|
@@ -293,7 +294,7 @@ export class EdgeHarbor extends Harbor {
|
|
|
293
294
|
async fetch(request: Request, path: string, base: string): Promise<Response> {
|
|
294
295
|
await this.ready();
|
|
295
296
|
const out = await this.#route(request, path, base);
|
|
296
|
-
await
|
|
297
|
+
await (await lent<Alarm>(this, TIMER))?.setting;
|
|
297
298
|
return out;
|
|
298
299
|
}
|
|
299
300
|
|
|
@@ -301,7 +302,7 @@ export class EdgeHarbor extends Harbor {
|
|
|
301
302
|
if (path === '/health') return json(200, this.health(), { 'access-control-allow-origin': '*' });
|
|
302
303
|
if (path === '/root') return this.rootAsk(request);
|
|
303
304
|
if (path === '/web' || path.startsWith('/web/')) return this.web(request, path, base);
|
|
304
|
-
if (path === '/api' || path.startsWith('/api/')) return this.api(request, path
|
|
305
|
+
if (path === '/api' || path.startsWith('/api/')) return this.api(request, path);
|
|
305
306
|
if (path === '/mcp' || path.startsWith('/mcp/')) return this.mcp(request, path, base);
|
|
306
307
|
if (path.startsWith('/.well-known/')) return this.wellKnown(request, path, base);
|
|
307
308
|
// The sealed bytes are the same route the daemon mounts; the upgrade is
|
|
@@ -310,7 +311,12 @@ export class EdgeHarbor extends Harbor {
|
|
|
310
311
|
if ((path === '/quo' || path === '/quo/') && request.headers.get('upgrade')?.toLowerCase() === 'websocket') return this.upgrade();
|
|
311
312
|
return (await quoRoute(this)(request, path.slice('/quo'.length))) ?? json(404, { error: 'no such route' });
|
|
312
313
|
}
|
|
313
|
-
|
|
314
|
+
// Every other path is the box's wire: a faculty that claims it answers,
|
|
315
|
+
// as the object's path and never the platform's prefix.
|
|
316
|
+
const wire = await lent<Http>(this, WIRE);
|
|
317
|
+
if (!wire) return json(404, { error: 'no such route' });
|
|
318
|
+
const url = new URL(request.url);
|
|
319
|
+
return wire.handle(new Request(new URL(`${path}${url.search}`, url.origin), request));
|
|
314
320
|
}
|
|
315
321
|
|
|
316
322
|
health() {
|
|
@@ -356,7 +362,7 @@ export class EdgeHarbor extends Harbor {
|
|
|
356
362
|
async rootAsk(request: Request): Promise<Response> {
|
|
357
363
|
const bearer = request.headers.get('authorization')?.replace(/^Bearer\s+/i, '') ?? '';
|
|
358
364
|
if (request.method !== 'POST' || !this.root || bearer.length !== this.root.length || !same(bearer, this.root)) return json(401, { error: 'the root holds the secret' });
|
|
359
|
-
let body: { init?: { ward?: unknown; user?: unknown }; settings?: Partial<Settings>; wear?: Record<string, unknown>; ward?: unknown; method?: unknown; args?: unknown } = {};
|
|
365
|
+
let body: { init?: { ward?: unknown; user?: unknown; class?: unknown }; settings?: Partial<Settings>; wear?: Record<string, unknown>; ward?: unknown; method?: unknown; args?: unknown } = {};
|
|
360
366
|
try {
|
|
361
367
|
body = (await request.json()) as typeof body;
|
|
362
368
|
} catch {
|
|
@@ -368,7 +374,7 @@ export class EdgeHarbor extends Harbor {
|
|
|
368
374
|
const user = typeof body.init.user === 'string' ? body.init.user : 'me';
|
|
369
375
|
if (this.wards.has(name)) return json(409, { error: `ward ${name} already exists here` });
|
|
370
376
|
const hosted = await this.create(name, user);
|
|
371
|
-
await setup(hosted, user);
|
|
377
|
+
await setup(hosted, user, typeof body.init.class === 'string' ? { home: body.init.class } : {});
|
|
372
378
|
return json(200, { name, pk: hosted.pk, user });
|
|
373
379
|
}
|
|
374
380
|
// The settings, merged over what is kept and answered whole, so a root
|
package/harbor/edge/exercise.ts
CHANGED
|
@@ -189,14 +189,14 @@ export async function exercise(request: Request, base: string, path: string, sta
|
|
|
189
189
|
// the same refusal. That the number a deployment runs under is the
|
|
190
190
|
// platform's own is the line below it, which asks the store directly.
|
|
191
191
|
test('[edge] a row past the object s ceiling is refused, the arrival that wrote it fails at the door, and she stands at what is kept', {}, async () => {
|
|
192
|
-
const
|
|
192
|
+
const sealed = await sealKey(env.NERVUR_SEED_KEY ?? '');
|
|
193
193
|
const small = 8_000;
|
|
194
|
-
const h = new EdgeHarbor(new DurableStorage(state.storage,
|
|
194
|
+
const h = new EdgeHarbor(new DurableStorage(state.storage, sealed, `c:${Date.now()}:`, small), undefined, DEPLOYED);
|
|
195
195
|
await h.ready();
|
|
196
196
|
const w = await h.create(`ceiling-${Date.now()}`);
|
|
197
197
|
for (const [k, c] of [
|
|
198
|
-
['P', 'Printer'],
|
|
199
|
-
['A', 'Customer'],
|
|
198
|
+
['P', 'classes/Printer'],
|
|
199
|
+
['A', 'classes/Customer'],
|
|
200
200
|
])
|
|
201
201
|
assert.deepEqual(await w.ask('boot', { key: k, class: c }), { booted: k });
|
|
202
202
|
const printer = w.being('P') as unknown as Printer;
|
package/harbor/edge/given.ts
CHANGED
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
// What a side mounted at the edge is given. `Given` is the one shape: the
|
|
3
3
|
// code a page runs, the design a world wears, the public origins, the apps
|
|
4
4
|
// table and the bell key. On the daemon the same list is what `cli/daemon.ts`
|
|
5
|
-
// reads off a disk and out of
|
|
5
|
+
// reads off a disk and out of the Dock's cells; here it has two provenances,
|
|
6
6
|
// and the split is what the value is, not an accident of this file.
|
|
7
7
|
//
|
|
8
8
|
// `Deployed` is the half the deploy carries: the tab's bundle, the service
|
|
@@ -18,39 +18,9 @@
|
|
|
18
18
|
// rows beside it, one per file, reached through `Folder` like every other
|
|
19
19
|
// terrain's.
|
|
20
20
|
import type { BeingClass } from 'nervur';
|
|
21
|
-
import { GRANTS } from '../../dock/grants.ts';
|
|
22
|
-
import { TIMER } from '../../dock/index.ts';
|
|
23
21
|
import type { Apps, Folder } from '../../human/web.ts';
|
|
24
22
|
import type { Routes } from '../../mcp/route.ts';
|
|
25
23
|
|
|
26
|
-
// What a worker lends the beings of the wards it holds, by name to the class
|
|
27
|
-
// that stands for it. The box's state has one home on every terrain, the box
|
|
28
|
-
// ward's partition, so the edge stands that ward like every other terrain;
|
|
29
|
-
// what differs is this list, and a faculty the platform has no answer for is
|
|
30
|
-
// not lent at all rather than lent and hollow.
|
|
31
|
-
//
|
|
32
|
-
// `grants` and `timer` are lent, and they are the whole of it. A bearer to an
|
|
33
|
-
// identity in a world is a record in her cells and nothing else, so the
|
|
34
|
-
// grants are the same code here as on a disk and the routes that honour a
|
|
35
|
-
// bearer, `api.` and `mcp.`, work the moment she stands. The timer is the
|
|
36
|
-
// same class too, with the one method that knows a terrain replaced: she arms
|
|
37
|
-
// the object's own storage alarm, which is the clock here, so a home on this
|
|
38
|
-
// terrain wakes for its schedules with nobody asking. `Alarm` in
|
|
39
|
-
// `edge/alarm.ts` is that subclass.
|
|
40
|
-
//
|
|
41
|
-
// The three others every box ships are not lent here, each for its own
|
|
42
|
-
// reason. `location` and `notifications` are a place and a screen, and a
|
|
43
|
-
// worker is at neither: it runs wherever the platform put it and no human is
|
|
44
|
-
// looking at it. `push` rings under this world's own web origin, and a worker
|
|
45
|
-
// learns its origin from the request it is answering and not at boot, so a
|
|
46
|
-
// ring signed at the moment the faculty stood would be signed for nowhere.
|
|
47
|
-
//
|
|
48
|
-
// There is no process on the other side of a worker either, so `local`,
|
|
49
|
-
// `dialers` and `agents`, which the daemon adds, name nothing here: no
|
|
50
|
-
// socket a process was seen on, no line this box holds open, and no command
|
|
51
|
-
// to run an agent in.
|
|
52
|
-
export const LENT: Record<string, string> = { [GRANTS]: 'Grants', [TIMER]: 'Alarm' };
|
|
53
|
-
|
|
54
24
|
// The ceiling on one row in this object's storage, key and value together,
|
|
55
25
|
// in bytes. The platform publishes it for a SQLite-backed Durable Object,
|
|
56
26
|
// which is what `wrangler.toml` declares this class to be, and past it the
|
|
@@ -74,7 +44,7 @@ export type Code = { tab: string; worker: string };
|
|
|
74
44
|
// the worker's own origin, no apps, no doorbell.
|
|
75
45
|
export type Settings = {
|
|
76
46
|
// the public faces of the four routes, as the proxy serves them, the same
|
|
77
|
-
// shape
|
|
47
|
+
// shape the Dock's routes cell holds on a disk
|
|
78
48
|
routes?: Routes;
|
|
79
49
|
// the apps this world's links open, or none
|
|
80
50
|
apps?: Apps | null;
|
|
@@ -97,7 +67,7 @@ export type SettingsStore = {
|
|
|
97
67
|
// platform is, and never a deploy.
|
|
98
68
|
export type Deployed = {
|
|
99
69
|
code?: Code;
|
|
100
|
-
//
|
|
70
|
+
// this deployment's own classes, held under `classes/` beside the dock's `beings/`
|
|
101
71
|
classes?: Record<string, BeingClass>;
|
|
102
72
|
};
|
|
103
73
|
|
package/harbor/edge/storage.ts
CHANGED
|
@@ -39,8 +39,8 @@ export function settingsInStorage(storage: Rows, prefix = ''): SettingsStore {
|
|
|
39
39
|
// storage instead. A row is text, because a stylesheet is text and so is a
|
|
40
40
|
// bundle; a file the root has not put there answers nothing and the route
|
|
41
41
|
// declines the path.
|
|
42
|
-
|
|
43
|
-
|
|
42
|
+
const DESIGN_ROW = 'design:';
|
|
43
|
+
const BEINGS_ROW = 'beings';
|
|
44
44
|
|
|
45
45
|
export function folderInStorage(storage: Rows, prefix = ''): Folder {
|
|
46
46
|
const design = (file: string) => `${prefix}${DESIGN_ROW}${file}`;
|
package/harbor/http.ts
CHANGED
|
@@ -100,7 +100,9 @@ export async function listenHttp(harbor: DiskHarbor, host: string, port: number,
|
|
|
100
100
|
{ headers: { 'access-control-allow-origin': '*' } },
|
|
101
101
|
);
|
|
102
102
|
}
|
|
103
|
-
|
|
103
|
+
// the longest prefix first, so a route mounted later under a longer
|
|
104
|
+
// path is still tried before the wire mounted at the root
|
|
105
|
+
for (const [prefix, handler] of [...mounts].sort(([a], [b]) => b.length - a.length)) {
|
|
104
106
|
if (url.pathname === prefix || url.pathname.startsWith(prefix + '/')) {
|
|
105
107
|
const out = await handler(asRequest(req, url), url.pathname.slice(prefix.length));
|
|
106
108
|
if (out) return out;
|
|
@@ -167,14 +169,14 @@ function asRequest(req: import('node:http').IncomingMessage, url: URL): Request
|
|
|
167
169
|
method,
|
|
168
170
|
headers,
|
|
169
171
|
...(bodied ? { body: Readable.toWeb(req) as ReadableStream<Uint8Array>, duplex: 'half' } : {}),
|
|
170
|
-
}
|
|
172
|
+
});
|
|
171
173
|
}
|
|
172
174
|
|
|
173
175
|
// And the one translation out.
|
|
174
176
|
async function writeResponse(out: Response, res: import('node:http').ServerResponse): Promise<void> {
|
|
175
177
|
const headers: Record<string, string | string[]> = {};
|
|
176
178
|
out.headers.forEach((v, k) => {
|
|
177
|
-
headers[k] = k === 'set-cookie' ? [...(Array.isArray(headers[k]) ? (headers[k]
|
|
179
|
+
headers[k] = k === 'set-cookie' ? [...(Array.isArray(headers[k]) ? (headers[k]) : []), v] : v;
|
|
178
180
|
});
|
|
179
181
|
res.writeHead(out.status, headers);
|
|
180
182
|
if (!out.body) return void res.end();
|
package/harbor/sealed.ts
CHANGED
|
@@ -146,38 +146,3 @@ export class Sealed implements Store {
|
|
|
146
146
|
await this.#files.write(`quo/${this.harbor}/reach.json`, JSON.stringify(all) + '\n');
|
|
147
147
|
}
|
|
148
148
|
}
|
|
149
|
-
|
|
150
|
-
// Files and secrets in memory, for a suite and for a box that keeps
|
|
151
|
-
// nothing: the custody rule is proven here once and holds on every
|
|
152
|
-
// platform that hands the two.
|
|
153
|
-
export function inMemory(): { files: Files; secrets: Secrets; disk: Map<string, string> } {
|
|
154
|
-
const disk = new Map<string, string>();
|
|
155
|
-
const dirs = new Set<string>();
|
|
156
|
-
const vault = new Map<string, string>();
|
|
157
|
-
const under = (dir: string) => [...disk.keys()].filter((k) => k.startsWith(`${dir}/`));
|
|
158
|
-
const files: Files = {
|
|
159
|
-
exists: async (p) => disk.has(p) || dirs.has(p),
|
|
160
|
-
read: async (p) => {
|
|
161
|
-
const v = disk.get(p);
|
|
162
|
-
if (v === undefined) throw new Error(`no such file: ${p}`);
|
|
163
|
-
return v;
|
|
164
|
-
},
|
|
165
|
-
write: async (p, text) => void disk.set(p, text),
|
|
166
|
-
remove: async (p) => void disk.delete(p),
|
|
167
|
-
mkdir: async (p) => {
|
|
168
|
-
const parts = p.split('/');
|
|
169
|
-
for (let i = 1; i <= parts.length; i += 1) dirs.add(parts.slice(0, i).join('/'));
|
|
170
|
-
},
|
|
171
|
-
rmdir: async (p) => {
|
|
172
|
-
for (const k of under(p)) disk.delete(k);
|
|
173
|
-
for (const d of [...dirs]) if (d === p || d.startsWith(`${p}/`)) dirs.delete(d);
|
|
174
|
-
},
|
|
175
|
-
list: async (p) => under(p).map((k) => k.slice(p.length + 1)).filter((k) => !k.includes('/')),
|
|
176
|
-
};
|
|
177
|
-
const secrets: Secrets = {
|
|
178
|
-
get: async (i) => vault.get(i),
|
|
179
|
-
set: async (i, v) => void vault.set(i, v),
|
|
180
|
-
remove: async (i) => void vault.delete(i),
|
|
181
|
-
};
|
|
182
|
-
return { files, secrets, disk };
|
|
183
|
-
}
|
package/harbor/tauri.ts
CHANGED
|
@@ -11,7 +11,7 @@
|
|
|
11
11
|
// ships no daemon. It has no ear, since no store rings a desk; a desk is
|
|
12
12
|
// reached while the app is open and drains what it missed when it opens.
|
|
13
13
|
import type { BeingClass, JsonObject } from 'nervur';
|
|
14
|
-
import {
|
|
14
|
+
import { Notifications } from '../box/index.ts';
|
|
15
15
|
import { BrowserHarbor } from './browser.ts';
|
|
16
16
|
import { Sealed, type Files, type Secrets } from './sealed.ts';
|
|
17
17
|
|
|
@@ -24,6 +24,11 @@ export class DeskNotifications extends Notifications {
|
|
|
24
24
|
super(stance);
|
|
25
25
|
this.#core = core;
|
|
26
26
|
}
|
|
27
|
+
// A desk's notification center is its core's, and a class handed a core
|
|
28
|
+
// stands wherever it was handed one.
|
|
29
|
+
static override stands(): boolean {
|
|
30
|
+
return true;
|
|
31
|
+
}
|
|
27
32
|
override async notify(args: JsonObject): Promise<JsonObject> {
|
|
28
33
|
const title = typeof args.title === 'string' ? args.title : '';
|
|
29
34
|
const body = typeof args.body === 'string' ? args.body : '';
|
|
@@ -32,13 +37,6 @@ export class DeskNotifications extends Notifications {
|
|
|
32
37
|
}
|
|
33
38
|
}
|
|
34
39
|
|
|
35
|
-
// What a desk lends: its notification center, under the name every box
|
|
36
|
-
// lends it under. Location is what the platform gives, which on a desk is
|
|
37
|
-
// nothing, so it is not lent rather than lent and dumb. No timer either: a
|
|
38
|
-
// desk holds presences and no home, so no Clock lives on it and a Timer
|
|
39
|
-
// there would arm for nobody. A Timer stands only on a box that hosts a
|
|
40
|
-
// home.
|
|
41
|
-
export const deskLent: Record<string, string> = { [NOTIFICATIONS]: 'Notifications' };
|
|
42
40
|
|
|
43
41
|
// The two seams over the core's commands. Paths are relative to the app's
|
|
44
42
|
// data folder, and the core refuses one that climbs out of it.
|
|
@@ -72,8 +70,13 @@ export async function deskHarbor(core: Core, name = 'nervur', classes: Record<st
|
|
|
72
70
|
}
|
|
73
71
|
}
|
|
74
72
|
const store = await Sealed.open(name, deskFiles(core), deskSecrets(core));
|
|
75
|
-
|
|
76
|
-
|
|
73
|
+
// A desk brings its notification center and nothing else. Location is what
|
|
74
|
+
// the platform gives, which on a desk is nothing, so no class for it is
|
|
75
|
+
// brought rather than one brought and dumb. No timer either: a desk holds
|
|
76
|
+
// presences and no home, so no Clock lives on it and a Timer there would
|
|
77
|
+
// arm for nobody.
|
|
78
|
+
const h = new BrowserHarbor(store, classes);
|
|
79
|
+
await h.stand({ harbor: { DeskNotifications: Center } });
|
|
77
80
|
await h.boot();
|
|
78
81
|
return h;
|
|
79
82
|
}
|
package/human/app.ts
CHANGED
|
@@ -19,12 +19,12 @@ import { domSurface } from './dom.ts';
|
|
|
19
19
|
import { guest } from './guest.ts';
|
|
20
20
|
import { door, held as heldPage } from './door.ts';
|
|
21
21
|
import { USER } from '../beings/avatar.ts';
|
|
22
|
-
import {
|
|
22
|
+
import { BOX, BOX_SEED, NOTIFICATIONS, Notifications, lent, settled } from '../box/index.ts';
|
|
23
23
|
import { fresh, joined, localName, ordered, relations, world, type Joined, type Relation, type World } from './worlds.ts';
|
|
24
24
|
import { giveWorld, takeWorld, type Giving } from './move.ts';
|
|
25
25
|
import { invitationOf, reachOf } from '../beings/link.ts';
|
|
26
26
|
import { doorbell, drained, told, notice as noticeOf, type Bell, type Held } from './wake.ts';
|
|
27
|
-
import type { Token } from '../
|
|
27
|
+
import type { Token } from '../box/index.ts';
|
|
28
28
|
import type { Config } from './tab.ts';
|
|
29
29
|
|
|
30
30
|
// What one world's address answers when an app asks it for JSON, and the
|
|
@@ -58,7 +58,7 @@ export type Dialing = Harbor & { dial(url: string): unknown };
|
|
|
58
58
|
//
|
|
59
59
|
// What the human reads when the device was woken while nobody was looking
|
|
60
60
|
// is composed here from objects that crossed sealed, never from the ring,
|
|
61
|
-
// and shown by asking the
|
|
61
|
+
// and shown by asking the box ward's notifications being through the
|
|
62
62
|
// harbor's own pointer: the same act on every terrain, and no second body.
|
|
63
63
|
export type Ear = () => Promise<Token | null>;
|
|
64
64
|
|
|
@@ -161,7 +161,7 @@ export async function app(o: AppOptions): Promise<Running> {
|
|
|
161
161
|
// by class and a class beats the hidden attribute.
|
|
162
162
|
//
|
|
163
163
|
// Its last entry is never a world: it is this device, the box's own ward
|
|
164
|
-
// shown through
|
|
164
|
+
// shown through its own being, where a human sees what the box lends and
|
|
165
165
|
// leaves a world. A box that lends nothing has no such page.
|
|
166
166
|
const switcher = () => {
|
|
167
167
|
nav.replaceChildren();
|
|
@@ -289,15 +289,15 @@ export async function app(o: AppOptions): Promise<Running> {
|
|
|
289
289
|
side = s;
|
|
290
290
|
};
|
|
291
291
|
|
|
292
|
-
// The box's own ward, as its harbor
|
|
292
|
+
// The box's own ward, as its harbor unpacked it: its being carrying every
|
|
293
293
|
// faculty this box lends. A box that lends nothing stands none, and there
|
|
294
294
|
// is no page of its own to show.
|
|
295
295
|
const deviceWard = async (): Promise<Hosted | null> => {
|
|
296
|
-
const ward = o.harbor.wards.get(
|
|
297
|
-
if (!ward || !ward.being(
|
|
296
|
+
const ward = o.harbor.wards.get(BOX_SEED);
|
|
297
|
+
if (!ward || !ward.being(BOX)) return null;
|
|
298
298
|
return ward;
|
|
299
299
|
};
|
|
300
|
-
const hasDevice = (): boolean => o.harbor.wards.get(
|
|
300
|
+
const hasDevice = (): boolean => o.harbor.wards.get(BOX_SEED)?.being(BOX) !== undefined;
|
|
301
301
|
|
|
302
302
|
// The device shown by itself: the last entry of the switcher, and the
|
|
303
303
|
// whole screen of a device that has joined no world. A fresh install has
|
|
@@ -316,7 +316,7 @@ export async function app(o: AppOptions): Promise<Running> {
|
|
|
316
316
|
const had = relations(ward)[0];
|
|
317
317
|
if (had) return { ward, rel: had };
|
|
318
318
|
const rel = await fresh(ward);
|
|
319
|
-
const inv = await ward.ask('invite', { being:
|
|
319
|
+
const inv = await ward.ask('invite', { being: BOX, id: rel.key });
|
|
320
320
|
if (!inv || typeof inv !== 'object') throw new Error('this device is already held under that name');
|
|
321
321
|
const took = (await ward.ask('knock', { being: rel.key, id: USER, invitation: inv as unknown as JsonObject })) as { taken?: string; error?: string };
|
|
322
322
|
if (took.taken !== USER) throw new Error(took.error ?? 'the shell could not take this device');
|
|
@@ -424,7 +424,7 @@ export async function app(o: AppOptions): Promise<Running> {
|
|
|
424
424
|
// was there a moment ago. A page paints once, so asking once more here
|
|
425
425
|
// is the difference between a device with its faculties on screen and a
|
|
426
426
|
// device that looks empty until the person navigates away and back.
|
|
427
|
-
await settled();
|
|
427
|
+
await settled(o.harbor);
|
|
428
428
|
const now = await rel.avatar.tools();
|
|
429
429
|
if ('asks' in now) got = now;
|
|
430
430
|
await inside(ward, rel, notice, got);
|
|
@@ -518,9 +518,8 @@ export async function app(o: AppOptions): Promise<Running> {
|
|
|
518
518
|
// the shell asks and does not read the answer, since the news is already
|
|
519
519
|
// on the page where the push landed.
|
|
520
520
|
const shown = async (line: { title: string; body: string }) => {
|
|
521
|
-
|
|
522
|
-
|
|
523
|
-
await dock.ask('ask', { being: NOTIFICATIONS, method: 'notify', args: line }).catch(() => {});
|
|
521
|
+
await (await lent<Notifications>(o.harbor, NOTIFICATIONS))?.notify(line).catch(() => {});
|
|
522
|
+
await o.harbor.wards.get(BOX_SEED)?.save();
|
|
524
523
|
};
|
|
525
524
|
|
|
526
525
|
// The device ward before anything is dialed: a harbor announces the wards
|
package/human/door.ts
CHANGED
|
@@ -90,7 +90,7 @@ export const ours: Painter = {
|
|
|
90
90
|
// an error object to a machine, and neither is a sentence.
|
|
91
91
|
export type Landed = { world?: string; host: string; at?: string; ok: boolean };
|
|
92
92
|
|
|
93
|
-
|
|
93
|
+
function landedTree(l: Landed): Node {
|
|
94
94
|
const of: Node[] = [
|
|
95
95
|
{ kind: 'image', src: MARK, alt: '' },
|
|
96
96
|
{ kind: 'text', text: l.ok ? 'That is done' : 'That did not finish', role: 'title' },
|
package/human/html.ts
CHANGED
|
@@ -436,7 +436,7 @@ export function page(m: Model): string {
|
|
|
436
436
|
const a = inGroup.find((x) => x.name === prefix + name);
|
|
437
437
|
return a ? one(kept, prefix)(a) : '';
|
|
438
438
|
},
|
|
439
|
-
answer: (name,
|
|
439
|
+
answer: (name, shown) => (inGroup.some((x) => x.name === prefix + name) ? answer(prefix + name, shown) : ''),
|
|
440
440
|
link: linkedBy(kept), // a carried standing vouches by her own look and never her carrier's
|
|
441
441
|
standing: () => '',
|
|
442
442
|
standings: () => '',
|
package/human/local.ts
CHANGED
|
@@ -11,7 +11,7 @@
|
|
|
11
11
|
// the subject is the owner's `ask` and nothing else: no standing and no
|
|
12
12
|
// relation, since the local ward is the human's and she is on it. The ward
|
|
13
13
|
// names the asker, bounds the wait, judges her answer and saves before the
|
|
14
|
-
// pointer returns, so this file holds
|
|
14
|
+
// pointer returns, so this file holds no id of its own.
|
|
15
15
|
import type { Answer, Blueprint, JsonObject } from 'nervur';
|
|
16
16
|
import type { Subject } from '../beings/side.ts';
|
|
17
17
|
|
package/human/move.ts
CHANGED
|
@@ -35,7 +35,7 @@ const KEY = 'courier';
|
|
|
35
35
|
async function courier(harbor: Harbor): Promise<Courier> {
|
|
36
36
|
const ward = harbor.wards.get(COURIER) ?? (await harbor.create(COURIER, 'me'));
|
|
37
37
|
if (!ward.being(KEY)) {
|
|
38
|
-
const out = (await ward.ask('boot', { key: KEY, class: 'Courier' })) as { error?: string };
|
|
38
|
+
const out = (await ward.ask('boot', { key: KEY, class: 'beings/Courier' })) as { error?: string };
|
|
39
39
|
if (out.error) throw new Error(`no courier on this device: ${out.error}`);
|
|
40
40
|
}
|
|
41
41
|
return ward.being(KEY) as Courier;
|