@shgroup/dsh-serenity-hooks 1.25.11 → 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.11",
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
  /**
@@ -6898,7 +6924,11 @@ dsp 只提供机制(双白名单强制 + 基础提示词 + 调试问答页)
6898
6924
 
6899
6925
  ── 运行 ──
6900
6926
  调试:设置面板「Serenity」页 Skiff 区块开启 → http://127.0.0.1:<debugPort> 问答页实测
6901
- 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)
6902
6932
  启停 = 人工(设置面板开关,不随插件加载自动启动);未开启零资源占用
6903
6933
  生效机制:角色配置**实时读取**(改 .opencode/serenity.json → 刷新调试页即生效);
6904
6934
  改配置后用 skiff_admin apply 做显式校验 + 应用确认(绑定 CCC + 角色清单)
@@ -8298,7 +8328,7 @@ function saveFileToTmp(root, fileName, data) {
8298
8328
  writeFileSync(join(dir, filename), bytes);
8299
8329
  return `${FILE_UPLOAD_DIR}/${filename}`;
8300
8330
  }
8301
- function readBody$2(req, maxBytes) {
8331
+ function readBody$3(req, maxBytes) {
8302
8332
  return new Promise((resolve, reject) => {
8303
8333
  let data = "";
8304
8334
  req.on("data", (chunk) => {
@@ -8312,7 +8342,7 @@ function readBody$2(req, maxBytes) {
8312
8342
  req.on("error", reject);
8313
8343
  });
8314
8344
  }
8315
- function sendJson$1(res, code, body) {
8345
+ function sendJson$2(res, code, body) {
8316
8346
  const payload = JSON.stringify(body);
8317
8347
  res.writeHead(code, {
8318
8348
  "content-type": "application/json; charset=utf-8",
@@ -8350,7 +8380,7 @@ function registerStatusApi(ctx, opts = {}) {
8350
8380
  handler: async (req, res) => {
8351
8381
  try {
8352
8382
  if (req.method !== "GET") {
8353
- sendJson$1(res, 405, { error: "method not allowed" });
8383
+ sendJson$2(res, 405, { error: "method not allowed" });
8354
8384
  return;
8355
8385
  }
8356
8386
  const url = new URL(req.url ?? "/", "http://127.0.0.1");
@@ -8359,12 +8389,12 @@ function registerStatusApi(ctx, opts = {}) {
8359
8389
  workspace: url.searchParams.get("workspace") ?? void 0
8360
8390
  }));
8361
8391
  if (!root) {
8362
- sendJson$1(res, 200, { handymen: [] });
8392
+ sendJson$2(res, 200, { handymen: [] });
8363
8393
  return;
8364
8394
  }
8365
- sendJson$1(res, 200, { handymen: listActiveHandymen(root) });
8395
+ sendJson$2(res, 200, { handymen: listActiveHandymen(root) });
8366
8396
  } catch (err) {
8367
- sendJson$1(res, 400, { error: err.message ?? String(err) });
8397
+ sendJson$2(res, 400, { error: err.message ?? String(err) });
8368
8398
  }
8369
8399
  }
8370
8400
  });
@@ -8374,14 +8404,14 @@ function registerStatusApi(ctx, opts = {}) {
8374
8404
  handler: async (req, res) => {
8375
8405
  try {
8376
8406
  if (req.method !== "POST") {
8377
- sendJson$1(res, 405, { error: "method not allowed" });
8407
+ sendJson$2(res, 405, { error: "method not allowed" });
8378
8408
  return;
8379
8409
  }
8380
8410
  if (req.headers["x-serenity-ui"] !== "1") {
8381
- sendJson$1(res, 403, { error: "图片落盘仅限 WebUI(client 专用)" });
8411
+ sendJson$2(res, 403, { error: "图片落盘仅限 WebUI(client 专用)" });
8382
8412
  return;
8383
8413
  }
8384
- const raw = await readBody$2(req, 20971520);
8414
+ const raw = await readBody$3(req, 20971520);
8385
8415
  const body = JSON.parse(raw);
8386
8416
  const workspace = resolveWorkspace(ctx, {
8387
8417
  sessionId: body.sessionId,
@@ -8389,12 +8419,12 @@ function registerStatusApi(ctx, opts = {}) {
8389
8419
  });
8390
8420
  const root = findSerenityRoot(workspace);
8391
8421
  if (!root) {
8392
- sendJson$1(res, 404, { error: `no CCC found from workspace: ${workspace}` });
8422
+ sendJson$2(res, 404, { error: `no CCC found from workspace: ${workspace}` });
8393
8423
  return;
8394
8424
  }
8395
- sendJson$1(res, 200, { path: saveImageToTmp(root, body.mediaType ?? "", body.data ?? "") });
8425
+ sendJson$2(res, 200, { path: saveImageToTmp(root, body.mediaType ?? "", body.data ?? "") });
8396
8426
  } catch (err) {
8397
- sendJson$1(res, 400, { error: err.message ?? String(err) });
8427
+ sendJson$2(res, 400, { error: err.message ?? String(err) });
8398
8428
  }
8399
8429
  }
8400
8430
  });
@@ -8404,14 +8434,14 @@ function registerStatusApi(ctx, opts = {}) {
8404
8434
  handler: async (req, res) => {
8405
8435
  try {
8406
8436
  if (req.method !== "POST") {
8407
- sendJson$1(res, 405, { error: "method not allowed" });
8437
+ sendJson$2(res, 405, { error: "method not allowed" });
8408
8438
  return;
8409
8439
  }
8410
8440
  if (req.headers["x-serenity-ui"] !== "1") {
8411
- sendJson$1(res, 403, { error: "文件落盘仅限 WebUI(client 专用)" });
8441
+ sendJson$2(res, 403, { error: "文件落盘仅限 WebUI(client 专用)" });
8412
8442
  return;
8413
8443
  }
8414
- const raw = await readBody$2(req, 20971520);
8444
+ const raw = await readBody$3(req, 20971520);
8415
8445
  const body = JSON.parse(raw);
8416
8446
  const workspace = resolveWorkspace(ctx, {
8417
8447
  sessionId: body.sessionId,
@@ -8419,12 +8449,12 @@ function registerStatusApi(ctx, opts = {}) {
8419
8449
  });
8420
8450
  const root = findSerenityRoot(workspace);
8421
8451
  if (!root) {
8422
- sendJson$1(res, 404, { error: `no CCC found from workspace: ${workspace}` });
8452
+ sendJson$2(res, 404, { error: `no CCC found from workspace: ${workspace}` });
8423
8453
  return;
8424
8454
  }
8425
- sendJson$1(res, 200, { path: saveFileToTmp(root, body.name ?? "", body.data ?? "") });
8455
+ sendJson$2(res, 200, { path: saveFileToTmp(root, body.name ?? "", body.data ?? "") });
8426
8456
  } catch (err) {
8427
- sendJson$1(res, 400, { error: err.message ?? String(err) });
8457
+ sendJson$2(res, 400, { error: err.message ?? String(err) });
8428
8458
  }
8429
8459
  }
8430
8460
  });
@@ -8440,7 +8470,7 @@ function registerStatusApi(ctx, opts = {}) {
8440
8470
  workspace: url.searchParams.get("workspace") ?? void 0
8441
8471
  }), configPaths);
8442
8472
  const runtime = ctx.get("codeRuntime");
8443
- sendJson$1(res, 200, {
8473
+ sendJson$2(res, 200, {
8444
8474
  ...status,
8445
8475
  ...runtime === void 0 ? { codeRuntime: null } : { codeRuntime: {
8446
8476
  language: runtime.language,
@@ -8451,10 +8481,10 @@ function registerStatusApi(ctx, opts = {}) {
8451
8481
  }
8452
8482
  if (req.method === "POST") {
8453
8483
  if (req.headers["x-serenity-ui"] !== "1") {
8454
- sendJson$1(res, 403, { error: "safe-mode 切换仅限 WebUI(agent 不可自行开关)" });
8484
+ sendJson$2(res, 403, { error: "safe-mode 切换仅限 WebUI(agent 不可自行开关)" });
8455
8485
  return;
8456
8486
  }
8457
- const raw = await readBody$2(req, 65536);
8487
+ const raw = await readBody$3(req, 65536);
8458
8488
  const body = JSON.parse(raw);
8459
8489
  const workspace = resolveWorkspace(ctx, {
8460
8490
  sessionId: body.sessionId,
@@ -8462,19 +8492,19 @@ function registerStatusApi(ctx, opts = {}) {
8462
8492
  });
8463
8493
  const root = findSerenityRoot(workspace);
8464
8494
  if (!root) {
8465
- sendJson$1(res, 404, { error: `no CCC found from workspace: ${workspace}` });
8495
+ sendJson$2(res, 404, { error: `no CCC found from workspace: ${workspace}` });
8466
8496
  return;
8467
8497
  }
8468
8498
  const result = setSafeMode(root, body.on === true);
8469
- sendJson$1(res, 200, {
8499
+ sendJson$2(res, 200, {
8470
8500
  ...getStatus(workspace, configPaths),
8471
8501
  ...result
8472
8502
  });
8473
8503
  return;
8474
8504
  }
8475
- sendJson$1(res, 405, { error: "method not allowed" });
8505
+ sendJson$2(res, 405, { error: "method not allowed" });
8476
8506
  } catch (err) {
8477
- sendJson$1(res, 400, { error: err.message ?? String(err) });
8507
+ sendJson$2(res, 400, { error: err.message ?? String(err) });
8478
8508
  }
8479
8509
  }
8480
8510
  });
@@ -8484,25 +8514,25 @@ function registerStatusApi(ctx, opts = {}) {
8484
8514
  handler: async (req, res) => {
8485
8515
  try {
8486
8516
  if (req.headers["x-serenity-ui"] !== "1") {
8487
- sendJson$1(res, 403, { error: "高级设定仅限 WebUI(client 专用)" });
8517
+ sendJson$2(res, 403, { error: "高级设定仅限 WebUI(client 专用)" });
8488
8518
  return;
8489
8519
  }
8490
8520
  if (req.method === "GET") {
8491
- sendJson$1(res, 200, { config: toWire(readAdvancedSettings()) });
8521
+ sendJson$2(res, 200, { config: toWire(readAdvancedSettings()) });
8492
8522
  return;
8493
8523
  }
8494
8524
  if (req.method === "PUT") {
8495
- const raw = await readBody$2(req, 131072);
8525
+ const raw = await readBody$3(req, 131072);
8496
8526
  const saved = applyWirePatch(JSON.parse(raw).config ?? {});
8497
8527
  try {
8498
8528
  ctx.emit?.("serenity/config-updated");
8499
8529
  } catch {}
8500
- sendJson$1(res, 200, { config: toWire(saved) });
8530
+ sendJson$2(res, 200, { config: toWire(saved) });
8501
8531
  return;
8502
8532
  }
8503
- sendJson$1(res, 405, { error: "method not allowed" });
8533
+ sendJson$2(res, 405, { error: "method not allowed" });
8504
8534
  } catch (err) {
8505
- sendJson$1(res, 400, { error: err.message ?? String(err) });
8535
+ sendJson$2(res, 400, { error: err.message ?? String(err) });
8506
8536
  }
8507
8537
  }
8508
8538
  });
@@ -8958,7 +8988,7 @@ function buildProxyHeaders(reqHeaders, mainPort, bodyOverride) {
8958
8988
  //#endregion
8959
8989
  //#region src/gateway.ts
8960
8990
  /** 读取请求体(≤ maxBytes;超限 reject) */
