@opencxh/domain 1.221.0 → 1.222.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.
@@ -99,6 +99,23 @@ export declare function channelKindOf(type: string): ActivityChannelKind | undef
99
99
  export declare function isMessageShape(shape: ActivityShape | undefined): boolean;
100
100
  /** See {@link isMessageShape}. For the built-in types. */
101
101
  export declare function isMessageType(type: string): boolean;
102
+ /**
103
+ * Does an answer of this kind fold into the list under the message it answers, instead of
104
+ * taking its own place in the timeline?
105
+ *
106
+ * Only notes. A mail carrying a `parentId` is still a message that was really sent, at a real
107
+ * moment, and burying it under the thing it answered would take it out of the chronology the
108
+ * rest of the conversation is read in — and out of view entirely while the thread is folded.
109
+ * An internal note has no such life of its own: it exists *about* that message.
110
+ *
111
+ * Keyed on the shape, so the rule has exactly one definition and an app-declared note kind
112
+ * behaves the same — see {@link ActivityTypeRegistry.nestsUnderParent}. `parentId` itself stays
113
+ * set on every reply regardless: it is a true fact about the row, and mail threading, the
114
+ * provider `In-Reply-To` and the reply notification all read it.
115
+ */
116
+ export declare function isNoteShape(shape: ActivityShape | undefined): boolean;
117
+ /** See {@link isNoteShape}. For the built-in types. */
118
+ export declare function nestsUnderParent(type: string): boolean;
102
119
  /** Can a human reply to it? Not to notes — those go nowhere. */
103
120
  export declare function isReplyableType(type: string): boolean;
104
121
  /**
@@ -70,6 +70,8 @@ export declare class ActivityTypeRegistry {
70
70
  * {@link ResolvedActivityType}.
71
71
  */
72
72
  isMessage(type: string): boolean;
73
+ /** Folds into the list under the message it answers. See {@link isNoteShape}. */
74
+ nestsUnderParent(type: string): boolean;
73
75
  /** Can a person reply to this? Notes cannot — those go nowhere. */
74
76
  isReplyable(type: string): boolean;
75
77
  /** Readable content for a model: messages, notes and transcripts. */
@@ -1,6 +1,7 @@
1
1
  import { ActingIdentity } from '../../platform/identity';
2
2
  import { TranscriptSegment } from '../../platform/media';
3
3
  import { AIMessageInput, AIMessageOutput } from '../ai-message/types';
4
+ import { ReactionSummary } from '../reaction/types';
4
5
  export type CallStatus = "new" | "connecting" | "ringing" | "connected" | "held" | "ended" | "failed";
5
6
  export type CallDirection = "inbound" | "outbound";
6
7
  export type CallType = "audio" | "video" | "data" | "screen-share";
@@ -34,6 +35,14 @@ export interface BaseActivity {
34
35
  contactId?: string;
35
36
  };
36
37
  direction: "inbound" | "outbound" | "internal" | "none";
38
+ /**
39
+ * The activity this one answers, if any — the in-app thread, one level deep.
40
+ *
41
+ * Not `EmailPayload.threadId`: that is the provider's own conversation id and groups a whole
42
+ * mail thread. This points at one activity, and an internal note can hang off a customer's
43
+ * mail with it. A reply to a reply stores the *top* parent, so the feed never nests twice.
44
+ */
45
+ parentId?: string;
37
46
  externalIds?: string[];
38
47
  attachments?: Attachment[];
39
48
  /**
@@ -42,6 +51,14 @@ export interface BaseActivity {
42
51
  * indicator while true.
43
52
  */
44
53
  pending?: boolean;
54
+ /**
55
+ * The emoji on this message, as the list and get routes project them.
56
+ *
57
+ * Read-only and **never stored on the activity row**: reactions are their own rows, keyed
58
+ * by (resource, actor, emoji), so two people reacting at once cannot race on one column.
59
+ * The server joins them on in one bulk query per page; a writer that sets this is ignored.
60
+ */
61
+ reactions?: ReactionSummary[];
45
62
  /** Epoch ms — matches the DB model and Interaction.createdAt. */
46
63
  createdAt?: number;
47
64
  updatedAt?: number;
