@hachej/boring-ui-kit 0.4.19 → 0.4.20

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 (54) hide show
  1. package/dist/shared-chat/index.d.ts +4 -0
  2. package/dist/shared-chat/index.js +42 -31
  3. package/dist/shared-chat/room.d.ts +41 -0
  4. package/dist/shared-chat/room.js +596 -0
  5. package/dist/shared-chat/socket.js +1 -0
  6. package/dist/shared-chat/source.d.ts +12 -0
  7. package/package.json +2 -2
  8. package/r/agent-target.json +4 -4
  9. package/r/app-shell.json +9 -9
  10. package/r/board-viewer.json +5 -5
  11. package/r/button.json +1 -1
  12. package/r/diagram-viewer.json +1 -1
  13. package/r/dock.json +1 -1
  14. package/r/documents.json +12 -12
  15. package/r/feedback-preview.json +1 -1
  16. package/r/feedback.json +6 -6
  17. package/r/file-tree.json +5 -5
  18. package/r/highlights.json +3 -3
  19. package/r/html-viewer.json +3 -3
  20. package/r/looks-viewer.json +1 -1
  21. package/r/maquette-viewer.json +2 -2
  22. package/r/markdown-editor.json +3 -3
  23. package/r/mockup-viewer.json +1 -1
  24. package/r/pi-ambient.json +9 -9
  25. package/r/pi-app.json +20 -20
  26. package/r/pi-artifacts.json +1 -1
  27. package/r/pi-chat-approval.json +1 -1
  28. package/r/pi-chat-attachments.json +1 -1
  29. package/r/pi-chat-markdown.json +1 -1
  30. package/r/pi-chat-mentions.json +1 -1
  31. package/r/pi-chat-pickers.json +1 -1
  32. package/r/pi-chat-plugins.json +11 -11
  33. package/r/pi-chat-question.json +1 -1
  34. package/r/pi-chat-slash.json +1 -1
  35. package/r/pi-chat-subagent.json +1 -1
  36. package/r/pi-chat-voice.json +2 -2
  37. package/r/pi-chat.json +5 -5
  38. package/r/pi-workspace.json +4 -4
  39. package/r/placed-agent.json +4 -4
  40. package/r/provider-setup.json +2 -2
  41. package/r/record-viewer.json +1 -1
  42. package/r/registry.json +166 -166
  43. package/r/requests-board.json +1 -1
  44. package/r/shared-chat.json +13 -13
  45. package/r/task-list-viewer.json +2 -2
  46. package/r/version-history.json +3 -3
  47. package/r/viewer-code.json +2 -2
  48. package/r/viewer-frame.json +5 -5
  49. package/r/viewer-html.json +4 -4
  50. package/r/viewer-image.json +2 -2
  51. package/r/viewer-markdown.json +5 -5
  52. package/r/viewer-pdf.json +3 -3
  53. package/r/viewers.json +6 -6
  54. package/r/windows.json +1 -1
