@lsamien/yaovi 0.1.1 → 0.1.2
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 +23 -3
- package/desktop/main.mjs +18 -9
- package/dist/assets/{AboutDialog-CBKv505Y.js → AboutDialog-CJ9tCjSs.js} +2 -2
- package/dist/assets/AboutDialog-CJ9tCjSs.js.map +1 -0
- package/dist/assets/{BotAutomationsView-S3aHciek.js → BotAutomationsView-CsoI3js3.js} +2 -2
- package/dist/assets/{BotAutomationsView-S3aHciek.js.map → BotAutomationsView-CsoI3js3.js.map} +1 -1
- package/dist/assets/{BotControlSettingsPanel-DJF4oA6g.js → BotControlSettingsPanel-DIuBZlLu.js} +2 -2
- package/dist/assets/{BotControlSettingsPanel-DJF4oA6g.js.map → BotControlSettingsPanel-DIuBZlLu.js.map} +1 -1
- package/dist/assets/{BotPluginsDialog-BVOQ3DTp.js → BotPluginsDialog-BsGszDMH.js} +2 -2
- package/dist/assets/{BotPluginsDialog-BVOQ3DTp.js.map → BotPluginsDialog-BsGszDMH.js.map} +1 -1
- package/dist/assets/{BotSettingsPanel-CEH3KSAO.js → BotSettingsPanel-j3ThYgt2.js} +3 -3
- package/dist/assets/{BotSettingsPanel-CEH3KSAO.js.map → BotSettingsPanel-j3ThYgt2.js.map} +1 -1
- package/dist/assets/{ComputerPanel-CC-xkAxr.js → ComputerPanel-DdlgOm-B.js} +2 -2
- package/dist/assets/{ComputerPanel-CC-xkAxr.js.map → ComputerPanel-DdlgOm-B.js.map} +1 -1
- package/dist/assets/{ComputerViewerView-CEQrOmy7.js → ComputerViewerView-I3adPI6O.js} +2 -2
- package/dist/assets/{ComputerViewerView-CEQrOmy7.js.map → ComputerViewerView-I3adPI6O.js.map} +1 -1
- package/dist/assets/ConversationsView-CBhzg-9w.css +1 -0
- package/dist/assets/{ConversationsView-CzFWn-79.js → ConversationsView-CxVFk5sQ.js} +73 -73
- package/dist/assets/ConversationsView-CxVFk5sQ.js.map +1 -0
- package/dist/assets/{DevicePairingDialog-ByYk05DC.js → DevicePairingDialog-C307ARt-.js} +2 -2
- package/dist/assets/{DevicePairingDialog-ByYk05DC.js.map → DevicePairingDialog-C307ARt-.js.map} +1 -1
- package/dist/assets/{LocalVmSettingsPanel-DBkqouAU.js → LocalVmSettingsPanel-Zs7CiXdf.js} +2 -2
- package/dist/assets/{LocalVmSettingsPanel-DBkqouAU.js.map → LocalVmSettingsPanel-Zs7CiXdf.js.map} +1 -1
- package/dist/assets/{ProviderLoginDialog-DdPS62uM.js → ProviderLoginDialog-D3mqdAlo.js} +2 -2
- package/dist/assets/{ProviderLoginDialog-DdPS62uM.js.map → ProviderLoginDialog-D3mqdAlo.js.map} +1 -1
- package/dist/assets/{RunnerSettingsPanel-gzA2xzOg.js → RunnerSettingsPanel-DjMGnXGP.js} +2 -2
- package/dist/assets/{RunnerSettingsPanel-gzA2xzOg.js.map → RunnerSettingsPanel-DjMGnXGP.js.map} +1 -1
- package/dist/assets/{StandaloneDialog-B9HCNEFb.js → StandaloneDialog-BXCK22Dx.js} +2 -2
- package/dist/assets/{StandaloneDialog-B9HCNEFb.js.map → StandaloneDialog-BXCK22Dx.js.map} +1 -1
- package/dist/assets/{UpdateCheckDialog-djkXTQgD.js → UpdateCheckDialog-DAY0VEQm.js} +2 -2
- package/dist/assets/{UpdateCheckDialog-djkXTQgD.js.map → UpdateCheckDialog-DAY0VEQm.js.map} +1 -1
- package/dist/assets/{UsageView-DFaWBB40.js → UsageView-DNwG8LAx.js} +2 -2
- package/dist/assets/{UsageView-DFaWBB40.js.map → UsageView-DNwG8LAx.js.map} +1 -1
- package/dist/assets/{WorkspaceKnowledgePanel-In2a_s_z.js → WorkspaceKnowledgePanel-hy_IkIJI.js} +2 -2
- package/dist/assets/{WorkspaceKnowledgePanel-In2a_s_z.js.map → WorkspaceKnowledgePanel-hy_IkIJI.js.map} +1 -1
- package/dist/assets/{WorkspaceRoutinesPanel-zOxFoqML.js → WorkspaceRoutinesPanel-B7PkBP7x.js} +2 -2
- package/dist/assets/{WorkspaceRoutinesPanel-zOxFoqML.js.map → WorkspaceRoutinesPanel-B7PkBP7x.js.map} +1 -1
- package/dist/assets/{WorkstreamsView-HvlivC3b.js → WorkstreamsView-BeE2TRXK.js} +2 -2
- package/dist/assets/{WorkstreamsView-HvlivC3b.js.map → WorkstreamsView-BeE2TRXK.js.map} +1 -1
- package/dist/assets/{index-BPE9bBla.js → index-D11cgXI9.js} +3 -3
- package/dist/assets/{index-BPE9bBla.js.map → index-D11cgXI9.js.map} +1 -1
- package/dist/assets/{pdf-Q2a37aK5.js → pdf-BjTcV6As.js} +2 -2
- package/dist/assets/{pdf-Q2a37aK5.js.map → pdf-BjTcV6As.js.map} +1 -1
- package/dist/index.html +1 -1
- package/dist-server/server/botControl.js +1 -1
- package/dist-server/server/desktopEnvironments.js +10 -9
- package/dist-server/server/desktopHostProtocol.js +1 -1
- package/dist-server/server/workspacePrompt.js +2 -2
- package/dist-server/shared/botEnvironment.js +10 -4
- package/dist-server/shared/fileTransferEndpoint.mjs +5 -5
- package/dist-server/shared/hostPaths.mjs +14 -4
- package/package.json +3 -2
- package/release.json +3 -3
- package/dist/assets/AboutDialog-CBKv505Y.js.map +0 -1
- package/dist/assets/ConversationsView-B6JNGLt_.css +0 -1
- package/dist/assets/ConversationsView-CzFWn-79.js.map +0 -1
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"WorkstreamsView-HvlivC3b.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, onBeforeUnmount, ref } 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}>(), { 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>()\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 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\">\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\"><summary>{{ task.error ? '查看问题' : '查看完整结果' }}</summary><p style=\"white-space:pre-wrap;overflow-wrap:anywhere\">{{ task.error || task.result }}</p></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.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, ref } 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}>(), { 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>()\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 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\">\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\"><summary>{{ task.error ? '查看问题' : '查看完整结果' }}</summary><p style=\"white-space:pre-wrap;overflow-wrap:anywhere\">{{ task.error || task.result }}</p></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.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'))\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 @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'))\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 @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-BeE2TRXK.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, onBeforeUnmount, ref } 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}>(), { 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>()\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 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\">\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\"><summary>{{ task.error ? '查看问题' : '查看完整结果' }}</summary><p style=\"white-space:pre-wrap;overflow-wrap:anywhere\">{{ task.error || task.result }}</p></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.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, ref } 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}>(), { 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>()\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 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\">\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\"><summary>{{ task.error ? '查看问题' : '查看完整结果' }}</summary><p style=\"white-space:pre-wrap;overflow-wrap:anywhere\">{{ task.error || task.result }}</p></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.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'))\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 @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'))\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 @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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"}
|