@shgroup/dsh-serenity-hooks 1.20.3 → 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 CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "id": "dsh-serenity-hooks",
3
- "version": "1.20.3",
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) {
@@ -250,13 +319,14 @@ window.__ModuleLoader__.load({
250
319
  }
251
320
  //#endregion
252
321
  //#region src/client/ImageFallbackDock.tsx
253
- /** input.dock 条目:图片发送失败自动落盘 + 文本重发 */
322
+ /** input.dock 条目:图片发送失败自动落盘 + 文本重发(补救后自动清空输入框 rail 图片) */
254
323
  function ImageFallbackDock(props) {
255
324
  const zone = props;
256
325
  const session = zone.session;
257
326
  const input = zone.input;
258
327
  const sessionId = props.sessionId;
259
328
  const { uploadImage, getDraftFiles, resendText } = props;
329
+ const inputActions = props.inputActions;
260
330
  const [state, setState] = (0, react.useState)("idle");
261
331
  const [paths, setPaths] = (0, react.useState)([]);
262
332
  const [errorText, setErrorText] = (0, react.useState)(null);
@@ -275,10 +345,14 @@ window.__ModuleLoader__.load({
275
345
  if (files.length === 0) throw new Error("no draft image files");
276
346
  const saved = [];
277
347
  for (const file of files) saved.push(await uploadImage(file, String(sessionId)));
278
- const note = saved.map((p) => `用户提供了图片在 ${p}`).join("\n");
348
+ const note = saved.length === 1 ? IMAGE_NOTE_TEMPLATE_SINGLE : IMAGE_NOTE_TEMPLATE_MULTI(saved.length);
279
349
  const draft = input?.draft ?? "";
280
350
  const text = draft === "" ? note : `${draft}\n${note}`;
281
- await resendText(String(sessionId), text);
351
+ if (inputActions?.removeImage !== void 0 && inputActions.setDraft !== void 0 && inputActions.submit !== void 0) {
352
+ for (const id of imageIds) inputActions.removeImage(String(id));
353
+ inputActions.setDraft(text);
354
+ inputActions.submit();
355
+ } else await resendText(String(sessionId), text);
282
356
  setPaths(saved);
283
357
  setState("done");
284
358
  } catch (err) {
@@ -297,7 +371,8 @@ window.__ModuleLoader__.load({
297
371
  uploadImage,
298
372
  getDraftFiles,
299
373
  resendText,
300
- input?.draft
374
+ input?.draft,
375
+ inputActions
301
376
  ]);
302
377
  if (state === "idle") return /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
303
378
  className: "serenity-image-fallback",
@@ -320,69 +395,6 @@ window.__ModuleLoader__.load({
320
395
  });
321
396
  }
322
397
  //#endregion
323
- //#region src/client/image-fallback-api.ts
324
- /** 图片落盘接口路径(node half api.ts,client 专属 x-serenity-ui 头) */
325
- const UPLOAD_PATH = "/serenity/image-upload";
326
- /** 浏览器 File → base64(与 ui-conversation serializeImages 等价的最小实现) */
327
- function fileToBase64(file) {
328
- return file.arrayBuffer().then((buffer) => {
329
- const bytes = new Uint8Array(buffer);
330
- let binary = "";
331
- const chunk = 32768;
332
- for (let offset = 0; offset < bytes.length; offset += chunk) binary += String.fromCharCode(...bytes.subarray(offset, offset + chunk));
333
- return btoa(binary);
334
- });
335
- }
336
- /**
337
- * 上传一张图片到 CCC _tmp/images_from_user/,返回相对路径(如 _tmp/images_from_user/xxx.png)。
338
- * sessionId 必传:node half 经会话 header.cwd 解析 CCC 根(进程 cwd 不可靠)。
339
- */
340
- async function uploadImage(file, sessionId) {
341
- const data = await fileToBase64(file);
342
- const res = await fetch(UPLOAD_PATH, {
343
- method: "POST",
344
- headers: {
345
- "content-type": "application/json",
346
- "x-serenity-ui": "1"
347
- },
348
- body: JSON.stringify({
349
- sessionId,
350
- mediaType: file.type,
351
- name: file.name,
352
- data
353
- })
354
- });
355
- const body = await res.json();
356
- if (!res.ok || typeof body.path !== "string") throw new Error(`serenity image upload failed: ${body.error ?? res.status}`);
357
- return body.path;
358
- }
359
- /**
360
- * 取 rail 图片的浏览器 File。
361
- * conversation.draftImages 是 root singleton 的公开方法(读 controller 的 draftAttachments Map,
362
- * 与调用 ctx 的作用域无关)——直接 ctx.get('conversation')。
363
- * ⚠️ 必须作为方法调用(conversation.draftImages(ids)):解构取出再调会丢失 this
364
- * (draftImages 内部读 this.draftAttachments → "Cannot read properties of undefined")。
365
- */
366
- async function getDraftFiles(ctx, _sessionId, ids) {
367
- const conversation = ctx.get?.("conversation");
368
- if (conversation?.draftImages === void 0) return [];
369
- return (conversation.draftImages(ids) ?? []).map((a) => a.file);
370
- }
371
- /**
372
- * 纯文本重发(绕过图片门禁——不含 image part,模型永不触发 MODEL_DOES_NOT_SUPPORT_IMAGES)。
373
- * ⚠️ 必须作为方法调用(session.prompt(...)):解构取出再调会丢失 this
374
- * (prompt 内部读 this.promptError → "Cannot set properties of undefined (setting 'promptError')")。
375
- */
376
- async function resendText(ctx, sessionId, text) {
377
- const session = ctx.sessions.binding(sessionId)?.session;
378
- if (session?.prompt === void 0) throw new Error("serenity image fallback: session unavailable");
379
- const result = await session.prompt([{
380
- type: "text",
381
- text
382
- }], "queue");
383
- if (!result.ok) throw new Error(`serenity image fallback resend failed: ${result.error?.code}: ${result.error?.message}`);
384
- }
385
- //#endregion
386
398
  //#region src/client/index.ts
387
399
  const inject = [
388
400
  "slots",
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@shgroup/dsh-serenity-hooks",
3
- "version": "1.20.3",
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": {