dsh-gitbash-shell 0.7.2 → 0.9.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/dsh.plugin.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "id": "dsh-external/dsh-gitbash-shell",
3
- "version": "0.7.2",
3
+ "version": "0.9.0",
4
4
  "main": "./src/index.js",
5
5
  "description": "Windows 全模式 Git Bash:用 Git for Windows bash 替换 PowerShell 执行器,并物化 standard/minimal/code/cordis 的 Git Bash 变体 preset。双 era 适配 dsh <= 0.1.1(内置 id 'code')与 >= 0.1.2(改名 'ptc'),启动探测自动选择,preset id 永不变。联动:dsh-ptc-cordis-preset(两者同装时 PTC 创造模式自动物化为 Git Bash 版)+ dsh-better-sidebar(终端 UI 标签与模型 terminal_* 工具自动切到 Git Bash)。",
6
6
  "engines": {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "dsh-gitbash-shell",
3
- "version": "0.7.2",
3
+ "version": "0.9.0",
4
4
  "description": "DSH plugin: run every agent shell command through Git for Windows bash on Windows instead of PowerShell. Replaces the pwsh executor with a Git Bash ctx.shell provider and materializes Git Bash variants of the standard/minimal/code/cordis agent presets into your user preset root at startup. On Windows it also adopts dsh-better-sidebar's terminal shell (UI tabs and the model-facing terminal_* tools) through the sidebar's official settings seam, and pairs with dsh-ptc-cordis-preset so the PTC Creation-mode preset materializes as its Git Bash variant. Ships both composition eras for dsh <= 0.1.1 (built-in preset id 'code') and dsh >= 0.1.2 (renamed id 'ptc') — the era is probed per boot and re-materialized automatically after a dsh upgrade, in either upgrade order; preset ids never change so pinned sessions stay valid.",
5
5
  "type": "module",
6
6
  "repository": {
@@ -10,7 +10,8 @@
10
10
  "main": "src/index.js",
11
11
  "exports": {
12
12
  ".": "./src/index.js",
13
- "./shell": "./src/shell.js"
13
+ "./shell": "./src/shell.js",
14
+ "./client": "./src/client.js"
14
15
  },
15
16
  "engines": {
16
17
  "node": ">=18"
@@ -18,6 +19,15 @@
18
19
  "dsh": {
19
20
  "bundle": {
20
21
  "patch": "./cordis.patch.yml"
22
+ },
23
+ "client": {
24
+ "inject": [
25
+ "@deepseek-ai/dsh-client-runtime",
26
+ "@deepseek-ai/dsh-client-locale",
27
+ "@deepseek-ai/dsh-client-ui-slots",
28
+ "@deepseek-ai/dsh-client-ui-settings"
29
+ ],
30
+ "platform": "web"
21
31
  }
22
32
  },
23
33
  "files": [
@@ -42,7 +52,8 @@
42
52
  ],
43
53
  "license": "MIT",
44
54
  "peerDependencies": {
45
- "@deepseek-ai/cordis": "^4.0.1"
55
+ "@deepseek-ai/cordis": "^4.0.1",
56
+ "@deepseek-ai/schemastery": "^3.18.1"
46
57
  },
47
58
  "publishConfig": {
48
59
  "access": "public"
package/src/client.js ADDED
@@ -0,0 +1,280 @@
1
+ /**
2
+ * dsh-gitbash-shell — browser half (hand-written ModuleLoader bundle).
3
+ *
4
+ * ONE job: the Settings → Plugins card that gates the unified POSIX path
5
+ * dialect. The Plugins tab dispatches the intersection of Host-served
6
+ * namespaces and registered cards, so the card appears only when the host
7
+ * half (src/index.js) has registered the 'gitbash-shell' settings namespace.
8
+ *
9
+ * The card flips one boolean — posixPaths — through a bound settingsScope:
10
+ * on → the host injects the order-126 directive (every tool takes MSYS
11
+ * drive roots /c/...) and the tools/execute wrapper translates
12
+ * path-argument fields (/x/ → X:/) for the Node-backed file tools;
13
+ * off → directive text is empty (dropped at assembly, zero prompt noise)
14
+ * and the wrapper passes calls through untouched (dsh-native).
15
+ * Bash itself is always Git Bash while the plugin is installed; the switch
16
+ * only governs the cross-tool path dialect.
17
+ *
18
+ * Hand-written bundle rules (no build step in this repo):
19
+ * - ONE window.__ModuleLoader__.load({...}) call, id = package name;
20
+ * - require restricted to the client-module BASELINE whitelist
21
+ * (react and @deepseek-ai/dsh-client-ui-primitives only, smoke-enforced);
22
+ * - plain React.createElement, no JSX/TS; components at module level;
23
+ * - dsh.client.inject in package.json lists the packages that must load
24
+ * first so locale / settingsScope / slots exist when this applies.
25
+ */
26
+ window.__ModuleLoader__.load({
27
+ id: "dsh-gitbash-shell",
28
+ factory: (require) => {
29
+ var module = { exports: {} };
30
+ var exports = module.exports;
31
+ Object.defineProperty(exports, Symbol.toStringTag, { value: "Module" });
32
+
33
+ var React = require("react");
34
+ var ui = require("@deepseek-ai/dsh-client-ui-primitives");
35
+
36
+ var E = React.createElement;
37
+ var useState = React.useState;
38
+
39
+ var TAG = "[gitbash-shell/client]";
40
+ /** Dictionary namespace (the plugin's own t seat). */
41
+ var NS = "gitbashShell";
42
+ /** The settings namespace the HOST half serves. */
43
+ var SETTINGS_NAMESPACE = "gitbash-shell";
44
+
45
+ // ── dictionaries (zh/en MUST stay key-aligned; smoke-enforced) ─────────
46
+
47
+ var zh = {
48
+ "title": "Git Bash 路径方言",
49
+ "cardDesc": "所有工具统一 /c/ POSIX 路径形式(默认关闭)",
50
+ "state.label": "当前状态",
51
+ "state.on": "已启用",
52
+ "state.off": "已关闭",
53
+ "switch.on": "启用",
54
+ "switch.off": "停用",
55
+ "error": "写入失败",
56
+ "hint": "启用后:所有工具(bash 命令与 workdir、read/write/edit/read_image/glob/grep 的路径参数)统一使用 MSYS 盘根 POSIX 路径(/c/Users/...);文件工具的路径参数由宿主自动翻译,模型无感。停用后恢复 dsh 原生行为(文件工具用 Windows 路径)。bash 始终是 Git Bash,不受此开关影响。",
57
+ };
58
+
59
+ var en = {
60
+ "title": "Git Bash path dialect",
61
+ "cardDesc": "One /c/ POSIX path style for every tool (off by default)",
62
+ "state.label": "Current state",
63
+ "state.on": "Enabled",
64
+ "state.off": "Disabled",
65
+ "switch.on": "Enable",
66
+ "switch.off": "Disable",
67
+ "error": "write failed",
68
+ "hint": "Enabled: every tool (bash commands and workdir, read/write/edit/read_image/glob/grep path arguments) uses MSYS drive-root POSIX paths (/c/Users/...); the host translates path fields for the file tools automatically. Disabled restores dsh-native behavior (Windows paths for file tools). Bash is always Git Bash while the plugin is installed; this switch only governs the cross-tool path dialect.",
69
+ };
70
+
71
+ // ── styles (gb- prefixed; tokens mirror PluginCard.module.css) ─────────
72
+
73
+ var STYLE_ID = "dsh-gitbash-shell-style";
74
+
75
+ var CSS = [
76
+ ".gb-card{list-style:none;border:1px solid var(--dsw-alias-border-l2);border-radius:12px;background:var(--dsw-alias-bg-layer-3);transition:border-color .16s,background .16s}",
77
+ ".gb-card:hover{border-color:var(--dsw-alias-label-dimmed)}",
78
+ ".gb-card.gb-open{background:var(--dsw-alias-bg-layer-2);border-color:var(--dsw-alias-label-dimmed)}",
79
+ ".gb-header{width:100%;appearance:none;border:0;background:none;font:inherit;color:inherit;text-align:left;cursor:pointer;display:flex;align-items:center;gap:12px;padding:14px 16px;border-radius:12px}",
80
+ ".gb-header:focus-visible{outline:2px solid var(--dsw-alias-brand-primary);outline-offset:-2px}",
81
+ ".gb-headText{flex:1;min-width:0;display:flex;flex-direction:column;gap:4px}",
82
+ ".gb-name{font-size:15px;font-weight:600;line-height:1.4;color:var(--dsw-alias-label-primary)}",
83
+ ".gb-desc{font-size:13px;line-height:1.5;color:var(--dsw-alias-label-tertiary)}",
84
+ ".gb-chevron{flex:none;color:var(--dsw-alias-label-tertiary);transition:transform .16s}",
85
+ ".gb-chevron.gb-chevronOpen{transform:rotate(180deg)}",
86
+ ".gb-body{display:flex;flex-direction:column;gap:12px;padding:4px 16px 16px;max-width:640px}",
87
+ ".gb-row{display:flex;align-items:baseline;gap:8px;font-size:13px;line-height:1.5;color:var(--dsw-alias-label-secondary)}",
88
+ ".gb-rowLabel{flex:none;color:var(--dsw-alias-label-tertiary)}",
89
+ ".gb-rowValue{min-width:0;overflow-wrap:anywhere;color:var(--dsw-alias-label-primary)}",
90
+ ".gb-seg{display:flex;gap:8px;flex-wrap:wrap}",
91
+ ".gb-segBtn{appearance:none;border:1px solid var(--dsw-alias-border-l2);border-radius:8px;background:var(--dsw-alias-bg-layer-2);color:var(--dsw-alias-label-secondary);font:inherit;font-size:13px;padding:6px 12px;cursor:pointer;transition:border-color .16s,color .16s}",
92
+ ".gb-segBtn:hover{border-color:var(--dsw-alias-label-dimmed)}",
93
+ ".gb-segBtn:focus-visible{outline:2px solid var(--dsw-alias-brand-primary);outline-offset:1px}",
94
+ ".gb-segBtn.gb-segActive{border-color:var(--dsw-alias-brand-primary);color:var(--dsw-alias-label-primary)}",
95
+ ".gb-hint{margin:0;font-size:12px;line-height:1.6;color:var(--dsw-alias-label-tertiary)}",
96
+ ".gb-error{margin:0;font-size:12px;color:var(--dsw-alias-status-danger, #e5484d)}",
97
+ ].join("\n");
98
+
99
+ function ensureStyles() {
100
+ try {
101
+ if (typeof document === "undefined" || typeof document.getElementById !== "function") return function () {};
102
+ if (document.getElementById(STYLE_ID)) return function () {};
103
+ var style = document.createElement("style");
104
+ style.id = STYLE_ID;
105
+ style.textContent = CSS;
106
+ document.head.appendChild(style);
107
+ return function () {
108
+ try {
109
+ if (style.parentNode) style.parentNode.removeChild(style);
110
+ } catch (error) { /* best effort */ }
111
+ };
112
+ } catch (error) {
113
+ return function () {};
114
+ }
115
+ }
116
+
117
+ /** Defensive primitives lookup: an unknown icon name degrades to a text chevron. */
118
+ function icon(name) {
119
+ try {
120
+ var component = ui && ui[name];
121
+ return typeof component === "function" ? component : null;
122
+ } catch (error) {
123
+ return null;
124
+ }
125
+ }
126
+
127
+ // ── error boundary (the dsh-better-workspace QuietBoundary pattern) ──────
128
+
129
+ /** A render failure degrades THIS card, never the settings page. */
130
+ function QuietBoundary(props) {}
131
+ QuietBoundary.prototype = Object.create(React.Component.prototype);
132
+ QuietBoundary.prototype.constructor = QuietBoundary;
133
+ QuietBoundary.state = { failed: false };
134
+ QuietBoundary.getDerivedStateFromError = function () { return { failed: true }; };
135
+ QuietBoundary.prototype.componentDidCatch = function (error) {
136
+ console.warn(TAG + " settings card render failed:", error && error.message ? error.message : error);
137
+ };
138
+ QuietBoundary.prototype.render = function () {
139
+ if (this.state && this.state.failed) return null;
140
+ return this.props.children;
141
+ };
142
+
143
+ // ── settings card (module-level component) ───────────────────────────────
144
+
145
+ /**
146
+ * The Settings → Plugins card. t arrives through the registration's
147
+ * locale field; scope arrives as a PLAIN prop from the inject factory.
148
+ * Snapshots are read per render; each write bumps a local tick so the
149
+ * card re-reads without an external-store hook adapter.
150
+ */
151
+ function GitBashCard(props) {
152
+ var t = typeof props.t === "function" ? props.t : function (key) { return key; };
153
+
154
+ var openState = useState(false);
155
+ var open = openState[0];
156
+ var setOpen = openState[1];
157
+
158
+ var errorState = useState("");
159
+ var error = errorState[0];
160
+ var setError = errorState[1];
161
+
162
+ var tickState = useState(0);
163
+ var tick = tickState[0];
164
+ var bumpTick = tickState[1];
165
+ void tick;
166
+
167
+ var scope = props.scope;
168
+
169
+ var snap = { status: "unavailable" };
170
+ try {
171
+ if (scope && typeof scope.getSnapshot === "function") snap = scope.getSnapshot();
172
+ } catch (error_) { /* keep unavailable */ }
173
+
174
+ if (snap.status !== "ready") return null;
175
+
176
+ var value = snap.value || {};
177
+ var enabled = value.posixPaths === true;
178
+
179
+ function write(next) {
180
+ setError("");
181
+ scope.set("posixPaths", next)
182
+ .then(function () { bumpTick(function (n) { return n + 1; }); })
183
+ .catch(function (err) {
184
+ bumpTick(function (n) { return n + 1; });
185
+ setError(t("error") + ": " + (err && err.message ? err.message : String(err)));
186
+ });
187
+ }
188
+
189
+ var Chevron = icon("IconChevronDownOutline14");
190
+
191
+ return E("li", { className: "gb-card" + (open ? " gb-open" : "") },
192
+ E("button", {
193
+ type: "button",
194
+ className: "gb-header",
195
+ "aria-expanded": open,
196
+ onClick: function () { setOpen(!open); },
197
+ },
198
+ E("span", { className: "gb-headText" },
199
+ E("span", { className: "gb-name" }, t("title")),
200
+ E("span", { className: "gb-desc" }, t("cardDesc")),
201
+ ),
202
+ Chevron
203
+ ? E(Chevron, { className: "gb-chevron" + (open ? " gb-chevronOpen" : "") })
204
+ : E("span", { className: "gb-chevron" + (open ? " gb-chevronOpen" : "") }, "▾"),
205
+ ),
206
+ open ? E("div", { className: "gb-body" },
207
+ E("div", { className: "gb-row" },
208
+ E("span", { className: "gb-rowLabel" }, t("state.label") + ":"),
209
+ E("span", { className: "gb-rowValue" }, enabled ? t("state.on") : t("state.off")),
210
+ ),
211
+ E("div", { className: "gb-seg" },
212
+ E("button", {
213
+ type: "button",
214
+ className: "gb-segBtn" + (enabled ? " gb-segActive" : ""),
215
+ onClick: function () { if (!enabled) write(true); },
216
+ }, t("switch.on")),
217
+ E("button", {
218
+ type: "button",
219
+ className: "gb-segBtn" + (!enabled ? " gb-segActive" : ""),
220
+ onClick: function () { if (enabled) write(false); },
221
+ }, t("switch.off")),
222
+ ),
223
+ error ? E("p", { className: "gb-error" }, error) : null,
224
+ E("p", { className: "gb-hint" }, t("hint")),
225
+ ) : null,
226
+ );
227
+ }
228
+
229
+ // ── plugin ────────────────────────────────────────────────────────────
230
+
231
+ exports.name = "dsh-gitbash-shell/client";
232
+
233
+ /** Required client services: locale runtime, settings scopes, slots. */
234
+ exports.inject = ["locale", "settingsScope", "slots"];
235
+
236
+ exports.apply = function (ctx) {
237
+ var scope = ctx.settingsScope.bind({ namespace: SETTINGS_NAMESPACE });
238
+
239
+ ctx.effect(function () {
240
+ var disposers = [ensureStyles()];
241
+ try {
242
+ var disposeDict = ctx.locale.register(NS, { zh: zh, en: en });
243
+ if (typeof disposeDict === "function") disposers.push(disposeDict);
244
+ } catch (error) {
245
+ console.warn(TAG + " dictionary registration failed:", error && error.message ? error.message : error);
246
+ }
247
+ return function () {
248
+ for (var i = 0; i < disposers.length; i++) {
249
+ try {
250
+ if (typeof disposers[i] === "function") disposers[i]();
251
+ } catch (error) { /* best effort */ }
252
+ }
253
+ };
254
+ }, "dsh-gitbash-shell: styles, dictionaries");
255
+
256
+ // Guarded registration (the dsh-better-workspace pattern): a thrown
257
+ // register degrades this one seat, never the plugin fiber.
258
+ try {
259
+ var slots = ctx.slots;
260
+ if (!slots || typeof slots.inject !== "function") {
261
+ console.warn(TAG + " slots service unavailable; settings card idle");
262
+ return;
263
+ }
264
+ slots.inject("settings.plugin.item",
265
+ {
266
+ name: "settings.plugin.item",
267
+ key: SETTINGS_NAMESPACE,
268
+ locale: NS,
269
+ inject: function () { return { scope: scope }; },
270
+ },
271
+ function (props) {
272
+ return E(QuietBoundary, null, E(GitBashCard, props));
273
+ },
274
+ );
275
+ } catch (error) {
276
+ console.warn(TAG + " settings card registration failed:", error && error.message ? error.message : error);
277
+ }
278
+ };
279
+ },
280
+ });
package/src/index.js CHANGED
@@ -290,6 +290,75 @@ export function installRegisterShim(reg) {
290
290
  }
291
291
  }
292
292
 
293
+ // ── unified path dialect: settings namespace + gate (v0.9.0) ───────────────
294
+ //
295
+ // The dialect (directive + translation wrapper) is OFF by default and gated
296
+ // by the posixPaths boolean in the 'gitbash-shell' settings namespace,
297
+ // flipped from the Settings → Plugins card (src/client.js). The directive's
298
+ // text closure returns '' while off — empty context contributions are
299
+ // dropped at assembly, so a disabled dialect adds zero prompt noise; the
300
+ // wrapper reads the same value per dispatch and passes calls through.
301
+
302
+ const SETTINGS_NAMESPACE = 'gitbash-shell'
303
+
304
+ /** The full directive text, injected only while posixPaths is on. */
305
+ const POSIX_DIRECTIVE_TEXT = 'The host is Windows and the working shell is Git for Windows bash: use POSIX-style paths with MSYS drive roots everywhere — /c/Users/..., /e/project/... Every tool accepts this form: bash commands and the workdir parameter, and the file tools (read, write, edit, read_image, glob, grep) file_path/path arguments — MSYS roots are translated for them automatically, so never switch to a Windows form on their behalf. Never emit Windows drive-letter paths (C:\Users or C:/Users): when instructions, facts, or context show one, rewrite it to the POSIX form before use; when a tool result prints a Windows path, use its POSIX form (/c/...) in later calls.'
306
+
307
+ /** Read the posixPaths switch from the live settings service; never throws. */
308
+ export function readPosixPaths(ctxLike) {
309
+ try {
310
+ const settings = ctxLike && typeof ctxLike.get === 'function' ? ctxLike.get('settings') : undefined
311
+ const value = settings && typeof settings.get === 'function' ? settings.get(SETTINGS_NAMESPACE) : undefined
312
+ return !!(value && value.posixPaths === true)
313
+ } catch {
314
+ return false
315
+ }
316
+ }
317
+
318
+ // ── MSYS drive-root path translation (Windows, EVERY tool dispatch) ────────
319
+ //
320
+ // The directive below tells the model to use POSIX-style MSYS paths (/c/...)
321
+ // for EVERY tool. Bash digests those natively, but the Node-backed file tools
322
+ // resolve an MSYS root against the current drive (/c/Users -> C:/c/Users), so a
323
+ // global tools/execute wrapper rewrites the path-bearing ARGUMENT FIELDS (by
324
+ // field NAME, not by tool name — dynamic tools using the standard field names
325
+ // are covered too) into the drive-letter form the host accepts. The bash
326
+ // command field is deliberately untouched: MSYS roots are Git Bash native
327
+ // there. Defensive by design: the waterfall hands the same mutable exec object
328
+ // to the tool body, but if a future registry freezes it the write throws, is
329
+ // swallowed, and the call proceeds with the model original arguments (the
330
+ // directive still stands).
331
+
332
+ const MSYS_DRIVE_ROOT = /^\/([a-z])\/(.*)$/i
333
+ const TRANSLATABLE_PATH_FIELDS = new Set(['file_path', 'path', 'workdir'])
334
+
335
+ /** '/c/Users/x' -> 'C:/Users/x'; any other shape returns the input unchanged. */
336
+ export function translateMsysPath(value) {
337
+ if (typeof value !== 'string') return value
338
+ const match = MSYS_DRIVE_ROOT.exec(value)
339
+ if (!match) return value
340
+ return match[1].toUpperCase() + ':/' + match[2]
341
+ }
342
+
343
+ /**
344
+ * Rewrite MSYS drive roots in an arguments object path fields, in place.
345
+ * @returns {boolean} whether any field changed (false also when args is not
346
+ * an object — the caller treats that as nothing to do).
347
+ */
348
+ export function translatePathArguments(args) {
349
+ if (!args || typeof args !== 'object') return false
350
+ let changed = false
351
+ for (const key of Object.keys(args)) {
352
+ if (!TRANSLATABLE_PATH_FIELDS.has(key)) continue
353
+ const value = args[key]
354
+ if (typeof value !== 'string') continue
355
+ const translated = translateMsysPath(value)
356
+ if (translated === value) continue
357
+ try { args[key] = translated; changed = true } catch { /* frozen: degrade */ }
358
+ }
359
+ return changed
360
+ }
361
+
293
362
  // ── dsh-better-sidebar shell cooperation ───────────────────────────────────
294
363
  //
295
364
  // dsh-better-sidebar (v0.15.2+) resolves its terminal shell PER OPEN: a
@@ -458,14 +527,65 @@ export async function apply(ctx, config = {}) {
458
527
  const disposeGitBash = ctx.provide('gitBash', gitBashCapability)
459
528
  ctx.effect(() => disposeGitBash, 'dsh-gitbash-shell: gitBash capability')
460
529
 
461
- // ── POSIX path directive (Windows Git Bash, EVERY session) ─────────────
462
- // The host shell is Git for Windows bash for every mode/preset on this
463
- // machine: drive-letter paths (C:/..., C:\...) are a Windows-shell idiom
464
- // that Git Bash only sometimes tolerates, /c/... is the unambiguous form.
465
- // One global runtime-context directive (same channel dsh-agent-lang uses;
530
+ // ── settings namespace: the posixPaths switch (default OFF) ──────────────
531
+ // Served on the host so the Plugins tab pairs it with the browser card
532
+ // (the tab dispatches Host-served namespaces ∩ registered cards). Dynamic
533
+ // imports keep the zero-dependency smoke path importable; the schema MUST
534
+ // be a callable schemastery object (dsh-settings calls schema(merged)).
535
+ try {
536
+ ctx.inject(['settings'], (sctx) => {
537
+ Promise.all([import('@deepseek-ai/dsh-settings'), import('@deepseek-ai/schemastery')])
538
+ .then(([ds, sm]) => {
539
+ const settings = sctx && sctx.settings
540
+ if (!settings || typeof settings.register !== 'function') return
541
+ const Schema = sm.default
542
+ // Era probe: newer dsh register() takes a plain string; the older
543
+ // one accepted the branded helper — one call satisfies both.
544
+ const ns = typeof ds.settingsNamespace === 'function'
545
+ ? ds.settingsNamespace(SETTINGS_NAMESPACE)
546
+ : SETTINGS_NAMESPACE
547
+ settings.register(ns, Schema.object({
548
+ posixPaths: Schema.boolean().default(false),
549
+ }))
550
+ console.log(`${TAG} settings namespace registered: ${SETTINGS_NAMESPACE} (posixPaths default off)`)
551
+ })
552
+ .catch((error) => {
553
+ console.log(`${TAG} settings namespace registration FAILED: ${error && error.stack || String(error)}`)
554
+ })
555
+ })
556
+ } catch (error) {
557
+ console.log(`${TAG} settings inject wiring failed: ${error?.message ?? error}`)
558
+ }
559
+
560
+ // ── MSYS path translation on every tool dispatch (Windows only) ─────────
561
+ // Covers every preset and mode: the wrapper sits on the global
562
+ // tools/execute waterfall, through which model-direct calls, PTC run_code
563
+ // sub-dispatches, and dynamic-tool calls all pass.
564
+ if (process.platform === 'win32') {
565
+ try {
566
+ ctx.on('tools/execute', (exec, next) => {
567
+ try {
568
+ if (exec && exec.arguments && typeof exec.arguments === 'object' && readPosixPaths(ctx)) {
569
+ translatePathArguments(exec.arguments)
570
+ }
571
+ } catch { /* never block a call on translation */ }
572
+ return next()
573
+ })
574
+ console.log(TAG + ' MSYS drive-root translation active on tool dispatch')
575
+ } catch (error) {
576
+ console.log(TAG + ' tools/execute wiring failed: ' + (error?.message ?? error))
577
+ }
578
+ }
579
+
580
+ // ── Unified POSIX path directive (Windows Git Bash, EVERY session) ─────
581
+ // ONE path style for every tool: MSYS drive roots (/c/Users/...). Bash
582
+ // digests them natively; the file tools receive the drive-letter form
583
+ // through the translation wrapper above, so the model never has to switch
584
+ // dialects. The directive also pins the rewrite rules that neutralize the
585
+ // Windows-form facts the harness injects elsewhere (backslash cwd strings,
586
+ // tool results printing drive-letter paths). Same channel as dsh-agent-lang;
466
587
  // order 126 sits after the official CONTEXT_ORDERS 110/115/120 and beside
467
- // the 125 free slot) so no preset/persona has to carry it. Non-Windows
468
- // mounts inject nothing.
588
+ // the 125 free slot. Non-Windows mounts inject nothing.
469
589
  if (process.platform === 'win32') {
470
590
  try {
471
591
  ctx.inject(['systemPrompt'], (pctx) => {
@@ -473,9 +593,17 @@ export async function apply(ctx, config = {}) {
473
593
  pctx.effect(() => pctx.systemPrompt.context({
474
594
  name: 'gitbash-shell:posix-paths',
475
595
  order: 126,
476
- text: 'The working shell is Git for Windows bash: use POSIX-style paths only — drive roots are /c/, /d/, ... (/c/Users/..., /c/Program Files/...). Windows drive-letter path forms are not accepted.',
596
+ text: () => {
597
+ try {
598
+ const settings = pctx.get('settings')
599
+ const value = settings && typeof settings.get === 'function' ? settings.get(SETTINGS_NAMESPACE) : undefined
600
+ return value && value.posixPaths === true ? POSIX_DIRECTIVE_TEXT : ''
601
+ } catch {
602
+ return ''
603
+ }
604
+ },
477
605
  }), 'dsh-gitbash-shell: posix-path context')
478
- console.log(TAG + ' POSIX-path directive context active (win32)')
606
+ console.log(TAG + ' unified POSIX-path directive context active (win32, gated by the posixPaths setting)')
479
607
  } catch (error) {
480
608
  console.log(TAG + ' context registration failed: ' + (error?.message ?? error))
481
609
  }
@@ -553,4 +681,4 @@ export async function apply(ctx, config = {}) {
553
681
  }
554
682
 
555
683
  // Test surface: pure helpers, no Cordis context required.
556
- export const _internal = { PRESET_IDS, MARKER_FILE, classify, materialize, cleanupOnDispose, firstUserRoot, hashTree, skillsHashes, syncDecision, installRegisterShim, baseForRoster, detectBase, pickComposition }
684
+ export const _internal = { PRESET_IDS, translateMsysPath, translatePathArguments, readPosixPaths, MARKER_FILE, classify, materialize, cleanupOnDispose, firstUserRoot, hashTree, skillsHashes, syncDecision, installRegisterShim, baseForRoster, detectBase, pickComposition }