dsh-d365-kit 0.0.0-stage → 0.34.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.
Files changed (79) hide show
  1. package/CHANGELOG.md +308 -0
  2. package/CHANGELOG.zh-CN.md +307 -0
  3. package/INSTALL.md +94 -0
  4. package/INSTALL.zh-CN.md +93 -0
  5. package/LICENSE +21 -0
  6. package/README.md +327 -3
  7. package/README.zh-CN.md +327 -0
  8. package/client.js +1818 -0
  9. package/cordis.patch.yml +44 -0
  10. package/docs/en/architecture.md +172 -0
  11. package/docs/en/checks.md +184 -0
  12. package/docs/en/installation.md +151 -0
  13. package/docs/en/permissions.md +161 -0
  14. package/docs/en/scenarios.md +202 -0
  15. package/docs/en/tools.md +665 -0
  16. package/docs/en/troubleshooting.md +131 -0
  17. package/docs/zh/architecture.md +172 -0
  18. package/docs/zh/checks.md +184 -0
  19. package/docs/zh/installation.md +151 -0
  20. package/docs/zh/permissions.md +161 -0
  21. package/docs/zh/scenarios.md +202 -0
  22. package/docs/zh/tools.md +665 -0
  23. package/docs/zh/troubleshooting.md +131 -0
  24. package/examples/prompts.en.md +206 -0
  25. package/examples/prompts.zh-CN.md +206 -0
  26. package/examples/waivers.example.json +20 -0
  27. package/icon.svg +6 -0
  28. package/index.js +142 -0
  29. package/lib/auth.js +181 -0
  30. package/lib/client.js +496 -0
  31. package/lib/code-check.js +549 -0
  32. package/lib/config.js +323 -0
  33. package/lib/contract-history.js +49 -0
  34. package/lib/contract.js +583 -0
  35. package/lib/csharp-check.js +493 -0
  36. package/lib/data-migrate.js +1318 -0
  37. package/lib/data-policy.js +78 -0
  38. package/lib/error-codes.js +80 -0
  39. package/lib/errors.js +81 -0
  40. package/lib/gitlab.js +161 -0
  41. package/lib/health.js +205 -0
  42. package/lib/migrate.js +732 -0
  43. package/lib/preflight.js +116 -0
  44. package/lib/refs.js +191 -0
  45. package/lib/resources.js +320 -0
  46. package/lib/retention-defaults.js +17 -0
  47. package/lib/retention.js +384 -0
  48. package/lib/runtime.js +232 -0
  49. package/lib/settings-http.js +512 -0
  50. package/lib/settings-store.js +371 -0
  51. package/lib/skill.js +76 -0
  52. package/lib/snapshot.js +149 -0
  53. package/lib/tool-code-check.js +133 -0
  54. package/lib/tool-configure.js +169 -0
  55. package/lib/tool-contract.js +376 -0
  56. package/lib/tool-data-sync.js +559 -0
  57. package/lib/tool-data-write.js +203 -0
  58. package/lib/tool-data.js +282 -0
  59. package/lib/tool-diff.js +484 -0
  60. package/lib/tool-environments.js +106 -0
  61. package/lib/tool-flows.js +540 -0
  62. package/lib/tool-health.js +127 -0
  63. package/lib/tool-invoke.js +284 -0
  64. package/lib/tool-layers.js +497 -0
  65. package/lib/tool-metadata.js +417 -0
  66. package/lib/tool-migrate.js +102 -0
  67. package/lib/tool-refs.js +206 -0
  68. package/lib/tool-reports.js +299 -0
  69. package/lib/tool-sync.js +430 -0
  70. package/lib/tool-trace.js +420 -0
  71. package/lib/tool-write.js +563 -0
  72. package/lib/waivers.js +83 -0
  73. package/lib/write-actions.js +1374 -0
  74. package/lib/write-policy.js +199 -0
  75. package/lib/write-store.js +167 -0
  76. package/locale/en.json +68 -0
  77. package/locale/zh.json +68 -0
  78. package/package.json +58 -3
  79. package/skills/d365-troubleshooting/SKILL.md +115 -0
