dsh-manage-session 0.5.4 → 0.5.7

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/package.json CHANGED
@@ -1,9 +1,9 @@
1
1
  {
2
2
  "name": "dsh-manage-session",
3
- "version": "0.5.4",
3
+ "version": "0.5.7",
4
4
  "publishConfig": {
5
5
  "registry": "https://registry.npmjs.org",
6
- "tag": "latest",
6
+ "tag": "beta",
7
7
  "access": "public"
8
8
  },
9
9
  "description": "DSH Web 插件:会话管家(合并包)—— 会话历史文件(归档浏览与清理)与健康检查(体检 → 处方 → 出院),外加独立 FTS5 会话内容搜索索引(标题/内容双模式、增量同步、非破坏性整理与快照导入导出)。不碰会话日志、只做可逆处置。",
@@ -50,7 +50,10 @@
50
50
  "@deepseek-ai/dsh-client-ui-settings",
51
51
  "@deepseek-ai/dsh-client-ui-settings-general"
52
52
  ]
53
- }
53
+ },
54
+ "pluginDependencies": [
55
+ "dsh-reference-session-collector"
56
+ ]
54
57
  },
55
58
  "scripts": {
56
59
  "build": "node -e \"require('fs').rmSync('lib',{recursive:true,force:true})\" && tsc -b && tsdown",
@@ -83,18 +86,18 @@
83
86
  ],
84
87
  "engines": {
85
88
  "node": ">=20",
86
- "dsh": "0.1.0-rc.2 || 0.1.0-rc.3 || 0.1.0-rc.6 || 0.1.0-rc.7 || 0.1.0-rc.8 || 0.1.1-rc.1 || 0.1.1-rc.2 || 0.1.2-rc.1 || 0.1.5-rc.1 || 0.1.5-rc.2 || 0.1.5-rc.3 || 0.1.7-rc.1 || 0.1.7-rc.2 || 0.2.0-rc.1 || 0.2.0-rc.2 || 0.2.1-rc.1 || 0.2.1-rc.2"
89
+ "dsh": "0.1.0-rc.2 || 0.1.0-rc.3 || 0.1.0-rc.6 || 0.1.0-rc.7 || 0.1.0-rc.8 || 0.1.1-rc.1 || 0.1.1-rc.2 || 0.1.2-rc.1 || 0.1.5-rc.1 || 0.1.5-rc.2 || 0.1.5-rc.3 || 0.1.7-rc.1 || 0.1.7-rc.2 || 0.2.0-rc.1 || 0.2.0-rc.2 || 0.2.1-rc.1 || 0.2.1-rc.2 || 0.2.1-rc.3"
87
90
  },
88
91
  "license": "MIT",
