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

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 2 additions & 1 deletion .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -2,9 +2,10 @@
.DS_Store
.pnpm-store/
coverage/
deno.lock
dist/
examples/vite/dist/
node_modules/
playwright-report/
test-results/
release-artifacts/
test-results/
9 changes: 9 additions & 0 deletions .prettierignore
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
coverage/
deno.lock
dist/
examples/vite/dist/
node_modules/
playwright-report/
pnpm-lock.yaml
release-artifacts/
test-results/
3 changes: 3 additions & 0 deletions .vscode/extensions.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
{
"recommendations": ["dbaeumer.vscode-eslint", "esbenp.prettier-vscode"]
}
8 changes: 8 additions & 0 deletions .vscode/settings.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
{
"[css][javascript][javascriptreact][json][jsonc][markdown][typescript][typescriptreact][yaml]": {
"editor.defaultFormatter": "esbenp.prettier-vscode",
"editor.formatOnSave": true
},
"eslint.useFlatConfig": true,
"prettier.requireConfig": true
}
32 changes: 21 additions & 11 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@

`react-acp` 将 [Agent Client Protocol (ACP)](https://agentclientprotocol.com/) 会话投影为 [assistant-ui](https://www.assistant-ui.com/) runtime。ACP session 是线程权威来源;消息、推理、工具调用、权限、计划、模式、配置与用量由协议事件驱动。

> 当前状态:`0.1.1` 开发版。兼容承诺覆盖官方 TypeScript SDK 标记为稳定的 ACP v1 API;实验 API 与 ACP v2 Draft 不在承诺范围内。
> 当前状态:`0.1.2` 开发版。兼容承诺覆盖官方 TypeScript SDK 标记为稳定的 ACP v1 API;实验 API 与 ACP v2 Draft 不在承诺范围内。

## 安装

Expand Down Expand Up @@ -34,25 +34,35 @@ export function AcpProvider({ children }: { children: React.ReactNode }) {
workspace: { cwd: "/absolute/project/path", mcpServers: [] },
});

return (
<AssistantRuntimeProvider runtime={runtime}>
{children}
</AssistantRuntimeProvider>
);
return <AssistantRuntimeProvider runtime={runtime}>{children}</AssistantRuntimeProvider>;
}
```

浏览器不能直接启动本地 stdio Agent。浏览器应用应由宿主注入 WebSocket/HTTP Stream,或注入自定义 `AcpClientAdapter`。本包不会替应用获得文件系统或终端权限。

高层 adapter、纯 reducer/projector 和结构化错误从 `@hafbit/react-acp/core` 导出;认证、计划、模式、配置、命令、权限和 ACP artifact 的无样式组件从 `@hafbit/react-acp/primitives` 导出。主入口同时提供对应 hooks。

`connection`、`workspace`、`clientServices`、`clientCapabilities` 和 `clientInfo` 是 Provider 的身份配置。切换 Agent 或 Workspace 时用 React `key` 重建 Provider;`threadId` 是标准受控属性,`onError`、`onThreadIdChange` 等回调可动态更新:

```tsx
function AgentRuntime({ agent, workspace, children }: Props) {
return (
<AcpProvider key={`${agent.id}:${workspace.cwd}`} agent={agent} workspace={workspace}>
{children}
</AcpProvider>
);
}
```

`useAcpRuntimeExtras()` 提供 `reconnect`、完整分页的 `refreshSessions` 和 session 生命周期方法。消息 metadata 只保留该消息自己的完整 ACP notifications;session 最新状态、工具通知和未知扩展通过 extras 中的公开 core state 读取。

## 入口与 API

| 入口 | 适用场景 | 主要导出 |
| --- | --- | --- |
| `@hafbit/react-acp` | React 应用的常规集成 | `useAcpRuntime`、ACP hooks、常用无样式组件与公开类型 |
| `@hafbit/react-acp/core` | 自定义宿主、transport 或状态投影 | `AcpThreadController`、`SdkAcpClientAdapter`、reducer、projector、serializer、错误和类型 |
| `@hafbit/react-acp/primitives` | 自定义 ACP 交互界面 | 认证、权限、计划、模式、配置、命令、用量和 tool artifact 组件 |
| 入口 | 适用场景 | 主要导出 |
| ------------------------------ | -------------------------------- | ---------------------------------------------------------------------------------------- |
| `@hafbit/react-acp` | React 应用的常规集成 | `useAcpRuntime`、ACP hooks、常用无样式组件与公开类型 |
| `@hafbit/react-acp/core` | 自定义宿主、transport 或状态投影 | `AcpThreadController`、`SdkAcpClientAdapter`、reducer、projector、serializer、错误和类型 |
| `@hafbit/react-acp/primitives` | 自定义 ACP 交互界面 | 认证、权限、计划、模式、配置、命令、用量、Diff、Terminal、Resource 和 Unsupported 组件 |

```tsx
import { useAcpRuntime } from "@hafbit/react-acp";
Expand Down
18 changes: 17 additions & 1 deletion docs/architecture.md
Original file line number Diff line number Diff line change
Expand Up @@ -19,15 +19,31 @@ flowchart LR

