@coze-arch/cli 0.0.19-alpha.502ddf → 0.0.19-alpha.a5388a

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 (31) hide show
  1. package/lib/__templates__/nuxt-vue/app/pages/index.vue +0 -6
  2. package/lib/__templates__/nuxt-vue/nuxt.config.ts +2 -2
  3. package/lib/__templates__/pi-agent/AGENTS.md +2 -7
  4. package/lib/__templates__/pi-agent/README.md +0 -2
  5. package/lib/__templates__/pi-agent/docs/project-overview.md +15 -9
  6. package/lib/__templates__/pi-agent/docs/user/getting-started.md +4 -3
  7. package/lib/__templates__/pi-agent/pi-resources/skills/coze-asr/SKILL.md +10 -4
  8. package/lib/__templates__/pi-agent/pi-resources/skills/coze-asr/scripts/asr.mjs +9 -0
  9. package/lib/__templates__/pi-agent/pi-resources/skills/coze-image-gen/SKILL.md +18 -6
  10. package/lib/__templates__/pi-agent/pi-resources/skills/coze-image-gen/scripts/gen.mjs +9 -0
  11. package/lib/__templates__/pi-agent/pi-resources/skills/coze-tts/SKILL.md +37 -9
  12. package/lib/__templates__/pi-agent/pi-resources/skills/coze-tts/scripts/tts.mjs +9 -0
  13. package/lib/__templates__/pi-agent/pi-resources/skills/coze-video-gen/SKILL.md +30 -17
  14. package/lib/__templates__/pi-agent/pi-resources/skills/coze-video-gen/scripts/gen.mjs +9 -0
  15. package/lib/__templates__/pi-agent/src/config.ts +19 -60
  16. package/lib/__templates__/pi-agent/src/core.ts +0 -1
  17. package/lib/__templates__/pi-agent/src/dashboard/api/docs.ts +204 -0
  18. package/lib/__templates__/pi-agent/src/dashboard/index.ts +4 -39
  19. package/lib/__templates__/pi-agent/src/dashboard/server.ts +12 -0
  20. package/lib/__templates__/pi-agent/src/dashboard/web/src/components/app-layout.tsx +15 -1
  21. package/lib/__templates__/pi-agent/src/dashboard/web/src/main.tsx +6 -0
  22. package/lib/__templates__/pi-agent/src/dashboard/web/src/pages/channels-page.tsx +188 -0
  23. package/lib/__templates__/pi-agent/src/dashboard/web/src/pages/chat-page.tsx +11 -0
  24. package/lib/__templates__/pi-agent/src/dashboard/web/src/pages/docs-page.tsx +65 -0
  25. package/lib/__templates__/pi-agent/src/dashboard/web/src/pages/models-page.tsx +122 -0
  26. package/lib/__templates__/pi-agent/src/dashboard/web/src/pages/overview-page.tsx +72 -268
  27. package/lib/__templates__/pi-agent/src/dashboard/web/src/styles.css +91 -0
  28. package/lib/__templates__/pi-agent/tests/config.test.ts +1 -63
  29. package/lib/__templates__/pi-agent/tests/dashboard-docs-api.test.ts +125 -0
  30. package/lib/cli.js +1 -1
  31. package/package.json +1 -1
