npm package discovery and stats viewer.

Discover Tips

  • General search

    [free text search, go nuts!]

  • Package details

    pkg:[package-name]

  • User packages

    @[username]

Sponsor

Optimize Toolset

I’ve always been into building performant and accessible sites, but lately I’ve been taking it extremely seriously. So much so that I’ve been building a tool to help me optimize and monitor the sites that I build to make sure that I’m making an attempt to offer the best experience to those who visit them. If you’re into performant, accessible and SEO friendly sites, you might like it too! You can check it out at Optimize Toolset.

About

Hi, 👋, I’m Ryan Hefner  and I built this site for me, and you! The goal of this site was to provide an easy way for me to check the stats on my npm packages, both for prioritizing issues and updates, and to give me a little kick in the pants to keep up on stuff.

As I was building it, I realized that I was actually using the tool to build the tool, and figured I might as well put this out there and hopefully others will find it to be a fast and useful way to search and browse npm packages as I have.

If you’re interested in other things I’m working on, follow me on Twitter or check out the open source projects I’ve been publishing on GitHub.

I am also working on a Twitter bot for this site to tweet the most popular, newest, random packages from npm. Please follow that account now and it will start sending out packages soon–ish.

Open Software & Tools

This site wouldn’t be possible without the immense generosity and tireless efforts from the people who make contributions to the world and share their work via open source initiatives. Thank you 🙏

© 2026 – Pkg Stats / Ryan Hefner

dsh-client-ui-trajectory-plus2

v0.1.1

Published

Verbatim copy of @deepseek-ai/[email protected] (MIT) with a six-line rename: the platform trajectory view republished as a second tab, 轨迹plus2 (view id plus2, order 21). No style, no layout and no feature differs from the official tab.

Readme

dsh-client-ui-trajectory-plus2

面板上的第二个「轨迹」。它不是我理解的轨迹,是官方那份轨迹的逐字副本:官方 @deepseek-ai/[email protected] 的客户端 bundle,只改了 6 行(模块 id / locale 命名空间 / 中英标签 / 视图 id / order)+ 3 处共存守卫(六处 register 与 provide、子槽改成"加入而不是抢",见下文),别的一个字符都没动,渲染行为没有任何改变。当前版本 0.1.1。

它是什么,不是什么

| | | | --- | --- | | 是 | 官方轨迹视图的一份逐字拷贝,注册成第二个标签页:视图 id plus2、order 21、标签 轨迹plus2 / Trajectory Plus 2 | | 是 | 一个指纹可验的基线:node --test 会把 src/client.js 逆向还原成官方原始字节并比对 sha256,差一个字符就红 | | 不是 | 手写的复刻、简化版或"抄个样子"。样式、布局、表格、泳道、折叠、JSON 树、右键菜单、图片子槽、虚拟滚动、历史加载、时长偏好——全部是官方原样 | | 不是 | 宿主侧有路由的插件。宿主半侧是官方那个空 apply,一个字节没改 |

这个包的存在意义就是基线:官方怎么跳、怎么折、怎么排序、什么时候发请求,这里一模一样。你要改哪里,指哪打哪,不用先怀疑"是不是抄错了"。

出处与指纹

| | | | --- | --- | | 来源 | @deepseek-ai/[email protected](MIT,Copyright (c) 2026 DeepSeek) | | 原始文件 | lib/client.js,421649 字节,8778 行(结尾没有换行),sha256 71fa00f0c3e92278e54e9203a819305f5abf7103d5b32114ad52afd467bb3b9c | | 宿主半侧 | lib/index.js,184 字节,sha256 1feff7e32e81646ad0b7f1b865a089414d88ea88d4ff81e24f0680ec018530b9,逐字复制 | | 本机原件 | /Users/337mu/.npm/_npx/7eff65a5cfe9d1d1/node_modules/@deepseek-ai/dsh-client-ui-trajectory/lib/client.js | | 许可 | 官方的 LICENSE 原样放在本包 LICENSE(MIT 正文一字未改) |

