Files
2026-08-02 16:09:21 +08:00

31 KiB
Raw Permalink Blame History

EasyNode AI Agent 架构与时序

本文描述 EasyNode 当前 AI Agent 的实现架构、主要数据流、权限边界和关键时序。

1. 架构目标

AI Agent 不是由浏览器直接拼接历史并调用模型,而是采用“服务端持有会话状态、WebSocket 驱动单轮任务、模型通过受控工具操作主机”的架构。

核心原则:

  • 会话历史以服务端数据库为准,前端每轮只提交本次输入。
  • REST 负责会话 CRUD,WebSocket 负责实时运行、审批和中断。
  • 模型看到当前作用域和主机选择允许的完整工具集;执行模式与 Plus 权限在每次调用时决定自动执行、审批或拒绝。
  • 主机必须由用户在本轮请求中显式选择,空集合表示纯聊天模式。
  • 会话权限与主机策略取交集,且每次工具调用都重新校验。
  • 命令执行前依次经过访问控制、风险识别和人工审批。
  • 普通运维 Agent 使用独立 SSH exec/SFTP 通道,终端 AI 使用当前 Web 终端 PTY。
  • 工具调用、审批结果、风险信息和部分失败结果都进入会话与审计记录。

2. 总体架构

flowchart TB
  subgraph Web["Web 前端"]
    Entry["全局 AI 入口<br/>ai-agent/index.vue"]
    TerminalUI["终端 AI 侧栏<br/>terminal-ai-chat.vue"]
    SessionState["会话状态<br/>useAgentSession.js"]
    EventReducer["事件归约与消息渲染<br/>agentMessages.js"]
    Terminal["当前 Web Terminal<br/>terminal.vue"]
  end

  subgraph Transport["传输层"]
    REST["REST /api/v1/agent-sessions"]
    AgentWS["Socket.IO /ai-agent"]
    TerminalWS["Socket.IO /terminal"]
  end

  subgraph Server["服务端 AI Agent"]
    SocketGateway["Agent Socket 网关<br/>socket/ai-agent.js"]
    Runtime["Agent Runtime<br/>ai/runtime.js"]
    Provider["模型适配<br/>ai/provider.js"]
    Prompt["System Prompt<br/>ai/prompt.js"]
    Policy["权限策略<br/>ai/policy.js"]
    Safety["Shell 解析与风险分类<br/>ai/shell-lexer.js<br/>ai/safety.js"]
    DataPolicy["敏感数据与文件目标<br/>ai/data-policy.js<br/>ai/file-mutation-policy.js"]
    Approval["审批网关<br/>ai/approval.js"]
    WriteGuard["写入预览与唯一备份<br/>ai/write-preview.js<br/>ai/file-backup.js"]
    ToolRegistry["工具注册<br/>ai/tools/spec.js"]
    Executors["工具执行器<br/>ai/tools/executors.js"]
    HostAccess["逐主机访问控制<br/>ai/host-access.js"]
    SSH["SSH/SFTP 连接池<br/>ai/ssh.js"]
    TerminalDispatch["终端命令调度<br/>ai/terminal-dispatch.js"]
    SessionStore["会话存储<br/>ai/session-store.js"]
    Compaction["上下文压缩<br/>ai/compaction.js"]
    OutputStore["长输出缓存<br/>ai/output-store.js"]
    Audit["安全审计<br/>ai/audit.js"]
  end

  subgraph Data["数据与外部系统"]
    AgentDB[("agent-session.db")]
    ConfigDB[("ai-config.db")]
    HostDB[("host.db")]
    ModelAPI["OpenAI-compatible / Anthropic / Google"]
    RemoteHost["远程 Linux 主机"]
  end

  Entry --> SessionState
  TerminalUI --> SessionState
  SessionState --> EventReducer
  SessionState --> REST
  SessionState <--> AgentWS
  AgentWS <--> SocketGateway
  REST <--> SessionStore

  SocketGateway --> Runtime
  Runtime --> Provider
  Runtime --> Prompt
  Runtime --> Policy
  Runtime --> Safety
  Runtime --> DataPolicy
  Runtime --> Approval
  Runtime --> WriteGuard
  Runtime --> Audit
  Runtime --> ToolRegistry
  Runtime --> SessionStore
  Runtime --> Compaction
  Provider --> ConfigDB
  Provider <--> ModelAPI
  SessionStore <--> AgentDB
  Runtime --> HostDB

  ToolRegistry --> Executors
  Safety --> DataPolicy
  Approval --> Audit
  Executors --> HostAccess
  HostAccess --> HostDB
  Executors --> WriteGuard
  WriteGuard --> SSH
  Executors --> OutputStore
  Executors --> Audit
  Executors --> SSH
  SSH <--> RemoteHost

  Executors --> TerminalDispatch
  TerminalDispatch --> SocketGateway
  SocketGateway --> SessionState
  SessionState --> Terminal
  Terminal <--> TerminalWS
  TerminalWS <--> RemoteHost

