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>
This commit is contained in:
claude-code-best
2026-06-13 23:04:33 +08:00
parent d236880bc3
commit 54d2bf6f12
32 changed files with 2253 additions and 196 deletions

View File

@@ -1,5 +1,7 @@
import {
createWorkflowTool,
workflowInputSchema,
WORKFLOW_TOOL_NAME,
type WorkflowToolDescriptor,
} from '@claude-code-best/workflow-engine'
import { buildTool, type Tool } from '../Tool.js'
@@ -8,25 +10,37 @@ import { getWorkflowService } from './service.js'
/**
* 把引擎自包含描述符适配为 buildTool 兼容的 Tool。
* 描述符统一走 service 单例(共享 ports/registry/store
*
* ports 解析延迟到首次实际方法调用lazytools.ts 在模块加载阶段feature-gated
* 调用 createWorkflowToolCore(),若此时立即解析 ports 会触发 service 实例化,
* 进而调用 getProjectRoot 等模块级副作用——这在 bootstrap 完成前可能拿到错误路径。
* Tool 对象本身的单例由 createWorkflowToolCore 的 cached 保证PermissionRequest
* 按引用匹配ports 单例由 getWorkflowService 保证。
*/
function buildWorkflowTool(): Tool {
const { ports } = getWorkflowService()
const descriptor: WorkflowToolDescriptor = createWorkflowTool(ports)
let cachedDescriptor: WorkflowToolDescriptor | null = null
const descriptor = (): WorkflowToolDescriptor => {
if (!cachedDescriptor) {
const { ports } = getWorkflowService()
cachedDescriptor = createWorkflowTool(ports)
}
return cachedDescriptor
}
return buildTool({
name: descriptor.name,
name: WORKFLOW_TOOL_NAME,
maxResultSizeChars: 50_000,
inputSchema: descriptor.inputSchema,
isEnabled: () => descriptor.isEnabled(),
isReadOnly: input => descriptor.isReadOnly(input),
inputSchema: workflowInputSchema,
isEnabled: () => descriptor().isEnabled(),
isReadOnly: input => descriptor().isReadOnly(input),
isConcurrencySafe: () => true,
async description() {
return descriptor.description()
return descriptor().description()
},
async prompt() {
return descriptor.prompt()
return descriptor().prompt()
},
async call(input, context, canUseTool, parentMessage, onProgress) {
const result = await descriptor.call(
const result = await descriptor().call(
input,
context,
canUseTool,
@@ -35,9 +49,9 @@ function buildWorkflowTool(): Tool {
)
return { data: result.data }
},
renderToolUseMessage: input => descriptor.renderToolUseMessage(input),
renderToolUseMessage: input => descriptor().renderToolUseMessage(input),
mapToolResultToToolResultBlockParam: (data, toolUseId) =>
descriptor.mapToolResultToToolResultBlockParam(data, toolUseId),
descriptor().mapToolResultToToolResultBlockParam(data, toolUseId),
})
}