@@ -0,0 +1,122 @@
1
+ import React from "react";
2
+ import { Alert, AlertDescription, AlertTitle } from "../components/ui/alert";
3
+ import { Card, CardContent, CardHeader, CardTitle } from "../components/ui/card";
4
+ import { Select, SelectContent, SelectItem, SelectTrigger, SelectValue } from "../components/ui/select";
5
+ import { useFetch } from "../hooks/use-fetch";
6
+ import { toast } from "sonner";
7
+
8
+ type ModelOption = {
9
+ value: string;
10
+ label: string;
11
+ };
12
+
13
+ type ModelsConfig = {
14
+ defaultModel: string;
15
+ options: ModelOption[];
16
+ };
17
+
18
+ export function ModelsPage() {
19
+ const { data, error, loading } = useFetch<ModelsConfig>("/api/models");
20
+ const [saving, setSaving] = React.useState(false);
21
+ const [form, setForm] = React.useState<ModelsConfig | null>(null);
22
+
23
+ React.useEffect(() => {
24
+ if (!data) return;
25
+ // Be defensive: if the backend returns an older shape or an error envelope,
26
+ // we still want a non-crashing UI.
27
+ setForm({
28
+ defaultModel: typeof data.defaultModel === "string" ? data.defaultModel : "",
29
+ options: Array.isArray((data as unknown as { options?: unknown }).options)
30
+ ? (data as unknown as { options: ModelOption[] }).options
31
+ : [],
32
+ });
33
+ }, [data]);
34
+
35
+ const saveDefaultModel = async (defaultModel: string) => {
36
+ setSaving(true);
37
+ try {
38
+ const res = await fetch("/api/models", {
39
+ method: "POST",
40
+ headers: { "Content-Type": "application/json" },
41
+ body: JSON.stringify({ models: { defaultModel } }),
42
+ });
43
+ const json = (await res.json()) as { ok?: boolean; error?: string };
44
+ if (!res.ok || json.ok === false) {
45
+ throw new Error(json.error || `${res.status} ${res.statusText}`);
46
+ }
47
+ toast.success("已保存(需要重启进程生效)");
48
+ } catch (e) {
49
+ toast.error(`保存失败:${String(e)}`, { duration: 4500 });
50
+ throw e;
51
+ } finally {
52
+ setSaving(false);
53
+ }
54
+ };
55
+
56
+ const options = form?.options ?? [];
57
+ const onDefaultModelChange = async (nextValue: string) => {
58
+ if (!form || nextValue === form.defaultModel) return;
59
+ const previousValue = form.defaultModel;
60
+ setForm({ ...form, defaultModel: nextValue });
61
+ try {
62
+ await saveDefaultModel(nextValue);
63
+ } catch {
64
+ setForm((current) => (current ? { ...current, defaultModel: previousValue } : current));
65
+ }
66
+ };
67
+
68
+ return (
69
+ <section className="space-y-4 px-4 pb-4 pt-0">
70
+ <div className="sticky top-0 z-20 -mx-4 bg-background px-4 pb-3 pt-4">
71
+ <div className="space-y-1 bg-background py-0">
72
+ <div className="space-y-1">
73
+ <h1 className="text-lg font-semibold tracking-tight">模型配置</h1>
74
+ </div>
75
+ </div>
76
+ </div>
77
+
78
+ {loading ? (
79
+ <Alert>
80
+ <AlertTitle>加载中</AlertTitle>
81
+ <AlertDescription>正在读取模型配置。</AlertDescription>
82
+ </Alert>
83
+ ) : error ? (
84
+ <Alert className="border-destructive/20 bg-destructive/5 text-destructive">
85
+ <AlertTitle>读取失败</AlertTitle>
86
+ <AlertDescription>错误:{error}</AlertDescription>
87
+ </Alert>
88
+ ) : form ? (
89
+ options.length > 0 ? (
90
+ <Card className="border-border/60 bg-background/70 shadow-none">
91
+ <CardHeader className="!pb-3">
92
+ <CardTitle className="text-sm">默认模型</CardTitle>
93
+ </CardHeader>
94
+ <CardContent className="pb-5 pt-0">
95
+ <Select
96
+ value={form.defaultModel}
97
+ onValueChange={onDefaultModelChange}
98
+ disabled={saving}
99
+ >
100
+ <SelectTrigger aria-label="选择默认模型">
101
+ <SelectValue placeholder="选择默认模型" />
102
+ </SelectTrigger>
103
+ <SelectContent>
104
+ {options.map((option) => (
105
+ <SelectItem key={option.value} value={option.value}>
106
+ {option.label}
107
+ </SelectItem>
108
+ ))}
109
+ </SelectContent>
110
+ </Select>
111
+ </CardContent>
112
+ </Card>
113
+ ) : (
114
+ <Alert>
115
+ <AlertTitle>没有可选模型</AlertTitle>
116
+ <AlertDescription>当前还没有可用模型,请先补充模型定义。</AlertDescription>
117
+ </Alert>
118
+ )
119
+ ) : null}
120
+ </section>
121
+ );
122
+ }
@@ -1,12 +1,8 @@
1
1
  import React from "react";
2
2
  import { Alert, AlertDescription, AlertTitle } from "../components/ui/alert";
3
- import { Button } from "../components/ui/button";
3
+ import { Badge } from "../components/ui/badge";
4
4
  import { Card, CardContent, CardDescription, CardHeader, CardTitle } from "../components/ui/card";
