dsh-gitbash-shell 0.22.0 → 0.24.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/cordis.patch.yml +4 -1
- package/dsh.plugin.json +1 -1
- package/icon.svg +7 -0
- package/locale/en.json +6 -0
- package/locale/zh.json +6 -0
- package/package.json +10 -3
- package/src/client.js +38 -3
- package/src/compositions.js +296 -0
- package/src/index.js +350 -76
package/cordis.patch.yml
CHANGED
|
@@ -34,7 +34,10 @@
|
|
|
34
34
|
timeoutMs: 60000
|
|
35
35
|
bashPath: "C:/Program Files/Git/bin/bash.exe"
|
|
36
36
|
|
|
37
|
-
|
|
37
|
+
# Row id 'gitbash-shell' (was 'gitbash-presets' through v0.23.0): dsh >= 0.1.7
|
|
38
|
+
# keys a plugin's live Config form and the one-shot legacy settings.yaml
|
|
39
|
+
# import by the row id, so it now matches the settings namespace string.
|
|
40
|
+
- id: gitbash-shell
|
|
38
41
|
name: dsh-gitbash-shell
|
|
39
42
|
|
|
40
43
|
# Edit the dsh-better-sidebar row when it is mounted (their own bundle
|
package/dsh.plugin.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"id": "dsh-external/dsh-gitbash-shell",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.24.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/icon.svg
ADDED
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 36 36" fill="none">
|
|
2
|
+
<rect x="4" y="6" width="28" height="24" rx="3.5" stroke="#4493f8" stroke-width="1.8"/>
|
|
3
|
+
<path d="M4 12h28" stroke="#4493f8" stroke-width="1.4"/>
|
|
4
|
+
<circle cx="7.6" cy="9" r="1" fill="#4493f8"/>
|
|
5
|
+
<circle cx="11" cy="9" r="1" fill="#4493f8"/>
|
|
6
|
+
<path d="M10 18.5l4 3.5-4 3.5M17 25.5h8" stroke="#f0883e" stroke-width="1.9" stroke-linecap="round" stroke-linejoin="round"/>
|
|
7
|
+
</svg>
|
package/locale/en.json
ADDED
|
@@ -0,0 +1,6 @@
|
|
|
1
|
+
{
|
|
2
|
+
"meta": {
|
|
3
|
+
"title": "Git Bash Shell",
|
|
4
|
+
"description": "Swaps Windows' PowerShell shell for Git for Windows bash: a bash-sandbox-compatible executor, a unified MSYS path dialect across every tool (posixPaths and friends, live-editable), and the four * Git Bash agent presets declared straight onto the roster on dsh 0.1.7+."
|
|
5
|
+
}
|
|
6
|
+
}
|
package/locale/zh.json
ADDED
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "dsh-gitbash-shell",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.24.0",
|
|
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": {
|
|
@@ -11,7 +11,8 @@
|
|
|
11
11
|
"exports": {
|
|
12
12
|
".": "./src/index.js",
|
|
13
13
|
"./shell": "./src/shell.js",
|
|
14
|
-
"./client": "./src/client.js"
|
|
14
|
+
"./client": "./src/client.js",
|
|
15
|
+
"./locale/*.json": "./locale/*.json"
|
|
15
16
|
},
|
|
16
17
|
"engines": {
|
|
17
18
|
"node": ">=18",
|
|
@@ -38,7 +39,9 @@
|
|
|
38
39
|
"dsh.plugin.json",
|
|
39
40
|
"README.md",
|
|
40
41
|
"README_EN.md",
|
|
41
|
-
"LICENSE"
|
|
42
|
+
"LICENSE",
|
|
43
|
+
"locale",
|
|
44
|
+
"icon.svg"
|
|
42
45
|
],
|
|
43
46
|
"scripts": {
|
|
44
47
|
"test": "node --test tests/smoke.mjs",
|
|
@@ -63,5 +66,9 @@
|
|
|
63
66
|
"homepage": "https://github.com/KannaKuron/dsh-gitbash-shell",
|
|
64
67
|
"bugs": {
|
|
65
68
|
"url": "https://github.com/KannaKuron/dsh-gitbash-shell/issues"
|
|
69
|
+
},
|
|
70
|
+
"icon": "./icon.svg",
|
|
71
|
+
"devDependencies": {
|
|
72
|
+
"@deepseek-ai/schemastery": "^3.18.1"
|
|
66
73
|
}
|
|
67
74
|
}
|
package/src/client.js
CHANGED
|
@@ -1058,11 +1058,46 @@ window.__ModuleLoader__.load({
|
|
|
1058
1058
|
|
|
1059
1059
|
exports.name = "dsh-gitbash-shell/client";
|
|
1060
1060
|
|
|
1061
|
-
/**
|
|
1062
|
-
|
|
1061
|
+
/**
|
|
1062
|
+
* Required client services: only era-guaranteed ones are hard-injected;
|
|
1063
|
+
* the settings face is acquired OPTIONALLY (dsh 0.1.7 removed the
|
|
1064
|
+
* settingsScope service and a hard inject would leave this fiber PENDING
|
|
1065
|
+
* forever, taking the card down with it).
|
|
1066
|
+
*/
|
|
1067
|
+
exports.inject = ["locale", "slots"];
|
|
1063
1068
|
|
|
1064
1069
|
exports.apply = function (ctx) {
|
|
1065
|
-
|
|
1070
|
+
// Era-split settings face (same contract both eras: getSnapshot/set/unset).
|
|
1071
|
+
var scope = null;
|
|
1072
|
+
|
|
1073
|
+
// OLD era (dsh <= 0.1.6): bound settings scope.
|
|
1074
|
+
try {
|
|
1075
|
+
ctx.inject(["settingsScope"], function (sctx) {
|
|
1076
|
+
try {
|
|
1077
|
+
var svc = sctx && sctx.settingsScope;
|
|
1078
|
+
if (svc && typeof svc.bind === "function") scope = svc.bind({ namespace: SETTINGS_NAMESPACE });
|
|
1079
|
+
} catch (error) {
|
|
1080
|
+
console.warn(TAG + " settingsScope acquisition failed:", error && error.message ? error.message : error);
|
|
1081
|
+
}
|
|
1082
|
+
});
|
|
1083
|
+
} catch (error) {
|
|
1084
|
+
console.warn(TAG + " settingsScope wiring failed:", error && error.message ? error.message : error);
|
|
1085
|
+
}
|
|
1086
|
+
|
|
1087
|
+
// NEW era (dsh >= 0.1.7): one ConfigForm per live profile entry; the form
|
|
1088
|
+
// key is the row id "gitbash-shell" (same string as the old namespace).
|
|
1089
|
+
try {
|
|
1090
|
+
ctx.inject(["configForms"], function (fctx) {
|
|
1091
|
+
try {
|
|
1092
|
+
var forms = fctx && fctx.configForms;
|
|
1093
|
+
if (forms && typeof forms.get === "function") scope = forms.get(SETTINGS_NAMESPACE);
|
|
1094
|
+
} catch (error) {
|
|
1095
|
+
console.warn(TAG + " configForms acquisition failed:", error && error.message ? error.message : error);
|
|
1096
|
+
}
|
|
1097
|
+
});
|
|
1098
|
+
} catch (error) {
|
|
1099
|
+
console.warn(TAG + " configForms wiring failed:", error && error.message ? error.message : error);
|
|
1100
|
+
}
|
|
1066
1101
|
/* The card's only copy entry point: a live lookup, never a captured
|
|
1067
1102
|
dictionary — the language preference switches without a reload. */
|
|
1068
1103
|
var t = translatorOf(ctx);
|
|
@@ -0,0 +1,296 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* dsh-gitbash-shell — declarative composition rows (dsh >= 0.1.7).
|
|
3
|
+
*
|
|
4
|
+
* dsh 0.1.7 removed directory presets; a preset is a definition registered
|
|
5
|
+
* through agentPresets.register(). This module is the committed, reviewable
|
|
6
|
+
* composition DATA for the four Git Bash variants — the new-era counterpart
|
|
7
|
+
* of the committed assets/<variant>/agent.cordis*.yml texts the materializer
|
|
8
|
+
* writes on older hosts.
|
|
9
|
+
*
|
|
10
|
+
* Every variant mirrors the official 0.1.7 preset it replaces (packages/
|
|
11
|
+
* bundle/web-app/presets/{standard,minimal,ptc,cordis}.patch.yml) with the
|
|
12
|
+
* one Git Bash delta: the bash rows are always on and the pwsh rows are
|
|
13
|
+
* always off (the host executor is this plugin's shell.js — on non-Windows
|
|
14
|
+
* hosts that is the native stack anyway, which is exactly what the old
|
|
15
|
+
* materialized variants encoded).
|
|
16
|
+
*/
|
|
17
|
+
|
|
18
|
+
/** Default Git Bash binary — must match src/shell.js and the patch config. */
|
|
19
|
+
const DEFAULT_GIT_BASH = 'C:/Program Files/Git/bin/bash.exe'
|
|
20
|
+
|
|
21
|
+
function off(value) {
|
|
22
|
+
return value === true ? { disabled: true } : {}
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
const PLAN_SECTION = [
|
|
26
|
+
'You are in plan mode. Stay in plan mode until exit_plan_mode succeeds or the user switches the session mode. Imperative language to implement changes means plan the implementation, not execute it. A user\'s conversational agreement — including an answer confirming you asked — approves nothing and does not end plan mode; fold the confirmed decision into the plan and submit it through exit_plan_mode.',
|
|
27
|
+
'',
|
|
28
|
+
'Explore first. Use non-mutating reads, searches, static analysis, and checks to ground the plan in the actual repository. Do not edit or write files, change configuration, run formatters or code generation that rewrites tracked files, commit, or otherwise carry out the plan. Prefer existing functions and patterns over new machinery.',
|
|
29
|
+
'',
|
|
30
|
+
'The tool catalog stays the same across modes for request-cache stability. These plan-mode rules override any later tool description or guidance that suggests using mutation tools; those tools remain listed to keep the tool catalog unchanged. Do not use todo_write to track this planning phase: it tracks implementation after an approved plan, while the plan itself belongs to exit_plan_mode.',
|
|
31
|
+
'',
|
|
32
|
+
'Resolve discoverable facts by inspection. Use ask_user_question only for user-owned choices or material ambiguity that inspection cannot answer. Do not ask the user where code lives or how current behavior works when you can find out.',
|
|
33
|
+
'',
|
|
34
|
+
'Make the plan decision-complete: state the goal and success criteria; group implementation changes by subsystem; identify public API, schema, and data-flow changes; cover edge cases, failure modes, tests, acceptance criteria, and explicit assumptions. Keep it concise enough to review but detailed enough that another engineer can implement it without making design decisions.',
|
|
35
|
+
'',
|
|
36
|
+
'When ready, call exit_plan_mode with the complete plan markdown, starting with a # title. Make exit_plan_mode the only and final tool call in that assistant response: it presents the plan for approval, and implementation begins in a later step after approval. Do not paste the final plan as a plain reply or ask "should I proceed?" through prose or ask_user_question. If review rejects it, incorporate the feedback and present again. If the review channel is unavailable or aborted, stay in plan mode and ask the user to switch the session mode manually; do not proceed with implementation.',
|
|
37
|
+
].join('\n')
|
|
38
|
+
|
|
39
|
+
/**
|
|
40
|
+
* The shared full-tool rows for standard / ptc / cordis variants (pure).
|
|
41
|
+
* @param {object} input
|
|
42
|
+
* @param {'standard'|'ptc'|'cordis'} input.kind - which official preset this
|
|
43
|
+
* variant mirrors (workflow rows follow it: enabled on standard/cordis,
|
|
44
|
+
* disabled on ptc).
|
|
45
|
+
* @param {boolean} input.gitBash - bash rows always on, pwsh rows always off.
|
|
46
|
+
* @param {string|undefined} input.skillsDir - Creation authoring skills
|
|
47
|
+
* directory (cordis variant only; resolved beside the agent-preset package).
|
|
48
|
+
* @returns {object[]} the declarative plugins list.
|
|
49
|
+
*/
|
|
50
|
+
export function pluginsFor({ kind, gitBash, skillsDir }) {
|
|
51
|
+
const win = typeof process !== 'undefined' && process.platform === 'win32'
|
|
52
|
+
const bashDisabled = gitBash ? false : win
|
|
53
|
+
const pwshDisabled = gitBash ? true : !win
|
|
54
|
+
const workflowOn = kind !== 'ptc'
|
|
55
|
+
const rows = [
|
|
56
|
+
{
|
|
57
|
+
id: 'persona',
|
|
58
|
+
name: '@deepseek-ai/dsh-persona',
|
|
59
|
+
config: {
|
|
60
|
+
prefix: 'You are a coding agent powered by the {{model}} model.',
|
|
61
|
+
suffix: 'Your working directory is {{cwd}}.',
|
|
62
|
+
},
|
|
63
|
+
},
|
|
64
|
+
{
|
|
65
|
+
id: 'agent-instructions',
|
|
66
|
+
name: '@deepseek-ai/dsh-agent-instructions',
|
|
67
|
+
config: { maxBytes: 65536 },
|
|
68
|
+
},
|
|
69
|
+
{ id: 'tool-bash', name: '@deepseek-ai/dsh-tool-bash', ...off(bashDisabled) },
|
|
70
|
+
{ id: 'tool-pwsh', name: '@deepseek-ai/dsh-tool-pwsh', ...off(pwshDisabled) },
|
|
71
|
+
{ id: 'tool-fs', name: '@deepseek-ai/dsh-tool-fs' },
|
|
72
|
+
{
|
|
73
|
+
id: 'tool-fs-search',
|
|
74
|
+
name: '@deepseek-ai/dsh-tool-fs-search',
|
|
75
|
+
config: { sampleOverCapGlobResults: false },
|
|
76
|
+
},
|
|
77
|
+
{ id: 'tool-jobs', name: '@deepseek-ai/dsh-tool-jobs' },
|
|
78
|
+
{
|
|
79
|
+
id: 'skill-filesystem',
|
|
80
|
+
name: '@deepseek-ai/dsh-skill-filesystem',
|
|
81
|
+
config: kind === 'cordis' && skillsDir ? { customSkillDirs: [skillsDir] } : {},
|
|
82
|
+
},
|
|
83
|
+
{ id: 'tool-skill', name: '@deepseek-ai/dsh-tool-skill' },
|
|
84
|
+
{ id: 'command-goal', name: '@deepseek-ai/dsh-command-goal' },
|
|
85
|
+
{ id: 'tool-goal', name: '@deepseek-ai/dsh-tool-goal' },
|
|
86
|
+
{
|
|
87
|
+
id: 'planning',
|
|
88
|
+
name: 'cordis:group',
|
|
89
|
+
group: true,
|
|
90
|
+
isolate: { planMode: true },
|
|
91
|
+
config: [
|
|
92
|
+
{
|
|
93
|
+
id: 'plan-mode',
|
|
94
|
+
name: '@deepseek-ai/dsh-plan-mode',
|
|
95
|
+
config: { section: PLAN_SECTION },
|
|
96
|
+
},
|
|
97
|
+
],
|
|
98
|
+
},
|
|
99
|
+
{
|
|
100
|
+
id: 'compaction',
|
|
101
|
+
name: 'cordis:group',
|
|
102
|
+
group: true,
|
|
103
|
+
isolate: { compaction: true, toolResultPruner: true },
|
|
104
|
+
config: [
|
|
105
|
+
{ id: 'compaction-basic', name: '@deepseek-ai/dsh-compaction-basic' },
|
|
106
|
+
{ id: 'command-compact', name: '@deepseek-ai/dsh-command-compact' },
|
|
107
|
+
{
|
|
108
|
+
id: 'tool-result-pruner',
|
|
109
|
+
name: '@deepseek-ai/dsh-compaction-tool-result-pruner',
|
|
110
|
+
config: { thresholdChars: 8192, headChars: 4096, tailChars: 1024 },
|
|
111
|
+
},
|
|
112
|
+
],
|
|
113
|
+
},
|
|
114
|
+
{
|
|
115
|
+
id: 'delegation',
|
|
116
|
+
name: 'cordis:group',
|
|
117
|
+
group: true,
|
|
118
|
+
isolate: { workflowEngine: true },
|
|
119
|
+
config: [
|
|
120
|
+
{ id: 'tool-subagent-control', name: '@deepseek-ai/dsh-tool-subagent-control' },
|
|
121
|
+
{ id: 'tool-subagent-list-agents', name: '@deepseek-ai/dsh-tool-subagent-control/list-agents' },
|
|
122
|
+
{
|
|
123
|
+
id: 'tool-subagent',
|
|
124
|
+
name: '@deepseek-ai/dsh-tool-subagent',
|
|
125
|
+
config: { provider: 'spawn', toolName: 'subagent', modelSelectionSettings: true, backgroundMode: 'continuable' },
|
|
126
|
+
},
|
|
127
|
+
{
|
|
128
|
+
id: 'tool-subagent-fork',
|
|
129
|
+
name: '@deepseek-ai/dsh-tool-subagent',
|
|
130
|
+
config: { provider: 'fork', toolName: 'subagent_fork', backgroundMode: 'continuable' },
|
|
131
|
+
},
|
|
132
|
+
{
|
|
133
|
+
id: 'tool-subagent-codex',
|
|
134
|
+
name: '@deepseek-ai/dsh-tool-subagent',
|
|
135
|
+
disabled: true,
|
|
136
|
+
config: { provider: 'codex', toolName: 'subagent_codex', backgroundMode: 'one-shot', maxDepth: 'provider-managed' },
|
|
137
|
+
},
|
|
138
|
+
{
|
|
139
|
+
id: 'tool-subagent-claude-code',
|
|
140
|
+
name: '@deepseek-ai/dsh-tool-subagent',
|
|
141
|
+
disabled: true,
|
|
142
|
+
config: { provider: 'claude-code', toolName: 'subagent_claude_code', backgroundMode: 'one-shot', maxDepth: 'provider-managed' },
|
|
143
|
+
},
|
|
144
|
+
{
|
|
145
|
+
id: 'workflow-ptc',
|
|
146
|
+
name: '@deepseek-ai/dsh-workflow-ptc',
|
|
147
|
+
...off(!workflowOn),
|
|
148
|
+
config: { provider: 'spawn' },
|
|
149
|
+
},
|
|
150
|
+
{ id: 'tool-workflow', name: '@deepseek-ai/dsh-tool-workflow', ...off(!workflowOn) },
|
|
151
|
+
{
|
|
152
|
+
id: 'tool-ralph',
|
|
153
|
+
name: '@deepseek-ai/dsh-tool-ralph',
|
|
154
|
+
disabled: true,
|
|
155
|
+
config: { subagentProvider: 'spawn', maxRounds: 64 },
|
|
156
|
+
},
|
|
157
|
+
],
|
|
158
|
+
},
|
|
159
|
+
{ id: 'tool-ask-user', name: '@deepseek-ai/dsh-tool-ask-user' },
|
|
160
|
+
{
|
|
161
|
+
id: 'tool-todo',
|
|
162
|
+
name: '@deepseek-ai/dsh-tool-todo',
|
|
163
|
+
config: { allowParallelInProgress: true },
|
|
164
|
+
},
|
|
165
|
+
{
|
|
166
|
+
id: 'tool-web',
|
|
167
|
+
name: '@deepseek-ai/dsh-tool-web',
|
|
168
|
+
config: { fetch: true, searchTimeoutMs: 60000 },
|
|
169
|
+
},
|
|
170
|
+
]
|
|
171
|
+
if (kind === 'ptc') {
|
|
172
|
+
rows.push({
|
|
173
|
+
id: 'tool-presentation',
|
|
174
|
+
name: '@deepseek-ai/dsh-agent-tool-presentation',
|
|
175
|
+
config: { mode: 'ptc' },
|
|
176
|
+
})
|
|
177
|
+
}
|
|
178
|
+
if (kind === 'cordis') {
|
|
179
|
+
rows.push({ id: 'tool-cordis', name: '@deepseek-ai/dsh-tool-cordis' })
|
|
180
|
+
}
|
|
181
|
+
rows.push({ id: 'present', name: '@deepseek-ai/dsh-tool-present' })
|
|
182
|
+
rows.push({
|
|
183
|
+
id: 'tool-plugin-manager',
|
|
184
|
+
name: '@deepseek-ai/dsh-plugin-manager/tools',
|
|
185
|
+
// The official 0.1.7 rows: ptc/standard ship it disabled, cordis keeps it
|
|
186
|
+
// on wherever a profile context exists (every web deployment does).
|
|
187
|
+
...off(kind !== 'cordis'),
|
|
188
|
+
})
|
|
189
|
+
return rows
|
|
190
|
+
}
|
|
191
|
+
|
|
192
|
+
/**
|
|
193
|
+
* The minimal variant: the official single-tool preset with the Git Bash
|
|
194
|
+
* delta — bash rows always on (the terminal row pins the Git Bash binary on
|
|
195
|
+
* Windows), pwsh rows always off (pure).
|
|
196
|
+
* @returns {object[]} the declarative plugins list.
|
|
197
|
+
*/
|
|
198
|
+
export function minimalPluginsFor() {
|
|
199
|
+
const win = typeof process !== 'undefined' && process.platform === 'win32'
|
|
200
|
+
return [
|
|
201
|
+
{
|
|
202
|
+
id: 'persona',
|
|
203
|
+
name: '@deepseek-ai/dsh-persona',
|
|
204
|
+
config: {
|
|
205
|
+
prefix: 'You are a helpful software engineer assistant.',
|
|
206
|
+
complete: true,
|
|
207
|
+
includeRuntimeContext: false,
|
|
208
|
+
},
|
|
209
|
+
},
|
|
210
|
+
{
|
|
211
|
+
id: 'persistent-shell',
|
|
212
|
+
name: 'cordis:group',
|
|
213
|
+
group: true,
|
|
214
|
+
isolate: { terminals: true },
|
|
215
|
+
config: [
|
|
216
|
+
{ id: 'pty', name: '@deepseek-ai/dsh-terminal' },
|
|
217
|
+
{
|
|
218
|
+
id: 'terminal-bash',
|
|
219
|
+
name: '@deepseek-ai/dsh-terminal-bash',
|
|
220
|
+
disabled: false,
|
|
221
|
+
config: {
|
|
222
|
+
timeoutMs: 300000,
|
|
223
|
+
shellPath: win ? DEFAULT_GIT_BASH : '/bin/bash',
|
|
224
|
+
},
|
|
225
|
+
},
|
|
226
|
+
{
|
|
227
|
+
id: 'persistent-bash',
|
|
228
|
+
name: '@deepseek-ai/dsh-tool-bash-persistent',
|
|
229
|
+
disabled: false,
|
|
230
|
+
config: {
|
|
231
|
+
timeoutMs: 300000,
|
|
232
|
+
description: [
|
|
233
|
+
'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)',
|
|
234
|
+
'* When invoking this tool, the contents of the "command" parameter does NOT need to be XML-escaped.',
|
|
235
|
+
'* Network access depends on the task environment. Prefer configured mirrors/proxies when they are available.',
|
|
236
|
+
'* State is persistent across command calls and discussions with the user.',
|
|
237
|
+
'* To inspect a particular line range of a file, e.g. lines 10-25, try \'sed -n 10,25p /path/to/the/file\'.',
|
|
238
|
+
'* Please avoid commands that may produce a very large amount of output.',
|
|
239
|
+
'* Please run long lived commands in the background, e.g. \'sleep 10 &\' or start a server in the background.',
|
|
240
|
+
].join('\n'),
|
|
241
|
+
},
|
|
242
|
+
},
|
|
243
|
+
{
|
|
244
|
+
id: 'terminal-pwsh',
|
|
245
|
+
name: '@deepseek-ai/dsh-terminal-bash',
|
|
246
|
+
disabled: true,
|
|
247
|
+
config: { shellDialect: 'pwsh', timeoutMs: 300000 },
|
|
248
|
+
},
|
|
249
|
+
{
|
|
250
|
+
id: 'persistent-pwsh',
|
|
251
|
+
name: '@deepseek-ai/dsh-tool-pwsh-persistent',
|
|
252
|
+
disabled: true,
|
|
253
|
+
config: {
|
|
254
|
+
timeoutMs: 300000,
|
|
255
|
+
description: [
|
|
256
|
+
'Run commands in a PowerShell shell',
|
|
257
|
+
'* When invoking this tool, the contents of the "command" parameter does NOT need to be XML-escaped.',
|
|
258
|
+
"* You don't have access to the internet via this tool.",
|
|
259
|
+
'* Use native Windows paths (C:/...) and $env:NAME variables; this is PowerShell, not bash.',
|
|
260
|
+
'* Please avoid commands that may produce a very large amount of output.',
|
|
261
|
+
'* Please run long lived commands in the background, e.g. \'Start-Job\' or start a server with Start-Process.',
|
|
262
|
+
].join('\n'),
|
|
263
|
+
},
|
|
264
|
+
},
|
|
265
|
+
],
|
|
266
|
+
},
|
|
267
|
+
]
|
|
268
|
+
}
|
|
269
|
+
|
|
270
|
+
/** Display metadata for the four registrations (mirrors the committed preset.yml files). */
|
|
271
|
+
export const PRESET_META = {
|
|
272
|
+
'standard-gitbash': {
|
|
273
|
+
kind: 'standard',
|
|
274
|
+
name: '标准模式 · Git Bash',
|
|
275
|
+
description: '功能完整的编码 Agent,支持文件编辑、Shell、文件与网页检索、Skills、计划、目标、子代理和工作流。(Shell 使用 Git Bash)',
|
|
276
|
+
order: 1,
|
|
277
|
+
},
|
|
278
|
+
'code-gitbash': {
|
|
279
|
+
kind: 'ptc',
|
|
280
|
+
name: 'PTC 模式 · Git Bash',
|
|
281
|
+
description: '功能完整的编码 Agent,但默认不提供 workflow 工具;其他工具通过 PTC 模式 SDK 呈现,让模型用一个 TypeScript 程序组合多步操作。(Shell 使用 Git Bash)',
|
|
282
|
+
order: 2,
|
|
283
|
+
},
|
|
284
|
+
'minimal-gitbash': {
|
|
285
|
+
kind: 'minimal',
|
|
286
|
+
name: '极简模式 · Git Bash',
|
|
287
|
+
description: '仅提供持久 shell 的单工具编码 Agent。(Shell 使用 Git Bash)',
|
|
288
|
+
order: 3,
|
|
289
|
+
},
|
|
290
|
+
'cordis-gitbash': {
|
|
291
|
+
kind: 'cordis',
|
|
292
|
+
name: '创造模式 · Git Bash',
|
|
293
|
+
description: '用于创建自定义 Agent preset:具备标准模式的全部能力,并提供运行时检查、插件实验和 preset 创作指导。(Shell 使用 Git Bash)',
|
|
294
|
+
order: 4,
|
|
295
|
+
},
|
|
296
|
+
}
|
package/src/index.js
CHANGED
|
@@ -52,8 +52,11 @@ import {
|
|
|
52
52
|
} from 'node:fs'
|
|
53
53
|
import { homedir, tmpdir } from 'node:os'
|
|
54
54
|
import { dirname, join } from 'node:path'
|
|
55
|
+
import Schema from '@deepseek-ai/schemastery'
|
|
55
56
|
import { fileURLToPath } from 'node:url'
|
|
56
57
|
|
|
58
|
+
import { PRESET_META, minimalPluginsFor, pluginsFor } from './compositions.js'
|
|
59
|
+
|
|
57
60
|
/** Plugin identity for cordis.yml rows. */
|
|
58
61
|
export const name = 'dsh-gitbash-shell'
|
|
59
62
|
|
|
@@ -662,6 +665,79 @@ export function installRegisterShim(reg) {
|
|
|
662
665
|
|
|
663
666
|
const SETTINGS_NAMESPACE = 'gitbash-shell'
|
|
664
667
|
|
|
668
|
+
/**
|
|
669
|
+
* dsh >= 0.1.7 marks a Config field live-editable without remount; a
|
|
670
|
+
* 0.1.6-era schemastery predates the method and the guard keeps this module
|
|
671
|
+
* loadable there (values then behave as ordinary config, read through the
|
|
672
|
+
* registered settings namespace instead).
|
|
673
|
+
*/
|
|
674
|
+
function live(schema) {
|
|
675
|
+
return typeof schema.volatile === 'function' ? schema.volatile() : schema
|
|
676
|
+
}
|
|
677
|
+
|
|
678
|
+
/**
|
|
679
|
+
* Row Config = the settings surface on dsh >= 0.1.7 (values persist under the
|
|
680
|
+
* row id; the patch row id is 'gitbash-shell', the same string as the old
|
|
681
|
+
* settings namespace, so the one-shot legacy settings.yaml import maps old
|
|
682
|
+
* user values onto the new home). Inert metadata on older hosts.
|
|
683
|
+
*/
|
|
684
|
+
export const Config = Schema.object({
|
|
685
|
+
// Row-level knob predating the Config surface (invariant 6): which preset
|
|
686
|
+
// variants to serve. Ordinary config — an edit remounts the plugin, which
|
|
687
|
+
// is the right weight for a list that changes the roster.
|
|
688
|
+
presets: Schema.array(Schema.string()).default(PRESET_IDS),
|
|
689
|
+
posixPaths: live(Schema.boolean().default(true)),
|
|
690
|
+
virtualMounts: live(Schema.boolean().default(true)),
|
|
691
|
+
globSplit: live(Schema.boolean().default(true)),
|
|
692
|
+
errorDialect: live(Schema.boolean().default(true)),
|
|
693
|
+
codePaths: live(Schema.boolean().default(true)),
|
|
694
|
+
gitAutocrlf: live(Schema.boolean().default(true)),
|
|
695
|
+
bashPath: live(Schema.string().default('')),
|
|
696
|
+
adoptSidebar: live(Schema.boolean().default(true)),
|
|
697
|
+
})
|
|
698
|
+
|
|
699
|
+
/** Read one Config value across eras: Volatile ref (>= 0.1.7) or plain value. */
|
|
700
|
+
export function valueOf(value) {
|
|
701
|
+
return value && typeof value.get === 'function' ? value.get() : value
|
|
702
|
+
}
|
|
703
|
+
|
|
704
|
+
/**
|
|
705
|
+
* Era-aware live settings reader for everything wired inside apply(): the
|
|
706
|
+
* NEW era reads this plugin's own Config refs (no listener needed — every
|
|
707
|
+
* consumer here already re-reads per dispatch/assembly), the OLD era keeps
|
|
708
|
+
* reading the registered namespace through the settings service.
|
|
709
|
+
* @param {object} ctx - the plugin's mounting context.
|
|
710
|
+
* @param {object|undefined} config - apply()'s resolved Config.
|
|
711
|
+
* @returns {{ dialect: () => object, posix: () => boolean, adoptSidebar: () => boolean }}
|
|
712
|
+
*/
|
|
713
|
+
function makeLiveReader(ctx, config) {
|
|
714
|
+
let legacy = false
|
|
715
|
+
try {
|
|
716
|
+
const settings = ctx.get('settings')
|
|
717
|
+
legacy = !!(settings && typeof settings.register === 'function')
|
|
718
|
+
} catch { legacy = false }
|
|
719
|
+
const hasConfig = !!(config && typeof config === 'object')
|
|
720
|
+
const dialect = () => {
|
|
721
|
+
if (legacy || !hasConfig) return readDialectSettings(ctx)
|
|
722
|
+
const bash = valueOf(config.bashPath)
|
|
723
|
+
return {
|
|
724
|
+
posixPaths: valueOf(config.posixPaths) === true,
|
|
725
|
+
virtualMounts: valueOf(config.virtualMounts) === true,
|
|
726
|
+
globSplit: valueOf(config.globSplit) === true,
|
|
727
|
+
errorDialect: valueOf(config.errorDialect) === true,
|
|
728
|
+
codePaths: valueOf(config.codePaths) === true,
|
|
729
|
+
gitAutocrlf: valueOf(config.gitAutocrlf) === true,
|
|
730
|
+
bashPath: typeof bash === 'string' ? bash : '',
|
|
731
|
+
adoptSidebar: valueOf(config.adoptSidebar) !== false,
|
|
732
|
+
}
|
|
733
|
+
}
|
|
734
|
+
return {
|
|
735
|
+
dialect,
|
|
736
|
+
posix: () => dialect().posixPaths === true,
|
|
737
|
+
adoptSidebar: () => dialect().adoptSidebar !== false,
|
|
738
|
+
}
|
|
739
|
+
}
|
|
740
|
+
|
|
665
741
|
/** The full directive text, injected only while posixPaths is on. */
|
|
666
742
|
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 — including the bash-native habits (~ home shorthand, /tmp, /dev/null, /usr/bin/...), which resolve in every tool exactly as bash itself resolves them.'
|
|
667
743
|
|
|
@@ -756,13 +832,17 @@ export function readAdoptSidebar(ctxLike) {
|
|
|
756
832
|
}
|
|
757
833
|
}
|
|
758
834
|
|
|
759
|
-
// Windows absolute path -> MSYS drive-root form, for rewriting
|
|
760
|
-
//
|
|
761
|
-
//
|
|
762
|
-
//
|
|
763
|
-
//
|
|
764
|
-
//
|
|
765
|
-
|
|
835
|
+
// Windows absolute path -> MSYS drive-root form, for rewriting PROSE in place
|
|
836
|
+
// (v0.10.0). Quoted spaced paths ("C:\Program Files\Git") are matched whole; a bare
|
|
837
|
+
// path stops at a closing punctuation and crosses a space ONLY into a token that
|
|
838
|
+
// itself carries a separator — so "C:\Program Files\Git\bin" comes back whole (v0.23.0)
|
|
839
|
+
// while "see C:\Users\kanna for details" keeps its prose. An unquoted path whose LAST
|
|
840
|
+
// segment holds the space (…\my dir\f.txt") stays ambiguous in prose; the model meets such
|
|
841
|
+
// a path as a whole-value FIELD, which never goes through here (driveToMsys).
|
|
842
|
+
// The lookbehind set rejects URL schemes (https:), file:// forms, and anything
|
|
843
|
+
// already mid-word, so only real drive-letter paths are translated. Separators
|
|
844
|
+
// normalize to single slashes.
|
|
845
|
+
const BARE_WIN_PATH = /(?<![A-Za-z0-9:\\/"'`])([A-Za-z]):(?:\\|\/)([^\s"'`<>|),;:!?]+(?: [^\s"'`<>|),;:!?]*[\\/][^\s"'`<>|),;:!?]*)*)/g
|
|
766
846
|
const QUOTED_WIN_PATH = /(["'`])([A-Za-z]):(?:\\|\/)([^`]*?)\1/g
|
|
767
847
|
|
|
768
848
|
/** Rewrite every Windows absolute path in a text to the MSYS form; pure. */
|
|
@@ -785,7 +865,7 @@ export function windowsToMsys(text) {
|
|
|
785
865
|
function msysEcho(value, env) {
|
|
786
866
|
const out = windowsToMsys(value)
|
|
787
867
|
if (typeof out !== 'string' || out.length === 0) return out
|
|
788
|
-
const tempRoot = env && env.tmpDir ?
|
|
868
|
+
const tempRoot = env && env.tmpDir ? driveToMsys(env.tmpDir) : ''
|
|
789
869
|
if (!tempRoot) return out
|
|
790
870
|
return mountTempRoot(out, tempRoot)
|
|
791
871
|
}
|
|
@@ -829,6 +909,38 @@ function posixSeparators(value) {
|
|
|
829
909
|
: value
|
|
830
910
|
}
|
|
831
911
|
|
|
912
|
+
/**
|
|
913
|
+
* One WHOLE path value -> the MSYS dialect (v0.23.0). windowsToMsys is a PROSE
|
|
914
|
+
* rewriter: its bare pattern stops at whitespace, so a path whose DIRECTORY
|
|
915
|
+
* contains a space came back half-translated — "C:\my dir\f.txt" turned into
|
|
916
|
+
* "/c/my dir\f.txt", and the model met that mixed form in a SUCCESSFUL
|
|
917
|
+
* read/write/edit result (issue #3). A whole-value field IS exactly one path, so
|
|
918
|
+
* the drive prefix is translated directly and every remaining separator
|
|
919
|
+
* normalizes; no prose pattern is involved. Relative values (grep match paths)
|
|
920
|
+
* only normalize. A UNC root becomes the MSYS spelling "//server/share";
|
|
921
|
+
* anything without a drive prefix passes through unchanged. Pure.
|
|
922
|
+
*/
|
|
923
|
+
function driveToMsys(value) {
|
|
924
|
+
if (typeof value !== 'string' || value.length === 0) return value
|
|
925
|
+
const drive = /^([A-Za-z]):(?=[\\/])/.exec(value)
|
|
926
|
+
if (drive) return '/' + drive[1].toLowerCase() + '/' + posixSeparators(value.slice(2)).replace(/^\/+/, '')
|
|
927
|
+
return posixSeparators(value)
|
|
928
|
+
}
|
|
929
|
+
|
|
930
|
+
/**
|
|
931
|
+
* The MSYS echo of one whole path VALUE — never prose (v0.23.0): the drive root
|
|
932
|
+
* and separators through driveToMsys, then the /tmp mount. Every whole-value
|
|
933
|
+
* face (read/write/edit path, glob paths[], grep matches[].path, present
|
|
934
|
+
* files[].path) goes through here, so none of them can disagree about the
|
|
935
|
+
* dialect, and a spaced directory no longer leaks a backslash.
|
|
936
|
+
*/
|
|
937
|
+
function pathEcho(value, env) {
|
|
938
|
+
const out = driveToMsys(value)
|
|
939
|
+
if (typeof out !== 'string' || out.length === 0) return out
|
|
940
|
+
const tempRoot = env && env.tmpDir ? driveToMsys(env.tmpDir) : ''
|
|
941
|
+
return tempRoot ? mountTempRoot(out, tempRoot) : out
|
|
942
|
+
}
|
|
943
|
+
|
|
832
944
|
/**
|
|
833
945
|
* Rewrite Windows absolute paths inside an ERROR result's text blocks to
|
|
834
946
|
* the MSYS dialect (pure, v0.17.1). Error results cannot carry a
|
|
@@ -918,7 +1030,11 @@ export function rewriteResultPaths(name, value, env) {
|
|
|
918
1030
|
// v0.17.0: paths under the user TEMP directory echo back in the bash
|
|
919
1031
|
// /tmp dialect (that IS what /tmp means in Git Bash), so the model sees
|
|
920
1032
|
// the same short root bash itself prints for $TMP.
|
|
921
|
-
|
|
1033
|
+
// v0.23.0: every value around here is ONE path, so it goes through
|
|
1034
|
+
// pathEcho — the prose rewriter stops at whitespace and left a spaced
|
|
1035
|
+
// directory half-translated (issue #3). posixSeparators now lives inside
|
|
1036
|
+
// pathEcho, so the three nested branches below need no second pass.
|
|
1037
|
+
const toMsys = (p) => pathEcho(p, env)
|
|
922
1038
|
if (name === 'read' || name === 'read_image' || name === 'write' || name === 'edit') {
|
|
923
1039
|
if (typeof value.path === 'string' && value.path !== '') {
|
|
924
1040
|
const out = toMsys(value.path)
|
|
@@ -930,7 +1046,7 @@ export function rewriteResultPaths(name, value, env) {
|
|
|
930
1046
|
let changed = false
|
|
931
1047
|
const files = value.files.map((file) => {
|
|
932
1048
|
if (!file || typeof file !== 'object' || typeof file.path !== 'string' || file.path === '') return file
|
|
933
|
-
const out =
|
|
1049
|
+
const out = toMsys(file.path)
|
|
934
1050
|
if (out === file.path) return file
|
|
935
1051
|
changed = true
|
|
936
1052
|
return { ...file, path: out }
|
|
@@ -941,7 +1057,7 @@ export function rewriteResultPaths(name, value, env) {
|
|
|
941
1057
|
let changed = false
|
|
942
1058
|
const paths = value.paths.map((p) => {
|
|
943
1059
|
if (typeof p !== 'string') return p
|
|
944
|
-
const out =
|
|
1060
|
+
const out = toMsys(p)
|
|
945
1061
|
if (out === p) return p
|
|
946
1062
|
changed = true
|
|
947
1063
|
return out
|
|
@@ -952,7 +1068,7 @@ export function rewriteResultPaths(name, value, env) {
|
|
|
952
1068
|
let changed = false
|
|
953
1069
|
const matches = value.matches.map((m) => {
|
|
954
1070
|
if (!m || typeof m !== 'object' || typeof m.path !== 'string') return m
|
|
955
|
-
const out =
|
|
1071
|
+
const out = toMsys(m.path)
|
|
956
1072
|
if (out === m.path) return m
|
|
957
1073
|
changed = true
|
|
958
1074
|
return { ...m, path: out }
|
|
@@ -1590,7 +1706,7 @@ function adoptSidebarShell(ctx, bashPath) {
|
|
|
1590
1706
|
|
|
1591
1707
|
const run = () => {
|
|
1592
1708
|
tried += 1
|
|
1593
|
-
const ok = reconcileSidebar(
|
|
1709
|
+
const ok = reconcileSidebar(liveSettings.adoptSidebar())
|
|
1594
1710
|
if (ok) {
|
|
1595
1711
|
// The namespace scope may register after the settings service; hook
|
|
1596
1712
|
// the live watch once it exists (idempotent via the effect disposer).
|
|
@@ -1682,6 +1798,105 @@ function purgeOrphans(userRoot, keep) {
|
|
|
1682
1798
|
}
|
|
1683
1799
|
}
|
|
1684
1800
|
|
|
1801
|
+
// ── declarative registration (dsh >= 0.1.7) ──────────────────────────────────
|
|
1802
|
+
//
|
|
1803
|
+
// dsh 0.1.7 removed directory presets: nothing reads ~/.dsh/.agent-presets
|
|
1804
|
+
// any more, and a preset is a definition registered through
|
|
1805
|
+
// agentPresets.register(). The era probe is the register method itself; the
|
|
1806
|
+
// composition data lives in src/compositions.js (the new-era counterpart of
|
|
1807
|
+
// the committed assets/<variant>/ texts).
|
|
1808
|
+
|
|
1809
|
+
/** Resolve the Creation-skills dir beside @deepseek-ai/dsh-agent-preset. */
|
|
1810
|
+
async function resolveSkillsDir() {
|
|
1811
|
+
try {
|
|
1812
|
+
const { createRequire } = await import('node:module')
|
|
1813
|
+
const here = createRequire(import.meta.url)
|
|
1814
|
+
const pkg = here.resolve('@deepseek-ai/dsh-agent-preset/package.json')
|
|
1815
|
+
return join(dirname(pkg), 'skills')
|
|
1816
|
+
} catch {
|
|
1817
|
+
return undefined
|
|
1818
|
+
}
|
|
1819
|
+
}
|
|
1820
|
+
|
|
1821
|
+
/** The legacy preset root, best effort (roster roots are gone on 0.1.7). */
|
|
1822
|
+
function legacyPresetRoot() {
|
|
1823
|
+
try {
|
|
1824
|
+
const env = process.env.DSH_HOME
|
|
1825
|
+
if (env && env.trim() !== '') return join(env, '.agent-presets')
|
|
1826
|
+
return join(homedir(), '.dsh', '.agent-presets')
|
|
1827
|
+
} catch {
|
|
1828
|
+
return undefined
|
|
1829
|
+
}
|
|
1830
|
+
}
|
|
1831
|
+
|
|
1832
|
+
/**
|
|
1833
|
+
* Remove the stale materialized trees a previous (pre-0.1.7-host) version
|
|
1834
|
+
* left behind, when they are still byte-identical to what we wrote (the
|
|
1835
|
+
* marker judges). User-modified and foreign trees stay untouched — the same
|
|
1836
|
+
* ownership rules the materializer itself follows.
|
|
1837
|
+
*/
|
|
1838
|
+
function cleanupLegacyTrees(presetIds) {
|
|
1839
|
+
const root = legacyPresetRoot()
|
|
1840
|
+
if (!root) return
|
|
1841
|
+
for (const presetId of presetIds) {
|
|
1842
|
+
const target = join(root, presetId)
|
|
1843
|
+
const state = classify(target)
|
|
1844
|
+
if (state === 'unmodified') {
|
|
1845
|
+
try {
|
|
1846
|
+
rmSync(target, { recursive: true, force: true })
|
|
1847
|
+
console.log(TAG + ' removed the stale materialized preset at ' + target + ' (directory presets are no longer read on this host; the declarative registration replaces it)')
|
|
1848
|
+
} catch (error) {
|
|
1849
|
+
console.log(TAG + ' stale preset cleanup failed: ' + (error && error.message ? error.message : error))
|
|
1850
|
+
}
|
|
1851
|
+
} else if (state === 'user-modified') {
|
|
1852
|
+
console.log(TAG + ' a user-modified preset tree remains at ' + target + ' — left untouched (this host ignores it; delete it manually if unwanted)')
|
|
1853
|
+
}
|
|
1854
|
+
}
|
|
1855
|
+
}
|
|
1856
|
+
|
|
1857
|
+
/**
|
|
1858
|
+
* Register one variant definition; returns the unregister function. Mount
|
|
1859
|
+
* failures stay visible through the roster's broken diagnostic instead of
|
|
1860
|
+
* failing the boot.
|
|
1861
|
+
*/
|
|
1862
|
+
async function registerVariant(ctx, presetId, { gitBashActive, skillsDir }) {
|
|
1863
|
+
const meta = PRESET_META[presetId]
|
|
1864
|
+
if (!meta) return undefined
|
|
1865
|
+
const plugins = meta.kind === 'minimal'
|
|
1866
|
+
? minimalPluginsFor()
|
|
1867
|
+
: pluginsFor({ kind: meta.kind, gitBash: gitBashActive, skillsDir })
|
|
1868
|
+
return ctx.agentPresets.register({
|
|
1869
|
+
id: presetId,
|
|
1870
|
+
name: meta.name,
|
|
1871
|
+
description: meta.description,
|
|
1872
|
+
order: meta.order,
|
|
1873
|
+
plugins,
|
|
1874
|
+
})
|
|
1875
|
+
}
|
|
1876
|
+
|
|
1877
|
+
/**
|
|
1878
|
+
* Declarative-era wiring: register every configured variant once at startup
|
|
1879
|
+
* and keep exactly one registration per variant for the plugin's lifetime.
|
|
1880
|
+
* The roster picks the entries up immediately; sessions already pinned to a
|
|
1881
|
+
* variant keep their revision until recomposed.
|
|
1882
|
+
*/
|
|
1883
|
+
async function runDeclarativeEra(ctx, presetIds) {
|
|
1884
|
+
cleanupLegacyTrees(presetIds)
|
|
1885
|
+
const gitBashActive = process.platform === 'win32'
|
|
1886
|
+
const skillsDir = await resolveSkillsDir()
|
|
1887
|
+
const unregisters = []
|
|
1888
|
+
for (const presetId of presetIds) {
|
|
1889
|
+
try {
|
|
1890
|
+
const unregister = await registerVariant(ctx, presetId, { gitBashActive, skillsDir })
|
|
1891
|
+
if (unregister) unregisters.push(unregister)
|
|
1892
|
+
} catch (error) {
|
|
1893
|
+
console.log(TAG + " preset '" + presetId + "' registration failed: " + (error && error.message ? error.message : error))
|
|
1894
|
+
}
|
|
1895
|
+
}
|
|
1896
|
+
console.log(TAG + ' registered ' + unregisters.length + ' preset(s) declaratively (' + presetIds.join(', ') + ')')
|
|
1897
|
+
ctx.effect(() => () => { for (const off of unregisters) void off() }, 'dsh-gitbash-shell: declarative preset registrations')
|
|
1898
|
+
}
|
|
1899
|
+
|
|
1685
1900
|
export async function apply(ctx, config = {}) {
|
|
1686
1901
|
const presetIds = Array.isArray(config.presets) && config.presets.length > 0 ? config.presets : PRESET_IDS
|
|
1687
1902
|
|
|
@@ -1717,12 +1932,28 @@ export async function apply(ctx, config = {}) {
|
|
|
1717
1932
|
const disposeGitBash = ctx.provide('gitBash', gitBashCapability)
|
|
1718
1933
|
ctx.effect(() => disposeGitBash, 'dsh-gitbash-shell: gitBash capability')
|
|
1719
1934
|
|
|
1720
|
-
//
|
|
1935
|
+
// Era-aware live settings: NEW hosts read this plugin's own Config refs
|
|
1936
|
+
// (volatile, re-read per dispatch/assembly), OLD hosts keep the registered
|
|
1937
|
+
// namespace. Every consumer below already re-reads on every use, so a
|
|
1938
|
+
// flipped knob lands on the next dispatch with zero wiring.
|
|
1939
|
+
const liveSettings = makeLiveReader(ctx, config)
|
|
1940
|
+
|
|
1941
|
+
// ── settings namespace: the posixPaths switch (default ON, v0.1.0) — OLD
|
|
1942
|
+
// hosts only; on dsh >= 0.1.7 the row Config above IS the surface.
|
|
1943
|
+
|
|
1721
1944
|
// Served on the host so the Plugins tab pairs it with the browser card
|
|
1722
1945
|
// (the tab dispatches Host-served namespaces ∩ registered cards). Dynamic
|
|
1723
1946
|
// imports keep the zero-dependency smoke path importable; the schema MUST
|
|
1724
1947
|
// be a callable schemastery object (dsh-settings calls schema(merged)).
|
|
1725
|
-
|
|
1948
|
+
const legacySettings = (() => {
|
|
1949
|
+
try {
|
|
1950
|
+
const settings = ctx.get('settings')
|
|
1951
|
+
return !!(settings && typeof settings.register === 'function')
|
|
1952
|
+
} catch {
|
|
1953
|
+
return false
|
|
1954
|
+
}
|
|
1955
|
+
})()
|
|
1956
|
+
if (legacySettings) try {
|
|
1726
1957
|
ctx.inject(['settings'], (sctx) => {
|
|
1727
1958
|
Promise.all([import('@deepseek-ai/dsh-settings'), import('@deepseek-ai/schemastery')])
|
|
1728
1959
|
.then(([ds, sm]) => {
|
|
@@ -1792,7 +2023,7 @@ export async function apply(ctx, config = {}) {
|
|
|
1792
2023
|
// own env layer (src/shell.js), not this registry.
|
|
1793
2024
|
variables: { [PATH_DIALECT_KEY]: { description: PATH_DIALECT_DESCRIPTION } },
|
|
1794
2025
|
resolve() {
|
|
1795
|
-
return
|
|
2026
|
+
return liveSettings.posix() ? { [PATH_DIALECT_KEY]: PATH_DIALECT_VALUE } : {}
|
|
1796
2027
|
},
|
|
1797
2028
|
})
|
|
1798
2029
|
envCtx.effect(() => unregister, 'dsh-gitbash-shell: shellEnv path-dialect fact')
|
|
@@ -1820,7 +2051,7 @@ export async function apply(ctx, config = {}) {
|
|
|
1820
2051
|
try {
|
|
1821
2052
|
ctx.on('system-prompt/assemble', (assembly, _assembleContext, next) => {
|
|
1822
2053
|
try {
|
|
1823
|
-
if (
|
|
2054
|
+
if (liveSettings.posix() && assembly && typeof assembly === 'object') {
|
|
1824
2055
|
for (const section of Array.isArray(assembly.sections) ? assembly.sections : []) {
|
|
1825
2056
|
if (section && typeof section.text === 'string') section.text = windowsToMsys(section.text)
|
|
1826
2057
|
}
|
|
@@ -1858,90 +2089,121 @@ export async function apply(ctx, config = {}) {
|
|
|
1858
2089
|
// ── MSYS path translation on every tool dispatch (Windows only) ─────────
|
|
1859
2090
|
// Covers every preset and mode: the wrapper sits on the global
|
|
1860
2091
|
// tools/execute waterfall, through which model-direct calls, PTC run_code
|
|
1861
|
-
// sub-dispatches, and dynamic-tool calls all pass.
|
|
2092
|
+
// sub-dispatches, and dynamic-tool calls all pass. It carries BOTH dialect
|
|
2093
|
+
// faces of one call — the path-bearing ARGUMENTS on the way in, and the
|
|
2094
|
+
// path-bearing RESULT metadata on the way out.
|
|
2095
|
+
//
|
|
2096
|
+
// The result face rides HERE, not tools/post-execute (v0.23.0, issue #2).
|
|
2097
|
+
// The registry rejects a post-execute decision that replaces `value` while
|
|
2098
|
+
// any listener on that waterfall replaced `content` —
|
|
2099
|
+
// "tools/post-execute accept decision cannot replace both value and content",
|
|
2100
|
+
// raised after the waterfall settles and outside every listener try/catch,
|
|
2101
|
+
// so the call ended as an isError and the model lost the result. An
|
|
2102
|
+
// around-dispatch wrapper may author the result instead: the registry re-runs
|
|
2103
|
+
// the owning output contract on what we return (normalizeDispatchResult ->
|
|
2104
|
+
// createSuccessResult -> render), so the content the model reads is rendered
|
|
2105
|
+
// FROM the dialect value handed back, and this plugin never projects a
|
|
2106
|
+
// `value` on a decision at all — the collision cannot happen. An unchanged
|
|
2107
|
+
// value returns the ORIGINAL result object, so the registry re-renders
|
|
2108
|
+
// nothing. Only a SUCCESSFUL result is touched; failures keep their value
|
|
2109
|
+
// (and their dialect rides the post-execute listener below).
|
|
1862
2110
|
if (process.platform === 'win32') {
|
|
1863
2111
|
try {
|
|
1864
2112
|
ctx.on('tools/execute', (exec, next) => {
|
|
2113
|
+
let echo = false
|
|
2114
|
+
let echoEnv = null
|
|
1865
2115
|
try {
|
|
1866
2116
|
const dialect = readDialectSettings(ctx)
|
|
1867
|
-
if (
|
|
2117
|
+
if (dialect.posixPaths) {
|
|
2118
|
+
echo = true
|
|
1868
2119
|
const env = dialect.virtualMounts ? buildTranslateEnv(dialect.bashPath) : null
|
|
1869
|
-
|
|
1870
|
-
if (exec.
|
|
1871
|
-
|
|
1872
|
-
if (
|
|
1873
|
-
|
|
1874
|
-
|
|
1875
|
-
|
|
1876
|
-
|
|
1877
|
-
|
|
1878
|
-
|
|
1879
|
-
|
|
1880
|
-
|
|
1881
|
-
|
|
1882
|
-
|
|
2120
|
+
echoEnv = env
|
|
2121
|
+
if (exec && exec.arguments && typeof exec.arguments === 'object') {
|
|
2122
|
+
let translated = translatePathArguments(exec.arguments, env)
|
|
2123
|
+
if (exec.name === 'glob' && dialect.globSplit) {
|
|
2124
|
+
const globTranslated = translateGlobArguments(translated, env)
|
|
2125
|
+
if (globTranslated !== translated) translated = globTranslated
|
|
2126
|
+
}
|
|
2127
|
+
// v0.20.0: a run_code program is DATA for a native Node process, so
|
|
2128
|
+
// a '/c/...' literal inside it never reached this layer and landed
|
|
2129
|
+
// on the current drive as '<drive>:\\c\\...'. The same mount table
|
|
2130
|
+
// now covers the program path literals (see rewriteCodePaths;
|
|
2131
|
+
// anything unclear leaves the code untouched).
|
|
2132
|
+
if (exec.name === 'run_code' && dialect.codePaths && typeof translated.code === 'string') {
|
|
2133
|
+
const rewritten = rewriteCodePaths(translated.code, env)
|
|
2134
|
+
const nextCode = programPrelude(env) + rewritten
|
|
2135
|
+
if (nextCode !== translated.code) translated = { ...translated, code: nextCode }
|
|
2136
|
+
}
|
|
2137
|
+
if (translated !== exec.arguments) exec.arguments = translated
|
|
1883
2138
|
}
|
|
1884
|
-
if (translated !== exec.arguments) exec.arguments = translated
|
|
1885
2139
|
}
|
|
1886
2140
|
} catch { /* never block a call on translation */ }
|
|
1887
|
-
return next()
|
|
2141
|
+
if (!echo) return next()
|
|
2142
|
+
return next().then((result) => {
|
|
2143
|
+
try {
|
|
2144
|
+
if (!result || typeof result !== 'object' || result.isError !== false) return result
|
|
2145
|
+
if (!result.value || typeof result.value !== 'object') return result
|
|
2146
|
+
const value = rewriteResultPaths(exec && exec.name, result.value, echoEnv)
|
|
2147
|
+
return value === result.value ? result : { ...result, value }
|
|
2148
|
+
} catch { return result }
|
|
2149
|
+
})
|
|
1888
2150
|
})
|
|
1889
|
-
console.log(TAG + ' MSYS
|
|
2151
|
+
console.log(TAG + ' MSYS path translation active on tool dispatch (arguments and result metadata)')
|
|
1890
2152
|
} catch (error) {
|
|
1891
2153
|
console.log(TAG + ' tools/execute wiring failed: ' + (error?.message ?? error))
|
|
1892
2154
|
}
|
|
1893
2155
|
}
|
|
1894
2156
|
|
|
1895
|
-
// ──
|
|
1896
|
-
//
|
|
1897
|
-
//
|
|
1898
|
-
//
|
|
1899
|
-
//
|
|
1900
|
-
//
|
|
1901
|
-
//
|
|
2157
|
+
// ── failure dialect on tools/post-execute (Windows only, gated; v0.17.1) ─
|
|
2158
|
+
// A FAILURE has two faces: `content` (the text the model reads on the failed
|
|
2159
|
+
// call, and what the UI card shows) and `error.message` (what the PTC bridge
|
|
2160
|
+
// hands a running program as its caught ToolCallError, while the durable
|
|
2161
|
+
// record keeps the structured identity). Rewriting only the content left a
|
|
2162
|
+
// program that printed a caught error holding the Windows form while every
|
|
2163
|
+
// other face said MSYS (v0.22.0).
|
|
2164
|
+
//
|
|
2165
|
+
// This listener projects CONTENT ONLY, and that is deliberate (v0.23.0,
|
|
2166
|
+
// issue #2): a decision carrying both `content` and `value` makes the
|
|
2167
|
+
// registry throw, and a replacement value is illegal on a failed result
|
|
2168
|
+
// anyway. The SUCCESS face therefore lives on the tools/execute wrapper
|
|
2169
|
+
// above, and this listener can never collide with any other plugin.
|
|
2170
|
+
//
|
|
2171
|
+
// error.message is rewritten IN PLACE on the result the waterfall was handed:
|
|
2172
|
+
// the registry keeps `result.error` by reference until the final
|
|
2173
|
+
// materialization, so this write reaches the PTC bridge and the record.
|
|
2174
|
+
// A post-execute `block` decision would rebuild the error as a bare message
|
|
2175
|
+
// and drop its structured identity — never do that.
|
|
1902
2176
|
if (process.platform === 'win32') {
|
|
1903
2177
|
try {
|
|
1904
2178
|
ctx.on('tools/post-execute', (exec, result, next) => {
|
|
1905
|
-
let patch
|
|
1906
2179
|
let contentPatch
|
|
1907
2180
|
try {
|
|
1908
|
-
const dialect =
|
|
1909
|
-
if (dialect.posixPaths && result && typeof result === 'object'
|
|
1910
|
-
|
|
1911
|
-
|
|
1912
|
-
|
|
1913
|
-
|
|
1914
|
-
|
|
1915
|
-
|
|
1916
|
-
|
|
1917
|
-
|
|
1918
|
-
const
|
|
1919
|
-
if (
|
|
1920
|
-
|
|
1921
|
-
const content = rewriteErrorContent(result.content, { nulHint: false, env })
|
|
1922
|
-
if (content !== result.content) contentPatch = content
|
|
1923
|
-
rewriteFailureMessage(result, env)
|
|
1924
|
-
} else if (ERROR_CONTENT_TOOLS.has(exec.name)) {
|
|
1925
|
-
const content = rewriteErrorContent(result.content, { env })
|
|
1926
|
-
if (content !== result.content) contentPatch = content
|
|
1927
|
-
rewriteFailureMessage(result, env)
|
|
1928
|
-
}
|
|
2181
|
+
const dialect = liveSettings.dialect()
|
|
2182
|
+
if (dialect.posixPaths && dialect.errorDialect && exec && result && typeof result === 'object'
|
|
2183
|
+
&& result.isError === true && Array.isArray(result.content)) {
|
|
2184
|
+
const env = dialect.virtualMounts ? buildTranslateEnv(dialect.bashPath) : null
|
|
2185
|
+
if (exec.name === 'run_code') {
|
|
2186
|
+
// paths only — the /dev/null hint is for file tools
|
|
2187
|
+
const content = rewriteErrorContent(result.content, { nulHint: false, env })
|
|
2188
|
+
if (content !== result.content) contentPatch = content
|
|
2189
|
+
rewriteFailureMessage(result, env)
|
|
2190
|
+
} else if (ERROR_CONTENT_TOOLS.has(exec.name)) {
|
|
2191
|
+
const content = rewriteErrorContent(result.content, { env })
|
|
2192
|
+
if (content !== result.content) contentPatch = content
|
|
2193
|
+
rewriteFailureMessage(result, env)
|
|
1929
2194
|
}
|
|
1930
2195
|
}
|
|
1931
2196
|
} catch { /* never block a result */ }
|
|
1932
2197
|
const chain = next()
|
|
1933
|
-
if (
|
|
2198
|
+
if (contentPatch === undefined) return chain
|
|
1934
2199
|
return chain.then((decision) => {
|
|
1935
2200
|
try {
|
|
1936
|
-
if (decision && decision.kind === 'accept') {
|
|
1937
|
-
if (patch !== undefined) return { ...decision, value: patch }
|
|
1938
|
-
return { ...decision, content: contentPatch }
|
|
1939
|
-
}
|
|
2201
|
+
if (decision && decision.kind === 'accept') return { ...decision, content: contentPatch }
|
|
1940
2202
|
} catch { /* keep the downstream decision */ }
|
|
1941
2203
|
return decision
|
|
1942
2204
|
})
|
|
1943
2205
|
})
|
|
1944
|
-
console.log(TAG + '
|
|
2206
|
+
console.log(TAG + ' failure dialect active on tools/post-execute (content + error.message)')
|
|
1945
2207
|
} catch (error) {
|
|
1946
2208
|
console.log(TAG + ' tools/post-execute wiring failed: ' + (error?.message ?? error))
|
|
1947
2209
|
}
|
|
@@ -1965,10 +2227,9 @@ export async function apply(ctx, config = {}) {
|
|
|
1965
2227
|
order: 126,
|
|
1966
2228
|
text: () => {
|
|
1967
2229
|
try {
|
|
1968
|
-
const
|
|
1969
|
-
|
|
1970
|
-
|
|
1971
|
-
return value.virtualMounts === false ? POSIX_DIRECTIVE_TEXT_STRICT : POSIX_DIRECTIVE_TEXT
|
|
2230
|
+
const dialect = liveSettings.dialect()
|
|
2231
|
+
if (dialect.posixPaths !== true) return ''
|
|
2232
|
+
return dialect.virtualMounts === false ? POSIX_DIRECTIVE_TEXT_STRICT : POSIX_DIRECTIVE_TEXT
|
|
1972
2233
|
} catch {
|
|
1973
2234
|
return ''
|
|
1974
2235
|
}
|
|
@@ -1992,6 +2253,19 @@ export async function apply(ctx, config = {}) {
|
|
|
1992
2253
|
adoptSidebarShell(ctx, gitBashCapability.bashPath)
|
|
1993
2254
|
}
|
|
1994
2255
|
|
|
2256
|
+
// ── era split: declarative registration on dsh >= 0.1.7 ──────────────────
|
|
2257
|
+
// The register method IS the era signal. On the new host the whole
|
|
2258
|
+
// materialization path below is dead code (nothing reads the directory any
|
|
2259
|
+
// more), so this branch serves the variants and returns.
|
|
2260
|
+
if (ctx.agentPresets && typeof ctx.agentPresets.register === 'function') {
|
|
2261
|
+
try {
|
|
2262
|
+
await runDeclarativeEra(ctx, presetIds)
|
|
2263
|
+
} catch (error) {
|
|
2264
|
+
console.log(TAG + ' declarative registration failed: ' + (error && error.message ? error.message : error))
|
|
2265
|
+
}
|
|
2266
|
+
return
|
|
2267
|
+
}
|
|
2268
|
+
|
|
1995
2269
|
const roots = ctx.agentPresets?.roots ?? []
|
|
1996
2270
|
const userRoot = firstUserRoot(roots)
|
|
1997
2271
|
if (!userRoot) {
|
|
@@ -2068,4 +2342,4 @@ export async function apply(ctx, config = {}) {
|
|
|
2068
2342
|
}
|
|
2069
2343
|
|
|
2070
2344
|
// Test surface: pure helpers, no Cordis context required.
|
|
2071
|
-
export const _internal = { PRESET_IDS, translateMsysPath, translatePathArguments, rewriteCodePaths, scanCodeLiterals, programPrelude, translateGlobArguments, buildTranslateEnv, rewriteErrorContent, rewriteErrorMessage, rewriteFailureMessage, msysEcho, ERROR_CONTENT_TOOLS, readPosixPaths, readAdoptSidebar, readDialectSettings, windowsToMsys, rewriteResultPaths, MARKER_FILE, classify, materialize, cleanupOnDispose, firstUserRoot, hashTree, skillsHashes, syncDecision, installRegisterShim, baseForRoster, detectBase, pickComposition, personaEraForText, detectPersonaEra, injectPresentRow, hostHasToolPresent, detectPresentSupport, injectPluginManagerRow, hostHasPluginManagerTools, rowFormOf, rowFormsOf, alignEngineRow, alignRalphRow, ROW_SOURCE }
|
|
2345
|
+
export const _internal = { PRESET_IDS, translateMsysPath, translatePathArguments, rewriteCodePaths, scanCodeLiterals, programPrelude, translateGlobArguments, buildTranslateEnv, rewriteErrorContent, rewriteErrorMessage, rewriteFailureMessage, msysEcho, driveToMsys, pathEcho, ERROR_CONTENT_TOOLS, readPosixPaths, readAdoptSidebar, readDialectSettings, windowsToMsys, rewriteResultPaths, MARKER_FILE, classify, materialize, cleanupOnDispose, firstUserRoot, hashTree, skillsHashes, syncDecision, installRegisterShim, baseForRoster, detectBase, pickComposition, personaEraForText, detectPersonaEra, injectPresentRow, hostHasToolPresent, detectPresentSupport, injectPluginManagerRow, hostHasPluginManagerTools, rowFormOf, rowFormsOf, alignEngineRow, alignRalphRow, ROW_SOURCE }
|