@mohou/host 1.0.21 → 1.0.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/package.json +6 -6
- package/src/host/config.ts +16 -1
- package/src/http/bind.ts +4 -4
- package/src/http/reply.ts +5 -1
- package/src/http/updates.ts +20 -4
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@mohou/host",
|
|
3
|
-
"version": "1.0.
|
|
3
|
+
"version": "1.0.23",
|
|
4
4
|
"license": "MIT",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"engines": {
|
|
@@ -49,11 +49,11 @@
|
|
|
49
49
|
"react-dom": "^19.2.0",
|
|
50
50
|
"tailwindcss": "^4.3.3",
|
|
51
51
|
"tw-animate-css": "^1.4.0",
|
|
52
|
-
"@mohou/
|
|
53
|
-
"@mohou/
|
|
54
|
-
"@mohou/
|
|
55
|
-
"@mohou/
|
|
56
|
-
"@mohou/
|
|
52
|
+
"@mohou/contract": "^1.0.23",
|
|
53
|
+
"@mohou/app-view": "^1.0.23",
|
|
54
|
+
"@mohou/mcp-client": "^1.0.23",
|
|
55
|
+
"@mohou/ui": "^1.0.23",
|
|
56
|
+
"@mohou/runtime-provider": "^1.0.23"
|
|
57
57
|
},
|
|
58
58
|
"devDependencies": {
|
|
59
59
|
"@types/better-sqlite3": "^9.6.0",
|
package/src/host/config.ts
CHANGED
|
@@ -25,6 +25,12 @@ export interface HostPolicy {
|
|
|
25
25
|
}
|
|
26
26
|
/** Absent, or `default`, means the builtin library. Not required to boot. */
|
|
27
27
|
defaultWorkbenchId?: string
|
|
28
|
+
/**
|
|
29
|
+
* Registry for this product's own update check and install, when the owner wants a mirror.
|
|
30
|
+
* Absent or empty follows the install prefix's build-time value. Our own npm children only:
|
|
31
|
+
* `~/.npmrc` and an app's own dependency installs are untouched.
|
|
32
|
+
*/
|
|
33
|
+
updateRegistry?: string
|
|
28
34
|
}
|
|
29
35
|
|
|
30
36
|
/** Seed numbers and names. Host policy, not a locked product value. */
|
|
@@ -44,7 +50,7 @@ export interface PortBound {
|
|
|
44
50
|
export const DEFAULT_PORT_BOUND: PortBound = { min: 1024, max: 65535 }
|
|
45
51
|
|
|
46
52
|
const PUBLIC_KEYS = ['runtimeRoot', 'hostPort', 'theme', 'palette', 'locale', 'chatLanguage', 'llm', 'runtimeProvider'] as const
|
|
47
|
-
const OPTIONAL_KEYS = ['defaultWorkbenchId'] as const
|
|
53
|
+
const OPTIONAL_KEYS = ['defaultWorkbenchId', 'updateRegistry'] as const
|
|
48
54
|
|
|
49
55
|
/**
|
|
50
56
|
* Read `host.json`. A missing file writes a complete seed. A present bad file throws and is left as it was.
|
|
@@ -183,9 +189,18 @@ function admitPolicy(raw: unknown, runtimeRoot: string, ports: PortBound): HostP
|
|
|
183
189
|
llm: admitLlm(raw.llm),
|
|
184
190
|
runtimeProvider: admitProvider(raw.runtimeProvider),
|
|
185
191
|
...admitWorkbench(raw.defaultWorkbenchId),
|
|
192
|
+
...admitUpdateRegistry(raw.updateRegistry),
|
|
186
193
|
}
|
|
187
194
|
}
|
|
188
195
|
|
|
196
|
+
/** An empty value means the packaged default, so it is not written at all. */
|
|
197
|
+
function admitUpdateRegistry(value: unknown): { updateRegistry: string } | Record<string, never> {
|
|
198
|
+
if (value === undefined) return {}
|
|
199
|
+
if (typeof value !== 'string') throw new ConfigError('config-invalid', 'host config updateRegistry is invalid')
|
|
200
|
+
const registry = value.trim()
|
|
201
|
+
return registry.length === 0 ? {} : { updateRegistry: registry }
|
|
202
|
+
}
|
|
203
|
+
|
|
189
204
|
function admitWorkbench(value: unknown): { defaultWorkbenchId: string } | Record<string, never> {
|
|
190
205
|
if (value === undefined || value === 'default') return {}
|
|
191
206
|
if (typeof value !== 'string' || value.length === 0) {
|
package/src/http/bind.ts
CHANGED
|
@@ -15,7 +15,7 @@ import { readVendorFile } from './ports.ts'
|
|
|
15
15
|
import type { LoopbackPorts } from './ports.ts'
|
|
16
16
|
import { authoringTokenMatches } from './guard.ts'
|
|
17
17
|
import { probeBrain, type HostPolicy } from '../host/config.ts'
|
|
18
|
-
import { checkPackageUpdate, discardUpdateResult, readUpdateResult, readUpdateSource, stagePackageUpdate } from './updates.ts'
|
|
18
|
+
import { checkPackageUpdate, discardUpdateResult, readUpdateResult, readUpdateSource, stagePackageUpdate, updateEnv } from './updates.ts'
|
|
19
19
|
import { McpError } from '@mohou/mcp-client'
|
|
20
20
|
|
|
21
21
|
import { admitMcpText } from '../host/mcp-import.ts'
|
|
@@ -147,16 +147,16 @@ export function bindLoopback(input: {
|
|
|
147
147
|
subscribeHost: listener => input.author.hostEvents.subscribe(listener),
|
|
148
148
|
subscribeFrames: listener => forwardFrames(input.author.appEvents, listener),
|
|
149
149
|
subscribeApp: (appId, since, listener) => input.author.appEvents.subscribe(appId, since, listener),
|
|
150
|
-
updateSource: () => Promise.resolve(readUpdateSource()),
|
|
150
|
+
updateSource: () => Promise.resolve(readUpdateSource(updateEnv(input.policy()))),
|
|
151
151
|
checkUpdate: async () => {
|
|
152
152
|
const attempt = readUpdateResult(input.runtimeRoot)
|
|
153
153
|
return {
|
|
154
|
-
...await checkPackageUpdate(),
|
|
154
|
+
...await checkPackageUpdate(undefined, updateEnv(input.policy())),
|
|
155
155
|
...attempt === undefined ? {} : { lastAttempt: attempt },
|
|
156
156
|
}
|
|
157
157
|
},
|
|
158
158
|
installUpdate: (version) => {
|
|
159
|
-
stagePackageUpdate(version)
|
|
159
|
+
stagePackageUpdate(version, updateEnv(input.policy()))
|
|
160
160
|
if (input.restart === undefined) return Promise.resolve()
|
|
161
161
|
const restart = input.restart
|
|
162
162
|
setTimeout(() => {
|
package/src/http/reply.ts
CHANGED
|
@@ -30,11 +30,15 @@ export function isRecord(value: unknown): value is Record<string, unknown> {
|
|
|
30
30
|
export function mergePolicy(current: unknown, body: Record<string, unknown>): Record<string, unknown> {
|
|
31
31
|
const base = isRecord(current) ? current : {}
|
|
32
32
|
const runtimeRoot = typeof base.runtimeRoot === 'string' ? base.runtimeRoot : undefined
|
|
33
|
+
const { updateRegistry: rawRegistry, ...rest } = body
|
|
34
|
+
const updateRegistry = typeof rawRegistry === 'string' ? rawRegistry.trim() : undefined
|
|
33
35
|
const provider = isRecord(body.runtimeProvider) ? body.runtimeProvider : undefined
|
|
34
36
|
const previous = isRecord(base.runtimeProvider) ? base.runtimeProvider : undefined
|
|
35
37
|
return {
|
|
36
38
|
...base,
|
|
37
|
-
...
|
|
39
|
+
...rest,
|
|
40
|
+
// An empty string means "the packaged default". A non-string never clobbers the stored value.
|
|
41
|
+
...updateRegistry === undefined ? {} : { updateRegistry },
|
|
38
42
|
...runtimeRoot === undefined ? {} : { runtimeRoot },
|
|
39
43
|
...provider === undefined || previous === undefined ? {} : {
|
|
40
44
|
runtimeProvider: {
|
package/src/http/updates.ts
CHANGED
|
@@ -5,6 +5,12 @@ import path from 'node:path'
|
|
|
5
5
|
import { homePackagesDir, hostUpdateResultPath } from '../host/layout.ts'
|
|
6
6
|
import { aboutInfo } from './ports.ts'
|
|
7
7
|
|
|
8
|
+
/**
|
|
9
|
+
* Ask the install channel once. A dev tree with no prefix still asks the registry and cannot install.
|
|
10
|
+
* The budget is host policy, not a locked number: a mirror's first response is often slow.
|
|
11
|
+
*/
|
|
12
|
+
export const updateCheckTimeoutMs = 8_000
|
|
13
|
+
|
|
8
14
|
/** Why an install did not finish. The panel owns the wording of each code. */
|
|
9
15
|
export const updateFailureCodes = ['timeout', 'exit', 'prepare', 'closed', 'leftover', 'verify', 'boot'] as const
|
|
10
16
|
|
|
@@ -91,7 +97,7 @@ export async function checkPackageUpdate(
|
|
|
91
97
|
const name = prefix === undefined ? about.name : '@mohou/shell'
|
|
92
98
|
try {
|
|
93
99
|
const response = await fetch(`${registry.replace(/\/$/, '')}/${name}/latest`, {
|
|
94
|
-
signal: AbortSignal.timeout(
|
|
100
|
+
signal: AbortSignal.timeout(updateCheckTimeoutMs),
|
|
95
101
|
})
|
|
96
102
|
if (!response.ok) return {
|
|
97
103
|
...empty,
|
|
@@ -109,7 +115,8 @@ export async function checkPackageUpdate(
|
|
|
109
115
|
channel: prefix?.channel ?? 'registry',
|
|
110
116
|
...prefix === undefined ? {} : { registry: prefix.registry ?? 'https://registry.npmjs.org' },
|
|
111
117
|
installable: prefix !== undefined,
|
|
112
|
-
|
|
118
|
+
// Newer only. A mirror syncs late, and an older `latest` is not an update.
|
|
119
|
+
updateAvailable: latest !== null && compareVersion(latest, about.current) > 0,
|
|
113
120
|
}
|
|
114
121
|
} catch (error) {
|
|
115
122
|
return {
|
|
@@ -122,10 +129,16 @@ export async function checkPackageUpdate(
|
|
|
122
129
|
}
|
|
123
130
|
}
|
|
124
131
|
|
|
132
|
+
export function updateEnv(policy: { readonly updateRegistry?: string }, env: NodeJS.ProcessEnv = process.env): NodeJS.ProcessEnv {
|
|
133
|
+
const registry = policy.updateRegistry?.trim()
|
|
134
|
+
return registry === undefined || registry.length === 0 ? env : { ...env, MINI_APP_NPM_REGISTRY: registry }
|
|
135
|
+
}
|
|
136
|
+
|
|
125
137
|
/**
|
|
126
138
|
* Where this install would update from. Reads the install prefix; asks no registry, so a
|
|
127
139
|
* panel can show the source while the network is slow or down.
|
|
128
|
-
* @param env - process environment; `MINI_APP_TARBALL_DIR` overrides the package folder
|
|
140
|
+
* @param env - process environment; `MINI_APP_TARBALL_DIR` overrides the package folder, and
|
|
141
|
+
* `MINI_APP_NPM_REGISTRY` overrides the prefix's registry (that is how the policy reaches us)
|
|
129
142
|
* @param cwd - directory the sidecar runs in
|
|
130
143
|
*/
|
|
131
144
|
export function readUpdateSource(env: NodeJS.ProcessEnv = process.env, cwd = process.cwd()): UpdateSource {
|
|
@@ -272,10 +285,13 @@ function readPrefixUpdate(cwd: string, env: NodeJS.ProcessEnv): PrefixUpdate | u
|
|
|
272
285
|
const packages = Object.keys(parsed.dependencies ?? {}).filter(name => name.startsWith('@mohou/'))
|
|
273
286
|
const override = env.MINI_APP_TARBALL_DIR
|
|
274
287
|
const tarballDir = override !== undefined && override.length > 0 ? override : parsed.mohou?.tarballDir
|
|
288
|
+
const registry = env.MINI_APP_NPM_REGISTRY?.trim()
|
|
275
289
|
return {
|
|
276
290
|
dir,
|
|
277
291
|
channel,
|
|
278
|
-
...
|
|
292
|
+
...registry !== undefined && registry.length > 0
|
|
293
|
+
? { registry }
|
|
294
|
+
: typeof parsed.mohou?.registry === 'string' ? { registry: parsed.mohou.registry } : {},
|
|
279
295
|
...tarballDir === undefined ? {} : { tarballDir },
|
|
280
296
|
packages: packages.length === 0 ? ['@mohou/shell'] : packages,
|
|
281
297
|
}
|