@tbox.cn/app-template-agent 0.1.0 → 0.7.4

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 +38 -5
  5. package/README.md +131 -5
  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 +12 -2
  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/QuickCommands/index.tsx +9 -2
  19. package/apps/client/src/components/ToolCallBlock/index.less +40 -0
  20. package/apps/client/src/components/ToolCallBlock/index.tsx +84 -22
  21. package/apps/client/src/hooks/useMediaUpload.ts +7 -2
  22. package/apps/client/src/main.tsx +4 -1
  23. package/apps/client/src/pages/index/index.tsx +35 -13
  24. package/apps/client/src/platform/AgentChatApp.tsx +5 -1
  25. package/apps/client/src/store/chat-config.ts +12 -1
  26. package/apps/client/src/store/chat.ts +72 -71
  27. package/apps/client/vite.config.ts +46 -26
  28. package/apps/server/package.json +5 -10
  29. package/apps/server/src/app-tools.ts +13 -0
  30. package/apps/server/src/app.ts +452 -0
  31. package/apps/server/src/cli/collect-registry.ts +11 -9
  32. package/apps/server/src/config/deployment.ts +47 -0
  33. package/apps/server/src/config/integration.ts +81 -0
  34. package/apps/server/src/dev.ts +81 -0
  35. package/apps/server/src/index.ts +24 -219
  36. package/apps/server/src/intent-routes.ts +11 -4
  37. package/apps/server/src/modules.ts +12 -5
  38. package/apps/server/src/pre-tools.ts +25 -0
  39. package/apps/server/src/tools/clock.ts +24 -0
  40. package/apps/server/src/tools/translate.ts +10 -4
  41. package/apps/server/tests/cards-registry.test.ts +9 -17
  42. package/apps/server/tests/deployment-loader.test.ts +140 -0
  43. package/apps/server/tests/intent-dispatcher.test.ts +58 -0
  44. package/apps/server/tests/tool-runtime.test.ts +124 -22
  45. package/apps/server/tsup.config.ts +15 -2
  46. package/config/chat.json +4 -1
  47. package/config/llm_provider.json +3 -1
  48. package/package.json +19 -11
  49. package/packages/contracts-mall/package.json +30 -0
  50. package/packages/contracts-mall/src/index.ts +1 -0
  51. package/packages/contracts-mall/src/runtime.ts +1 -0
  52. package/packages/contracts-mall/src/server/index.ts +1 -0
  53. package/packages/contracts-mall/tsconfig.json +7 -0
  54. package/packages/module-weather/package.json +6 -5
  55. package/packages/module-weather/skills/weather-guide/SKILL.md +22 -0
  56. package/packages/module-weather/skills/weather-guide/references/dressing-tips.md +5 -0
  57. package/packages/module-weather/src/server/cards/weather/meta.ts +0 -1
  58. package/packages/module-weather/src/server/index.ts +27 -1
  59. package/packages/module-weather/src/server/pre-tools.ts +20 -0
  60. package/packages/module-weather/src/server/tool.ts +24 -24
  61. package/packages/provider-custom/AGENTS.md +33 -0
  62. package/packages/provider-custom/LICENSE +21 -0
  63. package/packages/provider-custom/README.md +8 -0
  64. package/packages/provider-custom/package.json +62 -0
  65. package/packages/provider-custom/src/index.ts +5 -0
  66. package/packages/provider-custom/src/server/index.ts +40 -0
  67. package/packages/provider-custom/tbox.module.json +29 -0
  68. package/packages/provider-custom/tests/provider-custom.test.ts +17 -0
  69. package/packages/provider-custom/tsconfig.json +7 -0
  70. package/packages/provider-custom/tsup.config.ts +18 -0
  71. package/pnpm-lock.template.yaml +6297 -0
  72. package/pnpm-workspace.template.yaml +45 -45
  73. package/scripts/build.sh +6 -12
  74. package/scripts/check-workspace.mjs +11 -10
  75. package/scripts/stop.sh +48 -14
  76. package/scripts/verify-generated.mjs +4 -4
  77. package/tsconfig.base.json +1 -0
  78. package/apps/client/babel-plugins/code-inspect.cjs +0 -110
  79. package/apps/client/src/hooks/usePullDownHistory.ts +0 -97
  80. package/scripts/check-inspector.sh +0 -91
  81. /package/packages/module-weather/{tbox.component.json → tbox.module.json} +0 -0
