@tbox.cn/app-template-agent 0.2.0 → 0.7.5

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (81) hide show
  1. package/.gitignore +6 -0
  2. package/.tbox/app.json +12 -3
  3. package/AGENTS.md +17 -6
  4. package/ENV.md +36 -5
  5. package/README.md +23 -7
  6. package/apps/client/package.json +3 -2
  7. package/apps/client/src/app.tsx +28 -0
  8. package/apps/client/src/cards/CardRenderer.tsx +4 -5
  9. package/apps/client/src/components/ActionBar/DebugButton.tsx +5 -1
  10. package/apps/client/src/components/ActionBar/index.tsx +7 -1
  11. package/apps/client/src/components/ChatInput/index.less +54 -0
  12. package/apps/client/src/components/ChatInput/index.tsx +33 -9
  13. package/apps/client/src/components/ChatInput/voice/useVoicePress.ts +20 -1
  14. package/apps/client/src/components/MessageBubble/index.less +12 -1
  15. package/apps/client/src/components/MessageBubble/index.tsx +16 -8
  16. package/apps/client/src/components/PullDownIndicator/index.less +1 -1
  17. package/apps/client/src/components/PullDownIndicator/index.tsx +3 -4
  18. package/apps/client/src/components/ToolCallBlock/index.less +29 -0
  19. package/apps/client/src/components/ToolCallBlock/index.tsx +58 -24
  20. package/apps/client/src/hooks/useMediaUpload.ts +7 -2
  21. package/apps/client/src/main.tsx +4 -1
  22. package/apps/client/src/pages/index/index.tsx +35 -13
  23. package/apps/client/src/platform/AgentChatApp.tsx +5 -1
  24. package/apps/client/src/store/chat-config.ts +12 -1
  25. package/apps/client/src/store/chat.ts +62 -92
  26. package/apps/client/vite.config.ts +46 -26
  27. package/apps/server/package.json +5 -11
  28. package/apps/server/src/app.ts +452 -0
  29. package/apps/server/src/cli/collect-registry.ts +7 -6
  30. package/apps/server/src/config/deployment.ts +36 -70
  31. package/apps/server/src/config/integration.ts +81 -0
  32. package/apps/server/src/dev.ts +81 -0
  33. package/apps/server/src/index.ts +23 -376
  34. package/apps/server/src/intent-routes.ts +11 -4
  35. package/apps/server/src/modules.ts +3 -0
  36. package/apps/server/tests/cards-registry.test.ts +9 -17
  37. package/apps/server/tests/deployment-loader.test.ts +140 -0
  38. package/apps/server/tests/intent-dispatcher.test.ts +58 -0
  39. package/apps/server/tests/tool-runtime.test.ts +97 -23
  40. package/apps/server/tsup.config.ts +15 -2
  41. package/config/chat.json +4 -1
  42. package/config/llm_provider.json +3 -1
  43. package/package.json +19 -11
  44. package/packages/contracts-mall/package.json +30 -0
  45. package/packages/contracts-mall/src/index.ts +1 -0
  46. package/packages/contracts-mall/src/runtime.ts +1 -0
  47. package/packages/contracts-mall/src/server/index.ts +1 -0
  48. package/packages/contracts-mall/tsconfig.json +7 -0
  49. package/packages/module-weather/package.json +4 -4
  50. package/packages/module-weather/skills/weather-guide/SKILL.md +3 -0
  51. package/packages/module-weather/src/server/cards/weather/meta.ts +0 -1
  52. package/packages/module-weather/src/server/index.ts +1 -0
  53. package/packages/module-weather/src/server/tool.ts +24 -24
  54. package/packages/provider-custom/AGENTS.md +33 -0
  55. package/packages/provider-custom/LICENSE +21 -0
  56. package/packages/provider-custom/README.md +8 -0
  57. package/packages/provider-custom/package.json +62 -0
  58. package/packages/provider-custom/src/index.ts +5 -0
  59. package/packages/provider-custom/src/server/index.ts +40 -0
  60. package/packages/provider-custom/tbox.module.json +29 -0
  61. package/packages/provider-custom/tests/provider-custom.test.ts +17 -0
  62. package/packages/provider-custom/tsconfig.json +7 -0
  63. package/packages/provider-custom/tsup.config.ts +18 -0
  64. package/pnpm-lock.template.yaml +6297 -0
  65. package/pnpm-workspace.template.yaml +45 -46
  66. package/scripts/build.sh +6 -12
  67. package/scripts/check-workspace.mjs +11 -10
  68. package/scripts/stop.sh +48 -14
  69. package/scripts/verify-generated.mjs +4 -4
  70. package/tsconfig.base.json +1 -0
  71. package/apps/client/babel-plugins/code-inspect.cjs +0 -110
  72. package/apps/client/src/hooks/usePullDownHistory.ts +0 -97
  73. package/apps/server/skills/shopping-guide-demo/SKILL.md +0 -21
  74. package/apps/server/skills/shopping-guide-demo/references/guide/parking.md +0 -11
  75. package/apps/server/skills/shopping-guide-demo/references/mall-directory.md +0 -17
  76. package/apps/server/skills/shopping-guide-demo/references/promotion.md +0 -10
  77. package/apps/server/src/config/feature-gate.ts +0 -17
  78. package/apps/server/tests/feature-gate.test.ts +0 -30
  79. package/config/deployments/development.json +0 -27
  80. package/scripts/check-inspector.sh +0 -91
  81. /package/packages/module-weather/{tbox.component.json → tbox.module.json} +0 -0
