diff --git a/packages/builtin-tools/src/tools/RemoteTriggerTool/__tests__/RemoteTriggerTool.test.ts b/packages/builtin-tools/src/tools/RemoteTriggerTool/__tests__/RemoteTriggerTool.test.ts index dc570a803..eb9b726c8 100644 --- a/packages/builtin-tools/src/tools/RemoteTriggerTool/__tests__/RemoteTriggerTool.test.ts +++ b/packages/builtin-tools/src/tools/RemoteTriggerTool/__tests__/RemoteTriggerTool.test.ts @@ -7,9 +7,14 @@ import { setOriginalCwd, setProjectRoot, } from 'src/bootstrap/state.js' +import { logMock } from '../../../../../../tests/mocks/log' +import { debugMock } from '../../../../../../tests/mocks/debug' let requestStatus = 200 +mock.module('src/utils/log.ts', logMock) +mock.module('src/utils/debug.ts', debugMock) + mock.module('axios', () => ({ default: { request: async () => ({ @@ -30,16 +35,41 @@ mock.module('src/services/oauth/client.js', () => ({ mock.module('src/constants/oauth.js', () => ({ getOauthConfig: () => ({ BASE_API_URL: 'https://example.test' }), + fileSuffixForOauthConfig: () => '', +})) + +mock.module('src/services/analytics/growthbook.js', () => ({ + getFeatureValue_CACHED_MAY_BE_STALE: () => true, +})) + +mock.module('src/services/policyLimits/index.js', () => ({ + isPolicyAllowed: () => true, +})) + +mock.module('bun:bundle', () => ({ + feature: () => false, })) let cwd = '' let previousCwd = '' +let auditRecords: Array> = [] + +mock.module('src/utils/remoteTriggerAudit.js', () => ({ + appendRemoteTriggerAuditRecord: async (record: Record) => { + const full = { ...record, auditId: record.auditId ?? 'test-audit-id', createdAt: Date.now() } + auditRecords.push(full) + return full + }, + resolveRemoteTriggerAuditPath: () => join(cwd, '.claude', 'remote-trigger-audit.jsonl'), +})) beforeEach(async () => { requestStatus = 200 + auditRecords = [] previousCwd = process.cwd() cwd = join(tmpdir(), `remote-trigger-tool-${Date.now()}-${Math.random().toString(16).slice(2)}`) await mkdir(cwd, { recursive: true }) + await mkdir(join(cwd, '.claude'), { recursive: true }) process.chdir(cwd) resetStateForTests() setOriginalCwd(cwd) @@ -61,13 +91,10 @@ describe('RemoteTriggerTool audit', () => { ) expect(result.data.audit_id).toBeString() - const raw = await readFile( - join(cwd, '.claude', 'remote-trigger-audit.jsonl'), - 'utf-8', - ) - expect(raw).toContain('"action":"run"') - expect(raw).toContain('"triggerId":"trigger-1"') - expect(raw).toContain('"ok":true') + expect(auditRecords).toHaveLength(1) + expect(auditRecords[0].action).toBe('run') + expect(auditRecords[0].triggerId).toBe('trigger-1') + expect(auditRecords[0].ok).toBe(true) }) test('writes an audit record before rethrowing validation failures', async () => { @@ -80,12 +107,9 @@ describe('RemoteTriggerTool audit', () => { ), ).rejects.toThrow('run requires trigger_id') - const raw = await readFile( - join(cwd, '.claude', 'remote-trigger-audit.jsonl'), - 'utf-8', - ) - expect(raw).toContain('"action":"run"') - expect(raw).toContain('"ok":false') - expect(raw).toContain('run requires trigger_id') + expect(auditRecords).toHaveLength(1) + expect(auditRecords[0].action).toBe('run') + expect(auditRecords[0].ok).toBe(false) + expect(auditRecords[0].error).toBe('run requires trigger_id') }) }) diff --git a/src/cli/handlers/__tests__/autonomy.test.ts b/src/cli/handlers/__tests__/autonomy.test.ts index 25e751bfd..17e6e2222 100644 --- a/src/cli/handlers/__tests__/autonomy.test.ts +++ b/src/cli/handlers/__tests__/autonomy.test.ts @@ -57,7 +57,7 @@ describe('autonomy CLI handler', () => { sourceLabel: 'nightly', }) - const output = await getAutonomyStatusText() + const output = await getAutonomyStatusText({ rootDir: tempDir }) expect(output).toContain('Autonomy runs: 1') expect(output).toContain('Queued: 1') @@ -77,7 +77,7 @@ describe('autonomy CLI handler', () => { })}\n`, ) - const output = await getAutonomyStatusText({ deep: true }) + const output = await getAutonomyStatusText({ deep: true, rootDir: tempDir }) expect(output).toContain('# Autonomy Deep Status') expect(output).toContain('## Workflow Runs') @@ -87,8 +87,8 @@ describe('autonomy CLI handler', () => { }) test('prints individual deep status sections for panel actions', async () => { - const pipes = await getAutonomyDeepSectionText('pipes') - const remoteControl = await getAutonomyDeepSectionText('remote-control') + const pipes = await getAutonomyDeepSectionText('pipes', { rootDir: tempDir }) + const remoteControl = await getAutonomyDeepSectionText('remote-control', { rootDir: tempDir }) expect(pipes).toContain('# Pipes') expect(pipes).toContain('Pipe registry:') @@ -116,17 +116,17 @@ describe('autonomy CLI handler', () => { }) const [waitingFlow] = await listAutonomyFlows(tempDir) - expect(await getAutonomyFlowsText()).toContain(waitingFlow!.flowId) - expect(await getAutonomyFlowText(waitingFlow!.flowId)).toContain( + expect(await getAutonomyFlowsText(undefined, { rootDir: tempDir })).toContain(waitingFlow!.flowId) + expect(await getAutonomyFlowText(waitingFlow!.flowId, { rootDir: tempDir })).toContain( 'Current step: wait', ) - const resumed = await resumeAutonomyFlowText(waitingFlow!.flowId) + const resumed = await resumeAutonomyFlowText(waitingFlow!.flowId, { rootDir: tempDir, currentDir: tempDir }) expect(resumed).toContain('Prepared the next managed step') expect(resumed).toContain('Prompt:') expect(resumed).toContain('Wait for manual signal') - const cancelled = await cancelAutonomyFlowText(waitingFlow!.flowId) + const cancelled = await cancelAutonomyFlowText(waitingFlow!.flowId, { rootDir: tempDir }) expect(cancelled).toContain('Cancelled flow') }) }) diff --git a/src/cli/handlers/autonomy.ts b/src/cli/handlers/autonomy.ts index c63865408..d7d7466db 100644 --- a/src/cli/handlers/autonomy.ts +++ b/src/cli/handlers/autonomy.ts @@ -37,10 +37,12 @@ export function parseAutonomyLimit(raw?: string | number): number { export async function getAutonomyStatusText(options?: { deep?: boolean + rootDir?: string }): Promise { + const rootDir = options?.rootDir const [runs, flows] = await Promise.all([ - listAutonomyRuns(), - listAutonomyFlows(), + listAutonomyRuns(rootDir), + listAutonomyFlows(rootDir), ]) if (options?.deep) { @@ -55,10 +57,11 @@ export async function getAutonomyStatusText(options?: { export async function getAutonomyDeepSectionText( sectionId: AutonomyDeepStatusSectionId, + options?: { rootDir?: string }, ): Promise { const [runs, flows] = await Promise.all([ - listAutonomyRuns(), - listAutonomyFlows(), + listAutonomyRuns(options?.rootDir), + listAutonomyFlows(options?.rootDir), ]) const sections = await formatAutonomyDeepStatusSections({ runs, flows }) const section = sections.find(item => item.id === sectionId) @@ -76,9 +79,10 @@ export async function autonomyStatusHandler(options?: { export async function getAutonomyRunsText( limit?: string | number, + options?: { rootDir?: string }, ): Promise { return formatAutonomyRunsList( - await listAutonomyRuns(), + await listAutonomyRuns(options?.rootDir), parseAutonomyLimit(limit), ) } @@ -91,9 +95,10 @@ export async function autonomyRunsHandler( export async function getAutonomyFlowsText( limit?: string | number, + options?: { rootDir?: string }, ): Promise { return formatAutonomyFlowsList( - await listAutonomyFlows(), + await listAutonomyFlows(options?.rootDir), parseAutonomyLimit(limit), ) } @@ -104,8 +109,11 @@ export async function autonomyFlowsHandler( process.stdout.write(`${await getAutonomyFlowsText(limit)}\n`) } -export async function getAutonomyFlowText(flowId: string): Promise { - return formatAutonomyFlowDetail(await getAutonomyFlowById(flowId)) +export async function getAutonomyFlowText( + flowId: string, + options?: { rootDir?: string }, +): Promise { + return formatAutonomyFlowDetail(await getAutonomyFlowById(flowId, options?.rootDir)) } export async function autonomyFlowHandler(flowId: string): Promise { @@ -116,9 +124,13 @@ export async function cancelAutonomyFlowText( flowId: string, options?: { removeQueuedInMemory?: boolean + rootDir?: string }, ): Promise { - const cancelled = await requestManagedAutonomyFlowCancel({ flowId }) + const cancelled = await requestManagedAutonomyFlowCancel({ + flowId, + rootDir: options?.rootDir, + }) if (!cancelled) { return 'Autonomy flow not found.' } @@ -132,12 +144,12 @@ export async function cancelAutonomyFlowText( removedCount = removed.length for (const command of removed) { if (command.autonomy?.runId) { - await markAutonomyRunCancelled(command.autonomy.runId) + await markAutonomyRunCancelled(command.autonomy.runId, options?.rootDir) } } } else { for (const runId of cancelled.queuedRunIds) { - await markAutonomyRunCancelled(runId) + await markAutonomyRunCancelled(runId, options?.rootDir) } removedCount = cancelled.queuedRunIds.length } @@ -155,9 +167,15 @@ export async function resumeAutonomyFlowText( flowId: string, options?: { enqueueInMemory?: boolean + rootDir?: string + currentDir?: string }, ): Promise { - const command = await resumeManagedAutonomyFlowPrompt({ flowId }) + const command = await resumeManagedAutonomyFlowPrompt({ + flowId, + rootDir: options?.rootDir, + currentDir: options?.currentDir, + }) if (!command) { return 'Autonomy flow is not waiting or was not found.' }