@dsh-plugin/dsh-synapse 0.0.0-stage → 0.1.0-dev.41044818627

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 (150) hide show
  1. package/LICENSE +661 -0
  2. package/NOTICE.md +18 -0
  3. package/README.md +136 -2
  4. package/cordis.patch.yml +3 -0
  5. package/lib/bot.cjs +8314 -0
  6. package/lib/bot.cjs.map +7 -0
  7. package/lib/build.json +1 -0
  8. package/lib/client.js +46 -0
  9. package/lib/host/agent.d.ts +21 -0
  10. package/lib/host/agent.js +151 -0
  11. package/lib/host/agent.js.map +1 -0
  12. package/lib/host/bot.d.ts +17 -0
  13. package/lib/host/bot.js +158 -0
  14. package/lib/host/bot.js.map +1 -0
  15. package/lib/host/client.d.ts +2 -0
  16. package/lib/host/client.js +19 -0
  17. package/lib/host/client.js.map +1 -0
  18. package/lib/host/config.d.ts +24 -0
  19. package/lib/host/config.js +192 -0
  20. package/lib/host/config.js.map +1 -0
  21. package/lib/host/files.d.ts +29 -0
  22. package/lib/host/files.js +188 -0
  23. package/lib/host/files.js.map +1 -0
  24. package/lib/host/gateway.d.ts +33 -0
  25. package/lib/host/gateway.js +237 -0
  26. package/lib/host/gateway.js.map +1 -0
  27. package/lib/host/github.d.ts +47 -0
  28. package/lib/host/github.js +219 -0
  29. package/lib/host/github.js.map +1 -0
  30. package/lib/host/http.d.ts +4 -0
  31. package/lib/host/http.js +41 -0
  32. package/lib/host/http.js.map +1 -0
  33. package/lib/host/index.d.ts +32 -0
  34. package/lib/host/index.js +192 -0
  35. package/lib/host/index.js.map +1 -0
  36. package/lib/host/inference-worker.d.ts +1 -0
  37. package/lib/host/inference-worker.js +72 -0
  38. package/lib/host/inference-worker.js.map +1 -0
  39. package/lib/host/mcp.d.ts +138 -0
  40. package/lib/host/mcp.js +104 -0
  41. package/lib/host/mcp.js.map +1 -0
  42. package/lib/host/memory.d.ts +26 -0
  43. package/lib/host/memory.js +155 -0
  44. package/lib/host/memory.js.map +1 -0
  45. package/lib/host/panel.d.ts +1 -0
  46. package/lib/host/panel.html +174 -0
  47. package/lib/host/panel.js +388 -0
  48. package/lib/host/panel.js.map +1 -0
  49. package/lib/host/runtime.d.ts +21 -0
  50. package/lib/host/runtime.js +145 -0
  51. package/lib/host/runtime.js.map +1 -0
  52. package/lib/host/types.d.ts +112 -0
  53. package/lib/host/types.js +2 -0
  54. package/lib/host/types.js.map +1 -0
  55. package/package.json +94 -4
  56. package/scripts/build.mjs +90 -0
  57. package/scripts/check.mjs +13 -0
  58. package/scripts/dump-npm-latest.mjs +49 -0
  59. package/scripts/model-smoke.mjs +25 -0
  60. package/src/agent.ts +203 -0
  61. package/src/bot.ts +173 -0
  62. package/src/client.ts +20 -0
  63. package/src/config.ts +217 -0
  64. package/src/files.ts +206 -0
  65. package/src/gateway.ts +276 -0
  66. package/src/github.ts +263 -0
  67. package/src/http.ts +41 -0
  68. package/src/index.ts +227 -0
  69. package/src/inference-worker.ts +89 -0
  70. package/src/mcp.ts +112 -0
  71. package/src/memory.ts +178 -0
  72. package/src/panel.html +174 -0
  73. package/src/panel.ts +440 -0
  74. package/src/runtime.ts +164 -0
  75. package/src/types.ts +106 -0
  76. package/test/core.test.mjs +279 -0
  77. package/test/fixtures/mcp-server.mjs +14 -0
  78. package/test/integration.test.mjs +253 -0
  79. package/test/panel.test.mjs +73 -0
  80. package/tsconfig.json +18 -0
  81. package/vendor/gugle-chat-bot/LICENSE +661 -0
  82. package/vendor/gugle-chat-bot/package.json +57 -0
  83. package/vendor/gugle-chat-bot/src/allow-rule.yml +9345 -0
  84. package/vendor/gugle-chat-bot/src/api.json +4285 -0
  85. package/vendor/gugle-chat-bot/src/command/index.ts +141 -0
  86. package/vendor/gugle-chat-bot/src/config/features.ts +548 -0
  87. package/vendor/gugle-chat-bot/src/config/index.ts +184 -0
  88. package/vendor/gugle-chat-bot/src/config/manager.ts +357 -0
  89. package/vendor/gugle-chat-bot/src/constants/index.ts +13 -0
  90. package/vendor/gugle-chat-bot/src/custom.ts +548 -0
  91. package/vendor/gugle-chat-bot/src/event/index.ts +118 -0
  92. package/vendor/gugle-chat-bot/src/features/admin/index.ts +414 -0
  93. package/vendor/gugle-chat-bot/src/features/bili/image/impl/video.ts +39 -0
  94. package/vendor/gugle-chat-bot/src/features/bili/image/index.ts +37 -0
  95. package/vendor/gugle-chat-bot/src/features/bili/index.ts +65 -0
  96. package/vendor/gugle-chat-bot/src/features/bili/template/style/video.css +97 -0
  97. package/vendor/gugle-chat-bot/src/features/bili/template/video.html +57 -0
  98. package/vendor/gugle-chat-bot/src/features/discord-bridge/index.ts +1128 -0
  99. package/vendor/gugle-chat-bot/src/features/discord-bridge/markdown.ts +105 -0
  100. package/vendor/gugle-chat-bot/src/features/discord-bridge/slash.ts +355 -0
  101. package/vendor/gugle-chat-bot/src/features/discord-bridge/source.ts +96 -0
  102. package/vendor/gugle-chat-bot/src/features/discord-bridge/template/message.html +14 -0
  103. package/vendor/gugle-chat-bot/src/features/discord-bridge/template/style/message.css +144 -0
  104. package/vendor/gugle-chat-bot/src/features/github/api.ts +93 -0
  105. package/vendor/gugle-chat-bot/src/features/github/binding.ts +161 -0
  106. package/vendor/gugle-chat-bot/src/features/github/image/impl/issue.ts +132 -0
  107. package/vendor/gugle-chat-bot/src/features/github/image/impl/pr.ts +144 -0
  108. package/vendor/gugle-chat-bot/src/features/github/image/impl/release.ts +160 -0
  109. package/vendor/gugle-chat-bot/src/features/github/image/index.ts +310 -0
  110. package/vendor/gugle-chat-bot/src/features/github/index.ts +302 -0
  111. package/vendor/gugle-chat-bot/src/features/github/template/issue.html +118 -0
  112. package/vendor/gugle-chat-bot/src/features/github/template/pull_request.html +125 -0
  113. package/vendor/gugle-chat-bot/src/features/github/template/release.html +53 -0
  114. package/vendor/gugle-chat-bot/src/features/github/template/style/issue.css +468 -0
  115. package/vendor/gugle-chat-bot/src/features/github/template/style/pull_request.css +469 -0
  116. package/vendor/gugle-chat-bot/src/features/github/template/style/release.css +412 -0
  117. package/vendor/gugle-chat-bot/src/features/hermes/client.ts +184 -0
  118. package/vendor/gugle-chat-bot/src/features/hermes/config.ts +210 -0
  119. package/vendor/gugle-chat-bot/src/features/hermes/context.ts +67 -0
  120. package/vendor/gugle-chat-bot/src/features/hermes/history.ts +86 -0
  121. package/vendor/gugle-chat-bot/src/features/hermes/index.ts +1533 -0
  122. package/vendor/gugle-chat-bot/src/features/hermes/message.ts +319 -0
  123. package/vendor/gugle-chat-bot/src/features/hermes/reaction.ts +181 -0
  124. package/vendor/gugle-chat-bot/src/features/hermes/render.ts +118 -0
  125. package/vendor/gugle-chat-bot/src/features/hermes/reply-units.ts +331 -0
  126. package/vendor/gugle-chat-bot/src/features/hermes/skills.ts +406 -0
  127. package/vendor/gugle-chat-bot/src/features/hermes/template/approval.html +30 -0
  128. package/vendor/gugle-chat-bot/src/features/hermes/template/progress.html +21 -0
  129. package/vendor/gugle-chat-bot/src/features/hermes/template/style/approval.css +81 -0
  130. package/vendor/gugle-chat-bot/src/features/hermes/template/style/progress.css +120 -0
  131. package/vendor/gugle-chat-bot/src/features/hermes/trigger.ts +86 -0
  132. package/vendor/gugle-chat-bot/src/features/hermes/types.ts +327 -0
  133. package/vendor/gugle-chat-bot/src/features/management/index.ts +156 -0
  134. package/vendor/gugle-chat-bot/src/features/minecraft/index.ts +127 -0
  135. package/vendor/gugle-chat-bot/src/features/modrinth/index.ts +71 -0
  136. package/vendor/gugle-chat-bot/src/features/parentheses/index.ts +117 -0
  137. package/vendor/gugle-chat-bot/src/features/peak-valley-timer/index.ts +452 -0
  138. package/vendor/gugle-chat-bot/src/features/poke/index.ts +24 -0
  139. package/vendor/gugle-chat-bot/src/features/version-tracker/index.ts +78 -0
  140. package/vendor/gugle-chat-bot/src/features/welcome/index.ts +58 -0
  141. package/vendor/gugle-chat-bot/src/github-ip.json +5814 -0
  142. package/vendor/gugle-chat-bot/src/image/index.ts +278 -0
  143. package/vendor/gugle-chat-bot/src/index.ts +695 -0
  144. package/vendor/gugle-chat-bot/src/logger/index.ts +54 -0
  145. package/vendor/gugle-chat-bot/src/result.json +359 -0
  146. package/vendor/gugle-chat-bot/src/type/bili.d.ts +825 -0
  147. package/vendor/gugle-chat-bot/src/type/event.d.ts +146 -0
  148. package/vendor/gugle-chat-bot/src/type/github.d.ts +446 -0
  149. package/vendor/gugle-chat-bot/src/type/index.d.ts +254 -0
  150. package/vendor/gugle-chat-bot/tsconfig.json +46 -0
