@shgroup/dsh-serenity-hooks 1.20.4 → 1.20.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/dsh.plugin.json +1 -1
- package/lib/client.js +70 -64
- package/package.json +1 -1
package/dsh.plugin.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"id": "dsh-serenity-hooks",
|
|
3
|
-
"version": "1.20.
|
|
3
|
+
"version": "1.20.5",
|
|
4
4
|
"main": "lib/index.js",
|
|
5
5
|
"description": "宁静号 ACC harness(Native Cordis 插件):真实 DSH 工具 cc_fs/session/acc_msm/eap/neat/cce/loop + 拦截缝机械约束(safe-mode/路径守卫)。适配 DSH 公开版(0.1.0-rc,deepseek-ai/deepseek-harness)。私有(dsh-external 组织)。",
|
|
6
6
|
"engines": {
|
package/lib/client.js
CHANGED
|
@@ -240,6 +240,75 @@ window.__ModuleLoader__.load({
|
|
|
240
240
|
});
|
|
241
241
|
}
|
|
242
242
|
//#endregion
|
|
243
|
+
//#region src/client/image-fallback-api.ts
|
|
244
|
+
/** 图片落盘接口路径(node half api.ts,client 专属 x-serenity-ui 头) */
|
|
245
|
+
const UPLOAD_PATH = "/serenity/image-upload";
|
|
246
|
+
/**
|
|
247
|
+
* 识别结果消息模板(协议固有,S142 用户迭代:v1.20.5 去掉具体文件路径——UI 突兀,
|
|
248
|
+
* 改为目录级自然提示;agent 自行查 _tmp/images_from_user/ 找图片 → 调 CCC vlm MSM 识别)
|
|
249
|
+
*/
|
|
250
|
+
const IMAGE_NOTE_TEMPLATE_SINGLE = "用户提供了一张图片(已保存到 _tmp/images_from_user/),请查看该目录下的图片并处理";
|
|
251
|
+
const IMAGE_NOTE_TEMPLATE_MULTI = (count) => `用户提供了 ${count} 张图片(已保存到 _tmp/images_from_user/),请查看该目录下的图片并处理`;
|
|
252
|
+
/** 浏览器 File → base64(与 ui-conversation serializeImages 等价的最小实现) */
|
|
253
|
+
function fileToBase64(file) {
|
|
254
|
+
return file.arrayBuffer().then((buffer) => {
|
|
255
|
+
const bytes = new Uint8Array(buffer);
|
|
256
|
+
let binary = "";
|
|
257
|
+
const chunk = 32768;
|
|
258
|
+
for (let offset = 0; offset < bytes.length; offset += chunk) binary += String.fromCharCode(...bytes.subarray(offset, offset + chunk));
|
|
259
|
+
return btoa(binary);
|
|
260
|
+
});
|
|
261
|
+
}
|
|
262
|
+
/**
|
|
263
|
+
* 上传一张图片到 CCC _tmp/images_from_user/,返回相对路径(如 _tmp/images_from_user/xxx.png)。
|
|
264
|
+
* sessionId 必传:node half 经会话 header.cwd 解析 CCC 根(进程 cwd 不可靠)。
|
|
265
|
+
*/
|
|
266
|
+
async function uploadImage(file, sessionId) {
|
|
267
|
+
const data = await fileToBase64(file);
|
|
268
|
+
const res = await fetch(UPLOAD_PATH, {
|
|
269
|
+
method: "POST",
|
|
270
|
+
headers: {
|
|
271
|
+
"content-type": "application/json",
|
|
272
|
+
"x-serenity-ui": "1"
|
|
273
|
+
},
|
|
274
|
+
body: JSON.stringify({
|
|
275
|
+
sessionId,
|
|
276
|
+
mediaType: file.type,
|
|
277
|
+
name: file.name,
|
|
278
|
+
data
|
|
279
|
+
})
|
|
280
|
+
});
|
|
281
|
+
const body = await res.json();
|
|
282
|
+
if (!res.ok || typeof body.path !== "string") throw new Error(`serenity image upload failed: ${body.error ?? res.status}`);
|
|
283
|
+
return body.path;
|
|
284
|
+
}
|
|
285
|
+
/**
|
|
286
|
+
* 取 rail 图片的浏览器 File。
|
|
287
|
+
* conversation.draftImages 是 root singleton 的公开方法(读 controller 的 draftAttachments Map,
|
|
288
|
+
* 与调用 ctx 的作用域无关)——直接 ctx.get('conversation')。
|
|
289
|
+
* ⚠️ 必须作为方法调用(conversation.draftImages(ids)):解构取出再调会丢失 this
|
|
290
|
+
* (draftImages 内部读 this.draftAttachments → "Cannot read properties of undefined")。
|
|
291
|
+
*/
|
|
292
|
+
async function getDraftFiles(ctx, _sessionId, ids) {
|
|
293
|
+
const conversation = ctx.get?.("conversation");
|
|
294
|
+
if (conversation?.draftImages === void 0) return [];
|
|
295
|
+
return (conversation.draftImages(ids) ?? []).map((a) => a.file);
|
|
296
|
+
}
|
|
297
|
+
/**
|
|
298
|
+
* 纯文本重发(绕过图片门禁——不含 image part,模型永不触发 MODEL_DOES_NOT_SUPPORT_IMAGES)。
|
|
299
|
+
* ⚠️ 必须作为方法调用(session.prompt(...)):解构取出再调会丢失 this
|
|
300
|
+
* (prompt 内部读 this.promptError → "Cannot set properties of undefined (setting 'promptError')")。
|
|
301
|
+
*/
|
|
302
|
+
async function resendText(ctx, sessionId, text) {
|
|
303
|
+
const session = ctx.sessions.binding(sessionId)?.session;
|
|
304
|
+
if (session?.prompt === void 0) throw new Error("serenity image fallback: session unavailable");
|
|
305
|
+
const result = await session.prompt([{
|
|
306
|
+
type: "text",
|
|
307
|
+
text
|
|
308
|
+
}], "queue");
|
|
309
|
+
if (!result.ok) throw new Error(`serenity image fallback resend failed: ${result.error?.code}: ${result.error?.message}`);
|
|
310
|
+
}
|
|
311
|
+
//#endregion
|
|
243
312
|
//#region \0sp-css:/home/yh/home/home-serenity/AI_LAB/dsh-serenity-plugin/hooks/dsh-serenity-hooks/src/client/ImageFallbackDock
|
|
244
313
|
const css = "/* ImageFallbackDock — 图片自动落盘兜底状态条(S142) */\n\n.serenity-image-fallback {\n display: inline-flex;\n align-items: center;\n gap: 8px;\n font-size: 12px;\n line-height: 1.5;\n padding: 2px 8px;\n border-radius: 6px;\n background: var(--dsw-alias-surface-muted, rgba(127, 127, 127, 0.1));\n color: var(--dsw-alias-text-secondary, currentColor);\n}\n\n.serenity-image-fallback[data-state='busy'] {\n color: var(--dsw-alias-text-secondary, currentColor);\n}\n\n.serenity-image-fallback[data-state='done'] {\n color: var(--dsw-alias-text-success, currentColor);\n}\n\n.serenity-image-fallback[data-state='error'] {\n color: var(--dsw-alias-text-danger, currentColor);\n}\n\n.serenity-image-fallback code {\n font-family: var(--dsw-alias-font-mono, monospace);\n font-size: 11px;\n background: var(--dsw-alias-surface-muted, rgba(127, 127, 127, 0.12));\n border-radius: 4px;\n padding: 0 4px;\n}\n";
|
|
245
314
|
if (typeof document !== "undefined" && document.querySelector("style[data-sp-css]") === null) {
|
|
@@ -276,7 +345,7 @@ window.__ModuleLoader__.load({
|
|
|
276
345
|
if (files.length === 0) throw new Error("no draft image files");
|
|
277
346
|
const saved = [];
|
|
278
347
|
for (const file of files) saved.push(await uploadImage(file, String(sessionId)));
|
|
279
|
-
const note = saved.
|
|
348
|
+
const note = saved.length === 1 ? IMAGE_NOTE_TEMPLATE_SINGLE : IMAGE_NOTE_TEMPLATE_MULTI(saved.length);
|
|
280
349
|
const draft = input?.draft ?? "";
|
|
281
350
|
const text = draft === "" ? note : `${draft}\n${note}`;
|
|
282
351
|
if (inputActions?.removeImage !== void 0 && inputActions.setDraft !== void 0 && inputActions.submit !== void 0) {
|
|
@@ -326,69 +395,6 @@ window.__ModuleLoader__.load({
|
|
|
326
395
|
});
|
|
327
396
|
}
|
|
328
397
|
//#endregion
|
|
329
|
-
//#region src/client/image-fallback-api.ts
|
|
330
|
-
/** 图片落盘接口路径(node half api.ts,client 专属 x-serenity-ui 头) */
|
|
331
|
-
const UPLOAD_PATH = "/serenity/image-upload";
|
|
332
|
-
/** 浏览器 File → base64(与 ui-conversation serializeImages 等价的最小实现) */
|
|
333
|
-
function fileToBase64(file) {
|
|
334
|
-
return file.arrayBuffer().then((buffer) => {
|
|
335
|
-
const bytes = new Uint8Array(buffer);
|
|
336
|
-
let binary = "";
|
|
337
|
-
const chunk = 32768;
|
|
338
|
-
for (let offset = 0; offset < bytes.length; offset += chunk) binary += String.fromCharCode(...bytes.subarray(offset, offset + chunk));
|
|
339
|
-
return btoa(binary);
|
|
340
|
-
});
|
|
341
|
-
}
|
|
342
|
-
/**
|
|
343
|
-
* 上传一张图片到 CCC _tmp/images_from_user/,返回相对路径(如 _tmp/images_from_user/xxx.png)。
|
|
344
|
-
* sessionId 必传:node half 经会话 header.cwd 解析 CCC 根(进程 cwd 不可靠)。
|
|
345
|
-
*/
|
|
346
|
-
async function uploadImage(file, sessionId) {
|
|
347
|
-
const data = await fileToBase64(file);
|
|
348
|
-
const res = await fetch(UPLOAD_PATH, {
|
|
349
|
-
method: "POST",
|
|
350
|
-
headers: {
|
|
351
|
-
"content-type": "application/json",
|
|
352
|
-
"x-serenity-ui": "1"
|
|
353
|
-
},
|
|
354
|
-
body: JSON.stringify({
|
|
355
|
-
sessionId,
|
|
356
|
-
mediaType: file.type,
|
|
357
|
-
name: file.name,
|
|
358
|
-
data
|
|
359
|
-
})
|
|
360
|
-
});
|
|
361
|
-
const body = await res.json();
|
|
362
|
-
if (!res.ok || typeof body.path !== "string") throw new Error(`serenity image upload failed: ${body.error ?? res.status}`);
|
|
363
|
-
return body.path;
|
|
364
|
-
}
|
|
365
|
-
/**
|
|
366
|
-
* 取 rail 图片的浏览器 File。
|
|
367
|
-
* conversation.draftImages 是 root singleton 的公开方法(读 controller 的 draftAttachments Map,
|
|
368
|
-
* 与调用 ctx 的作用域无关)——直接 ctx.get('conversation')。
|
|
369
|
-
* ⚠️ 必须作为方法调用(conversation.draftImages(ids)):解构取出再调会丢失 this
|
|
370
|
-
* (draftImages 内部读 this.draftAttachments → "Cannot read properties of undefined")。
|
|
371
|
-
*/
|
|
372
|
-
async function getDraftFiles(ctx, _sessionId, ids) {
|
|
373
|
-
const conversation = ctx.get?.("conversation");
|
|
374
|
-
if (conversation?.draftImages === void 0) return [];
|
|
375
|
-
return (conversation.draftImages(ids) ?? []).map((a) => a.file);
|
|
376
|
-
}
|
|
377
|
-
/**
|
|
378
|
-
* 纯文本重发(绕过图片门禁——不含 image part,模型永不触发 MODEL_DOES_NOT_SUPPORT_IMAGES)。
|
|
379
|
-
* ⚠️ 必须作为方法调用(session.prompt(...)):解构取出再调会丢失 this
|
|
380
|
-
* (prompt 内部读 this.promptError → "Cannot set properties of undefined (setting 'promptError')")。
|
|
381
|
-
*/
|
|
382
|
-
async function resendText(ctx, sessionId, text) {
|
|
383
|
-
const session = ctx.sessions.binding(sessionId)?.session;
|
|
384
|
-
if (session?.prompt === void 0) throw new Error("serenity image fallback: session unavailable");
|
|
385
|
-
const result = await session.prompt([{
|
|
386
|
-
type: "text",
|
|
387
|
-
text
|
|
388
|
-
}], "queue");
|
|
389
|
-
if (!result.ok) throw new Error(`serenity image fallback resend failed: ${result.error?.code}: ${result.error?.message}`);
|
|
390
|
-
}
|
|
391
|
-
//#endregion
|
|
392
398
|
//#region src/client/index.ts
|
|
393
399
|
const inject = [
|
|
394
400
|
"slots",
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@shgroup/dsh-serenity-hooks",
|
|
3
|
-
"version": "1.20.
|
|
3
|
+
"version": "1.20.5",
|
|
4
4
|
"description": "宁静号 ACC harness — Native Cordis 插件(DeepSeek Harness 运行时)。真实 DSH 工具注册(cc_fs/session/acc_msm 等 9 工具)+ 拦截缝机械约束(safe-mode/路径守卫/会话落盘)+ 系统提示词注入(ACC/CCE/Constraints/SKILL/Session 五块)。适配 DSH 公开版(deepseek-ai/deepseek-harness 0.1.0-rc)。",
|
|
5
5
|
"license": "MIT",
|
|
6
6
|
"repository": {
|