改动清单:6 行改名 + 3 处共存守卫

src/client.js 顶部有 40 行出处声明(注释,不是 bundle 逻辑:原来的 32 行 + 声明共存守卫的 8 行)。守卫自己加了 39 行(它是本包唯一一处"不是改名"的改动),所以:

  • 守卫之前的行:本文件行号 = 官方行号 + 40
  • 守卫之后的行:本文件行号 = 官方行号 + 40 + 39

A. 6 行改名(和以前完全一样):

| # | 官方 lib/client.js | 本文件 | 改什么 | 为什么 | | --- | --- | --- | --- | --- | | 1 | 2 | 42 | 模块 id:"@deepseek-ai/dsh-client-ui-trajectory" → "dsh-client-ui-trajectory-plus2" | 模块加载器按 boot manifest 的行 id(= 裸包名)取工厂;不改就两个模块撞,且会被判成"加载后没注册"直接丢弃 | | 2 | 56 | 96 | locale 命名空间:"trajectory" → "dsh-client-ui-trajectory-plus2" | 两个插件共用一个命名空间会互相覆盖字典;我们这份是官方那张表的私有副本 | | 3 | 59 | 99 | 中文标签:"view.trajectory": "轨迹" → "轨迹plus2" | 面板标签 | | 4 | 254 | 294 | 英文标签:"view.trajectory": "Trajectory" → "Trajectory Plus 2" | 面板标签 | | 5 | 8738 | 8817 | 视图 id:id: "trajectory" → id: "plus2" | 与官方标签页共存,id 必须唯一 | | 6 | 8739 | 8818 | order:order: 10 → order: 21 | 排在 官方对话 0 < 官方轨迹 10 < 轨迹plus 20 < 轨迹plus2 21 |

B. 3 处共存守卫(把"抢"改成"加入";白名单里是 coexistJoin / coexistProvide / coexistChildSlot 三行):

| # | 官方 lib/client.js | 本文件 | 改什么 | 为什么 | | --- | --- | --- | --- | --- | | 7 | 8726 | 8766 | 六个 register 调用改走 joining(...) / shared:官方已登记同名 key 就 return () => {},不抢(+39 行) | uiConversation.events.register / .views.register 重名必抛(dsh-client-ui-conversation/lib/client.js:2624) | | 8 | 8732 | 8811 | ctx.uiSession.provide({…}) → joinHook({…}):只吞 .includes("duplicate hook") | dsh-client-ui-session/lib/client.js:454 同一个标准钩子名重名必抛 | | 9 | 8736 | 8815 | ctx.slots.register({…}) → joinSlot({…}):只在 is already declared 时去掉 children 再注册一次 | dsh-client-ui-slots/lib/index.js:193 同名子槽必抛 |

守卫只改"重复注册时怎么办",不改任何渲染行为:那 9 个 Definition、1 个 target、那个 trajectory 钩子与我们登记的是同一份代码(test/shared-registries.test.js 逐键比对过),谁登记渲染出来的东西一样;子槽是按名字取用的,官方那条 entry 已经声明过 conversation.trajectory.images,我们退回"不带 children"的注册之后,官方 TrajectoryView 里的 renderSlot("conversation.trajectory.images", …)(官方 bundle 第 8324 行)照样解析到它。

没改的(刻意):inject 服务表(slots/sessions/uiSession/uiConversation/locale)、target("trajectory")、hooks: ["trajectory"]、子槽 children: {"conversation.trajectory.images"}、ctx.slots.inject("conversation.view")、9 个 Definition 的全部实现、4 段 CSS、persist 名(dsh.trajectory.duration / dsh.trajectory.jsonStringWrapping)。