`AcpThreadController` 只维护客户端投影,不创建第二份会话真相。每个 session 的消息、工具、权限、计划、命令、模式、配置、用量和未处理扩展完全隔离。reducer 不执行 I/O,因此历史重放、乱序更新和失败恢复可独立测试。

原始协议数据按归属保存:消息和工具保留自身完整 `SessionNotification`;plan、mode、config、commands、usage 与 session-info 保存最新完整通知;未知扩展保存完整通知。session 不再维护无限增长的全量 raw log,消息 metadata 也不复制 session 日志。

## 连接边界

- `stream`:`SdkAcpClientAdapter` 在连接前注册全部 Agent→Client handler,再调用官方 SDK 的 `ClientApp`。
- `adapter`:宿主负责实际传输和 Agent 生命周期,但必须提供同一套稳定方法及事件分发。
- 浏览器应用通常需要宿主或网关把 stdio Agent 转换为可用 Stream;这不属于本包职责。
- 每次连接有独立 generation。旧连接通知、关闭回调和异步结果不会进入新连接状态;重连 initialize/auth 后必须重新 load 当前 session,缺少 load 时才使用 resume。

## Session 生命周期

- controller 记录当前连接已挂载的 session;未挂载或挂载失败的 session 禁止发送 prompt。
- session 选择使用递增 generation,只有最新用户选择可以更新 active session;较晚完成的旧 load 历史仍保存在其原 session。
- load 前保存快照并暂时清空重放区域;失败恢复快照与 settled active session,允许重试。
- close 取消该 session 未决权限并解除挂载,但保留缓存历史;delete 才移除本地 session。
- `session/list` 完整遍历分页并对账非 active session;远端列表暂时缺少 active session 时仍保留当前界面状态。

## 投影规则

- 文本、图片、音频、reasoning 优先使用 assistant-ui 原生 part。
- tool call 使用 `acp:<kind>` 的稳定名称,原始输入、输出、内容和更新放入 artifact。
- permission 映射为 tool approval;plan、非 HTTP resource 和未知事件使用命名 `data-acp-*` part。
- 所有消息的原始 update 和 `_meta` 保存在 `metadata.custom.acp`。
- `metadata.custom.acp` 只包含当前消息的 session ID、协议 message ID、完整原始 notifications、stop reason 和错误。
- 乐观用户消息保存真实 prompt content;live `user_message_chunk` 内容匹配时确认同一条本地消息并记录 `protocolMessageId`,保持 assistant-ui message ID 稳定。

## React 配置身份

连接、工作区、客户端服务、能力和 client info 在 controller 创建时固定。调用方改变这些身份配置时必须通过 React `key` 重建 Provider;回调使用 latest ref 动态生效。受控 `threadId` 同步不会回显 `onThreadIdChange`。
10 changes: 6 additions & 4 deletions docs/manual-smoke.md
Original file line number Diff line number Diff line change
Expand Up @@ -4,9 +4,11 @@ smoke 不进入常规 CI,也不在缺少本地 Agent 时报告成功。

1. 准备能产生 ACP Stream 的宿主,设置 `ACP_SMOKE_AGENT=codex` 或 `opencode`,并设置对应宿主命令。
2. 完成 initialize,记录 protocolVersion、agentInfo 和 capabilities。
3. 创建 session,发送文本 prompt,确认 chunk、tool、permission、plan、usage 和 stop reason 投影。
4. 若声明 list/load/resume/delete/close,逐项执行;确认 resume 不显示伪造历史。
5. 触发取消并确认所有未决 permission 收到 cancelled response。
6. 关闭连接,确认 terminal、文件句柄与子进程全部释放。
3. 创建 session,发送文本 prompt,确认 chunk、tool、permission、plan、usage 和 stop reason 投影;Agent 回传 `user_message_chunk` 时界面只能出现一条用户消息,协议 ID 与本地 ID 分别保留。
4. 断开并重连 transport,确认重新执行 initialize/auth→load;缓存历史保持可见且下一次 prompt 发往已挂载 session。Agent 不支持 load/resume 时应明确报错并禁止发送。
5. 快速发起 A→B session 切换并让 A 较晚完成,确认 active 始终为 B,A 历史只归属 A;制造 load 失败后确认快照恢复且可重试。
6. 若声明 list/load/resume/delete/close,逐项执行;确认分页刷新能发现新增/删除 session,active session 不因瞬时列表缺失而消失,resume 不显示伪造历史。
7. 触发取消和 close,确认该 session 所有未决 permission 收到 cancelled response;受控 thread 回调与 active ID 同步。
8. 关闭连接,确认 terminal、文件句柄与子进程全部释放;旧连接随后到达的 notification 不改变界面。

