@modusensus/dsh-mneme 0.9.2 → 0.9.3
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 +8 -7
- package/docs/CONFIGURATION.md +3 -2
- package/docs/WORKFLOWS.md +70 -3
- package/lib/api.js +119 -1
- package/lib/capabilities.js +1 -1
- package/lib/client.js +98 -7
- package/lib/config.js +20 -2
- package/lib/index.js +15 -0
- package/lib/pipeline-nudge-auto.js +126 -0
- package/lib/pipeline-nudge.js +337 -28
- package/lib/pipeline-runs.js +1 -1
- package/lib/pipeline.js +4 -4
- package/lib/scope.js +30 -1
- package/lib/settings.js +5 -1
- package/package.json +1 -1
- package/src/api.js +119 -1
- package/src/capabilities.js +1 -1
- package/src/config.js +20 -2
- package/src/index.js +15 -0
- package/src/pipeline-nudge-auto.js +126 -0
- package/src/pipeline-nudge.js +337 -28
- package/src/pipeline-runs.js +1 -1
- package/src/pipeline.js +4 -4
- package/src/scope.js +30 -1
- package/src/settings.js +5 -1
- package/test/api-nudge-route.test.js +259 -0
- package/test/api.test.js +6 -1
- package/test/client.test.js +39 -1
- package/test/pipeline-nudge-auto.test.js +224 -0
- package/test/pipeline-nudge.test.js +161 -6
- package/test/scope-optional-service.test.js +110 -0
- package/test/session-usage.test.js +11 -2
package/README.md
CHANGED
|
@@ -5,7 +5,7 @@
|
|
|
5
5
|
[](https://www.npmjs.com/package/@modusensus/dsh-mneme)
|
|
6
6
|
[](LICENSE)
|
|
7
7
|
[](https://github.com/awesome-dsh-plugin/awesome-dsh-plugin)
|
|
8
|
-
[](https://github.com/slow-stack/mneme)
|
|
9
9
|
[](https://github.com/slow-stack/mneme/actions)
|
|
10
10
|
[](https://nodejs.org)
|
|
11
11
|
[](https://www.npmjs.com/package/@modusensus/dsh-mneme)
|
|
@@ -64,7 +64,7 @@ dsh web
|
|
|
64
64
|
| `memory_register_document` | 登记 agent 产长文档的指针行(#230:摘要 + `doc_path` + evidence 落 `document` 类型;正文留在磁盘、由 agent 自管,注册口唯一,旁路写入被拒) |
|
|
65
65
|
| `memory_runtime` | 本地推理运行时的状态 / 供给 / 校验(v0.7.32,自管运行时三档取件) |
|
|
66
66
|
|
|
67
|
-
### 工作流(M.O
|
|
67
|
+
### 工作流(M.O.,v0.9 beta)
|
|
68
68
|
|
|
69
69
|
记忆库答的是「这件事**为什么**这么办」,答不了「这件事**到底怎么跑**」。v0.9 把后者从记忆库里的一行文字变成项目里可提交、可 review、可被 CI 读的一份文件——一条工作流一个 JSON:
|
|
70
70
|
|
|
@@ -267,13 +267,14 @@ v0.3.0 起新增**记忆基因**层:从记忆里抽取**命名实体**、**带
|
|
|
267
267
|
|
|
268
268
|
> 完整版本说明见 [CHANGELOG](CHANGELOG.md) 与各版本 [GitHub Release](https://github.com/slow-stack/mneme/releases);当前特性以本 README 正文与[配置表](#-配置)为准。早期版本中的实验性功能(Wiki-Link、tag 系统等)已在 v0.7.11 移除,详见 CHANGELOG 对应条目。
|
|
269
269
|
|
|
270
|
-
🧬 Gene → 🛡️ 审计加固 → 💤 Sleep Mode → 🕸️ 召回融合与图谱 → ✨ 面板体验 → 🌡️ 自进化记忆 → 🔐 作用域隔离 → 🌐 MCP 生态 → 🧭 M.O
|
|
270
|
+
🧬 Gene → 🛡️ 审计加固 → 💤 Sleep Mode → 🕸️ 召回融合与图谱 → ✨ 面板体验 → 🌡️ 自进化记忆 → 🔐 作用域隔离 → 🌐 MCP 生态 → 🧭 M.O.(项目级工作流,v0.9 beta)
|
|
271
271
|
|
|
272
272
|
| 版本 | 主题 |
|
|
273
273
|
|------|------|
|
|
274
|
-
| **v0.9.0**(2026-10-17) | M.O.(Modus Operandi
|
|
274
|
+
| **v0.9.0**(2026-10-17) | M.O.(Modus Operandi)beta:工作流本体住 `<项目>/.mneme/pipelines/`、候选分层与升格判据、面板清单与图同屏(`actionPipelineEnabled` 默认关);设置面板折叠线挪到「新手之外」并按人话重写 22 个条目名(#427);安全与供应链加固(39 条路由声明式鉴权、OSV 有漏洞即失败、发布带 provenance) |
|
|
275
275
|
| **v0.9.1**(2026-10-18) | M.O. 从「看得见」跑到「跑得出账」:执行窗口由 `/mo-run start` / `/mo-run stop` 显式划开、运行账落 `<项目>/.mneme/pipelines/<id>.runs.jsonl`(账本坏了与从没跑过严格分开)、归属只出候选、观察旁挂与脱节检出、步骤 `assert` / `optional`、R1/R2 提议;沉淀链从真实会话日志挖 draft 工作流(判据恒 `null`,答案只能由人补);人格定义页 v1 → v1.2(按 agent 寻址多卡,默认关);面板、向量与检查更新的一圈修复(#448 / #450 / #460);Release 正文改分层渲染 |
|
|
276
276
|
| **v0.9.2**(2026-10-19) | 让「看不见的失败」显形:功能开关的第三态「待重启」把「面板显示已改 ≠ 运行时已改」摆到界面上(#481);作用域级可逆闸门让一路记忆能被单独按下与松开、不必改配置重启(#471,默认关);巩固用的 `dreamMaxTokens` 默认值撞服务商上限、八天 180 次全失败,面板现在能改、超上限还会自动降级重试(#478);CLI 把 `--` 开头的值当新旗标的真实缺陷(一次 1/4096 概率的 CI 红揪出来的)、人格卡删除 × 永远不可见、自动蒸馏写入的行 100% 拿不到向量(#477) |
|
|
277
|
+
| **v0.9.3**(2026-10-20) | M.O. 从「挖得出来」到「会被叫去挖」:沉淀链算法自 v0.9.1 起就完整实现(多帧 zstd → 重复序列 → 缺席对照 → draft 草稿),运行时却没有任何调用点——机器挖得出来、却永远不会被叫去挖。这一版把归属与触发接上:按会话头 `cwd` 反查项目桶(桶名是路径的有损变形,查不到整跑跳过而不是降级成全局普查)、挂 `turn/end`、冷却 10 分钟、单次预算 4 秒、延到宏任务、一次一个项目、抛错只 `warn`,**只产提议不产草稿**(`actionNudgeEnabled`,beta,默认关);面板加「现在沉淀一次」,与自动跑同一条入口,区别只有它不改仓库;另修作用域反查自 v0.8.0 起每次都抛错走降级(#487)、`/nudge` 三处「没看全」被报成「确实没有」(#489 评审)、多帧 zstd 反证断言绑了 Node 整块解语义(#490),并把整个 M.O. 的「手法」改回「工作流」 |
|
|
277
278
|
| **v0.8.x** | 作用域隔离(agent / workspace 双维 + opt-in 硬过滤)、冲突队列人工裁决、生态化(stdio MCP server / 图召回轴 / 冷启动 / 注入截断) |
|
|
278
279
|
|
|
279
280
|
## 📦 安装
|
|
@@ -675,7 +676,7 @@ src/
|
|
|
675
676
|
├── api.js # HTTP 路由(Web 面板数据通道,含 /conflicts 冲突队列)
|
|
676
677
|
└── index.js # 插件接线
|
|
677
678
|
lib/ # src 的同步分发产物(npm run sync,prepack 钩子在发布时自动跑;一致性由 scripts/check-sync.js 在 CI 与 release.yml 校验——它只查不同步;唯一手写例外 lib/client.js——Web 面板 bundle,sync 不覆盖)
|
|
678
|
-
test/ #
|
|
679
|
+
test/ # 2257 个 node:test 测试(审计与三轴线压测不变量;src↔lib 一致性由 scripts/check-sync.js 发布闸门校验)
|
|
679
680
|
scripts/ # e2e-dsh.js 端到端演示 · stress-dsh.js 三轴线压测 · sync-lib.js 同步 · check-sync.js 发布闸门 · benchmark-recall.js / benchmark-embed.js / benchmark-rerank.js 基准 · sync-test-badge.mjs 测试徽章 · build-runtime-manifest.mjs 运行时清单
|
|
680
681
|
```
|
|
681
682
|
|
|
@@ -684,7 +685,7 @@ scripts/ # e2e-dsh.js 端到端演示 · stress-dsh.js 三轴线压
|
|
|
684
685
|
```bash
|
|
685
686
|
cd dsh-mneme
|
|
686
687
|
npm install # 安装 peer 依赖(以 devDependencies 形式,用于本地测试)
|
|
687
|
-
npm test # 运行
|
|
688
|
+
npm test # 运行 2257 个测试
|
|
688
689
|
npm run stress # 三轴线压测:长会话检索 / 冲突仲裁 / 多 Agent 并发(离线 mock LLM)
|
|
689
690
|
npm run sync # 把 src/ 同步到 lib/(发布时由 prepack 钩子自动执行)
|
|
690
691
|
```
|
|
@@ -702,7 +703,7 @@ npm run sync # 把 src/ 同步到 lib/(发布时由 prepack 钩子自动
|
|
|
702
703
|
- [实体结构化记忆设计](docs/ENTITIES.md)
|
|
703
704
|
- [语义增强架构](docs/SEMANTIC.md)
|
|
704
705
|
- [本地模型部署指南](docs/LOCAL_MODEL.md)
|
|
705
|
-
- [工作流(M.O
|
|
706
|
+
- [工作流(M.O.):本体住项目,证据住记忆库](docs/WORKFLOWS.md)
|
|
706
707
|
- [连续性抢救(#249 N3):触发器口径与事件契约](docs/CONTINUITY.md)
|
|
707
708
|
- [Sleep Mode:空闲四阶段深度维护](docs/SLEEP.md)
|
|
708
709
|
- [存储与回收:表结构、保留期、`dsh-mneme reclaim`](docs/STORAGE.md)
|
package/docs/CONFIGURATION.md
CHANGED
|
@@ -283,8 +283,9 @@
|
|
|
283
283
|
| `externalApiHost` | `127.0.0.1` | 绑定地址 | 改成非回环 = 把整个记忆库暴露给网络,自负其责 |
|
|
284
284
|
| `disableMemorySearch` | `false` | 对模型隐藏 `memory_search` 工具(v0.8.5) | 慢/轻量模型的工具往返节流;隐藏即不可调 |
|
|
285
285
|
| `disableMemoryArchive` | `false` | 对模型隐藏 `memory_archive` 工具 | 同上 |
|
|
286
|
-
| `actionPipelineEnabled` | `false` | M.O
|
|
286
|
+
| `actionPipelineEnabled` | `false` | M.O. 只读视图的门控:做法图谱 + 对话行结算卡 | opt-in beta;只是纯 UI 入口、不引入重型后台路径,故不进 `LIGHT_MODE_OFF`。写账另有独立的执行记账键(`/mo-run`),看图 ≠ 写账 |
|
|
287
287
|
| `actionRunEnabled` | `false` | M.O. 执行记账:`/mo-run` 开一次执行窗口,窗口内命令执行才可能被记成一条 run | opt-in beta;挂在热路径(每个会话事件都过一次监听),故 `LIGHT_MODE_OFF` 里有它 |
|
|
288
|
+
| `actionNudgeEnabled` | `false` | M.O. 自动沉淀:每轮结束(`turn/end`)后挖一次「这个项目重复走的动作」,结果落成 `memoryDir/proposals/<项目键>/` 里的提议,等人确认 | opt-in beta;三个 M.O. 键里最重的一条——挂 `session/event` 且要**同步解压 zstd 会话日志**(本机 118 份实测 16.2s,单次预算 `budgetMs`=4s 兜底),故 `LIGHT_MODE_OFF` 里有它。**永不自动落草稿**:`materialize` 恒 false,进仓库必须是人的显式动作 |
|
|
288
289
|
|
|
289
290
|
## 运行时与本地模型
|
|
290
291
|
|
|
@@ -316,7 +317,7 @@
|
|
|
316
317
|
`autoReindexOnBoot` · `hybridInject` · `injectGuidanceEnabled` · `searchSemanticDedup` ·
|
|
317
318
|
`selectiveInjectEnabled` · `bm25SearchEnabled` · `entityRecallEnabled` ·
|
|
318
319
|
`graphAnchoringEnabled` · `graphWeightEnabled` · `graphInjectHint` · `graphPassiveConfirm` ·
|
|
319
|
-
`actionRunEnabled` ·
|
|
320
|
+
`actionRunEnabled` · `actionNudgeEnabled` ·
|
|
320
321
|
`dreamNarrativeEnabled` · `documentMemoryEnabled` · `heatEnabled` · `continuityRescueEnabled` ·
|
|
321
322
|
`preInjectGate.enabled`(#380) · `distillRecords.enabled`(#399)
|
|
322
323
|
|
package/docs/WORKFLOWS.md
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
# dsh-mneme 工作流(M.O
|
|
1
|
+
# dsh-mneme 工作流(M.O.)——本体住项目,证据住记忆库
|
|
2
2
|
|
|
3
3
|
- **日期**:2026-10-15
|
|
4
4
|
- **状态**:🚧 第一刀(项目级读侧 + 条目清单;执行侧未做)
|
|
@@ -97,10 +97,14 @@ GET /api/dsh-mneme/pipeline?project=<绝对路径> # 该项目的清单(
|
|
|
97
97
|
GET /api/dsh-mneme/pipeline?project=…&id=<id> # 指定一条
|
|
98
98
|
|
|
99
99
|
GET /api/dsh-mneme/candidates?project=<绝对路径> # 该项目的**候选**(见 §9)
|
|
100
|
+
|
|
101
|
+
GET /api/dsh-mneme/nudge?project=<绝对路径> # 沉淀预览:只看不写(见 §10)
|
|
102
|
+
POST /api/dsh-mneme/nudge?project=<绝对路径> # 真跑一次:落提议(要 token)
|
|
100
103
|
```
|
|
101
104
|
|
|
102
|
-
|
|
103
|
-
`/candidates`
|
|
105
|
+
三条路由读的是**三处不同的东西**,别混:`/pipeline` 读定稿(住仓库),
|
|
106
|
+
`/candidates` 读候选(住本地),`/nudge` 是**产生**候选的那台机器(见 §10)。
|
|
107
|
+
分开是刻意的,理由分别见 §9 与 §10。
|
|
104
108
|
|
|
105
109
|
**`project` 必须是绝对路径**,否则回 `400 {"error":"project-not-absolute","detail":"…"}`。
|
|
106
110
|
忽略了它就会静默退成「全局」,而全局看起来一切正常——那正是这一版要修掉的那类错。
|
|
@@ -297,3 +301,66 @@ JSON,报错本身就是给人看的:`detail` 里是校验器**一次列全**
|
|
|
297
301
|
- **不做绕行的自动批准**:一律 R0/R1 记账先走、R2/R3 停下问人,没有「全都可以先走」
|
|
298
302
|
- **不把候选塞进注入路径**:它是草稿,不是知识
|
|
299
303
|
- **不做「图自动重写」**:图的变化一律经过提议 + 人批;回写是新版本,不是改旧版本
|
|
304
|
+
|
|
305
|
+
## 10. 自动沉淀:工作流从哪长出来
|
|
306
|
+
|
|
307
|
+
§1 说过「一条新工作流从哪来」在那之前一直是零代码。这一节就是那个来源。
|
|
308
|
+
|
|
309
|
+
```
|
|
310
|
+
turn/end → 这个项目自己的会话桶 → 挖重复动作 → memoryDir/proposals/<项目键>/
|
|
311
|
+
```
|
|
312
|
+
|
|
313
|
+
### 10.1 它写什么、不写什么
|
|
314
|
+
|
|
315
|
+
| 产物 | 住哪 | 谁批准 |
|
|
316
|
+
|---|---|---|
|
|
317
|
+
| **提议**(proposal) | `<memoryDir>/proposals/<项目键>/nudge-*.json` | 不需要批准,但**没人看就等于没有** |
|
|
318
|
+
| **草稿**(draft 工作流) | `<项目>/.mneme/pipelines/<id>.json` | 人显式要(`materialize: true`),自动跑**永不**落 |
|
|
319
|
+
| **定稿**(active) | 同上,换 `status` | 人批,机器永远不给 |
|
|
320
|
+
|
|
321
|
+
边界写在答复的 `boundaries` 里,跳过与跑完用的是**同一个常量**(`NUDGE_BOUNDARIES`)
|
|
322
|
+
——两条出口说两句不同的话,读的人就会以为其中一条忘了做:
|
|
323
|
+
|
|
324
|
+
```jsonc
|
|
325
|
+
{ "autoActivate": false, "autoAnswerCounterfactual": false,
|
|
326
|
+
"writesOnlyDrafts": true, "riskIsFloor": true, "crossProjectSupport": false }
|
|
327
|
+
```
|
|
328
|
+
|
|
329
|
+
### 10.2 归属:为什么不能拿全机会话去挖
|
|
330
|
+
|
|
331
|
+
会话日志在 DSH 侧按**工作区**分桶(`<DSH_HOME>/sessions/<桶>`),而桶名是路径的
|
|
332
|
+
**有损**变形、规则不在本仓。所以「这个项目的工作流」只有在拿到**这个项目自己的桶**
|
|
333
|
+
时才成立;归属靠会话头里的 `cwd` 反查(`resolveProjectSessionDir`)。
|
|
334
|
+
|
|
335
|
+
拿不到归属时这一跑**整跑跳过**(`skipped: true`)而不是降级成全局普查:后者会把
|
|
336
|
+
A 项目的动作算到 B 头上,而结果看起来完全正常(复核报告 §2.6 实测过)。
|
|
337
|
+
|
|
338
|
+
`reason` 的四种取值里,**`unreadable` 必须与 `no-bucket` 分开**:前者是「读不到」
|
|
339
|
+
(故障),后者是「确实没有」(关于这个项目的答案)。混起来等于把故障报成空结果。
|
|
340
|
+
端点层因此有两种状态码:`no-sessions-dir` / `unreadable` → **503**,其余 → **200**。
|
|
341
|
+
|
|
342
|
+
### 10.3 触发:为什么不是「读一次就沉淀一次」
|
|
343
|
+
|
|
344
|
+
| 控制 | 值 | 为什么是这个值 |
|
|
345
|
+
|---|---|---|
|
|
346
|
+
| 触发点 | `turn/end` | 一轮结束才算一个观测窗口,中途记的是半截 |
|
|
347
|
+
| 冷却 | `NUDGE_AUTO_MIN_INTERVAL_MS` = 10 分钟/项目 | **观测新鲜度**参数,不是性能参数:日志十分钟内长不出新重复模式 |
|
|
348
|
+
| 单次预算 | `NUDGE_AUTO_BUDGET_MS` = 4 秒 | 超了带着已读到的部分收工并标 `budgetExceeded`,**不重试、不补跑** |
|
|
349
|
+
| 执行时机 | `setTimeout(0)` | 解压会话日志是同步 I/O(本机 118 份实测 16.2 秒),压在 `turn/end` 那一帧上等于让回合收尾卡住 |
|
|
350
|
+
| 排队 | 一次只跑一个项目 | 同上,别把一帧拉长 |
|
|
351
|
+
|
|
352
|
+
**开与不开**:`actionNudgeEnabled`(默认关)。它与 `actionPipelineEnabled`(看图)、
|
|
353
|
+
`actionRunEnabled`(记账)是**三件独立能力**,开任何一个都不会顺带打开另一个。
|
|
354
|
+
它在 `LIGHT_MODE_OFF` 里——三个 M.O. 键里它最重(挂 `session/event` + 同步解压)。
|
|
355
|
+
|
|
356
|
+
### 10.4 为什么提议落盘要单独一个开关
|
|
357
|
+
|
|
358
|
+
`nudgeFromSessions` 原本只要算得出项目键就**无条件**往 memoryDir 里写提议。面板每刷
|
|
359
|
+
一次就 GET 一次,那样每刷一次就往记忆库里塞一批文件。所以有了 `persistProposals`
|
|
360
|
+
(默认 `true` 保持既有语义),预览路由显式传 `false`:
|
|
361
|
+
|
|
362
|
+
- `GET` / `HEAD` → `persistProposals: false`,**一个文件都不写**;
|
|
363
|
+
- `POST` / `PUT` → `persistProposals: true`,落提议。
|
|
364
|
+
|
|
365
|
+
这也是 `/nudge` 的 `policy` 是 `token-write` 而不是 `open` 的原因:GET 与
|
|
366
|
+
`/pipeline`、`/candidates` 同级(只读预览,免 token),POST 会写盘,写盘面一律受门控。
|
package/lib/api.js
CHANGED
|
@@ -18,6 +18,14 @@ import { loadRunsById, runsDir, RUNS_SUFFIX } from "./pipeline-runs.js";
|
|
|
18
18
|
import { observationDir, loadObservationsById } from "./pipeline-observations.js";
|
|
19
19
|
import { projectKey } from "./candidates.js";
|
|
20
20
|
import { readCandidateDoc } from "./candidates.js";
|
|
21
|
+
// M.O. 自动沉淀:/nudge 是 pipeline-nudge.js 的第一个运行时消费者(此前它只有测试在用,
|
|
22
|
+
// 是个孤儿模块——「不能自动沉淀工作流」的根因)。
|
|
23
|
+
import {
|
|
24
|
+
resolveProjectSessionDir,
|
|
25
|
+
nudgeForProject,
|
|
26
|
+
NUDGE_BOUNDARIES,
|
|
27
|
+
NUDGE_AUTO_BUDGET_MS
|
|
28
|
+
} from "./pipeline-nudge.js";
|
|
21
29
|
|
|
22
30
|
// headers:少数端点(/export 附件下载)需要追加 Content-Disposition 等响应头。
|
|
23
31
|
function sendJson(res, status, payload, headers = {}) {
|
|
@@ -1849,7 +1857,7 @@ export function createApi(ctx, service, settings, commands, embedder, semantic =
|
|
|
1849
1857
|
}
|
|
1850
1858
|
});
|
|
1851
1859
|
|
|
1852
|
-
// --- M.O.(Modus Operandi
|
|
1860
|
+
// --- M.O.(Modus Operandi)工作流 ---
|
|
1853
1861
|
// 只读端点,读的是**项目里**的 `<项目>/.mneme/pipelines/*.json`(文件头在
|
|
1854
1862
|
// src/pipeline.js),项目层优先、插件包里那份降级成全局层;投影成
|
|
1855
1863
|
// mneme.pipeline/v1 交给面板与 agent——两边读同一份,画面只是它的投影。
|
|
@@ -1992,6 +2000,116 @@ export function createApi(ctx, service, settings, commands, embedder, semantic =
|
|
|
1992
2000
|
}
|
|
1993
2001
|
});
|
|
1994
2002
|
|
|
2003
|
+
// --- M.O. 自动沉淀(nudge)---
|
|
2004
|
+
// 读+写两种方法同路:GET 是**预览**(不落盘),POST 是**真跑**(落提议、可落草稿)。
|
|
2005
|
+
// 为什么不全放 GET:GET 会被浏览器预取、被面板刷新反复调用,而这一跑要解压几十份
|
|
2006
|
+
// zstd 会话日志(同步 I/O,本机实测 16.2s)。预览走 GET 时才关掉提议落盘
|
|
2007
|
+
// (persistProposals:false)——否则每刷一次面板就往 memoryDir 里塞一批文件。
|
|
2008
|
+
//
|
|
2009
|
+
// policy 是 token-write 而不是 open:GET 免 token(与 /pipeline、/candidates 同级的
|
|
2010
|
+
// 只读预览),POST 要 token——它会写盘,写盘面一律受门控。
|
|
2011
|
+
register({
|
|
2012
|
+
kind: "exact",
|
|
2013
|
+
path: "/api/dsh-mneme/nudge",
|
|
2014
|
+
policy: "token-write",
|
|
2015
|
+
handler(req, res) {
|
|
2016
|
+
const method = String(req.method || "GET").toUpperCase();
|
|
2017
|
+
// 只认这四种。为什么必须在**读 body 之前**拦:下面 persistProposals 是按
|
|
2018
|
+
// 「不是 GET/HEAD」算的,于是 PATCH/DELETE 会被当成写动作,走进 run() 落提议;
|
|
2019
|
+
// 而它又不像 PUT/POST 那样读 body,等于一个不需要 body 的静默写盘口。
|
|
2020
|
+
// 这条 PR 自己的卖点是「GET 只预览、写动作一律要 POST」,那就得把口子堵死
|
|
2021
|
+
// (v0.9.3 评审抓出:带 token 的 PATCH/DELETE 会往 memoryDir 里写提议)。
|
|
2022
|
+
const allowed = method === "GET" || method === "HEAD" || method === "POST" || method === "PUT";
|
|
2023
|
+
if (!allowed) {
|
|
2024
|
+
sendJson(res, 405, { error: "method-not-allowed", allow: "GET, HEAD, POST, PUT" }, { Allow: "GET, HEAD, POST, PUT" });
|
|
2025
|
+
return;
|
|
2026
|
+
}
|
|
2027
|
+
const run = (body) => {
|
|
2028
|
+
try {
|
|
2029
|
+
const url = new URL(req.url, "http://localhost");
|
|
2030
|
+
const project = url.searchParams.get("project");
|
|
2031
|
+
// 与 /pipeline、/candidates 同一条规矩:不是绝对路径就拒掉,不能静默退成全局。
|
|
2032
|
+
if (project !== null && !isAbsolute(project)) {
|
|
2033
|
+
sendJson(res, 400, { error: "project-not-absolute", detail: project });
|
|
2034
|
+
return;
|
|
2035
|
+
}
|
|
2036
|
+
// 预算从**这里**起算,不是一个端点两套预算:归属解析(同步扫桶,最多 500 个)
|
|
2037
|
+
// 与后面的挖掘必须共用同一条截止线,否则「真卡就调 budgetMs」对前一段是空话。
|
|
2038
|
+
// POST(显式真跑)给三倍预算,面板预览给短预算。
|
|
2039
|
+
const previewMethod = method === "GET" || method === "HEAD";
|
|
2040
|
+
const startedAt = Date.now();
|
|
2041
|
+
const budgetMs = previewMethod ? NUDGE_AUTO_BUDGET_MS : NUDGE_AUTO_BUDGET_MS * 3;
|
|
2042
|
+
const deadline = startedAt + budgetMs;
|
|
2043
|
+
// 归属解析先做、单独回执:解析不到桶时要能说清是「这个项目还没跑过会话」
|
|
2044
|
+
// 还是「会话目录读不到」——两者在候选列表上都表现为空。
|
|
2045
|
+
const where = resolveProjectSessionDir({ projectRoot: project, deadline });
|
|
2046
|
+
if (!where.sessionDir) {
|
|
2047
|
+
// no-sessions-dir / unreadable / budget-exceeded / bucket-limit 都是**环境问题**
|
|
2048
|
+
// (读不到、没扫完、没扫全),回 503 让调用方知道这不是答案;
|
|
2049
|
+
// no-bucket / bad-project 是**关于这个项目的答案**,回 200。
|
|
2050
|
+
const envProblem = where.reason === "no-sessions-dir"
|
|
2051
|
+
|| where.reason === "unreadable"
|
|
2052
|
+
|| where.reason === "budget-exceeded"
|
|
2053
|
+
|| where.reason === "bucket-limit";
|
|
2054
|
+
sendJson(res, envProblem ? 503 : 200, {
|
|
2055
|
+
error: envProblem
|
|
2056
|
+
? (where.reason === "budget-exceeded"
|
|
2057
|
+
? "nudge-attribution-budget-exceeded"
|
|
2058
|
+
: where.reason === "bucket-limit"
|
|
2059
|
+
? "nudge-attribution-bucket-limit"
|
|
2060
|
+
: "nudge-sessions-unreadable")
|
|
2061
|
+
: undefined,
|
|
2062
|
+
contract: "mneme.nudge/v1",
|
|
2063
|
+
attribution: where,
|
|
2064
|
+
skipped: true,
|
|
2065
|
+
skippedReason: "no-session-attribution",
|
|
2066
|
+
counts: { candidates: 0, buckets: 0, sessionsWithCandidates: 0, savedProposals: 0, draftsWritten: 0 },
|
|
2067
|
+
available: [],
|
|
2068
|
+
drafts: [],
|
|
2069
|
+
boundaries: NUDGE_BOUNDARIES
|
|
2070
|
+
});
|
|
2071
|
+
return;
|
|
2072
|
+
}
|
|
2073
|
+
const memoryDir = (config && typeof config.memoryDir === "string" && config.memoryDir.trim())
|
|
2074
|
+
? config.memoryDir
|
|
2075
|
+
: "~/.dsh/memory";
|
|
2076
|
+
const doc = nudgeForProject({
|
|
2077
|
+
projectRoot: project,
|
|
2078
|
+
// 上面已经解析过并判过 400/503,这里传进来,别再扫一遍桶。
|
|
2079
|
+
attribution: where,
|
|
2080
|
+
// now 传起算时刻:nudgeForProject 内部按 now+budgetMs 重新算截止点,这样
|
|
2081
|
+
// 两处算出来是同一条线(传 Date.now() 会让挖掘白拿一段已经花掉的预算)。
|
|
2082
|
+
now: startedAt,
|
|
2083
|
+
memoryDir,
|
|
2084
|
+
// GET 只预览:不落提议、不落草稿。写动作只认 POST/PUT——PATCH/DELETE 已在
|
|
2085
|
+
// 入口回 405,否则它们会落进这个「不是 GET/HEAD 就算写」的分支。
|
|
2086
|
+
persistProposals: method !== "GET" && method !== "HEAD",
|
|
2087
|
+
// 落草稿是「往仓库里塞文件」,必须显式要(body.materialize),默认不落。
|
|
2088
|
+
materialize: body && body.materialize === true,
|
|
2089
|
+
// 与归属解析共用同一条预算线:宁可少看几份并如实标 budgetExceeded,
|
|
2090
|
+
// 也不能让一次面板刷新把宿主主线程按住十几秒。
|
|
2091
|
+
budgetMs
|
|
2092
|
+
});
|
|
2093
|
+
sendJson(res, 200, doc);
|
|
2094
|
+
} catch (error) {
|
|
2095
|
+
sendJson(res, 500, {
|
|
2096
|
+
error: "nudge-failed",
|
|
2097
|
+
detail: String((error && error.message) || error)
|
|
2098
|
+
});
|
|
2099
|
+
}
|
|
2100
|
+
};
|
|
2101
|
+
if (method === "PUT" || method === "POST") {
|
|
2102
|
+
readBody(req, res).then((text) => {
|
|
2103
|
+
// readBody 已经回过 413:null 就是「别再往下走」。
|
|
2104
|
+
if (text === null) return;
|
|
2105
|
+
run(parseBody(text));
|
|
2106
|
+
});
|
|
2107
|
+
return;
|
|
2108
|
+
}
|
|
2109
|
+
run(null);
|
|
2110
|
+
}
|
|
2111
|
+
});
|
|
2112
|
+
|
|
1995
2113
|
return {
|
|
1996
2114
|
routes: countRoutes(),
|
|
1997
2115
|
dispose: () => {
|
package/lib/capabilities.js
CHANGED
package/lib/client.js
CHANGED
|
@@ -531,12 +531,12 @@ window.__ModuleLoader__.load({
|
|
|
531
531
|
"memory.features.heatEnabled.hint": "开:像遗忘曲线一样给记忆降温,长期没用过的往后排、不占注入名额;关:一直按相关度排(热度衰减,默认关)",
|
|
532
532
|
"memory.features.documentMemoryEnabled": "长文档只存一张摘要卡",
|
|
533
533
|
"memory.features.documentMemoryEnabled.hint": "开:Agent 写的长文档在记忆里只放「摘要卡」,正文仍由 Agent 保管、按需读取,注入只带摘要;关:整篇都往上下文里带(document 型记忆,默认关)",
|
|
534
|
-
// --- v0.9.0 M.O.(Modus Operandi
|
|
534
|
+
// --- v0.9.0 M.O.(Modus Operandi,beta)---
|
|
535
535
|
// M.O. = Modus Operandi,拉丁语「一个人一贯的做事方式」;也是 org 词根
|
|
536
536
|
// modus(modusensus)。界面主显示 M.O.,设置项 hint 第一句给全名。
|
|
537
537
|
"memory.actions.beta": "beta",
|
|
538
538
|
"memory.actions.notice": "提炼结果是候选,可能有错、过时、会被推翻。",
|
|
539
|
-
"memory.actions.title": "M.O. ·
|
|
539
|
+
"memory.actions.title": "M.O. · 工作流",
|
|
540
540
|
"memory.actions.open": "打开 M.O.",
|
|
541
541
|
"memory.actions.viewFull": "完整视图",
|
|
542
542
|
"memory.actions.openPane": "在右栏打开完整视图",
|
|
@@ -588,13 +588,24 @@ window.__ModuleLoader__.load({
|
|
|
588
588
|
"memory.actions.broken": "有文件读不出来:",
|
|
589
589
|
"memory.actions.empty.title": "这个项目里还没有工作流",
|
|
590
590
|
"memory.actions.empty.body": "工作流的本体住在项目里的 .mneme/pipelines/ 下——跟着仓库走,能被提交、能被 review、能被 CI 读。做法被复用第二次才算节点类型,第一次只是实例;同一路径被走通两次之后,它才会从会话里被提炼出来、落到那个目录里。",
|
|
591
|
+
"memory.actions.distill.action": "现在沉淀一次",
|
|
592
|
+
"memory.actions.distill.busy": "正在读会话…",
|
|
593
|
+
"memory.actions.distill.hint": "看一遍这个项目自己的会话日志,把反复走通的动作挖成提议。只写提议,不落草稿、不改仓库。",
|
|
594
|
+
"memory.actions.distill.noProject": "读不到会话的工作目录,所以不知道该看哪个项目的日志",
|
|
595
|
+
"memory.actions.distill.saved": "已提出 {n} 条提议(本次挖到 {m} 条候选)",
|
|
596
|
+
"memory.actions.distill.none": "这一遍没挖到新的重复动作",
|
|
597
|
+
"memory.actions.distill.noSessions": "这个项目还没有自己的会话日志,先攒几轮再来",
|
|
598
|
+
"memory.actions.distill.unreadable": "会话日志读不出来(这是环境问题,不是「没有重复动作」)",
|
|
599
|
+
"memory.actions.distill.failed": "这一遍没跑完,详见插件日志",
|
|
591
600
|
"memory.actions.noRuns": "这一条还没有运行记录,所以每个节点都还是候选。",
|
|
592
601
|
"memory.actions.settle.title": "本次运行",
|
|
593
602
|
"memory.actions.settle.line": "「{title}」",
|
|
594
603
|
"memory.actions.settle.used": "本次复用 {n} 条做法(勾选为被用上)",
|
|
595
604
|
"memory.actions.settle.refutedWarn": "本次路径里有已被推翻的节点,结果仅供参考",
|
|
596
605
|
"memory.features.actionPipelineEnabled": "M.O.(beta)",
|
|
597
|
-
"memory.features.actionPipelineEnabled.hint": "
|
|
606
|
+
"memory.features.actionPipelineEnabled.hint": "只读视图:把真实会话里反复走通的动作连成一张图谱——会话头图标、右栏完整视图、每次运行的结算卡。看 ≠ 写:沉淀由「M.O. 自动沉淀」单独开,执行记账由 /mo-run 单独开。Modus Operandi(工作流)。重启 DSH 后生效",
|
|
607
|
+
"memory.features.actionNudgeEnabled": "自动沉淀工作流(beta)",
|
|
608
|
+
"memory.features.actionNudgeEnabled.hint": "开:每轮结束后看一眼这个项目自己的会话日志,把反复走通的动作挖成记忆库里的一条「提议」,等你确认才可能变成工作流。只产草稿、不自动启用、不往仓库里塞文件。挂热路径且要解压日志,轻量档会强制关掉。重启 DSH 后生效",
|
|
598
609
|
"memory.features.resilientModelDownload": "模型下载断了能续上",
|
|
599
610
|
"memory.features.resilientModelDownload.hint": "开:下载中断后从断点续传并自动重试;关:中断就整份重来(断点续传,默认开)",
|
|
600
611
|
"memory.features.searchSemanticDedup": "搜到意思相同的只留一条",
|
|
@@ -1254,13 +1265,24 @@ window.__ModuleLoader__.load({
|
|
|
1254
1265
|
"memory.actions.broken": "Some files could not be read:",
|
|
1255
1266
|
"memory.actions.empty.title": "No workflows in this project yet",
|
|
1256
1267
|
"memory.actions.empty.body": "A workflow lives in the project, under .mneme/pipelines/ — it travels with the repository: committable, reviewable, readable by CI. A practice becomes a node type on its second reuse; the first run is just an instance. Once the same path has been walked twice it gets distilled out of the session and lands in that directory.",
|
|
1268
|
+
"memory.actions.distill.action": "Distill now",
|
|
1269
|
+
"memory.actions.distill.busy": "Reading sessions…",
|
|
1270
|
+
"memory.actions.distill.hint": "Scan this project's own session logs and mine repeated actions into proposals. Writes proposals only — no drafts, no repository changes.",
|
|
1271
|
+
"memory.actions.distill.noProject": "The session's working directory is unknown, so there is no project to scan",
|
|
1272
|
+
"memory.actions.distill.saved": "{n} proposal(s) written ({m} candidate(s) mined this pass)",
|
|
1273
|
+
"memory.actions.distill.none": "No new repeated action this pass",
|
|
1274
|
+
"memory.actions.distill.noSessions": "This project has no session logs of its own yet — come back after a few turns",
|
|
1275
|
+
"memory.actions.distill.unreadable": "The session logs could not be read (an environment problem, not an empty result)",
|
|
1276
|
+
"memory.actions.distill.failed": "This pass did not finish; see the plugin log",
|
|
1257
1277
|
"memory.actions.noRuns": "No runs recorded for this workflow yet, so every step is still a candidate.",
|
|
1258
1278
|
"memory.actions.settle.title": "This run",
|
|
1259
1279
|
"memory.actions.settle.line": "\"{title}\"",
|
|
1260
1280
|
"memory.actions.settle.used": "Reused {n} steps this run (checked = actually used)",
|
|
1261
1281
|
"memory.actions.settle.refutedWarn": "This path contains refuted steps; take the result with caution",
|
|
1262
1282
|
"memory.features.actionPipelineEnabled": "M.O. (beta)",
|
|
1263
|
-
"memory.features.actionPipelineEnabled.hint": "
|
|
1283
|
+
"memory.features.actionPipelineEnabled.hint": "Read-only view: the ways of working that actually got walked through in real sessions, drawn as a graph — session-header icon, full sidebar view, and a per-run settlement card. Viewing is not writing: distillation has its own switch below, and run accounting has its own (/mo-run). Modus Operandi. Takes effect after restarting DSH",
|
|
1284
|
+
"memory.features.actionNudgeEnabled": "Auto-distill workflows (beta)",
|
|
1285
|
+
"memory.features.actionNudgeEnabled.hint": "On: after each turn, look at this project's own session logs and mine the actions it keeps repeating into a proposal in the memory store — it still needs your sign-off before becoming a workflow. Drafts only; never auto-activates; never writes files into the repo. Hooks a hot path and decompresses logs, so light mode forces it off. Takes effect after restarting DSH",
|
|
1264
1286
|
"memory.features.resilientModelDownload": "Model downloads resume after interruption",
|
|
1265
1287
|
"memory.features.resilientModelDownload.hint": "On: downloads continue from where they stopped and retry automatically. Off: an interrupted download starts over (resumable downloads, on by default)",
|
|
1266
1288
|
"memory.features.searchSemanticDedup": "Keep one entry when search hits near-duplicates",
|
|
@@ -2351,6 +2373,17 @@ window.__ModuleLoader__.load({
|
|
|
2351
2373
|
".mneme-pipe-wfMeta{display:block;margin-top:3px;font-size:12px;color:var(--mneme-fg-dim)}",
|
|
2352
2374
|
".mneme-pipe-wfId{display:block;margin-top:2px;font-size:11px;color:var(--mneme-fg-dim)}",
|
|
2353
2375
|
".mneme-pipe-broken{flex:none;font-size:12px;line-height:17px;color:var(--mneme-warn);padding:6px 14px 0}",
|
|
2376
|
+
// 手动沉淀:按钮与回执占一行。回执用 --mneme-fg-dim 而不是 --mneme-warn——
|
|
2377
|
+
// 「这一遍没挖到」是正常结果,不是警告;只有「读不出来」才是故障,那也由文案说清,
|
|
2378
|
+
// 不靠颜色区分(红黄两色在深浅主题里最容易一起掉对比度)。
|
|
2379
|
+
".mneme-pipe-distill{flex:none;display:flex;align-items:center;gap:8px;flex-wrap:wrap;font-size:12px}",
|
|
2380
|
+
".mneme-pipe-distillBtn{border:1px solid var(--dsw-alias-border-l2);background:0 0;border-radius:8px;cursor:pointer;color:var(--mneme-fg-dim);font-family:inherit;font-size:12px;line-height:16px;padding:2px 9px}",
|
|
2381
|
+
".mneme-pipe-distillBtn:hover:enabled{background:var(--dsw-alias-interactive-bg-hover)}",
|
|
2382
|
+
".mneme-pipe-distillBtn:focus-visible{outline:2px solid var(--dsw-alias-state-business-primary);outline-offset:1px}",
|
|
2383
|
+
// 禁用态用虚线边而不是降透明度:整块降 opacity 会把文字一起压到 AA 以下,
|
|
2384
|
+
// 而「为什么禁用」本来就由 title 说明(读不到会话工作目录)。
|
|
2385
|
+
".mneme-pipe-distillBtn:disabled{cursor:not-allowed;border-style:dashed;color:var(--mneme-fg-dim)}",
|
|
2386
|
+
".mneme-pipe-distillNote{color:var(--mneme-fg-dim);line-height:17px;min-width:0}",
|
|
2354
2387
|
].join("\n");
|
|
2355
2388
|
if (typeof document !== "undefined" && document.querySelector(`style[data-plugin-css="${CSS_TAG}"]`) === null) {
|
|
2356
2389
|
const tag = document.createElement("style");
|
|
@@ -2923,7 +2956,10 @@ window.__ModuleLoader__.load({
|
|
|
2923
2956
|
// (关=一个座位都不注册,见 apply 内的 M.O. 块)。独立成组只解决「混在
|
|
2924
2957
|
// enhance 末位」;可见层由 FEATURE_FIRST_SCREEN 决定(见其注释),
|
|
2925
2958
|
// 本轮已把组名提进首屏——组成员资格不决定可见性。
|
|
2926
|
-
|
|
2959
|
+
// 同一组的三个键是**三件独立能力**,不是一条链的三段:看图(座位闸门)、
|
|
2960
|
+
// 自动沉淀(写提议到记忆库)、执行记账(/mo-run 开窗)。各自默认关,
|
|
2961
|
+
// 开了任何一个都不会顺带打开另一个——写能力必须由人一项一项点。
|
|
2962
|
+
{ key: "group.mo", items: ["actionPipelineEnabled", "actionNudgeEnabled"] },
|
|
2927
2963
|
{ key: "group.enhance", items: ["entityExtractionEnabled", "codingRetrospect", "rerankEnabled", "resilientModelDownload", "searchSemanticDedup", "bm25SearchEnabled", "heatEnabled", "documentMemoryEnabled",
|
|
2928
2964
|
// 人格定义页(beta):注入总闸。分组随 enhance(行为开关,非座位闸门);
|
|
2929
2965
|
// 人格内容在设置页「个性化」区的人格卡。
|
|
@@ -7603,8 +7639,62 @@ window.__ModuleLoader__.load({
|
|
|
7603
7639
|
// 条目清单(名字 + 规模 + 作用域):图上方的**选择器**。
|
|
7604
7640
|
// 它解决的是「这是哪条工作流」——一进来就是一张图时这个问题没有落点,多条之间也没得选。
|
|
7605
7641
|
// 但它不占整屏:图和它同屏,见下面的 PipelineBrowser。
|
|
7642
|
+
// 「现在就跑一次」。自动沉淀只在 turn/end 之后跑、还有 10 分钟冷却,所以人想立刻
|
|
7643
|
+
// 看到结果时没有入口。这里的 POST 与自动跑走**同一条** nudgeForProject,区别只有
|
|
7644
|
+
// materialize:按钮永远不落草稿,只把提议写进记忆库——升格仍然要人批。
|
|
7645
|
+
// project 为空(会话里读不到 cwd)时按钮是禁用的:服务端只会回 400,
|
|
7646
|
+
// 而一个点了必然失败的按钮比没有按钮更糟。
|
|
7647
|
+
function PipelineDistillButton(props) {
|
|
7648
|
+
const { t, project } = props;
|
|
7649
|
+
const [busy, setBusy] = useState(false);
|
|
7650
|
+
const [note, setNote] = useState("");
|
|
7651
|
+
const disabled = busy || !project;
|
|
7652
|
+
const run = () => {
|
|
7653
|
+
if (disabled) return;
|
|
7654
|
+
setBusy(true);
|
|
7655
|
+
setNote("");
|
|
7656
|
+
apiFetch("/api/dsh-mneme/nudge?project=" + encodeURIComponent(project), {
|
|
7657
|
+
method: "POST",
|
|
7658
|
+
headers: { "Content-Type": "application/json" },
|
|
7659
|
+
body: JSON.stringify({})
|
|
7660
|
+
})
|
|
7661
|
+
.then((res) => res.json().then((j) => ({ ok: res.ok, status: res.status, body: j || {} })))
|
|
7662
|
+
.then(({ ok, status, body }) => {
|
|
7663
|
+
if (!ok) {
|
|
7664
|
+
// 503 = 会话日志读不到(环境问题),其余是这一跑本身失败。两句不能合并:
|
|
7665
|
+
// 「读不到」和「没有重复动作」在界面上长得一样时,人就只能瞎猜。
|
|
7666
|
+
setNote(t(status === 503
|
|
7667
|
+
? "memory.actions.distill.unreadable"
|
|
7668
|
+
: "memory.actions.distill.failed"));
|
|
7669
|
+
return;
|
|
7670
|
+
}
|
|
7671
|
+
if (body.skipped) {
|
|
7672
|
+
setNote(t("memory.actions.distill.noSessions"));
|
|
7673
|
+
return;
|
|
7674
|
+
}
|
|
7675
|
+
const saved = (body.counts && body.counts.savedProposals) || 0;
|
|
7676
|
+
const found = (body.counts && body.counts.candidates) || 0;
|
|
7677
|
+
setNote(found === 0
|
|
7678
|
+
? t("memory.actions.distill.none")
|
|
7679
|
+
: t("memory.actions.distill.saved").replace("{n}", String(saved)).replace("{m}", String(found)));
|
|
7680
|
+
})
|
|
7681
|
+
.catch(() => setNote(t("memory.actions.distill.failed")))
|
|
7682
|
+
.then(() => setBusy(false));
|
|
7683
|
+
};
|
|
7684
|
+
return h("span", { className: "mneme-pipe-distill" },
|
|
7685
|
+
h("button", {
|
|
7686
|
+
type: "button",
|
|
7687
|
+
className: "mneme-pipe-distillBtn",
|
|
7688
|
+
disabled,
|
|
7689
|
+
title: !project ? t("memory.actions.distill.noProject") : t("memory.actions.distill.hint"),
|
|
7690
|
+
onClick: run
|
|
7691
|
+
}, busy ? t("memory.actions.distill.busy") : t("memory.actions.distill.action")),
|
|
7692
|
+
note ? h("span", { className: "mneme-pipe-distillNote", role: "status" }, note) : null
|
|
7693
|
+
);
|
|
7694
|
+
}
|
|
7695
|
+
|
|
7606
7696
|
function PipelineWorkflowList(props) {
|
|
7607
|
-
const { t, data, activeId, onOpen } = props;
|
|
7697
|
+
const { t, data, activeId, onOpen, project } = props;
|
|
7608
7698
|
const list = (data && data.available) || [];
|
|
7609
7699
|
const scopeOf = (row) => (row.scope === "global"
|
|
7610
7700
|
? t("memory.actions.scope.global")
|
|
@@ -7614,6 +7704,7 @@ window.__ModuleLoader__.load({
|
|
|
7614
7704
|
h("span", null, t("memory.actions.list.heading")),
|
|
7615
7705
|
h("span", null, t("memory.actions.list.count").replace("{n}", String(list.length)))
|
|
7616
7706
|
),
|
|
7707
|
+
h(PipelineDistillButton, { t, project }),
|
|
7617
7708
|
list.length
|
|
7618
7709
|
? list.map((row) => h("button", {
|
|
7619
7710
|
key: row.id,
|
|
@@ -7664,7 +7755,7 @@ window.__ModuleLoader__.load({
|
|
|
7664
7755
|
className: "mneme-pipe",
|
|
7665
7756
|
style: { height: "100%", display: "flex", flexDirection: "column", minHeight: "0" }
|
|
7666
7757
|
},
|
|
7667
|
-
h(PipelineWorkflowList, { t, data: listDoc, activeId, onOpen: setPicked }),
|
|
7758
|
+
h(PipelineWorkflowList, { t, data: listDoc, activeId, onOpen: setPicked, project }),
|
|
7668
7759
|
h(PipelineView, { t, data: detailDoc })
|
|
7669
7760
|
);
|
|
7670
7761
|
}
|
package/lib/config.js
CHANGED
|
@@ -880,7 +880,7 @@ export const Config = z.object({
|
|
|
880
880
|
enforce: z.boolean().default(false)
|
|
881
881
|
}).default({}),
|
|
882
882
|
|
|
883
|
-
// --- v0.9.0 M.O.(Modus Operandi
|
|
883
|
+
// --- v0.9.0 M.O.(Modus Operandi,beta)--------------------------
|
|
884
884
|
// 命名:M.O. = Modus Operandi,拉丁语「一个人一贯的做事方式」,缩写 M.O.;
|
|
885
885
|
// 也正是 org 词根 modus(modusensus)。界面主显示 M.O.,设置项 hint 第一句
|
|
886
886
|
// 给全名——缩写不靠注释解释,靠用户第一眼能看见全名。
|
|
@@ -902,6 +902,20 @@ export const Config = z.object({
|
|
|
902
902
|
// 所以轻量档要关掉:`LIGHT_MODE_OFF` 里有它。
|
|
903
903
|
actionRunEnabled: z.boolean().default(false),
|
|
904
904
|
|
|
905
|
+
// M.O. 自动沉淀(v0.9.0 第三批):会话跑完一轮后,把这个项目自己的会话日志挖一遍,
|
|
906
|
+
// 把「反复出现的同一段能力序列」落成提议(memoryDir 下),可选再落一份 draft 草稿。
|
|
907
|
+
//
|
|
908
|
+
// 与上面两个键三件事分开:actionPipelineEnabled 管「看图的座位」,actionRunEnabled
|
|
909
|
+
// 管「往账本里写」,这个管「从日志里长出候选」。三个都是独立能力,混成一个键会让
|
|
910
|
+
// 「只想看图」的人顺手获得写盘能力。
|
|
911
|
+
//
|
|
912
|
+
// 默认关(beta 必须 opt-in),并且进 LIGHT_MODE_OFF:它挂在 session/event 热路径上,
|
|
913
|
+
// 且解压会话日志是**同步**的(本机 118 份实测 16.2s),轻量档最不该背这条路径。
|
|
914
|
+
// 触发侧另有 10 分钟/项目的冷却与 budgetMs 上限(见 src/pipeline-nudge.js 的
|
|
915
|
+
// NUDGE_AUTO_MIN_INTERVAL_MS 与 src/index.js 的装配注释)——旗标只是总闸。
|
|
916
|
+
// (冷却值以常量为准:早前这里写「30 分钟」,与实际 10 分钟不符。)
|
|
917
|
+
actionNudgeEnabled: z.boolean().default(false),
|
|
918
|
+
|
|
905
919
|
// --- 人格定义页(beta)---------------------------------------------------
|
|
906
920
|
// 注入器渲染人格段的顶层总闸(双闸门的 config 半边;user_settings "persona"
|
|
907
921
|
// 键内的 persona.enabled 是 per-agent 半边,两闸同真才渲染)。API 的 effective
|
|
@@ -962,7 +976,11 @@ const LIGHT_MODE_OFF = [
|
|
|
962
976
|
// M.O. 执行记账:它挂在**热路径**上(每个会话事件都过一次监听)。轻量档的取向
|
|
963
977
|
// 是「小模型 / 小上下文 / 少常驻路径」,这与一个每事件都过一遍的监听器相冲突。
|
|
964
978
|
// 默认关,这里只是轻量预设再确认一次;用户显式勾选仍然赢(合并顺序同上面两条)。
|
|
965
|
-
"actionRunEnabled"
|
|
979
|
+
"actionRunEnabled",
|
|
980
|
+
// M.O. 自动沉淀:同样挂在 session/event 热路径上,而且解压会话日志是同步 I/O。
|
|
981
|
+
// 轻量档的取向(小模型 / 小上下文 / 少常驻路径)与它直接冲突,预设里关掉;
|
|
982
|
+
// 用户显式勾选仍然赢(合并顺序同上面几条)。
|
|
983
|
+
"actionNudgeEnabled"
|
|
966
984
|
];
|
|
967
985
|
|
|
968
986
|
/**
|
package/lib/index.js
CHANGED
|
@@ -24,6 +24,9 @@ import { createSettings } from "./settings.js";
|
|
|
24
24
|
import { createCommandManager } from "./commands.js";
|
|
25
25
|
import { createExecutionRecorder } from "./pipeline-runner.js";
|
|
26
26
|
import { registerMoRunCommand } from "./pipeline-command.js";
|
|
27
|
+
// M.O. 自动沉淀的触发侧(冷却 / 排队 / 兜底)。挖掘与落盘在 pipeline-nudge.js,
|
|
28
|
+
// 这里只管「什么时候跑」。两者分开的理由见 src/pipeline-nudge-auto.js 文件头。
|
|
29
|
+
import { createNudgeAutoRun } from "./pipeline-nudge-auto.js";
|
|
27
30
|
import { createVectorIndex } from "./vector-index.js";
|
|
28
31
|
// semantic(embedder/reranker/boot 回填)纯搬移至 src/semantic.js(PR2):宿主与
|
|
29
32
|
// daemon(dsh-mneme-serve,#363)共用同一套装配,原文件保留 barrel 出口。
|
|
@@ -590,6 +593,18 @@ export const apply = (ctx, config) => {
|
|
|
590
593
|
disposers.push(api.dispose);
|
|
591
594
|
}
|
|
592
595
|
|
|
596
|
+
// M.O. 自动沉淀(v0.9.3):项目里重复走的那套动作,自动变成 memoryDir 里的一条
|
|
597
|
+
// 「提议」,等人确认后才可能进仓库。默认关(actionNudgeEnabled)。
|
|
598
|
+
//
|
|
599
|
+
// 为什么位置在这里而不是和 M.O. 记账同段:`disposers` 声明在 :532,而记账块在它
|
|
600
|
+
// 之前(TDZ)——照抄过去会在插件加载时直接抛 ReferenceError。这里已在其后。
|
|
601
|
+
//
|
|
602
|
+
// 为什么挂 session/event 却又要进轻量档黑名单:它读的是 zstd 会话日志(同步解压),
|
|
603
|
+
// 是本插件里最重的一条后台路径;且它只产「提议」,关掉不影响记忆本身的正确性。
|
|
604
|
+
if (cfg.actionNudgeEnabled === true) {
|
|
605
|
+
disposers.push(createNudgeAutoRun({ ctx, memoryDir, logger: ctx.logger }));
|
|
606
|
+
}
|
|
607
|
+
|
|
593
608
|
// Standalone external API (v0.7.12): plain node:http server for ecosystem
|
|
594
609
|
// integrations outside the DSH host. Persisted external_api settings win
|
|
595
610
|
// over the bundle config (enabled/port); the Bearer token lives in the same
|