Compare commits
2 Commits
5c503771d0
...
6375754ed3
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
6375754ed3 | ||
|
|
425dc9a793 |
125
.agents/skills/write-agent-blog/SKILL.md
Normal file
125
.agents/skills/write-agent-blog/SKILL.md
Normal file
@ -0,0 +1,125 @@
|
||||
---
|
||||
name: write-agent-blog
|
||||
description: 为 llm-to-agent 的《Agent进阶专题》系列创作中文文章,并在项目 blog 目录输出单个 Markdown 文件。当用户要求为某次提交、代码变更、小功能点、路线图事项、里程碑或实现过程撰写、生成、修改博客或文章时使用,包括“为本次提交写篇文章”“给这个功能写博客”“记录这次实现”等表达。
|
||||
---
|
||||
|
||||
# 创作 Agent 系列博客
|
||||
|
||||
为《Agent进阶专题》系列创作一篇以项目事实为依据的 Hexo 文章,并将成果保存为 `<项目根目录>/blog/` 下的单个 Markdown 文件。
|
||||
|
||||
## 工作流程
|
||||
|
||||
1. 使用 `git rev-parse --show-toplevel` 定位项目根目录。
|
||||
2. 确定用户要求的写作范围。对于“本次提交”等表达,使用 `git show` 检查 `HEAD`;用户明确指向尚未提交的工作时,还要检查工作区变更。
|
||||
3. 阅读相关实现、测试、包脚本、路线图事项和功能文档。阅读已有的 `blog/*.md`,延续编号、术语和内容深度,并避免重复。
|
||||
4. 确定文章对应的单个小功能点、所属阶段(如 `P1`、`P2`)、阶段内篇号、标题和仅含 ASCII 小写字符的短横线式别名。
|
||||
5. 以 `assets/article-template.md` 为写作骨架。替换所有占位内容并删除模板说明。
|
||||
6. 如果 `blog/` 不存在则创建它。除非用户明确要求修改已有文章,否则只新建一个 `.md` 文件。
|
||||
7. 检查元信息、文件名、永久链接、`<!-- more -->`、事实陈述、代码片段和 Git 差异。除非用户明确要求,否则不要创建提交。
|
||||
|
||||
## 事实依据
|
||||
|
||||
- 将源代码、测试和实际差异视为事实依据;使用文档和提交信息理解设计意图与背景。
|
||||
- 只描述选定变更已经体现的行为。明确标注计划和未来工作,不得将其写成已经实现的能力。
|
||||
- 不得虚构调试经历、性能数据、设计争论、执行命令、命令输出或用户反馈。
|
||||
- 优先选用能够揭示核心思路的短代码片段。确保片段与仓库内容一致,并在附近正文中注明仓库相对路径。
|
||||
- 如果目标提交主要是脚手架、配置或文档,应解释这项基础工作的目的和后续价值,但不得夸大现有能力。
|
||||
|
||||
## 文件名与元信息
|
||||
|
||||
文件名使用 `2026-PX-0X-xxx.md` 格式:
|
||||
|
||||
- 除非用户修改约定,否则本系列固定使用四位年份 `2026`。
|
||||
- 将 `PX` 替换为路线图阶段,例如 `P1`。
|
||||
- 将 `0X` 替换为该阶段内的两位文章序号,从 `01` 开始。
|
||||
- 将 `xxx` 替换为描述该功能的简短英文别名,只使用小写 ASCII 字母、数字和短横线。
|
||||
- 示例:`2026-P1-03-streaming-chat.md`。
|
||||
- 根据已有文章推断下一个未使用的序号。如果无法可靠判断所属阶段,应暂停并询问用户,不要自行猜测。
|
||||
- 不得覆盖已有文件。如果目标名称已经存在,先判断它是否对应同一功能;否则递增序号。
|
||||
|
||||
严格使用以下元信息结构:
|
||||
|
||||
```yaml
|
||||
---
|
||||
title: 【Agent进阶专题】中文标题
|
||||
tags: [AI, Agent]
|
||||
categories:
|
||||
- AI
|
||||
date: YYYY-MM-DD HH:mm:ss
|
||||
permalink: /agent/YYYY/MM/slug/
|
||||
---
|
||||
```
|
||||
|
||||
- 新建文件时使用当前本地日期和时间。
|
||||
- 标题应具体并体现结果,不使用章节编号,也不要使用“一些思考”之类含糊标题。
|
||||
- 文件名和永久链接使用相同的英文别名。
|
||||
- 永久链接中的 `YYYY/MM` 从 `date` 推导。
|
||||
|
||||
## 文章固定结构
|
||||
|
||||
保持以下三部分顺序:
|
||||
|
||||
1. Hexo 元信息。
|
||||
2. 位于 `<!-- more -->` 之前的简洁简介,用于列表页摘要。
|
||||
3. 位于 `<!-- more -->` 之后的完整正文。
|
||||
|
||||
使用自然且能够说明内容的标题,不要直接使用“简介部分”或“正文部分”等模板化标题。简介由一至两个短段落组成,需要说明问题、此次交付的能力及其价值。
|
||||
|
||||
正文应围绕具体功能组织,不要机械套用完全相同的标题。根据主题只选择必要内容,不要求每篇全部覆盖:
|
||||
|
||||
- 变更前存在的问题或限制;
|
||||
- 当前小功能点的目标与边界;
|
||||
- 核心设计或理解模型;
|
||||
- 配合精选代码片段说明实现路径;
|
||||
- 验证方式与可观察结果;
|
||||
- 取舍、当前限制和下一项自然演进能力;
|
||||
- 用简短结语将本篇内容连接到整个系列的演进主线。
|
||||
|
||||
- 导读、概念介绍和设计点题类文章建议控制在 1000 个中文字符左右;这是保持简洁的参考值,不是硬性上限。根据章节范围和必要信息调整篇幅,优先保证主题讲清且没有无关扩展。
|
||||
- 标题给出的主题就是内容边界。只讲清当前主题,不借机扩展相关协议、实现细节或远期能力。
|
||||
|
||||
## 读者视角与系列衔接
|
||||
|
||||
- 面向第一次接触本项目的外部读者写作,不要把作者已经掌握的项目背景当作读者常识。
|
||||
- 阶段编号和文章编号用于组织系列,不是正文的叙事前提。首次出现 `P0`、`P1` 等编号时必须用自然语言解释其含义;如果编号对理解当前内容没有帮助,就不要在正文中使用。
|
||||
- 每篇文章先从读者能够理解的场景、问题或一次自然的需求变化切入,再逐步引出项目术语和设计结论。
|
||||
- 用“原本有什么—遇到什么问题—做出什么选择”推进文章,但不要为了讲故事虚构场景或增加文学化铺垫。
|
||||
- 不得使用“如前所述”“大家已经知道”等措辞假设读者读过其他文章。即使文章位于系列中间,也应提供理解当前主题所需的最少背景。
|
||||
- 相邻文章应各守边界,避免提前讲完后续主题。结尾只需自然提出下一篇的问题,不要罗列尚未解释的阶段和术语。
|
||||
- 当前导读部分暂定为:`P0-00` 项目介绍与目录导航、`P0-01` 架构设计、`P0-02` 功能划分、`P0-03` 阶段规划、`P0-04` 目录规划。创作其中一篇时,不要侵占其他篇目的主要内容。
|
||||
|
||||
## 系列文风
|
||||
|
||||
- 使用清晰的简体中文,面向理解 TypeScript 和基本 LLM 概念、但可能刚接触 Agent 工程的开发者。
|
||||
- 直接、口语化、克制。先给结论,再补必要原因;能用一句话说清的内容不要扩成一段。
|
||||
- 删除不推动观点的过渡句、重复总结、设问和修饰语。每个段落只表达一个重点,通常不超过三句话。
|
||||
- 优先使用具体动词和短句,避免“我们需要先回答一个不那么显眼、却会影响整个项目的问题”一类绕弯表达。
|
||||
- 适度使用“我们”营造共同实践感,重点仍应放在技术推理和可复现的工程过程上。
|
||||
- 按照“动机—原理—实现—验证”的顺序展开。在进入密集实现细节前,先解释为什么这样做。
|
||||
- 保持段落简短、标题明确,并与项目文档使用一致的术语。
|
||||
- 技术术语首次出现时给出简要解释。代码中的英文标识符保持原样;不要为同一概念反复更换译名。
|
||||
- 避免营销语言、泛泛的 AI 背景介绍、夸张结论、填充式总结和流水账式叙述。
|
||||
- 每篇文章都应能够独立阅读,同时在概念上承接前一项能力并引出下一项能力。
|
||||
- 用最短的具体情境引出矛盾和选择,避免从项目内部术语或文档摘要直接起笔,也避免把技术文章写成散文。
|
||||
- 不为追求完整而增加章节。点题类文章通常使用两至四个短章节即可。
|
||||
|
||||
## Markdown 与代码
|
||||
|
||||
- 使用标准 Markdown,围栏代码块必须标注语言。
|
||||
- 正文标题从 `##` 开始;页面标题由 Hexo 元信息提供,不要在正文中重复一级标题。
|
||||
- 标题、列表、代码块和 `<!-- more -->` 前后保留空行。
|
||||
- 代码片段应尽量短且与主题直接相关。只有在省略范围明显且不会造成误解时才使用 `...`。
|
||||
- 展示命令时,应区分命令和示例输出。只有命令确实存在或实际执行过,才能将其作为验证命令写入文章。
|
||||
- 除非用户明确要求,否则不要添加目录。
|
||||
|
||||
## 最终检查
|
||||
|
||||
回复用户前确认:
|
||||
|
||||
- 只新增或修改了一篇目标 Markdown 文章。
|
||||
- 文件名符合 `^2026-P[0-9]+-[0-9]{2}-[a-z0-9]+(?:-[a-z0-9]+)*\.md$`。
|
||||
- 元信息结构有效;除非用户要求,否则只包含本系列规定的字段。
|
||||
- 有意义的简介之后恰好出现一次 `<!-- more -->`。
|
||||
- 没有遗留任何占位内容。
|
||||
- 事实陈述和代码片段符合当前仓库状态。
|
||||
- 最终回复包含文章文件链接,并用一句话说明文章主题。
|
||||
4
.agents/skills/write-agent-blog/agents/openai.yaml
Normal file
4
.agents/skills/write-agent-blog/agents/openai.yaml
Normal file
@ -0,0 +1,4 @@
|
||||
interface:
|
||||
display_name: "Agent 专题博客创作"
|
||||
short_description: "根据提交与项目文档创作统一风格的 Hexo Markdown 文章"
|
||||
default_prompt: "使用 $write-agent-blog 为本次提交写一篇文章。"
|
||||
40
.agents/skills/write-agent-blog/assets/article-template.md
Normal file
40
.agents/skills/write-agent-blog/assets/article-template.md
Normal file
@ -0,0 +1,40 @@
|
||||
---
|
||||
title: 【Agent进阶专题】{{中文标题}}
|
||||
tags: [AI, Agent]
|
||||
categories:
|
||||
- AI
|
||||
date: {{年-月-日 时:分:秒}}
|
||||
permalink: /agent/{{四位年份}}/{{两位月份}}/{{英文别名}}/
|
||||
---
|
||||
|
||||
{{面向首次接触项目的读者,从一个具体场景或问题切入,用一至两个短段落说明本篇主题及其价值,作为 Hexo 列表页摘要。}}
|
||||
|
||||
<!-- more -->
|
||||
|
||||
## {{问题或限制}}
|
||||
|
||||
{{说明需要解决的具体问题或此前存在的限制。}}
|
||||
|
||||
## {{目标与边界}}
|
||||
|
||||
{{说明本篇交付什么,以及刻意暂不处理什么。}}
|
||||
|
||||
## {{核心设计或理解模型}}
|
||||
|
||||
{{先解释核心思路,再进入实现细节。}}
|
||||
|
||||
## {{实现过程}}
|
||||
|
||||
{{配合精选代码片段说明关键实现路径。}}
|
||||
|
||||
## {{验证结果}}
|
||||
|
||||
{{说明实际使用的验证方式和可观察结果。}}
|
||||
|
||||
## {{取舍与下一步}}
|
||||
|
||||
{{记录当前限制、实现取舍和下一项自然演进能力。}}
|
||||
|
||||
## {{结语}}
|
||||
|
||||
{{将这个小功能点连接到从 LLM 走向 Agent 的系列主线。}}
|
||||
6
.gitignore
vendored
6
.gitignore
vendored
@ -1,5 +1,5 @@
|
||||
node_modules
|
||||
node_modules/
|
||||
.env
|
||||
pnpm-lock.yaml
|
||||
.DS_Store
|
||||
tmp
|
||||
tmp/
|
||||
.agent/
|
||||
|
||||
13
README.md
Normal file
13
README.md
Normal file
@ -0,0 +1,13 @@
|
||||
# llm-to-agent
|
||||
|
||||
个人使用、本地优先的开发助手与电脑管家。
|
||||
|
||||
项目坚持简单的 `Kernel + Extensions`:Kernel 只提供运行机制,具体能力全部由 Extension 实现;所有专属数据集中在用户目录,不写入绑定的项目。
|
||||
|
||||
文档:
|
||||
|
||||
- [架构总览](docs/architecture.md)
|
||||
- [源码目录](docs/source-layout.md)
|
||||
- [数据目录](docs/data-layout.md)
|
||||
- [Extension 目录](docs/extensions.md)
|
||||
- [路线图](docs/roadmap.md)
|
||||
@ -1,32 +0,0 @@
|
||||
# llm-to-agent 文档
|
||||
|
||||
文档分为三部分:
|
||||
|
||||
- [架构总览](architecture.md):稳定的职责边界与依赖原则;
|
||||
- [规划目录](directory-plan.md):当前建议的源码与运行数据布局,允许随实际开发调整;
|
||||
- [实现阶段](roadmap.md):先完成基本能力,再建立自举,随后由 Agent 继续实现自己的开发顺序;
|
||||
- `features/`:按整个项目的大功能分类,列出尽量完整且有实质性交付的小功能点。
|
||||
|
||||
## 功能目录
|
||||
|
||||
- [Runtime 与内核](features/runtime.md)
|
||||
- [本地数据与配置](features/data-config.md)
|
||||
- [Space、Conversation 与消息](features/spaces.md)
|
||||
- [模型、聊天与 Agent](features/model-agent.md)
|
||||
- [Run 与 Agent 执行](features/run-execution.md)
|
||||
- [Tools、Workspace 与产物](features/tools-execution.md)
|
||||
- [CLI](features/cli.md)
|
||||
- [Project 开发助手](features/project-assistant.md)
|
||||
- [System Evolution](features/system-evolution.md)
|
||||
- [扩展、Hooks 与 Profile](features/extensions.md)
|
||||
- [计划、工作流与多 Agent](features/planning-multi-agent.md)
|
||||
- [记忆、检索与知识](features/memory-knowledge.md)
|
||||
- [浏览器](features/browser.md)
|
||||
- [本机与桌面控制](features/desktop-control.md)
|
||||
- [自动化与后台任务](features/automation.md)
|
||||
- [可观测性与可靠性](features/observability-reliability.md)
|
||||
- [权限、确认与敏感数据](features/permissions-security.md)
|
||||
- [Web](features/web.md)
|
||||
- [Desktop](features/desktop.md)
|
||||
|
||||
具体字段、技术库和交互细节在进入对应实现阶段时讨论,不用早期规划替代真实开发反馈。
|
||||
@ -1,125 +1,54 @@
|
||||
# 架构总览
|
||||
|
||||
## 定位
|
||||
## 目标
|
||||
|
||||
`llm-to-agent` 是纯个人使用、本地优先的开发助手与电脑管家。它以一个常驻 Runtime 作为唯一运行宿主,并通过 CLI、Web、Desktop 三个客户端共享数据和执行状态。
|
||||
|
||||
系统采用四层结构:
|
||||
llm-to-agent 是个人项目,架构优先级是:
|
||||
|
||||
```text
|
||||
Launcher / Supervisor
|
||||
↓
|
||||
Runtime Host
|
||||
↓
|
||||
Application Runtime
|
||||
↓
|
||||
Microkernel + Extensions
|
||||
简单易懂 > 容易修改 > 容易扩展 > 通用性
|
||||
```
|
||||
|
||||
这四层表达职责和依赖方向,不要求第一版拆成四个独立进程。
|
||||
Runtime 内部长期只有:
|
||||
|
||||
## Launcher / Supervisor
|
||||
```text
|
||||
Kernel + Extensions
|
||||
```
|
||||
|
||||
Supervisor 位于 Agent 运行版本之外,只负责启动、版本切换、健康检查和失败回退。它不理解 Space、Conversation、Tool 或 Agent 推理。
|
||||
Kernel 管“Extension 怎样连接和运行”,Extension 管“系统具体能做什么”。
|
||||
|
||||
Supervisor 在 P3 的 System Evolution 发布闭环中实现,P1/P2 开发阶段可以先由普通启动脚本承担进程拉起。
|
||||
## Kernel
|
||||
|
||||
## Runtime Host
|
||||
Kernel 不认识 Workspace、Conversation、Run、Agent、模型或 Tool,只提供所有 Extension 共同需要的机制:
|
||||
|
||||
Runtime Host 是 Node.js 进程的组合入口,负责:
|
||||
- 生命周期与资源清理;
|
||||
- 单个能力的提供与调用;
|
||||
- 多个实现的注册与选择;
|
||||
- 事实事件的发布与监听;
|
||||
- 隔离存储、取消和错误传播;
|
||||
- 副作用能力共用的调用接缝。
|
||||
|
||||
- 创建并启动 Application Runtime 与 Microkernel;
|
||||
- 装配当前启用的扩展;
|
||||
- 开启本地客户端连接;
|
||||
- 处理进程信号、单实例和退出;
|
||||
- 将配置与基础设施交给内部模块。
|
||||
|
||||
Host 不包含 Project、Conversation、Agent 工作流等业务规则。
|
||||
|
||||
## Application Runtime
|
||||
|
||||
Application Runtime 负责这个产品不可缺少的应用能力:
|
||||
|
||||
- Project / Task Space;
|
||||
- Conversation 与持久化 Run Record;
|
||||
- 本地数据 Repository 和唯一写入;
|
||||
- 客户端 Command、Query 与 Event Stream;
|
||||
- 当前 Space/Conversation 上下文;
|
||||
- Profile 选择和基础并发策略。
|
||||
|
||||
它不包含 DeepSeek 请求、Shell、Git、浏览器驱动、提示词或具体 Agent 协作策略。
|
||||
|
||||
## Microkernel
|
||||
|
||||
Microkernel 负责一次 Agent 执行的通用机制:
|
||||
|
||||
- 创建内存中的 Run Context;
|
||||
- 调用 Agent Behavior;
|
||||
- 注册并调用 Tool;
|
||||
- 执行 Hook Pipeline;
|
||||
- 发出流式输出和运行事件;
|
||||
- 传播取消信号并隔离执行错误。
|
||||
|
||||
Microkernel 不认识 Project、Task 或具体存储,也不依赖任何具体扩展。
|
||||
只有所有 Extension 都必须遵守的运行规则才进入 Kernel。
|
||||
|
||||
## Extensions
|
||||
|
||||
扩展只放真正可替换、可按需增加的能力:
|
||||
Extension 可以提供能力、调用其他能力、添加同类实现、发布或监听事实,并管理自己的数据与资源。
|
||||
|
||||
- **Adapter**:DeepSeek、未来模型提供商、浏览器或检索驱动;
|
||||
- **Tool**:文件、Shell、Git、浏览器、桌面控制;
|
||||
- **Hook**:围绕 Tool/Run 生命周期观察、限制或响应;
|
||||
- **Behavior**:DefaultAgent、CodingAgent、Planner、Reviewer。
|
||||
控制流程必须使用明确调用;事件只表示已经发生的事实,不能承担顺序、返回值、审批或回滚。
|
||||
|
||||
Space、Conversation 和持久化 Run Record 是 Application Runtime 的产品领域,不作为可卸载插件。
|
||||
Extension 之间只依赖公开契约,不引用彼此的实现。
|
||||
|
||||
## Profile
|
||||
具体能力归属见 [Extension 目录](extensions.md)。
|
||||
|
||||
Profile 是 Application Runtime 管理的声明式能力组合,不是独立运行层:
|
||||
## Runtime 与产品
|
||||
|
||||
```text
|
||||
Profile
|
||||
├── 默认 Behavior
|
||||
├── 可用 Tools
|
||||
├── 启用 Hooks
|
||||
├── 模型设置
|
||||
└── 上下文/记忆策略
|
||||
```
|
||||
Runtime 持有唯一一套 Kernel、Extension 实例和本地数据。CLI、Web、Desktop 是不同产品入口,使用同一套能力和状态。
|
||||
|
||||
Project 和 Task 是 Space 类型;System Evolution 是应用于 Project 的特殊 Profile。
|
||||
自进化阶段再增加 Runtime 外的 Supervisor,用于版本切换、健康检查和失败回退。
|
||||
|
||||
## 依赖规则
|
||||
## 长期约束
|
||||
|
||||
```text
|
||||
Supervisor → Runtime Host
|
||||
Runtime Host → Application Runtime / Microkernel / Extensions
|
||||
Application Runtime → Microkernel 公共接口
|
||||
Extensions → Microkernel 扩展接口
|
||||
Microkernel ✕ Application Runtime / 具体 Extensions
|
||||
Clients → Runtime Interface
|
||||
```
|
||||
|
||||
目录隔离只是表现,以上依赖方向才是边界是否真实成立的判断依据。
|
||||
|
||||
## 客户端接口
|
||||
|
||||
CLI、Web、Desktop 逻辑上只使用三类交互:
|
||||
|
||||
- **Command**:创建 Space、发送消息、取消 Run 等状态变更;
|
||||
- **Query**:读取 Space、Conversation、Run 和日志;
|
||||
- **Event Stream**:接收模型增量、Tool 过程和 Run 状态。
|
||||
|
||||
具体采用 HTTP/SSE、Unix Socket 或其他本地协议在实现时确定,不提前冻结字段。
|
||||
|
||||
## Event 与 Hook
|
||||
|
||||
- **Run Event** 是发生过的事实,用于日志和客户端实时展示。
|
||||
- **Hook** 是事件前后实际执行的扩展代码。
|
||||
|
||||
两者不能混为同一个事件总线:记录了一个事件,不代表必须触发可修改流程的 Hook。
|
||||
|
||||
## 演化原则
|
||||
|
||||
任何一层都允许由 Agent 修改,包括 Application Runtime 和 Microkernel。Agent 在 System Evolution Project 的隔离 worktree 中修改源码、运行测试并展示 Diff;Git 写操作由用户确认,Supervisor 负责切换构建版本和失败回退。
|
||||
|
||||
自进化依赖的是源码可修改、候选版本隔离、测试、发布和回退,不要求把所有产品能力插件化。
|
||||
- 默认在现有 Extension 内增加普通代码,出现真实独立边界后再拆。
|
||||
- 第一版静态装配,不建设插件平台、热加载或依赖图。
|
||||
- 保持单进程、单用户和本地优先,直到真实需求要求改变。
|
||||
- 不提前建设 Command Bus、Middleware、事件溯源或分布式状态。
|
||||
- 新增 Kernel 机制必须有多个具体使用者。
|
||||
|
||||
53
docs/data-layout.md
Normal file
53
docs/data-layout.md
Normal file
@ -0,0 +1,53 @@
|
||||
# 数据目录
|
||||
|
||||
## 唯一数据根
|
||||
|
||||
所有 llm-to-agent 专属数据集中在 `LLM_TO_AGENT_HOME`。未设置时默认使用:
|
||||
|
||||
```text
|
||||
~/.llm-to-agent/
|
||||
```
|
||||
|
||||
开发环境可以指向另一个用户目录,测试使用临时目录。
|
||||
|
||||
## 物理目录
|
||||
|
||||
```text
|
||||
<LLM_TO_AGENT_HOME>/
|
||||
├── config/
|
||||
├── workspaces/
|
||||
│ └── <workspace-id>/
|
||||
│ ├── files/
|
||||
│ ├── conversations/
|
||||
│ ├── runs/
|
||||
│ └── extensions/
|
||||
├── extensions/
|
||||
├── releases/
|
||||
├── runtime/
|
||||
└── trash/
|
||||
```
|
||||
|
||||
目录按需要创建,不规定尚未实现的数据文件和格式。
|
||||
|
||||
## Workspace
|
||||
|
||||
- `managed` Workspace 的工作文件位于自己的 `files/`。
|
||||
- `linked` Workspace 只记录用户已有目录的绑定关系。
|
||||
- 两种 Workspace 的 Conversation、Run、记忆和 Extension 数据都位于数据根目录。
|
||||
|
||||
## 项目目录零落地
|
||||
|
||||
绑定外部项目时,项目源码保留在原位置,所有 llm-to-agent 专属数据仍保存在:
|
||||
|
||||
```text
|
||||
<LLM_TO_AGENT_HOME>/workspaces/<workspace-id>/
|
||||
```
|
||||
|
||||
项目中不创建 `.agent/`、`.llm-to-agent/` 或其他专属元数据。项目移动时更新绑定关系;删除 linked Workspace 绝不能删除外部项目源码。
|
||||
|
||||
## 数据规则
|
||||
|
||||
- Kernel 只提供安全路径、隔离和基础写入机制,不理解业务数据。
|
||||
- 每个 Extension 管理自己的数据,不能直接修改其他 Extension 的私有内容。
|
||||
- 缓存和临时数据必须可以清理重建。
|
||||
- 删除重要数据默认先进入 `trash/`,迁移和修复前先保留可恢复副本。
|
||||
@ -1,116 +0,0 @@
|
||||
# 规划目录
|
||||
|
||||
本文描述当前架构下建议采用的源码与运行数据目录。它用于指导项目起步,不是不可修改的目录规范。
|
||||
|
||||
后续实现或 System Evolution 过程中,如果真实依赖、代码规模或开发体验证明某个划分不合理,可以移动、合并或拆分目录;调整时应优先保持职责和依赖方向,而不是机械维持路径兼容。
|
||||
|
||||
## 源码仓库
|
||||
|
||||
```text
|
||||
llm-to-agent/
|
||||
├── apps/
|
||||
│ ├── runtime/ # Runtime Host / composition root
|
||||
│ ├── supervisor/ # 启动、版本切换、健康检查、回退
|
||||
│ ├── cli/ # REPL、非交互 CLI、TUI
|
||||
│ ├── web/ # Web 客户端
|
||||
│ └── desktop/ # Desktop Host 与原生桥接
|
||||
│
|
||||
├── packages/
|
||||
│ ├── kernel/ # Microkernel
|
||||
│ ├── application/ # Application Runtime
|
||||
│ └── client/ # 客户端共享的 Runtime Client
|
||||
│
|
||||
├── extensions/ # 第一方扩展,按完整能力纵向划分
|
||||
│ ├── deepseek/
|
||||
│ ├── workspace/
|
||||
│ ├── shell/
|
||||
│ ├── git/
|
||||
│ └── .../
|
||||
│
|
||||
├── docs/
|
||||
│ ├── architecture.md
|
||||
│ ├── directory-plan.md
|
||||
│ ├── roadmap.md
|
||||
│ └── features/
|
||||
│
|
||||
├── tests/
|
||||
│ └── e2e/ # 跨包、自举和版本切换测试
|
||||
│
|
||||
├── tooling/ # 构建、发布、开发辅助
|
||||
│
|
||||
├── package.json
|
||||
├── tsconfig.json
|
||||
└── workspace.yaml
|
||||
```
|
||||
|
||||
## 架构对应关系
|
||||
|
||||
| 架构职责 | 规划目录 |
|
||||
| --- | --- |
|
||||
| Launcher / Supervisor | `apps/supervisor/` |
|
||||
| Runtime Host | `apps/runtime/` |
|
||||
| Application Runtime | `packages/application/` |
|
||||
| Microkernel | `packages/kernel/` |
|
||||
| Extensions | `extensions/` |
|
||||
| Runtime Client | `packages/client/` |
|
||||
| CLI / Web / Desktop | `apps/cli/`、`apps/web/`、`apps/desktop/` |
|
||||
|
||||
## 目录原则
|
||||
|
||||
### Apps 是进程和产品入口
|
||||
|
||||
`apps/runtime/` 负责创建组件、安装扩展并开启本地连接,不承载 Space、Conversation、Tool 或 Agent Behavior 的具体实现。
|
||||
|
||||
CLI、Web、Desktop 通过同一个 Runtime Client 工作,不各自复制 Agent、存储和执行逻辑。
|
||||
|
||||
### Packages 固定核心依赖边界
|
||||
|
||||
- `kernel/` 只包含 Run Context、Behavior 调度、Tool Gateway、Hook Pipeline、Run Event、流式输出、取消和错误边界。
|
||||
- `application/` 承载 Project/Task、Conversation、Message、Run Record、Repository、Profile 以及 Command/Query 等产品领域。
|
||||
- `client/` 承载三个客户端共用的 Command、Query、Event Stream 和连接逻辑。
|
||||
|
||||
不建立泛化的 `shared/` 或 `types/` 大杂烩。类型应尽量由拥有该概念的模块导出,只有真正跨客户端传输的协议进入 `client/`。
|
||||
|
||||
### Extensions 按完整能力纵向划分
|
||||
|
||||
扩展不按 Adapter、Tool、Hook、Behavior 等技术类型横向拆目录,而是按能力组织。例如 `browser/` 可以同时包含浏览器 Adapter、相关 Tools、操作 Hook 和 Browser Behavior。
|
||||
|
||||
第一版不要求每个扩展都是独立 package。只有在依赖、测试、复用、版本或独立发布需求出现后,才将它提升为单独 workspace package。
|
||||
|
||||
### 功能文档不映射源码目录
|
||||
|
||||
`docs/features/` 按产品大功能组织,源码按职责和依赖组织,两者不要求一一对应。
|
||||
|
||||
例如 System Evolution 会同时涉及 Application Runtime、Git 扩展、Runtime Host、Supervisor 和 CLI,不应为了与功能文档对齐而把所有代码放入单个目录。
|
||||
|
||||
### 测试就近放置
|
||||
|
||||
单元测试和模块测试与所属源码放在一起。顶层 `tests/e2e/` 只保存真正跨包、跨进程或跨版本的场景,例如 CLI 到 Runtime、Project 开发、自举发布和版本回退。
|
||||
|
||||
## 运行数据目录
|
||||
|
||||
运行数据不进入源码仓库:
|
||||
|
||||
```text
|
||||
~/.agent/
|
||||
├── tasks/
|
||||
├── registry.json
|
||||
├── extensions/ # 用户本地安装的扩展
|
||||
├── releases/ # Installed Releases
|
||||
├── runtime/
|
||||
└── config/
|
||||
```
|
||||
|
||||
Project 专属 Agent 数据位于项目根目录的 `.agent/`。候选 worktree 和 Installed Release 由 `~/.agent/` 下的运行数据管理,不作为源码仓库中的固定目录。
|
||||
|
||||
## 允许调整的判断标准
|
||||
|
||||
满足以下任一情况时,可以调整目录:
|
||||
|
||||
- 一个目录长期承载了多个不相关职责;
|
||||
- 一项完整能力被迫跨越过多技术分类目录;
|
||||
- 模块需要独立测试、复用、加载、版本或发布;
|
||||
- 现有依赖方向导致循环依赖或核心反向依赖具体功能;
|
||||
- System Evolution 的真实开发过程证明当前结构降低了可理解性或修改效率。
|
||||
|
||||
目录调整后应同步更新本文,但不需要为保持旧规划而保留无价值的兼容层。
|
||||
54
docs/extensions.md
Normal file
54
docs/extensions.md
Normal file
@ -0,0 +1,54 @@
|
||||
# Extension 目录
|
||||
|
||||
具体能力全部由 Extension 实现。Tool、页面、命令和事件监听器只是 Extension 内部组成,不是新的架构类型。
|
||||
|
||||
## 第一版
|
||||
|
||||
```text
|
||||
src/extensions/
|
||||
├── shared/
|
||||
│ ├── workspace/
|
||||
│ ├── agent/
|
||||
│ └── deepseek/
|
||||
└── cli/
|
||||
```
|
||||
|
||||
- `workspace`:Workspace、Conversation 和消息。
|
||||
- `agent`:Run、上下文和 Agent 执行。
|
||||
- `deepseek`:DeepSeek 模型接入。
|
||||
- `cli`:终端输入、命令和展示。
|
||||
|
||||
第一版只静态装配这四个 Extension。
|
||||
|
||||
## 协作
|
||||
|
||||
- 一个明确能力由 Extension 提供,调用方取得后直接调用。
|
||||
- 模型、Tool 等多个实现通过同一扩展点汇集,由业务拥有者选择。
|
||||
- 状态真正发生后再发布事件,监听器只做展示、日志或派生处理。
|
||||
- Extension 只引用其他 Extension 的公开契约。
|
||||
|
||||
## 后续方向
|
||||
|
||||
后续公共能力大致包括:
|
||||
|
||||
```text
|
||||
项目操作与 Git
|
||||
自身进化
|
||||
计划与多 Agent
|
||||
记忆与知识
|
||||
浏览器与本机控制
|
||||
自动化
|
||||
```
|
||||
|
||||
CLI、Web、Desktop 各自先保持一个产品 Extension。远程仓库、外部 Tool 协议、Keychain 等在实际接入时再决定是否独立。
|
||||
|
||||
## 何时拆分
|
||||
|
||||
只有出现以下真实边界时才创建新 Extension:
|
||||
|
||||
- 需要独立启停;
|
||||
- 拥有独立的长期资源或数据;
|
||||
- 存在可替换实现;
|
||||
- 有明确产品或平台边界。
|
||||
|
||||
否则继续留在现有 Extension 内。目录只在能力开始实现时创建,不预建占位代码。
|
||||
@ -1,49 +0,0 @@
|
||||
# 自动化与后台任务
|
||||
|
||||
## 持久后台任务
|
||||
|
||||
**实现阶段:P7**
|
||||
|
||||
- 将 Run 提交为客户端断开后仍能执行的持久任务。
|
||||
- 保存执行状态、下一步动作、关联 Space 和人工介入点。
|
||||
- Runtime 重启后识别未完成任务并按策略恢复或等待用户处理。
|
||||
|
||||
## 提醒与定时执行
|
||||
|
||||
**实现阶段:P7**
|
||||
|
||||
- 创建一次性提醒和指定时间执行的 Agent 任务。
|
||||
- 正确处理时区、设备休眠、Runtime 未运行和错过执行时间。
|
||||
- 将执行结果通过 CLI、Web、Desktop 或系统通知反馈。
|
||||
|
||||
## 周期任务
|
||||
|
||||
**实现阶段:P7**
|
||||
|
||||
- 支持每日、每周及可表达的周期规则。
|
||||
- 管理启停、下次执行、失败重试和避免重复运行。
|
||||
- 每次执行生成独立 Run,并保留周期任务的整体历史。
|
||||
|
||||
## 事件触发自动化
|
||||
|
||||
**实现阶段:P7**
|
||||
|
||||
- 根据文件变化、下载完成、应用状态或其他本地事件触发 Run。
|
||||
- 对频繁事件去抖、合并并限制并发。
|
||||
- 显示触发来源,允许用户临时暂停或禁用自动化。
|
||||
|
||||
## 自动化模板与参数
|
||||
|
||||
**实现阶段:P7**
|
||||
|
||||
- 将成功的脚本、Tool 组合或工作流保存为可复用自动化。
|
||||
- 定义输入参数、默认值、执行条件和所需能力。
|
||||
- 修改模板不会篡改既有执行记录,能够查看版本差异。
|
||||
|
||||
## 自动化执行历史与通知
|
||||
|
||||
**实现阶段:P7**
|
||||
|
||||
- 汇总每个自动化的成功、失败、耗时、产物和人工介入记录。
|
||||
- 对连续失败或异常结果发送通知并暂停后续执行。
|
||||
- 从历史 Run 快速重试或进入对应 Conversation 排查。
|
||||
@ -1,49 +0,0 @@
|
||||
# 浏览器
|
||||
|
||||
## 搜索、导航与页面读取
|
||||
|
||||
**实现阶段:P7**
|
||||
|
||||
- 搜索互联网、打开 URL、读取页面文本和获取页面基本结构。
|
||||
- 管理重定向、加载失败、分页和动态页面等待。
|
||||
- 将页面内容转为适合模型使用且保留来源的结构化结果。
|
||||
|
||||
## 页面提取与站内探索
|
||||
|
||||
**实现阶段:P7**
|
||||
|
||||
- 提取列表、表格、链接、表单和指定区域内容。
|
||||
- 在站内跟随链接完成多页资料收集,并避免无界爬取。
|
||||
- 保存必要截图、页面快照或下载内容作为 Run 产物。
|
||||
|
||||
## DOM 交互与表单操作
|
||||
|
||||
**实现阶段:P7**
|
||||
|
||||
- 点击、输入、选择、滚动、等待和提交表单。
|
||||
- 通过可访问性树、DOM 和稳定定位信息减少脆弱坐标操作。
|
||||
- 提交、购买、发布等真实外部写操作进入确认流程。
|
||||
|
||||
## 标签页、会话与登录态
|
||||
|
||||
**实现阶段:P7**
|
||||
|
||||
- 管理浏览器实例、窗口、标签页、历史和页面间上下文。
|
||||
- 按 Profile 或任务复用已有登录态,同时保持不同任务之间的边界。
|
||||
- 处理登录过期、验证码和必须由用户接管的页面。
|
||||
|
||||
## 上传、下载与文件流转
|
||||
|
||||
**实现阶段:P7**
|
||||
|
||||
- 将 Workspace 文件上传到网页,并将下载内容保存为可追踪 Artifact。
|
||||
- 观察下载状态、文件名、重复文件和失败重试。
|
||||
- 将浏览器文件与 Task/Project Workspace 建立明确关联。
|
||||
|
||||
## 页面视觉理解与混合定位
|
||||
|
||||
**实现阶段:P7**
|
||||
|
||||
- 使用截图和视觉模型理解仅靠 DOM 难以操作的界面。
|
||||
- 组合 DOM、可访问性树、图像和坐标完成定位。
|
||||
- 视觉操作记录截图和动作序列,便于用户检查和失败恢复。
|
||||
@ -1,65 +0,0 @@
|
||||
# CLI
|
||||
|
||||
## Runtime 管理与首次配置
|
||||
|
||||
**实现阶段:P1**
|
||||
|
||||
- 提供 Runtime 启动、连接、状态查询和停止命令。
|
||||
- Runtime 未启动时自动拉起,CLI 退出后 Runtime 保持运行。
|
||||
- 完成 DeepSeek Key、数据目录和首个 Task 的最小首次配置。
|
||||
|
||||
## 自然语言 REPL
|
||||
|
||||
**实现阶段:P1**
|
||||
|
||||
- 在当前 Conversation 中进行流式聊天。
|
||||
- 支持输入历史、多行输入、取消当前请求和清晰的当前上下文提示符。
|
||||
- 终端重开后恢复最近 Space、Conversation 和消息历史。
|
||||
|
||||
## Space 与 Conversation 控制
|
||||
|
||||
**实现阶段:P1**
|
||||
|
||||
- 通过一组一致的斜杠命令创建、列出、切换、重命名和归档 Task/Conversation。
|
||||
- 为 Project 绑定和切换复用相同的导航体验。
|
||||
- 提供帮助、命令错误提示和最近使用列表。
|
||||
|
||||
## Run、Tool 与产物呈现
|
||||
|
||||
**实现阶段:P2**
|
||||
|
||||
- 实时展示模型回复、Tool 调用、Shell 输出、测试结果、错误和最终状态。
|
||||
- 长输出默认摘要,并能查看完整 Run 日志和 Artifact。
|
||||
- 提供 Run 取消、重新执行以及 Project Diff 查看入口。
|
||||
|
||||
## System Evolution 终端流程
|
||||
|
||||
**实现阶段:P3**
|
||||
|
||||
- 在 CLI 中查看候选版本、Diff、测试结果、发布说明和健康检查。
|
||||
- 完成 Git 写操作确认、版本切换和回退操作。
|
||||
- 确保拒绝发布或发布失败后仍能继续处理候选工作区。
|
||||
|
||||
## 非交互与管道模式
|
||||
|
||||
**实现阶段:P4**
|
||||
|
||||
- 支持一次性命令、stdin 输入、stdout 结果、结构化 JSON 输出和可靠退出码。
|
||||
- 允许 Shell 脚本或其他程序创建 Run、等待结果或后台提交。
|
||||
- 交互模式和非交互模式使用同一 Runtime 接口。
|
||||
|
||||
## 全屏 TUI
|
||||
|
||||
**实现阶段:P4**
|
||||
|
||||
- 提供 Space/Conversation 导航、聊天、Run 时间线、日志、产物和 Diff 工作区。
|
||||
- 加入命令面板、搜索、补全、快捷键、主题和可调整布局。
|
||||
- 支持配置、扩展、System Evolution 和后台 Run 管理,不替代快速 REPL。
|
||||
|
||||
## 多 Agent 与记忆工作台
|
||||
|
||||
**实现阶段:P6**
|
||||
|
||||
- 展示 Plan、Step、Child Run、Agent 分工、人工检查点和结果验证。
|
||||
- 管理 Conversation/Space/全局记忆,查看来源并进行编辑或遗忘。
|
||||
- 将 P5/P6 的复杂能力整合到一致的终端交互中。
|
||||
@ -1,57 +0,0 @@
|
||||
# 本地数据与配置
|
||||
|
||||
## 全局数据根目录与 Space 注册表
|
||||
|
||||
**实现阶段:P1**
|
||||
|
||||
- 建立 `~/.agent/` 全局目录、Runtime 数据和 Space 注册表。
|
||||
- 注册表保存 Space 的 ID、类型、名称、真实路径和最近访问,用于定位 Task 与分散的 Project。
|
||||
- 处理首次启动、目录缺失、重复注册和路径不可访问等情况。
|
||||
|
||||
## Space、Conversation 与 Run 文件仓库
|
||||
|
||||
**实现阶段:P1**
|
||||
|
||||
- 使用普通 JSON 保存当前元信息,使用 JSONL 保存消息与追加式 Run 记录。
|
||||
- 支持创建、读取、更新和列出 Space、Conversation、Message、Run。
|
||||
- 采用临时文件替换等简单方式保证单次写入完整,异常文件能够被识别并报告。
|
||||
|
||||
## 统一工作区目录
|
||||
|
||||
**实现阶段:P1**
|
||||
|
||||
- Task 根目录本身就是 Workspace,内部使用 `.agent/` 保存 Agent 数据。
|
||||
- Project 使用相同目录形状,使 Task 可以整体移动并升级为 Project。
|
||||
- 明确工作文件、Agent 元数据和全局注册信息各自的所有权。
|
||||
|
||||
## Run 日志与产物仓库
|
||||
|
||||
**实现阶段:P2**
|
||||
|
||||
- 保存模型调用、Tool 调用、Shell 输出、错误、测试结果和 Diff 等执行记录。
|
||||
- 将日志中的一次性内容与需要长期保留、预览或下载的 Artifact 分开管理。
|
||||
- 支持通过 Conversation、Run 和 Tool 调用定位相关记录与文件。
|
||||
|
||||
## 配置体系与覆盖规则
|
||||
|
||||
**实现阶段:P4**
|
||||
|
||||
- 管理全局、Space、Profile 和客户端配置,并定义清晰的覆盖顺序。
|
||||
- 区分普通配置、凭据引用和运行时临时选项。
|
||||
- 支持查看配置最终值、来源、修改和恢复默认值。
|
||||
|
||||
## 缓存、索引与临时数据
|
||||
|
||||
**实现阶段:P6**
|
||||
|
||||
- 为代码索引、知识检索、网页内容和模型缓存提供统一存放位置。
|
||||
- 缓存可重建且不与原始数据混淆,支持容量统计和按类型清理。
|
||||
- 保持 Project 数据、全局数据和临时数据之间的边界。
|
||||
|
||||
## 数据导入、导出与修复
|
||||
|
||||
**实现阶段:P8**
|
||||
|
||||
- 支持全局或按 Space 导出、导入、备份和恢复。
|
||||
- 提供目录迁移、格式升级、完整性检查和损坏数据修复工具。
|
||||
- 备份过程可验证,恢复前保留现有数据的回退副本。
|
||||
@ -1,51 +0,0 @@
|
||||
# 本机与桌面控制
|
||||
|
||||
本功能描述 Agent 面向电脑的 Tool;它不同于 P10 的 Desktop 客户端。
|
||||
|
||||
## 剪贴板、通知与系统信息
|
||||
|
||||
**实现阶段:P7**
|
||||
|
||||
- 读取和写入剪贴板文本或文件引用。
|
||||
- 发送本地通知并关联触发它的 Run。
|
||||
- 查询时间、网络、磁盘、进程和基础系统状态。
|
||||
|
||||
## 本地文件整理
|
||||
|
||||
**实现阶段:P7**
|
||||
|
||||
- 对用户指定目录执行分类、移动、重命名、去重和下载目录整理。
|
||||
- 先生成变更预览,再执行可能影响大量文件的操作。
|
||||
- 删除优先进入系统回收站,并记录可恢复位置。
|
||||
|
||||
## 应用生命周期与状态
|
||||
|
||||
**实现阶段:P7**
|
||||
|
||||
- 启动、聚焦、退出应用并查询运行状态。
|
||||
- 打开指定文件、URL 或项目位置到对应应用。
|
||||
- 处理应用未安装、无响应和需要用户登录等情况。
|
||||
|
||||
## 窗口与文件定位
|
||||
|
||||
**实现阶段:P7**
|
||||
|
||||
- 查询、切换、移动、调整大小和排列窗口。
|
||||
- 将 Workspace、终端、编辑器和浏览器定位到相关资源。
|
||||
- 在多屏幕和多个同名窗口环境中保持明确目标。
|
||||
|
||||
## 屏幕理解与键鼠操作
|
||||
|
||||
**实现阶段:P7**
|
||||
|
||||
- 截取屏幕或窗口,使用视觉能力识别界面状态。
|
||||
- 执行键盘、鼠标、拖放和快捷键操作。
|
||||
- 提供紧急停止、动作记录和操作前后截图。
|
||||
|
||||
## 系统权限与能力降级
|
||||
|
||||
**实现阶段:P7**
|
||||
|
||||
- 检测辅助功能、录屏、通知和自动化权限。
|
||||
- 指引用户完成必要授权,并在权限缺失时切换到可用能力。
|
||||
- 向 Agent 暴露真实平台能力,避免反复调用不可用 Tool。
|
||||
@ -1,57 +0,0 @@
|
||||
# Desktop
|
||||
|
||||
## Desktop Host 与共享界面
|
||||
|
||||
**实现阶段:P10**
|
||||
|
||||
- 选择 Desktop 框架,复用 Web 的主要界面、状态和 Runtime Client。
|
||||
- 管理应用窗口、深色模式、链接打开和本地导航。
|
||||
- Desktop 不包含第三套 Agent、存储或 Tool 执行逻辑。
|
||||
|
||||
## Runtime 生命周期管理
|
||||
|
||||
**实现阶段:P10**
|
||||
|
||||
- 安装、启动、停止、重启并观察本地 Runtime。
|
||||
- 处理 Runtime 未安装、启动失败、版本不匹配和客户端重连。
|
||||
- 提供健康状态、日志入口和故障恢复操作。
|
||||
|
||||
## 托盘、快捷键与通知
|
||||
|
||||
**实现阶段:P10**
|
||||
|
||||
- 支持系统托盘、开机启动、后台常驻和快速状态查看。
|
||||
- 使用全局快捷键快速唤起输入、当前任务或运行面板。
|
||||
- 将 Run 完成、失败、确认和自动化结果发送为原生通知。
|
||||
|
||||
## 原生确认与任务总览
|
||||
|
||||
**实现阶段:P10**
|
||||
|
||||
- 在应用不位于前台时显示原生确认窗口。
|
||||
- 汇总后台 Run、定时任务、自动化、资源使用和待处理人工介入。
|
||||
- 从通知或托盘直接跳转到对应 Space、Conversation 或 Run。
|
||||
|
||||
## 快速输入与语音交互
|
||||
|
||||
**实现阶段:P10**
|
||||
|
||||
- 通过全局快捷入口快速输入文本、粘贴剪贴板内容或发起语音请求。
|
||||
- 将语音转写、附件和当前前台应用上下文交给同一个 Conversation/Run 流程。
|
||||
- 支持录音状态、取消、转写确认和输出朗读,不建立独立语音会话系统。
|
||||
|
||||
## 系统能力桥接
|
||||
|
||||
**实现阶段:P10**
|
||||
|
||||
- 为剪贴板、窗口、文件选择器、浏览器和系统权限提供原生 Adapter。
|
||||
- Agent 使用的 Tool 仍归本机与桌面控制功能,Desktop 只提供平台桥接。
|
||||
- 处理权限申请、平台差异和应用关闭后的能力可用性。
|
||||
|
||||
## 版本、安装与更新
|
||||
|
||||
**实现阶段:P10**
|
||||
|
||||
- 打包 Desktop、Runtime 和必要资源,提供个人设备安装流程。
|
||||
- 展示 Installed Release、更新、重启、版本切换和回退状态。
|
||||
- 更新失败时保留可启动的旧版本,并与 Supervisor 的版本管理保持一致。
|
||||
@ -1,65 +0,0 @@
|
||||
# 扩展、Hooks 与 Profile
|
||||
|
||||
## 静态扩展装配
|
||||
|
||||
**实现阶段:P1**
|
||||
|
||||
- Runtime Host 通过明确的 composition root 装配首批 Adapter 和 Behavior。
|
||||
- P2 在同一机制中加入 Tool 与基础 Hook,不使用动态扫描或 manifest。
|
||||
- 扩展只能通过 Microkernel 公共接口注册,不能反向依赖 Host 内部实现。
|
||||
|
||||
## 基础 Hook Pipeline
|
||||
|
||||
**实现阶段:P2**
|
||||
|
||||
- 围绕 Tool 调用和 Run 完成提供少量稳定 Hook 点。
|
||||
- Hook 与 Run Event 分离:Hook 执行附加逻辑,Event 记录已经发生的事实。
|
||||
- Hook 失败不会悄然破坏主流程,执行结果进入 Run 日志。
|
||||
|
||||
## Hook 管理与诊断
|
||||
|
||||
**实现阶段:P4**
|
||||
|
||||
- 支持 Hook 顺序、启停、异常隔离、耗时和调用链查看。
|
||||
- 明确哪些 Hook 只观察、哪些可以拒绝或结构化修改调用。
|
||||
- 为日志、摘要、确认、通知和评估提供稳定接缝。
|
||||
|
||||
## 本地扩展加载
|
||||
|
||||
**实现阶段:P4**
|
||||
|
||||
- 加载本地 Tool、Adapter、Hook 和 Behavior,并校验声明与依赖。
|
||||
- 支持安装、启用、禁用、重新加载和错误回退。
|
||||
- 内置模块与外部扩展使用相同的运行接缝,但 Application Runtime 领域不插件化。
|
||||
|
||||
## Profile 组合
|
||||
|
||||
**实现阶段:P4**
|
||||
|
||||
- 声明每种 Space 默认使用的 Behavior、Tools、Hooks、模型和上下文策略。
|
||||
- 支持全局默认、Project/Task/System Evolution Profile 和 Space 级覆盖。
|
||||
- 运行前得到确定的能力集合,并能向用户解释最终配置来源。
|
||||
|
||||
## 扩展开发与版本管理
|
||||
|
||||
**实现阶段:P4**
|
||||
|
||||
- 提供扩展模板、测试工具、调试输出和兼容性检查。
|
||||
- 将扩展变更纳入 System Evolution 的候选、测试和回退流程。
|
||||
- 当内部模块真实长大后,支持从目录提升为独立 package,而不要求一开始拆包。
|
||||
|
||||
## Behavior 与 Skill 能力包
|
||||
|
||||
**实现阶段:P5**
|
||||
|
||||
- 将可复用的 Agent 角色、提示词、上下文策略和工作方法封装成能力包。
|
||||
- 支持 Behavior 组合、参数化和按 Profile 选择。
|
||||
- Skill 复用现有 Tool 和 Run 机制,不建立绕过 Microkernel 的第二套执行系统。
|
||||
|
||||
## 扩展分发
|
||||
|
||||
**实现阶段:P8**
|
||||
|
||||
- 支持本地扩展打包、来源记录、版本锁定、更新和卸载。
|
||||
- 对不兼容或启动失败的扩展提供隔离和回退。
|
||||
- 分发机制服务个人设备与自举,不以建设公共插件市场为前提。
|
||||
@ -1,57 +0,0 @@
|
||||
# 记忆、检索与知识
|
||||
|
||||
## Conversation 摘要与上下文记忆
|
||||
|
||||
**实现阶段:P6**
|
||||
|
||||
- 从长会话中提炼目标、决策、约束、未完成事项和重要结果。
|
||||
- 摘要可查看并能追溯原始消息,更新时不覆盖用户显式纠正。
|
||||
- Context Builder 按当前需求选择摘要与必要原文。
|
||||
|
||||
## Task 与 Project 记忆
|
||||
|
||||
**实现阶段:P6**
|
||||
|
||||
- 跨 Conversation 保存 Space 级事实、规则、架构、决策和经验。
|
||||
- Project 记忆与项目目录绑定,Task 记忆随 Task 升级迁移。
|
||||
- 新会话可以检索相关历史,而不是加载该 Space 的全部内容。
|
||||
|
||||
## 全局个人记忆
|
||||
|
||||
**实现阶段:P6**
|
||||
|
||||
- 保存跨 Space 有效的个人偏好、习惯和长期事实。
|
||||
- 区分用户明确声明与 Agent 从历史中提炼的内容。
|
||||
- 三个客户端共享同一记忆服务和修改结果。
|
||||
|
||||
## 记忆管理与来源
|
||||
|
||||
**实现阶段:P6**
|
||||
|
||||
- 支持查看、编辑、固定、合并、纠错、遗忘和恢复记忆。
|
||||
- 每条记忆保留来源、作用域、更新时间和相关 Conversation/Run。
|
||||
- 处理互相冲突、已过期和不再可信的信息。
|
||||
|
||||
## 全文、向量与混合检索
|
||||
|
||||
**实现阶段:P6**
|
||||
|
||||
- 对消息、Run、项目文档和知识资料建立全文索引。
|
||||
- 在需要语义检索时增加向量索引,并通过混合排序提高准确性。
|
||||
- 返回可解释的来源片段,避免只给出不可核查的记忆结论。
|
||||
|
||||
## 文档导入与个人知识库
|
||||
|
||||
**实现阶段:P6**
|
||||
|
||||
- 导入常见文本、代码、Markdown、PDF 和网页资料。
|
||||
- 管理解析、切分、索引、更新、删除和重复内容。
|
||||
- 回答时引用原始来源,并区分个人记忆与外部资料。
|
||||
|
||||
## 记忆提炼与维护 Hooks
|
||||
|
||||
**实现阶段:P6**
|
||||
|
||||
- 在 Conversation、Run 或 Project 事件后提出摘要与记忆更新。
|
||||
- 定期检测重复、冲突、过期和缺少来源的记忆。
|
||||
- 自动维护不得阻塞正常 Run,用户可以查看和撤销变更。
|
||||
@ -1,57 +0,0 @@
|
||||
# 模型、聊天与 Agent
|
||||
|
||||
## DeepSeek Adapter 与流式聊天
|
||||
|
||||
**实现阶段:P1**
|
||||
|
||||
- 通过 Model Port 接入 DeepSeek API,支持流式文本生成和基础请求配置。
|
||||
- 将模型增量传递给客户端,并在完成后保存用户可见消息。
|
||||
- 处理网络失败、API 错误、中途取消和未完成回复。
|
||||
|
||||
## DefaultAgent 文本执行
|
||||
|
||||
**实现阶段:P1**
|
||||
|
||||
- 实现单一 DefaultAgent Behavior,完成输入、上下文组装、模型调用和最终回答。
|
||||
- Behavior 运行在 Microkernel 中,通过端口使用模型,不直接写入 Repository。
|
||||
- 保持纯聊天 Run 与后续行动 Run 使用一致的执行入口。
|
||||
|
||||
## 系统提示词与上下文组装
|
||||
|
||||
**实现阶段:P1**
|
||||
|
||||
- 组合系统提示词、当前 Space、Conversation 历史和用户输入。
|
||||
- 定义消息角色、顺序和客户端可见内容与模型内部消息的边界。
|
||||
- 为 Project 规则、记忆、附件和子 Agent 上下文预留明确装配位置。
|
||||
|
||||
## 单 Agent Tool Calling
|
||||
|
||||
**实现阶段:P2**
|
||||
|
||||
- 将可用 Tool 描述交给模型,解析 Tool Call 并通过 Tool Gateway 执行。
|
||||
- 将结构化 Tool 结果送回模型,循环直至生成最终回答。
|
||||
- 对未知 Tool、无效参数、执行失败和循环上限给出可恢复反馈。
|
||||
|
||||
## 上下文裁剪与压缩
|
||||
|
||||
**实现阶段:P4**
|
||||
|
||||
- 在超过模型上下文限制前选择、裁剪或压缩历史内容。
|
||||
- 保留关键用户要求、Tool 结果和来源,避免摘要悄然改变任务意图。
|
||||
- 向用户展示发生过的上下文压缩,并允许查看原始历史。
|
||||
|
||||
## 多模型管理与路由
|
||||
|
||||
**实现阶段:P5**
|
||||
|
||||
- 接入多个 Model Adapter,按 Space、Profile、Behavior 或具体 Run 选择模型。
|
||||
- 支持模型能力匹配、失败降级和角色级模型路由。
|
||||
- 统一记录模型标识、Token、延迟和调用结果。
|
||||
|
||||
## 多模态模型能力
|
||||
|
||||
**实现阶段:P7**
|
||||
|
||||
- 支持图片、截图、文件等多模态输入和相应模型能力声明。
|
||||
- 将附件、屏幕内容和浏览器截图安全地组装进模型上下文。
|
||||
- 对不支持某种输入的模型进行能力降级或路由。
|
||||
@ -1,57 +0,0 @@
|
||||
# 可观测性与可靠性
|
||||
|
||||
## Run 时间线与基础日志
|
||||
|
||||
**实现阶段:P1**
|
||||
|
||||
- 记录 Run 状态、模型请求结果、错误和客户端输出时间线。
|
||||
- P2 加入 Tool、Shell、测试、Diff 和 Artifact 事件。
|
||||
- 日志保持可读、可关联且能够从 CLI 定位,不提前建设复杂遥测平台。
|
||||
|
||||
## Runtime 与功能诊断
|
||||
|
||||
**实现阶段:P4**
|
||||
|
||||
- 生成包含 Runtime、配置、扩展、模型、数据目录和最近错误的诊断报告。
|
||||
- 查看 Hook 调用、Tool 耗时、连接状态和异常堆栈。
|
||||
- 支持脱敏后导出诊断信息用于排查或交给 Agent 自己分析。
|
||||
|
||||
## Run 暂停与恢复
|
||||
|
||||
**实现阶段:P5**
|
||||
|
||||
- 在计划、子 Agent 或等待用户输入时持久化可恢复状态。
|
||||
- 区分可安全重放的步骤和已经产生外部副作用的步骤。
|
||||
- Runtime 重启后允许用户继续、跳过、回滚或终止未完成 Run。
|
||||
|
||||
## Runtime 崩溃恢复
|
||||
|
||||
**实现阶段:P8**
|
||||
|
||||
- 检测异常退出、孤儿子进程、未完成写入和悬空 worktree。
|
||||
- 重启后恢复健康状态并列出需要处理的 Run。
|
||||
- 对自动恢复过程保留记录,避免隐藏数据或副作用不一致。
|
||||
|
||||
## 数据完整性与修复
|
||||
|
||||
**实现阶段:P8**
|
||||
|
||||
- 检查注册表、JSON/JSONL、Artifact、索引和实际目录之间的一致性。
|
||||
- 修复可恢复问题,隔离损坏记录,并在操作前生成备份。
|
||||
- 定期验证备份可读取,而不是只确认备份文件存在。
|
||||
|
||||
## 资源、性能与成本监控
|
||||
|
||||
**实现阶段:P8**
|
||||
|
||||
- 统计 Runtime CPU/内存、磁盘占用、队列长度、模型 Token、延迟和费用。
|
||||
- 定位过慢 Tool、异常循环、长期占用的进程和持续增长的数据。
|
||||
- 为清理、并发限制和模型路由提供真实依据。
|
||||
|
||||
## 资源回收
|
||||
|
||||
**实现阶段:P8**
|
||||
|
||||
- 管理 Workspace 临时文件、worktree、日志、缓存、索引和 Artifact 的保留策略。
|
||||
- 在自动清理前保护用户固定内容和仍被 Run 引用的资源。
|
||||
- 提供空间预览、手动清理和可恢复删除。
|
||||
@ -1,57 +0,0 @@
|
||||
# 权限、确认与敏感数据
|
||||
|
||||
## API Key 与基础凭据配置
|
||||
|
||||
**实现阶段:P1**
|
||||
|
||||
- 首版安全读取 DeepSeek API Key,避免写入源码和普通 Run 日志。
|
||||
- 明确环境变量、配置引用和错误提示。
|
||||
- 所有凭据访问集中经过统一接口,为后续 Keychain 做准备。
|
||||
|
||||
## System Evolution Git 确认
|
||||
|
||||
**实现阶段:P3**
|
||||
|
||||
- 对 commit、merge、push 和活动版本切换展示具体对象与 Diff。
|
||||
- 用户明确确认后才能执行,拒绝不会导致候选数据丢失。
|
||||
- 确认结果关联到 Evolution Run 和发布记录。
|
||||
|
||||
## 自动化操作的就地确认
|
||||
|
||||
**实现阶段:P7**
|
||||
|
||||
- 文件批量修改或删除、网页提交、消息发布、系统设置等高影响动作在对应功能中请求确认。
|
||||
- 确认内容描述即将发生的真实副作用,而不是只显示抽象 Tool 名称。
|
||||
- 支持本次允许、拒绝和转为人工接管。
|
||||
|
||||
## 统一 Tool 风险与确认协议
|
||||
|
||||
**实现阶段:P8**
|
||||
|
||||
- 为 Tool 声明读取范围、写入副作用、外部提交和所需系统权限。
|
||||
- 按全局、Profile、Space 和具体 Tool 配置自动允许或询问。
|
||||
- 统一管理待确认、超时、拒绝、恢复和审计记录。
|
||||
|
||||
## Keychain 与凭据生命周期
|
||||
|
||||
**实现阶段:P8**
|
||||
|
||||
- 将模型、网站和外部服务凭据接入系统安全存储。
|
||||
- 支持创建、更新、撤销、失效提示和按能力授权访问。
|
||||
- Agent 只获得调用凭据的能力,不在普通上下文中看到明文。
|
||||
|
||||
## 敏感内容与历史清理
|
||||
|
||||
**实现阶段:P8**
|
||||
|
||||
- 对日志、模型输入输出和 Artifact 中的密钥、Cookie 与个人信息进行识别和脱敏。
|
||||
- 支持用户定位并清除已经写入历史的敏感内容。
|
||||
- 清理过程同步处理缓存、索引、备份策略和来源引用。
|
||||
|
||||
## 远程访问边界
|
||||
|
||||
**实现阶段:P9**
|
||||
|
||||
- Web 远程访问默认关闭,本地访问与远程暴露使用不同配置。
|
||||
- 远程模式提供轻量认证、连接撤销、来源限制和敏感操作再确认。
|
||||
- 不引入多用户系统,但避免无认证地暴露本机 Agent 能力。
|
||||
@ -1,57 +0,0 @@
|
||||
# 计划、工作流与多 Agent
|
||||
|
||||
## Plan 创建、编辑与执行
|
||||
|
||||
**实现阶段:P5**
|
||||
|
||||
- 将复杂目标拆成有顺序和依赖关系的 Step。
|
||||
- 在执行前查看、编辑、重新生成或直接批准计划。
|
||||
- 顺序执行 Step,并将每步的输入、状态、结果和产物关联到父 Run。
|
||||
|
||||
## Step 控制与动态重规划
|
||||
|
||||
**实现阶段:P5**
|
||||
|
||||
- 支持暂停、继续、跳过、重试和修改尚未执行的步骤。
|
||||
- 步骤失败后根据实际结果修订后续计划,而不是机械重复原方案。
|
||||
- 允许 Agent 在关键点等待用户补充信息或确认方向。
|
||||
|
||||
## Child Run 与子 Agent 委派
|
||||
|
||||
**实现阶段:P5**
|
||||
|
||||
- 主 Agent 创建 Child Run,传递清晰目标、上下文和可用能力范围。
|
||||
- 隔离不同子 Agent 的工作上下文并回收结构化结果。
|
||||
- 父 Run 可以观察、取消和处理子 Run 失败。
|
||||
|
||||
## Agent 角色、团队与 Reviewer
|
||||
|
||||
**实现阶段:P5**
|
||||
|
||||
- 支持 Planner、Worker、Reviewer 等 Behavior 角色,但不固定唯一协作模板。
|
||||
- 按任务动态选择角色、模型、Tool 和上下文。
|
||||
- Reviewer 依据测试、Diff 和完成条件给出通过、返工或人工处理结论。
|
||||
|
||||
## 并行与 DAG 调度
|
||||
|
||||
**实现阶段:P5**
|
||||
|
||||
- 并行执行没有依赖且不会争用同一资源的步骤。
|
||||
- 管理 DAG 依赖、并发上限、取消传播、工作区冲突和结果合并。
|
||||
- 并行失败不会造成其他步骤结果丢失或状态不明。
|
||||
|
||||
## 可复用工作流
|
||||
|
||||
**实现阶段:P5**
|
||||
|
||||
- 将稳定的计划和 Agent 协作方式保存为参数化工作流。
|
||||
- 支持从一次成功 Run 提炼模板、再次运行并查看版本变化。
|
||||
- 工作流调用统一的 Behavior、Tool 和 Run,不另建执行引擎。
|
||||
|
||||
## 任务验证与质量评估
|
||||
|
||||
**实现阶段:P5**
|
||||
|
||||
- 为计划和步骤定义可检查的完成条件。
|
||||
- 综合自动测试、Reviewer、外部状态和用户反馈评估结果。
|
||||
- 记录部分完成、回退、返工和最终交付之间的关系。
|
||||
@ -1,57 +0,0 @@
|
||||
# Project 开发助手
|
||||
|
||||
## 项目上下文与规则
|
||||
|
||||
**实现阶段:P2**
|
||||
|
||||
- 读取项目说明、目录结构、已有开发规则和构建配置。
|
||||
- 将相关 Project 上下文装配给 Coding Behavior,而不是一次性塞入全部源码。
|
||||
- 支持用户维护项目专属指令,并在 Run 中显示实际采用的规则。
|
||||
|
||||
## 仓库理解与代码检索
|
||||
|
||||
**实现阶段:P2**
|
||||
|
||||
- 浏览仓库结构、搜索符号与文本、定位入口和相关测试。
|
||||
- 形成面向当前需求的结构概览,避免为每次任务预先建立重型索引。
|
||||
- 在修改前识别影响范围、现有约定和可能的验证方式。
|
||||
|
||||
## 代码修改与 Diff
|
||||
|
||||
**实现阶段:P2**
|
||||
|
||||
- 使用文件与补丁 Tool 修改源码,处理多文件改动和冲突。
|
||||
- 持续展示工作区状态和 Diff,并关联每次修改的需求与 Run。
|
||||
- 保持用户已有未提交改动,不用破坏性 Git 操作覆盖现场。
|
||||
|
||||
## Build、Test、Lint 与验证
|
||||
|
||||
**实现阶段:P2**
|
||||
|
||||
- 发现或配置项目验证命令,运行构建、测试、Lint 和类型检查。
|
||||
- 解析退出状态和关键失败,允许 Agent 修正后重新验证。
|
||||
- 汇总执行过的验证、未执行项和剩余风险。
|
||||
|
||||
## 单 Agent 开发闭环
|
||||
|
||||
**实现阶段:P2**
|
||||
|
||||
- 跑通“理解需求 → 阅读源码 → 修改 → 验证 → 汇报”的完整流程。
|
||||
- 在普通非自身项目中验证该闭环后,才允许用于 System Evolution。
|
||||
- 最终回答包含改动摘要、验证结果、Diff 位置和需要用户决定的问题。
|
||||
|
||||
## Git 与 Worktree 工作流
|
||||
|
||||
**实现阶段:P2**
|
||||
|
||||
- 支持 status、diff、log、branch 和隔离 worktree 的创建、使用、检查与清理。
|
||||
- 候选开发默认在 worktree 中进行,稳定工作区保持可用。
|
||||
- Git 写操作通过明确的工作流执行,不让模型随意拼接高影响命令。
|
||||
|
||||
## 远程仓库、PR 与 CI
|
||||
|
||||
**实现阶段:P7**
|
||||
|
||||
- 读取远程 Issue、PR、Review 和 CI 状态,将其转换为本地开发上下文。
|
||||
- 支持创建提交、推送分支、创建或更新 PR,并展示外部结果。
|
||||
- 所有对外写操作进入统一确认和审计流程。
|
||||
@ -1,49 +0,0 @@
|
||||
# Run 与 Agent 执行
|
||||
|
||||
## Run Context、Record 与生命周期
|
||||
|
||||
**实现阶段:P1**
|
||||
|
||||
- Microkernel 创建内存 Run Context,Application Runtime 保存同 ID 的 Run Record。
|
||||
- 覆盖创建、运行、完成、失败和取消等基础生命周期。
|
||||
- 纯聊天与 Tool 行动共享同一 Run 概念,并关联所属 Space、Conversation 和输入消息。
|
||||
|
||||
## Run Event 与实时输出
|
||||
|
||||
**实现阶段:P1**
|
||||
|
||||
- 产生文本增量、状态变化、错误和完成结果等结构化 Run Event。
|
||||
- 将 Event 同时用于客户端实时展示和 Run 日志,不与 Hook 执行语义混淆。
|
||||
- 保证客户端断开不影响 Runtime 中 Run 的基本记录完整性。
|
||||
|
||||
## Tool 行动执行循环
|
||||
|
||||
**实现阶段:P2**
|
||||
|
||||
- 记录模型请求 Tool、Tool 执行、结果返回模型和最终回答的完整时间线。
|
||||
- 支持一个 Run 内多次顺序 Tool 调用及其错误反馈。
|
||||
- 将 Run 结果、实际副作用和产物建立可追踪关联。
|
||||
|
||||
## Run 控制与人工介入
|
||||
|
||||
**实现阶段:P4**
|
||||
|
||||
- 支持取消、重试、重新执行、暂停等待用户输入和继续运行。
|
||||
- 用户可以在长 Run 中回答 Agent 追问、修改约束或终止后续行动。
|
||||
- 重新执行时明确复用哪些输入、上下文和已经产生的副作用。
|
||||
|
||||
## 后台 Run 与运行队列
|
||||
|
||||
**实现阶段:P5**
|
||||
|
||||
- 支持客户端退出后继续执行、排队、优先级和并发限制。
|
||||
- 恢复连接后可以重新订阅进度、查看结果或取消后台 Run。
|
||||
- 为 Child Run、多 Agent 和自动化任务提供统一调度入口。
|
||||
|
||||
## Run 结果验证
|
||||
|
||||
**实现阶段:P5**
|
||||
|
||||
- 为 Run 定义可验证的完成条件,而不只依赖模型口头宣布完成。
|
||||
- 汇总测试、文件变化、Tool 结果和 Reviewer 结论形成最终结果。
|
||||
- 区分成功、部分完成、需要人工处理和不可继续的失败。
|
||||
@ -1,57 +0,0 @@
|
||||
# Runtime 与系统生命周期
|
||||
|
||||
## 工程基座与依赖边界
|
||||
|
||||
**实现阶段:P1**
|
||||
|
||||
- 建立 TypeScript + Node.js monorepo、统一构建、测试、类型检查和开发命令。
|
||||
- 落实 Supervisor、Runtime Host、Application Runtime、Microkernel、Extensions 和 Clients 的依赖方向。
|
||||
- 保证 Microkernel 不依赖产品领域或具体扩展,客户端不直接依赖 Runtime 内部实现。
|
||||
|
||||
## Runtime Host 与进程生命周期
|
||||
|
||||
**实现阶段:P1**
|
||||
|
||||
- 提供常驻 Node.js Runtime、单实例检测、启动、停止、退出信号和基础健康检查。
|
||||
- 负责读取启动配置、组装 Application Runtime、Microkernel 与首批内置能力。
|
||||
- CLI 在 Runtime 未启动时能够拉起并连接,退出 CLI 不终止 Runtime。
|
||||
|
||||
## Application Runtime
|
||||
|
||||
**实现阶段:P1**
|
||||
|
||||
- 管理 Space、Conversation、持久化 Run Record、当前上下文和本地 Repository。
|
||||
- 作为所有本地数据的唯一写入者,协调客户端请求与 Agent 执行。
|
||||
- 提供基础 Profile 选择和首版串行 Run 策略。
|
||||
|
||||
## Microkernel
|
||||
|
||||
**实现阶段:P1**
|
||||
|
||||
- 创建内存 Run Context,调度 Behavior,传播取消信号并隔离错误。
|
||||
- 提供 Tool Gateway、最小 Hook Pipeline、运行事件和流式输出接缝。
|
||||
- 通过可测试的公共接口运行,不认识 Project、Task、DeepSeek 或具体存储。
|
||||
|
||||
## Runtime Interface
|
||||
|
||||
**实现阶段:P1**
|
||||
|
||||
- 提供 Command、Query、Event Stream 三类客户端交互。
|
||||
- 支持流式文本、Tool 进度、Run 状态和错误事件。
|
||||
- 首版完成本地传输、连接识别、断线处理和协议级错误返回。
|
||||
|
||||
## 运行诊断与环境信息
|
||||
|
||||
**实现阶段:P4**
|
||||
|
||||
- 提供 Runtime 版本、进程、端口、数据目录、已加载能力和健康状态查询。
|
||||
- 支持诊断报告、连接恢复、配置来源追踪和调试模式。
|
||||
- 为 CLI/TUI、Web、Desktop 共享同一套诊断数据。
|
||||
|
||||
## 后台队列与并发运行
|
||||
|
||||
**实现阶段:P5**
|
||||
|
||||
- 从首版串行执行发展为后台 Run、排队、按 Space 并发和资源占用控制。
|
||||
- 支持客户端断开后继续运行、重新订阅和跨子 Run 的取消传播。
|
||||
- 为多 Agent、自动化和长期任务提供统一运行基础。
|
||||
@ -1,49 +0,0 @@
|
||||
# Space、Conversation 与消息
|
||||
|
||||
## Task 生命周期
|
||||
|
||||
**实现阶段:P1**
|
||||
|
||||
- 创建、命名、列出、切换、重命名、归档和删除 Task。
|
||||
- 提供首次启动的默认 Task,并记录最近使用位置。
|
||||
- Task 的工作文件和 Agent 数据随整个 Task 目录移动。
|
||||
|
||||
## Conversation 生命周期
|
||||
|
||||
**实现阶段:P1**
|
||||
|
||||
- 在每个 Space 内创建、命名、切换、重命名、归档和删除 Conversation。
|
||||
- 保存消息顺序、角色、可见内容和关联 Run。
|
||||
- 支持恢复最近 Conversation,并为自动标题保留实现入口。
|
||||
|
||||
## Project 绑定与发现
|
||||
|
||||
**实现阶段:P2**
|
||||
|
||||
- 将本地目录注册为 Project,并初始化或复用根目录中的 `.agent/`。
|
||||
- 支持 Project 移动后的重新定位、解绑和重新发现。
|
||||
- 保证项目源码不复制到 Agent 数据目录,多个会话共享同一个 Project 上下文。
|
||||
|
||||
## Task 升级为 Project
|
||||
|
||||
**实现阶段:P4**
|
||||
|
||||
- 将整个 Task 目录移动至用户指定位置并改为 Project。
|
||||
- 更新 Space 类型、全局注册路径和相关上下文引用。
|
||||
- 处理目标目录冲突、Git 初始化与升级失败回退。
|
||||
|
||||
## Conversation 搜索、分支与引用
|
||||
|
||||
**实现阶段:P4**
|
||||
|
||||
- 跨 Space 搜索 Conversation、消息和 Run 结果。
|
||||
- 支持编辑或重新执行历史输入,并通过会话分支保留原始对话。
|
||||
- 在新会话中引用其他 Conversation 或 Run,并保留来源导航。
|
||||
|
||||
## 会话附件
|
||||
|
||||
**实现阶段:P6**
|
||||
|
||||
- 在消息中附加文件、图片、代码片段和其他本地资料。
|
||||
- 管理附件复制或引用策略、预览、上下文注入和删除。
|
||||
- 为 Web/Desktop 和多模态模型共享统一附件语义。
|
||||
@ -1,49 +0,0 @@
|
||||
# System Evolution
|
||||
|
||||
## System Evolution Project
|
||||
|
||||
**实现阶段:P3**
|
||||
|
||||
- 将 Agent 自身源码仓库注册为特殊 Project,并应用 System Evolution Profile。
|
||||
- 用户在该 Project 的 Conversation 中提出自身需求和反馈,不要求 Agent 自动发现问题。
|
||||
- 复用 P2 已验证的单 Agent 开发能力,而不是建设第二套修改系统。
|
||||
|
||||
## Source、Candidate 与 Release 隔离
|
||||
|
||||
**实现阶段:P3**
|
||||
|
||||
- 区分源码仓库、候选 worktree 和 Supervisor 实际启动的 Installed Release。
|
||||
- 当前运行版本不会被候选修改直接覆盖。
|
||||
- 候选失败、放弃或重新修改不会影响稳定 Runtime。
|
||||
|
||||
## 自身修改与验证流程
|
||||
|
||||
**实现阶段:P3**
|
||||
|
||||
- 将用户需求转化为候选 worktree 中的代码修改。
|
||||
- 运行构建、测试、类型检查和最小启动验证。
|
||||
- 输出变更说明、完整 Diff、验证结果和已知风险。
|
||||
|
||||
## 发布提案与 Git 确认
|
||||
|
||||
**实现阶段:P3**
|
||||
|
||||
- 将候选版本整理为可审核的发布提案。
|
||||
- commit、merge、push 等 Git 写操作必须由用户明确确认。
|
||||
- 拒绝发布时保留候选上下文,允许继续修改或安全清理。
|
||||
|
||||
## Supervisor 发布与回退
|
||||
|
||||
**实现阶段:P3**
|
||||
|
||||
- 将通过确认的代码构建为不可变 Installed Release。
|
||||
- 原子切换活动版本、重启 Runtime、执行健康检查并完成客户端重连。
|
||||
- 新版本失败时自动切回旧版本,并支持用户主动回退演练。
|
||||
|
||||
## Evolution Record 与版本评估
|
||||
|
||||
**实现阶段:P4**
|
||||
|
||||
- 关联需求、Conversation、Run、worktree、Diff、测试、Git 提交、Release 和回退结果。
|
||||
- 保存基准任务和回归验证,用于比较候选与稳定版本。
|
||||
- 从历史进化记录中查看某项能力为何加入、如何验证和何时发布。
|
||||
@ -1,65 +0,0 @@
|
||||
# Tools、Workspace 与产物
|
||||
|
||||
## Tool 契约与统一 Gateway
|
||||
|
||||
**实现阶段:P2**
|
||||
|
||||
- 定义 Tool 名称、说明、参数 Schema、结构化结果和错误协议。
|
||||
- 支持注册、列出、调用和取消 Tool,并将所有调用纳入 Run Context。
|
||||
- 在统一 Gateway 中触发基础 Hook、日志和输出事件。
|
||||
|
||||
## Workspace 作用域与路径解析
|
||||
|
||||
**实现阶段:P2**
|
||||
|
||||
- 为 Task 和 Project 解析工作区根目录与当前工作目录。
|
||||
- 统一处理相对路径、绝对路径、符号链接和工作区外路径。
|
||||
- 将 Tool 产生的实际文件与 `.agent/` 元数据明确区分。
|
||||
|
||||
## 文件操作 Toolset
|
||||
|
||||
**实现阶段:P2**
|
||||
|
||||
- 支持目录浏览、文件读取、文本搜索、写入、补丁、移动、复制和创建目录。
|
||||
- 处理编码、大文件、二进制文件和修改冲突。
|
||||
- 返回适合模型继续工作的结构化摘要,同时保留完整结果入口。
|
||||
|
||||
## Shell 与进程 Toolset
|
||||
|
||||
**实现阶段:P2**
|
||||
|
||||
- 执行 Shell 命令,流式返回 stdout/stderr、退出码和耗时。
|
||||
- 支持超时、取消、工作目录、环境变量和基础后台进程处理。
|
||||
- 将模型临时生成的命令或脚本文本记录进 Run,不额外建立脚本库。
|
||||
|
||||
## Tool 进度与产物收集
|
||||
|
||||
**实现阶段:P2**
|
||||
|
||||
- 统一表达 Tool 开始、进度、完成、失败和取消。
|
||||
- 自动识别测试报告、截图、下载文件和其他可预览产物并关联到 Run。
|
||||
- 支持产物查看、固定、导出和后续 Tool 引用。
|
||||
|
||||
## 交互式终端会话
|
||||
|
||||
**实现阶段:P4**
|
||||
|
||||
- 使用 PTY 运行需要持续输入、终端控制序列或长时间驻留的命令。
|
||||
- 允许 Agent 与用户查看会话、发送输入、转入后台、重新连接和终止进程。
|
||||
- 将交互式会话与普通 Shell Tool、Run 日志和资源回收统一管理。
|
||||
|
||||
## Tool 能力范围与分组
|
||||
|
||||
**实现阶段:P4**
|
||||
|
||||
- 按 Profile、Space 和 Behavior 选择向模型暴露的 Tool 集合。
|
||||
- 支持 Tool 别名、分组、描述优化和能力发现,避免一次向模型暴露过多接口。
|
||||
- 为权限策略和不同平台能力降级提供统一元数据。
|
||||
|
||||
## 外部 Tool 协议接入
|
||||
|
||||
**实现阶段:P4**
|
||||
|
||||
- 通过 Adapter 接入 MCP 等外部 Tool 服务,将其映射到统一 Tool 契约。
|
||||
- 管理服务连接、能力同步、错误转换和生命周期。
|
||||
- 外部 Tool 与内置 Tool 使用一致的日志、确认和 Run 关联。
|
||||
@ -1,57 +0,0 @@
|
||||
# Web
|
||||
|
||||
## Web 客户端基础与 Runtime 连接
|
||||
|
||||
**实现阶段:P9**
|
||||
|
||||
- 选择 Web 框架并建立路由、状态管理和 Runtime Client。
|
||||
- 使用 Runtime 已有的 Command、Query、Event Stream,不重复实现 Agent 逻辑。
|
||||
- 处理连接状态、断线重连、版本不匹配和实时事件恢复。
|
||||
|
||||
## Space、Conversation 与聊天
|
||||
|
||||
**实现阶段:P9**
|
||||
|
||||
- 创建、切换和管理 Task、Project、Conversation 与消息附件。
|
||||
- 支持流式聊天、历史导航、搜索和会话分支。
|
||||
- 在桌面与移动尺寸下保持可用的响应式交互。
|
||||
|
||||
## Run 与 Agent 工作台
|
||||
|
||||
**实现阶段:P9**
|
||||
|
||||
- 展示 Run 时间线、Tool、Plan、Step、Child Run、Agent 角色、日志和 Artifact。
|
||||
- 提供取消、暂停、继续、重试、人工回答和计划调整。
|
||||
- 长任务在页面刷新或断线后能够恢复观察。
|
||||
|
||||
## Project 开发与 Review
|
||||
|
||||
**实现阶段:P9**
|
||||
|
||||
- 展示项目文件、代码修改、Build/Test/Lint 结果和 Git Diff。
|
||||
- 完成候选修改 Review、评论、确认和返回 Agent 继续修改。
|
||||
- 查看远程 Issue、PR 和 CI 结果。
|
||||
|
||||
## System Evolution 控制台
|
||||
|
||||
**实现阶段:P9**
|
||||
|
||||
- 查看自身候选版本、测试、Diff、Evolution Record 和 Release。
|
||||
- 执行 Git 确认、版本发布、健康检查、重启和回退。
|
||||
- 清晰区分稳定版本、当前运行版本和未发布候选版本。
|
||||
|
||||
## 记忆、扩展与自动化管理
|
||||
|
||||
**实现阶段:P9**
|
||||
|
||||
- 查看和编辑记忆、知识来源、Profile、模型、Tool、Hook、Behavior 与扩展。
|
||||
- 管理提醒、周期任务、事件触发器和自动化执行历史。
|
||||
- 提供统一配置和确认中心。
|
||||
|
||||
## 本地与远程访问体验
|
||||
|
||||
**实现阶段:P9**
|
||||
|
||||
- 默认服务本机使用,并提供明确的远程启用流程。
|
||||
- 支持会话过期、认证、设备连接管理和敏感操作保护。
|
||||
- 保持远程客户端不直接访问本地数据文件或 Tool 实现。
|
||||
@ -1,75 +1,46 @@
|
||||
# 实现阶段
|
||||
# 路线图
|
||||
|
||||
阶段只描述实际开发顺序,不等同于功能分类。大功能会跨越多个阶段,每个小功能点在对应功能文档中标记自己的实现阶段。
|
||||
路线图只描述大的实现顺序。安全、日志、取消和数据保护从每个阶段开始就随能力一起实现。
|
||||
|
||||
## P1:本地可交互基座
|
||||
## P1:能持续聊天
|
||||
|
||||
建立 TypeScript + Node.js monorepo、常驻 Runtime、本地文件数据、Task、Conversation、基础 Run、DeepSeek 流式聊天和 CLI REPL。
|
||||
建立最小 Kernel、第一版 Extension 和集中式数据目录。
|
||||
|
||||
完成标准:Runtime 可独立启动,CLI 可连接并管理多个 Task/Conversation,退出重进后能够继续聊天。
|
||||
完成标志:CLI 可以管理 Workspace 与 Conversation,流式聊天,并在重启后继续历史。
|
||||
|
||||
## P2:可行动的开发助手
|
||||
## P2:能操作项目
|
||||
|
||||
加入 Tool Gateway、文件与 Shell Tool、单 Agent Tool Calling、Project、源码修改、测试、Git/Worktree、Run 日志和终端执行观察。
|
||||
加入文件、Shell、Tool Calling、Git、Worktree、验证和最小权限确认。
|
||||
|
||||
完成标准:Agent 能在一个普通项目的隔离 worktree 中理解需求、修改源码、执行测试并展示可审核的 Diff。
|
||||
完成标志:Agent 可以在普通代码项目中完成一次可审核的修改与测试闭环。
|
||||
|
||||
## P3:最小自举闭环
|
||||
## P3:能修改自己
|
||||
|
||||
建立 System Evolution Project、候选 worktree、构建测试、变更审核、Git 写操作确认、Installed Release、Supervisor 切换和失败回退。
|
||||
加入隔离候选、构建验证、发布确认、版本切换和失败回退。
|
||||
|
||||
完成标准:Agent 为自身增加一个完整的小能力,经用户确认发布后由新 Runtime 接管,并能够实际回退到旧版本。
|
||||
完成标志:Agent 可以安全完成一次自身小能力的修改与发布。
|
||||
|
||||
P3 之后的功能默认优先通过该自举流程实现。
|
||||
## P4:能处理复杂工作
|
||||
|
||||
## P4:终端产品与扩展能力成熟
|
||||
加入多模型、计划、多 Agent、工作流、长期记忆和知识检索。
|
||||
|
||||
完善斜杠命令、非交互 CLI、全屏 TUI、Runtime 诊断、后台 Run、Hook Runtime、Profile、扩展加载与开发体验、多模型以及完整进化记录。
|
||||
完成标志:复杂目标可以持续执行,跨 Conversation 找到相关上下文,并给出可验证结果。
|
||||
|
||||
完成标准:终端可以承担日常工作和 System Evolution 的完整操作;新增能力通常能够落入清晰的 Adapter、Tool、Hook 或 Behavior 边界。
|
||||
## P5:能管理电脑
|
||||
|
||||
## P5:计划与多 Agent
|
||||
加入浏览器、本机控制、多模态、提醒、定时任务和自动化。
|
||||
|
||||
实现 Plan/Step、连续执行、失败重规划、Child Run、角色协作、并行/DAG 调度、结果验证和模型路由。
|
||||
完成标志:Agent 可以完成开发之外的高频电脑事务,并可靠重复执行。
|
||||
|
||||
完成标准:复杂目标可以被拆解、连续执行、动态调整,并由多个 Agent 分工完成与验证。
|
||||
## P6:适合长期使用
|
||||
|
||||
## P6:分层记忆与个人知识
|
||||
实现 Web 和 Desktop,完善远程访问、诊断、备份迁移、资源清理、凭据保护和版本维护。
|
||||
|
||||
实现 Conversation、Task/Project、全局个人记忆,记忆管理、全文/向量检索、文档导入和项目知识沉淀。
|
||||
完成标志:三个产品共用一套稳定 Runtime 和数据,能够长期日常使用。
|
||||
|
||||
完成标准:新会话能够准确找到相关历史和来源,用户可以查看、纠正或删除记忆。
|
||||
## 执行原则
|
||||
|
||||
## P7:电脑管家与个人自动化
|
||||
|
||||
实现浏览器、本机文件与应用、剪贴板、通知、屏幕理解和操作、定时/后台任务、多模态输入与可复用自动化流程。
|
||||
|
||||
完成标准:Agent 可以从终端完成开发之外的高频电脑事务,并沉淀重复工作。
|
||||
|
||||
## P8:可靠性、安全与数据治理
|
||||
|
||||
系统化建设崩溃恢复、数据备份和迁移、统一审批、风险策略、密钥与敏感数据、资源清理和版本维护。
|
||||
|
||||
完成标准:强能力不会因为进程崩溃、数据损坏、误操作或错误升级造成不可恢复的后果。
|
||||
|
||||
这不意味着早期阶段不处理错误和风险:每一阶段都必须具备支撑自身闭环的最小日志、取消、确认或回退;P8 负责将它们统一为完整体系。
|
||||
|
||||
## P9:Web 产品线
|
||||
|
||||
基于同一 Runtime 建设 Web 会话、运行观察、项目、记忆、扩展、审批和 System Evolution 管理,并支持受控远程访问。
|
||||
|
||||
完成标准:Web 能独立承担日常交互与执行观察,但不复制 Agent 逻辑和数据状态。
|
||||
|
||||
## P10:Desktop 产品线
|
||||
|
||||
复用 Web 界面并增加 Runtime 生命周期、托盘、快捷键、通知、原生确认、系统能力桥接、安装和更新体验。
|
||||
|
||||
完成标准:Desktop 提供 Web 无法自然提供的本地常驻和原生集成能力,而不是第三套 Agent 实现。
|
||||
|
||||
## 功能文档规则
|
||||
|
||||
- `docs/features/` 中每个文件对应一个大功能。
|
||||
- 大功能不设置阶段,因为其中的小功能可能分布在多个阶段。
|
||||
- 每个小功能必须有可独立验证的实质性交付;只需改动很少代码的细节应合并到相邻功能点。
|
||||
- 小功能只记录实现阶段和功能范围,不维护 todo/done、候选、已确认或待确认状态。
|
||||
- 进入某个阶段时,再围绕该阶段的小功能制定提交级实现计划。
|
||||
- 每个阶段开始时再拆提交级任务。
|
||||
- 先跑通最短链路,再根据真实压力抽象。
|
||||
- 新增普通能力默认修改 Extension;只有通用运行机制才修改 Kernel。
|
||||
- 路线图允许随实践调整,不维护小功能状态表。
|
||||
|
||||
39
docs/source-layout.md
Normal file
39
docs/source-layout.md
Normal file
@ -0,0 +1,39 @@
|
||||
# 源码目录
|
||||
|
||||
项目保持一个 TypeScript package:
|
||||
|
||||
```text
|
||||
llm-to-agent/
|
||||
├── src/
|
||||
│ ├── kernel/
|
||||
│ ├── extensions/
|
||||
│ │ ├── shared/
|
||||
│ │ ├── cli/
|
||||
│ │ ├── web/
|
||||
│ │ └── desktop/
|
||||
│ ├── products/
|
||||
│ └── main.ts
|
||||
├── docs/
|
||||
├── tests/
|
||||
├── tooling/
|
||||
├── package.json
|
||||
└── tsconfig.json
|
||||
```
|
||||
|
||||
## 目录职责
|
||||
|
||||
- `kernel/`:只保存 Extension 运行机制。
|
||||
- `extensions/shared/`:跨产品使用的具体能力。
|
||||
- `extensions/cli|web|desktop/`:三条产品线各自的输入、展示和平台集成。
|
||||
- `products/`:静态选择每个产品启用哪些 Extension,不写业务逻辑。
|
||||
- `tests/`:只保存跨 Extension、跨进程或跨版本测试;普通测试跟随源码。
|
||||
- `tooling/`:构建、开发和发布辅助。
|
||||
|
||||
Extension 的具体划分见 [Extension 目录](extensions.md)。
|
||||
|
||||
## 放置规则
|
||||
|
||||
- 业务概念和流程进入 Extension,不为了复用方便放进 Kernel。
|
||||
- 产品私有实现不能互相依赖,真实复用出现后再提升到 `shared/`。
|
||||
- 目录和文件只在真实代码出现时创建,不预建占位层级。
|
||||
- 当前保持单包;只有构建、平台依赖、独立分发或进程隔离造成实际问题时才拆包。
|
||||
16
package.json
16
package.json
@ -4,17 +4,17 @@
|
||||
"private": true,
|
||||
"type": "module",
|
||||
"scripts": {
|
||||
"dev": "pnpm --filter @llm-to-agent/cli dev",
|
||||
"dev:old": "pnpm --filter @llm-to-agent/old dev",
|
||||
"dev:core": "pnpm --filter @llm-to-agent/core dev",
|
||||
"dev:server": "pnpm --filter @llm-to-agent/server dev",
|
||||
"dev:web": "pnpm --filter @llm-to-agent/web dev",
|
||||
"dev:desktop": "pnpm --filter @llm-to-agent/desktop dev",
|
||||
"test": "pnpm --filter @llm-to-agent/tests test"
|
||||
"dev": "node --import tsx src/main.ts cli",
|
||||
"dev:runtime": "node --import tsx src/main.ts cli web desktop",
|
||||
"dev:cli": "node --import tsx src/main.ts cli",
|
||||
"dev:web": "node --import tsx src/main.ts web",
|
||||
"dev:desktop": "node --import tsx src/main.ts desktop",
|
||||
"typecheck": "tsc --noEmit",
|
||||
"test": "node --import tsx --test \"src/**/*.test.ts\""
|
||||
},
|
||||
"devDependencies": {
|
||||
"@types/node": "^25.9.1",
|
||||
"tsx": "^4.x",
|
||||
"typescript": "^5.x"
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@ -1,19 +0,0 @@
|
||||
{
|
||||
"name": "@llm-to-agent/cli",
|
||||
"version": "0.1.0",
|
||||
"type": "module",
|
||||
"private": true,
|
||||
"main": "./src/index.ts",
|
||||
"scripts": {
|
||||
"dev": "tsx src/index.ts"
|
||||
},
|
||||
"dependencies": {
|
||||
"@llm-to-agent/core": "workspace:*",
|
||||
"@llm-to-agent/plugins-builtin": "workspace:*"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@types/node": "^25.9.1",
|
||||
"tsx": "^4.x",
|
||||
"typescript": "^5.x"
|
||||
}
|
||||
}
|
||||
@ -1 +0,0 @@
|
||||
console.log('🚀 LLM-to-Agent CLI 初始化已完成,待开发。\n');
|
||||
@ -1,19 +0,0 @@
|
||||
{
|
||||
"name": "@llm-to-agent/core",
|
||||
"version": "0.1.0",
|
||||
"type": "module",
|
||||
"private": true,
|
||||
"main": "./src/index.ts",
|
||||
"types": "./src/index.ts",
|
||||
"scripts": {
|
||||
"dev": "tsx src/index.ts"
|
||||
},
|
||||
"dependencies": {
|
||||
"@llm-to-agent/types": "workspace:*"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@types/node": "^25.9.1",
|
||||
"tsx": "^4.x",
|
||||
"typescript": "^5.x"
|
||||
}
|
||||
}
|
||||
@ -1,6 +0,0 @@
|
||||
export * from '@llm-to-agent/types';
|
||||
|
||||
export const init = () => {
|
||||
console.log('🚀 Core 入口已初始化完成,待开发。');
|
||||
}
|
||||
|
||||
@ -1,7 +0,0 @@
|
||||
{
|
||||
"compilerOptions": {
|
||||
"module": "esnext",
|
||||
"moduleResolution": "bundler",
|
||||
"types": ["node"]
|
||||
}
|
||||
}
|
||||
@ -1,19 +0,0 @@
|
||||
{
|
||||
"name": "@llm-to-agent/desktop",
|
||||
"version": "0.1.0",
|
||||
"type": "module",
|
||||
"private": true,
|
||||
"main": "./src/main.ts",
|
||||
"scripts": {
|
||||
"dev": "tsx './src/main.ts'"
|
||||
},
|
||||
"dependencies": {
|
||||
"@llm-to-agent/core": "workspace:*",
|
||||
"@llm-to-agent/plugins-builtin": "workspace:*"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@types/node": "^25.9.1",
|
||||
"tsx": "^4.x",
|
||||
"typescript": "^5.x"
|
||||
}
|
||||
}
|
||||
@ -1 +0,0 @@
|
||||
console.log('🚀 Desktop 入口已初始化完成,待开发。');
|
||||
@ -1,7 +0,0 @@
|
||||
{
|
||||
"compilerOptions": {
|
||||
"module": "esnext",
|
||||
"moduleResolution": "bundler",
|
||||
"types": ["node"]
|
||||
}
|
||||
}
|
||||
@ -1,7 +0,0 @@
|
||||
{
|
||||
"compilerOptions": {
|
||||
"module": "esnext",
|
||||
"moduleResolution": "bundler",
|
||||
"types": ["node"]
|
||||
}
|
||||
}
|
||||
@ -1,16 +0,0 @@
|
||||
{
|
||||
"name": "@llm-to-agent/plugins-builtin",
|
||||
"version": "0.1.0",
|
||||
"type": "module",
|
||||
"private": true,
|
||||
"main": "./src/index.ts",
|
||||
"dependencies": {
|
||||
"@llm-to-agent/core": "workspace:*",
|
||||
"@llm-to-agent/types": "workspace:*"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@types/node": "^25.9.1",
|
||||
"tsx": "^4.x",
|
||||
"typescript": "^5.x"
|
||||
}
|
||||
}
|
||||
@ -1 +0,0 @@
|
||||
export default {}
|
||||
@ -1,7 +0,0 @@
|
||||
{
|
||||
"compilerOptions": {
|
||||
"module": "esnext",
|
||||
"moduleResolution": "bundler",
|
||||
"types": ["node"]
|
||||
}
|
||||
}
|
||||
@ -1,19 +0,0 @@
|
||||
{
|
||||
"name": "@llm-to-agent/server",
|
||||
"version": "0.1.0",
|
||||
"type": "module",
|
||||
"private": true,
|
||||
"main": "./src/index.ts",
|
||||
"scripts": {
|
||||
"dev": "tsx src/index.ts"
|
||||
},
|
||||
"dependencies": {
|
||||
"@llm-to-agent/core": "workspace:*",
|
||||
"@llm-to-agent/plugins-builtin": "workspace:*"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@types/node": "^25.9.1",
|
||||
"tsx": "^4.x",
|
||||
"typescript": "^5.x"
|
||||
}
|
||||
}
|
||||
@ -1 +0,0 @@
|
||||
console.log('🚀 Server 入口已初始化完成,待开发。');
|
||||
@ -1,7 +0,0 @@
|
||||
{
|
||||
"compilerOptions": {
|
||||
"module": "esnext",
|
||||
"moduleResolution": "bundler",
|
||||
"types": ["node"]
|
||||
}
|
||||
}
|
||||
@ -1,19 +0,0 @@
|
||||
{
|
||||
"name": "@llm-to-agent/tests",
|
||||
"version": "0.1.0",
|
||||
"type": "module",
|
||||
"private": true,
|
||||
"scripts": {
|
||||
"test": "tsx --test src/**/*.test.ts"
|
||||
},
|
||||
"dependencies": {
|
||||
"@llm-to-agent/types": "workspace:*",
|
||||
"@llm-to-agent/core": "workspace:*",
|
||||
"@llm-to-agent/plugins-builtin": "workspace:*"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@types/node": "^25.9.1",
|
||||
"tsx": "^4.x",
|
||||
"typescript": "^5.x"
|
||||
}
|
||||
}
|
||||
@ -1,17 +0,0 @@
|
||||
import { describe, it } from 'node:test';
|
||||
import assert from 'node:assert/strict';
|
||||
|
||||
import { init } from '@llm-to-agent/core';
|
||||
|
||||
describe('@llm-to-agent/core', () => {
|
||||
|
||||
it('init() 不抛异常', () => {
|
||||
assert.doesNotThrow(() => init());
|
||||
});
|
||||
|
||||
it('core 通过 re-export 暴露 types', async () => {
|
||||
const mod = await import('@llm-to-agent/core');
|
||||
assert.equal(typeof mod.init, 'function', '应导出 init 函数');
|
||||
});
|
||||
|
||||
});
|
||||
@ -1,11 +0,0 @@
|
||||
import { describe, it } from 'node:test';
|
||||
import assert from 'node:assert/strict';
|
||||
|
||||
describe('@llm-to-agent/plugins-builtin', () => {
|
||||
|
||||
it('入口文件可正常 import', async () => {
|
||||
const mod = await import('@llm-to-agent/plugins-builtin');
|
||||
assert.ok(mod.default !== undefined);
|
||||
});
|
||||
|
||||
});
|
||||
@ -1,57 +0,0 @@
|
||||
import { describe, it } from 'node:test';
|
||||
import assert from 'node:assert/strict';
|
||||
|
||||
import { Message, ToolCall, ToolDef, PluginManifest, SchedulerConfig, EventName, Listener } from '@llm-to-agent/types';
|
||||
|
||||
describe('@llm-to-agent/types', () => {
|
||||
|
||||
it('Message 类型可正常构造', () => {
|
||||
const msg: Message = { role: 'user', content: 'hello' };
|
||||
assert.equal(msg.role, 'user');
|
||||
assert.equal(msg.content, 'hello');
|
||||
});
|
||||
|
||||
it('Message 支持 tool_calls 和 tool_call_id', () => {
|
||||
const tc: ToolCall = { id: '1', function: { name: 'test', arguments: '{}' } };
|
||||
const msg: Message = { role: 'assistant', content: '', tool_calls: [tc] };
|
||||
assert.equal(msg.tool_calls![0].id, '1');
|
||||
});
|
||||
|
||||
it('ToolDef 可正常构造', () => {
|
||||
const tool: ToolDef = {
|
||||
type: 'function',
|
||||
function: {
|
||||
name: 'weather',
|
||||
description: '查询天气',
|
||||
parameters: { type: 'object', properties: {}, required: [] },
|
||||
},
|
||||
};
|
||||
assert.equal(tool.function.name, 'weather');
|
||||
});
|
||||
|
||||
it('PluginManifest 类型完整', () => {
|
||||
const manifest: PluginManifest = {
|
||||
name: '@agent/test',
|
||||
version: '1.0.0',
|
||||
type: 'tool',
|
||||
provides: ['test-tool'],
|
||||
entry: './index.ts',
|
||||
platforms: ['cli', 'desktop'],
|
||||
};
|
||||
assert.equal(manifest.type, 'tool');
|
||||
assert.deepEqual(manifest.platforms, ['cli', 'desktop']);
|
||||
});
|
||||
|
||||
it('SchedulerConfig 可正常构造', () => {
|
||||
const config: SchedulerConfig = { maxSteps: 5 };
|
||||
assert.equal(config.maxSteps, 5);
|
||||
});
|
||||
|
||||
it('EventName 和 Listener 类型定义正确', () => {
|
||||
const name: EventName = 'llm:call';
|
||||
const fn: Listener = (data: any) => data;
|
||||
assert.equal(typeof fn, 'function');
|
||||
assert.equal(name, 'llm:call');
|
||||
});
|
||||
|
||||
});
|
||||
@ -1,7 +0,0 @@
|
||||
{
|
||||
"compilerOptions": {
|
||||
"module": "esnext",
|
||||
"moduleResolution": "bundler",
|
||||
"types": ["node"]
|
||||
}
|
||||
}
|
||||
@ -1,12 +0,0 @@
|
||||
{
|
||||
"name": "@llm-to-agent/types",
|
||||
"version": "0.1.0",
|
||||
"type": "module",
|
||||
"private": true,
|
||||
"main": "./src/index.ts",
|
||||
"types": "./src/index.ts",
|
||||
"scripts": {},
|
||||
"devDependencies": {
|
||||
"typescript": "^5.x"
|
||||
}
|
||||
}
|
||||
@ -1,46 +0,0 @@
|
||||
// ===== Agent 消息 =====
|
||||
|
||||
export interface Message {
|
||||
role: 'system' | 'user' | 'assistant' | 'tool';
|
||||
content: string;
|
||||
tool_calls?: ToolCall[];
|
||||
tool_call_id?: string;
|
||||
}
|
||||
|
||||
export interface ToolCall {
|
||||
id: string;
|
||||
function: { name: string; arguments: string };
|
||||
}
|
||||
|
||||
// ===== 工具 =====
|
||||
|
||||
export interface ToolDef {
|
||||
type: 'function';
|
||||
function: {
|
||||
name: string;
|
||||
description: string;
|
||||
parameters: Record<string, unknown>;
|
||||
};
|
||||
}
|
||||
|
||||
// ===== 插件 =====
|
||||
|
||||
export interface PluginManifest {
|
||||
name: string;
|
||||
version: string;
|
||||
type: 'provider' | 'tool' | 'hook' | 'prompt';
|
||||
provides: string | string[];
|
||||
entry: string;
|
||||
platforms?: ('cli' | 'desktop' | 'web')[];
|
||||
}
|
||||
|
||||
// ===== 事件总线 =====
|
||||
|
||||
export type EventName = string;
|
||||
export type Listener = (data: any) => any | Promise<any>;
|
||||
|
||||
// ===== 调度器 =====
|
||||
|
||||
export interface SchedulerConfig {
|
||||
maxSteps?: number;
|
||||
}
|
||||
@ -1,7 +0,0 @@
|
||||
{
|
||||
"compilerOptions": {
|
||||
"module": "esnext",
|
||||
"moduleResolution": "bundler",
|
||||
"types": []
|
||||
}
|
||||
}
|
||||
@ -1,14 +0,0 @@
|
||||
<!DOCTYPE html>
|
||||
<html lang="zh-CN">
|
||||
<head>
|
||||
<meta charset="UTF-8" />
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
|
||||
<title>LLM-to-Agent Web</title>
|
||||
</head>
|
||||
<body>
|
||||
<div id="app">
|
||||
<h1>🚀 LLM-to-Agent Web</h1>
|
||||
<p>占位页面,待实现 React 聊天界面。</p>
|
||||
</div>
|
||||
</body>
|
||||
</html>
|
||||
@ -1,10 +0,0 @@
|
||||
{
|
||||
"name": "@llm-to-agent/web",
|
||||
"version": "0.1.0",
|
||||
"type": "module",
|
||||
"private": true,
|
||||
"scripts": {
|
||||
"dev": "echo 'TODO: vite dev'",
|
||||
"build": "echo 'TODO: vite build'"
|
||||
}
|
||||
}
|
||||
329
pnpm-lock.yaml
generated
Normal file
329
pnpm-lock.yaml
generated
Normal file
@ -0,0 +1,329 @@
|
||||
lockfileVersion: '9.0'
|
||||
|
||||
settings:
|
||||
autoInstallPeers: true
|
||||
excludeLinksFromLockfile: false
|
||||
|
||||
importers:
|
||||
|
||||
.:
|
||||
devDependencies:
|
||||
'@types/node':
|
||||
specifier: ^25.9.1
|
||||
version: 25.9.5
|
||||
tsx:
|
||||
specifier: ^4.x
|
||||
version: 4.23.1
|
||||
typescript:
|
||||
specifier: ^5.x
|
||||
version: 5.9.3
|
||||
|
||||
packages:
|
||||
|
||||
'@esbuild/aix-ppc64@0.28.1':
|
||||
resolution: {integrity: sha512-Svl7tq8k/08+p6CXPpRjQ1fKX+1odH/BQbb48fV6fj3CWHhsoIOoY87w1oHXm0qEpkIK3ZfVgp0hed3XBXzXMQ==}
|
||||
engines: {node: '>=18'}
|
||||
cpu: [ppc64]
|
||||
os: [aix]
|
||||
|
||||
'@esbuild/android-arm64@0.28.1':
|
||||
resolution: {integrity: sha512-34EGEbCIAgosYz6goLcopX6Mo7NyGv9tfwEM2/7Ce2VcVRk568iSvniGWcUXIy7wEDR1wzolcxcriFVrWYcwBg==}
|
||||
engines: {node: '>=18'}
|
||||
cpu: [arm64]
|
||||
os: [android]
|
||||
|
||||
'@esbuild/android-arm@0.28.1':
|
||||
resolution: {integrity: sha512-0k2F129Xdio1TdJfzJ8sy1Q47vUD2NnwdhiAf7drUN1EBTfPf4hsFCtmMgu/6m8JSzsBrlmVjudMBQqOfG8usQ==}
|
||||
engines: {node: '>=18'}
|
||||
cpu: [arm]
|
||||
os: [android]
|
||||
|
||||
'@esbuild/android-x64@0.28.1':
|
||||
resolution: {integrity: sha512-dbwY7ltSMDWsRatcRpCnES4F+im88OCUgGZjy52shC7GqHRE/cYlxNbB4Z4UpJswpcc4Qxd2oE/ufM0p61IKng==}
|
||||
engines: {node: '>=18'}
|
||||
cpu: [x64]
|
||||
os: [android]
|
||||
|
||||
'@esbuild/darwin-arm64@0.28.1':
|
||||
resolution: {integrity: sha512-TZbWkQY7kvTAXbXUT7uVACR5cMHsDiSz9z7ZKAX/RTq/WJEk3QyRr0wZpNhBDX+/0CtdqUIJlOiodQcta6tY3Q==}
|
||||
engines: {node: '>=18'}
|
||||
cpu: [arm64]
|
||||
os: [darwin]
|
||||
|
||||
'@esbuild/darwin-x64@0.28.1':
|
||||
resolution: {integrity: sha512-zfdzgK9ACBNZLI/CyHTOx81SyNbM6YXn7rxSgX97VjyiPl9W1i4Ka4fgKECEoFCKGpvBj5qArWIGgQjOwkgskQ==}
|
||||
engines: {node: '>=18'}
|
||||
cpu: [x64]
|
||||
os: [darwin]
|
||||
|
||||
'@esbuild/freebsd-arm64@0.28.1':
|
||||
resolution: {integrity: sha512-wG2EA8ENdEI0qhkSZMjfqrdY+ziCYCPMmtZjjIwOmXFjmyzEHn+UUxk5of+SYsjtfs3VpnlC7QLzSI5hY/rOAw==}
|
||||
engines: {node: '>=18'}
|
||||
cpu: [arm64]
|
||||
os: [freebsd]
|
||||
|
||||
'@esbuild/freebsd-x64@0.28.1':
|
||||
resolution: {integrity: sha512-i7dZ9vQgnvSCzi/rYCXNgtF/U+eKZNJBzu3eTQbRgHnM7tNSizLOkRFAl3qzVc/Op/u5YkHHa4pf/3DOYHthLQ==}
|
||||
engines: {node: '>=18'}
|
||||
cpu: [x64]
|
||||
os: [freebsd]
|
||||
|
||||
'@esbuild/linux-arm64@0.28.1':
|
||||
resolution: {integrity: sha512-yHs+0uc8+nvEAfAfxrWQKK5peSNzBc4PegcMO0EJ2hT71uA7vB8Ihg2e77R2P7SG5uYjPbHlLLmve4LLLRCf0g==}
|
||||
engines: {node: '>=18'}
|
||||
cpu: [arm64]
|
||||
os: [linux]
|
||||
|
||||
'@esbuild/linux-arm@0.28.1':
|
||||
resolution: {integrity: sha512-qVXBOHQS+d5Y722GwJzJUtOLlX7km3CraOaGormF1pDtPd2C/l1SHRPgjLunLGe51Sh5YYWKMFDyV4SxgMQYTQ==}
|
||||
engines: {node: '>=18'}
|
||||
cpu: [arm]
|
||||
os: [linux]
|
||||
|
||||
'@esbuild/linux-ia32@0.28.1':
|
||||
resolution: {integrity: sha512-d1z4ZuP0ajrfz/FhGT4vv278rX8KnPPJx8i5+AtK7TYbx9Le9F1hyzurZpkEyjkGa9dUGhQow4C1NmeGvqxN2w==}
|
||||
engines: {node: '>=18'}
|
||||
cpu: [ia32]
|
||||
os: [linux]
|
||||
|
||||
'@esbuild/linux-loong64@0.28.1':
|
||||
resolution: {integrity: sha512-M5sRjUVZrkm1OAPR3dlOYzNmN+loZKGVi1VUQGrwuqLcbR6qeAz+famMhjASeH3YVKvZz+zT1jlh/keC3Rj/lg==}
|
||||
engines: {node: '>=18'}
|
||||
cpu: [loong64]
|
||||
os: [linux]
|
||||
|
||||
'@esbuild/linux-mips64el@0.28.1':
|
||||
resolution: {integrity: sha512-mRObBZeHh2OxcBFPWE/FjylkRgZdYuiTR3vaTozquCGOH14iP9oN4x4Ge81CoIDYQrXmIxpFumJBu5MtZpnQJQ==}
|
||||
engines: {node: '>=18'}
|
||||
cpu: [mips64el]
|
||||
os: [linux]
|
||||
|
||||
'@esbuild/linux-ppc64@0.28.1':
|
||||
resolution: {integrity: sha512-slScBsMAb3GFDcdrCgLwZtPYRoH2H/youv10QiZyRjmsP48fznoveWytSgCI/R0ZcUgpc0ZhIUEx6LHts8yrfQ==}
|
||||
engines: {node: '>=18'}
|
||||
cpu: [ppc64]
|
||||
os: [linux]
|
||||
|
||||
'@esbuild/linux-riscv64@0.28.1':
|
||||
resolution: {integrity: sha512-kw0owk1o0GFETUJyW0jc0G4Yzs0BHZn0JDZ8JRT088vjJYX777BAs1fDGxAC+q831qOs2DTC96mNsG2opdfyyQ==}
|
||||
engines: {node: '>=18'}
|
||||
cpu: [riscv64]
|
||||
os: [linux]
|
||||
|
||||
'@esbuild/linux-s390x@0.28.1':
|
||||
resolution: {integrity: sha512-/lAIjX8aYFRByhh6L5rYtPEDRqa9de/4V/juOXcta5frjvzXO4/sqEtyytse0g3zZFuWu5cDN0MkLz2qRDD2Ag==}
|
||||
engines: {node: '>=18'}
|
||||
cpu: [s390x]
|
||||
os: [linux]
|
||||
|
||||
'@esbuild/linux-x64@0.28.1':
|
||||
resolution: {integrity: sha512-u/anNYF2mmVOEDwLtnQ1wOr3EZ9sTNGLWrsYGYwHWzGA3Si84IOkHXlbWTD1NB+9/1lcnweYKO54uhxZydNzfA==}
|
||||
engines: {node: '>=18'}
|
||||
cpu: [x64]
|
||||
os: [linux]
|
||||
|
||||
'@esbuild/netbsd-arm64@0.28.1':
|
||||
resolution: {integrity: sha512-oks0DYbLwWMmaakTsCb+zL4E+aHRVLom9IJZOAthMQEPiQmydXHkziYEsGYRx0uNV/IjEKGAV941JzH02pflqw==}
|
||||
engines: {node: '>=18'}
|
||||
cpu: [arm64]
|
||||
os: [netbsd]
|
||||
|
||||
'@esbuild/netbsd-x64@0.28.1':
|
||||
resolution: {integrity: sha512-aeL6lAnN89Hz43Mlh1G8ARasbuoYvSITDEx0tHh5b7jJnHcssqgjy9Yx430GDpmCa6OyrKoS0aNRjKundRizGg==}
|
||||
engines: {node: '>=18'}
|
||||
cpu: [x64]
|
||||
os: [netbsd]
|
||||
|
||||
'@esbuild/openbsd-arm64@0.28.1':
|
||||
resolution: {integrity: sha512-MEFJe5C3R8pwXdZ5Y21oo6m7ePiS0d9pWucn99O/wvyJZChoIQKrQDxKrGeW8F5+T0okTHesAmDeiHDTIq0V/Q==}
|
||||
engines: {node: '>=18'}
|
||||
cpu: [arm64]
|
||||
os: [openbsd]
|
||||
|
||||
'@esbuild/openbsd-x64@0.28.1':
|
||||
resolution: {integrity: sha512-i/ZLIOafE0Z8cI/XANJAixoJL/uRAoS2xOA3rb0xN+KK0K177cMAsQYkzHtBrtMXAKuAc7HGgcWiZ/sRC1Nxgw==}
|
||||
engines: {node: '>=18'}
|
||||
cpu: [x64]
|
||||
os: [openbsd]
|
||||
|
||||
'@esbuild/openharmony-arm64@0.28.1':
|
||||
resolution: {integrity: sha512-ge+Z7EXFNt2BO1oAMsVpiQ8EwndV9i1xXerAeTIK7AtPs3bKFXQM7nlRxDSIUIMeueR1CNXxqztLzdNeReKBJg==}
|
||||
engines: {node: '>=18'}
|
||||
cpu: [arm64]
|
||||
os: [openharmony]
|
||||
|
||||
'@esbuild/sunos-x64@0.28.1':
|
||||
resolution: {integrity: sha512-BEjgtECkL3vY+SaSQ6nzVfiALUeFxpawyp8Jmf5PtYhf1Ug40N1h/hxlhts+f1FvSvarEigdxS3BlSMI2PJLcQ==}
|
||||
engines: {node: '>=18'}
|
||||
cpu: [x64]
|
||||
os: [sunos]
|
||||
|
||||
'@esbuild/win32-arm64@0.28.1':
|
||||
resolution: {integrity: sha512-lCv9eK/H6ZJWbE7bh2nw54CZ9M2nupBxJcTsdk/QQnWkdSjKGuxmmH8/GWrlT1eMmZfn4dGcCjRte397WqfQXA==}
|
||||
engines: {node: '>=18'}
|
||||
cpu: [arm64]
|
||||
os: [win32]
|
||||
|
||||
'@esbuild/win32-ia32@0.28.1':
|
||||
resolution: {integrity: sha512-zvb/mB2bSCoJOpoCBgYKKpX6YM6mJBlBUVUtVj41DlZJVEB6/0CKlRYxP5wWl1C1ILiCoAU5wZZ4q1P3qeS6Eg==}
|
||||
engines: {node: '>=18'}
|
||||
cpu: [ia32]
|
||||
os: [win32]
|
||||
|
||||
'@esbuild/win32-x64@0.28.1':
|
||||
resolution: {integrity: sha512-bm4Mowrv+GXMlpWX++EcXw/iLyd1o3+bJkC2DkWXYVvgZCqD/bSj9ctZeAMC3cIxgjRVR2Dufaiu4YPxr5gW1A==}
|
||||
engines: {node: '>=18'}
|
||||
cpu: [x64]
|
||||
os: [win32]
|
||||
|
||||
'@types/node@25.9.5':
|
||||
resolution: {integrity: sha512-OScDchr2fwuUmWdf4kZ9h7PcJiYDVInhJizG/biAq3cAvqwYktuy/TYGGdZNMtNTFUP7rnb0NU4TUdm82kt4Rg==}
|
||||
|
||||
esbuild@0.28.1:
|
||||
resolution: {integrity: sha512-HrJrvZv5ayxBzPfwphOoNzkzOIIlifzk0KJrGK2c8R4+LKpMtpYLQeUdjnwjWv/LZlkH2laZk+4w78pi99D4Vw==}
|
||||
engines: {node: '>=18'}
|
||||
hasBin: true
|
||||
|
||||
fsevents@2.3.3:
|
||||
resolution: {integrity: sha512-5xoDfX+fL7faATnagmWPpbFtwh/R77WmMMqqHGS65C3vvB0YHrgF+B1YmZ3441tMj5n63k0212XNoJwzlhffQw==}
|
||||
engines: {node: ^8.16.0 || ^10.6.0 || >=11.0.0}
|
||||
os: [darwin]
|
||||
|
||||
tsx@4.23.1:
|
||||
resolution: {integrity: sha512-GQHnkIfxyx1wYCOS/wonik5MVRZU9hi1TEZmzGZSCJB1y9YgoZ8H6itNE/u4suE+yLmOzuE4E5S4TZ/ZX2wcWQ==}
|
||||
engines: {node: '>=18.0.0'}
|
||||
hasBin: true
|
||||
|
||||
typescript@5.9.3:
|
||||
resolution: {integrity: sha512-jl1vZzPDinLr9eUt3J/t7V6FgNEw9QjvBPdysz9KfQDD41fQrC2Y4vKQdiaUpFT4bXlb1RHhLpp8wtm6M5TgSw==}
|
||||
engines: {node: '>=14.17'}
|
||||
hasBin: true
|
||||
|
||||
undici-types@7.24.6:
|
||||
resolution: {integrity: sha512-WRNW+sJgj5OBN4/0JpHFqtqzhpbnV0GuB+OozA9gCL7a993SmU+1JBZCzLNxYsbMfIeDL+lTsphD5jN5N+n0zg==}
|
||||
|
||||
snapshots:
|
||||
|
||||
'@esbuild/aix-ppc64@0.28.1':
|
||||
optional: true
|
||||
|
||||
'@esbuild/android-arm64@0.28.1':
|
||||
optional: true
|
||||
|
||||
'@esbuild/android-arm@0.28.1':
|
||||
optional: true
|
||||
|
||||
'@esbuild/android-x64@0.28.1':
|
||||
optional: true
|
||||
|
||||
'@esbuild/darwin-arm64@0.28.1':
|
||||
optional: true
|
||||
|
||||
'@esbuild/darwin-x64@0.28.1':
|
||||
optional: true
|
||||
|
||||
'@esbuild/freebsd-arm64@0.28.1':
|
||||
optional: true
|
||||
|
||||
'@esbuild/freebsd-x64@0.28.1':
|
||||
optional: true
|
||||
|
||||
'@esbuild/linux-arm64@0.28.1':
|
||||
optional: true
|
||||
|
||||
'@esbuild/linux-arm@0.28.1':
|
||||
optional: true
|
||||
|
||||
'@esbuild/linux-ia32@0.28.1':
|
||||
optional: true
|
||||
|
||||
'@esbuild/linux-loong64@0.28.1':
|
||||
optional: true
|
||||
|
||||
'@esbuild/linux-mips64el@0.28.1':
|
||||
optional: true
|
||||
|
||||
'@esbuild/linux-ppc64@0.28.1':
|
||||
optional: true
|
||||
|
||||
'@esbuild/linux-riscv64@0.28.1':
|
||||
optional: true
|
||||
|
||||
'@esbuild/linux-s390x@0.28.1':
|
||||
optional: true
|
||||
|
||||
'@esbuild/linux-x64@0.28.1':
|
||||
optional: true
|
||||
|
||||
'@esbuild/netbsd-arm64@0.28.1':
|
||||
optional: true
|
||||
|
||||
'@esbuild/netbsd-x64@0.28.1':
|
||||
optional: true
|
||||
|
||||
'@esbuild/openbsd-arm64@0.28.1':
|
||||
optional: true
|
||||
|
||||
'@esbuild/openbsd-x64@0.28.1':
|
||||
optional: true
|
||||
|
||||
'@esbuild/openharmony-arm64@0.28.1':
|
||||
optional: true
|
||||
|
||||
'@esbuild/sunos-x64@0.28.1':
|
||||
optional: true
|
||||
|
||||
'@esbuild/win32-arm64@0.28.1':
|
||||
optional: true
|
||||
|
||||
'@esbuild/win32-ia32@0.28.1':
|
||||
optional: true
|
||||
|
||||
'@esbuild/win32-x64@0.28.1':
|
||||
optional: true
|
||||
|
||||
'@types/node@25.9.5':
|
||||
dependencies:
|
||||
undici-types: 7.24.6
|
||||
|
||||
esbuild@0.28.1:
|
||||
optionalDependencies:
|
||||
'@esbuild/aix-ppc64': 0.28.1
|
||||
'@esbuild/android-arm': 0.28.1
|
||||
'@esbuild/android-arm64': 0.28.1
|
||||
'@esbuild/android-x64': 0.28.1
|
||||
'@esbuild/darwin-arm64': 0.28.1
|
||||
'@esbuild/darwin-x64': 0.28.1
|
||||
'@esbuild/freebsd-arm64': 0.28.1
|
||||
'@esbuild/freebsd-x64': 0.28.1
|
||||
'@esbuild/linux-arm': 0.28.1
|
||||
'@esbuild/linux-arm64': 0.28.1
|
||||
'@esbuild/linux-ia32': 0.28.1
|
||||
'@esbuild/linux-loong64': 0.28.1
|
||||
'@esbuild/linux-mips64el': 0.28.1
|
||||
'@esbuild/linux-ppc64': 0.28.1
|
||||
'@esbuild/linux-riscv64': 0.28.1
|
||||
'@esbuild/linux-s390x': 0.28.1
|
||||
'@esbuild/linux-x64': 0.28.1
|
||||
'@esbuild/netbsd-arm64': 0.28.1
|
||||
'@esbuild/netbsd-x64': 0.28.1
|
||||
'@esbuild/openbsd-arm64': 0.28.1
|
||||
'@esbuild/openbsd-x64': 0.28.1
|
||||
'@esbuild/openharmony-arm64': 0.28.1
|
||||
'@esbuild/sunos-x64': 0.28.1
|
||||
'@esbuild/win32-arm64': 0.28.1
|
||||
'@esbuild/win32-ia32': 0.28.1
|
||||
'@esbuild/win32-x64': 0.28.1
|
||||
|
||||
fsevents@2.3.3:
|
||||
optional: true
|
||||
|
||||
tsx@4.23.1:
|
||||
dependencies:
|
||||
esbuild: 0.28.1
|
||||
optionalDependencies:
|
||||
fsevents: 2.3.3
|
||||
|
||||
typescript@5.9.3: {}
|
||||
|
||||
undici-types@7.24.6: {}
|
||||
@ -1,2 +0,0 @@
|
||||
packages:
|
||||
- 'packages/*'
|
||||
10
src/extensions/README.md
Normal file
10
src/extensions/README.md
Normal file
@ -0,0 +1,10 @@
|
||||
# Extensions
|
||||
|
||||
除 Kernel 的稳定运行机制外,所有具体能力都在这里实现。
|
||||
|
||||
- `shared/` 保存不依赖产品界面的公共能力;
|
||||
- `cli/`、`web/`、`desktop/` 保存三条产品线各自的输入、展示、协议和系统集成;
|
||||
- 产品线私有扩展之间不能互相依赖;两个产品需要的实现应提升到 `shared/`;
|
||||
- Kernel 不导入本目录中的任何具体扩展。
|
||||
|
||||
新增扩展时直接实现 `Extension`。只有真实能力开始开发时才创建对应源码目录,不再预建空 package 或 `export {}` 文件。
|
||||
3
src/extensions/cli/README.md
Normal file
3
src/extensions/cli/README.md
Normal file
@ -0,0 +1,3 @@
|
||||
# CLI extensions
|
||||
|
||||
这里保存 REPL、终端渲染、斜杠命令和 TUI 等 CLI 私有能力。CLI 扩展把终端输入提交给 Kernel,并把 Run 事件转换成终端输出。
|
||||
3
src/extensions/desktop/README.md
Normal file
3
src/extensions/desktop/README.md
Normal file
@ -0,0 +1,3 @@
|
||||
# Desktop extensions
|
||||
|
||||
这里保存原生桥接、窗口、托盘、快捷键、通知与更新等 Desktop 私有能力。与 Web 共用的界面逻辑应放入 `extensions/shared/`。
|
||||
5
src/extensions/shared/README.md
Normal file
5
src/extensions/shared/README.md
Normal file
@ -0,0 +1,5 @@
|
||||
# Shared extensions
|
||||
|
||||
这里保存模型、Workspace、Shell、Git、记忆、规划、多 Agent、浏览器、自动化和 System Evolution 等跨产品能力。
|
||||
|
||||
共享扩展的源码只依赖 Kernel 契约;需要协作时通过其他共享扩展注册的公开能力运行,不能认识 CLI、Web、Desktop 的界面与传输细节。
|
||||
3
src/extensions/web/README.md
Normal file
3
src/extensions/web/README.md
Normal file
@ -0,0 +1,3 @@
|
||||
# Web extensions
|
||||
|
||||
这里保存 HTTP 接入、事件流、路由与 Web 页面等 Web 私有能力。Web 扩展负责在网络交互和 Kernel 运行接口之间转换。
|
||||
62
src/kernel/events.ts
Normal file
62
src/kernel/events.ts
Normal file
@ -0,0 +1,62 @@
|
||||
import type { Awaitable } from "./extension";
|
||||
|
||||
declare const eventTypeValue: unique symbol;
|
||||
|
||||
export interface EventType<T> {
|
||||
readonly key: symbol;
|
||||
readonly name: string;
|
||||
readonly [eventTypeValue]: (value: T) => T;
|
||||
}
|
||||
|
||||
export type EventHandler<T> = (payload: T) => Awaitable<void>;
|
||||
export type Unsubscribe = () => void;
|
||||
|
||||
export interface EventDelivery {
|
||||
readonly errors: readonly unknown[];
|
||||
}
|
||||
|
||||
export function defineEventType<T>(name: string): EventType<T> {
|
||||
return Object.freeze({ key: Symbol(name), name }) as EventType<T>;
|
||||
}
|
||||
|
||||
export class EventBus {
|
||||
readonly #listeners = new Map<symbol, Set<EventHandler<unknown>>>();
|
||||
|
||||
on<T>(event: EventType<T>, handler: EventHandler<T>): Unsubscribe {
|
||||
const listeners = this.#listeners.get(event.key) ?? new Set();
|
||||
listeners.add(handler as EventHandler<unknown>);
|
||||
this.#listeners.set(event.key, listeners);
|
||||
|
||||
return () => {
|
||||
listeners.delete(handler as EventHandler<unknown>);
|
||||
|
||||
if (listeners.size === 0) {
|
||||
this.#listeners.delete(event.key);
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
async emit<T>(event: EventType<T>, payload: T): Promise<EventDelivery> {
|
||||
const listeners = this.#listeners.get(event.key);
|
||||
|
||||
if (!listeners) {
|
||||
return Object.freeze({ errors: Object.freeze([]) });
|
||||
}
|
||||
|
||||
const errors: unknown[] = [];
|
||||
|
||||
for (const listener of [...listeners]) {
|
||||
try {
|
||||
await listener(payload);
|
||||
} catch (error) {
|
||||
errors.push(error);
|
||||
}
|
||||
}
|
||||
|
||||
return Object.freeze({ errors: Object.freeze(errors) });
|
||||
}
|
||||
|
||||
clear(): void {
|
||||
this.#listeners.clear();
|
||||
}
|
||||
}
|
||||
34
src/kernel/extension.ts
Normal file
34
src/kernel/extension.ts
Normal file
@ -0,0 +1,34 @@
|
||||
import type { EventType, EventHandler, Unsubscribe } from "./events";
|
||||
|
||||
export type Awaitable<T> = T | Promise<T>;
|
||||
|
||||
declare const extensionPointType: unique symbol;
|
||||
|
||||
export interface ExtensionPoint<T> {
|
||||
readonly key: symbol;
|
||||
readonly name: string;
|
||||
readonly [extensionPointType]: (value: T) => T;
|
||||
}
|
||||
|
||||
export function defineExtensionPoint<T>(name: string): ExtensionPoint<T> {
|
||||
return Object.freeze({ key: Symbol(name), name }) as ExtensionPoint<T>;
|
||||
}
|
||||
|
||||
export interface ExtensionSetupContext {
|
||||
add<T>(point: ExtensionPoint<T>, value: T): void;
|
||||
on<T>(event: EventType<T>, handler: EventHandler<T>): Unsubscribe;
|
||||
}
|
||||
|
||||
export interface ExtensionRuntimeContext {
|
||||
all<T>(point: ExtensionPoint<T>): readonly T[];
|
||||
on<T>(event: EventType<T>, handler: EventHandler<T>): Unsubscribe;
|
||||
}
|
||||
|
||||
export interface Extension {
|
||||
readonly id: string;
|
||||
setup(context: ExtensionSetupContext): Awaitable<void>;
|
||||
start?(context: ExtensionRuntimeContext): Awaitable<void>;
|
||||
stop?(context: ExtensionRuntimeContext): Awaitable<void>;
|
||||
}
|
||||
|
||||
export type ExtensionFactory = () => Extension;
|
||||
4
src/kernel/index.ts
Normal file
4
src/kernel/index.ts
Normal file
@ -0,0 +1,4 @@
|
||||
export * from "./events";
|
||||
export * from "./extension";
|
||||
export * from "./kernel";
|
||||
export * from "./registry";
|
||||
222
src/kernel/kernel.test.ts
Normal file
222
src/kernel/kernel.test.ts
Normal file
@ -0,0 +1,222 @@
|
||||
import assert from "node:assert/strict";
|
||||
import test from "node:test";
|
||||
|
||||
import {
|
||||
EventBus,
|
||||
Kernel,
|
||||
defineEventType,
|
||||
defineExtensionPoint,
|
||||
type EventType,
|
||||
type Extension,
|
||||
type ExtensionPoint,
|
||||
type ExtensionRuntimeContext,
|
||||
type ExtensionSetupContext,
|
||||
} from "./index";
|
||||
|
||||
test("sets up every extension before starting them and stops in reverse order", async () => {
|
||||
const calls: string[] = [];
|
||||
const values = defineExtensionPoint<string>("test.values");
|
||||
|
||||
const first: Extension = {
|
||||
id: "first",
|
||||
setup(context) {
|
||||
calls.push("first.setup");
|
||||
context.add(values, "first");
|
||||
},
|
||||
start(context) {
|
||||
calls.push(`first.start:${context.all(values).join(",")}`);
|
||||
},
|
||||
stop() {
|
||||
calls.push("first.stop");
|
||||
},
|
||||
};
|
||||
|
||||
const second: Extension = {
|
||||
id: "second",
|
||||
setup(context) {
|
||||
calls.push("second.setup");
|
||||
context.add(values, "second");
|
||||
},
|
||||
start(context) {
|
||||
calls.push(`second.start:${context.all(values).join(",")}`);
|
||||
},
|
||||
stop() {
|
||||
calls.push("second.stop");
|
||||
},
|
||||
};
|
||||
|
||||
const kernel = new Kernel().use(first, second);
|
||||
await kernel.start();
|
||||
await kernel.stop();
|
||||
|
||||
assert.deepEqual(calls, [
|
||||
"first.setup",
|
||||
"second.setup",
|
||||
"first.start:first,second",
|
||||
"second.start:first,second",
|
||||
"second.stop",
|
||||
"first.stop",
|
||||
]);
|
||||
});
|
||||
|
||||
test("rejects duplicate extension ids", () => {
|
||||
const extension: Extension = { id: "duplicate", setup() {} };
|
||||
const kernel = new Kernel().use(extension);
|
||||
|
||||
assert.throws(() => kernel.use(extension), /already installed/);
|
||||
});
|
||||
|
||||
test("installs extension batches atomically", () => {
|
||||
const first: Extension = { id: "first", setup() {} };
|
||||
const duplicate: Extension = { id: "first", setup() {} };
|
||||
const kernel = new Kernel();
|
||||
|
||||
assert.throws(() => kernel.use(first, duplicate), /already installed/);
|
||||
assert.deepEqual(kernel.installedExtensionIds, []);
|
||||
});
|
||||
|
||||
test("clears registrations when setup fails", async () => {
|
||||
const values = defineExtensionPoint<string>("test.failed-setup");
|
||||
const kernel = new Kernel().use({
|
||||
id: "broken-setup",
|
||||
setup(context) {
|
||||
context.add(values, "temporary");
|
||||
throw new Error("setup failed");
|
||||
},
|
||||
});
|
||||
|
||||
await assert.rejects(kernel.start(), /setup failed/);
|
||||
assert.equal(kernel.state, "stopped");
|
||||
assert.deepEqual(kernel.all(values), []);
|
||||
});
|
||||
|
||||
test("stops every active extension and reaches stopped state after cleanup errors", async () => {
|
||||
const calls: string[] = [];
|
||||
const kernel = new Kernel().use(
|
||||
{
|
||||
id: "first",
|
||||
setup() {},
|
||||
stop() {
|
||||
calls.push("first.stop");
|
||||
},
|
||||
},
|
||||
{
|
||||
id: "second",
|
||||
setup() {},
|
||||
stop() {
|
||||
calls.push("second.stop");
|
||||
throw new Error("cleanup failed");
|
||||
},
|
||||
},
|
||||
);
|
||||
|
||||
await kernel.start();
|
||||
await assert.rejects(kernel.stop(), /failed to stop/);
|
||||
|
||||
assert.equal(kernel.state, "stopped");
|
||||
assert.deepEqual(calls, ["second.stop", "first.stop"]);
|
||||
});
|
||||
|
||||
test("preserves startup and cleanup failures while settling in stopped state", async () => {
|
||||
const kernel = new Kernel().use({
|
||||
id: "broken-start",
|
||||
setup() {},
|
||||
start() {
|
||||
throw new Error("start failed");
|
||||
},
|
||||
stop() {
|
||||
throw new Error("rollback failed");
|
||||
},
|
||||
});
|
||||
|
||||
let error: unknown;
|
||||
|
||||
try {
|
||||
await kernel.start();
|
||||
} catch (caught) {
|
||||
error = caught;
|
||||
}
|
||||
|
||||
assert(error instanceof AggregateError);
|
||||
assert.equal(error.errors.length, 2);
|
||||
assert.match(String(error.errors[0]), /start failed/);
|
||||
assert.match(String(error.errors[1]), /rollback failed/);
|
||||
assert.equal(kernel.state, "stopped");
|
||||
});
|
||||
|
||||
test("delivers an event to every listener before reporting listener failures", async () => {
|
||||
const bus = new EventBus();
|
||||
const event = defineEventType<string>("test.delivery");
|
||||
const received: string[] = [];
|
||||
|
||||
bus.on(event, () => {
|
||||
throw new Error("listener failed");
|
||||
});
|
||||
bus.on(event, (payload) => {
|
||||
received.push(payload);
|
||||
});
|
||||
|
||||
const delivery = await bus.emit(event, "delivered");
|
||||
|
||||
assert.deepEqual(received, ["delivered"]);
|
||||
assert.equal(delivery.errors.length, 1);
|
||||
assert.match(String(delivery.errors[0]), /listener failed/);
|
||||
});
|
||||
|
||||
test("revokes extension contexts after their lifecycle phase", async () => {
|
||||
const values = defineExtensionPoint<string>("test.revoked-context");
|
||||
const event = defineEventType<string>("test.revoked-event");
|
||||
let setupContext: ExtensionSetupContext | undefined;
|
||||
let runtimeContext: ExtensionRuntimeContext | undefined;
|
||||
|
||||
const kernel = new Kernel().use({
|
||||
id: "capture-contexts",
|
||||
setup(context) {
|
||||
setupContext = context;
|
||||
context.add(values, "registered");
|
||||
},
|
||||
start(context) {
|
||||
runtimeContext = context;
|
||||
},
|
||||
});
|
||||
|
||||
await kernel.start();
|
||||
assert.throws(() => setupContext?.add(values, "late"), /no longer active/);
|
||||
|
||||
await kernel.stop();
|
||||
assert.throws(() => runtimeContext?.all(values), /not readable/);
|
||||
assert.throws(() => runtimeContext?.on(event, () => {}), /not active/);
|
||||
});
|
||||
|
||||
test("revokes each setup context before setting up the next extension", async () => {
|
||||
const values = defineExtensionPoint<string>("test.per-extension-setup");
|
||||
let firstContext: ExtensionSetupContext | undefined;
|
||||
|
||||
const kernel = new Kernel().use(
|
||||
{
|
||||
id: "first",
|
||||
setup(context) {
|
||||
firstContext = context;
|
||||
},
|
||||
},
|
||||
{
|
||||
id: "second",
|
||||
setup() {
|
||||
assert.throws(() => firstContext?.add(values, "late"), /no longer active/);
|
||||
},
|
||||
},
|
||||
);
|
||||
|
||||
await kernel.start();
|
||||
await kernel.stop();
|
||||
});
|
||||
|
||||
const stringPoint = defineExtensionPoint<string>("test.string-point");
|
||||
// @ts-expect-error Extension points are invariant in their value type.
|
||||
const numberPoint: ExtensionPoint<number> = stringPoint;
|
||||
void numberPoint;
|
||||
|
||||
const stringEvent = defineEventType<string>("test.string-event");
|
||||
// @ts-expect-error Event types are invariant in their payload type.
|
||||
const numberEvent: EventType<number> = stringEvent;
|
||||
void numberEvent;
|
||||
192
src/kernel/kernel.ts
Normal file
192
src/kernel/kernel.ts
Normal file
@ -0,0 +1,192 @@
|
||||
import { EventBus, type EventHandler, type EventType, type Unsubscribe } from "./events";
|
||||
import type {
|
||||
Extension,
|
||||
ExtensionPoint,
|
||||
ExtensionRuntimeContext,
|
||||
ExtensionSetupContext,
|
||||
} from "./extension";
|
||||
import { ExtensionRegistry } from "./registry";
|
||||
|
||||
export type KernelState = "created" | "starting" | "running" | "stopping" | "stopped";
|
||||
|
||||
export class Kernel {
|
||||
readonly #extensions: Extension[] = [];
|
||||
readonly #extensionIds = new Set<string>();
|
||||
readonly #registry = new ExtensionRegistry();
|
||||
readonly #events = new EventBus();
|
||||
readonly #runtimeContext: ExtensionRuntimeContext;
|
||||
#activeExtensions: Extension[] = [];
|
||||
#state: KernelState = "created";
|
||||
|
||||
constructor() {
|
||||
this.#runtimeContext = Object.freeze({
|
||||
all: <T>(point: ExtensionPoint<T>) => {
|
||||
this.#assertRuntimeReadable();
|
||||
return this.#registry.all(point);
|
||||
},
|
||||
on: <T>(event: EventType<T>, handler: EventHandler<T>) => {
|
||||
this.#assertRuntimeActive();
|
||||
return this.#events.on(event, handler);
|
||||
},
|
||||
});
|
||||
}
|
||||
|
||||
get state(): KernelState {
|
||||
return this.#state;
|
||||
}
|
||||
|
||||
get installedExtensionIds(): readonly string[] {
|
||||
return Object.freeze(this.#extensions.map((extension) => extension.id));
|
||||
}
|
||||
|
||||
use(...extensions: readonly Extension[]): this {
|
||||
if (this.#state !== "created") {
|
||||
throw new Error(`Cannot install extensions while kernel is ${this.#state}.`);
|
||||
}
|
||||
|
||||
const newIds = new Set<string>();
|
||||
|
||||
for (const extension of extensions) {
|
||||
if (this.#extensionIds.has(extension.id) || newIds.has(extension.id)) {
|
||||
throw new Error(`Extension "${extension.id}" is already installed.`);
|
||||
}
|
||||
|
||||
newIds.add(extension.id);
|
||||
}
|
||||
|
||||
for (const extension of extensions) {
|
||||
this.#extensionIds.add(extension.id);
|
||||
this.#extensions.push(extension);
|
||||
}
|
||||
|
||||
return this;
|
||||
}
|
||||
|
||||
all<T>(point: ExtensionPoint<T>): readonly T[] {
|
||||
return this.#registry.all(point);
|
||||
}
|
||||
|
||||
on<T>(event: EventType<T>, handler: EventHandler<T>): Unsubscribe {
|
||||
return this.#events.on(event, handler);
|
||||
}
|
||||
|
||||
async start(): Promise<void> {
|
||||
if (this.#state !== "created") {
|
||||
throw new Error(`Cannot start kernel while it is ${this.#state}.`);
|
||||
}
|
||||
|
||||
this.#state = "starting";
|
||||
|
||||
try {
|
||||
for (const extension of this.#extensions) {
|
||||
const setup = this.#createSetupContext();
|
||||
|
||||
try {
|
||||
await extension.setup(setup.context);
|
||||
} finally {
|
||||
setup.close();
|
||||
}
|
||||
}
|
||||
|
||||
for (const extension of this.#extensions) {
|
||||
this.#activeExtensions.push(extension);
|
||||
await extension.start?.(this.#runtimeContext);
|
||||
}
|
||||
|
||||
this.#state = "running";
|
||||
} catch (error) {
|
||||
const cleanupErrors = await this.#stopActiveExtensions();
|
||||
this.#events.clear();
|
||||
this.#registry.clear();
|
||||
this.#state = "stopped";
|
||||
|
||||
if (cleanupErrors.length > 0) {
|
||||
throw new AggregateError(
|
||||
[error, ...cleanupErrors],
|
||||
"Kernel failed to start and one or more extensions failed to clean up.",
|
||||
);
|
||||
}
|
||||
|
||||
throw error;
|
||||
}
|
||||
}
|
||||
|
||||
async stop(): Promise<void> {
|
||||
if (this.#state === "stopped") {
|
||||
return;
|
||||
}
|
||||
|
||||
if (this.#state !== "running") {
|
||||
throw new Error(`Cannot stop kernel while it is ${this.#state}.`);
|
||||
}
|
||||
|
||||
this.#state = "stopping";
|
||||
const errors = await this.#stopActiveExtensions();
|
||||
|
||||
try {
|
||||
if (errors.length > 0) {
|
||||
throw new AggregateError(errors, "One or more extensions failed to stop.");
|
||||
}
|
||||
} finally {
|
||||
this.#events.clear();
|
||||
this.#registry.clear();
|
||||
this.#state = "stopped";
|
||||
}
|
||||
}
|
||||
|
||||
async #stopActiveExtensions(): Promise<unknown[]> {
|
||||
const errors: unknown[] = [];
|
||||
|
||||
for (const extension of [...this.#activeExtensions].reverse()) {
|
||||
try {
|
||||
await extension.stop?.(this.#runtimeContext);
|
||||
} catch (error) {
|
||||
errors.push(error);
|
||||
}
|
||||
}
|
||||
|
||||
this.#activeExtensions = [];
|
||||
return errors;
|
||||
}
|
||||
|
||||
#createSetupContext(): { context: ExtensionSetupContext; close: () => void } {
|
||||
let open = true;
|
||||
const assertOpen = () => {
|
||||
if (!open || this.#state !== "starting") {
|
||||
throw new Error("Extension setup context is no longer active.");
|
||||
}
|
||||
};
|
||||
|
||||
return {
|
||||
context: Object.freeze({
|
||||
add: <T>(point: ExtensionPoint<T>, value: T) => {
|
||||
assertOpen();
|
||||
this.#registry.add(point, value);
|
||||
},
|
||||
on: <T>(event: EventType<T>, handler: EventHandler<T>) => {
|
||||
assertOpen();
|
||||
return this.#events.on(event, handler);
|
||||
},
|
||||
}),
|
||||
close: () => {
|
||||
open = false;
|
||||
},
|
||||
};
|
||||
}
|
||||
|
||||
#assertRuntimeActive(): void {
|
||||
if (this.#state !== "starting" && this.#state !== "running") {
|
||||
throw new Error(`Extension runtime context is not active while kernel is ${this.#state}.`);
|
||||
}
|
||||
}
|
||||
|
||||
#assertRuntimeReadable(): void {
|
||||
if (
|
||||
this.#state !== "starting" &&
|
||||
this.#state !== "running" &&
|
||||
this.#state !== "stopping"
|
||||
) {
|
||||
throw new Error(`Extension runtime context is not readable while kernel is ${this.#state}.`);
|
||||
}
|
||||
}
|
||||
}
|
||||
25
src/kernel/registry.ts
Normal file
25
src/kernel/registry.ts
Normal file
@ -0,0 +1,25 @@
|
||||
import type { ExtensionPoint } from "./extension";
|
||||
|
||||
export class ExtensionRegistry {
|
||||
readonly #values = new Map<symbol, unknown[]>();
|
||||
|
||||
add<T>(point: ExtensionPoint<T>, value: T): void {
|
||||
const values = this.#values.get(point.key);
|
||||
|
||||
if (values) {
|
||||
values.push(value);
|
||||
return;
|
||||
}
|
||||
|
||||
this.#values.set(point.key, [value]);
|
||||
}
|
||||
|
||||
all<T>(point: ExtensionPoint<T>): readonly T[] {
|
||||
const values = this.#values.get(point.key) ?? [];
|
||||
return Object.freeze([...values]) as readonly T[];
|
||||
}
|
||||
|
||||
clear(): void {
|
||||
this.#values.clear();
|
||||
}
|
||||
}
|
||||
22
src/main.ts
Normal file
22
src/main.ts
Normal file
@ -0,0 +1,22 @@
|
||||
import { Kernel } from "./kernel";
|
||||
import { getProduct, sharedExtensions } from "./products";
|
||||
|
||||
const productIds = process.argv.slice(2);
|
||||
const products = (productIds.length > 0 ? productIds : ["cli"]).map(getProduct);
|
||||
const factories = [
|
||||
...sharedExtensions,
|
||||
...products.flatMap((product) => product.extensions),
|
||||
];
|
||||
const kernel = new Kernel().use(...factories.map((factory) => factory()));
|
||||
|
||||
await kernel.start();
|
||||
|
||||
console.log(
|
||||
`Kernel foundation is ready for ${products.map((product) => product.id).join(", ")}; ${kernel.installedExtensionIds.length} extensions installed.`,
|
||||
);
|
||||
|
||||
await kernel.stop();
|
||||
|
||||
console.log(
|
||||
`Kernel foundation is stopped.`,
|
||||
);
|
||||
6
src/products/cli.ts
Normal file
6
src/products/cli.ts
Normal file
@ -0,0 +1,6 @@
|
||||
import { defineProduct } from "./product";
|
||||
|
||||
export const cliProduct = defineProduct({
|
||||
id: "cli",
|
||||
extensions: [],
|
||||
});
|
||||
6
src/products/desktop.ts
Normal file
6
src/products/desktop.ts
Normal file
@ -0,0 +1,6 @@
|
||||
import { defineProduct } from "./product";
|
||||
|
||||
export const desktopProduct = defineProduct({
|
||||
id: "desktop",
|
||||
extensions: [],
|
||||
});
|
||||
22
src/products/index.ts
Normal file
22
src/products/index.ts
Normal file
@ -0,0 +1,22 @@
|
||||
import { cliProduct } from "./cli";
|
||||
import { desktopProduct } from "./desktop";
|
||||
import { webProduct } from "./web";
|
||||
|
||||
import type { ProductDefinition, ProductId } from "./product";
|
||||
|
||||
export * from "./product";
|
||||
export { sharedExtensions } from "./shared";
|
||||
|
||||
const products: Readonly<Record<ProductId, ProductDefinition>> = Object.freeze({
|
||||
cli: cliProduct,
|
||||
web: webProduct,
|
||||
desktop: desktopProduct,
|
||||
});
|
||||
|
||||
export function getProduct(id: string): ProductDefinition {
|
||||
if (id === "cli" || id === "web" || id === "desktop") {
|
||||
return products[id];
|
||||
}
|
||||
|
||||
throw new Error(`Unknown product "${id}". Expected cli, web, or desktop.`);
|
||||
}
|
||||
15
src/products/product.ts
Normal file
15
src/products/product.ts
Normal file
@ -0,0 +1,15 @@
|
||||
import type { ExtensionFactory } from "../kernel";
|
||||
|
||||
export type ProductId = "cli" | "web" | "desktop";
|
||||
|
||||
export interface ProductDefinition {
|
||||
readonly id: ProductId;
|
||||
readonly extensions: readonly ExtensionFactory[];
|
||||
}
|
||||
|
||||
export function defineProduct(definition: ProductDefinition): ProductDefinition {
|
||||
return Object.freeze({
|
||||
id: definition.id,
|
||||
extensions: Object.freeze([...definition.extensions]),
|
||||
});
|
||||
}
|
||||
4
src/products/shared.ts
Normal file
4
src/products/shared.ts
Normal file
@ -0,0 +1,4 @@
|
||||
import type { ExtensionFactory } from "../kernel";
|
||||
|
||||
// 公共扩展在实现真实能力时加入这里。
|
||||
export const sharedExtensions: readonly ExtensionFactory[] = Object.freeze([]);
|
||||
6
src/products/web.ts
Normal file
6
src/products/web.ts
Normal file
@ -0,0 +1,6 @@
|
||||
import { defineProduct } from "./product";
|
||||
|
||||
export const webProduct = defineProduct({
|
||||
id: "web",
|
||||
extensions: [],
|
||||
});
|
||||
Some files were not shown because too many files have changed in this diff Show More
Loading…
x
Reference in New Issue
Block a user