soulnet-dsh 0.1.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.
Files changed (72) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +64 -0
  3. package/cordis.patch.yml +40 -0
  4. package/lib/client.js +5427 -0
  5. package/lib/client.js.map +1 -0
  6. package/lib/commands.js +94 -0
  7. package/lib/commands.js.map +1 -0
  8. package/lib/events-Cz7g9aXF.js +134 -0
  9. package/lib/events-Cz7g9aXF.js.map +1 -0
  10. package/lib/events-DHPK3pow.js +134 -0
  11. package/lib/events-DHPK3pow.js.map +1 -0
  12. package/lib/events-DViXNRAZ.js +9 -0
  13. package/lib/events-DViXNRAZ.js.map +1 -0
  14. package/lib/index.js +2547 -0
  15. package/lib/index.js.map +1 -0
  16. package/lib/policy-HQKLctKr.js +96 -0
  17. package/lib/policy-HQKLctKr.js.map +1 -0
  18. package/lib/send-BL_Yr40O.js +33 -0
  19. package/lib/send-BL_Yr40O.js.map +1 -0
  20. package/lib/send-DcYQC5CC.js +127 -0
  21. package/lib/send-DcYQC5CC.js.map +1 -0
  22. package/lib/sessions.js +1197 -0
  23. package/lib/sessions.js.map +1 -0
  24. package/lib/tools.js +342 -0
  25. package/lib/tools.js.map +1 -0
  26. package/lib/types/alter-state.d.ts +185 -0
  27. package/lib/types/api/index.d.ts +52 -0
  28. package/lib/types/client/A2ANode.d.ts +4 -0
  29. package/lib/types/client/AlterPane.d.ts +9 -0
  30. package/lib/types/client/ChatPane.d.ts +13 -0
  31. package/lib/types/client/DraftCard.d.ts +11 -0
  32. package/lib/types/client/FriendComposer.d.ts +20 -0
  33. package/lib/types/client/FriendList.d.ts +12 -0
  34. package/lib/types/client/FriendPane.d.ts +13 -0
  35. package/lib/types/client/Inbox.d.ts +9 -0
  36. package/lib/types/client/InboxOverlay.d.ts +8 -0
  37. package/lib/types/client/Onboarding.d.ts +4 -0
  38. package/lib/types/client/SettingsSection.d.ts +29 -0
  39. package/lib/types/client/SidebarEntry.d.ts +9 -0
  40. package/lib/types/client/SoulmirrorPage.d.ts +12 -0
  41. package/lib/types/client/SoulmirrorView.d.ts +5 -0
  42. package/lib/types/client/a2a-node.d.ts +46 -0
  43. package/lib/types/client/alter-ui.d.ts +35 -0
  44. package/lib/types/client/api.d.ts +424 -0
  45. package/lib/types/client/inbox-state.d.ts +57 -0
  46. package/lib/types/client/index.d.ts +30 -0
  47. package/lib/types/client/locales.d.ts +239 -0
  48. package/lib/types/client/page-state.d.ts +116 -0
  49. package/lib/types/client/page-store.d.ts +96 -0
  50. package/lib/types/client/panel-store.d.ts +30 -0
  51. package/lib/types/client/styles.d.ts +2 -0
  52. package/lib/types/commands/index.d.ts +24 -0
  53. package/lib/types/drafts.d.ts +50 -0
  54. package/lib/types/events.d.ts +61 -0
  55. package/lib/types/friend-settings.d.ts +41 -0
  56. package/lib/types/index.d.ts +43 -0
  57. package/lib/types/network/fake.d.ts +10 -0
  58. package/lib/types/network/jsonrpc.d.ts +52 -0
  59. package/lib/types/network/send.d.ts +19 -0
  60. package/lib/types/network/soulnet.d.ts +123 -0
  61. package/lib/types/network/types.d.ts +203 -0
  62. package/lib/types/persona.d.ts +45 -0
  63. package/lib/types/policy.d.ts +105 -0
  64. package/lib/types/sessions/index.d.ts +182 -0
  65. package/lib/types/settings.d.ts +58 -0
  66. package/lib/types/tools/define.d.ts +35 -0
  67. package/lib/types/tools/index.d.ts +57 -0
  68. package/lib/types-zwvjR1n5.js +29 -0
  69. package/lib/types-zwvjR1n5.js.map +1 -0
  70. package/package.json +133 -0
  71. package/presets/soulmirror-chat/agent.cordis.yml +24 -0
  72. package/presets/soulmirror-chat/preset.yml +3 -0
