dsh-think-translate 1.2.0 → 1.2.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/lib/client.js CHANGED
@@ -23,7 +23,7 @@ window.__ModuleLoader__.load({
23
23
  // there yet (an older process still serving the port), and the contract test
24
24
  // pins PLUGIN_VERSION to package.json so the two can never drift.
25
25
  var PLUGIN_NAME = "dsh-think-translate";
26
- var PLUGIN_VERSION = "1.2.0";
26
+ var PLUGIN_VERSION = "1.2.1";
27
27
  var PLUGIN_REPO = "https://github.com/UncleK/dsh-think-translate";
28
28
  // The official MarkdownText contract changed in 0.1.2-alpha: it now takes a
29
29
  // nested `labels` object and dereferences it without defaults inside its code
@@ -1984,6 +1984,18 @@ window.__ModuleLoader__.load({
1984
1984
  })
1985
1985
  .catch(function (e) { setMsg(t.connFail + ": " + (e && e.message ? e.message : e)); });
1986
1986
  };
1987
+ // Where the host looked for the harness settings, and what it found there:
1988
+ // an empty scan used to be a dead end, so the panel now names the file it
1989
+ // read (the usual cause is a home directory the plugin guessed wrong).
1990
+ var dshScanNote = function () {
1991
+ var d = cfg && cfg.dshScan;
1992
+ if (!d || !d.home) return null;
1993
+ var where = d.home + "/settings.yaml";
1994
+ var detail = d.settingsFound
1995
+ ? (d.providers && d.providers.length ? d.providers.join(", ") : t.dshImportEmpty)
1996
+ : (t.dshImportEmpty + " · " + where);
1997
+ return createElement("div", { className: "xl-note", style: { marginTop: "4px" } }, detail);
1998
+ };
1987
1999
  var addAllDsh = function () {
1988
2000
  var next = chain.slice();
1989
2001
  dshAvailable.forEach(function (id) { if (next.indexOf(id) < 0) next.push(id); });
@@ -2127,17 +2139,15 @@ window.__ModuleLoader__.load({
2127
2139
  })),
2128
2140
  createElement("label", { className: "xl-form-row" },
2129
2141
  createElement("span", { className: "xl-form-label" }, t.providerApiKey),
2130
- // The env name has no row of its own (it arrives from a preset or a
2131
- // DSH row), so show it here: without the badge the value is invisible
2132
- // and typing a key would look like it had no effect.
2133
- formApiKeyEnv.trim() ? createElement("span", { className: "xl-badge", title: t.providerApiKey }, "env:" + formApiKeyEnv.trim()) : null,
2142
+ // No env badge here: the apiKeyEnv a preset fills in is an
2143
+ // implementation detail of that endpoint, and the saved row shows an
2144
+ // env:NAME badge anyway. Typing a key clears the env name (below), so
2145
+ // the key the user can see is always the one that wins.
2134
2146
  createElement("input", {
2135
2147
  className: "xl-cfg", placeholder: t.providerApiKey, value: formApiKey,
2136
2148
  onChange: function (e) {
2137
2149
  var v = e.target.value;
2138
2150
  setFormApiKey(v);
2139
- // Typing a key means "use this key": drop the env name, or the
2140
- // host would keep preferring the environment value.
2141
2151
  if (v.trim() !== "") setFormApiKeyEnv("");
2142
2152
  }
2143
2153
  })),
@@ -2181,7 +2191,9 @@ window.__ModuleLoader__.load({
2181
2191
  "+ " + t.dshImport + (dshAvailable.length ? " · " + dshAvailable.length : "")),
2182
2192
  dshMenu ? createElement("div", { className: "xl-dsh-menu" },
2183
2193
  dshAvailable.length === 0
2184
- ? createElement("div", { className: "xl-note" }, t.dshImportEmpty)
2194
+ ? createElement("div", null,
2195
+ createElement("div", { className: "xl-note" }, t.dshImportEmpty),
2196
+ dshScanNote())
2185
2197
  : createElement("div", null,
2186
2198
  dshAvailable.length > 1 ? createElement("button", {
2187
2199
  className: "xl-btn", type: "button", style: { marginBottom: "4px" }, onClick: addAllDsh
@@ -2219,6 +2231,23 @@ window.__ModuleLoader__.load({
2219
2231
  // that node, and taking it out of the tree behind React's back means React
2220
2232
  // can later patch or remove a detached node (NotFoundError on removeChild).
2221
2233
  // A sibling in the same slot looks identical and leaves React's tree intact.
2234
+ function shellNavIcon(btn) {
2235
+ // The SHELL's svg, never ours: once the holder is in place it comes first
2236
+ // in document order, so a plain querySelector('svg') returns the book — and
2237
+ // the next pass then hides the plugin's own glyph. That is exactly how the
2238
+ // nav icon disappeared after the first pass (fixed in 1.2.1).
2239
+ var kids = btn.children || [];
2240
+ for (var i = 0; i < kids.length; i++) {
2241
+ var tag = (kids[i].tagName || "").toLowerCase();
2242
+ if (tag === "svg" && !kids[i].hasAttribute("data-xl-nav-glyph-svg")) return kids[i];
2243
+ }
2244
+ var nested = btn.querySelectorAll ? btn.querySelectorAll("svg") : [];
2245
+ for (var j = 0; j < nested.length; j++) {
2246
+ if (!nested[j].hasAttribute("data-xl-nav-glyph-svg")) return nested[j];
2247
+ }
2248
+ return null;
2249
+ }
2250
+
2222
2251
  function decorateSettingsGlyph() {
2223
2252
  if (typeof document === "undefined") return;
2224
2253
  var st = UI_TEXT[store.lang] || UI_TEXT.en;
@@ -2231,7 +2260,7 @@ window.__ModuleLoader__.load({
2231
2260
  var btn = buttons[j];
2232
2261
  var label = btn.lastElementChild;
2233
2262
  if (!label || (label.textContent || "").trim() !== title) continue;
2234
- var svg = btn.querySelector("svg");
2263
+ var svg = shellNavIcon(btn);
2235
2264
  if (!svg || !svg.parentNode) continue;
2236
2265
  if (!btn.querySelector('[data-xl-nav-glyph="1"]')) {
2237
2266
  var holder = document.createElement("span");
@@ -2242,6 +2271,8 @@ window.__ModuleLoader__.load({
2242
2271
  if (svgClass) holder.setAttribute("class", svgClass);
2243
2272
  holder.style.display = "inline-flex";
2244
2273
  holder.innerHTML = NAV_GLYPH_SVG;
2274
+ var inner = holder.querySelector ? holder.querySelector("svg") : null;
2275
+ if (inner) inner.setAttribute("data-xl-nav-glyph-svg", "1");
2245
2276
  svg.parentNode.insertBefore(holder, svg);
2246
2277
  }
2247
2278
  // Self-healing: the shell may re-render its icon, and the decoration
package/lib/index.js CHANGED
@@ -22,13 +22,40 @@ export const inject = ['webServer', 'subprocess', 'timer']
22
22
  const CONFIG_PATH = fileURLToPath(new URL('./config.json', import.meta.url))
23
23
  const PACKAGE_PATH = fileURLToPath(new URL('../package.json', import.meta.url))
24
24
 
25
- // DSH harness home directory. Used to discover service providers from the
26
- // user's configured llm-pi-ai providers. Falls back to platform defaults
27
- // when the DSH_HOME env var is not set.
28
- const DSH_HOME = process.env.DSH_HOME ||
29
- (process.platform === 'darwin'
30
- ? (process.env.HOME + '/Library/Application Support/dsh-desktop/harness')
31
- : ((process.env.APPDATA || '') + '\\dsh-desktop\\harness'))
25
+ // Where the harness keeps its settings.yaml and .credentials.yaml.
26
+ //
27
+ // This mirrors DSH's own resolution — `$DSH_HOME`, then `~/.dsh` (see
28
+ // @deepseek-ai/dsh-home-paths: an explicit path, then DSH_HOME, then
29
+ // homedir()/.dsh, with a blank DSH_HOME treated as unset) — and only then the
30
+ // desktop-app layouts. Picking one guess was the bug: an install whose home is
31
+ // ~/.dsh fell through to %APPDATA%\dsh-desktop\harness, which does not exist
32
+ // there, so discovery silently reported "no provider configured" while the
33
+ // settings file sat right there. The candidates are resolved per call and the
34
+ // first one that actually holds a settings.yaml wins (see resolveHarnessHome).
35
+ function harnessHomeCandidates() {
36
+ const out = []
37
+ const env = process.env.DSH_HOME
38
+ if (typeof env === 'string' && env.trim()) out.push(env.trim())
39
+ const home = process.env.HOME || process.env.USERPROFILE || ''
40
+ if (home) {
41
+ out.push(home.replace(/[\\/]+$/, '') + '/.dsh')
42
+ if (process.platform === 'darwin') out.push(home.replace(/[\\/]+$/, '') + '/Library/Application Support/dsh-desktop/harness')
43
+ }
44
+ if (process.env.APPDATA) out.push(process.env.APPDATA.replace(/[\\/]+$/, '') + '\\dsh-desktop\\harness')
45
+ return out
46
+ }
47
+
48
+ // The first candidate holding a settings.yaml, else the first candidate at all
49
+ // (so the diagnostic can say which one was looked at).
50
+ function pickHarnessHome(candidates, exists) {
51
+ for (const dir of candidates) {
52
+ try { if (exists(dir + '/settings.yaml')) return { home: dir, settingsFound: true } } catch (e) { /* keep looking */ }
53
+ }
54
+ return { home: candidates[0] || '', settingsFound: false }
55
+ }
56
+
57
+ // Diagnostic for the settings panel: which home was used and what was read there.
58
+ let dshScanDiag = { home: null, settingsFound: false, credsFound: false, providers: [] }
32
59
 
33
60
  // Defaults: local model (Ollama) enabled from the start — the chain is
34
61
  // fail-open, so if Ollama is down it falls back to google/bing. `chain` is the
@@ -777,12 +804,17 @@ let dshScanOk = false
777
804
  async function discoverDshProviders() {
778
805
  const fs = await import('node:fs')
779
806
  const out = {}
807
+ const candidates = harnessHomeCandidates()
808
+ const picked = pickHarnessHome(candidates, function (p) { return fs.existsSync(p) })
809
+ const home = picked.home
810
+ const diag = { home: home, candidates: candidates, settingsFound: picked.settingsFound, credsFound: false, providers: [], error: null }
780
811
  try {
781
- const settingsPath = DSH_HOME + '/settings.yaml'
782
- const credsPath = DSH_HOME + '/.credentials.yaml'
812
+ const settingsPath = home + '/settings.yaml'
813
+ const credsPath = home + '/.credentials.yaml'
783
814
  let settingsTxt = '', credsTxt = ''
784
815
  try { settingsTxt = fs.readFileSync(settingsPath, 'utf8') } catch (e) {}
785
816
  try { credsTxt = fs.readFileSync(credsPath, 'utf8') } catch (e) {}
817
+ diag.credsFound = credsTxt.length > 0
786
818
  const providers = extractDshProviders(settingsTxt)
787
819
  const refs = extractCredRefs(credsTxt)
788
820
  dshScanOk = /llm-pi-ai:[\s\S]*?\n[ \t]{2}providers:/m.test(settingsTxt)
@@ -792,7 +824,7 @@ async function discoverDshProviders() {
792
824
  for (const k of Object.keys(refs)) credRefsCache.set(k, refs[k])
793
825
  for (const name of Object.keys(providers)) {
794
826
  const p = providers[name]
795
- if (!p.baseURL || !p.model) continue
827
+ if (!p.baseURL || !p.model) { diag.skipped = (diag.skipped || []).concat(name); continue }
796
828
  const entry = {
797
829
  type: 'openai',
798
830
  source: 'dsh',
@@ -802,8 +834,12 @@ async function discoverDshProviders() {
802
834
  }
803
835
  if (p.apiKeyEnv) entry.apiKeyEnv = p.apiKeyEnv
804
836
  out[name] = entry
837
+ diag.providers.push(name)
805
838
  }
806
- } catch (e) { /* silent — discovery must never block the plugin */ }
839
+ } catch (e) {
840
+ diag.error = e instanceof Error ? e.message : String(e)
841
+ }
842
+ dshScanDiag = diag
807
843
  return out
808
844
  }
809
845
 
@@ -1374,8 +1410,12 @@ export function apply(ctx) {
1374
1410
  const cfg = await loadConfig()
1375
1411
  await saveConfig(cfg)
1376
1412
  // The fresh merge is what fills in resolved credentials, so this route
1377
- // is the one that most needs the sanitizer.
1378
- sendJson(res, 200, stripResolvedKeys(cfg))
1413
+ // is the one that most needs the sanitizer. `dshScan` rides along (on the
1414
+ // response copy only, never saved) so the panel can explain an empty scan
1415
+ // instead of just saying "no provider configured".
1416
+ const out = stripResolvedKeys(cfg)
1417
+ out.dshScan = dshScanDiag
1418
+ sendJson(res, 200, out)
1379
1419
  } catch (e) {
1380
1420
  sendJson(res, 500, { error: e instanceof Error ? e.message : String(e) })
1381
1421
  }
@@ -1395,4 +1435,5 @@ export {
1395
1435
  needsTranslate, hasProse, chunkText, fnv, deepMerge,
1396
1436
  structuredCloneSafe, ADAPTERS,
1397
1437
  resolveApiKey, stripResolvedKeys, missingApiKey, clearNullFields, handleVersion,
1438
+ harnessHomeCandidates, pickHarnessHome,
1398
1439
  }
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "dsh-think-translate",
3
3
  "description": "Display-layer translation for the DSH web app: thinking chain, task cards, and optional answer text, in 8 target languages (local Ollama / any OpenAI-compatible or Anthropic endpoint / Google / Bing, fail-open)",
4
- "version": "1.2.0",
4
+ "version": "1.2.1",
5
5
  "type": "module",
6
6
  "main": "lib/index.js",
7
7
  "exports": {