@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.
- package/hooks/abs.opencode.ts +3 -43
- package/hooks/abs.pi.ts +7 -70
- package/package.json +1 -1
- package/skill/SKILL.md +0 -24
package/hooks/abs.opencode.ts
CHANGED
|
@@ -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
|
-
|
|
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 (
|
|
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
|
-
*
|
|
17
|
-
*
|
|
18
|
-
*
|
|
19
|
-
*
|
|
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
|
-
|
|
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
package/skill/SKILL.md
CHANGED
|
@@ -214,30 +214,6 @@ Done 区由 `abs wrapup` 在会话结束时自动把「超 3 天且整天都已
|
|
|
214
214
|
> OpenCode todowrite / pi `/list`)—— 那些多是会话内临时,不写 `.brain/todo.md`,
|
|
215
215
|
> 下会话接不上、收尾没影。原生 todo 顶多记“本会话不跨断点的临时拆解”。
|
|
216
216
|
|
|
217
|
-
## 写文件 = 任务开始(收到登记提醒就立即做)
|
|
218
|
-
|
|
219
|
-
**改了项目文件,说明有任务在进行。** hook 观测到写入会就地注入一条
|
|
220
|
-
`[abs 登记提醒]`(pi 在 `turn_end`、OpenCode 在 `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/打断、告一段落时,别停半空。** 这是“开场接上状态、结束落回状态”的闭环。
|