@@ -4,12 +4,15 @@
4
4
  * 共用同一份 set/get,避免双 store 断裂。
5
5
  */
6
6
  import { create } from 'zustand';
7
- import { createCoreChatState, getWsUrl, configureHelloContext, configureWsScopeErrorHandler } from '@tbox.cn/app-sdk/client';
7
+ import { createLogger } from '@tbox.cn/app-sdk/client';
8
+
9
+ const conversationLogger = createLogger('CONVERSATION');
10
+ const historyLogger = createLogger('HISTORY');
11
+ const feedbackLogger = createLogger('FEEDBACK');
12
+ import { createCoreChatState, getWsUrl, configureHelloContext } from '@tbox.cn/app-sdk/client';
8
13
  import type { CoreChatState } from '@tbox.cn/app-sdk/client';
9
- import { resolveHistoryMessage, buildHistoryToolCalls } from '@tbox.cn/app-sdk/client';
10
- import type { CardSnapshot, TboxCardPayload } from '@tbox.cn/app-contracts';
11
- import type { SkillRecord } from '@tbox.cn/app-contracts';
12
- import type { PreToolRecord } from '@tbox.cn/app-contracts';
14
+ import { convertHistoryMessages } from '@tbox.cn/app-sdk/client';
15
+ import type { PlatformHistoryMessage } from '@tbox.cn/app-sdk/client';
13
16
  import type { ChatMessage } from '@/types/chat';
14
17
  import type { IFeedbackAdapter } from '@tbox.cn/app-sdk/client';
15
18
  import type { AdapterContextValue } from '@tbox.cn/app-sdk/client';
@@ -17,78 +20,23 @@ import { isSessionExpiredError } from '@/utils/session-error';
17
20
  import { toast } from '@/utils/toast';
18
21
  import { authClient, platformApi } from '@/platform/agent-runtime';
19
22
 
