dsh-tiddlywiki 0.30.47 → 0.30.49
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 +2 -0
- package/lib/index.js +8146 -8089
- package/lib/index.js.map +1 -1
- package/package.json +2 -2
- package/src/index-git.ts +0 -2
- package/src/index-routes.ts +381 -0
- package/src/index.ts +53 -271
package/src/index.ts
CHANGED
|
@@ -18,37 +18,29 @@
|
|
|
18
18
|
*
|
|
19
19
|
* @module dsh-tiddlywiki
|
|
20
20
|
*/
|
|
21
|
-
import type { IncomingMessage } from 'node:http'
|
|
22
21
|
import { AutoCommitter, GitFace } from './host/git.ts'
|
|
23
|
-
import {
|
|
24
|
-
import {
|
|
25
|
-
import {
|
|
26
|
-
import { checkAllSeeds, runSeedById, removeSeedById } from './host/seeds.ts'
|
|
22
|
+
import { type SessionsFace, type WebServerFace } from './host/routes.ts'
|
|
23
|
+
import { type PluginConfigShape } from './host/config.ts'
|
|
24
|
+
import { resolveTwRoot } from './host/admin.ts'
|
|
27
25
|
import { TiddlyWebClient, isBinaryType, TEXT_LIST_FILTER } from './host/tw-api.ts'
|
|
28
|
-
import { WechatPublishRunner
|
|
29
|
-
import { registerTiddlywikiTools,
|
|
30
|
-
import { describePrompt, type PromptConfig } from './host/prompt.ts'
|
|
26
|
+
import { WechatPublishRunner } from './host/wechat-publish.ts'
|
|
27
|
+
import { registerTiddlywikiTools, type ToolsDeps, type ToolScope } from './host/tools.ts'
|
|
31
28
|
import {
|
|
32
29
|
defaultLocationStateFile,
|
|
33
30
|
locationPath,
|
|
34
31
|
readLocationState,
|
|
35
|
-
writeLocationState,
|
|
36
32
|
type WikiLocation,
|
|
37
33
|
} from './host/wiki-location.ts'
|
|
38
34
|
import { WikiInstance } from './host/wiki-instance.ts'
|
|
39
|
-
import { WikiFarm, resolveAgentScope,
|
|
40
|
-
import { defaultSessionScopeFile,
|
|
35
|
+
import { WikiFarm, resolveAgentScope, targetRuntimeFor } from './host/wiki-farm.ts'
|
|
36
|
+
import { defaultSessionScopeFile, readSessionScopes } from './host/session-scope.ts'
|
|
41
37
|
import { installSplitSkill } from './host/skill-install.ts'
|
|
42
38
|
import {
|
|
43
39
|
DEFAULT_WIKI_ID,
|
|
44
40
|
DEFAULT_WIKI_MODE,
|
|
45
|
-
applyWikiAction,
|
|
46
|
-
defaultEntry,
|
|
47
41
|
defaultRegistryFile,
|
|
48
|
-
entryPath,
|
|
49
42
|
readRegistry,
|
|
50
43
|
singleEntryRegistry,
|
|
51
|
-
writeRegistry,
|
|
52
44
|
type WikiEntry,
|
|
53
45
|
type WikiRegistry,
|
|
54
46
|
} from './host/wiki-registry.ts'
|
|
@@ -63,6 +55,7 @@ import { createWikiViews, proxyBaseForEntry, restartAffectedWikis, resolveWikiRo
|
|
|
63
55
|
import { resolveConfig, type ResolvedConfig, type TiddlywikiConfig } from './index-config.ts'
|
|
64
56
|
import { createMutationLock } from './host/mutation-lock.ts'
|
|
65
57
|
import { createGitLayer } from './index-git.ts'
|
|
58
|
+
import { createRouteStage } from './index-routes.ts'
|
|
66
59
|
import { createPromptSurface, createServerTuning } from './index-prompt.ts'
|
|
67
60
|
import { createClipSurface } from './index-clip.ts'
|
|
68
61
|
|
|
@@ -169,6 +162,18 @@ export {
|
|
|
169
162
|
locationPath,
|
|
170
163
|
normalizeLocation,
|
|
171
164
|
readLocationState,
|
|
165
|
+
// v0.30.49:这条 **barrel 再导出** 是给 `lib/index.js` 的**外部**使用者的
|
|
166
|
+
// (scripts/verify-wiki-switch.mjs 就 import 它)。⚠️ 再导出是**独立语句**,
|
|
167
|
+
// 它不消费上面那条 import 的绑定 —— 所以 `noUnusedLocals` 会把 import 里的
|
|
168
|
+
// 同名行点名成「未使用」,而**删掉 import 会让这条导出引用不到绑定**,
|
|
169
|
+
// `lib/index.js` 随即少一个导出(v0.30.48 实测:typecheck / verify:unit /
|
|
170
|
+
// verify:static 全绿,只有 e2e 的 verify-wiki-switch 崩在缺导出那行)。
|
|
171
|
+
// 判据现在由 scripts/verify-index-barrel.mjs 守住(反向验证过:删掉即红)。
|
|
172
|
+
//
|
|
173
|
+
// ⚠️ 写这段注释时踩到一个**就在这条语句内部**的坑:注释里**不能出现右花括号**,
|
|
174
|
+
// 否则任何按花括号配对的解析器(tsc 自己的扫描器也在内)都会把它当成 export
|
|
175
|
+
// 列表的结尾,后面的 from 子句变成语法垃圾 —— 症状是「导出了却仍报缺」,
|
|
176
|
+
// 而 tsc 的报错指向完全无关的 import 行。要描述这个语法请用文字。
|
|
172
177
|
writeLocationState,
|
|
173
178
|
LOCATION_STATE_VERSION,
|
|
174
179
|
type WikiLocation,
|
|
@@ -371,7 +376,6 @@ export function apply(ctx: HostCtx, rawConfig: TiddlywikiConfig = {}): void {
|
|
|
371
376
|
noteConfig: () => config.note,
|
|
372
377
|
defaultPath: () => wikiViews.defaultPath(),
|
|
373
378
|
registryFile: () => registryFile,
|
|
374
|
-
isDisposed: () => disposed,
|
|
375
379
|
})
|
|
376
380
|
const { repos, gitTargets, teardownCommitter, reapplyGitConfig, effectiveWorkspaceMark, effectiveBridge, effectiveWechat } = gitLayer
|
|
377
381
|
|
|
@@ -656,179 +660,38 @@ export function apply(ctx: HostCtx, rawConfig: TiddlywikiConfig = {}): void {
|
|
|
656
660
|
isDisposed: () => disposed,
|
|
657
661
|
applyPrompt,
|
|
658
662
|
})
|
|
659
|
-
const { defaultPath, defaultCurrentLocation
|
|
663
|
+
const { defaultPath, defaultCurrentLocation } = wikiViews
|
|
660
664
|
|
|
661
|
-
// Routes + settings-panel admin surface (lazy webServer).
|
|
665
|
+
// Routes + settings-panel admin surface (lazy webServer). v0.30.48: the whole
|
|
666
|
+
// assembly stage (the two Deps objects + both route tables) lives in
|
|
667
|
+
// index-routes.ts; the entry point keeps ownership of `disposed` and builds
|
|
668
|
+
// the ONE mutation lock, and hands both in. Registration still happens HERE,
|
|
669
|
+
// at the position it always had — nothing about the lifecycle order moved.
|
|
662
670
|
ctx.inject(['webServer'], (webCtx: HostCtx) => {
|
|
663
671
|
const ws = (webCtx as unknown as { webServer: WebServerFace }).webServer
|
|
664
|
-
|
|
665
|
-
|
|
666
|
-
|
|
667
|
-
const getSessionController = (): SessionControllerFace | undefined =>
|
|
668
|
-
ctx.get('sessionController') as SessionControllerFace | undefined
|
|
669
|
-
const getWorkspaceRegistry = (): WorkspaceRegistryFace | undefined =>
|
|
670
|
-
ctx.get('workspaceRegistry') as WorkspaceRegistryFace | undefined
|
|
671
|
-
// agentPresets (工作模式 roster) + sessionPersistence (per-session preset
|
|
672
|
-
// badges) are also core host services — resolve them lazily like the above.
|
|
673
|
-
const getAgentPresets = (): AgentPresetsFace | undefined =>
|
|
674
|
-
ctx.get('agentPresets') as AgentPresetsFace | undefined
|
|
675
|
-
const getSessionPersistence = (): SessionPersistenceFace | undefined =>
|
|
676
|
-
ctx.get('sessionPersistence') as SessionPersistenceFace | undefined
|
|
677
|
-
// permissionPresets (权限 preset roster for the picker + applying the
|
|
678
|
-
// chosen permission to new sessions) and the `sessions` in-memory store
|
|
679
|
-
// (the created live session handed to permissionPresets.set) are core host
|
|
680
|
-
// services — resolve them lazily like the ones above.
|
|
681
|
-
const getPermissionPresets = (): PermissionPresetsFace | undefined =>
|
|
682
|
-
ctx.get('permissionPresets') as PermissionPresetsFace | undefined
|
|
683
|
-
const getSessions = (): SessionsFace | undefined =>
|
|
684
|
-
ctx.get('sessions') as SessionsFace | undefined
|
|
685
|
-
// sessionQuery (会话日志查询,供「知识库」Tab 判定「本会话相关笔记」) 也是核心
|
|
686
|
-
// host 服务 —— 可选注入(本部署存在),懒解析。
|
|
687
|
-
const getSessionQuery = (): SessionQueryFace | undefined =>
|
|
688
|
-
ctx.get('sessionQuery') as SessionQueryFace | undefined
|
|
689
|
-
// v0.28.0: the request decides WHICH wiki. M1b-2b resolves every request to
|
|
690
|
-
// the farm's DEFAULT runtime (the wiki this plugin has always served); the
|
|
691
|
-
// `?wiki=<id>` selector arrives with the per-wiki GUI (M5). Every accessor
|
|
692
|
-
// falls back to the cordis BASE when nothing is running, so a stopped farm
|
|
693
|
-
// degrades to "base defaults + 503 on writes" instead of throwing.
|
|
694
|
-
/**
|
|
695
|
-
* Which wiki does this request target? `?wiki=<id>` (a running wiki), else
|
|
696
|
-
* the farm's default — resolved by the SHARED helper in host/wiki-farm.ts,
|
|
697
|
-
* so the host wiring and the verification harness cannot disagree about the
|
|
698
|
-
* selector (and an unknown id falls back instead of 404ing).
|
|
699
|
-
*/
|
|
700
|
-
const target = (req: IncomingMessage): WikiInstance | undefined => targetRuntimeFor(farm, req)
|
|
701
|
-
/**
|
|
702
|
-
* Why this request cannot be served (v0.29.0): set when `?wiki=<id>` names a
|
|
703
|
-
* REGISTERED but STOPPED knowledge base. `targetRuntimeFor` deliberately
|
|
704
|
-
* returns undefined for that case instead of falling back to the default wiki
|
|
705
|
-
* (an unknown id still falls back — a stale bookmark must not 404), so every
|
|
706
|
-
* route that either 503s on a missing client or has a harmless-looking
|
|
707
|
-
* fallback (config / wiki path / prompt / status) can say WHICH wiki is down
|
|
708
|
-
* and what to do, instead of quietly acting on a different one.
|
|
709
|
-
*/
|
|
710
|
-
const targetProblem = (req: IncomingMessage): string | undefined => {
|
|
711
|
-
const entry = stoppedWikiFromRequest(farm, req)
|
|
712
|
-
return entry === undefined ? undefined : stoppedWikiMessage(entry)
|
|
713
|
-
}
|
|
714
|
-
const fallbackUi = WikiInstance.uiDefaultsFrom(config)
|
|
715
|
-
const fallbackWechat = normalizeWechatConfig(config.wechat)
|
|
716
|
-
/** Used only while nothing runs: base defaults, no wiki tiddler to read. */
|
|
717
|
-
const idleConfig = new ConfigStore(baseShape)
|
|
718
|
-
// ONE mutation lock for the whole plugin (v0.30.12): /restart, /sync,
|
|
719
|
-
// /admin/restart and the seed restart all stop the TW child — before this,
|
|
720
|
-
// the admin half had no lock at all and could race the others.
|
|
721
|
-
const mutationLock = createMutationLock()
|
|
722
|
-
const disposeRoutes = registerRoutes({ webServer: ws }, {
|
|
723
|
-
mutationLock,
|
|
724
|
-
server: (req) => target(req)?.server,
|
|
725
|
-
// The roster the GUI selector + the settings page read. It reports what
|
|
726
|
-
// the FARM serves right now (in single mode that is the one synthesized
|
|
727
|
-
// entry); the control file's full candidate list is the admin route's job.
|
|
728
|
-
wikiSummaries: (req) => {
|
|
729
|
-
const registry = farm?.registry
|
|
730
|
-
return {
|
|
731
|
-
mode: registry?.mode ?? 'single',
|
|
732
|
-
defaultId: registry?.defaultId ?? DEFAULT_WIKI_ID,
|
|
733
|
-
items: (registry?.wikis ?? []).map((entry) => {
|
|
734
|
-
const runtime = farm?.runtime(entry.id)
|
|
735
|
-
return {
|
|
736
|
-
id: entry.id,
|
|
737
|
-
label: entry.label,
|
|
738
|
-
status: runtime?.server.status().status ?? 'stopped',
|
|
739
|
-
agentVisible: entry.agentVisible,
|
|
740
|
-
autostart: entry.autostart,
|
|
741
|
-
running: runtime !== undefined,
|
|
742
|
-
path: entryPath(entry),
|
|
743
|
-
// 每库图标(v0.28.4):侧边栏入口与设置页选择器共用这个来源。
|
|
744
|
-
icon: entry.icon,
|
|
745
|
-
}
|
|
746
|
-
}),
|
|
747
|
-
}
|
|
748
|
-
},
|
|
749
|
-
// The `/tw/<id>/…` form: the proxy resolves the child by NAME (it cannot
|
|
750
|
-
// use `target()`, whose `?wiki=` would be lost inside the iframe).
|
|
751
|
-
serverById: (id) => farm?.runtime(id)?.server,
|
|
752
|
-
wikiIds: () => farm?.registry.wikis.map((entry) => entry.id) ?? [],
|
|
753
|
-
getClient: (req) => target(req)?.client(),
|
|
672
|
+
const routeStage = createRouteStage({
|
|
673
|
+
webServer: ws,
|
|
674
|
+
get: (name) => ctx.get(name),
|
|
754
675
|
git,
|
|
755
|
-
|
|
756
|
-
|
|
757
|
-
|
|
758
|
-
|
|
759
|
-
|
|
760
|
-
|
|
761
|
-
|
|
762
|
-
|
|
763
|
-
|
|
764
|
-
|
|
765
|
-
// writing persists to session-scope.ts AND starts the wiki on demand — the
|
|
766
|
-
// tools resolve the scope SYNCHRONOUSLY and never start anything, so the
|
|
767
|
-
// selection itself has to bring the wiki up, or the very next tool call
|
|
768
|
-
// would have to refuse.
|
|
769
|
-
sessionScope: {
|
|
770
|
-
get: (sessionId: string) => {
|
|
771
|
-
const resolution = resolveAgentScope(farm, sessionScopes, sessionId)
|
|
772
|
-
const scopeId = sessionScopes[sessionId]
|
|
773
|
-
return {
|
|
774
|
-
...(scopeId !== undefined ? { scope: scopeId } : {}),
|
|
775
|
-
...(resolution.entry !== undefined ? { resolved: { id: resolution.entry.id, label: resolution.entry.label } } : {}),
|
|
776
|
-
...(resolution.reason !== undefined ? { reason: resolution.reason } : {}),
|
|
777
|
-
// v0.29.0: the client needs to know whether "resolved" is a real
|
|
778
|
-
// answer (multi: this session's tools act on THAT wiki, so its cards'
|
|
779
|
-
// links must open it) or just the single install's only wiki (keep
|
|
780
|
-
// the pre-multi DOM, which renders no wiki attribute at all).
|
|
781
|
-
mode: farm?.registry.mode ?? 'single',
|
|
782
|
-
}
|
|
783
|
-
},
|
|
784
|
-
set: async (sessionId: string, wikiId: string | undefined) => {
|
|
785
|
-
if (!isSafeSessionId(sessionId)) throw new Error('会话 id 非法')
|
|
786
|
-
if (wikiId !== undefined) {
|
|
787
|
-
const entry = farm?.registry.wikis.find((item) => item.id === wikiId)
|
|
788
|
-
if (entry === undefined) throw new Error(`知识库「${wikiId}」不在清单里`)
|
|
789
|
-
// `agentVisible: false` means "the agent never reaches this one"; a
|
|
790
|
-
// selector that allowed it would contradict the setting silently.
|
|
791
|
-
if (!entry.agentVisible) throw new Error(`知识库「${entry.label}」对 Agent 隐身,不能作为会话作用域`)
|
|
792
|
-
if (farm !== undefined && farm.runtime(entry.id) === undefined) await farm.startEntry(entry)
|
|
793
|
-
}
|
|
794
|
-
await setSessionScope(sessionId, wikiId, sessionScopeFile)
|
|
795
|
-
const next = { ...sessionScopes }
|
|
796
|
-
if (wikiId === undefined) delete next[sessionId]
|
|
797
|
-
else next[sessionId] = wikiId
|
|
798
|
-
sessionScopes = next
|
|
799
|
-
},
|
|
800
|
-
},
|
|
801
|
-
getSessionController,
|
|
802
|
-
getWorkspaceRegistry,
|
|
803
|
-
getAgentPresets,
|
|
804
|
-
getSessionPersistence,
|
|
805
|
-
getPermissionPresets,
|
|
806
|
-
getSessions,
|
|
807
|
-
getSessionQuery,
|
|
808
|
-
sendToAgentEnabled: (req) => target(req)?.sendToAgent().enabled ?? config.ui.sendToAgent.enabled,
|
|
809
|
-
sendToAgentToken: (req) => target(req)?.sendToAgent().token ?? (config.ui.sendToAgent.token ?? ''),
|
|
810
|
-
// 公众号发布(v0.23.3,可选功能):config 每请求重读(开关/adapter/token
|
|
811
|
-
// 保存即生效,且每个知识库各有一份);就绪探测每次都真跑一次
|
|
812
|
-
// `opencli --version`(几百毫秒),只在按钮预检与每次起任务前发生,
|
|
813
|
-
// 不做缓存以免装完 adapter 还要等 TTL。
|
|
814
|
-
wechatConfig: (req) => target(req)?.wechatConfig() ?? fallbackWechat,
|
|
815
|
-
wechatRunner: () => wechatRunner,
|
|
816
|
-
wechatReady: async (req) => {
|
|
817
|
-
const cfg = target(req)?.wechatConfig() ?? fallbackWechat
|
|
818
|
-
return checkWechatReady({ enabled: cfg.enabled, command: cfg.command })
|
|
676
|
+
repos,
|
|
677
|
+
getFarm: () => farm,
|
|
678
|
+
defaultPath,
|
|
679
|
+
defaultCurrentLocation,
|
|
680
|
+
controlRegistryNow,
|
|
681
|
+
setControlRegistry: (registry) => {
|
|
682
|
+
controlRegistry = registry
|
|
683
|
+
controlSource = 'file'
|
|
684
|
+
controlWarnings = []
|
|
685
|
+
controlError = undefined
|
|
819
686
|
},
|
|
820
|
-
|
|
821
|
-
|
|
822
|
-
|
|
823
|
-
|
|
824
|
-
|
|
825
|
-
|
|
826
|
-
|
|
827
|
-
|
|
828
|
-
config: (req) => target(req)?.config ?? idleConfig,
|
|
829
|
-
// A settings-page save may change prompt.*: re-register the section (no
|
|
830
|
-
// dsh web restart) and expose the built text for the preview panel.
|
|
831
|
-
// startup.readyTimeoutMs is re-applied here too (next start/restart).
|
|
687
|
+
getSessionScopes: () => sessionScopes,
|
|
688
|
+
setSessionScopes: (next) => { sessionScopes = next },
|
|
689
|
+
sessionScopeFile,
|
|
690
|
+
registryFile,
|
|
691
|
+
locationStateFile,
|
|
692
|
+
baseShape,
|
|
693
|
+
config,
|
|
694
|
+
wikiViews,
|
|
832
695
|
onConfigChanged: () => {
|
|
833
696
|
applyPrompt()
|
|
834
697
|
applyServerTuning()
|
|
@@ -836,94 +699,13 @@ export function apply(ctx: HostCtx, rawConfig: TiddlywikiConfig = {}): void {
|
|
|
836
699
|
// reapplyGitConfig: enabled/debounceMs/remote used to need a dsh web restart.
|
|
837
700
|
void reapplyGitConfig()
|
|
838
701
|
},
|
|
839
|
-
|
|
840
|
-
|
|
841
|
-
|
|
842
|
-
|
|
843
|
-
|
|
844
|
-
|
|
845
|
-
// decided, switch to another one, or drop back to the cordis default.
|
|
846
|
-
wiki: {
|
|
847
|
-
info: locationInfo,
|
|
848
|
-
switch: switchWikiLocation,
|
|
849
|
-
reset: resetWikiLocation,
|
|
850
|
-
},
|
|
851
|
-
// The knowledge-base LIST (v0.28.0): `info` reports the CONTROL FILE; every
|
|
852
|
-
// action is validated by the pure `applyWikiAction` and persisted BEFORE
|
|
853
|
-
// the farm reconciles — the file is the user's intent, and a reconcile
|
|
854
|
-
// failure is reported per wiki instead of silently discarding the edit.
|
|
855
|
-
wikis: {
|
|
856
|
-
info: async (): Promise<AdminWikisView> => buildWikisView(),
|
|
857
|
-
apply: async (body: unknown): Promise<AdminWikisApplyResult> => {
|
|
858
|
-
if (disposed) return { ok: false, error: '插件正在卸载,已取消修改' }
|
|
859
|
-
// RUNTIME actions first (v0.28.0): `start`/`stop` do not change the list,
|
|
860
|
-
// they change what is running. They live here rather than in the pure
|
|
861
|
-
// `applyWikiAction` because they touch processes, not configuration —
|
|
862
|
-
// and the GUI needs them: opening a wiki's panel must be able to bring
|
|
863
|
-
// a stopped knowledge base up.
|
|
864
|
-
const runtimeAction = (body as { action?: unknown } | null)?.action
|
|
865
|
-
if (runtimeAction === 'start' || runtimeAction === 'stop') {
|
|
866
|
-
if (farm === undefined) return { ok: false, error: '插件尚未就绪,请稍后再试' }
|
|
867
|
-
const id = (body as { id?: unknown }).id
|
|
868
|
-
const entry = typeof id === 'string' ? farm.registry.wikis.find((item) => item.id === id.trim().toLowerCase()) : undefined
|
|
869
|
-
if (entry === undefined) return { ok: false, error: `知识库「${String(id)}」不在清单里` }
|
|
870
|
-
const change = { started: [] as string[], stopped: [] as string[], updated: [] as string[], running: farm.runningIds(), errors: [] as Array<{ id: string; message: string }> }
|
|
871
|
-
if (runtimeAction === 'start') {
|
|
872
|
-
if (farm.runtime(entry.id) === undefined) {
|
|
873
|
-
await farm.startEntry(entry)
|
|
874
|
-
change.started.push(entry.id)
|
|
875
|
-
}
|
|
876
|
-
} else {
|
|
877
|
-
await farm.stopEntry(entry.id)
|
|
878
|
-
change.stopped.push(entry.id)
|
|
879
|
-
}
|
|
880
|
-
change.running = farm.runningIds()
|
|
881
|
-
return { ok: true, info: buildWikisView(), change }
|
|
882
|
-
}
|
|
883
|
-
const action = applyWikiAction(controlRegistryNow(), body)
|
|
884
|
-
if (action.registry === undefined) return { ok: false, error: action.error ?? '动作被拒绝' }
|
|
885
|
-
const registry = action.registry
|
|
886
|
-
try {
|
|
887
|
-
await writeRegistry(registry, registryFile)
|
|
888
|
-
} catch (err) {
|
|
889
|
-
return { ok: false, error: `清单写入失败:${err instanceof Error ? err.message : String(err)}` }
|
|
890
|
-
}
|
|
891
|
-
controlRegistry = registry
|
|
892
|
-
controlSource = 'file'
|
|
893
|
-
controlWarnings = []
|
|
894
|
-
controlError = undefined
|
|
895
|
-
// Reconcile. multi → the farm runs the whole list. single → it must
|
|
896
|
-
// keep serving the ONE wiki the legacy pointer names, so the
|
|
897
|
-
// synthesized run-registry is used and the pointer is kept in step
|
|
898
|
-
// (otherwise a restart in single mode would land somewhere else).
|
|
899
|
-
const single = defaultEntry(registry)
|
|
900
|
-
const runRegistry = registry.mode === 'multi'
|
|
901
|
-
? registry
|
|
902
|
-
: singleEntryRegistry(single === undefined ? defaultCurrentLocation() : { root: single.root, name: single.name }, DEFAULT_WIKI_ID, 'single')
|
|
903
|
-
const change = farm === undefined
|
|
904
|
-
? { started: [], stopped: [], updated: [], running: [], errors: [] }
|
|
905
|
-
: await farm.apply(runRegistry)
|
|
906
|
-
if (registry.mode === 'single' && single !== undefined) {
|
|
907
|
-
await writeLocationState({ root: single.root, name: single.name }, locationStateFile).catch(() => undefined)
|
|
908
|
-
}
|
|
909
|
-
return { ok: true, info: buildWikisView(), change }
|
|
910
|
-
},
|
|
911
|
-
},
|
|
912
|
-
seeds: {
|
|
913
|
-
// Tool summaries feed the GENERATED seed content (the doc note's tool
|
|
914
|
-
// list, v0.22.0) — pass them everywhere the registry can be re-run.
|
|
915
|
-
checkAll: async (c) => checkAllSeeds({ client: c, tools: tiddlywikiToolSummary() }),
|
|
916
|
-
run: async (c, id, force) => runSeedById({ client: c, tools: tiddlywikiToolSummary() }, id, force),
|
|
917
|
-
remove: async (c, id) => removeSeedById({ client: c }, id),
|
|
918
|
-
},
|
|
919
|
-
}
|
|
920
|
-
const disposeAdmin = registerAdminRoutes({ webServer: ws }, adminDeps)
|
|
921
|
-
return () => {
|
|
922
|
-
disposeRoutes()
|
|
923
|
-
disposeAdmin()
|
|
924
|
-
}
|
|
702
|
+
effectivePromptFor: (req) => ((req === undefined ? undefined : targetRuntimeFor(farm, req)?.eff()) ?? baseShape).prompt ?? {},
|
|
703
|
+
mutationLock: createMutationLock(),
|
|
704
|
+
wechatRunner,
|
|
705
|
+
isDisposed: () => disposed,
|
|
706
|
+
})
|
|
707
|
+
return () => routeStage.dispose()
|
|
925
708
|
})
|
|
926
|
-
|
|
927
709
|
// Teardown: everything reversible (R6 — hot reload must not leak).
|
|
928
710
|
// The disposer RETURNS its promise: cordis fiber.dispose() awaits effect
|
|
929
711
|
// disposers, and dsh web's shutdown controller awaits fiber.dispose() with a
|