@suifei/dsh-ai-team 0.3.31 → 0.3.32

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.
package/CHANGELOG.md CHANGED
@@ -1,5 +1,11 @@
1
1
  # Changelog
2
2
 
3
+ ## 0.3.32 — 2026-11-10
4
+
5
+ - 四象限「需要你」与收件箱使用同一过滤:纯进度汇报不再计入 Q1;
6
+ - 看板「需要你」筛选无 Task 但有真实收件箱决策时,显示「需要你处理 N 条」并一键打开收件箱,不再显示空任务;
7
+ - Client 侧同步过滤旧 bootstrap 的 Q1,无需重启宿主也能消除截图里的 3 条假待办。
8
+
3
9
  ## 0.3.31 — 2026-11-10
4
10
 
5
11
  - 信息性 Thread 汇报(“推进记录/备案/Human 决策收到”)不再生成 Human 决策收件箱项,避免“打开 Task 后无事可做”;
package/client.js CHANGED
@@ -331,6 +331,9 @@ window.__ModuleLoader__.load({
331
331
  filterLabel: '筛选:{name}',
332
332
  filterClear: '清除筛选',
333
333
  filterInboxExtra: '另有 {n} 条收件箱决策项',
334
+ filterInboxTitle: '需要你处理 {n} 条',
335
+ filterInboxBody: '这些是收件箱决策,不是 Task;去收件箱逐条处理。',
336
+ filterInboxAction: '打开收件箱',
334
337
  filteredEmptyTitle: '这个筛选下没有任务',
335
338
  filteredEmptyBody: '清除筛选后可看到全部任务。',
336
339
  itemCount: '{n} 项',
@@ -653,6 +656,9 @@ window.__ModuleLoader__.load({
653
656
  filterLabel: 'Filter: {name}',
654
657
  filterClear: 'Clear filter',
655
658
  filterInboxExtra: '{n} inbox decision item(s) not on the board',
659
+ filterInboxTitle: '{n} item(s) need you',
660
+ filterInboxBody: 'These are inbox decisions, not Tasks; open the inbox to handle them.',
661
+ filterInboxAction: 'Open inbox',
656
662
  filteredEmptyTitle: 'No tasks under this filter',
657
663
  filteredEmptyBody: 'Clear the filter to see all tasks.',
658
664
  itemCount: '{n} items',
@@ -847,10 +853,20 @@ window.__ModuleLoader__.load({
847
853
  };
848
854
  };
849
855
 
856
+ function isInformationalInboxItem(item) {
857
+ return item?.ref?.category === 'thread_mention'
858
+ && !(Array.isArray(item.ref?.questions) && item.ref.questions.length > 0);
859
+ }
860
+
850
861
  /** Derive the read-only quadrants projection from the bootstrap DTO (spike fallback). */
851
862
  function deriveQuadrants(data) {
852
863
  const direct = data?.quadrants;
853
- if (direct && direct.q1 && direct.q2 && direct.q3 && direct.q4) return direct;
864
+ if (direct && direct.q1 && direct.q2 && direct.q3 && direct.q4) {
865
+ const inboxById = new Map((data?.inbox ?? []).map((item) => [item.id, item]));
866
+ const q1Items = (direct.q1.items ?? []).filter((item) =>
867
+ item.kind !== 'inbox' || !isInformationalInboxItem(inboxById.get(item.id)));
868
+ return { ...direct, q1: { ...direct.q1, count: q1Items.length, items: q1Items } };
869
+ }
854
870
  const tasks = data?.tasks ?? [];
855
871
  const inbox = data?.inbox ?? [];
856
872
  const q1 = [];
@@ -1520,7 +1536,7 @@ window.__ModuleLoader__.load({
1520
1536
  })));
1521
1537
  }
1522
1538
 
1523
- function Board({ t, tasks, selected, follows, doneOpen, onToggleDone, onOpen, onToggleFollow, filter, onClearFilter, q1Extra, onBackMain }) {
1539
+ function Board({ t, tasks, selected, follows, doneOpen, onToggleDone, onOpen, onToggleFollow, filter, onClearFilter, q1Extra, onBackMain, onOpenInbox }) {
1524
1540
  const filtered = (filter === 'all' ? tasks : tasks.filter((task) => matchesFilter(task, filter)))
1525
1541
  .slice()
1526
1542
  .sort((left, right) => (right.number ?? 0) - (left.number ?? 0));
@@ -1540,13 +1556,21 @@ window.__ModuleLoader__.load({
1540
1556
  body: t('emptyBoardBody'),
1541
1557
  action: { label: t('emptyBoardAction'), onClick: onBackMain },
1542
1558
  })
1543
- : h(EmptyState, {
1544
- t,
1545
- icon: 'board',
1546
- title: t('filteredEmptyTitle'),
1547
- body: t('filteredEmptyBody'),
1548
- action: { label: t('filterClear'), onClick: onClearFilter },
1549
- }));
1559
+ : filter === 'q1' && q1Extra > 0
1560
+ ? h(EmptyState, {
1561
+ t,
1562
+ icon: 'inbox',
1563
+ title: tf(t, 'filterInboxTitle', { n: q1Extra }),
1564
+ body: t('filterInboxBody'),
1565
+ action: { label: t('filterInboxAction'), onClick: onOpenInbox },
1566
+ })
1567
+ : h(EmptyState, {
1568
+ t,
1569
+ icon: 'board',
1570
+ title: t('filteredEmptyTitle'),
1571
+ body: t('filteredEmptyBody'),
1572
+ action: { label: t('filterClear'), onClick: onClearFilter },
1573
+ }));
1550
1574
  }
1551
1575
  return h('div', { className: 'at-boardwrap', role: 'tabpanel', id: 'at-panel-board', 'aria-labelledby': 'at-tab-board' },
1552
1576
  bar,
@@ -2374,8 +2398,7 @@ window.__ModuleLoader__.load({
2374
2398
  const selectedTask = tasks.find((task) => task.id === selected) ?? tasks[0] ?? null;
2375
2399
  const thread = threadFor(data, selectedTask?.id);
2376
2400
  const quadrants = deriveQuadrants(data);
2377
- const inboxItems = (data.inbox ?? []).filter((item) =>
2378
- !(item.ref?.category === 'thread_mention' && !(Array.isArray(item.ref?.questions) && item.ref.questions.length > 0)));
2401
+ const inboxItems = (data.inbox ?? []).filter((item) => !isInformationalInboxItem(item));
2379
2402
  const inboxUnread = inboxItems.filter((item) => item.state === 'unread').length;
2380
2403
  const q1Extra = (quadrants.q1.items ?? []).filter((item) => item.kind === 'inbox').length;
2381
2404
  const threadHead = thread?.headSeq ?? thread?.entries?.length ?? 0;
@@ -3172,6 +3195,7 @@ window.__ModuleLoader__.load({
3172
3195
  filter: boardFilter,
3173
3196
  onClearFilter: () => setBoardFilter('all'),
3174
3197
  q1Extra,
3198
+ onOpenInbox: switchInbox,
3175
3199
  onBackMain: () => setTab('main'),
3176
3200
  })
3177
3201
  : tab === 'situation'
package/index.js CHANGED
@@ -34,7 +34,7 @@ export const name = 'ai-team';
34
34
  /** The workbench only makes sense where the Web carrier exists. */
35
35
  export const inject = ['webServer', 'permissionPresets', 'sandboxPolicy'];
36
36
 
37
- const VERSION = '0.3.31';
37
+ const VERSION = '0.3.32';
38
38
  const PROJECT_ID = 'prj_main';
39
39
  const HUMAN_ID = 'mem_human';
40
40
 
@@ -19,6 +19,12 @@ export const INBOX_TYPES = new Set([
19
19
 
20
20
  export const DECISION_TYPES = new Set(['decision_required']);
21
21
 
22
+ /** Progress reports mentioning Human are not actionable decisions. */
23
+ export function isInformationalThreadMention(item) {
24
+ return item?.ref?.category === 'thread_mention'
25
+ && !(Array.isArray(item.ref?.questions) && item.ref.questions.length > 0);
26
+ }
27
+
22
28
  /**
23
29
  * Create one inbox item. It points at a source object; it is not a copy of the
24
30
  * message body (the reader pulls the source on demand).
@@ -2,7 +2,7 @@ import { existsSync, readFileSync } from 'node:fs';
2
2
  import { ERRORS, fail } from '../domain/errors.js';
3
3
  import { findReportPath, parseDecisionQuestions } from './decision-report.js';
4
4
  import { ids } from '../domain/ids.js';
5
- import { ackInboxItem, createInboxItem } from '../domain/inbox.js';
5
+ import { ackInboxItem, createInboxItem, isInformationalThreadMention } from '../domain/inbox.js';
6
6
  import {
7
7
  acceptTask,
8
8
  assignTask,
@@ -126,7 +126,7 @@ export class TeamService {
126
126
  const thread = selected === undefined ? null : (state.threads?.[selected] ?? { taskId: selected, headSeq: 0, entries: [] });
127
127
  const inbox = Object.values(state.inbox ?? {}).filter((item) =>
128
128
  (item.memberId === state.humanMemberId || item.memberId === 'h1')
129
- && !(item.ref?.category === 'thread_mention' && !(Array.isArray(item.ref?.questions) && item.ref.questions.length > 0)));
129
+ && !isInformationalThreadMention(item));
130
130
  return {
131
131
  project: project ?? null,
132
132
  projects,
package/lib/host/views.js CHANGED
@@ -5,6 +5,8 @@
5
5
  * new events. The Host routes and the workbench both consume the same shapes.
6
6
  */
7
7
 
8
+ import { isInformationalThreadMention } from '../domain/inbox.js';
9
+
8
10
  const ACTIVE_MEMBER_STATUSES = new Set(['provisioning', 'running', 'idle']);
9
11
 
10
12
  function nameOf(state, memberId) {
@@ -146,6 +148,7 @@ export function quadrantsView(state, options = {}) {
146
148
 
147
149
  for (const item of Object.values(state.inbox ?? {})) {
148
150
  if (item.memberId !== humanId || item.requiresDecision !== true) continue;
151
+ if (isInformationalThreadMention(item)) continue;
149
152
  if (item.state === 'resolved' || item.state === 'dismissed' || item.state === 'superseded') continue;
150
153
  q1.push({
151
154
  id: item.id,
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@suifei/dsh-ai-team",
3
- "version": "0.3.31",
3
+ "version": "0.3.32",
4
4
  "type": "module",
5
5
  "description": "文渊阁 (Wenyuan Pavilion) — AI Team mode for DeepSeek Harness: concurrent projects, owned tasks, threads, inbox and an append-only team ledger",
6
6
  "license": "MIT",