dsh-hypatia-auto-memory 0.4.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(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(entry) && entry.ns === "hypatia-auto-memory-shelves");
64
- if (!isRecord(view) || !isRecord(view.base)) return void 0;
65
- const { shelves, error } = view.base;
66
- return {
67
- shelves: Array.isArray(shelves) ? shelves.flatMap((shelf) => isRecord(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,23 +73,20 @@ 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 = ".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)}}";
102
- const tagId = "dsh-hypatia-auto-memory/SettingsCard.module.css";
103
- if (typeof document !== "undefined" && document.querySelector("style[data-plugin-css=" + JSON.stringify(tagId) + "]") === null) {
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)}}";
77
+ const tagId$1 = "dsh-hypatia-auto-memory/SettingsCard.module.css";
78
+ if (typeof document !== "undefined" && document.querySelector("style[data-plugin-css=" + JSON.stringify(tagId$1) + "]") === null) {
104
79
  const tag = document.createElement("style");
105
80
  tag.dataset.plugin = "dsh-hypatia-auto-memory";
106
- tag.dataset.pluginCss = tagId;
107
- tag.textContent = css;
81
+ tag.dataset.pluginCss = tagId$1;
82
+ tag.textContent = css$1;
108
83
  document.head.appendChild(tag);
109
84
  }
110
85
  var SettingsCard_module_css_default = {
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,14 +166,54 @@ 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: "保存",
201
173
  saving: "保存中…",
202
174
  saveFailed: "保存失败:{message}",
203
175
  readOnly: "只读:当前上下文不可写入设置文档。",
204
- unsaved: "未保存"
176
+ unsaved: "未保存",
177
+ viewMemory: "记忆",
178
+ memoryTitle: "本会话的记忆",
179
+ memoryUpdatedAt: "状态更新于 {time}",
180
+ memoryRefresh: "刷新",
181
+ memoryLoading: "正在读取记忆状态…",
182
+ memoryNoSnapshot: "插件还没有发布记忆状态。启用后完成一轮对话再回来看看。",
183
+ memoryLoadFailed: "无法读取记忆状态:{message}",
184
+ memoryUnknown: "这个会话还没有记忆记录。第一轮对话结束后会开始记录。",
185
+ memoryLogged: "对话记录",
186
+ memorySeqValue: "第 {seq} 条事件",
187
+ memoryConsolidated: "摘要整合",
188
+ memoryCaughtUp: "已结清",
189
+ memoryBehind: "较记录落后 {behind} 条",
190
+ memoryPending: "待整合内容",
191
+ memoryPendingValue: "约 {tokens} token",
192
+ memorySessionNode: "会话节点",
193
+ memorySessionNodeYes: "已建立",
194
+ memorySessionNodeNo: "未建立(宿主未提供会话摘要)",
195
+ memoryBelongTo: "已链接消息",
196
+ memoryBelongToValue: "{count} 条",
197
+ memoryDeferred: "等待会话载入的任务",
198
+ memoryFailedTitle: "失败任务",
199
+ memoryFailedItem: "{kind} 连续失败 {attempts} 次",
200
+ memoryContentTitle: "记忆内容",
201
+ memoryContentFailed: "无法读取 shelf 中的记忆内容:{message}",
202
+ memorySummariesTitle: "跨度摘要",
203
+ memoryWorkUnitsTitle: "工作单元",
204
+ memoryNoSummaries: "这个会话还没有产生摘要。整合运行后会出现在这里。",
205
+ memoryNoWorkUnits: "这个会话还没有提炼出工作单元。",
206
+ memoryCount: "显示 {shown} / 共 {total}",
207
+ memoryTier: "{level} 级",
208
+ memoryWorkUnit: "工作单元",
209
+ memoryEntryExpand: "展开",
210
+ memoryEntryCollapse: "收起",
211
+ memoryEntryEmpty: "这条条目的正文为空,或已超过展示上限。",
212
+ memoryCopyCode: "复制",
213
+ memoryCopiedCode: "已复制",
214
+ memoryFootnotes: "脚注",
215
+ memoryTruncated: "内容超出展示上限,较早的条目或过长的正文已被截断。",
216
+ memoryHint: "诊断视图:原始 msg-* 对话正文不在此显示,需要时让 Agent 通过 hypatia 检索。状态每 5 秒重读一次;「状态更新于」是宿主最近一次看到这些状态发生变化的时间(首次读取也算一次),不变说明这段时间没有新进展。两个条数都按会话日志的事件序号计(含工具调用,不是消息条数),所以它们之差就是还没总结的部分。"
205
217
  };
206
218
  /** English dictionary checked against the Chinese key set. */
207
219
  const en = {
@@ -211,9 +223,9 @@ window.__ModuleLoader__.load({
211
223
  description: "Auto-memory logs conversation turns into Hypatia and runs background consolidation on a dedicated model route.",
212
224
  enable: "Enable auto-memory",
213
225
  autoApprove: "Auto-approve the agent's own hypatia commands",
214
- 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.",
215
227
  shelf: "Memory shelf",
216
- 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.",
217
229
  shelfLoading: "Reading hypatia list…",
218
230
  shelfLoadFailed: "Unable to load the shelf list.",
219
231
  shelfListingFailed: "hypatia list failed; the list may be stale: {message}",
@@ -243,14 +255,54 @@ window.__ModuleLoader__.load({
243
255
  maxOutputTokens: "Output budget (tokens)",
244
256
  maxWorkUnitsPerRun: "Max work units per run",
245
257
  recallPreload: "Recall: preload rules & taboos at session start",
246
- 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.",
247
259
  reset: "Reset",
248
260
  discard: "Discard",
249
261
  save: "Save",
250
262
  saving: "Saving…",
251
263
  saveFailed: "Save failed: {message}",
252
264
  readOnly: "Read-only: settings document is not writable in this context.",
253
- unsaved: "Unsaved"
265
+ unsaved: "Unsaved",
266
+ viewMemory: "Memory",
267
+ memoryTitle: "This session's memory",
268
+ memoryUpdatedAt: "State updated {time}",
269
+ memoryRefresh: "Refresh",
270
+ memoryLoading: "Reading memory status…",
271
+ memoryNoSnapshot: "The plugin has not published a status snapshot yet. Enable it, finish a turn, and come back.",
272
+ memoryLoadFailed: "Unable to read memory status: {message}",
273
+ memoryUnknown: "This session has no memory record yet. Logging starts once the first turn ends.",
274
+ memoryLogged: "Conversation log",
275
+ memorySeqValue: "event {seq}",
276
+ memoryConsolidated: "Summarisation",
277
+ memoryCaughtUp: "caught up",
278
+ memoryBehind: "{behind} events behind the log",
279
+ memoryPending: "Waiting to summarise",
280
+ memoryPendingValue: "~{tokens} tokens",
281
+ memorySessionNode: "Session node",
282
+ memorySessionNodeYes: "created",
283
+ memorySessionNodeNo: "not created (the Host supplied no session summary)",
284
+ memoryBelongTo: "Linked messages",
285
+ memoryBelongToValue: "{count}",
286
+ memoryDeferred: "Tasks waiting for the session to load",
287
+ memoryFailedTitle: "Failed tasks",
288
+ memoryFailedItem: "{kind} failed {attempts} times",
289
+ memoryContentTitle: "Memory content",
290
+ memoryContentFailed: "Unable to read memory content from the shelf: {message}",
291
+ memorySummariesTitle: "Span summaries",
292
+ memoryWorkUnitsTitle: "Work units",
293
+ memoryNoSummaries: "This session has produced no summary yet. They appear here once consolidation runs.",
294
+ memoryNoWorkUnits: "This session has yielded no work units yet.",
295
+ memoryCount: "showing {shown} of {total}",
296
+ memoryTier: "tier {level}",
297
+ memoryWorkUnit: "work unit",
298
+ memoryEntryExpand: "Expand",
299
+ memoryEntryCollapse: "Collapse",
300
+ memoryEntryEmpty: "This entry has no body, or it exceeded the display cap.",
301
+ memoryCopyCode: "Copy",
302
+ memoryCopiedCode: "Copied",
303
+ memoryFootnotes: "Footnotes",
304
+ memoryTruncated: "Content exceeded the display cap: earlier entries or over-long bodies were cut.",
305
+ memoryHint: "Diagnostic view: raw msg-* conversation text is not shown here — ask the agent to retrieve it through hypatia. Status is re-read every 5 seconds; the timestamp is when the Host last saw that state change (the first read counts as one), so an unchanged one means nothing new happened. Both counts index the session log's event sequence (tool calls included, not messages), so their difference is what is left to summarise."
254
306
  };
255
307
  //#endregion
256
308
  //#region src/client/SettingsCard.tsx
@@ -275,21 +327,21 @@ window.__ModuleLoader__.load({
275
327
  batchSize: 16
276
328
  }
277
329
  };
278
- function getSnapshotValue(scope) {
279
- return scope.getSnapshot();
330
+ function getSnapshotValue(form) {
331
+ return form.getSnapshot();
280
332
  }
281
- function subscribe(scope, cb) {
282
- return scope.subscribe(cb);
333
+ function subscribe(form, cb) {
334
+ return form.subscribe(cb);
283
335
  }
284
336
  function sectionValue(snap) {
285
337
  return snap.value ?? snap.base ?? void 0;
286
338
  }
287
- function useScopeValue(scope) {
288
- const [snap, setSnap] = (0, react.useState)(() => getSnapshotValue(scope));
339
+ function useScopeValue(form) {
340
+ const [snap, setSnap] = (0, react.useState)(() => getSnapshotValue(form));
289
341
  (0, react.useEffect)(() => {
290
- setSnap(getSnapshotValue(scope));
291
- return subscribe(scope, () => setSnap(getSnapshotValue(scope)));
292
- }, [scope]);
342
+ setSnap(getSnapshotValue(form));
343
+ return subscribe(form, () => setSnap(getSnapshotValue(form)));
344
+ }, [form]);
293
345
  return {
294
346
  snap,
295
347
  value: (0, react.useMemo)(() => sectionValue(snap), [snap])
@@ -310,7 +362,6 @@ window.__ModuleLoader__.load({
310
362
  recall: { preloadRulesTaboos: true }
311
363
  }, [value, base]);
312
364
  const [draft, setDraft] = (0, react.useState)(resolved);
313
- const [open, setOpen] = (0, react.useState)(false);
314
365
  const [saving, setSaving] = (0, react.useState)(false);
315
366
  const [error, setError] = (0, react.useState)(null);
316
367
  const [catalogStatus, setCatalogStatus] = (0, react.useState)("idle");
@@ -353,12 +404,11 @@ window.__ModuleLoader__.load({
353
404
  }
354
405
  }, [loadModelCatalog]);
355
406
  (0, react.useEffect)(() => {
356
- if (!open) return;
357
407
  loadCatalog();
358
408
  return () => {
359
409
  catalogGeneration.current += 1;
360
410
  };
361
- }, [open, loadCatalog]);
411
+ }, [loadCatalog]);
362
412
  const loadShelves = (0, react.useCallback)(async () => {
363
413
  const generation = ++shelfGeneration.current;
364
414
  setShelfStatus("loading");
@@ -374,12 +424,11 @@ window.__ModuleLoader__.load({
374
424
  }
375
425
  }, [loadShelfInventory]);
376
426
  (0, react.useEffect)(() => {
377
- if (!open) return;
378
427
  loadShelves();
379
428
  return () => {
380
429
  shelfGeneration.current += 1;
381
430
  };
382
- }, [open, loadShelves]);
431
+ }, [loadShelves]);
383
432
  const draftShelf = draft.shelf ?? "default";
384
433
  const baseShelf = base.shelf ?? "default";
385
434
  const choices = (0, react.useMemo)(() => shelfChoices(shelves, [
@@ -425,13 +474,20 @@ window.__ModuleLoader__.load({
425
474
  setSaving(true);
426
475
  setError(null);
427
476
  try {
428
- const tasks = [];
429
- if (!same(draft.enabled, resolved.enabled)) tasks.push(scope.set("enabled", draft.enabled));
430
- if (!same(draft.autoApprove, resolved.autoApprove)) tasks.push(scope.set("autoApprove", draft.autoApprove));
431
- if (!same(draft.shelf, resolved.shelf)) tasks.push(scope.set("shelf", draft.shelf));
432
- if (!same(draft.consolidation, resolved.consolidation)) tasks.push(scope.set("consolidation", draft.consolidation));
433
- if (!same(draft.recall, resolved.recall)) tasks.push(scope.set("recall", draft.recall));
434
- 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");
435
491
  } catch (err) {
436
492
  const message = err instanceof Error ? err.message : String(err);
437
493
  console.error(`[${NS}] save failed:`, err);
@@ -445,11 +501,11 @@ window.__ModuleLoader__.load({
445
501
  saveBlocked,
446
502
  scope
447
503
  ]);
448
- 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", {
449
505
  className: SettingsCard_module_css_default.status,
450
506
  children: t("loading", { ns: NS })
451
507
  });
452
- 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", {
453
509
  className: SettingsCard_module_css_default.status,
454
510
  children: t("unavailable", { ns: NS })
455
511
  });
@@ -489,31 +545,25 @@ window.__ModuleLoader__.load({
489
545
  }) : null
490
546
  ]
491
547
  }, candidate.key);
492
- return /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("li", {
493
- className: `${SettingsCard_module_css_default.card} ${open ? SettingsCard_module_css_default.cardOpen : ""}`,
494
- children: [/* @__PURE__ */ (0, react_jsx_runtime.jsxs)("button", {
495
- 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", {
496
552
  className: SettingsCard_module_css_default.header,
497
- onClick: () => setOpen(!open),
498
- "aria-expanded": open,
499
- children: [
500
- /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("span", {
501
- className: SettingsCard_module_css_default.headText,
502
- children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
503
- className: SettingsCard_module_css_default.name,
504
- children: t("title")
505
- }), /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
506
- className: SettingsCard_module_css_default.description,
507
- children: t("description")
508
- })]
509
- }),
510
- dirty ? /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
511
- className: SettingsCard_module_css_default.pending,
512
- children: t("unsaved")
513
- }) : null,
514
- /* @__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 : ""}` })
515
- ]
516
- }), 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", {
517
567
  className: SettingsCard_module_css_default.body,
518
568
  children: [
519
569
  !snap.writable ? /* @__PURE__ */ (0, react_jsx_runtime.jsx)("p", {
@@ -1003,7 +1053,628 @@ window.__ModuleLoader__.load({
1003
1053
  })]
1004
1054
  })
1005
1055
  ]