3. 分层职责

层 主要文件 职责
前端容器 web/src/components/ai-agent/index.vue 全局运维助手、主机选择、模型和模式切换
终端容器 web/src/views/terminal/components/terminal-ai-chat.vue 绑定当前终端主机,接收和转发终端命令
前端会话层 web/src/composables/useAgentSession.js Socket 生命周期、会话状态、审批、中断、REST 会话操作
前端事件层 web/src/composables/agentMessages.js 将流式事件归约为文本、推理块和工具卡片
Socket 网关 server/app/socket/ai-agent.js 鉴权、任务互斥、创建会话、启动 Runtime、最终落盘
Runtime server/app/ai/runtime.js 模型调用、统一操作分类、审批决策、工具循环、事件映射和超限重试
Provider server/app/ai/provider.js 将不同模型供应商适配为统一 AI SDK 模型
工具层 server/app/ai/tools/ 工具元数据、Zod Schema、权限过滤和具体执行
安全层 policy.js、host-access.js、safety.js、data-policy.js、file-mutation-policy.js、approval.js 权限交集、主机边界、Shell/数据风险判定和人工审批
文件保护层 write-preview.js、file-backup.js、remote-path.js 真实路径识别、完整差异、执行前快照复核和不可覆盖备份
执行层 ssh.js、terminal-dispatch.js、socket/terminal.js 独立 SSH/SFTP 执行或当前 PTY 执行
持久化层 session-store.js、compaction.js 会话 CRUD、消息修复、摘要压缩、Token 统计

4. 两种 Agent 作用域

4.1 运维助手 scope=ops

  • 可以选择零到多台主机。
  • 未选择主机时不注册任何主机工具,退化为纯聊天。
  • 选择主机后向模型开放运维工具集;执行模式不删减工具,具体调用按目标主机策略重新判定。
  • 命令通过服务端独立 SSH exec channel 执行。
  • 文件操作通过服务端 SFTP 执行。

4.2 终端助手 scope=terminal

  • 必须绑定且只能绑定一台主机。
  • 历史会话不能跨主机加载。
  • 模型只获得 terminal_command 和 read_output 工具。
  • terminal_command 不创建新的 SSH exec channel,而是发送到用户当前打开的 PTY。
  • 当前实现发送消息时只附带终端连接和主机信息,不默认把整个终端滚动缓冲区写入模型历史。
  • 通过 AI 执行的命令输出以结构化 Tool Result 进入会话历史。

5. 普通运维 Agent 主时序

