| 1 | import Link from "next/link"; |
| 2 | import { Icon } from "@/components/icon"; |
| 3 | import { PageHeader, Section } from "@/components/page-header"; |
| 4 | import { Status, type StatusTone } from "@/components/status-badge"; |
| 5 | import { getCachedRoadmap, type RoadmapItem } from "@/lib/roadmap-feed"; |
| 6 | import { getEnv } from "@/lib/kv"; |
| 7 | import { fill, getRoadmap, pickTextLocale } from "@/lib/i18n/dictionaries"; |
| 8 | import { buildPageMetadata } from "@/lib/page-meta"; |
| 9 | |
| 10 | export const revalidate = 1800; |
| 11 | |
| 12 | export async function generateMetadata({ params }: { params: Promise<{ locale: string }> }) { |
| 13 | const { locale } = await params; |
| 14 | const t = getRoadmap(locale); |
| 15 | return buildPageMetadata({ |
| 16 | path: "/roadmap", |
| 17 | locale, |
| 18 | title: t.metaTitle, |
| 19 | description: t.metaDescription, |
| 20 | }); |
| 21 | } |
| 22 | |
| 23 | const tracksEn = [ |
| 24 | { |
| 25 | title: "Shipped", |
| 26 | items: [ |
| 27 | { title: "Small searchable toolbox", note: "Every turn starts with read, write, edit, bash, agent, and tool_search. Specialized native, Web, MCP, plugin, memory, task, and verification tools are policy-filtered and searchable; activated schemas stay in a bounded per-conversation toolbox." }, |
| 28 | { title: "Sub-agent parallel execution", note: "agent; 64 concurrent sessions by default, configurable to 128, with bounded result handles" }, |
| 29 | { title: "RLM batched processing", note: "Persistent sandboxed Python REPL with 1–16 cheap parallel children for long-input analysis" }, |
| 30 | { title: "Three operating modes", note: "Plan (read-only authority), Work (execution), and Operate (Fleet/Workflow orchestration) keep one primitive vocabulary; Ask / Auto-Review / Full Access remains an orthogonal approval posture." }, |
| 31 | { title: "OS command sandbox", note: "Seatbelt on macOS when available; opt-in bubblewrap on Linux when installed. Windows currently reports no OS sandbox." }, |
| 32 | { title: "Durable sessions + tasks", note: "Save, resume, rollback; background task queue with replayable timelines" }, |
| 33 | { title: "Bidirectional MCP", note: "Consume tools from external servers; expose as server via `codewhale mcp`" }, |
| 34 | { title: "Skills + unified slash palette", note: "Skills load automatically from your Codewhale folder; /help, /mode, /status, /config, /trust, /feedback" }, |
| 35 | { title: "OpenRouter provider", note: "OpenRouter integration with 300+ models across dozens of providers" }, |
| 36 | { title: "OpenAI-compatible & local runtimes", note: "Generic `openai` route for any OpenAI-compatible gateway, plus vLLM, SGLang, and Ollama against your own localhost endpoints — no key required" }, |
| 37 | { title: "Multi-provider support", note: "Hot-swap between providers (DeepSeek, OpenAI, Anthropic, OpenRouter) per session" }, |
| 38 | { title: "Local web client", note: "In published releases: `codewhale web` is a loopback-only browser client over the Runtime API behind a one-time bootstrap session boundary; approvals and user input recover across page reloads" }, |
| 39 | { title: "Anonymous usage counting", note: "On by default and disclosed at first launch. Turning it off is a saved choice that later versions keep, and showing the notice never records any acceptance on your behalf. While on, sessions post aggregate session, feature, and error counts to a first-party endpoint whose full source is in the repository; it stores no IP, country, or geo data and keeps records for three months. Never conversations, code, prompts, files, file/repo/branch names, model content, credentials, or a per-turn/per-tool timeline. Turn it off with `codewhale config set telemetry false` or CODEWHALE_TELEMETRY=0." }, |
| 40 | ], |
| 41 | }, |
| 42 | { |
| 43 | title: "Underway", |
| 44 | items: [ |
| 45 | { title: "VS Code extension", note: "The repository ships an early local-runtime companion: terminal launch, health checks, read-only thread summaries, and restore-point browsing. Full chat and editor actions are not part of this slice." }, |
| 46 | { title: "Memory typed store", note: "The native store has shipped: Markdown is the durable source of truth, with a rebuildable SQLite FTS5 index over it. Graph-structured agent memory and multi-signal recall are still ahead" }, |
| 47 | { title: "Feishu / Lark bot", note: "First long-connection bridge over the runtime API shipped; richer chat features underway" }, |
| 48 | { title: "Chinese-market & i18n", note: "Locale-aware UI, platform refinements, region-specific search backends" }, |
| 49 | { title: "Hugging Face model discovery + Model Lab", note: "Browse, download, and manage models from Hugging Face Hub directly in the TUI" }, |
| 50 | ], |
| 51 | }, |
| 52 | { |
| 53 | title: "Considered", |
| 54 | items: [ |
| 55 | { title: "Workrooms", note: "Durable, addressable agent-work threads over the Runtime API and user surfaces" }, |
| 56 | { title: "Exa web-search backend", note: "Bundled alternative to the existing DDG + Bing path" }, |
| 57 | { title: "Homebrew core formula", note: "Tap exists; pursuing homebrew-core inclusion" }, |
| 58 | { title: "Native Windows installer", note: "MSI / WinGet; Scoop manifest already ships" }, |
| 59 | { title: "Unsloth / NeMo / Arcee fine-tune integration", note: "One-click fine-tuning workflows backed by Unsloth, NVIDIA NeMo, and Arcee toolkits" }, |
| 60 | ], |
| 61 | }, |
| 62 | { |
| 63 | title: "Ruled out", |
| 64 | items: [ |
| 65 | { title: "Silent or content-level telemetry", note: "Codewhale counts anonymous usage by default, discloses it at first launch, and keeps every opt-out. What stays ruled out: conversations, code, prompts, files, model content, credentials, per-turn or per-tool timelines, collection hidden from the user, and any third-party ad or analytics SDK in the runtime binary. A selected hosted provider still receives the context required for its model turn; loopback routes can keep inference local." }, |
| 66 | { title: "Mandatory hosted relay for local sessions", note: "The local runtime and bring-your-own-provider routes continue to work without sending sessions through a Codewhale service" }, |
| 67 | { title: "Required account for the local runtime", note: "Installing and running Codewhale locally requires no account" }, |
| 68 | { title: "Sponsored model promotion", note: "Model picker stays neutral — no paid placement" }, |
| 69 | { title: "Public share links for local sessions", note: "The retired share-link direction is not coming back; any future sharing design starts fresh." }, |
| 70 | ], |
| 71 | }, |
| 72 | { |
| 73 | title: "Open model platform · ideas, not started", |
| 74 | items: [ |
| 75 | { title: "Community model registry", note: "Discover, share, and rate community fine-tuned models with reproducible recipes" }, |
| 76 | { title: "One-click deploy", note: "Deploy any model to RunPod, Replicate, or your own infra with a single command" }, |
| 77 | { title: "Model evaluation dashboard", note: "Transparent, reproducible comparisons across providers, quantization levels, and hardware" }, |
| 78 | ], |
| 79 | }, |
| 80 | ]; |
| 81 | |
| 82 | const tracksZh = [ |
| 83 | { |
| 84 | title: "已完成", |
| 85 | items: [ |
| 86 | { title: "小型可搜索工具箱", note: "每轮以 read、write、edit、bash、agent 与 tool_search 开始。原生专用工具、Web、MCP、插件、记忆、任务和验证工具按策略过滤并可搜索;激活的 schema 保存在有界的会话工具箱中。" }, |
| 87 | { title: "子 Agent 并行执行", note: "agent;默认 64 个并发会话,可配置到 128 个,通过 var_handle 有界读取结果" }, |
| 88 | { title: "RLM 批量处理", note: "持久沙箱 Python REPL,支持 1–16 路廉价并行子调用,处理长文本分析" }, |
| 89 | { title: "三种运行模式", note: "Plan(只读权限)、Work(执行)与 Operate(Fleet / Workflow 编排)使用同一套基础工具名称;Ask、Auto-Review 与 Full Access 审批姿态独立设置。" }, |
| 90 | { title: "OS 命令沙箱", note: "macOS 在可用时使用 Seatbelt;Linux 在安装后可显式启用 bubblewrap。Windows 当前报告无 OS 沙箱。" }, |
| 91 | { title: "持久化会话 + 后台任务", note: "保存、恢复、回滚;后台任务队列,可回放时间线" }, |
| 92 | { title: "双向 MCP 协议", note: "消费外部服务器工具;通过 `codewhale mcp` 暴露为服务器" }, |
| 93 | { title: "技能 + 统一命令面板", note: "技能从 Codewhale 目录自动加载;/help、/mode、/status、/config、/trust、/feedback" }, |
| 94 | { title: "OpenRouter 提供商", note: "原生集成 OpenRouter,支持 300+ 模型,覆盖数十个提供商" }, |
| 95 | { title: "OpenAI 兼容与本地运行时", note: "通用 `openai` 路由可接入任意 OpenAI 兼容网关;vLLM、SGLang、Ollama 直连本地端点,无需密钥" }, |
| 96 | { title: "多提供商支持", note: "按会话动态切换提供商(DeepSeek、OpenAI、Anthropic、OpenRouter)" }, |
| 97 | { title: "本地 Web 客户端", note: "已在发布版本中提供:`codewhale web` 是基于 Runtime API 与一次性引导会话边界的回环地址浏览器客户端;审批与用户输入可在页面刷新后恢复" }, |
| 98 | { title: "匿名使用计数", note: "默认开启,并在首次启动时告知。关闭是会被后续版本保留的选择,显示告知绝不会代你记录任何同意。开启时,会话只会把聚合的会话、功能与错误计数发送到第一方端点,其完整源码在仓库中;它不存储 IP、国家或地理位置,记录保留三个月。永远不收集对话、代码、prompt、文件、文件/仓库/分支名、模型内容、凭据,也不发送逐轮或逐工具时间线。用 `codewhale config set telemetry false` 或 CODEWHALE_TELEMETRY=0 关闭。" }, |
| 99 | ], |
| 100 | }, |
| 101 | { |
| 102 | title: "进行中", |
| 103 | items: [ |
| 104 | { title: "VS Code 扩展", note: "仓库已提供早期本地 Runtime 配套扩展:终端启动、健康检查、只读线程摘要和还原点浏览;完整聊天与编辑器操作尚未包含在此版本中。" }, |
| 105 | { title: "记忆类型化存储", note: "原生存储已发布:以 Markdown 作为持久化事实来源,并在其上建立可重建的 SQLite FTS5 索引。图结构 Agent 记忆与多信号召回仍在规划中" }, |
| 106 | { title: "飞书 / Lark 机器人", note: "基于 runtime API 的长连接桥接已发布首版;更丰富的对话能力进行中" }, |
| 107 | { title: "中国市场与国际化改进", note: "本地化 UI、平台优化、区域搜索引擎" }, |
| 108 | { title: "Hugging Face 模型发现 + 模型实验室", note: "在 TUI 中直接浏览、下载和管理 Hugging Face Hub 上的模型" }, |
| 109 | ], |
| 110 | }, |
| 111 | { |
| 112 | title: "考虑中", |
| 113 | items: [ |
| 114 | { title: "Workrooms 工作间", note: "基于 Runtime API 与用户界面的持久、可寻址 Agent 工作线程" }, |
| 115 | { title: "Exa 网页搜索后端", note: "内建替代 DDG + Bing 的搜索路由" }, |
| 116 | { title: "Homebrew 核心仓库", note: "Tap 已有;正在争取进入 homebrew-core" }, |
| 117 | { title: "Windows 原生安装器", note: "MSI / WinGet;Scoop 清单已发布" }, |
| 118 | { title: "Unsloth / NeMo / Arcee 微调集成", note: "一键微调工作流,由 Unsloth、NVIDIA NeMo 和 Arcee 工具链驱动" }, |
| 119 | ], |
| 120 | }, |
| 121 | { |
| 122 | title: "暂不考虑", |
| 123 | items: [ |
| 124 | { title: "静默或内容级遥测", note: "Codewhale 默认开启匿名使用计数,并在首次启动时告知;所有关闭选项始终有效。仍被排除在外的是:对话、代码、prompt、文件、模型内容、凭据、逐轮或逐工具时间线、对用户隐藏的收集,以及运行时二进制中的任何第三方广告或分析 SDK。选用托管 provider 时仍会发送本轮所需上下文,回环地址路由可让推理保持本地。" }, |
| 125 | { title: "本地会话强制经过托管中继", note: "本地 Runtime 与自带提供商路由继续工作,无需把会话发送到 Codewhale 服务" }, |
| 126 | { title: "本地 Runtime 强制注册账户", note: "本地安装和运行 Codewhale 不需要账户" }, |
| 127 | { title: "赞助商模型推广", note: "模型选择器保持中立——无付费推荐位" }, |
| 128 | { title: "本地会话的公开分享链接", note: "已停用的分享链接方向不会恢复;未来的分享设计将重新开始。" }, |
| 129 | ], |
| 130 | }, |
| 131 | { |
| 132 | title: "开放模型平台 · 构想,尚未开始", |
| 133 | items: [ |
| 134 | { title: "社区模型注册中心", note: "发现、分享和评价社区微调模型,附带可复现的配方" }, |
| 135 | { title: "一键部署", note: "一条命令将任意模型部署到 RunPod、Replicate 或自有基础设施" }, |
| 136 | { title: "模型评测面板", note: "跨提供商、量化级别和硬件的透明、可复现对比" }, |
| 137 | ], |
| 138 | }, |
| 139 | ]; |
| 140 | |
| 141 | // Track order is shared by both languages: shipped, underway, considered, |
| 142 | // ruled out, and the open model platform direction (not started). |
| 143 | const TRACK_TONES: StatusTone[] = ["ready", "accent", "idle", "idle", "idle"]; |
| 144 | |
| 145 | const roadmapText = (text: string) => |
| 146 | text.replace(/^>\s*/, "").replaceAll("**", "").replaceAll("CodeWhale", "Codewhale"); |
| 147 | |
| 148 | export default async function RoadmapPage({ params }: { params: Promise<{ locale: string }> }) { |
| 149 | const { locale } = await params; |
| 150 | const t = getRoadmap(locale); |
| 151 | const baseTracks = { en: tracksEn, zh: tracksZh }[pickTextLocale(locale)]; |
| 152 | |
| 153 | // Live feed: shipped from GitHub Releases; underway/considered/ruled-out from issue labels. |
| 154 | // Per-category fallback to the static items so unlabeled categories stay populated. |
| 155 | let tracks = baseTracks; |
| 156 | try { |
| 157 | const env = await getEnv(); |
| 158 | const feed = await getCachedRoadmap(env.CURATED_KV, env.GITHUB_TOKEN); |
| 159 | if (feed) { |
| 160 | const liveByCategory: Record<string, RoadmapItem[]> = { |
| 161 | Shipped: feed.shipped, |
| 162 | Underway: feed.underway, |
| 163 | Considered: feed.considered, |
| 164 | "Ruled out": feed.ruledOut, |
| 165 | 已完成: feed.shipped, |
| 166 | 进行中: feed.underway, |
| 167 | 考虑中: feed.considered, |
| 168 | 暂不考虑: feed.ruledOut, |
| 169 | }; |
| 170 | tracks = baseTracks.map((t) => { |
| 171 | const live = liveByCategory[t.title]; |
| 172 | if (live && live.length > 0) { |
| 173 | return { ...t, items: live.map((it) => ({ title: it.title, note: it.note })) }; |
| 174 | } |
| 175 | return t; |
| 176 | }); |
| 177 | } |
| 178 | } catch { |
| 179 | /* keep static fallback */ |
| 180 | } |
| 181 | |
| 182 | const links = [ |
| 183 | { title: "Issues", detail: t.issuesDetail, href: "https://github.com/codewhale-hq/CodeWhale/issues" }, |
| 184 | { title: "Discussions", detail: t.discussionsDetail, href: "https://github.com/codewhale-hq/CodeWhale/discussions/new?category=ideas" }, |
| 185 | { title: "Pull requests", detail: t.pullsDetail, href: "https://github.com/codewhale-hq/CodeWhale/pulls" }, |
| 186 | ]; |
| 187 | |
| 188 | return ( |
| 189 | <> |
| 190 | <PageHeader kicker={t.eyebrow} title={t.title} lede={t.introduction} pose="browse" /> |
| 191 | |
| 192 | <div className="page-body"> |
| 193 | <Section |
| 194 | id="roadmap-status" |
| 195 | title={t.sectionTitle} |
| 196 | link={ |
| 197 | <Link href="https://github.com/codewhale-hq/CodeWhale/issues" className="section-link"> |
| 198 | {t.browseIssues} |
| 199 | <Icon name="external" className="icon" /> |
| 200 | </Link> |
| 201 | } |
| 202 | > |
| 203 | <div className="roadmap-tracks"> |
| 204 | {tracks.map((track, index) => ( |
| 205 | <section key={track.title} className="roadmap-track" aria-labelledby={`track-${index}`}> |
| 206 | <div className="roadmap-track-head"> |
| 207 | <h3 id={`track-${index}`}> |
| 208 | <Status tone={TRACK_TONES[index] ?? "idle"}>{track.title}</Status> |
| 209 | </h3> |
| 210 | <span className="page-meta tabular">{fill(track.items.length === 1 ? t.trackCountOne : t.trackCount, { count: track.items.length })}</span> |
| 211 | </div> |
| 212 | <ul className="group-card" role="list"> |
| 213 | {track.items.map((item) => ( |
| 214 | <li key={`${item.title}-${item.note}`} className="group-row roadmap-item"> |
| 215 | <p className="roadmap-item-title">{roadmapText(item.title)}</p> |
| 216 | <p className="roadmap-item-note">{roadmapText(item.note)}</p> |
| 217 | </li> |
| 218 | ))} |
| 219 | </ul> |
| 220 | </section> |
| 221 | ))} |
| 222 | </div> |
| 223 | </Section> |
| 224 | |
| 225 | <Section id="roadmap-contribute" title={t.contributeTitle} scope={t.contributeBody}> |
| 226 | <ul className="dir-list dir-list-card" role="list"> |
| 227 | {links.map((link) => ( |
| 228 | <li key={link.title}> |
| 229 | <Link href={link.href} className="dir-row"> |
| 230 | <span className="dir-mark" aria-hidden="true"> |
| 231 | <Icon name={link.title === "Pull requests" ? "git-pull-request" : link.title === "Discussions" ? "message" : "alert"} /> |
| 232 | </span> |
| 233 | <span className="dir-text"> |
| 234 | <span className="dir-title">{link.title}</span> |
| 235 | <span className="dir-purpose">{link.detail}</span> |
| 236 | </span> |
| 237 | <span className="dir-action" aria-hidden="true"><Icon name="external" /></span> |
| 238 | </Link> |
| 239 | </li> |
| 240 | ))} |
| 241 | </ul> |
| 242 | </Section> |
| 243 | </div> |
| 244 | </> |
| 245 | ); |
| 246 | } |
| 247 |