dsh-ctxlift 0.1.2 → 0.2.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/CHANGELOG.md CHANGED
@@ -1,46 +1,86 @@
1
1
  # Changelog
2
2
 
3
- 本项目遵循 [Keep a Changelog](https://keepachangelog.com/zh-CN/1.1.0/) 的格式。
3
+ 本项目所有值得记录的变更都记录在这个文件中。
4
4
 
5
- ## [0.1.2] - 2026-10-28
5
+ 格式基于 [Keep a Changelog](https://keepachangelog.com/zh-CN/1.1.0/),
6
+ 本项目遵循 [语义化版本](https://semver.org/lang/zh-CN/)。
6
7
 
7
- ### 修复
8
+ ## [Unreleased]
8
9
 
9
- - 修复上游 `flatMap` 崩溃缺陷:洋葱圈协调器在自主合成与原地替换 `agent-instructions` 消息时,严格强制为 `source` 提供合法数组 `changes: []`,彻底消除官方插件在后续步(多 step)触发 baseline 替换时调用 `visibleBaseline.changes.flatMap` 抛出的 `Cannot read properties of undefined (reading 'flatMap')` 崩溃。
10
- - 新增专项单测:严密断言注入消息的 `source.changes` 严格为数组且支持 `.flatMap()`。
10
+ ## [0.2.3] - 2026-11-14
11
11
 
12
- ## [0.1.1] - 2026-10-28
12
+ ### Fixed
13
13
 
14
- ### 新增
14
+ - `tools/result` 监听器签名错误。事件的真实签名是 `(exec, result)`,工具名在 `exec.name`;旧代码把它当单参事件对象读 `event.tool.name`,该字段不存在,判断恒为 false。结果是改完 `AGENTS.md` / `CLAUDE.md` 后渲染缓存不失效,注入的仍是旧内容。工具清单同时改为官方 `dsh-tool-fs` 的 `write` / `edit` / `apply_patch`,删掉并不存在的 `ops_replace`。
15
+ - `remote.getGlobalInfo` 硬编码 `'auto'`,忽略用户的 `globalPreference`。偏好设为 `claude` 且两份文件都存在时,Web 端编辑器显示 `AGENTS.md`,引擎实际加载 `CLAUDE.md`。
16
+ - `resolveWorkspaceDir` 的 `catch {}` 吞掉 `workingDirectory.ensure` 抛出的 `AbortError`,取消信号失效。改为只对该错误继续向上传播,其余异常仍静默回退。
15
17
 
16
- - 全局指令编辑器只读选择语义:点击未创建的全局文件(如缺失的 `CLAUDE.md`)时,前端编辑器直接显示空白草稿,杜绝在点击时自动克隆或创建占位脏文件。
17
- - 保存时自动创建:在空白草稿中输入内容点击「保存更改」后,底层临时文件与原子 rename 会自动新建并持久化该全局文件,并原位刷新当前选中文件状态。
18
+ ## [0.2.2] - 2026-11-13
18
19
 
19
- ### 优化
20
+ ### Fixed
20
21
 
21
- - `switchGlobalFilename` 纯只读化:移除 `copyExisting` 逻辑,返回完整的目标文件存在性(`exists`)、内容(`content`)与字节数(`bytes`)。
22
- - 前端 Store 状态闭环:保存成功后链式触发目标项刷新,防止保存后跳回默认选项。
23
- - 完善 CI 工作流与测试断言:覆盖缺失文件空内容只读呈现与保存自动创建的专项单测及 E2E 验证。
22
+ - reject 守卫:决策被拒绝时不再对空 messages 崩溃,对齐官方 agent-instructions 的类型契约
23
+ - 逐条消息去重:增量 delta 消息不再被整轮去重误伤,四层截断对增量消息同样生效
24
24
 
25
- ## [0.1.0] - 2026-10-27
25
+ ## [0.2.1] - 2026-10-31
26
+
27
+ ### Added
28
+
29
+ - 正式插件图标「漂游灯笼」,体积压缩至 196 KiB
30
+ - package.json 声明 icon 字段并加入发布白名单
31
+
32
+ ### Changed
33
+
34
+ - 未选中的历史参考图归档至 archive-icons
35
+
36
+ ## [0.2.0] - 2026-10-31
37
+
38
+ ### Changed
39
+
40
+ - 缓存失效:写文件仅清渲染缓存,不再回滚动态配置
41
+ - 存储与传输解耦:落盘逻辑下沉为独立领域函数,RPC 层变薄适配器
42
+ - 配置解析统一:volatile 访问器与裸对象统一收敛到 resolveConfig
43
+ - 渲染缓存指纹升级为内容摘要复合签名,等长文本修改也能正确感知
44
+
45
+ ### Removed
46
+
47
+ - 无调用者的 getAgentsHomeDir 导出
48
+ - 构建脚本手工复制默认值,改为复用单一事实来源
26
49
 
27
- 首个发布版本。完成官方范式重构与隔离实例全量验收。
50
+ ## [0.1.3] - 2026-10-29
28
51
 
29
- ### 新增
52
+ ### Fixed
30
53
 
31
- - 四层独立截断策略:全局(`globalPolicy`)、项目根 `CLAUDE.md`(`projectClaudePolicy`)、项目根 `AGENTS.md`(`projectAgentsPolicy`)、子目录(`subdirectoriesPolicy`),每层支持 `unlimited` / `truncate` / `disabled`。
32
- - 全局文件偏好:`auto` / `agents` / `claude` / `both`,打破官方硬编码 `USER_GLOBAL_FILE='AGENTS.md'` 的限制。
33
- - 浏览器内在线编辑全局指令文件(`AGENTS.md` / `CLAUDE.md`),经 RPC 服务原子落盘。
34
- - 按官方范式重构:`Config` Schema(schemastery,全字段 volatile、无默认值)、`InstructionsGuardService extends TypertRemoteService` + `@Remote`、`ctx.plugin` 挂载、`scripts/build-client.mjs` 生成 ModuleLoader 客户端产物、`configForms.whileServed + plugins.bundle.config` 配置面板。
35
- - 配置写入统一为官方 `{op:'set'|'unset'}` 通道,宿主持久化回写 `cordis.patch.yml` 并带 revision 冲突保护。
54
+ - 主目录边界隔离:寻根算法到 os.homedir() 强制中止,禁止把主目录当项目根
55
+ - 工作目录为主目录时排除 ~/CLAUDE.md,仅加载全局指令
56
+
57
+ ## [0.1.2] - 2026-10-28
58
+
59
+ ### Fixed
60
+
61
+ - 修复上游 flatMap 崩溃:合成消息强制提供 changes 数组
62
+
63
+ ## [0.1.1] - 2026-10-28
64
+
65
+ ### Added
66
+
67
+ - 全局指令编辑器只读选择语义:点击未创建文件显示空白草稿,不自动创建
68
+ - 保存时自动创建缺失文件并刷新选中状态
69
+
70
+ ### Changed
71
+
72
+ - switchGlobalFilename 纯只读化,返回存在性、内容与字节数
73
+
74
+ ## [0.1.0] - 2026-10-27
36
75
 
37
- ### 修复
76
+ ### Added
38
77
 
39
- - 插件"装配但未激活"根因:缺失 `Config` Schema 导致 settings 命名空间缺失;客户端产物非 ModuleLoader 形态被 client-runner 跳过;错误使用 `typertRemote.register` 手写注册。
40
- - `saveGlobalContent` 增加强校验:拒绝非字符串内容,杜绝空载荷覆写用户全局文件。
78
+ - 四层独立截断策略:全局、项目 CLAUDE.md、项目 AGENTS.md、子目录,各层支持 unlimited / truncate / disabled
79
+ - 全局文件偏好:auto / agents / claude / both
80
+ - 浏览器内在线编辑全局指令文件,经 RPC 原子落盘
81
+ - 配置面板与客户端产物遵循官方范式
41
82
 
42
- ### 验证
83
+ ### Fixed
43
84
 
44
- - 13 项单测全绿;隔离环境全功能验收(E2E)10 项标准全过。
45
- - 隔离实例真机验收:settings 命名空间出现 `dsh-ctxlift`、浏览器面板渲染、编辑器保存落盘字节校验、四层截断策略独立生效(17/17)、真实 agent 会话三态(truncate / disabled / unlimited)实测。
46
- - 冷装 / 热装 / 热卸载验收通过。
85
+ - 插件装配但未激活的三个根因:缺 Config Schema、客户端产物非 ModuleLoader 形态、错误的手写注册
86
+ - 保存内容强校验,拒绝非字符串载荷
package/CONTRIBUTING.md CHANGED
@@ -6,8 +6,9 @@
6
6
 
7
7
  - Node >= 20,pnpm >= 9
8
8
  - 克隆后 `pnpm install`(会安装 dev 与 peer 依赖)
9
- - `pnpm typecheck` / `pnpm build` / `pnpm test` 分别对应类型检查、构建(tsc + build-client)与单测
10
- - 隔离环境全功能验收:`node test/e2e/isolated-real-acceptance.mjs`
9
+ - `pnpm typecheck` / `pnpm build` / `pnpm test` 分别对应类型检查、构建(tsc + build-client)与单测(21 项,含图标规范)
10
+ - 隔离环境全功能验收:`pnpm test:e2e`(6 大区 10 项指标)
11
+ - 实机验收套件:`node test/verify-real-instance.mjs`(5 大域 12 项断言,含 RPC 原子落盘、SHA-1 复合指纹、ModuleLoader 契约)
11
12
 
12
13
  ## 提交规范
13
14
 
package/README.md CHANGED
@@ -24,6 +24,8 @@ The official `agent-instructions` plugin shares one global byte budget across al
24
24
  - Global file preference: `auto` / `agents` / `claude` / `both` — breaks the official hardcoded `USER_GLOBAL_FILE='AGENTS.md'` limit and supports a global `CLAUDE.md` (or both files at once).
25
25
  - In-browser editor for global instruction files (`AGENTS.md` / `CLAUDE.md`) with atomic writes through an RPC service. Missing files open as clean empty drafts with zero disk side effects and are created automatically on save.
26
26
  - Onion-skin replacement of the official `agent-instructions` messages on every agent step, so your policies take effect in real sessions.
27
+ - Official drifting lantern plugin icon: 512x512 Retina transparent PNG under 200 KiB, strictly complying with DSH 256 KiB `MAX_ICON_BYTES` limit and auto-converted by host to Base64 data URL.
28
+ - Home directory boundary isolation: project root traversal stops at `os.homedir()` and never treats the home directory as a project root, preventing unintended loading of `~/CLAUDE.md` when starting sessions from the user home.
27
29
 
28
30
  ## Install
29
31
 
@@ -64,14 +66,18 @@ src/
64
66
  ├── index.ts Host entry: exports { name, inject, Config, apply };
65
67
  │ assembles the coordinator and mounts the RPC service via ctx.plugin
66
68
  ├── config.ts Config Schema (schemastery, all fields volatile, no defaults);
67
- │ DEFAULT_CONFIG + resolveConfig for the engine
68
- ├── remote.ts InstructionsGuardService extends TypertRemoteService,
69
- │ @Remote('getGlobalInfo'|'saveGlobalContent'|'switchGlobalFilename')
70
- └── engine/ render.ts (UTF-8-safe truncation), files.ts (discovery),
71
- coordinator.ts (agent/pre-step onion-skin replacement)
69
+ │ DEFAULT_CONFIG + resolveConfig (natively unwraps volatile accessors)
70
+ ├── remote.ts InstructionsGuardService extends TypertRemoteService, a thin RPC
71
+ │ adapter: @Remote('getGlobalInfo'|'saveGlobalContent'|'switchGlobalFilename')
72
+ └── engine/
73
+ ├── render.ts UTF-8-safe truncation and granular instruction rendering
74
+ ├── files.ts discovery, home boundary isolation, and atomic persistence
75
+ │ (saveGlobalInstructionFile keeps fs out of the RPC layer)
76
+ └── coordinator.ts agent/pre-step onion-skin replacement, invalidateCache(),
77
+ SHA-1 composite cache signature (workspaceDir + path + length + digest)
72
78
  scripts/
73
- └── build-client.mjs Generates lib/client.js as a window.__ModuleLoader__.load bundle
74
- that $mounts the remote descriptors and registers the config panel
79
+ └── build-client.mjs Generates lib/client.js as a window.__ModuleLoader__.load bundle;
80
+ reuses DEFAULT_CONFIG from lib/config.js as the single source of truth
75
81
  locale/ zh.json / en.json
76
82
  ```
77
83
 
@@ -82,6 +88,7 @@ Key contracts (verified against DSH source):
82
88
  - Host RPC endpoints are discovered automatically from the `TypertRemoteService` subclass mounted via `ctx.plugin(...)`.
83
89
  - The client bundle must explicitly `ctx.remote.$mount(...)` its descriptors — the host only mounts the built-in official contributions.
84
90
  - The client needs `inject: ['slots', 'locale', 'configForms', 'remote']`.
91
+ - Cache invalidation is explicit: `tools/result` calls `coordinator.invalidateCache()`, never `updateConfig(...)`, so dynamic volatile config is never reverted to the startup closure value.
85
92
 
86
93
  ## Development
87
94
 
@@ -89,8 +96,9 @@ Key contracts (verified against DSH source):
89
96
  pnpm install # installs dev + peer dependencies
90
97
  pnpm typecheck # tsc --noEmit
91
98
  pnpm build # tsc into lib/, scripts/build-client.mjs generates lib/client.js
92
- pnpm test # node --test test/*.test.mjs (13 unit tests)
93
- node test/e2e/isolated-real-acceptance.mjs # isolated full-function acceptance
99
+ pnpm test # node --test test/*.test.mjs (21 unit tests, including icon contract)
100
+ pnpm test:e2e # isolated full-function acceptance (6 areas, 10 criteria)
101
+ node test/verify-real-instance.mjs # real-instance suite (5 domains, 12 assertions)
94
102
  ```
95
103
 
96
104
  ## Compatibility
package/README.zh-CN.md CHANGED
@@ -24,6 +24,7 @@
24
24
  - 全局文件偏好:`auto` / `agents` / `claude` / `both`——打破官方硬编码 `USER_GLOBAL_FILE='AGENTS.md'` 的限制,支持全局 `CLAUDE.md`(或两者同时注入)。
25
25
  - 浏览器内在线编辑全局指令文件(`AGENTS.md` / `CLAUDE.md`),经 RPC 服务原子落盘。未创建的文件以空白草稿呈现(纯只读无副作用),保存时自动新建。
26
26
  - 每个 agent 步骤上以洋葱圈方式替换官方 `agent-instructions` 消息,策略在真实会话中生效。
27
+ - 主目录边界隔离(Home Directory Boundary Isolation):向上寻根探测到 `os.homedir()` 立即截断且绝不将主目录本身判定为项目根,彻底防止从主目录启动会话时误载 `~/CLAUDE.md`。
27
28
 
28
29
  ## 安装
29
30
 
@@ -64,14 +65,17 @@ src/
64
65
  ├── index.ts Host 入口:导出 { name, inject, Config, apply };
65
66
  │ 组装 coordinator 并通过 ctx.plugin 挂载 RPC 服务
66
67
  ├── config.ts Config Schema(schemastery,全字段 volatile、无默认值);
67
- │ DEFAULT_CONFIG + resolveConfig(引擎归一)
68
- ├── remote.ts InstructionsGuardService extends TypertRemoteService,
68
+ │ DEFAULT_CONFIG + resolveConfig(原生解包 volatile 访问器)
69
+ ├── remote.ts InstructionsGuardService 纯 RPC 薄适配器;
69
70
  │ @Remote('getGlobalInfo'|'saveGlobalContent'|'switchGlobalFilename')
70
- └── engine/ render.ts(UTF-8 安全截断)、files.ts(文件发现)、
71
- coordinator.ts(agent/pre-step 洋葱圈替换)
71
+ └── engine/
72
+ ├── render.ts UTF-8 安全截断与分层指令渲染
73
+ ├── files.ts 文件发现、主目录边界隔离,以及原子落盘
74
+ │ (saveGlobalInstructionFile 让 fs 远离 RPC 层)
75
+ └── coordinator.ts agent/pre-step 洋葱圈替换、invalidateCache()、SHA-1 复合指纹
72
76
  scripts/
73
- └── build-client.mjs 生成 lib/client.js(window.__ModuleLoader__.load 形态),
74
- $mount 远端描述符并注册配置面板
77
+ └── build-client.mjs 生成 lib/client.js(window.__ModuleLoader__.load 形态);
78
+ 直接复用 lib/config.js 的 DEFAULT_CONFIG 作为单一事实来源
75
79
  locale/ zh.json / en.json
76
80
  ```
77
81
 
@@ -82,6 +86,7 @@ locale/ zh.json / en.json
82
86
  - 宿主 RPC 端点由 `ctx.plugin(TypertRemoteService 子类)` 自动发现。
83
87
  - 客户端 bundle 必须显式 `ctx.remote.$mount(...)` 自己的描述符——宿主只挂载内置官方贡献。
84
88
  - 客户端需要 `inject: ['slots', 'locale', 'configForms', 'remote']`。
89
+ - 缓存失效显式化:`tools/result` 只调用 `coordinator.invalidateCache()`,绝不再用启动闭包值回滚动态 volatile 配置。
85
90
 
86
91
  ## 开发
87
92
 
@@ -89,8 +94,9 @@ locale/ zh.json / en.json
89
94
  pnpm install # 安装 dev + peer 依赖
90
95
  pnpm typecheck # tsc --noEmit
91
96
  pnpm build # tsc 输出 lib/,scripts/build-client.mjs 生成 lib/client.js
92
- pnpm test # node --test test/*.test.mjs(13 项单测)
93
- node test/e2e/isolated-real-acceptance.mjs # 隔离环境全功能验收
97
+ pnpm test # node --test test/*.test.mjs(21 项单测,含图标规范测试)
98
+ pnpm test:e2e # 隔离环境全功能验收(6 大区 10 项指标)
99
+ node test/verify-real-instance.mjs # 实机验收套件(5 大域 12 项断言)
94
100
  ```
95
101
 
96
102
  ## 兼容性
Binary file
package/lib/client.js CHANGED
@@ -12,8 +12,8 @@ window.__ModuleLoader__.load({
12
12
  var PKG = "dsh-ctxlift";
13
13
  var LAYERS = ["globalPolicy","projectClaudePolicy","projectAgentsPolicy","subdirectoriesPolicy"];
14
14
  var DEFAULTS = {"globalPreference":"auto","globalPolicy":{"mode":"unlimited","maxBytes":131072},"projectClaudePolicy":{"mode":"truncate","maxBytes":65536},"projectAgentsPolicy":{"mode":"truncate","maxBytes":65536},"subdirectoriesPolicy":{"mode":"truncate","maxBytes":32768}};
15
- var ZH = {"panel.title":"工作区指令精细化截断与全局管理","panel.description":"独立精细控制全局指令、项目根目录 CLAUDE.md 与 AGENTS.md 的截断策略,杜绝原生一刀切丢弃,并支持在 Web 端直接查看与编辑全局守则。","pref.title":"全局指令文件偏好","pref.description":"打破官方硬编码限制,支持全局使用 CLAUDE.md 或两者合并注入","pref.auto":"自动探测 (存在优先)","pref.claude":"全局仅使用 CLAUDE.md","pref.agents":"全局仅使用 AGENTS.md","pref.both":"两者同时载入 (AGENTS + CLAUDE)","policy.globalPolicy.title":"全局指令 (Global)","policy.globalPolicy.desc":"生效于所有会话与项目的全局工作守则(~/.dsh/)","policy.projectClaudePolicy.title":"项目根目录 CLAUDE.md","policy.projectClaudePolicy.desc":"项目专属指令核心记忆库","policy.projectAgentsPolicy.title":"项目根目录 AGENTS.md","policy.projectAgentsPolicy.desc":"项目专属代理规范库","policy.subdirectoriesPolicy.title":"子目录指令 (Subdirectories)","policy.subdirectoriesPolicy.desc":"深层子目录中的局部指令文件","policy.mode.unlimited":"不截断 (完整保留)","policy.mode.truncate":"截断 (按字节上限)","policy.mode.disabled":"禁用 (不注入)","policy.bytes":"字节","editor.title":"全局指令文件在线编辑器","editor.size":"大小: {n} 字节","editor.dirty":"未保存草稿","editor.save":"保存更改","editor.saving":"保存中...","editor.saved":"已保存","editor.error":"保存或加载异常: {msg}","editor.confirmSwitch":"当前有未保存的修改,切换文件将丢弃未保存的草稿,是否继续?","editor.placeholder":"在 ~/.dsh/{file} 中输入全局指令内容...","state.loading":"加载中...","state.readOnly":"当前环境只读,无法修改配置","toast.saved":"配置已保存","toast.rejected":"保存被拒绝,配置已被服务端更新"};
16
- var EN = {"panel.title":"Granular Workspace Instructions","panel.description":"Fine-grained truncation control over global instructions, project CLAUDE.md and AGENTS.md, plus a web editor for global instruction files.","pref.title":"Global Instruction Preference","pref.description":"Breaks the official hard-coded limit: global CLAUDE.md or both files can be injected","pref.auto":"Auto detect (existing wins)","pref.claude":"Global CLAUDE.md only","pref.agents":"Global AGENTS.md only","pref.both":"Load both (AGENTS + CLAUDE)","policy.globalPolicy.title":"Global Instructions","policy.globalPolicy.desc":"Global working rules effective in all sessions (~/.dsh/)","policy.projectClaudePolicy.title":"Project root CLAUDE.md","policy.projectClaudePolicy.desc":"Project-specific instruction memory","policy.projectAgentsPolicy.title":"Project root AGENTS.md","policy.projectAgentsPolicy.desc":"Project-specific agent rules","policy.subdirectoriesPolicy.title":"Subdirectory Instructions","policy.subdirectoriesPolicy.desc":"Local instruction files in deeper subdirectories","policy.mode.unlimited":"Unlimited (full)","policy.mode.truncate":"Truncate (byte budget)","policy.mode.disabled":"Disabled (not injected)","policy.bytes":"bytes","editor.title":"Global Instruction Editor","editor.size":"Size: {n} bytes","editor.dirty":"Unsaved draft","editor.save":"Save changes","editor.saving":"Saving...","editor.saved":"Saved","editor.error":"Save/load error: {msg}","editor.confirmSwitch":"Unsaved changes exist. Switching files will discard the draft. Continue?","editor.placeholder":"Type global instruction content in ~/.dsh/{file}...","state.loading":"Loading...","state.readOnly":"Read-only environment, cannot modify configuration","toast.saved":"Configuration saved","toast.rejected":"Save rejected, configuration was updated on the server"};
15
+ var ZH = {"panel.title":"工作区指令精细化截断与全局管理","panel.description":"独立精细控制全局指令、项目根目录 CLAUDE.md 与 AGENTS.md 的截断策略,杜绝原生一刀切丢弃,并支持在 Web 端直接查看与编辑全局守则。","pref.title":"全局指令文件偏好","pref.description":"打破官方硬编码限制,支持全局使用 CLAUDE.md 或两者合并注入","pref.auto":"自动探测 (存在优先)","pref.claude":"全局仅使用 CLAUDE.md","pref.agents":"全局仅使用 AGENTS.md","pref.both":"两者同时载入 (AGENTS + CLAUDE)","policy.globalPolicy.title":"全局指令 (Global)","policy.globalPolicy.desc":"生效于所有会话与项目的全局工作守则(~/.dsh/)","policy.projectClaudePolicy.title":"项目根目录 CLAUDE.md","policy.projectClaudePolicy.desc":"项目专属指令核心记忆库","policy.projectAgentsPolicy.title":"项目根目录 AGENTS.md","policy.projectAgentsPolicy.desc":"项目专属代理规范库","policy.subdirectoriesPolicy.title":"子目录指令 (Subdirectories)","policy.subdirectoriesPolicy.desc":"深层子目录中的局部指令文件","policy.mode.unlimited":"不截断 (完整保留)","policy.mode.truncate":"截断 (按字节上限)","policy.mode.disabled":"禁用 (不注入)","policy.bytes":"字节","editor.title":"全局指令文件在线编辑器","editor.size":"大小: {n} 字节","editor.dirty":"未保存草稿","editor.save":"保存更改","editor.saving":"保存中...","editor.saved":"已保存","editor.error":"保存或加载异常: {msg}","editor.confirmSwitch":"当前有未保存的修改,切换文件将丢弃未保存的草稿,是否继续?","editor.placeholder":"在 ~/.dsh/{file} 中输入全局指令内容...","state.loading":"加载中...","state.readOnly":"当前环境只读,无法修改配置","toast.saved":"配置已保存","toast.rejected":"保存被拒绝,配置已被服务端更新","config.sectionTitle":"指令截断与全局策略","config.dirty":"配置有未保存修改","config.save":"保存配置","config.reset":"放弃修改"};
16
+ var EN = {"panel.title":"Granular Workspace Instructions","panel.description":"Fine-grained truncation control over global instructions, project CLAUDE.md and AGENTS.md, plus a web editor for global instruction files.","pref.title":"Global Instruction Preference","pref.description":"Breaks the official hard-coded limit: global CLAUDE.md or both files can be injected","pref.auto":"Auto detect (existing wins)","pref.claude":"Global CLAUDE.md only","pref.agents":"Global AGENTS.md only","pref.both":"Load both (AGENTS + CLAUDE)","policy.globalPolicy.title":"Global Instructions","policy.globalPolicy.desc":"Global working rules effective in all sessions (~/.dsh/)","policy.projectClaudePolicy.title":"Project root CLAUDE.md","policy.projectClaudePolicy.desc":"Project-specific instruction memory","policy.projectAgentsPolicy.title":"Project root AGENTS.md","policy.projectAgentsPolicy.desc":"Project-specific agent rules","policy.subdirectoriesPolicy.title":"Subdirectory Instructions","policy.subdirectoriesPolicy.desc":"Local instruction files in deeper subdirectories","policy.mode.unlimited":"Unlimited (full)","policy.mode.truncate":"Truncate (byte budget)","policy.mode.disabled":"Disabled (not injected)","policy.bytes":"bytes","editor.title":"Global Instruction Editor","editor.size":"Size: {n} bytes","editor.dirty":"Unsaved draft","editor.save":"Save changes","editor.saving":"Saving...","editor.saved":"Saved","editor.error":"Save/load error: {msg}","editor.confirmSwitch":"Unsaved changes exist. Switching files will discard the draft. Continue?","editor.placeholder":"Type global instruction content in ~/.dsh/{file}...","state.loading":"Loading...","state.readOnly":"Read-only environment, cannot modify configuration","toast.saved":"Configuration saved","toast.rejected":"Save rejected, configuration was updated on the server","config.sectionTitle":"Instruction & Policy Settings","config.dirty":"Unsaved config changes","config.save":"Save Configuration","config.reset":"Discard Changes"};
17
17
 
18
18
  var localeCtx = null;
19
19
  function t(key, params) {
@@ -265,26 +265,89 @@ window.__ModuleLoader__.load({
265
265
 
266
266
  var dirty = draft !== base;
267
267
 
268
- var applyOps = React.useCallback(function (ops, message) {
268
+ function normalizeConfig(c) {
269
+ var raw = c || {};
270
+ var res = {
271
+ globalPreference: typeof raw.globalPreference === "string" ? raw.globalPreference : DEFAULTS.globalPreference
272
+ };
273
+ for (var i = 0; i < LAYERS.length; i++) {
274
+ res[LAYERS[i]] = policyOf(raw, LAYERS[i]);
275
+ }
276
+ return res;
277
+ }
278
+
279
+ var serverConfig = React.useMemo(function () {
280
+ return normalizeConfig(config);
281
+ }, [config]);
282
+
283
+ var configDraftPair = React.useState(null);
284
+ var configDraft = configDraftPair[0];
285
+ var setConfigDraft = configDraftPair[1];
286
+
287
+ var currentConfig = configDraft || serverConfig;
288
+
289
+ var configDirty = React.useMemo(function () {
290
+ if (!configDraft) return false;
291
+ if (configDraft.globalPreference !== serverConfig.globalPreference) return true;
292
+ for (var i = 0; i < LAYERS.length; i++) {
293
+ var l = LAYERS[i];
294
+ if (configDraft[l].mode !== serverConfig[l].mode) return true;
295
+ if (configDraft[l].maxBytes !== serverConfig[l].maxBytes) return true;
296
+ }
297
+ return false;
298
+ }, [configDraft, serverConfig]);
299
+
300
+ var setPreferenceDraft = function (pref) {
301
+ setConfigDraft(function (prev) {
302
+ var baseConfig = prev || serverConfig;
303
+ return Object.assign({}, baseConfig, { globalPreference: pref });
304
+ });
305
+ };
306
+
307
+ var writePolicyDraft = function (layer, next) {
308
+ setConfigDraft(function (prev) {
309
+ var baseConfig = prev || serverConfig;
310
+ var updated = Object.assign({}, baseConfig);
311
+ updated[layer] = { mode: next.mode, maxBytes: next.maxBytes };
312
+ return updated;
313
+ });
314
+ };
315
+
316
+ var saveConfig = function () {
317
+ if (!configDirty || busy || !writable) return;
318
+ var ops = [];
319
+ if (currentConfig.globalPreference !== serverConfig.globalPreference) {
320
+ ops.push({ op: "set", path: ["globalPreference"], value: currentConfig.globalPreference });
321
+ }
322
+ for (var i = 0; i < LAYERS.length; i++) {
323
+ var l = LAYERS[i];
324
+ if (currentConfig[l].mode !== serverConfig[l].mode || currentConfig[l].maxBytes !== serverConfig[l].maxBytes) {
325
+ ops.push({ op: "set", path: [l], value: currentConfig[l] });
326
+ }
327
+ }
328
+ if (ops.length === 0) {
329
+ setConfigDraft(null);
330
+ return;
331
+ }
269
332
  setBusy(true);
270
333
  setNote(null);
271
334
  props.mutate(ops, snapshot.revision).then(function (landed) {
272
335
  setBusy(false);
273
- setNote(landed ? message : t("toast.rejected"));
336
+ if (landed) {
337
+ setConfigDraft(null);
338
+ setNote(t("toast.saved"));
339
+ } else {
340
+ setNote(t("toast.rejected"));
341
+ }
274
342
  });
275
- }, [props.mutate, snapshot.revision]);
276
-
277
- var setPreference = function (pref) {
278
- applyOps([{ op: "set", path: ["globalPreference"], value: pref }], t("toast.saved"));
279
343
  };
280
344
 
281
- // volatile 对象字段整块写:一次 op 覆盖整层策略,避免嵌套路径的合并语义歧义
282
- var writePolicy = function (layer, next) {
283
- applyOps([{ op: "set", path: [layer], value: next }], t("toast.saved"));
345
+ var resetConfig = function () {
346
+ setConfigDraft(null);
284
347
  };
285
348
 
286
349
  var renderPolicyRow = function (layer) {
287
- var policy = policyOf(config, layer);
350
+ var policy = currentConfig[layer];
288
351
  var rowKey = "policy." + layer;
289
352
  return e(
290
353
  "div",
@@ -305,7 +368,7 @@ window.__ModuleLoader__.load({
305
368
  disabled: busy || !writable,
306
369
  style: L.select,
307
370
  onChange: function (event) {
308
- writePolicy(layer, { mode: event.target.value, maxBytes: policy.maxBytes });
371
+ writePolicyDraft(layer, { mode: event.target.value, maxBytes: policy.maxBytes });
309
372
  }
310
373
  },
311
374
  e("option", { value: "unlimited" }, t("policy.mode.unlimited")),
@@ -326,7 +389,7 @@ window.__ModuleLoader__.load({
326
389
  onChange: function (event) {
327
390
  var next = Number(event.target.value);
328
391
  if (!Number.isFinite(next) || next < 128) return;
329
- writePolicy(layer, { mode: policy.mode, maxBytes: Math.floor(next) });
392
+ writePolicyDraft(layer, { mode: policy.mode, maxBytes: Math.floor(next) });
330
393
  }
331
394
  }),
332
395
  e("span", { style: L.hint }, t("policy.bytes"))
@@ -366,6 +429,31 @@ window.__ModuleLoader__.load({
366
429
  e("h3", { style: { margin: "0", fontSize: "15px", fontWeight: "600", color: "var(--dsw-alias-label-primary)" } }, t("panel.title")),
367
430
  e("p", { style: L.hint }, t("panel.description")),
368
431
 
432
+ e(
433
+ "div",
434
+ { style: { display: "flex", justifyContent: "space-between", alignItems: "center", flexWrap: "wrap", gap: "8px", marginTop: "4px" } },
435
+ e("span", { style: L.title }, t("config.sectionTitle")),
436
+ e(
437
+ "div",
438
+ { style: { display: "flex", alignItems: "center", gap: "8px" } },
439
+ configDirty ? e("span", { style: L.warn }, t("config.dirty")) : null,
440
+ configDirty
441
+ ? e("button", {
442
+ type: "button",
443
+ disabled: busy || !writable,
444
+ style: L.select,
445
+ onClick: resetConfig
446
+ }, t("config.reset"))
447
+ : null,
448
+ e(P.Button, {
449
+ variant: "primary",
450
+ size: "sm",
451
+ disabled: busy || !configDirty || !writable,
452
+ onClick: saveConfig
453
+ }, t("config.save"))
454
+ )
455
+ ),
456
+
369
457
  e(
370
458
  "div",
371
459
  { style: L.row },
@@ -378,10 +466,10 @@ window.__ModuleLoader__.load({
378
466
  e(
379
467
  "select",
380
468
  {
381
- value: typeof config.globalPreference === "string" ? config.globalPreference : DEFAULTS.globalPreference,
469
+ value: currentConfig.globalPreference,
382
470
  disabled: busy || !writable,
383
471
  style: L.select,
384
- onChange: function (event) { setPreference(event.target.value); }
472
+ onChange: function (event) { setPreferenceDraft(event.target.value); }
385
473
  },
386
474
  e("option", { value: "auto" }, t("pref.auto")),
387
475
  e("option", { value: "claude" }, t("pref.claude")),
package/lib/config.d.ts CHANGED
@@ -120,12 +120,8 @@ export declare const Config: Schema<Schemastery.ObjectS<NoInfer<{
120
120
  * 所以读取一律走 readVolatile,这里的类型只是"用户写下的形状"。
121
121
  */
122
122
  export type Config = GranularInstructionsConfig;
123
- /**
124
- * 校验并合并默认配置,保证每个字段类型健全
125
- */
126
- export declare function resolveConfig(rawConfig?: Partial<GranularInstructionsConfig>): GranularInstructionsConfig;
127
123
  /** volatile 字段在宿主里是带 get() 的访问器,单测里也可能直接传裸值 */
128
- interface VolatileRef<T> {
124
+ export interface VolatileRef<T> {
129
125
  get(): T;
130
126
  }
131
127
  /**
@@ -134,9 +130,12 @@ interface VolatileRef<T> {
134
130
  */
135
131
  export declare function readVolatile<T>(value: VolatileRef<T> | T | undefined): T | undefined;
136
132
  /**
137
- * 把宿主交给 apply 的原始配置解开成普通对象,再交给 resolveConfig 归一。
138
- * 这是引擎唯一入口,保证四处 policy 都拿到确定的值。
133
+ * 校验并合并默认配置,保证每个字段类型健全。
134
+ * 深模块设计:原生吸收 volatile 访问器或普通对象,调用方无需在外部手动解包。
135
+ */
136
+ export declare function resolveConfig(rawConfig?: unknown): GranularInstructionsConfig;
137
+ /**
138
+ * 兼容旧入口:统一委托给具备深层解包能力的 resolveConfig
139
139
  */
140
- export declare function resolvePluginConfig(rawConfig?: Partial<GranularInstructionsConfig>): GranularInstructionsConfig;
141
- export {};
140
+ export declare const resolvePluginConfig: typeof resolveConfig;
142
141
  //# sourceMappingURL=config.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"config.d.ts","sourceRoot":"","sources":["../src/config.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;GAUG;AAEH,OAAO,MAAM,MAAM,0BAA0B,CAAC;AAE9C,kBAAkB;AAClB,MAAM,MAAM,YAAY,GAAG,WAAW,GAAG,UAAU,GAAG,UAAU,CAAC;AAEjE,iBAAiB;AACjB,MAAM,MAAM,oBAAoB,GAAG,MAAM,GAAG,QAAQ,GAAG,QAAQ,GAAG,MAAM,CAAC;AAEzE,eAAe;AACf,MAAM,WAAW,gBAAgB;IAC/B;;;;;OAKG;IACH,IAAI,EAAE,YAAY,CAAC;IACnB;;OAEG;IACH,QAAQ,CAAC,EAAE,MAAM,CAAC;CACnB;AAED,aAAa;AACb,MAAM,WAAW,0BAA0B;IACzC;;;;;;OAMG;IACH,gBAAgB,EAAE,oBAAoB,CAAC;IACvC,gBAAgB;IAChB,YAAY,EAAE,gBAAgB,CAAC;IAC/B,yBAAyB;IACzB,mBAAmB,EAAE,gBAAgB,CAAC;IACtC,yBAAyB;IACzB,mBAAmB,EAAE,gBAAgB,CAAC;IACtC,gBAAgB;IAChB,oBAAoB,EAAE,gBAAgB,CAAC;CACxC;AAED,qCAAqC;AACrC,eAAO,MAAM,cAAc,EAAE,0BAkB5B,CAAC;AAKF;;;;GAIG;AACH,eAAO,MAAM,MAAM;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;aAkBjB,CAAC;AAEH;;;GAGG;AACH,MAAM,MAAM,MAAM,GAAG,0BAA0B,CAAC;AAEhD;;GAEG;AACH,wBAAgB,aAAa,CAAC,SAAS,CAAC,EAAE,OAAO,CAAC,0BAA0B,CAAC,GAAG,0BAA0B,CAiCzG;AAED,+CAA+C;AAC/C,UAAU,WAAW,CAAC,CAAC;IACrB,GAAG,IAAI,CAAC,CAAC;CACV;AAED;;;GAGG;AACH,wBAAgB,YAAY,CAAC,CAAC,EAAE,KAAK,EAAE,WAAW,CAAC,CAAC,CAAC,GAAG,CAAC,GAAG,SAAS,GAAG,CAAC,GAAG,SAAS,CAKpF;AAED;;;GAGG;AACH,wBAAgB,mBAAmB,CACjC,SAAS,CAAC,EAAE,OAAO,CAAC,0BAA0B,CAAC,GAC9C,0BAA0B,CAY5B"}
1
+ {"version":3,"file":"config.d.ts","sourceRoot":"","sources":["../src/config.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;GAUG;AAEH,OAAO,MAAM,MAAM,0BAA0B,CAAC;AAE9C,kBAAkB;AAClB,MAAM,MAAM,YAAY,GAAG,WAAW,GAAG,UAAU,GAAG,UAAU,CAAC;AAEjE,iBAAiB;AACjB,MAAM,MAAM,oBAAoB,GAAG,MAAM,GAAG,QAAQ,GAAG,QAAQ,GAAG,MAAM,CAAC;AAEzE,eAAe;AACf,MAAM,WAAW,gBAAgB;IAC/B;;;;;OAKG;IACH,IAAI,EAAE,YAAY,CAAC;IACnB;;OAEG;IACH,QAAQ,CAAC,EAAE,MAAM,CAAC;CACnB;AAED,aAAa;AACb,MAAM,WAAW,0BAA0B;IACzC;;;;;;OAMG;IACH,gBAAgB,EAAE,oBAAoB,CAAC;IACvC,gBAAgB;IAChB,YAAY,EAAE,gBAAgB,CAAC;IAC/B,yBAAyB;IACzB,mBAAmB,EAAE,gBAAgB,CAAC;IACtC,yBAAyB;IACzB,mBAAmB,EAAE,gBAAgB,CAAC;IACtC,gBAAgB;IAChB,oBAAoB,EAAE,gBAAgB,CAAC;CACxC;AAED,qCAAqC;AACrC,eAAO,MAAM,cAAc,EAAE,0BAkB5B,CAAC;AAKF;;;;GAIG;AACH,eAAO,MAAM,MAAM;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;aAkBjB,CAAC;AAEH;;;GAGG;AACH,MAAM,MAAM,MAAM,GAAG,0BAA0B,CAAC;AAEhD,+CAA+C;AAC/C,MAAM,WAAW,WAAW,CAAC,CAAC;IAC5B,GAAG,IAAI,CAAC,CAAC;CACV;AAED;;;GAGG;AACH,wBAAgB,YAAY,CAAC,CAAC,EAAE,KAAK,EAAE,WAAW,CAAC,CAAC,CAAC,GAAG,CAAC,GAAG,SAAS,GAAG,CAAC,GAAG,SAAS,CAKpF;AAED;;;GAGG;AACH,wBAAgB,aAAa,CAAC,SAAS,CAAC,EAAE,OAAO,GAAG,0BAA0B,CAwC7E;AAED;;GAEG;AACH,eAAO,MAAM,mBAAmB,sBAAgB,CAAC"}
package/lib/config.js CHANGED
@@ -57,57 +57,51 @@ export const Config = Schema.object({
57
57
  }).required(false).volatile()
58
58
  });
59
59
  /**
60
- * 校验并合并默认配置,保证每个字段类型健全
60
+ * 解开一个 volatile 字段:有 get() 就调用,否则原样返回。
61
+ * 单测与 headless 宿主可能传裸值,两种形态都要认。
62
+ */
63
+ export function readVolatile(value) {
64
+ if (value && typeof value === 'object' && typeof value.get === 'function') {
65
+ return value.get();
66
+ }
67
+ return value;
68
+ }
69
+ /**
70
+ * 校验并合并默认配置,保证每个字段类型健全。
71
+ * 深模块设计:原生吸收 volatile 访问器或普通对象,调用方无需在外部手动解包。
61
72
  */
62
73
  export function resolveConfig(rawConfig) {
63
74
  if (!rawConfig || typeof rawConfig !== 'object') {
64
75
  return structuredClone(DEFAULT_CONFIG);
65
76
  }
66
- const resolvePolicy = (policy, fallback) => {
77
+ const raw = rawConfig;
78
+ const resolvePolicy = (rawPolicyInput, fallback) => {
79
+ const policy = readVolatile(rawPolicyInput);
67
80
  if (!policy || typeof policy !== 'object') {
68
81
  return { ...fallback };
69
82
  }
70
- const mode = VALID_MODES.includes(policy.mode) ? policy.mode : fallback.mode;
71
- const maxBytes = typeof policy.maxBytes === 'number' && policy.maxBytes > 0
72
- ? Math.floor(policy.maxBytes)
83
+ const rawMode = readVolatile(policy.mode);
84
+ const mode = VALID_MODES.includes(rawMode) ? rawMode : fallback.mode;
85
+ const rawMaxBytes = readVolatile(policy.maxBytes);
86
+ const maxBytes = typeof rawMaxBytes === 'number' && rawMaxBytes > 0
87
+ ? Math.floor(rawMaxBytes)
73
88
  : fallback.maxBytes;
74
89
  return { mode, maxBytes };
75
90
  };
76
- const globalPreference = VALID_PREFERENCES.includes(rawConfig.globalPreference)
77
- ? rawConfig.globalPreference
91
+ const rawPref = readVolatile(raw.globalPreference);
92
+ const globalPreference = VALID_PREFERENCES.includes(rawPref)
93
+ ? rawPref
78
94
  : DEFAULT_CONFIG.globalPreference;
79
95
  return {
80
96
  globalPreference,
81
- globalPolicy: resolvePolicy(rawConfig.globalPolicy, DEFAULT_CONFIG.globalPolicy),
82
- projectClaudePolicy: resolvePolicy(rawConfig.projectClaudePolicy, DEFAULT_CONFIG.projectClaudePolicy),
83
- projectAgentsPolicy: resolvePolicy(rawConfig.projectAgentsPolicy, DEFAULT_CONFIG.projectAgentsPolicy),
84
- subdirectoriesPolicy: resolvePolicy(rawConfig.subdirectoriesPolicy, DEFAULT_CONFIG.subdirectoriesPolicy)
97
+ globalPolicy: resolvePolicy(raw.globalPolicy, DEFAULT_CONFIG.globalPolicy),
98
+ projectClaudePolicy: resolvePolicy(raw.projectClaudePolicy, DEFAULT_CONFIG.projectClaudePolicy),
99
+ projectAgentsPolicy: resolvePolicy(raw.projectAgentsPolicy, DEFAULT_CONFIG.projectAgentsPolicy),
100
+ subdirectoriesPolicy: resolvePolicy(raw.subdirectoriesPolicy, DEFAULT_CONFIG.subdirectoriesPolicy)
85
101
  };
86
102
  }
87
103
  /**
88
- * 解开一个 volatile 字段:有 get() 就调用,否则原样返回。
89
- * 单测与 headless 宿主可能传裸值,两种形态都要认。
104
+ * 兼容旧入口:统一委托给具备深层解包能力的 resolveConfig
90
105
  */
91
- export function readVolatile(value) {
92
- if (value && typeof value === 'object' && typeof value.get === 'function') {
93
- return value.get();
94
- }
95
- return value;
96
- }
97
- /**
98
- * 把宿主交给 apply 的原始配置解开成普通对象,再交给 resolveConfig 归一。
99
- * 这是引擎唯一入口,保证四处 policy 都拿到确定的值。
100
- */
101
- export function resolvePluginConfig(rawConfig) {
102
- if (!rawConfig || typeof rawConfig !== 'object') {
103
- return resolveConfig();
104
- }
105
- return resolveConfig({
106
- globalPreference: readVolatile(rawConfig.globalPreference),
107
- globalPolicy: readVolatile(rawConfig.globalPolicy),
108
- projectClaudePolicy: readVolatile(rawConfig.projectClaudePolicy),
109
- projectAgentsPolicy: readVolatile(rawConfig.projectAgentsPolicy),
110
- subdirectoriesPolicy: readVolatile(rawConfig.subdirectoriesPolicy)
111
- });
112
- }
106
+ export const resolvePluginConfig = resolveConfig;
113
107
  //# sourceMappingURL=config.js.map
package/lib/config.js.map CHANGED
@@ -1 +1 @@
1
- {"version":3,"file":"config.js","sourceRoot":"","sources":["../src/config.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;GAUG;AAEH,OAAO,MAAM,MAAM,0BAA0B,CAAC;AA2C9C,qCAAqC;AACrC,MAAM,CAAC,MAAM,cAAc,GAA+B;IACxD,gBAAgB,EAAE,MAAM;IACxB,YAAY,EAAE;QACZ,IAAI,EAAE,WAAW;QACjB,QAAQ,EAAE,MAAM;KACjB;IACD,mBAAmB,EAAE;QACnB,IAAI,EAAE,UAAU;QAChB,QAAQ,EAAE,KAAK;KAChB;IACD,mBAAmB,EAAE;QACnB,IAAI,EAAE,UAAU;QAChB,QAAQ,EAAE,KAAK;KAChB;IACD,oBAAoB,EAAE;QACpB,IAAI,EAAE,UAAU;QAChB,QAAQ,EAAE,KAAK;KAChB;CACF,CAAC;AAEF,MAAM,WAAW,GAAmB,CAAC,WAAW,EAAE,UAAU,EAAE,UAAU,CAAC,CAAC;AAC1E,MAAM,iBAAiB,GAA2B,CAAC,MAAM,EAAE,QAAQ,EAAE,QAAQ,EAAE,MAAM,CAAC,CAAC;AAEvF;;;;GAIG;AACH,MAAM,CAAC,MAAM,MAAM,GAAG,MAAM,CAAC,MAAM,CAAC;IAClC,gBAAgB,EAAE,MAAM,CAAC,KAAK,CAAC,iBAAiB,CAAC,CAAC,QAAQ,CAAC,KAAK,CAAC,CAAC,QAAQ,EAAE;IAC5E,YAAY,EAAE,MAAM,CAAC,MAAM,CAAC;QAC1B,IAAI,EAAE,MAAM,CAAC,KAAK,CAAC,WAAW,CAAC,CAAC,QAAQ,CAAC,KAAK,CAAC;QAC/C,QAAQ,EAAE,MAAM,CAAC,OAAO,EAAE,CAAC,GAAG,CAAC,GAAG,CAAC,CAAC,QAAQ,CAAC,KAAK,CAAC;KACpD,CAAC,CAAC,QAAQ,CAAC,KAAK,CAAC,CAAC,QAAQ,EAAE;IAC7B,mBAAmB,EAAE,MAAM,CAAC,MAAM,CAAC;QACjC,IAAI,EAAE,MAAM,CAAC,KAAK,CAAC,WAAW,CAAC,CAAC,QAAQ,CAAC,KAAK,CAAC;QAC/C,QAAQ,EAAE,MAAM,CAAC,OAAO,EAAE,CAAC,GAAG,CAAC,GAAG,CAAC,CAAC,QAAQ,CAAC,KAAK,CAAC;KACpD,CAAC,CAAC,QAAQ,CAAC,KAAK,CAAC,CAAC,QAAQ,EAAE;IAC7B,mBAAmB,EAAE,MAAM,CAAC,MAAM,CAAC;QACjC,IAAI,EAAE,MAAM,CAAC,KAAK,CAAC,WAAW,CAAC,CAAC,QAAQ,CAAC,KAAK,CAAC;QAC/C,QAAQ,EAAE,MAAM,CAAC,OAAO,EAAE,CAAC,GAAG,CAAC,GAAG,CAAC,CAAC,QAAQ,CAAC,KAAK,CAAC;KACpD,CAAC,CAAC,QAAQ,CAAC,KAAK,CAAC,CAAC,QAAQ,EAAE;IAC7B,oBAAoB,EAAE,MAAM,CAAC,MAAM,CAAC;QAClC,IAAI,EAAE,MAAM,CAAC,KAAK,CAAC,WAAW,CAAC,CAAC,QAAQ,CAAC,KAAK,CAAC;QAC/C,QAAQ,EAAE,MAAM,CAAC,OAAO,EAAE,CAAC,GAAG,CAAC,GAAG,CAAC,CAAC,QAAQ,CAAC,KAAK,CAAC;KACpD,CAAC,CAAC,QAAQ,CAAC,KAAK,CAAC,CAAC,QAAQ,EAAE;CAC9B,CAAC,CAAC;AAQH;;GAEG;AACH,MAAM,UAAU,aAAa,CAAC,SAA+C;IAC3E,IAAI,CAAC,SAAS,IAAI,OAAO,SAAS,KAAK,QAAQ,EAAE,CAAC;QAChD,OAAO,eAAe,CAAC,cAAc,CAAC,CAAC;IACzC,CAAC;IAED,MAAM,aAAa,GAAG,CACpB,MAA6C,EAC7C,QAA0B,EACR,EAAE;QACpB,IAAI,CAAC,MAAM,IAAI,OAAO,MAAM,KAAK,QAAQ,EAAE,CAAC;YAC1C,OAAO,EAAE,GAAG,QAAQ,EAAE,CAAC;QACzB,CAAC;QAED,MAAM,IAAI,GAAG,WAAW,CAAC,QAAQ,CAAC,MAAM,CAAC,IAAoB,CAAC,CAAC,CAAC,CAAC,MAAM,CAAC,IAAK,CAAC,CAAC,CAAC,QAAQ,CAAC,IAAI,CAAC;QAC9F,MAAM,QAAQ,GACZ,OAAO,MAAM,CAAC,QAAQ,KAAK,QAAQ,IAAI,MAAM,CAAC,QAAQ,GAAG,CAAC;YACxD,CAAC,CAAC,IAAI,CAAC,KAAK,CAAC,MAAM,CAAC,QAAQ,CAAC;YAC7B,CAAC,CAAC,QAAQ,CAAC,QAAQ,CAAC;QAExB,OAAO,EAAE,IAAI,EAAE,QAAQ,EAAE,CAAC;IAC5B,CAAC,CAAC;IAEF,MAAM,gBAAgB,GAAG,iBAAiB,CAAC,QAAQ,CAAC,SAAS,CAAC,gBAAwC,CAAC;QACrG,CAAC,CAAC,SAAS,CAAC,gBAAiB;QAC7B,CAAC,CAAC,cAAc,CAAC,gBAAgB,CAAC;IAEpC,OAAO;QACL,gBAAgB;QAChB,YAAY,EAAE,aAAa,CAAC,SAAS,CAAC,YAAY,EAAE,cAAc,CAAC,YAAY,CAAC;QAChF,mBAAmB,EAAE,aAAa,CAAC,SAAS,CAAC,mBAAmB,EAAE,cAAc,CAAC,mBAAmB,CAAC;QACrG,mBAAmB,EAAE,aAAa,CAAC,SAAS,CAAC,mBAAmB,EAAE,cAAc,CAAC,mBAAmB,CAAC;QACrG,oBAAoB,EAAE,aAAa,CAAC,SAAS,CAAC,oBAAoB,EAAE,cAAc,CAAC,oBAAoB,CAAC;KACzG,CAAC;AACJ,CAAC;AAOD;;;GAGG;AACH,MAAM,UAAU,YAAY,CAAI,KAAqC;IACnE,IAAI,KAAK,IAAI,OAAO,KAAK,KAAK,QAAQ,IAAI,OAAQ,KAAwB,CAAC,GAAG,KAAK,UAAU,EAAE,CAAC;QAC9F,OAAQ,KAAwB,CAAC,GAAG,EAAE,CAAC;IACzC,CAAC;IACD,OAAO,KAAsB,CAAC;AAChC,CAAC;AAED;;;GAGG;AACH,MAAM,UAAU,mBAAmB,CACjC,SAA+C;IAE/C,IAAI,CAAC,SAAS,IAAI,OAAO,SAAS,KAAK,QAAQ,EAAE,CAAC;QAChD,OAAO,aAAa,EAAE,CAAC;IACzB,CAAC;IAED,OAAO,aAAa,CAAC;QACnB,gBAAgB,EAAE,YAAY,CAAC,SAAS,CAAC,gBAAuB,CAAC;QACjE,YAAY,EAAE,YAAY,CAAC,SAAS,CAAC,YAAmB,CAAC;QACzD,mBAAmB,EAAE,YAAY,CAAC,SAAS,CAAC,mBAA0B,CAAC;QACvE,mBAAmB,EAAE,YAAY,CAAC,SAAS,CAAC,mBAA0B,CAAC;QACvE,oBAAoB,EAAE,YAAY,CAAC,SAAS,CAAC,oBAA2B,CAAC;KAC1E,CAAC,CAAC;AACL,CAAC"}
1
+ {"version":3,"file":"config.js","sourceRoot":"","sources":["../src/config.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,19 @@
1
+ /**
2
+ * 渲染缓存失效判定:哪些工具执行结果会让 AGENTS.md/CLAUDE.md 的内容可能变化。
3
+ *
4
+ * tools/result 事件的真实签名是 (exec, result),工具名在 exec.name 上
5
+ * (dsh-tools/lib/types/events.d.ts:48 + contracts.d.ts 的 ToolExecution.name)。
6
+ * 旧实现读 event.tool.name 这类不存在的字段,判断恒为 false,缓存永不失效。
7
+ */
8
+ /** 会改动工作区指令文件的工具。取自官方 dsh-tool-fs 注册清单。 */
9
+ export declare const FILE_WRITE_TOOLS: ReadonlySet<string>;
10
+ /**
11
+ * 判断一次工具执行是否应触发指令渲染缓存失效。
12
+ *
13
+ * @param exec - tools/result 的第一个参数(ToolExecution 快照)
14
+ * @returns 命中写工具时为 true;空输入或未知工具为 false
15
+ */
16
+ export declare function shouldInvalidateCache(exec: {
17
+ name?: unknown;
18
+ } | undefined | null): boolean;
19
+ //# sourceMappingURL=cache-invalidation.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"cache-invalidation.d.ts","sourceRoot":"","sources":["../../src/engine/cache-invalidation.ts"],"names":[],"mappings":"AAAA;;;;;;GAMG;AAEH,2CAA2C;AAC3C,eAAO,MAAM,gBAAgB,EAAE,WAAW,CAAC,MAAM,CAI/C,CAAA;AAEF;;;;;GAKG;AACH,wBAAgB,qBAAqB,CAAC,IAAI,EAAE;IAAE,IAAI,CAAC,EAAE,OAAO,CAAA;CAAE,GAAG,SAAS,GAAG,IAAI,GAAG,OAAO,CAI1F"}
@@ -0,0 +1,26 @@
1
+ /**
2
+ * 渲染缓存失效判定:哪些工具执行结果会让 AGENTS.md/CLAUDE.md 的内容可能变化。
3
+ *
4
+ * tools/result 事件的真实签名是 (exec, result),工具名在 exec.name 上
5
+ * (dsh-tools/lib/types/events.d.ts:48 + contracts.d.ts 的 ToolExecution.name)。
6
+ * 旧实现读 event.tool.name 这类不存在的字段,判断恒为 false,缓存永不失效。
7
+ */
8
+ /** 会改动工作区指令文件的工具。取自官方 dsh-tool-fs 注册清单。 */
9
+ export const FILE_WRITE_TOOLS = new Set([
10
+ 'write',
11
+ 'edit',
12
+ 'apply_patch',
13
+ ]);
14
+ /**
15
+ * 判断一次工具执行是否应触发指令渲染缓存失效。
16
+ *
17
+ * @param exec - tools/result 的第一个参数(ToolExecution 快照)
18
+ * @returns 命中写工具时为 true;空输入或未知工具为 false
19
+ */
20
+ export function shouldInvalidateCache(exec) {
21
+ if (exec === undefined || exec === null)
22
+ return false;
23
+ const name = exec.name;
24
+ return typeof name === 'string' && FILE_WRITE_TOOLS.has(name);
25
+ }
26
+ //# sourceMappingURL=cache-invalidation.js.map