20
- interface HistoryMessageInfo {
21
- messageId?: string;
22
- query?: string;
23
- answer?: string;
24
- content?: string;
25
- outerBusinessId?: string;
26
- createTime?: number;
27
- multiModalInputs?: Record<string, unknown>;
28
- /** P3 W1:服务端已恢复(migrate + isHistory=true)的卡片快照 */
29
- cards?: CardSnapshot[];
30
- /** 009 D10:服务端已恢复的 skill 加载记录(inputs.skills) */
31
- skills?: SkillRecord[];
32
- /** 010:服务端已恢复的前置工具记录(inputs.preTools) */
33
- preTools?: PreToolRecord[];
34
- }
35
-
36
- function convertHistoryMessages(messages: HistoryMessageInfo[]): ChatMessage[] {
37
- let idSeq = 0;
38
- const nextId = (prefix: string) => `${prefix}_${Date.now()}_${idSeq++}`;
39
- const result: ChatMessage[] = [];
40
- for (const info of messages) {
41
- // 009 D10 + 010:skill/preTool 双表达组合(B1 结构化;skill B2 query 解析补齐)
42
- // —— 010 下沉 S4:解析与组合规则在 SDK,模板只消费组合入口
43
- const resolved = resolveHistoryMessage(info);
44
- if (info.query) {
45
- const userMsg: ChatMessage = {
46
- id: `history-user-${info.messageId}`,
47
- role: 'user',
48
- content: resolved.content,
49
- status: 'done',
50
- createdAt: info.createTime || Date.now(),
51
- ...(info.outerBusinessId && { requestId: info.outerBusinessId }),
52
- };
53
- const imageUrls = info.multiModalInputs?.imageUrls as string[] | undefined;
54
- if (imageUrls && imageUrls.length > 0) {
55
- userMsg.mediaItems = imageUrls.map((url) => ({
56
- id: nextId('history-img'),
57
- type: 'image' as const,
58
- src: url,
59
- }));
60
- }
61
- result.push(userMsg);
62
- }
63
- if (info.answer || info.content) {
64
- result.push({
65
- id: `history-asst-${info.messageId}`,
66
- role: 'assistant',
67
- content: info.answer || info.content || '',
68
- status: 'done',
69
- createdAt: info.createTime || Date.now(),
70
- ...(info.outerBusinessId && { requestId: info.outerBusinessId }),
71
- // 009 D10 + 010:skill/preTool 加载标记挂在 user 消息后的 assistant 消息(ToolCallBlock 渲染)
72
- ...(resolved.skills.length > 0 || resolved.preTools.length > 0
73
- ? {
74
- toolCalls: buildHistoryToolCalls(
75
- resolved,
76
- info.messageId ?? nextId('history-skill'),
77
- ),
78
- }
79
- : {}),
80
- });
81
- }
82
- }
83
- return result;
84
- }
23
+ // ── 历史加载代际标志(模块级):newConversation 递增作废在途 loadMoreHistory(stale 响应整段丢弃)──
24
+ let historyEpoch = 0;
25
+ // ── 新建会话 in-flight 标志(模块级):双击「新对话」防双建会话 ──
26
+ let newConversationInFlight = false;
27
+ // ── 终态阻断重试 in-flight 标志(模块级):防并发重试 ──
28
+ let retryBootstrapInFlight: Promise<void> | null = null;
85
29
 
