fix(llm): isolate retry policy histories
This commit is contained in:
@@ -52,6 +52,8 @@ export interface Config {
|
||||
toolTasks?: NonNullable<agentCore.Config['toolTasks']>
|
||||
/** Controls automatic AGENTS.md/CLAUDE.md loading; configure a byte budget or set `false`. */
|
||||
workspaceContext: agentCore.Config['workspaceContext']
|
||||
/** Invalid at app level; configure `retryPolicy` under each provider. */
|
||||
llmRetry?: never
|
||||
}
|
||||
|
||||
// Each front door keeps a complete Loader schema so its deployment contract is
|
||||
@@ -73,6 +75,8 @@ export const Config: z<Config> = z.object({
|
||||
toolBash: agentCore.ToolBashConfigSchema,
|
||||
toolTasks: z.union([z.const(false), agentCore.ToolTasksConfigSchema]),
|
||||
workspaceContext: z.union([z.const(false), workspaceContext.Config]).required(),
|
||||
// Provider retryPolicy makes a top-level llmRetry invalid.
|
||||
llmRetry: z.never(),
|
||||
})
|
||||
/* jscpd:ignore-end */
|
||||
|
||||
|
||||
@@ -192,6 +192,39 @@ describe.skipIf(!existsSync(cliBin))('dsh-cli-demo BUILT bin', () => {
|
||||
}
|
||||
}, 30_000)
|
||||
|
||||
it('rejects legacy app-level llmRetry through the published Loader path', async () => {
|
||||
consumer = await makeConsumer()
|
||||
const configPath = join(consumer, 'cordis.yml')
|
||||
const config = await readFile(configPath, 'utf8')
|
||||
await writeFile(configPath, config.replace(
|
||||
' workspaceContext: false',
|
||||
' workspaceContext: false\n llmRetry:\n maxTransientRetries: 2',
|
||||
))
|
||||
|
||||
const result = await runBuiltBin(consumer, ['--config', './cordis.yml', 'task'])
|
||||
expect(result.code).not.toBe(0)
|
||||
expect(result.stdout).toBe('')
|
||||
expect(result.stderr).toContain('llmRetry')
|
||||
}, 30_000)
|
||||
|
||||
it('rejects legacy bundle-level llmRetry when the published spine is loaded directly', async () => {
|
||||
consumer = await makeConsumer()
|
||||
await writeFile(join(consumer, 'cordis.yml'), [
|
||||
'- id: spine',
|
||||
" name: '@deepseek-ai/dsh-agent-spine-demo'",
|
||||
' config:',
|
||||
' workspaceContext: false',
|
||||
' llmRetry:',
|
||||
' maxTransientRetries: 2',
|
||||
'',
|
||||
].join('\n'))
|
||||
|
||||
const result = await runBuiltBin(consumer, ['--config', './cordis.yml', 'task'])
|
||||
expect(result.code).not.toBe(0)
|
||||
expect(result.stdout).toBe('')
|
||||
expect(result.stderr).toContain('llmRetry')
|
||||
}, 30_000)
|
||||
|
||||
describe.skipIf(process.platform === 'win32')('POSIX signal delivery', () => {
|
||||
it.each([
|
||||
['SIGINT', 130],
|
||||
|
||||
@@ -173,6 +173,16 @@ afterEach(async () => {
|
||||
})
|
||||
|
||||
describe('parseCliArgs', () => {
|
||||
it('rejects app-level llmRetry config through plugin validation', async () => {
|
||||
const ctx = new Context()
|
||||
await expect(ctx.plugin(cliDemo, {
|
||||
provider: 'mock',
|
||||
model: 'mock',
|
||||
workspaceContext: false,
|
||||
llmRetry: { maxTransientRetries: 2 },
|
||||
} as never)).rejects.toThrow(/llmRetry/)
|
||||
})
|
||||
|
||||
it('parses defaults, explicit options, spaces, and an option-like task after --', () => {
|
||||
expect(parseCliArgs(['task with spaces'])).toEqual({
|
||||
kind: 'run', configPath: './cordis.yml', outputFormat: 'text', task: 'task with spaces',
|
||||
|
||||
Reference in New Issue
Block a user