/** * [ref](板 [ref]②/[ref]④ 两次点名的 server 接线半场)—— 零记忆面部署形的**持久记忆披露**。 * * 病(core [ref] 实测,[ref] 立案):一台没有任何持久记忆面的部署,用户说「请记住 X」,模型回一句 * 「已经记住了」——落盘处并不存在。用户带着「这条事实已入库」的信念离开,下一次会话才发现是空的。 * * core 5.26.0 的分工:引擎**能自证**只读的两态(engine 侧 `writeScope:null` / runner 侧 `handsReadOnly`) * 由引擎自动挂 `MEMORY_READONLY_NOTICE`;而「整台部署根本没有记忆面」这一形 core 只导出文本 * (`NO_PERSISTENT_MEMORY_NOTICE`)、**不注入** —— 逐字原文:「A deployment that composes its own prompt * should include this block exactly when no memory face is mounted」。组装归宿主,与 `MEMORY_SAFETY` * 同姿势。本模块就是那半场。 * * ⚠️ 两条边界,越界即双重注入/假话: * · **engine 在场就不是我们的座位**。哪怕 `memoryWrite:false`(writeScope null)——那恰恰是引擎自己 * 挂只读披露的状态,server 再叠一句口径不同的「你没有持久记忆」= 同一件事两种说法同框。 * · **推断到「有落盘路」就闭嘴**。判别式与 core 的 `rosterCanPersist` 同口径(已知落盘路 = 挂载且未 * 排除的文件写工具 / 可写 shell)。⚠️ 名单确实要读(codex 复审 [medium] 之后加的 `excludeTools` 一位: * roster 把落盘路**整批**卸掉时,手带在场也等于没有写通道),但负控仍然成立:必须**全部**已知落盘路 * 都不在才算断链 —— `excludeTools:["Write"]` 不算(Bash/Edit 还在),请求面摘一个工具名改不动结论。 */ import { type PromptProvider, type ToolCatalogEntry } from "@sema-agent/core"; import type { ScenarioHands } from "./hands-lane.js"; /** * 「已知落盘路」的判据,作用在 core **静态工具目录**的一行上(S-503 / [ref])—— 与 core 自己的 * `rosterCanPersist` 推断逐条同形,取的是同一批目录属性,不是它结论的抄件: * · `mountedBy` 落在 core 的 `HANDS_BAND_TAGS` 里 —— 手带 band 之外的定义(记忆面工具、调度族) * 不在本判别式说得着的面上:本判别式的输入全是**手带事实**(`hands` / `handsReadOnly` / * `excludeTools`),把一只 band 外的写工具算进来,就是拿一条根本没挂的通道去撤回披露(fail-open); * · `face.effect !== "read"`(缺席读 `write`,与 core 同缺省)—— 只读 shell 壳在这里出局,它与全壳 * 共用 `Bash` 这个 wire 名,靠的正是这一位分开; * · `face.pathTarget.access !== "read"`(文件写工具)**或** `face.family === "shell"`(可写 shell)。 * * 🔴 这条析取式**不是** effect=write 的全集:core 逐字裁过「a generic write-effect tool does NOT count * — a mail sender's side effect is not a memory store」。`TaskStop` 正是那种(effect=write、没有路径 * 目标、不是 shell),它在这条判据下自己就出局 —— 改前那张手抄名单要靠人记得别把它写进去。 * * 一条都派生不出来 ⇒ **抛**(不是回一个空集):空集会让下面那条判据读成「一条落盘路都不剩」⇒ 每台 * 有手的机器都开始披露「本部署没有持久记忆」,而那句话在那时是假的。目录换形是提货期事实(静态数据, * 与请求/环境无关:要么恒抛要么恒不抛),所以这一抛是**提货门**,不是运行期风险。 */ export declare function persistencePathToolNamesOf(entries: readonly ToolCatalogEntry[]): readonly string[]; /** * 「已知落盘路」的工具名闭集 —— **由 core 的静态工具目录派生**,本仓不再存第二份名表 * (S-503 / [ref];改前是手抄的四名表 Write / Edit / NotebookEdit / Bash,没有任何差集门)。 * * 为什么手抄在这一位上特别危险:core 改名/加一只写工具,手抄表静默失配成「没有一个写工具被排除」 * (fail-open 方向)—— `excludeTools` 把真正的落盘路全排掉了,而我们对着一张旧名单答「还剩」。 * 判据的属主现在是 {@link persistencePathToolNamesOf},值的属主是 core 的目录行。 */ export declare const KNOWN_PERSISTENCE_TOOL_NAMES: readonly string[]; /** 披露段在 `prompt.assembled` 清单里的段 id(`core/` 命名空间是 core 保留的,部署段一律自家前缀)。 */ export declare const NO_PERSISTENT_MEMORY_SECTION_ID = "sema/memory.no-persistent"; /** 判别式的全部输入 —— 每一位都是**部署/装配期事实**,没有一位来自请求体的自我表态。 */ export interface MemoryFaceFacts { /** 这一腿挂不挂记忆面 —— 由唯一属主 `memory-scope.ts` `memoryFaceMounted(spec.memory)` 算出(与 core `prepareMemory` * 同判:`normalizeMemorySpec(...)?.enabled === true`)。`false` = 引擎未接 / 多租户 dark / remote lane fail-closed / * S-405 `memory:"off"`(键在场但 `enabled:false`)—— 殊途同归:core 不会 prepare 记忆,也就不会挂任何披露。 * 🔴 不再收 `TaskSpec.memory` 本体:按「键在不在场」判是 7.84.0 前的病(合并复审 车HF F1)。 * 🔴 属主会**抛**(值闭包门没过的手搓 spec —— 单数 `scope` / 非 `"off"` 的 `capture`),理由与两个属主 * 的坐标逐字写在 `memory-scope.ts` 的 `memoryFaceMounted` 头注里;本判别式不接这条臂(接了就是在 * 披露轴上静默 fail-open)。 */ memoryFaceMounted: boolean; /** 部署声明 `MEMORY_PERSISTENCE_CAPABLE`(三态)。`true` = operator 自证有推断看不见的持久通道 * (自定义 writer / MCP 记忆服务)⇒ 披露会变成假话,撤回;`false` = 强制披露;缺席 ⇒ 走推断。 */ declaredCapable: boolean | undefined; /** 本请求命中场景的手带表态([ref] 判别位)。`none` = 该场景跑在不挂 `executionEnvFactory` 的 Runner 上, * core 的手带 band(Bash/Edit/Write/…)整批不 mount。 */ hands: ScenarioHands; /** 折完 governance+settings 后的最终 `handsReadOnly`(plan 模式 / autonomy 收窄)。手带只剩读腿。 */ handsReadOnly: boolean; /** 折完之后的最终 `TaskSpec.excludeAllTools`(S-493;core 7.8.0 [ref])。`true` = **整只**工具面卸空 * (core `prepareDeferClassify` 对它直接 `roster.unmountWhere(() => true)`)⇒ 手带在场也一条落盘路都不剩。 * 🔴 它与下面那张名单是**同一个语义面**(「哪些工具不在 roster 上」)的两种声明,判据合成一只 * {@link isUnmounted} —— 上游同版的姊妹谓词 `fullShellReachable` 也正是把 `spec.excludeAllTools !== true` * 当一个合取项(`prepare-inherited-gate.js` 亲读),本仓与它同口径。 */ excludeAllTools: boolean; /** 折完之后的最终 `TaskSpec.excludeTools`(roster 真卸载,不是延迟披露)。**全部**已知落盘路都被排掉 * ⇒ 手带在场也等于没有写通道 —— core 的 `rosterCanPersist` 也是按排除后的 roster 算的,这里跟上。 */ excludeTools: readonly string[] | undefined; /** 本部署的执行环境在 core 眼里算不算 **remote**(`isRemoteExecutionEnv`)。判别位 = `REMOTE_EXEC` 有没有 * 设 —— 注意 `host` 也算:core 的判别是**鸭子类型**(`workspaceHandle`/`execStream`/`suspendVM`/…), * 而我方的 `remote-env-host.ts` 这几件全实现,亲验坐实。 * 为什么这一位必须进判别式(codex 复审 round2 [high],已核真):core 的 `rosterCanPersist` 在 * `handsEnabled && isRemoteExecutionEnv` 时把 Write/Edit/NotebookEdit/Bash **全部不算**落盘路(沙箱盘 * 每任务即焚,够不着 host 记忆根)。而 server 在任何 remote 车道上默认让记忆引擎 dark —— 两件事合起来 * 正是**最常见的云部署形**:引擎不在、手带在,却一份持久面都没有。漏了这一位,那台机器就继续给 * 「已经记住了」的幻觉式回执 —— 恰恰是 [ref] 立案要消灭的形态。 */ remoteExecutionEnv: boolean; } /** * 该不该把 `NO_PERSISTENT_MEMORY_NOTICE` 组进本次任务的 system prompt。 * * 优先序(每一条都有独立理由,不是同一条判据的三种写法): * ① engine 在场 ⇒ **否**(引擎的座位,见文件头注); * ② 部署显式声明 ⇒ 声明说了算(`true` 撤回 / `false` 强制)—— operator 的部署事实压过我们的推断, * 与 core 对 `memoryPersistenceCapable` 的处置同向; * ③ 否则推断:有已知落盘路(手带在场、不是只读、不在 remote 车道、且没被卸载决定卸光)⇒ 否;没有 ⇒ 是。 */ export declare function shouldDiscloseNoPersistentMemory(facts: MemoryFaceFacts): boolean; /** * 把披露段组进一个 prompt provider,**按 provider 自己的钩子形分腿**(换形会改段身份与 append 座位): * · typed(`stableBlocks`)⇒ {@link mountNotice}(普通形追加 behavior 声明;已组装 identity 形接在其文本后)。 * · string(`stableSystem`)⇒ 接在 role 层文本之后(落进 `core/role.base` 段)。 * · 两个钩子都没有(= core 会用 `defaultPromptProvider`)⇒ **只发一条 typed 声明**,role 层留给 core 自己铸。 * * ⚠️ 最后一腿为什么不是「自铸 role 层」(2026-08-12 复扫 medium,已实测):自铸形 * `stableSystem: (ctx) => ${ctx.userSystemPrompt ?? DEFAULT_SYSTEM_PROMPT}\n\n${段落}` 把**调用方的** * systemPrompt 逐字变成了 provider 的输出。调用方传一份已组装 prompt(集成方/center 的常见形)时,装配器的 * 迁移卫就在 provider 输出上探到三锚 ⇒ pass-through 臂 ⇒ 保留集无 `core/role.append` ⇒ 已被 append 门放行的 * `appendSystemPrompt`/`settings.outputStyle` 静默蒸发(门在包装**之前**按未包装的 provider 判,两边分家)。 * 走 typed 声明腿就没有这条耦合:role 层仍由 core 按 `userSystemPrompt ?? DEFAULT_SYSTEM_PROMPT` 自己铸, * 我方只贡献一条 behavior 段,`providerDropsAppend` 的判决包装前后逐格相同。 */ export declare function withNoPersistentMemoryNotice(provider: PromptProvider | undefined): PromptProvider; //# sourceMappingURL=memory-notice.d.ts.map