@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.
- package/.gitignore +6 -0
- package/.tbox/app.json +12 -3
- package/AGENTS.md +17 -6
- package/ENV.md +38 -5
- package/README.md +131 -5
- package/apps/client/package.json +3 -2
- package/apps/client/src/app.tsx +28 -0
- package/apps/client/src/cards/CardRenderer.tsx +12 -2
- 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/QuickCommands/index.tsx +9 -2
- package/apps/client/src/components/ToolCallBlock/index.less +40 -0
- package/apps/client/src/components/ToolCallBlock/index.tsx +84 -22
- 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 +72 -71
- package/apps/client/vite.config.ts +46 -26
- package/apps/server/package.json +5 -10
- package/apps/server/src/app-tools.ts +13 -0
- package/apps/server/src/app.ts +452 -0
- package/apps/server/src/cli/collect-registry.ts +11 -9
- package/apps/server/src/config/deployment.ts +47 -0
- package/apps/server/src/config/integration.ts +81 -0
- package/apps/server/src/dev.ts +81 -0
- package/apps/server/src/index.ts +24 -219
- package/apps/server/src/intent-routes.ts +11 -4
- package/apps/server/src/modules.ts +12 -5
- package/apps/server/src/pre-tools.ts +25 -0
- package/apps/server/src/tools/clock.ts +24 -0
- package/apps/server/src/tools/translate.ts +10 -4
- 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 +124 -22
- 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 +6 -5
- package/packages/module-weather/skills/weather-guide/SKILL.md +22 -0
- package/packages/module-weather/skills/weather-guide/references/dressing-tips.md +5 -0
- package/packages/module-weather/src/server/cards/weather/meta.ts +0 -1
- package/packages/module-weather/src/server/index.ts +27 -1
- package/packages/module-weather/src/server/pre-tools.ts +20 -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 -45
- 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/scripts/check-inspector.sh +0 -91
- /package/packages/module-weather/{tbox.component.json → tbox.module.json} +0 -0
|
@@ -1,6 +1,10 @@
|
|
|
1
1
|
# 应用 workspace 模板:tbox-app create 用它生成应用的 pnpm-workspace.yaml。
|
|
2
2
|
# 本文件不触发 workspace root(非 pnpm-workspace.yaml 文件名)。
|
|
3
|
-
#
|
|
3
|
+
# 同步规则(guard-workspace 恒等式校验):
|
|
4
|
+
# - catalog 键集 = agent 三模板成员 catalog: 引用并集 ∪ 平台三键(@tbox.cn/app-{sdk,contracts*}——
|
|
5
|
+
# codegen 展开恒改写为 catalog: 的完备键);键随成员引用增删,不镜像根全量。
|
|
6
|
+
# - catalog 值与根 pnpm-workspace.yaml 同键全等(scripts/release.mjs sync-catalog 维护)。
|
|
7
|
+
# - 非 catalog 顶层键与根镜像(packages/overrides 除外——发布态不带 overrides)。
|
|
4
8
|
packages:
|
|
5
9
|
- apps/*
|
|
6
10
|
- packages/*
|
|
@@ -10,51 +14,47 @@ onlyBuiltDependencies:
|
|
|
10
14
|
|
|
11
15
|
allowBuilds:
|
|
12
16
|
esbuild: true
|
|
17
|
+
lefthook: false
|
|
18
|
+
|
|
19
|
+
resolvePeersFromWorkspaceRoot: true
|
|
20
|
+
|
|
21
|
+
# 递归脚本并发上限(pnpm -r 默认 4):全速并行时多包 vitest worker 独立 transform
|
|
22
|
+
# 同一源码树,CI 峰值内存曾致 V8 fatal(SIGABRT)。本地提速可 CLI 旗标覆盖。
|
|
23
|
+
workspaceConcurrency: 2
|
|
13
24
|
|
|
14
25
|
catalog:
|
|
15
|
-
typescript:
|
|
16
|
-
tsx:
|
|
17
|
-
zod:
|
|
18
|
-
vite:
|
|
19
|
-
vitest:
|
|
20
|
-
react:
|
|
21
|
-
react-dom:
|
|
22
|
-
"@types/react":
|
|
23
|
-
"@types/react-dom":
|
|
24
|
-
"@types/node":
|
|
25
|
-
express:
|
|
26
|
-
"@types/express":
|
|
27
|
-
ws:
|
|
28
|
-
"@types/ws":
|
|
29
|
-
"@mastra/core": ^1.33.0
|
|
30
|
-
"@mastra/observability": 1.12.0
|
|
31
|
-
"@ag-ui/core": ^0.0.47
|
|
32
|
-
ai: ^6.0.0
|
|
33
|
-
"@ai-sdk/openai": ^3.0.0
|
|
34
|
-
"@ai-sdk/provider": ^3.0.10
|
|
26
|
+
typescript: 5.9.3
|
|
27
|
+
tsx: 4.23.11
|
|
28
|
+
zod: 3.25.76
|
|
29
|
+
vite: 6.4.3
|
|
30
|
+
vitest: 4.1.10
|
|
31
|
+
react: 18.3.1
|
|
32
|
+
react-dom: 18.3.1
|
|
33
|
+
"@types/react": 18.3.31
|
|
34
|
+
"@types/react-dom": 18.3.7
|
|
35
|
+
"@types/node": 24.13.3
|
|
36
|
+
express: 4.22.2
|
|
37
|
+
"@types/express": 4.17.25
|
|
38
|
+
ws: 8.21.3
|
|
39
|
+
"@types/ws": 8.18.1
|
|
35
40
|
"@tbox.cn/agent-plugin": 0.1.0
|
|
36
|
-
"@tbox.cn/app-sdk":
|
|
37
|
-
"@tbox.cn/app-contracts":
|
|
38
|
-
"@tbox.cn/app-contracts-mall":
|
|
39
|
-
zustand:
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
cors: ^2.8.5
|
|
48
|
-
jose: ^6.2.2
|
|
49
|
-
"@types/cors": ^2.8.17
|
|
41
|
+
"@tbox.cn/app-sdk": 0.17.0
|
|
42
|
+
"@tbox.cn/app-contracts": 0.9.0
|
|
43
|
+
"@tbox.cn/app-contracts-mall": 0.9.0
|
|
44
|
+
zustand: 4.5.7
|
|
45
|
+
eslint: 9.39.5
|
|
46
|
+
typescript-eslint: 8.67.0
|
|
47
|
+
eslint-plugin-react-hooks: 5.2.0
|
|
48
|
+
tsup: 8.5.1
|
|
49
|
+
cors: 2.8.6
|
|
50
|
+
jose: 6.2.8
|
|
51
|
+
"@types/cors": 2.8.19
|
|
50
52
|
"@tbox-dev-js/sdk": 0.1.18
|
|
51
|
-
"@vitejs/plugin-react":
|
|
52
|
-
less:
|
|
53
|
-
"@phosphor-icons/react":
|
|
54
|
-
antd-mobile:
|
|
55
|
-
react-markdown:
|
|
56
|
-
remark-gfm:
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
happy-dom: ^20.9.0
|
|
60
|
-
postcss-pxtorem: ^6.1.0
|
|
53
|
+
"@vitejs/plugin-react": 4.7.0
|
|
54
|
+
less: 4.8.1
|
|
55
|
+
"@phosphor-icons/react": 2.1.10
|
|
56
|
+
antd-mobile: 5.42.3
|
|
57
|
+
react-markdown: 9.1.0
|
|
58
|
+
remark-gfm: 4.0.1
|
|
59
|
+
happy-dom: 20.11.2
|
|
60
|
+
postcss-pxtorem: 6.1.0
|
package/scripts/build.sh
CHANGED
|
@@ -9,7 +9,7 @@ cd "$PROJECT_DIR"
|
|
|
9
9
|
echo "Project directory: $PROJECT_DIR"
|
|
10
10
|
|
|
11
11
|
export NODE_ENV=production
|
|
12
|
-
echo "APP_API_URL: $APP_API_URL"
|
|
12
|
+
echo "APP_API_URL: ${APP_API_URL:-<unset>}"
|
|
13
13
|
|
|
14
14
|
SKIP_SERVER=false
|
|
15
15
|
for arg in "$@"; do
|
|
@@ -17,23 +17,17 @@ for arg in "$@"; do
|
|
|
17
17
|
done
|
|
18
18
|
echo "Skip server: $SKIP_SERVER"
|
|
19
19
|
|
|
20
|
-
#
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
# 平台基础设施守卫
|
|
24
|
-
bash "$SCRIPT_DIR/check-inspector.sh"
|
|
20
|
+
# 零依赖守卫 install 前 fail-fast(幂等毫秒级;check:inspector 依赖 node_modules,经 check 组合随构建序列执行)
|
|
21
|
+
pnpm check:workspace
|
|
25
22
|
|
|
26
23
|
echo "Installing dependencies..."
|
|
27
24
|
CI=true pnpm install
|
|
28
25
|
|
|
29
|
-
|
|
30
|
-
pnpm --filter @app/client build
|
|
31
|
-
|
|
26
|
+
# 构建序列单源 = package.json build / build:client(守卫经 check 组合注入);本脚本只管环境与 install
|
|
32
27
|
if [ "$SKIP_SERVER" = false ]; then
|
|
33
|
-
|
|
34
|
-
pnpm --filter @app/server build
|
|
28
|
+
pnpm build
|
|
35
29
|
else
|
|
36
|
-
|
|
30
|
+
pnpm build:client
|
|
37
31
|
fi
|
|
38
32
|
|
|
39
33
|
echo "All builds completed successfully!"
|
|
@@ -25,6 +25,7 @@ const nestedLockfiles = new Set([
|
|
|
25
25
|
'bun.lockb',
|
|
26
26
|
'package-lock.json',
|
|
27
27
|
'pnpm-lock.yaml',
|
|
28
|
+
'pnpm-lock.template.yaml',
|
|
28
29
|
'yarn.lock',
|
|
29
30
|
]);
|
|
30
31
|
const viteConfigPattern = /^vite\.config\.(?:js|mjs|cjs|ts|mts|cts)$/;
|
|
@@ -90,22 +91,22 @@ function printFailure(findings) {
|
|
|
90
91
|
console.error('\n检测到 apps/client/ 之外的 Vite 项目配置:');
|
|
91
92
|
for (const file of findings.detachedViteConfigs) console.error(` - ${file}`);
|
|
92
93
|
}
|
|
93
|
-
if (findings.
|
|
94
|
-
console.error('\npackages/* 缺少 tbox.
|
|
95
|
-
for (const file of findings.
|
|
94
|
+
if (findings.missingModuleDescriptors.length > 0) {
|
|
95
|
+
console.error('\npackages/* 缺少 tbox.module.json(contracts / app-sdk / shared 除外):');
|
|
96
|
+
for (const file of findings.missingModuleDescriptors) console.error(` - ${file}`);
|
|
96
97
|
}
|
|
97
98
|
|
|
98
99
|
console.error('');
|
|
99
100
|
process.exitCode = 1;
|
|
100
101
|
}
|
|
101
102
|
|
|
102
|
-
/** 豁免:SDK/契约包无 tbox.
|
|
103
|
+
/** 豁免:SDK/契约包无 tbox.module.json(002 §2.3 / 003 §4.1) */
|
|
103
104
|
function isExemptPackage(dirName) {
|
|
104
105
|
return dirName === 'app-sdk' || dirName === 'shared' || dirName.startsWith('contracts');
|
|
105
106
|
}
|
|
106
107
|
|
|
107
|
-
/** 校验 packages/* 每个业务包必须有 tbox.
|
|
108
|
-
async function
|
|
108
|
+
/** 校验 packages/* 每个业务包必须有 tbox.module.json(002 §2.3 / 003 §4.1) */
|
|
109
|
+
async function checkModuleDescriptors(projectDir, findings) {
|
|
109
110
|
const packagesDir = path.join(projectDir, 'packages');
|
|
110
111
|
if (!existsSync(packagesDir)) return;
|
|
111
112
|
const entries = await readdir(packagesDir, { withFileTypes: true });
|
|
@@ -114,8 +115,8 @@ async function checkComponentDescriptors(projectDir, findings) {
|
|
|
114
115
|
if (isExemptPackage(entry.name)) continue;
|
|
115
116
|
const pkgDir = path.join(packagesDir, entry.name);
|
|
116
117
|
if (!existsSync(path.join(pkgDir, 'package.json'))) continue;
|
|
117
|
-
if (!existsSync(path.join(pkgDir, 'tbox.
|
|
118
|
-
findings.
|
|
118
|
+
if (!existsSync(path.join(pkgDir, 'tbox.module.json'))) {
|
|
119
|
+
findings.missingModuleDescriptors.push(`packages/${entry.name}/`);
|
|
119
120
|
}
|
|
120
121
|
}
|
|
121
122
|
}
|
|
@@ -144,11 +145,11 @@ async function main() {
|
|
|
144
145
|
const findings = {
|
|
145
146
|
nestedLockfiles: [],
|
|
146
147
|
detachedViteConfigs: [],
|
|
147
|
-
|
|
148
|
+
missingModuleDescriptors: [],
|
|
148
149
|
};
|
|
149
150
|
|
|
150
151
|
await walk(projectDir, findings);
|
|
151
|
-
await
|
|
152
|
+
await checkModuleDescriptors(projectDir, findings);
|
|
152
153
|
|
|
153
154
|
if (Object.values(findings).some((items) => items.length > 0)) {
|
|
154
155
|
printFailure(findings);
|
package/scripts/stop.sh
CHANGED
|
@@ -1,26 +1,60 @@
|
|
|
1
1
|
#!/bin/bash
|
|
2
|
+
# 停止本应用全部进程。杀面收敛原则(docs/postmortem/0001):
|
|
3
|
+
# 1) cmdline 匹配:锚定本项目路径,逐字符 ERE 字面量化(BSD/GNU 无歧义),[^ ]* 锁单参数
|
|
4
|
+
# 2) 端口定位:只取 LISTEN 态——持有连接的客户端(编排探活/代理父进程)不入杀面
|
|
5
|
+
# PORT 契约:须与启动环境一致(生产形态 cmdline 不含路径,端口是唯一定位手段)
|
|
6
|
+
# 边界:根级浅层 <P>/node_modules/.bin/x 直调形态不在 pattern 内(模板内无此调用形态;.pnpm 深路径匹配)
|
|
2
7
|
|
|
3
8
|
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
|
|
4
9
|
PROJECT_DIR="$(dirname "$SCRIPT_DIR")"
|
|
5
10
|
cd "$PROJECT_DIR"
|
|
6
|
-
|
|
7
11
|
PORT=${PORT:-8000}
|
|
8
12
|
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
13
|
+
esc_ere() { # 元字符以 [c] 字面量化;单遍扫描防插入物自污染(BSD \+ 为量词,已实证)。
|
|
14
|
+
# 特例:^ 用 [\^]——裸置双引擎皆为行首锚(实证),[^] 则是非法未闭合括号表达式;\ 用 [\\]
|
|
15
|
+
local s="$1" i c out=""
|
|
16
|
+
for ((i=0; i<${#s}; i++)); do
|
|
17
|
+
c=${s:$i:1}
|
|
18
|
+
case $c in
|
|
19
|
+
'.'|'+'|'*'|'?'|'$'|'('|')'|'{'|'}'|'|'|'['|']') out+="[$c]" ;;
|
|
20
|
+
'^') out+='[\^]' ;;
|
|
21
|
+
'\') out+='[\\]' ;;
|
|
22
|
+
*) out+=$c ;;
|
|
23
|
+
esac
|
|
14
24
|
done
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
25
|
+
printf '%s' "$out"
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
LISTEN_TOOLS_OK=1 # 主作用域预计算(listen_pids 经命令替换运行于子 shell,内赋值不外传——R4b 实证)
|
|
29
|
+
command -v lsof >/dev/null 2>&1 || command -v ss >/dev/null 2>&1 || LISTEN_TOOLS_OK=0
|
|
30
|
+
listen_pids() { # 两级降级:lsof(macOS 恒有)→ ss(iproute2);参数化端口便于多端口扩展
|
|
31
|
+
local p=${1:-$PORT}
|
|
32
|
+
if command -v lsof >/dev/null 2>&1; then
|
|
33
|
+
lsof -nP -t -i :"$p" -sTCP:LISTEN 2>/dev/null | sort -u
|
|
34
|
+
elif command -v ss >/dev/null 2>&1; then
|
|
35
|
+
ss -ltnpH "sport = :$p" 2>/dev/null | grep -o 'pid=[0-9]*' | cut -d= -f2 | sort -u
|
|
36
|
+
else
|
|
37
|
+
echo "Warning: lsof/ss 均不可用,端口清理跳过(cmdline 兜底仍执行)" >&2
|
|
38
|
+
fi
|
|
39
|
+
}
|
|
20
40
|
|
|
21
41
|
echo "=== Cleaning up residual processes ==="
|
|
22
|
-
|
|
23
|
-
pkill -f "
|
|
24
|
-
pkill -f "vite" 2>/dev/null || true
|
|
42
|
+
ESC=$(esc_ere "$PROJECT_DIR")
|
|
43
|
+
pkill -TERM -f "$ESC/[^ ]*/node_modules" 2>/dev/null || true # 先于端口杀:杜绝 watch 复活窗口
|
|
25
44
|
|
|
45
|
+
PIDS=$(listen_pids "$PORT")
|
|
46
|
+
if [ -n "$PIDS" ]; then
|
|
47
|
+
echo "=== Stopping server on port $PORT ==="
|
|
48
|
+
for PID in $PIDS; do echo "Killing process $PID..."; kill -9 "$PID" 2>/dev/null || true; done
|
|
49
|
+
sleep 1
|
|
50
|
+
if [ -n "$(listen_pids "$PORT")" ]; then
|
|
51
|
+
echo "Warning: Port $PORT still in use"
|
|
52
|
+
else
|
|
53
|
+
echo "Port $PORT is now free"
|
|
54
|
+
fi
|
|
55
|
+
elif [ "$LISTEN_TOOLS_OK" = 1 ]; then
|
|
56
|
+
echo "=== Port $PORT: no listener ==="
|
|
57
|
+
else
|
|
58
|
+
echo "=== Port $PORT: listener status unknown(定位工具缺失)==="
|
|
59
|
+
fi
|
|
26
60
|
echo "=== All services stopped ==="
|
|
@@ -43,7 +43,7 @@ function readManifestModules() {
|
|
|
43
43
|
}
|
|
44
44
|
}
|
|
45
45
|
|
|
46
|
-
/** 收集 packages/* 下本地模块 id(有 tbox.
|
|
46
|
+
/** 收集 packages/* 下本地模块 id(有 tbox.module.json) */
|
|
47
47
|
async function listLocalModuleIds() {
|
|
48
48
|
const packagesDir = path.join(appDir, 'packages');
|
|
49
49
|
if (!existsSync(packagesDir)) return [];
|
|
@@ -51,7 +51,7 @@ async function listLocalModuleIds() {
|
|
|
51
51
|
const ids = [];
|
|
52
52
|
for (const entry of entries) {
|
|
53
53
|
if (!entry.isDirectory()) continue;
|
|
54
|
-
if (existsSync(path.join(packagesDir, entry.name, 'tbox.
|
|
54
|
+
if (existsSync(path.join(packagesDir, entry.name, 'tbox.module.json'))) {
|
|
55
55
|
ids.push(entry.name);
|
|
56
56
|
}
|
|
57
57
|
}
|
|
@@ -100,12 +100,12 @@ async function main() {
|
|
|
100
100
|
}
|
|
101
101
|
|
|
102
102
|
// ① 漂移 = 0:声明(manifest + 本地模块)vs 服务端 modules.ts 实际装配
|
|
103
|
-
// 契约包(无 tbox.
|
|
103
|
+
// 契约包(无 tbox.module.json / 无 serverModule)不参与 modules.ts 装配,不计入声明
|
|
104
104
|
// (客户端 modules.ts 装配漂移由 tbox-app module doctor 全量校验兜底)
|
|
105
105
|
const npmModules = readManifestModules();
|
|
106
106
|
const declared = new Set([
|
|
107
107
|
...npmModules
|
|
108
|
-
.filter((m) => existsSync(path.join(appDir, 'packages', m.id, 'tbox.
|
|
108
|
+
.filter((m) => existsSync(path.join(appDir, 'packages', m.id, 'tbox.module.json')))
|
|
109
109
|
.map((m) => m.id),
|
|
110
110
|
...(await listLocalModuleIds()),
|
|
111
111
|
]);
|
package/tsconfig.base.json
CHANGED
|
@@ -1,110 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* ============================================================================
|
|
3
|
-
* 平台配套 babel plugin - 给每个 JSXElement 注入 data-code-inspect-* 属性。
|
|
4
|
-
* 配合 tbox 平台 inspector runtime(/__vs_inspector__.js)使用,让 host 端微调浮层
|
|
5
|
-
* 能拿到源码位置,直接走「直改源码」快路径,而不是把意图发回 LLM 重新生成代码。
|
|
6
|
-
*
|
|
7
|
-
* 注入的属性(与 inspector runtime 期望严格对齐,名字别改):
|
|
8
|
-
* data-code-inspect-loc-filepath = 绝对路径
|
|
9
|
-
* data-code-inspect-loc-line = "{line}:{column}" (1-based, 来自 JSX opening tag)
|
|
10
|
-
* data-code-inspect-jsx-tag = JSX 标签名 (如 "div" / "Form.Item")
|
|
11
|
-
* data-code-inspect-component = 所在组件名 (爬 AST 找最近的函数/类组件)
|
|
12
|
-
*
|
|
13
|
-
* Vite 接入:在 vite.config.ts 里 `react({ babel: { plugins: [...] } })`,仅 dev(command==='serve')启用。
|
|
14
|
-
* ============================================================================
|
|
15
|
-
*/
|
|
16
|
-
|
|
17
|
-
const LOC_FILEPATH_ATTRIBUTE_NAME = 'data-code-inspect-loc-filepath';
|
|
18
|
-
const LOC_LINE_ATTRIBUTE_NAME = 'data-code-inspect-loc-line';
|
|
19
|
-
const TAG_ATTRIBUTE_NAME = 'data-code-inspect-jsx-tag';
|
|
20
|
-
const COMPONENT_ATTRIBUTE_NAME = 'data-code-inspect-component';
|
|
21
|
-
|
|
22
|
-
const PLUGIN_NAME = 'code-inspect-plugin';
|
|
23
|
-
|
|
24
|
-
function getJsxName(nameNode) {
|
|
25
|
-
if (nameNode.type === 'JSXIdentifier') return nameNode.name;
|
|
26
|
-
if (nameNode.type === 'JSXMemberExpression') {
|
|
27
|
-
return getJsxName(nameNode.object) + '.' + nameNode.property.name;
|
|
28
|
-
}
|
|
29
|
-
return '';
|
|
30
|
-
}
|
|
31
|
-
|
|
32
|
-
function getCurrentComponentName(path) {
|
|
33
|
-
let current = path;
|
|
34
|
-
while (current) {
|
|
35
|
-
if (current.isFunctionDeclaration() && current.node.id) {
|
|
36
|
-
return current.node.id.name;
|
|
37
|
-
}
|
|
38
|
-
if (
|
|
39
|
-
current.isVariableDeclarator() &&
|
|
40
|
-
current.node.id &&
|
|
41
|
-
current.node.init
|
|
42
|
-
) {
|
|
43
|
-
const init = current.node.init;
|
|
44
|
-
if (
|
|
45
|
-
init.type === 'ArrowFunctionExpression' ||
|
|
46
|
-
init.type === 'FunctionExpression'
|
|
47
|
-
) {
|
|
48
|
-
return current.node.id.name;
|
|
49
|
-
}
|
|
50
|
-
}
|
|
51
|
-
if (current.isClassDeclaration() && current.node.id) {
|
|
52
|
-
return current.node.id.name;
|
|
53
|
-
}
|
|
54
|
-
if (
|
|
55
|
-
(current.isFunctionExpression() ||
|
|
56
|
-
current.isArrowFunctionExpression() ||
|
|
57
|
-
current.isCallExpression()) &&
|
|
58
|
-
current.parent &&
|
|
59
|
-
current.parent.type === 'VariableDeclarator' &&
|
|
60
|
-
current.parent.id
|
|
61
|
-
) {
|
|
62
|
-
return current.parent.id.name;
|
|
63
|
-
}
|
|
64
|
-
current = current.parentPath;
|
|
65
|
-
}
|
|
66
|
-
return 'Unknown';
|
|
67
|
-
}
|
|
68
|
-
|
|
69
|
-
function CodeInspectPlugin(babel) {
|
|
70
|
-
const t = babel.types;
|
|
71
|
-
|
|
72
|
-
return {
|
|
73
|
-
name: PLUGIN_NAME,
|
|
74
|
-
visitor: {
|
|
75
|
-
JSXElement(path) {
|
|
76
|
-
const node = path.node;
|
|
77
|
-
const nodeName = getJsxName(node.openingElement.name);
|
|
78
|
-
if (!nodeName) return;
|
|
79
|
-
if (!node.loc) return;
|
|
80
|
-
|
|
81
|
-
const filename = path.hub.file.opts.filename || 'unknown';
|
|
82
|
-
const { line, column } = node.loc.start;
|
|
83
|
-
const componentName = getCurrentComponentName(path);
|
|
84
|
-
|
|
85
|
-
node.openingElement.attributes.push(
|
|
86
|
-
t.jsxAttribute(
|
|
87
|
-
t.jsxIdentifier(LOC_FILEPATH_ATTRIBUTE_NAME),
|
|
88
|
-
t.stringLiteral(filename),
|
|
89
|
-
),
|
|
90
|
-
t.jsxAttribute(
|
|
91
|
-
t.jsxIdentifier(LOC_LINE_ATTRIBUTE_NAME),
|
|
92
|
-
t.stringLiteral(`${line}:${column}`),
|
|
93
|
-
),
|
|
94
|
-
t.jsxAttribute(
|
|
95
|
-
t.jsxIdentifier(TAG_ATTRIBUTE_NAME),
|
|
96
|
-
t.stringLiteral(nodeName),
|
|
97
|
-
),
|
|
98
|
-
t.jsxAttribute(
|
|
99
|
-
t.jsxIdentifier(COMPONENT_ATTRIBUTE_NAME),
|
|
100
|
-
t.stringLiteral(componentName),
|
|
101
|
-
),
|
|
102
|
-
);
|
|
103
|
-
},
|
|
104
|
-
},
|
|
105
|
-
};
|
|
106
|
-
}
|
|
107
|
-
|
|
108
|
-
module.exports = CodeInspectPlugin;
|
|
109
|
-
module.exports.CodeInspectPlugin = CodeInspectPlugin;
|
|
110
|
-
module.exports.PLUGIN_NAME = PLUGIN_NAME;
|
|
@@ -1,97 +0,0 @@
|
|
|
1
|
-
import { useCallback, useRef, useState } from 'react';
|
|
2
|
-
|
|
3
|
-
export type PullStatus = 'idle' | 'pulling' | 'ready' | 'loading' | 'noMore';
|
|
4
|
-
|
|
5
|
-
const DAMPING = 0.4;
|
|
6
|
-
const THRESHOLD = 60;
|
|
7
|
-
|
|
8
|
-
interface Options {
|
|
9
|
-
isAtTopRef: React.MutableRefObject<boolean>;
|
|
10
|
-
isLoadingHistory: boolean;
|
|
11
|
-
hasMore: boolean;
|
|
12
|
-
onLoad: () => Promise<void>;
|
|
13
|
-
}
|
|
14
|
-
|
|
15
|
-
/** 列表顶部下拉加载历史(纯 H5 触摸,去 Taro)。 */
|
|
16
|
-
export function usePullDownHistory({ isAtTopRef, isLoadingHistory, hasMore, onLoad }: Options) {
|
|
17
|
-
const [pullStatus, setPullStatus] = useState<PullStatus>('idle');
|
|
18
|
-
const [pullDistance, setPullDistance] = useState(0);
|
|
19
|
-
const startYRef = useRef(0);
|
|
20
|
-
const isPullingRef = useRef(false);
|
|
21
|
-
|
|
22
|
-
const handleTouchStart = useCallback(
|
|
23
|
-
(e: React.TouchEvent) => {
|
|
24
|
-
if (isLoadingHistory || pullStatus === 'loading') return;
|
|
25
|
-
const touch = e.touches[0];
|
|
26
|
-
if (!touch) return;
|
|
27
|
-
startYRef.current = touch.clientY;
|
|
28
|
-
isPullingRef.current = isAtTopRef.current;
|
|
29
|
-
},
|
|
30
|
-
[isLoadingHistory, pullStatus, isAtTopRef],
|
|
31
|
-
);
|
|
32
|
-
|
|
33
|
-
const handleTouchMove = useCallback(
|
|
34
|
-
(e: React.TouchEvent) => {
|
|
35
|
-
if (!isPullingRef.current) return;
|
|
36
|
-
if (isLoadingHistory || pullStatus === 'loading') return;
|
|
37
|
-
if (!hasMore && pullStatus !== 'pulling' && pullStatus !== 'ready') return;
|
|
38
|
-
|
|
39
|
-
const touch = e.touches[0];
|
|
40
|
-
if (!touch) return;
|
|
41
|
-
const deltaY = touch.clientY - startYRef.current;
|
|
42
|
-
|
|
43
|
-
if (deltaY <= 0) {
|
|
44
|
-
if (pullStatus !== 'idle') {
|
|
45
|
-
setPullStatus('idle');
|
|
46
|
-
setPullDistance(0);
|
|
47
|
-
}
|
|
48
|
-
return;
|
|
49
|
-
}
|
|
50
|
-
|
|
51
|
-
try {
|
|
52
|
-
e.preventDefault();
|
|
53
|
-
} catch {
|
|
54
|
-
/* passive listener, ignore */
|
|
55
|
-
}
|
|
56
|
-
|
|
57
|
-
const distance = deltaY * DAMPING;
|
|
58
|
-
if (!hasMore) {
|
|
59
|
-
setPullStatus('noMore');
|
|
60
|
-
setPullDistance(Math.min(distance, THRESHOLD));
|
|
61
|
-
return;
|
|
62
|
-
}
|
|
63
|
-
setPullDistance(distance);
|
|
64
|
-
setPullStatus(distance > THRESHOLD ? 'ready' : 'pulling');
|
|
65
|
-
},
|
|
66
|
-
[isLoadingHistory, pullStatus, hasMore],
|
|
67
|
-
);
|
|
68
|
-
|
|
69
|
-
const handleTouchEnd = useCallback(() => {
|
|
70
|
-
if (!isPullingRef.current) return;
|
|
71
|
-
isPullingRef.current = false;
|
|
72
|
-
|
|
73
|
-
if (pullStatus === 'ready' && hasMore) {
|
|
74
|
-
setPullStatus('loading');
|
|
75
|
-
setPullDistance(THRESHOLD);
|
|
76
|
-
onLoad()
|
|
77
|
-
.then(() => {
|
|
78
|
-
setPullStatus('idle');
|
|
79
|
-
setPullDistance(0);
|
|
80
|
-
})
|
|
81
|
-
.catch(() => {
|
|
82
|
-
setPullStatus('idle');
|
|
83
|
-
setPullDistance(0);
|
|
84
|
-
});
|
|
85
|
-
} else if (pullStatus === 'noMore') {
|
|
86
|
-
setTimeout(() => {
|
|
87
|
-
setPullStatus('idle');
|
|
88
|
-
setPullDistance(0);
|
|
89
|
-
}, 1500);
|
|
90
|
-
} else {
|
|
91
|
-
setPullStatus('idle');
|
|
92
|
-
setPullDistance(0);
|
|
93
|
-
}
|
|
94
|
-
}, [pullStatus, hasMore, onLoad]);
|
|
95
|
-
|
|
96
|
-
return { pullStatus, pullDistance, handleTouchStart, handleTouchMove, handleTouchEnd };
|
|
97
|
-
}
|
|
@@ -1,91 +0,0 @@
|
|
|
1
|
-
#!/bin/bash
|
|
2
|
-
# ============================================================================
|
|
3
|
-
# 平台基础设施守卫 — 校验 tbox 微调(Inspector)注入未被破坏。
|
|
4
|
-
# Inspector 已下沉 @tbox.cn/app-sdk/platform(003 §3.1),本脚本断言同步更新。
|
|
5
|
-
# 支持两种上下文:
|
|
6
|
-
# 1. 根 workspace 成员形态(模板子树):向上探测 pnpm-workspace.yaml 取 SDK 真源
|
|
7
|
-
# 2. 生成应用发布态:app-sdk 经 node_modules 解析(检查包存在 + exports 含 ./platform)
|
|
8
|
-
# ============================================================================
|
|
9
|
-
set -e
|
|
10
|
-
|
|
11
|
-
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
|
|
12
|
-
PROJECT_DIR="$(dirname "$SCRIPT_DIR")"
|
|
13
|
-
|
|
14
|
-
INDEX_FILE="$PROJECT_DIR/apps/server/src/index.ts"
|
|
15
|
-
AGENT_HTML="$PROJECT_DIR/apps/client/index.html"
|
|
16
|
-
VITE_CFG="$PROJECT_DIR/apps/client/vite.config.ts"
|
|
17
|
-
|
|
18
|
-
fail() {
|
|
19
|
-
echo ""
|
|
20
|
-
echo "❌ [check-inspector] 平台基础设施校验失败:$1"
|
|
21
|
-
echo ""
|
|
22
|
-
echo " tbox 平台微调能力(点选元素 → 写入聊天框)是平台基础设施,AI agent 不得删除。"
|
|
23
|
-
echo " 必须保留:"
|
|
24
|
-
echo " 1. @tbox.cn/app-sdk/platform 存在并导出 installInspector(RUNTIME_PATH 约定不变)"
|
|
25
|
-
echo " 2. apps/server/src/index.ts 从 @tbox.cn/app-sdk/platform import 并调用 installInspector(app)"
|
|
26
|
-
echo " 3. apps/client/index.html 含 inspector 脚本标签"
|
|
27
|
-
echo " 4. apps/client/vite.config.ts 接入 code-inspect plugin"
|
|
28
|
-
echo ""
|
|
29
|
-
exit 1
|
|
30
|
-
}
|
|
31
|
-
|
|
32
|
-
# 定位 app-sdk 源码路径:模板内 → workspace 根 → node_modules(发布态)
|
|
33
|
-
SDK_SRC=""
|
|
34
|
-
for candidate in \
|
|
35
|
-
"$PROJECT_DIR/packages/app-sdk" \
|
|
36
|
-
"$PROJECT_DIR/../packages/app-sdk" \
|
|
37
|
-
"$PROJECT_DIR/../../packages/app-sdk"; do
|
|
38
|
-
if [ -f "$candidate/src/platform/inspector.ts" ]; then
|
|
39
|
-
SDK_SRC="$candidate"
|
|
40
|
-
break
|
|
41
|
-
fi
|
|
42
|
-
done
|
|
43
|
-
|
|
44
|
-
if [ -n "$SDK_SRC" ]; then
|
|
45
|
-
# 1. inspector.ts 存在 + 导出 installInspector + RUNTIME_PATH(RUNTIME_PATH 约定不变)
|
|
46
|
-
INSPECTOR_FILE="$SDK_SRC/src/platform/inspector.ts"
|
|
47
|
-
grep -qE "export\s+(async\s+)?function\s+installInspector" "$INSPECTOR_FILE" \
|
|
48
|
-
|| fail "inspector.ts 未导出 installInspector"
|
|
49
|
-
grep -qE "RUNTIME_PATH\s*=\s*['\"]/__vs_inspector__\.js['\"]" "$INSPECTOR_FILE" \
|
|
50
|
-
|| fail "inspector.ts 中 RUNTIME_PATH 被改动"
|
|
51
|
-
else
|
|
52
|
-
# 发布态:@tbox.cn/app-sdk 经 node_modules 解析
|
|
53
|
-
SDK_PKG=""
|
|
54
|
-
for base in "$PROJECT_DIR" "$PROJECT_DIR/../.."; do
|
|
55
|
-
if [ -d "$base/node_modules/@tbox.cn/app-sdk" ]; then
|
|
56
|
-
SDK_PKG="$base/node_modules/@tbox.cn/app-sdk"
|
|
57
|
-
break
|
|
58
|
-
fi
|
|
59
|
-
done
|
|
60
|
-
[ -n "$SDK_PKG" ] || fail "未找到 @tbox.cn/app-sdk(源码形态或 node_modules 发布态)"
|
|
61
|
-
node -e "
|
|
62
|
-
const fs = require('fs');
|
|
63
|
-
const pkg = JSON.parse(fs.readFileSync('$SDK_PKG/package.json', 'utf8'));
|
|
64
|
-
const ex = pkg.exports || {};
|
|
65
|
-
if (!ex['./platform']) { console.error('platform export missing'); process.exit(1); }
|
|
66
|
-
" || fail "@tbox.cn/app-sdk 的 package.json 未导出 ./platform"
|
|
67
|
-
fi
|
|
68
|
-
|
|
69
|
-
# 2. index.ts 从 @tbox.cn/app-sdk/platform import 并调用 installInspector(app)
|
|
70
|
-
grep -qE "from\s+['\"]@tbox\.cn/app-sdk/platform['\"]" "$INDEX_FILE" \
|
|
71
|
-
|| fail "index.ts 未从 @tbox.cn/app-sdk/platform import"
|
|
72
|
-
grep -qE "installInspector\s*\(\s*app\s*[,)]" "$INDEX_FILE" \
|
|
73
|
-
|| fail "index.ts 未调用 installInspector(app)"
|
|
74
|
-
|
|
75
|
-
# 3. client/index.html 内置 inspector 脚本标签
|
|
76
|
-
[ -f "$AGENT_HTML" ] || fail "缺失文件 apps/client/index.html"
|
|
77
|
-
grep -qE "/__vs_inspector__\.js" "$AGENT_HTML" \
|
|
78
|
-
|| fail "apps/client/index.html 缺少 inspector 脚本标签"
|
|
79
|
-
|
|
80
|
-
# 4. vite.config 接入 code-inspect plugin
|
|
81
|
-
[ -f "$VITE_CFG" ] || fail "缺失文件 apps/client/vite.config.ts"
|
|
82
|
-
grep -qE "codeInspectPlugin" "$VITE_CFG" \
|
|
83
|
-
|| fail "apps/client/vite.config.ts 未接入 code-inspect plugin"
|
|
84
|
-
|
|
85
|
-
# 5. 源码标注:code-inspect babel plugin 存在
|
|
86
|
-
CODE_INSPECT="$PROJECT_DIR/apps/client/babel-plugins/code-inspect.cjs"
|
|
87
|
-
[ -f "$CODE_INSPECT" ] || fail "缺失文件 apps/client/babel-plugins/code-inspect.cjs"
|
|
88
|
-
grep -qE "data-code-inspect-loc-filepath" "$CODE_INSPECT" \
|
|
89
|
-
|| fail "code-inspect.cjs 未注入 data-code-inspect-* 属性"
|
|
90
|
-
|
|
91
|
-
echo "✅ [check-inspector] 平台基础设施校验通过"
|
|
File without changes
|