1006
- }) : null]
1056
+ })]
1057
+ });
1058
+ }
1059
+ //#endregion
1060
+ //#region src/client/memory-client.ts
1061
+ /** Route prefix the Host claims; the tab appends `/session`. */
1062
+ const MEMORY_API_PREFIX = "/api/dsh-hypatia-auto-memory";
1063
+ /**
1064
+ * The URL one refresh reads.
1065
+ * @param sessionId - the session the tab is bound to.
1066
+ * @param content - whether to ask the Host to read the shelf as well.
1067
+ * @returns a same-origin path with its query.
1068
+ */
1069
+ function memoryUrl(sessionId, content) {
1070
+ const query = new URLSearchParams({ session: sessionId });
1071
+ if (content) query.set("content", "1");
1072
+ return `${MEMORY_API_PREFIX}/session?${query.toString()}`;
1073
+ }
1074
+ /** Fetch one session's memory from the Host. */
1075
+ const fetchMemory = async (sessionId, { content }) => {
1076
+ const response = await fetch(memoryUrl(sessionId, content), {
1077
+ method: "GET",
1078
+ credentials: "same-origin",
1079
+ headers: { accept: "application/json" }
1080
+ });
1081
+ const body = await response.json().catch(() => void 0);
1082
+ if (!response.ok) {
1083
+ const message = isRecord(body) && typeof body.error === "string" ? body.error : `HTTP ${String(response.status)}`;
1084
+ throw new Error(message);
1085
+ }
1086
+ const payload = parseMemoryPayload(body, sessionId);
1087
+ if (payload === void 0) throw new Error("the Host returned an unreadable memory payload");
1088
+ return payload;
1089
+ };
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
+ /**
1133
+ * Session-log events that are recorded but not yet consolidated.
1134
+ *
1135
+ * Both watermarks index the same log, so their difference is exactly the work
1136
+ * consolidation still owes. Clamped at zero: a consolidation run may cover a
1137
+ * range the logging watermark has not caught up to yet, which is not a backlog.
1138
+ *
1139
+ * @param session - the session half of a payload.
1140
+ * @returns the gap in session-log events.
1141
+ */
1142
+ function consolidationGap(session) {
1143
+ return Math.max(0, session.loggedSeq - session.consolidatedSeq);
1144
+ }
1145
+ function isRecord(value) {
1146
+ return typeof value === "object" && value !== null && !Array.isArray(value);
1147
+ }
1148
+ function num(value) {
1149
+ return typeof value === "number" && Number.isFinite(value) ? value : 0;
1150
+ }
1151
+ function str(value) {
1152
+ return typeof value === "string" ? value : "";
1153
+ }
1154
+ function parseFailedTasks(value) {
1155
+ if (!Array.isArray(value)) return [];
1156
+ return value.flatMap((entry) => isRecord(entry) ? [{
1157
+ kind: str(entry.kind),
1158
+ sessionId: str(entry.sessionId),
1159
+ attempts: num(entry.attempts),
1160
+ error: str(entry.error)
1161
+ }] : []);
1162
+ }
1163
+ function parseEntries(value) {
1164
+ if (!Array.isArray(value)) return [];
1165
+ return value.flatMap((entry) => {
1166
+ if (!isRecord(entry)) return [];
1167
+ const name = str(entry.name);
1168
+ if (name === "") return [];
1169
+ return [{
1170
+ name,
1171
+ markdown: str(entry.markdown),
1172
+ level: num(entry.level) || 1,
1173
+ createdAt: str(entry.createdAt)
1174
+ }];
1175
+ });
1176
+ }
1177
+ /**
1178
+ * Read the Host's answer into the tab's shape, defensively.
1179
+ *
1180
+ * A malformed field falls back to its zero value rather than reaching the DOM;
1181
+ * a payload with no usable `session` is rejected outright, because rendering a
1182
+ * panel of zeros for a session the Host never answered about would be a lie.
1183
+ *
1184
+ * A status-only answer parses to `content: undefined` — never to empty lists,
1185
+ * which is what made the five-second poll erase the shelf content. The Host also
1186
+ * states which half it sent (`content`), and that flag is what this prefers; the
1187
+ * key sniffing beside it keeps a bundle built after the flag from misreading a
1188
+ * Host that predates it, so the fix does not need a profile restart to land.
1189
+ *
1190
+ * @param value - the parsed response body.
1191
+ * @param sessionId - the session that was asked for.
1192
+ * @returns the payload, or undefined when the body is not one.
1193
+ */
1194
+ function parseMemoryPayload(value, sessionId) {
1195
+ if (!isRecord(value) || !isRecord(value.session)) return void 0;
1196
+ const session = value.session;
1197
+ const carried = value.content === true || Object.hasOwn(value, "summaries") || Object.hasOwn(value, "contentError");
1198
+ return {
1199
+ session: {
1200
+ sessionId: str(session.sessionId) || sessionId,
1201
+ known: session.known === true,
1202
+ loggedSeq: num(session.loggedSeq),
1203
+ consolidatedSeq: num(session.consolidatedSeq),
1204
+ caughtUp: session.caughtUp === true,
1205
+ pendingTokens: num(session.pendingTokens),
1206
+ sessionNode: session.sessionNode === true,
1207
+ belongTo: num(session.belongTo),
1208
+ deferred: num(session.deferred),
1209
+ failed: num(session.failed),
1210
+ failedTasks: parseFailedTasks(session.failedTasks),
1211
+ error: str(session.error)
1212
+ },
1213
+ updatedAt: num(value.updatedAt),
1214
+ content: carried ? {
1215
+ summaries: parseEntries(value.summaries),
1216
+ workUnits: parseEntries(value.workUnits),
1217
+ summaryCount: num(value.summaryCount),
1218
+ workUnitCount: num(value.workUnitCount),
1219
+ truncated: value.truncated === true,
1220
+ error: str(value.contentError)
1221
+ } : void 0
1222
+ };
1223
+ }
1224
+ /**
1225
+ * Fold a new answer onto the one on screen.
1226
+ *
1227
+ * Status is always the newest. Content is only replaced by an answer that
1228
+ * carried content, so the poll cannot erase it. An answer whose shelf read
1229
+ * failed keeps the content already shown — entries, counts and the truncation
1230
+ * flag together, so the header still describes the list under it — and records
1231
+ * the error beside it, because a transient hypatia failure should not blank
1232
+ * what the reader was looking at.
1233
+ *
1234
+ * @param previous - the payload on screen, if any.
1235
+ * @param next - the answer just parsed.
1236
+ * @returns the payload to render.
1237
+ */
1238
+ function mergeMemory(previous, next) {
1239
+ if (next.content === void 0) return {
1240
+ ...next,
1241
+ content: previous?.content
1242
+ };
1243
+ if (next.content.error !== "" && previous?.content !== void 0) return {
1244
+ ...next,
1245
+ content: {
1246
+ ...previous.content,
1247
+ error: next.content.error
1248
+ }
1249
+ };
1250
+ return next;
1251
+ }
1252
+ //#endregion
1253
+ //#region \0dsh-hypatia-css:/home/runner/work/dsh-hypatia-auto-memory/dsh-hypatia-auto-memory/src/client/MemoryView.module.css.mjs
1254
+ const css = ".LBuOzG_panel{box-sizing:border-box;width:100%;height:100%;min-height:0;padding:16px;padding-bottom:calc(var(--dsh-composer-height,152px) + 16px);color:var(--dsw-alias-label-primary);background:var(--dsw-alias-bg-layer-1);flex-direction:column;align-items:center;display:flex;overflow-y:auto}.LBuOzG_column{flex-direction:column;gap:20px;width:100%;min-width:0;max-width:880px;display:flex}.LBuOzG_head{justify-content:space-between;align-items:baseline;gap:12px;display:flex}.LBuOzG_title{color:var(--dsw-alias-label-primary);margin:0;font-size:15px;font-weight:600;line-height:1.4}.LBuOzG_headMeta{align-items:center;gap:10px;display:flex}.LBuOzG_updated{color:var(--dsw-alias-label-tertiary);font-size:12px}.LBuOzG_refresh{appearance:none;border:.5px solid var(--dsw-alias-border-l4);color:var(--dsw-alias-label-secondary);cursor:pointer;font:inherit;background:0 0;border-radius:8px;padding:2px 8px;font-size:12px}.LBuOzG_refresh:hover{border-color:var(--dsw-alias-label-dimmed);color:var(--dsw-alias-label-primary)}.LBuOzG_refresh:focus-visible{outline:2px solid var(--dsw-alias-brand-primary);outline-offset:1px}.LBuOzG_status,.LBuOzG_hint,.LBuOzG_error{margin:0;font-size:12px;line-height:1.6}.LBuOzG_status,.LBuOzG_hint{color:var(--dsw-alias-label-tertiary)}.LBuOzG_error{color:var(--dsw-alias-label-error)}.LBuOzG_group{border-top:.5px solid var(--dsw-alias-border-l2);flex-direction:column;gap:10px;padding-top:16px;display:flex}.LBuOzG_groupTitle{color:var(--dsw-alias-label-primary);align-items:baseline;gap:8px;margin:0;font-size:13px;font-weight:600;display:flex}.LBuOzG_count{color:var(--dsw-alias-label-tertiary);font-variant-numeric:tabular-nums;font-size:11px;font-weight:400}.LBuOzG_rows{grid-template-columns:max-content minmax(0,1fr);align-items:baseline;gap:7px 20px;margin:0;display:grid}.LBuOzG_row{display:contents}.LBuOzG_label{color:var(--dsw-alias-label-tertiary);white-space:nowrap;font-size:12px}.LBuOzG_value{min-width:0;color:var(--dsw-alias-label-primary);font-variant-numeric:tabular-nums;align-items:baseline;gap:8px;margin:0;font-size:12px;display:flex}.LBuOzG_calm,.LBuOzG_busy{white-space:nowrap;border-radius:6px;padding:1px 6px;font-size:11px}.LBuOzG_calm{color:var(--dsw-alias-label-tertiary);background:var(--dsw-alias-bg-layer-2)}.LBuOzG_busy{color:var(--dsw-alias-label-primary);border:.5px solid var(--dsw-alias-border-l4);background:var(--dsw-alias-bg-layer-3)}.LBuOzG_failures{gap:6px;margin:0;padding:0;list-style:none;display:grid}.LBuOzG_failure{background:var(--dsw-alias-bg-layer-2);border-radius:8px;gap:2px;padding:8px 10px;display:grid}.LBuOzG_failureKind{color:var(--dsw-alias-label-primary);font-size:12px}.LBuOzG_failureError{color:var(--dsw-alias-label-tertiary);overflow-wrap:anywhere;font-size:11px;line-height:1.5}.LBuOzG_entries{flex-direction:column;gap:8px;margin:0;padding:0;list-style:none;display:flex}.LBuOzG_entry{border:.5px solid var(--dsw-alias-border-l2);background:var(--dsw-alias-bg-layer-2);border-radius:10px;overflow:hidden}.LBuOzG_entryHead{appearance:none;width:100%;color:inherit;cursor:pointer;font:inherit;text-align:left;background:0 0;border:0;justify-content:space-between;align-items:baseline;gap:12px;padding:8px 12px;display:flex}.LBuOzG_entryHead:hover{background:var(--dsw-alias-bg-layer-3)}.LBuOzG_entryHead:focus-visible{outline:2px solid var(--dsw-alias-brand-primary);outline-offset:-2px}.LBuOzG_entryName{min-width:0;color:var(--dsw-alias-label-secondary);font-family:var(--dsw-font-family-mono,ui-monospace, SFMono-Regular, Menlo, monospace);text-overflow:ellipsis;white-space:nowrap;font-size:11px;overflow:hidden}.LBuOzG_entryMeta{flex:none;align-items:baseline;gap:8px;display:flex}.LBuOzG_badge{background:var(--dsw-alias-bg-layer-1);color:var(--dsw-alias-label-tertiary);white-space:nowrap;border-radius:6px;padding:1px 6px;font-size:11px}.LBuOzG_entryTime{color:var(--dsw-alias-label-quaternary);font-variant-numeric:tabular-nums;white-space:nowrap;font-size:11px}.LBuOzG_entryToggle{color:var(--dsw-alias-label-tertiary);white-space:nowrap;font-size:11px}.LBuOzG_entryBody{border-top:.5px solid var(--dsw-alias-border-l2);overflow-wrap:anywhere;padding:10px 12px;font-size:13px;line-height:1.65}.LBuOzG_entryPreview{color:var(--dsw-alias-label-tertiary);text-overflow:ellipsis;white-space:nowrap;margin:0;padding:0 12px 10px;font-size:12px;line-height:1.5;overflow:hidden}.LBuOzG_hint{border-top:.5px solid var(--dsw-alias-border-l2);padding-top:12px}";
1255
+ const tagId = "dsh-hypatia-auto-memory/MemoryView.module.css";
1256
+ if (typeof document !== "undefined" && document.querySelector("style[data-plugin-css=" + JSON.stringify(tagId) + "]") === null) {
1257
+ const tag = document.createElement("style");
1258
+ tag.dataset.plugin = "dsh-hypatia-auto-memory";
1259
+ tag.dataset.pluginCss = tagId;
1260
+ tag.textContent = css;
1261
+ document.head.appendChild(tag);
1262
+ }
1263
+ var MemoryView_module_css_default = {
1264
+ "badge": "LBuOzG_badge",
1265
+ "busy": "LBuOzG_busy",
1266
+ "calm": "LBuOzG_calm",
1267
+ "column": "LBuOzG_column",
1268
+ "count": "LBuOzG_count",
1269
+ "entries": "LBuOzG_entries",
1270
+ "entry": "LBuOzG_entry",
1271
+ "entryBody": "LBuOzG_entryBody",
1272
+ "entryHead": "LBuOzG_entryHead",
1273
+ "entryMeta": "LBuOzG_entryMeta",
1274
+ "entryName": "LBuOzG_entryName",
1275
+ "entryPreview": "LBuOzG_entryPreview",
1276
+ "entryTime": "LBuOzG_entryTime",
1277
+ "entryToggle": "LBuOzG_entryToggle",
1278
+ "error": "LBuOzG_error",
1279
+ "failure": "LBuOzG_failure",
1280
+ "failureError": "LBuOzG_failureError",
1281
+ "failureKind": "LBuOzG_failureKind",
1282
+ "failures": "LBuOzG_failures",
1283
+ "group": "LBuOzG_group",
1284
+ "groupTitle": "LBuOzG_groupTitle",
1285
+ "head": "LBuOzG_head",
1286
+ "headMeta": "LBuOzG_headMeta",
1287
+ "hint": "LBuOzG_hint",
1288
+ "label": "LBuOzG_label",
1289
+ "panel": "LBuOzG_panel",
1290
+ "refresh": "LBuOzG_refresh",
1291
+ "row": "LBuOzG_row",
1292
+ "rows": "LBuOzG_rows",
1293
+ "status": "LBuOzG_status",
1294
+ "title": "LBuOzG_title",
1295
+ "updated": "LBuOzG_updated",
1296
+ "value": "LBuOzG_value"
1297
+ };
1298
+ //#endregion
1299
+ //#region src/client/MemoryView.tsx
1300
+ /**
1301
+ * The conversation's Memory tab: what this session has been remembered as, what
1302
+ * it was remembered into, and what is stuck.
1303
+ *
1304
+ * Three sections, in the order a reader wants them: the pipeline's status
1305
+ * (watermarks, backlog, failures), then the span summaries consolidation
1306
+ * produced, then the work units derived from them. Bodies render through the
1307
+ * shell's own `MarkdownText`, so a summary reads exactly like assistant Markdown
1308
+ * elsewhere in the GUI and costs no bundle weight.
1309
+ *
1310
+ * Raw `msg-*` entries are deliberately absent: they hold the conversation
1311
+ * verbatim, the reader just wrote them, and the agent is the right reader for
1312
+ * them (see `src/memory-api.js` for the same rule on the Host side).
1313
+ *
1314
+ * Status is polled; content is fetched only when it can have changed — on mount,
1315
+ * on a session switch, on a manual refresh, and whenever consolidation advances
1316
+ * the session's watermark. Reading the shelf costs hypatia round trips, and
1317
+ * consolidation is the only thing that changes it.
1318
+ */
1319
+ /** How often the tab re-reads the status half while it is on screen. */
1320
+ const POLL_MS = 5e3;
1321
+ /** Local wall-clock stamp for the freshness line. */
1322
+ function formatTime(epochMs) {
1323
+ if (!Number.isFinite(epochMs) || epochMs <= 0) return "—";
1324
+ return new Date(epochMs).toLocaleTimeString();
1325
+ }
1326
+ /**
1327
+ * hypatia stamps `created_at` as `YYYY-MM-DD HH:MM:SS.ffffff`; the microseconds
1328
+ * are noise in a row that is 11px tall, so the tab shows down to the minute.
1329
+ * Slice rather than `new Date`: the space-separated form is not the ISO shape
1330
+ * every engine is required to parse.
1331
+ */
1332
+ function formatEntryTime(stamp) {
1333
+ return stamp.length >= 16 ? stamp.slice(0, 16).replace("T", " ") : stamp;
1334
+ }
1335
+ /** The first non-empty line of a body, for a collapsed row's preview. */
1336
+ function firstLine(markdown) {
1337
+ for (const line of markdown.split("\n")) {
1338
+ const trimmed = line.replace(/^[#>\-*\s]+/, "").trim();
1339
+ if (trimmed !== "") return trimmed;
1340
+ }
1341
+ return "";
1342
+ }
1343
+ /** One collapsible distilled entry. */
1344
+ function Entry({ entry, badge, open, onToggle, labels, t }) {
1345
+ return /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("li", {
1346
+ className: MemoryView_module_css_default.entry,
1347
+ children: [/* @__PURE__ */ (0, react_jsx_runtime.jsxs)("button", {
1348
+ type: "button",
1349
+ className: MemoryView_module_css_default.entryHead,
1350
+ "aria-expanded": open,
1351
+ onClick: onToggle,
1352
+ children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
1353
+ className: MemoryView_module_css_default.entryName,
1354
+ children: entry.name
1355
+ }), /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("span", {
1356
+ className: MemoryView_module_css_default.entryMeta,
1357
+ children: [
1358
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
1359
+ className: MemoryView_module_css_default.badge,
1360
+ children: badge
1361
+ }),
1362
+ entry.createdAt !== "" ? /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
1363
+ className: MemoryView_module_css_default.entryTime,
1364
+ children: formatEntryTime(entry.createdAt)
1365
+ }) : null,
1366
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
1367
+ className: MemoryView_module_css_default.entryToggle,
1368
+ children: open ? t("memoryEntryCollapse") : t("memoryEntryExpand")
1369
+ })
1370
+ ]
1371
+ })]
1372
+ }), open ? /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
1373
+ className: MemoryView_module_css_default.entryBody,
1374
+ children: entry.markdown === "" ? /* @__PURE__ */ (0, react_jsx_runtime.jsx)("p", {
1375
+ className: MemoryView_module_css_default.status,
1376
+ children: t("memoryEntryEmpty")
1377
+ }) : /* @__PURE__ */ (0, react_jsx_runtime.jsx)(_deepseek_ai_dsh_client_ui_primitives.MarkdownText, {
1378
+ text: entry.markdown,
1379
+ labels
1380
+ })
1381
+ }) : /* @__PURE__ */ (0, react_jsx_runtime.jsx)("p", {
1382
+ className: MemoryView_module_css_default.entryPreview,
1383
+ children: firstLine(entry.markdown)
1384
+ })]
1385
+ });
1386
+ }
1387
+ function MemoryView({ sessionId, fetch, t }) {
1388
+ const [payload, setPayload] = (0, react.useState)(void 0);
1389
+ const [loading, setLoading] = (0, react.useState)(true);
1390
+ const [error, setError] = (0, react.useState)("");
1391
+ /**
1392
+ * The entries the reader opened, by name. Absent means closed, which is the
1393
+ * default: a toggle survives the poll that replaces the payload, and an entry
1394
+ * a later poll adds starts closed rather than inheriting a neighbour's state.
1395
+ */
1396
+ const [overrides, setOverrides] = (0, react.useState)({});
1397
+ /** The watermark whose content is already in hand, so a poll never re-reads it. */
1398
+ const [contentAt, setContentAt] = (0, react.useState)(void 0);
1399
+ const copyLabel = t("memoryCopyCode");
1400
+ const copiedLabel = t("memoryCopiedCode");
1401
+ const footnotes = t("memoryFootnotes");
1402
+ const labels = (0, react.useMemo)(() => ({
1403
+ code: {
1404
+ copyLabel,
1405
+ copiedLabel
1406
+ },
1407
+ footnotes
1408
+ }), [
1409
+ copyLabel,
1410
+ copiedLabel,
1411
+ footnotes
1412
+ ]);
1413
+ /**
1414
+ * The session the tab is showing now. A request outlives the session it was
1415
+ * made for — a slow shelf read, or a poll already in flight at a switch — and
1416
+ * its answer must be dropped rather than folded into the next session's view.
1417
+ */
1418
+ const activeSession = (0, react.useRef)(sessionId);
1419
+ const load = (0, react.useCallback)(async (content) => {
1420
+ const current = () => activeSession.current === sessionId;
1421
+ try {
1422
+ const next = await fetch(sessionId, { content });
1423
+ if (!current()) return;
1424
+ setPayload((previous) => mergeMemory(previous, next));
1425
+ setError("");
1426
+ if (content) setContentAt(next.session.consolidatedSeq);
1427
+ } catch (failure) {
1428
+ if (!current()) return;
1429
+ setError(failure instanceof Error ? failure.message : String(failure));
1430
+ } finally {
1431
+ if (current()) setLoading(false);
1432
+ }
1433
+ }, [fetch, sessionId]);
1434
+ (0, react.useEffect)(() => {
1435
+ activeSession.current = sessionId;
1436
+ setPayload(void 0);
1437
+ setOverrides({});
1438
+ setContentAt(void 0);
1439
+ setLoading(true);
1440
+ load(true);
1441
+ }, [load, sessionId]);
1442
+ (0, react.useEffect)(() => {
1443
+ const timer = setInterval(() => {
1444
+ if (typeof document !== "undefined" && document.visibilityState === "hidden") return;
1445
+ load(false);
1446
+ }, POLL_MS);
1447
+ return () => clearInterval(timer);
1448
+ }, [load]);
1449
+ (0, react.useEffect)(() => {
1450
+ const at = payload?.session.consolidatedSeq;
1451
+ if (at === void 0 || contentAt === void 0) return;
1452
+ if (at !== contentAt) load(true);
1453
+ }, [
1454
+ payload?.session.consolidatedSeq,
1455
+ contentAt,
1456
+ load
1457
+ ]);
1458
+ const toggle = (0, react.useCallback)((name, open) => {
1459
+ setOverrides((current) => ({
1460
+ ...current,
1461
+ [name]: !open
1462
+ }));
1463
+ }, []);
1464
+ const session = payload?.session;
1465
+ const content = payload?.content;
1466
+ const summaries = content?.summaries ?? [];
1467
+ const workUnits = content?.workUnits ?? [];
1468
+ const renderEntries = (entries, kind) => /* @__PURE__ */ (0, react_jsx_runtime.jsx)("ul", {
1469
+ className: MemoryView_module_css_default.entries,
1470
+ children: entries.map((entry) => {
1471
+ const open = overrides[entry.name] === true;
1472
+ return /* @__PURE__ */ (0, react_jsx_runtime.jsx)(Entry, {
1473
+ entry,
1474
+ badge: kind === "summary" ? t("memoryTier", { level: String(entry.level) }) : t("memoryWorkUnit"),
1475
+ open,
1476
+ onToggle: () => toggle(entry.name, open),
1477
+ labels,
1478
+ t
1479
+ }, entry.name);
1480
+ })
1481
+ });
1482
+ return /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
1483
+ className: MemoryView_module_css_default.panel,
1484
+ "data-conversation-composer-overlay": "",
1485
+ children: /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
1486
+ className: MemoryView_module_css_default.column,
1487
+ children: [
1488
+ /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("header", {
1489
+ className: MemoryView_module_css_default.head,
1490
+ children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("h2", {
1491
+ className: MemoryView_module_css_default.title,
1492
+ children: t("memoryTitle")
1493
+ }), /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
1494
+ className: MemoryView_module_css_default.headMeta,
1495
+ children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
1496
+ className: MemoryView_module_css_default.updated,
1497
+ children: t("memoryUpdatedAt", { time: formatTime(payload?.updatedAt ?? 0) })
1498
+ }), /* @__PURE__ */ (0, react_jsx_runtime.jsx)("button", {
1499
+ type: "button",
1500
+ className: MemoryView_module_css_default.refresh,
1501
+ onClick: () => {
1502
+ load(true);
1503
+ },
1504
+ children: t("memoryRefresh")
1505
+ })]
1506
+ })]
1507
+ }),
1508
+ error !== "" ? /* @__PURE__ */ (0, react_jsx_runtime.jsx)("p", {
1509
+ className: MemoryView_module_css_default.error,
1510
+ role: "status",
1511
+ children: t("memoryLoadFailed", { message: error })
1512
+ }) : null,
1513
+ loading && payload === void 0 ? /* @__PURE__ */ (0, react_jsx_runtime.jsx)("p", {
1514
+ className: MemoryView_module_css_default.status,
1515
+ children: t("memoryLoading")
1516
+ }) : session === void 0 ? /* @__PURE__ */ (0, react_jsx_runtime.jsx)("p", {
1517
+ className: MemoryView_module_css_default.status,
1518
+ children: t("memoryNoSnapshot")
1519
+ }) : !session.known ? /* @__PURE__ */ (0, react_jsx_runtime.jsx)("p", {
1520
+ className: MemoryView_module_css_default.status,
1521
+ children: t("memoryUnknown")
1522
+ }) : /* @__PURE__ */ (0, react_jsx_runtime.jsx)("section", {
1523
+ className: MemoryView_module_css_default.group,
1524
+ "aria-label": t("memoryTitle"),
1525
+ children: /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("dl", {
1526
+ className: MemoryView_module_css_default.rows,
1527
+ children: [
1528
+ /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
1529
+ className: MemoryView_module_css_default.row,
1530
+ children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("dt", {
1531
+ className: MemoryView_module_css_default.label,
1532
+ children: t("memoryLogged")
1533
+ }), /* @__PURE__ */ (0, react_jsx_runtime.jsx)("dd", {
1534
+ className: MemoryView_module_css_default.value,
1535
+ children: t("memorySeqValue", { seq: String(session.loggedSeq) })
1536
+ })]
1537
+ }),
1538
+ /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
1539
+ className: MemoryView_module_css_default.row,
1540
+ children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("dt", {
1541
+ className: MemoryView_module_css_default.label,
1542
+ children: t("memoryConsolidated")
1543
+ }), /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("dd", {
1544
+ className: MemoryView_module_css_default.value,
1545
+ children: [t("memorySeqValue", { seq: String(session.consolidatedSeq) }), /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
1546
+ className: session.caughtUp ? MemoryView_module_css_default.calm : MemoryView_module_css_default.busy,
1547
+ children: session.caughtUp ? t("memoryCaughtUp") : t("memoryBehind", { behind: String(consolidationGap(session)) })
1548
+ })]
1549
+ })]
1550
+ }),
1551
+ /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
1552
+ className: MemoryView_module_css_default.row,
1553
+ children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("dt", {
1554
+ className: MemoryView_module_css_default.label,
1555
+ children: t("memoryPending")
1556
+ }), /* @__PURE__ */ (0, react_jsx_runtime.jsx)("dd", {
1557
+ className: MemoryView_module_css_default.value,
1558
+ children: t("memoryPendingValue", { tokens: String(session.pendingTokens) })
1559
+ })]
1560
+ }),
1561
+ /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
1562
+ className: MemoryView_module_css_default.row,
1563
+ children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("dt", {
1564
+ className: MemoryView_module_css_default.label,
1565
+ children: t("memorySessionNode")
1566
+ }), /* @__PURE__ */ (0, react_jsx_runtime.jsx)("dd", {
1567
+ className: MemoryView_module_css_default.value,
1568
+ children: session.sessionNode ? t("memorySessionNodeYes") : t("memorySessionNodeNo")
1569
+ })]
1570
+ }),
1571
+ /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
1572
+ className: MemoryView_module_css_default.row,
1573
+ children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("dt", {
1574
+ className: MemoryView_module_css_default.label,
1575
+ children: t("memoryBelongTo")
1576
+ }), /* @__PURE__ */ (0, react_jsx_runtime.jsx)("dd", {
1577
+ className: MemoryView_module_css_default.value,
1578
+ children: t("memoryBelongToValue", { count: String(session.belongTo) })
1579
+ })]
1580
+ }),
1581
+ session.deferred > 0 ? /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
1582
+ className: MemoryView_module_css_default.row,
1583
+ children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("dt", {
1584
+ className: MemoryView_module_css_default.label,
1585
+ children: t("memoryDeferred")
1586
+ }), /* @__PURE__ */ (0, react_jsx_runtime.jsx)("dd", {
1587
+ className: MemoryView_module_css_default.value,
1588
+ children: String(session.deferred)
1589
+ })]
1590
+ }) : null
1591
+ ]
1592
+ })
1593
+ }),
1594
+ session !== void 0 && (session.failed > 0 || session.failedTasks.length > 0) ? /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("section", {
1595
+ className: MemoryView_module_css_default.group,
1596
+ "aria-label": t("memoryFailedTitle"),
1597
+ children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("h3", {
1598
+ className: MemoryView_module_css_default.groupTitle,
1599
+ children: t("memoryFailedTitle")
1600
+ }), /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("ul", {
1601
+ className: MemoryView_module_css_default.failures,
1602
+ children: [session.failedTasks.map((task, index) => /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("li", {
1603
+ className: MemoryView_module_css_default.failure,
1604
+ children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
1605
+ className: MemoryView_module_css_default.failureKind,
1606
+ children: t("memoryFailedItem", {
1607
+ kind: task.kind,
1608
+ attempts: String(task.attempts)
1609
+ })
1610
+ }), task.error !== "" ? /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
1611
+ className: MemoryView_module_css_default.failureError,
1612
+ children: task.error
1613
+ }) : null]
1614
+ }, `${task.kind}-${String(index)}`)), session.failedTasks.length === 0 ? /* @__PURE__ */ (0, react_jsx_runtime.jsx)("li", {
1615
+ className: MemoryView_module_css_default.failure,
1616
+ children: session.error
1617
+ }) : null]
1618
+ })]
1619
+ }) : null,
1620
+ content !== void 0 && content.error !== "" ? /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("section", {
1621
+ className: MemoryView_module_css_default.group,
1622
+ "aria-label": t("memoryContentTitle"),
1623
+ children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("h3", {
1624
+ className: MemoryView_module_css_default.groupTitle,
1625
+ children: t("memoryContentTitle")
1626
+ }), /* @__PURE__ */ (0, react_jsx_runtime.jsx)("p", {
1627
+ className: MemoryView_module_css_default.error,
1628
+ role: "status",
1629
+ children: t("memoryContentFailed", { message: content.error })
1630
+ })]
1631
+ }) : null,
1632
+ content !== void 0 ? /* @__PURE__ */ (0, react_jsx_runtime.jsxs)(react_jsx_runtime.Fragment, { children: [
1633
+ /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("section", {
1634
+ className: MemoryView_module_css_default.group,
1635
+ "aria-label": t("memorySummariesTitle"),
1636
+ children: [/* @__PURE__ */ (0, react_jsx_runtime.jsxs)("h3", {
1637
+ className: MemoryView_module_css_default.groupTitle,
1638
+ children: [t("memorySummariesTitle"), /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
1639
+ className: MemoryView_module_css_default.count,
1640
+ children: t("memoryCount", {
1641
+ shown: String(summaries.length),
1642
+ total: String(content.summaryCount)
1643
+ })
1644
+ })]
1645
+ }), summaries.length === 0 ? /* @__PURE__ */ (0, react_jsx_runtime.jsx)("p", {
1646
+ className: MemoryView_module_css_default.status,
1647
+ children: t("memoryNoSummaries")
1648
+ }) : renderEntries(summaries, "summary")]
1649
+ }),
1650
+ /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("section", {
1651
+ className: MemoryView_module_css_default.group,
1652
+ "aria-label": t("memoryWorkUnitsTitle"),
1653
+ children: [/* @__PURE__ */ (0, react_jsx_runtime.jsxs)("h3", {
1654
+ className: MemoryView_module_css_default.groupTitle,
1655
+ children: [t("memoryWorkUnitsTitle"), /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
1656
+ className: MemoryView_module_css_default.count,
1657
+ children: t("memoryCount", {
1658
+ shown: String(workUnits.length),
1659
+ total: String(content.workUnitCount)
1660
+ })
1661
+ })]
1662
+ }), workUnits.length === 0 ? /* @__PURE__ */ (0, react_jsx_runtime.jsx)("p", {
1663
+ className: MemoryView_module_css_default.status,
1664
+ children: t("memoryNoWorkUnits")
1665
+ }) : renderEntries(workUnits, "workUnit")]
1666
+ }),
1667
+ content.truncated ? /* @__PURE__ */ (0, react_jsx_runtime.jsx)("p", {
1668
+ className: MemoryView_module_css_default.hint,
1669
+ children: t("memoryTruncated")
1670
+ }) : null,
1671
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)("p", {
1672
+ className: MemoryView_module_css_default.hint,
1673
+ children: t("memoryHint")
1674
+ })
1675
+ ] }) : null
1676
+ ]
1677
+ })
1007
1678
  });
