diff --git a/.gitignore b/.gitignore index 9dc547c9c60..46b23f57ce2 100644 --- a/.gitignore +++ b/.gitignore @@ -69,5 +69,9 @@ IDENTITY.md USER.md .tgz +# Next.js +**/.next/ +next-env.d.ts + # local tooling .serena/ diff --git a/apps/web/app/api/chat/route.ts b/apps/web/app/api/chat/route.ts new file mode 100644 index 00000000000..23b82ca26cd --- /dev/null +++ b/apps/web/app/api/chat/route.ts @@ -0,0 +1,149 @@ +import { createUIMessageStream, type UIMessage } from "ai"; +import { spawn } from "node:child_process"; +import { resolve } from "node:path"; +import { createInterface } from "node:readline"; + +// Allow streaming responses up to 10 minutes +export const maxDuration = 600; + +/** Resolve the repo root (two levels up from apps/web/) */ +function repoRoot(): string { + return resolve(process.cwd(), "..", ".."); +} + +type NdjsonEvent = { + event: string; + runId?: string; + stream?: string; + data?: Record; + seq?: number; + ts?: number; + sessionKey?: string; + status?: string; + result?: { + payloads?: Array<{ text?: string; mediaUrl?: string | null }>; + meta?: Record; + }; +}; + +export async function POST(req: Request) { + const { messages }: { messages: UIMessage[] } = await req.json(); + + // Extract the latest user message text + const lastUserMessage = messages.filter((m) => m.role === "user").pop(); + const userText = + lastUserMessage?.parts + ?.filter((p): p is { type: "text"; text: string } => p.type === "text") + .map((p) => p.text) + .join("\n") ?? ""; + + if (!userText.trim()) { + return new Response("No message provided", { status: 400 }); + } + + const root = repoRoot(); + const scriptPath = resolve(root, "scripts", "run-node.mjs"); + + const stream = createUIMessageStream({ + async execute({ writer }) { + const textPartId = `text-${Date.now()}`; + let started = false; + + await new Promise((resolvePromise, rejectPromise) => { + const child = spawn( + "node", + [scriptPath, "agent", "--agent", "main", "--message", userText, "--stream-json"], + { + cwd: root, + env: { ...process.env }, + stdio: ["ignore", "pipe", "pipe"], + }, + ); + + const rl = createInterface({ input: child.stdout }); + + rl.on("line", (line: string) => { + if (!line.trim()) return; + + let event: NdjsonEvent; + try { + event = JSON.parse(line) as NdjsonEvent; + } catch { + return; // skip non-JSON lines (e.g. banner) + } + + // Handle assistant text deltas + if (event.event === "agent" && event.stream === "assistant") { + const delta = + typeof event.data?.delta === "string" ? event.data.delta : undefined; + if (delta) { + if (!started) { + writer.write({ type: "text-start", id: textPartId }); + started = true; + } + writer.write({ type: "text-delta", id: textPartId, delta }); + } + } + + // Handle lifecycle end + if ( + event.event === "agent" && + event.stream === "lifecycle" && + event.data?.phase === "end" + ) { + if (started) { + writer.write({ type: "text-end", id: textPartId }); + } + } + }); + + child.on("close", (code) => { + // If we never started text, emit an empty response + if (!started) { + writer.write({ type: "text-start", id: textPartId }); + writer.write({ + type: "text-delta", + id: textPartId, + delta: "(No response from agent)", + }); + writer.write({ type: "text-end", id: textPartId }); + } + if (code !== 0 && code !== null) { + // Non-zero exit but we already streamed what we could + } + resolvePromise(); + }); + + child.on("error", (err) => { + if (!started) { + writer.write({ type: "text-start", id: textPartId }); + writer.write({ + type: "text-delta", + id: textPartId, + delta: `Error starting agent: ${err.message}`, + }); + writer.write({ type: "text-end", id: textPartId }); + } + resolvePromise(); + }); + + // Log stderr for debugging + child.stderr?.on("data", (chunk: Buffer) => { + console.error("[openclaw stderr]", chunk.toString()); + }); + }); + }, + onError: (error) => { + const message = error instanceof Error ? error.message : String(error); + return `Agent error: ${message}`; + }, + }); + + return new Response(stream, { + headers: { + "Content-Type": "text/event-stream; charset=utf-8", + "Cache-Control": "no-cache", + Connection: "keep-alive", + }, + }); +} diff --git a/apps/web/app/api/memories/route.ts b/apps/web/app/api/memories/route.ts new file mode 100644 index 00000000000..8d1cc30919a --- /dev/null +++ b/apps/web/app/api/memories/route.ts @@ -0,0 +1,63 @@ +import { readFileSync, readdirSync, existsSync } from "node:fs"; +import { join } from "node:path"; +import { homedir } from "node:os"; + +export const dynamic = "force-dynamic"; + +type MemoryFile = { + name: string; + path: string; + sizeBytes: number; +}; + +export async function GET() { + const workspaceDir = join(homedir(), ".openclaw", "workspace"); + let mainMemory: string | null = null; + const dailyLogs: MemoryFile[] = []; + + // Read main MEMORY.md + for (const filename of ["MEMORY.md", "memory.md"]) { + const memPath = join(workspaceDir, filename); + if (existsSync(memPath)) { + try { + mainMemory = readFileSync(memPath, "utf-8"); + } catch { + // skip unreadable + } + break; + } + } + + // Scan daily log files + const memoryDir = join(workspaceDir, "memory"); + if (existsSync(memoryDir)) { + try { + const entries = readdirSync(memoryDir, { withFileTypes: true }); + for (const entry of entries) { + if (!entry.isFile() || !entry.name.endsWith(".md")) continue; + const filePath = join(memoryDir, entry.name); + try { + const content = readFileSync(filePath, "utf-8"); + dailyLogs.push({ + name: entry.name, + path: filePath, + sizeBytes: Buffer.byteLength(content, "utf-8"), + }); + } catch { + // skip + } + } + } catch { + // dir unreadable + } + } + + // Sort daily logs by name (date-based filenames sort chronologically) + dailyLogs.sort((a, b) => b.name.localeCompare(a.name)); + + return Response.json({ + mainMemory, + dailyLogs, + workspaceDir, + }); +} diff --git a/apps/web/app/api/sessions/route.ts b/apps/web/app/api/sessions/route.ts new file mode 100644 index 00000000000..418de60d145 --- /dev/null +++ b/apps/web/app/api/sessions/route.ts @@ -0,0 +1,96 @@ +import { readFileSync, readdirSync, existsSync } from "node:fs"; +import { join } from "node:path"; +import { homedir } from "node:os"; + +export const dynamic = "force-dynamic"; + +type SessionEntry = { + sessionId: string; + updatedAt: number; + label?: string; + displayName?: string; + channel?: string; + model?: string; + modelProvider?: string; + thinkingLevel?: string; + inputTokens?: number; + outputTokens?: number; + totalTokens?: number; + contextTokens?: number; + compactionCount?: number; +}; + +type SessionRow = { + key: string; + sessionId: string; + updatedAt: number; + label?: string; + displayName?: string; + channel?: string; + model?: string; + modelProvider?: string; + thinkingLevel?: string; + inputTokens?: number; + outputTokens?: number; + totalTokens?: number; + contextTokens?: number; +}; + +function resolveOpenClawDir(): string { + return join(homedir(), ".openclaw"); +} + +export async function GET() { + const openclawDir = resolveOpenClawDir(); + const agentsDir = join(openclawDir, "agents"); + + if (!existsSync(agentsDir)) { + return Response.json({ agents: [], sessions: [] }); + } + + const allSessions: SessionRow[] = []; + const agentIds: string[] = []; + + try { + const entries = readdirSync(agentsDir, { withFileTypes: true }); + for (const entry of entries) { + if (!entry.isDirectory()) continue; + agentIds.push(entry.name); + + const storePath = join(agentsDir, entry.name, "sessions", "sessions.json"); + if (!existsSync(storePath)) continue; + + try { + const raw = readFileSync(storePath, "utf-8"); + const store = JSON.parse(raw) as Record; + for (const [key, session] of Object.entries(store)) { + if (!session || typeof session !== "object") continue; + allSessions.push({ + key, + sessionId: session.sessionId, + updatedAt: session.updatedAt, + label: session.label, + displayName: session.displayName, + channel: session.channel, + model: session.model, + modelProvider: session.modelProvider, + thinkingLevel: session.thinkingLevel, + inputTokens: session.inputTokens, + outputTokens: session.outputTokens, + totalTokens: session.totalTokens, + contextTokens: session.contextTokens, + }); + } + } catch { + // skip unreadable store files + } + } + } catch { + // agents dir unreadable + } + + // Sort by updatedAt descending + allSessions.sort((a, b) => (b.updatedAt ?? 0) - (a.updatedAt ?? 0)); + + return Response.json({ agents: agentIds, sessions: allSessions }); +} diff --git a/apps/web/app/api/skills/route.ts b/apps/web/app/api/skills/route.ts new file mode 100644 index 00000000000..822015cc519 --- /dev/null +++ b/apps/web/app/api/skills/route.ts @@ -0,0 +1,85 @@ +import { readFileSync, readdirSync, existsSync } from "node:fs"; +import { join } from "node:path"; +import { homedir } from "node:os"; + +export const dynamic = "force-dynamic"; + +type SkillEntry = { + name: string; + description: string; + emoji?: string; + source: string; + filePath: string; +}; + +/** Parse YAML frontmatter from a SKILL.md file (lightweight, no deps). */ +function parseSkillFrontmatter(content: string): { + name?: string; + description?: string; + emoji?: string; +} { + const match = content.match(/^---\s*\n([\s\S]*?)\n---/); + if (!match) return {}; + + const yaml = match[1]; + const result: Record = {}; + for (const line of yaml.split("\n")) { + const kv = line.match(/^(\w+)\s*:\s*(.+)/); + if (kv) { + result[kv[1]] = kv[2].replace(/^["']|["']$/g, "").trim(); + } + } + return { + name: result.name, + description: result.description, + emoji: result.emoji, + }; +} + +function scanSkillDir(dir: string, source: string): SkillEntry[] { + const skills: SkillEntry[] = []; + if (!existsSync(dir)) return skills; + + try { + const entries = readdirSync(dir, { withFileTypes: true }); + for (const entry of entries) { + if (!entry.isDirectory()) continue; + const skillMdPath = join(dir, entry.name, "SKILL.md"); + if (!existsSync(skillMdPath)) continue; + + try { + const content = readFileSync(skillMdPath, "utf-8"); + const meta = parseSkillFrontmatter(content); + skills.push({ + name: meta.name ?? entry.name, + description: meta.description ?? "", + emoji: meta.emoji, + source, + filePath: skillMdPath, + }); + } catch { + // skip unreadable skill files + } + } + } catch { + // dir unreadable + } + + return skills; +} + +export async function GET() { + const home = homedir(); + const openclawDir = join(home, ".openclaw"); + + const managedSkills = scanSkillDir(join(openclawDir, "skills"), "managed"); + const workspaceSkills = scanSkillDir( + join(openclawDir, "workspace", "skills"), + "workspace", + ); + + const allSkills = [...workspaceSkills, ...managedSkills]; + allSkills.sort((a, b) => a.name.localeCompare(b.name)); + + return Response.json({ skills: allSkills }); +} diff --git a/apps/web/app/components/chat-message.tsx b/apps/web/app/components/chat-message.tsx new file mode 100644 index 00000000000..082b94ed967 --- /dev/null +++ b/apps/web/app/components/chat-message.tsx @@ -0,0 +1,56 @@ +"use client"; + +import type { UIMessage } from "ai"; + +export function ChatMessage({ message }: { message: UIMessage }) { + const isUser = message.role === "user"; + + return ( +
+ {!isUser && ( +
+ O +
+ )} + +
+ {message.parts.map((part, index) => { + if (part.type === "text") { + return ( +
+ {part.text} +
+ ); + } + if (part.type.startsWith("tool-")) { + const toolPart = part as { type: string; toolCallId: string; state?: string; title?: string }; + return ( +
+ Tool: {toolPart.title ?? toolPart.toolCallId} + {toolPart.state === "result" && ( + done + )} +
+ ); + } + return null; + })} +
+ + {isUser && ( +
+ U +
+ )} +
+ ); +} diff --git a/apps/web/app/components/sidebar.tsx b/apps/web/app/components/sidebar.tsx new file mode 100644 index 00000000000..0fd89b6c32c --- /dev/null +++ b/apps/web/app/components/sidebar.tsx @@ -0,0 +1,322 @@ +"use client"; + +import { useEffect, useState } from "react"; + +// --- Types --- + +type SessionRow = { + key: string; + sessionId: string; + updatedAt: number; + label?: string; + displayName?: string; + channel?: string; + model?: string; + modelProvider?: string; + thinkingLevel?: string; + totalTokens?: number; +}; + +type SkillEntry = { + name: string; + description: string; + emoji?: string; + source: string; +}; + +type MemoryFile = { + name: string; + sizeBytes: number; +}; + +type SidebarSection = "sessions" | "skills" | "memories"; + +// --- Helpers --- + +function timeAgo(ts: number): string { + const diff = Date.now() - ts; + const seconds = Math.floor(diff / 1000); + if (seconds < 60) return `${seconds}s ago`; + const minutes = Math.floor(seconds / 60); + if (minutes < 60) return `${minutes}m ago`; + const hours = Math.floor(minutes / 60); + if (hours < 24) return `${hours}h ago`; + const days = Math.floor(hours / 24); + return `${days}d ago`; +} + +function formatTokens(n?: number): string { + if (n == null) return ""; + if (n >= 1_000_000) return `${(n / 1_000_000).toFixed(1)}M`; + if (n >= 1_000) return `${(n / 1_000).toFixed(1)}k`; + return String(n); +} + +// --- Section Components --- + +function SessionsSection({ sessions }: { sessions: SessionRow[] }) { + if (sessions.length === 0) { + return