@@ -0,0 +1 @@
1
+ export * from './types';
@@ -0,0 +1 @@
1
+ export {};
@@ -0,0 +1,73 @@
1
+ /**
2
+ * Who reacted.
3
+ *
4
+ * Not a bare `userId`, and not `MutationAuthor`: a reaction can come from outside the
5
+ * organisation. A Teams chat is synced with the customer in it, and their 👍 has to land
6
+ * somewhere — as a `contact`, with the name the provider gave. Mirrors `BaseActivity.author`,
7
+ * which already carries that same four-way distinction.
8
+ *
9
+ * `id` is the user id, the app name, or the provider's id for a contact; absent for `system`.
10
+ */
11
+ export interface ReactionActor {
12
+ type: "user" | "contact" | "app" | "system";
13
+ id?: string;
14
+ /** Shown on hover. Kept on the row because an external reactor has no record to look up. */
15
+ name?: string;
16
+ }
17
+ /**
18
+ * One emoji, put on one resource, by one actor.
19
+ *
20
+ * Addressed by (resourceType, resourceId) — the same `ResourceRef` shape read-state and the
21
+ * attribute store use — so an activity, a work comment, a file or a knowledge article all
22
+ * reuse this without the store learning what any of them are.
23
+ *
24
+ * A row per (resource, actor, emoji), never an array on the resource: two people reacting at
25
+ * the same moment would otherwise race on one column, and the unique index makes both the
26
+ * toggle and a re-sync from a provider idempotent.
27
+ */
28
+ export interface Reaction {
29
+ id: string;
30
+ organizationId: string;
31
+ resourceType: string;
32
+ resourceId: string;
33
+ actor: ReactionActor;
34
+ /**
35
+ * The literal character, e.g. `"👍"` — never a shortcode and never a provider's name for it.
36
+ * One vocabulary means no mapping table in the core; the six legacy Teams names are
37
+ * translated at the edge, in the app that speaks to Graph.
38
+ */
39
+ emoji: string;
40
+ createdAt?: number;
41
+ }
42
+ /** One chip: an emoji, how many put it there, and who — for the hover. */
43
+ export interface ReactionSummary {
44
+ emoji: string;
45
+ count: number;
46
+ actors: ReactionActor[];
47
+ }
48
+ /** `POST /reaction/:type/:id` body. `on` absent means "flip whatever it is now". */
49
+ export interface ToggleReactionRequest {
50
+ emoji: string;
51
+ on?: boolean;
52
+ /**
53
+ * Who to record it for, instead of the caller.
54
+ *
55
+ * **Honoured for an app-to-app caller only**, and ignored outright for a user — otherwise
56
+ * anyone could put an emoji on a message in a colleague's name. It exists for provider
57
+ * sync: a reaction pulled out of Teams belongs to the person who left it there, not to the
58
+ * app that carried it across.
59
+ */
60
+ actor?: ReactionActor;
61
+ }
62
+ /** Reactions per resource id, as the list endpoints hand them to a feed. */
63
+ export type ReactionsByResource = Record<string, ReactionSummary[]>;
64
+ /**
65
+ * Rows to chips.
66
+ *
67
+ * Most-used first so the chip that carries the most agreement sits nearest the message, with
68
+ * ties broken by which emoji appeared first — a stable order, so a chip does not jump when
69
+ * someone else reacts. Pure: this is the one piece of the feature worth a test.
70
+ */
71
+ export declare function summarizeReactions(rows: Reaction[]): ReactionSummary[];
72
+ /** Did this user put this emoji there? The chip's own state, asked without a second query. */
73
+ export declare function hasReacted(summary: ReactionSummary, userId: string | undefined): boolean;
@@ -1,3 +1,4 @@
1
+ import { ReactionSummary } from '../reaction/types';
1
2
  import { WorkStatusCategory } from './types';
2
3
  /**
3
4
  * The activity feed under a work item.
@@ -36,6 +37,8 @@ export interface WorkActivity {
36
37
  * "the customer answered" and would wake every waiting assignment on the item.
37
38
  */
38
39
  direction: "internal";
40
+ /** The activity this one answers — same one-level thread as comms' `BaseActivity.parentId`. */
41
+ parentId?: string;
39
42
  /** `html` is the composer's markup for a comment; `text` stays the plain reading of it. */
40
43
  payload: {
41
44
  text?: string;
@@ -46,6 +49,12 @@ export interface WorkActivity {
46
49
  toStatus?: string;
47
50
  toCategory?: WorkStatusCategory;
48
51
  };
52
+ /**
53
+ * The emoji on this comment, as the list route projects them. Read-only and never stored
54
+ * on the row — see `BaseActivity.reactions`, which this mirrors, and the generic store in
55
+ * the communication app that both read from.
56
+ */
57
+ reactions?: ReactionSummary[];
49
58
  createdAt?: number;
50
59
  updatedAt?: number;
51
60
  }
@@ -10,8 +10,17 @@ import { WorkItem, WorkProject } from './types';
10
10
  /** The scope kinds `apps/work` claims through `/provider/scope/describe`. */
11
11
  export declare const WORK_ITEM_SCOPE_KIND = "work_item";
12
12
  export declare const WORK_PROJECT_SCOPE_KIND = "work_project";
13
+ /**
14
+ * A row in an item's feed, addressable on its own.
15
+ *
16
+ * Claimed so the generic stores can hang something off a single comment — reactions today,
17
+ * whatever is resource-agnostic tomorrow. It inherits its item's access exactly the way
18
+ * comms' `activity:` inherits its conversation's: a comment is not a second boundary.
19
+ */
20
+ export declare const WORK_ACTIVITY_SCOPE_KIND = "work_activity";
13
21
  export declare function workItemScopeKey(itemId: string): string;
14
22
  export declare function workProjectScopeKey(projectId: string): string;
23
+ export declare function workActivityScopeKey(activityId: string): string;
15
24
  /**
16
25
  * The human key: `SAL-142`, or `SAL-142-1` for a subitem.
17
26
  *