@fanchao8609/agent_brain_sync 1.28.4 → 1.28.5
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-rounded-editor/format.ts +29 -10
- package/hooks/abs-rounded-editor/index.ts +14 -9
- package/hooks/abs.omp.ts +2 -1
- package/hooks/abs.pi.ts +62 -33
- package/package.json +1 -1
|
@@ -72,21 +72,40 @@ function dropCols(s: string, n: number): string {
|
|
|
72
72
|
}
|
|
73
73
|
|
|
74
74
|
/**
|
|
75
|
-
* 推理强度 →
|
|
75
|
+
* 推理强度 → 面板与输入框的主色(2026-11-07 用户需求)。
|
|
76
|
+
* 色值是用户逐档指定的(给的是 hex),不是自己调的:
|
|
77
|
+
* max #ACB8CD · xhigh #95A5C3 · high #768298 · medium #697387
|
|
78
|
+
* low #5C6577 · minimal #505867
|
|
79
|
+
* 色相是用户挑的灰蓝(不是绿),换色只许按用户给的值改,别拿主题色或自己的审美去替换。
|
|
80
|
+
* off / 未知 → 空串,表示「不上色,沿用默认色」—— 用户 2026-11-06 定过:推理关着就保持原样。
|
|
76
81
|
*
|
|
77
|
-
*
|
|
78
|
-
*
|
|
79
|
-
*
|
|
80
|
-
*
|
|
82
|
+
* 为何自己拼 ANSI 而不是走 theme.fg:fg() 只认主题里的 token 名,主题那七档色相跟这组不一样。
|
|
83
|
+
* 终端支持 24 位色就给 38;2,否则退到 256 色板里的一档(见 ansi256)。
|
|
84
|
+
* ★ 256 色板里没有这种灰蓝:只能退到灰阶档(250/248/244/242/241/240),保住“一档比一档暗”、
|
|
85
|
+
* 丢掉一点蓝调 —— 24 位色终端才是标称效果。
|
|
81
86
|
*
|
|
82
|
-
* ★ 同一份逻辑在 hooks/abs.pi.ts 里也有一份(`
|
|
87
|
+
* ★ 同一份逻辑在 hooks/abs.pi.ts 里也有一份(`effortAnsi`)。不抽公共模块是故意的:
|
|
83
88
|
* 安装后 `abs.ts` 与 `abs-rounded-editor/` 是两个可独立删除的扩展,抽成共享文件会让
|
|
84
89
|
* 「删掉输入框样式目录」连带把 todo 面板搞崩。
|
|
85
90
|
*/
|
|
86
|
-
export
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
91
|
+
export const EFFORT_COLOR: Record<string, { rgb: string; ansi256: number }> = {
|
|
92
|
+
max: { rgb: '172;184;205', ansi256: 250 },
|
|
93
|
+
xhigh: { rgb: '149;165;195', ansi256: 248 },
|
|
94
|
+
high: { rgb: '118;130;152', ansi256: 244 },
|
|
95
|
+
medium: { rgb: '105;115;135', ansi256: 242 },
|
|
96
|
+
low: { rgb: '92;101;119', ansi256: 241 },
|
|
97
|
+
minimal: { rgb: '80;88;103', ansi256: 240 },
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
export function effortAnsi(level: string | null | undefined, colorMode = 'truecolor'): string {
|
|
101
|
+
const g = EFFORT_COLOR[String(level || '').trim().toLowerCase()]
|
|
102
|
+
if (!g) return ''
|
|
103
|
+
return colorMode === 'truecolor' ? `38;2;${g.rgb}` : `38;5;${g.ansi256}`
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
/** 用 effortAnsi 的结果包一段文字;空串原样返回(off 时不着色)。 */
|
|
107
|
+
export function paintAnsi(ansi: string, text: string): string {
|
|
108
|
+
return ansi ? `\x1b[${ansi}m${text}\x1b[39m` : text
|
|
90
109
|
}
|
|
91
110
|
|
|
92
111
|
/**
|
|
@@ -18,7 +18,7 @@
|
|
|
18
18
|
* 注意:自动补全菜单行排在底边之下、且自带内容宽度,不套竖线(否则菜单被框住会错位)。
|
|
19
19
|
*/
|
|
20
20
|
import { CustomEditor } from '@earendil-works/pi-coding-agent'
|
|
21
|
-
import { buildStatus, roundBorder, shortenModel
|
|
21
|
+
import { buildStatus, effortAnsi, paintAnsi, roundBorder, shortenModel } from './format.ts'
|
|
22
22
|
|
|
23
23
|
const SIDE = '│'
|
|
24
24
|
|
|
@@ -46,12 +46,18 @@ export class RoundedEditor extends CustomEditor {
|
|
|
46
46
|
if (lines.length < 2) return lines
|
|
47
47
|
|
|
48
48
|
const borderColor = (this as any).borderColor?.bind(this)
|
|
49
|
-
|
|
50
|
-
//
|
|
51
|
-
const token = thinkingColorToken(effortLevel)
|
|
49
|
+
// 文字色 + 边框色 = 推理强度的主色;off(或取不到)时为空 → 文字不上色、边框交回 pi 自己的边框色。
|
|
50
|
+
// 选 24 位色还是 256 色,问主题自己的 getColorMode(),与 pi 其他文字一致。
|
|
52
51
|
const themeNow = themeGetter?.()
|
|
53
|
-
|
|
54
|
-
|
|
52
|
+
let colorMode = 'truecolor'
|
|
53
|
+
try { colorMode = String(themeNow?.getColorMode?.() || 'truecolor') } catch {}
|
|
54
|
+
const ansi = effortAnsi(effortLevel, colorMode)
|
|
55
|
+
const effortFg = ansi ? (s: string) => paintAnsi(ansi, s) : null
|
|
56
|
+
// ★ 边框也跟着上主色(2026-11-07 用户补提:「输入框也是要改颜色」)。
|
|
57
|
+
// 前一轮只染了文字,结果输入框空着时框里没字、屏幕上只剩 pi 那圈紫/橙色边框,
|
|
58
|
+
// 看起来就像完全没改 —— 边框不跟着变,这个功能肉眼就看不见。
|
|
59
|
+
// roundBorder 本来就是「剥掉原色 → 整行重新上色」,故把上色函数传进 wrap 即可。
|
|
60
|
+
const wrap = effortFg ?? ((s: string) => (borderColor ? borderColor(s) : s))
|
|
55
61
|
const out: string[] = []
|
|
56
62
|
|
|
57
63
|
// 上边框:原本是整行 `─`(含 working 图标与滚动提示),两端换圆角,右端贴状态。
|
|
@@ -62,9 +68,8 @@ export class RoundedEditor extends CustomEditor {
|
|
|
62
68
|
|
|
63
69
|
for (let i = 1; i < bottomIdx; i++) {
|
|
64
70
|
const body = lines[i]
|
|
65
|
-
//
|
|
66
|
-
//
|
|
67
|
-
// 整行包裹而不逐字:光标由 \x1b[7m 反转标记,与前景色互不干扰;
|
|
71
|
+
// 推理强度主色染**输入文字**(2026-11-06 用户需求,2026-11-07 换成用户指定的六档色)。
|
|
72
|
+
// 边框同色(见上面 wrap)。整行包裹而不逐字:光标由 \x1b[7m 反转标记,与前景色互不干扰;
|
|
68
73
|
// 输入框内容是用户手打的纯文本,行内不会有 reset 把后半行冲掉。
|
|
69
74
|
out.push(wrap(SIDE) + (effortFg ? effortFg(body) : body) + wrap(SIDE))
|
|
70
75
|
}
|
package/hooks/abs.omp.ts
CHANGED
|
@@ -542,7 +542,8 @@ export function todoAnimFrame(phase: number): string {
|
|
|
542
542
|
* 规则:`thinking` + 首字母大写(thinkingMinimal…thinkingMax)。
|
|
543
543
|
* off 返回空串而不是 `thinkingOff`:那个色接近背景,染文字会看不清(2026-11-06 用户定)。
|
|
544
544
|
*
|
|
545
|
-
* ★ 与 pi
|
|
545
|
+
* ★ 与 pi 版不再字面对应:pi 版 2026-11-07 已改成绿色阶(`effortAnsi`,用户要求),
|
|
546
|
+
* omp 这边仍跟主题的 thinkingXxx 七色 —— 用户只提了 pi 面板。两个模板分别装到不同宿主,
|
|
546
547
|
* 独立成文件才能各自删除/升级。
|
|
547
548
|
*/
|
|
548
549
|
export function effortToken(level: string | null | undefined): string {
|
package/hooks/abs.pi.ts
CHANGED
|
@@ -936,8 +936,11 @@ function clipToWidth(s: string, width: number): string {
|
|
|
936
936
|
// 为何用盲文点阵而不是 ⠋⠙⠹ 之外的 ASCII 旋转符:
|
|
937
937
|
// ⠿ 系符号在等宽字体里都是 1 列宽,且视觉上是个转动的方框,比 | / - \ 均匀。
|
|
938
938
|
// 为何帧宽必须恒定:面板每 250ms 重绘,宽度一变整行就会左右抖。盲文恰好全是 1 列。
|
|
939
|
-
|
|
940
|
-
|
|
939
|
+
/** 进行中那行的小动画(2026-11-07 用户换掉了原来的盲文十帧旋转 ⠋⠙⠹…,嫌难看,改要圆圈)。
|
|
940
|
+
* 4 帧一圈 × 200ms ≈ 0.8 秒转一圈 —— 与旧动画(10 帧 × 100ms = 1 秒)节奏相当,
|
|
941
|
+
* 帧数少了若不降速会转成一团花。 */
|
|
942
|
+
const TODO_ANIM_FRAMES = ['◐', '◓', '◑', '◒']
|
|
943
|
+
const TODO_ANIM_MS = 200
|
|
941
944
|
|
|
942
945
|
/** 动画相位(每 tick +1)。模块级 —— 同 agentEndSeen,闭包 flag 在重复注册时不共享。 */
|
|
943
946
|
let animPhase = 0
|
|
@@ -971,22 +974,39 @@ export function todoAnimFrame(phase: number): string {
|
|
|
971
974
|
return TODO_ANIM_FRAMES[i]
|
|
972
975
|
}
|
|
973
976
|
|
|
974
|
-
/**
|
|
975
|
-
*
|
|
977
|
+
/** 推理强度 → 面板与输入框的主色(2026-11-07 用户需求)。
|
|
978
|
+
* 色值是用户逐档指定的(用户原话给了 6 个 hex),不是自己调的:
|
|
979
|
+
* max #ACB8CD · xhigh #95A5C3 · high #768298 · medium #697387
|
|
980
|
+
* low #5C6577 · minimal #505867
|
|
981
|
+
* 色相是用户挑的灰蓝(不是绿),换色只许按用户给的值改,别拿主题色或自己的审美去替换。
|
|
982
|
+
* off / 未知 → 空串,表示「不上色」:调用方用灰阶(用户 2026-11-06 定过,推理关着就别发光)。
|
|
976
983
|
*
|
|
977
|
-
*
|
|
978
|
-
*
|
|
979
|
-
*
|
|
980
|
-
* 「off 时保持默认色」。
|
|
984
|
+
* 为何自己拼 ANSI 而不是 theme.fg:fg() 只认主题里的 token 名,主题那七档色相跟这组不一样。终端支持 24 位色就给 38;2,否则退到 256 色板里同色相的一档(见 ansi256)。
|
|
985
|
+
* ★ 256 色板里没有这种灰蓝:只能退到灰阶档(250/248/244/242/241/240),保住“一档比一档暗”、
|
|
986
|
+
* 丢掉一点蓝调 —— 24 位色终端才是标称效果。
|
|
981
987
|
*
|
|
982
|
-
* ★ 同一份逻辑在 hooks/abs-rounded-editor/format.ts 里也有一份(`
|
|
988
|
+
* ★ 同一份逻辑在 hooks/abs-rounded-editor/format.ts 里也有一份(`effortAnsi`)。
|
|
983
989
|
* 不抽公共模块是故意的:安装后 `abs.ts` 与 `abs-rounded-editor/` 是两个可独立删除的扩展,
|
|
984
990
|
* 抽成共享文件会让「删掉输入框样式目录」连带把 todo 面板搞崩。
|
|
985
991
|
*/
|
|
986
|
-
export
|
|
987
|
-
|
|
988
|
-
|
|
989
|
-
|
|
992
|
+
export const EFFORT_COLOR: Record<string, { rgb: string; ansi256: number }> = {
|
|
993
|
+
max: { rgb: '172;184;205', ansi256: 250 },
|
|
994
|
+
xhigh: { rgb: '149;165;195', ansi256: 248 },
|
|
995
|
+
high: { rgb: '118;130;152', ansi256: 244 },
|
|
996
|
+
medium: { rgb: '105;115;135', ansi256: 242 },
|
|
997
|
+
low: { rgb: '92;101;119', ansi256: 241 },
|
|
998
|
+
minimal: { rgb: '80;88;103', ansi256: 240 },
|
|
999
|
+
}
|
|
1000
|
+
|
|
1001
|
+
export function effortAnsi(level: string | null | undefined, colorMode = 'truecolor'): string {
|
|
1002
|
+
const g = EFFORT_COLOR[String(level || '').trim().toLowerCase()]
|
|
1003
|
+
if (!g) return ''
|
|
1004
|
+
return colorMode === 'truecolor' ? `38;2;${g.rgb}` : `38;5;${g.ansi256}`
|
|
1005
|
+
}
|
|
1006
|
+
|
|
1007
|
+
/** 用 effortAnsi 的结果包一段文字;空串原样返回(off 时不着色)。 */
|
|
1008
|
+
export function paintAnsi(ansi: string, text: string): string {
|
|
1009
|
+
return ansi ? `\x1b[${ansi}m${text}\x1b[39m` : text
|
|
990
1010
|
}
|
|
991
1011
|
|
|
992
1012
|
export function renderPanelLines(
|
|
@@ -997,21 +1017,23 @@ export function renderPanelLines(
|
|
|
997
1017
|
animPhase = -1,
|
|
998
1018
|
nickname = '',
|
|
999
1019
|
effort = '',
|
|
1020
|
+
colorMode = 'truecolor',
|
|
1000
1021
|
): string[] {
|
|
1001
1022
|
// ★ 空看板不再直接返回空数组(2026-10-06 用户要求:「即便 todo 没有列表,也要在 pi 上显示」)。
|
|
1002
1023
|
// 理由与看板段一致:面板是「机制在不在」的常驻指示,进去就看不见 = 不知道机制活着。
|
|
1003
1024
|
// 下面仍要画框和标题,所以这里只是不再提前 return。
|
|
1004
1025
|
//
|
|
1005
1026
|
// ★ 主色跟推理强度走(2026-11-06 用户需求):标题、[状态]、树线 ├─└─、复选框 [ ]、
|
|
1006
|
-
// 进行中动画同用一色,与 pi
|
|
1007
|
-
// ★
|
|
1027
|
+
// 进行中动画同用一色,与 pi 输入框文字同色系。
|
|
1028
|
+
// ★ 2026-11-07 用户把色子换成自己指定的六档主色:max 最亮 → minimal 最暗(见 effortAnsi)。
|
|
1029
|
+
// ★ 2026-11-07 用户再改:面板里**所有文字都用这一色**(标题、状态、id、描述、子项、+N more),
|
|
1030
|
+
// 不再给描述/子项降成灰 —— 拿“颜色深浅”区分主次的写法作废了,层次只靠符号与缩进。
|
|
1031
|
+
// 别把描述改回 muted:那是用户主动去掉的(原话「所有文字都一致吧」)。
|
|
1032
|
+
// ★ off 时整块面板用灰阶(2026-11-06 用户补提:「当推理关闭的时候 todo 面板文字应该全是灰色」)——
|
|
1008
1033
|
// 高亮色只在真的开着推理时给,否则「推理关了面板还在发光」让人误以为还在深度思考。
|
|
1009
|
-
//
|
|
1010
|
-
const
|
|
1011
|
-
const main =
|
|
1012
|
-
// 描述/未勾子项的颜色:开着推理时 muted(比主色暗、又不至于暗到看不清);
|
|
1013
|
-
// off 时主色已经是 muted,描述得再降一阶用 dim,否则两者同色分不出主次。
|
|
1014
|
-
const flow = off ? 'dim' : 'muted'
|
|
1034
|
+
// 既然要求“一致”,off 也用同一个 muted,不再分两阶灰。
|
|
1035
|
+
const mainAnsi = effortAnsi(effort, colorMode)
|
|
1036
|
+
const main = mainAnsi ? (s: string) => paintAnsi(mainAnsi, s) : (s: string) => fg('muted', s)
|
|
1015
1037
|
const lines: string[] = []
|
|
1016
1038
|
// 上描边行已删除(2026-11-04 用户要求「去掉上边框」)。
|
|
1017
1039
|
// 历史:那行曾用 `thinkingOff` 对齐 pi 输入框描边色(排查三轮才定色)。
|
|
@@ -1023,7 +1045,7 @@ export function renderPanelLines(
|
|
|
1023
1045
|
const title = whoPart ? `Todo (${data.total}) — ${whoPart}` : `Todo (${data.total})`
|
|
1024
1046
|
// 标题也顶最左(2026-11-04 用户)。旧代码曾在空看板时多缩 1 格,2026-11-06 空看板
|
|
1025
1047
|
// 已整块不显示(见 refreshTodoPanel),那个分支没意义了。
|
|
1026
|
-
lines.push(clipToWidth(
|
|
1048
|
+
lines.push(clipToWidth(main(title), width))
|
|
1027
1049
|
|
|
1028
1050
|
const lastIdx = data.rows.length - 1
|
|
1029
1051
|
data.rows.forEach((t, i) => {
|
|
@@ -1035,32 +1057,33 @@ export function renderPanelLines(
|
|
|
1035
1057
|
// 进行中的行带一个小动画(其余行不加,避免满屏都在跳)
|
|
1036
1058
|
// 单字符 spinner 紧跟 [进行中]:靠前放才能一眼看到(放行尾会被长描述挤到前面去看不见)。
|
|
1037
1059
|
// 2026-11-06 用户要求:「放在 [进行中] 后面」。
|
|
1038
|
-
const anim = t.state === '进行中' && animPhase >= 0 ? ' ' +
|
|
1060
|
+
const anim = t.state === '进行中' && animPhase >= 0 ? ' ' + main(todoAnimFrame(animPhase)) : ''
|
|
1039
1061
|
// 复选框 `[ ]`(2026-10-16 用户要求):面板是 todo.md 的只读视图,
|
|
1040
1062
|
// 就该长成 todo 原貌 —— 摆成与文件里 `- [ ]` 同形,读到就认得出对应哪一行。
|
|
1041
|
-
const box =
|
|
1042
|
-
//
|
|
1043
|
-
|
|
1044
|
-
const
|
|
1063
|
+
const box = main('[ ]')
|
|
1064
|
+
// 状态标签、id、描述全跟主色(2026-11-06 定:靠符号和文字区分状态,不靠颜色;
|
|
1065
|
+
// 2026-11-07 再定:描述也不单独降灰)。
|
|
1066
|
+
const head = `${main(branch)} ${box} ${main(`[${t.state}]`)}${anim} ${main(t.id)}`
|
|
1067
|
+
const desc = t.desc ? ' ' + main(`— ${t.desc}`) : ''
|
|
1045
1068
|
lines.push(clipToWidth(head + desc, width))
|
|
1046
1069
|
if (t.kids && t.kids.length) {
|
|
1047
1070
|
// 子项:标准 md 嵌套列表 → 画成树
|
|
1048
1071
|
// ├─ [ ] 子项一
|
|
1049
1072
|
// └─ [x] 子项二
|
|
1050
1073
|
// 树线前缀 = 父任务的 │(非末条)或空格(末条),与父任务分支对齐。
|
|
1051
|
-
//
|
|
1052
|
-
const stem = isLast ? ' ' :
|
|
1074
|
+
// 2026-11-07 用户要求全面板同色:勾过的子项也不再压暗,完成与否靠 [x] 区分。
|
|
1075
|
+
const stem = isLast ? ' ' : main('│ ')
|
|
1053
1076
|
const n = t.kids.length
|
|
1054
1077
|
t.kids.forEach((k, ki) => {
|
|
1055
1078
|
const kb = ki === n - 1 ? '└─' : '├─'
|
|
1056
|
-
const kbox =
|
|
1057
|
-
const ktext =
|
|
1058
|
-
lines.push(clipToWidth(`${stem} ${
|
|
1079
|
+
const kbox = main(k.done ? '[x]' : '[ ]')
|
|
1080
|
+
const ktext = main(k.text)
|
|
1081
|
+
lines.push(clipToWidth(`${stem} ${main(kb)} ${kbox} ${ktext}`, width))
|
|
1059
1082
|
})
|
|
1060
1083
|
}
|
|
1061
1084
|
})
|
|
1062
1085
|
|
|
1063
|
-
if (data.hidden > 0) lines.push(clipToWidth(
|
|
1086
|
+
if (data.hidden > 0) lines.push(clipToWidth(main(` +${data.hidden} more`), width))
|
|
1064
1087
|
return lines
|
|
1065
1088
|
}
|
|
1066
1089
|
/** 隐藏面板。**必须主动 requestRender** —— 否则数据清了、屏幕还留着旧画面。
|
|
@@ -1099,6 +1122,11 @@ async function refreshTodoPanel(ui: any, cwd: string): Promise<void> {
|
|
|
1099
1122
|
(tui: any, theme: any) => {
|
|
1100
1123
|
tuiRef = tui
|
|
1101
1124
|
lastTui = tui // 供 hidePanel 在"无动画"时也能请求重绘
|
|
1125
|
+
// 主色要按终端的色深选编码(24 位色 / 256 色)—— getColorMode() 是 pi 主题自己
|
|
1126
|
+
// 算好的那一个;取不到就当支持 24 位色(真拿不准时宁可亮一点也别一格都不出)。
|
|
1127
|
+
const colorMode = (() => {
|
|
1128
|
+
try { return String(theme?.getColorMode?.() || 'truecolor') } catch { return 'truecolor' }
|
|
1129
|
+
})()
|
|
1102
1130
|
return {
|
|
1103
1131
|
render: (width: number) =>
|
|
1104
1132
|
renderPanelLines(
|
|
@@ -1109,6 +1137,7 @@ async function refreshTodoPanel(ui: any, cwd: string): Promise<void> {
|
|
|
1109
1137
|
animPhase,
|
|
1110
1138
|
sessionNickname,
|
|
1111
1139
|
effortLevel,
|
|
1140
|
+
colorMode,
|
|
1112
1141
|
),
|
|
1113
1142
|
invalidate: () => {},
|
|
1114
1143
|
}
|