mirror of
https://github.com/openswarm-ai/openswarm.git
synced 2026-09-28 12:34:50 +02:00
[aidan] feat/scheduled-tasks: remember workflow tool permissions across runs
This commit is contained in:
@@ -75,6 +75,64 @@ export default function ActionsFacet({ draft, setDraft }: { draft: Workflow; set
|
||||
</Box>
|
||||
</Box>
|
||||
)}
|
||||
|
||||
<RememberedApprovals draft={draft} setDraft={setDraft} />
|
||||
</Box>
|
||||
);
|
||||
}
|
||||
|
||||
// Permissions this workflow learned on an earlier run and now reuses without
|
||||
// asking. A reused "allow" runs unattended, so the user has to be able to see
|
||||
// and take it back here.
|
||||
function RememberedApprovals({ draft, setDraft }: { draft: Workflow; setDraft: (w: Workflow) => void }) {
|
||||
const c = useClaudeTokens();
|
||||
const entries = Object.entries(draft.remembered_approvals || {});
|
||||
if (entries.length === 0) return null;
|
||||
|
||||
const prettyName = (tool: string) => (tool.includes('__') ? tool.split('__').pop() || tool : tool);
|
||||
const forget = (tool: string) => {
|
||||
const next = { ...(draft.remembered_approvals || {}) };
|
||||
const nextStepUsage = Object.fromEntries(
|
||||
Object.entries(draft.step_tool_usage || {}).map(([stepId, tools]) => {
|
||||
const copy = { ...(tools || {}) };
|
||||
delete copy[tool];
|
||||
return [stepId, copy];
|
||||
}),
|
||||
);
|
||||
delete next[tool];
|
||||
setDraft({ ...draft, remembered_approvals: next, step_tool_usage: nextStepUsage });
|
||||
};
|
||||
|
||||
return (
|
||||
<Box sx={{ display: 'flex', flexDirection: 'column', gap: 0.75, mt: 1, pt: 1.25, borderTop: `1px solid ${c.border.subtle}` }}>
|
||||
<Box sx={{ display: 'flex', alignItems: 'baseline', justifyContent: 'space-between' }}>
|
||||
<Typography sx={{ fontSize: BODY_FS, color: c.text.secondary, lineHeight: 1.5 }}>
|
||||
Saved permissions this workflow reuses on later runs.
|
||||
</Typography>
|
||||
<Box
|
||||
onClick={() => setDraft({ ...draft, remembered_approvals: {}, step_tool_usage: {} })}
|
||||
role="button"
|
||||
sx={{ fontSize: LABEL_FS, color: c.text.muted, cursor: 'pointer', whiteSpace: 'nowrap', ml: 1, '&:hover': { color: c.text.primary } }}>
|
||||
Clear all
|
||||
</Box>
|
||||
</Box>
|
||||
{entries.map(([tool, answer]) => (
|
||||
<Box key={tool} sx={{ display: 'flex', alignItems: 'center', gap: 1, py: 0.4, px: 0.75, borderRadius: `${c.radius.md}px`, bgcolor: c.bg.elevated }}>
|
||||
<Typography sx={{ fontSize: LABEL_FS, color: c.text.primary, flex: 1, minWidth: 0, overflow: 'hidden', textOverflow: 'ellipsis', whiteSpace: 'nowrap' }}>
|
||||
{prettyName(tool)}
|
||||
</Typography>
|
||||
<Typography sx={{ fontSize: LABEL_FS, fontWeight: 600, color: answer === 'allow' ? c.status.success : c.status.error }}>
|
||||
{answer === 'allow' ? 'Allowed' : 'Blocked'}
|
||||
</Typography>
|
||||
<Box
|
||||
onClick={() => forget(tool)}
|
||||
role="button"
|
||||
aria-label={`Forget ${prettyName(tool)}`}
|
||||
sx={{ display: 'inline-flex', fontSize: LABEL_FS, color: c.text.muted, cursor: 'pointer', px: 0.4, '&:hover': { color: c.status.error } }}>
|
||||
✕
|
||||
</Box>
|
||||
</Box>
|
||||
))}
|
||||
</Box>
|
||||
);
|
||||
}
|
||||
|
||||
@@ -81,6 +81,10 @@ export interface Workflow {
|
||||
edit_agent_session_id?: string | null;
|
||||
/** Sticky session id for the embedded scheduling agent (cadence -> gated tool call). */
|
||||
schedule_agent_session_id?: string | null;
|
||||
/** Tool permissions the user answered once and we reuse on later runs so an
|
||||
* unattended scheduled fire doesn't stall on a prompt. tool name -> answer. */
|
||||
remembered_approvals?: Record<string, 'allow' | 'deny'>;
|
||||
step_tool_usage?: Record<string, Record<string, boolean>>;
|
||||
}
|
||||
|
||||
export interface WorkflowRun {
|
||||
|
||||
Reference in New Issue
Block a user