dsh-gitbash-shell 0.35.2 → 0.35.3

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,18 @@
3
3
  > 倒序排列,新版本条目在最上面。条目格式:`## vX.Y.Z — YYYY-MM-DD` + 类型(feat / fix / docs / chore)+ 要点 + 相关链接。
4
4
  > 纪律见 AGENTS.md「变更记录纪律」:发版前先更新本文件并随版本提交;事故复盘、复现与真机验证记录也记在这里。
5
5
 
6
+ ## v0.35.3 — 2026-10-12
7
+
8
+ **类型**:fix(issue [#18](https://github.com/KannaKuron/dsh-gitbash-shell/issues/18) 三个独立根因;Windows 真机 v0.35.0 四面全灭)。
9
+
10
+ 外部用户(zjh1159086960-crypto / Lntano)的高质量报告:v0.35.0 四个新面(行内补位 bash、假 pwsh 遮蔽、`gitbash_pwsh`、`gb_*`)在 Windows 真机**一个都没生效**,且定位到行。三个根因相互独立,v0.35.2 的挂载时扫描只是让「断得更早的 wiring」多了一次运行机会——**根因仍在,四面依然全灭**。本版逐一修复,修法全部对照 dsh-ops 已在真机验证的实现:
11
+
12
+ - **根因一:`resolveHostPwsh7()` 零参调用 → 整个行内补位 wiring 流产**(主凶)。`exists` 是必填注入项,win32 下对象字面量求值即抛 `exists is not a function`,被 `mountInlineTools` 外层 catch 吞成一行日志——bash 补位、假 pwsh 遮蔽、`gitbash_pwsh` 三个面同死。修复:调用点注入 `{ exists: existsSync }`(`node:fs`,候选矩阵与 PATH 兜底逻辑本身无恙,报告者已验证)。
13
+ - **根因二:工具定义缺 host 必填的 `output` 契约 → `gitbash_pwsh` 与 `gb_*` 注册全被拒**。host `ToolRuntime.register` 硬校验 `output { schema, render }`(官方工具全部经 `defineTool` 包装补出)。修复:新增冻结的 `TOOL_OUTPUT_SCHEMA` + `renderTextContent`(dsh-ops `lib/tools.js` 的 OUTPUT_SCHEMA 同款形状,两个工具家族共享),`gitbashPwshDefinition` 与 `gb_*` 注册全部补上。
14
+ - **根因三:`restrict()` 从根 ctx 调用必然抛错 → 假 pwsh 的 deny 从未建立**。host 硬校验「requires a scoped context」,根 ctx 无 scope 键,异常被 catch 吞成 `fake-pwsh deny failed` 日志。修复:改走 **`agent.ctx.tools.restrict({ deny: ['pwsh'] })`**(dsh-ops `lib/index.js` `installHostShellDenial` 的同款调用;host 文档原文「Hiding tools from one agent is ctx.tools.restrict() on that agent's context」)。
15
+ - **冒烟**:116 项(115 过 + 1 跳)。新增两个测试:「the three issue-#18 root causes stay fixed」(三个根因的源码防回归锁,含 `doesNotMatch` 禁止零参调用与根 ctx restrict 回潮)与「the tool definitions speak the host output contract」(两家族共享契约 + render 行为:多块拼接、空 content 渲染单个空文本块)。
16
+ - **致谢**:三个根因全部由报告者先行定位,本版修复与其复现脚本逐一对齐;真机验证(四面生效)待报告者与用户复验。
17
+
6
18
  ## v0.35.2 — 2026-10-12
7
19
 
8
20
  **类型**:fix(Windows 真机报告;行内补位的时序洞 + 调用层兜底)。
package/dsh.plugin.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "id": "dsh-external/dsh-gitbash-shell",
3
- "version": "0.35.2",
3
+ "version": "0.35.3",
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.35.2",
3
+ "version": "0.35.3",
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": {
@@ -42,6 +42,11 @@ export const FASTCTX_FILE_TOOLS = Object.freeze(['inspect_local_file', 'grep', '
42
42
  export const GB_PREFIX = 'gb_'
43
43
  export const gbName = (raw) => GB_PREFIX + raw
44
44
 
45
+ // The HOST registry rejects any tool without an `output` contract (issue #18,
46
+ // root cause 2) — the frozen schema + text renderer the gitbash_pwsh
47
+ // definition ships, reused so both families speak one tool-result contract.
48
+ import { TOOL_OUTPUT_SCHEMA, renderTextContent } from './inline-tools.js'
49
+
45
50
  /** Payload packages probed, in order: upstream first, the pinned fork second. */
46
51
  export const PAYLOAD_PACKAGES = Object.freeze([
47
52
  { pkg: '@fastctx/win32-x64', exe: 'bin/fastctx.exe' },
@@ -323,6 +328,10 @@ export function mountFastctx(ctx, deps) {
323
328
  name: gbName(rawName),
324
329
  description: `${String(tool.description ?? rawName)}\n${FASTCTX_SANDBOX_NOTE}`,
325
330
  parameters: schema,
331
+ output: {
332
+ schema: TOOL_OUTPUT_SCHEMA,
333
+ render: (_args, value) => renderTextContent(value),
334
+ },
326
335
  async execute(args) {
327
336
  const translated = translateFastctxArgs(args, msysToWin)
328
337
  const result = await callWithReconnect(rawName, translated)
package/src/index.js CHANGED
@@ -2625,7 +2625,7 @@ export async function apply(ctx, config = {}) {
2625
2625
  if (!subprocess || typeof subprocess.spawn !== 'function') throw new Error('the host subprocess service is not available on this deployment')
2626
2626
  return subprocess.spawn(spec)
2627
2627
  },
2628
- pwshPath: resolveHostPwsh7(),
2628
+ pwshPath: resolveHostPwsh7({ exists: existsSync }),
2629
2629
  pwshEnabled: () => liveSettings.pwshTool(),
2630
2630
  msysToWin: (p) => translateMsysPath(p, buildTranslateEnv(readConfiguredBashPath())),
2631
2631
  echo: (text) => (liveSettings.dialect().posixPaths === true ? windowsToMsys(text) : text),
@@ -37,6 +37,44 @@
37
37
  * loads but mounts nothing.
38
38
  */
39
39
 
40
+ /**
41
+ * The tool-result contract the HOST registry requires from every registered
42
+ * tool (issue #18, root cause 2): `register()` throws
43
+ * `must declare output { schema, render, presentationMeta? }` unless the
44
+ * definition carries an `output` whose `schema` passes
45
+ * `assertSupportedJsonSchema` and whose `render` is a function. Both of this
46
+ * plugin's tool families return `{ content: [{ type: 'text', text }] }` from
47
+ * `execute`, so the schema and the text renderer below are shared verbatim
48
+ * with the FastCtx bridge — the same frozen shape dsh-ops ships for its own
49
+ * MCP-backed tools (proven on real Windows hosts).
50
+ */
51
+ export const TOOL_OUTPUT_SCHEMA = Object.freeze({
52
+ type: 'object',
53
+ properties: {
54
+ content: { type: 'array', items: {} },
55
+ structuredContent: {},
56
+ },
57
+ required: ['content'],
58
+ additionalProperties: false,
59
+ })
60
+
61
+ /**
62
+ * Render the `{ content: [...] }` execute value into text blocks. Non-text
63
+ * blocks and non-string text fields are skipped; empty content renders as one
64
+ * empty text block rather than an empty array, so the card never loses its
65
+ * placeholder.
66
+ * @param {unknown} value - the execute return value.
67
+ * @returns {Array<{ type: string, text: string }>} content blocks for the host renderer.
68
+ */
69
+ export function renderTextContent(value) {
70
+ const blocks = Array.isArray(value?.content) ? value.content : []
71
+ const text = blocks
72
+ .map((block) => (typeof block?.text === 'string' ? block.text : ''))
73
+ .filter((line) => line !== '')
74
+ .join('\n')
75
+ return [{ type: 'text', text }]
76
+ }
77
+
40
78
  /** Answer for one observed tool face. Pure. */
41
79
  export function inlineNeeds(names) {
42
80
  const set = new Set(Array.isArray(names) ? names : [])
@@ -308,9 +346,12 @@ export function mountInlineTools(ctx, deps) {
308
346
  if (needs.denyPwsh && !state.denyPwsh) {
309
347
  // Deny the fake pwsh first: restrict is per-agent and idempotent, and
310
348
  // the lying tool must never be callable, not even for the milliseconds
311
- // before the bash tool lands.
349
+ // before the bash tool lands. The call MUST go through the AGENT's
350
+ // scoped context (`agent.ctx.tools`, the dsh-ops-proven shape): on the
351
+ // ROOT context the host throws "requires a scoped context" and the
352
+ // deny silently never exists (issue #18, root cause 3).
312
353
  try {
313
- const deny = tools(ctx)?.restrict?.({ deny: ['pwsh'] })
354
+ const deny = agent.ctx?.tools?.restrict?.({ deny: ['pwsh'] })
314
355
  if (typeof deny === 'function') state.denyPwsh = deny
315
356
  } catch (error) {
316
357
  log(`fake-pwsh deny failed: ${error?.message ?? error}`)
@@ -413,6 +454,10 @@ export function gitbashPwshDefinition({ pwsh, spawn, allowed, msysToWin, echo })
413
454
  required: ['command'],
414
455
  additionalProperties: false,
415
456
  },
457
+ output: {
458
+ schema: TOOL_OUTPUT_SCHEMA,
459
+ render: (_args, value) => renderTextContent(value),
460
+ },
416
461
  async execute(args, exec) {
417
462
  const invalid = validatePwshArgs(args)
418
463
  if (invalid) throw new Error(invalid)