sapbuddy-dsh 0.0.7 → 0.0.9
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 +19 -6
- package/defaults/prompts/SYSTEM.md +2 -1
- package/dist/client.js +20 -5
- package/dist/index.js +85 -11
- package/dist/sap-tools/connections-store.js +12 -2
- package/package.json +8 -4
- package/skills/grilling/SKILL.md +43 -0
- package/skills/sap-fs/SKILL.md +76 -0
- package/skills/sap-fs/assets/templates/enhancement.md +52 -0
- package/skills/sap-fs/assets/templates/fs-archive.html +320 -0
- package/skills/sap-fs/assets/templates/fs-base.md +93 -0
- package/skills/sap-fs/assets/templates/functional-report.md +60 -0
- package/skills/sap-fs/assets/templates/interface.md +92 -0
- package/skills/sap-fs/assets/templates/query-report.md +55 -0
- package/skills/sap-fs/references/archive-html.md +34 -0
- package/skills/sap-fs/references/example-enhancement.md +163 -0
- package/skills/sap-fs/references/fs-contract.md +64 -0
- package/skills/sap-fs/references/grilling-integration.md +26 -0
- package/skills/sap-fs/references/review-checklist.md +13 -0
- package/skills/sap-fs/references/system-verification.md +30 -0
- package/skills/sap-fs/references/types/enhancement.md +21 -0
- package/skills/sap-fs/references/types/functional-report.md +22 -0
- package/skills/sap-fs/references/types/interface.md +27 -0
- package/skills/sap-fs/references/types/query-report.md +21 -0
package/README.md
CHANGED
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
|
|
3
3
|
[](https://www.npmjs.com/package/sapbuddy-dsh) [GitHub](https://github.com/gxx950224/sapbuddy-dsh)
|
|
4
4
|
|
|
5
|
-
[SapBuddy](https://github.com/gxx950224/sapbuddy) 的 DeepSeek Harness 插件,提供 41 个 SAP ABAP 工具、
|
|
5
|
+
[SapBuddy](https://github.com/gxx950224/sapbuddy) 的 DeepSeek Harness 插件,提供 41 个 SAP ABAP 工具、10 个技能、SapBuddy agent preset 和 SAP 连接设置页。
|
|
6
6
|
|
|
7
7
|
## 安装
|
|
8
8
|
|
|
@@ -10,13 +10,13 @@
|
|
|
10
10
|
在 DSH 的插件管理页添加:
|
|
11
11
|
|
|
12
12
|
```text
|
|
13
|
-
sapbuddy-dsh@0.0.
|
|
13
|
+
sapbuddy-dsh@0.0.9
|
|
14
14
|
```
|
|
15
15
|
|
|
16
16
|
也可以通过 DSH 命令行安装:
|
|
17
17
|
|
|
18
18
|
```bash
|
|
19
|
-
dsh plugin --profile desktop add sapbuddy-dsh@0.0.
|
|
19
|
+
dsh plugin --profile desktop add sapbuddy-dsh@0.0.9
|
|
20
20
|
```
|
|
21
21
|
|
|
22
22
|
更新已安装插件后,完整退出并重新启动 DSH,再新建会话选择 **SapBuddy**。
|
|
@@ -32,6 +32,19 @@ dsh plugin --profile desktop add sapbuddy-dsh@0.0.7
|
|
|
32
32
|
|
|
33
33
|
源码不一致、相关对象仍未激活或核对查询失败时,继续保留失败。对象元数据中的 `active` 字段不能单独证明本次修改已生效。非源码对象沿用其激活接口结果。
|
|
34
34
|
|
|
35
|
+
## 连接配置与排查
|
|
36
|
+
|
|
37
|
+
地址必须写全协议,协议要和端口一致:
|
|
38
|
+
|
|
39
|
+
| 端口 | 协议 | 示例 |
|
|
40
|
+
|---|---|---|
|
|
41
|
+
| ICM HTTP(常见 8000) | `http://` | `http://192.168.111.85:8000` |
|
|
42
|
+
| ICM HTTPS(常见 44300) | `https://` | `https://10.8.202.35:44300` |
|
|
43
|
+
|
|
44
|
+
把 HTTP 端口写成 `https://`(或反过来)会在 TLS 阶段直接失败。客户端号就是登录用的 `sap-client`,必须与要登录的客户端一致——系统是 300 却填 200 会直接返回 401 认证失败;登录语言不确定时填 `EN`。内部系统若是自签名证书,编辑连接时保持「允许自签名证书」勾选。
|
|
45
|
+
|
|
46
|
+
设置页的 **测试** 按钮与真实 SAP 工具走同一套协议/客户端号/语言(早期版本把探活写死成 https 且只发 `X-sap-login-client` 头,会出现"账号密码都对却报 401"或"http 端口永远探测失败"的假阴性)。测试返回 401 时按提示核对客户端号、账号状态与登录语言;403/404 多为 ADT 服务未激活或用户无权限。
|
|
47
|
+
|
|
35
48
|
## 连接切换与会话自愈
|
|
36
49
|
|
|
37
50
|
切换或保存 SAP 连接后,插件会立刻作废进程内池化的 ADT 客户端,下一次调用按新配置重新登录,不会带着上一个系统的 cookie 与 CSRF 令牌去读写当前系统。
|
|
@@ -44,7 +57,7 @@ dsh plugin --profile desktop add sapbuddy-dsh@0.0.7
|
|
|
44
57
|
|
|
45
58
|
工具涵盖对象搜索与读码、代码导航、语法诊断与激活、ATC 与单元测试、SQL 查询、DDIC、文本池、屏幕文本、消息类、函数模块接口和传输请求。
|
|
46
59
|
|
|
47
|
-
技能包括 `abap-code-review`、`abap-explain`、`abap-templates`、`abap-wiki`、`clean-abap`、`dev-write`、`sap-abap-skills`、`translate-workflow
|
|
60
|
+
技能包括 `abap-code-review`、`abap-explain`、`abap-templates`、`abap-wiki`、`clean-abap`、`dev-write`、`sap-abap-skills`、`translate-workflow`、`sap-fs`(四类 FS:MD + Claude 风 HTML)、`grilling`(经 sap-fs 加载的追问澄清)。
|
|
48
61
|
|
|
49
62
|
## 配置和写入保护
|
|
50
63
|
|
|
@@ -61,11 +74,11 @@ dsh plugin --profile desktop add sapbuddy-dsh@0.0.7
|
|
|
61
74
|
从 GitHub 克隆仓库后,在 `packages/sapbuddy-dsh` 下运行激活回归测试(需要 Node.js 24+,无需连接 SAP):
|
|
62
75
|
|
|
63
76
|
```bash
|
|
64
|
-
npm test # = test:activation + test:session + test:store
|
|
77
|
+
npm test # = test:activation + test:session + test:store + test:probe + test:route + test:bundle + test:sap-fs
|
|
65
78
|
npm pack --dry-run --ignore-scripts
|
|
66
79
|
```
|
|
67
80
|
|
|
68
|
-
`test:activation` 覆盖激活结果核对的误报恢复、失败计数、审计记录和核对失败边界;`test:session` 覆盖写前会话刷新、CSRF 失效自动重建,以及"已落库则绝不重试"的边界;`test:store`
|
|
81
|
+
`test:activation` 覆盖激活结果核对的误报恢复、失败计数、审计记录和核对失败边界;`test:session` 覆盖写前会话刷新、CSRF 失效自动重建,以及"已落库则绝不重试"的边界;`test:store` 用真实模块验证设置页保存/切换/删除连接确实会作废连接池;`test:probe` 覆盖连接探测的协议/客户端号/语言与 401 提示;`test:route` 起一个本地 http 服务端到端验证「测试」按钮(含 sap-client 传递与 401 提示);`test:bundle` 对设置页 bundle 做渲染冒烟。测试都使用模拟 SAP 响应,不能替代真实 SAP 联机验收。
|
|
69
82
|
|
|
70
83
|
## 许可证
|
|
71
84
|
|
|
@@ -32,7 +32,8 @@ SapBuddy — SAP ABAP AI 全能助手,面向**开发顾问与业务顾问**。
|
|
|
32
32
|
- **工具硬化**:`create_object_programmatically` 缺包名/描述会被拦截;传 $TMP 需用户单独确认(不进传输请求);`replace_string` 无未释放请求时自动建请求——这些无需 LLM 自觉,工具强制执行。
|
|
33
33
|
6c. **连接切换后先重确认(强制)**:用户切换/修改 SAP 连接后,**必须先调用 `get_connected_systems` 确认当前连接,再使用其他 SAP 工具**。工具层强制拦截:未确认前其他 SAP 工具一律失败(返回"连接配置已变更,先调用 get_connected_systems")。看到该拦截错误时,立即调用 `get_connected_systems`,再重试被拦的操作。
|
|
34
34
|
7. **写操作谨慎**:写工具(创建/编辑/激活)需要服务器 `security.readOnly=false`;生产系统只读。
|
|
35
|
-
8. **按技能加载(强制)**:写代码/写 SAP 对象(函数模块/表结构/函数组 include/文本元素等)先遵循 `dev-write` 技能(编码规范、写入规范);批量翻译(Excel 清单→SAP)先遵循 `translate-workflow` 技能(先 list 后 set、匹配不上报告、**禁止猜 key
|
|
35
|
+
8. **按技能加载(强制)**:写代码/写 SAP 对象(函数模块/表结构/函数组 include/文本元素等)先遵循 `dev-write` 技能(编码规范、写入规范);批量翻译(Excel 清单→SAP)先遵循 `translate-workflow` 技能(先 list 后 set、匹配不上报告、**禁止猜 key 写入**)。技能按话题自动加载,做对应任务时**必须遵守**;写 FS / 功能设计书时遵循 `sap-fs` 技能(意图不明先问,见铁律 9)。
|
|
36
|
+
9. **FS 意图门禁(强制)**:仅当用户**明确**要写/改 FS、功能设计书、接口/报表/增强**规格文档**(或明确说按 sap-fs 出可交付文档)时,才加载并执行 `sap-fs`。日常问答、配置怎么配、表/事务码解释、程序解释/审查、写 ABAP 代码 → **禁止**触发 sap-fs(写码→`dev-write`;审查→`abap-code-review`;解释逻辑→`abap-explain` / `abap-wiki`)。用户只抛业务描述或方案讨论、**未明确**要出 FS → **先问一句**是否要按 sap-fs 产出 `FS.md` + `FS.html`(接口另附 JSON);未确认前只澄清/回答,**不**套 FS 模板、不落盘 `output/fs/`。用户确认后才 Skill 加载 `sap-fs`;追问业务决策时再按 sap-fs 流程加载 `grilling`。
|
|
36
37
|
|
|
37
38
|
## 防死循环与止损(软约束,靠自觉;能代码强制的已由工具层兜底)
|
|
38
39
|
|
package/dist/client.js
CHANGED
|
@@ -66,6 +66,7 @@ var S = {
|
|
|
66
66
|
badge: { border: "1px solid var(--dsw-alias-border-l3)", color: "var(--dsw-alias-label-secondary)", borderRadius: 4, padding: "1px 6px", fontSize: 11, lineHeight: "16px", flex: "none" },
|
|
67
67
|
badgeActive: { background: "var(--dsw-alias-state-success-primary)", borderColor: "var(--dsw-alias-state-success-primary)", color: "var(--dsw-alias-label-primary-foreground)" },
|
|
68
68
|
meta: { margin: 0, fontSize: 12, lineHeight: "18px", color: "var(--dsw-alias-label-tertiary)", overflow: "hidden", textOverflow: "ellipsis", whiteSpace: "nowrap" },
|
|
69
|
+
testMsg: { margin: 0, fontSize: 12, lineHeight: "18px", color: "var(--dsw-alias-label-secondary)", whiteSpace: "pre-wrap" },
|
|
69
70
|
actions: { display: "flex", gap: 6, marginLeft: "auto", flex: "none" },
|
|
70
71
|
btn: { height: 28, padding: "0 10px", borderRadius: 14, fontSize: 12, lineHeight: "18px", cursor: "pointer", border: "1px solid var(--dsw-alias-border-l2)", background: "transparent", color: "var(--dsw-alias-label-primary)", font: "inherit" },
|
|
71
72
|
btnPrimary: { background: "var(--dsw-alias-button-primary-fill)", color: "var(--dsw-alias-label-primary-foreground)", border: "none" },
|
|
@@ -178,24 +179,38 @@ function Section() {
|
|
|
178
179
|
{ key: "m", style: S.meta },
|
|
179
180
|
c.url + (c.client ? " \xB7 Client " + c.client : "") + " \xB7 " + c.username + (c.hasPassword ? " \xB7 \u5BC6\u7801\u5DF2\u8BBE\u7F6E" : " \xB7 \u672A\u8BBE\u7F6E\u5BC6\u7801")
|
|
180
181
|
),
|
|
181
|
-
testResult[c.id] ? React.createElement("p", { key: "tr", style: S.
|
|
182
|
+
testResult[c.id] ? React.createElement("p", { key: "tr", style: S.testMsg }, testResult[c.id]) : null
|
|
182
183
|
]);
|
|
183
184
|
const editor = React.createElement("div", { key: "editor", style: S.editor }, [
|
|
184
185
|
React.createElement("div", { key: "g1", style: S.grid }, [
|
|
185
186
|
input("id", "\u8FDE\u63A5 ID\uFF08\u552F\u4E00\uFF0C\u5982 dev/prod\uFF09", "dev"),
|
|
186
187
|
input("name", "\u540D\u79F0", "\u5F00\u53D1\u7CFB\u7EDF"),
|
|
187
|
-
input("url", "\u5730\u5740\
|
|
188
|
-
input("client", "\u5BA2\u6237\u7AEF\u53F7", "100")
|
|
188
|
+
input("url", "\u5730\u5740\uFF08http(s)://host:port\uFF09", "http://10.8.202.35:8000 \u6216 https://10.8.202.35:44300"),
|
|
189
|
+
input("client", "\u5BA2\u6237\u7AEF\u53F7\uFF083 \u4F4D\uFF0C\u5FC5\u987B\u4E0E\u7CFB\u7EDF\u4E00\u81F4\uFF09", "100")
|
|
189
190
|
]),
|
|
190
191
|
React.createElement("div", { key: "g2", style: S.grid }, [
|
|
191
192
|
input("username", "\u7528\u6237\u540D", "YOUR_USER"),
|
|
192
193
|
input("password", "\u5BC6\u7801\uFF08\u7F16\u8F91\u65F6\u7559\u7A7A\u4FDD\u6301\u539F\u5BC6\u7801\uFF09", "\u2022\u2022\u2022\u2022\u2022\u2022"),
|
|
193
|
-
input("language", "\u8BED\u8A00", "ZH"),
|
|
194
|
+
input("language", "\u767B\u5F55\u8BED\u8A00\uFF08\u4E0D\u786E\u5B9A\u586B EN\uFF09", "ZH"),
|
|
194
195
|
input("authMethod", "\u8BA4\u8BC1\u65B9\u5F0F", "basic")
|
|
195
196
|
]),
|
|
196
197
|
React.createElement("div", { key: "g3", style: S.grid }, [
|
|
197
198
|
input("description", "\u63CF\u8FF0", "\u5F00\u53D1\u7CFB\u7EDF")
|
|
198
199
|
]),
|
|
200
|
+
React.createElement("label", { key: "ssl", style: { ...S.field, flexDirection: "row", alignItems: "center", gap: 8 } }, [
|
|
201
|
+
React.createElement("input", {
|
|
202
|
+
key: "i",
|
|
203
|
+
type: "checkbox",
|
|
204
|
+
checked: !draft.ssl || draft.ssl.allowSelfSigned !== false,
|
|
205
|
+
onChange: (evt) => setDraft((d) => ({ ...d, ssl: { ...(d.ssl || {}), allowSelfSigned: evt.target.checked } }))
|
|
206
|
+
}),
|
|
207
|
+
React.createElement("span", { key: "l", style: S.label }, "\u5141\u8BB8\u81EA\u7B7E\u540D\u8BC1\u4E66\uFF08\u5185\u90E8\u7CFB\u7EDF https \u7528\uFF09")
|
|
208
|
+
]),
|
|
209
|
+
React.createElement(
|
|
210
|
+
"p",
|
|
211
|
+
{ key: "hint", style: S.hint },
|
|
212
|
+
"\u7AEF\u53E3\u5BF9\u5E94\u534F\u8BAE\uFF1ASAP \u7684 ICM HTTP \u7AEF\u53E3\uFF08\u5E38\u89C1 8000\uFF09\u7528 http://\uFF0CHTTPS \u7AEF\u53E3\uFF08\u5E38\u89C1 44300\uFF09\u7528 https://\u3002\u5BA2\u6237\u7AEF\u53F7\u5C31\u662F\u767B\u5F55\u5BA2\u6237\u7AEF\uFF08sap-client\uFF09\uFF0C\u586B\u9519\u4F1A\u8FD4\u56DE 401\uFF1B\u767B\u5F55\u8BED\u8A00\u4E0D\u786E\u5B9A\u65F6\u586B EN\u3002"
|
|
213
|
+
),
|
|
199
214
|
React.createElement("div", { key: "acts", style: { display: "flex", gap: 8, justifyContent: "flex-end" } }, [
|
|
200
215
|
React.createElement("button", { key: "c", style: S.btn, onClick: () => {
|
|
201
216
|
setMode(null);
|
|
@@ -209,7 +224,7 @@ function Section() {
|
|
|
209
224
|
React.createElement(
|
|
210
225
|
"p",
|
|
211
226
|
{ key: "i", style: S.intro },
|
|
212
|
-
"\u914D\u7F6E\u4E00\u4E2A\u6216\u591A\u4E2A SAP \u7CFB\u7EDF\u8FDE\u63A5\uFF08ADT\uFF09\u3002\u5207\u6362\u4E3A\u542F\u7528\u540E\uFF0CSapBuddy \u4F1A\u8BDD\u7684 SAP \u5DE5\u5177\u9ED8\u8BA4\u4F7F\u7528\u8BE5\u8FDE\u63A5\uFF1B\u5207\u6362\u540E\u9700\u5728\u4F1A\u8BDD\u4E2D\u5148\u8C03\u7528 get_connected_systems \u786E\u8BA4\u3002\u5BC6\u7801\u4EC5\u4FDD\u5B58\u5728\u672C\u673A\u914D\u7F6E\u6587\u4EF6\u3002"
|
|
227
|
+
"\u914D\u7F6E\u4E00\u4E2A\u6216\u591A\u4E2A SAP \u7CFB\u7EDF\u8FDE\u63A5\uFF08ADT\uFF09\u3002\u5207\u6362\u4E3A\u542F\u7528\u540E\uFF0CSapBuddy \u4F1A\u8BDD\u7684 SAP \u5DE5\u5177\u9ED8\u8BA4\u4F7F\u7528\u8BE5\u8FDE\u63A5\uFF1B\u5207\u6362\u540E\u9700\u5728\u4F1A\u8BDD\u4E2D\u5148\u8C03\u7528 get_connected_systems \u786E\u8BA4\u3002\u5BC6\u7801\u4EC5\u4FDD\u5B58\u5728\u672C\u673A\u914D\u7F6E\u6587\u4EF6\u3002\u5730\u5740\u8981\u5199\u5168\u534F\u8BAE\uFF1Ahttp:// \u7528\u4E8E ICM HTTP \u7AEF\u53E3\uFF08\u5E38\u89C1 8000\uFF09\uFF0Chttps:// \u7528\u4E8E HTTPS \u7AEF\u53E3\uFF08\u5E38\u89C1 44300\uFF09\u3002"
|
|
213
228
|
),
|
|
214
229
|
msg ? React.createElement("p", { key: "m", style: S.msg }, msg) : null,
|
|
215
230
|
err ? React.createElement("p", { key: "e", style: S.err }, err) : null,
|
package/dist/index.js
CHANGED
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import http from "node:http";
|
|
1
2
|
import https from "node:https";
|
|
2
3
|
import { listConnections, saveConnection, deleteConnection, activateConnection, getConnectionSecret, } from "./sap-tools/connections-store.js";
|
|
3
4
|
const ROUTES = [
|
|
@@ -33,22 +34,95 @@ function readBody(req) {
|
|
|
33
34
|
req.on("error", reject);
|
|
34
35
|
});
|
|
35
36
|
}
|
|
37
|
+
/**
|
|
38
|
+
* 把连接配置规范成一次 ADT 探活请求。
|
|
39
|
+
*
|
|
40
|
+
* 注意两件事(曾经踩过的坑):
|
|
41
|
+
* 1. 协议必须跟随配置里的 URL:SAP 的 ICM HTTP 端口(常见 8000)是 **http**,
|
|
42
|
+
* HTTPS 端口(常见 44300)才是 https。写死 https 会让 http 连接永远探测失败。
|
|
43
|
+
* 2. 客户端号/语言要与真实工具调用一致——通过查询参数 `sap-client` / `sap-language` 传
|
|
44
|
+
* (abap-adt-api 登录就是这么做的),同时保留 `X-sap-login-client` 头兼容只认该头的系统。
|
|
45
|
+
* 只发头不发参数时,部分系统会忽略头、用默认客户端登录,于是返回 401。
|
|
46
|
+
*
|
|
47
|
+
* @returns {{ error: string } | { scheme: "http" | "https", url: URL, headers: Record<string, string> }}
|
|
48
|
+
*/
|
|
49
|
+
export function buildProbeRequest(conf) {
|
|
50
|
+
const raw = String(conf?.url ?? "").trim().replace(/\/+$/, "");
|
|
51
|
+
if (!/^https?:\/\//i.test(raw))
|
|
52
|
+
return { error: `连接地址要以 http:// 或 https:// 开头(当前是 "${raw || "(空)"}")。` +
|
|
53
|
+
`例如 http://10.8.202.35:8000(ICM HTTP 端口)或 https://10.8.202.35:44300(HTTPS 端口)` };
|
|
54
|
+
let url;
|
|
55
|
+
try {
|
|
56
|
+
url = new URL(`${raw}/sap/bc/adt/compatibility/graph`);
|
|
57
|
+
}
|
|
58
|
+
catch {
|
|
59
|
+
return { error: `连接地址不合法: ${raw}` };
|
|
60
|
+
}
|
|
61
|
+
const scheme = url.protocol === "https:" ? "https" : "http";
|
|
62
|
+
const qs = new URLSearchParams();
|
|
63
|
+
if (conf.client)
|
|
64
|
+
qs.set("sap-client", String(conf.client));
|
|
65
|
+
if (conf.language)
|
|
66
|
+
qs.set("sap-language", String(conf.language));
|
|
67
|
+
if ([...qs.keys()].length > 0)
|
|
68
|
+
url.search = qs.toString();
|
|
69
|
+
const headers = {
|
|
70
|
+
Authorization: "Basic " + Buffer.from(`${conf.username}:${conf.password ?? ""}`).toString("base64"),
|
|
71
|
+
Accept: "*/*",
|
|
72
|
+
};
|
|
73
|
+
if (conf.client)
|
|
74
|
+
headers["X-sap-login-client"] = String(conf.client);
|
|
75
|
+
return { scheme, url, headers };
|
|
76
|
+
}
|
|
77
|
+
/** 把探活拿到的 HTTP 状态码翻译成能直接照做的排查建议;null 表示成功 */
|
|
78
|
+
export function describeProbeStatus(status, conf) {
|
|
79
|
+
if (status >= 200 && status < 300)
|
|
80
|
+
return null;
|
|
81
|
+
if (status === 401)
|
|
82
|
+
return "认证失败(HTTP 401):服务器拒绝了账号/密码。按顺序排查:\n" +
|
|
83
|
+
`① 客户端号是否与系统一致(当前填的是 ${conf.client || "空"},登录时作为 sap-client 发送);\n` +
|
|
84
|
+
`② 用户名/密码是否正确、账号是否被锁或过期(当前用户 ${conf.username || "空"});\n` +
|
|
85
|
+
`③ 语言 ${conf.language || "空"} 是否为该账号可用语言(不确定先填 EN);\n` +
|
|
86
|
+
"④ 该客户端是否允许 ADT 登录(SICF 里 /sap/bc/adt 的登录过程)。";
|
|
87
|
+
if (status === 403)
|
|
88
|
+
return "HTTP 403:账号认证通过但无权访问 ADT,或 /sap/bc/adt 服务未激活/被限制。请让 BASIS 检查 SICF 与用户 ADT 权限。";
|
|
89
|
+
if (status === 404)
|
|
90
|
+
return "HTTP 404:这个地址上没有 ADT 服务。确认端口是 SAP 的 HTTP/HTTPS 端口(8000/44300),且 /sap/bc/adt 已在 SICF 激活。";
|
|
91
|
+
if (status >= 500)
|
|
92
|
+
return `HTTP ${status}:SAP 服务端错误(ICM/ADT 异常),请稍后重试或联系 BASIS。`;
|
|
93
|
+
return `HTTP ${status}:未预期的响应,请检查地址与端口。`;
|
|
94
|
+
}
|
|
95
|
+
/** 网络层失败时给出"协议/端口写反"这类最常见的提示 */
|
|
96
|
+
function describeProbeFailure(message, scheme) {
|
|
97
|
+
if (/wrong version number|SSL routines|EPROTO|packet length too long|socket hang up/i.test(message))
|
|
98
|
+
return `连接失败: ${message}\n` +
|
|
99
|
+
`协议和端口对不上:这个端口很可能是 ${scheme === "https" ? "纯 HTTP(用 http://)" : "HTTPS(用 https://)"}。` +
|
|
100
|
+
"SAP 的 ICM HTTP 端口(常见 8000)要用 http://,HTTPS 端口(常见 44300)要用 https://。";
|
|
101
|
+
if (/certificate|self-signed|SELF_SIGNED|CERT_|unable to verify/i.test(message))
|
|
102
|
+
return `连接失败: ${message}\n证书不被信任:内部/自签名证书请在连接里开启「允许自签名证书」。`;
|
|
103
|
+
if (/ENOTFOUND|EAI_AGAIN/i.test(message))
|
|
104
|
+
return `连接失败: 主机名解析不了(${message})。检查地址拼写,或改用 IP。`;
|
|
105
|
+
if (/ECONNREFUSED/i.test(message))
|
|
106
|
+
return `连接失败: 端口没有监听(${message})。检查端口号与 SAP 实例状态。`;
|
|
107
|
+
if (/timeout|ETIMEDOUT|超时/i.test(message))
|
|
108
|
+
return `连接失败: 连接超时(${message})。检查网络/防火墙是否放通该端口。`;
|
|
109
|
+
return `连接失败: ${message}`;
|
|
110
|
+
}
|
|
36
111
|
/** 测试连接:用连接明文凭据做一次 ADT 请求(结果只回状态码,不回敏感信息)。
|
|
37
|
-
* 用 node:https 直连以支持 ssl.allowSelfSigned(Node fetch 不支持该选项)。 */
|
|
112
|
+
* 用 node:http/https 直连以支持 ssl.allowSelfSigned(Node fetch 不支持该选项)。 */
|
|
38
113
|
async function testConnection(id) {
|
|
39
114
|
const c = getConnectionSecret(id);
|
|
40
115
|
if (!c)
|
|
41
116
|
return { ok: false, status: 0, message: `连接 ${id} 不存在` };
|
|
42
|
-
const
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
headers["X-sap-login-client"] = c.client;
|
|
117
|
+
const probe = buildProbeRequest(c);
|
|
118
|
+
if (probe.error)
|
|
119
|
+
return { ok: false, status: 0, message: probe.error };
|
|
120
|
+
const transport = probe.scheme === "https" ? https : http;
|
|
47
121
|
try {
|
|
48
122
|
const status = await new Promise((resolve, reject) => {
|
|
49
|
-
const req =
|
|
123
|
+
const req = transport.request(probe.url, {
|
|
50
124
|
method: "GET",
|
|
51
|
-
headers,
|
|
125
|
+
headers: probe.headers,
|
|
52
126
|
rejectUnauthorized: !(c.ssl?.allowSelfSigned ?? false),
|
|
53
127
|
timeout: 15_000,
|
|
54
128
|
}, (res) => { res.resume(); resolve(res.statusCode ?? 0); });
|
|
@@ -56,11 +130,11 @@ async function testConnection(id) {
|
|
|
56
130
|
req.on("error", reject);
|
|
57
131
|
req.end();
|
|
58
132
|
});
|
|
59
|
-
const
|
|
60
|
-
return { ok, status, message:
|
|
133
|
+
const detail = describeProbeStatus(status, c);
|
|
134
|
+
return detail ? { ok: false, status, message: detail } : { ok: true, status, message: "连接成功" };
|
|
61
135
|
}
|
|
62
136
|
catch (e) {
|
|
63
|
-
return { ok: false, status: 0, message:
|
|
137
|
+
return { ok: false, status: 0, message: describeProbeFailure(e instanceof Error ? e.message : String(e), probe.scheme) };
|
|
64
138
|
}
|
|
65
139
|
}
|
|
66
140
|
/**
|
|
@@ -55,10 +55,19 @@ function validate(input) {
|
|
|
55
55
|
return "连接 ID 不能为空";
|
|
56
56
|
if (!/^[A-Za-z0-9_-]+$/.test(id))
|
|
57
57
|
return "连接 ID 只能包含字母/数字/下划线/连字符";
|
|
58
|
-
|
|
58
|
+
const url = String(input.url ?? "").trim();
|
|
59
|
+
if (!url)
|
|
59
60
|
return "连接地址(url)不能为空";
|
|
61
|
+
// 协议必须显式写:SAP 的 ICM HTTP 端口(常见 8000)是 http,HTTPS 端口(常见 44300)才是 https。
|
|
62
|
+
// 不写协议时真实工具与探测都会拿不到明确结论,干脆在保存时就拦下。
|
|
63
|
+
if (!/^https?:\/\//i.test(url))
|
|
64
|
+
return "连接地址要以 http:// 或 https:// 开头(ICM HTTP 端口常见 8000 用 http://,HTTPS 端口常见 44300 用 https://)";
|
|
60
65
|
if (!String(input.username ?? "").trim())
|
|
61
66
|
return "用户名不能为空";
|
|
67
|
+
// 客户端号就是登录时的 sap-client,填错(比如系统是 300 却填 200)会直接 401
|
|
68
|
+
const client = String(input.client ?? "").trim();
|
|
69
|
+
if (client && !/^\d{1,3}$/.test(client))
|
|
70
|
+
return "客户端号应为 1-3 位数字(如 100)";
|
|
62
71
|
return undefined;
|
|
63
72
|
}
|
|
64
73
|
/** 新建或更新连接;更新时密码为空/'***' 保持原密码。返回 {ok} 或 {ok:false,error} */
|
|
@@ -72,12 +81,13 @@ export function saveConnection(input) {
|
|
|
72
81
|
const password = input.password === undefined || input.password === "" || input.password === PASSWORD_MASK
|
|
73
82
|
? existing?.password ?? ""
|
|
74
83
|
: input.password;
|
|
84
|
+
const rawClient = String(input.client ?? existing?.client ?? "").trim();
|
|
75
85
|
const next = {
|
|
76
86
|
...existing,
|
|
77
87
|
...input,
|
|
78
88
|
id,
|
|
79
89
|
password,
|
|
80
|
-
client:
|
|
90
|
+
client: rawClient ? rawClient.padStart(3, "0") : rawClient,
|
|
81
91
|
language: input.language ?? existing?.language ?? "ZH",
|
|
82
92
|
authMethod: input.authMethod ?? existing?.authMethod ?? "basic",
|
|
83
93
|
};
|
package/package.json
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "sapbuddy-dsh",
|
|
3
|
-
"version": "0.0.
|
|
4
|
-
"description": "SapBuddy —— SAP ABAP AI 全能助手(DeepSeek Harness 插件):41 个 SAP 工具 +
|
|
3
|
+
"version": "0.0.9",
|
|
4
|
+
"description": "SapBuddy —— SAP ABAP AI 全能助手(DeepSeek Harness 插件):41 个 SAP 工具 + 10 个 ABAP 技能 + SapBuddy preset + 连接设置页。一个包名装完。",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"main": "dist/index.js",
|
|
7
7
|
"exports": {
|
|
@@ -77,10 +77,14 @@
|
|
|
77
77
|
"typescript": "^5.6.0"
|
|
78
78
|
},
|
|
79
79
|
"scripts": {
|
|
80
|
-
"test": "npm run test:activation && npm run test:session && npm run test:store",
|
|
80
|
+
"test": "npm run test:activation && npm run test:session && npm run test:store && npm run test:probe && npm run test:route && npm run test:bundle && npm run test:sap-fs",
|
|
81
81
|
"test:activation": "node --experimental-vm-modules --test --test-isolation=none test/activation.test.mjs",
|
|
82
82
|
"test:session": "node --experimental-vm-modules --test --test-isolation=none test/session-recovery.test.mjs",
|
|
83
|
-
"test:store": "node --test --test-isolation=none test/connections-store.test.mjs"
|
|
83
|
+
"test:store": "node --test --test-isolation=none test/connections-store.test.mjs",
|
|
84
|
+
"test:probe": "node --test --test-isolation=none test/connection-probe.test.mjs",
|
|
85
|
+
"test:route": "node --test --test-isolation=none test/connection-test-route.test.mjs",
|
|
86
|
+
"test:bundle": "node --test --test-isolation=none test/client-bundle.test.mjs",
|
|
87
|
+
"test:sap-fs": "node --test --test-isolation=none test/sap-fs/loading.test.mjs"
|
|
84
88
|
},
|
|
85
89
|
"publishConfig": {
|
|
86
90
|
"access": "public"
|
|
@@ -0,0 +1,43 @@
|
|
|
1
|
+
---
|
|
2
|
+
name: grilling
|
|
3
|
+
description: >-
|
|
4
|
+
Grill the user relentlessly about a plan, decision, or idea. Use when the user
|
|
5
|
+
wants to stress-test their thinking, or uses any 'grill' trigger phrases
|
|
6
|
+
(including "grill me"). Also used by sap-fs when clarifying FS business decisions;
|
|
7
|
+
not the entry skill for FS intent (load via sap-fs after user confirms FS deliverable).
|
|
8
|
+
---
|
|
9
|
+
|
|
10
|
+
<!--
|
|
11
|
+
Third-party: mattpocock/skills (MIT, Copyright 2026 Matt Pocock)
|
|
12
|
+
Upstream: https://github.com/mattpocock/skills
|
|
13
|
+
skills/productivity/grilling/SKILL.md
|
|
14
|
+
Pinned blob: df69d9936880cd3314c28858c6c52f48db23b856 (fetched 2026-10-28)
|
|
15
|
+
Adaptation: bundled under sapbuddy-dsh/skills for DSH discovery; description notes sap-fs;
|
|
16
|
+
"grill me" triggers are covered here directly (no separate grill-me alias).
|
|
17
|
+
-->
|
|
18
|
+
|
|
19
|
+
Interview the user relentlessly until you reach a shared understanding. Map this as a **design tree**: every decision branches into the decisions that hang off it.
|
|
20
|
+
|
|
21
|
+
Work the tree in **rounds**. The **frontier** is every decision whose prerequisites are already settled: the questions you can ask _now_ without guessing at answers you haven't heard yet. Ask the whole frontier in one round: number each question and give your recommended answer. Then wait for the user's answers before the next round.
|
|
22
|
+
|
|
23
|
+
Format a round like so:
|
|
24
|
+
|
|
25
|
+
```
|
|
26
|
+
❓ **Q1** - **<question title>**: <question body, might be multiple paragraphs, including multiple choices>
|
|
27
|
+
|
|
28
|
+
➡️ <your recommended answer>
|
|
29
|
+
|
|
30
|
+
---
|
|
31
|
+
|
|
32
|
+
❓ **Q2** - **<question title>**: <question body, might be multiple paragraphs, including multiple choices>
|
|
33
|
+
|
|
34
|
+
➡️ <your recommended answer>
|
|
35
|
+
```
|
|
36
|
+
|
|
37
|
+
Word each question so "yes" accepts your recommended answer.
|
|
38
|
+
|
|
39
|
+
Each round the user answers reshapes the tree: settled decisions push the frontier outward and unblock questions that depended on them. Recompute the frontier and ask the next round. A question whose answer depends on another question still open in this round belongs to a _later_ round, not this one.
|
|
40
|
+
|
|
41
|
+
Finding _facts_ is your job, never the user's. When a frontier question needs a fact from the environment (filesystem, tools, etc.), dispatch a sub-agent to find it; don't ask the user for anything you could look up yourself. Don't block on it: a running exploration is an unsettled prerequisite, so only the questions downstream of it wait for the sub-agent to report; ask the rest of the frontier now. The _decisions_ are the user's: put each to them and wait.
|
|
42
|
+
|
|
43
|
+
The session is done when the frontier is empty: every branch of the design tree visited, nothing left silently assumed. Do not act on it until the user confirms you have reached a shared understanding.
|
|
@@ -0,0 +1,76 @@
|
|
|
1
|
+
---
|
|
2
|
+
name: sap-fs
|
|
3
|
+
description: >-
|
|
4
|
+
编写 SAP 功能规格书(FS / 功能设计书)。仅在用户明确要求写/改 FS、功能设计书、
|
|
5
|
+
接口设计、报表设计、增强设计,或明确说按 sap-fs 出可交付规格文档时触发。
|
|
6
|
+
日常问答、配置咨询、解释程序、代码审查、写 ABAP 不触发;意图不明时先问是否出 FS。
|
|
7
|
+
确认后:识别需求类型 → 系统取证 → 用 grilling 追问业务决策 → 按模板生成 FS。
|
|
8
|
+
触发关键词:写FS、功能规格、功能设计书、接口设计、报表设计、增强设计、出一份FS。
|
|
9
|
+
agent_created: true
|
|
10
|
+
disable: false
|
|
11
|
+
---
|
|
12
|
+
|
|
13
|
+
# sap-fs — 写 SAP 功能规格书
|
|
14
|
+
|
|
15
|
+
> 草稿结构(Codex 第一版目录)。模板在 `assets/templates/`,过程规范在 `references/`。
|
|
16
|
+
> 追问逻辑不内嵌:加载 `grilling`。上游许可与钉选说明见 `grilling/SKILL.md` 顶部注释。
|
|
17
|
+
|
|
18
|
+
## 何时使用
|
|
19
|
+
|
|
20
|
+
- 用户**明确**要写 / 改 FS、功能设计书、接口 / 报表 / 增强**规格文档**,或明确说按 sap-fs 出可交付文档。
|
|
21
|
+
- 用户确认「要出 FS」之后(见 SYSTEM 铁律 FS 意图门禁)。
|
|
22
|
+
|
|
23
|
+
不触发:日常问答、配置怎么配、表/事务码解释、程序解释(`abap-explain` / `abap-wiki`)、代码审查(`abap-code-review`)、写 ABAP(`dev-write` / `abap-templates`)。
|
|
24
|
+
意图不明(只抛业务描述 / 方案讨论、未说要出 FS):**先问一句**是否要按 sap-fs 产出 `FS.md` + `FS.html`(接口另附 JSON);未确认前不套模板、不落盘 `output/fs/`。
|
|
25
|
+
|
|
26
|
+
## 资源地图(按需加载,勿一次全读)
|
|
27
|
+
|
|
28
|
+
| 时机 | 读什么 |
|
|
29
|
+
| --- | --- |
|
|
30
|
+
| 触发后立刻 | 本文件 + `references/fs-contract.md` |
|
|
31
|
+
| 需要连系统 | `references/system-verification.md` |
|
|
32
|
+
| 需要追问 | `references/grilling-integration.md`,并 **Skill 加载 `grilling`**(含 “grill me” 类触发) |
|
|
33
|
+
| 已判定类型 | `references/types/<type>.md` + `assets/templates/fs-base.md` + `assets/templates/<type>.md` |
|
|
34
|
+
| 对齐详略 | `references/example-enhancement.md`(仅增强类样例) |
|
|
35
|
+
| 交付前 | `references/review-checklist.md` |
|
|
36
|
+
| 出归档 HTML / 接口 JSON | `references/archive-html.md` + `assets/templates/fs-archive.html` |
|
|
37
|
+
|
|
38
|
+
类型文件名:`interface` / `query-report` / `functional-report` / `enhancement`。
|
|
39
|
+
|
|
40
|
+
## 工作流程
|
|
41
|
+
|
|
42
|
+
1. **判类型**(混合取主导;ALV 按钮会改业务状态 → 功能报表)。读对应 `types/` 与模板。
|
|
43
|
+
2. **整理已知**:用户已说内容分到需求 / 现状 / 规则,先不提问。
|
|
44
|
+
3. **取证**:按 `system-verification.md` 只查能查的。
|
|
45
|
+
4. **追问**:按 `grilling-integration.md` 加载 `grilling`,只问业务决策。
|
|
46
|
+
5. **生成**:拼装 `fs-base` + 类型专项模板;规则 / 验收 / 待确认 / 附录按 `fs-contract.md`。
|
|
47
|
+
6. **自检**:`review-checklist.md` 通过后再交付。
|
|
48
|
+
|
|
49
|
+
## 拼装
|
|
50
|
+
|
|
51
|
+
```
|
|
52
|
+
assets/templates/fs-base.md # 第1、2、4、5、6、7节 + 附录A
|
|
53
|
+
+ assets/templates/<type>.md # 第3节
|
|
54
|
+
+ 头部状态行
|
|
55
|
+
= 一份完整 FS
|
|
56
|
+
```
|
|
57
|
+
|
|
58
|
+
默认落盘目录(用户未指定时):`output/fs/<需求名>/`(允许的 `output` 子树);详见下方交付物。
|
|
59
|
+
|
|
60
|
+
## 交付物(Markdown + HTML,接口另附 JSON)
|
|
61
|
+
|
|
62
|
+
同一需求目录默认:`output/fs/<需求名>/`
|
|
63
|
+
|
|
64
|
+
1. `FS.md` — 协作 / AI 主规格。
|
|
65
|
+
2. `FS.html` — 企业归档:套 `assets/templates/fs-archive.html`,规则见 `references/archive-html.md`。
|
|
66
|
+
3. **禁止 logo**(徽标、水印、品牌图都不放)。
|
|
67
|
+
4. **接口类**另出:正文 3.6 样例 JSON + 同目录 `io-request.json` / `io-response.json`(及必要的回调文件)。
|
|
68
|
+
|
|
69
|
+
先定稿 Markdown,再渲染 HTML / 写出 JSON;自检通过后一并交付路径。
|
|
70
|
+
|
|
71
|
+
## 硬边界
|
|
72
|
+
|
|
73
|
+
- 本技能流程**只读 SAP**;FS 确认 ≠ 写入许可。
|
|
74
|
+
- 正文只写事实与决定;训诫与生成纪律留在技能文件,不进 FS。
|
|
75
|
+
- 不虚构表名 / 字段 / 增强点 / 签署与测试结果。
|
|
76
|
+
- HTML 归档不使用任何 logo。
|
|
@@ -0,0 +1,52 @@
|
|
|
1
|
+
<!-- 本片段插入骨架第3节。增强只描述"在哪个时点、改变哪段行为、其余保持原样"。
|
|
2
|
+
第2节写整体现状;这里只写本次涉及的动作。 -->
|
|
3
|
+
|
|
4
|
+
## 3. 增强设计
|
|
5
|
+
|
|
6
|
+
### 3.1 改变的行为
|
|
7
|
+
|
|
8
|
+
| 动作 | 现状 | 目标 | 保持不变 |
|
|
9
|
+
| --- | --- | --- | --- |
|
|
10
|
+
| <保存 / 过账 / 审批> | <现在怎么处理> | <改变后怎么处理> | <仍须沿用的标准行为> |
|
|
11
|
+
|
|
12
|
+
**实施方式**:<标准配置能否满足;选增强的理由。未验证的判断标 Q。>
|
|
13
|
+
|
|
14
|
+
### 3.2 触发点
|
|
15
|
+
|
|
16
|
+
| 入口 | 时点 | 触发前状态 | 取得的数据 |
|
|
17
|
+
| --- | --- | --- | --- |
|
|
18
|
+
| <事务码 / 接口 / 后台> | <校验阶段 / 保存前 / 提交后> | <订单已保存 / 未保存> | <当前输入值 / 数据库旧值> |
|
|
19
|
+
|
|
20
|
+
- **覆盖入口**:<前台、后台、接口分别是否覆盖;未确认的标 Q。>
|
|
21
|
+
- **新旧值**:<规则用的是用户当前输入,还是已落库的值;修改后是否重新校验。>
|
|
22
|
+
- **未保存数据**:<字段尚未落库时以什么为准。>
|
|
23
|
+
- **派生值**:<标准逻辑或其它增强是否也会改同一字段;先后关系。>
|
|
24
|
+
|
|
25
|
+
### 3.3 适用与排除
|
|
26
|
+
|
|
27
|
+
| 项目 | 定义 |
|
|
28
|
+
| --- | --- |
|
|
29
|
+
| 命中条件 | <组织、单据类型、业务状态、角色> |
|
|
30
|
+
| 排除条件 | <必须绕过增强的场景> |
|
|
31
|
+
| 与其它规则的优先级 | <同时命中时先后;先报哪条> |
|
|
32
|
+
| 不命中时 | <维持原有行为> |
|
|
33
|
+
| 既有校验先失败时 | <是否继续执行本增强;用户最终看到什么> |
|
|
34
|
+
|
|
35
|
+
### 3.4 处理逻辑
|
|
36
|
+
|
|
37
|
+
<按顺序写:取数 → 判断 → 结果。引用 R-01 等规则编号,不在这里重复条件。>
|
|
38
|
+
|
|
39
|
+
### 3.5 重复触发与事务
|
|
40
|
+
|
|
41
|
+
| 场景 | 期望 |
|
|
42
|
+
| --- | --- |
|
|
43
|
+
| 同一操作多次触发 | <哪些可重复,哪些副作用只能发生一次> |
|
|
44
|
+
| 保存后再次修改 | <哪些字段变化需要重新判断> |
|
|
45
|
+
| 标准动作最终失败 | <本次已产生的变化是否必须一同撤销> |
|
|
46
|
+
| 已调用外部系统 | <外部成功但本地失败的核对与补偿> |
|
|
47
|
+
|
|
48
|
+
### 3.6 停用与回归
|
|
49
|
+
|
|
50
|
+
- **停用后**:<恢复原有行为;已保存数据不回滚。>
|
|
51
|
+
- **在途业务**:<处理中的单据、任务、外部请求如何处理。>
|
|
52
|
+
- **回归范围**:<既有增强、标准校验、相关模块(凭证流、库存、财务)需要复测的部分。>
|