@wowyuarm/dsh-agent-team 0.1.0 → 0.1.1

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 (81) hide show
  1. package/README.md +14 -2
  2. package/README.zh.md +10 -2
  3. package/package.json +2 -2
  4. package/packages/agent-team/lib/attachments.js +157 -0
  5. package/packages/agent-team/lib/index.js +274 -5
  6. package/packages/agent-team/lib/ledger.js +65 -13
  7. package/packages/agent-team/lib/recovery.js +87 -0
  8. package/packages/agent-team/lib/spec.js +17 -1
  9. package/packages/agent-team/lib/typert.host.js +321 -21
  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 +249 -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 +66 -1
  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 +85 -2
  23. package/packages/agent-team/lib/types/types.d.ts.map +1 -1
  24. package/packages/client-agent-team/README.md +2 -2
  25. package/packages/client-agent-team/README.zh.md +2 -2
  26. package/packages/client-agent-team/lib/client.js +1176 -281
  27. package/packages/client-agent-team/lib/client.js.map +1 -1
  28. package/packages/client-agent-team/lib/types/client/TeamAgentsPanel.d.ts +6 -2
  29. package/packages/client-agent-team/lib/types/client/TeamAgentsPanel.d.ts.map +1 -1
  30. package/packages/client-agent-team/lib/types/client/TeamAgentsPanel.js +104 -58
  31. package/packages/client-agent-team/lib/types/client/TeamChannelPage.d.ts +5 -1
  32. package/packages/client-agent-team/lib/types/client/TeamChannelPage.d.ts.map +1 -1
  33. package/packages/client-agent-team/lib/types/client/TeamChannelPage.js +44 -19
  34. package/packages/client-agent-team/lib/types/client/TeamChannelsPanel.d.ts.map +1 -1
  35. package/packages/client-agent-team/lib/types/client/TeamChannelsPanel.js +23 -18
  36. package/packages/client-agent-team/lib/types/client/TeamComposer.d.ts +4 -1
  37. package/packages/client-agent-team/lib/types/client/TeamComposer.d.ts.map +1 -1
  38. package/packages/client-agent-team/lib/types/client/TeamComposer.js +40 -4
  39. package/packages/client-agent-team/lib/types/client/TeamConversation.d.ts +1 -1
  40. package/packages/client-agent-team/lib/types/client/TeamConversation.d.ts.map +1 -1
  41. package/packages/client-agent-team/lib/types/client/TeamConversation.js +3 -3
  42. package/packages/client-agent-team/lib/types/client/TeamMessage.d.ts +7 -2
  43. package/packages/client-agent-team/lib/types/client/TeamMessage.d.ts.map +1 -1
  44. package/packages/client-agent-team/lib/types/client/TeamMessage.js +34 -8
  45. package/packages/client-agent-team/lib/types/client/TeamRunDivider.d.ts +10 -0
  46. package/packages/client-agent-team/lib/types/client/TeamRunDivider.d.ts.map +1 -0
  47. package/packages/client-agent-team/lib/types/client/TeamRunDivider.js +12 -0
  48. package/packages/client-agent-team/lib/types/client/TeamThreadPage.d.ts +3 -0
  49. package/packages/client-agent-team/lib/types/client/TeamThreadPage.d.ts.map +1 -1
  50. package/packages/client-agent-team/lib/types/client/TeamThreadPage.js +18 -11
  51. package/packages/client-agent-team/lib/types/client/TeamWorkspaceBrowser.d.ts +1 -1
  52. package/packages/client-agent-team/lib/types/client/TeamWorkspaceBrowser.d.ts.map +1 -1
  53. package/packages/client-agent-team/lib/types/client/TeamWorkspaceBrowser.js +6 -5
  54. package/packages/client-agent-team/lib/types/client/attachment-preview.d.ts +22 -0
  55. package/packages/client-agent-team/lib/types/client/attachment-preview.d.ts.map +1 -0
  56. package/packages/client-agent-team/lib/types/client/attachment-preview.js +35 -0
  57. package/packages/client-agent-team/lib/types/client/drafts.d.ts +41 -0
  58. package/packages/client-agent-team/lib/types/client/drafts.d.ts.map +1 -0
  59. package/packages/client-agent-team/lib/types/client/drafts.js +144 -0
  60. package/packages/client-agent-team/lib/types/client/index.d.ts +2 -0
  61. package/packages/client-agent-team/lib/types/client/index.d.ts.map +1 -1
  62. package/packages/client-agent-team/lib/types/client/index.js +50 -10
  63. package/packages/client-agent-team/lib/types/client/locales.d.ts +28 -0
  64. package/packages/client-agent-team/lib/types/client/locales.d.ts.map +1 -1
  65. package/packages/client-agent-team/lib/types/client/locales.js +28 -0
  66. package/packages/client-agent-team/lib/types/client/multi-menu-field.d.ts +33 -0
  67. package/packages/client-agent-team/lib/types/client/multi-menu-field.d.ts.map +1 -0
  68. package/packages/client-agent-team/lib/types/client/multi-menu-field.js +30 -0
  69. package/packages/client-agent-team/lib/types/client/navigation.d.ts +16 -1
  70. package/packages/client-agent-team/lib/types/client/navigation.d.ts.map +1 -1
  71. package/packages/client-agent-team/lib/types/client/navigation.js +45 -8
  72. package/packages/client-agent-team/lib/types/client/slots.d.ts +18 -2
  73. package/packages/client-agent-team/lib/types/client/slots.d.ts.map +1 -1
  74. package/packages/client-agent-team/lib/types/client/team-formatters.d.ts +2 -0
  75. package/packages/client-agent-team/lib/types/client/team-formatters.d.ts.map +1 -1
  76. package/packages/client-agent-team/lib/types/client/team-formatters.js +4 -0
  77. package/packages/client-agent-team/lib/types/client/team-separators.d.ts +8 -0
  78. package/packages/client-agent-team/lib/types/client/team-separators.d.ts.map +1 -1
  79. package/packages/client-agent-team/lib/types/client/team-separators.js +14 -0
  80. package/packages/tool-agent-team/lib/index.js +6 -2
  81. package/packages/tool-agent-team/lib/types/index.d.ts.map +1 -1
