@shgroup/dsh-serenity-hooks 1.56.10 → 1.57.1
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/README.md +25 -11
- package/dsh.plugin.json +3 -3
- package/lib/agent-idle.d.ts +2 -2
- package/lib/api.d.ts +0 -1
- package/lib/{ccc-DXky9UZu.js → ccc-B0ZGL9lx.js} +30 -13
- package/lib/{ccc-roots-CDW2dO0n.js → ccc-roots-GIJ3A_tw.js} +82 -5
- package/lib/ccc-roots.d.ts +3 -2
- package/lib/ccc.d.ts +23 -10
- package/lib/cheap-worker-ops.d.ts +49 -0
- package/lib/cheap-worker-preset-inherit.d.ts +35 -0
- package/lib/client.js +47 -471
- package/lib/config-ops.d.ts +3 -34
- package/lib/cro-guide.d.ts +1 -1
- package/lib/face-host.d.ts +7 -5
- package/lib/host/access.d.ts +2 -2
- package/lib/index.d.ts +0 -31
- package/lib/index.js +280 -1599
- package/lib/invariant.d.ts +1 -1
- package/lib/invariant.js +1 -1
- package/lib/localstore-ops.d.ts +1 -1
- package/lib/{message-source-CQNfQaTx.js → message-source-B4P3Uw6D.js} +1 -1
- package/lib/output-guard-seam.d.ts +9 -2
- package/lib/ports.d.ts +6 -6
- package/lib/seams/context.d.ts +2 -2
- package/lib/seams/lifecycle.d.ts +3 -3
- package/lib/{settings-section-xHABl8nk.js → settings-section-DQIVsTqE.js} +45 -30
- package/lib/settings-section.d.ts +3 -16
- package/lib/skiff-core.d.ts +6 -5
- package/lib/skiff-debug.d.ts +5 -4
- package/lib/{skiff-role-Det66X16.js → skiff-role-B615L9MV.js} +2 -2
- package/lib/skiff-role.d.ts +2 -2
- package/lib/status.d.ts +2 -2
- package/lib/tools/cheap-worker.d.ts +21 -0
- package/lib/tools/skiff-admin.d.ts +2 -2
- package/lib/unattended-ops.d.ts +6 -4
- package/lib/weixin-bridge.d.ts +9 -2
- package/lib/weixin-output-guard.d.ts +1 -1
- package/lib/{weixin-route-CxCciqQl.js → weixin-route-B9gU1YXz.js} +5 -5
- package/package.json +2 -2
- package/lib/access-CNccNtgf.js +0 -79
- package/lib/acp-core.d.ts +0 -80
- package/lib/acp-http.d.ts +0 -37
- package/lib/handyman-ops.d.ts +0 -97
- package/lib/handyman-preset-inherit.d.ts +0 -35
- package/lib/skiff-registry-BjIX_KD1.js +0 -43
- package/lib/tools/handyman.d.ts +0 -24
package/lib/access-CNccNtgf.js
DELETED
|
@@ -1,79 +0,0 @@
|
|
|
1
|
-
//#region src/host/access.ts
|
|
2
|
-
/**
|
|
3
|
-
* host/access.ts — 宿主访问收口(S142 review F-06)
|
|
4
|
-
*
|
|
5
|
-
* 为什么存在:dsp 此前在 30+ 个文件里各自用 `(ctx as unknown as {…})` 断言读取宿主
|
|
6
|
-
* 服务——契约表达分散、失败模式各异(多数静默 undefined)。本模块是**唯一**读取宿主
|
|
7
|
-
* 的入口:所有 `ctx.get` / `ctx.<service>` 的形状断言集中在此,调用点只拿类型化结果。
|
|
8
|
-
*
|
|
9
|
-
* 约定:
|
|
10
|
-
* - 只读、无副作用;服务缺失一律返回 `undefined`(调用方决定降级策略),**不抛错**
|
|
11
|
-
* (宿主对插件 apply 抛错 = 整个 dsh 启动失败,访问层不能成为单点)。
|
|
12
|
-
* - 形状以 `AI_LAB/dsh-harness-public` @ 0.1.2-rc.1 为准;成员缺失由
|
|
13
|
-
* `host/contract.ts` 的探针在装载时与 `dashboard health` 中报告。
|
|
14
|
-
* - 迁移是渐进的:新代码必须走本模块;旧调用点逐轮收敛(F-06 轮次记录见 CHANGELOG)。
|
|
15
|
-
*/
|
|
16
|
-
/** 通用读取:`ctx.get(name)`(含异常吞掉——服务 getter 抛错视为不可用) */
|
|
17
|
-
function hostService(ctx, name) {
|
|
18
|
-
const c = ctx;
|
|
19
|
-
if (typeof c?.get !== "function") return void 0;
|
|
20
|
-
try {
|
|
21
|
-
return c.get(name);
|
|
22
|
-
} catch {
|
|
23
|
-
return;
|
|
24
|
-
}
|
|
25
|
-
}
|
|
26
|
-
/**
|
|
27
|
-
* 直接属性读取(injected 服务:`ctx.<name>`),属性缺失**或读取抛错**时回落 `ctx.get`。
|
|
28
|
-
*
|
|
29
|
-
* v1.31.4 修复(R↓,实证 v1.31.3 真机首测):cordis 的 Context 是 Proxy,
|
|
30
|
-
* `ReflectService.handler.get`(`vendor/cordis/src/reflect.ts`)对**未经 `inject` 声明**
|
|
31
|
-
* 的服务名会沿 fiber 链查找,找不到时**抛错**而非返回 undefined:
|
|
32
|
-
*
|
|
33
|
-
* cannot get property "<name>" without inject
|
|
34
|
-
*
|
|
35
|
-
* 于是"先直接属性读、失败回落 ctx.get"的写法在**真实 cordis** 下永远走不到回落分支——
|
|
36
|
-
* 异常直接逃逸(`hostService` 的 try/catch 被绕过)。v1.31.3 的 `hostSubagents`
|
|
37
|
-
* 正是这样炸的:`subagents` 是 lazy 服务(不在插件 `inject` 列表内)。
|
|
38
|
-
* 现有 fake ctx 单测(普通对象无 getter)复现不了 → 真 cordis 用例见
|
|
39
|
-
* `tests/host/cordis-access.test.ts`。
|
|
40
|
-
*
|
|
41
|
-
* 为什么吞掉异常而不是让它冒泡:本模块的契约是"服务缺失一律返回 undefined,**不抛错**"
|
|
42
|
-
* (宿主对插件 apply 抛错 = 整个 dsh 启动失败)。读取失败 = 服务不可用,等价于缺失。
|
|
43
|
-
*/
|
|
44
|
-
function hostInjected(ctx, name) {
|
|
45
|
-
const c = ctx;
|
|
46
|
-
try {
|
|
47
|
-
const direct = c?.[name];
|
|
48
|
-
if (direct !== void 0) return direct;
|
|
49
|
-
} catch {}
|
|
50
|
-
return hostService(ctx, name);
|
|
51
|
-
}
|
|
52
|
-
/** `ctx.sessions`(injected) */
|
|
53
|
-
function hostSessions(ctx) {
|
|
54
|
-
return hostInjected(ctx, "sessions");
|
|
55
|
-
}
|
|
56
|
-
/** `ctx.agents`(injected) */
|
|
57
|
-
function hostAgents(ctx) {
|
|
58
|
-
return hostInjected(ctx, "agents");
|
|
59
|
-
}
|
|
60
|
-
/** `ctx.webServer`(injected) */
|
|
61
|
-
function hostWebServer(ctx) {
|
|
62
|
-
return hostInjected(ctx, "webServer");
|
|
63
|
-
}
|
|
64
|
-
/** `ctx.settings`(injected;提供 settings 面板装配通道) */
|
|
65
|
-
function hostSettings(ctx) {
|
|
66
|
-
return hostInjected(ctx, "settings");
|
|
67
|
-
}
|
|
68
|
-
/** `ctx.web`(injected;v1.30.12 web_fetch provider 注册通道) */
|
|
69
|
-
function hostWeb(ctx) {
|
|
70
|
-
return hostInjected(ctx, "web");
|
|
71
|
-
}
|
|
72
|
-
function hostSubagents(ctx) {
|
|
73
|
-
return hostInjected(ctx, "subagents");
|
|
74
|
-
}
|
|
75
|
-
function hostLlm(ctx) {
|
|
76
|
-
return hostInjected(ctx, "llm");
|
|
77
|
-
}
|
|
78
|
-
//#endregion
|
|
79
|
-
export { hostSessions as a, hostWeb as c, hostService as i, hostWebServer as l, hostInjected as n, hostSettings as o, hostLlm as r, hostSubagents as s, hostAgents as t };
|
package/lib/acp-core.d.ts
DELETED
|
@@ -1,80 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* acp-core.ts — ACP 会话管理层(F4c,v1.26.0 实验性)
|
|
3
|
-
*
|
|
4
|
-
* **传输无关**:方法处理器(initialize/session/new/prompt/cancel/list/close/request_permission)
|
|
5
|
-
* 不依赖任何传输(stdio/HTTP/直调)——企业微信桥同进程直调处理器函数,
|
|
6
|
-
* acp-http 端点(外部程序化面)与未来 stdio 独立进程复用同一处理器。
|
|
7
|
-
*
|
|
8
|
-
* Skiff 映射(S142 用户拍板:指定 认知容器+角色+会话(可选)进行对话):
|
|
9
|
-
* - session/new { ccc, role, sessionId? } → createSkiffAgent / getSkiffAgent 复用
|
|
10
|
-
* (无 sessionId → 新建;有 → 进程内延续——与 v1.25.10 调试页同语义)
|
|
11
|
-
* - session/prompt → askSkiff(agent, question, 0)(全量轨迹)
|
|
12
|
-
* - session/cancel → agent.interrupt(DSH 能力)
|
|
13
|
-
* - session/close → unregisterSkiffSession(释放 agent + 注册表清理)
|
|
14
|
-
* - session/request_permission → 恒 allow(G9:白名单即授权)
|
|
15
|
-
*
|
|
16
|
-
* 实验性质:仅当 ACP 服务装配(settings acpEnabled)且 CCC 配置了角色时才创建 agent;
|
|
17
|
-
* 未启用时本模块零副作用(无监听、无 agent)。
|
|
18
|
-
*/
|
|
19
|
-
import type { Context } from 'cordis';
|
|
20
|
-
interface JsonRpcResponse {
|
|
21
|
-
jsonrpc: '2.0';
|
|
22
|
-
id: string | number | null;
|
|
23
|
-
result?: unknown;
|
|
24
|
-
error?: {
|
|
25
|
-
code: number;
|
|
26
|
-
message: string;
|
|
27
|
-
data?: unknown;
|
|
28
|
-
};
|
|
29
|
-
}
|
|
30
|
-
/** JSON-RPC 错误码(对齐标准) */
|
|
31
|
-
export declare const RPC_ERROR: {
|
|
32
|
-
readonly PARSE: -32700;
|
|
33
|
-
readonly INVALID_REQUEST: -32600;
|
|
34
|
-
readonly METHOD_NOT_FOUND: -32601;
|
|
35
|
-
readonly INVALID_PARAMS: -32602;
|
|
36
|
-
readonly INTERNAL: -32603;
|
|
37
|
-
};
|
|
38
|
-
/**
|
|
39
|
-
* ACP 会话管理器:持有 skiff agent 会话(与 skiff-core 注册表同源——
|
|
40
|
-
* 创建走 createSkiffAgent(自动注册),关闭走 unregisterSkiffSession)。
|
|
41
|
-
* 传输无关:HTTP 端点/企业微信桥/未来 stdio 都调 handle(method, params)。
|
|
42
|
-
*/
|
|
43
|
-
export declare class AcpServer {
|
|
44
|
-
private readonly ctx;
|
|
45
|
-
constructor(ctx: Context);
|
|
46
|
-
/** 实现级方法调度(返回 result 或抛 JSON-RPC 语义错误) */
|
|
47
|
-
handle(method: string, params: Record<string, unknown> | undefined): Promise<unknown>;
|
|
48
|
-
private initialize;
|
|
49
|
-
/**
|
|
50
|
-
* session/new:{ ccc, role, sessionId? } → 创建或延续 skiff 会话。
|
|
51
|
-
* 无 sessionId → createSkiffAgent(新会话,continued:false);
|
|
52
|
-
* 有 → 进程内延续(getSkiffAgent 命中 + role/ccc 绑定校验;未命中 → 错误)。
|
|
53
|
-
*/
|
|
54
|
-
private newSession;
|
|
55
|
-
private listSessions;
|
|
56
|
-
/**
|
|
57
|
-
* session/prompt:{ sessionId, question } → 答案(**v1.26.10:不返回 trajectory**——
|
|
58
|
-
* 3100 对外只提供问答,轨迹含工具结果等内部信息;3099 调试页另走 /ask 保留)
|
|
59
|
-
*/
|
|
60
|
-
private prompt;
|
|
61
|
-
/** session/cancel:{ sessionId } → 取消该会话的当前活动(Agent.cancel,宿主 rc.1 契约) */
|
|
62
|
-
private cancel;
|
|
63
|
-
/** session/close:{ sessionId } → 释放 agent + 注册表清理(进程内) */
|
|
64
|
-
private closeSession;
|
|
65
|
-
}
|
|
66
|
-
/** 方法不存在错误(JSON-RPC -32601) */
|
|
67
|
-
export declare class RpcMethodError extends Error {
|
|
68
|
-
constructor(message: string);
|
|
69
|
-
}
|
|
70
|
-
/** 参数错误(JSON-RPC -32602) */
|
|
71
|
-
export declare class RpcInvalidParams extends Error {
|
|
72
|
-
constructor(message: string);
|
|
73
|
-
}
|
|
74
|
-
/**
|
|
75
|
-
* JSON-RPC 2.0 批处理(单帧):解析请求/通知 → 调 AcpServer.handle → 响应。
|
|
76
|
-
* 传输无关:acp-http 端点 / 未来 stdio 共用。
|
|
77
|
-
* @returns 响应帧数组(请求有响应;通知无;解析失败 → 单错误响应)
|
|
78
|
-
*/
|
|
79
|
-
export declare function dispatchRpc(server: AcpServer, frame: unknown): Promise<JsonRpcResponse[]>;
|
|
80
|
-
export {};
|
package/lib/acp-http.d.ts
DELETED
|
@@ -1,37 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* acp-http.ts — ACP HTTP JSON-RPC + 建议问答页(F4c/F4d,v1.26.x 实验性)
|
|
3
|
-
*
|
|
4
|
-
* node:http 服务(默认关,仅监听 127.0.0.1;启停 = 人工——设置面板「Serenity」
|
|
5
|
-
* 页 ACP 区块开关 / 问答页开关,不随插件加载自动启动;仿 skiff 调试服务模式)。
|
|
6
|
-
*
|
|
7
|
-
* - POST / → JSON-RPC 2.0 单帧/批处理(acpEnabled 门控;ACP 程序化面)
|
|
8
|
-
* - GET / → 容器列表页(publicAskEnabled 门控;v1.26.2:只列已开放容器)
|
|
9
|
-
* - GET /c/<name> → 单容器问答页(v1.26.2:URL 体现容器名,用户只输 key)
|
|
10
|
-
* - POST /c/<name>/ask → 该容器问答(key 校验 + allowed 白名单校验)
|
|
11
|
-
* - POST /ask → 兼容旧形态 { key, ccc, ... }(key 校验 + allowed 校验)
|
|
12
|
-
*
|
|
13
|
-
* 与 skiff 调试页(人工测试面)共享同一会话核心(acp-core → skiff-core);
|
|
14
|
-
* 企业微信桥(F4c-2)同进程直调 acp-core 处理器函数,不经本端点。
|
|
15
|
-
* stdio 形态(独立进程部署)后续复用同一 acp-core(官方 NDJSON 帧格式)。
|
|
16
|
-
*
|
|
17
|
-
* 实验性质:未开启时零资源占用(无监听、无 agent 创建)。
|
|
18
|
-
*/
|
|
19
|
-
import type { Context } from 'cordis';
|
|
20
|
-
import { type FaceSpec } from './face-host.js';
|
|
21
|
-
export declare function acpHttpActive(): boolean;
|
|
22
|
-
export declare function acpHttpPort(): number | null;
|
|
23
|
-
/**
|
|
24
|
-
* 本面的规格(C4 块 A/④:**只提供** name/port/host/enabled/handler)。
|
|
25
|
-
* 两个子面(ACP JSON-RPC / 对外问答页)的路由与鉴权仍全部归 {@link handle}——
|
|
26
|
-
* 面宿主不介入面语义,也**不合并** D/E 两面(两者共用会话核心但各有独立 listener/路由/HTML)。
|
|
27
|
-
*/
|
|
28
|
-
export declare function acpHttpSpec(ctx: Context, port: number, defaultRoot?: string): FaceSpec;
|
|
29
|
-
/**
|
|
30
|
-
* 启动 ACP HTTP JSON-RPC + 建议问答页服务(单实例;重复启动幂等返回既有实例)。
|
|
31
|
-
* C4 块 A:listener 的创建/监听/错误/端口占用/关闭归 `face-host`。
|
|
32
|
-
* @param ctx 插件上下文(AcpServer 内部经 skiff-core 创建 agent)
|
|
33
|
-
* @param port 监听端口(仅 127.0.0.1)
|
|
34
|
-
* @param defaultRoot 默认 CCC 根(问答页候选发现兜底;resolveSkiffRoot 传入)
|
|
35
|
-
*/
|
|
36
|
-
export declare function startAcpHttpServer(ctx: Context, port: number, defaultRoot?: string): Promise<void>;
|
|
37
|
-
export declare function stopAcpHttpServer(): void;
|
package/lib/handyman-ops.d.ts
DELETED
|
@@ -1,97 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* handyman-ops.ts — handyman(杂工)纯逻辑层(零 DSH 依赖,可独立单测)
|
|
3
|
-
*
|
|
4
|
-
* v1.24.0:loop(牛马)→ handyman(杂工)重命名。语义对齐 osp loop:
|
|
5
|
-
* 进度文件(handyman-<label>.md/.json)、续跑、轮次 prompt 结构、stop token。
|
|
6
|
-
* 不兼容旧 loop- 进度文件(用户拍板:仅新 handyman- 前缀)。
|
|
7
|
-
*/
|
|
8
|
-
interface HandymanProgress {
|
|
9
|
-
round: number;
|
|
10
|
-
done: boolean;
|
|
11
|
-
label: string;
|
|
12
|
-
model: string;
|
|
13
|
-
updated: string;
|
|
14
|
-
lastResponse: string;
|
|
15
|
-
/** 对齐 osp writeFailedStatus:failed 时 done=true + status=failed + errorCode */
|
|
16
|
-
status?: 'running' | 'done' | 'failed';
|
|
17
|
-
errorCode?: string;
|
|
18
|
-
errorMessage?: string;
|
|
19
|
-
}
|
|
20
|
-
/** label 脱敏(Windows 审计问题 17):非法字符 → '-',去尾点/空格,限长(按码点截断,修复代理对切散 U+FFFD) */
|
|
21
|
-
export declare function sanitizeLabel(label: string): string;
|
|
22
|
-
export declare function handymanProgressPaths(root: string, label: string): {
|
|
23
|
-
md: string;
|
|
24
|
-
json: string;
|
|
25
|
-
};
|
|
26
|
-
/** 读取进度(续跑);无文件返回 round 0 */
|
|
27
|
-
export declare function readProgress(root: string, label: string): HandymanProgress | null;
|
|
28
|
-
export declare function writeProgress(root: string, label: string, p: HandymanProgress): void;
|
|
29
|
-
/** 失败状态落盘(对齐 osp writeFailedStatus:done=true / status=failed / errorCode) */
|
|
30
|
-
export declare function writeFailedStatus(root: string, label: string, info: {
|
|
31
|
-
errorCode: string;
|
|
32
|
-
errorMessage?: string;
|
|
33
|
-
}): void;
|
|
34
|
-
export declare function newStopToken(): string;
|
|
35
|
-
/** 解析 model 字符串(provider/model)→ {provider, model};无 / 视为 model-only */
|
|
36
|
-
export declare function splitModel(model: string): {
|
|
37
|
-
provider: string | undefined;
|
|
38
|
-
model: string;
|
|
39
|
-
};
|
|
40
|
-
/** 校验模型在白名单内;不在 → 抛错(用户拍板:只能使用 CCC 配置的模型) */
|
|
41
|
-
export declare function requireWhitelistedModel(model: string, models: string[]): void;
|
|
42
|
-
/**
|
|
43
|
-
* providerAvailabilityError — 判定「**本机有没有该 provider 的 adapter**」,并产出**可执行**的错误文本。
|
|
44
|
-
* 返回 `null` = 全部可用(调用方照常继续)。
|
|
45
|
-
*
|
|
46
|
-
* 🔴 为什么需要它(S142 2026-09-25,owner 在 **Mac** 上实测):`handyman` 的模型白名单写在
|
|
47
|
-
* **CCC**(`.opencode/serenity.json`,**随 git 走**),而 provider adapter 由**每台机器的宿主**
|
|
48
|
-
* 注册(profile 配置 / settings 命名空间)⇒ 同一份白名单在一台机器可用、在另一台必失败。
|
|
49
|
-
* 实测症状:宿主抛 `no adapter registered for provider "<p>"`(`dsh-llm`,code `NO_ADAPTER`),
|
|
50
|
-
* 而**前台路径的 `diagnostic` 是空的** ⇒ 调用方(LLM)拿到 `stopReason:"error"` 却**不知道为什么**,
|
|
51
|
-
* 也就无法修正自己的配置 —— 本条把「沉默的失败」变成「可执行的错误」。
|
|
52
|
-
*
|
|
53
|
-
* 数据来源(宿主 `LlmRuntime`):`listProviders()` = **有 adapter 的路由**;
|
|
54
|
-
* `listConfigurableProviders()` = **已声明但未必激活**的路由(含激活用的 settings 命名空间)。
|
|
55
|
-
*
|
|
56
|
-
* 判据边界:`model` 串里**没有 `/`** 时取不到 provider(沿用 agent 默认路由)⇒ **不判定**(跳过)。
|
|
57
|
-
*/
|
|
58
|
-
export declare function providerAvailabilityError(models: readonly string[], facts: {
|
|
59
|
-
registered: readonly {
|
|
60
|
-
id: string;
|
|
61
|
-
name?: string;
|
|
62
|
-
}[];
|
|
63
|
-
declared?: readonly {
|
|
64
|
-
provider: string;
|
|
65
|
-
displayName?: string;
|
|
66
|
-
settingsNs?: string;
|
|
67
|
-
}[];
|
|
68
|
-
configPath?: string;
|
|
69
|
-
}): string | null;
|
|
70
|
-
/** 轮次 prompt(对齐老 loop 结构:回顾进度 → 自由工作 → 汇报;S134 EAP 化:固定详尽) */
|
|
71
|
-
export declare function buildRoundPrompt(opts: {
|
|
72
|
-
root: string;
|
|
73
|
-
session?: string;
|
|
74
|
-
label: string;
|
|
75
|
-
round: number;
|
|
76
|
-
stopToken: string;
|
|
77
|
-
progress?: HandymanProgress | null;
|
|
78
|
-
task?: string;
|
|
79
|
-
}): string;
|
|
80
|
-
/**
|
|
81
|
-
* handyman 规模化使用指引(guide 子命令输出;S134 继承 + v1.24.0 更新):
|
|
82
|
-
* 使用 handyman 前必须先加载 eap 设计方案;并行策略(jobs 编排);提示词规范(详尽固定 EAP);
|
|
83
|
-
* 阅读/文字编写类 handyman 内部也加载 eap。
|
|
84
|
-
*/
|
|
85
|
-
export declare const HANDYMAN_GUIDE = "# handyman \u2014 Scale-Up Usage Guide (guide)\n\n## Two modes (v1.31.3; default = foreground)\n| | foreground (default) | background |\n|---|---|---|\n| What | child agent(s) run the task ONCE and return their final text (jobs=[...] \u21D2 parallel, cap 10) | loop-validated worker: hard while-loop + random completion code |\n| Use when | you need result(s) back now (per-case testers, single conversions, parallel fan-out) | long/unattended work needing anti-early-finish, resumability, or parallel jobs |\n| Loop / validation | none | stop-token is the ONLY completion condition; round cap (default 100); auto-restart (\u2264100) |\n| Progress file | none | AGENT_SESSIONS/handyman-<label>.md/.json (same label resumes) |\n| Parallel jobs | **yes** \u2014 jobs=[{task,label?,model?},...], **cap 10 (fixed)** | yes (jobs=[...], handyman.maxParallel default 10) |\n| Mechanism | host delegation service ctx.subagents.start(\"spawn\") + agentOptions | ctx.agents.create + internal while-loop |\n| Model source | CCC handyman.models whitelist (same for both) | same |\n\n## \u26A0\uFE0F Before using: load eap and design the plan\nBefore calling handyman, load eap (acc-eap skill) and design the \"scale-up handyman plan\" based on the EAP framework.\n\n### 1. Task decomposition (E\u2191 Explicit)\n- Split large tasks into explicit subtasks: each subtask defines goal / input / boundaries (what to do, what not to do) / acceptance criteria\n- Make dependencies explicit: dependent tasks run serially, independent ones can run in parallel (both modes)\n\n### 2. Prompt design (handyman's task parameter)\n- task must be detailed, fixed, and EAP-compliant: clear goal, drawn boundaries, decidable acceptance criteria\n- foreground mode: the child does NOT share your conversation \u2014 restate everything it needs in task\n- Anti-example \"handle this file\" \u2014 ambiguous; good example \"read <path>, extract all rows of the\u300C\u5173\u952E\u51B3\u7B56\u300Dtable,\n output a JSON array (fields id/conclusion/evidence), do not modify the original file\"\n- Reading/curating or text-writing work (extracting from files, summarizing, writing docs, generating text, etc.):\n the handyman-internal agent is also required to load eap and organize output per the EAP standard\n\n### 3. Model whitelist (CCC-configured, mandatory)\n- handyman only uses models listed in .opencode/serenity.json \"handyman.models\" \u2014 never arbitrary models\n- The CCC is expected to point this whitelist at LOW-COST models: handyman is the bulk-execution channel\n- background mode: recursive subagents inside a worker inherit the worker's model automatically (DSH native)\n- foreground mode: the child is started through the host delegation service with the resolved model\n\n### 3b. \uD83D\uDD34 Provider availability (per-machine \u2014 the whitelist is NOT enough)\n- The whitelist lives in the **CCC** (\"serenity.json\", it **travels with the repository**), but provider\n **adapters are registered per machine by the host** (profile config / settings namespace). An entry that\n works on one machine can fail on another with: \"no adapter registered for provider <p>\".\n- Before spawning, handyman verifies the provider of the resolved model against the adapters registered\n **on this machine**; if it is missing it reports the requested model, where it is configured, **the\n providers that ARE available here**, any declared-but-dormant route (with its settings namespace), and the\n exact fix \u2014 foreground puts this in \"diagnostic\", background throws it.\n- \u21D2 If you see \"has no adapter registered on this machine\": edit \".opencode/serenity.json\" \"handyman.models\"\n (and \"handyman.defaultModel\") to a provider listed as available, or activate/register the provider on this\n machine. **Do not retry the same call unchanged** \u2014 it fails identically.\n\n### 4. Parallel strategy (both modes)\n- Independent subtasks can run in parallel:\n \u00B7 **foreground** \u2014 handyman(jobs=[{task,label?,model?},...]) \u21D2 fan-out of custom-model subagents, **cap 10**,\n results come back **in the order you passed them** (label is only a display name here; per-job \"model\" allowed)\n \u00B7 **background** \u2014 handyman(mode=\"background\", jobs=[...]): each job gets its own label + task + stop token + progress file\n- Concurrency safety guaranteed: unique sessionId (handyman-<label>-<uuid>), progress files isolated per label\n (AGENT_SESSIONS/handyman-<label>.json) \u2014 same label resumes, different labels never interfere\n- Parallel cap: **foreground 10 (fixed)** / background handyman.maxParallel (default 10 \u2014 cheap models are cheap)\n- Aggregation: foreground returns all outputs in one result; background: after each parallel job produces progress, the main agent merges (or spawns one aggregation handyman)\n- For programmable pipeline/phase orchestration at scale, use the platform's workflow tool instead\n\n## Completion criteria\n- foreground: the child's run settles; done=true only when stopReason=\"completed\" (otherwise read diagnostic)\n- background (osp loop standard): the only completion condition = the worker echoes this round's random verification\n code (stop token); dialogue round cap (default 100, osp fail-safe, forced stop beyond the cap, resumable);\n automatic restart on abnormal agent stop (\u2264100 restarts, anti-infinite-loop)\n\n## Waiting UI\n- The WebUI session-header Serenity detail card shows running background handymen's progress\n (label / round / last response), one line per parallel job, ~3s refresh (foreground mode writes no progress)\n";
|
|
86
|
-
/** handyman 运行状态(进度文件摘要;WebUI 等待界面数据源) */
|
|
87
|
-
interface HandymanRunInfo {
|
|
88
|
-
label: string;
|
|
89
|
-
round: number;
|
|
90
|
-
done: boolean;
|
|
91
|
-
model: string;
|
|
92
|
-
updated: string;
|
|
93
|
-
lastResponse: string;
|
|
94
|
-
}
|
|
95
|
-
/** 列出 AGENT_SESSIONS/handyman-*.json 的全部进度(按 updated 倒序;坏文件跳过) */
|
|
96
|
-
export declare function listActiveHandymen(root: string): HandymanRunInfo[];
|
|
97
|
-
export {};
|
|
@@ -1,35 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* handyman-preset-inherit.ts — handyman worker 的 preset 继承 + 工具面收窄(纯函数,可单测)
|
|
3
|
-
*
|
|
4
|
-
* v1.24.0:loop → handyman 重命名。对齐 subagent 先例(applyChildComposition →
|
|
5
|
-
* agentPresets.composeFrom):handyman worker 在创建窗口内 join 发起方 agent 的 standing
|
|
6
|
-
* preset 组合,从而获得 preset 层工具(read/write/edit 等)。
|
|
7
|
-
*
|
|
8
|
-
* **工具面收窄(用户拍板)**:worker 内部**不含 handyman 工具本身**——递归编排归主
|
|
9
|
-
* agent,worker 内部只走 subagent(模型继承 worker,同样低能)。实现:setup 钩子
|
|
10
|
-
* composeFrom 后对子 scope `tools.restrict({ deny: ['handyman'] })`(同 scope 注册的
|
|
11
|
-
* 规则,最近 scope 胜出——worker 看不到 handyman,主 agent 不受影响)。
|
|
12
|
-
*
|
|
13
|
-
* agentPresets 是可选服务(ctx.get 读取):
|
|
14
|
-
* - 无该服务 / 父未 join preset → 返回空(worker 落全局工具层 + deny handyman);
|
|
15
|
-
* - 有且父已 join → 返回 preset id(写入 session meta 供重建)+ setup 钩子
|
|
16
|
-
* (在 agent 未发布前执行 composeFrom + restrict,失败由创建事务回滚)。
|
|
17
|
-
*
|
|
18
|
-
* 仅依赖 cordis 的 Context 类型(type-only,运行时擦除)——保持可被 vitest
|
|
19
|
-
* 直接加载(与 ccc.ts / handyman-ops.ts 同级的纯模块约定)。
|
|
20
|
-
*/
|
|
21
|
-
import type { Context } from 'cordis';
|
|
22
|
-
/** 从父 agent ctx 解析 preset 继承的创建期组合。 */
|
|
23
|
-
interface HandymanPresetInheritance {
|
|
24
|
-
/** 父 agent 的 preset id(写入子 session meta.agentPreset,供持久化重建);无则不写。 */
|
|
25
|
-
readonly agentPreset?: string;
|
|
26
|
-
/** 创建 setup 钩子:子 scope join 父的 standing preset + deny handyman 工具;无 preset 继承时仅 deny。 */
|
|
27
|
-
readonly setup?: (childCtx: Context) => void;
|
|
28
|
-
}
|
|
29
|
-
/**
|
|
30
|
-
* 解析 handyman worker 从父 agent 继承的 preset,并组装创建 setup 钩子。
|
|
31
|
-
* @param parentCtx - 发起 handyman 的 agent 的 scope ctx;无(headless/非 agent 上下文)时为 undefined。
|
|
32
|
-
* @returns 继承结果:meta 用的 agentPreset 与创建 setup 钩子。
|
|
33
|
-
*/
|
|
34
|
-
export declare function handymanPresetInheritance(parentCtx: Context | undefined): HandymanPresetInheritance;
|
|
35
|
-
export {};
|
|
@@ -1,43 +0,0 @@
|
|
|
1
|
-
import { dirname, join } from "node:path";
|
|
2
|
-
import { readFileSync } from "node:fs";
|
|
3
|
-
import { fileURLToPath } from "node:url";
|
|
4
|
-
//#region src/constants.ts
|
|
5
|
-
/** 常量(纯模块,零 DSH 依赖) */
|
|
6
|
-
/**
|
|
7
|
-
* ACC 版本:自动从 package.json 读取(单一真相源,消除与 CHANGELOG 的漂移)。
|
|
8
|
-
* 发布时只需改 package.json 的 version。
|
|
9
|
-
*/
|
|
10
|
-
const ACC_VERSION = (() => {
|
|
11
|
-
try {
|
|
12
|
-
const here = dirname(fileURLToPath(import.meta.url));
|
|
13
|
-
return JSON.parse(readFileSync(join(here, "..", "package.json"), "utf-8")).version ?? "0.0.0";
|
|
14
|
-
} catch {
|
|
15
|
-
return "0.0.0";
|
|
16
|
-
}
|
|
17
|
-
})();
|
|
18
|
-
//#endregion
|
|
19
|
-
//#region src/skiff-registry.ts
|
|
20
|
-
const skiffSessions = /* @__PURE__ */ new Map();
|
|
21
|
-
/** 查 sessionId 的 Skiff 角色名(无 → null)——向后兼容(guards/seams 依赖) */
|
|
22
|
-
function skiffRoleFor(sessionId) {
|
|
23
|
-
return skiffSessions.get(sessionId)?.role ?? null;
|
|
24
|
-
}
|
|
25
|
-
/** 查 sessionId 的完整绑定(role + ccc;无 → null)——v1.25.10 会话追问校验 */
|
|
26
|
-
function skiffSessionInfo(sessionId) {
|
|
27
|
-
return skiffSessions.get(sessionId) ?? null;
|
|
28
|
-
}
|
|
29
|
-
function registerSkiffSession(sessionId, role, ccc) {
|
|
30
|
-
skiffSessions.set(sessionId, {
|
|
31
|
-
role,
|
|
32
|
-
ccc
|
|
33
|
-
});
|
|
34
|
-
}
|
|
35
|
-
function unregisterSkiffSession(sessionId) {
|
|
36
|
-
skiffSessions.delete(sessionId);
|
|
37
|
-
}
|
|
38
|
-
/** 测试/调试:注册表快照(sessionId → {role, ccc}) */
|
|
39
|
-
function skiffSessionSnapshot() {
|
|
40
|
-
return new Map(skiffSessions);
|
|
41
|
-
}
|
|
42
|
-
//#endregion
|
|
43
|
-
export { unregisterSkiffSession as a, skiffSessionSnapshot as i, skiffRoleFor as n, ACC_VERSION as o, skiffSessionInfo as r, registerSkiffSession as t };
|
package/lib/tools/handyman.d.ts
DELETED
|
@@ -1,24 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* handyman.ts — handyman(杂工)真实 DSH 工具(v1.24.0:loop 牛马 → handyman 杂工)
|
|
3
|
-
*
|
|
4
|
-
* 用户需求(S142 拍板):
|
|
5
|
-
* - **同步**(用户明确:这个事一般不会异步——工具阻塞到完成)
|
|
6
|
-
* - **指定模型**(CCC 白名单内——handyman.models,未配置报错)
|
|
7
|
-
* - **自主循环到完成**(stop-token 唯一完成判据,对齐 osp loop)
|
|
8
|
-
* - **内部递归同样低能 subagent**(worker 工具面含 subagent,DSH 原生模型继承)
|
|
9
|
-
* - **workflow 能力**(jobs 编排:主 agent 一次并行多个杂工,maxParallel 默认 10)
|
|
10
|
-
* - **worker 不含 handyman 本身**(递归编排归主 agent,防无限嵌套)
|
|
11
|
-
* - **不兼容旧 loop 进度文件**(仅 handyman- 前缀)
|
|
12
|
-
*
|
|
13
|
-
* 机制:ctx.agents.create()(带 setup 钩子)创建专用 agent(进程内),
|
|
14
|
-
* 每轮 followup → agent/status idle → 读 session.events 响应 → 写进度 → stop token 检查 →
|
|
15
|
-
* 未完成继续下一轮;followup/等待空闲抛错(非正常停止)→ dispose 并重新 create agent
|
|
16
|
-
* (重启计数,≤100),同一轮重试。工厂模式:apply 时闭包捕获插件 ctx(工具 execute 无 ctx 参数)。
|
|
17
|
-
*
|
|
18
|
-
* preset 继承 + 工具收窄:setup 钩子里对子 agent 执行 agentPresets.composeFrom(对齐
|
|
19
|
-
* subagent 先例)+ tools.restrict deny handyman(worker 内部看不到 handyman 工具)。
|
|
20
|
-
*/
|
|
21
|
-
import type { ToolDefinition } from '@deepseek-ai/dsh-tools';
|
|
22
|
-
import type { Context } from 'cordis';
|
|
23
|
-
/** 创建 handyman 工具(闭包捕获插件 ctx → 可访问 ctx.agents) */
|
|
24
|
-
export declare function createHandymanTool(ctx: Context): ToolDefinition;
|