@coze-arch/cli 0.0.13 → 0.0.14-alpha.c52ee4

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 (76) hide show
  1. package/lib/__templates__/expo/AGENTS.md +15 -7
  2. package/lib/__templates__/expo/README.md +15 -7
  3. package/lib/__templates__/expo/client/eslint.config.mjs +3 -0
  4. package/lib/__templates__/expo/eslint-plugins/expo/index.js +9 -0
  5. package/lib/__templates__/expo/eslint-plugins/expo/rule.js +105 -0
  6. package/lib/__templates__/expo/eslint-plugins/expo/tech.md +108 -0
  7. package/lib/__templates__/nextjs/AGENTS.md +9 -0
  8. package/lib/__templates__/nextjs/eslint.config.mjs +15 -0
  9. package/lib/__templates__/pi-agent/.coze +10 -0
  10. package/lib/__templates__/pi-agent/AGENTS.md +150 -0
  11. package/lib/__templates__/pi-agent/README.md +155 -0
  12. package/lib/__templates__/pi-agent/_gitignore +3 -0
  13. package/lib/__templates__/pi-agent/docs/project-overview.md +273 -0
  14. package/lib/__templates__/pi-agent/docs/user/getting-started.md +46 -0
  15. package/lib/__templates__/pi-agent/package.json +52 -0
  16. package/lib/__templates__/pi-agent/pnpm-lock.yaml +7840 -0
  17. package/lib/__templates__/pi-agent/scripts/dev.sh +14 -0
  18. package/lib/__templates__/pi-agent/scripts/prepare.sh +2 -0
  19. package/lib/__templates__/pi-agent/src/agent.ts +367 -0
  20. package/lib/__templates__/pi-agent/src/channels/feishu/index.ts +760 -0
  21. package/lib/__templates__/pi-agent/src/channels/feishu/streaming-card.ts +297 -0
  22. package/lib/__templates__/pi-agent/src/channels/wechat/index.ts +171 -0
  23. package/lib/__templates__/pi-agent/src/config.ts +596 -0
  24. package/lib/__templates__/pi-agent/src/core.ts +218 -0
  25. package/lib/__templates__/pi-agent/src/dashboard/api/channels.ts +148 -0
  26. package/lib/__templates__/pi-agent/src/dashboard/api/docs.ts +204 -0
  27. package/lib/__templates__/pi-agent/src/dashboard/api/models.ts +141 -0
  28. package/lib/__templates__/pi-agent/src/dashboard/api/overview.ts +33 -0
  29. package/lib/__templates__/pi-agent/src/dashboard/config-store.ts +64 -0
  30. package/lib/__templates__/pi-agent/src/dashboard/index.ts +39 -0
  31. package/lib/__templates__/pi-agent/src/dashboard/server.ts +622 -0
  32. package/lib/__templates__/pi-agent/src/dashboard/types.ts +25 -0
  33. package/lib/__templates__/pi-agent/src/dashboard/web/index.html +13 -0
  34. package/lib/__templates__/pi-agent/src/dashboard/web/postcss.config.cjs +7 -0
  35. package/lib/__templates__/pi-agent/src/dashboard/web/src/components/app-layout.tsx +186 -0
  36. package/lib/__templates__/pi-agent/src/dashboard/web/src/components/page-title.tsx +17 -0
  37. package/lib/__templates__/pi-agent/src/dashboard/web/src/components/ui/alert.tsx +22 -0
  38. package/lib/__templates__/pi-agent/src/dashboard/web/src/components/ui/badge.tsx +25 -0
  39. package/lib/__templates__/pi-agent/src/dashboard/web/src/components/ui/button.tsx +40 -0
  40. package/lib/__templates__/pi-agent/src/dashboard/web/src/components/ui/card.tsx +29 -0
  41. package/lib/__templates__/pi-agent/src/dashboard/web/src/components/ui/input.tsx +18 -0
  42. package/lib/__templates__/pi-agent/src/dashboard/web/src/components/ui/label.tsx +8 -0
  43. package/lib/__templates__/pi-agent/src/dashboard/web/src/components/ui/select.tsx +80 -0
  44. package/lib/__templates__/pi-agent/src/dashboard/web/src/components/ui/separator.tsx +23 -0
  45. package/lib/__templates__/pi-agent/src/dashboard/web/src/hooks/use-fetch.ts +32 -0
  46. package/lib/__templates__/pi-agent/src/dashboard/web/src/hooks/use-local-storage-state.ts +23 -0
  47. package/lib/__templates__/pi-agent/src/dashboard/web/src/main.tsx +30 -0
  48. package/lib/__templates__/pi-agent/src/dashboard/web/src/pages/channels-page.tsx +188 -0
  49. package/lib/__templates__/pi-agent/src/dashboard/web/src/pages/chat-page.tsx +451 -0
  50. package/lib/__templates__/pi-agent/src/dashboard/web/src/pages/docs-page.tsx +65 -0
  51. package/lib/__templates__/pi-agent/src/dashboard/web/src/pages/models-page.tsx +122 -0
  52. package/lib/__templates__/pi-agent/src/dashboard/web/src/pages/overview-page.tsx +134 -0
  53. package/lib/__templates__/pi-agent/src/dashboard/web/src/services/chat-ws-service.ts +167 -0
  54. package/lib/__templates__/pi-agent/src/dashboard/web/src/styles.css +294 -0
  55. package/lib/__templates__/pi-agent/src/dashboard/web/src/utils/index.ts +11 -0
  56. package/lib/__templates__/pi-agent/src/dashboard/web/tsconfig.json +13 -0
  57. package/lib/__templates__/pi-agent/src/dashboard/web/vite.config.ts +17 -0
  58. package/lib/__templates__/pi-agent/src/index.ts +123 -0
  59. package/lib/__templates__/pi-agent/src/session-store.ts +223 -0
  60. package/lib/__templates__/pi-agent/template.config.js +45 -0
  61. package/lib/__templates__/pi-agent/tests/config.test.ts +292 -0
  62. package/lib/__templates__/pi-agent/tests/dashboard-docs-api.test.ts +125 -0
  63. package/lib/__templates__/pi-agent/tests/dashboard-models-api.test.ts +171 -0
  64. package/lib/__templates__/pi-agent/tests/feishu-channel.test.ts +149 -0
  65. package/lib/__templates__/pi-agent/tests/feishu-streaming-card.test.ts +15 -0
  66. package/lib/__templates__/pi-agent/tests/session-store.test.ts +61 -0
  67. package/lib/__templates__/pi-agent/tests/smoke/run-smoke.ts +275 -0
  68. package/lib/__templates__/pi-agent/tsconfig.json +20 -0
  69. package/lib/__templates__/pi-agent/types/larksuiteoapi-node-sdk.d.ts +113 -0
  70. package/lib/__templates__/taro/pnpm-lock.yaml +24 -14
  71. package/lib/__templates__/taro/server/package.json +0 -2
  72. package/lib/__templates__/taro/src/presets/dev-debug.ts +2 -2
  73. package/lib/__templates__/templates.json +24 -0
  74. package/lib/__templates__/vite/AGENTS.md +5 -0
  75. package/lib/cli.js +1 -1
  76. package/package.json +1 -1
