dsh-lite-memory 0.18.29 → 0.18.30
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 +38 -10
- package/docs/RELEASE-0.18.30.md +7 -0
- package/index.js +33 -5
- package/package.json +1 -1
- package/store.js +39 -17
- package/test/contract.test.mjs +68 -5
- package/test/crossing.test.mjs +1 -1
package/client.js
CHANGED
|
@@ -2157,6 +2157,7 @@ window.__ModuleLoader__.load({
|
|
|
2157
2157
|
const [display, setDisplay] = React.useState({ ...DISPLAY_DEFAULTS });
|
|
2158
2158
|
const [hubFilter, setHubFilter] = React.useState(null);
|
|
2159
2159
|
const [checkup, setCheckup] = React.useState(false);
|
|
2160
|
+
const [expandedConflict, setExpandedConflict] = React.useState('');
|
|
2160
2161
|
const alive = React.useRef(true);
|
|
2161
2162
|
const generation = React.useRef(0);
|
|
2162
2163
|
const scopeRef = React.useRef('');
|
|
@@ -2205,10 +2206,13 @@ window.__ModuleLoader__.load({
|
|
|
2205
2206
|
if (!row || busy) return;
|
|
2206
2207
|
setBusy(true);
|
|
2207
2208
|
setNote('');
|
|
2208
|
-
post('/lite-memory/conflict', {
|
|
2209
|
+
post('/lite-memory/conflict', {
|
|
2210
|
+
sessionId, scopeKey: selectedKey, conflictId: row.id, expectedRevision: row.revision, op,
|
|
2211
|
+
}).then((result) => {
|
|
2209
2212
|
if (!alive.current) return;
|
|
2210
2213
|
setBusy(false);
|
|
2211
2214
|
if (result.ok && result.body && result.body.ok) {
|
|
2215
|
+
setExpandedConflict('');
|
|
2212
2216
|
if (op === 'drop') setNote('已忽略这条待裁决');
|
|
2213
2217
|
load(selectedKey);
|
|
2214
2218
|
} else setNote(failure(result, '待裁决没处理成'));
|
|
@@ -2220,6 +2224,7 @@ window.__ModuleLoader__.load({
|
|
|
2220
2224
|
setDisplay(readDisplaySettings(selectedKey));
|
|
2221
2225
|
setShowSettings(false);
|
|
2222
2226
|
setHubFilter(null);
|
|
2227
|
+
setExpandedConflict('');
|
|
2223
2228
|
}, [selectedKey]);
|
|
2224
2229
|
React.useEffect(() => {
|
|
2225
2230
|
setMemoryView((value) => viewModeOf(display, value));
|
|
@@ -2726,18 +2731,41 @@ window.__ModuleLoader__.load({
|
|
|
2726
2731
|
const memoryViewActive = viewModeOf(display, memoryView);
|
|
2727
2732
|
const evolutionViewActive = graphViewOf(display.graph, evolutionView);
|
|
2728
2733
|
|
|
2734
|
+
const conflictReadOnly = !memoryOn || (!globalView && selectedKey !== currentKey);
|
|
2735
|
+
const draftScope = (row) => (row.scope === 'global' ? '总记忆' : '本项目');
|
|
2729
2736
|
const conflictBar = queued.length === 0 ? null : h('div', {
|
|
2730
2737
|
style: { display: 'flex', flexDirection: 'column', gap: '3px', padding: '7px 9px 8px', borderBottom: '1px solid var(--dsw-alias-border-l1)', flex: '0 0 auto' },
|
|
2731
2738
|
},
|
|
2732
|
-
h('div', { style: { ...PANEL_MUTED, lineHeight: '18px' } }, `${queued.length}
|
|
2733
|
-
queued.slice(0, 2).map((row) =>
|
|
2734
|
-
|
|
2735
|
-
|
|
2736
|
-
|
|
2737
|
-
|
|
2738
|
-
|
|
2739
|
-
|
|
2740
|
-
|
|
2739
|
+
h('div', { style: { ...PANEL_MUTED, lineHeight: '18px' } }, `${queued.length} 条保存没确认${conflictReadOnly ? ' · 只读' : ''}`),
|
|
2740
|
+
queued.slice(0, 2).map((row) => {
|
|
2741
|
+
const open = expandedConflict === row.id;
|
|
2742
|
+
const draft = row.draft || {};
|
|
2743
|
+
return h('div', {
|
|
2744
|
+
key: row.id || row.title,
|
|
2745
|
+
style: { display: 'flex', flexDirection: 'column', gap: '2px', minWidth: 0 },
|
|
2746
|
+
},
|
|
2747
|
+
h('div', { style: { display: 'flex', alignItems: 'center', gap: '8px', minWidth: 0 } },
|
|
2748
|
+
h('button', {
|
|
2749
|
+
type: 'button',
|
|
2750
|
+
'aria-expanded': open,
|
|
2751
|
+
onClick: () => setExpandedConflict(open ? '' : row.id),
|
|
2752
|
+
style: {
|
|
2753
|
+
flex: 1, minWidth: 0, padding: 0, border: 0, background: 'transparent', color: 'inherit',
|
|
2754
|
+
textAlign: 'left', font: 'inherit', fontSize: '12px', lineHeight: '20px',
|
|
2755
|
+
overflow: 'hidden', textOverflow: 'ellipsis', whiteSpace: 'nowrap', cursor: 'pointer',
|
|
2756
|
+
},
|
|
2757
|
+
}, row.title),
|
|
2758
|
+
conflictReadOnly ? null : h(Action, { disabled: busy, onClick: () => resolveQueued(row, 'keep') }, '另存'),
|
|
2759
|
+
conflictReadOnly ? null : h(Action, { disabled: busy, onClick: () => resolveQueued(row, 'drop') }, '忽略'),
|
|
2760
|
+
),
|
|
2761
|
+
open ? h('div', {
|
|
2762
|
+
style: { ...PANEL_MUTED, padding: '2px 0 4px', lineHeight: '18px', overflowWrap: 'anywhere' },
|
|
2763
|
+
},
|
|
2764
|
+
h('div', null, `范围:${draftScope(row)}`),
|
|
2765
|
+
draft.when ? h('div', null, `适用:${draft.when}`) : null,
|
|
2766
|
+
h('div', null, String(draft.body || draft.summary || '(没有正文)').slice(0, 400)),
|
|
2767
|
+
) : null);
|
|
2768
|
+
}));
|
|
2741
2769
|
const memoryListPane = h('div', {
|
|
2742
2770
|
style: {
|
|
2743
2771
|
...PANEL_CARD, padding: '2px 4px', minHeight: 0, overflowY: 'auto',
|
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
# 0.18.30
|
|
2
|
+
|
|
3
|
+
- 待裁决入口按「当前会话项目 + 目标层 + 草稿版本」核验:跨项目请求直接拒绝,不再把别的 scope 静默解释成当前项目。
|
|
4
|
+
- 项目记忆关闭时,本入口(含选全局层另存)一律拒绝,不绕过已有总开关。
|
|
5
|
+
- 队列行增加稳定 id 与 revision;草稿被后续反馈替换后,旧面板的「另存」返回 `stale_draft`,不会保存用户没看过的新正文。
|
|
6
|
+
- 面板可展开一条待裁决,看草稿正文、适用范围和目标层;关闭或跨项目浏览时显示只读,不出现操作按钮。
|
|
7
|
+
- 回归覆盖:跨项目拒绝、关闭态拒绝、旧版本拒绝、全局草稿在项目关闭时拒绝。
|
package/index.js
CHANGED
|
@@ -553,14 +553,42 @@ function keepHandler(webServer, cwdForSession) {
|
|
|
553
553
|
const cwd = typeof cwdForSession === 'function' ? cwdForSession(sessionId) : '';
|
|
554
554
|
if (!cwd) return sendJson(res, 409, { ok: false, reason: '这个会话还没有项目目录' });
|
|
555
555
|
const projectScope = scopeOf(cwd);
|
|
556
|
+
if (!projectScope.confirmed) return sendJson(res, 409, { ok: false, reason: '项目根还没确认,先重新打开这个项目' });
|
|
557
|
+
/** 请求里的目标必须与当前会话项目一致;别的项目只读,不能借面板改过去。 */
|
|
556
558
|
const requested = typeof body.scopeKey === 'string' ? body.scopeKey : '';
|
|
559
|
+
if (!requested) return sendJson(res, 400, { ok: false, reason: '缺少目标范围' });
|
|
560
|
+
if (requested !== 'global' && requested !== projectScope.key) {
|
|
561
|
+
return sendJson(res, 409, { ok: false, reason: '别的项目只读,回到那个项目里再改' });
|
|
562
|
+
}
|
|
557
563
|
const target = requested === 'global' ? globalScope() : projectScope;
|
|
558
|
-
|
|
559
|
-
if (!
|
|
564
|
+
/** 关闭的项目记忆不能从这个入口绕过;选全局时全局总开关也必须开着。 */
|
|
565
|
+
if (!memoryEnabled(projectScope)) {
|
|
566
|
+
return sendJson(res, 409, { ok: false, code: 'memory_disabled', reason: memoryOffCopy(projectScope) });
|
|
567
|
+
}
|
|
568
|
+
if (target.key === 'global' && !memoryEnabled(target)) {
|
|
569
|
+
return sendJson(res, 409, { ok: false, code: 'memory_disabled', reason: '总记忆已关闭,先在面板打开' });
|
|
570
|
+
}
|
|
571
|
+
const op = body.op;
|
|
572
|
+
if (op !== 'keep' && op !== 'drop') return sendJson(res, 400, { ok: false, reason: '不认识这个操作' });
|
|
573
|
+
const conflictId = typeof body.conflictId === 'string' ? body.conflictId.trim() : '';
|
|
574
|
+
if (!conflictId) return sendJson(res, 400, { ok: false, reason: '缺少这条待裁决' });
|
|
575
|
+
const expectedRevision = Number(body.expectedRevision);
|
|
576
|
+
if (!Number.isInteger(expectedRevision) || expectedRevision < 1) {
|
|
577
|
+
return sendJson(res, 409, { ok: false, reason: '这条待裁决的版本不清楚,先刷新再处理' });
|
|
578
|
+
}
|
|
560
579
|
try {
|
|
561
|
-
const
|
|
562
|
-
|
|
563
|
-
|
|
580
|
+
const ref = { id: conflictId, expectedRevision };
|
|
581
|
+
const outcome = op === 'drop' ? dropConflict(target, ref) : resolveConflict(target, ref);
|
|
582
|
+
if (!outcome.ok) {
|
|
583
|
+
return sendJson(res, outcome.reasonCode === 'stale_draft' ? 409 : 409, {
|
|
584
|
+
ok: false, code: outcome.reasonCode || '', reason: outcome.reason || '这条待裁决还没处理成',
|
|
585
|
+
});
|
|
586
|
+
}
|
|
587
|
+
return sendJson(res, 200, {
|
|
588
|
+
ok: true,
|
|
589
|
+
remaining: conflictsOf(target).length,
|
|
590
|
+
record: outcome.record ? { id: outcome.record.id, title: outcome.record.title, revision: outcome.record.revision } : null,
|
|
591
|
+
});
|
|
564
592
|
} catch (error) {
|
|
565
593
|
return sendJson(res, 409, { ok: false, reason: error instanceof Error ? error.message : '待裁决处理失败' });
|
|
566
594
|
}
|
package/package.json
CHANGED
package/store.js
CHANGED
|
@@ -2761,20 +2761,32 @@ function queueConflictLocked(scope, draft, candidates) {
|
|
|
2761
2761
|
if (hit) {
|
|
2762
2762
|
hit.candidates = view;
|
|
2763
2763
|
hit.draft = replay;
|
|
2764
|
+
/** 草稿被后来的反馈替换过:版本必须涨,旧面板不能拿旧版直接批准。 */
|
|
2765
|
+
hit.revision = (Number(hit.revision) || 1) + 1;
|
|
2764
2766
|
hit.updatedAt = now;
|
|
2765
2767
|
} else {
|
|
2766
|
-
rows.push({ id: `cf_${crypto.randomUUID().replace(/-/g, '').slice(0, 12)}`, title, scope: scopeKey, candidates: view, draft: replay, createdAt: now, updatedAt: now });
|
|
2768
|
+
rows.push({ id: `cf_${crypto.randomUUID().replace(/-/g, '').slice(0, 12)}`, title, scope: scopeKey, candidates: view, draft: replay, revision: 1, createdAt: now, updatedAt: now });
|
|
2767
2769
|
}
|
|
2768
2770
|
index.conflicts = rows.slice(-CONFLICT_LIMIT);
|
|
2769
2771
|
saveIndex(scope, index);
|
|
2770
2772
|
return index.conflicts.length;
|
|
2771
2773
|
}
|
|
2772
2774
|
|
|
2773
|
-
|
|
2775
|
+
/** 按稳定 id 找;旧调用还传标题,只为兼容已有数据与测试。 */
|
|
2776
|
+
function conflictRowLocked(scope, ref) {
|
|
2777
|
+
const rows = Array.isArray(loadIndex(scope).conflicts) ? loadIndex(scope).conflicts : [];
|
|
2778
|
+
const layer = scope.key === 'global' ? 'global' : 'project';
|
|
2779
|
+
if (ref && typeof ref === 'object' && ref.id) return rows.find((row) => row.id === ref.id && row.scope === layer) || null;
|
|
2780
|
+
const norm = normalizeText(typeof ref === 'string' ? ref : '', 120);
|
|
2781
|
+
return rows.find((row) => row.title === norm && row.scope === layer) || null;
|
|
2782
|
+
}
|
|
2783
|
+
|
|
2784
|
+
function clearConflictLocked(scope, ref) {
|
|
2774
2785
|
const index = loadIndex(scope);
|
|
2775
2786
|
const rows = Array.isArray(index.conflicts) ? index.conflicts : [];
|
|
2776
|
-
const
|
|
2777
|
-
const
|
|
2787
|
+
const id = ref && typeof ref === 'object' && ref.id ? ref.id : '';
|
|
2788
|
+
const norm = id ? '' : normalizeText(typeof ref === 'string' ? ref : '', 120);
|
|
2789
|
+
const next = rows.filter((row) => (id ? row.id !== id : !(row.title === norm && row.scope === (scope.key === 'global' ? 'global' : 'project'))));
|
|
2778
2790
|
if (next.length === rows.length) return rows.length;
|
|
2779
2791
|
index.conflicts = next;
|
|
2780
2792
|
saveIndex(scope, index);
|
|
@@ -2787,28 +2799,38 @@ export function conflictsOf(scope) {
|
|
|
2787
2799
|
return Array.isArray(rows) ? JSON.parse(JSON.stringify(rows)) : [];
|
|
2788
2800
|
}
|
|
2789
2801
|
|
|
2790
|
-
/**
|
|
2791
|
-
|
|
2792
|
-
|
|
2793
|
-
|
|
2794
|
-
|
|
2802
|
+
/** 版本对不上就拒绝:用户看到的草稿和现在这条不是同一版。 */
|
|
2803
|
+
function staleDraft(row, ref) {
|
|
2804
|
+
if (!ref || typeof ref !== 'object' || ref.expectedRevision === undefined) return false;
|
|
2805
|
+
return Number(ref.expectedRevision) !== (Number(row.revision) || 1);
|
|
2806
|
+
}
|
|
2807
|
+
|
|
2808
|
+
/** 确认另存:用队列里的草稿按 distinct 重放一次,成功后按 id 出队。 */
|
|
2809
|
+
export function resolveConflict(scope, ref, options = {}) {
|
|
2810
|
+
const row = conflictRowLocked(scope, ref);
|
|
2795
2811
|
if (!row) return { ok: false, reason: '这条待裁决已经不在了,刷新面板看看' };
|
|
2812
|
+
if (staleDraft(row, ref)) return { ok: false, reasonCode: 'stale_draft', reason: '这条待裁决又更新了,刷新后再处理' };
|
|
2796
2813
|
const draft = { ...(row.draft || {}), resolution: 'distinct', writeMode: options.writeMode === 'append' ? 'append' : 'patch' };
|
|
2797
2814
|
if (scope.key === 'global') draft.scope = 'global';
|
|
2798
2815
|
else draft.scope = 'project';
|
|
2799
2816
|
if (!draft.title) draft.title = row.title;
|
|
2800
2817
|
const result = saveEntry(scope, draft);
|
|
2801
|
-
if (result.needsResolution) return { ok: false, reason: '还是相近:换一个标题或等确认', result };
|
|
2802
|
-
if (result.needsScope)
|
|
2803
|
-
|
|
2804
|
-
|
|
2818
|
+
if (result && result.needsResolution) return { ok: false, reason: '还是相近:换一个标题或等确认', result };
|
|
2819
|
+
if (result && result.needsScope) {
|
|
2820
|
+
const reason = result.reasonCode === 'memory_disabled' ? '这层记忆已关闭' : '这条记忆的适用范围还没定,先说明当前环境';
|
|
2821
|
+
return { ok: false, reason, result };
|
|
2822
|
+
}
|
|
2823
|
+
clearConflictLocked(scope, { id: row.id });
|
|
2824
|
+
return { ok: true, result, record: result && result.record };
|
|
2805
2825
|
}
|
|
2806
2826
|
|
|
2807
2827
|
/** 删掉待裁决:用户决定不再另存。 */
|
|
2808
|
-
export function dropConflict(scope,
|
|
2809
|
-
const
|
|
2810
|
-
|
|
2811
|
-
|
|
2828
|
+
export function dropConflict(scope, ref) {
|
|
2829
|
+
const row = conflictRowLocked(scope, ref);
|
|
2830
|
+
if (!row) return { ok: false, reason: '这条待裁决已经不在了,刷新面板看看' };
|
|
2831
|
+
if (staleDraft(row, ref)) return { ok: false, reasonCode: 'stale_draft', reason: '这条待裁决又更新了,刷新后再处理' };
|
|
2832
|
+
clearConflictLocked(scope, { id: row.id });
|
|
2833
|
+
return { ok: true };
|
|
2812
2834
|
}
|
|
2813
2835
|
|
|
2814
2836
|
/** 当前层还有几条没处理的冲突。 */
|
package/test/contract.test.mjs
CHANGED
|
@@ -1213,6 +1213,7 @@ assert.equal(ovArchivedBody.body.body, '右侧留白。');
|
|
|
1213
1213
|
// 待裁决:面板看得到队列,确认后按草稿另存,不预写正文
|
|
1214
1214
|
const cfCwd = fs.mkdtempSync(path.join(os.tmpdir(), 'lite-memory-contract-conflict-'));
|
|
1215
1215
|
sessionCwds.set('session-conflict-panel', cfCwd);
|
|
1216
|
+
sessionCwds.set('session-conflict-other', fs.mkdtempSync(path.join(os.tmpdir(), 'lite-memory-contract-conflict-other-')));
|
|
1216
1217
|
const cfAgent = { id: 'session-conflict-panel', cwd: cfCwd, session: { id: 'session-conflict-panel', header: { cwd: cfCwd } } };
|
|
1217
1218
|
await defs.get('memory_save').execute({ title: '发布窗口', summary: '只在周四晚上发版', body: '周四晚上再发版。', scope: 'global' }, { agent: cfAgent });
|
|
1218
1219
|
const cfSave = await defs.get('memory_save').execute({ title: '发布窗口', summary: '本项目周五发版', body: '周五发版。' }, { agent: cfAgent });
|
|
@@ -1221,15 +1222,77 @@ assert.equal(store.loadIndex(store.scopeOf(cfCwd)).entries.length, 0, '没确认
|
|
|
1221
1222
|
const cfBefore = await call('/lite-memory/overview', { sessionId: 'session-conflict-panel' });
|
|
1222
1223
|
assert.equal(cfBefore.status, 200);
|
|
1223
1224
|
assert.equal(cfBefore.body.selected.conflicts.length, 1);
|
|
1224
|
-
|
|
1225
|
-
assert.equal(
|
|
1226
|
-
|
|
1225
|
+
const cfRow = cfBefore.body.selected.conflicts[0];
|
|
1226
|
+
assert.equal(cfRow.title, '发布窗口');
|
|
1227
|
+
assert.equal(cfRow.draft.body, '周五发版。');
|
|
1228
|
+
assert.ok(cfRow.id, '待裁决要有稳定 id');
|
|
1229
|
+
assert.equal(cfRow.revision, 1);
|
|
1230
|
+
const cfScopeKey = cfBefore.body.selected.key;
|
|
1231
|
+
|
|
1232
|
+
// 跨项目:当前会话是 A,请求 B 的范围必须拒绝,且不能动到 A 的草稿
|
|
1233
|
+
const cfCross = await call('/lite-memory/conflict', {
|
|
1234
|
+
sessionId: 'session-conflict-panel', scopeKey: '另一个项目', conflictId: cfRow.id, expectedRevision: 1, op: 'drop',
|
|
1235
|
+
});
|
|
1236
|
+
assert.equal(cfCross.status, 409);
|
|
1237
|
+
assert.match(cfCross.body.reason, /只读/);
|
|
1238
|
+
assert.equal(store.loadIndex(store.scopeOf(cfCwd)).conflicts.length, 1, '跨项目请求不改数据');
|
|
1239
|
+
|
|
1240
|
+
// 关闭项目记忆后,本入口也不能落盘
|
|
1241
|
+
await call('/lite-memory/control', { sessionId: 'session-conflict-panel', enabled: false, operationId: 'op_cf_close' });
|
|
1242
|
+
const cfClosed = await call('/lite-memory/conflict', {
|
|
1243
|
+
sessionId: 'session-conflict-panel', scopeKey: cfScopeKey, conflictId: cfRow.id, expectedRevision: 1, op: 'keep',
|
|
1244
|
+
});
|
|
1245
|
+
assert.equal(cfClosed.status, 409);
|
|
1246
|
+
assert.equal(cfClosed.body.code, 'memory_disabled');
|
|
1247
|
+
assert.equal(store.loadIndex(store.scopeOf(cfCwd)).entries.length, 0, '关闭状态不写记忆');
|
|
1248
|
+
await call('/lite-memory/control', { sessionId: 'session-conflict-panel', enabled: true, operationId: 'op_cf_reopen' });
|
|
1249
|
+
|
|
1250
|
+
// 草稿被后来的反馈替换:旧版本点击必须拒绝
|
|
1251
|
+
await defs.get('memory_save').execute({ title: '发布窗口', summary: '本项目改成周六发版', body: '周六发版。' }, { agent: cfAgent });
|
|
1252
|
+
const cfStale = await call('/lite-memory/conflict', {
|
|
1253
|
+
sessionId: 'session-conflict-panel', scopeKey: cfScopeKey, conflictId: cfRow.id, expectedRevision: 1, op: 'keep',
|
|
1254
|
+
});
|
|
1255
|
+
assert.equal(cfStale.status, 409);
|
|
1256
|
+
assert.equal(cfStale.body.code, 'stale_draft');
|
|
1257
|
+
const cfStaleView = await call('/lite-memory/overview', { sessionId: 'session-conflict-panel' });
|
|
1258
|
+
assert.equal(cfStaleView.body.selected.conflicts[0].revision, 2, '草稿换了版本要涨');
|
|
1259
|
+
assert.equal(cfStaleView.body.selected.conflicts[0].draft.body, '周六发版。');
|
|
1260
|
+
|
|
1261
|
+
const cfKeep = await call('/lite-memory/conflict', {
|
|
1262
|
+
sessionId: 'session-conflict-panel', scopeKey: cfScopeKey, conflictId: cfRow.id, expectedRevision: 2, op: 'keep',
|
|
1263
|
+
});
|
|
1227
1264
|
assert.equal(cfKeep.status, 200);
|
|
1228
1265
|
assert.equal(cfKeep.body.ok, true);
|
|
1229
1266
|
const cfAfter = await call('/lite-memory/overview', { sessionId: 'session-conflict-panel' });
|
|
1230
1267
|
assert.equal(cfAfter.body.selected.conflicts.length, 0);
|
|
1231
|
-
assert.ok(cfAfter.body.selected.records.some((row) => row.title === '发布窗口' && row.summary === '
|
|
1232
|
-
const cfAgain = await call('/lite-memory/conflict', {
|
|
1268
|
+
assert.ok(cfAfter.body.selected.records.some((row) => row.title === '发布窗口' && row.summary === '本项目改成周六发版'));
|
|
1269
|
+
const cfAgain = await call('/lite-memory/conflict', {
|
|
1270
|
+
sessionId: 'session-conflict-panel', scopeKey: cfScopeKey, conflictId: cfRow.id, expectedRevision: 2, op: 'keep',
|
|
1271
|
+
});
|
|
1233
1272
|
assert.equal(cfAgain.status, 409, '队列没了就明确说,不重复落库');
|
|
1234
1273
|
|
|
1274
|
+
// 项目记忆关闭时,选全局待裁决也不能借这个入口落盘
|
|
1275
|
+
const cfgCwd = fs.mkdtempSync(path.join(os.tmpdir(), 'lite-memory-contract-conflict-global-'));
|
|
1276
|
+
sessionCwds.set('session-conflict-global', cfgCwd);
|
|
1277
|
+
const cfgAgent = { id: 'session-conflict-global', cwd: cfgCwd, session: { id: 'session-conflict-global', header: { cwd: cfgCwd } } };
|
|
1278
|
+
await defs.get('memory_save').execute({ title: '命名规则', summary: '项目里用短横线', body: '短横线。' }, { agent: cfgAgent });
|
|
1279
|
+
const cfgSave = await defs.get('memory_save').execute({ title: '命名规则', summary: '所有项目用下划线', body: '下划线。', scope: 'global' }, { agent: cfgAgent });
|
|
1280
|
+
assert.equal(cfgSave.needsResolution, true);
|
|
1281
|
+
const cfgView = await call('/lite-memory/overview', { sessionId: 'session-conflict-global', scopeKey: 'global' });
|
|
1282
|
+
assert.equal(cfgView.body.selected.conflicts.length, 1, '全局层也要有这条待裁决');
|
|
1283
|
+
const cfgRow = cfgView.body.selected.conflicts[0];
|
|
1284
|
+
await call('/lite-memory/control', { sessionId: 'session-conflict-global', enabled: false, operationId: 'op_cfg_close' });
|
|
1285
|
+
const cfgClosed = await call('/lite-memory/conflict', {
|
|
1286
|
+
sessionId: 'session-conflict-global', scopeKey: 'global', conflictId: cfgRow.id, expectedRevision: cfgRow.revision, op: 'keep',
|
|
1287
|
+
});
|
|
1288
|
+
assert.equal(cfgClosed.status, 409);
|
|
1289
|
+
assert.equal(cfgClosed.body.code, 'memory_disabled');
|
|
1290
|
+
assert.equal(store.conflictsOf(store.globalScope()).length, 1, '关闭状态不批准全局草稿');
|
|
1291
|
+
await call('/lite-memory/control', { sessionId: 'session-conflict-global', enabled: true, operationId: 'op_cfg_reopen' });
|
|
1292
|
+
const cfgKeep = await call('/lite-memory/conflict', {
|
|
1293
|
+
sessionId: 'session-conflict-global', scopeKey: 'global', conflictId: cfgRow.id, expectedRevision: cfgRow.revision, op: 'keep',
|
|
1294
|
+
});
|
|
1295
|
+
assert.equal(cfgKeep.status, 200);
|
|
1296
|
+
assert.equal(store.conflictsOf(store.globalScope()).length, 0);
|
|
1297
|
+
|
|
1235
1298
|
console.log('contract.test.mjs: all passed');
|
package/test/crossing.test.mjs
CHANGED
|
@@ -434,7 +434,7 @@ await test('S12 待裁决留可重放草稿,面板确认另存后才落库', (
|
|
|
434
434
|
assert.equal(pendingConflictsOf(scope), 0);
|
|
435
435
|
assert.equal(loadIndex(scope).entries.length, 1);
|
|
436
436
|
assert.equal(loadIndex(scope).entries[0].body, '项目补充');
|
|
437
|
-
assert.equal(dropConflict(scope, '不存在'), false);
|
|
437
|
+
assert.equal(dropConflict(scope, '不存在').ok, false);
|
|
438
438
|
});
|
|
439
439
|
|
|
440
440
|
console.log(`crossing.test.mjs: ${passed} passed`);
|