@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.
- package/lib/__templates__/expo/AGENTS.md +15 -7
- package/lib/__templates__/expo/README.md +15 -7
- package/lib/__templates__/expo/client/eslint.config.mjs +3 -0
- package/lib/__templates__/expo/eslint-plugins/expo/index.js +9 -0
- package/lib/__templates__/expo/eslint-plugins/expo/rule.js +105 -0
- package/lib/__templates__/expo/eslint-plugins/expo/tech.md +108 -0
- package/lib/__templates__/nextjs/AGENTS.md +9 -0
- package/lib/__templates__/nextjs/eslint.config.mjs +15 -0
- package/lib/__templates__/pi-agent/.coze +10 -0
- package/lib/__templates__/pi-agent/AGENTS.md +150 -0
- package/lib/__templates__/pi-agent/README.md +155 -0
- package/lib/__templates__/pi-agent/_gitignore +3 -0
- package/lib/__templates__/pi-agent/docs/project-overview.md +273 -0
- package/lib/__templates__/pi-agent/docs/user/getting-started.md +46 -0
- package/lib/__templates__/pi-agent/package.json +52 -0
- package/lib/__templates__/pi-agent/pnpm-lock.yaml +7840 -0
- package/lib/__templates__/pi-agent/scripts/dev.sh +14 -0
- package/lib/__templates__/pi-agent/scripts/prepare.sh +2 -0
- package/lib/__templates__/pi-agent/src/agent.ts +367 -0
- package/lib/__templates__/pi-agent/src/channels/feishu/index.ts +760 -0
- package/lib/__templates__/pi-agent/src/channels/feishu/streaming-card.ts +297 -0
- package/lib/__templates__/pi-agent/src/channels/wechat/index.ts +171 -0
- package/lib/__templates__/pi-agent/src/config.ts +596 -0
- package/lib/__templates__/pi-agent/src/core.ts +218 -0
- package/lib/__templates__/pi-agent/src/dashboard/api/channels.ts +148 -0
- package/lib/__templates__/pi-agent/src/dashboard/api/docs.ts +204 -0
- package/lib/__templates__/pi-agent/src/dashboard/api/models.ts +141 -0
- package/lib/__templates__/pi-agent/src/dashboard/api/overview.ts +33 -0
- package/lib/__templates__/pi-agent/src/dashboard/config-store.ts +64 -0
- package/lib/__templates__/pi-agent/src/dashboard/index.ts +39 -0
- package/lib/__templates__/pi-agent/src/dashboard/server.ts +622 -0
- package/lib/__templates__/pi-agent/src/dashboard/types.ts +25 -0
- package/lib/__templates__/pi-agent/src/dashboard/web/index.html +13 -0
- package/lib/__templates__/pi-agent/src/dashboard/web/postcss.config.cjs +7 -0
- package/lib/__templates__/pi-agent/src/dashboard/web/src/components/app-layout.tsx +186 -0
- package/lib/__templates__/pi-agent/src/dashboard/web/src/components/page-title.tsx +17 -0
- package/lib/__templates__/pi-agent/src/dashboard/web/src/components/ui/alert.tsx +22 -0
- package/lib/__templates__/pi-agent/src/dashboard/web/src/components/ui/badge.tsx +25 -0
- package/lib/__templates__/pi-agent/src/dashboard/web/src/components/ui/button.tsx +40 -0
- package/lib/__templates__/pi-agent/src/dashboard/web/src/components/ui/card.tsx +29 -0
- package/lib/__templates__/pi-agent/src/dashboard/web/src/components/ui/input.tsx +18 -0
- package/lib/__templates__/pi-agent/src/dashboard/web/src/components/ui/label.tsx +8 -0
- package/lib/__templates__/pi-agent/src/dashboard/web/src/components/ui/select.tsx +80 -0
- package/lib/__templates__/pi-agent/src/dashboard/web/src/components/ui/separator.tsx +23 -0
- package/lib/__templates__/pi-agent/src/dashboard/web/src/hooks/use-fetch.ts +32 -0
- package/lib/__templates__/pi-agent/src/dashboard/web/src/hooks/use-local-storage-state.ts +23 -0
- package/lib/__templates__/pi-agent/src/dashboard/web/src/main.tsx +30 -0
- package/lib/__templates__/pi-agent/src/dashboard/web/src/pages/channels-page.tsx +188 -0
- package/lib/__templates__/pi-agent/src/dashboard/web/src/pages/chat-page.tsx +451 -0
- package/lib/__templates__/pi-agent/src/dashboard/web/src/pages/docs-page.tsx +65 -0
- package/lib/__templates__/pi-agent/src/dashboard/web/src/pages/models-page.tsx +122 -0
- package/lib/__templates__/pi-agent/src/dashboard/web/src/pages/overview-page.tsx +134 -0
- package/lib/__templates__/pi-agent/src/dashboard/web/src/services/chat-ws-service.ts +167 -0
- package/lib/__templates__/pi-agent/src/dashboard/web/src/styles.css +294 -0
- package/lib/__templates__/pi-agent/src/dashboard/web/src/utils/index.ts +11 -0
- package/lib/__templates__/pi-agent/src/dashboard/web/tsconfig.json +13 -0
- package/lib/__templates__/pi-agent/src/dashboard/web/vite.config.ts +17 -0
- package/lib/__templates__/pi-agent/src/index.ts +123 -0
- package/lib/__templates__/pi-agent/src/session-store.ts +223 -0
- package/lib/__templates__/pi-agent/template.config.js +45 -0
- package/lib/__templates__/pi-agent/tests/config.test.ts +292 -0
- package/lib/__templates__/pi-agent/tests/dashboard-docs-api.test.ts +125 -0
- package/lib/__templates__/pi-agent/tests/dashboard-models-api.test.ts +171 -0
- package/lib/__templates__/pi-agent/tests/feishu-channel.test.ts +149 -0
- package/lib/__templates__/pi-agent/tests/feishu-streaming-card.test.ts +15 -0
- package/lib/__templates__/pi-agent/tests/session-store.test.ts +61 -0
- package/lib/__templates__/pi-agent/tests/smoke/run-smoke.ts +275 -0
- package/lib/__templates__/pi-agent/tsconfig.json +20 -0
- package/lib/__templates__/pi-agent/types/larksuiteoapi-node-sdk.d.ts +113 -0
- package/lib/__templates__/taro/pnpm-lock.yaml +24 -14
- package/lib/__templates__/taro/server/package.json +0 -2
- package/lib/__templates__/taro/src/presets/dev-debug.ts +2 -2
- package/lib/__templates__/templates.json +24 -0
- package/lib/__templates__/vite/AGENTS.md +5 -0
- package/lib/cli.js +1 -1
- package/package.json +1 -1
|
@@ -0,0 +1,294 @@
|
|
|
1
|
+
@import "tailwindcss";
|
|
2
|
+
@source "../../../../node_modules/streamdown/dist/*.js";
|
|
3
|
+
@source "../../../../node_modules/@streamdown/code/dist/*.js";
|
|
4
|
+
|
|
5
|
+
:root {
|
|
6
|
+
color-scheme: light;
|
|
7
|
+
--background: oklch(1 0 0);
|
|
8
|
+
--foreground: oklch(0.145 0 0);
|
|
9
|
+
--card: oklch(1 0 0);
|
|
10
|
+
--card-foreground: oklch(0.145 0 0);
|
|
11
|
+
--popover: oklch(1 0 0);
|
|
12
|
+
--popover-foreground: oklch(0.145 0 0);
|
|
13
|
+
--primary: oklch(0.205 0 0);
|
|
14
|
+
--primary-foreground: oklch(0.985 0 0);
|
|
15
|
+
--secondary: oklch(0.97 0 0);
|
|
16
|
+
--secondary-foreground: oklch(0.205 0 0);
|
|
17
|
+
--muted: oklch(0.97 0 0);
|
|
18
|
+
--muted-foreground: oklch(0.556 0 0);
|
|
19
|
+
--accent: oklch(0.97 0 0);
|
|
20
|
+
--accent-foreground: oklch(0.205 0 0);
|
|
21
|
+
--destructive: oklch(0.577 0.245 27.325);
|
|
22
|
+
--destructive-foreground: oklch(0.985 0 0);
|
|
23
|
+
--border: oklch(0.922 0 0);
|
|
24
|
+
--input: oklch(0.922 0 0);
|
|
25
|
+
--ring: oklch(0.708 0 0);
|
|
26
|
+
--radius: 0.625rem;
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
.dark {
|
|
30
|
+
color-scheme: dark;
|
|
31
|
+
--background: oklch(0.145 0 0);
|
|
32
|
+
--foreground: oklch(0.985 0 0);
|
|
33
|
+
--card: oklch(0.205 0 0);
|
|
34
|
+
--card-foreground: oklch(0.985 0 0);
|
|
35
|
+
--popover: oklch(0.205 0 0);
|
|
36
|
+
--popover-foreground: oklch(0.985 0 0);
|
|
37
|
+
--primary: oklch(0.922 0 0);
|
|
38
|
+
--primary-foreground: oklch(0.205 0 0);
|
|
39
|
+
--secondary: oklch(0.269 0 0);
|
|
40
|
+
--secondary-foreground: oklch(0.985 0 0);
|
|
41
|
+
--muted: oklch(0.269 0 0);
|
|
42
|
+
--muted-foreground: oklch(0.708 0 0);
|
|
43
|
+
--accent: oklch(0.269 0 0);
|
|
44
|
+
--accent-foreground: oklch(0.985 0 0);
|
|
45
|
+
--destructive: oklch(0.704 0.191 22.216);
|
|
46
|
+
--destructive-foreground: oklch(0.985 0 0);
|
|
47
|
+
--border: oklch(1 0 0 / 10%);
|
|
48
|
+
--input: oklch(1 0 0 / 15%);
|
|
49
|
+
--ring: oklch(0.556 0 0);
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
@theme inline {
|
|
53
|
+
--font-sans: ui-sans-serif, system-ui, -apple-system, "Segoe UI", Roboto, Arial, "Apple Color Emoji", "Segoe UI Emoji";
|
|
54
|
+
--color-background: var(--background);
|
|
55
|
+
--color-foreground: var(--foreground);
|
|
56
|
+
--color-card: var(--card);
|
|
57
|
+
--color-card-foreground: var(--card-foreground);
|
|
58
|
+
--color-popover: var(--popover);
|
|
59
|
+
--color-popover-foreground: var(--popover-foreground);
|
|
60
|
+
--color-primary: var(--primary);
|
|
61
|
+
--color-primary-foreground: var(--primary-foreground);
|
|
62
|
+
--color-secondary: var(--secondary);
|
|
63
|
+
--color-secondary-foreground: var(--secondary-foreground);
|
|
64
|
+
--color-muted: var(--muted);
|
|
65
|
+
--color-muted-foreground: var(--muted-foreground);
|
|
66
|
+
--color-accent: var(--accent);
|
|
67
|
+
--color-accent-foreground: var(--accent-foreground);
|
|
68
|
+
--color-destructive: var(--destructive);
|
|
69
|
+
--color-destructive-foreground: var(--destructive-foreground);
|
|
70
|
+
--color-border: var(--border);
|
|
71
|
+
--color-input: var(--input);
|
|
72
|
+
--color-ring: var(--ring);
|
|
73
|
+
--radius-sm: calc(var(--radius) - 6px);
|
|
74
|
+
--radius-md: calc(var(--radius) - 2px);
|
|
75
|
+
--radius-lg: var(--radius);
|
|
76
|
+
--radius-xl: calc(var(--radius) + 8px);
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
@layer base {
|
|
80
|
+
* {
|
|
81
|
+
box-sizing: border-box;
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
body {
|
|
85
|
+
@apply bg-background font-sans text-foreground antialiased transition-colors;
|
|
86
|
+
height: 100vh;
|
|
87
|
+
overflow: hidden;
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
#root {
|
|
91
|
+
height: 100%;
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
a {
|
|
95
|
+
@apply text-inherit;
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
h1,
|
|
99
|
+
h2,
|
|
100
|
+
h3,
|
|
101
|
+
p {
|
|
102
|
+
@apply m-0;
|
|
103
|
+
}
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
@supports (height: 100dvh) {
|
|
107
|
+
@layer base {
|
|
108
|
+
body {
|
|
109
|
+
height: 100dvh;
|
|
110
|
+
}
|
|
111
|
+
}
|
|
112
|
+
}
|
|
113
|
+
|
|
114
|
+
.streamdown-content [data-streamdown] {
|
|
115
|
+
overflow-wrap: anywhere;
|
|
116
|
+
}
|
|
117
|
+
|
|
118
|
+
.streamdown-content [data-streamdown="code-block"] {
|
|
119
|
+
position: relative;
|
|
120
|
+
margin: 1rem 0;
|
|
121
|
+
gap: 0.5rem;
|
|
122
|
+
border: 1px solid color-mix(in oklab, var(--border) 100%, transparent);
|
|
123
|
+
border-radius: 0.875rem;
|
|
124
|
+
background: color-mix(in oklab, var(--muted) 65%, var(--background));
|
|
125
|
+
padding: 0.75rem;
|
|
126
|
+
}
|
|
127
|
+
|
|
128
|
+
.streamdown-content [data-streamdown="code-block-header"] {
|
|
129
|
+
display: flex;
|
|
130
|
+
min-height: 1.75rem;
|
|
131
|
+
align-items: center;
|
|
132
|
+
height: auto;
|
|
133
|
+
padding: 0 4.5rem 0 0.125rem;
|
|
134
|
+
color: var(--muted-foreground);
|
|
135
|
+
font-size: 0.75rem;
|
|
136
|
+
}
|
|
137
|
+
|
|
138
|
+
.streamdown-content [data-streamdown="code-block-header"] + div {
|
|
139
|
+
position: absolute;
|
|
140
|
+
top: 0.75rem;
|
|
141
|
+
right: 0.75rem;
|
|
142
|
+
left: auto;
|
|
143
|
+
z-index: 20;
|
|
144
|
+
margin: 0;
|
|
145
|
+
padding: 0;
|
|
146
|
+
}
|
|
147
|
+
|
|
148
|
+
.streamdown-content [data-streamdown="code-block-actions"] {
|
|
149
|
+
gap: 0.375rem;
|
|
150
|
+
border: 1px solid color-mix(in oklab, var(--border) 100%, transparent);
|
|
151
|
+
border-radius: 0.625rem;
|
|
152
|
+
background: color-mix(in oklab, var(--background) 88%, transparent);
|
|
153
|
+
padding: 0.25rem;
|
|
154
|
+
box-shadow: none;
|
|
155
|
+
backdrop-filter: none;
|
|
156
|
+
}
|
|
157
|
+
|
|
158
|
+
.streamdown-content [data-streamdown="code-block-actions"] button {
|
|
159
|
+
display: inline-flex;
|
|
160
|
+
height: 1.75rem;
|
|
161
|
+
width: 1.75rem;
|
|
162
|
+
align-items: center;
|
|
163
|
+
justify-content: center;
|
|
164
|
+
border: 0;
|
|
165
|
+
border-radius: 0.5rem;
|
|
166
|
+
background: transparent;
|
|
167
|
+
color: var(--muted-foreground);
|
|
168
|
+
transition:
|
|
169
|
+
background-color 150ms ease,
|
|
170
|
+
color 150ms ease;
|
|
171
|
+
}
|
|
172
|
+
|
|
173
|
+
.streamdown-content [data-streamdown="code-block-actions"] button:hover {
|
|
174
|
+
background: color-mix(in oklab, var(--accent) 100%, transparent);
|
|
175
|
+
color: var(--foreground);
|
|
176
|
+
}
|
|
177
|
+
|
|
178
|
+
.streamdown-content [data-streamdown="code-block-actions"] button:focus-visible {
|
|
179
|
+
outline: 2px solid color-mix(in oklab, var(--ring) 65%, transparent);
|
|
180
|
+
outline-offset: 1px;
|
|
181
|
+
}
|
|
182
|
+
|
|
183
|
+
.streamdown-content [data-streamdown="code-block-body"] {
|
|
184
|
+
border: 1px solid color-mix(in oklab, var(--border) 100%, transparent);
|
|
185
|
+
border-radius: 0.75rem;
|
|
186
|
+
background: color-mix(in oklab, var(--background) 92%, var(--muted));
|
|
187
|
+
margin-top: 0.5rem;
|
|
188
|
+
padding: 0.875rem 1rem;
|
|
189
|
+
}
|
|
190
|
+
|
|
191
|
+
.streamdown-content [data-streamdown="code-block-body"] pre {
|
|
192
|
+
margin: 0;
|
|
193
|
+
background: transparent;
|
|
194
|
+
}
|
|
195
|
+
|
|
196
|
+
.streamdown-content pre {
|
|
197
|
+
overflow: auto;
|
|
198
|
+
}
|
|
199
|
+
|
|
200
|
+
.streamdown-content img {
|
|
201
|
+
max-width: 100%;
|
|
202
|
+
height: auto;
|
|
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
|
+
}
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
import { type ClassValue, clsx } from "clsx";
|
|
2
|
+
import { twMerge } from "tailwind-merge";
|
|
3
|
+
import { cloneDeep } from "lodash-es";
|
|
4
|
+
|
|
5
|
+
export function cn(...inputs: ClassValue[]) {
|
|
6
|
+
return twMerge(clsx(inputs));
|
|
7
|
+
}
|
|
8
|
+
|
|
9
|
+
export function deepClone<T>(value: T): T {
|
|
10
|
+
return cloneDeep(value);
|
|
11
|
+
}
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
{
|
|
2
|
+
"extends": "../../../tsconfig.json",
|
|
3
|
+
"compilerOptions": {
|
|
4
|
+
"jsx": "react-jsx",
|
|
5
|
+
"lib": ["ES2022", "DOM", "DOM.Iterable"],
|
|
6
|
+
"module": "ESNext",
|
|
7
|
+
"moduleResolution": "Bundler",
|
|
8
|
+
"types": ["vite/client"],
|
|
9
|
+
"noEmit": true
|
|
10
|
+
},
|
|
11
|
+
"include": ["vite.config.ts", "src/**/*.ts", "src/**/*.tsx"]
|
|
12
|
+
}
|
|
13
|
+
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
import { defineConfig } from "vite";
|
|
2
|
+
import react from "@vitejs/plugin-react";
|
|
3
|
+
import { fileURLToPath } from "node:url";
|
|
4
|
+
import { dirname, resolve } from "node:path";
|
|
5
|
+
|
|
6
|
+
const DIR = dirname(fileURLToPath(import.meta.url));
|
|
7
|
+
|
|
8
|
+
export default defineConfig({
|
|
9
|
+
root: DIR,
|
|
10
|
+
plugins: [react()],
|
|
11
|
+
base: "/",
|
|
12
|
+
build: {
|
|
13
|
+
outDir: resolve(DIR, "dist"),
|
|
14
|
+
emptyOutDir: true,
|
|
15
|
+
},
|
|
16
|
+
});
|
|
17
|
+
|
|
@@ -0,0 +1,123 @@
|
|
|
1
|
+
import { fileURLToPath } from "node:url";
|
|
2
|
+
import type { BotAppConfig } from "./core.js";
|
|
3
|
+
import type { FeishuChannel } from "./channels/feishu/index.js";
|
|
4
|
+
import { createFeishuChannel } from "./channels/feishu/index.js";
|
|
5
|
+
import type { WechatChannel } from "./channels/wechat/index.js";
|
|
6
|
+
import { createWechatChannel } from "./channels/wechat/index.js";
|
|
7
|
+
import { loadBotAppConfig } from "./config.js";
|
|
8
|
+
import { createAgentRuntime } from "./agent.js";
|
|
9
|
+
import type { DashboardServer } from "./dashboard/types.js";
|
|
10
|
+
import { createDashboard } from "./dashboard/index.js";
|
|
11
|
+
import type { ConfigStore } from "./dashboard/config-store.js";
|
|
12
|
+
|
|
13
|
+
export interface BotApp {
|
|
14
|
+
config: BotAppConfig;
|
|
15
|
+
channels: {
|
|
16
|
+
feishu?: FeishuChannel;
|
|
17
|
+
wechat?: WechatChannel;
|
|
18
|
+
};
|
|
19
|
+
dashboard: DashboardServer;
|
|
20
|
+
start(): Promise<void>;
|
|
21
|
+
stop(): Promise<void>;
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
export async function createBotApp(
|
|
25
|
+
config: BotAppConfig = loadBotAppConfig(),
|
|
26
|
+
options: { dashboardConfigStore?: ConfigStore } = {},
|
|
27
|
+
): Promise<BotApp> {
|
|
28
|
+
const runtime = await createAgentRuntime(config.agent);
|
|
29
|
+
const channels: BotApp["channels"] = {};
|
|
30
|
+
|
|
31
|
+
if (config.channels.feishu?.enabled) {
|
|
32
|
+
channels.feishu = createFeishuChannel(
|
|
33
|
+
{
|
|
34
|
+
appId: config.channels.feishu.appId,
|
|
35
|
+
appSecret: config.channels.feishu.appSecret,
|
|
36
|
+
domain: config.channels.feishu.domain,
|
|
37
|
+
encryptKey: config.channels.feishu.encryptKey,
|
|
38
|
+
verificationToken: config.channels.feishu.verificationToken,
|
|
39
|
+
routing: {
|
|
40
|
+
groupRequireMention: config.routing.feishuGroupRequireMention
|
|
41
|
+
}
|
|
42
|
+
},
|
|
43
|
+
{
|
|
44
|
+
async onMessage(message) {
|
|
45
|
+
const text = await runtime.run(message);
|
|
46
|
+
return { text };
|
|
47
|
+
},
|
|
48
|
+
async onStreamMessage(message, handlers) {
|
|
49
|
+
const result = await runtime.stream(message, {
|
|
50
|
+
onMeta: handlers.onMeta,
|
|
51
|
+
onDelta: handlers.onDelta,
|
|
52
|
+
onError: handlers.onError
|
|
53
|
+
});
|
|
54
|
+
return { text: result.finalText };
|
|
55
|
+
}
|
|
56
|
+
}
|
|
57
|
+
);
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
if (config.channels.wechat?.enabled) {
|
|
61
|
+
channels.wechat = createWechatChannel(
|
|
62
|
+
{
|
|
63
|
+
routing: {
|
|
64
|
+
groupRequireMention: config.routing.wechatGroupRequireMention
|
|
65
|
+
}
|
|
66
|
+
},
|
|
67
|
+
{
|
|
68
|
+
async onMessage(message) {
|
|
69
|
+
const text = await runtime.run(message);
|
|
70
|
+
return { text };
|
|
71
|
+
}
|
|
72
|
+
}
|
|
73
|
+
);
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
const dashboard = createDashboard({
|
|
77
|
+
botConfig: config,
|
|
78
|
+
channels,
|
|
79
|
+
agentRuntime: runtime,
|
|
80
|
+
configStore: options.dashboardConfigStore,
|
|
81
|
+
});
|
|
82
|
+
|
|
83
|
+
return {
|
|
84
|
+
config,
|
|
85
|
+
channels,
|
|
86
|
+
dashboard,
|
|
87
|
+
async start() {
|
|
88
|
+
await Promise.all([
|
|
89
|
+
...Object.values(channels).map((channel) => channel.start()),
|
|
90
|
+
dashboard.start()
|
|
91
|
+
]);
|
|
92
|
+
},
|
|
93
|
+
async stop() {
|
|
94
|
+
await Promise.all([
|
|
95
|
+
...Object.values(channels).map((channel) => channel.stop()),
|
|
96
|
+
dashboard.stop()
|
|
97
|
+
]);
|
|
98
|
+
await runtime.dispose();
|
|
99
|
+
}
|
|
100
|
+
};
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
export async function main(): Promise<void> {
|
|
104
|
+
const config = loadBotAppConfig();
|
|
105
|
+
const app = await createBotApp(config);
|
|
106
|
+
await app.start();
|
|
107
|
+
|
|
108
|
+
console.log(`[${config.appName}] started`);
|
|
109
|
+
console.log(`Feishu enabled: ${Boolean(app.channels.feishu)}`);
|
|
110
|
+
console.log(`WeChat enabled: ${Boolean(app.channels.wechat)}`);
|
|
111
|
+
console.log(`Dashboard: ${app.dashboard.getUrl()}`);
|
|
112
|
+
console.log(`Agent mode: ${config.agent.mode}`);
|
|
113
|
+
console.log(`Workspace dir: ${config.agent.cwd}`);
|
|
114
|
+
console.log(`Agent dir: ${config.agent.agentDir}`);
|
|
115
|
+
}
|
|
116
|
+
|
|
117
|
+
const entrypoint = process.argv[1];
|
|
118
|
+
if (entrypoint && fileURLToPath(import.meta.url) === entrypoint) {
|
|
119
|
+
main().catch((error: unknown) => {
|
|
120
|
+
console.error(error);
|
|
121
|
+
process.exit(1);
|
|
122
|
+
});
|
|
123
|
+
}
|
|
@@ -0,0 +1,223 @@
|
|
|
1
|
+
import { createHash, randomUUID } from "node:crypto";
|
|
2
|
+
import fs from "node:fs";
|
|
3
|
+
import path from "node:path";
|
|
4
|
+
import { CURRENT_SESSION_VERSION } from "@mariozechner/pi-coding-agent";
|
|
5
|
+
|
|
6
|
+
export type SessionRecord = {
|
|
7
|
+
sessionKey: string;
|
|
8
|
+
keyHash: string;
|
|
9
|
+
sessionId: string;
|
|
10
|
+
sessionFile: string;
|
|
11
|
+
updatedAt: number;
|
|
12
|
+
};
|
|
13
|
+
|
|
14
|
+
type IndexFileShapeV1 = {
|
|
15
|
+
version: 1;
|
|
16
|
+
updatedAt: number;
|
|
17
|
+
sessions: Record<
|
|
18
|
+
string,
|
|
19
|
+
{
|
|
20
|
+
keyHash: string;
|
|
21
|
+
sessionId: string;
|
|
22
|
+
sessionFile: string;
|
|
23
|
+
updatedAt: number;
|
|
24
|
+
}
|
|
25
|
+
>;
|
|
26
|
+
};
|
|
27
|
+
|
|
28
|
+
function sha256Hex(input: string): string {
|
|
29
|
+
return createHash("sha256").update(input).digest("hex");
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
function safeRename(from: string, to: string): void {
|
|
33
|
+
try {
|
|
34
|
+
fs.renameSync(from, to);
|
|
35
|
+
} catch {
|
|
36
|
+
// best-effort
|
|
37
|
+
}
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
function ensureDir(dirPath: string): void {
|
|
41
|
+
fs.mkdirSync(dirPath, { recursive: true });
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
function readJsonFile(filePath: string): unknown {
|
|
45
|
+
const raw = fs.readFileSync(filePath, "utf-8");
|
|
46
|
+
return JSON.parse(raw) as unknown;
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
function writeFileAtomic(filePath: string, content: string, mode: number): void {
|
|
50
|
+
const dir = path.dirname(filePath);
|
|
51
|
+
ensureDir(dir);
|
|
52
|
+
const tmpPath = `${filePath}.tmp`;
|
|
53
|
+
fs.writeFileSync(tmpPath, content, { encoding: "utf-8", mode });
|
|
54
|
+
fs.renameSync(tmpPath, filePath);
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
function ensureTranscriptHeader(params: { sessionFile: string; sessionId: string }): void {
|
|
58
|
+
if (fs.existsSync(params.sessionFile)) {
|
|
59
|
+
return;
|
|
60
|
+
}
|
|
61
|
+
ensureDir(path.dirname(params.sessionFile));
|
|
62
|
+
const header = {
|
|
63
|
+
type: "session",
|
|
64
|
+
version: CURRENT_SESSION_VERSION,
|
|
65
|
+
id: params.sessionId,
|
|
66
|
+
timestamp: new Date().toISOString(),
|
|
67
|
+
cwd: process.cwd(),
|
|
68
|
+
};
|
|
69
|
+
fs.writeFileSync(params.sessionFile, `${JSON.stringify(header)}\n`, {
|
|
70
|
+
encoding: "utf-8",
|
|
71
|
+
mode: 0o600,
|
|
72
|
+
});
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
function decodeIndexFileShapeV1(value: unknown): IndexFileShapeV1 | null {
|
|
76
|
+
if (!value || typeof value !== "object" || Array.isArray(value)) return null;
|
|
77
|
+
const rec = value as Record<string, unknown>;
|
|
78
|
+
if (rec.version !== 1) return null;
|
|
79
|
+
if (!("sessions" in rec) || !rec.sessions || typeof rec.sessions !== "object" || Array.isArray(rec.sessions)) {
|
|
80
|
+
return null;
|
|
81
|
+
}
|
|
82
|
+
const updatedAt = typeof rec.updatedAt === "number" ? rec.updatedAt : Date.now();
|
|
83
|
+
const sessions = rec.sessions as Record<string, unknown>;
|
|
84
|
+
const out: IndexFileShapeV1["sessions"] = {};
|
|
85
|
+
for (const [sessionKey, entry] of Object.entries(sessions)) {
|
|
86
|
+
if (!entry || typeof entry !== "object" || Array.isArray(entry)) continue;
|
|
87
|
+
const e = entry as Record<string, unknown>;
|
|
88
|
+
const keyHash = typeof e.keyHash === "string" ? e.keyHash : "";
|
|
89
|
+
const sessionId = typeof e.sessionId === "string" ? e.sessionId : "";
|
|
90
|
+
const sessionFile = typeof e.sessionFile === "string" ? e.sessionFile : "";
|
|
91
|
+
const entryUpdatedAt = typeof e.updatedAt === "number" ? e.updatedAt : updatedAt;
|
|
92
|
+
if (!sessionKey || !keyHash || !sessionId || !sessionFile) continue;
|
|
93
|
+
out[sessionKey] = { keyHash, sessionId, sessionFile, updatedAt: entryUpdatedAt };
|
|
94
|
+
}
|
|
95
|
+
return { version: 1, updatedAt, sessions: out };
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
export class SessionStore {
|
|
99
|
+
private readonly sessionsDir: string;
|
|
100
|
+
private readonly indexPath: string;
|
|
101
|
+
private readonly transcriptsDir: string;
|
|
102
|
+
private readonly archiveDir: string;
|
|
103
|
+
private readonly cache = new Map<string, SessionRecord>();
|
|
104
|
+
|
|
105
|
+
constructor(private readonly agentDir: string) {
|
|
106
|
+
this.sessionsDir = path.join(agentDir, ".pi-bot", "sessions");
|
|
107
|
+
this.indexPath = path.join(this.sessionsDir, "index.json");
|
|
108
|
+
this.transcriptsDir = path.join(this.sessionsDir, "transcripts");
|
|
109
|
+
this.archiveDir = path.join(this.sessionsDir, "archive");
|
|
110
|
+
}
|
|
111
|
+
|
|
112
|
+
load(): void {
|
|
113
|
+
this.cache.clear();
|
|
114
|
+
|
|
115
|
+
if (!fs.existsSync(this.indexPath)) {
|
|
116
|
+
return;
|
|
117
|
+
}
|
|
118
|
+
|
|
119
|
+
try {
|
|
120
|
+
const decoded = decodeIndexFileShapeV1(readJsonFile(this.indexPath));
|
|
121
|
+
if (!decoded) {
|
|
122
|
+
throw new Error("invalid index.json schema");
|
|
123
|
+
}
|
|
124
|
+
for (const [sessionKey, entry] of Object.entries(decoded.sessions)) {
|
|
125
|
+
this.cache.set(sessionKey, {
|
|
126
|
+
sessionKey,
|
|
127
|
+
keyHash: entry.keyHash,
|
|
128
|
+
sessionId: entry.sessionId,
|
|
129
|
+
sessionFile: entry.sessionFile,
|
|
130
|
+
updatedAt: entry.updatedAt,
|
|
131
|
+
});
|
|
132
|
+
}
|
|
133
|
+
} catch {
|
|
134
|
+
// Keep a corrupt copy for debugging instead of crashing startup.
|
|
135
|
+
safeRename(this.indexPath, `${this.indexPath}.corrupt-${Date.now()}`);
|
|
136
|
+
this.cache.clear();
|
|
137
|
+
}
|
|
138
|
+
}
|
|
139
|
+
|
|
140
|
+
listSessionKeys(): string[] {
|
|
141
|
+
return Array.from(this.cache.keys());
|
|
142
|
+
}
|
|
143
|
+
|
|
144
|
+
get(sessionKey: string): SessionRecord | undefined {
|
|
145
|
+
return this.cache.get(sessionKey);
|
|
146
|
+
}
|
|
147
|
+
|
|
148
|
+
ensureSession(sessionKey: string): SessionRecord {
|
|
149
|
+
const existing = this.cache.get(sessionKey);
|
|
150
|
+
if (existing) {
|
|
151
|
+
// If the transcript was deleted externally, recreate the header so the
|
|
152
|
+
// session remains recoverable.
|
|
153
|
+
ensureTranscriptHeader({ sessionFile: existing.sessionFile, sessionId: existing.sessionId });
|
|
154
|
+
return existing;
|
|
155
|
+
}
|
|
156
|
+
|
|
157
|
+
const now = Date.now();
|
|
158
|
+
const keyHash = sha256Hex(sessionKey);
|
|
159
|
+
const sessionId = randomUUID();
|
|
160
|
+
const sessionFile = path.join(this.transcriptsDir, `${keyHash}-${sessionId}.jsonl`);
|
|
161
|
+
const record: SessionRecord = {
|
|
162
|
+
sessionKey,
|
|
163
|
+
keyHash,
|
|
164
|
+
sessionId,
|
|
165
|
+
sessionFile,
|
|
166
|
+
updatedAt: now,
|
|
167
|
+
};
|
|
168
|
+
this.cache.set(sessionKey, record);
|
|
169
|
+
ensureTranscriptHeader({ sessionFile, sessionId });
|
|
170
|
+
this.saveIndex();
|
|
171
|
+
return record;
|
|
172
|
+
}
|
|
173
|
+
|
|
174
|
+
resetSession(sessionKey: string): SessionRecord {
|
|
175
|
+
const prior = this.cache.get(sessionKey);
|
|
176
|
+
if (prior && fs.existsSync(prior.sessionFile)) {
|
|
177
|
+
ensureDir(this.archiveDir);
|
|
178
|
+
const baseName = path.basename(prior.sessionFile);
|
|
179
|
+
const archivedBase = baseName.endsWith(".jsonl") ? baseName : `${baseName}.jsonl`;
|
|
180
|
+
const archivedPathCandidate = path.join(this.archiveDir, archivedBase);
|
|
181
|
+
const archivedPath = fs.existsSync(archivedPathCandidate)
|
|
182
|
+
? path.join(this.archiveDir, `${prior.keyHash}-${prior.sessionId}-${Date.now()}.jsonl`)
|
|
183
|
+
: archivedPathCandidate;
|
|
184
|
+
safeRename(prior.sessionFile, archivedPath);
|
|
185
|
+
}
|
|
186
|
+
|
|
187
|
+
// Keep keyHash stable for this sessionKey, rotate sessionId + sessionFile.
|
|
188
|
+
const now = Date.now();
|
|
189
|
+
const keyHash = prior?.keyHash ?? sha256Hex(sessionKey);
|
|
190
|
+
const sessionId = randomUUID();
|
|
191
|
+
const sessionFile = path.join(this.transcriptsDir, `${keyHash}-${sessionId}.jsonl`);
|
|
192
|
+
const next: SessionRecord = {
|
|
193
|
+
sessionKey,
|
|
194
|
+
keyHash,
|
|
195
|
+
sessionId,
|
|
196
|
+
sessionFile,
|
|
197
|
+
updatedAt: now,
|
|
198
|
+
};
|
|
199
|
+
this.cache.set(sessionKey, next);
|
|
200
|
+
ensureTranscriptHeader({ sessionFile, sessionId });
|
|
201
|
+
this.saveIndex();
|
|
202
|
+
return next;
|
|
203
|
+
}
|
|
204
|
+
|
|
205
|
+
private saveIndex(): void {
|
|
206
|
+
const now = Date.now();
|
|
207
|
+
const sessions: IndexFileShapeV1["sessions"] = {};
|
|
208
|
+
for (const [sessionKey, entry] of this.cache.entries()) {
|
|
209
|
+
sessions[sessionKey] = {
|
|
210
|
+
keyHash: entry.keyHash,
|
|
211
|
+
sessionId: entry.sessionId,
|
|
212
|
+
sessionFile: entry.sessionFile,
|
|
213
|
+
updatedAt: entry.updatedAt,
|
|
214
|
+
};
|
|
215
|
+
}
|
|
216
|
+
const payload: IndexFileShapeV1 = {
|
|
217
|
+
version: 1,
|
|
218
|
+
updatedAt: now,
|
|
219
|
+
sessions,
|
|
220
|
+
};
|
|
221
|
+
writeFileAtomic(this.indexPath, JSON.stringify(payload, null, 2), 0o600);
|
|
222
|
+
}
|
|
223
|
+
}
|