@@ -0,0 +1,253 @@
1
+ import test from "node:test";
2
+ import assert from "node:assert/strict";
3
+ import { mkdtemp, mkdir, readFile } from "node:fs/promises";
4
+ import { createServer } from "node:http";
5
+ import { WebSocketServer } from "ws";
6
+ import { once } from "node:events";
7
+ import path from "node:path";
8
+ import os from "node:os";
9
+ import { fileURLToPath } from "node:url";
10
+ import { defaultsFor } from "@dsh-plugin/dsh-loader/config";
11
+ import { template } from "../lib/host/config.js";
12
+ import { McpManager } from "../lib/host/mcp.js";
13
+ import { Bot } from "../lib/host/bot.js";
14
+ import { Gateway } from "../lib/host/gateway.js";
15
+ import { apply } from "../lib/host/index.js";
16
+ const waitFor = async (fn, ms = 10000) => {
17
+ const until = Date.now() + ms;
18
+ while (Date.now() < until) {
19
+ if (await fn()) return;
20
+ await new Promise((r) => setTimeout(r, 30));
21
+ }
22
+ throw new Error("Timed out waiting for integration state");
23
+ };
24
+ test("real MCP stdio discovery, allowlist and invocation", async () => {
25
+ const c = defaultsFor(template.fields);
26
+ c.mcp = [
27
+ {
28
+ name: "fixture",
29
+ enabled: true,
30
+ transport: "stdio",
31
+ command: process.execPath,
32
+ argsJson: JSON.stringify([
33
+ fileURLToPath(new URL("./fixtures/mcp-server.mjs", import.meta.url)),
34
+ ]),
35
+ envJson: "{}",
36
+ headersJson: "{}",
37
+ allowedTools: "read",
38
+ },
39
+ ];
40
+ const mcp = new McpManager(() => c);
41
+ try {
42
+ assert.equal((await mcp.list("fixture")).length, 2);
43
+ const available = await mcp.available();
44
+ assert.equal(available.length, 1);
45
+ assert.equal(
46
+ (await mcp.call(available[0], {})).content[0].text,
47
+ "fixture result",
48
+ );
49
+ await assert.rejects(
50
+ mcp.call({ server: "fixture", rawName: "blocked" }, {}),
51
+ /允许列表/,
52
+ );
53
+ } finally {
54
+ await mcp.close();
55
+ }
56
+ });
57
+ test("full upstream bot starts, connects OneBot and executes /help", async () => {
58
+ const home = await mkdtemp(path.join(os.tmpdir(), "synapse-bot-"));
59
+ const profile = path.join(home, "profiles", "test"),
60
+ data = path.join(profile, "data", "dsh-synapse");
61
+ await mkdir(data, { recursive: true });
62
+ const c = {
63
+ ...defaultsFor(template.fields),
64
+ enabled: true,
65
+ ragEnabled: false,
66
+ profileDir: profile,
67
+ homeDir: home,
68
+ };
69
+ c.features.find((x) => x.id === "command").json = JSON.stringify({
70
+ version: 1,
71
+ groups: [123],
72
+ });
73
+ c.features.find((x) => x.id === "hermes").json = JSON.stringify({
74
+ version: 1,
75
+ groups: [123],
76
+ reactionEnabled: false,
77
+ progressAsImage: false,
78
+ replyPerSentence: false,
79
+ });
80
+ const requests = [];
81
+ const http = createServer(async (req, res) => {
82
+ let body = "";
83
+ for await (const part of req) body += part;
84
+ requests.push({ url: req.url, body: body ? JSON.parse(body) : {} });
85
+ res.setHeader("Content-Type", "application/json");
86
+ res.end(
87
+ JSON.stringify({
88
+ status: "ok",
89
+ retcode: 0,
90
+ data:
91
+ req.url === "/get_login_info"
92
+ ? { user_id: 42, nickname: "testbot" }
93
+ : { message_id: 77 },
94
+ }),
95
+ );
96
+ });
97
+ http.listen(0, "127.0.0.1");
98
+ await once(http, "listening");
99
+ c.httpUrl = `http://127.0.0.1:${http.address().port}`;
100
+ const ws = new WebSocketServer({ port: 0, host: "127.0.0.1" });
101
+ await once(ws, "listening");
102
+ c.wsUrl = `ws://127.0.0.1:${ws.address().port}`;
103
+ let socket;
104
+ ws.on("connection", (s) => {
105
+ socket = s;
106
+ });
107
+ const errors = [],
108
+ gateway = new Gateway(
109
+ { run: async () => "hello" },
110
+ () => c,
111
+ (e) => errors.push(e.message),
112
+ );
113
+ await gateway.start();
114
+ const bot = new Bot(
115
+ { home, profile, data },
116
+ () => c,
117
+ gateway,
118
+ (e) => errors.push(e.message),
119
+ );
120
+ try {
121
+ await bot.start();
122
+ await waitFor(() => bot.state === "running" && socket, 15000);
123
+ socket.send(
124
+ JSON.stringify({
125
+ post_type: "message",
126
+ message_type: "group",
127
+ group_id: 123,
128
+ user_id: 1,
129
+ self_id: 42,
130
+ message_id: 1,
131
+ raw_message: "/help",
132
+ message: [{ type: "text", data: { text: "/help" } }],
133
+ sender: { user_id: 1, nickname: "tester", role: "owner" },
134
+ }),
135
+ );
136
+ await waitFor(
137
+ () => requests.some((r) => r.url === "/send_group_msg"),
138
+ 12000,
139
+ );
140
+ assert.match(
141
+ JSON.stringify(requests.find((r) => r.url === "/send_group_msg").body),
142
+ /mcv|help|github/,
143
+ );
144
+ socket.send(
145
+ JSON.stringify({
146
+ post_type: "message",
147
+ message_type: "private",
148
+ sub_type: "friend",
149
+ user_id: 1,
150
+ self_id: 42,
151
+ message_id: 2,
152
+ raw_message: "你好",
153
+ message: [{ type: "text", data: { text: "你好" } }],
154
+ sender: { user_id: 1, nickname: "tester" },
155
+ }),
156
+ );
157
+ await waitFor(
158
+ () =>
159
+ requests.some(
160
+ (r) =>
161
+ r.url === "/send_private_msg" &&
162
+ JSON.stringify(r.body).includes("hello"),
163
+ ),
164
+ 12000,
165
+ );
166
+ assert.deepEqual(errors, []);
167
+ } catch (e) {
168
+ const log = await readFile(
169
+ path.join(data, "bot/logs/latest.log"),
170
+ "utf8",
171
+ ).catch(() => "(no upstream log)");
172
+ throw new Error(
173
+ e.message +
174
+ "\n" +
175
+ bot.state +
176
+ "\n" +
177
+ bot.stderr +
178
+ "\n" +
179
+ log.slice(-6000),
180
+ );
181
+ } finally {
182
+ await bot.stop();
183
+ await gateway.close();
184
+ for (const s of ws.clients) s.terminate();
185
+ await new Promise((r) => ws.close(r));
186
+ await new Promise((r) => http.close(r));
187
+ }
188
+ });
189
+ test("loader 2.0 lifecycle registers, authenticates routes and disposes services", async () => {
190
+ const home = await mkdtemp(path.join(os.tmpdir(), "synapse-host-"));
191
+ const profile = path.join(home, "profiles", "test");
192
+ await mkdir(profile, { recursive: true });
193
+ const c = {
194
+ ...defaultsFor(template.fields),
195
+ profileDir: profile,
196
+ homeDir: home,
197
+ };
198
+ const routes = new Map(),
199
+ cleanups = [];
200
+ let registered, watched;
201
+ const ctx = {
202
+ effect(fn) {
203
+ cleanups.push(fn());
204
+ },
205
+ logger: { warn() {} },
206
+ dshLoader: {
207
+ config: {
208
+ async register(owner, t) {
209
+ registered = t;
210
+ return {
211
+ get: () => ({ value: c }),
212
+ watch(fn) {
213
+ watched = fn;
214
+ return () => {
215
+ watched = null;
216
+ };
217
+ },
218
+ };
219
+ },
220
+ },
221
+ services: { get: () => ({ authorizeIndex: () => true }) },
222
+ web: Object.fromEntries(
223
+ ["register", "post", "get"].map((method) => [
224
+ method,
225
+ (p, h) => {
226
+ routes.set(p, h);
227
+ return () => routes.delete(p);
228
+ },
229
+ ]),
230
+ ),
231
+ },
232
+ };
233
+ await apply(ctx);
234
+ assert.equal(registered.id, "dsh-synapse");
235
+ assert(watched);
236
+ assert(routes.has("/hooks/dsh-synapse/github"));
237
+ const status = await new Promise((resolve) =>
238
+ routes.get("/api/dsh-synapse/")(
239
+ { url: "/api/dsh-synapse/status", headers: {} },
240
+ {
241
+ writeHead() {},
242
+ end(s) {
243
+ resolve(JSON.parse(s));
244
+ },
245
+ },
246
+ ),
247
+ );
248
+ assert.equal(status.running, true);
249
+ assert.equal(status.bot, "stopped");
250
+ for (const fn of cleanups) fn();
251
+ assert.equal(routes.size, 0);
252
+ assert.equal(watched, null);
253
+ });
@@ -0,0 +1,73 @@
1
+ import test from "node:test";
2
+ import assert from "node:assert/strict";
3
+ import { JSDOM } from "jsdom";
4
+ import { readFile } from "node:fs/promises";
5
+ import { defaultsFor } from "@dsh-plugin/dsh-loader/config";
6
+ import { template } from "../lib/host/config.js";
7
+ test("panel renders persisted configuration, skills, MCP and Hermes import without HTML execution", async () => {
8
+ const html = await readFile(
9
+ new URL("../src/panel.html", import.meta.url),
10
+ "utf8",
11
+ );
12
+ const source = await readFile(
13
+ new URL("../lib/host/panel.js", import.meta.url),
14
+ "utf8",
15
+ );
16
+ const dom = new JSDOM(html, {
17
+ url: "http://localhost/synapse",
18
+ runScripts: "outside-only",
19
+ });
20
+ const w = dom.window,
21
+ calls = [];
22
+ w.structuredClone = structuredClone;
23
+ w.confirm = () => true;
24
+ w.fetch = async (url, options) => {
25
+ calls.push([url, options]);
26
+ let data;
27
+ if (url.startsWith("/api/dshloader/config"))
28
+ data = {
29
+ ok: true,
30
+ value: { value: defaultsFor(template.fields), template, revision: 1 },
31
+ };
32
+ else if (url.endsWith("/status")) data = { bot: "stopped" };
33
+ else if (url.endsWith("/skills"))
34
+ data = [
35
+ {
36
+ name: "demo",
37
+ content: "<script>window.pwned=true</script>",
38
+ revision: "abc",
39
+ },
40
+ ];
41
+ else if (url.endsWith("/memory/import"))
42
+ data = { items: [{ text: "memory" }] };
43
+ else data = [];
44
+ return { ok: true, json: async () => data };
45
+ };
46
+ const settle = () => new Promise((r) => setTimeout(r, 20));
47
+ try {
48
+ await w.eval(
49
+ `(async()=>{${source.replace(/export\s*\{\s*\};?/g, "")}\n})()`,
50
+ );
51
+ const click = async (label) => {
52
+ const b = [...w.document.querySelectorAll("button")].find(
53
+ (b) => b.textContent === label,
54
+ );
55
+ assert(b, `Missing ${label}`);
56
+ b.click();
57
+ await settle();
58
+ };
59
+ assert.equal(w.document.querySelectorAll("nav button").length, 5);
60
+ await click("Skill");
61
+ await click("demo");
62
+ assert.match(w.document.querySelector("pre").textContent, /<script>/);
63
+ assert.equal(w.pwned, undefined);
64
+ await click("MCP");
65
+ assert.match(w.document.body.textContent, /允许列表/);
66
+ await click("Hermes 记忆");
67
+ w.document.querySelector("textarea").value = "# User\n喜欢猫";
68
+ await click("预览分块");
69
+ assert(calls.some(([url]) => url.endsWith("/memory/import")));
70
+ } finally {
71
+ w.close();
72
+ }
73
+ });
package/tsconfig.json ADDED
@@ -0,0 +1,18 @@
1
+ {
2
+ "compilerOptions": {
3
+ "target": "ES2023",
4
+ "module": "NodeNext",
5
+ "moduleResolution": "NodeNext",
6
+ "lib": ["ES2023", "DOM", "DOM.Iterable"],
7
+ "types": ["node"],
8
+ "strictNullChecks": false,
9
+ "noImplicitAny": false,
10
+ "skipLibCheck": true,
11
+ "esModuleInterop": true,
12
+ "outDir": "lib/host",
13
+ "rootDir": "src",
14
+ "declaration": true,
15
+ "sourceMap": true
16
+ },
17
+ "include": ["src/**/*.ts"]
18
+ }