仓库脚本只做前置条件检查并输出明确的 `SKIP` 或待执行命令,不会替代宿主集成。
56 changes: 29 additions & 27 deletions docs/protocol-capability-matrix.md
Original file line number Diff line number Diff line change
Expand Up @@ -2,30 +2,32 @@

状态含义:`实现`为直接支持;`能力门控`为仅在 initialize 声明后开放;`不适用`为协议没有对应语义且不会伪造。

| ACP v1 能力 | 状态 | 实现位置 | 测试证据 |
| --- | --- | --- | --- |
| initialize / 版本协商 | 实现 | `SdkAcpClientAdapter`、controller | `stream-conformance.test.ts` |
| authenticate / logout | 实现 | controller、认证 hook/primitive | `controller.test.ts` |
| session/new | 实现 | controller | `controller.test.ts` |
| session/prompt / cancel | 实现 | controller | `controller.test.ts` |
| session/update 内容流 | 实现 | reducer/projector | `state.test.ts`、`projection.test.ts` |
| session/load | 能力门控 | `loadSession` | `controller.test.ts` |
| session/list + 全分页 | 能力门控 | `sessionCapabilities.list` | `controller.test.ts` |
| session/delete | 能力门控 | `sessionCapabilities.delete` | `controller.test.ts` |
| session/resume | 能力门控 | `sessionCapabilities.resume` | `controller.test.ts` |
| session/close | 能力门控 | `sessionCapabilities.close` | `controller.test.ts` |
| additionalDirectories | 能力门控 | `sessionCapabilities.additionalDirectories` | `serialize.test.ts` |
| 文本 / resource link prompt | 实现 | serializer | `serialize.test.ts` |
| 图片 / 音频 / embedded resource prompt | 能力门控 | `promptCapabilities` | `serialize.test.ts` |
| tool call / 增量 update | 实现 | reducer/projector | `state.test.ts` |
| permission request | 实现 | controller / tool approval | `controller.test.ts`、`projection.test.ts` |
| plan / commands | 实现 | reducer / hooks / primitives | `state.test.ts` |
| session modes | 实现 | controller / primitives | `controller.test.ts` |
| session config options | 实现 | controller / primitives | `controller.test.ts` |
| usage update | 实现 | reducer / hook / primitive | `state.test.ts` |
| Client fs/read_text_file | 按注入声明 | SDK adapter | `stream-conformance.test.ts`、`serialize.test.ts` |
| Client fs/write_text_file | 按注入声明 | SDK adapter | `stream-conformance.test.ts`、`serialize.test.ts` |
| Client terminal 全组方法 | 按整组注入声明 | SDK adapter | `stream-conformance.test.ts`、`serialize.test.ts` |
| `_meta` / 未知扩展 | 实现 | reducer/projector | `state.test.ts`、`projection.test.ts` |
| rename / archive / edit / regenerate / branch | 不适用 | 对应 runtime 能力关闭 | `runtime.test.tsx`、构建检查 |
| v2 Draft / `UNSTABLE` | 不承诺 | 作为 raw/unsupported 保留 | `state.test.ts` |
| ACP v1 能力 | 状态 | 实现位置 | 测试证据 |
| --------------------------------------------- | -------------- | ------------------------------------------- | ------------------------------------------------- |
| initialize / 版本协商 | 实现 | `SdkAcpClientAdapter`、controller | `stream-conformance.test.ts` |
| authenticate / logout | 实现 | controller、认证 hook/primitive | `controller.test.ts` |
| session/new | 实现 | controller | `controller.test.ts` |
| session/prompt / cancel | 实现 | controller | `controller.test.ts`、`workbench.spec.ts` |
| session/update 内容流 | 实现 | reducer/projector | `state.test.ts`、`projection.test.ts` |
| session/load / 重连重新挂载 | 能力门控 | `loadSession` / `resumeSession` | `controller.test.ts`、`workbench.spec.ts` |
| session/list + 全分页对账 | 能力门控 | `sessionCapabilities.list` | `controller.test.ts` |
| session/delete | 能力门控 | `sessionCapabilities.delete` | `controller.test.ts` |
| session/resume | 能力门控 | `sessionCapabilities.resume` | `controller.test.ts` |
| session/close | 能力门控 | `sessionCapabilities.close` | `controller.test.ts` |
| additionalDirectories | 能力门控 | `sessionCapabilities.additionalDirectories` | `serialize.test.ts` |
| 文本 / resource link prompt | 实现 | serializer | `serialize.test.ts` |
| 图片 / 音频 / embedded resource prompt | 能力门控 | `promptCapabilities` | `serialize.test.ts` |
| tool call / 增量 update | 实现 | reducer/projector | `state.test.ts` |
| permission request | 实现 | controller / tool approval | `controller.test.ts`、`projection.test.ts` |
| plan / commands | 实现 | reducer / hooks / primitives | `state.test.ts` |
| session modes | 实现 | controller / primitives | `controller.test.ts` |
| session config options | 实现 | controller / primitives | `controller.test.ts` |
| usage update | 实现 | reducer / hook / primitive | `state.test.ts` |
| Client fs/read_text_file | 按注入声明 | SDK adapter | `stream-conformance.test.ts`、`serialize.test.ts` |
| Client fs/write_text_file | 按注入声明 | SDK adapter | `stream-conformance.test.ts`、`serialize.test.ts` |
| Client terminal 全组方法 | 按整组注入声明 | SDK adapter | `stream-conformance.test.ts`、`serialize.test.ts` |
| `_meta` / 未知扩展 | 实现 | reducer/projector | `state.test.ts`、`projection.test.ts` |
| 乐观用户消息 / live echo 合并 | 实现 | controller/projector | `controller.test.ts`、`workbench.spec.ts` |
| session 快速切换 latest-wins | 实现 | controller | `controller.test.ts`、`workbench.spec.ts` |
| rename / archive / edit / regenerate / branch | 不适用 | 对应 runtime 能力关闭 | `runtime.test.tsx`、构建检查 |
| v2 Draft / `UNSTABLE` | 不承诺 | 作为 raw/unsupported 保留 | `state.test.ts` |
12 changes: 6 additions & 6 deletions docs/releasing.md
Original file line number Diff line number Diff line change
Expand Up @@ -4,12 +4,12 @@

