@tbox.cn/app-template-agent
v0.10.5
Published
tbox AI Agent 全栈模板(多包 pnpm workspace):apps/client + apps/server。唯一模板(W4 单一模板统一化:缺省空装配 chat 形态;mall 能力经 profiles/mall 加装),能力经 tbox-app module add 模块扩展(shopping-guide/member 等)。
Readme
template-agent
tbox AI Agent 全栈模板(npm 包名 @tbox.cn/app-template-agent,唯一模板——W4 单一模板统一化),多包 pnpm workspace:apps/ 薄组装端 + packages/ 模块。
缺省出厂 = 空装配(chat 形态);商圈形态经 profiles/mall 画像加装(tbox-app create --profile mall:mall-core + 六业务模块 + 商圈配置叠加)。
客户端同时支持浏览器和支付宝小程序 web-view;平台能力(鉴权/会话/TTS/ASR/上传/反馈)经 @tbox.cn/app-sdk 统一提供(007 下沉)。
快速开始
pnpm install
pnpm dev # 单进程 http://localhost:8000(API + WS + 页面 + HMR)
pnpm verify # typecheck + test + build能力加装(空装配起点,能力即模块;mall 全套可改用 create --profile mall 一步到位):
tbox-app module add shopping-guide # 导购/找店/业态/客服(模块级 intents + LLM 工具 + 卡片)
tbox-app module add member # 会员卡/积分/优惠券(queryPoints preTool always 身份预执行)
tbox-app module add parking promotion # 停车/活动(可选)
tbox-app module doctor # 26 条结构校验MallHome 服务域(015;--profile mall 加装 mall-core 后生效):家页 packages/module-mall-core 四域开箱——导购/会员/活动优惠(promotion)/停车服务(parking);域 query 走工具与意图路由链(模块 add/remove 即增删能力,无部署级门控)。
宿主效果链(015):支付/支付宝授权效果由 SDK 自动执行(host-effect-runner:壳内拉起收银台/授权、浏览器环境自动降级),结果经补签 grant 回传收口——模板零装配;停车场次/入会 pending 收口均为显式按钮交互。
环境变量:本地开发用 shell export 注入(见
ENV.md,含必需变量命令块;每次新终端需重新执行), 生产建议在 tbox 平台页面上配置(平台托管 env,密钥不进代码仓库)。
平台包消费(005 v0.4.0 发布态):生成应用初始通过
@app/contracts-mallshim 使用catalog:引用的发布包;添加依赖 contracts-mall 的 codegen 模块后,CLI 将 shim 替换为本地源码包—— 生产(registry 已发布)直接pnpm install;平台包未发布 npm 时,由tbox-app create --local-deps <平台 tgz 目录>注入 overrides(file:本地发布态产物)后 install 离线可用。依赖链说明(007):
@tbox.cn/app-sdk新增 dependencies@tbox-dev-js/sdk(平台能力客户端)、@ai-sdk/openai-compatible(LLM)—— 生成应用 install 时由 pnpm 自动解析(@tbox-dev-js/sdk已发布 npm;内网/离线环境需 registry 镜像或 store 缓存兜底)。
🔐 鉴权(统一登录):登录面 =
POST /api/auth/login(method:'code'|'browser'判别); 浏览器档位(guest/mock/off/auto)经换码器browserLoginMode能力口治理(登录槽 config, 支持${VAR:-default}展开经 env 分档);生产部署必须NODE_ENV=production。
工程结构
template-agent/
profiles/ 模板画像(mall.json 加装计划 + config 叠加 + rootFiles 根文件 TEMPLATE.md;
create --profile 消费——根文件落地应用根)
apps/
client/ React/Vite 装配端(业务页面/组件/hooks/样式;平台能力经 @tbox.cn/app-sdk/client)
server/ Express + WS 装配端(~120 行装配:auth 中间件 + 模块挂载 + 平台路由面/TTS/WS)
server/skills/ 本地 Skill(SKILL.md 目录格式,随模板复制/pack)
packages/
模块目录(空装配:能力经 `tbox-app module add` 扩展——`@app/<id>` 成员在此生成)
config/ chat/model/plugin 配置
scripts/ 构建、检查和平台脚本
pnpm-workspace.template.yaml 应用 workspace 模板(create 生成应用 pnpm-workspace.yaml 的来源)本地 Skill(009)
应用与模块可为 Agent 提供受控技能(SOP + 参考资料),支持双模式(全局配置,运行期恒定):
| 模式 | 环境变量 | 行为 | 适用 |
|---|---|---|---|
| accelerated(默认) | TBOX_SKILL_MODE=accelerated | 本地打分路由(零 LLM)命中 → <system-reminder skill="...">SOP+references</system-reminder> 注入当前轮 user 消息前部;同 skill 会话内最多一次;system prompt 与工具列表恒定 → 前缀缓存稳定、零 skill 轮次 | 大多数业务场景 |
| progressive | TBOX_SKILL_MODE=progressive | Agent 全量注册 InlineSkill,模型按需 skill_read 渐进读取(mastra 原生) | 需要模型自主挑选技能的复杂场景 |
应用级 Skill(目录格式)
apps/server/skills/<skill-name>/
├── SKILL.md # frontmatter(name/description/metadata.routingTerms)+ SOP 正文
└── references/ # 可选,递归 *.md(随 SKILL.md 内存化加载,运行期零文件系统暴露)
├── a.md
└── sub/b.md模板已内置 shopping-guide 示例(含子目录 references)。新增步骤:建目录 → 写 SKILL.md(name 须匹配 ^[a-z0-9-]{1,64}$)→ references 可选 → 启动加载(无需改代码)。
模块级 Skill(文件加载)
模块包内放 skills/ 目录(与 src/ 平级),顶层 await 加载后填回 serverModule.skills 声明式通道(装配端 registerAll 自动注册,register 内零代码):
packages/<module-id>/
├── skills/<skill-name>/
│ ├── SKILL.md # frontmatter(name/description/metadata.routingTerms)+ SOP
│ └── references/*.md
├── src/server/index.ts
└── package.json # exports 须含 "./package.json": "./package.json"// packages/<module-id>/src/server/index.ts
import { loadModuleSkills } from '@tbox.cn/app-sdk/server';
const skills = Object.fromEntries(
(await loadModuleSkills('@app/<module-id>', import.meta.url)).map((s) => [s.name, s]),
) satisfies Record<string, LocalSkill>;
export const serverModule = { cards, skills, register(ctx) { /* ... */ } } satisfies ServerModule;底层能力(SDK loadModuleSkills(packageName, import.meta.url))单策略(createRequire 包名解析)定位模块包根,dev / build dist / 发布态路径恒等:以调用文件为基准解析 ${packageName}/package.json(workspace link / 模块 dist / 应用 tsup bundle dist / node_modules 全形态命中)。前置:模块 exports 须含 "./package.json"。加载失败降级 [] + 可行动 warn(提示 exports 键/安装问题,不阻断启动)。生成应用内业务模块 codegen 源码调用名由 CLI 自动改写为本地装配包名(@app/<id>);contracts-mall 作为平台 codegen 依赖时,模块 import 与依赖键由同一归一化器改写为 @app/contracts-mall。
inline 声明(serverModule.skills 直接内联 instructions/references)仍是发布态模块的轻量选择(无文件系统依赖),两种形态可并存。
冒烟
# 启动日志会打印装配结果:mode + skill 名列表(不含内容)
[SKILL] mode=accelerated skills=shopping-guide-demo
# 提问触发词(accelerated):"商场里有什么值得逛"(shopping-guide-demo SOP)→ 商圈工具/卡片
# 切换模式:TBOX_SKILL_MODE=progressive前置 Tool 加速执行(010)
对「参数可工程化获取、不依赖模型抽参」的只读工具,模型请求前先执行、结果注入上下文——正式回答首字从「模型①生成 tool-call → 工具执行 → 模型②作答」缩短为「工具执行 → 模型①直接作答」,每个预执行工具省一整轮 loop。
三步声明法
// 模块:serverModule.preTools(key = tool.id;装配端自动注册,register 内零代码)
// 应用:BuildEngineOptions.preTools(apps/server/src/pre-tools.ts)
// 统一经 definePreTool(tool, spec) 构造——tool 实例引用 = 单一真源(id/description 派生)
import { definePreTool } from '@tbox.cn/app-sdk/server';
definePreTool(findStoreTool, {
routing: { mode: 'keywords', terms: ['找店', '门店', '哪里有'] }, // ② 结构化路由:词表打分 / { mode:'always' } 必选
decide: ({ text, identity }) => { // ③ 决策即构造:run(args) = 预执行 / skip = 不生效
const store = STORES.find((s) => text.includes(s.name));
return store ? { decision: 'run', args: { storeId: store.id } } : { decision: 'skip' };
},
});铁律:仅只读/幂等工具(requireApproval/交互式工具不参与);decide 不得抛(SDK 兜底 warn + skip);args 必须 JSON 可序列化(坏形状/非法参数 → warn + skip);身份经入参 identity、领域属性经入参 attributes 获取(per-run 并发安全——显式入参是唯一通道,ctx.auth 全局单例已拆除);always 路由每轮进入 decide(慎用,防每轮噪音);框架层无跨轮去重(同参可重入,执行策略完全由 decide 工程化自控)。
行为
- 注入:每预执行工具一个
<system-reminder pre-tool="...">数据块(<input>/<output>内嵌 JSON,user 消息前部;无合成工具消息——模型不会把结果误判为「已调用过该工具」,可跨轮正常再调用);server-only(不进事件/落库/历史恢复)。 - 前端:thinking 气泡内实时显示「自动查询 xx」徽标(执行前即出现 spinner)→「已自动获取 xx」+ 可展开结果详情;刷新后历史消息同样可见。
- 失败降级:decide skip / 执行失败 / 超时 → 该工具按普通 LLM 工具路径走,不影响本轮回答;意图路由命中 → 零预执行。
- 预算:单工具 decide+execute 共享
TBOX_PRE_TOOL_TIMEOUT_MS(默认 2000ms;0= 整体禁用);单轮最多 3 个工具并行预执行。
模板演示
| 示例 | 位置 | 形态 |
|---|---|---|
| getCurrentTime | apps/server/src/pre-tools.ts + tools/clock.ts | 零参工程化(词表命中即执行) |
| mall-info | modules/module-mall-core(W3-1 自模板迁入;--profile mall/module add 后经模块声明式 preTools 通道生效) | always + mall scope 入参化(每轮背景注入;会话内 dedup 一次) |
| queryPoints | modules/module-member(tbox-app module add member 后) | always 必选 + 身份门控(identity 取 userId,匿名 skip) |
| translate | 反例(不改代码) | 参数依赖语义抽取,不适配——保持 LLM 工具路径 |
冒烟
# 启动日志:timeout + 预执行工具清单
[PRETOOL] timeoutMs=2000 preTools=getCurrentTime
# ①"现在几点了" → 「自动查询 查询当前日期和时间」→ 作答(零参形态)
# ② 安装 member 后(tbox-app module add member)"我的积分有多少" → 「自动查询」徽标(always + 身份门控)→ 积分卡
# ③"帮我翻译 hello" → 无「自动查询」徽标(反例:正常 LLM 工具路径)
# ④ TBOX_PRE_TOOL_TIMEOUT_MS=0 复测① → 回退为模型调用工具两轮(对比基准)
# ⑤ 刷新页面 → 历史消息仍显示「已自动获取」徽标(inputs.preTools 重建)
# ⑥"现在几点了"后再问"那纽约时间呢" → decide 不命中 → 模型正常调用(无合成工具消息,跨轮不再泛化「已调用过」)
# ⑦(装配 mall.info 供给后)"这个商场叫什么名字/在哪儿" → mall-info 注入块背景供数(silent 零前端事件;模型直调时显示「查询商场信息」)平台能力定制(007 扩展点)
平台轴(安全管道/能力管道/协议客户端)已下沉 @tbox.cn/app-sdk,模板保留 3 个注入点 + 模板源码扩展:
| 口子 | 位置 | 场景 |
|---|---|---|
| ctx.authExchanges.register(...) | @tbox.cn/app-sdk/server | 自定义登录换码器(F2 注册制:scoped 谓词路由 / 无条件兜底,机制见 docs/reference/auth.md;静态 exchanges 注入仍兼容) |
| getToolRequestContext / requireToolRequestContext | @tbox.cn/app-sdk/server | 工具 execute 第二参读取运行上下文(fail-soft / 缺失抛 AUTH_FORBIDDEN;上下文统一化) |
| createToolExecContext(reqCtx) | @tbox.cn/app-sdk/server | 测试/直调助手:构造工具 execute 第二参(散键与引擎同构) |
| createPlatformAdapters(session, overrides) | @tbox.cn/app-sdk/client | 自定义 ASR/TTS/upload/feedback 实现(contracts 接口,UI 零改动) |
| createAuthClient({ loginPlatform, scopeKey }) | @tbox.cn/app-sdk/client | 客户端登录配置(main.tsx/platform/agent-runtime.ts);应用级单例——重复调用幂等返回首建实例,首建选项生效 |
| configureRuntime({ apiBaseUrl }) | @tbox.cn/app-sdk/client | API/WS 地址注入(main.tsx;对应 VITE_API_URL) |
| bridge 透传 | @tbox.cn/app-sdk/client 的 callJsapi | 业务新能力(扫码/导航/分享等,壳子补 JSAPI 白名单) |
UI 表现与编排(组件/hooks/容器/样式/id 前缀约定)、业务意图表、演示工具、配置数据留模板源码(源码即扩展点)。
发布态消费(005 v0.4.0):仓库内本模板是根 workspace 成员子树(无独立
pnpm-workspace.yaml/lock), 平台引用catalog:+ 根pnpm-workspace.yamloverrides: workspace:*回指根packages/真源;tbox-app create从模板源(目录、pack tgz 或 registry 包@tbox.cn/app-template-agent[@version])展开生成应用—— 生成应用不含平台成员,平台包经注入 overrides(file:本地 tgz,验证态)或 registry(生产态)安装;pnpm-workspace.yaml由pnpm-workspace.template.yaml改名生成。CLI 未安装时:pnpm add -g @tbox.cn/app-cli。
开发规则见 AGENTS.md。
