feat: distill any oversized goal objective, not just plans
The distillation moved from PlanView into setSessionGoal — the shared choke point for every goal source (composer, fork dialog, plans) — and into the scheduled-tasks goal creation server-side. Any objective over 5000 chars is distilled into completion criteria by the small model; on a transient failure a head+tail excerpt keeps the intent (top) and the acceptance criteria (bottom) with a trim marker, plus a toast. The working agent always received the full prompt in chat — only the auditor is bound by the limit.
This commit is contained in:
@@ -30,9 +30,6 @@ import { useProjectsStore } from '@/stores/useProjectsStore';
|
||||
import { useSelectionStore } from '@/sync/selection-store';
|
||||
import { useConfigStore } from '@/stores/useConfigStore';
|
||||
import { useSessionGoalArmStore } from '@/stores/useSessionGoalArmStore';
|
||||
import { SESSION_GOAL_OBJECTIVE_CHAR_LIMIT } from '@/lib/sessionGoalMetadata';
|
||||
import { distillPlanForGoal } from '@/lib/smallModel';
|
||||
import { toast } from '@/components/ui';
|
||||
import { useUIStore } from '@/stores/useUIStore';
|
||||
import { useGitStore } from '@/stores/useGitStore';
|
||||
import { useRuntimeAPIs } from '@/hooks/useRuntimeAPIs';
|
||||
@@ -608,27 +605,17 @@ export const PlanView: React.FC<PlanViewProps> = ({ targetPath = null }) => {
|
||||
// its file); on distillation failure a head+tail excerpt keeps the
|
||||
// intent (top) and acceptance criteria (bottom), sacrificing the
|
||||
// implementation middle the agent reads from the file anyway.
|
||||
let goalObjective: string | null = null;
|
||||
if (execution.runAsGoal === true) {
|
||||
const header = [
|
||||
`Implement the plan "${sendPromptTitle}" end-to-end${resolvedPath ? ` (plan file: ${resolvedPath})` : ''}.`,
|
||||
'Re-read that file for full details — it is the source of truth.',
|
||||
].join(' ');
|
||||
const budget = SESSION_GOAL_OBJECTIVE_CHAR_LIMIT - header.length - 2;
|
||||
if (content.length <= budget) {
|
||||
goalObjective = `${header}\n\n${content}`;
|
||||
} else {
|
||||
const distilled = await distillPlanForGoal(content);
|
||||
if (distilled) {
|
||||
goalObjective = `${header}\n\n${distilled.slice(0, budget)}`;
|
||||
} else {
|
||||
const marker = '\n\n[… plan trimmed for the auditor — the plan file has the full version …]\n\n';
|
||||
const half = Math.max(0, Math.floor((budget - marker.length) / 2));
|
||||
goalObjective = `${header}\n\n${content.slice(0, half)}${marker}${content.slice(-half)}`;
|
||||
toast.error(t('plans.goal.toast.distillFallback'));
|
||||
}
|
||||
}
|
||||
}
|
||||
// Oversized objectives (huge plans) are distilled into audit
|
||||
// criteria inside setSessionGoal — the shared path for every goal
|
||||
// source. Here we only compose header + full content.
|
||||
const goalObjective = execution.runAsGoal === true
|
||||
? [
|
||||
`Implement the plan "${sendPromptTitle}" end-to-end${resolvedPath ? ` (plan file: ${resolvedPath})` : ''}.`,
|
||||
'Re-read that file for full details — it is the source of truth.',
|
||||
'',
|
||||
content,
|
||||
].join('\n')
|
||||
: null;
|
||||
useSessionGoalArmStore.getState().setArmed(execution.runAsGoal === true, goalObjective);
|
||||
await sendMessage(
|
||||
visiblePrompt,
|
||||
@@ -646,7 +633,7 @@ export const PlanView: React.FC<PlanViewProps> = ({ targetPath = null }) => {
|
||||
setIsPlanSendSubmitting(false);
|
||||
}
|
||||
},
|
||||
[canCreateWorktree, content, createSession, currentProjectRef, initializeNewOpenChamberSession, pendingPlanSend, resolvedPath, routeToChat, sendMessage, sendPromptTitle, setCurrentSession, t]
|
||||
[canCreateWorktree, content, createSession, currentProjectRef, initializeNewOpenChamberSession, pendingPlanSend, resolvedPath, routeToChat, sendMessage, sendPromptTitle, setCurrentSession]
|
||||
);
|
||||
|
||||
const blockWidgets = React.useMemo(() => {
|
||||
|
||||
Reference in New Issue
Block a user