dsh-hypatia-auto-memory 0.5.0 → 1.0.0

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
@@ -47,31 +47,6 @@ window.__ModuleLoader__.load({
47
47
  }
48
48
  return candidates;
49
49
  }
50
- function isRecord$1(value) {
51
- return typeof value === "object" && value !== null && !Array.isArray(value);
52
- }
53
- /**
54
- * Pick the inventory namespace out of a settings descriptor.
55
- *
56
- * Reads the composition layer (`base`), which only the Host sets, rather than
57
- * the resolved value: a stray user-layer section for this namespace would
58
- * otherwise freeze the list.
59
- * @param described - the `namespaces` array a settings `describe()` returns.
60
- * @returns the listing, or undefined when the Host does not serve one.
61
- */
62
- function readShelfInventory(described) {
63
- const view = described.find((entry) => isRecord$1(entry) && entry.ns === "hypatia-auto-memory-shelves");
64
- if (!isRecord$1(view) || !isRecord$1(view.base)) return void 0;
65
- const { shelves, error } = view.base;
66
- return {
67
- shelves: Array.isArray(shelves) ? shelves.flatMap((shelf) => isRecord$1(shelf) && typeof shelf.name === "string" ? [{
68
- name: shelf.name,
69
- path: typeof shelf.path === "string" ? shelf.path : "",
70
- connected: shelf.connected === true
71
- }] : []) : [],
72
- error: typeof error === "string" ? error : ""
73
- };
74
- }
75
50
  /**
76
51
  * Every shelf the dropdown offers: the listing, plus the stored and composed
77
52
  * values when the listing does not carry them, so the current choice always
@@ -98,7 +73,7 @@ window.__ModuleLoader__.load({
98
73
  }
99
74
  //#endregion
100
75
  //#region \0dsh-hypatia-css:/home/runner/work/dsh-hypatia-auto-memory/dsh-hypatia-auto-memory/src/client/SettingsCard.module.css.mjs
101
- const css$1 = ".YVNYya_card{border:.5px solid var(--dsw-alias-border-l4);background:var(--dsw-alias-bg-layer-3);border-radius:16px;list-style:none;transition:border-color .16s,background .16s}.YVNYya_card:hover{border-color:var(--dsw-alias-label-dimmed)}.YVNYya_cardOpen{border-color:var(--dsw-alias-label-dimmed);background:var(--dsw-alias-bg-layer-2)}.YVNYya_header{appearance:none;width:100%;color:inherit;cursor:pointer;font:inherit;text-align:left;background:0 0;border:0;border-radius:12px;align-items:center;gap:12px;padding:14px 16px;display:flex}.YVNYya_header:focus-visible{outline:2px solid var(--dsw-alias-brand-primary);outline-offset:-2px}.YVNYya_headText{flex-direction:column;flex:1;gap:4px;min-width:0;display:flex}.YVNYya_name{color:var(--dsw-alias-label-primary);font-size:15px;font-weight:600;line-height:1.4}.YVNYya_description{color:var(--dsw-alias-label-tertiary);font-size:13px;line-height:1.5}.YVNYya_pending{corner-shape:round;background:var(--dsw-alias-bg-module-platform);color:var(--dsw-alias-label-secondary);white-space:nowrap;border-radius:999px;flex:none;padding:1px 8px;font-size:11px;font-weight:500;line-height:17px}.YVNYya_chevron{color:var(--dsw-alias-label-tertiary);flex:none;transition:transform .16s}.YVNYya_chevronOpen{transform:rotate(180deg)}.YVNYya_body{border-top:.5px solid var(--dsw-alias-border-l2);margin:0 16px;padding-bottom:8px}.YVNYya_status,.YVNYya_readOnly,.YVNYya_hint,.YVNYya_advancedHint,.YVNYya_error{margin:0;font-size:12px;line-height:1.5}.YVNYya_status,.YVNYya_readOnly,.YVNYya_hint,.YVNYya_advancedHint{color:var(--dsw-alias-label-tertiary)}.YVNYya_status{padding:12px 16px;list-style:none}.YVNYya_readOnly{padding-top:12px}.YVNYya_field{gap:6px;padding:12px 0;display:grid}.YVNYya_body>.YVNYya_field+.YVNYya_group,.YVNYya_body>.YVNYya_group+.YVNYya_field{border-top:.5px solid var(--dsw-alias-border-l2)}.YVNYya_group{padding:12px 0}.YVNYya_groupTitle{color:var(--dsw-alias-label-primary);margin:0;font-size:13px;font-weight:500;line-height:1.5}.YVNYya_limitsTitle{border-top:.5px solid var(--dsw-alias-border-l2);color:var(--dsw-alias-label-primary);margin:16px 0 0;padding-top:12px;font-size:13px;font-weight:500;line-height:1.5}.YVNYya_limitsTitle+.YVNYya_hint{margin-top:4px}.YVNYya_group>.YVNYya_field:first-of-type{padding-top:12px}.YVNYya_group>.YVNYya_field+.YVNYya_field,.YVNYya_pairedFields{border-top:.5px solid var(--dsw-alias-border-l2)}.YVNYya_fieldHead{align-items:center;gap:8px;display:flex}.YVNYya_label{min-width:0;color:var(--dsw-alias-label-primary);flex:1;font-size:13px;font-weight:500;line-height:1.5}.YVNYya_reset{color:var(--dsw-alias-label-secondary);cursor:pointer;font:inherit;background:0 0;border:0;padding:0;font-size:12px;line-height:1.5}.YVNYya_reset:hover:not(:disabled){color:var(--dsw-alias-label-primary)}.YVNYya_reset:disabled{cursor:default}.YVNYya_input{box-sizing:border-box;width:100%;min-width:0;display:flex}.YVNYya_select{box-sizing:border-box;border:.5px solid var(--dsw-alias-border-l3);width:100%;min-width:0;height:32px;color:var(--dsw-alias-label-primary);font:inherit;background:0 0;border-radius:8px;padding:0 8px;font-size:13px}.YVNYya_select:disabled{opacity:.5}.YVNYya_select:focus-visible{outline:2px solid var(--dsw-alias-brand-primary);outline-offset:2px}.YVNYya_warning{color:var(--dsw-alias-label-error);margin:0;font-size:12px;line-height:1.5}.YVNYya_pairedFields{grid-template-columns:repeat(auto-fit,minmax(220px,1fr));gap:16px;margin:4px 0;padding:4px 0;display:grid}.YVNYya_pairedFields .YVNYya_field{min-width:0}.YVNYya_switch{box-sizing:border-box;background:var(--dsw-alias-border-l3);cursor:pointer;border:0;border-radius:10px;width:36px;height:20px;padding:2px;position:relative}.YVNYya_switchOn{background:var(--dsw-alias-brand-primary)}.YVNYya_switch:disabled{cursor:default;opacity:.5}.YVNYya_switch:focus-visible{outline:2px solid var(--dsw-alias-brand-primary);outline-offset:2px}.YVNYya_switchThumb{corner-shape:round;background:var(--dsw-alias-label-primary-foreground);border-radius:50%;width:16px;height:16px;transition:transform .12s;display:block}.YVNYya_switchOn .YVNYya_switchThumb{transform:translate(16px)}.YVNYya_modelSelection{gap:10px;padding:12px 0;display:grid}.YVNYya_notice{color:var(--dsw-alias-label-tertiary);margin:0;font-size:12px;line-height:1.5}.YVNYya_catalogError{color:var(--dsw-alias-label-error);justify-content:space-between;align-items:center;gap:12px;font-size:12px;line-height:1.5;display:flex}.YVNYya_catalogError button{color:var(--dsw-alias-brand-primary);cursor:pointer;font:inherit;background:0 0;border:0;padding:0}.YVNYya_models{border:.5px solid var(--dsw-alias-border-l4);border-radius:8px;gap:6px;min-width:0;max-height:280px;margin:0;padding:10px;display:grid;overflow:auto}.YVNYya_models legend{color:var(--dsw-alias-label-secondary);padding:0 4px;font-size:12px}.YVNYya_modelGroup{gap:6px;display:grid}.YVNYya_modelGroup+.YVNYya_modelGroup{border-top:.5px solid var(--dsw-alias-border-l3);margin-top:4px;padding-top:10px}.YVNYya_providerName{color:var(--dsw-alias-label-tertiary);padding:0 6px;font-size:11px;font-weight:500}.YVNYya_model{cursor:pointer;border-radius:6px;grid-template-columns:auto minmax(0,1fr) auto;align-items:center;gap:8px;min-width:0;padding:6px;display:grid}.YVNYya_model:hover{background:var(--dsw-alias-bg-layer-4)}.YVNYya_modelName,.YVNYya_route{text-overflow:ellipsis;white-space:nowrap;display:block;overflow:hidden}.YVNYya_modelName{color:var(--dsw-alias-label-primary);font-size:13px}.YVNYya_route{color:var(--dsw-alias-label-tertiary);margin-top:2px;font-size:11px}.YVNYya_unavailable{color:var(--dsw-alias-label-tertiary);font-size:11px}.YVNYya_error{color:var(--dsw-alias-label-error)}.YVNYya_advancedHint{border-top:.5px solid var(--dsw-alias-border-l2);padding-top:12px}.YVNYya_footer{border-top:.5px solid var(--dsw-alias-border-l2);justify-content:flex-end;align-items:center;gap:8px;padding:12px 0 4px;display:flex}@media (width<=480px){.YVNYya_pairedFields{grid-template-columns:minmax(0,1fr);gap:0}.YVNYya_pairedFields .YVNYya_field+.YVNYya_field{border-top:.5px solid var(--dsw-alias-border-l2)}}";
76
+ const css$1 = ".YVNYya_card{border:.5px solid var(--dsw-alias-border-l4);background:var(--dsw-alias-bg-layer-2);border-radius:16px}.YVNYya_header{align-items:center;gap:12px;padding:14px 16px;display:flex}.YVNYya_headText{flex-direction:column;flex:1;gap:4px;min-width:0;display:flex}.YVNYya_name{color:var(--dsw-alias-label-primary);font-size:15px;font-weight:600;line-height:1.4}.YVNYya_description{color:var(--dsw-alias-label-tertiary);font-size:13px;line-height:1.5}.YVNYya_pending{corner-shape:round;background:var(--dsw-alias-bg-module-platform);color:var(--dsw-alias-label-secondary);white-space:nowrap;border-radius:999px;flex:none;padding:1px 8px;font-size:11px;font-weight:500;line-height:17px}.YVNYya_body{border-top:.5px solid var(--dsw-alias-border-l2);margin:0 16px;padding-bottom:8px}.YVNYya_status,.YVNYya_readOnly,.YVNYya_hint,.YVNYya_advancedHint,.YVNYya_error{margin:0;font-size:12px;line-height:1.5}.YVNYya_status,.YVNYya_readOnly,.YVNYya_hint,.YVNYya_advancedHint{color:var(--dsw-alias-label-tertiary)}.YVNYya_status{padding:12px 16px}.YVNYya_readOnly{padding-top:12px}.YVNYya_field{gap:6px;padding:12px 0;display:grid}.YVNYya_body>.YVNYya_field+.YVNYya_group,.YVNYya_body>.YVNYya_group+.YVNYya_field{border-top:.5px solid var(--dsw-alias-border-l2)}.YVNYya_group{padding:12px 0}.YVNYya_groupTitle{color:var(--dsw-alias-label-primary);margin:0;font-size:13px;font-weight:500;line-height:1.5}.YVNYya_limitsTitle{border-top:.5px solid var(--dsw-alias-border-l2);color:var(--dsw-alias-label-primary);margin:16px 0 0;padding-top:12px;font-size:13px;font-weight:500;line-height:1.5}.YVNYya_limitsTitle+.YVNYya_hint{margin-top:4px}.YVNYya_group>.YVNYya_field:first-of-type{padding-top:12px}.YVNYya_group>.YVNYya_field+.YVNYya_field,.YVNYya_pairedFields{border-top:.5px solid var(--dsw-alias-border-l2)}.YVNYya_fieldHead{align-items:center;gap:8px;display:flex}.YVNYya_label{min-width:0;color:var(--dsw-alias-label-primary);flex:1;font-size:13px;font-weight:500;line-height:1.5}.YVNYya_reset{color:var(--dsw-alias-label-secondary);cursor:pointer;font:inherit;background:0 0;border:0;padding:0;font-size:12px;line-height:1.5}.YVNYya_reset:hover:not(:disabled){color:var(--dsw-alias-label-primary)}.YVNYya_reset:disabled{cursor:default}.YVNYya_input{box-sizing:border-box;width:100%;min-width:0;display:flex}.YVNYya_select{box-sizing:border-box;border:.5px solid var(--dsw-alias-border-l3);width:100%;min-width:0;height:32px;color:var(--dsw-alias-label-primary);font:inherit;background:0 0;border-radius:8px;padding:0 8px;font-size:13px}.YVNYya_select:disabled{opacity:.5}.YVNYya_select:focus-visible{outline:2px solid var(--dsw-alias-brand-primary);outline-offset:2px}.YVNYya_warning{color:var(--dsw-alias-label-error);margin:0;font-size:12px;line-height:1.5}.YVNYya_pairedFields{grid-template-columns:repeat(auto-fit,minmax(220px,1fr));gap:16px;margin:4px 0;padding:4px 0;display:grid}.YVNYya_pairedFields .YVNYya_field{min-width:0}.YVNYya_switch{box-sizing:border-box;background:var(--dsw-alias-border-l3);cursor:pointer;border:0;border-radius:10px;width:36px;height:20px;padding:2px;position:relative}.YVNYya_switchOn{background:var(--dsw-alias-brand-primary)}.YVNYya_switch:disabled{cursor:default;opacity:.5}.YVNYya_switch:focus-visible{outline:2px solid var(--dsw-alias-brand-primary);outline-offset:2px}.YVNYya_switchThumb{corner-shape:round;background:var(--dsw-alias-label-primary-foreground);border-radius:50%;width:16px;height:16px;transition:transform .12s;display:block}.YVNYya_switchOn .YVNYya_switchThumb{transform:translate(16px)}.YVNYya_modelSelection{gap:10px;padding:12px 0;display:grid}.YVNYya_notice{color:var(--dsw-alias-label-tertiary);margin:0;font-size:12px;line-height:1.5}.YVNYya_catalogError{color:var(--dsw-alias-label-error);justify-content:space-between;align-items:center;gap:12px;font-size:12px;line-height:1.5;display:flex}.YVNYya_catalogError button{color:var(--dsw-alias-brand-primary);cursor:pointer;font:inherit;background:0 0;border:0;padding:0}.YVNYya_models{border:.5px solid var(--dsw-alias-border-l4);border-radius:8px;gap:6px;min-width:0;max-height:280px;margin:0;padding:10px;display:grid;overflow:auto}.YVNYya_models legend{color:var(--dsw-alias-label-secondary);padding:0 4px;font-size:12px}.YVNYya_modelGroup{gap:6px;display:grid}.YVNYya_modelGroup+.YVNYya_modelGroup{border-top:.5px solid var(--dsw-alias-border-l3);margin-top:4px;padding-top:10px}.YVNYya_providerName{color:var(--dsw-alias-label-tertiary);padding:0 6px;font-size:11px;font-weight:500}.YVNYya_model{cursor:pointer;border-radius:6px;grid-template-columns:auto minmax(0,1fr) auto;align-items:center;gap:8px;min-width:0;padding:6px;display:grid}.YVNYya_model:hover{background:var(--dsw-alias-bg-layer-4)}.YVNYya_modelName,.YVNYya_route{text-overflow:ellipsis;white-space:nowrap;display:block;overflow:hidden}.YVNYya_modelName{color:var(--dsw-alias-label-primary);font-size:13px}.YVNYya_route{color:var(--dsw-alias-label-tertiary);margin-top:2px;font-size:11px}.YVNYya_unavailable{color:var(--dsw-alias-label-tertiary);font-size:11px}.YVNYya_error{color:var(--dsw-alias-label-error)}.YVNYya_advancedHint{border-top:.5px solid var(--dsw-alias-border-l2);padding-top:12px}.YVNYya_footer{border-top:.5px solid var(--dsw-alias-border-l2);justify-content:flex-end;align-items:center;gap:8px;padding:12px 0 4px;display:flex}@media (width<=480px){.YVNYya_pairedFields{grid-template-columns:minmax(0,1fr);gap:0}.YVNYya_pairedFields .YVNYya_field+.YVNYya_field{border-top:.5px solid var(--dsw-alias-border-l2)}}";
102
77
  const tagId$1 = "dsh-hypatia-auto-memory/SettingsCard.module.css";
103
78
  if (typeof document !== "undefined" && document.querySelector("style[data-plugin-css=" + JSON.stringify(tagId$1) + "]") === null) {
104
79
  const tag = document.createElement("style");
@@ -111,10 +86,7 @@ window.__ModuleLoader__.load({
111
86
  "advancedHint": "YVNYya_advancedHint",
112
87
  "body": "YVNYya_body",
113
88
  "card": "YVNYya_card",
114
- "cardOpen": "YVNYya_cardOpen",
115
89
  "catalogError": "YVNYya_catalogError",
116
- "chevron": "YVNYya_chevron",
117
- "chevronOpen": "YVNYya_chevronOpen",
118
90
  "description": "YVNYya_description",
119
91
  "error": "YVNYya_error",
120
92
  "field": "YVNYya_field",
@@ -162,9 +134,9 @@ window.__ModuleLoader__.load({
162
134
  description: "自动将对话轮次记录到 Hypatia,并在新内容足够多时通过独立模型路由进行后台整合。",
163
135
  enable: "启用自动记忆",
164
136
  autoApprove: "自动批准模型发起的 hypatia 命令",
165
- autoApproveHint: "仅限不含管道、重定向、命令串联的纯 hypatia 调用;插件自身的写入不经过此路径。关闭后模型每次检索都会弹出批准框。修改后需重载配置档生效。",
137
+ autoApproveHint: "仅限不含管道、重定向、命令串联的纯 hypatia 调用;插件自身的写入不经过此路径。关闭后模型每次检索都会弹出批准框。保存后采集器会自动重启以应用新值。",
166
138
  shelf: "记忆写入的 shelf",
167
- shelfHint: "插件的记录、整合与会话开始时的规则预加载都读写这个 shelf;不是 default 时,会话开头会提示模型为 hypatia 命令加上 --shelf。修改后需重载配置档生效。每个 shelf 的记录进度单独保存,切回原来的 shelf 会从上次的位置继续;切到新的 shelf 后,每个有活动的会话会把完整历史重新记录进去并整合一遍(会消耗模型调用)。列表来自 hypatia list,约每分钟刷新一次。",
139
+ shelfHint: "插件的记录、整合与会话开始时的规则预加载都读写这个 shelf;不是 default 时,会话开头会提示模型为 hypatia 命令加上 --shelf。保存后采集器会自动重启,改写到新的 shelf。每个 shelf 的记录进度单独保存,切回原来的 shelf 会从上次的位置继续;切到新的 shelf 后,每个有活动的会话会把完整历史重新记录进去并整合一遍(会消耗模型调用)。列表来自 hypatia list,打开本页时按需读取。",
168
140
  shelfLoading: "正在读取 hypatia list…",
169
141
  shelfLoadFailed: "无法读取 shelf 列表。",
170
142
  shelfListingFailed: "hypatia list 执行失败,列表可能已过时:{message}",
@@ -194,7 +166,7 @@ window.__ModuleLoader__.load({
194
166
  maxOutputTokens: "输出预算(token)",
195
167
  maxWorkUnitsPerRun: "每次最多提取工作单元数",
196
168
  recallPreload: "召回:会话开始时预加载规则与禁忌",
197
- advancedHint: "高级字段(二进制、收集器上限、队列调优)可直接在 settings.yaml 的 {ns} 键下编辑。",
169
+ advancedHint: "高级字段(二进制、收集器上限、队列调优)可在「设置 → 插件」的本插件页,或 profile patch 的 {ns} 条目 config 下编辑。",
198
170
  reset: "重置",
199
171
  discard: "放弃",
200
172
  save: "保存",
@@ -251,9 +223,9 @@ window.__ModuleLoader__.load({
251
223
  description: "Auto-memory logs conversation turns into Hypatia and runs background consolidation on a dedicated model route.",
252
224
  enable: "Enable auto-memory",
253
225
  autoApprove: "Auto-approve the agent's own hypatia commands",
254
- autoApproveHint: "Only plain hypatia calls with no pipe, redirect or chaining; this plugin's own writes never take that path. With it off, every retrieval raises an approval prompt. Takes effect after a profile reload.",
226
+ autoApproveHint: "Only plain hypatia calls with no pipe, redirect or chaining; this plugin's own writes never take that path. With it off, every retrieval raises an approval prompt. Saving a change restarts the collector to apply it.",
255
227
  shelf: "Memory shelf",
256
- shelfHint: "Logging, consolidation and the session-start rules preload all read and write this shelf; when it is not default, each session is told to pass --shelf to its hypatia commands. Takes effect after a profile reload. Progress is kept per shelf, so switching back resumes where that shelf left off; on a new shelf, each session is logged from its start the next time it is active, and consolidated again (model calls). Choices come from hypatia list, refreshed about once a minute.",
228
+ shelfHint: "Logging, consolidation and the session-start rules preload all read and write this shelf; when it is not default, each session is told to pass --shelf to its hypatia commands. Saving a change restarts the collector on the new shelf. Progress is kept per shelf, so switching back resumes where that shelf left off; on a new shelf, each session is logged from its start the next time it is active, and consolidated again (model calls). Choices come from hypatia list, read on demand when this page opens.",
257
229
  shelfLoading: "Reading hypatia list…",
258
230
  shelfLoadFailed: "Unable to load the shelf list.",
259
231
  shelfListingFailed: "hypatia list failed; the list may be stale: {message}",
@@ -283,7 +255,7 @@ window.__ModuleLoader__.load({
283
255
  maxOutputTokens: "Output budget (tokens)",
284
256
  maxWorkUnitsPerRun: "Max work units per run",
285
257
  recallPreload: "Recall: preload rules & taboos at session start",
286
- advancedHint: "Advanced fields (binaries, collector caps, queue tuning) can be edited directly in settings.yaml under the {ns} key.",
258
+ advancedHint: "Advanced fields (binaries, collector caps, queue tuning) can be edited in Settings → Plugins under this plugin, or under the {ns} entry's config in the profile patch.",
287
259
  reset: "Reset",
288
260
  discard: "Discard",
289
261
  save: "Save",
@@ -355,21 +327,21 @@ window.__ModuleLoader__.load({
355
327
  batchSize: 16
356
328
  }
357
329
  };
358
- function getSnapshotValue(scope) {
359
- return scope.getSnapshot();
330
+ function getSnapshotValue(form) {
331
+ return form.getSnapshot();
360
332
  }
361
- function subscribe(scope, cb) {
362
- return scope.subscribe(cb);
333
+ function subscribe(form, cb) {
334
+ return form.subscribe(cb);
363
335
  }
364
336
  function sectionValue(snap) {
365
337
  return snap.value ?? snap.base ?? void 0;
366
338
  }
367
- function useScopeValue(scope) {
368
- const [snap, setSnap] = (0, react.useState)(() => getSnapshotValue(scope));
339
+ function useScopeValue(form) {
340
+ const [snap, setSnap] = (0, react.useState)(() => getSnapshotValue(form));
369
341
  (0, react.useEffect)(() => {
370
- setSnap(getSnapshotValue(scope));
371
- return subscribe(scope, () => setSnap(getSnapshotValue(scope)));
372
- }, [scope]);
342
+ setSnap(getSnapshotValue(form));
343
+ return subscribe(form, () => setSnap(getSnapshotValue(form)));
344
+ }, [form]);
373
345
  return {
374
346
  snap,
375
347
  value: (0, react.useMemo)(() => sectionValue(snap), [snap])
@@ -390,7 +362,6 @@ window.__ModuleLoader__.load({
390
362
  recall: { preloadRulesTaboos: true }
391
363
  }, [value, base]);
392
364
  const [draft, setDraft] = (0, react.useState)(resolved);
393
- const [open, setOpen] = (0, react.useState)(false);
394
365
  const [saving, setSaving] = (0, react.useState)(false);
395
366
  const [error, setError] = (0, react.useState)(null);
396
367
  const [catalogStatus, setCatalogStatus] = (0, react.useState)("idle");
@@ -433,12 +404,11 @@ window.__ModuleLoader__.load({
433
404
  }
434
405
  }, [loadModelCatalog]);
435
406
  (0, react.useEffect)(() => {
436
- if (!open) return;
437
407
  loadCatalog();
438
408
  return () => {
439
409
  catalogGeneration.current += 1;
440
410
  };
441
- }, [open, loadCatalog]);
411
+ }, [loadCatalog]);
442
412
  const loadShelves = (0, react.useCallback)(async () => {
443
413
  const generation = ++shelfGeneration.current;
444
414
  setShelfStatus("loading");
@@ -454,12 +424,11 @@ window.__ModuleLoader__.load({
454
424
  }
455
425
  }, [loadShelfInventory]);
456
426
  (0, react.useEffect)(() => {
457
- if (!open) return;
458
427
  loadShelves();
459
428
  return () => {
460
429
  shelfGeneration.current += 1;
461
430
  };
462
- }, [open, loadShelves]);
431
+ }, [loadShelves]);
463
432
  const draftShelf = draft.shelf ?? "default";
464
433
  const baseShelf = base.shelf ?? "default";
465
434
  const choices = (0, react.useMemo)(() => shelfChoices(shelves, [
@@ -505,13 +474,20 @@ window.__ModuleLoader__.load({
505
474
  setSaving(true);
506
475
  setError(null);
507
476
  try {
508
- const tasks = [];
509
- if (!same(draft.enabled, resolved.enabled)) tasks.push(scope.set("enabled", draft.enabled));
510
- if (!same(draft.autoApprove, resolved.autoApprove)) tasks.push(scope.set("autoApprove", draft.autoApprove));
511
- if (!same(draft.shelf, resolved.shelf)) tasks.push(scope.set("shelf", draft.shelf));
512
- if (!same(draft.consolidation, resolved.consolidation)) tasks.push(scope.set("consolidation", draft.consolidation));
513
- if (!same(draft.recall, resolved.recall)) tasks.push(scope.set("recall", draft.recall));
514
- await Promise.all(tasks);
477
+ const ops = [];
478
+ const setField = (field, value) => {
479
+ ops.push({
480
+ op: "set",
481
+ path: [field],
482
+ value
483
+ });
484
+ };
485
+ if (!same(draft.enabled, resolved.enabled)) setField("enabled", draft.enabled);
486
+ if (!same(draft.autoApprove, resolved.autoApprove)) setField("autoApprove", draft.autoApprove);
487
+ if (!same(draft.shelf, resolved.shelf)) setField("shelf", draft.shelf);
488
+ if (!same(draft.consolidation, resolved.consolidation)) setField("consolidation", draft.consolidation);
489
+ if (!same(draft.recall, resolved.recall)) setField("recall", draft.recall);
490
+ if (ops.length > 0 && !await scope.mutate(ops)) throw new Error("the Host refused the change; nothing was saved");
515
491
  } catch (err) {
516
492
  const message = err instanceof Error ? err.message : String(err);
517
493
  console.error(`[${NS}] save failed:`, err);
@@ -525,11 +501,11 @@ window.__ModuleLoader__.load({
525
501
  saveBlocked,
526
502
  scope
527
503
  ]);
528
- if (snap.status === "loading") return /* @__PURE__ */ (0, react_jsx_runtime.jsx)("li", {
504
+ if (snap.status === "loading") return /* @__PURE__ */ (0, react_jsx_runtime.jsx)("p", {
529
505
  className: SettingsCard_module_css_default.status,
530
506
  children: t("loading", { ns: NS })
531
507
  });
532
- if (snap.status === "unavailable") return /* @__PURE__ */ (0, react_jsx_runtime.jsx)("li", {
508
+ if (snap.status === "unavailable") return /* @__PURE__ */ (0, react_jsx_runtime.jsx)("p", {
533
509
  className: SettingsCard_module_css_default.status,
534
510
  children: t("unavailable", { ns: NS })
535
511
  });
@@ -569,31 +545,25 @@ window.__ModuleLoader__.load({
569
545
  }) : null
570
546
  ]
571
547
  }, candidate.key);
572
- return /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("li", {
573
- className: `${SettingsCard_module_css_default.card} ${open ? SettingsCard_module_css_default.cardOpen : ""}`,
574
- children: [/* @__PURE__ */ (0, react_jsx_runtime.jsxs)("button", {
575
- type: "button",
548
+ return /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("section", {
549
+ className: SettingsCard_module_css_default.card,
550
+ "aria-label": t("title"),
551
+ children: [/* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
576
552
  className: SettingsCard_module_css_default.header,
577
- onClick: () => setOpen(!open),
578
- "aria-expanded": open,
579
- children: [
580
- /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("span", {
581
- className: SettingsCard_module_css_default.headText,
582
- children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
583
- className: SettingsCard_module_css_default.name,
584
- children: t("title")
585
- }), /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
586
- className: SettingsCard_module_css_default.description,
587
- children: t("description")
588
- })]
589
- }),
590
- dirty ? /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
591
- className: SettingsCard_module_css_default.pending,
592
- children: t("unsaved")
593
- }) : null,
594
- /* @__PURE__ */ (0, react_jsx_runtime.jsx)(_deepseek_ai_dsh_client_ui_primitives.IconChevronDownOutline14, { className: `${SettingsCard_module_css_default.chevron} ${open ? SettingsCard_module_css_default.chevronOpen : ""}` })
595
- ]
596
- }), open ? /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
553
+ children: [/* @__PURE__ */ (0, react_jsx_runtime.jsxs)("span", {
554
+ className: SettingsCard_module_css_default.headText,
555
+ children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
556
+ className: SettingsCard_module_css_default.name,
557
+ children: t("title")
558
+ }), /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
559
+ className: SettingsCard_module_css_default.description,
560
+ children: t("description")
561
+ })]
562
+ }), dirty ? /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
563
+ className: SettingsCard_module_css_default.pending,
564
+ children: t("unsaved")
565
+ }) : null]
566
+ }), /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
597
567
  className: SettingsCard_module_css_default.body,
