dsh-working-activity 0.2.4 → 0.2.6
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/LICENSE +28 -28
- package/README.i18n.yaml +6 -6
- package/README.md +122 -122
- package/README.zh.md +113 -113
- package/cordis.patch.yml +19 -19
- package/lib/client.js +80 -0
- package/lib/client.js.map +1 -0
- package/lib/types/client/WorkingLine.d.ts.map +1 -1
- package/lib/types/client/WorkingLine.js +6 -1
- package/lib/types/registration.d.ts.map +1 -1
- package/lib/types/registration.js +53 -6
- package/package.json +15 -5
- package/src/client/WorkingLine.module.css +74 -74
- package/src/client/WorkingLine.tsx +42 -37
- package/src/client/activity.ts +50 -50
- package/src/client/css-modules.d.ts +6 -6
- package/src/client/index.ts +42 -42
- package/src/events.ts +45 -45
- package/src/index.ts +228 -228
- package/src/invariant.ts +69 -69
- package/src/phrases.ts +180 -180
- package/src/registration.ts +51 -6
- package/src/status.ts +501 -501
package/src/invariant.ts
CHANGED
|
@@ -1,69 +1,69 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* Package-owned `activity/status` snapshot invariants.
|
|
3
|
-
* @module dsh-working-activity/invariant
|
|
4
|
-
*/
|
|
5
|
-
|
|
6
|
-
import type { Context } from '@deepseek-ai/cordis'
|
|
7
|
-
import type { Session, SessionEvent } from '@deepseek-ai/dsh-session'
|
|
8
|
-
import type { InvariantFailure, InvariantInstaller } from '@deepseek-ai/dsh-invariants'
|
|
9
|
-
|
|
10
|
-
/** Cordis companion plugin name. */
|
|
11
|
-
export const name = 'working-activity-invariant'
|
|
12
|
-
/** Service required before the companion can reserve package ownership. */
|
|
13
|
-
export const inject = ['invariants']
|
|
14
|
-
|
|
15
|
-
const PACKAGE_NAME = 'dsh-working-activity'
|
|
16
|
-
const PHASES = new Set(['idle', 'waiting', 'thinking', 'tool', 'done'])
|
|
17
|
-
|
|
18
|
-
/** Validate one published activity snapshot before it reaches the durable log. */
|
|
19
|
-
function validateStatus(data: unknown, fail: InvariantFailure): void {
|
|
20
|
-
const record = data as Record<string, unknown> | null
|
|
21
|
-
if (record === null || typeof record !== 'object' || Array.isArray(record)) {
|
|
22
|
-
fail('activity/status data must be an object')
|
|
23
|
-
return
|
|
24
|
-
}
|
|
25
|
-
if (typeof record.phase !== 'string' || !PHASES.has(record.phase)) {
|
|
26
|
-
fail(`activity/status carries unknown phase ${JSON.stringify(record.phase)}`)
|
|
27
|
-
}
|
|
28
|
-
if (typeof record.line !== 'string' || record.line.length === 0) {
|
|
29
|
-
fail('activity/status line must be a non-empty string')
|
|
30
|
-
}
|
|
31
|
-
for (const key of ['toolCount', 'turnElapsedMs', 'phaseStartedAt']) {
|
|
32
|
-
const value = record[key]
|
|
33
|
-
if (typeof value !== 'number' || !Number.isFinite(value) || value < 0) {
|
|
34
|
-
fail(`activity/status ${key} must be a non-negative finite number`)
|
|
35
|
-
}
|
|
36
|
-
}
|
|
37
|
-
for (const key of ['label', 'detail', 'phrase']) {
|
|
38
|
-
if (record[key] !== undefined && typeof record[key] !== 'string') {
|
|
39
|
-
fail(`activity/status ${key} must be a string when present`)
|
|
40
|
-
}
|
|
41
|
-
}
|
|
42
|
-
}
|
|
43
|
-
|
|
44
|
-
/* jscpd:ignore-start -- package companions share replay and dispatch plumbing */
|
|
45
|
-
/** Validate the package-owned event shape and ignore unrelated events. */
|
|
46
|
-
function validateEvent(event: SessionEvent, fail: InvariantFailure): void {
|
|
47
|
-
if (event.type === 'activity/status') validateStatus(event.data, fail)
|
|
48
|
-
}
|
|
49
|
-
|
|
50
|
-
/** Install validation for loaded and newly appended activity snapshots. */
|
|
51
|
-
const install: InvariantInstaller = Object.assign((ctx: Context, fail: InvariantFailure) => {
|
|
52
|
-
for (const session of ctx.sessions.list()) {
|
|
53
|
-
for (const event of session.events) validateEvent(event, fail)
|
|
54
|
-
}
|
|
55
|
-
ctx.on('internal/dispatch', (_mode, eventName, args) => {
|
|
56
|
-
if (eventName !== 'session/event') return
|
|
57
|
-
const event = (args as [Session, SessionEvent])[1]
|
|
58
|
-
validateEvent(event, fail)
|
|
59
|
-
}, { global: true })
|
|
60
|
-
}, { inject: ['sessions'] })
|
|
61
|
-
/* jscpd:ignore-end */
|
|
62
|
-
|
|
63
|
-
/**
|
|
64
|
-
* Register the working-activity invariant companion.
|
|
65
|
-
* @param ctx - Cordis context carrying the invariant service.
|
|
66
|
-
* @returns the installed registration's disposer after setup succeeds.
|
|
67
|
-
*/
|
|
68
|
-
export const apply = (ctx: Context): Promise<() => void> =>
|
|
69
|
-
Promise.resolve(ctx.invariants.register(PACKAGE_NAME, install))
|
|
1
|
+
/**
|
|
2
|
+
* Package-owned `activity/status` snapshot invariants.
|
|
3
|
+
* @module dsh-working-activity/invariant
|
|
4
|
+
*/
|
|
5
|
+
|
|
6
|
+
import type { Context } from '@deepseek-ai/cordis'
|
|
7
|
+
import type { Session, SessionEvent } from '@deepseek-ai/dsh-session'
|
|
8
|
+
import type { InvariantFailure, InvariantInstaller } from '@deepseek-ai/dsh-invariants'
|
|
9
|
+
|
|
10
|
+
/** Cordis companion plugin name. */
|
|
11
|
+
export const name = 'working-activity-invariant'
|
|
12
|
+
/** Service required before the companion can reserve package ownership. */
|
|
13
|
+
export const inject = ['invariants']
|
|
14
|
+
|
|
15
|
+
const PACKAGE_NAME = 'dsh-working-activity'
|
|
16
|
+
const PHASES = new Set(['idle', 'waiting', 'thinking', 'tool', 'done'])
|
|
17
|
+
|
|
18
|
+
/** Validate one published activity snapshot before it reaches the durable log. */
|
|
19
|
+
function validateStatus(data: unknown, fail: InvariantFailure): void {
|
|
20
|
+
const record = data as Record<string, unknown> | null
|
|
21
|
+
if (record === null || typeof record !== 'object' || Array.isArray(record)) {
|
|
22
|
+
fail('activity/status data must be an object')
|
|
23
|
+
return
|
|
24
|
+
}
|
|
25
|
+
if (typeof record.phase !== 'string' || !PHASES.has(record.phase)) {
|
|
26
|
+
fail(`activity/status carries unknown phase ${JSON.stringify(record.phase)}`)
|
|
27
|
+
}
|
|
28
|
+
if (typeof record.line !== 'string' || record.line.length === 0) {
|
|
29
|
+
fail('activity/status line must be a non-empty string')
|
|
30
|
+
}
|
|
31
|
+
for (const key of ['toolCount', 'turnElapsedMs', 'phaseStartedAt']) {
|
|
32
|
+
const value = record[key]
|
|
33
|
+
if (typeof value !== 'number' || !Number.isFinite(value) || value < 0) {
|
|
34
|
+
fail(`activity/status ${key} must be a non-negative finite number`)
|
|
35
|
+
}
|
|
36
|
+
}
|
|
37
|
+
for (const key of ['label', 'detail', 'phrase']) {
|
|
38
|
+
if (record[key] !== undefined && typeof record[key] !== 'string') {
|
|
39
|
+
fail(`activity/status ${key} must be a string when present`)
|
|
40
|
+
}
|
|
41
|
+
}
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
/* jscpd:ignore-start -- package companions share replay and dispatch plumbing */
|
|
45
|
+
/** Validate the package-owned event shape and ignore unrelated events. */
|
|
46
|
+
function validateEvent(event: SessionEvent, fail: InvariantFailure): void {
|
|
47
|
+
if (event.type === 'activity/status') validateStatus(event.data, fail)
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
/** Install validation for loaded and newly appended activity snapshots. */
|
|
51
|
+
const install: InvariantInstaller = Object.assign((ctx: Context, fail: InvariantFailure) => {
|
|
52
|
+
for (const session of ctx.sessions.list()) {
|
|
53
|
+
for (const event of session.events) validateEvent(event, fail)
|
|
54
|
+
}
|
|
55
|
+
ctx.on('internal/dispatch', (_mode, eventName, args) => {
|
|
56
|
+
if (eventName !== 'session/event') return
|
|
57
|
+
const event = (args as [Session, SessionEvent])[1]
|
|
58
|
+
validateEvent(event, fail)
|
|
59
|
+
}, { global: true })
|
|
60
|
+
}, { inject: ['sessions'] })
|
|
61
|
+
/* jscpd:ignore-end */
|
|
62
|
+
|
|
63
|
+
/**
|
|
64
|
+
* Register the working-activity invariant companion.
|
|
65
|
+
* @param ctx - Cordis context carrying the invariant service.
|
|
66
|
+
* @returns the installed registration's disposer after setup succeeds.
|
|
67
|
+
*/
|
|
68
|
+
export const apply = (ctx: Context): Promise<() => void> =>
|
|
69
|
+
Promise.resolve(ctx.invariants.register(PACKAGE_NAME, install))
|
package/src/phrases.ts
CHANGED
|
@@ -1,180 +1,180 @@
|
|
|
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
|
+
* 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
|
+
}
|
package/src/registration.ts
CHANGED
|
@@ -16,19 +16,30 @@
|
|
|
16
16
|
* strict validators consult only THEIR copy's Set. Registering through the
|
|
17
17
|
* plugin's own import alone would leave the validator's copy untouched.
|
|
18
18
|
* Anchors: this module (plugin/profile tree) and the process entry point
|
|
19
|
-
* (the CLI tree the persistence backend resolves from).
|
|
20
|
-
*
|
|
19
|
+
* (the CLI tree the persistence backend resolves from). Each anchor covers
|
|
20
|
+
* its own tree's top-level copy, then walks one edge further — through
|
|
21
|
+
* `@deepseek-ai/dsh-session-persistence`'s own resolution — because the
|
|
22
|
+
* strict validators (persistence load / resume seed checks) consult the
|
|
23
|
+
* copy THEY resolve, a third physical copy under nested or split-tree
|
|
24
|
+
* layouts that neither anchor's top-level resolution can reach. Copies are
|
|
25
|
+
* deduped by realpath; a copy that cannot be resolved from an anchor simply
|
|
26
|
+
* is not there; registration never throws.
|
|
21
27
|
*
|
|
22
28
|
* Self-adjusting per the compat house rules: when upstream's generated
|
|
23
29
|
* catalog adopts `activity/status` (or a real registration API ships), the
|
|
24
30
|
* add() calls are no-ops and this module can be deleted.
|
|
25
31
|
* @module dsh-working-activity/registration
|
|
26
32
|
*/
|
|
33
|
+
import { realpathSync } from 'node:fs'
|
|
27
34
|
import { createRequire } from 'node:module'
|
|
28
35
|
|
|
29
36
|
/** The session-event type this plugin publishes. */
|
|
30
37
|
const ACTIVITY_EVENT_TYPE = 'activity/status'
|
|
31
38
|
|
|
39
|
+
/** The package whose own resolution chain leads to the validators' dsh-session copy. */
|
|
40
|
+
const PERSISTENCE_PACKAGE = '@deepseek-ai/dsh-session-persistence'
|
|
41
|
+
const SESSION_PACKAGE = '@deepseek-ai/dsh-session'
|
|
42
|
+
|
|
32
43
|
interface KnownTypesModule {
|
|
33
44
|
KNOWN_SESSION_EVENT_TYPES?: Set<string>
|
|
34
45
|
}
|
|
@@ -42,13 +53,47 @@ export function registerActivityEventType(): void {
|
|
|
42
53
|
const anchors = [import.meta.url, process.argv[1]].filter(
|
|
43
54
|
(anchor): anchor is string => typeof anchor === 'string' && anchor.length > 0,
|
|
44
55
|
)
|
|
56
|
+
/** Realpaths already registered — split trees symlink heavily, and require caches by realpath. */
|
|
57
|
+
const registered = new Set<string>()
|
|
45
58
|
for (const anchor of anchors) {
|
|
59
|
+
let req: NodeRequire
|
|
60
|
+
try {
|
|
61
|
+
req = createRequire(anchor)
|
|
62
|
+
} catch {
|
|
63
|
+
continue // Anchor not on disk (e.g. no argv[1] under some runners) — skip.
|
|
64
|
+
}
|
|
65
|
+
registerSessionCopy(req, registered)
|
|
66
|
+
// The validator edge: persistence's load()/resume seed checks consult
|
|
67
|
+
// THEIR OWN resolved dsh-session copy — a third physical copy under
|
|
68
|
+
// nested/split-tree layouts (CLI/profile split, rc.5↔rc.6 upgrade
|
|
69
|
+
// windows, pnpm nesting) that the anchor's top-level resolution misses.
|
|
70
|
+
try {
|
|
71
|
+
const persistenceReq = createRequire(req.resolve(PERSISTENCE_PACKAGE))
|
|
72
|
+
registerSessionCopy(persistenceReq, registered)
|
|
73
|
+
} catch {
|
|
74
|
+
// No persistence package reachable from this anchor — nothing more to cover.
|
|
75
|
+
}
|
|
76
|
+
}
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
/**
|
|
80
|
+
* Register into the dsh-session copy resolved by `req`, once per realpath.
|
|
81
|
+
* @param req - require anchored at the tree (or nested package) to probe.
|
|
82
|
+
* @param registered - realpaths already handled in this registration pass.
|
|
83
|
+
*/
|
|
84
|
+
function registerSessionCopy(req: NodeRequire, registered: Set<string>): void {
|
|
85
|
+
try {
|
|
86
|
+
const resolved = req.resolve(SESSION_PACKAGE)
|
|
87
|
+
let key = resolved
|
|
46
88
|
try {
|
|
47
|
-
|
|
48
|
-
const mod = req('@deepseek-ai/dsh-session') as KnownTypesModule
|
|
49
|
-
mod.KNOWN_SESSION_EVENT_TYPES?.add(ACTIVITY_EVENT_TYPE)
|
|
89
|
+
key = realpathSync(resolved)
|
|
50
90
|
} catch {
|
|
51
|
-
//
|
|
91
|
+
// Vanished between resolve and realpath — dedupe by the resolved path.
|
|
52
92
|
}
|
|
93
|
+
if (registered.has(key)) return
|
|
94
|
+
registered.add(key)
|
|
95
|
+
;(req(resolved) as KnownTypesModule).KNOWN_SESSION_EVENT_TYPES?.add(ACTIVITY_EVENT_TYPE)
|
|
96
|
+
} catch {
|
|
97
|
+
// No resolvable dsh-session copy from this anchor — nothing to register into.
|
|
53
98
|
}
|
|
54
99
|
}
|