sapbuddy-dsh 0.0.5 → 0.0.6
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md
CHANGED
|
@@ -1,162 +1,66 @@
|
|
|
1
1
|
# sapbuddy-dsh
|
|
2
2
|
|
|
3
|
-
[
|
|
4
|
-
**41 个 SAP ABAP 工具 + 8 个 ABAP 技能 + agent preset `sapbuddy` + 三层写门禁 + 连接设置页**。
|
|
3
|
+
[](https://www.npmjs.com/package/sapbuddy-dsh) [GitHub](https://github.com/gxx950224/sapbuddy-dsh)
|
|
5
4
|
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
---
|
|
5
|
+
[SapBuddy](https://github.com/gxx950224/sapbuddy) 的 DeepSeek Harness 插件,提供 41 个 SAP ABAP 工具、8 个技能、SapBuddy agent preset 和 SAP 连接设置页。
|
|
9
6
|
|
|
10
7
|
## 安装
|
|
11
8
|
|
|
12
|
-
|
|
9
|
+
需要 Node.js 22+ 和兼容的 DSH;具体宿主依赖要求见 package.json 的 peerDependencies。
|
|
10
|
+
在 DSH 的插件管理页添加:
|
|
13
11
|
|
|
14
|
-
```
|
|
15
|
-
sapbuddy-dsh
|
|
12
|
+
```text
|
|
13
|
+
sapbuddy-dsh@0.0.6
|
|
16
14
|
```
|
|
17
15
|
|
|
18
|
-
|
|
16
|
+
也可以通过 DSH 命令行安装:
|
|
19
17
|
|
|
20
18
|
```bash
|
|
21
|
-
dsh plugin --profile
|
|
22
|
-
```
|
|
23
|
-
|
|
24
|
-
**装完必须完整重启 DSH**(托盘退出,不是关窗口)——插件代码在宿主启动时加载。
|
|
25
|
-
重启后新建会话时选择 **SapBuddy** preset,并在 **设置 → SAP 连接** 里新建 SAP 连接。
|
|
26
|
-
|
|
27
|
-
### ⚠️ 手工安装前必须配好构建放行
|
|
28
|
-
|
|
29
|
-
依赖链里有个遥测包会注册构建脚本:
|
|
30
|
-
|
|
31
|
-
```
|
|
32
|
-
sapbuddy-dsh → abap-adt-api → io-ts-reporters → @scarf/scarf(遥测,无需构建)
|
|
33
|
-
```
|
|
34
|
-
|
|
35
|
-
pnpm 11 把「存在未批准的构建脚本」当**错误**(`ERR_PNPM_IGNORED_BUILDS`,退出码 1)。
|
|
36
|
-
而 **`dsh plugin add` 在 pnpm 失败后不会再注册 bundle** —— 包进了 `node_modules`,
|
|
37
|
-
`dsh.profile.bundles` 却没更新,于是 **preset 永远不出现,且没有任何明显报错**。
|
|
38
|
-
|
|
39
|
-
在目标 profile 的 `pnpm-workspace.yaml` 里放行:
|
|
40
|
-
|
|
41
|
-
```yaml
|
|
42
|
-
allowBuilds:
|
|
43
|
-
'@scarf/scarf': false
|
|
44
|
-
```
|
|
45
|
-
|
|
46
|
-
或在仓库里跑安装器(会自动做这一步):
|
|
47
|
-
|
|
48
|
-
```bash
|
|
49
|
-
node scripts/install.mjs --profile desktop
|
|
50
|
-
```
|
|
51
|
-
|
|
52
|
-
> 包自身声明 `pnpm.ignoredBuiltDependencies` **不足以**覆盖三级传递依赖(已实测),
|
|
53
|
-
> 必须在安装侧声明。
|
|
54
|
-
|
|
55
|
-
### 这个包往 profile 里插两条行
|
|
56
|
-
|
|
57
|
-
| 行 | 作用 |
|
|
58
|
-
|---|---|
|
|
59
|
-
| `sapbuddy-dsh`(顶层) | 设置页的 HTTP 路由,并让客户端页面被加载 |
|
|
60
|
-
| preset 内的 `sapbuddy-dsh/tools` | 41 个工具 + 写门禁 + 提示词注入 |
|
|
61
|
-
|
|
62
|
-
两条都必须存在,写法也受 DSH 约束(`dsh-client-modules` 只扫描**顶层**行、
|
|
63
|
-
且只认**精确包名**)。这也是包的主入口是设置页、工具走子路径的原因。
|
|
64
|
-
详见仓库 README 的「为什么只有一个包」。
|
|
65
|
-
|
|
66
|
-
---
|
|
67
|
-
|
|
68
|
-
## 首次运行会落盘什么
|
|
69
|
-
|
|
70
|
-
插件加载时把包内 `defaults/` 中**缺失**的文件补写到配置根
|
|
71
|
-
(`<DSH_HOME|~/.dsh>/.agent-presets/sapbuddy/`),并创建 `prompts/ logs/ output/ uploads/`:
|
|
72
|
-
|
|
73
|
-
```
|
|
74
|
-
prompts/SYSTEM.md # 铁律与输出约定(每次会话实时注入)
|
|
75
|
-
prompts/Memory.md # 避坑记录,随会话累积
|
|
76
|
-
AGENTS.md # 项目开发指南
|
|
77
|
-
connections.example.json # 连接配置示例(参考用)
|
|
78
|
-
settings.example.json
|
|
19
|
+
dsh plugin --profile desktop add sapbuddy-dsh@0.0.6
|
|
79
20
|
```
|
|
80
21
|
|
|
81
|
-
|
|
82
|
-
|
|
22
|
+
更新已安装插件后,完整退出并重新启动 DSH,再新建会话选择 **SapBuddy**。
|
|
23
|
+
在 **设置 → SAP 连接** 中配置连接。请只安装裸包名 `sapbuddy-dsh`,避免与同功能的作用域包或拆分插件同时启用。
|
|
83
24
|
|
|
84
|
-
|
|
85
|
-
「设置 → SAP 连接」中新建,或参照 `connections.example.json` 手工创建。
|
|
86
|
-
密码在设置页的响应中脱敏为 `***`。
|
|
25
|
+
依赖 `@scarf/scarf` 的遥测脚本不影响 SAP 功能。如果包管理器提示未批准构建脚本,可通过宿主提供的依赖脚本管理禁用该遥测脚本,再重试安装。
|
|
87
26
|
|
|
88
|
-
|
|
27
|
+
## 激活结果核对
|
|
89
28
|
|
|
90
|
-
|
|
29
|
+
`abap_activate` 先检查待激活源码的语法,执行激活后读取 active 源码进行核对;激活阶段返回错误或抛异常时也会继续核对。
|
|
91
30
|
|
|
92
|
-
|
|
31
|
+
例如 INCLUDE 阶段报错,后续主程序实际已经激活:只有 active 源码与激活前快照一致,并且实时未激活清单中没有目标、直接引用的 INCLUDE 或激活响应中报告的未激活对象时,才确认成功并清零该对象的连续失败计数。原始阶段诊断保留,本次不再登记为审计失败。
|
|
93
32
|
|
|
94
|
-
|
|
95
|
-
读取失败 fail-closed)。连接级判定不会被"不传 `connectionId`"绕过,所以可以做到
|
|
96
|
-
开发系统可写、QAS/生产锁死。
|
|
97
|
-
2. **开发客户端守卫** —— 读 `T000.CCCATEGORY`,默认只放行 `C`(定制/客户开发);
|
|
98
|
-
查不到时 fail-closed。生产系统(`P`)即使关掉只读也写不进去。
|
|
99
|
-
3. **人工确认** —— 需用户批准(确认词须位于句首;窗口默认 15 分钟,见配置根
|
|
100
|
-
`settings.json` 的 `approvalWindowMinutes`)。
|
|
33
|
+
源码不一致、相关对象仍未激活或核对查询失败时,继续保留失败。对象元数据中的 `active` 字段不能单独证明本次修改已生效。非源码对象沿用其激活接口结果。
|
|
101
34
|
|
|
102
|
-
|
|
103
|
-
命名空间强制:只写 `Z*`/`Y*`,SAP 标准对象只读。
|
|
35
|
+
## 工具与技能
|
|
104
36
|
|
|
105
|
-
|
|
37
|
+
工具涵盖对象搜索与读码、代码导航、语法诊断与激活、ATC 与单元测试、SQL 查询、DDIC、文本池、屏幕文本、消息类、函数模块接口和传输请求。
|
|
106
38
|
|
|
107
|
-
|
|
39
|
+
技能包括 `abap-code-review`、`abap-explain`、`abap-templates`、`abap-wiki`、`clean-abap`、`dev-write`、`sap-abap-skills`、`translate-workflow`。
|
|
108
40
|
|
|
109
|
-
|
|
110
|
-
DDIC(域 / 数据元素 / 结构 / 表)、文本池与文本元素、屏幕文字、消息类、函数模块接口、
|
|
111
|
-
传输请求、激活与语法诊断、Mermaid 校验等。
|
|
41
|
+
## 配置和写入保护
|
|
112
42
|
|
|
113
|
-
|
|
114
|
-
`clean-abap`、`dev-write`、`sap-abap-skills`、`translate-workflow`。
|
|
43
|
+
首次加载时,插件会在 `<DSH_HOME|~/.dsh>/.agent-presets/sapbuddy/` 补充缺失的默认提示词与示例配置;也可用 `SAPBUDDY_HOME` 指定位置。已有连接、Memory 和自定义提示词不会被覆盖。连接凭据通过 SAP 连接设置页管理,不应提交到 Git。
|
|
115
44
|
|
|
116
|
-
|
|
45
|
+
写操作受连接级只读、开发客户端检查、Z/Y 命名空间及人工授权约束。中文文案采用维护建议,不再按源码中的中文字符统一阻断。
|
|
117
46
|
|
|
118
|
-
|
|
47
|
+
插件包含顶层设置入口 `sapbuddy-dsh` 和 preset 内工具入口 `sapbuddy-dsh/tools`;使用插件管理器安装以同时登记 bundle 和 preset。
|
|
119
48
|
|
|
120
|
-
##
|
|
49
|
+
## 开发与验证
|
|
121
50
|
|
|
122
|
-
|
|
123
|
-
|---|---|
|
|
124
|
-
| `sapbuddy-dsh`(主入口) | 顶层插件:设置页的 `webServer` 路由 |
|
|
125
|
-
| `sapbuddy-dsh/tools` | preset 内插件:41 工具注册 + 写门禁 + 提示词注入 |
|
|
126
|
-
| `sapbuddy-dsh/client` | 设置页浏览器端 bundle(`dsh.client`) |
|
|
51
|
+
仓库中的 `packages/sapbuddy-dsh` 是可发布的统一插件,包含已构建运行代码、类型声明、默认提示词和技能。维护此目录时直接验证运行代码;这里不提供重建该统一包的 TypeScript 开发树。
|
|
127
52
|
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
## 开发
|
|
53
|
+
从 GitHub 克隆仓库后,在 `packages/sapbuddy-dsh` 下运行激活回归测试(需要 Node.js 24+,无需连接 SAP):
|
|
131
54
|
|
|
132
55
|
```bash
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
pnpm --filter sapbuddy-dsh test # 172 用例
|
|
56
|
+
npm run test:activation
|
|
57
|
+
npm pack --dry-run --ignore-scripts
|
|
136
58
|
```
|
|
137
59
|
|
|
138
|
-
|
|
139
|
-
`npm pack --dry-run` 确认内容仍在(`README.md` 与 `LICENSE` 是 npm 自动收录的)。
|
|
140
|
-
|
|
141
|
-
---
|
|
142
|
-
|
|
143
|
-
## 链接
|
|
144
|
-
|
|
145
|
-
- 仓库与完整文档:<https://github.com/gxx950224/sapbuddy-dsh>
|
|
146
|
-
- 上游 CLI / Web 版(pi 引擎):<https://github.com/gxx950224/sapbuddy>
|
|
147
|
-
- 问题反馈:<https://github.com/gxx950224/sapbuddy-dsh/issues>
|
|
148
|
-
|
|
149
|
-
> 注意区分:npm 上的 `sapbuddy` 是上游 CLI 版;本包 `sapbuddy-dsh` 是 DSH 底座版。
|
|
150
|
-
> 旧的 `sapbuddy-tools` / `sapbuddy-settings` / `sapbuddy-settings-ui` 已合并进本包并弃用。
|
|
60
|
+
回归测试使用模拟 SAP 响应,覆盖误报恢复、失败计数、审计记录和核对失败边界;不能替代真实 SAP 联机验收。
|
|
151
61
|
|
|
152
62
|
## 许可证
|
|
153
63
|
|
|
154
|
-
MIT
|
|
155
|
-
|
|
156
|
-
## 0.0.4
|
|
157
|
-
|
|
158
|
-
本版本基于已构建产物修改发布。取消 ABAP 源码硬编码中文的写入阻断;修复 Web 原生审批获批后未开启默认 15 分钟授权窗口的问题,写入与激活共用窗口。中文文案改为维护建议,其他连接与对象保护保持生效。
|
|
159
|
-
|
|
160
|
-
## 0.0.5
|
|
64
|
+
MIT。工具实现来自 [SapBuddy](https://github.com/gxx950224/sapbuddy)。
|
|
161
65
|
|
|
162
|
-
|
|
66
|
+
[](https://github.com/deepseek-ai/deepseek-harness)
|
|
@@ -41,7 +41,7 @@ SapBuddy — SAP ABAP AI 全能助手,面向**开发顾问与业务顾问**。
|
|
|
41
41
|
1. **用户纠错后先整体复盘**:用户指出"有问题/错了/没完成"时,先重新读取对象现状(激活状态、当前源码、诊断结果),列出全部异常清单、确认排查顺序后再动手;禁止直接埋头修最新一条报错。
|
|
42
42
|
2. **覆盖型写操作前先备份**:整体覆盖类工具(manage_text_elements 写、translate_text_pool、replace_string)动手前先读当前完整内容备份;操作可能影响多个条目时先说明覆盖范围;一旦发现"修一处坏一处"(补 A 丢 B)立即停手,先还原/说明,禁止继续覆盖式修补。
|
|
43
43
|
3. **工具不可用即换路**:工具返回 not found / 对象不存在时,说明该路径不通,换可行工具或如实告知用户,禁止继续深挖不存在的能力(如表、工具)。
|
|
44
|
-
4. **报完成前核对本次激活结果**:宣称"完成/验证通过"前,必须确认 abap_activate 本次调用成功;ABAP 源码对象还须通过工具内的 active
|
|
44
|
+
4. **报完成前核对本次激活结果**:宣称"完成/验证通过"前,必须确认 abap_activate 本次调用成功;ABAP 源码对象还须通过工具内的 active 源码核验。阶段报错后的最终结论以工具核验结果为准:确认源码一致且无相关未激活对象时,工具清零连续失败计数并保留原始诊断;核验失败或未确认则如实报告,不能仅用 get_abap_object_info 的 active 元数据判定成功。
|
|
45
45
|
5. **先读后说:位置/调用点类答案必须实证**(回答"在哪改 / 哪个程序 / FORM / 方法 / 增强点 / 第几行"时):
|
|
46
46
|
- 写进结论的**具体位置**(程序名 / FORM 名 / 方法名 / 行号 / 增强点名)必须**先读到真实源码或对象确认**才能落笔;
|
|
47
47
|
- 凭记忆记得的调用链只能当"待核实线索",明确标注,**禁止当成事实写进答案**(S/4 与 ECC 结构差异大,凭旧版本记忆必出错——如老版本 FORM `XINBILL_DOCUMENT_SAVE` 在 S/4 不存在);
|
|
@@ -6,13 +6,15 @@ export interface ActivationStage {
|
|
|
6
6
|
}
|
|
7
7
|
export interface ActivationReport extends ActivationResult {
|
|
8
8
|
stages: ActivationStage[];
|
|
9
|
-
verification?: "matched" | "mismatch" | "unavailable" | "not_applicable";
|
|
9
|
+
verification?: "matched" | "mismatch" | "unavailable" | "not_applicable" | "pending";
|
|
10
|
+
recovered?: boolean;
|
|
10
11
|
sourceHash?: string;
|
|
11
12
|
}
|
|
12
13
|
/** Preserve the origin of errors; an inactive object or error cannot be a successful stage. */
|
|
13
14
|
export declare function mergeActivationResults(results: ActivationResult[], names: string[]): ActivationReport;
|
|
14
15
|
/** Called inside the existing connection write mutex: check -> activate -> read active source. */
|
|
15
|
-
export declare function verifyActivation(client: Pick<ADTClient, "getObjectSource" | "syntaxCheck">, obj: {
|
|
16
|
+
export declare function verifyActivation(client: Pick<ADTClient, "getObjectSource" | "syntaxCheck" | "inactiveObjects">, obj: {
|
|
16
17
|
"adtcore:uri": string;
|
|
18
|
+
"adtcore:name"?: string;
|
|
17
19
|
"adtcore:type"?: string;
|
|
18
20
|
}, activate: () => Promise<ActivationReport>): Promise<ActivationReport>;
|
|
@@ -55,17 +55,46 @@ export async function verifyActivation(client, obj, activate) {
|
|
|
55
55
|
return failedStage("激活前语法检查", `无法确认待激活源码或语法结果,未执行激活:${sanitizeErrMsg(err)}`);
|
|
56
56
|
}
|
|
57
57
|
const expected = sourceHash(source);
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
58
|
+
let activation;
|
|
59
|
+
try {
|
|
60
|
+
activation = await activate();
|
|
61
|
+
}
|
|
62
|
+
catch (err) {
|
|
63
|
+
activation = failedStage("激活请求", sanitizeErrMsg(err));
|
|
64
|
+
}
|
|
65
|
+
const result = mergeActivationResults([syntax, activation], []);
|
|
61
66
|
try {
|
|
62
67
|
const active = await client.getObjectSource(uri, { version: "active" });
|
|
63
68
|
if (sourceHash(active) !== expected) {
|
|
64
69
|
return { ...mergeActivationResults([result, failedStage("激活版本核验", "active 源码与激活前核对的源码不一致,本次修改未确认生效。")], []), verification: "mismatch", sourceHash: expected };
|
|
65
70
|
}
|
|
71
|
+
if (!result.success) {
|
|
72
|
+
// An unchanged main source alone cannot prove its INCLUDE changes took effect.
|
|
73
|
+
// Re-read the live inactive list; the activation response may describe an earlier stage.
|
|
74
|
+
const related = [obj, ...result.inactive.map(r => r.object).filter(Boolean)];
|
|
75
|
+
for (const line of source.split("\n")) {
|
|
76
|
+
const match = /^\s*INCLUDE\s+([A-Za-z/][A-Za-z0-9_/]*)\s*\./i.exec(line);
|
|
77
|
+
if (match)
|
|
78
|
+
related.push({ "adtcore:name": match[1], "adtcore:type": "PROG/I" });
|
|
79
|
+
}
|
|
80
|
+
const normalizeUri = value => String(value ?? "").split(/[?#]/)[0].replace(/\/source\/main\/?$/i, "").replace(/\/$/, "").toLowerCase();
|
|
81
|
+
const pending = await client.inactiveObjects();
|
|
82
|
+
if (!Array.isArray(pending))
|
|
83
|
+
throw new Error("未激活对象清单无效");
|
|
84
|
+
const remaining = pending.filter(r => r.object && related.some(o =>
|
|
85
|
+
(o["adtcore:uri"] && normalizeUri(o["adtcore:uri"]) === normalizeUri(r.object["adtcore:uri"])) ||
|
|
86
|
+
(o["adtcore:name"] && o["adtcore:name"].toUpperCase() === String(r.object["adtcore:name"] ?? "").toUpperCase())));
|
|
87
|
+
if (remaining.length) {
|
|
88
|
+
return { ...mergeActivationResults([result, failedStage("激活状态核验", "目标或相关 INCLUDE 仍存在未激活版本,保留失败计数。")], []), inactive: remaining, verification: "pending", sourceHash: expected };
|
|
89
|
+
}
|
|
90
|
+
return {
|
|
91
|
+
...result, success: true, inactive: [], verification: "matched", recovered: true, sourceHash: expected,
|
|
92
|
+
stages: [...result.stages, { name: "激活后核验(源码一致且无相关未激活对象)", success: true, messages: [] }],
|
|
93
|
+
};
|
|
94
|
+
}
|
|
66
95
|
return { ...result, verification: "matched", sourceHash: expected };
|
|
67
96
|
}
|
|
68
97
|
catch (err) {
|
|
69
|
-
return { ...mergeActivationResults([result, failedStage("激活版本核验",
|
|
98
|
+
return { ...mergeActivationResults([result, failedStage("激活版本核验", `激活请求已返回,但无法完成激活状态核对,结果未确认:${sanitizeErrMsg(err)}`)], []), verification: "unavailable", sourceHash: expected };
|
|
70
99
|
}
|
|
71
100
|
}
|
|
@@ -313,7 +313,7 @@ async function activateFunctionGroupFormIncludes(client, fgName) {
|
|
|
313
313
|
catch (err) {
|
|
314
314
|
results.push({
|
|
315
315
|
success: false,
|
|
316
|
-
messages: [{ objDescr: "", type: "E", line: 0, href: "", forceSupported: false, shortText:
|
|
316
|
+
messages: [{ objDescr: "", type: "E", line: 0, href: "", forceSupported: false, shortText: sanitizeErrMsg(err) }],
|
|
317
317
|
inactive: [],
|
|
318
318
|
});
|
|
319
319
|
}
|
|
@@ -321,7 +321,7 @@ async function activateFunctionGroupFormIncludes(client, fgName) {
|
|
|
321
321
|
}
|
|
322
322
|
catch (err) {
|
|
323
323
|
names.push("读取函数组 include 清单");
|
|
324
|
-
results.push({ success: false, inactive: [], messages: [{ objDescr: "", type: "E", line: 0, href: "", forceSupported: false, shortText:
|
|
324
|
+
results.push({ success: false, inactive: [], messages: [{ objDescr: "", type: "E", line: 0, href: "", forceSupported: false, shortText: sanitizeErrMsg(err) }] });
|
|
325
325
|
}
|
|
326
326
|
return mergeActivationResults(results, names);
|
|
327
327
|
}
|
|
@@ -351,7 +351,7 @@ async function activateWithIncludes(client, obj) {
|
|
|
351
351
|
catch (err) {
|
|
352
352
|
results.push({
|
|
353
353
|
success: false,
|
|
354
|
-
messages: [{ objDescr: "", type: "E", line: 0, href: "", forceSupported: false, shortText:
|
|
354
|
+
messages: [{ objDescr: "", type: "E", line: 0, href: "", forceSupported: false, shortText: sanitizeErrMsg(err) }],
|
|
355
355
|
inactive: [],
|
|
356
356
|
});
|
|
357
357
|
}
|
|
@@ -361,7 +361,7 @@ async function activateWithIncludes(client, obj) {
|
|
|
361
361
|
catch (err) {
|
|
362
362
|
results.push({
|
|
363
363
|
success: false,
|
|
364
|
-
messages: [{ objDescr: "", type: "E", line: 0, href: "", forceSupported: false, shortText:
|
|
364
|
+
messages: [{ objDescr: "", type: "E", line: 0, href: "", forceSupported: false, shortText: sanitizeErrMsg(err) }],
|
|
365
365
|
inactive: [],
|
|
366
366
|
});
|
|
367
367
|
}
|
|
@@ -371,7 +371,7 @@ async function activateWithIncludes(client, obj) {
|
|
|
371
371
|
catch (err) {
|
|
372
372
|
results.push({
|
|
373
373
|
success: false,
|
|
374
|
-
messages: [{ objDescr: "", type: "E", line: 0, href: "", forceSupported: false, shortText:
|
|
374
|
+
messages: [{ objDescr: "", type: "E", line: 0, href: "", forceSupported: false, shortText: sanitizeErrMsg(err) }],
|
|
375
375
|
inactive: [],
|
|
376
376
|
});
|
|
377
377
|
}
|
|
@@ -433,7 +433,7 @@ async function activateWithIncludes(client, obj) {
|
|
|
433
433
|
catch (err) {
|
|
434
434
|
results.push({
|
|
435
435
|
success: false,
|
|
436
|
-
messages: [{ objDescr: "", type: "E", line: 0, href: "", forceSupported: false, shortText:
|
|
436
|
+
messages: [{ objDescr: "", type: "E", line: 0, href: "", forceSupported: false, shortText: sanitizeErrMsg(err) }],
|
|
437
437
|
inactive: [],
|
|
438
438
|
});
|
|
439
439
|
}
|
|
@@ -445,7 +445,7 @@ async function activateWithIncludes(client, obj) {
|
|
|
445
445
|
catch (err) {
|
|
446
446
|
results.push({
|
|
447
447
|
success: false,
|
|
448
|
-
messages: [{ objDescr: "", type: "E", line: 0, href: "", forceSupported: false, shortText:
|
|
448
|
+
messages: [{ objDescr: "", type: "E", line: 0, href: "", forceSupported: false, shortText: sanitizeErrMsg(err) }],
|
|
449
449
|
inactive: [],
|
|
450
450
|
});
|
|
451
451
|
}
|
|
@@ -454,7 +454,7 @@ async function activateWithIncludes(client, obj) {
|
|
|
454
454
|
export const activateTool = {
|
|
455
455
|
name: "abap_activate",
|
|
456
456
|
title: "Activate ABAP Object",
|
|
457
|
-
description: "激活 ABAP 对象。ABAP
|
|
457
|
+
description: "激活 ABAP 对象。ABAP 源码对象会先检查语法,激活请求返回或报错后均核对 active 源码。阶段报错但源码一致且无相关未激活对象时,确认已激活并清零该对象连续失败计数;原始诊断保留。核验不一致或不可用则保留失败,不能仅凭元数据 active 判定成功。写入后必须激活才能生效。\n" +
|
|
458
458
|
"⚠️ 函数组(FUGR)激活不会自动连带激活其内部的函数模块——函数模块需单独激活:objectName=函数模块名,objectType=FUGR/FF。激活函数模块时工具会自动先激活函数组的 FORM include(把未激活的 FORM 改动固化,否则报 FORM ... does not exist)、再激活主程序(SAPL<fg>,让主程序里新增的 INCLUDE 语句先固化生效)、最后激活函数模块本身,AI 无需手动排顺序。\n" +
|
|
459
459
|
"⚠️ 拆 INCLUDE 的可执行程序(主程序 PROG/P + 其 INCLUDE PROG/I)无法逐个编译——单独激活某个 INCLUDE 会对着兄弟 INCLUDE 的旧版本编译,必报 \"Field/Type unknown\"。激活时工具会自动先批量激活其全部 INCLUDE、再单独激活主程序(两步);同一对象连续激活失败 3 次会被强制拦截,需停下向用户说明。",
|
|
460
460
|
write: true,
|
|
@@ -492,13 +492,15 @@ export const activateTool = {
|
|
|
492
492
|
lines.push(`✅ ${obj["adtcore:type"]} ${obj["adtcore:name"]} 激活成功`);
|
|
493
493
|
if (result.verification === "matched")
|
|
494
494
|
lines.push("已核对 active 源码与本次待激活源码一致。");
|
|
495
|
+
if (result.recovered)
|
|
496
|
+
lines.push("激活阶段曾报错,但核对确认已激活且无相关未激活对象;该对象连续失败计数已清零,本次不计失败。以下保留原始阶段诊断。");
|
|
495
497
|
}
|
|
496
498
|
else {
|
|
497
499
|
lines.push(`❌ ${result.verification === "unavailable" ? "激活未确认" : "激活失败"}(${obj["adtcore:type"]} ${obj["adtcore:name"]}):`);
|
|
498
500
|
lines.push("本次操作未确认完成;对象元数据的 active 状态不能替代本次激活结果。");
|
|
499
501
|
}
|
|
500
502
|
for (const stage of result.stages) {
|
|
501
|
-
lines.push(`- ${stage.name}:${stage.success ? "通过" : "未完成"}`);
|
|
503
|
+
lines.push(`- ${stage.name}:${stage.success ? "通过" : result.recovered ? "阶段报错(最终核验已通过)" : "未完成"}`);
|
|
502
504
|
for (const m of stage.messages.slice(0, 10))
|
|
503
505
|
lines.push(` [${m.type}] 行 ${m.line}: ${m.shortText}`);
|
|
504
506
|
if (stage.messages.length > 10)
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "sapbuddy-dsh",
|
|
3
|
-
"version": "0.0.
|
|
3
|
+
"version": "0.0.6",
|
|
4
4
|
"description": "SapBuddy —— SAP ABAP AI 全能助手(DeepSeek Harness 插件):41 个 SAP 工具 + 8 个 ABAP 技能 + SapBuddy preset + 连接设置页。一个包名装完。",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"main": "dist/index.js",
|
|
@@ -77,10 +77,8 @@
|
|
|
77
77
|
"typescript": "^5.6.0"
|
|
78
78
|
},
|
|
79
79
|
"scripts": {
|
|
80
|
-
"
|
|
81
|
-
"test": "node
|
|
82
|
-
"check": "tsc --noEmit",
|
|
83
|
-
"prepack": "node scripts/clean.mjs && tsc && node scripts/build-client.mjs"
|
|
80
|
+
"test": "npm run test:activation",
|
|
81
|
+
"test:activation": "node --experimental-vm-modules --test --test-isolation=none test/activation.test.mjs"
|
|
84
82
|
},
|
|
85
83
|
"publishConfig": {
|
|
86
84
|
"access": "public"
|