@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/dist/beings/presence.js
CHANGED
|
@@ -1,5 +1,10 @@
|
|
|
1
1
|
import { Avatar, PUSHER, USER } from './avatar.js';
|
|
2
|
-
import {
|
|
2
|
+
import { LOCATION } from '../box/location.js';
|
|
3
|
+
import { NOTIFICATIONS } from '../box/notifications.js';
|
|
4
|
+
import { PUSH } from '../box/push.js';
|
|
5
|
+
import { TIMER } from '../box/timer.js';
|
|
6
|
+
import { join } from '../box/join.js';
|
|
7
|
+
import { titleOf } from '../box/contract.js';
|
|
3
8
|
export class Presence extends Avatar {
|
|
4
9
|
// Her world's user being sees the box, and nobody else: the way back she
|
|
5
10
|
// handed over at the join is the one occupant who may ask through her.
|
|
@@ -12,10 +17,14 @@ export class Presence extends Avatar {
|
|
|
12
17
|
static skips(id) {
|
|
13
18
|
return id === USER;
|
|
14
19
|
}
|
|
15
|
-
//
|
|
20
|
+
// What a device can do that a world may want: the contracts she asks her
|
|
21
|
+
// box for, each held under the word the contract calls itself.
|
|
22
|
+
static needs = [LOCATION, NOTIFICATIONS, PUSH, TIMER];
|
|
23
|
+
// Which of them the box lent her, by that word, settled after birth.
|
|
16
24
|
lent;
|
|
17
25
|
constructor(stance) {
|
|
18
26
|
super(stance);
|
|
19
|
-
|
|
27
|
+
const C = this.constructor;
|
|
28
|
+
this.lent = Promise.all(C.needs.map(async (c) => [titleOf(c), await join(this, c, titleOf(c))])).then((all) => Object.fromEntries(all));
|
|
20
29
|
}
|
|
21
30
|
}
|
package/dist/beings/setup.d.ts
CHANGED
|
@@ -1,3 +1,6 @@
|
|
|
1
1
|
import type { Hosted } from 'nervur/harbor';
|
|
2
|
-
export
|
|
3
|
-
|
|
2
|
+
export type Setup = {
|
|
3
|
+
front: string;
|
|
4
|
+
home: string;
|
|
5
|
+
};
|
|
6
|
+
export declare function setup(hosted: Hosted, user: string, classes?: Partial<Setup>): Promise<void>;
|
package/dist/beings/setup.js
CHANGED
|
@@ -1,11 +1,12 @@
|
|
|
1
1
|
import { DESK, HOME } from './user.js';
|
|
2
|
+
const SETUP = { front: 'beings/Desk', home: 'beings/User' };
|
|
2
3
|
// The root boots and steps back: it invites an owner on the ward's own pk
|
|
3
|
-
// and the
|
|
4
|
+
// and the home being knocks for it, so from then on placing a being,
|
|
4
5
|
// minting a way in or removing one is an ask on her, shown by her gate to a
|
|
5
6
|
// device that may mint, and the box is not logged into for it. The same
|
|
6
7
|
// standing a user being of another ward takes to own this one; here it is
|
|
7
8
|
// her own.
|
|
8
|
-
|
|
9
|
+
async function setupHome(hosted, user) {
|
|
9
10
|
const inv = (await hosted.ask('invite', { being: hosted.pk, id: HOME }));
|
|
10
11
|
if ('error' in inv)
|
|
11
12
|
throw new Error(`home: ${inv.error}`);
|
|
@@ -13,27 +14,27 @@ export async function setupHome(hosted, user) {
|
|
|
13
14
|
if (placed.taken !== HOME)
|
|
14
15
|
throw new Error(`home: ${placed.error ?? 'she could not take her own door'}`);
|
|
15
16
|
}
|
|
16
|
-
//
|
|
17
|
-
//
|
|
18
|
-
//
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
if (!/^[\w.-]+$/.test(user)
|
|
22
|
-
throw new Error(`a user is a word
|
|
17
|
+
// Everything setup places is placed the same way whatever the classes are:
|
|
18
|
+
// a home key the front already holds is refused by the ward, as any key
|
|
19
|
+
// taken is.
|
|
20
|
+
export async function setup(hosted, user, classes = {}) {
|
|
21
|
+
const { front, home } = { ...SETUP, ...classes };
|
|
22
|
+
if (!/^[\w.-]+$/.test(user))
|
|
23
|
+
throw new Error(`a user is a word: ${user}`);
|
|
23
24
|
const boot = async (args) => {
|
|
24
25
|
const out = (await hosted.ask('boot', args));
|
|
25
26
|
if (out.error)
|
|
26
27
|
throw new Error(`init: ${out.error}`);
|
|
27
28
|
};
|
|
28
|
-
await boot({ key:
|
|
29
|
-
const pub = (await hosted.ask('public', { key:
|
|
29
|
+
await boot({ key: DESK, class: front });
|
|
30
|
+
const pub = (await hosted.ask('public', { key: DESK }));
|
|
30
31
|
if (pub.error)
|
|
31
32
|
throw new Error(`init: ${pub.error}`);
|
|
32
|
-
await boot({ key: user, class:
|
|
33
|
+
await boot({ key: user, class: home });
|
|
33
34
|
const inv = (await hosted.ask('invite', { being: user, id: DESK }));
|
|
34
|
-
// the
|
|
35
|
-
const placed = (await hosted.ask('knock', { being:
|
|
35
|
+
// the front's first hello names her, since a being does not know her own key
|
|
36
|
+
const placed = (await hosted.ask('knock', { being: DESK, id: `user:${user}`, invitation: inv, method: 'hello', args: { name: user } }));
|
|
36
37
|
if (placed.taken !== `user:${user}`)
|
|
37
|
-
throw new Error('init: the
|
|
38
|
+
throw new Error('init: the front could not reach the home being');
|
|
38
39
|
await setupHome(hosted, user);
|
|
39
40
|
}
|
package/dist/beings/user.d.ts
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
|
-
import
|
|
2
|
-
import {
|
|
1
|
+
import { Being } from 'nervur';
|
|
2
|
+
import type { Asker, Blueprint, Invitation, JsonObject, OccupantRecord, Reply, Stance } from 'nervur';
|
|
3
3
|
export declare const DESK = "desk";
|
|
4
4
|
export declare const BELL = "bell";
|
|
5
5
|
export declare const HOME = "home";
|
|
@@ -8,11 +8,12 @@ declare const rootMinted: (occ: OccupantRecord | undefined) => boolean;
|
|
|
8
8
|
declare const mayWake: (occ: OccupantRecord | undefined) => boolean;
|
|
9
9
|
declare const mayMint: (occ: OccupantRecord | undefined) => boolean;
|
|
10
10
|
declare const mayRevoke: (occ: OccupantRecord | undefined) => boolean;
|
|
11
|
-
export declare class User extends
|
|
11
|
+
export declare class User extends Being {
|
|
12
12
|
#private;
|
|
13
13
|
static carries(occ: OccupantRecord | undefined): boolean;
|
|
14
14
|
static hidden(id: string): boolean;
|
|
15
15
|
static shows(id: string, occ: OccupantRecord | undefined): boolean;
|
|
16
|
+
answer(asker: Asker, method?: string, args?: JsonObject): Promise<Reply>;
|
|
16
17
|
static cells: {
|
|
17
18
|
name: string;
|
|
18
19
|
reports: JsonObject[];
|
package/dist/beings/user.js
CHANGED
|
@@ -7,9 +7,13 @@
|
|
|
7
7
|
// client identity the desk established. A device she let mint brings the
|
|
8
8
|
// next one in, so a human adds a device from a device already in and only
|
|
9
9
|
// the first one has to come from the root, on the box.
|
|
10
|
-
import { isInvitation, isSilence, isWord, wordOf } from 'nervur';
|
|
11
|
-
import {
|
|
12
|
-
import {
|
|
10
|
+
import { Being, isInvitation, isSilence, isWord, wordOf } from 'nervur';
|
|
11
|
+
import { CARRIES, carry } from './carry.js';
|
|
12
|
+
import { banner, isToken, sealBanner } from '../box/index.js';
|
|
13
|
+
import { PUSH as PUSHING } from '../box/push.js';
|
|
14
|
+
import { join } from '../box/join.js';
|
|
15
|
+
// Her standing at the box's push faculty, under an id of her own.
|
|
16
|
+
const PUSH = 'push';
|
|
13
17
|
export const DESK = 'desk';
|
|
14
18
|
// The prefix her doorbell standings are filed under, one per device: the
|
|
15
19
|
// way she rings that device, and never a way anything else may ask on.
|
|
@@ -61,12 +65,12 @@ const mayMint = (occ) => isDesk(occ) || (isDevice(occ) && held(occ).mint);
|
|
|
61
65
|
// it has the act out of reach and the root's own removals instead, and that
|
|
62
66
|
// is a choice an org makes rather than one the dock makes for it.
|
|
63
67
|
const mayRevoke = (occ) => isDesk(occ) || (isDevice(occ) && held(occ).revoke);
|
|
64
|
-
// She carries her standings,
|
|
65
|
-
// human allowed to reach them at the exchange: the note says so. A device
|
|
66
|
-
// without the note sees her own asks alone, and a model sees
|
|
68
|
+
// She carries her standings, her org, the calendar, the house, for a device
|
|
69
|
+
// the human allowed to reach them at the exchange: the note says so. A device
|
|
70
|
+
// without the note sees her own asks alone, and a model sees the org only
|
|
67
71
|
// because the human said it may. A device the root minted reaches: the root
|
|
68
72
|
// is the owner of everything she holds and chose to mint it.
|
|
69
|
-
export class User extends
|
|
73
|
+
export class User extends Being {
|
|
70
74
|
static carries(occ) {
|
|
71
75
|
return isDevice(occ) && held(occ).reach;
|
|
72
76
|
}
|
|
@@ -75,20 +79,23 @@ export class User extends Carrier {
|
|
|
75
79
|
// not bound yet, a phone in a tunnel. Nothing delivered spends nothing,
|
|
76
80
|
// so the invitation is tried again at the next push.
|
|
77
81
|
// The bell standing is plumbing and carries nothing: a device that may
|
|
78
|
-
// reach what she holds sees
|
|
82
|
+
// reach what she holds sees the org and the house, and never the envoy that
|
|
79
83
|
// wakes it. Her ways back are plumbing too, and a way back to a presence
|
|
80
84
|
// being carries that device's faculties, `to:phone-location-where`, which
|
|
81
85
|
// is how a model at home learns where the human is.
|
|
82
86
|
static hidden(id) {
|
|
83
|
-
return
|
|
87
|
+
return CARRIES.hidden(id) || id === PUSH || id.startsWith(`${BELL}:`);
|
|
84
88
|
}
|
|
85
89
|
// Her own door is carried to a device that may mint, and to nobody else:
|
|
86
90
|
// `mint` is the word for a device trusted to change who is in her world,
|
|
87
91
|
// and owning the world from that device is that trust said in full. A
|
|
88
|
-
// device that reaches sees
|
|
92
|
+
// device that reaches sees the org and the house, and no `home-boot`.
|
|
89
93
|
static shows(id, occ) {
|
|
90
94
|
return id !== HOME || held(occ).mint;
|
|
91
95
|
}
|
|
96
|
+
answer(asker, method, args = {}) {
|
|
97
|
+
return carry(this, asker, method, args, (a, m, g) => super.answer(a, m, g));
|
|
98
|
+
}
|
|
92
99
|
static cells = { name: '', reports: [], ways: {}, outbox: {} };
|
|
93
100
|
// Her standing at the box's push faculty, taken again at every birth: a
|
|
94
101
|
// standing points at a being and not at a box, and a ward that moved is
|
|
@@ -103,7 +110,7 @@ export class User extends Carrier {
|
|
|
103
110
|
#push;
|
|
104
111
|
constructor(stance) {
|
|
105
112
|
super(stance);
|
|
106
|
-
this.#push = join(this, PUSH);
|
|
113
|
+
this.#push = join(this, PUSHING, PUSH);
|
|
107
114
|
}
|
|
108
115
|
// A world whose own being she is, is a home: a device reaching her holds
|
|
109
116
|
// a standing at home and not a membership, and she says so in her notes,
|
|
@@ -1,6 +1,6 @@
|
|
|
1
|
-
import type { Asker, JsonObject, OccupantRecord } from 'nervur';
|
|
1
|
+
import type { Asker, JsonObject, OccupantRecord, Stance } from 'nervur';
|
|
2
|
+
import type { Harborage } from './box.ts';
|
|
2
3
|
import { Faculty } from './faculty.ts';
|
|
3
|
-
export declare const AGENTS = "agents";
|
|
4
4
|
export type Process = {
|
|
5
5
|
command: string;
|
|
6
6
|
args: string[];
|
|
@@ -25,6 +25,7 @@ export type Side = {
|
|
|
25
25
|
};
|
|
26
26
|
export type Runner = (row: Row) => Promise<Side>;
|
|
27
27
|
declare const root: (_occ: OccupantRecord | undefined, asker: Asker) => boolean;
|
|
28
|
+
export declare const AGENTS: import("nervur").Blueprint;
|
|
28
29
|
export declare class Agents extends Faculty {
|
|
29
30
|
#private;
|
|
30
31
|
static cells: JsonObject;
|
|
@@ -90,10 +91,12 @@ export declare class Agents extends Faculty {
|
|
|
90
91
|
for: typeof root;
|
|
91
92
|
};
|
|
92
93
|
};
|
|
93
|
-
|
|
94
|
+
static fulfils: import("nervur").Blueprint[];
|
|
95
|
+
static stands(): boolean;
|
|
96
|
+
readonly runner: Runner | undefined;
|
|
94
97
|
readonly running: Map<string, Side>;
|
|
98
|
+
constructor(stance: Stance, box?: Harborage, runner?: Runner);
|
|
95
99
|
private get rows();
|
|
96
|
-
start(runner: Runner): Promise<void>;
|
|
97
100
|
keep(args: JsonObject): Promise<JsonObject>;
|
|
98
101
|
drop(args: JsonObject): Promise<JsonObject>;
|
|
99
102
|
list(): JsonObject;
|
|
@@ -1,58 +1,51 @@
|
|
|
1
1
|
// SPDX-License-Identifier: Apache-2.0
|
|
2
|
-
// The box's agents:
|
|
3
|
-
//
|
|
4
|
-
//
|
|
5
|
-
//
|
|
6
|
-
//
|
|
2
|
+
// The box's agents: on an event, run something for an identity, as the timer
|
|
3
|
+
// does on a moment. What she holds is the table of agents in her cells,
|
|
4
|
+
// placed by the root with `keep`. A row names a client identity, the ward
|
|
5
|
+
// she is admitted into, and what runs for her: a process, a command with its
|
|
6
|
+
// args in its folder, or a model at a url.
|
|
7
7
|
//
|
|
8
|
-
//
|
|
9
|
-
//
|
|
10
|
-
//
|
|
11
|
-
//
|
|
12
|
-
// on a daemon she admits each identity into its ward as an avatar, through
|
|
13
|
-
// the box's own way in, and stands the side her row says over that avatar.
|
|
14
|
-
// A push from the user being is the event either way: the agent side runs
|
|
15
|
-
// the command with it and reports what it printed; the runner side turns it
|
|
16
|
-
// into a turn of the model's conversation with her describe as tools. The
|
|
17
|
-
// queue is in the avatar's cells, so it survives a restart; the row is in
|
|
18
|
-
// hers, so the agent comes back with the box.
|
|
19
|
-
//
|
|
20
|
-
// The daemon hands her how to run a row, as it hands the grants faculty how
|
|
21
|
-
// to persist: running an agent is admitting through a socket only a daemon
|
|
22
|
-
// has, and this file is not in the dock's index, since a tab has no process
|
|
23
|
-
// to spawn. A box with no runner keeps the table and runs nobody.
|
|
8
|
+
// How a row runs is the terrain's, since admitting through a socket and
|
|
9
|
+
// spawning a process are things only a daemon has, so it is handed to her
|
|
10
|
+
// at birth in the class body the terrain brings; this body runs nobody and
|
|
11
|
+
// stands nowhere. Every row runs once the box is up.
|
|
24
12
|
import { OWNER } from 'nervur';
|
|
13
|
+
import { contract } from './contract.js';
|
|
25
14
|
import { Faculty } from './faculty.js';
|
|
26
|
-
// The name the dock ward holds her under.
|
|
27
|
-
export const AGENTS = 'agents';
|
|
28
15
|
export const isProcess = (r) => 'command' in r;
|
|
29
|
-
// The root and nobody else, as the clock gates `keep`: an ask gated this way
|
|
30
|
-
// is the device's own and cannot be reached through a door.
|
|
31
16
|
const root = (_occ, asker) => asker.id === OWNER;
|
|
32
17
|
const ROW = {
|
|
33
18
|
type: 'object',
|
|
34
19
|
properties: { id: { type: 'string' }, ward: { type: 'string' }, command: { type: 'string' }, args: { type: 'array', items: { type: 'string' } }, dir: { type: 'string' }, url: { type: 'string' }, model: { type: 'string' }, key: { type: 'string' }, turns: { type: 'number' } },
|
|
35
20
|
required: ['id', 'ward'],
|
|
36
21
|
};
|
|
22
|
+
const ASKS = {
|
|
23
|
+
keep: { description: 'add an agent, or replace the one of that id: an identity, her ward, and what runs for her, a command with args in a folder, or a model at a url', input: ROW, for: root },
|
|
24
|
+
drop: { description: 'stop an agent and forget her row; the avatar in her ward stays until the root removes her', input: { type: 'object', properties: { id: { type: 'string' } }, required: ['id'] }, for: root },
|
|
25
|
+
list: { description: 'every agent she holds', input: { type: 'object' }, for: root },
|
|
26
|
+
};
|
|
27
|
+
// What the box's own code needs from the faculty that runs its agents.
|
|
28
|
+
export const AGENTS = contract('agents', ASKS);
|
|
37
29
|
export class Agents extends Faculty {
|
|
38
30
|
static cells = { watched: {}, minted: 0, agents: {} };
|
|
39
|
-
static asks =
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
}
|
|
31
|
+
static asks = ASKS;
|
|
32
|
+
static fulfils = [AGENTS];
|
|
33
|
+
static stands() {
|
|
34
|
+
return false;
|
|
35
|
+
}
|
|
44
36
|
runner;
|
|
45
37
|
running = new Map();
|
|
38
|
+
constructor(stance, box, runner) {
|
|
39
|
+
super(stance, box);
|
|
40
|
+
this.runner = runner;
|
|
41
|
+
this.box.up(async () => {
|
|
42
|
+
for (const row of Object.values(this.rows))
|
|
43
|
+
await this.#run(row);
|
|
44
|
+
});
|
|
45
|
+
}
|
|
46
46
|
get rows() {
|
|
47
47
|
return this.cells.agents;
|
|
48
48
|
}
|
|
49
|
-
// Every row up, once the daemon has handed the runner. Called after the
|
|
50
|
-
// wards are hosted, since an avatar is admitted into one of them.
|
|
51
|
-
async start(runner) {
|
|
52
|
-
this.runner = runner;
|
|
53
|
-
for (const row of Object.values(this.rows))
|
|
54
|
-
await this.#run(row);
|
|
55
|
-
}
|
|
56
49
|
async #run(row) {
|
|
57
50
|
if (!this.runner)
|
|
58
51
|
return;
|
|
@@ -45,10 +45,10 @@ export async function provider(a, made) {
|
|
|
45
45
|
// The device a token names, or nothing a ring may send. It is checked
|
|
46
46
|
// where a human can read why rather than by Apple two hops later.
|
|
47
47
|
export function device(token) {
|
|
48
|
-
const
|
|
49
|
-
if (typeof
|
|
48
|
+
const named = token.device;
|
|
49
|
+
if (typeof named !== 'string' || !/^[0-9a-fA-F]{8,200}$/.test(named))
|
|
50
50
|
throw new Error('an apns token is a device token');
|
|
51
|
-
return
|
|
51
|
+
return named;
|
|
52
52
|
}
|
|
53
53
|
// One ring, as Apple reads it: the path, the headers and the body. A ring
|
|
54
54
|
// with a banner is an alert Apple must show, and `mutable-content` is what
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
export type Harborage = {
|
|
2
|
+
save(): Promise<void>;
|
|
3
|
+
now(): number;
|
|
4
|
+
up(work: () => Promise<void>): void;
|
|
5
|
+
};
|
|
6
|
+
export declare const ALONE: Harborage;
|
|
7
|
+
export declare function harborageOver(save: () => Promise<void>, now?: () => number): Harborage & {
|
|
8
|
+
raise(): Promise<void>;
|
|
9
|
+
};
|
package/dist/box/box.js
ADDED
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
// The harborage a faculty holds when none was handed: a suite's ward, which
|
|
2
|
+
// keeps nothing, reads the runtime's clock, and does its work at once.
|
|
3
|
+
export const ALONE = { save: () => Promise.resolve(), now: () => Date.now(), up: (work) => void work().catch(() => { }) };
|
|
4
|
+
// A harborage over a ward's save, raised by the terrain once the box is up.
|
|
5
|
+
export function harborageOver(save, now = () => Date.now()) {
|
|
6
|
+
let waiting = [];
|
|
7
|
+
const run = (work) => work().catch(() => { });
|
|
8
|
+
return {
|
|
9
|
+
save,
|
|
10
|
+
now,
|
|
11
|
+
up: (work) => (waiting ? void waiting.push(work) : void run(work)),
|
|
12
|
+
raise: async () => {
|
|
13
|
+
const held = waiting ?? [];
|
|
14
|
+
waiting = null;
|
|
15
|
+
await Promise.all(held.map(run));
|
|
16
|
+
},
|
|
17
|
+
};
|
|
18
|
+
}
|
|
@@ -0,0 +1,6 @@
|
|
|
1
|
+
import type { AskSpec, Blueprint } from 'nervur';
|
|
2
|
+
export type Contract = Blueprint;
|
|
3
|
+
export declare function contract(title: string, asks: Record<string, AskSpec>): Contract;
|
|
4
|
+
export declare const nameOf: (c: Contract) => Promise<string>;
|
|
5
|
+
export declare const titleOf: (c: Contract) => string;
|
|
6
|
+
export declare function covers(held: Blueprint, c: Contract): boolean;
|
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
// SPDX-License-Identifier: Apache-2.0
|
|
2
|
+
// A contract: what a caller needs from a faculty, said as a blueprint and
|
|
3
|
+
// nothing else, so it is written, read and hashed exactly as a describe is.
|
|
4
|
+
// It is known by its digest, and that digest is the name a box lends under.
|
|
5
|
+
// Nothing enforces it, as Quo enforces no blueprint: the caller reads the
|
|
6
|
+
// describe she was handed and decides with `covers`.
|
|
7
|
+
import { canonical, digest } from 'nervur';
|
|
8
|
+
// A contract out of the asks a class declares. Descriptions stay behind,
|
|
9
|
+
// since they are words for a reader and a reworded sentence must not move a
|
|
10
|
+
// name. The title is the contract's own word for itself.
|
|
11
|
+
export function contract(title, asks) {
|
|
12
|
+
return {
|
|
13
|
+
asks: Object.entries(asks).map(([name, spec]) => ({ name, input: spec.input ?? { type: 'object' }, ...(spec.output !== undefined ? { output: spec.output } : {}) })),
|
|
14
|
+
notes: { title },
|
|
15
|
+
};
|
|
16
|
+
}
|
|
17
|
+
// The name a contract is lent under.
|
|
18
|
+
export const nameOf = (c) => digest(c);
|
|
19
|
+
// The word a contract calls itself.
|
|
20
|
+
export const titleOf = (c) => {
|
|
21
|
+
const t = c.notes?.title;
|
|
22
|
+
return typeof t === 'string' ? t : '';
|
|
23
|
+
};
|
|
24
|
+
// Whether a blueprint covers a contract, ask by ask: every ask the contract
|
|
25
|
+
// names stands in it with the same input, and the same output where the
|
|
26
|
+
// contract says one, in the canonical form. It may carry asks of its own.
|
|
27
|
+
export function covers(held, c) {
|
|
28
|
+
const byName = new Map(held.asks.map((a) => [a.name, a]));
|
|
29
|
+
return c.asks.every((want) => {
|
|
30
|
+
const got = byName.get(want.name);
|
|
31
|
+
if (!got || canonical(got.input) !== canonical(want.input))
|
|
32
|
+
return false;
|
|
33
|
+
return want.output === undefined || (got.output !== undefined && canonical(got.output) === canonical(want.output));
|
|
34
|
+
});
|
|
35
|
+
}
|
|
@@ -1,11 +1,12 @@
|
|
|
1
|
-
import type { Asker, JsonObject, OccupantRecord } from 'nervur';
|
|
1
|
+
import type { Asker, JsonObject, OccupantRecord, Stance } from 'nervur';
|
|
2
2
|
import { type Dialer, type Harbor } from 'nervur/harbor';
|
|
3
|
+
import type { Harborage } from './box.ts';
|
|
3
4
|
import { Faculty } from './faculty.ts';
|
|
4
|
-
export declare const DIALERS = "dialers";
|
|
5
5
|
export type Row = {
|
|
6
6
|
url: string;
|
|
7
7
|
};
|
|
8
8
|
declare const root: (_occ: OccupantRecord | undefined, asker: Asker) => boolean;
|
|
9
|
+
export declare const DIALERS: import("nervur").Blueprint;
|
|
9
10
|
export declare class Dialers extends Faculty {
|
|
10
11
|
#private;
|
|
11
12
|
static cells: JsonObject;
|
|
@@ -44,10 +45,12 @@ export declare class Dialers extends Faculty {
|
|
|
44
45
|
for: typeof root;
|
|
45
46
|
};
|
|
46
47
|
};
|
|
47
|
-
|
|
48
|
+
static fulfils: import("nervur").Blueprint[];
|
|
49
|
+
static stands(): boolean;
|
|
50
|
+
readonly harbor: Harbor | undefined;
|
|
48
51
|
readonly open: Map<string, Dialer>;
|
|
52
|
+
constructor(stance: Stance, box?: Harborage, harbor?: Harbor);
|
|
49
53
|
private get rows();
|
|
50
|
-
start(harbor: Harbor): void;
|
|
51
54
|
keep(args: JsonObject): JsonObject;
|
|
52
55
|
drop(args: JsonObject): JsonObject;
|
|
53
56
|
list(): JsonObject;
|
|
@@ -0,0 +1,76 @@
|
|
|
1
|
+
// SPDX-License-Identifier: Apache-2.0
|
|
2
|
+
// The box's lines: a socket held to another box's quo. route, one per row,
|
|
3
|
+
// announcing this harbor's wards on open and reconnecting when a line drops.
|
|
4
|
+
// The rows are her cells, placed by the root with `keep`. The harbor she
|
|
5
|
+
// dials for is the terrain's, handed at birth in the class body it brings;
|
|
6
|
+
// this body dials nothing and stands nowhere. Every row is dialed once the
|
|
7
|
+
// box is up, and a row kept after that is dialed at once.
|
|
8
|
+
import { OWNER } from 'nervur';
|
|
9
|
+
import { dial } from 'nervur/harbor';
|
|
10
|
+
import { contract } from './contract.js';
|
|
11
|
+
import { Faculty } from './faculty.js';
|
|
12
|
+
const root = (_occ, asker) => asker.id === OWNER;
|
|
13
|
+
const URL_ = { type: 'object', properties: { url: { type: 'string' } }, required: ['url'] };
|
|
14
|
+
const ASKS = {
|
|
15
|
+
keep: { description: 'hold a socket to that quo. route, from now and from every boot, until it is dropped', input: URL_, for: root },
|
|
16
|
+
drop: { description: 'close the socket to that route and forget it', input: URL_, for: root },
|
|
17
|
+
list: { description: 'every route she dials, and whether the line is open now', input: { type: 'object' }, for: root },
|
|
18
|
+
};
|
|
19
|
+
// What the box's own code needs from the faculty that holds its lines.
|
|
20
|
+
export const DIALERS = contract('dialers', ASKS);
|
|
21
|
+
export class Dialers extends Faculty {
|
|
22
|
+
static cells = { watched: {}, minted: 0, dialers: {} };
|
|
23
|
+
static asks = ASKS;
|
|
24
|
+
static fulfils = [DIALERS];
|
|
25
|
+
static stands() {
|
|
26
|
+
return false;
|
|
27
|
+
}
|
|
28
|
+
harbor;
|
|
29
|
+
open = new Map();
|
|
30
|
+
#up = false;
|
|
31
|
+
constructor(stance, box, harbor) {
|
|
32
|
+
super(stance, box);
|
|
33
|
+
this.harbor = harbor;
|
|
34
|
+
this.box.up(() => {
|
|
35
|
+
this.#up = true;
|
|
36
|
+
for (const row of Object.values(this.rows))
|
|
37
|
+
this.#dial(row.url);
|
|
38
|
+
return Promise.resolve();
|
|
39
|
+
});
|
|
40
|
+
}
|
|
41
|
+
get rows() {
|
|
42
|
+
return this.cells.dialers;
|
|
43
|
+
}
|
|
44
|
+
#dial(url) {
|
|
45
|
+
if (!this.harbor || !this.#up || this.open.has(url))
|
|
46
|
+
return;
|
|
47
|
+
this.open.set(url, dial(this.harbor, url));
|
|
48
|
+
}
|
|
49
|
+
keep(args) {
|
|
50
|
+
const url = args.url;
|
|
51
|
+
if (typeof url !== 'string' || !/^https?:\/\/\S+$/.test(url))
|
|
52
|
+
return { error: 'url is the quo. route of the harbor dialed, over http or https' };
|
|
53
|
+
const clean = url.replace(/\/$/, '');
|
|
54
|
+
this.rows[clean] = { url: clean };
|
|
55
|
+
this.#dial(clean);
|
|
56
|
+
return { kept: clean };
|
|
57
|
+
}
|
|
58
|
+
drop(args) {
|
|
59
|
+
const url = typeof args.url === 'string' ? args.url.replace(/\/$/, '') : '';
|
|
60
|
+
if (!this.rows[url])
|
|
61
|
+
return { error: 'no such route' };
|
|
62
|
+
this.open.get(url)?.close();
|
|
63
|
+
this.open.delete(url);
|
|
64
|
+
delete this.rows[url];
|
|
65
|
+
return { dropped: url };
|
|
66
|
+
}
|
|
67
|
+
list() {
|
|
68
|
+
return { dialers: Object.keys(this.rows).map((url) => ({ url, open: this.open.get(url)?.socket !== null && this.open.has(url) })) };
|
|
69
|
+
}
|
|
70
|
+
// The device puts the box down: every line closes, the rows stay.
|
|
71
|
+
close() {
|
|
72
|
+
for (const d of this.open.values())
|
|
73
|
+
d.close();
|
|
74
|
+
this.open.clear();
|
|
75
|
+
}
|
|
76
|
+
}
|