@@ -33,6 +33,8 @@ export declare function createSharedChat(source: ChannelSource, options: {
33
33
  loadEarlier: () => Promise<void>;
34
34
  connect: () => Promise<void>;
35
35
  send: (_whenBusy?: ChatAttempt["whenBusy"], input?: ChatSendInput) => Promise<ChannelMessage | undefined>;
36
+ /** Answers an agent's question with one of its choices (or any text): a reply to the question, leaving the composer and its reply as they are. */
37
+ answer: (question: ChannelMessage, text: string) => Promise<ChannelMessage | undefined>;
36
38
  reconcile: () => Promise<void>;
37
39
  retrySameRequest: () => Promise<void>;
38
40
  stop: () => Promise<never>;
@@ -48,5 +50,7 @@ export declare function memberMention(member: Member): string;
48
50
  export { createFakeChannelSource } from './fake.js';
49
51
  export type { FakeChannelSeed } from './fake.js';
50
52
  export { createChannelSocket } from './socket.js';
53
+ export { createRoomSource } from './room.js';
54
+ export type { RoomSource, RoomSourceOptions, RoomWords } from './room.js';
51
55
  export type { ChannelSocketOptions, ChannelSocketSource } from './socket.js';
52
56
  //# sourceMappingURL=index.d.ts.map
@@ -143,6 +143,41 @@ export function createSharedChat(source, options) {
143
143
  reconnect = refresh().then(replay).catch(error => publish({ connection: { kind: 'error', error } })).finally(() => { reconnect = undefined; });
144
144
  }
145
145
  };
146
+ /** One message into the thread. `answering` replies to a question without touching the composer's reply, files or draft. */
147
+ const post = async (input, answering) => {
148
+ active();
149
+ const selected = input ?? snapshot.draft;
150
+ const files = answering ? [] : [...snapshot.shared.files];
151
+ if (!selected.text.trim() && !selected.attachments?.length && !files.length)
152
+ return;
153
+ if (selected.attachments?.length)
154
+ throw new Error('Use shared file attachments, not native image payloads');
155
+ const clientId = randomUUID();
156
+ const mentions = snapshot.shared.members.filter(member => hasMemberMention(selected.text, member)).map(member => member.id);
157
+ const payload = { conversationId: id, text: selected.text, mentions, clientId, ...(answering ? { replyTo: answering.replyTo } : snapshot.shared.replyTo ? { replyTo: snapshot.shared.replyTo } : {}), ...(files.length ? { attachments: files } : {}) };
158
+ const result = outbox.enqueue(clientId, payload);
159
+ // The sender's own copy is in the thread at once, marked pending: the host's message with the same clientId replaces it
160
+ // (merge), whether it comes back from send, the live socket or a history read. A refusal takes it out again.
161
+ const local = { id: `pending:${clientId}`, conversationId: id, author: source.me.id, at: latestAt(snapshot.shared.messages), text: selected.text, mentions, clientId, pending: true,
162
+ ...(payload.replyTo ? { replyTo: payload.replyTo } : {}), ...(files.length ? { attachments: files.map(file => ({ path: file.name, title: file.name })) } : {}) };
163
+ publish({ shared: answering ? snapshot.shared : { ...snapshot.shared, files: [], replyTo: undefined }, ...(!input ? { draft: { text: '', attachments: [], version: snapshot.draft.version + 1 } } : {}), send: { kind: 'submitting' } });
164
+ merge([local]);
165
+ sendOne(clientId, payload);
166
+ try {
167
+ const message = await result;
168
+ publish({ send: { kind: 'admitted' } });
169
+ return message;
170
+ }
171
+ catch (error) {
172
+ if (!snapshot.disposed && error instanceof ChannelRequestError && error.refused) {
173
+ const current = snapshot.draft, others = snapshot.shared.messages.filter(message => message.id !== local.id);
174
+ publish(answering ? { send: { kind: 'blocked', reason: error.message }, shared: { ...snapshot.shared, messages: others } }
175
+ : { send: { kind: 'blocked', reason: error.message }, draft: { ...current, text: [input?.restore?.text ?? selected.text, current.text].filter(Boolean).join('\n'), version: current.version + 1 },
176
+ shared: { ...snapshot.shared, messages: others, files: [...files, ...snapshot.shared.files], replyTo: snapshot.shared.replyTo ?? payload.replyTo } });
177
+ }
178
+ return undefined;
179
+ }
180
+ };
146
181
  const controller = {
147
182
  getSnapshot: () => snapshot,
148
183
  subscribe: (listener) => { listeners.add(listener); return () => { listeners.delete(listener); }; },
@@ -190,38 +225,13 @@ export function createSharedChat(source, options) {
190
225
  }).catch(error => { unsubscribe?.(); unsubscribe = undefined; publish({ connection: { kind: 'error', error } }); }).finally(() => { opening = undefined; });
191
226
  return opening;
192
227
  },
