Files
claude-code/packages/workflow-engine/src/agentAdapter.ts
claude-code-best 54d2bf6f12 feat(workflow): 复刻 ultracode 手册并修复 worktree/inline/opt-in 三处缺口
围绕 ultracode skill 审查 agent 系统一致性后:
- ultracode.ts: 用系统提示版完整 Workflow 编排手册替换中文精简版
- HIGH#1 isolation:'worktree': claudeCodeBackend.run() 用 createAgentWorktree +
  runWithCwdOverride 包裹 runAgent + finally 清理实现真正的 cwd 隔离;slug 用
  sha256(runId:agentId) 派生以匹配 cleanupStaleAgentWorktrees 清理正则
  (修 runId 为 w+base36 非 UUID 导致的泄漏盲区);worktree.ts 注释同步修正
- HIGH#2 inline 持久化: 新增 persistInlineScript,WorkflowTool + service 两条
  inline 路径对称持久化到 .claude/workflow-runs/<runId>/script.js,返回可复用
  scriptPath(闭环 inline→编辑→scriptPath 重提迭代循环)
- HIGH#3 opt-in 分工: ultracode/WorkflowTool/effort 注明 session reminder 由
  harness 注入,repo 内无 ultracode 信号,保持 feature('WORKFLOW_SCRIPTS') +
  isEnabled 两层 gate,不自造注入
- 测试: 新增 persistInline.test.ts;扩展 claudeCodeBackend(isolation 4 用例)/
  WorkflowTool(inline)/service(scriptPath)/ultracode(harness)

含配套 workflow engine/panel 完善与 run-state-persistence design doc。

Co-Authored-By: Claude <noreply@anthropic.com>
2026-06-13 23:04:33 +08:00

148 lines
4.9 KiB
TypeScript
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
// Agent 后端适配器抽象。引擎通过 registry 取 adapter 再调 run不关心具体实现
// Anthropic SDK / 核心 runAgent / OpenAI / 本地模型 / mock 均为 adapter 的实现)。
import type {
AgentProgressUpdate,
AgentRunParams,
AgentRunResult,
} from './types.js'
import type { HostHandle } from './ports.js'
/** adapter 能力声明。引擎/脚本据此降级(如后端不支持 schema 则改文本 + 解析)。 */
export type AgentAdapterCapabilities = {
/** 支持 schema 结构化输出agent(schema) 直接返回对象)。 */
structuredOutput: boolean
/** 支持工具调用(仅核心 agent 后端有)。 */
tools?: boolean
/** 支持流式v1 引擎不消费,预留)。 */
stream?: boolean
}
/** adapter.run 的上下文。 */
export type AgentAdapterContext = {
/** 透传的不透明 host 句柄(核心 adapter 用;独立后端忽略)。 */
host: HostHandle
/** 取消信号(与 workflow signal 一致)。 */
signal: AbortSignal
/** 当前 workflow runId日志/追踪用)。 */
runId: string
/**
* 运行中进度上报(后端循环累计 token/tool 时调用)。可选:独立后端可不实现;
* 引擎据此发 agent_progress 事件(闭包带 agentId/runId 关联),面板实时刷新。
*/
onProgress?: (update: AgentProgressUpdate) => void
}
/**
* Agent 后端适配器。引擎只依赖此接口;具体后端实现它并注册到 registry。
* initialize/dispose 为可选生命周期(连接池/资源管理),由调用方通过
* registry.initializeAll/disposeAll 触发。
*/
export interface AgentAdapter {
/** 唯一标识registry 路由 / 日志)。 */
readonly id: string
/** 能力声明。 */
readonly capabilities: AgentAdapterCapabilities
/** 执行一次 agent 调用。 */
run(params: AgentRunParams, ctx: AgentAdapterContext): Promise<AgentRunResult>
/** 初始化(由 registry.initializeAll 触发)。 */
initialize?(): Promise<void>
/** 销毁(由 registry.disposeAll 触发)。 */
dispose?(): Promise<void>
}
/** 路由规则:决定哪些 params 走哪个 adapter。按添加顺序匹配先命中先用。 */
export type AdapterRouteRule =
| { kind: 'agentType'; agentType: string; adapter: string }
| { kind: 'model'; pattern: string; adapter: string }
| {
kind: 'custom'
match: (params: AgentRunParams) => boolean
adapter: string
}
/** registry 找不到匹配 adapter 时抛出。 */
export class AdapterNotFoundError extends Error {
constructor(message: string) {
super(message)
this.name = 'AdapterNotFoundError'
}
}
/**
* 多后端 registry。register 注册 adapterroute/default 配路由resolve 按
* 规则顺序匹配选 adapter。adapter 的 lifecycleinitialize/dispose通过
* initializeAll/disposeAll 统一触发(由调用方在运行前后调)。
*/
export class AgentAdapterRegistry {
private readonly adapters = new Map<string, AgentAdapter>()
private readonly rules: AdapterRouteRule[] = []
private defaultId: string | null = null
/** 注册一个 adapterid 重复则覆盖)。链式。 */
register(adapter: AgentAdapter): this {
this.adapters.set(adapter.id, adapter)
return this
}
/** 设默认 adapter无规则命中时用。链式。 */
default(adapterId: string): this {
this.defaultId = adapterId
return this
}
/** 加一条路由规则(按添加顺序匹配)。链式。 */
route(rule: AdapterRouteRule): this {
this.rules.push(rule)
return this
}
has(id: string): boolean {
return this.adapters.has(id)
}
get(id: string): AgentAdapter | undefined {
return this.adapters.get(id)
}
/** 按规则匹配;第一个命中返回;无命中走 default都没有抛 AdapterNotFoundError。 */
resolve(params: AgentRunParams): AgentAdapter {
for (const rule of this.rules) {
if (matchRule(rule, params)) {
const hit = this.adapters.get(rule.adapter)
if (hit) return hit
}
}
if (this.defaultId) {
const fallback = this.adapters.get(this.defaultId)
if (fallback) return fallback
}
throw new AdapterNotFoundError(
`无 adapter 匹配rules=${this.rules.length}, default=${this.defaultId ?? '无'}`,
)
}
/** 触发所有 adapter 的 initialize跳过未实现的。 */
async initializeAll(): Promise<void> {
for (const a of this.adapters.values()) {
await a.initialize?.()
}
}
/** 触发所有 adapter 的 dispose跳过未实现的。 */
async disposeAll(): Promise<void> {
for (const a of this.adapters.values()) {
await a.dispose?.()
}
}
}
function matchRule(rule: AdapterRouteRule, params: AgentRunParams): boolean {
if (rule.kind === 'agentType') return params.agentType === rule.agentType
if (rule.kind === 'model') {
return (
typeof params.model === 'string' && params.model.startsWith(rule.pattern)
)
}
return rule.match(params) // custom
}