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.
- package/LICENSE +21 -0
- package/README.md +64 -0
- package/cordis.patch.yml +40 -0
- package/lib/client.js +5427 -0
- package/lib/client.js.map +1 -0
- package/lib/commands.js +94 -0
- package/lib/commands.js.map +1 -0
- package/lib/events-Cz7g9aXF.js +134 -0
- package/lib/events-Cz7g9aXF.js.map +1 -0
- package/lib/events-DHPK3pow.js +134 -0
- package/lib/events-DHPK3pow.js.map +1 -0
- package/lib/events-DViXNRAZ.js +9 -0
- package/lib/events-DViXNRAZ.js.map +1 -0
- package/lib/index.js +2547 -0
- package/lib/index.js.map +1 -0
- package/lib/policy-HQKLctKr.js +96 -0
- package/lib/policy-HQKLctKr.js.map +1 -0
- package/lib/send-BL_Yr40O.js +33 -0
- package/lib/send-BL_Yr40O.js.map +1 -0
- package/lib/send-DcYQC5CC.js +127 -0
- package/lib/send-DcYQC5CC.js.map +1 -0
- package/lib/sessions.js +1197 -0
- package/lib/sessions.js.map +1 -0
- package/lib/tools.js +342 -0
- package/lib/tools.js.map +1 -0
- package/lib/types/alter-state.d.ts +185 -0
- package/lib/types/api/index.d.ts +52 -0
- package/lib/types/client/A2ANode.d.ts +4 -0
- package/lib/types/client/AlterPane.d.ts +9 -0
- package/lib/types/client/ChatPane.d.ts +13 -0
- package/lib/types/client/DraftCard.d.ts +11 -0
- package/lib/types/client/FriendComposer.d.ts +20 -0
- package/lib/types/client/FriendList.d.ts +12 -0
- package/lib/types/client/FriendPane.d.ts +13 -0
- package/lib/types/client/Inbox.d.ts +9 -0
- package/lib/types/client/InboxOverlay.d.ts +8 -0
- package/lib/types/client/Onboarding.d.ts +4 -0
- package/lib/types/client/SettingsSection.d.ts +29 -0
- package/lib/types/client/SidebarEntry.d.ts +9 -0
- package/lib/types/client/SoulmirrorPage.d.ts +12 -0
- package/lib/types/client/SoulmirrorView.d.ts +5 -0
- package/lib/types/client/a2a-node.d.ts +46 -0
- package/lib/types/client/alter-ui.d.ts +35 -0
- package/lib/types/client/api.d.ts +424 -0
- package/lib/types/client/inbox-state.d.ts +57 -0
- package/lib/types/client/index.d.ts +30 -0
- package/lib/types/client/locales.d.ts +239 -0
- package/lib/types/client/page-state.d.ts +116 -0
- package/lib/types/client/page-store.d.ts +96 -0
- package/lib/types/client/panel-store.d.ts +30 -0
- package/lib/types/client/styles.d.ts +2 -0
- package/lib/types/commands/index.d.ts +24 -0
- package/lib/types/drafts.d.ts +50 -0
- package/lib/types/events.d.ts +61 -0
- package/lib/types/friend-settings.d.ts +41 -0
- package/lib/types/index.d.ts +43 -0
- package/lib/types/network/fake.d.ts +10 -0
- package/lib/types/network/jsonrpc.d.ts +52 -0
- package/lib/types/network/send.d.ts +19 -0
- package/lib/types/network/soulnet.d.ts +123 -0
- package/lib/types/network/types.d.ts +203 -0
- package/lib/types/persona.d.ts +45 -0
- package/lib/types/policy.d.ts +105 -0
- package/lib/types/sessions/index.d.ts +182 -0
- package/lib/types/settings.d.ts +58 -0
- package/lib/types/tools/define.d.ts +35 -0
- package/lib/types/tools/index.d.ts +57 -0
- package/lib/types-zwvjR1n5.js +29 -0
- package/lib/types-zwvjR1n5.js.map +1 -0
- package/package.json +133 -0
- package/presets/soulmirror-chat/agent.cordis.yml +24 -0
- package/presets/soulmirror-chat/preset.yml +3 -0
package/lib/commands.js
ADDED
|
@@ -0,0 +1,94 @@
|
|
|
1
|
+
import { t as NetworkError } from "./types-zwvjR1n5.js";
|
|
2
|
+
//#region src/commands/index.ts
|
|
3
|
+
const name = "soulmirror-commands";
|
|
4
|
+
const inject = ["commands", "soulmirror"];
|
|
5
|
+
const fail = (text) => ({
|
|
6
|
+
kind: "error",
|
|
7
|
+
text
|
|
8
|
+
});
|
|
9
|
+
const ok = (text) => ({
|
|
10
|
+
kind: "success",
|
|
11
|
+
text
|
|
12
|
+
});
|
|
13
|
+
function describeError(error) {
|
|
14
|
+
if (error instanceof NetworkError) return `${error.message} (code ${error.code})`;
|
|
15
|
+
return error instanceof Error ? error.message : String(error);
|
|
16
|
+
}
|
|
17
|
+
function apply(ctx) {
|
|
18
|
+
const net = ctx.soulmirror;
|
|
19
|
+
ctx.commands.register({
|
|
20
|
+
name: "card",
|
|
21
|
+
description: "Show my SoulMirror card URI (paste it to others so they can add me).",
|
|
22
|
+
async handler() {
|
|
23
|
+
try {
|
|
24
|
+
const identity = await net.identity();
|
|
25
|
+
if (identity === void 0) return fail("No SoulMirror identity yet. Create one in Settings → SoulMirror network.");
|
|
26
|
+
return ok(`SoulMirror card of ${identity.name} (${identity.fp}):\n${identity.cardUri}`);
|
|
27
|
+
} catch (error) {
|
|
28
|
+
return fail(`Could not read the card: ${describeError(error)}`);
|
|
29
|
+
}
|
|
30
|
+
}
|
|
31
|
+
});
|
|
32
|
+
ctx.commands.register({
|
|
33
|
+
name: "friends",
|
|
34
|
+
description: "List SoulMirror friends (presence, unread) and pending friend requests.",
|
|
35
|
+
async handler() {
|
|
36
|
+
try {
|
|
37
|
+
const [friends, pending] = await Promise.all([net.friends.list(), net.friends.pending()]);
|
|
38
|
+
let online = {};
|
|
39
|
+
try {
|
|
40
|
+
online = await net.presence(friends.map((f) => f.fp));
|
|
41
|
+
} catch {}
|
|
42
|
+
const lines = friends.map((f) => {
|
|
43
|
+
return `${(online[f.fp] ?? f.online) === true ? "●" : "○"} ${f.name} ${f.fp}${f.unread > 0 ? ` (${f.unread} unread)` : ""}${f.typing === true ? " typing…" : ""}`;
|
|
44
|
+
});
|
|
45
|
+
if (lines.length === 0) lines.push("(no friends yet — /add <card_uri> to send a request)");
|
|
46
|
+
if (pending.length > 0) {
|
|
47
|
+
lines.push("", `Pending requests (${pending.length}; accept/reject on the SoulMirror page):`);
|
|
48
|
+
for (const p of pending) lines.push(` ${p.name} ${p.fp}${p.greeting === "" ? "" : ` "${p.greeting}"`}`);
|
|
49
|
+
}
|
|
50
|
+
return ok(lines.join("\n"));
|
|
51
|
+
} catch (error) {
|
|
52
|
+
return fail(`Could not list friends: ${describeError(error)}`);
|
|
53
|
+
}
|
|
54
|
+
}
|
|
55
|
+
});
|
|
56
|
+
ctx.commands.register({
|
|
57
|
+
name: "add",
|
|
58
|
+
description: "Send a SoulMirror friend request: /add <card_uri> [note]",
|
|
59
|
+
input: { hint: "<card_uri> [note]" },
|
|
60
|
+
async handler(invocation) {
|
|
61
|
+
const raw = invocation.rawInput.trim();
|
|
62
|
+
if (raw === "") return fail("Usage: /add <card_uri> [note]");
|
|
63
|
+
const space = raw.search(/\s/);
|
|
64
|
+
const cardUri = space === -1 ? raw : raw.slice(0, space);
|
|
65
|
+
const note = space === -1 ? void 0 : raw.slice(space).trim();
|
|
66
|
+
try {
|
|
67
|
+
const parsed = await net.parseCard(cardUri);
|
|
68
|
+
const friend = await net.friends.add(cardUri, note === "" ? void 0 : note);
|
|
69
|
+
return ok(`Friend request sent to ${parsed.name !== "" ? parsed.name : friend.name} (${friend.fp}). Messages can be exchanged once they accept.`);
|
|
70
|
+
} catch (error) {
|
|
71
|
+
return fail(`Could not send the friend request: ${describeError(error)}`);
|
|
72
|
+
}
|
|
73
|
+
}
|
|
74
|
+
});
|
|
75
|
+
ctx.commands.register({
|
|
76
|
+
name: "soulmirror",
|
|
77
|
+
description: "Open the SoulMirror page: talk to your alter, watch its conversations with your friends, review its drafts.",
|
|
78
|
+
async handler() {
|
|
79
|
+
try {
|
|
80
|
+
const [friends, pending] = await Promise.all([net.friends.list(), net.friends.pending()]);
|
|
81
|
+
const unread = friends.reduce((sum, f) => sum + (f.unread > 0 ? f.unread : 0), 0);
|
|
82
|
+
const pendingDrafts = ctx.get("soulmirrorSessions")?.drafts.count() ?? 0;
|
|
83
|
+
return ok(`SoulMirror page: ${friends.length} friend(s), ${unread} unread, ${pending.length} pending request(s), ${pendingDrafts} draft(s) of your alter waiting for review. Open it from the "SoulMirror" entry at the sidebar foot (or pick "My alter" / a friend from the /soulmirror popup).`);
|
|
84
|
+
} catch (error) {
|
|
85
|
+
return fail(`Could not read the network state: ${describeError(error)}`);
|
|
86
|
+
}
|
|
87
|
+
}
|
|
88
|
+
});
|
|
89
|
+
ctx.logger.info("soulmirror-commands: registered /card, /friends, /add, /soulmirror");
|
|
90
|
+
}
|
|
91
|
+
//#endregion
|
|
92
|
+
export { apply, inject, name };
|
|
93
|
+
|
|
94
|
+
//# sourceMappingURL=commands.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"commands.js","names":[],"sources":["../src/commands/index.ts"],"sourcesContent":["/**\n * soulmirror-commands — host slash commands (`ctx.commands.register`):\n *\n * /card show this user's card URI\n * /friends list friends (presence, unread); the client half\n * decorates the bare invocation with a popup that opens\n * a friend's session\n * /add <card_uri> [note]\n * send a friend request (the human typing the command\n * IS the approval; commands run outside a model turn, so\n * ctx.approval cannot be used here)\n * /soulmirror open the SoulMirror page (\"My alter\" + friends); the\n * client half decorates the bare invocation with a\n * popup (the page / one friend) — the host handler only\n * prints the hint, since a host command cannot drive\n * the browser UI\n *\n * Results are rendered by the composer and never enter model history.\n * No @deepseek-ai value imports (see ../index.ts header).\n */\nimport type { Context } from '@deepseek-ai/cordis'\nimport type { CommandResult } from '@deepseek-ai/dsh-commands'\n// Type-only: the ctx.commands Context merge.\nimport type {} from '@deepseek-ai/dsh-commands'\nimport { NetworkError } from '../network/types.ts'\nimport type {} from '../index.ts'\n\nexport const name = 'soulmirror-commands'\nexport const inject = ['commands', 'soulmirror']\n\nconst fail = (text: string): CommandResult => ({ kind: 'error', text })\nconst ok = (text: string): CommandResult => ({ kind: 'success', text })\n\nfunction describeError(error: unknown): string {\n if (error instanceof NetworkError) return `${error.message} (code ${error.code})`\n return error instanceof Error ? error.message : String(error)\n}\n\nexport function apply(ctx: Context): void {\n const net = ctx.soulmirror\n\n ctx.commands.register({\n name: 'card',\n description: 'Show my SoulMirror card URI (paste it to others so they can add me).',\n async handler() {\n try {\n const identity = await net.identity()\n if (identity === undefined) return fail('No SoulMirror identity yet. Create one in Settings → SoulMirror network.')\n return ok(`SoulMirror card of ${identity.name} (${identity.fp}):\\n${identity.cardUri}`)\n } catch (error: unknown) {\n return fail(`Could not read the card: ${describeError(error)}`)\n }\n },\n })\n\n ctx.commands.register({\n name: 'friends',\n description: 'List SoulMirror friends (presence, unread) and pending friend requests.',\n async handler() {\n try {\n const [friends, pending] = await Promise.all([net.friends.list(), net.friends.pending()])\n let online: Record<string, boolean> = {}\n try {\n online = await net.presence(friends.map(f => f.fp))\n } catch {\n // best effort\n }\n const lines = friends.map((f) => {\n const on = online[f.fp] ?? f.online\n return `${on === true ? '●' : '○'} ${f.name} ${f.fp}${f.unread > 0 ? ` (${f.unread} unread)` : ''}${f.typing === true ? ' typing…' : ''}`\n })\n if (lines.length === 0) lines.push('(no friends yet — /add <card_uri> to send a request)')\n if (pending.length > 0) {\n lines.push('', `Pending requests (${pending.length}; accept/reject on the SoulMirror page):`)\n for (const p of pending) lines.push(` ${p.name} ${p.fp}${p.greeting === '' ? '' : ` \"${p.greeting}\"`}`)\n }\n return ok(lines.join('\\n'))\n } catch (error: unknown) {\n return fail(`Could not list friends: ${describeError(error)}`)\n }\n },\n })\n\n ctx.commands.register({\n name: 'add',\n description: 'Send a SoulMirror friend request: /add <card_uri> [note]',\n input: { hint: '<card_uri> [note]' },\n async handler(invocation) {\n const raw = invocation.rawInput.trim()\n if (raw === '') return fail('Usage: /add <card_uri> [note]')\n const space = raw.search(/\\s/)\n const cardUri = space === -1 ? raw : raw.slice(0, space)\n const note = space === -1 ? undefined : raw.slice(space).trim()\n try {\n const parsed = await net.parseCard(cardUri)\n const friend = await net.friends.add(cardUri, note === '' ? undefined : note)\n return ok(`Friend request sent to ${parsed.name !== '' ? parsed.name : friend.name} (${friend.fp}). Messages can be exchanged once they accept.`)\n } catch (error: unknown) {\n return fail(`Could not send the friend request: ${describeError(error)}`)\n }\n },\n })\n\n ctx.commands.register({\n name: 'soulmirror',\n description: 'Open the SoulMirror page: talk to your alter, watch its conversations with your friends, review its drafts.',\n async handler() {\n try {\n const [friends, pending] = await Promise.all([net.friends.list(), net.friends.pending()])\n const unread = friends.reduce((sum, f) => sum + (f.unread > 0 ? f.unread : 0), 0)\n const drafts = (ctx as unknown as { get(name: string): unknown }).get('soulmirrorSessions') as { drafts: { count(): number } } | undefined\n const pendingDrafts = drafts?.drafts.count() ?? 0\n return ok(`SoulMirror page: ${friends.length} friend(s), ${unread} unread, ${pending.length} pending request(s), ${pendingDrafts} draft(s) of your alter waiting for review. Open it from the \"SoulMirror\" entry at the sidebar foot (or pick \"My alter\" / a friend from the /soulmirror popup).`)\n } catch (error: unknown) {\n return fail(`Could not read the network state: ${describeError(error)}`)\n }\n },\n })\n\n ctx.logger.info('soulmirror-commands: registered /card, /friends, /add, /soulmirror')\n}\n"],"mappings":"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"}
|
|
@@ -0,0 +1,134 @@
|
|
|
1
|
+
import { i as normalizeTier } from "./policy-HQKLctKr.js";
|
|
2
|
+
import { dirname, join } from "node:path";
|
|
3
|
+
import { mkdirSync, readFileSync, statSync, writeFileSync } from "node:fs";
|
|
4
|
+
import { mkdir, readFile, writeFile } from "node:fs/promises";
|
|
5
|
+
//#region src/friend-settings.ts
|
|
6
|
+
/**
|
|
7
|
+
* The plugin's own per-friend settings (P3) and the diplomacy protocol file.
|
|
8
|
+
*
|
|
9
|
+
* - `<home>/a2a/dsh-friends.json` — `{ friends: { [fp]: { tier } } }`: the
|
|
10
|
+
* reply tier per friend (`notify` | `draft` | `auto`). The peer's
|
|
11
|
+
* friends.yaml has no such field (the soulnet a2a package deliberately
|
|
12
|
+
* carries no auto_reply switch), so it lives beside the session map.
|
|
13
|
+
* The per-friend PROTOCOL OVERRIDE is NOT here: the peer stores it in
|
|
14
|
+
* friends.yaml (`friends.set {protocol}`), shared with SoulMirror.
|
|
15
|
+
* - `<home>/a2a/protocol.md` — the global diplomacy protocol. The peer
|
|
16
|
+
* writes the default text when the identity is created; this module
|
|
17
|
+
* reads it (cached by mtime so the prompt variable can be evaluated on
|
|
18
|
+
* every assembly) and writes it (Settings / page editor).
|
|
19
|
+
*/
|
|
20
|
+
const FRIEND_SETTINGS_FILE = "dsh-friends.json";
|
|
21
|
+
const PROTOCOL_FILE = "protocol.md";
|
|
22
|
+
/** In-memory copy of dsh-friends.json with write-through. */
|
|
23
|
+
var FriendSettingsStore = class FriendSettingsStore {
|
|
24
|
+
path;
|
|
25
|
+
data = { friends: {} };
|
|
26
|
+
loaded = false;
|
|
27
|
+
constructor(path) {
|
|
28
|
+
this.path = path;
|
|
29
|
+
}
|
|
30
|
+
static at(a2aDir) {
|
|
31
|
+
return new FriendSettingsStore(join(a2aDir, FRIEND_SETTINGS_FILE));
|
|
32
|
+
}
|
|
33
|
+
async load() {
|
|
34
|
+
try {
|
|
35
|
+
const raw = JSON.parse(await readFile(this.path, "utf8"));
|
|
36
|
+
const friends = {};
|
|
37
|
+
for (const [fp, value] of Object.entries(raw.friends ?? {})) {
|
|
38
|
+
const v = typeof value === "object" && value !== null ? value : {};
|
|
39
|
+
friends[fp] = { ...v["tier"] === void 0 ? {} : { tier: normalizeTier(v["tier"]) } };
|
|
40
|
+
}
|
|
41
|
+
this.data = { friends };
|
|
42
|
+
} catch {
|
|
43
|
+
this.data = { friends: {} };
|
|
44
|
+
}
|
|
45
|
+
this.loaded = true;
|
|
46
|
+
}
|
|
47
|
+
get isLoaded() {
|
|
48
|
+
return this.loaded;
|
|
49
|
+
}
|
|
50
|
+
get(fp) {
|
|
51
|
+
return this.data.friends[fp] ?? {};
|
|
52
|
+
}
|
|
53
|
+
/** Tier of a friend, or `fallback` (the global default) when none is stored. */
|
|
54
|
+
tier(fp, fallback) {
|
|
55
|
+
return this.data.friends[fp]?.tier ?? fallback;
|
|
56
|
+
}
|
|
57
|
+
/** Every stored entry (fp → settings). */
|
|
58
|
+
all() {
|
|
59
|
+
return this.data.friends;
|
|
60
|
+
}
|
|
61
|
+
/** Patch one friend's settings; an explicit `tier: undefined` clears the stored tier (back to the global default). */
|
|
62
|
+
async set(fp, patch) {
|
|
63
|
+
const current = this.get(fp);
|
|
64
|
+
const tier = "tier" in patch ? patch.tier : current.tier;
|
|
65
|
+
if (tier === void 0) delete this.data.friends[fp];
|
|
66
|
+
else this.data.friends[fp] = {
|
|
67
|
+
...current,
|
|
68
|
+
tier
|
|
69
|
+
};
|
|
70
|
+
await mkdir(dirname(this.path), { recursive: true });
|
|
71
|
+
await writeFile(this.path, JSON.stringify(this.data, null, 2), "utf8");
|
|
72
|
+
return this.get(fp);
|
|
73
|
+
}
|
|
74
|
+
async remove(fp) {
|
|
75
|
+
if (!(fp in this.data.friends)) return;
|
|
76
|
+
delete this.data.friends[fp];
|
|
77
|
+
await writeFile(this.path, JSON.stringify(this.data, null, 2), "utf8");
|
|
78
|
+
}
|
|
79
|
+
};
|
|
80
|
+
/**
|
|
81
|
+
* The global diplomacy protocol file. `read()` is synchronous and cached by
|
|
82
|
+
* mtime so a prompt-variable provider can call it on every assembly; `write()`
|
|
83
|
+
* replaces the file (and the cache).
|
|
84
|
+
*/
|
|
85
|
+
var ProtocolFile = class ProtocolFile {
|
|
86
|
+
path;
|
|
87
|
+
cache;
|
|
88
|
+
constructor(path) {
|
|
89
|
+
this.path = path;
|
|
90
|
+
}
|
|
91
|
+
static at(a2aDir) {
|
|
92
|
+
return new ProtocolFile(join(a2aDir, PROTOCOL_FILE));
|
|
93
|
+
}
|
|
94
|
+
/** Current text; `''` when the file does not exist yet (the peer writes it with the identity). */
|
|
95
|
+
read() {
|
|
96
|
+
try {
|
|
97
|
+
const stat = statSync(this.path);
|
|
98
|
+
if (this.cache !== void 0 && this.cache.mtimeMs === stat.mtimeMs && this.cache.size === stat.size) return this.cache.text;
|
|
99
|
+
const text = readFileSync(this.path, "utf8");
|
|
100
|
+
this.cache = {
|
|
101
|
+
mtimeMs: stat.mtimeMs,
|
|
102
|
+
size: stat.size,
|
|
103
|
+
text
|
|
104
|
+
};
|
|
105
|
+
return text;
|
|
106
|
+
} catch {
|
|
107
|
+
this.cache = void 0;
|
|
108
|
+
return "";
|
|
109
|
+
}
|
|
110
|
+
}
|
|
111
|
+
exists() {
|
|
112
|
+
try {
|
|
113
|
+
statSync(this.path);
|
|
114
|
+
return true;
|
|
115
|
+
} catch {
|
|
116
|
+
return false;
|
|
117
|
+
}
|
|
118
|
+
}
|
|
119
|
+
write(text) {
|
|
120
|
+
mkdirSync(dirname(this.path), { recursive: true });
|
|
121
|
+
writeFileSync(this.path, text, "utf8");
|
|
122
|
+
this.cache = void 0;
|
|
123
|
+
}
|
|
124
|
+
};
|
|
125
|
+
//#endregion
|
|
126
|
+
//#region src/events.ts
|
|
127
|
+
/** `source.plugin` of the `user/message` carrying an inbound A2A mail. */
|
|
128
|
+
const SOULMIRROR_PLUGIN = "soulmirror";
|
|
129
|
+
/** `source.form` of that message: dsh's built-in "a message another agent addressed to this one". */
|
|
130
|
+
const RELAY_FORM = "relay";
|
|
131
|
+
//#endregion
|
|
132
|
+
export { ProtocolFile as i, SOULMIRROR_PLUGIN as n, FriendSettingsStore as r, RELAY_FORM as t };
|
|
133
|
+
|
|
134
|
+
//# sourceMappingURL=events-Cz7g9aXF.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"events-Cz7g9aXF.js","names":[],"sources":["../src/friend-settings.ts","../src/events.ts"],"sourcesContent":["/**\n * The plugin's own per-friend settings (P3) and the diplomacy protocol file.\n *\n * - `<home>/a2a/dsh-friends.json` — `{ friends: { [fp]: { tier } } }`: the\n * reply tier per friend (`notify` | `draft` | `auto`). The peer's\n * friends.yaml has no such field (the soulnet a2a package deliberately\n * carries no auto_reply switch), so it lives beside the session map.\n * The per-friend PROTOCOL OVERRIDE is NOT here: the peer stores it in\n * friends.yaml (`friends.set {protocol}`), shared with SoulMirror.\n * - `<home>/a2a/protocol.md` — the global diplomacy protocol. The peer\n * writes the default text when the identity is created; this module\n * reads it (cached by mtime so the prompt variable can be evaluated on\n * every assembly) and writes it (Settings / page editor).\n */\nimport { mkdirSync, readFileSync, statSync, writeFileSync } from 'node:fs'\nimport { mkdir, readFile, writeFile } from 'node:fs/promises'\nimport { dirname, join } from 'node:path'\nimport { normalizeTier, type ReplyTier } from './policy.ts'\n\nexport const FRIEND_SETTINGS_FILE = 'dsh-friends.json'\nexport const PROTOCOL_FILE = 'protocol.md'\n\nexport interface FriendSettings {\n readonly tier?: ReplyTier\n}\n\ninterface FriendSettingsFile {\n friends: Record<string, FriendSettings>\n}\n\n/** In-memory copy of dsh-friends.json with write-through. */\nexport class FriendSettingsStore {\n private data: FriendSettingsFile = { friends: {} }\n private loaded = false\n\n constructor(private readonly path: string) {}\n\n static at(a2aDir: string): FriendSettingsStore {\n return new FriendSettingsStore(join(a2aDir, FRIEND_SETTINGS_FILE))\n }\n\n async load(): Promise<void> {\n try {\n const raw = JSON.parse(await readFile(this.path, 'utf8')) as Partial<FriendSettingsFile>\n const friends: Record<string, FriendSettings> = {}\n for (const [fp, value] of Object.entries(raw.friends ?? {})) {\n const v = (typeof value === 'object' && value !== null ? value : {}) as Record<string, unknown>\n friends[fp] = { ...(v['tier'] === undefined ? {} : { tier: normalizeTier(v['tier']) }) }\n }\n this.data = { friends }\n } catch {\n this.data = { friends: {} }\n }\n this.loaded = true\n }\n\n get isLoaded(): boolean {\n return this.loaded\n }\n\n get(fp: string): FriendSettings {\n return this.data.friends[fp] ?? {}\n }\n\n /** Tier of a friend, or `fallback` (the global default) when none is stored. */\n tier(fp: string, fallback: ReplyTier): ReplyTier {\n return this.data.friends[fp]?.tier ?? fallback\n }\n\n /** Every stored entry (fp → settings). */\n all(): Readonly<Record<string, FriendSettings>> {\n return this.data.friends\n }\n\n /** Patch one friend's settings; an explicit `tier: undefined` clears the stored tier (back to the global default). */\n async set(fp: string, patch: { tier?: ReplyTier | undefined }): Promise<FriendSettings> {\n const current = this.get(fp)\n const tier = 'tier' in patch ? patch.tier : current.tier\n if (tier === undefined) delete this.data.friends[fp]\n else this.data.friends[fp] = { ...current, tier }\n await mkdir(dirname(this.path), { recursive: true })\n await writeFile(this.path, JSON.stringify(this.data, null, 2), 'utf8')\n return this.get(fp)\n }\n\n async remove(fp: string): Promise<void> {\n if (!(fp in this.data.friends)) return\n delete this.data.friends[fp]\n await writeFile(this.path, JSON.stringify(this.data, null, 2), 'utf8')\n }\n}\n\n/**\n * The global diplomacy protocol file. `read()` is synchronous and cached by\n * mtime so a prompt-variable provider can call it on every assembly; `write()`\n * replaces the file (and the cache).\n */\nexport class ProtocolFile {\n private cache: { mtimeMs: number; size: number; text: string } | undefined\n\n constructor(readonly path: string) {}\n\n static at(a2aDir: string): ProtocolFile {\n return new ProtocolFile(join(a2aDir, PROTOCOL_FILE))\n }\n\n /** Current text; `''` when the file does not exist yet (the peer writes it with the identity). */\n read(): string {\n try {\n const stat = statSync(this.path)\n if (this.cache !== undefined && this.cache.mtimeMs === stat.mtimeMs && this.cache.size === stat.size) return this.cache.text\n const text = readFileSync(this.path, 'utf8')\n this.cache = { mtimeMs: stat.mtimeMs, size: stat.size, text }\n return text\n } catch {\n this.cache = undefined\n return ''\n }\n }\n\n exists(): boolean {\n try {\n statSync(this.path)\n return true\n } catch {\n return false\n }\n }\n\n write(text: string): void {\n mkdirSync(dirname(this.path), { recursive: true })\n writeFileSync(this.path, text, 'utf8')\n this.cache = undefined\n }\n}\n","/**\n * Shared vocabulary of @startupworld-ai/soulnet-dsh: branded ids and the\n * `user/message` source tag that marks an inbound A2A mail.\n *\n * There is deliberately NO custom SessionEventMap merge here any more. The P0\n * spike (dsh/SPIKE.md §1) showed that a custom durable event type makes the\n * session log unloadable (the persistence read path refuses unknown,\n * non-ignorable types), so every inbound mail is written as an ordinary\n * `user/message` whose `source` carries the fields below; the client half\n * recognises that source and renders the message as a chat bubble.\n *\n * Keep this file free of runtime imports apart from constants: the client\n * bundle imports it as well.\n */\nimport type { Branded } from '@deepseek-ai/dsh-brand'\n\n/** Stable A2A message id (the relay envelope id; replay-safe). */\nexport type A2AMessageId = Branded<'A2AMessageId'>\n/** Fingerprint of a peer's signing key (the A2A identity). */\nexport type Fingerprint = Branded<'Fingerprint'>\n\n/** `source.plugin` of the `user/message` carrying an inbound A2A mail. */\nexport const SOULMIRROR_PLUGIN = 'soulmirror'\n/** `source.form` of that message: dsh's built-in \"a message another agent addressed to this one\". */\nexport const RELAY_FORM = 'relay'\n\n/** The extra `a2a` block the host attaches to the relay source (wire-extensible; the literal type is closed upstream). */\nexport interface A2ASourceMeta {\n readonly id: string\n readonly fp: string\n readonly ts: number\n readonly auto?: true\n /** A2A message type (`text`, `app_share`, …); absent means `text`. */\n readonly type?: string\n}\n\n/** Shape the client renderer derives from such a `user/message` (one bubble). */\nexport interface A2AMessageData {\n readonly id: A2AMessageId\n /** `in`: friend → me; `out`: me → friend. */\n readonly dir: 'in' | 'out'\n /** Peer fingerprint (the friend this session belongs to). */\n readonly fp: Fingerprint\n /** Display name of the peer at send time. */\n readonly name: string\n readonly body: string\n /** Unix epoch ms at the sender. */\n readonly ts: number\n /** Outbound delivery state; absent on inbound. */\n readonly delivery?: 'queued' | 'sent' | 'failed'\n /** Set when the message was produced by a peer's auto-reply (loop guard). */\n readonly auto?: true\n}\n"],"mappings":"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"}
|
|
@@ -0,0 +1,134 @@
|
|
|
1
|
+
import { o as normalizeTier } from "./send-DcYQC5CC.js";
|
|
2
|
+
import { dirname, join } from "node:path";
|
|
3
|
+
import { mkdirSync, readFileSync, statSync, writeFileSync } from "node:fs";
|
|
4
|
+
import { mkdir, readFile, writeFile } from "node:fs/promises";
|
|
5
|
+
//#region src/friend-settings.ts
|
|
6
|
+
/**
|
|
7
|
+
* The plugin's own per-friend settings (P3) and the diplomacy protocol file.
|
|
8
|
+
*
|
|
9
|
+
* - `<home>/a2a/dsh-friends.json` — `{ friends: { [fp]: { tier } } }`: the
|
|
10
|
+
* reply tier per friend (`notify` | `draft` | `auto`). The peer's
|
|
11
|
+
* friends.yaml has no such field (the soulnet a2a package deliberately
|
|
12
|
+
* carries no auto_reply switch), so it lives beside the session map.
|
|
13
|
+
* The per-friend PROTOCOL OVERRIDE is NOT here: the peer stores it in
|
|
14
|
+
* friends.yaml (`friends.set {protocol}`), shared with SoulMirror.
|
|
15
|
+
* - `<home>/a2a/protocol.md` — the global diplomacy protocol. The peer
|
|
16
|
+
* writes the default text when the identity is created; this module
|
|
17
|
+
* reads it (cached by mtime so the prompt variable can be evaluated on
|
|
18
|
+
* every assembly) and writes it (Settings / page editor).
|
|
19
|
+
*/
|
|
20
|
+
const FRIEND_SETTINGS_FILE = "dsh-friends.json";
|
|
21
|
+
const PROTOCOL_FILE = "protocol.md";
|
|
22
|
+
/** In-memory copy of dsh-friends.json with write-through. */
|
|
23
|
+
var FriendSettingsStore = class FriendSettingsStore {
|
|
24
|
+
path;
|
|
25
|
+
data = { friends: {} };
|
|
26
|
+
loaded = false;
|
|
27
|
+
constructor(path) {
|
|
28
|
+
this.path = path;
|
|
29
|
+
}
|
|
30
|
+
static at(a2aDir) {
|
|
31
|
+
return new FriendSettingsStore(join(a2aDir, FRIEND_SETTINGS_FILE));
|
|
32
|
+
}
|
|
33
|
+
async load() {
|
|
34
|
+
try {
|
|
35
|
+
const raw = JSON.parse(await readFile(this.path, "utf8"));
|
|
36
|
+
const friends = {};
|
|
37
|
+
for (const [fp, value] of Object.entries(raw.friends ?? {})) {
|
|
38
|
+
const v = typeof value === "object" && value !== null ? value : {};
|
|
39
|
+
friends[fp] = { ...v["tier"] === void 0 ? {} : { tier: normalizeTier(v["tier"]) } };
|
|
40
|
+
}
|
|
41
|
+
this.data = { friends };
|
|
42
|
+
} catch {
|
|
43
|
+
this.data = { friends: {} };
|
|
44
|
+
}
|
|
45
|
+
this.loaded = true;
|
|
46
|
+
}
|
|
47
|
+
get isLoaded() {
|
|
48
|
+
return this.loaded;
|
|
49
|
+
}
|
|
50
|
+
get(fp) {
|
|
51
|
+
return this.data.friends[fp] ?? {};
|
|
52
|
+
}
|
|
53
|
+
/** Tier of a friend, or `fallback` (the global default) when none is stored. */
|
|
54
|
+
tier(fp, fallback) {
|
|
55
|
+
return this.data.friends[fp]?.tier ?? fallback;
|
|
56
|
+
}
|
|
57
|
+
/** Every stored entry (fp → settings). */
|
|
58
|
+
all() {
|
|
59
|
+
return this.data.friends;
|
|
60
|
+
}
|
|
61
|
+
/** Patch one friend's settings; an explicit `tier: undefined` clears the stored tier (back to the global default). */
|
|
62
|
+
async set(fp, patch) {
|
|
63
|
+
const current = this.get(fp);
|
|
64
|
+
const tier = "tier" in patch ? patch.tier : current.tier;
|
|
65
|
+
if (tier === void 0) delete this.data.friends[fp];
|
|
66
|
+
else this.data.friends[fp] = {
|
|
67
|
+
...current,
|
|
68
|
+
tier
|
|
69
|
+
};
|
|
70
|
+
await mkdir(dirname(this.path), { recursive: true });
|
|
71
|
+
await writeFile(this.path, JSON.stringify(this.data, null, 2), "utf8");
|
|
72
|
+
return this.get(fp);
|
|
73
|
+
}
|
|
74
|
+
async remove(fp) {
|
|
75
|
+
if (!(fp in this.data.friends)) return;
|
|
76
|
+
delete this.data.friends[fp];
|
|
77
|
+
await writeFile(this.path, JSON.stringify(this.data, null, 2), "utf8");
|
|
78
|
+
}
|
|
79
|
+
};
|
|
80
|
+
/**
|
|
81
|
+
* The global diplomacy protocol file. `read()` is synchronous and cached by
|
|
82
|
+
* mtime so a prompt-variable provider can call it on every assembly; `write()`
|
|
83
|
+
* replaces the file (and the cache).
|
|
84
|
+
*/
|
|
85
|
+
var ProtocolFile = class ProtocolFile {
|
|
86
|
+
path;
|
|
87
|
+
cache;
|
|
88
|
+
constructor(path) {
|
|
89
|
+
this.path = path;
|
|
90
|
+
}
|
|
91
|
+
static at(a2aDir) {
|
|
92
|
+
return new ProtocolFile(join(a2aDir, PROTOCOL_FILE));
|
|
93
|
+
}
|
|
94
|
+
/** Current text; `''` when the file does not exist yet (the peer writes it with the identity). */
|
|
95
|
+
read() {
|
|
96
|
+
try {
|
|
97
|
+
const stat = statSync(this.path);
|
|
98
|
+
if (this.cache !== void 0 && this.cache.mtimeMs === stat.mtimeMs && this.cache.size === stat.size) return this.cache.text;
|
|
99
|
+
const text = readFileSync(this.path, "utf8");
|
|
100
|
+
this.cache = {
|
|
101
|
+
mtimeMs: stat.mtimeMs,
|
|
102
|
+
size: stat.size,
|
|
103
|
+
text
|
|
104
|
+
};
|
|
105
|
+
return text;
|
|
106
|
+
} catch {
|
|
107
|
+
this.cache = void 0;
|
|
108
|
+
return "";
|
|
109
|
+
}
|
|
110
|
+
}
|
|
111
|
+
exists() {
|
|
112
|
+
try {
|
|
113
|
+
statSync(this.path);
|
|
114
|
+
return true;
|
|
115
|
+
} catch {
|
|
116
|
+
return false;
|
|
117
|
+
}
|
|
118
|
+
}
|
|
119
|
+
write(text) {
|
|
120
|
+
mkdirSync(dirname(this.path), { recursive: true });
|
|
121
|
+
writeFileSync(this.path, text, "utf8");
|
|
122
|
+
this.cache = void 0;
|
|
123
|
+
}
|
|
124
|
+
};
|
|
125
|
+
//#endregion
|
|
126
|
+
//#region src/events.ts
|
|
127
|
+
/** `source.plugin` of the `user/message` carrying an inbound A2A mail. */
|
|
128
|
+
const SOULMIRROR_PLUGIN = "soulmirror";
|
|
129
|
+
/** `source.form` of that message: dsh's built-in "a message another agent addressed to this one". */
|
|
130
|
+
const RELAY_FORM = "relay";
|
|
131
|
+
//#endregion
|
|
132
|
+
export { ProtocolFile as i, SOULMIRROR_PLUGIN as n, FriendSettingsStore as r, RELAY_FORM as t };
|
|
133
|
+
|
|
134
|
+
//# sourceMappingURL=events-DHPK3pow.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"events-DHPK3pow.js","names":[],"sources":["../src/friend-settings.ts","../src/events.ts"],"sourcesContent":["/**\n * The plugin's own per-friend settings (P3) and the diplomacy protocol file.\n *\n * - `<home>/a2a/dsh-friends.json` — `{ friends: { [fp]: { tier } } }`: the\n * reply tier per friend (`notify` | `draft` | `auto`). The peer's\n * friends.yaml has no such field (the soulnet a2a package deliberately\n * carries no auto_reply switch), so it lives beside the session map.\n * The per-friend PROTOCOL OVERRIDE is NOT here: the peer stores it in\n * friends.yaml (`friends.set {protocol}`), shared with SoulMirror.\n * - `<home>/a2a/protocol.md` — the global diplomacy protocol. The peer\n * writes the default text when the identity is created; this module\n * reads it (cached by mtime so the prompt variable can be evaluated on\n * every assembly) and writes it (Settings / page editor).\n */\nimport { mkdirSync, readFileSync, statSync, writeFileSync } from 'node:fs'\nimport { mkdir, readFile, writeFile } from 'node:fs/promises'\nimport { dirname, join } from 'node:path'\nimport { normalizeTier, type ReplyTier } from './policy.ts'\n\nexport const FRIEND_SETTINGS_FILE = 'dsh-friends.json'\nexport const PROTOCOL_FILE = 'protocol.md'\n\nexport interface FriendSettings {\n readonly tier?: ReplyTier\n}\n\ninterface FriendSettingsFile {\n friends: Record<string, FriendSettings>\n}\n\n/** In-memory copy of dsh-friends.json with write-through. */\nexport class FriendSettingsStore {\n private data: FriendSettingsFile = { friends: {} }\n private loaded = false\n\n constructor(private readonly path: string) {}\n\n static at(a2aDir: string): FriendSettingsStore {\n return new FriendSettingsStore(join(a2aDir, FRIEND_SETTINGS_FILE))\n }\n\n async load(): Promise<void> {\n try {\n const raw = JSON.parse(await readFile(this.path, 'utf8')) as Partial<FriendSettingsFile>\n const friends: Record<string, FriendSettings> = {}\n for (const [fp, value] of Object.entries(raw.friends ?? {})) {\n const v = (typeof value === 'object' && value !== null ? value : {}) as Record<string, unknown>\n friends[fp] = { ...(v['tier'] === undefined ? {} : { tier: normalizeTier(v['tier']) }) }\n }\n this.data = { friends }\n } catch {\n this.data = { friends: {} }\n }\n this.loaded = true\n }\n\n get isLoaded(): boolean {\n return this.loaded\n }\n\n get(fp: string): FriendSettings {\n return this.data.friends[fp] ?? {}\n }\n\n /** Tier of a friend, or `fallback` (the global default) when none is stored. */\n tier(fp: string, fallback: ReplyTier): ReplyTier {\n return this.data.friends[fp]?.tier ?? fallback\n }\n\n /** Every stored entry (fp → settings). */\n all(): Readonly<Record<string, FriendSettings>> {\n return this.data.friends\n }\n\n /** Patch one friend's settings; an explicit `tier: undefined` clears the stored tier (back to the global default). */\n async set(fp: string, patch: { tier?: ReplyTier | undefined }): Promise<FriendSettings> {\n const current = this.get(fp)\n const tier = 'tier' in patch ? patch.tier : current.tier\n if (tier === undefined) delete this.data.friends[fp]\n else this.data.friends[fp] = { ...current, tier }\n await mkdir(dirname(this.path), { recursive: true })\n await writeFile(this.path, JSON.stringify(this.data, null, 2), 'utf8')\n return this.get(fp)\n }\n\n async remove(fp: string): Promise<void> {\n if (!(fp in this.data.friends)) return\n delete this.data.friends[fp]\n await writeFile(this.path, JSON.stringify(this.data, null, 2), 'utf8')\n }\n}\n\n/**\n * The global diplomacy protocol file. `read()` is synchronous and cached by\n * mtime so a prompt-variable provider can call it on every assembly; `write()`\n * replaces the file (and the cache).\n */\nexport class ProtocolFile {\n private cache: { mtimeMs: number; size: number; text: string } | undefined\n\n constructor(readonly path: string) {}\n\n static at(a2aDir: string): ProtocolFile {\n return new ProtocolFile(join(a2aDir, PROTOCOL_FILE))\n }\n\n /** Current text; `''` when the file does not exist yet (the peer writes it with the identity). */\n read(): string {\n try {\n const stat = statSync(this.path)\n if (this.cache !== undefined && this.cache.mtimeMs === stat.mtimeMs && this.cache.size === stat.size) return this.cache.text\n const text = readFileSync(this.path, 'utf8')\n this.cache = { mtimeMs: stat.mtimeMs, size: stat.size, text }\n return text\n } catch {\n this.cache = undefined\n return ''\n }\n }\n\n exists(): boolean {\n try {\n statSync(this.path)\n return true\n } catch {\n return false\n }\n }\n\n write(text: string): void {\n mkdirSync(dirname(this.path), { recursive: true })\n writeFileSync(this.path, text, 'utf8')\n this.cache = undefined\n }\n}\n","/**\n * Shared vocabulary of soulnet-dsh: branded ids and the\n * `user/message` source tag that marks an inbound A2A mail.\n *\n * There is deliberately NO custom SessionEventMap merge here any more. The P0\n * spike (dsh/SPIKE.md §1) showed that a custom durable event type makes the\n * session log unloadable (the persistence read path refuses unknown,\n * non-ignorable types), so every inbound mail is written as an ordinary\n * `user/message` whose `source` carries the fields below; the client half\n * recognises that source and renders the message as a chat bubble.\n *\n * P4: the same relay source also carries the plugin's own NOTES into the\n * alter session (the owner approved / rejected / edited a draft) — marked by\n * `a2a.note`, never woken as a turn, rendered as a system line.\n *\n * Keep this file free of runtime imports apart from constants: the client\n * bundle imports it as well.\n */\nimport type { Branded } from '@deepseek-ai/dsh-brand'\n\n/** Stable A2A message id (the relay envelope id; replay-safe). */\nexport type A2AMessageId = Branded<'A2AMessageId'>\n/** Fingerprint of a peer's signing key (the A2A identity). */\nexport type Fingerprint = Branded<'Fingerprint'>\n\n/** `source.plugin` of the `user/message` carrying an inbound A2A mail. */\nexport const SOULMIRROR_PLUGIN = 'soulmirror'\n/** `source.form` of that message: dsh's built-in \"a message another agent addressed to this one\". */\nexport const RELAY_FORM = 'relay'\n\n/** Kinds of plugin notes written into the alter session (P4 draft decisions). */\nexport type A2ANoteKind = 'draft-approved' | 'draft-rejected' | 'draft-revise'\n\n/** The extra `a2a` block the host attaches to the relay source (wire-extensible; the literal type is closed upstream). */\nexport interface A2ASourceMeta {\n readonly id: string\n readonly fp: string\n readonly ts: number\n readonly auto?: true\n /** A2A message type (`text`, `app_share`, …); absent means `text`. */\n readonly type?: string\n /** Set on the plugin's own notes (draft decisions): not mail, never a turn trigger. */\n readonly note?: A2ANoteKind\n /** Draft id the note refers to. */\n readonly draftId?: string\n}\n\n/** Shape the client renderer derives from such a `user/message` (one bubble). */\nexport interface A2AMessageData {\n readonly id: A2AMessageId\n /** `in`: friend → me; `out`: me → friend. */\n readonly dir: 'in' | 'out'\n /** Peer fingerprint (the friend this message belongs to). */\n readonly fp: Fingerprint\n /** Display name of the peer at send time. */\n readonly name: string\n readonly body: string\n /** Unix epoch ms at the sender. */\n readonly ts: number\n /** Outbound delivery state; absent on inbound. */\n readonly delivery?: 'queued' | 'sent' | 'failed'\n /** Set when the message was produced by a peer's auto-reply (loop guard). */\n readonly auto?: true\n /** Set when the row is a plugin note (draft decision), not mail. */\n readonly note?: A2ANoteKind\n}\n"],"mappings":"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"}
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
//#region src/events.ts
|
|
2
|
+
/** `source.plugin` of the `user/message` carrying an inbound A2A mail. */
|
|
3
|
+
const SOULMIRROR_PLUGIN = "soulmirror";
|
|
4
|
+
/** `source.form` of that message: dsh's built-in "a message another agent addressed to this one". */
|
|
5
|
+
const RELAY_FORM = "relay";
|
|
6
|
+
//#endregion
|
|
7
|
+
export { SOULMIRROR_PLUGIN as n, RELAY_FORM as t };
|
|
8
|
+
|
|
9
|
+
//# sourceMappingURL=events-DViXNRAZ.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"events-DViXNRAZ.js","names":[],"sources":["../src/events.ts"],"sourcesContent":["/**\n * Shared vocabulary of @startupworld-ai/soulnet-dsh: branded ids and the\n * `user/message` source tag that marks an inbound A2A mail.\n *\n * There is deliberately NO custom SessionEventMap merge here any more. The P0\n * spike (dsh/SPIKE.md §1) showed that a custom durable event type makes the\n * session log unloadable (the persistence read path refuses unknown,\n * non-ignorable types), so every inbound mail is written as an ordinary\n * `user/message` whose `source` carries the fields below; the client half\n * recognises that source and renders the message as a chat bubble.\n *\n * Keep this file free of runtime imports apart from constants: the client\n * bundle imports it as well.\n */\nimport type { Branded } from '@deepseek-ai/dsh-brand'\n\n/** Stable A2A message id (the relay envelope id; replay-safe). */\nexport type A2AMessageId = Branded<'A2AMessageId'>\n/** Fingerprint of a peer's signing key (the A2A identity). */\nexport type Fingerprint = Branded<'Fingerprint'>\n\n/** `source.plugin` of the `user/message` carrying an inbound A2A mail. */\nexport const SOULMIRROR_PLUGIN = 'soulmirror'\n/** `source.form` of that message: dsh's built-in \"a message another agent addressed to this one\". */\nexport const RELAY_FORM = 'relay'\n\n/** The extra `a2a` block the host attaches to the relay source (wire-extensible; the literal type is closed upstream). */\nexport interface A2ASourceMeta {\n readonly id: string\n readonly fp: string\n readonly ts: number\n readonly auto?: true\n /** A2A message type (`text`, `app_share`, …); absent means `text`. */\n readonly type?: string\n}\n\n/** Shape the client renderer derives from such a `user/message` (one bubble). */\nexport interface A2AMessageData {\n readonly id: A2AMessageId\n /** `in`: friend → me; `out`: me → friend. */\n readonly dir: 'in' | 'out'\n /** Peer fingerprint (the friend this session belongs to). */\n readonly fp: Fingerprint\n /** Display name of the peer at send time. */\n readonly name: string\n readonly body: string\n /** Unix epoch ms at the sender. */\n readonly ts: number\n /** Outbound delivery state; absent on inbound. */\n readonly delivery?: 'queued' | 'sent' | 'failed'\n /** Set when the message was produced by a peer's auto-reply (loop guard). */\n readonly auto?: true\n}\n"],"mappings":";;AAsBA,MAAa,oBAAoB;;AAEjC,MAAa,aAAa"}
|