@@ -152,17 +152,25 @@ export default function TabLayout() {
152
152
  '--color-border',
153
153
  ]) as string[];
154
154
 
155
+ let tabBarStyle = {
156
+ backgroundColor: background,
157
+ borderTopWidth: 1,
158
+ borderTopColor: border,
159
+ };
160
+
161
+ // 用于修复 Web 上高度异常的问题(这个 if 逻辑必须添加)
162
+ if (Platform.OS === 'web') {
163
+ tabBarStyle = {
164
+ ...tabBarStyle,
165
+ height: 'auto',
166
+ }
167
+ }
168
+
155
169
  return (
156
170
  <Tabs
157
171
  screenOptions={{
158
172
  headerShown: false,
159
- tabBarStyle: {
160
- backgroundColor: background,
161
- borderTopWidth: 1,
162
- borderTopColor: border,
163
- // 通过固定宽度 55 来修正 Web 上的表现
164
- height: Platform.OS === 'web' ? 55 : 50 + insets.bottom,
165
- },
173
+ tabBarStyle,
166
174
  tabBarActiveTintColor: accent,
167
175
  tabBarInactiveTintColor: muted,
168
176
  }}
@@ -152,17 +152,25 @@ export default function TabLayout() {
152
152
  '--color-border',
153
153
  ]) as string[];
154
154
 