sequenceDiagram
  autonumber
  actor User as 用户
  participant UI as Vue AI Agent
  participant WS as /ai-agent
  participant Gateway as Socket Gateway
  participant Store as Session Store
  participant Runtime as Agent Runtime
  participant Provider as Model Provider
  participant Model as LLM API
  participant Tools as Tool Layer
  participant SSH as SSH/SFTP Pool
  participant Host as 远程主机

  User->>UI: 输入任务并选择模型、权限和主机
  UI->>WS: ws_agent_run(sessionId?, input, modelId, permission, hostIds, scope=ops)
  WS->>Gateway: 接收并校验请求

  alt 没有现有会话
    Gateway->>Store: createSession(...)
    Store-->>Gateway: 新会话
    Gateway-->>UI: agent_event(session_created)
  else 继续现有会话
    Gateway->>Store: getSession(sessionId)
    Store-->>Gateway: 会话元数据
  end

  Gateway->>Runtime: runTurn(...)
  Runtime->>Provider: resolveModel(modelId)
  Provider-->>Runtime: model、contextLimit、maxSteps
  Runtime->>Runtime: 计算权限交集并构造 Tools/System Prompt
  Runtime->>Store: loadForModel(sessionId)
  Store-->>Runtime: 修复/压缩后的模型历史
  Runtime-->>UI: turn_start(policy, availableTools)

  Runtime->>Model: streamText(system, history + input, tools)

  loop 最多 maxSteps 次模型/工具循环
    Model-->>Runtime: reasoning/text/tool-call
    Runtime-->>UI: reasoning_delta / text_delta / tool_call

    opt 模型调用工具
      Runtime->>Tools: 校验、审批并执行 Tool
      Tools->>SSH: acquire(hostId)
      SSH->>Host: SSH exec 或 SFTP 操作
      Host-->>SSH: stdout/stderr/exitCode 或文件结果
      SSH-->>Tools: 结构化执行结果
      Tools-->>Runtime: 脱敏、截断后的 Tool Result
      Runtime-->>UI: tool_progress / tool_result
      Runtime->>Model: 将 Tool Result 送回模型
      Runtime-->>UI: awaiting_model / model_resumed
    end
  end

  Model-->>Runtime: finish + usage + responseMessages
  Runtime-->>UI: finish
  Runtime-->>Gateway: 本轮完整或部分结果
  Gateway->>Store: appendTurn(user + responseMessages + toolMeta + usage)
  Store-->>Gateway: 更新后的会话
  Gateway-->>UI: session_saved

5.1 Socket 网关的落盘保证

appendTurn 位于 Socket 网关的 finally 中。即使发生以下情况,本轮已产生的数据也会尽量保存:

  • 用户主动停止;
  • 工具执行失败;
  • 模型流中途失败;
  • 审批被拒绝或超时;
  • 已经执行了工具,但模型没有生成最终回复。

这样可以避免“远端命令已经执行,但会话中没有记录”的情况。

6. 工具调用与审批时序

sequenceDiagram
  autonumber
  participant Model as LLM
  participant Runtime as Runtime toolApproval
  participant Access as Host Access
  participant Safety as Safety
  participant Approval as Approval Gateway
  participant UI as Approval UI
  participant Executor as Tool Executor
  participant Audit as Audit Log

  Model->>Runtime: tool-call(name, input)
  Runtime->>Access: resolveHostAccess(hostId)
  Access->>Access: 检查本轮主机集合、主机开关并解析生效策略

  alt 主机或能力不允许
    Access-->>Runtime: HostAccessError
    Runtime->>Audit: DENIED
    Runtime-->>UI: tool_denied
    Runtime-->>Model: denied(reason)
  else 访问允许
    Runtime->>Safety: 统一准备 effect/risk/targets/preview
    Safety-->>Runtime: 分类结果与执行前快照

    alt 风险级别为 deny
      Runtime->>Audit: DENIED
      Runtime-->>UI: tool_denied
      Runtime-->>Model: denied(reason)
    else 超出主机 maxEffect
      Runtime->>Audit: DENIED
      Runtime-->>UI: tool_denied
      Runtime-->>Model: denied(reason)
    else 不需要审批
      Runtime->>Runtime: 记录已授权快照/路径/脚本哈希
      Runtime->>Executor: execute(input)
      Executor->>Audit: TOOL_CALL / EXEC
      Executor-->>Runtime: result
      Runtime-->>Model: tool-result
    else 需要人工审批
      Runtime->>Approval: requestApproval(...)
      Approval-->>UI: approval_request
      UI->>Approval: ws_agent_approve(approved, scope)

      alt 用户允许
        Approval->>Audit: APPROVED
        Approval-->>Runtime: approved
        Runtime->>Runtime: 记录已批准快照/路径/脚本哈希
        Runtime->>Executor: execute(input)
        Executor-->>Runtime: result
        Runtime-->>Model: tool-result
      else 用户拒绝或五分钟超时
        Approval->>Audit: REJECTED
        Approval-->>Runtime: denied(reason)
        Runtime-->>Model: denied(reason)
      end
    end
  end

