@things-factory/ai-assistant 10.1.6 → 10.1.8
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/board-ai-chat.ts +165 -100
- package/client/components/chat-defaults.ts +13 -0
- package/client/components/chat-input-builder.test.ts +81 -0
- package/client/components/chat-input-builder.ts +43 -0
- package/client/components/chat-triggers.test.ts +72 -0
- package/client/components/chat-triggers.ts +62 -0
- package/dist-client/components/board-ai-chat.d.ts +8 -0
- package/dist-client/components/board-ai-chat.js +160 -100
- package/dist-client/components/board-ai-chat.js.map +1 -1
- package/dist-client/components/chat-defaults.d.ts +12 -0
- package/dist-client/components/chat-defaults.js +2 -0
- package/dist-client/components/chat-defaults.js.map +1 -1
- package/dist-client/components/chat-input-builder.d.ts +29 -0
- package/dist-client/components/chat-input-builder.js +22 -0
- package/dist-client/components/chat-input-builder.js.map +1 -1
- package/dist-client/components/chat-input-builder.test.d.ts +1 -0
- package/dist-client/components/chat-input-builder.test.js +67 -0
- package/dist-client/components/chat-input-builder.test.js.map +1 -0
- package/dist-client/components/chat-triggers.d.ts +16 -0
- package/dist-client/components/chat-triggers.js +42 -0
- package/dist-client/components/chat-triggers.js.map +1 -0
- package/dist-client/components/chat-triggers.test.d.ts +1 -0
- package/dist-client/components/chat-triggers.test.js +61 -0
- package/dist-client/components/chat-triggers.test.js.map +1 -0
- package/dist-client/tsconfig.tsbuildinfo +1 -1
- package/dist-server/service/assistant-chat-resolver.d.ts +32 -0
- package/dist-server/service/assistant-chat-resolver.js +342 -0
- package/dist-server/service/assistant-chat-resolver.js.map +1 -0
- package/dist-server/service/chat-session-resolver.js +18 -18
- package/dist-server/service/chat-session-resolver.js.map +1 -1
- package/dist-server/service/index.d.ts +3 -1
- package/dist-server/service/index.js +4 -0
- package/dist-server/service/index.js.map +1 -1
- package/dist-server/tsconfig.tsbuildinfo +1 -1
- package/package.json +5 -5
- package/server/service/assistant-chat-resolver.ts +326 -0
- package/server/service/chat-session-resolver.ts +18 -18
- package/server/service/index.ts +4 -0
- package/test/translations.test.ts +79 -0
- package/translations/en.json +59 -0
- package/translations/ja.json +58 -0
- package/translations/ko.json +59 -0
- package/translations/ms.json +58 -0
- package/translations/zh.json +58 -0
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
import '@things-factory/auth-base';
|
|
2
|
+
declare class AssistantMessageInput {
|
|
3
|
+
role: string;
|
|
4
|
+
content: string;
|
|
5
|
+
}
|
|
6
|
+
declare class AssistantChatInput {
|
|
7
|
+
sessionId?: string;
|
|
8
|
+
messages: AssistantMessageInput[];
|
|
9
|
+
systemPrompt: string;
|
|
10
|
+
toolCategories?: string[];
|
|
11
|
+
hostContext?: any;
|
|
12
|
+
truncateAfterMessageId?: string;
|
|
13
|
+
requireGroundingTools?: boolean;
|
|
14
|
+
model?: string;
|
|
15
|
+
temperature?: number;
|
|
16
|
+
maxTokens?: number;
|
|
17
|
+
}
|
|
18
|
+
declare class AssistantChatOutput {
|
|
19
|
+
reply: string;
|
|
20
|
+
clientId: string;
|
|
21
|
+
sessionId?: string;
|
|
22
|
+
userMessageId?: string;
|
|
23
|
+
assistantMessageId?: string;
|
|
24
|
+
toolUsages?: any;
|
|
25
|
+
offeredTools?: string[];
|
|
26
|
+
proposals?: any;
|
|
27
|
+
groundingWarnings?: any;
|
|
28
|
+
}
|
|
29
|
+
export declare class AssistantChatResolver {
|
|
30
|
+
assistantChat(input: AssistantChatInput, context: any): Promise<AssistantChatOutput>;
|
|
31
|
+
}
|
|
32
|
+
export {};
|
|
@@ -0,0 +1,342 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.AssistantChatResolver = void 0;
|
|
4
|
+
const tslib_1 = require("tslib");
|
|
5
|
+
/*
|
|
6
|
+
* The neutral door into a conversation.
|
|
7
|
+
*
|
|
8
|
+
* ── Why this exists (2026-09-11) ───────────────────────────────────────────────
|
|
9
|
+
* The chat component moved here from `board-ai`, but the only mutation it could call stayed
|
|
10
|
+
* behind. operato-figure mounted the dock, registered four tools, and got
|
|
11
|
+
* `Cannot query field "boardAIChat" on type "Mutation"` — it does not load board-ai. The twin has
|
|
12
|
+
* the same shape and it does not show: its space deliberation runs through board-ai's door purely
|
|
13
|
+
* because the board authoring half of the same product already loaded that package.
|
|
14
|
+
*
|
|
15
|
+
* ── Why the board resolver was not moved ───────────────────────────────────────
|
|
16
|
+
* `board-ai-resolver.ts` is 692 lines and the larger part of it is board authoring itself:
|
|
17
|
+
* `currentBoard` as a BoardModel, PatchEntry, board patch broadcasts, refid injection for
|
|
18
|
+
* component mentions. Moving that here would teach this package the board document, which is the
|
|
19
|
+
* dependency we just cut, pointing the other way.
|
|
20
|
+
*
|
|
21
|
+
* So the door moved and the editor did not. `boardAIChat` stays in board-ai for board authoring;
|
|
22
|
+
* this one is for a surface that asks questions and runs registered tools.
|
|
23
|
+
*
|
|
24
|
+
* ── What this is not ───────────────────────────────────────────────────────────
|
|
25
|
+
* It has no document, so it takes no document and returns no patch. Tools that write to a
|
|
26
|
+
* document are not offered here — `registryAssistantChat` enforces that, and it decides what may
|
|
27
|
+
* be dispatched from the same list it offers, so the two cannot drift apart.
|
|
28
|
+
*/
|
|
29
|
+
const type_graphql_1 = require("type-graphql");
|
|
30
|
+
const graphql_scalars_1 = require("graphql-scalars");
|
|
31
|
+
require("@things-factory/auth-base");
|
|
32
|
+
const env_1 = require("@things-factory/env");
|
|
33
|
+
const shell_1 = require("@things-factory/shell");
|
|
34
|
+
const ai_client_base_1 = require("@things-factory/ai-client-base");
|
|
35
|
+
const chat_session_js_1 = require("./chat-session/chat-session.js");
|
|
36
|
+
const chat_message_js_1 = require("./chat-message/chat-message.js");
|
|
37
|
+
const llm_history_js_1 = require("./chat-message/llm-history.js");
|
|
38
|
+
const chat_message_publish_js_1 = require("./chat-message/chat-message-publish.js");
|
|
39
|
+
const session_activity_publish_js_1 = require("./chat-session/session-activity-publish.js");
|
|
40
|
+
const session_inbox_js_1 = require("./chat-session/session-inbox.js");
|
|
41
|
+
/**
|
|
42
|
+
* How many stored messages reach the model.
|
|
43
|
+
*
|
|
44
|
+
* A deliberation runs for days. Without a cap the prompt grows without bound and the oldest turns
|
|
45
|
+
* bury the recent ones. Kept the same as the board conversation's cap so the two surfaces of one
|
|
46
|
+
* product do not remember differently.
|
|
47
|
+
*/
|
|
48
|
+
const LLM_HISTORY_MAX_TURNS = 40;
|
|
49
|
+
let AssistantMessageInput = class AssistantMessageInput {
|
|
50
|
+
};
|
|
51
|
+
tslib_1.__decorate([
|
|
52
|
+
(0, type_graphql_1.Field)({ description: "Role: 'user' or 'assistant'" }),
|
|
53
|
+
tslib_1.__metadata("design:type", String)
|
|
54
|
+
], AssistantMessageInput.prototype, "role", void 0);
|
|
55
|
+
tslib_1.__decorate([
|
|
56
|
+
(0, type_graphql_1.Field)({ description: 'Message content' }),
|
|
57
|
+
tslib_1.__metadata("design:type", String)
|
|
58
|
+
], AssistantMessageInput.prototype, "content", void 0);
|
|
59
|
+
AssistantMessageInput = tslib_1.__decorate([
|
|
60
|
+
(0, type_graphql_1.InputType)()
|
|
61
|
+
], AssistantMessageInput);
|
|
62
|
+
let AssistantChatInput = class AssistantChatInput {
|
|
63
|
+
};
|
|
64
|
+
tslib_1.__decorate([
|
|
65
|
+
(0, type_graphql_1.Field)({
|
|
66
|
+
nullable: true,
|
|
67
|
+
description: 'ChatSession id. Omit for a one-off question with no persistence.'
|
|
68
|
+
}),
|
|
69
|
+
tslib_1.__metadata("design:type", String)
|
|
70
|
+
], AssistantChatInput.prototype, "sessionId", void 0);
|
|
71
|
+
tslib_1.__decorate([
|
|
72
|
+
(0, type_graphql_1.Field)(() => [AssistantMessageInput], {
|
|
73
|
+
description: 'Conversation, newest last. For a persisted session only the last user message is appended; the rest is read from storage.'
|
|
74
|
+
}),
|
|
75
|
+
tslib_1.__metadata("design:type", Array)
|
|
76
|
+
], AssistantChatInput.prototype, "messages", void 0);
|
|
77
|
+
tslib_1.__decorate([
|
|
78
|
+
(0, type_graphql_1.Field)({
|
|
79
|
+
description: 'Who the assistant is on this surface, in the words of the product that owns the screen. The rules for using each tool come from whoever registered it and are appended by the server.'
|
|
80
|
+
}),
|
|
81
|
+
tslib_1.__metadata("design:type", String)
|
|
82
|
+
], AssistantChatInput.prototype, "systemPrompt", void 0);
|
|
83
|
+
tslib_1.__decorate([
|
|
84
|
+
(0, type_graphql_1.Field)(() => [String], {
|
|
85
|
+
nullable: true,
|
|
86
|
+
description: 'Registered tool categories this conversation may use (registerToolCategory names, e.g. ["figure-authoring"]). Omit to allow every registered category. An empty list allows none — a plain conversation.'
|
|
87
|
+
}),
|
|
88
|
+
tslib_1.__metadata("design:type", Array)
|
|
89
|
+
], AssistantChatInput.prototype, "toolCategories", void 0);
|
|
90
|
+
tslib_1.__decorate([
|
|
91
|
+
(0, type_graphql_1.Field)(() => graphql_scalars_1.GraphQLJSON, {
|
|
92
|
+
nullable: true,
|
|
93
|
+
description: 'What this surface is looking at (e.g. { figureId } while modelling). Tools read it so the model never has to ask the user for identifiers. Client-supplied — never used for authorization; a tool verifies the scope belongs to the caller tenant.'
|
|
94
|
+
}),
|
|
95
|
+
tslib_1.__metadata("design:type", Object)
|
|
96
|
+
], AssistantChatInput.prototype, "hostContext", void 0);
|
|
97
|
+
tslib_1.__decorate([
|
|
98
|
+
(0, type_graphql_1.Field)({
|
|
99
|
+
nullable: true,
|
|
100
|
+
description: 'Last message to keep from the stored history. The client keeps its own truncation when a user edits a message, so pass the last kept id to hide the retracted span from the model.'
|
|
101
|
+
}),
|
|
102
|
+
tslib_1.__metadata("design:type", String)
|
|
103
|
+
], AssistantChatInput.prototype, "truncateAfterMessageId", void 0);
|
|
104
|
+
tslib_1.__decorate([
|
|
105
|
+
(0, type_graphql_1.Field)({
|
|
106
|
+
nullable: true,
|
|
107
|
+
description: 'Require a tool call before the model may answer. For surfaces that report live state, where an unchecked assertion reads the same as a checked one.'
|
|
108
|
+
}),
|
|
109
|
+
tslib_1.__metadata("design:type", Boolean)
|
|
110
|
+
], AssistantChatInput.prototype, "requireGroundingTools", void 0);
|
|
111
|
+
tslib_1.__decorate([
|
|
112
|
+
(0, type_graphql_1.Field)({ nullable: true, description: 'Model identifier override. Falls back to the configured default.' }),
|
|
113
|
+
tslib_1.__metadata("design:type", String)
|
|
114
|
+
], AssistantChatInput.prototype, "model", void 0);
|
|
115
|
+
tslib_1.__decorate([
|
|
116
|
+
(0, type_graphql_1.Field)({ nullable: true, description: 'Sampling temperature override.' }),
|
|
117
|
+
tslib_1.__metadata("design:type", Number)
|
|
118
|
+
], AssistantChatInput.prototype, "temperature", void 0);
|
|
119
|
+
tslib_1.__decorate([
|
|
120
|
+
(0, type_graphql_1.Field)({ nullable: true, description: 'Max output tokens per call.' }),
|
|
121
|
+
tslib_1.__metadata("design:type", Number)
|
|
122
|
+
], AssistantChatInput.prototype, "maxTokens", void 0);
|
|
123
|
+
AssistantChatInput = tslib_1.__decorate([
|
|
124
|
+
(0, type_graphql_1.InputType)({ description: 'One turn of a conversation that has no document of its own.' })
|
|
125
|
+
], AssistantChatInput);
|
|
126
|
+
let AssistantChatOutput = class AssistantChatOutput {
|
|
127
|
+
};
|
|
128
|
+
tslib_1.__decorate([
|
|
129
|
+
(0, type_graphql_1.Field)({ description: 'The reply, including the notice when the loop stopped early.' }),
|
|
130
|
+
tslib_1.__metadata("design:type", String)
|
|
131
|
+
], AssistantChatOutput.prototype, "reply", void 0);
|
|
132
|
+
tslib_1.__decorate([
|
|
133
|
+
(0, type_graphql_1.Field)({ description: 'AI client identifier (provider:model).' }),
|
|
134
|
+
tslib_1.__metadata("design:type", String)
|
|
135
|
+
], AssistantChatOutput.prototype, "clientId", void 0);
|
|
136
|
+
tslib_1.__decorate([
|
|
137
|
+
(0, type_graphql_1.Field)({ nullable: true, description: 'Echo of the session id, when persisted.' }),
|
|
138
|
+
tslib_1.__metadata("design:type", String)
|
|
139
|
+
], AssistantChatOutput.prototype, "sessionId", void 0);
|
|
140
|
+
tslib_1.__decorate([
|
|
141
|
+
(0, type_graphql_1.Field)({ nullable: true, description: 'Stored ChatMessage id of the user input.' }),
|
|
142
|
+
tslib_1.__metadata("design:type", String)
|
|
143
|
+
], AssistantChatOutput.prototype, "userMessageId", void 0);
|
|
144
|
+
tslib_1.__decorate([
|
|
145
|
+
(0, type_graphql_1.Field)({ nullable: true, description: 'Stored ChatMessage id of the reply.' }),
|
|
146
|
+
tslib_1.__metadata("design:type", String)
|
|
147
|
+
], AssistantChatOutput.prototype, "assistantMessageId", void 0);
|
|
148
|
+
tslib_1.__decorate([
|
|
149
|
+
(0, type_graphql_1.Field)(() => graphql_scalars_1.GraphQLJSON, {
|
|
150
|
+
nullable: true,
|
|
151
|
+
description: 'Tool usages during the turn — {name, arguments, result, kind}. The surface shows them foldably.'
|
|
152
|
+
}),
|
|
153
|
+
tslib_1.__metadata("design:type", Object)
|
|
154
|
+
], AssistantChatOutput.prototype, "toolUsages", void 0);
|
|
155
|
+
tslib_1.__decorate([
|
|
156
|
+
(0, type_graphql_1.Field)(() => [String], {
|
|
157
|
+
nullable: true,
|
|
158
|
+
description: 'Tool names offered to the model this turn. A surface that expected a category and sees it missing here knows the registration did not happen, rather than reading a vague answer as a refusal.'
|
|
159
|
+
}),
|
|
160
|
+
tslib_1.__metadata("design:type", Array)
|
|
161
|
+
], AssistantChatOutput.prototype, "offeredTools", void 0);
|
|
162
|
+
tslib_1.__decorate([
|
|
163
|
+
(0, type_graphql_1.Field)(() => graphql_scalars_1.GraphQLJSON, {
|
|
164
|
+
nullable: true,
|
|
165
|
+
description: 'Actions a tool recorded without executing — each carries what the tool returned alongside `proposed: true`. The surface renders a confirm button; executing is the user\'s act.'
|
|
166
|
+
}),
|
|
167
|
+
tslib_1.__metadata("design:type", Object)
|
|
168
|
+
], AssistantChatOutput.prototype, "proposals", void 0);
|
|
169
|
+
tslib_1.__decorate([
|
|
170
|
+
(0, type_graphql_1.Field)(() => graphql_scalars_1.GraphQLJSON, {
|
|
171
|
+
nullable: true,
|
|
172
|
+
description: 'Identifiers in the reply that appear in nothing the model received. Hallucination candidates surfaced to the user; the reply itself is not altered. Null when the answer is grounded.'
|
|
173
|
+
}),
|
|
174
|
+
tslib_1.__metadata("design:type", Object)
|
|
175
|
+
], AssistantChatOutput.prototype, "groundingWarnings", void 0);
|
|
176
|
+
AssistantChatOutput = tslib_1.__decorate([
|
|
177
|
+
(0, type_graphql_1.ObjectType)()
|
|
178
|
+
], AssistantChatOutput);
|
|
179
|
+
function resolveAIClient() {
|
|
180
|
+
const existing = (0, ai_client_base_1.getDefaultAIClient)();
|
|
181
|
+
if (existing)
|
|
182
|
+
return existing;
|
|
183
|
+
const cfg = env_1.config.get('aiClient', null);
|
|
184
|
+
if (cfg && cfg.provider)
|
|
185
|
+
return (0, ai_client_base_1.createAIClient)(cfg);
|
|
186
|
+
return undefined;
|
|
187
|
+
}
|
|
188
|
+
let AssistantChatResolver = class AssistantChatResolver {
|
|
189
|
+
/*
|
|
190
|
+
* No `@transaction`, for the same reason the board conversation has none: a model call takes
|
|
191
|
+
* seconds to tens of seconds, and holding a write lock for that long collides with everything
|
|
192
|
+
* else on SQLite. Writes are short and around the call. For a conversation, a partly stored
|
|
193
|
+
* history is safer than a lost one.
|
|
194
|
+
*/
|
|
195
|
+
async assistantChat(input, context) {
|
|
196
|
+
const client = resolveAIClient();
|
|
197
|
+
if (!client) {
|
|
198
|
+
throw new Error('No AI client configured. Set config.aiClient = { provider, model, apiKey } or call setDefaultAIClient(...).');
|
|
199
|
+
}
|
|
200
|
+
const { domain, user } = context.state;
|
|
201
|
+
const sessionRepo = (0, shell_1.getRepository)(chat_session_js_1.ChatSession);
|
|
202
|
+
const messageRepo = (0, shell_1.getRepository)(chat_message_js_1.ChatMessage);
|
|
203
|
+
let session;
|
|
204
|
+
if (input.sessionId) {
|
|
205
|
+
const found = await sessionRepo.findOneBy({ id: input.sessionId, domain: { id: domain.id } });
|
|
206
|
+
if (!found)
|
|
207
|
+
throw new Error(`ChatSession ${input.sessionId} not found`);
|
|
208
|
+
session = found;
|
|
209
|
+
}
|
|
210
|
+
const lastUserMessage = input.messages[input.messages.length - 1];
|
|
211
|
+
let userMessageId;
|
|
212
|
+
if (session && lastUserMessage?.role === 'user') {
|
|
213
|
+
const previousLast = await messageRepo.findOne({
|
|
214
|
+
where: { session: { id: session.id } },
|
|
215
|
+
order: { createdAt: 'DESC' }
|
|
216
|
+
});
|
|
217
|
+
const saved = await messageRepo.save({
|
|
218
|
+
session: { id: session.id },
|
|
219
|
+
role: 'user',
|
|
220
|
+
content: lastUserMessage.content,
|
|
221
|
+
creator: user,
|
|
222
|
+
updater: user,
|
|
223
|
+
parentMessage: previousLast ? { id: previousLast.id } : undefined
|
|
224
|
+
});
|
|
225
|
+
userMessageId = saved.id;
|
|
226
|
+
/*
|
|
227
|
+
* Session activity is denormalised for the list: most-recent ordering and another
|
|
228
|
+
* participant's unread badge read it. Best effort — a conversation must not fail because a
|
|
229
|
+
* list sorts one position late.
|
|
230
|
+
*/
|
|
231
|
+
try {
|
|
232
|
+
const patch = (0, session_inbox_js_1.activityPatch)(lastUserMessage.content);
|
|
233
|
+
if (!session.name)
|
|
234
|
+
patch.name = (0, session_inbox_js_1.autoSessionName)(lastUserMessage.content);
|
|
235
|
+
await sessionRepo.update(session.id, patch);
|
|
236
|
+
}
|
|
237
|
+
catch {
|
|
238
|
+
/* noop */
|
|
239
|
+
}
|
|
240
|
+
(0, chat_message_publish_js_1.publishChatMessage)({ domainId: domain.id, sessionId: session.id, message: saved });
|
|
241
|
+
(0, session_activity_publish_js_1.publishSessionActivity)({
|
|
242
|
+
domainId: domain.id,
|
|
243
|
+
sessionId: session.id,
|
|
244
|
+
kind: 'message',
|
|
245
|
+
anchorType: session.anchorType,
|
|
246
|
+
anchorId: session.anchorId,
|
|
247
|
+
preview: lastUserMessage.content,
|
|
248
|
+
actorId: user?.id
|
|
249
|
+
});
|
|
250
|
+
}
|
|
251
|
+
const conversation = session
|
|
252
|
+
? (0, llm_history_js_1.buildLlmHistory)(await messageRepo.find({
|
|
253
|
+
where: { session: { id: session.id } },
|
|
254
|
+
order: { createdAt: 'ASC' },
|
|
255
|
+
relations: ['creator']
|
|
256
|
+
}), {
|
|
257
|
+
maxTurns: LLM_HISTORY_MAX_TURNS,
|
|
258
|
+
truncateAfterMessageId: input.truncateAfterMessageId,
|
|
259
|
+
/* The message just stored is this turn's question — truncation must never drop it. */
|
|
260
|
+
keepMessageIds: userMessageId ? [userMessageId] : undefined,
|
|
261
|
+
summary: session.lastSummary
|
|
262
|
+
? { text: session.lastSummary, upToMessageId: session.summaryUpToMessageId }
|
|
263
|
+
: undefined
|
|
264
|
+
})
|
|
265
|
+
: input.messages.map(message => ({ role: message.role, content: message.content }));
|
|
266
|
+
const result = await (0, ai_client_base_1.registryAssistantChat)({
|
|
267
|
+
client,
|
|
268
|
+
messages: conversation,
|
|
269
|
+
systemPrompt: input.systemPrompt,
|
|
270
|
+
toolCategories: input.toolCategories,
|
|
271
|
+
hostContext: input.hostContext,
|
|
272
|
+
state: context.state,
|
|
273
|
+
model: input.model,
|
|
274
|
+
temperature: input.temperature,
|
|
275
|
+
maxTokens: input.maxTokens,
|
|
276
|
+
requireToolOnFirstTurn: input.requireGroundingTools
|
|
277
|
+
});
|
|
278
|
+
let assistantMessageId;
|
|
279
|
+
if (session) {
|
|
280
|
+
const stored = await messageRepo.save({
|
|
281
|
+
session: { id: session.id },
|
|
282
|
+
role: 'assistant',
|
|
283
|
+
content: result.reply,
|
|
284
|
+
toolUsages: result.loop.toolUsages?.length ? JSON.stringify(result.loop.toolUsages) : undefined,
|
|
285
|
+
groundingWarnings: result.groundingWarnings?.length ? JSON.stringify(result.groundingWarnings) : undefined,
|
|
286
|
+
creator: user,
|
|
287
|
+
updater: user,
|
|
288
|
+
parentMessage: userMessageId ? { id: userMessageId } : undefined
|
|
289
|
+
});
|
|
290
|
+
assistantMessageId = stored.id;
|
|
291
|
+
(0, chat_message_publish_js_1.publishChatMessage)({ domainId: domain.id, sessionId: session.id, message: stored });
|
|
292
|
+
/* The reply is session activity too. Leaving it out shows other participants "nothing new". */
|
|
293
|
+
(0, session_activity_publish_js_1.publishSessionActivity)({
|
|
294
|
+
domainId: domain.id,
|
|
295
|
+
sessionId: session.id,
|
|
296
|
+
kind: 'message',
|
|
297
|
+
anchorType: session.anchorType,
|
|
298
|
+
anchorId: session.anchorId,
|
|
299
|
+
preview: result.reply,
|
|
300
|
+
actorId: user?.id
|
|
301
|
+
});
|
|
302
|
+
await sessionRepo.update(session.id, {
|
|
303
|
+
aiClientId: client.id,
|
|
304
|
+
updater: user,
|
|
305
|
+
...(0, session_inbox_js_1.activityPatch)(result.reply)
|
|
306
|
+
});
|
|
307
|
+
}
|
|
308
|
+
return {
|
|
309
|
+
reply: result.reply,
|
|
310
|
+
clientId: client.id,
|
|
311
|
+
sessionId: session?.id,
|
|
312
|
+
userMessageId,
|
|
313
|
+
assistantMessageId,
|
|
314
|
+
toolUsages: result.loop.toolUsages?.length ? result.loop.toolUsages : null,
|
|
315
|
+
offeredTools: result.offeredTools,
|
|
316
|
+
proposals: result.loop.proposals?.length ? result.loop.proposals : null,
|
|
317
|
+
groundingWarnings: result.groundingWarnings?.length ? result.groundingWarnings : null
|
|
318
|
+
};
|
|
319
|
+
}
|
|
320
|
+
};
|
|
321
|
+
exports.AssistantChatResolver = AssistantChatResolver;
|
|
322
|
+
tslib_1.__decorate([
|
|
323
|
+
(0, type_graphql_1.Mutation)(() => AssistantChatOutput, {
|
|
324
|
+
description: 'Ask the assistant a question on a surface that has no document of its own. Tools come from the registry; with a sessionId the conversation is persisted.'
|
|
325
|
+
})
|
|
326
|
+
/*
|
|
327
|
+
* `ai-assistant:mutation` — the same door as the board conversation's, which moved here on
|
|
328
|
+
* 2026-09-10. No `legacy` alias: this operation is new, so no one was ever passing under an old
|
|
329
|
+
* name, and declaring an alias would open a second name that nothing needs.
|
|
330
|
+
*/
|
|
331
|
+
,
|
|
332
|
+
(0, type_graphql_1.Directive)('@privilege(category: "ai-assistant", privilege: "mutation")'),
|
|
333
|
+
tslib_1.__param(0, (0, type_graphql_1.Arg)('input')),
|
|
334
|
+
tslib_1.__param(1, (0, type_graphql_1.Ctx)()),
|
|
335
|
+
tslib_1.__metadata("design:type", Function),
|
|
336
|
+
tslib_1.__metadata("design:paramtypes", [AssistantChatInput, Object]),
|
|
337
|
+
tslib_1.__metadata("design:returntype", Promise)
|
|
338
|
+
], AssistantChatResolver.prototype, "assistantChat", null);
|
|
339
|
+
exports.AssistantChatResolver = AssistantChatResolver = tslib_1.__decorate([
|
|
340
|
+
(0, type_graphql_1.Resolver)()
|
|
341
|
+
], AssistantChatResolver);
|
|
342
|
+
//# sourceMappingURL=assistant-chat-resolver.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"assistant-chat-resolver.js","sourceRoot":"","sources":["../../server/service/assistant-chat-resolver.ts"],"names":[],"mappings":"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","sourcesContent":["/*\n * The neutral door into a conversation.\n *\n * ── Why this exists (2026-09-11) ───────────────────────────────────────────────\n * The chat component moved here from `board-ai`, but the only mutation it could call stayed\n * behind. operato-figure mounted the dock, registered four tools, and got\n * `Cannot query field \"boardAIChat\" on type \"Mutation\"` — it does not load board-ai. The twin has\n * the same shape and it does not show: its space deliberation runs through board-ai's door purely\n * because the board authoring half of the same product already loaded that package.\n *\n * ── Why the board resolver was not moved ───────────────────────────────────────\n * `board-ai-resolver.ts` is 692 lines and the larger part of it is board authoring itself:\n * `currentBoard` as a BoardModel, PatchEntry, board patch broadcasts, refid injection for\n * component mentions. Moving that here would teach this package the board document, which is the\n * dependency we just cut, pointing the other way.\n *\n * So the door moved and the editor did not. `boardAIChat` stays in board-ai for board authoring;\n * this one is for a surface that asks questions and runs registered tools.\n *\n * ── What this is not ───────────────────────────────────────────────────────────\n * It has no document, so it takes no document and returns no patch. Tools that write to a\n * document are not offered here — `registryAssistantChat` enforces that, and it decides what may\n * be dispatched from the same list it offers, so the two cannot drift apart.\n */\nimport { Arg, Ctx, Directive, Field, InputType, Mutation, ObjectType, Resolver } from 'type-graphql'\nimport { GraphQLJSON } from 'graphql-scalars'\nimport '@things-factory/auth-base'\n\nimport { config } from '@things-factory/env'\nimport { getRepository } from '@things-factory/shell'\nimport { createAIClient, getDefaultAIClient, registryAssistantChat, type AIClient } from '@things-factory/ai-client-base'\n\nimport { ChatSession } from './chat-session/chat-session.js'\nimport { ChatMessage } from './chat-message/chat-message.js'\nimport { buildLlmHistory } from './chat-message/llm-history.js'\nimport { publishChatMessage } from './chat-message/chat-message-publish.js'\nimport { publishSessionActivity } from './chat-session/session-activity-publish.js'\nimport { activityPatch, autoSessionName } from './chat-session/session-inbox.js'\n\n/**\n * How many stored messages reach the model.\n *\n * A deliberation runs for days. Without a cap the prompt grows without bound and the oldest turns\n * bury the recent ones. Kept the same as the board conversation's cap so the two surfaces of one\n * product do not remember differently.\n */\nconst LLM_HISTORY_MAX_TURNS = 40\n\n@InputType()\nclass AssistantMessageInput {\n @Field({ description: \"Role: 'user' or 'assistant'\" })\n role: string\n\n @Field({ description: 'Message content' })\n content: string\n}\n\n@InputType({ description: 'One turn of a conversation that has no document of its own.' })\nclass AssistantChatInput {\n @Field({\n nullable: true,\n description: 'ChatSession id. Omit for a one-off question with no persistence.'\n })\n sessionId?: string\n\n @Field(() => [AssistantMessageInput], {\n description:\n 'Conversation, newest last. For a persisted session only the last user message is appended; the rest is read from storage.'\n })\n messages: AssistantMessageInput[]\n\n @Field({\n description:\n 'Who the assistant is on this surface, in the words of the product that owns the screen. The rules for using each tool come from whoever registered it and are appended by the server.'\n })\n systemPrompt: string\n\n @Field(() => [String], {\n nullable: true,\n description:\n 'Registered tool categories this conversation may use (registerToolCategory names, e.g. [\"figure-authoring\"]). Omit to allow every registered category. An empty list allows none — a plain conversation.'\n })\n toolCategories?: string[]\n\n @Field(() => GraphQLJSON, {\n nullable: true,\n description:\n 'What this surface is looking at (e.g. { figureId } while modelling). Tools read it so the model never has to ask the user for identifiers. Client-supplied — never used for authorization; a tool verifies the scope belongs to the caller tenant.'\n })\n hostContext?: any\n\n @Field({\n nullable: true,\n description:\n 'Last message to keep from the stored history. The client keeps its own truncation when a user edits a message, so pass the last kept id to hide the retracted span from the model.'\n })\n truncateAfterMessageId?: string\n\n @Field({\n nullable: true,\n description:\n 'Require a tool call before the model may answer. For surfaces that report live state, where an unchecked assertion reads the same as a checked one.'\n })\n requireGroundingTools?: boolean\n\n @Field({ nullable: true, description: 'Model identifier override. Falls back to the configured default.' })\n model?: string\n\n @Field({ nullable: true, description: 'Sampling temperature override.' })\n temperature?: number\n\n @Field({ nullable: true, description: 'Max output tokens per call.' })\n maxTokens?: number\n}\n\n@ObjectType()\nclass AssistantChatOutput {\n @Field({ description: 'The reply, including the notice when the loop stopped early.' })\n reply: string\n\n @Field({ description: 'AI client identifier (provider:model).' })\n clientId: string\n\n @Field({ nullable: true, description: 'Echo of the session id, when persisted.' })\n sessionId?: string\n\n @Field({ nullable: true, description: 'Stored ChatMessage id of the user input.' })\n userMessageId?: string\n\n @Field({ nullable: true, description: 'Stored ChatMessage id of the reply.' })\n assistantMessageId?: string\n\n @Field(() => GraphQLJSON, {\n nullable: true,\n description: 'Tool usages during the turn — {name, arguments, result, kind}. The surface shows them foldably.'\n })\n toolUsages?: any\n\n @Field(() => [String], {\n nullable: true,\n description:\n 'Tool names offered to the model this turn. A surface that expected a category and sees it missing here knows the registration did not happen, rather than reading a vague answer as a refusal.'\n })\n offeredTools?: string[]\n\n @Field(() => GraphQLJSON, {\n nullable: true,\n description:\n 'Actions a tool recorded without executing — each carries what the tool returned alongside `proposed: true`. The surface renders a confirm button; executing is the user\\'s act.'\n })\n proposals?: any\n\n @Field(() => GraphQLJSON, {\n nullable: true,\n description:\n 'Identifiers in the reply that appear in nothing the model received. Hallucination candidates surfaced to the user; the reply itself is not altered. Null when the answer is grounded.'\n })\n groundingWarnings?: any\n}\n\nfunction resolveAIClient(): AIClient | undefined {\n const existing = getDefaultAIClient()\n if (existing) return existing\n const cfg = config.get('aiClient', null) as any\n if (cfg && cfg.provider) return createAIClient(cfg)\n return undefined\n}\n\n@Resolver()\nexport class AssistantChatResolver {\n /*\n * No `@transaction`, for the same reason the board conversation has none: a model call takes\n * seconds to tens of seconds, and holding a write lock for that long collides with everything\n * else on SQLite. Writes are short and around the call. For a conversation, a partly stored\n * history is safer than a lost one.\n */\n @Mutation(() => AssistantChatOutput, {\n description:\n 'Ask the assistant a question on a surface that has no document of its own. Tools come from the registry; with a sessionId the conversation is persisted.'\n })\n /*\n * `ai-assistant:mutation` — the same door as the board conversation's, which moved here on\n * 2026-09-10. No `legacy` alias: this operation is new, so no one was ever passing under an old\n * name, and declaring an alias would open a second name that nothing needs.\n */\n @Directive('@privilege(category: \"ai-assistant\", privilege: \"mutation\")')\n async assistantChat(@Arg('input') input: AssistantChatInput, @Ctx() context: any): Promise<AssistantChatOutput> {\n const client = resolveAIClient()\n if (!client) {\n throw new Error(\n 'No AI client configured. Set config.aiClient = { provider, model, apiKey } or call setDefaultAIClient(...).'\n )\n }\n\n const { domain, user } = context.state\n const sessionRepo = getRepository(ChatSession)\n const messageRepo = getRepository(ChatMessage)\n\n let session: ChatSession | undefined\n if (input.sessionId) {\n const found = await sessionRepo.findOneBy({ id: input.sessionId, domain: { id: domain.id } as any })\n if (!found) throw new Error(`ChatSession ${input.sessionId} not found`)\n session = found\n }\n\n const lastUserMessage = input.messages[input.messages.length - 1]\n let userMessageId: string | undefined\n\n if (session && lastUserMessage?.role === 'user') {\n const previousLast = await messageRepo.findOne({\n where: { session: { id: session.id } as any },\n order: { createdAt: 'DESC' }\n })\n const saved = await messageRepo.save({\n session: { id: session.id } as any,\n role: 'user',\n content: lastUserMessage.content,\n creator: user,\n updater: user,\n parentMessage: previousLast ? ({ id: previousLast.id } as any) : undefined\n })\n userMessageId = saved.id\n\n /*\n * Session activity is denormalised for the list: most-recent ordering and another\n * participant's unread badge read it. Best effort — a conversation must not fail because a\n * list sorts one position late.\n */\n try {\n const patch: any = activityPatch(lastUserMessage.content)\n if (!session.name) patch.name = autoSessionName(lastUserMessage.content)\n await sessionRepo.update(session.id!, patch)\n } catch {\n /* noop */\n }\n\n publishChatMessage({ domainId: domain.id, sessionId: session.id, message: saved } as any)\n publishSessionActivity({\n domainId: domain.id,\n sessionId: session.id!,\n kind: 'message',\n anchorType: (session as any).anchorType,\n anchorId: (session as any).anchorId,\n preview: lastUserMessage.content,\n actorId: user?.id\n })\n }\n\n const conversation = session\n ? buildLlmHistory(\n await messageRepo.find({\n where: { session: { id: session.id } as any },\n order: { createdAt: 'ASC' },\n relations: ['creator']\n }),\n {\n maxTurns: LLM_HISTORY_MAX_TURNS,\n truncateAfterMessageId: input.truncateAfterMessageId,\n /* The message just stored is this turn's question — truncation must never drop it. */\n keepMessageIds: userMessageId ? [userMessageId] : undefined,\n summary: (session as any).lastSummary\n ? { text: (session as any).lastSummary, upToMessageId: (session as any).summaryUpToMessageId }\n : undefined\n }\n )\n : input.messages.map(message => ({ role: message.role as any, content: message.content }))\n\n const result = await registryAssistantChat({\n client,\n messages: conversation as any,\n systemPrompt: input.systemPrompt,\n toolCategories: input.toolCategories,\n hostContext: input.hostContext,\n state: context.state,\n model: input.model,\n temperature: input.temperature,\n maxTokens: input.maxTokens,\n requireToolOnFirstTurn: input.requireGroundingTools\n })\n\n let assistantMessageId: string | undefined\n if (session) {\n const stored = await messageRepo.save({\n session: { id: session.id } as any,\n role: 'assistant',\n content: result.reply,\n toolUsages: result.loop.toolUsages?.length ? JSON.stringify(result.loop.toolUsages) : undefined,\n groundingWarnings: result.groundingWarnings?.length ? JSON.stringify(result.groundingWarnings) : undefined,\n creator: user,\n updater: user,\n parentMessage: userMessageId ? ({ id: userMessageId } as any) : undefined\n })\n assistantMessageId = stored.id\n\n publishChatMessage({ domainId: domain.id, sessionId: session.id, message: stored } as any)\n /* The reply is session activity too. Leaving it out shows other participants \"nothing new\". */\n publishSessionActivity({\n domainId: domain.id,\n sessionId: session.id!,\n kind: 'message',\n anchorType: (session as any).anchorType,\n anchorId: (session as any).anchorId,\n preview: result.reply,\n actorId: user?.id\n })\n\n await sessionRepo.update(session.id!, {\n aiClientId: client.id,\n updater: user,\n ...activityPatch(result.reply)\n } as any)\n }\n\n return {\n reply: result.reply,\n clientId: client.id,\n sessionId: session?.id,\n userMessageId,\n assistantMessageId,\n toolUsages: result.loop.toolUsages?.length ? result.loop.toolUsages : null,\n offeredTools: result.offeredTools,\n proposals: result.loop.proposals?.length ? result.loop.proposals : null,\n groundingWarnings: result.groundingWarnings?.length ? result.groundingWarnings : null\n }\n }\n}\n"]}
|
|
@@ -480,7 +480,7 @@ let ChatSessionResolver = class ChatSessionResolver {
|
|
|
480
480
|
exports.ChatSessionResolver = ChatSessionResolver;
|
|
481
481
|
tslib_1.__decorate([
|
|
482
482
|
(0, type_graphql_1.Query)(() => chat_session_js_1.ChatSession, { nullable: true, description: 'Get AI chat session by id.' }),
|
|
483
|
-
(0, type_graphql_1.Directive)('@privilege(category: "ai-assistant", privilege: "query"
|
|
483
|
+
(0, type_graphql_1.Directive)('@privilege(category: "ai-assistant", privilege: "query")'),
|
|
484
484
|
tslib_1.__param(0, (0, type_graphql_1.Arg)('id')),
|
|
485
485
|
tslib_1.__param(1, (0, type_graphql_1.Ctx)()),
|
|
486
486
|
tslib_1.__metadata("design:type", Function),
|
|
@@ -491,7 +491,7 @@ tslib_1.__decorate([
|
|
|
491
491
|
(0, type_graphql_1.Query)(() => [chat_session_js_1.ChatSession], {
|
|
492
492
|
description: 'List AI chat sessions anchored to a target (oldest first). Anchor decides the nature and the history boundary of the conversation: board = authoring, space = the site and the twins running there. Board anchor also matches legacy rows that only have boardId.'
|
|
493
493
|
}),
|
|
494
|
-
(0, type_graphql_1.Directive)('@privilege(category: "ai-assistant", privilege: "query"
|
|
494
|
+
(0, type_graphql_1.Directive)('@privilege(category: "ai-assistant", privilege: "query")'),
|
|
495
495
|
tslib_1.__param(0, (0, type_graphql_1.Arg)('anchorType')),
|
|
496
496
|
tslib_1.__param(1, (0, type_graphql_1.Arg)('anchorId')),
|
|
497
497
|
tslib_1.__param(2, (0, type_graphql_1.Ctx)()),
|
|
@@ -503,7 +503,7 @@ tslib_1.__decorate([
|
|
|
503
503
|
(0, type_graphql_1.Query)(() => [ChatSessionInboxEntry], {
|
|
504
504
|
description: "List sessions anchored to a target with this viewer's unread state, most recent activity first. Supports free-text search over name and last preview, a mine-only filter, hidden inclusion, and paging. Uses the denormalized last-activity timestamp compared against the viewer's read point — no per-session aggregation."
|
|
505
505
|
}),
|
|
506
|
-
(0, type_graphql_1.Directive)('@privilege(category: "ai-assistant", privilege: "query"
|
|
506
|
+
(0, type_graphql_1.Directive)('@privilege(category: "ai-assistant", privilege: "query")'),
|
|
507
507
|
tslib_1.__param(0, (0, type_graphql_1.Arg)('anchorType')),
|
|
508
508
|
tslib_1.__param(1, (0, type_graphql_1.Arg)('anchorId')),
|
|
509
509
|
tslib_1.__param(2, (0, type_graphql_1.Ctx)()),
|
|
@@ -520,7 +520,7 @@ tslib_1.__decorate([
|
|
|
520
520
|
(0, type_graphql_1.Query)(() => type_graphql_1.Int, {
|
|
521
521
|
description: 'Total number of sessions matching the same filters as chatSessionInbox (before paging). Request it alongside the list so the UI can tell "end of page" from "end of data".'
|
|
522
522
|
}),
|
|
523
|
-
(0, type_graphql_1.Directive)('@privilege(category: "ai-assistant", privilege: "query"
|
|
523
|
+
(0, type_graphql_1.Directive)('@privilege(category: "ai-assistant", privilege: "query")'),
|
|
524
524
|
tslib_1.__param(0, (0, type_graphql_1.Arg)('anchorType')),
|
|
525
525
|
tslib_1.__param(1, (0, type_graphql_1.Arg)('anchorId')),
|
|
526
526
|
tslib_1.__param(2, (0, type_graphql_1.Ctx)()),
|
|
@@ -535,7 +535,7 @@ tslib_1.__decorate([
|
|
|
535
535
|
(0, type_graphql_1.Query)(() => type_graphql_1.Int, {
|
|
536
536
|
description: "Number of sessions on this anchor that the caller has hidden and that have had no activity since. Meant to be requested alongside chatSessionInbox in the same operation so the filter can say how many are folded away."
|
|
537
537
|
}),
|
|
538
|
-
(0, type_graphql_1.Directive)('@privilege(category: "ai-assistant", privilege: "query"
|
|
538
|
+
(0, type_graphql_1.Directive)('@privilege(category: "ai-assistant", privilege: "query")'),
|
|
539
539
|
tslib_1.__param(0, (0, type_graphql_1.Arg)('anchorType')),
|
|
540
540
|
tslib_1.__param(1, (0, type_graphql_1.Arg)('anchorId')),
|
|
541
541
|
tslib_1.__param(2, (0, type_graphql_1.Ctx)()),
|
|
@@ -547,7 +547,7 @@ tslib_1.__decorate([
|
|
|
547
547
|
(0, type_graphql_1.Mutation)(() => Boolean, {
|
|
548
548
|
description: 'Delete a chat session — creator only, since a shared deliberation cannot be un-deleted from the UI. Participants who are not the creator can hide it from their own list instead (hideAISession). Soft delete; broadcast as meta activity so every list drops it.'
|
|
549
549
|
}),
|
|
550
|
-
(0, type_graphql_1.Directive)('@privilege(category: "ai-assistant", privilege: "mutation"
|
|
550
|
+
(0, type_graphql_1.Directive)('@privilege(category: "ai-assistant", privilege: "mutation")'),
|
|
551
551
|
tslib_1.__param(0, (0, type_graphql_1.Arg)('sessionId')),
|
|
552
552
|
tslib_1.__param(1, (0, type_graphql_1.Ctx)()),
|
|
553
553
|
tslib_1.__metadata("design:type", Function),
|
|
@@ -558,7 +558,7 @@ tslib_1.__decorate([
|
|
|
558
558
|
(0, type_graphql_1.Mutation)(() => Boolean, {
|
|
559
559
|
description: 'Hide (or unhide) a session from the calling user\'s own list. Per-user and reversible — the session and other participants are unaffected. Activity newer than the hide makes it reappear; the inbox filter (showHidden) reveals hidden ones.'
|
|
560
560
|
}),
|
|
561
|
-
(0, type_graphql_1.Directive)('@privilege(category: "ai-assistant", privilege: "mutation"
|
|
561
|
+
(0, type_graphql_1.Directive)('@privilege(category: "ai-assistant", privilege: "mutation")'),
|
|
562
562
|
tslib_1.__param(0, (0, type_graphql_1.Arg)('sessionId')),
|
|
563
563
|
tslib_1.__param(1, (0, type_graphql_1.Ctx)()),
|
|
564
564
|
tslib_1.__param(2, (0, type_graphql_1.Arg)('hidden', { nullable: true })),
|
|
@@ -570,7 +570,7 @@ tslib_1.__decorate([
|
|
|
570
570
|
(0, type_graphql_1.Mutation)(() => Boolean, {
|
|
571
571
|
description: 'Rename a chat session. Any participant may rename it — the name is the topic label of a shared deliberation and the change is reversible. Broadcast as meta activity so every participant sees it without raising unread badges.'
|
|
572
572
|
}),
|
|
573
|
-
(0, type_graphql_1.Directive)('@privilege(category: "ai-assistant", privilege: "mutation"
|
|
573
|
+
(0, type_graphql_1.Directive)('@privilege(category: "ai-assistant", privilege: "mutation")'),
|
|
574
574
|
tslib_1.__param(0, (0, type_graphql_1.Arg)('sessionId')),
|
|
575
575
|
tslib_1.__param(1, (0, type_graphql_1.Arg)('name')),
|
|
576
576
|
tslib_1.__param(2, (0, type_graphql_1.Ctx)()),
|
|
@@ -582,7 +582,7 @@ tslib_1.__decorate([
|
|
|
582
582
|
(0, type_graphql_1.Mutation)(() => Boolean, {
|
|
583
583
|
description: "Mark a session as read up to now for the current user. Idempotent. Distinct from presence (lastSeenAt) — a window can be open without being read."
|
|
584
584
|
}),
|
|
585
|
-
(0, type_graphql_1.Directive)('@privilege(category: "ai-assistant", privilege: "mutation"
|
|
585
|
+
(0, type_graphql_1.Directive)('@privilege(category: "ai-assistant", privilege: "mutation")'),
|
|
586
586
|
tslib_1.__param(0, (0, type_graphql_1.Arg)('sessionId')),
|
|
587
587
|
tslib_1.__param(1, (0, type_graphql_1.Ctx)()),
|
|
588
588
|
tslib_1.__metadata("design:type", Function),
|
|
@@ -593,7 +593,7 @@ tslib_1.__decorate([
|
|
|
593
593
|
(0, type_graphql_1.Query)(() => [auth_base_1.User], {
|
|
594
594
|
description: 'List users in the current domain for `@` mention popup. board-ai 권한이면 누구나 멘션용 검색 가능 (관리자 전용 users() 와 별도).'
|
|
595
595
|
}),
|
|
596
|
-
(0, type_graphql_1.Directive)('@privilege(category: "ai-assistant", privilege: "query"
|
|
596
|
+
(0, type_graphql_1.Directive)('@privilege(category: "ai-assistant", privilege: "query")'),
|
|
597
597
|
tslib_1.__param(0, (0, type_graphql_1.Arg)('query', { nullable: true, description: 'Substring to match against name/email (case-insensitive). Empty → all.' })),
|
|
598
598
|
tslib_1.__param(1, (0, type_graphql_1.Arg)('limit', () => type_graphql_1.Int, { nullable: true, defaultValue: 50 })),
|
|
599
599
|
tslib_1.__param(2, (0, type_graphql_1.Ctx)()),
|
|
@@ -605,7 +605,7 @@ tslib_1.__decorate([
|
|
|
605
605
|
(0, type_graphql_1.Query)(() => [chat_session_participant_js_1.ChatSessionParticipant], {
|
|
606
606
|
description: 'List participants of a ChatSession (members / owner).'
|
|
607
607
|
}),
|
|
608
|
-
(0, type_graphql_1.Directive)('@privilege(category: "ai-assistant", privilege: "query"
|
|
608
|
+
(0, type_graphql_1.Directive)('@privilege(category: "ai-assistant", privilege: "query")'),
|
|
609
609
|
tslib_1.__param(0, (0, type_graphql_1.Arg)('sessionId')),
|
|
610
610
|
tslib_1.__param(1, (0, type_graphql_1.Ctx)()),
|
|
611
611
|
tslib_1.__metadata("design:type", Function),
|
|
@@ -616,7 +616,7 @@ tslib_1.__decorate([
|
|
|
616
616
|
(0, type_graphql_1.Mutation)(() => Boolean, {
|
|
617
617
|
description: 'Presence heartbeat — bump current user\'s lastSeenAt in the session (online indicator).'
|
|
618
618
|
}),
|
|
619
|
-
(0, type_graphql_1.Directive)('@privilege(category: "ai-assistant", privilege: "mutation"
|
|
619
|
+
(0, type_graphql_1.Directive)('@privilege(category: "ai-assistant", privilege: "mutation")'),
|
|
620
620
|
tslib_1.__param(0, (0, type_graphql_1.Arg)('sessionId')),
|
|
621
621
|
tslib_1.__param(1, (0, type_graphql_1.Ctx)()),
|
|
622
622
|
tslib_1.__metadata("design:type", Function),
|
|
@@ -625,7 +625,7 @@ tslib_1.__decorate([
|
|
|
625
625
|
], ChatSessionResolver.prototype, "touchPresence", null);
|
|
626
626
|
tslib_1.__decorate([
|
|
627
627
|
(0, type_graphql_1.Query)(() => [chat_message_js_1.ChatMessage], { description: 'List chat messages of a session, oldest first.' }),
|
|
628
|
-
(0, type_graphql_1.Directive)('@privilege(category: "ai-assistant", privilege: "query"
|
|
628
|
+
(0, type_graphql_1.Directive)('@privilege(category: "ai-assistant", privilege: "query")'),
|
|
629
629
|
tslib_1.__param(0, (0, type_graphql_1.Arg)('sessionId')),
|
|
630
630
|
tslib_1.__param(1, (0, type_graphql_1.Arg)('limit', () => type_graphql_1.Int, { nullable: true, defaultValue: 100 })),
|
|
631
631
|
tslib_1.__param(2, (0, type_graphql_1.Arg)('offset', () => type_graphql_1.Int, { nullable: true, defaultValue: 0 })),
|
|
@@ -639,7 +639,7 @@ tslib_1.__decorate([
|
|
|
639
639
|
(0, type_graphql_1.Mutation)(() => chat_session_js_1.ChatSession, {
|
|
640
640
|
description: 'Start (or get existing) AI chat session anchored to a target. Idempotent — returns first existing or creates one. Use anchorType board for board authoring, space for a site (its co-located twins).'
|
|
641
641
|
}),
|
|
642
|
-
(0, type_graphql_1.Directive)('@privilege(category: "ai-assistant", privilege: "mutation"
|
|
642
|
+
(0, type_graphql_1.Directive)('@privilege(category: "ai-assistant", privilege: "mutation")'),
|
|
643
643
|
tslib_1.__param(0, (0, type_graphql_1.Arg)('anchorType')),
|
|
644
644
|
tslib_1.__param(1, (0, type_graphql_1.Arg)('anchorId')),
|
|
645
645
|
tslib_1.__param(2, (0, type_graphql_1.Ctx)()),
|
|
@@ -652,7 +652,7 @@ tslib_1.__decorate([
|
|
|
652
652
|
(0, type_graphql_1.Mutation)(() => chat_session_js_1.ChatSession, {
|
|
653
653
|
description: 'Always create a new AI chat session anchored to a target (no idempotent reuse). For multi-session UX — 새 탭 열기.'
|
|
654
654
|
}),
|
|
655
|
-
(0, type_graphql_1.Directive)('@privilege(category: "ai-assistant", privilege: "mutation"
|
|
655
|
+
(0, type_graphql_1.Directive)('@privilege(category: "ai-assistant", privilege: "mutation")'),
|
|
656
656
|
tslib_1.__param(0, (0, type_graphql_1.Arg)('anchorType')),
|
|
657
657
|
tslib_1.__param(1, (0, type_graphql_1.Arg)('anchorId')),
|
|
658
658
|
tslib_1.__param(2, (0, type_graphql_1.Arg)('name', { nullable: true, description: 'Optional name (defaults to auto-generated `세션 N`).' })),
|
|
@@ -666,7 +666,7 @@ tslib_1.__decorate([
|
|
|
666
666
|
(0, type_graphql_1.Mutation)(() => Boolean, {
|
|
667
667
|
description: 'Rename a ChatSession (tab label).'
|
|
668
668
|
}),
|
|
669
|
-
(0, type_graphql_1.Directive)('@privilege(category: "ai-assistant", privilege: "mutation"
|
|
669
|
+
(0, type_graphql_1.Directive)('@privilege(category: "ai-assistant", privilege: "mutation")'),
|
|
670
670
|
tslib_1.__param(0, (0, type_graphql_1.Arg)('sessionId')),
|
|
671
671
|
tslib_1.__param(1, (0, type_graphql_1.Arg)('name')),
|
|
672
672
|
tslib_1.__param(2, (0, type_graphql_1.Ctx)()),
|
|
@@ -679,7 +679,7 @@ tslib_1.__decorate([
|
|
|
679
679
|
(0, type_graphql_1.Mutation)(() => Boolean, {
|
|
680
680
|
description: 'Append a system note to a session — context that did not come from a participant typing (e.g. a conversation carried over from another surface, an external state change). The AI sees it as part of the shared history.'
|
|
681
681
|
}),
|
|
682
|
-
(0, type_graphql_1.Directive)('@privilege(category: "ai-assistant", privilege: "mutation"
|
|
682
|
+
(0, type_graphql_1.Directive)('@privilege(category: "ai-assistant", privilege: "mutation")'),
|
|
683
683
|
tslib_1.__param(0, (0, type_graphql_1.Arg)('sessionId')),
|
|
684
684
|
tslib_1.__param(1, (0, type_graphql_1.Arg)('content')),
|
|
685
685
|
tslib_1.__param(2, (0, type_graphql_1.Ctx)()),
|
|
@@ -692,7 +692,7 @@ tslib_1.__decorate([
|
|
|
692
692
|
(0, type_graphql_1.Mutation)(() => Boolean, {
|
|
693
693
|
description: 'Carry a conversation from another surface into this session as its own turns (user / assistant), preserving order. Use when a lightweight exchange is promoted into a persistent discussion — the participants should read it as the conversation continuing, not as a machine dump.'
|
|
694
694
|
}),
|
|
695
|
-
(0, type_graphql_1.Directive)('@privilege(category: "ai-assistant", privilege: "mutation"
|
|
695
|
+
(0, type_graphql_1.Directive)('@privilege(category: "ai-assistant", privilege: "mutation")'),
|
|
696
696
|
tslib_1.__param(0, (0, type_graphql_1.Arg)('sessionId')),
|
|
697
697
|
tslib_1.__param(1, (0, type_graphql_1.Arg)('turns', () => graphql_scalars_1.GraphQLJSON)),
|
|
698
698
|
tslib_1.__param(2, (0, type_graphql_1.Ctx)()),
|