@khorsheed/dsh-room 0.1.0

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 (95) hide show
  1. package/CHANGELOG.md +9 -0
  2. package/README.i18n.yaml +6 -0
  3. package/README.md +48 -0
  4. package/README.zh.md +48 -0
  5. package/cordis.patch.yml +6 -0
  6. package/lib/client.js +11481 -0
  7. package/lib/index.js +3231 -0
  8. package/lib/invariant.js +26 -0
  9. package/lib/journal-CuRnWk66.js +435 -0
  10. package/lib/tool.js +350 -0
  11. package/lib/typert.host.d.ts +3 -0
  12. package/lib/typert.host.js +2615 -0
  13. package/lib/typert.remote-client.d.ts +55 -0
  14. package/lib/typert.remote-client.js +1711 -0
  15. package/lib/types/adapter.d.ts +42 -0
  16. package/lib/types/adapter.js +41 -0
  17. package/lib/types/agent-setup.d.ts +141 -0
  18. package/lib/types/agent-setup.js +145 -0
  19. package/lib/types/client/InviteAgentAction.d.ts +13 -0
  20. package/lib/types/client/InviteAgentAction.js +45 -0
  21. package/lib/types/client/InviteDialog.d.ts +90 -0
  22. package/lib/types/client/InviteDialog.js +216 -0
  23. package/lib/types/client/MemberCard.d.ts +33 -0
  24. package/lib/types/client/MemberCard.js +32 -0
  25. package/lib/types/client/MembersView.d.ts +5 -0
  26. package/lib/types/client/MembersView.js +168 -0
  27. package/lib/types/client/RoomComposer.d.ts +21 -0
  28. package/lib/types/client/RoomComposer.js +211 -0
  29. package/lib/types/client/RoomDockCapsules.d.ts +64 -0
  30. package/lib/types/client/RoomDockCapsules.js +277 -0
  31. package/lib/types/client/RoomEventView.d.ts +11 -0
  32. package/lib/types/client/RoomEventView.js +17 -0
  33. package/lib/types/client/RoomModelPicker.d.ts +18 -0
  34. package/lib/types/client/RoomModelPicker.js +134 -0
  35. package/lib/types/client/RoomPlanView.d.ts +17 -0
  36. package/lib/types/client/RoomPlanView.js +88 -0
  37. package/lib/types/client/RoomQueueStrip.d.ts +26 -0
  38. package/lib/types/client/RoomQueueStrip.js +28 -0
  39. package/lib/types/client/RoomRecoveryView.d.ts +11 -0
  40. package/lib/types/client/RoomRecoveryView.js +38 -0
  41. package/lib/types/client/RoomRelayView.d.ts +12 -0
  42. package/lib/types/client/RoomRelayView.js +20 -0
  43. package/lib/types/client/RoomRunView.d.ts +15 -0
  44. package/lib/types/client/RoomRunView.js +50 -0
  45. package/lib/types/client/RoomSpeechView.d.ts +18 -0
  46. package/lib/types/client/RoomSpeechView.js +62 -0
  47. package/lib/types/client/RoomStatsLine.d.ts +61 -0
  48. package/lib/types/client/RoomStatsLine.js +138 -0
  49. package/lib/types/client/RoomTaskLineView.d.ts +13 -0
  50. package/lib/types/client/RoomTaskLineView.js +21 -0
  51. package/lib/types/client/RoomTodoStrip.d.ts +24 -0
  52. package/lib/types/client/RoomTodoStrip.js +64 -0
  53. package/lib/types/client/format.d.ts +31 -0
  54. package/lib/types/client/format.js +48 -0
  55. package/lib/types/client/icons.d.ts +27 -0
  56. package/lib/types/client/icons.js +11 -0
  57. package/lib/types/client/index.d.ts +39 -0
  58. package/lib/types/client/index.js +471 -0
  59. package/lib/types/client/locales.d.ts +422 -0
  60. package/lib/types/client/locales.js +413 -0
  61. package/lib/types/client/member-color.d.ts +13 -0
  62. package/lib/types/client/member-color.js +22 -0
  63. package/lib/types/client/name-pool.d.ts +17 -0
  64. package/lib/types/client/name-pool.js +25 -0
  65. package/lib/types/client/nodes.d.ts +136 -0
  66. package/lib/types/client/nodes.js +212 -0
  67. package/lib/types/client/preset-visibility.d.ts +91 -0
  68. package/lib/types/client/preset-visibility.js +113 -0
  69. package/lib/types/client/request-ids.d.ts +7 -0
  70. package/lib/types/client/request-ids.js +21 -0
  71. package/lib/types/client/room-store.d.ts +137 -0
  72. package/lib/types/client/room-store.js +244 -0
  73. package/lib/types/client/slots.d.ts +319 -0
  74. package/lib/types/client/slots.js +1 -0
  75. package/lib/types/deliveries.d.ts +5 -0
  76. package/lib/types/deliveries.js +20 -0
  77. package/lib/types/dispatch.d.ts +105 -0
  78. package/lib/types/dispatch.js +467 -0
  79. package/lib/types/index.d.ts +301 -0
  80. package/lib/types/index.js +1200 -0
  81. package/lib/types/invariant.d.ts +16 -0
  82. package/lib/types/invariant.js +24 -0
  83. package/lib/types/journal.d.ts +117 -0
  84. package/lib/types/journal.js +434 -0
  85. package/lib/types/plan-service.d.ts +34 -0
  86. package/lib/types/plan-service.js +266 -0
  87. package/lib/types/plan.d.ts +168 -0
  88. package/lib/types/plan.js +494 -0
  89. package/lib/types/tool.d.ts +56 -0
  90. package/lib/types/tool.js +389 -0
  91. package/lib/types/types.d.ts +813 -0
  92. package/lib/types/types.js +1 -0
  93. package/locale/en.json +6 -0
  94. package/locale/zh.json +6 -0
  95. package/package.json +131 -0