89
92
  "peerDependencies": {
90
93
  "@deepseek-ai/cordis": "^4.0.1",
91
- "@deepseek-ai/dsh-client-locale": "0.1.0-rc.2 || 0.1.0-rc.3 || 0.1.0-rc.6 || 0.1.0-rc.7 || 0.1.0-rc.8 || 0.1.1-rc.1 || 0.1.1-rc.2 || 0.1.2-rc.1 || 0.1.5-rc.1 || 0.1.5-rc.2 || 0.1.5-rc.3 || 0.1.7-rc.1 || 0.1.7-rc.2 || 0.2.0-rc.1 || 0.2.0-rc.2 || 0.2.1-rc.1 || 0.2.1-rc.2",
92
- "@deepseek-ai/dsh-client-ui-settings": "0.1.0-rc.2 || 0.1.0-rc.3 || 0.1.0-rc.6 || 0.1.0-rc.7 || 0.1.0-rc.8 || 0.1.1-rc.1 || 0.1.1-rc.2 || 0.1.2-rc.1 || 0.1.5-rc.1 || 0.1.5-rc.2 || 0.1.5-rc.3 || 0.1.7-rc.1 || 0.1.7-rc.2 || 0.2.0-rc.1 || 0.2.0-rc.2 || 0.2.1-rc.1 || 0.2.1-rc.2",
93
- "@deepseek-ai/dsh-client-ui-settings-general": "0.1.0-rc.2 || 0.1.0-rc.3 || 0.1.0-rc.6 || 0.1.0-rc.7 || 0.1.0-rc.8 || 0.1.1-rc.1 || 0.1.1-rc.2 || 0.1.2-rc.1 || 0.1.5-rc.1 || 0.1.5-rc.2 || 0.1.5-rc.3 || 0.1.7-rc.1 || 0.1.7-rc.2 || 0.2.0-rc.1 || 0.2.0-rc.2 || 0.2.1-rc.1 || 0.2.1-rc.2",
94
- "@deepseek-ai/dsh-client-ui-slots": "0.1.0-rc.2 || 0.1.0-rc.3 || 0.1.0-rc.6 || 0.1.0-rc.7 || 0.1.0-rc.8 || 0.1.1-rc.1 || 0.1.1-rc.2 || 0.1.2-rc.1 || 0.1.5-rc.1 || 0.1.5-rc.2 || 0.1.5-rc.3 || 0.1.7-rc.1 || 0.1.7-rc.2 || 0.2.0-rc.1 || 0.2.0-rc.2 || 0.2.1-rc.1 || 0.2.1-rc.2",
94
+ "@deepseek-ai/dsh-client-locale": "0.1.0-rc.2 || 0.1.0-rc.3 || 0.1.0-rc.6 || 0.1.0-rc.7 || 0.1.0-rc.8 || 0.1.1-rc.1 || 0.1.1-rc.2 || 0.1.2-rc.1 || 0.1.5-rc.1 || 0.1.5-rc.2 || 0.1.5-rc.3 || 0.1.7-rc.1 || 0.1.7-rc.2 || 0.2.0-rc.1 || 0.2.0-rc.2 || 0.2.1-rc.1 || 0.2.1-rc.2 || 0.2.1-rc.3",
95
+ "@deepseek-ai/dsh-client-ui-settings": "0.1.0-rc.2 || 0.1.0-rc.3 || 0.1.0-rc.6 || 0.1.0-rc.7 || 0.1.0-rc.8 || 0.1.1-rc.1 || 0.1.1-rc.2 || 0.1.2-rc.1 || 0.1.5-rc.1 || 0.1.5-rc.2 || 0.1.5-rc.3 || 0.1.7-rc.1 || 0.1.7-rc.2 || 0.2.0-rc.1 || 0.2.0-rc.2 || 0.2.1-rc.1 || 0.2.1-rc.2 || 0.2.1-rc.3",
96
+ "@deepseek-ai/dsh-client-ui-settings-general": "0.1.0-rc.2 || 0.1.0-rc.3 || 0.1.0-rc.6 || 0.1.0-rc.7 || 0.1.0-rc.8 || 0.1.1-rc.1 || 0.1.1-rc.2 || 0.1.2-rc.1 || 0.1.5-rc.1 || 0.1.5-rc.2 || 0.1.5-rc.3 || 0.1.7-rc.1 || 0.1.7-rc.2 || 0.2.0-rc.1 || 0.2.0-rc.2 || 0.2.1-rc.1 || 0.2.1-rc.2 || 0.2.1-rc.3",
97
+ "@deepseek-ai/dsh-client-ui-slots": "0.1.0-rc.2 || 0.1.0-rc.3 || 0.1.0-rc.6 || 0.1.0-rc.7 || 0.1.0-rc.8 || 0.1.1-rc.1 || 0.1.1-rc.2 || 0.1.2-rc.1 || 0.1.5-rc.1 || 0.1.5-rc.2 || 0.1.5-rc.3 || 0.1.7-rc.1 || 0.1.7-rc.2 || 0.2.0-rc.1 || 0.2.0-rc.2 || 0.2.1-rc.1 || 0.2.1-rc.2 || 0.2.1-rc.3",
95
98
  "@deepseek-ai/schemastery": "^3.18.4",
96
99
  "react": "^18.2.0",
97
- "@deepseek-ai/dsh-session": "0.1.0-rc.2 || 0.1.0-rc.3 || 0.1.0-rc.6 || 0.1.0-rc.7 || 0.1.0-rc.8 || 0.1.1-rc.1 || 0.1.1-rc.2 || 0.1.2-rc.1 || 0.1.5-rc.1 || 0.1.5-rc.2 || 0.1.5-rc.3 || 0.1.7-rc.1 || 0.1.7-rc.2 || 0.2.0-rc.1 || 0.2.0-rc.2 || 0.2.1-rc.1 || 0.2.1-rc.2"
100
+ "@deepseek-ai/dsh-session": "0.1.0-rc.2 || 0.1.0-rc.3 || 0.1.0-rc.6 || 0.1.0-rc.7 || 0.1.0-rc.8 || 0.1.1-rc.1 || 0.1.1-rc.2 || 0.1.2-rc.1 || 0.1.5-rc.1 || 0.1.5-rc.2 || 0.1.5-rc.3 || 0.1.7-rc.1 || 0.1.7-rc.2 || 0.2.0-rc.1 || 0.2.0-rc.2 || 0.2.1-rc.1 || 0.2.1-rc.2 || 0.2.1-rc.3"
98
101
  },
99
102
  "peerDependenciesMeta": {
100
103
  "@deepseek-ai/dsh-client-locale": {
@@ -132,5 +135,8 @@
132
135
  },
133
136
  "optionalDependencies": {
134
137
  "better-sqlite3": "12.11.1"
138
+ },
139
+ "dependencies": {
140
+ "dsh-reference-session-collector": ">=0.2.0-beta.0 <0.3.0"
135
141
  }
136
142
  }
@@ -117,16 +117,8 @@ export function StewardSettingsCard({ t, scope }: StewardCardProps): JSX.Element
117
117
  onChange: (checked) => { void scope.set('enabled', checked) },
118
118
  }),
119
119
  }),