5
- import { Input } from "../components/ui/input";
6
- import { Label } from "../components/ui/label";
7
- import { Select, SelectContent, SelectItem, SelectTrigger, SelectValue } from "../components/ui/select";
8
5
  import { useFetch } from "../hooks/use-fetch";
9
- import { toast } from "sonner";
10
6
 
11
7
  type Overview = {
12
8
  appName: string;
@@ -18,41 +14,6 @@ type Overview = {
18
14
  enabledChannels: Array<{ id: string; enabled: boolean }>;
19
15
  };
20
16
 
21
- type Channels = {
22
- routing: {
23
- feishuGroupRequireMention: boolean;
24
- wechatGroupRequireMention: boolean;
25
- };
26
- feishu: {
27
- enabled: boolean;
28
- requireMention?: boolean;
29
- appId?: string;
30
- domain?: string;
31
- encryptKey?: string;
32
- verificationToken?: string;
33
- appSecret?: string;
34
- thinkingReaction?: {
35
- enabled?: boolean;
36
- emojiType?: string;
37
- };
38
- };
39
- wechat: {
40
- enabled: boolean;
41
- requireMention?: boolean;
42
- implementation?: string;
43
- };
44
- };
45
-
46
- type ModelOption = {
47
- value: string;
48
- label: string;
49
- };
50
-
51
- type ModelsConfig = {
52
- defaultModel: string;
53
- options: ModelOption[];
54
- };
55
-
56
17
  export function OverviewPage() {
57
18
  const { data, error, loading } = useFetch<Overview>("/api/overview");
58
19
  const enabledChannels = data?.enabledChannels ?? [];
@@ -67,7 +28,7 @@ export function OverviewPage() {
67
28
  <CardTitle className="text-base">系统摘要</CardTitle>
68
29
  <CardDescription>当前 bot、渠道与工作区的核心状态。</CardDescription>
69
30
  </CardHeader>
70
- <CardContent className="grid gap-3 sm:grid-cols-2">
31
+ <CardContent className="grid gap-3 sm:grid-cols-3">
71
32
  <SummaryStat
72
33
  label="运行状态"
73
34
  value={botStatusText}
@@ -78,196 +39,76 @@ export function OverviewPage() {
78
39
  value={String(activeChannelCount)}
79
40
  hint={enabledChannels.length ? `${enabledChannels.length} 个渠道已接入` : "暂无渠道信息"}
80
41
  />
42
+ <SummaryStat
43
+ label="Agent 模式"
44
+ value={data?.agentMode ?? "--"}
45
+ hint="当前启动模式"
46
+ />
81
47
  </CardContent>
82
48
  </Card>
83
49
 
84
- <DefaultModelSection />
50
+ <Card>
51
+ <CardHeader className="pb-3">
52
+ <CardTitle className="text-base">渠道状态</CardTitle>
53
+ <CardDescription>快速查看各渠道是否已启用。</CardDescription>
54
+ </CardHeader>
55
+ <CardContent className="flex flex-wrap gap-2">
56
+ {enabledChannels.length === 0 ? (
57
+ <Badge variant="outline">暂无数据</Badge>
58
+ ) : (
59
+ enabledChannels.map((channel) => (
60
+ <Badge
61
+ key={channel.id}
62
+ variant={channel.enabled ? "default" : "outline"}
63
+ className="rounded-full px-3 py-1"
64
+ >
65
+ {channel.id} · {channel.enabled ? "已开启" : "已关闭"}
66
+ </Badge>
67
+ ))
68
+ )}
69
+ </CardContent>
70
+ </Card>
85
71
  </div>
86
72
 
87
- <ChannelsSection />
88
- </section>
89
- );
90
- }
91
-
92
- function DefaultModelSection() {
93
- const { data, error, loading } = useFetch<ModelsConfig>("/api/models");
94
- const [saving, setSaving] = React.useState(false);
95
- const [form, setForm] = React.useState<ModelsConfig | null>(null);
96
-
97
- React.useEffect(() => {
98
- if (!data) return;
99
- setForm({
100
- defaultModel: typeof data.defaultModel === "string" ? data.defaultModel : "",
101
- options: Array.isArray((data as unknown as { options?: unknown }).options)
102
- ? (data as unknown as { options: ModelOption[] }).options
103
- : [],
104
- });
105
- }, [data]);
106
-
107
- const saveDefaultModel = async (defaultModel: string) => {
108
- setSaving(true);
109
- try {
110
- const res = await fetch("/api/models", {
111
- method: "POST",
112
- headers: { "Content-Type": "application/json" },
113
- body: JSON.stringify({ models: { defaultModel } }),
114
- });
115
- const json = (await res.json()) as { ok?: boolean; error?: string };
116
- if (!res.ok || json.ok === false) {
117
- throw new Error(json.error || `${res.status} ${res.statusText}`);
118
- }
119
- toast.success("已保存(需要重启进程生效)");
120
- } catch (e) {
121
- toast.error(`保存失败:${String(e)}`, { duration: 4500 });
122
- throw e;
123
- } finally {
124
- setSaving(false);
125
- }
126
- };
127
-
128
- const options = form?.options ?? [];
129
- const onDefaultModelChange = async (nextValue: string) => {
130
- if (!form || nextValue === form.defaultModel) return;
131
- const previousValue = form.defaultModel;
132
- setForm({ ...form, defaultModel: nextValue });
133
- try {
134
- await saveDefaultModel(nextValue);
135
- } catch {
136
- setForm((current) => (current ? { ...current, defaultModel: previousValue } : current));
137
- }
138
- };
139
-
140
- return (
141
- <Card className="border-border/60 bg-background/70 shadow-none">
142
- <CardHeader className="pb-3">
143
- <CardTitle className="text-base">默认模型</CardTitle>
144
- <CardDescription>切换当前使用的默认模型。</CardDescription>
145
- </CardHeader>
146
- <CardContent>
147
- {loading ? (
148
- <Alert>
149
- <AlertTitle>加载中</AlertTitle>
150
- <AlertDescription>正在读取模型配置。</AlertDescription>
151
- </Alert>
152
- ) : error ? (
153
- <Alert className="border-destructive/20 bg-destructive/5 text-destructive">
154
- <AlertTitle>读取失败</AlertTitle>
155
- <AlertDescription>错误:{error}</AlertDescription>
156
- </Alert>
157
- ) : form && options.length > 0 ? (
158
- <Select
159
- value={form.defaultModel}
160
- onValueChange={onDefaultModelChange}
161
- disabled={saving}
162
- >
163
- <SelectTrigger aria-label="选择默认模型">
164
- <SelectValue placeholder="选择默认模型" />
165
- </SelectTrigger>
166
- <SelectContent>
167
- {options.map((option) => (
168
- <SelectItem key={option.value} value={option.value}>
169
- {option.label}
170
- </SelectItem>
171
- ))}
172
- </SelectContent>
173
- </Select>
174
- ) : form ? (
175
- <Alert>
176
- <AlertTitle>没有可选模型</AlertTitle>
177
- <AlertDescription>当前还没有可用模型,请先补充模型定义。</AlertDescription>
178
- </Alert>
179
- ) : null}
180
- </CardContent>
181
- </Card>
182
- );
183
- }
184
-
185
- function ChannelsSection() {
186
- const { data, error, loading } = useFetch<Channels>("/api/channels");
187
- const [saving, setSaving] = React.useState(false);
188
- const [form, setForm] = React.useState<Channels | null>(null);
189
-
190
- React.useEffect(() => {
191
- if (data) setForm(data);
192
- }, [data]);
193
-
194
- const patch = (fn: (draft: Channels) => void) => {
195
- if (!form) return;
196
- const draft = { ...form, feishu: { ...form.feishu }, wechat: { ...form.wechat } };
197
- fn(draft);
198
- setForm(draft);
199
- };
200
-
201
- const onSave = async () => {
202
- if (!form) return;
203
- setSaving(true);
204
- try {
205
- const res = await fetch("/api/channels", {
206
- method: "POST",
207
- headers: { "Content-Type": "application/json" },
208
- body: JSON.stringify({ channels: form }),
209
- });
210
- const json = (await res.json()) as { ok?: boolean; error?: string };
211
- if (!res.ok || json.ok === false) {
212
- throw new Error(json.error || `${res.status} ${res.statusText}`);
213
- }
214
- toast.success("已保存(需要重启进程生效)");
215
- } catch (e) {
216
- toast.error(`保存失败:${String(e)}`, { duration: 4500 });
217
- } finally {
218
- setSaving(false);
219
- }
220
- };
221
-
222
- return (
223
- <Card className="border-border/60 bg-background/70 shadow-none">
224
- <CardHeader>
225
- <CardTitle className="text-base">飞书配置</CardTitle>
226
- <CardDescription>管理飞书渠道的启用状态与接入凭证。</CardDescription>
227
- </CardHeader>
228
- <CardContent>
229
- {loading ? (
230
- <Alert>
231
- <AlertTitle>加载中</AlertTitle>
232
- <AlertDescription>正在读取渠道配置。</AlertDescription>
233
- </Alert>
234
- ) : error ? (
235
- <Alert className="border-destructive/20 bg-destructive/5 text-destructive">
236
- <AlertTitle>读取失败</AlertTitle>
237
- <AlertDescription>错误:{error}</AlertDescription>
238
- </Alert>
239
- ) : form ? (
240
- <div className="space-y-4">
241
- <div className="grid gap-4">
242
- <CheckboxField
243
- label="启用状态"
244
- checked={!!form.feishu.enabled}
245
- onChange={(checked) => patch((d) => (d.feishu.enabled = checked))}
246
- />
247
- <TextField
248
- label="App ID"
249
- value={form.feishu.appId ?? ""}
250
- onChange={(value) => patch((d) => (d.feishu.appId = value))}
251
- />
252
- <TextField
253
- label="App Secret"
254
- value={form.feishu.appSecret ?? ""}
255
- onChange={(value) => patch((d) => (d.feishu.appSecret = value))}
256
- />
257
- </div>
258
- <div className="flex justify-start">
259
- <Button
260
- onClick={onSave}
261
- disabled={!form || saving || loading || Boolean(error)}
262
- className="shrink-0"
263
- >
264
- {saving ? "保存中…" : "保存"}
265
- </Button>
73
+ <Card className="overflow-hidden">
74
+ <CardHeader>
75
+ <CardTitle>概览</CardTitle>
76
+ <CardDescription>关键运行信息</CardDescription>
77
+ </CardHeader>
78
+ <CardContent>
79
+ {loading ? (
80
+ <Alert>
81
+ <AlertTitle>加载中</AlertTitle>
82
+ <AlertDescription>正在获取 Dashboard 运行信息。</AlertDescription>
83
+ </Alert>
84
+ ) : error ? (
85
+ <Alert className="border-destructive/20 bg-destructive/5 text-destructive">
86
+ <AlertTitle>请求失败</AlertTitle>
87
+ <AlertDescription>错误:{error}</AlertDescription>
88
+ </Alert>
89
+ ) : (
90
+ <div className="grid gap-3">
91
+ <InfoRow label="面板地址">
92
+ <a
93
+ className="underline decoration-border underline-offset-4 transition-colors hover:text-primary"
94
+ href={data?.dashboardUrl}
95
+ target="_blank"
96
+ rel="noreferrer"
97
+ >
98
+ {data?.dashboardUrl}
99
+ </a>
100
+ </InfoRow>
101
+ <InfoRow label="工作区目录" mono>
102
+ {data?.workspaceDir}
103
+ </InfoRow>
104
+ <InfoRow label="Agent 目录" mono>
105
+ {data?.agentDir}
106
+ </InfoRow>
266
107
  </div>
267
- </div>
268
- ) : null}
269
- </CardContent>
270
- </Card>
108
+ )}
109
+ </CardContent>
110
+ </Card>
111
+ </section>
271
112
  );
272
113
  }
273
114
 
@@ -281,50 +122,13 @@ function SummaryStat(props: { label: string; value: string; hint: string }) {
281
122
  );
282
123
  }
283
124
 
284
- function FieldShell(props: { label: string; children: React.ReactNode; description?: string }) {
125
+ function InfoRow(props: { label: string; children: React.ReactNode; mono?: boolean }) {
285
126
  return (
286
- <div className="space-y-2">
287
- <Label className="text-xs uppercase tracking-[0.16em] text-muted-foreground">{props.label}</Label>
288
- {props.children}
289
- {props.description ? <p className="text-xs text-muted-foreground">{props.description}</p> : null}
127
+ <div className="grid gap-2 rounded-xl border border-border/60 bg-muted/30 px-4 py-3 md:grid-cols-[220px_minmax(0,1fr)] md:items-start">
128
+ <div className="text-sm font-medium text-muted-foreground">{props.label}</div>
129
+ <div className={props.mono ? "[overflow-wrap:anywhere] font-mono text-xs sm:text-sm" : "[overflow-wrap:anywhere] text-sm"}>
130
+ {props.children}
131
+ </div>
290
132
  </div>
291
133
  );
292
134
  }
293
-
294
- function TextField(props: {
295
- label: string;
296
- value: string;
297
- placeholder?: string;
298
- onChange: (value: string) => void;
299
- }) {
300
- return (
301
- <FieldShell label={props.label}>
302
- <Input
303
- className="font-mono text-xs sm:text-sm"
304
- value={props.value}
305
- placeholder={props.placeholder}
306
- onChange={(e) => props.onChange(e.target.value)}
307
- />
308
- </FieldShell>
309
- );
310
- }
311
-
312
- function CheckboxField(props: {
313
- label: string;
314
- checked: boolean;
315
- onChange: (checked: boolean) => void;
316
- }) {
317
- return (
318
- <FieldShell label={props.label}>
319
- <label className="flex h-10 items-center gap-3 rounded-md border border-input bg-background/80 px-3 text-sm">
320
- <input
321
- type="checkbox"
322
- className="h-4 w-4 rounded border-border accent-primary"
323
- checked={props.checked}
324
- onChange={(e) => props.onChange(e.target.checked)}
325
- />
326
- <span className="text-sm text-foreground">{props.checked ? "已启用" : "未启用"}</span>
327
- </label>
328
- </FieldShell>
329
- );
330
- }
@@ -201,3 +201,94 @@
201
201
  max-width: 100%;
202
202
  height: auto;
203
203
  }
204
+
205
+ .docs-content [data-streamdown] {
206
+ overflow-wrap: anywhere;
207
+ }
208
+
209
+ .docs-content h1,
210
+ .docs-content h2,
211
+ .docs-content h3,
212
+ .docs-content h4 {
213
+ margin: 1.5rem 0 0.75rem;
214
+ font-weight: 600;
215
+ letter-spacing: -0.02em;
216
+ }
217
+
218
+ .docs-content h1:first-child,
219
+ .docs-content h2:first-child,
220
+ .docs-content h3:first-child {
221
+ margin-top: 0;
222
+ }
223
+
224
+ .docs-content h1 {
225
+ font-size: 1.5rem;
226
+ }
227
+
228
+ .docs-content h2 {
229
+ font-size: 1.2rem;
230
+ }
231
+
232
+ .docs-content h3 {
233
+ font-size: 1rem;
234
+ }
235
+
236
+ .docs-content p,
237
+ .docs-content ul,
238
+ .docs-content ol,
239
+ .docs-content table,
240
+ .docs-content blockquote {
241
+ margin: 0.75rem 0;
242
+ }
243
+
244
+ .docs-content ul,
245
+ .docs-content ol {
246
+ padding-left: 1.25rem;
247
+ }
248
+
249
+ .docs-content li + li {
250
+ margin-top: 0.35rem;
251
+ }
252
+
253
+ .docs-content a {
254
+ color: var(--primary);
255
+ text-decoration: underline;
256
+ text-underline-offset: 0.2em;
257
+ }
258
+
259
+ .docs-content hr {
260
+ margin: 1.25rem 0;
261
+ border: 0;
262
+ border-top: 1px solid color-mix(in oklab, var(--border) 100%, transparent);
263
+ }
264
+
265
+ .docs-content blockquote {
266
+ border-left: 3px solid color-mix(in oklab, var(--border) 100%, transparent);
267
+ padding-left: 1rem;
268
+ color: var(--muted-foreground);
269
+ }
270
+
271
+ .docs-content table {
272
+ width: 100%;
273
+ border-collapse: collapse;
274
+ overflow: hidden;
275
+ border-radius: 0.875rem;
276
+ border: 1px solid color-mix(in oklab, var(--border) 100%, transparent);
277
+ }
278
+
279
+ .docs-content th,
280
+ .docs-content td {
281
+ border-bottom: 1px solid color-mix(in oklab, var(--border) 100%, transparent);
282
+ padding: 0.75rem;
283
+ text-align: left;
284
+ vertical-align: top;
285
+ }
286
+
287
+ .docs-content th {
288
+ background: color-mix(in oklab, var(--muted) 80%, var(--background));
289
+ font-weight: 600;
290
+ }
291
+
292
+ .docs-content tbody tr:last-child td {
293
+ border-bottom: 0;
294
+ }
@@ -300,78 +300,16 @@ test("loadBotAppConfig builds the app config from config.json plus env-backed ch
300
300
 
301
301
  rmSync(tempRoot, { recursive: true, force: true });
302
302
 
303
- assert.equal(getDefaultConfigPath(), resolve(process.cwd(), "<%= workspaceDir %>/config.json"));
303
+ assert.equal(getDefaultConfigPath(), resolve(process.cwd(), "../workspace/config.json"));
304
304
  assert.equal(loaded.agent.mode, "pi");
305
305
  assert.equal(loaded.agent.provider, "coze");
306
306
  assert.equal(loaded.agent.model, "auto");
307
307
  assert.equal(loaded.agent.configPath, configPath);
308
308
  assert.equal(loaded.agent.thinkingLevel, "high");
309
309
  assert.equal(loaded.agent.cwd, dirname(configPath));
310
- assert.equal(
311
- (((loaded.configRoot?.agents as { defaults?: { model?: { primary?: string } } })?.defaults?.model?.primary) ?? ""),
312
- "coze/auto"
313
- );
314
310
  assert.equal(loaded.channels.feishu?.appId, "app-id");
315
311
  assert.equal(loaded.channels.feishu?.verificationToken, "verify-token");
316
312
  assert.equal(loaded.routing.feishuGroupRequireMention, false);
317
313
  assert.equal(loaded.channels.feishu?.thinkingReaction?.enabled, false);
318
314
  assert.equal(loaded.channels.feishu?.thinkingReaction?.emojiType, "OneSecond");
319
315
  });
