@fanchao8609/agent_brain_sync 1.19.6 → 1.20.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/README.md CHANGED
@@ -62,16 +62,15 @@ pi 与 omp 宿主额外多一层 UI(其它宿主没有):**编辑器上方
62
62
  ─────────────────────────────────────────────────
63
63
  📋 todo (3) — fanchao · ☕ 靠咖啡续命
64
64
  ├─ [进行中] some-task ●●○ — 干活中
65
- │ ↳ 断点: hooks/abs.pi.ts:120
65
+ │ - [ ] hooks/abs.pi.ts:120
66
66
  ├─ [滞留中] waiting — 等外部输入
67
67
  └─ [进行中] another ●●● — …
68
68
  ```
69
69
 
70
70
  - **只显示未完成**,Done 归计数不占位(数量在标题里)
71
71
  - **按当前使用者过滤**:显示 `[[我]]` 的 + 没标作者的(老任务/手写),别人的不显示
72
- - **断点行 `↳` 挂在父任务下**;`├─` / `└─` 表结构
73
- - **断点也可以是任务**:` ↳ [ ] <id> — 说明` 与普通任务同等对待(上看板、可 `done` / `note` / `state`);
74
- 单独的 `↳ 断点: 一句话` 仍是纯附属行、不算任务
72
+ - **子项(缩进的 `- [ ]`)挂在父任务下**;`├─` / `└─` 表结构
73
+ - **子项可各自勾选**:` - [ ] 一句话` → ` - [x] 一句话`;全部勾上才能 `done` 父任务
75
74
  - **「进行中」带三点动画**`○○○ → ●●○ → ●●●`(250ms/帧)—— 一眼看出哪条在跑
76
75
  - **实时刷新**:我调 `abs_task` / `abs_board` 后立即重画,不用等我讲完话
77
76
  - **上描边与输入框同色满宽**(主题色 `thinkingOff`,跟 pi 输入框的边框一致)
@@ -109,8 +108,8 @@ abs todo # 看板 Todo / Done(行首带状态标记)
109
108
 
110
109
  abs todo add TASK-1 --note "要做什么"
111
110
  abs todo note TASK-1 --note "改到 X 文件 L40" # 追加一条子项(标准 md 嵌套列表,可多条)
112
- abs todo bp-done TASK-1 [--n 2] # 勾子项(n=第几条,缺省最后一条)
113
- abs todo bp-undone TASK-1 [--n 2] # 取消勾(勾错了退回)
111
+ abs todo child-done TASK-1 [--n 2] # 勾子项(n=第几条,缺省最后一条)
112
+ abs todo child-undone TASK-1 [--n 2] # 取消勾(勾错了退回)
114
113
  abs todo memo TASK-1 "随手记一段(可多句,不上看板)" # 任务笔记 → sessions/;无参=查看
115
114
  abs todo state TASK-1 --note "进行中|讨论中|滞留中" # 改行首状态标记
116
115
  abs todo done TASK-1
@@ -132,7 +131,9 @@ abs update # 升级到最新版并刷新四宿主
132
131
 
133
132
  > `abs todo start` 与 `abs todo add` 等价(都登记任务)。
134
133
  > **子项就是标准 Markdown 嵌套列表**:任务下缩进 2 空格的 `- [ ] …`,可多条、可各自勾选;
135
- > 面板按缩进画树线,归档时原样保留缩进。旧格式 `↳ 断点:` 仍能读(存量不坏)。
134
+ > 面板按缩进画树线,归档时原样保留缩进。
135
+ > 旧版的 `↳ 断点:` 写法已在 1.20.0 移除;存量会在 `abs load` 或任意写操作时
136
+ > 自动迁移成标准子项(内容一条不丢),不需要手工改。
136
137
  > 任务 id 不能含空格(用连字符),含空格会被明确拒掉。
137
138
  > 旧版 `abs task ...` / `abs board` 已废,打它们会报「未知命令」并退出码 1。
138
139
  > `abs wrapup` / `abs teardown-check` 是 hook 内部命令,无需手动调用。
package/bin/abs.js CHANGED
@@ -311,9 +311,9 @@ const subUsage = {
311
311
  ' abs todo 看板',
312
312
  ' abs todo add <id> [--note "做什么"] [--section 讨论中|滞留中|搁置]',
313
313
  ' abs todo rename <旧id> --note "<新id>" 改任务 id(起错名/不可读时)',
314
- ' abs todo note <id> --note "断点/进度"',
315
- ' abs todo bp-done <id> 勾断点 → `↳ [x] 断点:`(做完落一句)',
316
- ' abs todo bp-undone <id> 取消勾(勾错了退回)',
314
+ ' abs todo note <id> --note "进度/子项" 追加一条子项(标准 md 缩进列表)',
315
+ ' abs todo child-done <id> [--n N] 勾子项(缺省最后一条)',
316
+ ' abs todo child-undone <id> [--n N] 取消勾',
317
317
  ' abs todo memo <id> "随手记(可多句)" 任务笔记 → sessions/,不上看板;无文字=查看',
318
318
  ' abs todo state <id> --note 进行中|讨论中|滞留中|搁置',
319
319
  ' abs todo done <id> [--as 落地|否决|仅方案] [结语文字]',
@@ -378,8 +378,8 @@ const TODO_ACTIONS = {
378
378
  add: 'start',
379
379
  start: 'start', // add 的别名(老习惯保留)
380
380
  note: 'note',
381
- 'bp-done': 'bp-done', // 勾断点:`↳ [ ] 断点:` → `↳ [x] 断点:`
382
- 'bp-undone': 'bp-undone', // 取消勾断点(勾错了退回)
381
+ 'child-done': 'child-done', // 勾子项(标准 md 缩进列表项)
382
+ 'child-undone': 'child-undone', // 取消勾子项
383
383
  memo: 'memo', // 任务随手记:多句/追加/不占看板(无文字时=查看)
384
384
  state: 'state', // 改行首状态标记:进行中|讨论中|滞留中|搁置(原地,不搬区)
385
385
  rename: 'rename', // 改任务 id:人工起错名、或旧 id 不可读时用
@@ -475,7 +475,7 @@ async function main() {
475
475
  if (!action) {
476
476
  throw new Error(
477
477
  `✗ 未知子命令 "${sub}"\n` +
478
- ` 可用: add / start / note / bp-done / bp-undone / memo / state / done / archive\n` +
478
+ ` 可用: add / start / note / child-done / child-undone / memo / state / done / archive\n` +
479
479
  ` 看板: abs todo(不带参数)\n` +
480
480
  ` 登记任务: abs todo add <id> --note "做什么"`,
481
481
  );
@@ -499,7 +499,7 @@ async function main() {
499
499
  break;
500
500
  }
501
501
  // 勾/取消勾子项:只收 <id>(子项位置 = 「某任务的第 N 条」,没有自己的 id)
502
- if (action === 'bp-done' || action === 'bp-undone') {
502
+ if (action === 'child-done' || action === 'child-undone') {
503
503
  rejectExtra(rest2, `abs todo ${sub} <id> [--n N]`);
504
504
  if (!id) throw new Error(`✗ 缺 <id>\n 用法: abs todo ${sub} <id> [--n N]`);
505
505
  const n = opts.n ? Number(opts.n) : undefined;
package/bin/mcp.js CHANGED
@@ -90,7 +90,7 @@ tool(
90
90
 
91
91
  tool(
92
92
  'abs_board',
93
- '读取当前项目 todo 看板(含断点/Next-Step)。先 resolve 得到项目,再传其根目录。',
93
+ '读取当前项目 todo 看板(含任务与子项)。先 resolve 得到项目,再传其根目录。',
94
94
  { cwd: z.string().describe('项目根目录(.brain/ 所在处)') },
95
95
  async ({ cwd }) => {
96
96
  const root = await findBrainRoot(cwd || process.cwd());
@@ -112,12 +112,12 @@ tool(
112
112
 
113
113
  tool(
114
114
  'abs_task',
115
- '任务实时落盘(幂等键 = id)。start 登记进 Todo / note 补子项(**追加**,标准 md 嵌套列表,`abs todo note`)/ bp_done、bp_undone 勾/取消勾子项(n=第几条,缺省最后一条)/ memo 任务随手记(多句/追加/不上看板)/ state 改状态 / done 完成归位 Done(done 时 note=结语文字,as=结语类型:落地|否决|仅方案)。必填: cwd, action, id。多会话并行时,start/add 带上 session(值见 system prompt 的 [会话] 段)以标记「这条任务谁在做」。',
115
+ '任务实时落盘(幂等键 = id)。start 登记进 Todo / note 补子项(**追加**,标准 md 嵌套列表,`abs todo note`)/ child_done、child_undone 勾/取消勾子项(n=第几条,缺省最后一条)/ memo 任务随手记(多句/追加/不上看板)/ state 改状态 / done 完成归位 Done(done 时 note=结语文字,as=结语类型:落地|否决|仅方案)。必填: cwd, action, id。多会话并行时,start/add 带上 session(值见 system prompt 的 [会话] 段)以标记「这条任务谁在做」。',
116
116
  {
117
- action: z.enum(['add', 'start', 'done', 'note', 'bp_done', 'bp_undone', 'memo', 'state']),
117
+ action: z.enum(['add', 'start', 'done', 'note', 'child_done', 'child_undone', 'memo', 'state']),
118
118
  id: z.string().describe('任务幂等键,如 TASK-xxx 或子任务名'),
119
- n: z.number().int().positive().optional().describe('bp_done/bp_undone 用:第几条子项(1-based,缺省=最后一条)'),
120
- cwd: z.string().describe('项目根目录(.brain/ 所在处)'), note: z.string().optional().describe('add/start=做什么; note=断点(文件/到哪步,一句话); memo=随手记(可多句、不定长,追加式;不传=查看已有笔记); state=进行中|讨论中|滞留中|搁置(搁置=用户改方向/不做了;滞留中=还要做只是卡住); done=结语文字'),
119
+ n: z.number().int().positive().optional().describe('child_done/child_undone 用:第几条子项(1-based,缺省=最后一条)'),
120
+ cwd: z.string().describe('项目根目录(.brain/ 所在处)'), note: z.string().optional().describe('add/start=做什么; note=追加一条子项(改到哪/下一步,一句话); memo=随手记(可多句、不定长,追加式;不传=查看已有笔记); state=进行中|讨论中|滞留中|搁置(搁置=用户改方向/不做了;滞留中=还要做只是卡住); done=结语文字'),
121
121
  as: z.enum(['落地', '否决', '仅方案']).optional().describe('仅 done:结语类型。默认 落地。做了又撤/评估后不做用 否决,只设计过用 仅方案 —— 别让假【落地】污染看板'),
122
122
  // 会话 id:多会话并行时标记「这条任务是谁在做」。
123
123
  // 值取自 system prompt 里的 `[会话] 本会话 id = xxx`(hook 注入),由模型原样回传。
@@ -130,8 +130,8 @@ tool(
130
130
  if (!root) return errNoBrain(cwd || process.cwd());
131
131
  // add 是 start 的别名(与 CLI `abs todo add` 对齐)
132
132
  const act = action === 'add' ? 'start' : action;
133
- // bp_done → bp-done(MCP 参数名不用连字符,内部 action 用)
134
- const act2 = act === 'bp_done' ? 'bp-done' : act === 'bp_undone' ? 'bp-undone' : act;
133
+ // child_done → child-done(MCP 参数名不用连字符,内部 action 用)
134
+ const act2 = act === 'child_done' ? 'child-done' : act === 'child_undone' ? 'child-undone' : act;
135
135
  return { content: [{ type: 'text', text: await cmdTask({ dir: root, action: act2, id, note, as, session, n }) }] };
136
136
  }
137
137
  );
package/hooks/abs.omp.ts CHANGED
@@ -104,7 +104,7 @@ function inputField(input: unknown, key: string): string {
104
104
  }
105
105
 
106
106
  /** 会改文件的工具名。用于判定「真的开始干活了」—— 排查类会话先跑一堆读命令,
107
- * 若用「第一次 tool_call」当判据,断点会被写在与任务无关的文件上。
107
+ * 若用「第一次 tool_call」当判据,子项会被写在与任务无关的文件上。
108
108
  * 白名单必须按宿主实际用法穷举,漏一个就把功能静默关掉(含 bash:大量修改是经 bash 完成的)。 */
109
109
  const WRITE_TOOL = /^(write|edit|apply_patch|patch|multiedit|notebookedit|bash|shell)$/i
110
110
 
@@ -193,6 +193,8 @@ const TODO_GUIDELINES = [
193
193
  'Never mark a task "done" when tests are failing, the implementation is partial, or an error is unresolved — keep it in progress and add a task for the blocker instead.',
194
194
  'Keep exactly one task in "进行中" at a time; when the user switches direction or drops a task, use action "state" with 搁置 (dropped/not doing it); 滞留中 for still-wanted-but-stuck; 讨论中 for still-open. If you finish one and another is ready, promote it explicitly.',
195
195
  'When completing, pick the honest conclusion via the `as` field: 落地 (built and verified) / 否决 (decided against, or built then reverted) / 仅方案 (designed only). A wrong conclusion makes the next session treat "considered" as "completed".',
196
+ // 文档精简九条(用户 2026-10-22 定,原文见 skill 的「文档精简」一节):管每一行写进 .brain 的文字。
197
+ 'Every line you write into .brain/ (todo rows, log rows, knowledge pages, session snapshots) must pass the nine-bullet doc-slimming rule in the abs skill: only what serves the topic (drop the rest), no unverified summaries, ordered and layered, plain declarative wording (no rhetorical questions, no guessing), no duplication (cite the other doc instead), terse prohibitions, nothing the user did not ask for, plain Chinese.',
196
198
  ]
197
199
 
198
200
  /** 从 skill 里挑出的【硬规则】—— 只第一轮送, 与 load 同机会(同 pi 版方案 B)。 */
@@ -354,8 +356,8 @@ export function parseOpenTasks(md: string, max = PANEL_MAX_ROWS, who = ''): {
354
356
  rows: { state: string; id: string; desc: string; kids: { done: boolean; text: string }[]; sid: string }[]
355
357
  hidden: number
356
358
  } {
357
- // 两阶段:先无过滤地按序解析(断点归属需要「上一条」是原文里真正的前一条),
358
- // 再按作者过滤。若边解析边过滤,被滤掉的条目下方的断点会挂到它上面那一条。
359
+ // 两阶段:先无过滤地按序解析(子项归属需要「上一条」是原文里真正的前一条),
360
+ // 再按作者过滤。若边解析边过滤,被滤掉的条目下方的子项会挂到它上面那一条。
359
361
  type Row = { state: string; id: string; desc: string; kids: { done: boolean; text: string }[]; authors: string[]; sid: string }
360
362
  const parsed: Row[] = []
361
363
  let inTodo = false
@@ -364,7 +366,7 @@ export function parseOpenTasks(md: string, max = PANEL_MAX_ROWS, who = ''): {
364
366
  if (/^##\s+Todo\s*$/.test(line)) { inTodo = true; continue }
365
367
  if (/^##\s+/.test(line)) { inTodo = false; continue }
366
368
  if (!inTodo) continue
367
- // 子项行(标准 md:缩进的 `- [ ]`;旧格式 `↳ 断点:` 继续认)→ 挂到上一条任务
369
+ // 子项行(标准 md:缩进的 `- [ ]`)→ 挂到上一条任务
368
370
  const kid = parseChildLine(line)
369
371
  if (kid && kid.indent > 0) {
370
372
  const last = parsed[parsed.length - 1]
@@ -372,9 +374,9 @@ export function parseOpenTasks(md: string, max = PANEL_MAX_ROWS, who = ''): {
372
374
  continue
373
375
  }
374
376
  // `- [ ] [状态] <id> …`;状态标记可能缺省。
375
- // 两种前缀都收:`- [ ]`(普通任务)与 `↳ [ ]`(断点任务,2026-10-16 新增)。
376
- // 断点任务必须显示 —— 它就是一等待任务(写得进去、看得见、能 done)。
377
- const m = line.match(/^\s*(?:-\s*\[ \]|↳\s*\[ \])\s*(?:\[([^\]]+)\]\s*)?(\S+)\s*(.*)$/)
377
+ // 只认 `- [ ]`(2026-10-20 旧 `↳ [ ]` 写法已废弃)。
378
+ // 子项任务必须显示 —— 它就是一等待任务(写得进去、看得见、能 done)。
379
+ const m = line.match(/^\s*-\s*\[ \]\s*(?:\[([^\]]+)\]\s*)?(\S+)\s*(.*)$/)
378
380
  if (!m) continue
379
381
  const state = m[1] || '进行中'
380
382
  const id = m[2]
@@ -417,13 +419,9 @@ async function currentUser(): Promise<string> {
417
419
  * 宽度规则:CJK/全角记 2 列,其余记 1 列。 */
418
420
  /** 解析子项行(与 src/todo/common.js 的 parseChild 同规则,双份实现 —— 见 pi 版说明)。 */
419
421
  function parseChildLine(l: string): { done: boolean; text: string; indent: number } | null {
420
- const s = String(l || '')
421
- const old = s.match(/^(\s*)↳\s*(?:\[[ x]\]\s*)?(?:断点[::]\s*)?(.*)$/)
422
- if (old) {
423
- if (!old[2].trim()) return null
424
- return { done: /^\s*↳\s*\[x\]/.test(s), text: old[2].trim(), indent: old[1].length }
425
- }
426
- const m = s.match(/^(\s+)-\s*\[([ x])\]\s*(.*)$/)
422
+ // 只认标准 md 缩进子项(2026-10-20 旧箭头写法已废弃)。
423
+ // 旧行由 CLI 侧 migrateLegacyChild 在读写时迁移掉,面板拿到的已是新格式。
424
+ const m = String(l || '').match(/^(\s+)-\s*\[([ x])\]\s*(.*)$/)
427
425
  if (!m) return null
428
426
  return { done: m[2] === 'x', text: m[3].trim(), indent: m[1].length }
429
427
  }
@@ -532,7 +530,7 @@ export function renderPanelLines(
532
530
  return lines
533
531
  }
534
532
  data.rows.forEach((t, i) => {
535
- // `└─` 只给最后一条 —— 不管它有没有断点(否则末条带断点时全篇没有收尾)。
533
+ // `└─` 只给最后一条 —— 不管它有没有子项(否则末条带子项时全篇没有收尾)。
536
534
  const isLast = i === lastIdx
537
535
  const branch = isLast ? '└─' : '├─'
538
536
  const anim = t.state === '进行中' && animPhase >= 0 ? ' ' + fg('accent', todoAnimFrame(animPhase)) : ''
@@ -707,7 +705,7 @@ export default function absOmpExtension(pi: ExtensionAPI): void {
707
705
  resetThrottle()
708
706
 
709
707
  pi.on("session_start", (_event: SessionStartEvent, ctx: ExtensionContext) => {
710
- // 新会话 = 重新登记:不重置则同进程的第二个会话永远不再落断点
708
+ // 新会话 = 重新登记:不重置则同进程的第二个会话永远不再落子项
711
709
  autoTaskSeen = false
712
710
  filesSeen.clear() // 同理:新会话的文件痕迹不能带到下个会话
713
711
  loadInjected.clear() // 同:新会话的每个目录都要重新摆一次开局状态
@@ -743,7 +741,7 @@ export default function absOmpExtension(pi: ExtensionAPI): void {
743
741
  pendingTodoTool = true
744
742
  logHook(`tool_call todo_tool name=${name} target=${target || '-'}`).catch(() => {})
745
743
  }
746
- // 自动登记断点:真动文件时把「改到哪个」记进本会话(可观测埋点,不写看板)。
744
+ // 自动登记子项:真动文件时把「改到哪个」记进本会话(可观测埋点,不写看板)。
747
745
  if (!autoTaskSeen && isWriteCall(name, input)) {
748
746
  autoTaskSeen = true
749
747
  const sid = sessionIdFor(ctx)
package/hooks/abs.pi.ts CHANGED
@@ -95,15 +95,15 @@ function sessionIdFor(ctx: any): string {
95
95
  }
96
96
 
97
97
  /** 会改文件的工具名。用于判定「真的开始干活了」—— 排查类会话先跑一堆读命令,
98
- * 若用「第一次 tool_call」当判据,断点会被写在与任务无关的文件上。
98
+ * 若用「第一次 tool_call」当判据,子项会被记在与任务无关的文件上。
99
99
  *
100
100
  * 为何含 bash:大量修改是经 bash 完成的(heredoc / sed -i / > 重定向),
101
- * 漏掉它 = 纯 bash 会话永远不记断点(见 concept opencode-inject-channel-verdict
101
+ * 漏掉它 = 纯 bash 会话永远不记子项(见 concept opencode-inject-channel-verdict
102
102
  * 的同源教训:白名单必须按宿主实际用法穷举,漏一个就把功能静默关掉)。
103
103
  */
104
104
  const WRITE_TOOL = /^(write|edit|apply_patch|patch|multiedit|notebookedit|bash|shell)$/i
105
105
 
106
- /** bash 命令里没有写入迹象则不算「干活」—— 否则 `ls`/`git status` 也会记断点。 */
106
+ /** bash 命令里没有写入迹象则不算「干活」—— 否则 `ls`/`git status` 也会记子项。 */
107
107
  const BASH_WRITES = /(^|[^\w])(>|>>|sed\s+-i|tee\b|mkdir\b|rm\b|mv\b|cp\b|touch\b|cat\s*>)/
108
108
 
109
109
  /** 这次 tool_call 是否算「真的动了文件」。 */
@@ -145,8 +145,8 @@ let agentEndSeen = false
145
145
  * 同样在模块级,理由同上(闭包 flag 在重复注册时不共享)。 */
146
146
  let pendingTodoTool = false
147
147
 
148
- /** 本会话是否已记过自动任务断点。模块级(重复注册时闭包 flag 不共享 —— 同 agentEndSeen 的坑)。
149
- * 只记第一次写文件,不是每次都记:断点是「记到哪」不是流水账。 */
148
+ /** 本会话是否已记过自动任务子项。模块级(重复注册时闭包 flag 不共享 —— 同 agentEndSeen 的坑)。
149
+ * 只记第一次写文件,不是每次都记:子项是「记到哪」不是流水账。 */
150
150
  let autoTaskSeen = false
151
151
 
152
152
  /** 本会话动过的文件(去重,最多记 FILES_SEEN_MAX 个)。
@@ -219,7 +219,7 @@ const TODO_GUIDELINES = [
219
219
  'Use the abs task tool as soon as you receive instructions that involve changing this project — including investigation work where you read code or ask questions before any edit. Call it with action "start" and a short id before the work itself, not after. Skip it for pure questions about general knowledge, one-line answers, and conversation. (Tool name varies by host: `mcp__abs` with tool="abs_task", or `mcp__abs__abs_task`, or `abs_task` — use whichever form this session exposes.)',
220
220
  // ② 完成即结:不许攒
221
221
  'Mark a task "done" immediately when it finishes — never batch completions at the end of a session.',
222
- // ③ 断点:跨会话接力靠它
222
+ // ③ 子项:跨会话接力靠它
223
223
  'Before starting a task, record the checkpoint with action "note" (which file, which step) so a later session can resume.',
224
224
  // ④ 反例(照 rpiv-todo):明确什么情况【不许】标完成 —— 只给正向要求时,
225
225
  // 模型倾向于把"我以为做完了"当成完成,结语失真会污染下个会话的判断。
@@ -228,6 +228,9 @@ const TODO_GUIDELINES = [
228
228
  'Keep exactly one task in "进行中" at a time; when the user switches direction or drops a task, use action "state" with 搁置 (dropped/not doing it); 滞留中 for still-wanted-but-stuck; 讨论中 for still-open. If you finish one and another is ready, promote it explicitly.',
229
229
  // ⑥ 结语要真实:三种结语各有含义,别一律写落地。
230
230
  'When completing, pick the honest conclusion via the `as` field: 落地 (built and verified) / 否决 (decided against, or built then reverted) / 仅方案 (designed only). A wrong conclusion makes the next session treat "considered" as "completed".',
231
+ // ⑦ 文档精简九条(用户 2026-10-22 定,原文见 skill 的「文档精简」一节):
232
+ // 管每一行写进 .brain 的文字;与上面六条行为规则不重叠,故不合并。
233
+ 'Every line you write into .brain/ (todo rows, log rows, knowledge pages, session snapshots) must pass the nine-bullet doc-slimming rule in the abs skill: only what serves the topic (drop the rest), no unverified summaries, ordered and layered, plain declarative wording (no rhetorical questions, no guessing), no duplication (cite the other doc instead), terse prohibitions, nothing the user did not ask for, plain Chinese.',
231
234
  ]
232
235
 
233
236
  /** 当前项目是否有 .brain 图谱 —— 有才加指引(没图谱的项目里这指引是噪音)。
@@ -717,15 +720,15 @@ export function pickNickname(rnd: () => number = Math.random): string {
717
720
  * who 不为空时只收“我的任务”:行内有 [[who]] 的,或**完全没标作者**的
718
721
  * (老任务/手写的没标,漏掉比多显示更糟)。标了别人的则不收。
719
722
  *
720
- * 断点行(缩进的 ↳)归属到它上一条任务,不单独成条。 */
723
+ * 子项行(缩进的 `- [ ]`)归属到它上一条任务,不单独成条。 */
721
724
  export function parseOpenTasks(md: string, max = PANEL_MAX_ROWS, who = ''): {
722
725
  total: number
723
726
  rows: { state: string; id: string; desc: string; kids: { done: boolean; text: string }[]; sid: string }[]
724
727
  hidden: number
725
728
  } {
726
- // 两阶段:先无过滤地按序解析(断点归属需要“上一条”是原文里真正的前一条),
727
- // 再按作者过滤。若边解析边过滤,被滤掉的条目下方的断点会挂到它上面那一条
728
- // —— 实测:别人任务的断点会显示在我的任务下面(2026-10-03 审查发现)。
729
+ // 两阶段:先无过滤地按序解析(子项归属需要“上一条”是原文里真正的前一条),
730
+ // 再按作者过滤。若边解析边过滤,被滤掉的条目下方的子项会挂到它上面那一条
731
+ // —— 实测:别人任务的子项会显示在我的任务下面(2026-10-03 审查发现)。
729
732
  type Row = { state: string; id: string; desc: string; kids: { done: boolean; text: string }[]; authors: string[]; sid: string }
730
733
  const parsed: Row[] = []
731
734
  let inTodo = false
@@ -734,7 +737,7 @@ export function parseOpenTasks(md: string, max = PANEL_MAX_ROWS, who = ''): {
734
737
  if (/^##\s+Todo\s*$/.test(line)) { inTodo = true; continue }
735
738
  if (/^##\s+/.test(line)) { inTodo = false; continue }
736
739
  if (!inTodo) continue
737
- // 子项行(标准 md:缩进的 `- [ ]`;旧格式 `↳ 断点:` 继续认)→ 挂到上一条任务。
740
+ // 子项行(标准 md:缩进的 `- [ ]`)→ 挂到上一条任务。
738
741
  // 子项可多条、可各自勾选(2026-10-16 用户定)。
739
742
  const kid = parseChildLine(line)
740
743
  if (kid && kid.indent > 0) {
@@ -743,10 +746,10 @@ export function parseOpenTasks(md: string, max = PANEL_MAX_ROWS, who = ''): {
743
746
  continue
744
747
  }
745
748
  // `- [ ] [状态] <id> …`;状态标记可能缺省。
746
- // 两种前缀都收:`- [ ]`(普通任务)与 `↳ [ ]`(断点任务,2026-10-16 新增)。
747
- // 断点任务必须显示 —— 它就是一等等任务(写得进去、看得见、能 done),
749
+ // 只认 `- [ ]`(2026-10-20 旧 `↳ [ ]` 写法已废弃)。
750
+ // 子项任务必须显示 —— 它就是一等等任务(写得进去、看得见、能 done),
748
751
  // 否则落回「静默死区」(用户在 .brain 里报的原症状)。
749
- const m = line.match(/^\s*(?:-\s*\[ \]|↳\s*\[ \])\s*(?:\[([^\]]+)\]\s*)?(\S+)\s*(.*)$/)
752
+ const m = line.match(/^\s*-\s*\[ \]\s*(?:\[([^\]]+)\]\s*)?(\S+)\s*(.*)$/)
750
753
  if (!m) continue
751
754
  const state = m[1] || '进行中'
752
755
  const id = m[2]
@@ -800,15 +803,11 @@ async function currentUser(): Promise<string> {
800
803
  *
801
804
  * 为何不 import src:面板是宿主扩展,单独装到 ~/.pi/extensions/,与包内 src/ 不同目录
802
805
  * (装完就断了)。故两端各一份 —— 规则简单(两步正则),且两边测试都盖。
803
- * 标准 md:缩进的 `- [ ] 内容`;旧格式 `↳ [ ] 断点: 内容` / `↳ 断点: 内容` 继续认。 */
806
+ * 标准 md:缩进的 `- [ ] 内容`。 */
804
807
  function parseChildLine(l: string): { done: boolean; text: string; indent: number } | null {
805
- const s = String(l || '')
806
- const old = s.match(/^(\s*)↳\s*(?:\[[ x]\]\s*)?(?:断点[::]\s*)?(.*)$/)
807
- if (old) {
808
- if (!old[2].trim()) return null
809
- return { done: /^\s*↳\s*\[x\]/.test(s), text: old[2].trim(), indent: old[1].length }
810
- }
811
- const m = s.match(/^(\s+)-\s*\[([ x])\]\s*(.*)$/)
808
+ // 只认标准 md 缩进子项(2026-10-20 旧箭头写法已废弃)。
809
+ // 旧行由 CLI 侧 migrateLegacyChild 在读写时迁移掉,面板拿到的已是新格式。
810
+ const m = String(l || '').match(/^(\s+)-\s*\[([ x])\]\s*(.*)$/)
812
811
  if (!m) return null
813
812
  return { done: m[2] === 'x', text: m[3].trim(), indent: m[1].length }
814
813
  }
@@ -945,9 +944,9 @@ export function renderPanelLines(
945
944
  return lines
946
945
  }
947
946
  data.rows.forEach((t, i) => {
948
- // `└─` 只给最后一条 —— 不管它有没有断点。
949
- // (2026-10-03 审查修正:原条件 `i === lastIdx && !t.note` 会让“末条带断点”时
950
- // 全篇没有 └─ 收尾,断点行的 │ 又延伸到视觉底部,看起来像列表被截断。)
947
+ // `└─` 只给最后一条 —— 不管它有没有子项。
948
+ // (2026-10-03 审查修正:原条件 `i === lastIdx && !t.note` 会让“末条带子项”时
949
+ // 全篇没有 └─ 收尾,子项行的 │ 又延伸到视觉底部,看起来像列表被截断。)
951
950
  const isLast = i === lastIdx
952
951
  const branch = isLast ? '└─' : '├─'
953
952
  // 进行中的行带一个小动画(其余行不加,避免满屏都在跳)
@@ -1044,7 +1043,7 @@ export default function absPiHook(pi: ExtensionAPI): void {
1044
1043
  resetThrottle()
1045
1044
 
1046
1045
  pi.on("session_start", (event: any, ctx: any) => {
1047
- // 新会话 = 重新登记:不重置则同进程的第二个会话永远不再落断点
1046
+ // 新会话 = 重新登记:不重置则同进程的第二个会话永远不再落子项
1048
1047
  autoTaskSeen = false
1049
1048
  filesSeen.clear() // 同理:新会话的文件痕迹不能带到下个会话
1050
1049
  loadInjected.clear() // 同:新会话的每个目录都要重新摆一次开局状态
@@ -1085,8 +1084,8 @@ export default function absPiHook(pi: ExtensionAPI): void {
1085
1084
  pendingTodoTool = true
1086
1085
  logHook(`tool_call todo_tool name=${name} target=${target || '-'}`).catch(() => {})
1087
1086
  }
1088
- // 自动登记断点(2026-10-05):真的动文件时,把「改到哪个」记进本会话的自动任务。
1089
- // 为何不用「第一次 tool_call」:排查类会话先跑一堆读命令,断点会落在无关文件上。
1087
+ // 自动登记子项(2026-10-05):真的动文件时,把「改到哪个」记进本会话的自动任务。
1088
+ // 为何不用「第一次 tool_call」:排查类会话先跑一堆读命令,子项会落在无关文件上。
1090
1089
  // 写类工具只留一行痕(可观测),不 spawn、不写看板 —— 见上面的删除说明。
1091
1090
  if (!autoTaskSeen && isWriteCall(name, input)) {
1092
1091
  autoTaskSeen = true
@@ -1162,11 +1161,7 @@ export default function absPiHook(pi: ExtensionAPI): void {
1162
1161
  }
1163
1162
  if (rulesOn) openingRulesLogged = true
1164
1163
  }
1165
- // 【临时取证 2026-10-06】seen= 把 Set 内容原样打出来:
1166
- // 真实环境里 agent_brain_sync 从未 load_delivered 却 still skip → 与「按 cwd 分」矛盾。
1167
- // 靠这行区分:Set 空(→ 判断写错)vs Set 里已有该 cwd(→ 有东西提前加进去了)。
1168
- // 定位并修好后删掉这行。
1169
- logHook(`before_agent_start todo_guide=${ok ? 'on' : 'off'} load_guide=${loadState} opening_rules=${openingRulesLogged ? 'on' : 'skip'} seen=[${[...loadInjected].join(',')}] pending=[${loadPending}] cwd=${cwd}`).catch(() => {})
1164
+ logHook(`before_agent_start todo_guide=${ok ? 'on' : 'off'} load_guide=${loadState} opening_rules=${openingRulesLogged ? 'on' : 'skip'} cwd=${cwd}`).catch(() => {})
1170
1165
 
1171
1166
  // 自动登记已整体移除(2026-10-05 用户实报两点):
1172
1167
  // ① 卡顿 —— agent_end 每轮 spawn 一个 node 进程
@@ -1205,7 +1200,7 @@ export default function absPiHook(pi: ExtensionAPI): void {
1205
1200
  }
1206
1201
  // 每轮 spawn 已删除(2026-10-05 用户实报"卡顿"):agentEndSeen 一旦置位,
1207
1202
  // 下面那个条件就恒真 → 每轮结束都起一个 node 进程,纯属白烧。
1208
- // 断点改由 agent 在用 abs_task 时自己写(那时它本来就在跑,不额外起进程)。
1203
+ // 子项改由 agent 在用 abs_task 时自己写(那时它本来就在跑,不额外起进程)。
1209
1204
  })
1210
1205
 
1211
1206
  pi.on("session_shutdown", (_e: any, ctx: any) => {
package/hooks/event.sh CHANGED
@@ -103,7 +103,7 @@ fi
103
103
  printf '[%s] wrapup-remind 会话已结束(%s): 若 Today 有滞留任务或经验未沉淀, 下会话开头走收尾循环\n' \
104
104
  "$(date '+%Y-%m-%d %H:%M:%S')" "$SESSION_ID"
105
105
  } >> "$LOG_DIR/wrapup.log" 2>/dev/null || true
106
- # 机械快照滞留清单: abs wrapup 把当前项目未完成任务+断点追加到 wrapup.log (跨会话收尾保险)。
106
+ # 机械快照滞留清单: abs wrapup 把当前项目未完成任务+子项追加到 wrapup.log (跨会话收尾保险)。
107
107
  # abs wrapup 已自行写 wrapup.log (honor ABS_LOG_DIR); 这里 stdout 只进 hooks.log 作痕迹, 避免污染 wrapup.log。
108
108
  "$NODE_BIN" "$ABS_BIN" wrapup >>"$LOG_DIR/hooks.log" 2>/dev/null || true
109
109
  fi
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@fanchao8609/agent_brain_sync",
3
- "version": "1.19.6",
3
+ "version": "1.20.1",
4
4
  "description": "agent-brain-sync: 跨会话 AI 编码记忆 — hook 纯触发 + CLI/MCP 读写 .brain markdown 图谱, 防并发写保护。",
5
5
  "type": "module",
6
6
  "license": "MIT",