dsh-kaomoji 0.1.3 → 0.1.5
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/CHANGELOG.md +15 -0
- package/README.en.md +6 -9
- package/README.md +3 -5
- package/lib/client.js +39 -14
- package/lib/index.js +52 -23
- package/package.json +1 -1
package/CHANGELOG.md
CHANGED
|
@@ -7,6 +7,21 @@ and this project adheres to [Semantic Versioning](https://semver.org/lang/zh-CN/
|
|
|
7
7
|
|
|
8
8
|
## [Unreleased]
|
|
9
9
|
|
|
10
|
+
## [0.1.5] - 2026-09-25
|
|
11
|
+
|
|
12
|
+
### Fixed
|
|
13
|
+
|
|
14
|
+
- 设置通道从 `connection.rpc` 改为插件自有的 `webServer` 路由
|
|
15
|
+
(`POST /dsh-kaomoji-settings`)。部分部署里 `connection` 服务不会进入插件
|
|
16
|
+
作用域,通道永远注册不上,浏览器侧只看到 `HTTP 405`,设置卡片因此无法保存。
|
|
17
|
+
|
|
18
|
+
## [0.1.4] - 2026-09-25
|
|
19
|
+
|
|
20
|
+
### Changed
|
|
21
|
+
|
|
22
|
+
- 设置卡片在保存失败时直接显示 Host 返回的真实原因(例如文件不可写、来源不受信任),
|
|
23
|
+
不再只显示通用的“保存失败”,便于定位服务器端问题。
|
|
24
|
+
|
|
10
25
|
## [0.1.3] - 2026-09-13
|
|
11
26
|
|
|
12
27
|
### Fixed
|
package/README.en.md
CHANGED
|
@@ -44,7 +44,7 @@ largest Japanese kaomoji sites.
|
|
|
44
44
|
3. The model emits one of the listed kaomoji where it fits. Because kaomoji
|
|
45
45
|
are plain text, no custom rendering is needed in the chat UI.
|
|
46
46
|
|
|
47
|
-
Settings use a
|
|
47
|
+
Settings use a same-origin route registered on the web server (`POST /dsh-kaomoji-settings`):
|
|
48
48
|
|
|
49
49
|
1. The Settings → General card reads/writes `~/.dsh/dsh-kaomoji.json`.
|
|
50
50
|
2. The Host updates the prompt section and emits `system-prompt/change`
|
|
@@ -107,19 +107,16 @@ Example `cordis.patch.yml`:
|
|
|
107
107
|
| `maxPerTurn` | `number` (1–5) | `1` | Max kaomoji per reply; `frequent` distributes that many across different sentences (fewer in very short replies) |
|
|
108
108
|
| `customPrompt` | `string` | `''` | Extra style/scene guidance; cannot change mode, whitelist or limits |
|
|
109
109
|
| `settingsFile` | `string` | `~/.dsh/dsh-kaomoji.json` | (advanced) user-settings file path |
|
|
110
|
-
| `settingsAuthority` | `'trusted-host' \| 'loopback'` | `'trusted-host'` | Settings RPC trust scope: by default the local Host plus declared trusted hosts (Tailscale/LAN) may write; set `loopback` to restrict to the local machine |
|
|
111
|
-
|
|
112
110
|
Card edits take effect immediately; deployment-default edits in
|
|
113
111
|
`cordis.patch.yml` require a dsh restart.
|
|
114
112
|
|
|
115
113
|
### Remote access (Tailscale / LAN)
|
|
116
114
|
|
|
117
|
-
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
-
|
|
121
|
-
server
|
|
122
|
-
`settingsAuthority: loopback` and edit on the server itself.
|
|
115
|
+
- The settings route is same-origin with the page (`POST /dsh-kaomoji-settings`),
|
|
116
|
+
so pages opened over Tailscale, LAN or an SSH tunnel can all save settings —
|
|
117
|
+
no `trustedHosts` configuration needed.
|
|
118
|
+
- The only requirement is that the page can reach that Host's HTTP port; any
|
|
119
|
+
failure now shows the server's original error in the card (0.1.4+).
|
|
123
120
|
|
|
124
121
|
## Kaomoji library & attribution
|
|
125
122
|
|
package/README.md
CHANGED
|
@@ -31,7 +31,7 @@
|
|
|
31
31
|
- 用户附加的 `customPrompt`(可选)。
|
|
32
32
|
3. 模型生成时在合适位置直接输出列表内的颜文字——因为它只是普通文本,聊天界面无需任何自定义渲染即可显示。
|
|
33
33
|
|
|
34
|
-
|
|
34
|
+
设置部分由插件在 webServer 上注册同源路由 `POST /dsh-kaomoji-settings`:
|
|
35
35
|
|
|
36
36
|
1. 「设置 → 通用设置」的卡片读取/写入 `~/.dsh/dsh-kaomoji.json`;
|
|
37
37
|
2. Host 收到写入后立刻更新提示词段并发出 `system-prompt/change`,**下一次回复就生效**,不需要重启。
|
|
@@ -123,14 +123,12 @@ pnpm add file:C:\path\to\dsh-kaomoji
|
|
|
123
123
|
| `maxPerTurn` | `number`(1–5) | `1` | 每条回复的颜文字数量上限;`frequent` 会按这个数量分散放在不同句子后,回复太短时自动减少 |
|
|
124
124
|
| `customPrompt` | `string` | `''` | 附加风格/场景说明;不能改变模式、白名单或数量上限 |
|
|
125
125
|
| `settingsFile` | `string` | `~/.dsh/dsh-kaomoji.json` | (进阶)用户设置持久化文件路径 |
|
|
126
|
-
| `settingsAuthority` | `'trusted-host' \| 'loopback'` | `'trusted-host'` | 设置 RPC 信任范围:默认允许本机与 Host 声明的受信主机(Tailscale/局域网)保存设置;改成 `loopback` 则仅本机可改 |
|
|
127
|
-
|
|
128
126
|
卡片里的修改即时写入用户层并热生效;修改 `cordis.patch.yml` 的部署默认值后需重启 dsh。
|
|
129
127
|
|
|
130
128
|
### 远程访问(Tailscale / 局域网)
|
|
131
129
|
|
|
132
|
-
-
|
|
133
|
-
-
|
|
130
|
+
- 设置路由和页面同源(`POST /dsh-kaomoji-settings`),所以 Tailscale、局域网或 SSH 端口转发打开的页面都能直接改设置,不需要配置 `trustedHosts`。
|
|
131
|
+
- 唯一要求是页面能访问该 Host 的 HTTP 端口;如果卡片报错,看提示里的具体原因(0.1.4+ 会显示服务端原始错误)。
|
|
134
132
|
|
|
135
133
|
## 词库来源与许可
|
|
136
134
|
|
package/lib/client.js
CHANGED
|
@@ -213,7 +213,8 @@ window.__ModuleLoader__.load({
|
|
|
213
213
|
* 轻量快照 store(getSnapshot/subscribe),slot runtime 会把它包装成
|
|
214
214
|
* useKaomoji(selector) hook。所有写操作都串行经过 loopback RPC。
|
|
215
215
|
*/
|
|
216
|
-
function createSettingsStore(
|
|
216
|
+
function createSettingsStore(fetchImpl) {
|
|
217
|
+
const doFetch = typeof fetchImpl === "function" ? fetchImpl : (...args) => fetch(...args);
|
|
217
218
|
let snapshot = {
|
|
218
219
|
status: "loading",
|
|
219
220
|
settings: cloneSettings(DEFAULT_SETTINGS),
|
|
@@ -230,7 +231,9 @@ window.__ModuleLoader__.load({
|
|
|
230
231
|
};
|
|
231
232
|
|
|
232
233
|
const publish = (patch) => {
|
|
233
|
-
snapshot =
|
|
234
|
+
snapshot = patch.saveError === null
|
|
235
|
+
? { ...snapshot, ...patch, saveErrorMessage: null }
|
|
236
|
+
: { ...snapshot, ...patch };
|
|
234
237
|
emit();
|
|
235
238
|
};
|
|
236
239
|
|
|
@@ -240,24 +243,45 @@ window.__ModuleLoader__.load({
|
|
|
240
243
|
return task;
|
|
241
244
|
};
|
|
242
245
|
|
|
243
|
-
const fail = (code) => {
|
|
246
|
+
const fail = (code, message) => {
|
|
244
247
|
publish({
|
|
245
248
|
status: "unavailable",
|
|
246
249
|
writable: false,
|
|
247
250
|
saveError: code,
|
|
251
|
+
saveErrorMessage: message || null,
|
|
248
252
|
justSaved: false,
|
|
249
253
|
});
|
|
250
254
|
};
|
|
251
255
|
|
|
252
256
|
const callRpc = async (endpoint, payload) => {
|
|
253
257
|
try {
|
|
254
|
-
const
|
|
258
|
+
const response = await doFetch(RPC_CHANNEL, {
|
|
259
|
+
method: "POST",
|
|
260
|
+
headers: { "Content-Type": "application/json" },
|
|
261
|
+
body: JSON.stringify({ endpoint, payload }),
|
|
262
|
+
});
|
|
263
|
+
const status = Number((response && response.status) || 0);
|
|
264
|
+
let result;
|
|
265
|
+
try {
|
|
266
|
+
result = await response.json();
|
|
267
|
+
} catch {
|
|
268
|
+
result = undefined;
|
|
269
|
+
}
|
|
255
270
|
if (result && result.ok === true) return { value: result.value };
|
|
256
|
-
const
|
|
257
|
-
|
|
271
|
+
const error = (result && result.error) || {};
|
|
272
|
+
if (status === 403 || error.code === "forbidden") {
|
|
273
|
+
return { error: "forbidden", message: error.message || "forbidden" };
|
|
274
|
+
}
|
|
275
|
+
return {
|
|
276
|
+
error: "rejected",
|
|
277
|
+
message: typeof error.message === "string" ? error.message : `HTTP ${String(status)}`,
|
|
278
|
+
};
|
|
258
279
|
} catch (error) {
|
|
259
280
|
const message = String((error && error.message) || error || "");
|
|
260
|
-
return {
|
|
281
|
+
return {
|
|
282
|
+
error: /403|forbidden/i.test(message) ? "forbidden" : "unreachable",
|
|
283
|
+
message,
|
|
284
|
+
};
|
|
261
285
|
}
|
|
262
286
|
};
|
|
263
287
|
|
|
@@ -270,7 +294,7 @@ window.__ModuleLoader__.load({
|
|
|
270
294
|
async refresh() {
|
|
271
295
|
const result = await callRpc("get", {});
|
|
272
296
|
if (result.error !== undefined) {
|
|
273
|
-
fail(result.error === "forbidden" ? "forbidden" : "load");
|
|
297
|
+
fail(result.error === "forbidden" ? "forbidden" : "load", result.message);
|
|
274
298
|
return;
|
|
275
299
|
}
|
|
276
300
|
const value = result.value;
|
|
@@ -297,7 +321,7 @@ window.__ModuleLoader__.load({
|
|
|
297
321
|
expectedRevision: snapshot.revision,
|
|
298
322
|
});
|
|
299
323
|
if (result.error !== undefined) {
|
|
300
|
-
fail(result.error === "forbidden" ? "forbidden" : "save");
|
|
324
|
+
fail(result.error === "forbidden" ? "forbidden" : "save", result.message);
|
|
301
325
|
return;
|
|
302
326
|
}
|
|
303
327
|
const value = result.value;
|
|
@@ -316,7 +340,7 @@ window.__ModuleLoader__.load({
|
|
|
316
340
|
publish({ saveError: null, justSaved: false });
|
|
317
341
|
const result = await callRpc("reset", { expectedRevision: snapshot.revision });
|
|
318
342
|
if (result.error !== undefined) {
|
|
319
|
-
fail(result.error === "forbidden" ? "forbidden" : "save");
|
|
343
|
+
fail(result.error === "forbidden" ? "forbidden" : "save", result.message);
|
|
320
344
|
return;
|
|
321
345
|
}
|
|
322
346
|
const value = result.value;
|
|
@@ -348,7 +372,9 @@ window.__ModuleLoader__.load({
|
|
|
348
372
|
const statusText = state.saveError
|
|
349
373
|
? state.saveError === "forbidden"
|
|
350
374
|
? t("status.forbidden")
|
|
351
|
-
:
|
|
375
|
+
: state.saveErrorMessage
|
|
376
|
+
? `${t("status.error")} ${state.saveErrorMessage}`
|
|
377
|
+
: t("status.error")
|
|
352
378
|
: state.status === "loading"
|
|
353
379
|
? t("status.loading")
|
|
354
380
|
: !state.writable
|
|
@@ -474,15 +500,14 @@ window.__ModuleLoader__.load({
|
|
|
474
500
|
);
|
|
475
501
|
}
|
|
476
502
|
|
|
477
|
-
const inject = ["slots", "locale"
|
|
503
|
+
const inject = ["slots", "locale"];
|
|
478
504
|
|
|
479
505
|
function apply(ctx) {
|
|
480
506
|
ctx.effect(
|
|
481
507
|
() => ctx.locale.register(NS, { zh, en }),
|
|
482
508
|
"dsh-kaomoji: dictionaries",
|
|
483
509
|
);
|
|
484
|
-
const
|
|
485
|
-
const controller = createSettingsStore(connection.rpc);
|
|
510
|
+
const controller = createSettingsStore();
|
|
486
511
|
void controller.refresh();
|
|
487
512
|
|
|
488
513
|
ctx.effect(() => {
|
package/lib/index.js
CHANGED
|
@@ -30,8 +30,6 @@ export const SETTINGS_FILE_SCHEMA_VERSION = 1;
|
|
|
30
30
|
|
|
31
31
|
export const MODES = ["off", "auto", "frequent"];
|
|
32
32
|
export const PLACEMENTS = ["inline", "end"];
|
|
33
|
-
/** 设置 RPC 的信任范围:trusted-host = 本机 + 服务端声明的受信主机(如 Tailscale)。 */
|
|
34
|
-
export const SETTINGS_AUTHORITIES = ["trusted-host", "loopback"];
|
|
35
33
|
|
|
36
34
|
export const DEFAULT_CONFIG = Object.freeze({
|
|
37
35
|
/** off=关闭;auto=智能(确实有助于表达才用);frequent=高频(每条对话回复都带)。 */
|
|
@@ -219,11 +217,6 @@ function cloneSettings(settings) {
|
|
|
219
217
|
return { ...settings, customPrompt: settings.customPrompt };
|
|
220
218
|
}
|
|
221
219
|
|
|
222
|
-
/** 设置 RPC 的信任范围;未显式配置时用 trusted-host(本机 + 服务端声明的受信主机)。 */
|
|
223
|
-
function resolveAuthority(config) {
|
|
224
|
-
return config?.settingsAuthority === "loopback" ? "loopback" : "trusted-host";
|
|
225
|
-
}
|
|
226
|
-
|
|
227
220
|
function defaultSettingsFile(config) {
|
|
228
221
|
if (typeof config?.settingsFile === "string" && config.settingsFile.trim() !== "") {
|
|
229
222
|
return resolve(config.settingsFile);
|
|
@@ -372,10 +365,9 @@ function createSettingsRpcHandler(ctx, settingsFile, getState, commit) {
|
|
|
372
365
|
return rpcError("bad-request", `Unknown dsh-kaomoji settings operation: ${endpoint}`);
|
|
373
366
|
});
|
|
374
367
|
} catch (error) {
|
|
375
|
-
|
|
376
|
-
|
|
377
|
-
);
|
|
378
|
-
return rpcError("settings-rejected", "The Host rejected the kaomoji settings.");
|
|
368
|
+
const reason = error instanceof Error ? error.message : String(error);
|
|
369
|
+
console.warn(`[dsh-kaomoji] settings rpc failed: ${reason}`);
|
|
370
|
+
return rpcError("settings-rejected", `The Host rejected the kaomoji settings: ${reason}`);
|
|
379
371
|
}
|
|
380
372
|
};
|
|
381
373
|
}
|
|
@@ -383,7 +375,6 @@ function createSettingsRpcHandler(ctx, settingsFile, getState, commit) {
|
|
|
383
375
|
export function apply(ctx, config = {}) {
|
|
384
376
|
const baseSettings = normalizeConfig(config);
|
|
385
377
|
const settingsFile = defaultSettingsFile(config);
|
|
386
|
-
const authority = resolveAuthority(config);
|
|
387
378
|
const stored = loadSettingsDocument(settingsFile);
|
|
388
379
|
|
|
389
380
|
// currentSettings = 用户层(~/.dsh/dsh-kaomoji.json)覆盖部署默认值后的实时设置。
|
|
@@ -421,21 +412,59 @@ export function apply(ctx, config = {}) {
|
|
|
421
412
|
"dsh-kaomoji: guidance",
|
|
422
413
|
);
|
|
423
414
|
|
|
424
|
-
//
|
|
425
|
-
//
|
|
426
|
-
ctx
|
|
427
|
-
|
|
428
|
-
|
|
415
|
+
// 设置读写走 webServer 直连路由,不用 connection.rpc:后者在部分部署里
|
|
416
|
+
// connection 服务不进入插件作用域,通道永远注册不上(浏览器侧表现为 405)。
|
|
417
|
+
const settingsHandler = createSettingsRpcHandler(ctx, settingsFile, getState, adoptSettings);
|
|
418
|
+
const attachSettingsRoute = (scope) => {
|
|
419
|
+
scope.effect(
|
|
429
420
|
() =>
|
|
430
|
-
|
|
431
|
-
|
|
421
|
+
scope.webServer.register({
|
|
422
|
+
kind: "exact",
|
|
423
|
+
path: SETTINGS_RPC_CHANNEL,
|
|
424
|
+
handler: async (request, response) => {
|
|
425
|
+
const reply = (status, value) => {
|
|
426
|
+
response.writeHead(status, {
|
|
427
|
+
"Content-Type": "application/json; charset=utf-8",
|
|
428
|
+
"Cache-Control": "no-store",
|
|
429
|
+
});
|
|
430
|
+
response.end(JSON.stringify(value));
|
|
431
|
+
};
|
|
432
|
+
if (request.method !== "POST") {
|
|
433
|
+
reply(405, rpcError("bad-request", "POST required"));
|
|
434
|
+
return;
|
|
435
|
+
}
|
|
436
|
+
let raw = "";
|
|
437
|
+
try {
|
|
438
|
+
for await (const chunk of request) raw += chunk;
|
|
439
|
+
} catch {
|
|
440
|
+
// 客户端中断:按空 body 处理,下面的解析会返回 400。
|
|
441
|
+
}
|
|
442
|
+
let endpoint = "";
|
|
443
|
+
let payload;
|
|
444
|
+
try {
|
|
445
|
+
const parsed = JSON.parse(raw === "" ? "{}" : raw);
|
|
446
|
+
endpoint = typeof parsed.endpoint === "string" ? parsed.endpoint : "";
|
|
447
|
+
payload = parsed.payload;
|
|
448
|
+
} catch {
|
|
449
|
+
reply(400, rpcError("bad-request", "invalid JSON body"));
|
|
450
|
+
return;
|
|
451
|
+
}
|
|
452
|
+
reply(200, await settingsHandler(endpoint, payload));
|
|
453
|
+
},
|
|
432
454
|
}),
|
|
433
|
-
"dsh-kaomoji: settings
|
|
455
|
+
"dsh-kaomoji: settings route",
|
|
434
456
|
);
|
|
435
|
-
|
|
457
|
+
console.log(`[dsh-kaomoji] settings route 已注册(path=${SETTINGS_RPC_CHANNEL})`);
|
|
458
|
+
};
|
|
459
|
+
const webServer = typeof ctx.get === "function" ? ctx.get("webServer") : undefined;
|
|
460
|
+
if (webServer !== undefined) {
|
|
461
|
+
attachSettingsRoute(ctx);
|
|
462
|
+
} else {
|
|
463
|
+
ctx.inject(["webServer"], attachSettingsRoute);
|
|
464
|
+
}
|
|
436
465
|
|
|
437
|
-
|
|
438
|
-
`[dsh-kaomoji] 已挂载(mode=${currentSettings.mode}, placement=${currentSettings.placement},
|
|
466
|
+
console.log(
|
|
467
|
+
`[dsh-kaomoji] 已挂载(mode=${currentSettings.mode}, placement=${currentSettings.placement}, settingsFile=${settingsFile})`,
|
|
439
468
|
);
|
|
440
469
|
}
|
|
441
470
|
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "dsh-kaomoji",
|
|
3
|
-
"version": "0.1.
|
|
3
|
+
"version": "0.1.5",
|
|
4
4
|
"description": "Add mood-aware Japanese kaomoji to DeepSeek Harness (dsh) replies; prompt-injection plugin with a curated library from kaomojiya.org.",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"main": "lib/index.js",
|