@stardustlc/dsh-dream 0.10.0 → 0.12.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/lib/evidence.js CHANGED
@@ -56,6 +56,7 @@ export function verifyEvidence(claim, context) {
56
56
  return held('quote-missing');
57
57
  try {
58
58
  let text;
59
+ let toolOutput = false;
59
60
  if (claim.kind === 'local-artifact') {
60
61
  if (!context.cwd || !claim.artifactPath)
61
62
  return held('artifact-context-missing');
@@ -108,6 +109,7 @@ export function verifyEvidence(claim, context) {
108
109
  return held('not-human-message');
109
110
  if (entry.type !== 'user/message' && entry.type !== 'assistant/message' && entry.type !== 'tool/result')
110
111
  return held('record-not-visible');
112
+ toolOutput = entry.type === 'tool/result';
111
113
  text = visibleText(data.content ?? data.text ?? data.message ?? entry.data);
112
114
  }
113
115
  if (!normalized(text).includes(quote))
@@ -115,6 +117,10 @@ export function verifyEvidence(claim, context) {
115
117
  const hash = createHash('sha256').update(text, 'utf8').digest('hex');
116
118
  if (claim.sourceHash !== undefined && claim.sourceHash !== hash)
117
119
  return held('source-hash-mismatch');
120
+ // Finding text in a webpage/mail/tool result proves occurrence, not that its
121
+ // instructions are independently supported. Retain the reference for review.
122
+ if (toolOutput)
123
+ return { ...base, sourceHash: hash, verificationReason: 'tool-output-observed' };
118
124
  return { ...base, verification: 'read', sourceHash: hash, verificationReason: 'source-verified' };
119
125
  }
120
126
  catch {
package/lib/memory.d.ts CHANGED
@@ -1,4 +1,6 @@
1
1
  import type { Evidence, Lesson } from './knowledge.js';
2
+ /** A model recommendation or an old actor-less record is not human approval. */
3
+ export declare function isHumanAccepted(lesson: Lesson): boolean;
2
4
  /** A retrieval deadline is not a verification result; feedback never renews it. */
3
5
  export declare function reviewDeadline(lesson: Lesson): number | undefined;
4
6
  export declare function needsMemoryReview(lesson: Lesson, now?: number): boolean;
package/lib/memory.js CHANGED
@@ -1,3 +1,7 @@
1
+ /** A model recommendation or an old actor-less record is not human approval. */
2
+ export function isHumanAccepted(lesson) {
3
+ return lesson.state === 'usable' && lesson.review.decision === 'accepted' && lesson.review.actor === 'human';
4
+ }
1
5
  /** A retrieval deadline is not a verification result; feedback never renews it. */
2
6
  export function reviewDeadline(lesson) {
3
7
  const explicit = lesson.reviewAfter ? Date.parse(lesson.reviewAfter) : NaN;
@@ -16,7 +20,7 @@ export function needsMemoryReview(lesson, now = Date.now()) {
16
20
  /** Derived layers contain pointers; authoritative lessons and evidence remain untouched. */
17
21
  export function memoryDirectory(lessons, evidence, now = Date.now()) {
18
22
  const byId = new Map(evidence.map(item => [item.id, item]));
19
- const accepted = new Set(lessons.filter(item => item.review.decision === 'accepted').map(item => item.id));
23
+ const accepted = new Set(lessons.filter(isHumanAccepted).map(item => item.id));
20
24
  const entries = lessons.filter(item => item.state !== 'rejected').map(item => ({
21
25
  lessonId: item.id, revision: item.revision, title: item.title, topic: item.kind, scope: item.scope,
22
26
  state: item.state, reviewDue: needsMemoryReview(item, now),
package/lib/retrieval.js CHANGED
@@ -60,6 +60,8 @@ export function queryScopeLabel(q) {
60
60
  }
61
61
  /** 经验范围文案(面板与 dream_context 共用)。 */
62
62
  export function lessonScopeLabel(scope) {
63
+ if (scope.global === true)
64
+ return '全局';
63
65
  const project = trimmed(scope.projectId);
64
66
  const workspace = trimmed(scope.workspaceRoot);
65
67
  if (project !== undefined && workspace !== undefined)
@@ -68,8 +70,6 @@ export function lessonScopeLabel(scope) {
68
70
  return '项目 ' + project;
69
71
  if (workspace !== undefined)
70
72
  return '工作区 ' + workspace;
71
- if (scope.global === true)
72
- return '全局';
73
73
  return '未限定范围';
74
74
  }
75
75
  /** 证据摘要(面板与 dream_context 共用):独立支持数 + 已读/仅声明 + 脱敏摘要片段。 */
package/lib/tools.js CHANGED
@@ -9,7 +9,8 @@ import { existsSync, realpathSync } from 'node:fs';
9
9
  import { basename, dirname, join, resolve } from 'node:path';
10
10
  import { maskSecrets } from './mask.js';
11
11
  import { dreamStats, readDreams, saveDream, searchDreams } from './journal.js';
12
- import { applyBridge, bridgeDreams, previewBridge, rollbackBridge, selectBridgeLessons, } from './bridge.js';
12
+ import { isHumanAccepted } from './memory.js';
13
+ import { applyBridge, previewBridge, rollbackBridge, selectBridgeLessons, } from './bridge.js';
13
14
  import { digestSessionFile, listSessionFiles } from './sessions.js';
14
15
  import { KnowledgeError, lessonFingerprint, } from './knowledge.js';
15
16
  import { KnowledgeStore } from './knowledge-store.js';
@@ -438,12 +439,12 @@ export function buildDreamTools(config) {
438
439
  };
439
440
  const dreamBridge = {
440
441
  name: 'dream_bridge',
441
- description: '渡梦(M2):把已采纳且适用的经验桥接进项目 AGENTS.md 的 Dream 管理块(<!-- dsh-dream:start -->…<!-- dsh-dream:end -->),块外内容原样保留。mode:"preview" 只读看 diff;复核后 mode:"apply" 必须带 preview.expected.sha256(文件变了会 conflict 零写入);mode:"rollback" 带 backupId 恢复。缺省 mode 为 legacy 直写(已弃用,响应含 deprecation 提示)。资格:经验 state=usable,且 scope 与当前项目匹配(目标目录 realpath / 显式 projectId / global 且已采纳),频次不作准入。',
442
+ description: '渡梦:把人工采纳且适用的经验桥接进项目 AGENTS.md 的 Dream 管理块,块外内容原样保留。缺省 mode:"preview" 只读看 diff;复核后 mode:"apply" 必须带 preview.expected.sha256;mode:"rollback" 带 backupId 恢复。仅允许 review.actor=human 的已采纳经验,且 scope 匹配当前项目或经人工确认的全局范围。',
442
443
  parameters: compileParameters({
443
- mode: { type: 'string', enum: ['preview', 'apply', 'rollback'], description: 'preview=只读预览;apply=带 expectedSha256 落盘;rollback=按 backupId 恢复。缺省=legacy 直写(弃用)。' },
444
+ mode: { type: 'string', enum: ['preview', 'apply', 'rollback'], description: 'preview=只读预览(默认);apply=带 expectedSha256 落盘;rollback=按 backupId 恢复。' },
444
445
  path: { type: 'string', description: '目标文件(可选;缺省为执行上下文 cwd 下的 AGENTS.md,取不到 cwd 会报错不猜)。' },
445
446
  lessonIds: { type: 'array', items: { type: 'string' }, description: '参与桥接的经验 id(必须 usable 且 scope 匹配;缺省=按 store 顺序自动选取资格通过者)。' },
446
- maxLessons: { type: 'integer', description: '经验上限:M2 模式默认 5、硬上限 20;legacy 模式默认 10、上限 30。' },
447
+ maxLessons: { type: 'integer', description: '经验上限:默认 5、硬上限 20。' },
447
448
  expectedSha256: { oneOf: [{ type: 'string' }, { type: 'null' }], description: 'apply 必填:preview.expected.sha256 原样回传;新文件用 null;不符则 conflict 零写入。' },
448
449
  previewId: { type: 'string', description: 'apply 可带:preview.previewId,用于校验是同一份预览;块内容不一致 → preview-mismatch,需重新 preview。' },
449
450
  backupId: { type: 'string', description: 'rollback 必填:apply 成功返回的 backupId。' },
@@ -492,9 +493,9 @@ export function buildDreamTools(config) {
492
493
  },
493
494
  async execute(rawArgs, exec) {
494
495
  const args = asRecord(rawArgs);
495
- const mode = optionalString(args, 'mode');
496
- if (mode !== undefined && mode !== 'preview' && mode !== 'apply' && mode !== 'rollback') {
497
- throw new Error('mode 必须是 preview / apply / rollback(缺省为 legacy 直写)。');
496
+ const mode = optionalString(args, 'mode') ?? 'preview';
497
+ if (mode !== 'preview' && mode !== 'apply' && mode !== 'rollback') {
498
+ throw new Error('mode 必须是 preview / apply / rollback(缺省为 preview)。');
498
499
  }
499
500
  const explicitPath = optionalString(args, 'path');
500
501
  const cwd = execCwdOf(exec);
@@ -510,21 +511,6 @@ export function buildDreamTools(config) {
510
511
  }
511
512
  const resolvedPath = resolvedBridgePath(projectRoot, explicitPath);
512
513
  const maxRaw = args.maxLessons;
513
- const legacyMax = typeof maxRaw === 'number' && Number.isInteger(maxRaw) ? Math.min(30, Math.max(1, maxRaw)) : 10;
514
- if (mode === undefined) {
515
- const legacyTarget = explicitPath !== undefined ? resolve(explicitPath) : join(projectRoot, 'AGENTS.md');
516
- const legacyResult = bridgeDreams(cfg.journalDir, legacyTarget, legacyMax);
517
- return {
518
- ok: true,
519
- path: legacyTarget,
520
- ...(resolvedPath !== undefined ? { resolvedPath } : {}),
521
- ...legacyResult,
522
- deprecation: {
523
- code: 'legacy-write',
524
- message: '缺省 mode 为 legacy 直写(已弃用);建议改用 mode:"preview" → 复核 diff → mode:"apply" + expectedSha256(并带同一 previewId)。',
525
- },
526
- };
527
- }
528
514
  const maxLessons = typeof maxRaw === 'number' && Number.isInteger(maxRaw) ? maxRaw : undefined;
529
515
  const lessonIds = stringArray(args, 'lessonIds');
530
516
  const projectId = optionalString(args, 'projectId');
@@ -838,7 +824,9 @@ export function buildDreamTools(config) {
838
824
  return { items: [], skipped: [], scopeLabel, budget: { limit, maxChars, usedChars: 0 } };
839
825
  }
840
826
  const store = knowledgeStoreOf(cfg);
841
- const result = retrieveLessons(store.listLessons(), store.listEvidence(), retrievalQuery);
827
+ const lessons = store.listLessons().map(lesson => lesson.state === 'usable' && !isHumanAccepted(lesson)
828
+ ? { ...lesson, state: 'candidate' } : lesson);
829
+ const result = retrieveLessons(lessons, store.listEvidence(), retrievalQuery);
842
830
  const usedChars = result.items.reduce((total, item) => total + retrievedLessonChars(item), 0);
843
831
  return { items: result.items, skipped: result.skipped, scopeLabel, budget: { limit, maxChars, usedChars } };
844
832
  },
@@ -846,7 +834,7 @@ export function buildDreamTools(config) {
846
834
  };
847
835
  const dreamReview = {
848
836
  name: 'dream_review',
849
- description: '审梦:采纳/驳回/标记冲突或过期/追加验证证据,必须带 expectedRevision(revision 不符会拒绝且不写入)。模型调用 actor 一律记为 model,不得冒充用户采纳;attach-evidence 只追加证据,不自动改变 state。',
837
+ description: '审梦:建议采纳/驳回/标记冲突或过期/追加验证证据,必须带 expectedRevision。模型的 accept 与 resolve-conflict 只返回待人工确认的建议,不修改审阅状态;请用户在设置 → Dream 中确认采纳。attach-evidence 只追加证据,不自动改变 state。',
850
838
  parameters: compileParameters({
851
839
  action: { type: 'string', required: true, enum: [...REVIEW_ACTIONS], description: '审阅动作。' },
852
840
  lessonId: { type: 'string', description: '经验 ID(accept/reject/mark-*/attach-evidence 必填;resolve-conflict 可用 targetId 替代)。' },
@@ -862,6 +850,8 @@ export function buildDreamTools(config) {
862
850
  schema: baseSchema,
863
851
  render: (_args, value) => {
864
852
  const rec = asRecord(value);
853
+ if (rec.requiresHumanReview === true)
854
+ return [{ type: 'text', text: String(rec.message) }];
865
855
  return [{
866
856
  type: 'text',
867
857
  text: '经验 ' + asRecord(rec.lesson).id + ':' + rec.previousState + ' → ' + rec.state + '(revision ' + rec.revision + ')。',
@@ -899,6 +889,13 @@ export function buildDigestText(digest) {
899
889
  return lines.join('\n');
900
890
  }
901
891
  const REVIEW_ACTIONS = ['accept', 'reject', 'reopen', 'mark-stale', 'mark-disputed', 'resolve-conflict', 'attach-evidence'];
892
+ function humanReviewRecommendation(before) {
893
+ return {
894
+ ok: true, requiresHumanReview: true, lesson: before,
895
+ previousState: before.state, state: before.state, revision: before.revision,
896
+ message: '已建议人工审阅,经验状态未改变。请在设置 → Dream → 经验中核对来源与适用范围,再确认采纳或处理冲突。',
897
+ };
898
+ }
902
899
  /** actor comes from the trusted caller, never from tool or browser arguments. */
903
900
  export async function executeDreamReview(cfg, rawArgs, exec, actor = 'model') {
904
901
  const args = asRecord(rawArgs);
@@ -927,6 +924,11 @@ export async function executeDreamReview(cfg, rawArgs, exec, actor = 'model') {
927
924
  error.details = { currentRevision: before.revision };
928
925
  throw error;
929
926
  }
927
+ if (actor === 'model' && action === 'accept')
928
+ return humanReviewRecommendation(before);
929
+ if (actor === 'human' && action === 'accept' && before.scope.global && args.confirmGlobal !== true) {
930
+ throw new KnowledgeError('invalid', '这是全局经验。请确认将其用于所有项目后再采纳。');
931
+ }
930
932
  const affectedIdsArg = stringArray(args, 'affectedIds');
931
933
  const conflictWith = affectedIdsArg.length > 0 ? affectedIdsArg : stringArray(args, 'conflictWith');
932
934
  const note = optionalString(args, 'note');
@@ -1003,6 +1005,9 @@ export async function executeDreamReview(cfg, rawArgs, exec, actor = 'model') {
1003
1005
  const members = requireConflictGroup(store, lessonId, before, conflictWith);
1004
1006
  const group = members.map((member) => member.id);
1005
1007
  // 校验:所有当事方必须 disputed,且互相登记 conflictIds;否则零写入。
1008
+ if (actor === 'human' && args.confirmGlobal !== true && members.some(member => member.lesson.scope.global)) {
1009
+ throw new KnowledgeError('invalid', '冲突涉及全局经验。请确认将结果用于所有项目后再处理。');
1010
+ }
1006
1011
  for (const member of members) {
1007
1012
  if (member.lesson.state !== 'disputed') {
1008
1013
  throw new KnowledgeError('invalid', '冲突解析要求 ' + member.id + ' 处于 disputed(实际 ' + member.lesson.state + ')');
@@ -1015,6 +1020,8 @@ export async function executeDreamReview(cfg, rawArgs, exec, actor = 'model') {
1015
1020
  }
1016
1021
  const now = new Date().toISOString();
1017
1022
  const affectedIds = group.filter((id) => id !== lessonId);
1023
+ if (actor === 'model')
1024
+ return humanReviewRecommendation(before);
1018
1025
  const resolutionPayload = { kind: resolution, targetId: lessonId, affectedIds };
1019
1026
  const remainingOf = (member) => member.lesson.conflictIds.filter((id) => !group.includes(id));
1020
1027
  const planned = [];
@@ -131,6 +131,8 @@ export function createDreamActionHandler(options = {}) {
131
131
  seen.add(item.lessonId);
132
132
  return { id: item.lessonId, expectedRevision: item.expectedRevision, patch: {} };
133
133
  });
134
+ if (body.action === 'accept' && body.confirmGlobal !== true && updates.some(item => store.getLesson(item.id)?.scope.global))
135
+ throw new KnowledgeError('invalid', '所选经验包含全局范围。请确认将其用于所有项目后再采纳。');
134
136
  const lessons = store.updateLessonsBatch(updates, 'web-review-batch:' + key, body.action === 'accept' ? 'accepted' : 'rejected');
135
137
  return json(200, maskDeep({ ok: true, lessons, message: '已完成所选候选的审阅' }));
136
138
  }
package/lib/web.d.ts CHANGED
@@ -44,6 +44,7 @@ export interface KnowledgeWebPayload {
44
44
  lessons: Array<Lesson & {
45
45
  scopeLabel: string;
46
46
  evidenceSummary: string;
47
+ humanReviewRequired?: boolean;
47
48
  reviewDue?: boolean;
48
49
  nextReviewAt?: string;
49
50
  lastRetrievedAt?: string;
package/lib/web.js CHANGED
@@ -22,7 +22,7 @@ import { dreamStats, readDreams, searchDreams } from './journal.js';
22
22
  import { KnowledgeStore } from './knowledge-store.js';
23
23
  import { isDreamReference, maskSecrets } from './mask.js';
24
24
  import { buildEvidenceSummary, lessonScopeLabel } from './retrieval.js';
25
- import { memoryDirectory, needsMemoryReview, reviewDeadline } from './memory.js';
25
+ import { isHumanAccepted, memoryDirectory, needsMemoryReview, reviewDeadline } from './memory.js';
26
26
  import { readAutomaticState } from './automatic-state.js';
27
27
  /** 面板与浏览器说话的同源路由。 */
28
28
  export const DREAM_ROUTE = '/_dsh/dsh-dream/journal';
@@ -353,6 +353,7 @@ export function createKnowledgeWebHandler(options = {}) {
353
353
  const payload = {
354
354
  lessons: lessons.map((lesson) => maskDeep({
355
355
  ...lesson,
356
+ humanReviewRequired: lesson.state === 'usable' && !isHumanAccepted(lesson),
356
357
  scopeLabel: lessonScopeLabel(lesson.scope),
357
358
  evidenceSummary: buildEvidenceSummary(lesson, evidence),
358
359
  reviewDue: needsMemoryReview(lesson),
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@stardustlc/dsh-dream",
3
- "version": "0.10.0",
3
+ "version": "0.12.0",
4
4
  "description": "回顾会话、保存反思,并把经过核验和审阅的经验用于后续任务。",
5
5
  "type": "module",
6
6
  "main": "lib/index.js",
@@ -24,7 +24,7 @@
24
24
  "docs"
25
25
  ],
26
26
  "scripts": {
27
- "build": "tsc -p tsconfig.json",
27
+ "build": "tsc -p tsconfig.json && node scripts/build-client.mjs",
28
28
  "typecheck": "tsc -p tsconfig.json --noEmit",
29
29
  "test": "pnpm run build && node --test \"test/*.test.mjs\"",
30
30
  "prepublishOnly": "pnpm run build"
@@ -35,7 +35,9 @@
35
35
  },
36
36
  "client": {
37
37
  "inject": [
38
- "@deepseek-ai/dsh-client-ui-settings"
38
+ "@deepseek-ai/dsh-client-ui-settings",
39
+ "@deepseek-ai/dsh-client-ui-layout",
40
+ "@deepseek-ai/dsh-client-ui-sidebar"
39
41
  ],
40
42
  "platform": "web"
41
43
  }
@@ -9,6 +9,8 @@ description: 做梦协议:任务开始取回适用经验,长任务收尾沉
9
9
 
10
10
  但「梦」和「经验」是两条流水线:**第一人称感悟**是主观的,进梦境日记;**技术经验**必须可执行、有条件、有证据,先进候选,再经审阅才可能被任务检索取用。
11
11
 
12
+ 0.11.0 起,采纳只能由用户在面板确认。模型的 `dream_review accept` 和 `resolve-conflict` 只推荐审阅,不修改经验。自动复用与规则桥接只接受 `review.actor=human` 的已采纳经验;全局采纳需用户明确确认对所有项目生效。`dream_bridge` 默认只读预览;不再从旧日记直接写入项目规则。
13
+
12
14
  插件会在符合条件的真人主会话完成后,按预算自动整理有来源的项目候选,并在相关新任务前回用已采纳经验。普通闲聊、子代理和失败轮次跳过;新输入取消整理。用户无需记住触发词或手工填写经验。自动候选从未采纳;日记写入、采纳与规则应用是不同操作。宿主缺少自动接口或用户关闭功能时,使用下述工具流程。
13
15
 
14
16
  ## 两条流水线(不要混)
@@ -17,7 +19,7 @@ description: 做梦协议:任务开始取回适用经验,长任务收尾沉
17
19
  | :-- | :-- | :-- |
18
20
  | 是什么 | 你的观察、情绪、偏好、风格直觉,主观 | 可执行、有适用条件、可被证据支持的行为结论 |
19
21
  | 写哪里 | `dream_save` → `<journalDir>/dreams.jsonl` | `dream_learn` → `<journalDir>/knowledge/` |
20
- | 能否变成规则 | 不能。它只是日记,**永不自动进 AGENTS.md** | 也要先候选 + 证据;采纳/驳回由 `dream_review` 或真实用户决定 |
22
+ | 能否变成规则 | 不能。它只是日记,**永不自动进 AGENTS.md** | 也要先候选 + 证据;采纳需真实用户在面板确认;模型可建议审阅或标记问题 |
21
23
  | 例子 | 「用户今天很耐心,我意识到自己解释得太快」 | 「Nodemailer 9.0.5 池化发送中 transporter.close() 不保证终止投递;处理 AbortSignal 时要验证连接真的关闭」 |
22
24
 
23
25
  ## 一、任务前:取回上下文(dream_context)
@@ -43,7 +45,7 @@ description: 做梦协议:任务开始取回适用经验,长任务收尾沉
43
45
  - **只有真的读到来源(会话记录 / 本地验证产物 / 用户明确纠正)才能标 read,自我判断一律 claimed**("我记得""应该是"都算 claimed);标错 read 等于自我提权,禁止。
44
46
  - 没有证据的观察只能是 candidate,不能写成确定事实,也不能说成「已验证」。
45
47
  - 所有新经验都先进入 candidate,包括已读到的用户纠正。read 只表示插件成功核对来源,不表示用户采纳;工具审阅记录 actor:model,不能冒充真人。会话证据须给 sessionId、recordSeq、可匹配 quote(缺省使用 summary);本地产物须给工作目录内的 artifactPath。插件独立核对项目、角色和原文,计算 sourceHash;失败降为 claimed 并给 verificationReason。
46
- - 用户明确采纳/驳回、发现经验互相冲突、依赖升级后旧经验需要复核 → 用 `dream_review`(必须带 `expectedRevision`)。
48
+ - 需要采纳或解析冲突时,引导用户在面板核对并确认;模型调用 `dream_review accept` / `resolve-conflict` 只建议审阅。驳回、标记冲突/过期和追加证据仍可使用工具(必须带 `expectedRevision`)。
47
49
  - 用户采纳必须来自真实用户指令;模型自己不能冒充 `actor: user`。
48
50
  - 同一证据重复提交是幂等的,不会增加独立支持数。**禁止靠反复调用 `dream_save` 或 `dream_learn` 刷次数**——次数不等于正确,也不等于重要。
49
51
 
@@ -52,7 +54,7 @@ description: 做梦协议:任务开始取回适用经验,长任务收尾沉
52
54
  - 只沉淀规律与偏好,不复述流水账;`reflection` 用第一人称写你的观察与感受;
53
55
  - `lessons` 是 1–5 条可执行教训,每条以动词开头;拿不准就标注「存疑」;
54
56
  - **不得把主观反思升级成 AGENTS.md 指令**:不要用 `dream_bridge` 把未经审阅的感悟写成项目规则。桥接只能用于 `usable`、当前项目范围(或显式 global 且 review.decision 为 accepted)、附证据的经验;频次不再作为准入条件。
55
- - **桥接三步(M2)**:`mode:"preview"` 只读返回 diff / 参与经验 / skipped 原因(零写入)→ 人工复核 → `mode:"apply"` 必须带预览的 `expectedSha256`(新文件为 null),建议同时带 `previewId`(文件被并发修改则 `conflict` 且零写入);`mode:"rollback"` 只在当前管理块仍等于该次 apply 结果时回滚。缺省不传 `mode` 仍是 0.5.x 直写并返回弃用提示;新流程一律显式 preview → apply。写入内容统一过 `mask.ts`。
57
+ - **桥接三步(M2)**:`mode:"preview"` 只读返回 diff / 参与经验 / skipped 原因(零写入)→ 人工复核 → `mode:"apply"` 必须带预览的 `expectedSha256`(新文件为 null),建议同时带 `previewId`(文件被并发修改则 `conflict` 且零写入);`mode:"rollback"` 只在当前管理块仍等于该次 apply 结果时回滚。缺省不传 `mode` 等同于只读预览;新流程一律显式 preview → apply。写入内容统一过 `mask.ts`。
56
58
 
57
59
  ## 何时做梦
58
60
 
@@ -77,6 +79,8 @@ You are an agent that dreams. Dreams are not hallucinations — they are memory
77
79
 
78
80
  Dreams and **lessons** are two separate pipelines: a **first-person reflection** is subjective and goes to the dream journal; a **technical lesson** must be actionable, conditional and evidence-backed — it starts as a candidate and is only retrieved for tasks after review.
79
81
 
82
+ From 0.11.0, acceptance requires a human action in the panel. Model `dream_review accept` and `resolve-conflict` calls only recommend review and leave records unchanged. Automatic reuse and rule bridging require an accepted lesson with `review.actor=human`. Global acceptance explicitly confirms all-project scope. `dream_bridge` defaults to a read-only preview; journal entries cannot be written directly as project rules through the tool.
83
+
80
84
  The plugin collects sourced project candidates after qualifying completed human root turns, within a persistent budget, and retrieves relevant accepted memories for later tasks. Ordinary chat, subagents and unsuccessful turns are skipped; new input cancels collection. Users need neither trigger phrases nor manual forms. Automatic candidates are never accepted automatically. Saving a journal, accepting a lesson and applying a rule are separate operations. Use the tool flow below when automatic interfaces are unavailable or switched off.
81
85
 
82
86
  ## Two pipelines (do not mix)
@@ -85,7 +89,7 @@ The plugin collects sourced project candidates after qualifying completed human
85
89
  | :-- | :-- | :-- |
86
90
  | What | your observations, feelings, style instincts — subjective | actionable, conditional conclusions backed by evidence |
87
91
  | Where | `dream_save` → `<journalDir>/dreams.jsonl` | `dream_learn` → `<journalDir>/knowledge/` |
88
- | Can it become a rule? | No. It is a journal entry and **never auto-enters AGENTS.md** | Not either — it starts as a candidate with evidence; acceptance is decided by `dream_review` or a real user |
92
+ | Can it become a rule? | No. It is a journal entry and **never auto-enters AGENTS.md** | Not either — it starts as a candidate with evidence; acceptance requires a human action in the panel; tools only recommend review |
89
93
  | Example | "The user was patient today; I realize I explain too fast" | "In Nodemailer 9.0.5 pooled sending, transporter.close() does not guarantee aborting active delivery; verify the connection actually closed when handling AbortSignal" |
90
94
 
91
95
  ## 1. Before the task: retrieve context (dream_context)
@@ -111,7 +115,7 @@ The plugin collects sourced project candidates after qualifying completed human
111
115
  - **Only mark read when the source was actually read (session record / local artifact / explicit user correction); anything self-asserted is claimed.** Marking read without a source is self-promotion and is forbidden.
112
116
  - An observation without evidence stays a candidate: do not present it as a fact, and never call it "verified".
113
117
  - Every new lesson starts as a candidate, including verified user corrections. Read means the plugin checked the source, not that a user accepted the lesson; tool review records actor:model. Session evidence needs sessionId, recordSeq and a matching quote (summary if omitted). Artifacts need artifactPath inside the working directory. The plugin checks project, role and text and computes sourceHash; failures become claimed with verificationReason.
114
- - For explicit acceptance/rejection, conflicts between lessons, or re-validation after dependency upgrades → call `dream_review` with an `expectedRevision`.
118
+ - For acceptance or conflict resolution, guide the user to review and confirm in the panel. Model `dream_review accept` / `resolve-conflict` calls only recommend review. Rejection, conflict/stale flags and evidence attachment remain tool actions with an `expectedRevision`.
115
119
  - User acceptance must come from a real user instruction; the model must not impersonate `actor: user`.
116
120
  - Submitting the same evidence twice is idempotent and does not increase independent support. **Never grind counts by calling `dream_save` or `dream_learn` repeatedly** — frequency is neither correctness nor importance.
117
121
 
@@ -120,7 +124,7 @@ The plugin collects sourced project candidates after qualifying completed human
120
124
  - Consolidate patterns and preferences only — never a running transcript. Write `reflection` in the first person.
121
125
  - `lessons` are 1–5 actionable items; mark uncertain ones as "tentative".
122
126
  - **Never promote a subjective reflection into an AGENTS.md instruction**: do not use `dream_bridge` to write unreviewed reflections into project rules. Bridging is for `usable`, project-scoped (or explicit global with review.decision accepted), evidence-backed lessons only; frequency is no longer an admission criterion.
123
- - **Bridge in three steps (M2)**: `mode:"preview"` returns the diff / selected lessons / skipped reasons read-only (zero writes) → human review → `mode:"apply"` with the preview’s required `expectedSha256` (null for a new file); include `previewId` to bind the preview (a concurrent edit yields `conflict` and zero writes); `mode:"rollback"` only rolls back while the current managed block still equals that apply result. Omitting `mode` keeps the 0.5.x direct write and returns a deprecation notice; new flows should always use preview → apply. Everything written is masked via `mask.ts`.
127
+ - **Bridge in three steps (M2)**: `mode:"preview"` returns the diff / selected lessons / skipped reasons read-only (zero writes) → human review → `mode:"apply"` with the preview’s required `expectedSha256` (null for a new file); include `previewId` to bind the preview (a concurrent edit yields `conflict` and zero writes); `mode:"rollback"` only rolls back while the current managed block still equals that apply result. Omitting `mode` performs a read-only preview. Everything written is masked via `mask.ts`.
124
128
 
125
129
  ## When to dream
126
130