mirror of
https://github.com/windmill-labs/windmill.git
synced 2026-09-10 00:05:27 +00:00
* fix: disable a dynamic input when its schema field is disabled Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01ERed9zo2oJjpSczzMayaNh * refactor: extract the run form's argument hygiene into job_args Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01ERed9zo2oJjpSczzMayaNh * feat: give Tabs an opt-in sliding selection indicator Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01ERed9zo2oJjpSczzMayaNh * refactor: share the chat's scroll-fade measurement Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01ERed9zo2oJjpSczzMayaNh * feat: give the chat a run-form contract and incremental job output Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01ERed9zo2oJjpSczzMayaNh * feat: run and test a script from the chat through an argument form Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01ERed9zo2oJjpSczzMayaNh * feat: carry a chat run's card and job across saves and reloads Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01ERed9zo2oJjpSczzMayaNh * feat: render a chat run as a tool call row with its form, logs and result Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01ERed9zo2oJjpSczzMayaNh * feat: open a pending run form in the sessions preview pane Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01ERed9zo2oJjpSczzMayaNh * test: benchmark running a deployed script from the chat Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01ERed9zo2oJjpSczzMayaNh * feat: offer a test run's dynamic options from the draft it previews Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> * docs: say that a test run's dynselect helper executes on form display * fix: send a schema default the model omitted when yolo skips the form * fix: infer a test run's schema when the stored one declares no properties * docs: tighten the note on the form's mount-time helper job * fix: apply a nested schema default the bypass posture counts as answered * fix: apply a declared default to a null value and an optional nested field * fix: check required fields inside a supplied optional object before bypassing * fix: read required args as own properties before bypassing the form * fix: stop the turn from the run form's action row in the preview panel * refactor: drop the run-form prediction and share its secret minting * refactor: prefill a proposed secret instead of emptying the field * docs: correct the comments the run-form prediction left behind * fix: keep a proposed secret out of the chat's stored messages * docs: say what a literal secret argument now does * test: restore the copilotInfo export the aiStore mock omits * docs: cut the run form's helper-script note to its constraints * refactor: settle a run form from one entry and fetch a job's logs once * fix: separate colliding secret paths, gate plan mode, keep polled logs * fix: mint before the form opens, skip empty fields, show what ran * revert: mint a run form's secrets at submit, not before it opens * fix: settle a cancelled run card on the form's arguments, not the proposal * fix: settle a stopped run form like a cancelled one, and keep an empty secret empty * fix: snapshot a run's arguments before minting its secrets --------- Co-authored-by: Claude Opus 5 <noreply@anthropic.com>
150 lines
5.2 KiB
Svelte
150 lines
5.2 KiB
Svelte
<script lang="ts">
|
|
import { VariableService } from '$lib/gen'
|
|
import { userStore, workspaceStore } from '$lib/stores'
|
|
import { ephemeralSecretPrefix, mintEphemeralSecret } from './secretArgUtils'
|
|
import { sendUserToast } from '$lib/toast'
|
|
import { Button } from './common'
|
|
import Password from './Password.svelte'
|
|
import { untrack } from 'svelte'
|
|
|
|
interface Props {
|
|
value?: string | undefined
|
|
disabled: boolean
|
|
minRows?: number
|
|
/** Workspace the ephemeral secret is minted in; defaults to the nav workspace.
|
|
* Session editors pass their acting workspace. */
|
|
workspace?: string | undefined
|
|
}
|
|
|
|
let { value = $bindable(undefined), disabled, minRows, workspace }: Props = $props()
|
|
|
|
let ws = $derived(workspace ?? $workspaceStore)
|
|
|
|
let path = $state('')
|
|
// Workspace the variable at `path` actually lives in; `ws` can move away from it.
|
|
let mintedIn = $state<string | undefined>(undefined)
|
|
// What the field mints from: an argument already holding a `$var:` ref has nothing to mint.
|
|
function plaintextOf(v: unknown): string {
|
|
return typeof v === 'string' && v !== '' && !v.startsWith('$var:') ? v : ''
|
|
}
|
|
let password = $state(plaintextOf(value))
|
|
|
|
// The argument no longer holds what this field would mint from — a parent can replace the whole
|
|
// args object without remounting it (previewing a saved input, say). Minting now would describe a
|
|
// secret the argument does not point at, and binding it would discard the replacement.
|
|
let argReplaced = $derived(path !== '' && value !== '$var:' + path)
|
|
|
|
let isGenerating = false
|
|
|
|
let username = $derived(($userStore?.username ?? $userStore?.email)?.split('@')[0] ?? '')
|
|
let userPrefix = $derived(ephemeralSecretPrefix(username))
|
|
async function generateValue() {
|
|
if (isGenerating || argReplaced) return
|
|
isGenerating = true
|
|
const mintWs = ws!
|
|
const boundBefore = value
|
|
try {
|
|
let npath = await mintEphemeralSecret(mintWs, username, password)
|
|
let nvalue = '$var:' + npath
|
|
// The arg can be replaced the same way while the create is in flight. Nothing ever
|
|
// referenced the variable just minted, so delete it; it expires on its own if that fails.
|
|
if (value !== boundBefore) {
|
|
VariableService.deleteVariable({ workspace: mintWs, path: npath }).catch(() => {})
|
|
return
|
|
}
|
|
path = npath
|
|
mintedIn = mintWs
|
|
console.log('generated', nvalue)
|
|
value = nvalue
|
|
debouncedUpdate()
|
|
} finally {
|
|
// Ended without binding: discarded just above, or the create failed after the argument
|
|
// moved. The field would otherwise keep showing a secret the argument does not hold, and
|
|
// the mint effect tracks `ws` — a workspace move would bind that stale plaintext over the
|
|
// replacement. Re-seeding leaves the field describing the argument again.
|
|
if (path === '' && value !== boundBefore) {
|
|
password = plaintextOf(value)
|
|
}
|
|
isGenerating = false
|
|
}
|
|
}
|
|
|
|
async function updateValue() {
|
|
// The first keystroke queues an update before anything is minted: letting it run would 404 and
|
|
// retry the mint, binding over an argument that was replaced while the first mint was in flight.
|
|
if (path === '') return
|
|
const updating = path
|
|
try {
|
|
await VariableService.updateVariable({
|
|
workspace: mintedIn ?? ws!,
|
|
path: path,
|
|
requestBody: {
|
|
value: password
|
|
}
|
|
})
|
|
} catch (e) {
|
|
// A re-mint can bind a fresh variable while this update is in flight; recovering then
|
|
// would orphan the one it just bound.
|
|
if (path !== updating) return
|
|
generateValue().catch((e) =>
|
|
sendUserToast(`Could not create the secret: ${e?.body ?? e?.message ?? e}`, true)
|
|
)
|
|
}
|
|
}
|
|
|
|
let timeout: number | undefined = undefined
|
|
function debouncedUpdate() {
|
|
timeout && clearTimeout(timeout)
|
|
timeout = setTimeout(updateValue, 500)
|
|
}
|
|
|
|
$effect(() => {
|
|
password && untrack(() => debouncedUpdate())
|
|
})
|
|
|
|
$effect(() => {
|
|
ws &&
|
|
($userStore?.username || $userStore?.email) &&
|
|
path == '' &&
|
|
password != '' &&
|
|
untrack(() =>
|
|
// A failed mint leaves the plaintext bound to nothing and the argument empty. Only a
|
|
// further keystroke re-runs this, so say so rather than submitting the job without it.
|
|
generateValue().catch((e) =>
|
|
sendUserToast(`Could not create the secret: ${e?.body ?? e?.message ?? e}`, true)
|
|
)
|
|
)
|
|
})
|
|
|
|
// The operating workspace can move after minting (a session forking, say), leaving the
|
|
// variable behind where the job will not find it: mint a fresh one in the new workspace.
|
|
// Bounded to a live instance: a field mounted onto an existing `$var:` holds neither the
|
|
// plaintext nor the workspace it was minted in, so it can only be moved by retyping it.
|
|
$effect(() => {
|
|
const cur = ws
|
|
if (!cur || path === '' || password === '' || mintedIn === cur || argReplaced) return
|
|
untrack(() =>
|
|
generateValue().catch((e) =>
|
|
sendUserToast(`Could not create the secret in ${cur}: ${e?.body ?? e?.message ?? e}`, true)
|
|
)
|
|
)
|
|
})
|
|
</script>
|
|
|
|
{#if value?.startsWith('$var:') && !value.startsWith('$var:' + userPrefix)}
|
|
<div class="flex items-center gap-2 text-sm text-primary">
|
|
Linked to static variable
|
|
<Button
|
|
size="xs"
|
|
variant="default"
|
|
onclick={() => {
|
|
value = ''
|
|
}}
|
|
>
|
|
Reset variable link
|
|
</Button>
|
|
</div>
|
|
{:else}
|
|
<Password {disabled} {minRows} bind:password />
|
|
{/if}
|