Files
apes-sidebar/lib/client.js
T
figmar 5f6018a9ec rename: apes--sidebar → apes-sidebar(包名改单连字符)
- package.json name、cordis.patch.yml id/name、lib/index.js 导出、client.js 插件 id/设置命名空间/显示名默认值,全部统一为单连字符 apes-sidebar
- 双连字符写法在 npm 包名和 loader 解析中易与单连字符混淆,统一后更规范
2026-09-02 22:47:26 +08:00

823 lines
44 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.
/**
* apes-sidebar — WorkBuddy/Tutti 风格右侧边栏增强(配置化主插件 v0.4.0)
*
* 单个 client bundle 注册四个可配置 Tab:
* - 'wb:tasks' —— WorkBuddy 式任务导航:按工作区分组的会话列表、搜索、
* 运行中徽标;升级:含「运行中任务」区(子代理拓扑 +
* 实时计数角标),一键打开/新建会话、当前会话高亮。
* - 'wb:agents' —— Tutti 式 Agent 总览:子代理/后台 agent 列表、运行状态、
* 所属工作区、一键打开(不依赖 remote.commands)。
* - 'wb:apps' —— Tutti 式应用中心:已装应用网格(Design/PPT/Video 等),
* 点击在会话视图打开。
* - 'wb:profile' —— WorkBuddy 底部用户区:当前 provider/model、连接状态、
* 设置/帮助入口。
*
* 配置项(better-sidebar 设置页「侧边卡片」,持久化在
* prefs.pluginSettings['apes-sidebar']):
* - displayName (string, 默认 'apes-sidebar') 显示名;预设 'feyOS',
* 或用户任意自定义
* - enableTasks (bool, 默认 true) 显示 Tasks Tab
* - enableAgents (bool, 默认 true) 显示 Agents Tab
* - enableApps (bool, 默认 true) 显示 Apps 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: "apes-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 = "apes-sidebar";
/** client 侧注入的服务名。 */
var inject = [
'betterSidebar',
'sessions',
'workspaces'
];
/** 显示名预设。 */
var DISPLAY_NAME_DEFAULT = 'apes-sidebar';
var DISPLAY_NAME_PRESET_FEYOS = 'feyOS';
/** 本插件设置命名空间(prefs.pluginSettings 下的键)。 */
var SETTINGS_NS = 'apes-sidebar';
var TASKS_TAB_ID = "wb:tasks";
var AGENTS_TAB_ID = "wb:agents";
var APPS_TAB_ID = "wb:apps";
var PROFILE_TAB_ID = "wb:profile";
var TASKS_STYLE_ID = "apes-sidebar-style";
var AGENTS_STYLE_ATTR = "data-apes-sidebar-agents-styles";
var PROFILE_STYLE_ID = "apes-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.enableApps": "应用中心 Tab",
"setting.enableAppsDesc": "已装应用网格(Design/PPT/Video),点击在会话视图打开",
"apps.title": "应用中心",
"apps.hint": "点击应用在会话视图打开;也可在会话顶栏视图切换",
"apps.open": "打开",
"apps.notFound": "未检测到该应用(可能未安装)",
"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": "apes-sidebar 0.4.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.enableApps": "Apps tab",
"setting.enableAppsDesc": "Installed app grid (Design/PPT/Video), click to open in conversation view",
"apps.title": "Apps",
"apps.hint": "Click an app to open it in the conversation view; also switchable in the view bar",
"apps.open": "Open",
"apps.notFound": "App not detected (maybe not installed)",
"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": "apes-sidebar 0.4.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 = { displayName: DISPLAY_NAME_DEFAULT, enableTasks: true, enableAgents: true, enableApps: true, enableProfile: true };
try {
var raw = store && store.prefs && store.prefs.pluginSettings ? store.prefs.pluginSettings[SETTINGS_NS] : null;
if (raw && typeof raw === 'object') {
return {
displayName: typeof raw.displayName === 'string' && raw.displayName.trim() !== '' ? raw.displayName.trim() : DISPLAY_NAME_DEFAULT,
enableTasks: raw.enableTasks !== false,
enableAgents: raw.enableAgents !== false,
enableApps: raw.enableApps !== false,
enableProfile: raw.enableProfile !== false
};
}
} catch (err) { /* ignore */ }
return defs;
}
/** 当前显示名(设置里的 displayName,默认 apes-sidebar)。 */
function displayNameOf(ctx) {
try {
var bs = ctx.get ? ctx.get('betterSidebar') : null;
var settings = readSettings(bs && bs.store ? bs.store : null);
return settings.displayName;
} catch (err) { return DISPLAY_NAME_DEFAULT; }
}
// ====================================================================
// 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}',
'#' + TASKS_STYLE_ID + '-tasks{flex:0 0 auto;margin:0 10px 2px;padding:8px 10px;border:1px solid var(--dsw-alias-border-l2,rgba(31,35,41,.08));border-radius:8px;background:var(--dsw-alias-bg-layer-1,transparent)}',
'#' + TASKS_STYLE_ID + '-tasks-head{display:flex;align-items:center;gap:6px;font-size:12px;font-weight:600;color:var(--dsw-alias-label-secondary,#5c6069);margin-bottom:6px}',
'#' + TASKS_STYLE_ID + '-tasks-count{margin-left:auto;padding:1px 7px;border-radius:999px;background:rgba(34,197,94,.12);color:var(--dsw-alias-state-success-primary,#16a34a);font-size:10px}',
'#' + TASKS_STYLE_ID + '-tasks-row{display:flex;align-items:center;gap:8px;padding:4px 2px;border-radius:6px;cursor:pointer;font-size:12px}',
'#' + TASKS_STYLE_ID + '-tasks-row:hover{background:var(--dsw-alias-interactive-bg-hover,rgba(0,0,0,.04))}',
'#' + TASKS_STYLE_ID + '-tasks-name{flex:1;min-width:0;overflow:hidden;text-overflow:ellipsis;white-space:nowrap}',
'#' + TASKS_STYLE_ID + '-tasks-none{color:var(--dsw-alias-label-tertiary,#8f959e);font-size:11px;padding:2px 0}'
].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); })));
}
}
// 「运行中任务」区:当前会话的子代理拓扑(运行中优先),实时计数
var runningChildren = [];
var allIds = Object.keys(byId || {});
for (var ri = 0; ri < allIds.length; ri++) {
var rid = allIds[ri];
var rentry = byId[rid];
if (!rentry) continue;
if (rid === currentId) continue;
var rp = rentry.parentSessionId || rentry.parentId || rentry.parent;
var isChild = (rp && rp === currentId) || (currentId && (rid.indexOf(currentId + ':') === 0 || rid.indexOf(currentId + '/') === 0));
if (!isChild) continue;
runningChildren.push({
id: rid,
title: sessionTitleOf(rentry) || rid,
running: rentry.running === true
});
}
runningChildren.sort(function (a, b) { return (a.running === b.running) ? 0 : (a.running ? -1 : 1); });
return e('div', { id: TASKS_STYLE_ID + '-root' },
runningChildren.length > 0 ? e('div', { className: TASKS_STYLE_ID + '-tasks' },
e('div', { className: TASKS_STYLE_ID + '-tasks-head' },
'运行中任务',
e('span', { className: TASKS_STYLE_ID + '-tasks-count' }, String(runningChildren.length))),
runningChildren.map(function (rc) {
return e('div', { key: rc.id, className: TASKS_STYLE_ID + '-tasks-row', title: rc.id,
onClick: function () { try { ctx.sessions.open(rc.id); } catch (err) {} } },
e('span', { className: TASKS_STYLE_ID + '-dot' + (rc.running ? ' is-running' : '') }),
e('span', { className: TASKS_STYLE_ID + '-tasks-name' }, rc.title));
})) : null,
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); }
};
}
// ====================================================================
// Apps Tab(Tutti 式应用中心)
// ====================================================================
/** 默认应用清单:deepseek 三件套(视图 id 与 slots 注册一致)。 */
var DEFAULT_APPS = [
{ id: 'ipollowork-design-studio', name: 'Design', desc: 'DeepSeek iDesign · 设计稿/UI', viewId: 'ipollowork-design-studio' },
{ id: 'ipollowork-ppt-studio', name: 'PPT', desc: 'DeepSeek iPPT · 演示文稿', viewId: 'ipollowork-ppt-studio' },
{ id: 'ipollowork-video-studio', name: 'Video', desc: 'DeepSeek iVideo · 视频创作', viewId: 'ipollowork-video-studio' }
];
var APPS_STYLE_ID = 'apes-sidebar-apps-style';
var APPS_CSS = [
'#' + APPS_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}',
'#' + APPS_STYLE_ID + '-root *{box-sizing:border-box}',
'#' + APPS_STYLE_ID + '-head{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))}',
'#' + APPS_STYLE_ID + '-title{flex:1;font-weight:600;font-size:13px}',
'#' + APPS_STYLE_ID + '-grid{flex:1 1 auto;min-height:0;overflow-y:auto;padding:10px;display:grid;grid-template-columns:repeat(2,1fr);gap:10px;align-content:start}',
'#' + APPS_STYLE_ID + '-card{border:1px solid var(--dsw-alias-border-l2,rgba(31,35,41,.08));border-radius:10px;padding:12px;cursor:pointer;background:var(--dsw-alias-bg-layer-1,transparent);display:flex;flex-direction:column;gap:6px;transition:border-color .15s,background .15s}',
'#' + APPS_STYLE_ID + '-card:hover{border-color:var(--dsw-alias-brand-primary,#3370ff);background:var(--dsw-alias-interactive-bg-hover,rgba(0,0,0,.04))}',
'#' + APPS_STYLE_ID + '-card-icon{width:34px;height:34px;border-radius:8px;display:flex;align-items:center;justify-content:center;font-size:16px;font-weight:700;color:#fff;background:linear-gradient(135deg,#3370ff,#7c3aed)}',
'#' + APPS_STYLE_ID + '-card-name{font-weight:600;font-size:13px}',
'#' + APPS_STYLE_ID + '-card-desc{color:var(--dsw-alias-label-tertiary,#8f959e);font-size:11px;line-height:1.4}',
'#' + APPS_STYLE_ID + '-card-open{margin-top:2px;font-size:11px;color:var(--dsw-alias-brand-primary,#3370ff)}',
'#' + APPS_STYLE_ID + '-hint{padding:14px;color:var(--dsw-alias-label-tertiary,#8f959e);font-size:12px;line-height:1.6}'
].join('\n');
/** 应用清单:配置里的 apps.list 覆盖默认(防御性合并)。 */
function appsList(store) {
try {
var raw = store && store.prefs && store.prefs.pluginSettings && store.prefs.pluginSettings[SETTINGS_NS] ? store.prefs.pluginSettings[SETTINGS_NS] : null;
var custom = raw && Array.isArray(raw.appsList) ? raw.appsList : null;
if (custom && custom.length > 0) return custom;
} catch (err) { /* ignore */ }
return DEFAULT_APPS;
}
/** 尝试在会话视图打开应用(viewId = conversation.view slot 的 id)。 */
function openAppView(ctx, app) {
var viewId = app.viewId || app.id;
try {
// 方式1:uiConversation 视图激活(若公开)
var uc = ctx.get ? ctx.get('uiConversation') : null;
if (uc && typeof uc.activateView === 'function') { uc.activateView(viewId); return true; }
if (uc && uc.views && typeof uc.views.activate === 'function') { uc.views.activate(viewId); return true; }
} catch (err) { /* ignore */ }
try {
// 方式2:slots 激活 conversation.view
var slots = ctx.get ? ctx.get('slots') : null;
if (slots && typeof slots.set === 'function') { slots.set('conversation.view', viewId); return true; }
} catch (err) { /* ignore */ }
// 降级:提示在会话视图顶栏手动切换
return false;
}
function AppsView(props) {
var ctx = props.ctx;
var apps = appsList(props.store);
return e('div', { id: APPS_STYLE_ID + '-root' },
e('div', { id: APPS_STYLE_ID + '-head' },
e('span', { id: APPS_STYLE_ID + '-title' }, t('apps.title'))),
e('div', { id: APPS_STYLE_ID + '-grid' },
apps.map(function (app) {
return e('div', { key: app.id || app.name, id: APPS_STYLE_ID + '-card',
title: app.desc || app.name,
onClick: function () { openAppView(ctx, app); } },
e('div', { id: APPS_STYLE_ID + '-card-icon' }, String(app.name || 'A').charAt(0).toUpperCase()),
e('div', { id: APPS_STYLE_ID + '-card-name' }, app.name || app.id),
app.desc ? e('div', { id: APPS_STYLE_ID + '-card-desc' }, app.desc) : null,
e('div', { id: APPS_STYLE_ID + '-card-open' }, t('apps.open')));
})),
e('div', { id: APPS_STYLE_ID + '-hint' }, t('apps.hint')));
}
function appsTabDescriptor(ctx) {
return {
id: APPS_TAB_ID,
title: function () { return t('apps.title'); },
icon: function (size) { return e(primitives.IconGridOutline16 || primitives.IconAppsOutline16, { size: size }); },
order: 90,
single: true,
component: function (props) { return e(AppsView, 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);
var displayName = displayNameOf(ctx);
var avatarLetter = String(displayName).charAt(0).toUpperCase() || 'A';
return e('div', { id: PROFILE_STYLE_ID + '-root' },
e('div', { id: PROFILE_STYLE_ID + '-card' },
e('div', { id: PROFILE_STYLE_ID + '-avatar' }, avatarLetter),
e('div', { id: PROFILE_STYLE_ID + '-name' }, displayName),
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 displayNameOf(ctx); },
icon: function (size) { return e(primitives.IconUserOutline16 || primitives.IconContactOutline16, { size: size }); },
order: 110,
single: true,
// 显示名 + 功能开关设置(better-sidebar 设置页「侧边卡片」)
settings: {
pluginToggles: [
{
key: "displayName",
title: function () { return '显示名'; },
desc: function () { return "默认 apes-sidebar;预设 feyOS,或输入任意名字"; },
type: "select",
options: [
{ value: DISPLAY_NAME_DEFAULT, title: function () { return 'apes-sidebar(默认)'; } },
{ value: DISPLAY_NAME_PRESET_FEYOS, title: function () { return 'feyOS'; } }
]
}
]
},
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(APPS_STYLE_ID, APPS_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.enableApps) disposers.push(ctx.betterSidebar.registerTab(appsTabDescriptor(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(appsTabDescriptor(ctx)));
disposers.push(ctx.betterSidebar.registerTab(profileTabDescriptor(ctx)));
}
return function () {
for (var i = 0; i < disposers.length; i++) { try { disposers[i](); } catch (err) { } }
};
}, "apes-sidebar: register tabs");
ctx.effect(function () {
return function () {
disposeStyles(TASKS_STYLE_ID);
disposeStyles(AGENTS_STYLE_ATTR);
disposeStyles(APPS_STYLE_ID);
disposeStyles(PROFILE_STYLE_ID);
};
}, "apes-sidebar: dispose styles");
}
module.exports = { name: name, inject: inject, apply: apply };
return module.exports;
}
});