那两处"没改"的含义要说清楚,因为它们是故意的:

  • target("trajectory") 与 hooks: ["trajectory"]:这是我们和官方共用同一条数据路径的原因——同一个 target、同一份 snapshot,所以它和官方一样快、一样准。
  • persist 名没改:两个标签页共用同一份"展开时长/JSON 换行"偏好(和官方行为一致)。
  • 那三处守卫也没有改任何一个 key、任何一份 Definition:官方不在场时(单独装本包、或本包排在官方之前加载),走的就是官方那条路径,注册结果与原样逐字节相同。

数据从哪来(为什么和官方一样快)

  • 宿主半侧:src/index.js 是官方那个空 apply——没有任何路由,一个 HTTP 请求都不发。
  • 浏览器半侧:数据全部来自平台客户端服务 uiSession / uiConversation(inject 声明的 5 个服务,官方原样)。它读的是平台已经在内存里的会话快照,不额外拉数据。
  • 所以它和官方轨迹的区别只有两处:标签页 id/order/文字。渲染路径、数据源、刷新时机完全相同。

合规

  • 来源包是 MIT,允许复制、修改、再分发;本包保留官方 LICENSE 正文,并在 src/client.js 顶部逐字写明:本文件是 @deepseek-ai/[email protected](MIT)的副本,改动仅限模块 id / 视图 id / order / 标签 / locale 命名空间,并附上原始 sha256。
  • src/index.js 是官方宿主半侧的逐字副本(测试里比 sha256)。
  • 本包的 LICENSE 是官方 MIT 原文;本包自身同样是 MIT。

⚠️ 和官方「轨迹」共存:已知会撞四处(先读这一段)

为什么

官方那份 bundle 除了画界面,还往平台全局注册表里写三类东西:

  1. ctx.uiConversation.events.register(...) × 9,键是 kind:trajectory-assistant-step / trajectory-turn-end / trajectory-compaction / trajectory-session-end / trajectory-inbox-next-step / trajectory-input-message / trajectory-system-message / trajectory-request-header / trajectory-tool-call
  2. ctx.uiConversation.views.register({ target: "trajectory", ... }) × 1
  3. ctx.uiSession.provide({ hooks: ["trajectory"], ... }) × 1,钩子名 trajectory
  4. 槽注册里的子槽声明 children: { "conversation.trajectory.images": { kind: "single", scope: "session" } }(@deepseek-ai/dsh-client-ui-attachment 往这个子槽里塞图片)

这四个注册表/去重表是全页共享的(uiConversation、slots、uiSession 每页各一个实例),而且重名直接抛错:

dsh-client-ui-conversation/lib/client.js:2624
  if (this.definitions.has(key)) throw new Error(duplicateMessage);
     ├─ events.register(:2656)→ conversation Definition "trajectory-inbox-next-step" is already registered
     └─ views.register (:2883)→ conversation view target "trajectory" is already registered
dsh-client-ui-slots/lib/index.js:193
  if (childRec?.spec) throw new Error('slot "' + childKey + '" is already declared (by ...)')
     └─ → slot "conversation.trajectory.images" is already declared
          (by an entry in "conversation.view" (@deepseek-ai/dsh-client-ui-trajectory))
dsh-client-ui-session/lib/client.js:454  (claimStandardProp,由 :261 rebuildBindings -> materializeAbsent() 在 provide 时就抛)
  if (finalProps.has(propName)) throw new Error('uiSession.provide: duplicate <kind> ' + name + ' at prop ' + propName)
     └─ → uiSession.provide: duplicate hook 'trajectory' at prop 'useTrajectory'

这不是猜测:@deepseek-ai/dsh-web-app 的依赖里有 @deepseek-ai/dsh-client-ui-trajectory,而 @deepseek-ai/dsh-web-app 就在本机 profile 的 dsh.profile.bundles 里——官方轨迹标签永远在场(你看得到它,就是证据)。

会发生什么(没有守卫的时候)

