dsh-gitbash-shell 0.39.1 → 0.39.3
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/CHANGELOG.md +100 -0
- package/README.md +38 -14
- package/README_EN.md +54 -18
- package/dsh.plugin.json +1 -1
- package/package.json +3 -3
- package/src/bash-render.js +21 -10
- package/src/bash-tool.js +79 -26
- package/src/client.js +36 -25
- package/src/compositions.js +11 -7
- package/src/fastctx-bridge.js +78 -31
- package/src/index.js +185 -152
- package/src/inline-tools.js +124 -50
- package/src/shell.js +14 -14
package/src/client.js
CHANGED
|
@@ -2063,6 +2063,42 @@ window.__ModuleLoader__.load({
|
|
|
2063
2063
|
if (scope && typeof scope.getSnapshot === "function") snap = scope.getSnapshot();
|
|
2064
2064
|
} catch (error_) { /* keep unavailable */ }
|
|
2065
2065
|
|
|
2066
|
+
var pageView = props.view === "page";
|
|
2067
|
+
|
|
2068
|
+
// Hooks run for every snapshot state; readiness only gates their work.
|
|
2069
|
+
useEffect(function () {
|
|
2070
|
+
if (snap.status !== "ready" || (!open && !pageView)) return undefined;
|
|
2071
|
+
if (toolsLoaded || toolBusy) return undefined;
|
|
2072
|
+
loadTools();
|
|
2073
|
+
return undefined;
|
|
2074
|
+
}, [open, pageView, snap.status]);
|
|
2075
|
+
|
|
2076
|
+
/* Poll ONLY while a job is unfinished, and through the cheap ?job=1
|
|
2077
|
+
form. A transient failure retries without discarding progress. */
|
|
2078
|
+
useEffect(function () {
|
|
2079
|
+
if (snap.status !== "ready" || toolJob === null || toolJob.finished) return undefined;
|
|
2080
|
+
var alive = true;
|
|
2081
|
+
var timer;
|
|
2082
|
+
function schedule() {
|
|
2083
|
+
timer = setTimeout(function () {
|
|
2084
|
+
fetch(TOOLS_URL + "?job=1", { headers: { accept: "application/json" } })
|
|
2085
|
+
.then(function (res) {
|
|
2086
|
+
if (!res.ok) throw new Error("HTTP " + res.status);
|
|
2087
|
+
return res.json();
|
|
2088
|
+
})
|
|
2089
|
+
.then(function (data) {
|
|
2090
|
+
if (!alive) return;
|
|
2091
|
+
var next = data !== null && typeof data === "object" && data.job ? data.job : null;
|
|
2092
|
+
setToolJob(next);
|
|
2093
|
+
if (next !== null && next.finished) loadTools();
|
|
2094
|
+
})
|
|
2095
|
+
.catch(function () { if (alive) schedule(); });
|
|
2096
|
+
}, 1200);
|
|
2097
|
+
}
|
|
2098
|
+
schedule();
|
|
2099
|
+
return function () { alive = false; clearTimeout(timer); };
|
|
2100
|
+
}, [toolJob, snap.status]);
|
|
2101
|
+
|
|
2066
2102
|
if (snap.status !== "ready") return null;
|
|
2067
2103
|
|
|
2068
2104
|
var value = snap.value || {};
|
|
@@ -2135,7 +2171,6 @@ window.__ModuleLoader__.load({
|
|
|
2135
2171
|
// dsh 0.1.6-alpha.2: the Plugins page renders this card through the
|
|
2136
2172
|
// `plugins.bundle.config` slot with view="page" — the page draws the
|
|
2137
2173
|
// title itself; the collapsible shell only shows on the popup seat.
|
|
2138
|
-
var pageView = props.view === "page";
|
|
2139
2174
|
|
|
2140
2175
|
var mounts = snap.value.virtualMounts !== false;
|
|
2141
2176
|
var errs = snap.value.errorDialect !== false;
|
|
@@ -2469,30 +2504,6 @@ window.__ModuleLoader__.load({
|
|
|
2469
2504
|
.then(function () { setToolBusy(false); });
|
|
2470
2505
|
}
|
|
2471
2506
|
|
|
2472
|
-
useEffect(function () {
|
|
2473
|
-
if (!open && !pageView) return undefined;
|
|
2474
|
-
if (toolsLoaded || toolBusy) return undefined;
|
|
2475
|
-
loadTools();
|
|
2476
|
-
return undefined;
|
|
2477
|
-
}, [open]);
|
|
2478
|
-
|
|
2479
|
-
/* Poll ONLY while a job is unfinished, and through the cheap ?job=1
|
|
2480
|
-
form: a full probe re-runs three winget commands per second. */
|
|
2481
|
-
useEffect(function () {
|
|
2482
|
-
if (toolJob === null || toolJob.finished) return undefined;
|
|
2483
|
-
var timer = setTimeout(function () {
|
|
2484
|
-
fetch(TOOLS_URL + "?job=1", { headers: { accept: "application/json" } })
|
|
2485
|
-
.then(function (res) { return res.json(); })
|
|
2486
|
-
.then(function (data) {
|
|
2487
|
-
var next = data !== null && typeof data === "object" && data.job ? data.job : null;
|
|
2488
|
-
setToolJob(next);
|
|
2489
|
-
if (next !== null && next.finished) loadTools();
|
|
2490
|
-
})
|
|
2491
|
-
.catch(function () { /* keep the last known progress */ });
|
|
2492
|
-
}, 1200);
|
|
2493
|
-
return function () { clearTimeout(timer); };
|
|
2494
|
-
}, [toolJob]);
|
|
2495
|
-
|
|
2496
2507
|
var toolsSection = fold("sec.tools",
|
|
2497
2508
|
toolsData === null && toolErr === "" ? E("p", { className: "gb-hint" }, t("tools.loading")) : null,
|
|
2498
2509
|
toolErr !== "" ? E("p", { className: "gb-error" }, toolErr) : null,
|
package/src/compositions.js
CHANGED
|
@@ -9,10 +9,10 @@
|
|
|
9
9
|
*
|
|
10
10
|
* Every variant mirrors the official 0.1.7 preset it replaces (packages/
|
|
11
11
|
* bundle/web-app/presets/{standard,minimal,ptc,cordis}.patch.yml) with the
|
|
12
|
-
*
|
|
13
|
-
*
|
|
14
|
-
*
|
|
15
|
-
*
|
|
12
|
+
* Git Bash routing: full variants keep the official platform-gated shell
|
|
13
|
+
* rows, and the inline patch adds OUR bash over a private executor on Windows.
|
|
14
|
+
* The minimal variant instead keeps its persistent Git Bash terminal rows.
|
|
15
|
+
* On non-Windows hosts the official stack stays native. A conditional delta exists while
|
|
16
16
|
* dsh-ptc-cordis-preset reports its experimental-CPython backend ACTIVE: the
|
|
17
17
|
* two workflow rows follow the official Python composition and go off (see
|
|
18
18
|
* `pythonActive` below). While that backend is off — the default, and also
|
|
@@ -56,7 +56,8 @@ const planSectionFor = (hostEra021) => hostEra021
|
|
|
56
56
|
* @param {'standard'|'ptc'|'cordis'} input.kind - which official preset this
|
|
57
57
|
* variant mirrors (workflow rows follow it: enabled on standard/cordis,
|
|
58
58
|
* disabled on ptc).
|
|
59
|
-
* @param {boolean} input.gitBash -
|
|
59
|
+
* @param {boolean} input.gitBash - retained for composition-call compatibility;
|
|
60
|
+
* full variants obtain Git Bash from the per-agent inline patch on Windows.
|
|
60
61
|
* @param {string|undefined} input.skillsDir - Creation authoring skills
|
|
61
62
|
* directory (cordis variant only; resolved beside the agent-preset package).
|
|
62
63
|
* @param {boolean} [input.pythonActive] - whether the experimental CPython
|
|
@@ -75,8 +76,11 @@ const planSectionFor = (hostEra021) => hostEra021
|
|
|
75
76
|
*/
|
|
76
77
|
export function pluginsFor({ kind, gitBash, skillsDir, pythonActive = false, hostExtras = {} }) {
|
|
77
78
|
const win = typeof process !== 'undefined' && process.platform === 'win32'
|
|
78
|
-
|
|
79
|
-
|
|
79
|
+
// Route B preserves the root PowerShell executor. On Windows the official
|
|
80
|
+
// tool-bash would execute through that executor, so full variants retain the
|
|
81
|
+
// official pwsh face and let the same per-agent inline patch add OUR bash.
|
|
82
|
+
const bashDisabled = win
|
|
83
|
+
const pwshDisabled = !win
|
|
80
84
|
const workflowOn = kind !== 'ptc' && pythonActive !== true
|
|
81
85
|
// dsh 0.2.1 added two rows to every full-tool official preset — time-context
|
|
82
86
|
// (durable clock readings) and tool-schedule (the reminder tools) — plus a
|
package/src/fastctx-bridge.js
CHANGED
|
@@ -106,20 +106,21 @@ export class McpStdio {
|
|
|
106
106
|
this.child.stderr.on('data', (chunk) => {
|
|
107
107
|
this.stderrTail = (this.stderrTail + chunk).slice(-2000)
|
|
108
108
|
})
|
|
109
|
-
this.child.on('
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
})
|
|
121
|
-
|
|
122
|
-
|
|
109
|
+
this.child.on('error', (error) => this.#fail(error))
|
|
110
|
+
this.child.stdin.on('error', (error) => this.#fail(error))
|
|
111
|
+
this.child.on('exit', (code) => this.#fail(new Error(`the FastCtx server exited (code ${code ?? 'null'})`)))
|
|
112
|
+
try {
|
|
113
|
+
await this.#request('initialize', {
|
|
114
|
+
protocolVersion: '2024-11-05',
|
|
115
|
+
capabilities: {},
|
|
116
|
+
clientInfo: { name: 'dsh-gitbash-shell', version: '0.35.0' },
|
|
117
|
+
})
|
|
118
|
+
this.#notify('notifications/initialized')
|
|
119
|
+
return this
|
|
120
|
+
} catch (error) {
|
|
121
|
+
await this.close()
|
|
122
|
+
throw error
|
|
123
|
+
}
|
|
123
124
|
}
|
|
124
125
|
|
|
125
126
|
/** List the server's tools: [{ name, description, inputSchema }]. */
|
|
@@ -140,15 +141,21 @@ export class McpStdio {
|
|
|
140
141
|
this.dead = true
|
|
141
142
|
const child = this.child
|
|
142
143
|
this.child = undefined
|
|
143
|
-
|
|
144
|
-
reject(new Error('the FastCtx bridge was stopped'))
|
|
145
|
-
}
|
|
146
|
-
this.pending.clear()
|
|
144
|
+
this.#fail(new Error('the FastCtx bridge was stopped'))
|
|
147
145
|
if (child && child.exitCode === null && !child.killed) {
|
|
148
146
|
try { child.kill() } catch { /* already gone */ }
|
|
149
147
|
}
|
|
150
148
|
}
|
|
151
149
|
|
|
150
|
+
#fail(error) {
|
|
151
|
+
this.dead = true
|
|
152
|
+
for (const { reject, timer } of this.pending.values()) {
|
|
153
|
+
clearTimeout(timer)
|
|
154
|
+
reject(error)
|
|
155
|
+
}
|
|
156
|
+
this.pending.clear()
|
|
157
|
+
}
|
|
158
|
+
|
|
152
159
|
#onChunk(chunk) {
|
|
153
160
|
this.buffer += chunk
|
|
154
161
|
let at = this.buffer.indexOf('\n')
|
|
@@ -310,6 +317,10 @@ export function mountFastctx(ctx, deps) {
|
|
|
310
317
|
let client = undefined
|
|
311
318
|
let decided = false
|
|
312
319
|
let withdrawnByOps = false
|
|
320
|
+
let closed = false
|
|
321
|
+
let publishing
|
|
322
|
+
let connecting
|
|
323
|
+
const starting = new Set()
|
|
313
324
|
|
|
314
325
|
const unpublish = (reason) => {
|
|
315
326
|
if (disposer) {
|
|
@@ -320,16 +331,34 @@ export function mountFastctx(ctx, deps) {
|
|
|
320
331
|
}
|
|
321
332
|
|
|
322
333
|
const callWithReconnect = async (rawName, args) => {
|
|
334
|
+
if (closed || withdrawnByOps) throw new Error('the FastCtx bridge was stopped')
|
|
323
335
|
if (client === undefined || client.isDead) {
|
|
324
|
-
|
|
325
|
-
|
|
326
|
-
|
|
336
|
+
if (!connecting) {
|
|
337
|
+
connecting = (async () => {
|
|
338
|
+
const { file } = resolveFastctx(ctx, createRequireFrom)
|
|
339
|
+
if (file === '') throw new Error('the FastCtx payload is not installed; repair the profile dependencies (frozen install) and retry')
|
|
340
|
+
const fresh = new McpStdio({ file, log })
|
|
341
|
+
starting.add(fresh)
|
|
342
|
+
try {
|
|
343
|
+
await fresh.start()
|
|
344
|
+
if (closed || withdrawnByOps) throw new Error('the FastCtx bridge was stopped')
|
|
345
|
+
client = fresh
|
|
346
|
+
} catch (error) {
|
|
347
|
+
await fresh.close()
|
|
348
|
+
throw error
|
|
349
|
+
} finally {
|
|
350
|
+
starting.delete(fresh)
|
|
351
|
+
}
|
|
352
|
+
})().finally(() => { connecting = undefined })
|
|
353
|
+
}
|
|
354
|
+
await connecting
|
|
327
355
|
}
|
|
356
|
+
if (closed || withdrawnByOps) throw new Error('the FastCtx bridge was stopped')
|
|
328
357
|
return client.callTool(rawName, args)
|
|
329
358
|
}
|
|
330
359
|
|
|
331
|
-
const
|
|
332
|
-
if (disposer || decided && withdrawnByOps) return
|
|
360
|
+
const publishOnce = async () => {
|
|
361
|
+
if (closed || disposer || decided && withdrawnByOps) return
|
|
333
362
|
const registry = tools(ctx)
|
|
334
363
|
if (!registry || typeof registry.register !== 'function') return
|
|
335
364
|
const { file, source } = resolveFastctx(ctx, createRequireFrom)
|
|
@@ -340,6 +369,7 @@ export function mountFastctx(ctx, deps) {
|
|
|
340
369
|
return
|
|
341
370
|
}
|
|
342
371
|
const fresh = new McpStdio({ file, log })
|
|
372
|
+
starting.add(fresh)
|
|
343
373
|
let toolList
|
|
344
374
|
try {
|
|
345
375
|
await fresh.start()
|
|
@@ -350,9 +380,12 @@ export function mountFastctx(ctx, deps) {
|
|
|
350
380
|
decided = true
|
|
351
381
|
withdrawnByOps = true
|
|
352
382
|
return
|
|
383
|
+
} finally {
|
|
384
|
+
starting.delete(fresh)
|
|
353
385
|
}
|
|
354
386
|
await fresh.close()
|
|
355
|
-
|
|
387
|
+
if (closed || withdrawnByOps) return
|
|
388
|
+
// Discovery never overwrites the independently owned lazy call client.
|
|
356
389
|
const wanted = toolList.filter((tool) => FASTCTX_FILE_TOOLS.includes(String(tool?.name)))
|
|
357
390
|
const registered = []
|
|
358
391
|
const registeredNames = []
|
|
@@ -386,16 +419,20 @@ export function mountFastctx(ctx, deps) {
|
|
|
386
419
|
schema: TOOL_OUTPUT_SCHEMA,
|
|
387
420
|
render: (_args, value) => renderTextContent(value),
|
|
388
421
|
},
|
|
389
|
-
async execute(args) {
|
|
390
|
-
//
|
|
391
|
-
//
|
|
422
|
+
async execute(args, execution) {
|
|
423
|
+
// Preserve v0.39.1 / #21: glob accepts real string arrays,
|
|
424
|
+
// while stringified arrays and malformed patterns fail loudly.
|
|
392
425
|
if (args !== null && typeof args === 'object' && 'pattern' in args) {
|
|
393
426
|
const invalid = validateFastctxPattern(rawName, args.pattern)
|
|
394
427
|
if (invalid) throw new Error(invalid)
|
|
395
428
|
}
|
|
396
|
-
const translated = translateFastctxArgs(args, msysToWin)
|
|
429
|
+
const translated = translateFastctxArgs(args, msysToWin ? (path) => msysToWin(path, execution) : undefined)
|
|
397
430
|
const result = await callWithReconnect(rawName, translated)
|
|
398
|
-
|
|
431
|
+
const text = renderFastctxResult(rawName, result, echo ? (value) => echo(value, execution) : undefined)
|
|
432
|
+
// The host treats a returned value as success; MCP failures must
|
|
433
|
+
// throw, just as its official MCP client does.
|
|
434
|
+
if (result?.isError === true) throw new Error(text)
|
|
435
|
+
return { content: [{ type: 'text', text }] }
|
|
399
436
|
},
|
|
400
437
|
})
|
|
401
438
|
if (typeof dispose === 'function') {
|
|
@@ -416,7 +453,13 @@ export function mountFastctx(ctx, deps) {
|
|
|
416
453
|
log(`FastCtx bridge active (${source}): ${registeredNames.join(', ')}`)
|
|
417
454
|
}
|
|
418
455
|
|
|
456
|
+
const publish = () => {
|
|
457
|
+
if (!publishing) publishing = publishOnce().finally(() => { publishing = undefined })
|
|
458
|
+
return publishing
|
|
459
|
+
}
|
|
460
|
+
|
|
419
461
|
const reconcile = (agent) => {
|
|
462
|
+
if (closed) return
|
|
420
463
|
if (!enabled()) { unpublish('FastCtx bridge disabled by the fastctxTools switch'); decided = true; withdrawnByOps = true; return }
|
|
421
464
|
if (decided && withdrawnByOps) return
|
|
422
465
|
let names = []
|
|
@@ -440,8 +483,12 @@ export function mountFastctx(ctx, deps) {
|
|
|
440
483
|
log(`FastCtx bridge wiring failed: ${error?.message ?? error}`)
|
|
441
484
|
return
|
|
442
485
|
}
|
|
443
|
-
ctx.effect(() => () => {
|
|
486
|
+
ctx.effect(() => async () => {
|
|
487
|
+
closed = true
|
|
444
488
|
unpublish()
|
|
445
|
-
|
|
489
|
+
const owned = new Set([...starting, client].filter(Boolean))
|
|
490
|
+
await Promise.allSettled([...owned].map((connection) => connection.close()))
|
|
491
|
+
await Promise.allSettled([publishing, connecting].filter(Boolean))
|
|
492
|
+
client = undefined
|
|
446
493
|
}, 'dsh-gitbash-shell: FastCtx bridge')
|
|
447
494
|
}
|