@suifei/dsh-ai-team 0.3.12 → 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,11 @@
1
1
  # Changelog
2
2
 
3
+ ## 0.3.13 — 2026-11-10
4
+
5
+ - 点击成员名字进入成员视图:该成员负责/执行的任务列表 + 直接对话 + 资料入口;
6
+ - 目录选择改用 `ctx.get('uiWorkspace').pickDirectory()`,修复「当前宿主不支持目录选择」;
7
+ - 选择目录取消不再误报为不支持;保留手填路径兜底。
8
+
3
9
  ## 0.3.12 — 2026-11-10
4
10
 
5
11
  - 点击成员名字直接打开该 Agent 的 DSH 原生 Session,可与它直接对话;
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;点击名字直接打开该 Agent 的 DSH Session 对话;「⋯」编辑资料 |
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
@@ -183,6 +183,11 @@ window.__ModuleLoader__.load({
183
183
  agentChat: '直接对话',
184
184
  agentChatOpening: '正在打开对话…',
185
185
  agentChatUnavailable: '宿主没有可用的 Session 打开能力。',
186
+ memberTasks: '他的任务',
187
+ memberNoTasks: '还没有属于他的 Task',
188
+ memberNoTasksBody: '在项目里创建或指派 Task 后,会出现在这里。',
189
+ taskOwner: '负责人',
190
+ taskAssignee: '执行',
186
191
  newProject: '新建项目',
187
192
  addAgent: '添加成员',
188
193
  newProjectMock: 'spike:新建项目属于 S-01 流程,M1 之后接入。',
@@ -473,6 +478,11 @@ window.__ModuleLoader__.load({
473
478
  agentChat: 'Direct chat',
474
479
  agentChatOpening: 'Opening conversation…',
475
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',
476
486
  newProject: 'New project',
477
487
  addAgent: 'Add member',
478
488
  newProjectMock: 'spike: project creation belongs to S-01 and lands after M1.',
@@ -938,6 +948,20 @@ window.__ModuleLoader__.load({
938
948
  .at-agentfail { flex: none; color: var(--dsw-alias-state-warn-primary); }
939
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; }
940
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; }
941
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; }
942
966
  .at-chead { flex: 0 0 auto; padding: 14px 18px 0; }
943
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; }
@@ -1520,6 +1544,33 @@ window.__ModuleLoader__.load({
1520
1544
  }, '⋯'));
1521
1545
  }