注意:

  • deny 是硬拦截,不向用户提供继续执行按钮。
  • high 即使在“授权”模式也必须逐次确认。
  • Runtime 是分类和审批的唯一决策点;Executor 只消费已准备的分类结果,并复核文件快照、敏感读取真实路径或脚本哈希等会发生变化的对象。
  • run_script 按实际脚本内容分类并复核内容哈希;普通静态脚本遵循当前模式矩阵。
  • 终端命令遵循同一模式矩阵:审查模式下所有主机操作需审批,其他模式按操作和风险判定。
  • 仅协助模式下 normal/write 且静态、单段、无重定向的少数服务生命周期操作支持“本会话允许同一操作”。当前仅覆盖 systemctl、service、rc-service、docker 和 podman 的明确 start/restart/reload 类操作。
  • 会话授权指纹包含工具、主机和完整命令参数;文件写入、终端命令、复合/动态命令、删除和 high 操作始终单次审批。

7. 终端 AI 命令时序

终端 AI 涉及两条 Socket:

  • /ai-agent:模型运行和 Agent 事件。
  • /terminal:现有 WebSSH PTY 输入输出。
sequenceDiagram
  autonumber
  actor User as 用户
  participant AIUI as 终端 AI 侧栏
  participant AgentWS as /ai-agent
  participant Runtime as Agent Runtime
  participant Dispatch as Terminal Dispatch
  participant TerminalVue as terminal.vue
  participant TerminalWS as /terminal
  participant Bridge as Terminal AI Bridge
  participant PTY as 远端 PTY

  User->>AIUI: 输入终端相关任务
  AIUI->>AIUI: 验证当前终端已连接
  AIUI->>AgentWS: ws_agent_run(scope=terminal, hostId, terminalContext)
  AgentWS->>Runtime: runTurn(...)
  Runtime->>Runtime: 仅注册 terminal_command/read_output

  Runtime->>Dispatch: requestTerminalDispatch(command)
  Dispatch-->>AIUI: terminal_command_request(requestId, command)
  AIUI->>TerminalVue: executeAiCommand(command)
  TerminalVue->>TerminalWS: ai_terminal_command(localRequestId, command)
  TerminalWS->>Bridge: 构造随机 begin/end marker
  Bridge->>PTY: 写入 Base64 包装命令

  PTY-->>Bridge: 命令回显、持续输出、结束 marker 和退出码
  Bridge->>Bridge: 过滤包装器和 marker,截取本次命令输出
  Bridge-->>TerminalVue: terminal_ai_command_progress
  TerminalVue-->>AIUI: reportProgress(...)
  AIUI->>AgentWS: ws_terminal_command_progress
  AgentWS->>Dispatch: reportTerminalDispatchProgress
  Dispatch-->>AIUI: agent_event(terminal_command_progress)

  PTY-->>Bridge: end marker
  Bridge-->>TerminalVue: terminal_ai_command_result
  TerminalVue-->>AIUI: Promise 完成
  AIUI->>AgentWS: ws_terminal_command_result
  AgentWS->>Dispatch: resolveTerminalDispatch
  Dispatch-->>Runtime: output、exitCode、durationMs
  Runtime->>Runtime: 生成 Tool Result 并继续模型循环

7.1 PTY 命令边界

当前 PTY 是交互式流,不能天然知道某条命令何时结束。服务端终端桥接层会:

  1. 为每条 AI 命令生成随机 token。
  2. 将原命令 Base64 编码,避免直接插入包装脚本时发生转义问题。
  3. 在命令前后输出随机 begin/end marker。
  4. 从 PTY 数据流中隐藏包装器和 marker。
  5. 只收集 begin/end 之间的输出。
  6. 从 end marker 读取退出码。

同一个终端同一时间只允许一条 AI 命令执行,单条命令最长等待 60 分钟。

8. 停止与断连时序