320
-
321
- test("loadConfig falls back to a minimal mock config when config.json is missing", () => {
322
- const tempDir = mkdtempSync(join(tmpdir(), "pi-bot-missing-config-"));
323
- const configPath = join(tempDir, "workspace", "config.json");
324
-
325
- const loaded = loadConfig(configPath);
326
-
327
- rmSync(tempDir, { recursive: true, force: true });
328
-
329
- assert.equal(loaded.source, "fallback-mock");
330
- assert.equal(loaded.path, configPath);
331
- assert.equal(loaded.workspaceDir, dirname(configPath));
332
- assert.deepEqual(loaded.defaultModel, {
333
- provider: "openai",
334
- modelId: "gpt-5-mini"
335
- });
336
- assert.equal(loaded.config.channels?.feishu?.enabled, false);
337
- assert.equal(loaded.config.channels?.wechat?.enabled, false);
338
- });
339
-
340
- test("loadBotAppConfig falls back to mock mode and skips file config path when config.json is missing", () => {
341
- const tempDir = mkdtempSync(join(tmpdir(), "pi-bot-missing-config-"));
342
- const configPath = join(tempDir, "workspace", "config.json");
343
-
344
- const loaded = loadBotAppConfig({
345
- configPath,
346
- env: {}
347
- });
348
-
349
- rmSync(tempDir, { recursive: true, force: true });
350
-
351
- assert.equal(loaded.agent.mode, "mock");
352
- assert.equal(loaded.agent.provider, "openai");
353
- assert.equal(loaded.agent.model, "gpt-5-mini");
354
- assert.equal(loaded.agent.configPath, undefined);
355
- assert.equal(loaded.agent.cwd, dirname(configPath));
356
- assert.equal(
357
- (((loaded.configRoot?.agents as { defaults?: { model?: { primary?: string } } })?.defaults?.model?.primary) ?? ""),
358
- "openai/gpt-5-mini"
359
- );
360
- });
361
-
362
- test("loadBotAppConfig still allows env to override mode when config.json is missing", () => {
363
- const tempDir = mkdtempSync(join(tmpdir(), "pi-bot-missing-config-"));
364
- const configPath = join(tempDir, "workspace", "config.json");
365
-
366
- const loaded = loadBotAppConfig({
367
- configPath,
368
- env: {
369
- PI_BOT_AGENT_MODE: "pi"
370
- }
371
- });
372
-
373
- rmSync(tempDir, { recursive: true, force: true });
374
-
375
- assert.equal(loaded.agent.mode, "pi");
376
- assert.equal(loaded.agent.configPath, undefined);
377
- });