@shgroup/dsh-serenity-hooks 1.25.10 → 1.26.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.
package/dsh.plugin.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "id": "dsh-serenity-hooks",
3
- "version": "1.25.10",
3
+ "version": "1.26.0",
4
4
  "main": "lib/index.js",
5
5
  "description": "宁静号 ACC harness(Native Cordis 插件):真实 DSH 工具 cc_fs/session/acc_msm/eap/neat/cce/handyman/session_rebuild/skiff_admin + 拦截缝机械约束(safe-mode/路径守卫)+ 高级设定面板(双端口网关/账号管理)+ Skiff 认知子集角色(实验性)。适配 DSH 公开版(0.1.0-rc,deepseek-ai/deepseek-harness)。",
6
6
  "engines": {
package/lib/client.js CHANGED
@@ -3895,6 +3895,9 @@ Instruction-following style:
3895
3895
  const setSkiffPort = (v) => {
3896
3896
  scope.set("skiffDebugPort", Math.min(65535, Math.max(1024, Math.round(v))));
3897
3897
  };
3898
+ const setAcpPort = (v) => {
3899
+ scope.set("acpHttpPort", Math.min(65535, Math.max(1024, Math.round(v))));
3900
+ };
3898
3901
  if (!ready) return /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
3899
3902
  className: "ss-section",
3900
3903
  children: /* @__PURE__ */ (0, react_jsx_runtime.jsx)("p", {
@@ -3908,6 +3911,8 @@ Instruction-following style:
3908
3911
  const namingOn = value?.namingEnabled ?? true;
3909
3912
  const skiffOn = value?.skiffEnabled ?? false;
3910
3913
  const skiffPort = value?.skiffDebugPort ?? 3099;
3914
+ const acpOn = value?.acpEnabled ?? false;
3915
+ const acpPort = value?.acpHttpPort ?? 3100;
3911
3916
  return /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
3912
3917
  className: "ss-section",
3913
3918
  children: [
@@ -3996,6 +4001,32 @@ Instruction-following style:
3996
4001
  })
3997
4002
  }) })]
3998
4003
  }),
4004
+ /* @__PURE__ */ (0, react_jsx_runtime.jsxs)(Group, {
4005
+ title: "ACP",
4006
+ children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("li", { children: /* @__PURE__ */ (0, react_jsx_runtime.jsx)(RowCard, {
4007
+ title: "ACP JSON-RPC 端点",
4008
+ desc: "程序化调用 Skiff 角色(指定 CCC+角色+会话;实验性,仅监听 127.0.0.1)",
4009
+ control: /* @__PURE__ */ (0, react_jsx_runtime.jsx)(Toggle, {
4010
+ checked: acpOn,
4011
+ onChange: (on) => toggle("acpEnabled", on)
4012
+ })
4013
+ }) }), /* @__PURE__ */ (0, react_jsx_runtime.jsx)("li", { children: /* @__PURE__ */ (0, react_jsx_runtime.jsx)(RowCard, {
4014
+ title: "HTTP 端口",
4015
+ desc: "ACP JSON-RPC 端口(1024 ~ 65535,默认 3100)",
4016
+ control: /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
4017
+ className: "ss-threshold",
4018
+ children: /* @__PURE__ */ (0, react_jsx_runtime.jsx)("input", {
4019
+ className: "ss-portInput",
4020
+ type: "number",
4021
+ min: 1024,
4022
+ max: 65535,
4023
+ value: acpPort,
4024
+ disabled: !acpOn,
4025
+ onChange: (e) => setAcpPort(Number(e.target.value))
4026
+ })
4027
+ })
4028
+ }) })]
4029
+ }),
3999
4030
  /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
4000
4031
  className: "ss-group",
4001
4032
  children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("h3", {
package/lib/index.js CHANGED
@@ -736,6 +736,13 @@ function registerSkiffSession$1(sessionId, role, ccc) {
736
736
  ccc
737
737
  });
738
738
  }
739
+ function unregisterSkiffSession$1(sessionId) {
740
+ skiffSessions.delete(sessionId);
741
+ }
742
+ /** 测试/调试:注册表快照(sessionId → {role, ccc}) */
743
+ function skiffSessionSnapshot$1() {
744
+ return new Map(skiffSessions);
745
+ }
739
746
  //#endregion
740
747
  //#region src/seams/guards.ts
741
748
  /**
@@ -2394,6 +2401,19 @@ function registerSkiffSession(sessionId, role, ccc, agent) {
2394
2401
  skiffAgents.set(sessionId, agent);
2395
2402
  registerSkiffSession$1(sessionId, role, ccc);
2396
2403
  }
2404
+ function unregisterSkiffSession(sessionId) {
2405
+ skiffAgents.delete(sessionId);
2406
+ unregisterSkiffSession$1(sessionId);
2407
+ }
2408
+ /** 测试/调试:注册表快照(sessionId → {role} 兼容展示) */
2409
+ function skiffSessionSnapshot() {
2410
+ const out = /* @__PURE__ */ new Map();
2411
+ for (const [id, b] of skiffSessionSnapshot$1()) out.set(id, {
2412
+ role: b.role,
2413
+ ccc: b.ccc
2414
+ });
2415
+ return out;
2416
+ }
2397
2417
  /** Skiff agent 挂载的 DSH preset(v1.25.3 修复:read/grep/glob 等平台工具由 preset 决定工具面;
2398
2418
  * handyman 经 composeFrom 继承父、skiff 无父上下文——直接挂 DSH 默认 standard preset;
2399
2419
  * guard 角色白名单再按角色过滤可见/可用面——白名单外工具仍 deny) */
