mirror of
https://github.com/Gitlawb/openclaude.git
synced 2026-08-24 02:34:15 -05:00
* feat(goal): add persisted session goal state Introduce the session-scoped goal state model, bounded evaluator, continuation controller, and transcript metadata persistence. Restore active goals on session resume while keeping achieved and cleared goals from auto-running. * feat(goal): add slash command controls Register /goal as a lazy local command with set, status, pause, resume, clear, and clear aliases. Route command-started continuations through hidden meta messages and make /clear clear active goal state. * feat(goal): continue goals through stop hooks Evaluate active goals once per terminal assistant turn after configured Stop hooks pass. Incomplete goals reuse the blocking-error continuation path; complete goals persist achieved status without spawning a parallel queue. * test(goal): cover commands continuation and resume Add focused coverage for command validation and aliases, state transitions, evaluator malformed-output handling, Stop-hook precedence, SDK/headless visibility, /clear lifecycle behavior, and durable resume persistence. * fix(goal): fail closed on evaluator failures * fix(goal): clear stale goal on resume * Persist goal continuations before auto-resume * test: isolate CI-sensitive state * test: isolate attribution provider state * test: tighten CI state isolation * fix(goal): clear cached metadata on resume * test(goal): harden resume metadata coverage * test: clean up teammate model fixture merge * fix(goal): align persistence session id type * fix(goal): address status command review * fix(goal): address follow-up review comments * test(goal): isolate review regression coverage * test(goal): make queryengine fixture ci-safe * fix(goal): clarify resume message * test: restore api preconnect provider mock * fix(goal): address review feedback
126 lines
4.3 KiB
TypeScript
126 lines
4.3 KiB
TypeScript
import { afterEach, beforeEach, describe, expect, mock, test } from 'bun:test'
|
|
import {
|
|
acquireSharedMutationLock,
|
|
releaseSharedMutationLock,
|
|
} from '../test/sharedMutationLock.js'
|
|
import * as actualProviders from './model/providers.js'
|
|
|
|
const originalEnv = { ...process.env }
|
|
const originalFetch = globalThis.fetch
|
|
|
|
function getMockApiProvider() {
|
|
if (process.env.CLAUDE_CODE_USE_OPENAI === '1') return 'openai'
|
|
if (process.env.CLAUDE_CODE_USE_GEMINI === '1') return 'gemini'
|
|
if (process.env.CLAUDE_CODE_USE_GITHUB === '1') return 'github'
|
|
return 'firstParty'
|
|
}
|
|
|
|
async function importFreshModule() {
|
|
mock.restore()
|
|
mock.module('./model/providers.js', () => ({
|
|
...actualProviders,
|
|
getAPIProvider: getMockApiProvider,
|
|
}))
|
|
return import(`./apiPreconnect.ts?ts=${Date.now()}-${Math.random()}`)
|
|
}
|
|
|
|
beforeEach(async () => {
|
|
await acquireSharedMutationLock('utils/apiPreconnect.test.ts')
|
|
process.env = { ...originalEnv }
|
|
})
|
|
|
|
afterEach(() => {
|
|
try {
|
|
process.env = { ...originalEnv }
|
|
globalThis.fetch = originalFetch
|
|
mock.restore()
|
|
mock.module('./model/providers.js', () => actualProviders)
|
|
} finally {
|
|
releaseSharedMutationLock()
|
|
}
|
|
})
|
|
|
|
describe('preconnectAnthropicApi', () => {
|
|
// The provider is injected directly rather than mocking getAPIProvider():
|
|
// bun does not unregister mock.module() overrides, so a leaked providers.js
|
|
// mock from another test file (e.g. fastMode) would otherwise force
|
|
// getAPIProvider() to 'firstParty' here and break these assertions.
|
|
test('does not fetch when OpenAI mode is enabled', async () => {
|
|
const fetchMock = mock(() => Promise.resolve(new Response(null, { status: 200 })))
|
|
globalThis.fetch = fetchMock as typeof globalThis.fetch
|
|
|
|
const { preconnectAnthropicApi } = await importFreshModule()
|
|
preconnectAnthropicApi('openai')
|
|
|
|
expect(fetchMock).not.toHaveBeenCalled()
|
|
})
|
|
|
|
test('does not fetch when Gemini mode is enabled', async () => {
|
|
const fetchMock = mock(() => Promise.resolve(new Response(null, { status: 200 })))
|
|
globalThis.fetch = fetchMock as typeof globalThis.fetch
|
|
|
|
const { preconnectAnthropicApi } = await importFreshModule()
|
|
preconnectAnthropicApi('gemini')
|
|
|
|
expect(fetchMock).not.toHaveBeenCalled()
|
|
})
|
|
|
|
test('does not fetch when GitHub mode is enabled', async () => {
|
|
const fetchMock = mock(() => Promise.resolve(new Response(null, { status: 200 })))
|
|
globalThis.fetch = fetchMock as typeof globalThis.fetch
|
|
|
|
const { preconnectAnthropicApi } = await importFreshModule()
|
|
preconnectAnthropicApi('github')
|
|
|
|
expect(fetchMock).not.toHaveBeenCalled()
|
|
})
|
|
|
|
test('fetches in first-party mode', async () => {
|
|
delete process.env.CLAUDE_CODE_USE_OPENAI
|
|
delete process.env.CLAUDE_CODE_USE_GEMINI
|
|
delete process.env.CLAUDE_CODE_USE_GITHUB
|
|
delete process.env.CLAUDE_CODE_USE_MISTRAL
|
|
delete process.env.CLAUDE_CODE_USE_BEDROCK
|
|
delete process.env.CLAUDE_CODE_USE_VERTEX
|
|
delete process.env.CLAUDE_CODE_USE_FOUNDRY
|
|
delete process.env.OPENAI_BASE_URL
|
|
delete process.env.OPENAI_API_BASE
|
|
delete process.env.OPENAI_MODEL
|
|
delete process.env.OPENAI_API_KEY
|
|
delete process.env.XAI_API_KEY
|
|
delete process.env.MINIMAX_API_KEY
|
|
delete process.env.VENICE_API_KEY
|
|
delete process.env.MIMO_API_KEY
|
|
delete process.env.NVIDIA_NIM
|
|
delete process.env.ANTHROPIC_BASE_URL
|
|
delete process.env.ANTHROPIC_API_KEY
|
|
delete process.env.HTTPS_PROXY
|
|
delete process.env.https_proxy
|
|
delete process.env.HTTP_PROXY
|
|
delete process.env.http_proxy
|
|
delete process.env.ANTHROPIC_UNIX_SOCKET
|
|
delete process.env.CLAUDE_CODE_CLIENT_CERT
|
|
delete process.env.CLAUDE_CODE_CLIENT_KEY
|
|
|
|
mock.module('./model/providers.js', () => ({
|
|
...actualProviders,
|
|
getAPIProvider: () => 'firstParty',
|
|
}))
|
|
const fetchMock = mock(() => Promise.resolve(new Response(null, { status: 200 })))
|
|
globalThis.fetch = fetchMock as typeof globalThis.fetch
|
|
|
|
const { preconnectAnthropicApi } = await importFreshModule()
|
|
preconnectAnthropicApi('firstParty')
|
|
|
|
expect(fetchMock).toHaveBeenCalledTimes(1)
|
|
})
|
|
|
|
test('keeps non-mocked provider exports available to neighboring imports', async () => {
|
|
await importFreshModule()
|
|
|
|
const providers = await import('./model/providers.js')
|
|
|
|
expect(typeof providers.isFirstPartyAnthropicBaseUrl).toBe('function')
|
|
})
|
|
})
|