mirror of
https://github.com/block/buzz.git
synced 2026-10-29 05:21:31 +00:00
fix(agents): wire controlled effort state through AgentEditMergedDialog
EffortPickerField gained required disabled/value/onChange props in #4625. Wire them through the merged dialog using the same Save-gated pattern as the now-deleted AgentInstanceEditDialog: effortLevel state + effortTouched ref in AgentEditMergedDialog, passed as effortValue/onEffortChange to the instance section, which forwards to EffortPickerField. In useAgentEditMergedSubmit, add effortLevel/effortTouched to AgentEditSubmitState and include effortLevel in the locked update_managed_agent call when touched. When no other I-fields changed (agentInput would be null), synthesise a minimal input from inst.pubkey so the effort write is still atomic. Co-authored-by: Will Pfleger <pfleger.will@gmail.com> Signed-off-by: Will Pfleger <pfleger.will@gmail.com>
This commit is contained in:
@@ -219,6 +219,10 @@ export function AgentEditMergedDialog({
|
||||
const [iIsCustomProviderEditing, setIIsCustomProviderEditing] =
|
||||
React.useState(false);
|
||||
const runtimeTouched = React.useRef(false);
|
||||
// Effort picker is Save-gated: hold pending selection in dialog state.
|
||||
// `effortTouched` distinguishes "user picked a value" from "seeded display".
|
||||
const [effortLevel, setEffortLevel] = React.useState<string | null>(null);
|
||||
const effortTouched = React.useRef(false);
|
||||
|
||||
// Convenience: the active model/provider — D-state when a definition is present,
|
||||
// I-state when instance-only (no definition). Used for the D-section and for
|
||||
@@ -315,6 +319,8 @@ export function AgentEditMergedDialog({
|
||||
setShowAdvancedFields(false);
|
||||
setIsAvatarUploadPending(false);
|
||||
runtimeTouched.current = false;
|
||||
setEffortLevel(null);
|
||||
effortTouched.current = false;
|
||||
autoSeededDefinitionRuntimeRef.current = null;
|
||||
// Capture the definition revision the form is baselined against.
|
||||
seededDefinitionUpdatedAtRef.current = def?.updatedAt ?? null;
|
||||
@@ -548,6 +554,8 @@ export function AgentEditMergedDialog({
|
||||
onValidate,
|
||||
onOpenChange,
|
||||
onUpdated,
|
||||
effortLevel,
|
||||
effortTouched,
|
||||
} satisfies AgentEditSubmitState;
|
||||
|
||||
const {
|
||||
@@ -921,6 +929,16 @@ export function AgentEditMergedDialog({
|
||||
systemPrompt={systemPrompt}
|
||||
onSystemPromptChange={setSystemPrompt}
|
||||
effortConfig={configSurfaceQuery.data}
|
||||
effortValue={
|
||||
effortTouched.current
|
||||
? effortLevel
|
||||
: (configSurfaceQuery.data?.normalized.thinkingEffort
|
||||
?.value ?? null)
|
||||
}
|
||||
onEffortChange={(level) => {
|
||||
effortTouched.current = true;
|
||||
setEffortLevel(level);
|
||||
}}
|
||||
/>
|
||||
) : null}
|
||||
|
||||
|
||||
@@ -129,6 +129,9 @@ export type AgentEditMergedISectionProps = {
|
||||
systemPrompt: string;
|
||||
onSystemPromptChange: (value: string) => void;
|
||||
effortConfig: RuntimeConfigSurface | undefined;
|
||||
/** Controlled pending effort selection (null = adapter default). */
|
||||
effortValue: string | null;
|
||||
onEffortChange: (level: string | null) => void;
|
||||
};
|
||||
|
||||
// ── Component ─────────────────────────────────────────────────────────────────
|
||||
@@ -208,6 +211,8 @@ export function AgentEditMergedInstanceSection({
|
||||
systemPrompt,
|
||||
onSystemPromptChange,
|
||||
effortConfig,
|
||||
effortValue,
|
||||
onEffortChange,
|
||||
}: AgentEditMergedISectionProps) {
|
||||
const shouldReduceMotion = useReducedMotion();
|
||||
const advancedFieldsTransition = shouldReduceMotion
|
||||
@@ -436,7 +441,13 @@ export function AgentEditMergedInstanceSection({
|
||||
</div>
|
||||
) : null}
|
||||
|
||||
<EffortPickerField agent={inst} config={effortConfig} />
|
||||
<EffortPickerField
|
||||
agent={inst}
|
||||
config={effortConfig}
|
||||
disabled={isSaving}
|
||||
value={effortValue}
|
||||
onChange={onEffortChange}
|
||||
/>
|
||||
|
||||
<AgentAiDefaultsNotice
|
||||
onEditDefaults={() => onAiDefaultsOpenChange(true)}
|
||||
|
||||
@@ -66,6 +66,8 @@ function makeDefinitionOnlyState(definition, overrides = {}) {
|
||||
defReadOnly: false,
|
||||
inheritedSubmissionProvider: null,
|
||||
runtimes: [],
|
||||
effortLevel: null,
|
||||
effortTouched: { current: false },
|
||||
...overrides,
|
||||
};
|
||||
}
|
||||
|
||||
@@ -93,6 +93,10 @@ export type AgentEditSubmitState = {
|
||||
onValidate?: () => string | null;
|
||||
onOpenChange: (open: boolean) => void;
|
||||
onUpdated?: (agent: ManagedAgent) => void;
|
||||
/** Controlled pending effort selection from the dialog (null = adapter default). */
|
||||
effortLevel: string | null;
|
||||
/** Ref tracking whether the user has made an explicit effort selection. */
|
||||
effortTouched: React.RefObject<boolean>;
|
||||
};
|
||||
|
||||
export type AgentEditSubmitHookReturn = {
|
||||
@@ -271,11 +275,19 @@ export function useAgentEditMergedSubmit(
|
||||
const seed = seedAgentFormModel(s.ctx);
|
||||
const next = buildNextAgentFormModel(seed, s);
|
||||
|
||||
const { personaInput, agentInput, policySets } = emitAgentFormDiff(
|
||||
seed,
|
||||
next,
|
||||
s.ctx,
|
||||
);
|
||||
const {
|
||||
personaInput,
|
||||
agentInput: rawAgentInput,
|
||||
policySets,
|
||||
} = emitAgentFormDiff(seed, next, s.ctx);
|
||||
|
||||
// Effort write: when touched, resolve and include in the locked update
|
||||
// (atomic with any access-policy change — PR #4625 semantics).
|
||||
let agentInput = rawAgentInput;
|
||||
if (s.effortTouched.current && inst) {
|
||||
agentInput = agentInput ?? { pubkey: inst.pubkey };
|
||||
agentInput = { ...agentInput, effortLevel: s.effortLevel };
|
||||
}
|
||||
|
||||
const refetchStores = () => refetchAgentStores(queryClient, def, inst);
|
||||
|
||||
|
||||
Reference in New Issue
Block a user