@a9i5k4/dsh-auto-memory 2.4.0 → 2.4.1

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/lib/client.js CHANGED
@@ -7,7 +7,7 @@
7
7
  * 3. settings.section — 自动记忆设置页(存储位置、注入、反思风格)
8
8
  * Data flows over /api/dsh-auto-memory/* (loopback-only host routes).
9
9
  */
10
- console.log('[dsh-auto-memory] client v0.1.13 fingerprint: drawer-summary-ok')
10
+ console.log('[dsh-auto-memory] client v2.4.1 fingerprint: drawer-summary-ok')
11
11
  window.__ModuleLoader__.load({
12
12
  id: '@a9i5k4/dsh-auto-memory',
13
13
  factory: (require) => {
@@ -284,7 +284,7 @@ window.__ModuleLoader__.load({
284
284
  fMaintSchedule: '定时 30 天蒸馏', fMaintScheduleHint: '每天到点自动把超过 30 天的旧日志蒸馏归档(等价 memory_maintain);无旧日志时零成本跳过。',
285
285
  fMaintScheduleTime: '蒸馏触发时间', fMaintScheduleTimeHint: 'HH:MM,默认 10:00(与固化时间错开)。',
286
286
  waterCardTitle: '上下文水位', waterAutoSrc: '自动检测', waterManualSrc: '手动设定', waterOfficialSrc: '官方路由容量', waterFallbackSrc: '回退默认值', waterNotMeasured: '本会话尚未测量(发送一轮后自动更新)', waterMeterOfficial: '官方计量', waterMeterHeuristic: '启发式降级', waterThresholdHint: '水位达到 {t} 时注入交接建议并自动补写账本;计量优先用官方 token-meter(与聊天框 context ring 同源),不可用时降级启发式估算;窗口优先取官方路由容量(request/context 的 contextWindow),其次按当前模型查 settings.yaml contextWindow,也可在下方手动覆盖。',
287
- autoContTitle: '自动接续', autoContHint: '开启后:水位达到阈值、且旧会话已空闲(harness 权威 running 位在轮次边界转为 false,取代旧「两轮水位持平」)时,弹出确认卡——同意=立即接续,拒绝=本轮跳过(同一边界不再提示),30-40 秒无操作视为挂机自动接续;接续前先让旧 Agent 刷新白板 PLAN 与交接账本,再建新会话(沿用工作区/模型/思考档位)并注入分层交接材料;触发后默认 30 分钟内不重复。', autoContOn: '自动接续:开', autoContOff: '自动接续:关', autoContThreshold: '阈值', autoContCountdown: '⏳ {s} 秒后自动接续…', autoContCancel: '取消', autoContConfirm: '本会话水位已达 {p}%,旧会话已空闲(轮次边界)——现在接续到新会话?', autoContAgree: '同意接续', autoContReject: '拒绝', autoContTimeout: '{s} 秒后自动接续(视为挂机)', autoContRejected: '已跳过本次接续(下一个轮次边界再提醒)',
287
+ autoContTitle: '自动接续', autoContHint: '开启后:水位达到阈值、且旧会话已空闲(harness 权威 running 位在轮次边界转为 false,取代旧「两轮水位持平」)时,弹出确认卡——同意=立即接续,拒绝=本轮跳过(同一边界不再提示),30-40 秒无操作视为挂机自动接续;接续前先让旧 Agent 刷新白板 PLAN 与交接账本,再建新会话(沿用工作区/模型/思考档位)并注入分层交接材料;触发后默认 30 分钟内不重复。', autoContOn: '自动接续:开', autoContOff: '自动接续:关', autoContThreshold: '阈值', autoContCountdown: '⏳ {s} 秒后自动接续…', autoContCancel: '取消', autoContConfirm: '本会话「可用额度」已用 {p}%(小圈读数见下行),旧会话已空闲(轮次边界)——现在接续到新会话?', autoContAgree: '同意接续', autoContReject: '拒绝', autoContTimeout: '{s} 秒后自动接续(视为挂机)', autoContRejected: '已跳过本次接续(下一个轮次边界再提醒)',
288
288
  continueCardTitle: '一键接续', continueCardHint: '写好交接账本→创建新会话→把交接材料作为首条消息预载,并自动切换到新会话继续任务(接续材料含白板节选+最新账本;当前工作区无账本时自动取全局最近一篇)。', continueBtn: '一键接续到新会话', continueBusy: '处理中…',
289
289
  fStyle: '反思风格', fStyleHint: '生活化 / 专业性 / 由内容决定。',
290
290
  fLocale: '界面语言', fLocaleHint: '默认跟随 DSH 系统语言;也可手动指定中文 / English。', followSystem: '跟随系统语言',
@@ -567,6 +567,30 @@ window.__ModuleLoader__.load({
567
567
 
568
568
  // ───────────────────────── 更新弹窗 / 首次指导 ─────────────────────────
569
569
  var CHANGELOG = {
570
+ '2.4.1': { zh: [
571
+ '修复:已接续过的会话会被再次触发接续(重复建窗口)。新增「已接续闩锁」并落盘(session id 为键、重启有效):宿主路径在交接材料投料成功后落闩,浏览器路径在新会话真建成后落闩,失败不落闩以便重试。',
572
+ '修复:宿主已完成接续后,确认卡仍停在「同意接续/拒绝」并与「已自动接续到新会话」并存;arm 状态改为按会话过滤,不再在其他窗口弹「本会话水位已达」。',
573
+ '修复:水位双口径那一行(本会话水位 / 官方小圈读数 / 距硬墙余量)此前因漏拷贝 ring/wall 永不渲染。',
574
+ '内部:界面路径表引入唯一前缀常量与一致性锁(客户端路径必须都在宿主表内、表外零裸字面量);写配置 4 条路径收敛为一个出口;10 处重复的语义状态刷新收敛为一处。',
575
+ '流程:自本版起,发版必须同步 CHANGELOG、应用内更新说明与版本标识(release.mjs 已加校验,缺一即拒绝构建)。',
576
+ ], en: [
577
+ 'Fix: a session that had already been continued could be armed again, creating duplicate windows. A persisted continuation latch (keyed by session id, survives restarts) is now written only after the handoff material is actually delivered (host path) or the new session really exists (browser path); failures do not latch, so retries still work.',
578
+ 'Fix: after the host finished continuing, the confirm card still sat on Agree/Decline next to "continued into a new session"; arming is now filtered per session, so other windows no longer show "this session reached the water level".',
579
+ 'Fix: the dual-scale water line (session level / official ring reading / distance to the hard wall) never rendered because ring/wall were not copied through.',
580
+ 'Internal: one prefix constant plus a consistency lock for the panel route table, a single config-write exit, and one shared semantic-status refresh replacing ten copies.',
581
+ 'Process: from this version on, releases must update CHANGELOG, in-app release notes and version identifiers together (release.mjs now refuses to build otherwise).',
582
+ ] },
583
+ '2.4.0': { zh: [
584
+ '★ 水位判据按「该会话当前模型」的预留额度自适应:消息可用上限 = 窗口 − 该路由的 maxTokens(实测 384,000 预留 → 可用 616,000),阈值随之落在健康区间;压缩与 CONTEXT_WINDOW_EXCEEDED 升为硬触发。',
585
+ '★ 修复自动接续静默失效:DSH 0.1.5 起 requestId 必填,宿主兜底接续漏传该字段时官方抛错被包装成误导性的 "prompt rejected" —— 现象是新会话建出来了、交接材料却从未送达。',
586
+ '★ 记忆容量口径重做:改为「文件容量上限」(noteCapacityChars / userCapacityChars,默认各 12000 字符),先 AI 折叠成要点、失败退回整条归档,写入不再被堵死。',
587
+ '★ Python 语义引擎贯通:安装向导写入正确的工作目录、模型落位与 onnxFile 契约对齐、向导常驻设置页,装了就能用。',
588
+ ], en: [
589
+ '★ Water-level judgement now adapts to the reserve of the session\'s current route: usable budget = window − that route\'s maxTokens (measured 384,000 reserve → 616,000 usable), so thresholds land in a healthy range; compaction and CONTEXT_WINDOW_EXCEEDED became hard triggers.',
590
+ '★ Fixed silent auto-continue failure: since DSH 0.1.5 requestId is required, and the host fallback omitted it, so the official error surfaced as a misleading "prompt rejected" — the new session existed but the handoff material never arrived.',
591
+ '★ Memory capacity reworked around per-file character caps (noteCapacityChars / userCapacityChars, 12000 each): fold into key points first, fall back to whole-entry archiving, so writes are never blocked.',
592
+ '★ Python semantic engine works end to end: the wizard writes the right working directory, model placement and onnxFile contracts line up, and the wizard is always reachable from Settings.',
593
+ ] },
570
594
  '2.3.0': { zh: [
571
595
  '★ 检索改为分层语义唤回:memory_recall 先返回 L0 摘要列表(每条记忆约 90 字,含 id/得分/匹配原因),需要原文再按 id 展开——此前全文直接进 embedding,超过模型 512 token 上限的条目尾部全部丢失(实测最长一条 9,822 字符);现在摘要永远在限制内,长记忆不再有损。',
572
596
  '★ 检索融合改为三臂 rank-space:词法 + 语义 + 时间 三路各自排名后按 RRF(k=60)融合,取代旧的 minmax 归一化加权——旧方案分数只反映「这一批里排第几」,候选集一换分数就漂移、候选只剩 1 条时退化为常数,而「要不要注入」的决策恰恰依赖分数与阈值比较。现在排序与决策解耦,分数不再说谎。',
@@ -855,45 +879,56 @@ window.__ModuleLoader__.load({
855
879
  }
856
880
 
857
881
  // ───────────────────────── API ─────────────────────────
882
+ // 路由前缀唯一常量(2026-09-10 第 0 步止血):表中每条路径由它拼出,不再逐个重复全串。
883
+ var ROUTE_PREFIX = '/api/dsh-auto-memory'
884
+ // 宿主半边(lib/index.js 的 API)声明的是同一批路径 —— 两个半边是独立 bundle(本文件经 __ModuleLoader__ 手写加载,
885
+ // 无法 import 宿主模块),所以"只有一份"这件事由 tests/smoke/ 下的路径表一致性锁保证:
886
+ // 客户端路径 ⊆ 宿主路径、宿主独有路径只允许显式白名单、本文件表外不得再出现裸路径字面量。
858
887
  var API = {
859
- state: '/api/dsh-auto-memory/state',
860
- list: '/api/dsh-auto-memory/list',
861
- file: '/api/dsh-auto-memory/file',
862
- recall: '/api/dsh-auto-memory/recall',
863
- smartRecall: '/api/dsh-auto-memory/smart-recall',
864
- workspaces: '/api/dsh-auto-memory/workspaces',
865
- debug: '/api/dsh-auto-memory/debug',
866
- scanDirty: '/api/dsh-auto-memory/scan-dirty',
867
- browseDir: '/api/dsh-auto-memory/browse-dir',
868
- pickDir: '/api/dsh-auto-memory/pick-dir',
869
- updateCheck: '/api/dsh-auto-memory/update-check',
870
- update: '/api/dsh-auto-memory/update',
871
- config: '/api/dsh-auto-memory/config',
872
- reflect: '/api/dsh-auto-memory/reflect',
873
- reflectAuto: '/api/dsh-auto-memory/reflect-auto',
874
- note: '/api/dsh-auto-memory/note',
875
- external: '/api/dsh-auto-memory/external',
876
- externalView: '/api/dsh-auto-memory/external-view',
877
- externalRemove: '/api/dsh-auto-memory/external-remove',
878
- externalImport: '/api/dsh-auto-memory/external-import',
879
- calendar: '/api/dsh-auto-memory/calendar',
880
- summarize: '/api/dsh-auto-memory/summarize',
881
- greet: '/api/dsh-auto-memory/greet',
882
- notices: '/api/dsh-auto-memory/notices',
883
- models: '/api/dsh-auto-memory/models',
884
- semanticDeepDetect: '/api/dsh-auto-memory/semantic-deep-detect',
885
- pyDetect: '/api/dsh-auto-memory/python-setup/detect',
886
- pyVenv: '/api/dsh-auto-memory/python-setup/venv',
887
- pyDeps: '/api/dsh-auto-memory/python-setup/deps',
888
- pyModel: '/api/dsh-auto-memory/python-setup/model',
889
- pyCancel: '/api/dsh-auto-memory/python-setup/cancel',
890
- pyStatus: '/api/dsh-auto-memory/python-setup/status',
891
- handoffState: '/api/dsh-auto-memory/handoff-state',
892
- handoffContinue: '/api/dsh-auto-memory/handoff-continue',
893
- handoffPermission: '/api/dsh-auto-memory/handoff-permission',
894
- autoContState: '/api/dsh-auto-memory/auto-continue-state',
895
- autoContDecide: '/api/dsh-auto-memory/auto-continue-decide',
896
- semanticDownload: '/api/dsh-auto-memory/semantic-download',
888
+ state: ROUTE_PREFIX + '/state',
889
+ list: ROUTE_PREFIX + '/list',
890
+ file: ROUTE_PREFIX + '/file',
891
+ recall: ROUTE_PREFIX + '/recall',
892
+ smartRecall: ROUTE_PREFIX + '/smart-recall',
893
+ workspaces: ROUTE_PREFIX + '/workspaces',
894
+ debug: ROUTE_PREFIX + '/debug',
895
+ scanDirty: ROUTE_PREFIX + '/scan-dirty',
896
+ browseDir: ROUTE_PREFIX + '/browse-dir',
897
+ pickDir: ROUTE_PREFIX + '/pick-dir',
898
+ updateCheck: ROUTE_PREFIX + '/update-check',
899
+ update: ROUTE_PREFIX + '/update',
900
+ config: ROUTE_PREFIX + '/config',
901
+ reflect: ROUTE_PREFIX + '/reflect',
902
+ reflectAuto: ROUTE_PREFIX + '/reflect-auto',
903
+ note: ROUTE_PREFIX + '/note',
904
+ external: ROUTE_PREFIX + '/external',
905
+ externalView: ROUTE_PREFIX + '/external-view',
906
+ externalRemove: ROUTE_PREFIX + '/external-remove',
907
+ externalImport: ROUTE_PREFIX + '/external-import',
908
+ calendar: ROUTE_PREFIX + '/calendar',
909
+ summarize: ROUTE_PREFIX + '/summarize',
910
+ greet: ROUTE_PREFIX + '/greet',
911
+ notices: ROUTE_PREFIX + '/notices',
912
+ models: ROUTE_PREFIX + '/models',
913
+ semanticDeepDetect: ROUTE_PREFIX + '/semantic-deep-detect',
914
+ semanticStatus: ROUTE_PREFIX + '/semantic-status',
915
+ semanticEmit: ROUTE_PREFIX + '/semantic-emit',
916
+ pyDetect: ROUTE_PREFIX + '/python-setup/detect',
917
+ pyVenv: ROUTE_PREFIX + '/python-setup/venv',
918
+ pyDeps: ROUTE_PREFIX + '/python-setup/deps',
919
+ pyModel: ROUTE_PREFIX + '/python-setup/model',
920
+ pyCancel: ROUTE_PREFIX + '/python-setup/cancel',
921
+ pyStatus: ROUTE_PREFIX + '/python-setup/status',
922
+ handoffState: ROUTE_PREFIX + '/handoff-state',
923
+ handoffContinue: ROUTE_PREFIX + '/handoff-continue',
924
+ handoffPermission: ROUTE_PREFIX + '/handoff-permission',
925
+ autoContState: ROUTE_PREFIX + '/auto-continue-state',
926
+ autoContDecide: ROUTE_PREFIX + '/auto-continue-decide',
927
+ semanticDownload: ROUTE_PREFIX + '/semantic-download',
928
+ shadowRecent: ROUTE_PREFIX + '/shadow-recent',
929
+ reviewFeedback: ROUTE_PREFIX + '/review-feedback',
930
+ memoryHub: ROUTE_PREFIX + '/memory-hub',
931
+ storageManage: ROUTE_PREFIX + '/storage-manage',
897
932
  }
898
933
  function query(params) {
899
934
  var search = new URLSearchParams()
@@ -913,6 +948,28 @@ window.__ModuleLoader__.load({
913
948
  if (!res.ok) throw new Error(body.error || ('POST ' + path + ' → HTTP ' + res.status))
914
949
  return body
915
950
  }
951
+ /**
952
+ * 写配置的唯一出口(2026-09-10 第 0 步止血):此前有 4 条互不相同的写配置路径
953
+ * (设置页整对象 POST / 接续页 patch / 向导两处 patch),端点、后置刷新、错误处理各写一遍。
954
+ * 现在全部经这里,以后加配置项或改保存行为只改这一处。
955
+ * hooks = { onSaved, onError } 可省;省略时静默失败(与原先的 fire-and-forget 行为一致)。
956
+ */
957
+ function saveConfigPatch(patch, hooks) {
958
+ var h = hooks || {}
959
+ var p = apiPost(API.config, patch)
960
+ if (h.onSaved || h.onError) p.then(function (d) { if (h.onSaved) h.onSaved(d) }, function (e) { if (h.onError) h.onError(e) })
961
+ else p.catch(function () {})
962
+ return p
963
+ }
964
+ /**
965
+ * 语义引擎状态的统一刷新(2026-09-10 第 0 步止血):此前同一段 fetch + 状态写回在界面里逐字重复 10 处,
966
+ * 响应形状或失败兜底要改时得挨个改。apply 收合并后的状态对象(已带 loaded:true),onError 可省。
967
+ */
968
+ function refreshSem(apply, onError) {
969
+ try {
970
+ fetch(API.semanticStatus).then(function (r) { return r.json() }).then(function (j) { if (typeof apply === 'function') apply(Object.assign({ loaded: true }, j)) }, function (e) { if (typeof onError === 'function') onError(e) }).catch(function () {})
971
+ } catch (_) {}
972
+ }
916
973
 
917
974
  // ───────────────────────── 样式 ─────────────────────────
918
975
  // 视觉:液态玻璃(毛玻璃)—— backdrop-filter + DSH 主题令牌(--dsw-alias-*),
@@ -1623,16 +1680,16 @@ window.__ModuleLoader__.load({
1623
1680
  var setFb = fbPair[1]
1624
1681
  useEffect(function () {
1625
1682
  var alive = true
1626
- fetch('/api/dsh-auto-memory/shadow-recent').then(function (r) { return r.json() }).then(function (j) {
1683
+ fetch(API.shadowRecent).then(function (r) { return r.json() }).then(function (j) {
1627
1684
  if (alive) setData((j && j.rows) || [])
1628
1685
  }).catch(function (e) { if (alive) setErr(String(e && e.message)) })
1629
- fetch('/api/dsh-auto-memory/review-feedback').then(function (r) { return r.json() }).then(function (j) {
1686
+ fetch(API.reviewFeedback).then(function (r) { return r.json() }).then(function (j) {
1630
1687
  if (alive) setFb(j || {})
1631
1688
  }).catch(function () {})
1632
1689
  return function () { alive = false }
1633
1690
  }, [])
1634
1691
  function send(obsId, choice) {
1635
- fetch('/api/dsh-auto-memory/review-feedback', { method: 'POST',
1692
+ fetch(API.reviewFeedback, { method: 'POST',
1636
1693
  headers: { 'Content-Type': 'application/json' },
1637
1694
  body: JSON.stringify({ observationId: obsId, choice: choice }) })
1638
1695
  .then(function (r) { return r.json() })
@@ -1801,7 +1858,7 @@ window.__ModuleLoader__.load({
1801
1858
  var setActMsg = actMsgPair[1]
1802
1859
  useEffect(function () {
1803
1860
  var alive = true
1804
- fetch('/api/dsh-auto-memory/memory-hub').then(function (r) { return r.json() }).then(function (j) {
1861
+ fetch(API.memoryHub).then(function (r) { return r.json() }).then(function (j) {
1805
1862
  if (alive) setData(j || null)
1806
1863
  }).catch(function (e) { if (alive) setErr(String(e && e.message)) })
1807
1864
  return function () { alive = false }
@@ -1809,7 +1866,7 @@ window.__ModuleLoader__.load({
1809
1866
  // M9 审批动作(G-02 同款 append-only 精神):晋升/激活/弃用走 /memory-hub POST,
1810
1867
  // 后端走 store 门槛判定,不绕过任何 gate;动作后刷新 overview。
1811
1868
  function hubAct(action, procedureId, v) {
1812
- fetch('/api/dsh-auto-memory/memory-hub', { method: 'POST',
1869
+ fetch(API.memoryHub, { method: 'POST',
1813
1870
  headers: { 'Content-Type': 'application/json' },
1814
1871
  body: JSON.stringify({ action: action, procedureId: procedureId, v: v }) })
1815
1872
  .then(function (r) { return r.json() })
@@ -1903,7 +1960,7 @@ window.__ModuleLoader__.load({
1903
1960
  var setDelFile = filePair[1]
1904
1961
  useEffect(function () {
1905
1962
  var alive = true
1906
- fetch('/api/dsh-auto-memory/storage-manage').then(function (r) { return r.json() }).then(function (j) {
1963
+ fetch(API.storageManage).then(function (r) { return r.json() }).then(function (j) {
1907
1964
  if (alive) setData(j || null)
1908
1965
  }).catch(function (e) { if (alive) setErr(String(e && e.message)) })
1909
1966
  return function () { alive = false }
@@ -1911,7 +1968,7 @@ window.__ModuleLoader__.load({
1911
1968
  function act(action, payload, onDone) {
1912
1969
  setMsg('')
1913
1970
  var body = Object.assign({ action: action }, payload || {})
1914
- fetch('/api/dsh-auto-memory/storage-manage', { method: 'POST',
1971
+ fetch(API.storageManage, { method: 'POST',
1915
1972
  headers: { 'Content-Type': 'application/json' }, body: JSON.stringify(body) })
1916
1973
  .then(function (r) { return r.json() })
1917
1974
  .then(function (j) {
@@ -1921,7 +1978,7 @@ window.__ModuleLoader__.load({
1921
1978
  }
1922
1979
  setMsg(action + ': ' + reason)
1923
1980
  if (onDone) onDone(j)
1924
- var again = fetch('/api/dsh-auto-memory/storage-manage').then(function (r2) { return r2.json() })
1981
+ var again = fetch(API.storageManage).then(function (r2) { return r2.json() })
1925
1982
  again.then(function (j2) { setData(j2 || null) })
1926
1983
  })
1927
1984
  .catch(function (e) { setMsg(action + ' failed: ' + String(e && e.message)) })
@@ -2177,7 +2234,7 @@ window.__ModuleLoader__.load({
2177
2234
  }, [])
2178
2235
  var autoSave = function (patch) {
2179
2236
  setAutoCfg(function (p) { return Object.assign({}, p, patch) })
2180
- apiPost(API.config, patch).catch(function () {})
2237
+ saveConfigPatch(patch)
2181
2238
  }
2182
2239
  async function oneClickContinue() {
2183
2240
  // 一键接续 = 共用执行器(刷新仪式 → 材料 → workspaceId 建会话 → 沿用模型 → 注入),与自动接续同一路径
@@ -3089,10 +3146,10 @@ window.__ModuleLoader__.load({
3089
3146
  if (!wizSt) {
3090
3147
  wizSt = { loaded: false }
3091
3148
  window['dsh-auto-memory.wizStatus'] = wizSt
3092
- fetch('/api/dsh-auto-memory/semantic-status').then(function (r) { return r.json() }).then(function (j) {
3093
- Object.assign(wizSt, j, { loaded: true })
3149
+ refreshSem(function (s) {
3150
+ Object.assign(wizSt, s)
3094
3151
  try { dlgTick() } catch (e9) {}
3095
- }).catch(function () { Object.assign(wizSt, { loaded: true, ready: false }) })
3152
+ }, function () { Object.assign(wizSt, { loaded: true, ready: false }) })
3096
3153
  }
3097
3154
  var tourDlPhase = wizSt && wizSt.download && wizSt.download.phase
3098
3155
  var tourShowing = !!dialogState && (dialogState.kind === 'welcomeTour' || dialogState.kind === 'modelDownload')
@@ -3102,19 +3159,19 @@ window.__ModuleLoader__.load({
3102
3159
  setTourStep(0)
3103
3160
  wizSt.tourCfg = null
3104
3161
  wizSt.cfgLoaded = false
3105
- fetch('/api/dsh-auto-memory/config').then(function (r) { return r.json() }).then(function (j) {
3162
+ fetch(API.config).then(function (r) { return r.json() }).then(function (j) {
3106
3163
  wizSt.tourCfg = (j && (j.config || j)) || {}
3107
3164
  // mode 型开关(唤起注入模式)实际存储在 embedding-config.json(semantic-emit 接口维护),
3108
3165
  // 需从 semantic-status 合并进来,导览开关才能显示真实当前值并与设置页联动。
3109
3166
  try {
3110
- fetch('/api/dsh-auto-memory/semantic-status').then(function (r2) { return r2.json() }).then(function (j2) {
3167
+ refreshSem(function (j2) {
3111
3168
  if (j2 && j2.activationEmitMode !== undefined) { wizSt.tourCfg.activationEmitMode = j2.activationEmitMode; try { dlgTick() } catch (e13) {} }
3112
- }).catch(function () {})
3169
+ })
3113
3170
  } catch (_) {}
3114
3171
  wizSt.cfgLoaded = true
3115
3172
  try { dlgTick() } catch (e11) {}
3116
3173
  }).catch(function () { wizSt.tourCfg = wizSt.tourCfg || {}; wizSt.cfgLoaded = true })
3117
- fetch('/api/dsh-auto-memory/external').then(function (r) { return r.json() }).then(function (j) {
3174
+ fetch(API.external).then(function (r) { return r.json() }).then(function (j) {
3118
3175
  wizSt.extScan = j
3119
3176
  try { dlgTick() } catch (e12) {}
3120
3177
  }).catch(function () {})
@@ -3124,9 +3181,9 @@ window.__ModuleLoader__.load({
3124
3181
  if (!tourShowing) return undefined
3125
3182
  if (wizSt.ready) return undefined // 已就绪无需轮询
3126
3183
  var iv = setInterval(function () {
3127
- fetch('/api/dsh-auto-memory/semantic-status').then(function (r) { return r.json() }).then(function (j) {
3184
+ refreshSem(function (j) {
3128
3185
  Object.assign(wizSt, j); try { dlgTick() } catch (e10) {}
3129
- }).catch(function () {})
3186
+ })
3130
3187
  }, 1500)
3131
3188
  return function () { clearInterval(iv) }
3132
3189
  }, [tourShowing, tourDlPhase, wizSt.ready])
@@ -3139,7 +3196,7 @@ window.__ModuleLoader__.load({
3139
3196
  if (wizSt.ready || wizSt.loaded === false) return undefined
3140
3197
  var ph = wizSt.download && wizSt.download.phase
3141
3198
  if (ph === 'downloading' || ph === 'verifying' || ph === 'done' || ph === 'error') return undefined
3142
- apiPost('/api/dsh-auto-memory/semantic-download', { action: 'start', mirror: 'auto' }).catch(function () {})
3199
+ apiPost(API.semanticDownload, { action: 'start', mirror: 'auto' }).catch(function () {})
3143
3200
  }, [tourShowing, tourStep, wizSt.ready])
3144
3201
  if (!dialogState) return null
3145
3202
  // 左下角小卡片(记忆按钮上方),高透明毛玻璃,不遮全屏
@@ -3257,7 +3314,7 @@ window.__ModuleLoader__.load({
3257
3314
  : t('dlDone'))
3258
3315
  : (locale === 'zh' ? '未下载 · 词法检索照常可用' : 'Not downloaded · lexical search keeps working')
3259
3316
  var startDl = function () {
3260
- apiPost('/api/dsh-auto-memory/semantic-download', { action: 'start', mirror: 'auto' }).catch(function () {})
3317
+ apiPost(API.semanticDownload, { action: 'start', mirror: 'auto' }).catch(function () {})
3261
3318
  }
3262
3319
  // 功能开关:读当前值(带每项默认)/点击即时写配置(乐观更新+POST,失败不回滚——下次重看向导会以真实配置为准)
3263
3320
  var EXT_SOURCE_KEYS = ['workbuddy-user', 'workbuddy-profile', 'codebuddy-memory', 'claude-global', 'project-conventions', 'workbuddy-sessions', 'claude-sessions', 'codex-sessions']
@@ -3283,9 +3340,9 @@ window.__ModuleLoader__.load({
3283
3340
  // mode 型开关(唤起注入模式)走 semantic-emit 接口——与设置页同源(embedding-config.json),
3284
3341
  // 保证导览切换与设置页双向联动;其余开关写常规 config。
3285
3342
  if (tg.mode) {
3286
- apiPost('/api/dsh-auto-memory/semantic-emit', { mode: patch[tg.key] }).catch(function () {})
3343
+ apiPost(API.semanticEmit, { mode: patch[tg.key] }).catch(function () {})
3287
3344
  } else {
3288
- apiPost('/api/dsh-auto-memory/config', patch).catch(function () {})
3345
+ saveConfigPatch(patch)
3289
3346
  }
3290
3347
  }
3291
3348
  // 外部来源单源勾选(扫描行;勾选即写 externalSources 对象)
@@ -3296,7 +3353,7 @@ window.__ModuleLoader__.load({
3296
3353
  cur[id] = (cur[id] !== false) ? false : true
3297
3354
  c.externalSources = cur
3298
3355
  dlgTick()
3299
- apiPost('/api/dsh-auto-memory/config', { externalSources: cur }).catch(function () {})
3356
+ saveConfigPatch({ externalSources: cur })
3300
3357
  }
3301
3358
  var tourExtOn = function (id) {
3302
3359
  var c = wizSt.tourCfg || {}
@@ -3582,8 +3639,11 @@ window.__ModuleLoader__.load({
3582
3639
  useEffect(function () {
3583
3640
  var alive = true
3584
3641
  var load = function () {
3642
+ // 带上当前会话 id:宿主据此只在「属于本窗口的」armed 上弹确认卡(2026-09-10)
3643
+ var sidQ = ''
3644
+ try { sidQ = String(currentSessionIdClient() || '') } catch (eS) {}
3585
3645
  apiGet(API.config).then(function (c) { if (alive) setAcCfg(c || {}) }).catch(function () {})
3586
- apiGet(API.autoContState).then(function (s) { if (alive && s) setAcState(s) }).catch(function () {})
3646
+ apiGet(API.autoContState, sidQ ? { sessionId: sidQ } : {}).then(function (s) { if (alive && s) setAcState(s) }).catch(function () {})
3587
3647
  }
3588
3648
  load()
3589
3649
  var iv = setInterval(load, 3000)
@@ -3598,21 +3658,34 @@ window.__ModuleLoader__.load({
3598
3658
  // 触发/倒计时/超时执行全部由宿主侧驱动(2.2.6),浏览器即使被节流也不影响接续。
3599
3659
  var poll = function () {
3600
3660
  if (!alive) return
3601
- apiGet(API.autoContState).then(function (s) {
3661
+ var sidQ = ''
3662
+ try { sidQ = String(currentSessionIdClient() || '') } catch (eS) {}
3663
+ apiGet(API.autoContState, sidQ ? { sessionId: sidQ } : {}).then(function (s) {
3602
3664
  if (!alive || !s) return
3603
3665
  setAcState(s)
3604
3666
  if (s.executing) {
3667
+ // 2026-09-10:执行中/已完成时必须**收起确认卡** —— 旧实现在这两个分支直接 return,
3668
+ // acConfirm 保持旧值,于是宿主已接续完成后,卡片仍停在「同意接续/拒绝」上
3669
+ // (实测:旧窗口一直显示确认卡 + 「✓ 已自动接续到新会话」并存,像是「它还想接续」)。
3670
+ setAcConfirm(null)
3671
+ setAcCd(0)
3605
3672
  setAcSt(locale === 'zh' ? '宿主正在自动接续:刷新白板/账本 → 建新会话 → 注入材料…' : 'Host auto-continuing: refresh PLAN/ledger → new session → inject…')
3606
3673
  return
3607
3674
  }
3608
- if (s.lastOk) {
3675
+ var okAt = Number(s.lastOk && s.lastOk.at) || 0
3676
+ // 10 分钟有效期(2026-09-10):宿主状态是全局单值,提示不设期限会一直挂在每个窗口上
3677
+ if (s.lastOk && (okAt === 0 || Date.now() - okAt < 10 * 60 * 1000)) {
3678
+ setAcConfirm(null)
3679
+ setAcCd(0)
3609
3680
  setAcSt((locale === 'zh' ? '✓ 已自动接续到新会话,可直接上手' : '✓ Auto-continued into a new session') + (s.lastOk.model ? ' · ' + s.lastOk.model + (s.lastOk.reasoningEffort ? '/' + s.lastOk.reasoningEffort : '') : ''))
3610
3681
  return
3611
3682
  }
3612
3683
  if (s.error) { setAcSt('✗ ' + s.error); return }
3613
3684
  var arm = s.armed
3614
3685
  if (arm && arm.ratio >= thr) {
3615
- setAcConfirm({ ratio: Number(arm.ratio) || 0, tokens: Number(arm.tokens) || 0, window: Number(arm.window) || 0, edgeAt: Number(arm.edgeAt) || 0 })
3686
+ // ring/wall 必须一起带进来:卡片读的是 acConfirm.ring / acConfirm.wall,
3687
+ // 宿主透出了但这里不拷 → 双口径那行永远不渲染(2026-09-10 自查发现)
3688
+ setAcConfirm({ ratio: Number(arm.ratio) || 0, tokens: Number(arm.tokens) || 0, window: Number(arm.window) || 0, ring: Number(arm.ring) || 0, wall: Number(arm.wall) || 0, edgeAt: Number(arm.edgeAt) || 0 })
3616
3689
  setAcCd(Math.max(0, Math.ceil((Number(arm.expiresAt) || 0) - Date.now()) / 1000))
3617
3690
  } else {
3618
3691
  setAcConfirm(null)
@@ -3653,7 +3726,9 @@ window.__ModuleLoader__.load({
3653
3726
  return h('div', { 'data-dam-autocont': '', style: { position: 'fixed', right: '16px', bottom: '16px', zIndex: 9000, maxWidth: 'min(440px, calc(100vw - 32px))', boxSizing: 'border-box', padding: '10px 12px', borderRadius: '12px', border: '1px solid rgba(128,128,128,.35)', background: 'rgba(18,22,30,.93)', color: '#e8eaed', fontSize: '12px', lineHeight: '1.55', fontFamily: 'system-ui', boxShadow: '0 10px 30px rgba(0,0,0,.35)', backdropFilter: 'blur(10px)' } },
3654
3727
  acConfirm ? h('div', { 'data-dam-autocont-confirm': '' }, [
3655
3728
  h('div', { style: { marginBottom: '4px' } }, t('autoContConfirm').replace('{p}', String(Math.round((acConfirm.ratio || 0) * 100)))),
3656
- h('div', { 'data-dam-hint': '' }, (acConfirm.tokens || 0).toLocaleString() + ' / ' + (acConfirm.window || 0).toLocaleString() + ' token'),
3729
+ h('div', { 'data-dam-hint': '' }, (acConfirm.tokens || 0).toLocaleString() + ' / ' + (acConfirm.window || 0).toLocaleString() + ' token'
3730
+ + (acConfirm.ring > 0 ? ' · ' + (locale === 'zh' ? '官方小圈读数 ' : 'ring ') + String(Math.round((acConfirm.ring || 0) * 100)) + '%' : '')
3731
+ + (acConfirm.wall > 0 ? ' · ' + (locale === 'zh' ? '距硬墙还剩 ' : 'to the wall ') + Math.max(0, (acConfirm.wall || 0) - (acConfirm.tokens || 0)).toLocaleString() + ' token' : '')),
3657
3732
  h('div', { style: { marginTop: '8px' } },
3658
3733
  h('button', { 'data-dam-btn': '', onClick: onAgree }, t('autoContAgree')),
3659
3734
  h('button', { 'data-dam-btn': '', style: { marginLeft: '6px' }, onClick: onReject }, t('autoContReject')),
@@ -3852,9 +3927,7 @@ window.__ModuleLoader__.load({
3852
3927
  var detBusy = detBusyPair[0]
3853
3928
  var setDetBusy = detBusyPair[1]
3854
3929
  useEffect(function () {
3855
- fetch('/api/dsh-auto-memory/semantic-status').then(function (r) { return r.json() }).then(function (j) {
3856
- setSem(Object.assign({ loaded: true }, j))
3857
- }).catch(function () { setSem({ loaded: true, ready: false }) })
3930
+ refreshSem(setSem, function () { setSem({ loaded: true, ready: false }) })
3858
3931
  return function () {}
3859
3932
  }, [])
3860
3933
  // 下载进行中每 1.5s 轮询真实进度(服务端流式记账 bytesDone/bytesTotal/mirrorUsed)
@@ -3862,9 +3935,7 @@ window.__ModuleLoader__.load({
3862
3935
  var ph = sem && sem.download && sem.download.phase
3863
3936
  if (ph !== 'downloading' && ph !== 'verifying') return function () {}
3864
3937
  var iv = setInterval(function () {
3865
- fetch('/api/dsh-auto-memory/semantic-status').then(function (r) { return r.json() }).then(function (j) {
3866
- setSem(Object.assign({ loaded: true }, j))
3867
- }).catch(function () {})
3938
+ refreshSem(setSem)
3868
3939
  }, 1500)
3869
3940
  return function () { clearInterval(iv) }
3870
3941
  }, [sem && sem.download && sem.download.phase])
@@ -3888,8 +3959,10 @@ window.__ModuleLoader__.load({
3888
3959
  function save() {
3889
3960
  if (busy) return
3890
3961
  setBusy(true); setMsg(''); setErr('')
3891
- apiPost(API.config, cfg).then(function (d) { setCfg(d.config); setDirty(false); setMsg(t('saved') + (d.migrated ? ' ' + d.migrated : '')); setBusy(false); if (d && d.config && d.config.locale) applyLocalePref(d.config.locale); try { fetch('/api/dsh-auto-memory/semantic-status').then(function (r2) { return r2.json() }).then(function (j2) { setSem(Object.assign({ loaded: true }, j2)) }).catch(function () {}) } catch (_) {} })
3892
- .catch(function (e) { setErr(e.message); setBusy(false) })
3962
+ saveConfigPatch(cfg, {
3963
+ onSaved: function (d) { setCfg(d.config); setDirty(false); setMsg(t('saved') + (d.migrated ? ' ' + d.migrated : '')); setBusy(false); if (d && d.config && d.config.locale) applyLocalePref(d.config.locale); refreshSem(setSem) },
3964
+ onError: function (e) { setErr(e.message); setBusy(false) }
3965
+ })
3893
3966
  }
3894
3967
  function field(label, control, hint) {
3895
3968
  return h('div', { 'data-dam-settings-row': '' },
@@ -3921,7 +3994,7 @@ window.__ModuleLoader__.load({
3921
3994
  else if ((rec === 'download-model' || rec === 'install-peer' || rec === 'setup-both') && mode !== 'python') setGuide('js')
3922
3995
  else if (rec === 'setup-both' && mode === 'python') setGuide('python')
3923
3996
  }).catch(function () { setDet({ failed: true }); setDetBusy(false) })
3924
- fetch('/api/dsh-auto-memory/semantic-status').then(function (r2) { return r2.json() }).then(function (j2) { setSem(Object.assign({ loaded: true }, j2)) }).catch(function () {})
3997
+ refreshSem(setSem)
3925
3998
  }
3926
3999
  function onEngineModeChange(e) {
3927
4000
  var v = e.target.value
@@ -3941,12 +4014,11 @@ window.__ModuleLoader__.load({
3941
4014
  // 2026-08-27 修复显示不跟随:切换后重新 fetch semantic-status,刷新「当前生效检索」
3942
4015
  // (sem.resolvedTier 原只在挂载/下载时更新,切换后不刷新导致一直显示旧档位)。
3943
4016
  try {
3944
- fetch('/api/dsh-auto-memory/semantic-status').then(function (r2) { return r2.json() }).then(function (j2) {
3945
- var s2 = Object.assign({ loaded: true }, j2)
4017
+ refreshSem(function (s2) {
3946
4018
  setSem(s2)
3947
4019
  if (v === 'js' && s2.ready === false) setGuide('js')
3948
4020
  else if (v === 'python' && s2.pythonInt8Present === false) setGuide('python')
3949
- }).catch(function () {})
4021
+ })
3950
4022
  } catch (_) {}
3951
4023
  }
3952
4024
  var sectionLabels = {
@@ -3973,7 +4045,7 @@ window.__ModuleLoader__.load({
3973
4045
  field(t('fJsCooldown'), h('input', { 'data-dam-input': '', type: 'number', min: 0, max: 60, value: cfg.jsDecideCooldownRounds === undefined ? 1 : cfg.jsDecideCooldownRounds, onChange: function (e) { set('jsDecideCooldownRounds', (function () { var v = Number(e.target.value); return (Number.isFinite(v) && v >= 0) ? v : 1 })()) /* #19: 0=不冷却 是合法值,|| 1 会吃掉 0 */ } }), t('fJsCooldownHint')),
3974
4046
  field(t('fJsDelta'), h('input', { 'data-dam-input': '', type: 'number', min: 0, max: 1, step: 0.005, value: cfg.jsDecideDeltaExp === undefined ? 0.01 : cfg.jsDecideDeltaExp, onChange: function (e) { var v = Number(e.target.value); set('jsDecideDeltaExp', Number.isFinite(v) && v >= 0 ? v : 0.01) } }), t('fJsDeltaHint')),
3975
4047
  field(t('fJsExcerpt'), h('input', { 'data-dam-input': '', type: 'number', min: 20, max: 480, value: cfg.jsDecideExcerptChars === undefined ? 40 : cfg.jsDecideExcerptChars, onChange: function (e) { set('jsDecideExcerptChars', Math.max(20, Math.min(480, Number(e.target.value) || 40))) } }), t('fJsExcerptHint')),
3976
- field(t('fEmitMode'), h('select', { 'data-dam-select': '', value: (sem && sem.activationEmitMode) || 'shadow', onChange: function (e) { var m = e.target.value; apiPost('/api/dsh-auto-memory/semantic-emit', { mode: m }).then(function () { try { fetch('/api/dsh-auto-memory/semantic-status').then(function (r2) { return r2.json() }).then(function (j2) { setSem(Object.assign({ loaded: true }, j2)) }).catch(function () {}) } catch (_) {} }).catch(function () {}) } },
4048
+ field(t('fEmitMode'), h('select', { 'data-dam-select': '', value: (sem && sem.activationEmitMode) || 'shadow', onChange: function (e) { var m = e.target.value; apiPost(API.semanticEmit, { mode: m }).then(function () { refreshSem(setSem) }).catch(function () {}) } },
3977
4049
  h('option', { value: 'shadow' }, locale === 'zh' ? 'shadow 只记录' : 'shadow (record only)'),
3978
4050
  h('option', { value: 'canary-explicit' }, locale === 'zh' ? 'canary 显式回忆注入' : 'canary (explicit recall)'),
3979
4051
  h('option', { value: 'active' }, locale === 'zh' ? 'active 全部注入' : 'active (all)')), t('fEmitModeHint')),
@@ -4076,13 +4148,13 @@ window.__ModuleLoader__.load({
4076
4148
  : (isJs && dl.phase === 'done' && !ready ? t('dlDone') + (locale === 'zh' ? '(缺运行库时需安装 @huggingface/transformers)' : ' (install @huggingface/transformers if runtime missing)')
4077
4149
  : (locale === 'zh' ? '下载进度' : 'Download progress'))))
4078
4150
  var refreshSem = function () {
4079
- fetch('/api/dsh-auto-memory/semantic-status').then(function (r2) { return r2.json() }).then(function (j2) { setSem(Object.assign({ loaded: true }, j2)) }).catch(function () {})
4151
+ refreshSem(setSem)
4080
4152
  }
4081
4153
  var startDl = function () {
4082
- apiPost('/api/dsh-auto-memory/semantic-download', { action: 'start', mirror: mirror }).then(refreshSem).catch(function () {})
4154
+ apiPost(API.semanticDownload, { action: 'start', mirror: mirror }).then(refreshSem).catch(function () {})
4083
4155
  }
4084
4156
  var cancelDl = function () {
4085
- apiPost('/api/dsh-auto-memory/semantic-download', { action: 'cancel', mirror: mirror }).then(refreshSem).catch(function () {})
4157
+ apiPost(API.semanticDownload, { action: 'cancel', mirror: mirror }).then(refreshSem).catch(function () {})
4086
4158
  }
4087
4159
  return h('div', { style: { border: '1px solid color-mix(in srgb, var(--dam-accent, #2456c4) 40%, transparent)', borderRadius: '10px', padding: '10px 12px', marginBottom: '8px', fontSize: 'calc(11.5px * var(--dam-scale))', lineHeight: 1.6 } },
4088
4160
  h('div', { 'data-dam-row': '', style: { alignItems: 'center' } },