@khorsheed/dsh-room 0.2.2 → 0.2.3

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 (109) hide show
  1. package/CHANGELOG.md +53 -0
  2. package/README.en.md +29 -62
  3. package/README.i18n.yaml +2 -2
  4. package/README.md +28 -61
  5. package/lib/client.js +13097 -4182
  6. package/lib/index.js +4045 -730
  7. package/lib/journal-B4FOGO6S.js +482 -0
  8. package/lib/journal-Rid9AAnD.js +485 -0
  9. package/lib/tool.js +112 -166
  10. package/lib/typert.host.js +2031 -374
  11. package/lib/typert.remote-client.d.ts +34 -10
  12. package/lib/typert.remote-client.js +1599 -210
  13. package/lib/types/agent-setup.d.ts +1 -1
  14. package/lib/types/client/InviteAgentAction.js +4 -16
  15. package/lib/types/client/InviteDialog.d.ts +28 -6
  16. package/lib/types/client/InviteDialog.js +76 -12
  17. package/lib/types/client/MemberCard.d.ts +5 -1
  18. package/lib/types/client/MemberCard.js +4 -4
  19. package/lib/types/client/MembersView.d.ts +1 -1
  20. package/lib/types/client/MembersView.js +58 -22
  21. package/lib/types/client/RemoveMemberConfirm.d.ts +40 -0
  22. package/lib/types/client/RemoveMemberConfirm.js +85 -0
  23. package/lib/types/client/RoomApprovalView.d.ts +11 -0
  24. package/lib/types/client/RoomApprovalView.js +28 -0
  25. package/lib/types/client/RoomComposer.d.ts +1 -1
  26. package/lib/types/client/RoomComposer.js +195 -29
  27. package/lib/types/client/RoomDecisionView.d.ts +22 -0
  28. package/lib/types/client/RoomDecisionView.js +35 -0
  29. package/lib/types/client/RoomHelpView.d.ts +17 -0
  30. package/lib/types/client/RoomHelpView.js +56 -0
  31. package/lib/types/client/RoomInboxView.d.ts +10 -0
  32. package/lib/types/client/RoomInboxView.js +14 -0
  33. package/lib/types/client/RoomQuestionView.d.ts +13 -0
  34. package/lib/types/client/RoomQuestionView.js +61 -0
  35. package/lib/types/client/RoomRelayView.js +1 -1
  36. package/lib/types/client/RoomRunView.d.ts +3 -1
  37. package/lib/types/client/RoomRunView.js +12 -5
  38. package/lib/types/client/RoomSettingsView.d.ts +19 -0
  39. package/lib/types/client/RoomSettingsView.js +30 -0
  40. package/lib/types/client/RoomSpeechView.d.ts +22 -8
  41. package/lib/types/client/RoomSpeechView.js +94 -11
  42. package/lib/types/client/RoomStatsLine.d.ts +26 -5
  43. package/lib/types/client/RoomStatsLine.js +53 -7
  44. package/lib/types/client/RoomTaskCapsule.d.ts +19 -0
  45. package/lib/types/client/RoomTaskCapsule.js +37 -0
  46. package/lib/types/client/TasksTab.d.ts +41 -0
  47. package/lib/types/client/TasksTab.js +43 -0
  48. package/lib/types/client/TasksView.d.ts +44 -0
  49. package/lib/types/client/TasksView.js +149 -0
  50. package/lib/types/client/TeamView.d.ts +33 -0
  51. package/lib/types/client/TeamView.js +210 -0
  52. package/lib/types/client/at-menu.d.ts +127 -0
  53. package/lib/types/client/at-menu.js +173 -0
  54. package/lib/types/client/draft-chips.d.ts +67 -0
  55. package/lib/types/client/draft-chips.js +95 -0
  56. package/lib/types/client/executions.d.ts +0 -30
  57. package/lib/types/client/executions.js +4 -34
  58. package/lib/types/client/icons-local.d.ts +25 -0
  59. package/lib/types/client/icons-local.js +10 -0
  60. package/lib/types/client/icons.d.ts +2 -1
  61. package/lib/types/client/icons.js +1 -0
  62. package/lib/types/client/index.js +276 -72
  63. package/lib/types/client/locales.d.ts +482 -52
  64. package/lib/types/client/locales.js +490 -60
  65. package/lib/types/client/nodes.d.ts +112 -17
  66. package/lib/types/client/nodes.js +197 -26
  67. package/lib/types/client/slots.d.ts +117 -47
  68. package/lib/types/client/task-tag.d.ts +32 -0
  69. package/lib/types/client/task-tag.js +33 -0
  70. package/lib/types/client/tasks.d.ts +50 -0
  71. package/lib/types/client/tasks.js +82 -0
  72. package/lib/types/client/team.d.ts +73 -0
  73. package/lib/types/client/team.js +187 -0
  74. package/lib/types/decisions.d.ts +49 -0
  75. package/lib/types/decisions.js +101 -0
  76. package/lib/types/deliveries.d.ts +17 -1
  77. package/lib/types/deliveries.js +82 -6
  78. package/lib/types/dispatch.d.ts +92 -4
  79. package/lib/types/dispatch.js +477 -109
  80. package/lib/types/help.d.ts +137 -0
  81. package/lib/types/help.js +216 -0
  82. package/lib/types/helps.d.ts +26 -0
  83. package/lib/types/helps.js +47 -0
  84. package/lib/types/index.d.ts +292 -52
  85. package/lib/types/index.js +1415 -199
  86. package/lib/types/jobs.d.ts +22 -0
  87. package/lib/types/jobs.js +77 -0
  88. package/lib/types/journal.d.ts +17 -27
  89. package/lib/types/journal.js +120 -43
  90. package/lib/types/messaging.d.ts +65 -0
  91. package/lib/types/messaging.js +160 -0
  92. package/lib/types/plan-service.js +7 -1
  93. package/lib/types/report-gate.d.ts +81 -0
  94. package/lib/types/report-gate.js +89 -0
  95. package/lib/types/tool-definition.d.ts +43 -20
  96. package/lib/types/tool-definition.js +145 -212
  97. package/lib/types/tool.d.ts +2 -2
  98. package/lib/types/tool.js +3 -2
  99. package/lib/types/types.d.ts +755 -78
  100. package/locale/zh.json +1 -1
  101. package/package.json +3 -3
  102. package/lib/types/client/RoomActivityDock.d.ts +0 -19
  103. package/lib/types/client/RoomActivityDock.js +0 -105
  104. package/lib/types/client/RoomDockCapsules.d.ts +0 -64
  105. package/lib/types/client/RoomDockCapsules.js +0 -277
  106. package/lib/types/client/RoomPlanTab.d.ts +0 -15
  107. package/lib/types/client/RoomPlanTab.js +0 -13
  108. package/lib/types/client/RoomTaskLineView.d.ts +0 -13
  109. package/lib/types/client/RoomTaskLineView.js +0 -21
