@suifei/dsh-ai-team 0.3.20 → 0.3.21
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 +6 -0
- package/index.js +1 -1
- package/lib/host/decision-report.js +48 -0
- package/lib/host/project-runtime.js +1 -1
- package/lib/host/service.js +20 -2
- package/package.json +1 -1
package/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,11 @@
|
|
|
1
1
|
# Changelog
|
|
2
2
|
|
|
3
|
+
## 0.3.21 — 2026-11-10
|
|
4
|
+
|
|
5
|
+
- 打开 Project 时,如果 Thread 里提到 Human 并登记了 Markdown 报告路径,会从报告的「6. 需 Human 决策的问题清单」提取全部问题;
|
|
6
|
+
- 现有项目无需重新问一遍:升级重启后,§6 六问直接进收件箱并可逐项填写提交;
|
|
7
|
+
- 收件箱决策标题显示问题数(如「需 Human 决策 · 6 问」)。
|
|
8
|
+
|
|
3
9
|
## 0.3.20 — 2026-11-10
|
|
4
10
|
|
|
5
11
|
- 收件箱决策项可直接选择/填写并提交,Human 决策会回写 Task Thread 并唤醒 owner;
|
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.21';
|
|
38
38
|
const PROJECT_ID = 'prj_main';
|
|
39
39
|
const HUMAN_ID = 'mem_human';
|
|
40
40
|
|
|
@@ -0,0 +1,48 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Read the Human-decision table from a linked Markdown report.
|
|
3
|
+
*
|
|
4
|
+
* This is deliberately narrow: only a report containing a section titled
|
|
5
|
+
* `6.` / `六` with a table gets converted. The Inbox still points back to the
|
|
6
|
+
* source Thread; the table is exposed so Human does not have to open the report
|
|
7
|
+
* just to discover the questions.
|
|
8
|
+
*/
|
|
9
|
+
|
|
10
|
+
function stripMarkdown(value) {
|
|
11
|
+
return String(value ?? '')
|
|
12
|
+
.replace(/\*\*/g, '')
|
|
13
|
+
.replace(/`/g, '')
|
|
14
|
+
.replace(/\[([^\]]+)\]\([^)]+\)/g, '$1')
|
|
15
|
+
.trim();
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
export function findReportPath(text) {
|
|
19
|
+
if (typeof text !== 'string') return null;
|
|
20
|
+
const matches = text.match(/[A-Za-z]:\\[^\r\n"'<>|]+?\.md\b/g);
|
|
21
|
+
return matches === null || matches.length === 0 ? null : matches[matches.length - 1];
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
export function parseDecisionQuestions(markdown) {
|
|
25
|
+
const lines = String(markdown ?? '').split(/\r?\n/);
|
|
26
|
+
const start = lines.findIndex((line) => /^##\s*6[.、\s]/.test(line) || /^##\s*6\s*$/.test(line));
|
|
27
|
+
if (start < 0) return [];
|
|
28
|
+
let end = lines.length;
|
|
29
|
+
for (let index = start + 1; index < lines.length; index += 1) {
|
|
30
|
+
if (/^##\s/.test(lines[index]) || /^---\s*$/.test(lines[index])) { end = index; break; }
|
|
31
|
+
}
|
|
32
|
+
const questions = [];
|
|
33
|
+
for (const line of lines.slice(start + 1, end)) {
|
|
34
|
+
if (!line.trim().startsWith('|')) continue;
|
|
35
|
+
const cells = line.split('|').slice(1, -1).map((cell) => cell.trim());
|
|
36
|
+
if (cells.length < 2) continue;
|
|
37
|
+
if (!/^\d+$/.test(stripMarkdown(cells[0]))) continue;
|
|
38
|
+
const text = stripMarkdown(cells[1]);
|
|
39
|
+
if (text === '') continue;
|
|
40
|
+
questions.push({
|
|
41
|
+
id: `q${questions.length + 1}`,
|
|
42
|
+
text,
|
|
43
|
+
...(cells[2] ? { recommendation: stripMarkdown(cells[2]) } : {}),
|
|
44
|
+
...(cells[3] ? { blocking: stripMarkdown(cells[3]) } : {}),
|
|
45
|
+
});
|
|
46
|
+
}
|
|
47
|
+
return questions;
|
|
48
|
+
}
|
|
@@ -61,7 +61,7 @@ export class ProjectRuntime {
|
|
|
61
61
|
});
|
|
62
62
|
}
|
|
63
63
|
await seedDefaultRoster(service, projectId, { humanId: HUMAN_ID });
|
|
64
|
-
await service.backfillHumanMentions();
|
|
64
|
+
await service.backfillHumanMentions(state.projects?.[projectId]?.workspacePath ?? '');
|
|
65
65
|
}
|
|
66
66
|
return runtime;
|
|
67
67
|
}
|
package/lib/host/service.js
CHANGED
|
@@ -1,4 +1,6 @@
|
|
|
1
|
+
import { existsSync, readFileSync } from 'node:fs';
|
|
1
2
|
import { ERRORS, fail } from '../domain/errors.js';
|
|
3
|
+
import { findReportPath, parseDecisionQuestions } from './decision-report.js';
|
|
2
4
|
import { ids } from '../domain/ids.js';
|
|
3
5
|
import { ackInboxItem, createInboxItem } from '../domain/inbox.js';
|
|
4
6
|
import {
|
|
@@ -141,7 +143,7 @@ export class TeamService {
|
|
|
141
143
|
* that explicitly asked Human to decide. Runs on Project open once; the
|
|
142
144
|
* per-entry dedupe makes it idempotent.
|
|
143
145
|
*/
|
|
144
|
-
async backfillHumanMentions() {
|
|
146
|
+
async backfillHumanMentions(workspacePath = '') {
|
|
145
147
|
const humanId = this.state.humanMemberId
|
|
146
148
|
?? Object.values(this.state.members ?? {}).find((member) => member.kind === 'human')?.id
|
|
147
149
|
?? null;
|
|
@@ -152,6 +154,20 @@ export class TeamService {
|
|
|
152
154
|
.filter((item) => item.memberId === humanId && item.ref?.entrySeq !== undefined)
|
|
153
155
|
.map((item) => `${item.ref?.taskId}:${item.ref?.entrySeq}`),
|
|
154
156
|
);
|
|
157
|
+
let reportPath = null;
|
|
158
|
+
for (const task of Object.values(this.state.tasks ?? {})) {
|
|
159
|
+
for (const entry of this.state.threads?.[task.id]?.entries ?? []) {
|
|
160
|
+
const found = findReportPath(entry.body);
|
|
161
|
+
if (found !== null && existsSync(found)) { reportPath = found; break; }
|
|
162
|
+
}
|
|
163
|
+
if (reportPath !== null) break;
|
|
164
|
+
}
|
|
165
|
+
let reportQuestions = [];
|
|
166
|
+
if (reportPath !== null) {
|
|
167
|
+
try {
|
|
168
|
+
reportQuestions = parseDecisionQuestions(readFileSync(reportPath, 'utf8'));
|
|
169
|
+
} catch { /* report may be unavailable; keep the Thread fallback */ }
|
|
170
|
+
}
|
|
155
171
|
let created = 0;
|
|
156
172
|
for (const task of Object.values(this.state.tasks ?? {})) {
|
|
157
173
|
if (task.tombstone === true) continue;
|
|
@@ -161,6 +177,7 @@ export class TeamService {
|
|
|
161
177
|
const key = `${task.id}:${entry.seq}`;
|
|
162
178
|
if (existing.has(key)) continue;
|
|
163
179
|
existing.add(key);
|
|
180
|
+
const useQuestions = reportQuestions.length > 0;
|
|
164
181
|
const item = createInboxItem({
|
|
165
182
|
memberId: humanId,
|
|
166
183
|
type: 'decision_required',
|
|
@@ -169,7 +186,8 @@ export class TeamService {
|
|
|
169
186
|
entrySeq: entry.seq,
|
|
170
187
|
memberId: entry.authorId,
|
|
171
188
|
category: 'thread_mention',
|
|
172
|
-
title: String(entry.body).slice(0, 180),
|
|
189
|
+
title: useQuestions ? `需 Human 决策 · ${reportQuestions.length} 问` : String(entry.body).slice(0, 180),
|
|
190
|
+
...(useQuestions ? { questions: reportQuestions, source: reportPath } : {}),
|
|
173
191
|
},
|
|
174
192
|
requiresDecision: true,
|
|
175
193
|
}, { at: this.now() });
|
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.21",
|
|
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",
|