@things-factory/ai-assistant 10.1.207 → 10.1.210
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/client/components/assistant-chat.ts +238 -9
- package/client/components/assistant-dock.ts +9 -4
- package/client/components/assistant-invite.ts +138 -0
- package/client/components/assistant-session-manager.ts +53 -12
- package/client/components/proposal-request.test.ts +10 -0
- package/client/components/roster-identity.test.ts +2 -12
- package/client/components/roster-identity.ts +0 -6
- package/client/setup-assistant-dock.ts +1 -0
- package/client/utils/assistant-session-actions.ts +3 -1
- package/client/utils/assistant-session-admin.ts +9 -2
- package/client/utils/assistant-session-controller.ts +1 -1
- package/client/utils/assistant-typing.test.ts +33 -0
- package/client/utils/assistant-typing.ts +53 -0
- package/dist-client/components/assistant-chat.d.ts +28 -0
- package/dist-client/components/assistant-chat.js +259 -8
- package/dist-client/components/assistant-chat.js.map +1 -1
- package/dist-client/components/assistant-dock.js +12 -4
- package/dist-client/components/assistant-dock.js.map +1 -1
- package/dist-client/components/assistant-invite.d.ts +39 -0
- package/dist-client/components/assistant-invite.js +150 -0
- package/dist-client/components/assistant-invite.js.map +1 -0
- package/dist-client/components/assistant-session-manager.d.ts +5 -1
- package/dist-client/components/assistant-session-manager.js +40 -10
- package/dist-client/components/assistant-session-manager.js.map +1 -1
- package/dist-client/components/proposal-request.test.js +8 -0
- package/dist-client/components/proposal-request.test.js.map +1 -1
- package/dist-client/components/roster-identity.d.ts +0 -2
- package/dist-client/components/roster-identity.js +0 -7
- package/dist-client/components/roster-identity.js.map +1 -1
- package/dist-client/components/roster-identity.test.js +2 -13
- package/dist-client/components/roster-identity.test.js.map +1 -1
- package/dist-client/setup-assistant-dock.js +1 -0
- package/dist-client/setup-assistant-dock.js.map +1 -1
- package/dist-client/tsconfig.tsbuildinfo +1 -1
- package/dist-client/utils/assistant-session-actions.d.ts +1 -0
- package/dist-client/utils/assistant-session-actions.js +3 -1
- package/dist-client/utils/assistant-session-actions.js.map +1 -1
- package/dist-client/utils/assistant-session-admin.d.ts +5 -2
- package/dist-client/utils/assistant-session-admin.js +3 -0
- package/dist-client/utils/assistant-session-admin.js.map +1 -1
- package/dist-client/utils/assistant-session-controller.d.ts +1 -1
- package/dist-client/utils/assistant-session-controller.js.map +1 -1
- package/dist-client/utils/assistant-typing.d.ts +20 -0
- package/dist-client/utils/assistant-typing.js +48 -0
- package/dist-client/utils/assistant-typing.js.map +1 -0
- package/dist-client/utils/assistant-typing.test.d.ts +1 -0
- package/dist-client/utils/assistant-typing.test.js +30 -0
- package/dist-client/utils/assistant-typing.test.js.map +1 -0
- package/dist-server/service/assistant-chat-resolver.js +5 -12
- package/dist-server/service/assistant-chat-resolver.js.map +1 -1
- package/dist-server/service/assistant-mode/index.d.ts +1 -1
- package/dist-server/service/assistant-mode/index.js +4 -1
- package/dist-server/service/assistant-mode/index.js.map +1 -1
- package/dist-server/service/assistant-mode/mode-categories.js +9 -2
- package/dist-server/service/assistant-mode/mode-categories.js.map +1 -1
- package/dist-server/service/assistant-mode/screen-rules.d.ts +19 -0
- package/dist-server/service/assistant-mode/screen-rules.js +40 -6
- package/dist-server/service/assistant-mode/screen-rules.js.map +1 -1
- package/dist-server/service/chat-message/chat-message-room.js +3 -1
- package/dist-server/service/chat-message/chat-message-room.js.map +1 -1
- package/dist-server/service/chat-message/chat-typing-subscription.d.ts +10 -0
- package/dist-server/service/chat-message/chat-typing-subscription.js +68 -0
- package/dist-server/service/chat-message/chat-typing-subscription.js.map +1 -0
- package/dist-server/service/chat-message/chat-typing.d.ts +28 -0
- package/dist-server/service/chat-message/chat-typing.js +40 -0
- package/dist-server/service/chat-message/chat-typing.js.map +1 -0
- package/dist-server/service/chat-session/attachment-tool-result.d.ts +1 -0
- package/dist-server/service/chat-session/attachment-tool-result.js +6 -0
- package/dist-server/service/chat-session/attachment-tool-result.js.map +1 -1
- package/dist-server/service/chat-session/attachment-tools.js +2 -2
- package/dist-server/service/chat-session/attachment-tools.js.map +1 -1
- package/dist-server/service/chat-session/chat-session.d.ts +2 -1
- package/dist-server/service/chat-session/chat-session.js +1 -1
- package/dist-server/service/chat-session/chat-session.js.map +1 -1
- package/dist-server/service/chat-session/session-activity-broadcast.d.ts +12 -0
- package/dist-server/service/chat-session/session-activity-broadcast.js +15 -0
- package/dist-server/service/chat-session/session-activity-broadcast.js.map +1 -1
- package/dist-server/service/chat-session/session-activity-publish.d.ts +2 -0
- package/dist-server/service/chat-session/session-activity-publish.js +5 -2
- package/dist-server/service/chat-session/session-activity-publish.js.map +1 -1
- package/dist-server/service/chat-session/session-visibility.d.ts +17 -2
- package/dist-server/service/chat-session/session-visibility.js +28 -3
- package/dist-server/service/chat-session/session-visibility.js.map +1 -1
- package/dist-server/service/chat-session/session-write-gate.d.ts +1 -1
- package/dist-server/service/chat-session/session-write-gate.js +1 -1
- package/dist-server/service/chat-session/session-write-gate.js.map +1 -1
- package/dist-server/service/chat-session-participant/index.d.ts +1 -0
- package/dist-server/service/chat-session-participant/index.js +4 -1
- package/dist-server/service/chat-session-participant/index.js.map +1 -1
- package/dist-server/service/chat-session-participant/invite-notification.d.ts +16 -1
- package/dist-server/service/chat-session-participant/invite-notification.js +34 -5
- package/dist-server/service/chat-session-participant/invite-notification.js.map +1 -1
- package/dist-server/service/chat-session-participant/mention-invite.d.ts +30 -4
- package/dist-server/service/chat-session-participant/mention-invite.js +78 -31
- package/dist-server/service/chat-session-participant/mention-invite.js.map +1 -1
- package/dist-server/service/chat-session-participant/read-roster.js +5 -2
- package/dist-server/service/chat-session-participant/read-roster.js.map +1 -1
- package/dist-server/service/chat-session-resolver.d.ts +27 -0
- package/dist-server/service/chat-session-resolver.js +226 -25
- package/dist-server/service/chat-session-resolver.js.map +1 -1
- package/dist-server/service/data-entry/entry-target.d.ts +19 -0
- package/dist-server/service/data-entry/entry-target.js +20 -5
- package/dist-server/service/data-entry/entry-target.js.map +1 -1
- package/dist-server/service/data-entry/entry-tools.js +2 -2
- package/dist-server/service/data-entry/entry-tools.js.map +1 -1
- package/dist-server/service/data-entry/entry-update.d.ts +2 -1
- package/dist-server/service/data-entry/entry-update.js +5 -3
- package/dist-server/service/data-entry/entry-update.js.map +1 -1
- package/dist-server/service/data-read/list-read.d.ts +10 -0
- package/dist-server/service/data-read/list-read.js +12 -0
- package/dist-server/service/data-read/list-read.js.map +1 -1
- package/dist-server/service/data-read/read-target.d.ts +7 -0
- package/dist-server/service/data-read/read-target.js +2 -2
- package/dist-server/service/data-read/read-target.js.map +1 -1
- package/dist-server/service/data-read/read-tools.js +1 -1
- package/dist-server/service/data-read/read-tools.js.map +1 -1
- package/dist-server/service/index.d.ts +4 -2
- package/dist-server/service/index.js +9 -1
- package/dist-server/service/index.js.map +1 -1
- package/dist-server/service/testing/conversation-script.d.ts +1 -1
- package/dist-server/service/testing/conversation-script.js.map +1 -1
- package/dist-server/service/testing/mode-conformance.js +4 -0
- package/dist-server/service/testing/mode-conformance.js.map +1 -1
- package/dist-server/tsconfig.tsbuildinfo +1 -1
- package/package.json +9 -9
- package/server/explicit-graphql-types.test.ts +1 -1
- package/server/service/assistant-chat-resolver.ts +7 -12
- package/server/service/assistant-mode/index.ts +1 -1
- package/server/service/assistant-mode/mode-categories.ts +11 -3
- package/server/service/assistant-mode/mode-front.test.ts +53 -1
- package/server/service/assistant-mode/resolve.test.ts +2 -1
- package/server/service/assistant-mode/screen-rules.test.ts +41 -3
- package/server/service/assistant-mode/screen-rules.ts +44 -5
- package/server/service/chat-message/chat-message-room.test.ts +7 -2
- package/server/service/chat-message/chat-message-room.ts +3 -1
- package/server/service/chat-message/chat-typing-subscription.ts +52 -0
- package/server/service/chat-message/chat-typing.test.ts +45 -0
- package/server/service/chat-message/chat-typing.ts +48 -0
- package/server/service/chat-message/fold-history-window.test.ts +1 -1
- package/server/service/chat-session/attachment-tool-result.test.ts +9 -1
- package/server/service/chat-session/attachment-tool-result.ts +7 -0
- package/server/service/chat-session/attachment-tools.ts +3 -3
- package/server/service/chat-session/chat-session.ts +3 -2
- package/server/service/chat-session/group-room.test.ts +233 -0
- package/server/service/chat-session/session-activity-broadcast.ts +20 -0
- package/server/service/chat-session/session-activity-publish.ts +6 -1
- package/server/service/chat-session/session-activity-receivers.test.ts +26 -0
- package/server/service/chat-session/session-visibility.test.ts +32 -1
- package/server/service/chat-session/session-visibility.ts +33 -4
- package/server/service/chat-session/session-write-gate.test.ts +7 -1
- package/server/service/chat-session/session-write-gate.ts +2 -2
- package/server/service/chat-session-participant/index.ts +2 -0
- package/server/service/chat-session-participant/invite-notification.test.ts +36 -1
- package/server/service/chat-session-participant/invite-notification.ts +43 -5
- package/server/service/chat-session-participant/mention-invite.test.ts +49 -1
- package/server/service/chat-session-participant/mention-invite.ts +82 -28
- package/server/service/chat-session-participant/read-roster.test.ts +13 -4
- package/server/service/chat-session-participant/read-roster.ts +6 -3
- package/server/service/chat-session-resolver.ts +204 -27
- package/server/service/data-entry/data-entry-update.test.ts +19 -0
- package/server/service/data-entry/data-entry.test.ts +65 -1
- package/server/service/data-entry/entry-target.ts +36 -5
- package/server/service/data-entry/entry-tools.ts +2 -2
- package/server/service/data-entry/entry-update.ts +6 -3
- package/server/service/data-read/list-missing.test.ts +49 -0
- package/server/service/data-read/list-read.ts +18 -0
- package/server/service/data-read/read-target.ts +10 -3
- package/server/service/data-read/read-tools.ts +1 -1
- package/server/service/data-read/record-keys.test.ts +36 -0
- package/server/service/index.ts +6 -1
- package/server/service/testing/conversation-script.ts +1 -1
- package/server/service/testing/mode-conformance.ts +2 -0
- package/test/assistant-dock.test.ts +5 -1
- package/test/assistant-session-manager.test.ts +4 -2
- package/test/open-doors.test.ts +10 -5
- package/translations/en.json +29 -4
- package/translations/es.json +29 -4
- package/translations/ja.json +29 -4
- package/translations/ko.json +30 -5
- package/translations/ms.json +29 -4
- package/translations/zh.json +29 -4
|
@@ -20,8 +20,23 @@ export interface Inviter {
|
|
|
20
20
|
name?: string | null;
|
|
21
21
|
email?: string | null;
|
|
22
22
|
}
|
|
23
|
+
/** The request's translator - `requestTranslator(context)`. Absent: the English words. */
|
|
24
|
+
export type InviteTranslate = (key: string, options: Record<string, unknown>) => string;
|
|
23
25
|
/**
|
|
24
26
|
* 초대된 사용자들에게 보낼 협업 초대 알림 payload 를 만든다.
|
|
25
27
|
* 받는 사람이 없으면 null (= 보낼 것 없음).
|
|
28
|
+
*
|
|
29
|
+
* The words are the inviting request's language (`t`). The people of one domain read one language; a receiver's
|
|
30
|
+
* own locale is not asked here (2026-10-24: the English words stood on a Korean screen).
|
|
31
|
+
*/
|
|
32
|
+
export declare function buildInviteNotification(userIds: string[], boardId: string | undefined, inviter: Inviter | undefined | null, sessionName?: string | null, t?: InviteTranslate): InviteNotification | null;
|
|
33
|
+
/**
|
|
34
|
+
* Tell the people just seated - in the app's notification list while they are in it (`foreground`, published to
|
|
35
|
+
* them live) and by web push when they are not (`background`). The notifier takes one way per call, so it is
|
|
36
|
+
* called once for each; the list was the one place an invite never reached (2026-10-24: only push was sent).
|
|
37
|
+
*
|
|
38
|
+
* A letter that does not arrive does not undo the seat - the roster is the record, the notice only tells of it.
|
|
26
39
|
*/
|
|
27
|
-
export declare function
|
|
40
|
+
export declare function sendInviteNotification(notify: ((payload: InviteNotification) => Promise<unknown> | unknown) | undefined, userIds: string[], inviter: Inviter | undefined | null, sessionName?: string | null,
|
|
41
|
+
/** A board conversation keeps its board wording and the link to the modeller. */
|
|
42
|
+
boardId?: string, t?: InviteTranslate): Promise<void>;
|
|
@@ -8,22 +8,27 @@
|
|
|
8
8
|
*/
|
|
9
9
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
10
10
|
exports.buildInviteNotification = buildInviteNotification;
|
|
11
|
+
exports.sendInviteNotification = sendInviteNotification;
|
|
11
12
|
/**
|
|
12
13
|
* 초대된 사용자들에게 보낼 협업 초대 알림 payload 를 만든다.
|
|
13
14
|
* 받는 사람이 없으면 null (= 보낼 것 없음).
|
|
15
|
+
*
|
|
16
|
+
* The words are the inviting request's language (`t`). The people of one domain read one language; a receiver's
|
|
17
|
+
* own locale is not asked here (2026-10-24: the English words stood on a Korean screen).
|
|
14
18
|
*/
|
|
15
|
-
function buildInviteNotification(userIds, boardId, inviter, sessionName) {
|
|
19
|
+
function buildInviteNotification(userIds, boardId, inviter, sessionName, t) {
|
|
16
20
|
if (!Array.isArray(userIds) || userIds.length === 0)
|
|
17
21
|
return null;
|
|
18
22
|
const who = inviter?.name || inviter?.email || 'someone';
|
|
23
|
+
const say = (key, defaultValue, params = {}) => t ? t(`ai-assistant.text.${key}`, { ...params, defaultValue }) : defaultValue.replace(/\{(\w+)\}/g, (_, name) => params[name] ?? '');
|
|
19
24
|
/* A board conversation keeps its board wording and the link to the modeller. */
|
|
20
25
|
if (boardId) {
|
|
21
26
|
return {
|
|
22
27
|
receivers: userIds,
|
|
23
28
|
type: 'board-collab-invite',
|
|
24
29
|
subject: 'info',
|
|
25
|
-
title: 'Board collaboration invite',
|
|
26
|
-
body:
|
|
30
|
+
title: say('board-invite-title', 'Board collaboration invite'),
|
|
31
|
+
body: say('board-invite-body', '{who} invited you to collaborate on a board.', { who }),
|
|
27
32
|
url: `board-modeller/${boardId}`,
|
|
28
33
|
mode: 'background'
|
|
29
34
|
};
|
|
@@ -37,9 +42,33 @@ function buildInviteNotification(userIds, boardId, inviter, sessionName) {
|
|
|
37
42
|
receivers: userIds,
|
|
38
43
|
type: 'chat-invite',
|
|
39
44
|
subject: 'info',
|
|
40
|
-
title: 'Conversation invite',
|
|
41
|
-
body: name
|
|
45
|
+
title: say('invite-title', 'Conversation invite'),
|
|
46
|
+
body: name
|
|
47
|
+
? say('invite-body-named', '{who} brought you into the conversation "{name}".', { who, name })
|
|
48
|
+
: say('invite-body', '{who} brought you into a conversation.', { who }),
|
|
42
49
|
mode: 'background'
|
|
43
50
|
};
|
|
44
51
|
}
|
|
52
|
+
/**
|
|
53
|
+
* Tell the people just seated - in the app's notification list while they are in it (`foreground`, published to
|
|
54
|
+
* them live) and by web push when they are not (`background`). The notifier takes one way per call, so it is
|
|
55
|
+
* called once for each; the list was the one place an invite never reached (2026-10-24: only push was sent).
|
|
56
|
+
*
|
|
57
|
+
* A letter that does not arrive does not undo the seat - the roster is the record, the notice only tells of it.
|
|
58
|
+
*/
|
|
59
|
+
async function sendInviteNotification(notify, userIds, inviter, sessionName,
|
|
60
|
+
/** A board conversation keeps its board wording and the link to the modeller. */
|
|
61
|
+
boardId, t) {
|
|
62
|
+
const payload = buildInviteNotification(userIds, boardId, inviter, sessionName, t);
|
|
63
|
+
if (!payload || !notify)
|
|
64
|
+
return;
|
|
65
|
+
for (const mode of ['foreground', 'background']) {
|
|
66
|
+
try {
|
|
67
|
+
await notify({ ...payload, mode });
|
|
68
|
+
}
|
|
69
|
+
catch {
|
|
70
|
+
/* the other way may still reach them */
|
|
71
|
+
}
|
|
72
|
+
}
|
|
73
|
+
}
|
|
45
74
|
//# sourceMappingURL=invite-notification.js.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"invite-notification.js","sourceRoot":"","sources":["../../../server/service/chat-session-participant/invite-notification.ts"],"names":[],"mappings":";AAAA;;;;;;GAMG;;
|
|
1
|
+
{"version":3,"file":"invite-notification.js","sourceRoot":"","sources":["../../../server/service/chat-session-participant/invite-notification.ts"],"names":[],"mappings":";AAAA;;;;;;GAMG;;AA6BH,0DAyCC;AASD,wDAkBC;AA3ED;;;;;;GAMG;AACH,SAAgB,uBAAuB,CACrC,OAAiB,EACjB,OAA2B,EAC3B,OAAmC,EACnC,WAA2B,EAC3B,CAAmB;IAEnB,IAAI,CAAC,KAAK,CAAC,OAAO,CAAC,OAAO,CAAC,IAAI,OAAO,CAAC,MAAM,KAAK,CAAC;QAAE,OAAO,IAAI,CAAA;IAEhE,MAAM,GAAG,GAAG,OAAO,EAAE,IAAI,IAAI,OAAO,EAAE,KAAK,IAAI,SAAS,CAAA;IACxD,MAAM,GAAG,GAAG,CAAC,GAAW,EAAE,YAAoB,EAAE,SAAiC,EAAE,EAAE,EAAE,CACrF,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,qBAAqB,GAAG,EAAE,EAAE,EAAE,GAAG,MAAM,EAAE,YAAY,EAAE,CAAC,CAAC,CAAC,CAAC,YAAY,CAAC,OAAO,CAAC,YAAY,EAAE,CAAC,CAAC,EAAE,IAAI,EAAE,EAAE,CAAC,MAAM,CAAC,IAAI,CAAC,IAAI,EAAE,CAAC,CAAA;IAEtI,gFAAgF;IAChF,IAAI,OAAO,EAAE,CAAC;QACZ,OAAO;YACL,SAAS,EAAE,OAAO;YAClB,IAAI,EAAE,qBAAqB;YAC3B,OAAO,EAAE,MAAM;YACf,KAAK,EAAE,GAAG,CAAC,oBAAoB,EAAE,4BAA4B,CAAC;YAC9D,IAAI,EAAE,GAAG,CAAC,mBAAmB,EAAE,8CAA8C,EAAE,EAAE,GAAG,EAAE,CAAC;YACvF,GAAG,EAAE,kBAAkB,OAAO,EAAE;YAChC,IAAI,EAAE,YAAY;SACnB,CAAA;IACH,CAAC;IAED;;;OAGG;IACH,MAAM,IAAI,GAAG,WAAW,EAAE,IAAI,EAAE,CAAA;IAChC,OAAO;QACL,SAAS,EAAE,OAAO;QAClB,IAAI,EAAE,aAAa;QACnB,OAAO,EAAE,MAAM;QACf,KAAK,EAAE,GAAG,CAAC,cAAc,EAAE,qBAAqB,CAAC;QACjD,IAAI,EAAE,IAAI;YACR,CAAC,CAAC,GAAG,CAAC,mBAAmB,EAAE,mDAAmD,EAAE,EAAE,GAAG,EAAE,IAAI,EAAE,CAAC;YAC9F,CAAC,CAAC,GAAG,CAAC,aAAa,EAAE,wCAAwC,EAAE,EAAE,GAAG,EAAE,CAAC;QACzE,IAAI,EAAE,YAAY;KACnB,CAAA;AACH,CAAC;AAED;;;;;;GAMG;AACI,KAAK,UAAU,sBAAsB,CAC1C,MAAiF,EACjF,OAAiB,EACjB,OAAmC,EACnC,WAA2B;AAC3B,iFAAiF;AACjF,OAAgB,EAChB,CAAmB;IAEnB,MAAM,OAAO,GAAG,uBAAuB,CAAC,OAAO,EAAE,OAAO,EAAE,OAAO,EAAE,WAAW,EAAE,CAAC,CAAC,CAAA;IAClF,IAAI,CAAC,OAAO,IAAI,CAAC,MAAM;QAAE,OAAM;IAC/B,KAAK,MAAM,IAAI,IAAI,CAAC,YAAY,EAAE,YAAY,CAAU,EAAE,CAAC;QACzD,IAAI,CAAC;YACH,MAAM,MAAM,CAAC,EAAE,GAAG,OAAO,EAAE,IAAI,EAAE,CAAC,CAAA;QACpC,CAAC;QAAC,MAAM,CAAC;YACP,wCAAwC;QAC1C,CAAC;IACH,CAAC;AACH,CAAC","sourcesContent":["/**\n * 협업 초대 알림 — 순수 payload 빌더.\n *\n * 부수효과(실제 발송)는 resolver 의 thin caller 가 `context.state.notify` 로 수행.\n * 여기엔 \"무엇을 보낼지\"(payload) 결정 로직만 둔다 — DB·pubsub·notify 의존 없이 테스트 가능.\n * (patch spine 의 board-patch-broadcast.ts 와 동일한 순수/부수효과 분리 컨벤션.)\n */\n\n/** notification-middleware 의 notifier 가 받는 인자 형태 (필요한 키만). */\nexport interface InviteNotification {\n receivers: string[]\n type: string\n subject: string\n title: string\n body: string\n url?: string\n mode: 'background' | 'foreground'\n}\n\n/** 초대자 식별용 — User 엔티티에서 쓰는 필드만 (duck-typed). */\nexport interface Inviter {\n name?: string | null\n email?: string | null\n}\n\n/** The request's translator - `requestTranslator(context)`. Absent: the English words. */\nexport type InviteTranslate = (key: string, options: Record<string, unknown>) => string\n\n/**\n * 초대된 사용자들에게 보낼 협업 초대 알림 payload 를 만든다.\n * 받는 사람이 없으면 null (= 보낼 것 없음).\n *\n * The words are the inviting request's language (`t`). The people of one domain read one language; a receiver's\n * own locale is not asked here (2026-10-24: the English words stood on a Korean screen).\n */\nexport function buildInviteNotification(\n userIds: string[],\n boardId: string | undefined,\n inviter: Inviter | undefined | null,\n sessionName?: string | null,\n t?: InviteTranslate\n): InviteNotification | null {\n if (!Array.isArray(userIds) || userIds.length === 0) return null\n\n const who = inviter?.name || inviter?.email || 'someone'\n const say = (key: string, defaultValue: string, params: Record<string, string> = {}) =>\n t ? t(`ai-assistant.text.${key}`, { ...params, defaultValue }) : defaultValue.replace(/\\{(\\w+)\\}/g, (_, name) => params[name] ?? '')\n\n /* A board conversation keeps its board wording and the link to the modeller. */\n if (boardId) {\n return {\n receivers: userIds,\n type: 'board-collab-invite',\n subject: 'info',\n title: say('board-invite-title', 'Board collaboration invite'),\n body: say('board-invite-body', '{who} invited you to collaborate on a board.', { who }),\n url: `board-modeller/${boardId}`,\n mode: 'background'\n }\n }\n\n /*\n * Every other conversation is not about a board. Both chat doors called this without one and every\n * invite read \"Board collaboration invite\" - in operato-ecs, in plant, anywhere (2026-09-24).\n */\n const name = sessionName?.trim()\n return {\n receivers: userIds,\n type: 'chat-invite',\n subject: 'info',\n title: say('invite-title', 'Conversation invite'),\n body: name\n ? say('invite-body-named', '{who} brought you into the conversation \"{name}\".', { who, name })\n : say('invite-body', '{who} brought you into a conversation.', { who }),\n mode: 'background'\n }\n}\n\n/**\n * Tell the people just seated - in the app's notification list while they are in it (`foreground`, published to\n * them live) and by web push when they are not (`background`). The notifier takes one way per call, so it is\n * called once for each; the list was the one place an invite never reached (2026-10-24: only push was sent).\n *\n * A letter that does not arrive does not undo the seat - the roster is the record, the notice only tells of it.\n */\nexport async function sendInviteNotification(\n notify: ((payload: InviteNotification) => Promise<unknown> | unknown) | undefined,\n userIds: string[],\n inviter: Inviter | undefined | null,\n sessionName?: string | null,\n /** A board conversation keeps its board wording and the link to the modeller. */\n boardId?: string,\n t?: InviteTranslate\n): Promise<void> {\n const payload = buildInviteNotification(userIds, boardId, inviter, sessionName, t)\n if (!payload || !notify) return\n for (const mode of ['foreground', 'background'] as const) {\n try {\n await notify({ ...payload, mode })\n } catch {\n /* the other way may still reach them */\n }\n }\n}\n"]}
|
|
@@ -5,7 +5,7 @@ export declare function newParticipantUserIds(mentioned: ReadonlyArray<string>,
|
|
|
5
5
|
export interface MentionInviteResult {
|
|
6
6
|
/** 이번에 새로 앉은 사람들. 알림은 이들에게 간다. */
|
|
7
7
|
invited: string[];
|
|
8
|
-
/**
|
|
8
|
+
/** 앉힐 수 없는 방이거나 부른 사람에게 그 자리가 없어 앉히지 못한 사람들 — 이름은 불렸고 방에는 못 들어온다. */
|
|
9
9
|
notSeated?: string[];
|
|
10
10
|
}
|
|
11
11
|
export interface MentionInviteInput {
|
|
@@ -18,12 +18,38 @@ export interface MentionInviteInput {
|
|
|
18
18
|
} | undefined | null;
|
|
19
19
|
/** 참여자 저장소 — 시험이 가짜를 넣는다. */
|
|
20
20
|
participants: any;
|
|
21
|
-
/** 이
|
|
21
|
+
/** 이 대화의 공개 범위(ADR-0073). 그룹은 owner 의 멘션만, 비공개는 아무의 멘션도 앉히지 않는다. */
|
|
22
|
+
visibility?: string | null;
|
|
23
|
+
}
|
|
24
|
+
/** 멘션된 사람을 참여자로 앉힌다 — 앉힐 수 있는 방이고 부른 사람에게 그 자리가 있을 때만(`maySeat`). */
|
|
25
|
+
export declare function inviteMentionedUsers(input: MentionInviteInput): Promise<MentionInviteResult>;
|
|
26
|
+
export interface SeatInput {
|
|
27
|
+
sessionId: string;
|
|
28
|
+
domainId: string;
|
|
29
|
+
inviter: {
|
|
30
|
+
id?: string;
|
|
31
|
+
} | undefined | null;
|
|
32
|
+
/** 앉힐 사람들. 이미 앉은 사람과 부른 사람 자신은 빠진다. */
|
|
33
|
+
userIds: ReadonlyArray<string>;
|
|
34
|
+
participants: any;
|
|
22
35
|
visibility?: string | null;
|
|
23
36
|
}
|
|
24
37
|
/**
|
|
25
|
-
*
|
|
38
|
+
* 사람을 앉힌다 — 멘션과 초대 단추가 함께 쓰는 한 곳.
|
|
26
39
|
*
|
|
40
|
+
* 규칙은 `maySeat` 한 줄이다: 공개 방은 누구나, 그룹은 owner 만, 비공개는 아무도. 앉히지 못하면
|
|
41
|
+
* 아무것도 쓰지 않고 그 사람들을 `notSeated` 로 돌려준다 — 화면이 그대로 말한다.
|
|
27
42
|
* 나갔던 사람(soft delete)은 되살린다 — 다시 부른 것이지 새 사람이 아니다.
|
|
28
43
|
*/
|
|
29
|
-
export declare function
|
|
44
|
+
export declare function seatParticipants(input: SeatInput): Promise<MentionInviteResult>;
|
|
45
|
+
/**
|
|
46
|
+
* The seats a person's turn gives - what every door that stores a person's words calls.
|
|
47
|
+
*
|
|
48
|
+
* The writer first: in an open room anyone may walk in and speak, and until 2026-10-24 speaking seated nobody. A
|
|
49
|
+
* conversation four people wrote in showed two in its roster - the one who started it and the one who was
|
|
50
|
+
* mentioned - and the other two had no presence, no unread badge and no activity notice. In a group or private
|
|
51
|
+
* room the writer is seated already (the write gate let them in by their seat). Then the people mentioned.
|
|
52
|
+
*/
|
|
53
|
+
export declare function seatTurn(input: MentionInviteInput): Promise<MentionInviteResult>;
|
|
54
|
+
/** Seat the person who spoke, in an open room only - there the seat grants nothing, it records who took part. */
|
|
55
|
+
export declare function seatWriter(input: Omit<SeatInput, 'userIds'>): Promise<boolean>;
|
|
@@ -1,4 +1,11 @@
|
|
|
1
1
|
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.extractMentionUserIds = extractMentionUserIds;
|
|
4
|
+
exports.newParticipantUserIds = newParticipantUserIds;
|
|
5
|
+
exports.inviteMentionedUsers = inviteMentionedUsers;
|
|
6
|
+
exports.seatParticipants = seatParticipants;
|
|
7
|
+
exports.seatTurn = seatTurn;
|
|
8
|
+
exports.seatWriter = seatWriter;
|
|
2
9
|
/*
|
|
3
10
|
* Mentioning someone tells them (ADR-0071 decision 1, as amended by ADR-0073).
|
|
4
11
|
*
|
|
@@ -20,11 +27,13 @@
|
|
|
20
27
|
* What went: the inviter's own role (there is nothing to hand out), the refusal verdicts, and the
|
|
21
28
|
* notice shown before sending. A private conversation is the one place a mention cannot seat
|
|
22
29
|
* someone — there the named person is told and stays outside until an owner opens it.
|
|
30
|
+
*
|
|
31
|
+
* ── A group (2026-10-24, ADR-0073 decision 3) ──────────────────────────────────
|
|
32
|
+
* In a group the seat is the key, so who seats is asked again: the owner's mention seats, a member's
|
|
33
|
+
* tells (`maySeat`). The invite door (`inviteToAISession`) seats through the same `seatParticipants`
|
|
34
|
+
* under the same rule - two ways in, one seat.
|
|
23
35
|
*/
|
|
24
|
-
|
|
25
|
-
exports.extractMentionUserIds = extractMentionUserIds;
|
|
26
|
-
exports.newParticipantUserIds = newParticipantUserIds;
|
|
27
|
-
exports.inviteMentionedUsers = inviteMentionedUsers;
|
|
36
|
+
const session_visibility_js_1 = require("../chat-session/session-visibility.js");
|
|
28
37
|
/** 본문에서 `@user{userId:UUID}` 마커의 userId 를 뽑는다 — 첫 등장 순, 중복 제거. */
|
|
29
38
|
function extractMentionUserIds(text) {
|
|
30
39
|
if (!text)
|
|
@@ -57,47 +66,85 @@ function newParticipantUserIds(mentioned, existing, selfUserId) {
|
|
|
57
66
|
}
|
|
58
67
|
return out;
|
|
59
68
|
}
|
|
60
|
-
/**
|
|
61
|
-
* 멘션된 사람을 참여자로 앉힌다.
|
|
62
|
-
*
|
|
63
|
-
* 나갔던 사람(soft delete)은 되살린다 — 다시 부른 것이지 새 사람이 아니다.
|
|
64
|
-
*/
|
|
69
|
+
/** 멘션된 사람을 참여자로 앉힌다 — 앉힐 수 있는 방이고 부른 사람에게 그 자리가 있을 때만(`maySeat`). */
|
|
65
70
|
async function inviteMentionedUsers(input) {
|
|
66
71
|
const mentioned = extractMentionUserIds(input.content);
|
|
67
72
|
if (mentioned.length === 0)
|
|
68
73
|
return { invited: [] };
|
|
74
|
+
return seatParticipants({ ...input, userIds: mentioned });
|
|
75
|
+
}
|
|
76
|
+
/**
|
|
77
|
+
* 사람을 앉힌다 — 멘션과 초대 단추가 함께 쓰는 한 곳.
|
|
78
|
+
*
|
|
79
|
+
* 규칙은 `maySeat` 한 줄이다: 공개 방은 누구나, 그룹은 owner 만, 비공개는 아무도. 앉히지 못하면
|
|
80
|
+
* 아무것도 쓰지 않고 그 사람들을 `notSeated` 로 돌려준다 — 화면이 그대로 말한다.
|
|
81
|
+
* 나갔던 사람(soft delete)은 되살린다 — 다시 부른 것이지 새 사람이 아니다.
|
|
82
|
+
*/
|
|
83
|
+
async function seatParticipants(input) {
|
|
69
84
|
const repo = input.participants;
|
|
70
85
|
const existing = await repo.find({
|
|
71
86
|
where: { session: { id: input.sessionId }, domain: { id: input.domainId } },
|
|
72
87
|
withDeleted: true
|
|
73
88
|
});
|
|
74
|
-
|
|
75
|
-
const
|
|
89
|
+
/* A hide-only `viewer` row is not a seat - the person is brought in over it, as a member. */
|
|
90
|
+
const seated = existing.filter((row) => !row.deletedAt && row.role !== 'viewer');
|
|
91
|
+
const activeIds = seated.map((row) => row.userId).filter(Boolean);
|
|
92
|
+
const fresh = newParticipantUserIds(input.userIds, activeIds, input.inviter?.id);
|
|
76
93
|
if (fresh.length === 0)
|
|
77
94
|
return { invited: [] };
|
|
78
95
|
/*
|
|
79
|
-
*
|
|
80
|
-
*
|
|
81
|
-
* 일이다 — 이름이 불렸다는 것만 알린다.
|
|
96
|
+
* 공개 방에서 앉히는 일은 아무것도 주지 않지만(누구나 들어온다), 그룹과 비공개에서는 그것이 곧 접근을
|
|
97
|
+
* 주는 일이다. 그룹에서는 owner 가 주고, 비공개에서는 아무도 주지 않는다 — 이름이 불렸다는 것만 남는다.
|
|
82
98
|
*/
|
|
83
|
-
|
|
99
|
+
const inviterRole = seated.find((row) => row.userId === input.inviter?.id)?.role;
|
|
100
|
+
if (!(0, session_visibility_js_1.maySeat)(input.visibility, inviterRole))
|
|
84
101
|
return { invited: [], notSeated: fresh };
|
|
85
|
-
for (const userId of fresh)
|
|
86
|
-
|
|
87
|
-
if (prior) {
|
|
88
|
-
await repo.update(prior.id, { deletedAt: null, role: 'member', updater: input.inviter });
|
|
89
|
-
}
|
|
90
|
-
else {
|
|
91
|
-
await repo.save({
|
|
92
|
-
domain: { id: input.domainId },
|
|
93
|
-
session: { id: input.sessionId },
|
|
94
|
-
user: { id: userId },
|
|
95
|
-
role: 'member',
|
|
96
|
-
creator: input.inviter,
|
|
97
|
-
updater: input.inviter
|
|
98
|
-
});
|
|
99
|
-
}
|
|
100
|
-
}
|
|
102
|
+
for (const userId of fresh)
|
|
103
|
+
await writeMemberSeat(repo, existing, input, userId);
|
|
101
104
|
return { invited: fresh };
|
|
102
105
|
}
|
|
106
|
+
/**
|
|
107
|
+
* The seats a person's turn gives - what every door that stores a person's words calls.
|
|
108
|
+
*
|
|
109
|
+
* The writer first: in an open room anyone may walk in and speak, and until 2026-10-24 speaking seated nobody. A
|
|
110
|
+
* conversation four people wrote in showed two in its roster - the one who started it and the one who was
|
|
111
|
+
* mentioned - and the other two had no presence, no unread badge and no activity notice. In a group or private
|
|
112
|
+
* room the writer is seated already (the write gate let them in by their seat). Then the people mentioned.
|
|
113
|
+
*/
|
|
114
|
+
async function seatTurn(input) {
|
|
115
|
+
await seatWriter(input);
|
|
116
|
+
return inviteMentionedUsers(input);
|
|
117
|
+
}
|
|
118
|
+
/** Seat the person who spoke, in an open room only - there the seat grants nothing, it records who took part. */
|
|
119
|
+
async function seatWriter(input) {
|
|
120
|
+
const writerId = input.inviter?.id;
|
|
121
|
+
if (!writerId || !(0, session_visibility_js_1.isOpenRoom)(input.visibility))
|
|
122
|
+
return false;
|
|
123
|
+
const repo = input.participants;
|
|
124
|
+
const existing = await repo.find({
|
|
125
|
+
where: { session: { id: input.sessionId }, domain: { id: input.domainId } },
|
|
126
|
+
withDeleted: true
|
|
127
|
+
});
|
|
128
|
+
if (existing.some((row) => row.userId === writerId && !row.deletedAt && row.role !== 'viewer'))
|
|
129
|
+
return false;
|
|
130
|
+
await writeMemberSeat(repo, existing, input, writerId);
|
|
131
|
+
return true;
|
|
132
|
+
}
|
|
133
|
+
/** One member seat: someone who left (soft delete) or only hid it (`viewer`) is brought back on that row, never a second one. */
|
|
134
|
+
async function writeMemberSeat(repo, existing, input, userId) {
|
|
135
|
+
const prior = existing.find((row) => row.userId === userId && (row.deletedAt || row.role === 'viewer'));
|
|
136
|
+
if (prior) {
|
|
137
|
+
await repo.update(prior.id, { deletedAt: null, role: 'member', updater: input.inviter });
|
|
138
|
+
}
|
|
139
|
+
else {
|
|
140
|
+
await repo.save({
|
|
141
|
+
domain: { id: input.domainId },
|
|
142
|
+
session: { id: input.sessionId },
|
|
143
|
+
user: { id: userId },
|
|
144
|
+
role: 'member',
|
|
145
|
+
creator: input.inviter,
|
|
146
|
+
updater: input.inviter
|
|
147
|
+
});
|
|
148
|
+
}
|
|
149
|
+
}
|
|
103
150
|
//# sourceMappingURL=mention-invite.js.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"mention-invite.js","sourceRoot":"","sources":["../../../server/service/chat-session-participant/mention-invite.ts"],"names":[],"mappings":"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","sourcesContent":["/*\n * Mentioning someone tells them (ADR-0071 decision 1, as amended by ADR-0073).\n *\n * The machinery existed in the board product only: `@name` in a board chat added a participant and\n * sent them an invite, and the same `@name` in a twin deliberation added nobody. The person typing\n * it had every reason to believe they had called a colleague in.\n *\n * ── What a mention is now ─────────────────────────────────────────────────────\n * A conversation in the domain is open: anyone may walk in, with nothing to approve and nobody to\n * ask (ADR-0073 decision 2). So a mention grants nothing — it points. It seats the named person so\n * the conversation reaches their list, and it tells them their name came up. Somebody who was never\n * mentioned can read the same room by opening it.\n *\n * Two rules survive the amendment:\n * ① only a door that stores **a person's turn** mentions — never the model, a tool, or the\n * assistant's own answer. What the assistant writes must not seat people.\n * ② whoever is named joins as a member and is told.\n *\n * What went: the inviter's own role (there is nothing to hand out), the refusal verdicts, and the\n * notice shown before sending. A private conversation is the one place a mention cannot seat\n * someone — there the named person is told and stays outside until an owner opens it.\n */\n\n/** 본문에서 `@user{userId:UUID}` 마커의 userId 를 뽑는다 — 첫 등장 순, 중복 제거. */\nexport function extractMentionUserIds(text: string | null | undefined): string[] {\n if (!text) return []\n const re = /\\{userId:([^}\\s]+)\\}/g\n const seen = new Set<string>()\n const out: string[] = []\n let m: RegExpExecArray | null\n while ((m = re.exec(text)) !== null) {\n const id = m[1]\n if (!seen.has(id)) {\n seen.add(id)\n out.push(id)\n }\n }\n return out\n}\n\n/** 멘션된 사람 중 **아직 참여자가 아니고 자기 자신도 아닌** 새 사람만. */\nexport function newParticipantUserIds(\n mentioned: ReadonlyArray<string>,\n existing: ReadonlyArray<string>,\n selfUserId?: string | null\n): string[] {\n const have = new Set(existing)\n if (selfUserId) have.add(selfUserId)\n const out: string[] = []\n const seen = new Set<string>()\n for (const id of mentioned) {\n if (!id || have.has(id) || seen.has(id)) continue\n seen.add(id)\n out.push(id)\n }\n return out\n}\n\nexport interface MentionInviteResult {\n /** 이번에 새로 앉은 사람들. 알림은 이들에게 간다. */\n invited: string[]\n /** 비공개 대화라 앉히지 못한 사람들 — 이름은 불렸고 방에는 못 들어온다. 화면이 그대로 말한다. */\n notSeated?: string[]\n}\n\nexport interface MentionInviteInput {\n /** 저장된 **사람의 말**. 어시스턴트의 답이나 도구 결과를 여기에 넘기지 않는다(조건 ①). */\n content: string\n sessionId: string\n domainId: string\n inviter: { id?: string } | undefined | null\n /** 참여자 저장소 — 시험이 가짜를 넣는다. */\n participants: any\n /** 이 대화가 공개인가(ADR-0073 판정 2). 비공개면 이름만 알리고 앉히지 않는다. */\n visibility?: string | null\n}\n\n/**\n * 멘션된 사람을 참여자로 앉힌다.\n *\n * 나갔던 사람(soft delete)은 되살린다 — 다시 부른 것이지 새 사람이 아니다.\n */\nexport async function inviteMentionedUsers(input: MentionInviteInput): Promise<MentionInviteResult> {\n const mentioned = extractMentionUserIds(input.content)\n if (mentioned.length === 0) return { invited: [] }\n\n const repo = input.participants\n const existing = await repo.find({\n where: { session: { id: input.sessionId } as any, domain: { id: input.domainId } as any },\n withDeleted: true\n })\n\n const activeIds = existing.filter((row: any) => !row.deletedAt).map((row: any) => row.userId).filter(Boolean)\n const fresh = newParticipantUserIds(mentioned, activeIds, input.inviter?.id)\n if (fresh.length === 0) return { invited: [] }\n\n /*\n * 비공개 대화에서는 이름을 불러도 앉히지 않는다. 공개 대화에서는 앉히는 일이 아무것도 주지 않지만\n * (누구나 들어온다), 비공개에서는 그것이 곧 접근을 주는 일이다. 부르는 사람이 아니라 owner 가 열\n * 일이다 — 이름이 불렸다는 것만 알린다.\n */\n if ((input.visibility ?? 'open') !== 'open') return { invited: [], notSeated: fresh }\n\n for (const userId of fresh) {\n const prior = existing.find((row: any) => row.userId === userId && row.deletedAt)\n if (prior) {\n await repo.update(prior.id, { deletedAt: null, role: 'member', updater: input.inviter } as any)\n } else {\n await repo.save({\n domain: { id: input.domainId } as any,\n session: { id: input.sessionId } as any,\n user: { id: userId } as any,\n role: 'member',\n creator: input.inviter,\n updater: input.inviter\n } as any)\n }\n }\n\n return { invited: fresh }\n}\n"]}
|
|
1
|
+
{"version":3,"file":"mention-invite.js","sourceRoot":"","sources":["../../../server/service/chat-session-participant/mention-invite.ts"],"names":[],"mappings":"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","sourcesContent":["/*\n * Mentioning someone tells them (ADR-0071 decision 1, as amended by ADR-0073).\n *\n * The machinery existed in the board product only: `@name` in a board chat added a participant and\n * sent them an invite, and the same `@name` in a twin deliberation added nobody. The person typing\n * it had every reason to believe they had called a colleague in.\n *\n * ── What a mention is now ─────────────────────────────────────────────────────\n * A conversation in the domain is open: anyone may walk in, with nothing to approve and nobody to\n * ask (ADR-0073 decision 2). So a mention grants nothing — it points. It seats the named person so\n * the conversation reaches their list, and it tells them their name came up. Somebody who was never\n * mentioned can read the same room by opening it.\n *\n * Two rules survive the amendment:\n * ① only a door that stores **a person's turn** mentions — never the model, a tool, or the\n * assistant's own answer. What the assistant writes must not seat people.\n * ② whoever is named joins as a member and is told.\n *\n * What went: the inviter's own role (there is nothing to hand out), the refusal verdicts, and the\n * notice shown before sending. A private conversation is the one place a mention cannot seat\n * someone — there the named person is told and stays outside until an owner opens it.\n *\n * ── A group (2026-10-24, ADR-0073 decision 3) ──────────────────────────────────\n * In a group the seat is the key, so who seats is asked again: the owner's mention seats, a member's\n * tells (`maySeat`). The invite door (`inviteToAISession`) seats through the same `seatParticipants`\n * under the same rule - two ways in, one seat.\n */\nimport { isOpenRoom, maySeat } from '../chat-session/session-visibility.js'\n\n/** 본문에서 `@user{userId:UUID}` 마커의 userId 를 뽑는다 — 첫 등장 순, 중복 제거. */\nexport function extractMentionUserIds(text: string | null | undefined): string[] {\n if (!text) return []\n const re = /\\{userId:([^}\\s]+)\\}/g\n const seen = new Set<string>()\n const out: string[] = []\n let m: RegExpExecArray | null\n while ((m = re.exec(text)) !== null) {\n const id = m[1]\n if (!seen.has(id)) {\n seen.add(id)\n out.push(id)\n }\n }\n return out\n}\n\n/** 멘션된 사람 중 **아직 참여자가 아니고 자기 자신도 아닌** 새 사람만. */\nexport function newParticipantUserIds(\n mentioned: ReadonlyArray<string>,\n existing: ReadonlyArray<string>,\n selfUserId?: string | null\n): string[] {\n const have = new Set(existing)\n if (selfUserId) have.add(selfUserId)\n const out: string[] = []\n const seen = new Set<string>()\n for (const id of mentioned) {\n if (!id || have.has(id) || seen.has(id)) continue\n seen.add(id)\n out.push(id)\n }\n return out\n}\n\nexport interface MentionInviteResult {\n /** 이번에 새로 앉은 사람들. 알림은 이들에게 간다. */\n invited: string[]\n /** 앉힐 수 없는 방이거나 부른 사람에게 그 자리가 없어 앉히지 못한 사람들 — 이름은 불렸고 방에는 못 들어온다. */\n notSeated?: string[]\n}\n\nexport interface MentionInviteInput {\n /** 저장된 **사람의 말**. 어시스턴트의 답이나 도구 결과를 여기에 넘기지 않는다(조건 ①). */\n content: string\n sessionId: string\n domainId: string\n inviter: { id?: string } | undefined | null\n /** 참여자 저장소 — 시험이 가짜를 넣는다. */\n participants: any\n /** 이 대화의 공개 범위(ADR-0073). 그룹은 owner 의 멘션만, 비공개는 아무의 멘션도 앉히지 않는다. */\n visibility?: string | null\n}\n\n/** 멘션된 사람을 참여자로 앉힌다 — 앉힐 수 있는 방이고 부른 사람에게 그 자리가 있을 때만(`maySeat`). */\nexport async function inviteMentionedUsers(input: MentionInviteInput): Promise<MentionInviteResult> {\n const mentioned = extractMentionUserIds(input.content)\n if (mentioned.length === 0) return { invited: [] }\n return seatParticipants({ ...input, userIds: mentioned })\n}\n\nexport interface SeatInput {\n sessionId: string\n domainId: string\n inviter: { id?: string } | undefined | null\n /** 앉힐 사람들. 이미 앉은 사람과 부른 사람 자신은 빠진다. */\n userIds: ReadonlyArray<string>\n participants: any\n visibility?: string | null\n}\n\n/**\n * 사람을 앉힌다 — 멘션과 초대 단추가 함께 쓰는 한 곳.\n *\n * 규칙은 `maySeat` 한 줄이다: 공개 방은 누구나, 그룹은 owner 만, 비공개는 아무도. 앉히지 못하면\n * 아무것도 쓰지 않고 그 사람들을 `notSeated` 로 돌려준다 — 화면이 그대로 말한다.\n * 나갔던 사람(soft delete)은 되살린다 — 다시 부른 것이지 새 사람이 아니다.\n */\nexport async function seatParticipants(input: SeatInput): Promise<MentionInviteResult> {\n const repo = input.participants\n const existing = await repo.find({\n where: { session: { id: input.sessionId } as any, domain: { id: input.domainId } as any },\n withDeleted: true\n })\n\n /* A hide-only `viewer` row is not a seat - the person is brought in over it, as a member. */\n const seated = existing.filter((row: any) => !row.deletedAt && row.role !== 'viewer')\n const activeIds = seated.map((row: any) => row.userId).filter(Boolean)\n const fresh = newParticipantUserIds(input.userIds, activeIds, input.inviter?.id)\n if (fresh.length === 0) return { invited: [] }\n\n /*\n * 공개 방에서 앉히는 일은 아무것도 주지 않지만(누구나 들어온다), 그룹과 비공개에서는 그것이 곧 접근을\n * 주는 일이다. 그룹에서는 owner 가 주고, 비공개에서는 아무도 주지 않는다 — 이름이 불렸다는 것만 남는다.\n */\n const inviterRole = seated.find((row: any) => row.userId === input.inviter?.id)?.role\n if (!maySeat(input.visibility, inviterRole)) return { invited: [], notSeated: fresh }\n\n for (const userId of fresh) await writeMemberSeat(repo, existing, input, userId)\n\n return { invited: fresh }\n}\n\n/**\n * The seats a person's turn gives - what every door that stores a person's words calls.\n *\n * The writer first: in an open room anyone may walk in and speak, and until 2026-10-24 speaking seated nobody. A\n * conversation four people wrote in showed two in its roster - the one who started it and the one who was\n * mentioned - and the other two had no presence, no unread badge and no activity notice. In a group or private\n * room the writer is seated already (the write gate let them in by their seat). Then the people mentioned.\n */\nexport async function seatTurn(input: MentionInviteInput): Promise<MentionInviteResult> {\n await seatWriter(input)\n return inviteMentionedUsers(input)\n}\n\n/** Seat the person who spoke, in an open room only - there the seat grants nothing, it records who took part. */\nexport async function seatWriter(input: Omit<SeatInput, 'userIds'>): Promise<boolean> {\n const writerId = input.inviter?.id\n if (!writerId || !isOpenRoom(input.visibility)) return false\n const repo = input.participants\n const existing = await repo.find({\n where: { session: { id: input.sessionId } as any, domain: { id: input.domainId } as any },\n withDeleted: true\n })\n if (existing.some((row: any) => row.userId === writerId && !row.deletedAt && row.role !== 'viewer')) return false\n await writeMemberSeat(repo, existing, input, writerId)\n return true\n}\n\n/** One member seat: someone who left (soft delete) or only hid it (`viewer`) is brought back on that row, never a second one. */\nasync function writeMemberSeat(repo: any, existing: any[], input: { sessionId: string; domainId: string; inviter: { id?: string } | undefined | null }, userId: string) {\n const prior = existing.find((row: any) => row.userId === userId && (row.deletedAt || row.role === 'viewer'))\n if (prior) {\n await repo.update(prior.id, { deletedAt: null, role: 'member', updater: input.inviter } as any)\n } else {\n await repo.save({\n domain: { id: input.domainId } as any,\n session: { id: input.sessionId } as any,\n user: { id: userId } as any,\n role: 'member',\n creator: input.inviter,\n updater: input.inviter\n } as any)\n }\n}\n"]}
|
|
@@ -2,14 +2,17 @@
|
|
|
2
2
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
3
|
exports.readRoster = readRoster;
|
|
4
4
|
const session_visibility_js_1 = require("../chat-session/session-visibility.js");
|
|
5
|
+
const participant_lookup_roles_js_1 = require("./participant-lookup-roles.js");
|
|
5
6
|
/** Read the room roster with user identities hydrated, after applying the room's visibility gate. */
|
|
6
7
|
async function readRoster(room, port) {
|
|
7
|
-
if (!(0, session_visibility_js_1.mayReadSession)(room.visibility, room.visibility
|
|
8
|
+
if (!(0, session_visibility_js_1.mayReadSession)(room.visibility, (0, session_visibility_js_1.isOpenRoom)(room.visibility) ? false : await port.isParticipant()))
|
|
8
9
|
return [];
|
|
9
|
-
|
|
10
|
+
/* A hide-only `viewer` row is not someone in the room - the roster lists the seats. */
|
|
11
|
+
const rows = await port.find({
|
|
10
12
|
where: { session: { id: room.id }, domain: { id: room.domainId } },
|
|
11
13
|
relations: ['user'],
|
|
12
14
|
order: { createdAt: 'ASC' }
|
|
13
15
|
});
|
|
16
|
+
return rows.filter((row) => (0, participant_lookup_roles_js_1.isRoomRole)(row?.role));
|
|
14
17
|
}
|
|
15
18
|
//# sourceMappingURL=read-roster.js.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"read-roster.js","sourceRoot":"","sources":["../../../server/service/chat-session-participant/read-roster.ts"],"names":[],"mappings":";;
|
|
1
|
+
{"version":3,"file":"read-roster.js","sourceRoot":"","sources":["../../../server/service/chat-session-participant/read-roster.ts"],"names":[],"mappings":";;AAIA,gCAYC;AAhBD,iFAAkF;AAClF,+EAA0D;AAE1D,qGAAqG;AAC9F,KAAK,UAAU,UAAU,CAC9B,IAA2D,EAC3D,IAA6E;IAE7E,IAAI,CAAC,IAAA,sCAAc,EAAC,IAAI,CAAC,UAAU,EAAE,IAAA,kCAAU,EAAC,IAAI,CAAC,UAAU,CAAC,CAAC,CAAC,CAAC,KAAK,CAAC,CAAC,CAAC,MAAM,IAAI,CAAC,aAAa,EAAE,CAAC;QAAE,OAAO,EAAE,CAAA;IACjH,uFAAuF;IACvF,MAAM,IAAI,GAAG,MAAM,IAAI,CAAC,IAAI,CAAC;QAC3B,KAAK,EAAE,EAAE,OAAO,EAAE,EAAE,EAAE,EAAE,IAAI,CAAC,EAAE,EAAE,EAAE,MAAM,EAAE,EAAE,EAAE,EAAE,IAAI,CAAC,QAAQ,EAAE,EAAE;QAClE,SAAS,EAAE,CAAC,MAAM,CAAC;QACnB,KAAK,EAAE,EAAE,SAAS,EAAE,KAAK,EAAE;KAC5B,CAAC,CAAA;IACF,OAAO,IAAI,CAAC,MAAM,CAAC,CAAC,GAAQ,EAAE,EAAE,CAAC,IAAA,wCAAU,EAAC,GAAG,EAAE,IAAI,CAAC,CAAC,CAAA;AACzD,CAAC","sourcesContent":["import { isOpenRoom, mayReadSession } from '../chat-session/session-visibility.js'\nimport { isRoomRole } from './participant-lookup-roles.js'\n\n/** Read the room roster with user identities hydrated, after applying the room's visibility gate. */\nexport async function readRoster<T>(\n room: { id: string; domainId: string; visibility?: string },\n port: { isParticipant(): Promise<boolean>; find(options: any): Promise<T[]> }\n): Promise<T[]> {\n if (!mayReadSession(room.visibility, isOpenRoom(room.visibility) ? false : await port.isParticipant())) return []\n /* A hide-only `viewer` row is not someone in the room - the roster lists the seats. */\n const rows = await port.find({\n where: { session: { id: room.id }, domain: { id: room.domainId } },\n relations: ['user'],\n order: { createdAt: 'ASC' }\n })\n return rows.filter((row: any) => isRoomRole(row?.role))\n}\n"]}
|
|
@@ -95,6 +95,28 @@ export declare class ChatSessionResolver {
|
|
|
95
95
|
* 강제하지 않는다 — 문이 막을 수 있는 것은 누가 바꾸느냐까지다.
|
|
96
96
|
*/
|
|
97
97
|
setSessionVisibility(sessionId: string, visibility: string, context: ResolverContext): Promise<boolean>;
|
|
98
|
+
/**
|
|
99
|
+
* Bring people into this conversation - the same seat a mention gives, by the same rule (`maySeat`): anyone
|
|
100
|
+
* in an open room, the owner in a group, nobody in a private one. Each person seated is told
|
|
101
|
+
* (`notifyInvited`). People outside the domain are not seated.
|
|
102
|
+
*/
|
|
103
|
+
inviteToAISession(sessionId: string, userIds: string[], context: ResolverContext): Promise<string[]>;
|
|
104
|
+
/**
|
|
105
|
+
* Send a member out - the owner only, and never the owner themself. In a group the seat was the key, so the
|
|
106
|
+
* person no longer reads or writes here (every gate asks the seat). Their list is told once and drops it.
|
|
107
|
+
*/
|
|
108
|
+
removeFromAISession(sessionId: string, userId: string, context: ResolverContext): Promise<boolean>;
|
|
109
|
+
/**
|
|
110
|
+
* Leave this conversation - a member steps out of their own seat. The owner does not leave: the room would be
|
|
111
|
+
* nobody's. They delete it, or make it private.
|
|
112
|
+
*/
|
|
113
|
+
leaveAISession(sessionId: string, context: ResolverContext): Promise<boolean>;
|
|
114
|
+
private _leave;
|
|
115
|
+
/**
|
|
116
|
+
* "I am typing" - a notice to the others in the room, stored nowhere. Only a person who may write here is
|
|
117
|
+
* typing here (the write gate), and the listeners pass the room's read gate (`ChatTypingSubscription`).
|
|
118
|
+
*/
|
|
119
|
+
sayTyping(sessionId: string, context: ResolverContext): Promise<boolean>;
|
|
98
120
|
renameAISession(sessionId: string, name: string, context: ResolverContext): Promise<boolean>;
|
|
99
121
|
/**
|
|
100
122
|
* 읽은 지점 갱신 — 이 세션을 여기까지 읽었다. 멱등(여러 번 호출해도 최신 시각으로만 이동).
|
|
@@ -103,6 +125,11 @@ export declare class ChatSessionResolver {
|
|
|
103
125
|
markSessionRead(sessionId: string, context: ResolverContext): Promise<boolean>;
|
|
104
126
|
domainUsersForMention(query: string | undefined, limit: number, context: ResolverContext): Promise<User[]>;
|
|
105
127
|
chatSessionParticipants(sessionId: string, context: ResolverContext): Promise<ChatSessionParticipant[]>;
|
|
128
|
+
/**
|
|
129
|
+
* May the caller seat someone here - the server's answer to the screen, so the screen keeps no copy of the
|
|
130
|
+
* rule (`maySeat`). The invite button and the line under a mention read it; the doors decide again.
|
|
131
|
+
*/
|
|
132
|
+
mayInviteToAISession(sessionId: string, context: ResolverContext): Promise<boolean>;
|
|
106
133
|
touchPresence(sessionId: string, context: ResolverContext): Promise<boolean>;
|
|
107
134
|
chatMessages(sessionId: string, limit: number, offset: number, context: ResolverContext): Promise<ChatMessage[]>;
|
|
108
135
|
startAISession(anchorType: string, anchorId: string, context: ResolverContext): Promise<ChatSession>;
|