From 74dc12a1df4e7cfb14dc855f10fe943f004a207e Mon Sep 17 00:00:00 2001 From: figmar Date: Wed, 2 Sep 2026 07:22:07 +0800 Subject: [PATCH] add lib/client.js (v0.2.0) --- lib/client.js | 650 ++++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 650 insertions(+) create mode 100644 lib/client.js diff --git a/lib/client.js b/lib/client.js new file mode 100644 index 0000000..f145b15 --- /dev/null +++ b/lib/client.js @@ -0,0 +1,650 @@ +/** + * 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; + } +});