@modusensus/dsh-mneme 0.7.32 → 0.8.1
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 +44 -20
- package/lib/api.js +100 -12
- package/lib/client.js +276 -4
- package/lib/commands.js +30 -3
- package/lib/config.js +21 -0
- package/lib/dream/sleep.js +43 -12
- package/lib/dream.js +43 -2
- package/lib/inject.js +39 -5
- package/lib/lang.js +23 -0
- package/lib/scope.js +118 -0
- package/lib/service.js +374 -62
- package/lib/settings.js +7 -0
- package/lib/store.js +245 -7
- package/lib/tools.js +138 -17
- package/lib/version-check.js +122 -0
- package/package.json +1 -1
- package/src/api.js +100 -12
- package/src/commands.js +30 -3
- package/src/config.js +21 -0
- package/src/dream/sleep.js +43 -12
- package/src/dream.js +43 -2
- package/src/inject.js +39 -5
- package/src/lang.js +23 -0
- package/src/scope.js +118 -0
- package/src/service.js +374 -62
- package/src/settings.js +7 -0
- package/src/store.js +245 -7
- package/src/tools.js +138 -17
- package/src/version-check.js +122 -0
- package/test/api.test.js +90 -4
- package/test/client.test.js +58 -0
- package/test/commands.test.js +40 -3
- package/test/conflict-freeze.test.js +15 -5
- package/test/inject.test.js +93 -0
- package/test/scope-candidates.test.js +239 -0
- package/test/scope-explicit.test.js +452 -0
- package/test/scope-retrieval.test.js +197 -0
- package/test/scope-storage.test.js +255 -0
- package/test/scope-strict.test.js +231 -0
- package/test/service.test.js +76 -0
- package/test/version-check.test.js +152 -0
package/lib/store.js
CHANGED
|
@@ -148,6 +148,25 @@ CREATE TABLE IF NOT EXISTS conflict_pending (
|
|
|
148
148
|
);
|
|
149
149
|
CREATE INDEX IF NOT EXISTS idx_conflict_pending_unresolved ON conflict_pending(resolved_at);
|
|
150
150
|
|
|
151
|
+
-- scope_changes: v0.8.1 底座(issue #170)scope 归属的人工修正审计。显式声明
|
|
152
|
+
-- (memory_save/memory_update 的 scope 参数、面板编辑)每次改变某条记忆的
|
|
153
|
+
-- agent/workspace 归属都落一行:改动前后值 + 来源 + actor(tool=模型侧 /
|
|
154
|
+
-- panel=人工侧)。与 conflict_pending 同款的 bookkeeping 表:只做审计,
|
|
155
|
+
-- 不触发 write hooks。放宽可见性(label→global)的候选筛选与回放都靠它。
|
|
156
|
+
CREATE TABLE IF NOT EXISTS scope_changes (
|
|
157
|
+
id TEXT PRIMARY KEY,
|
|
158
|
+
memory_id TEXT NOT NULL,
|
|
159
|
+
actor TEXT NOT NULL,
|
|
160
|
+
prev_agent_scope TEXT,
|
|
161
|
+
prev_workspace_scope TEXT,
|
|
162
|
+
next_agent_scope TEXT,
|
|
163
|
+
next_workspace_scope TEXT,
|
|
164
|
+
agent_scope_source TEXT,
|
|
165
|
+
workspace_scope_source TEXT,
|
|
166
|
+
decided_at TEXT NOT NULL
|
|
167
|
+
);
|
|
168
|
+
CREATE INDEX IF NOT EXISTS idx_scope_changes_memory ON scope_changes(memory_id);
|
|
169
|
+
|
|
151
170
|
-- llm_audit_logs: every background LLM call (autoDream consolidation + summary,
|
|
152
171
|
-- autoSummarize compression) is recorded here — tokens in/out, duration, status
|
|
153
172
|
-- and the trigger that caused it (Bug8). Failures are captured as status='error'
|
|
@@ -310,7 +329,9 @@ function escapeLike(q) {
|
|
|
310
329
|
// 法值一律返回 undefined → 不进 WHERE(忽略而非报错:面板传坏参数时宁可放宽
|
|
311
330
|
// 过滤也不要白屏)。updated_at 列是 toISOString 产生的 UTC "Z" 字符串,字典
|
|
312
331
|
// 序与时间序一致,SQL 里可直接比较。
|
|
313
|
-
|
|
332
|
+
// v0.8.0 A2:导出复用——occurred_at 时间过滤(service 的搜索后置过滤)与
|
|
333
|
+
// updated_at 过滤共用同一套边界归一化,坏参数口径一致。
|
|
334
|
+
export function updatedAtBounds(updatedFrom, updatedTo) {
|
|
314
335
|
const norm = (raw, endOfDay) => {
|
|
315
336
|
if (typeof raw !== "string" || !raw.trim()) return undefined;
|
|
316
337
|
const s = raw.trim();
|
|
@@ -332,6 +353,33 @@ function parseTags(raw) {
|
|
|
332
353
|
}
|
|
333
354
|
}
|
|
334
355
|
|
|
356
|
+
// scope 列(agent_scope/workspace_scope/sensitivity)写入归一化:非空 trim 字符串
|
|
357
|
+
// 原样收下,其余(undefined/null/空串/非字符串)一律 NULL。存储层不做枚举校验——
|
|
358
|
+
// scope 值是宿主身份标签(agentPreset id / 目录路径 / 自由 sensitivity 标签),
|
|
359
|
+
// 语义由调用方负责;这里只保证「脏输入落 NULL,绝不阻塞写入」。
|
|
360
|
+
function normalizeScopeText(raw) {
|
|
361
|
+
if (typeof raw !== "string") return null;
|
|
362
|
+
const s = raw.trim();
|
|
363
|
+
return s ? s : null;
|
|
364
|
+
}
|
|
365
|
+
|
|
366
|
+
// scope 来源列(issue #170 底座):'auto'=载体自动标注,'explicit'=显式声明。
|
|
367
|
+
// 只认这两个枚举值,其余(含 NULL)落 NULL——NULL 语义是「未标注,或 0.8.1
|
|
368
|
+
// 之前落库的存量标注(视为 auto 的软效力,第 3 步语义收窄时按此口径读)」。
|
|
369
|
+
function normalizeScopeSource(raw) {
|
|
370
|
+
return raw === "auto" || raw === "explicit" ? raw : null;
|
|
371
|
+
}
|
|
372
|
+
|
|
373
|
+
// occurred_at:事件发生时间(区别于 created_at 的入库时间)。可解析的时间戳
|
|
374
|
+
// 统一归一到 UTC ISO(A2 的时间过滤要按字典序直接比较);解析失败落 NULL,
|
|
375
|
+
// 同样不阻塞写入。
|
|
376
|
+
function normalizeOccurredAt(raw) {
|
|
377
|
+
if (typeof raw !== "string" || !raw.trim()) return null;
|
|
378
|
+
const ms = Date.parse(raw.trim());
|
|
379
|
+
if (Number.isNaN(ms)) return null;
|
|
380
|
+
return new Date(ms).toISOString();
|
|
381
|
+
}
|
|
382
|
+
|
|
335
383
|
function toRow(row) {
|
|
336
384
|
if (!row) return undefined;
|
|
337
385
|
return {
|
|
@@ -347,6 +395,13 @@ function toRow(row) {
|
|
|
347
395
|
content_history: parseJsonArray(row.content_history),
|
|
348
396
|
quality_score: row.quality_score !== null && row.quality_score !== undefined ? Number(row.quality_score) : undefined,
|
|
349
397
|
epistemic_status: row.epistemic_status ?? "subjective",
|
|
398
|
+
agent_scope: row.agent_scope ?? undefined,
|
|
399
|
+
workspace_scope: row.workspace_scope ?? undefined,
|
|
400
|
+
agent_scope_source: row.agent_scope_source ?? undefined,
|
|
401
|
+
workspace_scope_source: row.workspace_scope_source ?? undefined,
|
|
402
|
+
scope_decided_at: row.scope_decided_at ?? undefined,
|
|
403
|
+
sensitivity: row.sensitivity ?? undefined,
|
|
404
|
+
occurred_at: row.occurred_at ?? undefined,
|
|
350
405
|
created_at: row.created_at,
|
|
351
406
|
updated_at: row.updated_at,
|
|
352
407
|
last_accessed_at: row.last_accessed_at ?? undefined,
|
|
@@ -412,6 +467,22 @@ function toConflictPending(row) {
|
|
|
412
467
|
};
|
|
413
468
|
}
|
|
414
469
|
|
|
470
|
+
function toScopeChange(row) {
|
|
471
|
+
if (!row) return undefined;
|
|
472
|
+
return {
|
|
473
|
+
id: row.id,
|
|
474
|
+
memory_id: row.memory_id,
|
|
475
|
+
actor: row.actor,
|
|
476
|
+
prev_agent_scope: row.prev_agent_scope ?? undefined,
|
|
477
|
+
prev_workspace_scope: row.prev_workspace_scope ?? undefined,
|
|
478
|
+
next_agent_scope: row.next_agent_scope ?? undefined,
|
|
479
|
+
next_workspace_scope: row.next_workspace_scope ?? undefined,
|
|
480
|
+
agent_scope_source: row.agent_scope_source ?? undefined,
|
|
481
|
+
workspace_scope_source: row.workspace_scope_source ?? undefined,
|
|
482
|
+
decided_at: row.decided_at
|
|
483
|
+
};
|
|
484
|
+
}
|
|
485
|
+
|
|
415
486
|
function toRecallRun(row) {
|
|
416
487
|
if (!row) return undefined;
|
|
417
488
|
return {
|
|
@@ -597,6 +668,24 @@ export function createStore(path) {
|
|
|
597
668
|
addColumn("memories", "content_history", "ALTER TABLE memories ADD COLUMN content_history TEXT");
|
|
598
669
|
addColumn("memories", "quality_score", "ALTER TABLE memories ADD COLUMN quality_score REAL");
|
|
599
670
|
|
|
671
|
+
// v0.8.0 A1(issue #17)scope 隔离存储层:四列全部可空、不带 DEFAULT——存量行
|
|
672
|
+
// 零重写,读侧把 NULL 视为未标注(= 全局可见)。是否落值由 scopeEnabled 门控
|
|
673
|
+
// (service/tools 层),存储层只负责归一化(normalizeScopeText/normalizeOccurredAt)。
|
|
674
|
+
addColumn("memories", "agent_scope", "ALTER TABLE memories ADD COLUMN agent_scope TEXT");
|
|
675
|
+
addColumn("memories", "workspace_scope", "ALTER TABLE memories ADD COLUMN workspace_scope TEXT");
|
|
676
|
+
addColumn("memories", "sensitivity", "ALTER TABLE memories ADD COLUMN sensitivity TEXT");
|
|
677
|
+
addColumn("memories", "occurred_at", "ALTER TABLE memories ADD COLUMN occurred_at TEXT");
|
|
678
|
+
|
|
679
|
+
// v0.8.1 底座(issue #170):scope 标注的来源与决策时间。三列全部可空不带
|
|
680
|
+
// DEFAULT(与 A1 同款零重写迁移)。来源拆成 agent/workspace 各一列而非单列
|
|
681
|
+
// scope_source:混合来源(agent 维显式 + workspace 维自动)必须可表达——否则
|
|
682
|
+
// 「提升回全局」之后遗留的自动 workspace 标签会在效力收窄时被一并当成显式,
|
|
683
|
+
// 恰好复活 issue #170 抱怨的「自动标签吃硬过滤」问题。存量标注行三列皆 NULL,
|
|
684
|
+
// 读侧按 auto(软效力)口径解释。
|
|
685
|
+
addColumn("memories", "agent_scope_source", "ALTER TABLE memories ADD COLUMN agent_scope_source TEXT");
|
|
686
|
+
addColumn("memories", "workspace_scope_source", "ALTER TABLE memories ADD COLUMN workspace_scope_source TEXT");
|
|
687
|
+
addColumn("memories", "scope_decided_at", "ALTER TABLE memories ADD COLUMN scope_decided_at TEXT");
|
|
688
|
+
|
|
600
689
|
// Legacy dream_runs without policy_epoch → backfill with the default epoch.
|
|
601
690
|
addColumn("dream_runs", "policy_epoch", "ALTER TABLE dream_runs ADD COLUMN policy_epoch INTEGER NOT NULL DEFAULT 0");
|
|
602
691
|
addColumn("dream_runs", "run_type", "ALTER TABLE dream_runs ADD COLUMN run_type TEXT NOT NULL DEFAULT 'auto'");
|
|
@@ -643,7 +732,7 @@ export function createStore(path) {
|
|
|
643
732
|
return ts;
|
|
644
733
|
}
|
|
645
734
|
|
|
646
|
-
function count(type, { minImportance = null, source = null, includeForgotten = false, includeArchived = false, onlyArchived = false, depositedOnly = false, updatedFrom = null, updatedTo = null } = {}) {
|
|
735
|
+
function count(type, { minImportance = null, source = null, includeForgotten = false, includeArchived = false, onlyArchived = false, depositedOnly = false, updatedFrom = null, updatedTo = null, occurredFrom = null, occurredTo = null, visibility = null } = {}) {
|
|
647
736
|
const clauses = [];
|
|
648
737
|
const params = [];
|
|
649
738
|
if (type !== undefined) {
|
|
@@ -670,6 +759,36 @@ export function createStore(path) {
|
|
|
670
759
|
clauses.push("updated_at <= ?");
|
|
671
760
|
params.push(bounds.to);
|
|
672
761
|
}
|
|
762
|
+
// occurred_at 闭区间:与 list() 同口径(COALESCE 回退 created_at),
|
|
763
|
+
// memory_list 的 total 才能和过滤后的行保持一致。
|
|
764
|
+
const occurred = updatedAtBounds(occurredFrom, occurredTo);
|
|
765
|
+
if (occurred.from) {
|
|
766
|
+
clauses.push("COALESCE(occurred_at, created_at) >= ?");
|
|
767
|
+
params.push(occurred.from);
|
|
768
|
+
}
|
|
769
|
+
if (occurred.to) {
|
|
770
|
+
clauses.push("COALESCE(occurred_at, created_at) <= ?");
|
|
771
|
+
params.push(occurred.to);
|
|
772
|
+
}
|
|
773
|
+
// occurred_at 闭区间:与 list() 同过滤(visibility 同口径),memory_list
|
|
774
|
+
// 的 total 才能和过滤后的行保持一致。
|
|
775
|
+
if (visibility) {
|
|
776
|
+
// v0.8.1 第 3 步(issue #170 4.3):硬过滤只认显式声明——与
|
|
777
|
+
// service.isVisibleInScope 同口径。IS NOT 是 NULL 安全比较:来源为
|
|
778
|
+
// auto/NULL(v0.8.0 存量自动标注)的行不吃硬墙,只走 A2 软加权。
|
|
779
|
+
if (visibility.agentScope != null) {
|
|
780
|
+
clauses.push("(agent_scope IS NULL OR agent_scope_source IS NOT 'explicit' OR agent_scope = ?)");
|
|
781
|
+
params.push(visibility.agentScope);
|
|
782
|
+
} else {
|
|
783
|
+
clauses.push("(agent_scope IS NULL OR agent_scope_source IS NOT 'explicit')");
|
|
784
|
+
}
|
|
785
|
+
if (visibility.workspaceScope != null) {
|
|
786
|
+
clauses.push("(workspace_scope IS NULL OR workspace_scope_source IS NOT 'explicit' OR workspace_scope = ?)");
|
|
787
|
+
params.push(visibility.workspaceScope);
|
|
788
|
+
} else {
|
|
789
|
+
clauses.push("(workspace_scope IS NULL OR workspace_scope_source IS NOT 'explicit')");
|
|
790
|
+
}
|
|
791
|
+
}
|
|
673
792
|
if (!includeForgotten) {
|
|
674
793
|
clauses.push("forgotten = 0");
|
|
675
794
|
}
|
|
@@ -714,8 +833,8 @@ export function createStore(path) {
|
|
|
714
833
|
: inferEpistemicStatus(memory);
|
|
715
834
|
runAtomically(() => {
|
|
716
835
|
db.prepare(
|
|
717
|
-
`INSERT INTO memories (id, type, title, content, tags, importance, forgotten, archived, source, content_history, quality_score, embedding, epistemic_status, created_at, updated_at)
|
|
718
|
-
VALUES (?, ?, ?, ?, ?, ?, 0, ?, ?, ?, ?, ?, ?, ?, ?)`
|
|
836
|
+
`INSERT INTO memories (id, type, title, content, tags, importance, forgotten, archived, source, content_history, quality_score, embedding, epistemic_status, agent_scope, workspace_scope, agent_scope_source, workspace_scope_source, scope_decided_at, sensitivity, occurred_at, created_at, updated_at)
|
|
837
|
+
VALUES (?, ?, ?, ?, ?, ?, 0, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)`
|
|
719
838
|
).run(
|
|
720
839
|
id,
|
|
721
840
|
type,
|
|
@@ -729,6 +848,13 @@ export function createStore(path) {
|
|
|
729
848
|
Number.isFinite(memory.quality_score) ? memory.quality_score : null,
|
|
730
849
|
embedding,
|
|
731
850
|
epistemicStatus,
|
|
851
|
+
normalizeScopeText(memory.agent_scope),
|
|
852
|
+
normalizeScopeText(memory.workspace_scope),
|
|
853
|
+
normalizeScopeSource(memory.agent_scope_source),
|
|
854
|
+
normalizeScopeSource(memory.workspace_scope_source),
|
|
855
|
+
normalizeOccurredAt(memory.scope_decided_at),
|
|
856
|
+
normalizeScopeText(memory.sensitivity),
|
|
857
|
+
normalizeOccurredAt(memory.occurred_at),
|
|
732
858
|
now,
|
|
733
859
|
now
|
|
734
860
|
);
|
|
@@ -760,9 +886,26 @@ export function createStore(path) {
|
|
|
760
886
|
const qualityScore = patch.quality_score !== undefined && Number.isFinite(patch.quality_score)
|
|
761
887
|
? patch.quality_score
|
|
762
888
|
: (existing.quality_score ?? null);
|
|
889
|
+
// v0.8.1 底座(issue #170):scope 列只在本 patch 显式携带该键时才改写
|
|
890
|
+
// (undefined=不动;null=清空为未标注/global)。来源列只认 auto|explicit。
|
|
891
|
+
const nextAgentScope = patch.agent_scope !== undefined
|
|
892
|
+
? normalizeScopeText(patch.agent_scope)
|
|
893
|
+
: (existing.agent_scope ?? null);
|
|
894
|
+
const nextWorkspaceScope = patch.workspace_scope !== undefined
|
|
895
|
+
? normalizeScopeText(patch.workspace_scope)
|
|
896
|
+
: (existing.workspace_scope ?? null);
|
|
897
|
+
const nextAgentSource = patch.agent_scope_source !== undefined
|
|
898
|
+
? normalizeScopeSource(patch.agent_scope_source)
|
|
899
|
+
: (existing.agent_scope_source ?? null);
|
|
900
|
+
const nextWorkspaceSource = patch.workspace_scope_source !== undefined
|
|
901
|
+
? normalizeScopeSource(patch.workspace_scope_source)
|
|
902
|
+
: (existing.workspace_scope_source ?? null);
|
|
903
|
+
const nextDecidedAt = patch.scope_decided_at !== undefined
|
|
904
|
+
? normalizeOccurredAt(patch.scope_decided_at)
|
|
905
|
+
: (existing.scope_decided_at ?? null);
|
|
763
906
|
runAtomically(() => {
|
|
764
907
|
db.prepare(
|
|
765
|
-
`UPDATE memories SET type=?, title=?, content=?, tags=?, importance=?, source=?, content_history=?, quality_score=?, embedding=?, epistemic_status=?, updated_at=? WHERE id=?`
|
|
908
|
+
`UPDATE memories SET type=?, title=?, content=?, tags=?, importance=?, source=?, content_history=?, quality_score=?, embedding=?, epistemic_status=?, agent_scope=?, workspace_scope=?, agent_scope_source=?, workspace_scope_source=?, scope_decided_at=?, updated_at=? WHERE id=?`
|
|
766
909
|
).run(
|
|
767
910
|
type,
|
|
768
911
|
patch.title ?? existing.title,
|
|
@@ -774,6 +917,11 @@ export function createStore(path) {
|
|
|
774
917
|
qualityScore,
|
|
775
918
|
embedding,
|
|
776
919
|
epistemicStatus,
|
|
920
|
+
nextAgentScope,
|
|
921
|
+
nextWorkspaceScope,
|
|
922
|
+
nextAgentSource,
|
|
923
|
+
nextWorkspaceSource,
|
|
924
|
+
nextDecidedAt,
|
|
777
925
|
now,
|
|
778
926
|
id
|
|
779
927
|
);
|
|
@@ -936,7 +1084,7 @@ export function createStore(path) {
|
|
|
936
1084
|
return rows.map(toRow);
|
|
937
1085
|
}
|
|
938
1086
|
|
|
939
|
-
function list({ type, limit = 50, offset = 0, order = "importance", includeForgotten = false, includeArchived = false, onlyArchived = false, depositedOnly = false, minImportance = null, source = null, updatedFrom = null, updatedTo = null } = {}) {
|
|
1087
|
+
function list({ type, limit = 50, offset = 0, order = "importance", includeForgotten = false, includeArchived = false, onlyArchived = false, depositedOnly = false, minImportance = null, source = null, updatedFrom = null, updatedTo = null, occurredFrom = null, occurredTo = null, visibility = null } = {}) {
|
|
940
1088
|
const clauses = [];
|
|
941
1089
|
const params = [];
|
|
942
1090
|
if (type) {
|
|
@@ -964,6 +1112,40 @@ export function createStore(path) {
|
|
|
964
1112
|
clauses.push("updated_at <= ?");
|
|
965
1113
|
params.push(bounds.to);
|
|
966
1114
|
}
|
|
1115
|
+
// v0.8.0 A2(issue #17):occurred_at 闭区间过滤——按「事件发生时间」检索。
|
|
1116
|
+
// 未标注 occurred_at 的行(含全部存量)回退 created_at 比较(COALESCE),
|
|
1117
|
+
// 否则过滤器对旧库近乎不可用;边界归一化与 updated_at 共用 updatedAtBounds。
|
|
1118
|
+
const occurred = updatedAtBounds(occurredFrom, occurredTo);
|
|
1119
|
+
if (occurred.from) {
|
|
1120
|
+
clauses.push("COALESCE(occurred_at, created_at) >= ?");
|
|
1121
|
+
params.push(occurred.from);
|
|
1122
|
+
}
|
|
1123
|
+
if (occurred.to) {
|
|
1124
|
+
clauses.push("COALESCE(occurred_at, created_at) <= ?");
|
|
1125
|
+
params.push(occurred.to);
|
|
1126
|
+
}
|
|
1127
|
+
// v0.8.0 A3(issue #17):strictScope 硬过滤(memory_list 分页路径)。
|
|
1128
|
+
// SQL 与 service.isVisibleInScope 同口径:未标注(NULL)恒可见;v0.8.1 起
|
|
1129
|
+
// 硬墙只认 explicit 来源(4.3),当前维度解析不到时该维度只放行未标注/
|
|
1130
|
+
// 非显式行(fail-closed 收窄)。只有显式传 visibility 的用户面调用才
|
|
1131
|
+
// 过滤——dream/质量过滤等内部 store.list 调用不受影响。
|
|
1132
|
+
if (visibility) {
|
|
1133
|
+
// v0.8.1 第 3 步(issue #170 4.3):硬过滤只认显式声明——与
|
|
1134
|
+
// service.isVisibleInScope 同口径。IS NOT 是 NULL 安全比较:来源为
|
|
1135
|
+
// auto/NULL(v0.8.0 存量自动标注)的行不吃硬墙,只走 A2 软加权。
|
|
1136
|
+
if (visibility.agentScope != null) {
|
|
1137
|
+
clauses.push("(agent_scope IS NULL OR agent_scope_source IS NOT 'explicit' OR agent_scope = ?)");
|
|
1138
|
+
params.push(visibility.agentScope);
|
|
1139
|
+
} else {
|
|
1140
|
+
clauses.push("(agent_scope IS NULL OR agent_scope_source IS NOT 'explicit')");
|
|
1141
|
+
}
|
|
1142
|
+
if (visibility.workspaceScope != null) {
|
|
1143
|
+
clauses.push("(workspace_scope IS NULL OR workspace_scope_source IS NOT 'explicit' OR workspace_scope = ?)");
|
|
1144
|
+
params.push(visibility.workspaceScope);
|
|
1145
|
+
} else {
|
|
1146
|
+
clauses.push("(workspace_scope IS NULL OR workspace_scope_source IS NOT 'explicit')");
|
|
1147
|
+
}
|
|
1148
|
+
}
|
|
967
1149
|
if (!includeForgotten) {
|
|
968
1150
|
clauses.push("forgotten = 0");
|
|
969
1151
|
}
|
|
@@ -1520,7 +1702,14 @@ export function createStore(path) {
|
|
|
1520
1702
|
* order is normalized (sorted by id) so the same two memories are only ever
|
|
1521
1703
|
* pending once — a re-detection in a later dream run is a no-op, never a
|
|
1522
1704
|
* duplicate queue entry. Returns the pending row (freshly inserted, or the
|
|
1523
|
-
* existing unresolved row when the pair is already pending)
|
|
1705
|
+
* existing unresolved row when the pair is already pending), or undefined
|
|
1706
|
+
* when the re-detection is suppressed.
|
|
1707
|
+
*
|
|
1708
|
+
* v0.8.1(issue #170 复核项 4):人工已裁决过的对(resolved_at 非空)不再
|
|
1709
|
+
* 反复入队——除非任一侧在裁决之后又被改过(updated_at 晚于 resolved_at)。
|
|
1710
|
+
* 否则跨 scope 对每轮 sleep 都原样回来,把「保留双方」的决定变成噪声。
|
|
1711
|
+
* updated_at 是宽松代理(任何字段更新都会触发重新评审)——宁可多看一眼,
|
|
1712
|
+
* 不静音真冲突。
|
|
1524
1713
|
*/
|
|
1525
1714
|
function saveConflictPending({ run_id, memory_a, memory_b, reason }) {
|
|
1526
1715
|
const [a, b] = [memory_a, memory_b].sort();
|
|
@@ -1528,6 +1717,15 @@ export function createStore(path) {
|
|
|
1528
1717
|
"SELECT * FROM conflict_pending WHERE memory_a = ? AND memory_b = ? AND resolved_at IS NULL LIMIT 1"
|
|
1529
1718
|
).get(a, b);
|
|
1530
1719
|
if (existing) return toConflictPending(existing);
|
|
1720
|
+
const reviewed = db.prepare(
|
|
1721
|
+
"SELECT * FROM conflict_pending WHERE memory_a = ? AND memory_b = ? AND resolved_at IS NOT NULL ORDER BY resolved_at DESC, rowid DESC LIMIT 1"
|
|
1722
|
+
).get(a, b);
|
|
1723
|
+
if (reviewed) {
|
|
1724
|
+
const touched = db.prepare(
|
|
1725
|
+
"SELECT count(*) AS c FROM memories WHERE id IN (?, ?) AND updated_at > ?"
|
|
1726
|
+
).get(a, b, reviewed.resolved_at).c;
|
|
1727
|
+
if (touched === 0) return undefined;
|
|
1728
|
+
}
|
|
1531
1729
|
const id = randomUUID();
|
|
1532
1730
|
const now = nowIso();
|
|
1533
1731
|
db.prepare(
|
|
@@ -1573,6 +1771,44 @@ export function createStore(path) {
|
|
|
1573
1771
|
).get().c;
|
|
1574
1772
|
}
|
|
1575
1773
|
|
|
1774
|
+
/**
|
|
1775
|
+
* v0.8.1 底座(issue #170):scope 归属人工修正的审计行。record 描述「一次
|
|
1776
|
+
* 决策后的完整状态」(prev_* 为改前、next_* 为改后,NULL=global/未标注),
|
|
1777
|
+
* 由调用方(service.updateMemory)在 store.update 成功后写入;本函数不做
|
|
1778
|
+
* 业务校验,只保证审计落库失败不反噬主写入(异常上抛由调用方吞掉记 warn)。
|
|
1779
|
+
*/
|
|
1780
|
+
function saveScopeChange({ memory_id, actor, prev_agent_scope, prev_workspace_scope, next_agent_scope, next_workspace_scope, agent_scope_source, workspace_scope_source, decided_at }) {
|
|
1781
|
+
const id = randomUUID();
|
|
1782
|
+
db.prepare(
|
|
1783
|
+
`INSERT INTO scope_changes (id, memory_id, actor, prev_agent_scope, prev_workspace_scope, next_agent_scope, next_workspace_scope, agent_scope_source, workspace_scope_source, decided_at)
|
|
1784
|
+
VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?)`
|
|
1785
|
+
).run(
|
|
1786
|
+
id,
|
|
1787
|
+
memory_id,
|
|
1788
|
+
actor === "panel" ? "panel" : "tool",
|
|
1789
|
+
normalizeScopeText(prev_agent_scope),
|
|
1790
|
+
normalizeScopeText(prev_workspace_scope),
|
|
1791
|
+
normalizeScopeText(next_agent_scope),
|
|
1792
|
+
normalizeScopeText(next_workspace_scope),
|
|
1793
|
+
normalizeScopeSource(agent_scope_source),
|
|
1794
|
+
normalizeScopeSource(workspace_scope_source),
|
|
1795
|
+
normalizeOccurredAt(decided_at) ?? nowIso()
|
|
1796
|
+
);
|
|
1797
|
+
return toScopeChange(db.prepare("SELECT * FROM scope_changes WHERE id = ?").get(id));
|
|
1798
|
+
}
|
|
1799
|
+
|
|
1800
|
+
/** Scope 修正审计(单条记忆,新→旧)。审计回放与候选复核用。 */
|
|
1801
|
+
function listScopeChanges(memoryId, { limit = 50 } = {}) {
|
|
1802
|
+
const lim = Number.isInteger(limit) && limit > 0 ? Math.min(limit, 200) : 50;
|
|
1803
|
+
// tiebreaker 用 rowid(追加写入的单调序)而非 id:decided_at 只有毫秒精度,
|
|
1804
|
+
// 连续两次修正落在同一毫秒时 UUID 排序是随机的(CI windows/node24 抓到的
|
|
1805
|
+
// 真实 flake)——审计回放要求「最新一条」确定。
|
|
1806
|
+
const rows = db.prepare(
|
|
1807
|
+
"SELECT * FROM scope_changes WHERE memory_id = ? ORDER BY decided_at DESC, rowid DESC LIMIT ?"
|
|
1808
|
+
).all(memoryId, lim);
|
|
1809
|
+
return rows.map(toScopeChange);
|
|
1810
|
+
}
|
|
1811
|
+
|
|
1576
1812
|
function getFailureStats({ since } = {}) {
|
|
1577
1813
|
const clause = since ? "WHERE created_at >= ?" : "";
|
|
1578
1814
|
const params = since ? [since] : [];
|
|
@@ -2038,6 +2274,8 @@ export function createStore(path) {
|
|
|
2038
2274
|
listConflictPending,
|
|
2039
2275
|
resolveConflictPending,
|
|
2040
2276
|
countConflictPending,
|
|
2277
|
+
saveScopeChange,
|
|
2278
|
+
listScopeChanges,
|
|
2041
2279
|
createEntity,
|
|
2042
2280
|
findEntityByName,
|
|
2043
2281
|
findEntityById,
|
package/lib/tools.js
CHANGED
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import { defineTool } from "@deepseek-ai/dsh-tools";
|
|
2
|
+
import { createScopeResolver, normalizeExplicitScope } from "./scope.js";
|
|
2
3
|
import { describeLocalRuntime, resolveRuntimeEntry } from "./runtime/loader.js";
|
|
3
4
|
import { defaultRuntimeDir } from "./runtime/layout.js";
|
|
4
5
|
import { hostModulesDir, loadRuntimeManifest, provisionRuntime } from "./runtime/provision.js";
|
|
@@ -25,11 +26,48 @@ const MEMORY_ITEM_SCHEMA = {
|
|
|
25
26
|
tags: { type: "array", items: { type: "string" } },
|
|
26
27
|
importance: { type: "integer", required: true },
|
|
27
28
|
source: { type: "string" },
|
|
29
|
+
// v0.8.0 A2:scope 标注与事件发生时间随行透出(未标注时缺省,可选属性)。
|
|
30
|
+
// v0.8.1 底座:scope 来源(auto/explicit)与决策时间随行透出(同样可选)。
|
|
31
|
+
agent_scope: { type: "string" },
|
|
32
|
+
workspace_scope: { type: "string" },
|
|
33
|
+
agent_scope_source: { type: "string" },
|
|
34
|
+
workspace_scope_source: { type: "string" },
|
|
35
|
+
scope_decided_at: { type: "string" },
|
|
36
|
+
sensitivity: { type: "string" },
|
|
37
|
+
occurred_at: { type: "string" },
|
|
28
38
|
created_at: { type: "string", required: true },
|
|
29
39
|
updated_at: { type: "string", required: true }
|
|
30
40
|
}
|
|
31
41
|
};
|
|
32
42
|
|
|
43
|
+
// v0.8.0 A2:occurred 时间窗参数 → store 过滤选项(list/count 同口径);
|
|
44
|
+
// 未传参返回空对象,既有调用方不受影响。(A3 复核:此处入参仅含 occurred 边界。)
|
|
45
|
+
function occurredWindow(args) {
|
|
46
|
+
const from = args?.occurred_from ?? null;
|
|
47
|
+
const to = args?.occurred_to ?? null;
|
|
48
|
+
return from !== null || to !== null ? { occurredFrom: from, occurredTo: to } : {};
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
// v0.8.0 A3:strictScope 硬过滤的 visibility 选项(store.list/count 同口径)。
|
|
52
|
+
// flag 关或会话 scope 未解析出(flag 关时解析器返回 null)→ 空对象不过滤;
|
|
53
|
+
// scope 为 {null,null}(匿名会话)时照传——store 侧 fail-closed 只放行未标注行。
|
|
54
|
+
function strictVisibility(config, scope) {
|
|
55
|
+
if (config?.strictScope !== true || !scope) return {};
|
|
56
|
+
return { visibility: { agentScope: scope.agent_scope, workspaceScope: scope.workspace_scope } };
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
// v0.8.0 A2:检索结果的 provenance 附加段——只在有标注时出现,未标注行渲染
|
|
60
|
+
// 与 A2 前逐字节一致。
|
|
61
|
+
const MEMORY_PROVENANCE = (m) => {
|
|
62
|
+
const parts = [];
|
|
63
|
+
if (m.occurred_at) parts.push(`occurred: ${m.occurred_at}`);
|
|
64
|
+
if (m.agent_scope || m.workspace_scope) {
|
|
65
|
+
parts.push(`scope: ${m.agent_scope ?? "global"} / ${m.workspace_scope ?? "global"}`);
|
|
66
|
+
}
|
|
67
|
+
if (m.sensitivity) parts.push(`sensitivity: ${m.sensitivity}`);
|
|
68
|
+
return parts.length ? ` | ${parts.join(" | ")}` : "";
|
|
69
|
+
};
|
|
70
|
+
|
|
33
71
|
export function createTools(ctx, service, config, embedder) {
|
|
34
72
|
const toolsRegistry = ctx.tools;
|
|
35
73
|
let registeredTools = REGISTERED_TOOLS.get(toolsRegistry);
|
|
@@ -37,6 +75,30 @@ export function createTools(ctx, service, config, embedder) {
|
|
|
37
75
|
registeredTools = new Set();
|
|
38
76
|
REGISTERED_TOOLS.set(toolsRegistry, registeredTools);
|
|
39
77
|
}
|
|
78
|
+
// v0.8.0 A1/A2(issue #17):会话 scope 解析(agentPreset + workspace 反查)。
|
|
79
|
+
// flag 关闭时恒返回 null——写入不标注,检索不加权,行为与 A1 前完全一致。
|
|
80
|
+
// logger 透传:registry 反查失败时 warnOnce 才有出口(否则静默降级无观测)。
|
|
81
|
+
const resolveSessionScope = createScopeResolver({ ctx, config, logger: ctx.logger });
|
|
82
|
+
|
|
83
|
+
// 复核项 4(issue #170):strictScope 下他 scope(explicit)的行对工具侧按
|
|
84
|
+
// 「不存在」处理——update/delete 与 memory_get 同款无存在性泄漏。strictScope
|
|
85
|
+
// 关、或会话身份取不到(scope 为 null)时恒可见(与 get 的短路语义一致)。
|
|
86
|
+
const visibleUnderStrictScope = (memory, exec) => {
|
|
87
|
+
if (config?.strictScope !== true) return true;
|
|
88
|
+
const scope = resolveSessionScope(exec);
|
|
89
|
+
return !scope || service.isVisibleInScope(memory, scope);
|
|
90
|
+
};
|
|
91
|
+
// 在调用点以「展开恒为 undefined」的形态前置校验(抛错即中止写入)。
|
|
92
|
+
const assertVisibleForTool = (id, exec) => {
|
|
93
|
+
const memory = id ? service.getById(id) : undefined;
|
|
94
|
+
if (!visibleUnderStrictScope(memory, exec)) throw new Error("memory not found");
|
|
95
|
+
return undefined;
|
|
96
|
+
};
|
|
97
|
+
// delete 侧需要目标行本身判断存在性(不可见 = 视作不存在,不抛错)。
|
|
98
|
+
const visibleForTool = (id, exec) => {
|
|
99
|
+
const memory = id ? service.getById(id) : undefined;
|
|
100
|
+
return visibleUnderStrictScope(memory, exec) ? memory : undefined;
|
|
101
|
+
};
|
|
40
102
|
const tools = [
|
|
41
103
|
defineTool({
|
|
42
104
|
name: "memory_save",
|
|
@@ -50,7 +112,11 @@ export function createTools(ctx, service, config, embedder) {
|
|
|
50
112
|
content: { type: "string", required: true, description: "Memory body" },
|
|
51
113
|
tags: { type: "array", items: { type: "string" }, description: "Optional tags" },
|
|
52
114
|
importance: { type: "integer", description: "1-5; >= threshold auto-injects into future sessions" },
|
|
53
|
-
source: { type: "string", description: "Optional provenance" }
|
|
115
|
+
source: { type: "string", description: "Optional provenance" },
|
|
116
|
+
sensitivity: { type: "string", description: "Optional sensitivity label (free-form, e.g. personal). Same-title entries with different sensitivity stay separate instead of merging." },
|
|
117
|
+
occurred_at: { type: "string", description: "Optional ISO-8601 instant the remembered event happened (differs from write time). Invalid values are ignored." },
|
|
118
|
+
agent_scope: { type: "string", description: "Optional explicit agent-scope declaration (issue #170): 'global' or '*' makes this memory visible to every agent; any other value narrows it to that label. Overrides the automatic carrier label for this write; honored even when automatic scope labeling is disabled." },
|
|
119
|
+
workspace_scope: { type: "string", description: "Optional explicit workspace-scope declaration: 'global' or '*' makes this memory visible in every workspace; any other value narrows it to that label. Overrides the automatic carrier label for this write; honored even when automatic scope labeling is disabled." }
|
|
54
120
|
},
|
|
55
121
|
output: {
|
|
56
122
|
schema: {
|
|
@@ -63,14 +129,34 @@ export function createTools(ctx, service, config, embedder) {
|
|
|
63
129
|
},
|
|
64
130
|
render: (_args, value) => TEXT_OUTPUT(`memory ${value.action}: ${value.id}`)
|
|
65
131
|
},
|
|
66
|
-
async execute(args) {
|
|
132
|
+
async execute(args, exec) {
|
|
133
|
+
// scope 标注(v0.8.1 底座,issue #170):默认由会话身份解析(载体自动
|
|
134
|
+
// 标注,盖 auto 章);显式 agent_scope/workspace_scope 参数逐维覆盖并盖
|
|
135
|
+
// explicit 章。解析绝不抛错、取不到落 NULL。
|
|
136
|
+
// 复核项 2(#170):scopeEnabled 只门控「自动标注」分支——显式声明与
|
|
137
|
+
// 读取身份(search/list/get/注入经同一解析器)不受 flag 限制,否则
|
|
138
|
+
// strictScope 硬墙会被 flag 静默拆掉(fail-open)。
|
|
139
|
+
// (存储 NULL,由 source 列与「从未标注」区分)。非字符串参数在工具
|
|
140
|
+
// 入参 schema 层即被拒(type: "string")——脏值到不了归一化,无静默放宽。
|
|
141
|
+
const scope = resolveSessionScope(exec);
|
|
142
|
+
const autoStamping = config?.scopeEnabled === true;
|
|
143
|
+
const agentLabel = args.agent_scope !== undefined
|
|
144
|
+
? { value: normalizeExplicitScope(args.agent_scope), source: "explicit" }
|
|
145
|
+
: autoStamping && scope ? { value: scope.agent_scope, source: "auto" } : null;
|
|
146
|
+
const workspaceLabel = args.workspace_scope !== undefined
|
|
147
|
+
? { value: normalizeExplicitScope(args.workspace_scope), source: "explicit" }
|
|
148
|
+
: autoStamping && scope ? { value: scope.workspace_scope, source: "auto" } : null;
|
|
67
149
|
const { action, memory } = service.saveWithDedupe({
|
|
68
150
|
type: args.type,
|
|
69
151
|
title: args.title,
|
|
70
152
|
content: args.content,
|
|
71
153
|
tags: args.tags ?? [],
|
|
72
154
|
importance: args.importance ?? 3,
|
|
73
|
-
source: args.source ?? "tool"
|
|
155
|
+
source: args.source ?? "tool",
|
|
156
|
+
...(agentLabel ? { agent_scope: agentLabel.value, agent_scope_source: agentLabel.source } : {}),
|
|
157
|
+
...(workspaceLabel ? { workspace_scope: workspaceLabel.value, workspace_scope_source: workspaceLabel.source } : {}),
|
|
158
|
+
...(args.sensitivity !== undefined ? { sensitivity: args.sensitivity } : {}),
|
|
159
|
+
...(args.occurred_at !== undefined ? { occurred_at: args.occurred_at } : {})
|
|
74
160
|
});
|
|
75
161
|
return { action, id: memory.id };
|
|
76
162
|
}
|
|
@@ -79,12 +165,15 @@ export function createTools(ctx, service, config, embedder) {
|
|
|
79
165
|
defineTool({
|
|
80
166
|
name: "memory_search",
|
|
81
167
|
description: "Search the cross-session memory store. Use when you need past context: how a problem was solved, user preferences, project decisions. Substring-matches title/content/tags, and augments results with semantic (vector) recall + optional rerank when an embeddings provider is configured. Returns matching entries with source and timestamps.",
|
|
168
|
+
// A2 检索接线:occurred 时间窗 + 会话 scope 加成(见 execute)。
|
|
82
169
|
parameters: {
|
|
83
170
|
query: { type: "string", required: true, description: "Search text; substring match over title/content/tags" },
|
|
84
171
|
limit: { type: "integer", description: "Max results (default 20)" },
|
|
85
172
|
mode: { type: "string", enum: ["auto", "keyword", "vector", "hybrid"], description: "auto (default) = keyword hits first + vector fill when enabled; keyword = text only; vector = semantic recall first (falls back to keyword); hybrid = vector leads, keyword fills remaining slots" },
|
|
86
173
|
semantic: { type: "boolean", description: "Shorthand: enable semantic (vector) recall (same as mode=vector when true)" },
|
|
87
|
-
rerank: { type: "boolean", description: "Run cross-encoder rerank over candidates when a local reranker is configured (default true)" }
|
|
174
|
+
rerank: { type: "boolean", description: "Run cross-encoder rerank over candidates when a local reranker is configured (default true)" },
|
|
175
|
+
occurred_from: { type: "string", description: "Optional ISO date/timestamp lower bound on when the remembered event happened (occurred_at, falling back to created_at). Date-only values are inclusive from that day's 00:00Z." },
|
|
176
|
+
occurred_to: { type: "string", description: "Optional ISO date/timestamp upper bound on occurred_at. Date-only values are inclusive through that day's 23:59:59.999Z." }
|
|
88
177
|
},
|
|
89
178
|
output: {
|
|
90
179
|
schema: {
|
|
@@ -104,19 +193,26 @@ export function createTools(ctx, service, config, embedder) {
|
|
|
104
193
|
.map((m, i) => {
|
|
105
194
|
const preview = (m.content ?? "").replace(/\s+/g, " ").trim();
|
|
106
195
|
const cut = preview.length > 200 ? `${preview.slice(0, 200)}…` : preview;
|
|
107
|
-
return `[${i + 1}] ${m.title}\n ID: ${m.id} | type: ${m.type} | importance: ${m.importance} | updated: ${m.updated_at}\n ${cut}`;
|
|
196
|
+
return `[${i + 1}] ${m.title}\n ID: ${m.id} | type: ${m.type} | importance: ${m.importance} | updated: ${m.updated_at}${MEMORY_PROVENANCE(m)}\n ${cut}`;
|
|
108
197
|
})
|
|
109
198
|
.join("\n\n");
|
|
110
199
|
return TEXT_OUTPUT(`Found ${items.length} memory entr${items.length === 1 ? "y" : "ies"}:\n\n${body}`);
|
|
111
200
|
}
|
|
112
201
|
},
|
|
113
|
-
async execute(args) {
|
|
202
|
+
async execute(args, exec) {
|
|
114
203
|
const limit = args.limit ?? 20;
|
|
115
204
|
const mode = args.semantic === true && !args.mode ? "vector" : args.mode ?? "auto";
|
|
205
|
+
// v0.8.0 A2(issue #17):scope 检索加权 + occurred 时间窗的检索接线。
|
|
206
|
+
// flag 关闭或解析不出 scope 时为 null,检索行为与 A2 前一致。
|
|
207
|
+
const scope = resolveSessionScope(exec);
|
|
208
|
+
const occurredFrom = args.occurred_from ?? null;
|
|
209
|
+
const occurredTo = args.occurred_to ?? null;
|
|
116
210
|
const rows = await service.searchMemories(args.query, {
|
|
117
211
|
mode,
|
|
118
212
|
topK: limit,
|
|
119
|
-
useRerank: args.rerank !== false
|
|
213
|
+
useRerank: args.rerank !== false,
|
|
214
|
+
...(scope ? { scope } : {}),
|
|
215
|
+
...(occurredFrom !== null || occurredTo !== null ? { occurredFrom, occurredTo } : {})
|
|
120
216
|
});
|
|
121
217
|
return { items: service.toApiList(rows) };
|
|
122
218
|
}
|
|
@@ -129,7 +225,9 @@ export function createTools(ctx, service, config, embedder) {
|
|
|
129
225
|
type: { type: "string", enum: ["preference", "project", "decision", "history", "rejected_solution", "pitfall", "constraint"], description: "Filter by type; omit for all" },
|
|
130
226
|
limit: { type: "integer", description: "Page size (default 50)" },
|
|
131
227
|
offset: { type: "integer", description: "Page offset (default 0)" },
|
|
132
|
-
include_archived: { type: "boolean", description: "Include archived (hidden) entries so they can be found and restored (default false)" }
|
|
228
|
+
include_archived: { type: "boolean", description: "Include archived (hidden) entries so they can be found and restored (default false)" },
|
|
229
|
+
occurred_from: { type: "string", description: "Optional ISO date/timestamp lower bound on when the remembered event happened (occurred_at, falling back to created_at). Date-only values are inclusive from that day's 00:00Z." },
|
|
230
|
+
occurred_to: { type: "string", description: "Optional ISO date/timestamp upper bound on occurred_at. Date-only values are inclusive through that day's 23:59:59.999Z." }
|
|
133
231
|
},
|
|
134
232
|
output: {
|
|
135
233
|
schema: {
|
|
@@ -147,7 +245,7 @@ export function createTools(ctx, service, config, embedder) {
|
|
|
147
245
|
const items = value.items ?? [];
|
|
148
246
|
if (items.length === 0) return TEXT_OUTPUT(`0 memory entries (of ${value.total}).`);
|
|
149
247
|
const body = items
|
|
150
|
-
.map((m, i) => `[${i + 1}] ${m.title} (type=${m.type}, importance=${m.importance})\n ID: ${m.id} | updated: ${m.updated_at}`)
|
|
248
|
+
.map((m, i) => `[${i + 1}] ${m.title} (type=${m.type}, importance=${m.importance})\n ID: ${m.id} | updated: ${m.updated_at}${MEMORY_PROVENANCE(m)}`)
|
|
151
249
|
.join("\n\n");
|
|
152
250
|
return TEXT_OUTPUT(`${items.length} memory entries (of ${value.total}):\n\n${body}`);
|
|
153
251
|
}
|
|
@@ -158,9 +256,13 @@ export function createTools(ctx, service, config, embedder) {
|
|
|
158
256
|
type: args.type,
|
|
159
257
|
limit: args.limit ?? 50,
|
|
160
258
|
offset: args.offset ?? 0,
|
|
161
|
-
includeArchived
|
|
259
|
+
includeArchived,
|
|
260
|
+
...occurredWindow(args),
|
|
261
|
+
// A3:strict 开启时按会话 scope 硬过滤(宿主按 (args, runContext) 调用,
|
|
262
|
+
// 从 arguments 取第二参)。
|
|
263
|
+
...strictVisibility(config, resolveSessionScope(arguments[1]))
|
|
162
264
|
}));
|
|
163
|
-
return { items: rows, total: service.count(args.type, { includeArchived }) };
|
|
265
|
+
return { items: rows, total: service.count(args.type, { includeArchived, ...occurredWindow(args), ...strictVisibility(config, resolveSessionScope(arguments[1])) }) };
|
|
164
266
|
}
|
|
165
267
|
}),
|
|
166
268
|
|
|
@@ -186,6 +288,10 @@ export function createTools(ctx, service, config, embedder) {
|
|
|
186
288
|
tags: { type: "array", items: { type: "string" } },
|
|
187
289
|
content: { type: "string", required: true },
|
|
188
290
|
source: { type: "string" },
|
|
291
|
+
agent_scope: { type: "string" },
|
|
292
|
+
workspace_scope: { type: "string" },
|
|
293
|
+
sensitivity: { type: "string" },
|
|
294
|
+
occurred_at: { type: "string" },
|
|
189
295
|
created_at: { type: "string" },
|
|
190
296
|
updated_at: { type: "string" }
|
|
191
297
|
}
|
|
@@ -194,12 +300,19 @@ export function createTools(ctx, service, config, embedder) {
|
|
|
194
300
|
},
|
|
195
301
|
render: (_args, value) => {
|
|
196
302
|
const m = value.memory;
|
|
197
|
-
return TEXT_OUTPUT(`${m.title}\nID: ${m.id} | type: ${m.type} | importance: ${m.importance}\n\n${m.content}`);
|
|
303
|
+
return TEXT_OUTPUT(`${m.title}\nID: ${m.id} | type: ${m.type} | importance: ${m.importance}${MEMORY_PROVENANCE(m)}\n\n${m.content}`);
|
|
198
304
|
}
|
|
199
305
|
},
|
|
200
306
|
async execute(args) {
|
|
201
307
|
const memory = service.getById(args.id);
|
|
202
308
|
if (memory === undefined) throw new Error("memory not found");
|
|
309
|
+
// v0.8.0 A3:strictScope 下他 scope 的行按不存在处理(无存在性泄漏)。
|
|
310
|
+
// 宿主注册表按 (args, runContext) 两参调用处理器,从 arguments 取第二参。
|
|
311
|
+
const runContext = arguments[1];
|
|
312
|
+
const scope = resolveSessionScope(runContext);
|
|
313
|
+
if (config.strictScope === true && scope && !service.isVisibleInScope(memory, scope)) {
|
|
314
|
+
throw new Error("memory not found");
|
|
315
|
+
}
|
|
203
316
|
return { memory: service.toApiList([memory])[0] };
|
|
204
317
|
}
|
|
205
318
|
}),
|
|
@@ -214,7 +327,9 @@ export function createTools(ctx, service, config, embedder) {
|
|
|
214
327
|
type: { type: "string", enum: ["preference", "project", "decision", "history", "rejected_solution", "pitfall", "constraint"] },
|
|
215
328
|
tags: { type: "array", items: { type: "string" } },
|
|
216
329
|
importance: { type: "integer", description: "1-5" },
|
|
217
|
-
reason: { type: "string", description: "Optional context for the correction (what the user actually said/wanted), recorded for reflection" }
|
|
330
|
+
reason: { type: "string", description: "Optional context for the correction (what the user actually said/wanted), recorded for reflection" },
|
|
331
|
+
agent_scope: { type: "string", description: "Optional explicit agent-scope correction (issue #170): 'global' or '*' widens visibility to every agent; any other value narrows it to that label. Omit to keep the current agent scope. Recorded as an explicit scope decision (audited)." },
|
|
332
|
+
workspace_scope: { type: "string", description: "Optional explicit workspace-scope correction: 'global' or '*' widens visibility to every workspace; any other value narrows it to that label. Omit to keep the current workspace scope. Recorded as an explicit scope decision (audited)." }
|
|
218
333
|
},
|
|
219
334
|
output: {
|
|
220
335
|
schema: {
|
|
@@ -240,7 +355,12 @@ export function createTools(ctx, service, config, embedder) {
|
|
|
240
355
|
content: args.content,
|
|
241
356
|
type: args.type,
|
|
242
357
|
tags: args.tags,
|
|
243
|
-
importance: args.importance
|
|
358
|
+
importance: args.importance,
|
|
359
|
+
// 复核项 4(#170):strictScope 下他 scope 的行按「不存在」处理(与 get 对齐),
|
|
360
|
+
// 展开恒为 undefined,仅承担写入前校验。
|
|
361
|
+
...(assertVisibleForTool(args.id, arguments[1])),
|
|
362
|
+
...(args.agent_scope !== undefined ? { agent_scope: args.agent_scope } : {}),
|
|
363
|
+
...(args.workspace_scope !== undefined ? { workspace_scope: args.workspace_scope } : {})
|
|
244
364
|
}, { query: args.reason });
|
|
245
365
|
return { memory: { id: memory.id, title: memory.title, content: memory.content } };
|
|
246
366
|
}
|
|
@@ -261,9 +381,10 @@ export function createTools(ctx, service, config, embedder) {
|
|
|
261
381
|
render: (_args, value) => TEXT_OUTPUT(value.deleted ? "Memory deleted." : "Memory not found.")
|
|
262
382
|
},
|
|
263
383
|
async execute(args) {
|
|
264
|
-
|
|
265
|
-
|
|
266
|
-
|
|
384
|
+
// 复核项 4(#170):不可见(explicit 他 scope)= 视作不存在,无存在性泄漏。
|
|
385
|
+
const target = visibleForTool(args.id, arguments[1]);
|
|
386
|
+
if (target) service.remove(args.id);
|
|
387
|
+
return { deleted: !!target };
|
|
267
388
|
}
|
|
268
389
|
}),
|
|
269
390
|
|