sequenceDiagram
  autonumber
  actor User as 用户
  participant UI as Vue
  participant WS as /ai-agent
  participant Gateway as Socket Gateway
  participant Runtime as Runtime
  participant Approval as Approval
  participant Dispatch as Terminal Dispatch
  participant SSH as SSH/PTY
  participant Store as Session Store

  User->>UI: 点击停止
  UI->>WS: ws_agent_stop
  WS->>Gateway: stop
  Gateway->>Runtime: AbortController.abort()
  Gateway-->>UI: stopped

  par 清理审批
    Runtime->>Approval: AbortSignal
    Approval-->>UI: approval_cancelled
  and 中断普通 SSH 命令
    Runtime->>SSH: kill/close exec stream
  and 中断终端命令
    Runtime->>Dispatch: AbortSignal
    Dispatch-->>UI: terminal_command_cancel
    UI->>SSH: /terminal 发送 Ctrl-C
  end

  Runtime-->>UI: aborted
  Gateway->>Store: appendTurn(已产生的部分消息)
  Gateway-->>UI: session_saved

Socket 断连时还会:

  • 中止当前 AbortController;
  • 清理该会话挂起的审批;
  • 清理终端调度请求;
  • 清理该会话的长输出缓存;
  • 普通运维作用域主动释放使用过的 Agent SSH 连接;
  • 前端将仍在运行或等待审批的工具卡片标记为失败,避免永久转圈。

9. 上下文加载与压缩时序

sequenceDiagram
  autonumber
  participant Runtime as Runtime
  participant Store as Session Store
  participant Compact as Compaction
  participant Model as LLM
  participant DB as agent-session.db
  participant UI as Vue

  Runtime->>Store: loadForModel(sessionId, model, contextLimit)
  Store->>DB: 读取完整会话
  DB-->>Store: messages + compaction
  Store->>Compact: 估算未覆盖历史的 Token

  alt 达到上下文预算约 70%
    Compact->>Model: 生成旧历史摘要
    Model-->>Compact: summary
    Compact-->>Store: summary + splitAt
    Store->>DB: 保存 compaction(summary, upTo)
    Store-->>Runtime: compactedNow
    Runtime-->>UI: compacted
  end

  Store->>Store: 修复孤立 tool-call/tool-result
  Store->>Store: 按完整轮次执行硬裁剪
  Store-->>Runtime: 摘要 + 最近原始消息
  Runtime->>Model: 发起本轮 streamText

  alt Provider 返回上下文超限
    Runtime->>Compact: 强制压缩,保留最近一轮
    Compact->>Model: 生成应急摘要
    Model-->>Runtime: 压缩结果
    Runtime-->>UI: compacted
    Runtime->>Model: 整轮重试一次
  end

上下文策略:

  • 默认上下文预算为 64K Token,可由 AI 配置中的 contextLimit 覆盖。
  • 估算达到预算的约 70% 时主动压缩。
  • 正常压缩保留最近三轮原文。
  • 厂商仍返回上下文超限时,强制压缩并仅重试一次。
  • 摘要以 { summary, upTo } 单独保存;原始消息在硬上限内仍可供前端查看。
  • 单会话硬上限为 200 条消息或约 1.5 MiB,裁剪只从完整对话轮次边界进行。
  • 中断导致的无结果 Tool Call 会补充合成错误结果,防止后续模型请求因消息不配对返回 400。

10. 权限模型

10.1 执行模式

每次操作统一分类为 read | write | delete,风险分为 normal | high | deny。

只有能够明确证明只读的 Shell 命令归为 read,明确删除归为 delete,其他静态命令默认归为 normal/write。因此新增普通 CLI 无需加入安全白名单;动态构造以及明确命中的高危、永久拒绝操作仍受负向规则约束。

预设 普通读取 高危读取 普通写入 高危写入 删除
审查 review 主机读取审批;本地元数据自动 审批 审批 审批 审批
协助 assist 自动 审批 审批 审批 审批
授权 authorized 自动 审批 自动 审批 普通自动、高危审批

