Aster文档
搜索 Aster 文档
ESC
产品
版本

输入关键词开始搜索。

    文档目录

    ASTER ACP / 协议原理

    ACP 与 Core 的职责边界

    按一次客户端请求拆开协议适配、可信宿主、Core 会话和界面事件。

    客户端与执行者的职责

    客户端接收用户输入并显示运行过程。 aster-acp 接收 ACP 消息,再调用 Core。 Core 接纳执行、调用模型、治理工具并检查完成。 协议适配器把运行事件送回客户端。

    架构图一条请求跨过哪些责任层概念分层不代表四个进程。工作区资料不能提高宿主上限。
    一条请求跨过哪些责任层概念分层不代表四个进程。工作区资料不能提高宿主上限。Core 权威状态事实 / 权限 / 完成ACP 协议适配DTO / 事件映射可信宿主配置模型 / 续跑上限客户端交互输入 / 展示 / 回答
    1. 01从交互进入适配器

      客户端构造协议参数。ACP 在入口检查内容类型。

    2. 02可信宿主选择能力

      宿主提供可信配置。工作区配置保留原有信任限制。

    3. 03Core 处理事实与完成

      ACP 的显示事件不会替代内核回执。

      turn.rs L26 ↗

    下载 SVG ↗

    例如,用户在终端提交日期导出修复。 终端发送文本,ACP 找到对应会话。 Core 执行任务,ACP 翻译事件。 终端用事件中的原始 ID 更新工具行。

    三层结束含义怎样区分

    层次结束表示什么还要核对什么
    ACP prompt本轮协议请求返回Core 的运行与完成依据
    Core Run原始执行进入终态资源结清与该轮证据
    Durable Goal原目标满足完成条件全部步骤、等待与切片

    end_turn 是协议停止原因。 它不能独自证明长期目标已经完成。 客户端需要结合对应的 Core 状态显示结果。

    从连接到一轮执行

    架构图从建立连接到结束一轮先协商,再建立会话。prompt 响应只描述本轮协议终态。
    从建立连接到结束一轮先协商,再建立会话。prompt 响应只描述本轮协议终态。1234567initializesession/new 或 load创建或恢复会话session/prompt运行事件session/updateprompt 响应客户端ACP 代理Core 会话客户端ACP 代理Core 会话
    1. 01先协商能力

      客户端依据 initialize 响应选择已声明的能力。

    2. 02再建立或恢复会话

      session/new 与 session/load 有不同来源校验。

      resume.rs L37 ↗
    3. 03消费事件直到本轮结束

      客户端同时处理消息、工具更新和确认请求。

      turn.rs L26 ↗

    下载 SVG ↗

    客户端先调用 initialize。 随后创建或恢复会话,再提交 session/prompt。 运行期间,客户端消费 session/update。 需要确认时,客户端回答原始权限请求。 本轮结束后,客户端读取 prompt 响应。

    入口会拒绝哪些内容

    当前 ACP prompt 接受文本内容块。 图片、音频、资源和资源链接会返回参数错误。 拒绝发生在模型调用之前。 Core 内部具备某项能力,不代表 ACP 已暴露该能力。

    标准 ACP 方法与宿主扩展也要分开。 aster/goal/* 和 aster/review/* 属于宿主扩展。 未知扩展方法返回 method_not_found。 入口:宿主方法闭集 L45 ↗。

    哪些信息必须来自可信宿主

    信息作用工作区文本的边界
    模型与服务配置选择实际模型调用路径保留配置层的信任规则
    权限与沙箱判定工具调用提示词不能放宽
    goal_control限制自动续跑工作区声明不能提高上限
    工作策略选择选择已编入的策略客户端不能提交任意策略代码
    完成证明选择安装宿主检查模型不能自行安装证明器

    每项信息由其原入口准入。 ACP 不应把普通文本解释为额外执行权限。

    源码漫游从哪里开始

    源码漫游

    ACP 请求与事件源码漫游

    沿协议入口查看文本校验、事件映射、确认与无人值守结果。

    当前 prompt 接受文本。图片、音频和资源块在入口返回错误。

    Core 的能力与 ACP 已声明能力要分开判断。

    turn.rs L6-L30 ↗
    RS
    crates/code/acp/src/agent/turn.rs
    L6-L30 ↗
    pub(super) fn prompt_text(prompt: &[acp::ContentBlock]) -> acp::Result<String> {
    let unsupported = |kind: &str| {
    acp::Error::invalid_params().data(format!(
    "unsupported prompt content block `{kind}`; this agent accepts text only and did not send the request to the model"
    ))
    };
    let mut text = Vec::new();
    for block in prompt {
    match block {
    acp::ContentBlock::Text(content) => text.push(content.text.as_str()),
    acp::ContentBlock::Image(_) => return Err(unsupported("image")),
    acp::ContentBlock::Audio(_) => return Err(unsupported("audio")),
    acp::ContentBlock::Resource(_) => return Err(unsupported("resource")),
    acp::ContentBlock::ResourceLink(_) => return Err(unsupported("resource_link")),
    _ => return Err(unsupported("unknown")),
    }
    }
    Ok(text.join("\n"))
    }
    pub(super) async fn handle_event(
    &self,
    session_id: &acp::SessionId,
    session: &AgentSession,
    state: &mut TurnState,

    适配器分别处理文本与推理增量。

    这是协议投影,不是第二套执行日志。

    turn.rs L26-L54 ↗
    RS
    crates/code/acp/src/agent/turn.rs
    L26-L54 ↗
    pub(super) async fn handle_event(
    &self,
    session_id: &acp::SessionId,
    session: &AgentSession,
    state: &mut TurnState,
    event: AgentEvent,
    ) {
    let observation = state
    .observer
    .as_mut()
    .and_then(|observer| observer.event(&event));
    self.emit_run_observation(observation).await;
    match event {
    AgentEvent::TextDelta { text } => {
    if !text.is_empty() {
    state.saw_text = true;
    }
    state.delta_text.push_str(&text);
    self.emit_text(session_id, &text).await;
    }
    AgentEvent::ReasoningDelta { text } => {
    self.emit_thought(session_id, &text).await;
    }
    AgentEvent::ToolStart { .. } => {
    // Streaming and the fact runner both announce a call before its
    // arguments exist. Painting that announcement opens a second
    // Run row for one execution, and it also paints a twin that
    // the same-response collapse never runs.
    }

    ToolStart 不建立第二行。ToolExecutionStart 使用准确 ID 与参数。

    一条执行事实应对应一个工具调用身份。

    turn.rs L49-L75 ↗
    RS
    crates/code/acp/src/agent/turn.rs
    L49-L75 ↗
    AgentEvent::ToolStart { .. } => {
    // Streaming and the fact runner both announce a call before its
    // arguments exist. Painting that announcement opens a second
    // Run row for one execution, and it also paints a twin that
    // the same-response collapse never runs.
    }
    AgentEvent::ToolExecutionStart {
    id,
    name,
    args: tool_args,
    } => match note_tool_row(&mut state.painted_tools, &id) {
    ToolRowPaint::Open => {
    self.emit_tool_call(session_id, &id, &name, Some(&tool_args))
    .await;
    }
    ToolRowPaint::Update => {
    self.emit_tool_call_started(session_id, &id, &name, &tool_args)
    .await;
    }
    },
    AgentEvent::ToolOutputDelta { id, name, delta } => {
    self.emit_tool_call_output_delta(session_id, &id, &name, &delta)
    .await;
    }
    AgentEvent::ToolEnd {
    id,
    name,

    tool_permission_ask 构造会话、工具、参数与选项。

    界面返回代理给出的选项 ID。

    permission_ask.rs L11-L42 ↗
    RS
    crates/code/acp/src/agent/permission_ask.rs
    L11-L42 ↗
    pub(super) fn tool_permission_ask(
    session_id: &acp::SessionId,
    tool_id: &str,
    tool_name: &str,
    args: &serde_json::Value,
    workspace: Option<&Path>,
    ) -> acp::RequestPermissionRequest {
    let raw_input = prepared_raw_input(tool_name, args);
    let why = approval_reason(&assess(tool_name, args, workspace));
    let mut fields = acp::ToolCallUpdateFields::new()
    .title(tool_title(tool_name))
    .kind(tool_kind_for(tool_name))
    .status(acp::ToolCallStatus::Pending)
    .content(vec![acp::ToolCallContent::from(acp::ContentBlock::Text(
    acp::TextContent::new(why),
    ))]);
    if let Some(raw_input) = raw_input {
    fields = fields.raw_input(raw_input);
    }
    let mut options = vec![acp::PermissionOption::new(
    acp::PermissionOptionId::new("allow-once"),
    "Yes, proceed",
    acp::PermissionOptionKind::AllowOnce,
    )];
    let learnable = (tool_name == "bash")
    .then(|| args.get("command").and_then(serde_json::Value::as_str))
    .flatten()
    .and_then(crate::approval_learning::learnable_rule);
    if let Some(rule) = learnable {
    options.push(acp::PermissionOption::new(
    acp::PermissionOptionId::new("allow-always"),
    format!("Yes, and don't ask again for `{rule}` in this workspace"),

    缺少成功工具动作时,适配器记录失败。

    终态记录与完成证明仍需分别检查。

    unattended_turn.rs L59-L84 ↗
    RS
    crates/code/acp/src/agent/unattended_turn.rs
    L59-L84 ↗
    if failure.is_none() && !progress.has_action_evidence() {
    progress.mark_evidence_missing();
    failure = Some(anyhow::anyhow!(
    "A3S Code agent ended without a successful tool execution"
    ));
    }
    let (outcome, reason) = match &failure {
    None => (
    ExecutionResultOutcomeV1::Succeeded,
    TerminalReason::AgentEnd,
    ),
    Some(error) => classify_failure(&progress, error),
    };
    if let Some(path) = &unattended.result_file {
    if let Err(error) = persist_result(path, &progress.report(outcome, reason)).await {
    tracing::error!(path = %path.display(), "could not write the result record: {error:#}");
    failure.get_or_insert(error);
    }
    }
    match failure {
    None => Ok(acp::PromptResponse::new(acp::StopReason::EndTurn)),
    // These outcomes are recorded in the result file. Returning a
    // JSON-RPC internal error makes Harbor's summary say only
    // "Internal error" and hides the reason.
    Some(_)
    if matches!(

    源码与本文版本一致,保留原始行号。

    按故障选择下一篇