598
568
  children: [
599
569
  !snap.writable ? /* @__PURE__ */ (0, react_jsx_runtime.jsx)("p", {
@@ -1083,22 +1053,11 @@ window.__ModuleLoader__.load({
1083
1053
  })]
1084
1054
  })
1085
1055
  ]
1086
- }) : null]
1056
+ })]
1087
1057
  });
1088
1058
  }
1089
1059
  //#endregion
1090
1060
  //#region src/client/memory-client.ts
1091
- /**
1092
- * The Memory tab's read side: one same-origin request per refresh.
1093
- *
1094
- * The Host half serves it from `/api/dsh-hypatia-auto-memory/session`
1095
- * (see `src/memory-api.js`), which is parameterized by session and authenticates
1096
- * itself — unlike a settings namespace, it can answer for the one session the
1097
- * tab is bound to instead of shipping every session's state to the browser.
1098
- *
1099
- * Zero runtime imports beyond `fetch`, so the parsing half is unit-tested under
1100
- * `node:test` like `shelves.ts`.
1101
- */
1102
1061
  /** Route prefix the Host claims; the tab appends `/session`. */
1103
1062
  const MEMORY_API_PREFIX = "/api/dsh-hypatia-auto-memory";
1104
1063
  /**
@@ -1129,6 +1088,48 @@ window.__ModuleLoader__.load({
1129
1088
  return payload;
1130
1089
  };
1131
1090
  /**
1091
+ * Fetch the shelf listing for the settings card.
1092
+ *
1093
+ * Rides the same self-authenticating route family as the Memory tab — the
1094
+ * settings page passes the same loopback / same-origin checks — because the
1095
+ * 0.1.7 settings domain no longer lets a Host plugin publish a dynamic,
1096
+ * non-config namespace for the card to read.
1097
+ */
1098
+ const fetchShelves = async () => {
1099
+ const response = await fetch(`${MEMORY_API_PREFIX}/shelves`, {
1100
+ method: "GET",
1101
+ credentials: "same-origin",
1102
+ headers: { accept: "application/json" }
1103
+ });
1104
+ const body = await response.json().catch(() => void 0);
1105
+ if (!response.ok) {
1106
+ const message = isRecord(body) && typeof body.error === "string" ? body.error : `HTTP ${String(response.status)}`;
1107
+ throw new Error(message);
1108
+ }
1109
+ const inventory = parseShelfInventory(body);
1110
+ if (inventory === void 0) throw new Error("the Host returned an unreadable shelf listing");
1111
+ return inventory;
1112
+ };
1113
+ /**
1114
+ * Read the Host's `/shelves` answer into the card's shape, defensively.
1115
+ * @param value - the parsed response body.
1116
+ * @returns the listing, or undefined when the body is not one.
1117
+ */
1118
+ function parseShelfInventory(value) {
1119
+ if (!isRecord(value)) return void 0;
1120
+ return {
1121
+ shelves: Array.isArray(value.shelves) ? value.shelves.flatMap((shelf) => {
1122
+ if (!isRecord(shelf) || typeof shelf.name !== "string" || shelf.name === "") return [];
1123
+ return [{
1124
+ name: shelf.name,
1125
+ path: typeof shelf.path === "string" ? shelf.path : "",
1126
+ connected: shelf.connected === true
1127
+ }];
1128
+ }) : [],
1129
+ error: str(value.error)
1130
+ };
1131
+ }
1132
+ /**
1132
1133
  * Session-log events that are recorded but not yet consolidated.
1133
1134
  *
1134
1135
  * Both watermarks index the same log, so their difference is exactly the work
@@ -1689,27 +1690,29 @@ window.__ModuleLoader__.load({
1689
1690
  delete globalThis[APPLY_CLAIM];
1690
1691
  }
1691
1692
  /**
1692
- * Required client services: settings, locale, slots, the Host model catalog,
1693
- * and the settings descriptor. Each Remote namespace is its own service, so
1694
- * `remote.settings` must be declared here or reading it throws.
1693
+ * Required client services: slots, locale, the Host model catalog, and the
1694
+ * settings domain's shared configuration forms (the 0.1.7 successor of the
1695
+ * removed `settingsScope` service). Each Remote namespace is its own service,
1696
+ * so `remote.session` must be declared here or reading it throws.
1695
1697
  */