## 版本与 dist-tag

| Git tag | npm version | npm dist-tag | JSR version | GitHub Release |
| --- | --- | --- | --- | --- |
| `v1.2.3` | `1.2.3` | `latest` | `1.2.3` | 正式版 |
| `v1.2.3-alpha.0` | `1.2.3-alpha.0` | `alpha` | `1.2.3-alpha.0` | prerelease |
| `v1.2.3-beta.0` | `1.2.3-beta.0` | `beta` | `1.2.3-beta.0` | prerelease |
| `v1.2.3-rc.0` | `1.2.3-rc.0` | `rc` | `1.2.3-rc.0` | prerelease |
| Git tag | npm version | npm dist-tag | JSR version | GitHub Release |
| ---------------- | --------------- | ------------ | --------------- | -------------- |
| `v1.2.3` | `1.2.3` | `latest` | `1.2.3` | 正式版 |
| `v1.2.3-alpha.0` | `1.2.3-alpha.0` | `alpha` | `1.2.3-alpha.0` | prerelease |
| `v1.2.3-beta.0` | `1.2.3-beta.0` | `beta` | `1.2.3-beta.0` | prerelease |
| `v1.2.3-rc.0` | `1.2.3-rc.0` | `rc` | `1.2.3-rc.0` | prerelease |

其他 prerelease 格式会被 `release:check` 拒绝。发布 tag 必须是 annotated tag,且目标提交必须属于 `origin/latest`。JSR 没有 npm dist-tag 的对应概念,预发布版本通过完整 SemVer 获取。

Expand Down
12 changes: 11 additions & 1 deletion docs/requirements.md
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
# react-acp 0.1.0 需求基线
# react-acp 0.1.x 需求基线

本文是首版实现的中文权威需求记录。目标是提供单一可发布包,将 ACP v1 session 的事件流投影为 assistant-ui runtime、message 和 thread。

Expand All @@ -22,3 +22,13 @@
## 发布边界

本仓库生成 `@hafbit/react-acp@0.1.0` 发布产物。无 scope 的 `react-acp` 被 npm 相似名称策略拒绝后,维护者已确认改用组织 scope;首次发布仍由维护者通过 2FA 手工执行。

## 0.1.2 生命周期加固补充

- 每个新连接都必须重新挂载 active session;旧连接的通知、关闭回调和异步结果全部丢弃。
- session 选择遵循 latest-selection-wins;load 失败恢复消息快照和原 active session,并可重试。
- 未挂载或挂载失败的 session 禁止 prompt;prompt 传输失败回到 idle,消息保留错误供重试。
- 乐观用户消息不伪造协议通知;匹配的 live user echo 合并到稳定本地消息,协议 ID 单独保存。
- 原始通知按消息、工具和 session 最新状态归属保存,不保留 session 全量日志,不向每条消息复制全量通知。
- session list 完整分页并对账非 active session;close 保留缓存,delete 才移除;生命周期方法一致更新受控 thread 回调。
- Provider identity 配置通过 React `key` 重建,动态回调无需重建;不增加 ACP/assistant-ui 均不存在的公开状态。
Loading