@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,244 @@
1
+ /**
2
+ * The session the main view shows, read off both host lines: alpha.2 deleted
3
+ * the list's `current` for the per-row `retainedBy.mainView` reference count;
4
+ * 0.1.5 carries only `current`.
5
+ * @param list - the sessions list snapshot.
6
+ * @returns the main-view session id, or undefined when no session is shown.
7
+ */
8
+ export function mainSessionId(list) {
9
+ return Object.values(list.byId).find(summary => (summary.retainedBy?.mainView ?? 0) > 0)?.id
10
+ ?? list.current;
11
+ }
12
+ /** Poll cadence while the current room has running members. */
13
+ export const ROOM_POLL_INTERVAL_MS = 2_000;
14
+ /**
15
+ * Debounce coalescing the live-feed refresh: one room journal append usually
16
+ * arrives inside a burst of session events (the tool call, its result, the
17
+ * task/task-update edges), so the refresh waits for a quiet beat.
18
+ */
19
+ export const ROOM_LIVE_REFRESH_DEBOUNCE_MS = 300;
20
+ export class RoomStore {
21
+ ctx;
22
+ gateway;
23
+ room = new Map();
24
+ states = new Map();
25
+ listeners = new Set();
26
+ poller;
27
+ current;
28
+ /** The session the live feed is subscribed to (undefined = unattached). */
29
+ liveAttached;
30
+ liveUnsubscribe;
31
+ liveTimer;
32
+ /**
33
+ * @param ctx - client root context (the sessions list feed).
34
+ * @param gateway - the mounted room Remote, undefined when the mount failed.
35
+ */
36
+ constructor(ctx, gateway) {
37
+ this.ctx = ctx;
38
+ this.gateway = gateway;
39
+ }
40
+ /**
41
+ * Follow the current session: a new current room session gets its first
42
+ * pull and its live-feed subscription, and polling re-evaluates on every
43
+ * switch.
44
+ * @returns disposer (unsubscribe everything + stop polling).
45
+ */
46
+ start() {
47
+ const unsubscribe = this.ctx.sessions.list.subscribe(() => {
48
+ const current = mainSessionId(this.ctx.sessions.list.getSnapshot());
49
+ if (current === this.current)
50
+ return;
51
+ this.current = current;
52
+ if (current !== undefined)
53
+ void this.ensure(current);
54
+ this.attachLive();
55
+ this.adjustPolling();
56
+ });
57
+ const current = mainSessionId(this.ctx.sessions.list.getSnapshot());
58
+ if (current !== undefined) {
59
+ this.current = current;
60
+ void this.ensure(current);
61
+ this.attachLive();
62
+ }
63
+ return () => {
64
+ unsubscribe();
65
+ this.detachLive();
66
+ this.stopPolling();
67
+ };
68
+ }
69
+ /**
70
+ * The composer selector's synchronous read.
71
+ * @param sessionId - session to probe.
72
+ * @returns cached verdict, or undefined while the first pull is in flight.
73
+ */
74
+ isRoomCached(sessionId) {
75
+ return this.room.get(sessionId);
76
+ }
77
+ /**
78
+ * The cached room state (stable reference between refreshes — safe as a
79
+ * useSyncExternalStore snapshot).
80
+ * @param sessionId - room session.
81
+ * @returns the cached state, or undefined before the first successful pull.
82
+ */
83
+ getCached(sessionId) {
84
+ return this.states.get(sessionId);
85
+ }
86
+ /**
87
+ * Subscribe to cache changes.
88
+ * @param listener - called after any map mutation.
89
+ * @returns unsubscribe.
90
+ */
91
+ subscribe = (listener) => {
92
+ this.listeners.add(listener);
93
+ return () => {
94
+ this.listeners.delete(listener);
95
+ };
96
+ };
97
+ /**
98
+ * First pull for a session: isRoom, then getState when it is one.
99
+ * No-ops on a cached verdict.
100
+ * @param sessionId - session to ensure.
101
+ */
102
+ async ensure(sessionId) {
103
+ if (this.room.has(sessionId) || this.gateway === undefined)
104
+ return;
105
+ const carried = await this.gateway.isRoom({ sessionId });
106
+ if (!carried.ok)
107
+ return; // A transport failure is not a non-room verdict.
108
+ if (!carried.value) {
109
+ this.room.set(sessionId, false);
110
+ this.notify();
111
+ return;
112
+ }
113
+ await this.refresh(sessionId);
114
+ }
115
+ /**
116
+ * Re-pull the room state (after an own mutation, a live-feed nudge, or a
117
+ * poll tick).
118
+ * @param sessionId - room session.
119
+ */
120
+ async refresh(sessionId) {
121
+ if (this.gateway === undefined)
122
+ return;
123
+ const carried = await this.gateway.getState({ sessionId });
124
+ if (!carried.ok || !carried.value.ok)
125
+ return;
126
+ const was = this.room.get(sessionId);
127
+ this.room.set(sessionId, true);
128
+ this.states.set(sessionId, carried.value.value);
129
+ this.notify();
130
+ // A promotion flip (unknown/non-room → room) must re-elect the composer.
131
+ if (was !== true)
132
+ this.onPromoted?.();
133
+ // A completed pull is also the binding-availability retry point: the
134
+ // runtime may mint the session's binding after the list's current flip.
135
+ this.attachLive();
136
+ this.adjustPolling();
137
+ }
138
+ /**
139
+ * Subscribe the CURRENT session's live conversation feed so host-side
140
+ * journal appends nudge a refresh. The session binding is minted lazily by
141
+ * the runtime (it can lag the list's current flip), so every completed pull
142
+ * re-attempts the attach; an unattached store simply keeps its other
143
+ * triggers until one lands.
144
+ */
145
+ attachLive() {
146
+ if (this.liveAttached === this.current && this.liveUnsubscribe !== undefined)
147
+ return;
148
+ this.detachLive();
149
+ if (this.current === undefined)
150
+ return;
151
+ // Optional-call: the ISessions contract always provides binding, but a
152
+ // test double is allowed to omit it (no live feed in that bench).
153
+ const binding = this.ctx.sessions.binding?.(this.current);
154
+ // The session summary no longer notifies for arbitrary journal appends.
155
+ // Observe the public event window as a refresh signal, without folding it.
156
+ const face = binding?.eventSource ?? binding?.session;
157
+ if (face === undefined)
158
+ return;
159
+ this.liveUnsubscribe = face.subscribe(() => { this.onLiveEvent(); });
160
+ this.liveAttached = this.current;
161
+ }
162
+ /** Drop the live-feed subscription and any pending debounced refresh. */
163
+ detachLive() {
164
+ this.liveUnsubscribe?.();
165
+ this.liveUnsubscribe = undefined;
166
+ this.liveAttached = undefined;
167
+ if (this.liveTimer !== undefined)
168
+ clearTimeout(this.liveTimer);
169
+ this.liveTimer = undefined;
170
+ }
171
+ /**
172
+ * One live-feed nudge, debounced so an event burst costs one probe: a known
173
+ * room refreshes its state; a session cached NOT a room re-probes `isRoom`
174
+ * (a promotion lands as a host-side `room/created` append the client never
175
+ * initiated — the room_invite/room_message tools promote in place); a
176
+ * unknown verdict retries ensure after the same debounce.
177
+ */
178
+ onLiveEvent() {
179
+ if (this.current === undefined)
180
+ return;
181
+ if (this.liveTimer !== undefined)
182
+ clearTimeout(this.liveTimer);
183
+ this.liveTimer = setTimeout(() => {
184
+ this.liveTimer = undefined;
185
+ const current = this.current;
186
+ if (current === undefined)
187
+ return;
188
+ if (this.room.get(current) === true) {
189
+ void this.refresh(current);
190
+ }
191
+ else if (this.room.get(current) === false) {
192
+ void this.reprobe(current);
193
+ }
194
+ else {
195
+ void this.ensure(current);
196
+ }
197
+ }, ROOM_LIVE_REFRESH_DEBOUNCE_MS);
198
+ }
199
+ /**
200
+ * Re-probe a cached-non-room session's verdict: a promotion flips the cache
201
+ * and pulls the state (the composer takeover follows on the next notify).
202
+ * @param sessionId - the session whose verdict may have changed.
203
+ */
204
+ async reprobe(sessionId) {
205
+ if (this.gateway === undefined)
206
+ return;
207
+ const carried = await this.gateway.isRoom({ sessionId });
208
+ if (!carried.ok || !carried.value)
209
+ return;
210
+ // refresh flips the verdict and fires onPromoted on a unknown/false → true flip.
211
+ await this.refresh(sessionId);
212
+ }
213
+ notify() {
214
+ for (const listener of [...this.listeners])
215
+ listener();
216
+ }
217
+ /**
218
+ * Called when a session's verdict flips unknown/false → true (an in-place
219
+ * promotion): the composer chain elects at RENDER time and nothing
220
+ * re-renders the outlet for a promotion of an idle session, so the client
221
+ * wires this to re-register the composer entry (a slot version bump
222
+ * re-renders — and re-elects — the outlet).
223
+ */
224
+ onPromoted;
225
+ adjustPolling() {
226
+ const running = this.current !== undefined
227
+ && this.room.get(this.current) === true
228
+ && (this.states.get(this.current)?.runs.some(entry => entry.state === 'running') ?? false);
229
+ if (running && this.poller === undefined) {
230
+ this.poller = setInterval(() => {
231
+ if (this.current !== undefined)
232
+ void this.refresh(this.current);
233
+ }, ROOM_POLL_INTERVAL_MS);
234
+ }
235
+ else if (!running) {
236
+ this.stopPolling();
237
+ }
238
+ }
239
+ stopPolling() {
240
+ if (this.poller !== undefined)
241
+ clearInterval(this.poller);
242
+ this.poller = undefined;
243
+ }
244
+ }
@@ -0,0 +1,319 @@
1
+ /**
2
+ * Slot-facing types of the room client half: the injected action faces and
3
+ * the composed props of its slot entries.
4
+ */
5
+ import type { LocalAgentUi } from '@khorsheed/dsh-local-agent/client';
6
+ import type { ReactNode } from 'react';
7
+ import type { SessionId } from '@deepseek-ai/dsh-session/types';
8
+ import type { InjectFace, PropsLocale, PropsRuntime } from '@deepseek-ai/dsh-client-ui-slots';
9
+ import type { RoomProviderList } from '../types.ts';
10
+ import type { LocalAgentModelInfo, LocalAgentPromptResult } from '@khorsheed/dsh-local-agent/types';
11
+ import type { RoomChromeVisibility } from './preset-visibility.ts';
12
+ import type { RoomStore } from './room-store.ts';
13
+ /** A mutation outcome with a localized failure message. */
14
+ export type RoomMutationOutcome = {
15
+ readonly ok: true;
16
+ } | {
17
+ readonly ok: false;
18
+ readonly message: string;
19
+ };
20
+ /** Injected action face of the session-header 邀请 agent action. */
21
+ export type InviteAgentInjected = RoomInviteInjected;
22
+ /** Full props of the 'conversation.session.header.actions' entry. */
23
+ export type InviteAgentActionProps = PropsRuntime<'conversation.session.header.actions'> & InjectFace<InviteAgentInjected> & PropsLocale<'room'>;
24
+ /** Injected face of the dock capsules (goal capsule + task capsule). */
25
+ export interface RoomTasksInjected {
26
+ /** The client-side room state store (tasks and the goal ride the replayed state). */
27
+ readonly roomStore: RoomStore;
28
+ /** Add a pending task to a member's lane, optionally waiting on another member. */
29
+ readonly addTask: (member: string, title: string, blockedBy?: string) => Promise<RoomMutationOutcome>;
30
+ /**
31
+ * Close an open task. The status is the closing state: 'done' by default;
32
+ * the failed row's [关闭] passes 'cancelled' — the failure already speaks
33
+ * for itself, and a failed task must not count as goal progress.
34
+ */
35
+ readonly closeTask: (taskId: string, status?: 'done' | 'cancelled') => Promise<RoomMutationOutcome>;
36
+ /** Set (or, with a blank text, clear) the room's goal. */
37
+ readonly setGoal: (text: string) => Promise<RoomMutationOutcome>;
38
+ }
39
+ /**
40
+ * The invite dialog's injected share (sessionId binds at inject time). The
41
+ * three dialog hosts — the members tab, the session-header 邀请 agent action,
42
+ * and the fresh-room dock's invite capsule — take exactly this face; none
43
+ * inherits another's surface actions.
44
+ */
45
+ export interface RoomInviteInjected {
46
+ readonly renderHarnessModelPicker?: LocalAgentUi['renderHarnessModelPicker'] | undefined;
47
+ /** The room session's own cwd (the invite dialog's empty-cwd placeholder). */
48
+ readonly roomCwd?: string | undefined;
49
+ /** Invite a CLI member. */
50
+ readonly invite: (values: RoomInviteValues) => Promise<RoomInviteOutcome>;
51
+ /** List the invitable providers (undefined on transport failure). */
52
+ readonly listProviders: () => Promise<RoomProviderList | undefined>;
53
+ /**
54
+ * Pick a member-level working directory through the official picker call
55
+ * (`uiWorkspace.pickDirectory` on host 0.1.2 — the picker UI itself is not
56
+ * reusable: ui-workspace's flow holes adopt the pick as a workspace).
57
+ * Resolves null on cancel or when the host serves no native
58
+ * directory-picking capability.
59
+ */
60
+ readonly browseDirectory: () => Promise<string | null>;
61
+ /**
62
+ * The roster's current names, read from the store cache at call time (the
63
+ * invite dialog's name dice never rolls one of these). Empty on a cache
64
+ * miss — the host's duplicate check stays the backstop.
65
+ */
66
+ readonly listNames: () => readonly string[];
67
+ /**
68
+ * The pickable model surface of one harness (the localAgentGateway
69
+ * `harnessModel` read), feeding the invite dialog's model row: choices for
70
+ * the menu, the effective default the unset field displays. Undefined = no
71
+ * surface (a composition without the local-agent family's client half, or a
72
+ * brokerless harness): the field stays a plain text input, blank following
73
+ * the harness default.
74
+ */
75
+ readonly modelSurface: (harness: string) => Promise<LocalAgentModelInfo | undefined>;
76
+ /**
77
+ * The preset-composition visibility of room's session chrome (M3'
78
+ * self-hide): the invite chip returns null when this says no, and the
79
+ * members tab's registration toggle reads it. Every unreadable path fails
80
+ * open, and an actual room always shows.
81
+ */
82
+ readonly roomChrome: RoomChromeVisibility;
83
+ }
84
+ /**
85
+ * The main agent's durable per-session model selection (the host's
86
+ * session-controller vocabulary, duck-typed so room never imports
87
+ * ui-model-selection — the service is probed through `ctx.get`).
88
+ */
89
+ export interface RoomModelSelection {
90
+ readonly provider: string;
91
+ readonly model: string;
92
+ readonly reasoningEffort?: string;
93
+ }
94
+ /** One adapter-owned reasoning effort of an exact model route. */
95
+ export interface RoomModelEffort {
96
+ readonly id: string;
97
+ readonly name: string;
98
+ }
99
+ /** One catalog model inside its provider group. */
100
+ export interface RoomCatalogModel {
101
+ readonly id: string;
102
+ readonly name: string;
103
+ readonly reasoning?: {
104
+ readonly efforts: readonly RoomModelEffort[];
105
+ readonly defaultEffort?: string;
106
+ };
107
+ }
108
+ /** One provider and its successfully loaded model catalog. */
109
+ export interface RoomModelGroup {
110
+ readonly id: string;
111
+ readonly name: string;
112
+ readonly models: readonly RoomCatalogModel[];
113
+ }
114
+ /** The directory snapshot the room's main-agent model picker renders from. */
115
+ export interface RoomModelDirectoryState {
116
+ /** Effective selection: the durable next-request projection, then the host default. */
117
+ readonly current: RoomModelSelection | null;
118
+ /** Successfully loaded provider groups (last good load). */
119
+ readonly groups: readonly RoomModelGroup[];
120
+ /** Lifecycle of the in-flight operation. */
121
+ readonly status: 'idle' | 'loading' | 'ready' | 'selecting' | 'error';
122
+ /** Whole-request or selection failure text; null when none. */
123
+ readonly error: string | null;
124
+ }
125
+ /**
126
+ * The duck-typed slice of ui-model-selection's per-session ModelDirectory
127
+ * (`ctx.modelDirectories.directoryFor(sessionId)`) the composer's main-agent
128
+ * model picker consumes — the SAME directory the official composer seat
129
+ * (`conversation.input.model`) renders, so a switch here is exactly the
130
+ * official seat's switch: `select()` writes the durable per-session selection
131
+ * through the session controller's selectModel remote and the shared store
132
+ * notifies every reader. Absent service = no picker (degrade, never throw).
133
+ */
134
+ export interface RoomModelDirectory {
135
+ /** The shared snapshot store (useSyncExternalStore-safe). */
136
+ readonly store: {
137
+ readonly subscribe: (listener: () => void) => () => void;
138
+ readonly getSnapshot: () => RoomModelDirectoryState;
139
+ };
140
+ /** Ensure the shared advisory catalog is loaded (errors land on the store). */
141
+ readonly load: () => Promise<unknown>;
142
+ /**
143
+ * Select the complete provider/model/reasoning selection. 0.1.5 rejects on
144
+ * a refused switch; alpha.2 never rejects and answers the RemoteResult
145
+ * instead — a settled object carrying `ok` reports the outcome there, any
146
+ * other resolution reads as success.
147
+ */
148
+ readonly select: (selection: RoomModelSelection) => Promise<unknown>;
149
+ }
150
+ /**
151
+ * Injected face of the composer takeover entry: the dispatch submit plus the
152
+ * task-board actions (sessionId binds at inject time), because the takeover
153
+ * renders the task board itself — the `conversation.input.dock` seat rides
154
+ * the hidden official fallback. The takeover also inherits the official bar's
155
+ * Stop duty: the fallback's Stop button hides with it, so `stop` re-homes the
156
+ * main agent's turn cancel (the runtime session face's `cancel()`). The
157
+ * invite share rides along for the fresh-room dock's invite capsule.
158
+ * `modelDirectory` is the official per-session model directory for the room's
159
+ * own main agent (bare messages ARE its turns); undefined on a host without
160
+ * ui-model-selection — the picker simply does not render.
161
+ */
162
+ export interface RoomComposerInjected extends RoomTasksInjected, RoomInviteInjected {
163
+ readonly reconcileDelivery?: ((deliveryId: string, outcome: 'done' | 'cancelled', evidence: string) => Promise<RoomMutationOutcome>) | undefined;
164
+ readonly planCommand?: ((command: string) => Promise<RoomMutationOutcome>) | undefined;
165
+ readonly openPlanSession?: ((sessionId: string) => void) | undefined;
166
+ readonly renderMemberInbox?: LocalAgentUi['renderMemberInbox'] | undefined;
167
+ readonly renderMemberConfiguration?: LocalAgentUi['renderMemberConfiguration'] | undefined;
168
+ readonly stopMember?: ((name: string) => void) | undefined;
169
+ /**
170
+ * Dispatch an @-message into the room and refresh the store on success.
171
+ * Bare messages never reach here — the composer releases them to the
172
+ * official submit path (useInput/inputActions) itself. `targets` carries
173
+ * the mention-menu picks (explicit addressing wherever the `@name` sits in
174
+ * the sentence); the host unions them with the parsed leading tokens.
175
+ */
176
+ readonly submit: (sessionId: SessionId, text: string, targets?: readonly string[]) => Promise<RoomMutationOutcome>;
177
+ /** Interrupt the room's own main-agent turn (the hidden official bar's Stop). */
178
+ readonly stop: () => void;
179
+ /**
180
+ * The official per-session model directory of the room's main agent (the
181
+ * session's root agent). The picker writes the SESSION selection only — an
182
+ * @-addressed member dispatch rides room's own submit remote and is never
183
+ * touched by it. undefined = host without ui-model-selection: no picker.
184
+ */
185
+ readonly modelDirectory?: RoomModelDirectory | undefined;
186
+ }
187
+ /** The composer takeover match: the session is a cached room. */
188
+ export interface RoomComposerMatch {
189
+ readonly room: true;
190
+ }
191
+ /** Full props of the 'conversation.composer' chain entry. */
192
+ export type RoomComposerProps = PropsRuntime<'conversation.composer'> & {
193
+ matched: RoomComposerMatch;
194
+ } & InjectFace<RoomComposerInjected> & PropsLocale<'room'>;
195
+ /** Full props of the 'conversation.view' members-tab entry. */
196
+ export type MembersViewProps = PropsRuntime<'conversation.view'> & InjectFace<RoomMembersInjected> & PropsLocale<'room'>;
197
+ /** The invite form's values (sessionId binds at inject time). */
198
+ export interface RoomInviteValues {
199
+ readonly provider: string;
200
+ readonly name: string;
201
+ readonly instructions?: string;
202
+ /** Member-level working directory (omitted = inherits the room session's cwd). */
203
+ readonly cwd?: string;
204
+ readonly firstTask?: string;
205
+ /** The delegation's invite-time model (omitted = follow the harness default). */
206
+ readonly model?: string;
207
+ }
208
+ /** The invite outcome: the receipt's pendingFirstTask picks the success copy. */
209
+ export type RoomInviteOutcome = {
210
+ readonly ok: true;
211
+ readonly pendingFirstTask: boolean;
212
+ } | {
213
+ readonly ok: false;
214
+ readonly message: string;
215
+ };
216
+ /** Injected face of the members tab. */
217
+ export interface RoomMembersInjected extends RoomInviteInjected {
218
+ readonly renderMemberConfiguration?: ((childSessionId: string) => ReactNode) | undefined;
219
+ /** The client-side room state store (roster + runs). */
220
+ readonly roomStore: RoomStore;
221
+ /** Open a session (the member's child-session trajectory jump). */
222
+ readonly openSession: (sessionId: SessionId) => void;
223
+ /** Remove the named member from the roster. */
224
+ readonly setCoordinator?: ((memberId: string, expectedRevision: number) => Promise<RoomMutationOutcome>) | undefined;
225
+ readonly removeMember: (member: string) => Promise<RoomMutationOutcome>;
226
+ /**
227
+ * Edit the named member: rename, cwd override (null clears back to
228
+ * inheriting the room cwd), role instructions (null clears them), intended
229
+ * model (null clears it back to the harness default). Only the present
230
+ * fields change.
231
+ */
232
+ readonly updateMember: (member: string, patch: RoomMemberPatch) => Promise<RoomMutationOutcome>;
233
+ /**
234
+ * A CLI member's model surface (the localAgentGateway `memberModel` read,
235
+ * duck-typed): null = no broker (no model hint, pre-broker behavior),
236
+ * undefined = gateway absent or RPC failure. Absent = no model hints at all.
237
+ */
238
+ readonly memberModel?: ((childSessionId: string) => Promise<LocalAgentModelInfo | null | undefined>) | undefined;
239
+ /**
240
+ * Switch (undefined = clear the override → follow settings) a live member's
241
+ * session-level model (the localAgentGateway `setMemberModel` write): the
242
+ * edit dialog's immediate-effect half; its structured refusal (a round in
243
+ * flight) rides the dialog's error line. undefined = RPC failure (the
244
+ * journaled persist still proceeds).
245
+ */
246
+ readonly setMemberModel?: ((childSessionId: string, model?: string) => Promise<LocalAgentPromptResult | undefined>) | undefined;
247
+ /**
248
+ * The room session's official model directory (the same instance
249
+ * RoomModelPicker renders): the main-agent card's model hint. undefined =
250
+ * host without ui-model-selection.
251
+ */
252
+ readonly modelDirectory?: RoomModelDirectory | undefined;
253
+ }
254
+ /** One member edit (every field optional; null clears the field). */
255
+ export interface RoomMemberPatch {
256
+ /** New @-addressing name (unique, no whitespace, no "@"). */
257
+ readonly rename?: string;
258
+ /** New role instructions; null clears them. */
259
+ readonly instructions?: string | null;
260
+ /** New cwd override; null clears back to inheriting the room cwd. */
261
+ readonly cwd?: string | null;
262
+ /** New intended model; null clears back to the harness default. */
263
+ readonly model?: string | null;
264
+ }
265
+ /** Injected face of the member-speech chat node. */
266
+ export interface RoomSpeechInjected {
267
+ /** The client-side room state store (provider/kind lookup for the identity row). */
268
+ readonly roomStore: RoomStore;
269
+ /** Open a session (the speech's child-session jump). */
270
+ readonly openSession: (sessionId: SessionId) => void;
271
+ }
272
+ /** Full props of the 'room-speech' chat-node renderer. */
273
+ export type RoomSpeechViewProps = PropsRuntime<'conversation.chat.node', 'room-speech'> & InjectFace<RoomSpeechInjected> & PropsLocale<'room'>;
274
+ /** Injected face of the member-run chat node. */
275
+ export interface RoomRunInjected {
276
+ readonly renderMemberOutput?: LocalAgentUi['renderMemberOutput'] | undefined;
277
+ /** The client-side room state store (the roster carries the jump target). */
278
+ readonly roomStore: RoomStore;
279
+ /** Open a session (the whole-row jump into the member's child session). */
280
+ readonly openSession: (sessionId: SessionId) => void;
281
+ /** Cancel the named member's in-flight run through the Remote. */
282
+ readonly cancelMember: (member: string) => Promise<void>;
283
+ }
284
+ /** Full props of the 'room-run' chat-node renderer. */
285
+ export type RoomRunViewProps = PropsRuntime<'conversation.chat.node', 'room-run'> & InjectFace<RoomRunInjected> & PropsLocale<'room'>;
286
+ /** Full props of the 'room-event' chat-node renderer. */
287
+ export type RoomEventViewProps = PropsRuntime<'conversation.chat.node', 'room-event'> & PropsLocale<'room'>;
288
+ /** Injected face of the relay (member notification) chat node. */
289
+ export interface RoomRelayInjected {
290
+ /** The client-side room state store (member colors come from the roster order). */
291
+ readonly roomStore: RoomStore;
292
+ /** Confirm a pending relay (dispatches the notification to the recipient). */
293
+ readonly confirmRelay: (relayId: string) => Promise<void>;
294
+ /** Dismiss a pending relay (the notification never reaches anyone). */
295
+ readonly dismissRelay: (relayId: string) => Promise<void>;
296
+ }
297
+ /** Full props of the 'room-relay' chat-node renderer. */
298
+ export type RoomRelayViewProps = PropsRuntime<'conversation.chat.node', 'room-relay'> & InjectFace<RoomRelayInjected> & PropsLocale<'room'>;
299
+ /** Injected face of the task-advance chat node. */
300
+ export interface RoomTaskLineInjected {
301
+ /** The client-side room state store (the goal-progress suffix). */
302
+ readonly roomStore: RoomStore;
303
+ }
304
+ /** Full props of the 'room-task-line' chat-node renderer. */
305
+ export type RoomTaskLineViewProps = PropsRuntime<'conversation.chat.node', 'room-task-line'> & InjectFace<RoomTaskLineInjected> & PropsLocale<'room'>;
306
+ /**
307
+ * Props of the dock capsules (goal capsule + task capsule), rendered by the
308
+ * RoomComposer itself above the input card — NOT a slot entry:
309
+ * `conversation.input.dock` lives inside the official composer fallback,
310
+ * which the takeover hides. The invite share serves the fresh-room state:
311
+ * a room with neither a goal nor any task renders a single 「+ 邀请成员」
312
+ * capsule instead of the pair, opening the invite dialog straight from the
313
+ * dock.
314
+ */
315
+ export type RoomDockCapsulesProps = {
316
+ readonly sessionId: SessionId;
317
+ readonly formalPlans?: boolean;
318
+ } & RoomTasksInjected & RoomInviteInjected & PropsLocale<'room'>;
319
+ //# sourceMappingURL=slots.d.ts.map
@@ -0,0 +1 @@
1
+ export {};
@@ -0,0 +1,5 @@
1
+ import type { SessionEvent } from '@deepseek-ai/dsh-session/types';
2
+ import type { RoomDelivery } from './types.ts';
3
+ /** Accepted delivery intents and their latest durable outcomes. */
4
+ export declare function replayDeliveries(events: readonly SessionEvent[]): RoomDelivery[];
5
+ //# sourceMappingURL=deliveries.d.ts.map
@@ -0,0 +1,20 @@
1
+ /** Accepted delivery intents and their latest durable outcomes. */
2
+ export function replayDeliveries(events) {
3
+ const rows = new Map();
4
+ for (const event of events) {
5
+ if (event.type === 'room/dispatch' && event.data.targetIds !== undefined) {
6
+ for (const memberId of event.data.targetIds) {
7
+ const id = `${event.seq}:${memberId}`;
8
+ if (!rows.has(id))
9
+ rows.set(id, { id, dispatchSeq: event.seq, memberId, status: 'queued', text: event.data.text, origin: event.data.origin ?? 'human', ...event.data.plan === undefined ? {} : { plan: event.data.plan } });
10
+ }
11
+ }
12
+ else if (event.type === 'room/delivery-state') {
13
+ const row = rows.get(event.data.id);
14
+ if (row === undefined || row.memberId !== event.data.memberId || row.dispatchSeq !== event.data.dispatchSeq)
15
+ continue;
16
+ rows.set(row.id, { ...row, status: event.data.state, ...event.data.error === undefined ? {} : { error: event.data.error } });
17
+ }
18
+ }
19
+ return [...rows.values()];
20
+ }