@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
@@ -1,6 +1,10 @@
1
1
  # 应用 workspace 模板:tbox-app create 用它生成应用的 pnpm-workspace.yaml。
2
2
  # 本文件不触发 workspace root(非 pnpm-workspace.yaml 文件名)。
3
- # 同步规则:catalog 与根 pnpm-workspace.yaml 保持并集一致(单向复制,未引用条目无害)。
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: ^5.7.0
16
- tsx: ^4.19.0
17
- zod: ^3.24.0
18
- vite: ^6.4.3
19
- vitest: ^4.1.4
20
- react: ^18.3.1
21
- react-dom: ^18.3.1
22
- "@types/react": ^18.3.12
23
- "@types/react-dom": ^18.3.1
24
- "@types/node": ^24.0.0
25
- express: ^4.21.0
26
- "@types/express": ^4.17.21
27
- ws: ^8.18.0
28
- "@types/ws": ^8.5.0
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": ^0.1.0
37
- "@tbox.cn/app-contracts": ^0.1.0
38
- "@tbox.cn/app-contracts-mall": ^0.1.0
39
- zustand: ^4.5.5
40
- commander: ^12.1.0
41
- diff: ^7.0.0
42
- "@types/diff": ^7.0.0
43
- eslint: ^9.0.0
44
- typescript-eslint: ^8.57.2
45
- eslint-plugin-react-hooks: ^5.2.0
46
- tsup: ^8.0.0
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": ^4.3.3
52
- less: ^4.2.0
53
- "@phosphor-icons/react": ^2.1.10
54
- antd-mobile: ^5.42.3
55
- react-markdown: ^9.1.0
56
- remark-gfm: ^4.0.1
57
- "@types/supertest": ^7.2.0
58
- supertest: ^7.2.2
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
- node "$SCRIPT_DIR/check-workspace.mjs"
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
- echo "Building client (H5)..."
30
- pnpm --filter @app/client build
31
-
26
+ # 构建序列单源 = package.json build / build:client(守卫经 check 组合注入);本脚本只管环境与 install
32
27
  if [ "$SKIP_SERVER" = false ]; then
33
- echo "Building server..."
34
- pnpm --filter @app/server build
28
+ pnpm build
35
29
  else
36
- echo "Skipping server build (--skip-server)"
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.missingComponentDescriptors.length > 0) {
94
- console.error('\npackages/* 缺少 tbox.component.json(contracts / app-sdk / shared 除外):');
95
- for (const file of findings.missingComponentDescriptors) console.error(` - ${file}`);
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.component.json(002 §2.3 / 003 §4.1) */
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.component.json(002 §2.3 / 003 §4.1) */
108
- async function checkComponentDescriptors(projectDir, findings) {
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.component.json'))) {
118
- findings.missingComponentDescriptors.push(`packages/${entry.name}/`);
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
- missingComponentDescriptors: [],
148
+ missingModuleDescriptors: [],
148
149
  };
149
150
 
150
151
  await walk(projectDir, findings);
151
- await checkComponentDescriptors(projectDir, findings);
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
- echo "=== Stopping server on port $PORT ==="
10
- if lsof -i :"$PORT" > /dev/null 2>&1; then
11
- for PID in $(lsof -t -i :"$PORT"); do
12
- echo "Killing process $PID..."
13
- kill -9 "$PID" 2>/dev/null || true
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
- sleep 1
16
- lsof -i :"$PORT" > /dev/null 2>&1 && echo "Warning: Port $PORT still in use" || echo "Port $PORT is now free"
17
- else
18
- echo "Port $PORT is not in use"
19
- fi
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
- pkill -f "tsx watch src/index.ts" 2>/dev/null || true
23
- pkill -f "node.*dist/index.js" 2>/dev/null || true
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.component.json) */
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.component.json'))) {
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.component.json / 无 serverModule)不参与 modules.ts 装配,不计入声明
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.component.json')))
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
  ]);
@@ -12,6 +12,7 @@
12
12
  "isolatedModules": true,
13
13
  "allowImportingTsExtensions": true,
14
14
  "noEmit": true,
15
+ "incremental": true,
15
16
  "useDefineForClassFields": true,
16
17
  "noUnusedLocals": true,
17
18
  "noUnusedParameters": true,
@@ -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] 平台基础设施校验通过"