dsh-social-trace 0.1.18 → 0.1.20

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -22,20 +22,33 @@ export function hostAgentRuntime(getService, model) {
22
22
  const handles = new Map();
23
23
  const registry = () => {
24
24
  const agents = getService('agents');
25
- if (!agents?.create || !agents?.resume || !getService('compaction')) throw new Error('DSH Agent 或自动压缩服务未就绪,请更新或启用宿主服务;未退回旧的简化执行循环');
25
+ if (!agents?.create || !agents?.resume) throw new Error('DSH Agent 服务未就绪,请检查宿主插件是否已启用');
26
+ const presets = getService('agentPresets');
27
+ if (!getService('compaction') && (!presets?.mount || !presets?.serviceFor)) throw new Error('DSH Agent 预设或自动压缩服务未就绪,请检查宿主插件是否已启用');
26
28
  return agents;
27
29
  };
28
30
  return {
29
31
  selection: () => { registry(); return model.selection(); },
30
32
  available() { try { registry(); return model.available(); } catch { return false; } },
31
- info() { return { driver: 'dsh-agent', available: this.available(), compaction: !!getService('compaction') }; },
33
+ info() {
34
+ let error = '';
35
+ try { this.selection(); } catch (cause) { error = cause.message; }
36
+ return { driver: 'dsh-agent', available: !error, compaction: !!getService('compaction'), ...(error ? { error } : {}) };
37
+ },
32
38
  async run({ sessionId, system, messages, tools, catalog = tools, selection, signal, resume = false, onSession, onTool, onText, onPhase, onModel }) {
33
39
  const agents = registry(), hostId = 'social-trace-' + sessionId;
34
40
  let entry = handles.get(hostId);
35
41
  const fresh = !entry && !resume;
36
42
  if (!entry) {
37
43
  entry = { binding: null, handle: null };
38
- const setup = (scope) => {
44
+ const setup = async (scope, agent) => {
45
+ const presets = getService('agentPresets');
46
+ if (presets?.mount) {
47
+ const recorded = presets.presetForSession(agent.session);
48
+ const preset = await presets.mount(scope, recorded || undefined);
49
+ if (!recorded) agent.session.append('agent-preset/selected', { agentPreset: preset.id });
50
+ }
51
+ if (!getService('compaction') && !presets?.serviceFor(agent, 'compaction')) throw new Error('当前 DSH Agent 预设未提供自动压缩服务,请检查宿主预设配置');
39
52
  scope.tools.restrict({ allow: [] });
40
53
  scope.systemPrompt.section({ name: 'social-trace.business', order: 0, complete: true, text: () => entry.binding.system });
41
54
  for (const tool of catalog) scope.tools.register({ ...tool,
package/lib/client.js CHANGED
@@ -118,12 +118,12 @@
118
118
  : job.kind === 'history' && /长文接口未返回全文/.test(job.message || '')
119
119
  ? '这条微博的全文未获取到,插件会先自动重试,已有内容保留。'
120
120
  : (job.kind === 'history' ? '历史缓存已暂停:' : '新动态检查已暂停:') + (job.message || '读取未完成,已保留进度');
121
- function checkHealthReason(source, uid = '') {
121
+ function checkHealthReason(source, uid = '', targets = []) {
122
122
  const health = source?.health;
123
123
  if (!health) return '';
124
124
  const delayed = health.delayed.filter(check => !uid || check.uid === uid);
125
125
  const reasons = [];
126
- if (delayed.length) reasons.push((uid ? '这位博主' : delayed.length + ' 位博主') + '未按设定频率及时检查');
126
+ if (delayed.length) reasons.push(delayed.map(check => targets.find(target => target.uid === check.uid)?.name || 'UID ' + shortUid(check.uid)).join('、') + '未按设定频率及时检查');
127
127
  if (health.capacityLimited) reasons.push(health.activeAuthors + ' 位博主共用账号,仅检查一轮最新页就至少需要 ' + Math.ceil(health.minimumCycleMs / 1000) + ' 秒,超出了当前频率的处理能力');
128
128
  return reasons.join(';');
129
129
  }
@@ -155,7 +155,7 @@
155
155
  return result(historyOnly ? names[0] + (names.length > 1 ? '等 ' + names.length + ' 位博主' : '') + (problems.some(job => job.contentIssue || /长文接口未返回全文/.test(job.message || '')) ? ':有微博未读完整' : ':历史缓存中断') : names.length + ' 位博主的新动态检查需处理', jobs.some(job => job.status === 'queued') ? 'partial' : 'blocked', names.join('、') + ':' + taskProblem(problems[0]), 'tasks', problems[0].uid);
156
156
  }
157
157
  if (jobs.length && jobs.every(job => ['paused', 'complete'].includes(job.status))) return result('任务已暂停', 'paused', '在大屏开启需要执行的任务', 'tasks');
158
- const healthReason = jobs.some(job => job.kind === 'updates' && job.status === 'queued') && checkHealthReason(source, uid);
158
+ const healthReason = jobs.some(job => job.kind === 'updates' && job.status === 'queued') && checkHealthReason(source, uid, state.targets);
159
159
  if (healthReason) return result('检查延迟', 'partial', healthReason, 'frequency');
160
160
  const active = state.activeJobIds || [state.activeJobId];
161
161
  if (jobs.some(job => active.includes(job.id))) return result('正在读取', 'active', '新内容保存后会显示在本地档案中');
@@ -553,6 +553,30 @@
553
553
  !waiting && job.status === 'needs_review' ? h('button', { disabled: busy, className: 'st-primary', onClick: () => act('/jobs/skip-content', { id: job.id, postId: issue.postId, platform: job.platform }) }, job.kind === 'history' ? '跳过这条,继续缓存' : '跳过这条,继续检查') : null),
554
554
  !waiting ? h('p', { className: 'st-caption' }, '跳过时保留已经读到的片段和原文链接,不标记为全文。') : null);
555
555
  }
556
+ function CheckHealthNotice({ state, source, uid, openSettings, navigate }) {
557
+ const delayed = source?.health?.delayed?.filter(check => !uid || check.uid === uid) || [];
558
+ if (!delayed.length && !source?.health?.capacityLimited) return null;
559
+ const active = state.activeJobIds || [state.activeJobId];
560
+ return h('section', { className: 'st-inline-error', role: 'status', 'aria-label': '检查延迟的博主' },
561
+ h('p', null, delayed.length ? '以下博主的新动态检查有延迟,后台会继续执行,无需反复点击重试。' : checkHealthReason(source, uid, state.targets)),
562
+ delayed.length ? h('div', { style: { overflowX: 'auto' } }, h('table', null,
563
+ h('thead', null, h('tr', null, ['博主', '最近完成检查', '超过计划时间', '当前情况', '查看'].map(label => h('th', { key: label }, label)))),
564
+ h('tbody', null, delayed.map(check => {
565
+ const owner = state.targets.find(target => target.uid === check.uid);
566
+ const job = state.jobs.find(job => job.uid === check.uid && job.kind === 'updates');
567
+ const progress = state.progress?.find(item => item.uid === check.uid);
568
+ const name = owner?.name || 'UID ' + shortUid(check.uid);
569
+ const late = Number(check.lateMs);
570
+ const reason = active.includes(job?.id) ? '正在检查' : progress?.catchup ? '正在补查离线期间的消息' : '等待后台检查,延迟原因尚未确认';
571
+ return h('tr', { key: check.uid },
572
+ h('td', null, h('strong', null, name), h('small', { style: { display: 'block' } }, platformNames[source.id] || source.id, ' · UID ', shortUid(owner?.external_uid || check.uid))),
573
+ h('td', null, check.lastCheckedAt ? date(check.lastCheckedAt) : '尚无完成记录'),
574
+ h('td', null, Number.isFinite(late) ? late < 60000 ? Math.ceil(late / 1000) + ' 秒' : Math.ceil(late / 60000) + ' 分钟' : '待确认'),
575
+ h('td', null, reason),
576
+ h('td', null, uid ? h('button', { type: 'button', className: 'st-link-button', onClick: () => navigate('archive', { uid: check.uid, tab: 'frequency' }) }, '查看检查记录 →')
577
+ : h('button', { type: 'button', className: 'st-link-button', 'aria-label': '查看' + name + '的检查详情', onClick: () => openSettings({ scope: 'target', uid: check.uid, platform: source.id, tab: 'frequency' }) }, '查看详情 →')));
578
+ })))) : null);
579
+ }
556
580
  function LegacyNodeSettings({ context, state, act, busy, error, navigate, close, openSettings, embedded = false }) {
557
581
  const dialog = React.useRef(null);
558
582
  const target = state.targets.find(item => item.uid === context.uid), source = state.platforms?.find(item => item.id === context.platform);
@@ -589,7 +613,7 @@
589
613
  tab === 'frequency' ? h('form', { id: 'st-node-panel-frequency', role: 'tabpanel', 'aria-labelledby': embedded ? 'st-reader-tab-frequency' : 'st-node-tab-frequency', onSubmit: event => { event.preventDefault(); void run(target ? '/targets/policy' : '/settings', target ? { uid: target.uid, inherit: false, intervalMinutes: value.intervalMinutes, schedule: value.schedule } : { intervalMinutes: value.intervalMinutes, schedule: value.schedule }, '监控时间与频率已保存'); } },
590
614
  target ? h('p', { className: 'st-caption' }, target.policy ? '当前为这位博主单独设置。' : '已填入' + platformNames[context.platform] + '平台的时间;保存后只对这位博主生效。') : null,
591
615
  target ? h(TaskControl, { state, target, kind: 'updates', act, busy, openSettings: context => { setTab(context.tab || 'frequency'); } }) : null,
592
- checkHealthReason(source, target?.uid) ? h('p', { className: 'st-inline-error', role: 'status' }, checkHealthReason(source, target?.uid), '。登录正常不代表每次检查都及时完成。') : null,
616
+ h(CheckHealthNotice, { state, source, uid: target?.uid, openSettings, navigate }),
593
617
  progress?.recentChecks?.length ? h('details', null, h('summary', null, '最近检查记录'), h('table', null,
594
618
  h('thead', null, h('tr', null, ['检查时间', '接口结果', '返回的最新发布时间'].map(label => h('th', { key: label }, label)))),
595
619
  h('tbody', null, progress.recentChecks.slice(-6).reverse().map((check, index) => h('tr', { key: index },
@@ -1156,7 +1180,7 @@
1156
1180
  const title = data?.sessions.find(session => session.id === sessionId)?.title || '新对话';
1157
1181
  const compose = h('form', { className: 'st-chat-compose', onSubmit: event => { event.preventDefault(); submit(); } },
1158
1182
  h('textarea', { ref: composer, 'aria-label': '告诉 Social Trace 你想做什么', 'aria-describedby': 'st-chat-keyboard-help', onCompositionStart: () => { composing.current = true; }, onCompositionEnd: () => { composing.current = false; }, onKeyDown: event => { if (event.key === 'Enter' && !event.shiftKey && !event.altKey && !event.repeat && !event.nativeEvent.isComposing && event.nativeEvent.keyCode !== 229 && !composing.current) { event.preventDefault(); submit(); } }, placeholder: '想了解谁的动态,或安排什么任务?', value: input, maxLength: 4000, onChange: event => changeInput(event.target.value) }),
1159
- h('div', { className: 'st-chat-compose-footer' }, h('span', { className: 'st-caption' }, !data ? '正在连接模型服务…' : data.available ? 'DSH 默认模型 · 本地资料' : '请先在 DSH 配置默认模型'), h('button', { className: 'st-primary st-chat-send', 'aria-label': '发送', disabled: !canSend }, '↑')));
1183
+ h('div', { className: 'st-chat-compose-footer' }, h('span', { className: 'st-caption' }, !data ? '正在连接模型服务…' : data.available ? 'DSH 默认模型 · 本地资料' : data.runtime?.error || '请先在 DSH 配置默认模型'), h('button', { className: 'st-primary st-chat-send', 'aria-label': '发送', disabled: !canSend }, '↑')));
1160
1184
  const keyboardHelp = h('p', { id: 'st-chat-keyboard-help', className: 'st-chat-disclosure' }, 'Enter 发送 · Shift + Enter 换行。修改监控前需你确认。');
1161
1185
  return h('section', { className: 'st-chat' + (!data?.runs.length ? ' is-empty' : ''), 'aria-label': 'Social Trace 业务 Chat' },
1162
1186
  h('header', { className: 'st-chat-toolbar', 'aria-label': '对话标题栏' }, h('div', { className: 'st-chat-title' }, navIcon('chat'), h('h1', { title: title }, title)), h('div', { className: 'st-chat-header-actions' },
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "dsh-social-trace",
3
- "version": "0.1.18",
3
+ "version": "0.1.20",
4
4
  "description": "运行在 DeepSeek Harness 上的博主言论追踪与分析 Agent,支持微博、雪球与抖音",
5
5
  "type": "module",
6
6
  "license": "MIT",