pi-extensions-i18n 0.5.0 → 0.6.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/README.md CHANGED
@@ -14,7 +14,8 @@ Independent Pi extensions still need the same operational pieces: a portable con
14
14
  - `/config:language` interactive command, plus `/config:language en-US` direct selection.
15
15
  - Catalog loading and validation requiring both language entries for every message key.
16
16
  - Translator interpolation for user-facing UI, command descriptions, and agent prompts.
17
- - A single notice outlet, `notifyWithSource`, that prefixes every user-visible notice with a short source tag in the package's own label colour. Pi renders `info` notices as dim, unprefixed text, so without a tag you cannot tell which extension spoke.
17
+ - A single notice outlet, `notifyWithSource`, that draws every user-visible notice as a filled background block in the transcript (the same block Pi uses for extension messages) with a short source tag in the package's own label colour. Pi renders `info` notices as dim, unprefixed text, so without the block and tag you cannot tell which extension spoke.
18
+ - Notices land below the message and stay out of the LLM context: they are written as Pi custom entries (`appendEntry` + `registerEntryRenderer`) and only affect the transcript.
18
19
 
19
20
  ## Install
20
21
 
@@ -66,7 +67,7 @@ const NOTICE_SOURCE: NoticeSource = { tag: NOTICE_TAG, color: NOTICE_COLOR };
66
67
  notifyWithSource({ ctx, source: NOTICE_SOURCE, level: "warning", message: i18n.t("failed") });
67
68
  ```
68
69
 
69
- This renders as `[distill] message`: the tag carries the package colour, the message stays untouched, and `level` still decides Pi's yellow `Warning:` / red `Error:` prefix. Colours are only added in tui mode; rpc/print/json get plain text so no ANSI leaks into other frontends. Use `formatNotice({ source, message, mode, theme })` when you only need the rendered string.
70
+ This renders as a filled background block with `[distill] message` on its first line: the tag carries the package colour, the body colour follows `level` (`warning` yellow, `error` red, `info` the extension message text colour), and `textColor` overrides the body colour for verdict-style lines that carry their own semantic colour. In tui mode the notice is written as a Pi custom entry below the message; rpc/print/json keep using `ctx.ui.notify` with plain `[distill] message` text so no ANSI leaks into other frontends. The block is registered once by this package's own extension entry, so a package that uses the helper must load `../pi-extensions-i18n/index.ts` in its `pi.extensions` list. Use `formatNotice({ source, message, mode, theme })` when you only need the rendered string.
70
71
 
71
72
  Other exports:
72
73
 
package/README.zh-CN.md CHANGED
@@ -14,7 +14,8 @@ Pi 扩展公共国际化运行时。它提供基于 catalog 的小型 API,支
14
14
  - 提供 `/config:language` 交互式命令,也支持 `/config:language en-US` 直接设置。
15
15
  - 加载并校验 catalog,要求每个消息 key 同时提供两种语言。
16
16
  - 为 UI、命令描述和 Agent prompt 提供用户文案插值。
17
- - 提供统一的用户提示出口 `notifyWithSource`:给提示加「来源标签 + 固定颜色」,解决 Pi 对 `info` 级提示只显示暗灰无前缀文本、用户分不清消息来自哪个扩展的问题。
17
+ - 提供统一的用户提示出口 `notifyWithSource`:把提示画成会话区里的**带底色消息块**(用户消息同款底色,见下条),并配上「来源标签 + 固定颜色」,解决 Pi 对 `info` 级提示只显示暗灰无前缀文本、用户既分不清来源也不容易注意到的问题。
18
+ - 提示落在消息下方、不进 LLM 上下文:通过 Pi 的自定义条目(`appendEntry` + `registerEntryRenderer`)实现,条目只在本地渲染,不消耗上下文窗口。
18
19
 
19
20
  ## 安装
20
21
 
@@ -39,7 +40,13 @@ const NOTICE_SOURCE: NoticeSource = { tag: NOTICE_TAG, color: NOTICE_COLOR };
39
40
  notifyWithSource({ ctx, source: NOTICE_SOURCE, level: "warning", message: i18n.t("failed") });
40
41
  ```
41
42
 
