@ai-agent-forge/plugin-memory 0.88.18 → 0.88.20

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.
@@ -5,7 +5,7 @@ import { forgetDomainMemory, listDomainMemories, } from "./suite-memory.js";
5
5
  import { EVENT_TYPE } from "./write-face.js";
6
6
  const DEFAULT_LIST_LIMIT = 20;
7
7
  const listSchema = Type.Object({
8
- limit: Type.Optional(Type.Integer({ description: "Maximum number of entries to list (1-100)", minimum: 1, maximum: 100 })),
8
+ limit: Type.Optional(Type.Integer({ description: "Maximum number of entries to list (1-100, default 20)", minimum: 1, maximum: 100 })),
9
9
  });
10
10
  const forgetSchema = Type.Object({
11
11
  memoryId: Type.String({
@@ -1 +1 @@
1
- {"version":3,"file":"command-face.js","sourceRoot":"","sources":["../../src/memory/command-face.ts"],"names":[],"mappings":"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","sourcesContent":["/**\n * 命令面(capability 拆分战役 slice 6):memory_list / memory_forget 工具、\n * /memory 与 /promote 命令、memory_enable 征询执行面与 memory.scheduler 能力\n * 声明。物理删除的授权引用构造在本面(用户命令链专用); decline/download 仍\n * 薄委托 vectorChannelCtl(slice 3b 先例, 经 deps 下的 channel 对象调用);\n * 搬移函数体逐字节保留。\n */\nimport type { CapabilityAPI, CapabilityCommandResult, DisposableRegistration } from \"@agent-forge/plugin-sdk\";\nimport { Type } from \"typebox\";\nimport { writeMemorySettingsMode } from \"./enabling.ts\";\nimport type { DurableMemoryLedgerV1 } from \"./ledger.ts\";\nimport { promotePreferenceToUserDefault } from \"./preference-promotion.ts\";\nimport type { MemoryPurgeGateV1 } from \"./purge.ts\";\nimport type { MemoryPurgeJournalV1 } from \"./purge-journal.ts\";\nimport type { MemoryStoreV1 } from \"./store.ts\";\nimport {\n\tforgetDomainMemory,\n\tlistDomainMemories,\n\ttype SuiteForgetAuthorizationRefV1,\n\ttype SuiteMemoryDomainV1,\n\ttype SuiteMemoryListEntryV1,\n} from \"./suite-memory.ts\";\nimport type { MemoryVectorChannelV1 } from \"./vector-channel.ts\";\nimport type { MemoryVectorProjectionV1 } from \"./vector-projection.ts\";\nimport { EVENT_TYPE } from \"./write-face.ts\";\n\nconst DEFAULT_LIST_LIMIT = 20;\n\nconst listSchema = Type.Object({\n\tlimit: Type.Optional(\n\t\tType.Integer({ description: \"Maximum number of entries to list (1-100)\", minimum: 1, maximum: 100 }),\n\t),\n});\n\nconst forgetSchema = Type.Object({\n\tmemoryId: Type.String({\n\t\tdescription: \"The memoryId of the memory to forget, as returned by memory_write or memory_list\",\n\t}),\n});\n\nconst enableSchema = Type.Object({\n\taction: Type.Union([Type.Literal(\"download\"), Type.Literal(\"decline\"), Type.Literal(\"retry\")], {\n\t\tdescription:\n\t\t\t'\"download\" / \"retry\" run the one-time model download (retry re-runs after a failure); \"decline\" persists memory off for this machine',\n\t}),\n});\n\n/** Canonical memoryId of the promoted copy of one preference (promotePreferenceToUserDefault suffix). */\nconst userDefaultMemoryId = (memoryId: string): string => `${memoryId}-user-default`;\n\n/**\n * 命令面对 capability 闭包的显式依赖视图 (slice 6): 逐项来自\n * createMemoryCapabilityHarness 的闭包捕获核实。状态机 (vectorChannelCtl)、\n * 写侧投影 (vectorProjection) 与 purge 组件归 capability 装配所有, 本模块经\n * 对象只读消费; capability 模块级纯函数经 deps 注入避免双头定义。\n */\nexport interface MemoryCommandFaceDepsV1 {\n\t/** 工具注册面 (api.registerTool): memory_list/forget/enable 经同一公共通道注册。 */\n\treadonly registerTool: CapabilityAPI[\"registerTool\"];\n\t/** 命令注册面 (api.registerCommand): /memory 与 /promote。 */\n\treadonly registerCommand: CapabilityAPI[\"registerCommand\"];\n\t/** 事件发布面 (api.publish): memory.changed forgotten 事件。 */\n\treadonly publish: CapabilityAPI[\"publish\"];\n\t/** 能力声明面 (api.capabilities): memory.scheduler@1 provide。 */\n\treadonly capabilities: CapabilityAPI[\"capabilities\"];\n\t/** 检索行 owner (capability.BUILTIN_MEMORY_OWNER)。 */\n\treadonly owner: string;\n\t/** 会话 id (harness sessionId): forget 授权引用 confirmationRef 组成。 */\n\treadonly sessionId: string;\n\t/** capability 清单 (capabilityManifest): issuedBy / authorizedBy / 事件 correlationId 来源。 */\n\treadonly manifest: { readonly id: string };\n\t/** agent 根目录 (MemoryCapabilityContext.agentDir): 档位持久化路径域。 */\n\treadonly agentDir: string;\n\t/** 已解析的全局档位 (harness memoryMode, off 已被工厂入口拦截)。 */\n\treadonly memoryMode: \"light\" | \"full\";\n\t/** 时钟 (harness now)。 */\n\treadonly now: () => number;\n\t/** canonical store (per-capability 单例): 列表/成员判定/晋升回查。 */\n\treadonly store: MemoryStoreV1;\n\t/** purge 门 (capability.purgeGate): 物理删除唯一生产入口的授权面。 */\n\treadonly purgeGate: MemoryPurgeGateV1;\n\t/** purge 日志 (capability.purgeJournal)。 */\n\treadonly purgeJournal: MemoryPurgeJournalV1;\n\t/** 写侧投影 (slice 3a 的 vectorProjection): forget 批次的 index 副本分派与晋升埋点。 */\n\treadonly vectorProjection: MemoryVectorProjectionV1;\n\t/** 向量通道 (slice 3b 的 vectorChannelCtl): 状态行与 decline/download 薄委托。 */\n\treadonly vectorChannelCtl: MemoryVectorChannelV1;\n\t/** 域解析 (capability.resolveDomain): 每次调用时惰性解析 (B1 语义)。 */\n\treadonly resolveDomain: () => {\n\t\treadonly domain: SuiteMemoryDomainV1;\n\t\treadonly domainName: string;\n\t\treadonly ledger: DurableMemoryLedgerV1;\n\t};\n\t// ---- capability 模块级纯函数 (留守方, 经 deps 注入避免双头定义) ----\n\treadonly assertNonEmptyString: (value: unknown, label: string) => asserts value is string;\n\treadonly memoryToolInput: (first: unknown, second: unknown) => Record<string, unknown>;\n\treadonly statementOf: (payload: unknown) => string;\n\treadonly unwrapEgressStatement: (statement: string) => string;\n\t// ---- 写入面闭包视图 (slice 5 的 writeFace): 升档声明状态归 write-face 所有 ----\n\t/** 升档声明只读视图 (/memory 状态行 pending 尾巴)。 */\n\treadonly tierUpgradePending: () => boolean;\n\t/** 用户显式档位表达取代本实例升档声明 (/memory light)。 */\n\treadonly clearTierUpgrade: () => void;\n}\n\n/** 命令面工厂产物: 注册项集合 (capability registrations 按既有顺序收集)。 */\nexport interface MemoryCommandFaceV1 {\n\t/** memory_list 工具注册项。 */\n\treadonly listTool: DisposableRegistration;\n\t/** memory_forget 工具注册项 (仅登记请求, 物理删除在 /memory forget)。 */\n\treadonly forgetTool: DisposableRegistration;\n\t/** memory_enable 工具注册项 (征询门执行面)。 */\n\treadonly enableTool: DisposableRegistration;\n\t/** /memory 命令注册项。 */\n\treadonly memoryCommand: DisposableRegistration;\n\t/** /promote 命令注册项。 */\n\treadonly promoteCommand: DisposableRegistration;\n\t/** memory.scheduler@1 plugin capability 声明注册项。 */\n\treadonly schedulerCapability: DisposableRegistration;\n}\n\nexport function createMemoryCommandFace(deps: MemoryCommandFaceDepsV1): MemoryCommandFaceV1 {\n\tconst {\n\t\tregisterTool,\n\t\tregisterCommand,\n\t\tpublish,\n\t\tcapabilities,\n\t\towner,\n\t\tsessionId,\n\t\tagentDir,\n\t\tmemoryMode,\n\t\tnow,\n\t\tstore,\n\t\tpurgeGate,\n\t\tpurgeJournal,\n\t\tvectorProjection,\n\t\tvectorChannelCtl,\n\t\tresolveDomain,\n\t\tmemoryToolInput,\n\t\tstatementOf,\n\t\tunwrapEgressStatement,\n\t\ttierUpgradePending,\n\t\tclearTierUpgrade,\n\t} = deps;\n\tconst { manifest: capabilityManifest } = deps;\n\n\t/** Recency-first payload projection: unwraps the `{ statement }` payload convention. */\n\tfunction entryToToolEntry(entry: SuiteMemoryListEntryV1): Record<string, unknown> {\n\t\treturn {\n\t\t\tmemoryId: entry.memoryId,\n\t\t\tkind: entry.memoryKind,\n\t\t\tstatement: statementOf(entry.payload),\n\t\t\toccurredAt: entry.occurredAt,\n\t\t\t...(entry.preference === undefined\n\t\t\t\t? {}\n\t\t\t\t: {\n\t\t\t\t\t\tpreference: {\n\t\t\t\t\t\t\tsubject: entry.preference.subject,\n\t\t\t\t\t\t\tkey: entry.preference.key,\n\t\t\t\t\t\t\tscopeLevel: entry.preference.scopeLevel,\n\t\t\t\t\t\t\tcrossSuiteVisible: entry.preference.crossSuiteVisible,\n\t\t\t\t\t\t},\n\t\t\t\t\t}),\n\t\t};\n\t}\n\n\tconst listTool = registerTool({\n\t\tname: \"memory_list\",\n\t\tlabel: \"List memories\",\n\t\tdescription: \"List this suite's durable memories (the explicit management surface for personal data)\",\n\t\tparameters: listSchema,\n\t\texecute: async (first: unknown, second?: unknown) => {\n\t\t\tconst input = memoryToolInput(first, second);\n\t\t\tconst rawLimit: unknown = input.limit;\n\t\t\tconst limit =\n\t\t\t\trawLimit === undefined\n\t\t\t\t\t? DEFAULT_LIST_LIMIT\n\t\t\t\t\t: ((): number => {\n\t\t\t\t\t\t\tif (\n\t\t\t\t\t\t\t\ttypeof rawLimit !== \"number\" ||\n\t\t\t\t\t\t\t\t!Number.isSafeInteger(rawLimit) ||\n\t\t\t\t\t\t\t\trawLimit < 1 ||\n\t\t\t\t\t\t\t\trawLimit > 100\n\t\t\t\t\t\t\t) {\n\t\t\t\t\t\t\t\tthrow new Error(\"limit must be an integer between 1 and 100\");\n\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\treturn rawLimit;\n\t\t\t\t\t\t})();\n\t\t\tconst { domain } = resolveDomain();\n\t\t\tconst page = listDomainMemories({ store }, { owner, domain });\n\t\t\treturn {\n\t\t\t\tdomain: page.domain,\n\t\t\t\tentries: page.entries.slice(0, limit).map(entryToToolEntry),\n\t\t\t\ttotal: page.entries.length,\n\t\t\t\tpreferenceCount: page.preferenceCount,\n\t\t\t\tfactCount: page.factCount,\n\t\t\t\tfilter: page.filter,\n\t\t\t};\n\t\t},\n\t});\n\n\t/**\n\t * 物理删除唯一生产入口 (诚实化设计 §2, 2026-10-05 裁决): 用户命令链\n\t * `/memory forget <id> confirm`。授权引用由命令分发构造 (structured\n\t * confirmationRef `memory-forget-command:<session>:<memoryId>:confirmed`),\n\t * 模型工具面不再签发 user-immediate 授权 (记忆系统设计 :997 — 授权不能由\n\t * 模型生成)。\n\t */\n\tconst purgeMemoryViaUserCommand = (memoryId: string) => {\n\t\tconst { domain, ledger } = resolveDomain();\n\t\tconst authorizationRef: SuiteForgetAuthorizationRefV1 = {\n\t\t\tmode: \"user-immediate\",\n\t\t\tissuedAt: now(),\n\t\t\tissuedBy: `${capabilityManifest.id}:/memory-forget-confirm`,\n\t\t\tconfirmationRef: `memory-forget-command:${sessionId}:${memoryId}:confirmed`,\n\t\t};\n\t\treturn forgetDomainMemory(\n\t\t\t{\n\t\t\t\tstore,\n\t\t\t\tpurgeGate,\n\t\t\t\tpurgeJournal,\n\t\t\t\t// index 副本分派 (混合检索工程化): canonical 走下方原回调, 向量投影由\n\t\t\t\t// suite-memory 的 executePurgeBatch 按副本路由到这里。\n\t\t\t\tpurgeIndexReplica: vectorProjection.purgeMemories,\n\t\t\t\tnow,\n\t\t\t},\n\t\t\t{\n\t\t\t\towner,\n\t\t\t\tdomain,\n\t\t\t\tmemoryId,\n\t\t\t\tauthorizedBy: `${capabilityManifest.id}:memory-forget-command`,\n\t\t\t\tauthorizationRef,\n\t\t\t},\n\t\t\t(memoryIds) => {\n\t\t\t\tconst purged = new Set(memoryIds);\n\t\t\t\t// Physical replica purge: rewrite the durable ledger without the\n\t\t\t\t// purged atoms, then evict them from the in-memory store so the\n\t\t\t\t// running process cannot recall them either (both idempotent for\n\t\t\t\t// purge_eligible retries).\n\t\t\t\tledger.rewrite(ledger.atomsSnapshot().filter((atom) => !purged.has(atom.memoryId)));\n\t\t\t\tfor (const id of memoryIds) store.evict(id);\n\t\t\t},\n\t\t);\n\t};\n\n\tconst forgetTool = registerTool({\n\t\tname: \"memory_forget\",\n\t\tlabel: \"File a forget request\",\n\t\tdescription:\n\t\t\t\"File a request to forget one memory of this suite by memoryId. This tool never deletes anything — physical deletion is user-only: the user confirms via /memory forget <memoryId> confirm\",\n\t\tparameters: forgetSchema,\n\t\texecute: async (first: unknown, second?: unknown) => {\n\t\t\tconst input = memoryToolInput(first, second);\n\t\t\tdeps.assertNonEmptyString(input.memoryId, \"memoryId\");\n\t\t\tconst { domain, domainName } = resolveDomain();\n\t\t\tconst unscoped = store.get(input.memoryId, { owner });\n\t\t\tif (unscoped === undefined) {\n\t\t\t\treturn { status: \"not_found\" as const, memoryId: input.memoryId };\n\t\t\t}\n\t\t\t// 域成员判定与 forgetDomainMemory 同判据 (suiteId 匹配 / legacy 为\n\t\t\t// undefined): 请求面同样拒绝外域 id — 模型连\"请求遗忘\"都不能指向别的域。\n\t\t\tconst isMember =\n\t\t\t\tdomain.kind === \"suite\" ? unscoped.atom.suiteId === domain.suiteId : unscoped.atom.suiteId === undefined;\n\t\t\tif (!isMember) {\n\t\t\t\treturn { status: \"foreign_domain\" as const, memoryId: input.memoryId };\n\t\t\t}\n\t\t\t// 请求的登记价值由返回文本承载 (转告用户确认命令); 无进程内状态 —\n\t\t\t// 重复请求幂等 (同一提示再次返回)。\n\t\t\treturn {\n\t\t\t\tstatus: \"requested\" as const,\n\t\t\t\tmemoryId: input.memoryId,\n\t\t\t\trequest:\n\t\t\t\t\t`Forget request filed for \"${unwrapEgressStatement(statementOf(unscoped.atom.payload))}\" in domain ${domainName}. ` +\n\t\t\t\t\t`Nothing is deleted yet — tell the user to confirm the deletion with: /memory forget ${input.memoryId} confirm`,\n\t\t\t};\n\t\t},\n\t});\n\n\t/**\n\t * /promote (偏好晋升触发, B2; 2026-10-05 诚实化裁决起两步确认): the production\n\t * caller of {@link promotePreferenceToUserDefault}. Promotes one of THIS\n\t * session's preferences to the user-default scope: the promoted canonical atom\n\t * is appended to the session domain's durable ledger and committed to the\n\t * store, so every suite's preference-card read (which applies the suite read\n\t * boundary across all of the owner's ledgers) sees it from now on. The\n\t * original atom is never rewritten. Promotion is an irreversible cross-suite\n\t * authorization face, so the first run only shows the target and asks the\n\t * user to re-run with `confirm`. Errors (unknown id, non-preference, already\n\t * promoted) surface as error results, never as thrown failures — a slash\n\t * command must not crash the host.\n\t */\n\tconst promoteCommand = registerCommand({\n\t\tname: \"promote\",\n\t\tdescription:\n\t\t\t\"Promote a memory preference to the user-default scope (visible in every suite; two-step — re-run with confirm)\",\n\t\targumentHint: \"<memoryId> [confirm]\",\n\t\texecute: async (args: string) => {\n\t\t\tconst tokens = args.trim().split(/\\s+/).filter(Boolean);\n\t\t\tconst memoryId = tokens[0] ?? \"\";\n\t\t\tconst confirmed = tokens[1] === \"confirm\";\n\t\t\tif (memoryId === \"\" || tokens.length > 2 || (tokens[1] !== undefined && !confirmed)) {\n\t\t\t\treturn {\n\t\t\t\t\tstatus: \"error\" as const,\n\t\t\t\t\tmessage: \"Usage: /promote <memoryId> [confirm] — run memory_list for this suite's memory ids\",\n\t\t\t\t};\n\t\t\t}\n\t\t\tconst record = store.get(memoryId, { owner });\n\t\t\tif (!record) {\n\t\t\t\treturn {\n\t\t\t\t\tstatus: \"error\" as const,\n\t\t\t\t\tmessage: `No memory found with id ${memoryId}; run memory_list for this suite's memory ids`,\n\t\t\t\t};\n\t\t\t}\n\t\t\tconst atom = record.atom;\n\t\t\tif (atom.memoryKind !== \"preference\" || atom.preference === undefined) {\n\t\t\t\treturn {\n\t\t\t\t\tstatus: \"error\" as const,\n\t\t\t\t\tmessage: `${memoryId} is a fact, not a preference; only preferences can be promoted`,\n\t\t\t\t};\n\t\t\t}\n\t\t\tif (store.get(userDefaultMemoryId(memoryId), { owner }) !== undefined) {\n\t\t\t\treturn {\n\t\t\t\t\tstatus: \"error\" as const,\n\t\t\t\t\tmessage: `Preference ${memoryId} is already promoted to the user-default scope`,\n\t\t\t\t};\n\t\t\t}\n\t\t\tif (!confirmed) {\n\t\t\t\treturn {\n\t\t\t\t\tstatus: \"success\" as const,\n\t\t\t\t\tmessage:\n\t\t\t\t\t\t`Promotion target: \"${unwrapEgressStatement(statementOf(atom.payload))}\"\\n` +\n\t\t\t\t\t\t\"This makes the preference visible in EVERY suite (user-default scope, permanent). Nothing is promoted yet — re-run to confirm:\\n\" +\n\t\t\t\t\t\t`/promote ${memoryId} confirm`,\n\t\t\t\t};\n\t\t\t}\n\t\t\ttry {\n\t\t\t\tconst promoted = promotePreferenceToUserDefault({\n\t\t\t\t\tatom,\n\t\t\t\t\tauthorizedBy: \"user:/promote\",\n\t\t\t\t\tconfirmedAt: new Date(now()).toISOString(),\n\t\t\t\t});\n\t\t\t\tconst { domainName, ledger } = resolveDomain();\n\t\t\t\tledger.append(promoted);\n\t\t\t\tstore.commit(promoted);\n\t\t\t\t// 向量投影埋点: 与 memory_write 同一异步旁路 (失败不阻塞命令返回)。\n\t\t\t\tvectorProjection.upsertAtom(promoted);\n\t\t\t\treturn {\n\t\t\t\t\tstatus: \"success\" as const,\n\t\t\t\t\tmessage: `Promoted \"${unwrapEgressStatement(statementOf(promoted.payload))}\" to the user-default scope; it is now visible in every suite`,\n\t\t\t\t\tdata: { memoryId: promoted.memoryId, sourceMemoryId: memoryId, domain: domainName },\n\t\t\t\t};\n\t\t\t} catch (error) {\n\t\t\t\treturn { status: \"error\" as const, message: error instanceof Error ? error.message : String(error) };\n\t\t\t}\n\t\t},\n\t});\n\n\t// memory.scheduler@1 plugin capability (宪法 §3 显式声明面): a declarative\n\t// marker only — the scheduler enforcement itself (per-instance single\n\t// lease, acquired above) stays carried by the host builtin implementation.\n\t// The declaration anchors \"this suite's memory scheduling is provided by\n\t// this plugin\" so the code Profile assembly check (requiresMemoryScheduler)\n\t// and third-party replacement detection have an explicit surface:\n\t// list()-visible, a same-id double provide fails explicitly (no override\n\t// priority), and after dispose a replacement provider can take the id.\n\tconst schedulerCapability = capabilities.provide({ id: \"memory.scheduler\", version: 1, kind: \"service\" }, () =>\n\t\tObject.freeze({\n\t\t\tid: \"memory.scheduler\",\n\t\t\tversion: 1,\n\t\t\towner: \"agent-forge.builtin.memory\",\n\t\t\tleaseScope: \"per-agent-instance\",\n\t\t}),\n\t);\n\n\t// ---- 征询门执行面 (2026-10-05 用户裁决「首次使用征询、拒绝/失败可跳过」) ----\n\t// memory_enable 是 consent-required / failed 态的唯一下载入口: consentGranted\n\t// 只在这里置位, recall/auto-recall 永不触发下载。decline 同时持久化\n\t// (writeMemoryConsentDeclined), 只抑制默认档——用户显式设置 memory.mode 后\n\t// 下次启用会在首次使用时再次走到征询门。\n\t// ---- 记忆模型下载控制面 (2026-10-05 流程裁决「后台预热、不阻塞提问」) ----\n\t// 生产入口 (交互模式会话绑定后) 经公共工具面调用 memory_enable download\n\t// 触发后台预热; 命令面 /memory off|retry 是用户的直接出口。decline/download\n\t// 核心在 memory/vector-channel.ts (slice 3b), 工具与命令面经\n\t// vectorChannelCtl.decline()/download() 薄委托; decline 持久化只抑制默认档。\n\t/**\n\t * Structured status for host consoles (公共命令结果 data, additive):\n\t * the TUI `/memory` console reads mode/channel/domain without parsing the\n\t * human message. `channel` mirrors the vector state machine; decline is\n\t * reported as \"off\" (the line's existing semantics).\n\t */\n\tconst memoryStatusSnapshot = (): {\n\t\treadonly mode: \"light\" | \"full\";\n\t\treadonly channel: \"off\" | \"consent-required\" | \"enabling\" | \"ready\" | \"degraded\" | \"failed\";\n\t\treadonly declined: boolean;\n\t\treadonly pendingUpgrade: boolean;\n\t\treadonly failureReason?: string;\n\t} => {\n\t\t// pending 尾巴只对仍有效的升档声明 (declined 分支里档位键已被删除, 显示\n\t\t// pending 会失实 — 2026-10-06 复审 P2-1)。\n\t\tconst declined = vectorChannelCtl.declined();\n\t\tconst pendingUpgrade = tierUpgradePending() && !declined;\n\t\tconst failureReason = vectorChannelCtl.failure()?.reason;\n\t\treturn {\n\t\t\tmode: memoryMode,\n\t\t\tchannel: declined ? \"off\" : vectorChannelCtl.rawState(),\n\t\t\tdeclined,\n\t\t\tpendingUpgrade,\n\t\t\t...(failureReason === undefined ? {} : { failureReason }),\n\t\t};\n\t};\n\tconst memoryStatusLine = (status: ReturnType<typeof memoryStatusSnapshot>): string => {\n\t\tconst modeLine = `mode=${status.mode}${status.pendingUpgrade ? \", auto-upgrade to full pending (next session)\" : \"\"}`;\n\t\tif (status.declined) {\n\t\t\treturn `${modeLine}, off (declined on this machine). Set memory.mode (light|full) in settings.json and restart the session to re-enable`;\n\t\t}\n\t\tif (status.channel === \"ready\") return `${modeLine}, ready`;\n\t\tif (status.channel === \"degraded\") return `${modeLine}, ready (degraded)`;\n\t\tif (status.channel === \"failed\") return `${modeLine}, failed: ${status.failureReason ?? \"unknown reason\"}`;\n\t\tif (status.channel === \"consent-required\") {\n\t\t\treturn `${modeLine}, waiting for the model download decision (/memory retry to start it)`;\n\t\t}\n\t\treturn `${modeLine}, loading models in the background`;\n\t};\n\tconst enableTool = registerTool({\n\t\tname: \"memory_enable\",\n\t\tlabel: \"Enable memory\",\n\t\tdescription:\n\t\t\t'Run or decline the one-time memory model download. The host starts it in the background at session start; call it with \"retry\" after a failure, or with \"decline\" when the user chooses to keep memory off',\n\t\tparameters: enableSchema,\n\t\texecute: async (first: unknown, second?: unknown) => {\n\t\t\tconst input = memoryToolInput(first, second);\n\t\t\tconst action: unknown = input.action;\n\t\t\tif (action !== \"download\" && action !== \"decline\" && action !== \"retry\") {\n\t\t\t\tthrow new Error('action must be \"download\", \"decline\", or \"retry\"');\n\t\t\t}\n\t\t\tif (action === \"decline\") return vectorChannelCtl.decline();\n\t\t\treturn vectorChannelCtl.download();\n\t\t},\n\t});\n\tconst memoryCommand = registerCommand({\n\t\tname: \"memory\",\n\t\tdescription:\n\t\t\t\"Memory controls: bare shows status; light|full switches the model preset (persisted, effective next session); off persists disable; retry re-runs the model download; forget <memoryId> [confirm] is the user-only physical delete (two-step)\",\n\t\targumentHint: \"[light|full|off|retry|forget <memoryId> [confirm]]\",\n\t\texecute: async (args: string) => {\n\t\t\tconst tokens = args.trim().split(/\\s+/).filter(Boolean);\n\t\t\tif (tokens[0] === \"forget\") {\n\t\t\t\tconst memoryId = tokens[1] ?? \"\";\n\t\t\t\tconst confirmed = tokens[2] === \"confirm\";\n\t\t\t\tif (memoryId === \"\" || tokens.length > 3 || (tokens[2] !== undefined && !confirmed)) {\n\t\t\t\t\treturn { status: \"error\" as const, message: \"Usage: /memory forget <memoryId> [confirm]\" };\n\t\t\t\t}\n\t\t\t\tconst { domain, domainName } = resolveDomain();\n\t\t\t\tconst unscoped = store.get(memoryId, { owner });\n\t\t\t\tif (unscoped === undefined) {\n\t\t\t\t\treturn {\n\t\t\t\t\t\tstatus: \"error\" as const,\n\t\t\t\t\t\tmessage: `No memory found with id ${memoryId}; run memory_list for this suite's memory ids`,\n\t\t\t\t\t};\n\t\t\t\t}\n\t\t\t\tconst isMember =\n\t\t\t\t\tdomain.kind === \"suite\" ? unscoped.atom.suiteId === domain.suiteId : unscoped.atom.suiteId === undefined;\n\t\t\t\tif (!isMember) {\n\t\t\t\t\treturn {\n\t\t\t\t\t\tstatus: \"error\" as const,\n\t\t\t\t\t\tmessage: `${memoryId} belongs to another suite; each suite forgets only its own memories`,\n\t\t\t\t\t};\n\t\t\t\t}\n\t\t\t\tif (!confirmed) {\n\t\t\t\t\treturn {\n\t\t\t\t\t\tstatus: \"success\" as const,\n\t\t\t\t\t\tmessage:\n\t\t\t\t\t\t\t`Forget target: \"${unwrapEgressStatement(statementOf(unscoped.atom.payload))}\"\\n` +\n\t\t\t\t\t\t\t\"This permanently deletes the memory (irreversible, purge-gated). Nothing is deleted yet — re-run to confirm:\\n\" +\n\t\t\t\t\t\t\t`/memory forget ${memoryId} confirm`,\n\t\t\t\t\t};\n\t\t\t\t}\n\t\t\t\tconst result = purgeMemoryViaUserCommand(memoryId);\n\t\t\t\tif (result.status === \"completed\") {\n\t\t\t\t\tawait publish({\n\t\t\t\t\t\ttype: EVENT_TYPE,\n\t\t\t\t\t\tversion: 1,\n\t\t\t\t\t\tcorrelationId: capabilityManifest.id,\n\t\t\t\t\t\tdata: { action: \"forgotten\", memoryId, domain: domainName, batchId: result.batchId },\n\t\t\t\t\t});\n\t\t\t\t\treturn {\n\t\t\t\t\t\tstatus: \"success\" as const,\n\t\t\t\t\t\tmessage: `Forgot ${memoryId} (${domainName}): the memory is physically deleted`,\n\t\t\t\t\t\tdata: { memoryId, domain: domainName, batchId: result.batchId },\n\t\t\t\t\t};\n\t\t\t\t}\n\t\t\t\treturn { status: \"error\" as const, message: `forget failed: ${result.status}` };\n\t\t\t}\n\t\t\tconst sub = args.trim().toLowerCase();\n\t\t\tif (sub === \"\" || sub === \"status\") {\n\t\t\t\tconst snapshot = memoryStatusSnapshot();\n\t\t\t\tconst statusResult: CapabilityCommandResult = {\n\t\t\t\t\tstatus: \"success\",\n\t\t\t\t\tmessage: `memory: ${memoryStatusLine(snapshot)}`,\n\t\t\t\t\tdata: { memoryStatus: snapshot },\n\t\t\t\t};\n\t\t\t\treturn statusResult;\n\t\t\t}\n\t\t\tif (sub === \"light\" || sub === \"full\") {\n\t\t\t\t// 无\"已是该档\"早退: 持久化档位与会话内已解析档位是两回事——会话可能\n\t\t\t\t// 因默认档而运行 light, 用户降档仍需把持久化值落盘 (或覆盖旧档)。\n\t\t\t\twriteMemorySettingsMode(agentDir, sub);\n\t\t\t\t// 升/降档清了文件里的拒绝标记, 内存标记同步翻回: 用户最新的显式\n\t\t\t\t// 意图是启用——本会话恢复对当前预置的响应 (retry 可用), 新档与\n\t\t\t\t// 新模型下载在下一会话生效。\n\t\t\t\tvectorChannelCtl.clearDeclined();\n\t\t\t\t// 用户的显式档位表达取代本实例的自动升档声明: 降回 light 后 pending\n\t\t\t\t// 尾巴不再显示, 且显式 light 会让自动升档在后续写入中跳过 (复审 P2-1)。\n\t\t\t\tif (sub === \"light\") clearTierUpgrade();\n\t\t\t\treturn {\n\t\t\t\t\tstatus: \"success\" as const,\n\t\t\t\t\tmessage:\n\t\t\t\t\t\t`memory: preset ${sub} persisted (capabilities/builtin.memory.json). It takes effect next session — ` +\n\t\t\t\t\t\t(sub === \"full\"\n\t\t\t\t\t\t\t? \"the larger embedding model (bge-m3, hundreds of MB) downloads in the background at the next start, disk headroom ≥2GB, and write-side semantic dedup turns on. \"\n\t\t\t\t\t\t\t: \"\") +\n\t\t\t\t\t\t\"Existing memories carry over: the projection rebuilds onto the new model automatically.\",\n\t\t\t\t};\n\t\t\t}\n\t\t\t// 命令面结果映射: 工具闭包的 completed/failed 面转 CapabilityCommandResult\n\t\t\t// 的 success/error; 行动清单/next 指引并入 message (命令结果无 actions 字段)。\n\t\t\tconst toCommandResult = (\n\t\t\t\tresult: Awaited<ReturnType<typeof vectorChannelCtl.download>>,\n\t\t\t): { status: \"success\" | \"error\"; message: string } => ({\n\t\t\t\tstatus: result.status === \"completed\" ? \"success\" : \"error\",\n\t\t\t\tmessage:\n\t\t\t\t\tresult.status === \"failed\"\n\t\t\t\t\t\t? [result.message, ...(result.actions ?? []), result.next].filter(Boolean).join(\" — \")\n\t\t\t\t\t\t: result.message,\n\t\t\t});\n\t\t\tif (sub === \"off\") return toCommandResult(vectorChannelCtl.decline());\n\t\t\tif (sub === \"retry\") return toCommandResult(await vectorChannelCtl.download());\n\t\t\treturn {\n\t\t\t\tstatus: \"error\" as const,\n\t\t\t\tmessage: \"Usage: /memory [light|full|off|retry|forget <memoryId> [confirm]]\",\n\t\t\t};\n\t\t},\n\t});\n\n\treturn { listTool, forgetTool, enableTool, memoryCommand, promoteCommand, schedulerCapability };\n}\n"]}
1
+ {"version":3,"file":"command-face.js","sourceRoot":"","sources":["../../src/memory/command-face.ts"],"names":[],"mappings":"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","sourcesContent":["/**\n * 命令面(capability 拆分战役 slice 6):memory_list / memory_forget 工具、\n * /memory 与 /promote 命令、memory_enable 征询执行面与 memory.scheduler 能力\n * 声明。物理删除的授权引用构造在本面(用户命令链专用); decline/download 仍\n * 薄委托 vectorChannelCtl(slice 3b 先例, 经 deps 下的 channel 对象调用);\n * 搬移函数体逐字节保留。\n */\nimport type { CapabilityAPI, CapabilityCommandResult, DisposableRegistration } from \"@agent-forge/plugin-sdk\";\nimport { Type } from \"typebox\";\nimport { writeMemorySettingsMode } from \"./enabling.ts\";\nimport type { DurableMemoryLedgerV1 } from \"./ledger.ts\";\nimport { promotePreferenceToUserDefault } from \"./preference-promotion.ts\";\nimport type { MemoryPurgeGateV1 } from \"./purge.ts\";\nimport type { MemoryPurgeJournalV1 } from \"./purge-journal.ts\";\nimport type { MemoryStoreV1 } from \"./store.ts\";\nimport {\n\tforgetDomainMemory,\n\tlistDomainMemories,\n\ttype SuiteForgetAuthorizationRefV1,\n\ttype SuiteMemoryDomainV1,\n\ttype SuiteMemoryListEntryV1,\n} from \"./suite-memory.ts\";\nimport type { MemoryVectorChannelV1 } from \"./vector-channel.ts\";\nimport type { MemoryVectorProjectionV1 } from \"./vector-projection.ts\";\nimport { EVENT_TYPE } from \"./write-face.ts\";\n\nconst DEFAULT_LIST_LIMIT = 20;\n\nconst listSchema = Type.Object({\n\tlimit: Type.Optional(\n\t\tType.Integer({ description: \"Maximum number of entries to list (1-100, default 20)\", minimum: 1, maximum: 100 }),\n\t),\n});\n\nconst forgetSchema = Type.Object({\n\tmemoryId: Type.String({\n\t\tdescription: \"The memoryId of the memory to forget, as returned by memory_write or memory_list\",\n\t}),\n});\n\nconst enableSchema = Type.Object({\n\taction: Type.Union([Type.Literal(\"download\"), Type.Literal(\"decline\"), Type.Literal(\"retry\")], {\n\t\tdescription:\n\t\t\t'\"download\" / \"retry\" run the one-time model download (retry re-runs after a failure); \"decline\" persists memory off for this machine',\n\t}),\n});\n\n/** Canonical memoryId of the promoted copy of one preference (promotePreferenceToUserDefault suffix). */\nconst userDefaultMemoryId = (memoryId: string): string => `${memoryId}-user-default`;\n\n/**\n * 命令面对 capability 闭包的显式依赖视图 (slice 6): 逐项来自\n * createMemoryCapabilityHarness 的闭包捕获核实。状态机 (vectorChannelCtl)、\n * 写侧投影 (vectorProjection) 与 purge 组件归 capability 装配所有, 本模块经\n * 对象只读消费; capability 模块级纯函数经 deps 注入避免双头定义。\n */\nexport interface MemoryCommandFaceDepsV1 {\n\t/** 工具注册面 (api.registerTool): memory_list/forget/enable 经同一公共通道注册。 */\n\treadonly registerTool: CapabilityAPI[\"registerTool\"];\n\t/** 命令注册面 (api.registerCommand): /memory 与 /promote。 */\n\treadonly registerCommand: CapabilityAPI[\"registerCommand\"];\n\t/** 事件发布面 (api.publish): memory.changed forgotten 事件。 */\n\treadonly publish: CapabilityAPI[\"publish\"];\n\t/** 能力声明面 (api.capabilities): memory.scheduler@1 provide。 */\n\treadonly capabilities: CapabilityAPI[\"capabilities\"];\n\t/** 检索行 owner (capability.BUILTIN_MEMORY_OWNER)。 */\n\treadonly owner: string;\n\t/** 会话 id (harness sessionId): forget 授权引用 confirmationRef 组成。 */\n\treadonly sessionId: string;\n\t/** capability 清单 (capabilityManifest): issuedBy / authorizedBy / 事件 correlationId 来源。 */\n\treadonly manifest: { readonly id: string };\n\t/** agent 根目录 (MemoryCapabilityContext.agentDir): 档位持久化路径域。 */\n\treadonly agentDir: string;\n\t/** 已解析的全局档位 (harness memoryMode, off 已被工厂入口拦截)。 */\n\treadonly memoryMode: \"light\" | \"full\";\n\t/** 时钟 (harness now)。 */\n\treadonly now: () => number;\n\t/** canonical store (per-capability 单例): 列表/成员判定/晋升回查。 */\n\treadonly store: MemoryStoreV1;\n\t/** purge 门 (capability.purgeGate): 物理删除唯一生产入口的授权面。 */\n\treadonly purgeGate: MemoryPurgeGateV1;\n\t/** purge 日志 (capability.purgeJournal)。 */\n\treadonly purgeJournal: MemoryPurgeJournalV1;\n\t/** 写侧投影 (slice 3a 的 vectorProjection): forget 批次的 index 副本分派与晋升埋点。 */\n\treadonly vectorProjection: MemoryVectorProjectionV1;\n\t/** 向量通道 (slice 3b 的 vectorChannelCtl): 状态行与 decline/download 薄委托。 */\n\treadonly vectorChannelCtl: MemoryVectorChannelV1;\n\t/** 域解析 (capability.resolveDomain): 每次调用时惰性解析 (B1 语义)。 */\n\treadonly resolveDomain: () => {\n\t\treadonly domain: SuiteMemoryDomainV1;\n\t\treadonly domainName: string;\n\t\treadonly ledger: DurableMemoryLedgerV1;\n\t};\n\t// ---- capability 模块级纯函数 (留守方, 经 deps 注入避免双头定义) ----\n\treadonly assertNonEmptyString: (value: unknown, label: string) => asserts value is string;\n\treadonly memoryToolInput: (first: unknown, second: unknown) => Record<string, unknown>;\n\treadonly statementOf: (payload: unknown) => string;\n\treadonly unwrapEgressStatement: (statement: string) => string;\n\t// ---- 写入面闭包视图 (slice 5 的 writeFace): 升档声明状态归 write-face 所有 ----\n\t/** 升档声明只读视图 (/memory 状态行 pending 尾巴)。 */\n\treadonly tierUpgradePending: () => boolean;\n\t/** 用户显式档位表达取代本实例升档声明 (/memory light)。 */\n\treadonly clearTierUpgrade: () => void;\n}\n\n/** 命令面工厂产物: 注册项集合 (capability registrations 按既有顺序收集)。 */\nexport interface MemoryCommandFaceV1 {\n\t/** memory_list 工具注册项。 */\n\treadonly listTool: DisposableRegistration;\n\t/** memory_forget 工具注册项 (仅登记请求, 物理删除在 /memory forget)。 */\n\treadonly forgetTool: DisposableRegistration;\n\t/** memory_enable 工具注册项 (征询门执行面)。 */\n\treadonly enableTool: DisposableRegistration;\n\t/** /memory 命令注册项。 */\n\treadonly memoryCommand: DisposableRegistration;\n\t/** /promote 命令注册项。 */\n\treadonly promoteCommand: DisposableRegistration;\n\t/** memory.scheduler@1 plugin capability 声明注册项。 */\n\treadonly schedulerCapability: DisposableRegistration;\n}\n\nexport function createMemoryCommandFace(deps: MemoryCommandFaceDepsV1): MemoryCommandFaceV1 {\n\tconst {\n\t\tregisterTool,\n\t\tregisterCommand,\n\t\tpublish,\n\t\tcapabilities,\n\t\towner,\n\t\tsessionId,\n\t\tagentDir,\n\t\tmemoryMode,\n\t\tnow,\n\t\tstore,\n\t\tpurgeGate,\n\t\tpurgeJournal,\n\t\tvectorProjection,\n\t\tvectorChannelCtl,\n\t\tresolveDomain,\n\t\tmemoryToolInput,\n\t\tstatementOf,\n\t\tunwrapEgressStatement,\n\t\ttierUpgradePending,\n\t\tclearTierUpgrade,\n\t} = deps;\n\tconst { manifest: capabilityManifest } = deps;\n\n\t/** Recency-first payload projection: unwraps the `{ statement }` payload convention. */\n\tfunction entryToToolEntry(entry: SuiteMemoryListEntryV1): Record<string, unknown> {\n\t\treturn {\n\t\t\tmemoryId: entry.memoryId,\n\t\t\tkind: entry.memoryKind,\n\t\t\tstatement: statementOf(entry.payload),\n\t\t\toccurredAt: entry.occurredAt,\n\t\t\t...(entry.preference === undefined\n\t\t\t\t? {}\n\t\t\t\t: {\n\t\t\t\t\t\tpreference: {\n\t\t\t\t\t\t\tsubject: entry.preference.subject,\n\t\t\t\t\t\t\tkey: entry.preference.key,\n\t\t\t\t\t\t\tscopeLevel: entry.preference.scopeLevel,\n\t\t\t\t\t\t\tcrossSuiteVisible: entry.preference.crossSuiteVisible,\n\t\t\t\t\t\t},\n\t\t\t\t\t}),\n\t\t};\n\t}\n\n\tconst listTool = registerTool({\n\t\tname: \"memory_list\",\n\t\tlabel: \"List memories\",\n\t\tdescription: \"List this suite's durable memories (the explicit management surface for personal data)\",\n\t\tparameters: listSchema,\n\t\texecute: async (first: unknown, second?: unknown) => {\n\t\t\tconst input = memoryToolInput(first, second);\n\t\t\tconst rawLimit: unknown = input.limit;\n\t\t\tconst limit =\n\t\t\t\trawLimit === undefined\n\t\t\t\t\t? DEFAULT_LIST_LIMIT\n\t\t\t\t\t: ((): number => {\n\t\t\t\t\t\t\tif (\n\t\t\t\t\t\t\t\ttypeof rawLimit !== \"number\" ||\n\t\t\t\t\t\t\t\t!Number.isSafeInteger(rawLimit) ||\n\t\t\t\t\t\t\t\trawLimit < 1 ||\n\t\t\t\t\t\t\t\trawLimit > 100\n\t\t\t\t\t\t\t) {\n\t\t\t\t\t\t\t\tthrow new Error(\"limit must be an integer between 1 and 100\");\n\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\treturn rawLimit;\n\t\t\t\t\t\t})();\n\t\t\tconst { domain } = resolveDomain();\n\t\t\tconst page = listDomainMemories({ store }, { owner, domain });\n\t\t\treturn {\n\t\t\t\tdomain: page.domain,\n\t\t\t\tentries: page.entries.slice(0, limit).map(entryToToolEntry),\n\t\t\t\ttotal: page.entries.length,\n\t\t\t\tpreferenceCount: page.preferenceCount,\n\t\t\t\tfactCount: page.factCount,\n\t\t\t\tfilter: page.filter,\n\t\t\t};\n\t\t},\n\t});\n\n\t/**\n\t * 物理删除唯一生产入口 (诚实化设计 §2, 2026-10-05 裁决): 用户命令链\n\t * `/memory forget <id> confirm`。授权引用由命令分发构造 (structured\n\t * confirmationRef `memory-forget-command:<session>:<memoryId>:confirmed`),\n\t * 模型工具面不再签发 user-immediate 授权 (记忆系统设计 :997 — 授权不能由\n\t * 模型生成)。\n\t */\n\tconst purgeMemoryViaUserCommand = (memoryId: string) => {\n\t\tconst { domain, ledger } = resolveDomain();\n\t\tconst authorizationRef: SuiteForgetAuthorizationRefV1 = {\n\t\t\tmode: \"user-immediate\",\n\t\t\tissuedAt: now(),\n\t\t\tissuedBy: `${capabilityManifest.id}:/memory-forget-confirm`,\n\t\t\tconfirmationRef: `memory-forget-command:${sessionId}:${memoryId}:confirmed`,\n\t\t};\n\t\treturn forgetDomainMemory(\n\t\t\t{\n\t\t\t\tstore,\n\t\t\t\tpurgeGate,\n\t\t\t\tpurgeJournal,\n\t\t\t\t// index 副本分派 (混合检索工程化): canonical 走下方原回调, 向量投影由\n\t\t\t\t// suite-memory 的 executePurgeBatch 按副本路由到这里。\n\t\t\t\tpurgeIndexReplica: vectorProjection.purgeMemories,\n\t\t\t\tnow,\n\t\t\t},\n\t\t\t{\n\t\t\t\towner,\n\t\t\t\tdomain,\n\t\t\t\tmemoryId,\n\t\t\t\tauthorizedBy: `${capabilityManifest.id}:memory-forget-command`,\n\t\t\t\tauthorizationRef,\n\t\t\t},\n\t\t\t(memoryIds) => {\n\t\t\t\tconst purged = new Set(memoryIds);\n\t\t\t\t// Physical replica purge: rewrite the durable ledger without the\n\t\t\t\t// purged atoms, then evict them from the in-memory store so the\n\t\t\t\t// running process cannot recall them either (both idempotent for\n\t\t\t\t// purge_eligible retries).\n\t\t\t\tledger.rewrite(ledger.atomsSnapshot().filter((atom) => !purged.has(atom.memoryId)));\n\t\t\t\tfor (const id of memoryIds) store.evict(id);\n\t\t\t},\n\t\t);\n\t};\n\n\tconst forgetTool = registerTool({\n\t\tname: \"memory_forget\",\n\t\tlabel: \"File a forget request\",\n\t\tdescription:\n\t\t\t\"File a request to forget one memory of this suite by memoryId. This tool never deletes anything — physical deletion is user-only: the user confirms via /memory forget <memoryId> confirm\",\n\t\tparameters: forgetSchema,\n\t\texecute: async (first: unknown, second?: unknown) => {\n\t\t\tconst input = memoryToolInput(first, second);\n\t\t\tdeps.assertNonEmptyString(input.memoryId, \"memoryId\");\n\t\t\tconst { domain, domainName } = resolveDomain();\n\t\t\tconst unscoped = store.get(input.memoryId, { owner });\n\t\t\tif (unscoped === undefined) {\n\t\t\t\treturn { status: \"not_found\" as const, memoryId: input.memoryId };\n\t\t\t}\n\t\t\t// 域成员判定与 forgetDomainMemory 同判据 (suiteId 匹配 / legacy 为\n\t\t\t// undefined): 请求面同样拒绝外域 id — 模型连\"请求遗忘\"都不能指向别的域。\n\t\t\tconst isMember =\n\t\t\t\tdomain.kind === \"suite\" ? unscoped.atom.suiteId === domain.suiteId : unscoped.atom.suiteId === undefined;\n\t\t\tif (!isMember) {\n\t\t\t\treturn { status: \"foreign_domain\" as const, memoryId: input.memoryId };\n\t\t\t}\n\t\t\t// 请求的登记价值由返回文本承载 (转告用户确认命令); 无进程内状态 —\n\t\t\t// 重复请求幂等 (同一提示再次返回)。\n\t\t\treturn {\n\t\t\t\tstatus: \"requested\" as const,\n\t\t\t\tmemoryId: input.memoryId,\n\t\t\t\trequest:\n\t\t\t\t\t`Forget request filed for \"${unwrapEgressStatement(statementOf(unscoped.atom.payload))}\" in domain ${domainName}. ` +\n\t\t\t\t\t`Nothing is deleted yet — tell the user to confirm the deletion with: /memory forget ${input.memoryId} confirm`,\n\t\t\t};\n\t\t},\n\t});\n\n\t/**\n\t * /promote (偏好晋升触发, B2; 2026-10-05 诚实化裁决起两步确认): the production\n\t * caller of {@link promotePreferenceToUserDefault}. Promotes one of THIS\n\t * session's preferences to the user-default scope: the promoted canonical atom\n\t * is appended to the session domain's durable ledger and committed to the\n\t * store, so every suite's preference-card read (which applies the suite read\n\t * boundary across all of the owner's ledgers) sees it from now on. The\n\t * original atom is never rewritten. Promotion is an irreversible cross-suite\n\t * authorization face, so the first run only shows the target and asks the\n\t * user to re-run with `confirm`. Errors (unknown id, non-preference, already\n\t * promoted) surface as error results, never as thrown failures — a slash\n\t * command must not crash the host.\n\t */\n\tconst promoteCommand = registerCommand({\n\t\tname: \"promote\",\n\t\tdescription:\n\t\t\t\"Promote a memory preference to the user-default scope (visible in every suite; two-step — re-run with confirm)\",\n\t\targumentHint: \"<memoryId> [confirm]\",\n\t\texecute: async (args: string) => {\n\t\t\tconst tokens = args.trim().split(/\\s+/).filter(Boolean);\n\t\t\tconst memoryId = tokens[0] ?? \"\";\n\t\t\tconst confirmed = tokens[1] === \"confirm\";\n\t\t\tif (memoryId === \"\" || tokens.length > 2 || (tokens[1] !== undefined && !confirmed)) {\n\t\t\t\treturn {\n\t\t\t\t\tstatus: \"error\" as const,\n\t\t\t\t\tmessage: \"Usage: /promote <memoryId> [confirm] — run memory_list for this suite's memory ids\",\n\t\t\t\t};\n\t\t\t}\n\t\t\tconst record = store.get(memoryId, { owner });\n\t\t\tif (!record) {\n\t\t\t\treturn {\n\t\t\t\t\tstatus: \"error\" as const,\n\t\t\t\t\tmessage: `No memory found with id ${memoryId}; run memory_list for this suite's memory ids`,\n\t\t\t\t};\n\t\t\t}\n\t\t\tconst atom = record.atom;\n\t\t\tif (atom.memoryKind !== \"preference\" || atom.preference === undefined) {\n\t\t\t\treturn {\n\t\t\t\t\tstatus: \"error\" as const,\n\t\t\t\t\tmessage: `${memoryId} is a fact, not a preference; only preferences can be promoted`,\n\t\t\t\t};\n\t\t\t}\n\t\t\tif (store.get(userDefaultMemoryId(memoryId), { owner }) !== undefined) {\n\t\t\t\treturn {\n\t\t\t\t\tstatus: \"error\" as const,\n\t\t\t\t\tmessage: `Preference ${memoryId} is already promoted to the user-default scope`,\n\t\t\t\t};\n\t\t\t}\n\t\t\tif (!confirmed) {\n\t\t\t\treturn {\n\t\t\t\t\tstatus: \"success\" as const,\n\t\t\t\t\tmessage:\n\t\t\t\t\t\t`Promotion target: \"${unwrapEgressStatement(statementOf(atom.payload))}\"\\n` +\n\t\t\t\t\t\t\"This makes the preference visible in EVERY suite (user-default scope, permanent). Nothing is promoted yet — re-run to confirm:\\n\" +\n\t\t\t\t\t\t`/promote ${memoryId} confirm`,\n\t\t\t\t};\n\t\t\t}\n\t\t\ttry {\n\t\t\t\tconst promoted = promotePreferenceToUserDefault({\n\t\t\t\t\tatom,\n\t\t\t\t\tauthorizedBy: \"user:/promote\",\n\t\t\t\t\tconfirmedAt: new Date(now()).toISOString(),\n\t\t\t\t});\n\t\t\t\tconst { domainName, ledger } = resolveDomain();\n\t\t\t\tledger.append(promoted);\n\t\t\t\tstore.commit(promoted);\n\t\t\t\t// 向量投影埋点: 与 memory_write 同一异步旁路 (失败不阻塞命令返回)。\n\t\t\t\tvectorProjection.upsertAtom(promoted);\n\t\t\t\treturn {\n\t\t\t\t\tstatus: \"success\" as const,\n\t\t\t\t\tmessage: `Promoted \"${unwrapEgressStatement(statementOf(promoted.payload))}\" to the user-default scope; it is now visible in every suite`,\n\t\t\t\t\tdata: { memoryId: promoted.memoryId, sourceMemoryId: memoryId, domain: domainName },\n\t\t\t\t};\n\t\t\t} catch (error) {\n\t\t\t\treturn { status: \"error\" as const, message: error instanceof Error ? error.message : String(error) };\n\t\t\t}\n\t\t},\n\t});\n\n\t// memory.scheduler@1 plugin capability (宪法 §3 显式声明面): a declarative\n\t// marker only — the scheduler enforcement itself (per-instance single\n\t// lease, acquired above) stays carried by the host builtin implementation.\n\t// The declaration anchors \"this suite's memory scheduling is provided by\n\t// this plugin\" so the code Profile assembly check (requiresMemoryScheduler)\n\t// and third-party replacement detection have an explicit surface:\n\t// list()-visible, a same-id double provide fails explicitly (no override\n\t// priority), and after dispose a replacement provider can take the id.\n\tconst schedulerCapability = capabilities.provide({ id: \"memory.scheduler\", version: 1, kind: \"service\" }, () =>\n\t\tObject.freeze({\n\t\t\tid: \"memory.scheduler\",\n\t\t\tversion: 1,\n\t\t\towner: \"agent-forge.builtin.memory\",\n\t\t\tleaseScope: \"per-agent-instance\",\n\t\t}),\n\t);\n\n\t// ---- 征询门执行面 (2026-10-05 用户裁决「首次使用征询、拒绝/失败可跳过」) ----\n\t// memory_enable 是 consent-required / failed 态的唯一下载入口: consentGranted\n\t// 只在这里置位, recall/auto-recall 永不触发下载。decline 同时持久化\n\t// (writeMemoryConsentDeclined), 只抑制默认档——用户显式设置 memory.mode 后\n\t// 下次启用会在首次使用时再次走到征询门。\n\t// ---- 记忆模型下载控制面 (2026-10-05 流程裁决「后台预热、不阻塞提问」) ----\n\t// 生产入口 (交互模式会话绑定后) 经公共工具面调用 memory_enable download\n\t// 触发后台预热; 命令面 /memory off|retry 是用户的直接出口。decline/download\n\t// 核心在 memory/vector-channel.ts (slice 3b), 工具与命令面经\n\t// vectorChannelCtl.decline()/download() 薄委托; decline 持久化只抑制默认档。\n\t/**\n\t * Structured status for host consoles (公共命令结果 data, additive):\n\t * the TUI `/memory` console reads mode/channel/domain without parsing the\n\t * human message. `channel` mirrors the vector state machine; decline is\n\t * reported as \"off\" (the line's existing semantics).\n\t */\n\tconst memoryStatusSnapshot = (): {\n\t\treadonly mode: \"light\" | \"full\";\n\t\treadonly channel: \"off\" | \"consent-required\" | \"enabling\" | \"ready\" | \"degraded\" | \"failed\";\n\t\treadonly declined: boolean;\n\t\treadonly pendingUpgrade: boolean;\n\t\treadonly failureReason?: string;\n\t} => {\n\t\t// pending 尾巴只对仍有效的升档声明 (declined 分支里档位键已被删除, 显示\n\t\t// pending 会失实 — 2026-10-06 复审 P2-1)。\n\t\tconst declined = vectorChannelCtl.declined();\n\t\tconst pendingUpgrade = tierUpgradePending() && !declined;\n\t\tconst failureReason = vectorChannelCtl.failure()?.reason;\n\t\treturn {\n\t\t\tmode: memoryMode,\n\t\t\tchannel: declined ? \"off\" : vectorChannelCtl.rawState(),\n\t\t\tdeclined,\n\t\t\tpendingUpgrade,\n\t\t\t...(failureReason === undefined ? {} : { failureReason }),\n\t\t};\n\t};\n\tconst memoryStatusLine = (status: ReturnType<typeof memoryStatusSnapshot>): string => {\n\t\tconst modeLine = `mode=${status.mode}${status.pendingUpgrade ? \", auto-upgrade to full pending (next session)\" : \"\"}`;\n\t\tif (status.declined) {\n\t\t\treturn `${modeLine}, off (declined on this machine). Set memory.mode (light|full) in settings.json and restart the session to re-enable`;\n\t\t}\n\t\tif (status.channel === \"ready\") return `${modeLine}, ready`;\n\t\tif (status.channel === \"degraded\") return `${modeLine}, ready (degraded)`;\n\t\tif (status.channel === \"failed\") return `${modeLine}, failed: ${status.failureReason ?? \"unknown reason\"}`;\n\t\tif (status.channel === \"consent-required\") {\n\t\t\treturn `${modeLine}, waiting for the model download decision (/memory retry to start it)`;\n\t\t}\n\t\treturn `${modeLine}, loading models in the background`;\n\t};\n\tconst enableTool = registerTool({\n\t\tname: \"memory_enable\",\n\t\tlabel: \"Enable memory\",\n\t\tdescription:\n\t\t\t'Run or decline the one-time memory model download. The host starts it in the background at session start; call it with \"retry\" after a failure, or with \"decline\" when the user chooses to keep memory off',\n\t\tparameters: enableSchema,\n\t\texecute: async (first: unknown, second?: unknown) => {\n\t\t\tconst input = memoryToolInput(first, second);\n\t\t\tconst action: unknown = input.action;\n\t\t\tif (action !== \"download\" && action !== \"decline\" && action !== \"retry\") {\n\t\t\t\tthrow new Error('action must be \"download\", \"decline\", or \"retry\"');\n\t\t\t}\n\t\t\tif (action === \"decline\") return vectorChannelCtl.decline();\n\t\t\treturn vectorChannelCtl.download();\n\t\t},\n\t});\n\tconst memoryCommand = registerCommand({\n\t\tname: \"memory\",\n\t\tdescription:\n\t\t\t\"Memory controls: bare shows status; light|full switches the model preset (persisted, effective next session); off persists disable; retry re-runs the model download; forget <memoryId> [confirm] is the user-only physical delete (two-step)\",\n\t\targumentHint: \"[light|full|off|retry|forget <memoryId> [confirm]]\",\n\t\texecute: async (args: string) => {\n\t\t\tconst tokens = args.trim().split(/\\s+/).filter(Boolean);\n\t\t\tif (tokens[0] === \"forget\") {\n\t\t\t\tconst memoryId = tokens[1] ?? \"\";\n\t\t\t\tconst confirmed = tokens[2] === \"confirm\";\n\t\t\t\tif (memoryId === \"\" || tokens.length > 3 || (tokens[2] !== undefined && !confirmed)) {\n\t\t\t\t\treturn { status: \"error\" as const, message: \"Usage: /memory forget <memoryId> [confirm]\" };\n\t\t\t\t}\n\t\t\t\tconst { domain, domainName } = resolveDomain();\n\t\t\t\tconst unscoped = store.get(memoryId, { owner });\n\t\t\t\tif (unscoped === undefined) {\n\t\t\t\t\treturn {\n\t\t\t\t\t\tstatus: \"error\" as const,\n\t\t\t\t\t\tmessage: `No memory found with id ${memoryId}; run memory_list for this suite's memory ids`,\n\t\t\t\t\t};\n\t\t\t\t}\n\t\t\t\tconst isMember =\n\t\t\t\t\tdomain.kind === \"suite\" ? unscoped.atom.suiteId === domain.suiteId : unscoped.atom.suiteId === undefined;\n\t\t\t\tif (!isMember) {\n\t\t\t\t\treturn {\n\t\t\t\t\t\tstatus: \"error\" as const,\n\t\t\t\t\t\tmessage: `${memoryId} belongs to another suite; each suite forgets only its own memories`,\n\t\t\t\t\t};\n\t\t\t\t}\n\t\t\t\tif (!confirmed) {\n\t\t\t\t\treturn {\n\t\t\t\t\t\tstatus: \"success\" as const,\n\t\t\t\t\t\tmessage:\n\t\t\t\t\t\t\t`Forget target: \"${unwrapEgressStatement(statementOf(unscoped.atom.payload))}\"\\n` +\n\t\t\t\t\t\t\t\"This permanently deletes the memory (irreversible, purge-gated). Nothing is deleted yet — re-run to confirm:\\n\" +\n\t\t\t\t\t\t\t`/memory forget ${memoryId} confirm`,\n\t\t\t\t\t};\n\t\t\t\t}\n\t\t\t\tconst result = purgeMemoryViaUserCommand(memoryId);\n\t\t\t\tif (result.status === \"completed\") {\n\t\t\t\t\tawait publish({\n\t\t\t\t\t\ttype: EVENT_TYPE,\n\t\t\t\t\t\tversion: 1,\n\t\t\t\t\t\tcorrelationId: capabilityManifest.id,\n\t\t\t\t\t\tdata: { action: \"forgotten\", memoryId, domain: domainName, batchId: result.batchId },\n\t\t\t\t\t});\n\t\t\t\t\treturn {\n\t\t\t\t\t\tstatus: \"success\" as const,\n\t\t\t\t\t\tmessage: `Forgot ${memoryId} (${domainName}): the memory is physically deleted`,\n\t\t\t\t\t\tdata: { memoryId, domain: domainName, batchId: result.batchId },\n\t\t\t\t\t};\n\t\t\t\t}\n\t\t\t\treturn { status: \"error\" as const, message: `forget failed: ${result.status}` };\n\t\t\t}\n\t\t\tconst sub = args.trim().toLowerCase();\n\t\t\tif (sub === \"\" || sub === \"status\") {\n\t\t\t\tconst snapshot = memoryStatusSnapshot();\n\t\t\t\tconst statusResult: CapabilityCommandResult = {\n\t\t\t\t\tstatus: \"success\",\n\t\t\t\t\tmessage: `memory: ${memoryStatusLine(snapshot)}`,\n\t\t\t\t\tdata: { memoryStatus: snapshot },\n\t\t\t\t};\n\t\t\t\treturn statusResult;\n\t\t\t}\n\t\t\tif (sub === \"light\" || sub === \"full\") {\n\t\t\t\t// 无\"已是该档\"早退: 持久化档位与会话内已解析档位是两回事——会话可能\n\t\t\t\t// 因默认档而运行 light, 用户降档仍需把持久化值落盘 (或覆盖旧档)。\n\t\t\t\twriteMemorySettingsMode(agentDir, sub);\n\t\t\t\t// 升/降档清了文件里的拒绝标记, 内存标记同步翻回: 用户最新的显式\n\t\t\t\t// 意图是启用——本会话恢复对当前预置的响应 (retry 可用), 新档与\n\t\t\t\t// 新模型下载在下一会话生效。\n\t\t\t\tvectorChannelCtl.clearDeclined();\n\t\t\t\t// 用户的显式档位表达取代本实例的自动升档声明: 降回 light 后 pending\n\t\t\t\t// 尾巴不再显示, 且显式 light 会让自动升档在后续写入中跳过 (复审 P2-1)。\n\t\t\t\tif (sub === \"light\") clearTierUpgrade();\n\t\t\t\treturn {\n\t\t\t\t\tstatus: \"success\" as const,\n\t\t\t\t\tmessage:\n\t\t\t\t\t\t`memory: preset ${sub} persisted (capabilities/builtin.memory.json). It takes effect next session — ` +\n\t\t\t\t\t\t(sub === \"full\"\n\t\t\t\t\t\t\t? \"the larger embedding model (bge-m3, hundreds of MB) downloads in the background at the next start, disk headroom ≥2GB, and write-side semantic dedup turns on. \"\n\t\t\t\t\t\t\t: \"\") +\n\t\t\t\t\t\t\"Existing memories carry over: the projection rebuilds onto the new model automatically.\",\n\t\t\t\t};\n\t\t\t}\n\t\t\t// 命令面结果映射: 工具闭包的 completed/failed 面转 CapabilityCommandResult\n\t\t\t// 的 success/error; 行动清单/next 指引并入 message (命令结果无 actions 字段)。\n\t\t\tconst toCommandResult = (\n\t\t\t\tresult: Awaited<ReturnType<typeof vectorChannelCtl.download>>,\n\t\t\t): { status: \"success\" | \"error\"; message: string } => ({\n\t\t\t\tstatus: result.status === \"completed\" ? \"success\" : \"error\",\n\t\t\t\tmessage:\n\t\t\t\t\tresult.status === \"failed\"\n\t\t\t\t\t\t? [result.message, ...(result.actions ?? []), result.next].filter(Boolean).join(\" — \")\n\t\t\t\t\t\t: result.message,\n\t\t\t});\n\t\t\tif (sub === \"off\") return toCommandResult(vectorChannelCtl.decline());\n\t\t\tif (sub === \"retry\") return toCommandResult(await vectorChannelCtl.download());\n\t\t\treturn {\n\t\t\t\tstatus: \"error\" as const,\n\t\t\t\tmessage: \"Usage: /memory [light|full|off|retry|forget <memoryId> [confirm]]\",\n\t\t\t};\n\t\t},\n\t});\n\n\treturn { listTool, forgetTool, enableTool, memoryCommand, promoteCommand, schedulerCapability };\n}\n"]}
@@ -1 +1 @@
1
- {"version":3,"file":"inference-service-main.d.ts","sourceRoot":"","sources":["../../src/memory/inference-service-main.ts"],"names":[],"mappings":"AAkBA,OAAO,KAAK,EAAE,6BAA6B,EAAE,MAAM,yBAAyB,CAAC;AAE7E,OAAO,KAAK,EAAE,4BAA4B,EAAE,MAAM,yBAAyB,CAAC;AAG5E,OAAO,EAEN,KAAK,wBAAwB,EAC7B,KAAK,uBAAuB,EAC5B,MAAM,uBAAuB,CAAC;AAG/B,0FAAyC;AACzC,eAAO,MAAM,wCAAwC,QAAgB,CAAC;AAEtE,MAAM,WAAW,6BAA6B;IAC7C,QAAQ,CAAC,QAAQ,EAAE,MAAM,CAAC;IAC1B,QAAQ,CAAC,IAAI,EAAE,OAAO,GAAG,MAAM,CAAC;IAChC,+GAAmE;IACnE,QAAQ,CAAC,IAAI,CAAC,EAAE,MAAM,CAAC;IACvB,yDAAmD;IACnD,QAAQ,CAAC,aAAa,CAAC,EAAE,MAAM,CAAC;IAChC,QAAQ,CAAC,MAAM,CAAC,EAAE,WAAW,CAAC;IAC9B,sFAAsC;IACtC,QAAQ,CAAC,OAAO,CAAC,EAAE,wBAAwB,CAAC;IAC5C,QAAQ,CAAC,gBAAgB,CAAC,EAAE;QAAE,QAAQ,CAAC,OAAO,CAAC,EAAE,MAAM,CAAC;QAAC,QAAQ,CAAC,YAAY,CAAC,EAAE,MAAM,CAAA;KAAE,CAAC;IAC1F,QAAQ,CAAC,SAAS,CAAC,EAAE;QACpB,QAAQ,CAAC,SAAS,CAAC,EAAE,6BAA6B,CAAC;QACnD,QAAQ,CAAC,QAAQ,CAAC,EAAE,4BAA4B,CAAC;KACjD,CAAC;IACF,QAAQ,CAAC,GAAG,CAAC,EAAE,CAAC,IAAI,EAAE,MAAM,KAAK,IAAI,CAAC;CACtC;AAED;;;GAGG;AACH,wBAAsB,yBAAyB,CAC9C,OAAO,EAAE,6BAA6B,GACpC,OAAO,CAAC,uBAAuB,CAAC,CAoElC","sourcesContent":["/**\n * 记忆共享推理服务 — 服务进程主函数 (记忆共享推理服务设计 §3.1/§3.6, Slice B1)。\n *\n * `agent-forge --memory-inference` 进程入口的执行体: 装配真实推理后端\n * (transformers embedding + reranker + LanceDB 向量索引 — 与会话进程本地装配\n * 完全同一组构件与模型缓存路径, 设计 §3.6「下载与装配只发生一次」的装配侧),\n * 然后以固定默认端口常驻服务 (设计 §3.5: 空闲自杀、信号优雅停机)。\n *\n * consent 链完整性 (设计 §3.6): 服务进程**不做 consent 判定、永不自主下载** —\n * 拉起方 (B2 接线: 会话侧在用户同意、模型经预热下载在位后) 才 spawn 本进程;\n * 启动时模型文件缺失即结构化退出 (拉起方把它当服务不可用, 按裁决 A 呈现)。\n * 下载只发生在会话进程的既有预热流程 (memory_enable download), 行为不变。\n *\n * 测试缝: `backend` 直接注入完整后端 (跳过真实装配); `loadImpls`/`moduleEntryPaths`\n * 透传给真实装配 (与 capability 的 enabling 缝同构)。\n */\nimport { join } from \"node:path\";\nimport { VECTOR_DB_DIRNAME, VECTOR_MODEL_CACHE_DIRNAME, VECTOR_RERANKER_MODEL_ID } from \"../capability.ts\";\nimport type { TransformersEmbeddingLoadImpl } from \"./embedding-provider.ts\";\nimport { createTransformersEmbeddingProvider } from \"./embedding-provider.ts\";\nimport type { TransformersRerankerLoadImpl } from \"./embedding-reranker.ts\";\nimport { createTransformersReranker } from \"./embedding-reranker.ts\";\nimport { MODEL_MIN_WEIGHT_BYTES, modelFilesCheck, RERANKER_MIN_WEIGHT_BYTES, vectorPresetForMode } from \"./enabling.ts\";\nimport {\n\tcreateMemoryInferenceServer,\n\ttype MemoryInferenceBackendV1,\n\ttype MemoryInferenceServerV1,\n} from \"./inference-server.ts\";\nimport { createMemoryVectorIndex } from \"./vector-index.ts\";\n\n/** 服务空闲自杀阈值出厂默认 (设计 §3.5; 登记产品默认值清单)。 */\nexport const MEMORY_INFERENCE_SERVICE_IDLE_TIMEOUT_MS = 5 * 60 * 1000;\n\nexport interface MemoryInferenceServiceOptions {\n\treadonly agentDir: string;\n\treadonly mode: \"light\" | \"full\";\n\t/** 缺省固定默认端口 (单例互斥); env 覆盖由 createMemoryInferenceServer 端口参数承担。 */\n\treadonly port?: number;\n\t/** 缺省 MEMORY_INFERENCE_SERVICE_IDLE_TIMEOUT_MS。 */\n\treadonly idleTimeoutMs?: number;\n\treadonly signal?: AbortSignal;\n\t/** 测试缝: 直接给定后端, 跳过真实装配 (模型检查一并跳过)。 */\n\treadonly backend?: MemoryInferenceBackendV1;\n\treadonly moduleEntryPaths?: { readonly lancedb?: string; readonly transformers?: string };\n\treadonly loadImpls?: {\n\t\treadonly embedding?: TransformersEmbeddingLoadImpl;\n\t\treadonly reranker?: TransformersRerankerLoadImpl;\n\t};\n\treadonly log?: (line: string) => void;\n}\n\n/**\n * 装配并启动共享推理服务。真实装配路径的失败 (模型缺失) 抛结构化错误 —\n * 进程入口将其转为一行 stderr + 非零退出码; 已监听后返回 server 句柄。\n */\nexport async function runMemoryInferenceService(\n\toptions: MemoryInferenceServiceOptions,\n): Promise<MemoryInferenceServerV1> {\n\tconst log = options.log ?? (() => {});\n\tconst preset = vectorPresetForMode(options.mode);\n\tconst modelCacheDir = join(options.agentDir, \"memory\", VECTOR_MODEL_CACHE_DIRNAME);\n\n\tlet backend = options.backend;\n\tif (backend === undefined) {\n\t\t// consent 链守门: 模型缺失即退出, 服务进程绝不触发下载 (拉起方保证\n\t\t// \"已同意且已下载\"; 缺失说明拉起时机错误或缓存被清)。\n\t\tconst missingEmbedding = modelFilesCheck(\n\t\t\tmodelCacheDir,\n\t\t\tpreset.embeddingModelId,\n\t\t\tMODEL_MIN_WEIGHT_BYTES[options.mode],\n\t\t);\n\t\tconst missingReranker = modelFilesCheck(modelCacheDir, VECTOR_RERANKER_MODEL_ID, RERANKER_MIN_WEIGHT_BYTES);\n\t\tconst missing = missingEmbedding ?? missingReranker;\n\t\tif (missing !== undefined) {\n\t\t\tthrow new Error(\n\t\t\t\t`memory inference service refuses to start: ${missing} — the service never downloads models itself; ` +\n\t\t\t\t\t\"the spawner must start it after the session-side preheat has the models in place\",\n\t\t\t);\n\t\t}\n\t\t// remoteHosts 不传 (provider 缺省序列): 服务的前置条件是模型已在位,\n\t\t// 加载命中本地缓存、远端不可达也不影响; 显式空数组会被 provider 当\n\t\t// \"零镜像可用\", 首次加载直接报 no remote hosts (2026-10-06 live E2E)。\n\t\tconst embedProvider = createTransformersEmbeddingProvider({\n\t\t\tmodelId: preset.embeddingModelId,\n\t\t\tdimensions: preset.dimensions,\n\t\t\tcacheDir: modelCacheDir,\n\t\t\t...(options.moduleEntryPaths?.transformers === undefined\n\t\t\t\t? {}\n\t\t\t\t: { moduleEntryPath: options.moduleEntryPaths.transformers }),\n\t\t\t...(options.loadImpls?.embedding === undefined ? {} : { loadImpl: options.loadImpls.embedding }),\n\t\t});\n\t\tconst reranker = createTransformersReranker({\n\t\t\tmodelId: VECTOR_RERANKER_MODEL_ID,\n\t\t\tcacheDir: modelCacheDir,\n\t\t\t...(options.moduleEntryPaths?.transformers === undefined\n\t\t\t\t? {}\n\t\t\t\t: { moduleEntryPath: options.moduleEntryPaths.transformers }),\n\t\t\t...(options.loadImpls?.reranker === undefined ? {} : { loadImpl: options.loadImpls.reranker }),\n\t\t});\n\t\tconst vectorIndex = await createMemoryVectorIndex({\n\t\t\tdbPath: join(options.agentDir, \"memory\", VECTOR_DB_DIRNAME),\n\t\t\tdimensions: preset.dimensions,\n\t\t\t...(options.moduleEntryPaths?.lancedb === undefined\n\t\t\t\t? {}\n\t\t\t\t: { moduleEntryPath: options.moduleEntryPaths.lancedb }),\n\t\t});\n\t\tbackend = { embedProvider, reranker, vectorIndex };\n\t}\n\n\tconst server = await createMemoryInferenceServer({\n\t\tbackend,\n\t\t// agentDir 身份上报 (2026-10-06 串线修复): 服务进程的 agent-dir 由 spawn\n\t\t// env 解析而来, 经 hello 回报 — 会话装配据此拒绝跨 agent-dir 串线。\n\t\tagentDir: options.agentDir,\n\t\t...(options.port === undefined ? {} : { port: options.port }),\n\t\tidleTimeoutMs: options.idleTimeoutMs ?? MEMORY_INFERENCE_SERVICE_IDLE_TIMEOUT_MS,\n\t\t...(options.signal === undefined ? {} : { signal: options.signal }),\n\t\tonError: (error, context) => {\n\t\t\tlog(`memory-inference: ${context}: ${error.message}`);\n\t\t},\n\t});\n\tlog(\n\t\t`memory-inference: serving on ${server.host}:${server.port} (mode=${options.mode}, idle-exit=${options.idleTimeoutMs ?? MEMORY_INFERENCE_SERVICE_IDLE_TIMEOUT_MS}ms)`,\n\t);\n\treturn server;\n}\n"]}
1
+ {"version":3,"file":"inference-service-main.d.ts","sourceRoot":"","sources":["../../src/memory/inference-service-main.ts"],"names":[],"mappings":"AAkBA,OAAO,KAAK,EAAE,6BAA6B,EAAE,MAAM,yBAAyB,CAAC;AAE7E,OAAO,KAAK,EAAE,4BAA4B,EAAE,MAAM,yBAAyB,CAAC;AAG5E,OAAO,EAEN,KAAK,wBAAwB,EAC7B,KAAK,uBAAuB,EAC5B,MAAM,uBAAuB,CAAC;AAG/B,0FAAyC;AACzC,eAAO,MAAM,wCAAwC,QAAgB,CAAC;AAEtE,MAAM,WAAW,6BAA6B;IAC7C,QAAQ,CAAC,QAAQ,EAAE,MAAM,CAAC;IAC1B,QAAQ,CAAC,IAAI,EAAE,OAAO,GAAG,MAAM,CAAC;IAChC,+GAAmE;IACnE,QAAQ,CAAC,IAAI,CAAC,EAAE,MAAM,CAAC;IACvB,yDAAmD;IACnD,QAAQ,CAAC,aAAa,CAAC,EAAE,MAAM,CAAC;IAChC,QAAQ,CAAC,MAAM,CAAC,EAAE,WAAW,CAAC;IAC9B,sFAAsC;IACtC,QAAQ,CAAC,OAAO,CAAC,EAAE,wBAAwB,CAAC;IAC5C,QAAQ,CAAC,gBAAgB,CAAC,EAAE;QAAE,QAAQ,CAAC,OAAO,CAAC,EAAE,MAAM,CAAC;QAAC,QAAQ,CAAC,YAAY,CAAC,EAAE,MAAM,CAAA;KAAE,CAAC;IAC1F,QAAQ,CAAC,SAAS,CAAC,EAAE;QACpB,QAAQ,CAAC,SAAS,CAAC,EAAE,6BAA6B,CAAC;QACnD,QAAQ,CAAC,QAAQ,CAAC,EAAE,4BAA4B,CAAC;KACjD,CAAC;IACF,QAAQ,CAAC,GAAG,CAAC,EAAE,CAAC,IAAI,EAAE,MAAM,KAAK,IAAI,CAAC;CACtC;AAED;;;GAGG;AACH,wBAAsB,yBAAyB,CAC9C,OAAO,EAAE,6BAA6B,GACpC,OAAO,CAAC,uBAAuB,CAAC,CA2ElC","sourcesContent":["/**\n * 记忆共享推理服务 — 服务进程主函数 (记忆共享推理服务设计 §3.1/§3.6, Slice B1)。\n *\n * `agent-forge --memory-inference` 进程入口的执行体: 装配真实推理后端\n * (transformers embedding + reranker + LanceDB 向量索引 — 与会话进程本地装配\n * 完全同一组构件与模型缓存路径, 设计 §3.6「下载与装配只发生一次」的装配侧),\n * 然后以固定默认端口常驻服务 (设计 §3.5: 空闲自杀、信号优雅停机)。\n *\n * consent 链完整性 (设计 §3.6): 服务进程**不做 consent 判定、永不自主下载** —\n * 拉起方 (B2 接线: 会话侧在用户同意、模型经预热下载在位后) 才 spawn 本进程;\n * 启动时模型文件缺失即结构化退出 (拉起方把它当服务不可用, 按裁决 A 呈现)。\n * 下载只发生在会话进程的既有预热流程 (memory_enable download), 行为不变。\n *\n * 测试缝: `backend` 直接注入完整后端 (跳过真实装配); `loadImpls`/`moduleEntryPaths`\n * 透传给真实装配 (与 capability 的 enabling 缝同构)。\n */\nimport { join } from \"node:path\";\nimport { VECTOR_DB_DIRNAME, VECTOR_MODEL_CACHE_DIRNAME, VECTOR_RERANKER_MODEL_ID } from \"../capability.ts\";\nimport type { TransformersEmbeddingLoadImpl } from \"./embedding-provider.ts\";\nimport { createTransformersEmbeddingProvider } from \"./embedding-provider.ts\";\nimport type { TransformersRerankerLoadImpl } from \"./embedding-reranker.ts\";\nimport { createTransformersReranker } from \"./embedding-reranker.ts\";\nimport { MODEL_MIN_WEIGHT_BYTES, modelFilesCheck, RERANKER_MIN_WEIGHT_BYTES, vectorPresetForMode } from \"./enabling.ts\";\nimport {\n\tcreateMemoryInferenceServer,\n\ttype MemoryInferenceBackendV1,\n\ttype MemoryInferenceServerV1,\n} from \"./inference-server.ts\";\nimport { createMemoryVectorIndex } from \"./vector-index.ts\";\n\n/** 服务空闲自杀阈值出厂默认 (设计 §3.5; 登记产品默认值清单)。 */\nexport const MEMORY_INFERENCE_SERVICE_IDLE_TIMEOUT_MS = 5 * 60 * 1000;\n\nexport interface MemoryInferenceServiceOptions {\n\treadonly agentDir: string;\n\treadonly mode: \"light\" | \"full\";\n\t/** 缺省固定默认端口 (单例互斥); env 覆盖由 createMemoryInferenceServer 端口参数承担。 */\n\treadonly port?: number;\n\t/** 缺省 MEMORY_INFERENCE_SERVICE_IDLE_TIMEOUT_MS。 */\n\treadonly idleTimeoutMs?: number;\n\treadonly signal?: AbortSignal;\n\t/** 测试缝: 直接给定后端, 跳过真实装配 (模型检查一并跳过)。 */\n\treadonly backend?: MemoryInferenceBackendV1;\n\treadonly moduleEntryPaths?: { readonly lancedb?: string; readonly transformers?: string };\n\treadonly loadImpls?: {\n\t\treadonly embedding?: TransformersEmbeddingLoadImpl;\n\t\treadonly reranker?: TransformersRerankerLoadImpl;\n\t};\n\treadonly log?: (line: string) => void;\n}\n\n/**\n * 装配并启动共享推理服务。真实装配路径的失败 (模型缺失) 抛结构化错误 —\n * 进程入口将其转为一行 stderr + 非零退出码; 已监听后返回 server 句柄。\n */\nexport async function runMemoryInferenceService(\n\toptions: MemoryInferenceServiceOptions,\n): Promise<MemoryInferenceServerV1> {\n\tconst log = options.log ?? (() => {});\n\tconst preset = vectorPresetForMode(options.mode);\n\tconst modelCacheDir = join(options.agentDir, \"memory\", VECTOR_MODEL_CACHE_DIRNAME);\n\n\tlet backend = options.backend;\n\tif (backend === undefined) {\n\t\t// consent 链守门: 模型缺失即退出, 服务进程绝不触发下载 (拉起方保证\n\t\t// \"已同意且已下载\"; 缺失说明拉起时机错误或缓存被清)。\n\t\tconst missingEmbedding = modelFilesCheck(\n\t\t\tmodelCacheDir,\n\t\t\tpreset.embeddingModelId,\n\t\t\tMODEL_MIN_WEIGHT_BYTES[options.mode],\n\t\t);\n\t\tconst missingReranker = modelFilesCheck(modelCacheDir, VECTOR_RERANKER_MODEL_ID, RERANKER_MIN_WEIGHT_BYTES);\n\t\tconst missing = missingEmbedding ?? missingReranker;\n\t\tif (missing !== undefined) {\n\t\t\tthrow new Error(\n\t\t\t\t`memory inference service refuses to start: ${missing} — the service never downloads models itself; ` +\n\t\t\t\t\t\"the spawner must start it after the session-side preheat has the models in place\",\n\t\t\t);\n\t\t}\n\t\t// remoteHosts 不传 (provider 缺省序列): 服务的前置条件是模型已在位,\n\t\t// 加载命中本地缓存、远端不可达也不影响; 显式空数组会被 provider 当\n\t\t// \"零镜像可用\", 首次加载直接报 no remote hosts (2026-10-06 live E2E)。\n\t\tconst embedProvider = createTransformersEmbeddingProvider({\n\t\t\tmodelId: preset.embeddingModelId,\n\t\t\tdimensions: preset.dimensions,\n\t\t\tcacheDir: modelCacheDir,\n\t\t\t...(options.moduleEntryPaths?.transformers === undefined\n\t\t\t\t? {}\n\t\t\t\t: { moduleEntryPath: options.moduleEntryPaths.transformers }),\n\t\t\t...(options.loadImpls?.embedding === undefined ? {} : { loadImpl: options.loadImpls.embedding }),\n\t\t});\n\t\tconst reranker = createTransformersReranker({\n\t\t\tmodelId: VECTOR_RERANKER_MODEL_ID,\n\t\t\tcacheDir: modelCacheDir,\n\t\t\t...(options.moduleEntryPaths?.transformers === undefined\n\t\t\t\t? {}\n\t\t\t\t: { moduleEntryPath: options.moduleEntryPaths.transformers }),\n\t\t\t...(options.loadImpls?.reranker === undefined ? {} : { loadImpl: options.loadImpls.reranker }),\n\t\t});\n\t\tconst vectorIndex = await createMemoryVectorIndex({\n\t\t\tdbPath: join(options.agentDir, \"memory\", VECTOR_DB_DIRNAME),\n\t\t\tdimensions: preset.dimensions,\n\t\t\t...(options.moduleEntryPaths?.lancedb === undefined\n\t\t\t\t? {}\n\t\t\t\t: { moduleEntryPath: options.moduleEntryPaths.lancedb }),\n\t\t\t// P5 残留守卫 (与本地通道同语义): 拒绝覆盖外来同 id 行, 服务进程日志留痕。\n\t\t\tonCollision: (collision) => {\n\t\t\t\tlog(\n\t\t\t\t\t\"memory-inference: vector projection refused to overwrite a row whose memoryId belongs to a different atom (cross-domain id collision): \" +\n\t\t\t\t\t\tcollision.memoryId,\n\t\t\t\t);\n\t\t\t},\n\t\t});\n\t\tbackend = { embedProvider, reranker, vectorIndex };\n\t}\n\n\tconst server = await createMemoryInferenceServer({\n\t\tbackend,\n\t\t// agentDir 身份上报 (2026-10-06 串线修复): 服务进程的 agent-dir 由 spawn\n\t\t// env 解析而来, 经 hello 回报 — 会话装配据此拒绝跨 agent-dir 串线。\n\t\tagentDir: options.agentDir,\n\t\t...(options.port === undefined ? {} : { port: options.port }),\n\t\tidleTimeoutMs: options.idleTimeoutMs ?? MEMORY_INFERENCE_SERVICE_IDLE_TIMEOUT_MS,\n\t\t...(options.signal === undefined ? {} : { signal: options.signal }),\n\t\tonError: (error, context) => {\n\t\t\tlog(`memory-inference: ${context}: ${error.message}`);\n\t\t},\n\t});\n\tlog(\n\t\t`memory-inference: serving on ${server.host}:${server.port} (mode=${options.mode}, idle-exit=${options.idleTimeoutMs ?? MEMORY_INFERENCE_SERVICE_IDLE_TIMEOUT_MS}ms)`,\n\t);\n\treturn server;\n}\n"]}
@@ -68,6 +68,11 @@ export async function runMemoryInferenceService(options) {
68
68
  ...(options.moduleEntryPaths?.lancedb === undefined
69
69
  ? {}
70
70
  : { moduleEntryPath: options.moduleEntryPaths.lancedb }),
71
+ // P5 残留守卫 (与本地通道同语义): 拒绝覆盖外来同 id 行, 服务进程日志留痕。
72
+ onCollision: (collision) => {
73
+ log("memory-inference: vector projection refused to overwrite a row whose memoryId belongs to a different atom (cross-domain id collision): " +
74
+ collision.memoryId);
75
+ },
71
76
  });
72
77
  backend = { embedProvider, reranker, vectorIndex };
73
78
  }
@@ -1 +1 @@
1
- {"version":3,"file":"inference-service-main.js","sourceRoot":"","sources":["../../src/memory/inference-service-main.ts"],"names":[],"mappings":"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","sourcesContent":["/**\n * 记忆共享推理服务 — 服务进程主函数 (记忆共享推理服务设计 §3.1/§3.6, Slice B1)。\n *\n * `agent-forge --memory-inference` 进程入口的执行体: 装配真实推理后端\n * (transformers embedding + reranker + LanceDB 向量索引 — 与会话进程本地装配\n * 完全同一组构件与模型缓存路径, 设计 §3.6「下载与装配只发生一次」的装配侧),\n * 然后以固定默认端口常驻服务 (设计 §3.5: 空闲自杀、信号优雅停机)。\n *\n * consent 链完整性 (设计 §3.6): 服务进程**不做 consent 判定、永不自主下载** —\n * 拉起方 (B2 接线: 会话侧在用户同意、模型经预热下载在位后) 才 spawn 本进程;\n * 启动时模型文件缺失即结构化退出 (拉起方把它当服务不可用, 按裁决 A 呈现)。\n * 下载只发生在会话进程的既有预热流程 (memory_enable download), 行为不变。\n *\n * 测试缝: `backend` 直接注入完整后端 (跳过真实装配); `loadImpls`/`moduleEntryPaths`\n * 透传给真实装配 (与 capability 的 enabling 缝同构)。\n */\nimport { join } from \"node:path\";\nimport { VECTOR_DB_DIRNAME, VECTOR_MODEL_CACHE_DIRNAME, VECTOR_RERANKER_MODEL_ID } from \"../capability.ts\";\nimport type { TransformersEmbeddingLoadImpl } from \"./embedding-provider.ts\";\nimport { createTransformersEmbeddingProvider } from \"./embedding-provider.ts\";\nimport type { TransformersRerankerLoadImpl } from \"./embedding-reranker.ts\";\nimport { createTransformersReranker } from \"./embedding-reranker.ts\";\nimport { MODEL_MIN_WEIGHT_BYTES, modelFilesCheck, RERANKER_MIN_WEIGHT_BYTES, vectorPresetForMode } from \"./enabling.ts\";\nimport {\n\tcreateMemoryInferenceServer,\n\ttype MemoryInferenceBackendV1,\n\ttype MemoryInferenceServerV1,\n} from \"./inference-server.ts\";\nimport { createMemoryVectorIndex } from \"./vector-index.ts\";\n\n/** 服务空闲自杀阈值出厂默认 (设计 §3.5; 登记产品默认值清单)。 */\nexport const MEMORY_INFERENCE_SERVICE_IDLE_TIMEOUT_MS = 5 * 60 * 1000;\n\nexport interface MemoryInferenceServiceOptions {\n\treadonly agentDir: string;\n\treadonly mode: \"light\" | \"full\";\n\t/** 缺省固定默认端口 (单例互斥); env 覆盖由 createMemoryInferenceServer 端口参数承担。 */\n\treadonly port?: number;\n\t/** 缺省 MEMORY_INFERENCE_SERVICE_IDLE_TIMEOUT_MS。 */\n\treadonly idleTimeoutMs?: number;\n\treadonly signal?: AbortSignal;\n\t/** 测试缝: 直接给定后端, 跳过真实装配 (模型检查一并跳过)。 */\n\treadonly backend?: MemoryInferenceBackendV1;\n\treadonly moduleEntryPaths?: { readonly lancedb?: string; readonly transformers?: string };\n\treadonly loadImpls?: {\n\t\treadonly embedding?: TransformersEmbeddingLoadImpl;\n\t\treadonly reranker?: TransformersRerankerLoadImpl;\n\t};\n\treadonly log?: (line: string) => void;\n}\n\n/**\n * 装配并启动共享推理服务。真实装配路径的失败 (模型缺失) 抛结构化错误 —\n * 进程入口将其转为一行 stderr + 非零退出码; 已监听后返回 server 句柄。\n */\nexport async function runMemoryInferenceService(\n\toptions: MemoryInferenceServiceOptions,\n): Promise<MemoryInferenceServerV1> {\n\tconst log = options.log ?? (() => {});\n\tconst preset = vectorPresetForMode(options.mode);\n\tconst modelCacheDir = join(options.agentDir, \"memory\", VECTOR_MODEL_CACHE_DIRNAME);\n\n\tlet backend = options.backend;\n\tif (backend === undefined) {\n\t\t// consent 链守门: 模型缺失即退出, 服务进程绝不触发下载 (拉起方保证\n\t\t// \"已同意且已下载\"; 缺失说明拉起时机错误或缓存被清)。\n\t\tconst missingEmbedding = modelFilesCheck(\n\t\t\tmodelCacheDir,\n\t\t\tpreset.embeddingModelId,\n\t\t\tMODEL_MIN_WEIGHT_BYTES[options.mode],\n\t\t);\n\t\tconst missingReranker = modelFilesCheck(modelCacheDir, VECTOR_RERANKER_MODEL_ID, RERANKER_MIN_WEIGHT_BYTES);\n\t\tconst missing = missingEmbedding ?? missingReranker;\n\t\tif (missing !== undefined) {\n\t\t\tthrow new Error(\n\t\t\t\t`memory inference service refuses to start: ${missing} — the service never downloads models itself; ` +\n\t\t\t\t\t\"the spawner must start it after the session-side preheat has the models in place\",\n\t\t\t);\n\t\t}\n\t\t// remoteHosts 不传 (provider 缺省序列): 服务的前置条件是模型已在位,\n\t\t// 加载命中本地缓存、远端不可达也不影响; 显式空数组会被 provider 当\n\t\t// \"零镜像可用\", 首次加载直接报 no remote hosts (2026-10-06 live E2E)。\n\t\tconst embedProvider = createTransformersEmbeddingProvider({\n\t\t\tmodelId: preset.embeddingModelId,\n\t\t\tdimensions: preset.dimensions,\n\t\t\tcacheDir: modelCacheDir,\n\t\t\t...(options.moduleEntryPaths?.transformers === undefined\n\t\t\t\t? {}\n\t\t\t\t: { moduleEntryPath: options.moduleEntryPaths.transformers }),\n\t\t\t...(options.loadImpls?.embedding === undefined ? {} : { loadImpl: options.loadImpls.embedding }),\n\t\t});\n\t\tconst reranker = createTransformersReranker({\n\t\t\tmodelId: VECTOR_RERANKER_MODEL_ID,\n\t\t\tcacheDir: modelCacheDir,\n\t\t\t...(options.moduleEntryPaths?.transformers === undefined\n\t\t\t\t? {}\n\t\t\t\t: { moduleEntryPath: options.moduleEntryPaths.transformers }),\n\t\t\t...(options.loadImpls?.reranker === undefined ? {} : { loadImpl: options.loadImpls.reranker }),\n\t\t});\n\t\tconst vectorIndex = await createMemoryVectorIndex({\n\t\t\tdbPath: join(options.agentDir, \"memory\", VECTOR_DB_DIRNAME),\n\t\t\tdimensions: preset.dimensions,\n\t\t\t...(options.moduleEntryPaths?.lancedb === undefined\n\t\t\t\t? {}\n\t\t\t\t: { moduleEntryPath: options.moduleEntryPaths.lancedb }),\n\t\t});\n\t\tbackend = { embedProvider, reranker, vectorIndex };\n\t}\n\n\tconst server = await createMemoryInferenceServer({\n\t\tbackend,\n\t\t// agentDir 身份上报 (2026-10-06 串线修复): 服务进程的 agent-dir 由 spawn\n\t\t// env 解析而来, 经 hello 回报 — 会话装配据此拒绝跨 agent-dir 串线。\n\t\tagentDir: options.agentDir,\n\t\t...(options.port === undefined ? {} : { port: options.port }),\n\t\tidleTimeoutMs: options.idleTimeoutMs ?? MEMORY_INFERENCE_SERVICE_IDLE_TIMEOUT_MS,\n\t\t...(options.signal === undefined ? {} : { signal: options.signal }),\n\t\tonError: (error, context) => {\n\t\t\tlog(`memory-inference: ${context}: ${error.message}`);\n\t\t},\n\t});\n\tlog(\n\t\t`memory-inference: serving on ${server.host}:${server.port} (mode=${options.mode}, idle-exit=${options.idleTimeoutMs ?? MEMORY_INFERENCE_SERVICE_IDLE_TIMEOUT_MS}ms)`,\n\t);\n\treturn server;\n}\n"]}
1
+ {"version":3,"file":"inference-service-main.js","sourceRoot":"","sources":["../../src/memory/inference-service-main.ts"],"names":[],"mappings":"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","sourcesContent":["/**\n * 记忆共享推理服务 — 服务进程主函数 (记忆共享推理服务设计 §3.1/§3.6, Slice B1)。\n *\n * `agent-forge --memory-inference` 进程入口的执行体: 装配真实推理后端\n * (transformers embedding + reranker + LanceDB 向量索引 — 与会话进程本地装配\n * 完全同一组构件与模型缓存路径, 设计 §3.6「下载与装配只发生一次」的装配侧),\n * 然后以固定默认端口常驻服务 (设计 §3.5: 空闲自杀、信号优雅停机)。\n *\n * consent 链完整性 (设计 §3.6): 服务进程**不做 consent 判定、永不自主下载** —\n * 拉起方 (B2 接线: 会话侧在用户同意、模型经预热下载在位后) 才 spawn 本进程;\n * 启动时模型文件缺失即结构化退出 (拉起方把它当服务不可用, 按裁决 A 呈现)。\n * 下载只发生在会话进程的既有预热流程 (memory_enable download), 行为不变。\n *\n * 测试缝: `backend` 直接注入完整后端 (跳过真实装配); `loadImpls`/`moduleEntryPaths`\n * 透传给真实装配 (与 capability 的 enabling 缝同构)。\n */\nimport { join } from \"node:path\";\nimport { VECTOR_DB_DIRNAME, VECTOR_MODEL_CACHE_DIRNAME, VECTOR_RERANKER_MODEL_ID } from \"../capability.ts\";\nimport type { TransformersEmbeddingLoadImpl } from \"./embedding-provider.ts\";\nimport { createTransformersEmbeddingProvider } from \"./embedding-provider.ts\";\nimport type { TransformersRerankerLoadImpl } from \"./embedding-reranker.ts\";\nimport { createTransformersReranker } from \"./embedding-reranker.ts\";\nimport { MODEL_MIN_WEIGHT_BYTES, modelFilesCheck, RERANKER_MIN_WEIGHT_BYTES, vectorPresetForMode } from \"./enabling.ts\";\nimport {\n\tcreateMemoryInferenceServer,\n\ttype MemoryInferenceBackendV1,\n\ttype MemoryInferenceServerV1,\n} from \"./inference-server.ts\";\nimport { createMemoryVectorIndex } from \"./vector-index.ts\";\n\n/** 服务空闲自杀阈值出厂默认 (设计 §3.5; 登记产品默认值清单)。 */\nexport const MEMORY_INFERENCE_SERVICE_IDLE_TIMEOUT_MS = 5 * 60 * 1000;\n\nexport interface MemoryInferenceServiceOptions {\n\treadonly agentDir: string;\n\treadonly mode: \"light\" | \"full\";\n\t/** 缺省固定默认端口 (单例互斥); env 覆盖由 createMemoryInferenceServer 端口参数承担。 */\n\treadonly port?: number;\n\t/** 缺省 MEMORY_INFERENCE_SERVICE_IDLE_TIMEOUT_MS。 */\n\treadonly idleTimeoutMs?: number;\n\treadonly signal?: AbortSignal;\n\t/** 测试缝: 直接给定后端, 跳过真实装配 (模型检查一并跳过)。 */\n\treadonly backend?: MemoryInferenceBackendV1;\n\treadonly moduleEntryPaths?: { readonly lancedb?: string; readonly transformers?: string };\n\treadonly loadImpls?: {\n\t\treadonly embedding?: TransformersEmbeddingLoadImpl;\n\t\treadonly reranker?: TransformersRerankerLoadImpl;\n\t};\n\treadonly log?: (line: string) => void;\n}\n\n/**\n * 装配并启动共享推理服务。真实装配路径的失败 (模型缺失) 抛结构化错误 —\n * 进程入口将其转为一行 stderr + 非零退出码; 已监听后返回 server 句柄。\n */\nexport async function runMemoryInferenceService(\n\toptions: MemoryInferenceServiceOptions,\n): Promise<MemoryInferenceServerV1> {\n\tconst log = options.log ?? (() => {});\n\tconst preset = vectorPresetForMode(options.mode);\n\tconst modelCacheDir = join(options.agentDir, \"memory\", VECTOR_MODEL_CACHE_DIRNAME);\n\n\tlet backend = options.backend;\n\tif (backend === undefined) {\n\t\t// consent 链守门: 模型缺失即退出, 服务进程绝不触发下载 (拉起方保证\n\t\t// \"已同意且已下载\"; 缺失说明拉起时机错误或缓存被清)。\n\t\tconst missingEmbedding = modelFilesCheck(\n\t\t\tmodelCacheDir,\n\t\t\tpreset.embeddingModelId,\n\t\t\tMODEL_MIN_WEIGHT_BYTES[options.mode],\n\t\t);\n\t\tconst missingReranker = modelFilesCheck(modelCacheDir, VECTOR_RERANKER_MODEL_ID, RERANKER_MIN_WEIGHT_BYTES);\n\t\tconst missing = missingEmbedding ?? missingReranker;\n\t\tif (missing !== undefined) {\n\t\t\tthrow new Error(\n\t\t\t\t`memory inference service refuses to start: ${missing} — the service never downloads models itself; ` +\n\t\t\t\t\t\"the spawner must start it after the session-side preheat has the models in place\",\n\t\t\t);\n\t\t}\n\t\t// remoteHosts 不传 (provider 缺省序列): 服务的前置条件是模型已在位,\n\t\t// 加载命中本地缓存、远端不可达也不影响; 显式空数组会被 provider 当\n\t\t// \"零镜像可用\", 首次加载直接报 no remote hosts (2026-10-06 live E2E)。\n\t\tconst embedProvider = createTransformersEmbeddingProvider({\n\t\t\tmodelId: preset.embeddingModelId,\n\t\t\tdimensions: preset.dimensions,\n\t\t\tcacheDir: modelCacheDir,\n\t\t\t...(options.moduleEntryPaths?.transformers === undefined\n\t\t\t\t? {}\n\t\t\t\t: { moduleEntryPath: options.moduleEntryPaths.transformers }),\n\t\t\t...(options.loadImpls?.embedding === undefined ? {} : { loadImpl: options.loadImpls.embedding }),\n\t\t});\n\t\tconst reranker = createTransformersReranker({\n\t\t\tmodelId: VECTOR_RERANKER_MODEL_ID,\n\t\t\tcacheDir: modelCacheDir,\n\t\t\t...(options.moduleEntryPaths?.transformers === undefined\n\t\t\t\t? {}\n\t\t\t\t: { moduleEntryPath: options.moduleEntryPaths.transformers }),\n\t\t\t...(options.loadImpls?.reranker === undefined ? {} : { loadImpl: options.loadImpls.reranker }),\n\t\t});\n\t\tconst vectorIndex = await createMemoryVectorIndex({\n\t\t\tdbPath: join(options.agentDir, \"memory\", VECTOR_DB_DIRNAME),\n\t\t\tdimensions: preset.dimensions,\n\t\t\t...(options.moduleEntryPaths?.lancedb === undefined\n\t\t\t\t? {}\n\t\t\t\t: { moduleEntryPath: options.moduleEntryPaths.lancedb }),\n\t\t\t// P5 残留守卫 (与本地通道同语义): 拒绝覆盖外来同 id 行, 服务进程日志留痕。\n\t\t\tonCollision: (collision) => {\n\t\t\t\tlog(\n\t\t\t\t\t\"memory-inference: vector projection refused to overwrite a row whose memoryId belongs to a different atom (cross-domain id collision): \" +\n\t\t\t\t\t\tcollision.memoryId,\n\t\t\t\t);\n\t\t\t},\n\t\t});\n\t\tbackend = { embedProvider, reranker, vectorIndex };\n\t}\n\n\tconst server = await createMemoryInferenceServer({\n\t\tbackend,\n\t\t// agentDir 身份上报 (2026-10-06 串线修复): 服务进程的 agent-dir 由 spawn\n\t\t// env 解析而来, 经 hello 回报 — 会话装配据此拒绝跨 agent-dir 串线。\n\t\tagentDir: options.agentDir,\n\t\t...(options.port === undefined ? {} : { port: options.port }),\n\t\tidleTimeoutMs: options.idleTimeoutMs ?? MEMORY_INFERENCE_SERVICE_IDLE_TIMEOUT_MS,\n\t\t...(options.signal === undefined ? {} : { signal: options.signal }),\n\t\tonError: (error, context) => {\n\t\t\tlog(`memory-inference: ${context}: ${error.message}`);\n\t\t},\n\t});\n\tlog(\n\t\t`memory-inference: serving on ${server.host}:${server.port} (mode=${options.mode}, idle-exit=${options.idleTimeoutMs ?? MEMORY_INFERENCE_SERVICE_IDLE_TIMEOUT_MS}ms)`,\n\t);\n\treturn server;\n}\n"]}
@@ -1 +1 @@
1
- {"version":3,"file":"recall-pipeline.d.ts","sourceRoot":"","sources":["../../src/memory/recall-pipeline.ts"],"names":[],"mappings":"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","sourcesContent":["/**\n * 召回管线(capability 拆分战役 slice 4):双通道查询、RRF 融合、egress 门、语义召回与召回工具。状态机经 vectorChannel 只读面消费, 降级经回调上报\n */\nimport type { CapabilityAPI, DisposableRegistration, JsonValue } from \"@agent-forge/plugin-sdk\";\nimport { MEMORY_RECALL_TOOL_NAME } from \"@agent-forge/plugin-sdk\";\nimport { Type } from \"typebox\";\nimport type { MemoryEgressFactInfoV1, MemoryEgressPolicyGateV1 } from \"./egress-policy.ts\";\nimport type { MemoryAtomV1, MemoryPreferenceEnvelopeV1, MemorySuiteFilterStatsV1 } from \"./foundation.ts\";\nimport type { DurableMemoryLedgerV1 } from \"./ledger.ts\";\nimport type { MemoryNetworkExpansionV1, MemoryNetworkV1 } from \"./memory-network.ts\";\nimport type { PreferenceDisambiguatorV1 } from \"./preference-disambiguator.ts\";\nimport type { PreferenceContextV1, PreferenceResolverV1 } from \"./preference-resolver.ts\";\nimport type { MemoryRecallPacketV1 } from \"./recall-packet.ts\";\nimport type { MemoryStoreV1 } from \"./store.ts\";\nimport { listDomainMemories, type SuiteMemoryDomainV1 } from \"./suite-memory.ts\";\nimport type { MemoryCapabilityStateV1, MemoryConsentAskV1, MemoryEnablingFailureV1 } from \"./vector-channel.ts\";\nimport type { MemoryVectorHitV1 } from \"./vector-index.ts\";\nimport type { ActiveVectorChannelV1 } from \"./vector-projection.ts\";\n\n/**\n * Memory tools guide(记忆 runtime 注入面,原 system-prompt.ts MEMORY_TOOL_GUIDE,\n * D-071 迁移):the model-facing hint that the persistent memory tools exist.\n * It rides memory_recall's promptGuidelines so the text lives with the plugin\n * that owns the tools (宪法 §3) and renders whenever the tool face is active.\n */\nexport const MEMORY_TOOLS_GUIDE =\n\t\"Memory: You have persistent memory tools (memory_write, memory_recall, memory_list, memory_forget). Use memory_recall only when past decisions, preferences, or project facts are actually relevant to the current question — not on every turn. Use memory_write only for facts the user explicitly stated or that a tool/test verified; never save your own inferences or guesses as facts, and declare the honest evidence class. memory_forget only files a forget request — deletion happens after the user confirms it.\";\n\nconst DEFAULT_RECALL_LIMIT = 5;\n\n/** Bounded supplementary relation references per recall (记忆网络接线). */\nconst MAX_RELATED_REFS = 20;\n\n/** FTS / KNN 通道各自取回深度 (定版管线: depth 50)。 */\nconst HYBRID_CHANNEL_DEPTH = 50;\n/** RRF 常数, 与 vector-index 组件 queryFts 打分口径一致。 */\nconst HYBRID_RRF_K = 60;\n/** 融合候选池上限 (定版管线: top20 → 精排 → top limit)。 */\nconst HYBRID_FUSION_POOL = 20;\n\ninterface MemoryFact {\n\treadonly memoryId: string;\n\treadonly statement: string;\n\treadonly confidence: number;\n}\n\n/**\n * 三通道 RRF 融合 (定版管线): `score(id) += 1/(k + rank + 1)` 对各通道排名累加,\n * 取 top {@link HYBRID_FUSION_POOL}。分数并列时以 memoryId 升序打破, 保证确定性。\n */\nfunction rrfFuseRankings(rankings: readonly (readonly string[])[]): readonly string[] {\n\tconst scores = new Map<string, number>();\n\tfor (const ranking of rankings) {\n\t\tfor (const [rank, memoryId] of ranking.entries()) {\n\t\t\tscores.set(memoryId, (scores.get(memoryId) ?? 0) + 1 / (HYBRID_RRF_K + rank + 1));\n\t\t}\n\t}\n\treturn [...scores.entries()]\n\t\t.sort((left, right) => right[1] - left[1] || (left[0] < right[0] ? -1 : 1))\n\t\t.map(([memoryId]) => memoryId)\n\t\t.slice(0, HYBRID_FUSION_POOL);\n}\n\n/** One unresolvably competing preference, surfaced verbatim instead of a silent pick. */\ninterface RecallConflictV1 {\n\treadonly subject: MemoryPreferenceEnvelopeV1[\"subject\"];\n\treadonly key: string;\n\treadonly candidates: readonly {\n\t\treadonly memoryId: string;\n\t\treadonly statement: string;\n\t\treadonly value: JsonValue;\n\t\treadonly evidenceClass: MemoryPreferenceEnvelopeV1[\"evidence\"][\"class\"];\n\t\treadonly confidence: number;\n\t}[];\n\treadonly explanation: readonly string[];\n}\n\nconst recallSchema = Type.Object({\n\tquery: Type.String({\n\t\tdescription:\n\t\t\t\"Natural-language query; recall fuses semantic (vector) and full-text (BM25) matching and reranks the result. An exact memoryId retrieves that memory directly\",\n\t}),\n\tlimit: Type.Optional(\n\t\tType.Integer({ description: \"Maximum number of memories to return (1-100)\", minimum: 1, maximum: 100 }),\n\t),\n\tcontext: Type.Optional(\n\t\tType.Object(\n\t\t\t{\n\t\t\t\tscenes: Type.Optional(Type.Array(Type.String())),\n\t\t\t\tprojects: Type.Optional(Type.Array(Type.String())),\n\t\t\t\ttasks: Type.Optional(Type.Array(Type.String())),\n\t\t\t},\n\t\t\t{\n\t\t\t\tdescription:\n\t\t\t\t\t\"Current scope context; when present, recalled preferences are resolved against it (out-of-scope preferences drop, competing values resolve by scope specificity or surface as explicit conflicts)\",\n\t\t\t},\n\t\t),\n\t),\n});\n\n/**\n * 召回管线对 capability 闭包的显式依赖视图 (slice 4): 逐项来自\n * createMemoryCapabilityHarness 的闭包捕获核实。状态机 (vectorChannelCtl) 与\n * 写侧投影 (vectorProjection) 归 capability 装配所有, 本模块经只读回调消费与\n * 上报, 不持有任何状态机变量; capability 模块级纯函数经 deps 注入避免双头定义。\n */\nexport interface MemoryRecallPipelineDepsV1 {\n\t/** capability 日志面 (api.logger): 通道降级/增强失败的结构化日志。 */\n\treadonly logger: CapabilityAPI[\"logger\"];\n\t/** 检索行 owner (capability.BUILTIN_MEMORY_OWNER)。 */\n\treadonly owner: string;\n\t/** 会话 id (harness sessionId): egress operationId 组成。 */\n\treadonly sessionId: string;\n\t/** canonical store (per-capability 单例): store 回查与增强段 atom 读取。 */\n\treadonly store: MemoryStoreV1;\n\t/** 域解析 (capability.resolveDomain): 每次 recall 调用时惰性解析 (B1 语义)。 */\n\treadonly resolveDomain: () => {\n\t\treadonly domain: SuiteMemoryDomainV1;\n\t\treadonly domainName: string;\n\t\treadonly ledger: DurableMemoryLedgerV1;\n\t};\n\t/** egress 门 (capability.egressPolicy): 与旧锚点 packet 同一 gate。 */\n\treadonly egressPolicy: MemoryEgressPolicyGateV1;\n\t/** context-scoped 偏好解析 (2.4c/2.4d2)。 */\n\treadonly preferenceResolver: PreferenceResolverV1;\n\t/** 未决偏好的确定性消歧 (2.4c/2.4d2)。 */\n\treadonly preferenceDisambiguator: PreferenceDisambiguatorV1;\n\t/** 关系扩散 (记忆网络接线): recall 种子的一跳扩展。 */\n\treadonly memoryNetwork: MemoryNetworkV1;\n\t// ---- vectorChannel 只读面 (slice 3b 的 vectorChannelCtl, 状态机归其所有) ----\n\t/** warming 守卫: 后台预热在途且未拒绝时 recall 零等待返回 unavailable。 */\n\treadonly warming: () => boolean;\n\t/** 惰性装配 (vectorEnsure 单飞语义原样保留): undefined = 通道不可用。 */\n\treadonly ensureChannel: () => Promise<ActiveVectorChannelV1 | undefined>;\n\t/** 征询拒绝标记 (declined): recall 按模式关闭对待。 */\n\treadonly declined: () => boolean;\n\t/** 待征询信息 (consent-required): recall 返回征询话术包。 */\n\treadonly consentAsk: () => MemoryConsentAskV1 | undefined;\n\t/** enabling 结构化失败 (原因 + 行动清单, 设计 §3.2)。 */\n\treadonly failure: () => MemoryEnablingFailureV1 | undefined;\n\t/** 通道原始状态 (未做 declined 投影): packet 的 state 字段来源。 */\n\treadonly rawState: () => MemoryCapabilityStateV1;\n\t/** 运行期故障的 degraded 转换回调 (状态转换语义归状态机)。 */\n\treadonly markDegraded: () => void;\n\t/** 全通成功的 degraded 自愈回调。 */\n\treadonly recoverDegraded: () => void;\n\t// ---- 写侧投影 (slice 3a 的 vectorProjection) ----\n\t/** 读侧 FIFO 等待 (await vectorQueue): 保写入→召回次序。 */\n\treadonly settleProjection: () => Promise<unknown>;\n\t// ---- capability 模块级纯函数 (留守方, 经 deps 注入避免双头定义) ----\n\treadonly memoryToolInput: (first: unknown, second: unknown) => Record<string, unknown>;\n\treadonly assertNonEmptyString: (value: unknown, label: string) => asserts value is string;\n\treadonly isPlainObject: (value: unknown) => value is Record<string, unknown>;\n\treadonly identityHash: (value: string) => string;\n\treadonly statementOf: (payload: unknown) => string;\n\treadonly unwrapEgressStatement: (statement: string) => string;\n\t/** 工具注册面 (api.registerTool): memory_recall 经同一公共通道注册。 */\n\treadonly registerTool: CapabilityAPI[\"registerTool\"];\n}\n\n/** 召回管线工厂产物: 工具注册项 + auto-recall 直连的执行闭包。 */\nexport interface MemoryRecallPipelineV1 {\n\t/** memory_recall 工具注册项 (capability registrations 收集)。 */\n\treadonly recallTool: DisposableRegistration;\n\t/** 召回执行闭包 (原 recallExecute): autoRecallBlock (留守, slice 7) 直连同一 execute。 */\n\treadonly recallExecute: (first: unknown, second?: unknown) => Promise<unknown>;\n}\n\nexport function createMemoryRecallPipeline(deps: MemoryRecallPipelineDepsV1): MemoryRecallPipelineV1 {\n\t/**\n\t * 语义召回管线 (2.4d6 S2, 设计 §4) — 唯一检索路径: embed(查询, light 加 BGE\n\t * 前缀) → KNN depth50 + FTS depth50 → RRF k=60 top20 → 精排 → store 回查\n\t * (owner/suite/scope/过期, 单一可见性路径, 不足 limit 不回填)。降级层级\n\t * (2026-10-05 裁决 A「宁漏勿错」, 每次降级一条结构化日志, 状态转 degraded\n\t * 可自愈): 精排失败 → RRF 序 + degraded 标记 (不闩锁, 下次重试); 向量失败\n\t * (embed/KNN) → FTS-only + degraded; FTS 失败 → KNN-only + degraded;\n\t * 双通道皆败 → unavailable (语义召回不可用, memory_list 仍是列表入口;\n\t * recency 枚举兜底已移除 — 语义查询返回\"最近的几条\"是对查询语义的错答)。\n\t * degraded 标记对显式召回可见; auto-recall 见标记零注入。\n\t */\n\n\t/** 一次语义召回的中间候选: atom + 融合/降级序位 (精排并列打破用)。 */\n\tinterface RecallCandidateV1 {\n\t\treadonly atom: MemoryAtomV1;\n\t\treadonly position: number;\n\t}\n\n\t/** 出线事实行 (含精排可选分): score 缺席 = 该路径未产生精排分。 */\n\tinterface ScoredRecallFactV1 {\n\t\treadonly memoryId: string;\n\t\treadonly statement: string;\n\t\treadonly confidence: number;\n\t\treadonly position: number;\n\t\treadonly score?: number;\n\t}\n\n\ttype SemanticRecallOutcomeV1 =\n\t\t| {\n\t\t\t\treadonly kind: \"served\";\n\t\t\t\treadonly facts: readonly MemoryFact[];\n\t\t\t\treadonly candidateCount: number;\n\t\t\t\treadonly suiteFilter: MemorySuiteFilterStatsV1;\n\t\t\t\t/**\n\t\t\t\t * 降级原因短语 (2026-10-05 裁决 A「宁漏勿错」): 存在即本次结果未经\n\t\t\t\t * 精排打分。显式召回照常服务并携带该标记 (模型可见); auto-recall\n\t\t\t\t * 见标记零注入。\n\t\t\t\t */\n\t\t\t\treadonly degraded?: string;\n\t\t }\n\t\t| { readonly kind: \"blocked\"; readonly reason: string }\n\t\t| { readonly kind: \"unavailable\"; readonly message: string };\n\n\t/** recency 排序 (occurredAt 降序, memoryId 升序打破并列 — 与 recall index 同口径)。 */\n\tconst recencyOrdered = (entries: readonly RecallCandidateV1[]): readonly RecallCandidateV1[] =>\n\t\t[...entries]\n\t\t\t.sort(\n\t\t\t\t(left, right) =>\n\t\t\t\t\tDate.parse(right.atom.occurredAt) - Date.parse(left.atom.occurredAt) ||\n\t\t\t\t\t(left.atom.memoryId < right.atom.memoryId ? -1 : 1),\n\t\t\t)\n\t\t\t.map((entry, position) => ({ atom: entry.atom, position }));\n\n\t/**\n\t * superseded 过滤 (S3-B, store 回查后 / 精排前): 被某条在场候选的 supersedes\n\t * 关系指向的旧条目不再出线 — 合并后同义查询只出新条目。\"新条目在场\"以候选\n\t * 集为界: 继任条目被遗忘 (purge/过期) 后其关系随之消失, 旧条目自然恢复出线。\n\t * memoryId 主键直查不经此过滤 (引用不失效), memory_list 管理面不过滤\n\t * (nothing silently dropped — 用户可显式 forget 旧条目)。\n\t */\n\tconst filterSupersededCandidates = (candidates: readonly RecallCandidateV1[]): readonly RecallCandidateV1[] => {\n\t\tconst superseded = new Set<string>();\n\t\tfor (const entry of candidates) {\n\t\t\tfor (const relation of entry.atom.relations) {\n\t\t\t\tif (relation.kind === \"supersedes\" && relation.targetMemoryId !== undefined) {\n\t\t\t\t\tsuperseded.add(relation.targetMemoryId);\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\t\tif (superseded.size === 0) return candidates;\n\t\treturn candidates.filter((entry) => !superseded.has(entry.atom.memoryId));\n\t};\n\n\t/**\n\t * store 回查 (正确性关键): 候选 id 逐条走与旧锚点通道相同的可见性单一路径\n\t * (owner + suite 读边界 + 过期, 单一路径); suite 场景下不可见 id 按 legacy/\n\t * 外套件区分计数 (suiteFilter 可观测), 不可见者丢弃且不回填 (诚实分页)。\n\t */\n\tconst lookupCandidates = (\n\t\trankedIds: readonly (readonly [memoryId: string, position: number])[],\n\t\tsuiteId: string | undefined,\n\t): { readonly candidates: readonly RecallCandidateV1[]; readonly suiteFilter: MemorySuiteFilterStatsV1 } => {\n\t\tconst candidates: RecallCandidateV1[] = [];\n\t\tlet legacySkipped = 0;\n\t\tlet foreignSuiteSkipped = 0;\n\t\tfor (const [memoryId, position] of rankedIds) {\n\t\t\tconst record = deps.store.get(memoryId, {\n\t\t\t\towner: deps.owner,\n\t\t\t\t...(suiteId === undefined ? {} : { suiteId }),\n\t\t\t});\n\t\t\tif (record !== undefined) {\n\t\t\t\tcandidates.push({ atom: record.atom, position });\n\t\t\t\tcontinue;\n\t\t\t}\n\t\t\tif (suiteId !== undefined) {\n\t\t\t\tconst unfiltered = deps.store.get(memoryId, { owner: deps.owner });\n\t\t\t\tif (unfiltered !== undefined) {\n\t\t\t\t\tif (unfiltered.atom.suiteId === undefined) legacySkipped += 1;\n\t\t\t\t\telse foreignSuiteSkipped += 1;\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\t\treturn { candidates, suiteFilter: { legacySkipped, foreignSuiteSkipped } };\n\t};\n\n\t/**\n\t * egress 门重放 (设计 §11 MUST run): 对最终候选统一执行, 与旧锚点 packet 同\n\t * 一 gate、同一 factInfo 来源 (store); blocked 即 fail-closed, 该次 recall\n\t * 返回 failed packet (来源未登记的事实永不越过出界面)。\n\t */\n\tconst egressGate = (candidates: readonly RecallCandidateV1[]) => {\n\t\tconst factInfo: Record<string, MemoryEgressFactInfoV1 | undefined> = {};\n\t\tfor (const entry of candidates) {\n\t\t\tfactInfo[entry.atom.memoryId] = {\n\t\t\t\tsourceRefs: entry.atom.sourceRefs,\n\t\t\t\tpayload: entry.atom.payload as Record<string, unknown>,\n\t\t\t};\n\t\t}\n\t\treturn deps.egressPolicy.apply(\n\t\t\t{\n\t\t\t\toperationId: `memory_recall:${deps.sessionId}:${deps.identityHash(candidates.map((entry) => entry.atom.memoryId).join(\"\\u0000\"))}`,\n\t\t\t\tstatus: \"completed\",\n\t\t\t\tfacts: candidates.map((entry) => ({\n\t\t\t\t\tmemoryId: entry.atom.memoryId,\n\t\t\t\t\tcontentRevision: entry.atom.contentRevision,\n\t\t\t\t\tstatement: JSON.stringify(entry.atom.payload),\n\t\t\t\t\tconfidence: entry.atom.confidence,\n\t\t\t\t})),\n\t\t\t\tqueryPlan: { lookupUsed: false, filtersApplied: [], scorerVersion: \"semantic-rrf@1\" },\n\t\t\t\tattempts: 0,\n\t\t\t\tnarrowingHints: [],\n\t\t\t\tomittedCount: 0,\n\t\t\t\ttruncated: false,\n\t\t\t\ttruncationScope: \"none\",\n\t\t\t\tbudget: {\n\t\t\t\t\tcandidateBudget: 0,\n\t\t\t\t\tmodelInspectionLimit: 0,\n\t\t\t\t\tfinalResultLimit: 0,\n\t\t\t\t\tused: { candidateCount: 0, modelInspectedCount: 0, finalResultCount: 0 },\n\t\t\t\t},\n\t\t\t} satisfies MemoryRecallPacketV1,\n\t\t\tfactInfo,\n\t\t);\n\t};\n\n\tconst recallSemantically = async (input: {\n\t\treadonly query: string;\n\t\treadonly domain: SuiteMemoryDomainV1;\n\t\treadonly limit: number;\n\t\t/**\n\t\t * 自动召回质量门槛 (错误教训与召回质量护栏设计 §2.1, 内部执行缝):\n\t\t * memory_recall 工具 schema 不含该参数, 显式召回行为不变; auto-recall\n\t\t * 钩子经内部闭包恒传。仅精排实际产生 score 且过门槛的候选保留 (裁决 A\n\t\t * 收紧: 无 score 候选一律丢弃)。\n\t\t */\n\t\treadonly minScore?: number;\n\t}): Promise<SemanticRecallOutcomeV1> => {\n\t\t// 后台预热不阻塞读侧 (2026-10-05 流程裁决「后台预热、不阻塞提问」):\n\t\t// 下载/加载进行中时 recall 直接返回 warming 不可用包, 不等待 vectorQueue\n\t\t// (队列此时可能正背着启动对账或下载任务) 与 ensure 单飞。\n\t\tif (deps.warming()) {\n\t\t\treturn {\n\t\t\t\tkind: \"unavailable\",\n\t\t\t\tmessage: \"memory models are loading in the background; recall becomes available once they are ready\",\n\t\t\t};\n\t\t}\n\t\t// 读侧先等写侧队列在调用时刻已入队的部分 (待处理的投影写入 + 启动对账\n\t\t// 已入队的块), 保证写入→召回 FIFO: 本调用之前提交的写入其投影先落地,\n\t\t// 不与后台对账/埋点竞态。启动对账的分块自续 (RECONCILE_CHUNK_UPSERTS)\n\t\t// 在块完成时才入队下一块, 未入队的后续块不阻塞本次召回——未回填条目由\n\t\t// store 回查与 memory_list 兜底, 残行由后续对账继续补齐。\n\t\tawait deps.settleProjection();\n\t\tconst channel = await deps.ensureChannel();\n\t\tif (channel === undefined) {\n\t\t\tif (deps.declined()) {\n\t\t\t\treturn {\n\t\t\t\t\tkind: \"unavailable\",\n\t\t\t\t\tmessage:\n\t\t\t\t\t\t\"memory was declined on this machine — set memory.mode (light|full) in settings.json and restart the session to re-enable\",\n\t\t\t\t};\n\t\t\t}\n\t\t\tconst consentAsk = deps.consentAsk();\n\t\t\tif (consentAsk !== undefined) {\n\t\t\t\treturn { kind: \"unavailable\", message: consentAsk.message };\n\t\t\t}\n\t\t\tconst failure = deps.failure();\n\t\t\treturn {\n\t\t\t\tkind: \"unavailable\",\n\t\t\t\tmessage:\n\t\t\t\t\tfailure === undefined\n\t\t\t\t\t\t? `memory channel is unavailable (state=${deps.rawState()})`\n\t\t\t\t\t\t: `memory enabling failed: ${failure.reason} — actions: ${failure.actions.join(\" | \")}`,\n\t\t\t};\n\t\t}\n\t\tconst suiteId = input.domain.kind === \"suite\" ? input.domain.suiteId : undefined;\n\t\tlet vectorFailed = false;\n\t\tlet ftsFailed = false;\n\t\tlet knnHits: readonly MemoryVectorHitV1[] | undefined;\n\t\tlet ftsHits: readonly MemoryVectorHitV1[] | undefined;\n\t\ttry {\n\t\t\tconst [queryVector] = await channel.embed.embed([channel.queryPrefix + input.query]);\n\t\t\tknnHits = await channel.index.queryKnn(queryVector, {\n\t\t\t\towner: deps.owner,\n\t\t\t\tlimit: HYBRID_CHANNEL_DEPTH,\n\t\t\t});\n\t\t} catch (error) {\n\t\t\tvectorFailed = true;\n\t\t\tdeps.logger?.warn(\"memory recall degraded: the vector channel failed; continuing on the FTS channel\", {\n\t\t\t\terror: error instanceof Error ? error.message : String(error),\n\t\t\t});\n\t\t}\n\t\ttry {\n\t\t\tftsHits = await channel.index.queryFts(input.query, {\n\t\t\t\towner: deps.owner,\n\t\t\t\tlimit: HYBRID_CHANNEL_DEPTH,\n\t\t\t});\n\t\t} catch (error) {\n\t\t\tftsFailed = true;\n\t\t\tdeps.logger?.warn(\"memory recall degraded: the FTS channel failed\", {\n\t\t\t\terror: error instanceof Error ? error.message : String(error),\n\t\t\t});\n\t\t}\n\n\t\tlet candidates: readonly RecallCandidateV1[];\n\t\tlet suiteFilter: MemorySuiteFilterStatsV1 = { legacySkipped: 0, foreignSuiteSkipped: 0 };\n\t\tlet channelDegraded = false;\n\t\t// 裁决 A: 降级原因短语, 透出到工具结果的 degraded 字段 (无精排分数路径)。\n\t\tlet degradedReason: string | undefined;\n\t\tif (!vectorFailed && !ftsFailed && knnHits !== undefined && ftsHits !== undefined) {\n\t\t\t// 主路径: 双通道 RRF k=60 融合 top20。\n\t\t\tconst pool = rrfFuseRankings([ftsHits.map((hit) => hit.memoryId), knnHits.map((hit) => hit.memoryId)]);\n\t\t\tconst lookup = lookupCandidates(\n\t\t\t\tpool.map((memoryId, position) => [memoryId, position] as const),\n\t\t\t\tsuiteId,\n\t\t\t);\n\t\t\tcandidates = lookup.candidates;\n\t\t\tsuiteFilter = lookup.suiteFilter;\n\t\t} else if (ftsHits !== undefined) {\n\t\t\t// 向量失败: FTS-only + recency 排序。\n\t\t\tchannelDegraded = true;\n\t\t\tdegradedReason = \"vector channel failed; full-text-only results, not reranked\";\n\t\t\tconst lookup = lookupCandidates(\n\t\t\t\tftsHits.map((hit, position) => [hit.memoryId, position] as const),\n\t\t\t\tsuiteId,\n\t\t\t);\n\t\t\tcandidates = recencyOrdered(lookup.candidates);\n\t\t\tsuiteFilter = lookup.suiteFilter;\n\t\t} else if (knnHits !== undefined) {\n\t\t\t// FTS 失败: KNN-only + recency 排序 (对称降级)。\n\t\t\tchannelDegraded = true;\n\t\t\tdegradedReason = \"FTS channel failed; vector-only results, not reranked\";\n\t\t\tconst lookup = lookupCandidates(\n\t\t\t\tknnHits.map((hit, position) => [hit.memoryId, position] as const),\n\t\t\t\tsuiteId,\n\t\t\t);\n\t\t\tcandidates = recencyOrdered(lookup.candidates);\n\t\t\tsuiteFilter = lookup.suiteFilter;\n\t\t} else {\n\t\t\t// 双通道皆败 = 语义召回能力不可用 (2026-10-05 裁决 A): 显式召回返回\n\t\t\t// 结构化 unavailable (memory_list 仍是列表入口), recency 枚举兜底从\n\t\t\t// 语义召回路径移除 — 语义查询返回\"最近的几条\"是对查询语义的错答。\n\t\t\tdeps.markDegraded();\n\t\t\tdeps.logger?.warn(\"memory recall unavailable: both the vector and FTS channels failed\");\n\t\t\treturn {\n\t\t\t\tkind: \"unavailable\",\n\t\t\t\tmessage:\n\t\t\t\t\t\"memory recall is unavailable (both retrieval channels failed); use memory_list to enumerate this suite's memories\",\n\t\t\t};\n\t\t}\n\t\t// 运行期通道故障 → degraded (可自愈: 下一次全通 recall 回 ready)。\n\t\tif (channelDegraded) deps.markDegraded();\n\t\telse deps.recoverDegraded();\n\n\t\t// superseded 过滤 (S3-B): 回查后、egress/精排前 (主路径与全部降级路径\n\t\t// 统一收敛于此 — 被继任的旧条目任何通道都不再出线)。\n\t\tcandidates = filterSupersededCandidates(candidates);\n\n\t\tif (candidates.length === 0) {\n\t\t\treturn {\n\t\t\t\tkind: \"served\",\n\t\t\t\tfacts: [],\n\t\t\t\tcandidateCount: 0,\n\t\t\t\tsuiteFilter,\n\t\t\t\t...(degradedReason === undefined ? {} : { degraded: degradedReason }),\n\t\t\t};\n\t\t}\n\t\tconst egressOutcome = egressGate(candidates);\n\t\tif (egressOutcome.status === \"blocked\") return { kind: \"blocked\", reason: egressOutcome.reason };\n\t\tconst statementByMemoryId = new Map(egressOutcome.packet.facts.map((fact) => [fact.memoryId, fact.statement]));\n\t\tlet ordered: readonly ScoredRecallFactV1[] = candidates.map((entry) => ({\n\t\t\tmemoryId: entry.atom.memoryId,\n\t\t\tstatement: deps.unwrapEgressStatement(statementByMemoryId.get(entry.atom.memoryId) ?? \"\"),\n\t\t\tconfidence: entry.atom.confidence,\n\t\t\tposition: entry.position,\n\t\t}));\n\t\t// 精排 (仅主融合路径: 降级页的排序即其降级语义)。reranker 不可用或调用\n\t\t// 失败 → 保持 RRF 序并携带 degraded 标记; 调用失败不闩锁 (裁决 A: 下一次\n\t\t// 召回重试, 与组件层加载重试语义对齐), 每次失败记一条结构化日志。\n\t\tconst reranker = channel.reranker;\n\t\tif (!channelDegraded && reranker !== undefined) {\n\t\t\ttry {\n\t\t\t\tconst scores = await reranker.rerank(\n\t\t\t\t\tinput.query,\n\t\t\t\t\tordered.map((fact) => fact.statement),\n\t\t\t\t);\n\t\t\t\tordered = ordered\n\t\t\t\t\t.map((fact, position) => ({ ...fact, score: scores[position] }))\n\t\t\t\t\t.sort((left, right) => (right.score ?? 0) - (left.score ?? 0) || left.position - right.position);\n\t\t\t} catch (error) {\n\t\t\t\tdegradedReason ??= \"reranker failed; RRF order without rerank scores\";\n\t\t\t\tdeps.logger?.warn(\n\t\t\t\t\t\"memory reranker failed; recall serves the RRF order this round and retries the next recall\",\n\t\t\t\t\t{\n\t\t\t\t\t\terror: error instanceof Error ? error.message : String(error),\n\t\t\t\t\t},\n\t\t\t\t);\n\t\t\t}\n\t\t}\n\t\t// minScore 门槛 (§2.1, 裁决 A 收紧): 仅精排实际产生 score 且过门槛的候选\n\t\t// 参与过滤后结果 (无 score 的候选一律丢弃, 不再放行); 过滤先于 limit 截取。\n\t\t// minScore 只经 auto-recall 内部闭包传入, auto-recall 另按 degraded 标记\n\t\t// 零注入, 双保险钉住\"注入 ⟹ 全部条目有分且过门槛\"。\n\t\tconst minScore = input.minScore;\n\t\tconst eligible =\n\t\t\tminScore === undefined\n\t\t\t\t? ordered\n\t\t\t\t: ordered.filter((fact) => fact.score !== undefined && fact.score >= minScore);\n\t\treturn {\n\t\t\tkind: \"served\",\n\t\t\tfacts: eligible\n\t\t\t\t.slice(0, input.limit)\n\t\t\t\t.map(({ memoryId, statement, confidence }) => ({ memoryId, statement, confidence })),\n\t\t\tcandidateCount: ordered.length,\n\t\t\tsuiteFilter,\n\t\t\t...(degradedReason === undefined ? {} : { degraded: degradedReason }),\n\t\t};\n\t};\n\n\t/**\n\t * Context-scoped preference post-processing over the recalled facts: preference\n\t * atoms compete per (subject, key); the resolver drops out-of-scope preferences\n\t * and deterministically ranks the rest, and an unconfirmed ranking goes through\n\t * the scope-specificity disambiguator. Whatever remains unresolvable is\n\t * returned as an explicit conflict block — the recall never silently picks\n\t * between competing stored values. Non-preference facts pass through untouched.\n\t */\n\tfunction applyPreferenceContext(input: {\n\t\treadonly facts: readonly MemoryFact[];\n\t\treadonly context: PreferenceContextV1;\n\t\treadonly resolver: PreferenceResolverV1;\n\t\treadonly disambiguator: PreferenceDisambiguatorV1;\n\t\treadonly atomOf: (memoryId: string) => MemoryAtomV1 | undefined;\n\t}): { readonly facts: readonly MemoryFact[]; readonly conflicts: readonly RecallConflictV1[] } {\n\t\tconst { facts, context, resolver, disambiguator, atomOf } = input;\n\t\tconst preferenceAtoms: MemoryAtomV1[] = [];\n\t\tconst atomsByGroup = new Map<string, MemoryAtomV1[]>();\n\t\tconst preferenceMemoryIds = new Set<string>();\n\t\tfor (const fact of facts) {\n\t\t\tconst atom = atomOf(fact.memoryId);\n\t\t\tif (atom === undefined || atom.memoryKind !== \"preference\" || atom.preference === undefined) continue;\n\t\t\tpreferenceMemoryIds.add(fact.memoryId);\n\t\t\tpreferenceAtoms.push(atom);\n\t\t\tconst groupKey = `${atom.preference.subject}\\u0000${atom.preference.key}`;\n\t\t\tconst group = atomsByGroup.get(groupKey) ?? [];\n\t\t\tgroup.push(atom);\n\t\t\tatomsByGroup.set(groupKey, group);\n\t\t}\n\t\tif (preferenceMemoryIds.size === 0) return { facts, conflicts: [] };\n\n\t\tconst keptMemoryIds = new Set<string>();\n\t\tconst conflicts: RecallConflictV1[] = [];\n\t\tfor (const decision of resolver.resolve(preferenceAtoms, context)) {\n\t\t\tconst groupAtoms = atomsByGroup.get(`${decision.subject}\\u0000${decision.key}`) ?? [];\n\t\t\tif (decision.status === \"applied\") {\n\t\t\t\tif (decision.winnerMemoryId === undefined) {\n\t\t\t\t\tthrow new Error(`applied preference decision without a winnerMemoryId: ${JSON.stringify(decision)}`);\n\t\t\t\t}\n\t\t\t\tkeptMemoryIds.add(decision.winnerMemoryId);\n\t\t\t\tcontinue;\n\t\t\t}\n\t\t\tif (decision.status === \"instruction_override\") {\n\t\t\t\t// No stored fact survives a current-instruction override (this slice has\n\t\t\t\t// no input source for currentInstructions, so the branch stays inert).\n\t\t\t\tcontinue;\n\t\t\t}\n\t\t\tif (decision.status === \"conflict_unconfirmed\") {\n\t\t\t\tconst outcome = disambiguator.disambiguate(\n\t\t\t\t\tgroupAtoms.map((atom) => ({\n\t\t\t\t\t\tmemoryId: atom.memoryId,\n\t\t\t\t\t\tpreferredValue: atom.preference!.preferredValue,\n\t\t\t\t\t\tscope: atom.preference!.scope,\n\t\t\t\t\t\tevidenceClass: atom.preference!.evidence.class,\n\t\t\t\t\t\tapplicabilityConfidence: atom.preference!.applicabilityConfidence,\n\t\t\t\t\t})),\n\t\t\t\t\tcontext,\n\t\t\t\t);\n\t\t\t\tif (outcome.status === \"resolved\") {\n\t\t\t\t\tkeptMemoryIds.add(outcome.winnerMemoryId);\n\t\t\t\t\tcontinue;\n\t\t\t\t}\n\t\t\t\tconflicts.push({\n\t\t\t\t\tsubject: decision.subject,\n\t\t\t\t\tkey: decision.key,\n\t\t\t\t\tcandidates: groupAtoms.map((atom) => {\n\t\t\t\t\t\tconst preference = atom.preference!;\n\t\t\t\t\t\treturn {\n\t\t\t\t\t\t\tmemoryId: atom.memoryId,\n\t\t\t\t\t\t\tstatement: deps.unwrapEgressStatement(deps.statementOf(atom.payload)),\n\t\t\t\t\t\t\tvalue: preference.preferredValue,\n\t\t\t\t\t\t\tevidenceClass: preference.evidence.class,\n\t\t\t\t\t\t\tconfidence: preference.applicabilityConfidence,\n\t\t\t\t\t\t};\n\t\t\t\t\t}),\n\t\t\t\t\texplanation: [...outcome.explanation],\n\t\t\t\t});\n\t\t\t\tcontinue;\n\t\t\t}\n\t\t\tthrow new Error(`unexpected preference decision status: ${JSON.stringify(decision)}`);\n\t\t}\n\n\t\treturn {\n\t\t\tfacts: facts.filter((fact) => !preferenceMemoryIds.has(fact.memoryId) || keptMemoryIds.has(fact.memoryId)),\n\t\t\tconflicts,\n\t\t};\n\t}\n\n\t/**\n\t * Parses the optional recall context (scenes/projects/tasks) into a preference\n\t * resolution context. Returns undefined when absent so the no-context recall\n\t * path stays byte-identical to the pre-disambiguation behavior.\n\t */\n\tfunction parseRecallContext(value: unknown): PreferenceContextV1 | undefined {\n\t\tif (value === undefined) return undefined;\n\t\tif (!deps.isPlainObject(value)) throw new Error(\"context must be an object\");\n\t\tconst stringArray = (field: \"scenes\" | \"projects\" | \"tasks\"): readonly string[] | undefined => {\n\t\t\tconst entries: unknown = value[field];\n\t\t\tif (entries === undefined) return undefined;\n\t\t\tif (!Array.isArray(entries)) throw new Error(`context.${field} must be an array of strings`);\n\t\t\tfor (const entry of entries) {\n\t\t\t\tif (typeof entry !== \"string\") throw new Error(`context.${field} entries must be strings`);\n\t\t\t}\n\t\t\treturn entries;\n\t\t};\n\t\tconst scenes = stringArray(\"scenes\");\n\t\tconst projects = stringArray(\"projects\");\n\t\tconst tasks = stringArray(\"tasks\");\n\t\treturn {\n\t\t\t...(scenes === undefined ? {} : { scenes }),\n\t\t\t...(projects === undefined ? {} : { projects }),\n\t\t\t...(tasks === undefined ? {} : { tasks }),\n\t\t};\n\t}\n\n\t// 提取为具名闭包: 除模型工具面外, 下方的 auto-recall 钩子直接调用同一 execute\n\t// (单参输入约定, memoryToolInput 取第一参), 不经 runtime.invokeTool —— 钩子\n\t// 随插件注册/卸载, 与工具面同生命周期, 替换工具面不会重接自动召回。\n\tconst recallExecute = async (first: unknown, second?: unknown): Promise<unknown> => {\n\t\tconst input = deps.memoryToolInput(first, second);\n\t\tdeps.assertNonEmptyString(input.query, \"query\");\n\t\tconst rawLimit: unknown = input.limit;\n\t\tconst limit =\n\t\t\trawLimit === undefined\n\t\t\t\t? DEFAULT_RECALL_LIMIT\n\t\t\t\t: ((): number => {\n\t\t\t\t\t\tif (\n\t\t\t\t\t\t\ttypeof rawLimit !== \"number\" ||\n\t\t\t\t\t\t\t!Number.isSafeInteger(rawLimit) ||\n\t\t\t\t\t\t\trawLimit < 1 ||\n\t\t\t\t\t\t\trawLimit > 100\n\t\t\t\t\t\t) {\n\t\t\t\t\t\t\tthrow new Error(\"limit must be an integer between 1 and 100\");\n\t\t\t\t\t\t}\n\t\t\t\t\t\treturn rawLimit;\n\t\t\t\t\t})();\n\t\t// 内部执行缝 (§2.1): auto-recall 闭包经本字段恒传质量门槛; memory_recall\n\t\t// 工具 schema 不含该参数 (工具面恒 undefined), 显式召回行为不变。\n\t\tconst rawMinScore: unknown = input.minScore;\n\t\tconst minScore =\n\t\t\trawMinScore === undefined\n\t\t\t\t? undefined\n\t\t\t\t: ((): number => {\n\t\t\t\t\t\tif (typeof rawMinScore !== \"number\" || !Number.isFinite(rawMinScore)) {\n\t\t\t\t\t\t\tthrow new Error(\"minScore must be a finite number\");\n\t\t\t\t\t\t}\n\t\t\t\t\t\treturn rawMinScore;\n\t\t\t\t\t})();\n\t\tconst context = parseRecallContext(input.context);\n\t\tconst { domain } = deps.resolveDomain();\n\n\t\t// enabling 失败 = 本实例终态 (mode 记录回退 off): 记忆工具返回携带原因\n\t\t// 与五选一行动清单的不可用 packet (设计 §3.2 硬性要求), 不再重试。\n\t\tif (deps.rawState() === \"failed\") {\n\t\t\tconst failure = deps.failure() ?? { reason: \"memory enabling failed\", actions: [] as const };\n\t\t\treturn {\n\t\t\t\tstatus: \"unavailable\",\n\t\t\t\tstate: \"failed\",\n\t\t\t\tfacts: [] as MemoryFact[],\n\t\t\t\terror: `memory enabling failed: ${failure.reason}`,\n\t\t\t\tactions: failure.actions,\n\t\t\t};\n\t\t}\n\t\t// 征询门早退 (2026-10-05 用户裁决): 拒绝 = 本实例按模式关闭对待; 待征询 =\n\t\t// 返回带行动清单的 consent-required packet (后续 recall 不再反复探测文件)。\n\t\tif (deps.declined()) {\n\t\t\treturn {\n\t\t\t\tstatus: \"unavailable\",\n\t\t\t\tstate: \"off\",\n\t\t\t\tfacts: [] as MemoryFact[],\n\t\t\t\terror: \"memory was declined on this machine — set memory.mode (light|full) in settings.json and restart the session to re-enable\",\n\t\t\t\tactions: [] as const,\n\t\t\t};\n\t\t}\n\t\tconst consentAsk = deps.consentAsk();\n\t\tif (consentAsk !== undefined) {\n\t\t\treturn {\n\t\t\t\tstatus: \"unavailable\",\n\t\t\t\tstate: \"consent-required\",\n\t\t\t\tfacts: [] as MemoryFact[],\n\t\t\t\terror: consentAsk.message,\n\t\t\t\tactions: consentAsk.actions,\n\t\t\t};\n\t\t}\n\n\t\t// 主键快路径 (memoryIdEquals 语义保留, 幂等/引用而非检索): query 恰为本\n\t\t// 域可见 memoryId 时直接取该条, 不依赖向量通道 (enabling 期间也可用)。\n\t\tlet outcome: SemanticRecallOutcomeV1;\n\t\tconst directRecord = deps.store.get(input.query.trim(), {\n\t\t\towner: deps.owner,\n\t\t\t...(domain.kind === \"suite\" ? { suiteId: domain.suiteId } : {}),\n\t\t});\n\t\tif (directRecord !== undefined) {\n\t\t\tconst gate = egressGate([{ atom: directRecord.atom, position: 0 }]);\n\t\t\tif (gate.status === \"blocked\") {\n\t\t\t\treturn { status: \"failed\", facts: [] as MemoryFact[], error: `egress blocked: ${gate.reason}` };\n\t\t\t}\n\t\t\tconst statementByMemoryId = new Map(gate.packet.facts.map((fact) => [fact.memoryId, fact.statement]));\n\t\t\toutcome = {\n\t\t\t\tkind: \"served\",\n\t\t\t\tfacts: [\n\t\t\t\t\t{\n\t\t\t\t\t\tmemoryId: directRecord.atom.memoryId,\n\t\t\t\t\t\tstatement: deps.unwrapEgressStatement(statementByMemoryId.get(directRecord.atom.memoryId) ?? \"\"),\n\t\t\t\t\t\tconfidence: directRecord.atom.confidence,\n\t\t\t\t\t},\n\t\t\t\t],\n\t\t\t\tcandidateCount: 1,\n\t\t\t\tsuiteFilter: { legacySkipped: 0, foreignSuiteSkipped: 0 },\n\t\t\t\t// 裁决 A 缝隙收口 (2026-10-05 复审后用户裁决「修复」): 直查是引用\n\t\t\t\t// 语义、未经精排 — 显式召回如实带标记, auto-recall 见标记零注入\n\t\t\t\t// (此前用户消息恰为 memoryId 时会绕过质量门注入该条)。\n\t\t\t\tdegraded: \"primary-key direct lookup (reference semantics, not reranked)\",\n\t\t\t};\n\t\t} else {\n\t\t\ttry {\n\t\t\t\toutcome = await recallSemantically({\n\t\t\t\t\tquery: input.query,\n\t\t\t\t\tdomain,\n\t\t\t\t\tlimit,\n\t\t\t\t\t...(minScore === undefined ? {} : { minScore }),\n\t\t\t\t});\n\t\t\t} catch (error) {\n\t\t\t\t// 意外异常 (管线 throw) → degraded packet: 结构化返回, 不抛给模型。\n\t\t\t\tdeps.markDegraded();\n\t\t\t\tconst message = error instanceof Error ? error.message : String(error);\n\t\t\t\tdeps.logger?.warn(\"memory recall degraded: the recall pipeline threw; returning a degraded packet\", {\n\t\t\t\t\terror: message,\n\t\t\t\t});\n\t\t\t\treturn { status: \"failed\", facts: [] as MemoryFact[], error: message };\n\t\t\t}\n\t\t}\n\t\tif (outcome.kind === \"blocked\") {\n\t\t\treturn { status: \"failed\", facts: [] as MemoryFact[], error: `egress blocked: ${outcome.reason}` };\n\t\t}\n\t\tif (outcome.kind === \"unavailable\") {\n\t\t\t// 首次调用即在装配中途失败时, vectorState 在本调用内才翻成 failed —\n\t\t\t// 这里同样必须携带 enabling 失败的原因 + 行动清单 (设计 §3.2 硬性\n\t\t\t// 要求: 终态的第一份可见 packet 就是完整的), 不等下一次调用补。\n\t\t\t// (vectorState 是可变闭包状态, 语义检索内部会改写它, 入口预检的\n\t\t\t// 收窄在此处不成立 — 经函数边界读取以如实表达。)\n\t\t\tconst stateAtEgress = (): MemoryCapabilityStateV1 => deps.rawState();\n\t\t\tconst failureAtEgress = deps.failure();\n\t\t\tconst consentAskAtEgress = deps.consentAsk();\n\t\t\tif (stateAtEgress() === \"failed\" && failureAtEgress !== undefined) {\n\t\t\t\treturn {\n\t\t\t\t\tstatus: \"unavailable\",\n\t\t\t\t\tstate: \"failed\",\n\t\t\t\t\tfacts: [] as MemoryFact[],\n\t\t\t\t\terror: `memory enabling failed: ${failureAtEgress.reason}`,\n\t\t\t\t\tactions: failureAtEgress.actions,\n\t\t\t\t};\n\t\t\t}\n\t\t\treturn {\n\t\t\t\tstatus: \"unavailable\",\n\t\t\t\tstate: stateAtEgress(),\n\t\t\t\tfacts: [] as MemoryFact[],\n\t\t\t\terror: outcome.message,\n\t\t\t\t...(stateAtEgress() === \"consent-required\" && consentAskAtEgress !== undefined\n\t\t\t\t\t? { actions: consentAskAtEgress.actions }\n\t\t\t\t\t: {}),\n\t\t\t};\n\t\t}\n\t\tconst facts = outcome.facts;\n\t\t// 增强段 (偏好消歧 + 关系扩散) 是增量元数据面: 内部抛错不得砸穿工具面\n\t\t// (结构化失败契约), 也不吞掉整个召回 — 降级为基础事实 + 一条结构化 warn。\n\t\tconst { finalFacts, resolvedConflicts, related, relatedOmittedCount } = await (async () => {\n\t\t\ttry {\n\t\t\t\tconst resolved =\n\t\t\t\t\tcontext === undefined\n\t\t\t\t\t\t? undefined\n\t\t\t\t\t\t: applyPreferenceContext({\n\t\t\t\t\t\t\t\tfacts,\n\t\t\t\t\t\t\t\tcontext,\n\t\t\t\t\t\t\t\tresolver: deps.preferenceResolver,\n\t\t\t\t\t\t\t\tdisambiguator: deps.preferenceDisambiguator,\n\t\t\t\t\t\t\t\tatomOf: (memoryId) => deps.store.get(memoryId, { owner: deps.owner })?.atom,\n\t\t\t\t\t\t\t});\n\t\t\t\t// 偏好消歧先于关系扩散: the diffusion seeds are the atoms the user\n\t\t\t\t// actually sees — preference facts dropped by the conflict resolution\n\t\t\t\t// never diffuse their relations.\n\t\t\t\tconst baseFacts = resolved === undefined ? facts : resolved.facts;\n\t\t\t\tconst seeds: MemoryAtomV1[] = [];\n\t\t\t\tfor (const fact of baseFacts) {\n\t\t\t\t\tconst atom = deps.store.get(fact.memoryId, { owner: deps.owner })?.atom;\n\t\t\t\t\tif (atom !== undefined) seeds.push(atom);\n\t\t\t\t}\n\t\t\t\t// The traversal graph is the recall domain's visible atom set (same\n\t\t\t\t// suite read boundary). Diffusion targets outside this set are dropped,\n\t\t\t\t// so relations never leak across suites.\n\t\t\t\tconst domainMemoryIds = new Set<string>();\n\t\t\t\tconst graph: MemoryAtomV1[] = [];\n\t\t\t\tfor (const entry of listDomainMemories({ store: deps.store }, { owner: deps.owner, domain }).entries) {\n\t\t\t\t\tdomainMemoryIds.add(entry.memoryId);\n\t\t\t\t\tconst atom = deps.store.get(entry.memoryId, { owner: deps.owner })?.atom;\n\t\t\t\t\tif (atom !== undefined) graph.push(atom);\n\t\t\t\t}\n\t\t\t\tconst related: MemoryNetworkExpansionV1[] = [];\n\t\t\t\tlet relatedOmittedCount = 0;\n\t\t\t\tconst seenRelatedMemoryIds = new Set<string>();\n\t\t\t\tfor (const adapterResult of deps.memoryNetwork.expand({ seeds, graph })) {\n\t\t\t\t\tif (adapterResult.status !== \"completed\") continue;\n\t\t\t\t\tfor (const expansion of adapterResult.related) {\n\t\t\t\t\t\tif (!domainMemoryIds.has(expansion.memoryId)) continue;\n\t\t\t\t\t\tif (seenRelatedMemoryIds.has(expansion.memoryId)) continue;\n\t\t\t\t\t\tseenRelatedMemoryIds.add(expansion.memoryId);\n\t\t\t\t\t\tif (related.length >= MAX_RELATED_REFS) {\n\t\t\t\t\t\t\trelatedOmittedCount += 1;\n\t\t\t\t\t\t\tcontinue;\n\t\t\t\t\t\t}\n\t\t\t\t\t\trelated.push(expansion);\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t\treturn {\n\t\t\t\t\tfinalFacts: baseFacts,\n\t\t\t\t\tresolvedConflicts: resolved === undefined ? [] : resolved.conflicts,\n\t\t\t\t\trelated,\n\t\t\t\t\trelatedOmittedCount,\n\t\t\t\t};\n\t\t\t} catch (error) {\n\t\t\t\tdeps.logger?.warn(\n\t\t\t\t\t\"memory recall enhancement failed; serving the base facts without preference resolution and related references\",\n\t\t\t\t\t{ error: error instanceof Error ? error.message : String(error) },\n\t\t\t\t);\n\t\t\t\treturn {\n\t\t\t\t\tfinalFacts: facts,\n\t\t\t\t\tresolvedConflicts: [],\n\t\t\t\t\trelated: [] as MemoryNetworkExpansionV1[],\n\t\t\t\t\trelatedOmittedCount: 0,\n\t\t\t\t};\n\t\t\t}\n\t\t})();\n\t\treturn {\n\t\t\tstatus: finalFacts.length === 0 ? \"empty\" : \"completed\",\n\t\t\tfacts: finalFacts,\n\t\t\t...(outcome.degraded === undefined ? {} : { degraded: outcome.degraded }),\n\t\t\t...(resolvedConflicts.length > 0 ? { conflicts: resolvedConflicts } : {}),\n\t\t\t...(domain.kind === \"suite\" ? { suiteFilter: outcome.suiteFilter } : {}),\n\t\t\tomittedCount: Math.max(0, outcome.candidateCount - finalFacts.length),\n\t\t\ttruncated: false,\n\t\t\t// References only — no statement/payload egress; the model fetches\n\t\t\t// content via memory_list or another memory_recall.\n\t\t\t...(related.length > 0\n\t\t\t\t? {\n\t\t\t\t\t\trelated: related.map((expansion) => ({\n\t\t\t\t\t\t\tmemoryId: expansion.memoryId,\n\t\t\t\t\t\t\trelationKind: expansion.relationKind,\n\t\t\t\t\t\t\thop: expansion.hop,\n\t\t\t\t\t\t\tweight: expansion.weight,\n\t\t\t\t\t\t\tvia: expansion.via,\n\t\t\t\t\t\t})),\n\t\t\t\t\t}\n\t\t\t\t: {}),\n\t\t\t...(relatedOmittedCount > 0 ? { relatedOmittedCount } : {}),\n\t\t};\n\t};\n\tconst recallTool = deps.registerTool({\n\t\tname: MEMORY_RECALL_TOOL_NAME,\n\t\tlabel: \"Recall memories\",\n\t\tpromptGuidelines: [MEMORY_TOOLS_GUIDE],\n\t\tdescription:\n\t\t\t\"Search this suite's durable memories with semantic recall: vector and full-text channels fuse (RRF) and the result is reranked; an exact memoryId retrieves that memory directly; the result may include related memory references (ids and relation metadata only — recall or list a referenced memory again for its content)\",\n\t\tparameters: recallSchema,\n\t\texecute: recallExecute,\n\t});\n\n\treturn { recallTool, recallExecute };\n}\n"]}
1
+ {"version":3,"file":"recall-pipeline.d.ts","sourceRoot":"","sources":["../../src/memory/recall-pipeline.ts"],"names":[],"mappings":"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","sourcesContent":["/**\n * 召回管线(capability 拆分战役 slice 4):双通道查询、RRF 融合、egress 门、语义召回与召回工具。状态机经 vectorChannel 只读面消费, 降级经回调上报\n */\nimport type { CapabilityAPI, DisposableRegistration, JsonValue } from \"@agent-forge/plugin-sdk\";\nimport { MEMORY_RECALL_TOOL_NAME } from \"@agent-forge/plugin-sdk\";\nimport { Type } from \"typebox\";\nimport type { MemoryEgressFactInfoV1, MemoryEgressPolicyGateV1 } from \"./egress-policy.ts\";\nimport type { MemoryAtomV1, MemoryPreferenceEnvelopeV1, MemorySuiteFilterStatsV1 } from \"./foundation.ts\";\nimport type { DurableMemoryLedgerV1 } from \"./ledger.ts\";\nimport type { MemoryNetworkExpansionV1, MemoryNetworkV1 } from \"./memory-network.ts\";\nimport type { PreferenceDisambiguatorV1 } from \"./preference-disambiguator.ts\";\nimport type { PreferenceContextV1, PreferenceResolverV1 } from \"./preference-resolver.ts\";\nimport type { MemoryRecallPacketV1 } from \"./recall-packet.ts\";\nimport type { MemoryStoreV1 } from \"./store.ts\";\nimport { listDomainMemories, type SuiteMemoryDomainV1 } from \"./suite-memory.ts\";\nimport type { MemoryCapabilityStateV1, MemoryConsentAskV1, MemoryEnablingFailureV1 } from \"./vector-channel.ts\";\nimport type { MemoryVectorHitV1 } from \"./vector-index.ts\";\nimport type { ActiveVectorChannelV1 } from \"./vector-projection.ts\";\n\n/**\n * Memory tools guide(记忆 runtime 注入面,原 system-prompt.ts MEMORY_TOOL_GUIDE,\n * D-071 迁移):the model-facing hint that the persistent memory tools exist.\n * It rides memory_recall's promptGuidelines so the text lives with the plugin\n * that owns the tools (宪法 §3) and renders whenever the tool face is active.\n */\nexport const MEMORY_TOOLS_GUIDE =\n\t\"Memory: You have persistent memory tools (memory_write, memory_recall, memory_list, memory_forget). Use memory_recall only when past decisions, preferences, or project facts are actually relevant to the current question — not on every turn. Use memory_write only for facts the user explicitly stated or that a tool/test verified; never save your own inferences or guesses as facts, and declare the honest evidence class. memory_forget only files a forget request — deletion happens after the user confirms it.\";\n\nconst DEFAULT_RECALL_LIMIT = 5;\n\n/** Bounded supplementary relation references per recall (记忆网络接线). */\nconst MAX_RELATED_REFS = 20;\n\n/** FTS / KNN 通道各自取回深度 (定版管线: depth 50)。 */\nconst HYBRID_CHANNEL_DEPTH = 50;\n/** RRF 常数, 与 vector-index 组件 queryFts 打分口径一致。 */\nconst HYBRID_RRF_K = 60;\n/** 融合候选池上限 (定版管线: top20 → 精排 → top limit)。 */\nconst HYBRID_FUSION_POOL = 20;\n\ninterface MemoryFact {\n\treadonly memoryId: string;\n\treadonly statement: string;\n\treadonly confidence: number;\n}\n\n/**\n * 三通道 RRF 融合 (定版管线): `score(id) += 1/(k + rank + 1)` 对各通道排名累加,\n * 取 top {@link HYBRID_FUSION_POOL}。分数并列时以 memoryId 升序打破, 保证确定性。\n */\nfunction rrfFuseRankings(rankings: readonly (readonly string[])[]): readonly string[] {\n\tconst scores = new Map<string, number>();\n\tfor (const ranking of rankings) {\n\t\tfor (const [rank, memoryId] of ranking.entries()) {\n\t\t\tscores.set(memoryId, (scores.get(memoryId) ?? 0) + 1 / (HYBRID_RRF_K + rank + 1));\n\t\t}\n\t}\n\treturn [...scores.entries()]\n\t\t.sort((left, right) => right[1] - left[1] || (left[0] < right[0] ? -1 : 1))\n\t\t.map(([memoryId]) => memoryId)\n\t\t.slice(0, HYBRID_FUSION_POOL);\n}\n\n/** One unresolvably competing preference, surfaced verbatim instead of a silent pick. */\ninterface RecallConflictV1 {\n\treadonly subject: MemoryPreferenceEnvelopeV1[\"subject\"];\n\treadonly key: string;\n\treadonly candidates: readonly {\n\t\treadonly memoryId: string;\n\t\treadonly statement: string;\n\t\treadonly value: JsonValue;\n\t\treadonly evidenceClass: MemoryPreferenceEnvelopeV1[\"evidence\"][\"class\"];\n\t\treadonly confidence: number;\n\t}[];\n\treadonly explanation: readonly string[];\n}\n\nconst recallSchema = Type.Object({\n\tquery: Type.String({\n\t\tdescription:\n\t\t\t\"Natural-language query; recall fuses semantic (vector) and full-text (BM25) matching and reranks the result. An exact memoryId retrieves that memory directly\",\n\t}),\n\tlimit: Type.Optional(\n\t\tType.Integer({\n\t\t\tdescription: \"Maximum number of memories to return (1-100, default 5)\",\n\t\t\tminimum: 1,\n\t\t\tmaximum: 100,\n\t\t}),\n\t),\n\tcontext: Type.Optional(\n\t\tType.Object(\n\t\t\t{\n\t\t\t\tscenes: Type.Optional(\n\t\t\t\t\tType.Array(Type.String(), {\n\t\t\t\t\t\tdescription: \"Scene tags of the current task, used to resolve preferences by scope\",\n\t\t\t\t\t}),\n\t\t\t\t),\n\t\t\t\tprojects: Type.Optional(\n\t\t\t\t\tType.Array(Type.String(), {\n\t\t\t\t\t\tdescription: \"Project identifiers of the current task, used to resolve preferences by scope\",\n\t\t\t\t\t}),\n\t\t\t\t),\n\t\t\t\ttasks: Type.Optional(\n\t\t\t\t\tType.Array(Type.String(), {\n\t\t\t\t\t\tdescription: \"Task or goal identifiers of the current task, used to resolve preferences by scope\",\n\t\t\t\t\t}),\n\t\t\t\t),\n\t\t\t},\n\t\t\t{\n\t\t\t\tdescription:\n\t\t\t\t\t\"Current scope context; when present, recalled preferences are resolved against it (out-of-scope preferences drop, competing values resolve by scope specificity or surface as explicit conflicts)\",\n\t\t\t},\n\t\t),\n\t),\n});\n\n/**\n * 召回管线对 capability 闭包的显式依赖视图 (slice 4): 逐项来自\n * createMemoryCapabilityHarness 的闭包捕获核实。状态机 (vectorChannelCtl) 与\n * 写侧投影 (vectorProjection) 归 capability 装配所有, 本模块经只读回调消费与\n * 上报, 不持有任何状态机变量; capability 模块级纯函数经 deps 注入避免双头定义。\n */\nexport interface MemoryRecallPipelineDepsV1 {\n\t/** capability 日志面 (api.logger): 通道降级/增强失败的结构化日志。 */\n\treadonly logger: CapabilityAPI[\"logger\"];\n\t/** 检索行 owner (capability.BUILTIN_MEMORY_OWNER)。 */\n\treadonly owner: string;\n\t/** 会话 id (harness sessionId): egress operationId 组成。 */\n\treadonly sessionId: string;\n\t/** canonical store (per-capability 单例): store 回查与增强段 atom 读取。 */\n\treadonly store: MemoryStoreV1;\n\t/** 域解析 (capability.resolveDomain): 每次 recall 调用时惰性解析 (B1 语义)。 */\n\treadonly resolveDomain: () => {\n\t\treadonly domain: SuiteMemoryDomainV1;\n\t\treadonly domainName: string;\n\t\treadonly ledger: DurableMemoryLedgerV1;\n\t};\n\t/** egress 门 (capability.egressPolicy): 与旧锚点 packet 同一 gate。 */\n\treadonly egressPolicy: MemoryEgressPolicyGateV1;\n\t/** context-scoped 偏好解析 (2.4c/2.4d2)。 */\n\treadonly preferenceResolver: PreferenceResolverV1;\n\t/** 未决偏好的确定性消歧 (2.4c/2.4d2)。 */\n\treadonly preferenceDisambiguator: PreferenceDisambiguatorV1;\n\t/** 关系扩散 (记忆网络接线): recall 种子的一跳扩展。 */\n\treadonly memoryNetwork: MemoryNetworkV1;\n\t// ---- vectorChannel 只读面 (slice 3b 的 vectorChannelCtl, 状态机归其所有) ----\n\t/** warming 守卫: 后台预热在途且未拒绝时 recall 零等待返回 unavailable。 */\n\treadonly warming: () => boolean;\n\t/** 惰性装配 (vectorEnsure 单飞语义原样保留): undefined = 通道不可用。 */\n\treadonly ensureChannel: () => Promise<ActiveVectorChannelV1 | undefined>;\n\t/** 征询拒绝标记 (declined): recall 按模式关闭对待。 */\n\treadonly declined: () => boolean;\n\t/** 待征询信息 (consent-required): recall 返回征询话术包。 */\n\treadonly consentAsk: () => MemoryConsentAskV1 | undefined;\n\t/** enabling 结构化失败 (原因 + 行动清单, 设计 §3.2)。 */\n\treadonly failure: () => MemoryEnablingFailureV1 | undefined;\n\t/** 通道原始状态 (未做 declined 投影): packet 的 state 字段来源。 */\n\treadonly rawState: () => MemoryCapabilityStateV1;\n\t/** 运行期故障的 degraded 转换回调 (状态转换语义归状态机)。 */\n\treadonly markDegraded: () => void;\n\t/** 全通成功的 degraded 自愈回调。 */\n\treadonly recoverDegraded: () => void;\n\t// ---- 写侧投影 (slice 3a 的 vectorProjection) ----\n\t/** 读侧 FIFO 等待 (await vectorQueue): 保写入→召回次序。 */\n\treadonly settleProjection: () => Promise<unknown>;\n\t// ---- capability 模块级纯函数 (留守方, 经 deps 注入避免双头定义) ----\n\treadonly memoryToolInput: (first: unknown, second: unknown) => Record<string, unknown>;\n\treadonly assertNonEmptyString: (value: unknown, label: string) => asserts value is string;\n\treadonly isPlainObject: (value: unknown) => value is Record<string, unknown>;\n\treadonly identityHash: (value: string) => string;\n\treadonly statementOf: (payload: unknown) => string;\n\treadonly unwrapEgressStatement: (statement: string) => string;\n\t/** 工具注册面 (api.registerTool): memory_recall 经同一公共通道注册。 */\n\treadonly registerTool: CapabilityAPI[\"registerTool\"];\n}\n\n/** 召回管线工厂产物: 工具注册项 + auto-recall 直连的执行闭包。 */\nexport interface MemoryRecallPipelineV1 {\n\t/** memory_recall 工具注册项 (capability registrations 收集)。 */\n\treadonly recallTool: DisposableRegistration;\n\t/** 召回执行闭包 (原 recallExecute): autoRecallBlock (留守, slice 7) 直连同一 execute。 */\n\treadonly recallExecute: (first: unknown, second?: unknown) => Promise<unknown>;\n}\n\nexport function createMemoryRecallPipeline(deps: MemoryRecallPipelineDepsV1): MemoryRecallPipelineV1 {\n\t/**\n\t * 语义召回管线 (2.4d6 S2, 设计 §4) — 唯一检索路径: embed(查询, light 加 BGE\n\t * 前缀) → KNN depth50 + FTS depth50 → RRF k=60 top20 → 精排 → store 回查\n\t * (owner/suite/scope/过期, 单一可见性路径, 不足 limit 不回填)。降级层级\n\t * (2026-10-05 裁决 A「宁漏勿错」, 每次降级一条结构化日志, 状态转 degraded\n\t * 可自愈): 精排失败 → RRF 序 + degraded 标记 (不闩锁, 下次重试); 向量失败\n\t * (embed/KNN) → FTS-only + degraded; FTS 失败 → KNN-only + degraded;\n\t * 双通道皆败 → unavailable (语义召回不可用, memory_list 仍是列表入口;\n\t * recency 枚举兜底已移除 — 语义查询返回\"最近的几条\"是对查询语义的错答)。\n\t * degraded 标记对显式召回可见; auto-recall 见标记零注入。\n\t */\n\n\t/** 一次语义召回的中间候选: atom + 融合/降级序位 (精排并列打破用)。 */\n\tinterface RecallCandidateV1 {\n\t\treadonly atom: MemoryAtomV1;\n\t\treadonly position: number;\n\t}\n\n\t/** 出线事实行 (含精排可选分): score 缺席 = 该路径未产生精排分。 */\n\tinterface ScoredRecallFactV1 {\n\t\treadonly memoryId: string;\n\t\treadonly statement: string;\n\t\treadonly confidence: number;\n\t\treadonly position: number;\n\t\treadonly score?: number;\n\t}\n\n\ttype SemanticRecallOutcomeV1 =\n\t\t| {\n\t\t\t\treadonly kind: \"served\";\n\t\t\t\treadonly facts: readonly MemoryFact[];\n\t\t\t\treadonly candidateCount: number;\n\t\t\t\treadonly suiteFilter: MemorySuiteFilterStatsV1;\n\t\t\t\t/**\n\t\t\t\t * 降级原因短语 (2026-10-05 裁决 A「宁漏勿错」): 存在即本次结果未经\n\t\t\t\t * 精排打分。显式召回照常服务并携带该标记 (模型可见); auto-recall\n\t\t\t\t * 见标记零注入。\n\t\t\t\t */\n\t\t\t\treadonly degraded?: string;\n\t\t }\n\t\t| { readonly kind: \"blocked\"; readonly reason: string }\n\t\t| { readonly kind: \"unavailable\"; readonly message: string };\n\n\t/** recency 排序 (occurredAt 降序, memoryId 升序打破并列 — 与 recall index 同口径)。 */\n\tconst recencyOrdered = (entries: readonly RecallCandidateV1[]): readonly RecallCandidateV1[] =>\n\t\t[...entries]\n\t\t\t.sort(\n\t\t\t\t(left, right) =>\n\t\t\t\t\tDate.parse(right.atom.occurredAt) - Date.parse(left.atom.occurredAt) ||\n\t\t\t\t\t(left.atom.memoryId < right.atom.memoryId ? -1 : 1),\n\t\t\t)\n\t\t\t.map((entry, position) => ({ atom: entry.atom, position }));\n\n\t/**\n\t * superseded 过滤 (S3-B, store 回查后 / 精排前): 被某条在场候选的 supersedes\n\t * 关系指向的旧条目不再出线 — 合并后同义查询只出新条目。\"新条目在场\"以候选\n\t * 集为界: 继任条目被遗忘 (purge/过期) 后其关系随之消失, 旧条目自然恢复出线。\n\t * memoryId 主键直查不经此过滤 (引用不失效), memory_list 管理面不过滤\n\t * (nothing silently dropped — 用户可显式 forget 旧条目)。\n\t */\n\tconst filterSupersededCandidates = (candidates: readonly RecallCandidateV1[]): readonly RecallCandidateV1[] => {\n\t\tconst superseded = new Set<string>();\n\t\tfor (const entry of candidates) {\n\t\t\tfor (const relation of entry.atom.relations) {\n\t\t\t\tif (relation.kind === \"supersedes\" && relation.targetMemoryId !== undefined) {\n\t\t\t\t\tsuperseded.add(relation.targetMemoryId);\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\t\tif (superseded.size === 0) return candidates;\n\t\treturn candidates.filter((entry) => !superseded.has(entry.atom.memoryId));\n\t};\n\n\t/**\n\t * store 回查 (正确性关键): 候选 id 逐条走与旧锚点通道相同的可见性单一路径\n\t * (owner + suite 读边界 + 过期, 单一路径); suite 场景下不可见 id 按 legacy/\n\t * 外套件区分计数 (suiteFilter 可观测), 不可见者丢弃且不回填 (诚实分页)。\n\t */\n\tconst lookupCandidates = (\n\t\trankedIds: readonly (readonly [memoryId: string, position: number])[],\n\t\tsuiteId: string | undefined,\n\t): { readonly candidates: readonly RecallCandidateV1[]; readonly suiteFilter: MemorySuiteFilterStatsV1 } => {\n\t\tconst candidates: RecallCandidateV1[] = [];\n\t\tlet legacySkipped = 0;\n\t\tlet foreignSuiteSkipped = 0;\n\t\tfor (const [memoryId, position] of rankedIds) {\n\t\t\tconst record = deps.store.get(memoryId, {\n\t\t\t\towner: deps.owner,\n\t\t\t\t...(suiteId === undefined ? {} : { suiteId }),\n\t\t\t});\n\t\t\tif (record !== undefined) {\n\t\t\t\tcandidates.push({ atom: record.atom, position });\n\t\t\t\tcontinue;\n\t\t\t}\n\t\t\tif (suiteId !== undefined) {\n\t\t\t\tconst unfiltered = deps.store.get(memoryId, { owner: deps.owner });\n\t\t\t\tif (unfiltered !== undefined) {\n\t\t\t\t\tif (unfiltered.atom.suiteId === undefined) legacySkipped += 1;\n\t\t\t\t\telse foreignSuiteSkipped += 1;\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\t\treturn { candidates, suiteFilter: { legacySkipped, foreignSuiteSkipped } };\n\t};\n\n\t/**\n\t * egress 门重放 (设计 §11 MUST run): 对最终候选统一执行, 与旧锚点 packet 同\n\t * 一 gate、同一 factInfo 来源 (store); blocked 即 fail-closed, 该次 recall\n\t * 返回 failed packet (来源未登记的事实永不越过出界面)。\n\t */\n\tconst egressGate = (candidates: readonly RecallCandidateV1[]) => {\n\t\tconst factInfo: Record<string, MemoryEgressFactInfoV1 | undefined> = {};\n\t\tfor (const entry of candidates) {\n\t\t\tfactInfo[entry.atom.memoryId] = {\n\t\t\t\tsourceRefs: entry.atom.sourceRefs,\n\t\t\t\tpayload: entry.atom.payload as Record<string, unknown>,\n\t\t\t};\n\t\t}\n\t\treturn deps.egressPolicy.apply(\n\t\t\t{\n\t\t\t\toperationId: `memory_recall:${deps.sessionId}:${deps.identityHash(candidates.map((entry) => entry.atom.memoryId).join(\"\\u0000\"))}`,\n\t\t\t\tstatus: \"completed\",\n\t\t\t\tfacts: candidates.map((entry) => ({\n\t\t\t\t\tmemoryId: entry.atom.memoryId,\n\t\t\t\t\tcontentRevision: entry.atom.contentRevision,\n\t\t\t\t\tstatement: JSON.stringify(entry.atom.payload),\n\t\t\t\t\tconfidence: entry.atom.confidence,\n\t\t\t\t})),\n\t\t\t\tqueryPlan: { lookupUsed: false, filtersApplied: [], scorerVersion: \"semantic-rrf@1\" },\n\t\t\t\tattempts: 0,\n\t\t\t\tnarrowingHints: [],\n\t\t\t\tomittedCount: 0,\n\t\t\t\ttruncated: false,\n\t\t\t\ttruncationScope: \"none\",\n\t\t\t\tbudget: {\n\t\t\t\t\tcandidateBudget: 0,\n\t\t\t\t\tmodelInspectionLimit: 0,\n\t\t\t\t\tfinalResultLimit: 0,\n\t\t\t\t\tused: { candidateCount: 0, modelInspectedCount: 0, finalResultCount: 0 },\n\t\t\t\t},\n\t\t\t} satisfies MemoryRecallPacketV1,\n\t\t\tfactInfo,\n\t\t);\n\t};\n\n\tconst recallSemantically = async (input: {\n\t\treadonly query: string;\n\t\treadonly domain: SuiteMemoryDomainV1;\n\t\treadonly limit: number;\n\t\t/**\n\t\t * 自动召回质量门槛 (错误教训与召回质量护栏设计 §2.1, 内部执行缝):\n\t\t * memory_recall 工具 schema 不含该参数, 显式召回行为不变; auto-recall\n\t\t * 钩子经内部闭包恒传。仅精排实际产生 score 且过门槛的候选保留 (裁决 A\n\t\t * 收紧: 无 score 候选一律丢弃)。\n\t\t */\n\t\treadonly minScore?: number;\n\t}): Promise<SemanticRecallOutcomeV1> => {\n\t\t// 后台预热不阻塞读侧 (2026-10-05 流程裁决「后台预热、不阻塞提问」):\n\t\t// 下载/加载进行中时 recall 直接返回 warming 不可用包, 不等待 vectorQueue\n\t\t// (队列此时可能正背着启动对账或下载任务) 与 ensure 单飞。\n\t\tif (deps.warming()) {\n\t\t\treturn {\n\t\t\t\tkind: \"unavailable\",\n\t\t\t\tmessage: \"memory models are loading in the background; recall becomes available once they are ready\",\n\t\t\t};\n\t\t}\n\t\t// 读侧先等写侧队列在调用时刻已入队的部分 (待处理的投影写入 + 启动对账\n\t\t// 已入队的块), 保证写入→召回 FIFO: 本调用之前提交的写入其投影先落地,\n\t\t// 不与后台对账/埋点竞态。启动对账的分块自续 (RECONCILE_CHUNK_UPSERTS)\n\t\t// 在块完成时才入队下一块, 未入队的后续块不阻塞本次召回——未回填条目由\n\t\t// store 回查与 memory_list 兜底, 残行由后续对账继续补齐。\n\t\tawait deps.settleProjection();\n\t\tconst channel = await deps.ensureChannel();\n\t\tif (channel === undefined) {\n\t\t\tif (deps.declined()) {\n\t\t\t\treturn {\n\t\t\t\t\tkind: \"unavailable\",\n\t\t\t\t\tmessage:\n\t\t\t\t\t\t\"memory was declined on this machine — set memory.mode (light|full) in settings.json and restart the session to re-enable\",\n\t\t\t\t};\n\t\t\t}\n\t\t\tconst consentAsk = deps.consentAsk();\n\t\t\tif (consentAsk !== undefined) {\n\t\t\t\treturn { kind: \"unavailable\", message: consentAsk.message };\n\t\t\t}\n\t\t\tconst failure = deps.failure();\n\t\t\treturn {\n\t\t\t\tkind: \"unavailable\",\n\t\t\t\tmessage:\n\t\t\t\t\tfailure === undefined\n\t\t\t\t\t\t? `memory channel is unavailable (state=${deps.rawState()})`\n\t\t\t\t\t\t: `memory enabling failed: ${failure.reason} — actions: ${failure.actions.join(\" | \")}`,\n\t\t\t};\n\t\t}\n\t\tconst suiteId = input.domain.kind === \"suite\" ? input.domain.suiteId : undefined;\n\t\tlet vectorFailed = false;\n\t\tlet ftsFailed = false;\n\t\tlet knnHits: readonly MemoryVectorHitV1[] | undefined;\n\t\tlet ftsHits: readonly MemoryVectorHitV1[] | undefined;\n\t\ttry {\n\t\t\tconst [queryVector] = await channel.embed.embed([channel.queryPrefix + input.query]);\n\t\t\tknnHits = await channel.index.queryKnn(queryVector, {\n\t\t\t\towner: deps.owner,\n\t\t\t\tlimit: HYBRID_CHANNEL_DEPTH,\n\t\t\t});\n\t\t} catch (error) {\n\t\t\tvectorFailed = true;\n\t\t\tdeps.logger?.warn(\"memory recall degraded: the vector channel failed; continuing on the FTS channel\", {\n\t\t\t\terror: error instanceof Error ? error.message : String(error),\n\t\t\t});\n\t\t}\n\t\ttry {\n\t\t\tftsHits = await channel.index.queryFts(input.query, {\n\t\t\t\towner: deps.owner,\n\t\t\t\tlimit: HYBRID_CHANNEL_DEPTH,\n\t\t\t});\n\t\t} catch (error) {\n\t\t\tftsFailed = true;\n\t\t\tdeps.logger?.warn(\"memory recall degraded: the FTS channel failed\", {\n\t\t\t\terror: error instanceof Error ? error.message : String(error),\n\t\t\t});\n\t\t}\n\n\t\tlet candidates: readonly RecallCandidateV1[];\n\t\tlet suiteFilter: MemorySuiteFilterStatsV1 = { legacySkipped: 0, foreignSuiteSkipped: 0 };\n\t\tlet channelDegraded = false;\n\t\t// 裁决 A: 降级原因短语, 透出到工具结果的 degraded 字段 (无精排分数路径)。\n\t\tlet degradedReason: string | undefined;\n\t\tif (!vectorFailed && !ftsFailed && knnHits !== undefined && ftsHits !== undefined) {\n\t\t\t// 主路径: 双通道 RRF k=60 融合 top20。\n\t\t\tconst pool = rrfFuseRankings([ftsHits.map((hit) => hit.memoryId), knnHits.map((hit) => hit.memoryId)]);\n\t\t\tconst lookup = lookupCandidates(\n\t\t\t\tpool.map((memoryId, position) => [memoryId, position] as const),\n\t\t\t\tsuiteId,\n\t\t\t);\n\t\t\tcandidates = lookup.candidates;\n\t\t\tsuiteFilter = lookup.suiteFilter;\n\t\t} else if (ftsHits !== undefined) {\n\t\t\t// 向量失败: FTS-only + recency 排序。\n\t\t\tchannelDegraded = true;\n\t\t\tdegradedReason = \"vector channel failed; full-text-only results, not reranked\";\n\t\t\tconst lookup = lookupCandidates(\n\t\t\t\tftsHits.map((hit, position) => [hit.memoryId, position] as const),\n\t\t\t\tsuiteId,\n\t\t\t);\n\t\t\tcandidates = recencyOrdered(lookup.candidates);\n\t\t\tsuiteFilter = lookup.suiteFilter;\n\t\t} else if (knnHits !== undefined) {\n\t\t\t// FTS 失败: KNN-only + recency 排序 (对称降级)。\n\t\t\tchannelDegraded = true;\n\t\t\tdegradedReason = \"FTS channel failed; vector-only results, not reranked\";\n\t\t\tconst lookup = lookupCandidates(\n\t\t\t\tknnHits.map((hit, position) => [hit.memoryId, position] as const),\n\t\t\t\tsuiteId,\n\t\t\t);\n\t\t\tcandidates = recencyOrdered(lookup.candidates);\n\t\t\tsuiteFilter = lookup.suiteFilter;\n\t\t} else {\n\t\t\t// 双通道皆败 = 语义召回能力不可用 (2026-10-05 裁决 A): 显式召回返回\n\t\t\t// 结构化 unavailable (memory_list 仍是列表入口), recency 枚举兜底从\n\t\t\t// 语义召回路径移除 — 语义查询返回\"最近的几条\"是对查询语义的错答。\n\t\t\tdeps.markDegraded();\n\t\t\tdeps.logger?.warn(\"memory recall unavailable: both the vector and FTS channels failed\");\n\t\t\treturn {\n\t\t\t\tkind: \"unavailable\",\n\t\t\t\tmessage:\n\t\t\t\t\t\"memory recall is unavailable (both retrieval channels failed); use memory_list to enumerate this suite's memories\",\n\t\t\t};\n\t\t}\n\t\t// 运行期通道故障 → degraded (可自愈: 下一次全通 recall 回 ready)。\n\t\tif (channelDegraded) deps.markDegraded();\n\t\telse deps.recoverDegraded();\n\n\t\t// superseded 过滤 (S3-B): 回查后、egress/精排前 (主路径与全部降级路径\n\t\t// 统一收敛于此 — 被继任的旧条目任何通道都不再出线)。\n\t\tcandidates = filterSupersededCandidates(candidates);\n\n\t\tif (candidates.length === 0) {\n\t\t\treturn {\n\t\t\t\tkind: \"served\",\n\t\t\t\tfacts: [],\n\t\t\t\tcandidateCount: 0,\n\t\t\t\tsuiteFilter,\n\t\t\t\t...(degradedReason === undefined ? {} : { degraded: degradedReason }),\n\t\t\t};\n\t\t}\n\t\tconst egressOutcome = egressGate(candidates);\n\t\tif (egressOutcome.status === \"blocked\") return { kind: \"blocked\", reason: egressOutcome.reason };\n\t\tconst statementByMemoryId = new Map(egressOutcome.packet.facts.map((fact) => [fact.memoryId, fact.statement]));\n\t\tlet ordered: readonly ScoredRecallFactV1[] = candidates.map((entry) => ({\n\t\t\tmemoryId: entry.atom.memoryId,\n\t\t\tstatement: deps.unwrapEgressStatement(statementByMemoryId.get(entry.atom.memoryId) ?? \"\"),\n\t\t\tconfidence: entry.atom.confidence,\n\t\t\tposition: entry.position,\n\t\t}));\n\t\t// 精排 (仅主融合路径: 降级页的排序即其降级语义)。reranker 不可用或调用\n\t\t// 失败 → 保持 RRF 序并携带 degraded 标记; 调用失败不闩锁 (裁决 A: 下一次\n\t\t// 召回重试, 与组件层加载重试语义对齐), 每次失败记一条结构化日志。\n\t\tconst reranker = channel.reranker;\n\t\tif (!channelDegraded && reranker !== undefined) {\n\t\t\ttry {\n\t\t\t\tconst scores = await reranker.rerank(\n\t\t\t\t\tinput.query,\n\t\t\t\t\tordered.map((fact) => fact.statement),\n\t\t\t\t);\n\t\t\t\tordered = ordered\n\t\t\t\t\t.map((fact, position) => ({ ...fact, score: scores[position] }))\n\t\t\t\t\t.sort((left, right) => (right.score ?? 0) - (left.score ?? 0) || left.position - right.position);\n\t\t\t} catch (error) {\n\t\t\t\tdegradedReason ??= \"reranker failed; RRF order without rerank scores\";\n\t\t\t\tdeps.logger?.warn(\n\t\t\t\t\t\"memory reranker failed; recall serves the RRF order this round and retries the next recall\",\n\t\t\t\t\t{\n\t\t\t\t\t\terror: error instanceof Error ? error.message : String(error),\n\t\t\t\t\t},\n\t\t\t\t);\n\t\t\t}\n\t\t}\n\t\t// minScore 门槛 (§2.1, 裁决 A 收紧): 仅精排实际产生 score 且过门槛的候选\n\t\t// 参与过滤后结果 (无 score 的候选一律丢弃, 不再放行); 过滤先于 limit 截取。\n\t\t// minScore 只经 auto-recall 内部闭包传入, auto-recall 另按 degraded 标记\n\t\t// 零注入, 双保险钉住\"注入 ⟹ 全部条目有分且过门槛\"。\n\t\tconst minScore = input.minScore;\n\t\tconst eligible =\n\t\t\tminScore === undefined\n\t\t\t\t? ordered\n\t\t\t\t: ordered.filter((fact) => fact.score !== undefined && fact.score >= minScore);\n\t\treturn {\n\t\t\tkind: \"served\",\n\t\t\tfacts: eligible\n\t\t\t\t.slice(0, input.limit)\n\t\t\t\t.map(({ memoryId, statement, confidence }) => ({ memoryId, statement, confidence })),\n\t\t\tcandidateCount: ordered.length,\n\t\t\tsuiteFilter,\n\t\t\t...(degradedReason === undefined ? {} : { degraded: degradedReason }),\n\t\t};\n\t};\n\n\t/**\n\t * Context-scoped preference post-processing over the recalled facts: preference\n\t * atoms compete per (subject, key); the resolver drops out-of-scope preferences\n\t * and deterministically ranks the rest, and an unconfirmed ranking goes through\n\t * the scope-specificity disambiguator. Whatever remains unresolvable is\n\t * returned as an explicit conflict block — the recall never silently picks\n\t * between competing stored values. Non-preference facts pass through untouched.\n\t */\n\tfunction applyPreferenceContext(input: {\n\t\treadonly facts: readonly MemoryFact[];\n\t\treadonly context: PreferenceContextV1;\n\t\treadonly resolver: PreferenceResolverV1;\n\t\treadonly disambiguator: PreferenceDisambiguatorV1;\n\t\treadonly atomOf: (memoryId: string) => MemoryAtomV1 | undefined;\n\t}): { readonly facts: readonly MemoryFact[]; readonly conflicts: readonly RecallConflictV1[] } {\n\t\tconst { facts, context, resolver, disambiguator, atomOf } = input;\n\t\tconst preferenceAtoms: MemoryAtomV1[] = [];\n\t\tconst atomsByGroup = new Map<string, MemoryAtomV1[]>();\n\t\tconst preferenceMemoryIds = new Set<string>();\n\t\tfor (const fact of facts) {\n\t\t\tconst atom = atomOf(fact.memoryId);\n\t\t\tif (atom === undefined || atom.memoryKind !== \"preference\" || atom.preference === undefined) continue;\n\t\t\tpreferenceMemoryIds.add(fact.memoryId);\n\t\t\tpreferenceAtoms.push(atom);\n\t\t\tconst groupKey = `${atom.preference.subject}\\u0000${atom.preference.key}`;\n\t\t\tconst group = atomsByGroup.get(groupKey) ?? [];\n\t\t\tgroup.push(atom);\n\t\t\tatomsByGroup.set(groupKey, group);\n\t\t}\n\t\tif (preferenceMemoryIds.size === 0) return { facts, conflicts: [] };\n\n\t\tconst keptMemoryIds = new Set<string>();\n\t\tconst conflicts: RecallConflictV1[] = [];\n\t\tfor (const decision of resolver.resolve(preferenceAtoms, context)) {\n\t\t\tconst groupAtoms = atomsByGroup.get(`${decision.subject}\\u0000${decision.key}`) ?? [];\n\t\t\tif (decision.status === \"applied\") {\n\t\t\t\tif (decision.winnerMemoryId === undefined) {\n\t\t\t\t\tthrow new Error(`applied preference decision without a winnerMemoryId: ${JSON.stringify(decision)}`);\n\t\t\t\t}\n\t\t\t\tkeptMemoryIds.add(decision.winnerMemoryId);\n\t\t\t\tcontinue;\n\t\t\t}\n\t\t\tif (decision.status === \"instruction_override\") {\n\t\t\t\t// No stored fact survives a current-instruction override (this slice has\n\t\t\t\t// no input source for currentInstructions, so the branch stays inert).\n\t\t\t\tcontinue;\n\t\t\t}\n\t\t\tif (decision.status === \"conflict_unconfirmed\") {\n\t\t\t\tconst outcome = disambiguator.disambiguate(\n\t\t\t\t\tgroupAtoms.map((atom) => ({\n\t\t\t\t\t\tmemoryId: atom.memoryId,\n\t\t\t\t\t\tpreferredValue: atom.preference!.preferredValue,\n\t\t\t\t\t\tscope: atom.preference!.scope,\n\t\t\t\t\t\tevidenceClass: atom.preference!.evidence.class,\n\t\t\t\t\t\tapplicabilityConfidence: atom.preference!.applicabilityConfidence,\n\t\t\t\t\t})),\n\t\t\t\t\tcontext,\n\t\t\t\t);\n\t\t\t\tif (outcome.status === \"resolved\") {\n\t\t\t\t\tkeptMemoryIds.add(outcome.winnerMemoryId);\n\t\t\t\t\tcontinue;\n\t\t\t\t}\n\t\t\t\tconflicts.push({\n\t\t\t\t\tsubject: decision.subject,\n\t\t\t\t\tkey: decision.key,\n\t\t\t\t\tcandidates: groupAtoms.map((atom) => {\n\t\t\t\t\t\tconst preference = atom.preference!;\n\t\t\t\t\t\treturn {\n\t\t\t\t\t\t\tmemoryId: atom.memoryId,\n\t\t\t\t\t\t\tstatement: deps.unwrapEgressStatement(deps.statementOf(atom.payload)),\n\t\t\t\t\t\t\tvalue: preference.preferredValue,\n\t\t\t\t\t\t\tevidenceClass: preference.evidence.class,\n\t\t\t\t\t\t\tconfidence: preference.applicabilityConfidence,\n\t\t\t\t\t\t};\n\t\t\t\t\t}),\n\t\t\t\t\texplanation: [...outcome.explanation],\n\t\t\t\t});\n\t\t\t\tcontinue;\n\t\t\t}\n\t\t\tthrow new Error(`unexpected preference decision status: ${JSON.stringify(decision)}`);\n\t\t}\n\n\t\treturn {\n\t\t\tfacts: facts.filter((fact) => !preferenceMemoryIds.has(fact.memoryId) || keptMemoryIds.has(fact.memoryId)),\n\t\t\tconflicts,\n\t\t};\n\t}\n\n\t/**\n\t * Parses the optional recall context (scenes/projects/tasks) into a preference\n\t * resolution context. Returns undefined when absent so the no-context recall\n\t * path stays byte-identical to the pre-disambiguation behavior.\n\t */\n\tfunction parseRecallContext(value: unknown): PreferenceContextV1 | undefined {\n\t\tif (value === undefined) return undefined;\n\t\tif (!deps.isPlainObject(value)) throw new Error(\"context must be an object\");\n\t\tconst stringArray = (field: \"scenes\" | \"projects\" | \"tasks\"): readonly string[] | undefined => {\n\t\t\tconst entries: unknown = value[field];\n\t\t\tif (entries === undefined) return undefined;\n\t\t\tif (!Array.isArray(entries)) throw new Error(`context.${field} must be an array of strings`);\n\t\t\tfor (const entry of entries) {\n\t\t\t\tif (typeof entry !== \"string\") throw new Error(`context.${field} entries must be strings`);\n\t\t\t}\n\t\t\treturn entries;\n\t\t};\n\t\tconst scenes = stringArray(\"scenes\");\n\t\tconst projects = stringArray(\"projects\");\n\t\tconst tasks = stringArray(\"tasks\");\n\t\treturn {\n\t\t\t...(scenes === undefined ? {} : { scenes }),\n\t\t\t...(projects === undefined ? {} : { projects }),\n\t\t\t...(tasks === undefined ? {} : { tasks }),\n\t\t};\n\t}\n\n\t// 提取为具名闭包: 除模型工具面外, 下方的 auto-recall 钩子直接调用同一 execute\n\t// (单参输入约定, memoryToolInput 取第一参), 不经 runtime.invokeTool —— 钩子\n\t// 随插件注册/卸载, 与工具面同生命周期, 替换工具面不会重接自动召回。\n\tconst recallExecute = async (first: unknown, second?: unknown): Promise<unknown> => {\n\t\tconst input = deps.memoryToolInput(first, second);\n\t\tdeps.assertNonEmptyString(input.query, \"query\");\n\t\tconst rawLimit: unknown = input.limit;\n\t\tconst limit =\n\t\t\trawLimit === undefined\n\t\t\t\t? DEFAULT_RECALL_LIMIT\n\t\t\t\t: ((): number => {\n\t\t\t\t\t\tif (\n\t\t\t\t\t\t\ttypeof rawLimit !== \"number\" ||\n\t\t\t\t\t\t\t!Number.isSafeInteger(rawLimit) ||\n\t\t\t\t\t\t\trawLimit < 1 ||\n\t\t\t\t\t\t\trawLimit > 100\n\t\t\t\t\t\t) {\n\t\t\t\t\t\t\tthrow new Error(\"limit must be an integer between 1 and 100\");\n\t\t\t\t\t\t}\n\t\t\t\t\t\treturn rawLimit;\n\t\t\t\t\t})();\n\t\t// 内部执行缝 (§2.1): auto-recall 闭包经本字段恒传质量门槛; memory_recall\n\t\t// 工具 schema 不含该参数 (工具面恒 undefined), 显式召回行为不变。\n\t\tconst rawMinScore: unknown = input.minScore;\n\t\tconst minScore =\n\t\t\trawMinScore === undefined\n\t\t\t\t? undefined\n\t\t\t\t: ((): number => {\n\t\t\t\t\t\tif (typeof rawMinScore !== \"number\" || !Number.isFinite(rawMinScore)) {\n\t\t\t\t\t\t\tthrow new Error(\"minScore must be a finite number\");\n\t\t\t\t\t\t}\n\t\t\t\t\t\treturn rawMinScore;\n\t\t\t\t\t})();\n\t\tconst context = parseRecallContext(input.context);\n\t\tconst { domain } = deps.resolveDomain();\n\n\t\t// enabling 失败 = 本实例终态 (mode 记录回退 off): 记忆工具返回携带原因\n\t\t// 与五选一行动清单的不可用 packet (设计 §3.2 硬性要求), 不再重试。\n\t\tif (deps.rawState() === \"failed\") {\n\t\t\tconst failure = deps.failure() ?? { reason: \"memory enabling failed\", actions: [] as const };\n\t\t\treturn {\n\t\t\t\tstatus: \"unavailable\",\n\t\t\t\tstate: \"failed\",\n\t\t\t\tfacts: [] as MemoryFact[],\n\t\t\t\terror: `memory enabling failed: ${failure.reason}`,\n\t\t\t\tactions: failure.actions,\n\t\t\t};\n\t\t}\n\t\t// 征询门早退 (2026-10-05 用户裁决): 拒绝 = 本实例按模式关闭对待; 待征询 =\n\t\t// 返回带行动清单的 consent-required packet (后续 recall 不再反复探测文件)。\n\t\tif (deps.declined()) {\n\t\t\treturn {\n\t\t\t\tstatus: \"unavailable\",\n\t\t\t\tstate: \"off\",\n\t\t\t\tfacts: [] as MemoryFact[],\n\t\t\t\terror: \"memory was declined on this machine — set memory.mode (light|full) in settings.json and restart the session to re-enable\",\n\t\t\t\tactions: [] as const,\n\t\t\t};\n\t\t}\n\t\tconst consentAsk = deps.consentAsk();\n\t\tif (consentAsk !== undefined) {\n\t\t\treturn {\n\t\t\t\tstatus: \"unavailable\",\n\t\t\t\tstate: \"consent-required\",\n\t\t\t\tfacts: [] as MemoryFact[],\n\t\t\t\terror: consentAsk.message,\n\t\t\t\tactions: consentAsk.actions,\n\t\t\t};\n\t\t}\n\n\t\t// 主键快路径 (memoryIdEquals 语义保留, 幂等/引用而非检索): query 恰为本\n\t\t// 域可见 memoryId 时直接取该条, 不依赖向量通道 (enabling 期间也可用)。\n\t\tlet outcome: SemanticRecallOutcomeV1;\n\t\tconst directRecord = deps.store.get(input.query.trim(), {\n\t\t\towner: deps.owner,\n\t\t\t...(domain.kind === \"suite\" ? { suiteId: domain.suiteId } : {}),\n\t\t});\n\t\tif (directRecord !== undefined) {\n\t\t\tconst gate = egressGate([{ atom: directRecord.atom, position: 0 }]);\n\t\t\tif (gate.status === \"blocked\") {\n\t\t\t\treturn { status: \"failed\", facts: [] as MemoryFact[], error: `egress blocked: ${gate.reason}` };\n\t\t\t}\n\t\t\tconst statementByMemoryId = new Map(gate.packet.facts.map((fact) => [fact.memoryId, fact.statement]));\n\t\t\toutcome = {\n\t\t\t\tkind: \"served\",\n\t\t\t\tfacts: [\n\t\t\t\t\t{\n\t\t\t\t\t\tmemoryId: directRecord.atom.memoryId,\n\t\t\t\t\t\tstatement: deps.unwrapEgressStatement(statementByMemoryId.get(directRecord.atom.memoryId) ?? \"\"),\n\t\t\t\t\t\tconfidence: directRecord.atom.confidence,\n\t\t\t\t\t},\n\t\t\t\t],\n\t\t\t\tcandidateCount: 1,\n\t\t\t\tsuiteFilter: { legacySkipped: 0, foreignSuiteSkipped: 0 },\n\t\t\t\t// 裁决 A 缝隙收口 (2026-10-05 复审后用户裁决「修复」): 直查是引用\n\t\t\t\t// 语义、未经精排 — 显式召回如实带标记, auto-recall 见标记零注入\n\t\t\t\t// (此前用户消息恰为 memoryId 时会绕过质量门注入该条)。\n\t\t\t\tdegraded: \"primary-key direct lookup (reference semantics, not reranked)\",\n\t\t\t};\n\t\t} else {\n\t\t\ttry {\n\t\t\t\toutcome = await recallSemantically({\n\t\t\t\t\tquery: input.query,\n\t\t\t\t\tdomain,\n\t\t\t\t\tlimit,\n\t\t\t\t\t...(minScore === undefined ? {} : { minScore }),\n\t\t\t\t});\n\t\t\t} catch (error) {\n\t\t\t\t// 意外异常 (管线 throw) → degraded packet: 结构化返回, 不抛给模型。\n\t\t\t\tdeps.markDegraded();\n\t\t\t\tconst message = error instanceof Error ? error.message : String(error);\n\t\t\t\tdeps.logger?.warn(\"memory recall degraded: the recall pipeline threw; returning a degraded packet\", {\n\t\t\t\t\terror: message,\n\t\t\t\t});\n\t\t\t\treturn { status: \"failed\", facts: [] as MemoryFact[], error: message };\n\t\t\t}\n\t\t}\n\t\tif (outcome.kind === \"blocked\") {\n\t\t\treturn { status: \"failed\", facts: [] as MemoryFact[], error: `egress blocked: ${outcome.reason}` };\n\t\t}\n\t\tif (outcome.kind === \"unavailable\") {\n\t\t\t// 首次调用即在装配中途失败时, vectorState 在本调用内才翻成 failed —\n\t\t\t// 这里同样必须携带 enabling 失败的原因 + 行动清单 (设计 §3.2 硬性\n\t\t\t// 要求: 终态的第一份可见 packet 就是完整的), 不等下一次调用补。\n\t\t\t// (vectorState 是可变闭包状态, 语义检索内部会改写它, 入口预检的\n\t\t\t// 收窄在此处不成立 — 经函数边界读取以如实表达。)\n\t\t\tconst stateAtEgress = (): MemoryCapabilityStateV1 => deps.rawState();\n\t\t\tconst failureAtEgress = deps.failure();\n\t\t\tconst consentAskAtEgress = deps.consentAsk();\n\t\t\tif (stateAtEgress() === \"failed\" && failureAtEgress !== undefined) {\n\t\t\t\treturn {\n\t\t\t\t\tstatus: \"unavailable\",\n\t\t\t\t\tstate: \"failed\",\n\t\t\t\t\tfacts: [] as MemoryFact[],\n\t\t\t\t\terror: `memory enabling failed: ${failureAtEgress.reason}`,\n\t\t\t\t\tactions: failureAtEgress.actions,\n\t\t\t\t};\n\t\t\t}\n\t\t\treturn {\n\t\t\t\tstatus: \"unavailable\",\n\t\t\t\tstate: stateAtEgress(),\n\t\t\t\tfacts: [] as MemoryFact[],\n\t\t\t\terror: outcome.message,\n\t\t\t\t...(stateAtEgress() === \"consent-required\" && consentAskAtEgress !== undefined\n\t\t\t\t\t? { actions: consentAskAtEgress.actions }\n\t\t\t\t\t: {}),\n\t\t\t};\n\t\t}\n\t\tconst facts = outcome.facts;\n\t\t// 增强段 (偏好消歧 + 关系扩散) 是增量元数据面: 内部抛错不得砸穿工具面\n\t\t// (结构化失败契约), 也不吞掉整个召回 — 降级为基础事实 + 一条结构化 warn。\n\t\tconst { finalFacts, resolvedConflicts, related, relatedOmittedCount } = await (async () => {\n\t\t\ttry {\n\t\t\t\tconst resolved =\n\t\t\t\t\tcontext === undefined\n\t\t\t\t\t\t? undefined\n\t\t\t\t\t\t: applyPreferenceContext({\n\t\t\t\t\t\t\t\tfacts,\n\t\t\t\t\t\t\t\tcontext,\n\t\t\t\t\t\t\t\tresolver: deps.preferenceResolver,\n\t\t\t\t\t\t\t\tdisambiguator: deps.preferenceDisambiguator,\n\t\t\t\t\t\t\t\tatomOf: (memoryId) => deps.store.get(memoryId, { owner: deps.owner })?.atom,\n\t\t\t\t\t\t\t});\n\t\t\t\t// 偏好消歧先于关系扩散: the diffusion seeds are the atoms the user\n\t\t\t\t// actually sees — preference facts dropped by the conflict resolution\n\t\t\t\t// never diffuse their relations.\n\t\t\t\tconst baseFacts = resolved === undefined ? facts : resolved.facts;\n\t\t\t\tconst seeds: MemoryAtomV1[] = [];\n\t\t\t\tfor (const fact of baseFacts) {\n\t\t\t\t\tconst atom = deps.store.get(fact.memoryId, { owner: deps.owner })?.atom;\n\t\t\t\t\tif (atom !== undefined) seeds.push(atom);\n\t\t\t\t}\n\t\t\t\t// The traversal graph is the recall domain's visible atom set (same\n\t\t\t\t// suite read boundary). Diffusion targets outside this set are dropped,\n\t\t\t\t// so relations never leak across suites.\n\t\t\t\tconst domainMemoryIds = new Set<string>();\n\t\t\t\tconst graph: MemoryAtomV1[] = [];\n\t\t\t\tfor (const entry of listDomainMemories({ store: deps.store }, { owner: deps.owner, domain }).entries) {\n\t\t\t\t\tdomainMemoryIds.add(entry.memoryId);\n\t\t\t\t\tconst atom = deps.store.get(entry.memoryId, { owner: deps.owner })?.atom;\n\t\t\t\t\tif (atom !== undefined) graph.push(atom);\n\t\t\t\t}\n\t\t\t\tconst related: MemoryNetworkExpansionV1[] = [];\n\t\t\t\tlet relatedOmittedCount = 0;\n\t\t\t\tconst seenRelatedMemoryIds = new Set<string>();\n\t\t\t\tfor (const adapterResult of deps.memoryNetwork.expand({ seeds, graph })) {\n\t\t\t\t\tif (adapterResult.status !== \"completed\") continue;\n\t\t\t\t\tfor (const expansion of adapterResult.related) {\n\t\t\t\t\t\tif (!domainMemoryIds.has(expansion.memoryId)) continue;\n\t\t\t\t\t\tif (seenRelatedMemoryIds.has(expansion.memoryId)) continue;\n\t\t\t\t\t\tseenRelatedMemoryIds.add(expansion.memoryId);\n\t\t\t\t\t\tif (related.length >= MAX_RELATED_REFS) {\n\t\t\t\t\t\t\trelatedOmittedCount += 1;\n\t\t\t\t\t\t\tcontinue;\n\t\t\t\t\t\t}\n\t\t\t\t\t\trelated.push(expansion);\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t\treturn {\n\t\t\t\t\tfinalFacts: baseFacts,\n\t\t\t\t\tresolvedConflicts: resolved === undefined ? [] : resolved.conflicts,\n\t\t\t\t\trelated,\n\t\t\t\t\trelatedOmittedCount,\n\t\t\t\t};\n\t\t\t} catch (error) {\n\t\t\t\tdeps.logger?.warn(\n\t\t\t\t\t\"memory recall enhancement failed; serving the base facts without preference resolution and related references\",\n\t\t\t\t\t{ error: error instanceof Error ? error.message : String(error) },\n\t\t\t\t);\n\t\t\t\treturn {\n\t\t\t\t\tfinalFacts: facts,\n\t\t\t\t\tresolvedConflicts: [],\n\t\t\t\t\trelated: [] as MemoryNetworkExpansionV1[],\n\t\t\t\t\trelatedOmittedCount: 0,\n\t\t\t\t};\n\t\t\t}\n\t\t})();\n\t\treturn {\n\t\t\tstatus: finalFacts.length === 0 ? \"empty\" : \"completed\",\n\t\t\tfacts: finalFacts,\n\t\t\t// Domain visibility (P4): an empty result must not read as \"no memories\"\n\t\t\t// when the session fell back to the isolated legacy domain.\n\t\t\tdomain: domain.kind === \"suite\" ? domain.suiteId : \"legacy\",\n\t\t\t...(domain.kind === \"legacy\"\n\t\t\t\t? {\n\t\t\t\t\t\twarning:\n\t\t\t\t\t\t\t\"this session has no suite binding on its current branch; memory is using the isolated legacy domain (suite memories are not visible)\",\n\t\t\t\t\t}\n\t\t\t\t: {}),\n\t\t\t...(outcome.degraded === undefined ? {} : { degraded: outcome.degraded }),\n\t\t\t...(resolvedConflicts.length > 0 ? { conflicts: resolvedConflicts } : {}),\n\t\t\t...(domain.kind === \"suite\" ? { suiteFilter: outcome.suiteFilter } : {}),\n\t\t\tomittedCount: Math.max(0, outcome.candidateCount - finalFacts.length),\n\t\t\ttruncated: false,\n\t\t\t// References only — no statement/payload egress; the model fetches\n\t\t\t// content via memory_list or another memory_recall.\n\t\t\t...(related.length > 0\n\t\t\t\t? {\n\t\t\t\t\t\trelated: related.map((expansion) => ({\n\t\t\t\t\t\t\tmemoryId: expansion.memoryId,\n\t\t\t\t\t\t\trelationKind: expansion.relationKind,\n\t\t\t\t\t\t\thop: expansion.hop,\n\t\t\t\t\t\t\tweight: expansion.weight,\n\t\t\t\t\t\t\tvia: expansion.via,\n\t\t\t\t\t\t})),\n\t\t\t\t\t}\n\t\t\t\t: {}),\n\t\t\t...(relatedOmittedCount > 0 ? { relatedOmittedCount } : {}),\n\t\t};\n\t};\n\tconst recallTool = deps.registerTool({\n\t\tname: MEMORY_RECALL_TOOL_NAME,\n\t\tlabel: \"Recall memories\",\n\t\tpromptGuidelines: [MEMORY_TOOLS_GUIDE],\n\t\tdescription:\n\t\t\t\"Search this suite's durable memories with semantic recall: vector and full-text channels fuse (RRF) and the result is reranked; an exact memoryId retrieves that memory directly; the result may include related memory references (ids and relation metadata only — recall or list a referenced memory again for its content)\",\n\t\tparameters: recallSchema,\n\t\texecute: recallExecute,\n\t});\n\n\treturn { recallTool, recallExecute };\n}\n"]}
@@ -37,11 +37,21 @@ const recallSchema = Type.Object({
37
37
  query: Type.String({
38
38
  description: "Natural-language query; recall fuses semantic (vector) and full-text (BM25) matching and reranks the result. An exact memoryId retrieves that memory directly",
39
39
  }),
40
- limit: Type.Optional(Type.Integer({ description: "Maximum number of memories to return (1-100)", minimum: 1, maximum: 100 })),
40
+ limit: Type.Optional(Type.Integer({
41
+ description: "Maximum number of memories to return (1-100, default 5)",
42
+ minimum: 1,
43
+ maximum: 100,
44
+ })),
41
45
  context: Type.Optional(Type.Object({
42
- scenes: Type.Optional(Type.Array(Type.String())),
43
- projects: Type.Optional(Type.Array(Type.String())),
44
- tasks: Type.Optional(Type.Array(Type.String())),
46
+ scenes: Type.Optional(Type.Array(Type.String(), {
47
+ description: "Scene tags of the current task, used to resolve preferences by scope",
48
+ })),
49
+ projects: Type.Optional(Type.Array(Type.String(), {
50
+ description: "Project identifiers of the current task, used to resolve preferences by scope",
51
+ })),
52
+ tasks: Type.Optional(Type.Array(Type.String(), {
53
+ description: "Task or goal identifiers of the current task, used to resolve preferences by scope",
54
+ })),
45
55
  }, {
46
56
  description: "Current scope context; when present, recalled preferences are resolved against it (out-of-scope preferences drop, competing values resolve by scope specificity or surface as explicit conflicts)",
47
57
  })),
@@ -644,6 +654,14 @@ export function createMemoryRecallPipeline(deps) {
644
654
  return {
645
655
  status: finalFacts.length === 0 ? "empty" : "completed",
646
656
  facts: finalFacts,
657
+ // Domain visibility (P4): an empty result must not read as "no memories"
658
+ // when the session fell back to the isolated legacy domain.
659
+ domain: domain.kind === "suite" ? domain.suiteId : "legacy",
660
+ ...(domain.kind === "legacy"
661
+ ? {
662
+ warning: "this session has no suite binding on its current branch; memory is using the isolated legacy domain (suite memories are not visible)",
663
+ }
664
+ : {}),
647
665
  ...(outcome.degraded === undefined ? {} : { degraded: outcome.degraded }),
648
666
  ...(resolvedConflicts.length > 0 ? { conflicts: resolvedConflicts } : {}),
649
667
  ...(domain.kind === "suite" ? { suiteFilter: outcome.suiteFilter } : {}),