@@ -0,0 +1,307 @@
1
+ # 版本记录
2
+
3
+ **[English](CHANGELOG.md)** | 简体中文
4
+
5
+ 版本语义:修 bug 升 patch,加工具/加能力升 minor,改配置结构或改名升 major。
6
+
7
+ ## 0.34.1
8
+
9
+ `d365_flows show` 的触发器多一行「条件」。
10
+
11
+ - 之前只解析 `subscriptionRequest/filteringattributes`(监控哪些列),没解析 `subscriptionRequest/filterexpression`(真正的判断条件),于是「当记录创建,条件:`statuscode eq 1 and msdyn_activitytype eq 419550000`」这种最关键的过滤被漏掉;
12
+ - 实测 UAT 401 条云流里,带 `filterexpression` 的有二十多条,例如 `Contoso Evaluation Flow` 的四个 `and` 条件、`Contoso Insights Flow` 的 `msdyn_processedstage eq null and msdyn_opportunitypayload ne null`。
13
+
14
+ ## 0.34.0
15
+
16
+ 新工具 `d365_flows`:查 Power Automate 云流。
17
+
18
+ 云流在 Dataverse 里就是 `workflow` 表 `category = 5` 的行,所以它和经典工作流同表、同权限、走同一套认证——不新增任何权限闸,也不需要 Power Automate 的 API 权限。
19
+
20
+ - **`list`**:目录(名称、状态、owner、托管、修改时间),支持 `filter`;owner 名从 `systemusers`/`teams` 批量解析(`ownerid` 是多态查找,`$expand` 会被服务端拒绝);
21
+ - **`show`**:一条流程的骨架——触发器(含「当记录创建或更新,范围:组织,过滤列:…」这类细节)、动作数、涉及的表、连接器、连接引用,以及前 40 个动作的嵌套路径(`(否则)`、`(caseName)` 标出分支);
22
+ - **`refs`**:哪些云流会碰某张表。触发器或任意动作命中就列出行,`account` 在 DEV 274 条云流里命中 6 条、`opportunity` 命中 11 条。输出写明「扫描 N/M 条」,扫描被 `scanLimit` 截断时不会说成「没有」;
23
+ - **踩到的坑**:`refs` 最初显式传 `$top: 50`,Dataverse 把显式 `$top` 当成**总量**,正好返回 50 行且不给 `@odata.nextLink`,于是「仅扫 50 条」看起来像「扫完了、没有碰这张表」。现在不传 `$top`,交给分页探测;
24
+ - **`primaryentity` 为什么是 `none`**:云流不绑定单表,表名藏在 `clientdata` 每个动作的参数里(`entityName`),`refs` 就是把它们解析出来反查——这也是 `d365_metadata resource=workflows` 答不了「谁碰了这张表」的原因;
25
+ - **运行历史查不到**:失败在第几次、卡在哪个动作、耗时多少只在 Power Automate 服务侧(另一套 audience 与 `Flow.Read.All`)。这个工具只到「定义」层,文档里写清楚了;
26
+ - 没纳入解决方案的云流同样能看到(落在默认解决方案里,`ismanaged = false`)。
27
+
28
+ ## 0.33.2
29
+
30
+ 文档:工具参考与排查 skill 的开头各加一张「按问题查」的路由表。
31
+
32
+ - **`docs/zh/tools.md` / `docs/en/tools.md` 开头新增路由表**——16 个问题、对应工具,以及「容易误用成什么」(改一批记录该用 `d365_data_sync plan-update` 而不是 `d365_data_write`;「这个字段为什么不在表单上」该用 `d365_refs` + `d365_layers` 而不是只看 `attributes`;「两个环境差在哪」该用 `d365_sync` + `d365_diff` 而不是两边各查一遍对眼);
33
+ - **`skills/d365-troubleshooting/SKILL.md` 也带上同一张表**,排查时随 skill 一起加载,不用先查文档;
34
+ - 「只读与写入」的约定补上 `d365_invoke`(POST)也属于会改环境、需要确认的调用。
35
+
36
+ ## 0.33.1
37
+
38
+ 自检剩下的 medium 项——都是同一个小毛病的小一号版本:答案看着合理,但数据撑不住。
39
+
40
+ - **`d365_layers solution=` 原来在分页之后过滤**:层数超过 `top`(默认 500)时,明明存在的层会被报成「该解决方案没有层」。现在过滤进 `$filter`,服务端先筛再截(UAT 实测:`solution: "Active"` 正好返回 `contoso_taxcategoryos` 那一层 Active);
41
+ - **`d365_diff` 比较 `solution_components` 时用了每个组织各自分配的 GUID**,DEV↔Build 一次报出 22.7 万新增 + 21.8 万删除。现在按 `componenttype` 计数比较,报告里写明;同一次 DEV↔Build 变成「新增 0 / 删除 0 / 修改 362」(计数层面);
42
+ - **`d365_migrate` 把被导入的解决方案本身和 `Active` 伪解决方案**也算进「将被遮蔽的解决方案」。这两个都不会失去什么:被导入的那个是替换这些层,`Active` 根本不是解决方案;
43
+ - **`d365_code_check` 的「不确定匹配」分支不可达**:相近名返回 `assembly: null`,于是被报成「没有这个程序集」,「请确认映射」的提问从不触发。现在只有一个相近名时会带人工确认,多个相近名仍作为未解决列出;
44
+ - **HTTP 超时原来只管到响应头**:`timedFetch` 在 `fetch` 一解决就清了定时器,服务端发完响应头再卡住正文时,`response.text()` 不受超时约束。现在定时器保持到正文读完,卡住的正文报网络错误;
45
+ - **`d365_reports` 按报告文件计数**:每次分析写的是 `.json` + `.md` 一对,`reportKeep: 20` 实际只留 10 份,而且写完立即清理可能删掉同一对里的另一半。现在一对算一份,一起删;
46
+ - **选项集计划忽略 `language`**:`plan-add-options` / `plan-relabel` 固定写 `languageCode: 1033`,`UpdateOptionValue` 还带 `MergeLabels: false`,把其它语言的标签一起换成了英文。现在计划遵守 `language`(实测 `language: 2052` 落成 `languageCode: 2052`),改标签只合并到指定语言。
47
+
48
+ ## 0.33.0
49
+
50
+ 全仓自检一遍(`lib/` 47 个模块 + `index.js` + 1.7K 行的 `client.js`),修掉十几个「不报错、但给出错误结论」的 bug。
51
+
52
+ - **权限页「全部取消」原本无效**:一个环境全不勾选时整条被丢弃,而「没有条目」等于用默认授权(元数据读又回来了)。现在空数组照样写入,保存后按服务端重读,而不是相信自己的草稿;
53
+ - **保存设置页可能抹掉环境的凭据来源**:页面没有 `tenantIdEnv` / `clientIdEnv` / `note` 的输入框,服务端又把「请求里没有的字段」当删除。现在客户端把它们原样带回,服务端只改请求里出现的字段;
54
+ - **保存环境的接口没有同源校验**:其它写路由都要求 `x-d365-meta-write` 头,唯独写 URL 与 client secret 的这个没有。页面补上请求头,路由补上守卫;
55
+ - **执行确认卡写的与实际执行相反**:卡上只有四个可覆盖项,`保持 GUID` / `保留创建时间` / `抑制重复检测` 因此全显示「否」,实际执行却按计划里的「是」来。现在从计划的完整选项集出发;
56
+ - **批量更新第一次执行会「写成功又判失败」**:runs 目录只有迁移路径建过,批量更新写完所有 PATCH 后写执行记录 ENOENT,计划被标 failed。顺带:引用回填失败计入失败行而不是藏在结果里;`onMissingLookup: "fail"` 终于会拒绝——「这条记录有未解析引用」原本是按同一个策略算的,所以守卫永远看不到;
57
+ - **批量更新接受任意大小写的列名**,但只归一化了字段表、没归一化值,预览打印 `undefined`、PATCH 发的是计划里没显示的键。现在值按逻辑名归一;
58
+ - **`update-attribute` / `delete-attribute` / `update-entity` 忽略默认解决方案**:三个 planner 都校验了解决方案却不把 `solutionUniqueName` 放进 operation,设了默认解决方案时改动落到默认层。视图列调整另外补了列名校验,并改用函数式替换,layoutxml 里的 `$&` 不再被展开;
59
+ - **`d365_data` 把实体名原样拼进 `$filter`**(`odataString` 导入了却没用):名字里有单引号会被 400 吞成「实体不存在」,构造的名字还能扩大过滤范围。现在只接受逻辑名形态;
60
+ - **`d365_health` 没权限时报「0 个失败」**:三个作业查询都是 `catch(() => null)` 再 `?? 0`,缺 `data.read` / 403 时在排查工具上给出假的安全结论。现在三列显示 `—` 并写明原因;
61
+ - **`d365_trace` 只在 `get` 里屏蔽凭据**:`list` 与 `summary` 把原始 `exceptiondetails` 写进产物和对话。现在三个动作都屏蔽;
62
+ - **`d365_sync` 提供按实体取的资源却没有实体参数**(`attributes`、`keys`、`*_relationships`),会拼出 `LogicalName='undefined'`;现在跳过并在「问题」里说明。`d365_contract` 补上「已过期豁免」计数,`handler-missing` 改为按环境去重(以前同一个缺函数只在第一个环境报);
63
+ - 设置/配置 JSON 的临时文件名加了随机后缀:同一进程两次并发保存共用 `${path}.tmp-${pid}`,可能把彼此写了一半的内容改名落盘。
64
+
65
+ ## 0.32.1
66
+
67
+ 上一版把 skill 写好了,却让人手工复制到 `~/.dsh/skills/`——装插件不会带过去,等于没装。这一版改成插件自己注册。
68
+
69
+ - 新增 `lib/skill.js`:读包里的 `skills/d365-troubleshooting/SKILL.md`,解析 front matter,交给 Host 的 `skills` 服务注册(`source: "bundled"`、`modelInvocable`/`userInvocable` 都为真)。注册走 `ctx.inject(["skills"], …)` 晚绑定:宿主没有这个服务时静默跳过,老版本 Host 仍然只有工具;
70
+ - 因此 `install_bundle` 之后 skill 就在技能目录里,agent 按描述自动路由,用户不需要按名字调用;
71
+ - 卸载/禁用插件时注册随之撤销(技能目录里同步消失),不存在「删了插件、skill 还留着」的残留;
72
+ - INSTALL 中英两段从「手动复制」改成「装完即有;要改就改包里那份」;`package.json` 的 `files` 已包含 `skills/**`,发布包带得走。
73
+
74
+ ## 0.32.0
75
+
76
+ 这一版加的是 skill,不是工具:能力早就有,但「先看什么、后看什么」没写下来。
77
+
78
+ - 新增 `skills/d365-troubleshooting/SKILL.md`——插件失败的排查 skill:先确认环境、`data.read`、跟踪级别;先看异步作业再怀疑插件;用 `d365_trace summary` 把几百条失败归成签名;`get` 取一条完整调用;核对步骤到底注册在哪(消息 / 阶段 / 模式 / 过滤属性);最后用 `d365_invoke` 复现;
79
+ - 顺带把错误码表固化了(`0x80048d19` 请求体形状、`0x80040216` 记录不存在、`0x80040333` 重复检测、`0x80044150` 并发冲突、`0x80048423` 受管组件、`0x8004a0f6` 状态转换、`0x80060888` 不支持的属性),以及三个真花过时间的坑:成功执行的 trace 存空串、`$count` 在 5000 截断、元数据读与业务数据读是两道闸;
80
+ - 安装方式:复制到 `~/.dsh/skills/d365-troubleshooting/`(见 INSTALL)。就是纯 Markdown,按各自环境改。
81
+
82
+ ## 0.31.0
83
+
84
+ 批量改数:在一个环境里按显式 id 清单改多条记录的字段,形态与迁移一样——先只读预演,再人工确认。
85
+
86
+ - `d365_data_sync` 新增 `action=plan-update`:一个环境、一份显式 `ids`(去重,上限 1000 条)加一份 `values`(字段逻辑名 → 新值)。预演会读出现有值做 **before → after** 预览(前几条),列出不存在的 id,并让你确认「记录范围、更新字段、Bypass」三项;
87
+ - **故意不接受 `filter` / `fetchXml`**:按查询条件批量改没有撤销键。先用 `d365_data action=query` 查出 id,再传进来;
88
+ - 只支持标量字段:`values` 里出现 lookup 会以 `lookup-unsupported` 拒绝,因为 lookup 必须按导航属性名绑定(单条改走 `d365_data_write`);
89
+ - 执行沿用迁移那套保障:`plan-update` 要 `data.read`,`execute` 要 `data.write` 且人在确认卡片上批准;写请求按有界并发跑、默认带 `MSCRM.BypassCustomPluginExecution`,逐 id 的执行记录落在 `.d365/data-sync/runs`。只改不删。
90
+
91
+ ## 0.30.0
92
+
93
+ 这一版补的是「看现场」:能查一条记录、能调一条消息、能读插件跟踪日志。
94
+
95
+ - 新工具 `d365_trace`(读插件跟踪日志):`list` 列最近的失败、`get` 看某一条的异常全文与 messageblock、`summary` 按「插件类型 + 消息 + 表 + 异常首行」归并签名——300 条失败通常只有几个签名,签名一样就是同一个 bug 在反复发生。默认只看抛异常的记录;顺带修正一个真问题:**成功执行的 trace 存空串而不是 null**,只写 `exceptiondetails ne null` 会把成功执行也列成失败(实测 sit 168 小时从 300 条 / 25 个签名收敛到 104 条 / 7 个签名);
96
+ - `d365_trace` 会屏蔽插件写进 messageblock 的凭据(`secureConfig` 里的 `ClientSecret`、`Authorization: Bearer …`),输出与产物里都是 `***` 并告知屏蔽了几处。跟踪日志按业务数据处理,需要「读取业务数据」;
97
+ - 新工具 `d365_invoke`(调一条 action / function):返回 HTTP 状态与响应体,**失败不抛异常**,把状态码、Dataverse 错误码和响应体原样给出来——复现问题时这一步最有用。权限按方法分:`GET` function 走「读取业务数据」,`POST` action 走「调用消息」(`message.invoke`,默认关闭,每次都在对话里确认);
98
+ - `d365_data` 新增 `action=query`:自选列、排序、`$expand`、按主键取一条(支持带引号的备用键),标题区分「共 N 行 / 已列 N / 共 M 条 / 可能还有更多」。以前只有 count/group/sample,想看具体某几条得自己拼 OData 再绕过工具。
99
+
100
+ ## 0.29.0
101
+
102
+ 这一版修的是「看结构」时最容易误导人的几个地方:错误码被丢掉、清单被静默截断、派生列读不到、产物读不回来。
103
+
104
+ - 修复:`D365Error` 现在保留 Dataverse 错误码与响应体。以前 `0x80048d19` 只活在一行中文消息里,模型复制不出来也搜不到;现在错误对象带 `errorCode`、`body`,并在消息里附上该码的成因与下一步(`0x80048d19` 载荷形状、`0x80060888` 属性或查询参数不被支持、`0x80040333` 重复检测、`0x80044150` 并发冲突等);
105
+ - `d365_metadata` 新增 `step_images` 资源(插件步骤的注册镜像:镜像类型、别名、参与镜像的字段),`plugin_steps` 默认 `$expand` 插件类型与消息名——步骤的 `name` 经常是空的,以前摘要里认不出是哪一步;
106
+ - 清单型资源(`plugin_steps`、`plugin_types`、`step_images`、`attributes`)默认上限从 200 提到 1000。成熟组织的 `sdkmessageprocessingsteps` 实测 5000 条以上,200 行 + 按名称排序会让要找的步骤「看起来不存在」;
107
+ - 结果被截断时摘要改成「已列 N 条 / 共 M 条(M 取自服务端 `$count`,其上限 5000 显示为 ≥5000)」,不再只有一句「还有更多」;
108
+ - `d365_metadata` 支持按字段 keyed 读取并 cast 到派生类型(`attribute` + `cast`):`DefaultValue`、`OptionSet`、`Targets` 这些列不在 `AttributeMetadata` 基类型上,以前 select 它们只能拿到 400;
109
+ - 显式传 `select` 时摘要表格跟着显示这些列(以前固定按资源默认列渲染,"我要的 DefaultValue" 会被四列默认字段挡掉);
110
+ - `d365_health` 增加「插件跟踪」列(组织 `plugintracelogsetting`:关闭 / 仅异常 / 全部)。查不到跟踪日志最常见的原因就是它是关的,而这一列以前完全不可见;
111
+ - `d365_reports` 新增 `action=view`:按行窗口读回一个产物,支持 `match` 过滤、`offset`/`limit` 翻页、`wrap` 折行,`.json` 默认缩进展开;只允许读 `.d365` 目录内的文件,越界的绝对路径会被拒绝。以前只有「文件名 + 大小」,500 KB 的结果等于没保存。
112
+
113
+ ## 0.28.1
114
+
115
+ 迁移写入改用真实的导航属性名,设置页的语言也跟着 DSH 切了。
116
+
117
+ - 修复:`d365_data_sync` 原来拿字段逻辑名拼 `@odata.bind`,而 Web API 认的是关系的**导航属性名**。自定义关系里两者常常不同(`contoso_approverid` 要绑 `contoso_ApproverId`、`contoso_geographicunitid` 要绑 `contoso_GeographicUnitId`),这类字段原来 100% 报 `0x80048d19 … An undeclared property`,而预演看不出来。现在每个实体先从 `RelationshipDefinitions` 读一张「逻辑名 → 导航名」映射,写请求按导航名绑定;预演会把两者不同的字段单独列出来,读不到映射时退回逻辑名并在「需要注意」里说明;
118
+ - 修复:旧版本存下的待执行计划没有这张映射,`execute` 现在直接以 `stale-plan` 拒绝,提示重新 `plan`,不再把旧错误重复一遍;
119
+ - 写请求的 `content-type` 补上 `charset=utf-8`,非 ASCII 值不再依赖服务端猜编码;
120
+ - 修复:设置页原来在 `apply` 里只读一次 `ctx.get('locale')`,locale 服务晚到就永远拿不到,整页恒为英文。改成 `ctx.inject(['locale'])` 等它就绪,并用模块级订阅把语言变化推给已挂载的页面;词典也改成一次 `register('d365-meta', DICT)`。
121
+
122
+ ## 0.28.0
123
+
124
+ 删未受管层不再只限表和字段,`d365_layers` 认识的组件类型全部支持。
125
+
126
+ - `d365_write plan-remove-layer` 接受全部 11 种 `componentType`(`Entity`、`Attribute`、`OptionSet`、`SavedQuery`、`Workflow`、`SystemForm`、`WebResource`、`PluginType`、`PluginAssembly`、`SdkMessageProcessingStep`、`CustomAPI`);发给 `RemoveActiveCustomizations` 的数值 `ComponentType` 取自真实服务上的 `solutioncomponent.componenttype` 选项集,不是猜的(`CustomAPI` 用它所在表自身的 object type code,10519);
127
+ - 计划里先完整列出层栈,再说明删完由哪一层接管,删之前不用打开 JSON 也能核;
128
+ - 删除后的发布范围跟着组件走:表或字段发布该表,表单或视图发布其所属表,选项集发布该选项集,其余走全量发布——归属查不到时不猜,退回全量发布而不是发错范围;
129
+ - 修复:不归属任何表的表单,Dataverse 的 `objecttypecode` 会返回字面量 `none`,原先会被当成表名传进发布;现在按「不缩小发布范围」处理。
130
+
131
+ ## 0.27.0
132
+
133
+ 全局选项集能查层了,层输出说清了 `Active` 是什么,令牌也从「每个请求取一次」改成「并发只取一次」。
134
+
135
+ - `d365_layers` 支持 `componentType: OptionSet`:先用 `GlobalOptionSetDefinitions(Name='…')` 取 `MetadataId`,再按 `msdyn_solutioncomponentname = 'OptionSet'` 查层表。本地选项集跟着字段走,仍用 `componentType: Attribute` 加 `entity` 查;
136
+ - 层输出把未受管层标成 `Active(未受管)`,每个环境末尾明确一句:最上层是未受管层,还是这个环境根本没有未受管层。Active 层的更新时间显示「不记录」(它的 `msdyn_overwritetime` 恒为 `1900-01-01`,原来会渲染成一个假日期);
137
+ - `d365_metadata` 的 `option_sets` 新增 `name` 参数:一次 keyed 请求取一个,不用再拉全部 987 个;找不到返回 404,报「该环境没有名为 X 的全局选项集定义」;
138
+ - 给 `option_sets` 传 `filter` 会被当场拒绝(`filter-unsupported`),不再把原始的 `$filter is not supported` 抛给用户——该端点返回 HTTP 405;
139
+ - 取令牌改成并发合流:同一环境的并发请求共用一个令牌请求(实测 8 个并发原本发 8 次 OAuth,现在 1 次),提前刷新窗口从 1 分钟放宽到 2 分钟;
140
+ - `d365_layers` 的 description 现在列出全部 11 种支持的组件类型,传错 `componentType` 不必再靠一次失败去发现。
141
+
142
+ ## 0.26.0
143
+
144
+ 解决方案层现在可写了,但只开一种:移除未受管的自定义层。
145
+
146
+ - `d365_write` 新增 action `plan-remove-layer` 与能力 `layer.remove`(「移除自定义层」,高危、默认关闭):只有最上面那层显示为 `Active`(未命名的未受管层)时才删,删完由解决方案最后带来的那层重新生效;
147
+ - 计划里写出整条层栈、被删的层、以及接手的是哪一层(没有下层就是「系统定义」);执行仍走同一张确认卡片和同一次发布,发布范围收窄到受影响的表;
148
+ - 背后只有 `RemoveActiveCustomizations` 一个消息。最上层属于某个解决方案时直接拒绝(`not-active-layer`):那种定义归那个解决方案管,要么在解决方案里移除组件,要么发一个新 patch 覆盖它;
149
+ - `DeleteAndPromote` 与 `RemoveSolutionComponent` 明确不做;
150
+ - 层的解析函数(`COMPONENT_TYPES`、`resolveComponentId`、`buildComponentFilter`)从 `lib/tool-layers.js` 导出,读和写共用同一套解析;
151
+ - `RemoveActiveCustomizations` 加入元数据写白名单,所以这个操作要的是 `layer.remove`,而不是 `data.write`。
152
+
153
+ ## 0.25.0
154
+
155
+ 元数据读取变成一项独立权限,权限页也从一个宽表改成按环境分块。
156
+
157
+ - 新增能力 `metadata.read`(「读取元数据」),默认开启、排在权限页最前:关掉它连表、字段、表单、视图、层都读不到;至此每一次访问都会先查权限,请求没通过闸就到不了环境;
158
+ - `lib/client.js` 一处判定:元数据实体集要 `metadata.read`(`metadata-read-forbidden`),其余 `GET` 要 `data.read`(`data-read-forbidden`),非 `GET` 的写要 `data.write`(`data-write-forbidden`)——都在取令牌之前;
159
+ - 权限页每个环境单独一块、用分隔线隔开,块头显示该环境「已授权 x/y」;「元数据默认可读」的说明只在标题下出现一次;
160
+ - `write.environments` 的条目改为三态:没有条目 = 未配置(元数据默认可读)、空数组 = 全部关掉、列表 = 只开列出的这些。之前空数组在读取时会被丢掉,等于悄悄把默认值又打开了;
161
+ - `write.capabilityVersion`(2)用来区分「旧文件里的条目还没见过 metadata.read」:这些条目会被自动补上它,升级后行为不变。
162
+
163
+ ## 0.24.1
164
+
165
+ 第一次真写 550 条全部失败:每个请求都返回 `0x80048d19 … A 'PrimitiveValue' node with non-null value was found when trying to read the value of the property 'contoso_bg'`。一条也没写进去,修好后用同一个计划重跑。
166
+
167
+ - lookup 改为按 `@odata.bind` 导航属性发送 —— `"contoso_bg@odata.bind": "/contoso_geographicunits(<guid>)"` —— 而不是把裸 GUID 写进属性列;Dataverse 对任何 lookup 都拒绝后者;
168
+ - `ownerid` 同样用 `@odata.bind`,按源所有者实际类型指向 `/systemusers(...)` 或 `/teams(...)`,且只在所有者需要重映射时才写;
169
+ - 延后解析的 lookup(父记录创建后才拿到目标 GUID)不再塞进新建请求体,改为创建后用 `PATCH` 补;原代码只处理了普通列;
170
+ - 离线写路径测试改为断言请求体的真实形状,不再只判断「有某个键」——这正是裸 GUID 之前能混过去的原因。
171
+
172
+ ## 0.24.0
173
+
174
+ `d365_data_sync` 按真实测试的用法改预演流程:第一次真迁移(SIT → DEV)暴露了「不问条件就出全量计划」的问题。
175
+
176
+ - 预演报告必须列清三件事并请执行者确认:**迁移条件**(默认整表;数据量大时建议用 `since` / `filter` / `ids` / `fetchXml` 缩小)、**迁移字段**(默认全部;清单里含主键就等于保持 GUID 一致)、**是否 Bypass 自定义插件**(默认跳过);
177
+ - 不给条件不再报错:默认按整表预演,并在「需要注意」里说明读到多少条、是否已到 `maxRecords` 上限、其余未纳入本次计划;
178
+ - 预演报告新增「权限(execute 需要)」段:源读取 / 目标读取 / 目标写入各自是否已开启;缺 `data.write` 时当场提示去设置页「权限」勾「新增/修改业务数据」,而不是等 `execute` 才被拦下;
179
+ - Bypass 默认改为开(跳过目标环境的同步插件,避免触发下游通知/集成);`execute` 时也可以显式传 `bypassPlugins=false` 关掉。
180
+
181
+ ## 0.23.0
182
+
183
+ 新增 `d365_data_sync`:业务记录跨环境迁移。
184
+
185
+ - 两段式:`plan` 只读预演(落 `.d365/data-sync/plans`),`execute` 在确认卡片上批准后才写(记录落 `.d365/data-sync/runs`)。计划里写明数据量、字段清单、每个 lookup 的解析结果、owner 映射、冲突与「是否 Bypass 自定义插件」;
186
+ - 默认镜像:保持源 GUID(带 `overriddencreatedon` 保留创建时间、`MSCRM.SuppressDuplicateDetection`);`keepGuid=false` 时由目标分配主键,批内引用按依赖顺序重映射并在创建后二次 `PATCH` 补齐(环也能走通);
187
+ - lookup 先按 GUID 在目标找同一条,再按主名精确匹配;命不中记「缺失」,多处同名记「歧义」。多态 lookup 先在源环境确定它指向哪张表——`Targets` 在基础 `AttributeMetadata` 上不可选(0x80060888),改从 `LookupAttributeMetadata` 读;
188
+ - 范围必须明确(`since` / `filter` / `ids` / `fetchXml`,或 `mode=all` 配 `maxRecords`),范围不明直接报错要求补充,不猜「今天新增的」;
189
+ - 目标已存在同 GUID 默认只报告不写,`conflict="overwrite"` 才逐条 `PATCH`;缺失引用默认跳过这些记录,`null` 清空、`fail` 整体拒绝;
190
+ - 权限沿用两道闸:`plan` 要源和目标 `data.read`,`execute` 还要目标 `data.write`;只新增/修改,不删记录。
191
+
192
+ ## 0.22.2
193
+
194
+ 真实 UAT 环境上发现的问题修复。
195
+
196
+ - `d365_health` 不再把 Completed 的异步作业算成失败、把 Ready 算成挂起:`asyncoperation` 的 `statecode` 是 0 Ready / 1 Suspended / 3 Completed,失败要再看 `statuscode 31`(Completed 下还有 30 Succeeded、32 Canceled)。在 UAT 上「近期失败」从 59,293 降到 124——正是 `statecode eq 3 and statuscode eq 31` 的条数——失败清单也不再混入成功的作业(原先列出的某作业 577/577 行都是 statuscode 30)。Ready 的作业单独有「排队中」一列,失败行带上真实的 `operationtype`;
197
+ - `d365_metadata` 的 `workflows`(以及 `business_rules`)默认只返回定义行(`type eq 1`):Dataverse 给每个流程另存一条同名、`workflowid` 不同的激活行(`type 2`),于是每个流程出现两次、计数翻倍。显式传 `filter` 时默认过滤让位而不是 AND(所以 `type eq 2` 可用),`type` 也进入展示列;
198
+ - `d365_layers` 在多环境查询时,某个环境没有该组件不再让整次调用失败:键值元数据查询的 404 视为「该环境没有」,只在该环境报告,其它环境照常返回层并参与对比;全部环境都没有才报错;
199
+ - 产物路径改为绝对路径(`writeArtifact`),返回里打印的路径可以直接从会话工作区打开,不再是相对插件 cwd 的 `../../../Documents/...`。
200
+
201
+ ## 0.22.1
202
+
203
+ 按 npm 包的要求补齐。
204
+
205
+ - `package.json` 增加 `repository`、`homepage`、`bugs`、`keywords`、`author` 与 `publishConfig.access: public`,npm 页面上不会是一片空白;
206
+ - 文档把 npm 装法提到第一位(`install_bundle target="dsh-d365-kit@<版本>"`),Git 与本地目录作备选;`PUBLISH.md` 补了公网 npm 与内网 registry 两种 `npm publish` 写法。
207
+
208
+ ## 0.22.0
209
+
210
+ 配置可移植。
211
+
212
+ - Handler 检查不再内置项目前缀:`handlerPrefixes` 默认为空,改从源环境的未受管解决方案推断(占多数的发布者前缀,再加 `new_`)。合作方与示例解决方案被排除——它们压缩过的脚本会让每个 Handler 都像缺失;
213
+ - 漂移报告与工具输出会说明用了哪些前缀、来自配置还是自动推断;
214
+ - `d365.config.example.json` 增加 `handlerPrefixes`。
215
+
216
+ 已在真实环境验证:用自动推断出的前缀,Handler 检查结果与原先写死的前缀完全一致(同样 14 条)。
217
+
218
+ ## 0.21.0
219
+
220
+ 发布整理版。
221
+
222
+ - 文档重组:README 作为入口,新增 `docs/`(安装、工具参考、场景、权限、检查规则、故障排查、结构)与 `examples/`;
223
+ - 新增 `CHANGELOG.md`、根目录 `.gitignore`;
224
+ - 每份文档都有中英两版:`README.md` / `README.zh-CN.md`、`INSTALL.md` / `INSTALL.zh-CN.md`、`CHANGELOG.md` / `CHANGELOG.zh-CN.md`、`docs/en/` / `docs/zh/`(文件名一致)、`examples/prompts.en.md` / `examples/prompts.zh-CN.md`,每篇顶部都有语言切换;
225
+ - `d365.config.example.json` 补齐权限、保留策略、GitLab 段;
226
+ - `package.json` 的 `files` 加入文档与示例;
227
+ - 采用 MIT 许可证(`LICENSE` 与 `package.json` 的 `"license": "MIT"`)。
228
+
229
+ 代码与 0.20.0 一致,无行为变更。
230
+
231
+ ## 0.20.0
232
+
233
+ 改名为 `dsh-d365-kit`。
234
+
235
+ - 包名、bundle entry id、`index.js` 的 `name`、`client.js` 的模块 id 统一为 `dsh-d365-kit`;
236
+ 后两个是运行时自报名字,漏改会让加载器把同一模块执行两次,报 `duplicate factory registration` 并让整个设置页消失;
237
+ - 设置页标题改为「D365 工具箱 / D365 Toolkit」,描述覆盖读、写、检查三类能力;
238
+ - `locale/{zh,en}.json` 的 `tools` 补到 15 条;
239
+ - 新增 `INSTALL.md`;`cordis.patch.yml` 注释恢复 UTF-8。
240
+
241
+ ## 0.19.1
242
+
243
+ 审查后的四项修复。
244
+
245
+ - `/write/cancel` 检查计划状态:非 `pending` 一律 409,不再能删掉正在执行的计划;
246
+ - `/write/publish` 重新校验写权限与环境空闲,忙时返回 409 而不是原始 429;
247
+ - 删除文件的阻断只在确实还有表单引用时报;
248
+ - JS 引用分析拆成「字段 / 列 / 需人工判断」三组,字段缺失按字段聚合:所有表单都没有 = 阻断,只有部分没有 = 警告。
249
+
250
+ ## 0.19.0
251
+
252
+ - `executePlan` 增加计划状态机 `pending → running → done/failed`,第一次写入前落 `running`;工具与设置页都拒绝非 `pending` 计划;
253
+ - 重试策略按方法区分:`GET`/`HEAD` 在网络错误与 5xx 上重试,`POST`/`PATCH`/`DELETE` 只在 429 时重试;
254
+ - 表单 Handler 检查收敛:只看项目脚本、先查自己的库、预算 400 个脚本,误报从 306 条降到 14 条;
255
+ - `d365_reports` 的总数改用磁盘统计,截断时明确标注;
256
+ - 健康检查不再渲染空的失败原因。
257
+
258
+ ## 0.18.0
259
+
260
+ 数据治理。
261
+
262
+ - 保留策略:快照 / 报告 / diff / 查询留证 / 变更记录各有份数与天数上限,写产物后自动清理;
263
+ - `d365_reports` 增加 `cleanup`(默认预演)与 `compact`(缩进 JSON 压成单行);
264
+ - 设置页新增「数据」标签:占用、每类明细、预演与执行、压缩;
265
+ - 快照写入改为单行 JSON,实测四份快照省 68 MB;
266
+ - 修复设置页某路由未声明请求体模式导致 GET 返回裸 400 的问题。
267
+
268
+ ## 0.17.0
269
+
270
+ - 契约豁免(`waivers.json`,必须有过期时间)与漂移趋势(`contract-history.json`);
271
+ - 契约检查补充表单引用的 Web 资源、站点地图、表单 Handler;
272
+ - 新增 `d365_health`(挂起/失败作业、正在进行的导入发布);
273
+ - 新增 `d365_reports`(产物索引);
274
+ - 同一 `message|stage` 多步骤时提示 rank 冲突;JS 取值写法扩到 9 种;
275
+ - 快照过期提示、权限「复制到其他环境」。
276
+
277
+ ## 0.16.0
278
+
279
+ 契约矩阵与漂移引擎 `d365_contract`:程序集、插件类型、步骤(消息 + stage + mode)、选项值。
280
+
281
+ ## 0.14.0 – 0.15.0
282
+
283
+ - `data.write` 与 `d365_data_write`:新建/修改业务记录,需授权且每次确认,审计不落值;
284
+ - `d365_code_check` 增加 C# 分支:程序集精确匹配、类型/步骤/镜像/过滤属性核对,共享工程降为警告。
285
+
286
+ ## 0.11.0 – 0.13.0
287
+
288
+ - `d365_code_check`:GitLab MR → Web 资源 → 表单字段/Handler 核对,输出阻断/警告/人工确认;
289
+ - 设置页新增 GitLab 标签(地址、默认项目、只读令牌、连通性测试);
290
+ - `d365_refs`:字段 / Web 资源 / 程序集的反向索引;
291
+ - `d365_data`:count / group by / sample,受 `data.read` 管控。
292
+
293
+ ## 0.7.0 – 0.10.0
294
+
295
+ - 设置页改为六标签(环境、权限、计划、数据、GitLab、常规),语言跟随 DSH;
296
+ - 执行前检查:环境正在导入/发布时拒绝写入;
297
+ - 只发布受影响的组件(`PublishXml` 而非 `PublishAllXml`),后台执行并记录结果;
298
+ - 迁移评估 `d365_migrate`:层遮蔽、缺失、选项值丢失、版本与受管状态。
299
+
300
+ ## 0.1.0 – 0.6.0
301
+
302
+ - 元数据查询(19 类资源)、多环境对齐;
303
+ - 解决方案层 `d365_layers`(虚拟实体,必须组件 id + 类型);
304
+ - 快照 `d365_sync` 与对比 `d365_diff`(manifest 记录完整性、噪声过滤);
305
+ - 环境配置与设置页;
306
+ - 写入能力:选项集增/改/删值、字段增/改/删、表显示名、系统视图列;计划 → 人工确认 → 执行 → 审计;
307
+ - 修掉一批真实环境差异:`organization` 列缺失、元数据端点不支持 `$top`/`$orderby`、换用户后令牌缓存未失效、层表必须带类型。
package/INSTALL.md ADDED
@@ -0,0 +1,94 @@
1
+ # Install (for colleagues)
2
+
3
+ **English** | [简体中文](INSTALL.zh-CN.md)
4
+
5
+ `dsh-d365-kit` is a DeepSeek Harness bundle (host plugin + settings page). Once installed you get 15 `d365_*` tools and a "Settings → D365 Toolkit" page.
6
+
7
+ For the complete steps, see [docs/en/installation.md](docs/en/installation.md).
8
+
9
+ ## Prerequisites
10
+
11
+ - DSH desktop app, able to open the Web GUI;
12
+ - the machine can reach your Dataverse environment (`*.dynamics.com` or the regional domain);
13
+ - an **application user** (ClientId + secret) granted permission in the environment to read metadata,
14
+ read solutions (`prvReadSolution`) and read the layer table (`prvReadmsdyn_componentlayer`). Read-only is
15
+ enough; add the relevant permissions when you need writes.
16
+
17
+ ## Install
18
+
19
+ ### Option 1: Install from npm (shortest, upgradeable)
20
+
21
+ ```
22
+ plugin_manager install_bundle target="dsh-d365-kit@0.34.1"
23
+ ```
24
+
25
+ This needs the package to be published to the registry pnpm uses — public npm or an internal mirror. The name has no scope, so no scope mapping is required.
26
+
27
+ > Publishing to GitHub does not put the package in a registry: with only a GitHub repo, use option 2 (or `npm install github:<user>/<repo>#<tag>`). Pushing a `v*` tag publishes automatically once the `NPM_TOKEN` secret exists (`.github/workflows/publish.yml`).
28
+
29
+ ### Option 2: Install from Git (no registry needed)
30
+
31
+ ```
32
+ plugin_manager install_bundle target="git+https://<your Git>/<group>/dsh-d365-kit.git#v0.34.1"
33
+ ```
34
+
35
+ ### Option 3: Install from a local directory
36
+
37
+ Put the whole repository directory anywhere on the machine:
38
+
39
+ ```
40
+ plugin_manager install_bundle target="D:\path\to\dsh-d365-kit"
41
+ ```
42
+
43
+ This is a `link:` install: code changes take effect immediately, but upgrading is your own `git pull`.
44
+
45
+ All paths are the same: **restart DSH once after installing**, so the Web client loads the settings page code into the module graph.
46
+
47
+ ## First-time configuration
48
+
49
+ 1. Settings → D365 Toolkit → Environments → Add environment: key (e.g. `dev`), URL, tenant ID, ClientId, client secret;
50
+ 2. Click "Test connection"; if you see a version number, the credentials are fine;
51
+ 3. On the "Permissions" tab, tick what this machine is allowed to do (one block per environment, separated by a rule):
52
+ - "Read metadata" — already ticked by default; untick it and tables, fields and views become unreadable too;
53
+ - "Read business data" — without it the records in tables are unreadable;
54
+ - write capabilities are all off by default, and even when ticked every execution still needs a person to confirm it in the conversation;
55
+ 4. Click "Save".
56
+
57
+ By default the configuration is written to `<your DSH workspace>/d365.config.json`, and the secret is stored separately in `d365.secrets.json` in the same directory
58
+ (plain text, don't commit it). If your workspace isn't in the default location, set the `D365_CONFIG_FILE` environment variable, or point `configFile` at an absolute path in the profile's
59
+ `cordis.patch.yml`.
60
+
61
+ ## Verify
62
+
63
+ ```
64
+ d365_envs check=true
65
+ d365_metadata resource=entities top=3
66
+ ```
67
+
68
+ If both return content, you're connected. The first `d365_sync` pulls the full metadata (about 125 MB, three minutes or so); if you don't want to spend the disk space, you can skip it for now.
69
+
70
+ ### The troubleshooting skill comes with the plugin
71
+
72
+ The package ships an agent skill at `skills/d365-troubleshooting/SKILL.md` (how to work a plug-in failure: trace level → signature → one full call → registration → reproduce). The plugin registers it into the Host skill registry when it loads, so installing the bundle is all you have to do — nothing to copy, and the agent picks it up by description without anyone invoking it by name.
73
+
74
+ To change it, edit the file in the installed package (or in your clone, when you installed from source) and reload the plugin; edits made anywhere else are not read.
75
+
76
+ ## Upgrade
77
+
78
+ ```
79
+ plugin_manager remove_bundle target=dsh-d365-kit
80
+ plugin_manager install_bundle target="...<new version>..."
81
+ ```
82
+
83
+ You must remove before installing: when the package already exists the installer only returns `already-installed` and does not replace the code. Restart DSH after upgrading.
84
+
85
+ ## FAQ
86
+
87
+ - **Tools don't appear**: run `plugin_manager list_plugins` and check whether `include:dsh-d365-kit` is `active`; a fresh install needs a restart.
88
+ - **No "D365 Toolkit" on the settings page**: the client plugin didn't make it into the module graph — restart; if it's still missing, check whether the module id in `client.js` matches the package name.
89
+ - **Save reports "writes can only be started from the settings page"**: that's the CSRF guard — only save from the settings page; rejections are logged to `.d365/write-guard.log`.
90
+ - **Reading business data reports `data-read-forbidden`**: tick "Read business data" on the Permissions tab.
91
+ - **A write is rejected with `environment-busy`**: the environment is importing or publishing; wait for it to finish and retry.
92
+ - **GitLab 403**: the token's user needs at least the Reporter role in that project.
93
+
94
+ For other error codes, see [docs/en/troubleshooting.md](docs/en/troubleshooting.md).
@@ -0,0 +1,93 @@
1
+ # 安装(给同事)
2
+
3
+ **[English](INSTALL.md)** | 简体中文
4
+
5
+ `dsh-d365-kit` 是 DeepSeek Harness 的 bundle(Host 插件 + 设置页)。装完会有 16 个 `d365_*` 工具和「设置 → D365 工具箱」页面。
6
+
7
+ 更完整的步骤见 [docs/installation.md](docs/zh/installation.md)。
8
+
9
+ ## 前置
10
+
11
+ - DSH 桌面版,能打开 Web GUI;
12
+ - 机器能访问你的 Dataverse 环境(`*.dynamics.com` 或对应区域域名);
13
+ - 一个**应用用户**(ClientId + 密钥),在环境里给了读元数据、读解决方案(`prvReadSolution`)、
14
+ 读层表(`prvReadmsdyn_componentlayer`)的权限。只读就够用;要写入再加相应权限。
15
+
16
+ ## 安装
17
+
18
+ ### 方式一:从 npm 装(最短,可升级)
19
+
20
+ ```
21
+ plugin_manager install_bundle target="dsh-d365-kit@0.34.1"
22
+ ```
23
+
24
+ 前提是包已经发到 pnpm 用的那个 registry——公网 npm 或公司内网镜像都行。包名没有 scope,不需要配 scope 映射。
25
+
26
+ > 只推 GitHub 不等于进了 registry:只有 GitHub 仓库时用方式二(或 `npm install github:<user>/<repo>#<tag>`)。配好 `NPM_TOKEN` secret 后推 `v*` tag 会自动发布(`.github/workflows/publish.yml`)。
27
+
28
+ ### 方式二:从 Git 装(不需要 registry)
29
+
30
+ ```
31
+ plugin_manager install_bundle target="git+https://<你的 Git>/<group>/dsh-d365-kit.git#v0.34.1"
32
+ ```
33
+
34
+ ### 方式三:从本地目录装
35
+
36
+ 把整个仓库目录放到本机任意位置:
37
+
38
+ ```
39
+ plugin_manager install_bundle target="D:\path\to\dsh-d365-kit"
40
+ ```
41
+
42
+ 这是 `link:` 安装:改代码立刻生效,但升级要自己 `git pull`。
43
+
44
+ 三条路一样:**装完重启一次 DSH**,Web 客户端才会把设置页代码加载进模块图。
45
+
46
+ ## 首次配置
47
+
48
+ 1. 设置 → D365 工具箱 → 环境 → 添加环境:key(如 `dev`)、URL、租户 ID、ClientId、客户端密钥;
49
+ 2. 点「测试连接」,看到版本号说明凭据没问题;
50
+ 3. 到「权限」标签勾选这台机器允许做什么(每个环境单独一块,用分隔线隔开):
51
+ - 「读取元数据」——默认就是勾上的;不勾连表、字段、视图都查不到;
52
+ - 「读取业务数据」——不勾就读不到表里的记录;
53
+ - 写入类能力默认全关,勾了之后每次执行仍要人在对话里确认;
54
+ 4. 点「保存」。
55
+
56
+ 配置默认写到 `<你的 DSH 工作区>/d365.config.json`,密钥单独存同目录的 `d365.secrets.json`
57
+ (明文,别提交)。工作区不在默认位置时,设环境变量 `D365_CONFIG_FILE`,或在 profile 的
58
+ `cordis.patch.yml` 里给 `configFile` 写绝对路径。
59
+
60
+ ## 验证
61
+
62
+ ```
63
+ d365_envs check=true
64
+ d365_metadata resource=entities top=3
65
+ ```
66
+
67
+ 都有内容就说明通了。第一次 `d365_sync` 会拉全量元数据(约 125 MB、三分钟左右),不想占盘可以先不做。
68
+
69
+ ### 排查 skill 随插件一起装
70
+
71
+ 包里带了一个 agent skill:`skills/d365-troubleshooting/SKILL.md`(插件失败怎么查:跟踪级别 → 归并签名 → 取一条完整调用 → 看步骤注册 → 复现)。插件加载时会把它注册进 Host 的 skill 注册表,所以装完 bundle 就有了——不需要复制文件,也不用谁按名字手动调用,agent 按描述就会自己用。
72
+
73
+ 要改就直接改装好的包里的这一份(源码安装就改克隆里的那份),然后重载插件;写在别处的副本不会被读到。
74
+
75
+ ## 升级
76
+
77
+ ```
78
+ plugin_manager remove_bundle target=dsh-d365-kit
79
+ plugin_manager install_bundle target="...新版本..."
80
+ ```
81
+
82
+ 必须先 remove 再 install:包已存在时安装器只返回 `already-installed`,不会替换代码。升级后重启 DSH。
83
+
84
+ ## 常见问题
85
+
86
+ - **工具没出现**:`plugin_manager list_plugins` 看 `include:dsh-d365-kit` 是否 `active`;刚装完要重启。
87
+ - **设置页没有「D365 工具箱」**:客户端插件未进模块图,重启;仍没有就查 `client.js` 的模块 id 是否与包名一致。
88
+ - **保存报「写入只能从设置页发起」**:CSRF 守卫,只在设置页里保存;被拒记录在 `.d365/write-guard.log`。
89
+ - **读业务数据报 `data-read-forbidden`**:到权限标签勾「读取业务数据」。
90
+ - **写入被拒 `environment-busy`**:环境正在导入或发布,等结束再试。
91
+ - **GitLab 403**:令牌用户在该项目里至少要有 Reporter 角色。
92
+
93
+ 其余错误码见 [docs/troubleshooting.md](docs/zh/troubleshooting.md)。
package/LICENSE ADDED
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2026 iTanzhao
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.