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/README.md +93 -23
- package/README.zh-CN.md +73 -23
- package/cordis.patch.yml +4 -3
- package/lib/client.js +785 -108
- package/lib/client.js.map +1 -1
- package/package.json +15 -14
- package/src/client/MemoryView.module.css +312 -0
- package/src/client/MemoryView.tsx +335 -0
- package/src/client/SettingsCard.module.css +5 -37
- package/src/client/SettingsCard.tsx +406 -412
- package/src/client/index.tsx +59 -38
- package/src/client/locales.ts +86 -6
- package/src/client/memory-client.ts +294 -0
- package/src/client/shelves.ts +6 -38
- package/src/client/slot-contract.ts +5 -5
- package/src/config.js +145 -76
- package/src/index.js +155 -42
- package/src/memory-api.js +608 -0
- package/src/memory-status.js +167 -0
- package/src/shelf.js +5 -99
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-
|
|
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
|
|
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: "
|
|
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.
|
|
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.
|
|
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
|
|
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(
|
|
279
|
-
return
|
|
330
|
+
function getSnapshotValue(form) {
|
|
331
|
+
return form.getSnapshot();
|
|
280
332
|
}
|
|
281
|
-
function subscribe(
|
|
282
|
-
return
|
|
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(
|
|
288
|
-
const [snap, setSnap] = (0, react.useState)(() => getSnapshotValue(
|
|
339
|
+
function useScopeValue(form) {
|
|
340
|
+
const [snap, setSnap] = (0, react.useState)(() => getSnapshotValue(form));
|
|
289
341
|
(0, react.useEffect)(() => {
|
|
290
|
-
setSnap(getSnapshotValue(
|
|
291
|
-
return subscribe(
|
|
292
|
-
}, [
|
|
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
|
-
}, [
|
|
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
|
-
}, [
|
|
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
|
|
429
|
-
|
|
430
|
-
|
|
431
|
-
|
|
432
|
-
|
|
433
|
-
|
|
434
|
-
|
|
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)("
|
|
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)("
|
|
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)("
|
|
493
|
-
className:
|
|
494
|
-
|
|
495
|
-
|
|
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
|
-
|
|
498
|
-
|
|
499
|
-
|
|
500
|
-
|
|
501
|
-
|
|
502
|
-
|
|
503
|
-
|
|
504
|
-
|
|
505
|
-
|
|
506
|
-
|
|
507
|
-
|
|
508
|
-
|
|
509
|
-
|
|
510
|
-
|
|
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
|
-
})
|
|
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:
|
|
1023
|
-
*
|
|
1024
|
-
* `
|
|
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
|
-
"
|
|
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
|
|
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 =
|
|
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.
|
|
1065
|
-
name: "settings.
|
|
1066
|
-
|
|
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
|
}
|