prts-terrarchive 0.2.0 → 0.2.2
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/README.en.md +84 -18
- package/README.md +64 -15
- package/THIRD_PARTY_NOTICES.md +10 -0
- package/bin/build-runtime.mjs +32 -0
- package/bin/check-runtime.mjs +56 -0
- package/bin/install.js +24 -5
- package/contracts/corpus_i18n_v1.schema.json +10 -10
- package/cordis.patch.yml +4 -4
- package/docs/web-access.md +24 -0
- package/lib/client.js +84 -23
- package/lib/rhine/rhine.css +1 -1
- package/lib/rhine/rhine.js +208 -208
- package/lib/runtime/licenses/js-yaml-MIT.txt +21 -0
- package/lib/runtime/licenses/zod-MIT.txt +21 -0
- package/lib/runtime/versions.json +4 -0
- package/lib/runtime/yaml.js +3062 -0
- package/lib/runtime/zod.js +14356 -0
- package/lib/skins/common.css +59 -0
- package/lib/skins/endfield-aic.css +22 -9
- package/lib/skins/prts-agent.css +15 -5
- package/lib/skins/rhine-lab.css +9 -0
- package/package.json +15 -8
- package/presets/composition.js +53 -0
- package/presets/definition.js +2 -2
- package/presets/prts/agent.cordis.yml +1 -1
- package/presets/prts/preset.yml +1 -1
- package/presets/register.js +23 -10
- package/resources/runtime/yaml.js +1 -0
- package/resources/runtime/zod.js +1 -0
- package/skills/prts-composition/SKILL.md +60 -0
- package/skills/prts-investigation/SKILL.md +19 -102
- package/skills/prts-investigation/references/board-guide.md +131 -0
- package/skills/prts-retrieval/SKILL.md +5 -3
- package/src/composition-skill.js +19 -0
- package/src/i18n.js +11 -17
- package/src/index.js +63 -81
- package/src/investigation-review.js +14 -6
- package/src/investigation-store.js +97 -25
- package/src/investigation-tools.js +67 -30
- package/src/investigation-validation.js +33 -0
- package/src/manual-search.js +1 -1
- package/src/skill.js +25 -16
- package/src/skin-capabilities.js +28 -0
- package/src/tools.js +13 -0
- package/src/web-guidance.js +44 -0
- package/ui/rhine/archive-rack.ts +10 -8
- package/ui/rhine/catalogue.ts +16 -4
- package/ui/rhine/evidence-board-panel.ts +15 -0
- package/ui/rhine/investigation-board.css +3 -0
- package/ui/rhine/investigation-board.ts +71 -24
- package/ui/rhine/investigation-history.ts +15 -0
- package/ui/rhine/reading-room.css +14 -3
- package/ui/rhine/scene.ts +44 -24
- package/ui/rhine/tool-activity.ts +13 -6
- package/ui/rhine/types.ts +6 -0
- package/ui/rhine/workbench.ts +82 -40
package/lib/client.js
CHANGED
|
@@ -248,6 +248,7 @@ window.__ModuleLoader__.load({
|
|
|
248
248
|
'--dsw-alias-button-ghost-active-hover': canonical('rgba(17,18,20,.13)'),
|
|
249
249
|
'--dsw-alias-button-info-fill': canonical('#111214'),
|
|
250
250
|
'--dsw-alias-button-info-hover': canonical('#292b2f'),
|
|
251
|
+
'--dsw-alias-button-primary-fill': canonical('#111214'),
|
|
251
252
|
'--dsw-alias-button-primary-dimmed': canonical('#e5e6e2'),
|
|
252
253
|
'--dsw-alias-button-primary-hover': canonical('#34373b'),
|
|
253
254
|
'--dsw-alias-interactive-bg-active': canonical('rgba(17,18,20,.095)'),
|
|
@@ -396,6 +397,8 @@ window.__ModuleLoader__.load({
|
|
|
396
397
|
'--dsw-alias-label-secondary': canonical('#505747'),
|
|
397
398
|
'--dsw-alias-label-tertiary': canonical('#69705e'),
|
|
398
399
|
'--dsw-alias-label-caption': canonical('#717664'),
|
|
400
|
+
'--dsw-alias-button-primary-fill': canonical('#59634b'),
|
|
401
|
+
'--dsw-alias-button-primary-hover': canonical('#454e39'),
|
|
399
402
|
'--dsw-alias-button-info-fill': canonical('#59634b'),
|
|
400
403
|
'--dsw-alias-button-info-hover': canonical('#454e39'),
|
|
401
404
|
'--dsw-alias-button-elevated-fill': canonical('#f3f0e8'),
|
|
@@ -822,13 +825,18 @@ window.__ModuleLoader__.load({
|
|
|
822
825
|
const composerCard = conversation.querySelector('[data-composer-card]')
|
|
823
826
|
const composerSeatRect = composerSeat?.getBoundingClientRect?.()
|
|
824
827
|
const composerRect = composerCard?.getBoundingClientRect?.()
|
|
825
|
-
if (composerRect?.width > 0) {
|
|
828
|
+
if (conversation.dataset.phase === 'hero' && composerRect?.width > 0) {
|
|
826
829
|
const currentShift = Number.parseFloat(conversation.style.getPropertyValue('--prts-agent-composer-shift')) || 0
|
|
827
830
|
const actualCenter = composerRect.left + composerRect.width / 2
|
|
828
831
|
const nextShift = currentShift + contentCenter - actualCenter
|
|
829
832
|
if (Math.abs(nextShift - currentShift) > .5) {
|
|
830
833
|
conversation.style.setProperty('--prts-agent-composer-shift', `${nextShift}px`)
|
|
831
834
|
}
|
|
835
|
+
} else if (conversation.dataset.phase !== 'hero') {
|
|
836
|
+
// Only Hero applies the translation. Accumulating a correction while
|
|
837
|
+
// docked keeps triggering our style observer and shifts the next Hero.
|
|
838
|
+
conversation.style.removeProperty('--prts-agent-composer-shift')
|
|
839
|
+
conversation.style.removeProperty('--prts-agent-composer-y')
|
|
832
840
|
}
|
|
833
841
|
|
|
834
842
|
// 原版 CPU 在 fixed system-map,字标在其下方的 hero 流中。
|
|
@@ -2431,7 +2439,7 @@ window.__ModuleLoader__.load({
|
|
|
2431
2439
|
// 避免隐藏窗口仍持续渲染并让 renderer/GPU working set 不断增长。
|
|
2432
2440
|
const pageIsActive = () => document.visibilityState !== 'hidden'
|
|
2433
2441
|
&& globalThis.__PRTS_SHELL_BACKGROUND__ !== true
|
|
2434
|
-
&& !document.querySelector('[aria-modal="true"][role="dialog"]')
|
|
2442
|
+
&& !document.querySelector('[aria-modal="true"][role="dialog"], [data-plugin-panel]')
|
|
2435
2443
|
const syncMapActivity = () => {
|
|
2436
2444
|
const map = mapRef.current
|
|
2437
2445
|
if (!map) return
|
|
@@ -2647,8 +2655,11 @@ window.__ModuleLoader__.load({
|
|
|
2647
2655
|
}, [historyOpen])
|
|
2648
2656
|
const newSession = () => {
|
|
2649
2657
|
const options = currentSummary?.cwd ? { cwd: currentSummary.cwd } : {}
|
|
2650
|
-
|
|
2651
|
-
|
|
2658
|
+
const ctx = clientContext
|
|
2659
|
+
const navigation = optionalHostService(ctx, 'uiWorkspace')
|
|
2660
|
+
if (typeof navigation?.startSession === 'function') { navigation.startSession(); return }
|
|
2661
|
+
void ctx?.sessions?.create(options)
|
|
2662
|
+
.then((sessionId) => { if (clientContext === ctx) openHostSession(ctx, sessionId) })
|
|
2652
2663
|
.catch((error) => console.error('[prts-terrarchive] failed to create session', error))
|
|
2653
2664
|
}
|
|
2654
2665
|
const openSettings = () => { openHostSettings() }
|
|
@@ -2735,7 +2746,7 @@ window.__ModuleLoader__.load({
|
|
|
2735
2746
|
? sessionRows.map((session) => h('button', { type: 'button', key: session.id,
|
|
2736
2747
|
className: `aic-session${session.id === current ? ' active' : ''}`,
|
|
2737
2748
|
'aria-current': session.id === current ? 'page' : undefined,
|
|
2738
|
-
onClick: () => { clientContext
|
|
2749
|
+
onClick: () => { openHostSession(clientContext, session.id); setHistoryOpen(false) } },
|
|
2739
2750
|
h('b', null, session.title || (session.blank ? '新会话' : `会话_${String(session.id).slice(0, 6)}`)),
|
|
2740
2751
|
h('small', null, `${session.blank ? 'STANDBY' : 'ARCHIVED'} // ${new Date(session.updatedAt).toLocaleString('zh-CN')}`)))
|
|
2741
2752
|
: h('div', { className: 'aic-empty' }, 'NO SESSION RECORDS'))))
|
|
@@ -2792,7 +2803,7 @@ window.__ModuleLoader__.load({
|
|
|
2792
2803
|
const identity = version && (item.documentId || item.documentUid)
|
|
2793
2804
|
? `${version}:${item.documentId || item.documentUid}` : item.id
|
|
2794
2805
|
if (typeof identity !== 'string' || !identity) continue
|
|
2795
|
-
const incoming = { ...item, title: item.title.slice(0, 512),
|
|
2806
|
+
const incoming = { ...item, agentReceived: true, title: item.title.slice(0, 512),
|
|
2796
2807
|
id: version && item.documentUid ? `document:${version}:${item.documentUid}` : item.id || identity,
|
|
2797
2808
|
origin: ['cloud', 'web'].includes(item.origin) ? item.origin : 'local',
|
|
2798
2809
|
state: Object.hasOwn(rank, item.state) ? item.state : 'found',
|
|
@@ -3015,6 +3026,8 @@ window.__ModuleLoader__.load({
|
|
|
3015
3026
|
const operations = []
|
|
3016
3027
|
const toolCalls = []
|
|
3017
3028
|
const operationSources = new Map()
|
|
3029
|
+
let lastRead
|
|
3030
|
+
let lastReadSources = []
|
|
3018
3031
|
const entries = cache?.entries || rhineSnapshotEntries(order, nodes)
|
|
3019
3032
|
const userEntries = entries.filter(([, node]) => node?.kind === 'user' || node?.kind === 'steering')
|
|
3020
3033
|
const lastInput = userEntries.at(-1)
|
|
@@ -3189,8 +3202,17 @@ window.__ModuleLoader__.load({
|
|
|
3189
3202
|
wikiCitations.get(id).add(field)
|
|
3190
3203
|
}
|
|
3191
3204
|
}
|
|
3205
|
+
const delivered = sources.slice(sourceStart)
|
|
3206
|
+
if (isRhineReadTool(call.name)) {
|
|
3207
|
+
const readSources = delivered.filter(source => source.agentRead === true || source.state === 'read' || source.readRanges?.length > 0)
|
|
3208
|
+
if (readSources.length) {
|
|
3209
|
+
lastReadSources = readSources
|
|
3210
|
+
lastRead = { id: String(key), tool: call.name, kind: 'read', state: 'complete', sourceIds: [],
|
|
3211
|
+
...(Number.isFinite(root.callTime) ? { startedAt: root.callTime } : {}),
|
|
3212
|
+
...(Number.isFinite(root.time) ? { completedAt: root.time } : {}) }
|
|
3213
|
+
}
|
|
3214
|
+
}
|
|
3192
3215
|
if (current) {
|
|
3193
|
-
const delivered = sources.slice(sourceStart)
|
|
3194
3216
|
operationSources.set(String(key), delivered)
|
|
3195
3217
|
if (currentTurn) turnSources.push(...delivered)
|
|
3196
3218
|
}
|
|
@@ -3260,6 +3282,7 @@ window.__ModuleLoader__.load({
|
|
|
3260
3282
|
|| source.agentRead === true || source.state === 'read' || source.readRanges?.length > 0)
|
|
3261
3283
|
operation.sourceIds = [...new Set(delivered.flatMap(deliveredMaterialId))]
|
|
3262
3284
|
}
|
|
3285
|
+
if (lastRead) lastRead.sourceIds = [...new Set(lastReadSources.flatMap(deliveredMaterialId))]
|
|
3263
3286
|
// Report citations are recomputed from this answer only. Historical
|
|
3264
3287
|
// citations still mark the cumulative rack, but never enter a new report.
|
|
3265
3288
|
const reportIds = citations('report', materials, answer)
|
|
@@ -3287,6 +3310,7 @@ window.__ModuleLoader__.load({
|
|
|
3287
3310
|
}
|
|
3288
3311
|
const snapshot = { sessionId, running: !endKind && !interrupted && !terminalError && Boolean(sessionRunning || searching || assistantRunning || toolCalls.some(call => call.state === 'active')), searching, question, query, tool,
|
|
3289
3312
|
investigationId, outcome, turnSources: currentSources, reportSources,
|
|
3313
|
+
...(lastRead?.sourceIds.length ? { lastRead } : {}),
|
|
3290
3314
|
...(outcome === 'completed' && Number.isFinite(end?.time) ? { completedAt: end.time } : {}),
|
|
3291
3315
|
sources: materials, answer, records, operations, toolCalls, ...(error ? { error } : {}),
|
|
3292
3316
|
...(searching && activeDocumentId ? { activeDocumentId } : {}),
|
|
@@ -3427,13 +3451,26 @@ window.__ModuleLoader__.load({
|
|
|
3427
3451
|
}
|
|
3428
3452
|
// The workbench uses the same public session and model services as DSH's
|
|
3429
3453
|
// sidebar/composer. Each controller is owned by one open archive session.
|
|
3430
|
-
|
|
3454
|
+
// DSH Cordis has no optional inject group. Resolve optional capabilities at
|
|
3455
|
+
// action time through get(), which also avoids retaining an unloaded service.
|
|
3456
|
+
function optionalHostService(ctx, name) {
|
|
3457
|
+
return typeof ctx?.get === 'function' ? ctx.get(name) : ctx?.[name]
|
|
3458
|
+
}
|
|
3459
|
+
function openHostSession(ctx, sessionId) {
|
|
3460
|
+
const navigation = optionalHostService(ctx, 'uiWorkspace')
|
|
3461
|
+
if (typeof navigation?.openSession === 'function') navigation.openSession(sessionId)
|
|
3462
|
+
else if (typeof ctx?.sessions?.open === 'function') ctx.sessions.open(sessionId)
|
|
3463
|
+
else throw new Error('当前宿主未提供会话导航,请更新 DSH')
|
|
3464
|
+
}
|
|
3465
|
+
function createRhineHostControls(ctx, sessionId, isCurrent = () => true) {
|
|
3431
3466
|
const sessions = ctx.sessions
|
|
3432
3467
|
const session = sessions.binding(sessionId)?.session
|
|
3433
3468
|
const directory = ctx.modelDirectories?.directoryFor(sessionId)
|
|
3434
3469
|
const listeners = new Set()
|
|
3435
3470
|
let disposed = false
|
|
3436
3471
|
let busy = false
|
|
3472
|
+
let cancelling = false
|
|
3473
|
+
const lifetime = new AbortController()
|
|
3437
3474
|
let loading = false
|
|
3438
3475
|
let error = ''
|
|
3439
3476
|
let modes = []
|
|
@@ -3479,7 +3516,7 @@ window.__ModuleLoader__.load({
|
|
|
3479
3516
|
workspace: workspace?.title || workspace?.path || current?.cwd || '',
|
|
3480
3517
|
blank: Boolean(lifecycle?.blank ?? current?.blank),
|
|
3481
3518
|
running: Boolean(lifecycle?.running ?? current?.running),
|
|
3482
|
-
busy: busy || modelState?.status === 'selecting', loading,
|
|
3519
|
+
busy: busy || modelState?.status === 'selecting', loading, cancelling,
|
|
3483
3520
|
canSubmit: modelState?.routable !== false && !lifecycle?.removed,
|
|
3484
3521
|
error: error || (modelState?.routable === false ? '当前模型没有可用连接,请选择其他模型。' : modelState?.error || ''),
|
|
3485
3522
|
mode: typeof current?.projectionValues?.agentPreset === 'string' ? current.projectionValues.agentPreset : '',
|
|
@@ -3526,7 +3563,12 @@ window.__ModuleLoader__.load({
|
|
|
3526
3563
|
timer = setTimeout(emit, frame === null ? 16 : 100)
|
|
3527
3564
|
}
|
|
3528
3565
|
const assertActive = () => {
|
|
3529
|
-
|
|
3566
|
+
const list = sessions.list.getSnapshot()
|
|
3567
|
+
// New DSH owns selection in UI Workspace. The mounting conversation
|
|
3568
|
+
// supplies its identity; legacy hosts still expose list.current.
|
|
3569
|
+
if (disposed || clientContext !== ctx || !isCurrent()
|
|
3570
|
+
|| Object.hasOwn(list, 'current') && list.current !== sessionId
|
|
3571
|
+
|| session?.getSnapshot?.().removed) {
|
|
3530
3572
|
throw makeAbortError('会话已切换,请在当前会话重试')
|
|
3531
3573
|
}
|
|
3532
3574
|
}
|
|
@@ -3534,6 +3576,11 @@ window.__ModuleLoader__.load({
|
|
|
3534
3576
|
if (!result?.ok) throw new Error(result?.error?.message || `${label}失败`)
|
|
3535
3577
|
return result.value
|
|
3536
3578
|
}
|
|
3579
|
+
const selectHostModel = async (directory, selection) => {
|
|
3580
|
+
const result = await directory.select(selection)
|
|
3581
|
+
// Older DSH throws on failure and resolves void; 0.1.7 returns RemoteResult.
|
|
3582
|
+
if (result !== undefined) unwrap(result, '切换模型')
|
|
3583
|
+
}
|
|
3537
3584
|
const run = async (action) => {
|
|
3538
3585
|
assertActive()
|
|
3539
3586
|
if (busy || directory?.store.getSnapshot().status === 'selecting') throw new Error('上一项设置正在保存,请稍候')
|
|
@@ -3573,6 +3620,8 @@ window.__ModuleLoader__.load({
|
|
|
3573
3620
|
if (!id || !modes.some(mode => mode.id === id)) throw new Error('该模式暂不可用,请刷新模式列表')
|
|
3574
3621
|
}
|
|
3575
3622
|
const createSession = (requestedMode) => run(async () => {
|
|
3623
|
+
const navigation = optionalHostService(ctx, 'layout')?.beginNavigation?.()
|
|
3624
|
+
const assertNavigation = () => { assertActive(); navigation?.throwIfAborted() }
|
|
3576
3625
|
const original = summaryOf()
|
|
3577
3626
|
const mode = requestedMode || original?.projectionValues?.agentPreset
|
|
3578
3627
|
if (requestedMode) requireMode(requestedMode)
|
|
@@ -3603,20 +3652,24 @@ window.__ModuleLoader__.load({
|
|
|
3603
3652
|
}
|
|
3604
3653
|
const pending = pendingCreated
|
|
3605
3654
|
const id = pending.id
|
|
3655
|
+
let reference
|
|
3606
3656
|
try {
|
|
3607
|
-
|
|
3657
|
+
assertNavigation()
|
|
3658
|
+
reference = sessions.retain?.(id, { source: 'controllerOperation', signal: lifetime.signal })
|
|
3659
|
+
if (reference) await waitWithDeadline(reference.ready, { timeout: 12000, label: '新对话' })
|
|
3660
|
+
assertNavigation()
|
|
3608
3661
|
if (pending.mode) unwrap(await ctx.remote.agentPresets.select(id, pending.mode), '设置新对话模式')
|
|
3609
|
-
|
|
3610
|
-
if (pending.model) await ctx.modelDirectories.directoryFor(id)
|
|
3611
|
-
|
|
3612
|
-
|
|
3662
|
+
assertNavigation()
|
|
3663
|
+
if (pending.model) await selectHostModel(ctx.modelDirectories.directoryFor(id), { ...pending.model })
|
|
3664
|
+
assertNavigation()
|
|
3665
|
+
openHostSession(ctx, id)
|
|
3613
3666
|
if (pendingCreated === pending) pendingCreated = null
|
|
3614
3667
|
} catch (caught) {
|
|
3615
3668
|
// Keep the old conversation selected until the new one is fully
|
|
3616
3669
|
// configured. Retrying the same selection reuses this blank entry.
|
|
3617
3670
|
if (caught?.name !== 'AbortError') throw new Error(`新对话已创建,但尚未打开:${caught?.message || '配置失败'}。再次执行相同操作将重试此对话。`)
|
|
3618
3671
|
throw caught
|
|
3619
|
-
}
|
|
3672
|
+
} finally { reference?.release() }
|
|
3620
3673
|
})
|
|
3621
3674
|
for (const [source, store] of [['sessions', sessions.list], ['workspaces', ctx.workspaces?.list], ['session', session], ['models', directory?.store]]) {
|
|
3622
3675
|
if (store?.subscribe) cleanups.push(store.subscribe(() => requestEmit(source)))
|
|
@@ -3628,7 +3681,7 @@ window.__ModuleLoader__.load({
|
|
|
3628
3681
|
subscribe(listener) { listeners.add(listener); return () => listeners.delete(listener) },
|
|
3629
3682
|
openSession: (id) => run(async () => {
|
|
3630
3683
|
if (!getState().sessions.some(item => item.id === id)) throw new Error('该对话已不可用,请刷新会话列表')
|
|
3631
|
-
|
|
3684
|
+
openHostSession(ctx, id)
|
|
3632
3685
|
}),
|
|
3633
3686
|
selectMode: (id) => run(async () => {
|
|
3634
3687
|
requireMode(id)
|
|
@@ -3645,14 +3698,19 @@ window.__ModuleLoader__.load({
|
|
|
3645
3698
|
const current = directory.store.getSnapshot().current
|
|
3646
3699
|
const effort = current?.reasoningEffort
|
|
3647
3700
|
const reasoningEffort = choice.reasoning?.efforts?.some(item => item.id === effort) ? effort : choice.reasoning?.defaultEffort
|
|
3648
|
-
await directory
|
|
3701
|
+
await selectHostModel(directory, { provider: choice.provider, model: choice.model,
|
|
3649
3702
|
...(reasoningEffort ? { reasoningEffort } : {}) })
|
|
3650
3703
|
}),
|
|
3651
|
-
|
|
3704
|
+
async cancel() {
|
|
3705
|
+
assertActive()
|
|
3706
|
+
if (cancelling) return
|
|
3652
3707
|
if (!session?.cancel) throw new Error('当前会话尚未连接')
|
|
3653
|
-
|
|
3654
|
-
|
|
3655
|
-
|
|
3708
|
+
cancelling = true; error = ''; emit()
|
|
3709
|
+
try { unwrap(await session.cancel(), '停止当前任务') }
|
|
3710
|
+
catch (caught) { if (!disposed && caught?.name !== 'AbortError') error = caught?.message || '停止失败'; throw caught }
|
|
3711
|
+
finally { cancelling = false; emit() }
|
|
3712
|
+
},
|
|
3713
|
+
dispose() { disposed = true; lifetime.abort(); cancelPending(); monitor = undefined; pendingCreated = null; for (const cleanup of cleanups) cleanup(); listeners.clear() },
|
|
3656
3714
|
}
|
|
3657
3715
|
return controls
|
|
3658
3716
|
}
|
|
@@ -3699,6 +3757,9 @@ window.__ModuleLoader__.load({
|
|
|
3699
3757
|
setOpen(value === 'rhine-lab')
|
|
3700
3758
|
}
|
|
3701
3759
|
rhineSkinListeners.add(listener)
|
|
3760
|
+
// Skin loading can finish between this component's initial render and
|
|
3761
|
+
// effect subscription. Reconcile the live value for existing sessions.
|
|
3762
|
+
listener(activeSkin)
|
|
3702
3763
|
return () => rhineSkinListeners.delete(listener)
|
|
3703
3764
|
}, [])
|
|
3704
3765
|
useEffect(() => { workbench.current?.update(snapshot) }, [snapshot])
|
|
@@ -3746,7 +3807,7 @@ window.__ModuleLoader__.load({
|
|
|
3746
3807
|
void loadRhineBundle().then((runtime) => {
|
|
3747
3808
|
if (disposed || clientContext !== ctx || signal?.aborted) return
|
|
3748
3809
|
status.remove()
|
|
3749
|
-
hostControls = ctx.sessions?.list?.getSnapshot ? createRhineHostControls(ctx, sessionId) : null
|
|
3810
|
+
hostControls = ctx.sessions?.list?.getSnapshot ? createRhineHostControls(ctx, sessionId, () => latestSnapshot.current.sessionId === sessionId) : null
|
|
3750
3811
|
instance = runtime.mountRhineWorkbench(overlay, {
|
|
3751
3812
|
assetBase: RHINE_BASE, snapshot: latestSnapshot.current, snapshotDiagnostics,
|
|
3752
3813
|
...(hostControls ? { host: hostControls } : {}),
|