@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
|
@@ -0,0 +1,235 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* engram 适配器(M2-E 解耦样板)
|
|
3
|
+
*
|
|
4
|
+
* 核心代码只通过 src/adapters/registry.js 使用本模块;本目录是唯一允许出现
|
|
5
|
+
* 第三方插件(engram)包名/工具名/动作名/注入标记字面量的地方。
|
|
6
|
+
*
|
|
7
|
+
* 统一接口:{ id, probe, call, health, mode }
|
|
8
|
+
* - probe(): 探测宿主是否提供 engram 能力(按宿主传入的工具清单/显式 mode),返回 { ok, mode, evidence }
|
|
9
|
+
* - call(): 经宿主 invoke 转发一次 engram 工具调用;adapter 缺席时返回降级结果,不抛错
|
|
10
|
+
* - health(): { id, mode, ok, degraded, detail },供 doctor/lint/registry 汇总
|
|
11
|
+
* - mode: 'active' | 'degraded' | 'absent'
|
|
12
|
+
*/
|
|
13
|
+
import { readFileSync } from 'node:fs'
|
|
14
|
+
|
|
15
|
+
export const ID = 'engram'
|
|
16
|
+
export const PACKAGE_NAME = '@kenz1117/dsh-engram'
|
|
17
|
+
export const ACTION_PREFIX = 'engram.'
|
|
18
|
+
export const TOOL_PREFIX = 'engram_'
|
|
19
|
+
export const PROFILE_EDIT_ACTION = 'engram.profile.edit'
|
|
20
|
+
export const FORGET_ACTION = 'engram.forget'
|
|
21
|
+
export const SAVE_ACTION = 'engram.save'
|
|
22
|
+
export const FACTS_REPLACE_ACTION = 'engram.facts.replace'
|
|
23
|
+
export const CLASSIFY_PLUGIN_LABEL = 'plugin:dsh-engram'
|
|
24
|
+
export const EVIDENCE_PREFIX = 'engram:'
|
|
25
|
+
export const LEGACY_TOOL_NAMES = Object.freeze(['memory_save'])
|
|
26
|
+
export const INTERCEPTED_WRITE_TOOLS = Object.freeze([
|
|
27
|
+
'engram_save',
|
|
28
|
+
'engram_update',
|
|
29
|
+
'engram_facts',
|
|
30
|
+
'engram_forget',
|
|
31
|
+
'engram_profile_edit',
|
|
32
|
+
])
|
|
33
|
+
export const ACTION_NAMES = Object.freeze([
|
|
34
|
+
'engram.search',
|
|
35
|
+
'engram.save',
|
|
36
|
+
'engram.facts.replace',
|
|
37
|
+
'engram.forget',
|
|
38
|
+
'engram.profile.edit',
|
|
39
|
+
])
|
|
40
|
+
export const LEGACY_WRITE_TOOL_NAMES = Object.freeze(['memory_save'])
|
|
41
|
+
export const WRITE_TOOL_NAMES = Object.freeze(['memory_save', 'engram_save', 'engram_update', 'engram_facts', 'engram_forget', 'engram_profile_edit'])
|
|
42
|
+
export const ACTION_GROUPS = Object.freeze({
|
|
43
|
+
approval: ['engram.forget'],
|
|
44
|
+
write: ['engram.save', 'engram.facts.replace'],
|
|
45
|
+
profileEdit: ['engram.profile.edit'],
|
|
46
|
+
})
|
|
47
|
+
export const DEGRADE_TARGETS = Object.freeze({
|
|
48
|
+
'engram.search': 'live',
|
|
49
|
+
'engram.save': 'topics',
|
|
50
|
+
'engram.facts.replace': 'topics',
|
|
51
|
+
'engram.forget': 'topics',
|
|
52
|
+
'engram.profile.edit': 'topics',
|
|
53
|
+
})
|
|
54
|
+
export const SESSION_MARKER_PATTERNS = Object.freeze([
|
|
55
|
+
/<engram_profile\b/i,
|
|
56
|
+
/<engram_memory_context\b/i,
|
|
57
|
+
])
|
|
58
|
+
|
|
59
|
+
const DATA_FILES = Object.freeze({
|
|
60
|
+
'routing-rules': { url: new URL('./data/routing-rules.json', import.meta.url), sourcePath: 'src/adapters/engram/data/routing-rules.json' },
|
|
61
|
+
aliases: { url: new URL('./data/aliases.json', import.meta.url), sourcePath: 'src/adapters/engram/data/aliases.json' },
|
|
62
|
+
policy: { url: new URL('./data/policy.json', import.meta.url), sourcePath: 'src/adapters/engram/data/policy.json' },
|
|
63
|
+
})
|
|
64
|
+
const dataCache = new Map()
|
|
65
|
+
|
|
66
|
+
/**
|
|
67
|
+
* 读取 adapter 数据文件;缺失/坏 JSON 一律 fail-safe:返回 ok:false + 错误记录,
|
|
68
|
+
* 由调用方(registry/核心 loader)降级继续跑,不抛错。
|
|
69
|
+
*/
|
|
70
|
+
export function loadData(kind) {
|
|
71
|
+
if (dataCache.has(kind)) return dataCache.get(kind)
|
|
72
|
+
const entry = DATA_FILES[kind]
|
|
73
|
+
let result
|
|
74
|
+
if (!entry) {
|
|
75
|
+
result = { ok: false, kind, data: null, text: '', sourcePath: '', error: { code: 'E_ADAPTER_DATA_UNKNOWN', message: `未知 adapter 数据:${String(kind)}` } }
|
|
76
|
+
} else {
|
|
77
|
+
try {
|
|
78
|
+
const text = readFileSync(entry.url, 'utf8')
|
|
79
|
+
result = { ok: true, kind, data: JSON.parse(text), text, sourcePath: entry.sourcePath, error: null }
|
|
80
|
+
} catch (cause) {
|
|
81
|
+
result = {
|
|
82
|
+
ok: false,
|
|
83
|
+
kind,
|
|
84
|
+
data: null,
|
|
85
|
+
text: '',
|
|
86
|
+
sourcePath: entry.sourcePath,
|
|
87
|
+
error: {
|
|
88
|
+
code: cause && cause.code === 'ENOENT' ? 'E_ADAPTER_DATA_MISSING' : 'E_ADAPTER_DATA_INVALID',
|
|
89
|
+
message: `${kind} 数据不可用:${cause && cause.message ? cause.message : String(cause)}`,
|
|
90
|
+
},
|
|
91
|
+
}
|
|
92
|
+
}
|
|
93
|
+
}
|
|
94
|
+
dataCache.set(kind, result)
|
|
95
|
+
return result
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
export function resetDataCacheForTests() {
|
|
99
|
+
dataCache.clear()
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
export function isEngramToolName(name) {
|
|
103
|
+
if (typeof name !== 'string') return false
|
|
104
|
+
const trimmed = name.trim()
|
|
105
|
+
if (!trimmed) return false
|
|
106
|
+
return trimmed.startsWith(TOOL_PREFIX) || LEGACY_TOOL_NAMES.includes(trimmed)
|
|
107
|
+
}
|
|
108
|
+
|
|
109
|
+
export function isEngramActionName(name) {
|
|
110
|
+
if (typeof name !== 'string') return false
|
|
111
|
+
const trimmed = name.trim()
|
|
112
|
+
return trimmed.startsWith(ACTION_PREFIX) || trimmed.startsWith(TOOL_PREFIX)
|
|
113
|
+
}
|
|
114
|
+
|
|
115
|
+
function normalizeMode(value) {
|
|
116
|
+
return value === 'active' || value === 'degraded' || value === 'absent' ? value : null
|
|
117
|
+
}
|
|
118
|
+
|
|
119
|
+
/**
|
|
120
|
+
* @param {object} [options]
|
|
121
|
+
* @param {() => string[]} [options.tools] 宿主可用工具名列表(探测依据,不 import 第三方)
|
|
122
|
+
* @param {string} [options.mode] 显式覆盖 mode(测试/配置用)
|
|
123
|
+
* @param {(tool: string, args: object) => any} [options.invoke] 宿主工具调用通道
|
|
124
|
+
* @param {() => any} [options.afterProbe]
|
|
125
|
+
*/
|
|
126
|
+
export function createEngramAdapter(options = {}) {
|
|
127
|
+
const toolsProvider = typeof options.tools === 'function' ? options.tools : () => (Array.isArray(options.tools) ? options.tools : [])
|
|
128
|
+
const invoke = typeof options.invoke === 'function' ? options.invoke : null
|
|
129
|
+
const forcedMode = normalizeMode(options.mode)
|
|
130
|
+
let last = null
|
|
131
|
+
|
|
132
|
+
function probe() {
|
|
133
|
+
if (forcedMode) {
|
|
134
|
+
last = { ok: forcedMode !== 'absent', mode: forcedMode, evidence: ['explicit-mode'], at: null }
|
|
135
|
+
return last
|
|
136
|
+
}
|
|
137
|
+
let tools = []
|
|
138
|
+
try {
|
|
139
|
+
const value = toolsProvider()
|
|
140
|
+
tools = Array.isArray(value) ? value : []
|
|
141
|
+
} catch {
|
|
142
|
+
tools = []
|
|
143
|
+
}
|
|
144
|
+
const hits = tools.filter(isEngramToolName)
|
|
145
|
+
const mode = hits.length > 0 ? 'active' : 'absent'
|
|
146
|
+
last = { ok: mode === 'active', mode, evidence: hits.slice(0, 8), at: null }
|
|
147
|
+
return last
|
|
148
|
+
}
|
|
149
|
+
|
|
150
|
+
function health() {
|
|
151
|
+
const state = last || probe()
|
|
152
|
+
return {
|
|
153
|
+
id: ID,
|
|
154
|
+
mode: state.mode,
|
|
155
|
+
ok: state.mode !== 'absent',
|
|
156
|
+
degraded: state.mode === 'degraded' || state.mode === 'absent',
|
|
157
|
+
detail: state.mode === 'absent' ? '未探测到 engram 工具,降级为文件记忆(topics/journal)' : state.mode === 'degraded' ? 'engram 部分能力不可用' : 'engram 可用',
|
|
158
|
+
evidence: state.evidence.slice(),
|
|
159
|
+
}
|
|
160
|
+
}
|
|
161
|
+
|
|
162
|
+
/** 动作级降级计划:插件缺席时给出降级目标与 degraded 标记,不抛错。 */
|
|
163
|
+
function plan(actionName) {
|
|
164
|
+
if (!isEngramActionName(actionName)) {
|
|
165
|
+
return { ok: false, degraded: false, code: 'E_ADAPTER_ACTION', action: actionName }
|
|
166
|
+
}
|
|
167
|
+
const state = last || probe()
|
|
168
|
+
if (state.mode === 'active') {
|
|
169
|
+
return { ok: true, degraded: false, mode: state.mode, target: ID, action: actionName }
|
|
170
|
+
}
|
|
171
|
+
return {
|
|
172
|
+
ok: false,
|
|
173
|
+
degraded: true,
|
|
174
|
+
mode: state.mode,
|
|
175
|
+
target: DEGRADE_TARGETS[actionName] || 'topics',
|
|
176
|
+
action: 'topics.write',
|
|
177
|
+
fallback: 'file-memory',
|
|
178
|
+
message: '外部记忆插件不可用:该动作降级到文件记忆(topics/journal),不阻塞',
|
|
179
|
+
}
|
|
180
|
+
}
|
|
181
|
+
|
|
182
|
+
function call(request = {}) {
|
|
183
|
+
const tool = typeof request.tool === 'string' ? request.tool : ''
|
|
184
|
+
if (!isEngramToolName(tool)) {
|
|
185
|
+
return { ok: false, degraded: false, code: 'E_ADAPTER_TOOL', message: `不是 engram 工具:${tool}` }
|
|
186
|
+
}
|
|
187
|
+
const state = last || probe()
|
|
188
|
+
if (state.mode === 'absent') {
|
|
189
|
+
return {
|
|
190
|
+
ok: false,
|
|
191
|
+
degraded: true,
|
|
192
|
+
mode: 'absent',
|
|
193
|
+
code: 'E_ADAPTER_ABSENT',
|
|
194
|
+
tool,
|
|
195
|
+
fallbackTarget: 'topics',
|
|
196
|
+
message: 'engram 不可用:降级到文件记忆(topics/journal),不阻塞',
|
|
197
|
+
}
|
|
198
|
+
}
|
|
199
|
+
if (typeof invoke !== 'function') {
|
|
200
|
+
return { ok: false, degraded: true, mode: state.mode, code: 'E_ADAPTER_NO_INVOKE', tool, message: '宿主未提供工具调用通道' }
|
|
201
|
+
}
|
|
202
|
+
try {
|
|
203
|
+
return { ok: true, degraded: state.mode !== 'active', mode: state.mode, tool, result: invoke(tool, request.args || {}) }
|
|
204
|
+
} catch (cause) {
|
|
205
|
+
return { ok: false, degraded: true, mode: state.mode, code: 'E_ADAPTER_CALL', tool, message: String(cause && cause.message ? cause.message : cause) }
|
|
206
|
+
}
|
|
207
|
+
}
|
|
208
|
+
|
|
209
|
+
return {
|
|
210
|
+
id: ID,
|
|
211
|
+
packageName: PACKAGE_NAME,
|
|
212
|
+
get mode() {
|
|
213
|
+
return (last || probe()).mode
|
|
214
|
+
},
|
|
215
|
+
probe,
|
|
216
|
+
call,
|
|
217
|
+
plan,
|
|
218
|
+
health,
|
|
219
|
+
loadData,
|
|
220
|
+
constants: Object.freeze({
|
|
221
|
+
ACTION_PREFIX,
|
|
222
|
+
TOOL_PREFIX,
|
|
223
|
+
LEGACY_TOOL_NAMES,
|
|
224
|
+
LEGACY_WRITE_TOOL_NAMES,
|
|
225
|
+
WRITE_TOOL_NAMES,
|
|
226
|
+
INTERCEPTED_WRITE_TOOLS,
|
|
227
|
+
ACTION_NAMES,
|
|
228
|
+
ACTION_GROUPS,
|
|
229
|
+
DEGRADE_TARGETS,
|
|
230
|
+
CLASSIFY_PLUGIN_LABEL,
|
|
231
|
+
EVIDENCE_PREFIX,
|
|
232
|
+
SESSION_MARKER_PATTERNS,
|
|
233
|
+
}),
|
|
234
|
+
}
|
|
235
|
+
}
|
|
@@ -0,0 +1,315 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* 第三方插件适配器注册表(M2-E 解耦的唯一核心入口)
|
|
3
|
+
*
|
|
4
|
+
* 设计:核心代码不 import 任何第三方包、不出现第三方包名/工具名/动作名字面量;
|
|
5
|
+
* 所有第三方接触面(包名、工具名、动作名、规则/别名/策略片段、注入标记)都由
|
|
6
|
+
* adapter 提供,核心经本 registry 取用。适配器缺席/数据损坏时一律 fail-safe:
|
|
7
|
+
* 核心照常运行 + 显式 degraded 记录,不抛错、不阻塞。
|
|
8
|
+
*
|
|
9
|
+
* 统一接口(每个 adapter 必须提供):
|
|
10
|
+
* { id, probe, call, health, mode }
|
|
11
|
+
* 可选:loadData(kind) → { ok, data, error }
|
|
12
|
+
*
|
|
13
|
+
* 新增第三方插件 = 在 adapters/<id>/ 实现接口 + 在 createDefaultRegistry() 里注册,
|
|
14
|
+
* 并先登记到 scripts/lint-plugin-independence.mjs。
|
|
15
|
+
*/
|
|
16
|
+
import { createEngramAdapter } from './engram/index.js'
|
|
17
|
+
|
|
18
|
+
export const ADAPTER_REGISTRY_VERSION = 1
|
|
19
|
+
|
|
20
|
+
function safeCall(fn, fallback) {
|
|
21
|
+
try {
|
|
22
|
+
return typeof fn === 'function' ? fn() : fallback
|
|
23
|
+
} catch (cause) {
|
|
24
|
+
return fallback
|
|
25
|
+
}
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
/**
|
|
29
|
+
* 创建一个独立 registry(测试用;生产用默认 registry)。
|
|
30
|
+
* @param {Array<object>} adapters
|
|
31
|
+
*/
|
|
32
|
+
export function createAdapterRegistry(adapters = []) {
|
|
33
|
+
const entries = new Map()
|
|
34
|
+
const problems = []
|
|
35
|
+
|
|
36
|
+
const registry = {
|
|
37
|
+
register(adapter) {
|
|
38
|
+
if (!adapter || typeof adapter.id !== 'string' || !adapter.id.trim()) {
|
|
39
|
+
problems.push({ code: 'E_ADAPTER_INVALID', message: 'adapter 缺 id,忽略' })
|
|
40
|
+
return false
|
|
41
|
+
}
|
|
42
|
+
if (entries.has(adapter.id)) {
|
|
43
|
+
problems.push({ code: 'E_ADAPTER_DUPLICATE', message: `adapter id 重复:${adapter.id}` })
|
|
44
|
+
return false
|
|
45
|
+
}
|
|
46
|
+
entries.set(adapter.id, adapter)
|
|
47
|
+
return true
|
|
48
|
+
},
|
|
49
|
+
get(id) {
|
|
50
|
+
return entries.get(id) || null
|
|
51
|
+
},
|
|
52
|
+
list() {
|
|
53
|
+
return [...entries.values()]
|
|
54
|
+
},
|
|
55
|
+
ids() {
|
|
56
|
+
return [...entries.keys()]
|
|
57
|
+
},
|
|
58
|
+
/** 探测全部 adapter,返回 { modes, degraded, problems };单个 adapter 抛错不影响整体。 */
|
|
59
|
+
probeAll(options = {}) {
|
|
60
|
+
const modes = {}
|
|
61
|
+
const adapterProblems = []
|
|
62
|
+
for (const adapter of entries.values()) {
|
|
63
|
+
const result = safeCall(() => adapter.probe(options), { ok: false, mode: 'absent', evidence: [], error: { code: 'E_ADAPTER_PROBE' } })
|
|
64
|
+
modes[adapter.id] = result && result.mode ? result.mode : 'absent'
|
|
65
|
+
if (result && result.error) adapterProblems.push({ adapter: adapter.id, ...result.error })
|
|
66
|
+
}
|
|
67
|
+
return {
|
|
68
|
+
modes,
|
|
69
|
+
degraded: Object.values(modes).some((mode) => mode !== 'active'),
|
|
70
|
+
problems: [...adapterProblems],
|
|
71
|
+
}
|
|
72
|
+
},
|
|
73
|
+
/** 汇总 health;供 doctor/lint 输出。 */
|
|
74
|
+
healthAll(options = {}) {
|
|
75
|
+
const rows = []
|
|
76
|
+
for (const adapter of entries.values()) {
|
|
77
|
+
const health = safeCall(() => adapter.health(options), { id: adapter.id, mode: 'absent', degraded: true, detail: 'health() 异常' })
|
|
78
|
+
rows.push({ id: adapter.id, mode: health.mode || 'absent', ok: health.ok === true, degraded: health.degraded !== false, detail: health.detail || '' })
|
|
79
|
+
}
|
|
80
|
+
return { ok: rows.every((row) => row.ok), rows, problems: [...problems] }
|
|
81
|
+
},
|
|
82
|
+
/** 经适配器转发一次调用;adapter 缺席时返回降级结果。 */
|
|
83
|
+
call(id, request) {
|
|
84
|
+
const adapter = entries.get(id)
|
|
85
|
+
if (!adapter) {
|
|
86
|
+
return { ok: false, degraded: true, mode: 'absent', code: 'E_ADAPTER_UNKNOWN', message: `未注册的 adapter:${String(id)}` }
|
|
87
|
+
}
|
|
88
|
+
return safeCall(() => adapter.call(request), { ok: false, degraded: true, mode: 'absent', code: 'E_ADAPTER_CALL', message: 'adapter.call 异常' })
|
|
89
|
+
},
|
|
90
|
+
/** 合并各 adapter 的某类数据;失败仅记录 + degraded,不抛错。 */
|
|
91
|
+
loadData(kind) {
|
|
92
|
+
const items = []
|
|
93
|
+
const errors = []
|
|
94
|
+
const sources = []
|
|
95
|
+
for (const adapter of entries.values()) {
|
|
96
|
+
if (typeof adapter.loadData !== 'function') continue
|
|
97
|
+
const result = safeCall(() => adapter.loadData(kind), { ok: false, error: { code: 'E_ADAPTER_DATA', message: `adapter ${adapter.id} loadData 异常` } })
|
|
98
|
+
if (!result || !result.ok) {
|
|
99
|
+
errors.push({ adapter: adapter.id, kind, code: (result && result.error && result.error.code) || 'E_ADAPTER_DATA', message: (result && result.error && result.error.message) || 'adapter 数据不可用' })
|
|
100
|
+
continue
|
|
101
|
+
}
|
|
102
|
+
items.push({ adapter: adapter.id, data: result.data, text: result.text || '', sourcePath: result.sourcePath || '' })
|
|
103
|
+
sources.push(adapter.id)
|
|
104
|
+
}
|
|
105
|
+
return { ok: errors.length === 0, items, sources, errors, degraded: errors.length > 0 }
|
|
106
|
+
},
|
|
107
|
+
problems,
|
|
108
|
+
}
|
|
109
|
+
|
|
110
|
+
for (const adapter of adapters) registry.register(adapter)
|
|
111
|
+
return registry
|
|
112
|
+
}
|
|
113
|
+
|
|
114
|
+
/** 生产默认 registry:engram 是当前唯一样例;其它第三方插件只登记、不建 adapter。 */
|
|
115
|
+
export function createDefaultRegistry() {
|
|
116
|
+
return createAdapterRegistry([createEngramAdapter()])
|
|
117
|
+
}
|
|
118
|
+
|
|
119
|
+
export const defaultRegistry = createDefaultRegistry()
|
|
120
|
+
|
|
121
|
+
// ── 核心取用面(全部是中性命名,不含第三方字面量) ──────────────────────
|
|
122
|
+
|
|
123
|
+
export function adapterHealth() {
|
|
124
|
+
return defaultRegistry.healthAll()
|
|
125
|
+
}
|
|
126
|
+
|
|
127
|
+
export function adapterModes() {
|
|
128
|
+
return defaultRegistry.probeAll().modes
|
|
129
|
+
}
|
|
130
|
+
|
|
131
|
+
export function adapterEntries() {
|
|
132
|
+
return defaultRegistry.list().map((adapter) => ({ id: adapter.id, mode: safeCall(() => adapter.mode, 'absent') }))
|
|
133
|
+
}
|
|
134
|
+
|
|
135
|
+
export function adapterCall(id, request) {
|
|
136
|
+
return defaultRegistry.call(id, request)
|
|
137
|
+
}
|
|
138
|
+
|
|
139
|
+
/** 合并 adapter 提供的路由规则(保留 adapter 侧 _order 以复原原表顺序)。 */
|
|
140
|
+
export function loadAdapterRoutingRules() {
|
|
141
|
+
const loaded = defaultRegistry.loadData('routing-rules')
|
|
142
|
+
const rules = []
|
|
143
|
+
const targets = []
|
|
144
|
+
const errors = []
|
|
145
|
+
for (const item of loaded.items) {
|
|
146
|
+
const data = item.data || {}
|
|
147
|
+
if (Array.isArray(data.rules)) rules.push(...data.rules)
|
|
148
|
+
if (Array.isArray(data.targets)) targets.push(...data.targets)
|
|
149
|
+
}
|
|
150
|
+
errors.push(...loaded.errors)
|
|
151
|
+
return { ok: errors.length === 0, rules, targets, items: loaded.items, errors, degraded: errors.length > 0 }
|
|
152
|
+
}
|
|
153
|
+
|
|
154
|
+
/** 合并 adapter 提供的动作别名。 */
|
|
155
|
+
export function loadAdapterAliases() {
|
|
156
|
+
const loaded = defaultRegistry.loadData('aliases')
|
|
157
|
+
const actions = []
|
|
158
|
+
const errors = [...loaded.errors]
|
|
159
|
+
for (const item of loaded.items) {
|
|
160
|
+
const data = item.data || {}
|
|
161
|
+
if (Array.isArray(data.actions)) actions.push(...data.actions)
|
|
162
|
+
}
|
|
163
|
+
return { ok: errors.length === 0, actions, errors, degraded: errors.length > 0 }
|
|
164
|
+
}
|
|
165
|
+
|
|
166
|
+
/** 把 adapter 的策略片段(forbiddenActions / allowPrefixes)并入一份已校验的 profile。 */
|
|
167
|
+
export function applyAdapterPolicy(policy) {
|
|
168
|
+
const loaded = defaultRegistry.loadData('policy')
|
|
169
|
+
if (!loaded.ok || typeof policy !== 'object' || policy === null) {
|
|
170
|
+
return { policy, degraded: !loaded.ok, errors: loaded.errors }
|
|
171
|
+
}
|
|
172
|
+
const next = JSON.parse(JSON.stringify(policy))
|
|
173
|
+
const forbidden = new Set(next.rest && Array.isArray(next.rest.forbiddenActions) ? next.rest.forbiddenActions : [])
|
|
174
|
+
const allowed = new Set(next.writeScope && Array.isArray(next.writeScope.allowPrefixes) ? next.writeScope.allowPrefixes : [])
|
|
175
|
+
for (const item of loaded.items) {
|
|
176
|
+
const data = item.data || {}
|
|
177
|
+
for (const action of Array.isArray(data.forbiddenActions) ? data.forbiddenActions : []) forbidden.add(action)
|
|
178
|
+
for (const prefix of Array.isArray(data.allowPrefixes) ? data.allowPrefixes : []) allowed.add(prefix)
|
|
179
|
+
}
|
|
180
|
+
if (next.rest) next.rest.forbiddenActions = [...forbidden]
|
|
181
|
+
if (next.writeScope) next.writeScope.allowPrefixes = [...allowed]
|
|
182
|
+
return { policy: next, degraded: false, errors: [] }
|
|
183
|
+
}
|
|
184
|
+
|
|
185
|
+
export function optionalPeerNames() {
|
|
186
|
+
return defaultRegistry.list().map((adapter) => adapter.packageName).filter((name) => typeof name === 'string' && name.trim())
|
|
187
|
+
}
|
|
188
|
+
|
|
189
|
+
export function interceptedToolNames() {
|
|
190
|
+
const names = []
|
|
191
|
+
for (const adapter of defaultRegistry.list()) {
|
|
192
|
+
const constants = adapter.constants || {}
|
|
193
|
+
for (const name of constants.INTERCEPTED_WRITE_TOOLS || []) names.push(name)
|
|
194
|
+
for (const name of constants.LEGACY_TOOL_NAMES || []) names.push(name)
|
|
195
|
+
}
|
|
196
|
+
return [...new Set(names)]
|
|
197
|
+
}
|
|
198
|
+
|
|
199
|
+
export function classificationPluginLabels() {
|
|
200
|
+
return defaultRegistry.list().map((adapter) => adapter.constants && adapter.constants.CLASSIFY_PLUGIN_LABEL).filter(Boolean)
|
|
201
|
+
}
|
|
202
|
+
|
|
203
|
+
export function injectedMarkerPatterns() {
|
|
204
|
+
const patterns = []
|
|
205
|
+
for (const adapter of defaultRegistry.list()) {
|
|
206
|
+
for (const pattern of (adapter.constants && adapter.constants.SESSION_MARKER_PATTERNS) || []) patterns.push(pattern)
|
|
207
|
+
}
|
|
208
|
+
return patterns
|
|
209
|
+
}
|
|
210
|
+
|
|
211
|
+
export function pluginEvidencePrefixes() {
|
|
212
|
+
return defaultRegistry.list().map((adapter) => adapter.constants && adapter.constants.EVIDENCE_PREFIX).filter((prefix) => typeof prefix === 'string' && prefix)
|
|
213
|
+
}
|
|
214
|
+
|
|
215
|
+
// ── 语义判定(核心不关心是哪家插件) ────────────────────────────────
|
|
216
|
+
|
|
217
|
+
function externalActions() {
|
|
218
|
+
return defaultRegistry.list().flatMap((adapter) => (adapter.constants && adapter.constants.ACTION_NAMES) || [])
|
|
219
|
+
}
|
|
220
|
+
|
|
221
|
+
export function isExternalMemoryAction(action) {
|
|
222
|
+
if (typeof action !== 'string') return false
|
|
223
|
+
const trimmed = action.trim()
|
|
224
|
+
if (!trimmed) return false
|
|
225
|
+
return externalActions().some((name) => trimmed === name) || defaultRegistry.list().some((adapter) => {
|
|
226
|
+
const prefix = adapter.constants && adapter.constants.ACTION_PREFIX
|
|
227
|
+
return typeof prefix === 'string' && prefix && trimmed.startsWith(prefix)
|
|
228
|
+
})
|
|
229
|
+
}
|
|
230
|
+
|
|
231
|
+
export function isExternalMemoryTarget(target) {
|
|
232
|
+
if (typeof target !== 'string') return false
|
|
233
|
+
const trimmed = target.trim()
|
|
234
|
+
return defaultRegistry.list().some((adapter) => trimmed === adapter.id)
|
|
235
|
+
}
|
|
236
|
+
|
|
237
|
+
export function isExternalMemoryTool(toolName) {
|
|
238
|
+
if (typeof toolName !== 'string') return false
|
|
239
|
+
const trimmed = toolName.trim()
|
|
240
|
+
return defaultRegistry.list().some((adapter) => {
|
|
241
|
+
const constants = adapter.constants || {}
|
|
242
|
+
if ((constants.LEGACY_TOOL_NAMES || []).includes(trimmed)) return true
|
|
243
|
+
const prefix = constants.TOOL_PREFIX
|
|
244
|
+
return typeof prefix === 'string' && prefix && trimmed.startsWith(prefix)
|
|
245
|
+
})
|
|
246
|
+
}
|
|
247
|
+
|
|
248
|
+
export function isExternalMemoryKind(kind) {
|
|
249
|
+
if (typeof kind !== 'string') return false
|
|
250
|
+
return defaultRegistry.list().some((adapter) => kind.includes(adapter.id))
|
|
251
|
+
}
|
|
252
|
+
|
|
253
|
+
/** 按语义分组取第三方动作名(approval/write/profileEdit 等,由 adapter 声明)。 */
|
|
254
|
+
export function externalActionGroups(group) {
|
|
255
|
+
const out = []
|
|
256
|
+
for (const adapter of defaultRegistry.list()) {
|
|
257
|
+
const groups = adapter.constants && adapter.constants.ACTION_GROUPS
|
|
258
|
+
if (groups && Array.isArray(groups[group])) out.push(...groups[group])
|
|
259
|
+
}
|
|
260
|
+
return [...new Set(out)]
|
|
261
|
+
}
|
|
262
|
+
|
|
263
|
+
export function planExternalAction(action) {
|
|
264
|
+
for (const adapter of defaultRegistry.list()) {
|
|
265
|
+
if (typeof adapter.plan !== 'function') continue
|
|
266
|
+
const result = safeCall(() => adapter.plan(action), { ok: false, degraded: true, code: 'E_ADAPTER_PLAN' })
|
|
267
|
+
if (result && result.code !== 'E_ADAPTER_ACTION') return { ...result, adapter: adapter.id }
|
|
268
|
+
}
|
|
269
|
+
return { ok: false, degraded: false, code: 'E_ADAPTER_UNKNOWN_ACTION', action }
|
|
270
|
+
}
|
|
271
|
+
|
|
272
|
+
export function externalProfileEditAction() {
|
|
273
|
+
return externalActionGroups('profileEdit')[0] || null
|
|
274
|
+
}
|
|
275
|
+
|
|
276
|
+
export function externalLegacyToolNames() {
|
|
277
|
+
const out = []
|
|
278
|
+
for (const adapter of defaultRegistry.list()) {
|
|
279
|
+
const constants = adapter.constants || {}
|
|
280
|
+
out.push(...(constants.LEGACY_TOOL_NAMES || []))
|
|
281
|
+
}
|
|
282
|
+
return [...new Set(out)]
|
|
283
|
+
}
|
|
284
|
+
|
|
285
|
+
export function externalWriteToolNames() {
|
|
286
|
+
const out = []
|
|
287
|
+
for (const adapter of defaultRegistry.list()) {
|
|
288
|
+
const constants = adapter.constants || {}
|
|
289
|
+
out.push(...(constants.WRITE_TOOL_NAMES || []))
|
|
290
|
+
}
|
|
291
|
+
return [...new Set(out)]
|
|
292
|
+
}
|
|
293
|
+
|
|
294
|
+
export function externalLegacyWriteToolNames() {
|
|
295
|
+
const out = []
|
|
296
|
+
for (const adapter of defaultRegistry.list()) {
|
|
297
|
+
const constants = adapter.constants || {}
|
|
298
|
+
out.push(...(constants.LEGACY_WRITE_TOOL_NAMES || []))
|
|
299
|
+
}
|
|
300
|
+
return [...new Set(out)]
|
|
301
|
+
}
|
|
302
|
+
|
|
303
|
+
export function isExternalMemoryProfileEdit(action) {
|
|
304
|
+
return defaultRegistry.list().some((adapter) => {
|
|
305
|
+
const constants = adapter.constants || {}
|
|
306
|
+
return (constants.ACTION_NAMES || []).includes(action) && action.endsWith('.profile.edit')
|
|
307
|
+
})
|
|
308
|
+
}
|
|
309
|
+
|
|
310
|
+
export function isExternalMemoryDeleteAction(action) {
|
|
311
|
+
return defaultRegistry.list().some((adapter) => {
|
|
312
|
+
const constants = adapter.constants || {}
|
|
313
|
+
return (constants.ACTION_NAMES || []).includes(action) && action.endsWith('.forget')
|
|
314
|
+
})
|
|
315
|
+
}
|