dsh-tiddlywiki 0.30.49 → 0.30.50
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/docs/CHANGELOG.md +1 -0
- package/lib/index.js +13679 -13643
- package/lib/index.js.map +1 -1
- package/package.json +1 -1
- package/src/index-startup.ts +202 -0
- package/src/index.ts +53 -99
package/package.json
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "dsh-tiddlywiki",
|
|
3
3
|
"description": "把 TiddlyWiki 5 变成你和 Agent 共用的本地文档中心(纯文本 + git):你在 DSH 界面里阅读、写作、记日记,Agent 用 15 个 tiddlywiki_* 工具读写同一批笔记;每条笔记都是你能读、能改、能审计、能随时带走的文件——不是自动记忆插件,不做后台抓取。可选一键发布到微信公众号草稿箱。 Turn TiddlyWiki 5 into a document center you and your agent share, in plain text under git — a knowledge base you can read, edit, audit and take with you, not an auto-memory plugin. Mounts via the official dsh plugin system; no DSH source changes.",
|
|
4
|
-
"version": "0.30.
|
|
4
|
+
"version": "0.30.50",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"keywords": [
|
|
7
7
|
"dsh",
|
|
@@ -0,0 +1,202 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* dsh-tiddlywiki — host half: the STARTUP stage (v0.30.50).
|
|
3
|
+
*
|
|
4
|
+
* `apply()` used to inline this ~100-line task between the git layer and the
|
|
5
|
+
* wiki views. It is a STAGE, not wiring: it runs exactly once, in a fixed order,
|
|
6
|
+
* and every step after the first await is followed by a `disposed` check.
|
|
7
|
+
*
|
|
8
|
+
* WHAT STAYS IN THE ENTRY POINT
|
|
9
|
+
* -----------------------------
|
|
10
|
+
* - `disposed` — its owner is the teardown effect (which sets it) AND the
|
|
11
|
+
* startup task (which checks it after every await). The task therefore gets it
|
|
12
|
+
* as an **accessor** (`isDisposed`) and, when it notices teardown, stops the
|
|
13
|
+
* child ITSELF (v0.19.1: otherwise the disposer finishes first and the task
|
|
14
|
+
* then spawns an orphan TW child / binds a leaked listener).
|
|
15
|
+
* - `farm` — created BY this task, but owned by the whole plugin (routes, tools,
|
|
16
|
+
* wiki views all read it). So the task creates it through a `setFarm` callback
|
|
17
|
+
* instead of holding it: the entry point stays the single owner of the mutable
|
|
18
|
+
* state, exactly as `index-wikis.ts` / `index-routes.ts` do it.
|
|
19
|
+
* - The control-file cache (`controlRegistry` / `controlSource` /
|
|
20
|
+
* `controlWarnings` / `controlError`) and the session scopes are filled by this
|
|
21
|
+
* task but read everywhere else ⇒ same pattern, one `setControl*` each.
|
|
22
|
+
*
|
|
23
|
+
* ORDER IS SEMANTICS (do not reorder):
|
|
24
|
+
* 1. install the 「拆分知识库」 skill — deliberately BEFORE the wiki, because it
|
|
25
|
+
* must still happen when the wiki is slow, fails, or the config is broken.
|
|
26
|
+
* (It used to sit after `farm.startAll()`: a wiki that never became ready
|
|
27
|
+
* also silently never installed the skill. Found by restarting a real host.)
|
|
28
|
+
* 2. read the CONTROL FILE (mode + list + default id) — it must be read before
|
|
29
|
+
* anything starts, which is why it lives outside every wiki.
|
|
30
|
+
* 3. read the per-session scopes (a preference: an unreadable file just means
|
|
31
|
+
* "no explicit scope").
|
|
32
|
+
* 4. build the farm and `startAll()` — each instance brings ITSELF up (child →
|
|
33
|
+
* its config tiddler → seeds → git → committer); one wiki failing never
|
|
34
|
+
* stops the others.
|
|
35
|
+
* 5. `applyPrompt()` — the prompt section is a plugin-level singleton and the
|
|
36
|
+
* default wiki's config tiddler has only just been loaded.
|
|
37
|
+
* 6. bind the clip bridge once, on the DEFAULT wiki's configured port (serving
|
|
38
|
+
* while disabled is intentional: every request re-checks the effective flag,
|
|
39
|
+
* so the settings toggle works without a dsh web restart).
|
|
40
|
+
*
|
|
41
|
+
* @module dsh-tiddlywiki/index-startup
|
|
42
|
+
*/
|
|
43
|
+
import type { GitFace } from './host/git.ts'
|
|
44
|
+
import type { RepoCommitters } from './host/repo-committers.ts'
|
|
45
|
+
import type { BridgeConfig } from './host/clip-bridge.ts'
|
|
46
|
+
import { installSplitSkill } from './host/skill-install.ts'
|
|
47
|
+
import { readSessionScopes } from './host/session-scope.ts'
|
|
48
|
+
import { readLocationState, type WikiLocation } from './host/wiki-location.ts'
|
|
49
|
+
import { WikiInstance, type WikiInstanceBase } from './host/wiki-instance.ts'
|
|
50
|
+
import { WikiFarm } from './host/wiki-farm.ts'
|
|
51
|
+
import { resolveTwRoot } from './host/admin.ts'
|
|
52
|
+
import {
|
|
53
|
+
DEFAULT_WIKI_ID,
|
|
54
|
+
DEFAULT_WIKI_MODE,
|
|
55
|
+
readRegistry,
|
|
56
|
+
singleEntryRegistry,
|
|
57
|
+
type WikiEntry,
|
|
58
|
+
type WikiRegistry,
|
|
59
|
+
} from './host/wiki-registry.ts'
|
|
60
|
+
|
|
61
|
+
/** Where the control-file read landed (the settings page reads this back). */
|
|
62
|
+
export interface ControlFileState {
|
|
63
|
+
registry: WikiRegistry | undefined
|
|
64
|
+
source: 'file' | 'legacy' | 'default'
|
|
65
|
+
warnings: string[]
|
|
66
|
+
error: string | undefined
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
export interface StartupStageDeps {
|
|
70
|
+
/** The resolved cordis config, as every wiki's base layer. */
|
|
71
|
+
config: WikiInstanceBase
|
|
72
|
+
/** One `GitFace` for the whole plugin. */
|
|
73
|
+
git: GitFace
|
|
74
|
+
/** One commit layer for the whole plugin (keyed by repository). */
|
|
75
|
+
repos: RepoCommitters
|
|
76
|
+
/** The cordis-level default location (last resort in the registry chain). */
|
|
77
|
+
defaultLocation: WikiLocation
|
|
78
|
+
/** Pointer file the LEGACY single-wiki switch persists. */
|
|
79
|
+
locationStateFile: string
|
|
80
|
+
/** The multi-wiki control file: mode + wiki list + default id. */
|
|
81
|
+
registryFile: string
|
|
82
|
+
/** File the per-session scopes live in (outside every wiki). */
|
|
83
|
+
sessionScopeFile: string
|
|
84
|
+
/** The same-origin proxy base a wiki's TW frontend must use. */
|
|
85
|
+
proxyBase: (entry: WikiEntry) => string
|
|
86
|
+
/** The plugin has begun tearing down (checked after EVERY await below). */
|
|
87
|
+
isDisposed: () => boolean
|
|
88
|
+
/** Hand the newly built farm back to the entry point (its single owner). */
|
|
89
|
+
setFarm: (farm: WikiFarm<WikiInstance> | undefined) => void
|
|
90
|
+
/** Publish the control-file read (the settings page reads it back). */
|
|
91
|
+
setControl: (state: ControlFileState) => void
|
|
92
|
+
/** Publish the session scopes loaded from disk. */
|
|
93
|
+
setSessionScopes: (scopes: Record<string, string>) => void
|
|
94
|
+
/** Rebuild the plugin-level prompt section once the default config is loaded. */
|
|
95
|
+
applyPrompt: () => void
|
|
96
|
+
/** The clip bridge surface (bound here, owned by the entry point). */
|
|
97
|
+
clipBridge: { start: (port: number) => Promise<void>; stop: () => Promise<void>; port: number }
|
|
98
|
+
/** The EFFECTIVE bridge config (per-wiki override else the cordis base). */
|
|
99
|
+
effectiveBridge: () => BridgeConfig
|
|
100
|
+
/** The wiki's own location surface — only `defaultCurrentLocation` is read here. */
|
|
101
|
+
defaultCurrentLocation: () => WikiLocation
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
/**
|
|
105
|
+
* Build the fire-and-forget startup task. It resolves when startup has either
|
|
106
|
+
* finished or given up; failures are logged and self-healing stays armed.
|
|
107
|
+
*/
|
|
108
|
+
export function createStartupStage(deps: StartupStageDeps): { start: () => Promise<void> } {
|
|
109
|
+
/** single 模式的位置:旧指针文件 > cordis 默认(v0.22.0 的优先级,逐字保留)。 */
|
|
110
|
+
const singleModeLocation = async (): Promise<WikiLocation> => {
|
|
111
|
+
const state = await readLocationState(deps.locationStateFile)
|
|
112
|
+
if (state.error !== undefined) console.warn('[dsh-tiddlywiki]', state.error)
|
|
113
|
+
return state.active ?? deps.defaultLocation
|
|
114
|
+
}
|
|
115
|
+
|
|
116
|
+
const start = async (): Promise<void> => {
|
|
117
|
+
try {
|
|
118
|
+
/**
|
|
119
|
+
* Ship the 「拆分知识库」 skill FIRST (v0.28.0).
|
|
120
|
+
*
|
|
121
|
+
* It is deliberately independent of the wiki: it only writes a file into
|
|
122
|
+
* `$DSH_HOME/skills`, and it must still happen when the wiki is slow to
|
|
123
|
+
* start, fails to start, or the user is mid-migration with a broken config.
|
|
124
|
+
* A plugin cannot register a skill ROOT, so writing into the user's own root
|
|
125
|
+
* is the only zero-config path; it only ever touches its own file (the
|
|
126
|
+
* marker line decides — see host/skill-install.ts).
|
|
127
|
+
*/
|
|
128
|
+
try {
|
|
129
|
+
const installed = await installSplitSkill()
|
|
130
|
+
if (installed.action === 'failed') console.warn('[dsh-tiddlywiki] 拆库 skill 安装失败:', installed.error)
|
|
131
|
+
else console.info(`[dsh-tiddlywiki] 拆库 skill:${installed.action}(${installed.path})`)
|
|
132
|
+
} catch (err) {
|
|
133
|
+
console.warn('[dsh-tiddlywiki] 拆库 skill 安装异常:', err)
|
|
134
|
+
}
|
|
135
|
+
// THE CONTROL FILE FIRST (v0.28.0): it carries the mode (single/multi), the
|
|
136
|
+
// wiki list and the default id, and it must be read before anything starts
|
|
137
|
+
// — which is exactly why it lives OUTSIDE every wiki (wiki-registry.ts).
|
|
138
|
+
// The control file decides: mode + the wiki list.
|
|
139
|
+
const read = await readRegistry({ file: deps.registryFile, legacyFile: deps.locationStateFile, fallback: deps.defaultLocation })
|
|
140
|
+
// Per-session scopes are a PREFERENCE: an unreadable file just means "no
|
|
141
|
+
// explicit scope" and every session falls back to the default wiki.
|
|
142
|
+
const scopes = await readSessionScopes(deps.sessionScopeFile)
|
|
143
|
+
if (scopes.error !== undefined) console.warn('[dsh-tiddlywiki]', scopes.error)
|
|
144
|
+
deps.setSessionScopes(scopes.scopes)
|
|
145
|
+
deps.setControl({ registry: read.registry, source: read.source, warnings: read.warnings, error: read.error })
|
|
146
|
+
if (read.error !== undefined) console.warn('[dsh-tiddlywiki]', read.error)
|
|
147
|
+
for (const warning of read.warnings) console.warn('[dsh-tiddlywiki]', warning)
|
|
148
|
+
console.info(`[dsh-tiddlywiki] ${read.registry.mode} 模式 · ${read.registry.wikis.length} 个知识库(来源:${read.source})`)
|
|
149
|
+
// single 模式 = 今天的行为,逐字保留:位置仍由旧指针文件(其次 cordis 默认)
|
|
150
|
+
// 决定,registry 此时只记住 `mode` 与候选列表 —— 所以这里合成一份单条清单。
|
|
151
|
+
const runRegistry = read.registry.mode === 'multi'
|
|
152
|
+
? read.registry
|
|
153
|
+
: singleEntryRegistry(await singleModeLocation(), DEFAULT_WIKI_ID, DEFAULT_WIKI_MODE)
|
|
154
|
+
const farm = new WikiFarm<WikiInstance>(runRegistry, {
|
|
155
|
+
createRuntime: (entry) => new WikiInstance({
|
|
156
|
+
entry,
|
|
157
|
+
base: deps.config,
|
|
158
|
+
git: deps.git,
|
|
159
|
+
twRoot: resolveTwRoot,
|
|
160
|
+
touchCommit: (dir) => { void deps.repos.touch(dir) },
|
|
161
|
+
flushCommits: (dir) => deps.repos.flush(dir),
|
|
162
|
+
proxyBase: () => deps.proxyBase(entry),
|
|
163
|
+
}),
|
|
164
|
+
log: (message) => console.warn('[dsh-tiddlywiki]', message),
|
|
165
|
+
})
|
|
166
|
+
deps.setFarm(farm)
|
|
167
|
+
// Every instance brings ITSELF up (child → its config tiddler → seeds →
|
|
168
|
+
// git → committer), so this one call replaces the old step-by-step startup.
|
|
169
|
+
// One wiki failing never stops the others (the farm reports it).
|
|
170
|
+
const change = await farm.startAll()
|
|
171
|
+
if (change.started.length > 0) console.info(`[dsh-tiddlywiki] 已启动:${change.started.join(', ')}`)
|
|
172
|
+
// The prompt section is a plugin-level singleton: rebuild it now that the
|
|
173
|
+
// default wiki's config tiddler has been loaded.
|
|
174
|
+
deps.applyPrompt()
|
|
175
|
+
if (deps.isDisposed()) {
|
|
176
|
+
await farm.disposeAll()
|
|
177
|
+
return
|
|
178
|
+
}
|
|
179
|
+
// Clip bridge: bind once on the DEFAULT wiki's configured port (works even
|
|
180
|
+
// while disabled — every request re-checks the effective enabled flag, so
|
|
181
|
+
// the settings-page toggle applies without a dsh web restart).
|
|
182
|
+
try {
|
|
183
|
+
await deps.clipBridge.start(deps.effectiveBridge().port)
|
|
184
|
+
console.info(`[dsh-tiddlywiki] clip bridge listening on 127.0.0.1:${deps.clipBridge.port} (enabled=${deps.effectiveBridge().enabled})`)
|
|
185
|
+
} catch (err) {
|
|
186
|
+
console.warn('[dsh-tiddlywiki] clip bridge start:', err)
|
|
187
|
+
}
|
|
188
|
+
if (deps.isDisposed()) {
|
|
189
|
+
try { await deps.clipBridge.stop() } catch { /* already closing */ }
|
|
190
|
+
await farm.disposeAll()
|
|
191
|
+
return
|
|
192
|
+
}
|
|
193
|
+
} catch (err) {
|
|
194
|
+
console.warn('[dsh-tiddlywiki] startup issue (self-healing is armed):', err)
|
|
195
|
+
}
|
|
196
|
+
}
|
|
197
|
+
|
|
198
|
+
return { start }
|
|
199
|
+
}
|
|
200
|
+
|
|
201
|
+
/** Re-exported so the entry point keeps one import for the registry types. */
|
|
202
|
+
export type { WikiRegistry }
|
package/src/index.ts
CHANGED
|
@@ -21,20 +21,24 @@
|
|
|
21
21
|
import { AutoCommitter, GitFace } from './host/git.ts'
|
|
22
22
|
import { type SessionsFace, type WebServerFace } from './host/routes.ts'
|
|
23
23
|
import { type PluginConfigShape } from './host/config.ts'
|
|
24
|
-
import { resolveTwRoot } from './host/admin.ts'
|
|
25
24
|
import { TiddlyWebClient, isBinaryType, TEXT_LIST_FILTER } from './host/tw-api.ts'
|
|
26
25
|
import { WechatPublishRunner } from './host/wechat-publish.ts'
|
|
27
26
|
import { registerTiddlywikiTools, type ToolsDeps, type ToolScope } from './host/tools.ts'
|
|
28
27
|
import {
|
|
29
28
|
defaultLocationStateFile,
|
|
30
29
|
locationPath,
|
|
30
|
+
// v0.30.49 / v0.30.50:readLocationState / readRegistry **只**被下面的 barrel
|
|
31
|
+
// 再导出使用(`export { … } from '…'` 是独立语句、不消费 import 绑定)⇒
|
|
32
|
+
// `noUnusedLocals` 把它们点成「未使用」。**必须无视这条 error 并留着**:
|
|
33
|
+
// 删掉它们会让 lib/index.js 少一个导出(v0.30.48/v0.30.50 各实测一次 ——
|
|
34
|
+
// typecheck / verify:unit / verify:static 全绿,只有 e2e 才崩)。
|
|
35
|
+
// 判据:scripts/verify-index-barrel.mjs(进 verify:static)。
|
|
31
36
|
readLocationState,
|
|
32
37
|
type WikiLocation,
|
|
33
38
|
} from './host/wiki-location.ts'
|
|
34
39
|
import { WikiInstance } from './host/wiki-instance.ts'
|
|
35
40
|
import { WikiFarm, resolveAgentScope, targetRuntimeFor } from './host/wiki-farm.ts'
|
|
36
|
-
import { defaultSessionScopeFile
|
|
37
|
-
import { installSplitSkill } from './host/skill-install.ts'
|
|
41
|
+
import { defaultSessionScopeFile } from './host/session-scope.ts'
|
|
38
42
|
import {
|
|
39
43
|
DEFAULT_WIKI_ID,
|
|
40
44
|
DEFAULT_WIKI_MODE,
|
|
@@ -56,6 +60,7 @@ import { resolveConfig, type ResolvedConfig, type TiddlywikiConfig } from './ind
|
|
|
56
60
|
import { createMutationLock } from './host/mutation-lock.ts'
|
|
57
61
|
import { createGitLayer } from './index-git.ts'
|
|
58
62
|
import { createRouteStage } from './index-routes.ts'
|
|
63
|
+
import { createStartupStage } from './index-startup.ts'
|
|
59
64
|
import { createPromptSurface, createServerTuning } from './index-prompt.ts'
|
|
60
65
|
import { createClipSurface } from './index-clip.ts'
|
|
61
66
|
|
|
@@ -161,7 +166,6 @@ export {
|
|
|
161
166
|
listWikiCandidates,
|
|
162
167
|
locationPath,
|
|
163
168
|
normalizeLocation,
|
|
164
|
-
readLocationState,
|
|
165
169
|
// v0.30.49:这条 **barrel 再导出** 是给 `lib/index.js` 的**外部**使用者的
|
|
166
170
|
// (scripts/verify-wiki-switch.mjs 就 import 它)。⚠️ 再导出是**独立语句**,
|
|
167
171
|
// 它不消费上面那条 import 的绑定 —— 所以 `noUnusedLocals` 会把 import 里的
|
|
@@ -181,6 +185,15 @@ export {
|
|
|
181
185
|
type WikiLocationSource,
|
|
182
186
|
type WikiLocationState,
|
|
183
187
|
} from './host/wiki-location.ts'
|
|
188
|
+
// v0.30.49 / v0.30.50 —— **为什么这两条单独写在 `from` 形式之外**:
|
|
189
|
+
// 同一个名字既要在 `lib/index.js` 上存在(barrel 公共面),又会让
|
|
190
|
+
// `export { X } from '…'` 那种写法**不消费上面 import 的绑定** ⇒
|
|
191
|
+
// `noUnusedLocals` 报「声明了但没读」,而删掉 import 就静默少一个导出
|
|
192
|
+
// (v0.30.48 / v0.30.50 各踩一次;只有 e2e 能发现)。
|
|
193
|
+
// 用「无 from 的 export」= **真的读一次那个绑定**,两个要求同时满足:
|
|
194
|
+
// 绑定被消费、导出照样在。判据仍是 scripts/verify-index-barrel.mjs。
|
|
195
|
+
export { readLocationState }
|
|
196
|
+
export { readRegistry }
|
|
184
197
|
export { switchWiki, type WikiSwitchResult, type WikiSwitchDeps } from './host/wiki-switch.ts'
|
|
185
198
|
export {
|
|
186
199
|
DEFAULT_WIKI_ID,
|
|
@@ -199,7 +212,6 @@ export {
|
|
|
199
212
|
normalizeEntry,
|
|
200
213
|
normalizeWikiIcon,
|
|
201
214
|
normalizeWikiId,
|
|
202
|
-
readRegistry,
|
|
203
215
|
removeWiki,
|
|
204
216
|
singleEntryRegistry,
|
|
205
217
|
upsertWiki,
|
|
@@ -525,12 +537,37 @@ export function apply(ctx: HostCtx, rawConfig: TiddlywikiConfig = {}): void {
|
|
|
525
537
|
*/
|
|
526
538
|
let switching = false
|
|
527
539
|
|
|
528
|
-
|
|
529
|
-
|
|
530
|
-
|
|
531
|
-
|
|
532
|
-
|
|
533
|
-
|
|
540
|
+
// ── Startup stage (v0.30.50) ──────────────────────────────────────────────
|
|
541
|
+
// 整个自举任务(skill → 控制文件 → 会话作用域 → 建 farm → startAll →
|
|
542
|
+
// prompt → 剪藏桥)搬进 index-startup.ts。`disposed` 与 `farm` 仍归本文件
|
|
543
|
+
// (前者是拆卸 effect 与这个任务的共同主人,后者路由/工具/视图都要读),
|
|
544
|
+
// 所以它们以**回调**形式传进去 —— 与 index-wikis / index-routes 同一手法。
|
|
545
|
+
//
|
|
546
|
+
// ⚠️ 顺序本身是语义,见 index-startup.ts 的模块头(尤其是「skill 必须在
|
|
547
|
+
// wiki 之前装」那条,它是真实事故的产物)。
|
|
548
|
+
const startupStage = createStartupStage({
|
|
549
|
+
config,
|
|
550
|
+
git,
|
|
551
|
+
repos,
|
|
552
|
+
defaultLocation,
|
|
553
|
+
locationStateFile,
|
|
554
|
+
registryFile,
|
|
555
|
+
sessionScopeFile,
|
|
556
|
+
proxyBase,
|
|
557
|
+
isDisposed: () => disposed,
|
|
558
|
+
setFarm: (next) => { farm = next },
|
|
559
|
+
setControl: (state) => {
|
|
560
|
+
controlRegistry = state.registry
|
|
561
|
+
controlSource = state.source
|
|
562
|
+
controlWarnings = state.warnings
|
|
563
|
+
controlError = state.error
|
|
564
|
+
},
|
|
565
|
+
setSessionScopes: (scopes) => { sessionScopes = scopes },
|
|
566
|
+
applyPrompt,
|
|
567
|
+
clipBridge,
|
|
568
|
+
effectiveBridge,
|
|
569
|
+
defaultCurrentLocation: () => wikiViews.defaultCurrentLocation(),
|
|
570
|
+
})
|
|
534
571
|
|
|
535
572
|
/**
|
|
536
573
|
* The CONTROL FILE as the settings page edits it — deliberately NOT the
|
|
@@ -546,93 +583,10 @@ export function apply(ctx: HostCtx, rawConfig: TiddlywikiConfig = {}): void {
|
|
|
546
583
|
const controlRegistryNow = (): WikiRegistry =>
|
|
547
584
|
controlRegistry ?? singleEntryRegistry(defaultCurrentLocation(), DEFAULT_WIKI_ID, DEFAULT_WIKI_MODE)
|
|
548
585
|
|
|
549
|
-
|
|
550
|
-
|
|
551
|
-
|
|
552
|
-
|
|
553
|
-
*
|
|
554
|
-
* It is deliberately independent of the wiki: it only writes a file into
|
|
555
|
-
* `$DSH_HOME/skills`, and it must still happen when the wiki is slow to
|
|
556
|
-
* start, fails to start, or the user is mid-migration with a broken config.
|
|
557
|
-
* (It used to sit after `farm.startAll()` — which meant a wiki that never
|
|
558
|
-
* became ready also silently never installed the skill. Found by restarting
|
|
559
|
-
* a real host, not by a test.)
|
|
560
|
-
*
|
|
561
|
-
* A plugin cannot register a skill ROOT, so writing into the user's own root
|
|
562
|
-
* is the only zero-config path; it only ever touches its own file (the
|
|
563
|
-
* marker line decides — see host/skill-install.ts).
|
|
564
|
-
*/
|
|
565
|
-
try {
|
|
566
|
-
const installed = await installSplitSkill()
|
|
567
|
-
if (installed.action === 'failed') console.warn('[dsh-tiddlywiki] 拆库 skill 安装失败:', installed.error)
|
|
568
|
-
else console.info(`[dsh-tiddlywiki] 拆库 skill:${installed.action}(${installed.path})`)
|
|
569
|
-
} catch (err) {
|
|
570
|
-
console.warn('[dsh-tiddlywiki] 拆库 skill 安装异常:', err)
|
|
571
|
-
}
|
|
572
|
-
// THE CONTROL FILE FIRST (v0.28.0): it carries the mode (single/multi), the
|
|
573
|
-
// wiki list and the default id, and it must be read before anything starts
|
|
574
|
-
// — which is exactly why it lives OUTSIDE every wiki (wiki-registry.ts).
|
|
575
|
-
// The control file decides: mode + the wiki list.
|
|
576
|
-
const read = await readRegistry({ file: registryFile, legacyFile: locationStateFile, fallback: defaultLocation })
|
|
577
|
-
// Per-session scopes are a PREFERENCE: an unreadable file just means "no
|
|
578
|
-
// explicit scope" and every session falls back to the default wiki.
|
|
579
|
-
const scopes = await readSessionScopes(sessionScopeFile)
|
|
580
|
-
if (scopes.error !== undefined) console.warn('[dsh-tiddlywiki]', scopes.error)
|
|
581
|
-
sessionScopes = scopes.scopes
|
|
582
|
-
controlRegistry = read.registry
|
|
583
|
-
controlSource = read.source
|
|
584
|
-
controlWarnings = read.warnings
|
|
585
|
-
controlError = read.error
|
|
586
|
-
if (read.error !== undefined) console.warn('[dsh-tiddlywiki]', read.error)
|
|
587
|
-
for (const warning of read.warnings) console.warn('[dsh-tiddlywiki]', warning)
|
|
588
|
-
console.info(`[dsh-tiddlywiki] ${read.registry.mode} 模式 · ${read.registry.wikis.length} 个知识库(来源:${read.source})`)
|
|
589
|
-
// single 模式 = 今天的行为,逐字保留:位置仍由旧指针文件(其次 cordis 默认)
|
|
590
|
-
// 决定,registry 此时只记住 `mode` 与候选列表 —— 所以这里合成一份单条清单。
|
|
591
|
-
const runRegistry = read.registry.mode === 'multi'
|
|
592
|
-
? read.registry
|
|
593
|
-
: singleEntryRegistry(await singleModeLocation(), DEFAULT_WIKI_ID, 'single')
|
|
594
|
-
farm = new WikiFarm<WikiInstance>(runRegistry, {
|
|
595
|
-
createRuntime: (entry) => new WikiInstance({
|
|
596
|
-
entry,
|
|
597
|
-
base: config,
|
|
598
|
-
git,
|
|
599
|
-
twRoot: resolveTwRoot,
|
|
600
|
-
touchCommit: (dir) => { void repos.touch(dir) },
|
|
601
|
-
flushCommits: (dir) => repos.flush(dir),
|
|
602
|
-
proxyBase: () => proxyBase(entry),
|
|
603
|
-
}),
|
|
604
|
-
log: (message) => console.warn('[dsh-tiddlywiki]', message),
|
|
605
|
-
})
|
|
606
|
-
// Every instance brings ITSELF up (child → its config tiddler → seeds →
|
|
607
|
-
// git → committer), so this one call replaces the old step-by-step startup.
|
|
608
|
-
// One wiki failing never stops the others (the farm reports it).
|
|
609
|
-
const change = await farm.startAll()
|
|
610
|
-
if (change.started.length > 0) console.info(`[dsh-tiddlywiki] 已启动:${change.started.join(', ')}`)
|
|
611
|
-
// The prompt section is a plugin-level singleton: rebuild it now that the
|
|
612
|
-
// default wiki's config tiddler has been loaded.
|
|
613
|
-
applyPrompt()
|
|
614
|
-
if (disposed) {
|
|
615
|
-
await farm.disposeAll()
|
|
616
|
-
return
|
|
617
|
-
}
|
|
618
|
-
// Clip bridge: bind once on the DEFAULT wiki's configured port (works even
|
|
619
|
-
// while disabled — every request re-checks the effective enabled flag, so
|
|
620
|
-
// the settings-page toggle applies without a dsh web restart).
|
|
621
|
-
try {
|
|
622
|
-
await clipBridge.start(effectiveBridge().port)
|
|
623
|
-
console.info(`[dsh-tiddlywiki] clip bridge listening on 127.0.0.1:${clipBridge.port} (enabled=${effectiveBridge().enabled})`)
|
|
624
|
-
} catch (err) {
|
|
625
|
-
console.warn('[dsh-tiddlywiki] clip bridge start:', err)
|
|
626
|
-
}
|
|
627
|
-
if (disposed) {
|
|
628
|
-
try { await clipBridge.stop() } catch { /* already closing */ }
|
|
629
|
-
await farm.disposeAll()
|
|
630
|
-
return
|
|
631
|
-
}
|
|
632
|
-
} catch (err) {
|
|
633
|
-
console.warn('[dsh-tiddlywiki] startup issue (self-healing is armed):', err)
|
|
634
|
-
}
|
|
635
|
-
})()
|
|
586
|
+
// The task is fire-and-forget (v0.19.1): teardown can run while it still
|
|
587
|
+
// awaits, so the task itself stops the child when it notices `disposed`.
|
|
588
|
+
const startupPromise = startupStage.start()
|
|
589
|
+
void startupPromise
|
|
636
590
|
|
|
637
591
|
/**
|
|
638
592
|
* The folder the default wiki occupies, running or not.
|
|
@@ -719,7 +673,7 @@ export function apply(ctx: HostCtx, rawConfig: TiddlywikiConfig = {}): void {
|
|
|
719
673
|
disposed = true
|
|
720
674
|
try {
|
|
721
675
|
await Promise.race([
|
|
722
|
-
|
|
676
|
+
startupPromise.catch(() => undefined),
|
|
723
677
|
new Promise<void>((r) => { setTimeout(r, 3_000).unref?.() }),
|
|
724
678
|
])
|
|
725
679
|
} catch { /* startup issues are already logged */ }
|