dsh-hypatia-auto-memory 0.4.0 → 0.5.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,7 +47,7 @@ window.__ModuleLoader__.load({
47
47
  }
48
48
  return candidates;
49
49
  }
50
- function isRecord(value) {
50
+ function isRecord$1(value) {
51
51
  return typeof value === "object" && value !== null && !Array.isArray(value);
52
52
  }
53
53
  /**
@@ -60,11 +60,11 @@ window.__ModuleLoader__.load({
60
60
  * @returns the listing, or undefined when the Host does not serve one.
61
61
  */
62
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;
63
+ const view = described.find((entry) => isRecord$1(entry) && entry.ns === "hypatia-auto-memory-shelves");
64
+ if (!isRecord$1(view) || !isRecord$1(view.base)) return void 0;
65
65
  const { shelves, error } = view.base;
66
66
  return {
67
- shelves: Array.isArray(shelves) ? shelves.flatMap((shelf) => isRecord(shelf) && typeof shelf.name === "string" ? [{
67
+ shelves: Array.isArray(shelves) ? shelves.flatMap((shelf) => isRecord$1(shelf) && typeof shelf.name === "string" ? [{
68
68
  name: shelf.name,
69
69
  path: typeof shelf.path === "string" ? shelf.path : "",
70
70
  connected: shelf.connected === true
@@ -98,13 +98,13 @@ window.__ModuleLoader__.load({
98
98
  }
99
99
  //#endregion
100
100
  //#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) {
101
+ const css$1 = ".YVNYya_card{border:.5px solid var(--dsw-alias-border-l4);background:var(--dsw-alias-bg-layer-3);border-radius:16px;list-style:none;transition:border-color .16s,background .16s}.YVNYya_card:hover{border-color:var(--dsw-alias-label-dimmed)}.YVNYya_cardOpen{border-color:var(--dsw-alias-label-dimmed);background:var(--dsw-alias-bg-layer-2)}.YVNYya_header{appearance:none;width:100%;color:inherit;cursor:pointer;font:inherit;text-align:left;background:0 0;border:0;border-radius:12px;align-items:center;gap:12px;padding:14px 16px;display:flex}.YVNYya_header:focus-visible{outline:2px solid var(--dsw-alias-brand-primary);outline-offset:-2px}.YVNYya_headText{flex-direction:column;flex:1;gap:4px;min-width:0;display:flex}.YVNYya_name{color:var(--dsw-alias-label-primary);font-size:15px;font-weight:600;line-height:1.4}.YVNYya_description{color:var(--dsw-alias-label-tertiary);font-size:13px;line-height:1.5}.YVNYya_pending{corner-shape:round;background:var(--dsw-alias-bg-module-platform);color:var(--dsw-alias-label-secondary);white-space:nowrap;border-radius:999px;flex:none;padding:1px 8px;font-size:11px;font-weight:500;line-height:17px}.YVNYya_chevron{color:var(--dsw-alias-label-tertiary);flex:none;transition:transform .16s}.YVNYya_chevronOpen{transform:rotate(180deg)}.YVNYya_body{border-top:.5px solid var(--dsw-alias-border-l2);margin:0 16px;padding-bottom:8px}.YVNYya_status,.YVNYya_readOnly,.YVNYya_hint,.YVNYya_advancedHint,.YVNYya_error{margin:0;font-size:12px;line-height:1.5}.YVNYya_status,.YVNYya_readOnly,.YVNYya_hint,.YVNYya_advancedHint{color:var(--dsw-alias-label-tertiary)}.YVNYya_status{padding:12px 16px;list-style:none}.YVNYya_readOnly{padding-top:12px}.YVNYya_field{gap:6px;padding:12px 0;display:grid}.YVNYya_body>.YVNYya_field+.YVNYya_group,.YVNYya_body>.YVNYya_group+.YVNYya_field{border-top:.5px solid var(--dsw-alias-border-l2)}.YVNYya_group{padding:12px 0}.YVNYya_groupTitle{color:var(--dsw-alias-label-primary);margin:0;font-size:13px;font-weight:500;line-height:1.5}.YVNYya_limitsTitle{border-top:.5px solid var(--dsw-alias-border-l2);color:var(--dsw-alias-label-primary);margin:16px 0 0;padding-top:12px;font-size:13px;font-weight:500;line-height:1.5}.YVNYya_limitsTitle+.YVNYya_hint{margin-top:4px}.YVNYya_group>.YVNYya_field:first-of-type{padding-top:12px}.YVNYya_group>.YVNYya_field+.YVNYya_field,.YVNYya_pairedFields{border-top:.5px solid var(--dsw-alias-border-l2)}.YVNYya_fieldHead{align-items:center;gap:8px;display:flex}.YVNYya_label{min-width:0;color:var(--dsw-alias-label-primary);flex:1;font-size:13px;font-weight:500;line-height:1.5}.YVNYya_reset{color:var(--dsw-alias-label-secondary);cursor:pointer;font:inherit;background:0 0;border:0;padding:0;font-size:12px;line-height:1.5}.YVNYya_reset:hover:not(:disabled){color:var(--dsw-alias-label-primary)}.YVNYya_reset:disabled{cursor:default}.YVNYya_input{box-sizing:border-box;width:100%;min-width:0;display:flex}.YVNYya_select{box-sizing:border-box;border:.5px solid var(--dsw-alias-border-l3);width:100%;min-width:0;height:32px;color:var(--dsw-alias-label-primary);font:inherit;background:0 0;border-radius:8px;padding:0 8px;font-size:13px}.YVNYya_select:disabled{opacity:.5}.YVNYya_select:focus-visible{outline:2px solid var(--dsw-alias-brand-primary);outline-offset:2px}.YVNYya_warning{color:var(--dsw-alias-label-error);margin:0;font-size:12px;line-height:1.5}.YVNYya_pairedFields{grid-template-columns:repeat(auto-fit,minmax(220px,1fr));gap:16px;margin:4px 0;padding:4px 0;display:grid}.YVNYya_pairedFields .YVNYya_field{min-width:0}.YVNYya_switch{box-sizing:border-box;background:var(--dsw-alias-border-l3);cursor:pointer;border:0;border-radius:10px;width:36px;height:20px;padding:2px;position:relative}.YVNYya_switchOn{background:var(--dsw-alias-brand-primary)}.YVNYya_switch:disabled{cursor:default;opacity:.5}.YVNYya_switch:focus-visible{outline:2px solid var(--dsw-alias-brand-primary);outline-offset:2px}.YVNYya_switchThumb{corner-shape:round;background:var(--dsw-alias-label-primary-foreground);border-radius:50%;width:16px;height:16px;transition:transform .12s;display:block}.YVNYya_switchOn .YVNYya_switchThumb{transform:translate(16px)}.YVNYya_modelSelection{gap:10px;padding:12px 0;display:grid}.YVNYya_notice{color:var(--dsw-alias-label-tertiary);margin:0;font-size:12px;line-height:1.5}.YVNYya_catalogError{color:var(--dsw-alias-label-error);justify-content:space-between;align-items:center;gap:12px;font-size:12px;line-height:1.5;display:flex}.YVNYya_catalogError button{color:var(--dsw-alias-brand-primary);cursor:pointer;font:inherit;background:0 0;border:0;padding:0}.YVNYya_models{border:.5px solid var(--dsw-alias-border-l4);border-radius:8px;gap:6px;min-width:0;max-height:280px;margin:0;padding:10px;display:grid;overflow:auto}.YVNYya_models legend{color:var(--dsw-alias-label-secondary);padding:0 4px;font-size:12px}.YVNYya_modelGroup{gap:6px;display:grid}.YVNYya_modelGroup+.YVNYya_modelGroup{border-top:.5px solid var(--dsw-alias-border-l3);margin-top:4px;padding-top:10px}.YVNYya_providerName{color:var(--dsw-alias-label-tertiary);padding:0 6px;font-size:11px;font-weight:500}.YVNYya_model{cursor:pointer;border-radius:6px;grid-template-columns:auto minmax(0,1fr) auto;align-items:center;gap:8px;min-width:0;padding:6px;display:grid}.YVNYya_model:hover{background:var(--dsw-alias-bg-layer-4)}.YVNYya_modelName,.YVNYya_route{text-overflow:ellipsis;white-space:nowrap;display:block;overflow:hidden}.YVNYya_modelName{color:var(--dsw-alias-label-primary);font-size:13px}.YVNYya_route{color:var(--dsw-alias-label-tertiary);margin-top:2px;font-size:11px}.YVNYya_unavailable{color:var(--dsw-alias-label-tertiary);font-size:11px}.YVNYya_error{color:var(--dsw-alias-label-error)}.YVNYya_advancedHint{border-top:.5px solid var(--dsw-alias-border-l2);padding-top:12px}.YVNYya_footer{border-top:.5px solid var(--dsw-alias-border-l2);justify-content:flex-end;align-items:center;gap:8px;padding:12px 0 4px;display:flex}@media (width<=480px){.YVNYya_pairedFields{grid-template-columns:minmax(0,1fr);gap:0}.YVNYya_pairedFields .YVNYya_field+.YVNYya_field{border-top:.5px solid var(--dsw-alias-border-l2)}}";
102
+ const tagId$1 = "dsh-hypatia-auto-memory/SettingsCard.module.css";
103
+ if (typeof document !== "undefined" && document.querySelector("style[data-plugin-css=" + JSON.stringify(tagId$1) + "]") === null) {
104
104
  const tag = document.createElement("style");
105
105
  tag.dataset.plugin = "dsh-hypatia-auto-memory";
106
- tag.dataset.pluginCss = tagId;
107
- tag.textContent = css;
106
+ tag.dataset.pluginCss = tagId$1;
107
+ tag.textContent = css$1;
108
108
  document.head.appendChild(tag);
109
109
  }
110
110
  var SettingsCard_module_css_default = {
@@ -201,7 +201,47 @@ window.__ModuleLoader__.load({
201
201
  saving: "保存中…",
202
202
  saveFailed: "保存失败:{message}",
203
203
  readOnly: "只读:当前上下文不可写入设置文档。",
204
- unsaved: "未保存"
204
+ unsaved: "未保存",
205
+ viewMemory: "记忆",
206
+ memoryTitle: "本会话的记忆",
207
+ memoryUpdatedAt: "状态更新于 {time}",
208
+ memoryRefresh: "刷新",
209
+ memoryLoading: "正在读取记忆状态…",
210
+ memoryNoSnapshot: "插件还没有发布记忆状态。启用后完成一轮对话再回来看看。",
211
+ memoryLoadFailed: "无法读取记忆状态:{message}",
212
+ memoryUnknown: "这个会话还没有记忆记录。第一轮对话结束后会开始记录。",
213
+ memoryLogged: "对话记录",
214
+ memorySeqValue: "第 {seq} 条事件",
215
+ memoryConsolidated: "摘要整合",
216
+ memoryCaughtUp: "已结清",
217
+ memoryBehind: "较记录落后 {behind} 条",
218
+ memoryPending: "待整合内容",
219
+ memoryPendingValue: "约 {tokens} token",
220
+ memorySessionNode: "会话节点",
221
+ memorySessionNodeYes: "已建立",
222
+ memorySessionNodeNo: "未建立(宿主未提供会话摘要)",
223
+ memoryBelongTo: "已链接消息",
224
+ memoryBelongToValue: "{count} 条",
225
+ memoryDeferred: "等待会话载入的任务",
226
+ memoryFailedTitle: "失败任务",
227
+ memoryFailedItem: "{kind} 连续失败 {attempts} 次",
228
+ memoryContentTitle: "记忆内容",
229
+ memoryContentFailed: "无法读取 shelf 中的记忆内容:{message}",
230
+ memorySummariesTitle: "跨度摘要",
231
+ memoryWorkUnitsTitle: "工作单元",
232
+ memoryNoSummaries: "这个会话还没有产生摘要。整合运行后会出现在这里。",
233
+ memoryNoWorkUnits: "这个会话还没有提炼出工作单元。",
234
+ memoryCount: "显示 {shown} / 共 {total}",
235
+ memoryTier: "{level} 级",
236
+ memoryWorkUnit: "工作单元",
237
+ memoryEntryExpand: "展开",
238
+ memoryEntryCollapse: "收起",
239
+ memoryEntryEmpty: "这条条目的正文为空,或已超过展示上限。",
240
+ memoryCopyCode: "复制",
241
+ memoryCopiedCode: "已复制",
242
+ memoryFootnotes: "脚注",
243
+ memoryTruncated: "内容超出展示上限,较早的条目或过长的正文已被截断。",
244
+ memoryHint: "诊断视图:原始 msg-* 对话正文不在此显示,需要时让 Agent 通过 hypatia 检索。状态每 5 秒重读一次;「状态更新于」是宿主最近一次看到这些状态发生变化的时间(首次读取也算一次),不变说明这段时间没有新进展。两个条数都按会话日志的事件序号计(含工具调用,不是消息条数),所以它们之差就是还没总结的部分。"
205
245
  };
206
246
  /** English dictionary checked against the Chinese key set. */
207
247
  const en = {
@@ -250,7 +290,47 @@ window.__ModuleLoader__.load({
250
290
  saving: "Saving…",
251
291
  saveFailed: "Save failed: {message}",
252
292
  readOnly: "Read-only: settings document is not writable in this context.",
253
- unsaved: "Unsaved"
293
+ unsaved: "Unsaved",
294
+ viewMemory: "Memory",
295
+ memoryTitle: "This session's memory",
296
+ memoryUpdatedAt: "State updated {time}",
297
+ memoryRefresh: "Refresh",
298
+ memoryLoading: "Reading memory status…",
299
+ memoryNoSnapshot: "The plugin has not published a status snapshot yet. Enable it, finish a turn, and come back.",
300
+ memoryLoadFailed: "Unable to read memory status: {message}",
301
+ memoryUnknown: "This session has no memory record yet. Logging starts once the first turn ends.",
302
+ memoryLogged: "Conversation log",
303
+ memorySeqValue: "event {seq}",
304
+ memoryConsolidated: "Summarisation",
305
+ memoryCaughtUp: "caught up",
306
+ memoryBehind: "{behind} events behind the log",
307
+ memoryPending: "Waiting to summarise",
308
+ memoryPendingValue: "~{tokens} tokens",
309
+ memorySessionNode: "Session node",
310
+ memorySessionNodeYes: "created",
311
+ memorySessionNodeNo: "not created (the Host supplied no session summary)",
312
+ memoryBelongTo: "Linked messages",
313
+ memoryBelongToValue: "{count}",
314
+ memoryDeferred: "Tasks waiting for the session to load",
315
+ memoryFailedTitle: "Failed tasks",
316
+ memoryFailedItem: "{kind} failed {attempts} times",
317
+ memoryContentTitle: "Memory content",
318
+ memoryContentFailed: "Unable to read memory content from the shelf: {message}",
319
+ memorySummariesTitle: "Span summaries",
320
+ memoryWorkUnitsTitle: "Work units",
321
+ memoryNoSummaries: "This session has produced no summary yet. They appear here once consolidation runs.",
322
+ memoryNoWorkUnits: "This session has yielded no work units yet.",
323
+ memoryCount: "showing {shown} of {total}",
324
+ memoryTier: "tier {level}",
325
+ memoryWorkUnit: "work unit",
326
+ memoryEntryExpand: "Expand",
327
+ memoryEntryCollapse: "Collapse",
328
+ memoryEntryEmpty: "This entry has no body, or it exceeded the display cap.",
329
+ memoryCopyCode: "Copy",
330
+ memoryCopiedCode: "Copied",
331
+ memoryFootnotes: "Footnotes",
332
+ memoryTruncated: "Content exceeded the display cap: earlier entries or over-long bodies were cut.",
333
+ 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
334
  };
255
335
  //#endregion
256
336
  //#region src/client/SettingsCard.tsx
@@ -1007,6 +1087,596 @@ window.__ModuleLoader__.load({
1007
1087
  });
1008
1088
  }
1009
1089
  //#endregion
1090
+ //#region src/client/memory-client.ts
1091
+ /**
1092
+ * The Memory tab's read side: one same-origin request per refresh.
1093
+ *
1094
+ * The Host half serves it from `/api/dsh-hypatia-auto-memory/session`
1095
+ * (see `src/memory-api.js`), which is parameterized by session and authenticates
1096
+ * itself — unlike a settings namespace, it can answer for the one session the
1097
+ * tab is bound to instead of shipping every session's state to the browser.
1098
+ *
1099
+ * Zero runtime imports beyond `fetch`, so the parsing half is unit-tested under
1100
+ * `node:test` like `shelves.ts`.
1101
+ */
1102
+ /** Route prefix the Host claims; the tab appends `/session`. */
1103
+ const MEMORY_API_PREFIX = "/api/dsh-hypatia-auto-memory";
1104
+ /**
1105
+ * The URL one refresh reads.
1106
+ * @param sessionId - the session the tab is bound to.
1107
+ * @param content - whether to ask the Host to read the shelf as well.
1108
+ * @returns a same-origin path with its query.
1109
+ */
1110
+ function memoryUrl(sessionId, content) {
1111
+ const query = new URLSearchParams({ session: sessionId });
1112
+ if (content) query.set("content", "1");
1113
+ return `${MEMORY_API_PREFIX}/session?${query.toString()}`;
1114
+ }
1115
+ /** Fetch one session's memory from the Host. */
1116
+ const fetchMemory = async (sessionId, { content }) => {
1117
+ const response = await fetch(memoryUrl(sessionId, content), {
1118
+ method: "GET",
1119
+ credentials: "same-origin",
1120
+ headers: { accept: "application/json" }
1121
+ });
1122
+ const body = await response.json().catch(() => void 0);
1123
+ if (!response.ok) {
1124
+ const message = isRecord(body) && typeof body.error === "string" ? body.error : `HTTP ${String(response.status)}`;
1125
+ throw new Error(message);
1126
+ }
1127
+ const payload = parseMemoryPayload(body, sessionId);
1128
+ if (payload === void 0) throw new Error("the Host returned an unreadable memory payload");
1129
+ return payload;
1130
+ };
1131
+ /**
1132
+ * Session-log events that are recorded but not yet consolidated.
1133
+ *
1134
+ * Both watermarks index the same log, so their difference is exactly the work
1135
+ * consolidation still owes. Clamped at zero: a consolidation run may cover a
1136
+ * range the logging watermark has not caught up to yet, which is not a backlog.
1137
+ *
1138
+ * @param session - the session half of a payload.
1139
+ * @returns the gap in session-log events.
1140
+ */
1141
+ function consolidationGap(session) {
1142
+ return Math.max(0, session.loggedSeq - session.consolidatedSeq);
1143
+ }
1144
+ function isRecord(value) {
1145
+ return typeof value === "object" && value !== null && !Array.isArray(value);
1146
+ }
1147
+ function num(value) {
1148
+ return typeof value === "number" && Number.isFinite(value) ? value : 0;
1149
+ }
1150
+ function str(value) {
1151
+ return typeof value === "string" ? value : "";
1152
+ }
1153
+ function parseFailedTasks(value) {
1154
+ if (!Array.isArray(value)) return [];
1155
+ return value.flatMap((entry) => isRecord(entry) ? [{
1156
+ kind: str(entry.kind),
1157
+ sessionId: str(entry.sessionId),
1158
+ attempts: num(entry.attempts),
1159
+ error: str(entry.error)
1160
+ }] : []);
1161
+ }
1162
+ function parseEntries(value) {
1163
+ if (!Array.isArray(value)) return [];
1164
+ return value.flatMap((entry) => {
1165
+ if (!isRecord(entry)) return [];
1166
+ const name = str(entry.name);
1167
+ if (name === "") return [];
1168
+ return [{
1169
+ name,
1170
+ markdown: str(entry.markdown),
1171
+ level: num(entry.level) || 1,
1172
+ createdAt: str(entry.createdAt)
1173
+ }];
1174
+ });
1175
+ }
1176
+ /**
1177
+ * Read the Host's answer into the tab's shape, defensively.
1178
+ *
1179
+ * A malformed field falls back to its zero value rather than reaching the DOM;
1180
+ * a payload with no usable `session` is rejected outright, because rendering a
1181
+ * panel of zeros for a session the Host never answered about would be a lie.
1182
+ *
1183
+ * A status-only answer parses to `content: undefined` — never to empty lists,
1184
+ * which is what made the five-second poll erase the shelf content. The Host also
1185
+ * states which half it sent (`content`), and that flag is what this prefers; the
1186
+ * key sniffing beside it keeps a bundle built after the flag from misreading a
1187
+ * Host that predates it, so the fix does not need a profile restart to land.
1188
+ *
1189
+ * @param value - the parsed response body.
1190
+ * @param sessionId - the session that was asked for.
1191
+ * @returns the payload, or undefined when the body is not one.
1192
+ */
1193
+ function parseMemoryPayload(value, sessionId) {
1194
+ if (!isRecord(value) || !isRecord(value.session)) return void 0;
1195
+ const session = value.session;
1196
+ const carried = value.content === true || Object.hasOwn(value, "summaries") || Object.hasOwn(value, "contentError");
1197
+ return {
1198
+ session: {
1199
+ sessionId: str(session.sessionId) || sessionId,
1200
+ known: session.known === true,
1201
+ loggedSeq: num(session.loggedSeq),
1202
+ consolidatedSeq: num(session.consolidatedSeq),
1203
+ caughtUp: session.caughtUp === true,
1204
+ pendingTokens: num(session.pendingTokens),
1205
+ sessionNode: session.sessionNode === true,
1206
+ belongTo: num(session.belongTo),
1207
+ deferred: num(session.deferred),
1208
+ failed: num(session.failed),
1209
+ failedTasks: parseFailedTasks(session.failedTasks),
1210
+ error: str(session.error)
1211
+ },
1212
+ updatedAt: num(value.updatedAt),
1213
+ content: carried ? {
1214
+ summaries: parseEntries(value.summaries),
1215
+ workUnits: parseEntries(value.workUnits),
1216
+ summaryCount: num(value.summaryCount),
1217
+ workUnitCount: num(value.workUnitCount),
1218
+ truncated: value.truncated === true,
1219
+ error: str(value.contentError)
1220
+ } : void 0
1221
+ };
1222
+ }
1223
+ /**
1224
+ * Fold a new answer onto the one on screen.
1225
+ *
1226
+ * Status is always the newest. Content is only replaced by an answer that
1227
+ * carried content, so the poll cannot erase it. An answer whose shelf read
1228
+ * failed keeps the content already shown — entries, counts and the truncation
1229
+ * flag together, so the header still describes the list under it — and records
1230
+ * the error beside it, because a transient hypatia failure should not blank
1231
+ * what the reader was looking at.
1232
+ *
1233
+ * @param previous - the payload on screen, if any.
1234
+ * @param next - the answer just parsed.
1235
+ * @returns the payload to render.
1236
+ */
1237
+ function mergeMemory(previous, next) {
1238
+ if (next.content === void 0) return {
1239
+ ...next,
1240
+ content: previous?.content
1241
+ };
1242
+ if (next.content.error !== "" && previous?.content !== void 0) return {
1243
+ ...next,
1244
+ content: {
1245
+ ...previous.content,
1246
+ error: next.content.error
1247
+ }
1248
+ };
1249
+ return next;
1250
+ }
1251
+ //#endregion
1252
+ //#region \0dsh-hypatia-css:/home/runner/work/dsh-hypatia-auto-memory/dsh-hypatia-auto-memory/src/client/MemoryView.module.css.mjs
1253
+ 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}";
1254
+ const tagId = "dsh-hypatia-auto-memory/MemoryView.module.css";
1255
+ if (typeof document !== "undefined" && document.querySelector("style[data-plugin-css=" + JSON.stringify(tagId) + "]") === null) {
1256
+ const tag = document.createElement("style");
1257
+ tag.dataset.plugin = "dsh-hypatia-auto-memory";
1258
+ tag.dataset.pluginCss = tagId;
1259
+ tag.textContent = css;
1260
+ document.head.appendChild(tag);
1261
+ }
1262
+ var MemoryView_module_css_default = {
1263
+ "badge": "LBuOzG_badge",
1264
+ "busy": "LBuOzG_busy",
1265
+ "calm": "LBuOzG_calm",
1266
+ "column": "LBuOzG_column",
1267
+ "count": "LBuOzG_count",
1268
+ "entries": "LBuOzG_entries",
1269
+ "entry": "LBuOzG_entry",
1270
+ "entryBody": "LBuOzG_entryBody",
1271
+ "entryHead": "LBuOzG_entryHead",
1272
+ "entryMeta": "LBuOzG_entryMeta",
1273
+ "entryName": "LBuOzG_entryName",
1274
+ "entryPreview": "LBuOzG_entryPreview",
1275
+ "entryTime": "LBuOzG_entryTime",
1276
+ "entryToggle": "LBuOzG_entryToggle",
1277
+ "error": "LBuOzG_error",
1278
+ "failure": "LBuOzG_failure",
1279
+ "failureError": "LBuOzG_failureError",
1280
+ "failureKind": "LBuOzG_failureKind",
1281
+ "failures": "LBuOzG_failures",
1282
+ "group": "LBuOzG_group",
1283
+ "groupTitle": "LBuOzG_groupTitle",
1284
+ "head": "LBuOzG_head",
1285
+ "headMeta": "LBuOzG_headMeta",
1286
+ "hint": "LBuOzG_hint",
1287
+ "label": "LBuOzG_label",
1288
+ "panel": "LBuOzG_panel",
1289
+ "refresh": "LBuOzG_refresh",
1290
+ "row": "LBuOzG_row",
1291
+ "rows": "LBuOzG_rows",
1292
+ "status": "LBuOzG_status",
1293
+ "title": "LBuOzG_title",
1294
+ "updated": "LBuOzG_updated",
1295
+ "value": "LBuOzG_value"
1296
+ };
1297
+ //#endregion
1298
+ //#region src/client/MemoryView.tsx
1299
+ /**
1300
+ * The conversation's Memory tab: what this session has been remembered as, what
1301
+ * it was remembered into, and what is stuck.
1302
+ *
1303
+ * Three sections, in the order a reader wants them: the pipeline's status
1304
+ * (watermarks, backlog, failures), then the span summaries consolidation
1305
+ * produced, then the work units derived from them. Bodies render through the
1306
+ * shell's own `MarkdownText`, so a summary reads exactly like assistant Markdown
1307
+ * elsewhere in the GUI and costs no bundle weight.
1308
+ *
1309
+ * Raw `msg-*` entries are deliberately absent: they hold the conversation
1310
+ * verbatim, the reader just wrote them, and the agent is the right reader for
1311
+ * them (see `src/memory-api.js` for the same rule on the Host side).
1312
+ *
1313
+ * Status is polled; content is fetched only when it can have changed — on mount,
1314
+ * on a session switch, on a manual refresh, and whenever consolidation advances
1315
+ * the session's watermark. Reading the shelf costs hypatia round trips, and
1316
+ * consolidation is the only thing that changes it.
1317
+ */
1318
+ /** How often the tab re-reads the status half while it is on screen. */
1319
+ const POLL_MS = 5e3;
1320
+ /** Local wall-clock stamp for the freshness line. */
1321
+ function formatTime(epochMs) {
1322
+ if (!Number.isFinite(epochMs) || epochMs <= 0) return "—";
1323
+ return new Date(epochMs).toLocaleTimeString();
1324
+ }
1325
+ /**
1326
+ * hypatia stamps `created_at` as `YYYY-MM-DD HH:MM:SS.ffffff`; the microseconds
1327
+ * are noise in a row that is 11px tall, so the tab shows down to the minute.
1328
+ * Slice rather than `new Date`: the space-separated form is not the ISO shape
1329
+ * every engine is required to parse.
1330
+ */
1331
+ function formatEntryTime(stamp) {
1332
+ return stamp.length >= 16 ? stamp.slice(0, 16).replace("T", " ") : stamp;
1333
+ }
1334
+ /** The first non-empty line of a body, for a collapsed row's preview. */
1335
+ function firstLine(markdown) {
1336
+ for (const line of markdown.split("\n")) {
1337
+ const trimmed = line.replace(/^[#>\-*\s]+/, "").trim();
1338
+ if (trimmed !== "") return trimmed;
1339
+ }
1340
+ return "";
1341
+ }
1342
+ /** One collapsible distilled entry. */
1343
+ function Entry({ entry, badge, open, onToggle, labels, t }) {
1344
+ return /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("li", {
1345
+ className: MemoryView_module_css_default.entry,
1346
+ children: [/* @__PURE__ */ (0, react_jsx_runtime.jsxs)("button", {
1347
+ type: "button",
1348
+ className: MemoryView_module_css_default.entryHead,
1349
+ "aria-expanded": open,
1350
+ onClick: onToggle,
1351
+ children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
1352
+ className: MemoryView_module_css_default.entryName,
1353
+ children: entry.name
1354
+ }), /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("span", {
1355
+ className: MemoryView_module_css_default.entryMeta,
1356
+ children: [
1357
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
1358
+ className: MemoryView_module_css_default.badge,
1359
+ children: badge
1360
+ }),
1361
+ entry.createdAt !== "" ? /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
1362
+ className: MemoryView_module_css_default.entryTime,
1363
+ children: formatEntryTime(entry.createdAt)
1364
+ }) : null,
1365
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
1366
+ className: MemoryView_module_css_default.entryToggle,
1367
+ children: open ? t("memoryEntryCollapse") : t("memoryEntryExpand")
1368
+ })
1369
+ ]
1370
+ })]
1371
+ }), open ? /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
1372
+ className: MemoryView_module_css_default.entryBody,
1373
+ children: entry.markdown === "" ? /* @__PURE__ */ (0, react_jsx_runtime.jsx)("p", {
1374
+ className: MemoryView_module_css_default.status,
1375
+ children: t("memoryEntryEmpty")
1376
+ }) : /* @__PURE__ */ (0, react_jsx_runtime.jsx)(_deepseek_ai_dsh_client_ui_primitives.MarkdownText, {
1377
+ text: entry.markdown,
1378
+ labels
1379
+ })
1380
+ }) : /* @__PURE__ */ (0, react_jsx_runtime.jsx)("p", {
1381
+ className: MemoryView_module_css_default.entryPreview,
1382
+ children: firstLine(entry.markdown)
1383
+ })]
1384
+ });
1385
+ }
1386
+ function MemoryView({ sessionId, fetch, t }) {
1387
+ const [payload, setPayload] = (0, react.useState)(void 0);
1388
+ const [loading, setLoading] = (0, react.useState)(true);
1389
+ const [error, setError] = (0, react.useState)("");
1390
+ /**
1391
+ * The entries the reader opened, by name. Absent means closed, which is the
1392
+ * default: a toggle survives the poll that replaces the payload, and an entry
1393
+ * a later poll adds starts closed rather than inheriting a neighbour's state.
1394
+ */
1395
+ const [overrides, setOverrides] = (0, react.useState)({});
1396
+ /** The watermark whose content is already in hand, so a poll never re-reads it. */
1397
+ const [contentAt, setContentAt] = (0, react.useState)(void 0);
1398
+ const copyLabel = t("memoryCopyCode");
1399
+ const copiedLabel = t("memoryCopiedCode");
1400
+ const footnotes = t("memoryFootnotes");
1401
+ const labels = (0, react.useMemo)(() => ({
1402
+ code: {
1403
+ copyLabel,
1404
+ copiedLabel
1405
+ },
1406
+ footnotes
1407
+ }), [
1408
+ copyLabel,
1409
+ copiedLabel,
1410
+ footnotes
1411
+ ]);
1412
+ /**
1413
+ * The session the tab is showing now. A request outlives the session it was
1414
+ * made for — a slow shelf read, or a poll already in flight at a switch — and
1415
+ * its answer must be dropped rather than folded into the next session's view.
1416
+ */
1417
+ const activeSession = (0, react.useRef)(sessionId);
1418
+ const load = (0, react.useCallback)(async (content) => {
1419
+ const current = () => activeSession.current === sessionId;
1420
+ try {
1421
+ const next = await fetch(sessionId, { content });
1422
+ if (!current()) return;
1423
+ setPayload((previous) => mergeMemory(previous, next));
1424
+ setError("");
1425
+ if (content) setContentAt(next.session.consolidatedSeq);
1426
+ } catch (failure) {
1427
+ if (!current()) return;
1428
+ setError(failure instanceof Error ? failure.message : String(failure));
1429
+ } finally {
1430
+ if (current()) setLoading(false);
1431
+ }
1432
+ }, [fetch, sessionId]);
1433
+ (0, react.useEffect)(() => {
1434
+ activeSession.current = sessionId;
1435
+ setPayload(void 0);
1436
+ setOverrides({});
1437
+ setContentAt(void 0);
1438
+ setLoading(true);
1439
+ load(true);
1440
+ }, [load, sessionId]);
1441
+ (0, react.useEffect)(() => {
1442
+ const timer = setInterval(() => {
1443
+ if (typeof document !== "undefined" && document.visibilityState === "hidden") return;
1444
+ load(false);
1445
+ }, POLL_MS);
1446
+ return () => clearInterval(timer);
1447
+ }, [load]);
1448
+ (0, react.useEffect)(() => {
1449
+ const at = payload?.session.consolidatedSeq;
1450
+ if (at === void 0 || contentAt === void 0) return;
1451
+ if (at !== contentAt) load(true);
1452
+ }, [
1453
+ payload?.session.consolidatedSeq,
1454
+ contentAt,
1455
+ load
1456
+ ]);
1457
+ const toggle = (0, react.useCallback)((name, open) => {
1458
+ setOverrides((current) => ({
1459
+ ...current,
1460
+ [name]: !open
1461
+ }));
1462
+ }, []);
1463
+ const session = payload?.session;
1464
+ const content = payload?.content;
1465
+ const summaries = content?.summaries ?? [];
1466
+ const workUnits = content?.workUnits ?? [];
1467
+ const renderEntries = (entries, kind) => /* @__PURE__ */ (0, react_jsx_runtime.jsx)("ul", {
1468
+ className: MemoryView_module_css_default.entries,
1469
+ children: entries.map((entry) => {
1470
+ const open = overrides[entry.name] === true;
1471
+ return /* @__PURE__ */ (0, react_jsx_runtime.jsx)(Entry, {
1472
+ entry,
1473
+ badge: kind === "summary" ? t("memoryTier", { level: String(entry.level) }) : t("memoryWorkUnit"),
1474
+ open,
1475
+ onToggle: () => toggle(entry.name, open),
1476
+ labels,
1477
+ t
1478
+ }, entry.name);
1479
+ })
1480
+ });
1481
+ return /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
1482
+ className: MemoryView_module_css_default.panel,
1483
+ "data-conversation-composer-overlay": "",
1484
+ children: /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
1485
+ className: MemoryView_module_css_default.column,
1486
+ children: [
1487
+ /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("header", {
1488
+ className: MemoryView_module_css_default.head,
1489
+ children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("h2", {
1490
+ className: MemoryView_module_css_default.title,
1491
+ children: t("memoryTitle")
1492
+ }), /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
1493
+ className: MemoryView_module_css_default.headMeta,
1494
+ children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
1495
+ className: MemoryView_module_css_default.updated,
1496
+ children: t("memoryUpdatedAt", { time: formatTime(payload?.updatedAt ?? 0) })
1497
+ }), /* @__PURE__ */ (0, react_jsx_runtime.jsx)("button", {
1498
+ type: "button",
1499
+ className: MemoryView_module_css_default.refresh,
1500
+ onClick: () => {
1501
+ load(true);
1502
+ },
1503
+ children: t("memoryRefresh")
1504
+ })]
1505
+ })]
1506
+ }),
1507
+ error !== "" ? /* @__PURE__ */ (0, react_jsx_runtime.jsx)("p", {
1508
+ className: MemoryView_module_css_default.error,
1509
+ role: "status",
1510
+ children: t("memoryLoadFailed", { message: error })
1511
+ }) : null,
1512
+ loading && payload === void 0 ? /* @__PURE__ */ (0, react_jsx_runtime.jsx)("p", {
1513
+ className: MemoryView_module_css_default.status,
1514
+ children: t("memoryLoading")
1515
+ }) : session === void 0 ? /* @__PURE__ */ (0, react_jsx_runtime.jsx)("p", {
1516
+ className: MemoryView_module_css_default.status,
1517
+ children: t("memoryNoSnapshot")
1518
+ }) : !session.known ? /* @__PURE__ */ (0, react_jsx_runtime.jsx)("p", {
1519
+ className: MemoryView_module_css_default.status,
1520
+ children: t("memoryUnknown")
1521
+ }) : /* @__PURE__ */ (0, react_jsx_runtime.jsx)("section", {
1522
+ className: MemoryView_module_css_default.group,
1523
+ "aria-label": t("memoryTitle"),
1524
+ children: /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("dl", {
1525
+ className: MemoryView_module_css_default.rows,
1526
+ children: [
1527
+ /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
1528
+ className: MemoryView_module_css_default.row,
1529
+ children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("dt", {
1530
+ className: MemoryView_module_css_default.label,
1531
+ children: t("memoryLogged")
1532
+ }), /* @__PURE__ */ (0, react_jsx_runtime.jsx)("dd", {
1533
+ className: MemoryView_module_css_default.value,
1534
+ children: t("memorySeqValue", { seq: String(session.loggedSeq) })
1535
+ })]
1536
+ }),
1537
+ /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
1538
+ className: MemoryView_module_css_default.row,
1539
+ children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("dt", {
1540
+ className: MemoryView_module_css_default.label,
1541
+ children: t("memoryConsolidated")
1542
+ }), /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("dd", {
1543
+ className: MemoryView_module_css_default.value,
1544
+ children: [t("memorySeqValue", { seq: String(session.consolidatedSeq) }), /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
1545
+ className: session.caughtUp ? MemoryView_module_css_default.calm : MemoryView_module_css_default.busy,
1546
+ children: session.caughtUp ? t("memoryCaughtUp") : t("memoryBehind", { behind: String(consolidationGap(session)) })
1547
+ })]
1548
+ })]
1549
+ }),
1550
+ /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
1551
+ className: MemoryView_module_css_default.row,
1552
+ children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("dt", {
1553
+ className: MemoryView_module_css_default.label,
1554
+ children: t("memoryPending")
1555
+ }), /* @__PURE__ */ (0, react_jsx_runtime.jsx)("dd", {
1556
+ className: MemoryView_module_css_default.value,
1557
+ children: t("memoryPendingValue", { tokens: String(session.pendingTokens) })
1558
+ })]
1559
+ }),
1560
+ /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
1561
+ className: MemoryView_module_css_default.row,
1562
+ children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("dt", {
1563
+ className: MemoryView_module_css_default.label,
1564
+ children: t("memorySessionNode")
1565
+ }), /* @__PURE__ */ (0, react_jsx_runtime.jsx)("dd", {
1566
+ className: MemoryView_module_css_default.value,
1567
+ children: session.sessionNode ? t("memorySessionNodeYes") : t("memorySessionNodeNo")
1568
+ })]
1569
+ }),
1570
+ /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
1571
+ className: MemoryView_module_css_default.row,
1572
+ children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("dt", {
1573
+ className: MemoryView_module_css_default.label,
1574
+ children: t("memoryBelongTo")
1575
+ }), /* @__PURE__ */ (0, react_jsx_runtime.jsx)("dd", {
1576
+ className: MemoryView_module_css_default.value,
1577
+ children: t("memoryBelongToValue", { count: String(session.belongTo) })
1578
+ })]
1579
+ }),
1580
+ session.deferred > 0 ? /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
1581
+ className: MemoryView_module_css_default.row,
1582
+ children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("dt", {
1583
+ className: MemoryView_module_css_default.label,
1584
+ children: t("memoryDeferred")
1585
+ }), /* @__PURE__ */ (0, react_jsx_runtime.jsx)("dd", {
1586
+ className: MemoryView_module_css_default.value,
1587
+ children: String(session.deferred)
1588
+ })]
1589
+ }) : null
1590
+ ]
1591
+ })
1592
+ }),
1593
+ session !== void 0 && (session.failed > 0 || session.failedTasks.length > 0) ? /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("section", {
1594
+ className: MemoryView_module_css_default.group,
1595
+ "aria-label": t("memoryFailedTitle"),
1596
+ children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("h3", {
1597
+ className: MemoryView_module_css_default.groupTitle,
1598
+ children: t("memoryFailedTitle")
1599
+ }), /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("ul", {
1600
+ className: MemoryView_module_css_default.failures,
1601
+ children: [session.failedTasks.map((task, index) => /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("li", {
1602
+ className: MemoryView_module_css_default.failure,
1603
+ children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
1604
+ className: MemoryView_module_css_default.failureKind,
1605
+ children: t("memoryFailedItem", {
1606
+ kind: task.kind,
1607
+ attempts: String(task.attempts)
1608
+ })
1609
+ }), task.error !== "" ? /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
1610
+ className: MemoryView_module_css_default.failureError,
1611
+ children: task.error
1612
+ }) : null]
1613
+ }, `${task.kind}-${String(index)}`)), session.failedTasks.length === 0 ? /* @__PURE__ */ (0, react_jsx_runtime.jsx)("li", {
1614
+ className: MemoryView_module_css_default.failure,
1615
+ children: session.error
1616
+ }) : null]
1617
+ })]
1618
+ }) : null,
1619
+ content !== void 0 && content.error !== "" ? /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("section", {
1620
+ className: MemoryView_module_css_default.group,
1621
+ "aria-label": t("memoryContentTitle"),
1622
+ children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("h3", {
1623
+ className: MemoryView_module_css_default.groupTitle,
1624
+ children: t("memoryContentTitle")
1625
+ }), /* @__PURE__ */ (0, react_jsx_runtime.jsx)("p", {
1626
+ className: MemoryView_module_css_default.error,
1627
+ role: "status",
1628
+ children: t("memoryContentFailed", { message: content.error })
1629
+ })]
1630
+ }) : null,
1631
+ content !== void 0 ? /* @__PURE__ */ (0, react_jsx_runtime.jsxs)(react_jsx_runtime.Fragment, { children: [
1632
+ /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("section", {
1633
+ className: MemoryView_module_css_default.group,
1634
+ "aria-label": t("memorySummariesTitle"),
1635
+ children: [/* @__PURE__ */ (0, react_jsx_runtime.jsxs)("h3", {
1636
+ className: MemoryView_module_css_default.groupTitle,
1637
+ children: [t("memorySummariesTitle"), /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
1638
+ className: MemoryView_module_css_default.count,
1639
+ children: t("memoryCount", {
1640
+ shown: String(summaries.length),
1641
+ total: String(content.summaryCount)
1642
+ })
1643
+ })]
1644
+ }), summaries.length === 0 ? /* @__PURE__ */ (0, react_jsx_runtime.jsx)("p", {
1645
+ className: MemoryView_module_css_default.status,
1646
+ children: t("memoryNoSummaries")
1647
+ }) : renderEntries(summaries, "summary")]
1648
+ }),
1649
+ /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("section", {
1650
+ className: MemoryView_module_css_default.group,
1651
+ "aria-label": t("memoryWorkUnitsTitle"),
1652
+ children: [/* @__PURE__ */ (0, react_jsx_runtime.jsxs)("h3", {
1653
+ className: MemoryView_module_css_default.groupTitle,
1654
+ children: [t("memoryWorkUnitsTitle"), /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
1655
+ className: MemoryView_module_css_default.count,
1656
+ children: t("memoryCount", {
1657
+ shown: String(workUnits.length),
1658
+ total: String(content.workUnitCount)
1659
+ })
1660
+ })]
1661
+ }), workUnits.length === 0 ? /* @__PURE__ */ (0, react_jsx_runtime.jsx)("p", {
1662
+ className: MemoryView_module_css_default.status,
1663
+ children: t("memoryNoWorkUnits")
1664
+ }) : renderEntries(workUnits, "workUnit")]
1665
+ }),
1666
+ content.truncated ? /* @__PURE__ */ (0, react_jsx_runtime.jsx)("p", {
1667
+ className: MemoryView_module_css_default.hint,
1668
+ children: t("memoryTruncated")
1669
+ }) : null,
1670
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)("p", {
1671
+ className: MemoryView_module_css_default.hint,
1672
+ children: t("memoryHint")
1673
+ })
1674
+ ] }) : null
1675
+ ]
1676
+ })
1677
+ });
1678
+ }
1679
+ //#endregion
1010
1680
  //#region src/client/index.tsx
1011
1681
  const APPLY_CLAIM = "__dshHypatiaAutoMemoryApplied";
1012
1682
  function claimApply() {
@@ -1066,8 +1736,18 @@ window.__ModuleLoader__.load({
1066
1736
  key: NS,
1067
1737
  locale: NS
1068
1738
  }, renderCard));
1739
+ const t = locale.bind(NS);
1740
+ const unregisterMemoryView = ctx.slots.inject("conversation.view", () => ctx.slots.register({
1741
+ name: "conversation.view",
1742
+ id: "memory",
1743
+ order: 20,
1744
+ locale: NS,
1745
+ label: () => t("viewMemory"),
1746
+ inject: () => ({ fetch: fetchMemory })
1747
+ }, MemoryView));
1069
1748
  ctx.effect(() => () => {
1070
1749
  unregister();
1750
+ unregisterMemoryView();
1071
1751
  releaseApply();
1072
1752
  });
1073
1753
  }