feat(FN-2869): merge fusion/fn-2869
- docs(FN-2869): pluggable notification providers reference + architecture - chore: changeset for pluggable notification providers - ui(merge): clarify mergeConflictStrategy option labels — both "smart" and "prefer-main" run the full smart cascade; differ only in the final fallback (-X theirs vs -X ours) - TaskDetailModal: keep Node Routing summary block removed Conflicts in executor.ts, core/index.ts, and RoutingTab.* resolved by keeping HEAD — fn-2869 branched from a stale main and was dragging in older versions of files that have since been updated by sibling tasks (including the FN-2887 rebase fix on executor.ts itself).
This commit is contained in:
@@ -2998,13 +2998,21 @@ export function SettingsModal({
|
||||
setForm((f) => ({ ...f, mergeConflictStrategy: e.target.value as "smart" | "ai-only" | "prefer-main" | "abort" }))
|
||||
}
|
||||
>
|
||||
<option value="smart">Smart — AI, then auto-resolve, then prefer task branch (default)</option>
|
||||
<option value="ai-only">AI only — never silently pick a side</option>
|
||||
<option value="prefer-main">Prefer main — fall back to keeping main's version</option>
|
||||
<option value="abort">Abort — fail merge if AI can't resolve (manual review)</option>
|
||||
<option value="smart">Smart, prefer task on fallback — AI → auto-resolve → -X theirs (default, original behavior)</option>
|
||||
<option value="prefer-main">Smart, prefer main on fallback — AI → auto-resolve → -X ours (protects just-merged sibling work)</option>
|
||||
<option value="ai-only">AI only — AI → auto-resolve → AI retry; never silently pick a side</option>
|
||||
<option value="abort">Abort — one AI attempt; require manual resolution if it fails</option>
|
||||
</select>
|
||||
<small>
|
||||
What to do on the final merge attempt when AI and auto-resolve haven't fully resolved the conflict. <strong>Smart</strong> matches the historical behavior — the task branch wins on hard conflicts, which is fast but can resurrect code an earlier sibling task deleted. <strong>Prefer main</strong> sides with main instead, protecting just-merged sibling work. <strong>AI only</strong> retries the AI agent rather than silently choosing a side. <strong>Abort</strong> requires manual resolution for any conflict the AI can't handle.
|
||||
Every option starts with the same first two attempts: an AI agent resolves the conflict, then auto-resolve handles lock/generated/trivial files. They differ only in the <em>final fallback</em>:
|
||||
{" "}
|
||||
<strong>Smart, prefer task</strong> uses <code>-X theirs</code> so the task branch wins — fast, but can resurrect code an earlier sibling task deleted (the FN-2887 class of regression).
|
||||
{" "}
|
||||
<strong>Smart, prefer main</strong> uses <code>-X ours</code> so main wins instead — same speed, protects just-merged sibling work.
|
||||
{" "}
|
||||
<strong>AI only</strong> retries the AI agent rather than auto-picking a side.
|
||||
{" "}
|
||||
<strong>Abort</strong> stops after the first AI attempt and waits for a human.
|
||||
</small>
|
||||
</div>
|
||||
<div className="form-group">
|
||||
|
||||
@@ -1702,6 +1702,31 @@ export function TaskDetailModal({
|
||||
</div>
|
||||
)}
|
||||
<MergeDetails task={task} />
|
||||
<div className="detail-section">
|
||||
<h4>Node Routing</h4>
|
||||
<dl className="detail-source-grid">
|
||||
<div>
|
||||
<dt>Task Override</dt>
|
||||
<dd>{task.nodeId ?? <span className="detail-source-empty">(none)</span>}</dd>
|
||||
</div>
|
||||
<div>
|
||||
<dt>Effective Node</dt>
|
||||
<dd>{(task as Task & { effectiveNodeId?: string }).effectiveNodeId ?? "local execution"}</dd>
|
||||
</div>
|
||||
<div>
|
||||
<dt>Routing Source</dt>
|
||||
<dd>{(task as Task & { effectiveNodeSource?: string }).effectiveNodeSource ?? "local"}</dd>
|
||||
</div>
|
||||
<div>
|
||||
<dt>Unavailable Node Policy</dt>
|
||||
<dd>{(settings as Settings & { unavailableNodePolicy?: string } | undefined)?.unavailableNodePolicy ?? "block"}</dd>
|
||||
</div>
|
||||
<div>
|
||||
<dt>Blocking Reason</dt>
|
||||
<dd>{((task as Task & { blockedReason?: string; statusReason?: string }).blockedReason || (task as Task & { statusReason?: string }).statusReason) ?? <span className="detail-source-empty">(not blocked)</span>}</dd>
|
||||
</div>
|
||||
</dl>
|
||||
</div>
|
||||
{task.sourceIssue && (
|
||||
<div className="detail-section detail-source-section">
|
||||
<h4>Source Issue</h4>
|
||||
|
||||
Reference in New Issue
Block a user