@suifei/dsh-ai-team 0.3.29 → 0.3.31

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,18 @@
1
1
  # Changelog
2
2
 
3
+ ## 0.3.31 — 2026-11-10
4
+
5
+ - 信息性 Thread 汇报(“推进记录/备案/Human 决策收到”)不再生成 Human 决策收件箱项,避免“打开 Task 后无事可做”;
6
+ - 收件箱只保留真正的问题:`askHuman`、报告六问、失败/需要决定;
7
+ - 旧账本里的同类 `thread_mention` 项在 bootstrap/client 中隐藏,不再计入“待你处理”;
8
+ - 真正决策提交后仍自动回写 Thread 并唤醒 owner,不需要 Human 再发一句话。
9
+
10
+ ## 0.3.30 — 2026-11-10
11
+
12
+ - 已提交的决策项增加「重新决策」:复用原六问/选项/建议重开一份未读项,可直接采纳建议后修改;
13
+ - 看板已完成默认展开、按 Task 号倒序;
14
+ - 决策建议逐条/全部采纳。
15
+
3
16
  ## 0.3.29 — 2026-11-10
4
17
 
5
18
  - 看板「已完成」默认展开,不再先折叠成「展开全部」;
package/README.md CHANGED
@@ -87,7 +87,7 @@ dsh --profile ai-team --no-open --port 33271
87
87
  - **主对话**:Human 的一句话进入 Main stream,Project owner 拆 Task;顶部「团队动态」直接显示正在推进的 Task 和运行中的成员,点击 Task 可打开 Thread。
88
88
  - **看板**:四列;进行中的卡片显示 accountable owner 与执行者;数量只统计 active Task。
89
89
  - **态势**:四象限——需要你、团队在做、等待受阻、最近完成。点击条目打开对应 Task 的 Thread。
90
- - **收件箱**:跨 Project 聚合;顶部「待你处理 · N」一键进入;决策项直接展开全部问题与选项,可当场选择/填写并提交,不用再翻 Thread/报告。
90
+ - **收件箱**:跨 Project 聚合;顶部「待你处理 · N」一键进入;只放真正需要 Human 的决策/失败,Agent 的进度汇报留在 Thread/动态,不制造“打开 Task 后无事可做”。
91
91
  - **直接对话**:点击成员名字直接打开该 Agent 的 DSH 原生 Session;没有 session 会先激活;打不开时落到成员资料,资料里有「直接对话」按钮。
92
92
  - **成员任务**:成员资料里点「他的任务」,查看他 owner/assignee 的 Task。
93
93
  - **成员资料**:点「⋯」改名、职责、personality、provider/model、运行配方、notes、skills、re-distill、离队/人才库。
