@suifei/dsh-ai-team 0.3.11 → 0.3.13

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,17 @@
1
1
  # Changelog
2
2
 
3
+ ## 0.3.13 — 2026-11-10
4
+
5
+ - 点击成员名字进入成员视图:该成员负责/执行的任务列表 + 直接对话 + 资料入口;
6
+ - 目录选择改用 `ctx.get('uiWorkspace').pickDirectory()`,修复「当前宿主不支持目录选择」;
7
+ - 选择目录取消不再误报为不支持;保留手填路径兜底。
8
+
9
+ ## 0.3.12 — 2026-11-10
10
+
11
+ - 点击成员名字直接打开该 Agent 的 DSH 原生 Session,可与它直接对话;
12
+ - 没有 session 的空闲成员会先激活再打开;
13
+ - 「⋯」仍打开成员资料,资料内也增加「直接对话」按钮。
14
+
3
15
  ## 0.3.11 — 2026-11-10
4
16
 
5
17
  - 修复空闲默认成员不接收 Main stream:owner 无 session 时先自动激活,再记录 `wake/delivered`;
package/README.md CHANGED
@@ -88,7 +88,9 @@ dsh --profile ai-team --no-open --port 33271
88
88
  - **看板**:四列;进行中的卡片显示 accountable owner 与执行者;数量只统计 active Task。
89
89
  - **态势**:四象限——需要你、团队在做、等待受阻、最近完成。点击条目打开对应 Task 的 Thread。
90
90
  - **收件箱**:跨 Project 聚合;失败决策、需要你决定、关注 Task 的完成报告都在这里。
91
- - **成员资料**:改名、职责、personality、provider/model、运行配方、notes、skills、re-distill、离队/人才库。
91
+ - **成员视图**:点击成员名字,切到他的任务列表与「直接对话」入口。
92
+ - **直接对话**:成员视图里点「直接对话」,打开该 Agent 的 DSH 原生 Session;没有 session 会先激活。
93
+ - **成员资料**:点「⋯」改名、职责、personality、provider/model、运行配方、notes、skills、re-distill、离队/人才库。
92
94
 
93
95
  ---
94
96
 
@@ -103,7 +105,7 @@ dsh --profile ai-team --no-open --port 33271
103
105
  | 收件箱 | Human/Agent 各自拉取;跨 Project 聚合与项目内 ack |
104
106
  | 关注 | 关注 Task;完成后收到 `followed_task_update` |
105
107
  | 默认成员 | 新 Project 初始化墨渊 / 青岚 / 白砚;可改名、改职责、改个性、改模型 |
106
- | 成员 | 持久 session;名字/角色/personality/notes/skills;空闲 owner 收到 Main stream 会自动激活 |
108
+ | 成员 | 持久 session;点击名字进入成员视图(任务 + 直接对话);「⋯」编辑资料 |
107
109
  | 模型 | Provider / Model 下拉读取 DSH `llm` 目录;可跟随系统默认 |
108
110
  | 进化 | `team.evolve` 只改自己的 personality;验收后 reflection wake |
109
111
  | 人才库 | leave/rejoin;遗留 Task 可被接管(taken-over) |
