Files
maka/docs/archive/maka-core-tech-walkthrough.md
T
agent-radar 7be535059e
Dependency audit / audit (push) Canceled after 0s
CI / plan (push) Canceled after 0s
Gitoxide helper admission / macos-latest (push) Canceled after 0s
Gitoxide helper admission / ubuntu-latest (push) Canceled after 0s
Gitoxide helper admission / windows-latest (push) Canceled after 0s
Runtime Host peer admission / quality (push) Canceled after 0s
Windows recovery / windows_recovery (push) Canceled after 0s
CI / heavy (push) Canceled after 0s
CI / test (push) Canceled after 0s
源码快照 @ 2026-09-01 (source-only, 剔除非源码素材)
2026-09-01 19:05:14 +08:00

323 lines
15 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
<!--
Licensed to the Apache Software Foundation (ASF) under one
or more contributor license agreements. See the NOTICE file
distributed with this work for additional information
regarding copyright ownership. The ASF licenses this file
to you under the Apache License, Version 2.0 (the
"License"); you may not use this file except in compliance
with the License. You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing,
software distributed under the License is distributed on an
"AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
KIND, either express or implied. See the License for the
specific language governing permissions and limitations
under the License.
-->
# Maka 核心技术解读
> Archived on 2026-07-13. Current backend guidance lives in `ARCHITECTURE.md`, its architecture chapters, source, and tests.
> 一份面向工程师的 Maka 技术栈通读笔记。覆盖 runtime 内核、权限系统、持久化与恢复、Electron 集成。
> 生成日期:2026-06-25
## 目录
- [整体定位](#整体定位)
- [仓库分层](#仓库分层)
- [Runtime 内核架构](#runtime-内核架构)
- [SessionManager:公共 runtime API](#sessionmanager公共-runtime-api)
- [AgentRun:单 turn 的 durable 编排](#agentrun单-turn-的-durable-编排)
- [AiSdkBackend:流式 + 多步工具循环](#aisdkbackend流式--多步工具循环)
- [ToolRuntime:权限门控 seam](#toolruntime权限门控-seam)
- [ModelAdapter:provider 归一化](#modeladapterprovider-归一化)
- [RunTrace:best-effort 诊断](#runtracebest-effort-诊断)
- [权限系统](#权限系统)
- [四档 PermissionMode](#四档-permissionmode)
- [12 类 ToolCategory](#12-类-toolcategory)
- [mode × category 策略矩阵](#mode--category-策略矩阵)
- [纯函数原则](#纯函数原则)
- [parked Promise 机制](#parked-promise-机制)
- [持久化与恢复](#持久化与恢复)
- [文件布局](#文件布局)
- [AgentRun ledger](#agentrun-ledger)
- [启动恢复](#启动恢复)
- [Electron 集成层](#electron-集成层)
- [进程边界](#进程边界)
- [密钥安全边界](#密钥安全边界)
- [多入口](#多入口)
- [数据流:一次对话的完整路径](#数据流一次对话的完整路径)
- [技术亮点小结](#技术亮点小结)
---
## 整体定位
Maka 是一个**本地优先(local-first)的 Electron 桌面 AI agent 工作台**。一句话概括它的技术追求:让用户在自己的电脑上跑一个**可观察、可控、可恢复**的 agent——所有数据本地落盘,敏感值按各自边界保存在本地,工具调用走权限策略,单次对话崩溃后能从 ledger 恢复。
## 仓库分层
monorepo 用 npm workspaces,分层干净:
- `packages/core`:纯契约层,零运行时依赖,只定义类型和纯函数。
- `packages/storage`:文件持久化层。
- `packages/runtime`:内核实现,最核心的部分。
- `packages/ui`:共享渲染组件。
- `apps/desktop`:Electron 壳子,把 runtime 装进窗口。
**核心设计原则**:契约与实现分离,runtime 内核再拆成可独立理解的边界,公共 API 保持稳定的同时内部可演化。
---
## Runtime 内核架构
这是整个项目最值钱的部分。分层关系:
```
SessionManager ← 对外公共 API(桌面 / bot / gateway 都走它)
-> AgentRun ← 单次 turn 的生命周期 + 启动恢复
-> AiSdkBackend ← 流式 + 工具循环引擎
-> ModelAdapter ← provider stream / usage / error 归一化
-> ToolRuntime ← 工具输入校验 / 权限 / watchdog / abort / telemetry
-> RunTrace ← best-effort 诊断 trace,写失败不影响对话
-> AgentRunStore ← durable run ledger
```
关键代码入口:
| 模块 | 文件 |
| --- | --- |
| `SessionManager` 类 | `packages/runtime/src/session-manager.ts:232` |
| `AgentRun.execute()` | `packages/runtime/src/agent-run.ts` |
| `send()` 流式泵 | `packages/runtime/src/ai-sdk-backend.ts:435` |
| `wrapToolExecute` 权限门控 | `packages/runtime/src/tool-runtime.ts:146` |
| provider 适配 | `packages/runtime/src/model-adapter.ts` |
### SessionManager:公共 runtime API
对外(桌面 IPC、bot adapter、open-gateway 三类入口)暴露的唯一 runtime 门面。职责:
- session CRUD
- backend registry 编排
- active run 查找
- 恢复入口
真正干活的 turn 生命周期被委派给 `AgentRun`。
### AgentRun:单 turn 的 durable 编排
理解恢复机制的关键。一次 `sendMessage` 会创建一个 `AgentRun`,它负责:
1. 生成 runId,写 `run_created` 事件到 ledger。
2. 追加用户消息、写初始 turn 状态。
3. 锁定连接快照(`connectionLocked`),防止 turn 跑到一半连接被改。
4. 构建 prior runtime context(从历史 run 的 `RuntimeEvent` 投影成模型历史)。
5. 驱动 backend 流事件,投影 session 状态。
6. 写 turn 完成 / 失败 / abort / permission-wait 状态。
7. `finalize()` 收尾:注销 active run、更新 header、写 `run_completed / run_failed / run_cancelled`。
`execute()` 是 async generator,核心结构简洁:
```ts
async *execute(): AsyncIterable<SessionEvent> {
try {
const begin = await this.begin();
for await (const ev of begin.backend.send(begin.backendInput)) {
await this.recordSessionEvent(ev);
yield ev;
}
} catch (error) {
await this.recordFailure(error);
throw error;
} finally {
await this.finalize();
}
}
```
每个 backend 事件都会被 `recordSessionEvent` 投影成 session 状态变更(running / blocked / aborted),并记录到 run ledger。即使进程崩了,重启时也能从 ledger 重建。
### AiSdkBackend:流式 + 多步工具循环
agent loop 的引擎。用 Vercel AI SDK 的 `streamText` 配合 `stopWhen: stepCountIs(N)` 驱动多步工具调用循环——循环本身交给 AI SDK,但所有"自己的机器"(权限、持久化、materializer、watchdog)都保留在 Maka 这边。
`send()` 结构(`ai-sdk-backend.ts:435`):
1. 建 `AsyncEventQueue<SessionEvent>`,作为后台泵和前台 yield 之间的缓冲。
2. `ModelAdapter.resolveModel()` 解析 LanguageModel,失败立即推 error + complete。
3. 构建 provider tools dict,**每个工具的 `execute` 被权限层包了一层**。
4. 从 RuntimeEvent 历史构建模型消息。
5. 后台泵:`streamText({...})` → `for await (chunk of result.fullStream)` → `ModelAdapter.handleStreamChunk` 归一化 → 推 queue。
6. 前台 `yield* drain(queue)`。
精巧点:
- **StreamWatchdog**:流式连接有 connect timeout 和 idle timeout,超时会 abort controller 并推 error 事件,防止挂死。
- **step cap grace**:当 `finishReason === 'tool-calls'`(撞了 step 上限)且没有 assistant 文本时,注入一条确定性提示,告诉用户已达本轮工具上限、可发"继续"。
- **prepareStep 动态工具加载**:同 turn 内可以逐步激活更多工具(deferred load),active 工具集按 step 重算。
### ToolRuntime:权限门控 seam
整个安全模型的执行点。工具的 `execute` 回调被 `wrapToolExecute` 包裹后交给 AI SDK,每次模型调用工具都经过这条链:
1. **loop-gate**:同一个 tool + 同一组 args 连续失败 N 次,直接 block,防止 agent 死循环。block 本身不记 outcome,streak 停在阈值,后续相同调用继续被拦。
2. **PermissionEngine.evaluate()** 返回三态:
- `allow` → 跑真实 `impl`,写 `ToolResult`。
- `block` → 合成 `isError:true` 的工具结果返回给模型,不执行真实逻辑。
- `prompt` → 推 `PermissionRequestEvent` 给 UI,await 一个 parked Promise,等用户决定。allow 则跑,deny 则合成"用户拒绝"。
3. 工具执行期间**暂停 stream watchdog**(避免长任务被判超时),`finally` 里恢复。
4. abort signal 透传进工具,stop 按钮能中断。
5. 记录 telemetry、artifact candidate、错误分类。
### ModelAdapter:provider 归一化
把 provider / AI SDK 的细节挡在外面:stream chunk 类型、provider setup、usage 归一化、provider error mapping。
- `startStream` 统一调 `streamText`。
- `handleStreamChunk` 把各种 chunk 翻译成 Maka 的 `SessionEvent`(text delta、thinking delta、complete 等)。
这样未来加 provider 不需要重复权限 / 工具 / run / session 逻辑。
### RunTrace:best-effort 诊断
记录 turn 的里程碑事件:turn started、model resolved / stream started / completed / failed、tool started / completed / failed、permission requested / decided、usage recorded、abort requested。
**关键约束:trace 写失败绝不影响用户对话**。它和 session 消息 JSONL 是分开的两套持久化。
---
## 权限系统
这是 Maka 区别于普通 chat demo 的核心安全设计,定义在 `packages/core/src/permission.ts`。
### 四档 PermissionMode
`explore` / `ask` / `execute` / `bypass`,从只读到全自动。
### 12 类 ToolCategory
`read`、`web_read`、`file_write`、`fs_destructive`、`shell_safe`、`shell_unsafe`、`git_destructive`、`network_send`、`privileged`、`browser`、`custom_tool`、`subagent`。
### mode × category 策略矩阵
`PERMISSION_POLICY` 是纯函数决策表,决定每个组合是 `allow` / `prompt` / `block`。设计很克制:
| 模式 | 读 | 写 | 危险操作 | 网络 | 浏览器 |
| --- | --- | --- | --- | --- | --- |
| `explore` | allow | block | block | web_read=prompt | block |
| `ask` | allow | prompt | prompt | prompt | prompt |
| `execute` | allow | allow | **永远 prompt** | allow | **永远 prompt** |
| `bypass` | allow | allow | allow | allow | allow |
核心规则:**不可逆操作(`fs_destructive`、`git_destructive`、`privileged`、`browser`)在 `execute` 模式下仍强制 prompt**——浏览器操作可能发帖 / 下单,视为不可逆。`bypass` 全 allow,仅用户显式选择时进入。
`BUILTIN_TOOL_CATEGORY` 把 Claude SDK 风格的工具名映射到类别,`Bash` 默认 `shell_unsafe`,再由 `categorizeBash()` 根据命令前缀动态降级或升级(`ls`/`pwd` → `shell_safe`,`rm` → `fs_destructive`,`git push --force` → `git_destructive`)。
### 纯函数原则
`preToolUse()` 给定输入必然返回相同结果,不生成 UUID(requestId 由 runtime 层的 `PermissionEngine` 生成),便于测试。
### parked Promise 机制
`PermissionEngine` 为每个 outstanding 权限请求维护一个 parked Promise,UI 的决定通过 `respondToPermission()` resolve 回等待中的 adapter。turn 结束时未回答的请求会被 reject 成 `user_stop`。
---
## 持久化与恢复
### 文件布局
工作数据放 Electron `userData` 下的 workspace 目录:
```
<Electron userData>/workspaces/default/
llm-connections.json
credentials.json
settings.json
sessions/<sessionId>/
messages.jsonl
runs/<runId>/
run.json ← run header(原子写)
events.jsonl ← append-only run 事件
```
### AgentRun ledger
`FileAgentRunStore`(`packages/storage/src/agent-run-store.ts`)实现:
- `run.json` 原子写(`writeAtomic` 先写临时文件再 rename)。
- `events.jsonl` append-only。
- **同 run 写串行化**(`writeQueues` per sessionId+runId 的 Promise 链),避免并发写竞争。
- ID 校验 `SAFE_ID_PATTERN`,防路径穿越。
- 读事件时容忍损坏行(`event_corrupt`)和未终止尾部。
这套 ledger 和用户可见的 session 消息 JSONL 分开,诊断 / 恢复状态不污染对话历史。
### 启动恢复
`recoverInterruptedSessions()` 优先用 run ledger:扫描持久化的 run header 和事件,分类非终态 run(`created` / `running` / `waiting_permission`),修复后收敛 session / turn 投影。
覆盖的恢复场景:
- stale 的 `created` / `running` run
- 停在 `run_started` / `model_stream_started` 的 run
- `tool_started` 后的残留
- `permission_requested` 后的等待
- 缺终态事件的 `model_stream_completed`
- 损坏事件行
没有 run ledger 的老 session 走旧的 message + turn-state 恢复路径。
---
## Electron 集成层
### 进程边界
| 进程 | 文件 | 职责 |
| --- | --- | --- |
| main | `apps/desktop/src/main/main.ts`(4593 行) | 装配 `SessionManager`、注册 IPC、管理窗口、OAuth、bot、gateway |
| preload | `apps/desktop/src/preload/preload.ts` | `contextBridge` 暴露 `window.maka.*` 白名单 API |
| renderer | `apps/desktop/src/renderer/` | React UI + Settings |
`main.ts:1262` 装配 `runtime = new SessionManager({...})`,`main.ts:564` 起 `OpenGatewayService`。IPC 用 `ipcMain.handle` 注册大量通道(memory、artifacts、settings、connection、session 等)。
### 密钥安全边界
- Provider/API key、bot token、proxy password、gateway token、Tavily key 写入本机凭据文件,依赖 OS 账号边界和文件权限;subscription OAuth token 使用独立的系统安全存储。
- Renderer 永远拿不到明文密钥,Settings 只显示 masked 状态和测试结果。
### 多入口
除了桌面 UI,runtime 还服务两类外部入口:
- **Bot adapter**(`packages/runtime/src/bots/`):Telegram、飞书、企业微信、微信 iLink、Discord、钉钉、QQ,都继承 `base-adapter.ts`,统一对接 SessionManager。
- **Open Gateway**(`open-gateway.ts`,1489 行):本地 HTTP / SSE API,用 token 保护,让外部读取会话状态 / 事件 / 能力 / 健康摘要。
---
## 数据流:一次对话的完整路径
1. 用户在 renderer 输入消息 → `window.maka.session.send` → IPC → `SessionManager.sendMessage`。
2. 创建 `AgentRun`,写 `run_created` 到 ledger,append 用户消息到 `messages.jsonl`。
3. 从历史 run 的 RuntimeEvent 投影模型上下文。
4. `AiSdkBackend.send()`:构建权限包裹的工具 → `streamText(stepCountIs(N))` → 后台泵 fullStream。
5. 每个流 chunk 经 `ModelAdapter.handleStreamChunk` 归一化成 `SessionEvent` → 推 queue → 前台 yield → IPC 流到 renderer。
6. 模型调用工具 → `wrapToolExecute` → loop-gate → `PermissionEngine.evaluate` → allow / block / prompt → 执行或合成错误。
7. assistant 文本落 `messages.jsonl`,usage 记 telemetry,trace 写 run ledger。
8. `finalize()` 收尾,写 `run_completed`,更新 session header。
---
## 技术亮点小结
- **ledger + projection 模式**:run 事件是 source of truth,session 状态是投影,崩溃可重建——整个恢复能力的根基。
- **纯函数策略 + runtime 包装**:权限决策表是纯函数好测试,requestId / parking 状态由 runtime 管,职责分得清楚。
- **权限门控作为工具 execute 的 seam**:不动 AI SDK 的循环,在 execute 回调里插入 allow / block / prompt,最小侵入。
- **best-effort trace 不影响主路径**:诊断信息尽力写,失败静默,对话绝不受拖累。
- **同 run 写串行化 + 原子写**:文件持久化的并发安全靠 Promise 链和 rename 保证。
- **安全分层**:模式 × 类别矩阵 + 命令分类器 + 凭据文件权限 / subscription token 安全存储 + preload 白名单,多层防御。