@carlonicora/nestjs-neo4jsonapi 2.1.0 → 2.3.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/agents/graph/graph.module.d.ts.map +1 -1
- package/dist/agents/graph/graph.module.js +3 -0
- package/dist/agents/graph/graph.module.js.map +1 -1
- package/dist/agents/graph/interfaces/graph.catalog.interface.d.ts +24 -0
- package/dist/agents/graph/interfaces/graph.catalog.interface.d.ts.map +1 -1
- package/dist/agents/graph/services/graph.catalog.service.d.ts +9 -0
- package/dist/agents/graph/services/graph.catalog.service.d.ts.map +1 -1
- package/dist/agents/graph/services/graph.catalog.service.js +61 -1
- package/dist/agents/graph/services/graph.catalog.service.js.map +1 -1
- package/dist/agents/graph/services/graph.search.service.d.ts +16 -1
- package/dist/agents/graph/services/graph.search.service.d.ts.map +1 -1
- package/dist/agents/graph/services/graph.search.service.js +51 -4
- package/dist/agents/graph/services/graph.search.service.js.map +1 -1
- package/dist/agents/graph/services/humanize-tool.d.ts +17 -2
- package/dist/agents/graph/services/humanize-tool.d.ts.map +1 -1
- package/dist/agents/graph/services/humanize-tool.js +71 -6
- package/dist/agents/graph/services/humanize-tool.js.map +1 -1
- package/dist/agents/graph/services/materialise-bridge.d.ts +6 -0
- package/dist/agents/graph/services/materialise-bridge.d.ts.map +1 -1
- package/dist/agents/graph/services/materialise-bridge.js +17 -1
- package/dist/agents/graph/services/materialise-bridge.js.map +1 -1
- package/dist/agents/graph/services/scope.guard.d.ts +43 -0
- package/dist/agents/graph/services/scope.guard.d.ts.map +1 -0
- package/dist/agents/graph/services/scope.guard.js +91 -0
- package/dist/agents/graph/services/scope.guard.js.map +1 -0
- package/dist/agents/graph/tools/read-entity.tool.d.ts +9 -1
- package/dist/agents/graph/tools/read-entity.tool.d.ts.map +1 -1
- package/dist/agents/graph/tools/read-entity.tool.js +30 -5
- package/dist/agents/graph/tools/read-entity.tool.js.map +1 -1
- package/dist/agents/graph/tools/resolve-entity.tool.d.ts.map +1 -1
- package/dist/agents/graph/tools/resolve-entity.tool.js +8 -1
- package/dist/agents/graph/tools/resolve-entity.tool.js.map +1 -1
- package/dist/agents/graph/tools/search-entities.tool.d.ts +9 -1
- package/dist/agents/graph/tools/search-entities.tool.d.ts.map +1 -1
- package/dist/agents/graph/tools/search-entities.tool.js +37 -8
- package/dist/agents/graph/tools/search-entities.tool.js.map +1 -1
- package/dist/agents/graph/tools/tool.factory.d.ts +4 -0
- package/dist/agents/graph/tools/tool.factory.d.ts.map +1 -1
- package/dist/agents/graph/tools/tool.factory.js.map +1 -1
- package/dist/agents/graph/tools/traverse.tool.d.ts +9 -1
- package/dist/agents/graph/tools/traverse.tool.d.ts.map +1 -1
- package/dist/agents/graph/tools/traverse.tool.js +21 -5
- package/dist/agents/graph/tools/traverse.tool.js.map +1 -1
- package/dist/agents/operator/contexts/operator.context.d.ts +4 -0
- package/dist/agents/operator/contexts/operator.context.d.ts.map +1 -1
- package/dist/agents/operator/contexts/operator.context.js +4 -0
- package/dist/agents/operator/contexts/operator.context.js.map +1 -1
- package/dist/agents/operator/operator.module.d.ts.map +1 -1
- package/dist/agents/operator/operator.module.js +4 -0
- package/dist/agents/operator/operator.module.js.map +1 -1
- package/dist/agents/operator/services/operator.service.d.ts +15 -0
- package/dist/agents/operator/services/operator.service.d.ts.map +1 -1
- package/dist/agents/operator/services/operator.service.js +23 -5
- package/dist/agents/operator/services/operator.service.js.map +1 -1
- package/dist/agents/operator/tools/entity-write.tools.d.ts +124 -0
- package/dist/agents/operator/tools/entity-write.tools.d.ts.map +1 -0
- package/dist/agents/operator/tools/entity-write.tools.js +471 -0
- package/dist/agents/operator/tools/entity-write.tools.js.map +1 -0
- package/dist/agents/operator/tools/operator.tool.registry.d.ts +5 -1
- package/dist/agents/operator/tools/operator.tool.registry.d.ts.map +1 -1
- package/dist/agents/operator/tools/operator.tool.registry.js +19 -2
- package/dist/agents/operator/tools/operator.tool.registry.js.map +1 -1
- package/dist/agents/operator/tools/search-documents.tool.d.ts +16 -1
- package/dist/agents/operator/tools/search-documents.tool.d.ts.map +1 -1
- package/dist/agents/operator/tools/search-documents.tool.js +59 -3
- package/dist/agents/operator/tools/search-documents.tool.js.map +1 -1
- package/dist/agents/prompts/index.d.ts +1 -0
- package/dist/agents/prompts/index.d.ts.map +1 -1
- package/dist/agents/prompts/index.js +7 -1
- package/dist/agents/prompts/index.js.map +1 -1
- package/dist/agents/responder/contexts/responder.context.d.ts +19 -0
- package/dist/agents/responder/contexts/responder.context.d.ts.map +1 -1
- package/dist/agents/responder/contexts/responder.context.js +6 -0
- package/dist/agents/responder/contexts/responder.context.js.map +1 -1
- package/dist/agents/responder/factories/responder.context.factory.d.ts +4 -0
- package/dist/agents/responder/factories/responder.context.factory.d.ts.map +1 -1
- package/dist/agents/responder/factories/responder.context.factory.js +2 -0
- package/dist/agents/responder/factories/responder.context.factory.js.map +1 -1
- package/dist/agents/responder/nodes/graph.node.service.d.ts.map +1 -1
- package/dist/agents/responder/nodes/graph.node.service.js +18 -2
- package/dist/agents/responder/nodes/graph.node.service.js.map +1 -1
- package/dist/agents/responder/nodes/planner.node.service.d.ts +1 -1
- package/dist/agents/responder/nodes/planner.node.service.d.ts.map +1 -1
- package/dist/agents/responder/nodes/planner.node.service.js +5 -0
- package/dist/agents/responder/nodes/planner.node.service.js.map +1 -1
- package/dist/agents/responder/nodes/responder.answer.node.service.d.ts +33 -1
- package/dist/agents/responder/nodes/responder.answer.node.service.d.ts.map +1 -1
- package/dist/agents/responder/nodes/responder.answer.node.service.js +69 -27
- package/dist/agents/responder/nodes/responder.answer.node.service.js.map +1 -1
- package/dist/agents/responder/services/responder.service.d.ts +7 -0
- package/dist/agents/responder/services/responder.service.d.ts.map +1 -1
- package/dist/agents/responder/services/responder.service.js +3 -0
- package/dist/agents/responder/services/responder.service.js.map +1 -1
- package/dist/common/interfaces/entity.schema.interface.d.ts +16 -0
- package/dist/common/interfaces/entity.schema.interface.d.ts.map +1 -1
- package/dist/common/interfaces/index.d.ts +1 -0
- package/dist/common/interfaces/index.d.ts.map +1 -1
- package/dist/common/interfaces/index.js +1 -0
- package/dist/common/interfaces/index.js.map +1 -1
- package/dist/common/interfaces/seed.context.interface.d.ts +62 -0
- package/dist/common/interfaces/seed.context.interface.d.ts.map +1 -0
- package/dist/common/interfaces/seed.context.interface.js +20 -0
- package/dist/common/interfaces/seed.context.interface.js.map +1 -0
- package/dist/config/interfaces/config.prompts.interface.d.ts +12 -0
- package/dist/config/interfaces/config.prompts.interface.d.ts.map +1 -1
- package/dist/core/blocknote/services/blocknote.service.d.ts +23 -14
- package/dist/core/blocknote/services/blocknote.service.d.ts.map +1 -1
- package/dist/core/blocknote/services/blocknote.service.js +48 -32
- package/dist/core/blocknote/services/blocknote.service.js.map +1 -1
- package/dist/core/llm/services/llm.service.d.ts.map +1 -1
- package/dist/core/llm/services/llm.service.js +34 -2
- package/dist/core/llm/services/llm.service.js.map +1 -1
- package/dist/core/llm/services/tool-error-feedback.d.ts +40 -0
- package/dist/core/llm/services/tool-error-feedback.d.ts.map +1 -0
- package/dist/core/llm/services/tool-error-feedback.js +131 -0
- package/dist/core/llm/services/tool-error-feedback.js.map +1 -0
- package/dist/foundations/assistant/assistant.module.d.ts.map +1 -1
- package/dist/foundations/assistant/assistant.module.js +7 -1
- package/dist/foundations/assistant/assistant.module.js.map +1 -1
- package/dist/foundations/assistant/controllers/assistant.controller.d.ts +13 -1
- package/dist/foundations/assistant/controllers/assistant.controller.d.ts.map +1 -1
- package/dist/foundations/assistant/controllers/assistant.controller.js +37 -3
- package/dist/foundations/assistant/controllers/assistant.controller.js.map +1 -1
- package/dist/foundations/assistant/dtos/assistant-append.dto.d.ts +6 -0
- package/dist/foundations/assistant/dtos/assistant-append.dto.d.ts.map +1 -1
- package/dist/foundations/assistant/dtos/assistant-append.dto.js +1 -1
- package/dist/foundations/assistant/dtos/assistant-append.dto.js.map +1 -1
- package/dist/foundations/assistant/dtos/assistant-post.dto.d.ts +29 -0
- package/dist/foundations/assistant/dtos/assistant-post.dto.d.ts.map +1 -1
- package/dist/foundations/assistant/dtos/assistant-post.dto.js +47 -2
- package/dist/foundations/assistant/dtos/assistant-post.dto.js.map +1 -1
- package/dist/foundations/assistant/repositories/assistant.repository.d.ts +22 -0
- package/dist/foundations/assistant/repositories/assistant.repository.d.ts.map +1 -1
- package/dist/foundations/assistant/repositories/assistant.repository.js +28 -0
- package/dist/foundations/assistant/repositories/assistant.repository.js.map +1 -1
- package/dist/foundations/assistant/services/assistant-mention.extractor.d.ts +36 -0
- package/dist/foundations/assistant/services/assistant-mention.extractor.d.ts.map +1 -0
- package/dist/foundations/assistant/services/assistant-mention.extractor.js +99 -0
- package/dist/foundations/assistant/services/assistant-mention.extractor.js.map +1 -0
- package/dist/foundations/assistant/services/assistant.service.d.ts +93 -2
- package/dist/foundations/assistant/services/assistant.service.d.ts.map +1 -1
- package/dist/foundations/assistant/services/assistant.service.js +388 -60
- package/dist/foundations/assistant/services/assistant.service.js.map +1 -1
- package/package.json +1 -1
|
@@ -8,6 +8,9 @@ var __decorate = (this && this.__decorate) || function (decorators, target, key,
|
|
|
8
8
|
var __metadata = (this && this.__metadata) || function (k, v) {
|
|
9
9
|
if (typeof Reflect === "object" && typeof Reflect.metadata === "function") return Reflect.metadata(k, v);
|
|
10
10
|
};
|
|
11
|
+
var __param = (this && this.__param) || function (paramIndex, decorator) {
|
|
12
|
+
return function (target, key) { decorator(target, key, paramIndex); }
|
|
13
|
+
};
|
|
11
14
|
var AssistantService_1;
|
|
12
15
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
13
16
|
exports.AssistantService = exports.MAX_MESSAGES_TO_LLM = void 0;
|
|
@@ -19,9 +22,13 @@ const operator_checkpointer_service_1 = require("../../../agents/operator/servic
|
|
|
19
22
|
const operator_service_1 = require("../../../agents/operator/services/operator.service");
|
|
20
23
|
const responder_service_1 = require("../../../agents/responder/services/responder.service");
|
|
21
24
|
const graph_catalog_service_1 = require("../../../agents/graph/services/graph.catalog.service");
|
|
25
|
+
const scope_guard_1 = require("../../../agents/graph/services/scope.guard");
|
|
22
26
|
const user_modules_repository_1 = require("../../../agents/graph/repositories/user-modules.repository");
|
|
23
27
|
const agentmessage_type_1 = require("../../../common/enums/agentmessage.type");
|
|
28
|
+
const seed_context_interface_1 = require("../../../common/interfaces/seed.context.interface");
|
|
24
29
|
const entity_service_registry_1 = require("../../../common/registries/entity.service.registry");
|
|
30
|
+
const blocknote_service_1 = require("../../../core/blocknote/services/blocknote.service");
|
|
31
|
+
const jsonapi_paginator_1 = require("../../../core/jsonapi/serialisers/jsonapi.paginator");
|
|
25
32
|
const jsonapi_service_1 = require("../../../core/jsonapi/services/jsonapi.service");
|
|
26
33
|
const abstract_service_1 = require("../../../core/neo4j/abstracts/abstract.service");
|
|
27
34
|
const websocket_service_1 = require("../../../core/websocket/services/websocket.service");
|
|
@@ -33,7 +40,9 @@ const assistant_message_repository_1 = require("../../assistant-message/reposito
|
|
|
33
40
|
const assistant_message_service_1 = require("../../assistant-message/services/assistant-message.service");
|
|
34
41
|
const assistant_1 = require("../entities/assistant");
|
|
35
42
|
const assistant_meta_1 = require("../entities/assistant.meta");
|
|
43
|
+
const registry_1 = require("../../../common/registries/registry");
|
|
36
44
|
const assistant_repository_1 = require("../repositories/assistant.repository");
|
|
45
|
+
const assistant_mention_extractor_1 = require("./assistant-mention.extractor");
|
|
37
46
|
/**
|
|
38
47
|
* Maximum number of prior messages (turns) passed to the LLM on each turn.
|
|
39
48
|
* Keeps prompt size and cost bounded for long conversations.
|
|
@@ -55,7 +64,7 @@ exports.MAX_MESSAGES_TO_LLM = 20;
|
|
|
55
64
|
* AssistantMessageRepository.linkReferences).
|
|
56
65
|
*/
|
|
57
66
|
let AssistantService = AssistantService_1 = class AssistantService extends abstract_service_1.AbstractService {
|
|
58
|
-
constructor(jsonApiService, assistantRepository, clsService, userModuleIdsRepository, responder, assistantMessages, assistantMessageRepo, graphCatalog, entityServices, operator, assistantActions, assistantActionRepo, webSocketService, configService) {
|
|
67
|
+
constructor(jsonApiService, assistantRepository, clsService, userModuleIdsRepository, responder, assistantMessages, assistantMessageRepo, graphCatalog, entityServices, operator, assistantActions, assistantActionRepo, webSocketService, configService, mentions, blockNote, scopeGuard, seedContextProviders) {
|
|
59
68
|
super(jsonApiService, assistantRepository, clsService, assistant_1.AssistantDescriptor.model);
|
|
60
69
|
this.userModuleIdsRepository = userModuleIdsRepository;
|
|
61
70
|
this.responder = responder;
|
|
@@ -68,15 +77,31 @@ let AssistantService = AssistantService_1 = class AssistantService extends abstr
|
|
|
68
77
|
this.assistantActionRepo = assistantActionRepo;
|
|
69
78
|
this.webSocketService = webSocketService;
|
|
70
79
|
this.configService = configService;
|
|
80
|
+
this.mentions = mentions;
|
|
81
|
+
this.blockNote = blockNote;
|
|
82
|
+
this.scopeGuard = scopeGuard;
|
|
83
|
+
this.seedContextProviders = seedContextProviders;
|
|
71
84
|
this.descriptor = assistant_1.AssistantDescriptor;
|
|
72
85
|
this.assistantLogger = new common_1.Logger(AssistantService_1.name);
|
|
73
86
|
}
|
|
74
87
|
async createWithFirstMessage(params) {
|
|
75
88
|
const userModuleIds = await this.userModuleIdsRepository.findModuleIdsForUser(params.userId);
|
|
76
|
-
const
|
|
89
|
+
const ctx = this.buildTurnContext({
|
|
90
|
+
companyId: params.companyId,
|
|
91
|
+
userId: params.userId,
|
|
92
|
+
userModuleIds,
|
|
93
|
+
boundContent: params.boundContent,
|
|
94
|
+
});
|
|
95
|
+
const { content: firstMessage, plainText, pinned, } = await this.resolveUserTurnInput({
|
|
96
|
+
rawContent: params.firstMessage,
|
|
97
|
+
ctx,
|
|
98
|
+
});
|
|
99
|
+
const title = params.title?.trim() || this.autoTitle(plainText);
|
|
77
100
|
const assistantId = (0, crypto_1.randomUUID)();
|
|
78
101
|
const userMessageId = (0, crypto_1.randomUUID)();
|
|
79
102
|
// 1. Create the Assistant (owner edge attached from CLS via contextKey).
|
|
103
|
+
// A bound thread also gets the BOUND_TO edge to the resource it is
|
|
104
|
+
// confined to — the relationship is immutable, so it is set on create.
|
|
80
105
|
await this.createFromDTO({
|
|
81
106
|
data: {
|
|
82
107
|
type: assistant_meta_1.assistantMeta.type,
|
|
@@ -84,6 +109,7 @@ let AssistantService = AssistantService_1 = class AssistantService extends abstr
|
|
|
84
109
|
attributes: { title },
|
|
85
110
|
},
|
|
86
111
|
});
|
|
112
|
+
await this.attachBoundContent(assistantId, params.boundContent);
|
|
87
113
|
// 2. Create the first user message at position 0.
|
|
88
114
|
await this.assistantMessages.createFromDTO({
|
|
89
115
|
data: {
|
|
@@ -91,7 +117,7 @@ let AssistantService = AssistantService_1 = class AssistantService extends abstr
|
|
|
91
117
|
id: userMessageId,
|
|
92
118
|
attributes: {
|
|
93
119
|
role: "user",
|
|
94
|
-
content:
|
|
120
|
+
content: firstMessage,
|
|
95
121
|
position: 0,
|
|
96
122
|
},
|
|
97
123
|
relationships: {
|
|
@@ -99,15 +125,16 @@ let AssistantService = AssistantService_1 = class AssistantService extends abstr
|
|
|
99
125
|
},
|
|
100
126
|
},
|
|
101
127
|
});
|
|
128
|
+
await this.linkPinnedMentions({ messageId: userMessageId, pinned });
|
|
102
129
|
const userMessage = await this.assistantMessageRepo.findById({ id: userMessageId });
|
|
103
130
|
// 3. Run the agent turn using the just-created user message as context.
|
|
104
131
|
const turn = await this.runAgentTurn({
|
|
105
|
-
|
|
106
|
-
userId: params.userId,
|
|
107
|
-
userModuleIds,
|
|
132
|
+
ctx,
|
|
108
133
|
priorMessages: [],
|
|
109
|
-
newUserMessage: { role: "user", content:
|
|
134
|
+
newUserMessage: { role: "user", content: firstMessage },
|
|
135
|
+
pinned,
|
|
110
136
|
assistantId,
|
|
137
|
+
contentScope: this.toContentScope(params.boundContent),
|
|
111
138
|
howToMode: params.howToMode,
|
|
112
139
|
limitToHowToId: params.limitToHowToId,
|
|
113
140
|
});
|
|
@@ -156,6 +183,18 @@ let AssistantService = AssistantService_1 = class AssistantService extends abstr
|
|
|
156
183
|
// Verify ownership via the owner-RBAC-enforcing findById.
|
|
157
184
|
await this.repository.findById({ id: params.assistantId });
|
|
158
185
|
const userModuleIds = await this.userModuleIdsRepository.findModuleIdsForUser(params.userId);
|
|
186
|
+
// An append inherits the thread's binding — never a client-supplied one.
|
|
187
|
+
const contentScope = await this.resolveContentScope(params.assistantId);
|
|
188
|
+
const ctx = this.buildTurnContext({
|
|
189
|
+
companyId: params.companyId,
|
|
190
|
+
userId: params.userId,
|
|
191
|
+
userModuleIds,
|
|
192
|
+
boundContent: this.toBoundContent(contentScope),
|
|
193
|
+
});
|
|
194
|
+
const { content: newMessage, pinned } = await this.resolveUserTurnInput({
|
|
195
|
+
rawContent: params.newMessage,
|
|
196
|
+
ctx,
|
|
197
|
+
});
|
|
159
198
|
// Load prior messages for agent context.
|
|
160
199
|
const priorMessages = await this.loadRecentMessages({
|
|
161
200
|
assistantId: params.assistantId,
|
|
@@ -171,7 +210,7 @@ let AssistantService = AssistantService_1 = class AssistantService extends abstr
|
|
|
171
210
|
id: userMessageId,
|
|
172
211
|
attributes: {
|
|
173
212
|
role: "user",
|
|
174
|
-
content:
|
|
213
|
+
content: newMessage,
|
|
175
214
|
position: nextPosition,
|
|
176
215
|
},
|
|
177
216
|
relationships: {
|
|
@@ -179,14 +218,15 @@ let AssistantService = AssistantService_1 = class AssistantService extends abstr
|
|
|
179
218
|
},
|
|
180
219
|
},
|
|
181
220
|
});
|
|
221
|
+
await this.linkPinnedMentions({ messageId: userMessageId, pinned });
|
|
182
222
|
const userMessage = await this.assistantMessageRepo.findById({ id: userMessageId });
|
|
183
223
|
const turn = await this.runAgentTurn({
|
|
184
|
-
|
|
185
|
-
userId: params.userId,
|
|
186
|
-
userModuleIds,
|
|
224
|
+
ctx,
|
|
187
225
|
priorMessages,
|
|
188
|
-
newUserMessage: { role: "user", content:
|
|
226
|
+
newUserMessage: { role: "user", content: newMessage },
|
|
227
|
+
pinned,
|
|
189
228
|
assistantId: params.assistantId,
|
|
229
|
+
contentScope,
|
|
190
230
|
howToMode: params.howToMode,
|
|
191
231
|
limitToHowToId: params.limitToHowToId,
|
|
192
232
|
});
|
|
@@ -237,7 +277,17 @@ let AssistantService = AssistantService_1 = class AssistantService extends abstr
|
|
|
237
277
|
*/
|
|
238
278
|
async createWithFirstMessageOperator(params) {
|
|
239
279
|
const userModuleIds = await this.userModuleIdsRepository.findModuleIdsForUser(params.userId);
|
|
240
|
-
const
|
|
280
|
+
const ctx = this.buildTurnContext({
|
|
281
|
+
companyId: params.companyId,
|
|
282
|
+
userId: params.userId,
|
|
283
|
+
userModuleIds,
|
|
284
|
+
boundContent: params.boundContent,
|
|
285
|
+
});
|
|
286
|
+
const { content: firstMessage, plainText, pinned, } = await this.resolveUserTurnInput({
|
|
287
|
+
rawContent: params.firstMessage,
|
|
288
|
+
ctx,
|
|
289
|
+
});
|
|
290
|
+
const title = params.title?.trim() || this.autoTitle(plainText);
|
|
241
291
|
const assistantId = (0, crypto_1.randomUUID)();
|
|
242
292
|
const userMessageId = (0, crypto_1.randomUUID)();
|
|
243
293
|
// Persist the engine marker so clients can route follow-up turns to the
|
|
@@ -247,6 +297,13 @@ let AssistantService = AssistantService_1 = class AssistantService extends abstr
|
|
|
247
297
|
type: assistant_meta_1.assistantMeta.type,
|
|
248
298
|
id: assistantId,
|
|
249
299
|
attributes: { title, engine: "operator" },
|
|
300
|
+
...(params.boundContent
|
|
301
|
+
? {
|
|
302
|
+
relationships: {
|
|
303
|
+
content: { data: { type: params.boundContent.type, id: params.boundContent.id } },
|
|
304
|
+
},
|
|
305
|
+
}
|
|
306
|
+
: {}),
|
|
250
307
|
},
|
|
251
308
|
});
|
|
252
309
|
await this.assistantMessages.createFromDTO({
|
|
@@ -255,7 +312,7 @@ let AssistantService = AssistantService_1 = class AssistantService extends abstr
|
|
|
255
312
|
id: userMessageId,
|
|
256
313
|
attributes: {
|
|
257
314
|
role: "user",
|
|
258
|
-
content:
|
|
315
|
+
content: firstMessage,
|
|
259
316
|
position: 0,
|
|
260
317
|
},
|
|
261
318
|
relationships: {
|
|
@@ -263,18 +320,20 @@ let AssistantService = AssistantService_1 = class AssistantService extends abstr
|
|
|
263
320
|
},
|
|
264
321
|
},
|
|
265
322
|
});
|
|
323
|
+
await this.linkPinnedMentions({ messageId: userMessageId, pinned });
|
|
266
324
|
const userMessage = await this.assistantMessageRepo.findById({ id: userMessageId });
|
|
267
325
|
const threadId = `${assistantId}:${userMessageId}`;
|
|
268
|
-
const contentScope =
|
|
326
|
+
const contentScope = this.toContentScope(params.boundContent);
|
|
269
327
|
const result = await this.runOperatorTurn({
|
|
270
|
-
|
|
271
|
-
userId: params.userId,
|
|
272
|
-
userModuleIds,
|
|
328
|
+
ctx,
|
|
273
329
|
priorMessages: [],
|
|
274
|
-
question:
|
|
330
|
+
question: firstMessage,
|
|
331
|
+
pinned,
|
|
275
332
|
assistantId,
|
|
276
333
|
threadId,
|
|
277
334
|
contentScope,
|
|
335
|
+
howToMode: params.howToMode,
|
|
336
|
+
limitToHowToId: params.limitToHowToId,
|
|
278
337
|
});
|
|
279
338
|
const outcome = await this.persistOperatorOutcome({
|
|
280
339
|
assistantId,
|
|
@@ -296,6 +355,17 @@ let AssistantService = AssistantService_1 = class AssistantService extends abstr
|
|
|
296
355
|
// Verify ownership via the owner-RBAC-enforcing findById.
|
|
297
356
|
await this.repository.findById({ id: params.assistantId });
|
|
298
357
|
const userModuleIds = await this.userModuleIdsRepository.findModuleIdsForUser(params.userId);
|
|
358
|
+
const contentScope = await this.resolveContentScope(params.assistantId);
|
|
359
|
+
const ctx = this.buildTurnContext({
|
|
360
|
+
companyId: params.companyId,
|
|
361
|
+
userId: params.userId,
|
|
362
|
+
userModuleIds,
|
|
363
|
+
boundContent: this.toBoundContent(contentScope),
|
|
364
|
+
});
|
|
365
|
+
const { content: newMessage, pinned } = await this.resolveUserTurnInput({
|
|
366
|
+
rawContent: params.newMessage,
|
|
367
|
+
ctx,
|
|
368
|
+
});
|
|
299
369
|
const priorMessages = await this.loadRecentMessages({
|
|
300
370
|
assistantId: params.assistantId,
|
|
301
371
|
limit: exports.MAX_MESSAGES_TO_LLM,
|
|
@@ -310,7 +380,7 @@ let AssistantService = AssistantService_1 = class AssistantService extends abstr
|
|
|
310
380
|
id: userMessageId,
|
|
311
381
|
attributes: {
|
|
312
382
|
role: "user",
|
|
313
|
-
content:
|
|
383
|
+
content: newMessage,
|
|
314
384
|
position: nextPosition,
|
|
315
385
|
},
|
|
316
386
|
relationships: {
|
|
@@ -318,18 +388,19 @@ let AssistantService = AssistantService_1 = class AssistantService extends abstr
|
|
|
318
388
|
},
|
|
319
389
|
},
|
|
320
390
|
});
|
|
391
|
+
await this.linkPinnedMentions({ messageId: userMessageId, pinned });
|
|
321
392
|
const userMessage = await this.assistantMessageRepo.findById({ id: userMessageId });
|
|
322
393
|
const threadId = `${params.assistantId}:${userMessageId}`;
|
|
323
|
-
const contentScope = await this.resolveContentScope(params.assistantId);
|
|
324
394
|
const result = await this.runOperatorTurn({
|
|
325
|
-
|
|
326
|
-
userId: params.userId,
|
|
327
|
-
userModuleIds,
|
|
395
|
+
ctx,
|
|
328
396
|
priorMessages,
|
|
329
|
-
question:
|
|
397
|
+
question: newMessage,
|
|
398
|
+
pinned,
|
|
330
399
|
assistantId: params.assistantId,
|
|
331
400
|
threadId,
|
|
332
401
|
contentScope,
|
|
402
|
+
howToMode: params.howToMode,
|
|
403
|
+
limitToHowToId: params.limitToHowToId,
|
|
333
404
|
});
|
|
334
405
|
const outcome = await this.persistOperatorOutcome({
|
|
335
406
|
assistantId: params.assistantId,
|
|
@@ -387,6 +458,19 @@ let AssistantService = AssistantService_1 = class AssistantService extends abstr
|
|
|
387
458
|
type: this.roleToType(m.role),
|
|
388
459
|
content: m.content,
|
|
389
460
|
}));
|
|
461
|
+
// Rebuild the same turn context appendMessageOperator would build, so the
|
|
462
|
+
// resumed run gets the scope AND the seed contexts the original run had.
|
|
463
|
+
const resumeCtx = this.buildTurnContext({
|
|
464
|
+
companyId,
|
|
465
|
+
userId,
|
|
466
|
+
userModuleIds,
|
|
467
|
+
boundContent: this.toBoundContent(contentScope ?? {}),
|
|
468
|
+
});
|
|
469
|
+
const seedContexts = await this.collectSeedContexts({
|
|
470
|
+
ctx: resumeCtx,
|
|
471
|
+
contentScope: contentScope ?? {},
|
|
472
|
+
question: "",
|
|
473
|
+
});
|
|
390
474
|
let result;
|
|
391
475
|
try {
|
|
392
476
|
result = await this.operator.resume({
|
|
@@ -397,6 +481,9 @@ let AssistantService = AssistantService_1 = class AssistantService extends abstr
|
|
|
397
481
|
userModuleIds,
|
|
398
482
|
contentId: contentScope?.contentId,
|
|
399
483
|
contentType: contentScope?.contentType,
|
|
484
|
+
scopeId: resumeCtx.scopeId,
|
|
485
|
+
scopeType: resumeCtx.scopeType,
|
|
486
|
+
seedContexts,
|
|
400
487
|
messages,
|
|
401
488
|
});
|
|
402
489
|
}
|
|
@@ -447,9 +534,220 @@ let AssistantService = AssistantService_1 = class AssistantService extends abstr
|
|
|
447
534
|
// pending action supersedes the (now executed) original in the response.
|
|
448
535
|
return { assistantMessage: outcome.assistantMessage, action: outcome.action ?? updatedAction };
|
|
449
536
|
}
|
|
537
|
+
/**
|
|
538
|
+
* List the threads bound to one resource (e.g. every assistant thread for a
|
|
539
|
+
* campaign). Delegates to the inherited `findByRelated` over the `content`
|
|
540
|
+
* relationship — the BOUND_TO traversal already exists, so there is no new
|
|
541
|
+
* Cypher here and company/owner RBAC still comes from the repository.
|
|
542
|
+
*/
|
|
543
|
+
async findByBoundContent(params) {
|
|
544
|
+
const model = registry_1.modelRegistry.getByType(params.boundType);
|
|
545
|
+
if (!model) {
|
|
546
|
+
throw new common_1.BadRequestException(`Unknown resource type "${params.boundType}" for boundType.`);
|
|
547
|
+
}
|
|
548
|
+
const paginator = new jsonapi_paginator_1.JsonApiPaginator(params.query);
|
|
549
|
+
// `findByRelatedEdge`, not `findByRelated`: the latter derives the target
|
|
550
|
+
// label from `relationships.content.model`, which for this polymorphic edge
|
|
551
|
+
// is the `assistantMeta` placeholder, so it would look for
|
|
552
|
+
// `(:Assistant { id: <campaign-uuid> })` and always return nothing. This
|
|
553
|
+
// variant takes the edge spec explicitly, with the label resolved from the
|
|
554
|
+
// model registry.
|
|
555
|
+
const data = await this.repository.findByRelatedEdge({
|
|
556
|
+
cypherLabel: "BOUND_TO",
|
|
557
|
+
cypherDirection: "out",
|
|
558
|
+
relatedLabel: model.labelName,
|
|
559
|
+
relatedId: params.boundId,
|
|
560
|
+
cursor: paginator.generateCursor(),
|
|
561
|
+
});
|
|
562
|
+
return this.jsonApiService.buildList(assistant_1.AssistantDescriptor.model, data, paginator);
|
|
563
|
+
}
|
|
564
|
+
/**
|
|
565
|
+
* Build the run's UserContext once per turn. `scopeId`/`scopeType` are set
|
|
566
|
+
* only when the thread is bound to a catalogued scope ROOT (a descriptor
|
|
567
|
+
* whose compiled scope chain is empty, e.g. a campaign). A thread bound to
|
|
568
|
+
* something further down a chain still gets content scope, but not a
|
|
569
|
+
* transitive run scope — the guard has no root to confine the run to.
|
|
570
|
+
*/
|
|
571
|
+
buildTurnContext(params) {
|
|
572
|
+
const ctx = {
|
|
573
|
+
companyId: params.companyId,
|
|
574
|
+
userId: params.userId,
|
|
575
|
+
userModuleIds: params.userModuleIds,
|
|
576
|
+
};
|
|
577
|
+
const bound = params.boundContent;
|
|
578
|
+
if (!bound?.type || !bound?.id)
|
|
579
|
+
return ctx;
|
|
580
|
+
const scope = this.graphCatalog.getEntityDetail(bound.type, params.userModuleIds)?.scope;
|
|
581
|
+
if (scope && scope.path.length === 0 && scope.rootType === bound.type) {
|
|
582
|
+
ctx.scopeId = bound.id;
|
|
583
|
+
ctx.scopeType = bound.type;
|
|
584
|
+
}
|
|
585
|
+
else {
|
|
586
|
+
this.assistantLogger.warn(`buildTurnContext: bound type "${bound.type}" is not a catalogued scope root — the turn runs unscoped.`);
|
|
587
|
+
}
|
|
588
|
+
return ctx;
|
|
589
|
+
}
|
|
590
|
+
/**
|
|
591
|
+
* Collects seed-context blocks from the app-registered providers for one
|
|
592
|
+
* turn. Help-mode turns are never seeded. A provider returning null
|
|
593
|
+
* contributes nothing; a provider that throws is logged and skipped — the
|
|
594
|
+
* turn must run (unseeded) no matter what a provider does.
|
|
595
|
+
*/
|
|
596
|
+
async collectSeedContexts(params) {
|
|
597
|
+
if (!this.seedContextProviders?.length)
|
|
598
|
+
return [];
|
|
599
|
+
if (params.howToMode || params.limitToHowToId)
|
|
600
|
+
return [];
|
|
601
|
+
const collected = [];
|
|
602
|
+
for (const provider of this.seedContextProviders) {
|
|
603
|
+
try {
|
|
604
|
+
const seed = await provider.provide({
|
|
605
|
+
companyId: params.ctx.companyId,
|
|
606
|
+
userId: params.ctx.userId,
|
|
607
|
+
userModuleIds: params.ctx.userModuleIds,
|
|
608
|
+
scopeId: params.ctx.scopeId,
|
|
609
|
+
scopeType: params.ctx.scopeType,
|
|
610
|
+
contentId: params.contentScope.contentId,
|
|
611
|
+
contentType: params.contentScope.contentType,
|
|
612
|
+
question: params.question,
|
|
613
|
+
});
|
|
614
|
+
if (seed)
|
|
615
|
+
collected.push(seed);
|
|
616
|
+
}
|
|
617
|
+
catch (err) {
|
|
618
|
+
this.assistantLogger.warn(`seed context provider failed — skipped: ${err instanceof Error ? err.message : String(err)}`);
|
|
619
|
+
}
|
|
620
|
+
}
|
|
621
|
+
return collected;
|
|
622
|
+
}
|
|
623
|
+
/**
|
|
624
|
+
* Write the `BOUND_TO` edge that scopes a thread to a resource.
|
|
625
|
+
*
|
|
626
|
+
* Deliberately NOT part of the `createFromDTO` call above: `BOUND_TO` is
|
|
627
|
+
* polymorphic, so its descriptor entry carries `assistantMeta` as a
|
|
628
|
+
* placeholder model. The generic create path reads `rel.model.labelName` to
|
|
629
|
+
* validate the target and to build the edge, so a Campaign id sent that way
|
|
630
|
+
* is looked up as `(:Assistant { id: … })` and 400s with "One or more
|
|
631
|
+
* related nodes do not exist."
|
|
632
|
+
*
|
|
633
|
+
* The label is resolved from the model registry — the same registry the
|
|
634
|
+
* descriptor's own polymorphic discriminator uses on the read path — so an
|
|
635
|
+
* unregistered type fails loudly here rather than writing a dangling edge.
|
|
636
|
+
*/
|
|
637
|
+
async attachBoundContent(assistantId, boundContent) {
|
|
638
|
+
if (!boundContent)
|
|
639
|
+
return;
|
|
640
|
+
const model = registry_1.modelRegistry.getByType(boundContent.type);
|
|
641
|
+
if (!model) {
|
|
642
|
+
throw new common_1.BadRequestException(`Unknown resource type "${boundContent.type}" for the assistant's bound content.`);
|
|
643
|
+
}
|
|
644
|
+
await this.repository.bindContent({
|
|
645
|
+
assistantId,
|
|
646
|
+
targetLabel: model.labelName,
|
|
647
|
+
targetId: boundContent.id,
|
|
648
|
+
});
|
|
649
|
+
}
|
|
650
|
+
toContentScope(boundContent) {
|
|
651
|
+
return boundContent ? { contentId: boundContent.id, contentType: boundContent.type } : {};
|
|
652
|
+
}
|
|
653
|
+
toBoundContent(contentScope) {
|
|
654
|
+
return contentScope.contentId && contentScope.contentType
|
|
655
|
+
? { type: contentScope.contentType, id: contentScope.contentId }
|
|
656
|
+
: undefined;
|
|
657
|
+
}
|
|
658
|
+
/**
|
|
659
|
+
* A BlockNote-backed field arrives as a JSON string in its own string
|
|
660
|
+
* attribute — the convention every host-app entity already uses (e.g.
|
|
661
|
+
* `Npc.description`: `JSON.stringify` out, `JSON.parse` back). Detect that
|
|
662
|
+
* shape without trusting it: a plain-text message must never be mistaken for
|
|
663
|
+
* a document, so this requires an array whose first entry is an object with
|
|
664
|
+
* a string `type`, which is the minimal shape of a BlockNote block.
|
|
665
|
+
*/
|
|
666
|
+
parseBlockNoteDocument(raw) {
|
|
667
|
+
const trimmed = raw.trimStart();
|
|
668
|
+
if (!trimmed.startsWith("["))
|
|
669
|
+
return null;
|
|
670
|
+
try {
|
|
671
|
+
const parsed = JSON.parse(trimmed);
|
|
672
|
+
if (!Array.isArray(parsed) || parsed.length === 0)
|
|
673
|
+
return null;
|
|
674
|
+
const first = parsed[0];
|
|
675
|
+
if (!first || typeof first !== "object" || typeof first.type !== "string")
|
|
676
|
+
return null;
|
|
677
|
+
return parsed;
|
|
678
|
+
}
|
|
679
|
+
catch {
|
|
680
|
+
return null;
|
|
681
|
+
}
|
|
682
|
+
}
|
|
683
|
+
/**
|
|
684
|
+
* Derive from a user message: the markdown to persist (mention links kept, so
|
|
685
|
+
* the thread can re-render them), a plain-text form for the thread title, and
|
|
686
|
+
* the validated entity mentions to pin into the turn's focus context.
|
|
687
|
+
*
|
|
688
|
+
* Plain-text input passes straight through, so apps without a rich composer
|
|
689
|
+
* are unaffected.
|
|
690
|
+
*/
|
|
691
|
+
async resolveUserTurnInput(params) {
|
|
692
|
+
const nodes = this.parseBlockNoteDocument(params.rawContent);
|
|
693
|
+
if (!nodes)
|
|
694
|
+
return { content: params.rawContent, plainText: params.rawContent, pinned: [] };
|
|
695
|
+
const content = this.blockNote.convertToMarkdown({ nodes, preserveMentions: true });
|
|
696
|
+
// Alias-only rendering: what a human reads. Used for the thread title so it
|
|
697
|
+
// does not show raw `[Name](mention://npcs/<uuid>)` markup.
|
|
698
|
+
const plainText = this.blockNote.convertToMarkdown({ nodes });
|
|
699
|
+
const pinned = await this.mentions.validate({
|
|
700
|
+
mentions: this.mentions.extract(nodes),
|
|
701
|
+
ctx: params.ctx,
|
|
702
|
+
});
|
|
703
|
+
return { content, plainText, pinned };
|
|
704
|
+
}
|
|
705
|
+
/**
|
|
706
|
+
* Materialise REFERENCES edges from the USER message to the entities that
|
|
707
|
+
* message named. The reference metadata is fixed: a mention is an explicit,
|
|
708
|
+
* maximally relevant pointer, not a ranked retrieval hit.
|
|
709
|
+
*/
|
|
710
|
+
async linkPinnedMentions(params) {
|
|
711
|
+
if (!params.pinned.length)
|
|
712
|
+
return;
|
|
713
|
+
const references = params.pinned.map((mention) => ({
|
|
714
|
+
type: mention.type,
|
|
715
|
+
id: mention.id,
|
|
716
|
+
relevance: 100,
|
|
717
|
+
reason: "Named explicitly by the user in this message.",
|
|
718
|
+
}));
|
|
719
|
+
await this.assistantMessageRepo.linkReferences({ messageId: params.messageId, references });
|
|
720
|
+
}
|
|
721
|
+
/**
|
|
722
|
+
* Drop records that fall outside the run's scope root, grouping by JSON:API
|
|
723
|
+
* type so `ScopeGuard` can build one predicate per type. A no-op for an
|
|
724
|
+
* unscoped run — `ScopeGuard.filter` returns everything in that case, so the
|
|
725
|
+
* short-circuit only avoids a pointless round trip.
|
|
726
|
+
*/
|
|
727
|
+
async filterByScope(records, ctx) {
|
|
728
|
+
if (!ctx.scopeId || !ctx.scopeType || records.length === 0)
|
|
729
|
+
return records;
|
|
730
|
+
const byType = new Map();
|
|
731
|
+
for (const record of records) {
|
|
732
|
+
const type = typeof record.type === "string" ? record.type : undefined;
|
|
733
|
+
const id = typeof record.id === "string" ? record.id : undefined;
|
|
734
|
+
if (!type || !id)
|
|
735
|
+
continue;
|
|
736
|
+
const bucket = byType.get(type) ?? [];
|
|
737
|
+
bucket.push(record);
|
|
738
|
+
byType.set(type, bucket);
|
|
739
|
+
}
|
|
740
|
+
const kept = new Set();
|
|
741
|
+
for (const [type, bucket] of byType) {
|
|
742
|
+
const allowed = await this.scopeGuard.filter({ type, records: bucket, ctx });
|
|
743
|
+
for (const record of allowed)
|
|
744
|
+
kept.add(record);
|
|
745
|
+
}
|
|
746
|
+
return records.filter((record) => kept.has(record));
|
|
747
|
+
}
|
|
450
748
|
/**
|
|
451
749
|
* Resolve the content scope from the assistant's BOUND_TO relationship if
|
|
452
|
-
* loaded.
|
|
750
|
+
* loaded.
|
|
453
751
|
*/
|
|
454
752
|
async resolveContentScope(assistantId) {
|
|
455
753
|
try {
|
|
@@ -473,7 +771,7 @@ let AssistantService = AssistantService_1 = class AssistantService extends abstr
|
|
|
473
771
|
assistantId: params.assistantId,
|
|
474
772
|
turnStartedAt: now.toISOString().slice(0, 19).replace(/[T:]/g, "-"),
|
|
475
773
|
});
|
|
476
|
-
const hydrationContent = await this.buildHydrationMessage(params.priorMessages, params.
|
|
774
|
+
const hydrationContent = await this.buildHydrationMessage(params.priorMessages, params.ctx, params.pinned);
|
|
477
775
|
const trimmed = params.priorMessages.slice(-exports.MAX_MESSAGES_TO_LLM).map((m) => ({
|
|
478
776
|
type: this.roleToType(m.role),
|
|
479
777
|
content: m.content,
|
|
@@ -483,15 +781,25 @@ let AssistantService = AssistantService_1 = class AssistantService extends abstr
|
|
|
483
781
|
...trimmed,
|
|
484
782
|
{ type: agentmessage_type_1.AgentMessageType.User, content: params.question },
|
|
485
783
|
];
|
|
784
|
+
const seedContexts = await this.collectSeedContexts({
|
|
785
|
+
ctx: params.ctx,
|
|
786
|
+
contentScope: params.contentScope,
|
|
787
|
+
question: params.question,
|
|
788
|
+
howToMode: params.howToMode,
|
|
789
|
+
limitToHowToId: params.limitToHowToId,
|
|
790
|
+
});
|
|
486
791
|
return await this.operator.run({
|
|
487
|
-
companyId: params.companyId,
|
|
488
|
-
userId: params.userId,
|
|
489
|
-
userModuleIds: params.userModuleIds,
|
|
792
|
+
companyId: params.ctx.companyId,
|
|
793
|
+
userId: params.ctx.userId,
|
|
794
|
+
userModuleIds: params.ctx.userModuleIds,
|
|
490
795
|
contentId: params.contentScope.contentId,
|
|
491
796
|
contentType: params.contentScope.contentType,
|
|
797
|
+
scopeId: params.ctx.scopeId,
|
|
798
|
+
scopeType: params.ctx.scopeType,
|
|
492
799
|
messages,
|
|
493
800
|
question: params.question,
|
|
494
801
|
threadId: params.threadId,
|
|
802
|
+
seedContexts,
|
|
495
803
|
});
|
|
496
804
|
}
|
|
497
805
|
/**
|
|
@@ -604,7 +912,7 @@ let AssistantService = AssistantService_1 = class AssistantService extends abstr
|
|
|
604
912
|
turnStartedAt: now.toISOString().slice(0, 19).replace(/[T:]/g, "-"),
|
|
605
913
|
});
|
|
606
914
|
}
|
|
607
|
-
const hydrationContent = await this.buildHydrationMessage(params.priorMessages, params.
|
|
915
|
+
const hydrationContent = await this.buildHydrationMessage(params.priorMessages, params.ctx, params.pinned);
|
|
608
916
|
const trimmed = params.priorMessages.slice(-exports.MAX_MESSAGES_TO_LLM).map((m) => ({
|
|
609
917
|
type: this.roleToType(m.role),
|
|
610
918
|
content: m.content,
|
|
@@ -614,32 +922,28 @@ let AssistantService = AssistantService_1 = class AssistantService extends abstr
|
|
|
614
922
|
...trimmed,
|
|
615
923
|
{ type: agentmessage_type_1.AgentMessageType.User, content: params.newUserMessage.content },
|
|
616
924
|
];
|
|
617
|
-
|
|
618
|
-
|
|
619
|
-
|
|
620
|
-
|
|
621
|
-
|
|
622
|
-
|
|
623
|
-
|
|
624
|
-
contentId = c?.id;
|
|
625
|
-
contentType = c?.type;
|
|
626
|
-
}
|
|
627
|
-
catch {
|
|
628
|
-
// If load fails, proceed without content scope.
|
|
629
|
-
}
|
|
630
|
-
}
|
|
925
|
+
const seedContexts = await this.collectSeedContexts({
|
|
926
|
+
ctx: params.ctx,
|
|
927
|
+
contentScope: params.contentScope,
|
|
928
|
+
question: params.newUserMessage.content,
|
|
929
|
+
howToMode: params.howToMode,
|
|
930
|
+
limitToHowToId: params.limitToHowToId,
|
|
931
|
+
});
|
|
631
932
|
const response = await this.responder.run({
|
|
632
|
-
companyId: params.companyId,
|
|
633
|
-
userId: params.userId,
|
|
634
|
-
userModuleIds: params.userModuleIds,
|
|
635
|
-
contentId,
|
|
636
|
-
contentType,
|
|
933
|
+
companyId: params.ctx.companyId,
|
|
934
|
+
userId: params.ctx.userId,
|
|
935
|
+
userModuleIds: params.ctx.userModuleIds,
|
|
936
|
+
contentId: params.contentScope.contentId,
|
|
937
|
+
contentType: params.contentScope.contentType,
|
|
938
|
+
scopeId: params.ctx.scopeId,
|
|
939
|
+
scopeType: params.ctx.scopeType,
|
|
637
940
|
dataLimits: {
|
|
638
941
|
howToMode: params.howToMode,
|
|
639
942
|
limitToHowToId: params.limitToHowToId,
|
|
640
943
|
},
|
|
641
944
|
messages,
|
|
642
945
|
question: params.newUserMessage.content,
|
|
946
|
+
seedContexts,
|
|
643
947
|
});
|
|
644
948
|
return {
|
|
645
949
|
id: (0, crypto_1.randomUUID)(),
|
|
@@ -673,14 +977,17 @@ let AssistantService = AssistantService_1 = class AssistantService extends abstr
|
|
|
673
977
|
* the whole builder returns null so a single bad Neo4j hiccup does not
|
|
674
978
|
* fail the chat turn.
|
|
675
979
|
*/
|
|
676
|
-
async buildHydrationMessage(messages,
|
|
677
|
-
if (messages.length === 0)
|
|
980
|
+
async buildHydrationMessage(messages, ctx, pinned = []) {
|
|
981
|
+
if (messages.length === 0 && pinned.length === 0)
|
|
678
982
|
return null;
|
|
679
983
|
try {
|
|
680
|
-
const
|
|
681
|
-
|
|
682
|
-
|
|
683
|
-
|
|
984
|
+
const userModuleIds = ctx.userModuleIds;
|
|
985
|
+
const pairs = messages.length
|
|
986
|
+
? await this.assistantMessageRepo.findReferencedTypeIdPairs({
|
|
987
|
+
messageIds: messages.map((m) => m.id),
|
|
988
|
+
})
|
|
989
|
+
: [];
|
|
990
|
+
if (pairs.length === 0 && pinned.length === 0)
|
|
684
991
|
return null;
|
|
685
992
|
// The most recent assistant message is the focus anchor.
|
|
686
993
|
const prevAssistantId = [...messages].reverse().find((m) => m.role === "assistant")?.id;
|
|
@@ -690,6 +997,16 @@ let AssistantService = AssistantService_1 = class AssistantService extends abstr
|
|
|
690
997
|
const backgroundKeys = new Set();
|
|
691
998
|
const focusRefs = [];
|
|
692
999
|
const backgroundRefs = [];
|
|
1000
|
+
// Entities the user named explicitly lead the focus tier: they are the
|
|
1001
|
+
// subject of the new question, so they must survive FOCUS_CAP and must
|
|
1002
|
+
// never be demoted to a background stub by an earlier mention.
|
|
1003
|
+
for (const mention of pinned) {
|
|
1004
|
+
const key = `${mention.type}/${mention.id}`;
|
|
1005
|
+
if (focusKeys.has(key))
|
|
1006
|
+
continue;
|
|
1007
|
+
focusKeys.add(key);
|
|
1008
|
+
focusRefs.push({ type: mention.type, id: mention.id });
|
|
1009
|
+
}
|
|
693
1010
|
for (const p of pairs) {
|
|
694
1011
|
const key = `${p.type}/${p.id}`;
|
|
695
1012
|
if (prevAssistantId && p.messageId === prevAssistantId) {
|
|
@@ -716,9 +1033,12 @@ let AssistantService = AssistantService_1 = class AssistantService extends abstr
|
|
|
716
1033
|
if (backgroundOnly.length > BACKGROUND_CAP) {
|
|
717
1034
|
this.assistantLogger.warn(`buildHydrationMessage: background set capped at ${BACKGROUND_CAP} (had ${backgroundOnly.length})`);
|
|
718
1035
|
}
|
|
719
|
-
// Load focus records (full) and background records (project to label)
|
|
720
|
-
|
|
721
|
-
|
|
1036
|
+
// Load focus records (full) and background records (project to label),
|
|
1037
|
+
// then drop anything outside the run's scope. Hydration is a data-access
|
|
1038
|
+
// point like any tool: a record from another campaign must never reach
|
|
1039
|
+
// the LLM just because an earlier turn referenced it.
|
|
1040
|
+
const focusRecords = await this.filterByScope(await this.loadFocusRecords(focusCapped, userModuleIds), ctx);
|
|
1041
|
+
const backgroundStubs = await this.filterByScope(await this.loadBackgroundStubs(backgroundCapped, userModuleIds), ctx);
|
|
722
1042
|
// Diagnostic trace: in the dump it lets us see exactly which entities were
|
|
723
1043
|
// handed to the LLM as focus context (and whether any were bridges, since
|
|
724
1044
|
// bridge ids in focus are addressable directly without resolve_entity).
|
|
@@ -737,6 +1057,9 @@ let AssistantService = AssistantService_1 = class AssistantService extends abstr
|
|
|
737
1057
|
sections.push('These are the entities your previous answer was about. When the user\'s new question refers to any of them — by name or implicitly ("these", "them", "other orders", "their invoices") — use their id directly. Do not call resolve_entity for a name that matches one of these.');
|
|
738
1058
|
sections.push("");
|
|
739
1059
|
sections.push(JSON.stringify(focusRecords, null, 2));
|
|
1060
|
+
if (pinned.length > 0) {
|
|
1061
|
+
sections.push("The user named some of these entities explicitly in the new question. Treat every entity listed above as already identified: use its id directly and do not call resolve_entity for its name.");
|
|
1062
|
+
}
|
|
740
1063
|
sections.push("");
|
|
741
1064
|
}
|
|
742
1065
|
if (backgroundStubs.length > 0) {
|
|
@@ -862,6 +1185,8 @@ let AssistantService = AssistantService_1 = class AssistantService extends abstr
|
|
|
862
1185
|
exports.AssistantService = AssistantService;
|
|
863
1186
|
exports.AssistantService = AssistantService = AssistantService_1 = __decorate([
|
|
864
1187
|
(0, common_1.Injectable)(),
|
|
1188
|
+
__param(17, (0, common_1.Optional)()),
|
|
1189
|
+
__param(17, (0, common_1.Inject)(seed_context_interface_1.ASSISTANT_SEED_CONTEXT_PROVIDERS)),
|
|
865
1190
|
__metadata("design:paramtypes", [jsonapi_service_1.JsonApiService,
|
|
866
1191
|
assistant_repository_1.AssistantRepository,
|
|
867
1192
|
nestjs_cls_1.ClsService,
|
|
@@ -875,6 +1200,9 @@ exports.AssistantService = AssistantService = AssistantService_1 = __decorate([
|
|
|
875
1200
|
assistant_action_service_1.AssistantActionService,
|
|
876
1201
|
assistant_action_repository_1.AssistantActionRepository,
|
|
877
1202
|
websocket_service_1.WebSocketService,
|
|
878
|
-
config_1.ConfigService
|
|
1203
|
+
config_1.ConfigService,
|
|
1204
|
+
assistant_mention_extractor_1.AssistantMentionExtractor,
|
|
1205
|
+
blocknote_service_1.BlockNoteService,
|
|
1206
|
+
scope_guard_1.ScopeGuard, Array])
|
|
879
1207
|
], AssistantService);
|
|
880
1208
|
//# sourceMappingURL=assistant.service.js.map
|