add lib/client.js (v0.2.0)

This commit is contained in:
2026-09-02 07:22:07 +08:00
parent bdc6a026e4
commit 74dc12a1df
+650
View File
@@ -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;
}
});