Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
15 changes: 8 additions & 7 deletions AGENTS.md
Original file line number Diff line number Diff line change
Expand Up @@ -2,12 +2,12 @@

修改 CodeDock 代码前,先阅读 [`docs/architecture.md`](docs/architecture.md)。该文档是当前目录归属和模块边界的依据。

Agent Loop 已闭环:用户发文本、装上下文、调模型、产出文字或 Tool、事件落库并由 SSE 消费。默认注册 `ping`、记忆工具、编码八工具与 `plan_*`。Git 用户操作走 HTTP + `pkg/git`,不经过 Agent Tool。仓库根是当前会话冻结的工作目录(请求带 `session_id`);未带会话才回落 `GIT_REPO` / cwd。前端 Git 在 `packages/core/git`、`packages/views/git` 与 `apps/web` 的 `/git`,不扩 `AgentClient`。Codex 对话复用 Agent 页,新建会话时选择模式;HTTP 走独立 `/codex`,前端用 `CodexClient`,不扩 `AgentClient`。
Agent Loop 已闭环:用户发文本、装上下文、调模型、产出文字或 Tool、事件落库并由 SSE 消费。默认注册 `ping`、记忆工具、编码八工具与 `plan_*`。Git 用户操作走 HTTP + `pkg/git`,不经过 Agent Tool。仓库根是当前会话冻结的工作目录(请求带 `session_id`);未带会话才回落 `GIT_REPO` / cwd。前端 Git 在 `packages/core/git`、`packages/views/git` 与 `apps/web` 的 `/git`,不扩 `AgentClient`。Codex 与 Claude Code 对话复用 Agent 页,新建会话时选择模式;HTTP 分别走独立 `/codex`、`/claude`,前端用 `CodexClient` / `ClaudeClient`,不扩 `AgentClient`。会话 / 实录 / 配置从本机 CLI 读,不落库。

## 目录放置规则