@@ -5237,7 +5257,9 @@ const simpleSettingsSchema = z.object({
5237
5257
  rebuildThreshold: z.number().min(.01).max(1).default(.9),
5238
5258
  namingEnabled: z.boolean().default(true),
5239
5259
  skiffEnabled: z.boolean().default(false),
5240
- skiffDebugPort: z.number().min(1024).max(65535).default(3099)
5260
+ skiffDebugPort: z.number().min(1024).max(65535).default(3099),
5261
+ acpEnabled: z.boolean().default(false),
5262
+ acpHttpPort: z.number().min(1024).max(65535).default(3100)
5241
5263
  });
5242
5264
  /** 从插件 Config 提取 entry 默认(settings base 层) */
5243
5265
  function entryDefaults(config) {
@@ -5247,7 +5269,9 @@ function entryDefaults(config) {
5247
5269
  rebuildThreshold: config.rebuild?.thresholdRatio ?? .9,
5248
5270
  namingEnabled: config.naming?.enabled ?? true,
5249
5271
  skiffEnabled: config.skiff?.enabled ?? false,
5250
- skiffDebugPort: config.skiff?.debugPort ?? 3099
5272
+ skiffDebugPort: config.skiff?.debugPort ?? 3099,
5273
+ acpEnabled: config.acp?.enabled ?? false,
5274
+ acpHttpPort: config.acp?.httpPort ?? 3100
5251
5275
  };
5252
5276
  }
5253
5277
  /** 运行时源(installSettingsSection 注入:settings scope 或 entry fallback) */
@@ -5260,7 +5284,9 @@ function defaultSimpleSettings() {
5260
5284
  rebuildThreshold: .9,
5261
5285
  namingEnabled: true,
5262
5286
  skiffEnabled: false,
5263
- skiffDebugPort: 3099
5287
+ skiffDebugPort: 3099,
5288
+ acpEnabled: false,
5289
+ acpHttpPort: 3100
5264
5290
  };
5265
5291
  }