1008
1679
  }
1009
1680
  //#endregion
@@ -1019,27 +1690,29 @@ window.__ModuleLoader__.load({
1019
1690
  delete globalThis[APPLY_CLAIM];
1020
1691
  }
1021
1692
  /**
1022
- * Required client services: settings, locale, slots, the Host model catalog,
1023
- * and the settings descriptor. Each Remote namespace is its own service, so
1024
- * `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.
1025
1697
  */
1026
1698
  const inject = [
1027
1699
  "slots",
1028
- "settingsScope",
1700
+ "configForms",
1029
1701
  "locale",
1030
1702
  "remote",
1031
- "remote.session",
1032
- "remote.settings"
1703
+ "remote.session"
1033
1704
  ];
1034
1705
  /** Mount the browser half. */
1035
1706
  function apply(ctx) {
1036
1707
  if (!claimApply()) return;
1037
- const scope = ctx.get("settingsScope").bind({ namespace: NS });
1708
+ const configForms = ctx.get("configForms");
1709
+ const form = configForms.get(NS);
1038
1710
  const locale = ctx.get("locale");
1039
1711
  ctx.effect(() => locale.register(NS, {
1040
1712
  zh,
1041
1713
  en
1042
1714
  }), "dsh-hypatia-auto-memory: locale dictionaries");
1715
+ const t = locale.bind(NS);
1043
1716
  const loadModelCatalog = async () => {
1044
1717
  const response = await ctx.remote.session.modelCatalog();
1045
1718
  if (!response.ok) throw new Error(`${response.error.code}: ${response.error.message}`);
@@ -1048,26 +1721,30 @@ window.__ModuleLoader__.load({
1048
1721
  partial: response.value.failures.length > 0
1049
1722
  };
1050
1723
  };
1051
- const loadShelfInventory = async () => {
1052
- const response = await ctx.remote.settings.describe();
1053
- if (!response.ok) throw new Error(`${response.error.code}: ${response.error.message}`);
1054
- const inventory = readShelfInventory(response.value.namespaces);
1055
- if (inventory === void 0) throw new Error("the Host publishes no shelf listing");
1056
- return inventory;
1057
- };
1724
+ const loadShelfInventory = fetchShelves;
1058
1725
  const renderCard = (props) => (0, react.createElement)(SettingsCard, {
1059
- scope,
1726
+ scope: form,
1060
1727
  loadModelCatalog,
1061
1728
  loadShelfInventory,
1062
1729
  ...props
1063
1730
  });
1064
- const unregister = ctx.slots.inject("settings.plugin.item", () => ctx.slots.register({
1065
- name: "settings.plugin.item",
1066
- 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"),
1067
1735
  locale: NS
1068
- }, renderCard));
1736
+ }, renderCard))), "dsh-hypatia-auto-memory: plugins settings tab");
1737
+ const unregisterMemoryView = ctx.slots.inject("conversation.view", () => ctx.slots.register({
1738
+ name: "conversation.view",
1739
+ id: "memory",
1740
+ order: 20,
1741
+ locale: NS,
1742
+ label: () => t("viewMemory"),
1743
+ inject: () => ({ fetch: fetchMemory })
1744
+ }, MemoryView));
1069
1745
  ctx.effect(() => () => {
1070
1746
  unregister();
1747
+ unregisterMemoryView();
1071
1748
  releaseApply();
1072
1749
  });
1073
1750
  }