面向用户的模式描述为:审查模式下所有主机操作均需确认;协助模式仅明确只读的操作自动执行;授权模式自动执行未命中高危规则的操作。永久禁止的操作在所有模式下始终拦截。

10.2 主机策略

每台主机可配置:

{
  enabled: true,
  maxEffect: 'write',
  maxMode: 'authorized'
}

最终生效策略:

生效 mode = min(会话权限模式, 主机 maxMode)
写入和删除仅在 maxEffect = write 时允许

多主机会话展示工具并集,真正执行时按目标主机重新判定。

10.3 Shell 分类收敛

Shell 安全策略采用开放式授权模型,不维护“已知安全命令白名单”。解析层先拆分命令段、参数、重定向、管道、包装器和嵌套负载,分类层统一返回:

{
  effect,             // read | write | delete
  risk,               // normal | high | deny
  category,
  reason,
  targets,
  traits,             // recursive | force | bulk | dynamic | sensitive | external | hidden | opaque
  hits,
  segments
}

判定规则:

  1. 明确具有删除语义的操作归为 delete,包括文件删除或移动源、truncate、包/账号/数据库/容器/Kubernetes 删除等。
  2. 只有静态且能够明确证明只读的命令归为 read。
  3. 其他静态命令,包括尚未登记的新 CLI,默认归为 normal/write。
  4. 变量、命令替换、eval 和无法静态确认的解释器代码归为 high,而不是因为命令名未知而升级。
  5. 复合命令逐段分类并取最严格的 effect 和 risk;sh -c、su -c、远程 ssh 命令及 systemd-run 的静态负载会递归分析。
  6. 镜像名、包名、URL、文件内容或普通目标中的产品关键词不会改变风险等级。

因此 future-cli deploy app 这类未知静态命令表现为:审查、协助模式审批,授权模式自动执行。新增普通 CLI 不需要补安全规则,只有新增的明确高危或永久拒绝机制需要进入负向规则集。

10.4 风险边界

等级 主要类别 行为
deny dd;文件系统格式化/擦除;块设备直接写入;根目录或关键系统目录树整体销毁;fork bomb;递归锁死或完全放开关键系统目录权限;核心凭据外传;Base64 等隐藏载荷直接交给解释器执行 所有模式永久拒绝,只允许向用户展示原始命令和原因,不得自动改写、拆分或绕过
high 敏感读取/写入;强制、递归或批量删除;SSH、防火墙、网卡、账号和提权变更;关机重启;包卸载;数据库清空;容器强制删除、清理和卷删除;Kubernetes 命名空间或持久卷删除;动态/间接执行;文件外传;下载后执行 所有模式均需单次审批
normal 未命中上述负向规则的静态读取、写入和删除 按执行模式矩阵处理

该分类器是防误操作护栏,不是恶意命令沙箱;真正的权限边界仍是主机策略、目标主机集合和 SSH 账号自身权限。

11. 工具清单

工具 操作类型 Plus 策略 执行通道 特殊规则
host_list read 免费 NeDB 只返回本轮显式选择的主机
host_status read 免费 SSH exec 返回结构化系统状态
script_list read 免费 NeDB/脚本库 返回内置和用户脚本
run_script 按内容判定 写入/删除需 Plus SSH exec 按脚本内容分类并校验哈希
exec_command 按内容判定 写入/删除需 Plus SSH exec 经过统一 Shell 分类
read_file read 免费 SFTP 敏感读取需审批
write_file write Plus SFTP 默认备份,高危路径展示完整 diff
list_dir read 免费 SFTP 返回类型、大小、权限和时间
read_output read 免费 内存缓存 回读被截断的工具输出
terminal_command 按内容判定 写入/删除需 Plus 当前 Web PTY 按实际命令分类

工具规格是模型 Schema、System Prompt 和前端能力展示的共同数据源,避免多处声明发生漂移。

