dsh-gitbash-shell 0.12.0 → 0.13.1

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.
@@ -7,13 +7,13 @@
7
7
  # change across the 0.1.1 → 0.1.2 code→ptc rename, so no agent.cordis.ptc.yml
8
8
  # twin exists here. The plugin probes the roster and writes this file as the
9
9
  # preset's own agent.cordis.yml either way.
10
- # The `minimal` agent preset: a fixed-prompt, two-tool coding-agent composition.
10
+ # The `minimal` agent preset: a fixed-prompt, single-tool coding-agent composition.
11
11
  #
12
12
  # The persona is the complete system prompt, so global identity, Web orientation,
13
13
  # tool guidance, and later assembly listeners cannot add prompt text. Runtime
14
- # context snapshots are suppressed for this preset, and the model composes only
15
- # the persistent shell (Git for Windows bash; bash on POSIX) and
16
- # `str_replace_editor`. Context compaction is absent.
14
+ # context snapshots are suppressed for this preset, and the model receives only
15
+ # the persistent shell (Git for Windows bash; bash on POSIX). Context compaction
16
+ # is absent.
17
17
 
18
18
  - id: persona
19
19
  name: '@deepseek-ai/dsh-persona'
@@ -51,8 +51,7 @@
51
51
  description: |-
52
52
  Run commands in a bash shell (Git for Windows bash on Windows: native paths like C:/... work, and PATH plus the rest of the environment are inherited from the host)
53
53
  * When invoking this tool, the contents of the "command" parameter does NOT need to be XML-escaped.
54
- * You don't have access to the internet via this tool.
55
- * You do have access to a mirror of common linux and python packages via apt and pip.
54
+ * Network access depends on the task environment. Prefer configured mirrors/proxies when they are available.
56
55
  * State is persistent across command calls and discussions with the user.
57
56
  * To inspect a particular line range of a file, e.g. lines 10-25, try 'sed -n 10,25p /path/to/the/file'.
58
57
  * Please avoid commands that may produce a very large amount of output.
@@ -78,21 +77,3 @@
78
77
  * Use native Windows paths (C:\...) and $env:NAME variables; this is PowerShell, not bash.
79
78
  * Please avoid commands that may produce a very large amount of output.
80
79
  * Please run long lived commands in the background, e.g. 'Start-Job' or start a server with Start-Process.
81
-
82
- # The bare local filesystem shadows the host's sandboxed provider only for this
83
- # preset. The editor shares that realm and requires absolute paths.
84
- - id: filesystem
85
- name: cordis:group
86
- group: true
87
- isolate:
88
- fs: true
89
- config:
90
- - id: fs-local
91
- name: '@deepseek-ai/dsh-fs-local'
92
- config:
93
- cwd: !!js process.env.DSH_CWD ?? process.cwd()
94
-
95
- - id: str-replace-editor
96
- name: '@deepseek-ai/dsh-tool-str-replace-editor'
97
- config:
98
- maxOutputChars: 16000
@@ -2,13 +2,13 @@
2
2
  # change across the 0.1.1 → 0.1.2 code→ptc rename, so no agent.cordis.ptc.yml
3
3
  # twin exists here. The plugin probes the roster and writes this file as the
4
4
  # preset's own agent.cordis.yml either way.
5
- # The `minimal` agent preset: a fixed-prompt, two-tool coding-agent composition.
5
+ # The `minimal` agent preset: a fixed-prompt, single-tool coding-agent composition.
6
6
  #
7
7
  # The persona is the complete system prompt, so global identity, Web orientation,
8
8
  # tool guidance, and later assembly listeners cannot add prompt text. Runtime
9
- # context snapshots are suppressed for this preset, and the model composes only
10
- # the persistent shell (Git for Windows bash; bash on POSIX) and
11
- # `str_replace_editor`. Context compaction is absent.
9
+ # context snapshots are suppressed for this preset, and the model receives only
10
+ # the persistent shell (Git for Windows bash; bash on POSIX). Context compaction
11
+ # is absent.
12
12
 
13
13
  - id: persona
14
14
  name: '@deepseek-ai/dsh-persona'
@@ -46,8 +46,7 @@
46
46
  description: |-
47
47
  Run commands in a bash shell (Git for Windows bash on Windows: native paths like C:/... work, and PATH plus the rest of the environment are inherited from the host)
48
48
  * When invoking this tool, the contents of the "command" parameter does NOT need to be XML-escaped.