@@ -11,12 +11,15 @@
11
11
  * RoomService.postMessage) and renders through the official user node.
12
12
  * @module @khorsheed/dsh-room/client/nodes
13
13
  */
14
+ import type { RoomDecisionEvent, RoomHelp, RoomQuestionAnswer, RoomQuestionItem } from '../types.ts';
14
15
  import type { SessionId } from '@deepseek-ai/dsh-session/types';
15
16
  import type { ConversationNodeDefinition } from '@deepseek-ai/dsh-client-ui-conversation/client';
16
17
  /** Chat node data of one member speech row. */
17
18
  export interface RoomSpeechData {
18
19
  /** Partial output from a stopped or failed run; never a completed answer. */
19
20
  readonly interrupted?: 'cancelled' | 'failed';
21
+ /** The member this reply was also reported to. */
22
+ readonly reportedTo?: string;
20
23
  /** Seq of the room/speech event. */
21
24
  readonly seq: number;
22
25
  /** Unix epoch ms from the event. */
@@ -27,6 +30,10 @@ export interface RoomSpeechData {
27
30
  readonly childSessionId?: SessionId;
28
31
  /** Dispatch→settle milliseconds, when measured. */
29
32
  readonly durationMs?: number;
33
+ /** The run this speech closed: its execution metadata (tokens) is keyed by it. */
34
+ readonly runId?: string;
35
+ /** The member's closing message in its own session. */
36
+ readonly messageId?: string;
30
37
  }
31
38
  /** Chat node data of one member run row. */
32
39
  export interface RoomRunData {
@@ -57,6 +64,55 @@ export interface RoomEventData {
57
64
  readonly invitedBy?: 'human' | 'agent';
58
65
  }
59
66
  /** Chat node data of one member-to-member notification relay row. */
67
+ /** Chat node data of a member's tool approval (the 允许 / 拒绝 line). */
68
+ export interface RoomApprovalData {
69
+ readonly seq: number;
70
+ readonly time: number;
71
+ readonly approvalId: string;
72
+ readonly member: string;
73
+ readonly tool: string;
74
+ readonly reason?: string;
75
+ readonly state: 'pending' | 'allowed-once' | 'rejected' | 'cancelled';
76
+ }
77
+ /** Chat node data of one decision the room made on its own (a timeline row). */
78
+ export interface RoomDecisionData {
79
+ readonly seq: number;
80
+ readonly time: number;
81
+ readonly decision: RoomDecisionEvent;
82
+ }
83
+ /** Chat node data of a cross-session help thread, in the asking room. */
84
+ export interface RoomHelpData {
85
+ readonly seq: number;
86
+ readonly time: number;
87
+ readonly help: RoomHelp;
88
+ }
89
+ /** Chat node data of a help request (or a follow-up on it), in the helper's own session. */
90
+ export interface RoomHelpRequestData {
91
+ readonly seq: number;
92
+ readonly time: number;
93
+ readonly help: string;
94
+ /** The asking room's session and title. */
95
+ readonly room: string;
96
+ readonly roomTitle: string;
97
+ /** The asking member. */
98
+ readonly from: string;
99
+ readonly op: 'request' | 'message';
100
+ /** The asker's own words. */
101
+ readonly text: string;
102
+ /** Another room asked this room: the member it went to, and whether the paused room holds it. */
103
+ readonly member?: string;
104
+ readonly held?: boolean;
105
+ }
106
+ /** Chat node data of a member's question card (pick-to-answer). */
107
+ export interface RoomQuestionData {
108
+ readonly seq: number;
109
+ readonly time: number;
110
+ readonly questionId: string;
111
+ readonly member: string;
112
+ readonly questions: readonly RoomQuestionItem[];
113
+ readonly state: 'pending' | 'answered' | 'cancelled';
114
+ readonly answers?: readonly RoomQuestionAnswer[];
115
+ }
60
116
  export interface RoomRelayData {
61
117
  /** Seq of the relay's latest journal event. */
62
118
  readonly seq: number;
@@ -68,17 +124,19 @@ export interface RoomRelayData {
68
124
  readonly to: string;
69
125
  readonly content: string;
70
126
  readonly state: 'pending' | 'confirmed' | 'dismissed' | 'sent';
127
+ /** Why the message waits (absent on legacy relays). */
128
+ readonly reason?: 'confirm' | 'hop-limit' | 'paused';
71
129
  }
72
- /** Chat node data of one task-advance line (the dim "✓ ada 完成了「…」" row). */
73
- export interface RoomTaskLineData {
74
- /** Seq of the task's LATEST journal event (add or the closing update). */
130
+ /** Chat node data of the held-reports line for one member. */
131
+ export interface RoomInboxData {
132
+ /** Seq of the latest hold (the line sits there). */
75
133
  readonly seq: number;
76
- /** Unix epoch ms of that event. */
77
134
  readonly time: number;
78
- readonly taskId: string;
79
135
  readonly member: string;
80
- readonly title: string;
81
- readonly status: 'pending' | 'in_progress' | 'done' | 'cancelled' | 'failed';
136
+ /** Held dispatch seqs not yet released. */
137
+ readonly held: readonly number[];
138
+ /** The model's yes-probability on the latest hold, when it answered. */
139
+ readonly probability?: number;
82
140
  }
83
141
  declare module '@deepseek-ai/dsh-client-ui-chat/client' {
84
142
  interface ChatNodeDataMap {
@@ -90,8 +148,18 @@ declare module '@deepseek-ai/dsh-client-ui-chat/client' {
90
148
  'room-event': RoomEventData;
91
149
  /** room: a member-to-member notification relay (gate row). */
92
150
  'room-relay': RoomRelayData;
93
- /** room: a task closure's advance line (visible only once done). */
94
- 'room-task-line': RoomTaskLineData;
151
+ /** room: a member's mode tool waiting for the human's approval. */
152
+ 'room-approval': RoomApprovalData;
153
+ /** room: a decision the room made on its own (rule or decision model). */
154
+ 'room-decision': RoomDecisionData;
155
+ /** room: a member's questions for the human (pick-to-answer card). */
156
+ 'room-question': RoomQuestionData;
157
+ /** room: a help thread this room opened with another session. */
158
+ 'room-help': RoomHelpData;
159
+ /** room: a help request another session's room sent this session. */
160
+ 'room-help-request': RoomHelpRequestData;
161
+ /** room: reports the gate holds in a member's inbox. */
162
+ 'room-inbox': RoomInboxData;
95
163
  }
96
164
  }
97
165
  /**
@@ -117,6 +185,18 @@ export declare const roomRunDefinition: ConversationNodeDefinition<RoomRunData>;
117
185
  * through the official user node (it is also appended as `user/message`).
118
186
  */
119
187
  export declare const roomEventDefinition: ConversationNodeDefinition<RoomEventData>;
188
+ /** A room decision: one single-event Context per `room/decision`. */
189
+ export declare const roomDecisionDefinition: ConversationNodeDefinition<RoomDecisionData>;
190
+ /**
191
+ * A member's tool approval: one Context per approval, started by
192
+ * `room/approval` and settled in place by `room/approval-resolved`.
193
+ */
194
+ export declare const roomApprovalDefinition: ConversationNodeDefinition<RoomApprovalData>;
195
+ /**
196
+ * A member's question card: one Context per card, started by `room/question`
197
+ * and settled in place (with the answers) by `room/question-resolved`.
198
+ */
199
+ export declare const roomQuestionDefinition: ConversationNodeDefinition<RoomQuestionData>;
120
200
  /**
121
201
  * The relay Definition: one Context per relay (matched on the relay id),
122
202
  * started by the `room/relay` event (pending) and updated by each
@@ -124,13 +204,28 @@ export declare const roomEventDefinition: ConversationNodeDefinition<RoomEventDa
124
204
  */
125
205
  export declare const roomRelayDefinition: ConversationNodeDefinition<RoomRelayData>;
126
206
  /**
127
- * The task-advance Definition: one Context per task (matched on the task id),
128
- * started by the `room/task-added` event and updated by each
129
- * `room/task-updated` edge. The node stays HIDDEN while the task is open or
130
- * cancelled and materializes — at the closing event's seq, so the line sits
131
- * at the moment of completion — only when the task closes done: the dim
132
- * advance line `✓ ada 完成了「API 定稿」── 目标进度 2/5`. (The goal progress
133
- * suffix is computed by the renderer from the room store, not folded here.)
207
+ * The held-reports line: one Context per member (its gate verdicts), visible
208
+ * while the gate holds reports for it — 「📥 dsh 的收件箱里有 2 条回报,先攒着」
209
+ * with a 现在交给 button — and hidden once they are released. It moves to the
210
+ * latest hold, so it always sits after the reply that was held.
211
+ */
212
+ export declare const roomInboxDefinition: ConversationNodeDefinition<RoomInboxData>;
213
+ /**
214
+ * A help thread in the asking room: one Context per thread, started by
215
+ * `room/help` and folded in place by its `room/help-message` and
216
+ * `room/help-state` events (one row whose state and messages update).
217
+ */
218
+ export declare const roomHelpDefinition: ConversationNodeDefinition<RoomHelpData>;
219
+ /**
220
+ * A help request in the helper's session: the `user/message` the room queued
221
+ * there (its `room-help` source) renders as a card naming the asking room
222
+ * instead of a plain injected-context row.
223
+ */
224
+ export declare const roomHelpRequestDefinition: ConversationNodeDefinition<RoomHelpRequestData>;
225
+ /**
226
+ * Another room asked this room for help (`room/help-in`): the same request
227
+ * card, naming the member it went to (and that it waits while this room is
228
+ * paused), with a jump back to the asking room.
134
229
  */
135
- export declare const roomTaskLineDefinition: ConversationNodeDefinition<RoomTaskLineData>;
230
+ export declare const roomHelpInDefinition: ConversationNodeDefinition<RoomHelpRequestData>;
136
231
  //# sourceMappingURL=nodes.d.ts.map
@@ -1,3 +1,4 @@
1
+ import { isAppendSurfaceEvent } from '@deepseek-ai/dsh-session/surface';
1
2
  /** The shared final-node materialization (single shape across the three kinds). */
2
3
  function viewNode(context, kind, visibility = 'visible') {
3
4
  if (context.state === undefined)
@@ -33,8 +34,11 @@ export const roomSpeechDefinition = {
33
34
  member: event.data.member,
34
35
  text: event.data.text,
35
36
  ...event.data.interrupted === undefined ? {} : { interrupted: event.data.interrupted },
37
+ ...event.data.reportedTo === undefined ? {} : { reportedTo: event.data.reportedTo },
36
38
  ...event.data.childSessionId === undefined ? {} : { childSessionId: event.data.childSessionId },
37
39
  ...event.data.durationMs === undefined ? {} : { durationMs: event.data.durationMs },
40
+ ...event.data.runId === undefined ? {} : { runId: event.data.runId },
41
+ ...event.data.messageId === undefined ? {} : { messageId: event.data.messageId },
38
42
  };
39
43
  },
40
44
  update: context => context.state,
@@ -127,6 +131,86 @@ export const roomEventDefinition = {
127
131
  update: context => context.state,
128
132
  buildViewNode: context => viewNode(context, 'room-event'),
129
133
  };
134
+ /** A room decision: one single-event Context per `room/decision`. */
135
+ export const roomDecisionDefinition = {
136
+ kind: 'room-decision',
137
+ target: 'chat',
138
+ match: event => event.type === 'room/decision' ? { id: event.data.id, role: 'start' } : null,
139
+ start: (_context, match) => {
140
+ const event = match.event;
141
+ if (event.type !== 'room/decision')
142
+ throw new Error('room-decision start requires a room/decision event');
143
+ return { seq: event.seq, time: event.time, decision: event.data };
144
+ },
145
+ update: context => context.state,
146
+ buildViewNode: context => viewNode(context, 'room-decision'),
147
+ };
148
+ /**
149
+ * A member's tool approval: one Context per approval, started by
150
+ * `room/approval` and settled in place by `room/approval-resolved`.
151
+ */
152
+ export const roomApprovalDefinition = {
153
+ kind: 'room-approval',
154
+ target: 'chat',
155
+ match: (event) => {
156
+ switch (event.type) {
157
+ case 'room/approval':
158
+ return { id: event.data.id, role: 'start' };
159
+ case 'room/approval-resolved':
160
+ return { id: event.data.id, role: 'update' };
161
+ default:
162
+ return null;
163
+ }
164
+ },
165
+ start: (_context, match) => {
166
+ const event = match.event;
167
+ if (event.type !== 'room/approval')
168
+ throw new Error('room-approval start requires a room/approval event');
169
+ return {
170
+ seq: event.seq, time: event.time, approvalId: event.data.id, member: event.data.member, tool: event.data.tool,
171
+ ...event.data.reason === undefined ? {} : { reason: event.data.reason },
172
+ state: 'pending',
173
+ };
174
+ },
175
+ update: (context, match) => {
176
+ const event = match.event;
177
+ if (event.type !== 'room/approval-resolved')
178
+ return context.state;
179
+ return { ...context.state, state: event.data.outcome };
180
+ },
181
+ buildViewNode: context => viewNode(context, 'room-approval'),
182
+ };
183
+ /**
184
+ * A member's question card: one Context per card, started by `room/question`
185
+ * and settled in place (with the answers) by `room/question-resolved`.
186
+ */
187
+ export const roomQuestionDefinition = {
188
+ kind: 'room-question',
189
+ target: 'chat',
190
+ match: (event) => {
191
+ switch (event.type) {
192
+ case 'room/question':
193
+ return { id: event.data.id, role: 'start' };
194
+ case 'room/question-resolved':
195
+ return { id: event.data.id, role: 'update' };
196
+ default:
197
+ return null;
198
+ }
199
+ },
200
+ start: (_context, match) => {
201
+ const event = match.event;
202
+ if (event.type !== 'room/question')
203
+ throw new Error('room-question start requires a room/question event');
204
+ return { seq: event.seq, time: event.time, questionId: event.data.id, member: event.data.member, questions: event.data.questions, state: 'pending' };
205
+ },
206
+ update: (context, match) => {
207
+ const event = match.event;
208
+ if (event.type !== 'room/question-resolved')
209
+ return context.state;
210
+ return { ...context.state, state: event.data.outcome, ...event.data.answers === undefined ? {} : { answers: event.data.answers } };
211
+ },
212
+ buildViewNode: context => viewNode(context, 'room-question'),
213
+ };
130
214
  /**
131
215
  * The relay Definition: one Context per relay (matched on the relay id),
132
216
  * started by the `room/relay` event (pending) and updated by each
@@ -156,6 +240,7 @@ export const roomRelayDefinition = {
156
240
  from: event.data.from,
157
241
  to: event.data.to,
158
242
  content: event.data.content,
243
+ ...event.data.reason === undefined ? {} : { reason: event.data.reason },
159
244
  state: 'pending',
160
245
  };
161
246
  },
@@ -168,45 +253,131 @@ export const roomRelayDefinition = {
168
253
  buildViewNode: context => viewNode(context, 'room-relay'),
169
254
  };
170
255
  /**
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.)
256
+ * The held-reports line: one Context per member (its gate verdicts), visible
257
+ * while the gate holds reports for it — 「📥 dsh 的收件箱里有 2 条回报,先攒着」
258
+ * with a 现在交给 button — and hidden once they are released. It moves to the
259
+ * latest hold, so it always sits after the reply that was held.
178
260
  */
179
- export const roomTaskLineDefinition = {
180
- kind: 'room-task-line',
261
+ export const roomInboxDefinition = {
262
+ kind: 'room-inbox',
263
+ target: 'chat',
264
+ match: (event) => {
265
+ if (event.type !== 'room/report-gate' || event.data.verdict === 'deliver')
266
+ return null;
267
+ return { id: event.data.memberId, role: event.data.verdict === 'hold' ? 'start' : 'update' };
268
+ },
269
+ start: (context, match) => {
270
+ const event = match.event;
271
+ if (event.type !== 'room/report-gate')
272
+ throw new Error('room-inbox requires a room/report-gate event');
273
+ const previous = context.state?.held ?? [];
274
+ return {
275
+ seq: event.seq, time: event.time, member: event.data.member,
276
+ held: [...previous.filter(seq => seq !== event.data.dispatchSeq), event.data.dispatchSeq],
277
+ ...event.data.probability === undefined ? {} : { probability: event.data.probability },
278
+ };
279
+ },
280
+ update: (context, match) => {
281
+ const event = match.event;
282
+ if (event.type !== 'room/report-gate' || context.state === undefined)
283
+ return context.state;
284
+ if (event.data.verdict === 'hold') {
285
+ return { ...context.state, seq: event.seq, time: event.time, held: [...context.state.held.filter(seq => seq !== event.data.dispatchSeq), event.data.dispatchSeq],
286
+ ...event.data.probability === undefined ? {} : { probability: event.data.probability } };
287
+ }
288
+ return { ...context.state, held: context.state.held.filter(seq => seq !== event.data.dispatchSeq) };
289
+ },
290
+ buildViewNode: context => viewNode(context, 'room-inbox', context.state !== undefined && context.state.held.length > 0 ? 'visible' : 'hidden'),
291
+ };
292
+ /**
293
+ * A help thread in the asking room: one Context per thread, started by
294
+ * `room/help` and folded in place by its `room/help-message` and
295
+ * `room/help-state` events (one row whose state and messages update).
296
+ */
297
+ export const roomHelpDefinition = {
298
+ kind: 'room-help',
181
299
  target: 'chat',
182
300
  match: (event) => {
183
301
  switch (event.type) {
184
- case 'room/task-added':
302
+ case 'room/help':
185
303
  return { id: event.data.id, role: 'start' };
186
- case 'room/task-updated':
187
- return { id: event.data.id, role: 'update' };
304
+ case 'room/help-message':
305
+ case 'room/help-state':
306
+ return { id: event.data.help, role: 'update' };
188
307
  default:
189
308
  return null;
190
309
  }
191
310
  },
192
311
  start: (_context, match) => {
193
312
  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
- };
313
+ if (event.type !== 'room/help')
314
+ throw new Error('room-help start requires a room/help event');
315
+ return { seq: event.seq, time: event.time, help: { ...event.data, state: 'queued', since: event.time, messages: [], time: event.time } };
204
316
  },
205
317
  update: (context, match) => {
206
318
  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 };
319
+ const help = context.state.help;
320
+ if (event.type === 'room/help-message') {
321
+ return { ...context.state, help: { ...help, messages: [...help.messages, { ...event.data, time: event.time }] } };
322
+ }
323
+ if (event.type === 'room/help-state') {
324
+ const { error: _previous, ...rest } = help;
325
+ return { ...context.state, help: { ...rest, state: event.data.state, since: help.state === event.data.state ? help.since : event.time, ...event.data.error === undefined ? {} : { error: event.data.error } } };
326
+ }
327
+ return context.state;
328
+ },
329
+ buildViewNode: context => viewNode(context, 'room-help'),
330
+ };
331
+ /** A message source's help fields, when it is a room help source. */
332
+ function helpSourceOf(source) {
333
+ const value = source;
334
+ if (value?.kind !== 'room-help' || typeof value.help !== 'string' || typeof value.room !== 'string')
335
+ return undefined;
336
+ const text = (key) => typeof value[key] === 'string' ? value[key] : '';
337
+ return { help: value.help, room: value.room, roomTitle: text('roomTitle'), from: text('from'), op: value.op === 'message' ? 'message' : 'request', text: text('text') };
338
+ }
339
+ /**
340
+ * A help request in the helper's session: the `user/message` the room queued
341
+ * there (its `room-help` source) renders as a card naming the asking room
342
+ * instead of a plain injected-context row.
343
+ */
344
+ export const roomHelpRequestDefinition = {
345
+ kind: 'room-help-request',
346
+ target: 'chat',
347
+ match: (event) => {
348
+ if (event.type !== 'user/message' || !isAppendSurfaceEvent(event) || helpSourceOf(event.data.source) === undefined)
349
+ return null;
350
+ return { id: String(event.seq), role: 'start' };
351
+ },
352
+ start: (_context, match) => {
353
+ const event = match.event;
354
+ const source = event.type === 'user/message' ? helpSourceOf(event.data.source) : undefined;
355
+ if (source === undefined)
356
+ throw new Error('room-help-request start requires a room-help message');
357
+ return { seq: event.seq, time: event.time, ...source };
210
358
  },
211
- buildViewNode: context => viewNode(context, 'room-task-line', context.state !== undefined && context.state.status === 'done' ? 'visible' : 'hidden'),
359
+ update: context => context.state,
360
+ buildViewNode: context => viewNode(context, 'room-help-request'),
361
+ };
362
+ /**
363
+ * Another room asked this room for help (`room/help-in`): the same request
364
+ * card, naming the member it went to (and that it waits while this room is
365
+ * paused), with a jump back to the asking room.
366
+ */
367
+ export const roomHelpInDefinition = {
368
+ kind: 'room-help-in',
369
+ target: 'chat',
370
+ match: event => event.type === 'room/help-in' ? { id: String(event.seq), role: 'start' } : null,
371
+ start: (_context, match) => {
372
+ const event = match.event;
373
+ if (event.type !== 'room/help-in')
374
+ throw new Error('room-help-in start requires a room/help-in event');
375
+ const data = event.data;
376
+ return {
377
+ seq: event.seq, time: event.time, help: data.id, room: data.room, roomTitle: data.roomTitle, from: data.from,
378
+ op: data.op, text: data.text, member: data.member, ...data.held === true ? { held: true } : {},
379
+ };
380
+ },
381
+ update: context => context.state,
382
+ buildViewNode: context => viewNode(context, 'room-help-request'),
212
383
  };