@fanchao8609/agent_brain_sync 1.8.0 → 1.8.1

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.
@@ -15,17 +15,13 @@
15
15
  import { appendFile, mkdir, readFile, stat } from "node:fs/promises"
16
16
  import { homedir } from "node:os"
17
17
  import { join, dirname } from "node:path"
18
-
19
18
  const server = async ({ client, directory }) => {
20
19
  // 本会话是否真改过文件 (由 tool.execute.after 观测)
21
20
  let wroteFiles = false
22
21
  let nudged = false
23
22
  let idleSeen = false
24
- // 登记提醒计数:只为日志/诊断,不做去重 —— 用户要「频繁一点」,每次写文件都提醒。
25
- let registerNudged = 0
26
- // 当前 session id(由事件里拿),tool.execute.after 没带 sessionID 时靠它补。
23
+ // 当前 session id(由事件里拿)—— 收尾注入需要它找到会话。
27
24
  let currentSessionID = ""
28
-
29
25
  async function logHook(evt) {
30
26
  try {
31
27
  const dir = process.env.ABS_LOG_DIR || join(homedir(), ".abs", "log")
@@ -56,21 +52,6 @@ const server = async ({ client, directory }) => {
56
52
  } catch { return false }
57
53
  }
58
54
 
59
- // 登记提醒:注入失败不阻塞宿主。每次写文件都提醒(用户明确要「频繁一点」)。
60
- async function registerNudge(files) {
61
- registerNudged++
62
- await logHook(`tool.execute.after:register-nudge #${registerNudged} files=${files.length}`)
63
- if (!currentSessionID) return // 拿不到 session 就无法注入,静默跳过(不抛)
64
- const cwd = directory || process.cwd()
65
- if (!(await findBrain(cwd))) return // 无图谱 = 不在这项目沉淀,不打扰
66
- try {
67
- await client.session.promptAsync({
68
- path: { id: currentSessionID },
69
- query: { directory: cwd },
70
- body: { parts: [{ type: "text", text: REGISTER_MSG(files) }] },
71
- })
72
- } catch {}
73
- }
74
55
 
75
56
  const TEARDOWN_MSG =
76
57
  "[abs 收尾提醒] 本会话改过文件但 .brain/ 今天还没有记录。请立即走收尾循环:\n" +
@@ -83,23 +64,8 @@ const server = async ({ client, directory }) => {
83
64
  // bash 里只跑查询类命令不算改文件 (与 pi 侧 READONLY_CMD 同义, 但生成代码里要写进模板串)
84
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/
85
66
 
86
- // 写文件 = 任务开始的机械信号。排除 .brain/ 自身(`abs note`/`abs log` 也写文件,
87
- // 但那是记录行为不是任务 —— 不排除则每落一条经验都弹登记提醒,纯噪音)。
88
- const BRAIN_PATH = /\.brain[\\/]/
89
- function isProjectWrite(p, cmd) {
90
- if (cmd !== undefined) return !!cmd && !READONLY_CMD.test(cmd)
91
- if (!p) return false
92
- return !BRAIN_PATH.test(String(p))
93
- }
94
67
 
95
- const REGISTER_MSG = (files) =>
96
- "[abs 登记提醒] 本轮改了项目文件,说明有任务在进行。\n" +
97
- "改了:" + files.slice(0, 6).join(", ") + (files.length > 6 ? ` 等 ${files.length} 个` : "") + "\n" +
98
- "若这属于某个任务,立刻登记(别等到会话结束才回忆——那时必漏):\n" +
99
- " · 新任务: abs todo add <id> --note \"做什么\"\n" +
100
- " · 已有任务: abs todo note <id> --note \"改到哪/下一步\"\n" +
101
- " · 纯讨论/调研/改图谱自身 → 无需登记,回「跳过」即可。\n" +
102
- "简洁执行,不要复述本条提醒。"
68
+
103
69
 
104
70
  return {
105
71
  // 观测真实写操作: write/edit/patch 类工具成功即标记。
@@ -108,20 +74,14 @@ const server = async ({ client, directory }) => {
108
74
  "tool.execute.after": async ({ tool, args }) => {
109
75
  const t = String(tool || "").toLowerCase()
110
76
  const a = args || {}
111
- const fp = a.file_path || a.filePath || a.path || a.filename
112
77
  if (["write", "edit", "patch", "multiedit", "apply_patch"].includes(t)) {
113
78
  wroteFiles = true
114
- // 登记提醒:项目文件被写 = 任务已开始,立刻推(不等 idle)
115
- if (isProjectWrite(fp)) await registerNudge([String(fp)])
116
79
  return
117
80
  }
118
81
  // bash 里只有非只读命令算改文件(ls/cat/git status 之类不算)
119
82
  if (t === "bash") {
120
83
  const cmd = String(a.command || a.cmd || "")
121
- if (isProjectWrite(undefined, cmd)) {
122
- wroteFiles = true
123
- await registerNudge(["bash: " + cmd.slice(0, 60)])
124
- }
84
+ if (cmd && !READONLY_CMD.test(cmd)) wroteFiles = true
125
85
  }
126
86
  },
127
87
 
package/hooks/abs.pi.ts CHANGED
@@ -13,13 +13,10 @@
13
13
  * - turn_end (每轮结束) → 记改过哪些文件
14
14
  * 累积在**内存**(下方 sessionNotes), 收尾时拼进注入提示 —— **不落盘**。
15
15
  *
16
- * 登记提醒 (2026-09-15 用户定): 写文件 = 任务开始的机械信号。
17
- * 旧设计只在 agent_end 提醒收尾 —— 但那时用户已想结束, AI 只想收尾不想登记;
18
- * 且「任务何时开始」只有写文件那一刻清楚, 事后回忆必漏。
19
- * 故 turn_end 检测到**项目文件**写入即注入登记提示(不等 agent_end):
20
- * · 频繁没关系 —— 每轮改文件都提醒(用户明确要求), 用 nudgeCount 防同一轮重复
21
- * · 判据 = 整个项目, 但**排除 .brain/ 自身**(写图谱不是任务, 是记录行为)
22
- * · 文案明说「纯讨论可跳过」—— 不逼 AI 造任务(否则会长出假条目污染看板)
16
+ * 已删除(2026-09-15):曾加过一个「写文件即任务开始 → 每轮注入登记提醒」的功能。
17
+ * 已发布到 1.8.0 后用户实报「pi 里一直报 Follow-up」,判 C 整个删掉。
18
+ * 教训:每次改文件就往对话里插一句话,本身就是设计错误,不是频率问题 ——
19
+ * 改守卫(从每轮一次改成每会话一次)治不了它。别再加回来。
23
20
  */
24
21
  import type { ExtensionAPI } from "@earendil-works/pi-coding-agent"
25
22
  import { appendFile, mkdir, readFile, stat } from "node:fs/promises"
@@ -117,37 +114,6 @@ async function loggedToday(brain: string): Promise<boolean> {
117
114
  } catch { return false }
118
115
  }
119
116
 
120
- // 写文件 = 任务开始的机械信号。
121
- // 注: `abs note` / `abs log` 也会写文件 —— 但那是**记录行为**不是任务,
122
- // 所以要排除 .brain/ 自身(否则每落一条经验都弹一次登记提醒,纯噪音)。
123
- // ponytail: 用路径前缀判,不解析项目根(子目录/软链场景可能漏判;
124
- // 漏判的代价只是少提醒一次,不丢数据)。
125
- const BRAIN_PATH = /\.brain[\/]/;
126
-
127
- function isProjectWrite(p: string | undefined, cmd?: string): boolean {
128
- if (cmd !== undefined) return !READONLY_CMD.test(cmd); // bash: 非只读就算
129
- if (!p) return false;
130
- return !BRAIN_PATH.test(String(p));
131
- }
132
-
133
- /** 从一轮的 toolResults 里抽出「改过的项目文件」。排除 .brain/ 自身。 */
134
- function projectWrites(toolResults: any[]): string[] {
135
- const out = new Set<string>();
136
- for (const r of toolResults || []) {
137
- const name = String(r?.toolName || "");
138
- if (!WRITE_TOOLS.has(name)) continue;
139
- if (r?.isError) continue;
140
- if (name === "bash") {
141
- const cmd = String(r?.input?.command ?? r?.args?.command ?? "");
142
- if (!isProjectWrite(undefined, cmd)) continue;
143
- out.add("bash: " + cmd.slice(0, 60));
144
- continue;
145
- }
146
- const f = r?.input?.file_path ?? r?.args?.file_path ?? r?.input?.path ?? r?.args?.path;
147
- if (isProjectWrite(f ? String(f) : undefined)) out.add(String(f));
148
- }
149
- return [...out];
150
- }
151
117
 
152
118
  export default function absPiHook(pi: ExtensionAPI): void {
153
119
  // 收尾注入的节流状态。声明在**外层** + 在 session_start 里重置:
@@ -155,17 +121,10 @@ export default function absPiHook(pi: ExtensionAPI): void {
155
121
  // (2026-09-13 实测:同一天多个会话时后半场全部静默)。
156
122
  let teardownNudged = false
157
123
  let agentEndSeen = false
158
- // 登记提醒节流:只需防**同一轮内**重复注入(一个 turn_end 只提醒一次)。
159
- // 跨轮不去重 —— 用户明确要「频繁一点」:同一文件改第二回仍是新任务进展,
160
- // 该提醒。曾经用「文件列表」当去重键 → 同一文件再改就永不提醒(实测 step②失败)。
161
- let lastNudgedTurn = -1
162
- let turnSeq = 0
163
124
 
164
- pi.on("session_start", () => {
125
+ pi.on("session_start", (event: any, _ctx: any) => {
165
126
  teardownNudged = false
166
127
  agentEndSeen = false
167
- lastNudgedTurn = -1
168
- turnSeq = 0
169
128
  return logHook("session_start").catch(() => {})
170
129
  })
171
130
 
@@ -176,8 +135,7 @@ export default function absPiHook(pi: ExtensionAPI): void {
176
135
  })
177
136
 
178
137
  // 每轮结束 = 这轮干了什么(改了哪些文件)。机械事实,供收尾时回忆。
179
- // 同时:写文件 = 任务开始 → **立刻**提醒登记(不等 agent_end)。
180
- pi.on("turn_end", async (event: any, ctx: any) => {
138
+ pi.on("turn_end", async (event: any, _ctx: any) => {
181
139
  const files = new Set<string>()
182
140
  for (const r of event?.toolResults || []) {
183
141
  const name = String(r?.toolName || "")
@@ -191,29 +149,8 @@ export default function absPiHook(pi: ExtensionAPI): void {
191
149
  }
192
150
  }
193
151
  if (files.size) addNote("tool", [...files].join(", "))
194
-
195
- // ---- 登记提醒:项目文件被写 = 任务已经开始 ----
196
- const touched = projectWrites(event?.toolResults || [])
197
- if (!touched.length) return
198
- const me = ++turnSeq
199
- if (me === lastNudgedTurn) return // 同一轮不重复(turn_end 理论上只来一次,防御)
200
- lastNudgedTurn = me
201
- const cwd = (ctx && ctx.cwd) || process.cwd()
202
- if (!(await findBrain(cwd))) return // 无图谱 = 不在这项目沉淀,不打扰
203
- await logHook(`turn_end:register-nudge files=${touched.length}`).catch(() => {})
204
- try {
205
- pi.sendUserMessage(
206
- "[abs 登记提醒] 本轮改了项目文件,说明有任务在进行。\n" +
207
- "改了:" + touched.slice(0, 6).join(", ") + (touched.length > 6 ? ` 等 ${touched.length} 个` : "") + "\n" +
208
- "若这属于某个任务,立刻登记(别等到会话结束才回忆——那时必漏):\n" +
209
- " · 新任务: abs todo add <id> --note \"做什么\"\n" +
210
- " · 已有任务: abs todo note <id> --note \"改到哪/下一步\"\n" +
211
- " · 纯讨论/调研/改图谱自身 → 无需登记,回「跳过」即可。\n" +
212
- "简洁执行,不要复述本条提醒。",
213
- { deliverAs: "followUp" },
214
- )
215
- } catch {}
216
152
  })
153
+
217
154
  // 收尾注入: 每个会话最多一次, 避免反复打扰。
218
155
  pi.on("agent_end", async (event: any, ctx: any) => {
219
156
  if (teardownNudged) return
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@fanchao8609/agent_brain_sync",
3
- "version": "1.8.0",
3
+ "version": "1.8.1",
4
4
  "description": "agent-brain-sync: 跨会话 AI 编码记忆 — hook 纯触发 + CLI/MCP 读写 .brain markdown 图谱, 防并发写保护。",
5
5
  "type": "module",
6
6
  "license": "MIT",
package/skill/SKILL.md CHANGED
@@ -214,30 +214,6 @@ Done 区由 `abs wrapup` 在会话结束时自动把「超 3 天且整天都已
214
214
  > Op​enCode todowrite / pi `/list`)—— 那些多是会话内临时,不写 `.brain/todo.md`,
215
215
  > 下会话接不上、收尾没影。原生 todo 顶多记“本会话不跨断点的临时拆解”。
216
216
 
217
- ## 写文件 = 任务开始(收到登记提醒就立即做)
218
-
219
- **改了项目文件,说明有任务在进行。** hook 观测到写入会就地注入一条
220
- `[abs 登记提醒]`(pi 在 `turn_end`、Op​enCode 在 `tool.execute.after`),**不等会话结束**。
221
-
222
- **为什么不等收尾**(实测教训):旧设计只在会话末尾提醒 —— 而那时用户已想结束、
223
- AI 只想收尾不想登记。且「任务何时开始」只有写文件那一刻清楚:等收尾回忆必漏。
224
-
225
- 收到登记提醒,当场判断(**三选一,不猜**):
226
-
227
- | 情况 | 动作 |
228
- |---|---|
229
- | 属于某个任务,todo 里没有 | `abs todo add <id> --note "做什么"` |
230
- | 属于已在登记的任务 | `abs todo note <id> --note "改到哪/下一步"` |
231
- | 纯讨论/调研/只改 `.brain/` 自身 | **无需登记**,回「跳过」即可 |
232
-
233
- > **「跳过」是合法选项** —— 不逼你造任务。假条目比不登记更坏(污染看板,下会话当真)。
234
- >
235
- > **判据是「整个项目」,但排除 `.brain/` 自身**:`abs note` / `abs log` 也写文件,
236
- > 但那是**记录行为**不是任务 —— 不排除则每落一条经验都弹一次提醒,纯噪音。
237
- >
238
- > **提醒很频繁是故意的**:每轮写文件都提醒(同一文件改第二回也提)。嫌吵就当场登记,
239
- > 登记完下一轮仍有写入还会提 —— 它盯的是「有活干就有登记」,不是「提醒过一次就算了」。
240
-
241
217
  ## 每轮结束:收尾循环(Stop / 告一段落后必做)
242
218
 
243
219
  **每个任务边界、被 Stop/打断、告一段落时,别停半空。** 这是“开场接上状态、结束落回状态”的闭环。