@suifei/dsh-ai-team 0.3.14 → 0.3.16

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 CHANGED
@@ -1,5 +1,19 @@
1
1
  # Changelog
2
2
 
3
+ ## 0.3.16 — 2026-11-10
4
+
5
+ - 点击 Agent 列表中的成员名字直接打开真实对话;
6
+ - 没有 session 时先激活,再打开;
7
+ - 打开失败时落到成员资料,资料内提供「直接对话」和「他的任务」按钮。
8
+
9
+ ## 0.3.15 — 2026-11-10
10
+
11
+ - 成员 Session 自动挂到 Project workspace,不再散落在 DSH「未分组」;
12
+ - Task Thread 增加「对话」按钮,直接打开 assignee/owner 的真实 Session;
13
+ - 成员行增加直接对话按钮;点名字进入成员任务视图;
14
+ - 团队动态只在有 active Task 时显示(或全新项目刚唤醒时),已完成不再显示“正在拆解”;
15
+ - `huashu-nuwa` 从 Team 技能目录、prompt catalog 与 notes 加载中排除。
16
+
3
17
  ## 0.3.14 — 2026-11-10
4
18
 
5
19
  - 主对话顶部增加「团队动态」:显示正在推进的 Task、owner 和运行中的成员;
package/README.md CHANGED
@@ -88,8 +88,8 @@ dsh --profile ai-team --no-open --port 33271
88
88
  - **看板**:四列;进行中的卡片显示 accountable owner 与执行者;数量只统计 active Task。
89
89
  - **态势**:四象限——需要你、团队在做、等待受阻、最近完成。点击条目打开对应 Task 的 Thread。
90
90
  - **收件箱**:跨 Project 聚合;失败决策、需要你决定、关注 Task 的完成报告都在这里。
91
- - **成员视图**:点击成员名字,切到他的任务列表与「直接对话」入口。
92
- - **直接对话**:成员视图里点「直接对话」,打开该 Agent 的 DSH 原生 Session;没有 session 会先激活。
91
+ - **直接对话**:点击成员名字直接打开该 Agent 的 DSH 原生 Session;没有 session 会先激活;打不开时落到成员资料,资料里有「直接对话」按钮。
92
+ - **成员任务**:成员资料里点「他的任务」,查看他 owner/assignee 的 Task。
93
93
  - **成员资料**:点「⋯」改名、职责、personality、provider/model、运行配方、notes、skills、re-distill、离队/人才库。
94
94
 
95
95
  ---
@@ -105,7 +105,7 @@ dsh --profile ai-team --no-open --port 33271
105
105
  | 收件箱 | Human/Agent 各自拉取;跨 Project 聚合与项目内 ack |
106
106
  | 关注 | 关注 Task;完成后收到 `followed_task_update` |
107
107
  | 默认成员 | 新 Project 初始化墨渊 / 青岚 / 白砚;可改名、改职责、改个性、改模型 |
108
- | 成员 | 持久 session;点击名字进入成员视图(任务 + 直接对话);「⋯」编辑资料 |
108
+ | 成员 | 持久 session;点击名字直接对话;「⋯」资料里有任务视图与直接对话 |
109
109
  | 模型 | Provider / Model 下拉读取 DSH `llm` 目录;可跟随系统默认 |
110
110
  | 进化 | `team.evolve` 只改自己的 personality;验收后 reflection wake |
111
111
  | 人才库 | leave/rejoin;遗留 Task 可被接管(taken-over) |
@@ -215,7 +215,7 @@ round-19 DeepSeek + Zhipu 双 provider
215
215
  - 单机、单 Human;不支持多 Human 共享 Team;
216
216
  - billing/网络注入是测试 adapter,不是物理断网;
217
217
  - SSE 分桶用两个真实 provider 的独立订阅端验证,不是两个浏览器窗口;