1696
1698
  const inject = [
1697
1699
  "slots",
1698
- "settingsScope",
1700
+ "configForms",
1699
1701
  "locale",
1700
1702
  "remote",
1701
- "remote.session",
1702
- "remote.settings"
1703
+ "remote.session"
1703
1704
  ];
1704
1705
  /** Mount the browser half. */
1705
1706
  function apply(ctx) {
1706
1707
  if (!claimApply()) return;
1707
- const scope = ctx.get("settingsScope").bind({ namespace: NS });
1708
+ const configForms = ctx.get("configForms");
1709
+ const form = configForms.get(NS);
1708
1710
  const locale = ctx.get("locale");
1709
1711
  ctx.effect(() => locale.register(NS, {
1710
1712
  zh,
1711
1713
  en
1712
1714
  }), "dsh-hypatia-auto-memory: locale dictionaries");
1715
+ const t = locale.bind(NS);
1713
1716
  const loadModelCatalog = async () => {
1714
1717
  const response = await ctx.remote.session.modelCatalog();
1715
1718
  if (!response.ok) throw new Error(`${response.error.code}: ${response.error.message}`);
@@ -1718,25 +1721,19 @@ window.__ModuleLoader__.load({
1718
1721
  partial: response.value.failures.length > 0
1719
1722
  };
1720
1723
  };
1721
- const loadShelfInventory = async () => {
1722
- const response = await ctx.remote.settings.describe();
1723
- if (!response.ok) throw new Error(`${response.error.code}: ${response.error.message}`);
1724
- const inventory = readShelfInventory(response.value.namespaces);
1725
- if (inventory === void 0) throw new Error("the Host publishes no shelf listing");
1726
- return inventory;
1727
- };
1724
+ const loadShelfInventory = fetchShelves;
1728
1725
  const renderCard = (props) => (0, react.createElement)(SettingsCard, {
1729
- scope,
1726
+ scope: form,
1730
1727
  loadModelCatalog,
1731
1728
  loadShelfInventory,
1732
1729
  ...props
1733
1730
  });
1734
- const unregister = ctx.slots.inject("settings.plugin.item", () => ctx.slots.register({
1735
- name: "settings.plugin.item",
1736
- key: NS,
1731
+ const unregister = ctx.effect(() => configForms.whileServed([NS], () => ctx.slots.inject("settings.plugins.tab", () => ctx.slots.register({
1732
+ name: "settings.plugins.tab",
1733
+ id: NS,
1734
+ label: () => t("title"),
1737
1735
  locale: NS
1738
- }, renderCard));
1739
- const t = locale.bind(NS);
1736
+ }, renderCard))), "dsh-hypatia-auto-memory: plugins settings tab");
1740
1737
  const unregisterMemoryView = ctx.slots.inject("conversation.view", () => ctx.slots.register({
1741
1738
  name: "conversation.view",
1742
1739
  id: "memory",