@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
@@ -0,0 +1,14 @@
1
+ #!/usr/bin/env bash
2
+ set -euo pipefail
3
+
4
+ SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
5
+ PROJECT_ROOT="$(cd "${SCRIPT_DIR}/.." && pwd)"
6
+
7
+ cd "${PROJECT_ROOT}"
8
+
9
+ set -a
10
+ [ -f .env ] && source .env
11
+ [ -f .env.local ] && source .env.local
12
+ set +a
13
+
14
+ exec node --import tsx src/index.ts
@@ -0,0 +1,2 @@
1
+ echo "Installing dependencies..."
2
+ pnpm install
@@ -0,0 +1,367 @@
1
+ import { mkdirSync } from "node:fs";
2
+ import { join } from "node:path";
3
+ import {
4
+ type AgentRuntime,
5
+ type AgentRuntimeConfig,
6
+ type BotMessage,
7
+ getSessionKey
8
+ } from "./core.js";
9
+ import { resolveRuntimeModel } from "./config.js";
10
+ import type { AgentSession } from "@mariozechner/pi-coding-agent";
11
+ import {
12
+ AuthStorage,
13
+ createAgentSession,
14
+ DefaultResourceLoader,
15
+ ModelRegistry,
16
+ SessionManager,
17
+ SettingsManager
18
+ } from "@mariozechner/pi-coding-agent";
19
+ import { SessionStore } from "./session-store.js";
20
+
21
+ export type PiAgentStreamHandlers = {
22
+ onMeta?: (meta: { sessionKey: string }) => void;
23
+ onDelta?: (delta: string) => void;
24
+ onError?: (error: string) => void;
25
+ /**
26
+ * Optional hook for forwarding raw session events (useful for debugging/telemetry).
27
+ * Keep this best-effort: callers should not rely on a stable event schema here.
28
+ */
29
+ onEvent?: (event: unknown) => void;
30
+ };
31
+
32
+ /**
33
+ * Extension of AgentRuntime used by the dashboard.
34
+ * Channels keep using the stable `run()` API.
35
+ */
36
+ export interface PiAgentRuntime extends AgentRuntime {
37
+ stream(message: BotMessage, handlers: PiAgentStreamHandlers): Promise<{ sessionKey: string; finalText: string }>;
38
+ getSessionIfExists(sessionKey: string): AgentSession | undefined;
39
+ listSessionKeys(): string[];
40
+ ensureSessionLoaded(sessionKey: string): Promise<AgentSession | undefined>;
41
+ abortSession(sessionKey: string): Promise<void>;
42
+ resetSession(sessionKey: string): Promise<void>;
43
+ }
44
+
45
+ function extractAssistantText(session: AgentSession): string {
46
+ const messages = [...session.state.messages].reverse();
47
+
48
+ for (const message of messages) {
49
+ if ((message as { role?: string }).role !== "assistant") {
50
+ continue;
51
+ }
52
+
53
+ const content = (message as { content?: unknown }).content;
54
+ if (typeof content === "string") {
55
+ return content.trim();
56
+ }
57
+
58
+ if (!Array.isArray(content)) {
59
+ return "";
60
+ }
61
+
62
+ return content
63
+ .flatMap((part) => {
64
+ if (!part || typeof part !== "object") {
65
+ return [];
66
+ }
67
+
68
+ const typedPart = part as { type?: unknown; text?: unknown };
69
+ return typedPart.type === "text" && typeof typedPart.text === "string" ? [typedPart.text] : [];
70
+ })
71
+ .join("")
72
+ .trim();
73
+ }
74
+
75
+ return "";
76
+ }
77
+
78
+ function extractAssistantTextFromMessage(message: unknown): string {
79
+ if (!message || typeof message !== "object") return "";
80
+ if ((message as { role?: unknown }).role !== "assistant") return "";
81
+
82
+ const content = (message as { content?: unknown }).content;
83
+ if (typeof content === "string") {
84
+ return content;
85
+ }
86
+ if (!Array.isArray(content)) {
87
+ return "";
88
+ }
89
+ return content
90
+ .flatMap((part) => {
91
+ if (!part || typeof part !== "object") return [];
92
+ const typedPart = part as { type?: unknown; text?: unknown };
93
+ return typedPart.type === "text" && typeof typedPart.text === "string" ? [typedPart.text] : [];
94
+ })
95
+ .join("");
96
+ }
97
+
98
+ export function createMockAgentRuntime(): PiAgentRuntime {
99
+ const sessionHistory = new Map<string, string[]>();
100
+
101
+ return {
102
+ async run(message: BotMessage): Promise<string> {
103
+ const sessionKey = getSessionKey(message);
104
+ const history = sessionHistory.get(sessionKey) ?? [];
105
+ history.push(message.text);
106
+ sessionHistory.set(sessionKey, history);
107
+ return `mock:${sessionKey}: ${message.text}`;
108
+ },
109
+ async stream(message: BotMessage, handlers: PiAgentStreamHandlers) {
110
+ const sessionKey = getSessionKey(message);
111
+ handlers.onMeta?.({ sessionKey });
112
+ const history = sessionHistory.get(sessionKey) ?? [];
113
+ history.push(message.text);
114
+ sessionHistory.set(sessionKey, history);
115
+ const text = `mock:${sessionKey}: ${message.text}`;
116
+ handlers.onDelta?.(text);
117
+ return { sessionKey, finalText: text };
118
+ },
119
+ getSessionIfExists(_sessionKey: string): AgentSession | undefined {
120
+ return undefined;
121
+ },
122
+ listSessionKeys(): string[] {
123
+ return Array.from(sessionHistory.keys());
124
+ },
125
+ async ensureSessionLoaded(_sessionKey: string): Promise<AgentSession | undefined> {
126
+ return undefined;
127
+ },
128
+ async abortSession(_sessionKey: string): Promise<void> {},
129
+ async resetSession(_sessionKey: string): Promise<void> {},
130
+ async dispose(): Promise<void> {}
131
+ } satisfies PiAgentRuntime;
132
+ }
133
+
134
+ export async function createPiAgentRuntime(config: AgentRuntimeConfig): Promise<PiAgentRuntime> {
135
+ const cwd = config.cwd ?? process.cwd();
136
+ const agentDir = config.agentDir;
137
+ const sessionRootDir = agentDir ?? cwd;
138
+ const authStorage = agentDir ? AuthStorage.create(join(agentDir, "auth.json")) : AuthStorage.create();
139
+ const modelRegistry = new ModelRegistry(
140
+ authStorage,
141
+ agentDir ? join(agentDir, "models.json") : undefined
142
+ );
143
+ const settingsManager = SettingsManager.create(cwd, agentDir);
144
+ const resourceLoader = new DefaultResourceLoader({
145
+ cwd,
146
+ agentDir,
147
+ settingsManager
148
+ });
149
+ const resolvedModel = resolveRuntimeModel({
150
+ provider: config.provider,
151
+ model: config.model,
152
+ baseUrl: config.baseUrl,
153
+ configPath: config.configPath
154
+ });
155
+ const sessions = new Map<string, AgentSession>();
156
+ const pendingSessionLoads = new Map<string, Promise<AgentSession>>();
157
+ const sessionGenerations = new Map<string, number>();
158
+ const sessionStore = new SessionStore(sessionRootDir);
159
+
160
+ if (agentDir) {
161
+ mkdirSync(agentDir, { recursive: true });
162
+ }
163
+
164
+ await resourceLoader.reload();
165
+ sessionStore.load();
166
+
167
+ if (resolvedModel?.apiKey) {
168
+ authStorage.setRuntimeApiKey(resolvedModel.model.provider, resolvedModel.apiKey);
169
+ }
170
+
171
+ async function getOrCreateSession(sessionKey: string): Promise<AgentSession> {
172
+ const existing = sessions.get(sessionKey);
173
+ if (existing) return existing;
174
+
175
+ const pending = pendingSessionLoads.get(sessionKey);
176
+ if (pending) return pending;
177
+
178
+ const capturedGen = sessionGenerations.get(sessionKey) ?? 0;
179
+ const sessionPromise = (async () => {
180
+ const record = sessionStore.ensureSession(sessionKey);
181
+ const sessionManager = SessionManager.open(record.sessionFile);
182
+ const { session } = await createAgentSession({
183
+ authStorage,
184
+ cwd,
185
+ agentDir,
186
+ model: resolvedModel?.model,
187
+ modelRegistry,
188
+ thinkingLevel: config.thinkingLevel,
189
+ tools: [],
190
+ customTools: [],
191
+ resourceLoader,
192
+ settingsManager,
193
+ sessionManager
194
+ });
195
+
196
+ const currentGen = sessionGenerations.get(sessionKey) ?? 0;
197
+ if (currentGen !== capturedGen) {
198
+ // Session was reset while we were creating it; discard this instance.
199
+ try {
200
+ (session as unknown as { dispose?: () => void }).dispose?.();
201
+ } catch {
202
+ // ignore
203
+ }
204
+ throw new Error("stale session");
205
+ }
206
+
207
+ sessions.set(sessionKey, session);
208
+ return session;
209
+ })();
210
+
211
+ pendingSessionLoads.set(sessionKey, sessionPromise);
212
+
213
+ try {
214
+ return await sessionPromise;
215
+ } finally {
216
+ pendingSessionLoads.delete(sessionKey);
217
+ }
218
+ }
219
+
220
+ return {
221
+ async run(message: BotMessage): Promise<string> {
222
+ const sessionKey = getSessionKey(message);
223
+ const session = await getOrCreateSession(sessionKey);
224
+ if (session.isStreaming) {
225
+ await session.prompt(message.text, { streamingBehavior: "followUp" });
226
+ } else {
227
+ await session.prompt(message.text);
228
+ }
229
+ return extractAssistantText(session);
230
+ },
231
+ async stream(message: BotMessage, handlers: PiAgentStreamHandlers) {
232
+ const sessionKey = getSessionKey(message);
233
+ handlers.onMeta?.({ sessionKey });
234
+
235
+ const session = await (async () => {
236
+ try {
237
+ return await getOrCreateSession(sessionKey);
238
+ } catch (err) {
239
+ if (String(err).includes("stale session")) {
240
+ // Race with reset; retry once against the latest transcript.
241
+ return await getOrCreateSession(sessionKey);
242
+ }
243
+ throw err;
244
+ }
245
+ })();
246
+ if (session.isStreaming) {
247
+ // Dashboard UI expects a single in-flight stream per session.
248
+ throw new Error(`Session is busy: ${sessionKey}`);
249
+ }
250
+
251
+ let lastText = "";
252
+ let sawTextDelta = false;
253
+
254
+ const unsubscribe = session.subscribe((event) => {
255
+ handlers.onEvent?.(event);
256
+
257
+ // Primary streaming signal in pi-coding-agent is `assistantMessageEvent.text_delta`.
258
+ // Keep a fallback for older/alternate event shapes.
259
+ if (!event || typeof event !== "object") return;
260
+ const type = (event as { type?: unknown }).type;
261
+
262
+ if (type === "message_update") {
263
+ const assistantMessageEvent = (event as { assistantMessageEvent?: unknown }).assistantMessageEvent;
264
+ if (assistantMessageEvent && typeof assistantMessageEvent === "object") {
265
+ const evtType = (assistantMessageEvent as { type?: unknown }).type;
266
+ const delta = (assistantMessageEvent as { delta?: unknown }).delta;
267
+ if (evtType === "text_delta" && typeof delta === "string" && delta) {
268
+ sawTextDelta = true;
269
+ handlers.onDelta?.(delta);
270
+ return;
271
+ }
272
+ }
273
+ }
274
+
275
+ // Fallback: stream by diffing assistant full text snapshots.
276
+ // IMPORTANT: once we have real text_delta events, do not mix snapshot-based
277
+ // fallback deltas into the same output stream, otherwise we may duplicate
278
+ // or corrupt the accumulated text.
279
+ if (sawTextDelta) return;
280
+ if (type !== "message_start" && type !== "message_update" && type !== "message_end") return;
281
+
282
+ const msg = (event as { message?: unknown }).message;
283
+ if (!msg || typeof msg !== "object") return;
284
+ if ((msg as { role?: unknown }).role !== "assistant") return;
285
+
286
+ const fullText = extractAssistantTextFromMessage(msg);
287
+ if (fullText.length < lastText.length || !fullText.startsWith(lastText)) {
288
+ lastText = fullText;
289
+ if (fullText) {
290
+ console.log("[pi-bot][agent-stream] fallback_fulltext", fullText);
291
+ handlers.onDelta?.(fullText);
292
+ }
293
+ return;
294
+ }
295
+ const delta = fullText.slice(lastText.length);
296
+ lastText = fullText;
297
+ if (delta) {
298
+ handlers.onDelta?.(delta);
299
+ }
300
+ });
301
+
302
+ try {
303
+ await session.prompt(message.text);
304
+ const finalText = extractAssistantText(session);
305
+ return { sessionKey, finalText };
306
+ } catch (err) {
307
+ handlers.onError?.(String(err));
308
+ throw err;
309
+ } finally {
310
+ unsubscribe();
311
+ }
312
+ },
313
+ getSessionIfExists(sessionKey: string): AgentSession | undefined {
314
+ return sessions.get(sessionKey);
315
+ },
316
+ listSessionKeys(): string[] {
317
+ const all = new Set<string>([...sessionStore.listSessionKeys(), ...sessions.keys()]);
318
+ return Array.from(all);
319
+ },
320
+ async ensureSessionLoaded(sessionKey: string): Promise<AgentSession | undefined> {
321
+ try {
322
+ return await getOrCreateSession(sessionKey);
323
+ } catch (err) {
324
+ if (String(err).includes("stale session")) {
325
+ return await getOrCreateSession(sessionKey);
326
+ }
327
+ throw err;
328
+ }
329
+ },
330
+ async abortSession(sessionKey: string): Promise<void> {
331
+ const session = sessions.get(sessionKey);
332
+ if (!session) return;
333
+ await session.abort();
334
+ },
335
+ async resetSession(sessionKey: string): Promise<void> {
336
+ const nextGen = (sessionGenerations.get(sessionKey) ?? 0) + 1;
337
+ sessionGenerations.set(sessionKey, nextGen);
338
+ sessionStore.resetSession(sessionKey);
339
+ const session = sessions.get(sessionKey);
340
+ if (!session) return;
341
+ try {
342
+ if (session.isStreaming) {
343
+ await session.abort();
344
+ }
345
+ } catch {
346
+ // ignore
347
+ }
348
+ try {
349
+ (session as unknown as { dispose?: () => void }).dispose?.();
350
+ } catch {
351
+ // ignore
352
+ }
353
+ sessions.delete(sessionKey);
354
+ },
355
+ async dispose(): Promise<void> {
356
+ sessions.clear();
357
+ }
358
+ } satisfies PiAgentRuntime;
359
+ }
360
+
361
+ export async function createAgentRuntime(config: AgentRuntimeConfig): Promise<PiAgentRuntime> {
362
+ if (config.mode === "mock") {
363
+ return createMockAgentRuntime();
364
+ }
365
+
366
+ return createPiAgentRuntime(config);
367
+ }