@suifei/dsh-ai-team 0.3.13 → 0.3.15
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 +14 -0
- package/README.md +2 -2
- package/client.js +75 -6
- package/index.js +39 -3
- package/lib/runtime/skill-catalog.js +5 -1
- package/lib/runtime/skill-loader.js +2 -0
- package/lib/runtime/skill-policy.js +12 -0
- package/lib/runtime/skill-provider.js +2 -0
- package/package.json +1 -1
package/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,19 @@
|
|
|
1
1
|
# Changelog
|
|
2
2
|
|
|
3
|
+
## 0.3.15 — 2026-11-10
|
|
4
|
+
|
|
5
|
+
- 成员 Session 自动挂到 Project workspace,不再散落在 DSH「未分组」;
|
|
6
|
+
- Task Thread 增加「对话」按钮,直接打开 assignee/owner 的真实 Session;
|
|
7
|
+
- 成员行增加直接对话按钮;点名字进入成员任务视图;
|
|
8
|
+
- 团队动态只在有 active Task 时显示(或全新项目刚唤醒时),已完成不再显示“正在拆解”;
|
|
9
|
+
- `huashu-nuwa` 从 Team 技能目录、prompt catalog 与 notes 加载中排除。
|
|
10
|
+
|
|
11
|
+
## 0.3.14 — 2026-11-10
|
|
12
|
+
|
|
13
|
+
- 主对话顶部增加「团队动态」:显示正在推进的 Task、owner 和运行中的成员;
|
|
14
|
+
- 点击团队动态里的 Task 直接打开 Thread;没有 Task 时显示“成员已唤醒,正在拆解”;
|
|
15
|
+
- 解决 Human 发消息后主对话没有反馈、只能去任务看板找 Task 的问题。
|
|
16
|
+
|
|
3
17
|
## 0.3.13 — 2026-11-10
|
|
4
18
|
|
|
5
19
|
- 点击成员名字进入成员视图:该成员负责/执行的任务列表 + 直接对话 + 资料入口;
|
package/README.md
CHANGED
|
@@ -84,7 +84,7 @@ dsh --profile ai-team --no-open --port 33271
|
|
|
84
84
|
└─────────────────┴────────────────────────────────────┴─────────────┘
|
|
85
85
|
```
|
|
86
86
|
|
|
87
|
-
- **主对话**:Human 的一句话进入 Main stream,Project owner 拆 Task
|
|
87
|
+
- **主对话**:Human 的一句话进入 Main stream,Project owner 拆 Task;顶部「团队动态」直接显示正在推进的 Task 和运行中的成员,点击 Task 可打开 Thread。
|
|
88
88
|
- **看板**:四列;进行中的卡片显示 accountable owner 与执行者;数量只统计 active Task。
|
|
89
89
|
- **态势**:四象限——需要你、团队在做、等待受阻、最近完成。点击条目打开对应 Task 的 Thread。
|
|
90
90
|
- **收件箱**:跨 Project 聚合;失败决策、需要你决定、关注 Task 的完成报告都在这里。
|
|
@@ -215,7 +215,7 @@ round-19 DeepSeek + Zhipu 双 provider
|
|
|
215
215
|
- 单机、单 Human;不支持多 Human 共享 Team;
|
|
216
216
|
- billing/网络注入是测试 adapter,不是物理断网;
|
|
217
217
|
- SSE 分桶用两个真实 provider 的独立订阅端验证,不是两个浏览器窗口;
|
|
218
|
-
-
|
|
218
|
+
- distilly / darwin 不内置,有则用、无则不用;`huashu-nuwa` 已被 Team 技能目录排除。
|
|
219
219
|
- desktop profile 由插件 patch 固定为 `danger-full-access / never`,只应在可信机器使用。
|
|
220
220
|
|
|
221
221
|
---
|
package/client.js
CHANGED
|
@@ -42,6 +42,7 @@ window.__ModuleLoader__.load({
|
|
|
42
42
|
enterHint: 'Ctrl/⌘ + Enter 发送',
|
|
43
43
|
thread: '讨论',
|
|
44
44
|
closeThread: '关闭讨论',
|
|
45
|
+
threadChat: '对话',
|
|
45
46
|
follow: '关注',
|
|
46
47
|
unfollow: '取消关注',
|
|
47
48
|
convert: '转为 Task',
|
|
@@ -188,6 +189,11 @@ window.__ModuleLoader__.load({
|
|
|
188
189
|
memberNoTasksBody: '在项目里创建或指派 Task 后,会出现在这里。',
|
|
189
190
|
taskOwner: '负责人',
|
|
190
191
|
taskAssignee: '执行',
|
|
192
|
+
teamPulseTitle: '团队动态',
|
|
193
|
+
teamPulseCount: '{n} 个 Task 正在推进',
|
|
194
|
+
teamPulseWorking: '成员已接单',
|
|
195
|
+
teamPulseStarting: '成员已唤醒,正在拆解任务…',
|
|
196
|
+
teamPulseAgents: '运行中:{names}',
|
|
191
197
|
newProject: '新建项目',
|
|
192
198
|
addAgent: '添加成员',
|
|
193
199
|
newProjectMock: 'spike:新建项目属于 S-01 流程,M1 之后接入。',
|
|
@@ -337,6 +343,7 @@ window.__ModuleLoader__.load({
|
|
|
337
343
|
enterHint: 'Ctrl/⌘ + Enter to send',
|
|
338
344
|
thread: 'Discussion',
|
|
339
345
|
closeThread: 'Close discussion',
|
|
346
|
+
threadChat: 'Chat',
|
|
340
347
|
follow: 'Follow',
|
|
341
348
|
unfollow: 'Unfollow',
|
|
342
349
|
convert: 'Turn into task',
|
|
@@ -483,6 +490,11 @@ window.__ModuleLoader__.load({
|
|
|
483
490
|
memberNoTasksBody: 'Create or assign a Task in the Project and it appears here.',
|
|
484
491
|
taskOwner: 'Owner',
|
|
485
492
|
taskAssignee: 'Assignee',
|
|
493
|
+
teamPulseTitle: 'Team activity',
|
|
494
|
+
teamPulseCount: '{n} Task(s) in motion',
|
|
495
|
+
teamPulseWorking: 'Members picked up the work',
|
|
496
|
+
teamPulseStarting: 'Members are awake and breaking the work down…',
|
|
497
|
+
teamPulseAgents: 'Running: {names}',
|
|
486
498
|
newProject: 'New project',
|
|
487
499
|
addAgent: 'Add member',
|
|
488
500
|
newProjectMock: 'spike: project creation belongs to S-01 and lands after M1.',
|
|
@@ -946,8 +958,21 @@ window.__ModuleLoader__.load({
|
|
|
946
958
|
.at-btn-small { min-height: 24px; padding: 0 8px; font-size: 11px; }
|
|
947
959
|
.at-agentconfig { min-width: 0; display: flex; gap: 4px; align-items: center; color: var(--dsw-alias-label-tertiary); font-size: 10.5px; line-height: 15px; white-space: nowrap; overflow: hidden; text-overflow: ellipsis; }
|
|
948
960
|
.at-agentfail { flex: none; color: var(--dsw-alias-state-warn-primary); }
|
|
949
|
-
.at-agentmore { flex: 0 0 auto; width: 28px; height: 28px; border: 0; border-radius: var(--dsw-radius-sm); background: transparent; color: var(--dsw-alias-label-tertiary); font: inherit; font-size: 13px; cursor: pointer; }
|
|
950
|
-
.at-agentmore:hover { background: var(--dsw-alias-interactive-bg-hover); color: var(--dsw-alias-label-primary); }
|
|
961
|
+
.at-agentmore, .at-agentchat { flex: 0 0 auto; width: 28px; height: 28px; border: 0; border-radius: var(--dsw-radius-sm); background: transparent; color: var(--dsw-alias-label-tertiary); font: inherit; font-size: 13px; cursor: pointer; }
|
|
962
|
+
.at-agentmore:hover, .at-agentchat:hover { background: var(--dsw-alias-interactive-bg-hover); color: var(--dsw-alias-label-primary); }
|
|
963
|
+
.at-agentchat { display: inline-flex; align-items: center; justify-content: center; }
|
|
964
|
+
.at-teampulse { width: 100%; max-width: 760px; margin: 4px auto 10px; padding: 10px 12px; border: 1px solid var(--dsw-alias-state-business-primary); border-radius: var(--dsw-radius-md); background: var(--dsw-alias-bg-module-platform); }
|
|
965
|
+
.at-teampulsehead { display: flex; align-items: baseline; justify-content: space-between; gap: 8px; margin-bottom: 6px; }
|
|
966
|
+
.at-teampulsetitle { font-size: 12px; font-weight: 600; color: var(--dsw-alias-label-primary); }
|
|
967
|
+
.at-teampulsecount { font-size: 11px; color: var(--dsw-alias-state-business-primary); }
|
|
968
|
+
.at-teampulselist { display: flex; flex-direction: column; gap: 4px; }
|
|
969
|
+
.at-teampulsetask { display: flex; align-items: center; gap: 7px; width: 100%; padding: 4px 6px; border: 0; border-radius: var(--dsw-radius-sm); background: transparent; color: var(--dsw-alias-label-primary); font: inherit; text-align: left; cursor: pointer; }
|
|
970
|
+
.at-teampulsetask:hover { background: var(--dsw-alias-interactive-bg-hover); }
|
|
971
|
+
.at-teampulseno { flex: 0 0 auto; color: var(--dsw-alias-label-tertiary); font-size: 11px; font-variant-numeric: tabular-nums; }
|
|
972
|
+
.at-teampulsetasktitle { flex: 1 1 auto; min-width: 0; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; font-size: 12px; }
|
|
973
|
+
.at-teampulseowner { flex: 0 0 auto; color: var(--dsw-alias-label-tertiary); font-size: 11px; }
|
|
974
|
+
.at-teampulsehint { color: var(--dsw-alias-label-tertiary); font-size: 11px; line-height: 16px; }
|
|
975
|
+
.at-teampulseagents { margin-top: 6px; color: var(--dsw-alias-label-tertiary); font-size: 11px; line-height: 16px; }
|
|
951
976
|
.at-memberview { flex: 1 1 auto; min-height: 0; overflow: auto; padding: 18px; }
|
|
952
977
|
.at-memberviewhead { display: flex; align-items: center; flex-wrap: wrap; gap: 8px; padding-bottom: 12px; border-bottom: 1px solid var(--dsw-alias-border-l1); }
|
|
953
978
|
.at-memberviewid { display: flex; flex-direction: column; min-width: 0; flex: 1 1 180px; }
|
|
@@ -1512,7 +1537,7 @@ window.__ModuleLoader__.load({
|
|
|
1512
1537
|
h('span', { className: 'at-prowtitle' }, project.name));
|
|
1513
1538
|
}
|
|
1514
1539
|
|
|
1515
|
-
function AgentRow({ t, agent, onAgent, onAgentMore }) {
|
|
1540
|
+
function AgentRow({ t, agent, onAgent, onAgentMore, onAgentChat }) {
|
|
1516
1541
|
const statusText = t(AGENT_STATUS[agent.status] ?? 'agentIdle');
|
|
1517
1542
|
const config = agent.config ?? null;
|
|
1518
1543
|
const model = config?.model ? config.model : t('agentDefaultModel');
|
|
@@ -1535,6 +1560,13 @@ window.__ModuleLoader__.load({
|
|
|
1535
1560
|
),
|
|
1536
1561
|
),
|
|
1537
1562
|
h('span', { className: 'at-sr' }, statusText),
|
|
1563
|
+
h('button', {
|
|
1564
|
+
className: 'at-agentchat',
|
|
1565
|
+
type: 'button',
|
|
1566
|
+
'aria-label': tf(t, 'agentOpen', { name: agent.name }),
|
|
1567
|
+
title: t('agentChat'),
|
|
1568
|
+
onClick: () => onAgentChat(agent),
|
|
1569
|
+
}, h(Ico, { name: 'reply', size: 13 })),
|
|
1538
1570
|
h('button', {
|
|
1539
1571
|
className: 'at-agentmore',
|
|
1540
1572
|
type: 'button',
|
|
@@ -1544,6 +1576,30 @@ window.__ModuleLoader__.load({
|
|
|
1544
1576
|
}, '⋯'));
|
|
1545
1577
|
}
|
|
1546
1578
|
|
|
1579
|
+
function TeamPulse({ t, agents, tasks, onOpen }) {
|
|
1580
|
+
const active = tasks.filter((task) => ['pending', 'in_progress', 'pending_acceptance'].includes(task.status)).slice(-5);
|
|
1581
|
+
const running = agents.filter((agent) => agent.status === 'running');
|
|
1582
|
+
const starting = active.length === 0 && tasks.length === 0 && running.length > 0;
|
|
1583
|
+
if (active.length === 0 && !starting) return null;
|
|
1584
|
+
return h('section', { className: 'at-teampulse', 'aria-label': t('teamPulseTitle') },
|
|
1585
|
+
h('div', { className: 'at-teampulsehead' },
|
|
1586
|
+
h('span', { className: 'at-teampulsetitle' }, t('teamPulseTitle')),
|
|
1587
|
+
h('span', { className: 'at-teampulsecount' }, active.length ? t('teamPulseCount').replace('{n}', String(active.length)) : t('teamPulseWorking'))),
|
|
1588
|
+
active.length
|
|
1589
|
+
? h('div', { className: 'at-teampulselist' }, active.map((task) => h('button', {
|
|
1590
|
+
key: task.id,
|
|
1591
|
+
className: 'at-teampulsetask',
|
|
1592
|
+
type: 'button',
|
|
1593
|
+
onClick: () => onOpen(task),
|
|
1594
|
+
},
|
|
1595
|
+
h('span', { className: 'at-teampulseno' }, '#' + taskNo(task)),
|
|
1596
|
+
h('span', { className: 'at-teampulsetasktitle' }, task.title),
|
|
1597
|
+
h(StatusPill, { t, status: task.status }),
|
|
1598
|
+
h('span', { className: 'at-teampulseowner' }, task.owner ?? ''))))
|
|
1599
|
+
: h('div', { className: 'at-teampulsehint' }, t('teamPulseStarting')),
|
|
1600
|
+
running.length ? h('div', { className: 'at-teampulseagents' }, t('teamPulseAgents').replace('{names}', running.map((agent) => agent.name).join('、'))) : null);
|
|
1601
|
+
}
|
|
1602
|
+
|
|
1547
1603
|
function MemberView({ t, agent, tasks, onBack, onOpenTask, onOpenChat, onOpenProfile }) {
|
|
1548
1604
|
return h('div', { className: 'at-memberview' },
|
|
1549
1605
|
h('div', { className: 'at-memberviewhead' },
|
|
@@ -1571,7 +1627,7 @@ window.__ModuleLoader__.load({
|
|
|
1571
1627
|
: h(EmptyState, { t, title: t('memberNoTasks'), body: t('memberNoTasksBody'), compact: true })));
|
|
1572
1628
|
}
|
|
1573
1629
|
|
|
1574
|
-
function LeftPanel({ t, data, projectId, scope, tab, inboxUnread, onBack, onInbox, onBoard, onProject, onNewProject, onAgent, onAgentMore, onAddAgent, onRecruit }) {
|
|
1630
|
+
function LeftPanel({ t, data, projectId, scope, tab, inboxUnread, onBack, onInbox, onBoard, onProject, onNewProject, onAgent, onAgentMore, onAgentChat, onAddAgent, onRecruit }) {
|
|
1575
1631
|
const projects = data.projects.filter((project) => !project.archived);
|
|
1576
1632
|
const activeAgents = data.agents.filter((agent) => agent.status !== 'retired');
|
|
1577
1633
|
const talentAgents = data.agents.filter((agent) => agent.status === 'retired');
|
|
@@ -1621,7 +1677,7 @@ window.__ModuleLoader__.load({
|
|
|
1621
1677
|
h('button', { className: 'at-mini', type: 'button', 'aria-label': t('addAgent'), title: t('addAgent'), onClick: onAddAgent }, h(Ico, { name: 'plus', size: 12 })),
|
|
1622
1678
|
),
|
|
1623
1679
|
activeAgents.length
|
|
1624
|
-
? h('div', { role: 'list', 'aria-label': t('agentsAria') }, activeAgents.map((agent) => h(AgentRow, { key: agent.id, t, agent, onAgent, onAgentMore })))
|
|
1680
|
+
? h('div', { role: 'list', 'aria-label': t('agentsAria') }, activeAgents.map((agent) => h(AgentRow, { key: agent.id, t, agent, onAgent, onAgentMore, onAgentChat })))
|
|
1625
1681
|
: h('div', { className: 'at-colempty' }, t('emptyColumn')),
|
|
1626
1682
|
talentAgents.length ? h('details', { className: 'at-archived' },
|
|
1627
1683
|
h('summary', null, tf(t, 'talentPoolCount', { n: talentAgents.length })),
|
|
@@ -1735,7 +1791,7 @@ window.__ModuleLoader__.load({
|
|
|
1735
1791
|
task.submission?.summary ? h('p', { className: 'at-donetext' }, task.submission.summary) : null);
|
|
1736
1792
|
}
|
|
1737
1793
|
|
|
1738
|
-
function ThreadPanel({ t, task, thread, project, following, onToggleFollow, onClose, converting, convert, reply, onReplyChange, onReplySend, replyBase, onContinue, onContext, note, onDismissNote, acceptChecked, onAcceptChecked, qaChecks, onQaCheck, onAccept, onChanges, actions, panelRef, replyRef }) {
|
|
1794
|
+
function ThreadPanel({ t, task, thread, project, following, onToggleFollow, onClose, onChat, converting, convert, reply, onReplyChange, onReplySend, replyBase, onContinue, onContext, note, onDismissNote, acceptChecked, onAcceptChecked, qaChecks, onQaCheck, onAccept, onChanges, actions, panelRef, replyRef }) {
|
|
1739
1795
|
const headSeq = thread?.headSeq ?? thread?.entries?.length ?? 0;
|
|
1740
1796
|
const stale = replyBase !== null && replyBase !== undefined && headSeq > replyBase;
|
|
1741
1797
|
const entries = Array.isArray(thread?.entries) ? thread.entries : [];
|
|
@@ -1745,6 +1801,7 @@ window.__ModuleLoader__.load({
|
|
|
1745
1801
|
h('div', { className: 'at-rhmeta', title: t('thread') + ' · #' + taskNo(task) }, t('thread') + ' · #' + taskNo(task)),
|
|
1746
1802
|
h('div', { className: 'at-rhtitle', title: task.title }, task.title)),
|
|
1747
1803
|
h(FollowToggle, { t, taskNo: taskNo(task), following, onToggle: onToggleFollow }),
|
|
1804
|
+
onChat ? h('button', { className: 'at-rbtn', type: 'button', onClick: onChat }, t('threadChat')) : null,
|
|
1748
1805
|
h('button', { className: 'at-rbtn', type: 'button', 'aria-expanded': Boolean(converting), onClick: convert.onOpen }, t('convert')),
|
|
1749
1806
|
h('button', { className: 'at-rbtn at-iconbtn', type: 'button', 'aria-label': t('closeThread'), title: t('closeThread'), onClick: onClose }, h(Ico, { name: 'close', size: 14 }))),
|
|
1750
1807
|
note ? h('div', { className: 'at-rnote', role: 'status' },
|
|
@@ -2491,6 +2548,15 @@ window.__ModuleLoader__.load({
|
|
|
2491
2548
|
openProfile(agent);
|
|
2492
2549
|
}
|
|
2493
2550
|
}
|
|
2551
|
+
function openTaskAgentChat(task) {
|
|
2552
|
+
const memberId = task?.assigneeId ?? task?.ownerId;
|
|
2553
|
+
const agent = data.agents.find((candidate) => candidate.id === memberId) ?? null;
|
|
2554
|
+
if (agent === null) {
|
|
2555
|
+
say(t('agentChatUnavailable'));
|
|
2556
|
+
return;
|
|
2557
|
+
}
|
|
2558
|
+
void openAgentChat(agent);
|
|
2559
|
+
}
|
|
2494
2560
|
function openProfile(agent) {
|
|
2495
2561
|
setProfileAgent(agent);
|
|
2496
2562
|
setPName(agent.name ?? '');
|
|
@@ -2859,6 +2925,7 @@ window.__ModuleLoader__.load({
|
|
|
2859
2925
|
onNewProject: () => { closeMobile(); openNewProject(); },
|
|
2860
2926
|
onAgent: (agent) => { closeMobile(); openMemberView(agent); },
|
|
2861
2927
|
onAgentMore: (agent) => { closeMobile(); openProfile(agent); },
|
|
2928
|
+
onAgentChat: (agent) => { closeMobile(); void openAgentChat(agent); },
|
|
2862
2929
|
onAddAgent: () => { closeMobile(); openAddAgent(); },
|
|
2863
2930
|
onRecruit: (agent) => { closeMobile(); recruit(agent); },
|
|
2864
2931
|
};
|
|
@@ -2921,6 +2988,7 @@ window.__ModuleLoader__.load({
|
|
|
2921
2988
|
? h(Situation, { t, quadrants, tasks, onItem: openQuadrantItem, onViewAll: viewQuadrant })
|
|
2922
2989
|
: h('div', { className: 'at-stream', role: 'tabpanel', id: 'at-panel-main', 'aria-labelledby': 'at-tab-main' },
|
|
2923
2990
|
h('div', { className: 'at-streaminner', role: 'log', 'aria-live': 'polite', 'aria-label': t('streamAria') },
|
|
2991
|
+
h(TeamPulse, { t, agents: data.agents, tasks, onOpen: openTask }),
|
|
2924
2992
|
data.stream.length
|
|
2925
2993
|
? data.stream.map((item) => h(StreamMessage, {
|
|
2926
2994
|
key: item.id,
|
|
@@ -3017,6 +3085,7 @@ window.__ModuleLoader__.load({
|
|
|
3017
3085
|
following: followingOf(selectedTask),
|
|
3018
3086
|
onToggleFollow: () => toggleFollow(selectedTask),
|
|
3019
3087
|
onClose: closeThread,
|
|
3088
|
+
onChat: () => openTaskAgentChat(selectedTask),
|
|
3020
3089
|
converting,
|
|
3021
3090
|
convert: {
|
|
3022
3091
|
onOpen: openConvert,
|
package/index.js
CHANGED
|
@@ -20,6 +20,7 @@ import { classifyLlmFailure, retryDecision } from './lib/runtime/failure-policy.
|
|
|
20
20
|
import { memberIdentity } from './lib/domain/member-profile.js';
|
|
21
21
|
import { loadSkillContext } from './lib/runtime/skill-loader.js';
|
|
22
22
|
import { formatSkillCatalog } from './lib/runtime/skill-catalog.js';
|
|
23
|
+
import { isSkillExcluded } from './lib/runtime/skill-policy.js';
|
|
23
24
|
import { installFileSkillProvider } from './lib/runtime/skill-provider.js';
|
|
24
25
|
import { apply as applySkillOpt } from './lib/vendor/skillopt/src/index.js';
|
|
25
26
|
import { createMemberPermissionGuard } from './lib/host/permission-guard.js';
|
|
@@ -33,7 +34,7 @@ export const name = 'ai-team';
|
|
|
33
34
|
/** The workbench only makes sense where the Web carrier exists. */
|
|
34
35
|
export const inject = ['webServer', 'permissionPresets', 'sandboxPolicy'];
|
|
35
36
|
|
|
36
|
-
const VERSION = '0.3.
|
|
37
|
+
const VERSION = '0.3.15';
|
|
37
38
|
const PROJECT_ID = 'prj_main';
|
|
38
39
|
const HUMAN_ID = 'mem_human';
|
|
39
40
|
|
|
@@ -126,13 +127,47 @@ export function apply(ctx) {
|
|
|
126
127
|
}
|
|
127
128
|
return null;
|
|
128
129
|
};
|
|
130
|
+
const workspaceForProject = async (projectRuntime) => {
|
|
131
|
+
const registry = ctx.get('workspaceRegistry');
|
|
132
|
+
const path = projectRuntime?.service?.state?.projects?.[projectRuntime.id]?.workspacePath;
|
|
133
|
+
if (registry === undefined || typeof path !== 'string' || path === '') return null;
|
|
134
|
+
try {
|
|
135
|
+
const existing = await registry.resolveByPath?.(path);
|
|
136
|
+
if (existing !== undefined && existing !== null) return existing;
|
|
137
|
+
const created = await registry.create?.(path, projectRuntime.service.state.projects[projectRuntime.id]?.name ?? '文渊阁');
|
|
138
|
+
return created ?? null;
|
|
139
|
+
} catch (error) {
|
|
140
|
+
console.warn('[ai-team] workspace unavailable:', error?.message ?? error);
|
|
141
|
+
return null;
|
|
142
|
+
}
|
|
143
|
+
};
|
|
144
|
+
const attachMemberWorkspace = async (projectRuntime, sessionId) => {
|
|
145
|
+
if (typeof sessionId !== 'string' || sessionId === '') return;
|
|
146
|
+
const workspace = await workspaceForProject(projectRuntime);
|
|
147
|
+
if (workspace === null || workspace === undefined || typeof workspace.attachSession !== 'function') return;
|
|
148
|
+
try {
|
|
149
|
+
await workspace.attachSession(sessionId);
|
|
150
|
+
} catch (error) {
|
|
151
|
+
console.warn('[ai-team] member session workspace attach failed:', error?.message ?? error);
|
|
152
|
+
}
|
|
153
|
+
};
|
|
129
154
|
const access = validatePermissionProfile({
|
|
130
155
|
permissionPresets: ctx.get('permissionPresets'),
|
|
131
156
|
sandboxPolicy: ctx.get('sandboxPolicy'),
|
|
132
157
|
});
|
|
133
158
|
const ready = access.ok
|
|
134
159
|
? boot()
|
|
135
|
-
.then((value) => {
|
|
160
|
+
.then(async (value) => {
|
|
161
|
+
booted = value;
|
|
162
|
+
for (const runtime of value.runtimes.values()) {
|
|
163
|
+
for (const member of Object.values(runtime.service.state.members ?? {})) {
|
|
164
|
+
if (member.kind === 'agent' && typeof member.sessionId === 'string') {
|
|
165
|
+
await attachMemberWorkspace(runtime, member.sessionId);
|
|
166
|
+
}
|
|
167
|
+
}
|
|
168
|
+
}
|
|
169
|
+
return value;
|
|
170
|
+
})
|
|
136
171
|
.catch((error) => {
|
|
137
172
|
bootError = error;
|
|
138
173
|
console.error('[ai-team] ledger boot failed:', error?.message ?? error);
|
|
@@ -479,6 +514,7 @@ export function apply(ctx) {
|
|
|
479
514
|
throw error;
|
|
480
515
|
}
|
|
481
516
|
activations.set(result.childId, result.activation);
|
|
517
|
+
await attachMemberWorkspace(projectRuntime, result.childId);
|
|
482
518
|
return {
|
|
483
519
|
ok: true,
|
|
484
520
|
member: bound.member,
|
|
@@ -660,7 +696,7 @@ export function apply(ctx) {
|
|
|
660
696
|
teamTool: ctx.get('tools')?.definitions?.().some((definition) => definition.name === 'team') ?? null,
|
|
661
697
|
skillsService: skillsService !== null && skillsService !== undefined,
|
|
662
698
|
skillNames: skillsService?.list
|
|
663
|
-
? await Promise.resolve(skillsService.list({})).then((rows) => rows.map((skill) => skill.name)).catch(() => [])
|
|
699
|
+
? await Promise.resolve(skillsService.list({})).then((rows) => rows.map((skill) => skill.name).filter((skillName) => !isSkillExcluded(skillName))).catch(() => [])
|
|
664
700
|
: [],
|
|
665
701
|
skilloptTools: tools?.definitions?.().map((definition) => definition.name).filter((name) => name.startsWith('skillopt_')) ?? [],
|
|
666
702
|
presets: await Promise.resolve(ctx.get('agentPresets')?.list?.()).catch(() => []),
|
|
@@ -5,6 +5,8 @@
|
|
|
5
5
|
* demand instead of guessing.
|
|
6
6
|
*/
|
|
7
7
|
|
|
8
|
+
import { isSkillExcluded } from './skill-policy.js';
|
|
9
|
+
|
|
8
10
|
function clean(value, limit) {
|
|
9
11
|
const text = typeof value === 'string' ? value.replace(/\s+/g, ' ').trim() : '';
|
|
10
12
|
return text.length > limit ? `${text.slice(0, limit - 1)}…` : text;
|
|
@@ -16,8 +18,10 @@ export async function formatSkillCatalog(skills, options = {}) {
|
|
|
16
18
|
...(options.cwd ? { cwd: options.cwd } : {}),
|
|
17
19
|
}).catch(() => []);
|
|
18
20
|
if (!Array.isArray(summaries) || summaries.length === 0) return '';
|
|
21
|
+
const available = summaries.filter((skill) => !isSkillExcluded(skill?.name));
|
|
22
|
+
if (available.length === 0) return '';
|
|
19
23
|
const limit = Number.isSafeInteger(options.limit) && options.limit > 0 ? options.limit : 12;
|
|
20
|
-
const rows =
|
|
24
|
+
const rows = available.slice(0, limit).map((skill) => {
|
|
21
25
|
const name = clean(skill?.name, 80);
|
|
22
26
|
const description = clean(skill?.description, 240);
|
|
23
27
|
const when = clean(skill?.whenToUse, 180);
|
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import { readFile } from 'node:fs/promises';
|
|
2
2
|
import { basename, join } from 'node:path';
|
|
3
|
+
import { isSkillExcluded } from './skill-policy.js';
|
|
3
4
|
|
|
4
5
|
const MAX_SKILL_CHARS = 1800;
|
|
5
6
|
|
|
@@ -11,6 +12,7 @@ export async function loadSkillContext(member, dataDir) {
|
|
|
11
12
|
for (const raw of skills.slice(0, 4)) {
|
|
12
13
|
const name = basename(String(raw)).replace(/[^a-zA-Z0-9._-]/g, '');
|
|
13
14
|
if (name === '' || name === '.' || name === '..' || name.startsWith('.')) continue;
|
|
15
|
+
if (isSkillExcluded(name)) continue;
|
|
14
16
|
const candidates = [join(dataDir, 'skills', name, 'SKILL.md'), join(dataDir, 'skills', `${name}.md`)];
|
|
15
17
|
for (const candidate of candidates) {
|
|
16
18
|
try {
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Skills that must not be exposed by the AI Team plugin.
|
|
3
|
+
*
|
|
4
|
+
* These may still exist in the user's global DSH skill directory; the Team
|
|
5
|
+
* provider and prompt catalog filter them so they never enter a member's Team
|
|
6
|
+
* context or the workbench skill list.
|
|
7
|
+
*/
|
|
8
|
+
export const EXCLUDED_SKILL_NAMES = Object.freeze(new Set(['huashu-nuwa']));
|
|
9
|
+
|
|
10
|
+
export function isSkillExcluded(name) {
|
|
11
|
+
return typeof name === 'string' && EXCLUDED_SKILL_NAMES.has(name);
|
|
12
|
+
}
|
|
@@ -7,6 +7,7 @@
|
|
|
7
7
|
|
|
8
8
|
import { existsSync, readFileSync, readdirSync, statSync } from 'node:fs';
|
|
9
9
|
import { join } from 'node:path';
|
|
10
|
+
import { isSkillExcluded } from './skill-policy.js';
|
|
10
11
|
|
|
11
12
|
const MAX_BODY_BYTES = 512 * 1024;
|
|
12
13
|
|
|
@@ -61,6 +62,7 @@ function scanRoot(root, source) {
|
|
|
61
62
|
const fields = parseFrontmatter(text);
|
|
62
63
|
const name = unquote(fields.name) || entry.name;
|
|
63
64
|
if (!/^[a-z0-9][a-z0-9-]*$/.test(name)) continue;
|
|
65
|
+
if (isSkillExcluded(name)) continue;
|
|
64
66
|
candidates.push({
|
|
65
67
|
name,
|
|
66
68
|
description: unquote(fields.description) || `Skill ${name}`,
|
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.15",
|
|
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",
|