package/lib/index.js ADDED
@@ -0,0 +1,2547 @@
1
+ import { a as isReplyTier, n as DEFAULT_REPLY_TIER, o as normalizeTier, t as sendAndArchive } from "./send-DcYQC5CC.js";
2
+ import { i as ProtocolFile, n as SOULMIRROR_PLUGIN, t as RELAY_FORM } from "./events-DHPK3pow.js";
3
+ import { n as NetworkErrorCode, t as NetworkError } from "./types-zwvjR1n5.js";
4
+ import { createRequire } from "node:module";
5
+ import { delimiter, dirname, isAbsolute, join } from "node:path";
6
+ import { accessSync, chmodSync, constants, realpathSync } from "node:fs";
7
+ import { spawn } from "node:child_process";
8
+ import { homedir } from "node:os";
9
+ import { fileURLToPath, pathToFileURL } from "node:url";
10
+ import { createInterface } from "node:readline";
11
+ //#region src/api/index.ts
12
+ const API_PREFIX = "/soulmirror/api/";
13
+ function readJson(req, limit = 262144) {
14
+ return new Promise((resolve, reject) => {
15
+ const chunks = [];
16
+ let size = 0;
17
+ req.on("data", (chunk) => {
18
+ size += chunk.length;
19
+ if (size > limit) {
20
+ reject(/* @__PURE__ */ new Error("request body too large"));
21
+ req.destroy();
22
+ return;
23
+ }
24
+ chunks.push(chunk);
25
+ });
26
+ req.on("end", () => {
27
+ if (chunks.length === 0) {
28
+ resolve({});
29
+ return;
30
+ }
31
+ try {
32
+ const parsed = JSON.parse(Buffer.concat(chunks).toString("utf8"));
33
+ resolve(typeof parsed === "object" && parsed !== null ? parsed : {});
34
+ } catch (error) {
35
+ reject(error instanceof Error ? error : new Error(String(error)));
36
+ }
37
+ });
38
+ req.on("error", reject);
39
+ });
40
+ }
41
+ function send(res, status, body) {
42
+ const payload = JSON.stringify(body);
43
+ res.writeHead(status, {
44
+ "content-type": "application/json; charset=utf-8",
45
+ "cache-control": "no-store",
46
+ "content-length": Buffer.byteLength(payload)
47
+ });
48
+ res.end(payload);
49
+ }
50
+ function errorBody(error) {
51
+ if (error instanceof NetworkError) return { error: {
52
+ code: error.code,
53
+ message: error.message
54
+ } };
55
+ return { error: {
56
+ code: -32603,
57
+ message: error instanceof Error ? error.message : String(error)
58
+ } };
59
+ }
60
+ const bad = (message) => ({
61
+ status: 400,
62
+ body: { error: {
63
+ code: -32602,
64
+ message
65
+ } }
66
+ });
67
+ const text = (value) => typeof value === "string" && value.trim() !== "" ? value.trim() : void 0;
68
+ const num = (value) => {
69
+ if (typeof value === "number" && Number.isFinite(value)) return value;
70
+ if (typeof value === "string" && value.trim() !== "" && Number.isFinite(Number(value))) return Number(value);
71
+ };
72
+ /** `fps` as a JSON array (POST) or a comma-separated query value (GET). */
73
+ const fpList = (value) => {
74
+ if (Array.isArray(value)) return value.filter((v) => typeof v === "string" && v !== "");
75
+ if (typeof value === "string") return value.split(",").map((v) => v.trim()).filter((v) => v !== "");
76
+ return [];
77
+ };
78
+ /** GET routes whose query string stands in for the JSON body (`?fp=…&since=…&limit=…`, `?fps=a,b`). */
79
+ const QUERY_ROUTES = /* @__PURE__ */ new Set([
80
+ "state",
81
+ "conversation.get",
82
+ "presence",
83
+ "session.latest",
84
+ "session.history",
85
+ "protocol.get",
86
+ "drafts.list"
87
+ ]);
88
+ function createApiHandler(options) {
89
+ const { client } = options;
90
+ const sseClients = /* @__PURE__ */ new Set();
91
+ const protocol = ProtocolFile.at(join(options.home, "a2a"));
92
+ const broadcast = (event) => {
93
+ if (sseClients.size === 0) return;
94
+ const frame = `event: ${event.kind}\ndata: ${JSON.stringify(event)}\n\n`;
95
+ for (const res of sseClients) try {
96
+ res.write(frame);
97
+ } catch {
98
+ sseClients.delete(res);
99
+ }
100
+ };
101
+ const unsubscribe = client.subscribe(broadcast);
102
+ /** Direct send through the peer (the settings' debug "send as myself"), broadcast as `outbound` to every SSE client. */
103
+ const sendDirect = async (fp, body) => {
104
+ const result = await sendAndArchive(client, fp, body);
105
+ broadcast({
106
+ kind: "outbound",
107
+ fp,
108
+ entry: result.entry
109
+ });
110
+ return result;
111
+ };
112
+ /** The friend row as the browser sees it: the peer's record + the plugin's tier + pending draft count. */
113
+ const friendRow = (friend, sessions) => {
114
+ const fp = friend["fp"];
115
+ const tier = sessions?.tierOf(fp) ?? options.settings().defaultTier;
116
+ const explicit = sessions?.tierStored(fp) !== void 0;
117
+ const drafts = sessions?.drafts.count(fp) ?? 0;
118
+ return {
119
+ ...friend,
120
+ tier,
121
+ ...explicit ? { tierExplicit: true } : {},
122
+ ...drafts > 0 ? { drafts } : {}
123
+ };
124
+ };
125
+ const state = async () => {
126
+ const status = client.status();
127
+ let identity = null;
128
+ let friends = [];
129
+ let pending = [];
130
+ let error;
131
+ const sessions = options.sessions();
132
+ try {
133
+ const id = await client.identity();
134
+ if (id !== void 0) {
135
+ identity = {
136
+ fp: id.fp,
137
+ name: id.name,
138
+ cardUri: id.cardUri,
139
+ ...id.createdAt === void 0 ? {} : { createdAt: id.createdAt }
140
+ };
141
+ const [f, p] = await Promise.all([client.friends.list(), client.friends.pending()]);
142
+ pending = [...p];
143
+ let online = {};
144
+ if (f.length > 0) try {
145
+ online = await client.presence(f.map((x) => x.fp));
146
+ } catch {}
147
+ friends = f.map((x) => friendRow(online[x.fp] === void 0 ? x : {
148
+ ...x,
149
+ online: online[x.fp]
150
+ }, sessions));
151
+ }
152
+ } catch (e) {
153
+ error = e instanceof Error ? e.message : String(e);
154
+ }
155
+ const settings = options.settings();
156
+ const alterState = sessions?.latest();
157
+ return {
158
+ backend: client.backend,
159
+ status,
160
+ home: options.home,
161
+ settingsNamespace: options.settingsNamespace,
162
+ identity,
163
+ friends,
164
+ pending,
165
+ drafts: sessions?.drafts.list() ?? [],
166
+ alter: {
167
+ sessionId: sessions?.sessionId() ?? null,
168
+ status: alterState?.status ?? "idle",
169
+ defaultTier: settings.defaultTier,
170
+ autoReplyPerHour: settings.autoReplyPerHour,
171
+ directSend: settings.directSend,
172
+ protocolPath: protocol.path,
173
+ protocolExists: protocol.exists(),
174
+ legacyFriendSessions: sessions?.legacyFriendSessions() ?? {}
175
+ },
176
+ ...error === void 0 ? {} : { error }
177
+ };
178
+ };
179
+ const handle = async (route, method, body) => {
180
+ switch (route) {
181
+ case "state": return {
182
+ status: 200,
183
+ body: await state()
184
+ };
185
+ case "identity.create": {
186
+ const name = text(body["name"]);
187
+ if (name === void 0) return bad("name must not be empty");
188
+ return {
189
+ status: 200,
190
+ body: { identity: await client.createIdentity(name) }
191
+ };
192
+ }
193
+ case "card.parse": {
194
+ const uri = text(body["uri"]);
195
+ if (uri === void 0) return bad("uri must not be empty");
196
+ return {
197
+ status: 200,
198
+ body: await client.parseCard(uri)
199
+ };
200
+ }
201
+ case "friends.add": {
202
+ const cardUri = text(body["card_uri"]);
203
+ if (cardUri === void 0) return bad("card_uri must not be empty");
204
+ const friend = await client.friends.add(cardUri, text(body["note"]));
205
+ options.log("info", `friend request sent to ${friend.name} (${friend.fp}) via settings/command`);
206
+ return {
207
+ status: 200,
208
+ body: { friend }
209
+ };
210
+ }
211
+ case "friends.accept": {
212
+ const id = text(body["id"]);
213
+ if (id === void 0) return bad("id must not be empty");
214
+ const friend = await client.friends.accept(id, text(body["note"]));
215
+ options.sessions()?.noteFriend(friend);
216
+ return {
217
+ status: 200,
218
+ body: { friend: friendRow(friend, options.sessions()) }
219
+ };
220
+ }
221
+ case "friends.reject": {
222
+ const id = text(body["id"]);
223
+ if (id === void 0) return bad("id must not be empty");
224
+ await client.friends.reject(id);
225
+ return {
226
+ status: 200,
227
+ body: { ok: true }
228
+ };
229
+ }
230
+ case "friends.set": {
231
+ const fp = text(body["fp"]);
232
+ if (fp === void 0) return bad("fp must not be empty");
233
+ const note = text(body["note"]);
234
+ const protocolOverride = typeof body["protocol"] === "string" ? body["protocol"] : void 0;
235
+ const tierValue = body["tier"];
236
+ if (tierValue !== void 0 && tierValue !== null && tierValue !== "" && !isReplyTier(tierValue)) return bad("tier must be notify | draft | auto (empty = default)");
237
+ const sessions = options.sessions();
238
+ let friend = (await client.friends.list()).find((f) => f.fp === fp);
239
+ if (friend === void 0) return {
240
+ status: 404,
241
+ body: { error: {
242
+ code: -32002,
243
+ message: "not a friend"
244
+ } }
245
+ };
246
+ if (note !== void 0 || protocolOverride !== void 0) {
247
+ friend = await client.friends.set(fp, {
248
+ ...note === void 0 ? {} : { remark: note },
249
+ ...protocolOverride === void 0 ? {} : { protocol: protocolOverride }
250
+ });
251
+ sessions?.noteFriend(friend);
252
+ }
253
+ if (tierValue !== void 0 && sessions !== void 0) await sessions.setTier(fp, isReplyTier(tierValue) ? tierValue : void 0);
254
+ options.log("info", `friends.set ${fp}: ${[
255
+ note !== void 0 ? "note" : "",
256
+ protocolOverride !== void 0 ? "protocol" : "",
257
+ tierValue !== void 0 ? `tier=${String(tierValue)}` : ""
258
+ ].filter((s) => s !== "").join(" ")}`);
259
+ return {
260
+ status: 200,
261
+ body: { friend: friendRow(friend, sessions) }
262
+ };
263
+ }
264
+ case "friends.card": {
265
+ const fp = text(body["fp"]);
266
+ if (fp === void 0) return bad("fp must not be empty");
267
+ return {
268
+ status: 200,
269
+ body: await client.friends.card(fp)
270
+ };
271
+ }
272
+ case "conversation.markRead": {
273
+ const fp = text(body["fp"]);
274
+ if (fp === void 0) return bad("fp required");
275
+ await client.markRead(fp, typeof body["seq"] === "number" ? body["seq"] : 0);
276
+ await options.sessions()?.markRead(fp);
277
+ return {
278
+ status: 200,
279
+ body: { ok: true }
280
+ };
281
+ }
282
+ case "conversation.get": {
283
+ const fp = text(body["fp"]);
284
+ if (fp === void 0) return bad("fp must not be empty");
285
+ const since = num(body["since"]);
286
+ const limit = num(body["limit"]);
287
+ return {
288
+ status: 200,
289
+ body: await client.conversation(fp, {
290
+ ...since === void 0 ? {} : { since },
291
+ ...limit === void 0 ? {} : { limit }
292
+ })
293
+ };
294
+ }
295
+ case "message.send": {
296
+ const fp = text(body["fp"]);
297
+ if (fp === void 0) return bad("fp must not be empty");
298
+ const msg = typeof body["body"] === "string" ? body["body"].replace(/\s+$/, "") : "";
299
+ if (msg === "") return bad("body must not be empty");
300
+ const result = await sendDirect(fp, msg);
301
+ options.log("info", `direct send to ${fp} (debug): ${result.receipt.id} (${result.receipt.status}, seq ${result.receipt.seq ?? "?"})`);
302
+ return {
303
+ status: 200,
304
+ body: result
305
+ };
306
+ }
307
+ case "message.typing": {
308
+ const fp = text(body["fp"]);
309
+ if (fp === void 0) return bad("fp must not be empty");
310
+ await client.typing(fp, body["on"] !== false && body["on"] !== "false" && body["on"] !== 0);
311
+ return {
312
+ status: 200,
313
+ body: { ok: true }
314
+ };
315
+ }
316
+ case "presence": {
317
+ const fps = fpList(body["fps"]);
318
+ return {
319
+ status: 200,
320
+ body: { online: await client.presence(fps) }
321
+ };
322
+ }
323
+ case "alter.instruct": {
324
+ const instruction = typeof body["text"] === "string" ? body["text"].replace(/\s+$/, "") : "";
325
+ if (instruction === "") return bad("text must not be empty");
326
+ const sessions = options.sessions();
327
+ if (sessions === void 0) return {
328
+ status: 503,
329
+ body: { error: {
330
+ code: -32603,
331
+ message: "sessions plugin not mounted"
332
+ } }
333
+ };
334
+ const result = await sessions.instruct(instruction);
335
+ options.log("info", `owner → alter: session ${result.sessionId}, message ${result.messageId}`);
336
+ return {
337
+ status: 200,
338
+ body: {
339
+ ...result,
340
+ state: sessions.latest() ?? null
341
+ }
342
+ };
343
+ }
344
+ case "session.latest": return {
345
+ status: 200,
346
+ body: { state: options.sessions()?.latest() ?? null }
347
+ };
348
+ case "session.history": {
349
+ const sessions = options.sessions();
350
+ const limit = num(body["limit"]);
351
+ if (sessions === void 0) return {
352
+ status: 200,
353
+ body: {
354
+ sessionId: null,
355
+ status: "idle",
356
+ chat: {
357
+ items: [],
358
+ running: false,
359
+ seq: 0
360
+ }
361
+ }
362
+ };
363
+ const h = sessions.history(limit);
364
+ return {
365
+ status: 200,
366
+ body: {
367
+ sessionId: h.sessionId ?? null,
368
+ status: h.status,
369
+ chat: h.chat
370
+ }
371
+ };
372
+ }
373
+ case "drafts.list": {
374
+ const fp = text(body["fp"]);
375
+ return {
376
+ status: 200,
377
+ body: { drafts: options.sessions()?.drafts.list(fp) ?? [] }
378
+ };
379
+ }
380
+ case "drafts.decide": {
381
+ const id = text(body["id"]);
382
+ if (id === void 0) return bad("id must not be empty");
383
+ const action = text(body["action"]);
384
+ const sessions = options.sessions();
385
+ if (sessions === void 0) return {
386
+ status: 503,
387
+ body: { error: {
388
+ code: -32603,
389
+ message: "sessions plugin not mounted"
390
+ } }
391
+ };
392
+ if (action === "approve") {
393
+ const edited = typeof body["body"] === "string" ? body["body"] : void 0;
394
+ return {
395
+ status: 200,
396
+ body: {
397
+ ok: true,
398
+ ...await sessions.decideDraft(id, {
399
+ action: "approve",
400
+ ...edited === void 0 ? {} : { body: edited }
401
+ })
402
+ }
403
+ };
404
+ }
405
+ if (action === "reject") return {
406
+ status: 200,
407
+ body: {
408
+ ok: true,
409
+ ...await sessions.decideDraft(id, { action: "reject" })
410
+ }
411
+ };
412
+ if (action === "revise") {
413
+ const feedback = text(body["feedback"]);
414
+ if (feedback === void 0) return bad("feedback must not be empty");
415
+ return {
416
+ status: 200,
417
+ body: {
418
+ ok: true,
419
+ ...await sessions.decideDraft(id, {
420
+ action: "revise",
421
+ feedback
422
+ })
423
+ }
424
+ };
425
+ }
426
+ return bad("action must be approve | reject | revise");
427
+ }
428
+ case "protocol.get": return {
429
+ status: 200,
430
+ body: {
431
+ text: protocol.read(),
432
+ path: protocol.path,
433
+ exists: protocol.exists()
434
+ }
435
+ };
436
+ case "protocol.set":
437
+ if (typeof body["text"] !== "string") return bad("text must be a string");
438
+ protocol.write(body["text"]);
439
+ options.log("info", `diplomacy protocol saved (${body["text"].length} chars) → ${protocol.path}`);
440
+ return {
441
+ status: 200,
442
+ body: {
443
+ ok: true,
444
+ text: protocol.read(),
445
+ path: protocol.path
446
+ }
447
+ };
448
+ default: return {
449
+ status: 404,
450
+ body: { error: {
451
+ code: -32601,
452
+ message: `unknown route ${method} ${route}`
453
+ } }
454
+ };
455
+ }
456
+ };
457
+ const handler = (async (req, res) => {
458
+ const url = new URL(req.url ?? "/", "http://localhost");
459
+ const route = url.pathname.startsWith("/soulmirror/api/") ? url.pathname.slice(16) : "";
460
+ if (route === "events" && req.method === "GET") {
461
+ res.writeHead(200, {
462
+ "content-type": "text/event-stream",
463
+ "cache-control": "no-store",
464
+ connection: "keep-alive"
465
+ });
466
+ res.write(`event: status\ndata: ${JSON.stringify({
467
+ kind: "status",
468
+ status: client.status()
469
+ })}\n\n`);
470
+ sseClients.add(res);
471
+ const keepAlive = setInterval(() => {
472
+ try {
473
+ res.write(": keep-alive\n\n");
474
+ } catch {
475
+ clearInterval(keepAlive);
476
+ }
477
+ }, 25e3);
478
+ keepAlive.unref?.();
479
+ req.on("close", () => {
480
+ clearInterval(keepAlive);
481
+ sseClients.delete(res);
482
+ });
483
+ return;
484
+ }
485
+ if (req.method !== "POST" && !(req.method === "GET" && QUERY_ROUTES.has(route))) {
486
+ send(res, 405, { error: {
487
+ code: -32600,
488
+ message: "use POST with application/json (GET only for state / conversation.get / presence / session.latest / session.history / drafts.list / protocol.get / events)"
489
+ } });
490
+ return;
491
+ }
492
+ try {
493
+ const body = req.method === "POST" ? await readJson(req) : Object.fromEntries(url.searchParams.entries());
494
+ const result = await handle(route, req.method ?? "GET", body);
495
+ send(res, result.status, result.body);
496
+ } catch (error) {
497
+ options.log("warn", `api ${route} failed: ${String(error)}`);
498
+ send(res, error instanceof NetworkError ? 400 : 500, errorBody(error));
499
+ }
500
+ });
501
+ handler.broadcast = broadcast;
502
+ handler.dispose = () => {
503
+ unsubscribe();
504
+ for (const res of sseClients) try {
505
+ res.end();
506
+ } catch {}
507
+ sseClients.clear();
508
+ };
509
+ return handler;
510
+ }
511
+ /** Mount the API on `ctx.webServer` when (and for as long as) that service exists. */
512
+ function mountApi(ctx, options) {
513
+ ctx.inject(["webServer"], (wctx) => {
514
+ const handler = createApiHandler(options);
515
+ const dispose = wctx.webServer.register({
516
+ kind: "prefix",
517
+ path: API_PREFIX.slice(0, -1),
518
+ handler
519
+ });
520
+ options.log("info", `browser API mounted at ${API_PREFIX} (port ${wctx.webServer.port})`);
521
+ wctx.inject(["soulmirrorSessions"], (sctx) => {
522
+ const off = sctx.soulmirrorSessions.on((event) => {
523
+ handler.broadcast(event);
524
+ });
525
+ sctx.effect(() => off, "soulmirror: alter events → SSE");
526
+ });
527
+ wctx.effect(() => () => {
528
+ dispose();
529
+ handler.dispose();
530
+ }, "soulmirror: browser API");
531
+ });
532
+ }
533
+ //#endregion
534
+ //#region src/network/fake.ts
535
+ const fp$1 = (s) => s;
536
+ const mid$1 = () => `a2a-${crypto.randomUUID()}`;
537
+ const FAKE_FRIENDS = [{
538
+ fp: fp$1("fp-alice-1f2e3d4c5b6a7988"),
539
+ name: "college friend",
540
+ cardName: "Alice",
541
+ remark: "college friend",
542
+ online: true,
543
+ unread: 0,
544
+ count: 0
545
+ }, {
546
+ fp: fp$1("fp-bob-9a8b7c6d5e4f3021"),
547
+ name: "Bob",
548
+ cardName: "Bob",
549
+ online: false,
550
+ unread: 0,
551
+ count: 0
552
+ }];
553
+ const FAKE_PENDING = [{
554
+ id: "req-carol-1",
555
+ fp: fp$1("fp-carol-5566778899aabbcc"),
556
+ name: "Carol",
557
+ greeting: "Hi, Alice gave me your card."
558
+ }];
559
+ function createFakeNetworkClient(options = {}) {
560
+ const listeners = /* @__PURE__ */ new Set();
561
+ const friends = new Map(FAKE_FRIENDS.map((f) => [f.fp, f]));
562
+ const pending = new Map(FAKE_PENDING.map((p) => [p.id, p]));
563
+ const conversations = /* @__PURE__ */ new Map();
564
+ let identity = options.noIdentity === true ? void 0 : {
565
+ fp: fp$1("fp-me-0000aaaabbbbcccc"),
566
+ name: "dsh tester",
567
+ cardUri: "soulmirror://card?v=1&pk=FAKE&xpk=FAKE&name=dsh%20tester"
568
+ };
569
+ let firstInboundFired = false;
570
+ let disposed = false;
571
+ const status = {
572
+ backend: "fake",
573
+ state: "ready",
574
+ restarts: 0
575
+ };
576
+ const emit = (event) => {
577
+ for (const l of listeners) try {
578
+ l(event);
579
+ } catch {}
580
+ };
581
+ const archive = (peer, entry) => {
582
+ const list = conversations.get(peer) ?? [];
583
+ const full = {
584
+ seq: list.length + 1,
585
+ ...entry
586
+ };
587
+ list.push(full);
588
+ conversations.set(peer, list);
589
+ const friend = friends.get(peer);
590
+ if (friend !== void 0) friends.set(peer, {
591
+ ...friend,
592
+ count: list.length,
593
+ unread: entry.dir === "in" ? friend.unread + 1 : friend.unread,
594
+ lastTs: entry.ts,
595
+ lastBody: entry.body
596
+ });
597
+ return full;
598
+ };
599
+ const deliver = (from, body, auto) => {
600
+ if (disposed) return;
601
+ const friend = friends.get(from);
602
+ const id = mid$1();
603
+ const ts = Date.now();
604
+ const entry = archive(from, {
605
+ dir: "in",
606
+ id,
607
+ body,
608
+ ts,
609
+ ...auto ? { auto } : {}
610
+ });
611
+ emit({
612
+ kind: "message",
613
+ message: {
614
+ id,
615
+ from,
616
+ name: friend?.name ?? from,
617
+ body,
618
+ ts,
619
+ seq: entry.seq,
620
+ ...auto ? { auto } : {}
621
+ }
622
+ });
623
+ };
624
+ const requireIdentity = () => {
625
+ if (identity === void 0) throw new NetworkError("no identity yet (identity.create first)", NetworkErrorCode.noIdentity);
626
+ return identity;
627
+ };
628
+ const friendFromCard = (uri, remark) => ({
629
+ fp: fp$1(`fp-${uri.slice(-8).replace(/[^a-z0-9]/gi, "") || "card"}`),
630
+ name: remark ?? `card ${uri.slice(-4)}`,
631
+ cardName: `card ${uri.slice(-4)}`,
632
+ ...remark === void 0 ? {} : { remark },
633
+ unread: 0,
634
+ count: 0
635
+ });
636
+ return {
637
+ backend: "fake",
638
+ status: () => status,
639
+ identity: () => Promise.resolve(identity),
640
+ createIdentity: (name) => {
641
+ if (identity !== void 0) return Promise.reject(new NetworkError("identity already exists", NetworkErrorCode.identityExists));
642
+ identity = {
643
+ fp: fp$1("fp-me-0000aaaabbbbcccc"),
644
+ name,
645
+ cardUri: `soulmirror://card?v=1&pk=FAKE&xpk=FAKE&name=${encodeURIComponent(name)}`
646
+ };
647
+ return Promise.resolve(identity);
648
+ },
649
+ card: () => Promise.resolve(requireIdentity().cardUri),
650
+ parseCard: (uri) => {
651
+ if (!uri.startsWith("soulmirror://card")) return Promise.reject(new NetworkError("invalid card link", NetworkErrorCode.badCard));
652
+ const f = friendFromCard(uri);
653
+ return Promise.resolve({
654
+ fp: f.fp,
655
+ name: f.cardName ?? f.name,
656
+ uri
657
+ });
658
+ },
659
+ friends: {
660
+ list: () => Promise.resolve([...friends.values()]),
661
+ pending: () => Promise.resolve([...pending.values()]),
662
+ add: (uri, remark) => {
663
+ requireIdentity();
664
+ if (!uri.startsWith("soulmirror://card")) return Promise.reject(new NetworkError("invalid card link", NetworkErrorCode.badCard));
665
+ const f = friendFromCard(uri, remark);
666
+ friends.set(f.fp, f);
667
+ setTimeout(() => {
668
+ if (!disposed) emit({
669
+ kind: "friend_accept",
670
+ friend: f
671
+ });
672
+ }, 300);
673
+ return Promise.resolve(f);
674
+ },
675
+ accept: (requestId, note) => {
676
+ const req = pending.get(requestId);
677
+ if (req === void 0) return Promise.reject(new NetworkError("no such pending request", NetworkErrorCode.notFound));
678
+ pending.delete(requestId);
679
+ const f = {
680
+ fp: req.fp,
681
+ name: note ?? req.name,
682
+ cardName: req.name,
683
+ ...note === void 0 ? {} : { remark: note },
684
+ unread: 0,
685
+ count: 0
686
+ };
687
+ friends.set(f.fp, f);
688
+ return Promise.resolve(f);
689
+ },
690
+ reject: (requestId) => {
691
+ if (!pending.delete(requestId)) return Promise.reject(new NetworkError("no such pending request", NetworkErrorCode.notFound));
692
+ return Promise.resolve();
693
+ },
694
+ set: (id, patch) => {
695
+ const cur = friends.get(id);
696
+ if (cur === void 0) return Promise.reject(new NetworkError("not a friend", NetworkErrorCode.notFriend));
697
+ const { protocol: _old, ...rest } = cur;
698
+ const protocol = patch.protocol === void 0 ? cur.protocol : patch.protocol.trim() === "" ? void 0 : patch.protocol;
699
+ const next = {
700
+ ...rest,
701
+ ...patch.remark === void 0 ? {} : {
702
+ remark: patch.remark,
703
+ name: patch.remark
704
+ },
705
+ ...protocol === void 0 ? {} : { protocol }
706
+ };
707
+ friends.set(id, next);
708
+ return Promise.resolve(next);
709
+ },
710
+ remove: (id) => {
711
+ if (!friends.delete(id)) return Promise.reject(new NetworkError("not a friend", NetworkErrorCode.notFriend));
712
+ return Promise.resolve();
713
+ },
714
+ card: (id) => {
715
+ const cur = friends.get(id);
716
+ if (cur === void 0) return Promise.reject(new NetworkError("not a friend", NetworkErrorCode.notFriend));
717
+ return Promise.resolve({
718
+ fp: cur.fp,
719
+ name: cur.cardName ?? cur.name,
720
+ uri: `soulmirror://card?v=1&pk=FAKE-${cur.fp}&xpk=FAKE&name=${encodeURIComponent(cur.cardName ?? cur.name)}`
721
+ });
722
+ }
723
+ },
724
+ send: (to, body, options) => {
725
+ requireIdentity();
726
+ if (!friends.has(to)) return Promise.reject(new NetworkError("not a friend (friends.add first)", NetworkErrorCode.notFriend));
727
+ const id = mid$1();
728
+ const entry = archive(to, {
729
+ dir: "out",
730
+ id,
731
+ body,
732
+ ts: Date.now(),
733
+ status: "sent",
734
+ ...options?.auto === true ? { auto: true } : {}
735
+ });
736
+ setTimeout(() => {
737
+ deliver(to, `(auto-reply) got it: "${body.slice(0, 40)}"`, true);
738
+ }, 600);
739
+ return Promise.resolve({
740
+ id,
741
+ seq: entry.seq,
742
+ status: "sent"
743
+ });
744
+ },
745
+ typing: (to, on) => {
746
+ if (!friends.has(to)) return Promise.reject(new NetworkError("not a friend", NetworkErrorCode.notFriend));
747
+ return Promise.resolve();
748
+ },
749
+ conversation: (target, opts = {}) => {
750
+ let entries = conversations.get(target) ?? [];
751
+ if (opts.since !== void 0) entries = entries.filter((e) => e.seq > opts.since);
752
+ if (opts.limit !== void 0 && opts.limit > 0 && entries.length > opts.limit) entries = entries.slice(-opts.limit);
753
+ return Promise.resolve({
754
+ entries,
755
+ typing: false
756
+ });
757
+ },
758
+ markRead: (target) => {
759
+ const cur = friends.get(target);
760
+ if (cur === void 0) return Promise.reject(new NetworkError("not a friend", NetworkErrorCode.notFriend));
761
+ friends.set(target, {
762
+ ...cur,
763
+ unread: 0
764
+ });
765
+ return Promise.resolve();
766
+ },
767
+ presence: (fps) => Promise.resolve(Object.fromEntries(fps.map((f) => [f, friends.get(f)?.online ?? false]))),
768
+ subscribe: (listener) => {
769
+ listeners.add(listener);
770
+ if (!firstInboundFired) {
771
+ firstInboundFired = true;
772
+ const delay = options.firstInboundDelayMs ?? 1500;
773
+ if (delay >= 0) {
774
+ const alice = FAKE_FRIENDS[0];
775
+ setTimeout(() => {
776
+ deliver(alice.fp, "Hey, are you around? Hiking this weekend - bring your alter ego too!");
777
+ }, delay);
778
+ }
779
+ }
780
+ return () => {
781
+ listeners.delete(listener);
782
+ };
783
+ },
784
+ dispose: () => {
785
+ disposed = true;
786
+ listeners.clear();
787
+ return Promise.resolve();
788
+ },
789
+ debug: { inject: (from, body) => {
790
+ deliver(from, body);
791
+ } }
792
+ };
793
+ }
794
+ //#endregion
795
+ //#region src/network/jsonrpc.ts
796
+ /**
797
+ * Minimal line-delimited JSON-RPC 2.0 endpoint over a pair of Node streams.
798
+ *
799
+ * Used by the `soulnet` backend (stdin/stdout of the peer process) and by the
800
+ * unit tests (PassThrough streams standing in for a peer). One JSON object per
801
+ * line; requests carry an incrementing numeric id; frames without an id are
802
+ * notifications and are handed to `onNotification`.
803
+ */
804
+ /** A JSON-RPC error response, or a transport failure (code -32099 family). */
805
+ var JsonRpcError = class extends Error {
806
+ code;
807
+ data;
808
+ name = "JsonRpcError";
809
+ constructor(message, code, data) {
810
+ super(message);
811
+ this.code = code;
812
+ this.data = data;
813
+ }
814
+ };
815
+ /** Transport-level code: the endpoint closed before the response arrived. */
816
+ const JSONRPC_CLOSED = -32099;
817
+ /** Transport-level code: no response within the request timeout. */
818
+ const JSONRPC_TIMEOUT = -32098;
819
+ var JsonRpcEndpoint = class {
820
+ input;
821
+ output;
822
+ options;
823
+ pending = /* @__PURE__ */ new Map();
824
+ reader;
825
+ nextId = 1;
826
+ closed = false;
827
+ constructor(input, output, options = {}) {
828
+ this.input = input;
829
+ this.output = output;
830
+ this.options = options;
831
+ this.reader = createInterface({
832
+ input,
833
+ crlfDelay: Infinity
834
+ });
835
+ this.reader.on("line", (line) => {
836
+ this.handleLine(line);
837
+ });
838
+ this.reader.on("close", () => {
839
+ this.close();
840
+ });
841
+ input.on("error", (error) => {
842
+ this.close(error);
843
+ });
844
+ output.on("error", (error) => {
845
+ this.close(error);
846
+ });
847
+ }
848
+ get isClosed() {
849
+ return this.closed;
850
+ }
851
+ /** Send a request and await its result; rejects with {@link JsonRpcError}. */
852
+ request(method, params, options = {}) {
853
+ if (this.closed) return Promise.reject(new JsonRpcError(`${method}: endpoint is closed`, JSONRPC_CLOSED));
854
+ const id = this.nextId++;
855
+ return new Promise((resolve, reject) => {
856
+ const timeoutMs = options.timeoutMs ?? this.options.timeoutMs ?? 0;
857
+ const timer = timeoutMs > 0 ? setTimeout(() => {
858
+ this.pending.delete(id);
859
+ reject(new JsonRpcError(`${method}: no response within ${timeoutMs} ms`, JSONRPC_TIMEOUT));
860
+ }, timeoutMs) : void 0;
861
+ timer?.unref?.();
862
+ const settle = (fn) => {
863
+ if (timer !== void 0) clearTimeout(timer);
864
+ this.pending.delete(id);
865
+ fn();
866
+ };
867
+ this.pending.set(id, {
868
+ method,
869
+ timer,
870
+ resolve: (value) => {
871
+ settle(() => {
872
+ resolve(value);
873
+ });
874
+ },
875
+ reject: (error) => {
876
+ settle(() => {
877
+ reject(error);
878
+ });
879
+ }
880
+ });
881
+ if (options.signal !== void 0) {
882
+ const onAbort = () => {
883
+ this.pending.get(id)?.reject(new JsonRpcError(`${method}: aborted`, JSONRPC_CLOSED));
884
+ };
885
+ if (options.signal.aborted) onAbort();
886
+ else options.signal.addEventListener("abort", onAbort, { once: true });
887
+ }
888
+ if (!this.write({
889
+ jsonrpc: "2.0",
890
+ id,
891
+ method,
892
+ ...params === void 0 ? {} : { params }
893
+ })) this.pending.get(id)?.reject(new JsonRpcError(`${method}: endpoint is closed`, JSONRPC_CLOSED));
894
+ });
895
+ }
896
+ /** Fire-and-forget request without an id (no response expected). */
897
+ notify(method, params) {
898
+ this.write({
899
+ jsonrpc: "2.0",
900
+ method,
901
+ ...params === void 0 ? {} : { params }
902
+ });
903
+ }
904
+ /** Reject every pending request and stop reading. Idempotent. */
905
+ close(error) {
906
+ if (this.closed) return;
907
+ this.closed = true;
908
+ this.reader.close();
909
+ const reason = new JsonRpcError(error === void 0 ? "endpoint closed" : `endpoint closed: ${error.message}`, JSONRPC_CLOSED);
910
+ for (const entry of [...this.pending.values()]) entry.reject(reason);
911
+ this.pending.clear();
912
+ this.options.onClose?.(error);
913
+ }
914
+ write(frame) {
915
+ if (this.closed) return false;
916
+ try {
917
+ this.output.write(`${JSON.stringify(frame)}\n`);
918
+ return true;
919
+ } catch (error) {
920
+ this.close(error instanceof Error ? error : new Error(String(error)));
921
+ return false;
922
+ }
923
+ }
924
+ handleLine(line) {
925
+ const trimmed = line.trim();
926
+ if (trimmed === "") return;
927
+ let frame;
928
+ try {
929
+ frame = JSON.parse(trimmed);
930
+ } catch (error) {
931
+ this.options.onProtocolError?.(error instanceof Error ? error : new Error(String(error)), line);
932
+ return;
933
+ }
934
+ if (typeof frame !== "object" || frame === null) {
935
+ this.options.onProtocolError?.(/* @__PURE__ */ new Error("frame is not an object"), line);
936
+ return;
937
+ }
938
+ const f = frame;
939
+ if (typeof f.method === "string" && (f.id === void 0 || f.id === null)) {
940
+ this.options.onNotification?.({
941
+ method: f.method,
942
+ params: f.params
943
+ });
944
+ return;
945
+ }
946
+ if (typeof f.id !== "number") {
947
+ this.options.onProtocolError?.(/* @__PURE__ */ new Error("response without a numeric id"), line);
948
+ return;
949
+ }
950
+ const entry = this.pending.get(f.id);
951
+ if (entry === void 0) {
952
+ this.options.onProtocolError?.(/* @__PURE__ */ new Error(`response for unknown request id ${f.id}`), line);
953
+ return;
954
+ }
955
+ if (f.error !== void 0 && f.error !== null) {
956
+ const e = f.error;
957
+ entry.reject(new JsonRpcError(typeof e.message === "string" ? e.message : `${entry.method} failed`, typeof e.code === "number" ? e.code : -32603, e.data));
958
+ return;
959
+ }
960
+ entry.resolve(f.result);
961
+ }
962
+ };
963
+ //#endregion
964
+ //#region src/network/soulnet.ts
965
+ /**
966
+ * `soulnet` backend: spawns the soulnet light peer (Go, ../cmd/soulnet) and
967
+ * drives it over line-delimited JSON-RPC 2.0 on stdio (protocol: cmd/soulnet/
968
+ * README.md, `initialize.protocol === "soulnet/1"`).
969
+ *
970
+ * - request/response by id, notifications → `subscribe` listeners;
971
+ * - the process is restarted with exponential backoff when it dies;
972
+ * - `dispose()` sends `shutdown`, then kills the process if it lingers;
973
+ * - calls issued while the peer is (re)starting wait for it up to the request
974
+ * timeout instead of failing immediately.
975
+ *
976
+ * Binary lookup (`resolveSoulnetBinary` / `locateSoulnetBinary`), in order:
977
+ * 1. the explicit `peerBinary` setting (absolute path, or a bare name looked up on PATH);
978
+ * 2. the platform package `soulnet-peer-<os>-<arch>` installed next to
979
+ * this plugin as an optional dependency (`require.resolve('<pkg>/package.json')`
980
+ * from this file and from its realpath, so pnpm's symlinked virtual store and
981
+ * the hoisted layout both work) -> `<pkg>/bin/soulnet[.exe]`;
982
+ * 3. `soulnet` on PATH;
983
+ * 4. `<plugin dir>/bin/soulnet[.exe]` (a hand-dropped binary for development).
984
+ * The winner and its source are reported in `BackendStatus.binary` / `binarySource`.
985
+ */
986
+ const DEFAULT_RELAY = "https://relay.startupworld.cn";
987
+ const SOULNET_PROTOCOL = "soulnet/1";
988
+ /** Default home: `$SOULNET_HOME`, else `~/.soulnet` (same rule as the binary itself). */
989
+ function defaultSoulnetHome(env = process.env) {
990
+ const fromEnv = env["SOULNET_HOME"];
991
+ if (fromEnv !== void 0 && fromEnv !== "") return fromEnv;
992
+ return join(homedir(), ".soulnet");
993
+ }
994
+ function isExecutable(path) {
995
+ try {
996
+ accessSync(path, constants.F_OK);
997
+ return true;
998
+ } catch {
999
+ return false;
1000
+ }
1001
+ }
1002
+ /** npm scope of the platform packages that ship the binary. */
1003
+ /** Prefix of the per-platform binary packages on npm (`soulnet-peer-<os>-<arch>`). */
1004
+ const PLATFORM_PACKAGE_PREFIX = "soulnet-peer-";
1005
+ /** The `<os>-<arch>` pairs a platform package exists for (must match dsh/packages/soulnet-*). */
1006
+ const PLATFORM_PACKAGE_TARGETS = [
1007
+ "win32-x64",
1008
+ "darwin-arm64",
1009
+ "darwin-x64",
1010
+ "linux-x64",
1011
+ "linux-arm64"
1012
+ ];
1013
+ /** `soulnet-peer-<os>-<arch>` for a supported pair, else `undefined`. */
1014
+ function platformPackageName(platform = process.platform, arch = process.arch) {
1015
+ const target = `${platform}-${arch}`;
1016
+ return PLATFORM_PACKAGE_TARGETS.includes(target) ? `${PLATFORM_PACKAGE_PREFIX}${target}` : void 0;
1017
+ }
1018
+ /**
1019
+ * Resolve an installed package's directory from this plugin's location: first
1020
+ * from this file's URL (dsh loads lib/index.js from the profile's node_modules,
1021
+ * hoisted or symlinked), then from its realpath (pnpm's isolated virtual store
1022
+ * keeps the optional dependency next to the REAL plugin directory).
1023
+ */
1024
+ function defaultResolvePackageDir(name) {
1025
+ const bases = [import.meta.url];
1026
+ try {
1027
+ const real = realpathSync(fileURLToPath(import.meta.url));
1028
+ const realUrl = pathToFileURL(real).href;
1029
+ if (realUrl !== import.meta.url) bases.push(realUrl);
1030
+ } catch {}
1031
+ for (const base of bases) try {
1032
+ return dirname(createRequire(base).resolve(`${name}/package.json`));
1033
+ } catch {}
1034
+ }
1035
+ function ensureExecutable(path, platform) {
1036
+ if (platform === "win32") return;
1037
+ try {
1038
+ accessSync(path, constants.X_OK);
1039
+ } catch {
1040
+ try {
1041
+ chmodSync(path, 493);
1042
+ } catch {}
1043
+ }
1044
+ }
1045
+ /**
1046
+ * Find the `soulnet` binary (order documented at the top of this file) and say
1047
+ * where it came from. `undefined` when nothing was found (the caller reports a
1048
+ * clear error).
1049
+ */
1050
+ function locateSoulnetBinary(explicit, env = process.env, platform = process.platform, options = {}) {
1051
+ const names = platform === "win32" ? ["soulnet.exe", "soulnet"] : ["soulnet"];
1052
+ if (explicit !== void 0 && explicit.trim() !== "") {
1053
+ const candidate = explicit.trim();
1054
+ if (isAbsolute(candidate) || candidate.includes("/") || candidate.includes("\\")) return {
1055
+ path: candidate,
1056
+ source: "setting"
1057
+ };
1058
+ for (const dir of (env["PATH"] ?? "").split(delimiter)) {
1059
+ if (dir === "") continue;
1060
+ const full = join(dir, candidate);
1061
+ if (isExecutable(full)) return {
1062
+ path: full,
1063
+ source: "setting"
1064
+ };
1065
+ if (platform === "win32" && !candidate.toLowerCase().endsWith(".exe") && isExecutable(`${full}.exe`)) return {
1066
+ path: `${full}.exe`,
1067
+ source: "setting"
1068
+ };
1069
+ }
1070
+ return {
1071
+ path: candidate,
1072
+ source: "setting"
1073
+ };
1074
+ }
1075
+ const pkgName = platformPackageName(platform, options.arch ?? process.arch);
1076
+ if (pkgName !== void 0) {
1077
+ const dir = (options.resolvePackageDir ?? defaultResolvePackageDir)(pkgName);
1078
+ if (dir !== void 0) for (const name of names) {
1079
+ const full = join(dir, "bin", name);
1080
+ if (isExecutable(full)) {
1081
+ ensureExecutable(full, platform);
1082
+ return {
1083
+ path: full,
1084
+ source: "platform-package"
1085
+ };
1086
+ }
1087
+ }
1088
+ }
1089
+ for (const dir of (env["PATH"] ?? "").split(delimiter)) {
1090
+ if (dir === "") continue;
1091
+ for (const name of names) {
1092
+ const full = join(dir, name);
1093
+ if (isExecutable(full)) return {
1094
+ path: full,
1095
+ source: "path"
1096
+ };
1097
+ }
1098
+ }
1099
+ const here = dirname(fileURLToPath(import.meta.url));
1100
+ for (const root of [join(here, ".."), join(here, "..", "..")]) for (const name of names) {
1101
+ const full = join(root, "bin", name);
1102
+ if (isExecutable(full)) return {
1103
+ path: full,
1104
+ source: "plugin-bin"
1105
+ };
1106
+ }
1107
+ }
1108
+ const fp = (s) => s;
1109
+ const mid = (s) => s;
1110
+ function toMs(value) {
1111
+ if (typeof value === "number") return value;
1112
+ if (typeof value === "string") {
1113
+ const parsed = Date.parse(value);
1114
+ if (!Number.isNaN(parsed)) return parsed;
1115
+ }
1116
+ return Date.now();
1117
+ }
1118
+ function str(value, fallback = "") {
1119
+ return typeof value === "string" ? value : fallback;
1120
+ }
1121
+ function shortFp(value) {
1122
+ return value.length > 12 ? `${value.slice(0, 12)}…` : value;
1123
+ }
1124
+ function friendFromWire(w) {
1125
+ const fingerprint = str(w.fingerprint);
1126
+ const note = str(w.note);
1127
+ const cardName = str(w.card?.name);
1128
+ const name = note !== "" ? note : cardName !== "" ? cardName : shortFp(fingerprint);
1129
+ return {
1130
+ fp: fp(fingerprint),
1131
+ name,
1132
+ ...note === "" ? {} : { remark: note },
1133
+ ...cardName === "" ? {} : { cardName },
1134
+ ...str(w.protocol) === "" ? {} : { protocol: str(w.protocol) },
1135
+ unread: typeof w.unread === "number" ? w.unread : 0,
1136
+ count: typeof w.count === "number" ? w.count : 0,
1137
+ ...w.last?.ts === void 0 ? {} : { lastTs: toMs(w.last.ts) },
1138
+ ...w.last?.body === void 0 ? {} : { lastBody: w.last.body },
1139
+ ...w.typing === true ? { typing: true } : {},
1140
+ ...w.added_at === void 0 ? {} : { addedAt: w.added_at }
1141
+ };
1142
+ }
1143
+ function pendingFromWire(w) {
1144
+ const peer = str(w.peer);
1145
+ const cardName = str(w.incoming?.card?.name);
1146
+ return {
1147
+ id: str(w.id),
1148
+ fp: fp(peer),
1149
+ name: cardName !== "" ? cardName : shortFp(peer),
1150
+ greeting: str(w.incoming?.body),
1151
+ ...w.created_at === void 0 ? {} : { createdAt: w.created_at }
1152
+ };
1153
+ }
1154
+ function entryFromWire(w) {
1155
+ return {
1156
+ seq: typeof w.seq === "number" ? w.seq : 0,
1157
+ dir: w.dir === "out" ? "out" : "in",
1158
+ id: mid(str(w.id)),
1159
+ body: str(w.body),
1160
+ ts: toMs(w.ts),
1161
+ ...w.type === void 0 || w.type === "" ? {} : { type: w.type },
1162
+ ...w.auto === true ? { auto: true } : {},
1163
+ ...w.status === void 0 || w.status === "" ? {} : { status: w.status },
1164
+ ...w.artifact_name === void 0 || w.artifact_name === "" ? {} : { artifactName: w.artifact_name }
1165
+ };
1166
+ }
1167
+ function toNetworkError(error, method) {
1168
+ if (error instanceof NetworkError) return error;
1169
+ if (error instanceof JsonRpcError) {
1170
+ const code = error.code === -32099 || error.code === -32098 ? NetworkErrorCode.peerUnavailable : error.code;
1171
+ return new NetworkError(error.message, code, error.data);
1172
+ }
1173
+ return new NetworkError(`${method}: ${String(error)}`, -32603);
1174
+ }
1175
+ /**
1176
+ * Create the soulnet-backed NetworkClient. The process is spawned lazily on
1177
+ * the first call or on `start()`; `dispose()` stops it.
1178
+ */
1179
+ function createSoulnetNetworkClient(options) {
1180
+ const log = options.logger ?? (() => {});
1181
+ const relay = options.relay !== void 0 && options.relay.trim() !== "" ? options.relay.trim() : DEFAULT_RELAY;
1182
+ const requestTimeoutMs = options.requestTimeoutMs ?? 3e4;
1183
+ const backoffInitial = options.backoff?.initialMs ?? 500;
1184
+ const backoffMax = options.backoff?.maxMs ?? 3e4;
1185
+ const backoffFactor = options.backoff?.factor ?? 2;
1186
+ const listeners = /* @__PURE__ */ new Set();
1187
+ let child;
1188
+ let endpoint;
1189
+ let disposed = false;
1190
+ let started = false;
1191
+ let restarts = 0;
1192
+ let backoffMs = backoffInitial;
1193
+ let restartTimer;
1194
+ let status = {
1195
+ backend: "soulnet",
1196
+ state: "stopped",
1197
+ restarts: 0,
1198
+ relay,
1199
+ home: options.home
1200
+ };
1201
+ let cachedCardUri;
1202
+ const friendNames = /* @__PURE__ */ new Map();
1203
+ let readyWaiters = [];
1204
+ const emit = (event) => {
1205
+ for (const listener of listeners) try {
1206
+ listener(event);
1207
+ } catch (error) {
1208
+ log("warn", `network listener failed: ${String(error)}`);
1209
+ }
1210
+ };
1211
+ const setStatus = (patch) => {
1212
+ status = {
1213
+ ...status,
1214
+ ...patch
1215
+ };
1216
+ emit({
1217
+ kind: "status",
1218
+ status
1219
+ });
1220
+ };
1221
+ const clearError = () => {
1222
+ const { lastError: _dropped, ...rest } = status;
1223
+ status = rest;
1224
+ };
1225
+ const handleNotification = (method, params) => {
1226
+ const p = typeof params === "object" && params !== null ? params : {};
1227
+ const peer = str(p.peer);
1228
+ switch (method) {
1229
+ case "message.received": {
1230
+ const m = p.message ?? {};
1231
+ const type = str(m.type, "text");
1232
+ const name = friendNames.get(peer) ?? shortFp(peer);
1233
+ const body = str(m.body) !== "" ? str(m.body) : type === "app_share" ? "[app share]" : "";
1234
+ emit({
1235
+ kind: "message",
1236
+ message: {
1237
+ id: mid(str(m.id)),
1238
+ from: fp(peer),
1239
+ name,
1240
+ body,
1241
+ ts: toMs(m.ts),
1242
+ ...typeof p.seq === "number" ? { seq: p.seq } : {},
1243
+ ...m.auto === true ? { auto: true } : {},
1244
+ ...type === "text" ? {} : { type },
1245
+ ...p.artifact_path === void 0 || p.artifact_path === "" ? {} : { artifactPath: p.artifact_path },
1246
+ ...m.artifact_name === void 0 || m.artifact_name === "" ? {} : { artifactName: m.artifact_name }
1247
+ }
1248
+ });
1249
+ return;
1250
+ }
1251
+ case "friend.request": {
1252
+ const m = p.message ?? {};
1253
+ const cardName = str(m.card?.name);
1254
+ emit({
1255
+ kind: "friend_request",
1256
+ request: {
1257
+ id: str(p.pending_id),
1258
+ fp: fp(peer),
1259
+ name: cardName !== "" ? cardName : shortFp(peer),
1260
+ greeting: str(m.body)
1261
+ }
1262
+ });
1263
+ return;
1264
+ }
1265
+ case "friend.accepted": {
1266
+ const friend = friendFromWire(p.friend ?? { fingerprint: peer });
1267
+ friendNames.set(friend.fp, friend.name);
1268
+ emit({
1269
+ kind: "friend_accept",
1270
+ friend
1271
+ });
1272
+ return;
1273
+ }
1274
+ case "typing":
1275
+ emit({
1276
+ kind: "typing",
1277
+ fp: fp(peer),
1278
+ on: p.on === true
1279
+ });
1280
+ return;
1281
+ case "presence.changed":
1282
+ emit({
1283
+ kind: "presence",
1284
+ fp: fp(peer),
1285
+ online: p.on === true
1286
+ });
1287
+ return;
1288
+ case "mission.update":
1289
+ case "artifact.ready":
1290
+ log("info", `soulnet notification ${method} from ${shortFp(peer)} (not handled in M1)`);
1291
+ return;
1292
+ default: log("warn", `unknown soulnet notification ${method}`);
1293
+ }
1294
+ };
1295
+ const failWaiters = (error) => {
1296
+ const waiters = readyWaiters;
1297
+ readyWaiters = [];
1298
+ for (const w of waiters) w.reject(error);
1299
+ };
1300
+ const scheduleRestart = (reason) => {
1301
+ if (disposed) return;
1302
+ restarts += 1;
1303
+ const delay = backoffMs;
1304
+ backoffMs = Math.min(backoffMax, Math.round(backoffMs * backoffFactor));
1305
+ setStatus({
1306
+ state: "restarting",
1307
+ restarts,
1308
+ lastError: reason
1309
+ });
1310
+ log("warn", `soulnet peer died (${reason}); restart #${restarts} in ${delay} ms`);
1311
+ restartTimer = setTimeout(() => {
1312
+ restartTimer = void 0;
1313
+ spawnPeer();
1314
+ }, delay);
1315
+ restartTimer.unref?.();
1316
+ };
1317
+ const spawnPeer = () => {
1318
+ if (disposed) return;
1319
+ const location = locateSoulnetBinary(options.peerBinary);
1320
+ if (location === void 0) {
1321
+ const message = `soulnet binary not found: the platform package ${platformPackageName() ?? `soulnet-peer-<os>-<arch> (none published for ${process.platform}-${process.arch})`} is not installed next to the plugin (reinstall with optional dependencies enabled), or set \`peerBinary\` in the SoulMirror network settings, put \`soulnet\` on PATH, or place it in the plugin's bin/ directory`;
1322
+ setStatus({
1323
+ state: "error",
1324
+ lastError: message
1325
+ });
1326
+ log("error", message);
1327
+ failWaiters(new NetworkError(message, NetworkErrorCode.peerUnavailable));
1328
+ return;
1329
+ }
1330
+ const binary = location.path;
1331
+ const args = [
1332
+ "--home",
1333
+ options.home,
1334
+ "--relay",
1335
+ relay
1336
+ ];
1337
+ clearError();
1338
+ setStatus({
1339
+ state: "starting",
1340
+ binary,
1341
+ binarySource: location.source
1342
+ });
1343
+ log("info", `soulnet binary: ${binary} (${location.source})`);
1344
+ let proc;
1345
+ try {
1346
+ proc = options.spawn !== void 0 ? options.spawn({
1347
+ binary,
1348
+ args
1349
+ }) : spawn(binary, args, {
1350
+ stdio: [
1351
+ "pipe",
1352
+ "pipe",
1353
+ "pipe"
1354
+ ],
1355
+ windowsHide: true,
1356
+ env: {
1357
+ ...process.env,
1358
+ ...options.env ?? {}
1359
+ }
1360
+ });
1361
+ } catch (error) {
1362
+ scheduleRestart(`spawn failed: ${String(error)}`);
1363
+ return;
1364
+ }
1365
+ child = proc;
1366
+ if (proc.stdout === null || proc.stdin === null) {
1367
+ proc.kill();
1368
+ scheduleRestart("spawned process has no stdio pipes");
1369
+ return;
1370
+ }
1371
+ proc.stderr?.setEncoding("utf8");
1372
+ proc.stderr?.on("data", (chunk) => {
1373
+ for (const line of chunk.split(/\r?\n/)) if (line.trim() !== "") log("info", `[soulnet] ${line.trim()}`);
1374
+ });
1375
+ const ep = new JsonRpcEndpoint(proc.stdout, proc.stdin, {
1376
+ timeoutMs: requestTimeoutMs,
1377
+ onNotification: (n) => {
1378
+ handleNotification(n.method, n.params);
1379
+ },
1380
+ onProtocolError: (error, line) => {
1381
+ log("warn", `soulnet protocol: ${error.message}: ${line.slice(0, 200)}`);
1382
+ }
1383
+ });
1384
+ endpoint = ep;
1385
+ let exited = false;
1386
+ proc.on("error", (error) => {
1387
+ if (exited) return;
1388
+ exited = true;
1389
+ if (endpoint === ep) endpoint = void 0;
1390
+ ep.close(error);
1391
+ scheduleRestart(`process error: ${error.message}`);
1392
+ });
1393
+ proc.on("exit", (code, signal) => {
1394
+ if (exited) return;
1395
+ exited = true;
1396
+ if (endpoint === ep) endpoint = void 0;
1397
+ if (child === proc) child = void 0;
1398
+ ep.close();
1399
+ if (disposed) {
1400
+ setStatus({ state: "stopped" });
1401
+ return;
1402
+ }
1403
+ scheduleRestart(`exit code=${code ?? "null"} signal=${signal ?? "none"}`);
1404
+ });
1405
+ const name = options.displayName?.trim() ?? "";
1406
+ ep.request("initialize", name === "" ? {} : { name }, { timeoutMs: 15e3 }).then((result) => {
1407
+ const r = typeof result === "object" && result !== null ? result : {};
1408
+ if (r.protocol !== "soulnet/1") log("warn", `soulnet speaks ${String(r.protocol)}; this plugin was written for ${SOULNET_PROTOCOL}`);
1409
+ backoffMs = backoffInitial;
1410
+ cachedCardUri = void 0;
1411
+ setStatus({
1412
+ state: "ready",
1413
+ ...proc.pid === void 0 ? {} : { pid: proc.pid },
1414
+ ...r.protocol === void 0 ? {} : { protocol: r.protocol },
1415
+ ...r.version === void 0 ? {} : { version: r.version },
1416
+ ...r.home === void 0 ? {} : { home: r.home },
1417
+ ...r.relay === void 0 ? {} : { relay: r.relay }
1418
+ });
1419
+ log("info", `soulnet peer ready pid=${proc.pid ?? "?"} protocol=${String(r.protocol)} identity=${r.identity?.fingerprint ?? "none"}`);
1420
+ const waiters = readyWaiters;
1421
+ readyWaiters = [];
1422
+ for (const w of waiters) w.resolve(ep);
1423
+ }).catch((error) => {
1424
+ if (exited || disposed) return;
1425
+ log("error", `soulnet initialize failed: ${String(error)}`);
1426
+ proc.kill();
1427
+ });
1428
+ };
1429
+ const ready = (timeoutMs) => {
1430
+ if (disposed) return Promise.reject(new NetworkError("soulnet backend is disposed", NetworkErrorCode.peerUnavailable));
1431
+ if (endpoint !== void 0 && !endpoint.isClosed && status.state === "ready") return Promise.resolve(endpoint);
1432
+ if (!started) start();
1433
+ if (status.state === "error") return Promise.reject(new NetworkError(status.lastError ?? "soulnet backend unavailable", NetworkErrorCode.peerUnavailable));
1434
+ return new Promise((resolve, reject) => {
1435
+ const timer = setTimeout(() => {
1436
+ readyWaiters = readyWaiters.filter((w) => w.resolve !== resolve);
1437
+ reject(new NetworkError(`soulnet peer not ready within ${timeoutMs} ms (state=${status.state})`, NetworkErrorCode.peerUnavailable));
1438
+ }, timeoutMs);
1439
+ timer.unref?.();
1440
+ readyWaiters.push({
1441
+ resolve: (ep) => {
1442
+ clearTimeout(timer);
1443
+ resolve(ep);
1444
+ },
1445
+ reject: (error) => {
1446
+ clearTimeout(timer);
1447
+ reject(error);
1448
+ }
1449
+ });
1450
+ });
1451
+ };
1452
+ const call = async (method, params, timeoutMs = requestTimeoutMs) => {
1453
+ const ep = await ready(timeoutMs);
1454
+ try {
1455
+ return await ep.request(method, params, { timeoutMs });
1456
+ } catch (error) {
1457
+ throw toNetworkError(error, method);
1458
+ }
1459
+ };
1460
+ const start = () => {
1461
+ if (started || disposed) return;
1462
+ started = true;
1463
+ spawnPeer();
1464
+ };
1465
+ const identity = async () => {
1466
+ const r = await call("identity.get");
1467
+ if (r.identity === void 0 || r.identity === null) return void 0;
1468
+ const cardUri = await card();
1469
+ return {
1470
+ fp: fp(str(r.identity.fingerprint)),
1471
+ name: str(r.identity.name),
1472
+ cardUri,
1473
+ ...r.identity.created_at === void 0 ? {} : { createdAt: r.identity.created_at }
1474
+ };
1475
+ };
1476
+ const card = async () => {
1477
+ if (cachedCardUri !== void 0) return cachedCardUri;
1478
+ cachedCardUri = str((await call("card.get")).uri);
1479
+ return cachedCardUri;
1480
+ };
1481
+ const rememberNames = (friends) => {
1482
+ for (const f of friends) friendNames.set(f.fp, f.name);
1483
+ };
1484
+ return {
1485
+ backend: "soulnet",
1486
+ start,
1487
+ status: () => status,
1488
+ identity,
1489
+ createIdentity: async (name) => {
1490
+ const r = await call("identity.create", { name });
1491
+ cachedCardUri = void 0;
1492
+ const cardUri = await card();
1493
+ return {
1494
+ fp: fp(str(r.identity?.fingerprint)),
1495
+ name: str(r.identity?.name, name),
1496
+ cardUri
1497
+ };
1498
+ },
1499
+ card,
1500
+ parseCard: async (uri) => {
1501
+ const r = await call("card.parse", { uri });
1502
+ return {
1503
+ fp: fp(str(r.fingerprint)),
1504
+ name: str(r.card?.name),
1505
+ uri: str(r.uri, uri)
1506
+ };
1507
+ },
1508
+ friends: {
1509
+ list: async () => {
1510
+ const friends = ((await call("friends.list")).friends ?? []).map(friendFromWire);
1511
+ rememberNames(friends);
1512
+ return friends;
1513
+ },
1514
+ pending: async () => {
1515
+ return ((await call("friends.pending")).pending ?? []).map(pendingFromWire);
1516
+ },
1517
+ add: async (cardUri, note) => {
1518
+ const friend = friendFromWire((await call("friends.add", {
1519
+ card_uri: cardUri,
1520
+ ...note === void 0 ? {} : { note }
1521
+ })).friend ?? {});
1522
+ friendNames.set(friend.fp, friend.name);
1523
+ return friend;
1524
+ },
1525
+ accept: async (requestId, note) => {
1526
+ const friend = friendFromWire((await call("friends.accept", {
1527
+ id: requestId,
1528
+ ...note === void 0 ? {} : { note }
1529
+ })).friend ?? {});
1530
+ friendNames.set(friend.fp, friend.name);
1531
+ return friend;
1532
+ },
1533
+ reject: async (requestId) => {
1534
+ await call("friends.reject", { id: requestId });
1535
+ },
1536
+ set: async (target, patch) => {
1537
+ const friend = friendFromWire((await call("friends.set", {
1538
+ fp: target,
1539
+ ...patch.remark === void 0 ? {} : { note: patch.remark },
1540
+ ...patch.protocol === void 0 ? {} : { protocol: patch.protocol }
1541
+ })).friend ?? {});
1542
+ friendNames.set(friend.fp, friend.name);
1543
+ return friend;
1544
+ },
1545
+ remove: async (target) => {
1546
+ await call("friends.remove", { fp: target });
1547
+ friendNames.delete(target);
1548
+ },
1549
+ card: async (target) => {
1550
+ const r = await call("friends.card", { fp: target });
1551
+ return {
1552
+ fp: fp(str(r.fingerprint, target)),
1553
+ name: str(r.card?.name),
1554
+ uri: str(r.uri)
1555
+ };
1556
+ }
1557
+ },
1558
+ send: async (to, body, options) => {
1559
+ const r = await call("message.send", {
1560
+ to,
1561
+ body,
1562
+ ...options?.file === void 0 ? {} : { file: options.file },
1563
+ ...options?.auto === true ? { auto: true } : {}
1564
+ });
1565
+ return {
1566
+ id: mid(str(r.id)),
1567
+ status: str(r.status, "sent"),
1568
+ ...typeof r.seq === "number" ? { seq: r.seq } : {}
1569
+ };
1570
+ },
1571
+ typing: async (to, on) => {
1572
+ await call("message.typing", {
1573
+ to,
1574
+ on
1575
+ }, 1e4);
1576
+ },
1577
+ conversation: async (target, opts = {}) => {
1578
+ const r = await call("conversation.get", {
1579
+ fp: target,
1580
+ ...opts.since === void 0 ? {} : { since: opts.since },
1581
+ ...opts.limit === void 0 ? {} : { limit: opts.limit }
1582
+ });
1583
+ return {
1584
+ entries: (r.entries ?? []).map(entryFromWire),
1585
+ typing: r.typing === true
1586
+ };
1587
+ },
1588
+ markRead: async (target, seq) => {
1589
+ await call("conversation.markRead", {
1590
+ fp: target,
1591
+ seq
1592
+ });
1593
+ },
1594
+ presence: async (fps) => {
1595
+ return (await call("presence", { fps: [...fps] }, 15e3)).online ?? {};
1596
+ },
1597
+ subscribe: (listener) => {
1598
+ listeners.add(listener);
1599
+ return () => {
1600
+ listeners.delete(listener);
1601
+ };
1602
+ },
1603
+ dispose: async () => {
1604
+ if (disposed) return;
1605
+ disposed = true;
1606
+ if (restartTimer !== void 0) {
1607
+ clearTimeout(restartTimer);
1608
+ restartTimer = void 0;
1609
+ }
1610
+ failWaiters(new NetworkError("soulnet backend is disposed", NetworkErrorCode.peerUnavailable));
1611
+ const proc = child;
1612
+ const ep = endpoint;
1613
+ if (proc === void 0) {
1614
+ setStatus({ state: "stopped" });
1615
+ return;
1616
+ }
1617
+ const exited = new Promise((resolve) => {
1618
+ if (proc.exitCode !== null || proc.signalCode !== null) {
1619
+ resolve();
1620
+ return;
1621
+ }
1622
+ proc.once("exit", () => {
1623
+ resolve();
1624
+ });
1625
+ });
1626
+ if (ep !== void 0 && !ep.isClosed) try {
1627
+ await ep.request("shutdown", void 0, { timeoutMs: 2e3 });
1628
+ } catch {}
1629
+ const killTimer = setTimeout(() => {
1630
+ proc.kill();
1631
+ }, 2e3);
1632
+ killTimer.unref?.();
1633
+ await exited;
1634
+ clearTimeout(killTimer);
1635
+ ep?.close();
1636
+ setStatus({ state: "stopped" });
1637
+ log("info", "soulnet peer stopped");
1638
+ }
1639
+ };
1640
+ }
1641
+ //#endregion
1642
+ //#region ../../node_modules/.pnpm/@deepseek-ai+cosmokit@1.8.2/node_modules/@deepseek-ai/cosmokit/lib/index.js
1643
+ /** Return true when a value is `null` or `undefined`. */
1644
+ function isNullable(value) {
1645
+ return value === null || value === void 0;
1646
+ }
1647
+ /** Return true for non-array object values. */
1648
+ function isPlainObject(data) {
1649
+ return data && typeof data === "object" && !Array.isArray(data);
1650
+ }
1651
+ /** Filter object entries and return a new object. */
1652
+ function filterKeys(object, filter) {
1653
+ return Object.fromEntries(Object.entries(object).filter(([key, value]) => filter(key, value)));
1654
+ }
1655
+ /** Map object values while preserving the original key set. */
1656
+ function mapValues(object, transform) {
1657
+ return Object.fromEntries(Object.entries(object).map(([key, value]) => [key, transform(value, key)]));
1658
+ }
1659
+ /** Pick selected keys from an object, optionally including `undefined` values. */
1660
+ function pick(source, keys, forced) {
1661
+ if (!keys) return { ...source };
1662
+ const result = {};
1663
+ for (const key of keys) if (forced || source[key] !== void 0) result[key] = source[key];
1664
+ return result;
1665
+ }
1666
+ /** Test values using `instanceof` with a `toStringTag` fallback. */
1667
+ function is(type, value) {
1668
+ if (arguments.length === 1) return (value) => is(type, value);
1669
+ return type in globalThis && value instanceof globalThis[type] || Object.prototype.toString.call(value).slice(8, -1) === type;
1670
+ }
1671
+ function isArrayBufferLike(value) {
1672
+ return is("ArrayBuffer", value) || is("SharedArrayBuffer", value);
1673
+ }
1674
+ function isArrayBufferSource(value) {
1675
+ return isArrayBufferLike(value) || ArrayBuffer.isView(value);
1676
+ }
1677
+ /** Binary source detection and base64/hex conversion helpers. */
1678
+ var Binary;
1679
+ (function(Binary) {
1680
+ Binary.is = isArrayBufferLike;
1681
+ Binary.isSource = isArrayBufferSource;
1682
+ function fromSource(source) {
1683
+ if (ArrayBuffer.isView(source)) return source.buffer.slice(source.byteOffset, source.byteOffset + source.byteLength);
1684
+ else return source;
1685
+ }
1686
+ Binary.fromSource = fromSource;
1687
+ function toBase64(source) {
1688
+ source = fromSource(source);
1689
+ if (typeof Buffer !== "undefined") return Buffer.from(source).toString("base64");
1690
+ let binary = "";
1691
+ const bytes = new Uint8Array(source);
1692
+ for (let i = 0; i < bytes.byteLength; i++) binary += String.fromCharCode(bytes[i]);
1693
+ return btoa(binary);
1694
+ }
1695
+ Binary.toBase64 = toBase64;
1696
+ function fromBase64(source) {
1697
+ if (typeof Buffer !== "undefined") return fromSource(Buffer.from(source, "base64"));
1698
+ return Uint8Array.from(atob(source), (c) => c.charCodeAt(0));
1699
+ }
1700
+ Binary.fromBase64 = fromBase64;
1701
+ function toHex(source) {
1702
+ source = fromSource(source);
1703
+ if (typeof Buffer !== "undefined") return Buffer.from(source).toString("hex");
1704
+ return Array.from(new Uint8Array(source), (byte) => byte.toString(16).padStart(2, "0")).join("");
1705
+ }
1706
+ Binary.toHex = toHex;
1707
+ function fromHex(source) {
1708
+ if (typeof Buffer !== "undefined") return fromSource(Buffer.from(source, "hex"));
1709
+ const hex = source.length % 2 === 0 ? source : source.slice(0, source.length - 1);
1710
+ const buffer = [];
1711
+ for (let i = 0; i < hex.length; i += 2) buffer.push(parseInt(`${hex[i]}${hex[i + 1]}`, 16));
1712
+ return Uint8Array.from(buffer).buffer;
1713
+ }
1714
+ Binary.fromHex = fromHex;
1715
+ })(Binary || (Binary = {}));
1716
+ Binary.fromBase64;
1717
+ Binary.toBase64;
1718
+ Binary.fromHex;
1719
+ Binary.toHex;
1720
+ /** Deep-clone common JavaScript values while preserving prototypes and cycles. */
1721
+ function clone(source, refs = /* @__PURE__ */ new Map()) {
1722
+ if (!source || typeof source !== "object") return source;
1723
+ if (is("Date", source)) return new Date(source.valueOf());
1724
+ if (is("RegExp", source)) return new RegExp(source.source, source.flags);
1725
+ if (isArrayBufferLike(source)) return source.slice(0);
1726
+ if (ArrayBuffer.isView(source)) return source.buffer.slice(source.byteOffset, source.byteOffset + source.byteLength);
1727
+ const cached = refs.get(source);
1728
+ if (cached) return cached;
1729
+ if (Array.isArray(source)) {
1730
+ const result = [];
1731
+ refs.set(source, result);
1732
+ source.forEach((value, index) => {
1733
+ result[index] = Reflect.apply(clone, null, [value, refs]);
1734
+ });
1735
+ return result;
1736
+ }
1737
+ const result = Object.create(Object.getPrototypeOf(source));
1738
+ refs.set(source, result);
1739
+ for (const key of Reflect.ownKeys(source)) {
1740
+ const descriptor = { ...Reflect.getOwnPropertyDescriptor(source, key) };
1741
+ if ("value" in descriptor) descriptor.value = Reflect.apply(clone, null, [descriptor.value, refs]);
1742
+ Reflect.defineProperty(result, key, descriptor);
1743
+ }
1744
+ return result;
1745
+ }
1746
+ /** Deeply compare arrays, dates, regexps, buffers, and plain object fields. */
1747
+ function deepEqual(a, b, strict) {
1748
+ if (a === b) return true;
1749
+ if (!strict && isNullable(a) && isNullable(b)) return true;
1750
+ if (typeof a !== typeof b) return false;
1751
+ if (typeof a !== "object") return false;
1752
+ if (!a || !b) return false;
1753
+ function check(test, then) {
1754
+ return test(a) ? test(b) ? then(a, b) : false : test(b) ? false : void 0;
1755
+ }
1756
+ return check(Array.isArray, (a, b) => a.length === b.length && a.every((item, index) => deepEqual(item, b[index]))) ?? check(is("Date"), (a, b) => a.valueOf() === b.valueOf()) ?? check(is("RegExp"), (a, b) => a.source === b.source && a.flags === b.flags) ?? check(isArrayBufferLike, (a, b) => {
1757
+ if (a.byteLength !== b.byteLength) return false;
1758
+ const viewA = new Uint8Array(a);
1759
+ const viewB = new Uint8Array(b);
1760
+ for (let i = 0; i < viewA.length; i++) if (viewA[i] !== viewB[i]) return false;
1761
+ return true;
1762
+ }) ?? Object.keys({
1763
+ ...a,
1764
+ ...b
1765
+ }).every((key) => deepEqual(a[key], b[key], strict));
1766
+ }
1767
+ /** Time constants plus parsing and formatting helpers. */
1768
+ var Time;
1769
+ (function(Time) {
1770
+ Time.millisecond = 1;
1771
+ Time.second = 1e3;
1772
+ Time.minute = Time.second * 60;
1773
+ Time.hour = Time.minute * 60;
1774
+ Time.day = Time.hour * 24;
1775
+ Time.week = Time.day * 7;
1776
+ let timezoneOffset = (/* @__PURE__ */ new Date()).getTimezoneOffset();
1777
+ function setTimezoneOffset(offset) {
1778
+ timezoneOffset = offset;
1779
+ }
1780
+ Time.setTimezoneOffset = setTimezoneOffset;
1781
+ function getTimezoneOffset() {
1782
+ return timezoneOffset;
1783
+ }
1784
+ Time.getTimezoneOffset = getTimezoneOffset;
1785
+ function getDateNumber(date = /* @__PURE__ */ new Date(), offset) {
1786
+ if (typeof date === "number") date = new Date(date);
1787
+ if (offset === void 0) offset = timezoneOffset;
1788
+ return Math.floor((date.valueOf() / Time.minute - offset) / 1440);
1789
+ }
1790
+ Time.getDateNumber = getDateNumber;
1791
+ function fromDateNumber(value, offset) {
1792
+ const date = new Date(value * Time.day);
1793
+ if (offset === void 0) offset = timezoneOffset;
1794
+ return new Date(+date + offset * Time.minute);
1795
+ }
1796
+ Time.fromDateNumber = fromDateNumber;
1797
+ const numeric = /\d+(?:\.\d+)?/.source;
1798
+ const timeRegExp = new RegExp(`^${[
1799
+ "w(?:eek(?:s)?)?",
1800
+ "d(?:ay(?:s)?)?",
1801
+ "h(?:our(?:s)?)?",
1802
+ "m(?:in(?:ute)?(?:s)?)?",
1803
+ "s(?:ec(?:ond)?(?:s)?)?"
1804
+ ].map((unit) => `(${numeric}${unit})?`).join("")}$`);
1805
+ function parseTime(source) {
1806
+ const capture = timeRegExp.exec(source);
1807
+ if (!capture) return 0;
1808
+ return (parseFloat(capture[1]) * Time.week || 0) + (parseFloat(capture[2]) * Time.day || 0) + (parseFloat(capture[3]) * Time.hour || 0) + (parseFloat(capture[4]) * Time.minute || 0) + (parseFloat(capture[5]) * Time.second || 0);
1809
+ }
1810
+ Time.parseTime = parseTime;
1811
+ function parseDate(date) {
1812
+ const parsed = parseTime(date);
1813
+ if (parsed) date = Date.now() + parsed;
1814
+ else if (/^\d{1,2}(:\d{1,2}){1,2}$/.test(date)) date = `${(/* @__PURE__ */ new Date()).toLocaleDateString()}-${date}`;
1815
+ else if (/^\d{1,2}-\d{1,2}-\d{1,2}(:\d{1,2}){1,2}$/.test(date)) date = `${(/* @__PURE__ */ new Date()).getFullYear()}-${date}`;
1816
+ return date ? new Date(date) : /* @__PURE__ */ new Date();
1817
+ }
1818
+ Time.parseDate = parseDate;
1819
+ function format(ms) {
1820
+ const abs = Math.abs(ms);
1821
+ if (abs >= Time.day - Time.hour / 2) return Math.round(ms / Time.day) + "d";
1822
+ else if (abs >= Time.hour - Time.minute / 2) return Math.round(ms / Time.hour) + "h";
1823
+ else if (abs >= Time.minute - Time.second / 2) return Math.round(ms / Time.minute) + "m";
1824
+ else if (abs >= Time.second) return Math.round(ms / Time.second) + "s";
1825
+ return ms + "ms";
1826
+ }
1827
+ Time.format = format;
1828
+ function toDigits(source, length = 2) {
1829
+ return source.toString().padStart(length, "0");
1830
+ }
1831
+ Time.toDigits = toDigits;
1832
+ function template(template, time = /* @__PURE__ */ new Date()) {
1833
+ return template.replace("yyyy", time.getFullYear().toString()).replace("yy", time.getFullYear().toString().slice(2)).replace("MM", toDigits(time.getMonth() + 1)).replace("dd", toDigits(time.getDate())).replace("hh", toDigits(time.getHours())).replace("mm", toDigits(time.getMinutes())).replace("ss", toDigits(time.getSeconds())).replace("SSS", toDigits(time.getMilliseconds(), 3));
1834
+ }
1835
+ Time.template = template;
1836
+ })(Time || (Time = {}));
1837
+ //#endregion
1838
+ //#region ../../node_modules/.pnpm/@deepseek-ai+schemastery@3.18.1/node_modules/@deepseek-ai/schemastery/lib/index.mjs
1839
+ const kSchema = Symbol.for("schemastery");
1840
+ const kValidationError = Symbol.for("ValidationError");
1841
+ globalThis.__schemastery_index__ ??= 0;
1842
+ globalThis.__schemastery_refs__ = void 0;
1843
+ var ValidationError = class extends TypeError {
1844
+ options;
1845
+ name = "ValidationError";
1846
+ constructor(message, options) {
1847
+ let prefix = "$";
1848
+ for (const segment of options.path || []) if (typeof segment === "string") prefix += "." + segment;
1849
+ else if (typeof segment === "number") prefix += "[" + segment + "]";
1850
+ else if (typeof segment === "symbol") prefix += `[Symbol(${segment.toString()})]`;
1851
+ if (prefix.startsWith(".")) prefix = prefix.slice(1);
1852
+ super((prefix === "$" ? "" : `${prefix} `) + message);
1853
+ this.options = options;
1854
+ }
1855
+ static is(error) {
1856
+ return !!error?.[kValidationError];
1857
+ }
1858
+ };
1859
+ Object.defineProperty(ValidationError.prototype, kValidationError, { value: true });
1860
+ const Schema = function(options) {
1861
+ const schema = function(data, options = {}) {
1862
+ return Schema.resolve(data, schema, options)[0];
1863
+ };
1864
+ if (options.refs) {
1865
+ const refs = mapValues(options.refs, (options) => new Schema(options));
1866
+ const getRef = (uid) => refs[uid];
1867
+ for (const key in refs) {
1868
+ const options = refs[key];
1869
+ options.sKey = getRef(options.sKey);
1870
+ options.inner = getRef(options.inner);
1871
+ options.list = options.list && options.list.map(getRef);
1872
+ options.dict = options.dict && mapValues(options.dict, getRef);
1873
+ }
1874
+ return refs[options.uid];
1875
+ }
1876
+ Object.assign(schema, options);
1877
+ if (typeof schema.callback === "string") try {
1878
+ schema.callback = new Function("return " + schema.callback)();
1879
+ } catch {}
1880
+ Object.defineProperty(schema, "uid", { value: globalThis.__schemastery_index__++ });
1881
+ Object.setPrototypeOf(schema, Schema.prototype);
1882
+ schema.meta ||= {};
1883
+ schema.toString = schema.toString.bind(schema);
1884
+ return schema;
1885
+ };
1886
+ Schema.prototype = Object.create(Function.prototype);
1887
+ Schema.prototype[kSchema] = true;
1888
+ Object.defineProperty(Schema.prototype, "~standard", { get() {
1889
+ return {
1890
+ version: 1,
1891
+ vendor: "schemastery",
1892
+ validate: (value) => {
1893
+ try {
1894
+ return { value: Schema.resolve(value, this, {})[0] };
1895
+ } catch (error) {
1896
+ if (ValidationError.is(error)) return { issues: [{
1897
+ message: error.message,
1898
+ path: error.options.path
1899
+ }] };
1900
+ throw error;
1901
+ }
1902
+ }
1903
+ };
1904
+ } });
1905
+ Schema.ValidationError = ValidationError;
1906
+ Schema.prototype.toJSON = function toJSON() {
1907
+ if (globalThis.__schemastery_refs__) {
1908
+ globalThis.__schemastery_refs__[this.uid] ??= JSON.parse(JSON.stringify({ ...this }));
1909
+ return this.uid;
1910
+ }
1911
+ globalThis.__schemastery_refs__ = { [this.uid]: { ...this } };
1912
+ globalThis.__schemastery_refs__[this.uid] = JSON.parse(JSON.stringify({ ...this }));
1913
+ const result = {
1914
+ uid: this.uid,
1915
+ refs: globalThis.__schemastery_refs__
1916
+ };
1917
+ globalThis.__schemastery_refs__ = void 0;
1918
+ return result;
1919
+ };
1920
+ Schema.prototype.set = function set(key, value) {
1921
+ this.dict[key] = value;
1922
+ return this;
1923
+ };
1924
+ Schema.prototype.push = function push(value) {
1925
+ this.list.push(value);
1926
+ return this;
1927
+ };
1928
+ function mergeDesc(original, messages) {
1929
+ const result = typeof original === "string" ? { "": original } : { ...original };
1930
+ for (const locale in messages) {
1931
+ const value = messages[locale];
1932
+ if (value?.$description || value?.$desc) result[locale] = value.$description || value.$desc;
1933
+ else if (typeof value === "string") result[locale] = value;
1934
+ }
1935
+ return result;
1936
+ }
1937
+ function getInner(value) {
1938
+ return value?.$value ?? value?.$inner;
1939
+ }
1940
+ function extractKeys(data) {
1941
+ return filterKeys(data ?? {}, (key) => !key.startsWith("$"));
1942
+ }
1943
+ Schema.prototype.i18n = function i18n(messages) {
1944
+ const schema = Schema(this);
1945
+ const desc = mergeDesc(schema.meta.description, messages);
1946
+ if (Object.keys(desc).length) schema.meta.description = desc;
1947
+ if (schema.dict) schema.dict = mapValues(schema.dict, (inner, key) => {
1948
+ return inner.i18n(mapValues(messages, (data) => getInner(data)?.[key] ?? data?.[key]));
1949
+ });
1950
+ if (schema.list) schema.list = schema.list.map((inner, index) => {
1951
+ return inner.i18n(mapValues(messages, (data = {}) => {
1952
+ if (Array.isArray(getInner(data))) return getInner(data)[index];
1953
+ if (Array.isArray(data)) return data[index];
1954
+ return extractKeys(data);
1955
+ }));
1956
+ });
1957
+ if (schema.inner) schema.inner = schema.inner.i18n(mapValues(messages, (data) => {
1958
+ if (getInner(data)) return getInner(data);
1959
+ return extractKeys(data);
1960
+ }));
1961
+ if (schema.sKey) schema.sKey = schema.sKey.i18n(mapValues(messages, (data) => data?.$key));
1962
+ return schema;
1963
+ };
1964
+ Schema.prototype.extra = function extra(key, value) {
1965
+ const schema = Schema(this);
1966
+ schema.meta = {
1967
+ ...schema.meta,
1968
+ [key]: value
1969
+ };
1970
+ return schema;
1971
+ };
1972
+ for (const key of [
1973
+ "required",
1974
+ "disabled",
1975
+ "collapse",
1976
+ "hidden",
1977
+ "loose"
1978
+ ]) Object.assign(Schema.prototype, { [key](value = true) {
1979
+ const schema = Schema(this);
1980
+ schema.meta = {
1981
+ ...schema.meta,
1982
+ [key]: value
1983
+ };
1984
+ return schema;
1985
+ } });
1986
+ Schema.prototype.deprecated = function deprecated() {
1987
+ const schema = Schema(this);
1988
+ schema.meta.badges ||= [];
1989
+ schema.meta.badges.push({
1990
+ text: "deprecated",
1991
+ type: "danger"
1992
+ });
1993
+ return schema;
1994
+ };
1995
+ Schema.prototype.experimental = function experimental() {
1996
+ const schema = Schema(this);
1997
+ schema.meta.badges ||= [];
1998
+ schema.meta.badges.push({
1999
+ text: "experimental",
2000
+ type: "warning"
2001
+ });
2002
+ return schema;
2003
+ };
2004
+ Schema.prototype.pattern = function pattern(regexp) {
2005
+ const schema = Schema(this);
2006
+ const pattern = pick(regexp, ["source", "flags"]);
2007
+ schema.meta = {
2008
+ ...schema.meta,
2009
+ pattern
2010
+ };
2011
+ return schema;
2012
+ };
2013
+ Schema.prototype.simplify = function simplify(value) {
2014
+ if (deepEqual(value, this.meta.default, this.type === "dict")) return null;
2015
+ if (isNullable(value)) return value;
2016
+ if (this.type === "object" || this.type === "dict") {
2017
+ const result = {};
2018
+ for (const key in value) {
2019
+ const item = (this.type === "object" ? this.dict[key] : this.inner)?.simplify(value[key]);
2020
+ if (this.type === "dict" || !isNullable(item)) result[key] = item;
2021
+ }
2022
+ if (deepEqual(result, this.meta.default, this.type === "dict")) return null;
2023
+ return result;
2024
+ } else if (this.type === "array" || this.type === "tuple") {
2025
+ const result = [];
2026
+ value.forEach((value, index) => {
2027
+ const schema = this.type === "array" ? this.inner : this.list[index];
2028
+ const item = schema ? schema.simplify(value) : value;
2029
+ result.push(item);
2030
+ });
2031
+ return result;
2032
+ } else if (this.type === "intersect") {
2033
+ const result = {};
2034
+ for (const item of this.list) Object.assign(result, item.simplify(value));
2035
+ return result;
2036
+ } else if (this.type === "union") for (const schema of this.list) try {
2037
+ Schema.resolve(value, schema, {});
2038
+ return schema.simplify(value);
2039
+ } catch {}
2040
+ return value;
2041
+ };
2042
+ Schema.prototype.toString = function toString(inline) {
2043
+ return formatters[this.type]?.(this, inline) ?? `Schema<${this.type}>`;
2044
+ };
2045
+ Schema.prototype.role = function role(role, extra) {
2046
+ const schema = Schema(this);
2047
+ schema.meta = {
2048
+ ...schema.meta,
2049
+ role,
2050
+ extra
2051
+ };
2052
+ return schema;
2053
+ };
2054
+ for (const key of [
2055
+ "default",
2056
+ "link",
2057
+ "comment",
2058
+ "description",
2059
+ "max",
2060
+ "min",
2061
+ "step"
2062
+ ]) Object.assign(Schema.prototype, { [key](value) {
2063
+ const schema = Schema(this);
2064
+ schema.meta = {
2065
+ ...schema.meta,
2066
+ [key]: value
2067
+ };
2068
+ return schema;
2069
+ } });
2070
+ const resolvers = {};
2071
+ Schema.extend = function extend(type, resolve) {
2072
+ resolvers[type] = resolve;
2073
+ };
2074
+ Schema.resolve = function resolve(data, schema, options = {}, strict = false) {
2075
+ if (!schema) return [data];
2076
+ if (options.ignore?.(data, schema)) return [data];
2077
+ if (isNullable(data) && schema.type !== "lazy") {
2078
+ if (schema.meta.required) throw new ValidationError(`missing required value`, options);
2079
+ let current = schema;
2080
+ let fallback = schema.meta.default;
2081
+ while (current?.type === "intersect" && isNullable(fallback)) {
2082
+ current = current.list[0];
2083
+ fallback = current?.meta.default;
2084
+ }
2085
+ if (isNullable(fallback)) return [data];
2086
+ data = clone(fallback);
2087
+ }
2088
+ const callback = resolvers[schema.type];
2089
+ if (!callback) throw new ValidationError(`unsupported type "${schema.type}"`, options);
2090
+ try {
2091
+ return callback(data, schema, options, strict);
2092
+ } catch (error) {
2093
+ if (!schema.meta.loose) throw error;
2094
+ return [schema.meta.default];
2095
+ }
2096
+ };
2097
+ Schema.from = function from(source) {
2098
+ if (isNullable(source)) return Schema.any();
2099
+ else if ([
2100
+ "string",
2101
+ "number",
2102
+ "boolean"
2103
+ ].includes(typeof source)) return Schema.const(source).required();
2104
+ else if (source[kSchema]) return source;
2105
+ else if (typeof source === "function") switch (source) {
2106
+ case String: return Schema.string().required();
2107
+ case Number: return Schema.number().required();
2108
+ case Boolean: return Schema.boolean().required();
2109
+ case Function: return Schema.function().required();
2110
+ default: return Schema.is(source).required();
2111
+ }
2112
+ else throw new TypeError(`cannot infer schema from ${source}`);
2113
+ };
2114
+ Schema.lazy = function lazy(builder) {
2115
+ const toJSON = () => {
2116
+ if (!schema.inner[kSchema]) {
2117
+ schema.inner = schema.builder();
2118
+ schema.inner.meta = {
2119
+ ...schema.meta,
2120
+ ...schema.inner.meta
2121
+ };
2122
+ }
2123
+ return schema.inner.toJSON();
2124
+ };
2125
+ const schema = new Schema({
2126
+ type: "lazy",
2127
+ builder,
2128
+ inner: { toJSON }
2129
+ });
2130
+ return schema;
2131
+ };
2132
+ Schema.natural = function natural() {
2133
+ return Schema.number().step(1).min(0);
2134
+ };
2135
+ Schema.percent = function percent() {
2136
+ return Schema.number().step(.01).min(0).max(1).role("slider");
2137
+ };
2138
+ Schema.date = function date() {
2139
+ return Schema.union([Schema.is(Date), Schema.transform(Schema.string().role("datetime"), (value, options) => {
2140
+ const date = new Date(value);
2141
+ if (isNaN(+date)) throw new ValidationError(`invalid date "${value}"`, options);
2142
+ return date;
2143
+ }, true)]);
2144
+ };
2145
+ Schema.regExp = function regExp(flag = "") {
2146
+ return Schema.union([Schema.is(RegExp), Schema.transform(Schema.string().role("regexp", { flag }), (value, options) => {
2147
+ try {
2148
+ return new RegExp(value, flag);
2149
+ } catch (e) {
2150
+ throw new ValidationError(e.message, options);
2151
+ }
2152
+ }, true)]);
2153
+ };
2154
+ Schema.arrayBuffer = function arrayBuffer(encoding) {
2155
+ return Schema.union([
2156
+ Schema.is(ArrayBuffer),
2157
+ Schema.is(SharedArrayBuffer),
2158
+ Schema.transform(Schema.any(), (value, options) => {
2159
+ if (Binary.isSource(value)) return Binary.fromSource(value);
2160
+ throw new ValidationError(`expected ArrayBufferSource but got ${value}`, options);
2161
+ }, true),
2162
+ ...encoding ? [Schema.transform(Schema.string(), (value, options) => {
2163
+ try {
2164
+ return encoding === "base64" ? Binary.fromBase64(value) : Binary.fromHex(value);
2165
+ } catch (e) {
2166
+ throw new ValidationError(e.message, options);
2167
+ }
2168
+ }, true)] : []
2169
+ ]);
2170
+ };
2171
+ Schema.extend("lazy", (data, schema, options, strict) => {
2172
+ if (!schema.inner[kSchema]) {
2173
+ schema.inner = schema.builder();
2174
+ schema.inner.meta = {
2175
+ ...schema.meta,
2176
+ ...schema.inner.meta
2177
+ };
2178
+ }
2179
+ return Schema.resolve(data, schema.inner, options, strict);
2180
+ });
2181
+ Schema.extend("any", (data) => {
2182
+ return [data];
2183
+ });
2184
+ Schema.extend("never", (data, _, options) => {
2185
+ throw new ValidationError(`expected nullable but got ${data}`, options);
2186
+ });
2187
+ Schema.extend("const", (data, { value }, options) => {
2188
+ if (deepEqual(data, value)) return [value];
2189
+ throw new ValidationError(`expected ${value} but got ${data}`, options);
2190
+ });
2191
+ function checkWithinRange(data, meta, description, options, skipMin = false) {
2192
+ const { max = Infinity, min = -Infinity } = meta;
2193
+ if (data > max) throw new ValidationError(`expected ${description} <= ${max} but got ${data}`, options);
2194
+ if (data < min && !skipMin) throw new ValidationError(`expected ${description} >= ${min} but got ${data}`, options);
2195
+ }
2196
+ Schema.extend("string", (data, { meta }, options) => {
2197
+ if (typeof data !== "string") throw new ValidationError(`expected string but got ${data}`, options);
2198
+ if (meta.pattern) {
2199
+ const regexp = new RegExp(meta.pattern.source, meta.pattern.flags);
2200
+ if (!regexp.test(data)) throw new ValidationError(`expect string to match regexp ${regexp}`, options);
2201
+ }
2202
+ checkWithinRange(data.length, meta, "string length", options);
2203
+ return [data];
2204
+ });
2205
+ function decimalShift(data, digits) {
2206
+ const str = data.toString();
2207
+ if (str.includes("e")) return data * Math.pow(10, digits);
2208
+ const index = str.indexOf(".");
2209
+ if (index === -1) return data * Math.pow(10, digits);
2210
+ const frac = str.slice(index + 1);
2211
+ const integer = str.slice(0, index);
2212
+ if (frac.length <= digits) return +(integer + frac.padEnd(digits, "0"));
2213
+ return +(integer + frac.slice(0, digits) + "." + frac.slice(digits));
2214
+ }
2215
+ function isMultipleOf(data, min, step) {
2216
+ step = Math.abs(step);
2217
+ if (!/^\d+\.\d+$/.test(step.toString())) return (data - min) % step === 0;
2218
+ const index = step.toString().indexOf(".");
2219
+ const digits = step.toString().slice(index + 1).length;
2220
+ return Math.abs(decimalShift(data, digits) - decimalShift(min, digits)) % decimalShift(step, digits) === 0;
2221
+ }
2222
+ Schema.extend("number", (data, { meta }, options) => {
2223
+ if (typeof data !== "number") throw new ValidationError(`expected number but got ${data}`, options);
2224
+ checkWithinRange(data, meta, "number", options);
2225
+ const { step } = meta;
2226
+ if (step && !isMultipleOf(data, meta.min ?? 0, step)) throw new ValidationError(`expected number multiple of ${step} but got ${data}`, options);
2227
+ return [data];
2228
+ });
2229
+ Schema.extend("boolean", (data, _, options) => {
2230
+ if (typeof data === "boolean") return [data];
2231
+ throw new ValidationError(`expected boolean but got ${data}`, options);
2232
+ });
2233
+ Schema.extend("bitset", (data, { bits, meta }, options) => {
2234
+ let value = 0, keys = [];
2235
+ if (typeof data === "number") {
2236
+ value = data;
2237
+ for (const key in bits) if (data & bits[key]) keys.push(key);
2238
+ } else if (Array.isArray(data)) {
2239
+ keys = data;
2240
+ for (const key of keys) {
2241
+ if (typeof key !== "string") throw new ValidationError(`expected string but got ${key}`, options);
2242
+ if (key in bits) value |= bits[key];
2243
+ }
2244
+ } else throw new ValidationError(`expected number or array but got ${data}`, options);
2245
+ if (value === meta.default) return [value];
2246
+ return [value, keys];
2247
+ });
2248
+ Schema.extend("function", (data, _, options) => {
2249
+ if (typeof data === "function") return [data];
2250
+ throw new ValidationError(`expected function but got ${data}`, options);
2251
+ });
2252
+ Schema.extend("is", (data, { constructor }, options) => {
2253
+ if (typeof constructor === "function") {
2254
+ if (data instanceof constructor) return [data];
2255
+ throw new ValidationError(`expected ${constructor.name} but got ${data}`, options);
2256
+ } else {
2257
+ if (isNullable(data)) throw new ValidationError(`expected ${constructor} but got ${data}`, options);
2258
+ let prototype = Object.getPrototypeOf(data);
2259
+ while (prototype) {
2260
+ if (prototype.constructor?.name === constructor) return [data];
2261
+ prototype = Object.getPrototypeOf(prototype);
2262
+ }
2263
+ throw new ValidationError(`expected ${constructor} but got ${data}`, options);
2264
+ }
2265
+ });
2266
+ function property(data, key, schema, options) {
2267
+ try {
2268
+ const [value, adapted] = Schema.resolve(data[key], schema, {
2269
+ ...options,
2270
+ path: [...options.path || [], key]
2271
+ });
2272
+ if (adapted !== void 0) data[key] = adapted;
2273
+ return value;
2274
+ } catch (e) {
2275
+ if (!options?.autofix) throw e;
2276
+ delete data[key];
2277
+ return schema.meta.default;
2278
+ }
2279
+ }
2280
+ Schema.extend("array", (data, { inner, meta }, options) => {
2281
+ if (!Array.isArray(data)) throw new ValidationError(`expected array but got ${data}`, options);
2282
+ checkWithinRange(data.length, meta, "array length", options, !isNullable(inner.meta.default));
2283
+ return [data.map((_, index) => property(data, index, inner, options))];
2284
+ });
2285
+ Schema.extend("dict", (data, { inner, sKey }, options, strict) => {
2286
+ if (!isPlainObject(data)) throw new ValidationError(`expected object but got ${data}`, options);
2287
+ const result = {};
2288
+ for (const key in data) {
2289
+ let rKey;
2290
+ try {
2291
+ rKey = Schema.resolve(key, sKey, options)[0];
2292
+ } catch (error) {
2293
+ if (strict) continue;
2294
+ throw error;
2295
+ }
2296
+ result[rKey] = property(data, key, inner, options);
2297
+ data[rKey] = data[key];
2298
+ if (key !== rKey) delete data[key];
2299
+ }
2300
+ return [result];
2301
+ });
2302
+ Schema.extend("tuple", (data, { list }, options, strict) => {
2303
+ if (!Array.isArray(data)) throw new ValidationError(`expected array but got ${data}`, options);
2304
+ const result = list.map((inner, index) => property(data, index, inner, options));
2305
+ if (strict) return [result];
2306
+ result.push(...data.slice(list.length));
2307
+ return [result];
2308
+ });
2309
+ function merge(result, data) {
2310
+ for (const key in data) {
2311
+ if (key in result) continue;
2312
+ result[key] = data[key];
2313
+ }
2314
+ }
2315
+ Schema.extend("object", (data, { dict }, options, strict) => {
2316
+ if (!isPlainObject(data)) throw new ValidationError(`expected object but got ${data}`, options);
2317
+ const result = {};
2318
+ for (const key in dict) {
2319
+ const value = property(data, key, dict[key], options);
2320
+ if (!isNullable(value) || key in data) result[key] = value;
2321
+ }
2322
+ if (!strict) merge(result, data);
2323
+ return [result];
2324
+ });
2325
+ Schema.extend("union", (data, { list, toString }, options, strict) => {
2326
+ const messages = [];
2327
+ for (const inner of list) try {
2328
+ return Schema.resolve(data, inner, options, strict);
2329
+ } catch (error) {
2330
+ messages.push(error);
2331
+ }
2332
+ throw new ValidationError(`expected ${toString()} but got ${JSON.stringify(data)}`, options);
2333
+ });
2334
+ Schema.extend("intersect", (data, { list, toString }, options, strict) => {
2335
+ if (!list.length) return [data];
2336
+ let result;
2337
+ for (const inner of list) {
2338
+ const value = Schema.resolve(data, inner, options, true)[0];
2339
+ if (isNullable(value)) continue;
2340
+ if (isNullable(result)) result = value;
2341
+ else if (typeof result !== typeof value) throw new ValidationError(`expected ${toString()} but got ${JSON.stringify(data)}`, options);
2342
+ else if (typeof value === "object") merge(result ??= {}, value);
2343
+ else if (result !== value) throw new ValidationError(`expected ${toString()} but got ${JSON.stringify(data)}`, options);
2344
+ }
2345
+ if (!strict && isPlainObject(data)) merge(result, data);
2346
+ return [result];
2347
+ });
2348
+ Schema.extend("transform", (data, { inner, callback, preserve }, options) => {
2349
+ const [result, adapted = data] = Schema.resolve(data, inner, options, true);
2350
+ if (preserve) return [callback(result)];
2351
+ else return [callback(result), callback(adapted)];
2352
+ });
2353
+ const formatters = {};
2354
+ function defineMethod(name, keys, format) {
2355
+ formatters[name] = format;
2356
+ Object.assign(Schema, { [name](...args) {
2357
+ const schema = new Schema({ type: name });
2358
+ keys.forEach((key, index) => {
2359
+ switch (key) {
2360
+ case "sKey":
2361
+ schema.sKey = args[index] ?? Schema.string();
2362
+ break;
2363
+ case "inner":
2364
+ schema.inner = Schema.from(args[index]);
2365
+ break;
2366
+ case "list":
2367
+ schema.list = args[index].map(Schema.from);
2368
+ break;
2369
+ case "dict":
2370
+ schema.dict = mapValues(args[index], Schema.from);
2371
+ break;
2372
+ case "bits":
2373
+ schema.bits = {};
2374
+ for (const key in args[index]) {
2375
+ if (typeof args[index][key] !== "number") continue;
2376
+ schema.bits[key] = args[index][key];
2377
+ }
2378
+ break;
2379
+ case "callback": {
2380
+ const callback = schema.callback = args[index];
2381
+ callback["toJSON"] ||= () => callback.toString();
2382
+ break;
2383
+ }
2384
+ case "constructor": {
2385
+ const constructor = schema.constructor = args[index];
2386
+ if (typeof constructor === "function") constructor["toJSON"] ||= () => constructor["name"];
2387
+ break;
2388
+ }
2389
+ default: schema[key] = args[index];
2390
+ }
2391
+ });
2392
+ if (name === "object" || name === "dict") schema.meta.default = {};
2393
+ else if (name === "array" || name === "tuple") schema.meta.default = [];
2394
+ else if (name === "bitset") schema.meta.default = 0;
2395
+ return schema;
2396
+ } });
2397
+ }
2398
+ defineMethod("is", ["constructor"], ({ constructor }) => {
2399
+ if (typeof constructor === "function") return constructor.name;
2400
+ else return constructor;
2401
+ });
2402
+ defineMethod("any", [], () => "any");
2403
+ defineMethod("never", [], () => "never");
2404
+ defineMethod("const", ["value"], ({ value }) => typeof value === "string" ? JSON.stringify(value) : value);
2405
+ defineMethod("string", [], () => "string");
2406
+ defineMethod("number", [], () => "number");
2407
+ defineMethod("boolean", [], () => "boolean");
2408
+ defineMethod("bitset", ["bits"], () => "bitset");
2409
+ defineMethod("function", [], () => "function");
2410
+ defineMethod("array", ["inner"], ({ inner }) => `${inner.toString(true)}[]`);
2411
+ defineMethod("dict", ["inner", "sKey"], ({ inner, sKey }) => `{ [key: ${sKey.toString()}]: ${inner.toString()} }`);
2412
+ defineMethod("tuple", ["list"], ({ list }) => `[${list.map((inner) => inner.toString()).join(", ")}]`);
2413
+ defineMethod("object", ["dict"], ({ dict }) => {
2414
+ if (Object.keys(dict).length === 0) return "{}";
2415
+ return `{ ${Object.entries(dict).map(([key, inner]) => {
2416
+ return `${key}${inner.meta.required ? "" : "?"}: ${inner.toString()}`;
2417
+ }).join(", ")} }`;
2418
+ });
2419
+ defineMethod("union", ["list"], ({ list }, inline) => {
2420
+ const result = list.map(({ toString: format }) => format()).join(" | ");
2421
+ return inline ? `(${result})` : result;
2422
+ });
2423
+ defineMethod("intersect", ["list"], ({ list }) => {
2424
+ return `${list.map((inner) => inner.toString(true)).join(" & ")}`;
2425
+ });
2426
+ defineMethod("transform", [
2427
+ "inner",
2428
+ "callback",
2429
+ "preserve"
2430
+ ], ({ inner }, isInner) => inner.toString(isInner));
2431
+ //#endregion
2432
+ //#region src/settings.ts
2433
+ /**
2434
+ * The `soulmirror` user-settings namespace (dsh `ctx.settings`): what the
2435
+ * browser settings section "SoulMirror network" edits and what the network
2436
+ * plugin reads when it spawns the backend. The connection fields apply on
2437
+ * the next plugin (re)load — the peer process is spawned with them
2438
+ * (`applies: 'restart'`); the alter fields (`defaultTier`, `autoReplyPerHour`,
2439
+ * `directSend`) are read live through `ctx.soulmirrorConfig.current()`. The
2440
+ * tiers decide how the alter handles a friend's mail (P4: `draft` = the
2441
+ * alter's reply waits as a pending draft on the SoulMirror page).
2442
+ *
2443
+ * `@deepseek-ai/schemastery` is a VALUE import here on purpose: the settings
2444
+ * seam needs a real schemastery schema (callable validator + `toJSON()` for
2445
+ * the browser form). It is a vendored, dependency-free library and gets
2446
+ * inlined into lib/index.js by tsdown, so the host half still has zero
2447
+ * `@deepseek-ai/*` runtime edges into the harness instance (see ../README.md).
2448
+ */
2449
+ const SETTINGS_NAMESPACE = "soulmirror";
2450
+ const SOULMIRROR_SETTINGS_SCHEMA = Schema.object({
2451
+ relay: Schema.string().default(DEFAULT_RELAY).description("Relay URL (used when the identity is created)."),
2452
+ displayName: Schema.string().default("").description("Display name for a new identity (first start only)."),
2453
+ backend: Schema.union([Schema.const("soulnet"), Schema.const("fake")]).default("soulnet").description("soulnet = the light peer binary; fake = in-memory test backend."),
2454
+ peerBinary: Schema.string().default("").description("Path of the soulnet binary; empty = PATH, then the plugin bin/ directory."),
2455
+ home: Schema.string().default("").description("Data directory; empty = $SOULNET_HOME, then ~/.soulnet."),
2456
+ defaultTier: Schema.union([
2457
+ Schema.const("notify"),
2458
+ Schema.const("draft"),
2459
+ Schema.const("auto")
2460
+ ]).default(DEFAULT_REPLY_TIER).description("Default reply tier for friends: notify = mail is only shown; draft = the alter drafts a reply you review on the SoulMirror page; auto = the alter replies by itself (rate-limited)."),
2461
+ autoReplyPerHour: Schema.number().default(20).description("Maximum automatic replies per friend per hour in the auto tier (0 disables)."),
2462
+ directSend: Schema.boolean().default(false).description("Debug: offer \"Send as myself\" in a friend thread (bypasses the alter); off by default.")
2463
+ });
2464
+ /** Fill in defaults for a partial section (plugin config or a stored user section). */
2465
+ function resolveSettings(partial) {
2466
+ const perHour = typeof partial?.autoReplyPerHour === "number" && Number.isFinite(partial.autoReplyPerHour) ? Math.max(0, Math.floor(partial.autoReplyPerHour)) : 20;
2467
+ return {
2468
+ relay: partial?.relay !== void 0 && partial.relay.trim() !== "" ? partial.relay.trim() : DEFAULT_RELAY,
2469
+ displayName: partial?.displayName ?? "",
2470
+ backend: partial?.backend === "fake" ? "fake" : "soulnet",
2471
+ peerBinary: partial?.peerBinary ?? "",
2472
+ home: partial?.home ?? "",
2473
+ defaultTier: normalizeTier(partial?.defaultTier),
2474
+ autoReplyPerHour: perHour,
2475
+ directSend: partial?.directSend === true
2476
+ };
2477
+ }
2478
+ //#endregion
2479
+ //#region src/index.ts
2480
+ const name = "soulmirror-network";
2481
+ const inject = [];
2482
+ function apply(ctx, config = {}) {
2483
+ const log = (level, message) => {
2484
+ ctx.logger[level](`soulmirror-network: ${message}`);
2485
+ };
2486
+ const entry = resolveSettings(config);
2487
+ let effective = entry;
2488
+ let live = entry;
2489
+ const settingsNow = ctx.get("settings");
2490
+ if (settingsNow !== void 0) {
2491
+ const scope = settingsNow.register(SETTINGS_NAMESPACE, SOULMIRROR_SETTINGS_SCHEMA, {
2492
+ base: config,
2493
+ applies: "restart"
2494
+ });
2495
+ effective = resolveSettings(scope.get());
2496
+ live = effective;
2497
+ scope.watch(() => {
2498
+ live = resolveSettings(scope.get());
2499
+ log("info", `settings changed (tier=${live.defaultTier}, autoReplyPerHour=${live.autoReplyPerHour}, directSend=${String(live.directSend)} apply now; connection fields apply when the plugin reloads)`);
2500
+ });
2501
+ } else ctx.inject(["settings"], (sctx) => {
2502
+ const scope = sctx.settings.register(SETTINGS_NAMESPACE, SOULMIRROR_SETTINGS_SCHEMA, {
2503
+ base: config,
2504
+ applies: "restart"
2505
+ });
2506
+ live = resolveSettings(scope.get());
2507
+ scope.watch(() => {
2508
+ live = resolveSettings(scope.get());
2509
+ log("info", "settings changed; alter fields apply now, connection fields when the plugin reloads");
2510
+ });
2511
+ });
2512
+ ctx.provide("soulmirrorConfig", { current: () => live });
2513
+ const home = effective.home !== "" ? effective.home : defaultSoulnetHome();
2514
+ let client;
2515
+ if (effective.backend === "fake") client = createFakeNetworkClient();
2516
+ else {
2517
+ const peer = createSoulnetNetworkClient({
2518
+ home,
2519
+ relay: effective.relay,
2520
+ displayName: effective.displayName,
2521
+ ...effective.peerBinary === "" ? {} : { peerBinary: effective.peerBinary },
2522
+ logger: log
2523
+ });
2524
+ peer.start();
2525
+ client = peer;
2526
+ }
2527
+ ctx.provide("soulmirrorHome", home);
2528
+ ctx.provide("soulmirror", client);
2529
+ ctx.effect(() => () => {
2530
+ client.dispose().catch((error) => {
2531
+ log("warn", `dispose failed: ${String(error)}`);
2532
+ });
2533
+ }, "soulmirror-network: backend process");
2534
+ mountApi(ctx, {
2535
+ client,
2536
+ home,
2537
+ settingsNamespace: SETTINGS_NAMESPACE,
2538
+ sessions: () => ctx.get("soulmirrorSessions"),
2539
+ settings: () => live,
2540
+ log
2541
+ });
2542
+ log("info", `backend=${client.backend} home=${home} relay=${effective.relay}`);
2543
+ }
2544
+ //#endregion
2545
+ export { RELAY_FORM, SETTINGS_NAMESPACE, SOULMIRROR_PLUGIN, apply, inject, name };
2546
+
2547
+ //# sourceMappingURL=index.js.map