mirror of
https://github.com/NanmiCoder/cc-haha
synced 2026-07-19 13:33:35 +08:00
Coding sessions need a native way to keep working until a stated completion condition is actually met. This adds a CLI /goal command, goal state and evaluator logic, and a headless-safe command filter so interactive and print-mode runs can both start a goal and continue when the evaluator says more work remains. Constraint: The evaluator must work with saved third-party provider runtimes that may reject structured output schemas. Constraint: Completion evidence must come from visible transcript text, not hidden goal prompts or assistant thinking blocks. Rejected: Implement /goal as a bundled skill only | it would not own the query loop or support native continuation semantics. Rejected: Expose all local-jsx commands to headless mode | interactive UI commands would become available in print mode. Confidence: high Scope-risk: moderate Directive: Keep /goal evaluator evidence scoped to visible transcript text before adding richer completion signals. Tested: bun test src/commands/headless.test.ts src/goals/goalState.test.ts src/goals/goalEvaluator.test.ts Tested: bun run check:server Tested: bun run verify Tested: Real provider /goal smoke with MiniMax-M2.7-highspeed, including a two-turn continuation that logged Goal continuation #1 and completed with GOAL_SMOKE_LOOP_DONE. Not-tested: Durable cross-process goal persistence for resumed sessions.
73 lines
2.1 KiB
TypeScript
73 lines
2.1 KiB
TypeScript
import * as React from 'react'
|
|
import { getSessionId } from '../../bootstrap/state.js'
|
|
import type { LocalJSXCommandContext } from '../../commands.js'
|
|
import type { LocalJSXCommandOnDone } from '../../types/command.js'
|
|
import {
|
|
buildGoalStartPrompt,
|
|
clearThreadGoal,
|
|
formatGoalStatus,
|
|
getThreadGoal,
|
|
parseGoalCommand,
|
|
setThreadGoal,
|
|
updateThreadGoalStatus,
|
|
} from '../../goals/goalState.js'
|
|
|
|
export async function call(
|
|
onDone: LocalJSXCommandOnDone,
|
|
_context: LocalJSXCommandContext,
|
|
args: string,
|
|
): Promise<React.ReactNode> {
|
|
const threadId = getSessionId()
|
|
try {
|
|
const parsed = parseGoalCommand(args)
|
|
if (parsed.type === 'status') {
|
|
onDone(formatGoalStatus(getThreadGoal(threadId)), { display: 'system' })
|
|
return null
|
|
}
|
|
if (parsed.type === 'clear') {
|
|
const cleared = clearThreadGoal(threadId)
|
|
onDone(cleared ? 'Goal cleared.' : 'No active goal.', { display: 'system' })
|
|
return null
|
|
}
|
|
if (parsed.type === 'pause') {
|
|
const goal = updateThreadGoalStatus(threadId, 'paused')
|
|
onDone(goal ? formatGoalStatus(goal) : 'No active goal.', {
|
|
display: 'system',
|
|
})
|
|
return null
|
|
}
|
|
if (parsed.type === 'resume') {
|
|
const goal = updateThreadGoalStatus(threadId, 'active')
|
|
onDone(goal ? formatGoalStatus(goal) : 'No goal to resume.', {
|
|
display: 'system',
|
|
shouldQuery: Boolean(goal),
|
|
metaMessages: goal ? [buildGoalStartPrompt(goal)] : [],
|
|
})
|
|
return null
|
|
}
|
|
if (parsed.type === 'complete') {
|
|
const goal = updateThreadGoalStatus(threadId, 'complete')
|
|
onDone(goal ? 'Goal marked complete.' : 'No active goal.', {
|
|
display: 'system',
|
|
})
|
|
return null
|
|
}
|
|
|
|
const goal = setThreadGoal(threadId, {
|
|
objective: parsed.objective,
|
|
tokenBudget: parsed.tokenBudget,
|
|
})
|
|
onDone(formatGoalStatus(goal), {
|
|
display: 'system',
|
|
shouldQuery: true,
|
|
metaMessages: [buildGoalStartPrompt(goal)],
|
|
})
|
|
return null
|
|
} catch (error) {
|
|
onDone(error instanceof Error ? error.message : String(error), {
|
|
display: 'system',
|
|
})
|
|
return null
|
|
}
|
|
}
|