dsh-gitbash-shell 0.38.3 → 0.38.5

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 CHANGED
@@ -3,6 +3,25 @@
3
3
  > 倒序排列,新版本条目在最上面。条目格式:`## vX.Y.Z — YYYY-MM-DD` + 类型(feat / fix / docs / chore)+ 要点 + 相关链接。
4
4
  > 纪律见 AGENTS.md「变更记录纪律」:发版前先更新本文件并随版本提交;事故复盘、复现与真机验证记录也记在这里。
5
5
 
6
+ ## v0.38.5 — 2026-10-13
7
+
8
+ **类型**:fix(用户 27 工具全量冒烟报告的 FastCtx 两项;bash 工具真机验收通过)。
9
+
10
+ - **同轮验收**:用户 Windows 真机全量测试确认 **bash 工具出现且作为唯一命令通道正常工作**(退出码标记、后台任务、job_kill 全过)——v0.35.0 以来「行内补位」五层问题(注入缺失/output 契约/restrict 作用域/asar 解析/fiber 时序/服务授权)全部闭环。
11
+ - **gb_glob 畸形 pattern 由静默放行改为响亮报错**:上游对非字符串 pattern 静默当「全部文件」处理(测试实测:传数组形状的字符串返回全库),桥侧在 `glob`/`replace` 的 execute 里前置校验——pattern 必须非空字符串,否则抛 `pattern must be a non-empty string`。静默错误结果比报错更危险。
12
+ - **忽略规则透明化**:三套「找文件」默认行为各不相同(官方 glob 不忽略任何;gb_grep 尊重 .gitignore 跳 .git;gb_glob 只认裸 .ignore 且 .git 恒可见)——在 gb_glob/gb_grep 的 description 里**如实声明各自规则**,混用时模型不再拿到意外结果集而不知情。
13
+ - 其余发现归属:web_search 402 是 web-search-deepseek 插件的端点余额问题(非本插件);read_image webp 重编码/行尾不对称/wait_agent 下限等为宿主行为,不在本仓库范围。
14
+ - **冒烟**:119 项全绿(新增畸形 pattern 校验与忽略规则断言)。
15
+
16
+ ## v0.38.4 — 2026-10-13
17
+
18
+ **类型**:fix(bash 挂载的服务授权 + 折叠分区自绘;两条真机日志驱动)。
19
+
20
+ - **bash 挂载改走 cordis 官方插件通道**(`agent.ctx.plugin(mod)`):官方 tool-bash 模块对象自带 `inject: ['tools','shell','systemPrompt','shellEnv','workingDirectory']` 声明;此前手动 `mod.apply(scope.ctx)` 绕过声明,被 cordis 拒读服务(`cannot get property "shell" without inject`——上一版 fiber 时序修复的后续,ctx 已就绪、死在授权)。`ctx.plugin(mod)` 注册**真正的 fiber**(声明生效、cordis 自己跑 apply、Config 走官方 schema),`await fiber` 等激活完成;卸载 = `fiber.dispose()`,回滚语义由 cordis 保证。
21
+ - **折叠分区改为自绘**(按钮头 + 条件体,零外部组件):官方 `DisclosureRow` 在真机的插件受限 require 形状里拿不到(`icon("DisclosureRow")` 为 null),v0.38.0–0.38.3 的每个分区都在静默退化成「静态标题 + 平铺」——正是用户截图里那个"一条线分割的一列"。自绘后:七个带边框卡片、标题行常驻可点、默认全收起。
22
+ - **冒烟**:118 项全绿;断言改锁自绘形态(无外部组件依赖、折叠头、条件体)。
23
+ - **真机判据**:升级重启新建会话 → bash 出现 + 日志 `official bash tool activated for this agent`;设置卡 = 七个可折叠卡片。
24
+
6
25
  ## v0.38.3 — 2026-10-13
7
26
 
8
27
  **类型**:fix(补位激活的 scope 上下文时序;用户终端日志一锤定音)。
