@fanchao8609/agent_brain_sync 1.9.5 → 1.9.7

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.
@@ -3,25 +3,16 @@
3
3
  * 纯触发: 生命周期事件 → 技术日志一行 (~/.abs/log/hooks.log, ABS_LOG_DIR 可覆盖)。fire-and-forget。
4
4
  * 纪律: hook 事件只进技术日志, 不进图谱 log.md (log.md 只收工作成果沉淀, 与 event.sh 同纪律)。
5
5
  * Op​encode 事件: session.created / session.idle / session.deleted (idle = 每轮结束, 对应 pi 的 agent_end)。
6
- * 收尾注入: session.idle 且本会话真改过文件 + .brain 今日无记录 → promptAsync 推 agent 走收尾循环。
7
- *
8
- * 登记注入 (2026-09-15 用户定): 写文件 = 任务开始的机械信号。
9
- * 旧设计只在 idle(轮末) 提醒收尾 —— 那时用户已想结束, AI 只想收尾不想登记。
10
- * 故 tool.execute.after 检测到**项目文件**写入即注入登记提示(不等 idle):
11
- * · 频繁没关系 —— 每次写都提醒(用户明确要求)
12
- * · 判据 = 整个项目, 但**排除 .brain/ 自身**(写图谱是记录行为, 不是任务)
13
- * · 文案明说「纯讨论可跳过」—— 不逼 AI 造任务
6
+ * 收尾注入已删除(2026-09-18):曾用 session.idle + promptAsync 推一条收尾提醒, 用户实报
7
+ * 「每次干活干一会就出来, 任务就中断了」。根因同 pi 侧: 主动往对话里插 turn = 打断,
8
+ * 不是频率问题(2026-09-15 删登记注入时已得出同一结论)。保留 session.idle:seen 埋点。
14
9
  */
15
- import { appendFile, mkdir, readFile, stat } from "node:fs/promises"
10
+ import { appendFile, mkdir, stat } from "node:fs/promises"
16
11
  import { homedir } from "node:os"
