@khorsheed/dsh-room 0.2.2 → 0.2.4
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +58 -0
- package/README.en.md +29 -62
- package/README.i18n.yaml +2 -2
- package/README.md +28 -61
- package/lib/client.js +14620 -5705
- package/lib/index.js +4069 -730
- package/lib/journal-B4FOGO6S.js +482 -0
- package/lib/journal-Rid9AAnD.js +485 -0
- package/lib/tool.js +112 -166
- package/lib/typert.host.js +2031 -374
- package/lib/typert.remote-client.d.ts +34 -10
- package/lib/typert.remote-client.js +1599 -210
- package/lib/types/agent-setup.d.ts +1 -1
- package/lib/types/client/InviteAgentAction.js +4 -16
- package/lib/types/client/InviteDialog.d.ts +28 -6
- package/lib/types/client/InviteDialog.js +76 -12
- package/lib/types/client/MemberCard.d.ts +5 -1
- package/lib/types/client/MemberCard.js +4 -4
- package/lib/types/client/MembersView.d.ts +1 -1
- package/lib/types/client/MembersView.js +58 -22
- package/lib/types/client/RemoveMemberConfirm.d.ts +40 -0
- package/lib/types/client/RemoveMemberConfirm.js +85 -0
- package/lib/types/client/RoomApprovalView.d.ts +11 -0
- package/lib/types/client/RoomApprovalView.js +28 -0
- package/lib/types/client/RoomComposer.d.ts +1 -1
- package/lib/types/client/RoomComposer.js +195 -29
- package/lib/types/client/RoomDecisionView.d.ts +22 -0
- package/lib/types/client/RoomDecisionView.js +35 -0
- package/lib/types/client/RoomHelpView.d.ts +17 -0
- package/lib/types/client/RoomHelpView.js +56 -0
- package/lib/types/client/RoomInboxView.d.ts +10 -0
- package/lib/types/client/RoomInboxView.js +14 -0
- package/lib/types/client/RoomQuestionView.d.ts +13 -0
- package/lib/types/client/RoomQuestionView.js +61 -0
- package/lib/types/client/RoomRelayView.js +1 -1
- package/lib/types/client/RoomRunView.d.ts +3 -1
- package/lib/types/client/RoomRunView.js +12 -5
- package/lib/types/client/RoomSettingsView.d.ts +19 -0
- package/lib/types/client/RoomSettingsView.js +30 -0
- package/lib/types/client/RoomSpeechView.d.ts +22 -8
- package/lib/types/client/RoomSpeechView.js +94 -11
- package/lib/types/client/RoomStatsLine.d.ts +26 -5
- package/lib/types/client/RoomStatsLine.js +53 -7
- package/lib/types/client/RoomTaskCapsule.d.ts +19 -0
- package/lib/types/client/RoomTaskCapsule.js +37 -0
- package/lib/types/client/TasksTab.d.ts +41 -0
- package/lib/types/client/TasksTab.js +43 -0
- package/lib/types/client/TasksView.d.ts +44 -0
- package/lib/types/client/TasksView.js +149 -0
- package/lib/types/client/TeamView.d.ts +33 -0
- package/lib/types/client/TeamView.js +210 -0
- package/lib/types/client/at-menu.d.ts +127 -0
- package/lib/types/client/at-menu.js +173 -0
- package/lib/types/client/draft-chips.d.ts +67 -0
- package/lib/types/client/draft-chips.js +95 -0
- package/lib/types/client/executions.d.ts +0 -30
- package/lib/types/client/executions.js +4 -34
- package/lib/types/client/icons-local.d.ts +25 -0
- package/lib/types/client/icons-local.js +10 -0
- package/lib/types/client/icons.d.ts +2 -1
- package/lib/types/client/icons.js +1 -0
- package/lib/types/client/index.js +276 -72
- package/lib/types/client/locales.d.ts +482 -52
- package/lib/types/client/locales.js +490 -60
- package/lib/types/client/nodes.d.ts +112 -17
- package/lib/types/client/nodes.js +197 -26
- package/lib/types/client/slots.d.ts +117 -47
- package/lib/types/client/task-tag.d.ts +32 -0
- package/lib/types/client/task-tag.js +33 -0
- package/lib/types/client/tasks.d.ts +50 -0
- package/lib/types/client/tasks.js +82 -0
- package/lib/types/client/team.d.ts +73 -0
- package/lib/types/client/team.js +187 -0
- package/lib/types/decisions.d.ts +49 -0
- package/lib/types/decisions.js +101 -0
- package/lib/types/deliveries.d.ts +17 -1
- package/lib/types/deliveries.js +82 -6
- package/lib/types/dispatch.d.ts +102 -4
- package/lib/types/dispatch.js +498 -109
- package/lib/types/help.d.ts +137 -0
- package/lib/types/help.js +216 -0
- package/lib/types/helps.d.ts +26 -0
- package/lib/types/helps.js +47 -0
- package/lib/types/index.d.ts +292 -52
- package/lib/types/index.js +1421 -199
- package/lib/types/jobs.d.ts +22 -0
- package/lib/types/jobs.js +77 -0
- package/lib/types/journal.d.ts +17 -27
- package/lib/types/journal.js +120 -43
- package/lib/types/messaging.d.ts +65 -0
- package/lib/types/messaging.js +160 -0
- package/lib/types/plan-service.js +7 -1
- package/lib/types/report-gate.d.ts +81 -0
- package/lib/types/report-gate.js +89 -0
- package/lib/types/tool-definition.d.ts +43 -20
- package/lib/types/tool-definition.js +145 -212
- package/lib/types/tool.d.ts +2 -2
- package/lib/types/tool.js +3 -2
- package/lib/types/types.d.ts +755 -78
- package/locale/zh.json +1 -1
- package/package.json +3 -3
- package/lib/types/client/RoomActivityDock.d.ts +0 -19
- package/lib/types/client/RoomActivityDock.js +0 -105
- package/lib/types/client/RoomDockCapsules.d.ts +0 -64
- package/lib/types/client/RoomDockCapsules.js +0 -277
- package/lib/types/client/RoomPlanTab.d.ts +0 -15
- package/lib/types/client/RoomPlanTab.js +0 -13
- package/lib/types/client/RoomTaskLineView.d.ts +0 -13
- package/lib/types/client/RoomTaskLineView.js +0 -21
|
@@ -88,7 +88,7 @@ export declare function preloadPresetDerivationHost(): Promise<void>;
|
|
|
88
88
|
/** Test seam: drop the cached probe and host so a mocked resolution re-runs. */
|
|
89
89
|
export declare function resetPresetRegistryProbeForTest(): void;
|
|
90
90
|
/** The agentPresets face this package consumes (probed, never injected). */
|
|
91
|
-
export type AgentPresetsProbe = Pick<AgentPresets, 'resolve' | 'mount'
|
|
91
|
+
export type AgentPresetsProbe = Pick<AgentPresets, 'resolve' | 'mount'> & Partial<Pick<AgentPresets, 'list'>>;
|
|
92
92
|
/**
|
|
93
93
|
* Probe the agent-presets roster.
|
|
94
94
|
* @param ctx - host context.
|
|
@@ -8,7 +8,7 @@ import { Fragment as _Fragment, jsx as _jsx, jsxs as _jsxs } from "react/jsx-run
|
|
|
8
8
|
* @module @khorsheed/dsh-room/client/InviteAgentAction
|
|
9
9
|
*/
|
|
10
10
|
import { useEffect, useReducer, useState } from 'react';
|
|
11
|
-
import { InviteDialog } from './InviteDialog.js';
|
|
11
|
+
import { InviteDialog, inviteRequest } from './InviteDialog.js';
|
|
12
12
|
import css from './InviteAgentAction.module.css';
|
|
13
13
|
/** The header chip plus its dialog. */
|
|
14
14
|
export function InviteAgentAction({ roomCwd, invite, listProviders, listNames, browseDirectory, modelSurface, renderHarnessModelPicker, roomChrome, sessionId, t, }) {
|
|
@@ -21,25 +21,13 @@ export function InviteAgentAction({ roomCwd, invite, listProviders, listNames, b
|
|
|
21
21
|
const openDialog = () => {
|
|
22
22
|
setProviders(undefined);
|
|
23
23
|
setOpen(true);
|
|
24
|
-
void listProviders().then(setProviders);
|
|
25
|
-
};
|
|
26
|
-
const submit = async (values) => {
|
|
27
|
-
// Blank optional fields are omitted, not sent (the host rejects a
|
|
28
|
-
// present-but-blank role; an absent one simply carries no preset).
|
|
29
|
-
const outcome = await invite({
|
|
30
|
-
provider: values.provider,
|
|
31
|
-
name: values.name,
|
|
32
|
-
...values.instructions === '' ? {} : { instructions: values.instructions },
|
|
33
|
-
...values.cwd === '' ? {} : { cwd: values.cwd },
|
|
34
|
-
...values.firstTask === '' ? {} : { firstTask: values.firstTask },
|
|
35
|
-
...values.model === '' ? {} : { model: values.model },
|
|
36
|
-
});
|
|
37
|
-
return outcome;
|
|
24
|
+
void listProviders(sessionId).then(setProviders);
|
|
38
25
|
};
|
|
26
|
+
const submit = (values) => invite(inviteRequest(values));
|
|
39
27
|
// M3' self-hide: the chip is the room's dev affordance — it renders only
|
|
40
28
|
// when this session's preset composition grants the room tools (or the
|
|
41
29
|
// session IS a room, or the criterion has no answer — all fail-open).
|
|
42
30
|
if (!roomChrome.show(sessionId))
|
|
43
31
|
return null;
|
|
44
|
-
return (_jsxs(_Fragment, { children: [_jsx("button", { type: "button", className: css.trigger, "aria-label": t('action.inviteAgent'), onClick: openDialog, children: t('action.inviteAgent') }), open && (_jsx(InviteDialog, { mode: "invite", providers: providers?.providers, localAgentAvailable: providers?.localAgentAvailable ?? true, inheritedCwd: roomCwd, existingNames: listNames(), browseDirectory: browseDirectory, modelSurface: modelSurface, renderHarnessModelPicker: renderHarnessModelPicker, onSubmit: submit, onClose: () => { setOpen(false); }, t: t }))] }));
|
|
32
|
+
return (_jsxs(_Fragment, { children: [_jsx("button", { type: "button", className: css.trigger, "aria-label": t('action.inviteAgent'), onClick: openDialog, children: t('action.inviteAgent') }), open && (_jsx(InviteDialog, { mode: "invite", providers: providers?.providers, presets: providers?.presets, sessionPreset: providers?.sessionPreset, localAgentAvailable: providers?.localAgentAvailable ?? true, inheritedCwd: roomCwd, existingNames: listNames(), browseDirectory: browseDirectory, modelSurface: modelSurface, renderHarnessModelPicker: renderHarnessModelPicker, onSubmit: submit, onClose: () => { setOpen(false); }, t: t }))] }));
|
|
45
33
|
}
|
|
@@ -14,8 +14,10 @@ import type { LocalAgentUi } from '@khorsheed/dsh-local-agent/client';
|
|
|
14
14
|
* ▸ 高级设置 (role instructions + the member-level cwd, collapsed on invite —
|
|
15
15
|
* editing opens it, since editing IS changing those). The model field sits in
|
|
16
16
|
* the main form (promoted out of the drawer): the same control the provider
|
|
17
|
-
* settings cards use
|
|
18
|
-
*
|
|
17
|
+
* settings cards use, widened with the model's reasoning-effort row (a
|
|
18
|
+
* brokerless composition keeps the control model-only). Invites retain a
|
|
19
|
+
* saved provider choice, otherwise the shared picker chooses the first
|
|
20
|
+
* candidate. Edits retain member configuration.
|
|
19
21
|
* Older cores without the shared picker keep the legacy default/input fallback.
|
|
20
22
|
* The rest of the original
|
|
21
23
|
* contract is unchanged: logged-out providers grey with login guidance (an
|
|
@@ -28,25 +30,41 @@ import type { LocalAgentUi } from '@khorsheed/dsh-local-agent/client';
|
|
|
28
30
|
* edit mode diffs name/cwd/instructions/model.
|
|
29
31
|
*/
|
|
30
32
|
import { type ReactNode } from 'react';
|
|
31
|
-
import type { MembersViewProps, RoomInviteOutcome, RoomMutationOutcome } from './slots.ts';
|
|
32
|
-
import type { RoomMember, RoomProviderInfo } from '../types.ts';
|
|
33
|
+
import type { MembersViewProps, RoomInviteOutcome, RoomInviteValues, RoomMutationOutcome } from './slots.ts';
|
|
34
|
+
import type { RoomMember, RoomPresetInfo, RoomProviderInfo } from '../types.ts';
|
|
33
35
|
import type { LocalAgentModelInfo } from '@khorsheed/dsh-local-agent/types';
|
|
34
|
-
/** The dialog's submit values (edit mode carries name/cwd/instructions/model). */
|
|
36
|
+
/** The dialog's submit values (edit mode carries name/cwd/instructions/model/effort). */
|
|
35
37
|
export interface InviteDialogSubmit {
|
|
36
38
|
readonly provider: string;
|
|
37
39
|
readonly name: string;
|
|
40
|
+
/** `native`: a DSH child agent composing `preset`; otherwise a CLI member of `provider`. */
|
|
41
|
+
readonly kind: 'cli' | 'native';
|
|
42
|
+
/** Native only: the chosen host preset ('' = the host default). */
|
|
43
|
+
readonly preset: string;
|
|
44
|
+
/** Short public role ('' = none). */
|
|
45
|
+
readonly role: string;
|
|
38
46
|
readonly instructions: string;
|
|
39
47
|
readonly cwd: string;
|
|
40
48
|
readonly firstTask: string;
|
|
41
49
|
/** The delegation's invite-time model ('' = follow the harness default). */
|
|
42
50
|
readonly model: string;
|
|
51
|
+
/** The delegation's invite-time reasoning effort ('' = follow the harness default). */
|
|
52
|
+
readonly effort: string;
|
|
43
53
|
/**
|
|
44
54
|
* The model value the edit field opened with (the member's effective model
|
|
45
55
|
* when the broker answered, else the journaled intent): the edit-mode diff
|
|
46
56
|
* baseline — a save only touches the broker/journal when the value moved.
|
|
47
57
|
*/
|
|
48
58
|
readonly modelBaseline: string;
|
|
59
|
+
/** The effort value the edit field opened with (the journaled intent), the edit-mode diff baseline. */
|
|
60
|
+
readonly effortBaseline: string;
|
|
49
61
|
}
|
|
62
|
+
/**
|
|
63
|
+
* The dialog's submission as the host's invite request: blank optional
|
|
64
|
+
* fields are omitted, not sent (the host rejects a present-but-blank role).
|
|
65
|
+
* Every invite entry (members tab, header action, fresh-room capsule) shares it.
|
|
66
|
+
*/
|
|
67
|
+
export declare function inviteRequest(values: InviteDialogSubmit): RoomInviteValues;
|
|
50
68
|
export interface InviteDialogProps {
|
|
51
69
|
readonly renderHarnessModelPicker?: LocalAgentUi['renderHarnessModelPicker'] | undefined;
|
|
52
70
|
readonly memberConfiguration?: ReactNode | undefined;
|
|
@@ -55,6 +73,10 @@ export interface InviteDialogProps {
|
|
|
55
73
|
readonly member?: RoomMember | undefined;
|
|
56
74
|
/** The invitable providers; undefined while loading (invite mode). */
|
|
57
75
|
readonly providers?: readonly RoomProviderInfo[] | undefined;
|
|
76
|
+
/** Host agent presets a native DSH member may compose; absent = no native option. */
|
|
77
|
+
readonly presets?: readonly RoomPresetInfo[] | undefined;
|
|
78
|
+
/** The room session's own mode: the default mode of a new member, native or CLI. */
|
|
79
|
+
readonly sessionPreset?: string | undefined;
|
|
58
80
|
/** False = the delegation facade is absent (invite mode). */
|
|
59
81
|
readonly localAgentAvailable: boolean;
|
|
60
82
|
/** The room session's cwd (the empty-cwd placeholder, invite mode). */
|
|
@@ -86,5 +108,5 @@ export interface InviteDialogProps {
|
|
|
86
108
|
readonly t: MembersViewProps['t'];
|
|
87
109
|
}
|
|
88
110
|
/** The invite/edit modal card. */
|
|
89
|
-
export declare function InviteDialog({ mode, member, providers, localAgentAvailable, inheritedCwd, existingNames, browseDirectory, modelSurface, renderHarnessModelPicker, memberModel, memberConfiguration, onSubmit, onClose, t, }: InviteDialogProps): ReactNode;
|
|
111
|
+
export declare function InviteDialog({ mode, member, providers, presets, sessionPreset, localAgentAvailable, inheritedCwd, existingNames, browseDirectory, modelSurface, renderHarnessModelPicker, memberModel, memberConfiguration, onSubmit, onClose, t, }: InviteDialogProps): ReactNode;
|
|
90
112
|
//# sourceMappingURL=InviteDialog.d.ts.map
|
|
@@ -14,8 +14,10 @@ import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
|
|
14
14
|
* ▸ 高级设置 (role instructions + the member-level cwd, collapsed on invite —
|
|
15
15
|
* editing opens it, since editing IS changing those). The model field sits in
|
|
16
16
|
* the main form (promoted out of the drawer): the same control the provider
|
|
17
|
-
* settings cards use
|
|
18
|
-
*
|
|
17
|
+
* settings cards use, widened with the model's reasoning-effort row (a
|
|
18
|
+
* brokerless composition keeps the control model-only). Invites retain a
|
|
19
|
+
* saved provider choice, otherwise the shared picker chooses the first
|
|
20
|
+
* candidate. Edits retain member configuration.
|
|
19
21
|
* Older cores without the shared picker keep the legacy default/input fallback.
|
|
20
22
|
* The rest of the original
|
|
21
23
|
* contract is unchanged: logged-out providers grey with login guidance (an
|
|
@@ -28,18 +30,41 @@ import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
|
|
28
30
|
* edit mode diffs name/cwd/instructions/model.
|
|
29
31
|
*/
|
|
30
32
|
import { useEffect, useRef, useState } from 'react';
|
|
33
|
+
/** The provider select's value for a native DSH member (never a delegation provider id). */
|
|
34
|
+
const NATIVE = '__native__';
|
|
31
35
|
import { IconChevronDownOutlineMedium } from './icons.js';
|
|
32
36
|
import { MemberCard } from './MemberCard.js';
|
|
33
37
|
import { rollName } from './name-pool.js';
|
|
38
|
+
import { ROOM_ROLE_MAX } from '../journal.js';
|
|
34
39
|
import css from './InviteDialog.module.css';
|
|
40
|
+
/**
|
|
41
|
+
* The dialog's submission as the host's invite request: blank optional
|
|
42
|
+
* fields are omitted, not sent (the host rejects a present-but-blank role).
|
|
43
|
+
* Every invite entry (members tab, header action, fresh-room capsule) shares it.
|
|
44
|
+
*/
|
|
45
|
+
export function inviteRequest(values) {
|
|
46
|
+
return {
|
|
47
|
+
provider: values.provider,
|
|
48
|
+
name: values.name,
|
|
49
|
+
...values.instructions === '' ? {} : { instructions: values.instructions },
|
|
50
|
+
...values.role === '' ? {} : { role: values.role },
|
|
51
|
+
...values.kind === 'native' ? { kind: 'native' } : {},
|
|
52
|
+
...values.preset === '' ? {} : { preset: values.preset },
|
|
53
|
+
...values.cwd === '' ? {} : { cwd: values.cwd },
|
|
54
|
+
...values.firstTask === '' ? {} : { firstTask: values.firstTask },
|
|
55
|
+
...values.model === '' ? {} : { model: values.model },
|
|
56
|
+
...values.effort === '' ? {} : { effort: values.effort },
|
|
57
|
+
};
|
|
58
|
+
}
|
|
35
59
|
/** The addressing-name grammar (host-validated too; this is the precheck). */
|
|
36
60
|
function validName(name) {
|
|
37
61
|
return name !== '' && !/\s/.test(name) && !name.includes('@');
|
|
38
62
|
}
|
|
39
63
|
/** The invite/edit modal card. */
|
|
40
|
-
export function InviteDialog({ mode, member, providers, localAgentAvailable, inheritedCwd, existingNames, browseDirectory, modelSurface, renderHarnessModelPicker, memberModel, memberConfiguration, onSubmit, onClose, t, }) {
|
|
64
|
+
export function InviteDialog({ mode, member, providers, presets, sessionPreset, localAgentAvailable, inheritedCwd, existingNames, browseDirectory, modelSurface, renderHarnessModelPicker, memberModel, memberConfiguration, onSubmit, onClose, t, }) {
|
|
41
65
|
const [provider, setProvider] = useState('');
|
|
42
66
|
const [name, setName] = useState(member?.name ?? '');
|
|
67
|
+
const [role, setRole] = useState(member?.role ?? '');
|
|
43
68
|
const [instructions, setInstructions] = useState(member?.instructions ?? '');
|
|
44
69
|
const [cwd, setCwd] = useState(member?.cwd ?? '');
|
|
45
70
|
const [firstTask, setFirstTask] = useState('');
|
|
@@ -47,6 +72,13 @@ export function InviteDialog({ mode, member, providers, localAgentAvailable, inh
|
|
|
47
72
|
// replaces it when the memberModel read below answers.
|
|
48
73
|
const [model, setModel] = useState(mode === 'edit' ? member?.model ?? '' : '');
|
|
49
74
|
const [modelBaseline, setModelBaseline] = useState(mode === 'edit' ? member?.model ?? '' : '');
|
|
75
|
+
// The effort companion of the model field: '' = follow the harness default.
|
|
76
|
+
// Edit mode opens on the journaled intent (a live member's effective effort
|
|
77
|
+
// is the member-configuration card's business — it replaces this field).
|
|
78
|
+
const [effort, setEffort] = useState(mode === 'edit' ? member?.effort ?? '' : '');
|
|
79
|
+
// Read-only baseline: no broker read rewrites it (a live member's effort
|
|
80
|
+
// belongs to the member-configuration card that replaces this field).
|
|
81
|
+
const [effortBaseline] = useState(mode === 'edit' ? member?.effort ?? '' : '');
|
|
50
82
|
/** The model row's surface: menu vocabulary + the default an unset field displays. */
|
|
51
83
|
const [surface, setSurface] = useState(undefined);
|
|
52
84
|
const [modelMenuOpen, setModelMenuOpen] = useState(false);
|
|
@@ -84,7 +116,17 @@ export function InviteDialog({ mode, member, providers, localAgentAvailable, inh
|
|
|
84
116
|
return () => { document.removeEventListener('mousedown', onPointerDown); };
|
|
85
117
|
}, [modelMenuOpen]);
|
|
86
118
|
const authenticated = (providers ?? []).filter(entry => entry.authenticated);
|
|
87
|
-
const
|
|
119
|
+
const nativeAvailable = presets !== undefined;
|
|
120
|
+
const chosen = provider === '' ? (authenticated[0]?.provider ?? (nativeAvailable ? NATIVE : '')) : provider;
|
|
121
|
+
// Editing: the member's own kind, never the invite form's provider pick.
|
|
122
|
+
const native = mode === 'edit' ? member?.kind === 'native' : chosen === NATIVE;
|
|
123
|
+
// A new member works in the room's own mode unless the human picks another.
|
|
124
|
+
const roomMode = sessionPreset !== undefined && presets?.some(entry => entry.id === sessionPreset) === true ? sessionPreset : undefined;
|
|
125
|
+
const [preset, setPreset] = useState('');
|
|
126
|
+
const chosenPreset = preset === '' ? roomMode ?? presets?.find(entry => entry.isDefault)?.id ?? '' : preset;
|
|
127
|
+
/** A CLI member's mode: '' = none (only its own tools plus the room's); undefined = untouched (the room's mode). */
|
|
128
|
+
const [cliPicked, setCliPreset] = useState(undefined);
|
|
129
|
+
const cliPreset = cliPicked ?? roomMode ?? '';
|
|
88
130
|
const someLoggedOut = (providers ?? []).some(entry => !entry.authenticated);
|
|
89
131
|
/** The chosen provider's roster harness name (the harnessModel lookup key). */
|
|
90
132
|
const chosenHarness = (providers ?? []).find(entry => entry.provider === chosen)?.harness;
|
|
@@ -92,8 +134,11 @@ export function InviteDialog({ mode, member, providers, localAgentAvailable, inh
|
|
|
92
134
|
// broker read (or a composition without the family's client half) leaves
|
|
93
135
|
// the field a plain text input — blank still follows the harness default.
|
|
94
136
|
useEffect(() => {
|
|
137
|
+
// Edit mode's surface is the member's own (below): never reset it here.
|
|
138
|
+
if (mode !== 'invite')
|
|
139
|
+
return;
|
|
95
140
|
setSurface(undefined);
|
|
96
|
-
if (
|
|
141
|
+
if (modelSurface === undefined || chosenHarness === undefined)
|
|
97
142
|
return;
|
|
98
143
|
let cancelled = false;
|
|
99
144
|
void modelSurface(chosenHarness).then((result) => {
|
|
@@ -128,7 +173,7 @@ export function InviteDialog({ mode, member, providers, localAgentAvailable, inh
|
|
|
128
173
|
// is the only hard requirement.
|
|
129
174
|
const canSubmit = mode === 'edit'
|
|
130
175
|
? validName(name)
|
|
131
|
-
: localAgentAvailable && chosen !== '' && validName(name);
|
|
176
|
+
: (native || localAgentAvailable) && chosen !== '' && validName(name);
|
|
132
177
|
// The model row: the same control the provider settings cards use. An unset
|
|
133
178
|
// field DISPLAYS the provider's current default dimmed (inherited, never
|
|
134
179
|
// pinned); the chevron menu leads with the 默认 item (picking it clears the
|
|
@@ -150,13 +195,21 @@ export function InviteDialog({ mode, member, providers, localAgentAvailable, inh
|
|
|
150
195
|
const previewName = name.trim() === '' ? t('invite.previewName') : name.trim();
|
|
151
196
|
const previewProvider = mode === 'edit'
|
|
152
197
|
? member?.provider
|
|
153
|
-
: (providers ?? []).find(entry => entry.provider === chosen)?.displayName ?? chosen;
|
|
154
|
-
const previewMember = {
|
|
198
|
+
: native ? 'dsh' : (providers ?? []).find(entry => entry.provider === chosen)?.displayName ?? chosen;
|
|
199
|
+
const previewMember = mode === 'edit' && member !== undefined ? (({ instructions: _i, role: _r, ...rest }) => ({
|
|
200
|
+
...rest,
|
|
201
|
+
name: previewName,
|
|
202
|
+
...instructions.trim() === '' ? {} : { instructions: instructions.trim() },
|
|
203
|
+
...role.trim() === '' ? {} : { role: role.trim() },
|
|
204
|
+
}))(member) : {
|
|
155
205
|
name: previewName,
|
|
156
|
-
kind: 'cli',
|
|
206
|
+
kind: native ? 'native' : 'cli',
|
|
207
|
+
...native && chosenPreset !== '' ? { preset: chosenPreset } : {},
|
|
208
|
+
...!native && cliPreset !== '' ? { preset: cliPreset } : {},
|
|
157
209
|
invitedBy: 'human',
|
|
158
210
|
...previewProvider === undefined || previewProvider === '' ? {} : { provider: previewProvider },
|
|
159
211
|
...instructions.trim() === '' ? {} : { instructions: instructions.trim() },
|
|
212
|
+
...role.trim() === '' ? {} : { role: role.trim() },
|
|
160
213
|
};
|
|
161
214
|
const rollDice = () => {
|
|
162
215
|
const rolled = rollName(existingNames, name);
|
|
@@ -193,8 +246,8 @@ export function InviteDialog({ mode, member, providers, localAgentAvailable, inh
|
|
|
193
246
|
setError(null);
|
|
194
247
|
try {
|
|
195
248
|
const outcome = await onSubmit({
|
|
196
|
-
provider: chosen, name, instructions: instructions.trim(), cwd: cwd.trim(), firstTask: firstTask.trim(),
|
|
197
|
-
model: model.trim(), modelBaseline,
|
|
249
|
+
provider: native ? 'dsh' : chosen, kind: native ? 'native' : 'cli', preset: native ? chosenPreset : cliPreset, name, role: role.trim(), instructions: instructions.trim(), cwd: cwd.trim(), firstTask: firstTask.trim(),
|
|
250
|
+
model: model.trim(), modelBaseline, effort: effort.trim(), effortBaseline,
|
|
198
251
|
});
|
|
199
252
|
if (!outcome.ok) {
|
|
200
253
|
setError(outcome.message);
|
|
@@ -212,5 +265,16 @@ export function InviteDialog({ mode, member, providers, localAgentAvailable, inh
|
|
|
212
265
|
return (_jsx("div", { className: css.overlay, role: "presentation", onMouseDown: (event) => {
|
|
213
266
|
if (event.target === event.currentTarget)
|
|
214
267
|
onClose();
|
|
215
|
-
}, children: _jsxs("div", { className: css.card, role: "dialog", "aria-modal": "true", onKeyDown: onCardKeyDown, children: [_jsx("div", { className: css.title, children: mode === 'invite' ? t('invite.title') : t('invite.title.edit', { member: member?.name ?? '' }) }), _jsxs("div", { className: css.body, children: [_jsxs("div", { className: css.form, children: [mode === 'invite' && (!localAgentAvailable ? (_jsx("div", { className: css.hint, role: "status", children: t('invite.facadeMissing') })) : (_jsxs("label", { className: css.field, children: [_jsx("span", { className: css.label, children: t('invite.provider') }),
|
|
268
|
+
}, children: _jsxs("div", { className: css.card, "data-mode": mode, role: "dialog", "aria-modal": "true", onKeyDown: onCardKeyDown, children: [_jsx("div", { className: css.title, children: mode === 'invite' ? t('invite.title') : t('invite.title.edit', { member: member?.name ?? '' }) }), _jsxs("div", { className: css.body, children: [_jsxs("div", { className: css.form, children: [mode === 'invite' && (!localAgentAvailable && !nativeAvailable ? (_jsx("div", { className: css.hint, role: "status", children: t('invite.facadeMissing') })) : (_jsxs("label", { className: css.field, children: [_jsx("span", { className: css.label, children: t('invite.provider') }), _jsxs("select", { className: css.select, value: chosen, onChange: event => { setProvider(event.target.value); setModel(''); setEffort(''); }, children: [nativeAvailable && _jsx("option", { value: NATIVE, children: t('invite.native') }), localAgentAvailable && (providers ?? []).map(entry => (_jsxs("option", { value: entry.provider, disabled: !entry.authenticated, children: [entry.displayName, entry.authenticated ? '' : t('invite.loggedOut')] }, entry.provider)))] }), !native && someLoggedOut && _jsx("span", { className: css.hint, children: t('invite.loginHint') }), native && _jsx("span", { className: css.hint, children: t('invite.nativeHint') })] }))), mode === 'invite' && presets !== undefined && presets.length > 0 && (native || localAgentAvailable) && (_jsxs("label", { className: css.field, children: [_jsx("span", { className: css.label, children: t('invite.cliPreset') }), _jsxs("select", { className: css.select, "aria-label": t('invite.cliPreset'), value: native ? chosenPreset : cliPreset, onChange: event => { if (native)
|
|
269
|
+
setPreset(event.target.value);
|
|
270
|
+
else
|
|
271
|
+
setCliPreset(event.target.value); }, children: [!native && _jsx("option", { value: "", children: t('invite.cliPresetNone') }), presets.map(entry => (_jsx("option", { value: entry.id, disabled: entry.broken !== undefined, title: entry.broken ?? entry.description, children: entry.name ?? entry.id }, entry.id)))] }), _jsx("span", { className: css.hint, children: (() => {
|
|
272
|
+
const picked = presets.find(entry => entry.id === (native ? chosenPreset : cliPreset));
|
|
273
|
+
const about = picked?.broken ?? picked?.description;
|
|
274
|
+
return [about, t(native ? 'invite.presetHint' : 'invite.cliPresetHint')].filter(Boolean).join(' · ');
|
|
275
|
+
})() })] })), mode === 'edit' && _jsxs("div", { className: css.editHead, children: [_jsx(MemberCard, { preview: true, member: previewMember, run: undefined, elapsedMs: undefined, openSession: () => { }, onEdit: () => { }, onRemove: () => { }, t: t }), _jsxs("dl", { className: css.locked, children: [_jsxs("div", { children: [_jsx("dt", { children: t('invite.provider') }), _jsx("dd", { children: member?.kind === 'main-agent' || member?.kind === 'native' ? 'DSH' : member?.provider ?? '—' })] }), _jsxs("div", { children: [_jsx("dt", { children: t('invite.cliPreset') }), _jsx("dd", { children: member?.preset === undefined
|
|
276
|
+
? t('invite.cliPresetNone')
|
|
277
|
+
: presets?.find(entry => entry.id === member.preset)?.name ?? member.preset })] }), _jsx("span", { className: css.lockedNote, children: t('invite.lockedNote') })] })] }), native ? null : mode === 'edit' && memberConfiguration !== undefined
|
|
278
|
+
? _jsxs("div", { className: css.field, children: [_jsx("span", { className: css.label, children: t('invite.modelLabel') }), memberConfiguration] })
|
|
279
|
+
: memberConfiguration ?? (mode === 'invite' && chosenHarness !== undefined && (modelSurface === undefined || surface !== undefined) ? renderHarnessModelPicker?.(chosenHarness, { value: model, onChange: setModel, effort: { value: effort, onChange: setEffort }, disabled: busy, fields: { row: css.fieldPair, field: css.field, label: css.label, select: css.select } }) : undefined) ?? modelField, _jsxs("label", { className: css.field, children: [_jsx("span", { className: css.label, children: t('invite.name') }), _jsxs("span", { className: css.nameRow, children: [_jsx("input", { className: css.input, value: name, placeholder: "ada", onChange: event => { setName(event.target.value); } }), _jsx("button", { type: "button", className: css.dice, title: t('invite.randomName'), "aria-label": t('invite.randomName'), onClick: rollDice, children: "\uD83C\uDFB2" })] }), _jsx("span", { className: css.hint, children: t('invite.nameHint') })] }), _jsxs("label", { className: css.field, children: [_jsx("span", { className: css.label, children: t('invite.role') }), _jsx("input", { className: css.input, value: role, maxLength: ROOM_ROLE_MAX, placeholder: t('invite.rolePlaceholder'), onChange: event => { setRole(event.target.value); } }), _jsx("span", { className: css.hint, children: t('invite.roleHint') })] }), mode === 'invite' && (_jsxs("label", { className: css.field, children: [_jsx("span", { className: css.label, children: t('invite.firstTask') }), _jsx("textarea", { className: css.textarea, rows: 2, value: firstTask, onChange: event => { setFirstTask(event.target.value); } }), _jsx("span", { className: css.hint, children: t('invite.firstTaskHint') })] })), _jsxs("details", { className: css.advanced, open: advancedOpen, onToggle: (event) => { setAdvancedOpen(event.currentTarget.open); }, children: [_jsx("summary", { className: css.advancedSummary, children: t('invite.advanced') }), _jsxs("div", { className: css.advancedBody, children: [_jsxs("label", { className: css.field, children: [_jsx("span", { className: css.label, children: t('invite.instructions') }), _jsx("textarea", { className: css.textarea, rows: 3, value: instructions, placeholder: t('invite.instructionsPlaceholder'), onChange: event => { setInstructions(event.target.value); } }), _jsx("span", { className: css.hint, children: mode === 'edit' ? t('invite.instructionsHintEdit') : t('invite.instructionsHint') })] }), _jsxs("label", { className: css.field, children: [_jsx("span", { className: css.label, children: t('invite.cwd') }), _jsxs("span", { className: css.cwdRow, children: [_jsx("input", { className: css.input, value: cwd, placeholder: inheritedCwd ?? '', readOnly: true, "aria-label": t('invite.cwd') }), _jsx("button", { type: "button", className: css.browse, disabled: browsing, onClick: () => { void browse(); }, children: t('invite.browse') }), cwd !== '' && (_jsx("button", { type: "button", className: css.browse, "aria-label": t('invite.cwdReset'), onClick: () => { setCwd(''); }, children: "\u2715" }))] }), _jsx("span", { className: css.hint, children: t('invite.cwdHint') })] })] })] })] }), mode === 'invite' && _jsxs("div", { className: css.previewCol, children: [_jsx("span", { className: css.previewLabel, children: t('invite.preview') }), _jsx(MemberCard, { preview: true, member: previewMember, run: undefined, elapsedMs: undefined, openSession: () => { }, onEdit: () => { }, onRemove: () => { }, t: t })] })] }), error !== null && _jsx("div", { className: css.error, role: "alert", children: error }), _jsxs("div", { className: css.buttons, children: [_jsx("button", { type: "button", className: css.button, onClick: onClose, children: t('invite.cancel') }), _jsx("button", { type: "button", className: css.primary, disabled: !canSubmit || busy, onClick: () => { void submit(); }, children: mode === 'invite' ? t('invite.submit') : t('invite.save') })] })] }) }));
|
|
216
280
|
}
|
|
@@ -9,7 +9,11 @@ import { type ReactNode } from 'react';
|
|
|
9
9
|
import type { RoomMember, RoomMemberRun } from '../types.ts';
|
|
10
10
|
import type { MembersViewProps } from './slots.ts';
|
|
11
11
|
/** One roster card: identity, avatar block, role, status chip, actions. */
|
|
12
|
-
export declare function MemberCard({ member, run, elapsedMs, openSession, onEdit, onRemove, t, modelHint, coordinator, onPromote, preview, }: {
|
|
12
|
+
export declare function MemberCard({ member, run, elapsedMs, openSession, onEdit, onRemove, t, modelHint, coordinator, onPromote, preview, removal, onLocate, }: {
|
|
13
|
+
/** Reveal this member in the team page. */
|
|
14
|
+
readonly onLocate?: (() => void) | undefined;
|
|
15
|
+
/** The open removal confirmation, rendered inside the card. */
|
|
16
|
+
readonly removal?: ReactNode;
|
|
13
17
|
readonly coordinator?: boolean;
|
|
14
18
|
readonly onPromote?: (() => void) | undefined;
|
|
15
19
|
readonly member: RoomMember;
|
|
@@ -11,7 +11,7 @@ import { formatDurationMs } from './format.js';
|
|
|
11
11
|
import { memberColor } from './member-color.js';
|
|
12
12
|
import css from './MembersView.module.css';
|
|
13
13
|
/** One roster card: identity, avatar block, role, status chip, actions. */
|
|
14
|
-
export function MemberCard({ member, run, elapsedMs, openSession, onEdit, onRemove, t, modelHint, coordinator = false, onPromote, preview = false, }) {
|
|
14
|
+
export function MemberCard({ member, run, elapsedMs, openSession, onEdit, onRemove, t, modelHint, coordinator = false, onPromote, preview = false, removal, onLocate, }) {
|
|
15
15
|
const [expanded, setExpanded] = useState(false);
|
|
16
16
|
const [overflowing, setOverflowing] = useState(false);
|
|
17
17
|
const instructionsRef = useRef(null);
|
|
@@ -26,7 +26,7 @@ export function MemberCard({ member, run, elapsedMs, openSession, onEdit, onRemo
|
|
|
26
26
|
const child = member.childSessionId;
|
|
27
27
|
const running = run?.state === 'running';
|
|
28
28
|
const failed = run?.state === 'failed';
|
|
29
|
-
return (_jsxs("div", { className: css.card, "data-member": member.name, children: [_jsxs("div", { className: css.head, children: [_jsx("span", { className: css.avatarTile, style: preview ? undefined : { '--member-color': color }, "aria-hidden": true, children: member.name.slice(0, 1).toUpperCase() }), _jsxs("span", { className: css.identity, children: [_jsx("span", { className: css.name, children: member.name }), coordinator && _jsx("span", { className: css.hint, children: t('coordinator.label') }), _jsxs("span", { className: css.hint, children: [member.kind === 'main-agent' ? t('member.kind.main') : member.provider ?? '', modelHint !== undefined && modelHint !== '' && _jsxs(_Fragment, { children: [' · ', modelHint] })] })] }), running ? (_jsx("button", { type: "button", className: css.chipRunning, title: t('speech.jump'), disabled: child === undefined, onClick: () => { if (child !== undefined)
|
|
30
|
-
openSession(child); }, children: `${t('members.status.running')} · ${formatDurationMs(elapsedMs ?? 0)}` })) : (_jsx("span", { className: failed ? css.chipFailed : css.chipIdle, children: failed ? t('members.status.failed') : t('members.status.idle') }))] }), member.kind
|
|
31
|
-
openSession(child); }, children: t('members.trajectory') }), _jsx("button", { type: "button", className: css.action, onClick: onEdit, children: t('members.edit') }), _jsx("button", { type: "button", className: css.action
|
|
29
|
+
return (_jsxs("div", { className: css.card, "data-member": member.name, children: [_jsxs("div", { className: css.head, children: [_jsx("span", { className: css.avatarTile, style: preview ? undefined : { '--member-color': color }, "aria-hidden": true, children: member.name.slice(0, 1).toUpperCase() }), _jsxs("span", { className: css.identity, children: [_jsxs("span", { className: css.nameRow, children: [_jsx("span", { className: css.name, children: member.name }), member.role !== undefined && _jsx("span", { className: css.role, children: member.role })] }), coordinator && _jsx("span", { className: css.hint, children: t('coordinator.label') }), _jsxs("span", { className: css.hint, children: [member.kind === 'main-agent' ? t('member.kind.main') : member.kind === 'native' ? t('member.kind.native', { preset: member.preset ?? t('member.kind.defaultPreset') }) : member.provider ?? '', modelHint !== undefined && modelHint !== '' && _jsxs(_Fragment, { children: [' · ', modelHint] })] })] }), running ? (_jsx("button", { type: "button", className: css.chipRunning, title: t('speech.jump'), disabled: child === undefined, onClick: () => { if (child !== undefined)
|
|
30
|
+
openSession(child); }, children: `${t('members.status.running')} · ${formatDurationMs(elapsedMs ?? 0)}` })) : (_jsx("span", { className: failed ? css.chipFailed : css.chipIdle, children: failed ? t('members.status.failed') : t('members.status.idle') }))] }), member.kind !== 'main-agent' && (member.instructions === undefined ? (_jsx("p", { className: css.instructionsEmpty, children: t('members.instructions.empty') })) : (_jsxs("div", { className: css.instructionsBlock, children: [_jsx("p", { ref: instructionsRef, className: expanded ? css.instructionsExpanded : css.instructions, title: expanded ? undefined : member.instructions, children: member.instructions }), (overflowing || expanded) && (_jsx("button", { type: "button", className: css.expandToggle, onClick: () => { setExpanded(value => !value); }, children: expanded ? t('members.instructions.collapse') : t('members.instructions.expand') }))] }))), !preview && (_jsxs("div", { className: css.foot, children: [!coordinator && onPromote !== undefined && _jsx("button", { type: "button", className: css.action, disabled: running, onClick: onPromote, children: t('coordinator.promote') }), member.kind === 'main-agent' && onLocate !== undefined && (_jsx("span", { className: css.actions, children: _jsx("button", { type: "button", className: css.action, onClick: onLocate, children: t('team.locate') }) })), member.kind !== 'main-agent' && (_jsxs("span", { className: css.actions, children: [onLocate !== undefined && _jsx("button", { type: "button", className: css.action, onClick: onLocate, children: t('team.locate') }), _jsx("button", { type: "button", className: css.action, disabled: child === undefined, onClick: () => { if (child !== undefined)
|
|
31
|
+
openSession(child); }, children: t('members.trajectory') }), _jsx("button", { type: "button", className: css.action, onClick: onEdit, children: t('members.edit') }), _jsx("button", { type: "button", className: removal === undefined ? css.action : `${css.action} ${css.removeOpen}`, "aria-expanded": removal !== undefined, onClick: onRemove, children: t('members.remove') })] }))] })), removal] }));
|
|
32
32
|
}
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { type ReactNode } from 'react';
|
|
2
2
|
import type { MembersViewProps } from './slots.ts';
|
|
3
3
|
/** The members tab. */
|
|
4
|
-
export declare function MembersView({ sessionId, roomStore, roomCwd, openSession, removeMember, updateMember, invite, listProviders, browseDirectory, modelSurface, renderHarnessModelPicker, memberModel, setMemberModel, renderMemberConfiguration, modelDirectory, setCoordinator, t, }: MembersViewProps): ReactNode;
|
|
4
|
+
export declare function MembersView({ sessionId, roomStore, roomCwd, openSession, removeMember, restoreMember, setSettings, openTeam, unlinkSession, updateMember, invite, listProviders, browseDirectory, modelSurface, renderHarnessModelPicker, memberModel, setMemberModel, renderMemberConfiguration, modelDirectory, setCoordinator, t, }: MembersViewProps): ReactNode;
|
|
5
5
|
//# sourceMappingURL=MembersView.d.ts.map
|
|
@@ -12,12 +12,15 @@ import { Fragment as _Fragment, jsx as _jsx, jsxs as _jsxs } from "react/jsx-run
|
|
|
12
12
|
* elapsed honest). The main-agent member takes no instructions and cannot be
|
|
13
13
|
* removed — it is the room itself.
|
|
14
14
|
*/
|
|
15
|
-
import { coordinatorMember } from '../journal.js';
|
|
15
|
+
import { coordinatorMember, DEFAULT_ROOM_SETTINGS } from '../journal.js';
|
|
16
|
+
import { RoomSettingsView } from './RoomSettingsView.js';
|
|
16
17
|
import { useEffect, useState, useSyncExternalStore } from 'react';
|
|
18
|
+
import { memberColor } from './member-color.js';
|
|
17
19
|
import { Button } from '@deepseek-ai/dsh-client-ui-primitives';
|
|
18
20
|
import { IconAgentPresetOutlineMedium } from './icons.js';
|
|
21
|
+
import { RemoveMemberConfirm, removalImpact } from './RemoveMemberConfirm.js';
|
|
19
22
|
import { MemberCard } from './MemberCard.js';
|
|
20
|
-
import { InviteDialog } from './InviteDialog.js';
|
|
23
|
+
import { InviteDialog, inviteRequest } from './InviteDialog.js';
|
|
21
24
|
import css from './MembersView.module.css';
|
|
22
25
|
/**
|
|
23
26
|
* The main-agent card's model hint: the session's current selection from the
|
|
@@ -34,7 +37,7 @@ function MainAgentModelHint({ directory }) {
|
|
|
34
37
|
return _jsx(_Fragment, { children: choice?.model.name ?? `${state.current.provider}/${state.current.model}` });
|
|
35
38
|
}
|
|
36
39
|
/** The members tab. */
|
|
37
|
-
export function MembersView({ sessionId, roomStore, roomCwd, openSession, removeMember, updateMember, invite, listProviders, browseDirectory, modelSurface, renderHarnessModelPicker, memberModel, setMemberModel, renderMemberConfiguration, modelDirectory, setCoordinator, t, }) {
|
|
40
|
+
export function MembersView({ sessionId, roomStore, roomCwd, openSession, removeMember, restoreMember, setSettings, openTeam, unlinkSession, updateMember, invite, listProviders, browseDirectory, modelSurface, renderHarnessModelPicker, memberModel, setMemberModel, renderMemberConfiguration, modelDirectory, setCoordinator, t, }) {
|
|
38
41
|
// Entering the tab pulls the freshest state once.
|
|
39
42
|
useEffect(() => { void roomStore.refresh(sessionId); }, [roomStore, sessionId]);
|
|
40
43
|
const state = useSyncExternalStore(roomStore.subscribe, () => roomStore.getCached(sessionId));
|
|
@@ -42,6 +45,16 @@ export function MembersView({ sessionId, roomStore, roomCwd, openSession, remove
|
|
|
42
45
|
const [providers, setProviders] = useState(undefined);
|
|
43
46
|
const [notice, setNotice] = useState(null);
|
|
44
47
|
const [error, setError] = useState(null);
|
|
48
|
+
/** The card whose removal confirmation is open. */
|
|
49
|
+
const [removing, setRemoving] = useState(null);
|
|
50
|
+
/** The last removal, offered for undo for a few seconds. */
|
|
51
|
+
const [removed, setRemoved] = useState(null);
|
|
52
|
+
useEffect(() => {
|
|
53
|
+
if (removed === null)
|
|
54
|
+
return undefined;
|
|
55
|
+
const timer = setTimeout(() => { setRemoved(null); }, 5000);
|
|
56
|
+
return () => { clearTimeout(timer); };
|
|
57
|
+
}, [removed]);
|
|
45
58
|
/** The CLI members' effective models, keyed by member name (the cards' hints). */
|
|
46
59
|
const [memberModels, setMemberModels] = useState(new Map());
|
|
47
60
|
const anyRunning = state?.runs.some(run => run.state === 'running') ?? false;
|
|
@@ -82,7 +95,7 @@ export function MembersView({ sessionId, roomStore, roomCwd, openSession, remove
|
|
|
82
95
|
const openInvite = () => {
|
|
83
96
|
setProviders(undefined);
|
|
84
97
|
setDialog({ mode: 'invite' });
|
|
85
|
-
void listProviders().then(setProviders);
|
|
98
|
+
void listProviders(sessionId).then(setProviders);
|
|
86
99
|
};
|
|
87
100
|
const submitDialog = async (values) => {
|
|
88
101
|
if (dialog === null)
|
|
@@ -95,8 +108,12 @@ export function MembersView({ sessionId, roomStore, roomCwd, openSession, remove
|
|
|
95
108
|
const name = values.name.trim();
|
|
96
109
|
const cwd = values.cwd.trim();
|
|
97
110
|
const instructions = values.instructions.trim();
|
|
111
|
+
const role = values.role.trim();
|
|
98
112
|
const model = values.model.trim();
|
|
99
|
-
const
|
|
113
|
+
const effort = values.effort.trim();
|
|
114
|
+
const configurationOwned = member.childSessionId !== undefined && renderMemberConfiguration !== undefined;
|
|
115
|
+
const modelChanged = model !== values.modelBaseline.trim() && !configurationOwned;
|
|
116
|
+
const effortChanged = effort !== values.effortBaseline.trim() && !configurationOwned;
|
|
100
117
|
if (modelChanged && member.childSessionId !== undefined) {
|
|
101
118
|
if (setMemberModel === undefined)
|
|
102
119
|
return { ok: false, message: t('invite.error.generic') };
|
|
@@ -114,23 +131,18 @@ export function MembersView({ sessionId, roomStore, roomCwd, openSession, remove
|
|
|
114
131
|
...instructions !== (member.instructions ?? '')
|
|
115
132
|
? { instructions: instructions === '' ? null : instructions }
|
|
116
133
|
: {},
|
|
134
|
+
...role !== (member.role ?? '') ? { role: role === '' ? null : role } : {},
|
|
117
135
|
// The journaled intent: the first dispatch binds it for a never-
|
|
118
136
|
// started member (the broker call above is a no-op there — no
|
|
119
|
-
// delegation record — so this value alone carries the edit).
|
|
137
|
+
// delegation record — so this value alone carries the edit). Effort
|
|
138
|
+
// rides the same rule; a started member's effort belongs to the
|
|
139
|
+
// member-configuration card that replaces these fields outright.
|
|
120
140
|
...modelChanged && member.childSessionId === undefined ? { model: model === '' ? null : model } : {},
|
|
141
|
+
...effortChanged && member.childSessionId === undefined ? { effort: effort === '' ? null : effort } : {},
|
|
121
142
|
};
|
|
122
143
|
return Object.keys(patch).length === 0 ? { ok: true } : updateMember(member.name, patch);
|
|
123
144
|
}
|
|
124
|
-
const outcome = await invite(
|
|
125
|
-
provider: values.provider,
|
|
126
|
-
name: values.name,
|
|
127
|
-
// Blank instructions are omitted, not sent: the host rejects a
|
|
128
|
-
// present-but-blank role, and an absent one simply carries no preset.
|
|
129
|
-
...values.instructions === '' ? {} : { instructions: values.instructions },
|
|
130
|
-
...values.cwd === '' ? {} : { cwd: values.cwd },
|
|
131
|
-
...values.firstTask === '' ? {} : { firstTask: values.firstTask },
|
|
132
|
-
...values.model === '' ? {} : { model: values.model },
|
|
133
|
-
});
|
|
145
|
+
const outcome = await invite(inviteRequest(values));
|
|
134
146
|
if (outcome.ok) {
|
|
135
147
|
flash(outcome.pendingFirstTask
|
|
136
148
|
? t('invite.success.task', { member: values.name })
|
|
@@ -139,13 +151,32 @@ export function MembersView({ sessionId, roomStore, roomCwd, openSession, remove
|
|
|
139
151
|
return outcome;
|
|
140
152
|
};
|
|
141
153
|
const remove = async (member) => {
|
|
142
|
-
if (!window.confirm(t('members.removeConfirm', { member: member.name })))
|
|
143
|
-
return;
|
|
144
154
|
const outcome = await removeMember(member.name);
|
|
155
|
+
setRemoving(null);
|
|
156
|
+
if (!outcome.ok) {
|
|
157
|
+
setError(outcome.message);
|
|
158
|
+
return;
|
|
159
|
+
}
|
|
160
|
+
setError(null);
|
|
161
|
+
setRemoved(outcome.value);
|
|
162
|
+
};
|
|
163
|
+
const undo = async () => {
|
|
164
|
+
if (removed === null)
|
|
165
|
+
return;
|
|
166
|
+
const name = removed.name;
|
|
167
|
+
setRemoved(null);
|
|
168
|
+
const outcome = await restoreMember(name);
|
|
145
169
|
if (!outcome.ok)
|
|
146
170
|
setError(outcome.message);
|
|
147
171
|
};
|
|
148
|
-
|
|
172
|
+
/** The undo line: what the removal did, in one sentence. */
|
|
173
|
+
const removedSummary = (value) => [
|
|
174
|
+
t('remove.done', { member: value.name }),
|
|
175
|
+
...value.stopped ? [t('remove.done.stopped')] : [],
|
|
176
|
+
...value.cancelledDeliveries > 0 ? [t('remove.done.queued', { count: value.cancelledDeliveries })] : [],
|
|
177
|
+
...value.notified.length > 0 ? [t('remove.done.notified', { members: value.notified.join('、') })] : [],
|
|
178
|
+
].join(t('remove.done.sep'));
|
|
179
|
+
const dialogNode = dialog !== null && (_jsx(InviteDialog, { mode: dialog.mode, member: dialog.mode === 'edit' ? dialog.member : undefined, providers: providers?.providers, presets: providers?.presets, sessionPreset: providers?.sessionPreset, localAgentAvailable: providers?.localAgentAvailable ?? true, inheritedCwd: roomCwd, existingNames: (state?.members ?? []).map(entry => entry.name), browseDirectory: browseDirectory, modelSurface: modelSurface, renderHarnessModelPicker: renderHarnessModelPicker, memberModel: memberModel, memberConfiguration: dialog.mode === 'edit' && dialog.member.childSessionId !== undefined ? renderMemberConfiguration?.(dialog.member.childSessionId) : undefined, onSubmit: submitDialog, onClose: () => { setDialog(null); }, t: t }));
|
|
149
180
|
// A plain session gets the centered guide state (the official empty-state
|
|
150
181
|
// shape: glyph → title → one-line explainer → primary action) instead of
|
|
151
182
|
// the bare "not a room": inviting an agent IS the promotion (the header
|
|
@@ -155,14 +186,19 @@ export function MembersView({ sessionId, roomStore, roomCwd, openSession, remove
|
|
|
155
186
|
}
|
|
156
187
|
if (state === undefined)
|
|
157
188
|
return null;
|
|
158
|
-
return (_jsxs("div", { className: css.members, children: [_jsxs("div", { className: css.grid, children: [state.members.map((member) => {
|
|
189
|
+
return (_jsxs("div", { className: css.members, children: [_jsx(RoomSettingsView, { settings: state.settings ?? DEFAULT_ROOM_SETTINGS, onChange: setSettings, t: t }), _jsxs("div", { className: css.toolbar, children: [_jsx("span", { className: css.count, children: t('members.count', { count: state.members.length }) }), state.runs.some(run => run.state === 'running') && (_jsx("span", { className: css.hint, children: t('members.runningCount', { count: state.runs.filter(run => run.state === 'running').length }) })), openTeam !== undefined && (_jsx("button", { type: "button", className: css.teamButton, onClick: () => { openTeam(); }, children: t('team.open') }))] }), _jsxs("div", { className: css.grid, children: [state.members.map((member) => {
|
|
159
190
|
const run = state.runs.find(entry => entry.member === member.name);
|
|
160
191
|
const running = run?.state === 'running';
|
|
161
192
|
return (_jsx(MemberCard, { coordinator: coordinatorMember(state)?.name === member.name, onPromote: setCoordinator === undefined || member.id === undefined ? undefined : () => {
|
|
162
193
|
void setCoordinator(member.id, state.coordinator?.revision ?? 0).then(outcome => { if (!outcome.ok)
|
|
163
194
|
setError(outcome.message); });
|
|
164
|
-
}, member: member, run: run, elapsedMs: running ? Date.now() - (run?.startedAt ?? Date.now()) : undefined, openSession: openSession, onEdit: () => {
|
|
195
|
+
}, member: member, run: run, elapsedMs: running ? Date.now() - (run?.startedAt ?? Date.now()) : undefined, openSession: openSession, onEdit: () => {
|
|
196
|
+
setDialog({ mode: 'edit', member });
|
|
197
|
+
// The edit dialog names the member's (locked) mode.
|
|
198
|
+
if (providers === undefined)
|
|
199
|
+
void listProviders(sessionId).then(setProviders);
|
|
200
|
+
}, onLocate: openTeam === undefined ? undefined : () => { openTeam(member.name); }, onRemove: () => { setRemoving(current => current === member.name ? null : member.name); }, removal: removing === member.name ? (_jsx(RemoveMemberConfirm, { member: member, impact: removalImpact(state, member), coordinator: coordinatorMember(state)?.name === member.name, onConfirm: () => remove(member), onCancel: () => { setRemoving(null); }, t: t })) : undefined, modelHint: member.kind === 'main-agent'
|
|
165
201
|
? (modelDirectory !== undefined ? _jsx(MainAgentModelHint, { directory: modelDirectory }) : undefined)
|
|
166
202
|
: memberModels.get(member.name), t: t }, member.id ?? member.name));
|
|
167
|
-
}), _jsx("button", { type: "button", className: css.invite, onClick: openInvite, children: t('members.invite') })] }), notice !== null && _jsx("div", { className: css.notice, role: "status", children: notice }), error !== null && _jsx("div", { className: css.error, role: "alert", children: error }), dialogNode] }));
|
|
203
|
+
}), _jsx("button", { type: "button", className: css.invite, onClick: openInvite, children: t('members.invite') })] }), (state.linked?.length ?? 0) > 0 && (_jsxs("section", { className: css.linked, "aria-label": t('members.linked'), children: [_jsx("span", { className: css.count, children: t('members.linked') }), state.linked.map(entry => (_jsxs("div", { className: css.linkedRow, children: [_jsx("span", { className: css.linkedAvatar, style: { '--member-color': memberColor(entry.title) }, "aria-hidden": true, children: entry.title.slice(0, 1) }), _jsx("span", { className: css.linkedName, children: entry.title }), _jsx("span", { className: css.linkedTag, children: t('team.external.linked') }), _jsx("button", { type: "button", className: css.linkedAction, onClick: () => { openSession(entry.sessionId); }, children: t('members.linkedOpen') }), unlinkSession !== undefined && (_jsx("button", { type: "button", className: css.linkedAction, onClick: () => { unlinkSession(entry.sessionId); }, children: t('team.external.unlink') }))] }, entry.sessionId)))] })), removed !== null && (_jsxs("div", { className: css.undo, role: "status", children: [_jsx("span", { children: removedSummary(removed) }), _jsx("button", { type: "button", onClick: () => { void undo(); }, children: t('remove.undo') })] })), notice !== null && _jsx("div", { className: css.notice, role: "status", children: notice }), error !== null && _jsx("div", { className: css.error, role: "alert", children: error }), dialogNode] }));
|
|
168
204
|
}
|
|
@@ -0,0 +1,40 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The in-card removal confirmation: replaces the browser's native confirm
|
|
3
|
+
* with a layer anchored on the member's own card that states what removing
|
|
4
|
+
* THIS member will actually do — stop its running round, cancel its queued
|
|
5
|
+
* messages, tell whoever waits for its
|
|
6
|
+
* report — and then does exactly that (RoomService.removeMember). An idle
|
|
7
|
+
* member with nothing pending gets one sentence. The current coordinator
|
|
8
|
+
* cannot be removed: the layer says why and how instead of offering a
|
|
9
|
+
* button that would only fail. Esc and an outside click cancel; focus opens
|
|
10
|
+
* on 取消 so a stray Enter never removes anyone.
|
|
11
|
+
*/
|
|
12
|
+
import { type ReactNode } from 'react';
|
|
13
|
+
import type { RoomMember, RoomState } from '../types.ts';
|
|
14
|
+
import type { MembersViewProps } from './slots.ts';
|
|
15
|
+
/** What a removal would touch, read from the replayed room state. */
|
|
16
|
+
export interface RemovalImpact {
|
|
17
|
+
/** What the running round is doing (its message's first line); undefined when idle. */
|
|
18
|
+
readonly running: string | undefined;
|
|
19
|
+
readonly queued: number;
|
|
20
|
+
/** Members waiting on one of this member's reports. */
|
|
21
|
+
readonly waiting: readonly string[];
|
|
22
|
+
}
|
|
23
|
+
/**
|
|
24
|
+
* Read a removal's consequences from the room state the members tab holds.
|
|
25
|
+
* @param state - the replayed room state.
|
|
26
|
+
* @param member - the member about to be removed.
|
|
27
|
+
* @returns the impact summary.
|
|
28
|
+
*/
|
|
29
|
+
export declare function removalImpact(state: RoomState, member: RoomMember): RemovalImpact;
|
|
30
|
+
/** The confirmation layer. */
|
|
31
|
+
export declare function RemoveMemberConfirm({ member, impact, coordinator, onConfirm, onCancel, t, }: {
|
|
32
|
+
readonly member: RoomMember;
|
|
33
|
+
readonly impact: RemovalImpact;
|
|
34
|
+
/** True when this member is the current coordinator (not removable). */
|
|
35
|
+
readonly coordinator: boolean;
|
|
36
|
+
readonly onConfirm: () => Promise<void>;
|
|
37
|
+
readonly onCancel: () => void;
|
|
38
|
+
readonly t: MembersViewProps['t'];
|
|
39
|
+
}): ReactNode;
|
|
40
|
+
//# sourceMappingURL=RemoveMemberConfirm.d.ts.map
|