mirror of
https://github.com/claude-code-best/claude-code.git
synced 2026-06-15 21:05:51 +00:00
围绕 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>
177 lines
5.0 KiB
TypeScript
177 lines
5.0 KiB
TypeScript
import { describe, expect, test } from 'bun:test'
|
||
import type { RunProgress } from '../progress/store.js'
|
||
import type { WorkflowService } from '../service.js'
|
||
|
||
function makeMockService(runs: RunProgress[]): {
|
||
service: WorkflowService
|
||
emit: () => void
|
||
setRuns: (runs: RunProgress[]) => void
|
||
} {
|
||
let current = runs
|
||
const listeners = new Set<() => void>()
|
||
return {
|
||
service: {
|
||
ports: {},
|
||
launch: async () => ({ runId: 'x' }),
|
||
kill: () => {},
|
||
listRuns: () => current,
|
||
getRun: () => undefined,
|
||
subscribe: (fn: () => void) => {
|
||
listeners.add(fn)
|
||
return () => {
|
||
listeners.delete(fn)
|
||
}
|
||
},
|
||
listNamed: async () => [],
|
||
} as unknown as WorkflowService,
|
||
emit: () => {
|
||
for (const fn of listeners) fn()
|
||
},
|
||
setRuns: r => {
|
||
current = r
|
||
},
|
||
}
|
||
}
|
||
|
||
function makeRun(
|
||
runId: string,
|
||
status: RunProgress['status'],
|
||
overrides: Partial<RunProgress> = {},
|
||
): RunProgress {
|
||
return {
|
||
runId,
|
||
workflowName: 'wf',
|
||
status,
|
||
phases: [],
|
||
declaredPhases: [],
|
||
currentPhase: null,
|
||
agents: [],
|
||
agentCount: 0,
|
||
startedAt: Date.now(),
|
||
updatedAt: Date.now(),
|
||
...overrides,
|
||
}
|
||
}
|
||
|
||
describe('installWorkflowNotifications', () => {
|
||
test('running → completed 触发通知(含 workflow 名)', async () => {
|
||
const { installWorkflowNotifications } = await import('../notifications.js')
|
||
const { service, emit, setRuns } = makeMockService([
|
||
makeRun('r1', 'running'),
|
||
])
|
||
const calls: string[] = []
|
||
const unsubscribe = installWorkflowNotifications(service, msg =>
|
||
calls.push(msg),
|
||
)
|
||
|
||
// 第一次 emit:listener 记录初始 running 状态,不发通知
|
||
emit()
|
||
expect(calls.length).toBe(0)
|
||
|
||
setRuns([makeRun('r1', 'completed')])
|
||
emit()
|
||
|
||
expect(calls.length).toBe(1)
|
||
expect(calls[0]).toMatch(/task-notification/)
|
||
expect(calls[0]).toMatch(/completed successfully/)
|
||
expect(calls[0]).toMatch(/"wf"/)
|
||
unsubscribe()
|
||
})
|
||
|
||
test('running → failed 触发通知,含 error 文字', async () => {
|
||
const { installWorkflowNotifications } = await import('../notifications.js')
|
||
const { service, emit, setRuns } = makeMockService([
|
||
makeRun('r1', 'running'),
|
||
])
|
||
const calls: string[] = []
|
||
installWorkflowNotifications(service, msg => calls.push(msg))
|
||
|
||
emit() // 记录初始 running
|
||
setRuns([makeRun('r1', 'failed', { error: 'agent X boom' })])
|
||
emit()
|
||
|
||
expect(calls.length).toBe(1)
|
||
expect(calls[0]).toMatch(/failed/)
|
||
expect(calls[0]).toMatch(/agent X boom/)
|
||
})
|
||
|
||
test('running → killed 触发通知', async () => {
|
||
const { installWorkflowNotifications } = await import('../notifications.js')
|
||
const { service, emit, setRuns } = makeMockService([
|
||
makeRun('r1', 'running'),
|
||
])
|
||
const calls: string[] = []
|
||
installWorkflowNotifications(service, msg => calls.push(msg))
|
||
|
||
emit() // 记录初始 running
|
||
setRuns([makeRun('r1', 'killed')])
|
||
emit()
|
||
|
||
expect(calls.length).toBe(1)
|
||
expect(calls[0]).toMatch(/was stopped/)
|
||
})
|
||
|
||
test('初次见到 run(无 prev)不发通知(避免启动时通知历史 run)', async () => {
|
||
const { installWorkflowNotifications } = await import('../notifications.js')
|
||
const { service, emit, setRuns } = makeMockService([])
|
||
const calls: string[] = []
|
||
installWorkflowNotifications(service, msg => calls.push(msg))
|
||
|
||
// 启动后第一次 emit,看到 r1 已 completed——不应通知(不是从 running 转换来)
|
||
setRuns([makeRun('r1', 'completed')])
|
||
emit()
|
||
|
||
expect(calls.length).toBe(0)
|
||
})
|
||
|
||
test('running → running 不发通知', async () => {
|
||
const { installWorkflowNotifications } = await import('../notifications.js')
|
||
const { service, emit, setRuns } = makeMockService([
|
||
makeRun('r1', 'running'),
|
||
])
|
||
const calls: string[] = []
|
||
installWorkflowNotifications(service, msg => calls.push(msg))
|
||
|
||
emit() // 记录初始 running
|
||
setRuns([makeRun('r1', 'running', { agentCount: 1 })])
|
||
emit()
|
||
|
||
expect(calls.length).toBe(0)
|
||
})
|
||
|
||
test('已 completed 的 run 再次 emit 不重复通知', async () => {
|
||
const { installWorkflowNotifications } = await import('../notifications.js')
|
||
const { service, emit, setRuns } = makeMockService([
|
||
makeRun('r1', 'running'),
|
||
])
|
||
const calls: string[] = []
|
||
installWorkflowNotifications(service, msg => calls.push(msg))
|
||
|
||
emit() // 记录初始 running
|
||
setRuns([makeRun('r1', 'completed')])
|
||
emit()
|
||
expect(calls.length).toBe(1)
|
||
|
||
emit()
|
||
expect(calls.length).toBe(1)
|
||
})
|
||
|
||
test('unsubscribe 后不再发通知', async () => {
|
||
const { installWorkflowNotifications } = await import('../notifications.js')
|
||
const { service, emit, setRuns } = makeMockService([
|
||
makeRun('r1', 'running'),
|
||
])
|
||
const calls: string[] = []
|
||
const unsubscribe = installWorkflowNotifications(service, msg =>
|
||
calls.push(msg),
|
||
)
|
||
|
||
emit() // 记录初始 running
|
||
unsubscribe()
|
||
setRuns([makeRun('r1', 'completed')])
|
||
emit()
|
||
|
||
expect(calls.length).toBe(0)
|
||
})
|
||
})
|