11.1 文件与脚本执行不变量

  • read_file 在审批前解析真实路径,并同时检查请求路径与真实路径。敏感读取执行时必须匹配同一次工具调用批准的真实路径。
  • write_file 仅接受绝对路径、有效 UTF-8 文本和最多 256 KiB 的完整内容;权限值只能是 3 或 4 位八进制数字。
  • 写入前读取原文件并生成完整替换 diff,快照哈希覆盖主机、请求路径、真实路径、原内容、权限、备份选项和新内容。执行前重新生成快照,任何变化都会使原授权失效。
  • 覆盖已有文件时默认创建 ${原路径}.bak.<UTC 时间戳>;同一毫秒或并发碰撞时追加序号,并使用 SFTP 独占创建,绝不覆盖已有备份。
  • run_script 只执行脚本库返回的原始内容。Runtime 按实际脚本分类并记录 SHA-256,Executor 执行前重新读取脚本并校验哈希。
  • Executor 不重新解释不可变命令,只消费 Runtime 已保存的 effect/risk;仅保留文件快照、敏感读取真实路径和脚本哈希这类 TOCTOU 复核。

12. 长输出与敏感信息

12.1 长输出

  • SSH 命令单次最多收集约 2 MiB,超过后标记为截断。
  • 工具结果超过约 8 KiB 时,返回头部、尾部和临时 handle。
  • 模型可调用 read_output(handle, pattern, offset, limit) 分段回读。
  • 临时输出缓存默认保留 30 分钟,最多 200 项,并在会话断开时清理。

12.2 脱敏

普通工具结果返回模型前会自动脱敏。高危读取经过用户单次批准后,真实内容会发送给当前 AI Provider;未经审批的意外凭据输出仍会脱敏。核心凭据的明确外传命令直接归为 deny。

长输出 handle 与创建它的会话绑定,其他会话不能回读;存入缓存的是当前工具调用经过脱敏策略处理后的内容。

13. 模型与运行配置

运维助手与终端助手共用 AI 设置:

配置 说明
providerType openai-compatible、anthropic 或 google
apiUrl / apiKey Provider 的 API 前缀与凭据
models 允许在 Agent 中选择的模型 ID 列表;OpenAI-compatible Provider 支持从接口获取候选模型
contextLimit 模型上下文窗口,默认 65,536 Token
maxSteps 单轮最多模型/工具迭代次数,默认 25,服务端上限 50

Provider 层将三类服务适配为统一的 AI SDK 模型。请求中的模型 ID 必须位于已配置列表中;未显式选择时使用列表第一项。

14. 会话数据模型

agent-session.db 中单条记录的主要结构:

{
  _id,
  title,
  scope,              // ops | terminal
  hostId,             // terminal 会话绑定主机
  hostIds,            // 本会话主机集合
  modelId,
  permission,
  messages,           // AI SDK ModelMessage[]
  turnMeta: [
    {
      createdAt,
      usage
    }
  ],
  toolMeta: {
    [toolCallId]: {
      tool,
      effect,
      risk,
      riskReason,
      riskCategory,
      targets,
      approved,
      approvalScope,
      approvalCached,
      denied,
      sensitiveDisclosure,
      durationMs,
      failed
    }
  },
  usage,
  compaction: {
    summary,
    upTo,
    createdAt
  },
  createdAt,
  updatedAt
}

会话列表接口只返回摘要字段,不返回可能达到数百 KiB 的 messages。完整历史通过详情接口按需加载。

Agent 会话不按时间自动清理,仅支持用户主动删除单个会话或清空会话。

15. 接口与事件协议

15.1 REST

方法 路径 用途
GET /api/v1/agent-sessions 按 scope/hostId 获取会话列表
DELETE /api/v1/agent-sessions 按作用域清空会话
GET /api/v1/agent-sessions/:id 获取完整会话
PUT /api/v1/agent-sessions/:id 更新标题、主机、模型或权限
POST /api/v1/agent-sessions/:id/fork 从指定问答或回答创建分支
PUT /api/v1/agent-sessions/:id/messages/:turnIndex 截断旧分支并准备重新发送编辑后的消息
DELETE /api/v1/agent-sessions/:id 删除会话

15.2 客户端发送到 /ai-agent

