@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.
- package/CHANGELOG.md +9 -0
- package/README.i18n.yaml +6 -0
- package/README.md +48 -0
- package/README.zh.md +48 -0
- package/cordis.patch.yml +6 -0
- package/lib/client.js +11481 -0
- package/lib/index.js +3231 -0
- package/lib/invariant.js +26 -0
- package/lib/journal-CuRnWk66.js +435 -0
- package/lib/tool.js +350 -0
- package/lib/typert.host.d.ts +3 -0
- package/lib/typert.host.js +2615 -0
- package/lib/typert.remote-client.d.ts +55 -0
- package/lib/typert.remote-client.js +1711 -0
- package/lib/types/adapter.d.ts +42 -0
- package/lib/types/adapter.js +41 -0
- package/lib/types/agent-setup.d.ts +141 -0
- package/lib/types/agent-setup.js +145 -0
- package/lib/types/client/InviteAgentAction.d.ts +13 -0
- package/lib/types/client/InviteAgentAction.js +45 -0
- package/lib/types/client/InviteDialog.d.ts +90 -0
- package/lib/types/client/InviteDialog.js +216 -0
- package/lib/types/client/MemberCard.d.ts +33 -0
- package/lib/types/client/MemberCard.js +32 -0
- package/lib/types/client/MembersView.d.ts +5 -0
- package/lib/types/client/MembersView.js +168 -0
- package/lib/types/client/RoomComposer.d.ts +21 -0
- package/lib/types/client/RoomComposer.js +211 -0
- package/lib/types/client/RoomDockCapsules.d.ts +64 -0
- package/lib/types/client/RoomDockCapsules.js +277 -0
- package/lib/types/client/RoomEventView.d.ts +11 -0
- package/lib/types/client/RoomEventView.js +17 -0
- package/lib/types/client/RoomModelPicker.d.ts +18 -0
- package/lib/types/client/RoomModelPicker.js +134 -0
- package/lib/types/client/RoomPlanView.d.ts +17 -0
- package/lib/types/client/RoomPlanView.js +88 -0
- package/lib/types/client/RoomQueueStrip.d.ts +26 -0
- package/lib/types/client/RoomQueueStrip.js +28 -0
- package/lib/types/client/RoomRecoveryView.d.ts +11 -0
- package/lib/types/client/RoomRecoveryView.js +38 -0
- package/lib/types/client/RoomRelayView.d.ts +12 -0
- package/lib/types/client/RoomRelayView.js +20 -0
- package/lib/types/client/RoomRunView.d.ts +15 -0
- package/lib/types/client/RoomRunView.js +50 -0
- package/lib/types/client/RoomSpeechView.d.ts +18 -0
- package/lib/types/client/RoomSpeechView.js +62 -0
- package/lib/types/client/RoomStatsLine.d.ts +61 -0
- package/lib/types/client/RoomStatsLine.js +138 -0
- package/lib/types/client/RoomTaskLineView.d.ts +13 -0
- package/lib/types/client/RoomTaskLineView.js +21 -0
- package/lib/types/client/RoomTodoStrip.d.ts +24 -0
- package/lib/types/client/RoomTodoStrip.js +64 -0
- package/lib/types/client/format.d.ts +31 -0
- package/lib/types/client/format.js +48 -0
- package/lib/types/client/icons.d.ts +27 -0
- package/lib/types/client/icons.js +11 -0
- package/lib/types/client/index.d.ts +39 -0
- package/lib/types/client/index.js +471 -0
- package/lib/types/client/locales.d.ts +422 -0
- package/lib/types/client/locales.js +413 -0
- package/lib/types/client/member-color.d.ts +13 -0
- package/lib/types/client/member-color.js +22 -0
- package/lib/types/client/name-pool.d.ts +17 -0
- package/lib/types/client/name-pool.js +25 -0
- package/lib/types/client/nodes.d.ts +136 -0
- package/lib/types/client/nodes.js +212 -0
- package/lib/types/client/preset-visibility.d.ts +91 -0
- package/lib/types/client/preset-visibility.js +113 -0
- package/lib/types/client/request-ids.d.ts +7 -0
- package/lib/types/client/request-ids.js +21 -0
- package/lib/types/client/room-store.d.ts +137 -0
- package/lib/types/client/room-store.js +244 -0
- package/lib/types/client/slots.d.ts +319 -0
- package/lib/types/client/slots.js +1 -0
- package/lib/types/deliveries.d.ts +5 -0
- package/lib/types/deliveries.js +20 -0
- package/lib/types/dispatch.d.ts +105 -0
- package/lib/types/dispatch.js +467 -0
- package/lib/types/index.d.ts +301 -0
- package/lib/types/index.js +1200 -0
- package/lib/types/invariant.d.ts +16 -0
- package/lib/types/invariant.js +24 -0
- package/lib/types/journal.d.ts +117 -0
- package/lib/types/journal.js +434 -0
- package/lib/types/plan-service.d.ts +34 -0
- package/lib/types/plan-service.js +266 -0
- package/lib/types/plan.d.ts +168 -0
- package/lib/types/plan.js +494 -0
- package/lib/types/tool.d.ts +56 -0
- package/lib/types/tool.js +389 -0
- package/lib/types/types.d.ts +813 -0
- package/lib/types/types.js +1 -0
- package/locale/en.json +6 -0
- package/locale/zh.json +6 -0
- package/package.json +131 -0
|
@@ -0,0 +1,212 @@
|
|
|
1
|
+
/** The shared final-node materialization (single shape across the three kinds). */
|
|
2
|
+
function viewNode(context, kind, visibility = 'visible') {
|
|
3
|
+
if (context.state === undefined)
|
|
4
|
+
return null;
|
|
5
|
+
return {
|
|
6
|
+
key: context.key,
|
|
7
|
+
kind,
|
|
8
|
+
id: context.id,
|
|
9
|
+
target: 'chat',
|
|
10
|
+
anchorSeq: context.state.seq,
|
|
11
|
+
location: context.start?.location ?? { kind: 'unresolved' },
|
|
12
|
+
visibility,
|
|
13
|
+
data: context.state,
|
|
14
|
+
};
|
|
15
|
+
}
|
|
16
|
+
/**
|
|
17
|
+
* The member-speech Definition: one single-event Context per room/speech
|
|
18
|
+
* event (a CLI member's reply mirrored onto the blackboard).
|
|
19
|
+
*/
|
|
20
|
+
export const roomSpeechDefinition = {
|
|
21
|
+
kind: 'room-speech',
|
|
22
|
+
target: 'chat',
|
|
23
|
+
match: event => event.type === 'room/speech'
|
|
24
|
+
? { id: String(event.seq), role: 'start' }
|
|
25
|
+
: null,
|
|
26
|
+
start: (_context, match) => {
|
|
27
|
+
const event = match.event;
|
|
28
|
+
if (event.type !== 'room/speech')
|
|
29
|
+
throw new Error('room-speech start requires a room/speech event');
|
|
30
|
+
return {
|
|
31
|
+
seq: event.seq,
|
|
32
|
+
time: event.time,
|
|
33
|
+
member: event.data.member,
|
|
34
|
+
text: event.data.text,
|
|
35
|
+
...event.data.interrupted === undefined ? {} : { interrupted: event.data.interrupted },
|
|
36
|
+
...event.data.childSessionId === undefined ? {} : { childSessionId: event.data.childSessionId },
|
|
37
|
+
...event.data.durationMs === undefined ? {} : { durationMs: event.data.durationMs },
|
|
38
|
+
};
|
|
39
|
+
},
|
|
40
|
+
update: context => context.state,
|
|
41
|
+
buildViewNode: context => viewNode(context, 'room-speech'),
|
|
42
|
+
};
|
|
43
|
+
/**
|
|
44
|
+
* The member-run Definition: one Context per run (matched on
|
|
45
|
+
* member+startedAt), started by the running edge and updated by its terminal
|
|
46
|
+
* edge. A running run renders the live row; a failed run stays as a dim
|
|
47
|
+
* error row; done/cancelled hide the row IN PLACE — the assembler's live
|
|
48
|
+
* incremental flush forbids withdrawing a materialized node with null (the
|
|
49
|
+
* withdrawal contract is the same key with `visibility: 'hidden'`; a null
|
|
50
|
+
* there throws and freezes the session's whole chat pipeline), while the
|
|
51
|
+
* speech row or the cancel's own affordances carry the outcome.
|
|
52
|
+
*/
|
|
53
|
+
export const roomRunDefinition = {
|
|
54
|
+
kind: 'room-run',
|
|
55
|
+
target: 'chat',
|
|
56
|
+
match: event => event.type === 'room/run-state'
|
|
57
|
+
? {
|
|
58
|
+
id: `${event.data.member}:${event.data.startedAt}`,
|
|
59
|
+
role: event.data.state === 'running' ? 'start' : 'update',
|
|
60
|
+
}
|
|
61
|
+
: null,
|
|
62
|
+
start: (_context, match) => {
|
|
63
|
+
const event = match.event;
|
|
64
|
+
if (event.type !== 'room/run-state')
|
|
65
|
+
throw new Error('room-run start requires a room/run-state event');
|
|
66
|
+
return {
|
|
67
|
+
seq: event.seq,
|
|
68
|
+
time: event.time,
|
|
69
|
+
member: event.data.member,
|
|
70
|
+
startedAt: event.data.startedAt,
|
|
71
|
+
state: event.data.state,
|
|
72
|
+
};
|
|
73
|
+
},
|
|
74
|
+
update: (context, match) => {
|
|
75
|
+
const event = match.event;
|
|
76
|
+
if (event.type !== 'room/run-state')
|
|
77
|
+
return context.state;
|
|
78
|
+
const { error: _error, elapsedMs: _elapsed, ...previous } = context.state;
|
|
79
|
+
return {
|
|
80
|
+
...previous,
|
|
81
|
+
state: event.data.state,
|
|
82
|
+
...event.data.elapsedMs === undefined ? {} : { elapsedMs: event.data.elapsedMs },
|
|
83
|
+
...event.data.error === undefined ? {} : { error: event.data.error },
|
|
84
|
+
};
|
|
85
|
+
},
|
|
86
|
+
buildViewNode: context => viewNode(context, 'room-run', context.state !== undefined && (context.state.state === 'done' || context.state.state === 'cancelled')
|
|
87
|
+
? 'hidden'
|
|
88
|
+
: 'visible'),
|
|
89
|
+
};
|
|
90
|
+
/**
|
|
91
|
+
* The boundary Definition: one single-event Context per member-added (cli
|
|
92
|
+
* members only — the auto-seated main agent is skipped) or member-removed
|
|
93
|
+
* event. `room/dispatch` stays unclaimed: the human's @-message renders
|
|
94
|
+
* through the official user node (it is also appended as `user/message`).
|
|
95
|
+
*/
|
|
96
|
+
export const roomEventDefinition = {
|
|
97
|
+
kind: 'room-event',
|
|
98
|
+
target: 'chat',
|
|
99
|
+
match: (event) => {
|
|
100
|
+
switch (event.type) {
|
|
101
|
+
case 'room/member-added':
|
|
102
|
+
return event.data.kind === 'main-agent' ? null : { id: String(event.seq), role: 'start' };
|
|
103
|
+
case 'room/member-removed':
|
|
104
|
+
return { id: String(event.seq), role: 'start' };
|
|
105
|
+
default:
|
|
106
|
+
return null;
|
|
107
|
+
}
|
|
108
|
+
},
|
|
109
|
+
start: (_context, match) => {
|
|
110
|
+
const event = match.event;
|
|
111
|
+
switch (event.type) {
|
|
112
|
+
case 'room/member-added':
|
|
113
|
+
return {
|
|
114
|
+
seq: event.seq,
|
|
115
|
+
time: event.time,
|
|
116
|
+
sub: 'member-added',
|
|
117
|
+
member: event.data.name,
|
|
118
|
+
invitedBy: event.data.invitedBy,
|
|
119
|
+
...event.data.provider === undefined ? {} : { provider: event.data.provider },
|
|
120
|
+
};
|
|
121
|
+
case 'room/member-removed':
|
|
122
|
+
return { seq: event.seq, time: event.time, sub: 'member-removed', member: event.data.name };
|
|
123
|
+
default:
|
|
124
|
+
throw new Error('room-event start requires a room boundary event');
|
|
125
|
+
}
|
|
126
|
+
},
|
|
127
|
+
update: context => context.state,
|
|
128
|
+
buildViewNode: context => viewNode(context, 'room-event'),
|
|
129
|
+
};
|
|
130
|
+
/**
|
|
131
|
+
* The relay Definition: one Context per relay (matched on the relay id),
|
|
132
|
+
* started by the `room/relay` event (pending) and updated by each
|
|
133
|
+
* `room/relay-resolved` edge — the gate row's state folds in place.
|
|
134
|
+
*/
|
|
135
|
+
export const roomRelayDefinition = {
|
|
136
|
+
kind: 'room-relay',
|
|
137
|
+
target: 'chat',
|
|
138
|
+
match: (event) => {
|
|
139
|
+
switch (event.type) {
|
|
140
|
+
case 'room/relay':
|
|
141
|
+
return { id: event.data.id, role: 'start' };
|
|
142
|
+
case 'room/relay-resolved':
|
|
143
|
+
return { id: event.data.id, role: 'update' };
|
|
144
|
+
default:
|
|
145
|
+
return null;
|
|
146
|
+
}
|
|
147
|
+
},
|
|
148
|
+
start: (_context, match) => {
|
|
149
|
+
const event = match.event;
|
|
150
|
+
if (event.type !== 'room/relay')
|
|
151
|
+
throw new Error('room-relay start requires a room/relay event');
|
|
152
|
+
return {
|
|
153
|
+
seq: event.seq,
|
|
154
|
+
time: event.time,
|
|
155
|
+
relayId: event.data.id,
|
|
156
|
+
from: event.data.from,
|
|
157
|
+
to: event.data.to,
|
|
158
|
+
content: event.data.content,
|
|
159
|
+
state: 'pending',
|
|
160
|
+
};
|
|
161
|
+
},
|
|
162
|
+
update: (context, match) => {
|
|
163
|
+
const event = match.event;
|
|
164
|
+
if (event.type !== 'room/relay-resolved')
|
|
165
|
+
return context.state;
|
|
166
|
+
return { ...context.state, seq: event.seq, state: event.data.state };
|
|
167
|
+
},
|
|
168
|
+
buildViewNode: context => viewNode(context, 'room-relay'),
|
|
169
|
+
};
|
|
170
|
+
/**
|
|
171
|
+
* The task-advance Definition: one Context per task (matched on the task id),
|
|
172
|
+
* started by the `room/task-added` event and updated by each
|
|
173
|
+
* `room/task-updated` edge. The node stays HIDDEN while the task is open or
|
|
174
|
+
* cancelled and materializes — at the closing event's seq, so the line sits
|
|
175
|
+
* at the moment of completion — only when the task closes done: the dim
|
|
176
|
+
* advance line `✓ ada 完成了「API 定稿」── 目标进度 2/5`. (The goal progress
|
|
177
|
+
* suffix is computed by the renderer from the room store, not folded here.)
|
|
178
|
+
*/
|
|
179
|
+
export const roomTaskLineDefinition = {
|
|
180
|
+
kind: 'room-task-line',
|
|
181
|
+
target: 'chat',
|
|
182
|
+
match: (event) => {
|
|
183
|
+
switch (event.type) {
|
|
184
|
+
case 'room/task-added':
|
|
185
|
+
return { id: event.data.id, role: 'start' };
|
|
186
|
+
case 'room/task-updated':
|
|
187
|
+
return { id: event.data.id, role: 'update' };
|
|
188
|
+
default:
|
|
189
|
+
return null;
|
|
190
|
+
}
|
|
191
|
+
},
|
|
192
|
+
start: (_context, match) => {
|
|
193
|
+
const event = match.event;
|
|
194
|
+
if (event.type !== 'room/task-added')
|
|
195
|
+
throw new Error('room-task-line start requires a room/task-added event');
|
|
196
|
+
return {
|
|
197
|
+
seq: event.seq,
|
|
198
|
+
time: event.time,
|
|
199
|
+
taskId: event.data.id,
|
|
200
|
+
member: event.data.member,
|
|
201
|
+
title: event.data.title,
|
|
202
|
+
status: event.data.status,
|
|
203
|
+
};
|
|
204
|
+
},
|
|
205
|
+
update: (context, match) => {
|
|
206
|
+
const event = match.event;
|
|
207
|
+
if (event.type !== 'room/task-updated')
|
|
208
|
+
return context.state;
|
|
209
|
+
return { ...context.state, seq: event.seq, time: event.time, status: event.data.status };
|
|
210
|
+
},
|
|
211
|
+
buildViewNode: context => viewNode(context, 'room-task-line', context.state !== undefined && context.state.status === 'done' ? 'visible' : 'hidden'),
|
|
212
|
+
};
|
|
@@ -0,0 +1,91 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The preset-composition visibility of room's SESSION chrome (M3' self-hide,
|
|
3
|
+
* the mode-switcher proposal's "组合里有我的行"): the invite chip and the
|
|
4
|
+
* members tab show exactly when the CURRENT session's preset composition
|
|
5
|
+
* names the `@khorsheed/dsh-room-tool` row — the preset composition file is
|
|
6
|
+
* the single source of truth, read from the official `pluginInventory`
|
|
7
|
+
* Remote (the same criterion the worktrees badge implements; inlined here
|
|
8
|
+
* rather than extracted into a helper package, per the proposal's M3'
|
|
9
|
+
* deferral). Every unreadable path fails OPEN (visible): a host without the
|
|
10
|
+
* pluginInventory namespace, a pending/failed RPC, a missing or `broken`
|
|
11
|
+
* preset group, and sessions with no preset at all. An ACTUAL room session
|
|
12
|
+
* always shows its chrome regardless of preset — the gate hides the dev
|
|
13
|
+
* affordance in sessions that never granted it, never breaks an existing
|
|
14
|
+
* room (the E1 regression rule).
|
|
15
|
+
* @module @khorsheed/dsh-room/client/preset-visibility
|
|
16
|
+
*/
|
|
17
|
+
import type { Context } from '@deepseek-ai/cordis';
|
|
18
|
+
import type { SessionId } from '@deepseek-ai/dsh-session/types';
|
|
19
|
+
/** The composition criterion's row: room's companion tool package. */
|
|
20
|
+
export declare const ROOM_TOOL_ROW_MODULE = "@khorsheed/dsh-room-tool";
|
|
21
|
+
/**
|
|
22
|
+
* Minimal structural mirror of the official pluginInventory snapshot — only
|
|
23
|
+
* the agentPresets slice the criterion reads (duck-typed, host-version
|
|
24
|
+
* tolerant: an extra field never breaks a structural read).
|
|
25
|
+
*/
|
|
26
|
+
export interface RoomPluginInventorySnapshot {
|
|
27
|
+
readonly agentPresets?: readonly {
|
|
28
|
+
readonly id: string;
|
|
29
|
+
readonly broken?: string;
|
|
30
|
+
readonly rows: readonly {
|
|
31
|
+
readonly moduleName: string;
|
|
32
|
+
}[];
|
|
33
|
+
}[];
|
|
34
|
+
}
|
|
35
|
+
/** The visibility face the gated surfaces consume. */
|
|
36
|
+
export interface RoomChromeVisibility {
|
|
37
|
+
/**
|
|
38
|
+
* Whether room's session chrome shows for one session.
|
|
39
|
+
* @param sessionId - the session being rendered (undefined = no session
|
|
40
|
+
* context — fail-open, so registration-shape probes keep their entries).
|
|
41
|
+
*/
|
|
42
|
+
show: (sessionId: SessionId | undefined) => boolean;
|
|
43
|
+
/** Subscribe to criterion-input changes (session switches, the inventory answer). */
|
|
44
|
+
subscribe: (listener: () => void) => () => void;
|
|
45
|
+
}
|
|
46
|
+
/**
|
|
47
|
+
* The session-chrome visibility controller: one inventory fetch per page
|
|
48
|
+
* (composition data changes only with preset files, which a reload re-reads
|
|
49
|
+
* anyway), re-evaluated by its consumers on every session switch.
|
|
50
|
+
*/
|
|
51
|
+
export declare class RoomPresetVisibility implements RoomChromeVisibility {
|
|
52
|
+
private readonly ctx;
|
|
53
|
+
private readonly isRoom;
|
|
54
|
+
/** The inventory answer; null while pending/failed and on namespace-less hosts. */
|
|
55
|
+
private composition;
|
|
56
|
+
private readonly listeners;
|
|
57
|
+
/**
|
|
58
|
+
* @param ctx - client root context.
|
|
59
|
+
* @param isRoom - the RoomStore's cached room verdict (the E1 escape).
|
|
60
|
+
*/
|
|
61
|
+
constructor(ctx: Context, isRoom: (sessionId: SessionId) => boolean);
|
|
62
|
+
show(sessionId: SessionId | undefined): boolean;
|
|
63
|
+
subscribe(listener: () => void): () => void;
|
|
64
|
+
private notify;
|
|
65
|
+
}
|
|
66
|
+
/**
|
|
67
|
+
* The members tab's registration toggle: the `conversation.view` tab strip's
|
|
68
|
+
* BUTTONS enumerate the slot's registrations (ui-conversation's `viewTabs()`
|
|
69
|
+
* has no per-session predicate), so a hidden tab means NO registration — a
|
|
70
|
+
* component returning null would leave the button with an empty body. The
|
|
71
|
+
* toggle registers exactly while the criterion passes for the current
|
|
72
|
+
* session and disposes otherwise; the slot's own subscription re-renders the
|
|
73
|
+
* strip (the same re-registration mechanism the composer takeover's
|
|
74
|
+
* promotion bump uses).
|
|
75
|
+
*/
|
|
76
|
+
export declare class RegistrationToggle {
|
|
77
|
+
private readonly register;
|
|
78
|
+
private readonly show;
|
|
79
|
+
private dispose;
|
|
80
|
+
private ready;
|
|
81
|
+
/**
|
|
82
|
+
* @param register - mounts the entry and returns its disposer.
|
|
83
|
+
* @param show - the current decision (re-read on every sync).
|
|
84
|
+
*/
|
|
85
|
+
constructor(register: () => () => void, show: () => boolean);
|
|
86
|
+
/** The slot declaration appeared (slots.inject's arm) — decisions may register now. */
|
|
87
|
+
setReady(ready: boolean): void;
|
|
88
|
+
/** Re-evaluate: register while shown and ready, dispose otherwise. */
|
|
89
|
+
sync(): void;
|
|
90
|
+
}
|
|
91
|
+
//# sourceMappingURL=preset-visibility.d.ts.map
|
|
@@ -0,0 +1,113 @@
|
|
|
1
|
+
/** The composition criterion's row: room's companion tool package. */
|
|
2
|
+
export const ROOM_TOOL_ROW_MODULE = '@khorsheed/dsh-room-tool';
|
|
3
|
+
/** The current session's agent preset, the badge's dual read per host line. */
|
|
4
|
+
function presetOf(row) {
|
|
5
|
+
const value = row
|
|
6
|
+
?.projectionValues?.agentPreset
|
|
7
|
+
?? row?.agentPreset;
|
|
8
|
+
return typeof value === 'string' ? value : undefined;
|
|
9
|
+
}
|
|
10
|
+
/**
|
|
11
|
+
* The session-chrome visibility controller: one inventory fetch per page
|
|
12
|
+
* (composition data changes only with preset files, which a reload re-reads
|
|
13
|
+
* anyway), re-evaluated by its consumers on every session switch.
|
|
14
|
+
*/
|
|
15
|
+
export class RoomPresetVisibility {
|
|
16
|
+
ctx;
|
|
17
|
+
isRoom;
|
|
18
|
+
/** The inventory answer; null while pending/failed and on namespace-less hosts. */
|
|
19
|
+
composition = null;
|
|
20
|
+
listeners = new Set();
|
|
21
|
+
/**
|
|
22
|
+
* @param ctx - client root context.
|
|
23
|
+
* @param isRoom - the RoomStore's cached room verdict (the E1 escape).
|
|
24
|
+
*/
|
|
25
|
+
constructor(ctx, isRoom) {
|
|
26
|
+
this.ctx = ctx;
|
|
27
|
+
this.isRoom = isRoom;
|
|
28
|
+
// The OFFICIAL pluginInventory namespace, probed through ctx.get (never
|
|
29
|
+
// injected — declaring 'remote.pluginInventory' would pend the whole
|
|
30
|
+
// client on a host without it, and the criterion must fail open there).
|
|
31
|
+
const pluginInventory = ctx.get('remote.pluginInventory');
|
|
32
|
+
if (pluginInventory !== undefined) {
|
|
33
|
+
void pluginInventory.list().then(result => {
|
|
34
|
+
if (!result.ok)
|
|
35
|
+
return;
|
|
36
|
+
this.composition = result.value;
|
|
37
|
+
this.notify();
|
|
38
|
+
}).catch(() => { });
|
|
39
|
+
}
|
|
40
|
+
// Session switches re-decide the members tab's registration (the tab
|
|
41
|
+
// strip's buttons come from the registration, not the component).
|
|
42
|
+
ctx.effect(() => ctx.sessions.list.subscribe(() => { this.notify(); }), 'room: preset-visibility session follower');
|
|
43
|
+
}
|
|
44
|
+
show(sessionId) {
|
|
45
|
+
if (sessionId === undefined)
|
|
46
|
+
return true;
|
|
47
|
+
// An actual room keeps its chrome whatever its preset granted (E1).
|
|
48
|
+
if (this.isRoom(sessionId))
|
|
49
|
+
return true;
|
|
50
|
+
const preset = presetOf(this.ctx.sessions.list.getSnapshot().byId[sessionId]);
|
|
51
|
+
if (preset === undefined)
|
|
52
|
+
return true;
|
|
53
|
+
if (this.composition === null)
|
|
54
|
+
return true;
|
|
55
|
+
const group = this.composition.agentPresets?.find(candidate => candidate.id === preset);
|
|
56
|
+
if (group === undefined || group.broken !== undefined)
|
|
57
|
+
return true;
|
|
58
|
+
return group.rows.some(row => row.moduleName === ROOM_TOOL_ROW_MODULE);
|
|
59
|
+
}
|
|
60
|
+
subscribe(listener) {
|
|
61
|
+
this.listeners.add(listener);
|
|
62
|
+
return () => { this.listeners.delete(listener); };
|
|
63
|
+
}
|
|
64
|
+
notify() {
|
|
65
|
+
for (const listener of this.listeners)
|
|
66
|
+
listener();
|
|
67
|
+
}
|
|
68
|
+
}
|
|
69
|
+
/**
|
|
70
|
+
* The members tab's registration toggle: the `conversation.view` tab strip's
|
|
71
|
+
* BUTTONS enumerate the slot's registrations (ui-conversation's `viewTabs()`
|
|
72
|
+
* has no per-session predicate), so a hidden tab means NO registration — a
|
|
73
|
+
* component returning null would leave the button with an empty body. The
|
|
74
|
+
* toggle registers exactly while the criterion passes for the current
|
|
75
|
+
* session and disposes otherwise; the slot's own subscription re-renders the
|
|
76
|
+
* strip (the same re-registration mechanism the composer takeover's
|
|
77
|
+
* promotion bump uses).
|
|
78
|
+
*/
|
|
79
|
+
export class RegistrationToggle {
|
|
80
|
+
register;
|
|
81
|
+
show;
|
|
82
|
+
dispose;
|
|
83
|
+
ready = false;
|
|
84
|
+
/**
|
|
85
|
+
* @param register - mounts the entry and returns its disposer.
|
|
86
|
+
* @param show - the current decision (re-read on every sync).
|
|
87
|
+
*/
|
|
88
|
+
constructor(register, show) {
|
|
89
|
+
this.register = register;
|
|
90
|
+
this.show = show;
|
|
91
|
+
}
|
|
92
|
+
/** The slot declaration appeared (slots.inject's arm) — decisions may register now. */
|
|
93
|
+
setReady(ready) {
|
|
94
|
+
this.ready = ready;
|
|
95
|
+
if (!ready && this.dispose !== undefined) {
|
|
96
|
+
this.dispose();
|
|
97
|
+
this.dispose = undefined;
|
|
98
|
+
}
|
|
99
|
+
this.sync();
|
|
100
|
+
}
|
|
101
|
+
/** Re-evaluate: register while shown and ready, dispose otherwise. */
|
|
102
|
+
sync() {
|
|
103
|
+
if (!this.ready)
|
|
104
|
+
return;
|
|
105
|
+
if (this.show()) {
|
|
106
|
+
this.dispose ??= this.register();
|
|
107
|
+
}
|
|
108
|
+
else if (this.dispose !== undefined) {
|
|
109
|
+
this.dispose();
|
|
110
|
+
this.dispose = undefined;
|
|
111
|
+
}
|
|
112
|
+
}
|
|
113
|
+
}
|
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
/** Keep one retry identity per room draft until the server acknowledges it. */
|
|
2
|
+
export declare class RoomRequestIds {
|
|
3
|
+
private readonly pending;
|
|
4
|
+
forInput(sessionId: string, text: string, targets?: readonly string[]): string;
|
|
5
|
+
complete(sessionId: string, id: string): void;
|
|
6
|
+
}
|
|
7
|
+
//# sourceMappingURL=request-ids.d.ts.map
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
/** Keep one retry identity per room draft until the server acknowledges it. */
|
|
2
|
+
export class RoomRequestIds {
|
|
3
|
+
pending = new Map();
|
|
4
|
+
forInput(sessionId, text, targets = []) {
|
|
5
|
+
const signature = JSON.stringify({ text: text.trim(), targets: [...new Set(targets)].sort() });
|
|
6
|
+
const known = this.pending.get(sessionId);
|
|
7
|
+
if (known?.signature === signature)
|
|
8
|
+
return known.id;
|
|
9
|
+
const id = typeof globalThis.crypto.randomUUID === 'function' ? globalThis.crypto.randomUUID()
|
|
10
|
+
: Array.from(globalThis.crypto.getRandomValues(new Uint8Array(16)), value => value.toString(16).padStart(2, '0')).join('');
|
|
11
|
+
this.pending.delete(sessionId);
|
|
12
|
+
this.pending.set(sessionId, { signature, id });
|
|
13
|
+
if (this.pending.size > 128)
|
|
14
|
+
this.pending.delete(this.pending.keys().next().value);
|
|
15
|
+
return id;
|
|
16
|
+
}
|
|
17
|
+
complete(sessionId, id) {
|
|
18
|
+
if (this.pending.get(sessionId)?.id === id)
|
|
19
|
+
this.pending.delete(sessionId);
|
|
20
|
+
}
|
|
21
|
+
}
|
|
@@ -0,0 +1,137 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Client-side room state store: the synchronous cache the composer chain
|
|
3
|
+
* selector reads (selectors cannot await), plus the per-session RoomState
|
|
4
|
+
* the room components render. Fed by the mounted room Remote: a first pull
|
|
5
|
+
* (`isRoom`, then `getState` when true) per session, a refresh after every
|
|
6
|
+
* own mutation, a realtime refresh trigger off the current session's live
|
|
7
|
+
* conversation feed, and a light 2s poll of `getState` only while the
|
|
8
|
+
* CURRENT session is a room with a running member (no running members, no
|
|
9
|
+
* polling). The live feed: the host pushes EVERY session event — room
|
|
10
|
+
* journal appends included — as `session/event` frames, which the runtime
|
|
11
|
+
* folds into the client Session's conversation snapshot; subscribing that
|
|
12
|
+
* snapshot and refreshing (300ms debounced) is how host-side mutations the
|
|
13
|
+
* client never initiated (the `room_task`/`room_invite` tools, the
|
|
14
|
+
* local-agent member bridge) land in the dock within a beat. A cache miss
|
|
15
|
+
* reads as `undefined`, and the composer selector declines on a miss: the
|
|
16
|
+
* first instant of a freshly opened room shows the official input bar until
|
|
17
|
+
* the first pull lands (accepted, documented).
|
|
18
|
+
* @module @khorsheed/dsh-room/client/room-store
|
|
19
|
+
*/
|
|
20
|
+
import type { Context } from '@deepseek-ai/cordis';
|
|
21
|
+
import type { SessionId } from '@deepseek-ai/dsh-session/types';
|
|
22
|
+
import type { SessionListState } from '@deepseek-ai/dsh-api-session-controller/client';
|
|
23
|
+
import type { RemoteResult } from '@deepseek-ai/dsh-typert-protocol';
|
|
24
|
+
import type { RoomGetStateRequest, RoomGetStateResult, RoomIsRoomRequest, RoomState } from '../types.ts';
|
|
25
|
+
/**
|
|
26
|
+
* The session the main view shows, read off both host lines: alpha.2 deleted
|
|
27
|
+
* the list's `current` for the per-row `retainedBy.mainView` reference count;
|
|
28
|
+
* 0.1.5 carries only `current`.
|
|
29
|
+
* @param list - the sessions list snapshot.
|
|
30
|
+
* @returns the main-view session id, or undefined when no session is shown.
|
|
31
|
+
*/
|
|
32
|
+
export declare function mainSessionId(list: SessionListState): SessionId | undefined;
|
|
33
|
+
/** The slice of the mounted room Remote the store reads. */
|
|
34
|
+
export interface RoomGateway {
|
|
35
|
+
isRoom(request: RoomIsRoomRequest): Promise<RemoteResult<boolean>>;
|
|
36
|
+
getState(request: RoomGetStateRequest): Promise<RemoteResult<RoomGetStateResult>>;
|
|
37
|
+
}
|
|
38
|
+
/** Poll cadence while the current room has running members. */
|
|
39
|
+
export declare const ROOM_POLL_INTERVAL_MS = 2000;
|
|
40
|
+
/**
|
|
41
|
+
* Debounce coalescing the live-feed refresh: one room journal append usually
|
|
42
|
+
* arrives inside a burst of session events (the tool call, its result, the
|
|
43
|
+
* task/task-update edges), so the refresh waits for a quiet beat.
|
|
44
|
+
*/
|
|
45
|
+
export declare const ROOM_LIVE_REFRESH_DEBOUNCE_MS = 300;
|
|
46
|
+
export declare class RoomStore {
|
|
47
|
+
private readonly ctx;
|
|
48
|
+
private readonly gateway;
|
|
49
|
+
private readonly room;
|
|
50
|
+
private readonly states;
|
|
51
|
+
private readonly listeners;
|
|
52
|
+
private poller;
|
|
53
|
+
private current;
|
|
54
|
+
/** The session the live feed is subscribed to (undefined = unattached). */
|
|
55
|
+
private liveAttached;
|
|
56
|
+
private liveUnsubscribe;
|
|
57
|
+
private liveTimer;
|
|
58
|
+
/**
|
|
59
|
+
* @param ctx - client root context (the sessions list feed).
|
|
60
|
+
* @param gateway - the mounted room Remote, undefined when the mount failed.
|
|
61
|
+
*/
|
|
62
|
+
constructor(ctx: Context, gateway: RoomGateway | undefined);
|
|
63
|
+
/**
|
|
64
|
+
* Follow the current session: a new current room session gets its first
|
|
65
|
+
* pull and its live-feed subscription, and polling re-evaluates on every
|
|
66
|
+
* switch.
|
|
67
|
+
* @returns disposer (unsubscribe everything + stop polling).
|
|
68
|
+
*/
|
|
69
|
+
start(): () => void;
|
|
70
|
+
/**
|
|
71
|
+
* The composer selector's synchronous read.
|
|
72
|
+
* @param sessionId - session to probe.
|
|
73
|
+
* @returns cached verdict, or undefined while the first pull is in flight.
|
|
74
|
+
*/
|
|
75
|
+
isRoomCached(sessionId: SessionId): boolean | undefined;
|
|
76
|
+
/**
|
|
77
|
+
* The cached room state (stable reference between refreshes — safe as a
|
|
78
|
+
* useSyncExternalStore snapshot).
|
|
79
|
+
* @param sessionId - room session.
|
|
80
|
+
* @returns the cached state, or undefined before the first successful pull.
|
|
81
|
+
*/
|
|
82
|
+
getCached(sessionId: SessionId): RoomState | undefined;
|
|
83
|
+
/**
|
|
84
|
+
* Subscribe to cache changes.
|
|
85
|
+
* @param listener - called after any map mutation.
|
|
86
|
+
* @returns unsubscribe.
|
|
87
|
+
*/
|
|
88
|
+
readonly subscribe: (listener: () => void) => (() => void);
|
|
89
|
+
/**
|
|
90
|
+
* First pull for a session: isRoom, then getState when it is one.
|
|
91
|
+
* No-ops on a cached verdict.
|
|
92
|
+
* @param sessionId - session to ensure.
|
|
93
|
+
*/
|
|
94
|
+
ensure(sessionId: SessionId): Promise<void>;
|
|
95
|
+
/**
|
|
96
|
+
* Re-pull the room state (after an own mutation, a live-feed nudge, or a
|
|
97
|
+
* poll tick).
|
|
98
|
+
* @param sessionId - room session.
|
|
99
|
+
*/
|
|
100
|
+
refresh(sessionId: SessionId): Promise<void>;
|
|
101
|
+
/**
|
|
102
|
+
* Subscribe the CURRENT session's live conversation feed so host-side
|
|
103
|
+
* journal appends nudge a refresh. The session binding is minted lazily by
|
|
104
|
+
* the runtime (it can lag the list's current flip), so every completed pull
|
|
105
|
+
* re-attempts the attach; an unattached store simply keeps its other
|
|
106
|
+
* triggers until one lands.
|
|
107
|
+
*/
|
|
108
|
+
private attachLive;
|
|
109
|
+
/** Drop the live-feed subscription and any pending debounced refresh. */
|
|
110
|
+
private detachLive;
|
|
111
|
+
/**
|
|
112
|
+
* One live-feed nudge, debounced so an event burst costs one probe: a known
|
|
113
|
+
* room refreshes its state; a session cached NOT a room re-probes `isRoom`
|
|
114
|
+
* (a promotion lands as a host-side `room/created` append the client never
|
|
115
|
+
* initiated — the room_invite/room_message tools promote in place); a
|
|
116
|
+
* unknown verdict retries ensure after the same debounce.
|
|
117
|
+
*/
|
|
118
|
+
private onLiveEvent;
|
|
119
|
+
/**
|
|
120
|
+
* Re-probe a cached-non-room session's verdict: a promotion flips the cache
|
|
121
|
+
* and pulls the state (the composer takeover follows on the next notify).
|
|
122
|
+
* @param sessionId - the session whose verdict may have changed.
|
|
123
|
+
*/
|
|
124
|
+
private reprobe;
|
|
125
|
+
private notify;
|
|
126
|
+
/**
|
|
127
|
+
* Called when a session's verdict flips unknown/false → true (an in-place
|
|
128
|
+
* promotion): the composer chain elects at RENDER time and nothing
|
|
129
|
+
* re-renders the outlet for a promotion of an idle session, so the client
|
|
130
|
+
* wires this to re-register the composer entry (a slot version bump
|
|
131
|
+
* re-renders — and re-elects — the outlet).
|
|
132
|
+
*/
|
|
133
|
+
onPromoted: (() => void) | undefined;
|
|
134
|
+
private adjustPolling;
|
|
135
|
+
private stopPolling;
|
|
136
|
+
}
|
|
137
|
+
//# sourceMappingURL=room-store.d.ts.map
|