package/client.js CHANGED
@@ -178,8 +178,16 @@ window.__ModuleLoader__.load({
178
178
  projectSwitchMock: 'spike:项目切换只更新视图标题;按项目读取账本在 M1 接入。',
179
179
  archivedMock: 'spike:归档项目只读;取消归档在 M3 接入。',
180
180
  agentMock: 'spike:成员详情(打开会话 / 发消息 / 记忆与技能)在 M3 接入。',
181
- agentOpen: '查看 {name} 的详情',
181
+ agentOpen: '与 {name} 对话',
182
182
  moreActions: '更多操作',
183
+ agentChat: '直接对话',
184
+ agentChatOpening: '正在打开对话…',
185
+ agentChatUnavailable: '宿主没有可用的 Session 打开能力。',
186
+ memberTasks: '他的任务',
187
+ memberNoTasks: '还没有属于他的 Task',
188
+ memberNoTasksBody: '在项目里创建或指派 Task 后,会出现在这里。',
189
+ taskOwner: '负责人',
190
+ taskAssignee: '执行',
183
191
  newProject: '新建项目',
184
192
  addAgent: '添加成员',
185
193
  newProjectMock: 'spike:新建项目属于 S-01 流程,M1 之后接入。',
@@ -465,8 +473,16 @@ window.__ModuleLoader__.load({
465
473
  projectSwitchMock: 'spike: switching projects only updates the header; per-project ledger reads land with M1.',
466
474
  archivedMock: 'spike: archived projects are read-only; unarchive lands with M3.',
467
475
  agentMock: 'spike: member detail (open session / message / memory & skills) lands with M3.',
468
- agentOpen: 'Open details for {name}',
476
+ agentOpen: 'Chat with {name}',
469
477
  moreActions: 'More actions',
478
+ agentChat: 'Direct chat',
479
+ agentChatOpening: 'Opening conversation…',
480
+ agentChatUnavailable: 'This host cannot open a Session.',
481
+ memberTasks: 'Their tasks',
482
+ memberNoTasks: 'No Task belongs to this member yet',
483
+ memberNoTasksBody: 'Create or assign a Task in the Project and it appears here.',
484
+ taskOwner: 'Owner',
485
+ taskAssignee: 'Assignee',
470
486
  newProject: 'New project',
471
487
  addAgent: 'Add member',
472
488
  newProjectMock: 'spike: project creation belongs to S-01 and lands after M1.',
@@ -932,6 +948,20 @@ window.__ModuleLoader__.load({
932
948
  .at-agentfail { flex: none; color: var(--dsw-alias-state-warn-primary); }
933
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; }
934
950
  .at-agentmore:hover { background: var(--dsw-alias-interactive-bg-hover); color: var(--dsw-alias-label-primary); }
951
+ .at-memberview { flex: 1 1 auto; min-height: 0; overflow: auto; padding: 18px; }
952
+ .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
+ .at-memberviewid { display: flex; flex-direction: column; min-width: 0; flex: 1 1 180px; }
954
+ .at-memberviewid strong { font-size: 15px; line-height: 22px; }
955
+ .at-memberviewid span { color: var(--dsw-alias-label-tertiary); font-size: 11px; line-height: 16px; }
956
+ .at-memberviewactions { display: flex; gap: 6px; flex-wrap: wrap; }
957
+ .at-memberviewbody { max-width: 760px; margin: 0 auto; padding-top: 14px; }
958
+ .at-memberviewsub { color: var(--dsw-alias-label-tertiary); font-size: 12px; line-height: 18px; margin-bottom: 8px; }
959
+ .at-membertasklist { display: flex; flex-direction: column; gap: 6px; }
960
+ .at-membertask { display: flex; align-items: center; gap: 8px; width: 100%; padding: 8px 10px; border: 1px solid var(--dsw-alias-border-l1); border-radius: var(--dsw-radius-sm); background: var(--dsw-alias-bg-module-platform); color: var(--dsw-alias-label-primary); font: inherit; text-align: left; cursor: pointer; }
961
+ .at-membertask:hover { border-color: var(--dsw-alias-border-l2); background: var(--dsw-alias-interactive-bg-hover); }
962
+ .at-membertaskno { flex: 0 0 auto; color: var(--dsw-alias-label-tertiary); font-variant-numeric: tabular-nums; font-size: 11px; }
963
+ .at-membertasktitle { flex: 1 1 auto; min-width: 0; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; font-size: 12px; }
964
+ .at-membertaskmeta { flex: 0 0 auto; color: var(--dsw-alias-label-tertiary); font-size: 11px; }
935
965
  .at-sr { position: absolute; width: 1px; height: 1px; padding: 0; margin: -1px; overflow: hidden; clip: rect(0 0 0 0); clip-path: inset(50%); white-space: nowrap; border: 0; }
936
966
  .at-chead { flex: 0 0 auto; padding: 14px 18px 0; }
937
967
  .at-title { display: flex; align-items: baseline; gap: 8px; min-width: 0; color: var(--dsw-alias-label-primary); font-size: 16px; font-weight: 600; line-height: 22px; letter-spacing: -.01em; white-space: nowrap; overflow: hidden; text-overflow: ellipsis; }
@@ -1482,7 +1512,7 @@ window.__ModuleLoader__.load({
1482
1512
  h('span', { className: 'at-prowtitle' }, project.name));
1483
1513
  }
1484
1514
 
1485
- function AgentRow({ t, agent, onAgent }) {
1515
+ function AgentRow({ t, agent, onAgent, onAgentMore }) {
1486
1516
  const statusText = t(AGENT_STATUS[agent.status] ?? 'agentIdle');
1487
1517
  const config = agent.config ?? null;
1488
1518
  const model = config?.model ? config.model : t('agentDefaultModel');
@@ -1510,11 +1540,38 @@ window.__ModuleLoader__.load({
1510
1540
  type: 'button',
1511
1541
  'aria-label': tf(t, 'agentOpen', { name: agent.name }),
1512
1542
  title: t('moreActions'),
1513
- onClick: () => onAgent(agent),
1543
+ onClick: () => onAgentMore(agent),
1514
1544
  }, '⋯'));
1515
1545
  }
1516
1546
 
1517
- function LeftPanel({ t, data, projectId, scope, tab, inboxUnread, onBack, onInbox, onBoard, onProject, onNewProject, onAgent, onAddAgent, onRecruit }) {
1547
+ function MemberView({ t, agent, tasks, onBack, onOpenTask, onOpenChat, onOpenProfile }) {
1548
+ return h('div', { className: 'at-memberview' },
1549
+ h('div', { className: 'at-memberviewhead' },
1550
+ h('button', { className: 'at-btn at-btn-small', type: 'button', onClick: onBack }, '← ' + t('back')),
1551
+ h('span', { className: 'at-dot at-dot-' + (agent.status ?? 'idle'), 'aria-hidden': 'true' }),
1552
+ h('div', { className: 'at-memberviewid' },
1553
+ h('strong', null, agent.name),
1554
+ h('span', null, (agent.role ?? '') + (agent.qa ? ' · QA' : '') + ' · ' + t(AGENT_STATUS[agent.status] ?? 'agentIdle'))),
1555
+ h('div', { className: 'at-memberviewactions' },
1556
+ h('button', { className: 'at-btn at-btn-small at-btn-primary', type: 'button', onClick: () => onOpenChat(agent) }, t('agentChat')),
1557
+ h('button', { className: 'at-btn at-btn-small', type: 'button', onClick: () => onOpenProfile(agent) }, t('memberProfileTitle')))),
1558
+ h('div', { className: 'at-memberviewbody' },
1559
+ h('div', { className: 'at-memberviewsub' }, t('memberTasks') + ' · ' + tasks.length),
1560
+ tasks.length
1561
+ ? h('div', { className: 'at-membertasklist' }, tasks.map((task) => h('button', {
1562
+ key: task.id,
1563
+ className: 'at-membertask',
1564
+ type: 'button',
1565
+ onClick: () => onOpenTask(task),
1566
+ },
1567
+ h('span', { className: 'at-membertaskno' }, '#' + taskNo(task)),
1568
+ h('span', { className: 'at-membertasktitle' }, task.title),
1569
+ h(StatusPill, { t, status: task.status }),
1570
+ h('span', { className: 'at-membertaskmeta' }, task.ownerId === agent.id ? t('taskOwner') : t('taskAssignee')))))
1571
+ : h(EmptyState, { t, title: t('memberNoTasks'), body: t('memberNoTasksBody'), compact: true })));
1572
+ }
1573
+
1574
+ function LeftPanel({ t, data, projectId, scope, tab, inboxUnread, onBack, onInbox, onBoard, onProject, onNewProject, onAgent, onAgentMore, onAddAgent, onRecruit }) {
1518
1575
  const projects = data.projects.filter((project) => !project.archived);
1519
1576
  const activeAgents = data.agents.filter((agent) => agent.status !== 'retired');
1520
1577
  const talentAgents = data.agents.filter((agent) => agent.status === 'retired');
@@ -1564,7 +1621,7 @@ window.__ModuleLoader__.load({
1564
1621
  h('button', { className: 'at-mini', type: 'button', 'aria-label': t('addAgent'), title: t('addAgent'), onClick: onAddAgent }, h(Ico, { name: 'plus', size: 12 })),
1565
1622
  ),
1566
1623
  activeAgents.length
1567
- ? h('div', { role: 'list', 'aria-label': t('agentsAria') }, activeAgents.map((agent) => h(AgentRow, { key: agent.id, t, agent, onAgent })))
1624
+ ? h('div', { role: 'list', 'aria-label': t('agentsAria') }, activeAgents.map((agent) => h(AgentRow, { key: agent.id, t, agent, onAgent, onAgentMore })))
1568
1625
  : h('div', { className: 'at-colempty' }, t('emptyColumn')),
1569
1626
  talentAgents.length ? h('details', { className: 'at-archived' },
1570
1627
  h('summary', null, tf(t, 'talentPoolCount', { n: talentAgents.length })),
@@ -1988,6 +2045,7 @@ window.__ModuleLoader__.load({
1988
2045
  const [workspaceDraft, setWorkspaceDraft] = React.useState('');
1989
2046
  const [workspaceError, setWorkspaceError] = React.useState('');
1990
2047
  const [workspaceBusy, setWorkspaceBusy] = React.useState(false);
2048
+ const [memberViewId, setMemberViewId] = React.useState(null);
1991
2049
  const [nBusy, setNBusy] = React.useState(false);
1992
2050
  const [nError, setNError] = React.useState('');
1993
2051
  const [pSuccessor, setPSuccessor] = React.useState('');
@@ -2198,6 +2256,7 @@ window.__ModuleLoader__.load({
2198
2256
  }
2199
2257
  function goConversation() {
2200
2258
  closeMobile();
2259
+ setMemberViewId(null);
2201
2260
  setScope('project');
2202
2261
  setTab('main');
2203
2262
  setThreadOpen(false);
@@ -2209,6 +2268,7 @@ window.__ModuleLoader__.load({
2209
2268
  }
2210
2269
  function switchInbox() {
2211
2270
  closeMobile();
2271
+ setMemberViewId(null);
2212
2272
  setScope('inbox');
2213
2273
  setThreadOpen(false);
2214
2274
  setNote('');
@@ -2227,6 +2287,7 @@ window.__ModuleLoader__.load({
2227
2287
  await runCommand('project.activate', { projectId: next.id });
2228
2288
  setScope('project');
2229
2289
  setTab('main');
2290
+ setMemberViewId(null);
2230
2291
  setBoardFilter('all');
2231
2292
  setThreadOpen(false);
2232
2293
  setReply('');
@@ -2245,17 +2306,21 @@ window.__ModuleLoader__.load({
2245
2306
  setNewOpen(true);
2246
2307
  }
2247
2308
  async function pickWorkspacePath() {
2309
+ const service = ctx?.get?.('uiWorkspace') ?? ctx?.uiWorkspace;
2310
+ if (service === undefined || service === null || typeof service.pickDirectory !== 'function') {
2311
+ return { error: 'unavailable', path: null };
2312
+ }
2248
2313
  try {
2249
- const picked = await ctx?.uiWorkspace?.pickDirectory?.();
2250
- return picked ?? null;
2314
+ return { path: await service.pickDirectory(), error: null };
2251
2315
  } catch {
2252
- return null;
2316
+ return { error: 'failed', path: null };
2253
2317
  }
2254
2318
  }
2255
2319
  async function pickNewWorkspace() {
2256
2320
  const picked = await pickWorkspacePath();
2257
- if (picked) setNWorkspace(picked);
2258
- else setNError(t('workspacePickUnavailable'));
2321
+ if (picked.path) setNWorkspace(picked.path);
2322
+ else if (picked.error === 'unavailable') setNError(t('workspacePickUnavailable'));
2323
+ else if (picked.error === 'failed') setNError(t('workspacePickFailed'));
2259
2324
  }
2260
2325
  function openWorkspaceEditor() {
2261
2326
  setWorkspaceDraft(project?.workspacePath ?? '');
@@ -2264,8 +2329,9 @@ window.__ModuleLoader__.load({
2264
2329
  }
2265
2330
  async function pickProjectWorkspace() {
2266
2331
  const picked = await pickWorkspacePath();
2267
- if (picked) setWorkspaceDraft(picked);
2268
- else setWorkspaceError(t('workspacePickUnavailable'));
2332
+ if (picked.path) setWorkspaceDraft(picked.path);
2333
+ else if (picked.error === 'unavailable') setWorkspaceError(t('workspacePickUnavailable'));
2334
+ else if (picked.error === 'failed') setWorkspaceError(t('workspacePickFailed'));
2269
2335
  }
2270
2336
  async function saveWorkspace() {
2271
2337
  if (!project || workspaceBusy || !workspaceDraft.trim()) return;
@@ -2389,6 +2455,42 @@ window.__ModuleLoader__.load({
2389
2455
  setMBusy(false);
2390
2456
  }
2391
2457
  }
2458
+ function openMemberView(agent) {
2459
+ closeMobile();
2460
+ setMemberViewId(agent.id);
2461
+ setScope('project');
2462
+ setTab('main');
2463
+ setThreadOpen(false);
2464
+ setNote('');
2465
+ }
2466
+ function closeMemberView() {
2467
+ setMemberViewId(null);
2468
+ }
2469
+ async function openAgentChat(agent) {
2470
+ closeMobile();
2471
+ const service = ctx?.get?.('uiWorkspace') ?? ctx?.uiWorkspace;
2472
+ if (service?.openSession === undefined) {
2473
+ say(t('agentChatUnavailable'));
2474
+ openProfile(agent);
2475
+ return;
2476
+ }
2477
+ if (agent.sessionId) {
2478
+ service.openSession(agent.sessionId);
2479
+ return;
2480
+ }
2481
+ try {
2482
+ say(t('agentChatOpening'));
2483
+ const activated = await runCommand('member.activate', {
2484
+ memberId: agent.id,
2485
+ prompt: '你正在与 Human 直接对话。先简要说明你是谁、当前负责什么;不要创建 Task,除非 Human 明确要求。',
2486
+ });
2487
+ await reloadData();
2488
+ if (activated?.sessionId) service.openSession(activated.sessionId);
2489
+ } catch (error) {
2490
+ say(tf(t, 'sendFailed', { message: error?.message ?? String(error) }));
2491
+ openProfile(agent);
2492
+ }
2493
+ }
2392
2494
  function openProfile(agent) {
2393
2495
  setProfileAgent(agent);
2394
2496
  setPName(agent.name ?? '');
@@ -2737,6 +2839,8 @@ window.__ModuleLoader__.load({
2737
2839
  }
2738
2840
 
2739
2841
  const inInbox = scope === 'inbox';
2842
+ const memberViewAgent = data.agents.find((agent) => agent.id === memberViewId) ?? null;
2843
+ const memberViewTasks = memberViewAgent === null ? [] : tasks.filter((task) => task.ownerId === memberViewAgent.id || task.assigneeId === memberViewAgent.id);
2740
2844
  const headerTitle = inInbox ? t('inboxTitle') : project ? '# ' + project.name : t('noProject');
2741
2845
  const headerGoal = inInbox ? t('inboxGoal') : (project?.goal ?? '');
2742
2846
  const unread = inboxUnread > 99 ? '99+' : inboxUnread;
@@ -2753,7 +2857,8 @@ window.__ModuleLoader__.load({
2753
2857
  onBoard: switchBoard,
2754
2858
  onProject: switchProject,
2755
2859
  onNewProject: () => { closeMobile(); openNewProject(); },
2756
- onAgent: (agent) => { closeMobile(); openProfile(agent); },
2860
+ onAgent: (agent) => { closeMobile(); openMemberView(agent); },
2861
+ onAgentMore: (agent) => { closeMobile(); openProfile(agent); },
2757
2862
  onAddAgent: () => { closeMobile(); openAddAgent(); },
2758
2863
  onRecruit: (agent) => { closeMobile(); recruit(agent); },
2759
2864
  };
@@ -2787,6 +2892,16 @@ window.__ModuleLoader__.load({
2787
2892
  body: t('inboxEmptyBody'),
2788
2893
  action: { label: t('inboxEmptyAction'), onClick: switchBoard },
2789
2894
  })))
2895
+ : memberViewAgent && tab === 'main'
2896
+ ? h(MemberView, {
2897
+ t,
2898
+ agent: memberViewAgent,
2899
+ tasks: memberViewTasks,
2900
+ onBack: closeMemberView,
2901
+ onOpenTask: openTask,
2902
+ onOpenChat: (agent) => { void openAgentChat(agent); },
2903
+ onOpenProfile: openProfile,
2904
+ })
2790
2905
  : tab === 'board'
2791
2906
  ? h(Board, {
2792
2907
  t,
@@ -2883,7 +2998,7 @@ window.__ModuleLoader__.load({
2883
2998
  h('span', { className: 'at-notetext' }, note),
2884
2999
  h('button', { className: 'at-noteclose', type: 'button', 'aria-label': t('dismiss'), title: t('dismiss'), onClick: () => setNote('') }, h(Ico, { name: 'close', size: 12 })))) : null,
2885
3000
  body,
2886
- !inInbox && tab === 'main' ? h(Composer, {
3001
+ !inInbox && tab === 'main' && !memberViewAgent ? h(Composer, {
2887
3002
  t,
2888
3003
  mode,
2889
3004
  draft,
@@ -3030,6 +3145,7 @@ window.__ModuleLoader__.load({
3030
3145
  pError ? h('div', { className: 'at-modalerror', role: 'alert' }, pError) : null,
3031
3146
  h('div', { className: 'at-modalactions' },
3032
3147
  h('button', { className: 'at-btn', type: 'button', onClick: () => setProfileAgent(null), disabled: pBusy }, t('cancel')),
3148
+ h('button', { className: 'at-btn', type: 'button', onClick: () => { const target = profileAgent; setProfileAgent(null); void openAgentChat(target); } }, t('agentChat')),
3033
3149
  h('button', { className: 'at-btn', type: 'button', onClick: leaveProfile, disabled: pBusy }, t('leaveTeam')),
3034
3150
  profileAgent.status === 'failed' ? h('button', { className: 'at-btn', type: 'button', onClick: retryProfile, disabled: pBusy }, t('retry')) : null,
3035
3151
  h('button', { className: 'at-btn', type: 'button', onClick: distillProfile, disabled: pBusy }, t('redistill')),
package/index.js CHANGED
@@ -33,7 +33,7 @@ export const name = 'ai-team';
33
33
  /** The workbench only makes sense where the Web carrier exists. */
34
34
  export const inject = ['webServer', 'permissionPresets', 'sandboxPolicy'];
35
35
 
36
- const VERSION = '0.3.11';
36
+ const VERSION = '0.3.13';
37
37
  const PROJECT_ID = 'prj_main';
38
38
  const HUMAN_ID = 'mem_human';
39
39
 
@@ -101,7 +101,9 @@ export class TeamService {
101
101
  title: task.title,
102
102
  status: task.status,
103
103
  owner: state.members?.[task.ownerId]?.name ?? task.ownerId,
104
+ ownerId: task.ownerId,
104
105
  assignee: task.assigneeId === null || task.assigneeId === undefined ? null : (state.members?.[task.assigneeId]?.name ?? task.assigneeId),
106
+ assigneeId: task.assigneeId ?? null,
105
107
  replies: state.threads?.[task.id]?.headSeq ?? 0,
106
108
  following: (state.followers?.[task.id] ?? []).length > 0,
107
109
  blockedBy: task.blockedBy ?? [],
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@suifei/dsh-ai-team",
3
- "version": "0.3.11",
3
+ "version": "0.3.13",
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",