- 服务启动、配置读取、Router 和依赖装配放在 `server/cmd/server`。
- 大部分 HTTP 逻辑放在 `server/internal/handler`:Session / Message / Usage / Approval 的 CRUD,SSE,Run 的 Start / Continue / Cancel,审批裁决,用户侧记忆查看/删除,以及 Git(直接调 `pkg/git`)。创建 Session 时在本包冻结 `workspace_id`。不 import `internal/agent/tools`。新对话选目录由 web 弹出系统目录选择框,不走 Agent Tool。Codex 的独立 `/codex` HTTP 放在 `server/internal/handler/codex`。
- 大部分 HTTP 逻辑放在 `server/internal/handler`:Session / Message / Usage / Approval 的 CRUD,SSE,Run 的 Start / Continue / Cancel,审批裁决,用户侧记忆查看/删除,以及 Git(直接调 `pkg/git`)。创建 Session 时在本包冻结 `workspace_id`。不 import `internal/agent/tools`。新对话选目录由 web 弹出系统目录选择框,不走 Agent Tool。Codex 的独立 `/codex` HTTP 放在 `server/internal/handler/codex`。Claude Code HTTP 仍在 `server/internal/handler`,直接调 `pkg/claude`。
- Agent 运行时编排和 sqlc 持久化放在 `server/internal/agent`。
- 本机 Codex app-server 生命周期、内存排队/问票/SSE 放在 `server/internal/codex`。不新增 Codex 业务表;凡官方 API 能读到的都不入库。
- Markdown 记忆(热层目录+专题)与 context message 索引(冷层按工作区 FTS)放在 `server/internal/agent/memory`;不放 `pkg/memory`。memory 不 import 父包 `internal/agent`,不定义 Tool。
Expand All @@ -16,14 +16,15 @@ Agent Loop 已闭环:用户发文本、装上下文、调模型、产出文字
- 插件 SDK 与 proto 放在 `server/pkg/plugin`;宿主放在 `server/internal/pluginhost`。两者都不进 `pkg/agent`,也不知道主循环内部状态机。可装载的插件放在仓根 `plugin/`(子目录名即插件名)。`plugin/example` 是作者拷贝模板:不订阅、不改正文、不换向、不登记方法。不进 `pkg/plugin`。插件共享参数用 `PluginContext`,不进模型、不复用 Hidden。
- Git CLI 操作放在 `server/pkg/git`:无状态,不写产品流程;Handler 直接调用。不进 `pkg/agent`。
- Codex 协议与领域类型放在 `server/pkg/codex`:看板的子模块,JSONL 客户端给 `internal/codex` 调用;不查库、不 spawn CLI。不进 `pkg/agent`。
- Claude Code 对接放在 `server/pkg/claude`:无状态,不落库;会话 / 实录 / 模型 / 权限档从本机 Claude 读。Handler 直接调用。不进 `pkg/agent`,不走本地对话的工具 / 记忆 / 压缩。
- 进程内事件总线放在 `server/internal/events`。
- 数据库入口和 sqlc 生成代码放在 `server/pkg/db`。
- 数据库结构演进放在 `server/migrations`。
- 运行时产生的文件(SQLite 等)放在仓根 `data/`,不要写进 `server/`。该目录 gitignore。
- 无头业务放在 `packages/core`(`@codedock/core`):按业务域拆(现有 `chat/`、`git/`、`codex/`),文件直接在域目录下,不要 `src/`。不依赖 React、Next、DOM、`process.env`。`baseUrl` / `userId` 由调用方注入。Git 用独立 `GitClient`。Codex 用独立 `CodexClient`,不扩 `AgentClient`。
- 无头业务放在 `packages/core`(`@codedock/core`):按业务域拆(现有 `chat/`、`git/`、`codex/`、`claude/`),文件直接在域目录下,不要 `src/`。不依赖 React、Next、DOM、`process.env`。`baseUrl` / `userId` 由调用方注入。Git 用独立 `GitClient`。Codex 用独立 `CodexClient`,Claude 用独立 `ClaudeClient`,不扩 `AgentClient`。
- 无业务 UI 放在 `packages/ui`(`@codedock/ui`):`components/`、`lib/`、`styles/`,不要 `src/`,不按业务域拆。不依赖 core,不知道 Session / Run / TimelineItem。
- 组合层放在 `packages/views`(`@codedock/views`):按业务域拆,与 core 对齐(现有 `chat/`、`git/`、`codex/`)。包根 `provider.tsx` 注入 Agent client;Git 用 `views/git` 的 `GitProvider`;Codex 用 `views/codex` 的 `CodexProvider`,由 `ChatPage` 在 Codex 模式下组合,不单独做 Codex 页。不 import `next/*`;导航用回调。不要 `src/`,不预建空业务域。
- Web 路由和平台装配放在 `apps/web`:读 `NEXT_PUBLIC_*`、创建 `AgentClient` / `GitClient` / `CodexClient`、包对应 Provider、`router.push`。`/git` 放在 `(chat)` 组外。Codex 不单独路由,走 `/` 与 `/s/c/:id`。开发态切页顶栏只放 web。不解析 SSE。
- 组合层放在 `packages/views`(`@codedock/views`):按业务域拆,与 core 对齐(现有 `chat/`、`git/`、`codex/`、`claude/`)。包根 `provider.tsx` 注入 Agent client;Git 用 `views/git` 的 `GitProvider`;Codex 用 `views/codex` 的 `CodexProvider`,Claude 用 `views/claude` 的 `ClaudeProvider`,由 `ChatPage` 按模式组合,不单独做 Codex / Claude 页。不 import `next/*`;导航用回调。不要 `src/`,不预建空业务域。
- Web 路由和平台装配放在 `apps/web`:读 `NEXT_PUBLIC_*`、创建 `AgentClient` / `GitClient` / `CodexClient` / `ClaudeClient`、包对应 Provider、`router.push`。`/git` 放在 `(chat)` 组外。Codex 走 `/` 与 `/s/c/:id`,Claude 走 `/` 与 `/s/claude/:id`。开发态切页顶栏只放 web。不解析 SSE。
- 依赖方向:`apps/web` → `packages/views` → `packages/core`;`packages/views` → `packages/ui`。`ui` 不依赖 `core`。未来 CLI 只依赖 `core`。
- 不要创建 `server/pkg/ai`。大模型调用属于 `pkg/agent`。

Expand All @@ -39,9 +40,9 @@ Agent Loop 已闭环:用户发文本、装上下文、调模型、产出文字
- Agent 契约不得依赖 React、UI 包或路由框架。
- 流式事件是通知,不是真实数据源;重连时应按 `event_seq` 回放。
- 不要预先创建 Issue、Task、Review、Workspace 或其他具体业务域目录。
- 不要把产品工作流放入 `server/pkg`。
- 不要把产品工作流放入 `server/pkg`。Git、Codex 与 Claude Code 是本机 CLI 域:不落库,Handler 直接调 `pkg/git` / `pkg/codex` / `pkg/claude`。
- 前端三层不得反依赖:`core` 不依赖 React / Next / DOM / `process.env`;`ui` 不依赖 `core`;`views` 不 import `next/*`;`apps/web` 只做路由与平台装配。
- 前端按业务域拆模块,不要 `src/`:`core` / `views` 用同名域目录(现有 `chat` / `git` / `codex`);`ui` 只用 `components` / `lib` / `styles`。新业务再建目录,不预建空文件夹。
- 前端按业务域拆模块,不要 `src/`:`core` / `views` 用同名域目录(现有 `chat` / `git` / `codex` / `claude`);`ui` 只用 `components` / `lib` / `styles`。新业务再建目录,不预建空文件夹。

