@deepseek-harness-tui/dsh-tui 0.8.2 → 0.8.4
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/README.md +37 -12
- package/bin/dsh-tui.js +93 -18
- package/lib/types/commands.d.ts.map +1 -1
- package/lib/types/commands.js +1 -0
- package/lib/types/components/LogoV2.d.ts +4 -1
- package/lib/types/components/LogoV2.d.ts.map +1 -1
- package/lib/types/components/LogoV2.js +8 -3
- package/lib/types/components/PromptInput.d.ts.map +1 -1
- package/lib/types/components/PromptInput.js +1 -3
- package/lib/types/components/TipsPanel.d.ts +14 -0
- package/lib/types/components/TipsPanel.d.ts.map +1 -0
- package/lib/types/components/TipsPanel.js +37 -0
- package/lib/types/components/activityFrames.d.ts.map +1 -1
- package/lib/types/components/activityFrames.js +10 -0
- package/lib/types/dsh-adapter/channel.d.ts.map +1 -1
- package/lib/types/dsh-adapter/channel.js +8 -14
- package/lib/types/dsh-adapter/plugin.d.ts.map +1 -1
- package/lib/types/dsh-adapter/plugin.js +17 -0
- package/lib/types/effortPrefs.d.ts +7 -6
- package/lib/types/effortPrefs.d.ts.map +1 -1
- package/lib/types/effortPrefs.js +7 -6
- package/lib/types/i18n.d.ts +50 -14
- package/lib/types/i18n.d.ts.map +1 -1
- package/lib/types/i18n.js +21 -4
- package/lib/types/ink/ink.d.ts +1 -0
- package/lib/types/ink/ink.d.ts.map +1 -1
- package/lib/types/ink/ink.js +11 -0
- package/lib/types/ink/parse-keypress.d.ts.map +1 -1
- package/lib/types/ink/parse-keypress.js +15 -0
- package/lib/types/screens/Chat.d.ts.map +1 -1
- package/lib/types/screens/Chat.js +10 -3
- package/lib/types/screens/StatusLine.js +3 -3
- package/lib/types/tips.d.ts +30 -0
- package/lib/types/tips.d.ts.map +1 -0
- package/lib/types/tips.js +575 -0
- package/package.json +5 -3
- package/presets/liangshen/.dsh-tui-managed.json +1 -1
- package/presets/liangshen/agent.cordis.yml +7 -5
- package/presets/liangshen/custom-bash.mjs +184 -9
|
@@ -13,9 +13,26 @@
|
|
|
13
13
|
* the ordinary (cross-platform) subprocess seam keeps the schema anchor
|
|
14
14
|
* without the PTY dependency.
|
|
15
15
|
*
|
|
16
|
-
* Executable resolution (
|
|
17
|
-
*
|
|
18
|
-
*
|
|
16
|
+
* Executable resolution, in order (an explicit entry is the ONLY candidate:
|
|
17
|
+
* a miss fails loudly instead of silently substituting a guess):
|
|
18
|
+
* 1. explicit `config.bashPath` or the `DSH_TUI_LIANGSHEN_BASH_PATH`
|
|
19
|
+
* environment variable (absolute path to a Git Bash bash.exe);
|
|
20
|
+
* 2. a `git` on PATH followed to its installation tree — every Windows Git
|
|
21
|
+
* install ships bash next to git, wherever it was installed, so this
|
|
22
|
+
* covers installer, portable, and Scoop layouts (a Scoop `.shim` sidecar
|
|
23
|
+
* is followed to the real target) — yielding `<root>\bin\bash.exe` and
|
|
24
|
+
* `<root>\usr\bin\bash.exe`;
|
|
25
|
+
* 3. conventional install roots (`ProgramFiles`, `ProgramFiles(x86)`,
|
|
26
|
+
* `%LOCALAPPDATA%\Programs\Git`);
|
|
27
|
+
* 4. Scoop's conventional roots (`%SCOOP%`, else `%USERPROFILE%\scoop`)
|
|
28
|
+
* under `apps\git\current\` — Scoop does not always export `SCOOP`;
|
|
29
|
+
* 5. bare `bash` on PATH.
|
|
30
|
+
* `C:\Windows\System32\bash.exe` (and Sysnative) is the WSL launcher, not
|
|
31
|
+
* the Git Bash executor: it is ALWAYS rejected, so a PATH lookup can never
|
|
32
|
+
* silently hand the model a WSL shell. Resolution runs ONCE at apply time —
|
|
33
|
+
* a total miss warns and SKIPS registration, so `tool-bootstrap`'s
|
|
34
|
+
* missing-tool path fails open to the full catalog instead of registering a
|
|
35
|
+
* `bash` tool whose every call would fail mid-session.
|
|
19
36
|
*
|
|
20
37
|
* Semantics mirror the official bash tool: `bash -c <command>` in a fresh
|
|
21
38
|
* process, bounded output, non-zero exit reported not thrown. No sandbox
|
|
@@ -24,6 +41,8 @@
|
|
|
24
41
|
* `str_replace_editor` (Minimal's two tools).
|
|
25
42
|
*/
|
|
26
43
|
|
|
44
|
+
import { readFileSync } from 'node:fs'
|
|
45
|
+
|
|
27
46
|
/** Cordis plugin name used by loader diagnostics. */
|
|
28
47
|
export const name = 'custom-bash'
|
|
29
48
|
|
|
@@ -50,11 +69,167 @@ const commandSchema = {
|
|
|
50
69
|
additionalProperties: false,
|
|
51
70
|
}
|
|
52
71
|
|
|
72
|
+
function addCandidate(candidates, seen, candidate) {
|
|
73
|
+
if (typeof candidate !== 'string' || candidate.length === 0) return
|
|
74
|
+
const key = candidate.toLowerCase()
|
|
75
|
+
if (seen.has(key)) return
|
|
76
|
+
seen.add(key)
|
|
77
|
+
candidates.push(candidate)
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
/** The explicit override, if any: `config.bashPath`, else the environment variable. */
|
|
81
|
+
function explicitBashPath(config, environment) {
|
|
82
|
+
for (const value of [config.bashPath, environment.DSH_TUI_LIANGSHEN_BASH_PATH]) {
|
|
83
|
+
if (typeof value === 'string' && value.length > 0) return value
|
|
84
|
+
}
|
|
85
|
+
return undefined
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
/** Return conventional Git Bash locations in deterministic order (explicit config first). */
|
|
89
|
+
export function windowsBashCandidates(config = {}, environment = process.env) {
|
|
90
|
+
const explicit = explicitBashPath(config, environment)
|
|
91
|
+
if (explicit !== undefined) return [explicit]
|
|
92
|
+
|
|
93
|
+
const candidates = []
|
|
94
|
+
const seen = new Set()
|
|
95
|
+
for (const root of [
|
|
96
|
+
environment.ProgramFiles,
|
|
97
|
+
environment['ProgramFiles(x86)'],
|
|
98
|
+
]) {
|
|
99
|
+
if (typeof root !== 'string' || root.length === 0) continue
|
|
100
|
+
addCandidate(candidates, seen, `${root}\\Git\\bin\\bash.exe`)
|
|
101
|
+
addCandidate(candidates, seen, `${root}\\Git\\usr\\bin\\bash.exe`)
|
|
102
|
+
}
|
|
103
|
+
if (typeof environment.LOCALAPPDATA === 'string' && environment.LOCALAPPDATA.length > 0) {
|
|
104
|
+
addCandidate(candidates, seen, `${environment.LOCALAPPDATA}\\Programs\\Git\\bin\\bash.exe`)
|
|
105
|
+
addCandidate(candidates, seen, `${environment.LOCALAPPDATA}\\Programs\\Git\\usr\\bin\\bash.exe`)
|
|
106
|
+
}
|
|
107
|
+
// Scoop keeps Git under its own root; SCOOP is exported by some setups, but
|
|
108
|
+
// the per-user default location works without it.
|
|
109
|
+
const scoopRoots = []
|
|
110
|
+
if (typeof environment.SCOOP === 'string' && environment.SCOOP.length > 0) {
|
|
111
|
+
scoopRoots.push(environment.SCOOP)
|
|
112
|
+
}
|
|
113
|
+
if (typeof environment.USERPROFILE === 'string' && environment.USERPROFILE.length > 0) {
|
|
114
|
+
scoopRoots.push(`${environment.USERPROFILE}\\scoop`)
|
|
115
|
+
}
|
|
116
|
+
for (const root of scoopRoots) {
|
|
117
|
+
addCandidate(candidates, seen, `${root}\\apps\\git\\current\\bin\\bash.exe`)
|
|
118
|
+
addCandidate(candidates, seen, `${root}\\apps\\git\\current\\usr\\bin\\bash.exe`)
|
|
119
|
+
}
|
|
120
|
+
addCandidate(candidates, seen, 'bash')
|
|
121
|
+
return candidates
|
|
122
|
+
}
|
|
123
|
+
|
|
124
|
+
/** Windows' System32 bash.exe is a WSL launcher, not the Git Bash executor. */
|
|
125
|
+
export function isWindowsSubsystemLauncher(path) {
|
|
126
|
+
return /[\\/]windows[\\/](?:system32|sysnative)[\\/]bash\.exe$/i.test(path)
|
|
127
|
+
}
|
|
128
|
+
|
|
129
|
+
/**
|
|
130
|
+
* Scoop-style shim launchers keep the real target in a sibling `.shim` text
|
|
131
|
+
* file (`path = "..."`); follow it so a PATH-resolved git.exe still yields
|
|
132
|
+
* its installation tree.
|
|
133
|
+
*/
|
|
134
|
+
export function resolveShimTarget(exePath, reader = readFileSync) {
|
|
135
|
+
for (const sidecar of [exePath.replace(/\.exe$/i, '') + '.shim', `${exePath}.shim`]) {
|
|
136
|
+
try {
|
|
137
|
+
const text = reader(sidecar, 'utf8')
|
|
138
|
+
const match = text.match(/^\s*path\s*=\s*"?(.+?)"?\s*$/mi)
|
|
139
|
+
if (match !== null) return match[1]
|
|
140
|
+
} catch { /* not a shim launcher */ }
|
|
141
|
+
}
|
|
142
|
+
return exePath
|
|
143
|
+
}
|
|
144
|
+
|
|
145
|
+
/** Bash candidates derived from a git.exe inside a Git for Windows tree. */
|
|
146
|
+
export function bashCandidatesFromGit(gitPath) {
|
|
147
|
+
if (typeof gitPath !== 'string' || gitPath.length === 0) return []
|
|
148
|
+
const parts = gitPath.replace(/\//g, '\\').split('\\').filter(part => part.length > 0)
|
|
149
|
+
if (parts.at(-1)?.toLowerCase() !== 'git.exe') return []
|
|
150
|
+
const roots = []
|
|
151
|
+
const push = root => {
|
|
152
|
+
if (root.length > 0 && !roots.includes(root)) roots.push(root)
|
|
153
|
+
}
|
|
154
|
+
// <root>\cmd\git.exe and <root>\bin\git.exe -> <root>
|
|
155
|
+
if (parts.length >= 3) push(parts.slice(0, -2).join('\\'))
|
|
156
|
+
// <root>\mingw64\bin\git.exe -> <root>; a plain <root>\cmd layout must not
|
|
157
|
+
// also strip its real root.
|
|
158
|
+
if (parts.length >= 5
|
|
159
|
+
&& parts.at(-2)?.toLowerCase() === 'bin'
|
|
160
|
+
&& /^mingw(32|64)$/.test(parts.at(-3) ?? '')) {
|
|
161
|
+
push(parts.slice(0, -3).join('\\'))
|
|
162
|
+
}
|
|
163
|
+
const candidates = []
|
|
164
|
+
for (const root of roots) {
|
|
165
|
+
candidates.push(`${root}\\bin\\bash.exe`, `${root}\\usr\\bin\\bash.exe`)
|
|
166
|
+
}
|
|
167
|
+
return candidates
|
|
168
|
+
}
|
|
169
|
+
|
|
170
|
+
/**
|
|
171
|
+
* Follow the PATH-visible git executable to its installation tree: every
|
|
172
|
+
* Windows Git install ships git-bash next to git, wherever it was installed,
|
|
173
|
+
* so this covers installer, portable, and Scoop layouts without hardcoding.
|
|
174
|
+
*/
|
|
175
|
+
async function gitTreeCandidates(subprocess, failures) {
|
|
176
|
+
let gitPath
|
|
177
|
+
try {
|
|
178
|
+
gitPath = await subprocess.resolveExecutable('git')
|
|
179
|
+
} catch (error) {
|
|
180
|
+
failures.push(`git: ${error instanceof Error ? error.message : String(error)}`)
|
|
181
|
+
return []
|
|
182
|
+
}
|
|
183
|
+
return bashCandidatesFromGit(resolveShimTarget(gitPath))
|
|
184
|
+
}
|
|
185
|
+
|
|
186
|
+
/** Resolve Git Bash without accidentally accepting the WSL compatibility shim. */
|
|
187
|
+
export async function resolveWindowsBash(subprocess, config = {}, environment = process.env) {
|
|
188
|
+
const failures = []
|
|
189
|
+
const explicit = explicitBashPath(config, environment)
|
|
190
|
+
const candidates = explicit !== undefined
|
|
191
|
+
// An explicit path is honored as-is: never silently substitute a guess.
|
|
192
|
+
? [explicit]
|
|
193
|
+
: [...await gitTreeCandidates(subprocess, failures), ...windowsBashCandidates(config, environment)]
|
|
194
|
+
for (const candidate of candidates) {
|
|
195
|
+
try {
|
|
196
|
+
const resolved = await subprocess.resolveExecutable(candidate)
|
|
197
|
+
if (isWindowsSubsystemLauncher(resolved)) {
|
|
198
|
+
failures.push(`${candidate}: resolved to the Windows Subsystem for Linux launcher`)
|
|
199
|
+
continue
|
|
200
|
+
}
|
|
201
|
+
return resolved
|
|
202
|
+
} catch (error) {
|
|
203
|
+
failures.push(`${candidate}: ${error instanceof Error ? error.message : String(error)}`)
|
|
204
|
+
}
|
|
205
|
+
}
|
|
206
|
+
throw new Error(`Git Bash executable unavailable (${failures.join('; ')})`)
|
|
207
|
+
}
|
|
208
|
+
|
|
209
|
+
function warn(ctx, message) {
|
|
210
|
+
try {
|
|
211
|
+
ctx.logger.warn(message)
|
|
212
|
+
} catch {
|
|
213
|
+
// Logger unavailable — registration is skipped either way.
|
|
214
|
+
}
|
|
215
|
+
}
|
|
216
|
+
|
|
53
217
|
/** Register the model-facing `bash` tool. */
|
|
54
|
-
export function apply(ctx, config) {
|
|
55
|
-
const
|
|
56
|
-
const timeoutMs = Number.isSafeInteger(
|
|
57
|
-
const maxOutputBytes = Number.isSafeInteger(
|
|
218
|
+
export async function apply(ctx, config) {
|
|
219
|
+
const source = config === undefined ? {} : config
|
|
220
|
+
const timeoutMs = Number.isSafeInteger(source.timeoutMs) && source.timeoutMs > 0 ? source.timeoutMs : DEFAULT_TIMEOUT_MS
|
|
221
|
+
const maxOutputBytes = Number.isSafeInteger(source.maxOutputBytes) && source.maxOutputBytes > 0 ? source.maxOutputBytes : DEFAULT_MAX_OUTPUT_BYTES
|
|
222
|
+
|
|
223
|
+
let bashPath
|
|
224
|
+
try {
|
|
225
|
+
bashPath = await resolveWindowsBash(ctx.subprocess, source)
|
|
226
|
+
} catch (error) {
|
|
227
|
+
// Skip registration on a miss: an absent `bash` in the assembled catalog
|
|
228
|
+
// makes tool-bootstrap fail open to the full catalog, while a registered
|
|
229
|
+
// tool that cannot spawn would fail on every call mid-session.
|
|
230
|
+
warn(ctx, `custom-bash: ${error instanceof Error ? error.message : String(error)} — the Windows bootstrap bash tool is not registered; the bootstrap filter will expose the full catalog`)
|
|
231
|
+
return
|
|
232
|
+
}
|
|
58
233
|
|
|
59
234
|
ctx.tools.register({
|
|
60
235
|
name: 'bash',
|
|
@@ -69,6 +244,7 @@ export function apply(ctx, config) {
|
|
|
69
244
|
'* NOTE: runs without OS sandbox confinement on Windows (no landlock); treat output as untrusted.',
|
|
70
245
|
].join('\n'),
|
|
71
246
|
parameters: commandSchema,
|
|
247
|
+
timeoutMs,
|
|
72
248
|
output: {
|
|
73
249
|
schema: {
|
|
74
250
|
type: 'object',
|
|
@@ -81,13 +257,12 @@ export function apply(ctx, config) {
|
|
|
81
257
|
render: (_args, value) => [{ type: 'text', text: value.text }],
|
|
82
258
|
},
|
|
83
259
|
async execute(args, exec) {
|
|
84
|
-
const shell = await ctx.subprocess.resolveExecutable(bashPath, undefined, exec?.signal)
|
|
85
260
|
const workdir = typeof args.workdir === 'string' && args.workdir.length > 0
|
|
86
261
|
? args.workdir
|
|
87
262
|
: exec?.agent?.session?.header?.cwd
|
|
88
263
|
const signal = exec?.signal
|
|
89
264
|
const handle = ctx.subprocess.spawn({
|
|
90
|
-
argv: [
|
|
265
|
+
argv: [bashPath, '-c', args.command],
|
|
91
266
|
...workdir !== undefined ? { cwd: workdir } : {},
|
|
92
267
|
stdio: {
|
|
93
268
|
stdin: 'ignore',
|