add lib/client.js (v0.2.0)
This commit is contained in:
+650
@@ -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;
|
||||
}
|
||||
});
|
||||
Reference in New Issue
Block a user