@suifei/dsh-ai-team 0.3.18 → 0.3.19
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 +7 -0
- package/README.md +2 -2
- package/client.js +18 -1
- package/index.js +1 -1
- package/lib/host/member-tools.js +28 -2
- package/lib/host/service.js +54 -0
- package/lib/host/waker.js +1 -1
- package/package.json +1 -1
package/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,12 @@
|
|
|
1
1
|
# Changelog
|
|
2
2
|
|
|
3
|
+
## 0.3.19 — 2026-11-10
|
|
4
|
+
|
|
5
|
+
- 新增 `team askHuman`:Agent 必须把每个 Human 决策问题、选项、推荐项逐条提交;
|
|
6
|
+
- 收件箱决策项直接展开问题/选项/推荐,不再要求翻报告或 Thread;
|
|
7
|
+
- 顶部增加「待你处理 · N」一键入口;
|
|
8
|
+
- Thread 中 @Human 的普通消息仍自动生成决策项。
|
|
9
|
+
|
|
3
10
|
## 0.3.18 — 2026-11-10
|
|
4
11
|
|
|
5
12
|
- 打开 Project 时回填已有 Thread 中对 Human 的拍板请求到收件箱;
|
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 聚合;顶部「待你处理 · N」一键进入;决策项直接展开全部问题与选项,不用再翻 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 聚合;顶部待办入口;`askHuman` 结构化问题与选项直接展开 |
|
|
106
106
|
| 关注 | 关注 Task;完成后收到 `followed_task_update` |
|
|
107
107
|
| 默认成员 | 新 Project 初始化墨渊 / 青岚 / 白砚;可改名、改职责、改个性、改模型 |
|
|
108
108
|
| 成员 | 持久 session;点击名字直接对话;「⋯」资料里有任务视图与直接对话 |
|
package/client.js
CHANGED
|
@@ -277,6 +277,7 @@ window.__ModuleLoader__.load({
|
|
|
277
277
|
discussSent: '已发送给 Project owner。',
|
|
278
278
|
taskSent: '已创建 Task。',
|
|
279
279
|
sendFailed: '发送失败:{message}',
|
|
280
|
+
pendingHuman: '待你处理',
|
|
280
281
|
inboxUnread: '收件箱,{n} 条未读',
|
|
281
282
|
dismiss: '关闭提示',
|
|
282
283
|
retry: '重试',
|
|
@@ -578,6 +579,7 @@ window.__ModuleLoader__.load({
|
|
|
578
579
|
discussSent: 'Sent to the Project owner.',
|
|
579
580
|
taskSent: 'Task created.',
|
|
580
581
|
sendFailed: 'Send failed: {message}',
|
|
582
|
+
pendingHuman: 'Needs you',
|
|
581
583
|
inboxUnread: 'Inbox, {n} unread',
|
|
582
584
|
dismiss: 'Dismiss',
|
|
583
585
|
retry: 'Retry',
|
|
@@ -904,6 +906,7 @@ window.__ModuleLoader__.load({
|
|
|
904
906
|
.at-top { height: 40px; flex: 0 0 auto; display: flex; align-items: center; gap: 8px; min-width: 0; padding: 0 12px; border-bottom: 1px solid var(--dsw-alias-border-l1); background: var(--dsw-alias-bg-layer-1); }
|
|
905
907
|
.at-brand { color: var(--dsw-alias-label-primary); font-size: 12px; font-weight: 500; white-space: nowrap; }
|
|
906
908
|
.at-spike { display: inline-flex; align-items: center; height: 20px; padding: 0 6px; border: 1px solid var(--dsw-alias-border-l3); border-radius: var(--dsw-radius-xs); color: var(--dsw-alias-label-tertiary); font-size: 10px; line-height: 16px; letter-spacing: .04em; text-transform: uppercase; white-space: nowrap; }
|
|
909
|
+
.at-pending { margin-left: auto; height: 24px; padding: 0 8px; border: 1px solid var(--dsw-alias-state-warning-primary); border-radius: var(--dsw-radius-sm); background: var(--dsw-alias-bg-base); color: var(--dsw-alias-state-warning-primary); font: inherit; font-size: 11px; line-height: 16px; cursor: pointer; white-space: nowrap; }
|
|
907
910
|
.at-topproject { margin-left: auto; min-width: 0; color: var(--dsw-alias-label-tertiary); font-size: 11px; white-space: nowrap; overflow: hidden; text-overflow: ellipsis; }
|
|
908
911
|
.at-mobile { display: none; position: relative; min-width: 0; }
|
|
909
912
|
.at-mobilesummary { display: inline-flex; align-items: center; gap: 6px; min-height: 28px; padding: 0 8px; border: 1px solid var(--dsw-alias-border-l2); border-radius: var(--dsw-radius-sm); color: var(--dsw-alias-label-secondary); font-size: 11px; cursor: pointer; list-style: none; }
|
|
@@ -947,6 +950,11 @@ window.__ModuleLoader__.load({
|
|
|
947
950
|
.at-inboxitem { display: flex; align-items: flex-start; justify-content: space-between; gap: 12px; padding: 11px 13px; border: 1px solid var(--dsw-alias-border-l2); border-radius: var(--dsw-radius-sm); background: var(--dsw-alias-bg-layer-2); }
|
|
948
951
|
.at-inboxitem.at-inboxdone { opacity: 0.62; }
|
|
949
952
|
.at-inboxbody { min-width: 0; flex: 1; }
|
|
953
|
+
.at-inboxquestions { margin: 6px 0 2px; padding-left: 20px; color: var(--dsw-alias-label-secondary); font-size: 12px; line-height: 19px; }
|
|
954
|
+
.at-inboxquestions li { margin: 3px 0; }
|
|
955
|
+
.at-inboxquestion { display: block; color: var(--dsw-alias-label-primary); }
|
|
956
|
+
.at-inboxoptions { display: block; color: var(--dsw-alias-label-tertiary); font-size: 11px; }
|
|
957
|
+
.at-inboxrecommend { display: block; color: var(--dsw-alias-state-business-primary); font-size: 11px; }
|
|
950
958
|
.at-inboxtitle { color: var(--dsw-alias-label-primary); font-size: 13px; line-height: 20px; }
|
|
951
959
|
.at-inboxmeta { color: var(--dsw-alias-label-tertiary); font-size: 11px; line-height: 16px; }
|
|
952
960
|
.at-inboxstate { color: var(--dsw-alias-label-caption); font-size: 10.5px; }
|
|
@@ -2950,10 +2958,18 @@ window.__ModuleLoader__.load({
|
|
|
2950
2958
|
h('div', { className: 'at-inboxbody' },
|
|
2951
2959
|
h('div', { className: 'at-inboxtitle' },
|
|
2952
2960
|
item.type === 'decision_required'
|
|
2953
|
-
? t('inboxDecisionTitle')
|
|
2961
|
+
? (item.ref?.title || t('inboxDecisionTitle'))
|
|
2954
2962
|
: item.type === 'followed_task_update'
|
|
2955
2963
|
? tf(t, 'inboxCompletedTitle', { title: item.ref?.title ?? item.ref?.taskId ?? '' })
|
|
2956
2964
|
: t('inboxItemTitle')),
|
|
2965
|
+
Array.isArray(item.ref?.questions) && item.ref.questions.length
|
|
2966
|
+
? h('ol', { className: 'at-inboxquestions' }, item.ref.questions.map((question, index) => h('li', { key: question.id ?? index },
|
|
2967
|
+
h('span', { className: 'at-inboxquestion' }, question.text),
|
|
2968
|
+
Array.isArray(question.options) && question.options.length
|
|
2969
|
+
? h('span', { className: 'at-inboxoptions' }, question.options.join(' / '))
|
|
2970
|
+
: null,
|
|
2971
|
+
question.recommendation ? h('span', { className: 'at-inboxrecommend' }, '推荐:' + question.recommendation) : null)))
|
|
2972
|
+
: null,
|
|
2957
2973
|
h('div', { className: 'at-inboxmeta' },
|
|
2958
2974
|
item.projectName ? item.projectName + ' · ' : '',
|
|
2959
2975
|
item.ref?.category ? item.ref.category + ' · ' : '',
|
|
@@ -3024,6 +3040,7 @@ window.__ModuleLoader__.load({
|
|
|
3024
3040
|
h('div', { className: 'at-top' },
|
|
3025
3041
|
h('span', { className: 'at-brand' }, t('panel')),
|
|
3026
3042
|
h('span', { className: 'at-spike', title: t('spikeTitle') }, t('spike')),
|
|
3043
|
+
inboxUnread > 0 ? h('button', { className: 'at-pending', type: 'button', onClick: switchInbox }, t('pendingHuman') + ' · ' + unread) : null,
|
|
3027
3044
|
h('details', { className: 'at-mobile', ref: mobileRef },
|
|
3028
3045
|
h('summary', { className: 'at-mobilesummary', 'aria-label': t('menuAria') }, h(Ico, { name: 'menu', size: 14 }), h('span', null, t('menu'))),
|
|
3029
3046
|
h('div', { className: 'at-mobilepop' }, h(LeftPanel, leftProps))),
|
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.19';
|
|
38
38
|
const PROJECT_ID = 'prj_main';
|
|
39
39
|
const HUMAN_ID = 'mem_human';
|
|
40
40
|
|
package/lib/host/member-tools.js
CHANGED
|
@@ -7,7 +7,7 @@
|
|
|
7
7
|
*/
|
|
8
8
|
import { taskCard } from './views.js';
|
|
9
9
|
|
|
10
|
-
const ACTIONS = ['tasks', 'members', 'inbox', 'recall', 'create', 'member', 'evolve', 'distill', 'note', 'skill', 'claim', 'submit', 'thread', 'reply', 'accept', 'requestChanges'];
|
|
10
|
+
const ACTIONS = ['tasks', 'members', 'inbox', 'recall', 'create', 'askHuman', 'member', 'evolve', 'distill', 'note', 'skill', 'claim', 'submit', 'thread', 'reply', 'accept', 'requestChanges'];
|
|
11
11
|
|
|
12
12
|
function failure(error) {
|
|
13
13
|
return {
|
|
@@ -53,7 +53,7 @@ function rosterText(state) {
|
|
|
53
53
|
export function createTeamTool(options) {
|
|
54
54
|
return {
|
|
55
55
|
name: 'team',
|
|
56
|
-
description: 'Read and act on the AI Team board. The Project owner can create tasks with action=create and must set resultDefinition and ownerId. Use members for the exact roster id/name/role; use tasks for ids/revisions.
|
|
56
|
+
description: 'Read and act on the AI Team board. The Project owner can create tasks with action=create and must set resultDefinition and ownerId. Use members for the exact roster id/name/role; use tasks for ids/revisions. When Human must decide something, call askHuman with every question and options—do not bury questions in a report. Use recall to search your notes/skills and your past Tasks/Threads. Claim before working; read a Thread before replying; submit evidence before acceptance.',
|
|
57
57
|
parameters: {
|
|
58
58
|
type: 'object',
|
|
59
59
|
properties: {
|
|
@@ -72,6 +72,20 @@ export function createTeamTool(options) {
|
|
|
72
72
|
assigneeId: { type: 'string', description: 'Optional assignee id from members for create.' },
|
|
73
73
|
qaRequired: { type: 'boolean', description: 'Require a QA member to accept this task.' },
|
|
74
74
|
qaChecklist: { type: 'array', items: { type: 'string' }, description: 'QA checklist items; all must pass before acceptance.' },
|
|
75
|
+
questions: {
|
|
76
|
+
type: 'array',
|
|
77
|
+
description: 'Human decision questions for askHuman; include every question instead of pointing at a report.',
|
|
78
|
+
items: {
|
|
79
|
+
type: 'object',
|
|
80
|
+
properties: {
|
|
81
|
+
text: { type: 'string', description: 'The question.' },
|
|
82
|
+
options: { type: 'array', items: { type: 'string' }, description: 'Optional answer choices.' },
|
|
83
|
+
recommendation: { type: 'string', description: 'Optional recommended answer.' },
|
|
84
|
+
blocking: { type: 'string', description: 'What this decision blocks.' },
|
|
85
|
+
},
|
|
86
|
+
required: ['text'],
|
|
87
|
+
},
|
|
88
|
+
},
|
|
75
89
|
personality: { type: 'string', description: 'New working personality for evolve/distill.' },
|
|
76
90
|
reason: { type: 'string', description: 'Why the personality changed.' },
|
|
77
91
|
memberId: { type: 'string', description: 'Target member for member/distill/note/skill.' },
|
|
@@ -216,6 +230,18 @@ export function createTeamTool(options) {
|
|
|
216
230
|
});
|
|
217
231
|
return { ok: true, task: taskCard(options.getState(), result.task) };
|
|
218
232
|
}
|
|
233
|
+
if (args.action === 'askHuman') {
|
|
234
|
+
if (!Array.isArray(args.questions) || args.questions.length === 0) {
|
|
235
|
+
return failure({ code: 'E_INVALID_ARGUMENT', message: 'questions is required; list every question Human must answer' });
|
|
236
|
+
}
|
|
237
|
+
const result = await command('decision.request', {
|
|
238
|
+
title: typeof args.title === 'string' && args.title.trim() !== '' ? args.title.trim() : '需要你决策',
|
|
239
|
+
taskId: typeof args.taskId === 'string' && args.taskId !== '' ? args.taskId : null,
|
|
240
|
+
questions: args.questions,
|
|
241
|
+
source: 'team',
|
|
242
|
+
});
|
|
243
|
+
return { ok: true, item: result.item, duplicate: result.duplicate === true };
|
|
244
|
+
}
|
|
219
245
|
if (args.action === 'inbox') {
|
|
220
246
|
const items = Object.values(state.inbox ?? {})
|
|
221
247
|
.filter((item) => item.memberId === member.id && !['resolved', 'dismissed', 'superseded'].includes(item.state));
|
package/lib/host/service.js
CHANGED
|
@@ -232,6 +232,7 @@ export class TeamService {
|
|
|
232
232
|
case 'task.follow': return await this.follow(command, payload);
|
|
233
233
|
case 'thread.post': return await this.postThread(command, payload);
|
|
234
234
|
case 'inbox.ack': return await this.ack(command, payload);
|
|
235
|
+
case 'decision.request': return await this.requestDecision(command, payload);
|
|
235
236
|
default: fail(ERRORS.INVALID_ARGUMENT, `unknown command: ${String(command.type)}`);
|
|
236
237
|
}
|
|
237
238
|
}
|
|
@@ -889,6 +890,59 @@ export class TeamService {
|
|
|
889
890
|
return { thread: outcome.thread, entry: outcome.entry };
|
|
890
891
|
}
|
|
891
892
|
|
|
893
|
+
/** Surface an explicit Human decision request with all questions inline. */
|
|
894
|
+
async requestDecision(command, payload) {
|
|
895
|
+
const humanId = this.state.humanMemberId
|
|
896
|
+
?? Object.values(this.state.members ?? {}).find((member) => member.kind === 'human')?.id
|
|
897
|
+
?? null;
|
|
898
|
+
if (humanId === null) fail(ERRORS.NOT_FOUND, 'Human member not found');
|
|
899
|
+
const raw = Array.isArray(payload.questions) ? payload.questions : [];
|
|
900
|
+
if (raw.length === 0 || raw.length > 20) fail(ERRORS.INVALID_ARGUMENT, 'questions must contain 1..20 items');
|
|
901
|
+
const questions = raw.map((question, index) => {
|
|
902
|
+
if (typeof question === 'string') return { id: `q${index + 1}`, text: question.trim() };
|
|
903
|
+
if (question === null || typeof question !== 'object') fail(ERRORS.INVALID_ARGUMENT, `questions[${index}] must be a string or object`);
|
|
904
|
+
const text = typeof question.text === 'string' ? question.text.trim() : '';
|
|
905
|
+
if (text === '') fail(ERRORS.INVALID_ARGUMENT, `questions[${index}].text is required`);
|
|
906
|
+
const options = Array.isArray(question.options) ? question.options.filter((option) => typeof option === 'string' && option.trim() !== '').slice(0, 12) : [];
|
|
907
|
+
return {
|
|
908
|
+
id: typeof question.id === 'string' && question.id !== '' ? question.id : `q${index + 1}`,
|
|
909
|
+
text,
|
|
910
|
+
...(options.length ? { options } : {}),
|
|
911
|
+
...(typeof question.recommendation === 'string' && question.recommendation !== '' ? { recommendation: question.recommendation } : {}),
|
|
912
|
+
...(typeof question.blocking === 'string' && question.blocking !== '' ? { blocking: question.blocking } : {}),
|
|
913
|
+
};
|
|
914
|
+
});
|
|
915
|
+
const title = typeof payload.title === 'string' && payload.title.trim() !== '' ? payload.title.trim() : '需要你决策';
|
|
916
|
+
const taskId = typeof payload.taskId === 'string' ? payload.taskId : null;
|
|
917
|
+
const duplicate = Object.values(this.state.inbox ?? {}).find((item) =>
|
|
918
|
+
item.memberId === humanId
|
|
919
|
+
&& item.type === 'decision_required'
|
|
920
|
+
&& item.ref?.title === title
|
|
921
|
+
&& item.ref?.taskId === taskId
|
|
922
|
+
&& !['resolved', 'dismissed', 'superseded'].includes(item.state));
|
|
923
|
+
if (duplicate !== undefined) return { item: duplicate, duplicate: true };
|
|
924
|
+
const item = createInboxItem({
|
|
925
|
+
memberId: humanId,
|
|
926
|
+
type: 'decision_required',
|
|
927
|
+
ref: {
|
|
928
|
+
taskId,
|
|
929
|
+
title,
|
|
930
|
+
questions,
|
|
931
|
+
...(typeof payload.source === 'string' && payload.source !== '' ? { source: payload.source } : {}),
|
|
932
|
+
},
|
|
933
|
+
requiresDecision: true,
|
|
934
|
+
}, { at: this.now() });
|
|
935
|
+
await this.ledger.append({
|
|
936
|
+
type: 'inbox/item-created',
|
|
937
|
+
payload: { itemId: item.id, ...item },
|
|
938
|
+
actor: command.actor,
|
|
939
|
+
correlationId: command.correlationId,
|
|
940
|
+
projectId: command.projectId,
|
|
941
|
+
at: this.now(),
|
|
942
|
+
});
|
|
943
|
+
return { item, duplicate: false };
|
|
944
|
+
}
|
|
945
|
+
|
|
892
946
|
async ack(command, payload) {
|
|
893
947
|
const state = this.state;
|
|
894
948
|
const item = state.inbox?.[payload.itemId];
|
package/lib/host/waker.js
CHANGED
|
@@ -165,7 +165,7 @@ export function planWakes(state, result, actor = {}) {
|
|
|
165
165
|
null,
|
|
166
166
|
'human_message',
|
|
167
167
|
`human_message:${event.seq ?? event.payload?.seq}`,
|
|
168
|
-
`你是 Project owner。Human 在 Main stream 说:“${body}”。本轮必须先用 team action=members 查看成员,再用 action=create 创建至少一个 Task;ownerId 必须使用成员 id,写明可验证的 resultDefinition。可用成员:${roster || '无'}。如果需求很大或外部链接只是参考,先创建“调研/澄清/拆解”Task
|
|
168
|
+
`你是 Project owner。Human 在 Main stream 说:“${body}”。本轮必须先用 team action=members 查看成员,再用 action=create 创建至少一个 Task;ownerId 必须使用成员 id,写明可验证的 resultDefinition。可用成员:${roster || '无'}。如果需求很大或外部链接只是参考,先创建“调研/澄清/拆解”Task,不要直接开始实现;如果确实无法创建,必须明确说明原因。需要 Human 拍板时,必须调用 team action=askHuman,把每个问题和选项逐条放进 questions,不要只写“见报告/§6/已提交 Human”。resultDefinition 只写验收标准,不要写“等待 Human 验收”;不要只读文件或只回一段话结束。`,
|
|
169
169
|
);
|
|
170
170
|
}
|
|
171
171
|
if (event?.type === 'inbox/item-created') {
|
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.19",
|
|
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",
|