8961
- function readBody$1(req, maxBytes) {
8991
+ function readBody$2(req, maxBytes) {
8962
8992
  return new Promise((resolve, reject) => {
8963
8993
  let data = "";
8964
8994
  req.on("data", (chunk) => {
@@ -9091,7 +9121,7 @@ function startGateway(config, getAccounts) {
9091
9121
  if (session !== void 0) {
9092
9122
  if (req.method === "POST" && url.pathname === "/api/workspace.create") {
9093
9123
  if (!allowWorkspaceCreate) {
9094
- readBody$1(req, 131072).then((body) => {
9124
+ readBody$2(req, 131072).then((body) => {
9095
9125
  let rpcId = "unknown";
9096
9126
  try {
9097
9127
  const parsed = JSON.parse(body);
@@ -9127,7 +9157,7 @@ function startGateway(config, getAccounts) {
9127
9157
  });
9128
9158
  return;
9129
9159
  }
9130
- readBody$1(req, 131072).then((body) => {
9160
+ readBody$2(req, 131072).then((body) => {
9131
9161
  let rpcId = "unknown";
9132
9162
  let path;
9133
9163
  try {
@@ -11079,8 +11109,8 @@ x.lex;
11079
11109
  * 实验性质:未开启时零资源占用(无监听、无 agent 创建)。
11080
11110
  */
11081
11111
  /** 运行中的调试服务(单实例;进程级) */
11082
- let active = null;
11083
- function readBody(req) {
11112
+ let active$1 = null;
11113
+ function readBody$1(req) {
11084
11114
  return new Promise((resolve, reject) => {
11085
11115
  const chunks = [];
11086
11116
  req.on("data", (c) => chunks.push(c));
@@ -11088,7 +11118,7 @@ function readBody(req) {
11088
11118
  req.on("error", reject);
11089
11119
  });
11090
11120
  }
11091
- function sendJson(res, status, payload) {
11121
+ function sendJson$1(res, status, payload) {
11092
11122
  const body = JSON.stringify(payload);
11093
11123
  res.writeHead(status, {
11094
11124
  "Content-Type": "application/json; charset=utf-8",
@@ -11356,28 +11386,28 @@ function renderSkiffMarkdown(raw) {
11356
11386
  * @param webPort 主 WebUI 端口(WebUI 链接)
11357
11387
  */
11358
11388
  async function startSkiffDebugServer(ctx, root, port, webPort) {
11359
- if (active) return;
11389
+ if (active$1) return;
11360
11390
  const server = createServer((req, res) => {
11361
- handle(ctx, root, webPort, req, res);
11391
+ handle$1(ctx, root, webPort, req, res);
11362
11392
  });
11363
11393
  await new Promise((resolve, reject) => {
11364
11394
  server.once("error", reject);
11365
11395
  server.listen(port, "127.0.0.1", () => resolve());
11366
11396
  });
11367
- active = {
11397
+ active$1 = {
11368
11398
  server,
11369
11399
  port
11370
11400
  };
11371
11401
  console.log(`[serenity-hooks] ✓ Skiff 调试问答页: http://127.0.0.1:${port}(默认 CCC: ${root},WebUI: ${webPort})`);
11372
11402
  }
11373
11403
  function stopSkiffDebugServer() {
11374
- if (!active) return;
11404
+ if (!active$1) return;
11375
11405
  try {
11376
- active.server.close();
11406
+ active$1.server.close();
11377
11407
  } catch {}
11378
- active = null;
11408
+ active$1 = null;
11379
11409
  }
11380
- async function handle(ctx, defaultRoot, webPort, req, res) {
11410
+ async function handle$1(ctx, defaultRoot, webPort, req, res) {
11381
11411
  try {
11382
11412
  const url = (req.url ?? "/").split("?")[0] ?? "/";
11383
11413
  if (req.method === "GET" && url === "/") {
@@ -11387,11 +11417,11 @@ async function handle(ctx, defaultRoot, webPort, req, res) {
11387
11417
  if (req.method === "POST" && url === "/ask") {
11388
11418
  let body;
11389
11419
  try {
11390
- const parsed = JSON.parse(await readBody(req));
11420
+ const parsed = JSON.parse(await readBody$1(req));
11391
11421
  if (parsed === null || typeof parsed !== "object") throw new Error("not an object");
11392
11422
  body = parsed;
11393
11423
  } catch {
11394
- sendJson(res, 400, { error: "invalid JSON body" });
11424
+ sendJson$1(res, 400, { error: "invalid JSON body" });
11395
11425
  return;
11396
11426
  }
11397
11427
  const ccc = typeof body.ccc === "string" && body.ccc !== "" ? body.ccc : defaultRoot;
@@ -11400,11 +11430,11 @@ async function handle(ctx, defaultRoot, webPort, req, res) {
11400
11430
  const sessionId = typeof body.sessionId === "string" && body.sessionId !== "" ? body.sessionId : void 0;
11401
11431
  const role = readSkiffRoles(ccc).get(roleName);
11402
11432
  if (!roleName || !role) {
11403
- sendJson(res, 400, { error: `unknown role: ${roleName} (ccc: ${ccc})` });
11433
+ sendJson$1(res, 400, { error: `unknown role: ${roleName} (ccc: ${ccc})` });
11404
11434
  return;
11405
11435
  }
11406
11436
  if (!question.trim()) {
11407
- sendJson(res, 400, { error: "empty question" });
11437
+ sendJson$1(res, 400, { error: "empty question" });
11408
11438
  return;
11409
11439
  }
11410
11440
  let agent;
@@ -11413,11 +11443,11 @@ async function handle(ctx, defaultRoot, webPort, req, res) {
11413
11443
  const info = skiffSessionInfo(sessionId);
11414
11444
  const live = getSkiffAgent(sessionId);
11415
11445
  if (!info || !live) {
11416
- 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" });
11417
11447
  return;
11418
11448
  }
11419
11449
  if (info.role !== roleName || info.ccc !== ccc) {
11420
- 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` });
11421
11451
  return;
11422
11452
  }
11423
11453
  agent = live;
@@ -11426,7 +11456,7 @@ async function handle(ctx, defaultRoot, webPort, req, res) {
11426
11456
  const hc = readHandymanConfig(ccc);
11427
11457
  if (!agent) agent = (await createSkiffAgent(ctx, ccc, roleName, role, hc?.defaultModel)).agent;
11428
11458
  const result = await askSkiff(ctx, agent, question, 0);
11429
- sendJson(res, 200, {
11459
+ sendJson$1(res, 200, {
11430
11460
  answer: result.answer,
11431
11461
  answer_html: renderSkiffMarkdown(result.answer),
11432
11462
  sessionId: result.sessionId,
@@ -11435,6 +11465,273 @@ async function handle(ctx, defaultRoot, webPort, req, res) {
11435
11465
  });
11436
11466
  return;
11437
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
+ }
11438
11735
  sendJson(res, 404, { error: "not found" });
11439
11736
  } catch (err) {
11440
11737
  sendJson(res, 500, { error: err?.message ?? String(err) });
@@ -11476,6 +11773,10 @@ const Config = z.object({
11476
11773
  skiff: z.object({
11477
11774
  enabled: z.boolean().default(false),
11478
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)
11479
11780
  })
11480
11781
  });
11481
11782
  function apply(ctx, config) {
@@ -11515,6 +11816,7 @@ function apply(ctx, config) {
11515
11816
  if (config.opencodeSkills) registerOpencodeSkills(ctx);
11516
11817
  registerBootstrap(ctx);
11517
11818
  registerSkiff(ctx);
11819
+ registerAcp(ctx);
11518
11820
  }
11519
11821
  /**
11520
11822
  * F4 Skiff 调试服务装配:启停 = 人工(设置面板 Skiff 区块开关,settings 持久化)。
@@ -11579,5 +11881,29 @@ function readWebPort(ctx) {
11579
11881
  return 3080;
11580
11882
  }
11581
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
+ }
11582
11908
  //#endregion
11583
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.11",
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": {