No sessions found.

; + } + + return ( +
+ {sessions.map((s) => ( +
+
+ + {s.label ?? s.displayName ?? s.key} + + {s.updatedAt && ( + + {timeAgo(s.updatedAt)} + + )} +
+
+ {s.channel && ( + {s.channel} + )} + {s.model && ( + + {s.model} + + )} + {s.totalTokens != null && s.totalTokens > 0 && ( + + {formatTokens(s.totalTokens)} tok + + )} +
+
+ ))} +
+ ); +} + +function SkillsSection({ skills }: { skills: SkillEntry[] }) { + if (skills.length === 0) { + return

No skills found.

; + } + + return ( +
+ {skills.map((skill) => ( +
+
+ {skill.emoji && {skill.emoji}} + {skill.name} + {skill.source} +
+ {skill.description && ( +

+ {skill.description} +

+ )} +
+ ))} +
+ ); +} + +function MemoriesSection({ + mainMemory, + dailyLogs, +}: { + mainMemory: string | null; + dailyLogs: MemoryFile[]; +}) { + const [expanded, setExpanded] = useState(false); + + return ( +
+ {mainMemory ? ( +
+ + {expanded && ( +
+              {mainMemory}
+            
+ )} +
+ ) : ( +

No MEMORY.md found.

+ )} + + {dailyLogs.length > 0 && ( +
+

+ Daily logs ({dailyLogs.length}) +

+
+ {dailyLogs.slice(0, 10).map((log) => ( +
+ {log.name} + {(log.sizeBytes / 1024).toFixed(1)}kb +
+ ))} + {dailyLogs.length > 10 && ( +

+ ...and {dailyLogs.length - 10} more +

+ )} +
+
+ )} +
+ ); +} + +// --- Collapsible Header --- + +function SectionHeader({ + title, + count, + isOpen, + onToggle, +}: { + title: string; + count?: number; + isOpen: boolean; + onToggle: () => void; +}) { + return ( + + ); +} + +// --- Main Sidebar --- + +export function Sidebar() { + const [openSections, setOpenSections] = useState>( + new Set(["sessions"]), + ); + const [sessions, setSessions] = useState([]); + const [skills, setSkills] = useState([]); + const [mainMemory, setMainMemory] = useState(null); + const [dailyLogs, setDailyLogs] = useState([]); + const [loading, setLoading] = useState(true); + + const toggleSection = (section: SidebarSection) => { + setOpenSections((prev) => { + const next = new Set(prev); + if (next.has(section)) next.delete(section); + else next.add(section); + return next; + }); + }; + + useEffect(() => { + async function load() { + setLoading(true); + try { + const [sessionsRes, skillsRes, memoriesRes] = await Promise.all([ + fetch("/api/sessions").then((r) => r.json()), + fetch("/api/skills").then((r) => r.json()), + fetch("/api/memories").then((r) => r.json()), + ]); + setSessions(sessionsRes.sessions ?? []); + setSkills(skillsRes.skills ?? []); + setMainMemory(memoriesRes.mainMemory ?? null); + setDailyLogs(memoriesRes.dailyLogs ?? []); + } catch (err) { + console.error("Failed to load sidebar data:", err); + } finally { + setLoading(false); + } + } + load(); + }, []); + + return ( + + ); +} diff --git a/apps/web/app/globals.css b/apps/web/app/globals.css new file mode 100644 index 00000000000..4c678950480 --- /dev/null +++ b/apps/web/app/globals.css @@ -0,0 +1,39 @@ +@import "tailwindcss"; + +:root { + --color-bg: #0a0a0a; + --color-surface: #141414; + --color-surface-hover: #1a1a1a; + --color-border: #262626; + --color-text: #ededed; + --color-text-muted: #888; + --color-accent: #e85d3a; + --color-accent-hover: #f06a47; +} + +body { + background: var(--color-bg); + color: var(--color-text); + font-family: + "Inter", + -apple-system, + BlinkMacSystemFont, + "Segoe UI", + Roboto, + sans-serif; +} + +/* Scrollbar styling */ +::-webkit-scrollbar { + width: 6px; +} +::-webkit-scrollbar-track { + background: transparent; +} +::-webkit-scrollbar-thumb { + background: var(--color-border); + border-radius: 3px; +} +::-webkit-scrollbar-thumb:hover { + background: var(--color-text-muted); +} diff --git a/apps/web/app/layout.tsx b/apps/web/app/layout.tsx new file mode 100644 index 00000000000..58528f4df46 --- /dev/null +++ b/apps/web/app/layout.tsx @@ -0,0 +1,19 @@ +import type { Metadata } from "next"; +import "./globals.css"; + +export const metadata: Metadata = { + title: "OpenClaw Chat", + description: "OpenClaw agent chat interface", +}; + +export default function RootLayout({ + children, +}: { + children: React.ReactNode; +}) { + return ( + + {children} + + ); +} diff --git a/apps/web/app/page.tsx b/apps/web/app/page.tsx new file mode 100644 index 00000000000..75cb564a9ff --- /dev/null +++ b/apps/web/app/page.tsx @@ -0,0 +1,118 @@ +"use client"; + +import { useChat } from "@ai-sdk/react"; +import { DefaultChatTransport } from "ai"; +import { useEffect, useRef, useState } from "react"; +import { ChatMessage } from "./components/chat-message"; +import { Sidebar } from "./components/sidebar"; + +const transport = new DefaultChatTransport({ api: "/api/chat" }); + +export default function Home() { + const { messages, sendMessage, status, stop, error } = useChat({ transport }); + const [input, setInput] = useState(""); + const messagesEndRef = useRef(null); + + // Auto-scroll to bottom on new messages + useEffect(() => { + messagesEndRef.current?.scrollIntoView({ behavior: "smooth" }); + }, [messages]); + + const isStreaming = status === "streaming" || status === "submitted"; + + const handleSubmit = (e: React.FormEvent) => { + e.preventDefault(); + if (!input.trim() || isStreaming) return; + sendMessage({ text: input }); + setInput(""); + }; + + return ( +
+ + + {/* Main chat area */} +
+ {/* Chat header */} +
+
+

Agent Chat

+

+ {status === "ready" + ? "Ready" + : status === "submitted" + ? "Thinking..." + : status === "streaming" + ? "Streaming..." + : status === "error" + ? "Error" + : status} +

