@wowyuarm/dsh-agent-team 0.1.0 → 0.1.2

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.
Files changed (91) hide show
  1. package/README.md +14 -2
  2. package/README.zh.md +10 -2
  3. package/package.json +32 -24
  4. package/packages/agent-team/lib/attachments.js +157 -0
  5. package/packages/agent-team/lib/index.js +280 -22
  6. package/packages/agent-team/lib/ledger.js +98 -22
  7. package/packages/agent-team/lib/recovery.js +87 -0
  8. package/packages/agent-team/lib/spec.js +27 -5
  9. package/packages/agent-team/lib/typert.host.js +313 -26
  10. package/packages/agent-team/lib/typert.remote-client.d.ts +9 -1
  11. package/packages/agent-team/lib/typert.remote-client.d.ts.map +1 -1
  12. package/packages/agent-team/lib/typert.remote-client.js +234 -17
  13. package/packages/agent-team/lib/types/attachments.d.ts +69 -0
  14. package/packages/agent-team/lib/types/attachments.d.ts.map +1 -0
  15. package/packages/agent-team/lib/types/index.d.ts +68 -6
  16. package/packages/agent-team/lib/types/index.d.ts.map +1 -1
  17. package/packages/agent-team/lib/types/ledger.d.ts +17 -1
  18. package/packages/agent-team/lib/types/ledger.d.ts.map +1 -1
  19. package/packages/agent-team/lib/types/recovery.d.ts +42 -0
  20. package/packages/agent-team/lib/types/recovery.d.ts.map +1 -0
  21. package/packages/agent-team/lib/types/spec.d.ts.map +1 -1
  22. package/packages/agent-team/lib/types/types.d.ts +94 -3
  23. package/packages/agent-team/lib/types/types.d.ts.map +1 -1
  24. package/packages/agent-team/preset/team-member/agent.cordis.yml +2 -2
  25. package/packages/client-agent-team/README.md +4 -3
  26. package/packages/client-agent-team/README.zh.md +4 -3
  27. package/packages/client-agent-team/lib/client.js +1791 -322
  28. package/packages/client-agent-team/lib/client.js.map +1 -1
  29. package/packages/client-agent-team/lib/types/client/TeamAgentsPanel.d.ts +5 -2
  30. package/packages/client-agent-team/lib/types/client/TeamAgentsPanel.d.ts.map +1 -1
  31. package/packages/client-agent-team/lib/types/client/TeamAgentsPanel.js +108 -59
  32. package/packages/client-agent-team/lib/types/client/TeamChannelPage.d.ts +7 -1
  33. package/packages/client-agent-team/lib/types/client/TeamChannelPage.d.ts.map +1 -1
  34. package/packages/client-agent-team/lib/types/client/TeamChannelPage.js +80 -19
  35. package/packages/client-agent-team/lib/types/client/TeamChannelsPanel.d.ts.map +1 -1
  36. package/packages/client-agent-team/lib/types/client/TeamChannelsPanel.js +38 -19
  37. package/packages/client-agent-team/lib/types/client/TeamComposer.d.ts +4 -1
  38. package/packages/client-agent-team/lib/types/client/TeamComposer.d.ts.map +1 -1
  39. package/packages/client-agent-team/lib/types/client/TeamComposer.js +61 -4
  40. package/packages/client-agent-team/lib/types/client/TeamConversation.d.ts +1 -1
  41. package/packages/client-agent-team/lib/types/client/TeamConversation.d.ts.map +1 -1
  42. package/packages/client-agent-team/lib/types/client/TeamConversation.js +3 -3
  43. package/packages/client-agent-team/lib/types/client/TeamMessage.d.ts +12 -2
  44. package/packages/client-agent-team/lib/types/client/TeamMessage.d.ts.map +1 -1
  45. package/packages/client-agent-team/lib/types/client/TeamMessage.js +152 -14
  46. package/packages/client-agent-team/lib/types/client/TeamRunDivider.d.ts +10 -0
  47. package/packages/client-agent-team/lib/types/client/TeamRunDivider.d.ts.map +1 -0
  48. package/packages/client-agent-team/lib/types/client/TeamRunDivider.js +12 -0
  49. package/packages/client-agent-team/lib/types/client/TeamThreadPage.d.ts +7 -0
  50. package/packages/client-agent-team/lib/types/client/TeamThreadPage.d.ts.map +1 -1
  51. package/packages/client-agent-team/lib/types/client/TeamThreadPage.js +93 -16
  52. package/packages/client-agent-team/lib/types/client/TeamWorkspaceBrowser.d.ts +1 -1
  53. package/packages/client-agent-team/lib/types/client/TeamWorkspaceBrowser.d.ts.map +1 -1
  54. package/packages/client-agent-team/lib/types/client/TeamWorkspaceBrowser.js +6 -5
  55. package/packages/client-agent-team/lib/types/client/attachment-preview.d.ts +22 -0
  56. package/packages/client-agent-team/lib/types/client/attachment-preview.d.ts.map +1 -0
  57. package/packages/client-agent-team/lib/types/client/attachment-preview.js +35 -0
  58. package/packages/client-agent-team/lib/types/client/drafts.d.ts +41 -0
  59. package/packages/client-agent-team/lib/types/client/drafts.d.ts.map +1 -0
  60. package/packages/client-agent-team/lib/types/client/drafts.js +144 -0
  61. package/packages/client-agent-team/lib/types/client/index.d.ts +2 -0
  62. package/packages/client-agent-team/lib/types/client/index.d.ts.map +1 -1
  63. package/packages/client-agent-team/lib/types/client/index.js +50 -10
  64. package/packages/client-agent-team/lib/types/client/locales.d.ts +34 -0
  65. package/packages/client-agent-team/lib/types/client/locales.d.ts.map +1 -1
  66. package/packages/client-agent-team/lib/types/client/locales.js +34 -0
  67. package/packages/client-agent-team/lib/types/client/multi-menu-field.d.ts +33 -0
  68. package/packages/client-agent-team/lib/types/client/multi-menu-field.d.ts.map +1 -0
  69. package/packages/client-agent-team/lib/types/client/multi-menu-field.js +30 -0
  70. package/packages/client-agent-team/lib/types/client/navigation.d.ts +16 -1
  71. package/packages/client-agent-team/lib/types/client/navigation.d.ts.map +1 -1
  72. package/packages/client-agent-team/lib/types/client/navigation.js +45 -8
  73. package/packages/client-agent-team/lib/types/client/sidebar-drag.d.ts +35 -0
  74. package/packages/client-agent-team/lib/types/client/sidebar-drag.d.ts.map +1 -0
  75. package/packages/client-agent-team/lib/types/client/sidebar-drag.js +70 -0
  76. package/packages/client-agent-team/lib/types/client/sidebar-order.d.ts +27 -0
  77. package/packages/client-agent-team/lib/types/client/sidebar-order.d.ts.map +1 -0
  78. package/packages/client-agent-team/lib/types/client/sidebar-order.js +170 -0
  79. package/packages/client-agent-team/lib/types/client/slots.d.ts +18 -2
  80. package/packages/client-agent-team/lib/types/client/slots.d.ts.map +1 -1
  81. package/packages/client-agent-team/lib/types/client/task-refs.d.ts +20 -0
  82. package/packages/client-agent-team/lib/types/client/task-refs.d.ts.map +1 -0
  83. package/packages/client-agent-team/lib/types/client/task-refs.js +58 -0
  84. package/packages/client-agent-team/lib/types/client/team-formatters.d.ts +15 -0
  85. package/packages/client-agent-team/lib/types/client/team-formatters.d.ts.map +1 -1
  86. package/packages/client-agent-team/lib/types/client/team-formatters.js +30 -2
  87. package/packages/client-agent-team/lib/types/client/team-separators.d.ts +8 -0
  88. package/packages/client-agent-team/lib/types/client/team-separators.d.ts.map +1 -1
  89. package/packages/client-agent-team/lib/types/client/team-separators.js +14 -0
  90. package/packages/tool-agent-team/lib/index.js +10 -6
  91. package/packages/tool-agent-team/lib/types/index.d.ts.map +1 -1
