@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 +1 -1
- package/lib/client.js +31 -0
- package/lib/index.js +421 -72
- 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.
|
|
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
|
-
|
|
6163
|
-
|
|
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
|
-
|
|
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$
|
|
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$
|
|
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$
|
|
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$
|
|
8392
|
+
sendJson$2(res, 200, { handymen: [] });
|
|
8340
8393
|
return;
|
|
8341
8394
|
}
|
|
8342
|
-
sendJson$
|
|
8395
|
+
sendJson$2(res, 200, { handymen: listActiveHandymen(root) });
|
|
8343
8396
|
} catch (err) {
|
|
8344
|
-
sendJson$
|
|
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$
|
|
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$
|
|
8411
|
+
sendJson$2(res, 403, { error: "图片落盘仅限 WebUI(client 专用)" });
|
|
8359
8412
|
return;
|
|
8360
8413
|
}
|
|
8361
|
-
const raw = await readBody$
|
|
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$
|
|
8422
|
+
sendJson$2(res, 404, { error: `no CCC found from workspace: ${workspace}` });
|
|
8370
8423
|
return;
|
|
8371
8424
|
}
|
|
8372
|
-
sendJson$
|
|
8425
|
+
sendJson$2(res, 200, { path: saveImageToTmp(root, body.mediaType ?? "", body.data ?? "") });
|
|
8373
8426
|
} catch (err) {
|
|
8374
|
-
sendJson$
|
|
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$
|
|
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$
|
|
8441
|
+
sendJson$2(res, 403, { error: "文件落盘仅限 WebUI(client 专用)" });
|
|
8389
8442
|
return;
|
|
8390
8443
|
}
|
|
8391
|
-
const raw = await readBody$
|
|
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$
|
|
8452
|
+
sendJson$2(res, 404, { error: `no CCC found from workspace: ${workspace}` });
|
|
8400
8453
|
return;
|
|
8401
8454
|
}
|
|
8402
|
-
sendJson$
|
|
8455
|
+
sendJson$2(res, 200, { path: saveFileToTmp(root, body.name ?? "", body.data ?? "") });
|
|
8403
8456
|
} catch (err) {
|
|
8404
|
-
sendJson$
|
|
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$
|
|
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$
|
|
8484
|
+
sendJson$2(res, 403, { error: "safe-mode 切换仅限 WebUI(agent 不可自行开关)" });
|
|
8432
8485
|
return;
|
|
8433
8486
|
}
|
|
8434
|
-
const raw = await readBody$
|
|
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$
|
|
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$
|
|
8499
|
+
sendJson$2(res, 200, {
|
|
8447
8500
|
...getStatus(workspace, configPaths),
|
|
8448
8501
|
...result
|
|
8449
8502
|
});
|
|
8450
8503
|
return;
|
|
8451
8504
|
}
|
|
8452
|
-
sendJson$
|
|
8505
|
+
sendJson$2(res, 405, { error: "method not allowed" });
|
|
8453
8506
|
} catch (err) {
|
|
8454
|
-
sendJson$
|
|
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$
|
|
8517
|
+
sendJson$2(res, 403, { error: "高级设定仅限 WebUI(client 专用)" });
|
|
8465
8518
|
return;
|
|
8466
8519
|
}
|
|
8467
8520
|
if (req.method === "GET") {
|
|
8468
|
-
sendJson$
|
|
8521
|
+
sendJson$2(res, 200, { config: toWire(readAdvancedSettings()) });
|
|
8469
8522
|
return;
|
|
8470
8523
|
}
|
|
8471
8524
|
if (req.method === "PUT") {
|
|
8472
|
-
const raw = await readBody$
|
|
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$
|
|
8530
|
+
sendJson$2(res, 200, { config: toWire(saved) });
|
|
8478
8531
|
return;
|
|
8479
8532
|
}
|
|
8480
|
-
sendJson$
|
|
8533
|
+
sendJson$2(res, 405, { error: "method not allowed" });
|
|
8481
8534
|
} catch (err) {
|
|
8482
|
-
sendJson$
|
|
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$
|
|
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$
|
|
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$
|
|
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.
|
|
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": {
|