@@ -0,0 +1,144 @@
1
+ const STORAGE_KEY = 'dsh.agent-team.drafts.v1';
2
+ /** Retention ceiling; the oldest savedAt entries fall out first. */
3
+ const LIMIT = 50;
4
+ const EMPTY = Object.freeze({ draft: '', recipients: new Set() });
5
+ function isTeamDraftKey(value) {
6
+ return value.startsWith('channel:') || value.startsWith('thread:');
7
+ }
8
+ function readStored() {
9
+ if (typeof localStorage === 'undefined')
10
+ return new Map();
11
+ try {
12
+ const parsed = JSON.parse(localStorage.getItem(STORAGE_KEY) ?? '');
13
+ const restored = new Map();
14
+ for (const [key, value] of Object.entries(parsed)) {
15
+ if (!isTeamDraftKey(key) || typeof value !== 'object' || value === null)
16
+ continue;
17
+ const candidate = value;
18
+ if (typeof candidate.draft !== 'string' || !Array.isArray(candidate.recipientIds) || typeof candidate.savedAt !== 'number')
19
+ continue;
20
+ if (!candidate.recipientIds.every(id => typeof id === 'string'))
21
+ continue;
22
+ restored.set(key, { draft: candidate.draft, recipientIds: candidate.recipientIds, savedAt: candidate.savedAt });
23
+ }
24
+ return restored;
25
+ }
26
+ catch {
27
+ // Local persistence is a convenience; malformed content just starts empty.
28
+ return new Map();
29
+ }
30
+ }
31
+ function persistStored(entries) {
32
+ if (typeof localStorage === 'undefined')
33
+ return;
34
+ try {
35
+ const payload = Object.fromEntries([...entries].map(([key, entry]) => [key, entry]));
36
+ localStorage.setItem(STORAGE_KEY, JSON.stringify(payload));
37
+ }
38
+ catch {
39
+ // Private mode and quota failures do not block composing.
40
+ }
41
+ }
42
+ /**
43
+ * Root-scoped draft cache, one instance per Client context (created in
44
+ * `applyUi` and injected like the navigation service — never a module
45
+ * singleton). Slot lifetimes subscribe per key.
46
+ */
47
+ export class TeamDraftStore {
48
+ entries = readStored();
49
+ snapshots = new Map();
50
+ listeners = new Set();
51
+ subscribe = (listener) => {
52
+ this.listeners.add(listener);
53
+ return () => { this.listeners.delete(listener); };
54
+ };
55
+ /** Release listeners when the owning Client context disposes. */
56
+ dispose() {
57
+ this.listeners.clear();
58
+ }
59
+ /** Stable per-key snapshot identity for useSyncExternalStore. */
60
+ getSnapshot = (key) => {
61
+ const cached = this.snapshots.get(key);
62
+ if (cached !== undefined)
63
+ return cached;
64
+ const entry = this.entries.get(key);
65
+ if (entry === undefined)
66
+ return EMPTY;
67
+ const snapshot = Object.freeze({ draft: entry.draft, recipients: new Set(entry.recipientIds) });
68
+ this.snapshots.set(key, snapshot);
69
+ return snapshot;
70
+ };
71
+ writeDraft(key, draft) {
72
+ this.write(key, { draft });
73
+ }
74
+ writeRecipients(key, recipients) {
75
+ this.write(key, { recipientIds: [...recipients] });
76
+ }
77
+ /** Drop one key entirely — the success path after a committed send. */
78
+ clear(key) {
79
+ if (!this.entries.delete(key))
80
+ return;
81
+ this.snapshots.delete(key);
82
+ persistStored(this.entries);
83
+ for (const listener of this.listeners)
84
+ listener();
85
+ }
86
+ /** Test seam: forget everything, including what localStorage still holds. */
87
+ reset() {
88
+ this.entries = new Map();
89
+ this.snapshots.clear();
90
+ if (typeof localStorage !== 'undefined')
91
+ localStorage.removeItem(STORAGE_KEY);
92
+ for (const listener of this.listeners)
93
+ listener();
94
+ }
95
+ /** Re-read persisted content — what a fresh page load starts from. */
96
+ reload() {
97
+ this.entries = readStored();
98
+ this.snapshots.clear();
99
+ for (const listener of this.listeners)
100
+ listener();
101
+ }
102
+ write(key, patch) {
103
+ const previous = this.entries.get(key);
104
+ const next = {
105
+ draft: patch.draft ?? previous?.draft ?? '',
106
+ recipientIds: patch.recipientIds ?? previous?.recipientIds ?? [],
107
+ savedAt: Date.now(),
108
+ };
109
+ // Identical content must not bump savedAt or notify — convergence effects
110
+ // re-run freely and would otherwise loop through persistence.
111
+ if (previous !== undefined && previous.draft === next.draft
112
+ && previous.recipientIds.length === next.recipientIds.length
113
+ && previous.recipientIds.every((id, index) => id === next.recipientIds[index]))
114
+ return;
115
+ // An emptied composer leaves nothing worth restoring behind.
116
+ if (next.draft === '' && next.recipientIds.length === 0) {
117
+ this.clear(key);
118
+ return;
119
+ }
120
+ this.entries.set(key, next);
121
+ this.evict();
122
+ this.snapshots.delete(key);
123
+ persistStored(this.entries);
124
+ for (const listener of this.listeners)
125
+ listener();
126
+ }
127
+ evict() {
128
+ while (this.entries.size > LIMIT) {
129
+ let oldestKey;
130
+ let oldestSavedAt = Number.POSITIVE_INFINITY;
131
+ for (const [key, entry] of this.entries) {
132
+ if (entry.savedAt < oldestSavedAt) {
133
+ oldestSavedAt = entry.savedAt;
134
+ oldestKey = key;
135
+ }
136
+ }
137
+ if (oldestKey === undefined)
138
+ return;
139
+ this.entries.delete(oldestKey);
140
+ this.snapshots.delete(oldestKey);
141
+ }
142
+ }
143
+ }
144
+ export { STORAGE_KEY as TEAM_DRAFTS_STORAGE_KEY };
@@ -1,5 +1,6 @@
1
1
  import type { ClientContext } from '@deepseek-ai/dsh-client-runtime/client';