package/dsh.plugin.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "id": "dsh-external/dsh-gitbash-shell",
3
- "version": "0.38.3",
3
+ "version": "0.38.5",
4
4
  "main": "./src/index.js",
5
5
  "description": "Windows 全模式 Git Bash:用 Git for Windows bash 替换 PowerShell 执行器;官方四个模式经行内工具补位直接获得 Git Bash(v0.35.0,默认),四个 *·Git Bash 变体 preset 可开关;附带 gitbash_pwsh(系统 PowerShell 7)与 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.38.3",
3
+ "version": "0.38.5",
4
4
  "description": "Windows 上让 agent 的每条 shell 命令改经 Git for Windows bash 执行;物化四个内置预设的 Git Bash 变体,统一 MSYS 盘根路径方言,侧栏终端接管可开关。· Run every agent shell command through Git for Windows bash on Windows; materialize Git Bash variants of the built-in presets, unify the MSYS drive-root path dialect, and adopt the sidebar terminal behind a switch.",
5
5
  "type": "module",
6
6
  "repository": {
package/src/client.js CHANGED
@@ -1807,6 +1807,11 @@ window.__ModuleLoader__.load({
1807
1807
  seven header rows at a glance, switches only visible on expand. */
1808
1808
  ".gb-fold{border:1px solid var(--dsw-alias-border-l2);border-radius:10px;padding:4px 12px 12px;background:var(--dsw-alias-bg-layer-1,transparent)}",
1809
1809
  ".gb-fold .gb-stack{padding-top:10px}",
1810
+ ".gb-foldHead{display:flex;align-items:center;gap:8px;width:100%;appearance:none;background:none;border:0;padding:6px 0;font:inherit;font-size:13px;font-weight:600;letter-spacing:.02em;color:var(--dsw-alias-label-secondary);cursor:pointer;text-align:left}",
1811
+ ".gb-foldHead:hover{color:var(--dsw-alias-label-primary)}",
1812
+ ".gb-foldChevron{flex:none;display:inline-block;color:var(--dsw-alias-label-tertiary);transition:transform .16s}",
1813
+ ".gb-foldChevron.gb-foldChevronOpen{transform:rotate(90deg)}",
1814
+ ".gb-foldTitle{min-width:0;overflow-wrap:anywhere}",
1810
1815
  /* Missing-Git-Bash popup (v0.28.0): a root-level modal, because the
1811
1816
  user may never be on the settings page when their shell dies. */
1812
1817
  ".gb-modalBackdrop{position:fixed;inset:0;z-index:2147483000;display:flex;align-items:center;justify-content:center;background:rgba(0,0,0,.45)}",
@@ -2100,35 +2105,31 @@ window.__ModuleLoader__.load({
2100
2105
 
2101
2106
  var Chevron = icon("IconChevronDownOutline14");
2102
2107
 
2103
- /* v0.38.0: one FOLDABLE section. The header row is the OFFICIAL
2104
- `DisclosureRow` from the seed ui-primitives package (required at
2105
- the top of this file, same seam as every icon). All sections stay
2106
- collapsed by default — the user scans seven labelled header rows
2107
- and expands the one theme they recognise. A stripped-down host
2108
- without the export degrades to the static title+stack card. */
2109
- var DisclosureRowComponent = icon("DisclosureRow");
2110
- /* v0.38.2 BUGFIX: the call sites are `fold("sec.xxx", row, row, ...)`
2111
- — one dictionary key followed by the section's rows. The previous
2112
- draft parsed (key, titleKey, ...) and ATE the first row into the
2113
- title slot (t(<element>) → empty header, nothing folded). */
2108
+ /* v0.38.4: one FOLDABLE section, HAND-DRAWN. The official disclosure
2109
+ component was tried first (v0.38.0-0.38.3) but never rendered on
2110
+ the real machine — the ui-primitives module shape the plugin's
2111
+ restricted require returns does not expose it (the icon lookup
2112
+ comes back null there), so every section silently degraded to a
2113
+ static title+stack pile. Self-drawing costs one button + one
2114
+ conditional: header row visible at all times, body only while
2115
+ open. All sections stay collapsed by default — seven labelled
2116
+ header rows to scan. */
2114
2117
  function fold(titleKey) {
2115
2118
  var body = [];
2116
2119
  for (var i = 1; i < arguments.length; i++) body.push(arguments[i]);
2117
- if (!DisclosureRowComponent || typeof DisclosureRowComponent !== "function") {
2118
- return E("div", { className: "gb-section" },
2119
- E("div", { className: "gb-sectionTitle" }, t(titleKey)),
2120
- E("div", { className: "gb-stack" }, body),
2121
- );
2122
- }
2123
- return E(DisclosureRowComponent, {
2124
- className: "gb-fold",
2125
- icon: null,
2126
- title: t(titleKey),
2127
- open: folds[titleKey] === true,
2128
- expandable: true,
2129
- expandOnRowClick: true,
2130
- onToggle: function () { toggleFold(titleKey); },
2131
- }, E("div", { className: "gb-stack" }, body));
2120
+ var open = folds[titleKey] === true;
2121
+ return E("div", { className: "gb-fold" },
2122
+ E("button", {
2123
+ type: "button",
2124
+ className: "gb-foldHead",
2125
+ "aria-expanded": open ? "true" : "false",
2126
+ onClick: function () { toggleFold(titleKey); },
2127
+ },
2128
+ E("span", { className: "gb-foldChevron" + (open ? " gb-foldChevronOpen" : ""), "aria-hidden": "true" }, "▸"),
2129
+ E("span", { className: "gb-foldTitle" }, t(titleKey)),
2130
+ ),
2131
+ open ? E("div", { className: "gb-stack" }, body) : null,
2132
+ );
2132
2133
  }
2133
2134
 
2134
2135
  // dsh 0.1.6-alpha.2: the Plugins page renders this card through the
@@ -323,16 +323,37 @@ export function mountFastctx(ctx, deps) {
323
323
  for (const tool of wanted) {
324
324
  const rawName = String(tool.name)
325
325
  const schema = tool.inputSchema && typeof tool.inputSchema === 'object' ? tool.inputSchema : { type: 'object', properties: {} }
326
+ // Ignore-rule transparency (v0.38.5, the field smoke report): the three
327
+ // "find files" surfaces disagree by default — the official glob ignores
328
+ // nothing, gb_grep honors .gitignore and skips .git, while gb_glob only
329
+ // honors a bare .ignore file and keeps .git visible. State it on the
330
+ // tools the model sees, instead of letting mixed usage produce
331
+ // surprising result sets.
332
+ const ignoreNote = rawName === 'glob'
333
+ ? '\nIgnore rules: this tool only honors a bare .ignore file and does NOT hide .git — use gb_grep (honors .gitignore) or the official glob when those semantics are wanted.'
334
+ : rawName === 'grep'
335
+ ? '\nIgnore rules: this tool honors .gitignore and skips .git — unlike gb_glob (bare .ignore only) and the official glob (ignores nothing).'
336
+ : ''
326
337
  try {
327
338
  const dispose = registry.register({
328
339
  name: gbName(rawName),
329
- description: `${String(tool.description ?? rawName)}\n${FASTCTX_SANDBOX_NOTE}`,
340
+ description: `${String(tool.description ?? rawName)}\n${FASTCTX_SANDBOX_NOTE}${ignoreNote}`,
330
341
  parameters: schema,
331
342
  output: {
332
343
  schema: TOOL_OUTPUT_SCHEMA,
333
344
  render: (_args, value) => renderTextContent(value),
334
345
  },
335
346
  async execute(args) {
347
+ // Fail LOUD on malformed patterns (v0.38.5, the field smoke
348
+ // report): the upstream server silently treats a non-string
349
+ // pattern as "everything", which turns a typo into a full-tree
350
+ // result set — a silent wrong answer is worse than an error.
351
+ if ((rawName === 'glob' || rawName === 'replace') && args !== null && typeof args === 'object' && 'pattern' in args) {
352
+ const pattern = args.pattern
353
+ if (typeof pattern !== 'string' || pattern.trim() === '') {
354
+ throw new Error(`invalid arguments: ${gbName(rawName)} pattern must be a non-empty string (got ${pattern === null ? 'null' : typeof pattern})`)
355
+ }
356
+ }
336
357
  const translated = translateFastctxArgs(args, msysToWin)
337
358
  const result = await callWithReconnect(rawName, translated)
338
359
  return { content: [{ type: 'text', text: renderFastctxResult(rawName, result, echo) }] }
@@ -266,7 +266,7 @@ export async function resolveToolBashEntry(ctx) {
266
266
  * prescribes for per-agent variants. The official module is loaded ONCE per
267
267
  * process; each agent gets a fresh scope and a fresh apply().
268
268
  */
269
- export async function activateOfficialBash(agent, resolved, createScopeFn, log) {
269
+ export async function activateOfficialBash(agent, resolved, log) {
270
270
  let mod
271
271
  try {
272
272
  mod = await resolved.load()
@@ -278,29 +278,31 @@ export async function activateOfficialBash(agent, resolved, createScopeFn, log)
278
278
  log('official tool-bash module has no apply(); activation skipped')
279
279
  return undefined
280
280
  }
281
- const scope = createScopeFn(agent.ctx)
282
- // plugin() starts the fiber asynchronously; awaiting the wrapped thenable
283
- // guarantees the apply callback has captured scope.ctx before we hand it
284
- // to the official module (v0.38.2-and-earlier passed undefined here and
285
- // crashed inside `ctx.shell`).
286
- if (scope.fiber) {
287
- try { await scope.fiber } catch (error) {
288
- log(`official tool-bash scope fiber failed to start: ${error?.message ?? error}`)
289
- return undefined
290
- }
291
- }
292
- if (!scope.ctx) {
293
- log('official tool-bash scope context did not initialize; activation skipped')
281
+ // Mount through cordis's OWN plugin path. The official module object
282
+ // DECLARES its service needs (`inject: ['tools','shell','systemPrompt',
283
+ // 'shellEnv','workingDirectory']`); a hand-called mod.apply(ctx) has no
284
+ // such declaration, so cordis refuses every service read with
285
+ // `cannot get property "shell" without inject` (v0.38.2 field report).
286
+ // ctx.plugin(mod) registers a REAL fiber with the declaration honored,
287
+ // runs apply itself, and the wrapped fiber is a thenable — awaiting it
288
+ // waits for the activation (and its async fiber start) to finish.
289
+ let fiber
290
+ try {
291
+ fiber = agent.ctx.plugin(mod)
292
+ } catch (error) {
293
+ log(`official tool-bash mount failed: ${error?.message ?? error}`)
294
294
  return undefined
295
295
  }
296
296
  try {
297
- await mod.apply(scope.ctx, {})
298
- return scope
297
+ await fiber
299
298
  } catch (error) {
300
- try { await scope.dispose() } catch { /* best effort */ }
301
- log(`official tool-bash activation failed: ${error?.message ?? error}`)
299
+ log(`official tool-bash fiber failed: ${error?.message ?? error}`)
302
300
  return undefined
303
301
  }
302
+ return {
303
+ fiber,
304
+ dispose: async () => { try { await fiber?.dispose?.() } catch { /* already gone */ } },
305
+ }
304
306
  }
305
307
 
306
308
  /**
@@ -427,7 +429,7 @@ export function mountInlineTools(ctx, deps) {
427
429
  log('official @deepseek-ai/dsh-tool-bash is not resolvable (neither the host loader nor a plain resolution found it); bash stays absent for this agent')
428
430
  return
429
431
  }
430
- const scope = await activateOfficialBash(agent, resolved, createScope, log)
432
+ const scope = await activateOfficialBash(agent, resolved, log)
431
433
  if (scope && tracked.get(agent) === state) {
432
434
  state.scope = scope
433
435
  log('official bash tool activated for this agent (Git Bash host; fake pwsh denied)')