@lsamien/yaovi 0.1.3 → 0.1.4
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/build-info.json +15 -4
- package/desktop/main.mjs +1 -1
- package/dist/assets/{AboutDialog-CnNkIRo8.js → AboutDialog-CDpS-zAu.js} +2 -2
- package/dist/assets/{AboutDialog-CnNkIRo8.js.map → AboutDialog-CDpS-zAu.js.map} +1 -1
- package/dist/assets/{BotAutomationsView-J9Y7Unoq.js → BotAutomationsView-RXBQzdfc.js} +2 -2
- package/dist/assets/{BotAutomationsView-J9Y7Unoq.js.map → BotAutomationsView-RXBQzdfc.js.map} +1 -1
- package/dist/assets/{BotControlSettingsPanel-CtXD24FZ.js → BotControlSettingsPanel-GQEVHXbw.js} +2 -2
- package/dist/assets/{BotControlSettingsPanel-CtXD24FZ.js.map → BotControlSettingsPanel-GQEVHXbw.js.map} +1 -1
- package/dist/assets/BotPluginsDialog-HIhXqyGn.js +2 -0
- package/dist/assets/{BotPluginsDialog-qRw8dCbX.js.map → BotPluginsDialog-HIhXqyGn.js.map} +1 -1
- package/dist/assets/{BotSettingsPanel-DbCMOfAW.js → BotSettingsPanel-Bkqal3lR.js} +3 -3
- package/dist/assets/{BotSettingsPanel-DbCMOfAW.js.map → BotSettingsPanel-Bkqal3lR.js.map} +1 -1
- package/dist/assets/{ComputerPanel-CI-jDmC3.js → ComputerPanel-Dt73pgTG.js} +2 -2
- package/dist/assets/{ComputerPanel-CI-jDmC3.js.map → ComputerPanel-Dt73pgTG.js.map} +1 -1
- package/dist/assets/{ComputerViewerView-B76D6Fln.js → ComputerViewerView-ChYgaS9q.js} +2 -2
- package/dist/assets/{ComputerViewerView-B76D6Fln.js.map → ComputerViewerView-ChYgaS9q.js.map} +1 -1
- package/dist/assets/{ConversationsView-DsuaLKva.js → ConversationsView-DzcDJZh3.js} +88 -88
- package/dist/assets/ConversationsView-DzcDJZh3.js.map +1 -0
- package/dist/assets/ConversationsView-EdF5AM1T.css +1 -0
- package/dist/assets/{LocalVmSettingsPanel-BfXFdMYF.js → LocalVmSettingsPanel-DLbDb5Tq.js} +4 -4
- package/dist/assets/{LocalVmSettingsPanel-BfXFdMYF.js.map → LocalVmSettingsPanel-DLbDb5Tq.js.map} +1 -1
- package/dist/assets/{ModelNetworkChoice-DB6cLOBw.js → ModelNetworkChoice-Cfyt4Unl.js} +2 -2
- package/dist/assets/{ModelNetworkChoice-DB6cLOBw.js.map → ModelNetworkChoice-Cfyt4Unl.js.map} +1 -1
- package/dist/assets/{StandaloneDialog-BWUp2Wxo.js → StandaloneDialog-Cte829Ai.js} +2 -2
- package/dist/assets/{StandaloneDialog-BWUp2Wxo.js.map → StandaloneDialog-Cte829Ai.js.map} +1 -1
- package/dist/assets/{UpdateCheckDialog-CpZSPlUI.js → UpdateCheckDialog-BaMo_M4n.js} +2 -2
- package/dist/assets/{UpdateCheckDialog-CpZSPlUI.js.map → UpdateCheckDialog-BaMo_M4n.js.map} +1 -1
- package/dist/assets/{UsageView-BhiRCw7p.js → UsageView-CklMPG4n.js} +3 -3
- package/dist/assets/{UsageView-BhiRCw7p.js.map → UsageView-CklMPG4n.js.map} +1 -1
- package/dist/assets/WorkspaceKnowledgePanel-BdbhD3rO.js +3 -0
- package/dist/assets/{WorkspaceKnowledgePanel-D34YxglF.js.map → WorkspaceKnowledgePanel-BdbhD3rO.js.map} +1 -1
- package/dist/assets/WorkspaceRoutinesPanel-VpRoMe1x.js +2 -0
- package/dist/assets/{WorkspaceRoutinesPanel-Bn-W52Im.js.map → WorkspaceRoutinesPanel-VpRoMe1x.js.map} +1 -1
- package/dist/assets/{WorkstreamsView-CH6qDPvj.js → WorkstreamsView-BwZpuuhs.js} +2 -2
- package/dist/assets/{WorkstreamsView-CH6qDPvj.js.map → WorkstreamsView-BwZpuuhs.js.map} +1 -1
- package/dist/assets/index-C-5Gq82f.css +1 -0
- package/dist/assets/{index-DjhQ-RMG.js → index-Cx1SdzRY.js} +18 -18
- package/dist/assets/index-Cx1SdzRY.js.map +1 -0
- package/dist/assets/{pdf-wqiF06Qz.js → pdf-CoNh_Rmj.js} +2 -2
- package/dist/assets/{pdf-wqiF06Qz.js.map → pdf-CoNh_Rmj.js.map} +1 -1
- package/dist/index.html +2 -2
- package/dist-server/server/remoteAccess/dispatcher.js +5 -1
- package/node_modules/@yaovi/remote-core/index.mjs +13 -10
- package/node_modules/@yaovi/remote-core/package.json +2 -2
- package/node_modules/@yaovi/remote-core/types/remote-core/src/control/recovery.d.ts +1 -1
- package/node_modules/@yaovi/remote-core/types/remote-core/src/control/remote.d.ts +2 -0
- package/node_modules/@yaovi/remote-core/types/remote-core/src/index.d.ts +1 -0
- package/node_modules/@yaovi/remote-core/types/remote-core/src/transport/lan.d.ts +10 -4
- package/node_modules/@yaovi/remote-core/types/remote-core/src/transport/packet.d.ts +2 -2
- package/package.json +2 -2
- package/release.json +3 -3
- package/vendor/yaovi-remote-core-0.2.0-remote.18.tgz +0 -0
- package/dist/assets/BotPluginsDialog-qRw8dCbX.js +0 -2
- package/dist/assets/ConversationsView-DoA0eNiz.css +0 -1
- package/dist/assets/ConversationsView-DsuaLKva.js.map +0 -1
- package/dist/assets/WorkspaceKnowledgePanel-D34YxglF.js +0 -3
- package/dist/assets/WorkspaceRoutinesPanel-Bn-W52Im.js +0 -2
- package/dist/assets/index-DjhQ-RMG.js.map +0 -1
- package/dist/assets/index-DrIgDgnN.css +0 -1
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"WorkstreamsView-CH6qDPvj.js","names":[],"sources":["../../src/client/components/workspace/WorkstreamsPanel.vue","../../src/client/components/workspace/WorkstreamsPanel.vue","../../src/client/views/WorkstreamsView.vue","../../src/client/views/WorkstreamsView.vue"],"sourcesContent":["<script setup lang=\"ts\">\nimport { computed, nextTick, onBeforeUnmount, ref, watch } from 'vue'\nimport { apiRequest, ApiError } from '@/api/client'\nimport { createUuid } from '@/utils/id'\nimport { backgroundTaskLabel, backgroundTaskRequirementLabel, canStopBackgroundTask, canRetryBackgroundTask, type BackgroundTaskCommandRequest } from './backgroundTaskPresentation'\nimport type { BackgroundTask } from '@shared/backgroundTasks'\nimport { backgroundTaskTerminal } from '@shared/backgroundTasks'\n\nconst props = withDefaults(defineProps<{\n tasks: BackgroundTask[]\n conversationId?: string\n agentId?: string\n agents?: Array<{ id: string; name: string }>\n groupByBot?: boolean\n embedded?: boolean\n orchestration?: boolean\n commandCache?: Map<string, BackgroundTaskCommandRequest>\n /** Opened from a chat message: show it even when finished, scroll to it and expand its result. */\n focusTaskId?: string\n}>(), { groupByBot: false, embedded: false })\nconst emit = defineEmits<{ close: []; changed: [task: BackgroundTask] }>()\nconst filter = ref<'active' | 'all'>('active')\nconst busyId = ref(''), error = ref('')\nconst additions = ref<Record<string, string>>({})\nconst replacements = ref<Record<string, string>>({}), priorities = ref<Record<string, number>>({})\nconst pendingCommands = props.commandCache ?? new Map<string, BackgroundTaskCommandRequest>()\nconst root = ref<HTMLElement>()\nlet focused = ''\nwatch(() => [props.focusTaskId, props.tasks.some(task => task.id === props.focusTaskId)] as const, async ([id, present]) => {\n if (!id || !present || focused === id) return\n focused = id\n if (backgroundTaskTerminal(props.tasks.find(task => task.id === id)!.status)) filter.value = 'all'\n await nextTick()\n root.value?.querySelector(`[data-task-id=\"${id.replace(/[\"\\\\]/g, \"\")}\"]`)?.scrollIntoView?.({ block: 'center' })\n}, { immediate: true })\nlet disposed = false\nonBeforeUnmount(() => { disposed = true })\nconst priorityOptions = [{ value: -2, label: '最低' }, { value: -1, label: '较低' }, { value: 0, label: '普通' }, { value: 1, label: '较高' }, { value: 2, label: '最高' }]\nconst hasPending = (taskId: string, kind: string, payload: BackgroundTaskCommandRequest['payload']) => pendingCommands.has(JSON.stringify([taskId, kind, payload]))\nconst rows = computed(() => {\n const list = props.tasks\n .filter(task => !props.conversationId || task.originConversationId === props.conversationId)\n .filter(task => !props.agentId || task.agentId === props.agentId)\n .filter(task => filter.value === 'all' || !backgroundTaskTerminal(task.status))\n .sort((a, b) => b.updatedAt - a.updatedAt)\n return list\n})\nconst groups = computed(() => {\n if (props.groupByBot) {\n const roster = props.agents ?? []\n const known = new Set(roster.map(agent => agent.id))\n const sections = roster.map(agent => ({\n title: agent.name,\n tasks: rows.value.filter(task => task.agentId === agent.id),\n })).filter(group => group.tasks.length)\n const orphans = rows.value.filter(task => !known.has(task.agentId))\n if (orphans.length) {\n const byName = new Map<string, BackgroundTask[]>()\n for (const task of orphans) {\n const key = task.executorName || task.agentId || '其他'\n const list = byName.get(key) ?? []\n list.push(task)\n byName.set(key, list)\n }\n for (const [title, tasks] of byName) sections.push({ title, tasks })\n }\n return sections\n }\n return [\n { title: '并行工作流', tasks: rows.value.filter(task => task.kind === 'workstream') },\n { title: '其他后台任务', tasks: rows.value.filter(task => task.kind !== 'workstream') },\n ].filter(group => group.tasks.length)\n})\n\nasync function act(task: BackgroundTask, action: 'stop' | 'retry' | 'append' | 'replace' | 'priority') {\n if (busyId.value) return\n const text = additions.value[task.id]?.trim() ?? ''\n const instructions = (replacements.value[task.id] ?? task.instructions).trim()\n const priority = priorities.value[task.id] ?? task.priority ?? 0\n if (action === 'append' && !text) return\n if (action === 'replace' && (!instructions || (instructions === task.instructions.trim() && !hasPending(task.id, 'replace_spec', { instructions })))) return\n if (['replace', 'priority'].includes(action) && (!props.orchestration || task.orchestrationVersion !== 2)) return\n busyId.value = task.id; error.value = ''\n const kind = { append: 'append_requirement', stop: 'cancel', retry: 'resume', replace: 'replace_spec', priority: 'set_priority' }[action]\n const payload: BackgroundTaskCommandRequest['payload'] = action === 'append' ? { text } : action === 'replace' ? { instructions } : action === 'priority' ? { priority } : {}\n const commandKey = JSON.stringify([task.id, kind, payload])\n const accept = (result: { task: BackgroundTask }) => {\n pendingCommands.delete(commandKey)\n // A newer replacement supersedes any older uncertain request of that kind.\n if (action === 'replace' || action === 'priority') for (const [key, command] of pendingCommands) {\n if (JSON.parse(key)[0] === task.id && command.kind === kind) pendingCommands.delete(key)\n }\n if (disposed) return\n emit('changed', result.task)\n if (action === 'append') additions.value[task.id] = ''\n if (action === 'replace') delete replacements.value[task.id]\n if (action === 'priority') delete priorities.value[task.id]\n error.value = ''\n }\n try {\n let result: { task: BackgroundTask }\n if (props.orchestration) {\n const command = pendingCommands.get(commandKey) ?? {\n requestId: createUuid(), kind,\n ...(['append', 'replace'].includes(action) ? { expectedSpecRevision: task.specRevision ?? task.revision } : {}),\n ...(task.controlEpoch !== undefined ? { expectedControlEpoch: task.controlEpoch } : {}),\n payload,\n }\n pendingCommands.set(commandKey, command)\n result = await apiRequest<{ task: BackgroundTask }>(`/api/app/background-tasks/${encodeURIComponent(task.id)}/commands`, { method: 'POST', body: command })\n } else {\n result = await apiRequest<{ task: BackgroundTask }>(`/api/app/background-tasks/${encodeURIComponent(task.id)}/${action === 'append' ? 'update' : action}`, {\n method: 'POST', body: { expectedRevision: task.revision, ...(action === 'append' ? { instructions: `${task.instructions}\\n\\n补充要求:\\n${text}` } : {}) },\n })\n }\n accept(result)\n } catch (cause) {\n if (cause instanceof ApiError && cause.status >= 400 && cause.status < 500 && ![408, 429].includes(cause.status)) pendingCommands.delete(commandKey)\n if (disposed) return\n error.value = cause instanceof Error ? cause.message : '操作失败'\n // Reconcile a conflict or a lost receipt without retrying the mutation.\n try {\n const result = await apiRequest<{ task: BackgroundTask }>(`/api/app/background-tasks/${encodeURIComponent(task.id)}`)\n if (disposed) return\n const requestId = pendingCommands.get(commandKey)?.requestId\n const receipt = result.task.commands?.find(command => command.id === requestId)\n if (receipt && receipt.state !== 'rejected') accept(result)\n else emit('changed', result.task)\n } catch { /* Preserve the command and draft while offline. */ }\n } finally { busyId.value = '' }\n}\n</script>\n<template>\n <aside ref=\"root\" class=\"workstreams\" :class=\"{ 'workstreams--embedded': embedded }\" aria-label=\"工作流面板\">\n <header v-if=\"!embedded\">\n <div>\n <h2>工作流</h2>\n <p>任务独立执行,完成或需要你处理时会回到聊天。停止聊天回复不会停止这些任务。</p>\n </div>\n <button type=\"button\" class=\"icon-close\" aria-label=\"关闭\" @click=\"emit('close')\">×</button>\n </header>\n <div class=\"filters\" role=\"tablist\">\n <button type=\"button\" :aria-pressed=\"filter==='active'\" @click=\"filter='active'\">进行中</button>\n <button type=\"button\" :aria-pressed=\"filter==='all'\" @click=\"filter='all'\">全部</button>\n </div>\n <p v-if=\"error\" role=\"alert\">{{ error }}</p>\n <section v-for=\"group in groups\" :key=\"group.title\">\n <h3>{{ group.title }} · {{ group.tasks.length }}</h3>\n <article v-for=\"task in group.tasks\" :key=\"task.id\" :data-task-id=\"task.id\" :class=\"{ 'task--focused': task.id === focusTaskId }\">\n <div class=\"task-content\">\n <strong>{{ task.title }}</strong>\n <small>{{ backgroundTaskLabel(task) }}</small>\n <small v-if=\"backgroundTaskRequirementLabel(task)\" role=\"status\">{{ backgroundTaskRequirementLabel(task) }}</small>\n <details v-if=\"task.result || task.error\" :open=\"task.id === focusTaskId\">\n <summary>{{ task.error ? (task.result ? '查看结果与问题' : '查看问题') : '查看完整结果' }}</summary>\n <p v-if=\"task.error\" style=\"white-space:pre-wrap;overflow-wrap:anywhere\">{{ task.error }}</p>\n <p v-if=\"task.status === 'failed' && task.phase === 'review' && task.result\">已完成的执行结果(未通过复核):</p>\n <p v-if=\"task.result\" style=\"white-space:pre-wrap;overflow-wrap:anywhere\">{{ task.result }}</p>\n </details>\n <details v-if=\"canStopBackgroundTask(task)\" class=\"requirement-editor\">\n <summary>补充要求</summary>\n <form data-command=\"append\" @submit.prevent=\"act(task, 'append')\">\n <textarea v-model=\"additions[task.id]\" :aria-label=\"`补充 ${task.title} 的要求`\" :disabled=\"!!busyId\" rows=\"3\" placeholder=\"补充目标、约束或验收要求\" />\n <small>{{ orchestration && task.orchestrationVersion === 2 ? '补充先保存,在执行的安全边界采纳。' : '此任务沿用旧执行协议:确认旧执行停止后,再按补充要求继续。' }}</small>\n <button type=\"submit\" :disabled=\"!!busyId || !additions[task.id]?.trim()\">提交补充</button>\n </form>\n </details>\n <details v-if=\"orchestration && task.orchestrationVersion === 2 && canStopBackgroundTask(task)\" class=\"requirement-editor\">\n <summary>修改目标</summary>\n <form data-command=\"replace\" @submit.prevent=\"act(task, 'replace')\">\n <textarea :value=\"replacements[task.id] ?? task.instructions\" :aria-label=\"`修改 ${task.title} 的目标`\" :disabled=\"!!busyId\" rows=\"4\" @input=\"replacements[task.id] = ($event.target as HTMLTextAreaElement).value\" />\n <small>替换当前目标。系统会确认旧执行停止,再按新目标继续;已发生的操作不会自动撤销。</small>\n <button type=\"submit\" :disabled=\"!!busyId || !(replacements[task.id] ?? task.instructions).trim() || ((replacements[task.id] ?? task.instructions).trim() === task.instructions.trim() && !hasPending(task.id, 'replace_spec', { instructions: (replacements[task.id] ?? task.instructions).trim() }))\">保存新目标</button>\n </form>\n </details>\n <details v-if=\"orchestration && task.orchestrationVersion === 2 && canStopBackgroundTask(task)\" class=\"requirement-editor\">\n <summary>优先级 · {{ priorityOptions.find(option => option.value === (task.priority ?? 0))?.label }}</summary>\n <form data-command=\"priority\" @submit.prevent=\"act(task, 'priority')\">\n <select :value=\"priorities[task.id] ?? task.priority ?? 0\" :aria-label=\"`${task.title} 的优先级`\" :disabled=\"!!busyId\" @change=\"priorities[task.id] = Number(($event.target as HTMLSelectElement).value)\">\n <option v-for=\"option in priorityOptions\" :key=\"option.value\" :value=\"option.value\">{{ option.label }}</option>\n </select>\n <small>影响后续排队,不打断正在执行的操作。</small>\n <button type=\"submit\" :disabled=\"!!busyId || ((priorities[task.id] ?? task.priority ?? 0) === (task.priority ?? 0) && !hasPending(task.id, 'set_priority', { priority: priorities[task.id] ?? task.priority ?? 0 }))\">调整优先级</button>\n </form>\n </details>\n </div>\n <div class=\"actions\">\n <button v-if=\"canStopBackgroundTask(task)\" type=\"button\" :disabled=\"!!busyId\" @click=\"act(task,'stop')\">停止</button>\n <button v-if=\"canRetryBackgroundTask(task)\" type=\"button\" :disabled=\"!!busyId\" @click=\"act(task,'retry')\">{{ orchestration ? '继续' : '重试' }}</button>\n </div>\n </article>\n </section>\n <p v-if=\"!rows.length\" class=\"empty\">暂无符合条件的任务。主 Bot 聊天中的重活会自动出现在这里。</p>\n </aside>\n</template>\n<style scoped>\n.task--focused { outline: 2px solid color-mix(in srgb, var(--accent) 45%, transparent); outline-offset: 2px; border-radius: 12px; }\n.workstreams{display:flex;flex-direction:column;gap:12px;width:100%;min-width:0;height:100%;padding:16px;border-left:1px solid var(--line);background:var(--surface);color:var(--text-primary);overflow:auto;box-sizing:border-box}\n.workstreams--embedded{border-left:0;padding:0;background:transparent}\nheader{display:flex;align-items:flex-start;justify-content:space-between;gap:12px}\nheader h2{margin:0;font-size:16px}header p{margin:4px 0 0;color:var(--text-secondary);font-size:12px;line-height:1.5}\n.icon-close{width:36px;height:36px;border:0;border-radius:8px;background:transparent;font-size:22px;cursor:pointer;color:var(--text-secondary)}\n.icon-close:hover{background:var(--surface-hover)}\n.filters{display:flex;gap:8px}\n.filters button{min-height:36px;padding:0 12px;border:1px solid var(--line);border-radius:999px;background:var(--surface);color:var(--text-secondary);font:inherit;cursor:pointer}\n.filters button[aria-pressed=true]{border-color:var(--accent);color:var(--text-primary);background:color-mix(in srgb,var(--accent) 12%,var(--surface))}\nsection{display:grid;gap:10px}section h3{margin:8px 0 0;font-size:12px;font-weight:600;color:var(--text-muted);text-transform:none}\narticle{display:flex;gap:12px;align-items:flex-start;justify-content:space-between;padding:12px;border:1px solid var(--line);border-radius:12px;background:var(--surface-raised)}\narticle strong{display:block;font-size:13px}article small{display:block;margin-top:4px;color:var(--text-secondary);font-size:12px}\narticle p{margin:8px 0 0;color:var(--text-secondary);font-size:12px;line-height:1.5;white-space:pre-wrap}\n.task-content{min-width:0;flex:1}.requirement-editor{margin-top:10px}.requirement-editor form{display:grid;gap:8px;margin-top:8px}.requirement-editor textarea,.requirement-editor select{width:100%;box-sizing:border-box;resize:vertical;border:1px solid var(--line);border-radius:8px;padding:8px;background:var(--surface);color:var(--text-primary);font:inherit}.requirement-editor button{justify-self:start;min-height:32px;border:1px solid var(--line);border-radius:8px;background:var(--surface);color:var(--text-primary);padding:0 10px;cursor:pointer}\n.actions{display:grid;gap:6px} .actions button{min-height:32px;padding:0 10px;border:1px solid var(--line);border-radius:8px;background:var(--surface);font:inherit;font-size:12px;cursor:pointer}\n.actions button:hover{background:var(--surface-hover)}.empty{margin:24px 0;color:var(--text-muted);font-size:13px;line-height:1.6;text-align:center}\n[role=alert]{color:var(--danger);font-size:12px}\n@media(max-width:900px){.workstreams{position:fixed;inset:0 0 0 auto;width:min(400px,100vw);max-width:100%;z-index:35;box-shadow:-20px 0 60px #0002}}\n</style>\n","<script setup lang=\"ts\">\nimport { computed, nextTick, onBeforeUnmount, ref, watch } from 'vue'\nimport { apiRequest, ApiError } from '@/api/client'\nimport { createUuid } from '@/utils/id'\nimport { backgroundTaskLabel, backgroundTaskRequirementLabel, canStopBackgroundTask, canRetryBackgroundTask, type BackgroundTaskCommandRequest } from './backgroundTaskPresentation'\nimport type { BackgroundTask } from '@shared/backgroundTasks'\nimport { backgroundTaskTerminal } from '@shared/backgroundTasks'\n\nconst props = withDefaults(defineProps<{\n tasks: BackgroundTask[]\n conversationId?: string\n agentId?: string\n agents?: Array<{ id: string; name: string }>\n groupByBot?: boolean\n embedded?: boolean\n orchestration?: boolean\n commandCache?: Map<string, BackgroundTaskCommandRequest>\n /** Opened from a chat message: show it even when finished, scroll to it and expand its result. */\n focusTaskId?: string\n}>(), { groupByBot: false, embedded: false })\nconst emit = defineEmits<{ close: []; changed: [task: BackgroundTask] }>()\nconst filter = ref<'active' | 'all'>('active')\nconst busyId = ref(''), error = ref('')\nconst additions = ref<Record<string, string>>({})\nconst replacements = ref<Record<string, string>>({}), priorities = ref<Record<string, number>>({})\nconst pendingCommands = props.commandCache ?? new Map<string, BackgroundTaskCommandRequest>()\nconst root = ref<HTMLElement>()\nlet focused = ''\nwatch(() => [props.focusTaskId, props.tasks.some(task => task.id === props.focusTaskId)] as const, async ([id, present]) => {\n if (!id || !present || focused === id) return\n focused = id\n if (backgroundTaskTerminal(props.tasks.find(task => task.id === id)!.status)) filter.value = 'all'\n await nextTick()\n root.value?.querySelector(`[data-task-id=\"${id.replace(/[\"\\\\]/g, \"\")}\"]`)?.scrollIntoView?.({ block: 'center' })\n}, { immediate: true })\nlet disposed = false\nonBeforeUnmount(() => { disposed = true })\nconst priorityOptions = [{ value: -2, label: '最低' }, { value: -1, label: '较低' }, { value: 0, label: '普通' }, { value: 1, label: '较高' }, { value: 2, label: '最高' }]\nconst hasPending = (taskId: string, kind: string, payload: BackgroundTaskCommandRequest['payload']) => pendingCommands.has(JSON.stringify([taskId, kind, payload]))\nconst rows = computed(() => {\n const list = props.tasks\n .filter(task => !props.conversationId || task.originConversationId === props.conversationId)\n .filter(task => !props.agentId || task.agentId === props.agentId)\n .filter(task => filter.value === 'all' || !backgroundTaskTerminal(task.status))\n .sort((a, b) => b.updatedAt - a.updatedAt)\n return list\n})\nconst groups = computed(() => {\n if (props.groupByBot) {\n const roster = props.agents ?? []\n const known = new Set(roster.map(agent => agent.id))\n const sections = roster.map(agent => ({\n title: agent.name,\n tasks: rows.value.filter(task => task.agentId === agent.id),\n })).filter(group => group.tasks.length)\n const orphans = rows.value.filter(task => !known.has(task.agentId))\n if (orphans.length) {\n const byName = new Map<string, BackgroundTask[]>()\n for (const task of orphans) {\n const key = task.executorName || task.agentId || '其他'\n const list = byName.get(key) ?? []\n list.push(task)\n byName.set(key, list)\n }\n for (const [title, tasks] of byName) sections.push({ title, tasks })\n }\n return sections\n }\n return [\n { title: '并行工作流', tasks: rows.value.filter(task => task.kind === 'workstream') },\n { title: '其他后台任务', tasks: rows.value.filter(task => task.kind !== 'workstream') },\n ].filter(group => group.tasks.length)\n})\n\nasync function act(task: BackgroundTask, action: 'stop' | 'retry' | 'append' | 'replace' | 'priority') {\n if (busyId.value) return\n const text = additions.value[task.id]?.trim() ?? ''\n const instructions = (replacements.value[task.id] ?? task.instructions).trim()\n const priority = priorities.value[task.id] ?? task.priority ?? 0\n if (action === 'append' && !text) return\n if (action === 'replace' && (!instructions || (instructions === task.instructions.trim() && !hasPending(task.id, 'replace_spec', { instructions })))) return\n if (['replace', 'priority'].includes(action) && (!props.orchestration || task.orchestrationVersion !== 2)) return\n busyId.value = task.id; error.value = ''\n const kind = { append: 'append_requirement', stop: 'cancel', retry: 'resume', replace: 'replace_spec', priority: 'set_priority' }[action]\n const payload: BackgroundTaskCommandRequest['payload'] = action === 'append' ? { text } : action === 'replace' ? { instructions } : action === 'priority' ? { priority } : {}\n const commandKey = JSON.stringify([task.id, kind, payload])\n const accept = (result: { task: BackgroundTask }) => {\n pendingCommands.delete(commandKey)\n // A newer replacement supersedes any older uncertain request of that kind.\n if (action === 'replace' || action === 'priority') for (const [key, command] of pendingCommands) {\n if (JSON.parse(key)[0] === task.id && command.kind === kind) pendingCommands.delete(key)\n }\n if (disposed) return\n emit('changed', result.task)\n if (action === 'append') additions.value[task.id] = ''\n if (action === 'replace') delete replacements.value[task.id]\n if (action === 'priority') delete priorities.value[task.id]\n error.value = ''\n }\n try {\n let result: { task: BackgroundTask }\n if (props.orchestration) {\n const command = pendingCommands.get(commandKey) ?? {\n requestId: createUuid(), kind,\n ...(['append', 'replace'].includes(action) ? { expectedSpecRevision: task.specRevision ?? task.revision } : {}),\n ...(task.controlEpoch !== undefined ? { expectedControlEpoch: task.controlEpoch } : {}),\n payload,\n }\n pendingCommands.set(commandKey, command)\n result = await apiRequest<{ task: BackgroundTask }>(`/api/app/background-tasks/${encodeURIComponent(task.id)}/commands`, { method: 'POST', body: command })\n } else {\n result = await apiRequest<{ task: BackgroundTask }>(`/api/app/background-tasks/${encodeURIComponent(task.id)}/${action === 'append' ? 'update' : action}`, {\n method: 'POST', body: { expectedRevision: task.revision, ...(action === 'append' ? { instructions: `${task.instructions}\\n\\n补充要求:\\n${text}` } : {}) },\n })\n }\n accept(result)\n } catch (cause) {\n if (cause instanceof ApiError && cause.status >= 400 && cause.status < 500 && ![408, 429].includes(cause.status)) pendingCommands.delete(commandKey)\n if (disposed) return\n error.value = cause instanceof Error ? cause.message : '操作失败'\n // Reconcile a conflict or a lost receipt without retrying the mutation.\n try {\n const result = await apiRequest<{ task: BackgroundTask }>(`/api/app/background-tasks/${encodeURIComponent(task.id)}`)\n if (disposed) return\n const requestId = pendingCommands.get(commandKey)?.requestId\n const receipt = result.task.commands?.find(command => command.id === requestId)\n if (receipt && receipt.state !== 'rejected') accept(result)\n else emit('changed', result.task)\n } catch { /* Preserve the command and draft while offline. */ }\n } finally { busyId.value = '' }\n}\n</script>\n<template>\n <aside ref=\"root\" class=\"workstreams\" :class=\"{ 'workstreams--embedded': embedded }\" aria-label=\"工作流面板\">\n <header v-if=\"!embedded\">\n <div>\n <h2>工作流</h2>\n <p>任务独立执行,完成或需要你处理时会回到聊天。停止聊天回复不会停止这些任务。</p>\n </div>\n <button type=\"button\" class=\"icon-close\" aria-label=\"关闭\" @click=\"emit('close')\">×</button>\n </header>\n <div class=\"filters\" role=\"tablist\">\n <button type=\"button\" :aria-pressed=\"filter==='active'\" @click=\"filter='active'\">进行中</button>\n <button type=\"button\" :aria-pressed=\"filter==='all'\" @click=\"filter='all'\">全部</button>\n </div>\n <p v-if=\"error\" role=\"alert\">{{ error }}</p>\n <section v-for=\"group in groups\" :key=\"group.title\">\n <h3>{{ group.title }} · {{ group.tasks.length }}</h3>\n <article v-for=\"task in group.tasks\" :key=\"task.id\" :data-task-id=\"task.id\" :class=\"{ 'task--focused': task.id === focusTaskId }\">\n <div class=\"task-content\">\n <strong>{{ task.title }}</strong>\n <small>{{ backgroundTaskLabel(task) }}</small>\n <small v-if=\"backgroundTaskRequirementLabel(task)\" role=\"status\">{{ backgroundTaskRequirementLabel(task) }}</small>\n <details v-if=\"task.result || task.error\" :open=\"task.id === focusTaskId\">\n <summary>{{ task.error ? (task.result ? '查看结果与问题' : '查看问题') : '查看完整结果' }}</summary>\n <p v-if=\"task.error\" style=\"white-space:pre-wrap;overflow-wrap:anywhere\">{{ task.error }}</p>\n <p v-if=\"task.status === 'failed' && task.phase === 'review' && task.result\">已完成的执行结果(未通过复核):</p>\n <p v-if=\"task.result\" style=\"white-space:pre-wrap;overflow-wrap:anywhere\">{{ task.result }}</p>\n </details>\n <details v-if=\"canStopBackgroundTask(task)\" class=\"requirement-editor\">\n <summary>补充要求</summary>\n <form data-command=\"append\" @submit.prevent=\"act(task, 'append')\">\n <textarea v-model=\"additions[task.id]\" :aria-label=\"`补充 ${task.title} 的要求`\" :disabled=\"!!busyId\" rows=\"3\" placeholder=\"补充目标、约束或验收要求\" />\n <small>{{ orchestration && task.orchestrationVersion === 2 ? '补充先保存,在执行的安全边界采纳。' : '此任务沿用旧执行协议:确认旧执行停止后,再按补充要求继续。' }}</small>\n <button type=\"submit\" :disabled=\"!!busyId || !additions[task.id]?.trim()\">提交补充</button>\n </form>\n </details>\n <details v-if=\"orchestration && task.orchestrationVersion === 2 && canStopBackgroundTask(task)\" class=\"requirement-editor\">\n <summary>修改目标</summary>\n <form data-command=\"replace\" @submit.prevent=\"act(task, 'replace')\">\n <textarea :value=\"replacements[task.id] ?? task.instructions\" :aria-label=\"`修改 ${task.title} 的目标`\" :disabled=\"!!busyId\" rows=\"4\" @input=\"replacements[task.id] = ($event.target as HTMLTextAreaElement).value\" />\n <small>替换当前目标。系统会确认旧执行停止,再按新目标继续;已发生的操作不会自动撤销。</small>\n <button type=\"submit\" :disabled=\"!!busyId || !(replacements[task.id] ?? task.instructions).trim() || ((replacements[task.id] ?? task.instructions).trim() === task.instructions.trim() && !hasPending(task.id, 'replace_spec', { instructions: (replacements[task.id] ?? task.instructions).trim() }))\">保存新目标</button>\n </form>\n </details>\n <details v-if=\"orchestration && task.orchestrationVersion === 2 && canStopBackgroundTask(task)\" class=\"requirement-editor\">\n <summary>优先级 · {{ priorityOptions.find(option => option.value === (task.priority ?? 0))?.label }}</summary>\n <form data-command=\"priority\" @submit.prevent=\"act(task, 'priority')\">\n <select :value=\"priorities[task.id] ?? task.priority ?? 0\" :aria-label=\"`${task.title} 的优先级`\" :disabled=\"!!busyId\" @change=\"priorities[task.id] = Number(($event.target as HTMLSelectElement).value)\">\n <option v-for=\"option in priorityOptions\" :key=\"option.value\" :value=\"option.value\">{{ option.label }}</option>\n </select>\n <small>影响后续排队,不打断正在执行的操作。</small>\n <button type=\"submit\" :disabled=\"!!busyId || ((priorities[task.id] ?? task.priority ?? 0) === (task.priority ?? 0) && !hasPending(task.id, 'set_priority', { priority: priorities[task.id] ?? task.priority ?? 0 }))\">调整优先级</button>\n </form>\n </details>\n </div>\n <div class=\"actions\">\n <button v-if=\"canStopBackgroundTask(task)\" type=\"button\" :disabled=\"!!busyId\" @click=\"act(task,'stop')\">停止</button>\n <button v-if=\"canRetryBackgroundTask(task)\" type=\"button\" :disabled=\"!!busyId\" @click=\"act(task,'retry')\">{{ orchestration ? '继续' : '重试' }}</button>\n </div>\n </article>\n </section>\n <p v-if=\"!rows.length\" class=\"empty\">暂无符合条件的任务。主 Bot 聊天中的重活会自动出现在这里。</p>\n </aside>\n</template>\n<style scoped>\n.task--focused { outline: 2px solid color-mix(in srgb, var(--accent) 45%, transparent); outline-offset: 2px; border-radius: 12px; }\n.workstreams{display:flex;flex-direction:column;gap:12px;width:100%;min-width:0;height:100%;padding:16px;border-left:1px solid var(--line);background:var(--surface);color:var(--text-primary);overflow:auto;box-sizing:border-box}\n.workstreams--embedded{border-left:0;padding:0;background:transparent}\nheader{display:flex;align-items:flex-start;justify-content:space-between;gap:12px}\nheader h2{margin:0;font-size:16px}header p{margin:4px 0 0;color:var(--text-secondary);font-size:12px;line-height:1.5}\n.icon-close{width:36px;height:36px;border:0;border-radius:8px;background:transparent;font-size:22px;cursor:pointer;color:var(--text-secondary)}\n.icon-close:hover{background:var(--surface-hover)}\n.filters{display:flex;gap:8px}\n.filters button{min-height:36px;padding:0 12px;border:1px solid var(--line);border-radius:999px;background:var(--surface);color:var(--text-secondary);font:inherit;cursor:pointer}\n.filters button[aria-pressed=true]{border-color:var(--accent);color:var(--text-primary);background:color-mix(in srgb,var(--accent) 12%,var(--surface))}\nsection{display:grid;gap:10px}section h3{margin:8px 0 0;font-size:12px;font-weight:600;color:var(--text-muted);text-transform:none}\narticle{display:flex;gap:12px;align-items:flex-start;justify-content:space-between;padding:12px;border:1px solid var(--line);border-radius:12px;background:var(--surface-raised)}\narticle strong{display:block;font-size:13px}article small{display:block;margin-top:4px;color:var(--text-secondary);font-size:12px}\narticle p{margin:8px 0 0;color:var(--text-secondary);font-size:12px;line-height:1.5;white-space:pre-wrap}\n.task-content{min-width:0;flex:1}.requirement-editor{margin-top:10px}.requirement-editor form{display:grid;gap:8px;margin-top:8px}.requirement-editor textarea,.requirement-editor select{width:100%;box-sizing:border-box;resize:vertical;border:1px solid var(--line);border-radius:8px;padding:8px;background:var(--surface);color:var(--text-primary);font:inherit}.requirement-editor button{justify-self:start;min-height:32px;border:1px solid var(--line);border-radius:8px;background:var(--surface);color:var(--text-primary);padding:0 10px;cursor:pointer}\n.actions{display:grid;gap:6px} .actions button{min-height:32px;padding:0 10px;border:1px solid var(--line);border-radius:8px;background:var(--surface);font:inherit;font-size:12px;cursor:pointer}\n.actions button:hover{background:var(--surface-hover)}.empty{margin:24px 0;color:var(--text-muted);font-size:13px;line-height:1.6;text-align:center}\n[role=alert]{color:var(--danger);font-size:12px}\n@media(max-width:900px){.workstreams{position:fixed;inset:0 0 0 auto;width:min(400px,100vw);max-width:100%;z-index:35;box-shadow:-20px 0 60px #0002}}\n</style>\n","<script setup lang=\"ts\">\nimport { computed, onBeforeUnmount, onMounted, ref } from 'vue'\nimport { useRoute, useRouter } from 'vue-router'\nimport { apiRequest } from '@/api/client'\nimport AppIcon from '@/components/common/AppIcon.vue'\nimport AgentAvatar from '@/components/common/AgentAvatar.vue'\nimport WorkstreamsPanel from '@/components/workspace/WorkstreamsPanel.vue'\nimport { mergeBackgroundTasks, type BackgroundTaskCommandRequest } from '@/components/workspace/backgroundTaskPresentation'\nimport type { BackgroundTask } from '@shared/backgroundTasks'\ntype Bot = { id: string; name: string; avatar: string }\nconst router = useRouter(), route = useRoute()\nconst agents = ref<Bot[]>([]), tasks = ref<BackgroundTask[]>([])\nconst loading = ref(true), error = ref(''), botFilter = ref('all'), botQuery = ref('')\nconst features = ref<string[]>([])\nconst pendingCommands = new Map<string, BackgroundTaskCommandRequest>()\nconst backPath = computed(() => typeof route.query.from === 'string' && /^\\/conversations(?:\\/[a-f0-9-]{36})?(?:\\?taskId=[a-f0-9-]{36})?$/i.test(route.query.from) ? route.query.from : '/conversations')\nconst orchestration = computed(() => features.value.includes('task-orchestration-v2'))\n/** `?task=<id>` from a chat message opens that task's details (specs/022 FR-007). */\nconst focusTaskId = computed(() => typeof route.query.task === 'string' && /^[a-f0-9-]{36}$/i.test(route.query.task) ? route.query.task : undefined)\nconst backgroundAvailable = computed(() => features.value.includes('background-tasks-v1'))\nconst sidebarBots = computed(() => agents.value.filter(a => a.name.toLowerCase().includes(botQuery.value.trim().toLowerCase())))\nconst visibleTasks = computed(() => botFilter.value === 'all' ? tasks.value : tasks.value.filter(t => t.agentId === botFilter.value))\nconst taskCount = (id: string) => tasks.value.filter(t => t.agentId === id).length\n\nlet generation = 0, closed = false, timer: ReturnType<typeof setTimeout> | undefined\nasync function load() {\n const current = ++generation\n try {\n const [caps, roster] = await Promise.all([\n apiRequest<{ features: string[] }>('/api/app/capabilities'),\n apiRequest<{ agents: Array<Bot & { archived?: boolean }> }>('/api/app/agents'),\n ])\n if (closed || current !== generation) return\n features.value = caps.features\n agents.value = roster.agents.filter(a => !a.archived)\n if (caps.features.includes('background-tasks-v1')) {\n const list = await apiRequest<{ tasks: BackgroundTask[] }>('/api/app/background-tasks')\n if (closed || current !== generation) return\n tasks.value = mergeBackgroundTasks(tasks.value, list.tasks)\n } else {\n tasks.value = []\n }\n if (botFilter.value !== 'all' && !agents.value.some(a => a.id === botFilter.value)) botFilter.value = 'all'\n error.value = ''\n } catch (e) {\n if (!closed && current === generation) error.value = e instanceof Error ? e.message : '无法读取工作流'\n } finally {\n if (!closed && current === generation) loading.value = false\n }\n}\nasync function poll() {\n if (!document.hidden) await load()\n if (!closed) timer = setTimeout(poll, 5000)\n}\nfunction onChanged(task: BackgroundTask) {\n tasks.value = mergeBackgroundTasks(tasks.value, [task])\n}\nonMounted(poll)\nonBeforeUnmount(() => { closed = true; generation++; clearTimeout(timer) })\n</script>\n\n<template>\n <main class=\"workstreams-page\" aria-label=\"工作流\">\n <header class=\"workstreams-header\">\n <button class=\"icon-button\" type=\"button\" aria-label=\"返回聊天\" @click=\"router.push(backPath)\"><AppIcon name=\"chevron-left\" :size=\"20\" /></button>\n <AppIcon name=\"board\" :size=\"23\" /><h1>工作流</h1>\n <p class=\"workstreams-lead\">按 Bot 查看并行任务。停止聊天回复不会停止这些任务。</p>\n <button class=\"icon-button\" type=\"button\" aria-label=\"刷新工作流\" @click=\"load\"><AppIcon name=\"refresh\" :size=\"17\" /></button>\n </header>\n <div class=\"workstreams-body\">\n <aside class=\"workstreams-sidebar\" aria-label=\"按 Bot 筛选\">\n <h2>我的 Bot <span>{{ agents.length }}</span></h2>\n <input v-model=\"botQuery\" type=\"search\" aria-label=\"搜索 Bot\" placeholder=\"搜索 Bot\" />\n <nav aria-label=\"按 Bot 筛选\">\n <button type=\"button\" :aria-current=\"botFilter === 'all' ? 'true' : undefined\" @click=\"botFilter = 'all'\">\n <AppIcon name=\"users\" :size=\"22\" />全部 Bot<span>{{ tasks.length }}</span>\n </button>\n <button v-for=\"bot in sidebarBots\" :key=\"bot.id\" type=\"button\" :aria-current=\"botFilter === bot.id ? 'true' : undefined\" @click=\"botFilter = bot.id\">\n <AgentAvatar :name=\"bot.name\" :avatar=\"bot.avatar\" :size=\"26\" /><strong>{{ bot.name }}</strong><span>{{ taskCount(bot.id) }}</span>\n </button>\n </nav>\n <p v-if=\"!agents.length\">创建 Bot 后,主 Bot 聊天中的重活会按 Bot 出现在这里。</p>\n </aside>\n <section class=\"workstreams-content\" aria-live=\"polite\">\n <p v-if=\"error\" class=\"page-error\" role=\"alert\">{{ error }}</p>\n <p v-else-if=\"loading && !tasks.length\" class=\"loading\" role=\"status\">正在读取工作流…</p>\n <p v-else-if=\"!backgroundAvailable && !loading\" class=\"empty\">当前环境尚未开启后台工作流。</p>\n <WorkstreamsPanel\n v-else\n embedded\n :tasks=\"visibleTasks\"\n :agents=\"agents\"\n :group-by-bot=\"botFilter === 'all'\"\n :agent-id=\"botFilter === 'all' ? undefined : botFilter\"\n :orchestration=\"orchestration\"\n :command-cache=\"pendingCommands\"\n :focus-task-id=\"focusTaskId\"\n @changed=\"onChanged\"\n />\n </section>\n </div>\n </main>\n</template>\n\n<style scoped>\n.workstreams-page{height:100dvh;display:flex;flex-direction:column;background:var(--canvas);color:var(--text-primary);min-width:0}\n.workstreams-header{display:flex;align-items:center;gap:12px;min-height:72px;box-sizing:border-box;padding:12px 24px;border-bottom:1px solid var(--line);background:var(--surface);flex-wrap:wrap}\n.workstreams-header h1{font-size:20px;margin:0;letter-spacing:-.02em}\n.workstreams-lead{margin:0;margin-right:auto;font-size:12px;color:var(--text-secondary);line-height:1.5;max-width:420px}\n.workstreams-page button,.workstreams-page input{font:inherit;font-size:13px;color:inherit;border:1px solid var(--line);border-radius:9px;background:var(--surface);padding:8px 11px;min-height:40px;box-sizing:border-box}\n.workstreams-page button{cursor:pointer;display:inline-flex;align-items:center;justify-content:center;gap:7px}\n.workstreams-page button:hover{background:var(--surface-hover)}\n.workstreams-page :is(button,input):focus-visible{outline:2px solid var(--accent);outline-offset:2px}\n.icon-button{padding:8px;width:40px;flex-shrink:0}\n.workstreams-body{display:flex;flex:1;min-height:0}\n.workstreams-sidebar{width:224px;flex-shrink:0;box-sizing:border-box;padding:24px 16px;border-right:1px solid var(--line);background:var(--surface);display:flex;flex-direction:column;gap:12px;overflow:auto}\n.workstreams-sidebar h2{display:flex;justify-content:space-between;font-size:13px;margin:0 2px}\n.workstreams-sidebar h2 span{color:var(--text-muted);font-weight:400}\n.workstreams-sidebar input{width:100%;min-width:0}\n.workstreams-sidebar nav{display:grid;gap:4px}\n.workstreams-sidebar nav button{width:100%;justify-content:flex-start;border:0;text-align:left;min-width:0}\n.workstreams-sidebar nav button[aria-current]{background:var(--surface-soft)}\n.workstreams-sidebar nav strong{font-size:13px;font-weight:500;overflow:hidden;text-overflow:ellipsis;white-space:nowrap}\n.workstreams-sidebar nav span{margin-left:auto;font-size:12px;color:var(--text-muted)}\n.workstreams-sidebar>p{font-size:12px;color:var(--text-secondary);line-height:1.7}\n.workstreams-content{flex:1;min-width:0;overflow:auto;padding:20px 24px}\n.page-error{color:var(--danger);padding:12px;border-radius:9px;background:var(--surface);font-size:13px}\n.loading,.empty{padding:50px 20px;text-align:center;color:var(--text-secondary);font-size:13px;line-height:1.8}\n@media(max-width:760px){\n .workstreams-sidebar{display:none}\n .workstreams-header{padding:10px 12px}\n .workstreams-lead{order:3;width:100%;max-width:none}\n .workstreams-content{padding:16px 12px}\n}\n</style>\n","<script setup lang=\"ts\">\nimport { computed, onBeforeUnmount, onMounted, ref } from 'vue'\nimport { useRoute, useRouter } from 'vue-router'\nimport { apiRequest } from '@/api/client'\nimport AppIcon from '@/components/common/AppIcon.vue'\nimport AgentAvatar from '@/components/common/AgentAvatar.vue'\nimport WorkstreamsPanel from '@/components/workspace/WorkstreamsPanel.vue'\nimport { mergeBackgroundTasks, type BackgroundTaskCommandRequest } from '@/components/workspace/backgroundTaskPresentation'\nimport type { BackgroundTask } from '@shared/backgroundTasks'\ntype Bot = { id: string; name: string; avatar: string }\nconst router = useRouter(), route = useRoute()\nconst agents = ref<Bot[]>([]), tasks = ref<BackgroundTask[]>([])\nconst loading = ref(true), error = ref(''), botFilter = ref('all'), botQuery = ref('')\nconst features = ref<string[]>([])\nconst pendingCommands = new Map<string, BackgroundTaskCommandRequest>()\nconst backPath = computed(() => typeof route.query.from === 'string' && /^\\/conversations(?:\\/[a-f0-9-]{36})?(?:\\?taskId=[a-f0-9-]{36})?$/i.test(route.query.from) ? route.query.from : '/conversations')\nconst orchestration = computed(() => features.value.includes('task-orchestration-v2'))\n/** `?task=<id>` from a chat message opens that task's details (specs/022 FR-007). */\nconst focusTaskId = computed(() => typeof route.query.task === 'string' && /^[a-f0-9-]{36}$/i.test(route.query.task) ? route.query.task : undefined)\nconst backgroundAvailable = computed(() => features.value.includes('background-tasks-v1'))\nconst sidebarBots = computed(() => agents.value.filter(a => a.name.toLowerCase().includes(botQuery.value.trim().toLowerCase())))\nconst visibleTasks = computed(() => botFilter.value === 'all' ? tasks.value : tasks.value.filter(t => t.agentId === botFilter.value))\nconst taskCount = (id: string) => tasks.value.filter(t => t.agentId === id).length\n\nlet generation = 0, closed = false, timer: ReturnType<typeof setTimeout> | undefined\nasync function load() {\n const current = ++generation\n try {\n const [caps, roster] = await Promise.all([\n apiRequest<{ features: string[] }>('/api/app/capabilities'),\n apiRequest<{ agents: Array<Bot & { archived?: boolean }> }>('/api/app/agents'),\n ])\n if (closed || current !== generation) return\n features.value = caps.features\n agents.value = roster.agents.filter(a => !a.archived)\n if (caps.features.includes('background-tasks-v1')) {\n const list = await apiRequest<{ tasks: BackgroundTask[] }>('/api/app/background-tasks')\n if (closed || current !== generation) return\n tasks.value = mergeBackgroundTasks(tasks.value, list.tasks)\n } else {\n tasks.value = []\n }\n if (botFilter.value !== 'all' && !agents.value.some(a => a.id === botFilter.value)) botFilter.value = 'all'\n error.value = ''\n } catch (e) {\n if (!closed && current === generation) error.value = e instanceof Error ? e.message : '无法读取工作流'\n } finally {\n if (!closed && current === generation) loading.value = false\n }\n}\nasync function poll() {\n if (!document.hidden) await load()\n if (!closed) timer = setTimeout(poll, 5000)\n}\nfunction onChanged(task: BackgroundTask) {\n tasks.value = mergeBackgroundTasks(tasks.value, [task])\n}\nonMounted(poll)\nonBeforeUnmount(() => { closed = true; generation++; clearTimeout(timer) })\n</script>\n\n<template>\n <main class=\"workstreams-page\" aria-label=\"工作流\">\n <header class=\"workstreams-header\">\n <button class=\"icon-button\" type=\"button\" aria-label=\"返回聊天\" @click=\"router.push(backPath)\"><AppIcon name=\"chevron-left\" :size=\"20\" /></button>\n <AppIcon name=\"board\" :size=\"23\" /><h1>工作流</h1>\n <p class=\"workstreams-lead\">按 Bot 查看并行任务。停止聊天回复不会停止这些任务。</p>\n <button class=\"icon-button\" type=\"button\" aria-label=\"刷新工作流\" @click=\"load\"><AppIcon name=\"refresh\" :size=\"17\" /></button>\n </header>\n <div class=\"workstreams-body\">\n <aside class=\"workstreams-sidebar\" aria-label=\"按 Bot 筛选\">\n <h2>我的 Bot <span>{{ agents.length }}</span></h2>\n <input v-model=\"botQuery\" type=\"search\" aria-label=\"搜索 Bot\" placeholder=\"搜索 Bot\" />\n <nav aria-label=\"按 Bot 筛选\">\n <button type=\"button\" :aria-current=\"botFilter === 'all' ? 'true' : undefined\" @click=\"botFilter = 'all'\">\n <AppIcon name=\"users\" :size=\"22\" />全部 Bot<span>{{ tasks.length }}</span>\n </button>\n <button v-for=\"bot in sidebarBots\" :key=\"bot.id\" type=\"button\" :aria-current=\"botFilter === bot.id ? 'true' : undefined\" @click=\"botFilter = bot.id\">\n <AgentAvatar :name=\"bot.name\" :avatar=\"bot.avatar\" :size=\"26\" /><strong>{{ bot.name }}</strong><span>{{ taskCount(bot.id) }}</span>\n </button>\n </nav>\n <p v-if=\"!agents.length\">创建 Bot 后,主 Bot 聊天中的重活会按 Bot 出现在这里。</p>\n </aside>\n <section class=\"workstreams-content\" aria-live=\"polite\">\n <p v-if=\"error\" class=\"page-error\" role=\"alert\">{{ error }}</p>\n <p v-else-if=\"loading && !tasks.length\" class=\"loading\" role=\"status\">正在读取工作流…</p>\n <p v-else-if=\"!backgroundAvailable && !loading\" class=\"empty\">当前环境尚未开启后台工作流。</p>\n <WorkstreamsPanel\n v-else\n embedded\n :tasks=\"visibleTasks\"\n :agents=\"agents\"\n :group-by-bot=\"botFilter === 'all'\"\n :agent-id=\"botFilter === 'all' ? undefined : botFilter\"\n :orchestration=\"orchestration\"\n :command-cache=\"pendingCommands\"\n :focus-task-id=\"focusTaskId\"\n @changed=\"onChanged\"\n />\n </section>\n </div>\n </main>\n</template>\n\n<style scoped>\n.workstreams-page{height:100dvh;display:flex;flex-direction:column;background:var(--canvas);color:var(--text-primary);min-width:0}\n.workstreams-header{display:flex;align-items:center;gap:12px;min-height:72px;box-sizing:border-box;padding:12px 24px;border-bottom:1px solid var(--line);background:var(--surface);flex-wrap:wrap}\n.workstreams-header h1{font-size:20px;margin:0;letter-spacing:-.02em}\n.workstreams-lead{margin:0;margin-right:auto;font-size:12px;color:var(--text-secondary);line-height:1.5;max-width:420px}\n.workstreams-page button,.workstreams-page input{font:inherit;font-size:13px;color:inherit;border:1px solid var(--line);border-radius:9px;background:var(--surface);padding:8px 11px;min-height:40px;box-sizing:border-box}\n.workstreams-page button{cursor:pointer;display:inline-flex;align-items:center;justify-content:center;gap:7px}\n.workstreams-page button:hover{background:var(--surface-hover)}\n.workstreams-page :is(button,input):focus-visible{outline:2px solid var(--accent);outline-offset:2px}\n.icon-button{padding:8px;width:40px;flex-shrink:0}\n.workstreams-body{display:flex;flex:1;min-height:0}\n.workstreams-sidebar{width:224px;flex-shrink:0;box-sizing:border-box;padding:24px 16px;border-right:1px solid var(--line);background:var(--surface);display:flex;flex-direction:column;gap:12px;overflow:auto}\n.workstreams-sidebar h2{display:flex;justify-content:space-between;font-size:13px;margin:0 2px}\n.workstreams-sidebar h2 span{color:var(--text-muted);font-weight:400}\n.workstreams-sidebar input{width:100%;min-width:0}\n.workstreams-sidebar nav{display:grid;gap:4px}\n.workstreams-sidebar nav button{width:100%;justify-content:flex-start;border:0;text-align:left;min-width:0}\n.workstreams-sidebar nav button[aria-current]{background:var(--surface-soft)}\n.workstreams-sidebar nav strong{font-size:13px;font-weight:500;overflow:hidden;text-overflow:ellipsis;white-space:nowrap}\n.workstreams-sidebar nav span{margin-left:auto;font-size:12px;color:var(--text-muted)}\n.workstreams-sidebar>p{font-size:12px;color:var(--text-secondary);line-height:1.7}\n.workstreams-content{flex:1;min-width:0;overflow:auto;padding:20px 24px}\n.page-error{color:var(--danger);padding:12px;border-radius:9px;background:var(--surface);font-size:13px}\n.loading,.empty{padding:50px 20px;text-align:center;color:var(--text-secondary);font-size:13px;line-height:1.8}\n@media(max-width:760px){\n .workstreams-sidebar{display:none}\n .workstreams-header{padding:10px 12px}\n .workstreams-lead{order:3;width:100%;max-width:none}\n .workstreams-content{padding:16px 12px}\n}\n</style>\n"],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"WorkstreamsView-BwZpuuhs.js","names":[],"sources":["../../src/client/components/workspace/WorkstreamsPanel.vue","../../src/client/components/workspace/WorkstreamsPanel.vue","../../src/client/views/WorkstreamsView.vue","../../src/client/views/WorkstreamsView.vue"],"sourcesContent":["<script setup lang=\"ts\">\nimport { computed, nextTick, onBeforeUnmount, ref, watch } from 'vue'\nimport { apiRequest, ApiError } from '@/api/client'\nimport { createUuid } from '@/utils/id'\nimport { backgroundTaskLabel, backgroundTaskRequirementLabel, canStopBackgroundTask, canRetryBackgroundTask, type BackgroundTaskCommandRequest } from './backgroundTaskPresentation'\nimport type { BackgroundTask } from '@shared/backgroundTasks'\nimport { backgroundTaskTerminal } from '@shared/backgroundTasks'\n\nconst props = withDefaults(defineProps<{\n tasks: BackgroundTask[]\n conversationId?: string\n agentId?: string\n agents?: Array<{ id: string; name: string }>\n groupByBot?: boolean\n embedded?: boolean\n orchestration?: boolean\n commandCache?: Map<string, BackgroundTaskCommandRequest>\n /** Opened from a chat message: show it even when finished, scroll to it and expand its result. */\n focusTaskId?: string\n}>(), { groupByBot: false, embedded: false })\nconst emit = defineEmits<{ close: []; changed: [task: BackgroundTask] }>()\nconst filter = ref<'active' | 'all'>('active')\nconst busyId = ref(''), error = ref('')\nconst additions = ref<Record<string, string>>({})\nconst replacements = ref<Record<string, string>>({}), priorities = ref<Record<string, number>>({})\nconst pendingCommands = props.commandCache ?? new Map<string, BackgroundTaskCommandRequest>()\nconst root = ref<HTMLElement>()\nlet focused = ''\nwatch(() => [props.focusTaskId, props.tasks.some(task => task.id === props.focusTaskId)] as const, async ([id, present]) => {\n if (!id || !present || focused === id) return\n focused = id\n if (backgroundTaskTerminal(props.tasks.find(task => task.id === id)!.status)) filter.value = 'all'\n await nextTick()\n root.value?.querySelector(`[data-task-id=\"${id.replace(/[\"\\\\]/g, \"\")}\"]`)?.scrollIntoView?.({ block: 'center' })\n}, { immediate: true })\nlet disposed = false\nonBeforeUnmount(() => { disposed = true })\nconst priorityOptions = [{ value: -2, label: '最低' }, { value: -1, label: '较低' }, { value: 0, label: '普通' }, { value: 1, label: '较高' }, { value: 2, label: '最高' }]\nconst hasPending = (taskId: string, kind: string, payload: BackgroundTaskCommandRequest['payload']) => pendingCommands.has(JSON.stringify([taskId, kind, payload]))\nconst rows = computed(() => {\n const list = props.tasks\n .filter(task => !props.conversationId || task.originConversationId === props.conversationId)\n .filter(task => !props.agentId || task.agentId === props.agentId)\n .filter(task => filter.value === 'all' || !backgroundTaskTerminal(task.status))\n .sort((a, b) => b.updatedAt - a.updatedAt)\n return list\n})\nconst groups = computed(() => {\n if (props.groupByBot) {\n const roster = props.agents ?? []\n const known = new Set(roster.map(agent => agent.id))\n const sections = roster.map(agent => ({\n title: agent.name,\n tasks: rows.value.filter(task => task.agentId === agent.id),\n })).filter(group => group.tasks.length)\n const orphans = rows.value.filter(task => !known.has(task.agentId))\n if (orphans.length) {\n const byName = new Map<string, BackgroundTask[]>()\n for (const task of orphans) {\n const key = task.executorName || task.agentId || '其他'\n const list = byName.get(key) ?? []\n list.push(task)\n byName.set(key, list)\n }\n for (const [title, tasks] of byName) sections.push({ title, tasks })\n }\n return sections\n }\n return [\n { title: '并行工作流', tasks: rows.value.filter(task => task.kind === 'workstream') },\n { title: '其他后台任务', tasks: rows.value.filter(task => task.kind !== 'workstream') },\n ].filter(group => group.tasks.length)\n})\n\nasync function act(task: BackgroundTask, action: 'stop' | 'retry' | 'append' | 'replace' | 'priority') {\n if (busyId.value) return\n const text = additions.value[task.id]?.trim() ?? ''\n const instructions = (replacements.value[task.id] ?? task.instructions).trim()\n const priority = priorities.value[task.id] ?? task.priority ?? 0\n if (action === 'append' && !text) return\n if (action === 'replace' && (!instructions || (instructions === task.instructions.trim() && !hasPending(task.id, 'replace_spec', { instructions })))) return\n if (['replace', 'priority'].includes(action) && (!props.orchestration || task.orchestrationVersion !== 2)) return\n busyId.value = task.id; error.value = ''\n const kind = { append: 'append_requirement', stop: 'cancel', retry: 'resume', replace: 'replace_spec', priority: 'set_priority' }[action]\n const payload: BackgroundTaskCommandRequest['payload'] = action === 'append' ? { text } : action === 'replace' ? { instructions } : action === 'priority' ? { priority } : {}\n const commandKey = JSON.stringify([task.id, kind, payload])\n const accept = (result: { task: BackgroundTask }) => {\n pendingCommands.delete(commandKey)\n // A newer replacement supersedes any older uncertain request of that kind.\n if (action === 'replace' || action === 'priority') for (const [key, command] of pendingCommands) {\n if (JSON.parse(key)[0] === task.id && command.kind === kind) pendingCommands.delete(key)\n }\n if (disposed) return\n emit('changed', result.task)\n if (action === 'append') additions.value[task.id] = ''\n if (action === 'replace') delete replacements.value[task.id]\n if (action === 'priority') delete priorities.value[task.id]\n error.value = ''\n }\n try {\n let result: { task: BackgroundTask }\n if (props.orchestration) {\n const command = pendingCommands.get(commandKey) ?? {\n requestId: createUuid(), kind,\n ...(['append', 'replace'].includes(action) ? { expectedSpecRevision: task.specRevision ?? task.revision } : {}),\n ...(task.controlEpoch !== undefined ? { expectedControlEpoch: task.controlEpoch } : {}),\n payload,\n }\n pendingCommands.set(commandKey, command)\n result = await apiRequest<{ task: BackgroundTask }>(`/api/app/background-tasks/${encodeURIComponent(task.id)}/commands`, { method: 'POST', body: command })\n } else {\n result = await apiRequest<{ task: BackgroundTask }>(`/api/app/background-tasks/${encodeURIComponent(task.id)}/${action === 'append' ? 'update' : action}`, {\n method: 'POST', body: { expectedRevision: task.revision, ...(action === 'append' ? { instructions: `${task.instructions}\\n\\n补充要求:\\n${text}` } : {}) },\n })\n }\n accept(result)\n } catch (cause) {\n if (cause instanceof ApiError && cause.status >= 400 && cause.status < 500 && ![408, 429].includes(cause.status)) pendingCommands.delete(commandKey)\n if (disposed) return\n error.value = cause instanceof Error ? cause.message : '操作失败'\n // Reconcile a conflict or a lost receipt without retrying the mutation.\n try {\n const result = await apiRequest<{ task: BackgroundTask }>(`/api/app/background-tasks/${encodeURIComponent(task.id)}`)\n if (disposed) return\n const requestId = pendingCommands.get(commandKey)?.requestId\n const receipt = result.task.commands?.find(command => command.id === requestId)\n if (receipt && receipt.state !== 'rejected') accept(result)\n else emit('changed', result.task)\n } catch { /* Preserve the command and draft while offline. */ }\n } finally { busyId.value = '' }\n}\n</script>\n<template>\n <aside ref=\"root\" class=\"workstreams\" :class=\"{ 'workstreams--embedded': embedded }\" aria-label=\"工作流面板\">\n <header v-if=\"!embedded\">\n <div>\n <h2>工作流</h2>\n <p>任务独立执行,完成或需要你处理时会回到聊天。停止聊天回复不会停止这些任务。</p>\n </div>\n <button type=\"button\" class=\"icon-close\" aria-label=\"关闭\" @click=\"emit('close')\">×</button>\n </header>\n <div class=\"filters\" role=\"tablist\">\n <button type=\"button\" :aria-pressed=\"filter==='active'\" @click=\"filter='active'\">进行中</button>\n <button type=\"button\" :aria-pressed=\"filter==='all'\" @click=\"filter='all'\">全部</button>\n </div>\n <p v-if=\"error\" role=\"alert\">{{ error }}</p>\n <section v-for=\"group in groups\" :key=\"group.title\">\n <h3>{{ group.title }} · {{ group.tasks.length }}</h3>\n <article v-for=\"task in group.tasks\" :key=\"task.id\" :data-task-id=\"task.id\" :class=\"{ 'task--focused': task.id === focusTaskId }\">\n <div class=\"task-content\">\n <strong>{{ task.title }}</strong>\n <small>{{ backgroundTaskLabel(task) }}</small>\n <small v-if=\"backgroundTaskRequirementLabel(task)\" role=\"status\">{{ backgroundTaskRequirementLabel(task) }}</small>\n <details v-if=\"task.result || task.error\" :open=\"task.id === focusTaskId\">\n <summary>{{ task.error ? (task.result ? '查看结果与问题' : '查看问题') : '查看完整结果' }}</summary>\n <p v-if=\"task.error\" style=\"white-space:pre-wrap;overflow-wrap:anywhere\">{{ task.error }}</p>\n <p v-if=\"task.status === 'failed' && task.phase === 'review' && task.result\">已完成的执行结果(未通过复核):</p>\n <p v-if=\"task.result\" style=\"white-space:pre-wrap;overflow-wrap:anywhere\">{{ task.result }}</p>\n </details>\n <details v-if=\"canStopBackgroundTask(task)\" class=\"requirement-editor\">\n <summary>补充要求</summary>\n <form data-command=\"append\" @submit.prevent=\"act(task, 'append')\">\n <textarea v-model=\"additions[task.id]\" :aria-label=\"`补充 ${task.title} 的要求`\" :disabled=\"!!busyId\" rows=\"3\" placeholder=\"补充目标、约束或验收要求\" />\n <small>{{ orchestration && task.orchestrationVersion === 2 ? '补充先保存,在执行的安全边界采纳。' : '此任务沿用旧执行协议:确认旧执行停止后,再按补充要求继续。' }}</small>\n <button type=\"submit\" :disabled=\"!!busyId || !additions[task.id]?.trim()\">提交补充</button>\n </form>\n </details>\n <details v-if=\"orchestration && task.orchestrationVersion === 2 && canStopBackgroundTask(task)\" class=\"requirement-editor\">\n <summary>修改目标</summary>\n <form data-command=\"replace\" @submit.prevent=\"act(task, 'replace')\">\n <textarea :value=\"replacements[task.id] ?? task.instructions\" :aria-label=\"`修改 ${task.title} 的目标`\" :disabled=\"!!busyId\" rows=\"4\" @input=\"replacements[task.id] = ($event.target as HTMLTextAreaElement).value\" />\n <small>替换当前目标。系统会确认旧执行停止,再按新目标继续;已发生的操作不会自动撤销。</small>\n <button type=\"submit\" :disabled=\"!!busyId || !(replacements[task.id] ?? task.instructions).trim() || ((replacements[task.id] ?? task.instructions).trim() === task.instructions.trim() && !hasPending(task.id, 'replace_spec', { instructions: (replacements[task.id] ?? task.instructions).trim() }))\">保存新目标</button>\n </form>\n </details>\n <details v-if=\"orchestration && task.orchestrationVersion === 2 && canStopBackgroundTask(task)\" class=\"requirement-editor\">\n <summary>优先级 · {{ priorityOptions.find(option => option.value === (task.priority ?? 0))?.label }}</summary>\n <form data-command=\"priority\" @submit.prevent=\"act(task, 'priority')\">\n <select :value=\"priorities[task.id] ?? task.priority ?? 0\" :aria-label=\"`${task.title} 的优先级`\" :disabled=\"!!busyId\" @change=\"priorities[task.id] = Number(($event.target as HTMLSelectElement).value)\">\n <option v-for=\"option in priorityOptions\" :key=\"option.value\" :value=\"option.value\">{{ option.label }}</option>\n </select>\n <small>影响后续排队,不打断正在执行的操作。</small>\n <button type=\"submit\" :disabled=\"!!busyId || ((priorities[task.id] ?? task.priority ?? 0) === (task.priority ?? 0) && !hasPending(task.id, 'set_priority', { priority: priorities[task.id] ?? task.priority ?? 0 }))\">调整优先级</button>\n </form>\n </details>\n </div>\n <div class=\"actions\">\n <button v-if=\"canStopBackgroundTask(task)\" type=\"button\" :disabled=\"!!busyId\" @click=\"act(task,'stop')\">停止</button>\n <button v-if=\"canRetryBackgroundTask(task)\" type=\"button\" :disabled=\"!!busyId\" @click=\"act(task,'retry')\">{{ orchestration ? '继续' : '重试' }}</button>\n </div>\n </article>\n </section>\n <p v-if=\"!rows.length\" class=\"empty\">暂无符合条件的任务。主 Bot 聊天中的重活会自动出现在这里。</p>\n </aside>\n</template>\n<style scoped>\n.task--focused { outline: 2px solid color-mix(in srgb, var(--accent) 45%, transparent); outline-offset: 2px; border-radius: 12px; }\n.workstreams{display:flex;flex-direction:column;gap:12px;width:100%;min-width:0;height:100%;padding:16px;border-left:1px solid var(--line);background:var(--surface);color:var(--text-primary);overflow:auto;box-sizing:border-box}\n.workstreams--embedded{border-left:0;padding:0;background:transparent}\nheader{display:flex;align-items:flex-start;justify-content:space-between;gap:12px}\nheader h2{margin:0;font-size:16px}header p{margin:4px 0 0;color:var(--text-secondary);font-size:12px;line-height:1.5}\n.icon-close{width:36px;height:36px;border:0;border-radius:8px;background:transparent;font-size:22px;cursor:pointer;color:var(--text-secondary)}\n.icon-close:hover{background:var(--surface-hover)}\n.filters{display:flex;gap:8px}\n.filters button{min-height:36px;padding:0 12px;border:1px solid var(--line);border-radius:999px;background:var(--surface);color:var(--text-secondary);font:inherit;cursor:pointer}\n.filters button[aria-pressed=true]{border-color:var(--accent);color:var(--text-primary);background:color-mix(in srgb,var(--accent) 12%,var(--surface))}\nsection{display:grid;gap:10px}section h3{margin:8px 0 0;font-size:12px;font-weight:600;color:var(--text-muted);text-transform:none}\narticle{display:flex;gap:12px;align-items:flex-start;justify-content:space-between;padding:12px;border:1px solid var(--line);border-radius:12px;background:var(--surface-raised)}\narticle strong{display:block;font-size:13px}article small{display:block;margin-top:4px;color:var(--text-secondary);font-size:12px}\narticle p{margin:8px 0 0;color:var(--text-secondary);font-size:12px;line-height:1.5;white-space:pre-wrap}\n.task-content{min-width:0;flex:1}.requirement-editor{margin-top:10px}.requirement-editor form{display:grid;gap:8px;margin-top:8px}.requirement-editor textarea,.requirement-editor select{width:100%;box-sizing:border-box;resize:vertical;border:1px solid var(--line);border-radius:8px;padding:8px;background:var(--surface);color:var(--text-primary);font:inherit}.requirement-editor button{justify-self:start;min-height:32px;border:1px solid var(--line);border-radius:8px;background:var(--surface);color:var(--text-primary);padding:0 10px;cursor:pointer}\n.actions{display:grid;gap:6px} .actions button{min-height:32px;padding:0 10px;border:1px solid var(--line);border-radius:8px;background:var(--surface);font:inherit;font-size:12px;cursor:pointer}\n.actions button:hover{background:var(--surface-hover)}.empty{margin:24px 0;color:var(--text-muted);font-size:13px;line-height:1.6;text-align:center}\n[role=alert]{color:var(--danger);font-size:12px}\n@media(max-width:900px){.workstreams{position:fixed;inset:0 0 0 auto;width:min(400px,100vw);max-width:100%;z-index:35;box-shadow:-20px 0 60px #0002}}\n</style>\n","<script setup lang=\"ts\">\nimport { computed, nextTick, onBeforeUnmount, ref, watch } from 'vue'\nimport { apiRequest, ApiError } from '@/api/client'\nimport { createUuid } from '@/utils/id'\nimport { backgroundTaskLabel, backgroundTaskRequirementLabel, canStopBackgroundTask, canRetryBackgroundTask, type BackgroundTaskCommandRequest } from './backgroundTaskPresentation'\nimport type { BackgroundTask } from '@shared/backgroundTasks'\nimport { backgroundTaskTerminal } from '@shared/backgroundTasks'\n\nconst props = withDefaults(defineProps<{\n tasks: BackgroundTask[]\n conversationId?: string\n agentId?: string\n agents?: Array<{ id: string; name: string }>\n groupByBot?: boolean\n embedded?: boolean\n orchestration?: boolean\n commandCache?: Map<string, BackgroundTaskCommandRequest>\n /** Opened from a chat message: show it even when finished, scroll to it and expand its result. */\n focusTaskId?: string\n}>(), { groupByBot: false, embedded: false })\nconst emit = defineEmits<{ close: []; changed: [task: BackgroundTask] }>()\nconst filter = ref<'active' | 'all'>('active')\nconst busyId = ref(''), error = ref('')\nconst additions = ref<Record<string, string>>({})\nconst replacements = ref<Record<string, string>>({}), priorities = ref<Record<string, number>>({})\nconst pendingCommands = props.commandCache ?? new Map<string, BackgroundTaskCommandRequest>()\nconst root = ref<HTMLElement>()\nlet focused = ''\nwatch(() => [props.focusTaskId, props.tasks.some(task => task.id === props.focusTaskId)] as const, async ([id, present]) => {\n if (!id || !present || focused === id) return\n focused = id\n if (backgroundTaskTerminal(props.tasks.find(task => task.id === id)!.status)) filter.value = 'all'\n await nextTick()\n root.value?.querySelector(`[data-task-id=\"${id.replace(/[\"\\\\]/g, \"\")}\"]`)?.scrollIntoView?.({ block: 'center' })\n}, { immediate: true })\nlet disposed = false\nonBeforeUnmount(() => { disposed = true })\nconst priorityOptions = [{ value: -2, label: '最低' }, { value: -1, label: '较低' }, { value: 0, label: '普通' }, { value: 1, label: '较高' }, { value: 2, label: '最高' }]\nconst hasPending = (taskId: string, kind: string, payload: BackgroundTaskCommandRequest['payload']) => pendingCommands.has(JSON.stringify([taskId, kind, payload]))\nconst rows = computed(() => {\n const list = props.tasks\n .filter(task => !props.conversationId || task.originConversationId === props.conversationId)\n .filter(task => !props.agentId || task.agentId === props.agentId)\n .filter(task => filter.value === 'all' || !backgroundTaskTerminal(task.status))\n .sort((a, b) => b.updatedAt - a.updatedAt)\n return list\n})\nconst groups = computed(() => {\n if (props.groupByBot) {\n const roster = props.agents ?? []\n const known = new Set(roster.map(agent => agent.id))\n const sections = roster.map(agent => ({\n title: agent.name,\n tasks: rows.value.filter(task => task.agentId === agent.id),\n })).filter(group => group.tasks.length)\n const orphans = rows.value.filter(task => !known.has(task.agentId))\n if (orphans.length) {\n const byName = new Map<string, BackgroundTask[]>()\n for (const task of orphans) {\n const key = task.executorName || task.agentId || '其他'\n const list = byName.get(key) ?? []\n list.push(task)\n byName.set(key, list)\n }\n for (const [title, tasks] of byName) sections.push({ title, tasks })\n }\n return sections\n }\n return [\n { title: '并行工作流', tasks: rows.value.filter(task => task.kind === 'workstream') },\n { title: '其他后台任务', tasks: rows.value.filter(task => task.kind !== 'workstream') },\n ].filter(group => group.tasks.length)\n})\n\nasync function act(task: BackgroundTask, action: 'stop' | 'retry' | 'append' | 'replace' | 'priority') {\n if (busyId.value) return\n const text = additions.value[task.id]?.trim() ?? ''\n const instructions = (replacements.value[task.id] ?? task.instructions).trim()\n const priority = priorities.value[task.id] ?? task.priority ?? 0\n if (action === 'append' && !text) return\n if (action === 'replace' && (!instructions || (instructions === task.instructions.trim() && !hasPending(task.id, 'replace_spec', { instructions })))) return\n if (['replace', 'priority'].includes(action) && (!props.orchestration || task.orchestrationVersion !== 2)) return\n busyId.value = task.id; error.value = ''\n const kind = { append: 'append_requirement', stop: 'cancel', retry: 'resume', replace: 'replace_spec', priority: 'set_priority' }[action]\n const payload: BackgroundTaskCommandRequest['payload'] = action === 'append' ? { text } : action === 'replace' ? { instructions } : action === 'priority' ? { priority } : {}\n const commandKey = JSON.stringify([task.id, kind, payload])\n const accept = (result: { task: BackgroundTask }) => {\n pendingCommands.delete(commandKey)\n // A newer replacement supersedes any older uncertain request of that kind.\n if (action === 'replace' || action === 'priority') for (const [key, command] of pendingCommands) {\n if (JSON.parse(key)[0] === task.id && command.kind === kind) pendingCommands.delete(key)\n }\n if (disposed) return\n emit('changed', result.task)\n if (action === 'append') additions.value[task.id] = ''\n if (action === 'replace') delete replacements.value[task.id]\n if (action === 'priority') delete priorities.value[task.id]\n error.value = ''\n }\n try {\n let result: { task: BackgroundTask }\n if (props.orchestration) {\n const command = pendingCommands.get(commandKey) ?? {\n requestId: createUuid(), kind,\n ...(['append', 'replace'].includes(action) ? { expectedSpecRevision: task.specRevision ?? task.revision } : {}),\n ...(task.controlEpoch !== undefined ? { expectedControlEpoch: task.controlEpoch } : {}),\n payload,\n }\n pendingCommands.set(commandKey, command)\n result = await apiRequest<{ task: BackgroundTask }>(`/api/app/background-tasks/${encodeURIComponent(task.id)}/commands`, { method: 'POST', body: command })\n } else {\n result = await apiRequest<{ task: BackgroundTask }>(`/api/app/background-tasks/${encodeURIComponent(task.id)}/${action === 'append' ? 'update' : action}`, {\n method: 'POST', body: { expectedRevision: task.revision, ...(action === 'append' ? { instructions: `${task.instructions}\\n\\n补充要求:\\n${text}` } : {}) },\n })\n }\n accept(result)\n } catch (cause) {\n if (cause instanceof ApiError && cause.status >= 400 && cause.status < 500 && ![408, 429].includes(cause.status)) pendingCommands.delete(commandKey)\n if (disposed) return\n error.value = cause instanceof Error ? cause.message : '操作失败'\n // Reconcile a conflict or a lost receipt without retrying the mutation.\n try {\n const result = await apiRequest<{ task: BackgroundTask }>(`/api/app/background-tasks/${encodeURIComponent(task.id)}`)\n if (disposed) return\n const requestId = pendingCommands.get(commandKey)?.requestId\n const receipt = result.task.commands?.find(command => command.id === requestId)\n if (receipt && receipt.state !== 'rejected') accept(result)\n else emit('changed', result.task)\n } catch { /* Preserve the command and draft while offline. */ }\n } finally { busyId.value = '' }\n}\n</script>\n<template>\n <aside ref=\"root\" class=\"workstreams\" :class=\"{ 'workstreams--embedded': embedded }\" aria-label=\"工作流面板\">\n <header v-if=\"!embedded\">\n <div>\n <h2>工作流</h2>\n <p>任务独立执行,完成或需要你处理时会回到聊天。停止聊天回复不会停止这些任务。</p>\n </div>\n <button type=\"button\" class=\"icon-close\" aria-label=\"关闭\" @click=\"emit('close')\">×</button>\n </header>\n <div class=\"filters\" role=\"tablist\">\n <button type=\"button\" :aria-pressed=\"filter==='active'\" @click=\"filter='active'\">进行中</button>\n <button type=\"button\" :aria-pressed=\"filter==='all'\" @click=\"filter='all'\">全部</button>\n </div>\n <p v-if=\"error\" role=\"alert\">{{ error }}</p>\n <section v-for=\"group in groups\" :key=\"group.title\">\n <h3>{{ group.title }} · {{ group.tasks.length }}</h3>\n <article v-for=\"task in group.tasks\" :key=\"task.id\" :data-task-id=\"task.id\" :class=\"{ 'task--focused': task.id === focusTaskId }\">\n <div class=\"task-content\">\n <strong>{{ task.title }}</strong>\n <small>{{ backgroundTaskLabel(task) }}</small>\n <small v-if=\"backgroundTaskRequirementLabel(task)\" role=\"status\">{{ backgroundTaskRequirementLabel(task) }}</small>\n <details v-if=\"task.result || task.error\" :open=\"task.id === focusTaskId\">\n <summary>{{ task.error ? (task.result ? '查看结果与问题' : '查看问题') : '查看完整结果' }}</summary>\n <p v-if=\"task.error\" style=\"white-space:pre-wrap;overflow-wrap:anywhere\">{{ task.error }}</p>\n <p v-if=\"task.status === 'failed' && task.phase === 'review' && task.result\">已完成的执行结果(未通过复核):</p>\n <p v-if=\"task.result\" style=\"white-space:pre-wrap;overflow-wrap:anywhere\">{{ task.result }}</p>\n </details>\n <details v-if=\"canStopBackgroundTask(task)\" class=\"requirement-editor\">\n <summary>补充要求</summary>\n <form data-command=\"append\" @submit.prevent=\"act(task, 'append')\">\n <textarea v-model=\"additions[task.id]\" :aria-label=\"`补充 ${task.title} 的要求`\" :disabled=\"!!busyId\" rows=\"3\" placeholder=\"补充目标、约束或验收要求\" />\n <small>{{ orchestration && task.orchestrationVersion === 2 ? '补充先保存,在执行的安全边界采纳。' : '此任务沿用旧执行协议:确认旧执行停止后,再按补充要求继续。' }}</small>\n <button type=\"submit\" :disabled=\"!!busyId || !additions[task.id]?.trim()\">提交补充</button>\n </form>\n </details>\n <details v-if=\"orchestration && task.orchestrationVersion === 2 && canStopBackgroundTask(task)\" class=\"requirement-editor\">\n <summary>修改目标</summary>\n <form data-command=\"replace\" @submit.prevent=\"act(task, 'replace')\">\n <textarea :value=\"replacements[task.id] ?? task.instructions\" :aria-label=\"`修改 ${task.title} 的目标`\" :disabled=\"!!busyId\" rows=\"4\" @input=\"replacements[task.id] = ($event.target as HTMLTextAreaElement).value\" />\n <small>替换当前目标。系统会确认旧执行停止,再按新目标继续;已发生的操作不会自动撤销。</small>\n <button type=\"submit\" :disabled=\"!!busyId || !(replacements[task.id] ?? task.instructions).trim() || ((replacements[task.id] ?? task.instructions).trim() === task.instructions.trim() && !hasPending(task.id, 'replace_spec', { instructions: (replacements[task.id] ?? task.instructions).trim() }))\">保存新目标</button>\n </form>\n </details>\n <details v-if=\"orchestration && task.orchestrationVersion === 2 && canStopBackgroundTask(task)\" class=\"requirement-editor\">\n <summary>优先级 · {{ priorityOptions.find(option => option.value === (task.priority ?? 0))?.label }}</summary>\n <form data-command=\"priority\" @submit.prevent=\"act(task, 'priority')\">\n <select :value=\"priorities[task.id] ?? task.priority ?? 0\" :aria-label=\"`${task.title} 的优先级`\" :disabled=\"!!busyId\" @change=\"priorities[task.id] = Number(($event.target as HTMLSelectElement).value)\">\n <option v-for=\"option in priorityOptions\" :key=\"option.value\" :value=\"option.value\">{{ option.label }}</option>\n </select>\n <small>影响后续排队,不打断正在执行的操作。</small>\n <button type=\"submit\" :disabled=\"!!busyId || ((priorities[task.id] ?? task.priority ?? 0) === (task.priority ?? 0) && !hasPending(task.id, 'set_priority', { priority: priorities[task.id] ?? task.priority ?? 0 }))\">调整优先级</button>\n </form>\n </details>\n </div>\n <div class=\"actions\">\n <button v-if=\"canStopBackgroundTask(task)\" type=\"button\" :disabled=\"!!busyId\" @click=\"act(task,'stop')\">停止</button>\n <button v-if=\"canRetryBackgroundTask(task)\" type=\"button\" :disabled=\"!!busyId\" @click=\"act(task,'retry')\">{{ orchestration ? '继续' : '重试' }}</button>\n </div>\n </article>\n </section>\n <p v-if=\"!rows.length\" class=\"empty\">暂无符合条件的任务。主 Bot 聊天中的重活会自动出现在这里。</p>\n </aside>\n</template>\n<style scoped>\n.task--focused { outline: 2px solid color-mix(in srgb, var(--accent) 45%, transparent); outline-offset: 2px; border-radius: 12px; }\n.workstreams{display:flex;flex-direction:column;gap:12px;width:100%;min-width:0;height:100%;padding:16px;border-left:1px solid var(--line);background:var(--surface);color:var(--text-primary);overflow:auto;box-sizing:border-box}\n.workstreams--embedded{border-left:0;padding:0;background:transparent}\nheader{display:flex;align-items:flex-start;justify-content:space-between;gap:12px}\nheader h2{margin:0;font-size:16px}header p{margin:4px 0 0;color:var(--text-secondary);font-size:12px;line-height:1.5}\n.icon-close{width:36px;height:36px;border:0;border-radius:8px;background:transparent;font-size:22px;cursor:pointer;color:var(--text-secondary)}\n.icon-close:hover{background:var(--surface-hover)}\n.filters{display:flex;gap:8px}\n.filters button{min-height:36px;padding:0 12px;border:1px solid var(--line);border-radius:999px;background:var(--surface);color:var(--text-secondary);font:inherit;cursor:pointer}\n.filters button[aria-pressed=true]{border-color:var(--accent);color:var(--text-primary);background:color-mix(in srgb,var(--accent) 12%,var(--surface))}\nsection{display:grid;gap:10px}section h3{margin:8px 0 0;font-size:12px;font-weight:600;color:var(--text-muted);text-transform:none}\narticle{display:flex;gap:12px;align-items:flex-start;justify-content:space-between;padding:12px;border:1px solid var(--line);border-radius:12px;background:var(--surface-raised)}\narticle strong{display:block;font-size:13px}article small{display:block;margin-top:4px;color:var(--text-secondary);font-size:12px}\narticle p{margin:8px 0 0;color:var(--text-secondary);font-size:12px;line-height:1.5;white-space:pre-wrap}\n.task-content{min-width:0;flex:1}.requirement-editor{margin-top:10px}.requirement-editor form{display:grid;gap:8px;margin-top:8px}.requirement-editor textarea,.requirement-editor select{width:100%;box-sizing:border-box;resize:vertical;border:1px solid var(--line);border-radius:8px;padding:8px;background:var(--surface);color:var(--text-primary);font:inherit}.requirement-editor button{justify-self:start;min-height:32px;border:1px solid var(--line);border-radius:8px;background:var(--surface);color:var(--text-primary);padding:0 10px;cursor:pointer}\n.actions{display:grid;gap:6px} .actions button{min-height:32px;padding:0 10px;border:1px solid var(--line);border-radius:8px;background:var(--surface);font:inherit;font-size:12px;cursor:pointer}\n.actions button:hover{background:var(--surface-hover)}.empty{margin:24px 0;color:var(--text-muted);font-size:13px;line-height:1.6;text-align:center}\n[role=alert]{color:var(--danger);font-size:12px}\n@media(max-width:900px){.workstreams{position:fixed;inset:0 0 0 auto;width:min(400px,100vw);max-width:100%;z-index:35;box-shadow:-20px 0 60px #0002}}\n</style>\n","<script setup lang=\"ts\">\nimport { computed, onBeforeUnmount, onMounted, ref } from 'vue'\nimport { useRoute, useRouter } from 'vue-router'\nimport { apiRequest } from '@/api/client'\nimport AppIcon from '@/components/common/AppIcon.vue'\nimport AgentAvatar from '@/components/common/AgentAvatar.vue'\nimport WorkstreamsPanel from '@/components/workspace/WorkstreamsPanel.vue'\nimport { mergeBackgroundTasks, type BackgroundTaskCommandRequest } from '@/components/workspace/backgroundTaskPresentation'\nimport type { BackgroundTask } from '@shared/backgroundTasks'\ntype Bot = { id: string; name: string; avatar: string }\nconst router = useRouter(), route = useRoute()\nconst agents = ref<Bot[]>([]), tasks = ref<BackgroundTask[]>([])\nconst loading = ref(true), error = ref(''), botFilter = ref('all'), botQuery = ref('')\nconst features = ref<string[]>([])\nconst pendingCommands = new Map<string, BackgroundTaskCommandRequest>()\nconst backPath = computed(() => typeof route.query.from === 'string' && /^\\/conversations(?:\\/[a-f0-9-]{36})?(?:\\?taskId=[a-f0-9-]{36})?$/i.test(route.query.from) ? route.query.from : '/conversations')\nconst orchestration = computed(() => features.value.includes('task-orchestration-v2'))\n/** `?task=<id>` from a chat message opens that task's details (specs/022 FR-007). */\nconst focusTaskId = computed(() => typeof route.query.task === 'string' && /^[a-f0-9-]{36}$/i.test(route.query.task) ? route.query.task : undefined)\nconst backgroundAvailable = computed(() => features.value.includes('background-tasks-v1'))\nconst sidebarBots = computed(() => agents.value.filter(a => a.name.toLowerCase().includes(botQuery.value.trim().toLowerCase())))\nconst visibleTasks = computed(() => botFilter.value === 'all' ? tasks.value : tasks.value.filter(t => t.agentId === botFilter.value))\nconst taskCount = (id: string) => tasks.value.filter(t => t.agentId === id).length\n\nlet generation = 0, closed = false, timer: ReturnType<typeof setTimeout> | undefined\nasync function load() {\n const current = ++generation\n try {\n const [caps, roster] = await Promise.all([\n apiRequest<{ features: string[] }>('/api/app/capabilities'),\n apiRequest<{ agents: Array<Bot & { archived?: boolean }> }>('/api/app/agents'),\n ])\n if (closed || current !== generation) return\n features.value = caps.features\n agents.value = roster.agents.filter(a => !a.archived)\n if (caps.features.includes('background-tasks-v1')) {\n const list = await apiRequest<{ tasks: BackgroundTask[] }>('/api/app/background-tasks')\n if (closed || current !== generation) return\n tasks.value = mergeBackgroundTasks(tasks.value, list.tasks)\n } else {\n tasks.value = []\n }\n if (botFilter.value !== 'all' && !agents.value.some(a => a.id === botFilter.value)) botFilter.value = 'all'\n error.value = ''\n } catch (e) {\n if (!closed && current === generation) error.value = e instanceof Error ? e.message : '无法读取工作流'\n } finally {\n if (!closed && current === generation) loading.value = false\n }\n}\nasync function poll() {\n if (!document.hidden) await load()\n if (!closed) timer = setTimeout(poll, 5000)\n}\nfunction onChanged(task: BackgroundTask) {\n tasks.value = mergeBackgroundTasks(tasks.value, [task])\n}\nonMounted(poll)\nonBeforeUnmount(() => { closed = true; generation++; clearTimeout(timer) })\n</script>\n\n<template>\n <main class=\"workstreams-page\" aria-label=\"工作流\">\n <header class=\"workstreams-header\">\n <button class=\"icon-button\" type=\"button\" aria-label=\"返回聊天\" @click=\"router.push(backPath)\"><AppIcon name=\"chevron-left\" :size=\"20\" /></button>\n <AppIcon name=\"board\" :size=\"23\" /><h1>工作流</h1>\n <p class=\"workstreams-lead\">按 Bot 查看并行任务。停止聊天回复不会停止这些任务。</p>\n <button class=\"icon-button\" type=\"button\" aria-label=\"刷新工作流\" @click=\"load\"><AppIcon name=\"refresh\" :size=\"17\" /></button>\n </header>\n <div class=\"workstreams-body\">\n <aside class=\"workstreams-sidebar\" aria-label=\"按 Bot 筛选\">\n <h2>我的 Bot <span>{{ agents.length }}</span></h2>\n <input v-model=\"botQuery\" type=\"search\" aria-label=\"搜索 Bot\" placeholder=\"搜索 Bot\" />\n <nav aria-label=\"按 Bot 筛选\">\n <button type=\"button\" :aria-current=\"botFilter === 'all' ? 'true' : undefined\" @click=\"botFilter = 'all'\">\n <AppIcon name=\"users\" :size=\"22\" />全部 Bot<span>{{ tasks.length }}</span>\n </button>\n <button v-for=\"bot in sidebarBots\" :key=\"bot.id\" type=\"button\" :aria-current=\"botFilter === bot.id ? 'true' : undefined\" @click=\"botFilter = bot.id\">\n <AgentAvatar :name=\"bot.name\" :avatar=\"bot.avatar\" :size=\"26\" /><strong>{{ bot.name }}</strong><span>{{ taskCount(bot.id) }}</span>\n </button>\n </nav>\n <p v-if=\"!agents.length\">创建 Bot 后,主 Bot 聊天中的重活会按 Bot 出现在这里。</p>\n </aside>\n <section class=\"workstreams-content\" aria-live=\"polite\">\n <p v-if=\"error\" class=\"page-error\" role=\"alert\">{{ error }}</p>\n <p v-else-if=\"loading && !tasks.length\" class=\"loading\" role=\"status\">正在读取工作流…</p>\n <p v-else-if=\"!backgroundAvailable && !loading\" class=\"empty\">当前环境尚未开启后台工作流。</p>\n <WorkstreamsPanel\n v-else\n embedded\n :tasks=\"visibleTasks\"\n :agents=\"agents\"\n :group-by-bot=\"botFilter === 'all'\"\n :agent-id=\"botFilter === 'all' ? undefined : botFilter\"\n :orchestration=\"orchestration\"\n :command-cache=\"pendingCommands\"\n :focus-task-id=\"focusTaskId\"\n @changed=\"onChanged\"\n />\n </section>\n </div>\n </main>\n</template>\n\n<style scoped>\n.workstreams-page{height:100dvh;display:flex;flex-direction:column;background:var(--canvas);color:var(--text-primary);min-width:0}\n.workstreams-header{display:flex;align-items:center;gap:12px;min-height:72px;box-sizing:border-box;padding:12px 24px;border-bottom:1px solid var(--line);background:var(--surface);flex-wrap:wrap}\n.workstreams-header h1{font-size:20px;margin:0;letter-spacing:-.02em}\n.workstreams-lead{margin:0;margin-right:auto;font-size:12px;color:var(--text-secondary);line-height:1.5;max-width:420px}\n.workstreams-page button,.workstreams-page input{font:inherit;font-size:13px;color:inherit;border:1px solid var(--line);border-radius:9px;background:var(--surface);padding:8px 11px;min-height:40px;box-sizing:border-box}\n.workstreams-page button{cursor:pointer;display:inline-flex;align-items:center;justify-content:center;gap:7px}\n.workstreams-page button:hover{background:var(--surface-hover)}\n.workstreams-page :is(button,input):focus-visible{outline:2px solid var(--accent);outline-offset:2px}\n.icon-button{padding:8px;width:40px;flex-shrink:0}\n.workstreams-body{display:flex;flex:1;min-height:0}\n.workstreams-sidebar{width:224px;flex-shrink:0;box-sizing:border-box;padding:24px 16px;border-right:1px solid var(--line);background:var(--surface);display:flex;flex-direction:column;gap:12px;overflow:auto}\n.workstreams-sidebar h2{display:flex;justify-content:space-between;font-size:13px;margin:0 2px}\n.workstreams-sidebar h2 span{color:var(--text-muted);font-weight:400}\n.workstreams-sidebar input{width:100%;min-width:0}\n.workstreams-sidebar nav{display:grid;gap:4px}\n.workstreams-sidebar nav button{width:100%;justify-content:flex-start;border:0;text-align:left;min-width:0}\n.workstreams-sidebar nav button[aria-current]{background:var(--surface-soft)}\n.workstreams-sidebar nav strong{font-size:13px;font-weight:500;overflow:hidden;text-overflow:ellipsis;white-space:nowrap}\n.workstreams-sidebar nav span{margin-left:auto;font-size:12px;color:var(--text-muted)}\n.workstreams-sidebar>p{font-size:12px;color:var(--text-secondary);line-height:1.7}\n.workstreams-content{flex:1;min-width:0;overflow:auto;padding:20px 24px}\n.page-error{color:var(--danger);padding:12px;border-radius:9px;background:var(--surface);font-size:13px}\n.loading,.empty{padding:50px 20px;text-align:center;color:var(--text-secondary);font-size:13px;line-height:1.8}\n@media(max-width:760px){\n .workstreams-sidebar{display:none}\n .workstreams-header{padding:10px 12px}\n .workstreams-lead{order:3;width:100%;max-width:none}\n .workstreams-content{padding:16px 12px}\n}\n</style>\n","<script setup lang=\"ts\">\nimport { computed, onBeforeUnmount, onMounted, ref } from 'vue'\nimport { useRoute, useRouter } from 'vue-router'\nimport { apiRequest } from '@/api/client'\nimport AppIcon from '@/components/common/AppIcon.vue'\nimport AgentAvatar from '@/components/common/AgentAvatar.vue'\nimport WorkstreamsPanel from '@/components/workspace/WorkstreamsPanel.vue'\nimport { mergeBackgroundTasks, type BackgroundTaskCommandRequest } from '@/components/workspace/backgroundTaskPresentation'\nimport type { BackgroundTask } from '@shared/backgroundTasks'\ntype Bot = { id: string; name: string; avatar: string }\nconst router = useRouter(), route = useRoute()\nconst agents = ref<Bot[]>([]), tasks = ref<BackgroundTask[]>([])\nconst loading = ref(true), error = ref(''), botFilter = ref('all'), botQuery = ref('')\nconst features = ref<string[]>([])\nconst pendingCommands = new Map<string, BackgroundTaskCommandRequest>()\nconst backPath = computed(() => typeof route.query.from === 'string' && /^\\/conversations(?:\\/[a-f0-9-]{36})?(?:\\?taskId=[a-f0-9-]{36})?$/i.test(route.query.from) ? route.query.from : '/conversations')\nconst orchestration = computed(() => features.value.includes('task-orchestration-v2'))\n/** `?task=<id>` from a chat message opens that task's details (specs/022 FR-007). */\nconst focusTaskId = computed(() => typeof route.query.task === 'string' && /^[a-f0-9-]{36}$/i.test(route.query.task) ? route.query.task : undefined)\nconst backgroundAvailable = computed(() => features.value.includes('background-tasks-v1'))\nconst sidebarBots = computed(() => agents.value.filter(a => a.name.toLowerCase().includes(botQuery.value.trim().toLowerCase())))\nconst visibleTasks = computed(() => botFilter.value === 'all' ? tasks.value : tasks.value.filter(t => t.agentId === botFilter.value))\nconst taskCount = (id: string) => tasks.value.filter(t => t.agentId === id).length\n\nlet generation = 0, closed = false, timer: ReturnType<typeof setTimeout> | undefined\nasync function load() {\n const current = ++generation\n try {\n const [caps, roster] = await Promise.all([\n apiRequest<{ features: string[] }>('/api/app/capabilities'),\n apiRequest<{ agents: Array<Bot & { archived?: boolean }> }>('/api/app/agents'),\n ])\n if (closed || current !== generation) return\n features.value = caps.features\n agents.value = roster.agents.filter(a => !a.archived)\n if (caps.features.includes('background-tasks-v1')) {\n const list = await apiRequest<{ tasks: BackgroundTask[] }>('/api/app/background-tasks')\n if (closed || current !== generation) return\n tasks.value = mergeBackgroundTasks(tasks.value, list.tasks)\n } else {\n tasks.value = []\n }\n if (botFilter.value !== 'all' && !agents.value.some(a => a.id === botFilter.value)) botFilter.value = 'all'\n error.value = ''\n } catch (e) {\n if (!closed && current === generation) error.value = e instanceof Error ? e.message : '无法读取工作流'\n } finally {\n if (!closed && current === generation) loading.value = false\n }\n}\nasync function poll() {\n if (!document.hidden) await load()\n if (!closed) timer = setTimeout(poll, 5000)\n}\nfunction onChanged(task: BackgroundTask) {\n tasks.value = mergeBackgroundTasks(tasks.value, [task])\n}\nonMounted(poll)\nonBeforeUnmount(() => { closed = true; generation++; clearTimeout(timer) })\n</script>\n\n<template>\n <main class=\"workstreams-page\" aria-label=\"工作流\">\n <header class=\"workstreams-header\">\n <button class=\"icon-button\" type=\"button\" aria-label=\"返回聊天\" @click=\"router.push(backPath)\"><AppIcon name=\"chevron-left\" :size=\"20\" /></button>\n <AppIcon name=\"board\" :size=\"23\" /><h1>工作流</h1>\n <p class=\"workstreams-lead\">按 Bot 查看并行任务。停止聊天回复不会停止这些任务。</p>\n <button class=\"icon-button\" type=\"button\" aria-label=\"刷新工作流\" @click=\"load\"><AppIcon name=\"refresh\" :size=\"17\" /></button>\n </header>\n <div class=\"workstreams-body\">\n <aside class=\"workstreams-sidebar\" aria-label=\"按 Bot 筛选\">\n <h2>我的 Bot <span>{{ agents.length }}</span></h2>\n <input v-model=\"botQuery\" type=\"search\" aria-label=\"搜索 Bot\" placeholder=\"搜索 Bot\" />\n <nav aria-label=\"按 Bot 筛选\">\n <button type=\"button\" :aria-current=\"botFilter === 'all' ? 'true' : undefined\" @click=\"botFilter = 'all'\">\n <AppIcon name=\"users\" :size=\"22\" />全部 Bot<span>{{ tasks.length }}</span>\n </button>\n <button v-for=\"bot in sidebarBots\" :key=\"bot.id\" type=\"button\" :aria-current=\"botFilter === bot.id ? 'true' : undefined\" @click=\"botFilter = bot.id\">\n <AgentAvatar :name=\"bot.name\" :avatar=\"bot.avatar\" :size=\"26\" /><strong>{{ bot.name }}</strong><span>{{ taskCount(bot.id) }}</span>\n </button>\n </nav>\n <p v-if=\"!agents.length\">创建 Bot 后,主 Bot 聊天中的重活会按 Bot 出现在这里。</p>\n </aside>\n <section class=\"workstreams-content\" aria-live=\"polite\">\n <p v-if=\"error\" class=\"page-error\" role=\"alert\">{{ error }}</p>\n <p v-else-if=\"loading && !tasks.length\" class=\"loading\" role=\"status\">正在读取工作流…</p>\n <p v-else-if=\"!backgroundAvailable && !loading\" class=\"empty\">当前环境尚未开启后台工作流。</p>\n <WorkstreamsPanel\n v-else\n embedded\n :tasks=\"visibleTasks\"\n :agents=\"agents\"\n :group-by-bot=\"botFilter === 'all'\"\n :agent-id=\"botFilter === 'all' ? undefined : botFilter\"\n :orchestration=\"orchestration\"\n :command-cache=\"pendingCommands\"\n :focus-task-id=\"focusTaskId\"\n @changed=\"onChanged\"\n />\n </section>\n </div>\n </main>\n</template>\n\n<style scoped>\n.workstreams-page{height:100dvh;display:flex;flex-direction:column;background:var(--canvas);color:var(--text-primary);min-width:0}\n.workstreams-header{display:flex;align-items:center;gap:12px;min-height:72px;box-sizing:border-box;padding:12px 24px;border-bottom:1px solid var(--line);background:var(--surface);flex-wrap:wrap}\n.workstreams-header h1{font-size:20px;margin:0;letter-spacing:-.02em}\n.workstreams-lead{margin:0;margin-right:auto;font-size:12px;color:var(--text-secondary);line-height:1.5;max-width:420px}\n.workstreams-page button,.workstreams-page input{font:inherit;font-size:13px;color:inherit;border:1px solid var(--line);border-radius:9px;background:var(--surface);padding:8px 11px;min-height:40px;box-sizing:border-box}\n.workstreams-page button{cursor:pointer;display:inline-flex;align-items:center;justify-content:center;gap:7px}\n.workstreams-page button:hover{background:var(--surface-hover)}\n.workstreams-page :is(button,input):focus-visible{outline:2px solid var(--accent);outline-offset:2px}\n.icon-button{padding:8px;width:40px;flex-shrink:0}\n.workstreams-body{display:flex;flex:1;min-height:0}\n.workstreams-sidebar{width:224px;flex-shrink:0;box-sizing:border-box;padding:24px 16px;border-right:1px solid var(--line);background:var(--surface);display:flex;flex-direction:column;gap:12px;overflow:auto}\n.workstreams-sidebar h2{display:flex;justify-content:space-between;font-size:13px;margin:0 2px}\n.workstreams-sidebar h2 span{color:var(--text-muted);font-weight:400}\n.workstreams-sidebar input{width:100%;min-width:0}\n.workstreams-sidebar nav{display:grid;gap:4px}\n.workstreams-sidebar nav button{width:100%;justify-content:flex-start;border:0;text-align:left;min-width:0}\n.workstreams-sidebar nav button[aria-current]{background:var(--surface-soft)}\n.workstreams-sidebar nav strong{font-size:13px;font-weight:500;overflow:hidden;text-overflow:ellipsis;white-space:nowrap}\n.workstreams-sidebar nav span{margin-left:auto;font-size:12px;color:var(--text-muted)}\n.workstreams-sidebar>p{font-size:12px;color:var(--text-secondary);line-height:1.7}\n.workstreams-content{flex:1;min-width:0;overflow:auto;padding:20px 24px}\n.page-error{color:var(--danger);padding:12px;border-radius:9px;background:var(--surface);font-size:13px}\n.loading,.empty{padding:50px 20px;text-align:center;color:var(--text-secondary);font-size:13px;line-height:1.8}\n@media(max-width:760px){\n .workstreams-sidebar{display:none}\n .workstreams-header{padding:10px 12px}\n .workstreams-lead{order:3;width:100%;max-width:none}\n .workstreams-content{padding:16px 12px}\n}\n</style>\n"],"mappings":"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"}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
.remote-receipts[data-v-9497089f]{z-index:110;border:1px solid var(--line);background:var(--surface);max-width:min(460px,100vw - 32px);color:var(--text-primary);border-radius:12px;padding:12px 16px;font-size:13px;position:fixed;top:12px;right:16px;box-shadow:0 6px 24px #0002}.remote-receipts summary[data-v-9497089f]{cursor:pointer}.remote-receipts p[data-v-9497089f]{line-height:1.6}.remote-receipts div[data-v-9497089f]{justify-content:space-between;gap:16px;margin:10px 0;display:flex}.remote-receipts button[data-v-9497089f]{color:inherit;border:1px solid var(--line);cursor:pointer;background:0 0;border-radius:6px;padding:5px 10px}.brand-mark[data-v-b3aaa8b9]{color:var(--text-primary);align-items:center;gap:12px;display:inline-flex}.brand-mark__plate[data-v-b3aaa8b9]{background:#f1f1ef;border-radius:24%;flex:none;place-items:center;display:inline-grid;overflow:hidden}.brand-mark__plate img[data-v-b3aaa8b9]{object-fit:contain;width:100%;height:100%;display:block}.brand-mark__name[data-v-b3aaa8b9]{letter-spacing:-.035em;font-size:20px;font-weight:650}.brand-mark--compact[data-v-b3aaa8b9]{gap:8px}.brand-mark--compact .brand-mark__name[data-v-b3aaa8b9]{font-size:16px}.brand-mark--bare .brand-mark__plate[data-v-b3aaa8b9]{background:0 0;border-radius:0}.loading-screen[data-v-afd31251]{background:var(--canvas);place-content:center;justify-items:center;width:100%;height:100%;display:grid}.loading-line[data-v-afd31251]{color:var(--text-muted);align-items:center;gap:8px;margin-top:22px;font-size:11px;display:flex}.loading-line i[data-v-afd31251]{background:var(--text-primary);border-radius:50%;width:5px;height:5px;animation:1.15s ease-in-out infinite blink-afd31251}@keyframes blink-afd31251{50%{opacity:.2;transform:scale(.72)}}.app-icon[data-v-2a7cf303]{flex:none;display:block}.login-shell[data-v-255c142f]{background:var(--canvas);width:100%;min-height:100%;color:var(--text-primary);grid-template-rows:auto 1fr auto;display:grid;position:relative;overflow-y:auto}.login-bar[data-v-255c142f]{padding:22px 28px 0 calc(28px + var(--desktop-traffic-light-space,0px));justify-content:space-between;align-items:center;display:flex}html[data-desktop-chrome=mac-frameless] .login-bar[data-v-255c142f]{padding-left:96px}.login-brand[data-v-255c142f]{letter-spacing:-.02em;align-items:center;gap:10px;font-size:19px;font-weight:650;display:inline-flex}.login-logo[data-v-255c142f]{object-fit:contain;mix-blend-mode:multiply;width:34px;height:34px}:root.dark .login-logo[data-v-255c142f]{filter:invert();mix-blend-mode:screen}.icon-button[data-v-255c142f]{width:38px;height:38px;color:var(--text-primary);cursor:pointer;background:0 0;border:0;border-radius:10px;place-items:center;display:grid}.icon-button[data-v-255c142f]:hover{background:var(--surface-soft,#7f7f7f1a)}.login-panel[data-v-255c142f]{text-align:center;place-self:center;width:min(400px,100% - 48px);padding:24px 0 8px}.login-hero[data-v-255c142f]{width:84px;height:84px;margin:0 auto 18px;display:block}.login-panel h1[data-v-255c142f]{letter-spacing:-.03em;margin:0;font-size:30px;font-weight:650;line-height:1.25}.subtitle[data-v-255c142f]{color:var(--text-secondary);margin:10px 0 26px;font-size:15px}.login-tabs[data-v-255c142f]{border-bottom:1.5px solid var(--line);margin-bottom:24px;display:flex}.login-tabs button[data-v-255c142f]{height:46px;color:var(--text-muted);font:inherit;cursor:pointer;background:0 0;border:0;flex:1;font-size:15px;position:relative}.login-tabs [aria-selected=true][data-v-255c142f]{color:var(--text-primary);font-weight:650}.login-tabs [aria-selected=true][data-v-255c142f]:after{content:"";background:var(--text-primary);border-radius:2px;height:2.5px;position:absolute;bottom:-1.5px;left:0;right:0}.tab-panel[data-v-255c142f]{align-content:start;gap:16px;min-height:236px;display:grid}.note[data-v-255c142f]{text-align:left;border:1px solid var(--line);color:var(--text-secondary);border-radius:14px;align-items:flex-start;gap:14px;padding:16px 18px;display:flex}.note>span[data-v-255c142f]{gap:4px;display:grid}.note strong[data-v-255c142f]{color:var(--text-primary);font-size:15px;font-weight:600}.note small[data-v-255c142f]{font-size:13px;line-height:1.6}.primary[data-v-255c142f]{background:var(--text-primary);width:100%;height:50px;color:var(--canvas);font:inherit;cursor:pointer;border:0;border-radius:12px;justify-content:center;align-items:center;font-size:16px;font-weight:600;transition:opacity .15s;display:flex;position:relative}.primary svg[data-v-255c142f]{fill:none;stroke:currentColor;stroke-width:2px;stroke-linecap:round;stroke-linejoin:round;width:20px;height:20px;position:absolute;right:18px}.primary[data-v-255c142f]:hover:not(:disabled){opacity:.88}button[data-v-255c142f]:disabled{opacity:.5;cursor:default}.link[data-v-255c142f]{color:var(--text-secondary);font:inherit;text-underline-offset:4px;text-decoration:underline;-webkit-text-decoration-color:color-mix(in srgb, currentColor 35%, transparent);text-decoration-color:color-mix(in srgb, currentColor 35%, transparent);cursor:pointer;background:0 0;border:0;justify-content:center;align-items:center;gap:6px;padding:2px 0;font-size:13px;display:inline-flex}.qr-frame[data-v-255c142f]{border:1px solid var(--line);background:#fff;border-radius:16px;place-items:center;width:220px;height:220px;margin:0 auto;display:grid}.qr-frame img[data-v-255c142f]{border-radius:6px;display:block}.qr-frame--muted[data-v-255c142f]{background:0 0}.qr-state[data-v-255c142f]{max-width:180px;color:var(--text-muted);justify-items:center;gap:10px;font-size:13px;line-height:1.6;display:grid}.qr-steps[data-v-255c142f]{color:var(--text-secondary);justify-content:center;gap:18px;margin:2px 0 0;padding:0;font-size:13px;list-style:none;display:flex}.qr-steps li[data-v-255c142f]{align-items:center;gap:7px;display:inline-flex}.qr-steps b[data-v-255c142f]{background:var(--text-primary);width:18px;height:18px;color:var(--canvas);border-radius:50%;place-items:center;font-size:11px;display:inline-grid}.hint[data-v-255c142f]{color:var(--text-muted);margin:0;font-size:13px;line-height:1.7}.hint a[data-v-255c142f]{color:var(--text-secondary)}.signed-in[data-v-255c142f]{gap:14px;display:grid}.signed-in .banner[data-v-255c142f]{margin:0}.signed-in .actions[data-v-255c142f]{margin-top:4px}.host-list[data-v-255c142f]{text-align:left;gap:10px;display:grid}.host[data-v-255c142f]{text-align:left;border:1px solid var(--line);background:var(--surface);width:100%;min-height:58px;color:var(--text-primary);font:inherit;cursor:pointer;border-radius:14px;grid-template-columns:auto 1fr auto auto;align-items:center;gap:12px;padding:0 14px 0 18px;transition:border-color .15s,transform .15s;display:grid}.host[data-v-255c142f]:hover:not(:disabled){border-color:color-mix(in srgb, var(--text-primary) 35%, transparent)}.host strong[data-v-255c142f]{text-overflow:ellipsis;white-space:nowrap;font-size:15px;font-weight:600;overflow:hidden}.host-state[data-v-255c142f]{color:var(--text-muted);font-size:13px}.dot[data-v-255c142f]{background:var(--text-muted);border-radius:50%;width:9px;height:9px}.dot--on[data-v-255c142f],.dot--lan[data-v-255c142f]{background:var(--success);box-shadow:0 0 0 3px color-mix(in srgb, var(--success) 18%, transparent)}.dot--warn[data-v-255c142f]{background:var(--warning)}.empty[data-v-255c142f]{border:1px dashed var(--line);color:var(--text-muted);border-radius:14px;justify-items:center;gap:8px;padding:28px 22px;display:grid}.empty strong[data-v-255c142f]{color:var(--text-primary);font-size:15px}.empty small[data-v-255c142f]{font-size:13px;line-height:1.6}.actions[data-v-255c142f]{justify-content:center;gap:24px;margin-top:18px;display:flex}.banner[data-v-255c142f]{background:color-mix(in srgb, var(--text-primary) 6%, transparent);color:var(--text-secondary);text-align:left;border-radius:12px;margin:14px 0 0;padding:11px 14px;font-size:13px;line-height:1.6}.banner--error[data-v-255c142f]{background:color-mix(in srgb, var(--danger) 10%, transparent);color:var(--danger)}.login-foot[data-v-255c142f]{color:var(--text-muted);text-align:center;padding:18px 24px 26px;font-size:12px}button[data-v-255c142f]:focus-visible,a[data-v-255c142f]:focus-visible{outline:2px solid var(--text-primary);outline-offset:3px}@media (width<=520px){.login-bar[data-v-255c142f]{padding:16px 16px 0}.login-panel h1[data-v-255c142f]{font-size:26px}.qr-steps[data-v-255c142f]{flex-direction:column;align-items:center;gap:8px}}.agent-avatar[data-v-458f3dde]{background:0 0;flex:none;line-height:1;display:block;position:relative;overflow:visible}.agent-avatar>img[data-v-458f3dde],.agent-avatar>svg[data-v-458f3dde]{object-fit:cover;width:100%;height:100%;display:block}.agent-avatar__placeholder[data-v-458f3dde]{background:var(--surface-soft);width:100%;height:100%;box-shadow:inset 0 0 0 1px var(--line);border-radius:50%;display:block}.agent-avatar>svg[data-v-458f3dde]{pointer-events:none;overflow:visible}.agent-avatar--image>svg[data-v-458f3dde]{position:absolute;inset:0}.main-bot-star[data-v-458f3dde]{z-index:1;color:#fff;pointer-events:none;box-shadow:0 0 0 3px var(--surface,#fff);background:#ff9500;border-radius:50%;justify-content:center;align-items:center;display:flex;position:absolute;bottom:-2px;right:-2px}.main-bot-star[data-v-458f3dde] svg{fill:currentColor;flex:none}.working-dot[data-v-458f3dde]{z-index:1;pointer-events:none;box-shadow:0 0 0 3px var(--surface,#fff);border-radius:50%;position:absolute;bottom:-4px;right:-4px}.team-avatar[data-v-5afded22]{flex:none;display:block;position:relative;overflow:visible}.team-avatar__image[data-v-5afded22]{object-fit:cover;width:100%;height:100%;box-shadow:inset 0 0 0 1px var(--line);border-radius:50%;display:block}.team-avatar__cluster[data-v-5afded22]{display:block;position:absolute;inset:0}.team-avatar__member[data-v-5afded22]{position:absolute}.team-avatar__member--1[data-v-5afded22]{z-index:3;top:18%;left:0}.team-avatar__member--2[data-v-5afded22]{z-index:2;top:0;right:0}.team-avatar__member--3[data-v-5afded22]{z-index:1;bottom:0;right:1%}.working-dot[data-v-5afded22]{z-index:2;pointer-events:none;box-shadow:0 0 0 3px var(--surface,#fff);border-radius:50%;position:absolute;bottom:-4px;right:-4px}.identity-fixture[data-v-45178542]{box-sizing:border-box;background:var(--canvas);min-height:100vh;color:var(--text-primary);padding:72px clamp(28px,7vw,100px)}header[data-v-45178542]{max-width:850px}header>span[data-v-45178542]{color:var(--accent);letter-spacing:.12em;text-transform:uppercase;font-size:13px;font-weight:750}h1[data-v-45178542]{letter-spacing:-.045em;margin:13px 0 12px;font-size:clamp(38px,5vw,68px);line-height:1.02}header p[data-v-45178542]{max-width:720px;color:var(--text-muted);margin:0;font-size:18px;line-height:1.65}.shape-row[data-v-45178542]{grid-template-columns:repeat(3,minmax(0,1fr));gap:28px;max-width:760px;margin-top:60px;display:grid}.shape-row article[data-v-45178542]{border:1px solid var(--line);background:var(--surface-raised);border-radius:22px;place-items:center;gap:16px;min-height:160px;display:grid}.shape-row strong[data-v-45178542]{font-size:14px}.state-list[data-v-45178542]{gap:14px;max-width:760px;margin-top:28px;display:grid}.state-list article[data-v-45178542]{border:1px solid var(--line);background:var(--surface-soft);border-radius:20px;align-items:center;gap:20px;padding:19px 24px;display:flex}.state-list div[data-v-45178542]{gap:5px;display:grid}.state-list strong[data-v-45178542]{font-size:18px}.state-list span[data-v-45178542]{color:var(--text-muted);font-size:13px}@media (width<=620px){.identity-fixture[data-v-45178542]{padding:44px 22px}.shape-row[data-v-45178542]{gap:10px}.shape-row article[data-v-45178542]{min-height:132px}.state-list article[data-v-45178542]{padding:16px}}.group-preview[data-v-45178542]{border:1px solid var(--line);background:var(--surface-raised);border-radius:22px;align-items:center;gap:28px;max-width:712px;margin-top:18px;padding:24px;display:flex}.group-preview>div[data-v-45178542]{gap:6px;display:grid}.group-preview strong[data-v-45178542]{font-size:20px}.group-preview span[data-v-45178542]{color:var(--text-muted);font-size:13px}:root{--lightningcss-light:initial;--lightningcss-dark: ;color-scheme:light;--font-ui:Inter, ui-sans-serif, -apple-system, BlinkMacSystemFont, "SF Pro Text", "PingFang SC", "Microsoft YaHei", sans-serif;--font-code:"SFMono-Regular", Consolas, "Liberation Mono", monospace;--canvas:#fff;--conversation-canvas:#fff;--message-file-surface:#f1f1f1;--surface:#fff;--surface-raised:#fff;--surface-soft:#f2f2ef;--surface-hover:#ecece8;--settings-panel:#f7f7f8;--settings-sidebar:#fafafa;--settings-selected:#eaeaed;--capsule-bg:#f2f2f4;--capsule-bg-hover:#e9e9ec;--settings-nav-bg:#fff;--settings-pane-bg:#fafafa;--settings-card-bg:#fff;--settings-card-line:#19191612;--settings-divider:#19191614;--rail-bg:#151515;--rail-text:#ffffff94;--rail-active:#fff;--text-primary:#1f1f1d;--text-secondary:#656560;--text-muted:#6f6f69;--text-on-solid:#fff;--line:#1919161b;--line-strong:#19191630;--accent:#20201e;--accent-rgb:32, 32, 30;--workflow-accent:#2868d7;--delivery-accent:#245fc5;--workflow-on-accent:#fff;--danger:#b33d33;--success:#377a54;--warning:#9b6b1f;--focus-ring:#1f1f1d29;--chat-composer-bg:#fff;--input-border-color:#1f1f1d1f;--input-border-hover-color:#1f1f1d3d;--input-border-focus-color:#1f1f1d70;--input-placeholder-color:#72726c;--scrim:#12121152;--shadow-float:0 18px 55px #14141221;--radius-sm:9px;--radius-md:13px;--radius-lg:18px;--ease-out:cubic-bezier(.2, .8, .2, 1)}:root.dark{--lightningcss-light: ;--lightningcss-dark:initial;color-scheme:dark;--canvas:#181817;--conversation-canvas:#171a20;--message-file-surface:#262626;--surface:#1d1d1c;--surface-raised:#242423;--surface-soft:#282827;--surface-hover:#30302e;--settings-panel:#2b2b2e;--settings-sidebar:#202022;--settings-selected:#38383b;--capsule-bg:#2a2a2d;--capsule-bg-hover:#343437;--settings-nav-bg:#232325;--settings-pane-bg:#19191a;--settings-card-bg:#262628;--settings-card-line:#fffffa12;--settings-divider:#fffffa14;--rail-bg:#0d0d0d;--rail-text:#ffffff8a;--rail-active:#fff;--text-primary:#efefec;--text-secondary:#b2b2ad;--text-muted:#a0a09a;--text-on-solid:#151514;--line:#fffffa17;--line-strong:#fffffa2b;--accent:#efefec;--accent-rgb:239, 239, 236;--workflow-accent:#78a9ff;--delivery-accent:#78a9ff;--workflow-on-accent:#101b2f;--danger:#ed786f;--success:#77b88e;--warning:#d9ab60;--focus-ring:#ffffff1f;--chat-composer-bg:#242423;--input-border-color:#fffffa1a;--input-border-hover-color:#fffffa33;--input-border-focus-color:#fffffa61;--input-placeholder-color:#a0a09a;--scrim:#0000008f;--shadow-float:0 18px 55px #00000057}*,:before,:after{box-sizing:border-box}html,body,#app{width:100%;min-width:320px;height:100%;margin:0}html{background:var(--canvas)}body{background:var(--canvas);color:var(--text-primary);font-family:var(--font-ui);-webkit-font-smoothing:antialiased;text-rendering:optimizelegibility;font-size:14px;overflow:hidden}button,input,textarea,select{font:inherit}button,a{color:inherit}button:focus-visible,a:focus-visible,input:focus-visible,textarea:focus-visible,[tabindex]:focus-visible{outline:2px solid var(--text-primary);outline-offset:2px}::selection{background:rgba(var(--accent-rgb), .16)}::-webkit-scrollbar{width:10px;height:10px}::-webkit-scrollbar-track{background:0 0}::-webkit-scrollbar-thumb{background:color-mix(in srgb, var(--text-muted) 48%, transparent);background-clip:padding-box;border:3px solid #0000;border-radius:999px;min-height:32px}.sr-only{clip:rect(0, 0, 0, 0)!important;white-space:nowrap!important;border:0!important;width:1px!important;height:1px!important;margin:-1px!important;padding:0!important;position:absolute!important;overflow:hidden!important}.icon-button{width:34px;height:34px;color:var(--text-secondary);cursor:pointer;background:0 0;border:0;border-radius:9px;place-items:center;padding:0;transition:color .14s,background-color .14s,transform .14s;display:inline-grid}.icon-button:hover{color:var(--text-primary);background:var(--surface-hover)}.icon-button:active{transform:scale(.96)}.icon-button:disabled{cursor:not-allowed;opacity:.38}.solid-button,.quiet-button{cursor:pointer;border-radius:10px;justify-content:center;align-items:center;min-height:36px;padding:0 15px;font-weight:580;transition:transform .14s,background-color .14s,border-color .14s;display:inline-flex}.solid-button{border:1px solid var(--accent);background:var(--accent);color:var(--text-on-solid)}.solid-button:hover{transform:translateY(-1px)}.solid-button:disabled{cursor:not-allowed;opacity:.45;transform:none}.quiet-button{border:1px solid var(--line);color:var(--text-secondary);background:0 0}.quiet-button:hover{border-color:var(--line-strong);background:var(--surface-hover);color:var(--text-primary)}.status-dot{background:var(--text-muted);border-radius:50%;width:6px;height:6px;display:inline-block}.status-dot--online{background:var(--success);box-shadow:0 0 0 3px color-mix(in srgb, var(--success) 15%, transparent)}.status-dot--working{background:var(--warning);animation:1.5s ease-in-out infinite status-pulse}@keyframes status-pulse{50%{opacity:.42}}.view-fade-enter-active,.view-fade-leave-active{transition:opacity .14s ease, transform .16s var(--ease-out)}.view-fade-enter-from{opacity:0;transform:translateY(4px)}.view-fade-leave-to{opacity:0;transform:translateY(-2px)}@media (prefers-reduced-motion:reduce){*,:before,:after{scroll-behavior:auto!important;transition-duration:.001ms!important;animation-duration:.001ms!important;animation-iteration-count:1!important}}body.yaovi-column-resizing,body.yaovi-column-resizing *{cursor:col-resize!important;-webkit-user-select:none!important;user-select:none!important}body.yaovi-column-resizing iframe{pointer-events:none!important}:root[data-desktop-chrome=mac-frameless]{--desktop-titlebar-inset:42px;--desktop-traffic-lights-inset:76px}:root[data-desktop-chrome=mac-frameless] body:before{content:"";z-index:-1;height:var(--desktop-titlebar-inset);-webkit-app-region:drag;position:fixed;inset:0 0 auto}:root[data-desktop-chrome=mac-frameless] :is(button,a,input,select,textarea,summary,label,[role=button],[role=menuitem],[role=tab],[role=dialog],[contenteditable=true]){-webkit-app-region:no-drag}:root[data-desktop-chrome=mac-frameless] .desktop-sidebar{--sidebar-search-top:calc(58px + var(--desktop-titlebar-inset));padding-top:var(--desktop-titlebar-inset)}:root[data-desktop-chrome=mac-frameless] .workspace-shell--conversations>.desktop-sidebar{--sidebar-search-top:68px;padding-top:0}:root[data-desktop-chrome=mac-frameless] .workspace-shell--conversations .bot-list-header{padding-left:calc(18px + var(--desktop-traffic-lights-inset));box-sizing:border-box}@media (width<=900px){:root[data-desktop-chrome=mac-frameless] .workspace-shell,:root[data-desktop-chrome=mac-frameless] .workspace-shell--collapsed{grid-template-rows:calc(52px + var(--desktop-titlebar-inset)) minmax(0, 1fr)}:root[data-desktop-chrome=mac-frameless] .workspace-shell--conversations{grid-template-rows:minmax(0,1fr)}:root[data-desktop-chrome=mac-frameless] .mobile-header{padding-top:max(5px, env(safe-area-inset-top));padding-left:calc(10px + var(--desktop-traffic-lights-inset));box-sizing:border-box}:root[data-desktop-chrome=mac-frameless] .mobile-drawer{--sidebar-search-top:calc(58px + var(--desktop-titlebar-inset));padding-top:calc(9px + var(--desktop-titlebar-inset))}:root[data-desktop-chrome=mac-frameless] .workspace-shell--conversations>.desktop-sidebar,:root[data-desktop-chrome=mac-frameless] .workspace-shell--conversation-open>.workspace-main{padding-top:0}:root[data-desktop-chrome=mac-frameless] .workspace-shell--conversation-open .timeline-header{padding-left:calc(18px + var(--desktop-traffic-lights-inset));box-sizing:border-box}}:root[data-desktop-chrome=mac-frameless] :is(.usage-header,.automations-header,.workstreams-header){padding-left:calc(16px + var(--desktop-traffic-lights-inset));box-sizing:border-box}
|