+
+ {isStreaming && ( + + )} +
+ + {/* Messages */} +
+ {messages.length === 0 ? ( +
+
+

🦞

+

OpenClaw Chat

+

+ Send a message to start a conversation with your agent. +

+
+
+ ) : ( +
+ {messages.map((message) => ( + + ))} +
+
+ )} +
+ + {/* Error display */} + {error && ( +
+

Error: {error.message}

+
+ )} + + {/* Input */} +
+
+ setInput(e.target.value)} + placeholder="Message OpenClaw..." + disabled={isStreaming} + className="flex-1 px-4 py-3 bg-[var(--color-bg)] border border-[var(--color-border)] rounded-xl text-[var(--color-text)] placeholder:text-[var(--color-text-muted)] focus:outline-none focus:ring-2 focus:ring-[var(--color-accent)] focus:border-transparent disabled:opacity-50 text-sm" + /> + +
+
+
+
+ ); +} diff --git a/apps/web/next.config.ts b/apps/web/next.config.ts new file mode 100644 index 00000000000..e0d5e4bb9ab --- /dev/null +++ b/apps/web/next.config.ts @@ -0,0 +1,8 @@ +import type { NextConfig } from "next"; + +const nextConfig: NextConfig = { + // Allow long-running API routes for agent streaming + serverExternalPackages: [], +}; + +export default nextConfig; diff --git a/apps/web/package.json b/apps/web/package.json new file mode 100644 index 00000000000..17bef9edb9a --- /dev/null +++ b/apps/web/package.json @@ -0,0 +1,25 @@ +{ + "name": "openclaw-web", + "version": "0.1.0", + "private": true, + "scripts": { + "dev": "next dev --turbopack --port 3100", + "build": "next build", + "start": "next start --port 3100" + }, + "dependencies": { + "@ai-sdk/react": "^3.0.75", + "ai": "^6.0.73", + "next": "^15.3.3", + "react": "^19.1.0", + "react-dom": "^19.1.0" + }, + "devDependencies": { + "@tailwindcss/postcss": "^4.1.8", + "@types/node": "^22.15.21", + "@types/react": "^19.1.4", + "@types/react-dom": "^19.1.5", + "tailwindcss": "^4.1.8", + "typescript": "^5.8.3" + } +} diff --git a/apps/web/postcss.config.mjs b/apps/web/postcss.config.mjs new file mode 100644 index 00000000000..61e36849cf7 --- /dev/null +++ b/apps/web/postcss.config.mjs @@ -0,0 +1,7 @@ +const config = { + plugins: { + "@tailwindcss/postcss": {}, + }, +}; + +export default config; diff --git a/apps/web/tsconfig.json b/apps/web/tsconfig.json new file mode 100644 index 00000000000..5e716a7be18 --- /dev/null +++ b/apps/web/tsconfig.json @@ -0,0 +1,23 @@ +{ + "compilerOptions": { + "target": "ES2022", + "lib": ["dom", "dom.iterable", "ES2022"], + "allowJs": true, + "skipLibCheck": true, + "strict": true, + "noEmit": true, + "esModuleInterop": true, + "module": "esnext", + "moduleResolution": "bundler", + "resolveJsonModule": true, + "isolatedModules": true, + "jsx": "preserve", + "incremental": true, + "plugins": [{ "name": "next" }], + "paths": { + "@/*": ["./*"] + } + }, + "include": ["next-env.d.ts", "**/*.ts", "**/*.tsx", ".next/types/**/*.ts"], + "exclude": ["node_modules"] +} diff --git a/package.json b/package.json index 40e56c05cee..df1418e7dcd 100644 --- a/package.json +++ b/package.json @@ -147,7 +147,11 @@ "tui:dev": "OPENCLAW_PROFILE=dev CLAWDBOT_PROFILE=dev node scripts/run-node.mjs --dev tui", "ui:build": "node scripts/ui.js build", "ui:dev": "node scripts/ui.js dev", - "ui:install": "node scripts/ui.js install" + "ui:install": "node scripts/ui.js install", + "web:dev": "pnpm --dir apps/web dev", + "web:build": "pnpm --dir apps/web build", + "web:install": "pnpm --dir apps/web install", + "tail": "tail -f ~/.openclaw/agents/*/sessions/*.jsonl" }, "dependencies": { "@agentclientprotocol/sdk": "0.13.1", diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 90222962b49..fd428f2e948 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -268,6 +268,43 @@ importers: specifier: ^4.0.18 version: 4.0.18(@types/node@25.1.0)(@vitest/browser-playwright@4.0.18)(jiti@2.6.1)(tsx@4.21.0)(yaml@2.8.2) + apps/web: + dependencies: + '@ai-sdk/react': + specifier: ^3.0.75 + version: 3.0.75(react@19.1.0)(zod@4.3.6) + ai: + specifier: ^6.0.73 + version: 6.0.73(zod@4.3.6) + next: + specifier: ^15.3.3 + version: 15.3.3(react-dom@19.1.0)(react@19.1.0) + react: + specifier: ^19.1.0 + version: 19.1.0 + react-dom: + specifier: ^19.1.0 + version: 19.1.0(react@19.1.0) + devDependencies: + '@tailwindcss/postcss': + specifier: ^4.1.8 + version: 4.1.8 + '@types/node': + specifier: ^22.15.21 + version: 22.15.21 + '@types/react': + specifier: ^19.1.4 + version: 19.1.4 + '@types/react-dom': + specifier: ^19.1.5 + version: 19.1.5(@types/react@19.1.4) + tailwindcss: + specifier: ^4.1.8 + version: 4.1.8 + typescript: + specifier: ^5.8.3 + version: 5.9.3 + extensions/bluebubbles: devDependencies: openclaw: @@ -675,6 +712,18 @@ packages: zod: 4.3.6 dev: false + /@ai-sdk/gateway@3.0.36(zod@4.3.6): + resolution: {integrity: sha512-2r1Q6azvqMYxQ1hqfWZmWg4+8MajoldD/ty65XdhCaCoBfvDu7trcvxXDfTSU+3/wZ1JIDky46SWYFOHnTbsBw==} + engines: {node: '>=18'} + peerDependencies: + zod: ^3.25.76 || ^4.1.8 + dependencies: + '@ai-sdk/provider': 3.0.7 + '@ai-sdk/provider-utils': 4.0.13(zod@4.3.6) + '@vercel/oidc': 3.1.0 + zod: 4.3.6 + dev: false + /@ai-sdk/google@2.0.52(zod@4.3.6): resolution: {integrity: sha512-2XUnGi3f7TV4ujoAhA+Fg3idUoG/+Y2xjCRg70a1/m0DH1KSQqYaCboJ1C19y6ZHGdf5KNT20eJdswP6TvrY2g==} engines: {node: '>=18'} @@ -754,6 +803,18 @@ packages: zod: 4.3.6 dev: false + /@ai-sdk/provider-utils@4.0.13(zod@4.3.6): + resolution: {integrity: sha512-HHG72BN4d+OWTcq2NwTxOm/2qvk1duYsnhCDtsbYwn/h/4zeqURu1S0+Cn0nY2Ysq9a9HGKvrYuMn9bgFhR2Og==} + engines: {node: '>=18'} + peerDependencies: + zod: ^3.25.76 || ^4.1.8 + dependencies: + '@ai-sdk/provider': 3.0.7 + '@standard-schema/spec': 1.1.0 + eventsource-parser: 3.0.6 + zod: 4.3.6 + dev: false + /@ai-sdk/provider@2.0.1: resolution: {integrity: sha512-KCUwswvsC5VsW2PWFqF8eJgSCu5Ysj7m1TxiHTVA6g7k360bk0RNQENT8KTMAYEs+8fWPD3Uu4dEmzGHc+jGng==} engines: {node: '>=18'} @@ -768,6 +829,28 @@ packages: json-schema: 0.4.0 dev: false + /@ai-sdk/provider@3.0.7: + resolution: {integrity: sha512-VkPLrutM6VdA924/mG8OS+5frbVTcu6e046D2bgDo00tehBANR1QBJ/mPcZ9tXMFOsVcm6SQArOregxePzTFPw==} + engines: {node: '>=18'} + dependencies: + json-schema: 0.4.0 + dev: false + + /@ai-sdk/react@3.0.75(react@19.1.0)(zod@4.3.6): + resolution: {integrity: sha512-wh9YH0jTZ8hXEu6IcMuLD81FEvL/ncaggLI+uNK8EUPgKUr9Peyu2yzjUd025PvAZw3B/gUz8WCI65GnIlEfIQ==} + engines: {node: '>=18'} + peerDependencies: + react: ^18 || ~19.0.1 || ~19.1.2 || ^19.2.1 + dependencies: + '@ai-sdk/provider-utils': 4.0.13(zod@4.3.6) + ai: 6.0.73(zod@4.3.6) + react: 19.1.0 + swr: 2.4.0(react@19.1.0) + throttleit: 2.1.0 + transitivePeerDependencies: + - zod + dev: false + /@ai-sdk/xai@2.0.56(zod@4.3.6): resolution: {integrity: sha512-FGlqwWc3tAYqDHE8r8hQGQLcMiPUwgz90oU2QygUH930OWtCLapFkSu114DgVaIN/qoM1DUX+inv0Ee74Fgp5g==} engines: {node: '>=18'} @@ -780,6 +863,19 @@ packages: zod: 4.3.6 dev: false + /@alloc/quick-lru@5.2.0: + resolution: {integrity: sha512-UrcABB+4bUrFABwbluTIBErXwvbsU/V7TZWfmbgJfbkwiBuziS9gxdODUyuiecfdGQ85jglMW6juS3+z5TsKLw==} + engines: {node: '>=10'} + dev: true + + /@ampproject/remapping@2.3.0: + resolution: {integrity: sha512-30iZtAPgz+LTIYoeivqYo853f02jBYSd5uGnGpkFV0M3xOt9aN73erkgYAmZU43x4VfqcnLxW9Kpg3R5LC4YYw==} + engines: {node: '>=6.0.0'} + dependencies: + '@jridgewell/gen-mapping': 0.3.13 + '@jridgewell/trace-mapping': 0.3.31 + dev: true + /@anthropic-ai/sdk@0.71.2(zod@4.3.6): resolution: {integrity: sha512-TGNDEUuEstk/DKu0/TflXAEt+p+p/WhTlFzEnoosvbaDU2LTjm42igSdlL0VijrKpWejtOKxX0b8A7uc+XiSAQ==} hasBin: true @@ -2191,7 +2287,13 @@ packages: engines: {node: '>=18.0.0'} dependencies: minipass: 7.1.2 - dev: false + + /@jridgewell/gen-mapping@0.3.13: + resolution: {integrity: sha512-2kkt/7niJ6MgEPxF0bYdQ6etZaA+fQvDcLKckhy1yIQOzaoKjBBjSj63/aLVjYE3qhRt5dvM+uUyfCg6UKCBbA==} + dependencies: + '@jridgewell/sourcemap-codec': 1.5.5 + '@jridgewell/trace-mapping': 0.3.31 + dev: true /@jridgewell/resolve-uri@3.1.2: resolution: {integrity: sha512-bRISgCIjP20/tbWSPWMEi54QVPRZExkuD9lJL+UIxUKtwVJA8wW1Trb1jMs1RFXo1CBTNZ/5hpC9QvmKWdopKw==} @@ -2800,6 +2902,82 @@ packages: dev: true optional: true + /@next/env@15.3.3: + resolution: {integrity: sha512-OdiMrzCl2Xi0VTjiQQUK0Xh7bJHnOuET2s+3V+Y40WJBAXrJeGA3f+I8MZJ/YQ3mVGi5XGR1L66oFlgqXhQ4Vw==} + dev: false + + /@next/swc-darwin-arm64@15.3.3: + resolution: {integrity: sha512-WRJERLuH+O3oYB4yZNVahSVFmtxRNjNF1I1c34tYMoJb0Pve+7/RaLAJJizyYiFhjYNGHRAE1Ri2Fd23zgDqhg==} + engines: {node: '>= 10'} + cpu: [arm64] + os: [darwin] + requiresBuild: true + dev: false + optional: true + + /@next/swc-darwin-x64@15.3.3: + resolution: {integrity: sha512-XHdzH/yBc55lu78k/XwtuFR/ZXUTcflpRXcsu0nKmF45U96jt1tsOZhVrn5YH+paw66zOANpOnFQ9i6/j+UYvw==} + engines: {node: '>= 10'} + cpu: [x64] + os: [darwin] + requiresBuild: true + dev: false + optional: true + + /@next/swc-linux-arm64-gnu@15.3.3: + resolution: {integrity: sha512-VZ3sYL2LXB8znNGcjhocikEkag/8xiLgnvQts41tq6i+wql63SMS1Q6N8RVXHw5pEUjiof+II3HkDd7GFcgkzw==} + engines: {node: '>= 10'} + cpu: [arm64] + os: [linux] + requiresBuild: true + dev: false + optional: true + + /@next/swc-linux-arm64-musl@15.3.3: + resolution: {integrity: sha512-h6Y1fLU4RWAp1HPNJWDYBQ+e3G7sLckyBXhmH9ajn8l/RSMnhbuPBV/fXmy3muMcVwoJdHL+UtzRzs0nXOf9SA==} + engines: {node: '>= 10'} + cpu: [arm64] + os: [linux] + requiresBuild: true + dev: false + optional: true + + /@next/swc-linux-x64-gnu@15.3.3: + resolution: {integrity: sha512-jJ8HRiF3N8Zw6hGlytCj5BiHyG/K+fnTKVDEKvUCyiQ/0r5tgwO7OgaRiOjjRoIx2vwLR+Rz8hQoPrnmFbJdfw==} + engines: {node: '>= 10'} + cpu: [x64] + os: [linux] + requiresBuild: true + dev: false + optional: true + + /@next/swc-linux-x64-musl@15.3.3: + resolution: {integrity: sha512-HrUcTr4N+RgiiGn3jjeT6Oo208UT/7BuTr7K0mdKRBtTbT4v9zJqCDKO97DUqqoBK1qyzP1RwvrWTvU6EPh/Cw==} + engines: {node: '>= 10'} + cpu: [x64] + os: [linux] + requiresBuild: true + dev: false + optional: true + + /@next/swc-win32-arm64-msvc@15.3.3: + resolution: {integrity: sha512-SxorONgi6K7ZUysMtRF3mIeHC5aA3IQLmKFQzU0OuhuUYwpOBc1ypaLJLP5Bf3M9k53KUUUj4vTPwzGvl/NwlQ==} + engines: {node: '>= 10'} + cpu: [arm64] + os: [win32] + requiresBuild: true + dev: false + optional: true + + /@next/swc-win32-x64-msvc@15.3.3: + resolution: {integrity: sha512-4QZG6F8enl9/S2+yIiOiju0iCTFd93d8VC1q9LZS4p/Xuk81W2QDjCFeoogmrWWkAD59z8ZxepBQap2dKS5ruw==} + engines: {node: '>= 10'} + cpu: [x64] + os: [win32] + requiresBuild: true + dev: false + optional: true + /@noble/ciphers@2.1.1: resolution: {integrity: sha512-bysYuiVfhxNJuldNXlFEitTVdNnYUc+XNJZd7Qm2a5j1vZHgY+fazadNFWFaMK/2vye0JVlxV3gHmC0WDfAOQw==} engines: {node: '>= 20.19.0'} @@ -4237,7 +4415,7 @@ packages: resolution: {integrity: sha512-Wz7QYfPAlG/DR+DfABddUZeNgoeY7d1J39OCR2jR+v7VBsB8ezulDK5szTnDDPDwLH5IWhLvXIHlCFZV7MSKgA==} engines: {node: '>= 18', npm: '>= 8.6.0'} dependencies: - '@types/node': 25.1.0 + '@types/node': 22.15.21 dev: false /@slack/oauth@3.0.4: @@ -4247,7 +4425,7 @@ packages: '@slack/logger': 4.0.0 '@slack/web-api': 7.13.0 '@types/jsonwebtoken': 9.0.10 - '@types/node': 25.1.0 + '@types/node': 22.15.21 jsonwebtoken: 9.0.3 transitivePeerDependencies: - debug @@ -4259,7 +4437,7 @@ packages: dependencies: '@slack/logger': 4.0.0 '@slack/web-api': 7.13.0 - '@types/node': 25.1.0 + '@types/node': 22.15.21 '@types/ws': 8.18.1 eventemitter3: 5.0.4 ws: 8.19.0 @@ -4280,7 +4458,7 @@ packages: dependencies: '@slack/logger': 4.0.0 '@slack/types': 2.19.0 - '@types/node': 25.1.0 + '@types/node': 22.15.21 '@types/retry': 0.12.0 axios: 1.13.4(debug@4.4.3) eventemitter3: 5.0.4 @@ -4745,12 +4923,180 @@ packages: /@standard-schema/spec@1.1.0: resolution: {integrity: sha512-l2aFy5jALhniG5HgqrD6jXLi/rUWrKvqN/qJx6yoJsgKhblVd+iqqU4RCXavm/jPityDo5TCvKMnpjKnOriy0w==} + /@swc/counter@0.1.3: + resolution: {integrity: sha512-e2BR4lsJkkRlKZ/qCHPw9ZaSxc0MVUd7gtbtaB7aMvHeJVYe8sOB8DBZkP2DtISHGSku9sCK6T6cnY0CtXrOCQ==} + dev: false + + /@swc/helpers@0.5.15: + resolution: {integrity: sha512-JQ5TuMi45Owi4/BIMAJBoSQoOJu12oOk/gADqlcUL9JEdHB8vyjUSsxqeNXnmXHjYKMi2WcYtezGEEhqUI/E2g==} + dependencies: + tslib: 2.8.1 + dev: false + /@swc/helpers@0.5.18: resolution: {integrity: sha512-TXTnIcNJQEKwThMMqBXsZ4VGAza6bvN4pa41Rkqoio6QBKMvo+5lexeTMScGCIxtzgQJzElcvIltani+adC5PQ==} dependencies: tslib: 2.8.1 dev: false + /@tailwindcss/node@4.1.8: + resolution: {integrity: sha512-OWwBsbC9BFAJelmnNcrKuf+bka2ZxCE2A4Ft53Tkg4uoiE67r/PMEYwCsourC26E+kmxfwE0hVzMdxqeW+xu7Q==} + dependencies: + '@ampproject/remapping': 2.3.0 + enhanced-resolve: 5.19.0 + jiti: 2.6.1 + lightningcss: 1.30.1 + magic-string: 0.30.21 + source-map-js: 1.2.1 + tailwindcss: 4.1.8 + dev: true + + /@tailwindcss/oxide-android-arm64@4.1.8: + resolution: {integrity: sha512-Fbz7qni62uKYceWYvUjRqhGfZKwhZDQhlrJKGtnZfuNtHFqa8wmr+Wn74CTWERiW2hn3mN5gTpOoxWKk0jRxjg==} + engines: {node: '>= 10'} + cpu: [arm64] + os: [android] + requiresBuild: true + dev: true + optional: true + + /@tailwindcss/oxide-darwin-arm64@4.1.8: + resolution: {integrity: sha512-RdRvedGsT0vwVVDztvyXhKpsU2ark/BjgG0huo4+2BluxdXo8NDgzl77qh0T1nUxmM11eXwR8jA39ibvSTbi7A==} + engines: {node: '>= 10'} + cpu: [arm64] + os: [darwin] + requiresBuild: true + dev: true + optional: true + + /@tailwindcss/oxide-darwin-x64@4.1.8: + resolution: {integrity: sha512-t6PgxjEMLp5Ovf7uMb2OFmb3kqzVTPPakWpBIFzppk4JE4ix0yEtbtSjPbU8+PZETpaYMtXvss2Sdkx8Vs4XRw==} + engines: {node: '>= 10'} + cpu: [x64] + os: [darwin] + requiresBuild: true + dev: true + optional: true + + /@tailwindcss/oxide-freebsd-x64@4.1.8: + resolution: {integrity: sha512-g8C8eGEyhHTqwPStSwZNSrOlyx0bhK/V/+zX0Y+n7DoRUzyS8eMbVshVOLJTDDC+Qn9IJnilYbIKzpB9n4aBsg==} + engines: {node: '>= 10'} + cpu: [x64] + os: [freebsd] + requiresBuild: true + dev: true + optional: true + + /@tailwindcss/oxide-linux-arm-gnueabihf@4.1.8: + resolution: {integrity: sha512-Jmzr3FA4S2tHhaC6yCjac3rGf7hG9R6Gf2z9i9JFcuyy0u79HfQsh/thifbYTF2ic82KJovKKkIB6Z9TdNhCXQ==} + engines: {node: '>= 10'} + cpu: [arm] + os: [linux] + requiresBuild: true + dev: true + optional: true + + /@tailwindcss/oxide-linux-arm64-gnu@4.1.8: + resolution: {integrity: sha512-qq7jXtO1+UEtCmCeBBIRDrPFIVI4ilEQ97qgBGdwXAARrUqSn/L9fUrkb1XP/mvVtoVeR2bt/0L77xx53bPZ/Q==} + engines: {node: '>= 10'} + cpu: [arm64] + os: [linux] + requiresBuild: true + dev: true + optional: true + + /@tailwindcss/oxide-linux-arm64-musl@4.1.8: + resolution: {integrity: sha512-O6b8QesPbJCRshsNApsOIpzKt3ztG35gfX9tEf4arD7mwNinsoCKxkj8TgEE0YRjmjtO3r9FlJnT/ENd9EVefQ==} + engines: {node: '>= 10'} + cpu: [arm64] + os: [linux] + requiresBuild: true + dev: true + optional: true + + /@tailwindcss/oxide-linux-x64-gnu@4.1.8: + resolution: {integrity: sha512-32iEXX/pXwikshNOGnERAFwFSfiltmijMIAbUhnNyjFr3tmWmMJWQKU2vNcFX0DACSXJ3ZWcSkzNbaKTdngH6g==} + engines: {node: '>= 10'} + cpu: [x64] + os: [linux] + requiresBuild: true + dev: true + optional: true + + /@tailwindcss/oxide-linux-x64-musl@4.1.8: + resolution: {integrity: sha512-s+VSSD+TfZeMEsCaFaHTaY5YNj3Dri8rST09gMvYQKwPphacRG7wbuQ5ZJMIJXN/puxPcg/nU+ucvWguPpvBDg==} + engines: {node: '>= 10'} + cpu: [x64] + os: [linux] + requiresBuild: true + dev: true + optional: true + + /@tailwindcss/oxide-wasm32-wasi@4.1.8: + resolution: {integrity: sha512-CXBPVFkpDjM67sS1psWohZ6g/2/cd+cq56vPxK4JeawelxwK4YECgl9Y9TjkE2qfF+9/s1tHHJqrC4SS6cVvSg==} + engines: {node: '>=14.0.0'} + cpu: [wasm32] + requiresBuild: true + dev: true + optional: true + bundledDependencies: + - '@napi-rs/wasm-runtime' + - '@emnapi/core' + - '@emnapi/runtime' + - '@tybys/wasm-util' + - '@emnapi/wasi-threads' + - tslib + + /@tailwindcss/oxide-win32-arm64-msvc@4.1.8: + resolution: {integrity: sha512-7GmYk1n28teDHUjPlIx4Z6Z4hHEgvP5ZW2QS9ygnDAdI/myh3HTHjDqtSqgu1BpRoI4OiLx+fThAyA1JePoENA==} + engines: {node: '>= 10'} + cpu: [arm64] + os: [win32] + requiresBuild: true + dev: true + optional: true + + /@tailwindcss/oxide-win32-x64-msvc@4.1.8: + resolution: {integrity: sha512-fou+U20j+Jl0EHwK92spoWISON2OBnCazIc038Xj2TdweYV33ZRkS9nwqiUi2d/Wba5xg5UoHfvynnb/UB49cQ==} + engines: {node: '>= 10'} + cpu: [x64] + os: [win32] + requiresBuild: true + dev: true + optional: true + + /@tailwindcss/oxide@4.1.8: + resolution: {integrity: sha512-d7qvv9PsM5N3VNKhwVUhpK6r4h9wtLkJ6lz9ZY9aeZgrUWk1Z8VPyqyDT9MZlem7GTGseRQHkeB1j3tC7W1P+A==} + engines: {node: '>= 10'} + requiresBuild: true + dependencies: + detect-libc: 2.1.2 + tar: 7.5.7 + optionalDependencies: + '@tailwindcss/oxide-android-arm64': 4.1.8 + '@tailwindcss/oxide-darwin-arm64': 4.1.8 + '@tailwindcss/oxide-darwin-x64': 4.1.8 + '@tailwindcss/oxide-freebsd-x64': 4.1.8 + '@tailwindcss/oxide-linux-arm-gnueabihf': 4.1.8 + '@tailwindcss/oxide-linux-arm64-gnu': 4.1.8 + '@tailwindcss/oxide-linux-arm64-musl': 4.1.8 + '@tailwindcss/oxide-linux-x64-gnu': 4.1.8 + '@tailwindcss/oxide-linux-x64-musl': 4.1.8 + '@tailwindcss/oxide-wasm32-wasi': 4.1.8 + '@tailwindcss/oxide-win32-arm64-msvc': 4.1.8 + '@tailwindcss/oxide-win32-x64-msvc': 4.1.8 + dev: true + + /@tailwindcss/postcss@4.1.8: + resolution: {integrity: sha512-vB/vlf7rIky+w94aWMw34bWW1ka6g6C3xIOdICKX2GC0VcLtL6fhlLiafF0DVIwa9V6EHz8kbWMkS2s2QvvNlw==} + dependencies: + '@alloc/quick-lru': 5.2.0 + '@tailwindcss/node': 4.1.8 + '@tailwindcss/oxide': 4.1.8 + postcss: 8.5.6 + tailwindcss: 4.1.8 + dev: true + /@tinyhttp/content-disposition@2.2.3: resolution: {integrity: sha512-0nSvOgFHvq0a15+pZAdbAyHUk0+AGLX6oyo45b7fPdgWdPfHA19IfgUKRECYT0aw86ZP6ZDDLxGQ7FEA1fAVOg==} engines: {node: '>=12.17.0'} @@ -4855,7 +5201,7 @@ packages: resolution: {integrity: sha512-HLFeCYgz89uk22N5Qg3dvGvsv46B8GLvKKo1zKG4NybA8U2DiEO3w9lqGg29t/tfLRJpJ6iQxnVw4OnB7MoM9g==} dependencies: '@types/connect': 3.4.38 - '@types/node': 25.1.0 + '@types/node': 22.15.21 /@types/bun@1.3.6: resolution: {integrity: sha512-uWCv6FO/8LcpREhenN1d1b6fcspAB+cefwD7uti8C8VffIv0Um08TKMn98FynpTiU38+y2dUO55T11NgDt8VAA==} @@ -4887,7 +5233,7 @@ packages: /@types/connect@3.4.38: resolution: {integrity: sha512-K6uROf1LD88uDQqJCktA4yzL1YYAK6NgfsI0v/mTgyPKWsX1CnJ0XPSDhViejru1GcRkLWb8RlzFYJRqGUbaug==} dependencies: - '@types/node': 25.1.0 + '@types/node': 22.15.21 /@types/deep-eql@4.0.2: resolution: {integrity: sha512-c9h9dVVMigMPc4bwTvC5dxqtqJZwQPePsWjPlpSOnojbor6pGqdk541lfA7AqFQr5pB1BRdq0juY9db81BwyFw==} @@ -4899,7 +5245,7 @@ packages: /@types/express-serve-static-core@4.19.8: resolution: {integrity: sha512-02S5fmqeoKzVZCHPZid4b8JH2eM5HzQLZWN2FohQEy/0eXTq8VXZfSN6Pcr3F6N9R/vNrj7cpgbhjie6m/1tCA==} dependencies: - '@types/node': 25.1.0 + '@types/node': 22.15.21 '@types/qs': 6.14.0 '@types/range-parser': 1.2.7 '@types/send': 1.2.1 @@ -4908,7 +5254,7 @@ packages: /@types/express-serve-static-core@5.1.1: resolution: {integrity: sha512-v4zIMr/cX7/d2BpAEX3KNKL/JrT1s43s96lLvvdTmza1oEvDudCqK9aF/djc/SWgy8Yh0h30TZx5VpzqFCxk5A==} dependencies: - '@types/node': 25.1.0 + '@types/node': 22.15.21 '@types/qs': 6.14.0 '@types/range-parser': 1.2.7 '@types/send': 1.2.1 @@ -4936,7 +5282,7 @@ packages: resolution: {integrity: sha512-asx5hIG9Qmf/1oStypjanR7iKTv0gXQ1Ov/jfrX6kS/EO0OFni8orbmGCn0672NHR3kXHwpAwR+B368ZGN/2rA==} dependencies: '@types/ms': 2.1.0 - '@types/node': 25.1.0 + '@types/node': 22.15.21 dev: false /@types/linkify-it@5.0.0: @@ -4980,6 +5326,11 @@ packages: undici-types: 6.21.0 dev: false + /@types/node@22.15.21: + resolution: {integrity: sha512-EV/37Td6c+MgKAbkcLG6vqZ2zEYHD7bvSrzqqs2RIhbA6w3x+Dqz8MZM3sP6kGTeLrdoOgKZe+Xja7tUB2DNkQ==} + dependencies: + undici-types: 6.21.0 + /@types/node@24.10.9: resolution: {integrity: sha512-ne4A0IpG3+2ETuREInjPNhUGis1SFjv1d5asp8MzEAGtOZeTeHVDOYqOgqfhvseqg/iXty2hjBf1zAOb7RNiNw==} dependencies: @@ -5007,11 +5358,25 @@ packages: /@types/range-parser@1.2.7: resolution: {integrity: sha512-hKormJbkJqzQGhziax5PItDUTMAM9uE2XXQmM37dyd4hVM+5aVl7oVxMVUiVQn2oCQFN/LKCZdvSM0pFRqbSmQ==} + /@types/react-dom@19.1.5(@types/react@19.1.4): + resolution: {integrity: sha512-CMCjrWucUBZvohgZxkjd6S9h0nZxXjzus6yDfUb+xLxYM7VvjKNH1tQrE9GWLql1XoOP4/Ds3bwFqShHUYraGg==} + peerDependencies: + '@types/react': ^19.0.0 + dependencies: + '@types/react': 19.1.4 + dev: true + + /@types/react@19.1.4: + resolution: {integrity: sha512-EB1yiiYdvySuIITtD5lhW4yPyJ31RkJkkDw794LaQYrxCSaQV/47y5o1FMC4zF9ZyjUjzJMZwbovEnT5yHTW6g==} + dependencies: + csstype: 3.2.3 + dev: true + /@types/request@2.48.13: resolution: {integrity: sha512-FGJ6udDNUCjd19pp0Q3iTiDkwhYup7J8hpMW9c4k53NrccQFFWKRho6hvtPPEhnXWKvukfwAlB6DbDz4yhH5Gg==} dependencies: '@types/caseless': 0.12.5 - '@types/node': 25.1.0 + '@types/node': 22.15.21 '@types/tough-cookie': 4.0.5 form-data: 2.5.5 dev: false @@ -5028,19 +5393,19 @@ packages: resolution: {integrity: sha512-Uqt8rPBE8SY0RK8JB1EzVOIZ32uqy8HwdxCnoCOsYrvnswqmFZ/k+9Ikidlk/ImhsdvBsloHbAlewb2IEBV/Og==} dependencies: '@types/mime': 1.3.5 - '@types/node': 25.1.0 + '@types/node': 22.15.21 dev: false /@types/send@1.2.1: resolution: {integrity: sha512-arsCikDvlU99zl1g69TcAB3mzZPpxgw0UQnaHeC1Nwb015xp8bknZv5rIfri9xTOcMuaVgvabfIRA7PSZVuZIQ==} dependencies: - '@types/node': 25.1.0 + '@types/node': 22.15.21 /@types/serve-static@1.15.10: resolution: {integrity: sha512-tRs1dB+g8Itk72rlSI2ZrW6vZg0YrLI81iQSTkMmOqnqCaNr/8Ek4VwWcN5vZgCYWbg/JJSGBlUaYGAOP73qBw==} dependencies: '@types/http-errors': 2.0.5 - '@types/node': 25.1.0 + '@types/node': 22.15.21 '@types/send': 0.17.6 dev: false @@ -5048,7 +5413,7 @@ packages: resolution: {integrity: sha512-8mam4H1NHLtu7nmtalF7eyBH14QyOASmcxHhSfEoRyr0nP/YdoesEtU+uSRvMe96TW/HPTtkoKqQLl53N7UXMQ==} dependencies: '@types/http-errors': 2.0.5 - '@types/node': 25.1.0 + '@types/node': 22.15.21 /@types/tough-cookie@4.0.5: resolution: {integrity: sha512-/Ad8+nIOV7Rl++6f1BdKxFSMgmoqEoYbHRpPcx3JEfv8VRsQe9Z4mCXeJBzxs7mbHY/XOZZuXlRNfhpVPbs6ZA==} @@ -5060,7 +5425,7 @@ packages: /@types/ws@8.18.1: resolution: {integrity: sha512-ThVF6DCVhA8kUGy+aazFQ4kXQ7E1Ty7A3ypFOe0IcJV8O/M511G99AW24irKrW56Wt44yG9+ij8FaqoBGkuBXg==} dependencies: - '@types/node': 25.1.0 + '@types/node': 22.15.21 /@typescript/native-preview-darwin-arm64@7.0.0-dev.20260130.1: resolution: {integrity: sha512-Jo5kVoxaewKPn/3bKWyUB/gPR+Tjhj6isLc8VshV4OyFX4n6pkvVyk3ANivl7Kwmiv3WGKGUotbZ71DKCZATwA==} @@ -5397,6 +5762,19 @@ packages: zod: 4.3.6 dev: false + /ai@6.0.73(zod@4.3.6): + resolution: {integrity: sha512-p2/ICXIjAM4+bIFHEkAB+l58zq+aTmxAkotsb6doNt/CEms72zt6gxv2ky1fQDwU4ecMOcmMh78VJUSEKECzlg==} + engines: {node: '>=18'} + peerDependencies: + zod: ^3.25.76 || ^4.1.8 + dependencies: + '@ai-sdk/gateway': 3.0.36(zod@4.3.6) + '@ai-sdk/provider': 3.0.7 + '@ai-sdk/provider-utils': 4.0.13(zod@4.3.6) + '@opentelemetry/api': 1.9.0 + zod: 4.3.6 + dev: false + /ajv-formats@3.0.1(ajv@8.17.1): resolution: {integrity: sha512-8iUql50EUR+uUcdRQ3HDqa6EVyo3docL8g5WJ3FNcWmu62IbkGUue/pEyLBW8VGKKucTPgqeks4fIU1DA4yowQ==} peerDependencies: @@ -5693,10 +6071,17 @@ packages: /bun-types@1.3.6: resolution: {integrity: sha512-OlFwHcnNV99r//9v5IIOgQ9Uk37gZqrNMCcqEaExdkVq3Avwqok1bJFmvGMCkCE0FqzdY8VMOZpfpR3lwI+CsQ==} dependencies: - '@types/node': 25.1.0 + '@types/node': 22.15.21 dev: false optional: true + /busboy@1.6.0: + resolution: {integrity: sha512-8SFQbg/0hQ9xy3UNTB0YEnsNBbWfhf7RtnzpL7TkBiTBRfrQ9Fxcnz7VJsleJpyp6rVLvXiuORqjlHi5q+PYuA==} + engines: {node: '>=10.16.0'} + dependencies: + streamsearch: 1.1.0 + dev: false + /bytes@3.1.2: resolution: {integrity: sha512-/Nf7TyzTx6S3yRJObOAV7956r8cr2+Oj8AC5dt8wSP3BQAoeX58NoHyCU8P8zGkNXStjTSi6fzO6F0pBdcYbEg==} engines: {node: '>= 0.8'} @@ -5728,6 +6113,10 @@ packages: get-intrinsic: 1.3.0 dev: false + /caniuse-lite@1.0.30001769: + resolution: {integrity: sha512-BCfFL1sHijQlBGWBMuJyhZUhzo7wer5sVj9hqekB/7xn0Ypy+pER/edCYQm4exbXj4WiySGp40P8UuTh6w1srg==} + dev: false + /caseless@0.12.0: resolution: {integrity: sha512-4tYFyifaFfGacoiObjJegolkwSU4xQNGbVgUiNYVUxbQ2x2lUsFvY4hVgVzGiIe6WLOPqycWXA40l+PWsxthUw==} dev: false @@ -5771,7 +6160,6 @@ packages: /chownr@3.0.0: resolution: {integrity: sha512-+IxzY9BZOQd/XuYPRmrvEVjF/nqj5kgT4kEq7VofrDoM1MxoRjEWkrCC3EtLi59TVawxTAn+orJwFQcrqEN1+g==} engines: {node: '>=18'} - dev: false /ci-info@4.4.0: resolution: {integrity: sha512-77PSwercCZU2Fc4sX94eF8k8Pxte6JAwL4/ICZLFjJLqegs7kCuAsqqj/70NQF6TvDpgFjkubQB2FW2ZZddvQg==} @@ -5807,6 +6195,10 @@ packages: engines: {node: '>=6'} dev: false + /client-only@0.0.1: + resolution: {integrity: sha512-IV3Ou0jSMzZrd3pZ48nLkT9DA7Ag1pnPzaiQhpW7c3RbcqqzvzzVu+L8gfqMp/8IM2MQtSiqaCxrrcfu8I8rMA==} + dev: false + /cliui@7.0.4: resolution: {integrity: sha512-OcRE68cOsVMXp1Yvonl/fzkQOyjLSu/8bhPDfQt0e0/Eb283TKP20Fs2MqoPsr9SwA595rRCA+QMzYc9nBP+JQ==} dependencies: @@ -5979,6 +6371,10 @@ packages: resolution: {integrity: sha512-iKuQcq+NdHqlAcwUY0o/HL69XQrUaQdMjmStJ8JFmUaiiQErlhrmuigkg/CU4E2J0IyUKUrMAgl36TvN67MqTw==} dev: false + /csstype@3.2.3: + resolution: {integrity: sha512-z1HGKcYy2xA8AGQfwrn0PAy+PB7X/GSj3UVJW9qKyn43xWa+gl5nXmU4qqLMRzWVLFC8KusUX8T/0kCiOYpAIQ==} + dev: true + /curve25519-js@0.0.4: resolution: {integrity: sha512-axn2UMEnkhyDUPWOwVKBMVIzSQy2ejH2xRGy1wq81dqRwApXfIzfbE3hIX0ZRFBIihf/KDqK158DLwESu4AK1w==} dev: false @@ -6056,6 +6452,11 @@ packages: engines: {node: '>= 0.8'} dev: false + /dequal@2.0.3: + resolution: {integrity: sha512-0je+qPKHEMohvfRTCEo3CrPG6cAzAYgmzKyxRiYSSDkS6eGJdyVJm7WaYA5ECaAD9wLB2T4EEeymA5aFVcYXCA==} + engines: {node: '>=6'} + dev: false + /destroy@1.2.0: resolution: {integrity: sha512-2sJGJTaXIIaR1w4iJSNoN0hnMY7Gpc/n8D4qSCJw8QqFWXf7cuAgnEHxBpweaVcPevC2l3KpjYCx3NypQQgaJg==} engines: {node: '>= 0.8', npm: 1.2.8000 || >= 1.4.16} @@ -6064,7 +6465,6 @@ packages: /detect-libc@2.1.2: resolution: {integrity: sha512-Btj2BOOO83o3WyH59e8MgXsxEQVcarkUOpEYrubB0urwnN10yQ364rsiByU11nZlqWYZm05i/of7io4mzihBtQ==} engines: {node: '>=8'} - dev: false /diff@8.0.3: resolution: {integrity: sha512-qejHi7bcSD4hQAZE0tNAawRK1ZtafHDmMTMkrrIGgSLl7hTnQHmKCeB45xAcbfTqK2zowkM3j3bHt/4b/ARbYQ==} @@ -6164,6 +6564,14 @@ packages: engines: {node: '>= 0.8'} dev: false + /enhanced-resolve@5.19.0: + resolution: {integrity: sha512-phv3E1Xl4tQOShqSte26C7Fl84EwUdZsyOuSSk9qtAGyyQs2s3jJzComh+Abf4g187lUUAvH+H26omrqia2aGg==} + engines: {node: '>=10.13.0'} + dependencies: + graceful-fs: 4.2.11 + tapable: 2.3.0 + dev: true + /entities@4.5.0: resolution: {integrity: sha512-V0hjH4dGPh9Ao5p0MoRY6BVqtwCjhz6vI5LT8AJ55H+4g9/4vbHx1I54fS0XuclLhDHArPQCiMjDxjaL8fPxhw==} engines: {node: '>=0.12'} @@ -6765,7 +7173,6 @@ packages: /graceful-fs@4.2.11: resolution: {integrity: sha512-RbJ5/jmFcNNCcDV5o9eTnBLJ/HszWV0P73bc+Ff4nS/rJj+YaS6IGyiOL0VoBYX+l1Wrl3k63h/KrH+nhJ0XvQ==} - dev: false /grammy@1.39.3: resolution: {integrity: sha512-7arRRoOtOh9UwMwANZ475kJrWV6P3/EGNooeHlY0/SwZv4t3ZZ3Uiz9cAXK8Zg9xSdgmm8T21kx6n7SZaWvOcw==} @@ -7301,6 +7708,114 @@ packages: resolution: {integrity: sha512-Qt/Jl5dsNIsyCAZsHB6x3mbwHFn0HJbdmvF49sVX/bHgX2cW7+G+U+I67Zw+TPM1Sr21Gb2nfJMd2g6iUcI1EQ==} dev: false + /lightningcss-darwin-arm64@1.30.1: + resolution: {integrity: sha512-c8JK7hyE65X1MHMN+Viq9n11RRC7hgin3HhYKhrMyaXflk5GVplZ60IxyoVtzILeKr+xAJwg6zK6sjTBJ0FKYQ==} + engines: {node: '>= 12.0.0'} + cpu: [arm64] + os: [darwin] + requiresBuild: true + dev: true + optional: true + + /lightningcss-darwin-x64@1.30.1: + resolution: {integrity: sha512-k1EvjakfumAQoTfcXUcHQZhSpLlkAuEkdMBsI/ivWw9hL+7FtilQc0Cy3hrx0AAQrVtQAbMI7YjCgYgvn37PzA==} + engines: {node: '>= 12.0.0'} + cpu: [x64] + os: [darwin] + requiresBuild: true + dev: true + optional: true + + /lightningcss-freebsd-x64@1.30.1: + resolution: {integrity: sha512-kmW6UGCGg2PcyUE59K5r0kWfKPAVy4SltVeut+umLCFoJ53RdCUWxcRDzO1eTaxf/7Q2H7LTquFHPL5R+Gjyig==} + engines: {node: '>= 12.0.0'} + cpu: [x64] + os: [freebsd] + requiresBuild: true + dev: true + optional: true + + /lightningcss-linux-arm-gnueabihf@1.30.1: + resolution: {integrity: sha512-MjxUShl1v8pit+6D/zSPq9S9dQ2NPFSQwGvxBCYaBYLPlCWuPh9/t1MRS8iUaR8i+a6w7aps+B4N0S1TYP/R+Q==} + engines: {node: '>= 12.0.0'} + cpu: [arm] + os: [linux] + requiresBuild: true + dev: true + optional: true + + /lightningcss-linux-arm64-gnu@1.30.1: + resolution: {integrity: sha512-gB72maP8rmrKsnKYy8XUuXi/4OctJiuQjcuqWNlJQ6jZiWqtPvqFziskH3hnajfvKB27ynbVCucKSm2rkQp4Bw==} + engines: {node: '>= 12.0.0'} + cpu: [arm64] + os: [linux] + requiresBuild: true + dev: true + optional: true + + /lightningcss-linux-arm64-musl@1.30.1: + resolution: {integrity: sha512-jmUQVx4331m6LIX+0wUhBbmMX7TCfjF5FoOH6SD1CttzuYlGNVpA7QnrmLxrsub43ClTINfGSYyHe2HWeLl5CQ==} + engines: {node: '>= 12.0.0'} + cpu: [arm64] + os: [linux] + requiresBuild: true + dev: true + optional: true + + /lightningcss-linux-x64-gnu@1.30.1: + resolution: {integrity: sha512-piWx3z4wN8J8z3+O5kO74+yr6ze/dKmPnI7vLqfSqI8bccaTGY5xiSGVIJBDd5K5BHlvVLpUB3S2YCfelyJ1bw==} + engines: {node: '>= 12.0.0'} + cpu: [x64] + os: [linux] + requiresBuild: true + dev: true + optional: true + + /lightningcss-linux-x64-musl@1.30.1: + resolution: {integrity: sha512-rRomAK7eIkL+tHY0YPxbc5Dra2gXlI63HL+v1Pdi1a3sC+tJTcFrHX+E86sulgAXeI7rSzDYhPSeHHjqFhqfeQ==} + engines: {node: '>= 12.0.0'} + cpu: [x64] + os: [linux] + requiresBuild: true + dev: true + optional: true + + /lightningcss-win32-arm64-msvc@1.30.1: + resolution: {integrity: sha512-mSL4rqPi4iXq5YVqzSsJgMVFENoa4nGTT/GjO2c0Yl9OuQfPsIfncvLrEW6RbbB24WtZ3xP/2CCmI3tNkNV4oA==} + engines: {node: '>= 12.0.0'} + cpu: [arm64] + os: [win32] + requiresBuild: true + dev: true + optional: true + + /lightningcss-win32-x64-msvc@1.30.1: + resolution: {integrity: sha512-PVqXh48wh4T53F/1CCu8PIPCxLzWyCnn/9T5W1Jpmdy5h9Cwd+0YQS6/LwhHXSafuc61/xg9Lv5OrCby6a++jg==} + engines: {node: '>= 12.0.0'} + cpu: [x64] + os: [win32] + requiresBuild: true + dev: true + optional: true + + /lightningcss@1.30.1: + resolution: {integrity: sha512-xi6IyHML+c9+Q3W0S4fCQJOym42pyurFiJUHEcEyHS0CeKzia4yZDEsLlqOFykxOdHpNy0NmvVO31vcSqAxJCg==} + engines: {node: '>= 12.0.0'} + dependencies: + detect-libc: 2.1.2 + optionalDependencies: + lightningcss-darwin-arm64: 1.30.1 + lightningcss-darwin-x64: 1.30.1 + lightningcss-freebsd-x64: 1.30.1 + lightningcss-linux-arm-gnueabihf: 1.30.1 + lightningcss-linux-arm64-gnu: 1.30.1 + lightningcss-linux-arm64-musl: 1.30.1 + lightningcss-linux-x64-gnu: 1.30.1 + lightningcss-linux-x64-musl: 1.30.1 + lightningcss-win32-arm64-msvc: 1.30.1 + lightningcss-win32-x64-msvc: 1.30.1 + dev: true + /limiter@1.1.5: resolution: {integrity: sha512-FWWMIEOxz3GwUI4Ts/IvgVy6LPvoMPgjMdQ185nN6psJyBJ4yOpzqm695/h5umdLJg2vW3GR5iG11MAkR2AzJA==} dev: false @@ -7604,14 +8119,12 @@ packages: /minipass@7.1.2: resolution: {integrity: sha512-qOOzS1cBTWYF4BH8fVePDBOO9iptMnGUEZwNc/cMWnTV2nVLZ7VoNWEPHkYczZA0pdoA7dl6e7FL659nX9S2aw==} engines: {node: '>=16 || 14 >=14.17'} - dev: false /minizlib@3.1.0: resolution: {integrity: sha512-KZxYo1BUkWD2TVFLr0MQoM8vUUigWD3LlD83a/75BqC+4qE0Hb1Vo5v1FgcfaNXvfXzr+5EhQ6ing/CaBijTlw==} engines: {node: '>= 18'} dependencies: minipass: 7.1.2 - dev: false /mkdirp@3.0.1: resolution: {integrity: sha512-+NsyUUAZDmo6YVHzL/stxSu3t9YS1iljliy3BSDrXJ/dkn1KYdmtZODGGjLcc9XLgVVpH4KshHB8XmZgMhaBXg==} @@ -7701,6 +8214,52 @@ packages: engines: {node: '>= 0.4.0'} dev: false + /next@15.3.3(react-dom@19.1.0)(react@19.1.0): + resolution: {integrity: sha512-JqNj29hHNmCLtNvd090SyRbXJiivQ+58XjCcrC50Crb5g5u2zi7Y2YivbsEfzk6AtVI80akdOQbaMZwWB1Hthw==} + engines: {node: ^18.18.0 || ^19.8.0 || >= 20.0.0} + deprecated: This version has a security vulnerability. Please upgrade to a patched version. See https://nextjs.org/blog/CVE-2025-66478 for more details. + hasBin: true + peerDependencies: + '@opentelemetry/api': ^1.1.0 + '@playwright/test': ^1.41.2 + babel-plugin-react-compiler: '*' + react: ^18.2.0 || 19.0.0-rc-de68d2f4-20241204 || ^19.0.0 + react-dom: ^18.2.0 || 19.0.0-rc-de68d2f4-20241204 || ^19.0.0 + sass: ^1.3.0 + peerDependenciesMeta: + '@opentelemetry/api': + optional: true + '@playwright/test': + optional: true + babel-plugin-react-compiler: + optional: true + sass: + optional: true + dependencies: + '@next/env': 15.3.3 + '@swc/counter': 0.1.3 + '@swc/helpers': 0.5.15 + busboy: 1.6.0 + caniuse-lite: 1.0.30001769 + postcss: 8.4.31 + react: 19.1.0 + react-dom: 19.1.0(react@19.1.0) + styled-jsx: 5.1.6(react@19.1.0) + optionalDependencies: + '@next/swc-darwin-arm64': 15.3.3 + '@next/swc-darwin-x64': 15.3.3 + '@next/swc-linux-arm64-gnu': 15.3.3 + '@next/swc-linux-arm64-musl': 15.3.3 + '@next/swc-linux-x64-gnu': 15.3.3 + '@next/swc-linux-x64-musl': 15.3.3 + '@next/swc-win32-arm64-msvc': 15.3.3 + '@next/swc-win32-x64-msvc': 15.3.3 + sharp: 0.34.5 + transitivePeerDependencies: + - '@babel/core' + - babel-plugin-macros + dev: false + /node-addon-api@8.5.0: resolution: {integrity: sha512-/bRZty2mXUIFY/xU5HLvveNHlswNJej+RnxBjOMkidWfwZzgTbPG1E3K5TOxRLOR+5hX7bSofy8yf1hZevMS8A==} engines: {node: ^18 || ^20 || >= 21} @@ -8271,6 +8830,15 @@ packages: engines: {node: '>=14.19.0'} dev: true + /postcss@8.4.31: + resolution: {integrity: sha512-PS08Iboia9mts/2ygV3eLpY5ghnUcfLV/EXTOW1E2qYxJKGGBUtNjN76FYHnMs36RmARn41bC0AZmn+rR0OVpQ==} + engines: {node: ^10 || ^12 || >=14} + dependencies: + nanoid: 3.3.11 + picocolors: 1.1.1 + source-map-js: 1.2.1 + dev: false + /postcss@8.5.6: resolution: {integrity: sha512-3Ybi1tAuwAP9s0r1UQ2J4n5Y0G05bJkpUIO0/bI9MhwmD70S5aTWbXGBwxHrelT+XM1k6dM0pk+SwNkpTRN7Pg==} engines: {node: ^10 || ^12 || >=14} @@ -8373,7 +8941,7 @@ packages: '@protobufjs/path': 1.1.2 '@protobufjs/pool': 1.1.0 '@protobufjs/utf8': 1.1.0 - '@types/node': 25.1.0 + '@types/node': 22.15.21 long: 5.3.2 dev: false @@ -8392,7 +8960,7 @@ packages: '@protobufjs/path': 1.1.2 '@protobufjs/pool': 1.1.0 '@protobufjs/utf8': 1.1.0 - '@types/node': 25.1.0 + '@types/node': 22.15.21 long: 5.3.2 dev: false @@ -8503,6 +9071,20 @@ packages: strip-json-comments: 2.0.1 dev: false + /react-dom@19.1.0(react@19.1.0): + resolution: {integrity: sha512-Xs1hdnE+DyKgeHJeJznQmYMIBG3TKIHJJT95Q58nHLSrElKlGQqDTR2HQ9fx5CN/Gk6Vh/kupBTDLU11/nDk/g==} + peerDependencies: + react: ^19.1.0 + dependencies: + react: 19.1.0 + scheduler: 0.26.0 + dev: false + + /react@19.1.0: + resolution: {integrity: sha512-FS+XFBNvn3GTAWq26joslQgWNoFu08F4kl0J4CgdNKADkdSGXQyTCnKteIAJy96Br6YbpEU1LSzV5dYtjMkMDg==} + engines: {node: '>=0.10.0'} + dev: false + /readable-stream@2.3.8: resolution: {integrity: sha512-8p0AUk4XODgIewSi0l8Epjs+EVnWiK7NoDIEGU0HhE7+ZyY8D1IMY7odu5lRrFXGg71L15KG8QrPmum45RTtdA==} dependencies: @@ -8735,6 +9317,10 @@ packages: postcss: 8.5.6 dev: false + /scheduler@0.26.0: + resolution: {integrity: sha512-NlHwttCI/l5gCPR3D1nNXtWABUmBwvZpEQiD4IXSbIDq8BzLIK/7Ir5gTFSGZDUu37K5cMNp0hFtzO38sC7gWA==} + dev: false + /selderee@0.11.0: resolution: {integrity: sha512-5TF+l7p4+OsnP8BCCvSyZiSPc4x4//p5uPwK8TCnVPJYRmU2aYKMpOXvw8zM5a5JvuuCGN1jmsMwuU2W02ukfA==} dependencies: @@ -9128,6 +9714,11 @@ packages: engines: {node: '>=18'} dev: false + /streamsearch@1.1.0: + resolution: {integrity: sha512-Mcc5wHehp9aXz1ax6bZUyY5afg9u2rv5cqQI3mRrYkGC8rW2hM02jWuwjtL++LS5qinSyhj2QfLyNsuc+VsExg==} + engines: {node: '>=10.0.0'} + dev: false + /string-width@4.2.3: resolution: {integrity: sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g==} engines: {node: '>=8'} @@ -9197,12 +9788,39 @@ packages: '@tokenizer/token': 0.3.0 dev: false + /styled-jsx@5.1.6(react@19.1.0): + resolution: {integrity: sha512-qSVyDTeMotdvQYoHWLNGwRFJHC+i+ZvdBRYosOFgC+Wg1vx4frN2/RG/NA7SYqqvKNLf39P2LSRA2pu6n0XYZA==} + engines: {node: '>= 12.0.0'} + peerDependencies: + '@babel/core': '*' + babel-plugin-macros: '*' + react: '>= 16.8.0 || 17.x.x || ^18.0.0-0 || ^19.0.0-0' + peerDependenciesMeta: + '@babel/core': + optional: true + babel-plugin-macros: + optional: true + dependencies: + client-only: 0.0.1 + react: 19.1.0 + dev: false + /supports-color@7.2.0: resolution: {integrity: sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==} engines: {node: '>=8'} dependencies: has-flag: 4.0.0 + /swr@2.4.0(react@19.1.0): + resolution: {integrity: sha512-sUlC20T8EOt1pHmDiqueUWMmRRX03W7w5YxovWX7VR2KHEPCTMly85x05vpkP5i6Bu4h44ePSMD9Tc+G2MItFw==} + peerDependencies: + react: ^16.11.0 || ^17.0.0 || ^18.0.0 || ^19.0.0 + dependencies: + dequal: 2.0.3 + react: 19.1.0 + use-sync-external-store: 1.6.0(react@19.1.0) + dev: false + /table-layout@4.1.1: resolution: {integrity: sha512-iK5/YhZxq5GO5z8wb0bY1317uDF3Zjpha0QFFLA8/trAoiLbQD0HUbMesEaxyzUgDxi2QlcbM8IvqOlEjgoXBA==} engines: {node: '>=12.17'} @@ -9211,6 +9829,15 @@ packages: wordwrapjs: 5.1.1 dev: false + /tailwindcss@4.1.8: + resolution: {integrity: sha512-kjeW8gjdxasbmFKpVGrGd5T4i40mV5J2Rasw48QARfYeQ8YS9x02ON9SFWax3Qf616rt4Cp3nVNIj6Hd1mP3og==} + dev: true + + /tapable@2.3.0: + resolution: {integrity: sha512-g9ljZiwki/LfxmQADO3dEY1CbpmXT5Hm2fJ+QaGKwSXUylMybePR7/67YW7jOrrvjEgL1Fmz5kzyAjWVWLlucg==} + engines: {node: '>=6'} + dev: true + /tar@7.5.7: resolution: {integrity: sha512-fov56fJiRuThVFXD6o6/Q354S7pnWMJIVlDBYijsTNx6jKSE4pvrDTs6lUnmGvNyfJwFQQwWy3owKz1ucIhveQ==} engines: {node: '>=18'} @@ -9220,7 +9847,6 @@ packages: minipass: 7.1.2 minizlib: 3.1.0 yallist: 5.0.0 - dev: false /thenify-all@1.6.0: resolution: {integrity: sha512-RNxQH/qI8/t3thXJDwcstUO4zeqo64+Uy/+sNVRBx4Xn2OX+OZ9oP+iJnNFqplFra2ZUVeKCSa2oVWi3T4uVmA==} @@ -9241,6 +9867,11 @@ packages: real-require: 0.2.0 dev: false + /throttleit@2.1.0: + resolution: {integrity: sha512-nt6AMGKW1p/70DF/hGBdJB57B8Tspmbp5gfJ8ilhLnt7kkr2ye7hzD6NVG8GGErk2HWF34igrL2CXmNIkzKqKw==} + engines: {node: '>=18'} + dev: false + /tinybench@2.9.0: resolution: {integrity: sha512-0+DUvqWMValLmha6lr4kD8iAMK1HzV0/aKnCtWb9v9641TnP/MFb7Pc2bxoxQjTXAErryXVgUOfv2YqNllqGeg==} dev: true @@ -9387,7 +10018,6 @@ packages: /undici-types@6.21.0: resolution: {integrity: sha512-iwDZqg0QAGrg9Rav5H4n0M64c3mkR59cJ6wQp+7C4nI0gsmExaedaYLNO44eT4AtBBwjbTiGPMlt2Md0T9H9JQ==} - dev: false /undici-types@7.16.0: resolution: {integrity: sha512-Zz+aZWSj8LE6zoxD+xrjh4VfkIG8Ya6LvYkZqtUQGJPZjYl53ypCaUwWqo7eI0x66KBGeRo+mlBEkMSeSZ38Nw==} @@ -9425,6 +10055,14 @@ packages: resolution: {integrity: sha512-jk1+QP6ZJqyOiuEI9AEWQfju/nB2Pw466kbA0LEZljHwKeMgd9WrAEgEGxjPDD2+TNbbb37rTyhEfrCXfuKXnA==} dev: false + /use-sync-external-store@1.6.0(react@19.1.0): + resolution: {integrity: sha512-Pp6GSwGP/NrPIrxVFAIkOQeyw8lFenOHijQWkUTrDvrF4ALqylP2C/KCkeS9dpUM3KvYRQhna5vt7IL95+ZQ9w==} + peerDependencies: + react: ^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0 + dependencies: + react: 19.1.0 + dev: false + /util-deprecate@1.0.2: resolution: {integrity: sha512-EPD5q1uXyFxJpCrLnCc1nHnq3gOa6DZBocAIiI2TaSCA7VCJ1UJDMagCzIkXNsUYfD1daK//LTEQ8xiIbrHtcw==} dev: false @@ -9698,7 +10336,6 @@ packages: /yallist@5.0.0: resolution: {integrity: sha512-YgvUTfwqyc7UXVMrB+SImsVYSmTS8X/tSrtdNZMImM+n7+QTriRXyXim0mBrTXNeqzVF0KWGgHPeiyViFFrNDw==} engines: {node: '>=18'} - dev: false /yaml@2.8.2: resolution: {integrity: sha512-mplynKqc1C2hTVYxd0PU2xQAc22TI1vShAYGksCCfxbn/dFwnHTNi1bvYsBTkhdUNtGIf5xNOg938rrSSYvS9A==} diff --git a/pnpm-workspace.yaml b/pnpm-workspace.yaml index f3baa1d99e8..6b194a86011 100644 --- a/pnpm-workspace.yaml +++ b/pnpm-workspace.yaml @@ -1,6 +1,7 @@ packages: - . - ui + - apps/web - packages/* - extensions/* diff --git a/src/cli/program/register.agent.ts b/src/cli/program/register.agent.ts index 7d114591dd9..bbac3d0f435 100644 --- a/src/cli/program/register.agent.ts +++ b/src/cli/program/register.agent.ts @@ -41,6 +41,7 @@ export function registerAgentCommands(program: Command, args: { agentChannelOpti ) .option("--deliver", "Send the agent's reply back to the selected channel", false) .option("--json", "Output result as JSON", false) + .option("--stream-json", "Stream NDJSON events to stdout", false) .option( "--timeout ", "Override agent command timeout (seconds, default 600 or config value)", @@ -73,10 +74,17 @@ ${theme.muted("Docs:")} ${formatDocsLink("/cli/agent", "docs.openclaw.ai/cli/age .action(async (opts) => { const verboseLevel = typeof opts.verbose === "string" ? opts.verbose.toLowerCase() : ""; setVerbose(verboseLevel === "on"); + if (opts.json && opts.streamJson) { + throw new Error("Choose either --json or --stream-json, not both."); + } // Build default deps (keeps parity with other commands; future-proofing). const deps = createDefaultDeps(); await runCommandWithRuntime(defaultRuntime, async () => { - await agentCliCommand(opts, defaultRuntime, deps); + await agentCliCommand( + { ...opts, streamJson: Boolean(opts.streamJson) }, + defaultRuntime, + deps, + ); }); }); diff --git a/src/commands/agent-via-gateway.test.ts b/src/commands/agent-via-gateway.test.ts index d0513b6ccbe..cd03ce03ce9 100644 --- a/src/commands/agent-via-gateway.test.ts +++ b/src/commands/agent-via-gateway.test.ts @@ -15,7 +15,7 @@ import type { OpenClawConfig } from "../config/config.js"; import type { RuntimeEnv } from "../runtime.js"; import * as configModule from "../config/config.js"; import { callGateway } from "../gateway/call.js"; -import { agentCliCommand } from "./agent-via-gateway.js"; +import { agentCliCommand, emitNdjsonLine } from "./agent-via-gateway.js"; import { agentCommand } from "./agent.js"; const runtime: RuntimeEnv = { @@ -122,4 +122,102 @@ describe("agentCliCommand", () => { fs.rmSync(dir, { recursive: true, force: true }); } }); + + it("routes to streaming gateway path when --stream-json is set", async () => { + const dir = fs.mkdtempSync(path.join(os.tmpdir(), "openclaw-agent-cli-")); + const store = path.join(dir, "sessions.json"); + mockConfig(store); + + const stdoutSpy = vi.spyOn(process.stdout, "write").mockImplementation(() => true); + + // callGateway should receive an onEvent callback when streaming + vi.mocked(callGateway).mockImplementation(async (opts) => { + // Simulate a couple of gateway events via the onEvent callback + const onEvent = (opts as { onEvent?: (evt: unknown) => void }).onEvent; + if (onEvent) { + onEvent({ + event: "chat", + payload: { runId: "r1", state: "delta", message: { text: "he" } }, + seq: 1, + }); + onEvent({ + event: "chat", + payload: { runId: "r1", state: "final", message: { text: "hello" } }, + seq: 2, + }); + } + return { runId: "r1", status: "ok", result: { payloads: [{ text: "hello" }] } }; + }); + + try { + await agentCliCommand({ message: "hi", to: "+1555", streamJson: true }, runtime); + + expect(callGateway).toHaveBeenCalledTimes(1); + // Verify onEvent was passed to callGateway + const callOpts = vi.mocked(callGateway).mock.calls[0][0] as Record; + expect(typeof callOpts.onEvent).toBe("function"); + + // Verify NDJSON lines were written to stdout (2 events + 1 result) + const writes = stdoutSpy.mock.calls.map(([data]) => String(data)); + expect(writes).toHaveLength(3); + for (const line of writes) { + // Each line should be valid JSON followed by a newline + expect(line.endsWith("\n")).toBe(true); + expect(() => JSON.parse(line)).not.toThrow(); + } + + // The last line should be the result event + const lastLine = JSON.parse(writes[2]); + expect(lastLine.event).toBe("result"); + expect(lastLine.status).toBe("ok"); + + // Normal log output should NOT be called (NDJSON-only) + expect(runtime.log).not.toHaveBeenCalled(); + } finally { + stdoutSpy.mockRestore(); + fs.rmSync(dir, { recursive: true, force: true }); + } + }); + + it("passes --stream-json through to embedded agent when --local is set", async () => { + const dir = fs.mkdtempSync(path.join(os.tmpdir(), "openclaw-agent-cli-")); + const store = path.join(dir, "sessions.json"); + mockConfig(store); + + vi.mocked(agentCommand).mockResolvedValueOnce(undefined); + + try { + await agentCliCommand({ message: "hi", to: "+1555", local: true, streamJson: true }, runtime); + + expect(callGateway).not.toHaveBeenCalled(); + expect(agentCommand).toHaveBeenCalledTimes(1); + const passedOpts = vi.mocked(agentCommand).mock.calls[0][0] as Record; + expect(passedOpts.streamJson).toBe(true); + } finally { + fs.rmSync(dir, { recursive: true, force: true }); + } + }); +}); + +describe("emitNdjsonLine", () => { + it("writes valid JSON followed by a newline", () => { + const spy = vi.spyOn(process.stdout, "write").mockImplementation(() => true); + try { + emitNdjsonLine({ + event: "agent", + runId: "r1", + stream: "lifecycle", + data: { phase: "start" }, + }); + expect(spy).toHaveBeenCalledTimes(1); + const output = String(spy.mock.calls[0][0]); + expect(output.endsWith("\n")).toBe(true); + const parsed = JSON.parse(output); + expect(parsed.event).toBe("agent"); + expect(parsed.runId).toBe("r1"); + expect(parsed.data).toEqual({ phase: "start" }); + } finally { + spy.mockRestore(); + } + }); }); diff --git a/src/commands/agent-via-gateway.ts b/src/commands/agent-via-gateway.ts index ef1e8e97ba1..1a1b3d67cae 100644 --- a/src/commands/agent-via-gateway.ts +++ b/src/commands/agent-via-gateway.ts @@ -15,6 +15,11 @@ import { import { agentCommand } from "./agent.js"; import { resolveSessionKeyForRequest } from "./agent/session.js"; +/** Write a single NDJSON line to stdout. */ +export function emitNdjsonLine(obj: Record): void { + process.stdout.write(`${JSON.stringify(obj)}\n`); +} + type AgentGatewayResult = { payloads?: Array<{ text?: string; @@ -39,6 +44,8 @@ export type AgentCliOpts = { thinking?: string; verbose?: string; json?: boolean; + /** Stream NDJSON events to stdout during the agent run. */ + streamJson?: boolean; timeout?: string; deliver?: boolean; channel?: string; @@ -172,6 +179,78 @@ export async function agentViaGatewayCommand(opts: AgentCliOpts, runtime: Runtim return response; } +/** + * Gateway agent call with live NDJSON event streaming to stdout. + * Reuses callGateway with an onEvent callback to emit each gateway event as an NDJSON line. + */ +async function agentViaGatewayStreamJson(opts: AgentCliOpts, _runtime: RuntimeEnv) { + const body = (opts.message ?? "").trim(); + if (!body) { + throw new Error("Message (--message) is required"); + } + if (!opts.to && !opts.sessionId && !opts.agent) { + throw new Error("Pass --to , --session-id, or --agent to choose a session"); + } + + const cfg = loadConfig(); + const agentIdRaw = opts.agent?.trim(); + const agentId = agentIdRaw ? normalizeAgentId(agentIdRaw) : undefined; + if (agentId) { + const knownAgents = listAgentIds(cfg); + if (!knownAgents.includes(agentId)) { + throw new Error( + `Unknown agent id "${agentIdRaw}". Use "${formatCliCommand("openclaw agents list")}" to see configured agents.`, + ); + } + } + const timeoutSeconds = parseTimeoutSeconds({ cfg, timeout: opts.timeout }); + const gatewayTimeoutMs = Math.max(10_000, (timeoutSeconds + 30) * 1000); + + const sessionKey = resolveSessionKeyForRequest({ + cfg, + agentId, + to: opts.to, + sessionId: opts.sessionId, + }).sessionKey; + + const channel = normalizeMessageChannel(opts.channel) ?? DEFAULT_CHAT_CHANNEL; + const idempotencyKey = opts.runId?.trim() || randomIdempotencyKey(); + + const response = await callGateway({ + method: "agent", + params: { + message: body, + agentId, + to: opts.to, + replyTo: opts.replyTo, + sessionId: opts.sessionId, + sessionKey, + thinking: opts.thinking, + deliver: Boolean(opts.deliver), + channel, + replyChannel: opts.replyChannel, + replyAccountId: opts.replyAccount, + timeout: timeoutSeconds, + lane: opts.lane, + extraSystemPrompt: opts.extraSystemPrompt, + idempotencyKey, + }, + expectFinal: true, + timeoutMs: gatewayTimeoutMs, + clientName: GATEWAY_CLIENT_NAMES.CLI, + mode: GATEWAY_CLIENT_MODES.CLI, + onEvent: (evt) => { + // Emit each gateway event as an NDJSON line (chat deltas, agent tool/lifecycle events). + emitNdjsonLine({ event: evt.event, ...(evt.payload as Record) }); + }, + }); + + // Emit the final result as the last NDJSON line. + emitNdjsonLine({ event: "result", ...response }); + + return response; +} + export async function agentCliCommand(opts: AgentCliOpts, runtime: RuntimeEnv, deps?: CliDeps) { const localOpts = { ...opts, @@ -182,6 +261,11 @@ export async function agentCliCommand(opts: AgentCliOpts, runtime: RuntimeEnv, d return await agentCommand(localOpts, runtime, deps); } + // Stream NDJSON via the gateway (no embedded fallback — streaming should fail loud). + if (opts.streamJson) { + return await agentViaGatewayStreamJson(opts, runtime); + } + try { return await agentViaGatewayCommand(opts, runtime); } catch (err) { diff --git a/src/commands/agent.ts b/src/commands/agent.ts index e8818495b36..b8f93e1267d 100644 --- a/src/commands/agent.ts +++ b/src/commands/agent.ts @@ -46,6 +46,7 @@ import { import { clearAgentRunContext, emitAgentEvent, + onAgentEvent, registerAgentRunContext, } from "../infra/agent-events.js"; import { getRemoteSkillEligibility } from "../infra/skills-remote.js"; @@ -55,6 +56,7 @@ import { applyVerboseOverride } from "../sessions/level-overrides.js"; import { applyModelOverrideToSessionEntry } from "../sessions/model-overrides.js"; import { resolveSendPolicy } from "../sessions/send-policy.js"; import { resolveMessageChannel } from "../utils/message-channel.js"; +import { emitNdjsonLine } from "./agent-via-gateway.js"; import { deliverAgentCommandResult } from "./agent/delivery.js"; import { resolveAgentRunContext } from "./agent/run-context.js"; import { updateSessionStoreAfterAgentRun } from "./agent/session-store.js"; @@ -156,6 +158,24 @@ export async function agentCommand( let sessionEntry = resolvedSessionEntry; const runId = opts.runId?.trim() || sessionId; + // Subscribe to agent events for NDJSON streaming when --stream-json is active. + const unsubNdjson = opts.streamJson + ? onAgentEvent((evt) => { + if (evt.runId !== runId) { + return; + } + emitNdjsonLine({ + event: "agent", + runId: evt.runId, + seq: evt.seq, + stream: evt.stream, + ts: evt.ts, + data: evt.data, + ...(evt.sessionKey ? { sessionKey: evt.sessionKey } : {}), + }); + }) + : undefined; + try { if (opts.deliver === true) { const sendPolicy = resolveSendPolicy({ @@ -510,6 +530,16 @@ export async function agentCommand( }); } + // Emit the final result as NDJSON when streaming. + if (opts.streamJson) { + emitNdjsonLine({ + event: "result", + runId, + status: "ok", + payloads: result.payloads ?? [], + }); + } + const payloads = result.payloads ?? []; return await deliverAgentCommandResult({ cfg, @@ -521,6 +551,7 @@ export async function agentCommand( payloads, }); } finally { + unsubNdjson?.(); clearAgentRunContext(runId); } } diff --git a/src/commands/agent/types.ts b/src/commands/agent/types.ts index e59c88725dc..22290b1239e 100644 --- a/src/commands/agent/types.ts +++ b/src/commands/agent/types.ts @@ -41,6 +41,8 @@ export type AgentCommandOpts = { thinkingOnce?: string; verbose?: string; json?: boolean; + /** Stream NDJSON events to stdout during the agent run. */ + streamJson?: boolean; timeout?: string; deliver?: boolean; /** Override delivery target (separate from session routing). */ diff --git a/src/gateway/call.ts b/src/gateway/call.ts index bb196883a52..b35011cf5be 100644 --- a/src/gateway/call.ts +++ b/src/gateway/call.ts @@ -41,6 +41,8 @@ export type CallGatewayOptions = { * Does not affect config loading; callers still control auth via opts.token/password/env/config. */ configPath?: string; + /** Optional callback for gateway events received while the request is in flight. */ + onEvent?: (evt: { event: string; payload?: unknown; seq?: number }) => void; }; export type GatewayConnectionDetails = { @@ -220,6 +222,9 @@ export async function callGateway>( deviceIdentity: loadOrCreateDeviceIdentity(), minProtocol: opts.minProtocol ?? PROTOCOL_VERSION, maxProtocol: opts.maxProtocol ?? PROTOCOL_VERSION, + onEvent: opts.onEvent + ? (evt) => opts.onEvent!({ event: evt.event, payload: evt.payload, seq: evt.seq }) + : undefined, onHelloOk: async () => { try { const result = await client.request(opts.method, opts.params, {