事件 主要载荷 用途
ws_agent_run sessionId?、input、modelId、permission、hostIds、scope;终端作用域另带 hostId、terminalPermission、terminalContext 启动一轮任务
ws_agent_approve requestId、approved、scope 回复审批
ws_agent_stop 无 中止当前任务
ws_terminal_command_progress requestId、output、durationMs 回传 PTY 命令进度
ws_terminal_command_result requestId、output、exitCode 回传 PTY 最终结果

15.3 服务端统一发送 agent_event

主要 type:

类别 type
初始化 ready、session_created、turn_start、pending_approvals
模型流 reasoning_delta、text_delta、step_finish、finish
工具 tool_call、tool_progress、tool_result、tool_denied、tool_requires_plus
模型等待 awaiting_model、model_resumed
审批 approval_request、approval_timeout、approval_cancelled
终端 terminal_command_request、terminal_command_progress、terminal_command_cancel
上下文 history_repaired、compacted
生命周期 session_saved、aborted、stopped、error、stream_error

16. 关键实现文件

server/app/
├── ai/
│   ├── runtime.js             # 单轮 Agent 编排
│   ├── provider.js            # 模型供应商适配
│   ├── prompt.js              # System Prompt
│   ├── policy.js              # 双轴权限模型
│   ├── host-access.js         # 逐主机执行边界
│   ├── safety.js              # Shell 风险分类
│   ├── shell-lexer.js         # Shell 词法解析
│   ├── data-policy.js         # 敏感读取路径分类
│   ├── file-mutation-policy.js # Shell 文件变更目标提取
│   ├── write-preview.js       # write_file 完整差异与快照
│   ├── file-backup.js         # 唯一且不可覆盖的备份
│   ├── remote-path.js         # 远程真实路径解析
│   ├── approval.js            # 人工审批挂起/恢复
│   ├── audit.js               # 安全审计
│   ├── redact.js              # 输出脱敏
│   ├── ssh.js                 # Agent SSH/SFTP 连接池
│   ├── terminal-dispatch.js   # 当前 PTY 调度
│   ├── output-store.js        # 长输出缓存
│   ├── compaction.js          # 上下文摘要
│   ├── session-store.js       # Agent 会话持久化
│   ├── plus.js                # 加密后的 Plus 变更工具实现
│   └── tools/
│       ├── spec.js            # 工具元数据与 Schema
│       ├── index.js           # AI SDK Tool 装配
│       └── executors.js       # 工具执行器
├── script-library.js          # 内置/用户脚本统一读取
├── controller/
│   └── agent-session.js       # 会话 REST Controller
└── socket/
    ├── ai-agent.js            # Agent WebSocket 网关
    └── terminal.js            # PTY AI 命令边界协议

web/src/
├── components/ai-agent/       # 全局 Agent、审批卡、工具卡、会话列表和模式切换
├── components/common/
│   └── chat-sender.vue        # 运维/终端助手共用输入框
├── composables/
│   ├── useAgentSession.js     # 前端会话与 Socket 状态
│   ├── agentMessages.js       # Agent 事件归约
│   └── agentTools.js          # 前端工具能力展示
└── views/terminal/components/
    ├── terminal-ai-chat.vue   # 终端 AI 侧栏
    └── terminal.vue           # PTY 命令发送、进度和取消

17. 验证覆盖

暂存区实现包含以下自动化验证:

  • server/test/test-ai-safety.js:三档模式矩阵、Shell 复合命令、嵌套负载、路径变体、文件目标、高危和永久拒绝规则。
  • server/test/test-ai-access.js:主机隔离、纯聊天模式、Shell 转义、审批指纹、终端取消和执行步数上限。
  • server/test/test-ai-data.js:敏感路径、脱敏、写入预览、快照与唯一备份。
  • server/test/test-ai-session.js:消息修复、会话 CRUD、编辑、分支、持久化和用量累计。
  • server/test/test-ai-compaction.js:Token 估算、摘要切分、主动/应急压缩和上下文超限识别。
  • web/test/test-agent-messages.js:流式事件归约、工具卡、历史还原、回答重生成和不同事件来路的一致性。

常用校验命令:

yarn workspace server run test:ai
yarn workspace server run lint
yarn workspace web run test
yarn workspace web run lint
yarn workspace web run build
git diff --check