5266
5292
  /**
@@ -5984,6 +6010,43 @@ function renameDshSessionOnUse(deps, session, titles, active) {
5984
6010
  };
5985
6011
  }
5986
6012
  }
6013
+ /**
6014
+ * 从 create 结果构造命名用 ActiveSessionInfo(v1.25.11,S142 用户:create 也要命名):
6015
+ * createSession 返回的 result 已含 sessionId(S###/issue)与 dirName——无需等待 use 激活,
6016
+ * 直接构造(mdPath = sessionPath/SESSION.md,与 use 时一致)即可驱动 renameDshSessionOnUse。
6017
+ */
6018
+ function activeInfoFromCreate(result) {
6019
+ return {
6020
+ sessionId: result.sessionId,
6021
+ dirName: result.dirName,
6022
+ mdPath: join(result.sessionPath, "SESSION.md")
6023
+ };
6024
+ }
6025
+ /**
6026
+ * 把当前 dsh 会话重命名为指定 SESSION 的命名标题(use/create 共用;v1.25.11)。
6027
+ * 门控/失败可见性与 renameDshSessionOnUse 一致(不静默:成功 log / 失败 warn)。
6028
+ * 调用点(use 分支原内联逻辑提取,create 分支复用):
6029
+ * - use:激活后从 activeStore 取 info
6030
+ * - create:createSession 结果经 activeInfoFromCreate 构造 info
6031
+ */
6032
+ function renameDshSessionForActive(ctx, exec, info) {
6033
+ try {
6034
+ const titles = ctx.get?.("sessionTitle");
6035
+ const dshSession = exec.agent?.session;
6036
+ if (!dshSession) {
6037
+ console.warn(`[serenity-hooks] dsh 会话重命名未执行: 缺少 agent session(info: ${info.sessionId})`);
6038
+ return;
6039
+ }
6040
+ const result = renameDshSessionOnUse({
6041
+ namingEnabled: readSimpleSettings().namingEnabled,
6042
+ sessionTitleAvailable: true
6043
+ }, dshSession, titles, info);
6044
+ if (result.ok) console.log(`[serenity-hooks] dsh 会话已重命名: ${String(dshSession.id ?? "?")} → ${result.title}`);
6045
+ else console.warn(`[serenity-hooks] dsh 会话重命名未执行: ${result.reason}`);
6046
+ } catch (err) {
6047
+ console.warn(`[serenity-hooks] dsh 会话重命名异常: ${String(err?.message ?? err)}`);
6048
+ }
6049
+ }
5987
6050
  function renderText$3(value) {
5988
6051
  return [{
5989
6052
  type: "text",
@@ -6142,6 +6205,7 @@ function createSessionTool(ctx) {
6142
6205
  dryRun: args.dryRun ?? false
6143
6206
  });
6144
6207
  let message = result.message;
6208
+ if (!(args.dryRun ?? false)) renameDshSessionForActive(ctx, exec, activeInfoFromCreate(result));
6145
6209
  if (!(args.dryRun ?? false) && cccHooks.includes("create-transform")) try {
6146
6210
  const hookResult = await runMsmAsync(root, {
6147
6211
  action: "exec",
@@ -6159,23 +6223,8 @@ function createSessionTool(ctx) {
6159
6223
  if (!args.name) throw new Error("use requires name (S### or directory name)");
6160
6224
  const scope = agentScope$2(exec);
6161
6225
  const active = useSession(root, args.name, scope);
6162
- try {
6163
- const info = getActiveSessionInfo(scope);
6164
- if (info) {
6165
- const titles = ctx.get?.("sessionTitle");
6166
- const dshSession = exec.agent?.session;
6167
- if (dshSession) {
6168
- const result = renameDshSessionOnUse({
6169
- namingEnabled: readSimpleSettings().namingEnabled,
6170
- sessionTitleAvailable: true
6171
- }, dshSession, titles, info);
6172
- if (result.ok) console.log(`[serenity-hooks] dsh 会话已重命名: ${String(dshSession.id ?? "?")} → ${result.title}`);
6173
- else console.warn(`[serenity-hooks] dsh 会话重命名未执行: ${result.reason}`);
6174
- } else console.warn(`[serenity-hooks] dsh 会话重命名未执行: 缺少 agent session`);
6175
- }
6176
- } catch (err) {
6177
- console.warn(`[serenity-hooks] dsh 会话重命名异常: ${String(err?.message ?? err)}`);
6178
- }
6226
+ const info = getActiveSessionInfo(scope);
6227
+ if (info) renameDshSessionForActive(ctx, exec, info);
6179
6228
  return active;
6180
6229
  }
6181
6230
  case "close":
@@ -6875,7 +6924,11 @@ dsp 只提供机制(双白名单强制 + 基础提示词 + 调试问答页)
6875
6924
 
6876
6925
  ── 运行 ──
6877
6926
  调试:设置面板「Serenity」页 Skiff 区块开启 → http://127.0.0.1:<debugPort> 问答页实测
6878
- v1.25.0 唯一客户端面;ACP stdio 协议 F4c 后续,与调试页共用同一会话核心)
6927
+ ACP 程序化面(v1.26.0):设置面板「ACP」区块开启 HTTP JSON-RPC 端点(默认 3100)——
6928
+ POST / {jsonrpc, id, method, params}
6929
+ session/new {ccc, role, sessionId?} → 创建/延续会话(sessionId 可选=进程内追问)
6930
+ session/prompt {sessionId, question} → {answer, trajectory}
6931
+ session/cancel / session/close / session/list / request_permission(恒 allow)
6879
6932
  启停 = 人工(设置面板开关,不随插件加载自动启动);未开启零资源占用
6880
6933
  生效机制:角色配置**实时读取**(改 .opencode/serenity.json → 刷新调试页即生效);
6881
6934
  改配置后用 skiff_admin apply 做显式校验 + 应用确认(绑定 CCC + 角色清单)
@@ -8275,7 +8328,7 @@ function saveFileToTmp(root, fileName, data) {
8275
8328
  writeFileSync(join(dir, filename), bytes);
8276
8329
  return `${FILE_UPLOAD_DIR}/${filename}`;
8277
8330
  }
8278
- function readBody$2(req, maxBytes) {
8331
+ function readBody$3(req, maxBytes) {
8279
8332
  return new Promise((resolve, reject) => {
8280
8333
  let data = "";
8281
8334
  req.on("data", (chunk) => {
@@ -8289,7 +8342,7 @@ function readBody$2(req, maxBytes) {
8289
8342
  req.on("error", reject);
8290
8343
  });
8291
8344
  }
8292
- function sendJson$1(res, code, body) {
8345
+ function sendJson$2(res, code, body) {
8293
8346
  const payload = JSON.stringify(body);
8294
8347
  res.writeHead(code, {
8295
8348
  "content-type": "application/json; charset=utf-8",
@@ -8327,7 +8380,7 @@ function registerStatusApi(ctx, opts = {}) {
8327
8380
  handler: async (req, res) => {
8328
8381
  try {
8329
8382
  if (req.method !== "GET") {
8330
- sendJson$1(res, 405, { error: "method not allowed" });
8383
+ sendJson$2(res, 405, { error: "method not allowed" });
8331
8384
  return;
8332
8385
  }
8333
8386
  const url = new URL(req.url ?? "/", "http://127.0.0.1");
@@ -8336,12 +8389,12 @@ function registerStatusApi(ctx, opts = {}) {
8336
8389
  workspace: url.searchParams.get("workspace") ?? void 0
8337
8390
  }));
8338
8391
  if (!root) {
8339
- sendJson$1(res, 200, { handymen: [] });
8392
+ sendJson$2(res, 200, { handymen: [] });
8340
8393
  return;
8341
8394
  }
8342
- sendJson$1(res, 200, { handymen: listActiveHandymen(root) });
8395
+ sendJson$2(res, 200, { handymen: listActiveHandymen(root) });
8343
8396
  } catch (err) {
8344
- sendJson$1(res, 400, { error: err.message ?? String(err) });
8397
+ sendJson$2(res, 400, { error: err.message ?? String(err) });
8345
8398
  }
8346
8399
  }
8347
8400
  });
@@ -8351,14 +8404,14 @@ function registerStatusApi(ctx, opts = {}) {
8351
8404
  handler: async (req, res) => {
8352
8405
  try {
8353
8406
  if (req.method !== "POST") {
8354
- sendJson$1(res, 405, { error: "method not allowed" });
8407
+ sendJson$2(res, 405, { error: "method not allowed" });
8355
8408
  return;
8356
8409
  }
8357
8410
  if (req.headers["x-serenity-ui"] !== "1") {
8358
- sendJson$1(res, 403, { error: "图片落盘仅限 WebUI(client 专用)" });
8411
+ sendJson$2(res, 403, { error: "图片落盘仅限 WebUI(client 专用)" });
8359
8412
  return;
8360
8413
  }
8361
- const raw = await readBody$2(req, 20971520);
8414
+ const raw = await readBody$3(req, 20971520);
8362
8415
  const body = JSON.parse(raw);
8363
8416
  const workspace = resolveWorkspace(ctx, {
8364
8417
  sessionId: body.sessionId,
@@ -8366,12 +8419,12 @@ function registerStatusApi(ctx, opts = {}) {
8366
8419
  });
8367
8420
  const root = findSerenityRoot(workspace);
8368
8421
  if (!root) {
8369
- sendJson$1(res, 404, { error: `no CCC found from workspace: ${workspace}` });
8422
+ sendJson$2(res, 404, { error: `no CCC found from workspace: ${workspace}` });
8370
8423
  return;
8371
8424
  }
8372
- sendJson$1(res, 200, { path: saveImageToTmp(root, body.mediaType ?? "", body.data ?? "") });
8425
+ sendJson$2(res, 200, { path: saveImageToTmp(root, body.mediaType ?? "", body.data ?? "") });
8373
8426
  } catch (err) {
8374
- sendJson$1(res, 400, { error: err.message ?? String(err) });
8427
+ sendJson$2(res, 400, { error: err.message ?? String(err) });
8375
8428
  }
8376
8429
  }
8377
8430
  });
@@ -8381,14 +8434,14 @@ function registerStatusApi(ctx, opts = {}) {
8381
8434
  handler: async (req, res) => {
8382
8435
  try {
8383
8436
  if (req.method !== "POST") {
8384
- sendJson$1(res, 405, { error: "method not allowed" });
8437
+ sendJson$2(res, 405, { error: "method not allowed" });
8385
8438
  return;
8386
8439
  }
8387
8440
  if (req.headers["x-serenity-ui"] !== "1") {
8388
- sendJson$1(res, 403, { error: "文件落盘仅限 WebUI(client 专用)" });
8441
+ sendJson$2(res, 403, { error: "文件落盘仅限 WebUI(client 专用)" });
8389
8442
  return;
8390
8443
  }
8391
- const raw = await readBody$2(req, 20971520);
8444
+ const raw = await readBody$3(req, 20971520);
8392
8445
  const body = JSON.parse(raw);
8393
8446
  const workspace = resolveWorkspace(ctx, {
8394
8447
  sessionId: body.sessionId,
@@ -8396,12 +8449,12 @@ function registerStatusApi(ctx, opts = {}) {
8396
8449
  });
8397
8450
  const root = findSerenityRoot(workspace);
8398
8451
  if (!root) {
8399
- sendJson$1(res, 404, { error: `no CCC found from workspace: ${workspace}` });
8452
+ sendJson$2(res, 404, { error: `no CCC found from workspace: ${workspace}` });
8400
8453
  return;
8401
8454
  }
8402
- sendJson$1(res, 200, { path: saveFileToTmp(root, body.name ?? "", body.data ?? "") });
8455
+ sendJson$2(res, 200, { path: saveFileToTmp(root, body.name ?? "", body.data ?? "") });
8403
8456
  } catch (err) {
8404
- sendJson$1(res, 400, { error: err.message ?? String(err) });
8457
+ sendJson$2(res, 400, { error: err.message ?? String(err) });
8405
8458
  }
8406
8459
  }
8407
8460
  });
@@ -8417,7 +8470,7 @@ function registerStatusApi(ctx, opts = {}) {
8417
8470
  workspace: url.searchParams.get("workspace") ?? void 0
8418
8471
  }), configPaths);
8419
8472
  const runtime = ctx.get("codeRuntime");
8420
- sendJson$1(res, 200, {
8473
+ sendJson$2(res, 200, {
8421
8474
  ...status,
8422
8475
  ...runtime === void 0 ? { codeRuntime: null } : { codeRuntime: {
8423
8476
  language: runtime.language,
@@ -8428,10 +8481,10 @@ function registerStatusApi(ctx, opts = {}) {
8428
8481
  }
8429
8482
  if (req.method === "POST") {
8430
8483
  if (req.headers["x-serenity-ui"] !== "1") {
8431
- sendJson$1(res, 403, { error: "safe-mode 切换仅限 WebUI(agent 不可自行开关)" });
8484
+ sendJson$2(res, 403, { error: "safe-mode 切换仅限 WebUI(agent 不可自行开关)" });
8432
8485
  return;
8433
8486
  }
8434
- const raw = await readBody$2(req, 65536);
8487
+ const raw = await readBody$3(req, 65536);
8435
8488
  const body = JSON.parse(raw);
8436
8489
  const workspace = resolveWorkspace(ctx, {
8437
8490
  sessionId: body.sessionId,
@@ -8439,19 +8492,19 @@ function registerStatusApi(ctx, opts = {}) {
8439
8492
  });
8440
8493
  const root = findSerenityRoot(workspace);
8441
8494
  if (!root) {
8442
- sendJson$1(res, 404, { error: `no CCC found from workspace: ${workspace}` });
8495
+ sendJson$2(res, 404, { error: `no CCC found from workspace: ${workspace}` });
8443
8496
  return;
8444
8497
  }
8445
8498
  const result = setSafeMode(root, body.on === true);
8446
- sendJson$1(res, 200, {
8499
+ sendJson$2(res, 200, {
8447
8500
  ...getStatus(workspace, configPaths),
8448
8501
  ...result
8449
8502
  });
8450
8503
  return;
8451
8504
  }
8452
- sendJson$1(res, 405, { error: "method not allowed" });
8505
+ sendJson$2(res, 405, { error: "method not allowed" });
8453
8506
  } catch (err) {
8454
- sendJson$1(res, 400, { error: err.message ?? String(err) });
8507
+ sendJson$2(res, 400, { error: err.message ?? String(err) });
8455
8508
  }
8456
8509
  }
8457
8510
  });
@@ -8461,25 +8514,25 @@ function registerStatusApi(ctx, opts = {}) {
8461
8514
  handler: async (req, res) => {
8462
8515
  try {
8463
8516
  if (req.headers["x-serenity-ui"] !== "1") {
8464
- sendJson$1(res, 403, { error: "高级设定仅限 WebUI(client 专用)" });
8517
+ sendJson$2(res, 403, { error: "高级设定仅限 WebUI(client 专用)" });
8465
8518
  return;
8466
8519
  }
8467
8520
  if (req.method === "GET") {
8468
- sendJson$1(res, 200, { config: toWire(readAdvancedSettings()) });
8521
+ sendJson$2(res, 200, { config: toWire(readAdvancedSettings()) });
8469
8522
  return;
8470
8523
  }
8471
8524
  if (req.method === "PUT") {
8472
- const raw = await readBody$2(req, 131072);
8525
+ const raw = await readBody$3(req, 131072);
8473
8526
  const saved = applyWirePatch(JSON.parse(raw).config ?? {});
8474
8527
  try {
8475
8528
  ctx.emit?.("serenity/config-updated");
8476
8529
  } catch {}
8477
- sendJson$1(res, 200, { config: toWire(saved) });
8530
+ sendJson$2(res, 200, { config: toWire(saved) });
8478
8531
  return;
8479
8532
  }
8480
- sendJson$1(res, 405, { error: "method not allowed" });
8533
+ sendJson$2(res, 405, { error: "method not allowed" });
8481
8534
  } catch (err) {
8482
- sendJson$1(res, 400, { error: err.message ?? String(err) });
8535
+ sendJson$2(res, 400, { error: err.message ?? String(err) });
8483
8536
  }
8484
8537
  }
8485
8538
  });
@@ -8935,7 +8988,7 @@ function buildProxyHeaders(reqHeaders, mainPort, bodyOverride) {
8935
8988
  //#endregion
8936
8989
  //#region src/gateway.ts
8937
8990
  /** 读取请求体(≤ maxBytes;超限 reject) */
8938
- function readBody$1(req, maxBytes) {
8991
+ function readBody$2(req, maxBytes) {
8939
8992
  return new Promise((resolve, reject) => {
8940
8993
  let data = "";
8941
8994
  req.on("data", (chunk) => {
@@ -9068,7 +9121,7 @@ function startGateway(config, getAccounts) {
9068
9121
  if (session !== void 0) {
9069
9122
  if (req.method === "POST" && url.pathname === "/api/workspace.create") {
9070
9123
  if (!allowWorkspaceCreate) {
9071
- readBody$1(req, 131072).then((body) => {
9124
+ readBody$2(req, 131072).then((body) => {
9072
9125
  let rpcId = "unknown";
9073
9126
  try {
9074
9127
  const parsed = JSON.parse(body);
@@ -9104,7 +9157,7 @@ function startGateway(config, getAccounts) {
9104
9157
  });
9105
9158
  return;
9106
9159
  }
9107
- readBody$1(req, 131072).then((body) => {
9160
+ readBody$2(req, 131072).then((body) => {
9108
9161
  let rpcId = "unknown";
9109
9162
  let path;
9110
9163
  try {
@@ -11056,8 +11109,8 @@ x.lex;
11056
11109
  * 实验性质:未开启时零资源占用(无监听、无 agent 创建)。
11057
11110
  */
11058
11111
  /** 运行中的调试服务(单实例;进程级) */
11059
- let active = null;
11060
- function readBody(req) {
11112
+ let active$1 = null;
11113
+ function readBody$1(req) {
11061
11114
  return new Promise((resolve, reject) => {
11062
11115
  const chunks = [];
11063
11116
  req.on("data", (c) => chunks.push(c));
@@ -11065,7 +11118,7 @@ function readBody(req) {
11065
11118
  req.on("error", reject);
11066
11119
  });
11067
11120
  }
11068
- function sendJson(res, status, payload) {
11121
+ function sendJson$1(res, status, payload) {
11069
11122
  const body = JSON.stringify(payload);
11070
11123
  res.writeHead(status, {
11071
11124
  "Content-Type": "application/json; charset=utf-8",
@@ -11333,28 +11386,28 @@ function renderSkiffMarkdown(raw) {
11333
11386
  * @param webPort 主 WebUI 端口(WebUI 链接)
11334
11387
  */
11335
11388
  async function startSkiffDebugServer(ctx, root, port, webPort) {
11336
- if (active) return;
11389
+ if (active$1) return;
11337
11390
  const server = createServer((req, res) => {
11338
- handle(ctx, root, webPort, req, res);
11391
+ handle$1(ctx, root, webPort, req, res);
11339
11392
  });
11340
11393
  await new Promise((resolve, reject) => {
11341
11394
  server.once("error", reject);
11342
11395
  server.listen(port, "127.0.0.1", () => resolve());
11343
11396
  });
11344
- active = {
11397
+ active$1 = {
11345
11398
  server,
11346
11399
  port
11347
11400
  };
11348
11401
  console.log(`[serenity-hooks] ✓ Skiff 调试问答页: http://127.0.0.1:${port}(默认 CCC: ${root},WebUI: ${webPort})`);
11349
11402
  }
11350
11403
  function stopSkiffDebugServer() {
11351
- if (!active) return;
11404
+ if (!active$1) return;
11352
11405
  try {
11353
- active.server.close();
11406
+ active$1.server.close();
11354
11407
  } catch {}
11355
- active = null;
11408
+ active$1 = null;
11356
11409
  }
11357
- async function handle(ctx, defaultRoot, webPort, req, res) {
11410
+ async function handle$1(ctx, defaultRoot, webPort, req, res) {
11358
11411
  try {
11359
11412
  const url = (req.url ?? "/").split("?")[0] ?? "/";
11360
11413
  if (req.method === "GET" && url === "/") {
@@ -11364,11 +11417,11 @@ async function handle(ctx, defaultRoot, webPort, req, res) {
11364
11417
  if (req.method === "POST" && url === "/ask") {
11365
11418
  let body;
11366
11419
  try {
11367
- const parsed = JSON.parse(await readBody(req));
11420
+ const parsed = JSON.parse(await readBody$1(req));
11368
11421
  if (parsed === null || typeof parsed !== "object") throw new Error("not an object");
11369
11422
  body = parsed;
11370
11423
  } catch {
11371
- sendJson(res, 400, { error: "invalid JSON body" });
11424
+ sendJson$1(res, 400, { error: "invalid JSON body" });
11372
11425
  return;
11373
11426
  }
11374
11427
  const ccc = typeof body.ccc === "string" && body.ccc !== "" ? body.ccc : defaultRoot;
@@ -11377,11 +11430,11 @@ async function handle(ctx, defaultRoot, webPort, req, res) {
11377
11430
  const sessionId = typeof body.sessionId === "string" && body.sessionId !== "" ? body.sessionId : void 0;
11378
11431
  const role = readSkiffRoles(ccc).get(roleName);
11379
11432
  if (!roleName || !role) {
11380
- sendJson(res, 400, { error: `unknown role: ${roleName} (ccc: ${ccc})` });
11433
+ sendJson$1(res, 400, { error: `unknown role: ${roleName} (ccc: ${ccc})` });
11381
11434
  return;
11382
11435
  }
11383
11436
  if (!question.trim()) {
11384
- sendJson(res, 400, { error: "empty question" });
11437
+ sendJson$1(res, 400, { error: "empty question" });
11385
11438
  return;
11386
11439
  }
11387
11440
  let agent;
@@ -11390,11 +11443,11 @@ async function handle(ctx, defaultRoot, webPort, req, res) {
11390
11443
  const info = skiffSessionInfo(sessionId);
11391
11444
  const live = getSkiffAgent(sessionId);
11392
11445
  if (!info || !live) {
11393
- sendJson(res, 400, { error: "session is not recoverable (process restarted or session unknown) — start a new conversation" });
11446
+ sendJson$1(res, 400, { error: "session is not recoverable (process restarted or session unknown) — start a new conversation" });
11394
11447
  return;
11395
11448
  }
11396
11449
  if (info.role !== roleName || info.ccc !== ccc) {
11397
- sendJson(res, 400, { error: `session "${sessionId}" belongs to role "${info.role}" in another CCC — start a new conversation` });
11450
+ sendJson$1(res, 400, { error: `session "${sessionId}" belongs to role "${info.role}" in another CCC — start a new conversation` });
11398
11451
  return;
11399
11452
  }
11400
11453
  agent = live;
@@ -11403,7 +11456,7 @@ async function handle(ctx, defaultRoot, webPort, req, res) {
11403
11456
  const hc = readHandymanConfig(ccc);
11404
11457
  if (!agent) agent = (await createSkiffAgent(ctx, ccc, roleName, role, hc?.defaultModel)).agent;
11405
11458
  const result = await askSkiff(ctx, agent, question, 0);
11406
- sendJson(res, 200, {
11459
+ sendJson$1(res, 200, {
11407
11460
  answer: result.answer,
11408
11461
  answer_html: renderSkiffMarkdown(result.answer),
11409
11462
  sessionId: result.sessionId,
@@ -11412,6 +11465,273 @@ async function handle(ctx, defaultRoot, webPort, req, res) {
11412
11465
  });
11413
11466
  return;
11414
11467
  }
11468
+ sendJson$1(res, 404, { error: "not found" });
11469
+ } catch (err) {
11470
+ sendJson$1(res, 500, { error: err?.message ?? String(err) });
11471
+ }
11472
+ }
11473
+ //#endregion
11474
+ //#region src/acp-core.ts
11475
+ /** JSON-RPC 错误码(对齐标准) */
11476
+ const RPC_ERROR = {
11477
+ PARSE: -32700,
11478
+ INVALID_REQUEST: -32600,
11479
+ METHOD_NOT_FOUND: -32601,
11480
+ INVALID_PARAMS: -32602,
11481
+ INTERNAL: -32603
11482
+ };
11483
+ /**
11484
+ * ACP 会话管理器:持有 skiff agent 会话(与 skiff-core 注册表同源——
11485
+ * 创建走 createSkiffAgent(自动注册),关闭走 unregisterSkiffSession)。
11486
+ * 传输无关:HTTP 端点/企业微信桥/未来 stdio 都调 handle(method, params)。
11487
+ */
11488
+ var AcpServer = class {
11489
+ ctx;
11490
+ constructor(ctx) {
11491
+ this.ctx = ctx;
11492
+ }
11493
+ /** 实现级方法调度(返回 result 或抛 JSON-RPC 语义错误) */
11494
+ async handle(method, params) {
11495
+ switch (method) {
11496
+ case "initialize": return this.initialize();
11497
+ case "authenticate": return {};
11498
+ case "session/new": return this.newSession(params);
11499
+ case "session/list": return this.listSessions();
11500
+ case "session/prompt": return this.prompt(params);
11501
+ case "session/cancel": return this.cancel(params);
11502
+ case "session/close": return this.closeSession(params);
11503
+ case "session/request_permission": return { allow: true };
11504
+ default: throw new RpcMethodError(`method not found: ${method}`);
11505
+ }
11506
+ }
11507
+ initialize() {
11508
+ return {
11509
+ protocolVersion: 1,
11510
+ capabilities: {
11511
+ sessionId: true,
11512
+ ccc: true,
11513
+ role: true
11514
+ },
11515
+ serverInfo: {
11516
+ name: "dsh-serenity-hooks-acp",
11517
+ version: "1.26.0"
11518
+ }
11519
+ };
11520
+ }
11521
+ /**
11522
+ * session/new:{ ccc, role, sessionId? } → 创建或延续 skiff 会话。
11523
+ * 无 sessionId → createSkiffAgent(新会话,continued:false);
11524
+ * 有 → 进程内延续(getSkiffAgent 命中 + role/ccc 绑定校验;未命中 → 错误)。
11525
+ */
11526
+ async newSession(params) {
11527
+ const ccc = typeof params?.ccc === "string" && params.ccc !== "" ? params.ccc : void 0;
11528
+ const roleName = typeof params?.role === "string" ? params.role : void 0;
11529
+ const sessionId = typeof params?.sessionId === "string" && params.sessionId !== "" ? params.sessionId : void 0;
11530
+ if (!ccc) throw new RpcInvalidParams("session/new requires ccc (CCC root path)");
11531
+ if (!roleName) throw new RpcInvalidParams("session/new requires role");
11532
+ const role = readSkiffRoles(ccc).get(roleName);
11533
+ if (!role) throw new RpcInvalidParams(`unknown role: ${roleName} (ccc: ${ccc})`);
11534
+ if (sessionId) {
11535
+ const info = skiffSessionInfo(sessionId);
11536
+ const live = getSkiffAgent(sessionId);
11537
+ if (!info || !live) throw new RpcInvalidParams("session is not recoverable (process restarted or session unknown) — start a new conversation");
11538
+ if (info.role !== roleName || info.ccc !== ccc) throw new RpcInvalidParams(`session "${sessionId}" belongs to role "${info.role}" in another CCC`);
11539
+ return {
11540
+ sessionId,
11541
+ role: roleName,
11542
+ ccc,
11543
+ continued: true
11544
+ };
11545
+ }
11546
+ const hc = readHandymanConfig(ccc);
11547
+ return {
11548
+ sessionId: (await createSkiffAgent(this.ctx, ccc, roleName, role, hc?.defaultModel)).sessionId,
11549
+ role: roleName,
11550
+ ccc,
11551
+ continued: false
11552
+ };
11553
+ }
11554
+ listSessions() {
11555
+ const sessions = [];
11556
+ for (const [id, b] of skiffSessionSnapshot()) sessions.push({
11557
+ sessionId: id,
11558
+ role: b.role,
11559
+ ccc: b.ccc
11560
+ });
11561
+ return { sessions };
11562
+ }
11563
+ /** session/prompt:{ sessionId, question } → 答案 + 全量轨迹 */
11564
+ async prompt(params) {
11565
+ const sessionId = typeof params?.sessionId === "string" ? params.sessionId : void 0;
11566
+ const question = typeof params?.question === "string" ? params.question : void 0;
11567
+ if (!sessionId) throw new RpcInvalidParams("session/prompt requires sessionId");
11568
+ if (!question?.trim()) throw new RpcInvalidParams("session/prompt requires question");
11569
+ const agent = getSkiffAgent(sessionId);
11570
+ if (!agent) throw new RpcInvalidParams(`unknown session: ${sessionId} (not recoverable — process restarted?)`);
11571
+ const result = await askSkiff(this.ctx, agent, question, 0);
11572
+ return {
11573
+ answer: result.answer,
11574
+ sessionId: result.sessionId,
11575
+ trajectory: result.trajectory
11576
+ };
11577
+ }
11578
+ /** session/cancel:{ sessionId } → 中断当前 prompt(DSH interrupt) */
11579
+ cancel(params) {
11580
+ const sessionId = typeof params?.sessionId === "string" ? params.sessionId : void 0;
11581
+ if (!sessionId) return { cancelled: false };
11582
+ const agent = getSkiffAgent(sessionId);
11583
+ if (!agent) return { cancelled: false };
11584
+ try {
11585
+ agent.interrupt?.();
11586
+ } catch {}
11587
+ return { cancelled: true };
11588
+ }
11589
+ /** session/close:{ sessionId } → 释放 agent + 注册表清理(进程内) */
11590
+ closeSession(params) {
11591
+ const sessionId = typeof params?.sessionId === "string" ? params.sessionId : void 0;
11592
+ if (!sessionId) return { closed: false };
11593
+ if (!getSkiffAgent(sessionId)) return { closed: false };
11594
+ unregisterSkiffSession(sessionId);
11595
+ return { closed: true };
11596
+ }
11597
+ };
11598
+ /** 方法不存在错误(JSON-RPC -32601) */
11599
+ var RpcMethodError = class extends Error {
11600
+ constructor(message) {
11601
+ super(message);
11602
+ this.name = "RpcMethodError";
11603
+ }
11604
+ };
11605
+ /** 参数错误(JSON-RPC -32602) */
11606
+ var RpcInvalidParams = class extends Error {
11607
+ constructor(message) {
11608
+ super(message);
11609
+ this.name = "RpcInvalidParams";
11610
+ }
11611
+ };
11612
+ /**
11613
+ * JSON-RPC 2.0 批处理(单帧):解析请求/通知 → 调 AcpServer.handle → 响应。
11614
+ * 传输无关:acp-http 端点 / 未来 stdio 共用。
11615
+ * @returns 响应帧数组(请求有响应;通知无;解析失败 → 单错误响应)
11616
+ */
11617
+ async function dispatchRpc(server, frame) {
11618
+ if (frame === null || typeof frame !== "object") return [rpcError(null, RPC_ERROR.INVALID_REQUEST, "invalid request")];
11619
+ const req = frame;
11620
+ if (req.jsonrpc !== "2.0" || typeof req.method !== "string") return [rpcError(req.id ?? null, RPC_ERROR.INVALID_REQUEST, "invalid request")];
11621
+ if (req.id === void 0) {
11622
+ try {
11623
+ await server.handle(req.method, req.params);
11624
+ } catch {}
11625
+ return [];
11626
+ }
11627
+ try {
11628
+ const result = await server.handle(req.method, req.params);
11629
+ return [{
11630
+ jsonrpc: "2.0",
11631
+ id: req.id,
11632
+ result
11633
+ }];
11634
+ } catch (error) {
11635
+ return [rpcError(req.id, rpcCodeOf(error), error instanceof Error ? error.message : String(error))];
11636
+ }
11637
+ }
11638
+ function rpcCodeOf(error) {
11639
+ if (error instanceof RpcMethodError) return RPC_ERROR.METHOD_NOT_FOUND;
11640
+ if (error instanceof RpcInvalidParams) return RPC_ERROR.INVALID_PARAMS;
11641
+ return RPC_ERROR.INTERNAL;
11642
+ }
11643
+ function rpcError(id, code, message) {
11644
+ return {
11645
+ jsonrpc: "2.0",
11646
+ id,
11647
+ error: {
11648
+ code,
11649
+ message
11650
+ }
11651
+ };
11652
+ }
11653
+ //#endregion
11654
+ //#region src/acp-http.ts
11655
+ /**
11656
+ * acp-http.ts — ACP HTTP JSON-RPC 端点(F4c,v1.26.0 实验性)
11657
+ *
11658
+ * node:http 服务(默认关,仅监听 127.0.0.1;启停 = 人工——设置面板「Serenity」
11659
+ * 页 ACP 区块开关,不随插件加载自动启动;仿 skiff 调试服务模式)。
11660
+ *
11661
+ * - POST / → JSON-RPC 2.0 单帧/批处理(body 为 JSON)→ JSON-RPC 响应数组
11662
+ *
11663
+ * 与 skiff 调试页(人工测试面)共享同一会话核心(acp-core → skiff-core);
11664
+ * 企业微信桥(F4c-2)同进程直调 acp-core 处理器函数,不经本端点。
11665
+ * stdio 形态(独立进程部署)后续复用同一 acp-core(官方 NDJSON 帧格式)。
11666
+ *
11667
+ * 实验性质:未开启时零资源占用(无监听、无 agent 创建)。
11668
+ */
11669
+ /** 运行中的 ACP HTTP 服务(单实例;进程级) */
11670
+ let active = null;
11671
+ function readBody(req) {
11672
+ return new Promise((resolve, reject) => {
11673
+ const chunks = [];
11674
+ req.on("data", (c) => chunks.push(c));
11675
+ req.on("end", () => resolve(Buffer.concat(chunks).toString("utf-8")));
11676
+ req.on("error", reject);
11677
+ });
11678
+ }
11679
+ function sendJson(res, status, payload) {
11680
+ const body = JSON.stringify(payload);
11681
+ res.writeHead(status, {
11682
+ "Content-Type": "application/json; charset=utf-8",
11683
+ "Cache-Control": "no-store"
11684
+ });
11685
+ res.end(body);
11686
+ }
11687
+ /**
11688
+ * 启动 ACP HTTP JSON-RPC 端点(单实例;重复启动幂等返回既有实例)。
11689
+ * @param ctx 插件上下文(AcpServer 内部经 skiff-core 创建 agent)
11690
+ * @param port 监听端口(仅 127.0.0.1)
11691
+ */
11692
+ async function startAcpHttpServer(ctx, port) {
11693
+ if (active) return;
11694
+ const server = createServer((req, res) => {
11695
+ handle(ctx, req, res);
11696
+ });
11697
+ await new Promise((resolve, reject) => {
11698
+ server.once("error", reject);
11699
+ server.listen(port, "127.0.0.1", () => resolve());
11700
+ });
11701
+ active = {
11702
+ server,
11703
+ port
11704
+ };
11705
+ console.log(`[serenity-hooks] ✓ ACP HTTP JSON-RPC: http://127.0.0.1:${port}(session/new 支持 {ccc, role, sessionId?})`);
11706
+ }
11707
+ function stopAcpHttpServer() {
11708
+ if (!active) return;
11709
+ try {
11710
+ active.server.close();
11711
+ } catch {}
11712
+ active = null;
11713
+ }
11714
+ async function handle(ctx, req, res) {
11715
+ try {
11716
+ const url = (req.url ?? "/").split("?")[0] ?? "/";
11717
+ if (req.method === "POST" && url === "/") {
11718
+ let body;
11719
+ try {
11720
+ body = JSON.parse(await readBody(req));
11721
+ } catch {
11722
+ sendJson(res, 400, [{
11723
+ jsonrpc: "2.0",
11724
+ id: null,
11725
+ error: {
11726
+ code: -32700,
11727
+ message: "parse error"
11728
+ }
11729
+ }]);
11730
+ return;
11731
+ }
11732
+ sendJson(res, 200, await dispatchRpc(new AcpServer(ctx), body));
11733
+ return;
11734
+ }
11415
11735
  sendJson(res, 404, { error: "not found" });
11416
11736
  } catch (err) {
11417
11737
  sendJson(res, 500, { error: err?.message ?? String(err) });
@@ -11453,6 +11773,10 @@ const Config = z.object({
11453
11773
  skiff: z.object({
11454
11774
  enabled: z.boolean().default(false),
11455
11775
  debugPort: z.number().min(1024).max(65535).default(3099)
11776
+ }),
11777
+ acp: z.object({
11778
+ enabled: z.boolean().default(false),
11779
+ httpPort: z.number().min(1024).max(65535).default(3100)
11456
11780
  })
11457
11781
  });
11458
11782
  function apply(ctx, config) {
@@ -11492,6 +11816,7 @@ function apply(ctx, config) {
11492
11816
  if (config.opencodeSkills) registerOpencodeSkills(ctx);
11493
11817
  registerBootstrap(ctx);
11494
11818
  registerSkiff(ctx);
11819
+ registerAcp(ctx);
11495
11820
  }
11496
11821
  /**
11497
11822
  * F4 Skiff 调试服务装配:启停 = 人工(设置面板 Skiff 区块开关,settings 持久化)。
@@ -11556,5 +11881,29 @@ function readWebPort(ctx) {
11556
11881
  return 3080;
11557
11882
  }
11558
11883
  }
11884
+ /**
11885
+ * F4c ACP HTTP JSON-RPC 端点装配(v1.26.0 实验性):启停 = 人工(设置面板
11886
+ * 「Serenity」页 ACP 区块开关,settings 持久化)。会话创建/延续走 acp-core →
11887
+ * skiff-core(与调试页同核心);仅监听 127.0.0.1;未开启零资源占用。
11888
+ */
11889
+ function registerAcp(ctx) {
11890
+ let started = false;
11891
+ const sync = () => {
11892
+ const s = readSimpleSettings();
11893
+ if (s.acpEnabled && !started) startAcpHttpServer(ctx, s.acpHttpPort).then(() => {
11894
+ started = true;
11895
+ }).catch((err) => {
11896
+ console.error(`[serenity-hooks] ✗ ACP HTTP 端点启动失败: ${String(err?.message ?? err)}`);
11897
+ });
11898
+ else if (!s.acpEnabled && started) {
11899
+ stopAcpHttpServer();
11900
+ started = false;
11901
+ }
11902
+ };
11903
+ try {
11904
+ ctx.on("serenity/settings-changed", sync);
11905
+ } catch {}
11906
+ sync();
11907
+ }
11559
11908
  //#endregion
11560
11909
  export { Config, apply, inject, name };
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@shgroup/dsh-serenity-hooks",
3
- "version": "1.25.10",
3
+ "version": "1.26.0",
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": {