dshmarket 1.66.21 → 1.66.23

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/src/net.ts CHANGED
@@ -32,6 +32,40 @@
32
32
 
33
33
  import { Agent, EnvHttpProxyAgent, fetch as undiciFetch } from 'undici'
34
34
 
35
+ /**
36
+ * Hand the host's own undici the dispatcher our import took (#861).
37
+ *
38
+ * undici's `lib/global.js` runs at import time, and when its symbol is unset
39
+ * it installs an Agent on BOTH global dispatcher symbols: `.1`, which Node's
40
+ * built-in fetch reads, and `.2`, which undici 8's `getGlobalDispatcher()`
41
+ * reads. `setGlobalDispatcher` defines them non-configurable, so the value
42
+ * cannot be taken back — but it is writable, and undici 8 only ever READS
43
+ * `.2`, so hiding ours there is enough for it to install its own dispatcher
44
+ * and wrap it onto `.1` for the legacy consumers.
45
+ *
46
+ * Measured on Node 24.20 and 25.6 (undici 7.29 as the market's copy, 8.12 as
47
+ * the host's): with this, a host importing undici 8 after the market gets
48
+ * `getGlobalDispatcher() instanceof its own Agent` — without it, it is handed
49
+ * the market's undici 7 Agent (`instanceof` its own Agent: false) and never
50
+ * installs one, which is the state #861 reports as the host's own LLM and
51
+ * model-list requests failing once the market is loaded.
52
+ *
53
+ * `.1` is deliberately left alone: Node's built-in fetch reads it and asserts
54
+ * it is set, so clearing it breaks global fetch outright (measured: the same
55
+ * probe that makes `.2` undefined fails `assert(dispatcher)` when `.1` is
56
+ * cleared too). Our own requests are unaffected either way — every one of
57
+ * them carries a dispatcher this module created.
58
+ */
59
+ export function releaseHostUndiciDispatcher(
60
+ global: Record<symbol, unknown> = globalThis as unknown as Record<symbol, unknown>,
61
+ ownAgent: abstract new (...args: never[]) => unknown = Agent,
62
+ ): void {
63
+ const symbol = Symbol.for('undici.globalDispatcher.2')
64
+ if (global[symbol] instanceof ownAgent) global[symbol] = undefined
65
+ }
66
+
67
+ releaseHostUndiciDispatcher()
68
+
35
69
  /**
36
70
  * The proxy this process would use for the catalog, if any.
37
71
  *
@@ -2,6 +2,7 @@
2
2
 
3
3
  import { randomUUID } from 'node:crypto'
4
4
  import { progress, TARGET_RE, type DesktopPluginRuntime, type InstallResult } from './dsh-cli.ts'
5
+ import { activeRegion, DEFAULT_NPM_REGISTRY, routesFor } from './regions.ts'
5
6
 
6
7
  interface ManagedResult {
7
8
  application: string
@@ -10,7 +11,7 @@ interface ManagedResult {
10
11
  }
11
12
 
12
13
  export interface OfficialPluginManagerLike {
13
- installBundle(spec: string, options?: { requestId?: string }): Promise<ManagedResult>
14
+ installBundle(spec: string, options?: { requestId?: string; registry?: string }): Promise<ManagedResult>
14
15
  removeBundle(name: string): Promise<ManagedResult>
15
16
  cancelInstall(requestId: string): Promise<unknown>
16
17
  }
@@ -103,9 +104,12 @@ export function createOfficialDesktopRuntime(
103
104
  progress.cancelling = false
104
105
  let operation: Promise<ManagedResult>
105
106
  try {
107
+ // Read at each operation so region switches apply without a restart.
108
+ // The default route leaves the host's own npm configuration in charge.
109
+ const registry = routesFor(activeRegion()).npmRegistry
106
110
  operation = command === 'remove'
107
111
  ? manager.removeBundle(target)
108
- : manager.installBundle(spec, { requestId })
112
+ : manager.installBundle(spec, { requestId, ...(registry === DEFAULT_NPM_REGISTRY ? {} : { registry }) })
109
113
  } catch (error) {
110
114
  progress.active = false
111
115
  return Promise.resolve(failure(detail(error), 127))
package/src/profile.ts CHANGED
@@ -894,7 +894,8 @@ export function parsePatchRows(text: string): { names: string[]; ids: string[];
894
894
  // `insert:` indentation closes the block — those target OTHER plugins.
895
895
  let insertIndent: number | null = null
896
896
  // Indent of a `config:` key whose value is payload, not more rows.
897
- // Everything deeper than it is data, and its `id:` keys live in another
897
+ // Everything deeper than it — and a `- ` at its own column, a sequence
898
+ // YAML lets sit unindented under its key — is data, and its `id:` keys live in another
898
899
  // namespace (#841): a preset row writes `config.plugins[].id` for the
899
900
  // entries it will instantiate, so two bundles that instantiate the same
900
901
  // official plugins read as two bundles inserting the same entry ids —
@@ -947,17 +948,22 @@ export function parsePatchRows(text: string): { names: string[]; ids: string[];
947
948
  }
948
949
  return false
949
950
  }
950
- /** Whether the key at `keyIndent` holds a block sequence. */
951
+ /**
952
+ * Whether the key at `keyIndent` holds a block sequence. YAML lets that
953
+ * sequence sit at the key's own column (`config:` then `- id:` without
954
+ * further indent), so a `- ` there is the value, not a sibling.
955
+ */
951
956
  const valueIsList = (from: number, keyIndent: number): boolean => {
952
- for (let j = from + 1; j < rows.length; j++) {
953
- if (rows[j].indent <= keyIndent) return false
954
- return rows[j].trimmed === '-' || rows[j].trimmed.startsWith('- ')
955
- }
956
- return false
957
+ const next = from + 1
958
+ if (next >= rows.length || rows[next].indent < keyIndent) return false
959
+ return isDash(next)
957
960
  }
