02. 基础脚手架搭建
目标
搭建一个清晰、可扩展且符合现代工程化规范的 TypeScript Monorepo 结构。它将作为我们后续所有核心 Harness、内置工具和外接通道的基础基座。
最终目录结构
我们将使用 pnpm workspace 来管理多个子包,整体目录结构规划如下:
hachimi/
├── packages/
│ ├── core/ # 核心 Harness (与接入渠道完全解耦)
│ │ ├── src/
│ │ │ ├── agent/ # Agent Loop 循环与 Provider
│ │ │ ├── memory/ # 分层 MemoryManager
│ │ │ ├── session/ # SessionManager
│ │ │ ├── skills/ # SkillRegistry 技能系统
│ │ │ ├── tools/ # ToolRegistry 工具核心
│ │ │ └── types/ # 核心 TS 类型定义
│ │ └── package.json
│ ├── shared/ # 跨模块公共工具包 (如 generateId)
│ │ ├── src/
│ │ └── package.json
│ ├── channels/ # 接入渠道包 (如 Web, Telegram, Slack 适配器 - 预留)
│ ├── tools-builtin/ # 内置基础工具包 (如文件读写、网页剪裁 - 预留)
│ └── storage/ # 存储中间层 (SQLite, PostgreSQL - 预留)
├── apps/
│ ├── cli/ # CLI 命令行交互客户端
│ ├── desktop/ # 桌面端 (Tauri/Electron - 预留)
│ └── server/ # REST API 服务器 (预留)
├── scripts/ # 辅助开发/测试脚本
├── package.json # 根目录 package.json
├── pnpm-workspace.yaml # pnpm 独有的多包定义
└── tsconfig.json # 根目录全局 TS 配置初始化项目
首先,在你的 workspace 目录中创建项目文件夹并初始化:
mkdir hachimi
cd hachimi
pnpm init1. 根目录 package.json 配置
修改根目录下的 package.json,使其作为一个私有的 monorepo 根节点,并引入全局开发依赖。请注意,我们强制使用 ESM ("type": "module"):
{
"name": "hachimi",
"private": true,
"type": "module",
"scripts": {
"typecheck": "tsc --noEmit"
},
"devDependencies": {
"typescript": "^5.8.2",
"@types/node": "^22.13.10"
}
}2. 定义 pnpm-workspace.yaml
在根目录下创建 pnpm-workspace.yaml,指定子包和应用的放置范围,使 pnpm 能识别本地多包链接:
packages:
- 'packages/*'
- 'packages/channels/*'
- 'packages/tools-builtin/*'
- 'packages/storage/*'
- 'apps/*'创建子包
接下来,我们将创建两个最基础的子包:@hachimi/shared 和 @hachimi/core。
1. 公共工具包:packages/shared
这个子包存放没有任何外部依赖的底层纯函数工具。
mkdir -p packages/shared/src创建 packages/shared/package.json:
{
"name": "@hachimi/shared",
"version": "0.1.0",
"private": true,
"type": "module",
"main": "./src/index.ts",
"types": "./src/index.ts",
"exports": {
".": "./src/index.ts"
}
}在 packages/shared/src/index.ts 中编写我们第一个辅助函数(后续用于生成消息 ID 与会话 ID):
/**
* 生成带前缀的唯一标识符
*/
export function generateId(prefix = ""): string {
return `${prefix}${crypto.randomUUID()}`;
}
/**
* 获取当前高精度时间戳
*/
export function now(): number {
return Date.now();
}2. 核心架构包:packages/core
这是项目的绝对心脏,存放 Agent 所有的业务逻辑模型。
mkdir -p packages/core/src/{agent,memory,tools,skills,session,permissions,hooks,types}创建 packages/core/package.json,并声明对本地公共包 @hachimi/shared 的依赖。我们使用 workspace:* 告诉 pnpm 引用本地源码而不是去 npm 线上拉取:
{
"name": "@hachimi/core",
"version": "0.1.0",
"private": true,
"type": "module",
"main": "./src/index.ts",
"types": "./src/index.ts",
"exports": {
".": "./src/index.ts"
},
"dependencies": {
"@hachimi/shared": "workspace:*"
}
}TypeScript 全局配置
为了让 TypeScript 能够完美处理 ESM 导入、支持绝对路径别名解析,并在多包之间自由跳转开发,我们需要精细化配置根目录的 tsconfig.json:
{
"compilerOptions": {
"target": "ES2022",
"module": "NodeNext",
"moduleResolution": "NodeNext",
"strict": true,
"esModuleInterop": true,
"skipLibCheck": true,
"forceConsistentCasingInFileNames": true,
"resolveJsonModule": true,
"declaration": true,
"outDir": "dist",
"baseUrl": ".",
"paths": {
"@hachimi/core": ["packages/core/src"],
"@hachimi/shared": ["packages/shared/src"]
}
},
"include": ["packages/**/*", "apps/**/*", "scripts/**/*"]
}IMPORTANT
这里的 "module": "NodeNext" 与 "moduleResolution": "NodeNext" 至关重要。它们强制约束了我们在导入本地文件时,必须写明扩展名(例如 import { Agent } from "./agent.js"),这符合现代 Node.js 的原生 ESM 加载标准,能有效防止打包后的各种运行时模块未找到错误。
在子包中(如 packages/core/tsconfig.json),我们只需继承根配置即可:
{
"extends": "../../tsconfig.json",
"compilerOptions": {
"composite": true,
"rootDir": "src"
},
"include": ["src/**/*"]
}为什么采用这种结构?
- 完全解耦核心(Core)与接入渠道(Channels):这是最核心的工程决策。
@hachimi/core纯粹由输入输出驱动,任何环境都可以轻量级地调用它。 - 本地包热更新:借由
pnpm workspace与 TS 的paths别名配置,你在修改@hachimi/shared里的代码时,@hachimi/core能够实时生效,无需重复执行编译与打包命令。 - 清晰的职责拆分:随着项目演进,你随时可以在
packages/tools-builtin目录中追加第三方依赖(如 cheerio、sharp),而不必污染core的极简运行时。
本章总结
在本章中,我们:
- 使用
pnpmWorkspace 完成了多包 monorepo 基础配置。 - 引入并配置了严格支持 ESM 与绝对路径别名导入的 TypeScript 全局规则。
- 划分了核心包与共享工具包,写下了第一个本地包依赖关系。
基础脚手架已经稳固。在下一章,我们将实现第一个里程碑:最小可用核心。