@@ -0,0 +1,140 @@
1
+ /**
2
+ * integrations v4 装载链单测(deployment 派生自 integrations——单文件真源;base 精简面)。
3
+ * 覆盖:投影穿透(attributes 存活)/ 无参考表 → 空平面 + defaultInstanceId 独立在场 /
4
+ * stale 防线 / buildDefaultMallContext mallId-only 注入 / deploymentFromIntegrations 装配活性。
5
+ */
6
+ import { mkdtempSync, mkdirSync, rmSync, writeFileSync } from 'node:fs';
7
+ import { tmpdir } from 'node:os';
8
+ import { join } from 'node:path';
9
+ import { afterAll, describe, expect, it } from 'vitest';
10
+ import { loadIntegrations, assertNoStaleDeploymentControlPlane } from '../src/config/integration.js';
11
+ import { deploymentFromIntegrations, buildDefaultMallContext } from '../src/config/deployment.js';
12
+
13
+ const dir = mkdtempSync(join(tmpdir(), 'tbox-deploy-loader-'));
14
+ afterAll(() => rmSync(dir, { recursive: true, force: true }));
15
+
16
+ function writeConfig(name: string, data: unknown): string {
17
+ const p = join(dir, name);
18
+ writeFileSync(p, JSON.stringify(data));
19
+ return p;
20
+ }
21
+
22
+ const fullIntegrations = {
23
+ provider: 'acme',
24
+ instances: [
25
+ {
26
+ id: 'mall-demo',
27
+ name: '示例商场',
28
+ attributes: {
29
+ miniAppId: 'demo-app',
30
+ contactPhone: '0571-88888888',
31
+ location: { lat: 30.2741, lon: 120.1551, address: '湖滨示范点' },
32
+ },
33
+ },
34
+ ],
35
+ defaultInstanceId: 'mall-demo',
36
+ services: { 'member.account': {} },
37
+ };
38
+
39
+ describe('loadIntegrations:投影穿透', () => {
40
+ it('contactPhone/location/miniAppId 经投影存活到 controlPlane(不被 zod strip)', () => {
41
+ const loaded = loadIntegrations(writeConfig('full.json', fullIntegrations));
42
+ expect(loaded?.controlPlane.malls[0]).toMatchObject({
43
+ mallId: 'mall-demo',
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');
50
+ });
51
+
52
+ it('拼错 attributes 键 → fail-fast(strict——报错含实例 id 与允许键清单)', () => {
53
+ const p = writeConfig('typo-attr.json', {
54
+ instances: [{ id: 'm1', name: 'x', attributes: { miniAppid: 'typo' } }],
55
+ services: {},
56
+ });
57
+ expect(() => loadIntegrations(p)).toThrow(/m1.*允许键|允许键.*miniAppId/s);
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');
64
+ });
65
+
66
+ it('defaultInstanceId 占位 ${DEFAULT_MALL_ID} → 展开后载入;缺 env fail-fast', () => {
67
+ const p = writeConfig('env-placeholder.json', { defaultInstanceId: '${DEFAULT_MALL_ID}', services: {} });
68
+ const saved = process.env.DEFAULT_MALL_ID;
69
+ process.env.DEFAULT_MALL_ID = '1102588';
70
+ try {
71
+ expect(loadIntegrations(p)?.controlPlane.defaultMallId).toBe('1102588');
72
+ delete process.env.DEFAULT_MALL_ID;
73
+ expect(() => loadIntegrations(p)).toThrow(/DEFAULT_MALL_ID/);
74
+ } finally {
75
+ if (saved === undefined) delete process.env.DEFAULT_MALL_ID;
76
+ else process.env.DEFAULT_MALL_ID = saved;
77
+ }
78
+ });
79
+ });
80
+
81
+ describe('stale 防线(v4:deployments/development.json 含 malls → error 迁移指引)', () => {
82
+ function writeStaleRoot(name: string, stale: unknown): string {
83
+ const root = mkdtempSync(join(tmpdir(), `tbox-stale-${name}-`));
84
+ mkdirSync(join(root, 'config', 'deployments'), { recursive: true });
85
+ writeFileSync(join(root, 'config', 'deployments', 'development.json'), JSON.stringify(stale));
86
+ return root;
87
+ }
88
+
89
+ it('mall 家族形态(含 malls 键)→ throw 迁移指引', () => {
90
+ const root = writeStaleRoot('mall', { malls: [{ mallId: 'm', name: 'n' }], defaultMallId: 'm' });
91
+ try {
92
+ expect(() => assertNoStaleDeploymentControlPlane(root)).toThrow(/已并入 config\/integrations\.json/);
93
+ } finally {
94
+ rmSync(root, { recursive: true, force: true });
95
+ }
96
+ });
97
+
98
+ it('非 mall 形态(不含 malls 键)→ 不阻断(support 独立体系零触碰——G1)', () => {
99
+ const root = writeStaleRoot('other', { someOtherKey: 1 });
100
+ try {
101
+ expect(() => assertNoStaleDeploymentControlPlane(root)).not.toThrow();
102
+ } finally {
103
+ rmSync(root, { recursive: true, force: true });
104
+ }
105
+ });
106
+ });
107
+
108
+ describe('buildDefaultMallContext:v4 mallId-only 注入', () => {
109
+ it('defaultMallId + miniAppId 齐备 → 拼装完整 context;root 优先', () => {
110
+ const loaded = loadIntegrations(writeConfig('ctx-ok.json', fullIntegrations))!;
111
+ expect(buildDefaultMallContext(loaded.controlPlane, 'root-app')).toEqual({
112
+ mall: { mallId: 'mall-demo', miniAppId: 'root-app' },
113
+ });
114
+ expect(buildDefaultMallContext(loaded.controlPlane)).toEqual({
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' });
139
+ });
140
+ });
@@ -0,0 +1,58 @@
1
+ import { describe, expect, it } from 'vitest';
2
+ import { createServerContext } from '@tbox.cn/app-sdk/server';
3
+ import type { RequestContext } from '@tbox.cn/app-contracts';
4
+ import { createAppIntentDispatcher } from '../src/intent-routes.js';
5
+
6
+ /**
7
+ * 上下文统一化:意图分发契约锁——引擎 handlerDispatch 第三参 reqCtx 必须透传到
8
+ * HandlerRegistry.dispatch 第五参(handle 第四参)。断链 = 意图路径恒「缺少运行上下文」
9
+ * (typecheck 不可见:dispatcher 签名收窄即静默丢参)。
10
+ */
11
+ describe('createAppIntentDispatcher reqCtx 透传(上下文统一化)', () => {
12
+ it('handler 第四参收到 dispatcher 第三参原对象(引用级透传)', async () => {
13
+ const ctx = createServerContext();
14
+ const reqCtx: RequestContext = {
15
+ identity: { userId: 'u_intent', source: 'local' },
16
+ conversationId: 'conv_intent',
17
+ attributes: { mall: { miniAppId: 'app', mallId: 'mall-1' } },
18
+ };
19
+ const seen: (RequestContext | undefined)[] = [];
20
+ ctx.handlers.register({
21
+ id: 'probe-handler',
22
+ async handle(_action, _payload, _ctx, received) {
23
+ seen.push(received);
24
+ return { cards: [], text: 'ok' };
25
+ },
26
+ });
27
+
28
+ const dispatch = createAppIntentDispatcher(ctx);
29
+ await dispatch(
30
+ { id: 'probe', keywords: ['探针'], handlerId: 'probe-handler', action: 'run' },
31
+ '探针',
32
+ reqCtx,
33
+ );
34
+
35
+ expect(seen).toHaveLength(1);
36
+ expect(seen[0]).toBe(reqCtx);
37
+ });
38
+
39
+ it('无 reqCtx(匿名缺省形态)→ handle 第四参 undefined(不伪造上下文)', async () => {
40
+ const ctx = createServerContext();
41
+ const seen: (RequestContext | undefined)[] = [];
42
+ ctx.handlers.register({
43
+ id: 'probe-handler',
44
+ async handle(_action, _payload, _ctx, received) {
45
+ seen.push(received);
46
+ return { cards: [], text: 'ok' };
47
+ },
48
+ });
49
+
50
+ const dispatch = createAppIntentDispatcher(ctx);
51
+ await dispatch(
52
+ { id: 'probe', keywords: ['探针'], handlerId: 'probe-handler', action: 'run' },
53
+ '探针',
54
+ );
55
+
56
+ expect(seen[0]).toBeUndefined();
57
+ });
58
+ });
@@ -1,9 +1,16 @@
1
1
  import { describe, expect, it } from 'vitest';
2
- import { createServerContext, createSdkToolRuntime } from '@tbox.cn/app-sdk/server';
2
+ import {
3
+ createServerContext,
4
+ createSdkToolRuntime,
5
+ createEngineFromContext,
6
+ createInMemoryMemoryStore,
7
+ resolveMemoryStore,
8
+ } from '@tbox.cn/app-sdk/server';
3
9
  import { registerAll } from '../src/modules.js';
10
+ import { translateTool } from '../src/tools/translate.js';
4
11
 
5
12
  describe('tool runtime 宿主装配(自注册闭环)', () => {
6
- it('合并模块工具并用同一次加载结果创建卡片 resolver', async () => {
13
+ it('合并模块工具(v2:无 cardResolver——出卡由工具出参 cards 运行期声明)', async () => {
7
14
  const ctx = createServerContext();
8
15
  await registerAll(ctx);
9
16
 
@@ -14,27 +21,28 @@ describe('tool runtime 宿主装配(自注册闭环)', () => {
14
21
  expect(Object.keys(runtime.tools)).toEqual(
15
22
  expect.arrayContaining(['getWeather']),
16
23
  );
17
- expect(runtime.cardResolver.issues).toEqual([]);
18
- expect(
19
- runtime.cardResolver.resolve('getWeather', {
20
- city: '杭州',
21
- temp: 28,
22
- weather: '晴',
23
- humidity: 60,
24
- }),
25
- ).toEqual({
26
- status: 'ready',
27
- cardType: 'weather',
28
- data: {
29
- city: '杭州',
30
- temp: 28,
31
- weather: '晴',
32
- humidity: 60,
33
- },
24
+ });
25
+
26
+ it('weather 参考实现:execute 出参携带 cards 声明(顶层答话事实 + cards 数组)', async () => {
27
+ const ctx = createServerContext();
28
+ await registerAll(ctx);
29
+
30
+ const runtime = await createSdkToolRuntime(ctx, {
31
+ loadPlugins: async () => ({ tools: {}, references: [] }),
34
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}℃` }]);
35
43
  });
36
44
 
37
- it('未注册的 tool 不出卡,仅作为模型上下文', async () => {
45
+ it('未注册的 tool 不进运行时工具集(仅作为模型上下文面之外)', async () => {
38
46
  const ctx = createServerContext();
39
47
  await registerAll(ctx);
40
48
 
@@ -42,8 +50,102 @@ describe('tool runtime 宿主装配(自注册闭环)', () => {
42
50
  loadPlugins: async () => ({ tools: {}, references: [] }),
43
51
  });
44
52
 
45
- expect(runtime.cardResolver.resolve('notExistedTool', {})).toEqual({
46
- status: 'unbound',
53
+ expect(runtime.tools.notExistedTool).toBeUndefined();
54
+ });
55
+
56
+ describe('translate 参数规范(D11:string 形态 + 工具内 normalize)', () => {
57
+ // mastra Tool.execute 为可选 + 双参签名(input, context)——测试经非空断言直调
58
+ const callExecute = (input: { text: string; targetLang?: string }) =>
59
+ translateTool.execute!(input, {} as never);
60
+
61
+ it('合法 targetLang(zh/en)→ 生效', async () => {
62
+ const r = await callExecute({ text: '你好', targetLang: 'en' });
63
+ expect(r).toMatchObject({ targetLang: 'en' });
64
+ const r2 = await callExecute({ text: 'hello', targetLang: 'zh' });
65
+ expect(r2).toMatchObject({ targetLang: 'zh' });
66
+ });
67
+
68
+ it('非法 targetLang → 回落自动检测(不抛)', async () => {
69
+ const r = await callExecute({ text: '你好', targetLang: 'fr' });
70
+ expect(r).toMatchObject({ targetLang: 'en' }); // 中文输入 → 自动检测回落英文
71
+ const r2 = await callExecute({ text: 'hello', targetLang: 'xx' });
72
+ expect(r2).toMatchObject({ targetLang: 'zh' }); // 英文输入 → 自动检测回落中文
73
+ });
74
+
75
+ it('缺省 targetLang → 自动检测', async () => {
76
+ const r = await callExecute({ text: '你好' });
77
+ expect(r).toMatchObject({ targetLang: 'en' });
78
+ const r2 = await callExecute({ text: 'hello' });
79
+ expect(r2).toMatchObject({ targetLang: 'zh' });
47
80
  });
48
81
  });
49
82
  });
83
+
84
+ describe('长期记忆装配(017:默认 inmemory 开启 + off 逃生舱)', () => {
85
+ it('resolveMemoryStore 缺省 → inmemory(默认开启)', () => {
86
+ const m = resolveMemoryStore({});
87
+ expect(m.enabled).toBe(true);
88
+ expect(m.mode).toBe('inmemory');
89
+ expect(m.store).toBeDefined();
90
+ expect(m.store!.capabilities).toBeUndefined(); // 缺省全真
91
+ });
92
+
93
+ it('resolveMemoryStore off → 关闭(enabled=false)', () => {
94
+ const m = resolveMemoryStore({ store: 'off' });
95
+ expect(m.enabled).toBe(false);
96
+ expect(m.mode).toBe('off');
97
+ expect(m.store).toBeUndefined();
98
+ });
99
+
100
+ it('resolveMemoryStore knowledge 无 kbId → warn 回退 inmemory', () => {
101
+ const m = resolveMemoryStore({ store: 'knowledge' });
102
+ expect(m.enabled).toBe(true);
103
+ expect(m.mode).toBe('inmemory');
104
+ });
105
+
106
+ it('resolveMemoryStore knowledge 三件套齐备 → knowledge 模式(2026-09-01 翻转:caps 缺省全 true——平台已发布生产)', () => {
107
+ const m = resolveMemoryStore({ store: 'knowledge', kbId: 'kb-1', kbDocId: 'doc-1', apiKey: 'k' });
108
+ expect(m.mode).toBe('knowledge');
109
+ expect(m.store!.capabilities).toEqual({ save: true, search: true, list: true, delete: true });
110
+ });
111
+
112
+ it('resolveMemoryStore knowledge 缺 kbDocId → warn 回退 inmemory(三缺一)', () => {
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 () => {
123
+ const ctx = createServerContext();
124
+ await registerAll(ctx);
125
+ const engine = await createEngineFromContext(ctx, {
126
+ instructions: 'test',
127
+ model: {} as never,
128
+ memory: { store: createInMemoryMemoryStore() },
129
+ });
130
+ // 触发一轮 chat 使装配链路完整执行(无 runCtx → 匿名 → 零 recall)
131
+ await engine.chat('t-mem', 'hi', () => {});
132
+ // 工具名断言在 SDK build-engine 测试覆盖;模板层断言装配链路无异常 + skills 面不受影响
133
+ expect(engine.listSkills().length).toBeGreaterThan(0);
134
+ });
135
+
136
+ it('off 态 → 引擎无记忆工具装配(零异常 + 零工具)', async () => {
137
+ const ctx = createServerContext();
138
+ await registerAll(ctx);
139
+ const m = resolveMemoryStore({ store: 'off' });
140
+ const engine = await createEngineFromContext(
141
+ ctx,
142
+ {
143
+ instructions: 'test',
144
+ model: {} as never,
145
+ ...(m.enabled && m.store ? { memory: { store: m.store } } : {}),
146
+ },
147
+ );
148
+ await engine.chat('t-mem-off', 'hi', () => {});
149
+ expect(m.enabled).toBe(false);
150
+ });
151
+ });
@@ -9,14 +9,27 @@ export default defineConfig({
9
9
  clean: true,
10
10
  sourcemap: true,
11
11
  // 本地 workspace 源码包(exports 指向 src/*.ts,node 无法直接加载)必须内联:@app/*。
12
- // @tbox.cn/{app-sdk,contracts,contracts-mall} 以发布态消费(dist 产物,node 可直载)→ external;
12
+ // @tbox.cn/{app-sdk,contracts} 以发布态消费(dist 产物,node 可直载);codegen 平台包经 @app/* 内联;
13
13
  // 已发布 npm 的 @tbox.cn/agent-plugin(exports 指向 dist 产物)保持 external。
14
14
  noExternal: [/^@app\//],
15
+ // vite 不进 bundle 的结构防线:静态入口图(entry: index.ts → app.ts 链)任何 vite 引用
16
+ // (含 dev.ts 误入 entry)在 onResolve 即失败——dev 形态仅存在于 src/dev.ts(tsx 运行)。
17
+ esbuildPlugins: [
18
+ {
19
+ name: 'forbid-vite-in-bundle',
20
+ setup(build) {
21
+ build.onResolve({ filter: /^vite(\/|$)/ }, () => ({
22
+ errors: [
23
+ { text: 'vite 禁止进入 server bundle:dev 形态走 src/dev.ts(仅 tsx),禁入 tsup entry' },
24
+ ],
25
+ }));
26
+ },
27
+ },
28
+ ],
15
29
  // 确保不打包 node 内置模块
16
30
  external: [
17
31
  'express',
18
32
  'cors',
19
- 'ws',
20
33
  'path',
21
34
  'url',
22
35
  'http',
package/config/chat.json CHANGED
@@ -4,5 +4,8 @@
4
4
  "enableTTS": true,
5
5
  "enableVoiceInput": true,
6
6
  "defaultInputMode": "TEXT",
7
- "quickCommands": []
7
+ "quickCommands": [
8
+ { "label": "找美食", "query": "帮我找商场里的美食" },
9
+ { "label": "会员积分", "query": "我的会员积分是多少" }
10
+ ]
8
11
  }
@@ -1,3 +1,5 @@
1
1
  {
2
- "modelName": "glm-5.1"
2
+ "modelName": "glm-5.1",
3
+ "thinking": false,
4
+ "parallelToolCalls": true
3
5
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@tbox.cn/app-template-agent",
3
- "version": "0.1.0",
3
+ "version": "0.7.4",
4
4
  "description": "tbox AI Agent 全栈模板(多包 pnpm workspace):apps/client + apps/server + packages/module-*。客户端同时跑在浏览器与支付宝小程序 web-view 壳里。",
5
5
  "engines": {
6
6
  "node": ">=24.0.0 <25.0.0"
@@ -11,9 +11,11 @@
11
11
  "!apps/**/dist-ssr",
12
12
  "!apps/**/.vite",
13
13
  "!apps/**/node_modules",
14
+ "!apps/**/*.tsbuildinfo",
14
15
  "config",
15
16
  "packages",
16
17
  "!packages/**/node_modules",
18
+ "!packages/**/*.tsbuildinfo",
17
19
  "scripts",
18
20
  ".tbox",
19
21
  ".gitignore",
@@ -24,28 +26,34 @@
24
26
  "README.md",
25
27
  "eslint.config.mjs",
26
28
  "pnpm-workspace.template.yaml",
29
+ "pnpm-lock.template.yaml",
27
30
  "tsconfig.base.json",
28
31
  "LICENSE"
29
32
  ],
30
33
  "devDependencies": {
31
- "eslint": "^9.0.0",
32
- "typescript-eslint": "^8.57.2",
33
- "eslint-plugin-react-hooks": "^5.2.0"
34
+ "@tbox.cn/app-sdk": "0.17.0",
35
+ "eslint": "9.39.5",
36
+ "typescript-eslint": "8.67.0",
37
+ "eslint-plugin-react-hooks": "5.2.0",
38
+ "vite": "6.4.3"
34
39
  },
35
40
  "license": "MIT",
36
41
  "publishConfig": {
37
42
  "access": "public"
38
43
  },
39
44
  "scripts": {
40
- "dev": "pnpm --filter @app/server dev",
41
- "build": "pnpm check:workspace && pnpm --filter @app/server build && pnpm --filter @app/client build",
42
- "typecheck": "pnpm -r --filter \"@app/*\" typecheck",
43
- "test": "pnpm -r --filter \"@app/*\" test",
45
+ "dev": "pnpm --filter ./apps/server dev",
46
+ "build": "pnpm check && pnpm --filter ./apps/server build && pnpm --filter ./apps/client build",
47
+ "build:client": "pnpm check && pnpm --filter ./apps/client build",
48
+ "typecheck": "pnpm -r --filter \"./{apps,packages}/*\" typecheck",
49
+ "test": "pnpm -r --filter \"./{apps,packages}/*\" test",
44
50
  "lint": "eslint apps packages",
45
- "verify": "pnpm typecheck && pnpm test && pnpm lint && pnpm build && pnpm verify:generated",
51
+ "verify": "pnpm typecheck && pnpm test && pnpm lint && pnpm build && pnpm verify:generated && pnpm check:inspector",
46
52
  "verify:generated": "node scripts/verify-generated.mjs",
53
+ "check": "pnpm check:workspace && pnpm check:inspector",
47
54
  "check:workspace": "node scripts/check-workspace.mjs",
48
- "plugin:inspect": "pnpm --filter @app/server exec node --import tsx src/cli/plugin-inspect.ts",
49
- "plugin:doctor": "pnpm --filter @app/server exec node --import tsx src/cli/plugin-doctor.ts"
55
+ "check:inspector": "tbox-inspector-check",
56
+ "plugin:inspect": "pnpm --filter ./apps/server exec node --import tsx src/cli/plugin-inspect.ts",
57
+ "plugin:doctor": "pnpm --filter ./apps/server exec node --import tsx src/cli/plugin-doctor.ts"
50
58
  }
51
59
  }
@@ -0,0 +1,30 @@
1
+ {
2
+ "name": "@app/contracts-mall",
3
+ "version": "0.0.0",
4
+ "private": true,
5
+ "type": "module",
6
+ "exports": {
7
+ ".": {
8
+ "types": "./src/index.ts",
9
+ "import": "./src/runtime.ts"
10
+ },
11
+ "./server": {
12
+ "types": "./src/server/index.ts",
13
+ "import": "./src/server/index.ts"
14
+ },
15
+ "./package.json": "./package.json"
16
+ },
17
+ "scripts": {
18
+ "typecheck": "tsc --noEmit",
19
+ "test": "vitest run --passWithNoTests"
20
+ },
21
+ "dependencies": {
22
+ "@tbox.cn/app-contracts-mall": "catalog:"
23
+ },
24
+ "devDependencies": {
25
+ "@types/node": "24.13.3",
26
+ "typescript": "5.9.3",
27
+ "vitest": "4.1.10"
28
+ },
29
+ "tboxCodegenShim": true
30
+ }
@@ -0,0 +1 @@
1
+ export * from '@tbox.cn/app-contracts-mall';
@@ -0,0 +1 @@
1
+ export * from '@tbox.cn/app-contracts-mall';
@@ -0,0 +1 @@
1
+ export * from '@tbox.cn/app-contracts-mall/server';
@@ -0,0 +1,7 @@
1
+ {
2
+ "extends": "../../tsconfig.base.json",
3
+ "compilerOptions": {
4
+ "types": ["node"]
5
+ },
6
+ "include": ["src"]
7
+ }
@@ -15,7 +15,8 @@
15
15
  "./client": {
16
16
  "types": "./src/client/index.ts",
17
17
  "import": "./src/client/index.ts"
18
- }
18
+ },
19
+ "./package.json": "./package.json"
19
20
  },
20
21
  "scripts": {
21
22
  "typecheck": "tsc --noEmit"
@@ -25,11 +26,11 @@
25
26
  "@tbox.cn/app-contracts": "catalog:"
26
27
  },
27
28
  "peerDependencies": {
28
- "zod": "catalog:",
29
- "react": "catalog:"
29
+ "zod": "3.25.76",
30
+ "react": "18.3.1"
30
31
  },
31
32
  "devDependencies": {
32
- "@types/react": "catalog:",
33
- "typescript": "catalog:"
33
+ "@types/react": "18.3.31",
34
+ "typescript": "5.9.3"
34
35
  }
35
36
  }
@@ -0,0 +1,22 @@
1
+ ---
2
+ name: weather-guide
3
+ description: 天气查询与穿衣出行建议(调用 getWeather 真实数据)
4
+ metadata:
5
+ routingTerms: [天气, 穿衣, 温度, 湿度, 出行]
6
+ ---
7
+ ## 适用意图
8
+ 用户询问天气、穿衣建议、出行准备(如"今天穿什么")时使用本规范。
9
+
10
+ ## 调用工具
11
+ 必须调用 getWeather 工具查询真实天气(city 取用户提到的城市),禁止编造温度/天气数据。
12
+
13
+ ## 返回字段说明
14
+ getWeather 返回:temp(温度℃)、weather(天气现象)、humidity(湿度%)。
15
+
16
+ ## 回答组织方式
17
+ 1. 先报城市与天气现象、温度、湿度
18
+ 2. 再给出穿衣/出行建议(结合温度与天气现象)
19
+ 3. 温度 <10℃ 提醒保暖,>30℃ 提醒防晒
20
+
21
+ ## 出卡规则
22
+ getWeather 结果含天气卡:回复末尾另起一行输出 `<agent-ui-cards>` 引用本轮卡片 id,否则用户看不到卡。
@@ -0,0 +1,5 @@
1
+ ## 穿衣指数
2
+ - 温度 <10℃:厚外套/羽绒服
3
+ - 10~20℃:夹克/风衣
4
+ - 20~30℃:单衣/薄外套
5
+ - >30℃:短袖,注意防晒
@@ -13,7 +13,6 @@ export type WeatherCardData = z.infer<typeof dataSchema>;
13
13
  const meta: CardMeta<typeof dataSchema> = {
14
14
  cardType: 'weather',
15
15
  dataSchema,
16
- allowedTools: ['getWeather'],
17
16
  displayName: '天气卡片',
18
17
  description: '展示某城市的天气数据,含温度、天气状况、湿度',
19
18
  sampleData: {
@@ -1,6 +1,8 @@
1
1
  import { weatherTool } from './tool';
2
2
  import weatherMeta from './cards/weather/meta';
3
- import type { ServerContext, ServerModule } from '@tbox.cn/app-sdk/server';
3
+ import { loadModuleSkills } from '@tbox.cn/app-sdk/server';
4
+ import type { LocalSkill, ServerContext, ServerModule } from '@tbox.cn/app-sdk/server';
5
+ import { weatherPreTool } from './pre-tools';
4
6
 
5
7
  export { weatherTool, weatherMeta };
6
8
 
@@ -9,9 +11,33 @@ const cards = {
9
11
  weather: weatherMeta,
10
12
  } as const;
11
13
 
14
+ /**
15
+ * 模块 skill 声明(009 模块文件化:SKILL.md 目录格式,文件即唯一源)。
16
+ * - 底层能力:SDK `loadModuleSkills(packageName, import.meta.url)` 单策略定位包根
17
+ * (createRequire 包名解析:dev workspace link / 模块 dist / 应用 bundle dist / 发布态
18
+ * node_modules 全形态恒命中;失败 warn 可行动 + 恒 resolve []);
19
+ * - 顶层 await 加载 → 填回 serverModule.skills 声明式通道(装配端 registerAll 自动注册);
20
+ * - 前置:本包 exports 须含 "./package.json"(见 package.json);
21
+ * - 生成应用内(codegen 展开)调用名由 CLI 自动改写为 @app/<id>,源码按字面量包名写法。
22
+ */
23
+ const skills = Object.fromEntries(
24
+ (await loadModuleSkills('@app/module-weather', import.meta.url)).map((s) => [s.name, s]),
25
+ ) satisfies Record<string, LocalSkill>;
26
+
27
+ /**
28
+ * 模块前置工具声明(010:key 约定 = toolId,纯数据 + 一个 resolve 函数)。
29
+ * 装配端 registerServerModules 自动注册(register 内无需代码)。
30
+ */
31
+ const preTools = {
32
+ getWeather: weatherPreTool,
33
+ } as const;
34
+
12
35
  /** 模块注册入口:自注册闭环(与客户端 clientModule 对称) */
13
36
  export const serverModule = {
14
37
  cards,
38
+ skills,
39
+ preTools,
40
+ declaration: { moduleId: 'weather' },
15
41
  register(ctx: ServerContext): void {
16
42
  ctx.tools.register(weatherTool);
17
43
  ctx.cards.registerMap(cards);