dsh-ledger-memory 0.1.1 → 0.3.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 +49 -6
- package/lib/client.js +88 -1
- package/lib/index.js +912 -12
- package/package.json +3 -2
package/README.md
CHANGED
|
@@ -69,18 +69,59 @@ dsh plugin add dsh-ledger-memory
|
|
|
69
69
|
/ledger light 轻量压缩
|
|
70
70
|
/ledger log 查活动日志
|
|
71
71
|
/ledger handoff 写一份交接单
|
|
72
|
+
/ledger mode 看/切换压缩模式(steady / surgical)
|
|
73
|
+
/ledger lock 把本场对话锁到某个项目目录(总工作区里用)
|
|
72
74
|
```
|
|
73
75
|
|
|
76
|
+
### 对话建在总工作区时:锁定到子项目
|
|
77
|
+
|
|
78
|
+
有时对话是建在**装着多个项目的总工作区**上的。那时插件无从判断"这次说的是哪个项目",
|
|
79
|
+
默认就都落在当前目录。解决办法是**锁**:
|
|
80
|
+
|
|
81
|
+
```
|
|
82
|
+
/ledger lock 20-DSH插件/04-其余插件/10.项目台账
|
|
83
|
+
```
|
|
84
|
+
|
|
85
|
+
锁定后,本场对话的**所有**工具调用都落在那个目录上,不必每次再指定。
|
|
86
|
+
`/ledger lock` 不带参数看当前状态,`/ledger lock off` 解锁。
|
|
87
|
+
会话第一轮弹的那张卡片里也有这个选项。
|
|
88
|
+
|
|
89
|
+
> ★ 这个锁**只存在内存里**:不建注册表、不在你的目录里留任何标记、**一个文件都不写**,
|
|
90
|
+
> 换一场对话就回到默认。它只影响"这次往哪儿记",不改你磁盘上的任何东西。
|
|
91
|
+
>
|
|
92
|
+
> ★ 若当前目录**自己就有台账**,仍以当前目录为准 —— 锁只接管"没有台账"的目录。
|
|
93
|
+
|
|
74
94
|
### 设置面板
|
|
75
95
|
|
|
76
|
-
**设置 → 项目台账(台账 / 压缩 /
|
|
77
|
-
|
|
96
|
+
**设置 → 项目台账(台账 / 压缩 / 日志)**,可调十三项:**压缩模式**、回合末卡片开关、
|
|
97
|
+
高保真压缩水位、增量压缩的涨幅与保留量、活动日志五档开关、活跃台账行数上限、
|
|
98
|
+
变更登记条数、水位提醒档位等。
|
|
99
|
+
|
|
100
|
+
> 一条要知道的语义:`mode` / `maxActiveLines` / `changeRegisterMax` / `logLevels`
|
|
101
|
+
> **以每个项目自己清单里的值为准**。面板里这几项是**新项目初始化时写进去的默认值**。
|
|
102
|
+
|
|
103
|
+
## 两个压缩模式
|
|
104
|
+
|
|
105
|
+
压缩方式可以按项目选。默认是「正常推进」,两者共用同一套压缩,区别只在压缩前后多做或少做什么:
|
|
106
|
+
|
|
107
|
+
| | **正常推进**(`steady`,默认) | **反复追究**(`surgical`) |
|
|
108
|
+
|---|---|---|
|
|
109
|
+
| 压缩时怎么切 | **按位置**:只压最旧一段,更早的一刀切 | **按内容分流**,再压 |
|
|
110
|
+
| 谁做判断 | 不用判断(省一次模型输出) | AI 做一次分流 |
|
|
111
|
+
| 适合 | 一路向前干活 | 要反复回看约束与假设的活 |
|
|
112
|
+
|
|
113
|
+
**「反复追究」怎么工作**:水位到点时会先让 AI 调 `ledger_triage` 把这一段分成三个桶 ——
|
|
114
|
+
|
|
115
|
+
- **留(`keep`)**:下一轮不知道就会做错的东西(当前状态、未验证的假设、你定过的约束)。压缩后**逐字注回**。
|
|
116
|
+
- **落(`stash`)**:以后可能要查、但不该占上下文的(判断与理由、走不通的路、关键文件位置)。
|
|
117
|
+
写进项目的 `_stash/`,**永不注入**,需要时自己 grep。
|
|
118
|
+
- **丢(`drop`)**:已被取代的,只进日志。
|
|
119
|
+
|
|
120
|
+
`keep` 有条数上限——**留得太多等于没压**,超了会按 AI 给的顺序截断并如实报出截掉几条。
|
|
78
121
|
|
|
79
|
-
|
|
80
|
-
> 自己清单里的值为准**(日志是只追加的证据,全局设置改不动它)。
|
|
81
|
-
> 面板里这三项是**新项目初始化时写进去的默认值**。
|
|
122
|
+
切换:设置面板里选,或 `/ledger mode steady` / `/ledger mode surgical`(只影响当前项目)。
|
|
82
123
|
|
|
83
|
-
##
|
|
124
|
+
## 十四个工具
|
|
84
125
|
|
|
85
126
|
AI 在对话里调用,你也可以让它调用:
|
|
86
127
|
|
|
@@ -96,6 +137,8 @@ AI 在对话里调用,你也可以让它调用:
|
|
|
96
137
|
| `ledger_log` | 查活动日志(五档,**从不注入上下文**) |
|
|
97
138
|
| `ledger_log_write` | 追加一条回合小结 |
|
|
98
139
|
| `ledger_handoff` | 写交接单,供下一个会话接手 |
|
|
140
|
+
| `ledger_triage` | 把阶段性产出分成「留 / 落 / 丢」三个桶(「反复追究」模式用) |
|
|
141
|
+
| `ledger_lock` | 把本场对话锁到某个项目目录(只存内存,不写任何文件) |
|
|
99
142
|
| `ledger_compact` | 请求一次高保真压缩 |
|
|
100
143
|
| `ledger_checkpoint` | 把这一刻的选择交给用户(记台账 / 记+压缩 / 记+交接 / 跳过) |
|
|
101
144
|
|
package/lib/client.js
CHANGED
|
@@ -149,15 +149,40 @@ window.__ModuleLoader__.load({
|
|
|
149
149
|
const n = Number(uiValue);
|
|
150
150
|
return { [spec.key]: Number.isFinite(n) ? n : undefined };
|
|
151
151
|
}
|
|
152
|
+
// ★ `choice` 就是**字符串**。走下面那条兜底本来也对 —— 但显式写出来,
|
|
153
|
+
// 是为了让"这个类型的值是什么形状"一眼可见(免得下一个人又按 boolean 猜)。
|
|
154
|
+
if (spec.kind === "choice") {
|
|
155
|
+
return { [spec.key]: typeof uiValue === "string" && uiValue !== "" ? uiValue : undefined };
|
|
156
|
+
}
|
|
152
157
|
return { [spec.key]: uiValue };
|
|
153
158
|
}
|
|
154
159
|
|
|
155
|
-
/**
|
|
160
|
+
/**
|
|
161
|
+
* 从当前设置里取出某个控件要显示的初值。
|
|
162
|
+
*
|
|
163
|
+
* ★★★ 2026-10-10 修(用户报的真 bug):
|
|
164
|
+
* *"那个压缩模式为什么我选择之后不能保存,然后关掉再打开之后即使已经选择了,
|
|
165
|
+
* 它依旧是空白的。两个模式都是未选择状态。"*
|
|
166
|
+
*
|
|
167
|
+
* 根因就是这里的**兜底**:原来最后一行是 `return v === true;` —— 那是给
|
|
168
|
+
* **boolean** 写的兜底,而 `choice` 没有自己的分支 ⇒ 也掉进这一行。
|
|
169
|
+
* 于是已存 `"surgical"` 被读成 `false`,两个单选按钮**都不选中**。
|
|
170
|
+
* ★ 而且坏的不只是显示:`buildDraft` 用它造草稿 ⇒ 草稿初值是 `false` ⇒
|
|
171
|
+
* 用户点完选项、`diffPatch` 一比对"没变化" ⇒ **patch 是空的、什么都没提交**。
|
|
172
|
+
* ⇒ 一个兜底同时造成"显示不出来"与"保存不上",两半是同一个根因。
|
|
173
|
+
*
|
|
174
|
+
* 教训(比这个 bug 值钱):断言只验了"选项渲染出来了",**没验"选中值能不能往返"**
|
|
175
|
+
* —— 渲染断言对"选不中"完全无感。**一个新控件类型必须测它的值往返**。
|
|
176
|
+
*/
|
|
156
177
|
function settingsToUi(spec, settings) {
|
|
157
178
|
const v = settings === undefined ? undefined : settings[spec.key];
|
|
158
179
|
if (spec.kind === "tiers") return formatTiers(v);
|
|
159
180
|
if (spec.kind === "levels") return v === undefined || v === null ? {} : v;
|
|
160
181
|
if (spec.kind === "number") return v === undefined || v === null ? "" : String(v);
|
|
182
|
+
// ★ `choice`:原样把字符串交给控件(它自己按 options 比对谁被选中)。
|
|
183
|
+
// 没设过就返回 `""`(而不是 undefined)—— 与 number 的 `""` 同一条口径:
|
|
184
|
+
// 控件拿到的一定是**同一种类型**,不必每处再判 undefined。
|
|
185
|
+
if (spec.kind === "choice") return typeof v === "string" ? v : "";
|
|
161
186
|
return v === true;
|
|
162
187
|
}
|
|
163
188
|
|
|
@@ -266,6 +291,22 @@ window.__ModuleLoader__.load({
|
|
|
266
291
|
border: 1px solid var(--dsw-alias-border-l1); border-radius: 8px;
|
|
267
292
|
}
|
|
268
293
|
.pl-settings__mono { font-family: ui-monospace, SFMono-Regular, Menlo, monospace; }
|
|
294
|
+
/* 二选一(模式):把两个选项的**取舍**都摆在眼前,不下拉折叠 */
|
|
295
|
+
.pl-settings__choices { display: flex; flex-direction: column; gap: 6px; width: 100%; }
|
|
296
|
+
.pl-settings__choice {
|
|
297
|
+
display: flex; gap: 8px; align-items: flex-start; cursor: pointer;
|
|
298
|
+
padding: 8px 10px; border-radius: 8px;
|
|
299
|
+
border: 1px solid var(--dsw-alias-border-l1);
|
|
300
|
+
background: var(--dsw-alias-bg-layer-1);
|
|
301
|
+
}
|
|
302
|
+
.pl-settings__choice--on {
|
|
303
|
+
border-color: var(--dsw-alias-state-success-primary);
|
|
304
|
+
background: var(--dsw-alias-bg-layer-2);
|
|
305
|
+
}
|
|
306
|
+
.pl-settings__choice input { margin-top: 2px; flex: none; }
|
|
307
|
+
.pl-settings__choice-body { display: flex; flex-direction: column; gap: 2px; }
|
|
308
|
+
.pl-settings__choice-label { font-size: 13px; color: var(--dsw-alias-label-primary); }
|
|
309
|
+
.pl-settings__choice-hint { font-size: 12px; line-height: 1.55; color: var(--dsw-alias-label-secondary); }
|
|
269
310
|
`;
|
|
270
311
|
|
|
271
312
|
function installCss(ctx) {
|
|
@@ -334,6 +375,51 @@ window.__ModuleLoader__.load({
|
|
|
334
375
|
]);
|
|
335
376
|
}
|
|
336
377
|
|
|
378
|
+
/**
|
|
379
|
+
* 二选一(含以上)行 —— 每个选项一个单选按钮 + 名字 + 说明。
|
|
380
|
+
*
|
|
381
|
+
* ★ 为什么不用下拉框:这一项的两个选项**各有各的取舍**(省一次模型输出 vs
|
|
382
|
+
* 按内容分流),用户需要**看见取舍**才能选对。下拉框把说明藏起来了。
|
|
383
|
+
* ★ 选项来自宿主(`spec.options` 是**已展开的数组** —— 宿主那边是取值函数,
|
|
384
|
+
* 为躲 TDZ;见宿主 `SETTING_SPECS` 的注释)。客户端**不认识任何模式名**,
|
|
385
|
+
* 只按 `value`/`label`/`hint` 渲染 ⇒ 以后加第三个模式**不用改本文件**。
|
|
386
|
+
*/
|
|
387
|
+
function ChoiceRow(props) {
|
|
388
|
+
const { spec, value, onChange, disabled } = props;
|
|
389
|
+
const opts = Array.isArray(spec.options) ? spec.options : [];
|
|
390
|
+
return h(Row, { label: spec.label, hint: spec.hint, stack: true }, [
|
|
391
|
+
h(
|
|
392
|
+
"div",
|
|
393
|
+
{ key: "g", className: "pl-settings__choices" },
|
|
394
|
+
opts.map((o) =>
|
|
395
|
+
h(
|
|
396
|
+
"label",
|
|
397
|
+
{
|
|
398
|
+
key: String(o.value),
|
|
399
|
+
className:
|
|
400
|
+
"pl-settings__choice" + (String(o.value) === String(value) ? " pl-settings__choice--on" : ""),
|
|
401
|
+
},
|
|
402
|
+
[
|
|
403
|
+
h("input", {
|
|
404
|
+
key: "r",
|
|
405
|
+
type: "radio",
|
|
406
|
+
name: spec.key,
|
|
407
|
+
value: o.value,
|
|
408
|
+
checked: String(o.value) === String(value),
|
|
409
|
+
disabled: disabled === true,
|
|
410
|
+
onChange: () => onChange(o.value),
|
|
411
|
+
}),
|
|
412
|
+
h("span", { key: "t", className: "pl-settings__choice-body" }, [
|
|
413
|
+
h("span", { key: "l", className: "pl-settings__choice-label" }, o.label || String(o.value)),
|
|
414
|
+
o.hint ? h("span", { key: "h", className: "pl-settings__choice-hint" }, o.hint) : null,
|
|
415
|
+
]),
|
|
416
|
+
]
|
|
417
|
+
)
|
|
418
|
+
)
|
|
419
|
+
),
|
|
420
|
+
]);
|
|
421
|
+
}
|
|
422
|
+
|
|
337
423
|
/** 档位行(文本框,逗号分隔)。 */
|
|
338
424
|
function TiersRow(props) {
|
|
339
425
|
const { spec, value, onChange, disabled } = props;
|
|
@@ -394,6 +480,7 @@ window.__ModuleLoader__.load({
|
|
|
394
480
|
const value = draft[spec.key];
|
|
395
481
|
const set = (v) => setDraft(Object.assign({}, draft, { [spec.key]: v }));
|
|
396
482
|
if (spec.kind === "boolean") return h(BoolRow, { key: spec.key, spec, value, onChange: set, disabled });
|
|
483
|
+
if (spec.kind === "choice") return h(ChoiceRow, { key: spec.key, spec, value, onChange: set, disabled });
|
|
397
484
|
if (spec.kind === "number") return h(NumberRow, { key: spec.key, spec, value, onChange: set, disabled });
|
|
398
485
|
if (spec.kind === "tiers") return h(TiersRow, { key: spec.key, spec, value, onChange: set, disabled });
|
|
399
486
|
if (spec.kind === "levels") return h(LevelsRow, { key: spec.key, spec, value, onChange: set, disabled });
|