Skip to content
Merged

Dev #16

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
Binary file removed .agent-sessions/sessions.db
Binary file not shown.
15 changes: 15 additions & 0 deletions .dockerignore
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
.git
.github
.gitignore
.agent-sessions
.env
.env.local
.env.*
node_modules
**/node_modules
packages/*/dist
*.tsbuildinfo
bundle.*
docs
tests
.DS_Store
6 changes: 6 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -26,3 +26,9 @@ bundle.*
docs/*
!docs/index.html
.DS_Store # macOS
.agent-sessions/*
packages/client/dist/*
packages/server/dist/*
packages/agent-core/dist/*
sessions.db
packages/session-sqlite/dist/*
30 changes: 30 additions & 0 deletions Dockerfile
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
# 使用 Node 24 提供 node:sqlite 和 npm,Bun 负责按 bun.lock 安装依赖
FROM node:24-bookworm-slim

# coding agent 会在挂载的工作区执行命令,补齐常用工具
RUN apt-get update \
&& apt-get install -y --no-install-recommends bash git ripgrep ca-certificates \
&& rm -rf /var/lib/apt/lists/*

# 与 packageManager 保持一致,避免 bun.lock 被其他版本改写
RUN npm install -g bun@1.3.11

WORKDIR /app

# 先复制清单文件,依赖层可以复用 Docker 缓存
COPY package.json bun.lock bunfig.toml tsconfig.json ./
COPY packages/agent-core/package.json packages/agent-core/
COPY packages/client/package.json packages/client/
COPY packages/session-sqlite/package.json packages/session-sqlite/

RUN bun install --frozen-lockfile

COPY source source
COPY packages/agent-core packages/agent-core
COPY packages/session-sqlite packages/session-sqlite

# 默认把挂载的用户目录作为工作区,会话和工具执行都落在里面
WORKDIR /workspace

# 入口用 tsx 加载 TypeScript,tsconfig 固定指向 /app,避免受工作区影响
CMD ["node", "/app/node_modules/tsx/dist/cli.mjs", "--tsconfig", "/app/tsconfig.json", "/app/source/app.tsx"]
40 changes: 37 additions & 3 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,7 @@ call-code 是一个本地运行的终端编程 Agent(CLI coding agent),基
- 本地记忆:短期记忆按任务保存,长期记忆按主题沉淀,仅在进程内使用,不写入本地 JSON。
- 上下文预算:运行时基于 token 估算对历史消息做裁剪,减少超出模型上下文的风险。
- 会话持久化:基于 Node 内置 `node:sqlite` 保存会话、条目、泳道、分支、记录、统计、事实和租约,默认写入 `.agent-sessions/sessions.db`。
- 会话客户端:`packages/client` 提供 React + Vite 会话界面,静态展示已停止,后续用于实时对话展示
- 会话客户端:`packages/client` 提供 React + Vite 会话界面,通过 WebSocket 实时展示会话数据

## 架构

Expand Down Expand Up @@ -63,6 +63,7 @@ OpenAI-compatible LLM 模型层
```text
source/
app.tsx # Ink CLI 入口与交互界面
web-server.ts # Web 会话面板服务入口
packages/
agent-core/ # 核心 agent、上下文、记忆、工具与协议实现
src/
Expand All @@ -80,8 +81,8 @@ packages/
utils/ # shell 与文本截断等工具
types/ # 领域类型
utils/ # JSON、日志工具
web/ # 会话数据导出
client/ # TypeScript + React 会话界面客户端
web/ # 会话导出与 WebSocket 服务
client/ # TypeScript + React 会话界面客户端(WebSocket 数据)
session-sqlite/ # 基于 node:sqlite 的会话历史与运行状态存储
tests/ # 项目统一单元测试
vitest.config.ts # Vitest 测试配置
Expand Down Expand Up @@ -121,6 +122,7 @@ bun dev
| `AGENT_DESKTOP_DIR` | 可选,覆盖桌面目录路径,便于测试或自定义工作环境。 |
| `SESSION_DB_PATH` | 可选,SQLite 会话库文件路径,默认 `.agent-sessions/sessions.db`。 |
| `CALL_CODE_WEB_DATA` | 可选,CLI 内 `/export` 的输出路径,默认 `packages/client/public/data.json`。 |
| `CALL_CODE_WEB_PORT` | 可选,Web 会话面板监听端口,默认 4173。 |

## CLI 命令与快捷键

Expand Down Expand Up @@ -169,8 +171,40 @@ bun run export:web

# 构建会话客户端(本地预览用)
bun run build:client

# 启动 Web 会话面板(静态托管客户端 + WebSocket 数据服务)
bun run web:serve
```

## Web 会话面板

Web 会话面板由 `source/web-server.ts` 启动:它读取 `SESSION_DB_PATH` 指向的会话库,静态托管 `packages/client/dist`,并在 `/ws` 提供 `sessions.list` / `sessions.snapshot` 消息。客户端默认连接同源 `/ws`,并周期性刷新会话快照;也可以用 `?ws=<url>` 覆盖服务地址,用 `?session=<id>` 直达某个会话。

```bash
bun run build:client
bun run web:serve
```

然后打开 `http://127.0.0.1:4173`。开发模式下先启动 `web:serve`,再运行 `bun run dev:client`,Vite 会把 `/ws` 代理到会话服务。

## Docker 镜像

构建镜像:

```bash
docker build -t call-code .
```

以当前目录作为工作区运行,容器内工作目录固定为 `/workspace`,会话库会写入 `/workspace/.agent-sessions`:

```bash
docker run --rm -it \
-v "$PWD":/workspace \
call-code
```

API 配置优先从挂载目录下的 `.env.local` 读取,也可以用 `-e OPENAI_API_KEY=... -e OPENAI_MODEL=...` 传入。

## 测试说明

测试文件统一放在项目根目录的 `tests/` 目录下,根目录的 `vitest.config.ts` 会统一收集并运行。
Expand Down
21 changes: 21 additions & 0 deletions bun.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

5 changes: 4 additions & 1 deletion package.json
Original file line number Diff line number Diff line change
Expand Up @@ -14,10 +14,13 @@
"typecheck:client": "tsc -p packages/client/tsconfig.json --noEmit",
"test": "vitest run --reporter verbose",
"build:agent-core": "tsc -p packages/agent-core/tsconfig.json",
"build:session-sqlite": "tsc -p packages/session-sqlite/tsconfig.json",
"build:server": "bun run build:session-sqlite && bun run build:agent-core && bun run --cwd packages/server build",
"export:web": "tsx tests/export-web.ts",
"dev:client": "bun run --cwd packages/client dev",
"build:client": "bun run --cwd packages/client build",
"preview:client": "bun run --cwd packages/client preview"
"preview:client": "bun run --cwd packages/client preview",
"web:serve": "tsx source/web-server.ts"
},
"dependencies": {
"dotenv": "^17.4.2",
Expand Down
4 changes: 3 additions & 1 deletion packages/agent-core/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -13,9 +13,11 @@
"dotenv": "^17.4.2",
"openai": "^6.34.0",
"tesseract.js": "^7.0.0",
"tiktoken": "^1.0.20"
"tiktoken": "^1.0.20",
"ws": "^8.21.3"
},
"devDependencies": {
"@types/ws": "^8.18.1",
"typescript": "^6.0.3"
}
}
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
import type { ContextMessage } from '../context/context-types';
import type { EntryLike, SessionStoreLike } from '../session/store-types';
import type { SummarizeFn } from './compaction';
import type { ContextMessage } from '../context/context-types.js';
import type { EntryLike, SessionStoreLike } from '../session/store-types.js';
import type { SummarizeFn } from './compaction.js';
import {
computeFileLists,
createFileOps,
Expand All @@ -9,7 +9,7 @@ import {
formatFileOperations,
serializeConversation,
type FileOperations,
} from './utils';
} from './utils.js';

export interface BranchSummaryEntryPayload {
kind: 'branch_summary';
Expand Down
6 changes: 3 additions & 3 deletions packages/agent-core/src/harness/compaction/compaction.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
import type { ContextMessage } from '../context/context-types';
import type { EntryLike, SessionStoreLike } from '../session/store-types';
import type { ContextMessage } from '../context/context-types.js';
import type { EntryLike, SessionStoreLike } from '../session/store-types.js';
import {
computeFileLists,
createFileOps,
Expand All @@ -9,7 +9,7 @@ import {
formatFileOperations,
serializeConversation,
type FileOperations,
} from './utils';
} from './utils.js';

export interface CompactionSettings {
enabled: boolean;
Expand Down
6 changes: 3 additions & 3 deletions packages/agent-core/src/harness/compaction/index.ts
Original file line number Diff line number Diff line change
@@ -1,3 +1,3 @@
export * from './branch-summarization';
export * from './compaction';
export * from './utils';
export * from './branch-summarization.js';
export * from './compaction.js';
export * from './utils.js';
4 changes: 2 additions & 2 deletions packages/agent-core/src/harness/compaction/utils.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
import type { ContextMessage } from '../context/context-types';
import type { EntryLike } from '../session/store-types';
import type { ContextMessage } from '../context/context-types.js';
import type { EntryLike } from '../session/store-types.js';

/** 摘要阶段累积的文件读写信息,后续追加到摘要里让模型保留文件上下文。 */
export interface FileOperations {
Expand Down
6 changes: 3 additions & 3 deletions packages/agent-core/src/harness/context/builder.ts
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
export { ContextBuilder } from './context-builder';
export { ContextBuilder } from './context-builder.js';
export type {
ContextMessage,
MessageRole,
RuntimeContext,
} from './context-types';
export { buildRuntimeContext } from './runtime-context';
} from './context-types.js';
export { buildRuntimeContext } from './runtime-context.js';
2 changes: 1 addition & 1 deletion packages/agent-core/src/harness/context/context-builder.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
import { get_encoding, type Tiktoken } from 'tiktoken';
import type { ContextMessage } from './context-types';
import type { ContextMessage } from './context-types.js';

