mirror of
https://github.com/windmill-labs/windmill.git
synced 2026-10-12 16:02:56 +00:00
fix(sessions): keep the page's promises and its edits when it holds a call
The dynamic-select helper runs its job when the field mounts, not when Run is pressed, so a form relocated to the page ran one under plan mode — which the card withholds for exactly that reason. The page owes the same promise while it is standing in for a card. Its form is a copy, so arguments typed there were dropped when the call went back to the card: it then offered, and ran, what the page had opened with. The call now carries them back. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
This commit is contained in:
co-authored by
Claude Opus 5
parent
d5b96105c6
commit
88457fbe16
@@ -146,6 +146,10 @@
|
||||
* if the press then ends before `runAction`, so a guard that counts presses can stop
|
||||
* counting this one. */
|
||||
claimRun?: { claim: () => boolean; release: () => void }
|
||||
/** Withhold the dynamic-select helper, which runs the `dynselect-` entrypoint on mount
|
||||
* rather than on Run. For a form standing in for a chat call while plan mode is on:
|
||||
* nothing about pressing Run gates it, and plan mode promised no runs. */
|
||||
noHelperScript?: boolean
|
||||
buttonText?: string
|
||||
schedulable?: boolean
|
||||
detailed?: boolean
|
||||
@@ -174,6 +178,7 @@
|
||||
runnable,
|
||||
runAction,
|
||||
claimRun = undefined,
|
||||
noHelperScript = false,
|
||||
buttonText = 'Run',
|
||||
schedulable = true,
|
||||
detailed = true,
|
||||
@@ -364,11 +369,13 @@
|
||||
{#key reloadArgs}
|
||||
<div bind:clientHeight={schemaHeight}>
|
||||
<SchemaForm
|
||||
helperScript={{
|
||||
source: 'deployed',
|
||||
path: runnable.path!,
|
||||
runnable_kind: runnable.hash ? 'script' : 'flow'
|
||||
}}
|
||||
helperScript={noHelperScript
|
||||
? undefined
|
||||
: {
|
||||
source: 'deployed',
|
||||
path: runnable.path!,
|
||||
runnable_kind: runnable.hash ? 'script' : 'flow'
|
||||
}}
|
||||
prettifyHeader
|
||||
{noVariablePicker}
|
||||
{autofocus}
|
||||
|
||||
@@ -2110,11 +2110,18 @@ export class AIChatManager implements ChatViewHost {
|
||||
return true
|
||||
}
|
||||
|
||||
/** The arguments a parked form is waiting on, for a surface showing that form outside the
|
||||
* card. The draft itself, not a copy: the card and the page edit one set of arguments. */
|
||||
/** What a parked form is waiting on, for a surface showing that form outside the card. */
|
||||
pendingRunFormArgs = (toolId: string): Record<string, any> | undefined =>
|
||||
this.#runForms.get(toolId)?.draft?.args
|
||||
|
||||
/** Take over what the form is waiting on, for a surface handing the call back with what
|
||||
* the reader typed into it. A snapshot for the same reason the draft is one: these are
|
||||
* `$state` proxies off another component, and the draft must not alias them. */
|
||||
setRunFormArgs = (toolId: string, args: Record<string, any>) => {
|
||||
const entry = this.#runForms.get(toolId)
|
||||
if (entry) entry.draft.args = ($state.snapshot(args) ?? {}) as Record<string, any>
|
||||
}
|
||||
|
||||
handleRunFormCancel = (toolId: string) => {
|
||||
// The card's own copy is settled here rather than only in the tool's fn, which a form
|
||||
// restored from history no longer has: Cancel is that card's one way out, and while it
|
||||
|
||||
@@ -399,6 +399,16 @@
|
||||
runForm.setArgs(pendingRun.args ?? {})
|
||||
})
|
||||
|
||||
// The form here is a copy, so what the reader typed into it would be lost when the call
|
||||
// goes back to the card — the card would then offer, and run, the arguments this page
|
||||
// opened with. Written on the way out rather than per keystroke: one hand-back, whether
|
||||
// the tab closed, was re-pointed, or the reader picked a version.
|
||||
$effect(() => {
|
||||
const call = pendingRun
|
||||
if (!call) return
|
||||
return () => call.handBack(args ?? {})
|
||||
})
|
||||
|
||||
// The dev workspace's editor is not one the session panel can host, so from a preview tab
|
||||
// it opens in a new browser tab, as the session editors' own entry does.
|
||||
function editInFork(e: Event | undefined, itemPath: string) {
|
||||
@@ -936,6 +946,7 @@
|
||||
runnable={flow}
|
||||
{runAction}
|
||||
claimRun={pendingRun}
|
||||
noHelperScript={pendingRun?.planModeActive}
|
||||
bind:args
|
||||
schedulable={!pendingRun}
|
||||
bind:this={runForm}
|
||||
|
||||
@@ -448,6 +448,16 @@
|
||||
runForm.setArgs(pendingRun.args ?? {})
|
||||
})
|
||||
|
||||
// The form here is a copy, so what the reader typed into it would be lost when the call
|
||||
// goes back to the card — the card would then offer, and run, the arguments this page
|
||||
// opened with. Written on the way out rather than per keystroke: one hand-back, whether
|
||||
// the tab closed, was re-pointed, or the reader picked a version.
|
||||
$effect(() => {
|
||||
const call = pendingRun
|
||||
if (!call) return
|
||||
return () => call.handBack(args ?? {})
|
||||
})
|
||||
|
||||
// Read once on purpose: these args seed the form, so tracking the fragment would
|
||||
// overwrite what the user has typed whenever it changes.
|
||||
let hash = untrack(() => locationHash)
|
||||
@@ -1144,6 +1154,7 @@
|
||||
runnable={script}
|
||||
{runAction}
|
||||
claimRun={pendingRun}
|
||||
noHelperScript={pendingRun?.planModeActive}
|
||||
bind:args
|
||||
schedulable={!pendingRun}
|
||||
bind:this={runForm}
|
||||
|
||||
@@ -13,10 +13,16 @@ export type PendingRun = {
|
||||
* mount: a tab already showing this item is reused for the next request, so a latch that
|
||||
* only remembered "seeded" would leave the previous call's arguments on screen. */
|
||||
toolCallId: string
|
||||
/** What the model proposed, already narrowed to the deployed schema by the tool. The
|
||||
* draft the card edits, not a copy of it, so the two surfaces fill one set of arguments.
|
||||
* Gone once the call stops waiting, which a page mid-press outlives. */
|
||||
/** What the form is waiting on: the model's proposal, or whatever the card was last left
|
||||
* holding. Gone once the call stops waiting, which a page mid-press outlives. */
|
||||
args: Record<string, any> | undefined
|
||||
/** Whether the chat is in plan mode, which promised no runs. The page is standing in for
|
||||
* a chat card, so it owes that promise too — and the dynamic-select helper runs its job on
|
||||
* mount, where nothing about confirming the run can gate it. */
|
||||
planModeActive: boolean
|
||||
/** Give the call back holding what the reader typed here, so the card they return to
|
||||
* offers those arguments rather than the ones this page opened with. */
|
||||
handBack: (args: Record<string, any>) => void
|
||||
/** Take the call, before the form mints a `password` field into an ephemeral workspace
|
||||
* variable. False means this press runs nothing: plan mode is on, or a press is already
|
||||
* in flight. Both have to be answered here rather than in {@link submit}, which the form
|
||||
@@ -31,8 +37,6 @@ export type PendingRun = {
|
||||
submit: (args: Record<string, any>) => boolean
|
||||
/** Refuse the call, as the card's Cancel does. Not an undo of the prefill: there is
|
||||
* nothing to restore it to, and leaving the call parked with an emptied form would strand
|
||||
* the turn. `reason` is what the model is told instead of "cancelled" — for the page that
|
||||
* settles the call by running the item some other way, where reporting a refusal would
|
||||
* have the model say nothing ran. */
|
||||
decline: (reason?: string) => void
|
||||
* the turn. */
|
||||
decline: () => void
|
||||
}
|
||||
|
||||
@@ -601,6 +601,10 @@ function createRuntime(session: Session): SessionRuntime {
|
||||
return {
|
||||
toolCallId,
|
||||
args: manager.pendingRunFormArgs(toolCallId),
|
||||
// Read here rather than on the page, so the page's own `$derived` rebuilds when the
|
||||
// posture changes while its form sits open.
|
||||
planModeActive: manager.planModeActive,
|
||||
handBack: (args) => manager.setRunFormArgs(toolCallId, args),
|
||||
// Both guards belong ahead of the form's own `processSecretArgs`, which writes
|
||||
// ephemeral variables to the workspace: plan mode can be switched on while the form
|
||||
// sits here, and a second press would mint a second set of them.
|
||||
|
||||
Reference in New Issue
Block a user