Files
dsh-wb-sidebar/lib/client.js
T
2026-09-02 07:22:07 +08:00

651 lines
34 KiB
JavaScript
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
/**
* 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;
}
});