958
961
  for (let i = 0; i < rows.length; i++) {
959
962
  const { line, trimmed, indent } = rows[i]
960
- if (payloadIndent !== null && indent <= payloadIndent) payloadIndent = null
963
+ // A `- ` at the payload key's own column is still that key's value.
964
+ if (payloadIndent !== null && indent <= payloadIndent && !(indent === payloadIndent && isDash(i))) {
965
+ payloadIndent = null
966
+ }
961
967
  if (insertIndent !== null && indent <= insertIndent && !/^\s*-?\s*(id|name|config):/u.test(line)) {
962
968
  insertIndent = null
963
969
  }
package/src/trial.ts CHANGED
@@ -27,7 +27,7 @@
27
27
  import { existsSync, readFileSync } from 'node:fs'
28
28
  import { join } from 'node:path'
29
29
  import {
30
- buildBundleLayers, composeLayers, findDshInstallDir, parsePatchFile,
30
+ buildBundleLayers, composeLayers, duplicateEntryIdsAreFatal, findDshInstallDir, parsePatchFile,
31
31
  type DuplicateId, type LayerInput, type OrphanRow, type OverrideRow,
32
32
  } from './check.ts'
33
33
  import { resolveDshHome } from './home-paths.ts'
@@ -126,8 +126,15 @@ export function trialValidate(
126
126
  if (bundle.error !== null) errors.push({ layer: bundle.name, message: bundle.error })
127
127
  if (bundle.parseError !== null) errors.push({ layer: bundle.name, message: bundle.parseError })
128
128
  }
129
+ // Same version gate as the check report (#853): on a loader that folds one
130
+ // id into one entry (1.0.4+) this is an override, not a refusal. Making it
131
+ // fatal here is what rolled back every update for a profile that already
132
+ // had a package row and a user-patch row on one id.
133
+ const duplicateFatal = duplicateEntryIdsAreFatal(profileDir)
129
134
  for (const dup of composed.duplicates) {
130
- errors.push({ layer: dup.layers.join(' / '), message: `duplicate loader entry id ${JSON.stringify(dup.id)} (${dup.count} rows) / 重复的 loader 条目 id ${JSON.stringify(dup.id)}` })
135
+ const message = `duplicate loader entry id ${JSON.stringify(dup.id)} (${dup.count} rows) / 重复的 loader 条目 id ${JSON.stringify(dup.id)}`
136
+ if (duplicateFatal) errors.push({ layer: dup.layers.join(' / '), message })
137
+ else warnings.push({ layer: dup.layers.join(' / '), message: `${message} — 当前宿主会让后一层覆盖前一层,启动不受影响 / this host lets the later layer win, so the boot is unaffected` })
131
138
  }
132
139
  for (const orphan of composed.orphans) {
133
140
  warnings.push({ layer: orphan.layer, message: `${orphan.id}: ${orphan.reason}` })