chore: import upstream snapshot with attribution

This commit is contained in:
wehub-resource-sync
2026-07-13 12:44:08 +08:00
commit 983960e2dd
1244 changed files with 281996 additions and 0 deletions
@@ -0,0 +1,151 @@
import { beforeAll, beforeEach, describe, expect, it, vi } from 'vitest'
process.env['LEON_NODE_ENV'] = 'testing'
process.env['LEON_LLM'] = 'openai/gpt-5.4'
const coreMocks = vi.hoisted(() => ({
llmProvider: {
prompt: vi.fn()
},
llmManager: {
coreLLMDuties: {
'action-calling': {
maxTokens: 384,
thoughtTokensBudget: 0,
temperature: 0.1
}
}
}
}))
const skillHelperMocks = vi.hoisted(() => ({
getNewSkillConfig: vi.fn()
}))
vi.mock('@/helpers/log-helper', () => ({
LogHelper: {
title: vi.fn(),
success: vi.fn(),
info: vi.fn(),
warning: vi.fn(),
error: vi.fn()
}
}))
vi.mock('@/core', () => ({
LLM_PROVIDER: coreMocks.llmProvider,
LLM_MANAGER: coreMocks.llmManager
}))
vi.mock('@/helpers/skill-domain-helper', () => ({
SkillDomainHelper: skillHelperMocks
}))
let ActionCallingLLMDuty: typeof import('@/core/llm-manager/llm-duties/action-calling-llm-duty').ActionCallingLLMDuty
beforeAll(async () => {
;({ ActionCallingLLMDuty } = await import(
'@/core/llm-manager/llm-duties/action-calling-llm-duty'
))
})
beforeEach(() => {
vi.clearAllMocks()
})
describe('ActionCallingLLMDuty', () => {
it('uses tool calls to return a successful action selection', async () => {
skillHelperMocks.getNewSkillConfig.mockResolvedValue({
actions: {
set_timer: {
type: 'logic',
description: 'Set a timer.',
parameters: {
duration: {
type: 'string',
description: 'Timer duration.'
}
}
}
},
workflow: []
})
coreMocks.llmProvider.prompt.mockResolvedValue({
output: '',
toolCalls: [
{
id: 'tool_1',
type: 'function',
function: {
name: 'set_timer',
arguments: JSON.stringify({
duration: '3 seconds'
})
}
}
],
usedInputTokens: 10,
usedOutputTokens: 4
})
const duty = new ActionCallingLLMDuty({
input: 'Set a timer for 3 seconds',
skillName: 'timer_skill',
workflowContext: {
recentUtterances: [],
recentActionArguments: [],
collectedParameters: {},
recentEntities: []
}
})
await duty.init()
const result = await duty.execute()
const parsedOutput = JSON.parse(String(result?.output))
expect(parsedOutput).toEqual([
{
status: 'success',
name: 'set_timer',
arguments: {
duration: '3 seconds'
}
}
])
})
it('short-circuits when the skill workflow starts with a parameterless action', async () => {
skillHelperMocks.getNewSkillConfig.mockResolvedValue({
actions: {
set_up: {
type: 'dialog',
description: 'Set up the game.'
},
play: {
type: 'logic',
description: 'Play.'
}
},
workflow: ['set_up', 'play']
})
const duty = new ActionCallingLLMDuty({
input: 'Play rock paper scissors',
skillName: 'rochambeau_skill'
})
await duty.init()
const result = await duty.execute()
const parsedOutput = JSON.parse(String(result?.output))
expect(coreMocks.llmProvider.prompt).not.toHaveBeenCalled()
expect(parsedOutput).toEqual([
{
status: 'success',
name: 'set_up',
arguments: {}
}
])
})
})
+231
View File
@@ -0,0 +1,231 @@
import fs from 'node:fs'
import os from 'node:os'
import path from 'node:path'
import YAML from 'yaml'
import { afterEach, beforeEach, describe, expect, it, vi } from 'vitest'
const syncedEnvKeys = [
'LEON_LANG',
'LEON_HOST',
'LEON_PORT',
'LEON_ROUTING_MODE',
'LEON_MOOD',
'LEON_LLM',
'LEON_WORKFLOW_LLM',
'LEON_AGENT_LLM',
'LEON_WAKE_WORD',
'LEON_ASR',
'LEON_ASR_PROVIDER',
'LEON_TTS',
'LEON_TTS_PROVIDER',
'LEON_TIME_ZONE',
'LEON_AFTER_SPEECH',
'LEON_TELEMETRY',
'LEON_PY_TCP_SERVER_HOST',
'LEON_PY_TCP_SERVER_PORT',
'LEON_LLAMACPP_BASE_URL',
'LEON_SGLANG_BASE_URL',
'LEON_CLIENT_INTERFACE_TOKEN',
'LEON_OPENAI_API_KEY'
]
const profilePaths = {
configPath: ''
}
async function loadConfigManager(): Promise<
typeof import('@/config').CONFIG_MANAGER
> {
vi.doMock('@/leon-roots', () => ({
PROFILE_CONFIG_PATH: profilePaths.configPath
}))
const module = await import('@/config')
return module.CONFIG_MANAGER
}
describe('ConfigManager', () => {
let tmpDir = ''
let previousEnv: Record<string, string | undefined> = {}
beforeEach(() => {
tmpDir = fs.mkdtempSync(path.join(os.tmpdir(), 'leon-config-'))
profilePaths.configPath = path.join(tmpDir, 'config.yml')
previousEnv = Object.fromEntries(
syncedEnvKeys.map((key) => [key, process.env[key]])
)
vi.resetModules()
})
afterEach(() => {
for (const key of syncedEnvKeys) {
const value = previousEnv[key]
if (value === undefined) {
delete process.env[key]
} else {
process.env[key] = value
}
}
fs.rmSync(tmpDir, {
recursive: true,
force: true
})
})
it('returns profile config values and syncs runtime env mappings', async () => {
process.env['LEON_CLIENT_INTERFACE_TOKEN'] = 'client-secret'
process.env['LEON_OPENAI_API_KEY'] = 'openai-secret'
const profileConfig = {
language: 'fr-FR',
server: {
host: 'http://127.0.0.1',
port: 5_499
},
client_interface: {
allowed_origins: ['http://localhost:1420'],
auth: {
enabled: true,
token: {
env: 'LEON_CLIENT_INTERFACE_TOKEN'
}
}
},
routing: {
mode: 'controlled'
},
llm: {
default: null,
workflow: 'openai/gpt-4.1-mini',
agent: null,
providers: {
llamacpp: {
base_url: 'http://127.0.0.1:8081/v1',
api_key: {
env: 'LEON_LLAMACPP_API_KEY'
}
},
sglang: {
base_url: 'http://127.0.0.1:30001/v1',
api_key: {
env: 'LEON_SGLANG_API_KEY'
}
},
openrouter: {
api_key: {
env: 'LEON_OPENROUTER_API_KEY'
}
},
zai: {
api_key: {
env: 'LEON_ZAI_API_KEY'
}
},
minimax: {
api_key: {
env: 'LEON_MINIMAX_API_KEY'
}
},
openai: {
api_key: {
env: 'LEON_OPENAI_API_KEY'
}
},
anthropic: {
api_key: {
env: 'LEON_ANTHROPIC_API_KEY'
}
},
moonshotai: {
api_key: {
env: 'LEON_MOONSHOTAI_API_KEY'
}
},
huggingface: {
api_key: {
env: 'LEON_HUGGINGFACE_API_KEY'
}
},
cerebras: {
api_key: {
env: 'LEON_CEREBRAS_API_KEY'
}
},
groq: {
api_key: {
env: 'LEON_GROQ_API_KEY'
}
}
}
},
mood: {
mode: 'tired'
},
runtime: {
pulse_enabled: false,
private_diary_enabled: false
},
context: {
disabled_files: ['*']
},
availability: {
skills: {
allowed: ['date_time_skill'],
disabled: ['weather_forecast_skill']
},
tools: {
allowed: ['coding_development.codex'],
disabled: ['coding_development.claude_code']
}
},
python_tcp_server: {
host: '127.0.0.2',
port: 5_477
},
voice: {
wake_word_enabled: true,
asr: {
enabled: true,
provider: 'local'
},
tts: {
enabled: true,
provider: 'local'
}
},
time_zone: 'Europe/Paris',
after_speech_enabled: true,
telemetry_enabled: false
}
fs.writeFileSync(profilePaths.configPath, YAML.stringify(profileConfig))
const configManager = await loadConfigManager()
expect(configManager.getConfig()).toEqual(profileConfig)
expect(process.env['LEON_LANG']).toBe('fr-FR')
expect(process.env['LEON_HOST']).toBe('http://127.0.0.1')
expect(process.env['LEON_PORT']).toBe('5499')
expect(process.env['LEON_ROUTING_MODE']).toBe('controlled')
expect(process.env['LEON_MOOD']).toBe('tired')
expect(process.env['LEON_LLM']).toBe('')
expect(process.env['LEON_WORKFLOW_LLM']).toBe('openai/gpt-4.1-mini')
expect(process.env['LEON_AGENT_LLM']).toBe('')
expect(process.env['LEON_TIME_ZONE']).toBe('Europe/Paris')
expect(process.env['LEON_PY_TCP_SERVER_PORT']).toBe('5477')
expect(configManager.resolveSecretReference(
profileConfig.client_interface.auth.token
)).toBe('client-secret')
expect(configManager.getProviderAPIKeyEnv('openai')).toBe(
'LEON_OPENAI_API_KEY'
)
expect(configManager.getProviderAPIKey('openai')).toBe('openai-secret')
expect(configManager.getProviderBaseURL('llamacpp')).toBe(
'http://127.0.0.1:8081/v1'
)
})
})
@@ -0,0 +1,35 @@
import { describe, expect, it } from 'vitest'
import { JsonRedactionHelper } from '@/helpers/json-redaction-helper'
describe('JsonRedactionHelper', () => {
it('redacts sensitive keys recursively', () => {
expect(
JsonRedactionHelper.redactSensitiveValues({
OPENROUTER_API_KEY: 'sk-test',
nested: {
access_token: 'token-test',
model: 'google/gemini'
},
providers: [
{
clientSecret: 'secret-test',
name: 'openrouter'
}
]
})
).toEqual({
OPENROUTER_API_KEY: '***',
nested: {
access_token: '***',
model: 'google/gemini'
},
providers: [
{
clientSecret: '***',
name: 'openrouter'
}
]
})
})
})
+617
View File
@@ -0,0 +1,617 @@
import { beforeAll, beforeEach, describe, expect, it, vi } from 'vitest'
process.env['LEON_NODE_ENV'] = 'testing'
process.env['LEON_LLM'] = 'openai/gpt-5.4'
const defaultProcessResult = {
contextName: '',
skillName: '',
actionName: '',
skillConfigPath: '',
skillConfig: {
name: '',
bridge: 'python',
version: '',
workflow: []
},
localeSkillConfig: {
variables: {},
widgetContents: {}
},
actionConfig: null,
new: {
utterance: '',
actionArguments: {},
entities: [],
sentiment: {}
},
context: {
utterances: [],
actionArguments: [],
entities: [],
sentiments: [],
data: {}
}
}
const testState = vi.hoisted(() => ({
currentNlu: null as unknown,
skillConfigs: {} as Record<string, Record<string, unknown>>,
localeConfigs: {} as Record<string, Record<string, unknown>>
}))
const coreMocks = vi.hoisted(() => ({
brain: {
runSkillAction: vi.fn(),
talk: vi.fn(async () => undefined),
suggest: vi.fn(),
isMuted: false,
lang: 'en',
wernicke: vi.fn(() => '')
},
conversationLogger: {
push: vi.fn(async () => undefined),
load: vi.fn(async () => []),
loadAll: vi.fn(async () => [])
},
socket: {
emit: vi.fn()
},
memoryManager: {
observeTurn: vi.fn(async () => undefined),
savePersistentMemoryCandidatesFromTurn: vi.fn(async () => undefined)
},
selfModelManager: {
observeTurn: vi.fn(async () => undefined)
},
llmProvider: {
consumeLastProviderErrorMessage: vi.fn(() => null)
},
persona: {
refreshContextInfo: vi.fn()
},
toolCallLogger: {
runOwnerQuery: vi.fn(async (_utterance: string, runner: () => Promise<unknown>) => {
return runner()
})
},
pulseManager: {
observeOwnerUtterance: vi.fn(async () => undefined)
},
postTurnMaintenanceQueue: {
enqueue: vi.fn()
}
}))
const dutyMocks = vi.hoisted(() => ({
skillRouterExecute: vi.fn(),
actionCallingExecute: vi.fn(),
slotFillingExecute: vi.fn()
}))
const skillHelperMocks = vi.hoisted(() => ({
listSkillDescriptorsSync: vi.fn(() => {
return Object.keys(testState.skillConfigs).map((skillName) => ({
id: skillName,
format: 'leon-native'
}))
}),
getSkillDescriptorSync: vi.fn((skillName: string) => {
return testState.skillConfigs[skillName]
? {
id: skillName,
format: 'leon-native'
}
: null
}),
getNewSkillConfig: vi.fn(async (skillName: string) => {
return testState.skillConfigs[skillName] || null
}),
getNewSkillConfigPath: vi.fn((skillName: string) => {
return `/tmp/${skillName}/skill.json`
}),
getSkillLocaleConfig: vi.fn(async (_lang: string, skillName: string) => {
return (
testState.localeConfigs[skillName] || {
variables: {},
widget_contents: {},
actions: {}
}
)
})
}))
function cloneDefaultProcessResult(): typeof defaultProcessResult {
return JSON.parse(JSON.stringify(defaultProcessResult)) as typeof defaultProcessResult
}
async function applyProcessResultUpdate(
newResult: Record<string, unknown>
): Promise<void> {
const currentNlu = testState.currentNlu as {
nluProcessResult: typeof defaultProcessResult
} | null
if (!currentNlu) {
return
}
const current = currentNlu.nluProcessResult
if (newResult['new'] && typeof newResult['new'] === 'object') {
const newData = newResult['new'] as Record<string, unknown>
if (typeof newData['utterance'] === 'string' && newData['utterance'] !== '') {
currentNlu.nluProcessResult = {
...current,
new: {
utterance: newData['utterance'],
actionArguments: {},
entities: [],
sentiment: {}
},
context: {
...current.context,
utterances: [...current.context.utterances, newData['utterance']],
entities: [],
sentiments: []
}
}
return
}
if (newData['actionArguments']) {
currentNlu.nluProcessResult = {
...current,
new: {
...current.new,
actionArguments: newData['actionArguments'] as Record<string, unknown>
},
context: {
...current.context,
actionArguments: [
...current.context.actionArguments,
{ ...(newData['actionArguments'] as Record<string, unknown>) }
]
}
}
return
}
}
if (typeof newResult['skillName'] === 'string' && newResult['skillName'] !== '') {
const skillName = newResult['skillName'] as string
const skillConfig = testState.skillConfigs[skillName] as Record<string, unknown>
const localeConfig =
(testState.localeConfigs[skillName] as Record<string, unknown>) || {}
currentNlu.nluProcessResult = {
...cloneDefaultProcessResult(),
contextName: skillName.replace(/_skill$/, ''),
skillName,
skillConfigPath: `/tmp/${skillName}/skill.json`,
skillConfig: {
name: String(skillConfig['name'] || ''),
bridge: String(skillConfig['bridge'] || 'nodejs'),
version: String(skillConfig['version'] || '1.0.0'),
workflow: Array.isArray(skillConfig['workflow']) ? skillConfig['workflow'] : []
},
localeSkillConfig: {
variables:
(localeConfig['variables'] as Record<string, unknown>) || {},
widgetContents:
(localeConfig['widget_contents'] as Record<string, unknown>) || {}
},
new: current.new,
context: {
utterances: [current.new.utterance],
actionArguments: [],
entities: [],
sentiments: [],
data: current.context.data
}
}
return
}
if (typeof newResult['actionName'] === 'string' && newResult['actionName'] !== '') {
const actionName = newResult['actionName'] as string
const skillConfig = testState.skillConfigs[current.skillName] as Record<
string,
unknown
>
const actions = (skillConfig['actions'] as Record<string, unknown>) || {}
const localeConfig =
(testState.localeConfigs[current.skillName] as Record<string, unknown>) || {}
const localeActions =
(localeConfig['actions'] as Record<string, Record<string, unknown>>) || {}
currentNlu.nluProcessResult = {
...current,
actionName,
actionConfig: {
...(actions[actionName] as Record<string, unknown>),
...(localeActions[actionName] || {})
}
}
return
}
currentNlu.nluProcessResult = {
...current,
...(newResult as Partial<typeof defaultProcessResult>)
}
}
vi.mock('@/helpers/log-helper', () => ({
LogHelper: {
title: vi.fn(),
success: vi.fn(),
info: vi.fn(),
warning: vi.fn(),
error: vi.fn()
}
}))
vi.mock('@/constants', async (importOriginal) => {
const actual = await importOriginal<typeof import('@/constants')>()
return {
...actual,
LEON_ROUTING_MODE: 'controlled'
}
})
vi.mock('@/core', () => ({
BRAIN: coreMocks.brain,
CONVERSATION_LOGGER: coreMocks.conversationLogger,
SOCKET_SERVER: {
socket: coreMocks.socket,
emitToChatClients: coreMocks.socket.emit,
emitAnswerToChatClients: coreMocks.socket.emit,
clearLiveWidgets: vi.fn()
},
MEMORY_MANAGER: coreMocks.memoryManager,
PERSONA: coreMocks.persona,
LLM_PROVIDER: coreMocks.llmProvider,
TOOL_CALL_LOGGER: coreMocks.toolCallLogger,
SELF_MODEL_MANAGER: coreMocks.selfModelManager,
PULSE_MANAGER: coreMocks.pulseManager,
POST_TURN_MAINTENANCE_QUEUE: coreMocks.postTurnMaintenanceQueue
}))
vi.mock('@/core/config-states/config-state', () => ({
CONFIG_STATE: {
getRoutingModeState: (): { getRoutingMode: () => string } => ({
getRoutingMode: (): string => 'controlled'
}),
getModelState: (): {
getWorkflowTarget: () => { isEnabled: boolean }
getAgentTarget: () => { isEnabled: boolean }
} => ({
getWorkflowTarget: (): { isEnabled: boolean } => ({
isEnabled: true
}),
getAgentTarget: (): { isEnabled: boolean } => ({
isEnabled: true
})
})
}
}))
vi.mock('@/helpers/skill-domain-helper', () => ({
SkillDomainHelper: skillHelperMocks
}))
vi.mock('@/core/nlp/nlu/workflow-progress-widget', () => ({
WorkflowProgressWidget: class {
public startTurn(): void {}
public showChoosingSkill(): void {}
public showPickingAction(): void {}
public showResolvingParameters(): void {}
public startAction(): void {}
public completeRoutingOnly(): void {}
public completeSelectionNotFound(): void {}
public completeAll(): void {}
public reset(): void {}
}
}))
vi.mock('@/core/nlp/nlu/nlu-process-result-updater', () => ({
DEFAULT_NLU_PROCESS_RESULT: cloneDefaultProcessResult(),
NLUProcessResultUpdater: {
update: vi.fn(applyProcessResultUpdate)
}
}))
vi.mock('@/core/llm-manager/llm-duties/skill-router-llm-duty', () => ({
SkillRouterLLMDuty: class {
public async init(): Promise<void> {}
public async execute(): Promise<unknown> {
return dutyMocks.skillRouterExecute()
}
}
}))
vi.mock('@/core/llm-manager/llm-duties/action-calling-llm-duty', () => ({
ActionCallingLLMDuty: class {
public async init(): Promise<void> {}
public async execute(): Promise<unknown> {
return dutyMocks.actionCallingExecute()
}
}
}))
vi.mock('@/core/llm-manager/llm-duties/slot-filling-llm-duty', () => ({
SlotFillingLLMDuty: class {
public async init(): Promise<void> {}
public async execute(): Promise<unknown> {
return dutyMocks.slotFillingExecute()
}
}
}))
vi.mock('@/core/llm-manager/llm-duties/react-llm-duty', () => ({
ReActLLMDuty: class {
public async init(): Promise<void> {}
public async execute(): Promise<unknown> {
return {
output: 'fallback'
}
}
}
}))
let NLUClass: typeof import('@/core/nlp/nlu/nlu').default
beforeAll(async () => {
;({ default: NLUClass } = await import('@/core/nlp/nlu/nlu'))
})
beforeEach(() => {
vi.clearAllMocks()
testState.skillConfigs = {}
testState.localeConfigs = {}
const nlu = new NLUClass()
nlu.nluProcessResult = cloneDefaultProcessResult()
testState.currentNlu = nlu
})
describe('Controlled NLU', () => {
it('selects the only enabled native skill without calling the skill router', async () => {
const nlu = testState.currentNlu as InstanceType<typeof NLUClass>
testState.skillConfigs['demo_only_skill'] = {
name: 'Demo Only',
bridge: 'nodejs',
version: '1.0.0',
workflow: [],
actions: {
run: {
type: 'logic',
description: 'Run the only skill.'
}
}
}
const chosenSkill = await (nlu as unknown as {
chooseSkill: (utterance: string) => Promise<string | null>
}).chooseSkill('Run this')
expect(chosenSkill).toBe('demo_only_skill')
expect(dutyMocks.skillRouterExecute).not.toHaveBeenCalled()
})
it('executes workflow actions sequentially', async () => {
const nlu = testState.currentNlu as InstanceType<typeof NLUClass>
testState.skillConfigs['demo_flow_skill'] = {
name: 'Demo Workflow',
bridge: 'nodejs',
version: '1.0.0',
workflow: ['step_one', 'step_two'],
actions: {
step_one: {
type: 'logic',
description: 'First step.'
},
step_two: {
type: 'logic',
description: 'Second step.'
}
}
}
testState.localeConfigs['demo_flow_skill'] = {
variables: {},
widget_contents: {},
actions: {
step_one: {},
step_two: {}
}
}
nlu.nluProcessResult = {
...cloneDefaultProcessResult(),
contextName: 'demo_flow',
skillName: 'demo_flow_skill',
actionName: 'step_one',
skillConfigPath: '/tmp/demo_flow_skill/skill.json',
skillConfig: {
name: 'Demo Workflow',
bridge: 'nodejs',
version: '1.0.0',
workflow: ['step_one', 'step_two']
},
localeSkillConfig: {
variables: {},
widgetContents: {}
},
actionConfig: {
type: 'logic',
description: 'First step.'
},
new: {
utterance: 'Run the demo workflow',
actionArguments: {},
entities: [],
sentiment: {}
},
context: {
utterances: ['Run the demo workflow'],
actionArguments: [],
entities: [],
sentiments: [],
data: {}
}
}
coreMocks.brain.runSkillAction
.mockResolvedValueOnce({ core: {} })
.mockResolvedValueOnce({ core: {} })
await (nlu as unknown as { handleActionSuccess: (output: Record<string, unknown>) => Promise<void> }).handleActionSuccess({
status: 'success',
name: 'step_one',
arguments: {}
})
expect(coreMocks.brain.runSkillAction).toHaveBeenCalledTimes(2)
expect(coreMocks.brain.runSkillAction.mock.calls[0]?.[0].actionName).toBe(
'step_one'
)
expect(coreMocks.brain.runSkillAction.mock.calls[1]?.[0].actionName).toBe(
'step_two'
)
})
it('fills a missing parameter before executing the pending workflow action', async () => {
const nlu = testState.currentNlu as InstanceType<typeof NLUClass>
testState.skillConfigs['timer_skill'] = {
name: 'Timer',
bridge: 'nodejs',
version: '1.0.0',
workflow: [],
actions: {
set_timer: {
type: 'logic',
description: 'Set a timer.',
parameters: {
duration: {
type: 'string',
description: 'Timer duration.'
}
}
}
}
}
testState.localeConfigs['timer_skill'] = {
variables: {},
widget_contents: {},
actions: {
set_timer: {}
}
}
nlu.nluProcessResult = {
...cloneDefaultProcessResult(),
contextName: 'timer',
skillName: 'timer_skill',
actionName: 'set_timer',
skillConfigPath: '/tmp/timer_skill/skill.json',
skillConfig: {
name: 'Timer',
bridge: 'nodejs',
version: '1.0.0',
workflow: []
},
localeSkillConfig: {
variables: {},
widgetContents: {}
},
actionConfig: {
type: 'logic',
description: 'Set a timer.',
parameters: {
duration: {
type: 'string',
description: 'Timer duration.'
}
}
},
new: {
utterance: '3 secs',
actionArguments: {},
entities: [],
sentiment: {}
},
context: {
utterances: ['Set a timer', '3 secs'],
actionArguments: [],
entities: [],
sentiments: [],
data: {}
}
}
nlu.conversation.setActiveState({
startingUtterance: 'Set a timer',
pendingAction: 'timer_skill:set_timer',
missingParameters: ['duration'],
collectedParameters: {}
})
dutyMocks.slotFillingExecute.mockResolvedValue({
output: {
status: 'success',
filled_slots: {
duration: '3 secs'
}
}
})
coreMocks.brain.runSkillAction.mockResolvedValue({
core: {}
})
const shouldContinue = await (nlu as unknown as {
preProcessRoute: () => Promise<boolean>
}).preProcessRoute()
expect(shouldContinue).toBe(false)
expect(coreMocks.brain.runSkillAction).toHaveBeenCalledTimes(1)
expect(
coreMocks.brain.runSkillAction.mock.calls[0]?.[0].new.actionArguments
).toEqual({
duration: '3 secs'
})
})
it('offers fallback, code generation, or cancel when controlled mode cannot find a skill', async () => {
const nlu = testState.currentNlu as InstanceType<typeof NLUClass>
nlu.nluProcessResult = {
...cloneDefaultProcessResult(),
new: {
utterance: 'Do something unknown',
actionArguments: {},
entities: [],
sentiment: {}
}
}
await (nlu as unknown as {
handleSkillOrActionNotFound: () => Promise<void>
}).handleSkillOrActionNotFound()
expect(coreMocks.brain.talk).toHaveBeenCalledWith(
expect.stringContaining('fall back to agent mode'),
true
)
expect(coreMocks.brain.suggest).toHaveBeenCalledWith([
'Fallback to agent mode',
'Write the skill code',
'Cancel'
])
})
})
@@ -0,0 +1,75 @@
import { beforeAll, beforeEach, describe, expect, it, vi } from 'vitest'
process.env['LEON_NODE_ENV'] = 'testing'
process.env['LEON_LLM'] = 'openai/gpt-5.4'
const coreMocks = vi.hoisted(() => ({
eventEmitter: {
on: vi.fn()
},
persona: {
getDutySystemPrompt: vi.fn((prompt: string) => `persona:${prompt}`)
},
llmProvider: {
prompt: vi.fn(),
consumeLastProviderErrorMessage: vi.fn(() => null)
},
llmManager: {
coreLLMDuties: {
paraphrase: {
maxTokens: 192,
thoughtTokensBudget: 0,
temperature: 0.6
}
}
}
}))
vi.mock('@/helpers/log-helper', () => ({
LogHelper: {
title: vi.fn(),
success: vi.fn(),
info: vi.fn(),
warning: vi.fn(),
error: vi.fn()
}
}))
vi.mock('@/core', () => ({
EVENT_EMITTER: coreMocks.eventEmitter,
PERSONA: coreMocks.persona,
LLM_PROVIDER: coreMocks.llmProvider,
LLM_MANAGER: coreMocks.llmManager
}))
let ParaphraseLLMDuty: typeof import('@/core/llm-manager/llm-duties/paraphrase-llm-duty').ParaphraseLLMDuty
beforeAll(async () => {
;({ ParaphraseLLMDuty } = await import(
'@/core/llm-manager/llm-duties/paraphrase-llm-duty'
))
})
beforeEach(() => {
vi.clearAllMocks()
})
describe('ParaphraseLLMDuty', () => {
it('returns the provider paraphrase output', async () => {
coreMocks.llmProvider.prompt.mockResolvedValue({
output: 'I included your items in the shopping list.',
usedInputTokens: 12,
usedOutputTokens: 9
})
const duty = new ParaphraseLLMDuty({
input: 'I added your items to the shopping list.'
})
await duty.init()
const result = await duty.execute()
expect(result?.output).toBe('I included your items in the shopping list.')
expect(coreMocks.persona.getDutySystemPrompt).toHaveBeenCalled()
})
})
+237
View File
@@ -0,0 +1,237 @@
import fs from 'node:fs'
import os from 'node:os'
import path from 'node:path'
import YAML from 'yaml'
import { afterEach, beforeEach, describe, expect, it, vi } from 'vitest'
const profilePaths = {
configPath: '',
dotEnvPath: ''
}
async function loadProfileHelper(): Promise<
typeof import('@/helpers/profile-helper').ProfileHelper
> {
vi.doMock('@/leon-roots', () => ({
PROFILE_CONFIG_PATH: profilePaths.configPath,
PROFILE_DOT_ENV_PATH: profilePaths.dotEnvPath
}))
const module = await import('@/helpers/profile-helper')
return module.ProfileHelper
}
function writeAvailabilityConfig(availability: unknown): void {
fs.writeFileSync(
profilePaths.configPath,
YAML.stringify({
availability
})
)
}
function readAvailabilityConfig(): unknown {
return YAML.parse(fs.readFileSync(profilePaths.configPath, 'utf8')).availability
}
describe('ProfileHelper', () => {
let tmpDir = ''
beforeEach(() => {
tmpDir = fs.mkdtempSync(path.join(os.tmpdir(), 'leon-profile-helper-'))
profilePaths.configPath = path.join(tmpDir, 'config.yml')
profilePaths.dotEnvPath = path.join(tmpDir, '.env')
vi.resetModules()
})
afterEach(() => {
fs.rmSync(tmpDir, {
recursive: true,
force: true
})
})
it('allows everything by default when policy files are missing', async () => {
const ProfileHelper = await loadProfileHelper()
expect(ProfileHelper.isSkillDisabled('date_time_skill')).toBe(false)
expect(
ProfileHelper.isToolDisabled('codex', 'coding_development')
).toBe(false)
})
it('treats empty allowed lists as unrestricted', async () => {
writeAvailabilityConfig({
skills: {
allowed: [],
disabled: ['date_time_skill']
},
tools: {
allowed: [],
disabled: ['coding_development.codex']
}
})
const ProfileHelper = await loadProfileHelper()
expect(ProfileHelper.isSkillDisabled('weather_forecast_skill')).toBe(false)
expect(ProfileHelper.isSkillDisabled('date_time_skill')).toBe(true)
expect(
ProfileHelper.isToolDisabled('opencode', 'coding_development')
).toBe(false)
expect(
ProfileHelper.isToolDisabled('codex', 'coding_development')
).toBe(true)
})
it('restricts access when allowed lists contain ids', async () => {
writeAvailabilityConfig({
skills: {
allowed: ['coding_agent_router_skill'],
disabled: []
},
tools: {
allowed: ['coding_development.codex'],
disabled: []
}
})
const ProfileHelper = await loadProfileHelper()
expect(
ProfileHelper.isSkillDisabled('coding_agent_router_skill')
).toBe(false)
expect(ProfileHelper.isSkillDisabled('date_time_skill')).toBe(true)
expect(
ProfileHelper.isToolDisabled('codex', 'coding_development')
).toBe(false)
expect(
ProfileHelper.isToolDisabled('codex', 'other_toolkit')
).toBe(true)
})
it('ignores disabled ids while allow-only lists are active', async () => {
writeAvailabilityConfig({
skills: {
allowed: ['coding_agent_router_skill'],
disabled: ['coding_agent_router_skill']
},
tools: {
allowed: ['coding_development.codex'],
disabled: ['coding_development.codex']
}
})
const ProfileHelper = await loadProfileHelper()
expect(
ProfileHelper.isSkillDisabled('coding_agent_router_skill')
).toBe(false)
expect(
ProfileHelper.isToolDisabled('codex', 'coding_development')
).toBe(false)
})
it('keeps enable commands scoped to disabled lists', async () => {
writeAvailabilityConfig({
skills: {
allowed: ['existing_skill'],
disabled: ['coding_agent_router_skill']
},
tools: {
allowed: ['coding_development.opencode'],
disabled: ['coding_development.codex']
}
})
const ProfileHelper = await loadProfileHelper()
await ProfileHelper.enableSkill('coding_agent_router_skill')
await ProfileHelper.enableTool('coding_development.codex')
expect(readAvailabilityConfig()).toEqual({
skills: {
allowed: ['existing_skill'],
disabled: []
},
tools: {
allowed: ['coding_development.opencode'],
disabled: []
}
})
})
it('keeps disable commands scoped to disabled lists', async () => {
writeAvailabilityConfig({
skills: {
allowed: ['coding_agent_router_skill'],
disabled: []
},
tools: {
allowed: ['coding_development.codex'],
disabled: []
}
})
const ProfileHelper = await loadProfileHelper()
await ProfileHelper.disableSkill('coding_agent_router_skill')
await ProfileHelper.disableTool('coding_development.codex')
expect(readAvailabilityConfig()).toEqual({
skills: {
allowed: ['coding_agent_router_skill'],
disabled: ['coding_agent_router_skill']
},
tools: {
allowed: ['coding_development.codex'],
disabled: ['coding_development.codex']
}
})
})
it('adds and removes items from allow-only lists', async () => {
writeAvailabilityConfig({
skills: {
allowed: ['existing_skill'],
disabled: ['coding_agent_router_skill']
},
tools: {
allowed: ['coding_development.opencode'],
disabled: ['coding_development.codex']
}
})
const ProfileHelper = await loadProfileHelper()
await ProfileHelper.allowOnlySkill('coding_agent_router_skill')
await ProfileHelper.allowOnlyTool('coding_development.codex')
expect(readAvailabilityConfig()).toEqual({
skills: {
allowed: ['coding_agent_router_skill', 'existing_skill'],
disabled: ['coding_agent_router_skill']
},
tools: {
allowed: ['coding_development.codex', 'coding_development.opencode'],
disabled: ['coding_development.codex']
}
})
await ProfileHelper.removeAllowOnlySkill('existing_skill')
await ProfileHelper.removeAllowOnlyTool('coding_development.opencode')
expect(readAvailabilityConfig()).toEqual({
skills: {
allowed: ['coding_agent_router_skill'],
disabled: ['coding_agent_router_skill']
},
tools: {
allowed: ['coding_development.codex'],
disabled: ['coding_development.codex']
}
})
})
})
@@ -0,0 +1,84 @@
import { beforeAll, beforeEach, describe, expect, it, vi } from 'vitest'
process.env['LEON_NODE_ENV'] = 'testing'
process.env['LEON_LLM'] = 'openai/gpt-5.4'
const coreMocks = vi.hoisted(() => ({
llmProvider: {
prompt: vi.fn()
},
llmManager: {
skillListContent: 'timer_skill: Set timers',
coreLLMDuties: {
'skill-router': {
maxTokens: 128,
thoughtTokensBudget: 0,
temperature: 0
}
}
}
}))
vi.mock('@/helpers/log-helper', () => ({
LogHelper: {
title: vi.fn(),
success: vi.fn(),
info: vi.fn(),
warning: vi.fn(),
error: vi.fn()
}
}))
vi.mock('@/core', () => ({
LLM_PROVIDER: coreMocks.llmProvider,
LLM_MANAGER: coreMocks.llmManager
}))
let SkillRouterLLMDuty: typeof import('@/core/llm-manager/llm-duties/skill-router-llm-duty').SkillRouterLLMDuty
beforeAll(async () => {
;({ SkillRouterLLMDuty } = await import(
'@/core/llm-manager/llm-duties/skill-router-llm-duty'
))
})
beforeEach(() => {
vi.clearAllMocks()
})
describe('SkillRouterLLMDuty', () => {
it('returns the chosen skill from the provider result', async () => {
const history = [
{
who: 'owner',
message: 'Set a timer for me'
}
]
coreMocks.llmProvider.prompt.mockResolvedValue({
output: 'timer_skill',
usedInputTokens: 10,
usedOutputTokens: 2
})
const duty = new SkillRouterLLMDuty({
input: 'Set a timer for me',
history
})
await duty.init()
const result = await duty.execute()
expect(result?.output).toBe('timer_skill')
expect(coreMocks.llmProvider.prompt).toHaveBeenCalledWith(
'User Query: "Set a timer for me"\nChosen Skill Name: ',
expect.objectContaining({
dutyType: 'skill-router',
history,
maxTokens: 128,
temperature: 0,
disableThinking: true
})
)
})
})
@@ -0,0 +1,107 @@
import { beforeAll, beforeEach, describe, expect, it, vi } from 'vitest'
process.env['LEON_NODE_ENV'] = 'testing'
process.env['LEON_LLM'] = 'openai/gpt-5.4'
const coreMocks = vi.hoisted(() => ({
llmProvider: {
prompt: vi.fn()
},
llmManager: {
coreLLMDuties: {
'slot-filling': {
maxTokens: 96,
thoughtTokensBudget: 0,
temperature: 0
}
}
}
}))
vi.mock('@/helpers/log-helper', () => ({
LogHelper: {
title: vi.fn(),
success: vi.fn(),
info: vi.fn(),
warning: vi.fn(),
error: vi.fn()
}
}))
vi.mock('@/core', () => ({
LLM_PROVIDER: coreMocks.llmProvider,
LLM_MANAGER: coreMocks.llmManager
}))
let SlotFillingLLMDuty: typeof import('@/core/llm-manager/llm-duties/slot-filling-llm-duty').SlotFillingLLMDuty
beforeAll(async () => {
;({ SlotFillingLLMDuty } = await import(
'@/core/llm-manager/llm-duties/slot-filling-llm-duty'
))
})
beforeEach(() => {
vi.clearAllMocks()
})
describe('SlotFillingLLMDuty', () => {
it('returns a filled slot when the provider extracts one', async () => {
coreMocks.llmProvider.prompt.mockResolvedValue({
output: JSON.stringify({
filled_slots: {
duration: '3 secs'
}
}),
usedInputTokens: 20,
usedOutputTokens: 8
})
const duty = new SlotFillingLLMDuty({
input: {
slotName: 'duration',
slotDescription: 'Timer duration',
slotType: 'string',
latestUtterance: '3 secs',
recentUtterances: ['Set a timer', '3 secs']
},
startingUtterance: 'Set a timer'
})
await duty.init()
const result = await duty.execute()
expect(result?.output).toEqual({
status: 'success',
filled_slots: {
duration: '3 secs'
}
})
})
it('returns not_found when no slot is extracted', async () => {
coreMocks.llmProvider.prompt.mockResolvedValue({
output: JSON.stringify({}),
usedInputTokens: 12,
usedOutputTokens: 3
})
const duty = new SlotFillingLLMDuty({
input: {
slotName: 'duration',
slotDescription: 'Timer duration',
slotType: 'string',
latestUtterance: 'I do not know',
recentUtterances: ['Set a timer', 'I do not know']
},
startingUtterance: 'Set a timer'
})
await duty.init()
const result = await duty.execute()
expect(result?.output).toEqual({
status: 'not_found'
})
})
})