dsh-gitbash-shell 0.8.0 → 0.10.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.8.0",
3
+ "version": "0.10.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.8.0",
3
+ "version": "0.10.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,50 @@ 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 working shell is Git for Windows bash: paths use MSYS drive roots (/c/Users/...), and every tool accepts that form directly.'
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
+ // Windows absolute path -> MSYS drive-root form, for rewriting the OFFICIAL
319
+ // prompt text in place (v0.10.0). Two patterns keep quoted spaced paths
320
+ // ("C:\Program Files\Git") whole while bare paths stop at whitespace or a
321
+ // closing punctuation; the lookbehind set rejects URL schemes (https:),
322
+ // file:// forms, and anything already mid-word, so only real drive-letter
323
+ // paths are translated. Separators normalize to single slashes.
324
+ const BARE_WIN_PATH = /(?<![A-Za-z0-9:\\/"'`])([A-Za-z]):(?:\\|\/)([^\s"'`<>|),;:!?]+)/g
325
+ const QUOTED_WIN_PATH = /(["'`])([A-Za-z]):(?:\\|\/)([^`]*?)\1/g
326
+
327
+ /** Rewrite every Windows absolute path in a text to the MSYS form; pure. */
328
+ export function windowsToMsys(text) {
329
+ if (typeof text !== 'string' || text.length === 0) return text
330
+ return text
331
+ .replace(QUOTED_WIN_PATH, (_whole, quote, drive, rest) =>
332
+ quote + '/' + drive.toLowerCase() + '/' + rest.replace(/[\\/]+/g, '/') + quote)
333
+ .replace(BARE_WIN_PATH, (_whole, drive, rest) =>
334
+ '/' + drive.toLowerCase() + '/' + rest.replace(/[\\/]+/g, '/'))
335
+ }
336
+
293
337
  // ── MSYS drive-root path translation (Windows, EVERY tool dispatch) ────────
294
338
  //
295
339
  // The directive below tells the model to use POSIX-style MSYS paths (/c/...)
@@ -502,6 +546,74 @@ export async function apply(ctx, config = {}) {
502
546
  const disposeGitBash = ctx.provide('gitBash', gitBashCapability)
503
547
  ctx.effect(() => disposeGitBash, 'dsh-gitbash-shell: gitBash capability')
504
548
 
549
+ // ── settings namespace: the posixPaths switch (default OFF) ──────────────
550
+ // Served on the host so the Plugins tab pairs it with the browser card
551
+ // (the tab dispatches Host-served namespaces ∩ registered cards). Dynamic
552
+ // imports keep the zero-dependency smoke path importable; the schema MUST
553
+ // be a callable schemastery object (dsh-settings calls schema(merged)).
554
+ try {
555
+ ctx.inject(['settings'], (sctx) => {
556
+ Promise.all([import('@deepseek-ai/dsh-settings'), import('@deepseek-ai/schemastery')])
557
+ .then(([ds, sm]) => {
558
+ const settings = sctx && sctx.settings
559
+ if (!settings || typeof settings.register !== 'function') return
560
+ const Schema = sm.default
561
+ // Era probe: newer dsh register() takes a plain string; the older
562
+ // one accepted the branded helper — one call satisfies both.
563
+ const ns = typeof ds.settingsNamespace === 'function'
564
+ ? ds.settingsNamespace(SETTINGS_NAMESPACE)
565
+ : SETTINGS_NAMESPACE
566
+ settings.register(ns, Schema.object({
567
+ posixPaths: Schema.boolean().default(true),
568
+ }))
569
+ console.log(`${TAG} settings namespace registered: ${SETTINGS_NAMESPACE} (posixPaths default on)`)
570
+ })
571
+ .catch((error) => {
572
+ console.log(`${TAG} settings namespace registration FAILED: ${error && error.stack || String(error)}`)
573
+ })
574
+ })
575
+ } catch (error) {
576
+ console.log(`${TAG} settings inject wiring failed: ${error?.message ?? error}`)
577
+ }
578
+
579
+ // ── prompt-assembly path dialect (Windows only, gated; v0.10.0) ────────
580
+ // While posixPaths is on, EVERY Windows absolute path the model would
581
+ // see — section text, context text, and prompt variables — is rewritten
582
+ // IN PLACE to the MSYS drive-root form. Nothing is added or removed: the
583
+ // official prompt keeps its exact shape and only the path examples
584
+ // change dialect, so the model meets a /c/ world at the source instead
585
+ // of being asked to translate Windows forms it sees. Our own directive
586
+ // context is skipped (self-reference), and tool schemas stay untouched
587
+ // (they carry no drive-letter examples today, and blind string rewriting
588
+ // could corrupt pattern/default fields).
589
+ if (process.platform === 'win32') {
590
+ try {
591
+ ctx.on('system-prompt/assemble', (assembly, _assembleContext, next) => {
592
+ try {
593
+ if (readPosixPaths(ctx) && assembly && typeof assembly === 'object') {
594
+ for (const section of Array.isArray(assembly.sections) ? assembly.sections : []) {
595
+ if (section && typeof section.text === 'string') section.text = windowsToMsys(section.text)
596
+ }
597
+ for (const assembled of Array.isArray(assembly.contexts) ? assembly.contexts : []) {
598
+ if (!assembled || assembled.name === 'gitbash-shell:posix-paths') continue
599
+ if (typeof assembled.text === 'string') assembled.text = windowsToMsys(assembled.text)
600
+ }
601
+ const variables = assembly.variables
602
+ if (variables && typeof variables === 'object') {
603
+ for (const key of Object.keys(variables)) {
604
+ if (typeof variables[key] === 'string') variables[key] = windowsToMsys(variables[key])
605
+ }
606
+ }
607
+ }
608
+ } catch { /* never block assembly */ }
609
+ return next()
610
+ })
611
+ console.log(TAG + ' prompt-assembly path dialect active (gated by the posixPaths setting)')
612
+ } catch (error) {
613
+ console.log(TAG + ' system-prompt/assemble wiring failed: ' + (error?.message ?? error))
614
+ }
615
+ }
616
+
505
617
  // ── MSYS path translation on every tool dispatch (Windows only) ─────────
506
618
  // Covers every preset and mode: the wrapper sits on the global
507
619
  // tools/execute waterfall, through which model-direct calls, PTC run_code
@@ -510,7 +622,7 @@ export async function apply(ctx, config = {}) {
510
622
  try {
511
623
  ctx.on('tools/execute', (exec, next) => {
512
624
  try {
513
- if (exec && exec.arguments && typeof exec.arguments === 'object') {
625
+ if (exec && exec.arguments && typeof exec.arguments === 'object' && readPosixPaths(ctx)) {
514
626
  translatePathArguments(exec.arguments)
515
627
  }
516
628
  } catch { /* never block a call on translation */ }
@@ -538,9 +650,17 @@ export async function apply(ctx, config = {}) {
538
650
  pctx.effect(() => pctx.systemPrompt.context({
539
651
  name: 'gitbash-shell:posix-paths',
540
652
  order: 126,
541
- 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.',
653
+ text: () => {
654
+ try {
655
+ const settings = pctx.get('settings')
656
+ const value = settings && typeof settings.get === 'function' ? settings.get(SETTINGS_NAMESPACE) : undefined
657
+ return value && value.posixPaths === true ? POSIX_DIRECTIVE_TEXT : ''
658
+ } catch {
659
+ return ''
660
+ }
661
+ },
542
662
  }), 'dsh-gitbash-shell: posix-path context')
543
- console.log(TAG + ' unified POSIX-path directive context active (win32)')
663
+ console.log(TAG + ' unified POSIX-path directive context active (win32, gated by the posixPaths setting)')
544
664
  } catch (error) {
545
665
  console.log(TAG + ' context registration failed: ' + (error?.message ?? error))
546
666
  }
@@ -618,4 +738,4 @@ export async function apply(ctx, config = {}) {
618
738
  }
619
739
 
620
740
  // Test surface: pure helpers, no Cordis context required.
621
- export const _internal = { PRESET_IDS, translateMsysPath, translatePathArguments, MARKER_FILE, classify, materialize, cleanupOnDispose, firstUserRoot, hashTree, skillsHashes, syncDecision, installRegisterShim, baseForRoster, detectBase, pickComposition }
741
+ export const _internal = { PRESET_IDS, translateMsysPath, translatePathArguments, readPosixPaths, windowsToMsys, MARKER_FILE, classify, materialize, cleanupOnDispose, firstUserRoot, hashTree, skillsHashes, syncDecision, installRegisterShim, baseForRoster, detectBase, pickComposition }