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,124 @@
/**
* registry 多后端路由演示mock adapter无需 API key
*
* 两个 adapterstrong被 researcher 路由命中)+ fast默认
* 脚本里 agent({agentType:'researcher'}) → strong其余 → fast。
* 证明 agent 后端可通过 AgentAdapterRegistry 插拔 + 路由,引擎不关心实现。
*
* 用法bun run packages/workflow-engine/examples/registry-demo.ts
*/
import { tmpdir } from 'node:os'
import { join } from 'node:path'
import {
AgentAdapterRegistry,
createFileJournalStore,
createHostHandle,
runWorkflow,
type AgentAdapter,
type AgentRunParams,
type AgentRunResult,
type WorkflowPorts,
} from '@claude-code-best/workflow-engine'
const strongAdapter: AgentAdapter = {
id: 'strong',
capabilities: { structuredOutput: true, tools: true },
async run(p: AgentRunParams): Promise<AgentRunResult> {
return {
kind: 'ok',
output: `[strong] ← ${p.prompt}`,
usage: { outputTokens: 1 },
}
},
}
const fastAdapter: AgentAdapter = {
id: 'fast',
capabilities: { structuredOutput: false },
async run(p: AgentRunParams): Promise<AgentRunResult> {
return {
kind: 'ok',
output: `[fast] ← ${p.prompt}`,
usage: { outputTokens: 1 },
}
},
}
const registry = new AgentAdapterRegistry()
.register(strongAdapter)
.register(fastAdapter)
.route({ kind: 'agentType', agentType: 'researcher', adapter: 'strong' })
.default('fast')
const SCRIPT = `
export const meta = { name: 'registry-demo', description: 'multi-adapter routing' }
phase('Route')
const research = await agent('深度调研任务', { agentType: 'researcher', label: 'research' })
const quick = await agent('快速小任务', { label: 'quick' })
return { research, quick }
`
function makePorts(runsDir: string): WorkflowPorts {
return {
// registry 优先agentRunner 仅作形状占位(不会被调到)
agentRunner: { runAgentToResult: async () => ({ kind: 'dead' }) },
agentAdapterRegistry: registry,
progressEmitter: {
emit: e => {
if (e.type === 'phase_started') console.log(`\n━ phase: ${e.phase}`)
else if (e.type === 'agent_done') {
const out =
e.result.kind === 'ok'
? String(e.result.output)
: `[${e.result.kind}]`
console.log(`${e.label}${out}`)
}
},
},
taskRegistrar: {
register: () => ({
runId: 'demo',
signal: new AbortController().signal,
}),
complete() {},
fail() {},
kill() {},
pendingAction: () => null,
},
journalStore: createFileJournalStore(runsDir),
permissionGate: { isAborted: () => false },
logger: { debug: () => {}, event: () => {} },
hostFactory: () => ({
handle: createHostHandle(null),
cwd: process.cwd(),
budgetTotal: null,
}),
}
}
if (import.meta.main) {
await registry.initializeAll()
try {
const result = await runWorkflow({
script: SCRIPT,
runId: `demo-${Date.now()}`,
ports: makePorts(join(tmpdir(), 'wf-registry-demo')),
host: createHostHandle(null),
signal: new AbortController().signal,
cwd: process.cwd(),
budgetTotal: null,
})
console.log(`\n■ ${result.status}`)
if (result.status === 'completed') {
const ret = result.returnValue as { research: string; quick: string }
console.log(
`research(agentType:researcher) → ${ret.research.startsWith('[strong]') ? 'strong adapter ✓' : '??'}`,
)
console.log(
`quick(默认) → ${ret.quick.startsWith('[fast]') ? 'fast adapter ✓' : '??'}`,
)
}
} finally {
await registry.disposeAll()
}
}