fix(ai): bubble proposal-reject feedback into Mission iteration

The Planner reads `mission.iterations[].userFeedback` — not
`pendingProposals.userFeedback` — so feedback stored only on the
proposal row never reached the next plan. rejectProposal now also
appends to the matching iteration's `userFeedback` (merging with any
existing reasons via "\n· " bullets) when the proposal carries
missionId + iterationId.

Lazy imports the mission store to avoid a proposal↔mission cycle.
Best-effort: proposal rejection still succeeds even if the bubble fails.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
This commit is contained in:
Till JS 2026-04-15 00:57:25 +02:00
parent 98547b9e4e
commit ce646550cd

View file

@ -101,6 +101,29 @@ export async function rejectProposal(id: string, userFeedback?: string): Promise
userFeedback,
};
await table().update(id, updated);
// Bubble the feedback up to the Mission iteration so the next Planner
// pass (which reads `mission.iterations[].userFeedback` — NOT
// `pendingProposals.userFeedback`) can course-correct. Lazy-import
// to avoid a cycle: mission store ↔ proposal store.
if (userFeedback && proposal.missionId && proposal.iterationId) {
try {
const { addIterationFeedback, getMission } = await import('../missions/store');
const mission = await getMission(proposal.missionId);
// Merge with any existing feedback on the iteration — different
// steps within one iteration can produce different reasons.
const existingIt = mission?.iterations.find((it) => it.id === proposal.iterationId);
const merged = existingIt?.userFeedback
? `${existingIt.userFeedback}\${userFeedback}`
: userFeedback;
await addIterationFeedback(proposal.missionId, proposal.iterationId, merged);
} catch (err) {
// Feedback bubble is best-effort — the proposal was still
// rejected successfully if this fails.
console.error('[rejectProposal] failed to bubble feedback to iteration:', err);
}
}
return { ...proposal, ...updated };
}