| 1 | /** |
| 2 | * Immutable, reviewed DSH entry trees in the existing |
| 3 | * Cordis owner. Patch/expression/group/isolation semantics are upstream's. |
| 4 | * There is no session writer, prompt builder, skill parser or process launcher. |
| 5 | */ |
| 6 | import { Context, Service, type Fiber } from '@deepseek-ai/cordis' |
| 7 | import { FiberState } from './fiber-state.ts' |
| 8 | import { createHash } from 'node:crypto' |
| 9 | import { readFile } from 'node:fs/promises' |
| 10 | import { isAbsolute, relative, resolve, sep } from 'node:path' |
| 11 | import { fileURLToPath } from 'node:url' |
| 12 | import { Loader, EntryTree, Group, type EntryOptions } from './upstream/loader/src/index.ts' |
| 13 | import { applyEntryPatches, entryListSchema, type PatchOptions } from './upstream/include/src/index.ts' |
| 14 | import { load as loadYaml } from 'js-yaml' |
| 15 | import {reviewedMcpModule} from './mcp.ts' |
| 16 | import {reviewedSkillModule} from './skill-filesystem.ts' |
| 17 | import {reviewedHookModule} from './shell-hooks.ts' |
| 18 | import {admitReviewedClosure,importReviewedModule} from './resolve-hooks.ts' |
| 19 | import { PERSONA, reviewedPersonaModule } from './persona.ts' |
| 20 | import { AGENT_PRESETS, defineReviewedAgentPresets, type PresetCatalog, type PreparedPreset } from './agent-presets.ts' |
| 21 | |
| 22 | const MAX_LAYERS = 64 |
| 23 | const MAX_SOURCE_BYTES = 1024 * 1024 |
| 24 | const MAX_DATA_BYTES = 4 * 1024 * 1024 |
| 25 | const MAX_DATA_NODES = 100_000 |
| 26 | const MAX_ROWS = 1024 |
| 27 | const MAX_DEPTH = 32 |
| 28 | const SHA256 = /^[a-f0-9]{64}$/ |
| 29 | |
| 30 | export interface ReviewedLayer { |
| 31 | /** Original selected layer, for nonexecuting diagnostics. */ |
| 32 | path: string |
| 33 | sha256: string |
| 34 | /** Exact UTF-8 source; include's schema retains unevaluated !!js nodes. */ |
| 35 | source: string |
| 36 | } |
| 37 | |
| 38 | export interface ReviewedModule { |
| 39 | /** Exact row name; relative names are resolved during Rust staging. */ |
| 40 | name: string |
| 41 | /** Path relative to the immutable Codewhale bundle root. */ |
| 42 | path: string |
| 43 | sha256: string |
| 44 | } |
| 45 | |
| 46 | export interface ReviewedComposition { |
| 47 | version: 1 |
| 48 | layers: ReviewedLayer[] |
| 49 | modules: ReviewedModule[] |
| 50 | /** Original source/asset closure. Rust additionally hashes generated files. */ |
| 51 | files: Record<string, string> |
| 52 | } |
| 53 | |
| 54 | export interface CompositionReview { |
| 55 | version: 1 |
| 56 | /** Upstream's exact effective data. Expression nodes are still data. */ |
| 57 | entries: EntryOptions[] |
| 58 | warnings: string[] |
| 59 | skipped: {row?:string;package?:string;reason:string;layer:string;patch:number}[] |
| 60 | layers: { path: string; sha256: string }[] |
| 61 | } |
| 62 | |
| 63 | function digest(bytes: string | Uint8Array): string { |
| 64 | return createHash('sha256').update(bytes).digest('hex') |
| 65 | } |
| 66 | |
| 67 | function plainRelative(path: string): boolean { |
| 68 | return typeof path === 'string' && path.length > 0 && path.length <= 4096 && !isAbsolute(path) |
| 69 | && !path.startsWith('/') && !/[\\:\u0000-\u001f\u007f]/u.test(path) |
| 70 | && path.split('/').every(part => part !== '' && part !== '.' && part !== '..') |
| 71 | } |
| 72 | |
| 73 | /** Bound traversal before clone/stringification, including repeated YAML aliases. */ |
| 74 | function boundData(root: unknown): void { |
| 75 | let nodes = 0 |
| 76 | let bytes = 0 |
| 77 | const ancestors = new Set<object>() |
| 78 | function visit(value: unknown, depth: number) { |
| 79 | if (++nodes > MAX_DATA_NODES || depth > MAX_DEPTH) throw new Error('composition data exceeds its resource limit') |
| 80 | if (typeof value === 'string') bytes += Buffer.byteLength(value) |
| 81 | if (bytes > MAX_DATA_BYTES) throw new Error('composition expanded text exceeds 4 MiB') |
| 82 | if (value === null || typeof value !== 'object') return |
| 83 | if (ancestors.has(value)) throw new Error('cyclic configuration data is unsupported') |
| 84 | ancestors.add(value) |
| 85 | if (!Array.isArray(value) && Object.getPrototypeOf(value) !== Object.prototype && Object.getPrototypeOf(value) !== null) { |
| 86 | throw new Error('composition configuration must be plain data') |
| 87 | } |
| 88 | for (const [key, child] of Object.entries(value)) { |
| 89 | bytes += Buffer.byteLength(key) |
| 90 | visit(child, depth + 1) |
| 91 | } |
| 92 | ancestors.delete(value) |
| 93 | } |
| 94 | visit(root, 0) |
| 95 | } |
| 96 | |
| 97 | function assignmentSafe(value: object): void { |
| 98 | // Patch/entry fields are assigned onto helper objects. Nested configuration |
| 99 | // remains literal data; ordinary constructor/prototype config keys are valid. |
| 100 | if (Object.hasOwn(value, '__proto__')) throw new Error('prototype-mutating entry field is unsupported') |
| 101 | } |
| 102 | |
| 103 | function checkRows(rows: EntryOptions[], depth = 0, count = { value: 0 }): void { |
| 104 | if (depth > MAX_DEPTH) throw new Error('composition entry nesting exceeds 32 levels') |
| 105 | for (const row of rows) { |
| 106 | if (++count.value > MAX_ROWS) throw new Error('composition exceeds 1024 entry rows') |
| 107 | if (row === null || typeof row !== 'object' || Array.isArray(row)) throw new Error('entry row must be an object') |
| 108 | assignmentSafe(row) |
| 109 | if (row.id !== undefined && (typeof row.id !== 'string' || row.id.length > 512)) throw new Error('entry id must be a bounded string') |
| 110 | if (!(row.group===true && row.name===undefined) && (typeof row.name !== 'string' || row.name.length === 0 || row.name.length > 4096)) throw new Error('entry name must be a bounded string') |
| 111 | if ((row.group || row.name==='cordis:group' || row.name==='@deepseek-ai/cordis-plugin-group') && Array.isArray(row.config)) checkRows(row.config, depth + 1, count) |
| 112 | } |
| 113 | } |
| 114 | |
| 115 | /** Raw preset files are entry lists; apply the same bounded data/row guards |
| 116 | * before the borrowed recursive discovery/inventory code sees them. */ |
| 117 | export function parsePresetComposition(source: string): EntryOptions[] { |
| 118 | if (Buffer.byteLength(source) > MAX_SOURCE_BYTES) throw new Error('preset source exceeds its limit') |
| 119 | const rows = loadYaml(source, {schema:entryListSchema}) |
| 120 | boundData(rows) |
| 121 | if (!Array.isArray(rows)) throw new Error('preset composition must be an entry list') |
| 122 | checkRows(rows as EntryOptions[]) |
| 123 | return rows as EntryOptions[] |
| 124 | } |
| 125 | |
| 126 | /** Trusted pure reviewer: never imports row modules or evaluates expressions. */ |
| 127 | export function reviewComposition(spec: ReviewedComposition): CompositionReview { |
| 128 | if (!spec || spec.version !== 1 || !Array.isArray(spec.layers) || spec.layers.length === 0 || spec.layers.length > MAX_LAYERS) { |
| 129 | throw new Error('composition needs 1–64 reviewed layers') |
| 130 | } |
| 131 | let sourceBytes = 0 |
| 132 | const patches: PatchOptions[] = [] |
| 133 | const locations: {layer:string;patch:number}[] = [] |
| 134 | const layers: CompositionReview['layers'] = [] |
| 135 | for (const layer of spec.layers) { |
| 136 | if (!layer || !plainRelative(layer.path) || typeof layer.source !== 'string' || typeof layer.sha256 !== 'string' || !SHA256.test(layer.sha256)) throw new Error('invalid reviewed layer') |
| 137 | sourceBytes += Buffer.byteLength(layer.source) |
| 138 | if (sourceBytes > MAX_SOURCE_BYTES || digest(layer.source) !== layer.sha256) throw new Error('layer source changed or exceeds 1 MiB') |
| 139 | let parsed: unknown |
| 140 | try { parsed = loadYaml(layer.source, { schema: entryListSchema }) } catch { throw new Error('invalid DSH patch data') } |
| 141 | if (!Array.isArray(parsed)) throw new Error('DSH patch layer must be a list') |
| 142 | boundData(parsed) |
| 143 | for (const patch of parsed) { |
| 144 | if (!patch || typeof patch !== 'object' || Array.isArray(patch)) throw new Error('patch must be an object') |
| 145 | assignmentSafe(patch) |
| 146 | if (patch.insert !== undefined && patch.insert !== null) { |
| 147 | if (!Array.isArray(patch.insert)) throw new Error('patch insert must be a list') |
| 148 | checkRows(patch.insert) |
| 149 | } |
| 150 | } |
| 151 | patches.push(...parsed) |
| 152 | locations.push(...parsed.map((_,i)=>({layer:layer.path,patch:i+1}))) |
| 153 | layers.push({ path: layer.path, sha256: layer.sha256 }) |
| 154 | } |
| 155 | boundData(patches) |
| 156 | const warnings: string[] = [] |
| 157 | const skipped: CompositionReview['skipped'] = [] |
| 158 | let entries: EntryOptions[] = [] |
| 159 | for (const [i,patch] of patches.entries()) { |
| 160 | entries = applyEntryPatches(entries,[patch],(message,...args)=>{ |
| 161 | let index=0 |
| 162 | const reason=message.replace(/%C/g,()=>JSON.stringify(args[index++])) |
| 163 | warnings.push(reason) |
| 164 | skipped.push({...(typeof patch.id==='string'?{row:patch.id}:{}),...(typeof patch.name==='string'?{package:patch.name}:{}),reason,...locations[i]}) |
| 165 | }) |
| 166 | } |
| 167 | boundData(entries) |
| 168 | checkRows(entries) |
| 169 | return { version: 1, entries, warnings, skipped, layers } |
| 170 | } |
| 171 | |
| 172 | /** Known DSH rows that need an unavailable broker/service or replace core authority. */ |
| 173 | const UNSUPPORTED_ROWS = new Map<string,string>() |
| 174 | |
| 175 | /** These fixed Core adapters resolve without a package-provided module. */ |
| 176 | export function hasReviewedRowBridge(name:string): boolean { return name==='@deepseek-ai/dsh-mcp-client' || name==='@deepseek-ai/dsh-skill-filesystem' || name==='@deepseek-ai/dsh-hooks-claude-code' || name==='@deepseek-ai/dsh-hooks-codex' || name===PERSONA } |
| 177 | |
| 178 | /** Readiness uses the same known Core service boundary as final import. */ |
| 179 | export function compositionRowProblem(name:string): string | undefined { return UNSUPPORTED_ROWS.get(name) } |
| 180 | |
| 181 | /** Upstream Group needs its existing Loader during child creation. Declare |
| 182 | * that dependency for the host's strict Cordis service access checks. */ |
| 183 | class ReviewedGroup extends Group { static inject = ['loader'] } |
| 184 | |
| 185 | /** A Loader that uses upstream lifecycle/config logic without native internals. */ |
| 186 | export class ReviewedLoader extends Loader { |
| 187 | constructor(ctx: Context, config: Loader.Config) { |
| 188 | super(ctx, config) |
| 189 | this.builtins.group = ReviewedGroup |
| 190 | } |
| 191 | } |
| 192 | |
| 193 | // Loader installs global Cordis lifecycle observers. One host root must own |
| 194 | // exactly one loader; one loader per tree would duplicate interpolation. |
| 195 | const loaders = new WeakMap<Context, { ctx: Context; fiber: Fiber }>() |
| 196 | |
| 197 | /** HostRoot calls this before any Native owner can activate. */ |
| 198 | export function installCompositionLoader(root: Context): void { |
| 199 | if (loaders.has(root)) return |
| 200 | const ctx = root.isolate('loader') |
| 201 | const fiber = ctx.plugin(ReviewedLoader, {}) |
| 202 | loaders.set(root, { ctx, fiber }) |
| 203 | } |
| 204 | |
| 205 | class ReviewedTree extends EntryTree { |
| 206 | private readonly base: string |
| 207 | private readonly modules: ReadonlyMap<string, ReviewedModule> |
| 208 | private readonly spec: ReviewedComposition |
| 209 | |
| 210 | constructor(ctx: Context, base: string, spec: ReviewedComposition) { |
| 211 | super(ctx) |
| 212 | this.base = base |
| 213 | this.spec = spec |
| 214 | const modules = new Map<string, ReviewedModule>() |
| 215 | if (!Array.isArray(spec.modules) || spec.modules.length > MAX_ROWS || !spec.files || Object.keys(spec.files).length > 4096) throw new Error('invalid reviewed module closure') |
| 216 | for (const module of spec.modules) { |
| 217 | if (!module || typeof module.name !== 'string' || module.name.length === 0 || module.name.length > 4096 || modules.has(module.name) || !plainRelative(module.path) || !SHA256.test(module.sha256) |
| 218 | || spec.files[module.path] !== module.sha256) throw new Error('module map differs from reviewed closure') |
| 219 | modules.set(module.name, module) |
| 220 | } |
| 221 | this.modules = modules |
| 222 | } |
| 223 | |
| 224 | /** A reviewed composition is input; teardown and self-disposal never rewrite it. */ |
| 225 | write(): void {} |
| 226 | |
| 227 | override import(name: string): unknown { |
| 228 | if (name === PERSONA) return reviewedPersonaModule |
| 229 | if (name === AGENT_PRESETS) return defineReviewedAgentPresets(mountReviewedComposition) |
| 230 | if (name === 'cordis:group' || name === '@deepseek-ai/cordis-plugin-group') return ReviewedGroup |
| 231 | if(name==='@deepseek-ai/dsh-mcp-client')return reviewedMcpModule() |
| 232 | if(name==='@deepseek-ai/dsh-skill-filesystem')return reviewedSkillModule() |
| 233 | if(name==='@deepseek-ai/dsh-hooks-claude-code')return reviewedHookModule('claude-code',fileURLToPath(this.base),this.spec.files) |
| 234 | if(name==='@deepseek-ai/dsh-hooks-codex')return reviewedHookModule('codex',fileURLToPath(this.base),this.spec.files) |
| 235 | if (UNSUPPORTED_ROWS.has(name)) throw new Error(`${name}: ${UNSUPPORTED_ROWS.get(name)}`) |
| 236 | const module = this.modules.get(name) |
| 237 | if (!module) throw new Error(`row requires ${JSON.stringify(name)}, absent from the reviewed module closure`) |
| 238 | return this.importReviewed(module) |
| 239 | } |
| 240 | |
| 241 | private async importReviewed(module: ReviewedModule): Promise<unknown> { |
| 242 | const root = fileURLToPath(this.base) |
| 243 | const path = resolve(root, module.path) |
| 244 | const inside = relative(root, path) |
| 245 | if (inside.startsWith(`..${sep}`) || inside === '..' || isAbsolute(inside)) throw new Error('module escapes reviewed bundle') |
| 246 | if (this.spec.files[module.path] !== module.sha256 || digest(await readFile(path)) !== module.sha256) throw new Error('module changed after review') |
| 247 | // Transitive module bytes remain covered by the existing Rust Native |
| 248 | // receipt, immutable staging and per-call liveness checks. Native code is |
| 249 | // not contained from co-resident arbitrary Native code by this helper. |
| 250 | return importReviewedModule(this.base,module.path) |
| 251 | } |
| 252 | } |
| 253 | |
| 254 | export interface CompositionMount { |
| 255 | readonly tree: EntryTree |
| 256 | dispose(): Promise<void> |
| 257 | } |
| 258 | |
| 259 | interface ReviewedMountConfig { |
| 260 | baseUrl: string |
| 261 | spec: ReviewedComposition |
| 262 | review: CompositionReview |
| 263 | releaseClosure: () => void |
| 264 | } |
| 265 | |
| 266 | const mountedTrees = new WeakMap<ReviewedMountConfig, CompositionMount>() |
| 267 | |
| 268 | /** Validate configured rows too: upstream logs a failed create before it can |
| 269 | * enter tree.store, so iterating only the store would acknowledge no work. */ |
| 270 | function validateInventory(group: import('./upstream/loader/src/config/group.ts').EntryGroup): void { |
| 271 | for (const row of group.data) { |
| 272 | const entry = group.tree.store[row.id] |
| 273 | if (!entry || entry.parent !== group || entry.options !== row) { |
| 274 | throw new Error(`composition row ${JSON.stringify(row.id ?? row.name)} was not created`) |
| 275 | } |
| 276 | if (entry.disabled) continue |
| 277 | if (entry.subgroup) validateInventory(entry.subgroup) |
| 278 | if (entry.subtree) validateInventory(entry.subtree.root) |
| 279 | } |
| 280 | } |
| 281 | |
| 282 | /** Trusted carrier, following upstream PresetTree's explicit loader inject. |
| 283 | * The Native wrapper never gains a second loader or a global service grant. */ |
| 284 | class ReviewedCarrier { |
| 285 | static inject = ['loader'] |
| 286 | private readonly tree: ReviewedTree |
| 287 | private disposed = false |
| 288 | |
| 289 | constructor(private readonly ctx: Context, private readonly config: ReviewedMountConfig) { |
| 290 | this.tree = new ReviewedTree(ctx, config.baseUrl, config.spec) |
| 291 | mountedTrees.set(config, { tree: this.tree, dispose: () => this.dispose() }) |
| 292 | } |
| 293 | |
| 294 | private async dispose(): Promise<void> { |
| 295 | if (this.disposed) return |
| 296 | this.disposed = true |
| 297 | const fibers = [...new Set([...this.tree.entries()].map(entry => entry.fiber) |
| 298 | .filter((fiber): fiber is Fiber => fiber !== undefined))].reverse() |
| 299 | const pending = this.tree.getTasks() |
| 300 | let stopError: unknown |
| 301 | try { this.tree.root.stop() } catch (error) { stopError = error } |
| 302 | const outcomes = await Promise.allSettled([ |
| 303 | ...pending, |
| 304 | ...fibers.map(fiber => Promise.resolve().then(() => fiber.dispose())), |
| 305 | ]) |
| 306 | try { |
| 307 | await this.tree.await() |
| 308 | if (stopError !== undefined) throw stopError |
| 309 | const failed = outcomes.find((outcome): outcome is PromiseRejectedResult => outcome.status === 'rejected') |
| 310 | if (failed) throw failed.reason |
| 311 | } finally { this.config.releaseClosure() } |
| 312 | } |
| 313 | |
| 314 | async *[Service.init]() { |
| 315 | yield () => this.dispose() |
| 316 | await this.tree.root.update(this.config.review.entries) |
| 317 | await this.tree.await() |
| 318 | validateInventory(this.tree.root) |
| 319 | for (const entry of this.tree.entries()) { |
| 320 | if (entry.disabled) continue |
| 321 | if (!entry.fiber || entry.fiber.uid === null) throw new Error(`composition row ${JSON.stringify(entry.options.id ?? entry.options.name)} failed activation`) |
| 322 | await entry.fiber.await() |
| 323 | const missing = Object.keys(entry.fiber.inject).filter(name => entry.fiber!.ctx.get(name) === undefined) |
| 324 | if (missing.length || entry.fiber.state !== FiberState.ACTIVE) throw new Error(`composition row ${JSON.stringify(entry.options.id ?? entry.options.name)} is not active`) |
| 325 | } |
| 326 | const leaked = leakedServices(this.ctx, this.ctx.fiber) |
| 327 | if (leaked.length) throw new Error(`composition published shared-root services: ${leaked.join(', ')}`) |
| 328 | } |
| 329 | } |
| 330 | |
| 331 | /** Called only from the reviewed generated native entry, under its owner. */ |
| 332 | export async function mountReviewedComposition(ctx: Context, baseUrl: string, spec: ReviewedComposition): Promise<CompositionMount> { |
| 333 | const review = reviewComposition(spec) |
| 334 | // Anonymous legacy structural groups are explicit Group fibers; preserve the |
| 335 | // source patch identity comparison and normalize only the mounted projection. |
| 336 | function groups(rows: EntryOptions[]) { for (const row of rows) { if (row.group===true && row.name===undefined) row.name='cordis:group'; if (row.group===true && Array.isArray(row.config)) groups(row.config) } } |
| 337 | groups(review.entries) |
| 338 | const installed = loaders.get(ctx.root) |
| 339 | if (!installed) throw new Error('composition loader was not installed by this host') |
| 340 | await installed.fiber.await() |
| 341 | const loaderCtx = ctx.extend({ |
| 342 | baseUrl, |
| 343 | [Context.isolate]: Object.assign(Object.create(ctx[Context.isolate]), { |
| 344 | loader: installed.ctx[Context.isolate].loader, |
| 345 | }), |
| 346 | }) |
| 347 | const config: ReviewedMountConfig = { baseUrl, spec, review, releaseClosure: admitReviewedClosure(baseUrl, spec.files) } |
| 348 | const carrier = loaderCtx.plugin(ReviewedCarrier, config) |
| 349 | let disposed = false |
| 350 | const dispose = async () => { |
| 351 | if (disposed) return |
| 352 | disposed = true |
| 353 | try { |
| 354 | await mountedTrees.get(config)?.dispose() |
| 355 | await carrier.dispose() |
| 356 | } finally { config.releaseClosure() } |
| 357 | } |
| 358 | ctx.effect(() => dispose, 'reviewed DSH composition teardown') |
| 359 | try { |
| 360 | await carrier.await() |
| 361 | const mount = mountedTrees.get(config) |
| 362 | if (!mount || carrier.state !== FiberState.ACTIVE) throw new Error('reviewed composition carrier did not activate') |
| 363 | return { tree: mount.tree, dispose } |
| 364 | } catch (error) { |
| 365 | await dispose().catch(() => undefined) |
| 366 | throw error |
| 367 | } |
| 368 | } |
| 369 | |
| 370 | /** Reused fiber-identity/root-realm audit from DSH agent-presets/src/mount.ts. */ |
| 371 | function leakedServices(ctx: Context, mount: Fiber): string[] { |
| 372 | const rootIsolate = ctx.root[Context.isolate] |
| 373 | const leaked: string[] = [] |
| 374 | for (const key of Object.getOwnPropertySymbols(ctx.reflect.store)) { |
| 375 | const impl = ctx.reflect.store[key] |
| 376 | if (impl === undefined) continue |
| 377 | let current = impl.fiber |
| 378 | while (current !== mount && current.parent.fiber !== current) current = current.parent.fiber |
| 379 | if (current === mount && rootIsolate[impl.name] === key) leaked.push(impl.name) |
| 380 | } |
| 381 | return leaked.sort((left, right) => left.localeCompare(right)) |
| 382 | } |
| 383 | |
| 384 | /** Installer-created Native entry mounts the raw roster row and its selected |
| 385 | * subtree in one existing owner scope. No process-wide selected preset exists. */ |
| 386 | export async function mountReviewedPreset(ctx: Context, baseUrl: string, spec: ReviewedComposition, catalog: PresetCatalog, selected: PreparedPreset): Promise<CompositionMount> { |
| 387 | if (!selected) throw new Error('agent-presets: explicit selection is required when no default is configured') |
| 388 | const entries = structuredClone(reviewComposition(spec).entries) |
| 389 | let found = 0 |
| 390 | function prepare(rows: EntryOptions[]) { |
| 391 | for (const row of rows) { |
| 392 | if (row.group === true || row.name === 'cordis:group' || row.name === '@deepseek-ai/cordis-plugin-group') { |
| 393 | prepare(row.config as EntryOptions[]) |
| 394 | } else if (row.name === AGENT_PRESETS) { |
| 395 | found++ |
| 396 | row.isolate = { ...row.isolate, agentPresets: true } |
| 397 | row.config = { codewhale_reviewed: { catalog, selected, base_url: baseUrl } } |
| 398 | } |
| 399 | } |
| 400 | } |
| 401 | prepare(entries) |
| 402 | if (found !== 1) throw new Error('exactly one raw roster row is required for a selected preset') |
| 403 | const source = JSON.stringify([{ insert: entries }]) |
| 404 | const top = await mountReviewedComposition(ctx, baseUrl, { ...spec, layers: [{ path: 'selected-preset.json', sha256: digest(source), source }] }) |
| 405 | try { |
| 406 | // Service injection requires ACTIVE. Build the raw roster's scope first, |
| 407 | // then mount its selected subtree, matching upstream's later standing mount. |
| 408 | const entry = [...top.tree.entries()].find(entry => entry.options.name === AGENT_PRESETS && !entry.disabled) |
| 409 | const roster = entry?.ctx.get('agentPresets' as any) as { mountSelected(): Promise<unknown> } | undefined |
| 410 | if (!roster) throw new Error('selected raw roster did not become active') |
| 411 | await roster.mountSelected() |
| 412 | return top |
| 413 | } catch (error) { await top.dispose(); throw error } |
| 414 | } |
| 415 |