@nervur-org/dock 0.3.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/GETTING_STARTED.md +128 -0
- package/LICENSE +202 -0
- package/NOTICE +6 -0
- package/README.md +37 -0
- package/api/quo-api.md +196 -0
- package/api/route.ts +227 -0
- package/beings/GLOSSARY.md +118 -0
- package/beings/WORLDS.md +567 -0
- package/beings/avatar.ts +101 -0
- package/beings/carry.ts +159 -0
- package/beings/clock.ts +552 -0
- package/beings/courier.ts +107 -0
- package/beings/desk.ts +128 -0
- package/beings/doorbell.ts +129 -0
- package/beings/envoy.ts +218 -0
- package/beings/index.ts +16 -0
- package/beings/join.ts +51 -0
- package/beings/link.ts +104 -0
- package/beings/look.ts +139 -0
- package/beings/porter.ts +43 -0
- package/beings/presence.ts +39 -0
- package/beings/quo-app-bells.md +280 -0
- package/beings/quo-dock.md +1942 -0
- package/beings/setup.ts +45 -0
- package/beings/side.ts +60 -0
- package/beings/user.ts +459 -0
- package/beings/way.ts +65 -0
- package/cli/bundles.ts +20 -0
- package/cli/client.ts +73 -0
- package/cli/daemon.ts +581 -0
- package/cli/estate/Caddyfile +33 -0
- package/cli/estate/quo.service +35 -0
- package/cli/estate/systems.quo.plist +44 -0
- package/cli/estate.ts +48 -0
- package/cli/folder.ts +27 -0
- package/cli/oauth.ts +32 -0
- package/cli/pilot.ts +38 -0
- package/cli/quo-dock.md +1942 -0
- package/cli/quo.ts +203 -0
- package/cli/stand.ts +160 -0
- package/dist/api/route.d.ts +21 -0
- package/dist/api/route.js +193 -0
- package/dist/beings/avatar.d.ts +32 -0
- package/dist/beings/avatar.js +100 -0
- package/dist/beings/carry.d.ts +12 -0
- package/dist/beings/carry.js +174 -0
- package/dist/beings/clock.d.ts +161 -0
- package/dist/beings/clock.js +528 -0
- package/dist/beings/courier.d.ts +50 -0
- package/dist/beings/courier.js +96 -0
- package/dist/beings/desk.d.ts +53 -0
- package/dist/beings/desk.js +135 -0
- package/dist/beings/doorbell.d.ts +78 -0
- package/dist/beings/doorbell.js +133 -0
- package/dist/beings/envoy.d.ts +54 -0
- package/dist/beings/envoy.js +211 -0
- package/dist/beings/index.d.ts +14 -0
- package/dist/beings/index.js +16 -0
- package/dist/beings/join.d.ts +15 -0
- package/dist/beings/join.js +42 -0
- package/dist/beings/link.d.ts +14 -0
- package/dist/beings/link.js +91 -0
- package/dist/beings/look.d.ts +30 -0
- package/dist/beings/look.js +104 -0
- package/dist/beings/porter.d.ts +14 -0
- package/dist/beings/porter.js +41 -0
- package/dist/beings/presence.d.ts +8 -0
- package/dist/beings/presence.js +21 -0
- package/dist/beings/setup.d.ts +3 -0
- package/dist/beings/setup.js +39 -0
- package/dist/beings/side.d.ts +32 -0
- package/dist/beings/side.js +37 -0
- package/dist/beings/user.d.ts +315 -0
- package/dist/beings/user.js +462 -0
- package/dist/beings/way.d.ts +19 -0
- package/dist/beings/way.js +28 -0
- package/dist/cli/bundles.d.ts +5 -0
- package/dist/cli/bundles.js +17 -0
- package/dist/cli/client.d.ts +15 -0
- package/dist/cli/client.js +58 -0
- package/dist/cli/daemon.d.ts +50 -0
- package/dist/cli/daemon.js +584 -0
- package/dist/cli/estate/Caddyfile +33 -0
- package/dist/cli/estate/quo.service +35 -0
- package/dist/cli/estate/systems.quo.plist +44 -0
- package/dist/cli/estate.d.ts +5 -0
- package/dist/cli/estate.js +50 -0
- package/dist/cli/folder.d.ts +2 -0
- package/dist/cli/folder.js +25 -0
- package/dist/cli/oauth.d.ts +2 -0
- package/dist/cli/oauth.js +32 -0
- package/dist/cli/pilot.d.ts +5 -0
- package/dist/cli/pilot.js +22 -0
- package/dist/cli/quo.d.ts +2 -0
- package/dist/cli/quo.js +201 -0
- package/dist/cli/stand.d.ts +37 -0
- package/dist/cli/stand.js +110 -0
- package/dist/dock/agents.d.ts +102 -0
- package/dist/dock/agents.js +105 -0
- package/dist/dock/apns.d.ts +27 -0
- package/dist/dock/apns.js +98 -0
- package/dist/dock/console.d.ts +6 -0
- package/dist/dock/console.js +20 -0
- package/dist/dock/dialers.d.ts +56 -0
- package/dist/dock/dialers.js +75 -0
- package/dist/dock/faculty.d.ts +24 -0
- package/dist/dock/faculty.js +142 -0
- package/dist/dock/fcm.d.ts +13 -0
- package/dist/dock/fcm.js +77 -0
- package/dist/dock/grants.d.ts +41 -0
- package/dist/dock/grants.js +99 -0
- package/dist/dock/http2.d.ts +4 -0
- package/dist/dock/http2.js +68 -0
- package/dist/dock/index.d.ts +41 -0
- package/dist/dock/index.js +113 -0
- package/dist/dock/local.d.ts +13 -0
- package/dist/dock/local.js +44 -0
- package/dist/dock/location.d.ts +19 -0
- package/dist/dock/location.js +11 -0
- package/dist/dock/notifications.d.ts +38 -0
- package/dist/dock/notifications.js +60 -0
- package/dist/dock/push.d.ts +79 -0
- package/dist/dock/push.js +132 -0
- package/dist/dock/service.d.ts +4 -0
- package/dist/dock/service.js +107 -0
- package/dist/dock/timer.d.ts +52 -0
- package/dist/dock/timer.js +149 -0
- package/dist/dock/webpush.d.ts +12 -0
- package/dist/dock/webpush.js +144 -0
- package/dist/harbor/browser.d.ts +15 -0
- package/dist/harbor/browser.js +82 -0
- package/dist/harbor/capacitor.d.ts +28 -0
- package/dist/harbor/capacitor.js +218 -0
- package/dist/harbor/disk.d.ts +27 -0
- package/dist/harbor/disk.js +249 -0
- package/dist/harbor/edge/alarm.d.ts +10 -0
- package/dist/harbor/edge/alarm.js +38 -0
- package/dist/harbor/edge/edge.d.ts +54 -0
- package/dist/harbor/edge/edge.js +370 -0
- package/dist/harbor/edge/exercise.d.ts +7 -0
- package/dist/harbor/edge/exercise.js +232 -0
- package/dist/harbor/edge/given.d.ts +26 -0
- package/dist/harbor/edge/given.js +42 -0
- package/dist/harbor/edge/platform.d.ts +39 -0
- package/dist/harbor/edge/storage.d.ts +30 -0
- package/dist/harbor/edge/storage.js +180 -0
- package/dist/harbor/edge/worker.d.ts +17 -0
- package/dist/harbor/edge/worker.js +67 -0
- package/dist/harbor/files.d.ts +15 -0
- package/dist/harbor/files.js +220 -0
- package/dist/harbor/http.d.ts +19 -0
- package/dist/harbor/http.js +177 -0
- package/dist/harbor/idb.d.ts +15 -0
- package/dist/harbor/idb.js +0 -0
- package/dist/harbor/quo.d.ts +10 -0
- package/dist/harbor/quo.js +42 -0
- package/dist/harbor/resolve.d.ts +16 -0
- package/dist/harbor/resolve.js +17 -0
- package/dist/harbor/seal.d.ts +3 -0
- package/dist/harbor/seal.js +25 -0
- package/dist/harbor/sealed.d.ts +35 -0
- package/dist/harbor/sealed.js +162 -0
- package/dist/harbor/tauri.d.ts +16 -0
- package/dist/harbor/tauri.js +59 -0
- package/dist/harbor/ward.d.ts +9 -0
- package/dist/harbor/ward.js +15 -0
- package/dist/human/app.d.ts +32 -0
- package/dist/human/app.js +527 -0
- package/dist/human/apps.d.ts +10 -0
- package/dist/human/apps.js +29 -0
- package/dist/human/dom.d.ts +2 -0
- package/dist/human/dom.js +15 -0
- package/dist/human/door.d.ts +19 -0
- package/dist/human/door.js +92 -0
- package/dist/human/guest.d.ts +3 -0
- package/dist/human/guest.js +33 -0
- package/dist/human/html.d.ts +55 -0
- package/dist/human/html.js +451 -0
- package/dist/human/local.d.ts +7 -0
- package/dist/human/local.js +10 -0
- package/dist/human/mark.d.ts +8 -0
- package/dist/human/mark.js +47 -0
- package/dist/human/move.d.ts +17 -0
- package/dist/human/move.js +90 -0
- package/dist/human/screen.d.ts +19 -0
- package/dist/human/screen.js +119 -0
- package/dist/human/style.d.ts +2 -0
- package/dist/human/style.js +60 -0
- package/dist/human/tab.bundle.txt +4775 -0
- package/dist/human/tab.d.ts +15 -0
- package/dist/human/tab.js +409 -0
- package/dist/human/tree.d.ts +50 -0
- package/dist/human/tree.js +157 -0
- package/dist/human/wake.d.ts +34 -0
- package/dist/human/wake.js +179 -0
- package/dist/human/web.d.ts +33 -0
- package/dist/human/web.js +184 -0
- package/dist/human/worker.bundle.txt +3868 -0
- package/dist/human/worker.d.ts +1 -0
- package/dist/human/worker.js +81 -0
- package/dist/human/worlds.d.ts +24 -0
- package/dist/human/worlds.js +99 -0
- package/dist/mcp/agent.d.ts +10 -0
- package/dist/mcp/agent.js +78 -0
- package/dist/mcp/allow.d.ts +20 -0
- package/dist/mcp/allow.js +88 -0
- package/dist/mcp/http.d.ts +27 -0
- package/dist/mcp/http.js +92 -0
- package/dist/mcp/oauth.d.ts +91 -0
- package/dist/mcp/oauth.js +241 -0
- package/dist/mcp/route.d.ts +15 -0
- package/dist/mcp/route.js +42 -0
- package/dist/mcp/runner.d.ts +45 -0
- package/dist/mcp/runner.js +99 -0
- package/dist/mcp/server.d.ts +11 -0
- package/dist/mcp/server.js +102 -0
- package/dock/agents.ts +121 -0
- package/dock/apns.ts +138 -0
- package/dock/console.ts +23 -0
- package/dock/dialers.ts +85 -0
- package/dock/faculty.ts +148 -0
- package/dock/fcm.ts +103 -0
- package/dock/grants.ts +111 -0
- package/dock/http2.ts +70 -0
- package/dock/index.ts +133 -0
- package/dock/local.ts +48 -0
- package/dock/location.ts +22 -0
- package/dock/notifications.ts +67 -0
- package/dock/push.ts +161 -0
- package/dock/quo-app-bells.md +280 -0
- package/dock/quo-dock.md +1942 -0
- package/dock/service.ts +100 -0
- package/dock/timer.ts +174 -0
- package/dock/webpush.ts +182 -0
- package/harbor/browser.ts +97 -0
- package/harbor/capacitor.ts +223 -0
- package/harbor/disk.ts +237 -0
- package/harbor/edge/alarm.ts +48 -0
- package/harbor/edge/edge.ts +407 -0
- package/harbor/edge/exercise.ts +241 -0
- package/harbor/edge/given.ts +106 -0
- package/harbor/edge/platform.d.ts +39 -0
- package/harbor/edge/storage.ts +179 -0
- package/harbor/edge/text.d.ts +15 -0
- package/harbor/edge/worker.ts +70 -0
- package/harbor/edge/wrangler.toml +30 -0
- package/harbor/files.ts +221 -0
- package/harbor/http.ts +188 -0
- package/harbor/idb.ts +0 -0
- package/harbor/quo-harbor.md +850 -0
- package/harbor/quo.ts +44 -0
- package/harbor/resolve.ts +31 -0
- package/harbor/seal.ts +26 -0
- package/harbor/sealed.ts +183 -0
- package/harbor/tauri.ts +79 -0
- package/harbor/ward.ts +28 -0
- package/human/app.ts +580 -0
- package/human/apps.ts +47 -0
- package/human/dom.ts +21 -0
- package/human/door.ts +113 -0
- package/human/guest.ts +34 -0
- package/human/html.ts +490 -0
- package/human/local.ts +30 -0
- package/human/mark.ts +53 -0
- package/human/move.ts +122 -0
- package/human/quo-human.md +1042 -0
- package/human/screen.ts +136 -0
- package/human/style.ts +63 -0
- package/human/tab.bundle.txt +4775 -0
- package/human/tab.ts +446 -0
- package/human/tree.ts +217 -0
- package/human/wake.ts +191 -0
- package/human/web.ts +259 -0
- package/human/worker.bundle.txt +3868 -0
- package/human/worker.ts +124 -0
- package/human/worlds.ts +127 -0
- package/mcp/agent.ts +94 -0
- package/mcp/allow.ts +131 -0
- package/mcp/http.ts +92 -0
- package/mcp/oauth.ts +270 -0
- package/mcp/quo-mcp.md +343 -0
- package/mcp/route.ts +45 -0
- package/mcp/runner.ts +136 -0
- package/mcp/server.ts +101 -0
- package/package.json +121 -0
package/mcp/runner.ts
ADDED
|
@@ -0,0 +1,136 @@
|
|
|
1
|
+
// SPDX-License-Identifier: Apache-2.0
|
|
2
|
+
// The model side as a runner: an MCP client written inline, for a model that
|
|
3
|
+
// speaks function calling and not MCP. The avatar is the one in server.ts and
|
|
4
|
+
// the mapping is the same envelope; what moved in-process is the host that
|
|
5
|
+
// drives the loop, because a completions API cannot. The runner holds one
|
|
6
|
+
// conversation for as long as it serves. Two things start a turn in it: a
|
|
7
|
+
// line from the human, and a push from the user being, which is the world
|
|
8
|
+
// asking. A turn posts the conversation with her describe as tools, turns
|
|
9
|
+
// each tool call into a named ask on her standing, hands the result back as
|
|
10
|
+
// a tool message, and ends when the model answers with no tool call, or at
|
|
11
|
+
// the ceiling. Nothing here names a vendor: the endpoint is a base URL and a
|
|
12
|
+
// model name, which is LM Studio, OpenRouter, or a scripted fake alike.
|
|
13
|
+
import type { Blueprint, JsonObject } from '@nervur-org/nervur';
|
|
14
|
+
import type { Avatar } from '../beings/avatar.ts';
|
|
15
|
+
import { word, wordText, SILENCE_TEXT, UNREACHED_TEXT, type Serving } from '../beings/side.ts';
|
|
16
|
+
import { presentation } from '../beings/look.ts';
|
|
17
|
+
|
|
18
|
+
// Where the model is. `key` is a bearer for endpoints that want one, read
|
|
19
|
+
// from the device and never from cells. `turns` is the ceiling of model
|
|
20
|
+
// calls in one turn of the conversation; ten by default.
|
|
21
|
+
export type Model = { url: string; model: string; key?: string; turns?: number };
|
|
22
|
+
export const TURNS = 10;
|
|
23
|
+
|
|
24
|
+
// The wire shapes, only as much as the loop reads and writes.
|
|
25
|
+
export type Tool = { type: 'function'; function: { name: string; description?: string; parameters: JsonObject } };
|
|
26
|
+
export type ToolCall = { id: string; type: 'function'; function: { name: string; arguments: string } };
|
|
27
|
+
export type Message =
|
|
28
|
+
| { role: 'system' | 'user'; content: string }
|
|
29
|
+
| { role: 'assistant'; content: string | null; tool_calls?: ToolCall[] }
|
|
30
|
+
| { role: 'tool'; tool_call_id: string; content: string };
|
|
31
|
+
type Completion = { choices: { message: { role: 'assistant'; content: string | null; tool_calls?: ToolCall[] } }[]; error?: unknown };
|
|
32
|
+
|
|
33
|
+
// Her describe, spoken as a tools array. Name, description and input are
|
|
34
|
+
// verbatim, with the one narrowing an endpoint has asked for: an ask that
|
|
35
|
+
// declares no properties is sent with an empty `properties`, because
|
|
36
|
+
// LM Studio refuses a parameters schema without one. Her look, her page and
|
|
37
|
+
// every carried page are presentation, for a screen, and are not functions.
|
|
38
|
+
export function tools(bp: Blueprint): Tool[] {
|
|
39
|
+
const shown = presentation(bp);
|
|
40
|
+
return bp.asks
|
|
41
|
+
.filter((a) => !shown.has(a.name))
|
|
42
|
+
.map((a) => {
|
|
43
|
+
const t: Tool = { type: 'function', function: { name: a.name, parameters: { properties: {}, ...a.input, type: 'object' } } };
|
|
44
|
+
if (a.description !== undefined) t.function.description = a.description;
|
|
45
|
+
return t;
|
|
46
|
+
});
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
// One tool call, one ask, one tool message: the three words cross as the
|
|
50
|
+
// table in quo-mcp.md says, and a tool message is text, so the two words
|
|
51
|
+
// that carry nothing cross as the text the trunk gives them.
|
|
52
|
+
export async function result(avatar: Avatar, call: ToolCall): Promise<string> {
|
|
53
|
+
let args: JsonObject = {};
|
|
54
|
+
try {
|
|
55
|
+
const parsed: unknown = call.function.arguments ? JSON.parse(call.function.arguments) : {};
|
|
56
|
+
if (parsed !== null && typeof parsed === 'object' && !Array.isArray(parsed)) args = parsed as JsonObject;
|
|
57
|
+
} catch {
|
|
58
|
+
return JSON.stringify({ error: 'arguments are not JSON' });
|
|
59
|
+
}
|
|
60
|
+
const w = word(await avatar.call(call.function.name, args));
|
|
61
|
+
if (w.word === 'object' || w.word === 'error') return JSON.stringify(w.value);
|
|
62
|
+
return w.word === 'silence' ? SILENCE_TEXT : w.word === 'quo' ? wordText(w.name) : UNREACHED_TEXT;
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
export type Runner = Serving & {
|
|
66
|
+
// A line from the human; resolves with the model's final text, or null
|
|
67
|
+
// when the turn ended at the ceiling.
|
|
68
|
+
say(text: string): Promise<string | null>;
|
|
69
|
+
// The conversation so far, for whoever renders it.
|
|
70
|
+
readonly messages: readonly Message[];
|
|
71
|
+
};
|
|
72
|
+
|
|
73
|
+
// `hear` gets every final text the model says, whoever started the turn.
|
|
74
|
+
// `after` runs when an ask is done, as in the MCP server: a harbor that must
|
|
75
|
+
// write what the ward changed hooks it.
|
|
76
|
+
export function runnerSide(avatar: Avatar, model: Model, hear: (text: string) => void = () => {}, after: () => Promise<void> = async () => {}): Runner {
|
|
77
|
+
const messages: Message[] = [];
|
|
78
|
+
const ceiling = model.turns ?? TURNS;
|
|
79
|
+
let busy: Promise<unknown> = Promise.resolve();
|
|
80
|
+
|
|
81
|
+
async function complete(t: Tool[]): Promise<Completion['choices'][0]['message']> {
|
|
82
|
+
const headers: Record<string, string> = { 'content-type': 'application/json' };
|
|
83
|
+
if (model.key !== undefined) headers.authorization = `Bearer ${model.key}`;
|
|
84
|
+
const res = await fetch(`${model.url.replace(/\/$/, '')}/chat/completions`, { method: 'POST', headers, body: JSON.stringify({ model: model.model, messages, tools: t }) });
|
|
85
|
+
const body = (await res.json()) as Completion;
|
|
86
|
+
const m = body.choices?.[0]?.message;
|
|
87
|
+
if (!res.ok || !m) throw new Error(`model: ${res.status} ${JSON.stringify(body.error ?? body)}`);
|
|
88
|
+
return m;
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
// One turn: from the last appended message to the model's final text.
|
|
92
|
+
async function turn(): Promise<string | null> {
|
|
93
|
+
const bp = await avatar.tools();
|
|
94
|
+
const t = 'asks' in bp ? tools(bp) : [];
|
|
95
|
+
for (let i = 0; i < ceiling; i++) {
|
|
96
|
+
const m = await complete(t);
|
|
97
|
+
const reply: Message = { role: 'assistant', content: m.content };
|
|
98
|
+
if (m.tool_calls?.length) reply.tool_calls = m.tool_calls;
|
|
99
|
+
messages.push(reply);
|
|
100
|
+
if (!m.tool_calls?.length) {
|
|
101
|
+
const text = m.content ?? '';
|
|
102
|
+
hear(text);
|
|
103
|
+
return text;
|
|
104
|
+
}
|
|
105
|
+
for (const call of m.tool_calls) {
|
|
106
|
+
messages.push({ role: 'tool', tool_call_id: call.id, content: await result(avatar, call) });
|
|
107
|
+
await after();
|
|
108
|
+
}
|
|
109
|
+
}
|
|
110
|
+
return null;
|
|
111
|
+
}
|
|
112
|
+
|
|
113
|
+
// Turns never overlap: one conversation, one train of thought.
|
|
114
|
+
function start(message: Message): Promise<string | null> {
|
|
115
|
+
const next = busy.then(async () => {
|
|
116
|
+
messages.push(message);
|
|
117
|
+
return turn();
|
|
118
|
+
});
|
|
119
|
+
busy = next.catch(() => {});
|
|
120
|
+
return next;
|
|
121
|
+
}
|
|
122
|
+
|
|
123
|
+
const ear = (object: JsonObject) => {
|
|
124
|
+
void start({ role: 'user', content: JSON.stringify(object) }).catch(() => {});
|
|
125
|
+
};
|
|
126
|
+
avatar.ears.add(ear);
|
|
127
|
+
|
|
128
|
+
return {
|
|
129
|
+
messages,
|
|
130
|
+
say: (text) => start({ role: 'user', content: text }),
|
|
131
|
+
close: async () => {
|
|
132
|
+
avatar.ears.delete(ear);
|
|
133
|
+
await busy;
|
|
134
|
+
},
|
|
135
|
+
};
|
|
136
|
+
}
|
package/mcp/server.ts
ADDED
|
@@ -0,0 +1,101 @@
|
|
|
1
|
+
// SPDX-License-Identifier: Apache-2.0
|
|
2
|
+
// The model side as an MCP server: one subject, one transport. The subject
|
|
3
|
+
// is whoever answers the empty ask and a named one: an avatar on her one
|
|
4
|
+
// standing, or a ward's owner asks through the pilot. The mapping is total
|
|
5
|
+
// and needs nothing beyond this envelope. tools/list is her describe, and
|
|
6
|
+
// the empty ask itself is the first tool, so the notes of the describe are
|
|
7
|
+
// readable; tools/call is a named ask, a push is a logging notification,
|
|
8
|
+
// and the three words for "no object" each cross as the table in
|
|
9
|
+
// quo-mcp.md says. The transport is whatever the caller connected: the
|
|
10
|
+
// SDK's in-memory pair in a test, stdio for a local client, HTTP on a route.
|
|
11
|
+
import { Server } from '@modelcontextprotocol/sdk/server/index.js';
|
|
12
|
+
import type { Transport } from '@modelcontextprotocol/sdk/shared/transport.js';
|
|
13
|
+
import { ListToolsRequestSchema, CallToolRequestSchema, type CallToolResult, type Tool } from '@modelcontextprotocol/sdk/types.js';
|
|
14
|
+
import type { Blueprint, JsonObject } from '@nervur-org/nervur';
|
|
15
|
+
import { word, wordText, SILENCE_TEXT, UNREACHED_TEXT, type Serving, type Subject } from '../beings/side.ts';
|
|
16
|
+
import { hintFor, presentation, sanitise, type Look } from '../beings/look.ts';
|
|
17
|
+
import { isSilence, isWord, digest } from '@nervur-org/nervur';
|
|
18
|
+
|
|
19
|
+
export const NAME = 'quo';
|
|
20
|
+
export const VERSION = '0.0.0';
|
|
21
|
+
|
|
22
|
+
export type { Subject } from '../beings/side.ts';
|
|
23
|
+
|
|
24
|
+
// The empty ask as a tool: her describe whole, asks and notes.
|
|
25
|
+
export const DESCRIBE: Tool = { name: 'describe', description: 'the empty ask: her describe, the asks and the notes', inputSchema: { type: 'object' } };
|
|
26
|
+
|
|
27
|
+
// Her describe, spoken as tools, the empty ask first. Name, description and
|
|
28
|
+
// input are verbatim; an output schema crosses when she declared one. Her
|
|
29
|
+
// look, when she has one, is the hints: a title, and the annotations a host
|
|
30
|
+
// reads. Her look, her page and every carried page are presentation, for a
|
|
31
|
+
// screen, and are not tools.
|
|
32
|
+
export function tools(bp: Blueprint, look: Look = {}): Tool[] {
|
|
33
|
+
const shown = presentation(bp);
|
|
34
|
+
const asks = bp.asks
|
|
35
|
+
.filter((a) => !shown.has(a.name))
|
|
36
|
+
.map((a) => {
|
|
37
|
+
const t: Tool = { name: a.name, inputSchema: { ...a.input, type: 'object' } };
|
|
38
|
+
if (a.description !== undefined) t.description = a.description;
|
|
39
|
+
if (a.output !== undefined) t.outputSchema = { ...a.output, type: 'object' };
|
|
40
|
+
const h = hintFor(bp, look, a.name);
|
|
41
|
+
if (h.title !== undefined) t.title = h.title;
|
|
42
|
+
const notes: Record<string, boolean> = {};
|
|
43
|
+
if (h.readOnly !== undefined) notes.readOnlyHint = h.readOnly;
|
|
44
|
+
if (h.destructive !== undefined) notes.destructiveHint = h.destructive;
|
|
45
|
+
if (h.idempotent !== undefined) notes.idempotentHint = h.idempotent;
|
|
46
|
+
if (Object.keys(notes).length) t.annotations = notes;
|
|
47
|
+
return t;
|
|
48
|
+
});
|
|
49
|
+
return [DESCRIBE, ...asks];
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
// `after` runs when a call is done: a harbor that must write what the ward
|
|
53
|
+
// changed hooks it, since a same-ward ask never crosses the harbor.
|
|
54
|
+
export async function mcpSide(avatar: Subject, transport: Transport, after: () => Promise<void> = async () => {}): Promise<Serving> {
|
|
55
|
+
const server = new Server({ name: NAME, version: VERSION }, { capabilities: { tools: { listChanged: true }, logging: {} } });
|
|
56
|
+
|
|
57
|
+
// Her look, asked once per digest of her describe.
|
|
58
|
+
let seen: string | null = null;
|
|
59
|
+
let look: Look = {};
|
|
60
|
+
server.setRequestHandler(ListToolsRequestSchema, async () => {
|
|
61
|
+
const bp = await avatar.tools();
|
|
62
|
+
if ('error' in bp && !('asks' in bp)) return { tools: [] }; // not joined: nothing to show, and nothing to call
|
|
63
|
+
const d = await digest(bp as Blueprint);
|
|
64
|
+
if (d !== seen) {
|
|
65
|
+
seen = d;
|
|
66
|
+
if ((bp as Blueprint).asks.some((a) => a.name === 'look')) {
|
|
67
|
+
const l = await avatar.call('look', {});
|
|
68
|
+
look = isSilence(l) || isWord(l) ? {} : sanitise(l);
|
|
69
|
+
} else look = {};
|
|
70
|
+
}
|
|
71
|
+
return { tools: tools(bp as Blueprint, look) };
|
|
72
|
+
});
|
|
73
|
+
|
|
74
|
+
server.setRequestHandler(CallToolRequestSchema, async (req): Promise<CallToolResult> => {
|
|
75
|
+
const w = word(req.params.name === DESCRIBE.name ? await avatar.tools() : await avatar.call(req.params.name, (req.params.arguments ?? {}) as JsonObject));
|
|
76
|
+
await after();
|
|
77
|
+
if (w.word === 'object') {
|
|
78
|
+
const structured = w.value !== null && typeof w.value === 'object' && !Array.isArray(w.value) ? { structuredContent: w.value } : {};
|
|
79
|
+
return { content: [{ type: 'text', text: JSON.stringify(w.value) }], ...structured };
|
|
80
|
+
}
|
|
81
|
+
if (w.word === 'error') return { content: [{ type: 'text', text: JSON.stringify(w.value) }], structuredContent: w.value, isError: true };
|
|
82
|
+
if (w.word === 'silence') return { content: [{ type: 'text', text: SILENCE_TEXT }], structuredContent: { error: 'silence' }, isError: true };
|
|
83
|
+
if (w.word === 'quo') return { content: [{ type: 'text', text: wordText(w.name) }], structuredContent: { error: w.name }, isError: true };
|
|
84
|
+
return { content: [{ type: 'text', text: UNREACHED_TEXT }], structuredContent: { error: 'unreached' }, isError: true };
|
|
85
|
+
});
|
|
86
|
+
|
|
87
|
+
// A push from the user being is a notification to the client. If the
|
|
88
|
+
// transport is gone, the push is dropped, as the side says it is.
|
|
89
|
+
const ear = (object: JsonObject) => {
|
|
90
|
+
void server.sendLoggingMessage({ level: 'info', logger: NAME, data: object }).catch(() => {});
|
|
91
|
+
};
|
|
92
|
+
avatar.ears.add(ear);
|
|
93
|
+
|
|
94
|
+
await server.connect(transport);
|
|
95
|
+
return {
|
|
96
|
+
close: async () => {
|
|
97
|
+
avatar.ears.delete(ear);
|
|
98
|
+
await server.close();
|
|
99
|
+
},
|
|
100
|
+
};
|
|
101
|
+
}
|
package/package.json
ADDED
|
@@ -0,0 +1,121 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@nervur-org/dock",
|
|
3
|
+
"version": "0.3.0",
|
|
4
|
+
"description": "The dock: what every estate on Quo needs and nobody writes twice. A daemon and the quo command, the front desk, the user being and the avatar, harbors on disk, in a tab and on the edge, the model sides and the screen.",
|
|
5
|
+
"keywords": [
|
|
6
|
+
"quo",
|
|
7
|
+
"estate",
|
|
8
|
+
"harbor",
|
|
9
|
+
"mcp",
|
|
10
|
+
"daemon"
|
|
11
|
+
],
|
|
12
|
+
"author": "Razvan Gherghina",
|
|
13
|
+
"license": "Apache-2.0",
|
|
14
|
+
"homepage": "https://nervur.org",
|
|
15
|
+
"type": "module",
|
|
16
|
+
"engines": {
|
|
17
|
+
"node": ">=22.18"
|
|
18
|
+
},
|
|
19
|
+
"bin": {
|
|
20
|
+
"quo": "./dist/cli/quo.js"
|
|
21
|
+
},
|
|
22
|
+
"exports": {
|
|
23
|
+
".": {
|
|
24
|
+
"types": "./dist/beings/index.d.ts",
|
|
25
|
+
"default": "./dist/beings/index.js"
|
|
26
|
+
},
|
|
27
|
+
"./harbor/disk": {
|
|
28
|
+
"types": "./dist/harbor/disk.d.ts",
|
|
29
|
+
"default": "./dist/harbor/disk.js"
|
|
30
|
+
},
|
|
31
|
+
"./cli": "./dist/cli/quo.js",
|
|
32
|
+
"./stand": {
|
|
33
|
+
"types": "./dist/cli/stand.d.ts",
|
|
34
|
+
"default": "./dist/cli/stand.js"
|
|
35
|
+
},
|
|
36
|
+
"./harbor/browser": {
|
|
37
|
+
"types": "./dist/harbor/browser.d.ts",
|
|
38
|
+
"default": "./dist/harbor/browser.js"
|
|
39
|
+
},
|
|
40
|
+
"./harbor/capacitor": {
|
|
41
|
+
"types": "./dist/harbor/capacitor.d.ts",
|
|
42
|
+
"default": "./dist/harbor/capacitor.js"
|
|
43
|
+
},
|
|
44
|
+
"./harbor/tauri": {
|
|
45
|
+
"types": "./dist/harbor/tauri.d.ts",
|
|
46
|
+
"default": "./dist/harbor/tauri.js"
|
|
47
|
+
},
|
|
48
|
+
"./human/apps": {
|
|
49
|
+
"types": "./dist/human/apps.d.ts",
|
|
50
|
+
"default": "./dist/human/apps.js"
|
|
51
|
+
},
|
|
52
|
+
"./human/html": {
|
|
53
|
+
"types": "./dist/human/html.d.ts",
|
|
54
|
+
"default": "./dist/human/html.js"
|
|
55
|
+
},
|
|
56
|
+
"./human/app": {
|
|
57
|
+
"types": "./dist/human/app.d.ts",
|
|
58
|
+
"default": "./dist/human/app.js"
|
|
59
|
+
},
|
|
60
|
+
"./human/worlds": {
|
|
61
|
+
"types": "./dist/human/worlds.d.ts",
|
|
62
|
+
"default": "./dist/human/worlds.js"
|
|
63
|
+
},
|
|
64
|
+
"./beings/link": {
|
|
65
|
+
"types": "./dist/beings/link.d.ts",
|
|
66
|
+
"default": "./dist/beings/link.js"
|
|
67
|
+
},
|
|
68
|
+
"./human/style": {
|
|
69
|
+
"types": "./dist/human/style.d.ts",
|
|
70
|
+
"default": "./dist/human/style.js"
|
|
71
|
+
},
|
|
72
|
+
"./human/mark": {
|
|
73
|
+
"types": "./dist/human/mark.d.ts",
|
|
74
|
+
"default": "./dist/human/mark.js"
|
|
75
|
+
},
|
|
76
|
+
"./harbor/edge/worker": {
|
|
77
|
+
"types": "./dist/harbor/edge/worker.d.ts",
|
|
78
|
+
"default": "./dist/harbor/edge/worker.js"
|
|
79
|
+
}
|
|
80
|
+
},
|
|
81
|
+
"scripts": {
|
|
82
|
+
"build": "rm -rf dist && tsc -p tsconfig.build.json && node build/bundle.ts && chmod +x dist/cli/quo.js && cp harbor/edge/platform.d.ts dist/harbor/edge/ && cp -R cli/estate dist/cli/ && cp ../../papers/quo-dock.md beings/ && cp ../../papers/quo-dock.md dock/ && cp ../../papers/quo-dock.md cli/ && cp ../../papers/quo-human.md human/ && cp ../../papers/quo-mcp.md mcp/ && cp ../../papers/quo-api.md api/ && cp ../../papers/quo-harbor.md harbor/ && cp ../../papers/quo-app-bells.md beings/ && cp ../../papers/quo-app-bells.md dock/ && cp ../../papers/GLOSSARY.md beings/ && cp ../../papers/WORLDS.md beings/ && cp ../../papers/GETTING_STARTED.md GETTING_STARTED.md",
|
|
83
|
+
"check": "node --test \"test/*.test.ts\"",
|
|
84
|
+
"deep": "node --test \"test/terrain/*.test.ts\"",
|
|
85
|
+
"prepublishOnly": "test \"$QUO_GATED\" = 1 || { echo 'publish from the root, gated once: npm run release:dock' >&2; exit 1; }"
|
|
86
|
+
},
|
|
87
|
+
"dependencies": {
|
|
88
|
+
"@aparajita/capacitor-secure-storage": "^8.0.0",
|
|
89
|
+
"@capacitor/app": "^8.1.1",
|
|
90
|
+
"@capacitor/core": "^8.5.1",
|
|
91
|
+
"@capacitor/filesystem": "^8.1.3",
|
|
92
|
+
"@capacitor/geolocation": "^8.2.2",
|
|
93
|
+
"@capacitor/local-notifications": "^8.3.1",
|
|
94
|
+
"@modelcontextprotocol/sdk": "^1.30.0",
|
|
95
|
+
"@nervur-org/nervur": "^0.3.0",
|
|
96
|
+
"@nervur-org/ui": "^0.3.0",
|
|
97
|
+
"ws": "^8.21.3"
|
|
98
|
+
},
|
|
99
|
+
"devDependencies": {
|
|
100
|
+
"@types/ws": "^8.18.1",
|
|
101
|
+
"esbuild": "^0.28.2",
|
|
102
|
+
"wrangler": "^4.129.0"
|
|
103
|
+
},
|
|
104
|
+
"publishConfig": {
|
|
105
|
+
"access": "public"
|
|
106
|
+
},
|
|
107
|
+
"files": [
|
|
108
|
+
"dist",
|
|
109
|
+
"beings",
|
|
110
|
+
"dock",
|
|
111
|
+
"cli",
|
|
112
|
+
"harbor",
|
|
113
|
+
"mcp",
|
|
114
|
+
"human",
|
|
115
|
+
"api",
|
|
116
|
+
"GETTING_STARTED.md",
|
|
117
|
+
"README.md",
|
|
118
|
+
"LICENSE",
|
|
119
|
+
"NOTICE"
|
|
120
|
+
]
|
|
121
|
+
}
|