193
- send: async (_whenBusy, input) => {
228
+ send: (_whenBusy, input) => post(input),
229
+ /** Answers an agent's question with one of its choices (or any text): a reply to the question, leaving the composer and its reply as they are. */
230
+ answer: (question, text) => {
194
231
  active();
195
- const selected = input ?? snapshot.draft;
196
- const files = [...snapshot.shared.files];
197
- if (!selected.text.trim() && !selected.attachments?.length && !files.length)
198
- return;
199
- if (selected.attachments?.length)
200
- throw new Error('Use shared file attachments, not native image payloads');
201
- const clientId = randomUUID();
202
- const mentions = snapshot.shared.members.filter(member => hasMemberMention(selected.text, member)).map(member => member.id);
203
- const payload = { conversationId: id, text: selected.text, mentions, clientId, ...(snapshot.shared.replyTo ? { replyTo: snapshot.shared.replyTo } : {}), ...(files.length ? { attachments: files } : {}) };
204
- const result = outbox.enqueue(clientId, payload);
205
- // The sender's own copy is in the thread at once, marked pending: the host's message with the same clientId replaces it
206
- // (merge), whether it comes back from send, the live socket or a history read. A refusal takes it out again.
207
- const local = { id: `pending:${clientId}`, conversationId: id, author: source.me.id, at: latestAt(snapshot.shared.messages), text: selected.text, mentions, clientId, pending: true,
208
- ...(payload.replyTo ? { replyTo: payload.replyTo } : {}), ...(files.length ? { attachments: files.map(file => ({ path: file.name, title: file.name })) } : {}) };
209
- publish({ shared: { ...snapshot.shared, files: [], replyTo: undefined }, ...(!input ? { draft: { text: '', attachments: [], version: snapshot.draft.version + 1 } } : {}), send: { kind: 'submitting' } });
210
- merge([local]);
211
- sendOne(clientId, payload);
212
- try {
213
- const message = await result;
214
- publish({ send: { kind: 'admitted' } });
215
- return message;
216
- }
217
- catch (error) {
218
- if (!snapshot.disposed && error instanceof ChannelRequestError && error.refused) {
219
- const current = snapshot.draft;
220
- publish({ send: { kind: 'blocked', reason: error.message }, draft: { ...current, text: [input?.restore?.text ?? selected.text, current.text].filter(Boolean).join('\n'), version: current.version + 1 },
221
- shared: { ...snapshot.shared, messages: snapshot.shared.messages.filter(message => message.id !== local.id), files: [...files, ...snapshot.shared.files], replyTo: snapshot.shared.replyTo ?? payload.replyTo } });
222
- }
223
- return undefined;
224
- }
232
+ if (!question.question || question.question.answered || question.conversationId !== id)
233
+ return Promise.reject(new Error('This question is not open'));
234
+ return post({ text, attachments: [] }, { replyTo: question.id });
225
235
  },
226
236
  reconcile: async () => {
227
237
  active();
@@ -277,3 +287,4 @@ function hasMemberMention(text, member) {
277
287
  }
278
288
  export { createFakeChannelSource } from './fake.js';
279
289
  export { createChannelSocket } from './socket.js';
290
+ export { createRoomSource } from './room.js';
@@ -0,0 +1,41 @@
1
+ import type { ChannelSource } from './source.js';
2
+ /**
3
+ * A ChannelSource over the host's room door (`POST _room/<op>`, boring-factory `services/channel/room.mjs`) and its space
4
+ * WebSocket. The room is a pi-durable session: its order is the entry id, its view is the pure fold `roomView`, and a reader
5
+ * resumes from an entry id. The old `createChannelSocket` is untouched; this source is opt-in.
6
+ */
7
+ export interface RoomWords {
8
+ /** The card an agent's message turns into after `stallMs` with no frame (budget B6). */
9
+ readonly stalled: string;
10
+ readonly retry: string;
11
+ /** The label of a card's action when the room names it by id only (`try`, `later`, `validate`). */
12
+ readonly actions: Readonly<Record<string, string>>;
13
+ /** Titles of the room's own conversations. */
14
+ readonly titles: Readonly<Record<string, string>>;
15
+ }
16
+ export interface RoomSourceOptions {
17
+ /** The app's URL, for example `https://app.example/`. The door is `<url>/_room/<op>`, the socket `<url>/spaces/<space>/live`. */
18
+ readonly url: string | URL;
19
+ readonly fetch?: (request: Request) => Promise<Response>;
20
+ /** Host subprotocol authentication may be supplied here. */
21
+ readonly open?: (url: URL) => WebSocket;
22
+ readonly reconnectMs?: number;
23
+ /** A socket that has not opened after this long is given up and retried. Default 10000. */
24
+ readonly openMs?: number;
25
+ /** Call `POST _channel/join` before anything else (default). `false` for a host that enrols people another way. */
26
+ readonly join?: boolean;
27
+ readonly locale?: 'en' | 'fr';
28
+ /** Overrides the words (the stalled line, Retry, the titles of `main` and `admin`). */
29
+ readonly words?: Partial<RoomWords>;
30
+ /** No frame for this long while an agent writes: its message offers Retry (B6). Default 8000. */
31
+ readonly stallMs?: number;
32
+ readonly now?: () => number;
33
+ }
34
+ export interface RoomSource extends ChannelSource {
35
+ readonly space: string;
36
+ readonly engine: 'room';
37
+ /** The last entry id this source has read for the conversation (what a resume asks `after`). */
38
+ readonly cursor: (conversationId: string) => number | undefined;
39
+ }
40
+ export declare function createRoomSource(options: RoomSourceOptions): Promise<RoomSource>;
41
+ //# sourceMappingURL=room.d.ts.map