49
- * You don't have access to the internet via this tool.
50
- * You do have access to a mirror of common linux and python packages via apt and pip.
49
+ * Network access depends on the task environment. Prefer configured mirrors/proxies when they are available.
51
50
  * State is persistent across command calls and discussions with the user.
52
51
  * To inspect a particular line range of a file, e.g. lines 10-25, try 'sed -n 10,25p /path/to/the/file'.
53
52
  * Please avoid commands that may produce a very large amount of output.
@@ -73,21 +72,3 @@
73
72
  * Use native Windows paths (C:\...) and $env:NAME variables; this is PowerShell, not bash.
74
73
  * Please avoid commands that may produce a very large amount of output.
75
74
  * Please run long lived commands in the background, e.g. 'Start-Job' or start a server with Start-Process.
76
-
77
- # The bare local filesystem shadows the host's sandboxed provider only for this
78
- # preset. The editor shares that realm and requires absolute paths.
79
- - id: filesystem
80
- name: cordis:group
81
- group: true
82
- isolate:
83
- fs: true
84
- config:
85
- - id: fs-local
86
- name: '@deepseek-ai/dsh-fs-local'
87
- config:
88
- cwd: !!js process.env.DSH_CWD ?? process.cwd()
89
-
90
- - id: str-replace-editor
91
- name: '@deepseek-ai/dsh-tool-str-replace-editor'
92
- config:
93
- maxOutputChars: 16000
@@ -1,3 +1,3 @@
1
1
  name: 极简模式 · Git Bash
2
- description: 仅提供持久 bash 与 str_replace_editor 的双工具编码 Agent。(Shell 使用 Git Bash)
2
+ description: 仅提供持久 shell 的单工具编码 Agent。(Shell 使用 Git Bash)
3
3
  order: 3
package/dsh.plugin.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "id": "dsh-external/dsh-gitbash-shell",
3
- "version": "0.12.0",
3
+ "version": "0.13.1",
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.12.0",
3
+ "version": "0.13.1",
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": {
@@ -41,7 +41,8 @@
41
41
  "LICENSE"
42
42
  ],
43
43
  "scripts": {
44
- "test": "node --test tests/smoke.mjs"
44
+ "test": "node --test tests/smoke.mjs",
45
+ "version": "node -e \"const fs=require('fs');const m=JSON.parse(fs.readFileSync('dsh.plugin.json','utf8'));const v=JSON.parse(fs.readFileSync('package.json','utf8')).version;m.version=v;fs.writeFileSync('dsh.plugin.json',JSON.stringify(m,null,2)+'\\n')\" && git add dsh.plugin.json"
45
46
  },
46
47
  "keywords": [
47
48
  "dsh",
package/src/index.js CHANGED
@@ -205,6 +205,32 @@ export async function detectPersonaEra(agentPresets) {
205
205
  }
206
206
  }
207
207
 
208
+ /**
209
+ * Does the SHIPPED composition already carry the official `present` row? The
210
+ * row (`@deepseek-ai/dsh-tool-present`, immutable file-delivery download
211
+ * cards) first shipped with dsh 0.1.5-alpha.2, and a composition row that
212
+ * cannot be imported rejects the WHOLE preset mount — so the row is injected
213
+ * only on hosts whose own shipped presets have it. The live roster is the
214
+ * authority: package resolution alone would lie on CLI installs (first-party
215
+ * packages live outside the profile) and on linked development trees.
216
+ * `minimal` never gains the row (single-tool preset). Never throws.
217
+ */
218
+ async function detectPresentSupport(agentPresets) {
219
+ try {
220
+ const list = await agentPresets.list()
221
+ const entries = Array.isArray(list) ? list : []
222
+ const entry = ['ptc', 'standard', 'cordis']
223
+ .map((id) => entries.find((p) => p && p.id === id && typeof p.path === 'string'))
224
+ .find(Boolean)
225
+ if (!entry) return false
226
+ const file = /\.yml$/.test(entry.path) ? entry.path : join(entry.path, 'agent.cordis.yml')
227
+ return readFileSync(file, 'utf8').includes("'@deepseek-ai/dsh-tool-present'")
228
+ } catch (error) {
229
+ console.log(`${TAG} present-row probe failed (${error?.message ?? error}) — not injecting the row`)
230
+ return false
231
+ }
232
+ }
233
+
208
234
  /**
209
235
  * Pick the committed composition asset inside one variant directory (pure).
210
236
  * The ptc-era twin is preferred when the roster says `ptc`; the persona-split
@@ -224,11 +250,18 @@ function pickComposition(base, persona, available) {
224
250
  }
225
251
 
226
252
  /** Startup decision for an existing unmodified tree: 'refresh' or 'idle'. */
