@tbox.cn/app-template-agent 0.7.5 → 0.9.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/.gitignore +5 -0
- package/.tbox/app.json +9 -1
- package/AGENTS.md +12 -4
- package/ENV.md +105 -21
- package/README.md +40 -38
- package/apps/client/VISUAL_RESTORE.md +11 -11
- package/apps/client/index.html +3 -1
- package/apps/client/package.json +9 -8
- package/apps/client/src/business/App.tsx +4 -4
- package/apps/client/src/business/README.md +5 -11
- package/apps/client/src/cards/registry.ts +23 -8
- package/apps/client/src/main.tsx +36 -2
- package/apps/client/src/modules.ts +7 -4
- package/apps/client/src/pages/index/index.tsx +18 -162
- package/apps/client/src/platform/AgentChatApp.tsx +7 -6
- package/apps/client/src/platform/agent-runtime.ts +5 -0
- package/apps/client/src/store/chat.ts +47 -185
- package/apps/client/src/test/setup.ts +7 -0
- package/apps/client/vite.config.ts +16 -1
- package/apps/server/package.json +0 -3
- package/apps/server/src/app-tools.ts +2 -1
- package/apps/server/src/app.ts +248 -119
- package/apps/server/src/config/integration.ts +2 -6
- package/apps/server/src/dev.ts +0 -1
- package/apps/server/src/intent-routes.ts +3 -3
- package/apps/server/src/modules.ts +4 -4
- package/apps/server/src/pre-tools.ts +4 -3
- package/apps/server/tests/agent-prompt.test.ts +81 -0
- package/apps/server/tests/cards-registry.test.ts +1 -3
- package/apps/server/tests/deployment-loader.test.ts +35 -56
- package/apps/server/tests/tool-runtime.test.ts +25 -61
- package/package.json +5 -4
- package/packages/agent-ui-kit/LICENSE +21 -0
- package/packages/agent-ui-kit/README.md +17 -0
- package/packages/agent-ui-kit/package.json +58 -0
- package/packages/agent-ui-kit/src/ChatPage.less +62 -0
- package/packages/agent-ui-kit/src/ChatPage.tsx +305 -0
- package/packages/agent-ui-kit/src/chat-config.test.ts +82 -0
- package/packages/agent-ui-kit/src/chat-config.ts +62 -0
- package/{apps/client → packages/agent-ui-kit}/src/components/ActionBar/DebugButton.tsx +2 -2
- package/{apps/client → packages/agent-ui-kit}/src/components/ActionBar/index.less +19 -16
- package/{apps/client → packages/agent-ui-kit}/src/components/ActionBar/index.tsx +10 -15
- package/packages/agent-ui-kit/src/components/CardRenderer.tsx +36 -0
- package/packages/agent-ui-kit/src/components/ChatHeader/index.less +148 -0
- package/packages/agent-ui-kit/src/components/ChatHeader/index.test.tsx +74 -0
- package/{apps/client → packages/agent-ui-kit}/src/components/ChatHeader/index.tsx +27 -15
- package/{apps/client → packages/agent-ui-kit}/src/components/ChatInput/index.less +47 -39
- package/packages/agent-ui-kit/src/components/ChatInput/index.test.tsx +156 -0
- package/{apps/client → packages/agent-ui-kit}/src/components/ChatInput/index.tsx +13 -4
- package/{apps/client → packages/agent-ui-kit}/src/components/ChatInput/voice/useVoicePress.ts +2 -2
- package/packages/agent-ui-kit/src/components/HistoryEntry/index.less +49 -0
- package/packages/agent-ui-kit/src/components/HistoryEntry/index.tsx +42 -0
- package/{apps/client → packages/agent-ui-kit}/src/components/MediaList/index.less +3 -3
- package/{apps/client → packages/agent-ui-kit}/src/components/MediaList/index.tsx +6 -6
- package/{apps/client → packages/agent-ui-kit}/src/components/MessageBubble/index.less +25 -20
- package/packages/agent-ui-kit/src/components/MessageBubble/index.test.tsx +112 -0
- package/{apps/client → packages/agent-ui-kit}/src/components/MessageBubble/index.tsx +22 -17
- package/{apps/client → packages/agent-ui-kit}/src/components/PullDownIndicator/index.less +2 -2
- package/{apps/client → packages/agent-ui-kit}/src/components/PullDownIndicator/index.tsx +2 -2
- package/packages/agent-ui-kit/src/components/QuickCommands/index.less +63 -0
- package/packages/agent-ui-kit/src/components/QuickCommands/index.test.tsx +99 -0
- package/packages/agent-ui-kit/src/components/QuickCommands/index.tsx +52 -0
- package/{apps/client → packages/agent-ui-kit}/src/components/ScrollToBottomButton/index.less +1 -1
- package/packages/agent-ui-kit/src/components/ToolCallBlock/detail.test.ts +346 -0
- package/packages/agent-ui-kit/src/components/ToolCallBlock/detail.ts +204 -0
- package/packages/agent-ui-kit/src/components/ToolCallBlock/index.less +312 -0
- package/packages/agent-ui-kit/src/components/ToolCallBlock/index.test.tsx +261 -0
- package/packages/agent-ui-kit/src/components/ToolCallBlock/index.tsx +212 -0
- package/packages/agent-ui-kit/src/components/ToolCallBlock/titles.ts +102 -0
- package/packages/agent-ui-kit/src/hooks/use-drag-gesture.test.ts +66 -0
- package/packages/agent-ui-kit/src/hooks/use-drag-gesture.ts +129 -0
- package/packages/agent-ui-kit/src/hooks/use-pull-down-history.ts +241 -0
- package/packages/agent-ui-kit/src/hooks/use-pull-down-wheel-native.test.ts +64 -0
- package/{apps/client → packages/agent-ui-kit}/src/hooks/useMediaUpload.ts +3 -3
- package/{apps/client → packages/agent-ui-kit}/src/hooks/useTTSAutoPlay.ts +7 -5
- package/packages/agent-ui-kit/src/index.ts +69 -0
- package/packages/agent-ui-kit/src/store-context.tsx +92 -0
- package/packages/agent-ui-kit/src/test/setup.ts +7 -0
- package/packages/agent-ui-kit/src/theme/contract.json +72 -0
- package/packages/agent-ui-kit/src/theme/global.less +99 -0
- package/packages/agent-ui-kit/src/theme/motion.less +19 -0
- package/packages/agent-ui-kit/src/theme/theme-contract.test.ts +69 -0
- package/packages/agent-ui-kit/src/theme/theme.ts +17 -0
- package/packages/agent-ui-kit/src/theme/tokens.less +79 -0
- package/packages/agent-ui-kit/src/utils/haptics.test.ts +51 -0
- package/packages/agent-ui-kit/src/utils/haptics.ts +44 -0
- package/packages/agent-ui-kit/src/utils/pull-down-machine.test.ts +188 -0
- package/packages/agent-ui-kit/src/utils/pull-down-machine.ts +157 -0
- package/packages/agent-ui-kit/src/utils/tool-call-display.test.ts +240 -0
- package/packages/agent-ui-kit/src/utils/tool-call-display.ts +146 -0
- package/packages/agent-ui-kit/src/vite-env.d.ts +1 -0
- package/packages/agent-ui-kit/tsconfig.json +8 -0
- package/packages/agent-ui-kit/vitest.config.ts +7 -0
- package/pnpm-lock.template.yaml +177 -111
- package/pnpm-workspace.template.yaml +12 -4
- package/profiles/mall/config/agent.jsonc +33 -0
- package/profiles/mall/config/chat.json +21 -0
- package/profiles/mall/config/knowledge.json +5 -0
- package/profiles/mall/config/llm_provider.json +5 -0
- package/profiles/mall/config/plugins.json +3 -0
- package/profiles/mall.json +16 -0
- package/scripts/check-workspace.mjs +3 -2
- package/apps/client/assets/copy@2x.png +0 -0
- package/apps/client/assets/debug.png +0 -0
- package/apps/client/assets/dislike@2x.png +0 -0
- package/apps/client/assets/like@2x.png +0 -0
- package/apps/client/assets/speaker@2x.png +0 -0
- package/apps/client/src/cards/CardRenderer.tsx +0 -20
- package/apps/client/src/components/ChatHeader/index.less +0 -104
- package/apps/client/src/components/ErrorBoundary.tsx +0 -2
- package/apps/client/src/components/QuickCommands/index.less +0 -48
- package/apps/client/src/components/QuickCommands/index.tsx +0 -29
- package/apps/client/src/components/ToolCallBlock/index.less +0 -131
- package/apps/client/src/components/ToolCallBlock/index.tsx +0 -125
- package/apps/client/src/examples/tourism/TourismLanding.tsx +0 -96
- package/apps/client/src/examples/tourism/index.less +0 -192
- package/apps/client/src/pages/index/index.less +0 -43
- package/apps/client/src/store/chat-config.ts +0 -40
- package/apps/client/src/styles/global.less +0 -60
- package/apps/client/src/styles/tokens.less +0 -60
- package/apps/server/src/config/deployment.ts +0 -47
- package/packages/module-weather/package.json +0 -36
- package/packages/module-weather/skills/weather-guide/SKILL.md +0 -22
- package/packages/module-weather/skills/weather-guide/references/dressing-tips.md +0 -5
- package/packages/module-weather/src/client/cards/weather/index.less +0 -22
- package/packages/module-weather/src/client/cards/weather/index.tsx +0 -14
- package/packages/module-weather/src/client/index.ts +0 -19
- package/packages/module-weather/src/index.ts +0 -2
- package/packages/module-weather/src/server/cards/weather/meta.ts +0 -27
- package/packages/module-weather/src/server/index.ts +0 -45
- package/packages/module-weather/src/server/pre-tools.ts +0 -20
- package/packages/module-weather/src/server/tool.ts +0 -29
- package/packages/module-weather/tbox.module.json +0 -27
- package/packages/module-weather/tsconfig.json +0 -14
- /package/{apps/client → packages/agent-ui-kit}/assets/keyboard-circle.svg +0 -0
- /package/{apps/client → packages/agent-ui-kit}/assets/more-circle.svg +0 -0
- /package/{apps/client → packages/agent-ui-kit}/assets/new-chat.svg +0 -0
- /package/{apps/client → packages/agent-ui-kit}/assets/quick-command-star.svg +0 -0
- /package/{apps/client → packages/agent-ui-kit}/assets/send-gradient.svg +0 -0
- /package/{apps/client → packages/agent-ui-kit}/assets/stop@2x.png +0 -0
- /package/{apps/client → packages/agent-ui-kit}/assets/voice-circle.svg +0 -0
- /package/{apps/client → packages/agent-ui-kit}/src/components/ChatInput/voice/VoicePressField.tsx +0 -0
- /package/{apps/client → packages/agent-ui-kit}/src/components/ChatInput/voice/VoiceRecordingOverlay.tsx +0 -0
- /package/{apps/client → packages/agent-ui-kit}/src/components/ChatInput/voice/machine.ts +0 -0
- /package/{apps/client → packages/agent-ui-kit}/src/components/ReasoningBlock/index.less +0 -0
- /package/{apps/client → packages/agent-ui-kit}/src/components/ReasoningBlock/index.tsx +0 -0
- /package/{apps/client → packages/agent-ui-kit}/src/components/ScrollToBottomButton/index.tsx +0 -0
- /package/{apps/client/src/types/chat.ts → packages/agent-ui-kit/src/types.ts} +0 -0
- /package/{apps/client → packages/agent-ui-kit}/src/utils/copyText.ts +0 -0
- /package/{apps/client → packages/agent-ui-kit}/src/utils/imagePreview.ts +0 -0
- /package/{apps/client → packages/agent-ui-kit}/src/utils/session-error.ts +0 -0
- /package/{apps/client → packages/agent-ui-kit}/src/utils/toast.ts +0 -0
|
@@ -0,0 +1,81 @@
|
|
|
1
|
+
import { describe, expect, it } from 'vitest';
|
|
2
|
+
import { loadAgentPromptConfig } from '@tbox.cn/app-sdk/server';
|
|
3
|
+
import { existsSync } from 'node:fs';
|
|
4
|
+
import path from 'node:path';
|
|
5
|
+
import { fileURLToPath } from 'node:url';
|
|
6
|
+
|
|
7
|
+
const __dirname = path.dirname(fileURLToPath(import.meta.url));
|
|
8
|
+
|
|
9
|
+
/**
|
|
10
|
+
* agent.jsonc 提示词护栏(经 SDK loader 实读——防配置 typo 静默退化 + 厂商中立锚)。
|
|
11
|
+
* W4 单一模板统一化双段语义:
|
|
12
|
+
* - 缺省出厂 = 空装配(config/ 无 agent.jsonc)→ loader 走 SDK 默认兜底单行(chat 形态零域广告);
|
|
13
|
+
* - mall 画像 = profiles/mall/config/agent.jsonc(--profile mall 经 --config-dir 通道注入)——
|
|
14
|
+
* 四章节/禁工具名/承接句/中立锚断言锚定画像资产文件(create 展开/stage mall 完整档消费)。
|
|
15
|
+
*
|
|
16
|
+
* 部署态让位(2026-09-10):--config-dir 注入真实厂商档(integrations.json root provider 为
|
|
17
|
+
* 真实厂商名而非 mock)时 agent.jsonc 为部署方品牌人设——中立锚仅守卫出厂/mock 态(mock 档
|
|
18
|
+
* 不注入 agent.jsonc,保持出厂中立)。stage 真实厂商档完整链依赖此让位。
|
|
19
|
+
*
|
|
20
|
+
* 装配态感知(2026-09-17 评审修复):第一段的 config/ 状态在生成应用是装配变量——mall 完整档
|
|
21
|
+
* 素材注入、support 自含 config(出厂在场)均使 agent.jsonc 在场,恒常量断言失配(stage
|
|
22
|
+
* 完整档 94/95 缺口)。用例改为形态感知:缺席 → 兜底断言(出厂守卫保持);在场 → 读文件断言。
|
|
23
|
+
*/
|
|
24
|
+
describe('agent.jsonc(兜底语义:空装配 → SDK 默认单行;装配态注入 → 读文件)', () => {
|
|
25
|
+
const cfg = loadAgentPromptConfig(path.resolve(__dirname, '../../../config'));
|
|
26
|
+
const injected = existsSync(path.resolve(__dirname, '../../../config/agent.jsonc'));
|
|
27
|
+
|
|
28
|
+
it('config/ 无 agent.jsonc → loader 兜底单行(零域广告);在场(素材注入/自含)→ 读文件非兜底', () => {
|
|
29
|
+
if (injected) {
|
|
30
|
+
expect(cfg.instructions).not.toContain('你是智能助手,帮助用户完成日常问答与业务事务');
|
|
31
|
+
expect(cfg.instructions).toBeTruthy();
|
|
32
|
+
} else {
|
|
33
|
+
expect(cfg.instructions).toContain('你是智能助手,帮助用户完成日常问答与业务事务');
|
|
34
|
+
}
|
|
35
|
+
});
|
|
36
|
+
});
|
|
37
|
+
|
|
38
|
+
describe('agent.jsonc(mall 画像资产:profiles/mall/config)', () => {
|
|
39
|
+
const MALL_CONFIG = path.resolve(__dirname, '../../../profiles/mall/config');
|
|
40
|
+
const cfg = loadAgentPromptConfig(MALL_CONFIG);
|
|
41
|
+
|
|
42
|
+
it('loader 成功加载(非兜底:不含 SDK 默认单行)', () => {
|
|
43
|
+
expect(cfg.instructions).not.toContain('你是智能助手,帮助用户完成日常问答与业务事务');
|
|
44
|
+
expect(cfg.instructions).toBeTruthy();
|
|
45
|
+
});
|
|
46
|
+
|
|
47
|
+
it('首行身份句(不以标题开头)+ 四章节结构', () => {
|
|
48
|
+
const lines = cfg.instructions.split('\n');
|
|
49
|
+
expect(lines[0].startsWith('你是')).toBe(true);
|
|
50
|
+
for (const section of ['# 服务范围', '# 业务规则', '# 沟通风格', '# 兜底承接']) {
|
|
51
|
+
expect(lines).toContain(section);
|
|
52
|
+
}
|
|
53
|
+
});
|
|
54
|
+
|
|
55
|
+
it('禁工具名护栏与承接句规则在场(正文只出白话与结果)', () => {
|
|
56
|
+
expect(cfg.instructions).toContain('任何回复正文不出现工具名、接口名、字段名');
|
|
57
|
+
expect(cfg.instructions).toContain('需要提及操作时用业务语言');
|
|
58
|
+
expect(cfg.instructions).toContain('先用一句贴合本轮诉求的话承接再发起');
|
|
59
|
+
});
|
|
60
|
+
|
|
61
|
+
it('示例工具名只出现在禁令行,不作为正面示例扩散到其他行', () => {
|
|
62
|
+
const lines = cfg.instructions.split('\n').filter((line) => line.includes('listVehicles') || line.includes('customer-service-faq'));
|
|
63
|
+
expect(lines).toHaveLength(1);
|
|
64
|
+
expect(lines[0]).toContain('不出现工具名');
|
|
65
|
+
});
|
|
66
|
+
|
|
67
|
+
it('pre-tool 定性覆盖实际注入面(FAQ 与碰一碰)', () => {
|
|
68
|
+
expect(cfg.instructions).toContain('客服 FAQ 条目、碰一碰活动详情');
|
|
69
|
+
});
|
|
70
|
+
|
|
71
|
+
it('厂商中立:无品牌人设名(词表字面量零出现;厂商档部署 = 部署方品牌人设让位)', () => {
|
|
72
|
+
// 字面量拼接拆分:guard-vendor-neutrality 正则匹配源码行——拆开后门禁天然不命中,
|
|
73
|
+
// 免行内豁免标记;勿合并为连续字面量(会触发自家门禁)
|
|
74
|
+
expect(cfg.instructions).not.toContain('万' + '小豆');
|
|
75
|
+
});
|
|
76
|
+
|
|
77
|
+
it('memoryInstructions 在场(记忆域措辞随配置)', () => {
|
|
78
|
+
expect(cfg.memoryInstructions).toBeTruthy();
|
|
79
|
+
expect(cfg.memoryInstructions).toContain('长期记忆');
|
|
80
|
+
});
|
|
81
|
+
});
|
|
@@ -1,8 +1,6 @@
|
|
|
1
|
-
// 卡片 / 工具 registry
|
|
2
|
-
// 通过 registerAll(ctx) 收集真实注册状态,替代原手写 registry 静态断言。
|
|
1
|
+
// 卡片 / 工具 registry 一致性守护测试(自注册闭环版,mall 模板)。
|
|
3
2
|
// v2 出卡协议:tool→card 绑定(allowedTools/cardResolver)退场——出卡由工具出参
|
|
4
3
|
// cards 字段运行期声明,registry 仅余 cardType 注册与 dataSchema 校验职责。
|
|
5
|
-
// 注:C 方案下卡片渲染在 client(React 组件),server 无 template.html,故移除原 C12(模板占位符 ∈ schema)。
|
|
6
4
|
import { describe, it, expect } from 'vitest';
|
|
7
5
|
import { diagnosePlugins } from '@tbox.cn/agent-plugin/doctor';
|
|
8
6
|
import { createServerContext, createPluginIntegration } from '@tbox.cn/app-sdk/server';
|
|
@@ -1,14 +1,14 @@
|
|
|
1
1
|
/**
|
|
2
|
-
* integrations
|
|
3
|
-
*
|
|
4
|
-
*
|
|
2
|
+
* integrations 装载链单测(W3-1 收窄:控制平面投影/域装配段随 mall 语义迁
|
|
3
|
+
* modules/module-mall-core/tests/assembly.test.ts——本文件只测模板装载壳)。
|
|
4
|
+
* 覆盖:strict 校验 fail-fast / 占位展开(env 通道)/ defaultInstanceId 引用完整性 /
|
|
5
|
+
* stale 防线(deployments/development.json 含 malls → error 迁移指引)。
|
|
5
6
|
*/
|
|
6
7
|
import { mkdtempSync, mkdirSync, rmSync, writeFileSync } from 'node:fs';
|
|
7
8
|
import { tmpdir } from 'node:os';
|
|
8
9
|
import { join } from 'node:path';
|
|
9
10
|
import { afterAll, describe, expect, it } from 'vitest';
|
|
10
11
|
import { loadIntegrations, assertNoStaleDeploymentControlPlane } from '../src/config/integration.js';
|
|
11
|
-
import { deploymentFromIntegrations, buildDefaultMallContext } from '../src/config/deployment.js';
|
|
12
12
|
|
|
13
13
|
const dir = mkdtempSync(join(tmpdir(), 'tbox-deploy-loader-'));
|
|
14
14
|
afterAll(() => rmSync(dir, { recursive: true, force: true }));
|
|
@@ -19,6 +19,7 @@ function writeConfig(name: string, data: unknown): string {
|
|
|
19
19
|
return p;
|
|
20
20
|
}
|
|
21
21
|
|
|
22
|
+
/** 模拟 real-env 素材形态:root instances 注册表 + defaultInstanceId + services */
|
|
22
23
|
const fullIntegrations = {
|
|
23
24
|
provider: 'acme',
|
|
24
25
|
instances: [
|
|
@@ -36,39 +37,29 @@ const fullIntegrations = {
|
|
|
36
37
|
services: { 'member.account': {} },
|
|
37
38
|
};
|
|
38
39
|
|
|
39
|
-
describe('loadIntegrations
|
|
40
|
-
it('
|
|
40
|
+
describe('loadIntegrations:装载链(strict + 占位展开 + 缺席语义)', () => {
|
|
41
|
+
it('全量素材可载入(config 停留原样;投影消费方 = module-mall-core)', () => {
|
|
41
42
|
const loaded = loadIntegrations(writeConfig('full.json', fullIntegrations));
|
|
42
|
-
expect(loaded?.
|
|
43
|
-
|
|
44
|
-
name: '示例商场',
|
|
45
|
-
miniAppId: 'demo-app',
|
|
46
|
-
contactPhone: '0571-88888888',
|
|
47
|
-
location: { lat: 30.2741, lon: 120.1551, address: '湖滨示范点' },
|
|
48
|
-
});
|
|
49
|
-
expect(loaded?.controlPlane.defaultMallId).toBe('mall-demo');
|
|
43
|
+
expect(loaded?.config.defaultInstanceId).toBe('mall-demo');
|
|
44
|
+
expect(loaded?.source.endsWith('full.json')).toBe(true);
|
|
50
45
|
});
|
|
51
46
|
|
|
52
|
-
it('
|
|
47
|
+
it('attributes 严格校验归投影(W3-1:loader 不过滤——拼错键由 mall-core register 期 fail-fast)', () => {
|
|
53
48
|
const p = writeConfig('typo-attr.json', {
|
|
54
49
|
instances: [{ id: 'm1', name: 'x', attributes: { miniAppid: 'typo' } }],
|
|
55
50
|
services: {},
|
|
56
51
|
});
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
it('无参考表 → 空平面;defaultInstanceId 独立在场 = 动态默认(env 注入形态)', () => {
|
|
61
|
-
const loaded = loadIntegrations(writeConfig('no-table.json', { provider: 'globex', defaultInstanceId: '42020300520', services: {} }));
|
|
62
|
-
expect(loaded?.controlPlane.malls).toEqual([]);
|
|
63
|
-
expect(loaded?.controlPlane.defaultMallId).toBe('42020300520');
|
|
52
|
+
// 装载壳只做展开+parse;投影(含允许键清单 strict)在 module-mall-core 装配期
|
|
53
|
+
// (module tests/assembly.test.ts 覆盖 fail-fast 语义——启动链同点报错)
|
|
54
|
+
expect(loadIntegrations(p)?.config.instances?.length).toBe(1);
|
|
64
55
|
});
|
|
65
56
|
|
|
66
|
-
it('defaultInstanceId 占位 ${DEFAULT_MALL_ID} → 展开后载入;缺 env fail-fast', () => {
|
|
57
|
+
it('defaultInstanceId 占位 ${DEFAULT_MALL_ID} → loader 展开后载入;缺 env fail-fast', () => {
|
|
67
58
|
const p = writeConfig('env-placeholder.json', { defaultInstanceId: '${DEFAULT_MALL_ID}', services: {} });
|
|
68
59
|
const saved = process.env.DEFAULT_MALL_ID;
|
|
69
60
|
process.env.DEFAULT_MALL_ID = '1102588';
|
|
70
61
|
try {
|
|
71
|
-
expect(loadIntegrations(p)?.
|
|
62
|
+
expect(loadIntegrations(p)?.config.defaultInstanceId).toBe('1102588');
|
|
72
63
|
delete process.env.DEFAULT_MALL_ID;
|
|
73
64
|
expect(() => loadIntegrations(p)).toThrow(/DEFAULT_MALL_ID/);
|
|
74
65
|
} finally {
|
|
@@ -76,6 +67,19 @@ describe('loadIntegrations:投影穿透', () => {
|
|
|
76
67
|
else process.env.DEFAULT_MALL_ID = saved;
|
|
77
68
|
}
|
|
78
69
|
});
|
|
70
|
+
|
|
71
|
+
it('defaultInstanceId ∉ instances ids → zod 拒载(SDK 单源防线)', () => {
|
|
72
|
+
const p = writeConfig('bad-default.json', {
|
|
73
|
+
instances: [{ id: 'm1', name: 'x' }],
|
|
74
|
+
defaultInstanceId: '99999',
|
|
75
|
+
services: {},
|
|
76
|
+
});
|
|
77
|
+
expect(() => loadIntegrations(p)).toThrow(/defaultInstanceId/);
|
|
78
|
+
});
|
|
79
|
+
|
|
80
|
+
it('文件缺席 → undefined(槽位解析不启用)', () => {
|
|
81
|
+
expect(loadIntegrations(join(dir, 'absent.json'))).toBeUndefined();
|
|
82
|
+
});
|
|
79
83
|
});
|
|
80
84
|
|
|
81
85
|
describe('stale 防线(v4:deployments/development.json 含 malls → error 迁移指引)', () => {
|
|
@@ -103,38 +107,13 @@ describe('stale 防线(v4:deployments/development.json 含 malls → error
|
|
|
103
107
|
rmSync(root, { recursive: true, force: true });
|
|
104
108
|
}
|
|
105
109
|
});
|
|
106
|
-
});
|
|
107
110
|
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
mall: { mallId: 'mall-demo', miniAppId: 'demo-app' },
|
|
116
|
-
});
|
|
117
|
-
});
|
|
118
|
-
|
|
119
|
-
it('双缺 miniAppId → mallId-only 注入(v4:不再 warn-refuse 静默失效)', () => {
|
|
120
|
-
const loaded = loadIntegrations(writeConfig('ctx-nomini.json', { defaultInstanceId: 'm9', services: {} }))!;
|
|
121
|
-
expect(buildDefaultMallContext(loaded.controlPlane)).toEqual({ mall: { mallId: 'm9' } });
|
|
122
|
-
});
|
|
123
|
-
|
|
124
|
-
it('无 defaultMallId → undefined(不兜底)', () => {
|
|
125
|
-
const loaded = loadIntegrations(writeConfig('ctx-nodefault.json', { services: {} }))!;
|
|
126
|
-
expect(buildDefaultMallContext(loaded.controlPlane)).toBeUndefined();
|
|
127
|
-
});
|
|
128
|
-
});
|
|
129
|
-
|
|
130
|
-
describe('deploymentFromIntegrations:装配活性', () => {
|
|
131
|
-
it('assembly 三函数派生自投影控制平面', () => {
|
|
132
|
-
const loaded = loadIntegrations(writeConfig('asm.json', fullIntegrations))!;
|
|
133
|
-
const d = deploymentFromIntegrations(loaded);
|
|
134
|
-
expect(d.controlPlane).toBe(loaded.controlPlane);
|
|
135
|
-
expect(d.assembly.validateScope({ mall: { mallId: 'mall-demo' } })).toBe(true);
|
|
136
|
-
expect(d.assembly.validateScope({ mall: { miniAppId: 'a' } })).toBe(false);
|
|
137
|
-
expect(d.assembly.extractScopeKey({ identity: { userId: 'u', source: 'external' }, attributes: { mall: { mallId: 'mall-demo' } } })).toBe('mall-demo');
|
|
138
|
-
expect(d.assembly.buildScope({ mall: { mallId: 'mall-demo' } })).toEqual({ mallId: 'mall-demo' });
|
|
111
|
+
it('文件缺席 → 不阻断', () => {
|
|
112
|
+
const root = mkdtempSync(join(tmpdir(), 'tbox-stale-absent-'));
|
|
113
|
+
try {
|
|
114
|
+
expect(() => assertNoStaleDeploymentControlPlane(root)).not.toThrow();
|
|
115
|
+
} finally {
|
|
116
|
+
rmSync(root, { recursive: true, force: true });
|
|
117
|
+
}
|
|
139
118
|
});
|
|
140
119
|
});
|
|
@@ -10,7 +10,7 @@ import { registerAll } from '../src/modules.js';
|
|
|
10
10
|
import { translateTool } from '../src/tools/translate.js';
|
|
11
11
|
|
|
12
12
|
describe('tool runtime 宿主装配(自注册闭环)', () => {
|
|
13
|
-
it('
|
|
13
|
+
it('空装配(mall 模板):无模块工具,app 级工具正常注册', async () => {
|
|
14
14
|
const ctx = createServerContext();
|
|
15
15
|
await registerAll(ctx);
|
|
16
16
|
|
|
@@ -18,31 +18,13 @@ describe('tool runtime 宿主装配(自注册闭环)', () => {
|
|
|
18
18
|
loadPlugins: async () => ({ tools: {}, references: [] }),
|
|
19
19
|
});
|
|
20
20
|
|
|
21
|
-
|
|
21
|
+
// 缺省空装配:模块工具不存在,仅 app 级工具
|
|
22
|
+
expect(Object.keys(runtime.tools)).not.toEqual(
|
|
22
23
|
expect.arrayContaining(['getWeather']),
|
|
23
24
|
);
|
|
24
25
|
});
|
|
25
26
|
|
|
26
|
-
it('
|
|
27
|
-
const ctx = createServerContext();
|
|
28
|
-
await registerAll(ctx);
|
|
29
|
-
|
|
30
|
-
const runtime = await createSdkToolRuntime(ctx, {
|
|
31
|
-
loadPlugins: async () => ({ tools: {}, references: [] }),
|
|
32
|
-
});
|
|
33
|
-
const weather = runtime.tools.getWeather as unknown as {
|
|
34
|
-
execute: (input: { city: string }, context: unknown) => Promise<Record<string, unknown>>;
|
|
35
|
-
};
|
|
36
|
-
const out = await weather.execute({ city: '杭州' }, {} as never);
|
|
37
|
-
// 顶层答话事实
|
|
38
|
-
expect(out).toMatchObject({ city: '杭州', temp: expect.any(Number), weather: expect.any(String) });
|
|
39
|
-
// cards 出卡声明(capture 前原始形态:data = 顶层事实本体,不含 cards 键自身)
|
|
40
|
-
// W7 note 契约:主体+关键事实(城市/天气/温度)
|
|
41
|
-
const { cards, ...facts } = out;
|
|
42
|
-
expect(cards).toEqual([{ cardType: 'weather', data: facts, note: `杭州 ${facts.weather} ${facts.temp}℃` }]);
|
|
43
|
-
});
|
|
44
|
-
|
|
45
|
-
it('未注册的 tool 不进运行时工具集(仅作为模型上下文面之外)', async () => {
|
|
27
|
+
it('未注册的 tool 不进运行时工具集(v2:无 cardResolver——出卡由工具出参 cards 运行期声明)', async () => {
|
|
46
28
|
const ctx = createServerContext();
|
|
47
29
|
await registerAll(ctx);
|
|
48
30
|
|
|
@@ -81,45 +63,30 @@ describe('tool runtime 宿主装配(自注册闭环)', () => {
|
|
|
81
63
|
});
|
|
82
64
|
});
|
|
83
65
|
|
|
84
|
-
describe('长期记忆装配(017
|
|
66
|
+
describe('长期记忆装配(017:mall 域默认 inmemory + mallId 隔离 scope)', () => {
|
|
85
67
|
it('resolveMemoryStore 缺省 → inmemory(默认开启)', () => {
|
|
86
68
|
const m = resolveMemoryStore({});
|
|
87
69
|
expect(m.enabled).toBe(true);
|
|
88
70
|
expect(m.mode).toBe('inmemory');
|
|
89
71
|
expect(m.store).toBeDefined();
|
|
90
|
-
expect(m.store!.capabilities).toBeUndefined(); // 缺省全真
|
|
91
72
|
});
|
|
92
73
|
|
|
93
|
-
it('resolveMemoryStore off → 关闭(enabled=false
|
|
74
|
+
it('resolveMemoryStore off → 关闭(enabled=false,路由面与工具零装配)', () => {
|
|
94
75
|
const m = resolveMemoryStore({ store: 'off' });
|
|
95
76
|
expect(m.enabled).toBe(false);
|
|
96
77
|
expect(m.mode).toBe('off');
|
|
97
78
|
expect(m.store).toBeUndefined();
|
|
98
79
|
});
|
|
99
80
|
|
|
100
|
-
it('resolveMemoryStore knowledge
|
|
101
|
-
|
|
102
|
-
expect(
|
|
103
|
-
expect(m.mode).toBe('inmemory');
|
|
104
|
-
});
|
|
105
|
-
|
|
106
|
-
it('resolveMemoryStore knowledge 三件套齐备 → knowledge 模式(2026-09-01 翻转:caps 缺省全 true——平台已发布生产)', () => {
|
|
81
|
+
it('resolveMemoryStore knowledge 三缺一回退;齐备 → knowledge(caps 缺省全 true——2026-09-01 翻转)', () => {
|
|
82
|
+
expect(resolveMemoryStore({ store: 'knowledge' }).mode).toBe('inmemory');
|
|
83
|
+
expect(resolveMemoryStore({ store: 'knowledge', kbId: 'kb-1', apiKey: 'k' }).mode).toBe('inmemory');
|
|
107
84
|
const m = resolveMemoryStore({ store: 'knowledge', kbId: 'kb-1', kbDocId: 'doc-1', apiKey: 'k' });
|
|
108
85
|
expect(m.mode).toBe('knowledge');
|
|
109
86
|
expect(m.store!.capabilities).toEqual({ save: true, search: true, list: true, delete: true });
|
|
110
87
|
});
|
|
111
88
|
|
|
112
|
-
it('
|
|
113
|
-
const m = resolveMemoryStore({ store: 'knowledge', kbId: 'kb-1', apiKey: 'k' });
|
|
114
|
-
expect(m.mode).toBe('inmemory');
|
|
115
|
-
});
|
|
116
|
-
|
|
117
|
-
it('resolveMemoryStore 非法值 → warn 回退 inmemory', () => {
|
|
118
|
-
const m = resolveMemoryStore({ store: 'bogus' });
|
|
119
|
-
expect(m.mode).toBe('inmemory');
|
|
120
|
-
});
|
|
121
|
-
|
|
122
|
-
it('装配态(inmemory)→ 引擎装配链路无异常(chat 完成 + 本地 skills 不受影响)', async () => {
|
|
89
|
+
it('装配态(inmemory)→ 引擎装配链路无异常 + 本地 skills 不受影响', async () => {
|
|
123
90
|
const ctx = createServerContext();
|
|
124
91
|
await registerAll(ctx);
|
|
125
92
|
const engine = await createEngineFromContext(ctx, {
|
|
@@ -127,25 +94,22 @@ describe('长期记忆装配(017:默认 inmemory 开启 + off 逃生舱)',
|
|
|
127
94
|
model: {} as never,
|
|
128
95
|
memory: { store: createInMemoryMemoryStore() },
|
|
129
96
|
});
|
|
130
|
-
// 触发一轮 chat 使装配链路完整执行(无 runCtx → 匿名 → 零 recall
|
|
131
|
-
|
|
132
|
-
//
|
|
133
|
-
|
|
97
|
+
// 触发一轮 chat 使装配链路完整执行(无 runCtx → 匿名 → 零 recall)。
|
|
98
|
+
// skills/preTools 面不做形状断言:模板源空装配为 [],生成应用(module add 后)非空——
|
|
99
|
+
// 属装配环境差异而非记忆特性;本用例只锁记忆装配链路无异常(chat 完成即通过)。
|
|
100
|
+
await engine.chat('t-mem-mall', 'hi', () => {});
|
|
134
101
|
});
|
|
135
102
|
|
|
136
|
-
it('
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
const
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
{
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
);
|
|
148
|
-
await engine.chat('t-mem-off', 'hi', () => {});
|
|
149
|
-
expect(m.enabled).toBe(false);
|
|
103
|
+
it('mall 域 resolveScope 语义:attributes.mall → { mallId };无 mall → undefined(用户级全局)', () => {
|
|
104
|
+
// resolveScope 闭包形态对齐模板装配(buildScope 单点);此处按语义直测:
|
|
105
|
+
// 模板内 resolveScope = buildScope(attributes) 取 mallId 单键
|
|
106
|
+
const resolveScope = (attributes: Record<string, unknown>) => {
|
|
107
|
+
const raw = attributes?.mall as { mallId?: unknown } | undefined;
|
|
108
|
+
const mallId = typeof raw?.mallId === 'string' && raw.mallId ? raw.mallId : undefined;
|
|
109
|
+
return mallId ? { mallId } : undefined;
|
|
110
|
+
};
|
|
111
|
+
expect(resolveScope({ mall: { mallId: 'mall-demo', miniAppId: 'demo-app' } })).toEqual({ mallId: 'mall-demo' });
|
|
112
|
+
expect(resolveScope({})).toBeUndefined();
|
|
113
|
+
expect(resolveScope({ mall: { mallId: '' } })).toBeUndefined();
|
|
150
114
|
});
|
|
151
115
|
});
|
package/package.json
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@tbox.cn/app-template-agent",
|
|
3
|
-
"version": "0.
|
|
4
|
-
"description": "tbox AI Agent 全栈模板(多包 pnpm workspace):apps/client + apps/server
|
|
3
|
+
"version": "0.9.0",
|
|
4
|
+
"description": "tbox AI Agent 全栈模板(多包 pnpm workspace):apps/client + apps/server。唯一模板(W4 单一模板统一化:缺省空装配 chat 形态;mall 能力经 profiles/mall 加装),能力经 tbox-app module add 模块扩展(shopping-guide/member 等)。",
|
|
5
5
|
"engines": {
|
|
6
6
|
"node": ">=24.0.0 <25.0.0"
|
|
7
7
|
},
|
|
@@ -13,6 +13,7 @@
|
|
|
13
13
|
"!apps/**/node_modules",
|
|
14
14
|
"!apps/**/*.tsbuildinfo",
|
|
15
15
|
"config",
|
|
16
|
+
"profiles",
|
|
16
17
|
"packages",
|
|
17
18
|
"!packages/**/node_modules",
|
|
18
19
|
"!packages/**/*.tsbuildinfo",
|
|
@@ -31,7 +32,7 @@
|
|
|
31
32
|
"LICENSE"
|
|
32
33
|
],
|
|
33
34
|
"devDependencies": {
|
|
34
|
-
"@tbox.cn/app-sdk": "0.
|
|
35
|
+
"@tbox.cn/app-sdk": "0.20.0",
|
|
35
36
|
"eslint": "9.39.5",
|
|
36
37
|
"typescript-eslint": "8.67.0",
|
|
37
38
|
"eslint-plugin-react-hooks": "5.2.0",
|
|
@@ -47,7 +48,7 @@
|
|
|
47
48
|
"build:client": "pnpm check && pnpm --filter ./apps/client build",
|
|
48
49
|
"typecheck": "pnpm -r --filter \"./{apps,packages}/*\" typecheck",
|
|
49
50
|
"test": "pnpm -r --filter \"./{apps,packages}/*\" test",
|
|
50
|
-
"lint": "eslint apps packages",
|
|
51
|
+
"lint": "eslint apps $(find packages -mindepth 1 -maxdepth 1 -type d -print -quit 2>/dev/null | grep -q . && echo packages)",
|
|
51
52
|
"verify": "pnpm typecheck && pnpm test && pnpm lint && pnpm build && pnpm verify:generated && pnpm check:inspector",
|
|
52
53
|
"verify:generated": "node scripts/verify-generated.mjs",
|
|
53
54
|
"check": "pnpm check:workspace && pnpm check:inspector",
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026 tbox.cn
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
# @app/agent-ui-kit
|
|
2
|
+
|
|
3
|
+
Agent 对话壳 UI 组件包(源码发布)——单一模板统一化 W2-1 自 mall 模板抽取。
|
|
4
|
+
|
|
5
|
+
- **真源**:本包(`packages/agent-ui-kit`)。模板出厂副本经 `scripts/sync-agent-ui-kit.mjs` 派生
|
|
6
|
+
(name 改写 `@app/agent-ui-kit` → `@app/agent-ui-kit`),guard identity 锁定一致。
|
|
7
|
+
- **消费面**:应用 `<ChatPage store={useChatStore} cards={CHAT_REGISTRY} slots={clientCtx.slots}/>`
|
|
8
|
+
单点装配;域 UI(如商圈主屏)经 home 槽渲染函数自带。
|
|
9
|
+
- **主题**:kit 默认 tokens(`src/theme/tokens.less`);品牌资产包经 `html[data-theme]` 级联覆写 +
|
|
10
|
+
接线三行(`import '<pkg>/index.css'` + `import '<pkg>/brand.js'`[可选] + `applyTheme(id)`;见 docs/reference/templates.md)。
|
|
11
|
+
|
|
12
|
+
## 验证
|
|
13
|
+
|
|
14
|
+
```sh
|
|
15
|
+
pnpm --filter @app/agent-ui-kit typecheck
|
|
16
|
+
pnpm --filter @app/agent-ui-kit test
|
|
17
|
+
```
|
|
@@ -0,0 +1,58 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@app/agent-ui-kit",
|
|
3
|
+
"version": "0.1.0",
|
|
4
|
+
"type": "module",
|
|
5
|
+
"description": "Agent 对话壳 UI 组件包(源码发布,单一模板统一化 W2-1):ChatPage/壳组件族/主题 tokens。",
|
|
6
|
+
"exports": {
|
|
7
|
+
".": {
|
|
8
|
+
"types": "./src/index.ts",
|
|
9
|
+
"import": "./src/index.ts"
|
|
10
|
+
},
|
|
11
|
+
"./theme": {
|
|
12
|
+
"types": "./src/theme/theme.ts",
|
|
13
|
+
"import": "./src/theme/theme.ts"
|
|
14
|
+
},
|
|
15
|
+
"./package.json": "./package.json",
|
|
16
|
+
"./theme/global.less": "./src/theme/global.less",
|
|
17
|
+
"./theme/contract": "./src/theme/contract.json"
|
|
18
|
+
},
|
|
19
|
+
"scripts": {
|
|
20
|
+
"typecheck": "tsc --noEmit",
|
|
21
|
+
"test": "vitest run"
|
|
22
|
+
},
|
|
23
|
+
"files": [
|
|
24
|
+
"src",
|
|
25
|
+
"assets",
|
|
26
|
+
"README.md",
|
|
27
|
+
"LICENSE"
|
|
28
|
+
],
|
|
29
|
+
"dependencies": {
|
|
30
|
+
"@tbox.cn/app-sdk": "catalog:",
|
|
31
|
+
"@tbox.cn/app-contracts": "catalog:",
|
|
32
|
+
"@phosphor-icons/react": "catalog:",
|
|
33
|
+
"antd-mobile": "catalog:",
|
|
34
|
+
"react-markdown": "catalog:",
|
|
35
|
+
"remark-gfm": "catalog:",
|
|
36
|
+
"zustand": "catalog:"
|
|
37
|
+
},
|
|
38
|
+
"peerDependencies": {
|
|
39
|
+
"react": "catalog:",
|
|
40
|
+
"react-dom": "catalog:"
|
|
41
|
+
},
|
|
42
|
+
"devDependencies": {
|
|
43
|
+
"@types/node": "catalog:",
|
|
44
|
+
"@types/react": "catalog:",
|
|
45
|
+
"@types/react-dom": "catalog:",
|
|
46
|
+
"happy-dom": "catalog:",
|
|
47
|
+
"less": "catalog:",
|
|
48
|
+
"react": "catalog:",
|
|
49
|
+
"react-dom": "catalog:",
|
|
50
|
+
"typescript": "catalog:",
|
|
51
|
+
"vite": "catalog:",
|
|
52
|
+
"vitest": "catalog:"
|
|
53
|
+
},
|
|
54
|
+
"engines": {
|
|
55
|
+
"node": ">=20.0.0"
|
|
56
|
+
},
|
|
57
|
+
"license": "MIT"
|
|
58
|
+
}
|
|
@@ -0,0 +1,62 @@
|
|
|
1
|
+
.chat-page {
|
|
2
|
+
position: relative;
|
|
3
|
+
display: flex;
|
|
4
|
+
flex-direction: column;
|
|
5
|
+
width: 100%;
|
|
6
|
+
height: 100vh;
|
|
7
|
+
height: 100dvh;
|
|
8
|
+
min-height: 0;
|
|
9
|
+
overflow: hidden;
|
|
10
|
+
background: var(--tbox-bg-page);
|
|
11
|
+
|
|
12
|
+
&__bg {
|
|
13
|
+
position: absolute;
|
|
14
|
+
inset: 0;
|
|
15
|
+
z-index: 0;
|
|
16
|
+
background: var(--tbox-bg-page);
|
|
17
|
+
pointer-events: none;
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
&__body-wrapper {
|
|
21
|
+
position: relative;
|
|
22
|
+
z-index: 1;
|
|
23
|
+
display: flex;
|
|
24
|
+
flex: 1;
|
|
25
|
+
flex-direction: column;
|
|
26
|
+
min-height: 0;
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
&__body {
|
|
30
|
+
flex: 1;
|
|
31
|
+
width: 100%;
|
|
32
|
+
max-width: var(--tbox-content-max-width);
|
|
33
|
+
min-height: 0;
|
|
34
|
+
margin: 0 auto;
|
|
35
|
+
padding: calc(12PX + env(safe-area-inset-top, 0PX)) 0 24PX;
|
|
36
|
+
overflow-y: auto;
|
|
37
|
+
overflow-x: hidden;
|
|
38
|
+
overflow-anchor: none;
|
|
39
|
+
overscroll-behavior: contain;
|
|
40
|
+
scrollbar-width: none;
|
|
41
|
+
-webkit-overflow-scrolling: touch;
|
|
42
|
+
|
|
43
|
+
&::-webkit-scrollbar {
|
|
44
|
+
display: none;
|
|
45
|
+
}
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
&__orphan-card {
|
|
49
|
+
margin-right: var(--tbox-page-gutter);
|
|
50
|
+
margin-left: var(--tbox-page-gutter);
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
/* 顶部历史入口在场时欢迎区不再叠加自身顶部留白:胶囊到标题区留 4PX(= 欢迎区自带上边距)。 */
|
|
54
|
+
.history-entry ~ .mall-home {
|
|
55
|
+
padding-top: 0;
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
/* 对话开始后首页仍保留,消息接在联单主卡下方。 */
|
|
59
|
+
&--conversation &__body {
|
|
60
|
+
padding-top: calc(12PX + env(safe-area-inset-top, 0PX));
|
|
61
|
+
}
|
|
62
|
+
}
|