mirror of
https://github.com/windmill-labs/windmill.git
synced 2026-10-28 16:02:47 +00:00
* feat(ai-agent): built-in approval tool that parks the agent An AI agent step can now be given a built-in "Request approval" tool. The model calls `request_approval` with a message; the agent stores its conversation in the new `ai_agent_checkpoint` table, releases its worker by parking its own queue row like a workflow-as-code parent, and resumes with the answer (approved, approver, comment) or a timeout once someone answers from the run page. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> * fix(ai-agent): tie approval answers to their request and hold the turn - A turn that asks for approval runs none of its other calls; they are refused so the model repeats them after the answer. - The run page sends the request's call id. An answer naming another request is ignored and the agent parks again for the time left; `suspend` is zeroed when the answer is read, so answers sent while the agent runs are refused. - The checkpoint is read whatever the current roster holds, the answer's outcome is written by position, and a cancel that beats the park completes the job as cancelled. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> * fix(ai-agent): say why calls beside an invalid approval request were refused Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> * fix(ai-agent): one approval form per request, none on a finished run Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> * feat(ai-agent): answer approval requests from a flow's chat Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> * fix(ai-agent): show chat approvals for agents that do not stream Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> * feat(ai-agent): approval as a per-tool setting, built-in tool asks a question Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> * fix(ai-agent): say when a nested agent leaves out tools that require approval Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> * feat(ai-agent): show chat approvals and questions on the tool's own card Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> * feat(ai-agent): question choices, and approval buttons shared with approval steps Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> * feat(ai-agent): show a waiting agent as suspended, and refused calls as not run Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> * fix(ai-agent): each added chat card shows its own call, answered marker keyed by job Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> * test(ai-agent): approve and reject a held call through resume_suspended Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> * feat(ai-agent): require approval in a tool settings tab, plain descriptions on built-in tool panes Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> * fix(ai-agent): sentence case for the web search tool label Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> * feat(ai-agent): built-in tool drawers explain themselves in the header subtitle Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> * fix(ai-agent): clearer question tool subtitle Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> * fix(ai-agent): badge the tool settings tab like run settings Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> * chore(ai-agent): regenerate system prompts and MCP session files Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> --------- Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
95 lines
3.1 KiB
Svelte
95 lines
3.1 KiB
Svelte
<script lang="ts">
|
|
import { displayDate, msToReadableTime } from '$lib/utils'
|
|
import type { CompletedJob, QueuedJob } from '$lib/gen'
|
|
import Badge from './common/badge/Badge.svelte'
|
|
import { isSuspended } from './agentRequest'
|
|
import { forLater } from '$lib/forLater'
|
|
import { Circle } from 'lucide-svelte'
|
|
import NoWorkerWithTagWarning from './runs/NoWorkerWithTagWarning.svelte'
|
|
import QueuePosition from './QueuePosition.svelte'
|
|
import WaitTimeWarning from './common/waitTimeWarning/WaitTimeWarning.svelte'
|
|
|
|
const SMALL_ICON_SIZE = 12
|
|
|
|
interface Props {
|
|
job: QueuedJob | CompletedJob | undefined
|
|
large?: boolean
|
|
}
|
|
|
|
let { job, large = false }: Props = $props()
|
|
</script>
|
|
|
|
{#if job && 'success' in job && job.success}
|
|
<Badge {large} color="green">
|
|
Successfully ran in {msToReadableTime(job.duration_ms)}
|
|
{job.is_skipped ? '(Skipped)' : ''}
|
|
{#if job.self_wait_time_ms || job.aggregate_wait_time_ms}
|
|
<WaitTimeWarning
|
|
self_wait_time_ms={job.self_wait_time_ms}
|
|
aggregate_wait_time_ms={job.aggregate_wait_time_ms}
|
|
variant="alert"
|
|
/>
|
|
{/if}
|
|
</Badge>
|
|
{:else if job && 'success' in job && job.resolved}
|
|
<Badge {large} color="orange" title={job.resolution_note}>
|
|
<!-- `resolved_by` is absent both for an automatic resolution and for a manual one
|
|
outside EE, so the automatic wording comes from `resolved_automatically`. -->
|
|
{job.canceled ? 'Canceled' : 'Failed'} after {msToReadableTime(job.duration_ms)}, resolved{job.resolved_automatically
|
|
? ' automatically'
|
|
: job.resolved_by
|
|
? ` by ${job.resolved_by}`
|
|
: ''}
|
|
{#if job.self_wait_time_ms || job.aggregate_wait_time_ms}
|
|
<WaitTimeWarning
|
|
self_wait_time_ms={job.self_wait_time_ms}
|
|
aggregate_wait_time_ms={job.aggregate_wait_time_ms}
|
|
variant="alert"
|
|
/>
|
|
{/if}
|
|
</Badge>
|
|
{:else if job && 'success' in job}
|
|
<Badge {large} color="red">
|
|
{job.canceled ? 'Canceled' : 'Failed'} after {msToReadableTime(job.duration_ms)}
|
|
{#if job.self_wait_time_ms || job.aggregate_wait_time_ms}
|
|
<WaitTimeWarning
|
|
self_wait_time_ms={job.self_wait_time_ms}
|
|
aggregate_wait_time_ms={job.aggregate_wait_time_ms}
|
|
variant="alert"
|
|
/>
|
|
{/if}
|
|
</Badge>
|
|
{:else if job && 'running' in job && job.running && isSuspended(job)}
|
|
<div>
|
|
<Badge {large} color="violet">
|
|
Suspended
|
|
{#if job.flow_status}
|
|
({(job.flow_status?.step ?? 0) + 1} of {job.raw_flow?.modules?.length ?? '?'})
|
|
{/if}
|
|
</Badge>
|
|
</div>
|
|
{:else if job && 'running' in job && job.running}
|
|
<div>
|
|
<Badge {large} color="yellow">
|
|
Running
|
|
{#if job.flow_status}
|
|
({(job.flow_status?.step ?? 0) + 1} of {job.raw_flow?.modules?.length ?? '?'})
|
|
{/if}
|
|
</Badge>
|
|
</div>
|
|
{:else if job && 'running' in job && 'scheduled_for' in job && job.scheduled_for && forLater(job.scheduled_for)}
|
|
<div>
|
|
<Badge color="blue" {large}>
|
|
Scheduled for {displayDate(job.scheduled_for)}
|
|
</Badge>
|
|
</div>
|
|
{:else if job && 'running' in job}
|
|
<div class="flex flex-row gap-1 items-center">
|
|
<Badge color="orange" {large}>Queued</Badge>
|
|
<NoWorkerWithTagWarning tag={job.tag} />
|
|
<QueuePosition jobId={job.id} workspaceId={job.workspace_id} minimal />
|
|
</div>
|
|
{:else}
|
|
<Circle size={SMALL_ICON_SIZE} class="text-gray-200" />
|
|
{/if}
|