feat(workflow): add workflow engine, /workflows panel, /ultracode skill

将 feat/sdk-backend 分支中 workflow 相关的 20 个 commit 压缩为单 commit:

- 工作流引擎核心:phase / agent / parallel / pipeline 编排原语(packages/workflow-engine/)
- /workflows 面板:三区焦点布局(顶部 run tabs + 左侧 phase 侧栏 + 右侧 agent 列表)
- /ultracode skill:多 agent workflow 编排入口
- 进度存储 / journal / notification 系统
- WorkflowService 生命周期管理 + SentryErrorBoundary
- 脚本沙箱:禁用 dynamic import()、JSON args 防御性归一化
- journal 与 named-workflow 路径统一在 projectRoot
- 错误处理:parallel/pipeline hooks 错误日志、failure routing、semaphore abort
- workflow 工具升级为 core 工具 + PascalCase 命名

Co-Authored-By: glm-5.1 <zai-org@claude-code-best.win>
This commit is contained in:
claude-code-best
2026-06-13 20:07:18 +08:00
parent 91cffe16e2
commit d236880bc3
106 changed files with 16127 additions and 834 deletions

View File

@@ -0,0 +1,138 @@
// Agent 后端适配器抽象。引擎通过 registry 取 adapter 再调 run不关心具体实现
// Anthropic SDK / 核心 runAgent / OpenAI / 本地模型 / mock 均为 adapter 的实现)。
import type { 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
}
/**
* 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
}