@parlor/agent-ui 0.0.0-stage → 0.2.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/LICENSE +21 -0
- package/README.md +10 -2
- package/dist/client/agent-ui-client.d.ts +60 -0
- package/dist/client/agent-ui-client.d.ts.map +1 -0
- package/dist/client/agent-ui-client.js +407 -0
- package/dist/client/agent-ui-client.js.map +1 -0
- package/dist/client/confirmations.d.ts +34 -0
- package/dist/client/confirmations.d.ts.map +1 -0
- package/dist/client/confirmations.js +95 -0
- package/dist/client/confirmations.js.map +1 -0
- package/dist/client/environment.d.ts +37 -0
- package/dist/client/environment.d.ts.map +1 -0
- package/dist/client/environment.js +79 -0
- package/dist/client/environment.js.map +1 -0
- package/dist/client/highlight.d.ts +12 -0
- package/dist/client/highlight.d.ts.map +1 -0
- package/dist/client/highlight.js +98 -0
- package/dist/client/highlight.js.map +1 -0
- package/dist/client/index.d.ts +7 -0
- package/dist/client/index.d.ts.map +1 -0
- package/dist/client/index.js +7 -0
- package/dist/client/index.js.map +1 -0
- package/dist/client/json-equal.d.ts +2 -0
- package/dist/client/json-equal.d.ts.map +1 -0
- package/dist/client/json-equal.js +20 -0
- package/dist/client/json-equal.js.map +1 -0
- package/dist/client/registry.d.ts +54 -0
- package/dist/client/registry.d.ts.map +1 -0
- package/dist/client/registry.js +280 -0
- package/dist/client/registry.js.map +1 -0
- package/dist/client/screen.d.ts +20 -0
- package/dist/client/screen.d.ts.map +1 -0
- package/dist/client/screen.js +196 -0
- package/dist/client/screen.js.map +1 -0
- package/dist/client/write.d.ts +13 -0
- package/dist/client/write.d.ts.map +1 -0
- package/dist/client/write.js +101 -0
- package/dist/client/write.js.map +1 -0
- package/dist/index.d.ts +5 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +5 -0
- package/dist/index.js.map +1 -0
- package/dist/manifest.d.ts +44 -0
- package/dist/manifest.d.ts.map +1 -0
- package/dist/manifest.js +23 -0
- package/dist/manifest.js.map +1 -0
- package/dist/pages.d.ts +11 -0
- package/dist/pages.d.ts.map +1 -0
- package/dist/pages.js +42 -0
- package/dist/pages.js.map +1 -0
- package/dist/protocol.d.ts +206 -0
- package/dist/protocol.d.ts.map +1 -0
- package/dist/protocol.js +152 -0
- package/dist/protocol.js.map +1 -0
- package/dist/server/agent-ui.d.ts +36 -0
- package/dist/server/agent-ui.d.ts.map +1 -0
- package/dist/server/agent-ui.js +89 -0
- package/dist/server/agent-ui.js.map +1 -0
- package/dist/server/audience.d.ts +9 -0
- package/dist/server/audience.d.ts.map +1 -0
- package/dist/server/audience.js +15 -0
- package/dist/server/audience.js.map +1 -0
- package/dist/server/broker.d.ts +29 -0
- package/dist/server/broker.d.ts.map +1 -0
- package/dist/server/broker.js +152 -0
- package/dist/server/broker.js.map +1 -0
- package/dist/server/confirmations.d.ts +40 -0
- package/dist/server/confirmations.d.ts.map +1 -0
- package/dist/server/confirmations.js +230 -0
- package/dist/server/confirmations.js.map +1 -0
- package/dist/server/directory.d.ts +41 -0
- package/dist/server/directory.d.ts.map +1 -0
- package/dist/server/directory.js +193 -0
- package/dist/server/directory.js.map +1 -0
- package/dist/server/index.d.ts +10 -0
- package/dist/server/index.d.ts.map +1 -0
- package/dist/server/index.js +10 -0
- package/dist/server/index.js.map +1 -0
- package/dist/server/manifest-source.d.ts +10 -0
- package/dist/server/manifest-source.d.ts.map +1 -0
- package/dist/server/manifest-source.js +36 -0
- package/dist/server/manifest-source.js.map +1 -0
- package/dist/server/routes.d.ts +11 -0
- package/dist/server/routes.d.ts.map +1 -0
- package/dist/server/routes.js +38 -0
- package/dist/server/routes.js.map +1 -0
- package/dist/server/schemas.d.ts +69 -0
- package/dist/server/schemas.d.ts.map +1 -0
- package/dist/server/schemas.js +78 -0
- package/dist/server/schemas.js.map +1 -0
- package/dist/server/tools.d.ts +49 -0
- package/dist/server/tools.d.ts.map +1 -0
- package/dist/server/tools.js +312 -0
- package/dist/server/tools.js.map +1 -0
- package/dist/view.d.ts +54 -0
- package/dist/view.d.ts.map +1 -0
- package/dist/view.js +15 -0
- package/dist/view.js.map +1 -0
- package/dist/vite/index.d.ts +3 -0
- package/dist/vite/index.d.ts.map +1 -0
- package/dist/vite/index.js +3 -0
- package/dist/vite/index.js.map +1 -0
- package/dist/vite/manifest.d.ts +5 -0
- package/dist/vite/manifest.d.ts.map +1 -0
- package/dist/vite/manifest.js +107 -0
- package/dist/vite/manifest.js.map +1 -0
- package/dist/vite/plugin.d.ts +16 -0
- package/dist/vite/plugin.d.ts.map +1 -0
- package/dist/vite/plugin.js +192 -0
- package/dist/vite/plugin.js.map +1 -0
- package/package.json +65 -3
|
@@ -0,0 +1,230 @@
|
|
|
1
|
+
/** The 60 s the user gets, less 1 s so a client on MCP's default 60 s timeout hears our code. */
|
|
2
|
+
export const DEFAULT_CONFIRM_TIMEOUT_MS = 59_000;
|
|
3
|
+
export const ORPHAN_GRACE_MS = 5_000;
|
|
4
|
+
export const MAX_PENDING_PER_SCOPE = 8;
|
|
5
|
+
class Pending {
|
|
6
|
+
id;
|
|
7
|
+
request;
|
|
8
|
+
deadline;
|
|
9
|
+
finish;
|
|
10
|
+
cleanup;
|
|
11
|
+
audience = new Set();
|
|
12
|
+
orphanTimer;
|
|
13
|
+
#deadlineTimer;
|
|
14
|
+
#settled = false;
|
|
15
|
+
constructor(id, request, deadline, finish, cleanup) {
|
|
16
|
+
this.id = id;
|
|
17
|
+
this.request = request;
|
|
18
|
+
this.deadline = deadline;
|
|
19
|
+
this.finish = finish;
|
|
20
|
+
this.cleanup = cleanup;
|
|
21
|
+
}
|
|
22
|
+
armDeadline(timeoutMs) {
|
|
23
|
+
this.#deadlineTimer = setTimeout(() => this.settle("timeout"), timeoutMs);
|
|
24
|
+
}
|
|
25
|
+
settle(outcome) {
|
|
26
|
+
if (this.#settled)
|
|
27
|
+
return;
|
|
28
|
+
this.#settled = true;
|
|
29
|
+
clearTimeout(this.#deadlineTimer);
|
|
30
|
+
clearTimeout(this.orphanTimer);
|
|
31
|
+
this.cleanup();
|
|
32
|
+
this.finish(outcome);
|
|
33
|
+
}
|
|
34
|
+
}
|
|
35
|
+
const EMPTY_LIST = "[]";
|
|
36
|
+
function jsonOf(value) {
|
|
37
|
+
const seen = new WeakSet();
|
|
38
|
+
return JSON.stringify(value, (_key, item) => {
|
|
39
|
+
if (typeof item === "bigint")
|
|
40
|
+
return item.toString();
|
|
41
|
+
if (typeof item === "object" && item !== null) {
|
|
42
|
+
if (seen.has(item))
|
|
43
|
+
return "[circular]";
|
|
44
|
+
seen.add(item);
|
|
45
|
+
}
|
|
46
|
+
return item;
|
|
47
|
+
});
|
|
48
|
+
}
|
|
49
|
+
export class ConfirmationBoard {
|
|
50
|
+
#directory;
|
|
51
|
+
#timeoutMs;
|
|
52
|
+
#graceMs;
|
|
53
|
+
#maxPerScope;
|
|
54
|
+
#newId;
|
|
55
|
+
#pending = new Map();
|
|
56
|
+
#byScope = new Map();
|
|
57
|
+
#sent = new Map();
|
|
58
|
+
#forgetting = new Map();
|
|
59
|
+
#closed = false;
|
|
60
|
+
constructor(options) {
|
|
61
|
+
this.#directory = options.directory;
|
|
62
|
+
this.#timeoutMs = options.timeoutMs;
|
|
63
|
+
this.#graceMs = options.orphanGraceMs ?? ORPHAN_GRACE_MS;
|
|
64
|
+
this.#maxPerScope = options.maxPendingPerScope ?? MAX_PENDING_PER_SCOPE;
|
|
65
|
+
let ids = 0;
|
|
66
|
+
this.#newId = options.newId ?? (() => `${Date.now().toString(36)}-${++ids}`);
|
|
67
|
+
options.directory.subscribe(this.#changed);
|
|
68
|
+
}
|
|
69
|
+
ask(request) {
|
|
70
|
+
const { scope, signal } = request;
|
|
71
|
+
if (this.#closed)
|
|
72
|
+
return Promise.resolve("shutdown");
|
|
73
|
+
if (signal?.aborted)
|
|
74
|
+
return Promise.resolve("cancelled");
|
|
75
|
+
if ((this.#byScope.get(scope)?.size ?? 0) >= this.#maxPerScope) {
|
|
76
|
+
return Promise.resolve("unavailable");
|
|
77
|
+
}
|
|
78
|
+
const audience = this.#selected(request);
|
|
79
|
+
if (audience.length === 0)
|
|
80
|
+
return Promise.resolve("unavailable");
|
|
81
|
+
return new Promise((resolve) => {
|
|
82
|
+
const onAbort = () => pending.settle("cancelled");
|
|
83
|
+
const pending = new Pending(this.#newId(), request, Date.now() + this.#timeoutMs, resolve, () => {
|
|
84
|
+
signal?.removeEventListener("abort", onAbort);
|
|
85
|
+
this.#pending.delete(pending.id);
|
|
86
|
+
const peers = this.#byScope.get(scope);
|
|
87
|
+
peers?.delete(pending);
|
|
88
|
+
if (peers?.size === 0)
|
|
89
|
+
this.#byScope.delete(scope);
|
|
90
|
+
this.#refresh(scope);
|
|
91
|
+
});
|
|
92
|
+
for (const id of audience)
|
|
93
|
+
pending.audience.add(id);
|
|
94
|
+
this.#pending.set(pending.id, pending);
|
|
95
|
+
let peers = this.#byScope.get(scope);
|
|
96
|
+
if (!peers)
|
|
97
|
+
this.#byScope.set(scope, (peers = new Set()));
|
|
98
|
+
peers.add(pending);
|
|
99
|
+
pending.armDeadline(this.#timeoutMs);
|
|
100
|
+
signal?.addEventListener("abort", onAbort, { once: true });
|
|
101
|
+
this.#refresh(scope);
|
|
102
|
+
});
|
|
103
|
+
}
|
|
104
|
+
answer(clientId, scope, answer) {
|
|
105
|
+
const pending = this.#pending.get(answer.id);
|
|
106
|
+
if (!pending || pending.request.scope !== scope || !pending.audience.has(clientId)) {
|
|
107
|
+
return false;
|
|
108
|
+
}
|
|
109
|
+
pending.settle(answer.approve ? "approved" : "declined");
|
|
110
|
+
return true;
|
|
111
|
+
}
|
|
112
|
+
close() {
|
|
113
|
+
this.#closed = true;
|
|
114
|
+
for (const pending of [...this.#pending.values()])
|
|
115
|
+
pending.settle("shutdown");
|
|
116
|
+
for (const timer of this.#forgetting.values())
|
|
117
|
+
clearTimeout(timer);
|
|
118
|
+
this.#forgetting.clear();
|
|
119
|
+
}
|
|
120
|
+
#changed = (scope, clientId, change) => {
|
|
121
|
+
const peers = this.#byScope.get(scope);
|
|
122
|
+
if (change === "left") {
|
|
123
|
+
for (const pending of peers ?? [])
|
|
124
|
+
pending.audience.delete(clientId);
|
|
125
|
+
this.#forgetLater(clientId);
|
|
126
|
+
}
|
|
127
|
+
if (change === "joined") {
|
|
128
|
+
this.#stopForgetting(clientId);
|
|
129
|
+
if (this.#sent.has(clientId)) {
|
|
130
|
+
this.#sent.set(clientId, { scope, signature: "stale" });
|
|
131
|
+
}
|
|
132
|
+
}
|
|
133
|
+
this.#refresh(scope);
|
|
134
|
+
};
|
|
135
|
+
#forgetLater(clientId) {
|
|
136
|
+
this.#stopForgetting(clientId);
|
|
137
|
+
if (!this.#sent.has(clientId))
|
|
138
|
+
return;
|
|
139
|
+
const timer = setTimeout(() => {
|
|
140
|
+
this.#forgetting.delete(clientId);
|
|
141
|
+
this.#sent.delete(clientId);
|
|
142
|
+
}, this.#timeoutMs);
|
|
143
|
+
timer.unref();
|
|
144
|
+
this.#forgetting.set(clientId, timer);
|
|
145
|
+
}
|
|
146
|
+
#stopForgetting(clientId) {
|
|
147
|
+
clearTimeout(this.#forgetting.get(clientId));
|
|
148
|
+
this.#forgetting.delete(clientId);
|
|
149
|
+
}
|
|
150
|
+
#selected(request) {
|
|
151
|
+
if (request.thread)
|
|
152
|
+
return this.#watchers(request, request.thread);
|
|
153
|
+
const selection = this.#directory.select(request.scope, request.clientId);
|
|
154
|
+
if (!selection)
|
|
155
|
+
return [];
|
|
156
|
+
return [selection.target.client, ...selection.tied]
|
|
157
|
+
.filter((client) => client.canConfirm)
|
|
158
|
+
.map((client) => client.id);
|
|
159
|
+
}
|
|
160
|
+
#watchers(request, thread) {
|
|
161
|
+
return this.#directory
|
|
162
|
+
.targets(request.scope)
|
|
163
|
+
.map(({ client }) => client)
|
|
164
|
+
.filter((client) => client.canConfirm &&
|
|
165
|
+
(client.id === request.clientId || client.watching.includes(thread.threadId)))
|
|
166
|
+
.map((client) => client.id);
|
|
167
|
+
}
|
|
168
|
+
#canShow(clientId) {
|
|
169
|
+
return this.#directory.target(clientId)?.client.canConfirm ?? false;
|
|
170
|
+
}
|
|
171
|
+
#refresh(scope) {
|
|
172
|
+
const peers = this.#byScope.get(scope);
|
|
173
|
+
if (!peers && ![...this.#sent.values()].some((sent) => sent.scope === scope))
|
|
174
|
+
return;
|
|
175
|
+
for (const pending of peers ?? []) {
|
|
176
|
+
const selected = this.#selected(pending.request);
|
|
177
|
+
if (pending.request.thread)
|
|
178
|
+
pending.audience.clear();
|
|
179
|
+
for (const id of selected)
|
|
180
|
+
pending.audience.add(id);
|
|
181
|
+
}
|
|
182
|
+
for (const target of this.#directory.targets(scope)) {
|
|
183
|
+
this.#deliver(target, [...(peers ?? [])]);
|
|
184
|
+
}
|
|
185
|
+
for (const pending of peers ?? [])
|
|
186
|
+
this.#watchOrphan(pending);
|
|
187
|
+
}
|
|
188
|
+
#deliver(target, peers) {
|
|
189
|
+
const { id, scope, canConfirm } = target.client;
|
|
190
|
+
const mine = canConfirm ? peers.filter((pending) => pending.audience.has(id)) : [];
|
|
191
|
+
const now = Date.now();
|
|
192
|
+
const prompts = mine.map((pending) => this.#prompt(pending, now));
|
|
193
|
+
const signature = prompts.length === 0
|
|
194
|
+
? EMPTY_LIST
|
|
195
|
+
: jsonOf(prompts.map(({ expiresInMs: _expires, ...rest }) => rest));
|
|
196
|
+
const previous = this.#sent.get(id)?.signature;
|
|
197
|
+
if (previous === signature || (previous === undefined && prompts.length === 0))
|
|
198
|
+
return;
|
|
199
|
+
const frame = { type: "confirmations", items: prompts };
|
|
200
|
+
if (!target.send(jsonOf(frame)))
|
|
201
|
+
return;
|
|
202
|
+
if (prompts.length === 0)
|
|
203
|
+
this.#sent.delete(id);
|
|
204
|
+
else
|
|
205
|
+
this.#sent.set(id, { scope, signature });
|
|
206
|
+
}
|
|
207
|
+
#prompt(pending, now) {
|
|
208
|
+
const { id, request, deadline } = pending;
|
|
209
|
+
return {
|
|
210
|
+
id,
|
|
211
|
+
action: request.action,
|
|
212
|
+
description: request.description,
|
|
213
|
+
input: request.input ?? null,
|
|
214
|
+
origin: request.origin,
|
|
215
|
+
expiresInMs: Math.max(0, deadline - now),
|
|
216
|
+
...(request.thread ? { threadId: request.thread.threadId } : {}),
|
|
217
|
+
};
|
|
218
|
+
}
|
|
219
|
+
#watchOrphan(pending) {
|
|
220
|
+
const shown = [...pending.audience].some((id) => this.#canShow(id));
|
|
221
|
+
if (shown) {
|
|
222
|
+
clearTimeout(pending.orphanTimer);
|
|
223
|
+
pending.orphanTimer = undefined;
|
|
224
|
+
}
|
|
225
|
+
else if (pending.orphanTimer === undefined) {
|
|
226
|
+
pending.orphanTimer = setTimeout(() => pending.settle("unavailable"), this.#graceMs);
|
|
227
|
+
}
|
|
228
|
+
}
|
|
229
|
+
}
|
|
230
|
+
//# sourceMappingURL=confirmations.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"confirmations.js","sourceRoot":"","sources":["../../src/server/confirmations.ts"],"names":[],"mappings":"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","sourcesContent":["import type {\n AgentOrigin,\n ConfirmationAnswer,\n ConfirmationPrompt,\n ConfirmationsFrame,\n} from \"../protocol.js\";\nimport type { ClientDirectory, DirectoryListener } from \"./directory.js\";\n\n/** The 60 s the user gets, less 1 s so a client on MCP's default 60 s timeout hears our code. */\nexport const DEFAULT_CONFIRM_TIMEOUT_MS = 59_000;\n\nexport const ORPHAN_GRACE_MS = 5_000;\n\nexport const MAX_PENDING_PER_SCOPE = 8;\n\nexport interface ConfirmationRequest {\n readonly scope: string;\n readonly clientId?: string | undefined;\n /**\n * Set when an inline agent asks inside a chat turn: the audience is then every client\n * watching the thread plus `clientId`, not the presence selection.\n */\n readonly thread?: ConfirmationThread | undefined;\n readonly action: string;\n readonly description: string;\n readonly input: unknown;\n readonly origin: AgentOrigin;\n readonly signal?: AbortSignal | undefined;\n}\n\nexport interface ConfirmationThread {\n readonly threadId: string;\n readonly turnId: string;\n}\n\nexport type ConfirmationOutcome =\n | \"approved\"\n | \"declined\"\n | \"timeout\"\n | \"unavailable\"\n | \"cancelled\"\n | \"shutdown\";\n\nexport interface ConfirmationBoardOptions {\n readonly directory: ClientDirectory;\n readonly timeoutMs: number;\n readonly orphanGraceMs?: number | undefined;\n readonly maxPendingPerScope?: number | undefined;\n readonly newId?: (() => string) | undefined;\n}\n\ntype Timer = ReturnType<typeof setTimeout>;\n\nclass Pending {\n readonly audience = new Set<string>();\n orphanTimer: Timer | undefined;\n #deadlineTimer: Timer | undefined;\n #settled = false;\n\n constructor(\n readonly id: string,\n readonly request: ConfirmationRequest,\n readonly deadline: number,\n private readonly finish: (outcome: ConfirmationOutcome) => void,\n private readonly cleanup: () => void,\n ) {}\n\n armDeadline(timeoutMs: number): void {\n this.#deadlineTimer = setTimeout(() => this.settle(\"timeout\"), timeoutMs);\n }\n\n settle(outcome: ConfirmationOutcome): void {\n if (this.#settled) return;\n this.#settled = true;\n clearTimeout(this.#deadlineTimer);\n clearTimeout(this.orphanTimer);\n this.cleanup();\n this.finish(outcome);\n }\n}\n\nconst EMPTY_LIST = \"[]\";\n\nfunction jsonOf(value: unknown): string {\n const seen = new WeakSet<object>();\n return JSON.stringify(value, (_key, item: unknown) => {\n if (typeof item === \"bigint\") return item.toString();\n if (typeof item === \"object\" && item !== null) {\n if (seen.has(item)) return \"[circular]\";\n seen.add(item);\n }\n return item;\n });\n}\n\nexport class ConfirmationBoard {\n readonly #directory: ClientDirectory;\n readonly #timeoutMs: number;\n readonly #graceMs: number;\n readonly #maxPerScope: number;\n readonly #newId: () => string;\n readonly #pending = new Map<string, Pending>();\n readonly #byScope = new Map<string, Set<Pending>>();\n readonly #sent = new Map<string, { readonly scope: string; signature: string }>();\n readonly #forgetting = new Map<string, Timer>();\n #closed = false;\n\n constructor(options: ConfirmationBoardOptions) {\n this.#directory = options.directory;\n this.#timeoutMs = options.timeoutMs;\n this.#graceMs = options.orphanGraceMs ?? ORPHAN_GRACE_MS;\n this.#maxPerScope = options.maxPendingPerScope ?? MAX_PENDING_PER_SCOPE;\n let ids = 0;\n this.#newId = options.newId ?? (() => `${Date.now().toString(36)}-${++ids}`);\n options.directory.subscribe(this.#changed);\n }\n\n ask(request: ConfirmationRequest): Promise<ConfirmationOutcome> {\n const { scope, signal } = request;\n if (this.#closed) return Promise.resolve(\"shutdown\");\n if (signal?.aborted) return Promise.resolve(\"cancelled\");\n if ((this.#byScope.get(scope)?.size ?? 0) >= this.#maxPerScope) {\n return Promise.resolve(\"unavailable\");\n }\n const audience = this.#selected(request);\n if (audience.length === 0) return Promise.resolve(\"unavailable\");\n\n return new Promise((resolve) => {\n const onAbort = () => pending.settle(\"cancelled\");\n const pending = new Pending(\n this.#newId(),\n request,\n Date.now() + this.#timeoutMs,\n resolve,\n () => {\n signal?.removeEventListener(\"abort\", onAbort);\n this.#pending.delete(pending.id);\n const peers = this.#byScope.get(scope);\n peers?.delete(pending);\n if (peers?.size === 0) this.#byScope.delete(scope);\n this.#refresh(scope);\n },\n );\n for (const id of audience) pending.audience.add(id);\n this.#pending.set(pending.id, pending);\n let peers = this.#byScope.get(scope);\n if (!peers) this.#byScope.set(scope, (peers = new Set()));\n peers.add(pending);\n pending.armDeadline(this.#timeoutMs);\n signal?.addEventListener(\"abort\", onAbort, { once: true });\n this.#refresh(scope);\n });\n }\n\n answer(clientId: string, scope: string, answer: ConfirmationAnswer): boolean {\n const pending = this.#pending.get(answer.id);\n if (!pending || pending.request.scope !== scope || !pending.audience.has(clientId)) {\n return false;\n }\n pending.settle(answer.approve ? \"approved\" : \"declined\");\n return true;\n }\n\n close(): void {\n this.#closed = true;\n for (const pending of [...this.#pending.values()]) pending.settle(\"shutdown\");\n for (const timer of this.#forgetting.values()) clearTimeout(timer);\n this.#forgetting.clear();\n }\n\n readonly #changed: DirectoryListener = (scope, clientId, change) => {\n const peers = this.#byScope.get(scope);\n if (change === \"left\") {\n for (const pending of peers ?? []) pending.audience.delete(clientId);\n this.#forgetLater(clientId);\n }\n if (change === \"joined\") {\n this.#stopForgetting(clientId);\n if (this.#sent.has(clientId)) {\n this.#sent.set(clientId, { scope, signature: \"stale\" });\n }\n }\n this.#refresh(scope);\n };\n\n #forgetLater(clientId: string): void {\n this.#stopForgetting(clientId);\n if (!this.#sent.has(clientId)) return;\n const timer = setTimeout(() => {\n this.#forgetting.delete(clientId);\n this.#sent.delete(clientId);\n }, this.#timeoutMs);\n timer.unref();\n this.#forgetting.set(clientId, timer);\n }\n\n #stopForgetting(clientId: string): void {\n clearTimeout(this.#forgetting.get(clientId));\n this.#forgetting.delete(clientId);\n }\n\n #selected(request: ConfirmationRequest): string[] {\n if (request.thread) return this.#watchers(request, request.thread);\n const selection = this.#directory.select(request.scope, request.clientId);\n if (!selection) return [];\n return [selection.target.client, ...selection.tied]\n .filter((client) => client.canConfirm)\n .map((client) => client.id);\n }\n\n #watchers(request: ConfirmationRequest, thread: ConfirmationThread): string[] {\n return this.#directory\n .targets(request.scope)\n .map(({ client }) => client)\n .filter(\n (client) =>\n client.canConfirm &&\n (client.id === request.clientId || client.watching.includes(thread.threadId)),\n )\n .map((client) => client.id);\n }\n\n #canShow(clientId: string): boolean {\n return this.#directory.target(clientId)?.client.canConfirm ?? false;\n }\n\n #refresh(scope: string): void {\n const peers = this.#byScope.get(scope);\n if (!peers && ![...this.#sent.values()].some((sent) => sent.scope === scope)) return;\n for (const pending of peers ?? []) {\n const selected = this.#selected(pending.request);\n if (pending.request.thread) pending.audience.clear();\n for (const id of selected) pending.audience.add(id);\n }\n for (const target of this.#directory.targets(scope)) {\n this.#deliver(target, [...(peers ?? [])]);\n }\n for (const pending of peers ?? []) this.#watchOrphan(pending);\n }\n\n #deliver(\n target: ReturnType<ClientDirectory[\"targets\"]>[number],\n peers: readonly Pending[],\n ): void {\n const { id, scope, canConfirm } = target.client;\n const mine = canConfirm ? peers.filter((pending) => pending.audience.has(id)) : [];\n const now = Date.now();\n const prompts = mine.map((pending) => this.#prompt(pending, now));\n const signature =\n prompts.length === 0\n ? EMPTY_LIST\n : jsonOf(prompts.map(({ expiresInMs: _expires, ...rest }) => rest));\n const previous = this.#sent.get(id)?.signature;\n if (previous === signature || (previous === undefined && prompts.length === 0))\n return;\n const frame: ConfirmationsFrame = { type: \"confirmations\", items: prompts };\n if (!target.send(jsonOf(frame))) return;\n if (prompts.length === 0) this.#sent.delete(id);\n else this.#sent.set(id, { scope, signature });\n }\n\n #prompt(pending: Pending, now: number): ConfirmationPrompt {\n const { id, request, deadline } = pending;\n return {\n id,\n action: request.action,\n description: request.description,\n input: request.input ?? null,\n origin: request.origin,\n expiresInMs: Math.max(0, deadline - now),\n ...(request.thread ? { threadId: request.thread.threadId } : {}),\n };\n }\n\n #watchOrphan(pending: Pending): void {\n const shown = [...pending.audience].some((id) => this.#canShow(id));\n if (shown) {\n clearTimeout(pending.orphanTimer);\n pending.orphanTimer = undefined;\n } else if (pending.orphanTimer === undefined) {\n pending.orphanTimer = setTimeout(\n () => pending.settle(\"unavailable\"),\n this.#graceMs,\n );\n }\n }\n}\n"]}
|
|
@@ -0,0 +1,41 @@
|
|
|
1
|
+
import type { ChannelConnection } from "@parlor/sync";
|
|
2
|
+
import { type ClientSummary, type PresenceUpdate, type UiOp } from "../protocol.js";
|
|
3
|
+
export interface ConnectedClient {
|
|
4
|
+
readonly id: string;
|
|
5
|
+
readonly scope: string;
|
|
6
|
+
readonly platform: string;
|
|
7
|
+
readonly buildId: string | undefined;
|
|
8
|
+
readonly focused: boolean;
|
|
9
|
+
readonly visible: boolean;
|
|
10
|
+
readonly ops: readonly UiOp[];
|
|
11
|
+
readonly canConfirm: boolean;
|
|
12
|
+
readonly watching: readonly string[];
|
|
13
|
+
readonly seenAt: number;
|
|
14
|
+
}
|
|
15
|
+
export interface ClientTarget {
|
|
16
|
+
readonly client: ConnectedClient;
|
|
17
|
+
send(data: string): boolean;
|
|
18
|
+
}
|
|
19
|
+
export interface Selection {
|
|
20
|
+
readonly target: ClientTarget;
|
|
21
|
+
readonly tied: readonly ConnectedClient[];
|
|
22
|
+
}
|
|
23
|
+
export type PresenceOutcome = "rejected" | "updated" | "registered";
|
|
24
|
+
export type DirectoryChange = "joined" | "updated" | "left";
|
|
25
|
+
export type DirectoryListener = (scope: string, clientId: string, change: DirectoryChange) => void;
|
|
26
|
+
export declare class ClientDirectory {
|
|
27
|
+
#private;
|
|
28
|
+
constructor(now?: () => number);
|
|
29
|
+
subscribe(listener: DirectoryListener): () => void;
|
|
30
|
+
connect(stream: ChannelConnection): () => void;
|
|
31
|
+
touch(clientId: string | undefined, scope: string): void;
|
|
32
|
+
presence(clientId: string, scope: string, update: PresenceUpdate): PresenceOutcome;
|
|
33
|
+
leave(clientId: string, scope: string): boolean;
|
|
34
|
+
connected(scope: string, clientId: string): boolean;
|
|
35
|
+
targets(scope: string): readonly ClientTarget[];
|
|
36
|
+
target(clientId: string): ClientTarget | undefined;
|
|
37
|
+
select(scope: string, clientId?: string): Selection | undefined;
|
|
38
|
+
summaries(scope: string): ClientSummary[];
|
|
39
|
+
summary(client: ConnectedClient): ClientSummary;
|
|
40
|
+
}
|
|
41
|
+
//# sourceMappingURL=directory.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"directory.d.ts","sourceRoot":"","sources":["../../src/server/directory.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,iBAAiB,EAAE,MAAM,cAAc,CAAC;AAEtD,OAAO,EACL,KAAK,aAAa,EAGlB,KAAK,cAAc,EACnB,KAAK,IAAI,EACV,MAAM,gBAAgB,CAAC;AAGxB,MAAM,WAAW,eAAe;IAC9B,QAAQ,CAAC,EAAE,EAAE,MAAM,CAAC;IACpB,QAAQ,CAAC,KAAK,EAAE,MAAM,CAAC;IACvB,QAAQ,CAAC,QAAQ,EAAE,MAAM,CAAC;IAC1B,QAAQ,CAAC,OAAO,EAAE,MAAM,GAAG,SAAS,CAAC;IACrC,QAAQ,CAAC,OAAO,EAAE,OAAO,CAAC;IAC1B,QAAQ,CAAC,OAAO,EAAE,OAAO,CAAC;IAC1B,QAAQ,CAAC,GAAG,EAAE,SAAS,IAAI,EAAE,CAAC;IAC9B,QAAQ,CAAC,UAAU,EAAE,OAAO,CAAC;IAC7B,QAAQ,CAAC,QAAQ,EAAE,SAAS,MAAM,EAAE,CAAC;IACrC,QAAQ,CAAC,MAAM,EAAE,MAAM,CAAC;CACzB;AAED,MAAM,WAAW,YAAY;IAC3B,QAAQ,CAAC,MAAM,EAAE,eAAe,CAAC;IACjC,IAAI,CAAC,IAAI,EAAE,MAAM,GAAG,OAAO,CAAC;CAC7B;AAED,MAAM,WAAW,SAAS;IACxB,QAAQ,CAAC,MAAM,EAAE,YAAY,CAAC;IAC9B,QAAQ,CAAC,IAAI,EAAE,SAAS,eAAe,EAAE,CAAC;CAC3C;AAED,MAAM,MAAM,eAAe,GAAG,UAAU,GAAG,SAAS,GAAG,YAAY,CAAC;AAEpE,MAAM,MAAM,eAAe,GAAG,QAAQ,GAAG,SAAS,GAAG,MAAM,CAAC;AAE5D,MAAM,MAAM,iBAAiB,GAAG,CAC9B,KAAK,EAAE,MAAM,EACb,QAAQ,EAAE,MAAM,EAChB,MAAM,EAAE,eAAe,KACpB,IAAI,CAAC;AAOV,qBAAa,eAAe;;IAQ1B,YAAY,GAAG,GAAE,MAAM,MAAiB,EAEvC;IAED,SAAS,CAAC,QAAQ,EAAE,iBAAiB,GAAG,MAAM,IAAI,CAGjD;IAED,OAAO,CAAC,MAAM,EAAE,iBAAiB,GAAG,MAAM,IAAI,CAY7C;IAED,KAAK,CAAC,QAAQ,EAAE,MAAM,GAAG,SAAS,EAAE,KAAK,EAAE,MAAM,GAAG,IAAI,CAMvD;IAED,QAAQ,CAAC,QAAQ,EAAE,MAAM,EAAE,KAAK,EAAE,MAAM,EAAE,MAAM,EAAE,cAAc,GAAG,eAAe,CAgCjF;IAED,KAAK,CAAC,QAAQ,EAAE,MAAM,EAAE,KAAK,EAAE,MAAM,GAAG,OAAO,CAU9C;IAED,SAAS,CAAC,KAAK,EAAE,MAAM,EAAE,QAAQ,EAAE,MAAM,GAAG,OAAO,CAElD;IAED,OAAO,CAAC,KAAK,EAAE,MAAM,GAAG,SAAS,YAAY,EAAE,CAE9C;IAED,MAAM,CAAC,QAAQ,EAAE,MAAM,GAAG,YAAY,GAAG,SAAS,CAGjD;IAED,MAAM,CAAC,KAAK,EAAE,MAAM,EAAE,QAAQ,CAAC,EAAE,MAAM,GAAG,SAAS,GAAG,SAAS,CAY9D;IAED,SAAS,CAAC,KAAK,EAAE,MAAM,GAAG,aAAa,EAAE,CAIxC;IAED,OAAO,CAAC,MAAM,EAAE,eAAe,GAAG,aAAa,CAS9C;CAwEF"}
|
|
@@ -0,0 +1,193 @@
|
|
|
1
|
+
import { isUiOp, LEGACY_UI_OPS, } from "../protocol.js";
|
|
2
|
+
import { audienceOf } from "./audience.js";
|
|
3
|
+
export class ClientDirectory {
|
|
4
|
+
#now;
|
|
5
|
+
#byId = new Map();
|
|
6
|
+
#byScope = new Map();
|
|
7
|
+
#unannounced = new Map();
|
|
8
|
+
#listeners = new Set();
|
|
9
|
+
constructor(now = Date.now) {
|
|
10
|
+
this.#now = now;
|
|
11
|
+
}
|
|
12
|
+
subscribe(listener) {
|
|
13
|
+
this.#listeners.add(listener);
|
|
14
|
+
return () => this.#listeners.delete(listener);
|
|
15
|
+
}
|
|
16
|
+
connect(stream) {
|
|
17
|
+
const { clientId } = stream;
|
|
18
|
+
if (clientId === undefined)
|
|
19
|
+
return () => { };
|
|
20
|
+
const connection = {
|
|
21
|
+
...stream,
|
|
22
|
+
scope: audienceOf(stream.scope, stream.principal),
|
|
23
|
+
};
|
|
24
|
+
this.#keepUnannounced(clientId, [
|
|
25
|
+
...(this.#unannounced.get(clientId) ?? []),
|
|
26
|
+
connection,
|
|
27
|
+
]);
|
|
28
|
+
return () => this.#disconnect(clientId, connection);
|
|
29
|
+
}
|
|
30
|
+
touch(clientId, scope) {
|
|
31
|
+
if (clientId === undefined)
|
|
32
|
+
return;
|
|
33
|
+
const entry = this.#byId.get(clientId);
|
|
34
|
+
if (!entry || entry.client.scope !== scope)
|
|
35
|
+
return;
|
|
36
|
+
entry.client = { ...entry.client, seenAt: this.#now() };
|
|
37
|
+
this.#notify(entry.client, "updated");
|
|
38
|
+
}
|
|
39
|
+
presence(clientId, scope, update) {
|
|
40
|
+
const announcing = this.#takeUnannounced(clientId, scope);
|
|
41
|
+
let entry = this.#byId.get(clientId);
|
|
42
|
+
if (entry && entry.client.scope !== scope) {
|
|
43
|
+
if (announcing.length === 0)
|
|
44
|
+
return "rejected";
|
|
45
|
+
this.#forget(entry);
|
|
46
|
+
entry = undefined;
|
|
47
|
+
}
|
|
48
|
+
if (!entry && announcing.length === 0)
|
|
49
|
+
return "rejected";
|
|
50
|
+
const client = {
|
|
51
|
+
id: clientId,
|
|
52
|
+
scope,
|
|
53
|
+
platform: update.platform,
|
|
54
|
+
buildId: update.buildId,
|
|
55
|
+
focused: update.focused,
|
|
56
|
+
visible: update.visible,
|
|
57
|
+
ops: update.ops ? update.ops.filter(isUiOp) : LEGACY_UI_OPS,
|
|
58
|
+
canConfirm: update.canConfirm ?? false,
|
|
59
|
+
watching: update.watching ?? [],
|
|
60
|
+
seenAt: this.#now(),
|
|
61
|
+
};
|
|
62
|
+
if (entry) {
|
|
63
|
+
entry.client = client;
|
|
64
|
+
}
|
|
65
|
+
else {
|
|
66
|
+
entry = { client, connections: [] };
|
|
67
|
+
this.#byId.set(clientId, entry);
|
|
68
|
+
this.#scopeSet(scope).add(entry);
|
|
69
|
+
}
|
|
70
|
+
entry.connections.push(...announcing);
|
|
71
|
+
const outcome = announcing.length > 0 ? "registered" : "updated";
|
|
72
|
+
this.#notify(client, outcome === "registered" ? "joined" : "updated");
|
|
73
|
+
return outcome;
|
|
74
|
+
}
|
|
75
|
+
leave(clientId, scope) {
|
|
76
|
+
const entry = this.#byId.get(clientId);
|
|
77
|
+
if (!entry || entry.client.scope !== scope)
|
|
78
|
+
return false;
|
|
79
|
+
const released = entry.connections.splice(0);
|
|
80
|
+
this.#forget(entry);
|
|
81
|
+
this.#keepUnannounced(clientId, [
|
|
82
|
+
...(this.#unannounced.get(clientId) ?? []),
|
|
83
|
+
...released,
|
|
84
|
+
]);
|
|
85
|
+
return true;
|
|
86
|
+
}
|
|
87
|
+
connected(scope, clientId) {
|
|
88
|
+
return this.#byId.get(clientId)?.client.scope === scope;
|
|
89
|
+
}
|
|
90
|
+
targets(scope) {
|
|
91
|
+
return [...(this.#byScope.get(scope) ?? [])].map((entry) => this.#targetOf(entry));
|
|
92
|
+
}
|
|
93
|
+
target(clientId) {
|
|
94
|
+
const entry = this.#byId.get(clientId);
|
|
95
|
+
return entry && this.#targetOf(entry);
|
|
96
|
+
}
|
|
97
|
+
select(scope, clientId) {
|
|
98
|
+
const entries = [...(this.#byScope.get(scope) ?? [])];
|
|
99
|
+
if (entries.length === 0)
|
|
100
|
+
return undefined;
|
|
101
|
+
const explicit = clientId === undefined ? undefined : this.#byId.get(clientId);
|
|
102
|
+
if (explicit && explicit.client.scope === scope) {
|
|
103
|
+
return { target: this.#targetOf(explicit), tied: [] };
|
|
104
|
+
}
|
|
105
|
+
const focused = entries.filter((e) => e.client.focused);
|
|
106
|
+
if (focused.length > 0)
|
|
107
|
+
return this.#mostRecent(focused, () => true);
|
|
108
|
+
const visible = entries.filter((e) => e.client.visible);
|
|
109
|
+
const pool = visible.length > 0 ? visible : entries;
|
|
110
|
+
return this.#mostRecent(pool, (a, b) => a.client.seenAt === b.client.seenAt);
|
|
111
|
+
}
|
|
112
|
+
summaries(scope) {
|
|
113
|
+
return [...(this.#byScope.get(scope) ?? [])].map((entry) => this.summary(entry.client));
|
|
114
|
+
}
|
|
115
|
+
summary(client) {
|
|
116
|
+
return {
|
|
117
|
+
id: client.id,
|
|
118
|
+
platform: client.platform,
|
|
119
|
+
...(client.buildId === undefined ? {} : { buildId: client.buildId }),
|
|
120
|
+
focused: client.focused,
|
|
121
|
+
visible: client.visible,
|
|
122
|
+
seenAgoMs: Math.max(0, this.#now() - client.seenAt),
|
|
123
|
+
};
|
|
124
|
+
}
|
|
125
|
+
#mostRecent(pool, ties) {
|
|
126
|
+
let best = pool[0];
|
|
127
|
+
for (const entry of pool)
|
|
128
|
+
if (entry.client.seenAt >= best.client.seenAt)
|
|
129
|
+
best = entry;
|
|
130
|
+
const tied = pool.filter((entry) => entry !== best && ties(entry, best));
|
|
131
|
+
return { target: this.#targetOf(best), tied: tied.map((entry) => entry.client) };
|
|
132
|
+
}
|
|
133
|
+
#targetOf(entry) {
|
|
134
|
+
return {
|
|
135
|
+
client: entry.client,
|
|
136
|
+
send: (data) => {
|
|
137
|
+
const connection = entry.connections.at(-1);
|
|
138
|
+
if (!connection)
|
|
139
|
+
return false;
|
|
140
|
+
connection.send(data);
|
|
141
|
+
return true;
|
|
142
|
+
},
|
|
143
|
+
};
|
|
144
|
+
}
|
|
145
|
+
#takeUnannounced(clientId, scope) {
|
|
146
|
+
const waiting = this.#unannounced.get(clientId) ?? [];
|
|
147
|
+
this.#keepUnannounced(clientId, waiting.filter((connection) => connection.scope !== scope));
|
|
148
|
+
return waiting.filter((connection) => connection.scope === scope);
|
|
149
|
+
}
|
|
150
|
+
#keepUnannounced(clientId, connections) {
|
|
151
|
+
if (connections.length > 0)
|
|
152
|
+
this.#unannounced.set(clientId, connections);
|
|
153
|
+
else
|
|
154
|
+
this.#unannounced.delete(clientId);
|
|
155
|
+
}
|
|
156
|
+
#disconnect(clientId, connection) {
|
|
157
|
+
const waiting = this.#unannounced.get(clientId);
|
|
158
|
+
if (waiting?.includes(connection)) {
|
|
159
|
+
this.#keepUnannounced(clientId, waiting.filter((candidate) => candidate !== connection));
|
|
160
|
+
return;
|
|
161
|
+
}
|
|
162
|
+
const entry = this.#byId.get(clientId);
|
|
163
|
+
const index = entry?.connections.indexOf(connection) ?? -1;
|
|
164
|
+
if (!entry || index === -1)
|
|
165
|
+
return;
|
|
166
|
+
entry.connections.splice(index, 1);
|
|
167
|
+
if (entry.connections.length === 0)
|
|
168
|
+
this.#forget(entry);
|
|
169
|
+
}
|
|
170
|
+
#forget(entry) {
|
|
171
|
+
if (this.#byId.get(entry.client.id) !== entry)
|
|
172
|
+
return;
|
|
173
|
+
this.#byId.delete(entry.client.id);
|
|
174
|
+
const peers = this.#byScope.get(entry.client.scope);
|
|
175
|
+
peers?.delete(entry);
|
|
176
|
+
if (peers?.size === 0)
|
|
177
|
+
this.#byScope.delete(entry.client.scope);
|
|
178
|
+
this.#notify(entry.client, "left");
|
|
179
|
+
}
|
|
180
|
+
#notify(client, change) {
|
|
181
|
+
for (const listener of this.#listeners)
|
|
182
|
+
listener(client.scope, client.id, change);
|
|
183
|
+
}
|
|
184
|
+
#scopeSet(scope) {
|
|
185
|
+
let set = this.#byScope.get(scope);
|
|
186
|
+
if (!set) {
|
|
187
|
+
set = new Set();
|
|
188
|
+
this.#byScope.set(scope, set);
|
|
189
|
+
}
|
|
190
|
+
return set;
|
|
191
|
+
}
|
|
192
|
+
}
|
|
193
|
+
//# sourceMappingURL=directory.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"directory.js","sourceRoot":"","sources":["../../src/server/directory.ts"],"names":[],"mappings":"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","sourcesContent":["import type { ChannelConnection } from \"@parlor/sync\";\n\nimport {\n type ClientSummary,\n isUiOp,\n LEGACY_UI_OPS,\n type PresenceUpdate,\n type UiOp,\n} from \"../protocol.js\";\nimport { audienceOf } from \"./audience.js\";\n\nexport interface ConnectedClient {\n readonly id: string;\n readonly scope: string;\n readonly platform: string;\n readonly buildId: string | undefined;\n readonly focused: boolean;\n readonly visible: boolean;\n readonly ops: readonly UiOp[];\n readonly canConfirm: boolean;\n readonly watching: readonly string[];\n readonly seenAt: number;\n}\n\nexport interface ClientTarget {\n readonly client: ConnectedClient;\n send(data: string): boolean;\n}\n\nexport interface Selection {\n readonly target: ClientTarget;\n readonly tied: readonly ConnectedClient[];\n}\n\nexport type PresenceOutcome = \"rejected\" | \"updated\" | \"registered\";\n\nexport type DirectoryChange = \"joined\" | \"updated\" | \"left\";\n\nexport type DirectoryListener = (\n scope: string,\n clientId: string,\n change: DirectoryChange,\n) => void;\n\ninterface Entry {\n client: ConnectedClient;\n readonly connections: ChannelConnection[];\n}\n\nexport class ClientDirectory {\n readonly #now: () => number;\n readonly #byId = new Map<string, Entry>();\n readonly #byScope = new Map<string, Set<Entry>>();\n readonly #unannounced = new Map<string, ChannelConnection[]>();\n\n readonly #listeners = new Set<DirectoryListener>();\n\n constructor(now: () => number = Date.now) {\n this.#now = now;\n }\n\n subscribe(listener: DirectoryListener): () => void {\n this.#listeners.add(listener);\n return () => this.#listeners.delete(listener);\n }\n\n connect(stream: ChannelConnection): () => void {\n const { clientId } = stream;\n if (clientId === undefined) return () => {};\n const connection: ChannelConnection = {\n ...stream,\n scope: audienceOf(stream.scope, stream.principal),\n };\n this.#keepUnannounced(clientId, [\n ...(this.#unannounced.get(clientId) ?? []),\n connection,\n ]);\n return () => this.#disconnect(clientId, connection);\n }\n\n touch(clientId: string | undefined, scope: string): void {\n if (clientId === undefined) return;\n const entry = this.#byId.get(clientId);\n if (!entry || entry.client.scope !== scope) return;\n entry.client = { ...entry.client, seenAt: this.#now() };\n this.#notify(entry.client, \"updated\");\n }\n\n presence(clientId: string, scope: string, update: PresenceUpdate): PresenceOutcome {\n const announcing = this.#takeUnannounced(clientId, scope);\n let entry = this.#byId.get(clientId);\n if (entry && entry.client.scope !== scope) {\n if (announcing.length === 0) return \"rejected\";\n this.#forget(entry);\n entry = undefined;\n }\n if (!entry && announcing.length === 0) return \"rejected\";\n const client: ConnectedClient = {\n id: clientId,\n scope,\n platform: update.platform,\n buildId: update.buildId,\n focused: update.focused,\n visible: update.visible,\n ops: update.ops ? update.ops.filter(isUiOp) : LEGACY_UI_OPS,\n canConfirm: update.canConfirm ?? false,\n watching: update.watching ?? [],\n seenAt: this.#now(),\n };\n if (entry) {\n entry.client = client;\n } else {\n entry = { client, connections: [] };\n this.#byId.set(clientId, entry);\n this.#scopeSet(scope).add(entry);\n }\n entry.connections.push(...announcing);\n const outcome = announcing.length > 0 ? \"registered\" : \"updated\";\n this.#notify(client, outcome === \"registered\" ? \"joined\" : \"updated\");\n return outcome;\n }\n\n leave(clientId: string, scope: string): boolean {\n const entry = this.#byId.get(clientId);\n if (!entry || entry.client.scope !== scope) return false;\n const released = entry.connections.splice(0);\n this.#forget(entry);\n this.#keepUnannounced(clientId, [\n ...(this.#unannounced.get(clientId) ?? []),\n ...released,\n ]);\n return true;\n }\n\n connected(scope: string, clientId: string): boolean {\n return this.#byId.get(clientId)?.client.scope === scope;\n }\n\n targets(scope: string): readonly ClientTarget[] {\n return [...(this.#byScope.get(scope) ?? [])].map((entry) => this.#targetOf(entry));\n }\n\n target(clientId: string): ClientTarget | undefined {\n const entry = this.#byId.get(clientId);\n return entry && this.#targetOf(entry);\n }\n\n select(scope: string, clientId?: string): Selection | undefined {\n const entries = [...(this.#byScope.get(scope) ?? [])];\n if (entries.length === 0) return undefined;\n const explicit = clientId === undefined ? undefined : this.#byId.get(clientId);\n if (explicit && explicit.client.scope === scope) {\n return { target: this.#targetOf(explicit), tied: [] };\n }\n const focused = entries.filter((e) => e.client.focused);\n if (focused.length > 0) return this.#mostRecent(focused, () => true);\n const visible = entries.filter((e) => e.client.visible);\n const pool = visible.length > 0 ? visible : entries;\n return this.#mostRecent(pool, (a, b) => a.client.seenAt === b.client.seenAt);\n }\n\n summaries(scope: string): ClientSummary[] {\n return [...(this.#byScope.get(scope) ?? [])].map((entry) =>\n this.summary(entry.client),\n );\n }\n\n summary(client: ConnectedClient): ClientSummary {\n return {\n id: client.id,\n platform: client.platform,\n ...(client.buildId === undefined ? {} : { buildId: client.buildId }),\n focused: client.focused,\n visible: client.visible,\n seenAgoMs: Math.max(0, this.#now() - client.seenAt),\n };\n }\n\n #mostRecent(pool: Entry[], ties: (a: Entry, b: Entry) => boolean): Selection {\n let best = pool[0]!;\n for (const entry of pool) if (entry.client.seenAt >= best.client.seenAt) best = entry;\n const tied = pool.filter((entry) => entry !== best && ties(entry, best));\n return { target: this.#targetOf(best), tied: tied.map((entry) => entry.client) };\n }\n\n #targetOf(entry: Entry): ClientTarget {\n return {\n client: entry.client,\n send: (data) => {\n const connection = entry.connections.at(-1);\n if (!connection) return false;\n connection.send(data);\n return true;\n },\n };\n }\n\n #takeUnannounced(clientId: string, scope: string): ChannelConnection[] {\n const waiting = this.#unannounced.get(clientId) ?? [];\n this.#keepUnannounced(\n clientId,\n waiting.filter((connection) => connection.scope !== scope),\n );\n return waiting.filter((connection) => connection.scope === scope);\n }\n\n #keepUnannounced(clientId: string, connections: ChannelConnection[]): void {\n if (connections.length > 0) this.#unannounced.set(clientId, connections);\n else this.#unannounced.delete(clientId);\n }\n\n #disconnect(clientId: string, connection: ChannelConnection): void {\n const waiting = this.#unannounced.get(clientId);\n if (waiting?.includes(connection)) {\n this.#keepUnannounced(\n clientId,\n waiting.filter((candidate) => candidate !== connection),\n );\n return;\n }\n const entry = this.#byId.get(clientId);\n const index = entry?.connections.indexOf(connection) ?? -1;\n if (!entry || index === -1) return;\n entry.connections.splice(index, 1);\n if (entry.connections.length === 0) this.#forget(entry);\n }\n\n #forget(entry: Entry): void {\n if (this.#byId.get(entry.client.id) !== entry) return;\n this.#byId.delete(entry.client.id);\n const peers = this.#byScope.get(entry.client.scope);\n peers?.delete(entry);\n if (peers?.size === 0) this.#byScope.delete(entry.client.scope);\n this.#notify(entry.client, \"left\");\n }\n\n #notify(client: ConnectedClient, change: DirectoryChange): void {\n for (const listener of this.#listeners) listener(client.scope, client.id, change);\n }\n\n #scopeSet(scope: string): Set<Entry> {\n let set = this.#byScope.get(scope);\n if (!set) {\n set = new Set();\n this.#byScope.set(scope, set);\n }\n return set;\n }\n}\n"]}
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
export { type AgentUi, type AgentUiDependencies, type AgentUiOptions, createAgentUi, DEFAULT_UI_TIMEOUT_MS, type ExternalUiPolicy, webOriginFromEnv, } from "./agent-ui.js";
|
|
2
|
+
export { audienceOf, userAudience } from "./audience.js";
|
|
3
|
+
export { type ConfirmationOutcome, type ConfirmationRequest, type ConfirmationThread, DEFAULT_CONFIRM_TIMEOUT_MS, } from "./confirmations.js";
|
|
4
|
+
export { fileManifestSource, type UiManifestInput, type UiManifestSource, } from "./manifest-source.js";
|
|
5
|
+
export { type BrokerOptions, UiBroker, UiFailure, type UiReceipt } from "./broker.js";
|
|
6
|
+
export { ClientDirectory, type ClientTarget, type ConnectedClient, type PresenceOutcome, type Selection, } from "./directory.js";
|
|
7
|
+
export { type AgentUiRouteOptions, MAX_CLIENT_MESSAGE_BYTES, registerAgentUiRoutes, } from "./routes.js";
|
|
8
|
+
export { parseClientMessage, uiOpInputs } from "./schemas.js";
|
|
9
|
+
export { type AgentTool, type AgentToolContext, type HighlightToolResult, type NavigateToolResult, type ScreenToolResult, type SetToolResult, UI_MANIFEST_RESOURCE, UI_TOOLS, type UiToolName, type UnreachableResult, } from "./tools.js";
|
|
10
|
+
//# sourceMappingURL=index.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/server/index.ts"],"names":[],"mappings":"AAAA,OAAO,EACL,KAAK,OAAO,EACZ,KAAK,mBAAmB,EACxB,KAAK,cAAc,EACnB,aAAa,EACb,qBAAqB,EACrB,KAAK,gBAAgB,EACrB,gBAAgB,GACjB,MAAM,eAAe,CAAC;AACvB,OAAO,EAAE,UAAU,EAAE,YAAY,EAAE,MAAM,eAAe,CAAC;AACzD,OAAO,EACL,KAAK,mBAAmB,EACxB,KAAK,mBAAmB,EACxB,KAAK,kBAAkB,EACvB,0BAA0B,GAC3B,MAAM,oBAAoB,CAAC;AAC5B,OAAO,EACL,kBAAkB,EAClB,KAAK,eAAe,EACpB,KAAK,gBAAgB,GACtB,MAAM,sBAAsB,CAAC;AAC9B,OAAO,EAAE,KAAK,aAAa,EAAE,QAAQ,EAAE,SAAS,EAAE,KAAK,SAAS,EAAE,MAAM,aAAa,CAAC;AACtF,OAAO,EACL,eAAe,EACf,KAAK,YAAY,EACjB,KAAK,eAAe,EACpB,KAAK,eAAe,EACpB,KAAK,SAAS,GACf,MAAM,gBAAgB,CAAC;AACxB,OAAO,EACL,KAAK,mBAAmB,EACxB,wBAAwB,EACxB,qBAAqB,GACtB,MAAM,aAAa,CAAC;AACrB,OAAO,EAAE,kBAAkB,EAAE,UAAU,EAAE,MAAM,cAAc,CAAC;AAC9D,OAAO,EACL,KAAK,SAAS,EACd,KAAK,gBAAgB,EACrB,KAAK,mBAAmB,EACxB,KAAK,kBAAkB,EACvB,KAAK,gBAAgB,EACrB,KAAK,aAAa,EAClB,oBAAoB,EACpB,QAAQ,EACR,KAAK,UAAU,EACf,KAAK,iBAAiB,GACvB,MAAM,YAAY,CAAC"}
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
export { createAgentUi, DEFAULT_UI_TIMEOUT_MS, webOriginFromEnv, } from "./agent-ui.js";
|
|
2
|
+
export { audienceOf, userAudience } from "./audience.js";
|
|
3
|
+
export { DEFAULT_CONFIRM_TIMEOUT_MS, } from "./confirmations.js";
|
|
4
|
+
export { fileManifestSource, } from "./manifest-source.js";
|
|
5
|
+
export { UiBroker, UiFailure } from "./broker.js";
|
|
6
|
+
export { ClientDirectory, } from "./directory.js";
|
|
7
|
+
export { MAX_CLIENT_MESSAGE_BYTES, registerAgentUiRoutes, } from "./routes.js";
|
|
8
|
+
export { parseClientMessage, uiOpInputs } from "./schemas.js";
|
|
9
|
+
export { UI_MANIFEST_RESOURCE, UI_TOOLS, } from "./tools.js";
|
|
10
|
+
//# sourceMappingURL=index.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.js","sourceRoot":"","sources":["../../src/server/index.ts"],"names":[],"mappings":"AAAA,OAAO,EAIL,aAAa,EACb,qBAAqB,EAErB,gBAAgB,GACjB,MAAM,eAAe,CAAC;AACvB,OAAO,EAAE,UAAU,EAAE,YAAY,EAAE,MAAM,eAAe,CAAC;AACzD,OAAO,EAIL,0BAA0B,GAC3B,MAAM,oBAAoB,CAAC;AAC5B,OAAO,EACL,kBAAkB,GAGnB,MAAM,sBAAsB,CAAC;AAC9B,OAAO,EAAsB,QAAQ,EAAE,SAAS,EAAkB,MAAM,aAAa,CAAC;AACtF,OAAO,EACL,eAAe,GAKhB,MAAM,gBAAgB,CAAC;AACxB,OAAO,EAEL,wBAAwB,EACxB,qBAAqB,GACtB,MAAM,aAAa,CAAC;AACrB,OAAO,EAAE,kBAAkB,EAAE,UAAU,EAAE,MAAM,cAAc,CAAC;AAC9D,OAAO,EAOL,oBAAoB,EACpB,QAAQ,GAGT,MAAM,YAAY,CAAC","sourcesContent":["export {\n type AgentUi,\n type AgentUiDependencies,\n type AgentUiOptions,\n createAgentUi,\n DEFAULT_UI_TIMEOUT_MS,\n type ExternalUiPolicy,\n webOriginFromEnv,\n} from \"./agent-ui.js\";\nexport { audienceOf, userAudience } from \"./audience.js\";\nexport {\n type ConfirmationOutcome,\n type ConfirmationRequest,\n type ConfirmationThread,\n DEFAULT_CONFIRM_TIMEOUT_MS,\n} from \"./confirmations.js\";\nexport {\n fileManifestSource,\n type UiManifestInput,\n type UiManifestSource,\n} from \"./manifest-source.js\";\nexport { type BrokerOptions, UiBroker, UiFailure, type UiReceipt } from \"./broker.js\";\nexport {\n ClientDirectory,\n type ClientTarget,\n type ConnectedClient,\n type PresenceOutcome,\n type Selection,\n} from \"./directory.js\";\nexport {\n type AgentUiRouteOptions,\n MAX_CLIENT_MESSAGE_BYTES,\n registerAgentUiRoutes,\n} from \"./routes.js\";\nexport { parseClientMessage, uiOpInputs } from \"./schemas.js\";\nexport {\n type AgentTool,\n type AgentToolContext,\n type HighlightToolResult,\n type NavigateToolResult,\n type ScreenToolResult,\n type SetToolResult,\n UI_MANIFEST_RESOURCE,\n UI_TOOLS,\n type UiToolName,\n type UnreachableResult,\n} from \"./tools.js\";\n"]}
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
import type { UiManifest, ViewEntry } from "../manifest.js";
|
|
2
|
+
export type UiManifestInput = Omit<UiManifest, "views"> & {
|
|
3
|
+
readonly views: readonly (Omit<ViewEntry, "writable"> & {
|
|
4
|
+
readonly writable?: readonly string[] | undefined;
|
|
5
|
+
})[];
|
|
6
|
+
};
|
|
7
|
+
export type UiManifestSource = () => UiManifestInput | undefined;
|
|
8
|
+
export declare function normalizeManifest(manifest: UiManifestInput): UiManifest;
|
|
9
|
+
export declare function fileManifestSource(path: string): UiManifestSource;
|
|
10
|
+
//# sourceMappingURL=manifest-source.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"manifest-source.d.ts","sourceRoot":"","sources":["../../src/server/manifest-source.ts"],"names":[],"mappings":"AAEA,OAAO,KAAK,EAAE,UAAU,EAAE,SAAS,EAAE,MAAM,gBAAgB,CAAC;AAE5D,MAAM,MAAM,eAAe,GAAG,IAAI,CAAC,UAAU,EAAE,OAAO,CAAC,GAAG;IACxD,QAAQ,CAAC,KAAK,EAAE,SAAS,CAAC,IAAI,CAAC,SAAS,EAAE,UAAU,CAAC,GAAG;QACtD,QAAQ,CAAC,QAAQ,CAAC,EAAE,SAAS,MAAM,EAAE,GAAG,SAAS,CAAC;KACnD,CAAC,EAAE,CAAC;CACN,CAAC;AAEF,MAAM,MAAM,gBAAgB,GAAG,MAAM,eAAe,GAAG,SAAS,CAAC;AAEjE,wBAAgB,iBAAiB,CAAC,QAAQ,EAAE,eAAe,GAAG,UAAU,CAKvE;AAMD,wBAAgB,kBAAkB,CAAC,IAAI,EAAE,MAAM,GAAG,gBAAgB,CAqBjE"}
|
|
@@ -0,0 +1,36 @@
|
|
|
1
|
+
import { readFileSync, statSync } from "node:fs";
|
|
2
|
+
export function normalizeManifest(manifest) {
|
|
3
|
+
return {
|
|
4
|
+
...manifest,
|
|
5
|
+
views: manifest.views.map((view) => ({ ...view, writable: view.writable ?? [] })),
|
|
6
|
+
};
|
|
7
|
+
}
|
|
8
|
+
function readManifest(path) {
|
|
9
|
+
return normalizeManifest(JSON.parse(readFileSync(path, "utf8")));
|
|
10
|
+
}
|
|
11
|
+
export function fileManifestSource(path) {
|
|
12
|
+
let mtimeMs;
|
|
13
|
+
let manifest;
|
|
14
|
+
return () => {
|
|
15
|
+
let stat;
|
|
16
|
+
try {
|
|
17
|
+
stat = statSync(path);
|
|
18
|
+
}
|
|
19
|
+
catch {
|
|
20
|
+
mtimeMs = undefined;
|
|
21
|
+
manifest = undefined;
|
|
22
|
+
return undefined;
|
|
23
|
+
}
|
|
24
|
+
if (stat.mtimeMs === mtimeMs)
|
|
25
|
+
return manifest;
|
|
26
|
+
try {
|
|
27
|
+
manifest = readManifest(path);
|
|
28
|
+
mtimeMs = stat.mtimeMs;
|
|
29
|
+
}
|
|
30
|
+
catch {
|
|
31
|
+
return manifest;
|
|
32
|
+
}
|
|
33
|
+
return manifest;
|
|
34
|
+
};
|
|
35
|
+
}
|
|
36
|
+
//# sourceMappingURL=manifest-source.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"manifest-source.js","sourceRoot":"","sources":["../../src/server/manifest-source.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,YAAY,EAAE,QAAQ,EAAE,MAAM,SAAS,CAAC;AAYjD,MAAM,UAAU,iBAAiB,CAAC,QAAyB;IACzD,OAAO;QACL,GAAG,QAAQ;QACX,KAAK,EAAE,QAAQ,CAAC,KAAK,CAAC,GAAG,CAAC,CAAC,IAAI,EAAE,EAAE,CAAC,CAAC,EAAE,GAAG,IAAI,EAAE,QAAQ,EAAE,IAAI,CAAC,QAAQ,IAAI,EAAE,EAAE,CAAC,CAAC;KAClF,CAAC;AACJ,CAAC;AAED,SAAS,YAAY,CAAC,IAAY;IAChC,OAAO,iBAAiB,CAAC,IAAI,CAAC,KAAK,CAAC,YAAY,CAAC,IAAI,EAAE,MAAM,CAAC,CAAoB,CAAC,CAAC;AACtF,CAAC;AAED,MAAM,UAAU,kBAAkB,CAAC,IAAY;IAC7C,IAAI,OAA2B,CAAC;IAChC,IAAI,QAAgC,CAAC;IACrC,OAAO,GAAG,EAAE;QACV,IAAI,IAAI,CAAC;QACT,IAAI,CAAC;YACH,IAAI,GAAG,QAAQ,CAAC,IAAI,CAAC,CAAC;QACxB,CAAC;QAAC,MAAM,CAAC;YACP,OAAO,GAAG,SAAS,CAAC;YACpB,QAAQ,GAAG,SAAS,CAAC;YACrB,OAAO,SAAS,CAAC;QACnB,CAAC;QACD,IAAI,IAAI,CAAC,OAAO,KAAK,OAAO;YAAE,OAAO,QAAQ,CAAC;QAC9C,IAAI,CAAC;YACH,QAAQ,GAAG,YAAY,CAAC,IAAI,CAAC,CAAC;YAC9B,OAAO,GAAG,IAAI,CAAC,OAAO,CAAC;QACzB,CAAC;QAAC,MAAM,CAAC;YACP,OAAO,QAAQ,CAAC;QAClB,CAAC;QACD,OAAO,QAAQ,CAAC;IAClB,CAAC,CAAC;AACJ,CAAC","sourcesContent":["import { readFileSync, statSync } from \"node:fs\";\n\nimport type { UiManifest, ViewEntry } from \"../manifest.js\";\n\nexport type UiManifestInput = Omit<UiManifest, \"views\"> & {\n readonly views: readonly (Omit<ViewEntry, \"writable\"> & {\n readonly writable?: readonly string[] | undefined;\n })[];\n};\n\nexport type UiManifestSource = () => UiManifestInput | undefined;\n\nexport function normalizeManifest(manifest: UiManifestInput): UiManifest {\n return {\n ...manifest,\n views: manifest.views.map((view) => ({ ...view, writable: view.writable ?? [] })),\n };\n}\n\nfunction readManifest(path: string): UiManifest {\n return normalizeManifest(JSON.parse(readFileSync(path, \"utf8\")) as UiManifestInput);\n}\n\nexport function fileManifestSource(path: string): UiManifestSource {\n let mtimeMs: number | undefined;\n let manifest: UiManifest | undefined;\n return () => {\n let stat;\n try {\n stat = statSync(path);\n } catch {\n mtimeMs = undefined;\n manifest = undefined;\n return undefined;\n }\n if (stat.mtimeMs === mtimeMs) return manifest;\n try {\n manifest = readManifest(path);\n mtimeMs = stat.mtimeMs;\n } catch {\n return manifest;\n }\n return manifest;\n };\n}\n"]}
|