155
+ let tabBarStyle = {
156
+ backgroundColor: background,
157
+ borderTopWidth: 1,
158
+ borderTopColor: border,
159
+ };
160
+
161
+ // 用于修复 Web 上高度异常的问题(这个 if 逻辑必须添加)
162
+ if (Platform.OS === 'web') {
163
+ tabBarStyle = {
164
+ ...tabBarStyle,
165
+ height: 'auto',
166
+ }
167
+ }
168
+
155
169
  return (
156
170
  <Tabs
157
171
  screenOptions={{
158
172
  headerShown: false,
159
- tabBarStyle: {
160
- backgroundColor: background,
161
- borderTopWidth: 1,
162
- borderTopColor: border,
163
- // 通过固定宽度 55 来修正 Web 上的表现
164
- height: Platform.OS === 'web' ? 55 : 50 + insets.bottom,
165
- },
173
+ tabBarStyle,
166
174
  tabBarActiveTintColor: accent,
167
175
  tabBarInactiveTintColor: muted,
168
176
  }}
@@ -10,6 +10,7 @@ import reanimated from '../eslint-plugins/reanimated/index.js';
10
10
  import reactnative from '../eslint-plugins/react-native/index.js';
11
11
  import forbidEmoji from '../eslint-plugins/forbid-emoji/index.js';
12
12
  import restrictLinearGradient from '../eslint-plugins/restrict-linear-gradient/index.js';
13
+ import expo from '../eslint-plugins/expo/index.js';
13
14
 
14
15
  export default [
15
16
  {
@@ -66,6 +67,7 @@ export default [
66
67
  reactnative,
67
68
  forbidEmoji,
68
69
  restrictLinearGradient,
70
+ expo,
69
71
  },
70
72
  rules: {
71
73
  // 关闭代码风格规则
@@ -90,6 +92,7 @@ export default [
90
92
  'reanimated/ban-mix-use': 'error',
91
93
  'forbidEmoji/no-emoji': 'error',
92
94
  'restrictLinearGradient/no-linear-gradient-backgroundcolor': 'error',
95
+ 'expo/require-globalcss-and-provider': 'error',
93
96
  // 禁止使用 via.placeholder.com 服务
94
97
  'no-restricted-syntax': [
95
98
  'error',
@@ -0,0 +1,9 @@
1
+ const requireGlobalCssAndProvider = require('./rule')
2
+
3
+ const plugin = {
4
+ rules: {
5
+ 'require-globalcss-and-provider': requireGlobalCssAndProvider,
6
+ },
7
+ }
8
+
9
+ module.exports = plugin
@@ -0,0 +1,105 @@
1
+ function normalizeFilename(filename) {
2
+ // ESLint may provide Windows paths; normalize to forward slashes for suffix checks.
3
+ return typeof filename === 'string' ? filename.replace(/\\/g, '/') : ''
4
+ }
5
+
6
+ function isTargetLayoutFile(context) {
7
+ return normalizeFilename(context.getFilename()).endsWith('/app/_layout.tsx')
8
+ }
9
+
10
+ function isSideEffectImportOf(node, sourceValue) {
11
+ return (
12
+ node &&
13
+ node.type === 'ImportDeclaration' &&
14
+ node.source &&
15
+ node.source.type === 'Literal' &&
16
+ node.source.value === sourceValue &&
17
+ Array.isArray(node.specifiers) &&
18
+ node.specifiers.length === 0
19
+ )
20
+ }
21
+
22
+ function getProviderImportLocalName(node) {
23
+ if (
24
+ !node ||
25
+ node.type !== 'ImportDeclaration' ||
26
+ !node.source ||
27
+ node.source.type !== 'Literal' ||
28
+ node.source.value !== '@/components/Provider'
29
+ ) {
30
+ return null
31
+ }
32
+
33
+ const specifiers = Array.isArray(node.specifiers) ? node.specifiers : []
34
+ if (specifiers.length !== 1) return null
35
+ const [specifier] = specifiers
36
+ if (specifier.type !== 'ImportSpecifier') return null
37
+ if (!specifier.imported || specifier.imported.type !== 'Identifier') return null
38
+ if (!specifier.local || specifier.local.type !== 'Identifier') return null
39
+
40
+ // Allow `import { Provider } ...` and `import { Provider as XXX } ...`
41
+ if (specifier.imported.name !== 'Provider') return null
42
+ return specifier.local.name
43
+ }
44
+
45
+ module.exports = {
46
+ meta: {
47
+ type: 'problem',
48
+ docs: {
49
+ description: 'Require global.css import and Provider usage in app/_layout.tsx',
50
+ recommended: 'error',
51
+ },
52
+ schema: [],
53
+ messages: {
54
+ missingGlobalCssImport: `app/_layout.tsx 必须引入 global.css 文件(参考写法:import '../global.css')`,
55
+ requireProviderImportAndUsage:
56
+ "app/_layout.tsx 必须从 @/components/Provider 导入 Provider(参考写法:import { Provider } from '@/components/Provider'),并使用导入的 Provider 包裹其余组件",
57
+ },
58
+ },
59
+
60
+ create(context) {
61
+ if (!isTargetLayoutFile(context)) {
62
+ return {}
63
+ }
64
+
65
+ let hasGlobalCssImport = false
66
+ let providerImportLocalName = null
67
+ let hasProviderJsxUsage = false
68
+
69
+ return {
70
+ Program(node) {
71
+ const body = Array.isArray(node.body) ? node.body : []
72
+ for (const stmt of body) {
73
+ if (isSideEffectImportOf(stmt, '../global.css')) {
74
+ hasGlobalCssImport = true
75
+ }
76
+ const localName = getProviderImportLocalName(stmt)
77
+ if (localName) {
78
+ providerImportLocalName = localName
79
+ }
80
+ }
81
+ },
82
+
83
+ JSXOpeningElement(node) {
84
+ if (!node || !node.name) return
85
+ if (
86
+ providerImportLocalName &&
87
+ node.name.type === 'JSXIdentifier' &&
88
+ node.name.name === providerImportLocalName
89
+ ) {
90
+ hasProviderJsxUsage = true
91
+ }
92
+ },
93
+
94
+ 'Program:exit'(node) {
95
+ if (!hasGlobalCssImport) {
96
+ context.report({ node, messageId: 'missingGlobalCssImport' })
97
+ }
98
+
99
+ if (!providerImportLocalName || !hasProviderJsxUsage) {
100
+ context.report({ node, messageId: 'requireProviderImportAndUsage' })
101
+ }
102
+ },
103
+ }
104
+ },
105
+ }
@@ -0,0 +1,108 @@
1
+ # expo 技术设计(layout 规则)
2
+
3
+ ## 目标
4
+ - 强约束 Expo Router 的 `app/_layout.tsx` 模板结构,避免初始化项目后被误删导致运行时样式/上下文缺失
5
+ - 校验 `app/_layout.tsx` 必须:
6
+ - 包含 `import '../global.css'`
7
+ - 使用从 `@/components/Provider` 导入的 Provider 组件作为 JSX 组件(允许 `import { Provider } ...` 或 `import { Provider as XXX } ...`)
8
+ - 实现轻量、易维护,不引入新依赖
9
+
10
+ ## 规则命名与对外形态
11
+ - 插件目录:expo/eslint-plugins/expo
12
+ - 规则名:expo/require-globalcss-and-provider
13
+ - 规则类型:problem
14
+ - 默认等级:error
15
+
16
+ ## 规则行为定义
17
+ ### 报错
18
+ 仅对目标文件 `app/_layout.tsx` 生效,满足以下任一情况报错:
19
+ - 缺少 side-effect import:`import '../global.css'`
20
+ - 缺少或不符合要求的 Provider 导入语句:
21
+ - 必须从 `@/components/Provider` 导入
22
+ - 必须为命名导入,导入名必须为 `Provider`
23
+ - 允许形式:
24
+ - `import { Provider } from '@/components/Provider';`
25
+ - `import { Provider as XXX } from '@/components/Provider';`
26
+ - 未在 JSX 中使用导入的 Provider 组件(例如:只导入但从未作为 JSX 组件出现)
27
+
28
+ ### 不报错
29
+ - 非 `app/_layout.tsx` 文件不做检查
30
+ - 目标文件同时满足:
31
+ - 存在 `import '../global.css'`
32
+ - 存在 Provider 命名导入(允许 as 别名)
33
+ - JSX 中至少出现一次 `<Provider ...>` / `<Provider>` 或 `<XXX ...>` / `<XXX>`(XXX 为别名)
34
+
35
+ ## 检测策略
36
+ ### 目标文件判定
37
+ - 通过 `context.getFilename()` 获取当前文件路径
38
+ - 仅当路径以 `/app/_layout.tsx` 结尾时启用规则(同时兼容 Windows 路径分隔符做归一化)
39
+
40
+ ### AST 访问节点
41
+ - `Program`:收集顶层 `ImportDeclaration`,判定是否存在目标 import
42
+ - `JSXOpeningElement` / `JSXElement`:判定是否使用 `<Provider>`
43
+
44
+ ### Import 判定细则
45
+ - global.css:
46
+ - `ImportDeclaration.source.value === '../global.css'`
47
+ - `ImportDeclaration.specifiers.length === 0`(side-effect import)
48
+ - Provider import(严格形式):
49
+ - `ImportDeclaration.source.value === '@/components/Provider'`
50
+ - `ImportDeclaration.specifiers` 仅包含一个 `ImportSpecifier`
51
+ - `specifier.imported.name === 'Provider'`,`specifier.local.name` 允许为 `Provider` 或任意别名
52
+
53
+ ## 报错信息
54
+ - messageId: missingGlobalCssImport
55
+ - 文案:`app/_layout.tsx 必须包含 "import '../global.css';"`
56
+ - messageId: requireProviderImportAndUsage
57
+ - 文案:`app/_layout.tsx 中必须从 @/components/Provider 导入 Provider(参考写法:import { Provider } from '@/components/Provider'),并使用导入的 Provider 包裹其余组件`
58
+
59
+ ## 典型示例
60
+ ### 触发
61
+ - 缺少 global.css:
62
+ - 未包含 `import '../global.css';`
63
+ - Provider import 形式不对:
64
+ - `import Provider from '@/components/Provider'`
65
+ - `import { Provider } from '@/components/provider'`
66
+ - Provider 未使用:
67
+ - 有 `import { Provider } ...`,但 JSX 中没有 `<Provider>`
68
+
69
+ ### 不触发
70
+ ```tsx
71
+ import { Provider } from '@/components/Provider';
72
+ import '../global.css';
73
+
74
+ export default function RootLayout() {
75
+ return (
76
+ <Provider>
77
+ {/* ... */}
78
+ </Provider>
79
+ );
80
+ }
81
+ ```
82
+
83
+ ```tsx
84
+ import { Provider as AppProvider } from '@/components/Provider';
85
+ import '../global.css';
86
+
87
+ export default function RootLayout() {
88
+ return (
89
+ <AppProvider>
90
+ {/* ... */}
91
+ </AppProvider>
92
+ );
93
+ }
94
+ ```
95
+
96
+ ## 边界与决策
97
+ - 仅做结构约束,不尝试自动修复(fixer)以避免在 TSX 顶部插入 import 引起格式与注释位置变化
98
+ - 仅检查 JSX 中的 `<Provider>` 使用,不做“必须最外层包裹”的结构分析,降低复杂度与误报
99
+
100
+ ## 性能考虑
101
+ - 仅在命中目标文件时启用
102
+ - 仅扫描顶层 import 与 JSX 标签名,复杂度 O(n)
103
+
104
+ ## 测试计划
105
+ - 缺少 global.css import:报 `missingGlobalCssImport`
106
+ - 未从 `@/components/Provider` 正确导入 Provider:报 `requireProviderImportAndUsage`
107
+ - Provider 未在 JSX 使用:报 `requireProviderImportAndUsage`
108
+ - 满足全部要求:不报错
@@ -42,6 +42,15 @@
42
42
 
43
43
  ## 开发规范
44
44
 
45
+ ### 编码规范
46
+
47
+ - 默认按 TypeScript `strict` 心智写代码;优先复用当前作用域已声明的变量、函数、类型和导入,禁止引用未声明标识符或拼错变量名。
48
+ - 禁止隐式 `any` 和 `as any`;函数参数、返回值、解构项、事件对象、`catch` 错误在使用前应有明确类型或先完成类型收窄,并清理未使用的变量和导入。
49
+
50
+ ### next.config 配置规范
51
+
52
+ - 配置的路径不要写死绝对路径,必须使用 path.resolve(__dirname, ...)、import.meta.dirname 或 process.cwd() 动态拼接。
53
+
45
54
  ### Hydration 问题防范
46
55
 
47
56
  1. 严禁在 JSX 渲染逻辑中直接使用 typeof window、Date.now()、Math.random() 等动态数据。**必须使用 'use client' 并配合 useEffect + useState 确保动态内容仅在客户端挂载后渲染**;同时严禁非法 HTML 嵌套(如 <p> 嵌套 <div>)。
@@ -10,6 +10,15 @@ const syntaxRules = [
10
10
  },
11
11
  ];
12
12
 
13
+ const nextConfigRestrictedSyntaxRules = [
14
+ {
15
+ selector:
16
+ 'Property[key.name=/^(root|outputFileTracingRoot)$/] > Literal[value=/^\\//]',
17
+ message:
18
+ '禁止在 next.config 中写死绝对路径,请改用 path.resolve(__dirname, ...)、import.meta.dirname 或 process.cwd() 动态拼接。',
19
+ },
20
+ ];
21
+
13
22
  const eslintConfig = defineConfig([
14
23
  ...nextVitals,
15
24
  ...nextTs,
@@ -19,6 +28,12 @@ const eslintConfig = defineConfig([
19
28
  'no-restricted-syntax': ['error', ...syntaxRules],
20
29
  },
21
30
  },
31
+ {
32
+ files: ['next.config.ts'],
33
+ rules: {
34
+ 'no-restricted-syntax': ['error', ...nextConfigRestrictedSyntaxRules],
35
+ },
36
+ },
22
37
  // Override default ignores of eslint-config-next.
23
38
  globalIgnores([
24
39
  // Default ignores of eslint-config-next:
@@ -0,0 +1,10 @@
1
+ [project]
2
+ requires = ["nodejs-24"]
3
+
4
+ [dev]
5
+ build = ["bash", "./scripts/prepare.sh"]
6
+ run = ["bash", "./scripts/dev.sh"]
7
+
8
+ [deploy]
9
+ build = ["bash", "pnpm install"]
10
+ run = ["bash", "./scripts/dev.sh"]
@@ -0,0 +1,150 @@
1
+ # Pi Bot AGENTS Guide
2
+
3
+ ## 1. 项目定位
4
+
5
+ - 这是一个已经交付给用户、会继续被迭代的 Pi Bot 项目。
6
+ - 你面对的是“当前用户的项目”,不是一个需要保持通用性的初始化模板。
7
+ - 当新需求和现有行为冲突时,默认优先保护已有功能、配置兼容性、会话数据和可调试性。
8
+
9
+ ## 2. 工作区与配置
10
+
11
+ - 用户工作区和运行时配置默认位于相邻目录 `<%= workspaceDir %>/`。
12
+ - 默认配置文件路径是 `<%= workspaceDir %>/config.json`。
13
+ - 运行时配置的代码入口是 `src/config.ts`。不要绕过它再引入新的平行配置源。
14
+ - 如果要修改配置写回逻辑,必须保留无关字段,避免覆盖用户已有配置。
15
+
16
+ ## 3. 必读顺序
17
+
18
+ 在进行较大修改前,优先按下面顺序理解项目:
19
+
20
+ 1. `README.md`
21
+ 2. `docs/project-overview.md`
22
+ 3. 与需求相关的源码入口
23
+ 4. 与改动相关的测试
24
+
25
+ 不要依赖当前目录之外的文档。
26
+
27
+ ## 4. 核心架构约束
28
+
29
+ ### 4.1 消息与分层
30
+
31
+ - 平台消息必须先归一化为 `BotMessage`,再进入 runtime。
32
+ - `channel` 层负责平台协议适配、路由过滤、消息发送。
33
+ - `runtime` 层负责 agent 调用、session 生命周期和流式输出。
34
+ - `dashboard` 负责调试界面、运行态观察和受控配置编辑。
35
+ - 不要把平台逻辑、UI 逻辑、模型逻辑混写在一起。
36
+
37
+ ### 4.2 Session 与持久化
38
+
39
+ - 会话归类规则以 `src/core.ts` 中的 `getSessionKey()` 为准,不要随意改动会话 key 规则。
40
+ - `src/session-store.ts` 负责 session 索引、transcript 文件和 reset 归档逻辑。
41
+ - reset session 时必须归档旧 transcript,不能直接覆盖旧会话文件。
42
+ - 不要删除、清空或重置 .pi-bot/ 下的会话数据,除非用户明确要求。
43
+
44
+ ### 4.3 配置与兼容性
45
+
46
+ - 用户可编辑配置以 `<%= workspaceDir %>/config.json` 为 source of truth。
47
+ - 新增配置项时,优先采用向后兼容的方式;避免无说明地重命名、删除或改变现有字段语义。
48
+ - Dashboard 的配置写回必须是“局部更新”,不能把不相关配置抹掉。
49
+ - 若变更会影响配置 schema、session 持久化格式、HTTP API shape 或默认行为,先确认再修改。
50
+
51
+ ### 4.4 外部平台的降级能力
52
+
53
+ - 飞书、微信、模型 provider、流式卡片等外部能力都可能失败。
54
+ - 外部能力失败时,优先保证主链路可用,并提供合理降级。
55
+ - 例如 reaction、streaming card 或可选增强能力失败时,不应阻断最终回复。
56
+
57
+ ### 4.5 Agent 能力扩展方式
58
+
59
+ - 新增“搜索 / 检索 / 信息采集 / 外部知识 / 热点汇总”这类能力时,默认优先使用 `pi-coding-agent` / `pi-mono` 生态已有的扩展方式:
60
+ - `customTools`
61
+ - extensions
62
+ - `ResourceLoader`
63
+ - skills / prompt templates
64
+ - 不要在 `channel` 层、`onMessage()`、`createBotApp()` 或其他 host 侧装配代码里,通过关键词匹配、意图判断或硬编码分支,手动把请求路由到某个 `collector`、`searcher`、`crawler`。
65
+ - `channel` 层只负责平台协议适配、消息归一化、路由过滤和发送回复;不负责“这个问题该不该调用外部信息能力”的业务决策。
66
+ - `runtime` 层负责把能力注册为 agent 可调用的 tool / extension,再由 agent 在对话过程中决定是否使用。
67
+ - 如果需求确实无法通过 tool / extension / skill 方式实现,必须先说明为什么现有扩展点不适用,再考虑增加 host 侧分流逻辑。
68
+ - 反例:
69
+ - 在 `onMessage()` 里判断“如果用户提到 Hacker News / GitHub 就直接调用 collector”
70
+ - 正例:
71
+ - 在 runtime 注册一个“抓取热点内容”的自定义 tool,让 agent 自主决定何时调用
72
+
73
+ ## 5. 修改策略
74
+
75
+ - 优先做小步、增量、可验证的修改,避免没有必要的大规模重写。
76
+ - 用户可见行为发生变化时,优先考虑是否应该做成配置项,而不是直接硬编码。
77
+ - 修 bug 或加功能时,同时检查是否需要补测试和文档。
78
+
79
+ ## 6. 常见改动的落点
80
+
81
+ - 改运行时配置或模型/provider 解析:
82
+ - `src/config.ts`
83
+ - `tests/config.test.ts`
84
+ - 改 session 行为或 transcript 持久化:
85
+ - `src/core.ts`
86
+ - `src/agent.ts`
87
+ - `src/session-store.ts`
88
+ - `tests/session-store.test.ts`
89
+ - 改飞书/微信 channel:
90
+ - `src/channels/*`
91
+ - 对应 channel tests
92
+ - 改 Dashboard 接口或配置编辑:
93
+ - `src/dashboard/api/*`
94
+ - `src/dashboard/server.ts`
95
+ - 对应 API tests
96
+ - 改 Dashboard 前端:
97
+ - `src/dashboard/web/src/*`
98
+ - 必要时补充前后端联动验证
99
+
100
+ ## 7. 验证要求
101
+
102
+ 默认情况下:
103
+
104
+ - 代码改动后至少运行:
105
+ - `npm test`
106
+ - `npm run typecheck`
107
+ - 如果修改了 Dashboard 前端或其构建相关内容,再运行:
108
+ - `npm run dashboard:build`
109
+ - 如果修改影响了跨模块主链路,例如 config、channel、runtime、session、dashboard 联动,再运行:
110
+ - `npm run smoke`
111
+
112
+ 如果因为环境限制无法完成验证,需要在最终说明中明确指出未验证项和风险,并给出用户手动验证的方式。
113
+
114
+ ## 8. 文档同步要求
115
+
116
+ 出现以下情况时,同步更新相关文档:
117
+
118
+ - 启动或配置方式变化
119
+ - 用户可见行为变化
120
+ - 目录结构或核心模块职责变化
121
+ - 测试方式或验证要求变化
122
+
123
+ ### 8.1 文档落点与命名规范
124
+
125
+ - 除 `README.md`、`AGENTS.md` 这类仓库入口文件外,不要随意在项目根目录新增文档。
126
+ - 架构说明、设计说明、实现说明、迭代记录等开发文档,放在 `docs/` 下。
127
+ - 面向最终用户的使用说明、接入说明、操作指南,放在 `docs/user/` 下。
128
+ - 新增文档文件名使用 kebab-case,例如 `agent-tooling-guide.md`、`dashboard-chat-flow.md`。
129
+ - 不要创建语义含糊的文件名,例如 `new-doc.md`、`notes.md`、`temp.md`、`test.md`。
130
+ - 如果某次改动需要补文档,优先更新已有文档;只有在现有文档承载不合适时,才新增文档文件。
131
+ - 新增文档后,应该让相关入口文档能找到它,避免产生“写了但没人知道在哪”的文档孤岛。
132
+
133
+ ## 9. 需要先确认的高风险变更
134
+
135
+ 遇到以下改动,先暂停并与用户确认:
136
+
137
+ - 修改 `config.json` schema 或默认值语义
138
+ - 修改 `getSessionKey()` 规则
139
+ - 修改 session 文件格式、索引格式或归档规则
140
+ - 修改默认 channel 行为,导致消息路由或触发条件变化
141
+ - 删除现有 API、页面、配置项或持久化数据
142
+
143
+ ## 10. 目标
144
+
145
+ 你的目标是在当前项目基础上持续迭代,稳定交付用户需要的能力,同时尽量保持:
146
+
147
+ - 现有行为可预测
148
+ - 配置与数据可兼容
149
+ - 出问题时便于排查
150
+ - 后续继续迭代时容易维护
@@ -0,0 +1,155 @@
1
+ # Pi Bot
2
+
3
+ 这是一个基于 `pi-mono` / `@mariozechner/pi-coding-agent` 的 Bot 项目。
4
+
5
+ ## 当前结构
6
+
7
+ - `src/config.ts`
8
+ - 项目唯一配置入口,读取 `config.json` 并装配 `BotAppConfig`
9
+ - `src/index.ts`
10
+ - 应用启动入口
11
+ - `src/agent.ts`
12
+ - agent runtime 封装
13
+ - `src/dashboard/config-store.ts`
14
+ - Dashboard 配置读写抽象,支持文件和内存两种存储
15
+ - `src/core.ts`
16
+ - 通用消息协议、共享类型和 helper
17
+ - `src/channels/feishu`
18
+ - 飞书 channel
19
+ - `src/channels/wechat`
20
+ - 微信 channel
21
+ - `tests/smoke`
22
+ - 基础 smoke test(使用内存配置存储验证 dashboard 配置读写链路)
23
+ - `tests/config.test.ts`
24
+ - config 解析与模型构建测试
25
+ - `types`
26
+ - 本地类型声明补充
27
+ - `docs/project-overview.md`
28
+ - 项目结构与实现介绍
29
+ - `docs/user/getting-started.md`
30
+ - 用户快速开始文档,Dashboard 的 `Docs` 页面会直接展示这份 Markdown
31
+
32
+ ## Start
33
+
34
+ ```bash
35
+ npm install
36
+ npm run dev
37
+ ```
38
+
39
+ `npm run dev` 会先按顺序加载 `.env` 和 `.env.local`,然后启动 `src/index.ts`。
40
+
41
+ ## 用户文档
42
+
43
+ 启动后,可以在 Dashboard 的 `Docs` 页面查看当前项目的用户指引,默认地址是:
44
+
45
+ ```txt
46
+ http://127.0.0.1:5000/docs
47
+ ```
48
+
49
+ 用户文档的 Markdown 源文件位于 `docs/user/getting-started.md`。如果你修改了接入步骤或调整了用户可见行为,请同步更新这份文档。
50
+
51
+ ## 常用命令
52
+
53
+ ```bash
54
+ npm run dev
55
+ npm test
56
+ npm run smoke
57
+ npm run typecheck
58
+ ```
59
+
60
+ ## Agent 模式
61
+
62
+ - `PI_BOT_AGENT_MODE=mock`
63
+ - 使用 mock runtime,适合本地链路验证
64
+ - `PI_BOT_AGENT_MODE=pi`
65
+ - 使用真实 `pi-coding-agent` runtime
66
+
67
+ 除 `PI_BOT_AGENT_MODE` 和飞书相关环境变量外,agent 的模型、provider、workspace、thinking level 都从 `<%= workspaceDir %>/config.json` 读取。
68
+
69
+ ## Provider 配置
70
+
71
+ provider 不再通过项目内的特化实现硬编码,而是从 `<%= workspaceDir %>/config.json` 的 `models.providers` 读取。
72
+
73
+ 如果你已经启动了本地 Dashboard,现在也可以直接在界面里切换默认模型:
74
+
75
+ - `http://127.0.0.1:5000/models`
76
+ - 选择默认模型(选择后会自动保存)
77
+
78
+ 默认情况下,Dashboard 会通过文件型 `ConfigStore` 读写 `<%= workspaceDir %>/config.json`,保存后需要重启进程让配置生效。测试或嵌入场景也可以通过 `createBotApp(..., { dashboardConfigStore })` 注入内存型 `ConfigStore`,避免依赖真实配置文件。
79
+
80
+ 默认主模型来自:
81
+
82
+ ```json
83
+ {
84
+ "agents": {
85
+ "defaults": {
86
+ "thinkingLevel": "medium",
87
+ "model": {
88
+ "primary": "coze/glm-4-7-251222"
89
+ }
90
+ }
91
+ }
92
+ }
93
+ ```
94
+
95
+ provider 定义示例:
96
+
97
+ ```json
98
+ {
99
+ "models": {
100
+ "providers": {
101
+ "coze": {
102
+ "api": "openai-completions",
103
+ "apiKey": "${COZE_WORKLOAD_IDENTITY_API_KEY}",
104
+ "baseUrl": "${COZE_INTEGRATION_MODEL_BASE_URL}",
105
+ "models": [
106
+ {
107
+ "id": "glm-4-7-251222",
108
+ "name": "GLM 4.7",
109
+ "reasoning": false,
110
+ "input": ["text"],
111
+ "contextWindow": 200000,
112
+ "maxTokens": 8192,
113
+ "cost": {
114
+ "input": 0,
115
+ "output": 0,
116
+ "cacheRead": 0,
117
+ "cacheWrite": 0
118
+ }
119
+ }
120
+ ]
121
+ }
122
+ }
123
+ }
124
+ }
125
+ ```
126
+
127
+ 后续要扩展新的 openai-compatible provider,只需要在 `<%= workspaceDir %>/config.json` 里新增一个 provider 配置和模型定义即可。
128
+
129
+ 当前 Dashboard 的模型配置页仅用于切换默认模型,不支持编辑 Provider 或模型参数。如果要扩展或修改 provider/model,仍然建议直接编辑 `<%= workspaceDir %>/config.json`。
130
+
131
+ ## Feishu Debug
132
+
133
+ 如果你在联调真实飞书机器人,并想排查重复回复或事件重试问题,可开启:
134
+
135
+ ```bash
136
+ export PI_BOT_DEBUG_FEISHU=1
137
+ npm run dev
138
+ ```
139
+
140
+ 飞书 channel 会输出收到事件、过滤原因、去重结果和发送回复等关键日志。
141
+
142
+ 默认情况下,飞书 channel 会在 bot 开始处理消息时给原消息加一个 `OneSecond` reaction,并在发送回复前移除它。这里的 `emojiType` 需要填写飞书文档里的 `emoji_type` 枚举值,比如 `OneSecond`、`MUSCLE`。你也可以在 `<%= workspaceDir %>/config.json` 里覆盖这个行为:
143
+
144
+ ```json
145
+ {
146
+ "channels": {
147
+ "feishu": {
148
+ "thinkingReaction": {
149
+ "enabled": true,
150
+ "emojiType": "OneSecond"
151
+ }
152
+ }
153
+ }
154
+ }
155
+ ```
@@ -0,0 +1,3 @@
1
+ node_modules
2
+ dist
3
+ .env