@khorsheed/dsh-room 0.2.4 → 0.2.5

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 (45) hide show
  1. package/CHANGELOG.md +13 -0
  2. package/lib/client.js +2256 -1074
  3. package/lib/index.js +548 -36
  4. package/lib/typert.host.js +249 -65
  5. package/lib/typert.remote-client.d.ts +4 -1
  6. package/lib/typert.remote-client.js +208 -53
  7. package/lib/types/client/InviteDialog.js +11 -4
  8. package/lib/types/client/RoomAttachments.d.ts +20 -0
  9. package/lib/types/client/RoomAttachments.js +36 -0
  10. package/lib/types/client/RoomComposer.d.ts +6 -12
  11. package/lib/types/client/RoomComposer.js +116 -12
  12. package/lib/types/client/RoomQuestionView.d.ts +10 -2
  13. package/lib/types/client/RoomQuestionView.js +18 -5
  14. package/lib/types/client/RoomRunView.d.ts +1 -1
  15. package/lib/types/client/RoomRunView.js +15 -5
  16. package/lib/types/client/RoomSpeechView.d.ts +24 -1
  17. package/lib/types/client/RoomSpeechView.js +39 -3
  18. package/lib/types/client/RoomStatsLine.d.ts +3 -1
  19. package/lib/types/client/RoomStatsLine.js +3 -1
  20. package/lib/types/client/RoomTaskCapsule.d.ts +6 -1
  21. package/lib/types/client/RoomTaskCapsule.js +2 -2
  22. package/lib/types/client/TasksView.js +1 -1
  23. package/lib/types/client/TeamView.js +1 -1
  24. package/lib/types/client/icons.d.ts +2 -1
  25. package/lib/types/client/icons.js +1 -0
  26. package/lib/types/client/index.js +94 -15
  27. package/lib/types/client/locales.d.ts +44 -0
  28. package/lib/types/client/locales.js +44 -0
  29. package/lib/types/client/nodes.d.ts +22 -3
  30. package/lib/types/client/nodes.js +28 -5
  31. package/lib/types/client/path-mentions.d.ts +34 -0
  32. package/lib/types/client/path-mentions.js +39 -0
  33. package/lib/types/client/slots.d.ts +51 -1
  34. package/lib/types/dispatch.d.ts +50 -0
  35. package/lib/types/dispatch.js +115 -1
  36. package/lib/types/index.d.ts +69 -1
  37. package/lib/types/index.js +362 -20
  38. package/lib/types/jobs.js +7 -3
  39. package/lib/types/journal.d.ts +1 -1
  40. package/lib/types/journal.js +18 -2
  41. package/lib/types/types.d.ts +77 -0
  42. package/package.json +10 -6
  43. package/lib/journal-B4FOGO6S.js +0 -482
  44. package/lib/journal-BWL3-i0s.js +0 -462
  45. package/lib/journal-Rid9AAnD.js +0 -485
