dsh-lite-memory 0.16.2 → 0.16.4
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/client.js +54 -19
- package/index.js +2 -2
- package/package.json +1 -1
- package/store.js +1 -1
- package/test/learning.test.mjs +1 -1
package/client.js
CHANGED
|
@@ -315,6 +315,7 @@ window.__ModuleLoader__.load({
|
|
|
315
315
|
return h('div', { 'data-tool': props.toolName, style: ROW }, h('span', { style: DOT }, '·'), h('span', { style: LABEL }, label), button);
|
|
316
316
|
};
|
|
317
317
|
|
|
318
|
+
const scopeTag = (scope) => (scope === 'global' ? '[全局]' : '[项目]');
|
|
318
319
|
const versionLine = (shown) => {
|
|
319
320
|
const scope = shown.scopeLabel || (shown.scope === 'global' ? '全局' : '项目');
|
|
320
321
|
const archived = shown.status === 'archived' ? ' · 已归档' : '';
|
|
@@ -389,7 +390,7 @@ window.__ModuleLoader__.load({
|
|
|
389
390
|
'aria-label': `查看记忆 ${item.title || item.id}`,
|
|
390
391
|
title: item.title || item.id,
|
|
391
392
|
style: ITEM, // ui-copy-ignore
|
|
392
|
-
}, `${item.title || item.id}${item.origin === 'project_evolution' ? ' · 实验' : ''}`)) : [];
|
|
393
|
+
}, `${scopeTag(item.scope)} ${item.title || item.id}${item.origin === 'project_evolution' ? ' · 实验' : ''}`)) : [];
|
|
393
394
|
const detail = shown && shown.ref && items.some((item) => item.id === shown.id && item.revision === shown.ref.revision)
|
|
394
395
|
? h('div', { style: BODY },
|
|
395
396
|
shown.loading ? '读取中' : null,
|
|
@@ -686,8 +687,10 @@ window.__ModuleLoader__.load({
|
|
|
686
687
|
const primaryKind = props.primaryKind || 'memory';
|
|
687
688
|
const graph = React.useMemo(() => (props.graphOf || graphOf)(props.records), [props.records, props.graphOf]);
|
|
688
689
|
const [zoom, setZoom] = React.useState(1);
|
|
690
|
+
const [hoveredId, setHoveredId] = React.useState('');
|
|
689
691
|
const selectedId = props.selectedId;
|
|
690
|
-
const labelSet = graph.people <=
|
|
692
|
+
const labelSet = graph.people <= 40;
|
|
693
|
+
const pickHub = props.onPickHub;
|
|
691
694
|
const stepZoom = (delta) => setZoom((value) => Math.min(2.5, Math.max(0.75, Math.round((value + delta) * 100) / 100)));
|
|
692
695
|
const frame = React.useMemo(() => {
|
|
693
696
|
const list = graph.nodes;
|
|
@@ -765,26 +768,48 @@ window.__ModuleLoader__.load({
|
|
|
765
768
|
h('g', null, graph.nodes.map((node) => {
|
|
766
769
|
const isPrimary = node.kind === primaryKind;
|
|
767
770
|
const active = node.id === selectedId;
|
|
771
|
+
const hovered = node.id === hoveredId;
|
|
772
|
+
const pick = isPrimary ? props.onPick : (pickHub || undefined);
|
|
768
773
|
const fill = !isPrimary ? 'var(--dsw-alias-label-secondary)'
|
|
769
774
|
: node.state ? (STAGE_TONE[node.state] || 'var(--dsw-alias-brand-primary)')
|
|
770
775
|
: node.experimental ? 'var(--dsw-alias-state-warn-primary)' : 'var(--dsw-alias-brand-primary)';
|
|
771
|
-
const showLabel = active || (isPrimary && labelSet) || !isPrimary;
|
|
776
|
+
const showLabel = active || hovered || (isPrimary && labelSet) || !isPrimary;
|
|
777
|
+
const nameChars = active || hovered ? 14 : isPrimary ? 7 : 10;
|
|
772
778
|
return h('g', {
|
|
773
779
|
key: node.id,
|
|
774
780
|
transform: `translate(${node.x} ${node.y})`,
|
|
775
|
-
onClick:
|
|
776
|
-
|
|
781
|
+
onClick: pick ? () => pick(node) : undefined,
|
|
782
|
+
onMouseEnter: () => setHoveredId(node.id),
|
|
783
|
+
onMouseLeave: () => setHoveredId((value) => (value === node.id ? '' : value)),
|
|
784
|
+
style: { cursor: pick ? 'pointer' : 'default' },
|
|
777
785
|
},
|
|
786
|
+
h('circle', { r: Math.max(12, node.radius + 6), fill: 'transparent' }),
|
|
778
787
|
active ? h('circle', { r: node.radius + 5, fill: 'none', stroke: 'var(--dsw-alias-brand-primary)', strokeWidth: 1.4, opacity: 0.5 }) : null,
|
|
779
|
-
h('circle', {
|
|
788
|
+
h('circle', {
|
|
789
|
+
r: node.radius, fill,
|
|
790
|
+
opacity: active ? 1 : isPrimary ? 0.9 : 0.5,
|
|
791
|
+
stroke: hovered ? 'var(--dsw-alias-brand-primary)' : 'none',
|
|
792
|
+
strokeWidth: hovered ? 1.2 : 0,
|
|
793
|
+
}),
|
|
780
794
|
showLabel ? h('text', {
|
|
781
795
|
y: node.radius + 12, textAnchor: 'middle',
|
|
782
796
|
fill: active ? 'var(--dsw-alias-label-primary)' : 'var(--dsw-alias-label-secondary)',
|
|
783
|
-
fontSize: isPrimary ?
|
|
784
|
-
|
|
797
|
+
fontSize: isPrimary ? 9.5 : 8.5, opacity: active ? 0.95 : 0.72,
|
|
798
|
+
paintOrder: 'stroke', stroke: 'var(--dsw-alias-bg-layer-1)', strokeWidth: 3,
|
|
799
|
+
strokeLinejoin: 'round',
|
|
800
|
+
}, SHORT(node.label, nameChars)) : null,
|
|
785
801
|
h('title', null, node.title || node.label),
|
|
786
802
|
);
|
|
787
803
|
}))),
|
|
804
|
+
h('div', {
|
|
805
|
+
style: {
|
|
806
|
+
position: 'absolute', left: '10px', bottom: '8px', zIndex: 1,
|
|
807
|
+
maxWidth: 'calc(100% - 60px)', padding: '2px 6px', borderRadius: '6px',
|
|
808
|
+
background: 'var(--dsw-alias-bg-layer-1)',
|
|
809
|
+
color: 'var(--dsw-alias-label-secondary)', fontSize: '11px', lineHeight: '1.5',
|
|
810
|
+
pointerEvents: 'none',
|
|
811
|
+
},
|
|
812
|
+
}, props.legend || '圆点 记忆 · 小点 标签/主题 · 虚线 关联'),
|
|
788
813
|
);
|
|
789
814
|
};
|
|
790
815
|
|
|
@@ -1126,6 +1151,11 @@ window.__ModuleLoader__.load({
|
|
|
1126
1151
|
const changedCount = activity && typeof activity === 'object' && Number.isInteger(activity.changed)
|
|
1127
1152
|
? activity.changed
|
|
1128
1153
|
: (Number.isInteger(activity && activity.count) ? activity.count : null);
|
|
1154
|
+
const activityReason = activity && typeof activity === 'object' && typeof activity.reason === 'string'
|
|
1155
|
+
? activity.reason
|
|
1156
|
+
: (latestReceipt && typeof latestReceipt.reason === 'string' ? latestReceipt.reason : '');
|
|
1157
|
+
const staleLearning = /没采用|作废|已更新|换到别的作品/.test(activityReason);
|
|
1158
|
+
const activityReasonText = staleLearning ? '整理过程中反馈或作品变了,这轮结果没采用。' : activityReason;
|
|
1129
1159
|
let activityText = '';
|
|
1130
1160
|
if (!evolution && memoryOn) {
|
|
1131
1161
|
activityText = '读不到自进化状态';
|
|
@@ -1142,7 +1172,7 @@ window.__ModuleLoader__.load({
|
|
|
1142
1172
|
} else if (activityCode === 'quota' || activityCode === 'exhausted') {
|
|
1143
1173
|
activityText = '本轮整理额度已用完';
|
|
1144
1174
|
} else if (activityCode === 'failed' || activityCode === 'error') {
|
|
1145
|
-
activityText = '最近整理失败,可查看原因';
|
|
1175
|
+
activityText = staleLearning ? '这轮整理没采用' : '最近整理失败,可查看原因';
|
|
1146
1176
|
} else if (activityCode === 'waiting' || activityCode === 'idle' || activityCode === 'empty') {
|
|
1147
1177
|
activityText = '等待可学习的反馈';
|
|
1148
1178
|
}
|
|
@@ -1155,16 +1185,12 @@ window.__ModuleLoader__.load({
|
|
|
1155
1185
|
else if (rules.length === 0) activityText = countsUnknown ? '等待可学习的反馈' : '还没有整理出经验';
|
|
1156
1186
|
else activityText = '暂无整理记录';
|
|
1157
1187
|
}
|
|
1158
|
-
const activityReason = activity && typeof activity === 'object' && typeof activity.reason === 'string'
|
|
1159
|
-
? activity.reason
|
|
1160
|
-
: (latestReceipt && typeof latestReceipt.reason === 'string' ? latestReceipt.reason : '');
|
|
1161
|
-
|
|
1162
1188
|
const filtered = React.useMemo(() => {
|
|
1163
1189
|
const needle = query.trim().toLowerCase();
|
|
1164
1190
|
if (!needle) return records;
|
|
1165
1191
|
return records.filter((record) => {
|
|
1166
1192
|
const tags = Array.isArray(record.tags) ? record.tags.join(' ') : '';
|
|
1167
|
-
return `${record.title} ${record.summary} ${tags} ${KIND_LABEL[record.kind] || ''}`.toLowerCase().includes(needle);
|
|
1193
|
+
return `${record.title} ${record.summary} ${tags} ${record.topicKey || ''} ${KIND_LABEL[record.kind] || ''}`.toLowerCase().includes(needle);
|
|
1168
1194
|
});
|
|
1169
1195
|
}, [records, query]);
|
|
1170
1196
|
const filteredRules = React.useMemo(() => {
|
|
@@ -1355,7 +1381,15 @@ window.__ModuleLoader__.load({
|
|
|
1355
1381
|
const memoryGraphPane = h('div', {
|
|
1356
1382
|
style: { ...PANEL_CARD, padding: 0, minHeight: 0, overflow: 'hidden', display: 'flex' },
|
|
1357
1383
|
},
|
|
1358
|
-
h(MemoryGraph, {
|
|
1384
|
+
h(MemoryGraph, {
|
|
1385
|
+
records: filtered, selectedId: selectedMemory, onPick: onPickNode,
|
|
1386
|
+
onPickHub: (node) => {
|
|
1387
|
+
setQuery(node.kind === 'tag' ? node.label.replace(/^#/, '') : node.label);
|
|
1388
|
+
setMemoryView('list');
|
|
1389
|
+
},
|
|
1390
|
+
emptyText: query ? '没有匹配的记忆' : '还没有记忆', ariaLabel: '记忆关系图',
|
|
1391
|
+
legend: '圆点 记忆 · 小点 标签/主题 · 虚线 关联 · 点任意点筛选或查看',
|
|
1392
|
+
}));
|
|
1359
1393
|
const memoryTab = h('div', { role: 'tabpanel', 'aria-label': '记忆', style: { flex: 1, minHeight: 0, display: 'flex', flexDirection: 'column', gap: '9px' } },
|
|
1360
1394
|
h('div', { style: { display: 'flex', alignItems: 'center', gap: '8px', flex: '0 0 auto' } },
|
|
1361
1395
|
h('input', {
|
|
@@ -1452,6 +1486,7 @@ window.__ModuleLoader__.load({
|
|
|
1452
1486
|
records: filteredRules, graphOf: ruleGraphOf, primaryKind: 'rule',
|
|
1453
1487
|
selectedId: selectedRule, onPick: onPickRuleNode,
|
|
1454
1488
|
emptyText: evolutionEmptyText, ariaLabel: '经验关系图',
|
|
1489
|
+
legend: '圆点 经验 · 小点 条件 · 虚线 共用条件 · 点圆点查看',
|
|
1455
1490
|
}));
|
|
1456
1491
|
const evolutionTab = evolutionFailed ? evolutionFailed : h('div', { role: 'tabpanel', 'aria-label': '自进化', style: { flex: 1, minHeight: 0, display: 'flex', flexDirection: 'column', gap: '9px' } },
|
|
1457
1492
|
h('div', { style: { ...PANEL_CARD, padding: '9px 11px', display: 'flex', alignItems: 'flex-start', gap: '8px', flex: '0 0 auto' } },
|
|
@@ -1459,9 +1494,9 @@ window.__ModuleLoader__.load({
|
|
|
1459
1494
|
h('div', { style: { display: 'flex', flexDirection: 'column', gap: '2px', minWidth: 0 } },
|
|
1460
1495
|
h('div', { style: { fontSize: '12px', lineHeight: '1.55' } }, activityText || (rules.length === 0 ? '还没有整理出经验' : '暂无整理记录')),
|
|
1461
1496
|
countsUnknown ? h('div', { style: PANEL_MUTED }, '数量未知') : null,
|
|
1462
|
-
|
|
1497
|
+
activityReasonText
|
|
1463
1498
|
? (showReason
|
|
1464
|
-
? h('div', { style: { ...PANEL_MUTED, whiteSpace: 'pre-wrap', wordBreak: 'break-word' } },
|
|
1499
|
+
? h('div', { style: { ...PANEL_MUTED, whiteSpace: 'pre-wrap', wordBreak: 'break-word' } }, activityReasonText)
|
|
1465
1500
|
: h(Action, { onClick: () => setShowReason(true) }, '查看原因'))
|
|
1466
1501
|
: null,
|
|
1467
1502
|
),
|
|
@@ -1536,8 +1571,8 @@ window.__ModuleLoader__.load({
|
|
|
1536
1571
|
if (!sessionId && refs.length === 0) return null;
|
|
1537
1572
|
if (groups.found.length === 0 && groups.provided.length === 0 && groups.update.length === 0) return null;
|
|
1538
1573
|
return h('div', { style: TAIL },
|
|
1539
|
-
h(RefSection, { label: '
|
|
1540
|
-
h(RefSection, { label: '
|
|
1574
|
+
h(RefSection, { label: '搜索命中', items: groups.found, sessionId, turnKey: turn }),
|
|
1575
|
+
h(RefSection, { label: '读过正文', items: groups.provided, sessionId, turnKey: turn }),
|
|
1541
1576
|
h(RefSection, { label: '本轮更新', items: groups.update, sessionId, turnKey: turn }),
|
|
1542
1577
|
);
|
|
1543
1578
|
};
|
package/index.js
CHANGED
|
@@ -1361,7 +1361,7 @@ export function apply(ctx, config) {
|
|
|
1361
1361
|
|
|
1362
1362
|
ctx.effect(() => tools.register({
|
|
1363
1363
|
name: 'memory_save',
|
|
1364
|
-
description:
|
|
1364
|
+
description: `保存一条记忆。只在会改变以后判断时才存;先搜已有条目,能更新就不要新建。保存前先蒸馏:标题是结论,正文只写原则与触发条件,通常 2–4 句;路径、字段表、完整命令、版本号、尺寸数值不要抄进正文,改成文档或 skill 的指针。用户明确说「记住」时直接调用;用户自己的话已经是长期规则(「以后都这样」「别再 X」)时也直接调用,不要先问。更新同一件事时带 id:身份只看标题、旧标题和前置条件,像不像不授权覆盖。文本相近但标题不同时先不写,返回 needsResolution 与 1–2 条相近条目:确实是同一条就用那个 id 更新,确实是另一条才带 resolution="distinct" 再存。带 id 更新时没给的字段保持原值,正文整段替换,旧正文只留作内部撤回,不展示也不参与检索。条数到硬上限时把同层最久没被读的一条移进归档,结果里写明动了哪条,不拒绝新记忆;单条正文超限只报错、不截断。前置条件不同的同一标题是两条。${NOTES}`,
|
|
1365
1365
|
parameters: SAVE_PARAMS,
|
|
1366
1366
|
output: {
|
|
1367
1367
|
presentationMeta: (_args, value) => ({
|
|
@@ -2250,7 +2250,7 @@ export function apply(ctx, config) {
|
|
|
2250
2250
|
if (sawTool) throw new Error('整理返回了工具调用');
|
|
2251
2251
|
/** 运行期间用户又给了新反馈:旧结果不能覆盖新纠正,直接作废,等排队的下一次整理。 */
|
|
2252
2252
|
if ((feedbackRevisions.get(scope.key) || 0) !== candidate.feedbackRevision) {
|
|
2253
|
-
throw new Error('
|
|
2253
|
+
throw new Error('反馈已更新,这轮结果没采用');
|
|
2254
2254
|
}
|
|
2255
2255
|
const parsed = learning.parseOutput(text, { finish, refs: input.refs, rules: input.rules, ranges });
|
|
2256
2256
|
if (!parsed.ok) throw new Error(parsed.reason);
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "dsh-lite-memory",
|
|
3
|
-
"version": "0.16.
|
|
3
|
+
"version": "0.16.4",
|
|
4
4
|
"description": "On-demand memory for the DeepSeek Harness: nothing in the system prompt, one muted line only when something is saved, hit or suggested.",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"main": "index.js",
|
package/store.js
CHANGED
|
@@ -2546,7 +2546,7 @@ export function commitLearning(scope, batch = {}) {
|
|
|
2546
2546
|
if (!episode) throw new Error('作品已经失效,这次整理作废');
|
|
2547
2547
|
if (batch.sessionId) {
|
|
2548
2548
|
const focus = evolution.tasks?.sessions?.[batch.sessionId];
|
|
2549
|
-
if (!focus || focus.episodeId !== episodeId) throw new Error('
|
|
2549
|
+
if (!focus || focus.episodeId !== episodeId) throw new Error('这期已经换到别的作品,这轮结果没采用');
|
|
2550
2550
|
}
|
|
2551
2551
|
if (batch.episodeUpdatedAt && episode.updatedAt !== batch.episodeUpdatedAt) throw new Error('用户又改过这期,这次整理作废');
|
|
2552
2552
|
const prepared = new Map((Array.isArray(batch.preparedTargets) ? batch.preparedTargets : []).map((row) => [row.id, row]));
|
package/test/learning.test.mjs
CHANGED
|
@@ -433,7 +433,7 @@ test('P06 作品版本变了旧整理整批作废', () => {
|
|
|
433
433
|
store.saveTaskState(scope, later.tasks, { operationId: 'tasks_later' });
|
|
434
434
|
const stale = commitNew(scope, grown, started, NEW_CHANGE, { now: Date.now() + 2000 });
|
|
435
435
|
assert.equal(stale.ok, false);
|
|
436
|
-
assert.match(stale.reason,
|
|
436
|
+
assert.match(stale.reason, /换到别的作品|改过这期|失效|没采用/);
|
|
437
437
|
assert.equal(ruleRows(scope).length, 0);
|
|
438
438
|
});
|
|
439
439
|
|