@sparkelf/dsh-group-chat 0.5.2 → 0.5.4

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/lib/index.js CHANGED
@@ -492,6 +492,29 @@ const messageId = (event) => {
492
492
  * message text. Parsing text would misread a code sample containing `@`, and it
493
493
  * would let any message smuggle itself into an execution path.
494
494
  */
495
+ /**
496
+ * Member ids a prompt's sender declared on its source, in pick order.
497
+ *
498
+ * The composer's roster source inserts chips with `appearance: 'mention'`, and the reference
499
+ * pipeline hands those ids to the Host on the prompt; this reads them back. Only ids that source
500
+ * owns qualify — another source's chip is a file, a session, or a quoted message, never a member.
501
+ */
502
+ const declaredMentions = (source) => {
503
+ const raw = source?.mentions;
504
+ if (!Array.isArray(raw)) return [];
505
+ const out = [];
506
+ const seen = /* @__PURE__ */ new Set();
507
+ for (const entry of raw) {
508
+ if (typeof entry !== "object" || entry === null) continue;
509
+ const record = entry;
510
+ if (record.sourceId !== "@sparkelf/dsh-group-chat/member") continue;
511
+ const memberId = record.itemId;
512
+ if (typeof memberId !== "string" || memberId.length === 0 || seen.has(memberId)) continue;
513
+ seen.add(memberId);
514
+ out.push(memberId);
515
+ }
516
+ return out;
517
+ };
495
518
  const mentionsOf = (event) => {
496
519
  const raw = eventSource(event)?.["mentions"];
497
520
  if (!Array.isArray(raw)) return [];
@@ -769,6 +792,49 @@ const cleanupRoomSessions = async (input) => {
769
792
  };
770
793
  };
771
794
  /**
795
+ * Register a room's Session with the workspace that owns its directory.
796
+ *
797
+ * The shell's `openSession` selects a Session that belongs to a Workspace, so a room created
798
+ * with a `cwd` and no workspace id is not selectable until this runs. Registration is idempotent
799
+ * on the registry (`attachSession` checks membership before writing), which is what lets the
800
+ * discovery poll run it on every boot without duplicating rows.
801
+ *
802
+ * Never throws: a room that cannot be registered still reads and writes through its Host, only
803
+ * the main view cannot follow it, and that is a state worth reporting rather than a failed open.
804
+ * @param registry - the workspace registry surface, absent on a minimal host.
805
+ * @param directory - the room directory the workspace is resolved by.
806
+ * @param sessionId - the room's canonical Session.
807
+ * @param log - optional warning sink; the plugin passes its Cordis logger.
808
+ * @returns whether the Session is now attached.
809
+ */
810
+ const attachRoomSession = async (registry, directory, sessionId, log) => {
811
+ if (registry === void 0) {
812
+ log?.warn?.("[group-chat] the workspace registry is unavailable; the room session stays unlisted", sessionId);
813
+ return {
814
+ attached: false,
815
+ reason: "registry-unavailable"
816
+ };
817
+ }
818
+ try {
819
+ const workspace = await registry.resolveByPath(directory);
820
+ if (workspace === void 0) {
821
+ log?.warn?.("[group-chat] no workspace owns the room directory; the room session stays unlisted", sessionId, directory);
822
+ return {
823
+ attached: false,
824
+ reason: "workspace-not-found"
825
+ };
826
+ }
827
+ await workspace.attachSession(sessionId);
828
+ return { attached: true };
829
+ } catch (error) {
830
+ log?.warn?.("[group-chat] attaching the room session to its workspace failed", sessionId, error);
831
+ return {
832
+ attached: false,
833
+ reason: "attach-failed"
834
+ };
835
+ }
836
+ };
837
+ /**
772
838
  * Wrapper for built-in http.js to emulate the browser XMLHttpRequest object.
773
839
  *
774
840
  * This can be used with JS designed for browsers to improve reuse of code and
@@ -9248,6 +9314,7 @@ var RoomSessions = class {
9248
9314
  const resumed = await this.resolvePersisted(sessionId, directory);
9249
9315
  if (resumed !== void 0) {
9250
9316
  await this.installRoomModel(resumed, room, false);
9317
+ await this.attachToWorkspace(sessionId, directory);
9251
9318
  return resumed;
9252
9319
  }
9253
9320
  const created = await this.ctx.sessionController.create({
@@ -9262,6 +9329,7 @@ var RoomSessions = class {
9262
9329
  const session = this.existing(roomId);
9263
9330
  if (session === void 0) throw new Error(`group-chat could not open a session for room ${roomId}`);
9264
9331
  await this.installRoomModel(session, room, true);
9332
+ await this.attachToWorkspace(sessionId, directory);
9265
9333
  return session;
9266
9334
  }
9267
9335
  /** Load a persisted room Session, or `undefined` only when the controller denies it exists. */
@@ -9308,6 +9376,20 @@ var RoomSessions = class {
9308
9376
  get archiveRegistry() {
9309
9377
  return hostService(this.ctx, "workspaceRegistry");
9310
9378
  }
9379
+ /**
9380
+ * Attach the room's Session to the workspace that owns its directory.
9381
+ *
9382
+ * The shell's `openSession` selects a Session that belongs to a Workspace, so a room created
9383
+ * with a `cwd` alone is not selectable and clicking it moves the highlight only. Attaching is
9384
+ * idempotent on the registry side, and every failure here is logged rather than fatal: a room
9385
+ * whose registration failed still reads and writes through its Host, only the main view
9386
+ * cannot follow it.
9387
+ * @param sessionId - the room's canonical Session.
9388
+ * @param directory - the room directory the workspace is resolved by.
9389
+ */
9390
+ async attachToWorkspace(sessionId, directory) {
9391
+ await attachRoomSession(hostService(this.ctx, "workspaceRegistry"), directory, sessionId, this.ctx.logger);
9392
+ }
9311
9393
  /** Hide a replacement the controller minted instead of honoring the room id. */
9312
9394
  async archiveReplacement(sessionId) {
9313
9395
  try {
@@ -9755,7 +9837,8 @@ const activate = (ctx, target) => {
9755
9837
  if (typeof messageId === "string" && messageId.startsWith(WARM_UP_PREFIX)) return;
9756
9838
  if (data.source?.kind === QUOTE_CONTEXT_KIND) return;
9757
9839
  const content = data.content;
9758
- const mentions = mentionedMemberIds(textOfContent(content), runtimes.get(roomId)?.members ?? []);
9840
+ const declared = declaredMentions(data.source);
9841
+ const mentions = declared.length > 0 ? declared : mentionedMemberIds(textOfContent(content), runtimes.get(roomId)?.members ?? []);
9759
9842
  const quote = pendingQuotes.get(roomId);
9760
9843
  const outgoing = quote === void 0 ? content : withQuote(content, quote);
9761
9844
  backend.sendMessage(roomId, {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@sparkelf/dsh-group-chat",
3
- "version": "0.5.2",
3
+ "version": "0.5.4",
4
4
  "description": "Human-agent group chat for DataOps-managed DSH workspaces: rooms broadcast to every member, and only a member's own @mention wakes that member's agent.",
5
5
  "type": "module",
6
6
  "main": "./lib/index.js",