86
30
  interface ChatState extends CoreChatState {
87
31
  historyMessages: ChatMessage[];
88
32
  historyHasMore: boolean;
89
33
  isLoadingHistory: boolean;
90
34
  initConversation: () => Promise<void>;
35
+ /** 新建会话:清会话态(连接复用)+ 重置历史态 + create 新会话 */
36
+ newConversation: () => Promise<void>;
91
37
  loadMoreHistory: () => Promise<void>;
38
+ /** 终态阻断后重试:清匿名降级缓存 → 拆除连接 → 重走 initConversation(重复点击防抖) */
39
+ retryBootstrap: () => Promise<void>;
92
40
  submitFeedback: (
93
41
  messageId: string,
94
42
  type: 'like' | 'dislike' | 'cancel',
@@ -115,28 +63,58 @@ export const useChatStore = create<ChatState>((set, get) => ({
115
63
  let conversationId: string | null = null;
116
64
  // 006:create 的 userId 只来自 token 身份(body 废弃);匿名(无 token)不可建会话 → 直接走 no-conv 分支
117
65
  if (!authClient.getAuthState()?.token) {
118
- console.warn('[CONVERSATION] anonymous: skip create conversation (no-conv)');
66
+ conversationLogger.warn('anonymous: skip create conversation (no-conv)');
119
67
  } else {
120
68
  for (let i = 0; i < 2 && !conversationId; i++) {
121
69
  try {
122
70
  const { status, data } = await platformApi.createConversation();
123
71
  if (status === 200 && data?.conversationId) conversationId = data.conversationId;
124
- } catch {
125
- // retry
72
+ } catch (e) {
73
+ conversationLogger.warn(`createConversation 失败(重试 ${i + 1}/2):err=${(e as Error).message}`);
126
74
  }
127
75
  }
128
76
  }
129
77
  if (conversationId) set({ conversationId });
130
78
  get().init();
131
79
  } catch (err) {
132
- console.error('[CONVERSATION] init error:', err);
80
+ conversationLogger.error('init error:', err);
133
81
  get().init();
134
82
  }
135
83
  },
136
84
 
85
+ // 终态阻断(登录失败/scope 拒绝)后重试:清匿名降级缓存(不清则必复败)→ 拆连接 → 重走完整引导
86
+ retryBootstrap: () => {
87
+ if (retryBootstrapInFlight) return retryBootstrapInFlight; // 并发重试防抖
88
+ const task = (async () => {
89
+ authClient.clearAuth(); // 匿名降级结果被缓存,不清则重试必然复败
90
+ get().destroy();
91
+ await get().initConversation();
92
+ })();
93
+ const tracked = task.finally(() => {
94
+ if (retryBootstrapInFlight === tracked) retryBootstrapInFlight = null;
95
+ });
96
+ retryBootstrapInFlight = tracked;
97
+ return tracked;
98
+ },
99
+
100
+ // 新建会话:连接复用(clearChat 不断连——连接/身份是身份域),create 拿新会话键
101
+ newConversation: async () => {
102
+ if (newConversationInFlight) return; // 双击防双建会话
103
+ newConversationInFlight = true;
104
+ try {
105
+ historyEpoch++; // 作废在途历史加载(stale 响应整段丢弃,含 isLoadingHistory 复位)
106
+ get().clearChat(); // SDK:内聚取消运行中 run + 清会话态
107
+ set({ historyMessages: [], historyHasMore: true, isLoadingHistory: false });
108
+ await get().initConversation(); // create 新会话;init 对 OPEN 连接幂等早返
109
+ } finally {
110
+ newConversationInFlight = false;
111
+ }
112
+ },
113
+
137
114
  loadMoreHistory: async () => {
138
115
  const { isLoadingHistory, historyHasMore, historyMessages } = get();
139
116
  if (isLoadingHistory || !historyHasMore) return;
117
+ const epoch = historyEpoch; // 快照:newConversation 递增后本响应作废
140
118
  set({ isLoadingHistory: true });
141
119
 
142
120
  const pageSize = 5;
@@ -154,22 +132,16 @@ export const useChatStore = create<ChatState>((set, get) => ({
154
132
  : { userId: get().userId }),
155
133
  ...(earliestRequestId ? { beforeId: earliestRequestId } : {}),
156
134
  });
135
+ // stale 响应(newConversation 已重置态):整段丢弃——含 isLoadingHistory 复位也不落
136
+ if (epoch !== historyEpoch) return;
157
137
  if (status === 200 && data) {
158
- const rawList: HistoryMessageInfo[] = (data.data as HistoryMessageInfo[]) || [];
138
+ const rawList: PlatformHistoryMessage[] = (data.data as PlatformHistoryMessage[]) || [];
159
139
  const currentRequestIds = new Set(get().messages.map((m) => m.requestId).filter(Boolean));
160
140
  const filtered = rawList.filter(
161
141
  (item) => !item.outerBusinessId || !currentRequestIds.has(item.outerBusinessId),
162
142
  );
163
- const newMessages = convertHistoryMessages(filtered.reverse());
164
- // P3 W1:历史卡片按 messageId 挂载到 assistant 气泡(isHistory 已由服务端标记)
165
- const cardsByMessageId: Record<string, TboxCardPayload[]> = {};
166
- for (const info of filtered) {
167
- if (!info.messageId || !info.cards || info.cards.length === 0) continue;
168
- cardsByMessageId[`history-asst-${info.messageId}`] = info.cards.map((s) => ({
169
- ...s,
170
- isHistory: true,
171
- }));
172
- }
143
+ // SDK 单真源:消息对 + 历史卡片桶同 key 派生(平台 id 字段归一;isHistory 已由服务端标记)
144
+ const { messages: newMessages, cardsByMessageId } = convertHistoryMessages(filtered.reverse());
173
145
  set((s) => ({
174
146
  historyMessages: [...newMessages, ...s.historyMessages],
175
147
  historyHasMore: rawList.length >= pageSize,
@@ -180,7 +152,8 @@ export const useChatStore = create<ChatState>((set, get) => ({
180
152
  set({ isLoadingHistory: false });
181
153
  }
182
154
  } catch (err) {
183
- console.error('[HISTORY] load more error:', err);
155
+ historyLogger.error('load more error:', err);
156
+ if (epoch !== historyEpoch) return; // stale:newConversation 已重置 isLoadingHistory
184
157
  set({ isLoadingHistory: false });
185
158
  }
186
159
  },
@@ -190,7 +163,7 @@ export const useChatStore = create<ChatState>((set, get) => ({
190
163
  const oldFeedbackState = oldMsg?.feedbackState ?? 'none';
191
164
  const feedbackRequestId = oldMsg?.requestId;
192
165
  if (!feedbackRequestId) {
193
- console.warn('[FEEDBACK] no requestId for message', messageId);
166
+ feedbackLogger.warn('no requestId for message', messageId);
194
167
  return;
195
168
  }
196
169
  const newFeedbackState = type === 'cancel' ? 'none' : type;
@@ -210,7 +183,7 @@ export const useChatStore = create<ChatState>((set, get) => ({
210
183
  return;
211
184
  }
212
185
  } catch (retryErr) {
213
- console.error('[FEEDBACK] retry failed:', retryErr);
186
+ feedbackLogger.error('retry failed:', retryErr);
214
187
  }
215
188
  }
216
189
  get().updateMessage(messageId, { feedbackState: oldFeedbackState });
@@ -226,7 +199,4 @@ configureHelloContext(() => {
226
199
  return { mall: { mallId, ...(miniAppId ? { miniAppId } : {}) } };
227
200
  });
228
201
 
229
- // 011 评审 7.2:4002 scope 拒绝 → 停止重连 + 轻提示(L4「切 mall = 新会话 + 轻提示」UI 端接线)
230
- configureWsScopeErrorHandler((message) => {
231
- toast(message);
232
- });
202
+ // 4002 scope 拒绝 / 4001 鉴权耗尽的终态提示与禁用由 SDK 统一出站(chat-store configureWsFatalCloseHandler)
@@ -1,11 +1,12 @@
1
- import { defineConfig } from 'vite';
1
+ import { defineConfig, type Plugin } from 'vite';
2
2
  import react from '@vitejs/plugin-react';
3
3
  import pxToRem from 'postcss-pxtorem';
4
+ import { existsSync } from 'node:fs';
4
5
  import { resolve } from 'node:path';
5
- // 平台微调配套 babel plugin:给每个 JSX 注入 data-code-inspect-* 源码位置属性,
6
- // 供 inspector runtime(/__vs_inspector__.js) 点选元素→定位源码。仅 dev 启用(见下)。
7
- // @ts-expect-error 平台 babel plugin(CJS、无类型声明),运行时由 esbuild 解析。
8
- import codeInspectPlugin from './babel-plugins/code-inspect.cjs';
6
+ import { pathToFileURL } from 'node:url';
7
+ // 平台微调配套 babel plugin(随 @tbox.cn/app-sdk 分发):给每个 JSX 注入 data-code-inspect-*
8
+ // 源码位置属性,供 inspector runtime(/__vs_inspector__.js) 点选元素→定位源码。仅 dev 启用(见下)。
9
+ import codeInspectPlugin from '@tbox.cn/app-sdk/platform/code-inspect';
9
10
 
10
11
  const pxToRemPlugin = pxToRem({
11
12
  rootValue: 100,
@@ -14,27 +15,46 @@ const pxToRemPlugin = pxToRem({
14
15
  selectorBlackList: ['.no-rem'],
15
16
  minPixelValue: 1,
16
17
  mediaQuery: false,
17
- exclude: /node_modules/,
18
+ // 0.3.2/0.2.4 D3:排除 node_modules 内三方样式,但放行 pnpm 布局下的 @tbox.cn 模块 CSS。
19
+ // pxtorem 6.1.0 regex 语义为全路径任意 match(源码 index.js:131-140);五形态推演:
20
+ // 1) pnpm 装 @tbox.cn 模块(.pnpm/@tbox.cn+x/node_modules/@tbox.cn/x/...)→ 转换 ✓
21
+ // 2) pnpm 装三方(antd-mobile 等)→ 排除 ✓
22
+ // 3) npm flat 装 @tbox.cn(node_modules/@tbox.cn/x)→ 转换 ✓
23
+ // 4) npm flat 装三方 → 排除 ✓
24
+ // 5) dev 软链(Vite 解析真实路径,无 node_modules 前缀)→ 转换 ✓
25
+ exclude: /node_modules\/(?!\.pnpm|@tbox\.cn)/,
18
26
  });
19
27
 
20
- export default defineConfig(({ command }) => ({
21
- plugins: [
22
- react({
23
- babel: { plugins: command === 'serve' ? [codeInspectPlugin] : [] },
24
- }),
25
- ],
26
- resolve: {
27
- alias: {
28
- '@': resolve(__dirname, 'src'),
29
- fs: resolve(__dirname, 'src/shims/node-stub.ts'),
30
- path: resolve(__dirname, 'src/shims/node-stub.ts'),
28
+ export default defineConfig(async ({ command }) => {
29
+ // tbox-app dev 探测行(dev 探针常量;构件区在位时启用原地装配改指,缺席零行为变化。
30
+ // guard 断言本标记在场——重定位需同步 cli 探针与 guard 两处)
31
+ // __dirname 基准 = apps/client:构件区在模板根(上一级×2)
32
+ const devOverridePath = resolve(__dirname, '../../.tbox-dev.local/vite-override.mjs');
33
+ const devPlugins: Plugin[] = [];
34
+ if (existsSync(devOverridePath)) {
35
+ const { default: tboxDevOverride } = await import(pathToFileURL(devOverridePath).href);
36
+ devPlugins.push(tboxDevOverride());
37
+ }
38
+ return {
39
+ plugins: [
40
+ react({
41
+ babel: { plugins: command === 'serve' ? [codeInspectPlugin] : [] },
42
+ }),
43
+ ...devPlugins,
44
+ ],
45
+ resolve: {
46
+ alias: {
47
+ '@': resolve(__dirname, 'src'),
48
+ fs: resolve(__dirname, 'src/shims/node-stub.ts'),
49
+ path: resolve(__dirname, 'src/shims/node-stub.ts'),
50
+ },
31
51
  },
32
- },
33
- css: {
34
- postcss: { plugins: [pxToRemPlugin] },
35
- },
36
- build: {
37
- rollupOptions: { input: { index: resolve(__dirname, 'index.html') } },
38
- },
39
- // middlewareMode 下不需要 server 配置(由 server 端 vite middleware 托管)
40
- }));
52
+ css: {
53
+ postcss: { plugins: [pxToRemPlugin] },
54
+ },
55
+ build: {
56
+ rollupOptions: { input: { index: resolve(__dirname, 'index.html') } },
57
+ },
58
+ // middlewareMode 下不需要 server 配置(由 server 端 vite middleware 托管)
59
+ };
60
+ });
@@ -4,7 +4,7 @@
4
4
  "private": true,
5
5
  "type": "module",
6
6
  "scripts": {
7
- "dev": "tsx watch --exclude ../../apps/client src/index.ts",
7
+ "dev": "tsx watch --exclude ../../apps/client src/dev.ts",
8
8
  "build": "tsup",
9
9
  "start": "node dist/index.js",
10
10
  "test": "vitest run",
@@ -12,30 +12,24 @@
12
12
  },
13
13
  "dependencies": {
14
14
  "@app/module-weather": "workspace:*",
15
- "@ag-ui/core": "catalog:",
16
15
  "@tbox.cn/agent-plugin": "catalog:",
17
16
  "@tbox.cn/app-sdk": "catalog:",
18
17
  "@tbox.cn/app-contracts": "catalog:",
19
- "@tbox.cn/app-contracts-mall": "catalog:",
18
+ "@app/contracts-mall": "workspace:*",
20
19
  "@tbox-dev-js/sdk": "catalog:",
21
- "ai": "catalog:",
22
20
  "cors": "catalog:",
23
21
  "express": "catalog:",
24
- "ws": "catalog:",
25
- "zod": "catalog:"
26
- },
27
- "peerDependencies": {
28
- "vite": "catalog:"
22
+ "zod": "catalog:",
23
+ "@app/provider-custom": "workspace:*"
29
24
  },
30
25
  "devDependencies": {
31
26
  "@types/cors": "catalog:",
32
27
  "@types/express": "catalog:",
33
28
  "@types/node": "catalog:",
34
- "@types/ws": "catalog:",
35
29
  "tsx": "catalog:",
36
30
  "tsup": "catalog:",
37
31
  "typescript": "catalog:",
38
32
  "vite": "catalog:",
39
33
  "vitest": "catalog:"
40
34
  }
41
- }
35
+ }