@shgroup/dsh-serenity-hooks 1.47.1 → 1.47.3

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.47.1",
3
+ "version": "1.47.3",
4
4
  "main": "lib/index.js",
5
5
  "description": "宁静号 ACC harness(Native Cordis 插件):给 DSH 装一个「AI 工作区」——11 个工具(container_fs/container_trajectory/dashboard/container_git/msm/praxis/handyman/localstore/container_admin/im-bridge/acc-diag;后两个按配置条件出现)+ 机械约束(安全模式/工作区围墙/密钥守卫)+ 轨迹日志与原地重建 + 网页登录入口/微信桥/子角色/对外问答页/trajectory 唤醒注册表。适配 DSH 0.1.5-rc.2(deepseek-ai/deepseek-harness)。",
6
6
  "engines": {
@@ -72,5 +72,8 @@ function hostWeb(ctx) {
72
72
  function hostSubagents(ctx) {
73
73
  return hostInjected(ctx, "subagents");
74
74
  }
75
+ function hostLlm(ctx) {
76
+ return hostInjected(ctx, "llm");
77
+ }
75
78
  //#endregion
76
- export { hostSettings as a, hostWebServer as c, hostSessions as i, hostInjected as n, hostSubagents as o, hostService as r, hostWeb as s, hostAgents as t };
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 };
@@ -1,5 +1,5 @@
1
1
  import { t as __exportAll } from "./rolldown-runtime-D7D4PA-g.js";
2
- import { i as hostSessions, r as hostService } from "./access-fiehjxV6.js";
2
+ import { a as hostSessions, i as hostService } from "./access-CNccNtgf.js";
3
3
  import { a as findSerenityRoot } from "./ccc-DG4Oc7I1.js";
4
4
  import { basename } from "node:path";
5
5
  //#region src/ccc-roots.ts
@@ -39,6 +39,34 @@ export declare function splitModel(model: string): {
39
39
  };
40
40
  /** 校验模型在白名单内;不在 → 抛错(用户拍板:只能使用 CCC 配置的模型) */
41
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;
42
70
  /** 轮次 prompt(对齐老 loop 结构:回顾进度 → 自由工作 → 汇报;S134 EAP 化:固定详尽) */
43
71
  export declare function buildRoundPrompt(opts: {
44
72
  root: string;
@@ -54,7 +82,7 @@ export declare function buildRoundPrompt(opts: {
54
82
  * 使用 handyman 前必须先加载 eap 设计方案;并行策略(jobs 编排);提示词规范(详尽固定 EAP);
55
83
  * 阅读/文字编写类 handyman 内部也加载 eap。
56
84
  */
57
- 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 | one serial child agent runs the task ONCE and returns its final text | loop-validated worker: hard while-loop + random completion code |\n| Use when | you need exactly one result back now (per-case testers, single conversions) | 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 | no (call once per task) | 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 (background mode)\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### 4. Parallel strategy (background mode only)\n- Independent subtasks can run in parallel via 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: handyman.maxParallel (default 10 \u2014 cheap models are cheap)\n- Aggregation: 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";
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 | one serial child agent runs the task ONCE and returns its final text | loop-validated worker: hard while-loop + random completion code |\n| Use when | you need exactly one result back now (per-case testers, single conversions) | 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 | no (call once per task) | 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 (background mode)\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 (background mode only)\n- Independent subtasks can run in parallel via 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: handyman.maxParallel (default 10 \u2014 cheap models are cheap)\n- Aggregation: 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";
58
86
  /** handyman 运行状态(进度文件摘要;WebUI 等待界面数据源) */
59
87
  interface HandymanRunInfo {
60
88
  label: string;
@@ -96,6 +96,27 @@ interface HostSubagents {
96
96
  start?: (name: string, request: unknown) => Promise<unknown>;
97
97
  }
98
98
  export declare function hostSubagents(ctx: unknown): HostSubagents | undefined;
99
+ /**
100
+ * `ctx.llm`(`LlmRuntime`;v1.48:handyman 的 **provider 预检**取证面)。
101
+ *
102
+ * 形状对照宿主 rc.2(`@deepseek-ai/dsh-llm`):
103
+ * · `listProviders()` → `{ id, name }[]` —— **有 adapter** 的路由("本机真能用")
104
+ * · `listConfigurableProviders()` → `{ provider, displayName, settingsNs, … }[]` ——
105
+ * **已声明但未必激活**的路由("可以激活")
106
+ *
107
+ * 🔴 为什么走 `hostInjected` 而不是直接属性读:`llm` **不在** dsp 的 `inject` 列表内
108
+ * (见 `src/index.ts` 的 `inject`)⇒ 真实 cordis 下直接属性读对未声明服务**会抛**
109
+ * (`cannot get property "llm" without inject`),必须回落 `ctx.get`。
110
+ *
111
+ * 🔴 为什么**刻意不把 `llm` 加进 `inject`**:预检是**尽力而为**的增强,不是运行前提。
112
+ * 加进 `inject` 会让"没有 llm 服务的 profile"**整块不激活插件** —— 为了一条诊断信息
113
+ * 换来插件不上线,代价与收益倒挂。缺失一律 `undefined`,调用方**不得**因此抛错。
114
+ */
115
+ interface HostLlm {
116
+ listProviders?: () => unknown;
117
+ listConfigurableProviders?: () => unknown;
118
+ }
119
+ export declare function hostLlm(ctx: unknown): HostLlm | undefined;
99
120
  /** 会话 cwd 列表(live 会话;形状不符时返回空数组而非抛错) */
100
121
  export declare function hostSessionCwds(ctx: unknown): string[];
101
122
  export {};
package/lib/index.d.ts CHANGED
@@ -11,7 +11,7 @@
11
11
  *
12
12
  * 配置:进程级 Config(cordis.yml 提供)+ 运行时读取 CCC 的 .opencode/serenity.json(规范位置,.dsh 回退)。
13
13
  */
14
- import type { Context } from 'cordis';
14
+ import type { Context, Volatile } from 'cordis';
15
15
  import z from '@deepseek-ai/schemastery';
16
16
  export declare const name = "dsh-serenity-hooks";
17
17
  /** 主动调用的服务;其余(agent 事件)随 harness 装配必然存在
@@ -83,25 +83,151 @@ export interface Config {
83
83
  enabled?: boolean;
84
84
  };
85
85
  /** F1 双端口网关总开关(面板层;部署层同义键 = `gateway.enabled`) */
86
- gatewayEnabled?: boolean;
86
+ gatewayEnabled?: Volatile<boolean | undefined>;
87
87
  /** F2 超限重建总开关(面板层;部署层同义键 = `rebuild.enabled`) */
88
- rebuildEnabled?: boolean;
88
+ rebuildEnabled?: Volatile<boolean | undefined>;
89
89
  /** F2 触发阈值(K token;面板层;部署层同义键 = `rebuild.thresholdK`) */
90
- rebuildThresholdK?: number;
90
+ rebuildThresholdK?: Volatile<number | undefined>;
91
91
  /** F4 Skiff 调试服务总开关(面板层;部署层同义键 = `skiff.enabled`) */
92
- skiffEnabled?: boolean;
92
+ skiffEnabled?: Volatile<boolean | undefined>;
93
93
  /** F4 Skiff 调试端口(面板层;部署层同义键 = `skiff.debugPort`) */
94
- skiffDebugPort?: number;
94
+ skiffDebugPort?: Volatile<number | undefined>;
95
95
  /** F4c ACP HTTP 端点总开关(面板层;部署层同义键 = `acp.enabled`) */
96
- acpEnabled?: boolean;
96
+ acpEnabled?: Volatile<boolean | undefined>;
97
97
  /** F4c ACP HTTP 端口(面板层;部署层同义键 = `acp.httpPort`) */
98
- acpHttpPort?: number;
98
+ acpHttpPort?: Volatile<number | undefined>;
99
99
  /** F4d 建议问答页总开关(面板层;无部署层同义键) */
100
- publicAskEnabled?: boolean;
100
+ publicAskEnabled?: Volatile<boolean | undefined>;
101
101
  /** CRO(轨迹自编程唤起)总闸(面板层;无部署层同义键;缺省开) */
102
- croEnabled?: boolean;
102
+ croEnabled?: Volatile<boolean | undefined>;
103
103
  /** 无人值守代理回复总闸(面板层;无部署层同义键;缺省关) */
104
- unattendedEnabled?: boolean;
104
+ unattendedEnabled?: Volatile<boolean | undefined>;
105
105
  }
106
- export declare const Config: z<Config>;
106
+ export declare const Config: z<Schemastery.ObjectS<NoInfer<{
107
+ serenityConfigPaths: z<string[], string[], "defined">;
108
+ tools: z<boolean, boolean, "defined">;
109
+ guards: z<boolean, boolean, "defined">;
110
+ keeper: z<boolean, boolean, "defined">;
111
+ keeperThreshold: z<number, number, "defined">;
112
+ context: z<boolean, boolean, "defined">;
113
+ compactRetention: z<boolean, boolean, "defined">;
114
+ api: z<boolean, boolean, "defined">;
115
+ entrySkillMaxChars: z<number, number, "defined">;
116
+ env: z<boolean, boolean, "defined">;
117
+ opencodeSkills: z<boolean, boolean, "defined">;
118
+ gateway: z<Schemastery.ObjectS<NoInfer<{
119
+ enabled: z<boolean, boolean, "defined">;
120
+ }>>, Schemastery.ObjectT<NoInfer<{
121
+ enabled: z<boolean, boolean, "defined">;
122
+ }>>, "plain">;
123
+ rebuild: z<Schemastery.ObjectS<NoInfer<{
124
+ enabled: z<boolean, boolean, "defined">;
125
+ thresholdK: z<number, number, "defined">;
126
+ }>>, Schemastery.ObjectT<NoInfer<{
127
+ enabled: z<boolean, boolean, "defined">;
128
+ thresholdK: z<number, number, "defined">;
129
+ }>>, "plain">;
130
+ skiff: z<Schemastery.ObjectS<NoInfer<{
131
+ enabled: z<boolean, boolean, "defined">;
132
+ debugPort: z<number, number, "defined">;
133
+ }>>, Schemastery.ObjectT<NoInfer<{
134
+ enabled: z<boolean, boolean, "defined">;
135
+ debugPort: z<number, number, "defined">;
136
+ }>>, "plain">;
137
+ acp: z<Schemastery.ObjectS<NoInfer<{
138
+ enabled: z<boolean, boolean, "defined">;
139
+ httpPort: z<number, number, "defined">;
140
+ }>>, Schemastery.ObjectT<NoInfer<{
141
+ enabled: z<boolean, boolean, "defined">;
142
+ httpPort: z<number, number, "defined">;
143
+ }>>, "plain">;
144
+ webFetch: z<Schemastery.ObjectS<NoInfer<{
145
+ enabled: z<boolean, boolean, "defined">;
146
+ }>>, Schemastery.ObjectT<NoInfer<{
147
+ enabled: z<boolean, boolean, "defined">;
148
+ }>>, "plain">;
149
+ opencodeProvider: z<Schemastery.ObjectS<NoInfer<{
150
+ autoConfigure: z<boolean, boolean, "defined">;
151
+ }>>, Schemastery.ObjectT<NoInfer<{
152
+ autoConfigure: z<boolean, boolean, "defined">;
153
+ }>>, "plain">;
154
+ visionPatch: z<Schemastery.ObjectS<NoInfer<{
155
+ enabled: z<boolean, boolean, "defined">;
156
+ }>>, Schemastery.ObjectT<NoInfer<{
157
+ enabled: z<boolean, boolean, "defined">;
158
+ }>>, "plain">;
159
+ gatewayEnabled: z<boolean, boolean, "volatile">;
160
+ rebuildEnabled: z<boolean, boolean, "volatile">;
161
+ rebuildThresholdK: z<number, number, "volatile">;
162
+ skiffEnabled: z<boolean, boolean, "volatile">;
163
+ skiffDebugPort: z<number, number, "volatile">;
164
+ acpEnabled: z<boolean, boolean, "volatile">;
165
+ acpHttpPort: z<number, number, "volatile">;
166
+ publicAskEnabled: z<boolean, boolean, "volatile">;
167
+ croEnabled: z<boolean, boolean, "volatile">;
168
+ unattendedEnabled: z<boolean, boolean, "volatile">;
169
+ }>>, Schemastery.ObjectT<NoInfer<{
170
+ serenityConfigPaths: z<string[], string[], "defined">;
171
+ tools: z<boolean, boolean, "defined">;
172
+ guards: z<boolean, boolean, "defined">;
173
+ keeper: z<boolean, boolean, "defined">;
174
+ keeperThreshold: z<number, number, "defined">;
175
+ context: z<boolean, boolean, "defined">;
176
+ compactRetention: z<boolean, boolean, "defined">;
177
+ api: z<boolean, boolean, "defined">;
178
+ entrySkillMaxChars: z<number, number, "defined">;
179
+ env: z<boolean, boolean, "defined">;
180
+ opencodeSkills: z<boolean, boolean, "defined">;
181
+ gateway: z<Schemastery.ObjectS<NoInfer<{
182
+ enabled: z<boolean, boolean, "defined">;
183
+ }>>, Schemastery.ObjectT<NoInfer<{
184
+ enabled: z<boolean, boolean, "defined">;
185
+ }>>, "plain">;
186
+ rebuild: z<Schemastery.ObjectS<NoInfer<{
187
+ enabled: z<boolean, boolean, "defined">;
188
+ thresholdK: z<number, number, "defined">;
189
+ }>>, Schemastery.ObjectT<NoInfer<{
190
+ enabled: z<boolean, boolean, "defined">;
191
+ thresholdK: z<number, number, "defined">;
192
+ }>>, "plain">;
193
+ skiff: z<Schemastery.ObjectS<NoInfer<{
194
+ enabled: z<boolean, boolean, "defined">;
195
+ debugPort: z<number, number, "defined">;
196
+ }>>, Schemastery.ObjectT<NoInfer<{
197
+ enabled: z<boolean, boolean, "defined">;
198
+ debugPort: z<number, number, "defined">;
199
+ }>>, "plain">;
200
+ acp: z<Schemastery.ObjectS<NoInfer<{
201
+ enabled: z<boolean, boolean, "defined">;
202
+ httpPort: z<number, number, "defined">;
203
+ }>>, Schemastery.ObjectT<NoInfer<{
204
+ enabled: z<boolean, boolean, "defined">;
205
+ httpPort: z<number, number, "defined">;
206
+ }>>, "plain">;
207
+ webFetch: z<Schemastery.ObjectS<NoInfer<{
208
+ enabled: z<boolean, boolean, "defined">;
209
+ }>>, Schemastery.ObjectT<NoInfer<{
210
+ enabled: z<boolean, boolean, "defined">;
211
+ }>>, "plain">;
212
+ opencodeProvider: z<Schemastery.ObjectS<NoInfer<{
213
+ autoConfigure: z<boolean, boolean, "defined">;
214
+ }>>, Schemastery.ObjectT<NoInfer<{
215
+ autoConfigure: z<boolean, boolean, "defined">;
216
+ }>>, "plain">;
217
+ visionPatch: z<Schemastery.ObjectS<NoInfer<{
218
+ enabled: z<boolean, boolean, "defined">;
219
+ }>>, Schemastery.ObjectT<NoInfer<{
220
+ enabled: z<boolean, boolean, "defined">;
221
+ }>>, "plain">;
222
+ gatewayEnabled: z<boolean, boolean, "volatile">;
223
+ rebuildEnabled: z<boolean, boolean, "volatile">;
224
+ rebuildThresholdK: z<number, number, "volatile">;
225
+ skiffEnabled: z<boolean, boolean, "volatile">;
226
+ skiffDebugPort: z<number, number, "volatile">;
227
+ acpEnabled: z<boolean, boolean, "volatile">;
228
+ acpHttpPort: z<number, number, "volatile">;
229
+ publicAskEnabled: z<boolean, boolean, "volatile">;
230
+ croEnabled: z<boolean, boolean, "volatile">;
231
+ unattendedEnabled: z<boolean, boolean, "volatile">;
232
+ }>>, "plain">;
107
233
  export declare function apply(ctx: Context, config: Config): void;
package/lib/index.js CHANGED
@@ -1,14 +1,14 @@
1
1
  import { t as __exportAll } from "./rolldown-runtime-D7D4PA-g.js";
2
- import { a as hostSettings, c as hostWebServer, i as hostSessions, n as hostInjected, o as hostSubagents, r as hostService, s as hostWeb, t as hostAgents } from "./access-fiehjxV6.js";
2
+ import { a as hostSessions, c as hostWeb, i as hostService, l as hostWebServer, n as hostInjected, o as hostSettings, r as hostLlm, s as hostSubagents, t as hostAgents } from "./access-CNccNtgf.js";
3
3
  import { a as findSerenityRoot, c as matchBlacklist, d as readCccName$1, f as readExclusiveTools, g as resolveInside, i as findGitRoot, l as pathInside, m as readTrajectorySkills, n as SAFE_MODE_MARKER, o as isSafeModeOn, p as readHandymanConfig, r as classifyPath, s as loadSerenityConfig, t as DEFAULT_SERENITY_CONFIG_PATHS, u as readBlacklist } from "./ccc-DG4Oc7I1.js";
4
- import { i as cccRootForExec, n as agentCwdFor, o as listCccs, r as cccRootForCwd, t as NO_CCC_FROM_AGENT_CWD } from "./ccc-roots-FxWXuZPw.js";
5
- import { C as showSession, D as localFileStamp, E as isoLocal, S as setActiveSessionInfo, T as useSession, _ as parseSessionContextFromEvents, a as pruneMissingBindings, b as sessionEvents$1, c as setBindingStore, d as clearActiveSessionInfo, f as createSession, g as listSessions, h as getActiveSessionInfo, l as DEFAULT_SESSION_SCOPE, m as findSession, n as PLUGIN_SOURCE, o as readLastBound, p as extractSessionMdPathFromText, r as appendBound, s as resolveSessionTrajectoryLabel, t as ACC_MESSAGE_KIND, u as TRAJECTORY_ACTIONS, v as readActiveSessionMd, w as summarize, x as sessionsRoot, y as resolveSessionByTitle } from "./message-source-Boc-AbHs.js";
4
+ import { i as cccRootForExec, n as agentCwdFor, o as listCccs, r as cccRootForCwd, t as NO_CCC_FROM_AGENT_CWD } from "./ccc-roots-NpTWo0-c.js";
5
+ import { C as showSession, D as localFileStamp, E as isoLocal, S as setActiveSessionInfo, T as useSession, _ as parseSessionContextFromEvents, a as pruneMissingBindings, b as sessionEvents$1, c as setBindingStore, d as clearActiveSessionInfo, f as createSession, g as listSessions, h as getActiveSessionInfo, l as DEFAULT_SESSION_SCOPE, m as findSession, n as PLUGIN_SOURCE, o as readLastBound, p as extractSessionMdPathFromText, r as appendBound, s as resolveSessionTrajectoryLabel, t as ACC_MESSAGE_KIND, u as TRAJECTORY_ACTIONS, v as readActiveSessionMd, w as summarize, x as sessionsRoot, y as resolveSessionByTitle } from "./message-source-aR-FJ38L.js";
6
6
  import { a as readWeixinCredential, c as weixinInboundDir, d as LOCALSTORE_SCOPES, f as checkLocalstoreGitCompliance, g as runLocalStore, h as readStore, i as matchWeixinRoute, l as weixinSessionIdFor, m as readGitTrack, n as extractWeixinText, o as readWeixinSettings, p as localstorePath, r as hasVoiceItem, s as sanitizeFileName$1, t as extractWeixinMedia } from "./weixin-route-CWMRa36W.js";
7
7
  import { a as unregisterSkiffSession$1, i as skiffSessionSnapshot$1, n as skiffRoleFor$1, o as ACC_VERSION, r as skiffSessionInfo$1, t as registerSkiffSession$1 } from "./skiff-registry-BjIX_KD1.js";
8
8
  import { a as readSkiffRoles, c as roleMsmWhitelist, d as systemPromptSource, f as trajectorySubset, i as isSkiffSessionId, l as roleToolWhitelist, n as buildSkiffBasePrompt, o as resolveRoleSystemPrompt, r as createRolePromptReader, s as resolveSkiffKind } from "./skiff-role-KqQA6pkr.js";
9
- import { a as FACE_PORTS, c as MECHANISM_PORTS, i as ACP_HTTP_PORT, l as SKIFF_DEBUG_PORT, n as registerSettingsSection, o as GATEWAY_PORT, s as MAIN_WEB_PORT, t as readSimpleSettings, u as WEIXIN_SEND_PORT } from "./settings-section-DTJW4smh.js";
9
+ import { a as FACE_PORTS, c as MECHANISM_PORTS, i as ACP_HTTP_PORT, l as SKIFF_DEBUG_PORT, n as registerSettingsSection, o as GATEWAY_PORT, s as MAIN_WEB_PORT, t as readSimpleSettings, u as WEIXIN_SEND_PORT } from "./settings-section-DxMwlCv9.js";
10
10
  import { r as sessionsRootDir, t as hasSessionLogById } from "./session-cleanup-BtkSmry8.js";
11
- import { a as registerDisposer, c as WAKE_CATCH_UP_MS, i as registerCroTurnTracking, l as addWake, n as sendToTrajectory, o as CRO_FILENAME, r as wakeSchedulerState, s as buildCroSnapshot, t as registerWakeScheduler, u as listWakes } from "./wake-scheduler-DdaRNofO.js";
11
+ import { a as registerDisposer, c as WAKE_CATCH_UP_MS, i as registerCroTurnTracking, l as addWake, n as sendToTrajectory, o as CRO_FILENAME, r as wakeSchedulerState, s as buildCroSnapshot, t as registerWakeScheduler, u as listWakes } from "./wake-scheduler-BR4hD8Vj.js";
12
12
  import { a as markdownToPlainText, c as sendTyping, i as getUpdates, l as sniffImageExt, n as downloadMedia, o as sendFileMessage, r as getConfig, s as sendTextMessage, t as TypingStatus } from "./weixin-api-CtFYm53S.js";
13
13
  import z from "@deepseek-ai/schemastery";
14
14
  import { defineTool } from "@deepseek-ai/dsh-tools";
@@ -1762,6 +1762,61 @@ function splitModel(model) {
1762
1762
  function requireWhitelistedModel(model, models) {
1763
1763
  if (!models.includes(model)) throw new Error(`handyman: model "${model}" is not in the CCC whitelist. Configure .opencode/serenity.json "handyman.models" with one of: ${models.join(", ")}`);
1764
1764
  }
1765
+ /**
1766
+ * providerAvailabilityError — 判定「**本机有没有该 provider 的 adapter**」,并产出**可执行**的错误文本。
1767
+ * 返回 `null` = 全部可用(调用方照常继续)。
1768
+ *
1769
+ * 🔴 为什么需要它(S142 2026-09-25,owner 在 **Mac** 上实测):`handyman` 的模型白名单写在
1770
+ * **CCC**(`.opencode/serenity.json`,**随 git 走**),而 provider adapter 由**每台机器的宿主**
1771
+ * 注册(profile 配置 / settings 命名空间)⇒ 同一份白名单在一台机器可用、在另一台必失败。
1772
+ * 实测症状:宿主抛 `no adapter registered for provider "<p>"`(`dsh-llm`,code `NO_ADAPTER`),
1773
+ * 而**前台路径的 `diagnostic` 是空的** ⇒ 调用方(LLM)拿到 `stopReason:"error"` 却**不知道为什么**,
1774
+ * 也就无法修正自己的配置 —— 本条把「沉默的失败」变成「可执行的错误」。
1775
+ *
1776
+ * 数据来源(宿主 `LlmRuntime`):`listProviders()` = **有 adapter 的路由**;
1777
+ * `listConfigurableProviders()` = **已声明但未必激活**的路由(含激活用的 settings 命名空间)。
1778
+ *
1779
+ * 判据边界:`model` 串里**没有 `/`** 时取不到 provider(沿用 agent 默认路由)⇒ **不判定**(跳过)。
1780
+ */
1781
+ function providerAvailabilityError(models, facts) {
1782
+ const registered = new Set(facts.registered.map((r) => r.id));
1783
+ const requested = models.map((m) => ({
1784
+ raw: m,
1785
+ provider: splitModel(m).provider
1786
+ })).filter((e) => typeof e.provider === "string" && e.provider !== "");
1787
+ const missing = [...new Set(requested.filter((e) => !registered.has(e.provider)).map((e) => e.provider))];
1788
+ if (missing.length === 0) return null;
1789
+ const list = (items, cap = 12) => items.length === 0 ? " (none)" : items.slice(0, cap).map((s) => ` - ${s}`).join("\n") + (items.length > cap ? `\n … and ${items.length - cap} more` : "");
1790
+ const available = facts.registered.map((r) => r.name ? `${r.id} (${r.name})` : r.id).sort();
1791
+ const declared = (facts.declared ?? []).filter((d) => missing.includes(d.provider));
1792
+ const configPath = facts.configPath ?? "<ccc>/.opencode/serenity.json";
1793
+ const missingList = missing.map((p) => `"${p}"`).join(", ");
1794
+ return [
1795
+ `handyman: provider ${missingList} has no adapter registered on this machine.`,
1796
+ "",
1797
+ " Requested model(s) :",
1798
+ ...requested.map((e) => ` ${e.raw}`),
1799
+ ` Configured in : ${configPath} → "handyman.models" / "handyman.defaultModel"`,
1800
+ "",
1801
+ " Why this happens : the whitelist lives in the CCC (it travels with the repository), while",
1802
+ " provider adapters are registered per machine by the host. An entry that",
1803
+ " works on one machine can fail on another.",
1804
+ "",
1805
+ " Available on this machine (registered adapters):",
1806
+ list(available),
1807
+ "",
1808
+ " Declared but NOT active here (activate via settings, then retry):",
1809
+ list(declared.map((d) => `${d.provider}${d.displayName ? ` (${d.displayName})` : ""}${d.settingsNs ? ` — settings namespace "${d.settingsNs}"` : ""}`)),
1810
+ "",
1811
+ " Fix (pick one, then retry):",
1812
+ ` 1. Point the whitelist at a provider that IS registered here — edit ${configPath}`,
1813
+ " (\"handyman.models\", and \"handyman.defaultModel\" if that is what you relied on).",
1814
+ ` 2. Register/activate provider ${missingList} on this machine (host profile config,`,
1815
+ " or the settings namespace listed above).",
1816
+ "",
1817
+ " Do not retry unchanged — the same call fails identically."
1818
+ ].join("\n");
1819
+ }
1765
1820
  /** 轮次 prompt(对齐老 loop 结构:回顾进度 → 自由工作 → 汇报;S134 EAP 化:固定详尽) */
1766
1821
  function buildRoundPrompt(opts) {
1767
1822
  const { root, session, label, round, stopToken, progress, task } = opts;
@@ -1828,6 +1883,18 @@ Before calling handyman, load eap (acc-eap skill) and design the "scale-up handy
1828
1883
  - background mode: recursive subagents inside a worker inherit the worker's model automatically (DSH native)
1829
1884
  - foreground mode: the child is started through the host delegation service with the resolved model
1830
1885
 
1886
+ ### 3b. 🔴 Provider availability (per-machine — the whitelist is NOT enough)
1887
+ - The whitelist lives in the **CCC** ("serenity.json", it **travels with the repository**), but provider
1888
+ **adapters are registered per machine by the host** (profile config / settings namespace). An entry that
1889
+ works on one machine can fail on another with: "no adapter registered for provider <p>".
1890
+ - Before spawning, handyman verifies the provider of the resolved model against the adapters registered
1891
+ **on this machine**; if it is missing it reports the requested model, where it is configured, **the
1892
+ providers that ARE available here**, any declared-but-dormant route (with its settings namespace), and the
1893
+ exact fix — foreground puts this in "diagnostic", background throws it.
1894
+ - ⇒ If you see "has no adapter registered on this machine": edit ".opencode/serenity.json" "handyman.models"
1895
+ (and "handyman.defaultModel") to a provider listed as available, or activate/register the provider on this
1896
+ machine. **Do not retry the same call unchanged** — it fails identically.
1897
+
1831
1898
  ### 4. Parallel strategy (background mode only)
1832
1899
  - Independent subtasks can run in parallel via handyman(mode="background", jobs=[...]): each job gets its own label + task + stop token + progress file
1833
1900
  - Concurrency safety guaranteed: unique sessionId (handyman-<label>-<uuid>), progress files isolated per label
@@ -4252,6 +4319,38 @@ function subagentOutputText(output) {
4252
4319
  return output.filter((b) => typeof b === "object" && b !== null).filter((b) => b.type === "text" && typeof b.text === "string").map((b) => b.text).join("");
4253
4320
  }
4254
4321
  /**
4322
+ * providerPreflight — 在**创建任何 worker 之前**判定「本机有没有该 provider 的 adapter」。
4323
+ *
4324
+ * 返回**可执行**的错误文本;`null` = 可用,**或无法取证**(服务缺失 / 形状不符 / 读取抛错
4325
+ * ⇒ 一律**不阻断**,保持旧行为 —— 增强不得变成新的失败源)。
4326
+ *
4327
+ * 为什么必须放在"创建之前":宿主对未知 provider 抛 `no adapter registered for provider "…"`
4328
+ * (`dsh-llm`,code `NO_ADAPTER`)。若照常创建:前台只拿回 `stopReason:"error"` + **空
4329
+ * `diagnostic`**(实测),后台整条失败且**不带原因** ⇒ 调用方(LLM)无法自我修正配置。
4330
+ */
4331
+ function providerPreflight(ctx, model) {
4332
+ const llm = hostLlm(ctx);
4333
+ if (typeof llm?.listProviders !== "function") return null;
4334
+ let registered;
4335
+ try {
4336
+ const raw = llm.listProviders();
4337
+ if (!Array.isArray(raw)) return null;
4338
+ registered = raw.filter((r) => typeof r === "object" && r !== null && typeof r.id === "string");
4339
+ } catch {
4340
+ return null;
4341
+ }
4342
+ let declared = [];
4343
+ try {
4344
+ const raw = llm.listConfigurableProviders?.();
4345
+ if (Array.isArray(raw)) declared = raw.filter((d) => typeof d === "object" && d !== null && typeof d.provider === "string");
4346
+ } catch {}
4347
+ return providerAvailabilityError([model], {
4348
+ registered,
4349
+ declared,
4350
+ configPath: ".opencode/serenity.json"
4351
+ });
4352
+ }
4353
+ /**
4255
4354
  * foreground 模式:单次前台串行委派(用户裁决"本次所需的简单实现")。
4256
4355
  *
4257
4356
  * 与 background 的差别(R↓):
@@ -4269,6 +4368,15 @@ async function runForegroundJob(ctx, opts) {
4269
4368
  requireWhitelistedModel(model, models);
4270
4369
  const subagents = hostSubagents(ctx);
4271
4370
  if (!subagents?.start) throw new Error("handyman foreground: host subagents service unavailable — neither ctx.subagents nor ctx.get(\"subagents\") yielded a start() function. Check `dashboard health` (host-contract section, service \"subagents\"); mode=\"background\" does not need this service and still works.");
4371
+ const preflight = providerPreflight(ctx, model);
4372
+ if (preflight !== null) return {
4373
+ mode: "foreground",
4374
+ done: false,
4375
+ model,
4376
+ stopReason: "error",
4377
+ output: "",
4378
+ diagnostic: preflight
4379
+ };
4272
4380
  const { provider, model: modelName } = splitModel(model);
4273
4381
  const run = await subagents.start("spawn", {
4274
4382
  ...label === void 0 ? {} : { label },
@@ -4309,6 +4417,8 @@ async function runHandymanJob(ctx, opts) {
4309
4417
  const label = job.label;
4310
4418
  const model = job.model ?? defaultModel;
4311
4419
  requireWhitelistedModel(model, models);
4420
+ const preflightError = providerPreflight(ctx, model);
4421
+ if (preflightError !== null) throw new Error(preflightError);
4312
4422
  const { provider, model: modelName } = splitModel(model);
4313
4423
  const stopToken = newStopToken();
4314
4424
  let progress = readProgress(root, label);
@@ -4426,7 +4536,7 @@ async function runHandymanJob(ctx, opts) {
4426
4536
  function createHandymanTool(ctx) {
4427
4537
  return defineTool({
4428
4538
  name: "handyman",
4429
- description: "The handyman (杂工): delegate work to a worker agent on a CCC-configured model. Two modes (v1.31.3).\nMode \"foreground\" (DEFAULT): one serial child agent runs the task once and returns its final text — no loop, no completion-code validation, no progress file; use it for one-shot serial delegation (e.g. per-case testers that just gather evidence). Usage: handyman(task, [label], [model]).\nMode \"background\": the loop-validated worker — internal hard while-loop whose ONLY completion condition is the worker echoing this round's random completion code (stop token, prevents low-intelligence models from finishing early), round cap (default 100, resumable), automatic restart on abnormal stop (≤100), progress file AGENT_SESSIONS/handyman-<label>.md/.json (same label resumes), and parallel jobs (handyman.maxParallel, default 10). Usage: handyman(mode=\"background\", task, label, [model]) or handyman(mode=\"background\", jobs=[{task,label,model?},...]).\nChoosing: need exactly one result back now → foreground; need anti-early-finish guarantees, resumability or parallel jobs → background.\nModel: only models whitelisted in .opencode/serenity.json \"handyman.models\" (missing config = error); default reads handyman.defaultModel. Both modes share the same whitelist and default.\nRecursion: a worker's tool set excludes handyman itself (orchestration belongs to the main agent).\nGuide: handyman(guide=true) prints the scale-up usage guide.",
4539
+ description: "The handyman (杂工): delegate work to a worker agent on a CCC-configured model. Two modes (v1.31.3).\nMode \"foreground\" (DEFAULT): one serial child agent runs the task once and returns its final text — no loop, no completion-code validation, no progress file; use it for one-shot serial delegation (e.g. per-case testers that just gather evidence). Usage: handyman(task, [label], [model]).\nMode \"background\": the loop-validated worker — internal hard while-loop whose ONLY completion condition is the worker echoing this round's random completion code (stop token, prevents low-intelligence models from finishing early), round cap (default 100, resumable), automatic restart on abnormal stop (≤100), progress file AGENT_SESSIONS/handyman-<label>.md/.json (same label resumes), and parallel jobs (handyman.maxParallel, default 10). Usage: handyman(mode=\"background\", task, label, [model]) or handyman(mode=\"background\", jobs=[{task,label,model?},...]).\nChoosing: need exactly one result back now → foreground; need anti-early-finish guarantees, resumability or parallel jobs → background.\nModel: only models whitelisted in .opencode/serenity.json \"handyman.models\" (missing config = error); default reads handyman.defaultModel. Both modes share the same whitelist and default.\nProvider check: the whitelist lives in the CCC (it travels with the repo) while provider adapters are registered per machine by the host — so before spawning, the provider of the resolved model is verified against the adapters present on THIS machine. If it is missing you get the available providers plus the exact fix (foreground: in `diagnostic`; background: thrown), never an opaque failure.\nRecursion: a worker's tool set excludes handyman itself (orchestration belongs to the main agent).\nGuide: handyman(guide=true) prints the scale-up usage guide.",
4430
4540
  parameters: {
4431
4541
  mode: {
4432
4542
  type: "string",
@@ -4508,7 +4618,8 @@ function createHandymanTool(ctx) {
4508
4618
  if (typeof args.task !== "string" || typeof args.label !== "string") throw new Error("handyman: task and label are required (single-job mode)");
4509
4619
  return [{
4510
4620
  task: args.task,
4511
- label: args.label
4621
+ label: args.label,
4622
+ ...typeof args.model === "string" && args.model !== "" ? { model: args.model } : {}
4512
4623
  }];
4513
4624
  })();
4514
4625
  if (jobs.length === 0) throw new Error("handyman: no jobs provided");
@@ -7779,7 +7890,7 @@ function registerStatusApi(ctx, opts = {}) {
7779
7890
  return;
7780
7891
  }
7781
7892
  const settings = readAdvancedSettings();
7782
- const { readSimpleSettings } = await import("./settings-section-DTJW4smh.js").then((n) => n.r);
7893
+ const { readSimpleSettings } = await import("./settings-section-DxMwlCv9.js").then((n) => n.r);
7783
7894
  const simple = readSimpleSettings();
7784
7895
  const allowed = settings.publicAsk.allowed;
7785
7896
  const port = simple.acpHttpPort ?? ACP_HTTP_PORT;
@@ -14134,7 +14245,7 @@ function registerWeixinBridge(ctx) {
14134
14245
  const syncFromLive = () => {
14135
14246
  (async () => {
14136
14247
  try {
14137
- const { listCccs } = await import("./ccc-roots-FxWXuZPw.js").then((n) => n.a);
14248
+ const { listCccs } = await import("./ccc-roots-NpTWo0-c.js").then((n) => n.a);
14138
14249
  for (const entry of await listCccs(ctx)) syncCccBridge(ctx, entry.root);
14139
14250
  } catch {}
14140
14251
  })();
@@ -14260,7 +14371,7 @@ function matchCcc(input, candidates) {
14260
14371
  }
14261
14372
  /** 组装候选列表(`ccc-roots.listCccs` 投影 + `.serenity` 名;动态 import 保持本模块静态依赖轻量) */
14262
14373
  async function collectCandidates(ctx) {
14263
- const { listCccs } = await import("./ccc-roots-FxWXuZPw.js").then((n) => n.a);
14374
+ const { listCccs } = await import("./ccc-roots-NpTWo0-c.js").then((n) => n.a);
14264
14375
  const entries = await listCccs(ctx, { defaultRoot: process.cwd() });
14265
14376
  const seen = /* @__PURE__ */ new Set();
14266
14377
  const out = [];
@@ -16081,16 +16192,16 @@ const Config = z.object({
16081
16192
  webFetch: z.object({ enabled: z.boolean().default(true) }),
16082
16193
  opencodeProvider: z.object({ autoConfigure: z.boolean().default(true) }),
16083
16194
  visionPatch: z.object({ enabled: z.boolean().default(true) }),
16084
- gatewayEnabled: z.boolean(),
16085
- rebuildEnabled: z.boolean(),
16086
- rebuildThresholdK: z.number().min(50).max(4e3),
16087
- skiffEnabled: z.boolean(),
16088
- skiffDebugPort: z.number().min(1024).max(65535),
16089
- acpEnabled: z.boolean(),
16090
- acpHttpPort: z.number().min(1024).max(65535),
16091
- publicAskEnabled: z.boolean(),
16092
- croEnabled: z.boolean(),
16093
- unattendedEnabled: z.boolean()
16195
+ gatewayEnabled: z.boolean().volatile(),
16196
+ rebuildEnabled: z.boolean().volatile(),
16197
+ rebuildThresholdK: z.number().min(50).max(4e3).volatile(),
16198
+ skiffEnabled: z.boolean().volatile(),
16199
+ skiffDebugPort: z.number().min(1024).max(65535).volatile(),
16200
+ acpEnabled: z.boolean().volatile(),
16201
+ acpHttpPort: z.number().min(1024).max(65535).volatile(),
16202
+ publicAskEnabled: z.boolean().volatile(),
16203
+ croEnabled: z.boolean().volatile(),
16204
+ unattendedEnabled: z.boolean().volatile()
16094
16205
  });
16095
16206
  function apply(ctx, config) {
16096
16207
  try {
@@ -1,4 +1,4 @@
1
- import { r as cccRootForCwd } from "./ccc-roots-FxWXuZPw.js";
1
+ import { r as cccRootForCwd } from "./ccc-roots-NpTWo0-c.js";
2
2
  import { basename, dirname, join } from "node:path";
3
3
  import { existsSync, mkdirSync, readFileSync, readdirSync, renameSync, statSync, writeFileSync } from "node:fs";
4
4
  //#region src/time.ts
@@ -1,5 +1,5 @@
1
1
  import { t as __exportAll } from "./rolldown-runtime-D7D4PA-g.js";
2
- import { a as hostSettings } from "./access-fiehjxV6.js";
2
+ import { o as hostSettings } from "./access-CNccNtgf.js";
3
3
  //#region src/ports.ts
4
4
  /**
5
5
  * 集中端口表。
@@ -63,7 +63,8 @@ var settings_section_exports = /* @__PURE__ */ __exportAll({
63
63
  defaultSimpleSettings: () => defaultSimpleSettings,
64
64
  readSimpleSettings: () => readSimpleSettings,
65
65
  registerSettingsSection: () => registerSettingsSection,
66
- simpleSettingsFromConfig: () => simpleSettingsFromConfig
66
+ simpleSettingsFromConfig: () => simpleSettingsFromConfig,
67
+ unwrapVolatile: () => unwrapVolatile
67
68
  });
68
69
  /** 进程级默认(无 Config 可读时的兜底;与 Config 各字段的缺省一致) */
69
70
  function defaultSimpleSettings() {
@@ -81,6 +82,30 @@ function defaultSimpleSettings() {
81
82
  };
82
83
  }
83
84
  /**
85
+ * volatile 字段的**结构化解包**(v1.47.2;本文件读取语义的第一道)。
86
+ *
87
+ * ## 为什么必须有它(两个事实的合成,缺一即错)
88
+ * ① **0.1.7 的面板层字段必须标 `.volatile()`** —— 否则本插件命名空间整个不进宿主的
89
+ * `settings.describe()`(`volatileForm()` 只认 `meta.volatile`)⇒ **设置面板那一节消失**。
90
+ * ② **标了之后,运行期拿到的是引用包装,不是裸值** —— schemastery 的 `Schema.resolve()` 对
91
+ * `meta.volatile` **无条件**包 `createVolatile(value)`,且该分支排在"缺省值回退"那一段
92
+ * **之前** ⇒ **"用户没设过"的字段也是恒真对象**(`.get()` 返回 `undefined`),**不是 `undefined`**。
93
+ *
94
+ * ⇒ 若这里不解包,下面十行的 `??` 链会**短路在这层包装上** ⇒ 网关 / Skiff / ACP 会被
95
+ * **无意打开**(比"面板不显示"更坏:静默改变对外暴露面)。
96
+ *
97
+ * ## 为什么用结构判定
98
+ * 可观察形状就是"有 `get()`"(cosmokit 的 `isVolatile` 也这么判);本模块不引它的运行时依赖。
99
+ *
100
+ * @param value - volatile 引用 | 裸值 | undefined
101
+ * @returns 解包后的裸值(引用 ⇒ `.get()`;裸值原样;缺省 ⇒ `undefined`)
102
+ */
103
+ function unwrapVolatile(value) {
104
+ if (value === void 0 || value === null) return void 0;
105
+ if (typeof value.get === "function") return value.get();
106
+ return value;
107
+ }
108
+ /**
84
109
  * Config(宿主 Loader 校验后的解析值)→ 扁平简单配置。**本文件唯一的读取语义**。
85
110
  *
86
111
  * 优先级:**面板层(扁平键)> 部署层(嵌套段)> 内建缺省**。
@@ -90,16 +115,16 @@ function defaultSimpleSettings() {
90
115
  function simpleSettingsFromConfig(config) {
91
116
  const d = defaultSimpleSettings();
92
117
  return {
93
- gatewayEnabled: config.gatewayEnabled ?? config.gateway?.enabled ?? d.gatewayEnabled,
94
- rebuildEnabled: config.rebuildEnabled ?? config.rebuild?.enabled ?? d.rebuildEnabled,
95
- rebuildThresholdK: config.rebuildThresholdK ?? config.rebuild?.thresholdK ?? d.rebuildThresholdK,
96
- skiffEnabled: config.skiffEnabled ?? config.skiff?.enabled ?? d.skiffEnabled,
97
- skiffDebugPort: config.skiffDebugPort ?? config.skiff?.debugPort ?? d.skiffDebugPort,
98
- acpEnabled: config.acpEnabled ?? config.acp?.enabled ?? d.acpEnabled,
99
- acpHttpPort: config.acpHttpPort ?? config.acp?.httpPort ?? d.acpHttpPort,
100
- publicAskEnabled: config.publicAskEnabled ?? d.publicAskEnabled,
101
- croEnabled: config.croEnabled ?? d.croEnabled,
102
- unattendedEnabled: config.unattendedEnabled ?? d.unattendedEnabled
118
+ gatewayEnabled: unwrapVolatile(config.gatewayEnabled) ?? config.gateway?.enabled ?? d.gatewayEnabled,
119
+ rebuildEnabled: unwrapVolatile(config.rebuildEnabled) ?? config.rebuild?.enabled ?? d.rebuildEnabled,
120
+ rebuildThresholdK: unwrapVolatile(config.rebuildThresholdK) ?? config.rebuild?.thresholdK ?? d.rebuildThresholdK,
121
+ skiffEnabled: unwrapVolatile(config.skiffEnabled) ?? config.skiff?.enabled ?? d.skiffEnabled,
122
+ skiffDebugPort: unwrapVolatile(config.skiffDebugPort) ?? config.skiff?.debugPort ?? d.skiffDebugPort,
123
+ acpEnabled: unwrapVolatile(config.acpEnabled) ?? config.acp?.enabled ?? d.acpEnabled,
124
+ acpHttpPort: unwrapVolatile(config.acpHttpPort) ?? config.acp?.httpPort ?? d.acpHttpPort,
125
+ publicAskEnabled: unwrapVolatile(config.publicAskEnabled) ?? d.publicAskEnabled,
126
+ croEnabled: unwrapVolatile(config.croEnabled) ?? d.croEnabled,
127
+ unattendedEnabled: unwrapVolatile(config.unattendedEnabled) ?? d.unattendedEnabled
103
128
  };
104
129
  }
105
130
  /** 运行时源(`registerSettingsSection` 装配:读本插件 fiber 的**当前** Config) */
@@ -33,7 +33,16 @@
33
33
  * 🔴 面板层的键**故意不带 schemastery 默认值** —— 有默认值就永远非 `undefined`,
34
34
  * 部署层会被无声压死("未设置"必须可观测)。
35
35
  */
36
- import type { Context } from 'cordis';
36
+ import type { Context, Volatile } from 'cordis';
37
+ /**
38
+ * 面板层布尔键的**两种来法**(v1.47.2):
39
+ * - 宿主 Loader 解析后的 Config ⇒ `Volatile<boolean | undefined>` **引用包装**(0.1.7 起面板字段必须 volatile)
40
+ * - 手写 Config / 部署 bundle / 单测 ⇒ 裸布尔
41
+ * ⇒ 本层两种都收,**读取一律先 `unwrapVolatile()`**(否则包装对象恒真 ⇒ 开关被无意打开)。
42
+ */
43
+ type PanelBoolean = boolean | Volatile<boolean | undefined>;
44
+ /** 面板层数值键:同 `PanelBoolean`(两种来法) */
45
+ type PanelNumber = number | Volatile<number | undefined>;
37
46
  /** 插件 Config 的简单配置片段(index.ts 的 Config 子集;面板层 + 部署层两种拼写) */
38
47
  interface SimpleConfigFragment {
39
48
  gateway?: {
@@ -53,16 +62,16 @@ interface SimpleConfigFragment {
53
62
  enabled?: boolean;
54
63
  httpPort?: number;
55
64
  };
56
- gatewayEnabled?: boolean;
57
- rebuildEnabled?: boolean;
58
- rebuildThresholdK?: number;
59
- skiffEnabled?: boolean;
60
- skiffDebugPort?: number;
61
- acpEnabled?: boolean;
62
- acpHttpPort?: number;
63
- publicAskEnabled?: boolean;
64
- croEnabled?: boolean;
65
- unattendedEnabled?: boolean;
65
+ gatewayEnabled?: PanelBoolean;
66
+ rebuildEnabled?: PanelBoolean;
67
+ rebuildThresholdK?: PanelNumber;
68
+ skiffEnabled?: PanelBoolean;
69
+ skiffDebugPort?: PanelNumber;
70
+ acpEnabled?: PanelBoolean;
71
+ acpHttpPort?: PanelNumber;
72
+ publicAskEnabled?: PanelBoolean;
73
+ croEnabled?: PanelBoolean;
74
+ unattendedEnabled?: PanelBoolean;
66
75
  }
67
76
  /**
68
77
  * 设置面板命名空间 = **profile 条目 id**(v0.1.7 起;与旧 `settings.yaml` 段名逐字相同)。
@@ -107,6 +116,30 @@ interface SerenitySimpleSettings {
107
116
  }
108
117
  /** 进程级默认(无 Config 可读时的兜底;与 Config 各字段的缺省一致) */
109
118
  export declare function defaultSimpleSettings(): SerenitySimpleSettings;
119
+ /** volatile 引用的**可观察形状**(结构判定用;刻意不引 cosmokit —— 它是宿主内部包,不是我们的 peerDep) */
120
+ interface VolatileRef<T> {
121
+ get(): T;
122
+ }
123
+ /**
124
+ * volatile 字段的**结构化解包**(v1.47.2;本文件读取语义的第一道)。
125
+ *
126
+ * ## 为什么必须有它(两个事实的合成,缺一即错)
127
+ * ① **0.1.7 的面板层字段必须标 `.volatile()`** —— 否则本插件命名空间整个不进宿主的
128
+ * `settings.describe()`(`volatileForm()` 只认 `meta.volatile`)⇒ **设置面板那一节消失**。
129
+ * ② **标了之后,运行期拿到的是引用包装,不是裸值** —— schemastery 的 `Schema.resolve()` 对
130
+ * `meta.volatile` **无条件**包 `createVolatile(value)`,且该分支排在"缺省值回退"那一段
131
+ * **之前** ⇒ **"用户没设过"的字段也是恒真对象**(`.get()` 返回 `undefined`),**不是 `undefined`**。
132
+ *
133
+ * ⇒ 若这里不解包,下面十行的 `??` 链会**短路在这层包装上** ⇒ 网关 / Skiff / ACP 会被
134
+ * **无意打开**(比"面板不显示"更坏:静默改变对外暴露面)。
135
+ *
136
+ * ## 为什么用结构判定
137
+ * 可观察形状就是"有 `get()`"(cosmokit 的 `isVolatile` 也这么判);本模块不引它的运行时依赖。
138
+ *
139
+ * @param value - volatile 引用 | 裸值 | undefined
140
+ * @returns 解包后的裸值(引用 ⇒ `.get()`;裸值原样;缺省 ⇒ `undefined`)
141
+ */
142
+ export declare function unwrapVolatile<T>(value: T | VolatileRef<T> | undefined): T | undefined;
110
143
  /**
111
144
  * Config(宿主 Loader 校验后的解析值)→ 扁平简单配置。**本文件唯一的读取语义**。
112
145
  *
@@ -1,8 +1,8 @@
1
- import { r as hostService, t as hostAgents } from "./access-fiehjxV6.js";
1
+ import { i as hostService, t as hostAgents } from "./access-CNccNtgf.js";
2
2
  import { g as resolveInside } from "./ccc-DG4Oc7I1.js";
3
- import { o as listCccs } from "./ccc-roots-FxWXuZPw.js";
4
- import { E as isoLocal, O as localHuman, i as listBoundSessionIds, k as localIdStamp, m as findSession, n as PLUGIN_SOURCE, x as sessionsRoot } from "./message-source-Boc-AbHs.js";
5
- import { t as readSimpleSettings } from "./settings-section-DTJW4smh.js";
3
+ import { o as listCccs } from "./ccc-roots-NpTWo0-c.js";
4
+ import { E as isoLocal, O as localHuman, i as listBoundSessionIds, k as localIdStamp, m as findSession, n as PLUGIN_SOURCE, x as sessionsRoot } from "./message-source-aR-FJ38L.js";
5
+ import { t as readSimpleSettings } from "./settings-section-DxMwlCv9.js";
6
6
  import { basename, dirname, join } from "node:path";
7
7
  import { existsSync, mkdirSync, readFileSync, readdirSync, renameSync, statSync, writeFileSync } from "node:fs";
8
8
  import { spawn } from "node:child_process";
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@shgroup/dsh-serenity-hooks",
3
- "version": "1.47.1",
3
+ "version": "1.47.3",
4
4
  "description": "宁静号 ACC harness(Native Cordis 插件)——给 DeepSeek Harness 装一个「AI 工作区」:11 个工具(container_fs/container_trajectory/dashboard/container_git/msm/praxis/handyman/localstore/container_admin/im-bridge/acc-diag)+ 机械约束(安全模式/工作区围墙/密钥守卫/对外输出守卫)+ 工作日志与原地重建 + 网页登录入口/微信桥/子角色/对外问答页/trajectory 唤醒注册表。适配 DSH 0.1.7-rc.1。",
5
5
  "license": "MIT",
6
6
  "repository": {