@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.
Files changed (109) hide show
  1. package/CHANGELOG.md +58 -0
  2. package/README.en.md +29 -62
  3. package/README.i18n.yaml +2 -2
  4. package/README.md +28 -61
  5. package/lib/client.js +14620 -5705
  6. package/lib/index.js +4069 -730
  7. package/lib/journal-B4FOGO6S.js +482 -0
  8. package/lib/journal-Rid9AAnD.js +485 -0
  9. package/lib/tool.js +112 -166
  10. package/lib/typert.host.js +2031 -374
  11. package/lib/typert.remote-client.d.ts +34 -10
  12. package/lib/typert.remote-client.js +1599 -210
  13. package/lib/types/agent-setup.d.ts +1 -1
  14. package/lib/types/client/InviteAgentAction.js +4 -16
  15. package/lib/types/client/InviteDialog.d.ts +28 -6
  16. package/lib/types/client/InviteDialog.js +76 -12
  17. package/lib/types/client/MemberCard.d.ts +5 -1
  18. package/lib/types/client/MemberCard.js +4 -4
  19. package/lib/types/client/MembersView.d.ts +1 -1
  20. package/lib/types/client/MembersView.js +58 -22
  21. package/lib/types/client/RemoveMemberConfirm.d.ts +40 -0
  22. package/lib/types/client/RemoveMemberConfirm.js +85 -0
  23. package/lib/types/client/RoomApprovalView.d.ts +11 -0
  24. package/lib/types/client/RoomApprovalView.js +28 -0
  25. package/lib/types/client/RoomComposer.d.ts +1 -1
  26. package/lib/types/client/RoomComposer.js +195 -29
  27. package/lib/types/client/RoomDecisionView.d.ts +22 -0
  28. package/lib/types/client/RoomDecisionView.js +35 -0
  29. package/lib/types/client/RoomHelpView.d.ts +17 -0
  30. package/lib/types/client/RoomHelpView.js +56 -0
  31. package/lib/types/client/RoomInboxView.d.ts +10 -0
  32. package/lib/types/client/RoomInboxView.js +14 -0
  33. package/lib/types/client/RoomQuestionView.d.ts +13 -0
  34. package/lib/types/client/RoomQuestionView.js +61 -0
  35. package/lib/types/client/RoomRelayView.js +1 -1
  36. package/lib/types/client/RoomRunView.d.ts +3 -1
  37. package/lib/types/client/RoomRunView.js +12 -5
  38. package/lib/types/client/RoomSettingsView.d.ts +19 -0
  39. package/lib/types/client/RoomSettingsView.js +30 -0
  40. package/lib/types/client/RoomSpeechView.d.ts +22 -8
  41. package/lib/types/client/RoomSpeechView.js +94 -11
  42. package/lib/types/client/RoomStatsLine.d.ts +26 -5
  43. package/lib/types/client/RoomStatsLine.js +53 -7
  44. package/lib/types/client/RoomTaskCapsule.d.ts +19 -0
  45. package/lib/types/client/RoomTaskCapsule.js +37 -0
  46. package/lib/types/client/TasksTab.d.ts +41 -0
  47. package/lib/types/client/TasksTab.js +43 -0
  48. package/lib/types/client/TasksView.d.ts +44 -0
  49. package/lib/types/client/TasksView.js +149 -0
  50. package/lib/types/client/TeamView.d.ts +33 -0
  51. package/lib/types/client/TeamView.js +210 -0
  52. package/lib/types/client/at-menu.d.ts +127 -0
  53. package/lib/types/client/at-menu.js +173 -0
  54. package/lib/types/client/draft-chips.d.ts +67 -0
  55. package/lib/types/client/draft-chips.js +95 -0
  56. package/lib/types/client/executions.d.ts +0 -30
  57. package/lib/types/client/executions.js +4 -34
  58. package/lib/types/client/icons-local.d.ts +25 -0
  59. package/lib/types/client/icons-local.js +10 -0
  60. package/lib/types/client/icons.d.ts +2 -1
  61. package/lib/types/client/icons.js +1 -0
  62. package/lib/types/client/index.js +276 -72
  63. package/lib/types/client/locales.d.ts +482 -52
  64. package/lib/types/client/locales.js +490 -60
  65. package/lib/types/client/nodes.d.ts +112 -17
  66. package/lib/types/client/nodes.js +197 -26
  67. package/lib/types/client/slots.d.ts +117 -47
  68. package/lib/types/client/task-tag.d.ts +32 -0
  69. package/lib/types/client/task-tag.js +33 -0
  70. package/lib/types/client/tasks.d.ts +50 -0
  71. package/lib/types/client/tasks.js +82 -0
  72. package/lib/types/client/team.d.ts +73 -0
  73. package/lib/types/client/team.js +187 -0
  74. package/lib/types/decisions.d.ts +49 -0
  75. package/lib/types/decisions.js +101 -0
  76. package/lib/types/deliveries.d.ts +17 -1
  77. package/lib/types/deliveries.js +82 -6
  78. package/lib/types/dispatch.d.ts +102 -4
  79. package/lib/types/dispatch.js +498 -109
  80. package/lib/types/help.d.ts +137 -0
  81. package/lib/types/help.js +216 -0
  82. package/lib/types/helps.d.ts +26 -0
  83. package/lib/types/helps.js +47 -0
  84. package/lib/types/index.d.ts +292 -52
  85. package/lib/types/index.js +1421 -199
  86. package/lib/types/jobs.d.ts +22 -0
  87. package/lib/types/jobs.js +77 -0
  88. package/lib/types/journal.d.ts +17 -27
  89. package/lib/types/journal.js +120 -43
  90. package/lib/types/messaging.d.ts +65 -0
  91. package/lib/types/messaging.js +160 -0
  92. package/lib/types/plan-service.js +7 -1
  93. package/lib/types/report-gate.d.ts +81 -0
  94. package/lib/types/report-gate.js +89 -0
  95. package/lib/types/tool-definition.d.ts +43 -20
  96. package/lib/types/tool-definition.js +145 -212
  97. package/lib/types/tool.d.ts +2 -2
  98. package/lib/types/tool.js +3 -2
  99. package/lib/types/types.d.ts +755 -78
  100. package/locale/zh.json +1 -1
  101. package/package.json +3 -3
  102. package/lib/types/client/RoomActivityDock.d.ts +0 -19
  103. package/lib/types/client/RoomActivityDock.js +0 -105
  104. package/lib/types/client/RoomDockCapsules.d.ts +0 -64
  105. package/lib/types/client/RoomDockCapsules.js +0 -277
  106. package/lib/types/client/RoomPlanTab.d.ts +0 -15
  107. package/lib/types/client/RoomPlanTab.js +0 -13
  108. package/lib/types/client/RoomTaskLineView.d.ts +0 -13
  109. 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. Invites retain a saved provider choice, otherwise the
