/** * dsh-wb-sidebar — WorkBuddy/Tutti 风格右侧边栏增强(配置化主插件 v0.2.0) * * 单个 client bundle 注册三个可配置 Tab: * - 'wb:tasks' —— WorkBuddy 式任务导航:按工作区分组的会话列表、搜索、 * 运行中徽标、一键打开/新建会话、当前会话高亮。 * - 'wb:agents' —— Tutti 式 Agent 总览:子代理/后台 agent 列表、运行状态、 * 所属工作区、一键打开(不依赖 remote.commands)。 * - 'wb:profile' —— WorkBuddy 底部用户区:当前 provider/model、连接状态、 * 设置/帮助入口。 * * 配置项(better-sidebar 设置页「侧边卡片」,持久化在 * prefs.pluginSettings['dsh-wb-sidebar']): * - enableTasks (bool, 默认 true) 显示 Tasks Tab * - enableAgents (bool, 默认 true) 显示 Agents Tab * - enableProfile (bool, 默认 true) 显示 Profile Tab * - tasks.showTime / tasks.showUngrouped Tasks 子设置 * * 发布格式:DSH client 插件协议 —— window.__ModuleLoader__.load({ id, factory })。 * 纯 JS + React.createElement,无 JSX / TS / 构建步骤。 */ window.__ModuleLoader__.load({ id: "dsh-wb-sidebar", factory: (require) => { var module = { exports: {} }; var exports = module.exports; Object.defineProperty(exports, Symbol.toStringTag, { value: "Module" }); // ---- 依赖 ---------------------------------------------------------- var react = require("react"); var primitives = require("@deepseek-ai/dsh-client-ui-primitives"); // ---- 常量 ---------------------------------------------------------- /** 插件身份(client 侧注册名,与 host 同名同 id)。 */ var name = "dsh-wb-sidebar"; /** client 侧注入的服务名。 */ var inject = [ 'betterSidebar', 'sessions', 'workspaces', 'locale' ]; /** 本插件设置命名空间(prefs.pluginSettings 下的键)。 */ var SETTINGS_NS = 'dsh-wb-sidebar'; var TASKS_TAB_ID = "wb:tasks"; var AGENTS_TAB_ID = "wb:agents"; var PROFILE_TAB_ID = "wb:profile"; var TASKS_STYLE_ID = "dsh-wb-sidebar-style"; var AGENTS_STYLE_ATTR = "data-wb-agents-styles"; var PROFILE_STYLE_ID = "dsh-wb-sidebar-profile-style"; // ---- React 简写 ---------------------------------------------------- var e = react.createElement; var useState = react.useState; var useEffect = react.useEffect; var useRef = react.useRef; var useMemo = react.useMemo; // ---- 本地文案(zh / en) ------------------------------------------- var zh = { "tab.tasks.title": "任务", "tab.agents.title": "Agents", "tab.profile.title": "Profile", "search.placeholder": "搜索会话…", "search.remote": "搜索中…", "search.empty": "没有匹配的会话", "list.empty": "暂无会话。点击工作区右侧「+」新建。", "group.ungrouped": "未分组", "group.new": "新建会话", "badge.running": "运行中", "setting.enableTasks": "任务导航 Tab", "setting.enableTasksDesc": "按工作区+时间分组的会话列表,含搜索与运行中徽标", "setting.enableAgents": "Agent 总览 Tab", "setting.enableAgentsDesc": "子代理/后台 agent 列表与运行状态", "setting.enableProfile": "Profile Tab", "setting.enableProfileDesc": "底部用户区:模型信息与设置/帮助入口", "setting.showTime": "显示相对时间", "setting.showTimeDesc": "会话行右侧显示最后活动时间", "setting.showUngrouped": "显示未分组会话", "setting.showUngroupedDesc": "未归入任何工作区的会话单独列出", "agents.empty": "当前没有运行中的子代理/后台 agent", "agents.hint": "子代理启动后出现在这里,点击可跳转", "agents.childOf": "子代理", "agents.done": "完成", "agents.running": "运行中", "agents.open": "打开", "agents.roomsDisabled": "团队房间依赖 dsh-background-agents(当前与 DSH 基座存在兼容冲突,暂不可用)", "profile.status": "状态", "profile.connected": "已连接", "profile.disconnected": "未连接", "profile.provider": "Provider", "profile.model": "模型", "profile.settings": "设置", "profile.help": "帮助", "profile.footer": "dsh-wb-sidebar 0.2.0 · WorkBuddy/Tutti 风格增强" }; var en = { "tab.tasks.title": "Tasks", "tab.agents.title": "Agents", "tab.profile.title": "Profile", "search.placeholder": "Search sessions…", "search.remote": "Searching…", "search.empty": "No matching sessions", "list.empty": "No sessions. Click "+" next to a workspace to create one.", "group.ungrouped": "Ungrouped", "group.new": "New session", "badge.running": "Running", "setting.enableTasks": "Tasks tab", "setting.enableTasksDesc": "Workspace-grouped session list with search and running badges", "setting.enableAgents": "Agents tab", "setting.enableAgentsDesc": "Subagent / background agent list and status", "setting.enableProfile": "Profile tab", "setting.enableProfileDesc": "Bottom user area: model info + settings/help", "setting.showTime": "Show relative time", "setting.showTimeDesc": "Show last activity time on session rows", "setting.showUngrouped": "Show ungrouped sessions", "setting.showUngroupedDesc": "List sessions not in any workspace separately", "agents.empty": "No running subagents / background agents", "agents.hint": "Subagents appear here; click to jump", "agents.childOf": "subagent", "agents.done": "done", "agents.running": "running", "agents.open": "open", "agents.roomsDisabled": "Team rooms require dsh-background-agents (currently incompatible with the DSH base; unavailable)", "profile.status": "Status", "profile.connected": "Connected", "profile.disconnected": "Disconnected", "profile.provider": "Provider", "profile.model": "Model", "profile.settings": "Settings", "profile.help": "Help", "profile.footer": "dsh-wb-sidebar 0.2.0 · WorkBuddy/Tutti style enhancement" }; function isZh() { try { return (typeof navigator !== 'undefined' && /zh/i.test(navigator.language || '')); } catch (err) { return true; } } function t(key, params) { var dict = isZh() ? zh : en; var text = dict[key] !== void 0 ? dict[key] : key; if (params) { for (var k in params) { if (Object.prototype.hasOwnProperty.call(params, k)) { text = text.split('{' + k + '}').join(String(params[k])); } } } return text; } // ---- 样式注入(DSH CSS 变量) -------------------------------------- function injectStyles(styleId, cssText) { try { if (document.head.querySelector('style[data-wb-style="' + styleId + '"]')) return; var tag = document.createElement("style"); tag.setAttribute('data-wb-style', styleId); tag.textContent = cssText; document.head.appendChild(tag); } catch (err) { /* ignore */ } } function disposeStyles(styleId) { try { var el = document.head.querySelector('style[data-wb-style="' + styleId + '"]'); if (el && typeof el.remove === 'function') el.remove(); } catch (err) { /* ignore */ } } // ==================================================================== // 配置读写(better-sidebar 设置) // ==================================================================== /** 读取本插件的设置快照(防御性,读不到返回默认值)。 */ function readSettings(store) { var defs = { enableTasks: true, enableAgents: true, enableProfile: true }; try { var raw = store && store.prefs && store.prefs.pluginSettings ? store.prefs.pluginSettings[SETTINGS_NS] : null; if (raw && typeof raw === 'object') { return { enableTasks: raw.enableTasks !== false, enableAgents: raw.enableAgents !== false, enableProfile: raw.enableProfile !== false }; } } catch (err) { /* ignore */ } return defs; } // ==================================================================== // Tasks Tab(WorkBuddy 式任务导航) // ==================================================================== var TASKS_CSS = [ '#' + TASKS_STYLE_ID + '-root{display:flex;flex-direction:column;height:100%;min-height:0;background:var(--dsw-alias-bg-base,transparent);color:var(--dsw-alias-label-primary,#1f2329);font-size:13px;line-height:1.5}', '#' + TASKS_STYLE_ID + '-root *{box-sizing:border-box}', '#' + TASKS_STYLE_ID + '-search{flex:0 0 auto;display:flex;align-items:center;gap:6px;margin:8px 10px 6px;padding:5px 8px;border:1px solid var(--dsw-alias-border-l2,transparent);border-radius:6px;background:var(--dsw-alias-bg-layer-1,transparent)}', '#' + TASKS_STYLE_ID + '-search:focus-within{border-color:var(--dsw-alias-brand-primary,#3370ff)}', '#' + TASKS_STYLE_ID + '-search input{flex:1;min-width:0;border:0;outline:0;background:transparent;color:var(--dsw-alias-label-primary,#1f2329);font-size:13px}', '#' + TASKS_STYLE_ID + '-search input::placeholder{color:var(--dsw-alias-label-tertiary,#8f959e)}', '#' + TASKS_STYLE_ID + '-search-icon{display:inline-flex;color:var(--dsw-alias-label-tertiary,#8f959e)}', '#' + TASKS_STYLE_ID + '-clear{flex:0 0 auto;border:0;background:transparent;cursor:pointer;color:var(--dsw-alias-label-tertiary,#8f959e);padding:0 2px;line-height:1}', '#' + TASKS_STYLE_ID + '-clear:hover{color:var(--dsw-alias-label-primary,#1f2329)}', '#' + TASKS_STYLE_ID + '-list{flex:1 1 auto;min-height:0;overflow-y:auto;padding:0 4px 12px}', '#' + TASKS_STYLE_ID + '-hint{padding:20px 14px;color:var(--dsw-alias-label-tertiary,#8f959e);text-align:center;font-size:12px}', '#' + TASKS_STYLE_ID + '-group{margin:6px 4px 2px}', '#' + TASKS_STYLE_ID + '-group-head{display:flex;align-items:center;gap:6px;padding:6px 8px;border-radius:6px;cursor:pointer;user-select:none}', '#' + TASKS_STYLE_ID + '-group-head:hover{background:var(--dsw-alias-interactive-bg-hover,rgba(0,0,0,.04))}', '#' + TASKS_STYLE_ID + '-group-title{flex:1;min-width:0;overflow:hidden;text-overflow:ellipsis;white-space:nowrap;color:var(--dsw-alias-label-secondary,#5c6069);font-weight:600;font-size:12px}', '#' + TASKS_STYLE_ID + '-group-count{flex:0 0 auto;color:var(--dsw-alias-label-tertiary,#8f959e);font-size:11px}', '#' + TASKS_STYLE_ID + '-group-add{flex:0 0 auto;display:inline-flex;align-items:center;justify-content:center;width:18px;height:18px;border:0;border-radius:4px;background:transparent;cursor:pointer;color:var(--dsw-alias-label-secondary,#5c6069)}', '#' + TASKS_STYLE_ID + '-group-add:hover{background:var(--dsw-alias-interactive-bg-hover,rgba(0,0,0,.04));color:var(--dsw-alias-label-primary,#1f2329)}', '#' + TASKS_STYLE_ID + '-row{box-sizing:border-box;width:100%;padding:6px 8px;border-radius:8px;cursor:pointer;display:flex;align-items:center;gap:8px;color:var(--dsw-alias-label-primary,#1f2329);position:relative}', '#' + TASKS_STYLE_ID + '-row:hover{background:var(--dsw-alias-interactive-bg-hover,rgba(0,0,0,.04))}', '#' + TASKS_STYLE_ID + '-row.is-active{background:var(--dsw-alias-interactive-bg-active,rgba(51,112,255,.1))}', '#' + TASKS_STYLE_ID + '-row.is-active::before{content:"";position:absolute;left:0;top:6px;bottom:6px;width:3px;border-radius:3px;background:var(--dsw-alias-brand-primary,#3370ff)}', '#' + TASKS_STYLE_ID + '-dot{flex:0 0 auto;width:8px;height:8px;border-radius:50%;background:var(--dsw-alias-label-tertiary,#8f959e)}', '#' + TASKS_STYLE_ID + '-dot.is-running{background:var(--dsw-alias-state-success-primary,#22c55e);animation:wb-pulse 1.6s ease-in-out infinite}', '@keyframes wb-pulse{0%,100%{opacity:1}50%{opacity:.35}}', '#' + TASKS_STYLE_ID + '-title{flex:1;min-width:0;overflow:hidden;text-overflow:ellipsis;white-space:nowrap;font-size:13px}', '#' + TASKS_STYLE_ID + '-time{flex:0 0 auto;color:var(--dsw-alias-label-tertiary,#8f959e);font-size:11px}', '#' + TASKS_STYLE_ID + '-badge{flex:0 0 auto;padding:1px 6px;border-radius:999px;background:rgba(34,197,94,.12);color:var(--dsw-alias-state-success-primary,#16a34a);font-size:10px;font-weight:600}' ].join('\n'); function sessionsSnapshot(ctx) { try { return ctx.sessions && ctx.sessions.list ? ctx.sessions.list.getSnapshot() : { current: null, byId: {} }; } catch (err) { return { current: null, byId: {} }; } } function workspacesSnapshot(ctx) { try { return ctx.workspaces && ctx.workspaces.list ? ctx.workspaces.list.getSnapshot() : { items: [] }; } catch (err) { return { items: [] }; } } function sessionTitleOf(session) { if (!session) return ''; return session.title || session.displayTitle || session.name || ''; } function sessionUpdatedAt(session) { if (!session) return 0; var v = session.updatedAt !== void 0 ? session.updatedAt : session.lastActive !== void 0 ? session.lastActive : session.lastUpdatedAt !== void 0 ? session.lastUpdatedAt : session.updated !== void 0 ? session.updated : 0; return typeof v === 'number' ? v : Date.parse(String(v)) || 0; } /** useStoreSnapshot:手动 subscribe + 快照引用变化才 setState。 */ function useStoreSnapshot(ctx, storeName, getSnapshot) { var snap = useState(function () { return getSnapshot(); })[0]; var setSnap = useState(function () { return function () {}; })[1]; // 用 update 方式:subscribe 回调里比较引用 var updater = useState(null)[1]; useEffect(function () { var store = null; try { if (storeName === 'sessions') store = ctx.sessions && ctx.sessions.list; else if (storeName === 'workspaces') store = ctx.workspaces && ctx.workspaces.list; } catch (err) { store = null; } if (!store || typeof store.subscribe !== 'function') return function () {}; var off = store.subscribe(function () { var next = getSnapshot(); updater(function (prev) { return prev === next ? prev : next; }); }); return off; }, [storeName]); return snap; } /** Tasks 主视图。 */ function TasksView(props) { var ctx = props.ctx; var sessions = useStoreSnapshot(ctx, 'sessions', function () { return sessionsSnapshot(ctx); }); var workspaces = useStoreSnapshot(ctx, 'workspaces', function () { return workspacesSnapshot(ctx); }); var query = useState('')[0]; var setQuery = useState('')[1]; var expanded = useState({})[0]; var setExpanded = useState({})[1]; var currentId = (props.scope && props.scope.sessionId) || sessions.current || null; var byId = sessions.byId || {}; var items = workspaces.items || []; var groups = []; var seen = {}; for (var i = 0; i < items.length; i++) { var ws = items[i]; var wsId = ws.id || ws.workspaceId || 'ws-' + i; var members = []; var wsSessions = ws.sessionIds || ws.sessions || []; for (var j = 0; j < wsSessions.length; j++) { var sid = wsSessions[j]; if (byId[sid]) { members.push(byId[sid]); seen[sid] = true; } } members.sort(byRecency); groups.push({ id: wsId, title: ws.title || ws.name || wsId, sessions: members, count: members.length }); } var ungrouped = []; var allIds = Object.keys(byId); for (var k = 0; k < allIds.length; k++) { if (!seen[allIds[k]]) ungrouped.push(byId[allIds[k]]); } ungrouped.sort(byRecency); var q = (query || '').trim().toLowerCase(); var searchActive = q.length > 0; var filterFn = function (s) { if (!searchActive) return true; var title = sessionTitleOf(s).toLowerCase(); var id = (s.id || '').toLowerCase(); var cwd = (s.cwd || '').toLowerCase(); return title.indexOf(q) !== -1 || id.indexOf(q) !== -1 || cwd.indexOf(q) !== -1; }; var children = []; if (searchActive) { var hits = []; for (var m = 0; m < groups.length; m++) { for (var n = 0; n < groups[m].sessions.length; n++) { var s = groups[m].sessions[n]; if (filterFn(s)) hits.push(s); } } for (var p = 0; p < ungrouped.length; p++) { if (filterFn(ungrouped[p])) hits.push(ungrouped[p]); } if (!hits.length) { children.push(e('div', { key: 'empty', className: TASKS_STYLE_ID + '-hint' }, t('search.empty'))); } else { children.push(e('div', { key: 'searchsec' }, hits.map(function (s) { return sessionRow(ctx, s, currentId); }))); } } else { var hasAny = groups.length > 0 || ungrouped.length > 0; if (!hasAny) { children.push(e('div', { key: 'empty', className: TASKS_STYLE_ID + '-hint' }, t('list.empty'))); } for (var g = 0; g < groups.length; g++) { var group = groups[g]; var isOpen = expanded[group.id] !== false; children.push(e('div', { key: group.id, className: TASKS_STYLE_ID + '-group' }, e('div', { key: 'h', className: TASKS_STYLE_ID + '-group-head', onClick: function (ev) { ev.stopPropagation(); var gid = group.id; setExpanded(function (prev) { var next = {}; for (var kk in prev) next[kk] = prev[kk]; next[gid] = prev[gid] === false; return next; }); } }, e('span', { className: TASKS_STYLE_ID + '-group-title' }, group.title), e('span', { className: TASKS_STYLE_ID + '-group-count' }, String(group.count)), e('button', { type: 'button', className: TASKS_STYLE_ID + '-group-add', title: t('group.new'), onClick: function (ev) { ev.stopPropagation(); try { ctx.workspaces.startSession(group.id).catch(function () {}); } catch (err) {} } }, '+')), isOpen ? group.sessions.map(function (s) { return sessionRow(ctx, s, currentId); }) : null)); } var showUngrouped = true; try { var prefs = props.store && props.store.prefs ? props.store.prefs.pluginSettings : null; if (prefs && prefs[TASKS_TAB_ID] && prefs[TASKS_TAB_ID].showUngrouped === false) showUngrouped = false; } catch (err) { } if (ungrouped.length && showUngrouped) { children.push(e('div', { key: 'ungrouped', className: TASKS_STYLE_ID + '-group' }, e('div', { key: 'h', className: TASKS_STYLE_ID + '-group-head' }, e('span', { className: TASKS_STYLE_ID + '-group-title' }, t('group.ungrouped')), e('span', { className: TASKS_STYLE_ID + '-group-count' }, String(ungrouped.length))), ungrouped.map(function (s) { return sessionRow(ctx, s, currentId); }))); } } return e('div', { id: TASKS_STYLE_ID + '-root' }, e('div', { className: TASKS_STYLE_ID + '-search' }, e('span', { className: TASKS_STYLE_ID + '-search-icon' }, '⌕'), e('input', { placeholder: t('search.placeholder'), value: query, onChange: function (ev) { setQuery(ev.target.value); } }), query ? e('button', { type: 'button', className: TASKS_STYLE_ID + '-clear', onClick: function () { setQuery(''); } }, '✕') : null), e('div', { className: TASKS_STYLE_ID + '-list' }, children)); } function byRecency(a, b) { var ta = sessionUpdatedAt(a); var tb = sessionUpdatedAt(b); if (ta !== tb) return tb - ta; return String(a.id || '').localeCompare(String(b.id || '')); } function sessionRow(ctx, s, currentId) { var sid = s.id || s.sessionId || ''; var running = s.running === true; var isActive = sid === currentId; return e('div', { key: sid, className: TASKS_STYLE_ID + '-row' + (isActive ? ' is-active' : ''), onClick: function () { try { ctx.sessions.open(sid); } catch (err) {} } }, e('span', { className: TASKS_STYLE_ID + '-dot' + (running ? ' is-running' : '') }), e('span', { className: TASKS_STYLE_ID + '-title', title: sid }, sessionTitleOf(s) || sid), running ? e('span', { className: TASKS_STYLE_ID + '-badge' }, t('badge.running')) : null); } function tasksBadge(ctx, scope, state) { try { var snap = sessionsSnapshot(ctx); var currentId = (scope && scope.sessionId) || snap.current || null; var count = 0; var byId = snap.byId || {}; for (var id in byId) { if (Object.prototype.hasOwnProperty.call(byId, id) && byId[id] && byId[id].running === true) count += 1; } return count > 0 ? count : null; } catch (err) { return null; } } function tasksTabDescriptor(ctx) { return { id: TASKS_TAB_ID, title: function () { return t('tab.tasks.title'); }, icon: function (size) { return e(primitives.IconListPenOutline16, { size: size }); }, order: 100, single: true, badge: function (bctx, scope, state) { return tasksBadge(bctx, scope, state); }, settings: { pluginToggles: [ { key: "showTime", title: function () { return t('setting.showTime'); }, desc: function () { return t('setting.showTimeDesc'); } }, { key: "showUngrouped", title: function () { return t('setting.showUngrouped'); }, desc: function () { return t('setting.showUngroupedDesc'); } } ] }, component: function (props) { return e(TasksView, props); } }; } // ==================================================================== // Agents Tab(Tutti 式 Agent 总览)—— 不依赖 remote.commands // ==================================================================== var AGENTS_CSS = [ '[data-wb-agents-styles]{display:flex;flex-direction:column;height:100%;min-height:0;background:var(--dsw-alias-bg-base,transparent);color:var(--dsw-alias-label-primary,#1f2329);font-size:13px;line-height:1.5}', '[data-wb-agents-styles] *{box-sizing:border-box}', '.wb-agents-toolbar{flex:0 0 auto;display:flex;align-items:center;gap:8px;padding:8px 10px;border-bottom:1px solid var(--dsw-alias-border-l2,rgba(31,35,41,.08))}', '.wb-agents-title{flex:1;font-weight:600;font-size:13px}', '.wb-agents-count{font-size:11px;color:var(--dsw-alias-label-tertiary,#8f959e);background:rgba(34,197,94,.12);padding:2px 8px;border-radius:999px}', '.wb-agents-refresh{flex:0 0 auto;border:1px solid var(--dsw-alias-border-l2,rgba(31,35,41,.08));background:transparent;color:var(--dsw-alias-label-secondary,#5c6069);border-radius:6px;padding:3px 10px;cursor:pointer;font-size:12px}', '.wb-agents-refresh:hover{background:var(--dsw-alias-interactive-bg-hover,rgba(0,0,0,.04))}', '.wb-agents-list{flex:1 1 auto;min-height:0;overflow-y:auto;padding:6px 8px}', '.wb-agents-hint{padding:24px 14px;color:var(--dsw-alias-label-tertiary,#8f959e);text-align:center;font-size:12px}', '.wb-agents-row{box-sizing:border-box;width:100%;padding:8px;border-radius:8px;cursor:pointer;display:flex;align-items:center;gap:8px}', '.wb-agents-row:hover{background:var(--dsw-alias-interactive-bg-hover,rgba(0,0,0,.04))}', '.wb-agents-dot{flex:0 0 auto;width:8px;height:8px;border-radius:50%;background:var(--dsw-alias-label-tertiary,#8f959e)}', '.wb-agents-dot.is-running{background:var(--dsw-alias-state-success-primary,#22c55e);animation:wb-agents-pulse 1.6s ease-in-out infinite}', '@keyframes wb-agents-pulse{0%,100%{opacity:1}50%{opacity:.35}}', '.wb-agents-name{flex:1;min-width:0;overflow:hidden;text-overflow:ellipsis;white-space:nowrap;font-size:13px}', '.wb-agents-meta{color:var(--dsw-alias-label-tertiary,#8f959e);font-size:11px;flex:0 0 auto;max-width:40%;overflow:hidden;text-overflow:ellipsis;white-space:nowrap}', '.wb-agents-tag{flex:0 0 auto;padding:1px 6px;border-radius:999px;font-size:10px;font-weight:600}', '.wb-agents-tag.running{background:rgba(34,197,94,.12);color:var(--dsw-alias-state-success-primary,#16a34a)}', '.wb-agents-tag.done{background:var(--dsw-alias-interactive-bg-hover,rgba(0,0,0,.04));color:var(--dsw-alias-label-tertiary,#8f959e)}', '.wb-agents-rooms-note{margin:8px 10px;padding:8px 10px;border:1px dashed var(--dsw-alias-border-l2,rgba(31,35,41,.15));border-radius:8px;color:var(--dsw-alias-label-tertiary,#8f959e);font-size:11px;line-height:1.5}' ].join('\n'); /** 判定一个会话是否为子代理/后台 agent(防御性,多信号)。 */ function isSubagentLike(sessionId, entry, byId, currentId) { if (!entry || !sessionId || sessionId === currentId) return false; if (entry.kind === 'subagent' || entry.kind === 'continuable' || entry.agentId) return true; var parent = entry.parentSessionId || entry.parentId || entry.parent || (entry.scope && entry.scope.parentSessionId); if (parent && byId[parent]) return true; if (entry.running === true) { // running 的非顶层会话视作活跃子代理候选 if (sessionId.indexOf(currentId + ':') === 0 || sessionId.indexOf(currentId + '/') === 0) return true; } return false; } /** 子代理行列表:当前会话的直接子代理优先,其次其他运行中的子代理。 */ function subagentRows(ctx, scope) { var snap = sessionsSnapshot(ctx); var byId = snap.byId || {}; var currentId = (scope && scope.sessionId) || snap.current || null; var rows = []; for (var id in byId) { if (!Object.prototype.hasOwnProperty.call(byId, id)) continue; var entry = byId[id]; if (!isSubagentLike(id, entry, byId, currentId)) continue; var title = entry.title || entry.displayTitle || entry.name || ''; if (!title) title = String(id).slice(0, 24); rows.push({ id: id, title: title, running: entry.running === true, cwd: entry.cwd || '', workspace: entry.cwd ? String(entry.cwd).split('/').filter(Boolean).slice(-1)[0] || '' : '', parentId: entry.parentSessionId || entry.parentId || entry.parent || '', relatedToCurrent: currentId ? (id.indexOf(currentId + ':') === 0 || id.indexOf(currentId + '/') === 0 || entry.parentSessionId === currentId || entry.parentId === currentId) : false }); } rows.sort(function (a, b) { if (a.relatedToCurrent !== b.relatedToCurrent) return a.relatedToCurrent ? -1 : 1; if (a.running !== b.running) return a.running ? -1 : 1; return String(a.id).localeCompare(String(b.id)); }); return rows; } function agentsBadge(ctx, scope, state) { try { var rows = subagentRows(ctx, scope); var running = 0; for (var i = 0; i < rows.length; i++) if (rows[i].running) running += 1; return running > 0 ? running : null; } catch (err) { return null; } } function AgentsView(props) { var ctx = props.ctx; var scope = props.scope; var refreshTick = useState(0)[0]; var setRefreshTick = useState(0)[1]; var sessions = useStoreSnapshot(ctx, 'sessions', function () { return sessionsSnapshot(ctx); }); var rows = subagentRows(ctx, scope); var running = 0; for (var i = 0; i < rows.length; i++) if (rows[i].running) running += 1; return e('div', { 'data-wb-agents-styles': '' }, e('div', { className: 'wb-agents-toolbar' }, e('span', { className: 'wb-agents-title' }, t('tab.agents.title')), running > 0 ? e('span', { className: 'wb-agents-count' }, String(running) + ' running') : null, e('button', { type: 'button', className: 'wb-agents-refresh', onClick: function () { setRefreshTick(function (x) { return x + 1; }); } }, '↻')), e('div', { className: 'wb-agents-list' }, !rows.length ? e('div', { className: 'wb-agents-hint' }, t('agents.empty')) : rows.map(function (row) { return e('div', { key: row.id, className: 'wb-agents-row', title: row.id, onClick: function () { try { ctx.sessions.open(row.id); } catch (err) {} } }, e('span', { className: 'wb-agents-dot' + (row.running ? ' is-running' : '') }), e('span', { className: 'wb-agents-name' }, row.title), row.workspace ? e('span', { className: 'wb-agents-meta' }, row.workspace) : null, e('span', { className: 'wb-agents-tag ' + (row.running ? 'running' : 'done') }, row.running ? t('agents.running') : t('agents.done'))); })), e('div', { className: 'wb-agents-rooms-note' }, t('agents.roomsDisabled'))); } function agentsTabDescriptor(ctx) { return { id: AGENTS_TAB_ID, title: function () { return t('tab.agents.title'); }, icon: function (size) { return e(primitives.IconBranchOutline16, { size: size }); }, order: 80, single: true, badge: function (bctx, scope, state) { return agentsBadge(bctx, scope, state); }, component: function (props) { return e(AgentsView, props); } }; } // ==================================================================== // Profile Tab(WorkBuddy 底部用户区) // ==================================================================== var PROFILE_CSS = [ '#' + PROFILE_STYLE_ID + '-root{display:flex;flex-direction:column;height:100%;min-height:0;background:var(--dsw-alias-bg-base,transparent);color:var(--dsw-alias-label-primary,#1f2329);font-size:13px;line-height:1.5}', '#' + PROFILE_STYLE_ID + '-root *{box-sizing:border-box}', '#' + PROFILE_STYLE_ID + '-card{margin:10px;padding:12px;border:1px solid var(--dsw-alias-border-l2,rgba(31,35,41,.08));border-radius:10px;background:var(--dsw-alias-bg-layer-1,transparent)}', '#' + PROFILE_STYLE_ID + '-avatar{width:44px;height:44px;border-radius:50%;display:flex;align-items:center;justify-content:center;font-size:18px;font-weight:700;color:#fff;background:linear-gradient(135deg,#3370ff,#7c3aed);margin-bottom:10px}', '#' + PROFILE_STYLE_ID + '-name{font-weight:600;font-size:14px}', '#' + PROFILE_STYLE_ID + '-sub{color:var(--dsw-alias-label-tertiary,#8f959e);font-size:12px;margin-top:2px}', '#' + PROFILE_STYLE_ID + '-row{display:flex;justify-content:space-between;align-items:center;gap:8px;padding:8px 0;border-bottom:1px solid var(--dsw-alias-border-l1,rgba(31,35,41,.05))}', '#' + PROFILE_STYLE_ID + '-row:last-child{border-bottom:0}', '#' + PROFILE_STYLE_ID + '-row-label{color:var(--dsw-alias-label-secondary,#5c6069);font-size:12px}', '#' + PROFILE_STYLE_ID + '-row-value{font-size:12px;word-break:break-all;text-align:right;max-width:60%}', '#' + PROFILE_STYLE_ID + '-dot{display:inline-block;width:8px;height:8px;border-radius:50%;margin-right:6px;background:var(--dsw-alias-state-success-primary,#22c55e)}', '#' + PROFILE_STYLE_ID + '-actions{display:flex;gap:8px;margin:10px}', '#' + PROFILE_STYLE_ID + '-btn{flex:1;padding:7px 0;border:1px solid var(--dsw-alias-border-l2,rgba(31,35,41,.08));border-radius:6px;background:var(--dsw-alias-bg-layer-1,transparent);color:var(--dsw-alias-label-primary,#1f2329);font-size:12px;cursor:pointer;text-align:center}', '#' + PROFILE_STYLE_ID + '-btn:hover{background:var(--dsw-alias-interactive-bg-hover,rgba(0,0,0,.04))}', '#' + PROFILE_STYLE_ID + '-footer{margin-top:auto;padding:8px 10px;color:var(--dsw-alias-label-tertiary,#8f959e);font-size:11px;border-top:1px solid var(--dsw-alias-border-l1,rgba(31,35,41,.05))}' ].join('\n'); function currentModelInfo(profileCtx) { var info = { provider: '', model: '', connected: true }; try { var snap = sessionsSnapshot(profileCtx); var sid = snap.current || null; var entry = sid && snap.byId ? snap.byId[sid] : null; if (entry) { if (entry.provider) info.provider = entry.provider; if (entry.model) info.model = entry.model; } } catch (err) { /* ignore */ } return info; } function ProfileView(props) { var ctx = props.ctx; var info = currentModelInfo(ctx); return e('div', { id: PROFILE_STYLE_ID + '-root' }, e('div', { id: PROFILE_STYLE_ID + '-card' }, e('div', { id: PROFILE_STYLE_ID + '-avatar' }, 'DS'), e('div', { id: PROFILE_STYLE_ID + '-name' }, 'DeepSeek Harness'), e('div', { id: PROFILE_STYLE_ID + '-sub' }, '本地 Agent 工作台'), e('div', { id: PROFILE_STYLE_ID + '-row' }, e('span', { id: PROFILE_STYLE_ID + '-row-label' }, t('profile.status')), e('span', { id: PROFILE_STYLE_ID + '-row-value' }, e('span', { id: PROFILE_STYLE_ID + '-dot' }), info.connected ? t('profile.connected') : t('profile.disconnected'))), e('div', { id: PROFILE_STYLE_ID + '-row' }, e('span', { id: PROFILE_STYLE_ID + '-row-label' }, t('profile.provider')), e('span', { id: PROFILE_STYLE_ID + '-row-value' }, info.provider || '—')), e('div', { id: PROFILE_STYLE_ID + '-row' }, e('span', { id: PROFILE_STYLE_ID + '-row-label' }, t('profile.model')), e('span', { id: PROFILE_STYLE_ID + '-row-value' }, info.model || '—'))), e('div', { id: PROFILE_STYLE_ID + '-actions' }, e('button', { id: PROFILE_STYLE_ID + '-btn', type: 'button', onClick: function () { try { if (ctx.commands) ctx.commands.execute('settings'); } catch (err) {} } }, t('profile.settings')), e('button', { id: PROFILE_STYLE_ID + '-btn', type: 'button', onClick: function () { try { if (ctx.commands) ctx.commands.execute('help'); else window.open('https://github.com/deepseek-ai/deepseek-harness', '_blank'); } catch (err) {} } }, t('profile.help'))), e('div', { id: PROFILE_STYLE_ID + '-footer' }, t('profile.footer'))); } function profileTabDescriptor(ctx) { return { id: PROFILE_TAB_ID, title: function () { return t('tab.profile.title'); }, icon: function (size) { return e(primitives.IconUserOutline16 || primitives.IconContactOutline16, { size: size }); }, order: 110, single: true, component: function (props) { return e(ProfileView, props); } }; } // ==================================================================== // apply:按配置注册三个 Tab // ==================================================================== function apply(ctx) { injectStyles(TASKS_STYLE_ID, TASKS_CSS); injectStyles(AGENTS_STYLE_ATTR, AGENTS_CSS); injectStyles(PROFILE_STYLE_ID, PROFILE_CSS); ctx.effect(function () { var disposers = []; try { // 从 store 读取配置(better-sidebar 设置页开关) var settings = readSettings(ctx.get('betterSidebar') && ctx.get('betterSidebar').store ? ctx.get('betterSidebar').store : null); if (settings.enableTasks) disposers.push(ctx.betterSidebar.registerTab(tasksTabDescriptor(ctx))); if (settings.enableAgents) disposers.push(ctx.betterSidebar.registerTab(agentsTabDescriptor(ctx))); if (settings.enableProfile) disposers.push(ctx.betterSidebar.registerTab(profileTabDescriptor(ctx))); } catch (err) { // 配置读取失败时注册全部(保守默认) disposers.push(ctx.betterSidebar.registerTab(tasksTabDescriptor(ctx))); disposers.push(ctx.betterSidebar.registerTab(agentsTabDescriptor(ctx))); disposers.push(ctx.betterSidebar.registerTab(profileTabDescriptor(ctx))); } return function () { for (var i = 0; i < disposers.length; i++) { try { disposers[i](); } catch (err) { } } }; }, "dsh-wb-sidebar: register tabs"); ctx.effect(function () { return function () { disposeStyles(TASKS_STYLE_ID); disposeStyles(AGENTS_STYLE_ATTR); disposeStyles(PROFILE_STYLE_ID); }; }, "dsh-wb-sidebar: dispose styles"); } module.exports = { name: name, inject: inject, apply: apply }; return module.exports; } });