mirror of
https://github.com/deepseek-ai/deepseek-harness
synced 2026-08-15 21:04:50 +00:00
refactor(goal): loosen executor presence checks
This commit is contained in:
@@ -132,14 +132,14 @@ function resolveConfig(config: Config): ResolvedConfig {
|
||||
return { blockedAfterConsecutiveRounds: blockedAfter }
|
||||
}
|
||||
|
||||
/** Remove only empty provider fillers from fields unused by the selected action. */
|
||||
function normalizeUpdateArgs(args: Record<string, unknown>): Record<string, unknown> {
|
||||
const action = args['action']
|
||||
const empty = (value: unknown): boolean => value === undefined || value === null || value === '' || value === 0
|
||||
const unused = (key: string): boolean => key === 'blocked_reason'
|
||||
? action !== 'blocked'
|
||||
: (key === 'objective' || key === 'max_goal_rounds') && action !== 'edit'
|
||||
return Object.fromEntries(Object.entries(args).filter(([key, value]) => !unused(key) || !empty(value)))
|
||||
/** Whether an optional string carries a meaningful action-specific value. */
|
||||
function hasText(value: string | undefined): boolean {
|
||||
return value !== undefined && value !== ''
|
||||
}
|
||||
|
||||
/** Whether an optional round cap carries a meaningful action-specific value. */
|
||||
function hasRoundCap(value: number | undefined): boolean {
|
||||
return value !== undefined && value !== 0
|
||||
}
|
||||
|
||||
/** Build the exact compare-and-set ref from model arguments. */
|
||||
@@ -254,7 +254,7 @@ export function apply(ctx: Context, config: Config): void {
|
||||
presentCall: args => present('Create goal', 'other', args.objective),
|
||||
}))
|
||||
|
||||
const updateGoal = defineTool({
|
||||
ctx.tools.register(defineTool({
|
||||
name: 'update_goal',
|
||||
description: 'Update the exact current goal revision. edit, pause, and resume require a direct '
|
||||
+ 'top-level human request. During an automatic continuation of the current goal, complete '
|
||||
@@ -286,7 +286,9 @@ export function apply(ctx: Context, config: Config): void {
|
||||
}
|
||||
if (args.action === 'edit') {
|
||||
requireDirectHuman(ctx, execution)
|
||||
if (args.blocked_reason !== undefined) {
|
||||
// Some strict-schema providers emit empty placeholders for every declared
|
||||
// optional field. Reject only values that could change another action.
|
||||
if (hasText(args.blocked_reason)) {
|
||||
throw new HarnessError('blocked_reason is valid only with action blocked', 'GOAL_TOOL_INVALID_UPDATE')
|
||||
}
|
||||
const goal = ctx.goals.edit(execution.agent, ref, replacements)
|
||||
@@ -295,7 +297,7 @@ export function apply(ctx: Context, config: Config): void {
|
||||
}
|
||||
if (args.action === 'pause' || args.action === 'resume') {
|
||||
requireDirectHuman(ctx, execution)
|
||||
if (args.objective !== undefined || args.max_goal_rounds !== undefined || args.blocked_reason !== undefined) {
|
||||
if (hasText(args.objective) || hasRoundCap(args.max_goal_rounds) || hasText(args.blocked_reason)) {
|
||||
throw new HarnessError(
|
||||
'objective and max_goal_rounds are valid only with action edit; blocked_reason is valid only with action blocked',
|
||||
'GOAL_TOOL_INVALID_UPDATE',
|
||||
@@ -308,13 +310,13 @@ export function apply(ctx: Context, config: Config): void {
|
||||
return Promise.resolve(goalValue(goal))
|
||||
}
|
||||
const authority = completionAuthority(ctx, execution)
|
||||
if (args.objective !== undefined || args.max_goal_rounds !== undefined) {
|
||||
if (hasText(args.objective) || hasRoundCap(args.max_goal_rounds)) {
|
||||
throw new HarnessError(
|
||||
'objective and max_goal_rounds are valid only with action edit',
|
||||
'GOAL_TOOL_INVALID_UPDATE',
|
||||
)
|
||||
}
|
||||
if (args.action === 'complete' && args.blocked_reason !== undefined) {
|
||||
if (args.action === 'complete' && hasText(args.blocked_reason)) {
|
||||
throw new HarnessError('blocked_reason is valid only with action blocked', 'GOAL_TOOL_INVALID_UPDATE')
|
||||
}
|
||||
if (args.action === 'blocked'
|
||||
@@ -343,10 +345,5 @@ export function apply(ctx: Context, config: Config): void {
|
||||
'other',
|
||||
args.blocked_reason ?? args.objective ?? args.goal_id,
|
||||
),
|
||||
})
|
||||
// Object-literal execute methods do not use `this`; retaining the reference is safe.
|
||||
// eslint-disable-next-line @typescript-eslint/unbound-method
|
||||
const executeUpdate = updateGoal.execute
|
||||
updateGoal.execute = (args, exec) => executeUpdate(normalizeUpdateArgs(args as Record<string, unknown>), exec)
|
||||
ctx.tools.register(updateGoal)
|
||||
}))
|
||||
}
|
||||
|
||||
@@ -444,7 +444,7 @@ describe('goal tool state transitions', () => {
|
||||
action: 'pause',
|
||||
objective: '',
|
||||
max_goal_rounds: 0,
|
||||
blocked_reason: null,
|
||||
blocked_reason: '',
|
||||
}, root.agent)
|
||||
expect(resultGoal(paused)).toMatchObject({ phase: 'paused', objective: 'edited' })
|
||||
goal = ctx.goals.get(root.agent)!
|
||||
@@ -453,8 +453,8 @@ describe('goal tool state transitions', () => {
|
||||
goal_id: goal.id,
|
||||
revision: goal.revision,
|
||||
action: 'resume',
|
||||
objective: null,
|
||||
max_goal_rounds: '',
|
||||
objective: '',
|
||||
max_goal_rounds: 0,
|
||||
blocked_reason: '',
|
||||
}, root.agent)
|
||||
expect(resultGoal(resumed)).toMatchObject({ phase: 'active', objective: 'edited' })
|
||||
@@ -465,7 +465,7 @@ describe('goal tool state transitions', () => {
|
||||
revision: goal.revision,
|
||||
action: 'blocked',
|
||||
objective: '',
|
||||
max_goal_rounds: null,
|
||||
max_goal_rounds: 0,
|
||||
blocked_reason: 'actual blocker',
|
||||
}, root.agent)
|
||||
expect(resultGoal(blocked)).toMatchObject({ phase: 'blocked' })
|
||||
|
||||
Reference in New Issue
Block a user