42
- 输出形如 `[distill] 提示正文`:标签按扩展固定色,正文保持原样,`level` 仍然决定 Pi 侧的黄色 `Warning:` / 红色 `Error:` 前缀。颜色只在 tui 模式添加,rpc/print/json 模式输出纯文本,不会出现 ANSI 乱码。
43
+ 输出是一个和用户消息同款的**实心底色块**,首行是 `[distill] 提示正文`:标签按扩展固定色,正文颜色由 `level` 决定(`warning` 黄、`error` 红、`info` 用扩展消息正文色),也可以用 `textColor` 覆盖成结论行自带的语义色(`dim`/`success` 等)。
44
+
45
+ 渲染细节:
46
+
47
+ - 只有 TUI 会把提示画成底色块;rpc/print/json 仍走 `ctx.ui.notify`,输出纯文本 `[distill] 提示正文`,不会出现 ANSI 乱码。
48
+ - 底色块由本包的扩展入口 `installNoticeRenderer(pi)` 注册一次,因此使用本包的功能包必须在自己的 `pi.extensions` 里加载 `../pi-extensions-i18n/index.ts`。
49
+ - 老版本 Pi 没有这两个能力时不会注入,提示自动退回 `ctx.ui.notify`(仍然可见,只是没有底色)。
43
50
 
44
51
  需要更细粒度控制时用 `formatNotice({ source, message, mode, theme })` 只取文本。
45
52
 
package/SKILL.md CHANGED
@@ -23,10 +23,15 @@ description: "配置 Pi 扩展共享语言并排查 locale 优先级、持久化
23
23
 
24
24
  ## 统一提示出口
25
25
 
26
- 功能包的用户可见提示必须走 `notifyWithSource`(而不是直接 `ctx.ui.notify`),否则 Pi 会把 `info` 级提示渲染成暗灰无前缀文本,用户分不清消息来自哪个扩展:
26
+ 功能包的用户可见提示必须走 `notifyWithSource`(而不是直接 `ctx.ui.notify`),否则 Pi 会把 `info` 级提示渲染成暗灰无前缀的一行文字,用户既分不清来源也不容易注意到:
27
27
 
28
28
  ```ts
29
29
  notifyWithSource({ ctx, source: NOTICE_SOURCE, level: "warning", message: i18n.t("failed") });
30
30
  ```
31
31
 
32
- 每个包用短的唯一 tag 与固定颜色;颜色只在 tui 模式添加(自动处理,不要自己在调用点拼 ANSI)。排查提示显示问题时,先确认调用是否走了这个出口,再看 tag 与 level 是否合理。
32
+ - 呈现:TUI 下画成会话区里的带底色消息块(落在消息下方,不进 LLM 上下文);rpc/print/json 仍走 `ctx.ui.notify` 的纯文本。
33
+ - 标签:每个包用短的唯一 tag 与固定颜色;颜色只在 tui 模式添加(自动处理,不要自己在调用点拼 ANSI)。
34
+ - 依赖:底色块的渲染器由本包的扩展入口 `installNoticeRenderer(pi)` 注册一次,所以用它的包必须在 `pi.extensions` 里加载 `../pi-extensions-i18n/index.ts`,否则提示会退回纯文本(不报错、不丢提示)。
35
+ - 自带语义色的结论行(如「已打断,未判定」)用 `textColor` 传色,不要再自己写页脚状态行。
36
+
37
+ 排查提示显示问题时,先确认包声明里加载了 i18n 入口,再看调用是否走了这个出口,最后看 tag 与 level 是否合理。
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "pi-extensions-i18n",
3
- "version": "0.5.0",
3
+ "version": "0.6.0",
4
4
  "description": "Shared i18n catalog loader & translator for pi extensions",
5
5
  "type": "module",
6
6
  "main": "./index.ts",
@@ -54,10 +54,12 @@
54
54
  "coding-agent"
55
55
  ],
56
56
  "peerDependencies": {
57
- "@earendil-works/pi-coding-agent": ">=0.80.0"
57
+ "@earendil-works/pi-coding-agent": ">=0.80.0",
58
+ "@earendil-works/pi-tui": ">=0.80.0"
58
59
  },