2
2
  import { TeamNavigation } from './navigation.ts';
3
+ import { TeamDraftStore } from './drafts.ts';
3
4
  import { type TeamKey } from './locales.ts';
4
5
  export type { TeamMode, TeamNavigationActions, TeamNavigationSnapshot } from './navigation.ts';
5
6
  export type { TeamKey } from './locales.ts';
@@ -13,6 +14,7 @@ declare module '@deepseek-ai/dsh-client-ui-slots' {
13
14
  declare module '@deepseek-ai/cordis' {
14
15
  interface Context {
15
16
  teamNavigation: TeamNavigation;
17
+ teamDrafts: TeamDraftStore;
16
18
  }
17
19
  }
18
20
  export declare function apply(ctx: ClientContext): Promise<void>;
@@ -1 +1 @@
1
- {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../../src/client/index.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,aAAa,EAAa,MAAM,wCAAwC,CAAA;AA0BtF,OAAO,EAAE,cAAc,EAAE,MAAM,iBAAiB,CAAA;AAMhD,OAAO,EAAU,KAAK,OAAO,EAAE,MAAM,cAAc,CAAA;AAEnD,YAAY,EAAE,QAAQ,EAAE,qBAAqB,EAAE,sBAAsB,EAAE,MAAM,iBAAiB,CAAA;AAC9F,YAAY,EAAE,OAAO,EAAE,MAAM,cAAc,CAAA;AAC3C,OAAO,EAAE,cAAc,EAAE,MAAM,iBAAiB,CAAA;AAIhD,eAAO,MAAM,MAAM,UAElB,CAAA;AAED,OAAO,QAAQ,kCAAkC,CAAC;IAChD,UAAU,kBAAkB;QAC1B,IAAI,EAAE,OAAO,CAAA;KACd;CACF;AAED,OAAO,QAAQ,qBAAqB,CAAC;IACnC,UAAU,OAAO;QACf,cAAc,EAAE,cAAc,CAAA;KAC/B;CACF;AA+GD,wBAAsB,KAAK,CAAC,GAAG,EAAE,aAAa,GAAG,OAAO,CAAC,IAAI,CAAC,CAI7D"}
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../../src/client/index.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,aAAa,EAAa,MAAM,wCAAwC,CAAA;AA8BtF,OAAO,EAAE,cAAc,EAAE,MAAM,iBAAiB,CAAA;AAEhD,OAAO,EAAE,cAAc,EAAE,MAAM,aAAa,CAAA;AAK5C,OAAO,EAAU,KAAK,OAAO,EAAE,MAAM,cAAc,CAAA;AAEnD,YAAY,EAAE,QAAQ,EAAE,qBAAqB,EAAE,sBAAsB,EAAE,MAAM,iBAAiB,CAAA;AAC9F,YAAY,EAAE,OAAO,EAAE,MAAM,cAAc,CAAA;AAC3C,OAAO,EAAE,cAAc,EAAE,MAAM,iBAAiB,CAAA;AAIhD,eAAO,MAAM,MAAM,UAElB,CAAA;AAED,OAAO,QAAQ,kCAAkC,CAAC;IAChD,UAAU,kBAAkB;QAC1B,IAAI,EAAE,OAAO,CAAA;KACd;CACF;AAED,OAAO,QAAQ,qBAAqB,CAAC;IACnC,UAAU,OAAO;QACf,cAAc,EAAE,cAAc,CAAA;QAC9B,UAAU,EAAE,cAAc,CAAA;KAC3B;CACF;AAoJD,wBAAsB,KAAK,CAAC,GAAG,EAAE,aAAa,GAAG,OAAO,CAAC,IAAI,CAAC,CAI7D"}
@@ -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,19 @@ 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),
27
+ restartMember: (request) => ctx.remote.agentTeam.restartMember(request),
24
28
  // The Host-scoped catalog needs no live Member, so suspended ones stay editable too.
25
29
  loadModels: async () => {
26
30
  const connection = ctx.get('connection');
@@ -28,16 +32,27 @@ function registerModeShadow(ctx, navigation, changes, name, component, extraInje
28
32
  return response.result;
29
33
  },
30
34
  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);
35
+ // Stay in Team mode: the conversation shadow stands down for Member
36
+ // Session views (see registerModeShadow), so the shipped conversation
37
+ // root renders the selected Member Session inside the Team shell.
38
+ const sessions = ctx.sessions;
39
+ const snapshot = navigation.getSnapshot();
40
+ const current = sessions.list.getSnapshot().current;
41
+ // The return target is captured on first entry only — switching between
42
+ // Member Sessions must keep pointing at the Human's original session.
43
+ const returnTo = snapshot.memberSessionId === undefined && current !== undefined && current !== sessionId ? current : undefined;
44
+ navigation.actions().enterMemberSession(sessionId, returnTo);
45
+ sessions.open(sessionId);
35
46
  },
