mirror of
https://github.com/andy-stack/vaultkeeper-ai.git
synced 2026-07-22 06:42:03 +00:00
Replace the single Replan tool with three targeted alternatives: ReviseStep, RevisePlan, and SkipStep. This gives the orchestration agent more precise control over plan recovery — revising only the current step, replacing all remaining steps, or skipping a step entirely — rather than triggering a full replan each time. Also give the orchestration agent read access to vault files so it can resolve execution failures without routing back to the planning agent, and update the execution agent prompt to stop it from attempting self-recovery (gap resolution is now the orchestrator's responsibility).
269 lines
No EOL
8.2 KiB
Svelte
269 lines
No EOL
8.2 KiB
Svelte
<script lang="ts">
|
|
import { setElementIcon } from "Helpers/ElementHelper";
|
|
import { Copy } from "Enums/Copy";
|
|
import Spinner from "./Spinner.svelte";
|
|
import { tick, onDestroy } from "svelte";
|
|
import { fade, slide } from "svelte/transition";
|
|
import type { IExecutionPlanState } from "Stores/ExecutionPlanStore";
|
|
import type { Writable } from "svelte/store";
|
|
|
|
export let executionPlanState: Writable<IExecutionPlanState>;
|
|
export let editModeActive = false;
|
|
export let busyPlanning = false;
|
|
|
|
let expanded = false;
|
|
let expandedHeight = 0;
|
|
let collapsedHeight = 0;
|
|
|
|
let wrapperDiv: HTMLDivElement;
|
|
let contentDiv: HTMLDivElement;
|
|
let stepElements: (HTMLDivElement | null)[] = [];
|
|
let isTransitioning = false;
|
|
let resizeObserver: ResizeObserver | null = null;
|
|
|
|
$: steps = $executionPlanState.plan?.executionSteps;
|
|
$: activeStepIndex = $executionPlanState.currentStepIndex;
|
|
|
|
$: if (steps) {
|
|
tick().then(() => {
|
|
requestAnimationFrame(() => {
|
|
updateHeight();
|
|
});
|
|
});
|
|
}
|
|
|
|
$: if (contentDiv) {
|
|
setupResizeObserver();
|
|
}
|
|
|
|
$: if (activeStepIndex >= 0 && !isTransitioning) {
|
|
scrollToActiveStep();
|
|
}
|
|
|
|
$: if (wrapperDiv && expanded !== undefined) {
|
|
handleExpandTransition();
|
|
}
|
|
|
|
function handleExpandTransition() {
|
|
isTransitioning = true;
|
|
const onTransitionEnd = () => {
|
|
isTransitioning = false;
|
|
wrapperDiv.removeEventListener('transitionend', onTransitionEnd);
|
|
scrollToActiveStep();
|
|
};
|
|
wrapperDiv.addEventListener('transitionend', onTransitionEnd);
|
|
}
|
|
|
|
function setupResizeObserver() {
|
|
if (resizeObserver) {
|
|
resizeObserver.disconnect();
|
|
}
|
|
|
|
resizeObserver = new ResizeObserver(() => {
|
|
updateHeight();
|
|
});
|
|
|
|
resizeObserver.observe(contentDiv);
|
|
}
|
|
|
|
async function updateHeight() {
|
|
const firstStep = stepElements[0];
|
|
if (!contentDiv || !firstStep || !steps) {
|
|
return;
|
|
}
|
|
|
|
expandedHeight = contentDiv.scrollHeight;
|
|
|
|
// Calculate heights fresh each time to ensure accurate measurements
|
|
const stepHeight = firstStep.offsetHeight;
|
|
const separatorHeight = firstStep.nextElementSibling?.clientHeight ?? 0;
|
|
|
|
const stepsToShow = Math.min(3, steps.length);
|
|
collapsedHeight = (stepsToShow * stepHeight) + (Math.max(0, stepsToShow - 1) * separatorHeight);
|
|
}
|
|
|
|
onDestroy(() => {
|
|
if (resizeObserver) {
|
|
resizeObserver.disconnect();
|
|
}
|
|
});
|
|
|
|
async function scrollToActiveStep() {
|
|
if (!wrapperDiv || !$executionPlanState.plan || activeStepIndex === -1) {
|
|
return;
|
|
}
|
|
|
|
await tick();
|
|
const stepElement = stepElements[activeStepIndex];
|
|
if (!stepElement) return;
|
|
|
|
// Calculate position relative to the scroll container (wrapperDiv)
|
|
const stepTop = stepElement.offsetTop - contentDiv.offsetTop;
|
|
const stepHeight = stepElement.offsetHeight;
|
|
const wrapperHeight = wrapperDiv.clientHeight;
|
|
const scrollTo = stepTop - (wrapperHeight / 2) + (stepHeight / 2);
|
|
|
|
wrapperDiv.scrollTo({ top: scrollTo, behavior: 'smooth' });
|
|
}
|
|
|
|
function toggleExpand() {
|
|
expanded = !expanded;
|
|
requestAnimationFrame(() => {
|
|
updateHeight();
|
|
});
|
|
}
|
|
</script>
|
|
|
|
{#if busyPlanning}
|
|
<div id="chat-planning-in-progress" transition:slide>
|
|
<Spinner {editModeActive} alternateBackground={true}/>
|
|
<span id="chat-planning-in-progress-text">{Copy.PlanningInProgress}</span>
|
|
</div>
|
|
{/if}
|
|
{#if steps && steps?.length > 0}
|
|
<div id="chat-plan-area"
|
|
out:slide
|
|
on:click={toggleExpand}
|
|
on:keydown={(e) => e.key === 'Enter' && toggleExpand()}
|
|
aria-label={expanded ? "Collapse planned steps" : "Expand planned steps"}
|
|
role="button"
|
|
tabindex=0>
|
|
<div id="chat-plan-steps-wrapper" style:height="{expanded ? expandedHeight : collapsedHeight}px" bind:this={wrapperDiv}>
|
|
<div id="chat-plan-steps" bind:this={contentDiv}>
|
|
{#each steps as step, index }
|
|
<div class="chat-plan-step" bind:this={stepElements[index]}>
|
|
{#if index < activeStepIndex}
|
|
<div class="chat-plan-step-icon" use:setElementIcon={"circle-check"} style:color="var(--color-green)"></div>
|
|
{/if}
|
|
{#if index === activeStepIndex}
|
|
<div class="chat-plan-step-icon">
|
|
<Spinner {editModeActive} alternateBackground={true}/>
|
|
</div>
|
|
{/if}
|
|
{#if index > activeStepIndex}
|
|
<div class="chat-plan-step-icon" use:setElementIcon={"circle"} style:opacity={0.5}></div>
|
|
{/if}
|
|
<span class="chat-plan-step-span">
|
|
{`${index + 1}. ${step.description}`}
|
|
</span>
|
|
</div>
|
|
{#if index < steps.length - 1}
|
|
<div class="chat-plan-step-icon"
|
|
use:setElementIcon={"ellipsis-vertical"}
|
|
style:opacity={index < activeStepIndex ? 1 : 0.25}>
|
|
</div>
|
|
{/if}
|
|
{/each}
|
|
</div>
|
|
</div>
|
|
{#if steps.length > 2}
|
|
<div class="chat-plan-fade top-fade" transition:fade></div>
|
|
<div class="chat-plan-fade bottom-fade" transition:fade></div>
|
|
{/if}
|
|
<div id="chat-plan-chevron"
|
|
class="transparent-button"
|
|
use:setElementIcon={expanded ? "chevrons-down-up" : "chevrons-up-down"}>
|
|
</div>
|
|
</div>
|
|
{/if}
|
|
|
|
<style>
|
|
#chat-planning-in-progress {
|
|
display: flex;
|
|
gap: var(--size-4-2);
|
|
background-color: var(--background-secondary-alt);
|
|
border-radius: var(--radius-m);
|
|
padding: var(--size-4-2);
|
|
font-style: italic;
|
|
font-size: var(--font-smaller);
|
|
justify-content: center;
|
|
}
|
|
|
|
#chat-planning-in-progress-text {
|
|
max-width: 90%;
|
|
white-space: nowrap;
|
|
overflow: hidden;
|
|
text-overflow: ellipsis;
|
|
}
|
|
|
|
#chat-plan-area {
|
|
grid-row: 1;
|
|
grid-column: 1;
|
|
display: grid;
|
|
grid-template-rows: auto;
|
|
grid-template-columns: 1fr auto;
|
|
background-color: var(--background-secondary-alt);
|
|
border-radius: var(--radius-m);
|
|
padding: var(--size-4-1) var(--size-4-2);
|
|
cursor: pointer;
|
|
position: relative;
|
|
}
|
|
|
|
#chat-plan-steps-wrapper {
|
|
grid-row: auto;
|
|
grid-column: 1;
|
|
transition: height 0.2s ease-out;
|
|
overflow-x: hidden;
|
|
overflow-y: auto;
|
|
scroll-behavior: smooth;
|
|
max-height: 20vh;
|
|
}
|
|
|
|
#chat-plan-steps-wrapper::-webkit-scrollbar {
|
|
display: none;
|
|
}
|
|
|
|
#chat-plan-steps {
|
|
position: relative;
|
|
display: flex;
|
|
flex-direction: column;
|
|
font-size: var(--font-smallest);
|
|
width: 100%;
|
|
}
|
|
|
|
.chat-plan-step {
|
|
display: flex;
|
|
align-items: center;
|
|
}
|
|
|
|
.chat-plan-step-span {
|
|
max-width: 90%;
|
|
white-space: nowrap;
|
|
overflow: hidden;
|
|
text-overflow: ellipsis;
|
|
}
|
|
|
|
.chat-plan-step-icon {
|
|
display: flex;
|
|
align-items: center;
|
|
padding-right: var(--size-4-2);
|
|
}
|
|
|
|
#chat-plan-chevron {
|
|
grid-row: auto;
|
|
grid-column: 2;
|
|
display: flex;
|
|
align-items: center;
|
|
justify-content: center;
|
|
}
|
|
|
|
.chat-plan-fade {
|
|
position: absolute;
|
|
left: 0;
|
|
right: 0;
|
|
height: var(--size-4-1);
|
|
border-radius: var(--radius-m);
|
|
pointer-events: none;
|
|
z-index: 1;
|
|
}
|
|
|
|
.top-fade {
|
|
top: var(--size-4-1);
|
|
background-image: linear-gradient(to bottom, var(--background-secondary-alt), transparent);
|
|
}
|
|
|
|
.bottom-fade {
|
|
bottom: var(--size-4-1);
|
|
background-image: linear-gradient(to top, var(--background-secondary-alt), transparent);
|
|
}
|
|
</style> |