dsh-gitbash-shell 0.45.0 → 0.46.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +26 -0
- package/README.md +15 -12
- package/README_EN.md +26 -0
- package/dsh.plugin.json +1 -1
- package/package.json +1 -1
- package/src/ops-adopt.js +203 -77
package/CHANGELOG.md
CHANGED
|
@@ -3,6 +3,32 @@
|
|
|
3
3
|
> 倒序排列,新版本条目在最上面。条目格式:`## vX.Y.Z — YYYY-MM-DD` + 类型(feat / fix / docs / chore)+ 要点 + 相关链接。
|
|
4
4
|
> 纪律见 AGENTS.md「变更记录纪律」:发版前先更新本文件并随版本提交;事故复盘、复现与真机验证记录也记在这里。
|
|
5
5
|
|
|
6
|
+
## [0.46.0] — 2026-10-28
|
|
7
|
+
|
|
8
|
+
### fix:跟随 dsh-ops 0.4.3 的 preset-mode 行集(ops_bash 剥离不再失效)
|
|
9
|
+
|
|
10
|
+
- **背景(dsh-ops 0.4.3 Breaking change)**:dsh-ops 把七个组件行换成了八行 preset-mode 开关
|
|
11
|
+
(`dsh-ops-mode-minimal` / `-standard` / `-ptc` / `-cordis` / `dsh-ops-discovery` /
|
|
12
|
+
`dsh-ops-compat` / `dsh-ops-panel` / `dsh-ops-tui`),旧的 `dsh-ops-shell` / `dsh-ops-file` /
|
|
13
|
+
`dsh-ops-ptc` / `dsh-ops-hide-official` 行在 bundle 中不再存在。v0.42.0 的剥离逻辑只认
|
|
14
|
+
`dsh-ops-shell` 一行,升级 0.4.3 后 `planOpsAdopt` 恒为 `skip-row-absent`——配置层的
|
|
15
|
+
`ops_bash` 剥离静默失效(可见性层面只剩本插件对 `ops_bash`/`ops_run` 的逐 agent deny 兜底)。
|
|
16
|
+
- **修复**:`src/ops-adopt.js` 的行集扩为 `OPS_SHELL_ROW_IDS`(旧行 + 四个 mode 行)。四行都
|
|
17
|
+
可能发 Windows shell(dsh-ops `MODE_PLANS[*].shell = true`),因此对**在场的每一行**都写
|
|
18
|
+
`publishBashTool=false`;行不存在绝不写,混合 profile(旧行 + 新行同时在场,或只装旧版 dsh-ops)
|
|
19
|
+
也能正确处理。写入/回读/还原仍是官方 `configEditor` 通道,回读逐行验证;`disabled` 的 mode 行
|
|
20
|
+
不写(它本来就不发布),但还原仍会撤回我们留在其上的值。日志行会列出实际处理的每个行 id。
|
|
21
|
+
- **还原**:dispose 钩子逐行撤回我们写的 `false`(恢复 inherited 值或删键),用户自己把值改回
|
|
22
|
+
`true` 的行视为其选择、不动(与 v0.42.0 同规则)。
|
|
23
|
+
- **边界(写进 README)**:dsh-ops 0.4.3+ 还按 **preset id** 门控工具,只认官方四个 preset;
|
|
24
|
+
本插件的 `ptc-cordis` 与「· Git Bash」变体 id 不在其中 → 那些会话里 dsh-ops 既不发布也不隐藏
|
|
25
|
+
任何工具,我们的 bash 是唯一命令工具,四条 mode 行的开关在那里不会触发。旧版 dsh-ops 升级到
|
|
26
|
+
0.4.3 时,profile patch 里遗留的旧行覆盖会产生 dsh-ops 自己的 `patch: entry ... not found`
|
|
27
|
+
警告——那是 dsh-ops 官方升级须知里要求手动删除的残留,与本插件的写入无关(我们只写活行)。
|
|
28
|
+
- **测试(343 项:342 pass + 1 平台 skip)**:`tests/ops-adopt.mjs` 新增四组 case——行集完整性
|
|
29
|
+
(旧行 + 四 mode 行、顺序)、0.4.3 profile 逐行写入 + 日志行 id、混合 profile 只写仍在发布的行、
|
|
30
|
+
disabled 行不写且全 disabled 静默收口、逐行还原(恢复 inherited / 删键 / 保留用户改成 true 的行)。
|
|
31
|
+
|
|
6
32
|
## [0.45.0] — 2026-10-28
|
|
7
33
|
|
|
8
34
|
### feat:bash 初始化脚本 `initScript`(BASH_ENV;issue #37 的 fnm/nvm 场景)
|
package/README.md
CHANGED
|
@@ -47,23 +47,26 @@ POSIX 宿主上完整保留;
|
|
|
47
47
|
`/c/` 方言统一:`ops_*` 的入参(含 `root`/`operations[].path`/`protect`)
|
|
48
48
|
自动转换、返回与报错路径自动回流 `/c/`。
|
|
49
49
|
|
|
50
|
-
- **与 dsh-ops 并存:bash 只留一份(v0.42.0)**:宿主机制决定「可见但拒绝」到不了「看不见」——
|
|
50
|
+
- **与 dsh-ops 并存:bash 只留一份(v0.42.0;v0.46.0 跟随 0.4.3 行集)**:宿主机制决定「可见但拒绝」到不了「看不见」——
|
|
51
51
|
`tools.restrict` 只能过滤继承面,而 `ops_bash` 注册在 agent 自有层。于是本插件直接接管 dsh-ops
|
|
52
52
|
自带的 `publishBashTool` 开关(经官方 configEditor 通道写为 `false`):安装本插件时 `ops_bash`
|
|
53
53
|
不再出现在模型工具面;**ops 的 pwsh7 overlay(给官方 pwsh 工具供路径)与文件/后台增强不受影响**
|
|
54
54
|
——publishBashTool 的门只套在 `mountSessionBash` 上,pwsh 走 `mountBundledPwsh` 独立路径。
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
55
|
+
**行集随 dsh-ops 0.4.3 变化**:旧版开关在 `dsh-ops-shell` 组件行上;0.4.3 起该行消失,改成四个
|
|
56
|
+
preset-mode 行 `dsh-ops-mode-minimal` / `-standard` / `-ptc` / `-cordis`(四行的执行计划都含
|
|
57
|
+
Windows shell,都能发 `ops_bash`),本插件对**在场的每一行**都写 `false`,旧行仍在的混合 profile
|
|
58
|
+
也照样处理。**`ops_run` 无需处理:它在 dsh-ops 的 bundle 装配中本就没有发布路径**——bundle 只有
|
|
59
|
+
mode/discovery/compat/panel/tui 八行,前台 `ops_run` 只是未被任何调用点使用的默认参数。
|
|
59
60
|
**停用或卸载本插件的瞬间自动还原**(dispose 钩子撤回覆盖,同 row 的其它设置原样保留),重新启用
|
|
60
|
-
|
|
61
|
-
②停用时若覆盖值已被你手动改回 `true
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
61
|
+
又会自动接管。三个边界:①关机状态下卸载的残余覆盖,用 dsh-ops 设置卡「恢复默认」一键撤回;
|
|
62
|
+
②停用时若覆盖值已被你手动改回 `true`,视为你的选择、不动它;③**dsh-ops 0.4.3 起按 preset id
|
|
63
|
+
门控**:它只认官方四个 preset,本插件的 `ptc-cordis` 与「· Git Bash」变体 id 不在其中——那些
|
|
64
|
+
会话里 dsh-ops 什么也不发布(也不隐藏官方 pwsh/read/glob/grep),我们的 bash 是唯一命令工具。
|
|
65
|
+
无设置开关——想用 `ops_bash` 就停用本插件,还原是自动的。
|
|
66
|
+
**后台任务与文件/PTC 增强全部让给 dsh-ops(2026-10-19 用户决策)**:剔除清单被守卫测试
|
|
67
|
+
锁死为 `ops_bash` + `ops_run` 两项,永不含 `ops_run_background`/`ops_job_*`——你启用
|
|
68
|
+
dsh-ops 的「后台任务」组件后,这组全局工具在普通会话与 PTC 创造模式下原样可见可用,
|
|
69
|
+
本插件不做任何 deny。bash 归 gitbash,后台与增强归 dsh-ops,职责互不越界。
|
|
67
70
|
|
|
68
71
|
四个「· Git Bash」**变体 preset 仍保留,默认关闭**(`variantPresets` 开关,
|
|
69
72
|
设置卡可开):开启后注册 `standard-gitbash`/`minimal-gitbash`/`code-gitbash`/
|
package/README_EN.md
CHANGED
|
@@ -215,6 +215,32 @@ Two boundaries: whatever the script prints lands in the command's output (do not
|
|
|
215
215
|
in it), and the file must exist — bash silently ignores a missing `BASH_ENV`, so a typo
|
|
216
216
|
behaves exactly like "not set". `~` expands to the home directory.
|
|
217
217
|
|
|
218
|
+
## Cooperation with dsh-ops: one bash per face (v0.42.0; rows follow 0.4.3)
|
|
219
|
+
|
|
220
|
+
The host cannot turn "visible but denied" into "hidden": `tools.restrict` filters
|
|
221
|
+
only the inherited surface, while dsh-ops registers `ops_bash` in each agent's
|
|
222
|
+
OWN layer. So this plugin drives dsh-ops's own `publishBashTool` switch instead —
|
|
223
|
+
`false`, through the official `configEditor` channel — and `ops_bash` stops
|
|
224
|
+
being registered at all. dsh-ops keeps its pwsh7 overlay, file tools and
|
|
225
|
+
background jobs (`publishBashTool` gates `mountSessionBash` only).
|
|
226
|
+
|
|
227
|
+
**dsh-ops 0.4.3 replaced the component roster with preset-mode rows.** The
|
|
228
|
+
switch used to live on the single `dsh-ops-shell` row; today four rows carry it —
|
|
229
|
+
`dsh-ops-mode-minimal`, `-standard`, `-ptc`, `-cordis` — and every one of them
|
|
230
|
+
plans a Windows shell (`MODE_PLANS[*].shell`), so every one can publish
|
|
231
|
+
`ops_bash`. v0.46.0 walks the whole set: the legacy row when an older dsh-ops is
|
|
232
|
+
mounted, the four mode rows on 0.4.3+, and a mixed profile handles whichever
|
|
233
|
+
rows are actually mounted. Disabling or uninstalling this plugin lifts the
|
|
234
|
+
override again (dispose hook) and leaves every unrelated setting in the same row
|
|
235
|
+
untouched.
|
|
236
|
+
|
|
237
|
+
Two edges: (1) an override left behind by uninstalling while dsh was shut down
|
|
238
|
+
is one click away from gone — dsh-ops's own settings card offers "restore
|
|
239
|
+
defaults" for each row; (2) dsh-ops 0.4.3+ also gates its tools on the agent's
|
|
240
|
+
preset id and knows only the four official ones — this plugin's `ptc-cordis`
|
|
241
|
+
preset and the `· Git Bash` variant ids match no mode, so dsh-ops publishes
|
|
242
|
+
nothing there (and hides nothing either): our bash is the only command tool.
|
|
243
|
+
|
|
218
244
|
## Cooperation with dsh-ptc-cordis-preset
|
|
219
245
|
|
|
220
246
|
This plugin publishes a `gitBash` host capability (`{ active, bashPath }`,
|
package/dsh.plugin.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"id": "dsh-external/dsh-gitbash-shell",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.46.0",
|
|
4
4
|
"main": "./src/index.js",
|
|
5
5
|
"description": "Windows 全模式 Git Bash:自带 bash 工具(私有 Git Bash 执行器实例,官方 PowerShell 执行器原样保留),官方四个模式经行内工具补位直接获得 Git Bash(v0.35.0 起;v0.39.0 起不再替换宿主执行器),四个 *·Git Bash 变体 preset 可开关;官方 pwsh 工具默认隐藏、开关可见(真 PowerShell);附带 gb_* FastCtx 文件工具,检测到 dsh-ops 自动让位。联动:dsh-ptc-cordis-preset(PTC 创造模式带 Git Bash 行且可去重)+ dsh-better-sidebar(终端标签自动切 Git Bash)。",
|
|
6
6
|
"engines": {
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "dsh-gitbash-shell",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.46.0",
|
|
4
4
|
"description": "Windows 上给 agent 一个真正的 Git Bash 命令工具:私有 Git Bash 执行器实例(官方 PowerShell 执行器原样保留),统一 MSYS 盘根路径方言,官方 pwsh 工具默认隐藏可开关,物化四个内置预设的 Git Bash 变体,侧栏终端接管可开关。· Give agents a real Git Bash tool on Windows: a private Git Bash executor instance (the official PowerShell executor stays untouched), a unified MSYS drive-root path dialect, an opt-in official pwsh tool, and four Git Bash preset variants.",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"repository": {
|
package/src/ops-adopt.js
CHANGED
|
@@ -23,6 +23,24 @@
|
|
|
23
23
|
* prompt-policy tool table drops the bash row. Setting it is therefore not a
|
|
24
24
|
* hack into someone else's plugin; it is their own supported configuration.
|
|
25
25
|
*
|
|
26
|
+
* WHICH ROWS CARRY IT (v0.46.0, dsh-ops 0.4.3 breaking change): up to 0.4.2
|
|
27
|
+
* the switch lived on the single `dsh-ops-shell` component row. 0.4.3 replaced
|
|
28
|
+
* the component roster with preset-mode switches — `dsh-ops-mode-minimal`,
|
|
29
|
+
* `-standard`, `-ptc` and `-cordis` — and EVERY one of them plans a Windows
|
|
30
|
+
* shell (`MODE_PLANS[*].shell = true` in src/modes/plan.ts), so every one of
|
|
31
|
+
* them can publish ops_bash. This module therefore walks the whole set
|
|
32
|
+
* ({@link OPS_SHELL_ROW_IDS}): the legacy row when an older dsh-ops is
|
|
33
|
+
* mounted, the four mode rows when 0.4.3+ is. A row that is not mounted is
|
|
34
|
+
* never written to (hard rule 1), which is what makes one walk serve both
|
|
35
|
+
* eras and mixed/partially-overridden profiles.
|
|
36
|
+
*
|
|
37
|
+
* NOT COVERED BY THIS MODULE (documented, not a bug): dsh-ops 0.4.3+ gates
|
|
38
|
+
* its tools on the AGENT'S PRESET ID (`modeOf` -> `modeByPreset`), and the
|
|
39
|
+
* four ids are the official ones. A custom preset such as ours (`ptc-cordis`)
|
|
40
|
+
* or the `· Git Bash` variant ids matches no mode, so dsh-ops publishes
|
|
41
|
+
* nothing there and the row switches we write simply never fire — the face
|
|
42
|
+
* keeps only our bash.
|
|
43
|
+
*
|
|
26
44
|
* WHY THIS MODULE TALKS TO configEditor (same discipline as
|
|
27
45
|
* terminal-shell.js): the write must survive restarts and must be undoable.
|
|
28
46
|
* configEditor.edit(entry, change) is the channel the official settings UI
|
|
@@ -56,11 +74,24 @@
|
|
|
56
74
|
* 3. NEVER claim success without reading the value back.
|
|
57
75
|
* 4. Restore returns the INHERITED value for OUR key only: unrelated
|
|
58
76
|
* overrides in the same row (the user's own dsh-ops settings) survive.
|
|
77
|
+
* 5. NEVER write to a disabled row (it publishes nothing); restore still
|
|
78
|
+
* lifts our value from a row the user disabled afterwards.
|
|
59
79
|
*/
|
|
60
80
|
|
|
61
|
-
/** The Loader row id of the dsh-ops shell component. */
|
|
81
|
+
/** The Loader row id of the dsh-ops shell component (dsh-ops <= 0.4.2). */
|
|
62
82
|
export const OPS_SHELL_ROW_ID = 'dsh-ops-shell'
|
|
63
83
|
|
|
84
|
+
/** dsh-ops 0.4.3+ preset-mode rows; every one plans a Windows shell. */
|
|
85
|
+
export const OPS_MODE_ROW_IDS = Object.freeze([
|
|
86
|
+
'dsh-ops-mode-minimal',
|
|
87
|
+
'dsh-ops-mode-standard',
|
|
88
|
+
'dsh-ops-mode-ptc',
|
|
89
|
+
'dsh-ops-mode-cordis',
|
|
90
|
+
])
|
|
91
|
+
|
|
92
|
+
/** Every row that can carry the publishBashTool switch, legacy first. */
|
|
93
|
+
export const OPS_SHELL_ROW_IDS = Object.freeze([OPS_SHELL_ROW_ID, ...OPS_MODE_ROW_IDS])
|
|
94
|
+
|
|
64
95
|
/**
|
|
65
96
|
* The effective publishBashTool of the row: the profile patch override wins
|
|
66
97
|
* over the inherited layer value; a missing key means the dsh-ops default
|
|
@@ -82,17 +113,20 @@ export function effectivePublishBashTool(row) {
|
|
|
82
113
|
}
|
|
83
114
|
|
|
84
115
|
/**
|
|
85
|
-
* Decide what to do about ops_bash. Pure: every fact arrives as
|
|
86
|
-
* so the whole matrix (absent / already overridden / off at the
|
|
87
|
-
* to-adopt) is unit-testable anywhere.
|
|
88
|
-
* @param {object|undefined} row -
|
|
89
|
-
*
|
|
116
|
+
* Decide what to do about ops_bash FOR ONE ROW. Pure: every fact arrives as
|
|
117
|
+
* an argument, so the whole matrix (absent / already overridden / off at the
|
|
118
|
+
* source / disabled row / to-adopt) is unit-testable anywhere.
|
|
119
|
+
* @param {object|undefined} row - one row from configEditor.configuration(),
|
|
120
|
+
* or undefined when not mounted.
|
|
90
121
|
* @returns {{action: string, reason: string}} the plan.
|
|
91
122
|
*/
|
|
92
123
|
export function planOpsAdopt(row) {
|
|
93
124
|
if (row === undefined || row === null) {
|
|
94
125
|
return { action: 'skip-row-absent', reason: 'the dsh-ops shell row is not mounted in this profile' }
|
|
95
126
|
}
|
|
127
|
+
if (row.entry?.options?.disabled === true) {
|
|
128
|
+
return { action: 'skip-disabled', reason: 'the row is disabled, so it publishes no bash tool' }
|
|
129
|
+
}
|
|
96
130
|
const overrideValue = row.override !== null && row.override !== undefined ? row.override.publishBashTool : undefined
|
|
97
131
|
if (typeof overrideValue === 'boolean' && overrideValue === false) {
|
|
98
132
|
return { action: 'present', reason: 'the row override already sets publishBashTool=false' }
|
|
@@ -107,52 +141,105 @@ export function planOpsAdopt(row) {
|
|
|
107
141
|
}
|
|
108
142
|
|
|
109
143
|
/**
|
|
110
|
-
*
|
|
144
|
+
* The row id of one editor entry, '' when the shape is malformed.
|
|
145
|
+
* @param {object|undefined} row - one configEditor.configuration() entry.
|
|
146
|
+
* @returns {string} the id.
|
|
147
|
+
*/
|
|
148
|
+
function rowIdOf(row) {
|
|
149
|
+
const id = row?.entry?.options?.id
|
|
150
|
+
return typeof id === 'string' ? id : ''
|
|
151
|
+
}
|
|
152
|
+
|
|
153
|
+
/**
|
|
154
|
+
* Every shell-carrying dsh-ops row in an editor view, legacy first then the
|
|
155
|
+
* mode rows in roster order. Rows for other dsh-ops ids are ignored.
|
|
156
|
+
* @param {object} editor - ctx.configEditor (or a stand-in in tests).
|
|
157
|
+
* @returns {object[]} the rows (possibly empty).
|
|
158
|
+
*/
|
|
159
|
+
function opsRowsOf(editor) {
|
|
160
|
+
const rows = editor.configuration()
|
|
161
|
+
if (!Array.isArray(rows)) return []
|
|
162
|
+
const found = []
|
|
163
|
+
for (const id of OPS_SHELL_ROW_IDS) {
|
|
164
|
+
for (const candidate of rows) {
|
|
165
|
+
if (rowIdOf(candidate) === id) found.push(candidate)
|
|
166
|
+
}
|
|
167
|
+
}
|
|
168
|
+
return found
|
|
169
|
+
}
|
|
170
|
+
|
|
171
|
+
/**
|
|
172
|
+
* Read one row back by id, or undefined when it vanished.
|
|
111
173
|
* @param {object} editor - ctx.configEditor (or a stand-in in tests).
|
|
112
|
-
* @
|
|
174
|
+
* @param {string} id - the row id.
|
|
175
|
+
* @returns {object|undefined} the fresh row.
|
|
113
176
|
*/
|
|
114
|
-
function
|
|
177
|
+
function freshRowOf(editor, id) {
|
|
115
178
|
const rows = editor.configuration()
|
|
116
|
-
return Array.isArray(rows) ? rows.find((candidate) => candidate
|
|
179
|
+
return Array.isArray(rows) ? rows.find((candidate) => rowIdOf(candidate) === id) : undefined
|
|
117
180
|
}
|
|
118
181
|
|
|
119
182
|
/**
|
|
120
|
-
* Apply the plan through the official config editor, then READ
|
|
183
|
+
* Apply the plan through the official config editor, then READ EVERY VALUE
|
|
184
|
+
* BACK. One walk serves both eras: the legacy `dsh-ops-shell` row and the four
|
|
185
|
+
* 0.4.3+ `dsh-ops-mode-*` rows; absent rows are skipped, never written.
|
|
121
186
|
* @param {object} editor - ctx.configEditor (or a stand-in in tests).
|
|
122
|
-
* @returns {Promise<{status: string, detail?: string}>} the
|
|
123
|
-
* logs it — this function never lies about success and
|
|
124
|
-
* expected condition.
|
|
187
|
+
* @returns {Promise<{status: string, rows: object[], detail?: string}>} the
|
|
188
|
+
* outcome; the caller logs it — this function never lies about success and
|
|
189
|
+
* never throws for an expected condition.
|
|
125
190
|
*/
|
|
126
191
|
export async function adoptOpsBashRemoval(editor) {
|
|
127
192
|
if (editor === null || editor === undefined || typeof editor.configuration !== 'function') {
|
|
128
|
-
return { status: 'skip-row-absent', detail: 'the config editor is unavailable, so the dsh-ops row cannot be configured' }
|
|
193
|
+
return { status: 'skip-row-absent', rows: [], detail: 'the config editor is unavailable, so the dsh-ops row cannot be configured' }
|
|
129
194
|
}
|
|
130
|
-
const
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
if (typeof editor.edit !== 'function') {
|
|
134
|
-
return { status: 'write-failed', detail: 'the config editor exposes no edit()' }
|
|
195
|
+
const rows = opsRowsOf(editor)
|
|
196
|
+
if (rows.length === 0) {
|
|
197
|
+
return { status: 'skip-row-absent', rows: [], detail: 'no dsh-ops shell or mode row is mounted in this profile' }
|
|
135
198
|
}
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
199
|
+
const plans = rows.map((row) => planOpsAdopt(row))
|
|
200
|
+
const results = rows.map((row, index) => ({ id: rowIdOf(row), action: plans[index].action, reason: plans[index].reason }))
|
|
201
|
+
const targets = rows.filter((row, index) => results[index].action === 'adopt')
|
|
202
|
+
if (targets.length > 0 && typeof editor.edit !== 'function') {
|
|
203
|
+
return { status: 'write-failed', rows: results, detail: 'the config editor exposes no edit()' }
|
|
140
204
|
}
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
205
|
+
for (const row of targets) {
|
|
206
|
+
const id = rowIdOf(row)
|
|
207
|
+
try {
|
|
208
|
+
await editor.edit(row.entry, (raw) => ({ ...raw, publishBashTool: false }))
|
|
209
|
+
} catch (error) {
|
|
210
|
+
return {
|
|
211
|
+
status: 'write-failed',
|
|
212
|
+
rows: results,
|
|
213
|
+
detail: 'the official config editor refused the write for ' + id + ': ' + (error && error.message ? error.message : String(error)),
|
|
214
|
+
}
|
|
215
|
+
}
|
|
147
216
|
}
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
217
|
+
// Read back: edit() resolving is NOT proof the rows now carry our value.
|
|
218
|
+
for (const row of targets) {
|
|
219
|
+
const id = rowIdOf(row)
|
|
220
|
+
let after
|
|
221
|
+
try {
|
|
222
|
+
after = freshRowOf(editor, id)
|
|
223
|
+
} catch (error) {
|
|
224
|
+
return {
|
|
225
|
+
status: 'write-failed',
|
|
226
|
+
rows: results,
|
|
227
|
+
detail: 'the write succeeded but reading ' + id + ' back failed: ' + (error && error.message ? error.message : String(error)),
|
|
228
|
+
}
|
|
229
|
+
}
|
|
230
|
+
if (after === undefined || after.override === null || after.override === undefined || after.override.publishBashTool !== false) {
|
|
231
|
+
return {
|
|
232
|
+
status: 'write-failed',
|
|
233
|
+
rows: results,
|
|
234
|
+
detail: 'the write did not stick on ' + id + ': it does not read publishBashTool=false'
|
|
235
|
+
+ (after === undefined ? ' (the row vanished during the write)' : ''),
|
|
236
|
+
}
|
|
153
237
|
}
|
|
154
238
|
}
|
|
155
|
-
return { status: 'adopted' }
|
|
239
|
+
if (targets.length > 0) return { status: 'adopted', rows: results }
|
|
240
|
+
if (results.some((result) => result.action === 'present')) return { status: 'present', rows: results }
|
|
241
|
+
if (results.some((result) => result.action === 'unchanged-inherited-off')) return { status: 'unchanged-inherited-off', rows: results }
|
|
242
|
+
return { status: 'skip-disabled-rows', rows: results, detail: 'every dsh-ops shell row is disabled' }
|
|
156
243
|
}
|
|
157
244
|
|
|
158
245
|
/**
|
|
@@ -160,53 +247,88 @@ export async function adoptOpsBashRemoval(editor) {
|
|
|
160
247
|
* fiber dispose hook (plugin disabled / uninstalled while dsh runs). Only OUR
|
|
161
248
|
* key is touched: unrelated overrides in the same row survive.
|
|
162
249
|
* @param {object} editor - ctx.configEditor (or a stand-in in tests).
|
|
163
|
-
* @returns {Promise<{status: string, detail?: string}>} the outcome.
|
|
250
|
+
* @returns {Promise<{status: string, rows: object[], detail?: string}>} the outcome.
|
|
164
251
|
*/
|
|
165
252
|
export async function restoreOpsBash(editor) {
|
|
166
253
|
if (editor === null || editor === undefined || typeof editor.configuration !== 'function') {
|
|
167
|
-
return { status: 'restore-failed', detail: 'the config editor is unavailable, so the override cannot be lifted' }
|
|
254
|
+
return { status: 'restore-failed', rows: [], detail: 'the config editor is unavailable, so the override cannot be lifted' }
|
|
168
255
|
}
|
|
169
|
-
const
|
|
170
|
-
if (
|
|
171
|
-
return { status: 'skip-row-absent', detail: '
|
|
256
|
+
const rows = opsRowsOf(editor)
|
|
257
|
+
if (rows.length === 0) {
|
|
258
|
+
return { status: 'skip-row-absent', rows: [], detail: 'no dsh-ops shell or mode row is mounted — nothing to restore' }
|
|
172
259
|
}
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
260
|
+
// Only rows where OUR value is in force are touched. A row the user flipped
|
|
261
|
+
// back ON afterwards is their own later choice — leave it exactly where it is.
|
|
262
|
+
const results = []
|
|
263
|
+
const targets = []
|
|
264
|
+
for (const row of rows) {
|
|
265
|
+
const id = rowIdOf(row)
|
|
266
|
+
const overrideValue = row.override !== null && row.override !== undefined ? row.override.publishBashTool : undefined
|
|
267
|
+
if (typeof overrideValue !== 'boolean') {
|
|
268
|
+
results.push({ id, action: 'unchanged', reason: 'no publishBashTool override to lift' })
|
|
269
|
+
continue
|
|
270
|
+
}
|
|
271
|
+
if (overrideValue !== false) {
|
|
272
|
+
results.push({ id, action: 'unchanged', reason: 'the override reads publishBashTool=true — a later choice of the user, left alone' })
|
|
273
|
+
continue
|
|
274
|
+
}
|
|
275
|
+
results.push({ id, action: 'restore' })
|
|
276
|
+
targets.push(row)
|
|
176
277
|
}
|
|
177
|
-
if (
|
|
178
|
-
|
|
179
|
-
// us. Lifting that would REPLACE their choice with the inherited default —
|
|
180
|
-
// leave their value exactly where it is.
|
|
181
|
-
return { status: 'unchanged', detail: 'the override reads publishBashTool=true — a later choice of the user, left alone' }
|
|
278
|
+
if (targets.length === 0) {
|
|
279
|
+
return { status: 'unchanged', rows: results, detail: results.map((row) => row.id + ': ' + row.reason).join('; ') }
|
|
182
280
|
}
|
|
183
281
|
if (typeof editor.edit !== 'function') {
|
|
184
|
-
return { status: 'restore-failed', detail: 'the config editor exposes no edit()' }
|
|
282
|
+
return { status: 'restore-failed', rows: results, detail: 'the config editor exposes no edit()' }
|
|
185
283
|
}
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
next
|
|
191
|
-
|
|
192
|
-
|
|
284
|
+
for (const row of targets) {
|
|
285
|
+
const id = rowIdOf(row)
|
|
286
|
+
try {
|
|
287
|
+
await editor.edit(row.entry, (raw, inherited) => {
|
|
288
|
+
const next = { ...raw }
|
|
289
|
+
if (inherited !== null && inherited !== undefined && Object.hasOwn(inherited, 'publishBashTool')) {
|
|
290
|
+
next.publishBashTool = inherited.publishBashTool
|
|
291
|
+
} else {
|
|
292
|
+
delete next.publishBashTool
|
|
293
|
+
}
|
|
294
|
+
return next
|
|
295
|
+
})
|
|
296
|
+
} catch (error) {
|
|
297
|
+
return {
|
|
298
|
+
status: 'restore-failed',
|
|
299
|
+
rows: results,
|
|
300
|
+
detail: 'the official config editor refused the restore for ' + id + ': ' + (error && error.message ? error.message : String(error)),
|
|
193
301
|
}
|
|
194
|
-
|
|
195
|
-
})
|
|
196
|
-
} catch (error) {
|
|
197
|
-
return { status: 'restore-failed', detail: 'the official config editor refused the restore: ' + (error && error.message ? error.message : String(error)) }
|
|
198
|
-
}
|
|
199
|
-
let after
|
|
200
|
-
try {
|
|
201
|
-
after = opsRowOf(editor)
|
|
202
|
-
} catch (error) {
|
|
203
|
-
return { status: 'restore-failed', detail: 'the restore succeeded but reading it back failed: ' + (error && error.message ? error.message : String(error)) }
|
|
302
|
+
}
|
|
204
303
|
}
|
|
205
|
-
const
|
|
206
|
-
|
|
207
|
-
|
|
304
|
+
for (const row of targets) {
|
|
305
|
+
const id = rowIdOf(row)
|
|
306
|
+
let after
|
|
307
|
+
try {
|
|
308
|
+
after = freshRowOf(editor, id)
|
|
309
|
+
} catch (error) {
|
|
310
|
+
return {
|
|
311
|
+
status: 'restore-failed',
|
|
312
|
+
rows: results,
|
|
313
|
+
detail: 'the restore succeeded but reading ' + id + ' back failed: ' + (error && error.message ? error.message : String(error)),
|
|
314
|
+
}
|
|
315
|
+
}
|
|
316
|
+
const left = after !== undefined && after.override !== null && after.override !== undefined ? after.override.publishBashTool : undefined
|
|
317
|
+
if (typeof left === 'boolean' && left === false) {
|
|
318
|
+
return { status: 'restore-failed', rows: results, detail: 'the restore did not stick on ' + id + ': it still reads publishBashTool=false' }
|
|
319
|
+
}
|
|
208
320
|
}
|
|
209
|
-
return { status: 'restored' }
|
|
321
|
+
return { status: 'restored', rows: results }
|
|
322
|
+
}
|
|
323
|
+
|
|
324
|
+
/**
|
|
325
|
+
* The row ids one result touched, for the log line.
|
|
326
|
+
* @param {object} result - an adopt/restore result.
|
|
327
|
+
* @returns {string} comma-joined ids, or ''.
|
|
328
|
+
*/
|
|
329
|
+
export function opsRowList(result) {
|
|
330
|
+
const rows = Array.isArray(result?.rows) ? result.rows : []
|
|
331
|
+
return rows.map((row) => row.id).filter((id) => typeof id === 'string' && id !== '').join(', ')
|
|
210
332
|
}
|
|
211
333
|
|
|
212
334
|
/**
|
|
@@ -216,19 +338,22 @@ export async function restoreOpsBash(editor) {
|
|
|
216
338
|
* @returns {string} the log line, without the plugin tag.
|
|
217
339
|
*/
|
|
218
340
|
export function opsAdoptReport(result) {
|
|
341
|
+
const listed = opsRowList(result)
|
|
219
342
|
switch (result.status) {
|
|
220
343
|
case 'adopted':
|
|
221
|
-
return 'dsh-ops bash removed from the agent face (publishBashTool=false through the official config editor): the host cannot hide own-layer tools, so dsh-ops must simply not register it — disable this plugin any time you want ops_bash back (the removal is lifted automatically)'
|
|
344
|
+
return 'dsh-ops bash removed from the agent face (publishBashTool=false on ' + listed + ' through the official config editor): the host cannot hide own-layer tools, so dsh-ops must simply not register it — disable this plugin any time you want ops_bash back (the removal is lifted automatically)'
|
|
222
345
|
case 'present':
|
|
223
|
-
return 'dsh-ops bash already removed:
|
|
346
|
+
return 'dsh-ops bash already removed: ' + listed + ' already read publishBashTool=false (nothing written)'
|
|
224
347
|
case 'unchanged-inherited-off':
|
|
225
|
-
return 'dsh-ops bash already off at the source: publishBashTool=false in the dsh-ops configuration (nothing written)'
|
|
348
|
+
return 'dsh-ops bash already off at the source: publishBashTool=false in the dsh-ops configuration of ' + listed + ' (nothing written)'
|
|
349
|
+
case 'skip-disabled-rows':
|
|
350
|
+
return 'dsh-ops bash removal not needed: ' + (result.detail ?? 'every dsh-ops shell row is disabled')
|
|
226
351
|
case 'skip-row-absent':
|
|
227
352
|
return 'dsh-ops bash removal skipped: ' + (result.detail ?? 'the dsh-ops shell row is not mounted')
|
|
228
353
|
case 'write-failed':
|
|
229
354
|
return 'dsh-ops bash removal FAILED — ' + (result.detail ?? '')
|
|
230
355
|
+ ' | next steps: ops_bash stays VISIBLE and our pre-execute denial keeps answering its calls;'
|
|
231
|
-
+ ' retry by restarting dsh, or set publishBashTool=false on the dsh-ops
|
|
356
|
+
+ ' retry by restarting dsh, or set publishBashTool=false on the dsh-ops shell/mode row by hand'
|
|
232
357
|
+ ' (Settings -> the dsh-ops card -> publishBashTool), or turn to the README for the manual patch row.'
|
|
233
358
|
default:
|
|
234
359
|
return 'dsh-ops bash removal skipped: ' + (result.detail ?? result.status)
|
|
@@ -241,16 +366,17 @@ export function opsAdoptReport(result) {
|
|
|
241
366
|
* @returns {string} the log line, without the plugin tag.
|
|
242
367
|
*/
|
|
243
368
|
export function opsRestoreReport(result) {
|
|
369
|
+
const listed = opsRowList(result)
|
|
244
370
|
switch (result.status) {
|
|
245
371
|
case 'restored':
|
|
246
|
-
return 'dsh-ops bash setting restored: the publishBashTool override is lifted, ops_bash follows the dsh-ops settings again'
|
|
372
|
+
return 'dsh-ops bash setting restored: the publishBashTool override is lifted on ' + (listed || 'the rows') + ', ops_bash follows the dsh-ops settings again'
|
|
247
373
|
case 'unchanged':
|
|
248
374
|
return 'dsh-ops bash restore not needed: ' + (result.detail ?? 'no override of ours to lift')
|
|
249
375
|
case 'skip-row-absent':
|
|
250
376
|
return 'dsh-ops bash restore skipped: ' + (result.detail ?? 'the dsh-ops shell row is gone')
|
|
251
377
|
case 'restore-failed':
|
|
252
378
|
return 'dsh-ops bash restore FAILED — ' + (result.detail ?? '')
|
|
253
|
-
+ ' | the dsh-ops
|
|
379
|
+
+ ' | the dsh-ops override may survive this plugin: lift it by hand'
|
|
254
380
|
+ ' (Settings -> the dsh-ops card -> restore defaults) if ops_bash should come back.'
|
|
255
381
|
default:
|
|
256
382
|
return 'dsh-ops bash restore skipped: ' + (result.detail ?? result.status)
|