18
- * shared picker chooses the first candidate. Edits retain member configuration.
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. Invites retain a saved provider choice, otherwise the
18
- * shared picker chooses the first candidate. Edits retain member configuration.
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 chosen = provider === '' ? (authenticated[0]?.provider ?? '') : provider;
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 (mode !== 'invite' || modelSurface === undefined || chosenHarness === undefined)
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') }), _jsx("select", { className: css.select, value: chosen, onChange: event => { setProvider(event.target.value); setModel(''); }, children: (providers ?? []).map(entry => (_jsxs("option", { value: entry.provider, disabled: !entry.authenticated, children: [entry.displayName, entry.authenticated ? '' : t('invite.loggedOut')] }, entry.provider))) }), someLoggedOut && _jsx("span", { className: css.hint, children: t('invite.loginHint') })] }))), memberConfiguration ?? (chosenHarness !== undefined && (modelSurface === undefined || surface !== undefined) ? renderHarnessModelPicker?.(chosenHarness, { value: model, onChange: setModel, disabled: busy }) : 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') })] }), 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') })] })] })] })] }), _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') })] })] }) }));
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 === 'cli' && (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 === 'cli' && (_jsxs("span", { className: css.actions, children: [_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: css.action, disabled: coordinator, onClick: onRemove, children: t('members.remove') })] }))] }))] }));
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 modelChanged = model !== values.modelBaseline.trim() && !(member.childSessionId !== undefined && renderMemberConfiguration !== undefined);
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
- const dialogNode = dialog !== null && (_jsx(InviteDialog, { mode: dialog.mode, member: dialog.mode === 'edit' ? dialog.member : undefined, providers: providers?.providers, 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 }));
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: () => { setDialog({ mode: 'edit', member }); }, onRemove: () => { void remove(member); }, modelHint: member.kind === 'main-agent'
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