227
- function syncDecision({ state, marker, version, sourceHashes, base = 'code', persona = 'text' }) {
253
+ function syncDecision({ state, marker, version, sourceHashes, base = 'code', persona = 'text', present = false }) {
228
254
  if (state !== 'unmodified' || !marker) return 'refresh'
229
255
  if (marker.version !== version) return 'refresh'
230
256
  if (marker.base !== base) return 'refresh'
231
257
  if ((marker.persona ?? 'text') !== persona) return 'refresh'
258
+ // Host-capability flip: @deepseek-ai/dsh-tool-present first shipped with dsh
259
+ // 0.1.5-alpha.2. A preset composed with the row on a host that lacks the
260
+ // package REJECTS THE WHOLE MOUNT (mount.ts: a row whose module failed to
261
+ // import already rejects the mount), so the row is injected only when the
262
+ // host resolves it — and a host upgrade past that point must re-materialize
263
+ // to gain it (same marker-dimension pattern as persona).
264
+ if ((marker.present ?? false) !== present) return 'refresh'
232
265
  const recorded = {}
233
266
  for (const k of Object.keys(marker.files)) if (k.startsWith('skills/')) recorded[k] = marker.files[k]
234
267
  if (sourceHashes === null) return Object.keys(recorded).length === 0 ? 'idle' : 'refresh'
@@ -241,14 +274,56 @@ function syncDecision({ state, marker, version, sourceHashes, base = 'code', per
241
274
 
242
275
  // ── materialization ─────────────────────────────────────────────────────────
243
276
 
277
+ /*
278
+ * Conditional present-row injection (v0.13.0, dsh 0.1.5-alpha.2 sync). The
279
+ * official ptc/standard/cordis compositions appended `- id: present / name:
280
+ * '@deepseek-ai/dsh-tool-present'` (immutable file-delivery download cards).
281
+ * The row is NOT committed into the ptc-era twin files: a host older than
282
+ * 0.1.5-alpha.2 lacks the package, and a composition row that fails to
283
+ * import rejects the WHOLE preset mount. Instead the materializer probes the
284
+ * host (hostHasToolPresent) and splices the row in as PLAIN TEXT — anchor
285
+ * string surgery only, never a YAML round-trip, so !!js literals survive.
286
+ * Placement mirrors the official compositions: right after the
287
+ * tool-presentation block when the composition has one (all ptc-derived
288
+ * twins), otherwise appended at the tail (cordis/standard twins). Only
289
+ * `.ptc.` files are ever touched: code-era (<= 0.1.1) snapshots and the
290
+ * minimal variants are frozen history (official minimal never gained the
291
+ * row — it is the single-tool preset).
292
+ */
293
+ const PRESENT_ROW = "\n- id: present\n name: '@deepseek-ai/dsh-tool-present'\n"
294
+ const PRESENT_ANCHOR = " name: '@deepseek-ai/dsh-agent-tool-presentation'\n config:\n mode: ptc\n"
295
+
296
+ function injectPresentRow(text) {
297
+ if (text.includes("'@deepseek-ai/dsh-tool-present'")) return text
298
+ const at = text.indexOf(PRESENT_ANCHOR)
299
+ if (at !== -1) return text.slice(0, at + PRESENT_ANCHOR.length) + PRESENT_ROW + text.slice(at + PRESENT_ANCHOR.length)
300
+ return text.endsWith('\n') ? text + PRESENT_ROW : text + '\n' + PRESENT_ROW
301
+ }
302
+
303
+ let toolPresentCache
304
+ /** Probe whether THIS host can resolve the present package (cached per boot). */
305
+ async function hostHasToolPresent() {
306
+ if (toolPresentCache !== undefined) return toolPresentCache
307
+ try {
308
+ const { createRequire } = await import('node:module')
309
+ createRequire(import.meta.url).resolve('@deepseek-ai/dsh-tool-present')
310
+ toolPresentCache = true
311
+ } catch {
312
+ toolPresentCache = false
313
+ }
314
+ return toolPresentCache
315
+ }
316
+
244
317
  /** Write one preset directory from scratch. Returns 'ok' or 'no-skills-source'. */
245
- function materialize({ target, presetId, skillsSource, version, base = 'code', persona = 'text' }) {
318
+ function materialize({ target, presetId, skillsSource, version, base = 'code', persona = 'text', present = false }) {
246
319
  rmSync(target, { recursive: true, force: true })
247
320
  mkdirSync(target, { recursive: true })
248
321
 
249
322
  const available = readdirSync(join(pkgDir, 'assets', presetId)).filter((f) => f.endsWith('.yml'))
250
323
  const compositionFile = pickComposition(base, persona, available)
251
- writeFileSync(join(target, 'agent.cordis.yml'), readFileSync(join(pkgDir, 'assets', presetId, compositionFile)))
324
+ let composition = readFileSync(join(pkgDir, 'assets', presetId, compositionFile), 'utf8')
325
+ if (present && compositionFile.includes('.ptc.')) composition = injectPresentRow(composition)
326
+ writeFileSync(join(target, 'agent.cordis.yml'), composition)
252
327
  writeFileSync(join(target, 'preset.yml'), readFileSync(join(pkgDir, 'assets', presetId, 'preset.yml')))
253
328
 
254
329
  let skills = 'none'
@@ -262,7 +337,7 @@ function materialize({ target, presetId, skillsSource, version, base = 'code', p
262
337
  }
263
338
  }
264
339
 
265
- const marker = { managedBy: MANAGED_BY, version, presetId, base, persona, files: hashTree(target) }
340
+ const marker = { managedBy: MANAGED_BY, version, presetId, base, persona, present, files: hashTree(target) }
266
341
  writeFileSync(join(target, MARKER_FILE), JSON.stringify(marker, null, 2) + '\n')
267
342
  return skills
268
343
  }
@@ -871,6 +946,11 @@ export async function apply(ctx, config = {}) {
871
946
  const skillsSource = await findSkillsSource(ctx.agentPresets)
872
947
  const base = await detectBase(ctx.agentPresets)
873
948
  const persona = await detectPersonaEra(ctx.agentPresets)
949
+ // Two independent signals, either of which is sufficient: the shipped
950
+ // composition text (authoritative on every install layout) and the package
951
+ // resolving from this plugin's own tree (covers hosts where the roster
952
+ // probe is unavailable).
953
+ const present = (await detectPresentSupport(ctx.agentPresets)) || (await hostHasToolPresent())
874
954
  const userRootPath = userRoot.path
875
955
  purgeOrphans(userRootPath, presetIds)
876
956
 
@@ -900,12 +980,12 @@ export async function apply(ctx, config = {}) {
900
980
 
901
981
  const sourceHashes = presetId === 'cordis-gitbash' ? skillsHashes(skillsSource) : null
902
982
  const marker = readMarker(target)
903
- if (state === 'unmodified' && syncDecision({ state, marker, version, sourceHashes, base, persona }) === 'idle') {
983
+ if (state === 'unmodified' && syncDecision({ state, marker, version, sourceHashes, base, persona, present }) === 'idle') {
904
984
  ctx.logger?.('gitbash-shell')?.debug?.( `preset '${presetId}' up to date (v${version}, ${base}-era${persona === 'split' ? ', persona-split' : ''}) — idle`)
905
985
  continue
906
986
  }
907
987
 
908
- const skills = materialize({ target, presetId, skillsSource, version, base, persona })
988
+ const skills = materialize({ target, presetId, skillsSource, version, base, persona, present })
909
989
  const verb = state === 'absent' ? 'materialized' : 'refreshed'
910
990
  console.log(
911
991
  `${TAG} ${verb} preset '${presetId}' into ${userRootPath} (v${version}, ${base}-era composition${persona === 'split' ? ', persona-split' : ''})` +
@@ -915,4 +995,4 @@ export async function apply(ctx, config = {}) {
915
995
  }
916
996
 
917
997
  // Test surface: pure helpers, no Cordis context required.
918
- export const _internal = { PRESET_IDS, translateMsysPath, translatePathArguments, readPosixPaths, windowsToMsys, rewriteResultPaths, MARKER_FILE, classify, materialize, cleanupOnDispose, firstUserRoot, hashTree, skillsHashes, syncDecision, installRegisterShim, baseForRoster, detectBase, pickComposition, personaEraForText, detectPersonaEra }
998
+ export const _internal = { PRESET_IDS, translateMsysPath, translatePathArguments, readPosixPaths, windowsToMsys, rewriteResultPaths, MARKER_FILE, classify, materialize, cleanupOnDispose, firstUserRoot, hashTree, skillsHashes, syncDecision, installRegisterShim, baseForRoster, detectBase, pickComposition, personaEraForText, detectPersonaEra, injectPresentRow, hostHasToolPresent, detectPresentSupport }