dsh-super-code 0.3.2 → 0.3.3

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/client/index.js CHANGED
@@ -256,6 +256,13 @@ const ActionButton = React.forwardRef(function ActionButton({ children, title, .
256
256
 
257
257
  const LOCALE_NS = 'dshSuperCode'
258
258
  const zh = {
259
+ 'memory.add': '新增记忆', 'memory.edit': '编辑', 'memory.cancel': '取消', 'memory.save': '保存', 'memory.delete': '删除', 'memory.deleting': '正在删除…', 'memory.saving': '正在保存…',
260
+ 'memory.topic': '主题', 'memory.kind': '类型', 'memory.summaryLabel': '标题', 'memory.detailLabel': '内容', 'memory.manual': '手动维护',
261
+ 'memory.topicHint': '字母、数字、点、下划线或短横线,最多 96 个字符。',
262
+ 'memory.deleteTitle': '删除记忆', 'memory.deleteHint': '确定删除“{summary}”吗?删除后无法恢复。',
263
+ 'memory.writeError': '操作失败,内容已保留,请重试。', 'memory.conflict': '这条记忆已在别处更改,请取消后刷新再编辑。',
264
+ 'memory.missingEdit': '这条记忆已被删除,输入内容已保留。', 'memory.full': '该主题或记忆库已满,请删除不再需要的条目。',
265
+ 'workspace.title': 'Super Code', 'workspace.description': 'Agent 执行树与记忆库', 'workspace.resize': '调整执行树与记忆库高度',
259
266
  'tree.collapsedLabel': '已收起', 'tree.organizeLabel': '自动整理',
260
267
  'detail.toolCall': '工具调用', 'detail.toolResult': '工具结果', 'detail.toolError': '工具失败',
261
268
  'detail.cache': '缓存', 'detail.info': '详情', 'tree.countCompact': '{shown}/{total} · 运行 {running}',
@@ -319,6 +326,13 @@ const zh = {
319
326
  'detail.path': 'Agent 路径', 'detail.title': 'Agent 详情', 'detail.refresh': '刷新对话详情', 'detail.task': '任务', 'detail.agent': 'Agent', 'detail.interrupted': 'Agent · 已中断', 'detail.error': '执行错误', 'detail.incomplete': '执行未完成,请稍后重试。', 'detail.input': '输入', 'detail.output': '输出', 'detail.cacheRead': '缓存读取', 'detail.cacheWrite': '缓存写入', 'detail.total': 'Token 总量', 'detail.hitRate': '缓存命中率', 'detail.moreUsage': '用量详情', 'detail.unavailable': '未提供', 'detail.reading': '正在读取对话…', 'detail.read': '读取中…', 'detail.older': '加载更早记录', 'detail.latest': '回到最新', 'detail.empty': '暂无执行内容', 'detail.new': '有新内容 · 查看最新', 'detail.loadError': '对话详情读取失败', 'detail.connection': '连接中断,正在重连', 'detail.historyError': '历史记录读取失败', 'detail.open': '展开全文', 'detail.noAccess': '无法打开详情,请重试', 'tree.loadError': '部分执行记录读取失败,请刷新重试', 'detail.unknown': '用量未知', 'main.agent': '主 Agent', 'tree.open': '点击查看详情', 'tree.expand': '展开', 'tree.collapse': '收起', 'image': '[图片]'
320
327
  }
321
328
  const en = {
329
+ 'memory.add': 'Add memory', 'memory.edit': 'Edit', 'memory.cancel': 'Cancel', 'memory.save': 'Save', 'memory.delete': 'Delete', 'memory.deleting': 'Deleting…', 'memory.saving': 'Saving…',
330
+ 'memory.topic': 'Topic', 'memory.kind': 'Type', 'memory.summaryLabel': 'Title', 'memory.detailLabel': 'Content', 'memory.manual': 'Manually maintained',
331
+ 'memory.topicHint': 'Letters, digits, dots, underscores or hyphens; up to 96 characters.',
332
+ 'memory.deleteTitle': 'Delete memory', 'memory.deleteHint': 'Delete “{summary}”? This cannot be undone.',
333
+ 'memory.writeError': 'The operation failed. Your input is retained; try again.', 'memory.conflict': 'This memory changed elsewhere. Cancel and refresh before editing again.',
334
+ 'memory.missingEdit': 'This memory was deleted. Your input is retained.', 'memory.full': 'This topic or library is full. Remove entries you no longer need.',
335
+ 'workspace.title': 'Super Code', 'workspace.description': 'Agent tree and memory library', 'workspace.resize': 'Resize agent tree and memory library',
322
336
  'tree.collapsedLabel': 'Collapsed', 'tree.organizeLabel': 'Organize',
323
337
  'detail.toolCall': 'Tool call', 'detail.toolResult': 'Tool result', 'detail.toolError': 'Tool failed',
324
338
  'detail.cache': 'Cache', 'detail.info': 'Details', 'tree.countCompact': '{shown}/{total} · Running {running}',
@@ -611,9 +625,10 @@ function agentHistoryWindow(count, scrollTop, height) {
611
625
  }
612
626
 
613
627
  /** Local scroll state keeps the graph out of the scrolling render path. */
614
- function AgentHistory({ view, hidden, totals, open, t = key => zh[key] || key }) {
615
- const [filters, setFilters] = useState({ query: '', range: 'all', from: '', to: '', sort: 'time-desc' })
616
- const [scroll, setScroll] = useState({ top: 0, height: 168 })
628
+ function AgentHistory({ view, hidden, totals, open, saved, t = key => zh[key] || key }) {
629
+ const [filters, setFilters] = useState(saved.historyFilters || { query: '', range: 'all', from: '', to: '', sort: 'time-desc' })
630
+ const [scroll, setScroll] = useState({ top: saved.historyTop || 0, height: 168 })
631
+ const previousFilters = useRef(filters)
617
632
  const viewport = useRef(null), focusTarget = useRef('')
618
633
  const records = useMemo(() => agentHistoryRows(view, hidden, totals, { ...filters, t }), [view, hidden, totals, filters, t])
619
634
  const language = localeCode(t)
@@ -622,20 +637,25 @@ function AgentHistory({ view, hidden, totals, open, t = key => zh[key] || key })
622
637
  const range = agentHistoryRange(filters.range, filters.from, filters.to)
623
638
  const change = (key, value) => setFilters(previous => ({ ...previous, [key]: value }))
624
639
  useLayoutEffect(() => {
625
- viewport.current.scrollTop = 0
626
- setScroll(previous => ({ ...previous, top: 0 }))
627
- }, [filters])
640
+ saved.historyFilters = filters
641
+ if (previousFilters.current !== filters) {
642
+ viewport.current.scrollTop = 0; saved.historyTop = 0
643
+ setScroll(previous => ({ ...previous, top: 0 }))
644
+ }
645
+ previousFilters.current = filters
646
+ }, [filters, saved])
628
647
  useLayoutEffect(() => {
629
648
  const element = viewport.current
630
649
  const measure = () => setScroll(previous => previous.top === element.scrollTop && previous.height === element.clientHeight
631
650
  ? previous : { top: element.scrollTop, height: element.clientHeight })
651
+ element.scrollTop = saved.historyTop || 0
632
652
  measure()
633
653
  const observer = new ResizeObserver(measure)
634
654
  observer.observe(element)
635
655
  return () => observer.disconnect()
636
656
  }, [])
637
657
  useLayoutEffect(() => {
638
- viewport.current.scrollTop = window.top
658
+ viewport.current.scrollTop = window.top; saved.historyTop = window.top
639
659
  if (focusTarget.current) {
640
660
  const target = Array.from(viewport.current.querySelectorAll('button')).find(button => button.dataset.historyId === focusTarget.current)
641
661
  if (target) { target.focus({ preventScroll: true }); focusTarget.current = '' }
@@ -680,7 +700,7 @@ function AgentHistory({ view, hidden, totals, open, t = key => zh[key] || key })
680
700
  !records.length && React.createElement('p', { className: 'dsh-super-code-history-empty' }, text(t, 'history.empty'))))
681
701
  }
682
702
 
683
- function AgentTree({ useSessions, useSessionStatus, sessionId, useTabInfo, treeStates, openDetail, refresh, watchCatalog, t = key => zh[key] || key }) {
703
+ function AgentTree({ useSessions, useSessionStatus, sessionId, useTabInfo, treeStates, openDetail, refresh, watchCatalog, onToggle, t = key => zh[key] || key }) {
684
704
  const catalog = useSessions(s => s)
685
705
  const statuses = useSessionStatus ? useSessionStatus(s => s) : undefined
686
706
  const state = useMemo(() => agentSessionState(catalog, sessionId, statuses), [catalog, sessionId, statuses])
@@ -694,7 +714,59 @@ function AgentTree({ useSessions, useSessionStatus, sessionId, useTabInfo, treeS
694
714
  }
695
715
  return value
696
716
  }, [treeStates, tab.signal, view.root])
697
- return React.createElement(AgentTreeBody, { key: view.root, state, view, saved, openDetail, refresh, watchCatalog, t })
717
+ return React.createElement(AgentTreeBody, { key: view.root, state, view, saved, openDetail, refresh, watchCatalog, onToggle, t })
718
+ }
719
+
720
+ function WorkspaceSectionToggle({ section, expanded, onToggle, children, t }) {
721
+ const label = text(t, section === 'tree' ? 'tree.title' : 'memory.title')
722
+ return React.createElement(ActionButton, { type: 'button', className: 'dsh-super-code-section-toggle',
723
+ 'data-workspace-section': section, 'aria-label': label, 'aria-expanded': expanded, onClick: onToggle },
724
+ React.createElement(Glyph, { type: 'chevron' }),
725
+ React.createElement(Glyph, { type: section === 'tree' ? 'branch' : 'memory' }),
726
+ React.createElement('strong', null, label), children)
727
+ }
728
+
729
+ /** One active section guarantees exactly one open view; tab state retains drafts. */
730
+ function SuperCodeWorkspace(props) {
731
+ const { tab } = props.useTabInfo()
732
+ const address = typeof tab.navigation?.params?.memory === 'string' ? tab.navigation.params.memory : tab.contentId
733
+ const navigationKey = `${address}/${tab.navigation?.revision || 0}`
734
+ const memoryTarget = memoryLibraryTarget(address, '')
735
+ const saved = useMemo(() => {
736
+ const value = props.workspaceStates.get(tab.signal) || { active: 'tree' }
737
+ if (value.navigationKey !== navigationKey) {
738
+ value.navigationKey = navigationKey
739
+ if (memoryTarget.id) value.active = 'memory'
740
+ }
741
+ props.workspaceStates.set(tab.signal, value)
742
+ return value
743
+ }, [props.workspaceStates, tab.signal])
744
+ const [active, setActive] = useState(saved.active === 'memory' ? 'memory' : 'tree')
745
+ const layout = useRef(null), focusSection = useRef('')
746
+ const toggle = () => {
747
+ const next = active === 'tree' ? 'memory' : 'tree'
748
+ saved.active = next; focusSection.current = next; setActive(next)
749
+ }
750
+ useLayoutEffect(() => {
751
+ if (saved.navigationKey === navigationKey) return
752
+ saved.navigationKey = navigationKey
753
+ if (memoryTarget.id) { saved.active = 'memory'; setActive('memory') }
754
+ }, [saved, navigationKey])
755
+ useLayoutEffect(() => {
756
+ if (!focusSection.current) return
757
+ layout.current.querySelector(`[data-workspace-section="${focusSection.current}"]`)?.focus({ preventScroll: true })
758
+ focusSection.current = ''
759
+ }, [active])
760
+ return React.createElement('section', { className: 'dsh-super-code-workspace', 'aria-label': text(props.t, 'workspace.title') },
761
+ React.createElement('div', { ref: layout, className: 'dsh-super-code-workspace-layout', 'data-active-section': active, style: {
762
+ gridTemplateRows: active === 'tree' ? 'minmax(280px, 1fr) 44px' : '44px minmax(280px, 1fr)',
763
+ } },
764
+ active === 'tree' ? React.createElement(AgentTree, { ...props, key: 'tree', onToggle: toggle })
765
+ : React.createElement('header', { key: 'tree-collapsed', className: 'dsh-super-code-workspace-collapsed' },
766
+ React.createElement(WorkspaceSectionToggle, { section: 'tree', expanded: false, onToggle: toggle, t: props.t })),
767
+ active === 'memory' ? React.createElement(MemoryLibrary, { ...props, key: 'memory', onToggle: toggle })
768
+ : React.createElement('header', { key: 'memory-collapsed', className: 'dsh-super-code-workspace-collapsed' },
769
+ React.createElement(WorkspaceSectionToggle, { section: 'memory', expanded: false, onToggle: toggle, t: props.t }))))
698
770
  }
699
771
 
700
772
  function AgentCatalogWatch({ id, watchCatalog }) {
@@ -705,6 +777,8 @@ function AgentCatalogWatch({ id, watchCatalog }) {
705
777
  return null
706
778
  }
707
779
 
780
+ // Keep connector anchors and canvas bounds in step with the node's text stack.
781
+ const agentTreeNodeHeight = 120, agentTreeLevelSpacing = 152
708
782
  function agentTreeLayout(view, limit) {
709
783
  const { rows, more } = visibleAgentRows(view, () => true, limit)
710
784
  const positions = new Map(), edges = []
@@ -713,14 +787,14 @@ function agentTreeLayout(view, limit) {
713
787
  const row = rows[i]
714
788
  const children = (view.children.get(row.id) || []).filter(id => positions.has(id) && positions.get(id).level === row.level + 1)
715
789
  const x = children.length ? children.reduce((sum, id) => sum + positions.get(id).x, 0) / children.length : 70 + leaves++ * 140
716
- positions.set(row.id, { ...row, x, y: 18 + (row.level - 1) * 128 })
790
+ positions.set(row.id, { ...row, x, y: 18 + (row.level - 1) * agentTreeLevelSpacing })
717
791
  for (const child of children) edges.push({ parent: row.id, child })
718
792
  }
719
793
  const width = Math.max(140, leaves * 140)
720
794
  // Reverse the postorder leaf allocation to preserve catalog order.
721
795
  for (const position of positions.values()) position.x = width - position.x
722
796
  return { rows: rows.map(row => positions.get(row.id)), edges, positions, more, width,
723
- height: Math.max(140, ...rows.map(row => row.level * 128)) }
797
+ height: Math.max(140, ...rows.map(row => row.level * agentTreeLevelSpacing)) }
724
798
  }
725
799
 
726
800
  function agentExecutionStatus(node, isRoot, t = key => zh[key] || key) {
@@ -729,7 +803,7 @@ function agentExecutionStatus(node, isRoot, t = key => zh[key] || key) {
729
803
  return isRoot || node.mode === 'continuable' ? { tone: 'idle', label: text(t, 'tree.legend.idle') } : { tone: 'stopped', label: text(t, 'tree.legend.stopped') }
730
804
  }
731
805
 
732
- function AgentTreeBody({ state, view, saved, openDetail, refresh, watchCatalog, t = key => zh[key] || key }) {
806
+ function AgentTreeBody({ state, view, saved, openDetail, refresh, watchCatalog, onToggle, t = key => zh[key] || key }) {
733
807
  const [pan, setPan] = useState(saved.pan || { x: 0, y: 0 })
734
808
  const [zoom, setZoom] = useState(saved.zoom || 1)
735
809
  const viewport = useRef(null), graph = useRef(null)
@@ -768,9 +842,9 @@ function AgentTreeBody({ state, view, saved, openDetail, refresh, watchCatalog,
768
842
  const [focused, setFocused] = useState(saved.focused)
769
843
  const [expanded, setExpanded] = useState(saved.expanded || new Set())
770
844
  const [collapsed, setCollapsed] = useState(saved.collapsed || new Set())
771
- const [historyOpen, setHistoryOpen] = useState(false)
845
+ const [historyOpen, setHistoryOpen] = useState(saved.historyOpen || false)
772
846
  const [compact, setCompact] = useState(false)
773
- useEffect(() => { Object.assign(saved, { limit, focused, expanded, collapsed }) }, [saved, limit, focused, expanded, collapsed])
847
+ useEffect(() => { Object.assign(saved, { limit, focused, expanded, collapsed, historyOpen }) }, [saved, limit, focused, expanded, collapsed, historyOpen])
774
848
  const catalogs = state.subagentsByParent || {}
775
849
  const display = useMemo(() => agentTreeDisplay(view, catalogs, { expanded, collapsed, focused, compact }), [view, catalogs, expanded, collapsed, focused, compact])
776
850
  const { rows, more, edges, positions, width, height } = useMemo(() => agentTreeLayout(display.view, limit), [display, limit])
@@ -845,8 +919,11 @@ function AgentTreeBody({ state, view, saved, openDetail, refresh, watchCatalog,
845
919
  watched.map(id => React.createElement(AgentCatalogWatch, { key: id, id, watchCatalog })),
846
920
  React.createElement('header', { className: 'dsh-super-code-tree-header' },
847
921
  React.createElement('div', { className: 'dsh-super-code-tree-heading' },
848
- React.createElement('div', { className: 'dsh-super-code-title-line' }, React.createElement(Glyph, { type: 'branch' }), React.createElement('strong', null, text(t, 'tree.title'))),
849
- React.createElement('span', { className: 'dsh-super-code-tree-count', title: text(t, 'tree.count', { shown: rows.length, total: view.nodes.size, running }) }, text(t, 'tree.countCompact', { shown: rows.length, total: view.nodes.size, running }))),
922
+ onToggle ? React.createElement(WorkspaceSectionToggle, { section: 'tree', expanded: true, onToggle, t },
923
+ React.createElement('span', { className: 'dsh-super-code-tree-count' }, text(t, 'tree.countCompact', { shown: rows.length, total: view.nodes.size, running })))
924
+ : React.createElement(React.Fragment, null,
925
+ React.createElement('div', { className: 'dsh-super-code-title-line' }, React.createElement(Glyph, { type: 'branch' }), React.createElement('strong', null, text(t, 'tree.title'))),
926
+ React.createElement('span', { className: 'dsh-super-code-tree-count', title: text(t, 'tree.count', { shown: rows.length, total: view.nodes.size, running }) }, text(t, 'tree.countCompact', { shown: rows.length, total: view.nodes.size, running })))),
850
927
  React.createElement('div', { className: 'dsh-super-code-tree-toolbar' },
851
928
  React.createElement(ActionButton, { type: 'button', className: 'dsh-super-code-icon-button', disabled: busy || !view.root, onClick: reload, title: text(t, 'tree.refresh'), 'aria-label': text(t, 'tree.refresh') }, React.createElement(IconRefreshOutline16)),
852
929
  React.createElement('div', { className: 'dsh-super-code-graph-controls', 'aria-label': text(t, 'tree.canvas') },
@@ -856,13 +933,13 @@ function AgentTreeBody({ state, view, saved, openDetail, refresh, watchCatalog,
856
933
  React.createElement(ActionButton, { type: 'button', className: 'dsh-super-code-icon-button', title: text(t, 'tree.fit'), 'aria-label': text(t, 'tree.fit'), onClick: fit }, React.createElement(Glyph, { type: 'fit' })),
857
934
  (display.hidden.length > 0 || expanded.size > 0 || collapsed.size > 0) && React.createElement('div', { className: 'dsh-super-code-history-controls' },
858
935
  React.createElement(ActionButton, { type: 'button', title: text(t, 'tree.collapsed', { count: display.hidden.length }), 'aria-label': text(t, 'tree.collapsed', { count: display.hidden.length }), 'aria-expanded': historyOpen, onClick: () => setHistoryOpen(!historyOpen) }, React.createElement(Glyph, { type: 'history' }), React.createElement('span', { className: 'dsh-super-code-action-label' }, text(t, 'tree.collapsedLabel')), React.createElement('span', { className: 'dsh-super-code-action-count' }, display.hidden.length)),
859
- (expanded.size > 0 || collapsed.size > 0) && React.createElement(ActionButton, { type: 'button', title: text(t, 'tree.organize'), 'aria-label': text(t, 'tree.organize'), onClick: () => { autoFit.current = true; setExpanded(new Set()); setCollapsed(new Set()); setFocused(state.current) } }, React.createElement(Glyph, { type: 'organize' }), React.createElement('span', { className: 'dsh-super-code-action-label' }, text(t, 'tree.organizeLabel'))))),
936
+ (expanded.size > 0 || collapsed.size > 0) && React.createElement(ActionButton, { type: 'button', title: text(t, 'tree.organize'), 'aria-label': text(t, 'tree.organize'), onClick: () => { autoFit.current = true; setExpanded(new Set()); setCollapsed(new Set()); setHistoryOpen(false); setFocused(state.current) } }, React.createElement(Glyph, { type: 'organize' }), React.createElement('span', { className: 'dsh-super-code-action-label' }, text(t, 'tree.organizeLabel'))))),
860
937
  React.createElement('div', { className: 'dsh-super-code-graph-legend', 'aria-label': text(t, 'tree.status') },
861
938
  ['tree.legend.active', 'tree.legend.idle', 'tree.legend.stopped', 'tree.legend.unavailable'].map((key, index) => React.createElement('span', { key },
862
939
  React.createElement('i', { className: 'dsh-super-code-circle ' + ['active', 'idle', 'stopped', 'unavailable'][index], 'aria-hidden': true }), text(t, key))))),
863
940
  (error || catalogError) && React.createElement('p', { role: 'alert', className: 'dsh-super-code-tree-error' }, text(t, error || 'tree.loadError')),
864
941
  !view.root && React.createElement('p', { className: 'dsh-super-code-tree-empty' }, text(t, 'tree.empty')),
865
- historyOpen && React.createElement(AgentHistory, { view, hidden: display.hidden, totals, open, t }),
942
+ historyOpen && React.createElement(AgentHistory, { view, hidden: display.hidden, totals, open, saved, t }),
866
943
  React.createElement('div', { ref: viewport, className: 'dsh-super-code-graph-scroll',
867
944
  onPointerDown: event => {
868
945
  if (!event.isPrimary || event.button !== 0) return
@@ -887,7 +964,7 @@ function AgentTreeBody({ state, view, saved, openDetail, refresh, watchCatalog,
887
964
  React.createElement('div', { ref: graph, role: 'tree', 'aria-label': text(t, 'tree.aria'), className: 'dsh-super-code-graph', style: { width, height, transform: `translate(${pan.x}px, ${pan.y}px) scale(${zoom})` } },
888
965
  React.createElement('svg', { width, height, className: 'dsh-super-code-graph-edges', 'aria-hidden': true }, edges.map(edge => {
889
966
  const parent = positions.get(edge.parent), child = positions.get(edge.child)
890
- return React.createElement('path', { key: edge.child, d: `M ${parent.x} ${parent.y + 105} V ${child.y - 12} H ${child.x} V ${child.y}`, fill: 'none' })
967
+ return React.createElement('path', { key: edge.child, d: `M ${parent.x} ${parent.y + agentTreeNodeHeight} V ${child.y - 16} H ${child.x} V ${child.y - 10}`, fill: 'none' })
891
968
  })),
892
969
  rows.map(row => {
893
970
  const node = view.nodes.get(row.id)
@@ -1077,7 +1154,7 @@ function MemoryActivity({ usage, openMemory, sessionId, t }) {
1077
1154
  onClick: () => openMemory(sessionId, row), title: text(t, 'memory.open') },
1078
1155
  React.createElement('span', { className: 'dsh-super-code-memory-action', 'data-action': row.action }, text(t, `memory.${row.action}`)),
1079
1156
  React.createElement('span', null, row.summary || row.id),
1080
- React.createElement('small', null, `${text(t, `memory.${row.scope}`)} · ${row.topic} · r${row.revision}`)))),
1157
+ React.createElement('small', null, `${text(t, `memory.${row.scope}`)} · ${row.topic}`)))),
1081
1158
  usage?.omitted > 0 && React.createElement('p', { className: 'dsh-super-code-memory-muted' }, text(t, 'memory.omitted')))
1082
1159
  }
1083
1160
 
@@ -1098,6 +1175,18 @@ function memoryLibraryTarget(address, current) {
1098
1175
  return { sessionId: current, scope: 'project', topic: '', id: '' }
1099
1176
  }
1100
1177
 
1178
+ /** A repeated explicit navigation reopens its entry; ordinary remounts retain reading state. */
1179
+ function memoryLibraryNavigation(saved, tab, current) {
1180
+ const address = typeof tab.navigation?.params?.memory === 'string' ? tab.navigation.params.memory : tab.contentId
1181
+ const navigationKey = `${address}/${tab.navigation?.revision || 0}`
1182
+ if (saved.navigationKey === navigationKey) return saved
1183
+ const target = memoryLibraryTarget(address, current)
1184
+ return { ...saved, navigationKey, ...(target.id ? {
1185
+ scope: target.scope, selected: { topic: target.topic, id: target.id }, detailTop: 0,
1186
+ ...(saved.scope && saved.scope !== target.scope ? { topicFilter: '', top: 0 } : {}),
1187
+ } : {}) }
1188
+ }
1189
+
1101
1190
  /** A fixed-height index keeps large libraries cheap; entry bodies load on click. */
1102
1191
  function memoryLibraryWindow(items, query, top, height) {
1103
1192
  const search = query.trim().toLocaleLowerCase()
@@ -1107,23 +1196,119 @@ function memoryLibraryWindow(items, query, top, height) {
1107
1196
  return { rows: matches.slice(start, end), count: matches.length, before: start * 76, after: (matches.length - end) * 76 }
1108
1197
  }
1109
1198
 
1110
- function MemoryLibrary({ useTabInfo, useSessions, sessionId: scopeSessionId, api, memoryStates, t = key => zh[key] || key }) {
1199
+ /** Drafts and in-flight operations survive the host unmounting a hidden tab. */
1200
+ function createMemoryEditor(saved, api) {
1201
+ if (saved.editorStore) return saved.editorStore
1202
+ let snapshot = { draft: null, confirmation: null, busy: false, error: '', refresh: 0 }
1203
+ const listeners = new Set()
1204
+ const update = patch => { snapshot = { ...snapshot, ...patch }; for (const listener of listeners) listener() }
1205
+ const run = async (method, target) => {
1206
+ if (snapshot.busy) return { kind: 'busy' }
1207
+ update({ busy: true, error: '' })
1208
+ try {
1209
+ const response = method === 'save' ? await api.saveEntry(target.sessionId, target.scope, target.topic, target.id, JSON.stringify({
1210
+ expected: target.expected, kind: target.kind, summary: target.summary.trim(), detail: target.detail.trim(),
1211
+ })) : await api.deleteEntry(target.sessionId, target.scope, target.topic, target.id, target.expected)
1212
+ if (!response.ok) throw new Error('Remote unavailable')
1213
+ const result = response.value
1214
+ if (!['saved', 'removed'].includes(result.kind)) {
1215
+ update({ busy: false, error: { conflict: 'memory.conflict', missing: 'memory.missingEdit', full: 'memory.full' }[result.kind] || 'memory.writeError' })
1216
+ return result
1217
+ }
1218
+ saved.scope = target.scope
1219
+ saved.selected = result.kind === 'saved' ? { topic: target.topic, id: target.id } : null
1220
+ saved.detailTop = 0
1221
+ update({ draft: null, confirmation: null, busy: false, error: '', refresh: snapshot.refresh + 1 })
1222
+ return result
1223
+ } catch {
1224
+ update({ busy: false, error: 'memory.writeError' })
1225
+ return { kind: 'error' }
1226
+ }
1227
+ }
1228
+ saved.editorStore = { getSnapshot: () => snapshot, subscribe: listener => { listeners.add(listener); return () => listeners.delete(listener) },
1229
+ update, save: () => run('save', snapshot.draft), remove: () => run('remove', snapshot.confirmation) }
1230
+ return saved.editorStore
1231
+ }
1232
+
1233
+ function MemoryEditor({ store, state, t }) {
1234
+ const draft = state.draft
1235
+ const set = (key, value) => store.update({ draft: { ...draft, [key]: value }, error: '' })
1236
+ return React.createElement('form', { className: 'dsh-super-code-memory-editor', 'aria-label': text(t, draft.expected ? 'memory.edit' : 'memory.add'),
1237
+ onSubmit: event => { event.preventDefault(); void store.save() } },
1238
+ React.createElement('div', { className: 'dsh-super-code-memory-editor-heading' },
1239
+ React.createElement('strong', null, text(t, draft.expected ? 'memory.edit' : 'memory.add')),
1240
+ React.createElement('small', null, text(t, `memory.${draft.scope}`))),
1241
+ React.createElement('label', null, text(t, 'memory.topic'),
1242
+ React.createElement('input', { value: draft.topic, 'aria-label': text(t, 'memory.topic'), required: true, maxLength: 96,
1243
+ disabled: state.busy || Boolean(draft.expected) || draft.scope === 'global', onChange: event => set('topic', event.target.value) }),
1244
+ !draft.expected && draft.scope !== 'global' && React.createElement('small', null, text(t, 'memory.topicHint'))),
1245
+ React.createElement(SelectControl, { label: text(t, 'memory.kind'), value: draft.kind, disabled: state.busy || draft.scope === 'global',
1246
+ choices: ['constraint', 'fact', 'experience'].map(kind => [kind, text(t, `memory.${kind}`)]), onChange: kind => set('kind', kind) }),
1247
+ React.createElement('label', null, text(t, 'memory.summaryLabel'), React.createElement('small', null, `${Array.from(draft.summary).length}/30`),
1248
+ React.createElement('input', { value: draft.summary, 'aria-label': text(t, 'memory.summaryLabel'), required: true, disabled: state.busy, onChange: event => set('summary', event.target.value) })),
1249
+ React.createElement('label', null, text(t, 'memory.detailLabel'), React.createElement('small', null, `${draft.detail.length}/1000`),
1250
+ React.createElement('textarea', { value: draft.detail, 'aria-label': text(t, 'memory.detailLabel'), required: true, maxLength: 1000, rows: 5, disabled: state.busy, onChange: event => set('detail', event.target.value) })),
1251
+ state.error && React.createElement('p', { role: 'alert' }, text(t, state.error)),
1252
+ React.createElement('div', { className: 'dsh-super-code-memory-actions' },
1253
+ React.createElement(ActionButton, { type: 'button', disabled: state.busy, onClick: () => store.update({ draft: null, error: '' }) }, text(t, 'memory.cancel')),
1254
+ React.createElement(ActionButton, { type: 'submit', className: 'dsh-super-code-memory-primary', disabled: state.busy || !/^[a-zA-Z0-9][a-zA-Z0-9._-]{0,95}$/.test(draft.topic) || !draft.summary.trim() || Array.from(draft.summary.trim()).length > 30 || !draft.detail.trim() }, text(t, state.busy ? 'memory.saving' : 'memory.save'))))
1255
+ }
1256
+
1257
+ function MemoryDeleteConfirmation({ store, state, t }) {
1258
+ const dialog = useRef(null), target = state.confirmation
1259
+ useLayoutEffect(() => {
1260
+ dialog.current.showModal()
1261
+ return () => { if (dialog.current?.open) dialog.current.close() }
1262
+ }, [])
1263
+ const cancel = () => { if (!store.getSnapshot().busy) store.update({ confirmation: null, error: '' }) }
1264
+ return React.createElement('dialog', { ref: dialog, className: 'dsh-super-code-confirm', 'aria-label': text(t, 'memory.deleteTitle'),
1265
+ onCancel: event => { event.preventDefault(); cancel() }, onKeyDown: event => {
1266
+ if (event.key === 'Escape') { event.preventDefault(); event.stopPropagation(); cancel() }
1267
+ } },
1268
+ React.createElement('h3', null, text(t, 'memory.deleteTitle')),
1269
+ React.createElement('p', null, text(t, 'memory.deleteHint', { summary: target.summary })),
1270
+ state.error && React.createElement('p', { role: 'alert' }, text(t, state.error)),
1271
+ React.createElement('div', { className: 'dsh-super-code-confirm-actions' },
1272
+ React.createElement(ActionButton, { type: 'button', disabled: state.busy, onClick: cancel }, text(t, 'memory.cancel')),
1273
+ React.createElement(ActionButton, { type: 'button', className: 'dsh-super-code-memory-danger', disabled: state.busy, onClick: () => { void store.remove() } }, text(t, state.busy ? 'memory.deleting' : 'memory.delete'))))
1274
+ }
1275
+
1276
+ function MemoryLibrary({ useTabInfo, useSessions, sessionId: scopeSessionId, api, memoryStates, onToggle, t = key => zh[key] || key }) {
1111
1277
  const { tab } = useTabInfo(), current = useSessions(state => scopeSessionId || state.current)
1112
- const target = memoryLibraryTarget(tab.contentId, current)
1278
+ const address = typeof tab.navigation?.params?.memory === 'string' ? tab.navigation.params.memory : tab.contentId
1279
+ const target = memoryLibraryTarget(address, current)
1113
1280
  const saved = useMemo(() => {
1114
- if (!memoryStates.has(tab.signal)) memoryStates.set(tab.signal, {})
1115
- return memoryStates.get(tab.signal)
1281
+ const value = memoryStates.get(tab.signal) || {}
1282
+ Object.assign(value, memoryLibraryNavigation(value, tab, current))
1283
+ memoryStates.set(tab.signal, value)
1284
+ return value
1116
1285
  }, [memoryStates, tab.signal])
1286
+ const editor = useMemo(() => createMemoryEditor(saved, api), [saved, api])
1287
+ const editing = useSyncExternalStore(editor.subscribe, editor.getSnapshot, editor.getSnapshot)
1117
1288
  const [scope, setScope] = useState(saved.scope || target.scope)
1118
1289
  const [query, setQuery] = useState(saved.query || '')
1119
1290
  const [topicFilter, setTopicFilter] = useState(saved.topicFilter || '')
1120
- const [selected, setSelected] = useState(saved.selected || (target.id ? { topic: target.topic, id: target.id } : null))
1291
+ const [selected, setSelected] = useState(saved.selected || null)
1121
1292
  const [state, setState] = useState({ key: '', items: [], loading: true, error: false })
1122
1293
  const [detail, setDetail] = useState(null), [revision, setRevision] = useState(0)
1123
1294
  const [viewport, setViewport] = useState({ top: saved.top || 0, height: 600 })
1124
- const list = useRef(null), detailBody = useRef(null), sessionId = target.sessionId
1295
+ const list = useRef(null), detailBody = useRef(null), sessionId = editing.draft?.sessionId || editing.confirmation?.sessionId || target.sessionId
1125
1296
  const key = `${sessionId}/${scope}`, selectionKey = selected ? `${key}/${selected.topic}/${selected.id}` : ''
1126
1297
  const visibleState = state.key === key ? state : { items: [], loading: true, error: false }
1298
+ useLayoutEffect(() => {
1299
+ if (editing.draft || editing.confirmation || editing.busy) return
1300
+ const next = memoryLibraryNavigation(saved, tab, current)
1301
+ if (next === saved) return
1302
+ Object.assign(saved, next)
1303
+ setScope(saved.scope || target.scope); setSelected(saved.selected || null)
1304
+ setTopicFilter(saved.topicFilter || '')
1305
+ setViewport(value => ({ ...value, top: saved.top || 0 }))
1306
+ if (detailBody.current) detailBody.current.scrollTop = 0
1307
+ }, [saved, address, tab.navigation?.revision, editing.draft, editing.confirmation, editing.busy])
1308
+ useLayoutEffect(() => {
1309
+ if (!editing.refresh) return
1310
+ setScope(saved.scope); setSelected(saved.selected); setRevision(value => value + 1)
1311
+ }, [editing.refresh])
1127
1312
  useEffect(() => {
1128
1313
  if (!tab.visible || !sessionId) return
1129
1314
  let cancelled = false
@@ -1150,9 +1335,12 @@ function MemoryLibrary({ useTabInfo, useSessions, sessionId: scopeSessionId, api
1150
1335
  resize()
1151
1336
  const observer = new ResizeObserver(resize); observer.observe(element)
1152
1337
  return () => observer.disconnect()
1153
- }, [selected, key, tab.visible])
1338
+ }, [selected, key, tab.visible, Boolean(editing.draft)])
1339
+ useLayoutEffect(() => {
1340
+ if (detailBody.current) detailBody.current.scrollTop = saved.detailTop || 0
1341
+ }, [selectionKey, detail?.key, tab.visible, Boolean(editing.draft)])
1154
1342
  const resetScroll = () => { saved.top = 0; if (list.current) list.current.scrollTop = 0; setViewport(value => ({ ...value, top: 0 })) }
1155
- const choose = value => { if (detailBody.current) detailBody.current.scrollTop = 0; saved.selected = value; setSelected(value) }
1343
+ const choose = value => { saved.detailTop = 0; if (detailBody.current) detailBody.current.scrollTop = 0; saved.selected = value; setSelected(value) }
1156
1344
  const changeTopic = value => { saved.topicFilter = value; setTopicFilter(value); resetScroll() }
1157
1345
  const topics = useMemo(() => {
1158
1346
  const counts = new Map()
@@ -1167,28 +1355,36 @@ function MemoryLibrary({ useTabInfo, useSessions, sessionId: scopeSessionId, api
1167
1355
  return React.createElement('section', { className: 'dsh-super-code-memory', 'aria-label': text(t, 'memory.title') },
1168
1356
  React.createElement('header', null,
1169
1357
  React.createElement('div', { className: 'dsh-super-code-memory-heading' },
1170
- React.createElement('div', { className: 'dsh-super-code-title-line' }, React.createElement(Glyph, { type: 'memory' }), React.createElement('h2', null, text(t, 'memory.title'))),
1171
- React.createElement(ActionButton, { type: 'button', className: 'dsh-super-code-icon-button', title: text(t, 'memory.refresh'), 'aria-label': text(t, 'memory.refresh'), onClick: () => setRevision(value => value + 1) }, React.createElement(IconRefreshOutline16))),
1358
+ onToggle ? React.createElement(WorkspaceSectionToggle, { section: 'memory', expanded: true, onToggle, t })
1359
+ : React.createElement('div', { className: 'dsh-super-code-title-line' }, React.createElement(Glyph, { type: 'memory' }), React.createElement('h2', null, text(t, 'memory.title'))),
1360
+ React.createElement('div', { className: 'dsh-super-code-memory-header-actions' },
1361
+ React.createElement(ActionButton, { type: 'button', className: 'dsh-super-code-view-button', 'aria-label': text(t, 'memory.add'), disabled: !sessionId || editing.busy || Boolean(editing.draft), onClick: () => editor.update({ error: '', draft: {
1362
+ sessionId, scope, topic: scope === 'global' ? 'preferences' : topicFilter || 'notes', id: window.crypto.randomUUID(), expected: '', kind: 'constraint', summary: '', detail: '',
1363
+ } }) }, `+ ${text(t, 'memory.add')}`),
1364
+ React.createElement(ActionButton, { type: 'button', className: 'dsh-super-code-icon-button', title: text(t, 'memory.refresh'), 'aria-label': text(t, 'memory.refresh'), disabled: editing.busy || Boolean(editing.draft), onClick: () => { editor.update({ error: '' }); setRevision(value => value + 1) } }, React.createElement(IconRefreshOutline16)))),
1172
1365
  React.createElement('div', { className: 'dsh-super-code-memory-scopes', role: 'group', 'aria-label': text(t, 'memory.title') },
1173
- ['project', 'global'].map(value => React.createElement(ActionButton, { key: value, type: 'button', 'aria-pressed': scope === value, onClick: () => {
1366
+ ['project', 'global'].map(value => React.createElement(ActionButton, { key: value, type: 'button', 'aria-pressed': scope === value, disabled: editing.busy || Boolean(editing.draft), onClick: () => {
1174
1367
  saved.scope = value; setScope(value); changeTopic(''); choose(null)
1175
1368
  } }, text(t, `memory.${value}`)))),
1176
- !selected && React.createElement('div', { className: 'dsh-super-code-memory-search' }, React.createElement(Glyph, { type: 'search' }),
1369
+ !selected && !editing.draft && React.createElement('div', { className: 'dsh-super-code-memory-search' }, React.createElement(Glyph, { type: 'search' }),
1177
1370
  React.createElement('input', { type: 'search', 'aria-label': text(t, 'memory.search'), placeholder: text(t, 'memory.search'), value: query, onChange: event => { saved.query = event.target.value; setQuery(event.target.value); resetScroll() } }))),
1178
- !sessionId ? React.createElement('p', { className: 'dsh-super-code-memory-empty' }, text(t, 'memory.selectSession')) : selected
1179
- ? React.createElement('div', { ref: detailBody, className: 'dsh-super-code-memory-body', 'aria-busy': !currentDetail },
1180
- React.createElement(ActionButton, { type: 'button', className: 'dsh-super-code-view-button', onClick: () => choose(null) }, `← ${text(t, 'memory.back')}`),
1371
+ !sessionId ? React.createElement('p', { className: 'dsh-super-code-memory-empty' }, text(t, 'memory.selectSession')) : editing.draft ? React.createElement(MemoryEditor, { store: editor, state: editing, t }) : selected
1372
+ ? React.createElement('div', { ref: detailBody, className: 'dsh-super-code-memory-body', 'aria-busy': !currentDetail, onScroll: event => { saved.detailTop = event.currentTarget.scrollTop } },
1373
+ React.createElement('div', { className: 'dsh-super-code-memory-actions' },
1374
+ React.createElement(ActionButton, { type: 'button', className: 'dsh-super-code-view-button', onClick: () => choose(null) }, `← ${text(t, 'memory.back')}`),
1375
+ entry && React.createElement(React.Fragment, null,
1376
+ React.createElement(ActionButton, { type: 'button', onClick: () => editor.update({ error: '', draft: { sessionId, scope, topic: entry.topic, id: entry.id, expected: entry.token, kind: entry.kind, summary: entry.summary, detail: entry.detail } }) }, text(t, 'memory.edit')),
1377
+ React.createElement(ActionButton, { type: 'button', className: 'dsh-super-code-memory-danger', onClick: () => editor.update({ error: '', confirmation: { sessionId, scope, topic: entry.topic, id: entry.id, expected: entry.token, summary: entry.summary } }) }, text(t, 'memory.delete')))),
1181
1378
  !currentDetail && React.createElement('p', { role: 'status', className: 'dsh-super-code-memory-empty' }, text(t, 'memory.loading')),
1182
1379
  currentDetail?.kind === 'error' && React.createElement('p', { role: 'alert' }, text(t, 'memory.error')),
1183
1380
  currentDetail?.kind === 'missing' && React.createElement('p', null, text(t, 'memory.missing')),
1184
1381
  entry && React.createElement('article', { 'data-kind': entry.kind },
1185
1382
  React.createElement('div', { className: 'dsh-super-code-memory-detail-meta' },
1186
1383
  React.createElement('span', null, React.createElement(Glyph, { type: 'folder' }), entry.topic),
1187
- React.createElement('span', null, text(t, `memory.${entry.kind}`)), React.createElement('span', null, `r${entry.revision}`)),
1384
+ React.createElement('span', null, text(t, `memory.${entry.kind}`))),
1188
1385
  React.createElement('h3', null, entry.summary), React.createElement('p', { className: 'dsh-super-code-memory-detail' }, entry.detail),
1189
1386
  React.createElement('dl', null, React.createElement('dt', null, text(t, 'memory.source')),
1190
- React.createElement('dd', null, `${text(t, `memory.${entry.source.kind}`)} · #${entry.source.eventSeq}`, React.createElement('br'), entry.source.sessionId),
1191
- entry.sourceVersion && React.createElement(React.Fragment, null, React.createElement('dt', null, text(t, 'memory.version')), React.createElement('dd', null, entry.sourceVersion))),
1387
+ React.createElement('dd', null, `${text(t, `memory.${entry.source.kind}`)}${entry.source.eventSeq === undefined ? '' : ` · #${entry.source.eventSeq}`}`, React.createElement('br'), entry.source.sessionId)),
1192
1388
  related.length > 0 && React.createElement('section', { className: 'dsh-super-code-memory-related' },
1193
1389
  React.createElement('h4', null, text(t, 'memory.related')),
1194
1390
  related.map(item => React.createElement(ActionButton, { key: item.id, type: 'button', onClick: () => choose({ topic: item.topic, id: item.id }) },
@@ -1210,8 +1406,9 @@ function MemoryLibrary({ useTabInfo, useSessions, sessionId: scopeSessionId, api
1210
1406
  React.createElement(ActionButton, { type: 'button', className: 'dsh-super-code-memory-row', 'data-topic': item.topic, 'data-kind': item.kind, onClick: () => choose({ topic: item.topic, id: item.id }) },
1211
1407
  React.createElement('span', { className: 'dsh-super-code-memory-row-heading' }, React.createElement(Glyph, { type: 'folder' }), React.createElement('span', { className: 'dsh-super-code-memory-row-topic' }, item.topic)),
1212
1408
  React.createElement('strong', null, item.summary),
1213
- React.createElement('span', { className: 'dsh-super-code-memory-row-meta' }, React.createElement('span', { className: 'dsh-super-code-memory-row-kind' }, text(t, `memory.${item.kind}`)), React.createElement('span', null, `r${item.revision}`))))),
1214
- React.createElement('div', { 'aria-hidden': true, style: { height: page.after } })))))
1409
+ React.createElement('span', { className: 'dsh-super-code-memory-row-meta' }, React.createElement('span', { className: 'dsh-super-code-memory-row-kind' }, text(t, `memory.${item.kind}`)))))),
1410
+ React.createElement('div', { 'aria-hidden': true, style: { height: page.after } })))),
1411
+ editing.confirmation && React.createElement(MemoryDeleteConfirmation, { store: editor, state: editing, t }))
1215
1412
  }
1216
1413
 
1217
1414
  function AgentDetail({ useTabInfo, useSessions, useSessionStatus, sessionId, createInspector, detailStates, openMemory, t = key => zh[key] || key }) {
@@ -1330,9 +1527,9 @@ const presetRemoteContribution = {
1330
1527
  })),
1331
1528
  }
1332
1529
  const memoryRemoteContribution = {
1333
- package: 'dsh-super-code', descriptors: ['list', 'read'].map(method => ({
1530
+ package: 'dsh-super-code', descriptors: ['list', 'read', 'saveEntry', 'deleteEntry'].map(method => ({
1334
1531
  id: `dsh-super-code:superCodeMemory/${method}`, service: 'superCodeMemory', namespace: 'superCodeMemory', method, invocation: { kind: 'direct' },
1335
- parameters: (method === 'list' ? ['sessionId', 'scope'] : ['sessionId', 'scope', 'topic', 'id']).map(name => ({ name, wire: name, source: 'json', codec: remoteStringCodec })), result: { mode: 'src-json' },
1532
+ parameters: (method === 'list' ? ['sessionId', 'scope'] : method === 'saveEntry' ? ['sessionId', 'scope', 'topic', 'id', 'content'] : method === 'deleteEntry' ? ['sessionId', 'scope', 'topic', 'id', 'expected'] : ['sessionId', 'scope', 'topic', 'id']).map(name => ({ name, wire: name, source: 'json', codec: remoteStringCodec })), result: { mode: 'src-json' },
1336
1533
  })),
1337
1534
  }
1338
1535
 
@@ -1514,13 +1711,7 @@ function apply(ctx) {
1514
1711
  void ctx.remote.$mount({ package: 'dsh-super-code', descriptors: [
1515
1712
  ...presetRemoteContribution.descriptors, ...memoryRemoteContribution.descriptors,
1516
1713
  ] }).catch(error => console.warn('Super Code Remote:', error))
1517
- const memoryStates = new WeakMap()
1518
- ctx.inject(['remote.superCodeMemory'], ready => {
1519
- ready.slots.inject('sidebar.right.pane.tab', () => ready.slots.register({
1520
- name: 'sidebar.right.pane.tab', key: 'dsh-super-code-memory', locale: LOCALE_NS,
1521
- inject: () => ({ api: ready.remote.superCodeMemory, memoryStates }),
1522
- }, MemoryLibrary))
1523
- })
1714
+ const memoryStates = new WeakMap(), workspaceStates = new WeakMap()
1524
1715
  // Locale already requires its host's preference service. Declare that same
1525
1716
  // service here: Cordis rejects even optional reads without an injection.
1526
1717
  const preferenceService = 'configForms' in ctx ? 'configForms' : 'settingsScope'
@@ -1539,7 +1730,7 @@ function apply(ctx) {
1539
1730
  style.dataset.dshSuperCode = 'true'
1540
1731
  style.textContent = `
1541
1732
  /* Host aliases remain the source of theme colors. */
1542
- :where(.dsh-super-code-settings,.dsh-super-code-tree,.dsh-super-code-detail,.dsh-super-code-memory,.dsh-super-code-confirm,.dsh-super-code-select,.dsh-super-code-tooltip){
1733
+ :where(.dsh-super-code-workspace,.dsh-super-code-settings,.dsh-super-code-tree,.dsh-super-code-detail,.dsh-super-code-memory,.dsh-super-code-confirm,.dsh-super-code-select,.dsh-super-code-tooltip){
1543
1734
  --sc-text:var(--dsw-alias-label-primary,#242831);
1544
1735
  --sc-muted:var(--dsw-alias-label-secondary,#6a707a);
1545
1736
  --sc-faint:var(--dsw-alias-label-tertiary,#818793);
@@ -1557,14 +1748,14 @@ function apply(ctx) {
1557
1748
  color:var(--sc-text);font-size:14px;line-height:1.55;letter-spacing:0;
1558
1749
  box-sizing:border-box;min-width:0;
1559
1750
  }
1560
- :where(.dsh-super-code-settings,.dsh-super-code-tree,.dsh-super-code-detail,.dsh-super-code-memory,.dsh-super-code-confirm) *{box-sizing:border-box}
1561
- :where(.dsh-super-code-settings,.dsh-super-code-tree,.dsh-super-code-detail,.dsh-super-code-memory,.dsh-super-code-confirm) :is(button,input,select){font:inherit;color:inherit;min-width:0}
1562
- :where(.dsh-super-code-settings,.dsh-super-code-tree,.dsh-super-code-detail,.dsh-super-code-memory,.dsh-super-code-confirm) button{cursor:pointer;transition:background-color .12s,border-color .12s;color:inherit}
1563
- :where(.dsh-super-code-settings,.dsh-super-code-tree,.dsh-super-code-detail,.dsh-super-code-memory,.dsh-super-code-confirm) button:disabled{opacity:.45;cursor:default}
1564
- :where(.dsh-super-code-settings,.dsh-super-code-tree,.dsh-super-code-detail,.dsh-super-code-memory,.dsh-super-code-confirm) :is(button,input,select,summary):focus-visible{outline:2px solid var(--sc-accent);outline-offset:2px}
1565
- :where(.dsh-super-code-settings,.dsh-super-code-tree,.dsh-super-code-detail,.dsh-super-code-memory) :is(input,select){width:100%;height:36px;padding:7px 12px;border:1px solid var(--sc-line);border-radius:8px;background:var(--sc-bg);transition:border-color .12s;outline-offset:2px}
1566
- :where(.dsh-super-code-settings,.dsh-super-code-tree,.dsh-super-code-memory) input::placeholder{color:var(--sc-faint)}
1567
- :where(.dsh-super-code-settings,.dsh-super-code-tree,.dsh-super-code-memory) :is(input,select):hover{border-color:color-mix(in srgb,var(--sc-text) 25%,var(--sc-line))}
1751
+ :where(.dsh-super-code-workspace,.dsh-super-code-settings,.dsh-super-code-tree,.dsh-super-code-detail,.dsh-super-code-memory,.dsh-super-code-confirm) *{box-sizing:border-box}
1752
+ :where(.dsh-super-code-workspace,.dsh-super-code-settings,.dsh-super-code-tree,.dsh-super-code-detail,.dsh-super-code-memory,.dsh-super-code-confirm) :is(button,input,select){font:inherit;color:inherit;min-width:0}
1753
+ :where(.dsh-super-code-workspace,.dsh-super-code-settings,.dsh-super-code-tree,.dsh-super-code-detail,.dsh-super-code-memory,.dsh-super-code-confirm) button{cursor:pointer;transition:background-color .12s,border-color .12s;color:inherit}
1754
+ :where(.dsh-super-code-workspace,.dsh-super-code-settings,.dsh-super-code-tree,.dsh-super-code-detail,.dsh-super-code-memory,.dsh-super-code-confirm) button:disabled{opacity:.45;cursor:default}
1755
+ :where(.dsh-super-code-workspace,.dsh-super-code-settings,.dsh-super-code-tree,.dsh-super-code-detail,.dsh-super-code-memory,.dsh-super-code-confirm) :is(button,input,select,summary):focus-visible{outline:2px solid var(--sc-accent);outline-offset:2px}
1756
+ :where(.dsh-super-code-workspace,.dsh-super-code-settings,.dsh-super-code-tree,.dsh-super-code-detail,.dsh-super-code-memory) :is(input,select){width:100%;height:36px;padding:7px 12px;border:1px solid var(--sc-line);border-radius:8px;background:var(--sc-bg);transition:border-color .12s;outline-offset:2px}
1757
+ :where(.dsh-super-code-workspace,.dsh-super-code-settings,.dsh-super-code-tree,.dsh-super-code-memory) input::placeholder{color:var(--sc-faint)}
1758
+ :where(.dsh-super-code-workspace,.dsh-super-code-settings,.dsh-super-code-tree,.dsh-super-code-memory) :is(input,select):hover{border-color:color-mix(in srgb,var(--sc-text) 25%,var(--sc-line))}
1568
1759
  .dsh-super-code-technical,.dsh-super-code-confirm code,.dsh-super-code-preset-status code{font-family:var(--sc-mono);font-size:12px}
1569
1760
  .dsh-super-code-title-line{display:flex;align-items:center;gap:8px;min-width:0}
1570
1761
  .dsh-super-code-glyph{width:18px;height:18px;flex:none;color:var(--sc-muted)}
@@ -1630,6 +1821,19 @@ function apply(ctx) {
1630
1821
  .dsh-super-code-confirm-actions{display:flex;justify-content:flex-end;gap:8px;margin-top:20px}
1631
1822
  .dsh-super-code-confirm .dsh-super-code-confirm-empty{padding:20px 16px;background:var(--sc-surface);border-radius:12px}
1632
1823
 
1824
+ /* Exactly one section fills the pane; the other keeps its clickable heading. */
1825
+ .dsh-super-code-workspace{height:100%;min-height:0;overflow:auto;background:var(--sc-bg)}
1826
+ .dsh-super-code-workspace-layout{display:grid;height:100%;min-height:324px;min-width:0}
1827
+ .dsh-super-code-workspace-collapsed{display:flex;align-items:center;border-block:1px solid var(--sc-line);background:var(--sc-bg);padding:0 12px}
1828
+ .dsh-super-code-section-toggle{display:flex;align-items:center;gap:6px;flex:1;min-width:0;width:100%;min-height:36px;border:0;border-radius:6px;background:transparent;padding:6px 8px;text-align:left}
1829
+ .dsh-super-code-section-toggle>.dsh-super-code-tree-count{margin-left:auto}
1830
+ .dsh-super-code-section-toggle strong{font-size:14px;font-weight:600;white-space:nowrap}
1831
+ .dsh-super-code-section-toggle>.dsh-super-code-glyph{width:16px;height:16px}
1832
+ .dsh-super-code-section-toggle>.dsh-super-code-glyph:first-child{width:12px;height:12px;transition:transform .12s}
1833
+ .dsh-super-code-section-toggle[aria-expanded=false]>.dsh-super-code-glyph:first-child{transform:rotate(-90deg)}
1834
+ .dsh-super-code-section-toggle:hover{background:var(--sc-hover)}
1835
+ .dsh-super-code-workspace-collapsed>.dsh-super-code-section-toggle{width:100%;height:100%}
1836
+
1633
1837
  /* Memory library: geometry stays at 76px per row. */
1634
1838
  .dsh-super-code-memory{height:100%;min-height:0;display:flex;flex-direction:column;container-type:inline-size}
1635
1839
  .dsh-super-code-memory>header{padding:16px;border-bottom:1px solid var(--sc-line);flex:none}
@@ -1709,7 +1913,32 @@ function apply(ctx) {
1709
1913
  .dsh-super-code-memory-action[data-action=read]{color:var(--sc-accent)}
1710
1914
  .dsh-super-code-memory-action[data-action=remember]{color:var(--sc-success)}
1711
1915
 
1712
- /* Graph: preserve 128px node width and layout anchors. */
1916
+ /* Direct memory editing keeps one current copy, with no version UI. */
1917
+ .dsh-super-code-memory-editor{display:grid;align-content:start;gap:12px;flex:1;min-height:0;overflow:auto;padding:12px 16px}
1918
+ .dsh-super-code-memory-editor-heading{display:flex;align-items:center;justify-content:space-between;gap:8px}
1919
+ .dsh-super-code-memory-editor label{display:grid;grid-template-columns:1fr auto;gap:6px;font-size:12px}
1920
+ .dsh-super-code-memory-editor label>:is(input,textarea),.dsh-super-code-memory-editor label>small:last-child{grid-column:1/-1}
1921
+ .dsh-super-code-memory-editor textarea{font:inherit;color:inherit;resize:vertical;min-height:100px;width:100%;padding:8px 12px;border:1px solid var(--sc-line);border-radius:8px;background:var(--sc-bg)}
1922
+ .dsh-super-code-memory-editor [role=alert]{margin:0;font-size:12px;color:var(--sc-error)}
1923
+ .dsh-super-code-memory-actions{display:flex;align-items:center;justify-content:flex-end;gap:6px;flex-wrap:wrap}
1924
+ .dsh-super-code-memory-actions>button{padding:5px 10px;font-size:12px;border:1px solid var(--sc-line);border-radius:7px;background:var(--sc-bg)}
1925
+ .dsh-super-code-memory-actions>.dsh-super-code-view-button{margin-right:auto;border:0;padding-left:0}
1926
+ .dsh-super-code-memory-actions>.dsh-super-code-memory-primary{background:var(--sc-text);color:var(--sc-bg);border-color:var(--sc-text)}
1927
+ .dsh-super-code-memory-danger{color:var(--sc-error)!important}
1928
+ .dsh-super-code-memory-editor textarea:focus-visible{outline:2px solid var(--sc-accent);outline-offset:2px}
1929
+
1930
+ /* Compact library header leaves room for entries in the shared pane. */
1931
+ .dsh-super-code-workspace .dsh-super-code-memory>header{display:grid;grid-template-columns:minmax(0,1fr) auto;gap:8px;padding:10px 12px}
1932
+ .dsh-super-code-workspace .dsh-super-code-memory-heading{display:contents}
1933
+ .dsh-super-code-workspace .dsh-super-code-memory h2{font-size:14px}
1934
+ .dsh-super-code-workspace .dsh-super-code-memory-header-actions{display:flex;align-items:center;gap:4px;grid-column:2;grid-row:1}
1935
+ .dsh-super-code-memory-header-actions>.dsh-super-code-view-button{font-size:12px;white-space:nowrap}
1936
+ .dsh-super-code-workspace .dsh-super-code-memory-scopes{grid-column:1/-1;margin:0;padding:2px}
1937
+ .dsh-super-code-workspace .dsh-super-code-memory-search{grid-column:1/-1;margin:0}
1938
+ .dsh-super-code-workspace .dsh-super-code-memory-topic-index{padding:8px 12px;gap:6px}
1939
+ .dsh-super-code-workspace .dsh-super-code-memory-list-heading{padding:8px 12px 6px}
1940
+
1941
+ /* Graph: reserve space for animated/selected rings before the text stack. */
1713
1942
  .dsh-super-code-tree{height:100%;min-height:0;display:flex;flex-direction:column;overflow:auto;container-type:inline-size}
1714
1943
  .dsh-super-code-tree>header,.dsh-super-code-graph-legend,.dsh-super-code-graph-controls,.dsh-super-code-history-controls{flex:none}
1715
1944
  .dsh-super-code-tree-header{display:flex;flex-direction:column;gap:8px;padding:12px}
@@ -1750,12 +1979,12 @@ function apply(ctx) {
1750
1979
  .dsh-super-code-graph-scroll.dragging,.dsh-super-code-graph-scroll.dragging *{cursor:grabbing!important}
1751
1980
  .dsh-super-code-graph{position:relative;transform-origin:0 0}
1752
1981
  .dsh-super-code-graph-edges{position:absolute;inset:0;pointer-events:none;stroke:color-mix(in srgb,var(--sc-muted) 35%,var(--sc-surface));stroke-width:1.25}
1753
- .dsh-super-code-graph-node{position:absolute;display:flex;align-items:center;flex-direction:column;width:128px;height:105px;gap:4px;padding:0 4px;border:0;border-radius:12px;background:transparent;cursor:pointer}
1982
+ .dsh-super-code-graph-node{position:absolute;display:flex;align-items:center;flex-direction:column;width:128px;height:${agentTreeNodeHeight}px;gap:16px;padding:0 4px;border:0;border-radius:12px;background:transparent;cursor:pointer}
1754
1983
  .dsh-super-code-graph-node[aria-disabled]{cursor:default;opacity:.65}
1755
1984
  .dsh-super-code-graph-node>.dsh-super-code-circle{position:relative;display:grid;place-items:center;flex:none;width:40px;height:40px;border-radius:50%!important;corner-shape:round!important;background:linear-gradient(145deg,color-mix(in srgb,var(--state-color) 83%,white),var(--state-color));box-shadow:inset 0 1px 1px #ffffff40,0 2px 6px #00000012;color:#fff;font-size:13px;font-weight:600}
1756
1985
  .dsh-super-code-graph-node>.dsh-super-code-circle:after{content:"";position:absolute;inset:-5px;border:1px solid color-mix(in srgb,var(--state-color) 22%,transparent);border-radius:50%;corner-shape:round;pointer-events:none}
1757
1986
  .dsh-super-code-graph-node>.dsh-super-code-circle.idle:after{animation:super-agent-breathe 3s ease-in-out infinite}
1758
- .dsh-super-code-graph-node>.dsh-super-code-circle.active:after{border-width:2px;border-top-color:var(--state-color);border-right-color:var(--state-color);animation:super-agent-process 1.6s linear infinite}
1987
+ .dsh-super-code-graph-node>.dsh-super-code-circle.active:after{inset:-6px;border-width:3px;border-color:color-mix(in srgb,var(--state-color) 14%,transparent);border-top-color:var(--state-color);border-right-color:color-mix(in srgb,var(--state-color) 60%,transparent);animation:super-agent-process 1.2s linear infinite}
1759
1988
  .dsh-super-code-graph-node>.dsh-super-code-circle.stopped{background:linear-gradient(145deg,#9297a0,#747a84);box-shadow:inset 0 1px 1px #ffffff30}
1760
1989
  .dsh-super-code-graph-node>.dsh-super-code-circle.stopped:after{border-color:color-mix(in srgb,var(--state-color) 14%,transparent)}
1761
1990
  .dsh-super-code-graph-node>.dsh-super-code-circle.unavailable:after{border-style:dashed;border-color:var(--state-color)}
@@ -1856,8 +2085,8 @@ function apply(ctx) {
1856
2085
  .dsh-super-code-detail-message details pre{margin-top:10px;padding:8px 10px;border:1px solid var(--sc-line);border-radius:7px;background:var(--sc-bg)}
1857
2086
  .dsh-super-code-detail-entry button{margin:8px 0 0}
1858
2087
  .dsh-super-code-new-content{flex:none;align-self:center;margin:8px 16px 12px;border:1px solid var(--sc-line);border-radius:8px;padding:6px 12px;background:var(--sc-bg);font-size:13px}
1859
- @media(hover:none) and (pointer:coarse){:where(.dsh-super-code-settings,.dsh-super-code-memory,.dsh-super-code-tree) input{font-size:16px}}
1860
- @media(prefers-reduced-motion:reduce){:where(.dsh-super-code-settings,.dsh-super-code-tree,.dsh-super-code-detail,.dsh-super-code-memory,.dsh-super-code-confirm,.dsh-super-code-select) *{transition:none!important;animation:none!important}.dsh-super-code-graph-node>.dsh-super-code-circle:after{animation:none!important}}
2088
+ @media(hover:none) and (pointer:coarse){:where(.dsh-super-code-workspace,.dsh-super-code-settings,.dsh-super-code-memory,.dsh-super-code-tree) input{font-size:16px}}
2089
+ @media(prefers-reduced-motion:reduce){:where(.dsh-super-code-workspace,.dsh-super-code-settings,.dsh-super-code-tree,.dsh-super-code-detail,.dsh-super-code-memory,.dsh-super-code-confirm,.dsh-super-code-select) *{transition:none!important;animation:none!important}.dsh-super-code-graph-node>.dsh-super-code-circle:after{animation:none!important}}
1861
2090
 
1862
2091
  /* Same custom trigger and option list for all six dropdowns. Fixed placement
1863
2092
  * keeps history menus visible outside the clipped graph/sidebar viewport. */
@@ -1888,19 +2117,18 @@ function apply(ctx) {
1888
2117
  document.head.appendChild(style)
1889
2118
  return () => { style.remove() }
1890
2119
  }, 'super-code: browser styles')
1891
- // Both the tree and read-only details stay in the current conversation's tabs.
2120
+ // Keep the original tree kind so restored tabs become the unified workspace.
1892
2121
  const treeId = 'dsh-super-code'
1893
2122
  const treeKind = 'super-agent-agents'
1894
2123
  const disposeTreeType = ctx.sidebarRightTabs.register({
1895
2124
  id: treeId, kind: treeKind,
1896
- title: () => t('tree.aria'),
1897
- guide: [{ order: 40, title: () => t('tree.aria'), description: () => t('tree.canvas') }],
2125
+ title: () => t('workspace.title'),
2126
+ guide: [{ order: 40, title: () => t('workspace.title'), description: () => t('workspace.description') }],
1898
2127
  })
1899
2128
  const disposeMemoryType = ctx.sidebarRightTabs.register({ id: 'dsh-super-code-memory', kind: 'super-code-memory',
1900
- patterns: ['dsh-resource://super-code-memory/**'], title: () => t('memory.title'),
1901
- guide: [{ order: 41, title: () => t('memory.title'), description: () => t('memory.description') }],
2129
+ patterns: ['dsh-resource://super-code-memory/**'], title: () => t('workspace.title'),
1902
2130
  })
1903
- const openMemory = (sessionId, row) => ctx.sidebarRight.openResource(memoryLibraryAddress(sessionId, row), { kind: 'super-code-memory' })
2131
+ const openMemory = (sessionId, row) => ctx.sidebarRight.openTab(treeKind, { params: { memory: memoryLibraryAddress(sessionId, row) } })
1904
2132
  const treeActions = {
1905
2133
  // The host unmounts hidden bodies; tab signals live until close/unload.
1906
2134
  treeStates: new WeakMap(),
@@ -1918,7 +2146,7 @@ function apply(ctx) {
1918
2146
  },
1919
2147
  })
1920
2148
  ctx.slots.inject('sidebar.right.pane.tab.title', function* () {
1921
- for (const [key, titleKey] of [[treeId, 'tree.aria'], ['dsh-super-code-memory', 'memory.title']]) {
2149
+ for (const [key, titleKey] of [[treeId, 'workspace.title'], ['dsh-super-code-memory', 'workspace.title']]) {
1922
2150
  yield ctx.slots.register({ name: 'sidebar.right.pane.tab.title', key, locale: LOCALE_NS,
1923
2151
  inject: () => ({ titleKey }),
1924
2152
  }, LocalizedTabTitle)
@@ -1931,16 +2159,19 @@ function apply(ctx) {
1931
2159
  ctx.slots.inject('sidebar.right.pane.tab', () => ctx.slots.register({
1932
2160
  name: 'sidebar.right.pane.tab', key: 'dsh-super-code-detail', locale: LOCALE_NS, inject: () => ({ ...detailActions }),
1933
2161
  }, AgentDetail))
1934
- ctx.slots.inject('sidebar.right.pane.tab', () => ctx.slots.register({
1935
- // Keyed tab bodies dispatch by the definition id, while `kind` is the
1936
- // page/content discriminator used by the Sidebar controller.
1937
- name: 'sidebar.right.pane.tab', key: treeId, locale: LOCALE_NS,
1938
- inject: () => ({ ...treeActions }),
1939
- }, AgentTree))
1940
- ctx.effect(() => disposeTreeType, 'super-code: Agent tree tab')
2162
+ ctx.inject(['remote.superCodeMemory'], ready => {
2163
+ ready.slots.inject('sidebar.right.pane.tab', function* () {
2164
+ // The hidden legacy key restores old memory tabs and resource URLs.
2165
+ for (const key of [treeId, 'dsh-super-code-memory']) yield ready.slots.register({
2166
+ name: 'sidebar.right.pane.tab', key, locale: LOCALE_NS,
2167
+ inject: () => ({ ...treeActions, api: ready.remote.superCodeMemory, memoryStates, workspaceStates }),
2168
+ }, SuperCodeWorkspace)
2169
+ })
2170
+ })
2171
+ ctx.effect(() => disposeTreeType, 'super-code: workspace tab')
1941
2172
  ctx.effect(() => disposeDetailType, 'super-code: Agent detail tab')
1942
2173
  ctx.effect(() => disposeMemoryType, 'super-code: memory library tab')
1943
2174
  }
1944
2175
 
1945
- return { inject, apply, synchronizePresetDisplay, agentSessionState, buildAgentView, visibleAgentRows, agentTreeDisplay, agentTreeTotals, agentTreeLayout, agentExecutionStatus, agentDetailAddress, agentDetailTarget, agentPath, detailRecord, createAgentInspector, agentHistoryRange, agentHistoryRows, agentHistoryWindow, memoryLibraryAddress, memoryLibraryTarget, memoryLibraryWindow }
2176
+ return { inject, apply, synchronizePresetDisplay, agentSessionState, buildAgentView, visibleAgentRows, agentTreeDisplay, agentTreeTotals, agentTreeLayout, agentExecutionStatus, agentDetailAddress, agentDetailTarget, agentPath, detailRecord, createAgentInspector, agentHistoryRange, agentHistoryRows, agentHistoryWindow, memoryLibraryAddress, memoryLibraryTarget, memoryLibraryNavigation, memoryLibraryWindow, createMemoryEditor }
1946
2177
  } })