fix(llm): isolate retry policy histories

This commit is contained in:
Turtle
2026-07-25 15:38:58 +08:00
parent 38ce422f71
commit efc725b7e4
31 changed files with 666 additions and 28 deletions

View File

@@ -112,6 +112,8 @@ export interface Config {
invariants?: InvariantConfig
/** Opt-in persisted same-session goal stack; set false or omit to leave it unmounted. */
goals?: GoalConfig | false
/** Invalid at bundle level; configure `retryPolicy` under each provider. */
llmRetry?: never
}
/** The skill config schema exported for app packages that forward `skills`. */
@@ -152,6 +154,9 @@ export const Config = z.intersect([
toolTasks: z.union([z.const(false), ToolTasksConfigSchema]),
invariants: InvariantService.Config,
goals: z.union([z.const(false), GoalConfigSchema]),
// Schemastery preserves unknown object properties. A top-level llmRetry is
// known-but-impossible because provider retryPolicy owns this configuration.
llmRetry: z.never(),
}) as unknown as z<Pick<Config, 'tools' | 'dshHome' | 'sessionTitle' | 'skills' | 'workspaceContext' | 'toolBash' | 'toolTasks' | 'invariants' | 'goals'>>,
]) as unknown as z<Config>

View File

@@ -594,6 +594,14 @@ describe('dsh-agent-spine-demo bundle', () => {
expect(agentCore.name).toBe('agent-spine-demo')
})
it('rejects bundle-level llmRetry config through plugin validation', async () => {
const ctx = new Context()
await expect(ctx.plugin(agentCore, {
workspaceContext: false,
llmRetry: { maxTransientRetries: 2 },
} as never)).rejects.toThrow(/llmRetry/)
})
it('has the namespace-plugin export shape (no stray default) so the Loader keeps name/Config/apply', () => {
// A default export would make `unwrapExports` collapse this inject-less namespace and silently
// drop `name`/`Config`. Apps import the bundle directly, so this is its Loader-shape guard.