@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.
- package/.gitignore +6 -0
- package/.tbox/app.json +12 -3
- package/AGENTS.md +17 -6
- package/ENV.md +36 -5
- package/README.md +23 -7
- package/apps/client/package.json +3 -2
- package/apps/client/src/app.tsx +28 -0
- package/apps/client/src/cards/CardRenderer.tsx +4 -5
- package/apps/client/src/components/ActionBar/DebugButton.tsx +5 -1
- package/apps/client/src/components/ActionBar/index.tsx +7 -1
- package/apps/client/src/components/ChatInput/index.less +54 -0
- package/apps/client/src/components/ChatInput/index.tsx +33 -9
- package/apps/client/src/components/ChatInput/voice/useVoicePress.ts +20 -1
- package/apps/client/src/components/MessageBubble/index.less +12 -1
- package/apps/client/src/components/MessageBubble/index.tsx +16 -8
- package/apps/client/src/components/PullDownIndicator/index.less +1 -1
- package/apps/client/src/components/PullDownIndicator/index.tsx +3 -4
- package/apps/client/src/components/ToolCallBlock/index.less +29 -0
- package/apps/client/src/components/ToolCallBlock/index.tsx +58 -24
- package/apps/client/src/hooks/useMediaUpload.ts +7 -2
- package/apps/client/src/main.tsx +4 -1
- package/apps/client/src/pages/index/index.tsx +35 -13
- package/apps/client/src/platform/AgentChatApp.tsx +5 -1
- package/apps/client/src/store/chat-config.ts +12 -1
- package/apps/client/src/store/chat.ts +62 -92
- package/apps/client/vite.config.ts +46 -26
- package/apps/server/package.json +5 -11
- package/apps/server/src/app.ts +452 -0
- package/apps/server/src/cli/collect-registry.ts +7 -6
- package/apps/server/src/config/deployment.ts +36 -70
- package/apps/server/src/config/integration.ts +81 -0
- package/apps/server/src/dev.ts +81 -0
- package/apps/server/src/index.ts +23 -376
- package/apps/server/src/intent-routes.ts +11 -4
- package/apps/server/src/modules.ts +3 -0
- package/apps/server/tests/cards-registry.test.ts +9 -17
- package/apps/server/tests/deployment-loader.test.ts +140 -0
- package/apps/server/tests/intent-dispatcher.test.ts +58 -0
- package/apps/server/tests/tool-runtime.test.ts +97 -23
- package/apps/server/tsup.config.ts +15 -2
- package/config/chat.json +4 -1
- package/config/llm_provider.json +3 -1
- package/package.json +19 -11
- package/packages/contracts-mall/package.json +30 -0
- package/packages/contracts-mall/src/index.ts +1 -0
- package/packages/contracts-mall/src/runtime.ts +1 -0
- package/packages/contracts-mall/src/server/index.ts +1 -0
- package/packages/contracts-mall/tsconfig.json +7 -0
- package/packages/module-weather/package.json +4 -4
- package/packages/module-weather/skills/weather-guide/SKILL.md +3 -0
- package/packages/module-weather/src/server/cards/weather/meta.ts +0 -1
- package/packages/module-weather/src/server/index.ts +1 -0
- package/packages/module-weather/src/server/tool.ts +24 -24
- package/packages/provider-custom/AGENTS.md +33 -0
- package/packages/provider-custom/LICENSE +21 -0
- package/packages/provider-custom/README.md +8 -0
- package/packages/provider-custom/package.json +62 -0
- package/packages/provider-custom/src/index.ts +5 -0
- package/packages/provider-custom/src/server/index.ts +40 -0
- package/packages/provider-custom/tbox.module.json +29 -0
- package/packages/provider-custom/tests/provider-custom.test.ts +17 -0
- package/packages/provider-custom/tsconfig.json +7 -0
- package/packages/provider-custom/tsup.config.ts +18 -0
- package/pnpm-lock.template.yaml +6297 -0
- package/pnpm-workspace.template.yaml +45 -46
- package/scripts/build.sh +6 -12
- package/scripts/check-workspace.mjs +11 -10
- package/scripts/stop.sh +48 -14
- package/scripts/verify-generated.mjs +4 -4
- package/tsconfig.base.json +1 -0
- package/apps/client/babel-plugins/code-inspect.cjs +0 -110
- package/apps/client/src/hooks/usePullDownHistory.ts +0 -97
- package/apps/server/skills/shopping-guide-demo/SKILL.md +0 -21
- package/apps/server/skills/shopping-guide-demo/references/guide/parking.md +0 -11
- package/apps/server/skills/shopping-guide-demo/references/mall-directory.md +0 -17
- package/apps/server/skills/shopping-guide-demo/references/promotion.md +0 -10
- package/apps/server/src/config/feature-gate.ts +0 -17
- package/apps/server/tests/feature-gate.test.ts +0 -30
- package/config/deployments/development.json +0 -27
- package/scripts/check-inspector.sh +0 -91
- /package/packages/module-weather/{tbox.component.json → tbox.module.json} +0 -0
|
@@ -1,9 +1,11 @@
|
|
|
1
1
|
// 卡片 / 工具 registry 一致性守护测试(自注册闭环版)。
|
|
2
2
|
// 通过 registerAll(ctx) 收集真实注册状态,替代原手写 registry 静态断言。
|
|
3
|
+
// v2 出卡协议:tool→card 绑定(allowedTools/cardResolver)退场——出卡由工具出参
|
|
4
|
+
// cards 字段运行期声明,registry 仅余 cardType 注册与 dataSchema 校验职责。
|
|
3
5
|
// 注:C 方案下卡片渲染在 client(React 组件),server 无 template.html,故移除原 C12(模板占位符 ∈ schema)。
|
|
4
6
|
import { describe, it, expect } from 'vitest';
|
|
5
7
|
import { diagnosePlugins } from '@tbox.cn/agent-plugin/doctor';
|
|
6
|
-
import { createServerContext,
|
|
8
|
+
import { createServerContext, createPluginIntegration } from '@tbox.cn/app-sdk/server';
|
|
7
9
|
import { registerAll } from '../src/modules.js';
|
|
8
10
|
|
|
9
11
|
describe('工具 / 卡片 registry 一致性(自注册闭环)', () => {
|
|
@@ -15,24 +17,22 @@ describe('工具 / 卡片 registry 一致性(自注册闭环)', () => {
|
|
|
15
17
|
}
|
|
16
18
|
});
|
|
17
19
|
|
|
18
|
-
it('C3:
|
|
20
|
+
it('C3: 每张卡 meta.cardType === ctx.cards 的 key', async () => {
|
|
19
21
|
const ctx = createServerContext();
|
|
20
22
|
await registerAll(ctx);
|
|
21
|
-
const
|
|
22
|
-
|
|
23
|
-
expect(Object.keys(cards)).toContain(binding.cardType);
|
|
23
|
+
for (const [key, meta] of Object.entries(ctx.cards.getAll())) {
|
|
24
|
+
expect((meta as { cardType?: string }).cardType).toBe(key);
|
|
24
25
|
}
|
|
25
26
|
});
|
|
26
27
|
|
|
27
|
-
it('C3:
|
|
28
|
+
it('C3: 插件配置诊断(无卡片绑定,v2 协议下 bindings 恒空)', async () => {
|
|
28
29
|
const ctx = createServerContext();
|
|
29
30
|
await registerAll(ctx);
|
|
30
|
-
const cards = ctx.cards.getAll();
|
|
31
31
|
const diagnostics = diagnosePlugins({
|
|
32
32
|
config: createPluginIntegration().readConfig(),
|
|
33
|
-
bindings:
|
|
33
|
+
bindings: [],
|
|
34
34
|
staticToolIds: Object.keys(ctx.tools.getAll()),
|
|
35
|
-
cardTypes: Object.keys(cards),
|
|
35
|
+
cardTypes: Object.keys(ctx.cards.getAll()),
|
|
36
36
|
apiKeyConfigured: true,
|
|
37
37
|
});
|
|
38
38
|
expect(diagnostics.filter((item) => item.level === 'error')).toEqual([]);
|
|
@@ -44,12 +44,4 @@ describe('工具 / 卡片 registry 一致性(自注册闭环)', () => {
|
|
|
44
44
|
warnings: [],
|
|
45
45
|
});
|
|
46
46
|
});
|
|
47
|
-
|
|
48
|
-
it('C3: 每张卡 meta.cardType === ctx.cards 的 key', async () => {
|
|
49
|
-
const ctx = createServerContext();
|
|
50
|
-
await registerAll(ctx);
|
|
51
|
-
for (const [key, meta] of Object.entries(ctx.cards.getAll())) {
|
|
52
|
-
expect((meta as { cardType?: string }).cardType).toBe(key);
|
|
53
|
-
}
|
|
54
|
-
});
|
|
55
47
|
});
|
|
@@ -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,10 +1,16 @@
|
|
|
1
1
|
import { describe, expect, it } from 'vitest';
|
|
2
|
-
import {
|
|
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';
|
|
4
10
|
import { translateTool } from '../src/tools/translate.js';
|
|
5
11
|
|
|
6
12
|
describe('tool runtime 宿主装配(自注册闭环)', () => {
|
|
7
|
-
it('
|
|
13
|
+
it('合并模块工具(v2:无 cardResolver——出卡由工具出参 cards 运行期声明)', async () => {
|
|
8
14
|
const ctx = createServerContext();
|
|
9
15
|
await registerAll(ctx);
|
|
10
16
|
|
|
@@ -15,37 +21,36 @@ describe('tool runtime 宿主装配(自注册闭环)', () => {
|
|
|
15
21
|
expect(Object.keys(runtime.tools)).toEqual(
|
|
16
22
|
expect.arrayContaining(['getWeather']),
|
|
17
23
|
);
|
|
18
|
-
expect(runtime.cardResolver.issues).toEqual([]);
|
|
19
|
-
expect(
|
|
20
|
-
runtime.cardResolver.resolve('getWeather', {
|
|
21
|
-
city: '杭州',
|
|
22
|
-
temp: 28,
|
|
23
|
-
weather: '晴',
|
|
24
|
-
humidity: 60,
|
|
25
|
-
}),
|
|
26
|
-
).toEqual({
|
|
27
|
-
status: 'ready',
|
|
28
|
-
cardType: 'weather',
|
|
29
|
-
data: {
|
|
30
|
-
city: '杭州',
|
|
31
|
-
temp: 28,
|
|
32
|
-
weather: '晴',
|
|
33
|
-
humidity: 60,
|
|
34
|
-
},
|
|
35
|
-
});
|
|
36
24
|
});
|
|
37
25
|
|
|
38
|
-
it('
|
|
26
|
+
it('weather 参考实现:execute 出参携带 cards 声明(顶层答话事实 + cards 数组)', async () => {
|
|
39
27
|
const ctx = createServerContext();
|
|
40
28
|
await registerAll(ctx);
|
|
41
29
|
|
|
42
30
|
const runtime = await createSdkToolRuntime(ctx, {
|
|
43
31
|
loadPlugins: async () => ({ tools: {}, references: [] }),
|
|
44
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
|
+
});
|
|
45
44
|
|
|
46
|
-
|
|
47
|
-
|
|
45
|
+
it('未注册的 tool 不进运行时工具集(仅作为模型上下文面之外)', async () => {
|
|
46
|
+
const ctx = createServerContext();
|
|
47
|
+
await registerAll(ctx);
|
|
48
|
+
|
|
49
|
+
const runtime = await createSdkToolRuntime(ctx, {
|
|
50
|
+
loadPlugins: async () => ({ tools: {}, references: [] }),
|
|
48
51
|
});
|
|
52
|
+
|
|
53
|
+
expect(runtime.tools.notExistedTool).toBeUndefined();
|
|
49
54
|
});
|
|
50
55
|
|
|
51
56
|
describe('translate 参数规范(D11:string 形态 + 工具内 normalize)', () => {
|
|
@@ -75,3 +80,72 @@ describe('tool runtime 宿主装配(自注册闭环)', () => {
|
|
|
75
80
|
});
|
|
76
81
|
});
|
|
77
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
|
|
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
package/config/llm_provider.json
CHANGED
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@tbox.cn/app-template-agent",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.7.5",
|
|
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
|
-
"
|
|
32
|
-
"
|
|
33
|
-
"eslint
|
|
34
|
+
"@tbox.cn/app-sdk": "0.18.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
|
|
41
|
-
"build": "pnpm check
|
|
42
|
-
"
|
|
43
|
-
"
|
|
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
|
-
"
|
|
49
|
-
"plugin:
|
|
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';
|
|
@@ -26,11 +26,11 @@
|
|
|
26
26
|
"@tbox.cn/app-contracts": "catalog:"
|
|
27
27
|
},
|
|
28
28
|
"peerDependencies": {
|
|
29
|
-
"zod": "
|
|
30
|
-
"react": "
|
|
29
|
+
"zod": "3.25.76",
|
|
30
|
+
"react": "18.3.1"
|
|
31
31
|
},
|
|
32
32
|
"devDependencies": {
|
|
33
|
-
"@types/react": "
|
|
34
|
-
"typescript": "
|
|
33
|
+
"@types/react": "18.3.31",
|
|
34
|
+
"typescript": "5.9.3"
|
|
35
35
|
}
|
|
36
36
|
}
|