1522
1546
 
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
+
1523
1574
  function LeftPanel({ t, data, projectId, scope, tab, inboxUnread, onBack, onInbox, onBoard, onProject, onNewProject, onAgent, onAgentMore, onAddAgent, onRecruit }) {
1524
1575
  const projects = data.projects.filter((project) => !project.archived);
1525
1576
  const activeAgents = data.agents.filter((agent) => agent.status !== 'retired');
@@ -1994,6 +2045,7 @@ window.__ModuleLoader__.load({
1994
2045
  const [workspaceDraft, setWorkspaceDraft] = React.useState('');
1995
2046
  const [workspaceError, setWorkspaceError] = React.useState('');
1996
2047
  const [workspaceBusy, setWorkspaceBusy] = React.useState(false);
2048
+ const [memberViewId, setMemberViewId] = React.useState(null);
1997
2049
  const [nBusy, setNBusy] = React.useState(false);
1998
2050
  const [nError, setNError] = React.useState('');
1999
2051
  const [pSuccessor, setPSuccessor] = React.useState('');
@@ -2204,6 +2256,7 @@ window.__ModuleLoader__.load({
2204
2256
  }
2205
2257
  function goConversation() {
2206
2258
  closeMobile();
2259
+ setMemberViewId(null);
2207
2260
  setScope('project');
2208
2261
  setTab('main');
2209
2262
  setThreadOpen(false);
@@ -2215,6 +2268,7 @@ window.__ModuleLoader__.load({
2215
2268
  }
2216
2269
  function switchInbox() {
2217
2270
  closeMobile();
2271
+ setMemberViewId(null);
2218
2272
  setScope('inbox');
2219
2273
  setThreadOpen(false);
2220
2274
  setNote('');
@@ -2233,6 +2287,7 @@ window.__ModuleLoader__.load({
2233
2287
  await runCommand('project.activate', { projectId: next.id });
2234
2288
  setScope('project');
2235
2289
  setTab('main');
2290
+ setMemberViewId(null);
2236
2291
  setBoardFilter('all');
2237
2292
  setThreadOpen(false);
2238
2293
  setReply('');
@@ -2251,17 +2306,21 @@ window.__ModuleLoader__.load({
2251
2306
  setNewOpen(true);
2252
2307
  }
2253
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
+ }
2254
2313
  try {
2255
- const picked = await ctx?.uiWorkspace?.pickDirectory?.();
2256
- return picked ?? null;
2314
+ return { path: await service.pickDirectory(), error: null };
2257
2315
  } catch {
2258
- return null;
2316
+ return { error: 'failed', path: null };
2259
2317
  }
2260
2318
  }
2261
2319
  async function pickNewWorkspace() {
2262
2320
  const picked = await pickWorkspacePath();
2263
- if (picked) setNWorkspace(picked);
2264
- 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'));
2265
2324
  }
2266
2325
  function openWorkspaceEditor() {
2267
2326
  setWorkspaceDraft(project?.workspacePath ?? '');
@@ -2270,8 +2329,9 @@ window.__ModuleLoader__.load({
2270
2329
  }
2271
2330
  async function pickProjectWorkspace() {
2272
2331
  const picked = await pickWorkspacePath();
2273
- if (picked) setWorkspaceDraft(picked);
2274
- 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'));
2275
2335
  }
2276
2336
  async function saveWorkspace() {
2277
2337
  if (!project || workspaceBusy || !workspaceDraft.trim()) return;
@@ -2395,15 +2455,27 @@ window.__ModuleLoader__.load({
2395
2455
  setMBusy(false);
2396
2456
  }
2397
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
+ }
2398
2469
  async function openAgentChat(agent) {
2399
2470
  closeMobile();
2400
- if (ctx?.uiWorkspace?.openSession === undefined) {
2471
+ const service = ctx?.get?.('uiWorkspace') ?? ctx?.uiWorkspace;
2472
+ if (service?.openSession === undefined) {
2401
2473
  say(t('agentChatUnavailable'));
2402
2474
  openProfile(agent);
2403
2475
  return;
2404
2476
  }
2405
2477
  if (agent.sessionId) {
2406
- ctx.uiWorkspace.openSession(agent.sessionId);
2478
+ service.openSession(agent.sessionId);
2407
2479
  return;
2408
2480
  }
2409
2481
  try {
@@ -2413,7 +2485,7 @@ window.__ModuleLoader__.load({
2413
2485
  prompt: '你正在与 Human 直接对话。先简要说明你是谁、当前负责什么;不要创建 Task,除非 Human 明确要求。',
2414
2486
  });
2415
2487
  await reloadData();
2416
- if (activated?.sessionId) ctx.uiWorkspace.openSession(activated.sessionId);
2488
+ if (activated?.sessionId) service.openSession(activated.sessionId);
2417
2489
  } catch (error) {
2418
2490
  say(tf(t, 'sendFailed', { message: error?.message ?? String(error) }));
2419
2491
  openProfile(agent);
@@ -2767,6 +2839,8 @@ window.__ModuleLoader__.load({
2767
2839
  }
2768
2840
 
2769
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);
2770
2844
  const headerTitle = inInbox ? t('inboxTitle') : project ? '# ' + project.name : t('noProject');
2771
2845
  const headerGoal = inInbox ? t('inboxGoal') : (project?.goal ?? '');
2772
2846
  const unread = inboxUnread > 99 ? '99+' : inboxUnread;
@@ -2783,7 +2857,7 @@ window.__ModuleLoader__.load({
2783
2857
  onBoard: switchBoard,
2784
2858
  onProject: switchProject,
2785
2859
  onNewProject: () => { closeMobile(); openNewProject(); },
2786
- onAgent: (agent) => { closeMobile(); void openAgentChat(agent); },
2860
+ onAgent: (agent) => { closeMobile(); openMemberView(agent); },
2787
2861
  onAgentMore: (agent) => { closeMobile(); openProfile(agent); },
2788
2862
  onAddAgent: () => { closeMobile(); openAddAgent(); },
2789
2863
  onRecruit: (agent) => { closeMobile(); recruit(agent); },
@@ -2818,6 +2892,16 @@ window.__ModuleLoader__.load({
2818
2892
  body: t('inboxEmptyBody'),
2819
2893
  action: { label: t('inboxEmptyAction'), onClick: switchBoard },
2820
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
+ })
2821
2905
  : tab === 'board'
2822
2906
  ? h(Board, {
2823
2907
  t,
@@ -2914,7 +2998,7 @@ window.__ModuleLoader__.load({
2914
2998
  h('span', { className: 'at-notetext' }, note),
2915
2999
  h('button', { className: 'at-noteclose', type: 'button', 'aria-label': t('dismiss'), title: t('dismiss'), onClick: () => setNote('') }, h(Ico, { name: 'close', size: 12 })))) : null,
2916
3000
  body,
2917
- !inInbox && tab === 'main' ? h(Composer, {
3001
+ !inInbox && tab === 'main' && !memberViewAgent ? h(Composer, {
2918
3002
  t,
2919
3003
  mode,
2920
3004
  draft,
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.12';
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.12",
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",