我们的 apply() 按官方顺序先跑 registerTrajectoryMessageDefinitions(ctx),第一句 events.register 就撞上第 1 类,抛错 → 这个客户端插件加载失败 → 面板上不会出现「轨迹plus2」(第 2/3/4 类排在它后面,都走不到;控制台里看到的会是第 1 类那句)。这句推断现在是可复现的:test/coexistence.test.js 在一个平台形状的 stub 上下文里先跑官方那份 bundle,第二次 apply() 抛的就是 conversation Definition "trajectory-inbox-next-step" is already registered。

修法:已经打上去了(白名单第 7–9 行)

四处都改成"官方已经登记过,我就加入它,不再抢"。下面是设计稿(原来只贴在 README 里的那三段),落盘代码与它只有一处收窄,见代码块后面的说明:

// 1) apply(ctx) 里:六个 register 调用改走守卫层
		const joining = (registry) => ({
			register: (definition) => {
				const key = definition.kind ?? definition.target;
				if (registry.entries().some((entry) => (entry.kind ?? entry.target) === key)) return () => {};
				return registry.register(definition);
			}
		});
		const shared = {
			uiConversation: {
				events: joining(ctx.uiConversation.events),
				views: joining(ctx.uiConversation.views)
			}
		};
		registerTrajectoryMessageDefinitions(shared);          // 原本传 ctx
		registerTrajectoryRequestHeaderDefinition(shared);
		registerTrajectoryAssistantDefinition(shared);
		registerTrajectoryToolDefinition(shared);
		registerTrajectoryCompactionDefinitions(shared);
		registerTrajectoryConversationView(shared);

// 2) uiSession 钩子:钩子名是标准的,平台按名字去重、重复即抛;官方已经供了同一个钩子,加入即可
		try {
			ctx.uiSession.provide({
				hooks: ["trajectory"],
				resolve: (binding) => ({ hooks: { trajectory: trajectorySource(binding) } })
			});
		} catch (error) {
			if (!String(error?.message ?? error).includes("duplicate hook")) throw error;
		}

// 3) 槽注册处:子槽已被官方声明时,退回"不带 children"再注册一次
			ctx.slots.inject("conversation.view", () => {
				const entry = { /* 原来那一整块,含 children: { "conversation.trajectory.images": ... } */ };
				try {
					return ctx.slots.register(entry, TrajectoryView);
				} catch (error) {
					if (!String(error?.message ?? error).includes("is already declared")) throw error;
					const { children: _joined, ...withoutChildren } = entry;
					return ctx.slots.register(withoutChildren, TrajectoryView);
				}
			});

落盘代码与设计稿的唯一差别(src/client.js 里的 shared):设计稿的 shared.uiConversation 是一个只带 events/views 的普通对象,但 registerTrajectoryRequestHeaderDefinition 的两条 Definition 在渲染时还会回手调用 ctx.uiConversation.inspectSystemPrompt(...) / inspectRequestPrompt(...)(官方 bundle 第 1443–1444 行:那两个箭头函数闭包住的 ctx 就是传进去的 shared)。照设计稿原样传,这两个调用会变成 undefined is not a function —— 那正好是"改变渲染行为" ✗。所以落盘版本用一个 Proxy:只替换 events/views,其余成员(inspectSystemPrompt、inspectRequestPrompt、binding、imageUrl…)一律原样转发给平台服务。

const shared = {
    uiConversation: new Proxy(ctx.uiConversation, {
        get: (target, property) => property === "events" ? joining(target.events) : property === "views" ? joining(target.views) : Reflect.get(target, property, target)
    })
};

