@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.
- package/README.md +14 -2
- package/README.zh.md +10 -2
- package/package.json +2 -2
- package/packages/agent-team/lib/attachments.js +157 -0
- package/packages/agent-team/lib/index.js +274 -5
- package/packages/agent-team/lib/ledger.js +65 -13
- package/packages/agent-team/lib/recovery.js +87 -0
- package/packages/agent-team/lib/spec.js +17 -1
- package/packages/agent-team/lib/typert.host.js +321 -21
- package/packages/agent-team/lib/typert.remote-client.d.ts +9 -1
- package/packages/agent-team/lib/typert.remote-client.d.ts.map +1 -1
- package/packages/agent-team/lib/typert.remote-client.js +249 -17
- package/packages/agent-team/lib/types/attachments.d.ts +69 -0
- package/packages/agent-team/lib/types/attachments.d.ts.map +1 -0
- package/packages/agent-team/lib/types/index.d.ts +66 -1
- package/packages/agent-team/lib/types/index.d.ts.map +1 -1
- package/packages/agent-team/lib/types/ledger.d.ts +17 -1
- package/packages/agent-team/lib/types/ledger.d.ts.map +1 -1
- package/packages/agent-team/lib/types/recovery.d.ts +42 -0
- package/packages/agent-team/lib/types/recovery.d.ts.map +1 -0
- package/packages/agent-team/lib/types/spec.d.ts.map +1 -1
- package/packages/agent-team/lib/types/types.d.ts +85 -2
- package/packages/agent-team/lib/types/types.d.ts.map +1 -1
- package/packages/client-agent-team/README.md +2 -2
- package/packages/client-agent-team/README.zh.md +2 -2
- package/packages/client-agent-team/lib/client.js +1176 -281
- package/packages/client-agent-team/lib/client.js.map +1 -1
- package/packages/client-agent-team/lib/types/client/TeamAgentsPanel.d.ts +6 -2
- package/packages/client-agent-team/lib/types/client/TeamAgentsPanel.d.ts.map +1 -1
- package/packages/client-agent-team/lib/types/client/TeamAgentsPanel.js +104 -58
- package/packages/client-agent-team/lib/types/client/TeamChannelPage.d.ts +5 -1
- package/packages/client-agent-team/lib/types/client/TeamChannelPage.d.ts.map +1 -1
- package/packages/client-agent-team/lib/types/client/TeamChannelPage.js +44 -19
- package/packages/client-agent-team/lib/types/client/TeamChannelsPanel.d.ts.map +1 -1
- package/packages/client-agent-team/lib/types/client/TeamChannelsPanel.js +23 -18
- package/packages/client-agent-team/lib/types/client/TeamComposer.d.ts +4 -1
- package/packages/client-agent-team/lib/types/client/TeamComposer.d.ts.map +1 -1
- package/packages/client-agent-team/lib/types/client/TeamComposer.js +40 -4
- package/packages/client-agent-team/lib/types/client/TeamConversation.d.ts +1 -1
- package/packages/client-agent-team/lib/types/client/TeamConversation.d.ts.map +1 -1
- package/packages/client-agent-team/lib/types/client/TeamConversation.js +3 -3
- package/packages/client-agent-team/lib/types/client/TeamMessage.d.ts +7 -2
- package/packages/client-agent-team/lib/types/client/TeamMessage.d.ts.map +1 -1
- package/packages/client-agent-team/lib/types/client/TeamMessage.js +34 -8
- package/packages/client-agent-team/lib/types/client/TeamRunDivider.d.ts +10 -0
- package/packages/client-agent-team/lib/types/client/TeamRunDivider.d.ts.map +1 -0
- package/packages/client-agent-team/lib/types/client/TeamRunDivider.js +12 -0
- package/packages/client-agent-team/lib/types/client/TeamThreadPage.d.ts +3 -0
- package/packages/client-agent-team/lib/types/client/TeamThreadPage.d.ts.map +1 -1
- package/packages/client-agent-team/lib/types/client/TeamThreadPage.js +18 -11
- package/packages/client-agent-team/lib/types/client/TeamWorkspaceBrowser.d.ts +1 -1
- package/packages/client-agent-team/lib/types/client/TeamWorkspaceBrowser.d.ts.map +1 -1
- package/packages/client-agent-team/lib/types/client/TeamWorkspaceBrowser.js +6 -5
- package/packages/client-agent-team/lib/types/client/attachment-preview.d.ts +22 -0
- package/packages/client-agent-team/lib/types/client/attachment-preview.d.ts.map +1 -0
- package/packages/client-agent-team/lib/types/client/attachment-preview.js +35 -0
- package/packages/client-agent-team/lib/types/client/drafts.d.ts +41 -0
- package/packages/client-agent-team/lib/types/client/drafts.d.ts.map +1 -0
- package/packages/client-agent-team/lib/types/client/drafts.js +144 -0
- package/packages/client-agent-team/lib/types/client/index.d.ts +2 -0
- package/packages/client-agent-team/lib/types/client/index.d.ts.map +1 -1
- package/packages/client-agent-team/lib/types/client/index.js +50 -10
- package/packages/client-agent-team/lib/types/client/locales.d.ts +28 -0
- package/packages/client-agent-team/lib/types/client/locales.d.ts.map +1 -1
- package/packages/client-agent-team/lib/types/client/locales.js +28 -0
- package/packages/client-agent-team/lib/types/client/multi-menu-field.d.ts +33 -0
- package/packages/client-agent-team/lib/types/client/multi-menu-field.d.ts.map +1 -0
- package/packages/client-agent-team/lib/types/client/multi-menu-field.js +30 -0
- package/packages/client-agent-team/lib/types/client/navigation.d.ts +16 -1
- package/packages/client-agent-team/lib/types/client/navigation.d.ts.map +1 -1
- package/packages/client-agent-team/lib/types/client/navigation.js +45 -8
- package/packages/client-agent-team/lib/types/client/slots.d.ts +18 -2
- package/packages/client-agent-team/lib/types/client/slots.d.ts.map +1 -1
- package/packages/client-agent-team/lib/types/client/team-formatters.d.ts +2 -0
- package/packages/client-agent-team/lib/types/client/team-formatters.d.ts.map +1 -1
- package/packages/client-agent-team/lib/types/client/team-formatters.js +4 -0
- package/packages/client-agent-team/lib/types/client/team-separators.d.ts +8 -0
- package/packages/client-agent-team/lib/types/client/team-separators.d.ts.map +1 -1
- package/packages/client-agent-team/lib/types/client/team-separators.js +14 -0
- package/packages/tool-agent-team/lib/index.js +6 -2
- 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;
|
|
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
|
-
//
|
|
32
|
-
//
|
|
33
|
-
|
|
34
|
-
ctx.sessions
|
|
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
|
|
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: () => ({
|
|
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
|
|
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;
|
|
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({
|
|
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
|
-
/**
|
|
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>>;
|