dsh-hypatia-auto-memory 0.2.0 → 0.4.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 CHANGED
@@ -1,5 +1,7 @@
1
1
  # dsh-hypatia-auto-memory
2
2
 
3
+ > 中文:[README.zh-CN.md](./README.zh-CN.md)
4
+
3
5
  Event-driven [Hypatia](https://github.com/tkliuxing/hypatia) memory for
4
6
  [DSH](https://github.com/deepseek-ai/deepseek-harness): automatic conversation
5
7
  logging and background consolidation — span summaries, a log₁₆(n) archive
@@ -124,15 +126,15 @@ agent/session-start ──▶ rules/taboos inject()
124
126
  secrets, rewrites relative dates against the time the message was sent, caps
125
127
  both user and assistant text, and enqueues idempotent log tasks.
126
128
 
127
- Each assistant message carries a ledger of its own step's tool calls in the
128
- shape the hypatia-memory protocol asks for — what was called, how long it
129
- took, whether it worked, and one line of error on failure — **never the
130
- output** (`1. \`read\` ×5 — ✅ 1.2s total`). Tool outputs had been 84% of all
131
- stored message bytes, mostly file paths and contents, and made unrelated
132
- keyword searches match chat logs. Tool-call blocks leave no `[tool-call]`
133
- placeholder in the content; a step that only called tools says
134
- `(tool calls only)`. Plugin-sourced
135
- messages are skipped — no feedback loops.
129
+ Tool-call, thinking and reasoning blocks are internal to the model and leave
130
+ no placeholder in the content. Assistant turns whose only output is one of
131
+ those (tool calls only, empty reasoning marker, etc.) produce no `msg-*`
132
+ entry at all — there is nothing substantive to remember. Plugin-sourced
133
+ messages are skipped — no feedback loops. By default no tool-call ledger is
134
+ written at all; set `hypatia-auto-memory.collector.toolLedger: true` to record
135
+ a compact ledger (`1. \`read\` ×5 — ✅ 1.2s total`) for assistant messages that
136
+ do have text/image content. In either mode **tool outputs themselves are never
137
+ stored** in the log layer.
136
138
 
137
139
  - **The log layer is not embedded.** `msg-*` and `session-*` entries and the
138
140
  `belongTo` / `summary` edges are written `embed: false` (hypatia #26): stored,
@@ -267,7 +269,7 @@ hypatia-auto-memory:
267
269
  enabled: true
268
270
  maxAssistantChars: 8000 # per-message cap before truncation marker
269
271
  maxUserChars: 32000 # same, for what the user typed or pasted
270
- toolLedger: true # collapse repeated tool calls
272
+ toolLedger: false # off by default; true records a compact tool-call ledger
271
273
  consolidation:
272
274
  enabled: true
273
275
  # Each attempt, including a retry, rotates to the next selected route.
@@ -355,12 +357,23 @@ changes. No card claims it, so it renders nowhere; nothing ever writes to it.
355
357
  (get-before-create); uncovered ranges are re-enqueued from watermarks.
356
358
  4. To check the route warning: consolidation with no selected `models`
357
359
  logs a one-time warning and otherwise stays silent.
358
- 5. `hypatia backfill --status` reports the shelf's embedding debt. With a
360
+ 5. Which model a background call actually ran on is recorded per attempt in
361
+ `~/.dsh/storages/hypatia_auto_memory_diag.json` (`tables.model_calls`,
362
+ newest first by `seq`, bounded to 100). The settings list is not the answer:
363
+ one process-local cursor rotates through `consolidation.models` across every
364
+ span summary, adjudication and cascade call, so consecutive attempts
365
+ alternate. `outcome` is `pending` (in flight, or the process died mid-call),
366
+ `ok` (a usable result was produced), `incomplete` (the call settled without
367
+ one — output cap, abort, unparseable reply) or `error` (the call threw); in
368
+ the last two the cursor was consumed and nothing was stored. `detail` is a
369
+ finish kind, a fixed label, or the provider's message, capped at 200
370
+ characters — never message content and never model output.
371
+ 6. `hypatia backfill --status` reports the shelf's embedding debt. With a
359
372
  current hypatia, logging a turn adds nothing to it; only summaries and work
360
373
  units are pending until the next flush. `hypatia scope list --count` shows
361
374
  the scopes in use — the one this project's `msg-*` entries carry is the one
362
375
  the session seed reads.
363
- 6. Uninstall: `dsh plugin --profile web remove dsh-hypatia-auto-memory` —
376
+ 7. Uninstall: `dsh plugin --profile web remove dsh-hypatia-auto-memory` —
364
377
  hypatia entries themselves are left in `~/.hypatia/`.
365
378
 
366
379
  ## Failure modes
@@ -372,8 +385,8 @@ changes. No card claims it, so it renders nowhere; nothing ever writes to it.
372
385
  | Entries appear only after a turn finishes | By design: spans are cut at `turn/end`, or at the first `step/end` once a turn has run longer than `flushWindowMs`, so an entry never lacks its own tool results |
373
386
  | Logging works, no summaries | `consolidation.models` is empty or invalid — one warning at first trigger |
374
387
  | Summaries but no `sum2-*` | Fewer than `cascade.batchSize` unarchived tier-1 summaries in that project yet |
375
- | Work units have no relationships | No embedding model on the shelf (`similar` fails), or every candidate was beyond `dedupMaxDistance`. Since hypatia #19 the local model is looked up in `~/.hypatia/models/<org>/<name>` (or the Hugging Face cache), no longer beside the shelf: a shelf that used to answer `similar` and now says `is not installed` needs `hypatia model install <model>`, or `hypatia model register <model> <dir>` pointing at the files it already has |
376
- | `similar` still returns `msg-*` rows | They were written before this plugin opted the log layer out of embedding, or by a hypatia without `--no-embed`. Retract the knowledge vectors once with `embedding.skip_tags = ["message", "session"]` in the shelf's `shelf.toml` followed by `hypatia backfill` (a binary older than the key refuses to open the shelf, so upgrade every binary sharing it first). Statements have no tags and no update command, so `belongTo` / `summary` edges written before keep their vectors |
388
+ | Work units have no relationships | No embedding model on the shelf (`similar` fails), every candidate was beyond `dedupMaxDistance`, or the adjudication call itself produced no verdict — `model_calls` shows that as `incomplete` / `max-tokens`, which is what a thinking-enabled route does when it spends the whole output budget on reasoning before answering. The call now asks a route for `reasoningEffort: 'off'` once the adapter advertises it, and carries a 1024-token cap for routes that cannot honour it. Since hypatia #19 the local model is looked up in `~/.hypatia/models/<org>/<name>` (or the Hugging Face cache), no longer beside the shelf: a shelf that used to answer `similar` and now says `is not installed` needs `hypatia model install <model>`, or `hypatia model register <model> <dir>` pointing at the files it already has |
389
+ | `similar` still returns `msg-*` rows | They were written before this plugin opted the log layer out of embedding, or by a hypatia without `--no-embed`. Retract the knowledge vectors once with `embedding.skip_tags = ["message"]` in the shelf's `shelf.toml` followed by `hypatia backfill` (a binary older than the key refuses to open the shelf, so upgrade every binary sharing it first). Do not add `session` to that list: `skip_tags` matches any entry carrying the tag, and knowledge people write about sessions carries it too — one such entry lost its vector on the shelf this was tried on. The plugin's own `session-*` nodes are few and now opt out per write. Statements have no tags and no update command, so `belongTo` / `summary` edges written before keep their vectors |
377
390
  | Task in `deferred` state | Neither the live store nor persistence could supply its session. Not an error: it spends no attempts and runs as soon as one of them can. Normally storage answers immediately — the state persists only when `sessionPersistence` is absent from the composition, or its read failed (look for `could not be read` in the log) |
378
391
  | Task in `failed` state | A hypatia or model error persisted after `maxAttempts`. Failed `log-message` records are pruned at the next startup, since the watermark re-derives their range; other kinds are kept for inspection — delete one to let the next trigger re-create it |
379
392
  | Watermark says logged, but the shelf has no entries | The shelf was reset or switched after logging. Handled at startup: `housekeeping.reconcileOnStartup` resets any row whose session has no `msg-*` left, and that session is re-logged — and re-consolidated — from the start on its next activity. A shelf query that fails leaves the row untouched. A session whose messages were all deleted on purpose is indistinguishable and is logged again; turn the switch off if that matters |
@@ -384,6 +397,7 @@ changes. No card claims it, so it renders nowhere; nothing ever writes to it.
384
397
  | Duplicate `msg-*` after weird manual edits | Delete the entry in hypatia and lower `lastLoggedSeq` for that session in the state domain — backfill recreates it once |
385
398
  | The agent's own `hypatia` write still asks for approval | `autoApprove: false` (needs a profile reload to change), the command pipes/redirects/chains outside quotes, or its first word is not one of `binaries` — only plain calls are answered, by design. Reads never reach approval at all, so nothing to fix there |
386
399
  | The agent got a memory protocol that tells it to log messages by hand | Another plugin registered `hypatia-memory` first (`dsh-hypatia` still in the profile), or a `hypatia-memory` exists on disk — typically `~/.agents/skills/hypatia-memory`, written by `hypatia skill install --agent codex`. Agent presets load disk skills in a layer nearer than plugins, so it wins in sessions even though the skill center may list this plugin. Remove the copy the startup warning names |
400
+ | Can't tell which model actually ran | By design the choice rotates: one process-local cursor is shared by span summaries, adjudication and cascade, so consecutive calls alternate through `consolidation.models`. Each attempt lands in `~/.dsh/storages/hypatia_auto_memory_diag.json` (`model_calls`) with purpose, provider/model, outcome and duration. The usage ledger cannot answer this — it folds agent turns (`assistant/message`) and never sees a plugin's direct `llm.stream`. A `pending` row means the call is still in flight, or the process died mid-call: the row is written before the call and settled after it |
387
401
  | Startup warnings never appear in the terminal | `dsh web` mounts no log exporter, so plugin log lines of any level go nowhere; the console exporter's default threshold would also drop warnings (warn is level 2, above info's 1). Mount a logger such as `dsh-logbook` or `dsh-boot-doctor` temporarily to read them |
388
402
  | Project scope looks wrong | Scope = basename of the session cwd's git top level (`git rev-parse --show-toplevel`), or of the cwd itself when git finds no work tree or cannot answer (not installed, a repo it refuses as unsafe). A `rev-parse` slower than 3 s leaves that one session on the cwd's own name. Two same-named checkouts share a scope by design; a linked worktree is scoped by its own directory, not the main checkout's; git resolves symlinks, so a checkout opened through a link named differently from its target gets the target's name. A name hypatia would rewrite is normalized first: a session at `/` is scoped `/`, commas become `_`, surrounding whitespace goes. Entries written before these fixes stay where they were stored: a subdirectory session's under that directory's name (`repo/src` wrote under `src` — the git root was never read), a session at `/` with no scope, `a,b` under both `a` and `b`, `foo,` under `foo` and global. Padded names were stored trimmed, which is what the query now asks for. Nothing migrates them; `knowledge-update --scopes` moves one entry and keeps its `created_at`. `hypatia scope list --count` (hypatia #30) shows every spelling in use and how many entries each holds |
389
403
 
@@ -480,6 +494,7 @@ dsh-hypatia-auto-memory/
480
494
  │ ├── writer.js # idempotent get-before-create writes
481
495
  │ ├── consolidator.js # thresholds, prompt, llm.stream, validation
482
496
  │ ├── cascade.js # log₁₆(n) hierarchical summary archive
497
+ │ ├── model-log.js # bounded record of which model each attempt ran on
483
498
  │ ├── recall.js # rules/taboos preload at session start
484
499
  │ ├── auto-approve.js # approves the agent's own plain bash hypatia calls
485
500
  │ ├── skills.js # bundled skill registration (never shadows another provider)
@@ -0,0 +1,430 @@
1
+ # dsh-hypatia-auto-memory
2
+
3
+ > 本文件是 [README.md](./README.md) 的中文版。若两者不一致,以英文版为准。
4
+
5
+ 面向 [DSH](https://github.com/deepseek-ai/deepseek-harness) 的事件驱动
6
+ [Hypatia](https://github.com/tkliuxing/hypatia) 记忆:自动记录对话、后台巩固
7
+ (span 摘要、log₁₆(n) 归档级联、带裁决的工作单元抽取)——全部走**专用模型路由**,
8
+ 主会话零模型工作。
9
+
10
+ 写入是全自动的;**读取由 Agent 负责**,通过随包附带的 `hypatia-memory` 技能完成。
11
+ 这种分工是有意为之——见下文 *Recall*。
12
+
13
+ **本插件取代 `dsh-hypatia`。** 后者把写入留给 Agent,实际上并没有发生:在编写本
14
+ 插件的那个部署环境里,模型加载了协议,却在十五个会话中只有一个会话主动执行了
15
+ `hypatia` 命令——而且是被人直接要求才执行的。这里,协议改由 **DSH 原生事件**驱动,
16
+ 插件通过自己的 `hypatia mcp` 进程(在 subprocess service 上)与 hypatia 通信
17
+ (CLI 加 argv 数组作为回退)——没有 shell、没有工具调用、没有沙箱提权、没有审批
18
+ 弹窗。`dsh-hypatia` 里仍值得保留的东西都带过来了:对 Agent 自己的 bash `hypatia`
19
+ 调用的自动批准,以及它的 `hypatia` / `hypatia-dream` 技能。二者装一个即可,不要
20
+ 同时安装。
21
+
22
+ ## 安装
23
+
24
+ 前置条件:`hypatia` CLI 在 PATH 上(或把它的 basename 加进 `binaries`)。带 `mcp`
25
+ 子命令的构建(hypatia #20)走 MCP;更旧的构建则走 CLI,首次调用会有一条警告。
26
+ 另外两个后加的能力在二进制具备时启用、不具备时跳过:`--no-embed`(hypatia #26)
27
+ 让日志层不进向量索引,`similar --exclude-tags`(#35)让日志层不进入工作单元候选
28
+ 检索。首次调用会记录二进制提供了哪些(`hypatia over mcp: no-embed yes, similar
29
+ filters yes`)。
30
+
31
+ ```bash
32
+ # 从本地 checkout 安装(开发)
33
+ dsh plugin --profile web add link:/path/to/dsh-hypatia-auto-memory
34
+
35
+ # 发布后从 registry 安装
36
+ dsh plugin --profile web add dsh-hypatia-auto-memory
37
+ ```
38
+
39
+ 对其它 profile(desktop、dsh-tui)重复以上步骤。安装后重启 profile。
40
+
41
+ 浏览器设置卡片预构建在 `lib/client.js` 中,随包发布。如果你改了 `src/client/*`,
42
+ 在本目录运行 `npm install && npm run build` 重新生成它;当 DSH checkout 里
43
+ `pnpm run dev:web` 处于运行状态时,client-plugin 的改动无需整页刷新即可热更新。
44
+
45
+ **替换 `dsh-hypatia`:** 把它从 profile 移除并重启。
46
+
47
+ ```bash
48
+ dsh plugin --profile web remove dsh-hypatia
49
+ ```
50
+
51
+ 两者注册了同名的技能,registry 保留先注册的那一个。本插件拒绝遮蔽另一个提供者
52
+ (改为告警),所以留着 `dsh-hypatia` 会把那份 Agent 驱动的协议原样交给 Agent——
53
+ 而那正是本插件要修复的失败。如果它因其它原因必须留下,至少在其 bundle 行上设
54
+ `skills: false`。
55
+
56
+ 磁盘上**同名技能**——`~/.agents/skills`、`~/.dsh/skills`、自定义目录,或项目的
57
+ `.dsh/skills` / `.agents/skills`——在每个 Agent 会话里同样会胜出。DSH 的技能
58
+ registry 按 scope 分层:最近一层的同名条目直接胜出,rank(project > plugin >
59
+ user)只在一层内打破平局。Agent 预设(`st`、`standard`、…)会在预设层挂载自己的
60
+ 文件系统技能提供者,比本插件的全局注册更靠近 Agent。这包括 `hypatia skill install
61
+ --agent codex` 写到 `~/.agents/skills` 的规范 `hypatia-memory`:它是 Agent 驱动的
62
+ 协议,需要 DSH 没有的宿主 hook。删掉这样一份拷贝,或把它移出 skills 目录;原地改
63
+ 目录名不够,因为 DSH 是按 frontmatter 里的 `name` 来命名技能的。启动告警会指出
64
+ 目录名。技能中心展示的是全局视图,所以它可能把本插件列为某技能的提供者,而会话
65
+ 实际加载的是磁盘拷贝。
66
+
67
+ ## 工作原理
68
+
69
+ ```
70
+ session/event (emit)
71
+ ├─ turn/end ──▶ durable queue (storageDomain tasks)
72
+ │ ├─ log-message ──▶ hypatia msg-<sid>-<index>
73
+ │ ├─ consolidate ──▶ ctx.llm.stream ──▶ sum-* (summary 1) + wu-*
74
+ │ └─ cascade ──▶ $not-summaried ──▶ sum<N>-* (summary N)
75
+ └─ session/title | compaction/summary ──▶ session-node ──▶ session-<sid> + belongTo
76
+ session/disposed ──▶ final flush + consolidation, thresholds waived (DSH 仍在运行时
77
+ 关闭的会话;进程重启则由启动时的 consolidation backfill 覆盖)
78
+ agent/session-start ──▶ rules/taboos inject()
79
+ ```
80
+
81
+ - **传输**是一个私有的 `hypatia mcp` 进程:在 subprocess service 上通过 stdio 走
82
+ JSON-RPC,一次一个请求(那个 server 本来就是这样处理的)。它不是 `dsh-mcp-client`
83
+ 条目,所以模型永远看不到这些工具,插件的调用也不经过任何工具策略、hook 或审批。
84
+ 走 CLI 时,每次调用都是一个打开 shelf 的进程;这里一批写入共享一个。内容以 JSON
85
+ 传输,而不是单个 argv 元素,所以不再因为要装进命令行而被截断到 96 KiB。tags 和
86
+ scopes 按 CLI 的方式以逗号切分,所以无论哪种传输,条目存储结果一致。
87
+
88
+ 一个进程意味着同一时间只有一个调用,而 CLI 能并行到 `queue.concurrency` 那么多。
89
+ 一个调用要等正在飞行的那个——最坏情况是一个 `similar` 在重载嵌入模型,或一次写入
90
+ 在偿还逾期的嵌入债务——会话启动时的 rules/taboos 预载也在其中。
91
+
92
+ 进程在首次调用时启动,60 秒无调用后关闭。`hypatia mcp` 在语义调用后保留嵌入模型
93
+ 的分配(数百 MB),并在启动时读取一次 shelf registry。出于同样原因,一个
94
+ `shelf '<name>' is not connected` 错误会重启它,这样期间已连接的 shelf 能在重试时
95
+ 被找到,而设置卡片的 shelf 列表始终来自 `hypatia list`。一个调用超过 30 秒期限会
96
+ 杀掉进程,正如 CLI 路径会杀掉命令一样;下一次调用会另起一个。
97
+
98
+ `transport: cli` 恢复为每次调用一条 CLI 命令。一个提供不了可用 MCP 的二进制——
99
+ 没有 `mcp` 子命令、缺少本插件调用的某个工具、或握手返回错误——会让插件在 profile
100
+ 重载前一直用 CLI。其它任何启动失败只让那一次调用失败,下一次仍会再试 MCP:一个
101
+ 完全无法启动的二进制走 CLI 也会失败,而一次超时或崩溃的启动未必会重演。
102
+
103
+ - **Collector** 记录人类的 `user/message` 和 `assistant/message`,脱敏密钥、按消息
104
+ 发送时间把相对日期改写为绝对日期、对用户和助手文本都设上限,并排入幂等的日志
105
+ 任务。
106
+
107
+ 工具调用、thinking 和 reasoning 块是模型内部的,不会在内容里留下占位符。只输出
108
+ 这些内容(仅有工具调用、空的 reasoning 标记等)的助手轮次根本不产生 `msg-*` 条目
109
+ ——没有任何值得记住的实质内容。插件来源的消息被跳过——避免反馈回路。默认完全不写
110
+ 工具调用台账;设 `hypatia-auto-memory.collector.toolLedger: true` 可为**有**
111
+ 文本/图片内容的助手消息记录一份精简台账(`1. \`read\` ×5 — ✅ 1.2s total`)。无论
112
+ 哪种模式,**工具输出本身永远不会存入**日志层。
113
+
114
+ - **日志层不嵌入。** `msg-*` 和 `session-*` 条目以及 `belongTo` / `summary` 边都以
115
+ `embed: false` 写入(hypatia #26):已存储、可全文检索、可被 JSE 找到,但不生成
116
+ 向量。协议设计了沿 `summary` 边从摘要精确下钻的召回方式,而不是按语义命中原始
117
+ 消息——嵌入后,一条原始消息会因为它攥着原话而排在被蒸馏出来的知识之前。在测量过
118
+ 的 shelf 上,这一层占条目的 85%、三元组的 77%,每条都要在对话热路径上跑一次前向。
119
+ 摘要和工作单元照旧嵌入。三元组没有 tags,所以 shelf 的 `embedding.skip_tags` 够不
120
+ 到这些边;插件每次写入都会说明这一点。在没这个 flag 的二进制上,传输会丢弃它,
121
+ 一切都照旧嵌入。
122
+
123
+ span 在 `turn/end` 处切分,并且——对于超过 `flushWindowMs` 还在跑的 turn——在
124
+ `step/end` 处切分,从不在任意时刻切。DSH 在**运行它所请求的工具之前**先追加
125
+ `assistant/message`,等工具全部跑完才追加 `step/end`,所以一个 span 始终包含完整
126
+ 的 step,没有条目会缺少它自己的工具结果。DSH 压缩在 `compaction/prune` 之后重新
127
+ 追加的工具结果是旧结果的副本,不会被重复计数。
128
+
129
+ 条目按**稠密消息序号**命名,而不是 session-log seq:DSH 给每个流式 token delta 都
130
+ 分配一个 seq,导致 seq 编号产生稀疏键空间,破坏了 `$not-summaried` 的 FIFO 分批。
131
+ 序号从不可变日志前缀重新计数,所以重放能复现相同的名字。
132
+
133
+ - **Queue** 在调度之前就持久化:会话内有序、`queue.concurrency` 跨会话限流、带退避
134
+ 的重试、以及启动时的进度水位回填。一个运行中吸收新工作的任务会被重新调度而不是
135
+ 丢弃。Writer 都是 get-before-create,并且会**重申它的边**,所以条目和它的三元组
136
+ 之间若崩溃,重放即可修复。
137
+
138
+ - **Consolidator** 在 `turn/end` 阈值满足时运行,并在 `session/disposed` 无条件再
139
+ 跑一次——否则一个短于 `checkEveryTurns` 轮的任务会被记录却永远变不成知识。
140
+ `turn/end` 携带原因:用户中断被视为任务边界并降低 token 门槛,而 blocked、failed
141
+ 或 truncated 的 turn 根本不是切记忆的地方。它的 transcript 与日志路径完全一致地
142
+ 脱敏并绝对化日期,超预算的 span 只巩固其**最旧**的条目,水位也只推进到那里,剩下
143
+ 的不会被打成已完成。
144
+
145
+ - **Cascade** 把十六个未归档的 tier-N 摘要归档为一条 tier-(N+1) 条目,用 hypatia 自带
146
+ 的 `$not-summaried`(它对 `statement.tail` 做反连接并按 `created_at ASC` 排序,
147
+ 免费得到 FIFO 分批)。这就是协议的 log₁₆(n) 归档:每一层只压缩上一层已经蒸馏过的
148
+ 内容。
149
+
150
+ - **工作单元是裁决出来的,不是猜出来的。** 候选来自 `similar`(唯一会报告距离的
151
+ 检索),且排除操作层——在具备该能力的二进制上(hypatia #35)通过查询里的
152
+ `--exclude-tags`;在没这个能力的二进制上,抓四倍的量再丢弃——再按距离上限过滤,
153
+ 然后由一次小型模型调用裁决 `duplicate | refines | extends | supersedes |
154
+ contradicts | unrelated`。矛盾会保留**两条**条目并记录 `supersedes`——记忆系统绝不
155
+ 能悄悄忘记它曾经相信过什么。
156
+
157
+ - **Housekeeping** 每次启动对进度表跑一遍。它重置那些 session 在 shelf 里已无
158
+ `msg-*` 条目的行——一个被清空或被替换的 shelf 否则会让该会话永久不被记录——并删除
159
+ DSH 已不存在的会话的行和任务。重置的代价是一次重新记录和一次重新巩固,所以两遍
160
+ 都只在有正面证据时才行动:shelf 查询失败或空的会话列表都不做任何改动。
161
+
162
+ 第三遍巩固那些被重启打断的内容。session-end 触发器活不过重启:DSH 在关闭时跑它的
163
+ 关闭路径(追加 `session/end-seed`),但那里排入的任何东西在进程消失前都来不及
164
+ 持久化——实测在一次真实重启中,storage 文件根本没写,会话回来时
165
+ `lastConsolidatedSeq: 0`。所以一个有已记录但从未巩固的尾部的行,会在下次启动时被
166
+ 排入队列,受同样的 `consolidation.minNewTokens` 下限约束,并且从行里读取而不是
167
+ 加载会话。没有这道门槛,每次重启都会为每个带尾部的会话花一次模型调用。
168
+
169
+ 这些任务随后从 **storage** 读取事件,而不只是从活 store。`session/created`——唤醒
170
+ 被延迟任务的信号——只在 Agent 实际运行的地方触发,所以一个已结束的会话可能再也不
171
+ 会发它,而 subagent 会话(侧栏甚至不列出)几乎肯定不会:有一个就曾跨重启停留在
172
+ `deferred` 状态,尽管它的 transcript 正开在屏幕上。`sessionPersistence.load()`
173
+ 返回同样的日志而不发布任何东西,执行器只用 `snapshotEvents`、
174
+ `inheritedEventCount`、`seq` 和 `header`,所以一个不会再被重新打开的尾部仍能变成
175
+ 知识。没有 `sessionPersistence` 的组合行为照旧:任务等待。
176
+
177
+ - **Recall** 在会话启动时预载项目/全局的 rules 和 taboos。除此之外什么都不推送,
178
+ 除了一行在 shelf 不是 `default` 时指明 shelf 名字,好让 Agent 自己的 `hypatia`
179
+ 调用去插件写入的地方找。检索由 Agent 通过随包技能完成,这正是
180
+ [`memory-nolinear.md`](https://github.com/tkliuxing/hypatia/blob/main/docs/memory-nolinear.md)
181
+ 为那些持有上下文、能调用工具的 Agent 所开出的方案;此前的每轮注入器还会因为从
182
+ `agent/pre-step` 返回而不调用 `next()`,否决掉 DSH 自己的运行时上下文段。
183
+
184
+ - **Auto-approve** 只回答 Agent 自己的 bash `hypatia` 调用的审批请求,而且仅限这些:
185
+ 可执行词必须是一个可信的 basename(去掉 `KEY=value` 前缀之后),并且命令在
186
+ **引号之外**不能有管道、重定向、串联或命令替换——一个引号内满是 `|` 和 `>` 的 JSE
187
+ 参数仍然合格。其余都交给人类。插件自己的写入从不走这条路。
188
+
189
+ Scope 是实测而非想当然:在 DSH 里,一个 bash 调用**只有**在沙箱拒绝、且模型带
190
+ `sandbox_permissions` 重试时才会发起审批请求。检索(`query`、`search`、`list`、
191
+ `knowledge-get`)在 `workspace-write` 沙箱内运行,无论有没有本插件都不会询问。这里
192
+ 回答的是 hypatia 的**写入**——`~/.hypatia` 在工作区之外,所以 `knowledge-create`
193
+ 之类会被拒绝、提权,然后每次用户说"记住这个"都会卡在弹窗上。
194
+
195
+ - **Skills** 随包附带:`hypatia-memory` 是本插件的变体(自动层写入、Agent 检索),
196
+ 外加 hypatia 的 `hypatia` CLI 参考(逐字节相同)和 `hypatia-dream`(打过补丁,见
197
+ *已知限制*)的 vendored 拷贝——因为移除 `dsh-hypatia` 否则会把它们一起带走。另一个
198
+ 插件注册同名技能会被放过并报告,因为 DSH 保留先发生的运行时注册。磁盘上的拷贝会被
199
+ 登记上去但在 Agent 会话里仍胜出——预设会在更近的一层加载磁盘技能——所以会连同目录
200
+ 一起报告。
201
+
202
+ ## 配置
203
+
204
+ 设置命名空间 `hypatia-auto-memory`(编辑 `settings.yaml` 或 Web 设置;所有字段可选,
205
+ 展示的是默认值):
206
+
207
+ ```yaml
208
+ hypatia-auto-memory:
209
+ enabled: true
210
+ binaries: [hypatia]
211
+ transport: mcp # 或 cli;没有 `mcp` 的二进制回退到 cli
212
+ shelf: default # 所有条目写入/查找的地方;启动时读取(见下)
213
+ autoApprove: true # 批准 Agent 自己的纯 `hypatia …` bash 调用
214
+ collector:
215
+ enabled: true
216
+ maxAssistantChars: 8000 # 每条消息在截断标记前的上限
217
+ maxUserChars: 32000 # 用户输入或粘贴的内容,同上
218
+ toolLedger: false # 默认不写工具调用台账;true 才为有内容的助手消息记录
219
+ consolidation:
220
+ enabled: true
221
+ # 每次尝试(含重试)轮换到下一条已选路由。
222
+ models: [] # 选择一条或多条 { provider, model } 路由
223
+ maxInputTokens: 16000 # transcript 上限(chars/4 估算)
224
+ maxOutputTokens: 2000
225
+ timeoutMs: 120000
226
+ checkEveryTurns: 5 # 两次接受任务之间的最小轮数
227
+ minNewTokens: 3000 # 触发所需的最小未巩固 token 数
228
+ maxWorkUnitsPerRun: 3
229
+ adjudicate: true # 判断新工作单元与邻近记忆的关系
230
+ dedupMaxDistance: 0.45 # 值得裁决的候选的余弦距离上限
231
+ dedupCandidates: 5
232
+ cascade:
233
+ enabled: true
234
+ batchSize: 16 # 归档上一层之前,每层的条目数
235
+ queue:
236
+ concurrency: 1 # 并行会话数
237
+ maxAttempts: 3
238
+ retryDelayMs: 5000
239
+ # 不是分批旋钮。span 在 turn/end 切分;对一个跑这么久的 turn,
240
+ # 下一个 step/end 会把已完成的部分刷出。
241
+ flushWindowMs: 120000
242
+ recall:
243
+ enabled: true
244
+ preloadRulesTaboos: true
245
+ housekeeping:
246
+ reconcileOnStartup: true # 重置 session 在 shelf 里已无 msg-* 的行
247
+ pruneVanishedSessions: true # 删除 DSH 已不存在的会话的行和任务
248
+ backfillConsolidation: true # 巩固会话内触发器从未到达的已记录尾部
249
+ ```
250
+
251
+ bundle 行上的 cordis config 块只承载技能打包:
252
+
253
+ ```yaml
254
+ - id: hypatia-auto-memory
255
+ config:
256
+ skills: true # 注册 hypatia-memory + hypatia + hypatia-dream
257
+ skillsDir: /abs/path # 覆盖打包的 skills/ 目录
258
+ ```
259
+
260
+ `enabled` 和 `autoApprove` 在插件启动时读取,改动二者需要重载 profile;其它开关
261
+ 立即生效。
262
+
263
+ ### 选择 shelf
264
+
265
+ `shelf` 指定所有条目写入、所有查找读取的 hypatia shelf——记录、巩固、级联、
266
+ rules/taboos 预载、启动清理。设置卡片把它做成 `hypatia list` 所报告内容的下拉框
267
+ (约每分钟刷新一次,每次设置变更后也刷新),并标注已注册但未连接的 shelf。
268
+
269
+ - **像 `enabled` 一样,profile 重载后才生效。** 运行中的队列会继续写到它开始的地方;
270
+ 设置变更时日志会说明。
271
+ - **进度按 shelf 保存。** 水位和排队的任务按 shelf 存储,所以新选的 shelf 会让每个
272
+ 会话从零开始,而切回去会恰好从该 shelf 上次停下的地方继续——不会把已有的内容重新
273
+ 记录或重新巩固进某个 shelf。代价落在新 shelf 上:每个活会话从它的开头开始记录——
274
+ 其它会话下次活跃时也一样——并重新巩固,每个 span 一次模型调用。为另一个 shelf
275
+ 排队的工作原样等待,直到再次选中它,清理(修剪消失会话、失败任务)也只碰当前
276
+ shelf 的行。`default` 保留该设置出现之前写的行。
277
+ - **Agent 会跟随。** 当 shelf 不是 `default` 时,会话种子会告诉 Agent 在自己的
278
+ `hypatia` 命令上带 `--shelf <name>`;随包的 `hypatia-memory` 技能同样说明,
279
+ `hypatia-dream` 在请求没指定时整理那个 shelf。
280
+ - **缺失的 shelf 在启动时记录,而非拒绝:** 写入它会像其它 hypatia 失败一样失败并
281
+ 重试,直到它被连接(`hypatia connect <dir> --name <name>`)。走 MCP 时,该失败会
282
+ 重启 server,所以 `connect` 之后的重试能到达那个 shelf。
283
+
284
+ 列表以第二个只读设置命名空间 `hypatia-auto-memory-shelves` 到达浏览器,宿主在列表
285
+ 变化时重新注册它。没有卡片认领它,所以它不渲染在任何地方;也从不写入它。
286
+
287
+ ## 运维清单
288
+
289
+ 1. 一个 turn 结束后,条目在几秒内出现:
290
+ `hypatia knowledge-get msg-<sessionId>-<n>`(`n` 从 0 开始数消息)
291
+ 2. 水位在 state domain 里;失败的任务连同最后一次错误留在 `tasks` 表里供检查。
292
+ 3. 写入中途重启是安全的:已存储的消息被跳过(get-before-create);未覆盖的范围从
293
+ 水位重新排队。
294
+ 4. 检查路由告警:没选 `models` 的巩固只记录一次告警,其余时间静默。
295
+ 5. 后台调用实际用了哪个模型,逐次记在
296
+ `~/.dsh/storages/hypatia_auto_memory_diag.json`(`tables.model_calls`,按 `seq`
297
+ 倒序,上限 100 条)。设置卡片里的列表不是答案:一个进程内游标被 span 摘要、
298
+ 裁决和 cascade 三者共用,所以相邻几次调用是轮流换模型的。`outcome` 为
299
+ `pending`(在飞行中,或进程中途死了)、`ok`(产出了可用结果)、`incomplete`
300
+ (调用结束了但没产出——撞输出上限、被中止、回复解析不出来)或 `error`(调用本身
301
+ 抛错);后两种都消耗了游标且没有写入任何条目。`detail` 是 finish kind、固定标签
302
+ 或 provider 的错误消息,截断到 200 字符——绝不放消息正文,也绝不放模型输出。
303
+ 6. `hypatia backfill --status` 报告 shelf 的嵌入债务。用当前的 hypatia,记录一个
304
+ turn 不会增加债务;只有摘要和工作单元在下一次 flush 前处于待处理。`hypatia scope
305
+ list --count` 显示在用的 scope——本项目 `msg-*` 条目所带的那个,就是会话种子读取
306
+ 的那个。
307
+ 7. 卸载:`dsh plugin --profile web remove dsh-hypatia-auto-memory`——hypatia 条目
308
+ 本身留在 `~/.hypatia/`。
309
+
310
+ ## 故障模式
311
+
312
+ | 症状 | 原因 / 处理 |
313
+ |---|---|
314
+ | 聊完没有条目 | `enabled: false`、缺少 `hypatia` 二进制、或 collect fiber 处于 PENDING(需要基础组合里的 `sessions`、`storageDomain`、`subprocess`、`settings`)——查 profile 日志 |
315
+ | 重启后一整轮什么都没记录 | storage domain 因为某条已存记录不符合 schema 而拒绝打开。storage service 在读时校验、不在写时校验,所以一次坏写入只会在下次启动时暴露——而一条坏记录会拖垮整个 domain。在 profile 日志里找 `startup failed` / `does not match its schema`。缺 `error` 的任务行现在已默认化;其它坏行则停 DSH、从 `~/.dsh/storages/hypatia_auto_memory.json` 删除它再重启 |
316
+ | 条目只在 turn 结束后才出现 | 设计如此:span 在 `turn/end` 切分,或当 turn 跑得比 `flushWindowMs` 久时在第一个 `step/end` 切分,所以条目不会缺它自己的工具结果 |
317
+ | 有记录,没摘要 | `consolidation.models` 为空或无效——首次触发时告警一次 |
318
+ | 有摘要但没有 `sum2-*` | 该项目里未归档的 tier-1 摘要还不足 `cascade.batchSize` 条 |
319
+ | 工作单元没有关系 | shelf 没有嵌入模型(`similar` 失败),所有候选都超过了 `dedupMaxDistance`,或裁决调用本身没有产出裁决——`model_calls` 里表现为 `incomplete` / `max-tokens`,也就是开启了思考的路由把整个输出预算花在推理上、还没开始作答就撞了上限。现在裁决会先确认适配器声明了 `off`,再对该路由请求 `reasoningEffort: 'off'`;对无法接受它的路由则把上限提到 1024。自 hypatia #19 起,本地模型在 `~/.hypatia/models/<org>/<name>`(或 Hugging Face 缓存)里找,不再挨着 shelf:一个以前能回答 `similar`、现在说 `is not installed` 的 shelf,需要 `hypatia model install <model>`,或 `hypatia model register <model> <dir>` 指向它已有的文件 |
320
+ | `similar` 仍返回 `msg-*` 行 | 它们是在本插件让日志层退出嵌入之前写的,或由一个没有 `--no-embed` 的 hypatia 写的。在 shelf 的 `shelf.toml` 里用 `embedding.skip_tags = ["message"]` 加一次 `hypatia backfill` 一次性收回知识向量(比该 key 旧的二进制会拒绝打开 shelf,所以先升级所有共享它的二进制)。不要把 `session` 加进那个列表:`skip_tags` 匹配任何带该 tag 的条目,而人们写的关于会话的知识也带它——在试过的 shelf 上就有一条这样的条目丢了向量。插件自己的 `session-*` 节点很少,现在已逐次写入退出。三元组没有 tags 也没有 update 命令,所以之前写的 `belongTo` / `summary` 边保留向量 |
321
+ | 任务处于 `deferred` 状态 | live store 和 persistence 都没能提供它的会话。不是错误:它不消耗尝试次数,一旦二者之一能提供就运行。通常 storage 立即回答——只有组合里没有 `sessionPersistence`,或它的读取失败(日志里找 `could not be read`)时才会持久化这个状态 |
322
+ | 任务处于 `failed` 状态 | 一次 hypatia 或模型错误在 `maxAttempts` 之后持久化了。失败的 `log-message` 记录在下次启动时被修剪,因为水位会重新推导它们的范围;其它类型保留供检查——删掉一条好让下一个触发器重新创建它 |
323
+ | 水位说已记录,但 shelf 里没条目 | shelf 在记录后被重置或切换。启动时处理:`housekeeping.reconcileOnStartup` 重置任何 session 已无 `msg-*` 的行,该会话下次活跃时从头重新记录——并重新巩固。shelf 查询失败则不动该行。一个所有消息都被故意删除的会话无法区分,会被重新记录;如果这要紧,关掉这个开关 |
324
+ | 警告 `hypatia mcp unavailable: …; using the hypatia CLI until the profile reloads` | 二进制早于 `hypatia mcp`(消息会引用它的 `unrecognized subcommand`)、缺本插件调用的某工具、或握手返回错误。一切继续走 CLI;升级 hypatia 并重载 profile 以使用 MCP |
325
+ | 每次调用都失败 `hypatia mcp exited …` 或 `timed out` | 二进制能启动但它的 MCP server 起不来——`binaries` 里一个改了 clap 退出码或措辞的包装脚本不会被识别为旧二进制。设 `transport: cli` |
326
+ | 改了 `shelf` 之后每次写入都失败 | shelf 没注册或没连接——启动时记录 `shelf "<name>" is not registered`。连接它(`hypatia connect <dir> --name <name>`)并重载 profile,或换一个 |
327
+ | Agent 在 `default` 里搜,而插件写在别处 | 一份磁盘上的 `hypatia-memory`(见下)替换了随包技能,或 recall 被禁用,所以没有东西告诉 Agent 用哪个 shelf |
328
+ | 手动乱改后出现重复 `msg-*` | 在 hypatia 里删掉该条目,并在 state domain 里把该会话的 `lastLoggedSeq` 调低——回填会重新创建它一次 |
329
+ | Agent 自己的 `hypatia` 写入仍要审批 | `autoApprove: false`(改动需重载 profile)、命令在引号外有管道/重定向/串联、或首词不是 `binaries` 之一——按设计只回答纯调用。读取根本不会走到审批,所以那里没有要修的 |
330
+ | Agent 拿到了一份教它手动记录消息的记忆协议 | 另一个插件先注册了 `hypatia-memory`(profile 里还有 `dsh-hypatia`),或磁盘上有 `hypatia-memory`——通常是 `hypatia skill install --agent codex` 写的 `~/.agents/skills/hypatia-memory`。Agent 预设会在比插件更近的一层加载磁盘技能,所以它在会话里胜出,尽管技能中心可能列出本插件。删掉启动告警指出的那份拷贝 |
331
+ | 不知道实际用了哪个模型 | 设计上就是轮流的:span 摘要、裁决、cascade 共用一个进程内游标,所以相邻调用在 `consolidation.models` 里交替。每次尝试都落在 `~/.dsh/storages/hypatia_auto_memory_diag.json`(`model_calls`),带 purpose、provider/model、outcome 和耗时。usage ledger 答不了这个问题——它只折算 Agent 的 turn(`assistant/message`),看不见插件直连的 `llm.stream`。`pending` 行表示调用仍在进行中,或进程在调用中途死了:记录在调用前先落盘、调用结束后回填 |
332
+ | 启动告警从不出现在终端 | `dsh web` 不挂日志导出器,所以插件任何级别的日志行都没地方去;控制台导出器的默认阈值也会丢掉警告(warn 是 2 级,高于 info 的 1 级)。临时挂一个 logger,如 `dsh-logbook` 或 `dsh-boot-doctor` 来读它们 |
333
+ | 项目 scope 看起来不对 | Scope = 会话 cwd 的 git 顶层目录的 basename(`git rev-parse --show-toplevel`);git 找不到工作树或答不上(未安装、或某 repo 被判定不安全)时,则是 cwd 本身的 basename。一次慢于 3 秒的 `rev-parse` 会让那个会话留在 cwd 自己的名字上。两个同名 checkout 按设计共享 scope;linked worktree 按它自己的目录而非主 checkout 的目录定 scope;git 会解析符号链接,所以通过一个与目标不同名的链接打开的 checkout 会得到目标的名字。一个会被 hypatia 改写的名字会先归一化:位于 `/` 的会话定 scope 为 `/`,逗号变 `_`,去掉首尾空白。这些修复之前写的条目留在原处:子目录会话写在那个目录名下(`repo/src` 写在 `src` 下——git 根从未被读取)、位于 `/` 的会话无 scope、`a,b` 同时写在 `a` 和 `b` 下、`foo,` 写在 `foo` 和全局下。带空白的名字被去空白存储,这也正是查询现在所请求的。没有任何东西迁移它们;`knowledge-update --scopes` 可移动单条并保留其 `created_at`。`hypatia scope list --count`(hypatia #30)显示每种在用的拼写及其条目数 |
334
+
335
+ ## 已知限制
336
+
337
+ 刻意为之,依赖它们之前值得了解:
338
+
339
+ - **没有话题切换检测。**
340
+ [`memory-nolinear.md`](https://github.com/tkliuxing/hypatia/blob/main/docs/memory-nolinear.md)
341
+ 把话题切换列为最强的会话切分信号,排在任务边界和时间间隔之上。检测它需要每个 turn
342
+ 一次模型调用,这与本插件的核心取舍(主会话零模型工作)相悖,所以未实现。任务边界
343
+ (`turn/end` 原因)和会话关闭是被尊重的;一个覆盖三个无关话题的会话会被总结成一条。
344
+ - **摘要名是机械的。** 协议要求从内容里提取描述性名字。模型写的标题不能做 key——
345
+ 重试时可能措辞不同、悄悄分叉条目——所以 key 保持可推导(`sum-<session>-<from>-<to>`、
346
+ `sum<N>-<digest>`),描述性标题放在正文的第一个标题里。
347
+ - **没有宿主摘要就没有 `session-<id>` 节点。** 协议禁止凭空捏造,DSH 也不发自己的
348
+ session-summary 事件;节点在出现 `session/title` 或 `compaction/summary` 时由它们
349
+ 构建。
350
+ - **插件尚未使用 `knowledge-update`。** hypatia 在 #20 加入它(保留 `created_at`、
351
+ 丢弃旧向量并在下次 flush 时重新嵌入),但更旧的二进制没有,所以插件仍从不在原地
352
+ 编辑条目:第二个会话标题不会替换第一个,工作单元也从不重写。
353
+ - **重复的 `knowledge-create` 靠错误文本识别。** hypatia 对知识条目没有 upsert,所以
354
+ `hypatia-cli.js` 识别 `UNIQUE constraint failed:` / `duplicate key` 使重放幂等。
355
+ `statement-create` 自 hypatia #20 起自身幂等(重复执行退出 0 并打印 `Statement
356
+ already exists`);更旧的二进制走同样的错误文本路径。匹配被限制在一个函数内,
357
+ `test/integration` 接受两种 statement 行为。
358
+ - **已嵌入的仍是嵌入的。** 日志层在写入时退出向量索引,所以这个版本之前写的、或
359
+ 由一个没有 `--no-embed` 的 hypatia 写的条目和边保留它们的向量,会继续出现在
360
+ `similar` 里。`skip_tags` + `backfill` 收回知识条目的(见*故障模式*);边除了删除
361
+ 重建没有收回路径,插件不做这件事。
362
+ - **裁决和去重需要嵌入模型。** 在没有嵌入模型的 shelf 上,`similar` 直接失败,工作
363
+ 单元以无关系的方式存储——绝不会被丢弃。
364
+ - **启动回填读取活会话。** 一个已持久化但未加载的会话的缺口,在它下次加载时被补上。
365
+ - **级联在批次中途崩溃会移动分组边界。** 已连接的成员离开该层的未归档集合,所以
366
+ 重试会归档另一批。每样东西仍恰好归档一次;只是分组不再是第一次尝试想要的。
367
+ - **三个随包技能里有两个是 vendored 拷贝。** `hypatia` 和 `hypatia-dream` 从
368
+ [hypatia 仓库](https://github.com/tkliuxing/hypatia) 的 `skills/` 拷贝而来,因为
369
+ 已发布的包够不到自身之外。没有任何东西刷新它们:上游原件变了就手工拷进来。
370
+ `hypatia` 逐字节相同。`hypatia-dream` 带两处本地补丁,刷新时必须重新打:
371
+ - **Shelf。** 请求里没给 shelf 时,它用会话种子指明的那个,只有种子没指明时才用
372
+ `default`。上游总是回退到 `default`,那会去整理一个本插件可能没写的 shelf(见
373
+ *选择 shelf*)。
374
+ - **图读取排除操作关系。** 两个 statement 查询都排除了 `summary` 和 `belongTo`,
375
+ 技能本就视其为受保护、从不作为证据。本插件为每条已记录消息、span 摘要和归档层
376
+ 写它们,所以在测量的 shelf 上它们占 1258 条三元组里的 971 条。算进读取的 10000
377
+ 行上限里,它们会让技能在真正审阅的三元组远未达到上限前就停在截断检查上。
378
+
379
+ 它的 `evals/evals.json` 为这两处补丁新增 case 9 和 10。
380
+ - **顶层的 `enabled: false` 在插件启动时读取**;实时切换需要重载 profile,而各特性
381
+ 开关立即生效。
382
+
383
+ ## 目录结构
384
+
385
+ ```
386
+ dsh-hypatia-auto-memory/
387
+ ├── package.json # bundle + client manifest、依赖
388
+ ├── cordis.patch.yml # 插入 id=hypatia-auto-memory 的 bundle 层
389
+ ├── tsconfig.json # 浏览器 TS/TSX 类型检查配置
390
+ ├── tsconfig.build.json # client bundle 的声明产物
391
+ ├── tsdown.config.ts # 浏览器 CJS 工厂构建
392
+ ├── src/
393
+ │ ├── index.js # fiber 组合(collect + 可选子模块)
394
+ │ ├── config.js # 设置命名空间、默认值、热更新
395
+ │ ├── shelf.js # 每 shelf 表视图、`hypatia list`、shelf 清单
396
+ │ ├── state.js # storageDomain 规格 + 进度助手
397
+ │ ├── collector.js # session/event 过滤、台账、回填
398
+ │ ├── content-policy.js # 脱敏、日期、上限、slug(纯函数)
399
+ │ ├── queue.js # 带重试/dispose 的持久化每会话队列
400
+ │ ├── hypatia-client.js # 传输切换:MCP、CLI 回退
401
+ │ ├── hypatia-mcp.js # 私有 `hypatia mcp` 连接 + 结果映射
402
+ │ ├── hypatia-cli.js # 仅 argv 的子进程包装
403
+ │ ├── writer.js # 幂等 get-before-create 写入
404
+ │ ├── consolidator.js # 阈值、prompt、llm.stream、校验
405
+ │ ├── cascade.js # log₁₆(n) 分层摘要归档
406
+ │ ├── model-log.js # 每次尝试实际用了哪个模型的有界记录
407
+ │ ├── recall.js # 会话启动时的 rules/taboos 预载
408
+ │ ├── auto-approve.js # 批准 Agent 自己的纯 bash hypatia 调用
409
+ │ ├── skills.js # 随包技能注册(从不遮蔽其它提供者)
410
+ │ ├── status.js # 计数器 + 结构化日志
411
+ │ └── client/ # 浏览器设置卡片
412
+ │ ├── index.tsx # client 插件入口 + slot 注册
413
+ │ ├── SettingsCard.tsx
414
+ │ ├── shelves.ts # 来自清单命名空间的 shelf 下拉选项
415
+ │ └── slot-contract.ts
416
+ ├── lib/
417
+ │ └── client.js # 构建好的浏览器工厂(提交此文件)
418
+ ├── skills/
419
+ │ ├── hypatia-memory/ # 本插件变体(检索由 Agent 负责)
420
+ │ ├── hypatia/ # 从 hypatia 的 skills/ vendored
421
+ │ └── hypatia-dream/ # 从 hypatia 的 skills/ vendored,带本地补丁
422
+ ├── scripts/
423
+ │ └── it-shelf.sh # 手动 CLI 探测用的一次性 shelf
424
+ ├── test/ # node:test 单元测试(npm test)
425
+ │ └── integration/ # 针对真实 hypatia 的契约(npm run test:integration)
426
+ ├── docs/
427
+ │ ├── jev-decision-model-research.md # Jev/Laya 决策模型调研结论
428
+ │ └── jev-decision-model-research-scripts/ # 对应实验脚本
429
+ └── README.md
430
+ ```
package/lib/client.js CHANGED
@@ -108,46 +108,46 @@ window.__ModuleLoader__.load({
108
108
  document.head.appendChild(tag);
109
109
  }
110
110
  var SettingsCard_module_css_default = {
111
- "hint": "YVNYya_hint",
112
- "field": "YVNYya_field",
113
- "readOnly": "YVNYya_readOnly",
114
- "select": "YVNYya_select",
115
- "switchThumb": "YVNYya_switchThumb",
116
- "pairedFields": "YVNYya_pairedFields",
117
- "fieldHead": "YVNYya_fieldHead",
118
- "modelName": "YVNYya_modelName",
119
- "status": "YVNYya_status",
120
- "switchOn": "YVNYya_switchOn",
121
- "headText": "YVNYya_headText",
122
- "chevron": "YVNYya_chevron",
123
- "footer": "YVNYya_footer",
124
- "unavailable": "YVNYya_unavailable",
125
- "modelSelection": "YVNYya_modelSelection",
126
- "chevronOpen": "YVNYya_chevronOpen",
111
+ "advancedHint": "YVNYya_advancedHint",
127
112
  "body": "YVNYya_body",
128
- "error": "YVNYya_error",
129
- "modelGroup": "YVNYya_modelGroup",
130
113
  "card": "YVNYya_card",
131
- "warning": "YVNYya_warning",
132
- "switch": "YVNYya_switch",
133
- "notice": "YVNYya_notice",
134
- "limitsTitle": "YVNYya_limitsTitle",
135
- "reset": "YVNYya_reset",
114
+ "cardOpen": "YVNYya_cardOpen",
115
+ "catalogError": "YVNYya_catalogError",
116
+ "chevron": "YVNYya_chevron",
117
+ "chevronOpen": "YVNYya_chevronOpen",
136
118
  "description": "YVNYya_description",
119
+ "error": "YVNYya_error",
120
+ "field": "YVNYya_field",
121
+ "fieldHead": "YVNYya_fieldHead",
122
+ "footer": "YVNYya_footer",
137
123
  "group": "YVNYya_group",
138
- "name": "YVNYya_name",
139
- "advancedHint": "YVNYya_advancedHint",
140
- "models": "YVNYya_models",
141
- "catalogError": "YVNYya_catalogError",
124
+ "groupTitle": "YVNYya_groupTitle",
125
+ "headText": "YVNYya_headText",
126
+ "header": "YVNYya_header",
127
+ "hint": "YVNYya_hint",
128
+ "input": "YVNYya_input",
142
129
  "label": "YVNYya_label",
143
- "providerName": "YVNYya_providerName",
144
- "cardOpen": "YVNYya_cardOpen",
130
+ "limitsTitle": "YVNYya_limitsTitle",
131
+ "model": "YVNYya_model",
132
+ "modelGroup": "YVNYya_modelGroup",
133
+ "modelName": "YVNYya_modelName",
134
+ "modelSelection": "YVNYya_modelSelection",
135
+ "models": "YVNYya_models",
136
+ "name": "YVNYya_name",
137
+ "notice": "YVNYya_notice",
138
+ "pairedFields": "YVNYya_pairedFields",
145
139
  "pending": "YVNYya_pending",
140
+ "providerName": "YVNYya_providerName",
141
+ "readOnly": "YVNYya_readOnly",
142
+ "reset": "YVNYya_reset",
146
143
  "route": "YVNYya_route",
147
- "groupTitle": "YVNYya_groupTitle",
148
- "model": "YVNYya_model",
149
- "input": "YVNYya_input",
150
- "header": "YVNYya_header"
144
+ "select": "YVNYya_select",
145
+ "status": "YVNYya_status",
146
+ "switch": "YVNYya_switch",
147
+ "switchOn": "YVNYya_switchOn",
148
+ "switchThumb": "YVNYya_switchThumb",
149
+ "unavailable": "YVNYya_unavailable",
150
+ "warning": "YVNYya_warning"
151
151
  };
152
152
  //#endregion
153
153
  //#region src/client/locales.ts
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "dsh-hypatia-auto-memory",
3
- "version": "0.2.0",
3
+ "version": "0.4.0",
4
4
  "description": "Event-driven Hypatia memory for DSH: automatic conversation logging via session events, queued background consolidation (span summaries + work-unit extraction) with a dedicated model route, and recall injection — zero per-call model work in the main session.",
5
5
  "type": "module",
6
6
  "main": "src/index.js",