36
47
  };
37
48
  ctx.slots.inject(name, () => {
38
49
  let dispose;
39
50
  const reconcile = () => {
40
- const active = navigation.getSnapshot().mode === 'team';
51
+ const snapshot = navigation.getSnapshot();
52
+ // The conversation seat yields to the shipped conversation root while a
53
+ // Member Session view is embedded; both sidebar seats stay shadowed so
54
+ // the Team chrome keeps working around the Member conversation.
55
+ const active = snapshot.mode === 'team' && !(name === 'conversation' && snapshot.memberSessionId !== undefined);
41
56
  if (active && dispose === undefined) {
42
57
  dispose = ctx.slots.register({
43
58
  name,
@@ -52,6 +67,8 @@ function registerModeShadow(ctx, navigation, changes, name, component, extraInje
52
67
  readThread: (request) => ctx.remote.agentTeam.readThread(request),
53
68
  loadThreadHistory: (request) => ctx.remote.agentTeam.threadHistory(request),
54
69
  sendMessage: (request) => ctx.remote.agentTeam.sendMessage(request),
70
+ putAttachment: (request) => ctx.remote.agentTeam.putAttachment(request),
71
+ getAttachment: (request) => ctx.remote.agentTeam.getAttachment(request),
55
72
  reply: (request) => ctx.remote.agentTeam.reply(request),
56
73
  changeTask: (request) => ctx.remote.agentTeam.changeTask(request),
57
74
  } : {}),
@@ -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,20 @@ export declare const zh: {
36
36
  readonly manageMembers: "管理成员";
37
37
  readonly editChannel: "编辑频道";
38
38
  readonly editAgent: "编辑 Agent";
39
+ readonly resumeAgent: "恢复";
40
+ readonly restartAgent: "重启会话";
41
+ readonly optionalSuffix: "(可选)";
42
+ readonly agentDescriptionPlaceholder: "留空则暂无描述";
43
+ readonly channelsPickerEmpty: "选择初始频道";
44
+ readonly channelsPickerCount: "已选 {count} 个频道";
45
+ readonly membersPickerEmpty: "选择初始成员";
46
+ readonly membersPickerCount: "已选 {count} 个成员";
47
+ readonly reasoningEffort: "推理强度";
48
+ readonly effortFollowDefault: "跟随模型默认";
49
+ readonly attachFiles: "添加附件";
50
+ readonly removeFile: "移除 {name}";
51
+ readonly attachmentExpired: "文件已过期清理";
52
+ readonly viewImage: "查看大图 {name}";
39
53
  readonly actionsChannel: "{name} 的操作";
40
54
  readonly actionsAgent: "{name} 的操作";
41
55
  readonly editSave: "保存";
@@ -147,6 +161,20 @@ export declare const en: {
147
161
  readonly manageMembers: "Manage members";
148
162
  readonly editChannel: "Edit Channel";
149
163
  readonly editAgent: "Edit Agent";
164
+ readonly resumeAgent: "Resume";
165
+ readonly restartAgent: "Restart session";
166
+ readonly optionalSuffix: " (optional)";
167
+ readonly agentDescriptionPlaceholder: "Leave empty if not needed";
168
+ readonly channelsPickerEmpty: "Choose initial channels";
169
+ readonly channelsPickerCount: "{count} selected";
170
+ readonly membersPickerEmpty: "Choose initial members";
171
+ readonly membersPickerCount: "{count} selected";
172
+ readonly reasoningEffort: "Reasoning effort";
173
+ readonly effortFollowDefault: "Follow model default";
174
+ readonly attachFiles: "Add attachments";
175
+ readonly removeFile: "Remove {name}";
176
+ readonly attachmentExpired: "File expired and cleaned up";
177
+ readonly viewImage: "View image {name}";
150
178
  readonly actionsChannel: "{name} actions";
151
179
  readonly actionsAgent: "{name} actions";
152
180
  readonly editSave: "Save";
@@ -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;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;CA4HL,CAAA;AAEV,eAAO,MAAM,EAAE;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;CA4HL,CAAA;AAEV,MAAM,MAAM,OAAO,GAAG,MAAM,OAAO,EAAE,CAAA"}
@@ -36,6 +36,20 @@ export const zh = {
36
36
  manageMembers: '管理成员',
37
37
  editChannel: '编辑频道',
38
38
  editAgent: '编辑 Agent',
39
+ resumeAgent: '恢复',
40
+ restartAgent: '重启会话',
41
+ optionalSuffix: '(可选)',
42
+ agentDescriptionPlaceholder: '留空则暂无描述',
43
+ channelsPickerEmpty: '选择初始频道',
44
+ channelsPickerCount: '已选 {count} 个频道',
45
+ membersPickerEmpty: '选择初始成员',
46
+ membersPickerCount: '已选 {count} 个成员',
47
+ reasoningEffort: '推理强度',
48
+ effortFollowDefault: '跟随模型默认',
49
+ attachFiles: '添加附件',
50
+ removeFile: '移除 {name}',
51
+ attachmentExpired: '文件已过期清理',
52
+ viewImage: '查看大图 {name}',
39
53
  actionsChannel: '{name} 的操作',
40
54
  actionsAgent: '{name} 的操作',
41
55
  editSave: '保存',
@@ -147,6 +161,20 @@ export const en = {
147
161
  manageMembers: 'Manage members',
148
162
  editChannel: 'Edit Channel',
149
163
  editAgent: 'Edit Agent',
164
+ resumeAgent: 'Resume',
165
+ restartAgent: 'Restart session',
166
+ optionalSuffix: ' (optional)',
167
+ agentDescriptionPlaceholder: 'Leave empty if not needed',
168
+ channelsPickerEmpty: 'Choose initial channels',
169
+ channelsPickerCount: '{count} selected',
170
+ membersPickerEmpty: 'Choose initial members',
171
+ membersPickerCount: '{count} selected',
172
+ reasoningEffort: 'Reasoning effort',
173
+ effortFollowDefault: 'Follow model default',
174
+ attachFiles: 'Add attachments',
175
+ removeFile: 'Remove {name}',
176
+ attachmentExpired: 'File expired and cleaned up',
177
+ viewImage: 'View image {name}',
150
178
  actionsChannel: '{name} actions',
151
179
  actionsAgent: '{name} actions',
152
180
  editSave: 'Save',
@@ -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;
@@ -1,18 +1,28 @@
1
1
  import type { PropsLocale, PropsRuntime } from '@deepseek-ai/dsh-client-ui-slots';
2
- import type { AgentTeamAddMemberRequest, AgentTeamChannelRef, AgentTeamClientMemberStatus, AgentTeamCreateChannelRequest, AgentTeamCreateChannelResult, AgentTeamJoinChannelRequest, AgentTeamJoinChannelResult, AgentTeamMemberResult, AgentTeamMembersRequest, AgentTeamRemoveChannelMemberRequest, AgentTeamRemoveChannelMemberResult, AgentTeamReplyRequest, AgentTeamReplyResult, AgentTeamConfirmationRequired, AgentTeamThreadHistory, AgentTeamThreadHistoryRequest, AgentTeamThreadReadRequest, AgentTeamThreadReadResult, AgentTeamTaskRequest, AgentTeamTaskResult, AgentTeamSendMessageRequest, AgentTeamSendMessageResult, AgentTeamUpdateChannelRequest, AgentTeamUpdateChannelResult, AgentTeamUpdateMemberRequest, AgentTeamView, AgentTeamViewRequest } from '@wowyuarm/dsh-agent-team/types';
2
+ import type { AgentTeamAddMemberRequest, AgentTeamChannelRef, AgentTeamClientMemberStatus, AgentTeamCreateChannelRequest, AgentTeamCreateChannelResult, AgentTeamJoinChannelRequest, AgentTeamJoinChannelResult, AgentTeamGetAttachmentRequest, AgentTeamGetAttachmentResult, AgentTeamPutAttachmentRequest, AgentTeamPutAttachmentResult, AgentTeamMemberResult, AgentTeamRecoverMemberRequest, AgentTeamRecoverMemberResult, AgentTeamRestartMemberRequest, AgentTeamRestartMemberResult, AgentTeamMembersRequest, AgentTeamRemoveChannelMemberRequest, AgentTeamRemoveChannelMemberResult, AgentTeamReplyRequest, AgentTeamReplyResult, AgentTeamConfirmationRequired, AgentTeamThreadHistory, AgentTeamThreadHistoryRequest, AgentTeamThreadReadRequest, AgentTeamThreadReadResult, AgentTeamTaskRequest, AgentTeamTaskResult, AgentTeamSendMessageRequest, AgentTeamSendMessageResult, AgentTeamUpdateChannelRequest, AgentTeamUpdateChannelResult, AgentTeamUpdateMemberRequest, AgentTeamView, AgentTeamViewRequest } from '@wowyuarm/dsh-agent-team/types';
3
3
  import type { RemoteResult } from '@deepseek-ai/dsh-typert-protocol';
4
4
  import type { TeamNavigationActions, TeamNavigationSnapshot } from './navigation.ts';
5
5
  import type { TeamChangeListener, TeamChangeScope } from './team-changes.ts';
6
+ import type { TeamDraftStore } from './drafts.ts';
6
7
  export interface TeamNavigationSource {
7
8
  getSnapshot: () => TeamNavigationSnapshot;
8
9
  subscribe: (listener: () => void) => () => void;
9
10
  }
10
11
  /** Subscribe to one projection scope's invalidation stream; disposal aborts the shared poll. */
11
12
  export type SubscribeTeamChanges = (scope: TeamChangeScope, listener: TeamChangeListener) => () => void;
13
+ /** One adapter-owned selectable reasoning effort of one model route. */
14
+ export interface TeamModelEffortOption {
15
+ readonly id: string;
16
+ readonly name: string;
17
+ }
12
18
  /** One selectable model row inside one provider group of the Host catalog. */
13
19
  export interface TeamModelOption {
14
20
  readonly id: string;
15
21
  readonly name: string;
22
+ /** Selectable reasoning levels when the adapter exposes them. */
23
+ readonly reasoning?: {
24
+ readonly efforts: readonly TeamModelEffortOption[];
25
+ };
16
26
  }
17
27
  /** Provider-grouped slice of the Host model catalog the editors render. */
18
28
  export interface TeamModelProviderGroup {
@@ -38,20 +48,26 @@ export type TeamSidebarProps = PropsRuntime<'sidebar.workspaces'> & PropsLocale<
38
48
  createChannel: (request: AgentTeamCreateChannelRequest) => Promise<RemoteResult<AgentTeamCreateChannelResult>>;
39
49
  updateChannel: (request: AgentTeamUpdateChannelRequest) => Promise<RemoteResult<AgentTeamUpdateChannelResult>>;
40
50
  updateMember: (request: AgentTeamUpdateMemberRequest) => Promise<RemoteResult<AgentTeamMemberResult>>;
51
+ recoverMember: (request: AgentTeamRecoverMemberRequest) => Promise<RemoteResult<AgentTeamRecoverMemberResult>>;
52
+ restartMember: (request: AgentTeamRestartMemberRequest) => Promise<RemoteResult<AgentTeamRestartMemberResult>>;
41
53
  joinChannel: (request: AgentTeamJoinChannelRequest) => Promise<RemoteResult<AgentTeamJoinChannelResult>>;
42
54
  removeChannelMember: (request: AgentTeamRemoveChannelMemberRequest) => Promise<RemoteResult<AgentTeamRemoveChannelMemberResult>>;
43
55
  /** Session-independent Host model catalog (`llm.models`); needs no live Member. */
44
56
  loadModels: () => Promise<RemoteResult<TeamModelCatalog>>;
45
- /** Focus the Member's own Session conversation page in the ordinary shell. */
57
+ /** Embed the Member's Session conversation in the Team conversation seat. */
46
58
  openMemberSession: (sessionId: AgentTeamClientMemberStatus['member']['sessionId']) => void;
47
59
  selectedChannelRef?: AgentTeamChannelRef;
48
60
  };
49
61
  export type TeamConversationProps = PropsRuntime<'conversation'> & PropsLocale<'team'> & TeamNavigationActions & {
50
62
  navigation: TeamNavigationSource;
63
+ /** Keyed composer draft cache; one store per Client context. */
64
+ drafts: TeamDraftStore;
51
65
  loadChannels: (request: AgentTeamViewRequest) => Promise<RemoteResult<AgentTeamView>>;
52
66
  readThread: (request: AgentTeamThreadReadRequest) => Promise<RemoteResult<AgentTeamThreadReadResult>>;
53
67
  loadThreadHistory: (request: AgentTeamThreadHistoryRequest) => Promise<RemoteResult<AgentTeamThreadHistory>>;
54
68
  subscribeChanges: SubscribeTeamChanges;
69
+ putAttachment: (request: AgentTeamPutAttachmentRequest) => Promise<RemoteResult<AgentTeamPutAttachmentResult>>;
70
+ getAttachment: (request: AgentTeamGetAttachmentRequest) => Promise<RemoteResult<AgentTeamGetAttachmentResult>>;
55
71
  sendMessage: (request: AgentTeamSendMessageRequest) => Promise<RemoteResult<AgentTeamSendMessageResult>>;
56
72
  joinChannel: (request: AgentTeamJoinChannelRequest) => Promise<RemoteResult<AgentTeamJoinChannelResult>>;
57
73
  removeChannelMember: (request: AgentTeamRemoveChannelMemberRequest) => Promise<RemoteResult<AgentTeamRemoveChannelMemberResult>>;