@jjh32456/dsh-longlive 1.6.7 → 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 +70 -1508
- package/LICENSE +1 -1
- package/QUICKSTART.md +34 -13
- package/README.en.md +18 -7
- package/README.md +34 -9
- package/SPEC.md +5 -5
- package/cordis.patch.yml +23 -9
- package/docs/ANSWER-GUARD.md +92 -0
- package/docs/ARCHITECTURE.md +4 -3
- package/docs/AUTO-RECALL-WIRING.md +1 -1
- package/docs/AUTO-RECALL.md +15 -9
- package/docs/CONFIG.md +168 -35
- package/docs/ERRORS.md +17 -5
- package/docs/FAQ.md +7 -7
- package/docs/FEATURES.md +96 -28
- 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 +4 -2
- package/docs/RESIDENT-WATCHER.md +1 -1
- package/docs/SESSION-WATCH.md +6 -6
- package/docs/TROUBLESHOOTING.md +4 -4
- package/package.json +47 -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 +760 -0
- package/src/activity/model.js +351 -0
- package/src/activity/store.js +644 -0
- 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 +171 -47
- package/src/deps.js +6 -4
- package/src/features.js +182 -6
- package/src/heartbeat.js +156 -23
- package/src/identity/handover.js +52 -1
- 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/live.js +22 -2
- 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 +105 -16
- 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/board.mjs +95 -15
- package/src/router/service/config.mjs +5 -5
- package/src/router/service/context.mjs +1 -1
- package/src/router/service/hint.mjs +14 -0
- package/src/router/service/queue.mjs +1 -1
- package/src/router/service/service.mjs +27 -11
- 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/auto-recall.test.mjs +0 -474
- package/test/auto-truncate.test.mjs +0 -931
- package/test/autobind.test.mjs +0 -385
- 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 -204
- 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 -568
- package/test/filter-shadow.test.mjs +0 -170
- 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 -1020
- 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 -182
- 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 -1044
- 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 -299
- package/test/run.mjs +0 -2469
- package/test/safety-defaults.test.mjs +0 -179
- package/test/seeded-load.test.mjs +0 -436
- package/test/session-original.test.mjs +0 -338
- 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
|
@@ -0,0 +1,425 @@
|
|
|
1
|
+
// src/role/index.js — 角色内核装配(v2.8 §16.3;1.8.0 默认 ON + 独立 kill switch)
|
|
2
|
+
//
|
|
3
|
+
// 1.8.0 角色内核 = attention queue + channel adapter + relationship graph +
|
|
4
|
+
// personality projection + 三接口(src/role/interfaces.js)。
|
|
5
|
+
//
|
|
6
|
+
// 激活纪律(任务「功能没跑起来就等于没实现」):
|
|
7
|
+
// - `role.enabled` 由 config schema 默认 **true**(1.8.0 默认 ON);显式
|
|
8
|
+
// `role.enabled === false` 是**独立 kill switch**:不注册 hook、不建目录、零 IO。
|
|
9
|
+
// - 开启后按子开关注册,且每个组件都有**运行期真实调用**(不是只有测试直接调 readers):
|
|
10
|
+
// attention.enabled -> session/event 生产者:真事件 → attention.jsonl 行(P0/P1/P2)
|
|
11
|
+
// relationships.enabled-> 同一 hook 在 user/message 上真实做 tier/recall-weight 查询
|
|
12
|
+
// channels.enabled -> 同一 hook touch 宿主持久渠道(lastSeenAt 落盘)+ 注册表
|
|
13
|
+
// personality.enabled -> 首次生成快照 + systemPrompt.section 语气投影(每次取文真实读快照)
|
|
14
|
+
// - registerRole 返回 `runtime` 计数器 + `probe()`:给 scripts/verify-activation.mjs 做
|
|
15
|
+
// 「开关 on / 真实调用 > 0 / 文件已落盘 / 注入 section 非空」的激活证据,而非单测自证。
|
|
16
|
+
// - 所有 hook 内部 try/catch:角色层故障不得拖垮一个 turn。
|
|
17
|
+
|
|
18
|
+
import { createAttentionQueue, attentionPriorityOfKind, orderOpsItems, ATTENTION_DEFAULTS } from './attention.js';
|
|
19
|
+
import { createChannelRegistry, normalizeInboundMessage, CHANNEL_DEFAULTS } from './channels.js';
|
|
20
|
+
import { generatePersonalitySnapshot, readPersonality, toneProjection, PERSONALITY_DEFAULTS } from './personality.js';
|
|
21
|
+
import { readRelationships, RELATIONSHIP_DEFAULTS } from './relationships.js';
|
|
22
|
+
import { createRoleInterfaces, INTERFACE_CONTRACTS } from './interfaces.js';
|
|
23
|
+
import { iso, nowMs, tryRolePaths } from './store.js';
|
|
24
|
+
import { readAttentionView, readChannelsView } from './readers.js';
|
|
25
|
+
import fs from 'node:fs';
|
|
26
|
+
|
|
27
|
+
export const ROLE_FEATURE_NAME = 'role';
|
|
28
|
+
export const ROLE_SCHEMA_VERSION = 1;
|
|
29
|
+
export const ROLE_DEFAULTS = Object.freeze({
|
|
30
|
+
// 1.8.0 默认 ON(任务):功能必须真的跑起来;kill switch = 显式 false。
|
|
31
|
+
enabled: true,
|
|
32
|
+
attention: Object.freeze({ enabled: true, ...ATTENTION_DEFAULTS }),
|
|
33
|
+
channels: Object.freeze({ enabled: true }),
|
|
34
|
+
relationships: Object.freeze({ enabled: true }),
|
|
35
|
+
personality: Object.freeze({ enabled: true, profilePaths: PERSONALITY_DEFAULTS.profilePaths }),
|
|
36
|
+
});
|
|
37
|
+
/** 启用角色内核时默认注册的宿主内 presence 渠道(没有 send,发送会降级)。 */
|
|
38
|
+
export const DEFAULT_HOST_CHANNELS = Object.freeze([
|
|
39
|
+
Object.freeze({ id: 'web', type: 'web', capabilities: ['presence'] }),
|
|
40
|
+
Object.freeze({ id: 'tool:host', type: 'tool', capabilities: ['presence'], meta: { channelKind: 'host-tool' } }),
|
|
41
|
+
]);
|
|
42
|
+
|
|
43
|
+
function bool(value, fallback) {
|
|
44
|
+
if (value === undefined) return fallback;
|
|
45
|
+
return value === true;
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
function clampInt(value, fallback, min, max) {
|
|
49
|
+
const number = Number(value);
|
|
50
|
+
if (!Number.isFinite(number)) return fallback;
|
|
51
|
+
return Math.min(max, Math.max(min, Math.floor(number)));
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
/** Normalize the `role` config block (does not touch normalizeFeatureConfig's shape). */
|
|
55
|
+
export function normalizeRoleConfig(config = {}) {
|
|
56
|
+
const value = config && typeof config === 'object' ? config : {};
|
|
57
|
+
const role = value.role && typeof value.role === 'object' ? value.role : {};
|
|
58
|
+
const attention = role.attention && typeof role.attention === 'object' ? role.attention : {};
|
|
59
|
+
const channels = role.channels && typeof role.channels === 'object' ? role.channels : {};
|
|
60
|
+
const relationships = role.relationships && typeof role.relationships === 'object' ? role.relationships : {};
|
|
61
|
+
const personality = role.personality && typeof role.personality === 'object' ? role.personality : {};
|
|
62
|
+
const profilePaths = Array.isArray(personality.profilePaths)
|
|
63
|
+
? personality.profilePaths.filter((item) => typeof item === 'string' && item.trim() !== '')
|
|
64
|
+
: [...ROLE_DEFAULTS.personality.profilePaths];
|
|
65
|
+
return {
|
|
66
|
+
// kill switch:只有显式 false 才关闭(默认 ON;字符串/缺省一律按开)。
|
|
67
|
+
enabled: role.enabled === false ? false : ROLE_DEFAULTS.enabled,
|
|
68
|
+
attention: {
|
|
69
|
+
enabled: bool(attention.enabled, ROLE_DEFAULTS.attention.enabled),
|
|
70
|
+
maxItems: clampInt(attention.maxItems, ATTENTION_DEFAULTS.maxItems, 1, 500),
|
|
71
|
+
agingMs: clampInt(attention.agingMs, ATTENTION_DEFAULTS.agingMs, 0, 7 * 24 * 60 * 60 * 1000),
|
|
72
|
+
demoteSteps: clampInt(attention.demoteSteps, ATTENTION_DEFAULTS.demoteSteps, 0, 2),
|
|
73
|
+
injectMaxItems: clampInt(attention.injectMaxItems, ATTENTION_DEFAULTS.injectMaxItems, 1, 20),
|
|
74
|
+
},
|
|
75
|
+
channels: { enabled: bool(channels.enabled, ROLE_DEFAULTS.channels.enabled) },
|
|
76
|
+
relationships: { enabled: bool(relationships.enabled, ROLE_DEFAULTS.relationships.enabled) },
|
|
77
|
+
personality: {
|
|
78
|
+
enabled: bool(personality.enabled, ROLE_DEFAULTS.personality.enabled),
|
|
79
|
+
profilePaths,
|
|
80
|
+
},
|
|
81
|
+
};
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
function safeMessage(event) {
|
|
85
|
+
const data = event?.data ?? {};
|
|
86
|
+
const message = data.message && typeof data.message === 'object' ? data.message : data;
|
|
87
|
+
return { data, message };
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
/**
|
|
91
|
+
* Turn one host `session/event` into an attention row. Producers (v2.8 ①):
|
|
92
|
+
* user/message -> P1 kind=user subject=turn:<turn>
|
|
93
|
+
* tool/result error -> P0 kind=error subject=tool:<name>
|
|
94
|
+
* turn/end failed -> P0 kind=ops subject=turn:<turn>:<reason>
|
|
95
|
+
* turn/end completed-> P2 kind=session subject=turn:<turn>:completed
|
|
96
|
+
* Returns the enqueue result or null when the event is not an attention event.
|
|
97
|
+
*/
|
|
98
|
+
export function ingestSessionEvent(queue, session, event, now = () => new Date()) {
|
|
99
|
+
const type = event?.type;
|
|
100
|
+
const turn = Number.isInteger(event?.data?.turn) ? event.data.turn : (Number.isInteger(event?.turn) ? event.turn : 0);
|
|
101
|
+
const at = iso(nowMs(now()));
|
|
102
|
+
const sessionId = typeof session?.id === 'string' ? session.id : (typeof session?.header?.id === 'string' ? session.header.id : '');
|
|
103
|
+
if (type === 'user/message') {
|
|
104
|
+
return queue.enqueue({ kind: 'user', priority: 'P1', subject: `turn:${turn}`, source: `session/user${sessionId ? `:${sessionId}` : ''}`, at });
|
|
105
|
+
}
|
|
106
|
+
if (type === 'tool/result') {
|
|
107
|
+
const { data, message } = safeMessage(event);
|
|
108
|
+
const failed = message?.isError === true || data.isError === true;
|
|
109
|
+
if (!failed) return null;
|
|
110
|
+
const toolName = typeof data.name === 'string' && data.name !== '' ? data.name : 'unknown';
|
|
111
|
+
return queue.enqueue({ kind: 'error', priority: 'P0', subject: `tool:${toolName}`, source: `session/tool${sessionId ? `:${sessionId}` : ''}`, at });
|
|
112
|
+
}
|
|
113
|
+
if (type === 'turn/end') {
|
|
114
|
+
const reason = event?.data?.reason?.kind ?? 'completed';
|
|
115
|
+
const failed = reason !== 'completed';
|
|
116
|
+
return queue.enqueue({
|
|
117
|
+
kind: failed ? 'ops' : 'session',
|
|
118
|
+
priority: failed ? 'P0' : 'P2',
|
|
119
|
+
subject: `turn:${turn}:${reason}`,
|
|
120
|
+
source: `session/turn${sessionId ? `:${sessionId}` : ''}`,
|
|
121
|
+
at,
|
|
122
|
+
});
|
|
123
|
+
}
|
|
124
|
+
return null;
|
|
125
|
+
}
|
|
126
|
+
|
|
127
|
+
/** Convenience producer for memory write/recall events (`kind=ops` interface). */
|
|
128
|
+
export function recordAttentionEvent({ memoryRoot, now = () => new Date(), config = {}, priority, kind = 'ops', subject = '', source = 'memory' } = {}) {
|
|
129
|
+
const queue = createAttentionQueue({ memoryRoot, now, config });
|
|
130
|
+
return queue.enqueue({ priority, kind, subject, source });
|
|
131
|
+
}
|
|
132
|
+
|
|
133
|
+
/** Runtime identity for the relationship bridge (host/user best effort, deterministic fallback). */
|
|
134
|
+
function runtimePersonId(session, event) {
|
|
135
|
+
const data = event?.data ?? {};
|
|
136
|
+
const candidates = [
|
|
137
|
+
data.userId,
|
|
138
|
+
data.personId,
|
|
139
|
+
data.actor,
|
|
140
|
+
session?.header?.userId,
|
|
141
|
+
session?.header?.actor,
|
|
142
|
+
session?.userId,
|
|
143
|
+
];
|
|
144
|
+
for (const value of candidates) {
|
|
145
|
+
if (typeof value === 'string' && value.trim() !== '') return value.trim();
|
|
146
|
+
}
|
|
147
|
+
return 'user:host';
|
|
148
|
+
}
|
|
149
|
+
|
|
150
|
+
/**
|
|
151
|
+
* Register the role kernel on a host context.
|
|
152
|
+
* 1.8.0 默认 ON(Config schema role.enabled=true);显式 false 是独立 kill switch,
|
|
153
|
+
* 关闭时零 hook / 零目录 / 零 IO。runtime/probe() 提供运行期真实调用证据。
|
|
154
|
+
* @returns {{enabled:boolean, hooks:string[], modules:object, config:object, reason?:string, dispose:Function}}
|
|
155
|
+
*/
|
|
156
|
+
export function registerRole(ctx, config = {}, options = {}) {
|
|
157
|
+
const cfg = normalizeRoleConfig(config);
|
|
158
|
+
const memoryRoot = typeof options.memoryRoot === 'string' && options.memoryRoot.trim() !== ''
|
|
159
|
+
? options.memoryRoot.trim()
|
|
160
|
+
: null;
|
|
161
|
+
const now = typeof options.now === 'function' ? options.now : () => new Date();
|
|
162
|
+
const warn = typeof options.warn === 'function' ? options.warn : () => {};
|
|
163
|
+
// §16.8:role/** 宿主私有写保护(auditKey 可注入;默认走 <memoryRoot>/.longlive/.hostkey)。
|
|
164
|
+
const auditKey = options.auditKey ?? null;
|
|
165
|
+
const actorKind = typeof options.actorKind === 'string' ? options.actorKind : 'host';
|
|
166
|
+
const caller = options.caller ?? null;
|
|
167
|
+
const subject = options.subject ?? undefined;
|
|
168
|
+
const hooks = [];
|
|
169
|
+
const disposers = [];
|
|
170
|
+
const modules = { interfaces: null, attention: null, channels: null };
|
|
171
|
+
const noop = () => {};
|
|
172
|
+
|
|
173
|
+
if (!cfg.enabled) return { enabled: false, hooks, modules, config: cfg, reason: 'disabled', dispose: noop };
|
|
174
|
+
if (memoryRoot === null) {
|
|
175
|
+
return { enabled: false, hooks, modules, config: cfg, reason: 'no-memory-root', dispose: noop };
|
|
176
|
+
}
|
|
177
|
+
|
|
178
|
+
modules.interfaces = createRoleInterfaces({ memoryRoot, now, caller, auditKey });
|
|
179
|
+
|
|
180
|
+
// 运行期真实调用计数器(任务 激活证据):只由真实 hook / 注入回调递增。
|
|
181
|
+
const runtime = {
|
|
182
|
+
eventsSeen: 0,
|
|
183
|
+
attentionEnqueued: 0,
|
|
184
|
+
relationshipLookups: 0,
|
|
185
|
+
channelTouches: 0,
|
|
186
|
+
personalityProjections: 0,
|
|
187
|
+
hooks: 0,
|
|
188
|
+
lastRelationship: null,
|
|
189
|
+
lastAttentionSubject: null,
|
|
190
|
+
lastProducedAt: null,
|
|
191
|
+
};
|
|
192
|
+
|
|
193
|
+
// ③ relationship graph:只读装配;写入走 G7 relationship.update。
|
|
194
|
+
if (cfg.relationships.enabled) {
|
|
195
|
+
modules.relationships = {
|
|
196
|
+
read: () => readRelationships({ memoryRoot }),
|
|
197
|
+
tierFor: modules.interfaces.relationship.tierOf,
|
|
198
|
+
recallWeightFor: modules.interfaces.relationship.recallWeightFor,
|
|
199
|
+
multiplierForTier: modules.interfaces.relationship.multiplierForTier,
|
|
200
|
+
defaults: RELATIONSHIP_DEFAULTS,
|
|
201
|
+
};
|
|
202
|
+
}
|
|
203
|
+
|
|
204
|
+
// ② channel adapter:宿主内注册表 + 能力协商 + 降级。
|
|
205
|
+
if (cfg.channels.enabled) {
|
|
206
|
+
const registry = createChannelRegistry({ memoryRoot, now, auditKey, actorKind });
|
|
207
|
+
modules.channels = registry;
|
|
208
|
+
const candidates = [...DEFAULT_HOST_CHANNELS, ...(Array.isArray(options.channels) ? options.channels : [])];
|
|
209
|
+
for (const candidate of candidates) {
|
|
210
|
+
try {
|
|
211
|
+
registry.registerAdapter(candidate);
|
|
212
|
+
} catch (error) {
|
|
213
|
+
warn(`[role] 渠道注册失败 ${candidate?.id ?? '?'}(${error?.code ?? error?.message ?? error})`);
|
|
214
|
+
}
|
|
215
|
+
}
|
|
216
|
+
if (typeof options.channelSend === 'function') {
|
|
217
|
+
try {
|
|
218
|
+
registry.registerAdapter({
|
|
219
|
+
id: 'tool:host',
|
|
220
|
+
type: 'tool',
|
|
221
|
+
capabilities: ['send', 'attachments'],
|
|
222
|
+
send: options.channelSend,
|
|
223
|
+
meta: { channelKind: 'host-tool' },
|
|
224
|
+
});
|
|
225
|
+
} catch (error) {
|
|
226
|
+
warn(`[role] 宿主 send 适配器注册失败(${error?.code ?? error?.message ?? error})`);
|
|
227
|
+
}
|
|
228
|
+
}
|
|
229
|
+
}
|
|
230
|
+
|
|
231
|
+
// ① attention queue:真实 session/event 生产者(hook 在 unified bridge 注册)。
|
|
232
|
+
if (cfg.attention.enabled) {
|
|
233
|
+
modules.attention = createAttentionQueue({ memoryRoot, now, config: cfg.attention, auditKey, actorKind });
|
|
234
|
+
}
|
|
235
|
+
|
|
236
|
+
// 统一 runtime bridge:真实 session/event → attention 入队 + relationship 查询 + channel touch。
|
|
237
|
+
// 「运行期真实调用」证据只在这里递增;测试直接调 readers 不计入。
|
|
238
|
+
const queue = modules.attention;
|
|
239
|
+
const bridgeEnabled = Boolean(queue) || Boolean(modules.relationships) || Boolean(modules.channels);
|
|
240
|
+
if (bridgeEnabled) {
|
|
241
|
+
const bridge = (session, event) => {
|
|
242
|
+
runtime.eventsSeen += 1;
|
|
243
|
+
try {
|
|
244
|
+
if (queue) {
|
|
245
|
+
const enqueued = ingestSessionEvent(queue, session, event, now);
|
|
246
|
+
if (enqueued) {
|
|
247
|
+
runtime.attentionEnqueued += 1;
|
|
248
|
+
runtime.lastAttentionSubject = enqueued?.row?.subject ?? enqueued?.subject ?? null;
|
|
249
|
+
}
|
|
250
|
+
}
|
|
251
|
+
} catch {
|
|
252
|
+
/* 角色层故障不得拖垮 turn(attention 只是顺序偏好) */
|
|
253
|
+
}
|
|
254
|
+
try {
|
|
255
|
+
// relationship → recall weight:真实 graph 读取(未知身份按 stranger 返回,仍是真调用)。
|
|
256
|
+
if (modules.relationships && event?.type === 'user/message') {
|
|
257
|
+
const personId = runtimePersonId(session, event);
|
|
258
|
+
const weight = modules.interfaces.relationship.recallWeightFor(personId);
|
|
259
|
+
runtime.relationshipLookups += 1;
|
|
260
|
+
runtime.lastRelationship = { personId, tier: weight.tier, multiplier: weight.multiplier };
|
|
261
|
+
}
|
|
262
|
+
} catch {
|
|
263
|
+
/* best effort */
|
|
264
|
+
}
|
|
265
|
+
try {
|
|
266
|
+
// channel adapter:真实 touch 宿主持久渠道(lastSeenAt 落盘)。
|
|
267
|
+
if (modules.channels && typeof modules.channels.touch === 'function'
|
|
268
|
+
&& (event?.type === 'user/message' || event?.type === 'turn/end')) {
|
|
269
|
+
const touched = modules.channels.touch('web', { at: now() });
|
|
270
|
+
if (touched?.ok === true) runtime.channelTouches += 1;
|
|
271
|
+
}
|
|
272
|
+
} catch {
|
|
273
|
+
/* best effort */
|
|
274
|
+
}
|
|
275
|
+
runtime.lastProducedAt = iso(nowMs(now()));
|
|
276
|
+
};
|
|
277
|
+
try {
|
|
278
|
+
if (ctx && typeof ctx.on === 'function') {
|
|
279
|
+
const disposer = ctx.on('session/event', bridge);
|
|
280
|
+
if (typeof disposer === 'function') disposers.push(disposer);
|
|
281
|
+
hooks.push('session/event');
|
|
282
|
+
runtime.hooks += 1;
|
|
283
|
+
}
|
|
284
|
+
} catch (error) {
|
|
285
|
+
warn(`[role] session/event bridge 注册失败(${error?.code ?? error?.message ?? error})`);
|
|
286
|
+
}
|
|
287
|
+
}
|
|
288
|
+
|
|
289
|
+
// ④ personality projection:首次生成快照 + 语气投影(systemPrompt.section)。
|
|
290
|
+
if (cfg.personality.enabled) {
|
|
291
|
+
try {
|
|
292
|
+
let current = readPersonality({ memoryRoot, caller, auditKey }).snapshot;
|
|
293
|
+
if (current === null || (current.version ?? 0) === 0) {
|
|
294
|
+
const generated = generatePersonalitySnapshot({
|
|
295
|
+
memoryRoot,
|
|
296
|
+
profilePaths: cfg.personality.profilePaths,
|
|
297
|
+
now,
|
|
298
|
+
auditKey,
|
|
299
|
+
actorKind,
|
|
300
|
+
subject,
|
|
301
|
+
});
|
|
302
|
+
current = generated.snapshot;
|
|
303
|
+
}
|
|
304
|
+
modules.personality = {
|
|
305
|
+
snapshot: () => readPersonality({ memoryRoot, caller, auditKey }).snapshot,
|
|
306
|
+
toneProjection: () => {
|
|
307
|
+
runtime.personalityProjections += 1;
|
|
308
|
+
return toneProjection({ snapshot: readPersonality({ memoryRoot, caller, auditKey }).snapshot });
|
|
309
|
+
},
|
|
310
|
+
retentionBiasFor: modules.interfaces.personality.retentionBiasFor,
|
|
311
|
+
decayThresholdsFor: modules.interfaces.personality.decayThresholdsFor,
|
|
312
|
+
};
|
|
313
|
+
const section = ctx?.systemPrompt;
|
|
314
|
+
if (section && typeof section.section === 'function') {
|
|
315
|
+
const initial = toneProjection({ snapshot: current });
|
|
316
|
+
if (initial.inject) {
|
|
317
|
+
const order = typeof section.getSectionOrder === 'function'
|
|
318
|
+
? (section.getSectionOrder('TOOL_SUBAGENT') ?? 900)
|
|
319
|
+
: 900;
|
|
320
|
+
section.section({
|
|
321
|
+
name: 'role:tone',
|
|
322
|
+
order,
|
|
323
|
+
// 每次宿主取 section 文本都真实重读快照(并计入激活证据)。
|
|
324
|
+
text: () => {
|
|
325
|
+
runtime.personalityProjections += 1;
|
|
326
|
+
return toneProjection({ snapshot: readPersonality({ memoryRoot, caller, auditKey }).snapshot }).styleLine;
|
|
327
|
+
},
|
|
328
|
+
});
|
|
329
|
+
hooks.push('systemPrompt.section');
|
|
330
|
+
runtime.hooks += 1;
|
|
331
|
+
}
|
|
332
|
+
}
|
|
333
|
+
} catch (error) {
|
|
334
|
+
warn(`[role] personality 装配失败(${error?.code ?? error?.message ?? error})`);
|
|
335
|
+
}
|
|
336
|
+
}
|
|
337
|
+
|
|
338
|
+
/** 运行期激活证据快照:给 scripts/verify-activation.mjs / 宿主 doctor 用。 */
|
|
339
|
+
function probe() {
|
|
340
|
+
const attentionView = modules.attention ? readAttentionView({ memoryRoot, now }) : null;
|
|
341
|
+
const relationshipGraph = readRelationships({ memoryRoot });
|
|
342
|
+
const personalityRead = readPersonality({ memoryRoot, caller, auditKey });
|
|
343
|
+
let channelsView = null;
|
|
344
|
+
try {
|
|
345
|
+
channelsView = modules.channels && typeof modules.channels.view === 'function' ? modules.channels.view({ now }) : null;
|
|
346
|
+
} catch {
|
|
347
|
+
channelsView = null;
|
|
348
|
+
}
|
|
349
|
+
const paths = tryRolePaths(memoryRoot);
|
|
350
|
+
const files = paths
|
|
351
|
+
? {
|
|
352
|
+
attention: fs.existsSync(paths.attention),
|
|
353
|
+
relationships: fs.existsSync(paths.relationships),
|
|
354
|
+
personality: fs.existsSync(paths.personality),
|
|
355
|
+
channels: fs.existsSync(paths.channels),
|
|
356
|
+
audit: fs.existsSync(paths.audit),
|
|
357
|
+
}
|
|
358
|
+
: {};
|
|
359
|
+
const checks = {
|
|
360
|
+
enabled: true,
|
|
361
|
+
hooksRegistered: runtime.hooks > 0,
|
|
362
|
+
eventsSeen: runtime.eventsSeen > 0,
|
|
363
|
+
attentionIngested: runtime.attentionEnqueued > 0,
|
|
364
|
+
relationshipRead: runtime.relationshipLookups > 0,
|
|
365
|
+
channelTouched: runtime.channelTouches > 0,
|
|
366
|
+
personalityProjected: runtime.personalityProjections > 0,
|
|
367
|
+
attentionFilePresent: files.attention === true,
|
|
368
|
+
channelsFilePresent: files.channels === true,
|
|
369
|
+
personalityFilePresent: files.personality === true,
|
|
370
|
+
};
|
|
371
|
+
return {
|
|
372
|
+
schema: 'role-activation/v1',
|
|
373
|
+
enabled: true,
|
|
374
|
+
hooks: [...hooks],
|
|
375
|
+
counters: { ...runtime },
|
|
376
|
+
checks,
|
|
377
|
+
attention: attentionView
|
|
378
|
+
? {
|
|
379
|
+
present: attentionView.present,
|
|
380
|
+
count: attentionView.count,
|
|
381
|
+
code: attentionView.code,
|
|
382
|
+
// 前 10 行按优先级排序后的证据(verify-activation 断言 P0 先注入)。
|
|
383
|
+
items: (attentionView.items ?? []).slice(0, 10).map((item) => ({
|
|
384
|
+
priority: item.priority,
|
|
385
|
+
effectivePriority: item.effectivePriority,
|
|
386
|
+
kind: item.kind,
|
|
387
|
+
subject: item.subject,
|
|
388
|
+
})),
|
|
389
|
+
}
|
|
390
|
+
: null,
|
|
391
|
+
relationships: { present: relationshipGraph.present, count: relationshipGraph.count, code: relationshipGraph.code },
|
|
392
|
+
personality: {
|
|
393
|
+
present: personalityRead.present,
|
|
394
|
+
version: personalityRead.snapshot?.version ?? 0,
|
|
395
|
+
tone: personalityRead.snapshot?.tone ?? 'neutral',
|
|
396
|
+
},
|
|
397
|
+
channels: channelsView ? { present: channelsView.present, count: channelsView.count, code: channelsView.code } : null,
|
|
398
|
+
files,
|
|
399
|
+
readAt: iso(nowMs(now())),
|
|
400
|
+
};
|
|
401
|
+
}
|
|
402
|
+
|
|
403
|
+
function dispose() {
|
|
404
|
+
for (const disposer of disposers) {
|
|
405
|
+
try { disposer(); } catch { /* already disposed */ }
|
|
406
|
+
}
|
|
407
|
+
disposers.length = 0;
|
|
408
|
+
}
|
|
409
|
+
if (typeof ctx?.effect === 'function') {
|
|
410
|
+
try { ctx.effect(() => dispose, 'longlive.role'); } catch { /* fake ctx */ }
|
|
411
|
+
}
|
|
412
|
+
return { enabled: true, hooks, modules, config: cfg, dispose, runtime, probe };
|
|
413
|
+
}
|
|
414
|
+
|
|
415
|
+
export {
|
|
416
|
+
createAttentionQueue,
|
|
417
|
+
createChannelRegistry,
|
|
418
|
+
createRoleInterfaces,
|
|
419
|
+
generatePersonalitySnapshot,
|
|
420
|
+
normalizeInboundMessage,
|
|
421
|
+
attentionPriorityOfKind,
|
|
422
|
+
orderOpsItems,
|
|
423
|
+
INTERFACE_CONTRACTS,
|
|
424
|
+
CHANNEL_DEFAULTS,
|
|
425
|
+
};
|
|
@@ -0,0 +1,115 @@
|
|
|
1
|
+
// src/role/interfaces.js — 角色内核 · 三接口契约(v2.8 §16.3 接口接线)
|
|
2
|
+
//
|
|
3
|
+
// 三接口(字段名/类型/默认值/错误码在这里冻结,测试直接断言本文件的常量):
|
|
4
|
+
// 1) relationshipTier → recall 排序权重乘子(只排序,不改事实真值/保护谓词)
|
|
5
|
+
// 2) attention → kind=ops 优先级(只排序;与 recall 冲突时记忆事实优先)
|
|
6
|
+
// 3) personality snapshot → 遗忘偏好(retentionBias 只调阈值,不越权 protected/forbid)
|
|
7
|
+
//
|
|
8
|
+
// 消费方(记忆召回/遗忘/注入)可以直接 import 纯函数,也可以拿
|
|
9
|
+
// `createRoleInterfaces({memoryRoot})` 拿到与某个记忆根绑定的实现。
|
|
10
|
+
|
|
11
|
+
import { orderOpsItems, attentionPriorityOfKind, normalizeAttentionRow, ATTENTION_PRIORITIES } from './attention.js';
|
|
12
|
+
import {
|
|
13
|
+
RELATIONSHIP_TIERS, TIER_WEIGHTS, applyRelationshipWeights, readRelationships, recallWeightFor, relationshipTierMultiplier,
|
|
14
|
+
} from './relationships.js';
|
|
15
|
+
import {
|
|
16
|
+
PERSONALITY_SCHEMA, RETENTION_KINDS, applyRetentionBiasToRules, readPersonality, retentionBiasFor, toneProjection,
|
|
17
|
+
} from './personality.js';
|
|
18
|
+
import { nowMs, readJsonlSafe, tryRolePaths } from './store.js';
|
|
19
|
+
|
|
20
|
+
export const ROLE_INTERFACE_VERSION = '1.0.0';
|
|
21
|
+
|
|
22
|
+
/** 三接口契约描述(机器可读;测试用它锁字段名/类型/默认值/错误码)。 */
|
|
23
|
+
export const INTERFACE_CONTRACTS = Object.freeze({
|
|
24
|
+
relationshipTierToRecallWeight: Object.freeze({
|
|
25
|
+
id: 'relationshipTier->recallWeight',
|
|
26
|
+
version: ROLE_INTERFACE_VERSION,
|
|
27
|
+
fields: Object.freeze({
|
|
28
|
+
tier: Object.freeze({ type: 'enum', values: RELATIONSHIP_TIERS, default: 'stranger' }),
|
|
29
|
+
multiplier: Object.freeze({ type: 'number', default: TIER_WEIGHTS.stranger, table: TIER_WEIGHTS }),
|
|
30
|
+
}),
|
|
31
|
+
errors: Object.freeze(['E_RELATIONSHIP_TIER', 'E_ROLE_CAS_CONFLICT', 'E_ROLE_PARAMS']),
|
|
32
|
+
guarantees: Object.freeze(['只影响召回排序', '不改变事实真值/保护谓词']),
|
|
33
|
+
}),
|
|
34
|
+
attentionToOpsPriority: Object.freeze({
|
|
35
|
+
id: 'attention->kind=ops priority',
|
|
36
|
+
version: ROLE_INTERFACE_VERSION,
|
|
37
|
+
fields: Object.freeze({
|
|
38
|
+
priority: Object.freeze({ type: 'enum', values: ATTENTION_PRIORITIES, default: 'P2' }),
|
|
39
|
+
kind: Object.freeze({ type: 'enum', values: Object.freeze(['ops', 'user', 'tool', 'session', 'error']), default: 'ops' }),
|
|
40
|
+
subject: Object.freeze({ type: 'string', default: '' }),
|
|
41
|
+
}),
|
|
42
|
+
errors: Object.freeze(['E_ROLE_PARAMS']),
|
|
43
|
+
guarantees: Object.freeze(['只影响注入/面板顺序', '不改变召回内容', '与 recall 冲突时记忆事实优先']),
|
|
44
|
+
}),
|
|
45
|
+
personalitySnapshotToRetentionBias: Object.freeze({
|
|
46
|
+
id: 'personality snapshot->retention bias',
|
|
47
|
+
version: ROLE_INTERFACE_VERSION,
|
|
48
|
+
fields: Object.freeze({
|
|
49
|
+
snapshotId: Object.freeze({ type: 'string', default: null }),
|
|
50
|
+
version: Object.freeze({ type: 'number', default: 0 }),
|
|
51
|
+
retentionBias: Object.freeze({ type: 'map', keys: RETENTION_KINDS, value: Object.freeze({ type: 'number', default: 1.0, min: 0.8, max: 1.5 }) }),
|
|
52
|
+
tone: Object.freeze({ type: 'string', default: 'neutral' }),
|
|
53
|
+
createdAt: Object.freeze({ type: 'string', default: null }),
|
|
54
|
+
}),
|
|
55
|
+
errors: Object.freeze(['E_ROLE_PARAMS', 'E_ROLE_CORRUPT']),
|
|
56
|
+
guarantees: Object.freeze(['只影响衰减阈值', '不越权 protected/forbid/immuneSalience']),
|
|
57
|
+
}),
|
|
58
|
+
});
|
|
59
|
+
|
|
60
|
+
/** Read attention rows from disk without building the writer queue. */
|
|
61
|
+
export function readAttentionRows({ memoryRoot, limit = 10_000, auditKey = null } = {}) {
|
|
62
|
+
const paths = tryRolePaths(memoryRoot);
|
|
63
|
+
if (paths === null) return { rows: [], bad: 0, present: false, total: 0 };
|
|
64
|
+
const read = readJsonlSafe(paths.attention, { limit, memoryRoot, auditKey });
|
|
65
|
+
if (read.tampered === true) return { rows: [], bad: 0, present: false, total: 0, tampered: true, code: read.code ?? 'E_INTEGRITY_TAMPER' };
|
|
66
|
+
const rows = [];
|
|
67
|
+
let bad = 0;
|
|
68
|
+
for (const raw of read.rows) {
|
|
69
|
+
const row = normalizeAttentionRow(raw);
|
|
70
|
+
if (row) rows.push(row);
|
|
71
|
+
else bad += 1;
|
|
72
|
+
}
|
|
73
|
+
return { rows, bad, present: read.present, total: read.total ?? rows.length };
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
/**
|
|
77
|
+
* Bind the three interfaces to one memory root (read-side helpers).
|
|
78
|
+
* Writes stay in the owning modules (attention queue / updateRelationship /
|
|
79
|
+
* generatePersonalitySnapshot) so consumers cannot bypass validation.
|
|
80
|
+
*/
|
|
81
|
+
export function createRoleInterfaces({ memoryRoot, now = () => new Date(), caller = null, auditKey = null } = {}) {
|
|
82
|
+
const clock = () => nowMs(now());
|
|
83
|
+
return {
|
|
84
|
+
version: ROLE_INTERFACE_VERSION,
|
|
85
|
+
memoryRoot,
|
|
86
|
+
caller,
|
|
87
|
+
contracts: INTERFACE_CONTRACTS,
|
|
88
|
+
relationship: {
|
|
89
|
+
tierOf: (personId) => {
|
|
90
|
+
const graph = readRelationships({ memoryRoot, caller, auditKey });
|
|
91
|
+
const hit = graph.nodes.find((node) => node.personId === String(personId ?? ''));
|
|
92
|
+
return hit ? hit.tier : 'stranger';
|
|
93
|
+
},
|
|
94
|
+
multiplierForTier: (tier) => relationshipTierMultiplier(tier),
|
|
95
|
+
recallWeightFor: (personId) => recallWeightFor({ memoryRoot, personId, caller, auditKey }),
|
|
96
|
+
applyWeights: (items, options = {}) => applyRelationshipWeights(items, { memoryRoot, caller, auditKey, ...options }),
|
|
97
|
+
},
|
|
98
|
+
attention: {
|
|
99
|
+
priorityOfKind: (kind) => attentionPriorityOfKind(kind),
|
|
100
|
+
orderOps: (items, options = {}) => {
|
|
101
|
+
const read = readAttentionRows({ memoryRoot, auditKey });
|
|
102
|
+
return orderOpsItems(items, read.rows, { now: options.now ?? clock() });
|
|
103
|
+
},
|
|
104
|
+
},
|
|
105
|
+
personality: {
|
|
106
|
+
read: () => readPersonality({ memoryRoot, caller, auditKey }),
|
|
107
|
+
snapshot: () => readPersonality({ memoryRoot, caller, auditKey }).snapshot,
|
|
108
|
+
retentionBiasFor: (kind) => retentionBiasFor(kind, { snapshot: readPersonality({ memoryRoot, caller, auditKey }).snapshot }),
|
|
109
|
+
decayThresholdsFor: (baseRules) => applyRetentionBiasToRules(baseRules, { snapshot: readPersonality({ memoryRoot, caller, auditKey }).snapshot }),
|
|
110
|
+
toneProjection: () => toneProjection({ snapshot: readPersonality({ memoryRoot, caller, auditKey }).snapshot }),
|
|
111
|
+
},
|
|
112
|
+
};
|
|
113
|
+
}
|
|
114
|
+
|
|
115
|
+
export { PERSONALITY_SCHEMA };
|