mirror of
https://github.com/claude-code-best/claude-code.git
synced 2026-06-17 22:05:50 +00:00
Squashed 5 commits: Features (from 5 feature branches): - MCP fix, pipe mute, stub recovery - KAIROS activation, openclaw autonomy - Daemon/job command hierarchy + cross-platform bg engine Upstream fixes: - fix: Bun.hash compatibility - chore: chrome dependency update - docs: browser support guide MIME detection fix: - Screenshot detectMimeFromBase64(): decode raw bytes from base64 instead of broken charCodeAt comparison - Fixes API 400 on Windows (JPEG) and macOS (PNG) screenshots
238 lines
7.0 KiB
TypeScript
238 lines
7.0 KiB
TypeScript
import { afterEach, beforeEach, describe, expect, test } from 'bun:test'
|
|
import autonomyCommand from '../autonomy'
|
|
import {
|
|
resetStateForTests,
|
|
setOriginalCwd,
|
|
setProjectRoot,
|
|
} from '../../bootstrap/state'
|
|
import { listAutonomyFlows } from '../../utils/autonomyFlows'
|
|
import {
|
|
createAutonomyQueuedPrompt,
|
|
markAutonomyRunCompleted,
|
|
startManagedAutonomyFlowFromHeartbeatTask,
|
|
} from '../../utils/autonomyRuns'
|
|
import {
|
|
enqueuePendingNotification,
|
|
getCommandQueueSnapshot,
|
|
resetCommandQueue,
|
|
} from '../../utils/messageQueueManager'
|
|
import { cleanupTempDir, createTempDir } from '../../../tests/mocks/file-system'
|
|
|
|
let tempDir = ''
|
|
|
|
beforeEach(async () => {
|
|
tempDir = await createTempDir('autonomy-command-')
|
|
resetStateForTests()
|
|
resetCommandQueue()
|
|
setOriginalCwd(tempDir)
|
|
setProjectRoot(tempDir)
|
|
})
|
|
|
|
afterEach(async () => {
|
|
resetStateForTests()
|
|
resetCommandQueue()
|
|
if (tempDir) {
|
|
await cleanupTempDir(tempDir)
|
|
}
|
|
})
|
|
|
|
describe('/autonomy', () => {
|
|
test('status reports autonomy runs and managed flows separately', async () => {
|
|
const plainRun = await createAutonomyQueuedPrompt({
|
|
basePrompt: 'scheduled prompt',
|
|
trigger: 'scheduled-task',
|
|
rootDir: tempDir,
|
|
currentDir: tempDir,
|
|
sourceLabel: 'nightly',
|
|
})
|
|
await markAutonomyRunCompleted(plainRun.autonomy!.runId, tempDir)
|
|
|
|
await startManagedAutonomyFlowFromHeartbeatTask({
|
|
task: {
|
|
name: 'weekly-report',
|
|
interval: '7d',
|
|
prompt: 'Ship the weekly report',
|
|
steps: [
|
|
{
|
|
name: 'gather',
|
|
prompt: 'Gather weekly inputs',
|
|
},
|
|
{
|
|
name: 'draft',
|
|
prompt: 'Draft the weekly report',
|
|
},
|
|
],
|
|
},
|
|
rootDir: tempDir,
|
|
currentDir: tempDir,
|
|
})
|
|
|
|
const mod = await autonomyCommand.load()
|
|
const result = await mod.call('', {} as any)
|
|
|
|
expect(result.type).toBe('text')
|
|
expect(result.value).toContain('Autonomy runs: 2')
|
|
expect(result.value).toContain('Autonomy flows: 1')
|
|
expect(result.value).toContain('Completed: 1')
|
|
expect(result.value).toContain('Queued: 1')
|
|
})
|
|
|
|
test('runs subcommand lists recent autonomy runs', async () => {
|
|
const queued = await createAutonomyQueuedPrompt({
|
|
basePrompt: '<tick>12:00:00</tick>',
|
|
trigger: 'proactive-tick',
|
|
rootDir: tempDir,
|
|
currentDir: tempDir,
|
|
})
|
|
|
|
const mod = await autonomyCommand.load()
|
|
const result = await mod.call('runs 5', {} as any)
|
|
|
|
expect(result.type).toBe('text')
|
|
expect(result.value).toContain(queued.autonomy!.runId)
|
|
expect(result.value).toContain('proactive-tick')
|
|
})
|
|
|
|
test('flows subcommand lists managed flows and flow subcommand shows detail', async () => {
|
|
await startManagedAutonomyFlowFromHeartbeatTask({
|
|
task: {
|
|
name: 'weekly-report',
|
|
interval: '7d',
|
|
prompt: 'Ship the weekly report',
|
|
steps: [
|
|
{
|
|
name: 'gather',
|
|
prompt: 'Gather weekly inputs',
|
|
},
|
|
{
|
|
name: 'draft',
|
|
prompt: 'Draft the weekly report',
|
|
},
|
|
],
|
|
},
|
|
rootDir: tempDir,
|
|
currentDir: tempDir,
|
|
})
|
|
|
|
const [flow] = await listAutonomyFlows(tempDir)
|
|
const mod = await autonomyCommand.load()
|
|
|
|
const flowsResult = await mod.call('flows 5', {} as any)
|
|
expect(flowsResult.type).toBe('text')
|
|
expect(flowsResult.value).toContain(flow!.flowId)
|
|
expect(flowsResult.value).toContain('managed')
|
|
|
|
const flowResult = await mod.call(`flow ${flow!.flowId}`, {} as any)
|
|
expect(flowResult.type).toBe('text')
|
|
expect(flowResult.value).toContain(`Flow: ${flow!.flowId}`)
|
|
expect(flowResult.value).toContain('Mode: managed')
|
|
expect(flowResult.value).toContain('Current step: gather')
|
|
})
|
|
|
|
test('flow resume queues the next waiting step', async () => {
|
|
const waitingStart = await startManagedAutonomyFlowFromHeartbeatTask({
|
|
task: {
|
|
name: 'weekly-report',
|
|
interval: '7d',
|
|
prompt: 'Ship the weekly report',
|
|
steps: [
|
|
{
|
|
name: 'gather',
|
|
prompt: 'Gather weekly inputs',
|
|
waitFor: 'manual',
|
|
},
|
|
{
|
|
name: 'draft',
|
|
prompt: 'Draft the weekly report',
|
|
},
|
|
],
|
|
},
|
|
rootDir: tempDir,
|
|
currentDir: tempDir,
|
|
})
|
|
|
|
expect(waitingStart).toBeNull()
|
|
const [flow] = await listAutonomyFlows(tempDir)
|
|
|
|
const mod = await autonomyCommand.load()
|
|
const result = await mod.call(`flow resume ${flow!.flowId}`, {} as any)
|
|
|
|
expect(result.type).toBe('text')
|
|
expect(result.value).toContain('Queued the next managed step')
|
|
expect(getCommandQueueSnapshot()).toHaveLength(1)
|
|
expect(getCommandQueueSnapshot()[0]!.autonomy?.flowId).toBe(flow!.flowId)
|
|
})
|
|
|
|
test('flow cancel removes queued managed steps and marks the flow cancelled', async () => {
|
|
const queued = await startManagedAutonomyFlowFromHeartbeatTask({
|
|
task: {
|
|
name: 'weekly-report',
|
|
interval: '7d',
|
|
prompt: 'Ship the weekly report',
|
|
steps: [
|
|
{
|
|
name: 'gather',
|
|
prompt: 'Gather weekly inputs',
|
|
},
|
|
{
|
|
name: 'draft',
|
|
prompt: 'Draft the weekly report',
|
|
},
|
|
],
|
|
},
|
|
rootDir: tempDir,
|
|
currentDir: tempDir,
|
|
})
|
|
|
|
expect(queued).not.toBeNull()
|
|
enqueuePendingNotification(queued!)
|
|
expect(getCommandQueueSnapshot()).toHaveLength(1)
|
|
const [flow] = await listAutonomyFlows(tempDir)
|
|
const mod = await autonomyCommand.load()
|
|
const result = await mod.call(`flow cancel ${flow!.flowId}`, {} as any)
|
|
const [cancelledFlow] = await listAutonomyFlows(tempDir)
|
|
|
|
expect(result.type).toBe('text')
|
|
expect(result.value).toContain('Cancelled flow')
|
|
expect(cancelledFlow!.status).toBe('cancelled')
|
|
expect(getCommandQueueSnapshot()).toHaveLength(0)
|
|
})
|
|
|
|
test('flow cancel refuses to rewrite a terminal managed flow', async () => {
|
|
const queued = await startManagedAutonomyFlowFromHeartbeatTask({
|
|
task: {
|
|
name: 'weekly-report',
|
|
interval: '7d',
|
|
prompt: 'Ship the weekly report',
|
|
steps: [
|
|
{
|
|
name: 'gather',
|
|
prompt: 'Gather weekly inputs',
|
|
},
|
|
],
|
|
},
|
|
rootDir: tempDir,
|
|
currentDir: tempDir,
|
|
})
|
|
|
|
await markAutonomyRunCompleted(queued!.autonomy!.runId, tempDir)
|
|
|
|
const [flow] = await listAutonomyFlows(tempDir)
|
|
const mod = await autonomyCommand.load()
|
|
const result = await mod.call(`flow cancel ${flow!.flowId}`, {} as any)
|
|
const [terminalFlow] = await listAutonomyFlows(tempDir)
|
|
|
|
expect(result.type).toBe('text')
|
|
expect(result.value).toContain('already terminal')
|
|
expect(terminalFlow!.status).toBe('succeeded')
|
|
})
|
|
|
|
test('invalid subcommands return usage text', async () => {
|
|
const mod = await autonomyCommand.load()
|
|
const result = await mod.call('unknown', {} as any)
|
|
|
|
expect(result.type).toBe('text')
|
|
expect(result.value).toContain('Usage: /autonomy')
|
|
})
|
|
})
|