export class ContextBuilder {
private readonly encoder: Tiktoken;
Expand Down
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import type { ContextMessage } from './context-types';
import type { ContextMessage } from './context-types.js';

export interface HistorySummary {
summary: string;
Expand Down
10 changes: 5 additions & 5 deletions packages/agent-core/src/harness/context/runtime-context.ts
Original file line number Diff line number Diff line change
@@ -1,11 +1,11 @@
import type { TaskState } from '@agent-core/harness/core/state';
import { createTaskContext } from './task-context';
import { summarizeHistory } from './context-summarizer';
import { ContextBuilder } from './context-builder';
import type { TaskState } from '../core/state.js';
import { createTaskContext } from './task-context.js';
import { summarizeHistory } from './context-summarizer.js';
import { ContextBuilder } from './context-builder.js';
import type {
ContextMessage,
RuntimeContext,
} from './context-types';
} from './context-types.js';

export interface BuildRuntimeContextInput {
system: string;
Expand Down
2 changes: 1 addition & 1 deletion packages/agent-core/src/harness/context/task-context.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import type { TaskState } from '@agent-core/harness/core/state';
import type { TaskState } from '../core/state.js';

export interface TaskContext {
id: string;
Expand Down
6 changes: 3 additions & 3 deletions packages/agent-core/src/harness/core/agent.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
import { runLoop } from '@agent-core/harness/runtime/run-loop';
import type { StreamHandlers } from '@agent-core/harness/core/llm';
import { createTaskState, type AgentMode } from '@agent-core/harness/core/state';
import { runLoop } from '../runtime/run-loop.js';
import type { StreamHandlers } from './llm.js';
import { createTaskState, type AgentMode } from './state.js';

export interface AgentOptions {
mode?: AgentMode;
Expand Down
2 changes: 1 addition & 1 deletion packages/agent-core/src/harness/memory/memory-retriever.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import { memoryStore } from './memory-store';
import { memoryStore } from './memory-store.js';

export interface RetrievedMemory {
longFacts: string[];
Expand Down
2 changes: 1 addition & 1 deletion packages/agent-core/src/harness/memory/memory-store.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@ import { randomUUID } from 'node:crypto';
import type {
LongMemoryItem,
MemorySnapshot,
} from './memory-schema';
} from './memory-schema.js';

export class MemoryStore {
private readonly longMemory: LongMemoryItem[] = [];
Expand Down
6 changes: 3 additions & 3 deletions packages/agent-core/src/harness/memory/memory-writer.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
import type { TaskState } from '@core/state';
import type { ContextMessage } from '../context/context-types';
import { memoryStore } from './memory-store';
import type { TaskState } from '../core/state.js';
import type { ContextMessage } from '../context/context-types.js';
import { memoryStore } from './memory-store.js';

const countStableMentions = (messages: ContextMessage[], text: string): number =>
messages.reduce((count, item) => {
Expand Down
2 changes: 1 addition & 1 deletion packages/agent-core/src/harness/prompt/modes.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import type { AgentMode } from '@agent-core/harness/core/state';
import type { AgentMode } from '../core/state.js';

const modeSystemPrompts: Record<AgentMode, string> = {
plan: `
Expand Down
10 changes: 5 additions & 5 deletions packages/agent-core/src/harness/protocol/index.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
export type { ToolCallAction, FinalAction, AgentAction } from '@agent-core/harness/protocol/action';
export { isToolCallAction, isFinalAction, isAgentAction } from '@agent-core/harness/protocol/action';
export type { ToolResultObservation } from '@agent-core/harness/protocol/observation';
export { createToolResultObservation } from '@agent-core/harness/protocol/observation';
export { parseAgentResponse, shouldContinueLoop, extractFinalText } from '@agent-core/harness/protocol/parser';
export type { ToolCallAction, FinalAction, AgentAction } from './action.js';
export { isToolCallAction, isFinalAction, isAgentAction } from './action.js';
export type { ToolResultObservation } from './observation.js';
export { createToolResultObservation } from './observation.js';
export { parseAgentResponse, shouldContinueLoop, extractFinalText } from './parser.js';
2 changes: 1 addition & 1 deletion packages/agent-core/src/harness/protocol/parser.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import { isAgentAction, isFinalAction, isToolCallAction, type AgentAction } from '@agent-core/harness/protocol/action';
import { isAgentAction, isFinalAction, isToolCallAction, type AgentAction } from './action.js';

export const parseAgentResponse = (response: string): AgentAction | null => {
try {
Expand Down
6 changes: 3 additions & 3 deletions packages/agent-core/src/harness/runtime/index.ts
Original file line number Diff line number Diff line change
@@ -1,3 +1,3 @@
export * from './run-loop';
export * from './session-runtime';
export * from './tool-runtime';
export * from './run-loop.js';
export * from './session-runtime.js';
export * from './tool-runtime.js';
Loading
Loading