5 Commits

5 changed files with 165 additions and 39 deletions
+35 -2
View File
@@ -58,6 +58,25 @@ profile 的 `package.json`:
改完执行 `pnpm install`(关闭 DSH 后执行最稳,避免 Windows 上正在使用的原生模块被占用),再启动。
### 方式三:DSH 桌面版(Electron)
桌面版也是 DSH,插件机制相同,但 profile 名是**保留的 `desktop`**,且由桌面应用自己管理。要点:
1. **前提版本**:桌面版需基于 DSH **≥ 0.2.1-rc.2**(本插件 `dsh.engines.dsh` 的要求;0.1.x 不支持预设声明行机制)。
2. **安装入口**:用**桌面版自带的插件管理界面**(设置 → 插件 / Plugin Manager)安装本仓库目录作为 bundle;
```text
install_bundle target = <本仓库克隆目录的绝对路径>
```
或安装一个可解析的包 / Git 仓库地址。
⚠️ 不要用 npm 全局安装的 `dsh` CLI 去管 `desktop` profile——CLI 会拒绝桌面版 profile 的启动与插件管理请求,必须使用桌面安装自带的命令运行时。
3. **依赖**:`@nefevcore/abap-adt-dsh-plugin` 会随包安装;桌面版的插件操作会使用其安装的包管理器(需能访问 npm registry)。
4. **重启桌面应用** → 新建会话选「ABAP 工作台」→ 设置 → ABAP ADT 配置目的地/模式(用法与 Web 版一致)。
5. **已知差异/注意**:
- 桌面版的 Web UI 同样加载本插件的 Client 半(设置页「ABAP ADT」+ 会话状态徽章),但 `dsh.client` 支持取决于桌面版是否加载第三方 client 插件;若设置页没有出现 ABAP ADT,先确认桌面版版本与插件是否真的激活。
- 内置 mock `demo` 会在 `127.0.0.1` 随机端口监听,桌面版可能弹一次防火墙提示(仅本机回环,放行即可/拒绝也不影响真实 SAP 目的地)。
- 工作区是普通目录,目的地/模式配置仍写在工作区 `.dsh-abap-adt/`,与 Web 版一致。
- Host 半只用 Node 内置模块;`@nefevcore` core 通过扫描 `~/.dsh/profiles/*` 定位,桌面版 profile 在 `~/.dsh/profiles/desktop` 下同样适用。
### 安装后
1. **重启 DSH**;
@@ -82,10 +101,12 @@ profile 的 `package.json`:
| 模式 | 策略 | 说明 |
|---|---|---|
| 只读 | `allowedPackages` 哨兵值、`enableTransports=false`、`allowTransportableEdits=false`、`allowExecution=false`、`allowDebugger=false` | 安全基线 |
| 调试 | 只读 + `allowExecution=true` + `allowDebugger=true` | 断点/单步/变量/栈 |
| 只读 | `allowedPackages` 哨兵值、`allowTransportableEdits=false`、`allowExecution=false`、`allowDebugger=false`;`enableTransports=true`(**仅用于读**传输请求/版本) | 安全基线:可搜索/读源码/读传输请求与版本/数据预览,写与激活全部拒绝 |
| 调试 | 只读 + `allowExecution=true` + `allowDebugger=true` | 额外允许执行触发断点、断点/单步/变量/栈;写与传输修改仍关闭 |
| 更改 | `allowTransportableEdits=true`、`enableTransports=true`、`allowExecution=true`、`allowDebugger=true`、白名单 | 写前 diff、传输请求纪律 |
> 说明:abap-adt 的 `enableTransports` 会把整个 transport 工具族(`adt_transports`、`adt_object_versions`、`adt_cochange`)一起关掉——这些在 0.12.2 里**都是只读工具**,所以只读/调试模式保持 `enableTransports=true`,真正拦写的是 `allowedPackages` 哨兵值 + `allowTransportableEdits=false`。
- 模式切换**只在设置页**,会话里不提供开关(安全);
- 只读/调试下白名单不生效但会保留,切回更改时复用;
- 白名单存在 `<workspace>/.dsh-abap-adt/ui-settings.json`(本插件私有,abap-adt 不会读取)。
@@ -97,6 +118,18 @@ profile 的 `package.json`:
- 改名(标识)时默认目的地下拉会自动跟随,不会出现 `defaultDestination ... is not a destination name`;
- 会话状态徽章、目的地表格、默认目的地选择都会显示「显示名(标识)」。
### 预设内建规则(来自 2026-10 实战复盘)
预设 persona 已内建以下纪律(细节来自内部复盘文档,未随仓库发布):
1. **动手前澄清**:交付几个对象(多表/多实体不得自行合并)、是否沿用参考程序结构(include 拆分)、包名与传输号、哪些必须由用户在 GUI 完成(GUI 状态、文本元素、SMW0、SE41)。
2. **写前决策闸门**:凡是为绕开工具/系统限制而改变形态或既有约定的动作,先停下请示并给 2–3 个方案与代价,由用户决策;纯机械的编译器对齐可直接做但须报告。
3. **工具能力边界**:`adt_batch` 404、`adt_where_used` 405、`adt_search` quickSearchSource 不支持、`INCL` 不能新建、`FUNC` 可能建出函数组、DDIC 写能力可能不可用、`adt_object_read` 可能只回摘要(改用 `adt_export_objects`)、GUI 状态/文本元素/SMW0 无工具。
4. **传输纪律**:写对象时 `transport` 传**父请求号**(传任务号会报“已在请求中锁定”);对象常挂在任务号,先 `E070`(K=请求/S=任务,D=开放/R=已释放)再 `E071`;容器与成员可能在不同请求;`adt_unlock_all` 不管传输占用。
5. **验证阶梯(禁止跳步)**:`adt_check → adt_activate`(相关对象一次传齐)`→ adt_version_diff → TRDIR-SUBC → adt_run_atc → 报告`;`adt_check` PASSED ≠ 可激活;工具成功文案 ≠ 落地成功(DDIC 看 `AS4LOCAL`,源码导出比对)。
6. **DDIC 走“Agent 出规格 + 人工 SE11”**:探针优先(`$TMP` 一次性对象,验完即删),失败最多两轮。
7. **`adt_data_preview` 硬约束**:SQL ≤255 字符(IN 拆包)、列名先查、OR+LIKE/COUNT/GROUP BY 可能被拒、并行查询偶发 400。
### 安全
- 只读 ≠ 无害:仍能读取 SAP 源码与业务数据,建议连开发/测试系统 + 最小权限账号;
+23 -18
View File
@@ -38,8 +38,8 @@ window.__ModuleLoader__.load({
modeTitle: '运行模式(本工作区)',
modes: { 'read-only': '只读', debug: '调试', change: '更改' },
modeHints: {
'read-only': '只读:写/激活/删除/传输/执行/调试全部关闭,只做调查与补丁建议。',
debug: '调试:允许执行以触发断点,可用 adt_debug 设断点/单步/看变量;写与传输仍关闭。',
'read-only': '只读:写/激活/删除/执行/调试关闭;可搜索、读源码、读传输请求与版本、数据预览。',
debug: '调试:在只读基础上允许执行以触发断点,可用 adt_debug;写与传输修改仍关闭。',
change: '更改:按白名单可写(含传输,每次写需给请求号),允许执行与调试器;写前先给用户看 diff。',
},
modeSwitched: '运行模式已切换',
@@ -101,8 +101,8 @@ window.__ModuleLoader__.load({
modeTitle: 'Run mode (this workspace)',
modes: { 'read-only': 'Read-only', debug: 'Debug', change: 'Change' },
modeHints: {
'read-only': 'Read-only: writes, activation, deletion, transports, execution and the debugger are off; investigate and propose patches only.',
debug: 'Debug: execution is allowed to trigger breakpoints; adt_debug is available; writes and transports stay off.',
'read-only': 'Read-only: writes, activation, deletion, execution and the debugger are off; search, read source, READ transport requests/versions and preview data.',
debug: 'Debug: read-only plus execution to trigger breakpoints and adt_debug; writes and transport changes stay off.',
change: 'Change: whitelisted packages are writable (transports need the request number on every write), execution and the debugger are allowed; show a diff before writing.',
},
modeSwitched: 'Run mode switched',
@@ -431,22 +431,32 @@ window.__ModuleLoader__.load({
)
: null
const workspacePaths = workspaces.map((item) => item.path).filter(Boolean)
const workspaceOptions =
workspace && !workspacePaths.includes(workspace) ? [workspace, ...workspacePaths] : workspacePaths
const workspaceTitles = Object.fromEntries(workspaces.map((item) => [item.path, item.title]))
const workspaceRow = h(
'div',
{ style: { display: 'grid', gridTemplateColumns: '2fr 1fr', gap: 12, marginTop: 16 } },
h(
Field,
{ label: DICTS.zh.workspace, hint: DICTS.zh.workspaceHint },
h('input', {
style: inputStyle,
value: workspace,
list: 'abap-adt-workspaces',
onChange: (event) => setWorkspace(event.target.value),
onBlur: () => {
if (workspace && workspace !== file?.path?.replace(/[\\/]\.dsh-abap-adt[\\/].*$/, '')) load(workspace)
h(
'select',
{
style: inputStyle,
value: workspace,
onChange: (event) => {
const next = event.target.value
setWorkspace(next)
if (next) load(next)
},
},
placeholder: 'D:\\AIagent\\SAP',
}),
workspaceOptions.map((path) => {
const title = workspaceTitles[path]
return h('option', { key: path, value: path }, title && title !== path ? title + ' — ' + path : path)
}),
),
),
h(
Field,
@@ -457,11 +467,6 @@ window.__ModuleLoader__.load({
file?.path ? `${file.path}${file.exists ? '' : DICTS.zh.notExists}` : DICTS.zh.never,
),
),
h(
'datalist',
{ id: 'abap-adt-workspaces' },
workspaces.map((item) => h('option', { key: item.id || item.path, value: item.path }, item.title)),
),
)
const destinationCards = destinations.length
+50 -12
View File
@@ -27,16 +27,52 @@
name: '@deepseek-ai/dsh-persona'
config:
prefix: |-
你是 SAP ABAP 工作台代理(只读 / 调试 / 更改三种模式由当前工作区的策略决定),运行在 DeepSeek Harness 上(模型 {{model}}),通过 adt_* 工具经 ADT REST 访问 SAP 系统。当前工作目录见运行时上下文。
你是 SAP ABAP 工作台代理(只读 / 调试 / 更改三种运行模式由当前工作区的权限策略决定),运行在 DeepSeek Harness 上(模型 {{model}}),通过 adt_* 工具经 ADT REST 访问 SAP。当前工作目录见运行时上下文。
第一步永远是:adt_permissions 查看当前生效策略,adt_list_destinations 看目的地;引用目的地必须用精确名字(拼错会报错,不会静默落到默认系统)。模式以 adt_permissions 的结果为准,不要凭猜测——同一条指令在不同工作区可能是不同模式。
第一步永远是:adt_permissions 看生效策略,adt_list_destinations 看目的地;目的地必须用精确名字(拼错会报错,不会静默落到默认系统)。模式以 adt_permissions 的结果为准,不要凭猜测——同一条指令在不同工作区可能是不同模式。
三种模式与纪律:
1) 只读模式(写/激活/删除/传输/执行/调试均被拒):只做调查与解释,输出结论 + 证据 + 建议补丁;遇到 [POLICY] 拒绝时如实告诉用户,不要换参数重试,可建议用户在该工作区的设置页把模式切到「调试」或「更改」。
2) 调试模式(只读 + 允许执行 + 调试器):先用只读手段定位(adt_search / adt_object_read / adt_where_used / adt_dumps);adt_debug 是唯一调试工具:先 action:"setBreakpoint",再触发代码路径,然后 action:"listen" 长轮询;命中后用 variables / stack / step(stepInto/stepOver/stepReturn/stepContinue)检查;结束必须 action:"detach"——一个目的地一个调试会话,detach 后不能重连。断点只对目标用户的客户代码生效,命中一次后要重新设置;用 adt_execute 触发程序或 if_oo_adt_classrun 类。调试变量写值默认关闭,需要时由用户显式开启。
3) 更改模式(按当前工作区白名单可写(默认 Z* 与 $TMP)、允许传输、允许执行与调试器):传输请求纪律最高优先——对可传输包的任何修改前先问用户要请求号(adt_transports {status:"modifiable"} 可列出打开请求),每次写操作显式传 transport 参数,绝不让后端自己开 task;$TMP 本地包不需要请求号;包归属不清楚时先问。释放传输是人的决定,没有对应工具,也不主动提议。写前必须先给用户看 Before/After diff(结构类型用字段级 patch,源码类型给完整新源码)并等确认;读后改:adt_object_read 建快照,adt_object_edit 在锁下校验,[CONFLICT] 重新读重新改,persisted:false 视为硬失败;顺序 check(adt_check)→ lock → write → activate,激活不级联(主程序与 include 要在一次 adt_activate 里),HTTP 200 不等于激活成功。验证阶梯:adt_run_unit_tests → adt_run_atc → adt_execute;数据用 adt_data_preview(ABAP SQL:DESCENDING、无 LIMIT、length/offset 分页)。做到 release-ready 为止,报告改了哪些对象、记到哪个请求号、check/激活/单测/ATC 结论。
【三种模式】
1) 只读:可搜索、读源码、读传输请求与版本(enableTransports 只影响只读传输工具族)、数据预览、短转储;写/激活/删除/执行/调试被 [POLICY] 拒绝。如实报告拒绝,不换参数重试,可建议用户切「调试」或「更改」。
2) 调试:只读 + 允许执行(触发断点)+ 调试器。adt_debug 唯一调试工具:先 action:"setBreakpoint",再触发代码路径,然后 action:"listen" 长轮询;命中后用 variables/stack/step;结束必须 action:"detach"(一个目的地一个调试会话,detach 后不能重连);断点仅对客户代码生效且命中一次。调试变量写值默认关闭。
3) 更改:按当前工作区白名单可写(默认 Z* 与 $TMP)、允许传输、允许执行与调试器。写前必须先给用户看 Before/After diff 并等确认;读后改(OCC 快照,[CONFLICT] 重读重改,persisted:false 视为硬失败);激活不级联(主程序与 include 必须在一次 adt_activate 里传齐);释放传输是人的决定,没有工具,不主动提议。
通用规则:先读后答,不臆测对象内容;列表输出自带截断说明,只有「没有说明的空列表」才是真为空;结构化类型(DOMA/DTEL/TTYP/MSAG)读作 JSON;用户要改动时给可直接应用的补丁(Before/After 或 unified diff);被 [POLICY] 拒绝时说清是哪条策略并建议切换模式,不要绕过或反复重试。
【动手前必须澄清(成本最低、收益最高)】
写第一个文件之前,先复述确认:①交付几个对象(多表/多实体时:一个程序还是拆开;出现「对应的 A、B 的…」「两个/分别」这类表述,不得自行合并);②命名与结构是否沿用参考程序(是否要 include 拆分);③包名与传输号,是否要求直接写入 SAP;④哪些必须由用户在 GUI 完成(GUI 状态 S1000、文本元素、SMW0、SE41 等)。
【写前决策闸门(最高优先级之一)】
凡是为了绕开工具/系统限制而改变形态或既有约定的动作,先停下请示,附 2–3 个方案与代价,由用户决策。必须请示的情形:换实现形态(include 建不了→合并单文件、改对象数量或命名);改交互方式(工具条按钮→弹窗)、删功能、精简参考程序已有写法;为消除一个用户手工步骤而改设计(如为避免建 GUI 状态);任何让 SAP 内对象与团队既有约定不一致的改动。纯机械的编译器对齐(形参类型、字段长度、拼写、括号/ENDFORM)可以直接做,但必须在报告里说明。请示格式:现状 → 卡在哪(附报错原文)→ 方案 A/B/C 各自动作与代价 → 推荐及理由。
【工具能力边界(本后端实测;后端可能不同,动手前先探针)】
- adt_batch 可能 404(未部署 $batch)→ 顺序调用;adt_where_used 可能 405 → 改用 DD03L/DD04L 反查或 adt_search;adt_search 的 quickSearchSource 可能不支持(自动回落对象搜索)。
- adt_object_write 建 INCL 不支持(只能读/改/删);type=FUNC 可能建出函数组;TABL/DTEL/DOMA/TTYP 的写能力可能不可用。遇到这些限制不要自行换设计,先请示。
- adt_object_read 可能只回摘要(如 "N source line(s) (via owner)")→ 用 adt_export_objects 导出到本地再 read。
- 长源码/大文件改法:占位创建(不带 source)→ adt_object_read 建 OCC 快照 → adt_object_edit(sourceFile=本地文件, transport=父请求) → adt_activate。不要把全文塞进工具参数。
- GUI 状态、文本元素、选择文本、SMW0、SE41:没有工具,只能给用户精确规格(功能码、文本、位置);ATC 会因「未定义 GUI 状态」报预期 ERROR,需在报告中说明。
- 结构化 DDIC(DTEL/DOMA/TTYP/STRU)读不到内容 → 读 DDIC 表:DD03L(字段/ROLLNAME/类型长度)、DD04L(数据元素→域)、DD04T(数据元素文本)、DD40L(表类型→行类型)、DD02L(TABCLASS/BUFFERED)、TRDIR(SUBC,1=可执行程序)。注意 DD01L 无 ROLLNAME、DD04L 无 DDTEXT。
- 探针优先:任何没验证过的写能力,先在 $TMP 建一次性对象试,验证后立即 adt_object_delete;失败最多试两轮,之后转人工,不要反复换参数硬试。
【传输请求纪律(最容易踩坑)】
- 写对象时 transport 要传「父请求号」:传任务号会报「对象 R3TR PROG X 已在用户 U 的请求 R 中锁定」,传父请求号成功。
- 对象往往挂在任务号而非请求号:E071 用 TRKORR=<请求> 常为空,先用 E070(TRKORR/STRKORR、TRFUNCTION:K=请求/S=任务/T=拷贝;TRSTATUS:D=开放/R=已释放;T+R 是历史记录不阻塞)拿到任务号,再查 E071。
- 容器与成员可能在不同请求(函数模块与其函数组常常分别挂在不同的请求/任务下)→ 用对象所在占用方的父请求号,否则报「对象 LSM 已在用户 的请求 中锁定」(用户/请求是空串,别被误导)。
- adt_unlock_all 清的是 ADT 编辑锁台账,不解决传输占用;dryRun 显示 0 条正常。
- 包是否可编辑、允许哪些传输号由工作区白名单决定;被 [POLICY] 拒绝时说明规则,不绕过。
【验证纪律(禁止把「成功文案」当完成)】
- 部署阶梯:adt_check → adt_activate(相关对象一次传齐)→ adt_version_diff(saved=active)→ TRDIR-SUBC 校验 → adt_run_atc → 交付报告。绝不能把 adt_check PASSED 当作可激活/可交付:Dynpro 生成、类型一致性、跨对象一致性只在激活 preaudit 检查。
- 工具成功 ≠ 落地成功:written/patched (via owner) 之后必须复核——DDIC 看 DD02L/DD03L/DD04L(含 AS4LOCAL:A=已激活、L=未激活);源码用 adt_export_objects 读回比对(字符数/关键行);adt_version_diff 确认 saved 与 active 一致。
- 修改前置检查:直接 UPDATE 主数据前先查 DD02L-BUFFERED;每行一次 COMMIT WORK(不要每字段一次);选择屏幕不要用变量做 FRAME TITLE/COMMENT(名字 ≤8 字符,中文走文本元素);常量/变量传给他人方法形参前先对齐类型(string vs CHAR31、CHAR10 vs CDTABKEY 之类都会炸)。
【adt_data_preview 硬约束】
ABAP SQL 方言:DESCENDING 不是 DESC、无 LIMIT、用 length/offset 分页;单条 SQL 字面量 ≤255 字符(IN 列表要拆包);列名必须真实存在(先查 DD03L 取列清单);OR+LIKE、COUNT(*)/GROUP BY 可能被拒;并行 freestyle 查询偶发 400,逐个重试即可;以「fetched N row(s)」为准,resultRows=0 是正常现象。
【交付报告必须包含】
改了哪些对象、记录到哪个传输请求号(父请求)、check/激活/单测/ATC 的结论、ATC 里预期中的 ERROR 及消除条件、需要用户在 GUI 完成的事项清单,以及本次是否修改了 SAP。
【环境纪律】
受限权限档下 pwsh/win_bash 可能起不来(SetNamedSecurityInfoW Win32 5)→ 需要时用 danger-full-access 单条执行并说明理由,不修改文件权限;执行策略禁止 .ps1 → 命令内联;写文件 Win32 32 失败 → 原样重试一次;Office 文件在盘上是 DLP 密文 → 只能用 read_document_text/Office 通道读,不要因 zip 报「中央目录缺失」判定文件损坏;不要为找参数名全盘 grep 源码(先看报错、先问用户、先做单键探针)。
通用规则:先读后答,不臆测对象内容;列表输出自带截断说明,只有「没有说明的空列表」才是真为空;用户要改动时给可直接应用的补丁(Before/After 或 unified diff);被 [POLICY] 拒绝时说清是哪条策略并建议切换模式,不要绕过或反复重试。
- id: agent-instructions
name: '@deepseek-ai/dsh-agent-instructions'
config:
@@ -167,21 +203,23 @@
name: '@deepseek-ai/dsh-plugin-manager/tools'
disabled: true
# ============ ABAP 工具与安全基线(Read-Only baseline)============
# 预设只带「安全基线」:写/激活/删除/传输/执行/调试全部关闭。
# 预设只带「安全基线」:写/激活/删除/执行/调试关闭;传输只允许“读”
# (adt_transports / adt_object_versions 是只读工具,enableTransports 只控制
# 传输工具族与传输号校验,真正拦写的是 allowedPackages 哨兵值)。
# 运行模式由工作区文件 <workspace>/.dsh-abap-adt/destinations.yaml 的 policy
# 覆盖(该工作区内的目的地按工作区策略执行):
# 只读 = 继承本基线;调试 = allowExecution + allowDebugger;
# 更改 = allowTransportableEdits + enableTransports + allowedPackages Z*,$TMP。
# 只读 = 继承本基线(可读传输请求/版本);调试 = + allowExecution + allowDebugger;
# 更改 = allowTransportableEdits + allowedPackages 白名单 + allowDebugger。
# 切换入口:设置 → ABAP ADT → 运行模式(写回工作区文件,立即生效)。
- id: abap-adt
name: '@nefevcore/abap-adt-dsh-plugin'
config:
demo: true
defaultDestination: demo
enableTransports: false
enableTransports: true
allowTransportableEdits: false
allowedPackages: "__READ_ONLY_NO_PACKAGE__"
allowedTransports: "__READ_ONLY_NO_TRANSPORT__"
allowedTransports: "*"
allowExecution: false
allowBatchWrites: false
allowDebugger: false
+56 -6
View File
@@ -16,7 +16,7 @@
* the DSH credential service (falling back to the process environment).
*/
import { existsSync, readdirSync } from 'node:fs'
import { existsSync, readdirSync, statSync } from 'node:fs'
import { mkdir, readFile, writeFile } from 'node:fs/promises'
import { request as httpRequest } from 'node:http'
import { request as httpsRequest } from 'node:https'
@@ -65,8 +65,12 @@ const ROUTE_PREFIX = '/abap-adt'
*/
const MODE_POLICIES = {
'read-only': {
enableTransports: false,
allowedTransports: '__READ_ONLY_NO_TRANSPORT__',
// Transport READS stay available in read-only mode: `enableTransports`
// gates the transport tool family (adt_transports / adt_object_versions —
// both read-only tools); writes are blocked by the package sentinel below
// plus allowTransportableEdits=false.
enableTransports: true,
allowedTransports: '*',
allowTransportableEdits: false,
allowedPackages: '__READ_ONLY_NO_PACKAGE__',
allowExecution: false,
@@ -75,8 +79,10 @@ const MODE_POLICIES = {
allowDebugVariables: false,
},
debug: {
enableTransports: false,
allowedTransports: '__READ_ONLY_NO_TRANSPORT__',
// Same transport-read exception as read-only; the debugger/execution are
// what this mode adds.
enableTransports: true,
allowedTransports: '*',
allowTransportableEdits: false,
allowedPackages: '__READ_ONLY_NO_PACKAGE__',
allowExecution: true,
@@ -502,8 +508,52 @@ export function apply(ctx, config) {
}
}
/**
* The workspace whose destinations file was written most recently. An empty
* destinations list (a scratch config, or one where every system was removed)
* loses to any workspace that still lists systems, so the settings page never
* opens on an empty page while a usable config exists elsewhere.
*/
async function lastModifiedWorkspace() {
let best
let bestTime = -Infinity
let any
let anyTime = -Infinity
for (const row of listWorkspaces()) {
if (!row.path) continue
for (const fileName of FILE_NAMES) {
const candidate = join(row.path, CONFIG_DIR, fileName)
let mtimeMs
try {
mtimeMs = statSync(candidate).mtimeMs
} catch {
continue
}
if (mtimeMs > anyTime) {
anyTime = mtimeMs
any = row.path
}
if (mtimeMs <= bestTime) continue
try {
const parsed = await parseDestinations(await readFile(candidate, 'utf8'))
if (!normalizeDestinations(parsed.destinations).length) continue
} catch {
continue
}
bestTime = mtimeMs
best = row.path
}
}
return best || any
}
async function stateOf({ sessionId, workspace }) {
const cwd = workspace || resolveCwd(sessionId) || listWorkspaces()[0]?.path || process.cwd()
const cwd =
workspace ||
resolveCwd(sessionId) ||
(await lastModifiedWorkspace()) ||
listWorkspaces()[0]?.path ||
process.cwd()
const file = existingWorkspaceFile(cwd)
let raw = ''
let exists = false
+1 -1
View File
@@ -1,6 +1,6 @@
{
"name": "dsh-abap-workbench",
"version": "0.1.0",
"version": "0.1.3",
"private": false,
"description": "SAP ABAP 工作台:一个 DSH bundle,内含 ABAP 预设(只读/调试/更改,按工作区策略切换)、可视化配置页(目的地、连接测试、运行模式、可写白名单)和会话状态徽章。依赖 @nefevcore/abap-adt-dsh-plugin 提供 adt_* 工具。",
"type": "module",