17
- import { join, dirname } from "node:path"
18
- const server = async ({ client, directory }) => {
19
- // 本会话是否真改过文件 (由 tool.execute.after 观测)
20
- let wroteFiles = false
21
- let nudged = false
12
+ import { join } from "node:path"
13
+ const server = async ({ directory }) => {
14
+ // 本会话首次 idle 是否已埋点
22
15
  let idleSeen = false
23
- // 当前 session id(由事件里拿)—— 收尾注入需要它找到会话。
24
- let currentSessionID = ""
25
16
  async function logHook(evt) {
26
17
  try {
27
18
  const dir = process.env.ABS_LOG_DIR || join(homedir(), ".abs", "log")
@@ -39,94 +30,32 @@ const server = async ({ client, directory }) => {
39
30
  try { const st = await stat(join(d, ".brain")); return st.isDirectory() ? join(d, ".brain") : null } catch { return null }
40
31
  }
41
32
 
42
- // 判定"今天是否收尾过": 只认【工作成果】条目(kind=dev),行头 '## [YYYY-MM-DD HH:MM] [[name]] dev |'。
43
- // 坑1: 曾用裸日期 substring, 正文/任务行里任何一处提到今天就把 nudge 永久压掉(守卫形同虚设)。
44
- // 坑2(2026-09-13 实测, pi 侧先修): `abs note` 也写 log.md(kind=note),
45
- // 旧判据只看「今天有没有行」会把「沉淀了一条经验」误判成「已收尾」→ 整天不再提醒。
46
- // 三宿主(pi/CC/op​encode)判据必须一致 —— 见 [[hook-throttle-alignment]]。
47
- async function loggedToday(brain) {
48
- try {
49
- const txt = await readFile(join(brain, "log.md"), "utf8")
50
- const d = new Date()
51
- const pad = (n) => String(n).padStart(2, "0")
52
- const today = d.getFullYear() + "-" + pad(d.getMonth() + 1) + "-" + pad(d.getDate())
53
- return new RegExp("^## \\[" + today + " \\d{2}:\\d{2}\\] (?:\\[[^\\]]+\\]\\] )?dev \\|", "m").test(txt)
54
- } catch { return false }
55
- }
56
-
57
-
58
- const TEARDOWN_MSG =
59
- "[abs] 本会话改过文件,.brain/ 今日无记录。\n" +
60
- "这条是信息不是命令:该登记/沉淀就登记,没有可沉淀的直接回一句「无可沉淀」,不用凑。\n" +
61
- "先看任务:本会话做完的事有没有进看板?(abs todo / abs todo done <id>;干到一半 abs todo note <id> --note 断点)\n" +
62
- "再看沉淀:经验/坑 → abs note \"...\";成果流水 → abs log \"...\""
63
-
64
- // bash 里只跑查询类命令不算改文件 (与 pi 侧 READONLY_CMD 同义, 但生成代码里要写进模板串)
65
- const READONLY_CMD = /^\s*(ls|cat|grep|rg|find|head|tail|wc|git\s+(status|log|diff|show|branch)|pwd|which|echo|node\s+-v|npm\s+(ls|view)|curl)\b/
66
-
67
-
68
-
69
-
33
+ // 收尾注入已停用(2026-09-18):promptAsync 会在每轮 idle 抢一个 turn,用户实报
34
+ // 「每次干活干一会就出来, 任务就中断了」。wroteFiles / nudged / READONLY_CMD /
35
+ // tool.execute.after / loggedToday 随注入一起删除(注入没了,它们已无消费方)。
36
+ // 保留 session.idle:seen 埋点:区分「事件没触发」与「被守卫拦下」。
70
37
  return {
71
- // 观测真实写操作: write/edit/patch 类工具成功即标记。
72
- // 坑: 曾漏掉 bash —— op​encode 里很多修改是经 bash(heredoc/sed) 完成的, 纯 bash 会话
73
- // 永远不置位 wroteFiles, 于是收尾提醒静默不发。与 pi 侧 WRITE_TOOLS 保持一致。
74
- "tool.execute.after": async ({ tool, args }) => {
75
- const t = String(tool || "").toLowerCase()
76
- const a = args || {}
77
- if (["write", "edit", "patch", "multiedit", "apply_patch"].includes(t)) {
78
- wroteFiles = true
79
- return
80
- }
81
- // bash 里只有非只读命令算改文件(ls/cat/git status 之类不算)
82
- if (t === "bash") {
83
- const cmd = String(a.command || a.cmd || "")
84
- if (cmd && !READONLY_CMD.test(cmd)) wroteFiles = true
85
- }
86
- },
87
-
88
38
  event: async ({ event }) => {
89
39
  const type = event && event.type
90
40
  if (!type) return
91
41
  if (type === "session.created" || type === "session.deleted") {
92
42
  await logHook(type)
93
- // session.created / idle 等事件带 sessionID —— 存下来供 tool.execute.after 用
94
- const sid = event.properties && event.properties.sessionID
95
- if (sid) currentSessionID = String(sid)
96
- // 新会话 = 重置节流状态。坑(2026-09-13 pi 侧同构实测): 状态声明在 server 工厂闭包里,
97
- // 同一进程的第二个会话会继承上个会话的 true → 永久不再提醒(后半场全部静默)。
43
+ // 新会话 = 重置埋点状态。坑(2026-09-13 pi 侧同构实测): 状态声明在 server 工厂闭包里,
44
+ // 同一进程的第二个会话会继承上个会话的 true → 永久不再留痕。
98
45
  // pi 侧在 session_start 里重置; op​encode 的对应事件就是 session.created。
99
46
  if (type === "session.created") {
100
- wroteFiles = false
101
- nudged = false
102
47
  idleSeen = false
103
48
  }
104
49
  return
105
50
  }
106
- if (type !== "session.idle") return // idle = 每轮结束, 不记日志(太吵), 只做收尾判定
107
- const sid0 = event.properties && event.properties.sessionID
108
- if (sid0) currentSessionID = String(sid0)
51
+ if (type !== "session.idle") return // idle = 每轮结束, 不记日志(太吵), 只做可观测性埋点
109
52
  const cwd = directory || process.cwd()
110
53
  const brain = await findBrain(cwd)
111
- // 可观测性: 首次 idle 无条件留一行痕(否则无法区分“事件没触发”与“被守卫拦下”)
54
+ // 可观测性: 首次 idle 留一行痕(否则无法区分“事件没触发”与“被守卫拦下”)
112
55
  if (!idleSeen) {
113
56
  idleSeen = true
114
57
  await logHook(`session.idle:seen cwd=${cwd} brain=${brain || 'none'}`)
115
58
  }
116
- if (nudged || !wroteFiles) return
117
- const sessionID = event.properties && event.properties.sessionID
118
- if (!sessionID) return
119
- if (!brain) return // 无图谱=不在这项目沉淀, 不打扰
120
- if (await loggedToday(brain)) return // 今天已收尾过
121
- nudged = true
122
- await logHook("session.idle:teardown-nudge")
123
- try {
124
- await client.session.promptAsync({
125
- path: { id: sessionID },
126
- query: { directory: cwd },
127
- body: { parts: [{ type: "text", text: TEARDOWN_MSG }] },
128
- })
129
- } catch {} // 注入失败不阻塞宿主
130
59
  },
131
60
  }
132
61
  }
package/hooks/abs.pi.ts CHANGED
@@ -4,49 +4,22 @@
4
4
  * 纪律: hook 事件只进技术日志, 不进图谱 log.md (log.md 只收工作成果沉淀, 与 event.sh 同纪律)。
5
5
  * Pi 事件: session_start / session_shutdown (对应 host hook 的 SessionStart/SessionEnd)。
6
6
  * session_shutdown 额外触发 abs wrapup: 把当前项目未完成任务快照到 wrapup.log (跨会话收尾保险)。
7
- * agent_end 收尾注入: 本会话真改过文件 + .brain 今日无记录 → 注入一条收尾指令, 逼 agent 走收尾循环
8
- * (被动记日志不够 —— 没人提醒就不会有人收尾)。每会话最多一次, 且已收尾后不再打扰。
7
+ * agent_end 只留可观测性埋点(agent_end:seen), 不再向对话注入任何东西。
9
8
  *
10
- * 素材累积 (B+C, 2026-09-13 用户定): 话题在会话中途连续产生, 而收尾提醒只在会话末尾——
11
- * 用一次性提醒追持续事件 = 记录率 7% (14 条话题只落 1 条)。故在能机械观察的锚点上累积素材:
12
- * - before_agent_start (用户发话 = 天然话题边界) → 记用户原话
13
- * - turn_end (每轮结束) → 记改过哪些文件
14
- * 累积在**内存**(下方 sessionNotes), 收尾时拼进注入提示 —— **不落盘**。
15
- *
16
- * 已删除(2026-09-15):曾加过一个「写文件即任务开始 → 每轮注入登记提醒」的功能。
17
- * 已发布到 1.8.0 后用户实报「pi 里一直报 Follow-up」,判 C 整个删掉。
18
- * 教训:每次改文件就往对话里插一句话,本身就是设计错误,不是频率问题 ——
19
- * 改守卫(从每轮一次改成每会话一次)治不了它。别再加回来。
9
+ * 已删除(2026-09-15):「写文件即任务开始 → 每轮注入登记提醒」。实报「pi 里一直报 Follow-up」。
10
+ * 已删除(2026-09-18):收尾注入本身(agent_end + sendUserMessage deliverAs=followUp)。
11
+ * 实报「每次干活干一会出来, 任务就中断了」。两次同一个根因:
12
+ * **往对话里插一句话本身就是设计错误,不是频率问题** —— 改守卫(每轮→每会话)治不了它。
13
+ * 别再加回来。素材锚点(before_agent_start/turn_end)随注入一起删除, 已无消费方。
20
14
  */
21
15
  import type { ExtensionAPI } from "@earendil-works/pi-coding-agent"
22
- import { appendFile, mkdir, readFile, stat } from "node:fs/promises"
16
+ import { appendFile, mkdir, stat } from "node:fs/promises"
23
17
  import { homedir } from "node:os"
24
- import { join, dirname } from "node:path"
18
+ import { join } from "node:path"
25
19
  import { spawn } from "node:child_process"
26
20
 
27
21
  const ABS_BIN = "@@ABS_BIN@@"
28
22
 
29
- // 素材累积锚点:会话内存里累积,收尾时拼进注入提示。**不落盘、不 spawn、不然上下文**。
30
- // 预算:只留最近几条(收尾回忆只需线索,不需全史)。
31
- const NOTES_MAX = 12
32
- const NOTE_LEN = 120
33
- const sessionNotes: string[] = []
34
-
35
- function addNote(kind: string, text: string): void {
36
- const clean = String(text ?? "").replace(/\s+/g, " ").trim().slice(0, NOTE_LEN)
37
- if (!clean) return
38
- sessionNotes.push(`${kind}: ${clean}`)
39
- if (sessionNotes.length > NOTES_MAX) sessionNotes.shift()
40
- }
41
-
42
- /** 把本会话素材拼成一段(收尾注入用)。无素材则空字符串。
43
- * 用途:让 AI 据线索**归纳**话题,而不是凭记忆回想整场对话(那正是 7% 记录率的原因)。 */
44
- function notesBlock(notes: string[]): string {
45
- if (!notes.length) return ""
46
- return " 本会话素材(hook 机械记录,供归纳)\n" +
47
- notes.map((n) => " · " + n).join("\n") + "\n"
48
- }
49
-
50
23
  async function logHook(evt: string): Promise<void> {
51
24
  const dir = process.env.ABS_LOG_DIR || join(homedir(), ".abs", "log")
52
25
  const d = new Date()
@@ -68,81 +41,30 @@ function snapshotWrapup(cwd: string): void {
68
41
  } catch {} // fire-and-forget
69
42
  }
70
43
 
71
- // ---- 收尾注入判定 ----
72
- // 真改过文件的工具(read/grep/ls 不算, 那些不产生可沉淀的产出)
73
- const WRITE_TOOLS = new Set(["write", "edit", "multi_edit", "apply_patch", "bash"])
74
- // bash 里只跑查询类命令不算改文件
75
- const READONLY_CMD = /^\s*(ls|cat|grep|rg|find|head|tail|wc|git\s+(status|log|diff|show|branch)|pwd|which|echo|node\s+-v|npm\s+(ls|view)|curl)\b/
76
-
77
- function hasWriteWork(toolResults: any[]): boolean {
78
- for (const r of toolResults || []) {
79
- const name = String(r?.toolName || "")
80
- if (!WRITE_TOOLS.has(name)) continue
81
- if (r?.isError) continue
82
- if (name === "bash") {
83
- const cmd = String(r?.input?.command ?? r?.args?.command ?? "")
84
- if (READONLY_CMD.test(cmd)) continue
85
- }
86
- return true
87
- }
88
- return false
89
- }
90
-
91
44
  /** 定位当前项目的 .brain/:只看 cwd 本身,不向上搜索(防爬到家目录图谱)。 */
92
45
  async function findBrain(cwd: string): Promise<string | null> {
93
46
  const d = cwd || process.cwd()
94
47
  try { const st = await stat(join(d, ".brain")); return st.isDirectory() ? join(d, ".brain") : null } catch { return null }
95
48
  }
96
49
 
97
- /**
98
- * .brain/log.md 今天有记录吗? 有=已收尾, 不再打扰。
99
- * 必须匹配条目头 '## [YYYY-MM-DD HH:MM]' —— 裸日期 substring 会被正文里任意一处
100
- * 今天的日期误命中, 导致"已收尾"误判、nudge 永不触发。
101
- */
102
- async function loggedToday(brain: string): Promise<boolean> {
103
- try {
104
- const txt = await readFile(join(brain, "log.md"), "utf8")
105
- const d = new Date()
106
- const pad = (n: number) => String(n).padStart(2, "0")
107
- const today = d.getFullYear() + "-" + pad(d.getMonth() + 1) + "-" + pad(d.getDate())
108
- // 只认**工作成果**条目(kind=dev),不能只看“今天有没有行”。
109
- // 坑(2026-09-13 实测):`abs note` 也写 log.md(kind=note),
110
- // 旧判据 (^## [今天 HH:MM]) 把“沉淀了一条经验”当成“今天已收尾”→
111
- // 整天不再提醒 → 会话后半场全部漏登。
112
- // 行格式: `## [YYYY-MM-DD HH:MM] [[name]] dev | 内容`。
113
- return new RegExp("^## \\[" + today + " \\d{2}:\\d{2}\\] (?:\\[\\[[^\\]]+\\]\\] )?dev \\|", "m").test(txt)
114
- } catch { return false }
115
- }
116
-
117
-
118
- /** 收尾注入的节流状态。
50
+ /** agent_end 埋点状态。
119
51
  *
120
52
  * 为何在**模块级**而不在 absPiHook() 里(2026-09-17 实测):扩展会被重复注册
121
- * (session_start 11 分钟内触发 8 次),每次注册都新建一份闭包 → flag 不共享 →
122
- * 实测同会话 5 分钟注入 6 次。模块级状态在同一进程内只有一份,注册多少次都共享。
53
+ * (session_start 11 分钟内触发 8 次),每次注册都新建一份闭包 → flag 不共享。
54
+ * 模块级状态在同一进程内只有一份,注册多少次都共享。
123
55
  *
124
- * teardownNudged = 本会话已注入过(终态,session_start 才重置)
125
- * teardownInFlight = 正在判定中(抢在 await 之前置位,堵住并发重入)
126
- * agentEndSeen = 本会话已留过 seen 痕(可观测性,同样每会话一次)
56
+ * agentEndSeen = 本会话已留过 seen 痕(可观测性,每会话一次)
127
57
  */
128
- let teardownNudged = false
129
- let teardownInFlight = false
130
58
  let agentEndSeen = false
131
59
 
132
- /** 会话边界重置节流(session_start 与注册时都调)。
133
- * 不重置 → 同进程第二个会话继承 true 永久静默(2026-09-13 实测过的坑)。 */
60
+ /** 会话边界重置埋点状态。
61
+ * 不重置 → 同进程第二个会话继承 true 永久不留痕(2026-09-13 实测过的坑)。 */
134
62
  function resetThrottle(): void {
135
- teardownNudged = false
136
- teardownInFlight = false
137
63
  agentEndSeen = false
138
64
  }
139
65
 
140
66
  export default function absPiHook(pi: ExtensionAPI): void {
141
- // 收尾注入的节流状态。故意声明在**模块级**(不在被反复调用的工厂函数里),
142
- // 因为**扩展会被重复注册**:实测 session_start 在 11 分钟内触发 8 次
143
- // (2026-09-17 00:07:58 一口气 3 次)。注册几份就得到几份独立闭包,
144
- // 各自的 flag 彼此看不见 → “每会话一次”变成“每实例一次” → 实测同会话 5 分钟注入 6 次。
145
- // 模块级状态在同一进程内共享,注册多少次都只有一份。
67
+ // 埋点状态同上,故意声明在**模块级**(不在被反复调用的工厂函数里)。
146
68
  resetThrottle()
147
69
 
148
70
  pi.on("session_start", (event: any, _ctx: any) => {
@@ -150,73 +72,19 @@ export default function absPiHook(pi: ExtensionAPI): void {
150
72
  return logHook("session_start").catch(() => {})
151
73
  })
152
74
 
153
- // ---- 素材锚点(不烧上下文、不注入消息、不 spawn、不落盘)----
154
- // 用户发话 = 天然的话题边界。这是 C 路线:你说的每句话都是一个锚点。
155
- pi.on("before_agent_start", async (event: any, _ctx: any) => {
156
- addNote("user", String(event?.prompt || ""))
157
- })
158
-
159
- // 每轮结束 = 这轮干了什么(改了哪些文件)。机械事实,供收尾时回忆。
160
- pi.on("turn_end", async (event: any, _ctx: any) => {
161
- const files = new Set<string>()
162
- for (const r of event?.toolResults || []) {
163
- const name = String(r?.toolName || "")
164
- if (!WRITE_TOOLS.has(name)) continue
165
- if (r?.isError) continue
166
- const f = r?.input?.file_path ?? r?.args?.file_path ?? r?.input?.path ?? r?.args?.path
167
- if (f) files.add(String(f))
168
- else if (name === "bash") {
169
- const cmd = String(r?.input?.command ?? r?.args?.command ?? "")
170
- if (!READONLY_CMD.test(cmd)) files.add("bash: " + cmd.slice(0, 60))
171
- }
172
- }
173
- if (files.size) addNote("tool", [...files].join(", "))
174
- })
175
-
176
- // 收尾注入: 每个会话最多一次, 避免反复打扰。
75
+ // 收尾注入已停用(2026-09-18):sendUserMessage(deliverAs:"followUp") 会在每轮
76
+ // agent_end 抢一个 follow-up turn,用户实报「每次干活干一会出来, 任务就中断了」。
177
77
  //
178
- // 坑(2026-09-17 实测定根因):原实现是「先检查 flag → 若干 await → 才置位」,
179
- // 而 agent_end **每轮 run 结束都触发**(官方 docs/extensions.md:569:它不等于会话结束,
180
- // Pi 之后还可能继续跑 follow-up)。并发的多次调用因此**全部先通过 `if (nudged) return`**,
181
- // 之后才各自置位 → 一起注入。实测同会话 5 分钟内注入 6 次,全日志 55 次。
182
- // 修法:进函数**第一个动作**就抢占(置位在任何 await 之前),失败再把标志放回。
183
- // 这样“检查+设置”之间没有让出点,并发调用只有一个能赢。
184
- pi.on("agent_end", async (event: any, ctx: any) => {
185
- if (teardownNudged || teardownInFlight) return
186
- teardownInFlight = true
187
- // 从这里往下任何提前 return 都必须解除 in-flight(否则本次会话永久不再提醒)。
188
- const release = () => { teardownInFlight = false }
78
+ // 为何是删除而非调参:这跟 2026-09-15 删掉「写文件即注入登记提醒」是同一个错
79
+ // ——「往对话里插一句话」本身就不是频率问题(见文件头)。每轮弹、每会话弹、
80
+ // 每项目弹,都还是在打断用户。故不再有 teardownNudged/inFlight/loggedToday 判据。
81
+ // 只保留 agent_end:seen 埋点:区分「事件没触发」与「被守卫拦下」。
82
+ pi.on("agent_end", async (_event: any, ctx: any) => {
83
+ if (agentEndSeen) return
84
+ agentEndSeen = true
189
85
  const cwd = (ctx && ctx.cwd) || process.cwd()
190
86
  const brain = await findBrain(cwd)
191
- // 可观测性: 本会话首次 agent_end 无条件留一行痕。
192
- // 没有它就无法区分三种状态: ①事件没触发 ②触发了但被守卫拦下 ③真注入了。
193
- if (!agentEndSeen) {
194
- agentEndSeen = true
195
- // 带上 cwd 与命中的 brain —— 否则事后无法解释“为何这条 nudge 会出现”
196
- await logHook(`agent_end:seen cwd=${cwd} brain=${brain || 'none'}`).catch(() => {})
197
- }
198
- if (!brain) return release() // 无图谱=不在这项目沉淀, 不打扰
199
- if (!hasWriteWork(event?.messages ? collectToolResults(event.messages) : [])) return release()
200
- // 今日已收尾 → 只在**本会话还没真正干事**时才静默。
201
- // 旧行为:今天 log 有一行就整天闭口 —— 于是收尾之后的产出全部没人提醒。
202
- // 注意不能用 sessionNotes.length 当判据:用户每说一句话就会 push 一条,
203
- // 那会让 nudge 每轮都触发(噪音)。判据保持「今天已收尾」但配合下面的笔记消费。
204
- if (await loggedToday(brain)) return release()
205
- teardownNudged = true
206
- teardownInFlight = false
207
- await logHook("agent_end:teardown-nudge").catch(() => {})
208
- // 素材交给 AI 后清空:同一会话再触发时不该重复喂旧料。
209
- const notes = sessionNotes.splice(0, sessionNotes.length)
210
- try {
211
- pi.sendUserMessage(
212
- "[abs] 本会话改过文件,.brain/ 今日无记录。\n" +
213
- "这条是信息不是命令:该登记/沉淀就登记,没有可沉淀的直接回一句「无可沉淀」,不用凑。\n" +
214
- "先看任务:本会话做完的事有没有进看板?(abs todo / abs todo done <id>;干到一半 abs todo note <id> --note 断点)\n" +
215
- "再看沉淀:经验/坑 → abs note \"...\";成果流水 → abs log \"...\"" +
216
- notesBlock(notes),
217
- { deliverAs: "followUp" },
218
- )
219
- } catch {}
87
+ await logHook(`agent_end:seen cwd=${cwd} brain=${brain || 'none'}`).catch(() => {})
220
88
  })
221
89
 
222
90
  pi.on("session_shutdown", (_e: any, ctx: any) => {
@@ -225,12 +93,4 @@ export default function absPiHook(pi: ExtensionAPI): void {
225
93
  })
226
94
  }
227
95
 
228
- /** agent_end 的 event.messages 里翻出 toolResult 消息。 */
229
- function collectToolResults(messages: any[]): any[] {
230
- const out: any[] = []
231
- for (const m of messages || []) {
232
- if (m && m.role === "toolResult") out.push(m)
233
- }
234
- return out
235
- }
236
96
  @@MARK@@
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@fanchao8609/agent_brain_sync",
3
- "version": "1.9.5",
3
+ "version": "1.9.7",
4
4
  "description": "agent-brain-sync: 跨会话 AI 编码记忆 — hook 纯触发 + CLI/MCP 读写 .brain markdown 图谱, 防并发写保护。",
5
5
  "type": "module",
6
6
  "license": "MIT",
@@ -305,10 +305,12 @@ abs todo archive # 一条命令搞定,无需手工搬
305
305
  > Stop 时 hook 把「未完成任务 + 断点」快照进 `~/.abs/log/wrapup.log`(`abs wrapup`,机械幂等),
306
306
  > 下会话 `abs load` 会自动把滞留顶到顶部(`⏳ 上会话滞留`)—— 所以收尾不靠自觉,是开场被强制接上。
307
307
  >
308
- > **主动注入**:pi 扩展在 `agent_end` 检测「本会话真改过文件」且「log.md 今日无记录」时注入
309
- > `[abs 收尾提醒]`(每会话最多一次)。**它是一条信息,不是命令** ——
310
- > 自己判断该不该沉淀;没有可沉淀的就回一句「无可沉淀」,不用强行凑。
311
- > Cl​aude/Co​dex 靠 `Stop` 事件达成同样效果。
308
+ > **不再主动注入**(2026-09-18):pi 扩展曾在 `agent_end` 往对话里推一条 `[abs 收尾提醒]`
309
+ > (Claude/Co​dex 靠 `Stop` 事件、op​encode 靠 `session.idle` 达成同样效果)。
310
+ > 已全部删除:用户实报「每次干活干一会就出来, 任务就中断了」。**主动往对话里插一个 turn
311
+ > 就是打断,不是频率问题** —— 每轮弹、每会话弹都还是打断。收尾靠**你自己在该收尾时走**,
312
+ > 以及 `wrapup.log` + `abs load` 的机制兵底(开场强制接上滞留任务)。
313
+ > 挂钩只在 `~/.abs/log/hooks.log` 留痕,不再往对话里说话。
312
314
 
313
315
  当用户**明确说要收尾/结束/切别的事**时,按下面走(不是每个词都触发,见开头总则):
314
316