@@ -1,6 +1,7 @@
1
1
  import agentTeamRemote from '../../../agent-team/lib/typert.remote-client.js';
2
2
  import { TeamNavigation } from "./navigation.js";
3
3
  import { TeamChangeStream } from "./team-changes.js";
4
+ import { TeamDraftStore } from "./drafts.js";
4
5
  import { TeamFooterAction } from "./TeamFooterAction.js";
5
6
  import { TeamSettings } from "./TeamSettings.js";
6
7
  import { TeamConversation } from "./TeamConversation.js";
@@ -11,16 +12,18 @@ const NS = 'team';
11
12
  export const inject = [
12
13
  'slots', 'workspaces', 'locale', 'remote', 'sessions', 'connection',
13
14
  ];
14
- function registerModeShadow(ctx, navigation, changes, name, component, extraInject) {
15
+ function registerModeShadow(ctx, navigation, changes, drafts, name, component, extraInject) {
15
16
  // Remote bindings shared by every Team slot; surface-specific entries extend it below.
16
17
  const sharedRemotes = {
17
18
  loadChannels: (request) => ctx.remote.agentTeam.view(request),
18
19
  subscribeChanges: (scope, listener) => changes.subscribe(scope, listener),
20
+ drafts,
19
21
  loadMembers: (request) => ctx.remote.agentTeam.members(request),
20
22
  joinChannel: (request) => ctx.remote.agentTeam.joinChannel(request),
21
23
  removeChannelMember: (request) => ctx.remote.agentTeam.removeChannelMember(request),
22
24
  updateChannel: (request) => ctx.remote.agentTeam.updateChannel(request),
23
25
  updateMember: (request) => ctx.remote.agentTeam.updateMember(request),
26
+ recoverMember: (request) => ctx.remote.agentTeam.recoverMember(request),
24
27
  // The Host-scoped catalog needs no live Member, so suspended ones stay editable too.
25
28
  loadModels: async () => {
26
29
  const connection = ctx.get('connection');
@@ -28,16 +31,27 @@ function registerModeShadow(ctx, navigation, changes, name, component, extraInje
28
31
  return response.result;
29
32
  },
30
33
  openMemberSession: (sessionId) => {
31
- // Leave Team mode first so every Team shadow deregisters and the
32
- // ordinary shell actually renders the Member Session selected below.
33
- navigation.actions().leaveTeam();
34
- ctx.sessions.open(sessionId);
34
+ // Stay in Team mode: the conversation shadow stands down for Member
35
+ // Session views (see registerModeShadow), so the shipped conversation
36
+ // root renders the selected Member Session inside the Team shell.
37
+ const sessions = ctx.sessions;
38
+ const snapshot = navigation.getSnapshot();
39
+ const current = sessions.list.getSnapshot().current;
40
+ // The return target is captured on first entry only — switching between
41
+ // Member Sessions must keep pointing at the Human's original session.
42
+ const returnTo = snapshot.memberSessionId === undefined && current !== undefined && current !== sessionId ? current : undefined;
43
+ navigation.actions().enterMemberSession(sessionId, returnTo);
44
+ sessions.open(sessionId);
35
45
  },
36
46
  };
37
47
  ctx.slots.inject(name, () => {
38
48
  let dispose;
39
49
  const reconcile = () => {
40
- const active = navigation.getSnapshot().mode === 'team';
50
+ const snapshot = navigation.getSnapshot();
51
+ // The conversation seat yields to the shipped conversation root while a
52
+ // Member Session view is embedded; both sidebar seats stay shadowed so
53
+ // the Team chrome keeps working around the Member conversation.
54
+ const active = snapshot.mode === 'team' && !(name === 'conversation' && snapshot.memberSessionId !== undefined);
41
55
  if (active && dispose === undefined) {
42
56
  dispose = ctx.slots.register({
43
57
  name,
@@ -52,8 +66,11 @@ function registerModeShadow(ctx, navigation, changes, name, component, extraInje
52
66
  readThread: (request) => ctx.remote.agentTeam.readThread(request),
53
67
  loadThreadHistory: (request) => ctx.remote.agentTeam.threadHistory(request),
54
68
  sendMessage: (request) => ctx.remote.agentTeam.sendMessage(request),
69
+ putAttachment: (request) => ctx.remote.agentTeam.putAttachment(request),
70
+ getAttachment: (request) => ctx.remote.agentTeam.getAttachment(request),
55
71
  reply: (request) => ctx.remote.agentTeam.reply(request),
56
72
  changeTask: (request) => ctx.remote.agentTeam.changeTask(request),
73
+ resolveTaskRefs: (request) => ctx.remote.agentTeam.resolveTaskRefs(request),
57
74
  } : {}),
58
75
  ...(name === 'sidebar.workspaces' ? {
59
76
  addMember: (request) => ctx.remote.agentTeam.addMember(request),
@@ -80,9 +97,13 @@ function applyUi(ctx) {
80
97
  ctx.effect(() => ctx.locale.register(NS, { zh, en }), 'agent-team: dictionaries');
81
98
  const navigation = new TeamNavigation();
82
99
  const disposeNavigation = ctx.reflect.provide('teamNavigation', navigation);
100
+ const drafts = new TeamDraftStore();
101
+ const disposeDrafts = ctx.reflect.provide('teamDrafts', drafts);
83
102
  ctx.effect(() => () => {
84
103
  navigation.dispose();
104
+ drafts.dispose();
85
105
  void disposeNavigation();
106
+ void disposeDrafts();
86
107
  }, 'agent-team: navigation service');
87
108
  const changes = new TeamChangeStream((request, signal) => ctx.remote.agentTeam.changes(request, signal));
88
109
  const loadMemberGroups = async () => {
@@ -100,11 +121,30 @@ function applyUi(ctx) {
100
121
  id: 'agent-team',
101
122
  order: 100,
102
123
  locale: NS,
103
- inject: () => ({ navigation, ...navigation.actions(), loadMemberGroups }),
124
+ inject: () => ({
125
+ navigation,
126
+ ...navigation.actions(),
127
+ // The footer is the only surface that leaves Team mode; it also closes
128
+ // an embedded Member Session view, restoring the session the Human came
129
+ // from so the ordinary shell never strands them inside a Member Session.
130
+ leaveTeam: () => {
131
+ const snapshot = navigation.getSnapshot();
132
+ if (snapshot.memberSessionId === undefined) {
133
+ navigation.actions().leaveTeam();
134
+ return;
135
+ }
136
+ const returnTo = snapshot.returnToSessionId;
137
+ navigation.actions().exitMemberSession();
138
+ if (returnTo !== undefined)
139
+ ctx.sessions.open(returnTo);
140
+ navigation.actions().leaveTeam();
141
+ },
142
+ loadMemberGroups,
143
+ }),
104
144
  }, TeamFooterAction));
105
- registerModeShadow(ctx, navigation, changes, 'sidebar.workspaces', TeamWorkspaceBrowser);
106
- registerModeShadow(ctx, navigation, changes, 'conversation', TeamConversation);
107
- registerModeShadow(ctx, navigation, changes, 'sidebar.settings', TeamSettings, () => ({ loadMemberGroups }));
145
+ registerModeShadow(ctx, navigation, changes, drafts, 'sidebar.workspaces', TeamWorkspaceBrowser);
146
+ registerModeShadow(ctx, navigation, changes, drafts, 'conversation', TeamConversation);
147
+ registerModeShadow(ctx, navigation, changes, drafts, 'sidebar.settings', TeamSettings, () => ({ loadMemberGroups }));
108
148
  }
109
149
  export async function apply(ctx) {
110
150
  const disposeRemote = await ctx.remote.$mount(agentTeamRemote);
@@ -36,6 +36,19 @@ export declare const zh: {
36
36
  readonly manageMembers: "管理成员";
37
37
  readonly editChannel: "编辑频道";
38
38
  readonly editAgent: "编辑 Agent";
39
+ readonly resumeAgent: "恢复";
40
+ readonly optionalSuffix: "(可选)";
41
+ readonly agentDescriptionPlaceholder: "留空则暂无描述";
42
+ readonly channelsPickerEmpty: "选择初始频道";
43
+ readonly channelsPickerCount: "已选 {count} 个频道";
44
+ readonly membersPickerEmpty: "选择初始成员";
45
+ readonly membersPickerCount: "已选 {count} 个成员";
46
+ readonly reasoningEffort: "推理强度";
47
+ readonly effortFollowDefault: "跟随模型默认";
48
+ readonly attachFiles: "添加附件";
49
+ readonly removeFile: "移除 {name}";
50
+ readonly attachmentExpired: "文件已过期清理";
51
+ readonly viewImage: "查看大图 {name}";
39
52
  readonly actionsChannel: "{name} 的操作";
40
53
  readonly actionsAgent: "{name} 的操作";
41
54
  readonly editSave: "保存";
@@ -101,6 +114,10 @@ export declare const zh: {
101
114
  readonly activityClaimReleased: "{actor} 释放了「{direction}」";
102
115
  readonly activityClaimsReleased: "{actor} 因成员权限变化释放了 {count} 个 Claim";
103
116
  readonly activityAccepted: "{actor} 验收了此 Task";
117
+ readonly activityAcceptedWithClaims: "{actor} 验收了此 Task,并一并完成了 {count} 个未完成 Claim";
118
+ readonly acceptEarlyTitle: "提前验收任务";
119
+ readonly acceptEarlyBody: "将验收本 Task,并把以下 {count} 个未完成的 Claim 一并标记为完成:";
120
+ readonly acceptingTask: "验收中…";
104
121
  readonly activityClosed: "{actor} 关闭了此 Task";
105
122
  readonly activityReopened: "{actor} 重新打开了此 Task";
106
123
  readonly mentionConfirmation: "提及未关注成员需要确认,再次发送以继续";
@@ -147,6 +164,19 @@ export declare const en: {
147
164
  readonly manageMembers: "Manage members";
148
165
  readonly editChannel: "Edit Channel";
149
166
  readonly editAgent: "Edit Agent";
167
+ readonly resumeAgent: "Resume";
168
+ readonly optionalSuffix: " (optional)";
169
+ readonly agentDescriptionPlaceholder: "Leave empty if not needed";
170
+ readonly channelsPickerEmpty: "Choose initial channels";
171
+ readonly channelsPickerCount: "{count} selected";
172
+ readonly membersPickerEmpty: "Choose initial members";
173
+ readonly membersPickerCount: "{count} selected";
174
+ readonly reasoningEffort: "Reasoning effort";
175
+ readonly effortFollowDefault: "Follow model default";
176
+ readonly attachFiles: "Add attachments";
177
+ readonly removeFile: "Remove {name}";
178
+ readonly attachmentExpired: "File expired and cleaned up";
179
+ readonly viewImage: "View image {name}";
150
180
  readonly actionsChannel: "{name} actions";
151
181
  readonly actionsAgent: "{name} actions";
152
182
  readonly editSave: "Save";
@@ -212,6 +242,10 @@ export declare const en: {
212
242
  readonly activityClaimReleased: "{actor} released “{direction}”";
213
243
  readonly activityClaimsReleased: "{actor} released {count} Claim(s) after a membership change";
214
244
  readonly activityAccepted: "{actor} accepted this Task";
245
+ readonly activityAcceptedWithClaims: "{actor} accepted this Task and completed {count} unfinished Claims with it";
246
+ readonly acceptEarlyTitle: "Accept Task early";
247
+ readonly acceptEarlyBody: "This accepts the Task and marks the following {count} unfinished Claims as done:";
248
+ readonly acceptingTask: "Accepting…";
215
249
  readonly activityClosed: "{actor} closed this Task";
216
250
  readonly activityReopened: "{actor} reopened this Task";
217
251
  readonly mentionConfirmation: "Mentioning an unfollowed member requires confirmation; send again to continue";
@@ -1 +1 @@
1
- {"version":3,"file":"locales.d.ts","sourceRoot":"","sources":["../../../src/client/locales.ts"],"names":[],"mappings":"AAAA,eAAO,MAAM,EAAE;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;CA8GL,CAAA;AAEV,eAAO,MAAM,EAAE;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;CA8GL,CAAA;AAEV,MAAM,MAAM,OAAO,GAAG,MAAM,OAAO,EAAE,CAAA"}
1
+ {"version":3,"file":"locales.d.ts","sourceRoot":"","sources":["../../../src/client/locales.ts"],"names":[],"mappings":"AAAA,eAAO,MAAM,EAAE;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;CA+HL,CAAA;AAEV,eAAO,MAAM,EAAE;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;CA+HL,CAAA;AAEV,MAAM,MAAM,OAAO,GAAG,MAAM,OAAO,EAAE,CAAA"}
@@ -36,6 +36,19 @@ export const zh = {
36
36
  manageMembers: '管理成员',
37
37
  editChannel: '编辑频道',
38
38
  editAgent: '编辑 Agent',
39
+ resumeAgent: '恢复',
40
+ optionalSuffix: '(可选)',
41
+ agentDescriptionPlaceholder: '留空则暂无描述',
42
+ channelsPickerEmpty: '选择初始频道',
43
+ channelsPickerCount: '已选 {count} 个频道',
44
+ membersPickerEmpty: '选择初始成员',
45
+ membersPickerCount: '已选 {count} 个成员',
46
+ reasoningEffort: '推理强度',
47
+ effortFollowDefault: '跟随模型默认',
48
+ attachFiles: '添加附件',
49
+ removeFile: '移除 {name}',
50
+ attachmentExpired: '文件已过期清理',
51
+ viewImage: '查看大图 {name}',
39
52
  actionsChannel: '{name} 的操作',
40
53
  actionsAgent: '{name} 的操作',
41
54
  editSave: '保存',
@@ -101,6 +114,10 @@ export const zh = {
101
114
  activityClaimReleased: '{actor} 释放了「{direction}」',
102
115
  activityClaimsReleased: '{actor} 因成员权限变化释放了 {count} 个 Claim',
103
116
  activityAccepted: '{actor} 验收了此 Task',
117
+ activityAcceptedWithClaims: '{actor} 验收了此 Task,并一并完成了 {count} 个未完成 Claim',
118
+ acceptEarlyTitle: '提前验收任务',
119
+ acceptEarlyBody: '将验收本 Task,并把以下 {count} 个未完成的 Claim 一并标记为完成:',
120
+ acceptingTask: '验收中…',
104
121
  activityClosed: '{actor} 关闭了此 Task',
105
122
  activityReopened: '{actor} 重新打开了此 Task',
106
123
  mentionConfirmation: '提及未关注成员需要确认,再次发送以继续',
@@ -147,6 +164,19 @@ export const en = {
147
164
  manageMembers: 'Manage members',
148
165
  editChannel: 'Edit Channel',
149
166
  editAgent: 'Edit Agent',
167
+ resumeAgent: 'Resume',
168
+ optionalSuffix: ' (optional)',
169
+ agentDescriptionPlaceholder: 'Leave empty if not needed',
170
+ channelsPickerEmpty: 'Choose initial channels',
171
+ channelsPickerCount: '{count} selected',
172
+ membersPickerEmpty: 'Choose initial members',
173
+ membersPickerCount: '{count} selected',
174
+ reasoningEffort: 'Reasoning effort',
175
+ effortFollowDefault: 'Follow model default',
176
+ attachFiles: 'Add attachments',
177
+ removeFile: 'Remove {name}',
178
+ attachmentExpired: 'File expired and cleaned up',
179
+ viewImage: 'View image {name}',
150
180
  actionsChannel: '{name} actions',
151
181
  actionsAgent: '{name} actions',
152
182
  editSave: 'Save',
@@ -212,6 +242,10 @@ export const en = {
212
242
  activityClaimReleased: '{actor} released “{direction}”',
213
243
  activityClaimsReleased: '{actor} released {count} Claim(s) after a membership change',
214
244
  activityAccepted: '{actor} accepted this Task',
245
+ activityAcceptedWithClaims: '{actor} accepted this Task and completed {count} unfinished Claims with it',
246
+ acceptEarlyTitle: 'Accept Task early',
247
+ acceptEarlyBody: 'This accepts the Task and marks the following {count} unfinished Claims as done:',
248
+ acceptingTask: 'Accepting…',
215
249
  activityClosed: '{actor} closed this Task',
216
250
  activityReopened: '{actor} reopened this Task',
217
251
  mentionConfirmation: 'Mentioning an unfollowed member requires confirmation; send again to continue',
@@ -0,0 +1,33 @@
1
+ import type { ReactNode } from 'react';
2
+ /** One selectable entry; disabled rows render greyed with their reason. */
3
+ export interface MultiMenuOption {
4
+ readonly id: string;
5
+ readonly label: string;
6
+ readonly disabled?: boolean;
7
+ /** Leading marker such as a presence dot. */
8
+ readonly icon?: ReactNode;
9
+ /** Secondary reason text rendered after the label. */
10
+ readonly hint?: string;
11
+ }
12
+ /**
13
+ * Render the labeled multi-select Menu field.
14
+ * @param props.label - field caption; doubles as the trigger's accessible name.
15
+ * @param props.options - selectable entries in display order.
16
+ * @param props.selected - currently checked ids.
17
+ * @param props.onToggle - invoked with an id when its row is clicked.
18
+ * @param props.disabled - disables the trigger while a mutation is in flight.
19
+ * @param props.emptyText - shown instead of the picker when there is nothing to pick.
20
+ * @param props.triggerEmptyLabel - trigger caption when nothing is selected.
21
+ * @param props.formatCount - builds the trigger caption for N selections.
22
+ */
23
+ export declare function MultiMenuField<I extends string>({ label, options, selected, onToggle, disabled, emptyText, triggerEmptyLabel, formatCount }: {
24
+ readonly label: string;
25
+ readonly options: readonly MultiMenuOption[];
26
+ readonly selected: readonly I[];
27
+ readonly onToggle: (id: I) => void;
28
+ readonly disabled?: boolean;
29
+ readonly emptyText?: string;
30
+ readonly triggerEmptyLabel: string;
31
+ readonly formatCount: (count: number) => string;
32
+ }): import("react").JSX.Element;
33
+ //# sourceMappingURL=multi-menu-field.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"multi-menu-field.d.ts","sourceRoot":"","sources":["../../../src/client/multi-menu-field.tsx"],"names":[],"mappings":"AAMA,OAAO,KAAK,EAAE,SAAS,EAAE,MAAM,OAAO,CAAA;AAMtC,2EAA2E;AAC3E,MAAM,WAAW,eAAe;IAC9B,QAAQ,CAAC,EAAE,EAAE,MAAM,CAAA;IACnB,QAAQ,CAAC,KAAK,EAAE,MAAM,CAAA;IACtB,QAAQ,CAAC,QAAQ,CAAC,EAAE,OAAO,CAAA;IAC3B,6CAA6C;IAC7C,QAAQ,CAAC,IAAI,CAAC,EAAE,SAAS,CAAA;IACzB,sDAAsD;IACtD,QAAQ,CAAC,IAAI,CAAC,EAAE,MAAM,CAAA;CACvB;AAED;;;;;;;;;;GAUG;AACH,wBAAgB,cAAc,CAAC,CAAC,SAAS,MAAM,EAAE,EAAE,KAAK,EAAE,OAAO,EAAE,QAAQ,EAAE,QAAQ,EAAE,QAAgB,EAAE,SAAS,EAAE,iBAAiB,EAAE,WAAW,EAAE,EAAE;IACpJ,QAAQ,CAAC,KAAK,EAAE,MAAM,CAAA;IACtB,QAAQ,CAAC,OAAO,EAAE,SAAS,eAAe,EAAE,CAAA;IAC5C,QAAQ,CAAC,QAAQ,EAAE,SAAS,CAAC,EAAE,CAAA;IAC/B,QAAQ,CAAC,QAAQ,EAAE,CAAC,EAAE,EAAE,CAAC,KAAK,IAAI,CAAA;IAClC,QAAQ,CAAC,QAAQ,CAAC,EAAE,OAAO,CAAA;IAC3B,QAAQ,CAAC,SAAS,CAAC,EAAE,MAAM,CAAA;IAC3B,QAAQ,CAAC,iBAAiB,EAAE,MAAM,CAAA;IAClC,QAAQ,CAAC,WAAW,EAAE,CAAC,KAAK,EAAE,MAAM,KAAK,MAAM,CAAA;CAChD,+BAsCA"}
@@ -0,0 +1,30 @@
1
+ import { jsx as _jsx, Fragment as _Fragment, jsxs as _jsxs } from "react/jsx-runtime";
2
+ // MultiMenuField: one labeled multi-select picker shared by every creation
3
+ // form (Agent initial Channels, Channel initial Members). Selection toggles
4
+ // without closing the list so several entries can be picked in one pass; the
5
+ // capped card scrolls internally through long catalogs.
6
+ import { useState } from 'react';
7
+ import { IconChevronDownOutline14, Menu } from '@deepseek-ai/dsh-client-ui-primitives';
8
+ import createCss from './create.module.css';
9
+ import css from './sidebar.module.css';
10
+ /**
11
+ * Render the labeled multi-select Menu field.
12
+ * @param props.label - field caption; doubles as the trigger's accessible name.
13
+ * @param props.options - selectable entries in display order.
14
+ * @param props.selected - currently checked ids.
15
+ * @param props.onToggle - invoked with an id when its row is clicked.
16
+ * @param props.disabled - disables the trigger while a mutation is in flight.
17
+ * @param props.emptyText - shown instead of the picker when there is nothing to pick.
18
+ * @param props.triggerEmptyLabel - trigger caption when nothing is selected.
19
+ * @param props.formatCount - builds the trigger caption for N selections.
20
+ */
21
+ export function MultiMenuField({ label, options, selected, onToggle, disabled = false, emptyText, triggerEmptyLabel, formatCount }) {
22
+ const [open, setOpen] = useState(false);
23
+ const items = options.map(option => ({
24
+ id: option.id,
25
+ label: option.hint === undefined ? option.label : _jsxs(_Fragment, { children: [option.label, _jsx("small", { className: css.menuHint, children: ` ${option.hint}` })] }),
26
+ ...(option.disabled === true ? { disabled: true } : {}),
27
+ ...(option.icon === undefined ? {} : { icon: option.icon }),
28
+ }));
29
+ return (_jsxs("div", { className: createCss.field, children: [_jsx("span", { children: label }), options.length === 0 && emptyText !== undefined ? _jsx("small", { className: css.editHint, children: emptyText }) : (_jsx(Menu, { open: open, portal: true, className: createCss.menuCap, items: items, selectedIds: selected, onSelect: id => { onToggle(id); }, onClose: () => { setOpen(false); }, anchor: _jsxs("button", { type: "button", className: createCss.selectTrigger, "aria-label": label, "aria-haspopup": "listbox", "aria-expanded": open, disabled: disabled, onClick: () => { setOpen(value => !value); }, children: [_jsx("span", { className: createCss.selectValue, children: selected.length === 0 ? triggerEmptyLabel : formatCount(selected.length) }), _jsx("span", { className: `${createCss.chevron} ${open ? createCss.chevronOpen : ''}`, "aria-hidden": true, children: _jsx(IconChevronDownOutline14, {}) })] }) }))] }));
30
+ }
@@ -1,4 +1,4 @@
1
- import type { WorkspaceId } from '@deepseek-ai/dsh-client-runtime/client';
1
+ import type { SessionId, WorkspaceId } from '@deepseek-ai/dsh-client-runtime/client';
2
2
  import type { AgentTeamChannelRef, AgentTeamTaskRef, AgentTeamThreadRef } from '@wowyuarm/dsh-agent-team/types';
3
3
  export type TeamMode = 'conversation' | 'team';
4
4
  export interface TeamNavigationSnapshot {
@@ -8,6 +8,10 @@ export interface TeamNavigationSnapshot {
8
8
  taskRef?: AgentTeamTaskRef;
9
9
  threadRef?: AgentTeamThreadRef;
10
10
  taskNumber?: number;
11
+ /** Runtime-only Member Session embedded in the conversation seat; never persisted. */
12
+ memberSessionId?: SessionId;
13
+ /** Runtime-only session to restore when the Member view closes; never persisted. */
14
+ returnToSessionId?: SessionId;
11
15
  }
12
16
  declare const STORAGE_KEY = "dsh.agent-team.navigation";
13
17
  export interface TeamNavigationActions {
@@ -19,6 +23,14 @@ export interface TeamNavigationActions {
19
23
  backToWorkspace: () => void;
20
24
  /** Leave the selected Channel for the workspace Channel list; keeps mode and Workspace. */
21
25
  backToChannels: () => void;
26
+ /**
27
+ * Swap the conversation seat to a Member Session while Team chrome stays
28
+ * mounted. `returnToSessionId` is captured once — switching between Member
29
+ * Sessions keeps the original target.
30
+ */
31
+ enterMemberSession: (sessionId: SessionId, returnToSessionId?: SessionId) => void;
32
+ /** Close the embedded Member Session view and return to the Team views. */
33
+ exitMemberSession: () => void;
22
34
  }
23
35
  /** Root-scoped Team mode state. Slot lifetimes subscribe to this source. */
24
36
  export declare class TeamNavigation {
@@ -29,6 +41,9 @@ export declare class TeamNavigation {
29
41
  actions(): TeamNavigationActions;
30
42
  dispose(): void;
31
43
  private setMode;
44
+ private setMemberSession;
45
+ /** Any explicit Team navigation or the footer leave closes a Member view. */
46
+ private clearMemberSession;
32
47
  private setWorkspace;
33
48
  private setChannel;
34
49
  private clearChannel;
@@ -1 +1 @@
1
- {"version":3,"file":"navigation.d.ts","sourceRoot":"","sources":["../../../src/client/navigation.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,WAAW,EAAE,MAAM,wCAAwC,CAAA;AACzE,OAAO,KAAK,EAAE,mBAAmB,EAAE,gBAAgB,EAAE,kBAAkB,EAAE,MAAM,gCAAgC,CAAA;AAE/G,MAAM,MAAM,QAAQ,GAAG,cAAc,GAAG,MAAM,CAAA;AAE9C,MAAM,WAAW,sBAAsB;IACrC,IAAI,EAAE,QAAQ,CAAA;IACd,WAAW,CAAC,EAAE,WAAW,CAAA;IACzB,UAAU,CAAC,EAAE,mBAAmB,CAAA;IAChC,OAAO,CAAC,EAAE,gBAAgB,CAAA;IAC1B,SAAS,CAAC,EAAE,kBAAkB,CAAA;IAC9B,UAAU,CAAC,EAAE,MAAM,CAAA;CACpB;AAED,QAAA,MAAM,WAAW,8BAA8B,CAAA;AAyB/C,MAAM,WAAW,qBAAqB;IACpC,SAAS,EAAE,MAAM,IAAI,CAAA;IACrB,SAAS,EAAE,MAAM,IAAI,CAAA;IACrB,eAAe,EAAE,CAAC,WAAW,EAAE,WAAW,KAAK,IAAI,CAAA;IACnD,aAAa,EAAE,CAAC,UAAU,EAAE,mBAAmB,KAAK,IAAI,CAAA;IACxD,YAAY,EAAE,CAAC,OAAO,EAAE,gBAAgB,EAAE,SAAS,EAAE,kBAAkB,EAAE,UAAU,CAAC,EAAE,mBAAmB,EAAE,UAAU,CAAC,EAAE,MAAM,KAAK,IAAI,CAAA;IACvI,eAAe,EAAE,MAAM,IAAI,CAAA;IAC3B,2FAA2F;IAC3F,cAAc,EAAE,MAAM,IAAI,CAAA;CAC3B;AAED,4EAA4E;AAC5E,qBAAa,cAAc;IACzB,OAAO,CAAC,QAAQ,CAAiB;IACjC,OAAO,CAAC,QAAQ,CAAC,SAAS,CAAwB;IAElD,QAAQ,CAAC,WAAW,QAAO,sBAAsB,CAAiB;IAElE,QAAQ,CAAC,SAAS,GAAI,UAAU,MAAM,IAAI,KAAG,CAAC,MAAM,IAAI,CAAC,CAGxD;IAED,OAAO,IAAI,qBAAqB;IAYhC,OAAO,IAAI,IAAI;IAIf,OAAO,CAAC,OAAO;IAMf,OAAO,CAAC,YAAY;IAOpB,OAAO,CAAC,UAAU;IAOlB,OAAO,CAAC,YAAY;IAOpB,OAAO,CAAC,SAAS;IAYjB,OAAO,CAAC,MAAM;CAKf;AAED,OAAO,EAAE,WAAW,EAAE,CAAA"}
1
+ {"version":3,"file":"navigation.d.ts","sourceRoot":"","sources":["../../../src/client/navigation.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,SAAS,EAAE,WAAW,EAAE,MAAM,wCAAwC,CAAA;AACpF,OAAO,KAAK,EAAE,mBAAmB,EAAE,gBAAgB,EAAE,kBAAkB,EAAE,MAAM,gCAAgC,CAAA;AAE/G,MAAM,MAAM,QAAQ,GAAG,cAAc,GAAG,MAAM,CAAA;AAE9C,MAAM,WAAW,sBAAsB;IACrC,IAAI,EAAE,QAAQ,CAAA;IACd,WAAW,CAAC,EAAE,WAAW,CAAA;IACzB,UAAU,CAAC,EAAE,mBAAmB,CAAA;IAChC,OAAO,CAAC,EAAE,gBAAgB,CAAA;IAC1B,SAAS,CAAC,EAAE,kBAAkB,CAAA;IAC9B,UAAU,CAAC,EAAE,MAAM,CAAA;IACnB,sFAAsF;IACtF,eAAe,CAAC,EAAE,SAAS,CAAA;IAC3B,oFAAoF;IACpF,iBAAiB,CAAC,EAAE,SAAS,CAAA;CAC9B;AAED,QAAA,MAAM,WAAW,8BAA8B,CAAA;AAuC/C,MAAM,WAAW,qBAAqB;IACpC,SAAS,EAAE,MAAM,IAAI,CAAA;IACrB,SAAS,EAAE,MAAM,IAAI,CAAA;IACrB,eAAe,EAAE,CAAC,WAAW,EAAE,WAAW,KAAK,IAAI,CAAA;IACnD,aAAa,EAAE,CAAC,UAAU,EAAE,mBAAmB,KAAK,IAAI,CAAA;IACxD,YAAY,EAAE,CAAC,OAAO,EAAE,gBAAgB,EAAE,SAAS,EAAE,kBAAkB,EAAE,UAAU,CAAC,EAAE,mBAAmB,EAAE,UAAU,CAAC,EAAE,MAAM,KAAK,IAAI,CAAA;IACvI,eAAe,EAAE,MAAM,IAAI,CAAA;IAC3B,2FAA2F;IAC3F,cAAc,EAAE,MAAM,IAAI,CAAA;IAC1B;;;;OAIG;IACH,kBAAkB,EAAE,CAAC,SAAS,EAAE,SAAS,EAAE,iBAAiB,CAAC,EAAE,SAAS,KAAK,IAAI,CAAA;IACjF,2EAA2E;IAC3E,iBAAiB,EAAE,MAAM,IAAI,CAAA;CAC9B;AAED,4EAA4E;AAC5E,qBAAa,cAAc;IACzB,OAAO,CAAC,QAAQ,CAAiB;IACjC,OAAO,CAAC,QAAQ,CAAC,SAAS,CAAwB;IAElD,QAAQ,CAAC,WAAW,QAAO,sBAAsB,CAAiB;IAElE,QAAQ,CAAC,SAAS,GAAI,UAAU,MAAM,IAAI,KAAG,CAAC,MAAM,IAAI,CAAC,CAGxD;IAED,OAAO,IAAI,qBAAqB;IAchC,OAAO,IAAI,IAAI;IAIf,OAAO,CAAC,OAAO;IAMf,OAAO,CAAC,gBAAgB;IAaxB,6EAA6E;IAC7E,OAAO,CAAC,kBAAkB;IAO1B,OAAO,CAAC,YAAY;IAOpB,OAAO,CAAC,UAAU;IAOlB,OAAO,CAAC,YAAY;IAOpB,OAAO,CAAC,SAAS;IAYjB,OAAO,CAAC,MAAM;CAKf;AAED,OAAO,EAAE,WAAW,EAAE,CAAA"}
@@ -4,9 +4,16 @@ function readSnapshot() {
4
4
  return { mode: 'conversation' };
5
5
  try {
6
6
  const parsed = JSON.parse(localStorage.getItem(STORAGE_KEY) ?? '');
7
+ const hasThread = typeof parsed.taskRef === 'string' && typeof parsed.threadRef === 'string';
7
8
  return {
8
9
  mode: parsed.mode === 'team' ? 'team' : 'conversation',
9
10
  ...(typeof parsed.workspaceId === 'string' ? { workspaceId: parsed.workspaceId } : {}),
11
+ ...(typeof parsed.channelRef === 'string' ? { channelRef: parsed.channelRef } : {}),
12
+ ...(hasThread ? {
13
+ taskRef: parsed.taskRef,
14
+ threadRef: parsed.threadRef,
15
+ ...(typeof parsed.taskNumber === 'number' && Number.isInteger(parsed.taskNumber) && parsed.taskNumber > 0 ? { taskNumber: parsed.taskNumber } : {}),
16
+ } : {}),
10
17
  };
11
18
  }
12
19
  catch {
@@ -17,8 +24,15 @@ function persistSnapshot(snapshot) {
17
24
  if (typeof localStorage === 'undefined')
18
25
  return;
19
26
  try {
20
- const { mode, workspaceId } = snapshot;
21
- localStorage.setItem(STORAGE_KEY, JSON.stringify({ mode, ...(workspaceId === undefined ? {} : { workspaceId }) }));
27
+ const { mode, workspaceId, channelRef, taskRef, threadRef, taskNumber } = snapshot;
28
+ localStorage.setItem(STORAGE_KEY, JSON.stringify({
29
+ mode,
30
+ ...(workspaceId === undefined ? {} : { workspaceId }),
31
+ ...(channelRef === undefined ? {} : { channelRef }),
32
+ ...(taskRef === undefined ? {} : { taskRef }),
33
+ ...(threadRef === undefined ? {} : { threadRef }),
34
+ ...(taskNumber === undefined ? {} : { taskNumber }),
35
+ }));
22
36
  }
23
37
  catch {
24
38
  // Local persistence is a convenience; private mode and quota failures do not block navigation.
@@ -35,13 +49,15 @@ export class TeamNavigation {
35
49
  };
36
50
  actions() {
37
51
  return {
38
- enterTeam: () => { this.setMode('team'); },
52
+ enterTeam: () => { this.clearMemberSession(); this.setMode('team'); },
39
53
  leaveTeam: () => { this.setMode('conversation'); },
40
- selectWorkspace: workspaceId => { this.setWorkspace(workspaceId); },
41
- selectChannel: channelRef => { this.setChannel(channelRef); },
42
- selectThread: (taskRef, threadRef, channelRef, taskNumber) => { this.setThread(taskRef, threadRef, channelRef, taskNumber); },
43
- backToWorkspace: () => { this.setThread(undefined); },
44
- backToChannels: () => { this.clearChannel(); },
54
+ selectWorkspace: workspaceId => { this.clearMemberSession(); this.setWorkspace(workspaceId); },
55
+ selectChannel: channelRef => { this.clearMemberSession(); this.setChannel(channelRef); },
56
+ selectThread: (taskRef, threadRef, channelRef, taskNumber) => { this.clearMemberSession(); this.setThread(taskRef, threadRef, channelRef, taskNumber); },
57
+ backToWorkspace: () => { this.clearMemberSession(); this.setThread(undefined); },
58
+ backToChannels: () => { this.clearMemberSession(); this.clearChannel(); },
59
+ enterMemberSession: (sessionId, returnToSessionId) => { this.setMemberSession(sessionId, returnToSessionId); },
60
+ exitMemberSession: () => { this.clearMemberSession(); },
45
61
  };
46
62
  }
47
63
  dispose() {
@@ -53,6 +69,27 @@ export class TeamNavigation {
53
69
  this.snapshot = { ...this.snapshot, mode };
54
70
  this.commit();
55
71
  }
72
+ setMemberSession(sessionId, returnToSessionId) {
73
+ // Re-selecting the active Member keeps the original return target.
74
+ if (this.snapshot.memberSessionId === sessionId && this.snapshot.mode === 'team')
75
+ return;
76
+ const { memberSessionId: _memberSessionId, ...base } = this.snapshot;
77
+ this.snapshot = {
78
+ ...base,
79
+ mode: 'team',
80
+ memberSessionId: sessionId,
81
+ ...(returnToSessionId === undefined ? {} : { returnToSessionId }),
82
+ };
83
+ this.commit();
84
+ }
85
+ /** Any explicit Team navigation or the footer leave closes a Member view. */
86
+ clearMemberSession() {
87
+ if (this.snapshot.memberSessionId === undefined && this.snapshot.returnToSessionId === undefined)
88
+ return;
89
+ const { memberSessionId: _memberSessionId, returnToSessionId: _returnToSessionId, ...base } = this.snapshot;
90
+ this.snapshot = base;
91
+ this.commit();
92
+ }
56
93
  setWorkspace(workspaceId) {
57
94
  if (this.snapshot.workspaceId === workspaceId)
58
95
  return;
@@ -0,0 +1,35 @@
1
+ import type { ReactNode } from 'react';
2
+ /** Insert side of an in-flight drop relative to the hovered row. */
3
+ export type SidebarDropMarker = 'before' | 'after';
4
+ export interface SidebarRowDragHandlers<K extends string> {
5
+ /** Per-row native wiring plus the marker this row currently shows. */
6
+ readonly rowProps: (orderKey: K) => {
7
+ readonly draggable: true;
8
+ readonly className?: string | undefined;
9
+ readonly onDragStart: (event: React.DragEvent<HTMLElement>) => void;
10
+ readonly onDragEnd: () => void;
11
+ readonly onDragOver: (event: React.DragEvent<HTMLElement>) => void;
12
+ readonly onDrop: (event: React.DragEvent<HTMLElement>) => void;
13
+ };
14
+ }
15
+ /**
16
+ * One drag gesture per panel instance. `onCommit` receives the moved ref,
17
+ * the drop target and the insertion side exactly once per completed gesture;
18
+ * releases outside the list never reach it.
19
+ */
20
+ export declare function useSidebarRowDrag<K extends string>({ refs, onCommit }: {
21
+ /** Effective current order of the owning list, used for hit validation. */
22
+ readonly refs: readonly K[];
23
+ readonly onCommit: (movedRef: K, targetRef: K, marker: SidebarDropMarker) => void;
24
+ }): SidebarRowDragHandlers<K>;
25
+ /**
26
+ * Transparent wrapper that owns the drop-marker styling for one draggable
27
+ * sidebar row. It adds no layout of its own; the styled row stays inside so
28
+ * hover/focus descendant selectors keep working.
29
+ */
30
+ export declare function SortableRow<K extends string>({ drag, orderKey, children }: {
31
+ readonly drag: SidebarRowDragHandlers<K>;
32
+ readonly orderKey: K;
33
+ readonly children: ReactNode;
34
+ }): import("react").JSX.Element;
35
+ //# sourceMappingURL=sidebar-drag.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"sidebar-drag.d.ts","sourceRoot":"","sources":["../../../src/client/sidebar-drag.tsx"],"names":[],"mappings":"AASA,OAAO,KAAK,EAAE,SAAS,EAAE,MAAM,OAAO,CAAA;AAGtC,oEAAoE;AACpE,MAAM,MAAM,iBAAiB,GAAG,QAAQ,GAAG,OAAO,CAAA;AAElD,MAAM,WAAW,sBAAsB,CAAC,CAAC,SAAS,MAAM;IACtD,sEAAsE;IACtE,QAAQ,CAAC,QAAQ,EAAE,CAAC,QAAQ,EAAE,CAAC,KAAK;QAClC,QAAQ,CAAC,SAAS,EAAE,IAAI,CAAA;QACxB,QAAQ,CAAC,SAAS,CAAC,EAAE,MAAM,GAAG,SAAS,CAAA;QACvC,QAAQ,CAAC,WAAW,EAAE,CAAC,KAAK,EAAE,KAAK,CAAC,SAAS,CAAC,WAAW,CAAC,KAAK,IAAI,CAAA;QACnE,QAAQ,CAAC,SAAS,EAAE,MAAM,IAAI,CAAA;QAC9B,QAAQ,CAAC,UAAU,EAAE,CAAC,KAAK,EAAE,KAAK,CAAC,SAAS,CAAC,WAAW,CAAC,KAAK,IAAI,CAAA;QAClE,QAAQ,CAAC,MAAM,EAAE,CAAC,KAAK,EAAE,KAAK,CAAC,SAAS,CAAC,WAAW,CAAC,KAAK,IAAI,CAAA;KAC/D,CAAA;CACF;AAED;;;;GAIG;AACH,wBAAgB,iBAAiB,CAAC,CAAC,SAAS,MAAM,EAAE,EAAE,IAAI,EAAE,QAAQ,EAAE,EAAE;IACtE,2EAA2E;IAC3E,QAAQ,CAAC,IAAI,EAAE,SAAS,CAAC,EAAE,CAAA;IAC3B,QAAQ,CAAC,QAAQ,EAAE,CAAC,QAAQ,EAAE,CAAC,EAAE,SAAS,EAAE,CAAC,EAAE,MAAM,EAAE,iBAAiB,KAAK,IAAI,CAAA;CAClF,GAAG,sBAAsB,CAAC,CAAC,CAAC,CAwC5B;AAED;;;;GAIG;AACH,wBAAgB,WAAW,CAAC,CAAC,SAAS,MAAM,EAAE,EAAE,IAAI,EAAE,QAAQ,EAAE,QAAQ,EAAE,EAAE;IAC1E,QAAQ,CAAC,IAAI,EAAE,sBAAsB,CAAC,CAAC,CAAC,CAAA;IACxC,QAAQ,CAAC,QAAQ,EAAE,CAAC,CAAA;IACpB,QAAQ,CAAC,QAAQ,EAAE,SAAS,CAAA;CAC7B,+BAOA"}
@@ -0,0 +1,70 @@
1
+ import { jsx as _jsx } from "react/jsx-runtime";
2
+ /**
3
+ * Native whole-row drag for the Team sidebar lists. Unlike the Harness
4
+ * workspace list there is no document-level acceptance here: hovering another
5
+ * row shows the before/after insertion marker and releasing on a row commits
6
+ * exactly once per gesture, while a release outside any row — including over
7
+ * the other sidebar list, whose panel never responds — just cancels without
8
+ * committing.
9
+ */
10
+ import { useRef, useState } from 'react';
11
+ import css from './sidebar.module.css';
12
+ /**
13
+ * One drag gesture per panel instance. `onCommit` receives the moved ref,
14
+ * the drop target and the insertion side exactly once per completed gesture;
15
+ * releases outside the list never reach it.
16
+ */
17
+ export function useSidebarRowDrag({ refs, onCommit }) {
18
+ const [state, setState] = useState(null);
19
+ const committed = useRef(false);
20
+ const half = (event) => {
21
+ const rect = event.currentTarget.getBoundingClientRect();
22
+ return event.clientY - rect.top < rect.height / 2 ? 'before' : 'after';
23
+ };
24
+ return {
25
+ rowProps: orderKey => ({
26
+ draggable: true,
27
+ className: state === null ? undefined
28
+ : state.active === orderKey ? css.sidebarRowDragging
29
+ : state.over === orderKey ? (state.marker === 'before' ? css.sidebarRowDropBefore : css.sidebarRowDropAfter)
30
+ : undefined,
31
+ onDragStart: event => {
32
+ if (event.dataTransfer === null)
33
+ return;
34
+ event.dataTransfer.effectAllowed = 'move';
35
+ event.dataTransfer.setData('text/plain', orderKey);
36
+ committed.current = false;
37
+ setState({ active: orderKey, over: orderKey, marker: 'after' });
38
+ },
39
+ onDragEnd: () => { setState(null); },
40
+ onDragOver: event => {
41
+ if (state === null || !refs.includes(orderKey))
42
+ return;
43
+ event.preventDefault();
44
+ if (event.dataTransfer !== null)
45
+ event.dataTransfer.dropEffect = 'move';
46
+ const marker = half(event);
47
+ if (state.over !== orderKey || state.marker !== marker)
48
+ setState({ ...state, over: orderKey, marker });
49
+ },
50
+ onDrop: event => {
51
+ if (state === null || !refs.includes(orderKey))
52
+ return;
53
+ event.preventDefault();
54
+ if (committed.current)
55
+ return;
56
+ committed.current = true;
57
+ onCommit(state.active, orderKey, half(event));
58
+ },
59
+ }),
60
+ };
61
+ }
62
+ /**
63
+ * Transparent wrapper that owns the drop-marker styling for one draggable
64
+ * sidebar row. It adds no layout of its own; the styled row stays inside so
65
+ * hover/focus descendant selectors keep working.
66
+ */
67
+ export function SortableRow({ drag, orderKey, children }) {
68
+ const props = drag.rowProps(orderKey);
69
+ return (_jsx("div", { ...props, children: children }));
70
+ }
@@ -0,0 +1,27 @@
1
+ import type { WorkspaceId } from '@deepseek-ai/dsh-client-runtime/client';
2
+ /** Which sidebar list an order belongs to; the two ledgers are independent. */
3
+ export type TeamSidebarListKind = 'channels' | 'agents';
4
+ /** Insert side of a drop relative to the row under the pointer. */
5
+ export type SidebarDropMarker = 'before' | 'after';
6
+ /**
7
+ * Merge the saved personal order into the current Remote order: kept refs
8
+ * stay in the user's relative order with duplicates collapsed, removed refs
9
+ * disappear, and new refs join in their Remote default positions after the
10
+ * known ones.
11
+ */
12
+ export declare function reconcileSidebarOrder<R extends string>(saved: readonly string[], current: readonly R[]): readonly R[];
13
+ /**
14
+ * Effective row order for one sidebar list: the user's saved order folded
15
+ * into the given Remote default order. Identity-stable across renders while
16
+ * neither the data nor this browser's preference changes.
17
+ */
18
+ export declare function useSidebarOrder<R extends string>(workspaceId: WorkspaceId | undefined, kind: TeamSidebarListKind, refs: readonly R[]): readonly R[];
19
+ /**
20
+ * The only mutation path: move `movedRef` to the given side of `targetRef`
21
+ * inside the list's current effective order. Returns the new order, or
22
+ * `undefined` when the request cannot change anything (unknown refs, dropping
23
+ * a row onto itself, or an adjacent marker that would put it right back).
24
+ * Persistence and subscriber notification happen here.
25
+ */
26
+ export declare function moveSidebarItem<R extends string>(workspaceId: WorkspaceId | undefined, kind: TeamSidebarListKind, refs: readonly R[], movedRef: R, targetRef: R, marker: SidebarDropMarker): readonly R[] | undefined;
27
+ //# sourceMappingURL=sidebar-order.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"sidebar-order.d.ts","sourceRoot":"","sources":["../../../src/client/sidebar-order.ts"],"names":[],"mappings":"AAYA,OAAO,KAAK,EAAE,WAAW,EAAE,MAAM,wCAAwC,CAAA;AAEzE,+EAA+E;AAC/E,MAAM,MAAM,mBAAmB,GAAG,UAAU,GAAG,QAAQ,CAAA;AAEvD,mEAAmE;AACnE,MAAM,MAAM,iBAAiB,GAAG,QAAQ,GAAG,OAAO,CAAA;AA2ElD;;;;;GAKG;AACH,wBAAgB,qBAAqB,CAAC,CAAC,SAAS,MAAM,EAAE,KAAK,EAAE,SAAS,MAAM,EAAE,EAAE,OAAO,EAAE,SAAS,CAAC,EAAE,GAAG,SAAS,CAAC,EAAE,CAerH;AAqCD;;;;GAIG;AACH,wBAAgB,eAAe,CAAC,CAAC,SAAS,MAAM,EAAE,WAAW,EAAE,WAAW,GAAG,SAAS,EAAE,IAAI,EAAE,mBAAmB,EAAE,IAAI,EAAE,SAAS,CAAC,EAAE,GAAG,SAAS,CAAC,EAAE,CAQnJ;AAED;;;;;;GAMG;AACH,wBAAgB,eAAe,CAAC,CAAC,SAAS,MAAM,EAC9C,WAAW,EAAE,WAAW,GAAG,SAAS,EACpC,IAAI,EAAE,mBAAmB,EACzB,IAAI,EAAE,SAAS,CAAC,EAAE,EAClB,QAAQ,EAAE,CAAC,EACX,SAAS,EAAE,CAAC,EACZ,MAAM,EAAE,iBAAiB,GACxB,SAAS,CAAC,EAAE,GAAG,SAAS,CAe1B"}