@@ -0,0 +1,216 @@
1
+ import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
2
+ /**
3
+ * The invite/edit dialog as a character-creation card: a self-drawn
4
+ * lightweight modal (overlay + centered card, Esc/overlay-click closes) with
5
+ * a live member-card preview beside the form — every keystroke re-renders
6
+ * the preview (the preview avatar stays neutral; the name-hash color joins
7
+ * once the member is seated), and a 🎲 dice button rolls a random
8
+ * game-flavored name from the pool (name-pool.ts), skipping names already
9
+ * on the roster. The chrome rides the official design tokens: the near-black
10
+ * button-primary-fill capsule is the one filled action, and no blue accent
11
+ * appears (see InviteDialog.module.css).
12
+ *
13
+ * Field order is primary-first: provider → 默认模型 → 称呼(+🎲) → 首个任务 →
14
+ * ▸ 高级设置 (role instructions + the member-level cwd, collapsed on invite —
15
+ * editing opens it, since editing IS changing those). The model field sits in
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.
19
+ * Older cores without the shared picker keep the legacy default/input fallback.
20
+ * The rest of the original
21
+ * contract is unchanged: logged-out providers grey with login guidance (an
22
+ * absent facade degrades the whole section to a hint), the name precheck
23
+ * plus the host's structured duplicate/invalid errors ride the inline error
24
+ * line, instructions ride the front of the member's FIRST task message
25
+ * (never a system-prompt channel), the cwd is a read-only display filled by
26
+ * the 浏览… button (the official `uiWorkspace.pickDirectory` call;
27
+ * empty = inherit the room session's cwd, shown as the placeholder), and
28
+ * edit mode diffs name/cwd/instructions/model.
29
+ */
30
+ import { useEffect, useRef, useState } from 'react';
31
+ import { IconChevronDownOutlineMedium } from './icons.js';
32
+ import { MemberCard } from './MemberCard.js';
33
+ import { rollName } from './name-pool.js';
34
+ import css from './InviteDialog.module.css';
35
+ /** The addressing-name grammar (host-validated too; this is the precheck). */
36
+ function validName(name) {
37
+ return name !== '' && !/\s/.test(name) && !name.includes('@');
38
+ }
39
+ /** The invite/edit modal card. */
40
+ export function InviteDialog({ mode, member, providers, localAgentAvailable, inheritedCwd, existingNames, browseDirectory, modelSurface, renderHarnessModelPicker, memberModel, memberConfiguration, onSubmit, onClose, t, }) {
41
+ const [provider, setProvider] = useState('');
42
+ const [name, setName] = useState(member?.name ?? '');
43
+ const [instructions, setInstructions] = useState(member?.instructions ?? '');
44
+ const [cwd, setCwd] = useState(member?.cwd ?? '');
45
+ const [firstTask, setFirstTask] = useState('');
46
+ // Edit mode opens on the journaled intent; the broker's effective model
47
+ // replaces it when the memberModel read below answers.
48
+ const [model, setModel] = useState(mode === 'edit' ? member?.model ?? '' : '');
49
+ const [modelBaseline, setModelBaseline] = useState(mode === 'edit' ? member?.model ?? '' : '');
50
+ /** The model row's surface: menu vocabulary + the default an unset field displays. */
51
+ const [surface, setSurface] = useState(undefined);
52
+ const [modelMenuOpen, setModelMenuOpen] = useState(false);
53
+ const modelFieldRef = useRef(null);
54
+ const [error, setError] = useState(null);
55
+ const [busy, setBusy] = useState(false);
56
+ const [browsing, setBrowsing] = useState(false);
57
+ // Invite keeps the advanced drawer folded; edit opens it — changing those
58
+ // fields is what the edit dialog is for.
59
+ const [advancedOpen, setAdvancedOpen] = useState(mode === 'edit');
60
+ useEffect(() => {
61
+ const onKey = (event) => {
62
+ if (event.key !== 'Escape')
63
+ return;
64
+ // An open model menu eats the first Esc; the next one closes the dialog.
65
+ if (modelMenuOpen) {
66
+ setModelMenuOpen(false);
67
+ return;
68
+ }
69
+ onClose();
70
+ };
71
+ document.addEventListener('keydown', onKey);
72
+ return () => { document.removeEventListener('keydown', onKey); };
73
+ }, [onClose, modelMenuOpen]);
74
+ // Click-outside closes the model menu (the settings cards' pattern).
75
+ useEffect(() => {
76
+ if (!modelMenuOpen)
77
+ return;
78
+ const onPointerDown = (event) => {
79
+ if (modelFieldRef.current?.contains(event.target) === true)
80
+ return;
81
+ setModelMenuOpen(false);
82
+ };
83
+ document.addEventListener('mousedown', onPointerDown);
84
+ return () => { document.removeEventListener('mousedown', onPointerDown); };
85
+ }, [modelMenuOpen]);
86
+ const authenticated = (providers ?? []).filter(entry => entry.authenticated);
87
+ const chosen = provider === '' ? (authenticated[0]?.provider ?? '') : provider;
88
+ const someLoggedOut = (providers ?? []).some(entry => !entry.authenticated);
89
+ /** The chosen provider's roster harness name (the harnessModel lookup key). */
90
+ const chosenHarness = (providers ?? []).find(entry => entry.provider === chosen)?.harness;
91
+ // The model surface follows the chosen provider: a harness without a
92
+ // broker read (or a composition without the family's client half) leaves
93
+ // the field a plain text input — blank still follows the harness default.
94
+ useEffect(() => {
95
+ setSurface(undefined);
96
+ if (mode !== 'invite' || modelSurface === undefined || chosenHarness === undefined)
97
+ return;
98
+ let cancelled = false;
99
+ void modelSurface(chosenHarness).then((result) => {
100
+ if (!cancelled) {
101
+ setSurface(result);
102
+ const saved = result?.settings;
103
+ if (saved)
104
+ setModel(current => current || saved);
105
+ }
106
+ }, () => { });
107
+ return () => { cancelled = true; };
108
+ }, [mode, modelSurface, chosenHarness]);
109
+ // Edit mode: the member's own model surface prefills the field (effective)
110
+ // and serves the menu (choices). null (no delegation record yet — a
111
+ // never-started member — or a brokerless harness) and undefined (RPC
112
+ // failure) answers keep the journaled intent as the baseline.
113
+ const editChild = mode === 'edit' ? member?.childSessionId : undefined;
114
+ useEffect(() => {
115
+ if (mode !== 'edit' || memberModel === undefined || editChild === undefined)
116
+ return;
117
+ let cancelled = false;
118
+ void memberModel(editChild).then((info) => {
119
+ if (cancelled || info === null || info === undefined)
120
+ return;
121
+ setModel(info.effective ?? '');
122
+ setModelBaseline(info.effective ?? '');
123
+ setSurface(info);
124
+ }, () => { });
125
+ return () => { cancelled = true; };
126
+ }, [mode, memberModel, editChild]);
127
+ // Edit mode: instructions may clear (empty = no preset injected); the name
128
+ // is the only hard requirement.
129
+ const canSubmit = mode === 'edit'
130
+ ? validName(name)
131
+ : localAgentAvailable && chosen !== '' && validName(name);
132
+ // The model row: the same control the provider settings cards use. An unset
133
+ // field DISPLAYS the provider's current default dimmed (inherited, never
134
+ // pinned); the chevron menu leads with the 默认 item (picking it clears the
135
+ // field back to follow-default) over the full unfiltered vocabulary.
136
+ const modelChoices = surface?.choices ?? [];
137
+ const surfaceDefault = surface?.effective ?? surface?.cliDefault;
138
+ const modelPlaceholder = mode === 'edit'
139
+ ? t('invite.modelPlaceholderEdit')
140
+ : surfaceDefault !== undefined && surfaceDefault !== ''
141
+ ? t('invite.modelPlaceholderDefault', { model: surfaceDefault })
142
+ : t('invite.modelPlaceholder');
143
+ const modelDefaultItem = mode === 'edit'
144
+ ? t('invite.modelDefaultItemEdit')
145
+ : surfaceDefault !== undefined && surfaceDefault !== ''
146
+ ? t('invite.modelDefaultItemNamed', { model: surfaceDefault })
147
+ : t('invite.modelDefaultItem');
148
+ const modelField = (_jsxs("label", { className: css.field, children: [_jsx("span", { className: css.label, children: t('invite.model') }), modelChoices.length > 0 ? (_jsxs("div", { className: css.modelField, ref: modelFieldRef, children: [_jsx("input", { className: css.modelInput, value: model, placeholder: modelPlaceholder, "aria-label": t('invite.model'), onChange: event => { setModel(event.target.value); } }), _jsx("button", { type: "button", className: css.modelMenuButton, "aria-label": t('invite.modelMenu'), "aria-haspopup": "menu", "aria-expanded": modelMenuOpen, onClick: () => { setModelMenuOpen(open => !open); }, children: _jsx(IconChevronDownOutlineMedium, { className: modelMenuOpen ? `${css.modelMenuChevron} ${css.modelMenuChevronOpen}` : css.modelMenuChevron }) }), modelMenuOpen && (_jsxs("div", { className: css.modelMenu, role: "menu", "aria-label": t('invite.modelMenu'), children: [_jsxs("button", { type: "button", role: "menuitemradio", "aria-checked": model.trim() === '', className: model.trim() === '' ? `${css.modelItemDefault} ${css.modelItemCurrent}` : css.modelItemDefault, onClick: () => { setModel(''); setModelMenuOpen(false); }, children: [_jsx("span", { className: css.modelItemLabel, children: modelDefaultItem }), _jsx("span", { className: css.modelItemCheck, "aria-hidden": true, children: model.trim() === '' ? '✓' : '' })] }), modelChoices.map(choice => (_jsxs("button", { type: "button", role: "menuitemradio", "aria-checked": choice === model.trim(), className: choice === model.trim() ? css.modelItemCurrent : css.modelItem, onClick: () => { setModel(choice); setModelMenuOpen(false); }, children: [_jsx("span", { className: css.modelItemLabel, children: choice }), _jsx("span", { className: css.modelItemCheck, "aria-hidden": true, children: choice === model.trim() ? '✓' : '' })] }, choice)))] }))] })) : (_jsx("input", { className: css.input, value: model, placeholder: modelPlaceholder, "aria-label": t('invite.model'), onChange: event => { setModel(event.target.value); } })), _jsx("span", { className: css.hint, children: mode === 'edit' ? t('invite.modelHintEdit') : t('invite.modelHint') })] }));
149
+ // The live preview: the form values as a roster card, always idle.
150
+ const previewName = name.trim() === '' ? t('invite.previewName') : name.trim();
151
+ const previewProvider = mode === 'edit'
152
+ ? member?.provider
153
+ : (providers ?? []).find(entry => entry.provider === chosen)?.displayName ?? chosen;
154
+ const previewMember = {
155
+ name: previewName,
156
+ kind: 'cli',
157
+ invitedBy: 'human',
158
+ ...previewProvider === undefined || previewProvider === '' ? {} : { provider: previewProvider },
159
+ ...instructions.trim() === '' ? {} : { instructions: instructions.trim() },
160
+ };
161
+ const rollDice = () => {
162
+ const rolled = rollName(existingNames, name);
163
+ if (rolled !== undefined)
164
+ setName(rolled);
165
+ };
166
+ const browse = async () => {
167
+ if (browsing)
168
+ return;
169
+ setBrowsing(true);
170
+ setError(null);
171
+ try {
172
+ const picked = await browseDirectory();
173
+ if (picked !== null)
174
+ setCwd(picked);
175
+ }
176
+ catch {
177
+ // No native picking capability on this host (or a transport failure):
178
+ // the field keeps its value and the hint explains the miss.
179
+ setError(t('invite.browseFailed'));
180
+ }
181
+ finally {
182
+ setBrowsing(false);
183
+ }
184
+ };
185
+ const submit = async () => {
186
+ if (!canSubmit || busy)
187
+ return;
188
+ if (!validName(name)) {
189
+ setError(t('invite.error.invalid'));
190
+ return;
191
+ }
192
+ setBusy(true);
193
+ setError(null);
194
+ try {
195
+ const outcome = await onSubmit({
196
+ provider: chosen, name, instructions: instructions.trim(), cwd: cwd.trim(), firstTask: firstTask.trim(),
197
+ model: model.trim(), modelBaseline,
198
+ });
199
+ if (!outcome.ok) {
200
+ setError(outcome.message);
201
+ return;
202
+ }
203
+ onClose();
204
+ }
205
+ finally {
206
+ setBusy(false);
207
+ }
208
+ };
209
+ const onCardKeyDown = (event) => {
210
+ event.stopPropagation();
211
+ };
212
+ return (_jsx("div", { className: css.overlay, role: "presentation", onMouseDown: (event) => {
213
+ if (event.target === event.currentTarget)
214
+ 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') })] })] }) }));
216
+ }
@@ -0,0 +1,33 @@
1
+ /**
2
+ * One roster card: identity, avatar block, role, status chip, actions.
3
+ * Extracted from MembersView so the invite/edit dialog can render the same
4
+ * card as its live preview — `preview` drops the action foot (the preview
5
+ * member has no session to jump to and nothing to edit or remove) and is
6
+ * always rendered idle (no run to report).
7
+ */
8
+ import { type ReactNode } from 'react';
9
+ import type { RoomMember, RoomMemberRun } from '../types.ts';
10
+ import type { MembersViewProps } from './slots.ts';
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, }: {
13
+ readonly coordinator?: boolean;
14
+ readonly onPromote?: (() => void) | undefined;
15
+ readonly member: RoomMember;
16
+ readonly run: RoomMemberRun | undefined;
17
+ /** Tick-driven elapsed for a running member (undefined otherwise). */
18
+ readonly elapsedMs: number | undefined;
19
+ readonly openSession: MembersViewProps['openSession'];
20
+ readonly onEdit: () => void;
21
+ readonly onRemove: () => void;
22
+ readonly t: MembersViewProps['t'];
23
+ /**
24
+ * The effective-model hint, appended to the provider/kind line (`kimi-cli ·
25
+ * kimi-code/k3`): the member's memberModel read (CLI) or the session's
26
+ * official directory selection (main agent). Absent = no hint (pre-broker
27
+ * behavior).
28
+ */
29
+ readonly modelHint?: ReactNode;
30
+ /** True = the dialog's live preview: no action foot. */
31
+ readonly preview?: boolean;
32
+ }): ReactNode;
33
+ //# sourceMappingURL=MemberCard.d.ts.map
@@ -0,0 +1,32 @@
1
+ import { Fragment as _Fragment, jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
2
+ /**
3
+ * One roster card: identity, avatar block, role, status chip, actions.
4
+ * Extracted from MembersView so the invite/edit dialog can render the same
5
+ * card as its live preview — `preview` drops the action foot (the preview
6
+ * member has no session to jump to and nothing to edit or remove) and is
7
+ * always rendered idle (no run to report).
8
+ */
9
+ import { useEffect, useRef, useState } from 'react';
10
+ import { formatDurationMs } from './format.js';
11
+ import { memberColor } from './member-color.js';
12
+ import css from './MembersView.module.css';
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, }) {
15
+ const [expanded, setExpanded] = useState(false);
16
+ const [overflowing, setOverflowing] = useState(false);
17
+ const instructionsRef = useRef(null);
18
+ useEffect(() => {
19
+ if (expanded)
20
+ return;
21
+ const element = instructionsRef.current;
22
+ if (element !== null)
23
+ setOverflowing(element.scrollHeight > element.clientHeight + 1);
24
+ }, [member.instructions, expanded]);
25
+ const color = memberColor(member.name);
26
+ const child = member.childSessionId;
27
+ const running = run?.state === 'running';
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') })] }))] }))] }));
32
+ }
@@ -0,0 +1,5 @@
1
+ import { type ReactNode } from 'react';
2
+ import type { MembersViewProps } from './slots.ts';
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;
5
+ //# sourceMappingURL=MembersView.d.ts.map
@@ -0,0 +1,168 @@
1
+ import { Fragment as _Fragment, jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
2
+ /**
3
+ * Members view: the room session's 成员 conversation-view tab. Owns
4
+ * everything member-related — a card grid (member-color avatar block with the
5
+ * uppercased name, provider/kind, clamped role instructions with an inline
6
+ * expander, status chip, per-card [轨迹→] [编辑] [移除]) and the dashed
7
+ * invite card. The running chip is the "去看看它在干什么" jump into the
8
+ * member's child session; interrupt deliberately does NOT live here — it
9
+ * belongs to the run surfaces (the chat-stream run row's stop, the member
10
+ * session's own Stop). Data rides the room store (refreshed on entering the
11
+ * tab and after every own mutation; the store's running-only poll keeps the
12
+ * elapsed honest). The main-agent member takes no instructions and cannot be
13
+ * removed — it is the room itself.
14
+ */
15
+ import { coordinatorMember } from '../journal.js';
16
+ import { useEffect, useState, useSyncExternalStore } from 'react';
17
+ import { Button } from '@deepseek-ai/dsh-client-ui-primitives';
18
+ import { IconAgentPresetOutlineMedium } from './icons.js';
19
+ import { MemberCard } from './MemberCard.js';
20
+ import { InviteDialog } from './InviteDialog.js';
21
+ import css from './MembersView.module.css';
22
+ /**
23
+ * The main-agent card's model hint: the session's current selection from the
24
+ * official per-session directory (the same instance the composer's model seat
25
+ * reads), null before the first load resolves.
26
+ */
27
+ function MainAgentModelHint({ directory }) {
28
+ const state = useSyncExternalStore(fn => directory.store.subscribe(fn), () => directory.store.getSnapshot());
29
+ if (state.current === null)
30
+ return null;
31
+ const choice = state.groups
32
+ .flatMap(group => group.models.map(model => ({ group, model })))
33
+ .find(entry => entry.group.id === state.current?.provider && entry.model.id === state.current.model);
34
+ return _jsx(_Fragment, { children: choice?.model.name ?? `${state.current.provider}/${state.current.model}` });
35
+ }
36
+ /** The members tab. */
37
+ export function MembersView({ sessionId, roomStore, roomCwd, openSession, removeMember, updateMember, invite, listProviders, browseDirectory, modelSurface, renderHarnessModelPicker, memberModel, setMemberModel, renderMemberConfiguration, modelDirectory, setCoordinator, t, }) {
38
+ // Entering the tab pulls the freshest state once.
39
+ useEffect(() => { void roomStore.refresh(sessionId); }, [roomStore, sessionId]);
40
+ const state = useSyncExternalStore(roomStore.subscribe, () => roomStore.getCached(sessionId));
41
+ const [dialog, setDialog] = useState(null);
42
+ const [providers, setProviders] = useState(undefined);
43
+ const [notice, setNotice] = useState(null);
44
+ const [error, setError] = useState(null);
45
+ /** The CLI members' effective models, keyed by member name (the cards' hints). */
46
+ const [memberModels, setMemberModels] = useState(new Map());
47
+ const anyRunning = state?.runs.some(run => run.state === 'running') ?? false;
48
+ const [, setTick] = useState(0);
49
+ useEffect(() => {
50
+ if (!anyRunning)
51
+ return undefined;
52
+ const timer = setInterval(() => { setTick(value => value + 1); }, 1000);
53
+ return () => { clearInterval(timer); };
54
+ }, [anyRunning]);
55
+ // The cards' model hints: one memberModel read per dispatched CLI member,
56
+ // refetched with the store (a switch through the member composer or the
57
+ // edit dialog lands on the next refresh). Null/absent answers render no
58
+ // hint — the pre-broker behavior exactly.
59
+ useEffect(() => {
60
+ if (memberModel === undefined)
61
+ return;
62
+ const fetchable = (state?.members ?? [])
63
+ .filter(member => member.kind === 'cli' && member.childSessionId !== undefined);
64
+ let cancelled = false;
65
+ void Promise.all(fetchable.map(async (member) => [member.name, await memberModel(member.childSessionId)])).then((results) => {
66
+ if (cancelled)
67
+ return;
68
+ const next = new Map();
69
+ for (const [name, info] of results) {
70
+ if (info === null || info === undefined)
71
+ continue;
72
+ next.set(name, info.effective ?? t('members.model.default'));
73
+ }
74
+ setMemberModels(next);
75
+ });
76
+ return () => { cancelled = true; };
77
+ }, [memberModel, state?.members, t]);
78
+ const flash = (message) => {
79
+ setNotice(message);
80
+ setTimeout(() => { setNotice(null); }, 3000);
81
+ };
82
+ const openInvite = () => {
83
+ setProviders(undefined);
84
+ setDialog({ mode: 'invite' });
85
+ void listProviders().then(setProviders);
86
+ };
87
+ const submitDialog = async (values) => {
88
+ if (dialog === null)
89
+ return { ok: false, message: '' };
90
+ if (dialog.mode === 'edit') {
91
+ // Diff against the member record: only changed fields ride the patch;
92
+ // an emptied cwd/instructions CLEARS (null) back to the inherit/no-
93
+ // preset state.
94
+ const member = dialog.member;
95
+ const name = values.name.trim();
96
+ const cwd = values.cwd.trim();
97
+ const instructions = values.instructions.trim();
98
+ const model = values.model.trim();
99
+ const modelChanged = model !== values.modelBaseline.trim() && !(member.childSessionId !== undefined && renderMemberConfiguration !== undefined);
100
+ if (modelChanged && member.childSessionId !== undefined) {
101
+ if (setMemberModel === undefined)
102
+ return { ok: false, message: t('invite.error.generic') };
103
+ // Compatibility entry also uses the core queue. No journaled model
104
+ // mutation for a started member: its durable owner is local-agent.
105
+ const result = await setMemberModel(member.childSessionId, model === '' ? undefined : model);
106
+ if (result === undefined)
107
+ return { ok: false, message: t('invite.error.generic') };
108
+ if (!result.ok)
109
+ return { ok: false, message: result.error };
110
+ }
111
+ const patch = {
112
+ ...name !== member.name ? { rename: name } : {},
113
+ ...cwd !== (member.cwd ?? '') ? { cwd: cwd === '' ? null : cwd } : {},
114
+ ...instructions !== (member.instructions ?? '')
115
+ ? { instructions: instructions === '' ? null : instructions }
116
+ : {},
117
+ // The journaled intent: the first dispatch binds it for a never-
118
+ // started member (the broker call above is a no-op there — no
119
+ // delegation record — so this value alone carries the edit).
120
+ ...modelChanged && member.childSessionId === undefined ? { model: model === '' ? null : model } : {},
121
+ };
122
+ return Object.keys(patch).length === 0 ? { ok: true } : updateMember(member.name, patch);
123
+ }
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
+ });
134
+ if (outcome.ok) {
135
+ flash(outcome.pendingFirstTask
136
+ ? t('invite.success.task', { member: values.name })
137
+ : t('invite.success.idle', { member: values.name }));
138
+ }
139
+ return outcome;
140
+ };
141
+ const remove = async (member) => {
142
+ if (!window.confirm(t('members.removeConfirm', { member: member.name })))
143
+ return;
144
+ const outcome = await removeMember(member.name);
145
+ if (!outcome.ok)
146
+ setError(outcome.message);
147
+ };
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 }));
149
+ // A plain session gets the centered guide state (the official empty-state
150
+ // shape: glyph → title → one-line explainer → primary action) instead of
151
+ // the bare "not a room": inviting an agent IS the promotion (the header
152
+ // action is its twin entry).
153
+ if (roomStore.isRoomCached(sessionId) === false) {
154
+ return (_jsxs("div", { className: css.guide, children: [_jsx("span", { className: css.guideIcon, "aria-hidden": true, children: _jsx(IconAgentPresetOutlineMedium, { size: 30 }) }), _jsx("p", { className: css.guideTitle, children: t('members.notRoomTitle') }), _jsx("p", { className: css.guideHint, children: t('members.notRoom') }), _jsx(Button, { variant: "primary", onClick: openInvite, children: t('members.notRoomAction') }), dialogNode] }));
155
+ }
156
+ if (state === undefined)
157
+ return null;
158
+ return (_jsxs("div", { className: css.members, children: [_jsxs("div", { className: css.grid, children: [state.members.map((member) => {
159
+ const run = state.runs.find(entry => entry.member === member.name);
160
+ const running = run?.state === 'running';
161
+ return (_jsx(MemberCard, { coordinator: coordinatorMember(state)?.name === member.name, onPromote: setCoordinator === undefined || member.id === undefined ? undefined : () => {
162
+ void setCoordinator(member.id, state.coordinator?.revision ?? 0).then(outcome => { if (!outcome.ok)
163
+ 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'
165
+ ? (modelDirectory !== undefined ? _jsx(MainAgentModelHint, { directory: modelDirectory }) : undefined)
166
+ : 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] }));
168
+ }
@@ -0,0 +1,21 @@
1
+ import { type ReactNode } from 'react';
2
+ import type { SessionId } from '@deepseek-ai/dsh-session/types';
3
+ import type { ComposerChainProps } from '@deepseek-ai/dsh-client-ui-conversation/client';
4
+ import type { RoomComposerMatch, RoomComposerProps } from './slots.ts';
5
+ /**
6
+ * Pure composer-chain selector: elect exactly the cached-room sessions.
7
+ * Pending interactions (questions/approvals) belong to the official
8
+ * ApprovalPanel — a chain entry at priority 1. Election runs ascending, so
9
+ * this -10 entry would shadow it: decline and let the interaction render
10
+ * (the member-channel rule, local-agent 84a2ed0). A cache miss declines too —
11
+ * the freshly opened room shows the official bar for the first pull's
12
+ * duration (accepted, see room-store.ts).
13
+ * @param owner - the composer chain currency dispatched by ConversationRoot.
14
+ * @param isRoomCached - the room store's cache probe (injected: the selector
15
+ * must stay a pure function of the owner props plus stable state reads).
16
+ * @returns the room match, or null to pass the election down the chain.
17
+ */
18
+ export declare function selectRoomComposer(owner: ComposerChainProps, isRoomCached: (sessionId: SessionId) => boolean): RoomComposerMatch | null;
19
+ /** The room composer takeover component. */
20
+ export declare function RoomComposer({ sessionId, roomStore, submit, stop, addTask, closeTask, setGoal, planCommand, openPlanSession, reconcileDelivery, roomCwd, invite, listProviders, listNames, browseDirectory, modelSurface, renderHarnessModelPicker, roomChrome, modelDirectory, renderMemberConfiguration, renderMemberInbox, stopMember, useSession, useProjection, t, }: RoomComposerProps): ReactNode;
21
+ //# sourceMappingURL=RoomComposer.d.ts.map