120
- createElement(Row, {
121
- key: 'history',
122
- title: translate(t, 'card.history'),
123
- desc: translate(t, 'card.history.desc'),
124
- control: createElement(Toggle, {
125
- checked: value.historyFiles ?? DEFAULT_CONFIG.historyFiles,
126
- disabled: !writable,
127
- onChange: (checked) => { void scope.set('historyFiles', checked) },
128
- }),
129
- }),
120
+ // 2026-10-20 养老院页签退场:historyFiles 开关不再暴露(配置字段保留,
121
+ // 宿主侧历史文件能力/路由不动)。
130
122
  createElement(Row, {
131
123
  key: 'health',
132
124
  title: translate(t, 'card.health'),
@@ -137,6 +129,18 @@ export function StewardSettingsCard({ t, scope }: StewardCardProps): JSX.Element
137
129
  onChange: (checked) => { void scope.set('healthCheck', checked) },
138
130
  }),
139
131
  }),
132
+ // 直接删除入口(功能①,2026-10-20 用户定标):缺省关——破坏性入口
133
+ // 不默认铺开;开启后搜索面板行内出删除按钮(同款两步确认)。
134
+ createElement(Row, {
135
+ key: 'directDelete',
136
+ title: translate(t, 'card.directDelete'),
137
+ desc: translate(t, 'card.directDelete.desc'),
138
+ control: createElement(Toggle, {
139
+ checked: value.directDelete ?? DEFAULT_CONFIG.directDelete,
140
+ disabled: !writable,
141
+ onChange: (checked) => { void scope.set('directDelete', checked) },
142
+ }),
143
+ }),
140
144
  )
141
145
  if (!writable) rows.push(createElement('div', { key: 'ro', className: 'dss_setDesc' }, translate(t, 'card.readonly')))
142
146
  }