218
- - nuwa / distilly / darwin 不内置,有则用、无则不用;
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
- if (active.length === 0 && running.length === 0) return null;
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' },
@@ -2520,7 +2532,12 @@ window.__ModuleLoader__.load({
2520
2532
  return;
2521
2533
  }
2522
2534
  if (agent.sessionId) {
2523
- service.openSession(agent.sessionId);
2535
+ try {
2536
+ service.openSession(agent.sessionId);
2537
+ } catch (error) {
2538
+ say(tf(t, 'sendFailed', { message: error?.message ?? String(error) }));
2539
+ openProfile(agent);
2540
+ }
2524
2541
  return;
2525
2542
  }
2526
2543
  try {
@@ -2536,6 +2553,15 @@ window.__ModuleLoader__.load({
2536
2553
  openProfile(agent);
2537
2554
  }
2538
2555
  }
2556
+ function openTaskAgentChat(task) {
2557
+ const memberId = task?.assigneeId ?? task?.ownerId;
2558
+ const agent = data.agents.find((candidate) => candidate.id === memberId) ?? null;
2559
+ if (agent === null) {
2560
+ say(t('agentChatUnavailable'));
2561
+ return;
2562
+ }
2563
+ void openAgentChat(agent);
2564
+ }
2539
2565
  function openProfile(agent) {
2540
2566
  setProfileAgent(agent);
2541
2567
  setPName(agent.name ?? '');
@@ -2902,8 +2928,9 @@ window.__ModuleLoader__.load({
2902
2928
  onBoard: switchBoard,
2903
2929
  onProject: switchProject,
2904
2930
  onNewProject: () => { closeMobile(); openNewProject(); },
2905
- onAgent: (agent) => { closeMobile(); openMemberView(agent); },
2931
+ onAgent: (agent) => { closeMobile(); void openAgentChat(agent); },
2906
2932
  onAgentMore: (agent) => { closeMobile(); openProfile(agent); },
2933
+ onAgentChat: (agent) => { closeMobile(); void openAgentChat(agent); },
2907
2934
  onAddAgent: () => { closeMobile(); openAddAgent(); },
2908
2935
  onRecruit: (agent) => { closeMobile(); recruit(agent); },
2909
2936
  };
@@ -3063,6 +3090,7 @@ window.__ModuleLoader__.load({
3063
3090
  following: followingOf(selectedTask),
3064
3091
  onToggleFollow: () => toggleFollow(selectedTask),
3065
3092
  onClose: closeThread,
3093
+ onChat: () => openTaskAgentChat(selectedTask),
3066
3094
  converting,
3067
3095
  convert: {
3068
3096
  onOpen: openConvert,
@@ -3191,6 +3219,7 @@ window.__ModuleLoader__.load({
3191
3219
  pError ? h('div', { className: 'at-modalerror', role: 'alert' }, pError) : null,
3192
3220
  h('div', { className: 'at-modalactions' },
3193
3221
  h('button', { className: 'at-btn', type: 'button', onClick: () => setProfileAgent(null), disabled: pBusy }, t('cancel')),
3222
+ h('button', { className: 'at-btn', type: 'button', onClick: () => { const target = profileAgent; setProfileAgent(null); openMemberView(target); } }, t('memberTasks')),
3194
3223
  h('button', { className: 'at-btn', type: 'button', onClick: () => { const target = profileAgent; setProfileAgent(null); void openAgentChat(target); } }, t('agentChat')),
3195
3224
  h('button', { className: 'at-btn', type: 'button', onClick: leaveProfile, disabled: pBusy }, t('leaveTeam')),
3196
3225
  profileAgent.status === 'failed' ? h('button', { className: 'at-btn', type: 'button', onClick: retryProfile, disabled: pBusy }, t('retry')) : null,
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.14';
37
+ const VERSION = '0.3.16';
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) => { booted = value; return 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 = summaries.slice(0, limit).map((skill) => {
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.14",
3
+ "version": "0.3.16",
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",