第 2、3 段也没有整块包 try/catch,而是抽成 joinHook / joinSlot 两个小函数,调用点各自只改一个词:ctx.uiSession.provide({ → joinHook({,ctx.slots.inject("conversation.view", () => ctx.slots.register({ → … => joinSlot({。这样 id / order 那两行改名仍然是两行独立的白名单行,不会和守卫混在一起(守卫里只有第 7 行加了 39 行,第 8、9 行都是等长替换)。

"加入"为什么等于"自己做",有两处硬依据:

  • 那 9 个 Definition + 1 个 target + 那个 trajectory 钩子与我们登记的是同一份代码(副本逐字相同,test/shared-registries.test.js 逐键比对过),谁登记渲染出的东西一样;
  • 子槽是按名字取用的:官方 TrajectoryView 里写的是 renderSlot("conversation.trajectory.images", {...})(官方 bundle 第 8324 行),名字已被官方那条 entry 声明过就能取到——所以退回"不带 children"的注册,图片照样渲染。

另一处不报错但要知道的耦合:CSS

4 个 <style> 标签的注入守卫是 style[data-plugin-css="<官方包名>/<文件>.module.css"],标签属性写的是官方包名(副本逐字,所以它仍自称 @deepseek-ai/dsh-client-ui-trajectory)。后果:

  • 页面上只会有一份样式(第二次注入被守卫挡住),不重复;
  • 但 dsh-client-modules 的 removeOwnedStyles(id) 按 data-plugin 属性删样式:官方包热重载/卸载会连带删掉轨迹plus2 赖以绘制的样式(我们不删官方的)。本地开发时如果看到"轨迹plus2 突然没样式了",先看官方那条 entry 是不是刚重建过。

守卫带来的一个"只影响热重载"的边角(不报错,但要知道)

官方那条 entry 被卸载/热重载时,它的子槽声明是跟着 entry 一起撤销的;本包退回的那次注册(不带 children)不会替它补上,所以从官方卸载到它重新注册之间,plus2 的图片会短暂不渲染(slots.snapshot() 对未声明的槽返回空——降级,不抛错)。官方 entry 回来后子槽自动恢复;本包自己的标签页、表格、泳道等不受影响。这是"共存"换来的代价里唯一一处看得见的,且只在开发时的热重载窗口内。

安装(宿主与 profile 由你来做)

~/.dsh/profiles/web/package.json:

"dependencies": {
  "dsh-client-ui-trajectory-plus2": "link:/Users/337mu/Documents/Default Project/dsh-client-ui-trajectory-plus2"
},
"dsh": {
  "profile": {
    "bundles": [
      "…原有的…",
      "dsh-client-ui-trajectory-plus2"
    ]
  }
}

装完 pnpm install(或你惯用的流程)→ 重启 DSH → 刷新页面。order 21 会把它排在官方「轨迹」(10) 的右边、兄弟插件「轨迹plus」(20) 的右边。

测试

node --test test/*.test.js

| 文件 | 管什么 | | --- | --- | | test/diff-whitelist.test.js | 差异白名单:把 9 条替换(6 处改名 + 3 处守卫)逆向还原,比对官方 sha256/字节数/行数;再逐条断言"官方行号对不对",并用两条 NEGATIVE 证明"改坏一个字符就会红" | | test/registration.test.js | 在 stub 浏览器里真的执行 bundle、真的跑 apply():模块 id、服务表、视图 id/order/标签(中英)、子槽、hook provider、4 个 style 标签 | | test/shared-registries.test.js | 把官方 bundle 和我们的副本放进同一个 stub 对比注册结果,把上面那条共存警告钉成可验证的事实:同一个 stub 里跑两份 bundle,逐键比对注册结果,并附上平台源码那四个抛错点的行号 | | test/coexistence.test.js | 共存守卫的证明:在同一个"平台形状"的上下文里先挂官方、再挂本包,断言 apply() 不再抛错、9 个 key 仍是官方那一份(且只有一份)、plus2 标签照样注册(并按 is already declared 退掉 children)、单独安装时行为不变;外加两条负向控制(官方字节跑两遍 → 抛;provide/register 的非重复错误 → 照样抛且不重试) | | test/sources.js / test/browser-stub.js | 读源文件的入口 + 那个只做"注册"不做"渲染"的 stub 浏览器(stub 的四个注册表按平台的行为拒绝重名,否则"官方已经在了"这种状态根本没法复现) |

"差异白名单"是怎么写的(最重要的那条):

  1. test/sources.js 里有一张白名单表 PATCHES:每行是一个字面替换(from/to,带制表符)+ 它在官方文件里的行号 + 为什么。表就是授权:加一行 = 批准一处改动,删一行 = 禁止一处改动。9 行 = 6 处改名(moduleId/namespace/labelZh/labelEn/viewId/viewOrder)+ 3 处共存守卫(coexistJoin/coexistProvide/coexistChildSlot)。
  2. 测试按这张表反向替换 src/client.js 的正文,得到"被还原的官方字节",再比对 OFFICIAL.sha256(实测 71fa00f0…)。421 KB 里错一个字符就红。
  3. 顺带钉住:字节数 421649、行数 8778、没有结尾换行、每条替换在官方文本里的行号与表里一致、我们的文件 = 官方行数 8778 + 出处声明 40 行 + 守卫 39 行 = 8857 行(ADDED_LINES 这个数字由表自己算出来,改一个字符都对不上)。
  4. 如果这台机器上装着的官方原件被顺便找到了,就直接逐字节比对,并断言原件自身的 sha256 没变——平台升级了会报警而不是静默漂移。
  5. 两条负向验证:①正文改一个字符(padding:0 8px → padding:0 9px)→ 还原结果 sha256 必变;②一份"没做替换"的原件 → 还原函数必须拒绝,而不是误判为原件。

我(这份包)验证不了的

  • 没有浏览器:轨迹plus2 和官方「轨迹」画出来是不是一模一样,必须你重启后亲眼并排比一遍(表格行、泳道色条、折叠摘要、JSON 树、右键菜单、图片、历史加载、时长偏好、"展开 JSON 字符串")。测试只到"注册的东西一样"。
  • 守卫"在真机上"的效果:test/coexistence.test.js 证明的是"同一份平台代码把重名当错误"这条规则之下的行为;真浏览器里两个标签都开、都能翻历史、都能看图片,仍然要你看一眼(尤其:plus2 退回"不带 children"的注册之后,图片子槽要能渲染出来)。
  • 加载链路(dsh.client 扫描、boot manifest 行、模块图、profile 安装)是宿主侧的事,不在本包测试范围,由你验证。
  • 上面那段"会发生什么"——守卫之前会在第一句 events.register 上抛——现在在 stub 里可复现(test/coexistence.test.js 的 NEGATIVE 那条),但真机没跑过:你重启后如果「轨迹plus2」没出现,控制台里应该是上面四句里的第 1 句,或者别的没被守卫覆盖的第 5 类;把那句发我。
  • 加载顺序:守卫让"谁先注册谁拥有"。官方 dsh-web-app 的依赖图把官方轨迹排在前面,所以正常路径是官方先注册、本包"加入";万一本包排到了官方前面,被拒的会是官方那个标签——这条已经写成测试钉住了(KNOWN LIMIT),profile 的 bundles 里别把本包挪到官方前面。

文件清单

| 文件 | 说明 | | --- | --- | | src/client.js | 官方 lib/client.js 的逐字副本 + 40 行出处声明 + 6 行改名 + 39 行共存守卫(8857 行) | | src/index.js | 官方 lib/index.js 的逐字副本:空 apply,无宿主路由 | | LICENSE | 官方 MIT 原文(Copyright (c) 2026 DeepSeek) | | cordis.patch.yml | 把插件行插进 profile roster(条目名必须是裸包名) | | package.json | dsh.client.inject 与官方逐字相同;dsh.bundle.patch 指向上面那个 yml | | test/* | 见上一节 |

版本 0.1.1 写在三处并且有测试盯着它们一致:package.json 的 version、src/client.js 顶部声明的 VERSION 0.1.1、本 README。