## 注释规则

Expand Down
4 changes: 4 additions & 0 deletions apps/web/app/(chat)/s/claude/[sessionId]/page.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
// ClaudeSessionPage 只占 /s/claude/:id,内容由 ChatHost 按路径装配。
export default function ClaudeSessionPage() {
return null;
}
22 changes: 20 additions & 2 deletions apps/web/app/chat-host.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ import { usePathname, useRouter } from "next/navigation";

import { rememberSession } from "@/lib/session";

// ChatHost 解析对话路径并把导航收成回调;views 不知道具体 URL。
export function ChatHost() {
const pathname = usePathname();
const router = useRouter();
Expand All @@ -19,8 +20,9 @@ export function ChatHost() {
engine={parsed.engine}
brandSrc="/brand/codedock-berth-mark.svg"
codexIconSrc="/brand/codex-app-icon.png"
claudeIconSrc="/brand/claude-app-icon.svg"
onOpenSession={(id, engine = parsed.engine ?? "agent") => {
const path = engine === "codex" ? `/s/c/${id}` : `/s/${id}`;
const path = pathFor(id, engine);
if (pathname !== path) {
router.push(path);
}
Expand All @@ -34,14 +36,30 @@ export function ChatHost() {
);
}

// parseChatPath 先认 /s/claude/:id,再认 /s/c/:id,避免吃掉 Claude 前缀。
function parseChatPath(pathname: string): { sessionId?: string; engine?: SessionEngine } {
const claude = pathname.match(/^\/s\/claude\/([^/]+)/);
if (claude?.[1]) {
return { sessionId: decodeURIComponent(claude[1]), engine: "claude" };
}
const codex = pathname.match(/^\/s\/c\/([^/]+)/);
if (codex?.[1]) {
return { sessionId: decodeURIComponent(codex[1]), engine: "codex" };
}
const agent = pathname.match(/^\/s\/([^/]+)/);
if (agent?.[1] && agent[1] !== "c") {
if (agent?.[1] && agent[1] !== "c" && agent[1] !== "claude") {
return { sessionId: decodeURIComponent(agent[1]), engine: "agent" };
}
return {};
}

// pathFor 由 web 决定三套会话路径。
function pathFor(id: string, engine: SessionEngine): string {
if (engine === "claude") {
return `/s/claude/${id}`;
}
if (engine === "codex") {
return `/s/c/${id}`;
}
return `/s/${id}`;
}
8 changes: 7 additions & 1 deletion apps/web/app/providers.tsx
Original file line number Diff line number Diff line change
@@ -1,7 +1,9 @@
"use client";

import { AgentClient } from "@codedock/core/chat";
import { ClaudeClient } from "@codedock/core/claude";
import { CodexClient } from "@codedock/core/codex";
import { ClaudeProvider } from "@codedock/views/claude";
import { CodexProvider } from "@codedock/views/codex";
import { AgentProvider } from "@codedock/views";
import { useMemo, type ReactNode } from "react";
Expand All @@ -10,17 +12,21 @@ import { apiBase, defaultUserId } from "@/lib/env";
import { pickDirectory } from "@/lib/pick-directory";
import { pickFiles } from "@/lib/pick-files";

// Providers 装配本机 Web 的 Agent / Codex / Claude 客户端,views 不读环境变量。
export function Providers({ children }: { children: ReactNode }) {
const client = useMemo(() => new AgentClient({ baseUrl: apiBase }), []);
const codex = useMemo(() => new CodexClient({ baseUrl: apiBase }), []);
const claude = useMemo(() => new ClaudeClient({ baseUrl: apiBase, userId: defaultUserId }), []);
return (
<AgentProvider
client={client}
userId={defaultUserId}
pickDirectory={pickDirectory}
pickFiles={pickFiles}
>
<CodexProvider client={codex}>{children}</CodexProvider>
<CodexProvider client={codex}>
<ClaudeProvider client={claude}>{children}</ClaudeProvider>
</CodexProvider>
</AgentProvider>
);
}
12 changes: 12 additions & 0 deletions apps/web/public/brand/claude-app-icon.svg
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Loading