59
60
  "devDependencies": {
60
61
  "@earendil-works/pi-coding-agent": "0.85.1",
62
+ "@earendil-works/pi-tui": "0.85.1",
61
63
  "@types/node": "24.12.4",
62
64
  "tsx": "4.23.1",
63
65
  "typescript": "5.9.3"
package/src/index.ts CHANGED
@@ -6,7 +6,12 @@ import { mkdirSync, readFileSync, writeFileSync } from "node:fs";
6
6
  import { dirname, join } from "node:path";
7
7
  import { homedir } from "node:os";
8
8
  import { fileURLToPath } from "node:url";
9
- import { notifyWithSource, type NoticeColor, type NoticeSource } from "./notice.ts";
9
+ import {
10
+ installNoticeRenderer,
11
+ notifyWithSource,
12
+ type NoticeColor,
13
+ type NoticeSource,
14
+ } from "./notice.ts";
10
15
 
11
16
  export const SUPPORTED_LOCALES = ["zh-CN", "en-US"] as const;
12
17
  export type Locale = (typeof SUPPORTED_LOCALES)[number];
@@ -273,15 +278,27 @@ function registerLocaleCommand(pi: ExtensionAPI): void {
273
278
  }
274
279
 
275
280
  export default function piI18n(pi: ExtensionAPI): void {
281
+ // 提示改走会话区的自定义条目(带底色消息块),渲染器在这里一次性注册。
282
+ installNoticeRenderer(pi);
276
283
  registerLocaleCommand(pi);
277
284
  }
278
285
 
279
286
  export {
280
287
  formatNotice,
281
288
  notifyWithSource,
289
+ installNoticeRenderer,
290
+ hasNoticeRenderer,
291
+ resetNoticeRenderer,
292
+ renderNoticeEntry,
293
+ noticeBodyColor,
294
+ NOTICE_BACKGROUND_COLOR,
282
295
  NOTICE_COLOR_MODE,
296
+ NOTICE_ENTRY_TYPE,
297
+ type NoticeApi,
283
298
  type NoticeColor,
284
299
  type NoticeContext,
300
+ type NoticeEntryData,
301
+ type NoticeEntryTheme,
285
302
  type NoticeLevel,
286
303
  type NoticeRenderOptions,
287
304
  type NoticeSendOptions,
package/src/notice.ts CHANGED
@@ -1,26 +1,38 @@
1
1
  /**
2
- * 用户可见提示的统一呈现:给每条提示加「来源标签」,并按级别上色。
2
+ * 用户可见提示的统一呈现:把提示画成会话区里的「带底色消息块」,并标出来源扩展。
3
3
  *
4
- * 背景:Pi 的 ui.notify 只有 info(暗灰、无前缀)/ warning(黄色 Warning:)/ error(红色 Error:)
5
- * 三种呈现,多数扩展全部用 info,用户在会话里无法分辨消息来自哪个扩展。
6
- * 这里给每个扩展一个固定短标签与固定颜色,让提示一眼可辨。
4
+ * 背景:Pi 的 ui.notify(info) 只是一行暗灰色文字(warning/error 才有黄色/红色前缀),
5
+ * 各扩展全用 info 时用户在会话里既分不清来源,也分不清哪条是提示。
7
6
  *
8
- * 本模块不依赖 Pi 的具体实现,只用结构化类型,便于独立测试。
7
+ * 做法:提示改走 Pi 的自定义条目(appendEntry + registerEntryRenderer),
8
+ * 渲染成和用户消息同款的实心底色块(主题色 customMessageBg),左侧标注来源扩展的短标签。
9
+ * 这些条目不进入 LLM 上下文,只影响会话区外观。
10
+ *
11
+ * 本模块只用结构化类型,不直接依赖 Pi 的实现,便于独立测试。
9
12
  */
13
+ import { Box, Text, type Component } from "@earendil-works/pi-tui";
14
+
15
+ /** 允许使用的提示级别;同时是运行时校验的唯一真值来源。 */
16
+ const NOTICE_LEVELS = ["info", "warning", "error"] as const;
10
17
 
11
18
  /** 提示级别,与 Pi 的 ui.notify 类型一致。 */
12
- export type NoticeLevel = "info" | "warning" | "error";
13
-
14
- /** 主题色名;取值是 Pi 主题色的子集,避免依赖具体主题实现。 */
15
- export type NoticeColor =
16
- | "accent"
17
- | "success"
18
- | "warning"
19
- | "error"
20
- | "muted"
21
- | "dim"
22
- | "text"
23
- | "toolTitle";
19
+ export type NoticeLevel = (typeof NOTICE_LEVELS)[number];
20
+
21
+ /** 允许使用的主题色名(Pi 主题色的子集);同时是运行时校验的唯一真值来源。 */
22
+ const NOTICE_COLORS = [
23
+ "accent",
24
+ "success",
25
+ "warning",
26
+ "error",
27
+ "muted",
28
+ "dim",
29
+ "text",
30
+ "customMessageText",
31
+ "toolTitle",
32
+ ] as const;
33
+
34
+ /** 提示用到的主题色名。 */
35
+ export type NoticeColor = (typeof NOTICE_COLORS)[number];
24
36
 
25
37
  /** 一个扩展的提示来源:短标签 + 固定颜色。 */
26
38
  export interface NoticeSource {
@@ -32,10 +44,10 @@ export interface NoticeSource {
32
44
 
33
45
  /** 渲染提示所需的最小 UI 上下文。 */
34
46
  export interface NoticeContext {
35
- /** 运行模式;只有 tui 能安全地显示 ANSI 颜色。 */
47
+ /** 运行模式;只有 tui 会渲染成带底色的消息块。 */
36
48
  mode?: string;
37
49
  ui: {
38
- /** Pi 的提示出口。 */
50
+ /** Pi 的提示出口;非 TUI 模式仍走这里。 */
39
51
  notify(message: string, type?: NoticeLevel): void;
40
52
  /** 主题;缺失时不加颜色。 */
41
53
  theme?: { fg(color: NoticeColor, text: string): string };
@@ -60,18 +72,189 @@ export interface NoticeSendOptions {
60
72
  ctx: NoticeContext;
61
73
  /** 来源标签与颜色。 */
62
74
  source: NoticeSource;
63
- /** 提示级别;决定 Pi 侧的前缀与主色。 */
75
+ /** 提示级别;决定正文颜色(warning 黄、error 红、info 用正文色)。 */
64
76
  level: NoticeLevel;
65
77
  /** 提示正文(已本地化)。 */
66
78
  message: string;
79
+ /** 正文颜色覆盖;例如判定结论行自带语义色(dim/success)时用它。 */
80
+ textColor?: NoticeColor;
81
+ /** 展开时才显示的细节行,平时只占一行,避免刷屏。 */
82
+ details?: string[];
67
83
  }
68
84
 
69
85
  /** 只有 TUI 模式能安全地看到 ANSI 颜色。 */
70
86
  export const NOTICE_COLOR_MODE = "tui";
71
87
 
88
+ /** 提示条目的类型名;所有扩展共用一种,渲染器只需注册一次。 */
89
+ export const NOTICE_ENTRY_TYPE = "pi-extensions-notice";
90
+
91
+ /** 提示块的底色主题色:和 Pi 的扩展消息同款,视觉效果接近输入框。 */
92
+ export const NOTICE_BACKGROUND_COLOR = "customMessageBg";
93
+
72
94
  /** 标签与消息之间的分隔符。 */
73
95
  const TAG_SEPARATOR = " ";
74
96
 
97
+ /** 落进会话的提示条目数据;渲染器只依赖这些字段,重启后也能原样重建。 */
98
+ export interface NoticeEntryData {
99
+ /** 来源短标签。 */
100
+ tag: string;
101
+ /** 标签颜色。 */
102
+ color: NoticeColor;
103
+ /** 提示级别;决定正文默认颜色。 */
104
+ level: NoticeLevel;
105
+ /** 提示正文。 */
106
+ message: string;
107
+ /** 正文颜色覆盖。 */
108
+ textColor?: NoticeColor;
109
+ /** 展开时才显示的细节行(Ctrl+O 展开工具输出时一起展开)。 */
110
+ details?: string[];
111
+ }
112
+
113
+ /** 渲染器拿到的主题:只需要前景色与底色。 */
114
+ export interface NoticeEntryTheme {
115
+ /** 前景色。 */
116
+ fg(color: NoticeColor, text: string): string;
117
+ /** 底色。 */
118
+ bg(color: typeof NOTICE_BACKGROUND_COLOR, text: string): string;
119
+ }
120
+
121
+ /** 提示渲染所需的 Pi 能力:写入自定义条目 + 注册条目渲染器。 */
122
+ export interface NoticeApi {
123
+ /** 追加一条不进 LLM 上下文的自定义条目。 */
124
+ appendEntry(customType: string, data?: unknown): void;
125
+ /** 注册自定义条目的 TUI 渲染器;Pi 会把展开状态一起传进来。 */
126
+ registerEntryRenderer(
127
+ customType: string,
128
+ renderer: (
129
+ entry: { data?: unknown },
130
+ options: { expanded?: boolean },
131
+ theme: NoticeEntryTheme,
132
+ ) => Component,
133
+ ): void;
134
+ }
135
+
136
+ /** 判断一个未知值是不是普通对象。 */
137
+ function isRecord(value: unknown): value is Record<string, unknown> {
138
+ return typeof value === "object" && value !== null;
139
+ }
140
+
141
+ /** 运行时校验主题色名。 */
142
+ function isNoticeColor(value: unknown): value is NoticeColor {
143
+ return typeof value === "string" && NOTICE_COLORS.some((color) => color === value);
144
+ }
145
+
146
+ /** 运行时校验提示级别。 */
147
+ function isNoticeLevel(value: unknown): value is NoticeLevel {
148
+ return typeof value === "string" && NOTICE_LEVELS.some((level) => level === value);
149
+ }
150
+
151
+ /**
152
+ * 当前会话的提示出口。
153
+ *
154
+ * 这是本模块唯一的可变状态,注入点是扩展入口的 installNoticeRenderer:
155
+ * 提示调用点分散在 15 个包的几十处(含 tps、turn-elapsed 等拿不到 pi 的模块),
156
+ * 逐个传参会把 Pi 的写入能力扩散到所有业务函数里,因此只在入口注入一次。
157
+ * 扩展重载会重新执行入口,这里始终保存最近一次的 Pi 实例。
158
+ */
159
+ let noticeApi: NoticeApi | undefined;
160
+
161
+ /**
162
+ * 注入提示出口并注册条目渲染器。
163
+ * 由 pi-extensions-i18n 的扩展入口调用;依赖它的扩展会自动带上这个入口。
164
+ * 老版本 Pi 没有这两个能力时直接不注入,提示会退回 ui.notify(仍然可见,只是没有底色)。
165
+ */
166
+ /** 提示块的水平内边距:让文字不贴边。 */
167
+ const NOTICE_PADDING_X = 1;
168
+ /** 提示块的垂直内边距:0 表示只占一行,避免提示刷屏。 */
169
+ const NOTICE_PADDING_Y = 0;
170
+
171
+ /**
172
+ * 注册提示条目渲染器,并记下用于写入条目的 Pi 实例。
173
+ * 由 pi-extensions-i18n 的扩展入口调用;依赖它的扩展会自动带上这个入口。
174
+ * 老版本 Pi 没有这两个能力时直接不注入,提示会退回 ui.notify(仍然可见,只是没有底色)。
175
+ */
176
+ export function installNoticeRenderer(api: NoticeApi): void {
177
+ if (typeof api.appendEntry !== "function" || typeof api.registerEntryRenderer !== "function") {
178
+ return;
179
+ }
180
+ api.registerEntryRenderer(NOTICE_ENTRY_TYPE, (entry, options, theme) =>
181
+ renderNoticeEntry(entry, theme, isExpanded(options)));
182
+ noticeApi = api;
183
+ }
184
+
185
+ /** 当前是否已具备把提示画成带底色消息块的能力。 */
186
+ export function hasNoticeRenderer(): boolean {
187
+ return noticeApi !== undefined;
188
+ }
189
+
190
+ /** 测试与重载用:清掉已注入的提示出口。 */
191
+ export function resetNoticeRenderer(): void {
192
+ noticeApi = undefined;
193
+ }
194
+
195
+ /** 正文默认颜色:warning 黄、error 红、info 用扩展消息正文色。 */
196
+ export function noticeBodyColor(level: NoticeLevel, textColor?: NoticeColor): NoticeColor {
197
+ if (textColor !== undefined) return textColor;
198
+ if (level === "warning") return "warning";
199
+ if (level === "error") return "error";
200
+ return "customMessageText";
201
+ }
202
+
203
+ /**
204
+ * 把未知的条目数据收敛成提示条目数据。
205
+ * 逐字段运行时校验;缺失或类型不符时给出可读兜底,不信任外来数据。
206
+ */
207
+ function readNoticeEntryData(input: unknown): NoticeEntryData {
208
+ const raw = isRecord(input) && isRecord(input.data) ? input.data : {};
209
+ const tag = typeof raw.tag === "string" && raw.tag !== "" ? raw.tag : "notice";
210
+ const message = typeof raw.message === "string" ? raw.message : "";
211
+ return {
212
+ tag,
213
+ color: isNoticeColor(raw.color) ? raw.color : "muted",
214
+ level: isNoticeLevel(raw.level) ? raw.level : "info",
215
+ message,
216
+ textColor: isNoticeColor(raw.textColor) ? raw.textColor : undefined,
217
+ details: readDetails(raw.details),
218
+ };
219
+ }
220
+
221
+ /** 只保留非空字符串细节行,避免渲染出空气泡。 */
222
+ function readDetails(value: unknown): string[] | undefined {
223
+ if (!Array.isArray(value)) return undefined;
224
+ const lines = value.filter((line): line is string => typeof line === "string" && line.trim() !== "");
225
+ return lines.length > 0 ? lines : undefined;
226
+ }
227
+
228
+ /** 判断渲染器是否处于展开状态(Ctrl+O);缺省视为收起。 */
229
+ function isExpanded(options: unknown): boolean {
230
+ return isRecord(options) && options.expanded === true;
231
+ }
232
+
233
+ /**
234
+ * 把一个提示条目渲染成带底色的消息块。
235
+ *
236
+ * 默认只占一行(上下不加空白),避免提示刷屏;细节行只在展开(Ctrl+O)时追加。
237
+ * 这里直接构造 pi-tui 的 Box/Text:带底色消息块的排版(整块铺底色、按宽度换行)
238
+ * 由 pi-tui 提供,Pi 自带的扩展消息渲染也是同样写法,属于有意为之的绑定。
239
+ */
240
+ export function renderNoticeEntry(
241
+ input: unknown,
242
+ theme: NoticeEntryTheme,
243
+ expanded = false,
244
+ ): Component {
245
+ const data = readNoticeEntryData(input);
246
+ const label = theme.fg(data.color, `[${data.tag}]`);
247
+ const body = theme.fg(noticeBodyColor(data.level, data.textColor), data.message);
248
+ const box = new Box(NOTICE_PADDING_X, NOTICE_PADDING_Y, (text) => theme.bg(NOTICE_BACKGROUND_COLOR, text));
249
+ box.addChild(new Text(`${label}${TAG_SEPARATOR}${body}`, 0, 0));
250
+ if (expanded && data.details !== undefined) {
251
+ for (const line of data.details) {
252
+ box.addChild(new Text(theme.fg("dim", line), 0, 0));
253
+ }
254
+ }
255
+ return box;
256
+ }
257
+
75
258
  /**
76
259
  * 给提示文本加上来源标签与颜色。
77
260
  * 非 TUI 模式或没有主题时返回纯文本,避免把 ANSI 序列转发给前端。
@@ -84,11 +267,30 @@ export function formatNotice(options: NoticeRenderOptions): string {
84
267
  }
85
268
 
86
269
  /**
87
- * 统一的提示出口:加来源标签后交给 Pi 的 notify。
88
- * 级别仍由调用方指定,保证 warning/error 依然带 Pi 自带的黄色/红色前缀。
270
+ * 统一的提示出口。
271
+ *
272
+ * TUI:写一条自定义条目,由 registerEntryRenderer 画成带底色的消息块。
273
+ * 其它模式(RPC/print/json):仍走 ui.notify,行为与改造前一致。
274
+ * 条目写入失败时退回 ui.notify,保证提示不会因为渲染方式而丢失。
89
275
  */
90
276
  export function notifyWithSource(options: NoticeSendOptions): void {
91
- const { ctx, source, level, message } = options;
277
+ const { ctx, source, level, message, textColor, details } = options;
278
+ if (ctx.mode === NOTICE_COLOR_MODE && noticeApi !== undefined) {
279
+ const data: NoticeEntryData = {
280
+ tag: source.tag,
281
+ color: source.color,
282
+ level,
283
+ message,
284
+ textColor,
285
+ details,
286
+ };
287
+ try {
288
+ noticeApi.appendEntry(NOTICE_ENTRY_TYPE, data);
289
+ return;
290
+ } catch {
291
+ // 落到下面的 ui.notify:提示照常可见,只是没有底色。
292
+ }
293
+ }
92
294
  const text = formatNotice({ source, message, mode: ctx.mode, theme: ctx.ui.theme });
93
295
  ctx.ui.notify(text, level);
94
296
  }