@jjh32456/dsh-longlive 1.6.8 → 1.8.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/CHANGELOG.md +69 -1539
- package/LICENSE +1 -1
- package/QUICKSTART.md +33 -12
- package/README.en.md +17 -7
- package/README.md +29 -10
- package/SPEC.md +5 -5
- package/cordis.patch.yml +17 -12
- package/docs/ANSWER-GUARD.md +92 -0
- package/docs/ARCHITECTURE.md +3 -3
- package/docs/AUTO-RECALL-WIRING.md +1 -1
- package/docs/AUTO-RECALL.md +15 -9
- package/docs/CONFIG.md +143 -37
- package/docs/ERRORS.md +17 -5
- package/docs/FAQ.md +7 -7
- package/docs/FEATURES.md +66 -29
- package/docs/MIGRATION-1.6.0.md +2 -2
- package/docs/OPERATIONS.md +6 -6
- package/docs/PANEL-INTEGRATION.md +6 -0
- package/docs/PRIVACY.md +88 -0
- package/docs/QUICKSTART.md +5 -5
- package/docs/README.en.md +3 -2
- package/docs/RESIDENT-WATCHER.md +1 -1
- package/docs/SESSION-WATCH.md +6 -6
- package/docs/TROUBLESHOOTING.md +4 -4
- package/package.json +42 -117
- package/runbook.md +2 -2
- package/scripts/backfill-auto-cards.mjs +98 -0
- package/scripts/install-surgery-task.ps1 +5 -2
- package/scripts/lib/session-watch-fixture.mjs +1 -1
- package/scripts/longlive-doctor.mjs +6 -0
- package/scripts/migrate-180.mjs +163 -0
- package/scripts/recall-index-build.mjs +128 -0
- package/scripts/session-watch-drill.mjs +1 -1
- package/scripts/surgery-run.cmd +1 -1
- package/scripts/unattended-surgery.ps1 +5 -2
- package/scripts/verify-card-anchors.mjs +138 -0
- package/src/actions/audit.js +125 -0
- package/src/actions/evidence.js +122 -0
- package/src/actions/fs.js +162 -0
- package/src/activity/index.js +129 -18
- package/src/activity/model.js +11 -1
- package/src/activity/store.js +22 -3
- package/src/activity/thinking.js +176 -0
- package/src/adapters/constants.js +11 -0
- package/src/adapters/engram/data/aliases.json +51 -0
- package/src/adapters/engram/data/policy.json +8 -0
- package/src/adapters/engram/data/routing-rules.json +546 -0
- package/src/adapters/engram/index.js +235 -0
- package/src/adapters/registry.js +315 -0
- package/src/answer/audit.js +193 -0
- package/src/answer/evidence.js +30 -0
- package/src/answer/guard.js +1370 -0
- package/src/answer/index.js +1067 -0
- package/src/answer/query.js +111 -0
- package/src/answer/second-hop.mjs +101 -0
- package/src/auto-truncate.js +1 -1
- package/src/config.js +145 -48
- package/src/deps.js +6 -4
- package/src/features.js +158 -8
- package/src/identity/index.js +13 -5
- package/src/identity/inject-position.js +2 -2
- package/src/index.js +97 -7
- package/src/lifecycle/gap-clocks.js +328 -0
- package/src/lifecycle/index.js +269 -0
- package/src/memory/activity-capture.js +229 -0
- package/src/memory/anchors.js +194 -0
- package/src/memory/audit-records.js +222 -0
- package/src/memory/auto-cards.js +401 -0
- package/src/memory/capture.js +442 -0
- package/src/memory/card-anchors.js +203 -0
- package/src/memory/conflicts.js +106 -0
- package/src/memory/decay.js +311 -0
- package/src/memory/distill.js +471 -0
- package/src/memory/event-index.js +917 -0
- package/src/memory/evidence-chain.js +295 -0
- package/src/memory/evidence.js +145 -0
- package/src/memory/filter-runner.js +1 -1
- package/src/memory/filter.js +5 -1
- package/src/memory/forget-v2.js +410 -0
- package/src/memory/forget.js +71 -0
- package/src/memory/frames-activation.js +247 -0
- package/src/memory/fts-query.js +174 -0
- package/src/memory/governance.js +604 -0
- package/src/memory/index.js +1 -0
- package/src/memory/l2-gate.js +169 -0
- package/src/memory/lock.js +121 -0
- package/src/memory/migrate.js +139 -0
- package/src/memory/recall-v2.js +223 -0
- package/src/memory/recall.js +65 -2
- package/src/memory/records.js +425 -0
- package/src/memory/retention.js +128 -0
- package/src/memory/review.js +124 -0
- package/src/memory/trust.js +88 -0
- package/src/memory/version-chain.js +264 -0
- package/src/memory/wal.js +137 -0
- package/src/memory/working-frame.js +516 -0
- package/src/metrics/classify.mjs +5 -3
- package/src/metrics/index.mjs +21 -1
- package/src/metrics/prestep-latency.mjs +300 -0
- package/src/metrics/store.mjs +25 -0
- package/src/policy-profile.js +18 -3
- package/src/recall/anaphora.mjs +87 -0
- package/src/recall/cards.mjs +7 -0
- package/src/recall/embedding.mjs +8 -8
- package/src/recall/entities.mjs +112 -0
- package/src/recall/index.js +235 -19
- package/src/recall/inject-order.mjs +10 -8
- package/src/recall/privacy.mjs +1 -1
- package/src/recall/recall-core.mjs +116 -10
- package/src/recall/shared-visibility.js +302 -0
- package/src/recall/time-normalizer.mjs +196 -0
- package/src/role/activation.js +191 -0
- package/src/role/attention.js +318 -0
- package/src/role/base.js +36 -0
- package/src/role/channels.js +423 -0
- package/src/role/index.js +425 -0
- package/src/role/interfaces.js +115 -0
- package/src/role/personality.js +370 -0
- package/src/role/readers.js +151 -0
- package/src/role/relationships.js +477 -0
- package/src/role/store.js +202 -0
- package/src/role/writeprotect.js +373 -0
- package/src/router/aliases.json +176 -25
- package/src/router/background/agent-runner.mjs +1 -1
- package/src/router/background/anchor.mjs +1 -1
- package/src/router/background/audit.mjs +2 -2
- package/src/router/background/budget-ledger.mjs +2 -2
- package/src/router/background/host-sim.mjs +1 -1
- package/src/router/background/memory.mjs +4 -4
- package/src/router/background/queue.mjs +1 -1
- package/src/router/background/retry.mjs +1 -1
- package/src/router/background/router.mjs +1 -1
- package/src/router/background/stream.mjs +1 -1
- package/src/router/background/tool-policy.mjs +1 -1
- package/src/router/design-headings.json +5 -5
- package/src/router/face/config.mjs +1 -1
- package/src/router/face/identity.mjs +3 -3
- package/src/router/judge.mjs +8 -7
- package/src/router/policies/generic.json +21 -4
- package/src/router/route.js +109 -22
- package/src/router/router.mjs +10 -6
- package/src/router/routing-rules.json +260 -445
- package/src/router/run-sim.mjs +8 -8
- package/src/router/runtime.js +23 -12
- package/src/router/service/config.mjs +5 -5
- package/src/router/service/context.mjs +1 -1
- package/src/router/service/queue.mjs +1 -1
- package/src/router/shadow.mjs +4 -3
- package/src/surgery-recovery.js +2 -2
- package/src/tool-output.js +2 -2
- package/src/tools.js +39 -7
- package/src/truncate-visible.js +2 -2
- package/src/ui/actions.js +1312 -0
- package/src/ui/panel-api.js +367 -0
- package/src/ui/panel-auth.js +251 -0
- package/src/ui/panel-integrity.js +243 -0
- package/src/ui/routes-readonly.js +695 -0
- package/src/ui/routes.js +3 -0
- package/docs/DEV.md +0 -279
- package/docs/PRESENCE-BUS-WIRING.md +0 -491
- package/docs/PRESENCE-BUS.md +0 -694
- package/docs/SURGERY.md +0 -186
- package/scripts/mark-core-cards.mjs +0 -289
- package/scripts/pack.mjs +0 -392
- package/scripts/router-proposals.mjs +0 -386
- package/src/router/fixtures/M1.json +0 -16
- package/src/router/fixtures/M2.json +0 -16
- package/src/router/fixtures/M3.json +0 -20
- package/src/router/fixtures/M4.json +0 -20
- package/src/router/fixtures/M5.json +0 -45
- package/src/router/fixtures/M6.json +0 -53
- package/src/router/fixtures/M7.json +0 -46
- package/src/router/fixtures/S1.json +0 -20
- package/src/router/fixtures/S2.json +0 -23
- package/src/router/fixtures/S3.json +0 -20
- package/src/router/fixtures/S4.json +0 -34
- package/src/router/fixtures/S5.json +0 -43
- package/src/router/fixtures/S6.json +0 -47
- package/src/router/fixtures/T1.json +0 -23
- package/src/router/fixtures/T2.json +0 -16
- package/src/router/fixtures/T3.json +0 -19
- package/src/router/fixtures/T4.json +0 -16
- package/src/router/fixtures/X1.json +0 -17
- package/src/router/fixtures/X2.json +0 -20
- package/src/router/gen-design-headings.mjs +0 -107
- package/src/router/policies/owner-rulings-2026-11-03.json +0 -62
- package/test/activity-realtime.test.mjs +0 -959
- package/test/activity-stream.test.mjs +0 -512
- package/test/auto-recall.test.mjs +0 -474
- package/test/auto-truncate.test.mjs +0 -931
- package/test/autobind.test.mjs +0 -399
- package/test/bus.test.mjs +0 -1572
- package/test/cli-guard.test.mjs +0 -198
- package/test/conclusion.test.mjs +0 -408
- package/test/config-schema.test.mjs +0 -210
- package/test/d6-output.test.mjs +0 -194
- package/test/d6.test.mjs +0 -395
- package/test/deps.test.mjs +0 -342
- package/test/doctor-setup.test.mjs +0 -297
- package/test/embedding-recall.test.mjs +0 -347
- package/test/feature-flags.test.mjs +0 -585
- package/test/filter-shadow.test.mjs +0 -170
- package/test/fixtures/activity-append-child.mjs +0 -29
- package/test/fixtures/baseline-0.8.0-registration.json +0 -556
- package/test/fixtures/d6-header-tools.json +0 -1
- package/test/fixtures/lock-child.mjs +0 -67
- package/test/fixtures/pre-step-container.mjs +0 -100
- package/test/fixtures/privacy-memory/topics/pii-unmarked.md +0 -9
- package/test/fixtures/privacy-memory/topics/secret-local-only.md +0 -11
- package/test/fixtures/profile-session-longlive-config.json +0 -20
- package/test/fixtures/proposal-cas-child.mjs +0 -66
- package/test/host-engine-status.test.mjs +0 -307
- package/test/hot-truncate.test.mjs +0 -1349
- package/test/identity-card-marker.test.mjs +0 -307
- package/test/identity-router/run.mjs +0 -721
- package/test/integration-pre-step.test.mjs +0 -553
- package/test/live.test.mjs +0 -1023
- package/test/maintenance-schedule.test.mjs +0 -538
- package/test/memory.test.mjs +0 -2828
- package/test/metrics.test.mjs +0 -478
- package/test/orchestrator.test.mjs +0 -266
- package/test/p1_probe.mjs +0 -97
- package/test/packaging.test.mjs +0 -184
- package/test/portability.test.mjs +0 -343
- package/test/rc4-regressions.test.mjs +0 -562
- package/test/rc5-regressions.test.mjs +0 -197
- package/test/rc6-chinese.test.mjs +0 -394
- package/test/router-1100/apply-off.test.mjs +0 -175
- package/test/router-1100/budget-reconcile.test.mjs +0 -215
- package/test/router-1100/config-context.test.mjs +0 -211
- package/test/router-1100/helpers.mjs +0 -129
- package/test/router-1100/hint-envelope.test.mjs +0 -150
- package/test/router-1100/lock-crossproc.test.mjs +0 -206
- package/test/router-1100/proposal-cas-crossproc.test.mjs +0 -184
- package/test/router-1100/router-apply.test.mjs +0 -372
- package/test/router-1100/router-fixb.test.mjs +0 -602
- package/test/router-1100/router-proposals.test.mjs +0 -405
- package/test/router-1100/router-queue-lease-wal.test.mjs +0 -355
- package/test/router-1100/router-security-cr.test.mjs +0 -333
- package/test/router-1100/service-lifecycle.test.mjs +0 -220
- package/test/router-1100/tools-contract.test.mjs +0 -164
- package/test/router-1100/turn-reflect-tick.test.mjs +0 -89
- package/test/router-1100/v6-contract.test.mjs +0 -158
- package/test/router-1100/v6b-contract.test.mjs +0 -293
- package/test/router-1100/v6c-contract.test.mjs +0 -249
- package/test/router-1100/wal-rotation.test.mjs +0 -375
- package/test/router-1100/wp1-config.test.mjs +0 -170
- package/test/router-1100/wp1-default-off.test.mjs +0 -77
- package/test/router-1100/wp1-model-status.test.mjs +0 -90
- package/test/router-1100/wp1-status.test.mjs +0 -220
- package/test/router-1100/wp1-tools.test.mjs +0 -146
- package/test/router-1100/wp5-cli.test.mjs +0 -240
- package/test/router-1100/wp5-model-auto.test.mjs +0 -418
- package/test/router-1100/wp6-merge.test.mjs +0 -386
- package/test/router-1100/wp6-wake.test.mjs +0 -502
- package/test/router-background/audit.test.mjs +0 -37
- package/test/router-background/budget-ledger.test.mjs +0 -330
- package/test/router-background/cross-process.test.mjs +0 -222
- package/test/router-background/fixtures/cross-child.mjs +0 -85
- package/test/router-background/fixtures/real-shapes.mjs +0 -239
- package/test/router-background/router.test.mjs +0 -1076
- package/test/router-background/stream-retry.test.mjs +0 -358
- package/test/router-background/tool-agent.test.mjs +0 -384
- package/test/router-face/bridge.test.mjs +0 -169
- package/test/router-face/face.test.mjs +0 -316
- package/test/router-failclosed.test.mjs +0 -466
- package/test/router-identity.test.mjs +0 -384
- package/test/router-l1-l2.contract.test.mjs +0 -1075
- package/test/router-memory-tools.test.mjs +0 -262
- package/test/router-policy.test.mjs +0 -225
- package/test/router-runtime.test.mjs +0 -438
- package/test/router-shadow.test.mjs +0 -287
- package/test/router.test.mjs +0 -903
- package/test/run-rc.mjs +0 -315
- package/test/run.mjs +0 -2469
- package/test/safety-defaults.test.mjs +0 -181
- package/test/seeded-load.test.mjs +0 -436
- package/test/session-original.test.mjs +0 -338
- package/test/state-truth.test.mjs +0 -937
- package/test/surgery-recovery.test.mjs +0 -936
- package/test/t7_drive.mjs +0 -151
- package/test/tool-output.test.mjs +0 -393
- package/test/tool-schema.test.mjs +0 -177
- package/test/tools_contract.test.mjs +0 -716
- package/test/truncate-visible.test.mjs +0 -1150
- package/test/ui-flags.test.mjs +0 -477
- package/test/v4-hardening.test.mjs +0 -427
- package/test/version-consistency.test.mjs +0 -286
package/src/recall/index.js
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* dsh-auto-recall — 插件骨架(**未安装**,见 ../README.md 与 WIRING.md §9)。
|
|
3
3
|
*
|
|
4
|
-
* 与 WIRING.md §3
|
|
4
|
+
* 与 WIRING.md §3 的差异(评审第三轮 mustFix 已修):
|
|
5
5
|
* - 注册用显式 `{ prepend: false }`,不跟 identity-router 抢外层;
|
|
6
6
|
* identity-router 在 `next()` 之前把开机卡插到第一条真实 user 之后,auto 作为内层
|
|
7
7
|
* 一定看得到它,最终顺序固定为 `[user, boot, runtime, auto]`(真实 Cordis
|
|
@@ -10,7 +10,7 @@
|
|
|
10
10
|
* - firstUserText 只认真正的用户消息:`session/event` 里跳过 `【开机卡】`/`【自动想起】`
|
|
11
11
|
* 注入(它们不再是 kind:'user',但历史消息仍可能带标记),否则开机卡会污染之后
|
|
12
12
|
* 每一轮的查询词;第 1 轮内后来到达的真人消息覆盖先前候选,防外部插件用
|
|
13
|
-
* kind:'user'
|
|
13
|
+
* kind:'user' 把文本插到真实提问之前(评审 ADV1);
|
|
14
14
|
* - 注入前过 privacyScan(privacy-gate 的 scanPrivacy);标题、摘要、路径都扫,
|
|
15
15
|
* 路径命中整卡丢弃;
|
|
16
16
|
* - identity-router 已注入的卡路径进 seenPaths(路径级排除),不再无条件排除
|
|
@@ -23,6 +23,13 @@
|
|
|
23
23
|
*/
|
|
24
24
|
|
|
25
25
|
import { loadCards } from './cards.mjs';
|
|
26
|
+
import { sharedVisibility } from './shared-visibility.js';
|
|
27
|
+
import { extractTimeRefs } from './time-normalizer.mjs';
|
|
28
|
+
import { readEntityAliases, resolveEntityAliases, expandQueryWithEntities } from './entities.mjs';
|
|
29
|
+
import { resolveAnaphora, expandQueryWithAnaphora } from './anaphora.mjs';
|
|
30
|
+
import { appendAuditRecord } from '../memory/audit-records.js';
|
|
31
|
+
import { buildEvidencePackage, writeEvidencePackage } from '../memory/evidence.js';
|
|
32
|
+
import { fastAnswerFromRecallItems } from '../memory/recall-v2.js';
|
|
26
33
|
import { buildQueryText, chooseRecall, decideTrigger, renderInjection } from './recall-core.mjs';
|
|
27
34
|
import { createEmbeddingRuntime } from './embedding.mjs';
|
|
28
35
|
import { privacyScan } from './privacy.mjs';
|
|
@@ -34,10 +41,13 @@ import {
|
|
|
34
41
|
isRealUserMessage,
|
|
35
42
|
mergeInjectionOrder,
|
|
36
43
|
} from './inject-order.mjs';
|
|
37
|
-
import { appendFileSync, mkdirSync } from 'node:fs';
|
|
44
|
+
import { appendFileSync, mkdirSync, readdirSync, statSync } from 'node:fs';
|
|
38
45
|
import { dirname, join } from 'node:path';
|
|
39
46
|
import { dshHome } from '../home.js';
|
|
40
47
|
import { DEFAULT_MEMORY_DIRNAME } from '../memory/paths.js';
|
|
48
|
+
import { startPrestepPhase, notePrestepDegrade, setPrestepMeta } from '../metrics/prestep-latency.mjs';
|
|
49
|
+
import { readVerifiedAutoCards } from '../memory/auto-cards.js';
|
|
50
|
+
import { sampleContentHash } from '../memory/auto-cards.js';
|
|
41
51
|
|
|
42
52
|
export const name = 'dsh-auto-recall';
|
|
43
53
|
export const inject = [];
|
|
@@ -72,12 +82,13 @@ const DEFAULTS = Object.freeze({
|
|
|
72
82
|
privacyPolicy: 'drop',
|
|
73
83
|
skipSubagents: true,
|
|
74
84
|
log: false,
|
|
75
|
-
embedding: { enabled:
|
|
85
|
+
embedding: { enabled: true },
|
|
86
|
+
evidencePackage: true,
|
|
76
87
|
});
|
|
77
88
|
|
|
78
89
|
function normalize(config = {}) {
|
|
79
90
|
const cfg = { ...DEFAULTS, ...config };
|
|
80
|
-
//
|
|
91
|
+
// 评审 M1:空 / 未配置时走可移植默认链,绝不落到构建机的绝对路径。
|
|
81
92
|
const root = typeof cfg.memoryRoot === 'string' ? cfg.memoryRoot.trim() : '';
|
|
82
93
|
cfg.memoryRoot = root === '' ? defaultMemoryRoot() : root;
|
|
83
94
|
// 注入硬上限 2K token/轮(A4);下限保留 200,避免误配把注入挤成一条。
|
|
@@ -90,7 +101,11 @@ function normalize(config = {}) {
|
|
|
90
101
|
// 测试/嵌入时可注入消息工厂,避免依赖 @deepseek-ai/dsh-llm 的解析环境。
|
|
91
102
|
cfg.createUserMessage = typeof config.createUserMessage === 'function' ? config.createUserMessage : null;
|
|
92
103
|
// embedding 由 embedding.mjs 自己归一化(enabled 默认 false、modelPath 必填)。
|
|
93
|
-
cfg.embedding = config.embedding && typeof config.embedding === 'object' ? { ...config.embedding } : { enabled:
|
|
104
|
+
cfg.embedding = config.embedding && typeof config.embedding === 'object' ? { ...config.embedding } : { enabled: true };
|
|
105
|
+
cfg.evidencePackage = cfg.evidencePackage !== false;
|
|
106
|
+
cfg.evidenceAuditKey = config.evidenceAuditKey && (Buffer.isBuffer(config.evidenceAuditKey) || typeof config.evidenceAuditKey === 'string')
|
|
107
|
+
? config.evidenceAuditKey
|
|
108
|
+
: null;
|
|
94
109
|
return cfg;
|
|
95
110
|
}
|
|
96
111
|
|
|
@@ -168,19 +183,57 @@ export function apply(ctx, config = {}) {
|
|
|
168
183
|
}
|
|
169
184
|
};
|
|
170
185
|
|
|
186
|
+
// B2: entity alias registry is read lazily (missing file => no-op, no latency).
|
|
187
|
+
let entityAliasesCache = null;
|
|
188
|
+
const getEntityAliases = () => {
|
|
189
|
+
if (entityAliasesCache !== null) return entityAliasesCache;
|
|
190
|
+
try {
|
|
191
|
+
entityAliasesCache = readEntityAliases({ memoryRoot: cfg.memoryRoot });
|
|
192
|
+
} catch {
|
|
193
|
+
entityAliasesCache = { present: false, aliases: {} };
|
|
194
|
+
}
|
|
195
|
+
return entityAliasesCache;
|
|
196
|
+
};
|
|
197
|
+
|
|
171
198
|
const states = new Map();
|
|
172
199
|
const stateFor = (key) => {
|
|
173
200
|
let st = states.get(key);
|
|
174
201
|
if (!st) {
|
|
175
|
-
st = { turn: 0, seen: new Set(), lastQuery: '', compacted: false, warned: false, firstUserText: '' };
|
|
202
|
+
st = { turn: 0, seen: new Set(), lastQuery: '', compacted: false, warned: false, firstUserText: '', sharedInjected: false };
|
|
176
203
|
states.set(key, st);
|
|
177
204
|
}
|
|
178
205
|
return st;
|
|
179
206
|
};
|
|
180
207
|
|
|
208
|
+
/** Cheap mtime/size signature so new or edited cards are visible next turn. */
|
|
209
|
+
const cardSignature = () => {
|
|
210
|
+
const parts = [];
|
|
211
|
+
const walk = (dir) => {
|
|
212
|
+
let entries = [];
|
|
213
|
+
try { entries = readdirSync(dir, { withFileTypes: true }); } catch { return; }
|
|
214
|
+
for (const entry of entries) {
|
|
215
|
+
const full = join(dir, entry.name);
|
|
216
|
+
if (entry.isDirectory()) walk(full);
|
|
217
|
+
else if (entry.isFile() && /\.md$/i.test(entry.name)) {
|
|
218
|
+
try {
|
|
219
|
+
const st = statSync(full);
|
|
220
|
+
parts.push(`${full}:${st.mtimeMs}:${st.size}:${sampleContentHash(full, st.size)}`);
|
|
221
|
+
} catch { /* unreadable card is not part of the signature */ }
|
|
222
|
+
}
|
|
223
|
+
}
|
|
224
|
+
};
|
|
225
|
+
for (const dir of cfg.dirs) walk(join(cfg.memoryRoot, dir));
|
|
226
|
+
parts.sort();
|
|
227
|
+
return parts.join('|');
|
|
228
|
+
};
|
|
229
|
+
|
|
181
230
|
let cards = null;
|
|
231
|
+
let cardsSignature = '';
|
|
182
232
|
const getCards = () => {
|
|
183
|
-
|
|
233
|
+
const signature = cardSignature();
|
|
234
|
+
if (cards && signature === cardsSignature) return cards;
|
|
235
|
+
cardsSignature = signature;
|
|
236
|
+
cards = null;
|
|
184
237
|
try {
|
|
185
238
|
cards = loadCards(cfg.memoryRoot, { dirs: cfg.dirs });
|
|
186
239
|
} catch (error) {
|
|
@@ -239,13 +292,55 @@ export function apply(ctx, config = {}) {
|
|
|
239
292
|
const st = stateFor(key);
|
|
240
293
|
const messages = Array.isArray(decision.messages) ? decision.messages : [];
|
|
241
294
|
const turn = Number.isFinite(Number(payload?.turn)) ? Number(payload.turn) : st.turn + 1;
|
|
242
|
-
|
|
295
|
+
// M2-C R1–R6: shared visibility block is injected on the FIRST turn only.
|
|
296
|
+
const rawSharedNow = payload?.at ?? payload?.eventTime ?? payload?.now ?? null;
|
|
297
|
+
const sharedNow = rawSharedNow === null || rawSharedNow === undefined
|
|
298
|
+
? Date.now()
|
|
299
|
+
: (Number.isFinite(Number(rawSharedNow)) ? Number(rawSharedNow) : (Date.parse(String(rawSharedNow)) || Date.now()));
|
|
300
|
+
let sharedMessage = null;
|
|
301
|
+
let sharedVisibilityResult = null;
|
|
302
|
+
const attachShared = (value) => (sharedMessage === null ? value : { ...value, messages: mergeInjectionOrder(Array.isArray(value?.messages) ? value.messages : messages, sharedMessage) });
|
|
303
|
+
|
|
304
|
+
// B3: recall phase wraps the whole recall decision/injection (timing only).
|
|
305
|
+
const recallPhase = startPrestepPhase(key, turn, 'recall');
|
|
243
306
|
try {
|
|
307
|
+
if (turn === 1 && st.sharedInjected !== true && cfg.memoryRoot) {
|
|
308
|
+
try {
|
|
309
|
+
const visibility = sharedVisibility({
|
|
310
|
+
memoryRoot: cfg.memoryRoot,
|
|
311
|
+
currentSessionId: payload?.agent?.session?.id ?? key,
|
|
312
|
+
now: sharedNow,
|
|
313
|
+
privacyScan,
|
|
314
|
+
});
|
|
315
|
+
if (visibility && visibility.block) {
|
|
316
|
+
const factory = await getCreateUserMessage();
|
|
317
|
+
if (factory) {
|
|
318
|
+
sharedMessage = assertInjectionMessage(factory({ content: [{ type: 'text', text: visibility.block }], source: injectionSource() }));
|
|
319
|
+
sharedVisibilityResult = { count: visibility.count, chars: visibility.block.length, sessions: visibility.sessions.map((session) => session.sessionId) };
|
|
320
|
+
st.sharedInjected = true;
|
|
321
|
+
}
|
|
322
|
+
}
|
|
323
|
+
} catch { /* shared visibility must never block the turn */ }
|
|
324
|
+
}
|
|
325
|
+
const recentTexts = userTexts(messages).slice(-2);
|
|
244
326
|
const queryText = buildQueryText({
|
|
245
327
|
firstUserText: st.firstUserText,
|
|
246
|
-
recentTexts
|
|
328
|
+
recentTexts,
|
|
247
329
|
cwd: process.cwd(),
|
|
248
330
|
});
|
|
331
|
+
// B2: entity aliases + anaphora expansion run before retrieval (read-only channels).
|
|
332
|
+
const aliases = getEntityAliases();
|
|
333
|
+
const aliasResolution = aliases.present ? resolveEntityAliases(queryText, aliases.aliases) : { terms: [], matches: [] };
|
|
334
|
+
const entityExpanded = expandQueryWithEntities(queryText, aliasResolution);
|
|
335
|
+
const anaphora = resolveAnaphora({ text: queryText, recentTexts });
|
|
336
|
+
const anaphoraExpanded = expandQueryWithAnaphora(entityExpanded.text, anaphora);
|
|
337
|
+
const expandedQuery = anaphoraExpanded.text;
|
|
338
|
+
// Event time only when the host/pre-step provides it; never Date.now() here.
|
|
339
|
+
const rawEventNow = payload?.at ?? payload?.eventTime ?? payload?.now ?? null;
|
|
340
|
+
const eventNow = rawEventNow === null || rawEventNow === undefined
|
|
341
|
+
? null
|
|
342
|
+
: (Number.isFinite(Number(rawEventNow)) ? Number(rawEventNow) : (Date.parse(String(rawEventNow)) || null));
|
|
343
|
+
const queryTimeRefs = eventNow === null ? [] : extractTimeRefs(queryText, { now: eventNow });
|
|
249
344
|
|
|
250
345
|
const gate = decideTrigger({
|
|
251
346
|
step: Number(payload?.step),
|
|
@@ -255,12 +350,30 @@ export function apply(ctx, config = {}) {
|
|
|
255
350
|
options: { everyNTurns: cfg.everyNTurns },
|
|
256
351
|
});
|
|
257
352
|
if (!gate.recall) {
|
|
353
|
+
notePrestepDegrade(key, turn, { kind: 'gate-skip', phase: 'recall', detail: gate.why });
|
|
258
354
|
logRecall(key, turn, { items: [], rejections: [] });
|
|
259
355
|
observe({ key, turn, why: gate.why, queryChars: queryText.length, tokens: 0, ms: Date.now() - started });
|
|
260
|
-
return decision;
|
|
356
|
+
return attachShared(decision);
|
|
261
357
|
}
|
|
262
358
|
|
|
263
359
|
const loaded = getCards();
|
|
360
|
+
// 1.8.0 AutoCards: JSONL event→index cards are recall candidates too, so a
|
|
361
|
+
// matched card carries its origin anchor into the injected block.
|
|
362
|
+
let loadedForRecall = loaded;
|
|
363
|
+
if (cfg.memoryRoot) {
|
|
364
|
+
try {
|
|
365
|
+
const autoCards = readVerifiedAutoCards({ memoryRoot: cfg.memoryRoot }).map((card) => ({
|
|
366
|
+
path: `auto:${card.cardId}`,
|
|
367
|
+
title: card.title,
|
|
368
|
+
body: [card.title, Array.isArray(card.terms) ? card.terms.join(' ') : ''].filter(Boolean).join('\n'),
|
|
369
|
+
scopeIn: [],
|
|
370
|
+
aliases: Array.isArray(card.terms) ? card.terms : [],
|
|
371
|
+
cardId: card.cardId,
|
|
372
|
+
anchor: card.anchor,
|
|
373
|
+
}));
|
|
374
|
+
if (autoCards.length > 0) loadedForRecall = [...loaded, ...autoCards];
|
|
375
|
+
} catch { /* cards are additive; never block markdown recall */ }
|
|
376
|
+
}
|
|
264
377
|
// 路径级排除:identity-router 本轮已经注入过的卡,不再注入第二遍。
|
|
265
378
|
// 不再无条件排除首轮 handover/:没装 identity-router 时那张卡就是唯一开机卡。
|
|
266
379
|
const seen = [...new Set([
|
|
@@ -274,25 +387,29 @@ export function apply(ctx, config = {}) {
|
|
|
274
387
|
let vectors = null;
|
|
275
388
|
if (embedding.enabled) {
|
|
276
389
|
try {
|
|
277
|
-
const ready = await embedding.ensure(loaded);
|
|
390
|
+
const ready = await embedding.ensure(loaded, { signature: cardsSignature });
|
|
278
391
|
if (ready.ok) {
|
|
279
392
|
queryVector = await embedding.embedQuery(queryText);
|
|
280
393
|
vectors = ready.cardVectors;
|
|
281
394
|
} else if (!embeddingWarned) {
|
|
282
395
|
embeddingWarned = true;
|
|
396
|
+
notePrestepDegrade(key, turn, { kind: 'embedding-fallback', phase: 'recall', detail: ready.code ?? 'not-ready' });
|
|
283
397
|
warn('EMBEDDING_FALLBACK', ready.code, ready.message);
|
|
284
398
|
}
|
|
285
399
|
} catch (error) {
|
|
286
400
|
if (!embeddingWarned) {
|
|
287
401
|
embeddingWarned = true;
|
|
402
|
+
notePrestepDegrade(key, turn, { kind: 'embedding-fallback', phase: 'recall', detail: String(error?.code ?? error?.message ?? error) });
|
|
288
403
|
warn('EMBEDDING_FALLBACK', error?.code ?? error?.message ?? error);
|
|
289
404
|
}
|
|
290
405
|
}
|
|
291
406
|
}
|
|
292
407
|
|
|
293
|
-
|
|
294
|
-
|
|
295
|
-
|
|
408
|
+
// B3: budget phase = ranking + budget trimming inside chooseRecall.
|
|
409
|
+
const budgetPhase = startPrestepPhase(key, turn, 'budget');
|
|
410
|
+
const recallInput = {
|
|
411
|
+
queryText: expandedQuery,
|
|
412
|
+
cards: loadedForRecall,
|
|
296
413
|
queryVector,
|
|
297
414
|
vectors,
|
|
298
415
|
budget: cfg.budget,
|
|
@@ -304,6 +421,9 @@ export function apply(ctx, config = {}) {
|
|
|
304
421
|
privacyPolicy: cfg.privacyPolicy,
|
|
305
422
|
privacyScan,
|
|
306
423
|
trace: true,
|
|
424
|
+
// B2 channels: neutral when empty (legacy ordering unchanged).
|
|
425
|
+
queryTimeRefs,
|
|
426
|
+
entityAliases: aliases.present ? aliases.aliases : undefined,
|
|
307
427
|
fusion: {
|
|
308
428
|
keywordWeight: embedding.config.keywordWeight,
|
|
309
429
|
vectorWeight: embedding.config.vectorWeight,
|
|
@@ -311,9 +431,97 @@ export function apply(ctx, config = {}) {
|
|
|
311
431
|
minFusedScore: embedding.config.minFusedScore,
|
|
312
432
|
},
|
|
313
433
|
},
|
|
434
|
+
};
|
|
435
|
+
let result = chooseRecall(recallInput);
|
|
436
|
+
budgetPhase.end();
|
|
437
|
+
// 1.8.0 two-layer quota: at most 2 curated cards + 1 AutoCard per turn;
|
|
438
|
+
// curated hits always render first (tier weight 1.12 only affects order
|
|
439
|
+
// inside the curated layer, so existing output stays stable).
|
|
440
|
+
if (Array.isArray(result.items) && result.items.length > 0) {
|
|
441
|
+
const isAuto = (item) => String(item?.path ?? '').startsWith('auto:');
|
|
442
|
+
let curatedItems = result.items.filter((item) => !isAuto(item))
|
|
443
|
+
.sort((a, b) => (b.score * 1.12) - (a.score * 1.12));
|
|
444
|
+
const eventItems = result.items.filter(isAuto).slice(0, 1);
|
|
445
|
+
// Curated floor: event cards may never squeeze curated cards out.
|
|
446
|
+
if (curatedItems.length === 0 && loaded.length > 0) {
|
|
447
|
+
const curatedOnly = chooseRecall({ ...recallInput, cards: loaded });
|
|
448
|
+
if (curatedOnly.items.length > 0) {
|
|
449
|
+
result = curatedOnly;
|
|
450
|
+
curatedItems = curatedOnly.items.slice().sort((a, b) => (b.score * 1.12) - (a.score * 1.12));
|
|
451
|
+
}
|
|
452
|
+
}
|
|
453
|
+
result.items = [...curatedItems.slice(0, 2), ...eventItems].slice(0, cfg.maxItems);
|
|
454
|
+
}
|
|
455
|
+
// M2-C #6: every recall decision atomically publishes .longlive/evidence/latest.json.
|
|
456
|
+
let evidencePackage = null;
|
|
457
|
+
if (cfg.evidencePackage && cfg.memoryRoot) {
|
|
458
|
+
try {
|
|
459
|
+
const nowIso = new Date().toISOString();
|
|
460
|
+
const fast = fastAnswerFromRecallItems({ question: queryText, items: result.items });
|
|
461
|
+
const pkg = buildEvidencePackage({
|
|
462
|
+
sessionId: payload?.agent?.session?.id ?? key,
|
|
463
|
+
turn,
|
|
464
|
+
query: queryText,
|
|
465
|
+
timings: {
|
|
466
|
+
questionAt: eventNow === null ? null : new Date(eventNow).toISOString(),
|
|
467
|
+
queueMs: 0,
|
|
468
|
+
prefetchAt: new Date(started).toISOString(),
|
|
469
|
+
retrievedAt: nowIso,
|
|
470
|
+
evidenceReadyAt: nowIso,
|
|
471
|
+
},
|
|
472
|
+
anchors: result.items.map((item) => ({
|
|
473
|
+
path: item.path,
|
|
474
|
+
excerpt: `${item.title ?? ''} — ${item.excerpt ?? ''}`.trim(),
|
|
475
|
+
score: Number.isFinite(Number(item.score)) ? Number(item.score) : null,
|
|
476
|
+
l0Status: 'card',
|
|
477
|
+
})),
|
|
478
|
+
status: result.items.length > 0 ? 'ready' : 'no_evidence',
|
|
479
|
+
fastAnswer: fast.fastAnswer === null ? null : { text: fast.fastAnswer.text, valueType: fast.fastAnswer.valueType, anchor: fast.fastAnswer.anchor },
|
|
480
|
+
fallback: { used: false, reason: result.items.length > 0 ? null : 'zero-hit' },
|
|
481
|
+
});
|
|
482
|
+
const written = writeEvidencePackage({ memoryRoot: cfg.memoryRoot, pkg, auditKey: cfg.evidenceAuditKey });
|
|
483
|
+
evidencePackage = { queryId: pkg.queryId, status: pkg.status, anchors: pkg.anchors.length, fastAnswer: pkg.fastAnswer !== null, packageSha256: written.packageSha256 };
|
|
484
|
+
} catch (error) {
|
|
485
|
+
// Write failure must never block injection.
|
|
486
|
+
evidencePackage = { error: error?.code ?? 'E_EVIDENCE_WRITE' };
|
|
487
|
+
notePrestepDegrade(key, turn, { kind: 'evidence-write', phase: 'recall', detail: String(error?.code ?? error?.message ?? error) });
|
|
488
|
+
warn('EVIDENCE_WRITE_FAILED', error);
|
|
489
|
+
}
|
|
490
|
+
}
|
|
491
|
+
if (Number(result?.hits?.droppedByBudget ?? 0) > 0) {
|
|
492
|
+
notePrestepDegrade(key, turn, { kind: 'budget-trim', phase: 'budget', detail: `dropped=${result.hits.droppedByBudget}` });
|
|
493
|
+
}
|
|
494
|
+
setPrestepMeta(key, turn, {
|
|
495
|
+
budget: cfg.budget,
|
|
496
|
+
eligible: Number(result?.hits?.eligible ?? 0),
|
|
497
|
+
tokens: Number(result?.tokens ?? 0),
|
|
314
498
|
});
|
|
315
499
|
|
|
500
|
+
// A5 unified audit: every real recall decision lands one .longlive/audit/records.jsonl row.
|
|
501
|
+
try {
|
|
502
|
+
appendAuditRecord({
|
|
503
|
+
memoryRoot: cfg.memoryRoot,
|
|
504
|
+
action: 'recall',
|
|
505
|
+
recordId: result.items.length > 0 ? result.items.map((item) => item.path).join(',') : null,
|
|
506
|
+
readerTier: 'host',
|
|
507
|
+
questionId: 'unknown',
|
|
508
|
+
expectedTurns: null,
|
|
509
|
+
answerText: null,
|
|
510
|
+
extra: {
|
|
511
|
+
turn,
|
|
512
|
+
queryChars: queryText.length,
|
|
513
|
+
expanded: expandedQuery !== queryText,
|
|
514
|
+
expansion: { entityTerms: aliasResolution.terms ?? [], anaphoraTerms: anaphora.terms ?? [], timeRefs: queryTimeRefs.length },
|
|
515
|
+
sharedVisibility: sharedVisibilityResult,
|
|
516
|
+
evidencePackage,
|
|
517
|
+
eligible: result.hits.eligible,
|
|
518
|
+
injected: result.items.map((item) => item.path),
|
|
519
|
+
tokens: result.tokens,
|
|
520
|
+
},
|
|
521
|
+
});
|
|
522
|
+
} catch { /* recall auditing must never block injection */ }
|
|
316
523
|
if (result.items.length === 0) {
|
|
524
|
+
notePrestepDegrade(key, turn, { kind: 'zero-hit', phase: 'recall', detail: `eligible=${result.hits.eligible}` });
|
|
317
525
|
st.lastQuery = queryText;
|
|
318
526
|
st.compacted = false;
|
|
319
527
|
logRecall(key, turn, result);
|
|
@@ -328,12 +536,16 @@ export function apply(ctx, config = {}) {
|
|
|
328
536
|
privacy: result.hits.droppedByPrivacy,
|
|
329
537
|
},
|
|
330
538
|
});
|
|
331
|
-
return decision; //
|
|
539
|
+
return attachShared(decision); // 无命中:什么都不注入(共享可见块仍可注入)
|
|
332
540
|
}
|
|
333
541
|
|
|
542
|
+
// B3: inject phase = render + message factory + merge into decision.messages.
|
|
543
|
+
const injectPhase = startPrestepPhase(key, turn, 'inject');
|
|
334
544
|
const text = renderInjection(result, { privacyScan });
|
|
335
545
|
const factory = await getCreateUserMessage();
|
|
336
546
|
if (!factory) {
|
|
547
|
+
injectPhase.end();
|
|
548
|
+
notePrestepDegrade(key, turn, { kind: 'factory-unavailable', phase: 'inject' });
|
|
337
549
|
logRecall(key, turn, { items: [], rejections: result.rejections });
|
|
338
550
|
return decision;
|
|
339
551
|
}
|
|
@@ -348,6 +560,7 @@ export function apply(ctx, config = {}) {
|
|
|
348
560
|
for (const item of result.items) st.seen.add(item.path);
|
|
349
561
|
st.lastQuery = queryText;
|
|
350
562
|
st.compacted = false;
|
|
563
|
+
injectPhase.end();
|
|
351
564
|
|
|
352
565
|
logRecall(key, turn, result);
|
|
353
566
|
observe({
|
|
@@ -363,15 +576,18 @@ export function apply(ctx, config = {}) {
|
|
|
363
576
|
},
|
|
364
577
|
});
|
|
365
578
|
|
|
366
|
-
return { ...decision, messages: mergeInjectionOrder(messages, message) };
|
|
579
|
+
return { ...decision, messages: mergeInjectionOrder(sharedMessage === null ? messages : mergeInjectionOrder(messages, sharedMessage), message) };
|
|
367
580
|
} catch (error) {
|
|
368
581
|
if (!st.warned) {
|
|
369
582
|
st.warned = true;
|
|
370
583
|
warn('RECALL_FAILED', error?.code ?? error?.message ?? error);
|
|
371
584
|
}
|
|
585
|
+
notePrestepDegrade(key, turn, { kind: 'error', phase: 'recall', detail: String(error?.code ?? error?.message ?? error) });
|
|
372
586
|
observe({ key, turn, why: 'error', error: String(error?.code ?? error?.message ?? error), tokens: 0 });
|
|
373
587
|
logRecall(key, turn, { items: [], rejections: [] });
|
|
374
|
-
return decision; // 插件失败不许拖垮本轮
|
|
588
|
+
return attachShared(decision); // 插件失败不许拖垮本轮
|
|
589
|
+
} finally {
|
|
590
|
+
try { recallPhase.end(); } catch { /* timing only */ }
|
|
375
591
|
}
|
|
376
592
|
}, { prepend: false });
|
|
377
593
|
|
|
@@ -381,7 +597,7 @@ export function apply(ctx, config = {}) {
|
|
|
381
597
|
const st = stateFor(session?.id ?? 'unknown');
|
|
382
598
|
if (event?.type === 'user/message' && isRealUserMessage(event?.data)) {
|
|
383
599
|
const text = firstTextOf(event?.data);
|
|
384
|
-
//
|
|
600
|
+
// 评审第三轮 mustFix #1:注入的【开机卡】/【自动想起】也是 user/message,
|
|
385
601
|
// 旧代码只看 type 就把它们当成 firstUserText,导致之后每轮 query 都混进开机卡。
|
|
386
602
|
//
|
|
387
603
|
// v4 / ADV1:外部插件仍可能用 kind:'user' 把一段上下文插到真实提问**之前**。
|
|
@@ -12,12 +12,12 @@
|
|
|
12
12
|
*
|
|
13
13
|
* 开机卡插在**第一条真实 user 消息之后**,不再 unshift 到最前:宿主 TurnNavigator
|
|
14
14
|
* 取该轮第一个可见 `kind:'user'` 节点做 tooltip 的 prompt(client.js:8373-8384),
|
|
15
|
-
* boot 在最前会让第 1
|
|
15
|
+
* boot 在最前会让第 1 轮显示【开机卡】而不是用户提问(评审第三轮 mustFix #2)。
|
|
16
16
|
*
|
|
17
17
|
* auto 只负责把自己 append 到最后;不再把消息插到 boot 后面(那会得到
|
|
18
|
-
* `[boot, auto, user, runtime]
|
|
18
|
+
* `[boot, auto, user, runtime]`,评审 S2 的顺序问题)。
|
|
19
19
|
*
|
|
20
|
-
* v4 /
|
|
20
|
+
* v4 / 评审第四轮 M1(2026-11-04):注入消息的 source.kind 从 'user' 改为
|
|
21
21
|
* 'session-reference',形状固定为
|
|
22
22
|
*
|
|
23
23
|
* { kind:'session-reference', form:'recall', version:1, references:[] }
|
|
@@ -35,9 +35,9 @@
|
|
|
35
35
|
*
|
|
36
36
|
* 识别契约:
|
|
37
37
|
* - 本插件 / identity-router 的注入靠 kind 直接识别(不再依赖正文标记);
|
|
38
|
-
* -
|
|
38
|
+
* - 正文标记(【自动想起】/【开机卡】)继续保留,用于识别历史消息与外部记忆注入
|
|
39
39
|
* 风格的注入,also keeps firstUserText from eating a legacy card;
|
|
40
|
-
* - **外部插件**仍可能用 kind:'user'
|
|
40
|
+
* - **外部插件**仍可能用 kind:'user' 把文本插到真实提问前(评审 ADV1)。识别不了
|
|
41
41
|
* 任意纯文本,所以 firstUserText 的取值在 index.js 里按「第 1 轮最后一条真人
|
|
42
42
|
* 消息」锁定,isRealUserMessage 只负责排掉已知注入形态。
|
|
43
43
|
*/
|
|
@@ -47,6 +47,8 @@
|
|
|
47
47
|
* 注入只允许白名单内的 kind;坏 kind 会在 append 时被本插件拒绝,避免日志被
|
|
48
48
|
* 毒化(append 接受任意非空字符串,但 adoptSessionEvent 会抛 invalid source)。
|
|
49
49
|
*/
|
|
50
|
+
import { isExternalMemoryKind } from '../adapters/constants.js';
|
|
51
|
+
|
|
50
52
|
export const HOST_SOURCE_KINDS = Object.freeze([
|
|
51
53
|
'user', 'model', 'tool', 'system-prompt', 'model-selection', 'runtime-context',
|
|
52
54
|
'working-directory', 'user-approval', 'ptc-mode', 'tool-registry', 'user-rpc',
|
|
@@ -153,7 +155,7 @@ export function isAutoMessage(message) {
|
|
|
153
155
|
* 且不是本插件 / identity-router / 已知外部注入形态。
|
|
154
156
|
*
|
|
155
157
|
* v4 起注入用 kind:'session-reference',kind 本身就能挡掉;这里再按正文特征
|
|
156
|
-
*
|
|
158
|
+
* 排一层(评审 ADV1):带 `source.form` / `source.sections` 的、首行是已知标记的,
|
|
157
159
|
* 都不算真人消息。无法凭正文区分「任意外部插件写的纯文本 kind:user」——那类消息
|
|
158
160
|
* 由 firstUserText 的「第 1 轮最后一条」规则处理(见 index.js)。
|
|
159
161
|
* @param {object} message
|
|
@@ -181,7 +183,7 @@ export function textsOf(message) {
|
|
|
181
183
|
}
|
|
182
184
|
|
|
183
185
|
/**
|
|
184
|
-
* 本轮已经注入的文本:identity 开机卡 + 旧 plugin:* /
|
|
186
|
+
* 本轮已经注入的文本:identity 开机卡 + 旧 plugin:* / 外部记忆注入消息。
|
|
185
187
|
* 给 chooseRecall 做文本级去重;kind 合法化成 session-reference 后不能只靠 kind 判断。
|
|
186
188
|
*/
|
|
187
189
|
export function injectedTexts(messages) {
|
|
@@ -192,7 +194,7 @@ export function injectedTexts(messages) {
|
|
|
192
194
|
continue;
|
|
193
195
|
}
|
|
194
196
|
const kind = sourceKindOf(message);
|
|
195
|
-
if (!kind.startsWith('plugin:') && !kind
|
|
197
|
+
if (!kind.startsWith('plugin:') && !isExternalMemoryKind(kind)) continue;
|
|
196
198
|
out.push(...textsOf(message));
|
|
197
199
|
}
|
|
198
200
|
return out;
|
package/src/recall/privacy.mjs
CHANGED
|
@@ -13,7 +13,7 @@
|
|
|
13
13
|
* 命中口径 = 隐私门的 verdict==='local_only' **或** 核心兜底正则命中(取并集,宁可多拦)。
|
|
14
14
|
* 兜底分两类:
|
|
15
15
|
* - PII:手机号 / 邮箱 / 身份证(1.6.7 起就有);
|
|
16
|
-
* - 密钥/令牌(1.6.8
|
|
16
|
+
* - 密钥/令牌(1.6.8 评审 S3 补):sk-*、Bearer …、*_API_KEY=*、token=/secret=/password=、
|
|
17
17
|
* 私钥头、AWS/GitHub/Slack 凭证前缀。活动板是跨会话共享的,
|
|
18
18
|
* 手机号只泄漏一个人的联系方式,密钥泄漏的是账号本身。
|
|
19
19
|
*
|