@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,211 @@
1
+ import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
2
+ /**
3
+ * Room composer: the `conversation.composer` chain takeover for room
4
+ * sessions. Replaces the official input bar while the selector holds (the
5
+ * official bar is display:none underneath), so it owns the whole interaction:
6
+ * a textarea (Enter sends, Shift+Enter newlines), a send button, and the
7
+ * @-completion menu listing ONLY existing roster members (pure addressing —
8
+ * invitation lives in the members tab). Addressing is the union of two
9
+ * channels: leading `@name` tokens (the host's parseMentions grammar,
10
+ * stripped from the dispatched text) and the mention menu's picks (a pick
11
+ * records explicit addressing wherever the `@name` sits in the sentence —
12
+ * the text is dispatched verbatim, the picks ride the postMessage request's
13
+ * `targets`). Hand-typed mid-sentence mentions without a menu pick stay
14
+ * prose: they address nobody. A bare message (neither channel) is NOT room
15
+ * business: it is released to the official submit
16
+ * path — the takeover writes the text into the session's official input
17
+ * machine (`inputActions.setDraft` + `inputActions.submit()`, the same entry
18
+ * the InputBar's Enter key drives), so it becomes an ordinary main-agent
19
+ * turn; sent while that turn runs, the machine's busy admission enqueues it
20
+ * exactly as the official bar's does. A structured rejection (unknown
21
+ * targets) shows as an inline error line.
22
+ *
23
+ * Taking over the chain inherits the official bar's environment duties (the
24
+ * member-channel rule, local-agent 84a2ed0): a pending interaction yields (the
25
+ * selector declines while `owner.pendingInteraction` is set, so the priority-1
26
+ * ApprovalPanel elects), and everything the hidden fallback tree carried is
27
+ * re-homed INTO this component — the main agent's turn Stop (the send circle
28
+ * swaps while `running`), the dock capsules (RoomDockCapsules), the main
29
+ * agent's todo strip (RoomTodoStrip, the `todos` projection), the queued-
30
+ * messages strip (RoomQueueStrip, the `inbox` projection — the 0.1.5 session
31
+ * snapshot's queue on the old line), the session
32
+ * stats row (RoomStatsLine below the card), and the main agent's model seat
33
+ * (RoomModelPicker over the official per-session ModelDirectory — the seat
34
+ * itself is single-owner and cannot be re-hosted, so the picker is our own
35
+ * trigger on the same directory; a host without ui-model-selection gets no
36
+ * picker).
37
+ */
38
+ import { coordinatorMember } from '../journal.js';
39
+ import { useRef, useState, useSyncExternalStore, } from 'react';
40
+ import { memberColor } from './member-color.js';
41
+ import { RoomStatsLine } from './RoomStatsLine.js';
42
+ import { RoomDockCapsules } from './RoomDockCapsules.js';
43
+ import { RoomTodoStrip } from './RoomTodoStrip.js';
44
+ import { RoomPlanView } from './RoomPlanView.js';
45
+ import { RoomQueueStrip } from './RoomQueueStrip.js';
46
+ import { RoomRecoveryView } from './RoomRecoveryView.js';
47
+ import { RoomModelPicker } from './RoomModelPicker.js';
48
+ import css from './RoomComposer.module.css';
49
+ const EMPTY_LEGACY_QUEUE = [];
50
+ const QUEUE_PREVIEW_CHARS = 200;
51
+ /** The queue row's flat preview: attachment blocks dropped, the rest joined and whitespace-collapsed, 200 chars max (the official QueueDock's rule). */
52
+ function previewOf(content) {
53
+ const flat = content
54
+ .filter(block => block.type !== 'image' && block.type !== 'file')
55
+ .map(block => (block.type === 'text' ? block.text : `[${block.type}]`))
56
+ .join(' ').replace(/\s+/g, ' ').trim();
57
+ const chars = Array.from(flat);
58
+ return chars.length > QUEUE_PREVIEW_CHARS ? `${chars.slice(0, QUEUE_PREVIEW_CHARS).join('')}…` : flat;
59
+ }
60
+ /**
61
+ * Detect an active mention being typed: the text before the caret ends with
62
+ * `@partial` whose `@` follows the line start or whitespace — a mid-sentence
63
+ * mention completes as happily as a leading one. `\S` covers CJK member
64
+ * names (K酱). A menu pick of one of these candidates is explicit addressing
65
+ * even though the host's parseMentions reads only leading tokens.
66
+ */
67
+ function detectMention(draft, caret) {
68
+ const head = draft.slice(0, caret);
69
+ const match = /(?:^|\s)@(\S*)$/.exec(head);
70
+ if (match === null)
71
+ return null;
72
+ return { query: match[1], start: caret - match[1].length - 1 };
73
+ }
74
+ /**
75
+ * Pure composer-chain selector: elect exactly the cached-room sessions.
76
+ * Pending interactions (questions/approvals) belong to the official
77
+ * ApprovalPanel — a chain entry at priority 1. Election runs ascending, so
78
+ * this -10 entry would shadow it: decline and let the interaction render
79
+ * (the member-channel rule, local-agent 84a2ed0). A cache miss declines too —
80
+ * the freshly opened room shows the official bar for the first pull's
81
+ * duration (accepted, see room-store.ts).
82
+ * @param owner - the composer chain currency dispatched by ConversationRoot.
83
+ * @param isRoomCached - the room store's cache probe (injected: the selector
84
+ * must stay a pure function of the owner props plus stable state reads).
85
+ * @returns the room match, or null to pass the election down the chain.
86
+ */
87
+ export function selectRoomComposer(owner, isRoomCached) {
88
+ if (owner.pendingInteraction !== undefined)
89
+ return null;
90
+ const sessionId = owner.sessionId ?? owner.session?.sessionId;
91
+ return sessionId !== undefined && isRoomCached(sessionId) ? { room: true } : null;
92
+ }
93
+ /** The room composer takeover component. */
94
+ export 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, }) {
95
+ const state = useSyncExternalStore(roomStore.subscribe, () => roomStore.getCached(sessionId));
96
+ const [draft, setDraft] = useState('');
97
+ const [busy, setBusy] = useState(false);
98
+ const [error, setError] = useState(null);
99
+ const [mention, setMention] = useState(null);
100
+ /** Menu-picked addressees: a pick is explicit addressing wherever the
101
+ `@name` sits (see the module header); cleared with the draft on send. */
102
+ const [picked, setPicked] = useState([]);
103
+ const areaRef = useRef(null);
104
+ const members = state?.members ?? [];
105
+ const candidates = mention === null
106
+ ? []
107
+ : members.filter(member => member.name.startsWith(mention.query));
108
+ // The inherited environment state: the room's own main-agent turn flag
109
+ // (drives the Send/Stop swap) and the still-queued inbox rows (the official
110
+ // queue dock's data, re-homed as a read-only strip). alpha.2 moved the
111
+ // queue off the session snapshot into the `inbox` projection; 0.1.5 serves
112
+ // no `inbox` key — the undefined read falls back to the legacy snapshot
113
+ // queue.
114
+ const nativeRunning = useSession(snapshot => snapshot.running) ?? false;
115
+ const coordinator = state === undefined ? undefined : coordinatorMember(state);
116
+ const external = coordinator?.kind === 'cli';
117
+ const running = external ? state?.runs.some(run => run.member === coordinator.name && run.state === 'running') ?? false : nativeRunning;
118
+ const legacyQueue = useSession(snapshot => snapshot.queue)
119
+ ?? EMPTY_LEGACY_QUEUE;
120
+ const inbox = (useProjection === undefined ? undefined : useProjection('inbox'));
121
+ const nextTurn = inbox?.['next-turn'];
122
+ const queued = nextTurn === undefined
123
+ ? legacyQueue
124
+ .filter(row => row.placement === 'queued')
125
+ .map(row => ({ id: String(row.id), preview: row.preview }))
126
+ : nextTurn.map(row => ({ id: String(row.id), preview: previewOf(row.content) }));
127
+ const resize = (area) => {
128
+ area.style.height = 'auto';
129
+ area.style.height = `${area.scrollHeight}px`;
130
+ };
131
+ const onChange = (event) => {
132
+ const value = event.target.value;
133
+ setDraft(value);
134
+ resize(event.target);
135
+ const active = detectMention(value, event.target.selectionStart ?? value.length);
136
+ setMention(active === null ? null : { ...active, index: 0 });
137
+ };
138
+ const applyMention = (name) => {
139
+ if (mention === null)
140
+ return;
141
+ const caret = areaRef.current?.selectionStart ?? draft.length;
142
+ const next = `${draft.slice(0, mention.start)}@${name} ${draft.slice(caret)}`;
143
+ setDraft(next);
144
+ setMention(null);
145
+ setPicked(picked.includes(name) ? picked : [...picked, name]);
146
+ };
147
+ const send = async () => {
148
+ const text = draft.trim();
149
+ if (text === '' || busy)
150
+ return;
151
+ setBusy(true);
152
+ setError(null);
153
+ try {
154
+ // Two-argument form while nothing was menu-picked: the injected face's
155
+ // third parameter is the pick channel only.
156
+ const outcome = picked.length > 0
157
+ ? await submit(sessionId, text, picked)
158
+ : await submit(sessionId, text);
159
+ if (!outcome.ok) {
160
+ setError(outcome.message);
161
+ return;
162
+ }
163
+ setDraft('');
164
+ setMention(null);
165
+ setPicked([]);
166
+ }
167
+ catch {
168
+ setError(t('composer.error.generic'));
169
+ }
170
+ finally {
171
+ setBusy(false);
172
+ }
173
+ };
174
+ const onKeyDown = (event) => {
175
+ if (mention !== null) {
176
+ if (event.key === 'Escape') {
177
+ event.preventDefault();
178
+ setMention(null);
179
+ return;
180
+ }
181
+ if (candidates.length > 0) {
182
+ if (event.key === 'ArrowDown') {
183
+ event.preventDefault();
184
+ setMention({ ...mention, index: (mention.index + 1) % candidates.length });
185
+ return;
186
+ }
187
+ if (event.key === 'ArrowUp') {
188
+ event.preventDefault();
189
+ setMention({ ...mention, index: (mention.index + candidates.length - 1) % candidates.length });
190
+ return;
191
+ }
192
+ if (event.key === 'Enter' || event.key === 'Tab') {
193
+ event.preventDefault();
194
+ applyMention(candidates[mention.index].name);
195
+ return;
196
+ }
197
+ }
198
+ }
199
+ if (event.key === 'Enter' && !event.shiftKey) {
200
+ event.preventDefault();
201
+ void send();
202
+ }
203
+ };
204
+ return (_jsxs("div", { className: css.root, children: [_jsxs("div", { className: css.dock, children: [!external && useProjection !== undefined && _jsx(RoomTodoStrip, { useProjection: useProjection, t: t }), _jsx(RoomDockCapsules, { sessionId: sessionId, roomStore: roomStore, addTask: addTask, closeTask: closeTask, setGoal: setGoal, formalPlans: planCommand !== undefined, roomCwd: roomCwd, invite: invite, listProviders: listProviders, listNames: listNames, browseDirectory: browseDirectory, modelSurface: modelSurface, renderHarnessModelPicker: renderHarnessModelPicker, roomChrome: roomChrome, t: t }), state !== undefined && planCommand !== undefined && _jsx(RoomPlanView, { plan: state.plan, members: state.members, command: planCommand, openSession: openPlanSession, stopMember: stopMember, t: t }), state !== undefined && reconcileDelivery !== undefined && _jsx(RoomRecoveryView, { deliveries: state.deliveries ?? [], members: state.members, reconcile: reconcileDelivery, openSession: openPlanSession, t: t }), _jsx(RoomQueueStrip, { items: [...external ? [] : queued, ...(state?.deliveries ?? []).filter(row => row.memberId === coordinator?.id && (row.status === 'queued' || row.status === 'uncertain')).map(row => ({ id: row.id, preview: row.status === 'uncertain' ? `${row.error ?? 'Outcome unknown'}: ${row.text}` : row.text }))], t: t })] }), error !== null && _jsx("div", { className: css.error, role: "alert", children: error }), external && coordinator.childSessionId !== undefined && renderMemberInbox?.(coordinator.childSessionId), _jsxs("div", { className: css.card, children: [_jsxs("div", { className: css.coordinator, children: [t('coordinator.label'), " \u00B7 ", coordinator?.name ?? 'dsh', external ? ` · ${coordinator.provider}` : ' · DSH'] }), mention !== null && candidates.length > 0 && (_jsx("ul", { className: css.menu, role: "listbox", "aria-label": "members", children: candidates.map((member, index) => (_jsx("li", { children: _jsxs("button", { type: "button", role: "option", "aria-selected": index === mention.index, className: index === mention.index ? css.itemActive : css.item, onMouseDown: (event) => {
205
+ event.preventDefault();
206
+ applyMention(member.name);
207
+ }, children: [_jsx("span", { className: css.dot, style: { background: memberColor(member.name) }, "aria-hidden": true }), _jsx("span", { children: member.name }), _jsx("span", { className: css.hint, children: member.kind === 'main-agent' ? t('member.kind.main') : member.provider ?? '' })] }) }, member.name))) })), _jsx("textarea", { ref: areaRef, className: css.input, rows: 2, value: draft, placeholder: t('composer.placeholder'), onChange: onChange, onKeyDown: onKeyDown }), _jsxs("div", { className: css.row, children: [external && coordinator.childSessionId !== undefined && renderMemberConfiguration?.(coordinator.childSessionId), !external && modelDirectory !== undefined && (_jsx(RoomModelPicker, { directory: modelDirectory, onError: setError, t: t })), running && (_jsx("button", { type: "button", className: css.primary, "aria-label": t('composer.stop'), onMouseDown: (event) => { event.preventDefault(); }, onClick: () => { if (external)
208
+ stopMember?.(coordinator.name);
209
+ else
210
+ stop(); }, children: _jsx("svg", { viewBox: "0 0 16 16", width: "16", height: "16", "aria-hidden": true, children: _jsx("rect", { x: "3", y: "3", width: "10", height: "10", rx: "3", fill: "currentColor" }) }) })), _jsx("button", { type: "button", className: css.primary, "aria-label": t('composer.send'), disabled: busy || draft.trim() === '', onMouseDown: (event) => { event.preventDefault(); }, onClick: () => { void send(); }, children: _jsx("svg", { viewBox: "0 0 16 16", width: "16", height: "16", "aria-hidden": true, children: _jsx("path", { d: "M8.3125 0.980183C8.66767 1.0531 8.97902 1.20418 9.2627 1.43233C9.48724 1.61297 9.73029 1.85793 9.97949 2.10714L14.707 6.83468L13.293 8.24874L9 3.95577V15.0417H7V3.95577L2.70703 8.24874L1.29297 6.83468L6.02051 2.10714C6.26971 1.85793 6.51277 1.61297 6.7373 1.43233C6.97662 1.23986 7.28445 1.04402 7.6875 0.980183C7.8973 0.947006 8.1031 0.95516 8.3125 0.980183Z", fill: "currentColor" }) }) })] })] }), !external && useProjection !== undefined && _jsx(RoomStatsLine, { useProjection: useProjection, t: t })] }));
211
+ }
@@ -0,0 +1,64 @@
1
+ /**
2
+ * The room dock capsules, rendered by the RoomComposer itself above the input
3
+ * card — the seat of the old task-board strip (NOT a `conversation.input.dock`
4
+ * entry: that slot lives inside the official composer fallback, which the
5
+ * room's composer takeover hides). One collapsed row of two capsules plus a
6
+ * quick-add button:
7
+ *
8
+ * ```
9
+ * [◔33% 插件API v2上线] [☑ 当前进度 1/3 · ●ada 在做] +
10
+ * ```
11
+ *
12
+ * The row is ANCHORED just above the input card: an expanded panel renders
13
+ * above the capsule row (top-to-bottom: panel → capsules → input card) and
14
+ * grows upward only, so folding/unfolding never shifts the row under the
15
+ * user's pointer.
16
+ *
17
+ * The GOAL capsule's only visual focus is the progress ring: an SVG circle
18
+ * (tertiary track + business-primary progress arc) followed by the percent
19
+ * and the truncated goal text; without a goal it renders the 「+ 设定目标」
20
+ * guide state (no ring). It expands into the goal card: the full text with an
21
+ * inline [编辑], a progress bar with the done/total fraction, and the recent
22
+ * advance records (who finished what, relative time).
23
+ *
24
+ * The TASK capsule leads with the checklist icon (IconChecklistOutlineMedium from
25
+ * the official primitives — never a Unicode glyph), then the open-task count
26
+ * and the members currently running (color dot + name). It expands into the
27
+ * Linear-style task panel: a member filter chip row (color-dot capsules, the
28
+ * selected one takes a tinted fill of the member color — no outline) with the
29
+ * +添加 trigger at its right end, small-caps tertiary group headers, and task
30
+ * rows (colored status icon — business-primary half-ring for in_progress,
31
+ * tertiary empty ring for pending, filled check for done, filled error × for
32
+ * failed — + title + member chip + right-aligned tertiary status/relative
33
+ * time + a row-end [完成]; a failed row's action is [关闭], dismissing to
34
+ * cancelled — the run did not finish, so it never closes done).
35
+ * Closing a task plays a restrained completion beat (the check pops, the
36
+ * title's strike draws in, <300ms, reduced-motion safe) — the row remounts on
37
+ * a status change (`key = id:status`) so the CSS animation replays. A blocked
38
+ * row greys with a 「等 ada」 tag until its `blockedBy` member has no open
39
+ * task left (display only: nothing auto-dispatches). The trailing + opens
40
+ * the panel with the add form already expanded (member select + title +
41
+ * optional "等谁"). Panels close on a second capsule click or a click
42
+ * outside; no overlay.
43
+ *
44
+ * Visual layer: the capsules ride the official chip register (28px, r24,
45
+ * interactive hover fill, 13/20/500 label) with a visible outline (the
46
+ * InputBar card's `l2-darkmode-thin` pair, firming to `l3` on hover/expand);
47
+ * the expanded cards are the
48
+ * official Menu surface (`--dsw-specific-menu`, r12, inverted hairline,
49
+ * shadow-lv3); the task capsule sweeps a restrained glare band while tasks
50
+ * run (the ToolRow pattern, reduced-motion safe). Every token reference
51
+ * carries the light-theme literal as fallback; no divider lines anywhere.
52
+ *
53
+ * FRESH-ROOM STATE: a room with neither a goal nor any task (membership does
54
+ * not count — the main agent is always there) has no use for the guide/task
55
+ * pair, which would be pure noise. The collapsed row then renders a single
56
+ * 「+ 邀请成员」 capsule (same chip family) that opens the invite dialog
57
+ * straight from the dock. The moment a goal is set or a task exists the pair
58
+ * returns, and the invite entry retires — the members tab already carries it.
59
+ */
60
+ import { type ReactNode } from 'react';
61
+ import type { RoomDockCapsulesProps } from './slots.ts';
62
+ /** The dock capsules (goal + tasks) with their expanded cards. */
63
+ export declare function RoomDockCapsules({ sessionId, roomStore, addTask, closeTask, setGoal, formalPlans, roomCwd, invite, listProviders, browseDirectory, modelSurface, renderHarnessModelPicker, t, }: RoomDockCapsulesProps): ReactNode;
64
+ //# sourceMappingURL=RoomDockCapsules.d.ts.map
@@ -0,0 +1,277 @@
1
+ import { Fragment as _Fragment, jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
2
+ /**
3
+ * The room dock capsules, rendered by the RoomComposer itself above the input
4
+ * card — the seat of the old task-board strip (NOT a `conversation.input.dock`
5
+ * entry: that slot lives inside the official composer fallback, which the
6
+ * room's composer takeover hides). One collapsed row of two capsules plus a
7
+ * quick-add button:
8
+ *
9
+ * ```
10
+ * [◔33% 插件API v2上线] [☑ 当前进度 1/3 · ●ada 在做] +
11
+ * ```
12
+ *
13
+ * The row is ANCHORED just above the input card: an expanded panel renders
14
+ * above the capsule row (top-to-bottom: panel → capsules → input card) and
15
+ * grows upward only, so folding/unfolding never shifts the row under the
16
+ * user's pointer.
17
+ *
18
+ * The GOAL capsule's only visual focus is the progress ring: an SVG circle
19
+ * (tertiary track + business-primary progress arc) followed by the percent
20
+ * and the truncated goal text; without a goal it renders the 「+ 设定目标」
21
+ * guide state (no ring). It expands into the goal card: the full text with an
22
+ * inline [编辑], a progress bar with the done/total fraction, and the recent
23
+ * advance records (who finished what, relative time).
24
+ *
25
+ * The TASK capsule leads with the checklist icon (IconChecklistOutlineMedium from
26
+ * the official primitives — never a Unicode glyph), then the open-task count
27
+ * and the members currently running (color dot + name). It expands into the
28
+ * Linear-style task panel: a member filter chip row (color-dot capsules, the
29
+ * selected one takes a tinted fill of the member color — no outline) with the
30
+ * +添加 trigger at its right end, small-caps tertiary group headers, and task
31
+ * rows (colored status icon — business-primary half-ring for in_progress,
32
+ * tertiary empty ring for pending, filled check for done, filled error × for
33
+ * failed — + title + member chip + right-aligned tertiary status/relative
34
+ * time + a row-end [完成]; a failed row's action is [关闭], dismissing to
35
+ * cancelled — the run did not finish, so it never closes done).
36
+ * Closing a task plays a restrained completion beat (the check pops, the
37
+ * title's strike draws in, <300ms, reduced-motion safe) — the row remounts on
38
+ * a status change (`key = id:status`) so the CSS animation replays. A blocked
39
+ * row greys with a 「等 ada」 tag until its `blockedBy` member has no open
40
+ * task left (display only: nothing auto-dispatches). The trailing + opens
41
+ * the panel with the add form already expanded (member select + title +
42
+ * optional "等谁"). Panels close on a second capsule click or a click
43
+ * outside; no overlay.
44
+ *
45
+ * Visual layer: the capsules ride the official chip register (28px, r24,
46
+ * interactive hover fill, 13/20/500 label) with a visible outline (the
47
+ * InputBar card's `l2-darkmode-thin` pair, firming to `l3` on hover/expand);
48
+ * the expanded cards are the
49
+ * official Menu surface (`--dsw-specific-menu`, r12, inverted hairline,
50
+ * shadow-lv3); the task capsule sweeps a restrained glare band while tasks
51
+ * run (the ToolRow pattern, reduced-motion safe). Every token reference
52
+ * carries the light-theme literal as fallback; no divider lines anywhere.
53
+ *
54
+ * FRESH-ROOM STATE: a room with neither a goal nor any task (membership does
55
+ * not count — the main agent is always there) has no use for the guide/task
56
+ * pair, which would be pure noise. The collapsed row then renders a single
57
+ * 「+ 邀请成员」 capsule (same chip family) that opens the invite dialog
58
+ * straight from the dock. The moment a goal is set or a task exists the pair
59
+ * returns, and the invite entry retires — the members tab already carries it.
60
+ */
61
+ import { useEffect, useRef, useState, useSyncExternalStore, } from 'react';
62
+ import { IconChecklistOutlineMedium } from './icons.js';
63
+ import { taskProgress } from '../journal.js';
64
+ import { formatRelativeTime } from './format.js';
65
+ import { memberColor } from './member-color.js';
66
+ import { InviteDialog } from './InviteDialog.js';
67
+ import css from './RoomDockCapsules.module.css';
68
+ /** Glyph tooltip text per status. */
69
+ function statusLabel(status, t) {
70
+ switch (status) {
71
+ case 'pending': return t('tasks.status.pending');
72
+ case 'in_progress': return t('tasks.status.in_progress');
73
+ case 'done': return t('tasks.status.done');
74
+ case 'cancelled': return t('tasks.status.cancelled');
75
+ case 'failed': return t('tasks.status.failed');
76
+ }
77
+ }
78
+ /* The Linear-style status icons: colored, 14px, centered in the 16px cell. */
79
+ /** Done: filled success circle, check cut in the card surface tone. */
80
+ function DoneGlyph() {
81
+ return (_jsxs("svg", { width: 14, height: 14, viewBox: "0 0 14 14", fill: "none", "aria-hidden": "true", className: css.glyphDone, children: [_jsx("circle", { cx: "7", cy: "7", r: "7", fill: "currentColor" }), _jsx("path", { d: "M4.2 7.3L6.2 9.3L9.8 5.2", stroke: "var(--dsw-specific-menu, var(--dsw-alias-bg-base, #ffffff))", strokeWidth: "1.4", strokeLinecap: "round", strokeLinejoin: "round" })] }));
82
+ }
83
+ /** In-progress: business-primary half ring; CSS spins the svg. */
84
+ function ProgressGlyph() {
85
+ return (_jsx("svg", { width: 14, height: 14, viewBox: "0 0 14 14", fill: "none", "aria-hidden": "true", className: css.glyphProgress, children: _jsx("circle", { cx: "7", cy: "7", r: "5.5", stroke: "currentColor", strokeWidth: "1.6", strokeLinecap: "round", strokeDasharray: `${Math.PI * 5.5} ${2 * Math.PI * 5.5}` }) }));
86
+ }
87
+ /** Pending: the tertiary empty ring. */
88
+ function PendingGlyph() {
89
+ return (_jsx("svg", { width: 14, height: 14, viewBox: "0 0 14 14", fill: "none", "aria-hidden": "true", className: css.glyphPending, children: _jsx("circle", { cx: "7", cy: "7", r: "5.5", stroke: "currentColor", strokeWidth: "1.4" }) }));
90
+ }
91
+ /** Cancelled: the pending ring in the dimmer caption tone. */
92
+ function CancelledGlyph() {
93
+ return (_jsx("svg", { width: 14, height: 14, viewBox: "0 0 14 14", fill: "none", "aria-hidden": "true", className: css.glyphCancelled, children: _jsx("circle", { cx: "7", cy: "7", r: "5.5", stroke: "currentColor", strokeWidth: "1.4" }) }));
94
+ }
95
+ /** Failed: filled error circle, × cut in the card surface tone (DoneGlyph's cutout idiom). */
96
+ function FailedGlyph() {
97
+ return (_jsxs("svg", { width: 14, height: 14, viewBox: "0 0 14 14", fill: "none", "aria-hidden": "true", className: css.glyphFailed, children: [_jsx("circle", { cx: "7", cy: "7", r: "7", fill: "currentColor" }), _jsx("path", { d: "M4.8 4.8L9.2 9.2M9.2 4.8L4.8 9.2", stroke: "var(--dsw-specific-menu, var(--dsw-alias-bg-base, #ffffff))", strokeWidth: "1.4", strokeLinecap: "round" })] }));
98
+ }
99
+ function StatusGlyph({ status }) {
100
+ switch (status) {
101
+ case 'done': return _jsx(DoneGlyph, {});
102
+ case 'in_progress': return _jsx(ProgressGlyph, {});
103
+ case 'pending': return _jsx(PendingGlyph, {});
104
+ case 'cancelled': return _jsx(CancelledGlyph, {});
105
+ case 'failed': return _jsx(FailedGlyph, {});
106
+ }
107
+ }
108
+ /**
109
+ * The goal capsule's progress ring — the collapsed row's single visual focus:
110
+ * a tertiary track arc plus the business-primary progress arc, -90° rotated
111
+ * so the arc starts at twelve o'clock.
112
+ */
113
+ function GoalRing({ fraction }) {
114
+ const clamped = Math.min(1, Math.max(0, fraction));
115
+ const R = 5.5;
116
+ const C = 2 * Math.PI * R;
117
+ return (_jsxs("svg", { width: 14, height: 14, viewBox: "0 0 14 14", fill: "none", "aria-hidden": "true", className: css.ring, children: [_jsx("circle", { cx: "7", cy: "7", r: R, className: css.ringTrack, strokeWidth: "2" }), _jsx("circle", { cx: "7", cy: "7", r: R, className: css.ringFill, strokeWidth: "2", strokeLinecap: "round", strokeDasharray: `${C * clamped} ${C}`, transform: "rotate(-90 7 7)" })] }));
118
+ }
119
+ /**
120
+ * A task is BLOCKED while the member its `blockedBy` names still has an open
121
+ * (pending/in_progress) task — the row greys until the wait is over. The task
122
+ * ITSELF never counts as its own blocker (a "等谁" pointing at the owner
123
+ * would otherwise grey forever). Display only: nothing auto-dispatches when
124
+ * it clears.
125
+ */
126
+ function isBlocked(task, tasks) {
127
+ return task.blockedBy !== undefined
128
+ && tasks.some(entry => entry.id !== task.id && entry.member === task.blockedBy
129
+ && (entry.status === 'pending' || entry.status === 'in_progress'));
130
+ }
131
+ /** How many recent advance records the goal card lists. */
132
+ const RECENT_ADVANCES = 5;
133
+ /** One task row of the task panel. */
134
+ function TaskRow({ task, tasks, now, closeTask, t }) {
135
+ const blocked = isBlocked(task, tasks);
136
+ const open = task.status === 'pending' || task.status === 'in_progress';
137
+ return (_jsxs("li", { className: css.item, "data-status": task.status, "data-blocked": blocked || undefined, children: [_jsx("span", { className: css.glyph, title: statusLabel(task.status, t), children: _jsx(StatusGlyph, { status: task.status }) }), _jsx("span", { className: css.content, children: _jsx("span", { className: css.title, children: task.title }) }), _jsxs("span", { className: css.memberChip, children: [_jsx("span", { className: css.memberDot, style: { background: memberColor(task.member) }, "aria-hidden": true }), task.member] }), blocked && _jsx("span", { className: css.blockedTag, children: t('tasks.blocked', { member: task.blockedBy ?? '' }) }), _jsxs("span", { className: css.meta, children: [statusLabel(task.status, t), " \u00B7 ", formatRelativeTime(task.updatedAt, now, t)] }), open && (_jsx("button", { type: "button", className: css.action, onClick: () => { void closeTask(task.id); }, children: t('tasks.close') })), task.status === 'failed' && (_jsx("button", { type: "button", className: css.action, onClick: () => { void closeTask(task.id, 'cancelled'); }, children: t('tasks.dismiss') }))] }));
138
+ }
139
+ /** The dock capsules (goal + tasks) with their expanded cards. */
140
+ export function RoomDockCapsules({ sessionId, roomStore, addTask, closeTask, setGoal, formalPlans = false, roomCwd, invite, listProviders, browseDirectory, modelSurface, renderHarnessModelPicker, t, }) {
141
+ const state = useSyncExternalStore(roomStore.subscribe, () => roomStore.getCached(sessionId));
142
+ const [open, setOpen] = useState(null);
143
+ const [addOpen, setAddOpen] = useState(false);
144
+ const [filter, setFilter] = useState('');
145
+ const [editingGoal, setEditingGoal] = useState(false);
146
+ const [goalDraft, setGoalDraft] = useState('');
147
+ const [draftTitle, setDraftTitle] = useState('');
148
+ const [draftMember, setDraftMember] = useState('');
149
+ const [draftBlockedBy, setDraftBlockedBy] = useState('');
150
+ const [error, setError] = useState(null);
151
+ const [inviteOpen, setInviteOpen] = useState(false);
152
+ const [providers, setProviders] = useState(undefined);
153
+ const rootRef = useRef(null);
154
+ // Collapse on any press outside the dock (no overlay; a second click on the
155
+ // capsule toggles too).
156
+ useEffect(() => {
157
+ if (open === null)
158
+ return;
159
+ const onPointerDown = (event) => {
160
+ if (rootRef.current !== null && event.target instanceof Node
161
+ && !rootRef.current.contains(event.target)) {
162
+ setOpen(null);
163
+ setAddOpen(false);
164
+ setEditingGoal(false);
165
+ }
166
+ };
167
+ document.addEventListener('mousedown', onPointerDown);
168
+ return () => { document.removeEventListener('mousedown', onPointerDown); };
169
+ }, [open]);
170
+ // A cache miss (first pull in flight) or a non-room: the dock vanishes.
171
+ if (state === undefined || roomStore.isRoomCached(sessionId) !== true)
172
+ return null;
173
+ // Fresh room (no goal, no tasks — membership does not count, the main
174
+ // agent is always there): the guide/task pair would be noise, so the row
175
+ // carries a single 「+ 邀请成员」 capsule that opens the invite dialog
176
+ // straight from the dock. The pair returns the moment a goal or a task
177
+ // exists; the invite entry then retires (the members tab carries it).
178
+ const fresh = state.goal === undefined && state.tasks.length === 0;
179
+ if (fresh) {
180
+ const openInvite = () => {
181
+ setProviders(undefined);
182
+ setInviteOpen(true);
183
+ void listProviders().then(setProviders);
184
+ };
185
+ const submitInvite = (values) => invite({
186
+ provider: values.provider,
187
+ name: values.name,
188
+ // Blank instructions are omitted, not sent: the host rejects a
189
+ // present-but-blank role, and an absent one simply carries no preset.
190
+ ...values.instructions === '' ? {} : { instructions: values.instructions },
191
+ ...values.cwd === '' ? {} : { cwd: values.cwd },
192
+ ...values.firstTask === '' ? {} : { firstTask: values.firstTask },
193
+ ...values.model === '' ? {} : { model: values.model },
194
+ });
195
+ return (_jsxs("section", { ref: rootRef, className: css.root, "aria-label": t('goal.label'), children: [_jsx("div", { className: css.capsules, children: _jsx("button", { type: "button", className: css.capsule, onClick: openInvite, children: _jsx("span", { className: css.capsuleGuide, children: t('members.invite') }) }) }), inviteOpen && (_jsx(InviteDialog, { mode: "invite", providers: providers?.providers, localAgentAvailable: providers?.localAgentAvailable ?? true, inheritedCwd: roomCwd, existingNames: state.members.map(entry => entry.name), browseDirectory: browseDirectory, modelSurface: modelSurface, renderHarnessModelPicker: renderHarnessModelPicker, onSubmit: submitInvite, onClose: () => { setInviteOpen(false); }, t: t }))] }));
196
+ }
197
+ const now = Date.now();
198
+ const progress = taskProgress(state.tasks);
199
+ const runners = [...new Set(state.tasks.filter(task => task.status === 'in_progress').map(task => task.member))];
200
+ const members = state.members;
201
+ const chosen = members.some(member => member.name === draftMember)
202
+ ? draftMember
203
+ : (members[0]?.name ?? '');
204
+ const advances = state.tasks
205
+ .filter(task => task.status === 'done')
206
+ .slice()
207
+ .sort((a, b) => b.updatedAt - a.updatedAt);
208
+ const fraction = progress.total === 0 ? 0 : progress.done / progress.total;
209
+ const toggle = (panel) => {
210
+ if (open === panel) {
211
+ setOpen(null);
212
+ setAddOpen(false);
213
+ setEditingGoal(false);
214
+ return;
215
+ }
216
+ setOpen(panel);
217
+ setAddOpen(false);
218
+ setEditingGoal(false);
219
+ setError(null);
220
+ if (panel === 'goal' && state.goal === undefined) {
221
+ setGoalDraft('');
222
+ setEditingGoal(true);
223
+ }
224
+ };
225
+ const saveGoal = async () => {
226
+ const outcome = await setGoal(goalDraft);
227
+ if (!outcome.ok) {
228
+ setError(outcome.message);
229
+ return;
230
+ }
231
+ setEditingGoal(false);
232
+ setError(null);
233
+ };
234
+ const add = async () => {
235
+ const title = draftTitle.trim();
236
+ if (title === '' || chosen === '')
237
+ return;
238
+ const outcome = await addTask(chosen, title, draftBlockedBy === '' ? undefined : draftBlockedBy);
239
+ if (!outcome.ok) {
240
+ setError(outcome.message);
241
+ return;
242
+ }
243
+ setDraftTitle('');
244
+ setDraftBlockedBy('');
245
+ setError(null);
246
+ };
247
+ const onAddKeyDown = (event) => {
248
+ if (event.key === 'Enter') {
249
+ event.preventDefault();
250
+ void add();
251
+ }
252
+ };
253
+ const visibleTasks = filter === ''
254
+ ? undefined
255
+ : state.tasks.filter(task => task.member === filter);
256
+ return (_jsxs("section", { ref: rootRef, className: css.root, "aria-label": t('goal.label'), children: [!formalPlans && open === 'goal' && (_jsxs("div", { className: css.card, children: [editingGoal ? (_jsxs("div", { className: css.goalEditRow, children: [_jsx("input", { className: css.input, value: goalDraft, placeholder: t('goal.placeholder'), onChange: event => { setGoalDraft(event.target.value); }, onKeyDown: (event) => {
257
+ if (event.key === 'Enter') {
258
+ event.preventDefault();
259
+ void saveGoal();
260
+ }
261
+ } }), _jsx("button", { type: "button", className: css.action, onClick: () => { void saveGoal(); }, children: t('goal.save') }), state.goal !== undefined && (_jsx("button", { type: "button", className: css.action, onClick: () => { setEditingGoal(false); }, children: t('goal.cancel') }))] })) : (_jsxs("div", { className: css.goalRow, children: [_jsx("span", { className: css.goalText, children: state.goal }), _jsx("button", { type: "button", className: css.action, onClick: () => {
262
+ setGoalDraft(state.goal ?? '');
263
+ setEditingGoal(true);
264
+ }, children: t('goal.edit') })] })), _jsxs("div", { className: css.progressRow, children: [_jsx("div", { className: css.progressTrack, role: "progressbar", "aria-valuemin": 0, "aria-valuemax": progress.total, "aria-valuenow": progress.done, children: _jsx("div", { className: css.progressFill, style: { width: `${fraction * 100}%` } }) }), _jsxs("span", { className: css.progressMeta, children: [progress.done, "/", progress.total] })] }), _jsx("div", { className: css.recentLabel, children: t('goal.recent') }), advances.length === 0 ? (_jsx("div", { className: css.meta, children: t('goal.noAdvances') })) : (_jsx("ul", { className: css.list, children: advances.slice(0, RECENT_ADVANCES).map(task => (_jsxs("li", { className: css.advanceRow, children: [t('advance.line', { member: task.member, title: task.title }), _jsxs("span", { className: css.meta, children: [" \u00B7 ", formatRelativeTime(task.updatedAt, now, t)] })] }, task.id))) })), error !== null && _jsx("div", { className: css.error, role: "alert", children: error })] })), open === 'tasks' && (_jsxs("div", { className: css.card, children: [_jsxs("div", { className: css.panelBar, children: [_jsxs("div", { className: css.chips, role: "group", "aria-label": t(formalPlans ? 'tasks.chat' : 'tasks.capsule'), children: [_jsx("button", { type: "button", className: css.chip, "data-active": filter === '' || undefined, onClick: () => { setFilter(''); }, children: t('tasks.filter.all') }), members.map(member => (_jsxs("button", { type: "button", className: css.chip, "data-active": filter === member.name || undefined, style: { '--room-chip-color': memberColor(member.name) }, onClick: () => { setFilter(member.name); }, children: [_jsx("span", { className: css.memberDot, style: { background: memberColor(member.name) }, "aria-hidden": true }), member.name] }, member.name)))] }), !addOpen && (_jsxs("button", { type: "button", className: css.addTrigger, onClick: () => { setAddOpen(true); }, children: ["\uFF0B ", t('tasks.addTrigger')] }))] }), _jsx("div", { className: css.groups, children: filter === ''
265
+ ? members.map((member) => {
266
+ const own = state.tasks.filter(task => task.member === member.name);
267
+ if (own.length === 0)
268
+ return null;
269
+ return (_jsxs("div", { className: css.group, children: [_jsx("div", { className: css.head, children: member.name }), _jsx("ul", { className: css.list, children: own.map(task => (_jsx(TaskRow, { task: task, tasks: state.tasks, now: now, closeTask: closeTask, t: t }, `${task.id}:${task.status}`))) })] }, member.name));
270
+ })
271
+ : (_jsx("ul", { className: css.list, children: (visibleTasks ?? []).map(task => (_jsx(TaskRow, { task: task, tasks: state.tasks, now: now, closeTask: closeTask, t: t }, `${task.id}:${task.status}`))) })) }), addOpen && (_jsxs("div", { className: css.addRow, children: [_jsx("select", { className: css.memberSelect, "aria-label": t('tasks.addMember'), value: chosen, onChange: event => { setDraftMember(event.target.value); }, children: members.map(member => (_jsx("option", { value: member.name, children: member.name }, member.name))) }), _jsx("input", { className: css.input, value: draftTitle, placeholder: t('tasks.addPlaceholder'), onChange: event => { setDraftTitle(event.target.value); }, onKeyDown: onAddKeyDown }), _jsxs("select", { className: css.memberSelect, "aria-label": t('tasks.addBlockedBy'), value: draftBlockedBy, onChange: event => { setDraftBlockedBy(event.target.value); }, children: [_jsx("option", { value: "", children: t('tasks.addBlockedByNone') }), members.map(member => (_jsx("option", { value: member.name, children: member.name }, member.name)))] }), _jsx("button", { type: "button", className: css.action, disabled: draftTitle.trim() === '' || chosen === '', onClick: () => { void add(); }, children: t('tasks.add') })] })), error !== null && _jsx("div", { className: css.error, role: "alert", children: error })] })), _jsxs("div", { className: css.capsules, children: [!formalPlans && _jsx("button", { type: "button", className: css.capsule, "aria-expanded": open === 'goal', "aria-label": t('goal.label'), onClick: () => { toggle('goal'); }, children: state.goal === undefined ? (_jsx("span", { className: css.capsuleGuide, children: t('goal.set') })) : (_jsxs(_Fragment, { children: [_jsx(GoalRing, { fraction: fraction }), _jsxs("span", { className: css.capsulePercent, children: [Math.round(fraction * 100), "%"] }), _jsx("span", { className: css.capsuleText, children: state.goal })] })) }), _jsxs("button", { type: "button", className: css.capsule, "aria-expanded": open === 'tasks', "aria-label": t(formalPlans ? 'tasks.chat' : 'tasks.capsule'), "data-running": runners.length > 0 || undefined, onClick: () => { toggle('tasks'); }, children: [_jsx(IconChecklistOutlineMedium, { size: 14, className: css.checklistIcon }), progress.total === 0 ? (_jsx("span", { className: css.capsuleText, children: t(formalPlans ? 'tasks.chat' : 'tasks.capsule') })) : (_jsxs(_Fragment, { children: [_jsx("span", { className: css.capsuleText, children: t(formalPlans ? 'tasks.chat' : 'tasks.capsule') }), _jsxs("span", { className: css.capsuleCount, children: [progress.done, "/", progress.total] }), runners.length > 0 && (_jsxs("span", { className: css.runners, children: [runners.map(name => (_jsxs("span", { className: css.runner, children: [_jsx("span", { className: css.memberDot, style: { background: memberColor(name) }, "aria-hidden": true }), name] }, name))), _jsx("span", { className: css.runnerSuffix, children: t('tasks.doing') })] }))] }))] }), _jsx("button", { type: "button", className: css.addCapsule, "aria-label": t('tasks.addTrigger'), onClick: () => {
272
+ setOpen('tasks');
273
+ setAddOpen(true);
274
+ setEditingGoal(false);
275
+ setError(null);
276
+ }, children: "\uFF0B" })] })] }));
277
+ }
@@ -0,0 +1,11 @@
1
+ /**
2
+ * Boundary node ('room-event' keyed renderer): one dim
3
+ * compaction-marker-style line per member join/leave. The human's own
4
+ * @-messages are NOT here — postMessage appends them as standard
5
+ * `user/message` events, so the official user bubble carries them.
6
+ */
7
+ import type { ReactNode } from 'react';
8
+ import type { RoomEventViewProps } from './slots.ts';
9
+ /** The boundary dim line. */
10
+ export declare function RoomEventView({ node, t }: RoomEventViewProps): ReactNode;
11
+ //# sourceMappingURL=RoomEventView.d.ts.map
@@ -0,0 +1,17 @@
1
+ import { jsx as _jsx } from "react/jsx-runtime";
2
+ import css from './RoomEventView.module.css';
3
+ /** The boundary dim line. */
4
+ export function RoomEventView({ node, t }) {
5
+ const data = node.data;
6
+ let text;
7
+ switch (data.sub) {
8
+ case 'member-added':
9
+ text = t('event.joined', { member: data.member ?? '', provider: data.provider ?? '' })
10
+ + (data.invitedBy === 'agent' ? t('event.joinedByAgent') : '');
11
+ break;
12
+ case 'member-removed':
13
+ text = t('event.left', { member: data.member ?? '' });
14
+ break;
15
+ }
16
+ return _jsx("div", { className: css.row, children: text });
17
+ }
@@ -0,0 +1,18 @@
1
+ import type { PropsLocale } from '@deepseek-ai/dsh-client-ui-slots';
2
+ import type { RoomModelDirectory } from './slots.ts';
3
+ interface RoomModelPickerProps {
4
+ /** The session's shared official directory (same instance the /model popup reads). */
5
+ readonly directory: RoomModelDirectory;
6
+ /** Selection failures ride the composer's inline error line. */
7
+ readonly onError: (message: string) => void;
8
+ readonly t: PropsLocale<'room'>['t'];
9
+ }
10
+ /**
11
+ * Render the main-agent model seat: trigger (current model name + effort in
12
+ * the caption tone + chevron) and, while open, the two-level menu.
13
+ * @param props - the shared directory, the error sink, and the locale seat.
14
+ * @returns the picker.
15
+ */
16
+ export declare function RoomModelPicker({ directory, onError, t }: RoomModelPickerProps): import("react").JSX.Element;
17
+ export {};
18
+ //# sourceMappingURL=RoomModelPicker.d.ts.map