@suifei/dsh-ai-team 0.3.14 → 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 +8 -0
- package/README.md +1 -1
- package/client.js +30 -7
- 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,13 @@
|
|
|
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
|
+
|
|
3
11
|
## 0.3.14 — 2026-11-10
|
|
4
12
|
|
|
5
13
|
- 主对话顶部增加「团队动态」:显示正在推进的 Task、owner 和运行中的成员;
|
package/README.md
CHANGED
|
@@ -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',
|
|
@@ -342,6 +343,7 @@ window.__ModuleLoader__.load({
|
|
|
342
343
|
enterHint: 'Ctrl/⌘ + Enter to send',
|
|
343
344
|
thread: 'Discussion',
|
|
344
345
|
closeThread: 'Close discussion',
|
|
346
|
+
threadChat: 'Chat',
|
|
345
347
|
follow: 'Follow',
|
|
346
348
|
unfollow: 'Unfollow',
|
|
347
349
|
convert: 'Turn into task',
|
|
@@ -956,8 +958,9 @@ window.__ModuleLoader__.load({
|
|
|
956
958
|
.at-btn-small { min-height: 24px; padding: 0 8px; font-size: 11px; }
|
|
957
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; }
|
|
958
960
|
.at-agentfail { flex: none; color: var(--dsw-alias-state-warn-primary); }
|
|
959
|
-
.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; }
|
|
960
|
-
.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; }
|
|
961
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); }
|
|
962
965
|
.at-teampulsehead { display: flex; align-items: baseline; justify-content: space-between; gap: 8px; margin-bottom: 6px; }
|
|
963
966
|
.at-teampulsetitle { font-size: 12px; font-weight: 600; color: var(--dsw-alias-label-primary); }
|
|
@@ -1534,7 +1537,7 @@ window.__ModuleLoader__.load({
|
|
|
1534
1537
|
h('span', { className: 'at-prowtitle' }, project.name));
|
|
1535
1538
|
}
|
|
1536
1539
|
|
|
1537
|
-
function AgentRow({ t, agent, onAgent, onAgentMore }) {
|
|
1540
|
+
function AgentRow({ t, agent, onAgent, onAgentMore, onAgentChat }) {
|
|
1538
1541
|
const statusText = t(AGENT_STATUS[agent.status] ?? 'agentIdle');
|
|
1539
1542
|
const config = agent.config ?? null;
|
|
1540
1543
|
const model = config?.model ? config.model : t('agentDefaultModel');
|
|
@@ -1557,6 +1560,13 @@ window.__ModuleLoader__.load({
|
|
|
1557
1560
|
),
|
|
1558
1561
|
),
|
|
1559
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 })),
|
|
1560
1570
|
h('button', {
|
|
1561
1571
|
className: 'at-agentmore',
|
|
1562
1572
|
type: 'button',
|
|
@@ -1569,7 +1579,8 @@ window.__ModuleLoader__.load({
|
|
|
1569
1579
|
function TeamPulse({ t, agents, tasks, onOpen }) {
|
|
1570
1580
|
const active = tasks.filter((task) => ['pending', 'in_progress', 'pending_acceptance'].includes(task.status)).slice(-5);
|
|
1571
1581
|
const running = agents.filter((agent) => agent.status === 'running');
|
|
1572
|
-
|
|
1582
|
+
const starting = active.length === 0 && tasks.length === 0 && running.length > 0;
|
|
1583
|
+
if (active.length === 0 && !starting) return null;
|
|
1573
1584
|
return h('section', { className: 'at-teampulse', 'aria-label': t('teamPulseTitle') },
|
|
1574
1585
|
h('div', { className: 'at-teampulsehead' },
|
|
1575
1586
|
h('span', { className: 'at-teampulsetitle' }, t('teamPulseTitle')),
|
|
@@ -1616,7 +1627,7 @@ window.__ModuleLoader__.load({
|
|
|
1616
1627
|
: h(EmptyState, { t, title: t('memberNoTasks'), body: t('memberNoTasksBody'), compact: true })));
|
|
1617
1628
|
}
|
|
1618
1629
|
|
|
1619
|
-
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 }) {
|
|
1620
1631
|
const projects = data.projects.filter((project) => !project.archived);
|
|
1621
1632
|
const activeAgents = data.agents.filter((agent) => agent.status !== 'retired');
|
|
1622
1633
|
const talentAgents = data.agents.filter((agent) => agent.status === 'retired');
|
|
@@ -1666,7 +1677,7 @@ window.__ModuleLoader__.load({
|
|
|
1666
1677
|
h('button', { className: 'at-mini', type: 'button', 'aria-label': t('addAgent'), title: t('addAgent'), onClick: onAddAgent }, h(Ico, { name: 'plus', size: 12 })),
|
|
1667
1678
|
),
|
|
1668
1679
|
activeAgents.length
|
|
1669
|
-
? 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 })))
|
|
1670
1681
|
: h('div', { className: 'at-colempty' }, t('emptyColumn')),
|
|
1671
1682
|
talentAgents.length ? h('details', { className: 'at-archived' },
|
|
1672
1683
|
h('summary', null, tf(t, 'talentPoolCount', { n: talentAgents.length })),
|
|
@@ -1780,7 +1791,7 @@ window.__ModuleLoader__.load({
|
|
|
1780
1791
|
task.submission?.summary ? h('p', { className: 'at-donetext' }, task.submission.summary) : null);
|
|
1781
1792
|
}
|
|
1782
1793
|
|
|
1783
|
-
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 }) {
|
|
1784
1795
|
const headSeq = thread?.headSeq ?? thread?.entries?.length ?? 0;
|
|
1785
1796
|
const stale = replyBase !== null && replyBase !== undefined && headSeq > replyBase;
|
|
1786
1797
|
const entries = Array.isArray(thread?.entries) ? thread.entries : [];
|
|
@@ -1790,6 +1801,7 @@ window.__ModuleLoader__.load({
|
|
|
1790
1801
|
h('div', { className: 'at-rhmeta', title: t('thread') + ' · #' + taskNo(task) }, t('thread') + ' · #' + taskNo(task)),
|
|
1791
1802
|
h('div', { className: 'at-rhtitle', title: task.title }, task.title)),
|
|
1792
1803
|
h(FollowToggle, { t, taskNo: taskNo(task), following, onToggle: onToggleFollow }),
|
|
1804
|
+
onChat ? h('button', { className: 'at-rbtn', type: 'button', onClick: onChat }, t('threadChat')) : null,
|
|
1793
1805
|
h('button', { className: 'at-rbtn', type: 'button', 'aria-expanded': Boolean(converting), onClick: convert.onOpen }, t('convert')),
|
|
1794
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 }))),
|
|
1795
1807
|
note ? h('div', { className: 'at-rnote', role: 'status' },
|
|
@@ -2536,6 +2548,15 @@ window.__ModuleLoader__.load({
|
|
|
2536
2548
|
openProfile(agent);
|
|
2537
2549
|
}
|
|
2538
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
|
+
}
|
|
2539
2560
|
function openProfile(agent) {
|
|
2540
2561
|
setProfileAgent(agent);
|
|
2541
2562
|
setPName(agent.name ?? '');
|
|
@@ -2904,6 +2925,7 @@ window.__ModuleLoader__.load({
|
|
|
2904
2925
|
onNewProject: () => { closeMobile(); openNewProject(); },
|
|
2905
2926
|
onAgent: (agent) => { closeMobile(); openMemberView(agent); },
|
|
2906
2927
|
onAgentMore: (agent) => { closeMobile(); openProfile(agent); },
|
|
2928
|
+
onAgentChat: (agent) => { closeMobile(); void openAgentChat(agent); },
|
|
2907
2929
|
onAddAgent: () => { closeMobile(); openAddAgent(); },
|
|
2908
2930
|
onRecruit: (agent) => { closeMobile(); recruit(agent); },
|
|
2909
2931
|
};
|
|
@@ -3063,6 +3085,7 @@ window.__ModuleLoader__.load({
|
|
|
3063
3085
|
following: followingOf(selectedTask),
|
|
3064
3086
|
onToggleFollow: () => toggleFollow(selectedTask),
|
|
3065
3087
|
onClose: closeThread,
|
|
3088
|
+
onChat: () => openTaskAgentChat(selectedTask),
|
|
3066
3089
|
converting,
|
|
3067
3090
|
convert: {
|
|
3068
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",
|