@suifei/dsh-ai-team 0.3.16 → 0.3.18
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 +11 -0
- package/README.md +2 -2
- package/client.js +8 -1
- package/index.js +1 -1
- package/lib/host/project-runtime.js +1 -0
- package/lib/host/service.js +90 -0
- package/lib/host/views.js +1 -1
- package/package.json +1 -1
package/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,16 @@
|
|
|
1
1
|
# Changelog
|
|
2
2
|
|
|
3
|
+
## 0.3.18 — 2026-11-10
|
|
4
|
+
|
|
5
|
+
- 打开 Project 时回填已有 Thread 中对 Human 的拍板请求到收件箱;
|
|
6
|
+
- 修复升级前已有的“Thread 里等 Human 决定,但收件箱为空”。
|
|
7
|
+
|
|
8
|
+
## 0.3.17 — 2026-11-10
|
|
9
|
+
|
|
10
|
+
- Thread 中 Agent 明确提到 Human / 请你拍板时,自动创建 Human 决策收件箱项;
|
|
11
|
+
- 团队动态计数改为真实状态拆分:进行中 / 待处理 / 待验收,不再把待处理算成“正在推进”;
|
|
12
|
+
- 收件箱的决策项保留 Thread 标题,便于识别。
|
|
13
|
+
|
|
3
14
|
## 0.3.16 — 2026-11-10
|
|
4
15
|
|
|
5
16
|
- 点击 Agent 列表中的成员名字直接打开真实对话;
|
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
|
|
90
|
+
- **收件箱**:跨 Project 聚合;失败、需要你决定、关注 Task 的完成报告,以及 Agent 在 Thread 里明确请你拍板的消息都在这里。
|
|
91
91
|
- **直接对话**:点击成员名字直接打开该 Agent 的 DSH 原生 Session;没有 session 会先激活;打不开时落到成员资料,资料里有「直接对话」按钮。
|
|
92
92
|
- **成员任务**:成员资料里点「他的任务」,查看他 owner/assignee 的 Task。
|
|
93
93
|
- **成员资料**:点「⋯」改名、职责、personality、provider/model、运行配方、notes、skills、re-distill、离队/人才库。
|
|
@@ -102,7 +102,7 @@ dsh --profile ai-team --no-open --port 33271
|
|
|
102
102
|
| Task | 唯一 accountable owner;pending / in_progress / pending_acceptance / done / cancelled |
|
|
103
103
|
| Thread | append-only;回复带 `baseSeq`,旧基线会被拒并说明变化 |
|
|
104
104
|
| 验收 | maker 不能验收;QA 硬门禁;`qaChecklist` 逐项通过 |
|
|
105
|
-
| 收件箱 | Human/Agent 各自拉取;跨 Project
|
|
105
|
+
| 收件箱 | Human/Agent 各自拉取;跨 Project 聚合;Thread 中提到 Human 会自动生成决策项 |
|
|
106
106
|
| 关注 | 关注 Task;完成后收到 `followed_task_update` |
|
|
107
107
|
| 默认成员 | 新 Project 初始化墨渊 / 青岚 / 白砚;可改名、改职责、改个性、改模型 |
|
|
108
108
|
| 成员 | 持久 session;点击名字直接对话;「⋯」资料里有任务视图与直接对话 |
|
package/client.js
CHANGED
|
@@ -1581,10 +1581,17 @@ window.__ModuleLoader__.load({
|
|
|
1581
1581
|
const running = agents.filter((agent) => agent.status === 'running');
|
|
1582
1582
|
const starting = active.length === 0 && tasks.length === 0 && running.length > 0;
|
|
1583
1583
|
if (active.length === 0 && !starting) return null;
|
|
1584
|
+
const counts = { in_progress: 0, pending: 0, pending_acceptance: 0 };
|
|
1585
|
+
for (const task of active) counts[task.status] += 1;
|
|
1586
|
+
const summary = [
|
|
1587
|
+
counts.in_progress ? `${counts.in_progress} ${t('stInProgress')}` : '',
|
|
1588
|
+
counts.pending ? `${counts.pending} ${t('stPending')}` : '',
|
|
1589
|
+
counts.pending_acceptance ? `${counts.pending_acceptance} ${t('stPendingAcceptance')}` : '',
|
|
1590
|
+
].filter(Boolean).join(' · ');
|
|
1584
1591
|
return h('section', { className: 'at-teampulse', 'aria-label': t('teamPulseTitle') },
|
|
1585
1592
|
h('div', { className: 'at-teampulsehead' },
|
|
1586
1593
|
h('span', { className: 'at-teampulsetitle' }, t('teamPulseTitle')),
|
|
1587
|
-
h('span', { className: 'at-teampulsecount' },
|
|
1594
|
+
h('span', { className: 'at-teampulsecount' }, summary || t('teamPulseWorking'))),
|
|
1588
1595
|
active.length
|
|
1589
1596
|
? h('div', { className: 'at-teampulselist' }, active.map((task) => h('button', {
|
|
1590
1597
|
key: task.id,
|
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.
|
|
37
|
+
const VERSION = '0.3.18';
|
|
38
38
|
const PROJECT_ID = 'prj_main';
|
|
39
39
|
const HUMAN_ID = 'mem_human';
|
|
40
40
|
|
package/lib/host/service.js
CHANGED
|
@@ -19,6 +19,11 @@ import { boardView, quadrantsView } from './views.js';
|
|
|
19
19
|
import { applyMemberConfigPatch, normalizeMemberConfig } from '../domain/member-config.js';
|
|
20
20
|
import { isQaMember, unfinishedTasksFor } from '../domain/member-profile.js';
|
|
21
21
|
|
|
22
|
+
function mentionsHuman(body) {
|
|
23
|
+
return typeof body === 'string'
|
|
24
|
+
&& /(^|[^A-Za-z])(human|@你|待你|需要你|请你|等你|你拍板)([^A-Za-z]|$)/i.test(body);
|
|
25
|
+
}
|
|
26
|
+
|
|
22
27
|
/**
|
|
23
28
|
* Team domain service on top of a Project ledger.
|
|
24
29
|
*
|
|
@@ -131,6 +136,56 @@ export class TeamService {
|
|
|
131
136
|
};
|
|
132
137
|
}
|
|
133
138
|
|
|
139
|
+
/**
|
|
140
|
+
* Create missing Human decision items for existing Agent Thread messages
|
|
141
|
+
* that explicitly asked Human to decide. Runs on Project open once; the
|
|
142
|
+
* per-entry dedupe makes it idempotent.
|
|
143
|
+
*/
|
|
144
|
+
async backfillHumanMentions() {
|
|
145
|
+
const humanId = this.state.humanMemberId
|
|
146
|
+
?? Object.values(this.state.members ?? {}).find((member) => member.kind === 'human')?.id
|
|
147
|
+
?? null;
|
|
148
|
+
if (humanId === null) return 0;
|
|
149
|
+
const projectId = Object.keys(this.state.projects ?? {})[0];
|
|
150
|
+
const existing = new Set(
|
|
151
|
+
Object.values(this.state.inbox ?? {})
|
|
152
|
+
.filter((item) => item.memberId === humanId && item.ref?.entrySeq !== undefined)
|
|
153
|
+
.map((item) => `${item.ref?.taskId}:${item.ref?.entrySeq}`),
|
|
154
|
+
);
|
|
155
|
+
let created = 0;
|
|
156
|
+
for (const task of Object.values(this.state.tasks ?? {})) {
|
|
157
|
+
if (task.tombstone === true) continue;
|
|
158
|
+
for (const entry of this.state.threads?.[task.id]?.entries ?? []) {
|
|
159
|
+
const author = this.state.members?.[entry.authorId];
|
|
160
|
+
if (author?.kind !== 'agent' || !mentionsHuman(entry.body)) continue;
|
|
161
|
+
const key = `${task.id}:${entry.seq}`;
|
|
162
|
+
if (existing.has(key)) continue;
|
|
163
|
+
existing.add(key);
|
|
164
|
+
const item = createInboxItem({
|
|
165
|
+
memberId: humanId,
|
|
166
|
+
type: 'decision_required',
|
|
167
|
+
ref: {
|
|
168
|
+
taskId: task.id,
|
|
169
|
+
entrySeq: entry.seq,
|
|
170
|
+
memberId: entry.authorId,
|
|
171
|
+
category: 'thread_mention',
|
|
172
|
+
title: String(entry.body).slice(0, 180),
|
|
173
|
+
},
|
|
174
|
+
requiresDecision: true,
|
|
175
|
+
}, { at: this.now() });
|
|
176
|
+
await this.ledger.append({
|
|
177
|
+
type: 'inbox/item-created',
|
|
178
|
+
payload: { itemId: item.id, ...item },
|
|
179
|
+
actor: { kind: 'system' },
|
|
180
|
+
projectId,
|
|
181
|
+
at: this.now(),
|
|
182
|
+
});
|
|
183
|
+
created += 1;
|
|
184
|
+
}
|
|
185
|
+
}
|
|
186
|
+
return created;
|
|
187
|
+
}
|
|
188
|
+
|
|
134
189
|
/**
|
|
135
190
|
* Dispatch one command. The caller is an exact Team member (or Human) and
|
|
136
191
|
* the ledger serializes commands per Project.
|
|
@@ -796,6 +851,41 @@ export class TeamService {
|
|
|
796
851
|
projectId: command.projectId,
|
|
797
852
|
at: this.now(),
|
|
798
853
|
});
|
|
854
|
+
// A Thread message that explicitly asks Human to decide must land in the
|
|
855
|
+
// Human inbox; otherwise the board looks busy while the Human never sees
|
|
856
|
+
// the question (the exact failure mode behind “对话面板没有反馈”).
|
|
857
|
+
const body = typeof payload.body === 'string' ? payload.body : '';
|
|
858
|
+
if (actorKind === 'agent' && mentionsHuman(body)) {
|
|
859
|
+
const humanId = this.state.humanMemberId
|
|
860
|
+
?? Object.values(this.state.members ?? {}).find((candidate) => candidate.kind === 'human')?.id
|
|
861
|
+
?? null;
|
|
862
|
+
const duplicate = humanId === null ? undefined : Object.values(this.state.inbox ?? {}).find((item) =>
|
|
863
|
+
item.memberId === humanId
|
|
864
|
+
&& item.ref?.taskId === taskId
|
|
865
|
+
&& item.ref?.entrySeq === outcome.entry.seq
|
|
866
|
+
&& !['resolved', 'dismissed', 'superseded'].includes(item.state));
|
|
867
|
+
if (humanId !== null && duplicate === undefined) {
|
|
868
|
+
const item = createInboxItem({
|
|
869
|
+
memberId: humanId,
|
|
870
|
+
type: 'decision_required',
|
|
871
|
+
ref: {
|
|
872
|
+
taskId,
|
|
873
|
+
entrySeq: outcome.entry.seq,
|
|
874
|
+
memberId: authorId,
|
|
875
|
+
category: 'thread_mention',
|
|
876
|
+
title: String(body).slice(0, 180),
|
|
877
|
+
},
|
|
878
|
+
requiresDecision: true,
|
|
879
|
+
}, { at: this.now() });
|
|
880
|
+
await this.ledger.append({
|
|
881
|
+
type: 'inbox/item-created',
|
|
882
|
+
payload: { itemId: item.id, ...item },
|
|
883
|
+
actor: { kind: 'system' },
|
|
884
|
+
projectId: command.projectId,
|
|
885
|
+
at: this.now(),
|
|
886
|
+
});
|
|
887
|
+
}
|
|
888
|
+
}
|
|
799
889
|
return { thread: outcome.thread, entry: outcome.entry };
|
|
800
890
|
}
|
|
801
891
|
|
package/lib/host/views.js
CHANGED
|
@@ -151,7 +151,7 @@ export function quadrantsView(state, options = {}) {
|
|
|
151
151
|
id: item.id,
|
|
152
152
|
kind: 'inbox',
|
|
153
153
|
category: 'decision',
|
|
154
|
-
title: item.type ?? 'decision_required',
|
|
154
|
+
title: item.ref?.title ?? item.type ?? 'decision_required',
|
|
155
155
|
taskId: item.ref?.taskId ?? null,
|
|
156
156
|
taskNumber: null,
|
|
157
157
|
ownerName: null,
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@suifei/dsh-ai-team",
|
|
3
|
-
"version": "0.3.
|
|
3
|
+
"version": "0.3.18",
|
|
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",
|