@@ -18,15 +18,22 @@ import {
18
18
  import { StewardCardScope, StewardSettingsCard, type CardTranslate } from './card.tsx'
19
19
  import { searchBridgeScope, stewardBridgeScope, withBridgeFallback, type BridgeScope } from './bridge-scope.ts'
20
20
  import { dictionaries, translate, type LocaleKey } from './locales.ts'
21
- import { StewardFooter, StewardPanel, TAB_HEALTH, TAB_HISTORY } from './panel.tsx'
21
+ import { StewardFooter, StewardPanel, TAB_HEALTH } from './panel.tsx'
22
22
  // 搜索子域(原 dsh-search-index client 半身):面板/入口组件、设置卡、
23
23
  // 打开会话的宿主服务面解析、独立字典命名空间(switch-search,键集不变)。
24
24
  import { injectStyles as injectSearchStyles, SwitchFooter } from './search/panel.tsx'
25
25
  import { SearchSettingsCard, type SwitchCardScope } from './search/card.tsx'
26
26
  import { openSessionThrough } from './search/host-api.ts'
27
+ import { configureLocator } from './search/locate.ts'
28
+ import {
29
+ SESSION_MENU_DELETE_ID, SESSION_MENU_DELETE_ORDER, SESSION_MENU_SEAT, StewardDeleteMenuItem,
30
+ } from './session-menu.tsx'
31
+ import {
32
+ installLegacySessionMenuDelete, makeDirectDeleteProbe, purgeSessionById,
33
+ } from './session-menu-legacy.ts'
27
34
  import { dictionaries as searchDictionaries, NS as SEARCH_NS, translate as translateSearch, type LocaleKey as SearchLocaleKey } from './search/locales.ts'
28
35
 
29
- export { STEWARD_ENTRY_ID, STEWARD_SETTINGS_NAMESPACE, TAB_HEALTH, TAB_HISTORY }
36
+ export { STEWARD_ENTRY_ID, STEWARD_SETTINGS_NAMESPACE, TAB_HEALTH }
30
37
  export { translate } from './locales.ts'
31
38
 
32
39
  /** 字典命名空间(locale.register 用)。 */
@@ -111,23 +118,14 @@ function injectStyles(): () => void {
111
118
  .dss_tabBody{display:flex;flex-direction:column;gap:8px;padding-top:8px}
112
119
  .dss_actBtn{border:1px solid var(--dsw-alias-border-l2,#e5e7eb);background:transparent;border-radius:8px;padding:4px 10px;cursor:pointer;font-size:12px;color:var(--dsw-alias-label-primary,#111827)}
113
120
  .dss_actBtn:disabled{opacity:.5;cursor:default}
114
- .dss_dangerBtn{border-color:var(--dsw-alias-state-error-primary,#dc2626);color:var(--dsw-alias-state-error-primary,#dc2626)}
115
- .dss_editActive{background:var(--dsw-alias-interactive-bg-active,rgba(63,99,216,.12))}
116
121
  .dss_btnRow{display:flex;gap:6px;flex-wrap:wrap;align-items:center}
117
122
  .dss_list{list-style:none;margin:0;padding:0;display:flex;flex-direction:column;gap:4px}
118
123
  .dss_row{display:flex;gap:8px;align-items:center;padding:5px 8px;border-radius:8px;border:1px solid transparent}
119
124
  .dss_row:hover{border-color:var(--dsw-alias-border-l2,#e5e7eb)}
120
- .dss_rowTitle{display:flex;gap:6px;align-items:center;flex:1 1 auto;min-width:0}
121
- .dss_titleText{overflow:hidden;text-overflow:ellipsis;white-space:nowrap}
122
- .dss_tag{font-size:11px;color:var(--dsw-alias-label-tertiary,#9ca3af)}
123
125
  .dss_meta{font-size:11px;color:var(--dsw-alias-label-tertiary,#9ca3af)}
124
- .dss_metaLine{font-size:11px;color:var(--dsw-alias-label-tertiary,#9ca3af)}
125
126
  .dss_uuid{font-family:var(--ds-font-family-code,monospace)}
126
127
  /* 按行的磁盘占用:右对齐、不参与标题压缩,方便纵向比对「哪条最占地方」。 */
127
- .dss_size{flex:none;font-variant-numeric:tabular-nums;white-space:nowrap}
128
- .dss_check{display:inline-flex;align-items:center}
129
128
  .dss_status{font-size:12px;color:var(--dsw-alias-label-secondary,#6b7280)}
130
- .dss_warnText{color:var(--dsw-alias-state-warning-primary,#d97706)}
131
129
  .dss_error{font-size:12px;color:var(--dsw-alias-state-error-primary,#dc2626)}
132
130
  .dss_empty{font-size:12px;color:var(--dsw-alias-label-tertiary,#9ca3af);padding:10px 2px}
133
131
  .dss_gateList{display:flex;flex-direction:column;gap:6px}
@@ -223,8 +221,25 @@ export function apply(ctx: Context): void {
223
221
  openSessionThrough((name) => ctx.get(name), sessionId)
224
222
  }
225
223
 
224
+ // 命中定位翻页供给(T7.4 v4):宿主历史分页(IConversation.loadOlder),
225
+ // 巡逻循环靠它往回拉页直到目标行挂载。conversation 惰性按名取(装载序不
226
+ // 保证);老线无此服务/取用抛错 → false,定位自动退化为纯 DOM 巡逻。
227
+ configureLocator({
228
+ loadOlder: async (): Promise<boolean> => {
229
+ try {
230
+ const conv = ctx.get('conversation') as { loadOlder?: () => Promise<void> } | undefined
231
+ if (conv === undefined || typeof conv.loadOlder !== 'function') return false
232
+ await conv.loadOlder()
233
+ return true
234
+ } catch {
235
+ return false
236
+ }
237
+ },
238
+ })
239
+
226
240
  // 侧边栏入口(两枚,同一 bundle 注册):搜索(order 5,受 enabled+search 双门)与
227
- // 管家(order 6,受 enabled+historyFiles/healthCheck 门)。UI 收敛为单一操作台
241
+ // 管家(order 6,受 enabled+search/healthCheck 门;2026-10-20 养老院页签退场,
242
+ // historyFiles 配置字段保留但不再控制 UI)。UI 收敛为单一操作台
228
243
  // 属 P2 批量台任务(.agents/plans/steward-merge-search-index T9)。
229
244
  slots.inject('sidebar.footer.action', () => slots.register(
230
245
  { name: 'sidebar.footer.action', id: 'dsh-search-index', order: 5 },
@@ -292,6 +307,30 @@ export function apply(ctx: Context): void {
292
307
  ]),
293
308
  ), 'dsh-manage-session: plugins page config card')
294
309
 
310
+ // 侧栏会话菜单删除臂(2026-10-20 用户定标,按左侧边栏换代识别):
311
+ // 席位 `sidebar.workspaces.session.menu.item` 自 0.1.7 存在(宿主自带
312
+ // pin/rename/fork/archive/session-id 行),本行 order=500 落末位;
313
+ // ≤0.1.5 菜单写死无席位 → inject 静默等待,删除入口由搜索面板行内按钮承担。
314
+ // 行可见性由组件自探针 directDelete(缺省关),非 true 不渲染。
315
+ slots.inject(SESSION_MENU_SEAT, () => slots.register(
316
+ { name: SESSION_MENU_SEAT, id: SESSION_MENU_DELETE_ID, order: SESSION_MENU_DELETE_ORDER, locale: NS },
317
+ StewardDeleteMenuItem,
318
+ ), 'dsh-manage-session: session menu delete row')
319
+
320
+ // 老线(≤0.1.5)菜单无席位——DOM 注入臂补上删除行:监听 portal 出的
321
+ // [role=menu]、从被点锚点行的 React fiber 取 sessionId、插两步确认删除。
322
+ // 现代线(0.1.7+)席位行已带 data-steward-menu-delete 标记 → 注入器 dedup
323
+ // 跳过,不会出两条。全程 best-effort,异常只影响老线菜单、不拖垮侧栏。
324
+ if (typeof document !== 'undefined') {
325
+ const probe = makeDirectDeleteProbe()
326
+ ctx.effect(() => installLegacySessionMenuDelete({
327
+ doc: document,
328
+ purge: purgeSessionById,
329
+ isEnabled: probe,
330
+ lang: () => (typeof navigator !== 'undefined' ? navigator.language : undefined),
331
+ }), 'dsh-manage-session: legacy session menu delete injector')
332
+ }
333
+
295
334
  // 家族节宿主选举(次优先级):宿主 dsh-thinking-levels 缺席时顶上「起子插件
296
335
  // 设置」节。下载量排名 session-steward(1090) 低于 session-guard(1522) → 宽限期
297
336
  // 更长,guard 先尝试接管;guard 也缺席时本插件按同一 id `dsh-family` 接管,
@@ -398,10 +437,10 @@ export function StewardEntry(
398
437
  () => (props.scope !== undefined ? props.scope.getSnapshot() : { value: DEFAULT_CONFIG }),
399
438
  )
400
439
  const config: StewardConfig = { ...DEFAULT_CONFIG, ...(snapshot.value ?? {}) }
401
- const historyFiles = config.historyFiles !== false
402
440
  const healthCheck = config.healthCheck !== false
403
441
  const searchEnabled = config.search !== false
404
- const visible = config.enabled !== false && (historyFiles || healthCheck || searchEnabled)
442
+ // 养老院页签已退场:historyFiles 不再参与可见性(宿主侧能力保留)。
443
+ const visible = config.enabled !== false && (healthCheck || searchEnabled)
405
444
  const wide = props.wide === true
406
445
  // 入口文案与面板标题同源:字典是唯一出处,组件不再持有兜底中文。
407
446
  const label = translate(undefined, 'panel.title')
@@ -418,7 +457,6 @@ export function StewardEntry(
418
457
  open
419
458
  ? createElement(StewardPanel, {
420
459
  key: 'panel',
421
- historyFiles,
422
460
  healthCheck,
423
461
  searchEnabled,
424
462
  openSession: props.openSession,
@@ -18,6 +18,10 @@ export const zh = {
18
18
  'card.history.desc': '归档浏览与清理(养老院)。关闭后不注册历史接口、不渲染该页签。',
19
19
  'card.health': '健康检查',
20
20
  'card.health.desc': '体检 → 处方 → 出院。关闭后不注册体检接口、不渲染该页签。',
21
+ 'card.directDelete': '直接删除入口',
22
+ 'card.directDelete.desc': '开启后搜索面板结果行出现「删除」按钮(两步确认,与管理端同款链路)。默认关闭。',
23
+ 'menu.deleteSession': '删除会话',
24
+ 'menu.deleteSession.confirm': '确认删除',
21
25
  'card.readonly': '当前会话无法修改设置(只读挂载)。',
22
26
  'card.unavailable': '设置服务不可用,无法读取配置。',
23
27
 
@@ -119,6 +123,10 @@ export const en: Record<LocaleKey, string> = {
119
123
  'card.history.desc': 'Archive browsing and cleanup (Retirement Home). When off, history routes and the tab are gone.',
120
124
  'card.health': 'Health check',
121
125
  'card.health.desc': 'Checkup → Prescription → Discharge. When off, health routes and the tab are gone.',
126
+ 'card.directDelete': 'Direct delete entry',
127
+ 'card.directDelete.desc': 'When on, result rows in the search panel get a Delete button (two-step confirm, same pipeline as the manage view). Off by default.',
128
+ 'menu.deleteSession': 'Delete session',
129
+ 'menu.deleteSession.confirm': 'Confirm delete',
122
130
  'card.readonly': 'This session cannot edit settings (read-only mount).',
123
131
  'card.unavailable': 'Settings service unavailable; configuration cannot be read.',
124
132
  'family.title': 'Screwdriver plugin settings',
@@ -214,6 +222,10 @@ export const ja: Partial<Record<LocaleKey, string>> = {
214
222
  'card.history.desc': 'アーカイブの閲覧とクリーンアップ(老人ホーム)。オフにすると履歴 API を登録せず、このタブも描画しません。',
215
223
  'card.health': 'ヘルスチェック',
216
224
  'card.health.desc': '健診 → 処方 → 退院。オフにすると健診 API を登録せず、このタブも描画しません。',
225
+ 'card.directDelete': '直接削除の入口',
226
+ 'card.directDelete.desc': 'オンにすると検索パネルの結果行に「削除」ボタンが表示されます(二段階確認、管理画面と同じ経路)。デフォルトはオフ。',
227
+ 'menu.deleteSession': 'セッションを削除',
228
+ 'menu.deleteSession.confirm': '削除を確認',
217
229
  'card.readonly': 'このセッションでは設定を変更できません(読み取り専用マウント)。',
218
230
  'card.unavailable': '設定サービスが利用できず、構成を読み取れません。',
219
231
  'family.title': 'スクリュードライバー プラグイン設定',
@@ -302,6 +314,10 @@ export const ko: Partial<Record<LocaleKey, string>> = {
302
314
  'card.history.desc': '아카이브 탐색 및 정리(요양소). 끄면 기록 API를 등록하지 않고 이 탭을 렌더링하지 않습니다.',
303
315
  'card.health': '건강 점검',
304
316
  'card.health.desc': '진료 → 처방 → 퇴원. 끄면 진료 API를 등록하지 않고 이 탭을 렌더링하지 않습니다.',
317
+ 'card.directDelete': '직접 삭제 입구',
318
+ 'card.directDelete.desc': '켜면 검색 패널 결과 행에 「삭제」 버튼이 표시됩니다(2단계 확인, 관리 화면과 동일한 경로). 기본은 끄기.',
319
+ 'menu.deleteSession': '세션 삭제',
320
+ 'menu.deleteSession.confirm': '삭제 확인',
305
321
  'card.readonly': '이 세션에서는 설정을 수정할 수 없습니다(읽기 전용 마운트).',
306
322
  'card.unavailable': '설정 서비스를 사용할 수 없어 구성을 읽을 수 없습니다.',
307
323
  'family.title': '스크루드라이버 플러그인 설정',
@@ -390,6 +406,10 @@ export const fr: Partial<Record<LocaleKey, string>> = {
390
406
  'card.history.desc': 'Consultation et nettoyage des archives (Maison de retraite). Désactivé, aucune API d’historique n’est enregistrée et cet onglet n’est pas rendu.',
391
407
  'card.health': 'Contrôle de santé',
392
408
  'card.health.desc': 'Examen → Ordonnance → Sortie. Désactivé, aucune API de contrôle n’est enregistrée et cet onglet n’est pas rendu.',
409
+ 'card.directDelete': 'Entrée de suppression directe',
410
+ 'card.directDelete.desc': 'Activé, les lignes de résultat du panneau de recherche affichent un bouton Supprimer (double confirmation, même pipeline que la gestion). Désactivé par défaut.',
411
+ 'menu.deleteSession': 'Supprimer la session',
412
+ 'menu.deleteSession.confirm': 'Confirmer la suppression',
393
413
  'card.readonly': 'Cette session ne peut pas modifier les réglages (montage en lecture seule).',
394
414
  'card.unavailable': 'Service de réglages indisponible ; configuration illisible.',
395
415
  'family.title': 'Réglages du plugin Screwdriver',
@@ -478,6 +498,10 @@ export const de: Partial<Record<LocaleKey, string>> = {
478
498
  'card.history.desc': 'Archivansicht und Bereinigung (Altersheim). Wenn aus, werden keine Verlaufs-Routen registriert und dieser Tab nicht gerendert.',
479
499
  'card.health': 'Health-Check',
480
500
  'card.health.desc': 'Untersuchung → Rezept → Entlassung. Wenn aus, werden keine Check-Routen registriert und dieser Tab nicht gerendert.',
501
+ 'card.directDelete': 'Direkt-Lösch-Eingang',
502
+ 'card.directDelete.desc': 'Wenn aktiviert, zeigen Ergebniszeilen im Suchpanel eine Löschen-Schaltfläche (Bestätigung in zwei Schritten, gleiche Pipeline wie die Verwaltung). Standardmäßig aus.',
503
+ 'menu.deleteSession': 'Sitzung löschen',
504
+ 'menu.deleteSession.confirm': 'Löschen bestätigen',
481
505
  'card.readonly': 'Diese Sitzung kann Einstellungen nicht ändern (schreibgeschützter Mount).',
482
506
  'card.unavailable': 'Einstellungsdienst nicht verfügbar; Konfiguration kann nicht gelesen werden.',
483
507
  'family.title': 'Screwdriver-Plugin-Einstellungen',
@@ -566,6 +590,10 @@ export const it: Partial<Record<LocaleKey, string>> = {
566
590
  'card.history.desc': 'Consultazione e pulizia degli archivi (Casa di riposo). Se disattivato, non registra API di cronologia e non rende questa scheda.',
567
591
  'card.health': 'Controllo di salute',
568
592
  'card.health.desc': 'Visita → Prescrizione → Dimissione. Se disattivato, non registra API di controllo e non rende questa scheda.',
593
+ 'card.directDelete': 'Ingresso eliminazione diretta',
594
+ 'card.directDelete.desc': 'Se attivo, le righe dei risultati nel pannello di ricerca mostrano un pulsante Elimina (conferma in due passaggi, stessa pipeline della gestione). Predefinito disattivo.',
595
+ 'menu.deleteSession': 'Elimina sessione',
596
+ 'menu.deleteSession.confirm': 'Conferma eliminazione',
569
597
  'card.readonly': 'Questa sessione non può modificare le impostazioni (montaggio in sola lettura).',
570
598
  'card.unavailable': 'Servizio di impostazioni non disponibile; impossibile leggere la configurazione.',
571
599
  'family.title': 'Impostazioni del plugin Screwdriver',
@@ -654,6 +682,10 @@ export const ru: Partial<Record<LocaleKey, string>> = {
654
682
  'card.history.desc': 'Просмотр и очистка архивов (Пансионат). При выключении не регистрируются API истории и эта вкладка не отображается.',
655
683
  'card.health': 'Проверка здоровья',
656
684
  'card.health.desc': 'Осмотр → Рецепт → Выписка. При выключении не регистрируются API проверки и эта вкладка не отображается.',
685
+ 'card.directDelete': 'Точка прямого удаления',
686
+ 'card.directDelete.desc': 'Если включено, в строках результатов панели поиска появляется кнопка «Удалить» (двухшаговое подтверждение, тот же конвейер, что и в администраторе). По умолчанию выключено.',
687
+ 'menu.deleteSession': 'Удалить сессию',
688
+ 'menu.deleteSession.confirm': 'Подтвердить удаление',
657
689
  'card.readonly': 'Эта сессия не может изменять настройки (монтирование только для чтения).',
658
690
  'card.unavailable': 'Сервис настроек недоступен; конфигурацию невозможно прочитать.',
659
691
  'family.title': 'Настройки плагинов Screwdriver',
@@ -742,6 +774,10 @@ export const es: Partial<Record<LocaleKey, string>> = {
742
774
  'card.history.desc': 'Visualización y limpieza del archivo (Residencia). Al desactivarlo no se registran rutas de historial ni se muestra esa pestaña.',
743
775
  'card.health': 'Comprobación de salud',
744
776
  'card.health.desc': 'Revisión → Receta → Alta. Al desactivarlo no se registran rutas de revisión ni se muestra esa pestaña.',
777
+ 'card.directDelete': 'Entrada de borrado directo',
778
+ 'card.directDelete.desc': 'Al activarlo, las filas de resultados del panel de búsqueda muestran un botón Eliminar (confirmación en dos pasos, misma ruta que la gestión). Desactivado de forma predeterminada.',
779
+ 'menu.deleteSession': 'Eliminar sesión',
780
+ 'menu.deleteSession.confirm': 'Confirmar eliminación',
745
781
  'card.readonly': 'Esta sesión no puede modificar la configuración (montaje de solo lectura).',
746
782
  'card.unavailable': 'Servicio de configuración no disponible; no se puede leer la configuración.',
747
783
  'family.title': 'Ajustes del plugin Screwdriver',
@@ -1,12 +1,17 @@
1
1
  /**
2
- * 会话管家面板壳:一个侧边栏入口,两个页签(养老院 / 体检)。
2
+ * 会话管家面板壳:一个侧边栏入口,两个页签(管理=主页面 / 体检=备用)。
3
3
  *
4
- * 页签可见性由开关决定:`historyFiles=false` 时「养老院」消失,`healthCheck=false`
5
- * 时「体检」消失;两者都关时进入面板直接显示空壳提示(客户端也会隐藏入口)。
4
+ * 2026-10-20 页面调整(用户定标):「养老院」页签取消——管理台已覆盖其全部
5
+ * 动作面(archive/unarchive/purge + 归档域筛选),独立页签属重复入口;
6
+ * 历史文件组件(history-panel.tsx)随摘除,宿主侧 history 路由与配置字段
7
+ * (historyFiles)保留不动,字典键留用以备回迁。
8
+ *
9
+ * 页签可见性由开关决定:`search=false` 时「管理」消失(退到体检),
10
+ * `healthCheck=false` 时「体检」消失;两者都关时进入面板直接显示空壳提示
11
+ * (客户端也会隐藏入口)。
6
12
  */
7
13
  import { createElement, useEffect, useState, type ReactElement } from 'react'
8
14
  import { createPortal } from 'react-dom'
9
- import { HistoryPanel } from './history-panel.tsx'
10
15
  import { HealthPanel } from './health-panel.tsx'
11
16
  import { ManageConsole } from './search/manage.tsx'
12
17
  import { translate, type LocaleKey } from './locales.ts'
@@ -15,15 +20,12 @@ import { translate, type LocaleKey } from './locales.ts'
15
20
  export type PanelTranslate = (key: LocaleKey, params?: Record<string, unknown>) => string
16
21
 
17
22
  /** 页签 id(导出供测试断言)。 */
18
- export const TAB_HISTORY = 'dss-tab-history'
19
23
  export const TAB_HEALTH = 'dss-tab-health'
20
24
  export const TAB_MANAGE = 'dss-tab-manage'
21
25
 
22
26
  /** 面板入参。 */
23
27
  export interface StewardPanelProps {
24
28
  t?: PanelTranslate
25
- /** 历史文件子域是否可见。 */
26
- historyFiles: boolean
27
29
  /** 健康检查子域是否可见。 */
28
30
  healthCheck: boolean
29
31
  /** 搜索子域是否启用(启用才有「管理」页签——它读统一索引语料)。 */
@@ -34,15 +36,15 @@ export interface StewardPanelProps {
34
36
  }
35
37
 
36
38
  /** 会话管家对话框。 */
37
- export function StewardPanel({ t, historyFiles, healthCheck, searchEnabled = false, openSession, onClose }: StewardPanelProps): ReactElement {
38
- const [tab, setTab] = useState<'history' | 'health' | 'manage'>(historyFiles ? 'history' : healthCheck ? 'health' : 'manage')
39
+ export function StewardPanel({ t, healthCheck, searchEnabled = false, openSession, onClose }: StewardPanelProps): ReactElement {
40
+ // 主页面=管理;管理关闭时退到体检兜底。
41
+ const [tab, setTab] = useState<'health' | 'manage'>(searchEnabled ? 'manage' : 'health')
39
42
 
40
43
  // 开关变化后收敛到仍然可见的页签。
41
44
  useEffect(() => {
42
- if (tab === 'history' && !historyFiles && (healthCheck || searchEnabled)) setTab(healthCheck ? 'health' : 'manage')
43
- if (tab === 'health' && !healthCheck && (historyFiles || searchEnabled)) setTab(historyFiles ? 'history' : 'manage')
44
- if (tab === 'manage' && !searchEnabled && historyFiles) setTab('history')
45
- }, [tab, historyFiles, healthCheck, searchEnabled])
45
+ if (tab === 'manage' && !searchEnabled && healthCheck) setTab('health')
46
+ if (tab === 'health' && !healthCheck && searchEnabled) setTab('manage')
47
+ }, [tab, healthCheck, searchEnabled])
46
48
 
47
49
  useEffect(() => {
48
50
  const onKey = (event: KeyboardEvent): void => { if (event.key === 'Escape') onClose() }
@@ -51,15 +53,15 @@ export function StewardPanel({ t, historyFiles, healthCheck, searchEnabled = fal
51
53
  }, [onClose])
52
54
 
53
55
  const tabs: ReactElement[] = []
54
- if (historyFiles) {
56
+ if (searchEnabled) {
55
57
  tabs.push(createElement('button', {
56
- key: TAB_HISTORY,
57
- id: TAB_HISTORY,
58
+ key: TAB_MANAGE,
59
+ id: TAB_MANAGE,
58
60
  type: 'button',
59
- className: `dss_tab${tab === 'history' ? ' dss_tabActive' : ''}`,
60
- 'aria-selected': tab === 'history',
61
- onClick: () => { setTab('history') },
62
- }, translate(t, 'panel.tab.history')))
61
+ className: `dss_tab${tab === 'manage' ? ' dss_tabActive' : ''}`,
62
+ 'aria-selected': tab === 'manage',
63
+ onClick: () => { setTab('manage') },
64
+ }, translate(t, 'panel.tab.manage')))
63
65
  }
64
66
  if (healthCheck) {
65
67
  tabs.push(createElement('button', {
@@ -71,25 +73,13 @@ export function StewardPanel({ t, historyFiles, healthCheck, searchEnabled = fal
71
73
  onClick: () => { setTab('health') },
72
74
  }, translate(t, 'panel.tab.health')))
73
75
  }
74
- if (searchEnabled) {
75
- tabs.push(createElement('button', {
76
- key: TAB_MANAGE,
77
- id: TAB_MANAGE,
78
- type: 'button',
79
- className: `dss_tab${tab === 'manage' ? ' dss_tabActive' : ''}`,
80
- 'aria-selected': tab === 'manage',
81
- onClick: () => { setTab('manage') },
82
- }, translate(t, 'panel.tab.manage')))
83
- }
84
76
 
85
- const body: ReactElement = tab === 'history' && historyFiles
86
- ? createElement(HistoryPanel, { t, onClose })
77
+ const body: ReactElement = tab === 'manage' && searchEnabled
78
+ ? createElement('div', { style: { display: 'flex', flexDirection: 'column', flex: '1 1 auto', minHeight: 0 } },
79
+ createElement(ManageConsole, { open: openSession ?? (() => {}) }))
87
80
  : tab === 'health' && healthCheck
88
81
  ? createElement(HealthPanel, { t, onClose })
89
- : tab === 'manage' && searchEnabled
90
- ? createElement('div', { style: { display: 'flex', flexDirection: 'column', flex: '1 1 auto', minHeight: 0 } },
91
- createElement(ManageConsole, { open: openSession ?? (() => {}) }))
92
- : createElement('div', { className: 'dss_empty' }, translate(t, 'card.enabled.desc'))
82
+ : createElement('div', { className: 'dss_empty' }, translate(t, 'card.enabled.desc'))
93
83
 
94
84
  return createPortal(createElement('div', { key: 'steward-root' }, [
95
85
  createElement('div', { key: 'backdrop', className: 'dss_backdrop', onClick: onClose }),
@@ -122,6 +122,11 @@ export interface HostIndexStatus {
122
122
  * older host half — treated as `unknown`, which renders the neutral hint.
123
123
  */
124
124
  steward?: 'installed' | 'missing' | 'unknown'
125
+ /**
126
+ * 直接删除入口开关(功能①,管家 session-steward.directDelete):true 时
127
+ * 面板结果行渲染行内删除按钮。旧宿主半无此字段 = undefined = 不渲染。
128
+ */
129
+ directDelete?: boolean
125
130
  sync?: HostSyncState
126
131
  rebuild?: HostRebuildState
127
132
  error?: string