@@ -133,6 +133,8 @@ export interface RoomMemberRemovedEvent {
133
133
  * log.
134
134
  */
135
135
  export interface RoomDispatchEvent {
136
+ /** The human message's admitted attachments, re-sent with the text on a retry. */
137
+ readonly attachments?: readonly RoomDispatchAttachment[];
136
138
  /** Goal work/report correlation, absent for ordinary chat. */
137
139
  readonly plan?: {
138
140
  readonly goalId: string;
@@ -209,6 +211,11 @@ export interface RoomSpeechEvent {
209
211
  readonly reportedTo?: string;
210
212
  readonly member: string;
211
213
  readonly text: string;
214
+ /** Questions the round asked, at their offsets in `text` (-1 = no split point found: shown first, marked mid-round). */
215
+ readonly asks?: readonly {
216
+ readonly id: string;
217
+ readonly at: number;
218
+ }[];
212
219
  readonly childSessionId?: SessionId;
213
220
  /** Native admission→settle milliseconds, excluding queue time. */
214
221
  readonly durationMs?: number;
@@ -235,6 +242,11 @@ export interface RoomRunUsage {
235
242
  readonly reasoningTokens?: number;
236
243
  }
237
244
  /** A member run's lifecycle edge. */
245
+ /** A running member's card moves to the bottom of the conversation; its run is untouched. */
246
+ export interface RoomRunMovedEvent {
247
+ readonly member: string;
248
+ readonly startedAt: number;
249
+ }
238
250
  export interface RoomRunStateEvent {
239
251
  readonly childSessionId?: SessionId;
240
252
  /** The child session's round this run reached (the last turn/start it wrote); terminal edges only, live sessions only. */
@@ -489,6 +501,12 @@ export interface RoomQuestionEvent {
489
501
  readonly member: string;
490
502
  readonly memberId: string;
491
503
  readonly questions: readonly RoomQuestionItem[];
504
+ /** The asker's round (its run id): the question renders inside that round's card and reply. */
505
+ readonly runId?: string;
506
+ /** The tail of what the round had said when it asked — where the reply splits around the question. */
507
+ readonly before?: string;
508
+ /** A consent gate (room_help's 允许/不允许): its answer only matters while the call waits, so it is never answered late. */
509
+ readonly consent?: true;
492
510
  }
493
511
  /** How a member's questions ended. */
494
512
  export interface RoomQuestionResolvedEvent {
@@ -496,6 +514,20 @@ export interface RoomQuestionResolvedEvent {
496
514
  /** `cancelled`: the call ended, timed out, or the host restarted before an answer. */
497
515
  readonly outcome: 'answered' | 'cancelled';
498
516
  readonly answers?: readonly RoomQuestionAnswer[];
517
+ /** Answered after the card expired: the answers went to the member as a new message instead of its (gone) waiting call. */
518
+ readonly late?: true;
519
+ }
520
+ /** Every question card the room has seen, with its outcome — inline question blocks read it. */
521
+ export interface RoomAsk {
522
+ readonly id: string;
523
+ readonly member: string;
524
+ readonly questions: readonly RoomQuestionItem[];
525
+ readonly state: 'pending' | 'answered' | 'cancelled';
526
+ readonly answers?: readonly RoomQuestionAnswer[];
527
+ readonly late?: true;
528
+ readonly consent?: true;
529
+ readonly runId?: string;
530
+ readonly time: number;
499
531
  }
500
532
  /** A member's questions still waiting for the human. */
501
533
  export interface RoomQuestion {
@@ -696,6 +728,8 @@ declare module '@deepseek-ai/dsh-session/types' {
696
728
  'room/speech': RoomSpeechEvent;
697
729
  /** Member run lifecycle. */
698
730
  'room/run-state': RoomRunStateEvent;
731
+ /** A running member's card re-anchors below a reply or human message that landed after it (presentation only). */
732
+ 'room/run-moved': RoomRunMovedEvent;
699
733
  /** Log-only observed metadata for a single execution. */
700
734
  'room/execution-metadata': RoomExecutionMetadataEvent;
701
735
  /** Notification gate: a member-to-member relay arrived (pending). */
@@ -829,6 +863,8 @@ export interface RoomExecution extends RoomMemberRun {
829
863
  * design note).
830
864
  */
831
865
  export interface RoomState {
866
+ /** All question cards with their outcomes (inline blocks in run cards and replies). */
867
+ readonly askLog?: readonly RoomAsk[];
832
868
  readonly executions?: readonly RoomExecution[];
833
869
  readonly plan?: Omit<RoomPlan, 'requests'>;
834
870
  readonly deliveries?: readonly RoomDelivery[];
@@ -904,6 +940,11 @@ export type RoomFailure = {
904
940
  } | {
905
941
  readonly code: 'empty-text';
906
942
  }
943
+ /** An attachment was refused (not uploaded to this room, too large, an unsupported image…). */
944
+ | {
945
+ readonly code: 'attachment-invalid';
946
+ readonly message: string;
947
+ }
907
948
  /** The public role is longer than 30 characters. */
908
949
  | {
909
950
  readonly code: 'role-too-long';
@@ -1002,6 +1043,16 @@ export interface RoomMemberMessage {
1002
1043
  */
1003
1044
  export type RoomMemberMessageReceipt = 'sent' | 'pending-confirm' | 'busy' | `sent ${string}` | `pending-confirm ${string}`;
1004
1045
  /** isRoom probe: does this session carry the room marker? */
1046
+ /** pendingCards request: no arguments (every live room the host knows). */
1047
+ export interface RoomPendingCardsRequest {
1048
+ readonly sessionId?: SessionId;
1049
+ }
1050
+ /** One question/approval card waiting for the human in a room (the session list's 「等你处理」). */
1051
+ export interface RoomPendingCard {
1052
+ readonly sessionId: string;
1053
+ readonly id: string;
1054
+ readonly kind: 'question' | 'approval';
1055
+ }
1005
1056
  export interface RoomIsRoomRequest {
1006
1057
  /** Session to probe. */
1007
1058
  readonly sessionId: SessionId;
@@ -1199,6 +1250,30 @@ export interface RoomPostMessageRequest {
1199
1250
  readonly targets?: readonly string[];
1200
1251
  /** The composer's task tag: `new` or a task root id (see RoomSetTaskHintRequest). */
1201
1252
  readonly task?: string;
1253
+ /**
1254
+ * Draft attachments in the official composer's wire form: an image's
1255
+ * base64 bytes, or a file's upload receipt (uploaded to this room session
1256
+ * on pick). The host admits them into the human message; members get them
1257
+ * by local path.
1258
+ */
1259
+ readonly attachments?: readonly RoomAttachmentInput[];
1260
+ }
1261
+ /** One composer attachment as it crosses the wire (the official prompt part shape). */
1262
+ export type RoomAttachmentInput = {
1263
+ readonly type: 'image';
1264
+ readonly data: string;
1265
+ readonly mediaType: string;
1266
+ readonly name?: string;
1267
+ } | {
1268
+ readonly type: 'file';
1269
+ readonly receiptId: string;
1270
+ };
1271
+ /** An admitted attachment as a dispatch records it: what members are told to read. */
1272
+ export interface RoomDispatchAttachment {
1273
+ readonly kind: 'image' | 'file';
1274
+ readonly name?: string;
1275
+ /** Absolute host path; absent when the attachment store is not file-backed. */
1276
+ readonly path?: string;
1202
1277
  }
1203
1278
  /** What the postMessage parser made of the raw text. */
1204
1279
  export interface RoomPostParsed {
@@ -1472,10 +1547,12 @@ export interface RoomJob {
1472
1547
  /** 1-based ordinal in the room, for 「任务 N」 and the coordinator's `task` argument. */
1473
1548
  readonly number: number;
1474
1549
  readonly title: string;
1550
+ /** `from`: the member id a member/coordinator dispatch came from; absent = the human. */
1475
1551
  readonly messages: readonly {
1476
1552
  readonly id: string;
1477
1553
  readonly time: number;
1478
1554
  readonly text: string;
1555
+ readonly from?: string;
1479
1556
  readonly by?: RoomTaskLinkEvent['by'];
1480
1557
  readonly probability?: number;
1481
1558
  }[];
package/package.json CHANGED
@@ -7,7 +7,7 @@
7
7
  "cordis",
8
8
  "multi-agent"
9
9
  ],
10
- "version": "0.2.4",
10
+ "version": "0.2.5",
11
11
  "type": "module",
12
12
  "main": "lib/index.js",
13
13
  "types": "lib/types/index.d.ts",
@@ -75,6 +75,7 @@
75
75
  "@deepseek-ai/dsh-client-locale": "^0.1.0-rc.6 || ^0.2.0-rc.1 || ^0.2.1-rc.1",
76
76
  "@deepseek-ai/dsh-client-ui-conversation": "^0.1.0-rc.6 || ^0.2.0-rc.1 || ^0.2.1-rc.1",
77
77
  "@deepseek-ai/dsh-client-ui-primitives": "^0.1.0-rc.6 || ^0.2.0-rc.1 || ^0.2.1-rc.1",
78
+ "@deepseek-ai/dsh-client-ui-sidebar-right": "^0.1.0-rc.6 || ^0.2.0-rc.1 || ^0.2.1-rc.1",
78
79
  "@deepseek-ai/dsh-client-ui-slots": "^0.1.0-rc.6 || ^0.2.0-rc.1 || ^0.2.1-rc.1",
79
80
  "@deepseek-ai/dsh-llm": "^0.1.0-rc.6 || ^0.2.0-rc.1 || ^0.2.1-rc.1",
80
81
  "@deepseek-ai/dsh-session": "^0.1.0-rc.6 || ^0.2.0-rc.1 || ^0.2.1-rc.1",
@@ -85,9 +86,9 @@
85
86
  "@deepseek-ai/dsh-tool-todo": "^0.1.0-rc.6 || ^0.2.0-rc.1 || ^0.2.1-rc.1",
86
87
  "@deepseek-ai/dsh-tools": "^0.1.0-rc.6 || ^0.2.0-rc.1 || ^0.2.1-rc.1",
87
88
  "@deepseek-ai/dsh-typert-protocol": "^0.1.0-rc.6 || ^0.2.0-rc.1 || ^0.2.1-rc.1",
88
- "@khorsheed/dsh-local-agent": "^0.2.1",
89
- "react": "^18.2.0",
90
- "@deepseek-ai/dsh-client-ui-sidebar-right": "^0.1.0-rc.6 || ^0.2.0-rc.1 || ^0.2.1-rc.1"
89
+ "@deepseek-ai/dsh-util-workspace-path": "^0.1.0-rc.6 || ^0.2.0-rc.1 || ^0.2.1-rc.1",
90
+ "@khorsheed/dsh-local-agent": "^0.2.2",
91
+ "react": "^18.2.0"
91
92
  },
92
93
  "peerDependenciesMeta": {
93
94
  "@deepseek-ai/dsh-agent-preset-registry": {
@@ -96,6 +97,9 @@
96
97
  "@deepseek-ai/dsh-agent-presets": {
97
98
  "optional": true
98
99
  },
100
+ "@deepseek-ai/dsh-client-ui-sidebar-right": {
101
+ "optional": true
102
+ },
99
103
  "@deepseek-ai/dsh-session-stats": {
100
104
  "optional": true
101
105
  },
@@ -105,10 +109,10 @@
105
109
  "@deepseek-ai/dsh-tool-todo": {
106
110
  "optional": true
107
111
  },
108
- "@khorsheed/dsh-local-agent": {
112
+ "@deepseek-ai/dsh-util-workspace-path": {
109
113
  "optional": true
110
114
  },
111
- "@deepseek-ai/dsh-client-ui-sidebar-right": {
115
+ "@khorsheed/dsh-local-agent": {
112
116
  "optional": true
113
117
  }
114
118
  },
@@ -1,482 +0,0 @@
1
- //#region lib/types/deliveries.js
2
- /** Accepted delivery intents and their latest durable outcomes. */
3
- function replayDeliveries(events) {
4
- const rows = /* @__PURE__ */ new Map();
5
- for (const event of events) if (event.type === "room/dispatch" && event.data.targetIds !== void 0) for (const memberId of event.data.targetIds) {
6
- const id = `${event.seq}:${memberId}`;
7
- if (!rows.has(id)) rows.set(id, {
8
- id,
9
- dispatchSeq: event.seq,
10
- memberId,
11
- status: "queued",
12
- text: event.data.text,
13
- origin: event.data.origin ?? "human",
14
- ...event.data.replyTo === void 0 ? {} : { replyTo: event.data.replyTo },
15
- ...event.data.from === void 0 ? {} : { from: event.data.from },
16
- ...event.data.kind === void 0 ? {} : { kind: event.data.kind },
17
- ...event.data.plan === void 0 ? {} : { plan: event.data.plan }
18
- });
19
- }
20
- else if (event.type === "room/delivery-state") {
21
- const row = rows.get(event.data.id);
22
- if (row === void 0 || row.memberId !== event.data.memberId || row.dispatchSeq !== event.data.dispatchSeq) continue;
23
- rows.set(row.id, {
24
- ...row,
25
- status: event.data.state,
26
- ...event.data.error === void 0 ? {} : { error: event.data.error }
27
- });
28
- }
29
- return [...rows.values()];
30
- }
31
- //#endregion
32
- //#region lib/types/journal.js
33
- /**
34
- * Room journal replay: folds the room session's `room/*` custom events into
35
- * the derived {@link RoomState} — roster, notification relays, the task
36
- * board, and run states. Pure: the Remote surface reads through it, the
37
- * dispatch engine reads instruction cursors through it, and the unit tests
38
- * exercise it without a cordis composition. Reload-replay recovers the exact
39
- * same state, which is what makes the journal the room's single source of
40
- * truth. There is NO blackboard fold: member prompts never consume the
41
- * room's running log (see the design note) — speech/dispatch events are
42
- * journaled for UI projection and replay only.
43
- * @module @khorsheed/dsh-room/journal
44
- */
45
- /**
46
- * The native DSH member's addressing name in new rooms. It starts as the
47
- * coordinator; bare messages follow the selected coordinator thereafter.
48
- * Existing journals retain their recorded names, including legacy main.
49
- */
50
- const MAIN_AGENT_MEMBER = "dsh";
51
- /**
52
- * Every `room/*` session-event type this package introduces. Room registers
53
- * them into the harness's KNOWN_SESSION_EVENT_TYPES persistence catalog at
54
- * apply time (see the RoomService constructor): the catalog's read-path
55
- * refusal exists so a build that does NOT understand an event type never
56
- * silently mangles the log — a build with room mounted understands these, so
57
- * registration is the semantically correct declaration, and a build without
58
- * room keeps refusing them. The catalog's own header defers a registration
59
- * surface for out-of-repo plugins "until such a consumer exists"; this
60
- * in-place add is that surface's temporary form — migrate to the official
61
- * one when it lands upstream.
62
- */
63
- const ROOM_EVENT_TYPES = [
64
- "room/created",
65
- "room/plan-state",
66
- "room/coordinator",
67
- "room/delivery-state",
68
- "room/member-added",
69
- "room/member-updated",
70
- "room/member-removed",
71
- "room/dispatch",
72
- "room/speech",
73
- "room/run-state",
74
- "room/execution-metadata",
75
- "room/relay",
76
- "room/relay-resolved",
77
- "room/task-added",
78
- "room/task-updated",
79
- "room/task-edited",
80
- "room/goal",
81
- "room/settings"
82
- ];
83
- /** Collaboration settings of a room that never changed them. */
84
- const DEFAULT_ROOM_SETTINGS = {
85
- memberMessages: "free",
86
- hopLimit: 10,
87
- paused: false
88
- };
89
- /** Whether an event log carries the room identity marker. */
90
- function isRoomLog(events) {
91
- return events.some((event) => event.type === "room/created");
92
- }
93
- /**
94
- * Parse the leading `@name` tokens of a raw composer message.
95
- * @param raw - the raw text.
96
- * @returns the addressed names (deduped, order-preserving) and the body with
97
- * the mention prefix stripped. Only LEADING tokens address: an `@name`
98
- * inside prose is plain text (a bare message goes to the selected coordinator).
99
- */
100
- function parseMentions(raw) {
101
- const targets = [];
102
- let rest = raw.trim();
103
- for (;;) {
104
- const match = /^@(\S+)(?:\s+|$)/.exec(rest);
105
- if (match === null) break;
106
- const name = match[1];
107
- if (!targets.includes(name)) targets.push(name);
108
- rest = rest.slice(match[0].length).trim();
109
- }
110
- return {
111
- targets,
112
- text: rest
113
- };
114
- }
115
- /**
116
- * Fold a session's event log into the room state.
117
- *
118
- * Write-side invariants (invite/update/remove validate before appending) are
119
- * NOT re-enforced here: a replayed log may be hand-edited or forked, so the
120
- * fold is defensive — a duplicate member-added keeps the first record (first
121
- * wins, a replay cannot re-seat a member), and updates/removals naming an
122
- * unknown member are dropped. Unknown event types (e.g. a `room/note` from
123
- * the dropped blackboard design) fall through the switch and are skipped.
124
- * Removal also drops the member's run state: the roster reads clean after a
125
- * remove. Relays and tasks fold by id, latest resolution winning.
126
- * @param events - the session's event log.
127
- * @returns the folded room state (empty for a non-room log).
128
- */
129
- function replay(events) {
130
- const members = [];
131
- const byName = /* @__PURE__ */ new Map();
132
- const relays = [];
133
- const relayById = /* @__PURE__ */ new Map();
134
- const tasks = [];
135
- const taskById = /* @__PURE__ */ new Map();
136
- const runs = /* @__PURE__ */ new Map();
137
- const executions = /* @__PURE__ */ new Map();
138
- let goal;
139
- let settings = DEFAULT_ROOM_SETTINGS;
140
- let coordinator;
141
- for (const event of events) switch (event.type) {
142
- case "room/coordinator":
143
- if (event.data.revision > (coordinator?.revision ?? 0)) coordinator = event.data;
144
- break;
145
- case "room/member-added": {
146
- if (byName.has(event.data.name)) break;
147
- const added = {
148
- id: event.data.id ?? `legacy:${event.seq}`,
149
- name: event.data.name,
150
- kind: event.data.kind,
151
- invitedBy: event.data.invitedBy,
152
- ...event.data.provider === void 0 ? {} : { provider: event.data.provider },
153
- ...event.data.instructions === void 0 ? {} : { instructions: event.data.instructions },
154
- ...event.data.role === void 0 ? {} : { role: event.data.role },
155
- ...event.data.preset === void 0 ? {} : { preset: event.data.preset },
156
- ...event.data.cwd === void 0 ? {} : { cwd: event.data.cwd },
157
- ...event.data.model === void 0 ? {} : { model: event.data.model },
158
- ...event.data.childSessionId === void 0 ? {} : { childSessionId: event.data.childSessionId }
159
- };
160
- byName.set(added.name, added);
161
- members.push(added);
162
- break;
163
- }
164
- case "room/member-updated": {
165
- const member = byName.get(event.data.name);
166
- if (member === void 0) break;
167
- const withoutInstructions = event.data.instructions === null ? (({ instructions: _cleared, ...rest }) => rest)(member) : {
168
- ...member,
169
- ...event.data.instructions === void 0 ? {} : { instructions: event.data.instructions }
170
- };
171
- const withoutRole = event.data.role === null ? (({ role: _cleared, ...rest }) => rest)(withoutInstructions) : {
172
- ...withoutInstructions,
173
- ...event.data.role === void 0 ? {} : { role: event.data.role }
174
- };
175
- const withoutCwd = event.data.cwd === null ? (({ cwd: _cleared, ...rest }) => rest)(withoutRole) : {
176
- ...withoutRole,
177
- ...event.data.cwd === void 0 ? {} : { cwd: event.data.cwd }
178
- };
179
- const patched = {
180
- ...event.data.model === null ? (({ model: _cleared, ...rest }) => rest)(withoutCwd) : {
181
- ...withoutCwd,
182
- ...event.data.model === void 0 ? {} : { model: event.data.model }
183
- },
184
- ...event.data.childSessionId === void 0 ? {} : { childSessionId: event.data.childSessionId }
185
- };
186
- for (const [id, execution] of executions) if (execution.memberId === member.id && execution.childSessionId === void 0 && patched.childSessionId !== void 0) executions.set(id, {
187
- ...execution,
188
- childSessionId: patched.childSessionId
189
- });
190
- const rename = event.data.rename;
191
- if (rename === void 0 || rename === member.name || byName.has(rename)) {
192
- byName.set(member.name, patched);
193
- members[members.indexOf(member)] = patched;
194
- break;
195
- }
196
- const renamed = {
197
- ...patched,
198
- name: rename
199
- };
200
- byName.delete(member.name);
201
- byName.set(rename, renamed);
202
- members[members.indexOf(member)] = renamed;
203
- for (let index = 0; index < tasks.length; index++) {
204
- const task = tasks[index];
205
- if (task.member !== member.name && task.blockedBy !== member.name) continue;
206
- const migrated = {
207
- ...task,
208
- ...task.member === member.name ? { member: rename } : {},
209
- ...task.blockedBy === member.name ? { blockedBy: rename } : {}
210
- };
211
- taskById.set(migrated.id, migrated);
212
- tasks[index] = migrated;
213
- }
214
- for (let index = 0; index < relays.length; index++) {
215
- const relay = relays[index];
216
- if (relay.from !== member.name && relay.to !== member.name) continue;
217
- const migrated = {
218
- ...relay,
219
- from: relay.from === member.name ? rename : relay.from,
220
- to: relay.to === member.name ? rename : relay.to
221
- };
222
- relayById.set(migrated.id, migrated);
223
- relays[index] = migrated;
224
- }
225
- const run = runs.get(member.name);
226
- if (run !== void 0) {
227
- runs.delete(member.name);
228
- runs.set(rename, {
229
- ...run,
230
- member: rename
231
- });
232
- }
233
- break;
234
- }
235
- case "room/member-removed": {
236
- const member = byName.get(event.data.name);
237
- if (member === void 0) break;
238
- byName.delete(event.data.name);
239
- members.splice(members.indexOf(member), 1);
240
- runs.delete(event.data.name);
241
- break;
242
- }
243
- case "room/relay": {
244
- if (relayById.has(event.data.id)) break;
245
- const relay = {
246
- id: event.data.id,
247
- from: event.data.from,
248
- to: event.data.to,
249
- content: event.data.content,
250
- state: "pending",
251
- ...event.data.provenance === void 0 ? {} : { provenance: event.data.provenance },
252
- ...event.data.reason === void 0 ? {} : { reason: event.data.reason },
253
- ...event.data.intent === void 0 ? {} : { intent: event.data.intent }
254
- };
255
- relayById.set(relay.id, relay);
256
- relays.push(relay);
257
- break;
258
- }
259
- case "room/relay-resolved": {
260
- const relay = relayById.get(event.data.id);
261
- if (relay === void 0) break;
262
- const resolved = {
263
- ...relay,
264
- state: event.data.state
265
- };
266
- relayById.set(resolved.id, resolved);
267
- relays[relays.indexOf(relay)] = resolved;
268
- break;
269
- }
270
- case "room/task-added": {
271
- if (taskById.has(event.data.id)) break;
272
- const task = {
273
- id: event.data.id,
274
- member: event.data.member,
275
- title: event.data.title,
276
- ...event.data.deliveryId === void 0 ? {} : { deliveryId: event.data.deliveryId },
277
- status: event.data.status,
278
- ...event.data.blockedBy === void 0 ? {} : { blockedBy: event.data.blockedBy },
279
- updatedAt: event.time
280
- };
281
- taskById.set(task.id, task);
282
- tasks.push(task);
283
- break;
284
- }
285
- case "room/task-updated": {
286
- const task = taskById.get(event.data.id);
287
- if (task === void 0) break;
288
- const updated = {
289
- ...task,
290
- status: event.data.status,
291
- updatedAt: event.time
292
- };
293
- taskById.set(updated.id, updated);
294
- tasks[tasks.indexOf(task)] = updated;
295
- break;
296
- }
297
- case "room/task-edited": {
298
- const task = taskById.get(event.data.id);
299
- if (task === void 0) break;
300
- const edited = {
301
- ...event.data.blockedBy === null ? (({ blockedBy: _cleared, ...rest }) => rest)(task) : {
302
- ...task,
303
- ...event.data.blockedBy === void 0 ? {} : { blockedBy: event.data.blockedBy }
304
- },
305
- ...event.data.title === void 0 ? {} : { title: event.data.title },
306
- ...event.data.member === void 0 ? {} : { member: event.data.member },
307
- updatedAt: event.time
308
- };
309
- taskById.set(edited.id, edited);
310
- tasks[tasks.indexOf(task)] = edited;
311
- break;
312
- }
313
- case "room/settings":
314
- settings = {
315
- memberMessages: event.data.memberMessages ?? settings.memberMessages,
316
- hopLimit: event.data.hopLimit ?? settings.hopLimit,
317
- paused: event.data.paused ?? settings.paused
318
- };
319
- break;
320
- case "room/goal": {
321
- const text = event.data.text.trim();
322
- goal = text === "" ? void 0 : text;
323
- break;
324
- }
325
- case "room/execution-metadata": {
326
- const previous = executions.get(event.data.runId);
327
- if (previous) executions.set(previous.id, {
328
- ...previous,
329
- ...event.data
330
- });
331
- break;
332
- }
333
- case "room/run-state": {
334
- const member = byName.get(event.data.member);
335
- const id = event.data.runId ?? `legacy:${member?.id ?? event.data.member}:${event.data.startedAt}`;
336
- const previous = executions.get(id);
337
- executions.set(id, {
338
- id,
339
- ...member?.id === void 0 ? {} : { memberId: member.id },
340
- ...member?.childSessionId === void 0 ? {} : { childSessionId: member.childSessionId },
341
- ...member?.provider === void 0 ? {} : { provider: member.provider },
342
- ...previous,
343
- ...event.data,
344
- ...event.data.state === "running" || event.data.elapsedMs !== void 0 ? {} : { elapsedMs: Math.max(0, event.time - event.data.startedAt) }
345
- });
346
- const current = runs.get(event.data.member);
347
- if (event.data.state !== "running" && event.data.runId !== void 0 && current?.runId !== void 0 && current.runId !== event.data.runId) break;
348
- runs.set(event.data.member, {
349
- member: event.data.member,
350
- state: event.data.state,
351
- startedAt: event.data.startedAt,
352
- ...event.data.runId === void 0 ? {} : { runId: event.data.runId },
353
- ...event.data.elapsedMs === void 0 ? {} : { elapsedMs: event.data.elapsedMs },
354
- ...event.data.error === void 0 ? {} : { error: event.data.error }
355
- });
356
- break;
357
- }
358
- }
359
- const planEvent = events.filter((event) => event.type === "room/plan-state").at(-1);
360
- const plan = planEvent?.type === "room/plan-state" ? (({ requests: _requests, ...view }) => view)(planEvent.data) : void 0;
361
- const deliveries = replayDeliveries(events);
362
- return {
363
- members,
364
- relays,
365
- tasks,
366
- ...executions.size === 0 ? {} : { executions: [...executions.values()] },
367
- ...plan === void 0 ? {} : { plan },
368
- ...deliveries.length === 0 ? {} : { deliveries },
369
- runs: [...runs.values()],
370
- ...coordinator === void 0 ? {} : { coordinator },
371
- ...goal === void 0 ? {} : { goal },
372
- settings
373
- };
374
- }
375
- /**
376
- * The member's dispatch cursor as of JUST BEFORE a given dispatch event: the
377
- * latest earlier dispatch naming them. The engine reads the role-instruction
378
- * carry against this cursor (pass {@link Number.MAX_SAFE_INTEGER} for a
379
- * dispatch that is not itself journaled, e.g. a relay delivery).
380
- * @param events - the session's event log.
381
- * @param member - the dispatch target.
382
- * @param beforeSeq - the current dispatch event's seq.
383
- * @returns the previous dispatch seq, or undefined on the member's first dispatch.
384
- */
385
- function previousCursor(events, member, beforeSeq) {
386
- let cursor;
387
- for (const event of events) {
388
- if (event.type !== "room/dispatch" || event.seq >= beforeSeq) continue;
389
- if (event.data.targets.includes(member)) cursor = event.seq;
390
- }
391
- return cursor;
392
- }
393
- /**
394
- * What changed in the room since a member's last dispatch, as one-line
395
- * notes for the next prompt: other members joining or leaving, renames, role
396
- * changes, the member's own role change, and a goal change. The full roster
397
- * rides only the first dispatch's briefing; later dispatches carry these
398
- * deltas instead (the member's own session holds the rest), and a member who
399
- * wants the whole picture calls `room_read`. Model, cwd and delegation-handle
400
- * updates never reach a prompt. A member with no cursor gets no deltas — the
401
- * briefing already covers everything.
402
- * @param events - the session's event log.
403
- * @param cursor - the member's pre-dispatch cursor ({@link previousCursor}).
404
- * @param self - the member's CURRENT name (its own rename is not news to it).
405
- * @returns the change notes (empty when nothing changed).
406
- */
407
- function roomChangesSince(events, cursor, self) {
408
- if (cursor === void 0) return [];
409
- const notes = [];
410
- let goal;
411
- for (const event of events) {
412
- if (event.seq <= cursor) continue;
413
- if (event.type === "room/member-added" && event.data.name !== self && event.data.kind !== "main-agent") {
414
- const detail = [event.data.role, event.data.provider].filter(Boolean).join(" · ");
415
- notes.push(`${event.data.name} 加入${detail === "" ? "" : `(${detail})`}`);
416
- } else if (event.type === "room/member-removed" && event.data.name !== self) notes.push(`${event.data.name} 离开`);
417
- else if (event.type === "room/member-updated") {
418
- if (event.data.rename !== void 0 && event.data.rename !== self) notes.push(`${event.data.name} 改名为 ${event.data.rename}`);
419
- if (event.data.role !== void 0) {
420
- const who = event.data.rename ?? event.data.name;
421
- const subject = who === self ? "你的职责" : `${who} 的职责`;
422
- notes.push(event.data.role === null ? `${subject}已清除` : `${subject}改为:${event.data.role}`);
423
- }
424
- } else if (event.type === "room/goal") goal = { text: event.data.text.trim() };
425
- }
426
- if (goal !== void 0) notes.push(goal.text === "" ? "房间目标已清除" : `房间目标更新为:${goal.text}`);
427
- return notes;
428
- }
429
- /**
430
- * The role-instructions carry for the next dispatch to a member: 'initial'
431
- * when the member has never been dispatched (their instructions open the
432
- * first prompt), 'update' when the latest member-added/member-updated event
433
- * naming them postdates their dispatch cursor (an edit rides the next
434
- * dispatch as a context update). Instructions events older than the cursor
435
- * were already carried.
436
- * @param events - the session's event log.
437
- * @param member - the dispatch target.
438
- * @param cursor - the member's pre-dispatch cursor ({@link previousCursor}).
439
- * @returns the carry, or undefined when nothing needs carrying.
440
- */
441
- function pendingInstructions(events, member, cursor) {
442
- let lastSeq;
443
- let lastInstructions;
444
- for (const event of events) if (event.type === "room/member-added" && event.data.name === member && event.data.instructions !== void 0) {
445
- lastSeq = event.seq;
446
- lastInstructions = event.data.instructions;
447
- } else if (event.type === "room/member-updated" && event.data.name === member) {
448
- if (event.data.instructions === null) {
449
- lastSeq = void 0;
450
- lastInstructions = void 0;
451
- } else if (event.data.instructions !== void 0) {
452
- lastSeq = event.seq;
453
- lastInstructions = event.data.instructions;
454
- }
455
- }
456
- if (lastSeq === void 0 || lastInstructions === void 0) return void 0;
457
- if (cursor === void 0) return {
458
- kind: "initial",
459
- instructions: lastInstructions
460
- };
461
- if (lastSeq > cursor) return {
462
- kind: "update",
463
- instructions: lastInstructions
464
- };
465
- }
466
- /** Resolve legacy join identity through renames; never key a new dispatch by display name. */
467
- function memberId(events, member) {
468
- if (member.id !== void 0) return member.id;
469
- let name = member.name;
470
- for (let index = events.length - 1; index >= 0; index--) {
471
- const event = events[index];
472
- if (event.type === "room/member-updated" && event.data.rename === name) name = event.data.name;
473
- if (event.type === "room/member-added" && event.data.name === name) return event.data.id ?? `legacy:${event.seq}`;
474
- }
475
- throw new Error("Room member has no join identity");
476
- }
477
- function coordinatorMember(state, events) {
478
- if (state.coordinator === void 0) return state.members.find((member) => member.kind === "main-agent");
479
- return state.members.find((member) => (member.id ?? (events === void 0 ? void 0 : memberId(events, member))) === state.coordinator.memberId);
480
- }
481
- //#endregion
482
- export { isRoomLog as a, pendingInstructions as c, roomChangesSince as d, replayDeliveries as f, coordinatorMember as i, previousCursor as l, MAIN_AGENT_MEMBER as n, memberId as o, ROOM_EVENT_TYPES as r, parseMentions as s, DEFAULT_ROOM_SETTINGS as t, replay as u };