dsh-working-activity 0.2.6 → 0.3.0

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/src/phrases.ts CHANGED
@@ -1,180 +1,306 @@
1
- /**
2
- * Copy pools for the working-activity status line: short, colloquial, playful
3
- * Chinese fragments with deadpan English one-liners mixed in, matching the
4
- * pi-working-activity tone. Everything here is pure data + pure pickers.
5
- * @module @deepseek-ai/dsh-working-activity/phrases
6
- */
7
-
8
- /** A pool of copy fragments. */
9
- export type PhrasePool = readonly string[]
10
-
11
- /** Pick one random entry; repeated draws avoid the previous entry when possible. */
12
- export function pickPhrase(entries: PhrasePool, previous?: string): string {
13
- if (entries.length === 0) throw new Error('pickPhrase() requires a non-empty pool')
14
- if (entries.length === 1) return entries[0] as string
15
- let next = entries[Math.floor(Math.random() * entries.length)] as string
16
- let guard = 0
17
- while (next === previous && guard++ < 8) {
18
- next = entries[Math.floor(Math.random() * entries.length)] as string
19
- }
20
- return next
21
- }
22
-
23
- /** Thinking phrases while the model works without a tool. */
24
- export const THINKING_PHRASES: readonly string[] = [
25
- '嗯…让我捋捋', '盘一下盘一下', '大脑转起来了', '思考.gif', '给我一秒', '脑子在冒烟',
26
- '想呢想呢', '别催别催', '啾,让我想想', '让我琢磨下', '嗯…等一下哦', '正在盘逻辑',
27
- '小脑瓜动一下', '嗯?哦…', '让我理理', '翻翻脑子', '回想中', '等一下下', '让我嗅嗅',
28
- '脑内风暴中', '嗯…让我品品', '滴滴滴思考中', '稍等,在想', '盘明白了么', '挠头…',
29
- '让子弹飞一会', '让我脑补一下', '加载中', '你说 我在听', '噢…是这样', '让我嚼一嚼',
30
- '嗯…有点意思', '搓搓手想想', '等下,在想', '让我康康', '想好了告诉你', '脑子转圈圈',
31
- '嗯…让我反应下', '等下下嘛', '思路加载中', '琢磨中', '嗯…让我拆一下', '盘,都可以盘',
32
- '让我嗅探一下', '脑内跑火车', '嗯…让我缓一下', '滴滴,想呢', '思索.jpg', '嗯…有点东西',
33
- '让我品', '小跑一下思路', '等下,有画面了', '让我咀嚼', '嗯…发会儿呆', '思考泡泡',
34
- '脑电波传输中', '嗯…转转', '等下,盘好了', '让我回味', '滴滴滴', '思考的鱼',
35
- '嗯…让我摸一下', '脑子在煮咖啡', '等下,我打个腹稿', '嗯…重启一下', '让我挠墙',
36
- '嗯,来了来了', '脑子冒泡泡', '嗯…有点烫', '思考猫猫', '让我咕噜一下', '嗯…盘它',
37
- '等下,我闪个思路', '脑子在蹦迪', '嗯…', '让我想想', '盘一下', '', 'lol', 'hm', 'oh',
38
- 'ok', 'um', 'heh', 'uh', 'nah', 'mm', 'wow', 'nice', 'rgrg', 'okk', 'hhh', 'emm', 'emmm',
39
- 'CPU烧了', '让我打个log看看', '先跑一下试试', '定位一下', '排查一下', '看看日志',
40
- 'loading 99%', '让我捋一下逻辑',
41
- ]
42
-
43
- /** Tiered phrases when thinking runs long (elapsed >= threshold). */
44
- export const THINKING_TIERS: readonly {
45
- /** Minimum thinking ms for this tier. */
46
- readonly atMs: number
47
- readonly pool: readonly string[]
48
- }[] = [
49
- { atMs: 30_000, pool: ['嗯,让我细想想', '30秒了,还在盘', '等下,快好了', '别急,就快出结果了', '让我再捋一捋', '嗯…思路没断', '30秒,快了', '等等,有眉目了', '有点久…', '转圈圈…', '马上马上', '快了快了', '别走,就快好了', '在盘了呢', '还在定位', '快复现了'] },
50
- { atMs: 60_000, pool: ['1分钟,还在想', '这题有点东西', '让我再钻研下', '嗯…问题不简单', '1分钟,别走开', '盘得有点深', '脑细胞在燃烧', '等等,快盘清了', '还在努力…', '这个有点绕…', '烧脑中…', '别走,快了', '一分钟了,再等等', '这题值得盘', '还在排查', '这个有点复杂'] },
51
- { atMs: 300_000, pool: ['5分钟,大工程', '这把我得认真', '确实有点绕', '等等,我在修仙', '快好了,真的', '盘了一大圈', '别慌,在收尾', '给我一首歌的时间', '还没放弃…', '这题真的硬…', '我给跪了…', '憋大招中', '5分钟了,等值了', '快了,真快了', '这个需求很简单', '能跑就别动'] },
52
- ]
53
-
54
- /** Phrases shown while waiting for the first streamed token. */
55
- export const WAITING_PHRASES: readonly string[] = [
56
- '呼叫模型…', '模型在路上了', '等它开口…', '稍等,它有点慢', '模型加载中', '嗯…等它一下',
57
- '它在组织语言', '等等我嘛', '模型醒了么', '等它伸懒腰', '它打了个哈欠', '模型:来了来了',
58
- '等它出字', '别急,在等', '它磨蹭呢', '模型说等一下', '等它滴一声', '模型在咕噜',
59
- '等它反应过来', '嗯…等它', '模型在喝水', '它说再等一下', '等它喘口气', '模型:快了快了',
60
- '别急别急', '来了来了', '等它跑完', '还在排队', '马上出结果', '等它热身', '模型在酝酿',
61
- '它翻了个身', '模型:马上', '等它开机', '它卡了一下', '模型在冥想', '等它眨个眼',
62
- '它说稍等', '模型在查资料', '等它缓一缓', '模型在数数', '等它回神', '它终于动了',
63
- ]
64
-
65
- /** Tool-name patterns mapped to playful action verbs. */
66
- export const ACTION_MAP: readonly {
67
- readonly test: RegExp
68
- readonly actions: readonly string[]
69
- }[] = [
70
- { test: /^(read|read_file|cat)$/i, actions: ['翻翻文档', '让我康康', '读一下', '看一眼', '翻阅中', '读读看', '翻翻', '看看', '瞄一眼', '康康', '翻一页'] },
71
- { test: /^(write|write_file|create_file)$/i, actions: ['写写写', '下笔中', '码字呢', '写一段', '记录一下', '写一下', '记下来', '落笔', '开写', '存个文件'] },
72
- { test: /^(edit|edit_file|str_replace|apply_patch|search_replace)$/i, actions: ['改改', '修修补补', '润色一下', '编辑中', '调整调整', '改一改', '修一下', '改两行', '调一下', '补一刀'] },
73
- { test: /^(bash|shell|run|exec|powershell|cmd)$/i, actions: ['跑个命令', 'bash一下', '敲敲指令', '命令行走起', '执行一下', '敲回车', '跑一下', '敲个命令', '跑命令', '使唤终端'] },
74
- { test: /^(grep|rg|search|search_in_files)$/i, actions: ['搜搜东西', 'grep 一下', '找找匹配', '关键词走你', '过滤中', '搜搜看', '搜一下', '找找', '扫一眼', '挖一挖'] },
75
- { test: /^(find|glob)$/i, actions: ['找找文件', '找一下', '寻宝中', '找啊找', '文件在哪', '查找中', '搜搜目录'] },
76
- { test: /^(ls|list_dir|list)$/i, actions: ['列个清单', '看看目录', 'ls 看一眼', '瞄一下文件', '目录走起', '列出来', '列一下', '瞟一眼', '翻翻'] },
77
- { test: /^(web_search|search_web|brave|tavily|exa)$/i, actions: ['网上搜搜', '搜一下', '网络冲浪', '查找资料', '上网瞄瞄', '上网搜搜', '查查', '搜一圈', '打听一下'] },
78
- { test: /^(web_fetch|fetch|fetch_content)$/i, actions: ['抓个页面', '拉取一下', 'fetch ', '扒拉网页', '取点内容', '抓取资料', '扒一下', '打开看看'] },
79
- { test: /^(mcp)/i, actions: ['mcp 连一下', '调个服务', '接个工具', 'mcp 走你', '调接口', '连一下', '喊外援', '接一下'] },
80
- { test: /^(subagent|agent|task)$/i, actions: ['派个小弟', '小助手出动', '支个 agent', '让小弟跑腿', '代理干活', '子任务起飞', '分个任务', '交给小弟', '派出去'] },
81
- { test: /^(todo|manage_todo_list)$/i, actions: ['列个待办', '写个清单', 'todo 安排', '记一下', '待办走起', '清单一下', '记个待办', '打个勾'] },
82
- { test: /^(browser|chrome|playwright)/i, actions: ['开个浏览器', '浏览器跑腿', '网页操作', '浏览器干活', '开网页', '点点页面'] },
83
- { test: /^(git|gh|github)/i, actions: ['git 操作', '提交一下', '版本控制', 'git 走你', '提交代码', '管个仓库', 'git 一下'] },
84
- { test: /^(ask_user_question|ask)$/i, actions: ['提问中', '问一个问题', 'ask 一下', '请教一下', '问问看', '问你个事', '确认一下'] },
85
- { test: /^(goal_complete|goal_blocked)$/i, actions: ['定个目标', '设定目标', 'goal 设置', '目标走起', '规划一下', '更新进度'] },
86
- { test: /^(todo_write)$/i, actions: ['记个待办', '划个清单', '打个勾'] },
87
- ]
88
-
89
- /** Fallback verbs for unknown tools. */
90
- export const FALLBACK_ACTIONS: readonly string[] = ['干活', '调用', '整一下', '搞一下', '动动手', '备选方案', '换条路']
91
-
92
- /** Tool failure phrases, replacing a bare ✗. */
93
- export const FAIL_PHRASES: readonly string[] = [
94
- '翻车了', '哎呀', '掉了', '没跑通', '摔了一跤', '再来一次', '这不对', '出岔子了', '不灵了',
95
- '坏消息', '权限不对?', '连不上?', '404了', '不太对', '有点问题', '再看看', '没接住', '漏了',
96
- '我本地能跑啊', '昨天还能跑', '重启试试', '清一下缓存', '删了重装', '你刷新一下', '环境问题',
97
- '少了个分号', '拼错了', '没保存', '又不是不能用', '绷不住了', '难绷', '卒', '裂开',
98
- '血压上来了', '缓存害我', '再给我一次机会', '这波大意了', '手滑', '回滚重来', '换个姿势',
99
- ]
100
-
101
- /** Turn-completion phrases. */
102
- export const DONE_PHRASES: readonly string[] = [
103
- '交差!', '搞定,下一个', '好了,收工', '完成啦', '交作业', '结束,完美', '完工咯', '搞定啦',
104
- '任务完成', '好了,歇会儿', '搞定', '收工', '妥了', '完事', '交差', '齐活', '拿下', '收工!',
105
- '搞定收工', '收!', '完事!', '下一题', '能跑!', '没报错', '过了', '上线!', '稳了', '6',
106
- '完工!', '完美收场', '这波不亏', '一次过', '收工摸鱼', '漂亮', '全绿', '干净利落',
107
- '手到擒来', '水到渠成', '下班!', '歇口气', '交接完成', '工单关闭', '收尾完毕',
108
- ]
109
-
110
- /** Night-owl phrases mixed in between 00:00 and 06:00 local time. */
111
- export const NIGHT_PHRASES: readonly string[] = [
112
- '修仙中…', '深夜冒泡', '你也是夜猫子呀', '月亮不睡我不睡', '夜里脑子慢,谅解', '晚安?还早呢',
113
- '深夜盘东西', '熬夜冠军上线', '困了,但能行', '过了零点照样肝', '夜猫子出没', '深夜档营业',
114
- '星星都睡了', '凌晨还在盘', '深夜上线', '凌晨部署', '通宵了',
115
- ]
116
-
117
- /** Common git tool names / bash commands containing `git `. */
118
- export const GIT_TOOL_RE = /^(?:git|git_diff|git_commit|git_push|git_pull|git_checkout|git_branch|git_merge|git_rebase|github|gh)$/i
119
-
120
- /** Detect the 00:00–06:00 night window (local time). */
121
- export function isNight(hour: number): boolean {
122
- return hour >= 0 && hour < 6
123
- }
124
-
125
- /**
126
- * Pick a thinking phrase appropriate for the elapsed thinking time.
127
- * @param elapsedMs - Milliseconds spent thinking in the current phase.
128
- * @param previous - Previously shown phrase, to avoid repeats.
129
- * @param night - Mix night-owl copy into the pool.
130
- */
131
- export function thinkingPhrase(elapsedMs: number, previous?: string, night = false): string {
132
- let pool: readonly string[] = THINKING_PHRASES
133
- for (const tier of THINKING_TIERS) {
134
- if (elapsedMs >= tier.atMs) {
135
- pool = tier.pool
136
- break
137
- }
138
- }
139
- if (night && pool === THINKING_PHRASES) {
140
- return pickPhrase([...pool, ...NIGHT_PHRASES], previous)
141
- }
142
- return pickPhrase(pool, previous)
143
- }
144
-
145
- /**
146
- * Map a tool name to a playful action verb.
147
- * @param toolName - Registry tool name (unqualified).
148
- * @param custom - Exact-name custom action pools, matched case-insensitively.
149
- */
150
- export function actionFor(toolName: string, custom?: Readonly<Record<string, readonly string[]>>): string {
151
- const normalized = toolName.trim().toLowerCase()
152
- const customPool = custom?.[normalized]
153
- if (customPool !== undefined && customPool.length > 0) return pickPhrase(customPool)
154
- for (const { test, actions } of ACTION_MAP) {
155
- if (test.test(normalized)) return pickPhrase(actions)
156
- }
157
- return pickPhrase(FALLBACK_ACTIONS)
158
- }
159
-
160
- /** Whether a tool is a git operation (name match, or a shell command containing `git `). */
161
- export function isGitTool(toolName: string, args?: Readonly<Record<string, unknown>>): boolean {
162
- if (GIT_TOOL_RE.test(toolName.trim())) return true
163
- if (/^(?:bash|shell|cmd|powershell|pwsh)$/i.test(toolName.trim())) {
164
- const command = args?.command ?? args?.cmdline
165
- return typeof command === 'string' && /\bgit\s+/.test(command)
166
- }
167
- return false
168
- }
169
-
170
- /** Format milliseconds as a compact human duration (`1m23s`). */
171
- export function fmtDuration(ms: number): string {
172
- if (ms < 1000) return '0s'
173
- const total = Math.floor(ms / 1000)
174
- if (total < 60) return `${total}s`
175
- const minutes = Math.floor(total / 60)
176
- const seconds = total % 60
177
- if (minutes < 60) return `${minutes}m${seconds}s`
178
- const hours = Math.floor(minutes / 60)
179
- return `${hours}h${minutes % 60}m`
180
- }
1
+ /**
2
+ * Copy pools for the working-activity status line: short, colloquial, playful
3
+ * fragments with a "live person" vibe, in both Chinese and English. The zh
4
+ * pools are the original copy; the en pools mirror the same tone — deadpan,
5
+ * meme-y, occasionally unhinged. Everything here is pure data + pure pickers;
6
+ * the active language is resolved per pick via `langNow()`.
7
+ * @module @deepseek-ai/dsh-working-activity/phrases
8
+ */
9
+
10
+ import { langNow } from './lang.js'
11
+
12
+ /** A pool of copy fragments. */
13
+ export type PhrasePool = readonly string[]
14
+
15
+ /** Pick one random entry; repeated draws avoid the previous entry when possible. */
16
+ export function pickPhrase(entries: PhrasePool, previous?: string): string {
17
+ if (entries.length === 0) throw new Error('pickPhrase() requires a non-empty pool')
18
+ if (entries.length === 1) return entries[0] as string
19
+ let next = entries[Math.floor(Math.random() * entries.length)] as string
20
+ let guard = 0
21
+ while (next === previous && guard++ < 8) {
22
+ next = entries[Math.floor(Math.random() * entries.length)] as string
23
+ }
24
+ return next
25
+ }
26
+
27
+ // ── zh pools (original copy) ─────────────────────────────────────────────
28
+
29
+ /** Thinking phrases while the model works without a tool. */
30
+ export const THINKING_PHRASES: readonly string[] = [
31
+ '嗯…让我捋捋', '盘一下盘一下', '大脑转起来了', '思考.gif', '给我一秒', '脑子在冒烟',
32
+ '想呢想呢', '别催别催', '啾,让我想想', '让我琢磨下', '嗯…等一下哦', '正在盘逻辑',
33
+ '小脑瓜动一下', '嗯?哦…', '让我理理', '翻翻脑子', '回想中', '等一下下', '让我嗅嗅',
34
+ '脑内风暴中', '嗯…让我品品', '滴滴滴思考中', '稍等,在想', '盘明白了么', '挠头…',
35
+ '让子弹飞一会', '让我脑补一下', '加载中', '你说 我在听', '噢…是这样', '让我嚼一嚼',
36
+ '嗯…有点意思', '搓搓手想想', '等下,在想', '让我康康', '想好了告诉你', '脑子转圈圈',
37
+ '嗯…让我反应下', '等下下嘛', '思路加载中', '琢磨中', '嗯…让我拆一下', '盘,都可以盘',
38
+ '让我嗅探一下', '脑内跑火车', '嗯…让我缓一下', '滴滴,想呢', '思索.jpg', '嗯…有点东西',
39
+ '让我品', '小跑一下思路', '等下,有画面了', '让我咀嚼', '嗯…发会儿呆', '思考泡泡',
40
+ '脑电波传输中', '嗯…转转', '等下,盘好了', '让我回味', '滴滴滴', '思考的鱼',
41
+ '嗯…让我摸一下', '脑子在煮咖啡', '等下,我打个腹稿', '嗯…重启一下', '让我挠墙',
42
+ '嗯,来了来了', '脑子冒泡泡', '嗯…有点烫', '思考猫猫', '让我咕噜一下', '嗯…盘它',
43
+ '等下,我闪个思路', '脑子在蹦迪', '嗯…', '让我想想', '盘一下', '啾', 'lol', 'hm', 'oh',
44
+ 'ok', 'um', 'heh', 'uh', 'nah', 'mm', 'wow', 'nice', 'rgrg', 'okk', 'hhh', 'emm', 'emmm',
45
+ 'CPU烧了', '让我打个log看看', '先跑一下试试', '定位一下', '排查一下', '看看日志',
46
+ 'loading 99%', '让我捋一下逻辑',
47
+ ]
48
+
49
+ /** Tiered phrases when thinking runs long (elapsed >= threshold). */
50
+ export const THINKING_TIERS: readonly {
51
+ /** Minimum thinking ms for this tier. */
52
+ readonly atMs: number
53
+ readonly pool: readonly string[]
54
+ }[] = [
55
+ { atMs: 30_000, pool: ['嗯,让我细想想', '30秒了,还在盘', '等下,快好了', '别急,就快出结果了', '让我再捋一捋', '嗯…思路没断', '30秒,快了', '等等,有眉目了', '有点久…', '转圈圈…', '马上马上', '快了快了', '别走,就快好了', '在盘了呢', '还在定位', '快复现了'] },
56
+ { atMs: 60_000, pool: ['1分钟,还在想', '这题有点东西', '让我再钻研下', '嗯…问题不简单', '1分钟,别走开', '盘得有点深', '脑细胞在燃烧', '等等,快盘清了', '还在努力…', '这个有点绕…', '烧脑中…', '别走,快了', '一分钟了,再等等', '这题值得盘', '还在排查', '这个有点复杂'] },
57
+ { atMs: 300_000, pool: ['5分钟,大工程', '这把我得认真', '确实有点绕', '等等,我在修仙', '快好了,真的', '盘了一大圈', '别慌,在收尾', '给我一首歌的时间', '还没放弃…', '这题真的硬…', '我给跪了…', '憋大招中', '5分钟了,等值了', '快了,真快了', '这个需求很简单', '能跑就别动'] },
58
+ ]
59
+
60
+ /** Phrases shown while waiting for the first streamed token. */
61
+ export const WAITING_PHRASES: readonly string[] = [
62
+ '呼叫模型…', '模型在路上了', '等它开口…', '稍等,它有点慢', '模型加载中', '嗯…等它一下',
63
+ '它在组织语言', '等等我嘛', '模型醒了么', '等它伸懒腰', '它打了个哈欠', '模型:来了来了',
64
+ '等它出字', '别急,在等', '它磨蹭呢', '模型说等一下', '等它滴一声', '模型在咕噜',
65
+ '等它反应过来', '嗯…等它', '模型在喝水', '它说再等一下', '等它喘口气', '模型:快了快了',
66
+ '别急别急', '来了来了', '等它跑完', '还在排队', '马上出结果', '等它热身', '模型在酝酿',
67
+ '它翻了个身', '模型:马上', '等它开机', '它卡了一下', '模型在冥想', '等它眨个眼',
68
+ '它说稍等', '模型在查资料', '等它缓一缓', '模型在数数', '等它回神', '它终于动了',
69
+ ]
70
+
71
+ /** Tool-name patterns mapped to playful action verbs. */
72
+ export const ACTION_MAP: readonly {
73
+ readonly test: RegExp
74
+ readonly actions: readonly string[]
75
+ }[] = [
76
+ { test: /^(read|read_file|cat)$/i, actions: ['翻翻文档', '让我康康', '读一下', '看一眼', '翻阅中', '读读看', '翻翻', '看看', '瞄一眼', '康康', '翻一页'] },
77
+ { test: /^(write|write_file|create_file)$/i, actions: ['写写写', '下笔中', '码字呢', '写一段', '记录一下', '写一下', '记下来', '落笔', '开写', '存个文件'] },
78
+ { test: /^(edit|edit_file|str_replace|apply_patch|search_replace)$/i, actions: ['改改', '修修补补', '润色一下', '编辑中', '调整调整', '改一改', '修一下', '改两行', '调一下', '补一刀'] },
79
+ { test: /^(bash|shell|run|exec|powershell|cmd)$/i, actions: ['跑个命令', 'bash一下', '敲敲指令', '命令行走起', '执行一下', '敲回车', '跑一下', '敲个命令', '跑命令', '使唤终端'] },
80
+ { test: /^(grep|rg|search|search_in_files)$/i, actions: ['搜搜东西', 'grep 一下', '找找匹配', '关键词走你', '过滤中', '搜搜看', '搜一下', '找找', '扫一眼', '挖一挖'] },
81
+ { test: /^(find|glob)$/i, actions: ['找找文件', '找一下', '寻宝中', '找啊找', '文件在哪', '查找中', '搜搜目录'] },
82
+ { test: /^(ls|list_dir|list)$/i, actions: ['列个清单', '看看目录', 'ls 看一眼', '瞄一下文件', '目录走起', '列出来', '列一下', '瞟一眼', '翻翻'] },
83
+ { test: /^(web_search|search_web|brave|tavily|exa)$/i, actions: ['网上搜搜', '搜一下', '网络冲浪', '查找资料', '上网瞄瞄', '上网搜搜', '查查', '搜一圈', '打听一下'] },
84
+ { test: /^(web_fetch|fetch|fetch_content)$/i, actions: ['抓个页面', '拉取一下', 'fetch ', '扒拉网页', '取点内容', '抓取资料', '扒一下', '打开看看'] },
85
+ { test: /^(mcp)/i, actions: ['mcp 连一下', '调个服务', '接个工具', 'mcp 走你', '调接口', '连一下', '喊外援', '接一下'] },
86
+ { test: /^(subagent|agent|task)$/i, actions: ['派个小弟', '小助手出动', '支个 agent', '让小弟跑腿', '代理干活', '子任务起飞', '分个任务', '交给小弟', '派出去'] },
87
+ { test: /^(todo|manage_todo_list)$/i, actions: ['列个待办', '写个清单', 'todo 安排', '记一下', '待办走起', '清单一下', '记个待办', '打个勾'] },
88
+ { test: /^(browser|chrome|playwright)/i, actions: ['开个浏览器', '浏览器跑腿', '网页操作', '浏览器干活', '开网页', '点点页面'] },
89
+ { test: /^(git|gh|github)/i, actions: ['git 操作', '提交一下', '版本控制', 'git 走你', '提交代码', '管个仓库', 'git 一下'] },
90
+ { test: /^(ask_user_question|ask)$/i, actions: ['提问中', '问一个问题', 'ask 一下', '请教一下', '问问看', '问你个事', '确认一下'] },
91
+ { test: /^(goal_complete|goal_blocked)$/i, actions: ['定个目标', '设定目标', 'goal 设置', '目标走起', '规划一下', '更新进度'] },
92
+ { test: /^(todo_write)$/i, actions: ['记个待办', '划个清单', '打个勾'] },
93
+ ]
94
+
95
+ /** Fallback verbs for unknown tools. */
96
+ export const FALLBACK_ACTIONS: readonly string[] = ['干活', '调用', '整一下', '搞一下', '动动手', '备选方案', '换条路']
97
+
98
+ /** Tool failure phrases, replacing a bare ✗. */
99
+ export const FAIL_PHRASES: readonly string[] = [
100
+ '翻车了', '哎呀', '掉了', '没跑通', '摔了一跤', '再来一次', '这不对', '出岔子了', '不灵了',
101
+ '坏消息', '权限不对?', '连不上?', '404了', '不太对', '有点问题', '再看看', '没接住', '漏了',
102
+ '我本地能跑啊', '昨天还能跑', '重启试试', '清一下缓存', '删了重装', '你刷新一下', '环境问题',
103
+ '少了个分号', '拼错了', '没保存', '又不是不能用', '绷不住了', '难绷', '', '裂开',
104
+ '血压上来了', '缓存害我', '再给我一次机会', '这波大意了', '手滑', '回滚重来', '换个姿势',
105
+ ]
106
+
107
+ /** Turn-completion phrases. */
108
+ export const DONE_PHRASES: readonly string[] = [
109
+ '交差!', '搞定,下一个', '好了,收工', '完成啦', '交作业', '结束,完美', '完工咯', '搞定啦',
110
+ '任务完成', '好了,歇会儿', '搞定', '收工', '妥了', '完事', '交差', '齐活', '拿下', '收工!',
111
+ '搞定收工', '收!', '完事!', '下一题', '能跑!', '没报错', '过了', '上线!', '稳了', '6',
112
+ '完工!', '完美收场', '这波不亏', '一次过', '收工摸鱼', '漂亮', '全绿', '干净利落',
113
+ '手到擒来', '水到渠成', '下班!', '歇口气', '交接完成', '工单关闭', '收尾完毕',
114
+ ]
115
+
116
+ /** Night-owl phrases mixed in between 00:00 and 06:00 local time. */
117
+ export const NIGHT_PHRASES: readonly string[] = [
118
+ '修仙中…', '深夜冒泡', '你也是夜猫子呀', '月亮不睡我不睡', '夜里脑子慢,谅解', '晚安?还早呢',
119
+ '深夜盘东西', '熬夜冠军上线', '困了,但能行', '过了零点照样肝', '夜猫子出没', '深夜档营业',
120
+ '星星都睡了', '凌晨还在盘', '深夜上线', '凌晨部署', '通宵了',
121
+ ]
122
+
123
+ // ── en pools (same vibe, different language) ─────────────────────────────
124
+
125
+ /** English thinking phrases — casual, meme-y, alive. */
126
+ export const EN_THINKING_PHRASES: readonly string[] = [
127
+ 'Thinking…', 'Pondering…', 'Mulling it over', 'Brain.exe running', 'Loading thoughts…',
128
+ 'Deep in thought', 'Hmm…', 'Cogitating', 'Reasoning intensifies', 'On it', 'Working it out',
129
+ 'Connecting the dots', 'Crunching ideas', 'Chewing on it', 'Neurons firing',
130
+ 'Reticulating splines', 'Thinking cap on', 'Give me a sec', 'Halfway there',
131
+ 'Still thinking', 'Turning the crank', 'Ideas brewing', 'Thoughts loading…',
132
+ 'Calculating life choices', 'Let me cook', 'Cooking…', 'Brain cells: engaged',
133
+ 'Thinking thoughts', 'Mmm…', 'Processing…', 'Almost', 'Just vibing with the problem',
134
+ 'Mystery math happening', 'Gathering thoughts', 'In the zone', 'Distracted by a pigeon',
135
+ 'Have you tried thinking harder?', 'Summoning wisdom', 'rrrr', 'um', 'ok ok', 'wait…',
136
+ ]
137
+
138
+ /** English tiered phrases when thinking runs long. */
139
+ export const EN_THINKING_TIERS: readonly {
140
+ readonly atMs: number
141
+ readonly pool: readonly string[]
142
+ }[] = [
143
+ { atMs: 30_000, pool: ['30s in, still thinking', "This one's a thinker", 'Deeper than it looks', 'Getting warmer…', 'Still cooking', 'Brain on overtime', 'Not done yet', 'Almost there…', 'The gears are turning', 'One more sec', 'Loading 99%… again'] },
144
+ { atMs: 60_000, pool: ['1m in, still going', "This is a tough one", 'Full brain power', '1m, stay with me', 'Still grinding', 'Brain at full tilt', 'Almost…', 'Taking the scenic route', "It's a marathon, not a sprint", 'The plot thickens'] },
145
+ { atMs: 300_000, pool: ['5m in, big brain energy', 'This is a marathon', 'Seriously deep now', 'Meditating on it', 'Slow and steady', 'Getting there', 'Worth the wait', 'One song later…', '5 minutes of pure thought', "I've seen things", 'Ascending to another plane of thought'] },
146
+ ]
147
+
148
+ /** English phrases while waiting for the first token. */
149
+ export const EN_WAITING_PHRASES: readonly string[] = [
150
+ 'Pinging the model…', 'Model inbound…', 'Waiting on the muse', 'Wake up, model',
151
+ "It's warming up", 'Model: almost there', 'Holding for a token…', 'Brewing a response…',
152
+ 'Model is stretching', 'Just a sec', 'One moment, please', 'Loading…', 'Connecting…',
153
+ 'Model said "be right back"', "It's yawning", 'First token incoming…', 'Hmm, still waiting',
154
+ 'Model is putting on its glasses', 'Tick tock…', 'Summoning tokens…', 'Patiently waiting',
155
+ 'Model is thinking of a hello', 'Booting brain…', 'Is it plugged in?', 'Gently poking the model',
156
+ 'Waiting for the magic words…', 'A wild token appears… soon', 'Reticulating the request',
157
+ ]
158
+
159
+ /** English tool-name → action verbs. */
160
+ export const EN_ACTION_MAP: readonly {
161
+ readonly test: RegExp
162
+ readonly actions: readonly string[]
163
+ }[] = [
164
+ { test: /^(read|read_file|cat)$/i, actions: ['Reading', 'Peeking at', 'Snooping through', 'Skimming', 'Checking out', 'Eyes on', 'Giving it a read'] },
165
+ { test: /^(write|write_file|create_file)$/i, actions: ['Writing', 'Typing it out', 'Crafting', 'Saving progress', 'Pen to paper (virtually)', 'Putting words down'] },
166
+ { test: /^(edit|edit_file|str_replace|apply_patch|search_replace)$/i, actions: ['Editing', 'Patching', 'Tweaking', 'Polishing', 'Fixing a typo (probably)', 'Adjusting', 'Giving it a touch-up'] },
167
+ { test: /^(bash|shell|run|exec|powershell|cmd)$/i, actions: ['Running', 'Executing', 'Shelling out', 'Firing a command', 'Terminal time', 'Doing terminal things', 'Making the computer do stuff'] },
168
+ { test: /^(grep|rg|search|search_in_files)$/i, actions: ['Searching', 'Grepping', 'Hunting for matches', 'Looking for a needle', 'Sifting through', 'Diving into the haystack'] },
169
+ { test: /^(find|glob)$/i, actions: ['Finding files', 'Looking for files', 'File hunt', 'Hunting down a path'] },
170
+ { test: /^(ls|list_dir|list)$/i, actions: ['Listing', 'Peeking at the dir', "What's in here?", 'Taking inventory'] },
171
+ { test: /^(web_search|search_web|brave|tavily|exa)$/i, actions: ['Searching the web', 'Googling', 'Researching', 'Web diving (the safe kind)', 'Looking it up'] },
172
+ { test: /^(web_fetch|fetch|fetch_content)$/i, actions: ['Fetching a page', 'Grabbing content', 'Pulling the page', 'Downloading knowledge'] },
173
+ { test: /^(mcp)/i, actions: ['Calling MCP', 'Hitting a service', 'MCP time', 'Talking to a server'] },
174
+ { test: /^(subagent|agent|task)$/i, actions: ['Delegating', 'Sending a subagent', 'Calling a helper', 'Outsourcing', 'Drafting a minion'] },
175
+ { test: /^(todo|manage_todo_list)$/i, actions: ['Updating todos', 'Checking the list', 'Todo time', 'Adding a checkbox'] },
176
+ { test: /^(browser|chrome|playwright)/i, actions: ['Driving the browser', 'Clicking around', 'Browsing', 'Puppeteering a browser'] },
177
+ { test: /^(git|gh|github)/i, actions: ['Git-ing', 'Committing', 'Version control dance', 'Git things', 'Saving the timeline'] },
178
+ { test: /^(ask_user_question|ask)$/i, actions: ['Asking you', 'Checking with you', 'Quick question', 'Pinging the human'] },
179
+ { test: /^(goal_complete|goal_blocked)$/i, actions: ['Updating goals', 'Tracking progress', 'Checking the objective'] },
180
+ { test: /^(todo_write)$/i, actions: ['Writing todos', 'Making a list', 'Adding a checkbox'] },
181
+ ]
182
+
183
+ /** English fallback verbs for unknown tools. */
184
+ export const EN_FALLBACK_ACTIONS: readonly string[] = [
185
+ 'Working on it', 'Doing a thing', 'Handling it', 'Taking care of it',
186
+ 'Something productive', 'Figuring it out', 'Winging it',
187
+ ]
188
+
189
+ /** English tool failure phrases. */
190
+ export const EN_FAIL_PHRASES: readonly string[] = [
191
+ 'That failed', 'Oops', 'No dice', "Didn't work", 'Broke it (it was already broken)',
192
+ "It's not a bug, it's a feature", '404: success not found', 'Retry?', "Hmm, that's odd",
193
+ 'RIP', 'Sigh…', 'One more time', 'Classic', 'Works on my machine',
194
+ 'Have you tried turning it off and on?', 'Someone unplugged the internet',
195
+ 'Out of ideas, trying again', 'The computer said no', 'Error: user error', 'So close…',
196
+ ]
197
+
198
+ /** English turn-completion phrases. */
199
+ export const EN_DONE_PHRASES: readonly string[] = [
200
+ 'Done!', 'All set', 'Finished', "That's that", 'Done and dusted', 'Mission complete',
201
+ 'Sorted', 'Wrapped up', 'Ship it!', 'Clean run', 'Nice', 'One and done', 'All green',
202
+ 'Knocked out', 'Closed out', 'Task: destroyed', 'EZ', 'GG', 'Another one bites the dust',
203
+ 'Victory lap', 'Boom', 'Tada!', 'Smooth sailing', 'No notes', 'Crushed it',
204
+ ]
205
+
206
+ /** English night-owl phrases. */
207
+ export const EN_NIGHT_PHRASES: readonly string[] = [
208
+ 'Night owl shift', 'Burning midnight oil', 'Past midnight, still going', "The moon's out",
209
+ 'Night grind', 'Up late', 'Almost dawn', '2AM thoughts', 'Red-eye shift',
210
+ 'Who needs sleep anyway?', 'The stars are my witness',
211
+ ]
212
+
213
+ /** Common git tool names / bash commands containing `git `. */
214
+ export const GIT_TOOL_RE = /^(?:git|git_diff|git_commit|git_push|git_pull|git_checkout|git_branch|git_merge|git_rebase|github|gh)$/i
215
+
216
+ /** Detect the 00:00–06:00 night window (local time). */
217
+ export function isNight(hour: number): boolean {
218
+ return hour >= 0 && hour < 6
219
+ }
220
+
221
+ /** The zh or en base thinking pool by the active language. */
222
+ function thinkingPools(): readonly { atMs: number; pool: readonly string[] }[] {
223
+ return langNow() === 'en' ? EN_THINKING_TIERS : THINKING_TIERS
224
+ }
225
+
226
+ /** The zh or en base waiting pool by the active language. */
227
+ export function waitingPool(): readonly string[] {
228
+ return langNow() === 'en' ? EN_WAITING_PHRASES : WAITING_PHRASES
229
+ }
230
+
231
+ /**
232
+ * Pick a thinking phrase appropriate for the elapsed thinking time, in the
233
+ * active language.
234
+ * @param elapsedMs - Milliseconds spent thinking in the current phase.
235
+ * @param previous - Previously shown phrase, to avoid repeats.
236
+ * @param night - Mix night-owl copy into the pool.
237
+ */
238
+ export function thinkingPhrase(elapsedMs: number, previous?: string, night = false): string {
239
+ let pool: readonly string[] = langNow() === 'en' ? EN_THINKING_PHRASES : THINKING_PHRASES
240
+ for (const tier of thinkingPools()) {
241
+ if (elapsedMs >= tier.atMs) {
242
+ pool = tier.pool
243
+ break
244
+ }
245
+ }
246
+ if (night && pool === (langNow() === 'en' ? EN_THINKING_PHRASES : THINKING_PHRASES)) {
247
+ const nightPool = langNow() === 'en' ? EN_NIGHT_PHRASES : NIGHT_PHRASES
248
+ return pickPhrase([...pool, ...nightPool], previous)
249
+ }
250
+ return pickPhrase(pool, previous)
251
+ }
252
+
253
+ /** Pick a waiting phrase in the active language. */
254
+ export function waitingPhrase(previous?: string): string {
255
+ return pickPhrase(waitingPool(), previous)
256
+ }
257
+
258
+ /** Pick a tool-failure phrase in the active language. */
259
+ export function failPhrase(): string {
260
+ return pickPhrase(langNow() === 'en' ? EN_FAIL_PHRASES : FAIL_PHRASES)
261
+ }
262
+
263
+ /** Pick a turn-completion phrase in the active language. */
264
+ export function donePhrase(): string {
265
+ return pickPhrase(langNow() === 'en' ? EN_DONE_PHRASES : DONE_PHRASES)
266
+ }
267
+
268
+ /**
269
+ * Map a tool name to a playful action verb, in the active language.
270
+ * @param toolName - Registry tool name (unqualified).
271
+ * @param custom - Exact-name custom action pools, matched case-insensitively
272
+ * (user-owned copy, used verbatim in any language).
273
+ */
274
+ export function actionFor(toolName: string, custom?: Readonly<Record<string, readonly string[]>>): string {
275
+ const normalized = toolName.trim().toLowerCase()
276
+ const customPool = custom?.[normalized]
277
+ if (customPool !== undefined && customPool.length > 0) return pickPhrase(customPool)
278
+ const map = langNow() === 'en' ? EN_ACTION_MAP : ACTION_MAP
279
+ const fallback = langNow() === 'en' ? EN_FALLBACK_ACTIONS : FALLBACK_ACTIONS
280
+ for (const { test, actions } of map) {
281
+ if (test.test(normalized)) return pickPhrase(actions)
282
+ }
283
+ return pickPhrase(fallback)
284
+ }
285
+
286
+ /** Whether a tool is a git operation (name match, or a shell command containing `git `). */
287
+ export function isGitTool(toolName: string, args?: Readonly<Record<string, unknown>>): boolean {
288
+ if (GIT_TOOL_RE.test(toolName.trim())) return true
289
+ if (/^(?:bash|shell|cmd|powershell|pwsh)$/i.test(toolName.trim())) {
290
+ const command = args?.command ?? args?.cmdline
291
+ return typeof command === 'string' && /\bgit\s+/.test(command)
292
+ }
293
+ return false
294
+ }
295
+
296
+ /** Format milliseconds as a compact human duration (`1m23s`). */
297
+ export function fmtDuration(ms: number): string {
298
+ if (ms < 1000) return '0s'
299
+ const total = Math.floor(ms / 1000)
300
+ if (total < 60) return `${total}s`
301
+ const minutes = Math.floor(total / 60)
302
+ const seconds = total % 60
303
+ if (minutes < 60) return `${minutes}m${seconds}s`
304
+ const hours = Math.floor(minutes / 60)
305
+ return `${hours}h${minutes % 60}m`
306
+ }