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.
Files changed (56) hide show
  1. package/README.en.md +84 -18
  2. package/README.md +64 -15
  3. package/THIRD_PARTY_NOTICES.md +10 -0
  4. package/bin/build-runtime.mjs +32 -0
  5. package/bin/check-runtime.mjs +56 -0
  6. package/bin/install.js +24 -5
  7. package/contracts/corpus_i18n_v1.schema.json +10 -10
  8. package/cordis.patch.yml +4 -4
  9. package/docs/web-access.md +24 -0
  10. package/lib/client.js +84 -23
  11. package/lib/rhine/rhine.css +1 -1
  12. package/lib/rhine/rhine.js +208 -208
  13. package/lib/runtime/licenses/js-yaml-MIT.txt +21 -0
  14. package/lib/runtime/licenses/zod-MIT.txt +21 -0
  15. package/lib/runtime/versions.json +4 -0
  16. package/lib/runtime/yaml.js +3062 -0
  17. package/lib/runtime/zod.js +14356 -0
  18. package/lib/skins/common.css +59 -0
  19. package/lib/skins/endfield-aic.css +22 -9
  20. package/lib/skins/prts-agent.css +15 -5
  21. package/lib/skins/rhine-lab.css +9 -0
  22. package/package.json +15 -8
  23. package/presets/composition.js +53 -0
  24. package/presets/definition.js +2 -2
  25. package/presets/prts/agent.cordis.yml +1 -1
  26. package/presets/prts/preset.yml +1 -1
  27. package/presets/register.js +23 -10
  28. package/resources/runtime/yaml.js +1 -0
  29. package/resources/runtime/zod.js +1 -0
  30. package/skills/prts-composition/SKILL.md +60 -0
  31. package/skills/prts-investigation/SKILL.md +19 -102
  32. package/skills/prts-investigation/references/board-guide.md +131 -0
  33. package/skills/prts-retrieval/SKILL.md +5 -3
  34. package/src/composition-skill.js +19 -0
  35. package/src/i18n.js +11 -17
  36. package/src/index.js +63 -81
  37. package/src/investigation-review.js +14 -6
  38. package/src/investigation-store.js +97 -25
  39. package/src/investigation-tools.js +67 -30
  40. package/src/investigation-validation.js +33 -0
  41. package/src/manual-search.js +1 -1
  42. package/src/skill.js +25 -16
  43. package/src/skin-capabilities.js +28 -0
  44. package/src/tools.js +13 -0
  45. package/src/web-guidance.js +44 -0
  46. package/ui/rhine/archive-rack.ts +10 -8
  47. package/ui/rhine/catalogue.ts +16 -4
  48. package/ui/rhine/evidence-board-panel.ts +15 -0
  49. package/ui/rhine/investigation-board.css +3 -0
  50. package/ui/rhine/investigation-board.ts +71 -24
  51. package/ui/rhine/investigation-history.ts +15 -0
  52. package/ui/rhine/reading-room.css +14 -3
  53. package/ui/rhine/scene.ts +44 -24
  54. package/ui/rhine/tool-activity.ts +13 -6
  55. package/ui/rhine/types.ts +6 -0
  56. 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
- void clientContext?.sessions?.create(options)
2651
- .then((sessionId) => { clientContext?.sessions?.open(sessionId) })
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?.sessions?.open(session.id); setHistoryOpen(false) } },
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
- function createRhineHostControls(ctx, sessionId) {
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
- if (disposed || clientContext !== ctx || sessions.list.getSnapshot().current !== sessionId) {
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
- assertActive()
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
- assertActive()
3610
- if (pending.model) await ctx.modelDirectories.directoryFor(id).select({ ...pending.model })
3611
- assertActive()
3612
- sessions.open(id)
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
- sessions.open(id)
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.select({ provider: choice.provider, model: choice.model,
3701
+ await selectHostModel(directory, { provider: choice.provider, model: choice.model,
3649
3702
  ...(reasoningEffort ? { reasoningEffort } : {}) })
3650
3703
  }),
3651
- cancel: () => run(async () => {
3704
+ async cancel() {
3705
+ assertActive()
3706
+ if (cancelling) return
3652
3707
  if (!session?.cancel) throw new Error('当前会话尚未连接')
3653
- unwrap(await session.cancel(), '停止当前任务')
3654
- }),
3655
- dispose() { disposed = true; cancelPending(); monitor = undefined; pendingCreated = null; for (const cleanup of cleanups) cleanup(); listeners.clear() },
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 } : {}),