diff --git a/CHANGELOG.md b/CHANGELOG.md index 27b2ed4..9f37596 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -3,8 +3,10 @@ ## Unreleased + [新增] Agent 支持查看、创建、编辑、删除、启停和显式调用本地 Codex Skill,并同步多标签页变更。 ++ [新增] Agent 支持从当前对话或当前画布生成可编辑的 Skill 草稿,并在用户确认后保存。 + [调整] Agent 面板连接状态、内容标签和新对话统一排版,窄面板仅隐藏文字并保留图标与数量。 + [优化] Agent 输入区控件随面板宽度在图标与图标加文字之间自适应。 ++ [修复] Agent Skill 草稿期间保持 MCP 活动画布隔离,支持停止临时生成任务,并禁止并发修改 Skill。 ## v0.13.0 - 2026-08-03 diff --git a/docs/content/docs/progress/pending-test.mdx b/docs/content/docs/progress/pending-test.mdx index 064acaf..949e547 100644 --- a/docs/content/docs/progress/pending-test.mdx +++ b/docs/content/docs/progress/pending-test.mdx @@ -16,6 +16,7 @@ description: 当前版本已实现但仍需人工验证的变更项 - Agent 顶部栏:在对话、历史和日志之间新增「技能」标签并显示已加载数量;连接状态应位于左侧 Agent 标题旁,并与右侧新对话使用相同规格的文字按钮,只有内容标签显示选中线;标签图标始终显示,窄面板仅隐藏文字并保留小号数量,宽面板显示图标和文字,拖动宽度时不应换行、横向滚动或错位。 - Agent 输入框窄屏布局:底部上传、Skill、确认模式、权限模式、模型和思考程度控件在窄面板下应只显示图标,宽面板下恢复文字;所有纯图标操作悬停时应使用统一样式显示名称或当前状态,任意宽度都不出现横向溢出。 - Agent 本地 Skill 管理:连接 Canvas Agent 后进入「技能」,应能读取当前 Codex 工作空间发现的项目、个人、系统和管理员 Skill,并支持搜索、按范围筛选、刷新及启停;未能加载的 Skill 应通过可点击、可滚动的详情查看原因;画布在站点工作空间 `.agents/skills` 下创建的 Skill 应可新建、编辑和删除,其他来源只读且不得被画布改写;编辑期间文件被其他窗口修改时应拒绝覆盖,清空显示名称、短说明和默认提示词后旧值应被删除,同时保留图标、品牌色等未由当前界面管理的元数据;多个标签页同时连接时,任一页面的 Skill 变更应自动同步,断开或切换 Agent 时旧请求不得重新写回列表或弹出编辑器。 +- Agent Skill 草稿生成:创建 Skill 时应提供「空白创建」「从当前对话生成草稿」「从当前画布生成草稿」三种入口;空对话不得发起生成,取消时不得写入文件,确认后才写入站点工作空间 `.agents/skills`;Agent 运行中应禁用生成,多标签页下对话和画布来源必须分别严格绑定发起请求的 `threadId` 和 `clientId`,生成期间外部 MCP 仍应操作最近聚焦页面;草稿运行时创建、启停、删除和更新 Skill 应被统一拒绝,点击停止应真实中断临时草稿 turn;草稿不得泄露本地路径、密钥、媒体数据、真实节点 ID 或任务进度等临时状态,普通中文斜杠文本与 `/imagine` 等命令必须保持完整,生成草稿使用的临时 turn 不得出现在 Agent 对话、历史或日志中。 - Agent Skill 调用:在技能页点击「使用」或在输入框左下方选择 Skill 后,输入框上方应显示可移除标签;发送任务时应通过 Codex 原生结构化 Skill 输入执行,而不是只把名称拼进普通提示词,发送成功后清除选择,失败时保留;新建对话、断开连接、Skill 被停用或删除时也应清除失效选择,发送前、任务完成后和刷新历史后的用户消息文字应保持一致。 - Agent Markdown 样式:右侧 Agent 回复中的代码块应为横向占满消息区域、无语言标题和无双层边框的紧凑代码条,单行内容不应保留纵向大面积空白,复制操作仅在悬停时弱化显示;行内代码、链接在浅色和深色主题下应清晰;点击外部链接后应显示中文紧凑确认弹窗,长路径能够正常换行且复制、继续打开和关闭操作可用;点击 `/Users/`、`/home/` 等本地绝对文件路径时应改为提示在系统文件管理器中定位,支持复制路径,且浏览器地址不应跳转为 localhost 文件路径。 - Agent 工具确认模式:右侧面板标题栏不应再显示「工具确认」开关;对话输入框左下方应显示确认模式选择,默认选中「自动确认」,画布写入工具应直接执行;切换为「手动确认」后,画布写入工具应在对话中展示等待确认卡片,并支持批准或拒绝。 diff --git a/docs/content/docs/progress/todo.mdx b/docs/content/docs/progress/todo.mdx index 36ce080..749bb04 100644 --- a/docs/content/docs/progress/todo.mdx +++ b/docs/content/docs/progress/todo.mdx @@ -8,3 +8,4 @@ description: 当前项目后续值得处理的事项 本文档用来记录当前项目后续比较值得处理的事项。 - 本地 Agent 接入后续:按[本地 Agent 接入规划](/docs/progress/local-agent-integration-plan)把 Claude Code CLI Adapter 升级为 Claude Agent SDK Adapter,并完善工具队列体验。 +- Agent Skill 后续:补充网络检索安装、Skill 资源文件管理和可控的本地记忆沉淀。 diff --git a/web/src/components/agent/agent-chat.tsx b/web/src/components/agent/agent-chat.tsx index 67b6d18..eece3ff 100644 --- a/web/src/components/agent/agent-chat.tsx +++ b/web/src/components/agent/agent-chat.tsx @@ -89,7 +89,7 @@ export function AgentChatTimeline({ /> ) : null} {pendingApprovals.map((approval) => onApprovalDecision(approval, decision)} />)} - {(sending || waiting || bootstrapStatus) && !streaming && !pendingTool && !pendingApprovals.length ? ({ name, ...item }))} activityKey={working.key} theme={theme} /> : null} + {(sending || waiting || bootstrapStatus) && !streaming && !pendingTool && !pendingApprovals.length ? ({ name, ...item }))} activityKey={working.key} theme={theme} /> : null} {showScrollToBottom ? ( diff --git a/web/src/components/agent/agent-skills-view.tsx b/web/src/components/agent/agent-skills-view.tsx index 58a71d9..b083004 100644 --- a/web/src/components/agent/agent-skills-view.tsx +++ b/web/src/components/agent/agent-skills-view.tsx @@ -1,39 +1,52 @@ import { useEffect, useMemo, useRef, useState } from "react"; -import { App, Button, Form, Input, Modal, Select, Switch, Tooltip } from "antd"; -import { Check, CircleAlert, FilePenLine, LoaderCircle, LockKeyhole, Plus, RefreshCw, Search, Sparkles, Trash2 } from "lucide-react"; +import { App, Button, Collapse, Dropdown, Form, Input, Modal, Select, Switch, Tooltip } from "antd"; +import type { MenuProps } from "antd"; +import { Check, ChevronDown, CircleAlert, FilePenLine, LoaderCircle, LockKeyhole, MessageSquareText, Plus, RefreshCw, Search, Sparkles, Trash2, Workflow } from "lucide-react"; import { canvasThemes } from "@/lib/canvas-theme"; -import { createCodexSkill, deleteCodexSkill, fetchCodexSkill, setCodexSkillEnabled, updateCodexSkill, type AgentSkillDetail, type AgentSkillInterface, type AgentSkillScope, type AgentSkillSummary } from "@/services/api/canvas-agent"; +import { createCodexSkill, createCodexSkillDraft, deleteCodexSkill, fetchCodexSkill, postState, setCodexSkillEnabled, updateCodexSkill, type AgentSkillDetail, type AgentSkillDraft, type AgentSkillInterface, type AgentSkillScope, type AgentSkillSummary } from "@/services/api/canvas-agent"; import { useAgentSkillStore } from "@/stores/use-agent-skill-store"; -import { useAgentStore } from "@/stores/use-agent-store"; +import { useAgentStore, type AgentChatItem } from "@/stores/use-agent-store"; import { useThemeStore } from "@/stores/use-theme-store"; type ScopeFilter = "all" | AgentSkillScope; -type SkillEditor = { mode: "create" } | { mode: "edit"; detail: AgentSkillDetail }; +type SkillDraftSource = "conversation" | "canvas"; +type SkillEditor = { mode: "create"; values?: SkillFormValues } | { mode: "edit"; detail: AgentSkillDetail }; type SkillFormValues = { name: string; description: string; instructions: string; displayName?: string; shortDescription?: string; defaultPrompt?: string }; const scopeLabels: Record = { repo: "项目", user: "个人", system: "系统", admin: "管理员" }; const skillNamePattern = /^[a-z0-9]+(?:-[a-z0-9]+)*$/; -export function AgentSkillsView() { +export function AgentSkillsView({ clientId }: { clientId: string }) { const theme = canvasThemes[useThemeStore((state) => state.theme)]; const { message, modal } = App.useApp(); const connected = useAgentStore((state) => state.connected); const url = useAgentStore((state) => state.url); const token = useAgentStore((state) => state.token); + const activeThreadId = useAgentStore((state) => state.activeThreadId); + const hasConversation = useAgentStore((state) => hasSettledConversation(state.messages, state.activeThreadId)); + const hasCanvas = useAgentStore((state) => Boolean(state.canvasContext)); + const sending = useAgentStore((state) => state.sending); + const waiting = useAgentStore((state) => state.waiting); const setAgentState = useAgentStore((state) => state.setAgentState); const skills = useAgentSkillStore((state) => state.skills); const selectedSkill = useAgentSkillStore((state) => state.selectedSkill); const loading = useAgentSkillStore((state) => state.loading); const loaded = useAgentSkillStore((state) => state.loaded); const errors = useAgentSkillStore((state) => state.errors); + const draft = useAgentSkillStore((state) => state.draft); + const generatingSource = useAgentSkillStore((state) => state.generatingSource); const loadSkills = useAgentSkillStore((state) => state.loadSkills); const selectSkill = useAgentSkillStore((state) => state.selectSkill); const clearSelection = useAgentSkillStore((state) => state.clearSelection); + const setDraft = useAgentSkillStore((state) => state.setDraft); + const setGeneratingSource = useAgentSkillStore((state) => state.setGeneratingSource); const [query, setQuery] = useState(""); const [scope, setScope] = useState("all"); const [editor, setEditor] = useState(null); const [saving, setSaving] = useState(false); + const [advancedOpen, setAdvancedOpen] = useState(false); + const [createMenuOpen, setCreateMenuOpen] = useState(false); const [busySkill, setBusySkill] = useState(""); const [errorsOpen, setErrorsOpen] = useState(false); const confirmRef = useRef<{ destroy: () => void } | null>(null); @@ -47,7 +60,7 @@ export function AgentSkillsView() { }); }, [query, scope, skills]); - const editorValues = editor?.mode === "edit" ? skillFormValues(editor.detail) : undefined; + const editorValues = editor?.mode === "edit" ? skillFormValues(editor.detail) : editor?.values; const refresh = (forceReload = true) => loadSkills(endpoint, token, forceReload); const connectionIsCurrent = (revision: number) => { @@ -55,12 +68,17 @@ export function AgentSkillsView() { const skillsState = useAgentSkillStore.getState(); return skillsState.connectionRevision === revision && agent.connected && agent.url.trim().replace(/\/$/, "") === endpoint && agent.token === token; }; + useEffect(() => { + if (draft) setEditor((current) => current || { mode: "create", values: draftFormValues(draft) }); + }, [draft]); useEffect(() => { if (connected) return; confirmRef.current?.destroy(); confirmRef.current = null; setEditor(null); setSaving(false); + setAdvancedOpen(false); + setCreateMenuOpen(false); setBusySkill(""); setErrorsOpen(false); form.resetFields(); @@ -69,8 +87,39 @@ export function AgentSkillsView() { selectSkill(skill); setAgentState({ activeTab: "chat" }); }; + const generateDraft = async (source: SkillDraftSource) => { + const agent = useAgentStore.getState(); + if (agent.sending || agent.waiting) return message.warning("Codex 正在运行,请完成当前任务后再提炼 Skill"); + if (source === "conversation" && !hasSettledConversation(agent.messages, agent.activeThreadId)) return message.warning("当前对话还没有可提炼的已完成内容"); + if (source === "canvas" && !agent.canvasContext) return message.warning("当前页面没有可提炼的画布"); + if (!clientId) return message.warning("当前页面仍在连接 Agent,请稍后再试"); + const connectionRevision = useAgentSkillStore.getState().connectionRevision; + setGeneratingSource(source); + try { + if (source === "canvas") { + const synced = await postState(endpoint, token, clientId, agent.canvasContext?.snapshot || null); + if (!synced) throw new Error("同步当前画布失败,请检查 Agent 连接后重试"); + } + if (!connectionIsCurrent(connectionRevision)) return; + const response = await createCodexSkillDraft(endpoint, token, { + source, + threadId: agent.activeThreadId, + clientId, + ...(agent.model ? { model: agent.model } : {}), + ...(agent.reasoningEffort ? { effort: agent.reasoningEffort } : {}), + }); + if (!connectionIsCurrent(connectionRevision)) return; + if (!response.data) throw new Error("未生成 Skill 草稿"); + setDraft(response.data); + message.success("草稿已生成,可在技能页确认后创建"); + } catch (error) { + if (connectionIsCurrent(connectionRevision)) message.error(error instanceof Error ? error.message : "生成 Skill 草稿失败"); + } finally { + if (connectionIsCurrent(connectionRevision)) setGeneratingSource(null); + } + }; const openEdit = async (skill: AgentSkillSummary) => { - if (!skill.managed || busySkill) return; + if (!skill.managed || busySkill || useAgentSkillStore.getState().generatingSource) return; const connectionRevision = useAgentSkillStore.getState().connectionRevision; setBusySkill(skill.path); try { @@ -90,12 +139,17 @@ export function AgentSkillsView() { try { values = await form.validateFields(); } catch { + const firstError = form.getFieldsError().find((field) => field.errors.length); + if (firstError?.name.some((name) => name === "shortDescription" || name === "defaultPrompt")) setAdvancedOpen(true); + if (firstError) requestAnimationFrame(() => form.scrollToField(firstError.name, { block: "center" })); return; } const name = editor.mode === "edit" ? editor.detail.name : values.name.trim(); const skillInterface = compactInterface(values); if (skillInterface?.defaultPrompt && !mentionsSkill(skillInterface.defaultPrompt, name)) { form.setFields([{ name: "defaultPrompt", errors: [`默认提示词需要包含 $${name}`] }]); + setAdvancedOpen(true); + requestAnimationFrame(() => form.scrollToField("defaultPrompt", { block: "center" })); return; } const connectionRevision = useAgentSkillStore.getState().connectionRevision; @@ -106,7 +160,9 @@ export function AgentSkillsView() { if (editor.mode === "create") await createCodexSkill(endpoint, token, { name, ...input }); else await updateCodexSkill(endpoint, token, name, { ...input, expectedRevision: editor.detail.revision }); if (!connectionIsCurrent(connectionRevision)) return; + setDraft(null); setEditor(null); + setAdvancedOpen(false); await refresh(); if (!connectionIsCurrent(connectionRevision)) return; message.success(editor.mode === "create" ? "Skill 已创建" : "Skill 已更新"); @@ -165,6 +221,51 @@ export function AgentSkillsView() { if (connectionIsCurrent(connectionRevision)) setBusySkill(""); } }; + const codexBusy = sending || waiting; + const createMenu: MenuProps = { + items: [ + { + key: "conversation", + icon: , + disabled: codexBusy || !hasConversation, + label: ( +
+
从当前对话生成草稿
+
{codexBusy ? "Codex 运行结束后可用" : hasConversation ? "整理当前对话中的可复用流程" : activeThreadId ? "当前对话还没有已完成内容" : "请先开始一段对话"}
+
+ ), + }, + { + key: "canvas", + icon: , + disabled: codexBusy || !hasCanvas, + label: ( +
+
从当前画布生成草稿
+
{codexBusy ? "Codex 运行结束后可用" : hasCanvas ? "整理当前页面的节点与生成流程" : "当前页面没有可用画布"}
+
+ ), + }, + { type: "divider" as const }, + { + key: "manual", + icon: , + label: ( +
+
空白创建
+
从空白表单开始编写
+
+ ), + }, + ], + onClick: ({ key }) => { + if (key === "manual") { + setDraft(null); + setEditor({ mode: "create" }); + } + else void generateDraft(key as SkillDraftSource); + }, + }; return (
@@ -178,19 +279,24 @@ export function AgentSkillsView() { + + +
- setQuery(event.target.value)} prefix={} placeholder="搜索 Skill" /> + setQuery(event.target.value)} prefix={} placeholder="搜索 Skill" /> size="small" variant="borderless" + aria-label="按来源筛选 Skill" className="w-28 shrink-0" disabled={!connected} value={scope} onChange={setScope} - options={[{ value: "all", label: "全部范围" }, ...Object.entries(scopeLabels).map(([value, label]) => ({ value: value as AgentSkillScope, label }))]} + options={[{ value: "all", label: "全部来源" }, ...Object.entries(scopeLabels).map(([value, label]) => ({ value: value as AgentSkillScope, label }))]} />
{errors.length ? ( @@ -222,15 +328,15 @@ export function AgentSkillsView() {
{skill.managed ? ( <> -
@@ -243,7 +349,7 @@ export function AgentSkillsView() {
{!connected ? "连接 Agent 后查看 Skill" : skills.length ? "没有匹配的 Skill" : "还没有本地 Skill"}
-
{!connected ? "连接成功后会读取本机已安装的 Skill" : skills.length ? "换个关键词或范围试试" : "新建一个,或在本机安装后刷新"}
+
{!connected ? "连接成功后会读取本机已安装的 Skill" : skills.length ? "换个关键词或来源试试" : "创建一个,或在本机安装后刷新"}
)}
@@ -254,16 +360,53 @@ export function AgentSkillsView() { - !saving && setEditor(null)} onOk={() => void saveSkill()}> -
- - - - - typeof value === "string" && /[<>]/.test(value) ? Promise.reject(new Error("触发说明不能包含尖括号")) : Promise.resolve() }]}> - - - + { + if (saving) return; + setDraft(null); + setEditor(null); + setAdvancedOpen(false); + }} + onOk={() => void saveSkill()} + > +
保存到 本地 Agent 工作区 · .agents/skills
+ +
基本信息
+
+ + + + +
+ typeof value === "string" && /[<>]/.test(value) ? Promise.reject(new Error("使用场景不能包含尖括号")) : Promise.resolve() }]}> + + setAdvancedOpen((Array.isArray(keys) ? keys : [keys]).includes("advanced"))} + items={[{ + key: "advanced", + forceRender: true, + label: 高级设置, + children: ( + <> + + + + ), + }]} + />
@@ -281,6 +424,21 @@ function skillFormValues(detail: AgentSkillDetail): SkillFormValues { }; } +function draftFormValues(draft: AgentSkillDraft): SkillFormValues { + return { + name: draft.name, + description: draft.description, + instructions: draft.instructions, + displayName: draft.displayName || undefined, + shortDescription: draft.shortDescription || undefined, + defaultPrompt: draft.defaultPrompt || undefined, + }; +} + +function hasSettledConversation(messages: AgentChatItem[], threadId: string) { + return Boolean(threadId && messages.some((item) => item.role === "user" && item.threadId === threadId && item.turnId)); +} + function compactInterface(values: SkillFormValues): AgentSkillInterface | undefined { const skillInterface = { displayName: values.displayName?.trim() || undefined, diff --git a/web/src/components/agent/local-agent-panel.tsx b/web/src/components/agent/local-agent-panel.tsx index bd0b83a..0971fcd 100644 --- a/web/src/components/agent/local-agent-panel.tsx +++ b/web/src/components/agent/local-agent-panel.tsx @@ -17,7 +17,7 @@ import { useShallow } from "zustand/react/shallow"; import { useAgentStore, type AgentCanvasContext, type AgentChatItem, type AgentModel, type AgentPendingApproval, type AgentPendingToolCall, type AgentPermissionMode, type AgentReasoningEffort, type AgentThreadSummary } from "@/stores/use-agent-store"; import { type CanvasAgentOp, type CanvasAgentSnapshot } from "@/lib/canvas/canvas-agent-ops"; import { isSiteTool, runSiteTool } from "@/lib/agent/agent-site-tools"; -import { acknowledgeCodexHistory, activateAgentClient, discoverAgentConfig, fetchAgentJson, postCodexApproval, postState, postToolResult } from "@/services/api/canvas-agent"; +import { acknowledgeCodexHistory, activateAgentClient, discoverAgentConfig, fetchAgentJson, interruptCodexTurn, postCodexApproval, postState, postToolResult } from "@/services/api/canvas-agent"; import { AgentChatTimeline, AgentTaskProgress, AgentUsageBar } from "./agent-chat"; import { AgentChatComposer } from "./agent-chat-composer"; import { AgentConnectView } from "./agent-connect-view"; @@ -66,7 +66,7 @@ import { AgentSkillsView } from "./agent-skills-view"; const MAX_ATTACHMENTS = 6; const MAX_ATTACHMENT_PAYLOAD_BYTES = 28 * 1024 * 1024; const DEFAULT_AGENT_URL = "http://127.0.0.1:17371"; -const AGENT_PROTOCOL_VERSION = 4; +const AGENT_PROTOCOL_VERSION = 5; const HISTORY_RETRY_DELAYS_MS = [0, 150, 350, 700, 1200]; const AGENT_REASONING_EFFORTS = new Set(["minimal", "low", "medium", "high", "xhigh", "max", "ultra"]); const AGENT_REASONING_LABELS: Record = { minimal: "最低", low: "轻度", medium: "中", high: "高", xhigh: "极高", max: "最高", ultra: "Ultra" }; @@ -676,10 +676,11 @@ export function LocalAgentPanel({ embedded, headless, autoConnect }: { embedded? if (!connected || (!sending && !waiting)) return; setAgentState({ activity: "停止中" }); try { - await fetch(`${endpoint}/agent/codex/interrupt?token=${encodeURIComponent(token)}`, { method: "POST", headers: { "content-type": "application/json" }, body: JSON.stringify({ threadId: useAgentStore.getState().activeThreadId || undefined }) }); - addEventLog("停止任务", "已发送停止请求"); - } catch { + await interruptCodexTurn(endpoint, token, useAgentStore.getState().activeThreadId || undefined); + addEventLog("停止任务", "任务已停止"); + } catch (error) { setAgentState({ activity: "停止失败" }); + addEventLog("停止失败", error); } }; @@ -1281,7 +1282,7 @@ export function LocalAgentPanel({ embedded, headless, autoConnect }: { embedded? onToggleEnabled={toggleAgentConnection} /> ) : activeTab === "skills" ? ( - + ) : activeTab === "history" ? ( (endpoint, token, "/agent/codex/skills", jsonPost(input)); } +export function createCodexSkillDraft(endpoint: string, token: string, input: AgentSkillDraftInput) { + return fetchAgentJson(endpoint, token, "/agent/codex/skills/draft", jsonPost(input)); +} + export function updateCodexSkill(endpoint: string, token: string, name: string, input: AgentSkillInput) { return fetchAgentJson(endpoint, token, `/agent/codex/skills/${encodeURIComponent(name)}`, jsonPost(input)); } diff --git a/web/src/stores/use-agent-skill-store.ts b/web/src/stores/use-agent-skill-store.ts index 9810679..ff3a6e2 100644 --- a/web/src/stores/use-agent-skill-store.ts +++ b/web/src/stores/use-agent-skill-store.ts @@ -1,6 +1,6 @@ import { create } from "zustand"; -import { fetchCodexSkills, type AgentSkillSummary } from "@/services/api/canvas-agent"; +import { fetchCodexSkills, type AgentSkillDraft, type AgentSkillSummary } from "@/services/api/canvas-agent"; import { useAgentStore } from "@/stores/use-agent-store"; let loadSequence = 0; @@ -11,12 +11,16 @@ type AgentSkillStore = { selectionRevision: number; connectionRevision: number; autoPrompt: string; + draft: AgentSkillDraft | null; + generatingSource: "conversation" | "canvas" | null; loading: boolean; loaded: boolean; errors: string[]; loadSkills: (endpoint: string, token: string, forceReload?: boolean) => Promise; selectSkill: (skill: AgentSkillSummary | null) => void; clearSelection: (expectedRevision?: number) => void; + setDraft: (draft: AgentSkillDraft | null) => void; + setGeneratingSource: (source: "conversation" | "canvas" | null) => void; reset: () => void; }; @@ -26,6 +30,8 @@ export const useAgentSkillStore = create((set, get) => ({ selectionRevision: 0, connectionRevision: 0, autoPrompt: "", + draft: null, + generatingSource: null, loading: false, loaded: false, errors: [], @@ -60,11 +66,13 @@ export const useAgentSkillStore = create((set, get) => ({ replaceAutoPrompt(current.autoPrompt, "", false); set({ selectedSkill: null, autoPrompt: "", selectionRevision: current.selectionRevision + 1 }); }, + setDraft: (draft) => set({ draft }), + setGeneratingSource: (generatingSource) => set({ generatingSource }), reset: () => { loadSequence += 1; const current = get(); replaceAutoPrompt(current.autoPrompt, "", false); - set({ skills: [], selectedSkill: null, selectionRevision: current.selectionRevision + 1, connectionRevision: current.connectionRevision + 1, autoPrompt: "", loading: false, loaded: false, errors: [] }); + set({ skills: [], selectedSkill: null, selectionRevision: current.selectionRevision + 1, connectionRevision: current.connectionRevision + 1, autoPrompt: "", draft: null, generatingSource: null, loading: false, loaded: false, errors: [] }); }, }));