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/index.js
CHANGED
|
@@ -612,15 +612,15 @@ const POSIX_DIRECTIVE_TEXT_STRICT = 'The working shell is Git for Windows bash:
|
|
|
612
612
|
* write paths into, so the sentence would be pure noise there. Cheap and
|
|
613
613
|
* honest: the tool list of the very assembly being rendered decides.
|
|
614
614
|
*/
|
|
615
|
-
const RUN_CODE_DIRECTIVE_TEXT = '
|
|
616
|
-
const
|
|
615
|
+
const RUN_CODE_DIRECTIVE_TEXT = ' In a run_code program, compatible plain MSYS path literals are translated on a best-effort basis; interpolated or tagged templates and uncertain syntax are not rewritten. For direct native Node fs/path APIs, prefer explicit Windows drive-letter or cwd-relative paths. Do not rely on literal rewriting for dynamic paths, shell command strings, routes or search data; tool path arguments are translated separately. Shell expansion is unavailable in program strings, so do not rely on $VARS.'
|
|
616
|
+
const RUN_CODE_DIRECTIVE_OFF_TEXT = ' In a run_code program, automatic path-literal rewriting is OFF. Direct native Node fs/path APIs require Windows drive-letter or cwd-relative paths, not MSYS /c/... strings. Tool path arguments are still translated separately; program strings do not perform shell expansion.'
|
|
617
617
|
|
|
618
618
|
/** The run_code sentence for this assembly, or '' when the tool is not offered. */
|
|
619
|
-
export function runCodeHintFor(tools) {
|
|
619
|
+
export function runCodeHintFor(tools, codePaths = true) {
|
|
620
620
|
if (!Array.isArray(tools)) return ''
|
|
621
621
|
for (const tool of tools) {
|
|
622
622
|
const name = tool && typeof tool.name === 'string' ? tool.name : ''
|
|
623
|
-
if (name === 'run_code') return RUN_CODE_DIRECTIVE_TEXT
|
|
623
|
+
if (name === 'run_code') return codePaths === true ? RUN_CODE_DIRECTIVE_TEXT : RUN_CODE_DIRECTIVE_OFF_TEXT
|
|
624
624
|
}
|
|
625
625
|
return ''
|
|
626
626
|
}
|
|
@@ -1155,20 +1155,26 @@ export function translatePathArguments(args, env) {
|
|
|
1155
1155
|
// sees it (see AGENTS.md 4c). The same mount table is therefore applied to the
|
|
1156
1156
|
// program's string literals — but ONLY when the whole program scans cleanly and
|
|
1157
1157
|
// a literal's ENTIRE content is an absolute MSYS path. Anything unclear — an
|
|
1158
|
-
// unterminated literal
|
|
1159
|
-
//
|
|
1158
|
+
// unterminated literal or a regex we cannot place — turns the whole rewrite
|
|
1159
|
+
// into a no-op. Interpolated/tagged templates are skipped as data. A partial guess would
|
|
1160
1160
|
// be far worse than an untranslated one, and this layer never guesses.
|
|
1161
1161
|
//
|
|
1162
1162
|
// Deliberately NOT translated: shell-style '$VAR/x' (a JS literal is data, not
|
|
1163
1163
|
// a shell word) and anything carrying a scheme ('http://...').
|
|
1164
1164
|
|
|
1165
1165
|
/** Source text allowed inside a rewritten literal: no escapes, no dollar sign. */
|
|
1166
|
-
const CODE_LITERAL_SAFE = /^[
|
|
1166
|
+
const CODE_LITERAL_SAFE = /^[\p{L}\p{M}\p{N}._@+~#\-/ ]+$/u
|
|
1167
1167
|
|
|
1168
1168
|
/** Previous significant character that lets a '/' start a regex literal. */
|
|
1169
1169
|
function regexCanStart(previous) {
|
|
1170
1170
|
if (previous === '') return true
|
|
1171
|
-
|
|
1171
|
+
if (['return', 'throw', 'yield', 'await', 'case', 'delete', 'void', 'typeof', 'in', 'instanceof', 'of', 'else', 'do', 'break', 'continue', 'debugger'].includes(previous)) return true
|
|
1172
|
+
return previous.length === 1 && '(,=:[!&|?{;+-*%<>~^'.includes(previous)
|
|
1173
|
+
}
|
|
1174
|
+
|
|
1175
|
+
/** Read an identifier as ONE token: keyword-led regexes are not division. */
|
|
1176
|
+
function codeWordAt(code, index) {
|
|
1177
|
+
return /^[A-Za-z_$][A-Za-z0-9_$]*/.exec(code.slice(index))?.[0]
|
|
1172
1178
|
}
|
|
1173
1179
|
|
|
1174
1180
|
/** Index after the closing quote of the literal at `index`, or -1. */
|
|
@@ -1239,6 +1245,15 @@ function skipBracedExpression(code, index) {
|
|
|
1239
1245
|
last = 'x'
|
|
1240
1246
|
continue
|
|
1241
1247
|
}
|
|
1248
|
+
// A slash after a closing group may start a statement regex OR division.
|
|
1249
|
+
// Without a parser that distinction is uncertain: abort the WHOLE scan.
|
|
1250
|
+
if (c === '/' && (last === ')' || last === '}')) return -1
|
|
1251
|
+
const word = codeWordAt(code, i)
|
|
1252
|
+
if (word) {
|
|
1253
|
+
last = last === '.' ? 'x' : word
|
|
1254
|
+
i += word.length
|
|
1255
|
+
continue
|
|
1256
|
+
}
|
|
1242
1257
|
if (c === '/' && regexCanStart(last)) {
|
|
1243
1258
|
const end = skipRegexLiteral(code, i)
|
|
1244
1259
|
if (end === -1) return -1
|
|
@@ -1294,7 +1309,8 @@ export function scanCodeLiterals(code) {
|
|
|
1294
1309
|
const next = code[i + 1]
|
|
1295
1310
|
if (c === '/' && next === '/') {
|
|
1296
1311
|
const nl = code.indexOf('\n', i)
|
|
1297
|
-
|
|
1312
|
+
// A line comment may legally terminate at EOF (outside interpolation).
|
|
1313
|
+
if (nl === -1) break
|
|
1298
1314
|
i = nl
|
|
1299
1315
|
continue
|
|
1300
1316
|
}
|
|
@@ -1315,11 +1331,25 @@ export function scanCodeLiterals(code) {
|
|
|
1315
1331
|
if (c === '`') {
|
|
1316
1332
|
const template = skipTemplateLiteral(code, i)
|
|
1317
1333
|
if (template === null) return null
|
|
1318
|
-
|
|
1334
|
+
// A preceding expression can be a tag (String.raw, fn(), obj[key], ...).
|
|
1335
|
+
// Tags own raw/cooked semantics: never rewrite their template data.
|
|
1336
|
+
// Contextual words can also be identifiers/tags; skip rather than guess.
|
|
1337
|
+
if (template.plain && regexCanStart(last) && !['of', 'await', 'yield'].includes(last)) {
|
|
1338
|
+
spans.push({ start: i + 1, end: template.end - 1 })
|
|
1339
|
+
}
|
|
1319
1340
|
i = template.end
|
|
1320
1341
|
last = 'x'
|
|
1321
1342
|
continue
|
|
1322
1343
|
}
|
|
1344
|
+
// A slash after a closing group may start a statement regex OR division.
|
|
1345
|
+
// Without a parser that distinction is uncertain: abort the WHOLE scan.
|
|
1346
|
+
if (c === '/' && (last === ')' || last === '}')) return null
|
|
1347
|
+
const word = codeWordAt(code, i)
|
|
1348
|
+
if (word) {
|
|
1349
|
+
last = last === '.' ? 'x' : word
|
|
1350
|
+
i += word.length
|
|
1351
|
+
continue
|
|
1352
|
+
}
|
|
1323
1353
|
if (c === '/' && regexCanStart(last)) {
|
|
1324
1354
|
const end = skipRegexLiteral(code, i)
|
|
1325
1355
|
if (end === -1) return null
|
|
@@ -1380,8 +1410,8 @@ export function rewriteCodePaths(code, env) {
|
|
|
1380
1410
|
// is written into, and a quote inside it (a home directory with an
|
|
1381
1411
|
// apostrophe) would end that literal early — refuse rather than corrupt.
|
|
1382
1412
|
const quote = code[span.start - 1]
|
|
1383
|
-
if (translated.indexOf(quote) >= 0)
|
|
1384
|
-
if (quote === '`' && translated.indexOf('${') >= 0)
|
|
1413
|
+
if (translated.indexOf(quote) >= 0) return code
|
|
1414
|
+
if (quote === '`' && translated.indexOf('${') >= 0) return code
|
|
1385
1415
|
const source = translated.replace(/\\/g, '\\\\')
|
|
1386
1416
|
out += code.slice(cursor, span.start) + source
|
|
1387
1417
|
cursor = span.end
|
|
@@ -1465,13 +1495,15 @@ export function translateGlobArguments(args, env) {
|
|
|
1465
1495
|
// captured and restored on disposal (reload/update/uninstall), so removing
|
|
1466
1496
|
// this plugin returns the sidebar to what it was before.
|
|
1467
1497
|
|
|
1468
|
-
|
|
1498
|
+
// The settings namespace is the row id in dsh-better-sidebar's own bundle
|
|
1499
|
+
// patch (`- id: better-sidebar`; verified against the published 0.25.0
|
|
1500
|
+
// tarball) — NOT the npm package name. Using the package name made
|
|
1501
|
+
// settings.describe() never match and every takeover silently no-op with one
|
|
1502
|
+
// "unavailable" log line (issue #25, Bug 3).
|
|
1503
|
+
const SIDEBAR_NS = 'better-sidebar'
|
|
1469
1504
|
const SIDEBAR_TRIES = 12
|
|
1470
1505
|
const SIDEBAR_RETRY_MS = 1500
|
|
1471
1506
|
|
|
1472
|
-
// Reconciler installed by adoptSidebarShell; a no-op until then.
|
|
1473
|
-
let reconcileSidebar = () => {}
|
|
1474
|
-
|
|
1475
1507
|
/**
|
|
1476
1508
|
* Reconcile the better-sidebar `terminalShell` takeover with the adoptSidebar
|
|
1477
1509
|
* setting (v0.15.0, default ON). ON writes our bashPath (remembering what was
|
|
@@ -1491,95 +1523,69 @@ function adoptSidebarShell(ctx, bashPath, readAdopt) {
|
|
|
1491
1523
|
let previous = ''
|
|
1492
1524
|
let tried = 0
|
|
1493
1525
|
let timer = null
|
|
1494
|
-
|
|
1495
|
-
|
|
1526
|
+
let closed = false
|
|
1527
|
+
let serial = Promise.resolve()
|
|
1496
1528
|
const readShell = () => {
|
|
1497
1529
|
const settings = ctx.get('settings')
|
|
1498
1530
|
if (!settings || typeof settings.update !== 'function') return null
|
|
1499
1531
|
try {
|
|
1500
|
-
|
|
1501
|
-
|
|
1502
|
-
|
|
1503
|
-
|
|
1504
|
-
|
|
1505
|
-
|
|
1506
|
-
|
|
1507
|
-
|
|
1508
|
-
|
|
1509
|
-
|
|
1510
|
-
|
|
1532
|
+
if (typeof settings.describe !== 'function') return null
|
|
1533
|
+
const entry = settings.describe().find((row) => row && row.ns === SIDEBAR_NS)
|
|
1534
|
+
const value = entry?.value
|
|
1535
|
+
return value && typeof value.terminalShell === 'string' ? value.terminalShell : ''
|
|
1536
|
+
} catch { return null }
|
|
1537
|
+
}
|
|
1538
|
+
const restore = async () => {
|
|
1539
|
+
if (!adopted) return
|
|
1540
|
+
const now = readShell()
|
|
1541
|
+
if (now === null) return
|
|
1542
|
+
// The user may have selected another shell since our takeover.
|
|
1543
|
+
if (now === bashPath) {
|
|
1544
|
+
await ctx.get('settings').update(SIDEBAR_NS, { terminalShell: previous })
|
|
1545
|
+
console.log(TAG + " better-sidebar shell restored ('" + previous + "')")
|
|
1511
1546
|
}
|
|
1547
|
+
adopted = false
|
|
1512
1548
|
}
|
|
1513
|
-
|
|
1514
|
-
reconcileSidebar = (desired) => {
|
|
1549
|
+
const reconcileSidebar = async (desired) => {
|
|
1515
1550
|
const current = readShell()
|
|
1516
1551
|
if (current === null) return false
|
|
1517
|
-
|
|
1518
|
-
if (
|
|
1519
|
-
|
|
1520
|
-
|
|
1521
|
-
|
|
1522
|
-
|
|
1523
|
-
adopted = true
|
|
1524
|
-
console.log(
|
|
1525
|
-
`${TAG} dsh-better-sidebar terminal shell -> Git Bash (${bashPath})` +
|
|
1526
|
-
(was ? ` (took over from '${was}')` : ''),
|
|
1527
|
-
)
|
|
1528
|
-
})
|
|
1529
|
-
.catch((error) => {
|
|
1530
|
-
console.log(`${TAG} better-sidebar shell adoption unavailable: ${error?.message ?? error}`)
|
|
1531
|
-
})
|
|
1532
|
-
return true
|
|
1533
|
-
}
|
|
1534
|
-
if (desired && current === bashPath) {
|
|
1535
|
-
adopted = true
|
|
1536
|
-
return true
|
|
1537
|
-
}
|
|
1538
|
-
if (!desired && adopted && current === bashPath) {
|
|
1539
|
-
settings.update(SIDEBAR_NS, { terminalShell: previous })
|
|
1540
|
-
.then(() => {
|
|
1541
|
-
adopted = false
|
|
1542
|
-
console.log(`${TAG} dsh-better-sidebar terminal shell restored ('${previous || 'sidebar default'}') — adoptSidebar off`)
|
|
1543
|
-
})
|
|
1544
|
-
.catch(() => {})
|
|
1545
|
-
return true
|
|
1546
|
-
}
|
|
1552
|
+
if (!desired) { await restore(); return true }
|
|
1553
|
+
if (adopted || current === bashPath) return true
|
|
1554
|
+
previous = current
|
|
1555
|
+
await ctx.get('settings').update(SIDEBAR_NS, { terminalShell: bashPath })
|
|
1556
|
+
adopted = true
|
|
1557
|
+
console.log(TAG + ' dsh-better-sidebar terminal shell -> Git Bash (' + bashPath + ')')
|
|
1547
1558
|
return true
|
|
1548
1559
|
}
|
|
1549
|
-
|
|
1550
1560
|
const run = () => {
|
|
1551
|
-
|
|
1552
|
-
|
|
1553
|
-
|
|
1554
|
-
|
|
1555
|
-
|
|
1556
|
-
|
|
1557
|
-
|
|
1558
|
-
|
|
1559
|
-
|
|
1560
|
-
|
|
1561
|
-
|
|
1562
|
-
|
|
1563
|
-
|
|
1564
|
-
|
|
1565
|
-
|
|
1566
|
-
(
|
|
1567
|
-
if (
|
|
1568
|
-
|
|
1569
|
-
|
|
1570
|
-
|
|
1571
|
-
|
|
1572
|
-
|
|
1573
|
-
|
|
1574
|
-
|
|
1575
|
-
|
|
1576
|
-
|
|
1577
|
-
|
|
1578
|
-
|
|
1579
|
-
}
|
|
1580
|
-
},
|
|
1581
|
-
'dsh-gitbash-shell: sidebar shell revert',
|
|
1582
|
-
)
|
|
1561
|
+
if (closed) return serial
|
|
1562
|
+
if (timer) { clearTimeout(timer); timer = null }
|
|
1563
|
+
serial = serial.then(async () => {
|
|
1564
|
+
if (closed) return
|
|
1565
|
+
tried += 1
|
|
1566
|
+
const ok = await reconcileSidebar(readAdopt())
|
|
1567
|
+
if (ok || closed) return
|
|
1568
|
+
if (tried < SIDEBAR_TRIES) timer = setTimeout(run, SIDEBAR_RETRY_MS)
|
|
1569
|
+
else console.log(TAG + ' better-sidebar shell adoption skipped: settings unavailable')
|
|
1570
|
+
}).catch((error) => {
|
|
1571
|
+
console.log(TAG + ' better-sidebar shell adoption unavailable: ' + (error?.message ?? error))
|
|
1572
|
+
})
|
|
1573
|
+
return serial
|
|
1574
|
+
}
|
|
1575
|
+
try {
|
|
1576
|
+
ctx.on('loader/volatile-update', (paths) => {
|
|
1577
|
+
if (!Array.isArray(paths) || !paths.some((path) => Array.isArray(path) && path[0] === 'adoptSidebar')) return
|
|
1578
|
+
tried = 0
|
|
1579
|
+
void run()
|
|
1580
|
+
})
|
|
1581
|
+
} catch { /* a diagnostic context may have no event service */ }
|
|
1582
|
+
ctx.effect(() => async () => {
|
|
1583
|
+
closed = true
|
|
1584
|
+
if (timer) clearTimeout(timer)
|
|
1585
|
+
await serial
|
|
1586
|
+
await restore()
|
|
1587
|
+
}, 'dsh-gitbash-shell: sidebar shell revert')
|
|
1588
|
+
return run()
|
|
1583
1589
|
}
|
|
1584
1590
|
|
|
1585
1591
|
// ── plugin ──────────────────────────────────────────────────────────────────
|
|
@@ -1759,6 +1765,38 @@ export function minimalPtyBashPath(readResolution) {
|
|
|
1759
1765
|
* failures stay visible through the roster's broken diagnostic instead of
|
|
1760
1766
|
* failing the boot.
|
|
1761
1767
|
*/
|
|
1768
|
+
async function migrateVariantDefault(ctx, readVariants) {
|
|
1769
|
+
if (readVariants() === true) return true
|
|
1770
|
+
const readDefault = () => { try { return ctx.agentPresets.defaultId } catch { return undefined } }
|
|
1771
|
+
const current = readDefault()
|
|
1772
|
+
if (typeof current !== 'string' || !PRESET_IDS.includes(current)) return true
|
|
1773
|
+
const target = VARIANT_DEFAULT_SUCCESSORS[current]
|
|
1774
|
+
try {
|
|
1775
|
+
const settings = ctx.get('settings')
|
|
1776
|
+
if (!settings || typeof settings.update !== 'function') return false
|
|
1777
|
+
const roster = await ctx.agentPresets.list()
|
|
1778
|
+
if (!Array.isArray(roster) || !roster.some((row) => row && row.id === target)) {
|
|
1779
|
+
console.warn(TAG + ' default-preset migration stood down: successor "' + target + '" is not registered. Set an OFFICIAL default or turn variantPresets on.')
|
|
1780
|
+
return false
|
|
1781
|
+
}
|
|
1782
|
+
// A roster read is asynchronous: never overwrite a newer user selection.
|
|
1783
|
+
if (readVariants() === true || readDefault() !== current) return true
|
|
1784
|
+
await settings.update('agent-preset-registry', { selectedDefault: target })
|
|
1785
|
+
const after = readDefault()
|
|
1786
|
+
if (after !== target) {
|
|
1787
|
+
console.warn(TAG + ' default-preset migration did not stick (read back "' + after + '"). Set an OFFICIAL default or turn variantPresets on.')
|
|
1788
|
+
return false
|
|
1789
|
+
}
|
|
1790
|
+
console.log(TAG + ' deployment default migrated: "' + current + '" -> "' + target
|
|
1791
|
+
+ '" (variantPresets is off). Pinned sessions are unchanged; agent-preset/locked still requires a NEW session.')
|
|
1792
|
+
return true
|
|
1793
|
+
} catch (error) {
|
|
1794
|
+
console.warn(TAG + ' default-preset migration failed: ' + (error?.message ?? error)
|
|
1795
|
+
+ '. Set an OFFICIAL default or turn variantPresets on; variants were not retired.')
|
|
1796
|
+
return false
|
|
1797
|
+
}
|
|
1798
|
+
}
|
|
1799
|
+
|
|
1762
1800
|
async function registerVariant(ctx, presetId, { gitBashActive, skillsDir, pythonActive, hostExtras = {}, readBashResolution }) {
|
|
1763
1801
|
const meta = PRESET_META[presetId]
|
|
1764
1802
|
if (!meta) return undefined
|
|
@@ -1811,6 +1849,17 @@ async function runDeclarativeEra(ctx, presetIds, readSuppress, readBashResolutio
|
|
|
1811
1849
|
/** The CPython fact the LIVE registrations were built with. */
|
|
1812
1850
|
let registeredPython = false
|
|
1813
1851
|
let serial = Promise.resolve()
|
|
1852
|
+
let closed = false
|
|
1853
|
+
ctx.effect(() => async () => {
|
|
1854
|
+
closed = true
|
|
1855
|
+
await serial
|
|
1856
|
+
const failures = []
|
|
1857
|
+
for (const [presetId, off] of [...live]) {
|
|
1858
|
+
live.delete(presetId)
|
|
1859
|
+
try { await off() } catch (error) { failures.push(error) }
|
|
1860
|
+
}
|
|
1861
|
+
if (failures.length) throw new AggregateError(failures, 'Git Bash preset cleanup failed')
|
|
1862
|
+
}, 'dsh-gitbash-shell: declarative preset registrations')
|
|
1814
1863
|
|
|
1815
1864
|
// v0.35.0: the variant roster is opt-in (`variantPresets`, default OFF).
|
|
1816
1865
|
// OFF means an EMPTY desired set — the cleanup above still runs, so a
|
|
@@ -1820,8 +1869,16 @@ async function runDeclarativeEra(ctx, presetIds, readSuppress, readBashResolutio
|
|
|
1820
1869
|
: [])
|
|
1821
1870
|
|
|
1822
1871
|
const reconcile = () => {
|
|
1872
|
+
if (closed) return serial
|
|
1823
1873
|
serial = serial.then(async () => {
|
|
1874
|
+
if (closed) return
|
|
1824
1875
|
const want = desired()
|
|
1876
|
+
const pythonActive = peerPython
|
|
1877
|
+
// Dynamic OFF repairs the deployment default BEFORE retiring its id.
|
|
1878
|
+
// Keep registrations if the official migration cannot be verified.
|
|
1879
|
+
if (live.size > 0 && readVariants() !== true
|
|
1880
|
+
&& !await migrateVariantDefault(ctx, readVariants)) return
|
|
1881
|
+
if (closed) return
|
|
1825
1882
|
// The peer's EFFECTIVE CPython backend rewrites the workflow rows of
|
|
1826
1883
|
// EVERY variant (the official Python composition disables them, and
|
|
1827
1884
|
// workflow-ptc refuses a non-TypeScript backend), so a flip re-registers
|
|
@@ -1829,14 +1886,16 @@ async function runDeclarativeEra(ctx, presetIds, readSuppress, readBashResolutio
|
|
|
1829
1886
|
// as the backend swap itself: the peer's patch is evaluated at boot. A
|
|
1830
1887
|
// bare INTENT without the effective backend changes nothing here — the
|
|
1831
1888
|
// composition still runs Node, so the workflow rows must stay live.
|
|
1832
|
-
if (live.size > 0 && registeredPython !==
|
|
1889
|
+
if (live.size > 0 && registeredPython !== pythonActive) {
|
|
1833
1890
|
for (const [presetId, off] of [...live]) {
|
|
1834
1891
|
live.delete(presetId)
|
|
1835
1892
|
try {
|
|
1836
1893
|
await off()
|
|
1837
1894
|
console.log(TAG + " preset '" + presetId + "' retired (peer CPython backend changed; rows are rebuilt)")
|
|
1838
1895
|
} catch (error) {
|
|
1896
|
+
live.set(presetId, off)
|
|
1839
1897
|
console.log(TAG + " preset '" + presetId + "' retirement failed: " + (error && error.message ? error.message : error))
|
|
1898
|
+
return // never register replacements after an unverified retirement
|
|
1840
1899
|
}
|
|
1841
1900
|
}
|
|
1842
1901
|
}
|
|
@@ -1847,14 +1906,18 @@ async function runDeclarativeEra(ctx, presetIds, readSuppress, readBashResolutio
|
|
|
1847
1906
|
await off()
|
|
1848
1907
|
console.log(TAG + " preset '" + presetId + "' retired (dsh-ptc-cordis-preset covers Creation mode on Git Bash)")
|
|
1849
1908
|
} catch (error) {
|
|
1909
|
+
live.set(presetId, off)
|
|
1850
1910
|
console.log(TAG + " preset '" + presetId + "' retirement failed: " + (error && error.message ? error.message : error))
|
|
1911
|
+
return
|
|
1851
1912
|
}
|
|
1852
1913
|
}
|
|
1853
1914
|
for (const presetId of want) {
|
|
1915
|
+
if (closed) break
|
|
1854
1916
|
if (live.has(presetId)) continue
|
|
1855
1917
|
try {
|
|
1856
|
-
const unregister = await registerVariant(ctx, presetId, { gitBashActive, skillsDir, pythonActive
|
|
1918
|
+
const unregister = await registerVariant(ctx, presetId, { gitBashActive, skillsDir, pythonActive, hostExtras, readBashResolution })
|
|
1857
1919
|
if (unregister) {
|
|
1920
|
+
if (closed) { await unregister(); break }
|
|
1858
1921
|
live.set(presetId, unregister)
|
|
1859
1922
|
console.log(TAG + " preset '" + presetId + "' registered declaratively")
|
|
1860
1923
|
}
|
|
@@ -1862,7 +1925,7 @@ async function runDeclarativeEra(ctx, presetIds, readSuppress, readBashResolutio
|
|
|
1862
1925
|
console.log(TAG + " preset '" + presetId + "' registration failed: " + (error && error.message ? error.message : error))
|
|
1863
1926
|
}
|
|
1864
1927
|
}
|
|
1865
|
-
registeredPython =
|
|
1928
|
+
registeredPython = pythonActive
|
|
1866
1929
|
}).catch((error) => {
|
|
1867
1930
|
console.log(TAG + ' preset reconcile failed: ' + (error && error.message ? error.message : error))
|
|
1868
1931
|
})
|
|
@@ -1870,6 +1933,7 @@ async function runDeclarativeEra(ctx, presetIds, readSuppress, readBashResolutio
|
|
|
1870
1933
|
}
|
|
1871
1934
|
|
|
1872
1935
|
await reconcile()
|
|
1936
|
+
if (closed) return
|
|
1873
1937
|
console.log(TAG + ' registered ' + live.size + ' preset(s) declaratively (' + [...live.keys()].join(', ') + ')'
|
|
1874
1938
|
+ (live.size === 0 ? ' — variantPresets is off: the official presets carry Git Bash through the inline tool patch (see the CHANGELOG)'
|
|
1875
1939
|
: '')
|
|
@@ -1932,7 +1996,8 @@ async function runDeclarativeEra(ctx, presetIds, readSuppress, readBashResolutio
|
|
|
1932
1996
|
console.log(TAG + ' dedupe switch wiring failed: ' + (error && error.message ? error.message : error))
|
|
1933
1997
|
}
|
|
1934
1998
|
|
|
1935
|
-
|
|
1999
|
+
// If settings appears after an OFF transition failed closed, retry it.
|
|
2000
|
+
ctx.inject(['settings'], () => { void reconcile() })
|
|
1936
2001
|
}
|
|
1937
2002
|
|
|
1938
2003
|
export async function apply(ctx, config = {}) {
|
|
@@ -2125,7 +2190,7 @@ export async function apply(ctx, config = {}) {
|
|
|
2125
2190
|
// v0.20.1: the run_code sentence rides the SAME context entry, but
|
|
2126
2191
|
// only when this assembly actually offers the tool (see
|
|
2127
2192
|
// runCodeHintFor) — every other mode stays quiet.
|
|
2128
|
-
const hint = runCodeHintFor(assembly.tools)
|
|
2193
|
+
const hint = runCodeHintFor(assembly.tools, dialect.codePaths === true)
|
|
2129
2194
|
if (hint !== '') {
|
|
2130
2195
|
for (const assembled of Array.isArray(assembly.contexts) ? assembly.contexts : []) {
|
|
2131
2196
|
if (assembled && assembled.name === 'gitbash-shell:posix-paths' && typeof assembled.text === 'string') {
|
|
@@ -2611,8 +2676,8 @@ export async function apply(ctx, config = {}) {
|
|
|
2611
2676
|
// official tool surface mirrored call-for-call) plus a HIDE on the real pwsh
|
|
2612
2677
|
// tool while the pwsh switch is off (route B: the official executor stays in
|
|
2613
2678
|
// place, so pwsh is real PowerShell and hiding it is a product choice). The
|
|
2614
|
-
//
|
|
2615
|
-
//
|
|
2679
|
+
// Full Windows variants keep the same pwsh-only face as official presets,
|
|
2680
|
+
// so they receive this same private-executor patch. Minimal stays persistent.
|
|
2616
2681
|
if (process.platform === 'win32') {
|
|
2617
2682
|
try {
|
|
2618
2683
|
mountInlineTools(ctx, {
|
|
@@ -2661,55 +2726,10 @@ export async function apply(ctx, config = {}) {
|
|
|
2661
2726
|
// With variantPresets ON nothing here runs: the variants stay registered,
|
|
2662
2727
|
// so a variant default keeps working as-is.
|
|
2663
2728
|
try {
|
|
2664
|
-
|
|
2665
|
-
|
|
2666
|
-
|
|
2667
|
-
|
|
2668
|
-
const settings = settingsCtx.settings
|
|
2669
|
-
if (!settings || typeof settings.update !== 'function') return
|
|
2670
|
-
// The switch is the whole contract: keep-old-modes ON means a
|
|
2671
|
-
// variant default is a legitimate choice and stays untouched.
|
|
2672
|
-
if (liveSettings.variantPresets() === true) return
|
|
2673
|
-
const readDefault = () => { try { return ctx.agentPresets.defaultId } catch { return undefined } }
|
|
2674
|
-
const current = readDefault()
|
|
2675
|
-
if (typeof current !== 'string' || !PRESET_IDS.includes(current)) return
|
|
2676
|
-
const target = VARIANT_DEFAULT_SUCCESSORS[current]
|
|
2677
|
-
if (typeof target !== 'string') return
|
|
2678
|
-
// The successor must exist in the CURRENT roster: official ids
|
|
2679
|
-
// renamed once already (code→ptc), and writing an unknown id
|
|
2680
|
-
// would trade a known failure for a worse one.
|
|
2681
|
-
const roster = await ctx.agentPresets.list()
|
|
2682
|
-
if (!Array.isArray(roster) || !roster.some((row) => row && row.id === target)) {
|
|
2683
|
-
console.warn(TAG + ' default-preset migration stood down: the deployment default is the variant "'
|
|
2684
|
-
+ current + '" but the successor "' + target + '" is not registered on this host.'
|
|
2685
|
-
+ ' Set the default manually among the OFFICIAL presets, or turn variantPresets on.')
|
|
2686
|
-
return
|
|
2687
|
-
}
|
|
2688
|
-
try {
|
|
2689
|
-
await settings.update('agent-preset-registry', { selectedDefault: target })
|
|
2690
|
-
} catch (error) {
|
|
2691
|
-
console.warn(TAG + ' default-preset migration write failed (' + (error?.message ?? error)
|
|
2692
|
-
+ '): the deployment default is still the variant "' + current
|
|
2693
|
-
+ '" — NEW sessions would fail with agent-preset/not-found.'
|
|
2694
|
-
+ ' Fix: set an OFFICIAL default in the preset picker, or turn variantPresets on.')
|
|
2695
|
-
return
|
|
2696
|
-
}
|
|
2697
|
-
const after = readDefault()
|
|
2698
|
-
if (after === target) {
|
|
2699
|
-
console.log(TAG + ' deployment default migrated: "' + current + '" -> "' + target
|
|
2700
|
-
+ '" (variantPresets is off). Old sessions pinned to a variant still need a NEW'
|
|
2701
|
-
+ ' session — the official agent-preset/locked rule blocks a mid-session switch.')
|
|
2702
|
-
} else {
|
|
2703
|
-
console.warn(TAG + ' default-preset migration did not stick (read back "'
|
|
2704
|
-
+ (after === undefined ? 'unreadable' : after) + '"). Set the default manually in'
|
|
2705
|
-
+ ' Settings -> agent preset, or turn variantPresets on.')
|
|
2706
|
-
}
|
|
2707
|
-
} catch (error) {
|
|
2708
|
-
console.log(TAG + ' default-preset migration failed: ' + (error?.message ?? error))
|
|
2709
|
-
}
|
|
2710
|
-
})()
|
|
2711
|
-
})
|
|
2712
|
-
}
|
|
2729
|
+
ctx.inject(['settings'], () => {
|
|
2730
|
+
// Boot migration is detached: registry.list() may await the Loader boot.
|
|
2731
|
+
void migrateVariantDefault(ctx, () => liveSettings.variantPresets())
|
|
2732
|
+
})
|
|
2713
2733
|
} catch (error) {
|
|
2714
2734
|
console.log(TAG + ' default-preset migration wiring failed: ' + (error?.message ?? error))
|
|
2715
2735
|
}
|
|
@@ -2727,8 +2747,21 @@ export async function apply(ctx, config = {}) {
|
|
|
2727
2747
|
enabled: () => liveSettings.fastctxTools(),
|
|
2728
2748
|
createRequireFrom: (base) => createRequire(base ?? import.meta.url),
|
|
2729
2749
|
tools: (c) => { try { return c.get('tools') } catch { return undefined } },
|
|
2730
|
-
msysToWin: (p) =>
|
|
2731
|
-
|
|
2750
|
+
msysToWin: (p, execution) => {
|
|
2751
|
+
const dialect = liveSettings.dialect()
|
|
2752
|
+
if (dialect.posixPaths !== true || !dialectApplies(dialect, execution?.agent)) return p
|
|
2753
|
+
if (!dialect.virtualMounts) {
|
|
2754
|
+
return typeof p === 'string' && (MSYS_DRIVE_ROOT.test(p) || MSYS_BARE_DRIVE.test(p))
|
|
2755
|
+
? translateMsysPath(p, null) : p
|
|
2756
|
+
}
|
|
2757
|
+
return translateMsysPath(p, buildTranslateEnv(readConfiguredBashPath()))
|
|
2758
|
+
},
|
|
2759
|
+
echo: (text, execution) => {
|
|
2760
|
+
const dialect = liveSettings.dialect()
|
|
2761
|
+
if (dialect.posixPaths !== true || !dialectApplies(dialect, execution?.agent)) return text
|
|
2762
|
+
const env = dialect.virtualMounts ? buildTranslateEnv(readConfiguredBashPath()) : null
|
|
2763
|
+
return env ? msysEcho(text, env) : windowsToMsys(text)
|
|
2764
|
+
},
|
|
2732
2765
|
log: (m) => console.log(TAG + ' ' + m),
|
|
2733
2766
|
})
|
|
2734
2767
|
} catch (error) {
|