package/client.js CHANGED
@@ -118,6 +118,9 @@ window.__ModuleLoader__.load({
118
118
  decisionAdopt: '采纳',
119
119
  decisionAdoptAll: '全部采纳建议',
120
120
  decisionAdoptHint: '建议可直接采纳,再按需修改',
121
+ decisionReopen: '重新决策',
122
+ decisionReopenSuffix: '复核',
123
+ decisionReopened: '已重开一份可编辑的决策项。',
121
124
  decisionSubmit: '提交决策',
122
125
  decisionSubmitted: '决策已提交给团队。',
123
126
  inboxEmptyAction: '看任务看板',
@@ -433,6 +436,9 @@ window.__ModuleLoader__.load({
433
436
  decisionAdopt: 'Adopt',
434
437
  decisionAdoptAll: 'Adopt all suggestions',
435
438
  decisionAdoptHint: 'Adopt a suggestion, then edit if needed',
439
+ decisionReopen: 'Decide again',
440
+ decisionReopenSuffix: 'review',
441
+ decisionReopened: 'A new editable decision item is ready.',
436
442
  decisionSubmit: 'Submit decision',
437
443
  decisionSubmitted: 'Decision sent to the team.',
438
444
  inboxEmptyAction: 'Open the board',
@@ -2368,9 +2374,9 @@ window.__ModuleLoader__.load({
2368
2374
  const selectedTask = tasks.find((task) => task.id === selected) ?? tasks[0] ?? null;
2369
2375
  const thread = threadFor(data, selectedTask?.id);
2370
2376
  const quadrants = deriveQuadrants(data);
2371
- const inboxUnread = Number.isFinite(data.inboxUnread)
2372
- ? data.inboxUnread
2373
- : (data.inbox ?? []).filter((item) => !item.read).length;
2377
+ const inboxItems = (data.inbox ?? []).filter((item) =>
2378
+ !(item.ref?.category === 'thread_mention' && !(Array.isArray(item.ref?.questions) && item.ref.questions.length > 0)));
2379
+ const inboxUnread = inboxItems.filter((item) => item.state === 'unread').length;
2374
2380
  const q1Extra = (quadrants.q1.items ?? []).filter((item) => item.kind === 'inbox').length;
2375
2381
  const threadHead = thread?.headSeq ?? thread?.entries?.length ?? 0;
2376
2382
  const followingOf = (card) => (Object.prototype.hasOwnProperty.call(follows, card.id) ? follows[card.id] : Boolean(card.following));
@@ -2864,6 +2870,20 @@ window.__ModuleLoader__.load({
2864
2870
  say(tf(t, 'sendFailed', { message: error?.message ?? String(error) }));
2865
2871
  }
2866
2872
  }
2873
+ async function reopenDecision(item) {
2874
+ try {
2875
+ await runCommand('decision.request', {
2876
+ title: (item.ref?.title || t('inboxDecisionTitle')) + ' · ' + t('decisionReopenSuffix'),
2877
+ questions: item.ref?.questions ?? [],
2878
+ taskId: item.ref?.taskId ?? null,
2879
+ source: item.ref?.source ?? 'reopen',
2880
+ }, item.projectId ?? projectId);
2881
+ say(t('decisionReopened'));
2882
+ await reloadData();
2883
+ } catch (error) {
2884
+ say(tf(t, 'sendFailed', { message: error?.message ?? String(error) }));
2885
+ }
2886
+ }
2867
2887
  async function ackInbox(item) {
2868
2888
  try {
2869
2889
  await runCommand('inbox.ack', { itemId: item.id, disposition: 'read' }, item.projectId ?? projectId);
@@ -3088,8 +3108,8 @@ window.__ModuleLoader__.load({
3088
3108
  const body = inInbox
3089
3109
  ? h('div', { className: 'at-stream', role: 'region', 'aria-label': t('inboxTitle') },
3090
3110
  h('div', { className: 'at-streaminner' },
3091
- (data.inbox ?? []).length
3092
- ? (data.inbox ?? []).map((item) => h('div', { key: item.id, className: cx('at-inboxitem', item.state !== 'unread' && 'at-inboxdone') },
3111
+ inboxItems.length
3112
+ ? inboxItems.map((item) => h('div', { key: item.id, className: cx('at-inboxitem', item.state !== 'unread' && 'at-inboxdone') },
3093
3113
  h('div', { className: 'at-inboxbody' },
3094
3114
  h('div', { className: 'at-inboxtitle' },
3095
3115
  item.type === 'decision_required'
@@ -3116,6 +3136,9 @@ window.__ModuleLoader__.load({
3116
3136
  item.state !== 'unread' ? h('div', { className: 'at-inboxstate' }, item.state) : null),
3117
3137
  h('div', { className: 'at-inboxactions' },
3118
3138
  item.ref?.taskId ? h('button', { className: 'at-btn at-btn-small', type: 'button', onClick: () => openInboxTask(item) }, t('inboxOpenTask')) : null,
3139
+ item.type === 'decision_required' && item.state !== 'unread' && Array.isArray(item.ref?.questions) && item.ref.questions.length
3140
+ ? h('button', { className: 'at-btn at-btn-small', type: 'button', onClick: () => reopenDecision(item) }, t('decisionReopen'))
3141
+ : null,
3119
3142
  item.state === 'unread' && !(item.type === 'decision_required' && Array.isArray(item.ref?.questions) && item.ref.questions.length)
3120
3143
  ? h('button', { className: 'at-btn at-btn-small at-btn-primary', type: 'button', onClick: () => ackInbox(item) }, t('inboxAck'))
3121
3144
  : null)))
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.29';
37
+ const VERSION = '0.3.31';
38
38
  const PROJECT_ID = 'prj_main';
39
39
  const HUMAN_ID = 'mem_human';
40
40
 
@@ -124,6 +124,9 @@ export class TeamService {
124
124
  const stream = project === undefined ? [] : (state.stream ?? []).filter((entry) => entry.projectId === project.id);
125
125
  const selected = threadTaskId ?? tasks.find((task) => task.status === 'in_progress')?.id ?? tasks[0]?.id;
126
126
  const thread = selected === undefined ? null : (state.threads?.[selected] ?? { taskId: selected, headSeq: 0, entries: [] });
127
+ const inbox = Object.values(state.inbox ?? {}).filter((item) =>
128
+ (item.memberId === state.humanMemberId || item.memberId === 'h1')
129
+ && !(item.ref?.category === 'thread_mention' && !(Array.isArray(item.ref?.questions) && item.ref.questions.length > 0)));
127
130
  return {
128
131
  project: project ?? null,
129
132
  projects,
@@ -132,7 +135,7 @@ export class TeamService {
132
135
  tasks,
133
136
  stream,
134
137
  thread,
135
- inbox: Object.values(state.inbox ?? {}).filter((item) => item.memberId === state.humanMemberId || item.memberId === 'h1'),
138
+ inbox,
136
139
  board: project === undefined ? null : this.board(project.id),
137
140
  quadrants: project === undefined ? null : this.quadrants(project.id),
138
141
  };
@@ -149,11 +152,6 @@ export class TeamService {
149
152
  ?? null;
150
153
  if (humanId === null) return 0;
151
154
  const projectId = Object.keys(this.state.projects ?? {})[0];
152
- const existing = new Set(
153
- Object.values(this.state.inbox ?? {})
154
- .filter((item) => item.memberId === humanId && item.ref?.entrySeq !== undefined)
155
- .map((item) => `${item.ref?.taskId}:${item.ref?.entrySeq}`),
156
- );
157
155
  let reportPath = null;
158
156
  for (const task of Object.values(this.state.tasks ?? {})) {
159
157
  for (const entry of this.state.threads?.[task.id]?.entries ?? []) {
@@ -168,18 +166,17 @@ export class TeamService {
168
166
  reportQuestions = parseDecisionQuestions(readFileSync(reportPath, 'utf8'));
169
167
  } catch { /* report may be unavailable; keep the Thread fallback */ }
170
168
  }
169
+ if (reportQuestions.length === 0) return 0;
171
170
  const candidates = [];
172
171
  for (const task of Object.values(this.state.tasks ?? {})) {
173
172
  if (task.tombstone === true) continue;
174
173
  for (const entry of this.state.threads?.[task.id]?.entries ?? []) {
175
174
  const author = this.state.members?.[entry.authorId];
176
175
  if (author?.kind !== 'agent' || !mentionsHuman(entry.body)) continue;
177
- const key = `${task.id}:${entry.seq}`;
178
- if (existing.has(key) && reportQuestions.length === 0) continue;
179
- candidates.push({ task, entry, key });
176
+ candidates.push({ task, entry, key: `${task.id}:${entry.seq}` });
180
177
  }
181
178
  }
182
- if (reportQuestions.length > 0 && candidates.length > 0) {
179
+ if (candidates.length > 0) {
183
180
  const alreadyStructured = Object.values(this.state.inbox ?? {}).some((item) =>
184
181
  item.memberId === humanId
185
182
  && item.ref?.source === reportPath
@@ -212,33 +209,7 @@ export class TeamService {
212
209
  });
213
210
  return 1;
214
211
  }
215
- let created = 0;
216
- for (const candidate of candidates) {
217
- const { task, entry } = candidate;
218
- existing.add(candidate.key);
219
- const item = createInboxItem({
220
- memberId: humanId,
221
- type: 'decision_required',
222
- ref: {
223
- taskId: task.id,
224
- entrySeq: entry.seq,
225
- memberId: entry.authorId,
226
- memberName: this.state.members?.[entry.authorId]?.name ?? entry.authorId,
227
- category: 'thread_mention',
228
- title: String(entry.body).slice(0, 180),
229
- },
230
- requiresDecision: true,
231
- }, { at: this.now() });
232
- await this.ledger.append({
233
- type: 'inbox/item-created',
234
- payload: { itemId: item.id, ...item },
235
- actor: { kind: 'system' },
236
- projectId,
237
- at: this.now(),
238
- });
239
- created += 1;
240
- }
241
- return created;
212
+ return 0;
242
213
  }
243
214
 
244
215
  /**
@@ -926,42 +897,6 @@ export class TeamService {
926
897
  projectId: command.projectId,
927
898
  at: this.now(),
928
899
  });
929
- // A Thread message that explicitly asks Human to decide must land in the
930
- // Human inbox; otherwise the board looks busy while the Human never sees
931
- // the question (the exact failure mode behind “对话面板没有反馈”).
932
- const body = typeof payload.body === 'string' ? payload.body : '';
933
- if (actorKind === 'agent' && mentionsHuman(body)) {
934
- const humanId = this.state.humanMemberId
935
- ?? Object.values(this.state.members ?? {}).find((candidate) => candidate.kind === 'human')?.id
936
- ?? null;
937
- const duplicate = humanId === null ? undefined : Object.values(this.state.inbox ?? {}).find((item) =>
938
- item.memberId === humanId
939
- && item.ref?.taskId === taskId
940
- && item.ref?.entrySeq === outcome.entry.seq
941
- && !['resolved', 'dismissed', 'superseded'].includes(item.state));
942
- if (humanId !== null && duplicate === undefined) {
943
- const item = createInboxItem({
944
- memberId: humanId,
945
- type: 'decision_required',
946
- ref: {
947
- taskId,
948
- entrySeq: outcome.entry.seq,
949
- memberId: authorId,
950
- memberName: this.state.members?.[authorId]?.name ?? authorId,
951
- category: 'thread_mention',
952
- title: String(body).slice(0, 180),
953
- },
954
- requiresDecision: true,
955
- }, { at: this.now() });
956
- await this.ledger.append({
957
- type: 'inbox/item-created',
958
- payload: { itemId: item.id, ...item },
959
- actor: { kind: 'system' },
960
- projectId: command.projectId,
961
- at: this.now(),
962
- });
963
- }
964
- }
965
900
  return { thread: outcome.thread, entry: outcome.entry };
966
901
  }
967
902
 
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@suifei/dsh-ai-team",
3
- "version": "0.3.29",
3
+ "version": "0.3.31",
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",