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
@@ -0,0 +1,127 @@
1
+ //#region src/policy.ts
2
+ const DEFAULT_REPLY_TIER = "draft";
3
+ function isReplyTier(value) {
4
+ return value === "notify" || value === "draft" || value === "auto";
5
+ }
6
+ function normalizeTier(value, fallback = DEFAULT_REPLY_TIER) {
7
+ return isReplyTier(value) ? value : fallback;
8
+ }
9
+ const UNKNOWN_TRIGGER = { kind: "unknown" };
10
+ function routeInbound(input) {
11
+ if (!input.isFriend) return {
12
+ action: "append",
13
+ reason: "not-a-friend"
14
+ };
15
+ if (input.auto) return {
16
+ action: "append",
17
+ reason: "loop-guard-auto"
18
+ };
19
+ if (input.tier === "notify") return {
20
+ action: "append",
21
+ reason: "tier-notify"
22
+ };
23
+ return { action: "wake" };
24
+ }
25
+ function sendGate(input) {
26
+ switch (input.trigger.kind) {
27
+ case "owner": return {
28
+ kind: "allow",
29
+ auto: false,
30
+ reason: "owner-initiated"
31
+ };
32
+ case "inbound-auto": return {
33
+ kind: "draft",
34
+ reason: "loop-guard-auto"
35
+ };
36
+ case "inbound":
37
+ if (input.trigger.fp !== void 0 && input.trigger.fp !== input.target) return {
38
+ kind: "draft",
39
+ reason: "other-friend"
40
+ };
41
+ if (input.tier === "auto") {
42
+ if (input.limit > 0 && input.autoSentInWindow < input.limit) return {
43
+ kind: "allow",
44
+ auto: true,
45
+ reason: "auto-tier"
46
+ };
47
+ return {
48
+ kind: "draft",
49
+ reason: "rate-limited"
50
+ };
51
+ }
52
+ return {
53
+ kind: "draft",
54
+ reason: input.tier === "draft" ? "draft-tier" : "notify-tier"
55
+ };
56
+ default: return {
57
+ kind: "draft",
58
+ reason: "unknown-trigger"
59
+ };
60
+ }
61
+ }
62
+ const HOUR_MS = 36e5;
63
+ /** Sliding one-hour window of timestamps per key (friend fingerprint). */
64
+ var HourlyWindow = class {
65
+ windowMs;
66
+ hits = /* @__PURE__ */ new Map();
67
+ constructor(windowMs = HOUR_MS) {
68
+ this.windowMs = windowMs;
69
+ }
70
+ /** Timestamps still inside the window for `key`, oldest first. */
71
+ count(key, now = Date.now()) {
72
+ return this.prune(key, now).length;
73
+ }
74
+ /** Record one hit for `key`; returns the new count. */
75
+ record(key, now = Date.now()) {
76
+ const list = this.prune(key, now);
77
+ list.push(now);
78
+ this.hits.set(key, list);
79
+ return list.length;
80
+ }
81
+ /** Milliseconds until the oldest hit leaves the window (0 when none). */
82
+ retryAfter(key, now = Date.now()) {
83
+ const list = this.prune(key, now);
84
+ if (list.length === 0) return 0;
85
+ return Math.max(0, list[0] + this.windowMs - now);
86
+ }
87
+ prune(key, now) {
88
+ const list = (this.hits.get(key) ?? []).filter((ts) => now - ts < this.windowMs);
89
+ if (list.length === 0) this.hits.delete(key);
90
+ else this.hits.set(key, list);
91
+ return list;
92
+ }
93
+ };
94
+ //#endregion
95
+ //#region src/network/send.ts
96
+ async function sendAndArchive(client, fp, body, options) {
97
+ const receipt = await client.send(fp, body, options);
98
+ let entry;
99
+ if (receipt.seq !== void 0 && receipt.seq > 0) try {
100
+ const { entries } = await client.conversation(fp, {
101
+ since: receipt.seq - 1,
102
+ limit: 1
103
+ });
104
+ entry = entries.find((e) => e.seq === receipt.seq);
105
+ } catch {}
106
+ entry ??= {
107
+ seq: receipt.seq ?? 0,
108
+ dir: "out",
109
+ id: receipt.id,
110
+ body,
111
+ ts: Date.now(),
112
+ status: receipt.status,
113
+ ...options?.auto === true ? { auto: true } : {}
114
+ };
115
+ return {
116
+ entry,
117
+ receipt: {
118
+ id: receipt.id,
119
+ status: receipt.status,
120
+ ...receipt.seq === void 0 ? {} : { seq: receipt.seq }
121
+ }
122
+ };
123
+ }
124
+ //#endregion
125
+ export { isReplyTier as a, sendGate as c, UNKNOWN_TRIGGER as i, DEFAULT_REPLY_TIER as n, normalizeTier as o, HourlyWindow as r, routeInbound as s, sendAndArchive as t };
126
+
127
+ //# sourceMappingURL=send-DcYQC5CC.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"send-DcYQC5CC.js","names":[],"sources":["../src/policy.ts","../src/network/send.ts"],"sourcesContent":["/**\n * Reply policy of the alter (P3, reshaped in P4 for the single alter session)\n * — pure, no I/O, unit-tested in test/policy.test.ts:\n *\n * - `ReplyTier` per friend: `notify` (inbound mail is appended to the alter\n * session, no turn), `draft` (a turn is woken; the alter's reply is stored\n * as a PENDING DRAFT the owner reviews on the SoulMirror page), `auto` (a\n * turn is woken; the reply is sent without review, rate-limited per\n * friend per hour).\n * - `routeInbound()` — what to do with one inbound mail: append only, or\n * append AND wake the alter. The loop guard lives here: mail flagged\n * `auto` (another alter's automatic reply) never wakes a turn, and mail\n * from a non-friend never does either.\n * - `sendGate()` — whether `soulmirror_send_message` may send now or must\n * queue a draft. Owner-initiated turns send freely (SoulMirror rule:\n * friend messages on the owner's instruction are direct; only tasks /\n * money need confirmation). Inbound-triggered turns send freely only to\n * the friend who wrote, in the `auto` tier and under the hourly cap;\n * everything else becomes a draft for the owner. There is no dsh\n * approval panel in this path any more (P4).\n * - `HourlyWindow` — the per-friend sliding-window counter behind the cap.\n */\n\nexport type ReplyTier = 'notify' | 'draft' | 'auto'\n\nexport const REPLY_TIERS: readonly ReplyTier[] = ['notify', 'draft', 'auto']\n\nexport const DEFAULT_REPLY_TIER: ReplyTier = 'draft'\n\nexport const DEFAULT_AUTO_REPLY_PER_HOUR = 20\n\nexport function isReplyTier(value: unknown): value is ReplyTier {\n return value === 'notify' || value === 'draft' || value === 'auto'\n}\n\nexport function normalizeTier(value: unknown, fallback: ReplyTier = DEFAULT_REPLY_TIER): ReplyTier {\n return isReplyTier(value) ? value : fallback\n}\n\n/**\n * What woke the current turn of the alter session:\n * - `owner`: the owner typed an instruction (a `user/message` with\n * `source.kind === 'user'`) — from the SoulMirror page composer or dsh's\n * own input bar. `fp` is never set: an instruction that names a friend\n * is still the owner speaking (the persona resolves the friend as \"none\");\n * - `inbound`: mail from a friend woke the turn; `fp` / `name` identify\n * that friend (the per-friend tier and protocol override are resolved from\n * it, per turn);\n * - `inbound-auto`: that mail was itself flagged `auto` (should never wake a\n * turn — kept for the belt-and-braces branch in the gate);\n * - `unknown`: no user message in the current turn could be attributed (a\n * tool call from another session, a steer, a resumed turn …).\n */\nexport interface TurnTrigger {\n readonly kind: 'owner' | 'inbound' | 'inbound-auto' | 'unknown'\n /** Fingerprint of the friend whose mail woke the turn (inbound kinds only). */\n readonly fp?: string\n /** Display name of that friend at delivery time. */\n readonly name?: string\n /** A2A id of that mail. */\n readonly messageId?: string\n}\n\nexport const UNKNOWN_TRIGGER: TurnTrigger = { kind: 'unknown' }\n\nexport interface InboundRoute {\n /** Wake a model turn (the mail is delivered through the agent inbox) or append only. */\n readonly action: 'wake' | 'append'\n /** Why it was not woken (logged). */\n readonly reason?: 'tier-notify' | 'loop-guard-auto' | 'not-a-friend'\n}\n\nexport interface RouteInboundInput {\n readonly tier: ReplyTier\n /** The mail carries the A2A `auto` flag (another alter's automatic reply). */\n readonly auto: boolean\n /** The sender is in the friend list (the peer only archives friend mail, but the session layer may lag). */\n readonly isFriend: boolean\n}\n\nexport function routeInbound(input: RouteInboundInput): InboundRoute {\n if (!input.isFriend) return { action: 'append', reason: 'not-a-friend' }\n if (input.auto) return { action: 'append', reason: 'loop-guard-auto' }\n if (input.tier === 'notify') return { action: 'append', reason: 'tier-notify' }\n return { action: 'wake' }\n}\n\nexport type SendGateReason =\n | 'owner-initiated' | 'auto-tier'\n | 'draft-tier' | 'notify-tier' | 'rate-limited' | 'loop-guard-auto' | 'unknown-trigger' | 'other-friend'\n\nexport type SendDecision =\n /** Send now; `auto` says whether to flag the wire message as an automatic reply. */\n | { readonly kind: 'allow'; readonly auto: boolean; readonly reason: 'owner-initiated' | 'auto-tier' }\n /** Do not send: store a pending draft for the owner to review on the page. */\n | { readonly kind: 'draft'; readonly reason: Exclude<SendGateReason, 'owner-initiated' | 'auto-tier'> }\n\nexport interface SendGateInput {\n readonly trigger: TurnTrigger\n /** The target friend of this send. */\n readonly target: string\n /** Effective reply tier of the TARGET friend. */\n readonly tier: ReplyTier\n /** Automatic replies already sent to the target in the current hour window. */\n readonly autoSentInWindow: number\n /** `autoReplyPerHour` (<= 0 disables automatic sends entirely). */\n readonly limit: number\n}\n\nexport function sendGate(input: SendGateInput): SendDecision {\n switch (input.trigger.kind) {\n case 'owner':\n return { kind: 'allow', auto: false, reason: 'owner-initiated' }\n case 'inbound-auto':\n return { kind: 'draft', reason: 'loop-guard-auto' }\n case 'inbound':\n // The alter answering friend A by writing to friend B is not an auto reply to A.\n if (input.trigger.fp !== undefined && input.trigger.fp !== input.target) return { kind: 'draft', reason: 'other-friend' }\n if (input.tier === 'auto') {\n if (input.limit > 0 && input.autoSentInWindow < input.limit) return { kind: 'allow', auto: true, reason: 'auto-tier' }\n return { kind: 'draft', reason: 'rate-limited' }\n }\n return { kind: 'draft', reason: input.tier === 'draft' ? 'draft-tier' : 'notify-tier' }\n case 'unknown':\n default:\n return { kind: 'draft', reason: 'unknown-trigger' }\n }\n}\n\nexport const HOUR_MS = 3_600_000\n\n/** Sliding one-hour window of timestamps per key (friend fingerprint). */\nexport class HourlyWindow {\n private readonly hits = new Map<string, number[]>()\n\n constructor(private readonly windowMs: number = HOUR_MS) {}\n\n /** Timestamps still inside the window for `key`, oldest first. */\n count(key: string, now: number = Date.now()): number {\n return this.prune(key, now).length\n }\n\n /** Record one hit for `key`; returns the new count. */\n record(key: string, now: number = Date.now()): number {\n const list = this.prune(key, now)\n list.push(now)\n this.hits.set(key, list)\n return list.length\n }\n\n /** Milliseconds until the oldest hit leaves the window (0 when none). */\n retryAfter(key: string, now: number = Date.now()): number {\n const list = this.prune(key, now)\n if (list.length === 0) return 0\n return Math.max(0, list[0]! + this.windowMs - now)\n }\n\n private prune(key: string, now: number): number[] {\n const list = (this.hits.get(key) ?? []).filter(ts => now - ts < this.windowMs)\n if (list.length === 0) this.hits.delete(key)\n else this.hits.set(key, list)\n return list\n }\n}\n","/**\n * Send through the peer and read the archived entry back (shared by the\n * browser API's direct send and the alter's `soulmirror_send_message` tool):\n * the peer answers `seq` / `status`; the archive line carries the exact `ts`\n * and `auto` flag, which the SoulMirror page needs to reconcile its\n * optimistic bubble or to paint the alter's send. Falls back to a constructed\n * entry when the read-back misses.\n */\nimport type { Fingerprint } from '../events.ts'\nimport type { ConversationEntry, NetworkClient, SendOptions, SendReceipt } from './types.ts'\n\nexport interface SentEntry {\n readonly entry: ConversationEntry\n readonly receipt: { id: string; seq?: number; status: string }\n}\n\nexport async function sendAndArchive(client: NetworkClient, fp: Fingerprint, body: string, options?: SendOptions): Promise<SentEntry> {\n const receipt: SendReceipt = await client.send(fp, body, options)\n let entry: ConversationEntry | undefined\n if (receipt.seq !== undefined && receipt.seq > 0) {\n try {\n const { entries } = await client.conversation(fp, { since: receipt.seq - 1, limit: 1 })\n entry = entries.find(e => e.seq === receipt.seq)\n } catch {\n // archive read-back is best effort\n }\n }\n entry ??= {\n seq: receipt.seq ?? 0,\n dir: 'out',\n id: receipt.id,\n body,\n ts: Date.now(),\n status: receipt.status,\n ...(options?.auto === true ? { auto: true as const } : {}),\n }\n return { entry, receipt: { id: receipt.id, status: receipt.status, ...(receipt.seq === undefined ? {} : { seq: receipt.seq }) } }\n}\n"],"mappings":"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"}