crewx 0.8.8-rc.2 → 0.8.8-rc.21
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/bin/cli-commands.js +1 -1
- package/dist/assets/{MarketPage-Bh1UfiC2.js → MarketPage-B5JbqK4T.js} +9 -14
- package/dist/assets/{PromptTab-DYmYVBKo.js → PromptTab-_97mSbLk.js} +7 -7
- package/dist/assets/{arc-C14FbhzP.js → arc-3FDm7TxG.js} +1 -1
- package/dist/assets/architectureDiagram-UYN6MBPD-D8X56UlC.js +36 -0
- package/dist/assets/blockDiagram-ZHA2E4KO-B6jUnEVc.js +121 -0
- package/dist/assets/{c4Diagram-6F5ED5ID-DLSfQS-Z.js → c4Diagram-6F5ED5ID-Cy8x6Ims.js} +1 -1
- package/dist/assets/channel-Bs0J8jjN.js +1 -0
- package/dist/assets/{chunk-5HRBRIJM-j7YcJ7Bx.js → chunk-5HRBRIJM-Bh819Q5A.js} +1 -1
- package/dist/assets/{chunk-7U56Z5CX-Baqt1cj3.js → chunk-7U56Z5CX-CfNtPGa8.js} +1 -1
- package/dist/assets/{chunk-ASOPGD6M-F1YJ2mzH.js → chunk-ASOPGD6M-CMR1ElQ6.js} +1 -1
- package/dist/assets/{chunk-KFBOBJHC-Dvhzl6G9.js → chunk-KFBOBJHC-Dm2I1dHM.js} +1 -1
- package/dist/assets/{chunk-T2TOU4HS-BDczCXP6.js → chunk-T2TOU4HS-0tf-aNtk.js} +1 -1
- package/dist/assets/{chunk-TMUBEWPD-4BhwBH_F.js → chunk-TMUBEWPD-b5ZrpKJw.js} +1 -1
- package/dist/assets/classDiagram-LNE6IOMH-B1-6qC07.js +1 -0
- package/dist/assets/classDiagram-v2-MQ7JQ4JX-B1-6qC07.js +1 -0
- package/dist/assets/dagre-4EVJKHTY-zYmFBlwR.js +4 -0
- package/dist/assets/diagram-QW4FP2JN-DkZngH-Z.js +24 -0
- package/dist/assets/{erDiagram-6RL3IURR-DRhNKkH0.js → erDiagram-6RL3IURR-8TfuIQ6D.js} +2 -2
- package/dist/assets/{flowDiagram-7ASYPVHJ-BDJZsUJS.js → flowDiagram-7ASYPVHJ-BnhSLZTo.js} +1 -1
- package/dist/assets/{ganttDiagram-NTVNEXSI-BYBeyULV.js → ganttDiagram-NTVNEXSI--jDGPnYB.js} +3 -3
- package/dist/assets/gitGraph-YCYPL57B-DkCOt6bF.js +133 -0
- package/dist/assets/gitGraphDiagram-NRZ2UAAF-D4p17Lzw.js +65 -0
- package/dist/assets/graph-BlwPajkw.js +1 -0
- package/dist/assets/infoDiagram-A4XQUW5V-COnYi0Q8.js +2 -0
- package/dist/assets/{journeyDiagram-G5WM74LC-BhSSQ4Od.js → journeyDiagram-G5WM74LC-CTZcqDJf.js} +1 -1
- package/dist/assets/{kanban-definition-QRCXZQQD-CZOkitn-.js → kanban-definition-QRCXZQQD-CezLYsK3.js} +1 -1
- package/dist/assets/layout-Bvwu4dCi.js +1 -0
- package/dist/assets/{linear-BRR4YPhd.js → linear-BcYBjD0V.js} +1 -1
- package/dist/assets/main-CgT_Atre.js +1210 -0
- package/dist/assets/main-kJ_DZpZU.css +10 -0
- package/dist/assets/{mindmap-definition-GWI6TPTV-CiMg1rSL.js → mindmap-definition-GWI6TPTV-NM7qIPkp.js} +1 -1
- package/dist/assets/pieDiagram-YF2LJOPJ-Ck2UBMrZ.js +30 -0
- package/dist/assets/{quadrantDiagram-OS5C2QUG-cYMjsA0L.js → quadrantDiagram-OS5C2QUG-0OKnDbeW.js} +1 -1
- package/dist/assets/{requirementDiagram-MIRIMTAZ-D8piGWQV.js → requirementDiagram-MIRIMTAZ-Nq_mStSk.js} +2 -2
- package/dist/assets/{sankeyDiagram-Y46BX6SQ-DfNocUti.js → sankeyDiagram-Y46BX6SQ-ZPgHNk3C.js} +1 -1
- package/dist/assets/{sequenceDiagram-G6AWOVSC-Xu2X2iUX.js → sequenceDiagram-G6AWOVSC-BRzYMlx2.js} +1 -1
- package/dist/assets/stateDiagram-MAYHULR4-CCNAeCLE.js +1 -0
- package/dist/assets/stateDiagram-v2-4JROLMXI-Bz1kxCVf.js +1 -0
- package/dist/assets/{timeline-definition-U7ZMHBDA-CNra78s7.js → timeline-definition-U7ZMHBDA-BSw5vmnf.js} +1 -1
- package/dist/assets/{xychartDiagram-6QU3TZC5-BSwY5sKC.js → xychartDiagram-6QU3TZC5-BuqrahPf.js} +2 -2
- package/dist/index.html +2 -2
- package/dist-server/app.module.js +4 -0
- package/dist-server/bootstrap/crewx-server.js +6 -3
- package/dist-server/common/limits/defaults.js +2 -0
- package/dist-server/domain/auth/auth.module.js +2 -1
- package/dist-server/domain/auth/guards/base-auth.guard.js +36 -14
- package/dist-server/domain/doc/doc.controller.js +21 -0
- package/dist-server/domain/doc/doc.service.js +36 -6
- package/dist-server/domain/doc/image-constants.js +11 -0
- package/dist-server/domain/events/events.module.js +31 -0
- package/dist-server/domain/events/server-conversation.plugin.js +45 -0
- package/dist-server/domain/events/workspace-event.bus.js +32 -0
- package/dist-server/domain/events/workspace-event.types.js +12 -0
- package/dist-server/domain/events/workspace-events-test.controller.js +83 -0
- package/dist-server/domain/events/workspace-events.controller.js +47 -0
- package/dist-server/domain/mcp/browser-session.store.js +50 -0
- package/dist-server/domain/mcp/chromex-blacklist.js +26 -0
- package/dist-server/domain/mcp/chromex-negotiate.controller.js +77 -0
- package/dist-server/domain/mcp/chromex-negotiate.dto.js +95 -0
- package/dist-server/domain/mcp/chromex-negotiate.service.js +249 -0
- package/dist-server/domain/mcp/mcp-auth.guard.js +40 -26
- package/dist-server/domain/mcp/mcp.constants.js +4 -1
- package/dist-server/domain/mcp/mcp.controller.js +5 -4
- package/dist-server/domain/mcp/mcp.module.js +6 -2
- package/dist-server/domain/mcp/mcp.service.js +62 -5
- package/dist-server/domain/message/dto/send-message.dto.js +48 -1
- package/dist-server/domain/message/message.controller.js +40 -1
- package/dist-server/domain/message/message.service.js +50 -5
- package/dist-server/domain/task/task.service.js +34 -13
- package/dist-server/domain/thread/thread.controller.js +16 -0
- package/dist-server/domain/thread/thread.module.js +2 -0
- package/dist-server/domain/thread/thread.service.js +80 -11
- package/dist-server/domain/token/token.controller.js +73 -0
- package/dist-server/domain/token/token.dto.js +56 -0
- package/dist-server/domain/token/token.module.js +22 -0
- package/dist-server/domain/token/token.service.js +228 -0
- package/dist-server/main.js +23 -2
- package/dist-server/modules/crewx-pool.service.js +129 -5
- package/dist-server/modules/crewx.module.js +6 -0
- package/package.json +17 -14
- package/packages/cli/dist/builtin.js +2 -0
- package/packages/cli/dist/commands/init.js +5 -5
- package/packages/cli/dist/commands/registry.js +1 -1
- package/packages/cli/package.json +2 -1
- package/dist/assets/_baseUniq-B5kIqYhv.js +0 -1
- package/dist/assets/architectureDiagram-UYN6MBPD-Bvk4uHjQ.js +0 -36
- package/dist/assets/blockDiagram-ZHA2E4KO-C9-iioqe.js +0 -121
- package/dist/assets/channel-B1S1YSXQ.js +0 -1
- package/dist/assets/classDiagram-LNE6IOMH-BMiPdBl0.js +0 -1
- package/dist/assets/classDiagram-v2-MQ7JQ4JX-BMiPdBl0.js +0 -1
- package/dist/assets/clone-DkRtovKq.js +0 -1
- package/dist/assets/dagre-4EVJKHTY-LsgK-pqb.js +0 -4
- package/dist/assets/diagram-QW4FP2JN-uvhH5Eln.js +0 -24
- package/dist/assets/gitGraph-YCYPL57B-D9Hkzkze.js +0 -133
- package/dist/assets/gitGraphDiagram-NRZ2UAAF-oWiYlrjZ.js +0 -65
- package/dist/assets/graph-D66Q_If6.js +0 -1
- package/dist/assets/infoDiagram-A4XQUW5V-aJ6i9Snw.js +0 -2
- package/dist/assets/layout-CaxkerSC.js +0 -1
- package/dist/assets/main-3IYf82MG.css +0 -10
- package/dist/assets/main-DCHJFonK.js +0 -1167
- package/dist/assets/min-uKYaLZUT.js +0 -1
- package/dist/assets/pieDiagram-YF2LJOPJ-BFJ_DAm-.js +0 -30
- package/dist/assets/stateDiagram-MAYHULR4-WmJVTQsF.js +0 -1
- package/dist/assets/stateDiagram-v2-4JROLMXI-DNAOV6w0.js +0 -1
|
@@ -16,6 +16,8 @@ const mcp_dto_js_1 = require("./mcp.dto.js");
|
|
|
16
16
|
const crewx_tool_factory_js_1 = require("./crewx-tool.factory.js");
|
|
17
17
|
const browser_session_store_js_1 = require("./browser-session.store.js");
|
|
18
18
|
const tool_router_service_js_1 = require("./tool-router.service.js");
|
|
19
|
+
const chromex_negotiate_service_js_1 = require("./chromex-negotiate.service.js");
|
|
20
|
+
const chromex_blacklist_js_1 = require("./chromex-blacklist.js");
|
|
19
21
|
const crewx_pool_service_js_1 = require("../../modules/crewx-pool.service.js");
|
|
20
22
|
const project_repository_js_1 = require("../../repository/project.repository.js");
|
|
21
23
|
const repository_1 = require("@crewx/sdk/repository");
|
|
@@ -166,14 +168,16 @@ let McpService = McpService_1 = class McpService {
|
|
|
166
168
|
browserSessions;
|
|
167
169
|
toolRouter;
|
|
168
170
|
toolCallRepo;
|
|
171
|
+
negotiateService;
|
|
169
172
|
logger = new common_1.Logger(McpService_1.name);
|
|
170
173
|
activeTaskMap = new Map();
|
|
171
|
-
constructor(crewxPool, projectRepo, browserSessions, toolRouter, toolCallRepo) {
|
|
174
|
+
constructor(crewxPool, projectRepo, browserSessions, toolRouter, toolCallRepo, negotiateService) {
|
|
172
175
|
this.crewxPool = crewxPool;
|
|
173
176
|
this.projectRepo = projectRepo;
|
|
174
177
|
this.browserSessions = browserSessions;
|
|
175
178
|
this.toolRouter = toolRouter;
|
|
176
179
|
this.toolCallRepo = toolCallRepo;
|
|
180
|
+
this.negotiateService = negotiateService;
|
|
177
181
|
}
|
|
178
182
|
async getToolForCurrentRequest() {
|
|
179
183
|
const crewx = await this.crewxPool.getForCurrentRequest();
|
|
@@ -235,7 +239,7 @@ let McpService = McpService_1 = class McpService {
|
|
|
235
239
|
isNotification(body) {
|
|
236
240
|
return body.id === undefined || body.id === null;
|
|
237
241
|
}
|
|
238
|
-
async handleRequest(body, sessionId, clientType) {
|
|
242
|
+
async handleRequest(body, sessionId, clientType, request) {
|
|
239
243
|
const { method, id } = body;
|
|
240
244
|
this.logger.debug(`Handling MCP method: ${method}`);
|
|
241
245
|
if (this.isNotification(body)) {
|
|
@@ -255,7 +259,7 @@ let McpService = McpService_1 = class McpService {
|
|
|
255
259
|
case 'tools/list':
|
|
256
260
|
return this.handleToolsList(id, sessionId);
|
|
257
261
|
case 'tools/call':
|
|
258
|
-
return this.handleToolsCall(id, body.params, sessionId);
|
|
262
|
+
return this.handleToolsCall(id, body.params, sessionId, request);
|
|
259
263
|
case 'prompts/list':
|
|
260
264
|
return {
|
|
261
265
|
jsonrpc: '2.0',
|
|
@@ -294,6 +298,13 @@ let McpService = McpService_1 = class McpService {
|
|
|
294
298
|
getBrowserSessionStore() {
|
|
295
299
|
return this.browserSessions;
|
|
296
300
|
}
|
|
301
|
+
/** Push a JSON-RPC notification to the latest Chrome X browser SSE session. Returns false if no browser connected. */
|
|
302
|
+
pushToChromexSession(payload) {
|
|
303
|
+
const session = this.browserSessions.getLatestBrowserSession();
|
|
304
|
+
if (!session)
|
|
305
|
+
return false;
|
|
306
|
+
return this.browserSessions.pushNotification(session.id, payload);
|
|
307
|
+
}
|
|
297
308
|
handleInitialize(id, clientType) {
|
|
298
309
|
const sessionId = this.createBrowserSession(clientType);
|
|
299
310
|
return {
|
|
@@ -348,7 +359,7 @@ let McpService = McpService_1 = class McpService {
|
|
|
348
359
|
this.browserSessions.registerTools(sessionId, params.tools, params.mode);
|
|
349
360
|
return null;
|
|
350
361
|
}
|
|
351
|
-
async handleToolsCall(id, params, sessionId) {
|
|
362
|
+
async handleToolsCall(id, params, sessionId, request) {
|
|
352
363
|
if (!params?.name) {
|
|
353
364
|
return {
|
|
354
365
|
jsonrpc: '2.0',
|
|
@@ -364,7 +375,52 @@ let McpService = McpService_1 = class McpService {
|
|
|
364
375
|
// Browser tool: route via SSE to the extension (dual routing: prefix + session lookup)
|
|
365
376
|
if (this.toolRouter.isBrowserTool(params.name, sessionId)) {
|
|
366
377
|
this.logger.log(`tools/call [browser]: ${params.name}`);
|
|
378
|
+
const claims = request?.tokenClaims;
|
|
379
|
+
// Browser control is only available through a negotiate token. The token's
|
|
380
|
+
// agentScope is the authority; do not trust a client-supplied agentId.
|
|
381
|
+
if (claims?.source !== 'negotiate' || !claims.agentScope) {
|
|
382
|
+
return {
|
|
383
|
+
jsonrpc: '2.0',
|
|
384
|
+
id: id ?? null,
|
|
385
|
+
error: { code: -32001, message: 'Negotiate token required for browser tools' },
|
|
386
|
+
};
|
|
387
|
+
}
|
|
388
|
+
const requestedTabId = params.arguments?.['tabId'];
|
|
389
|
+
if (claims.browserScope &&
|
|
390
|
+
claims.browserScope !== 'all_tabs' &&
|
|
391
|
+
claims.browserScope.startsWith('tab:') &&
|
|
392
|
+
String(requestedTabId) !== claims.browserScope.slice(4)) {
|
|
393
|
+
return {
|
|
394
|
+
jsonrpc: '2.0',
|
|
395
|
+
id: id ?? null,
|
|
396
|
+
error: { code: -32001, message: 'Browser scope denied' },
|
|
397
|
+
};
|
|
398
|
+
}
|
|
399
|
+
const nestedArgs = params.arguments?.['args'];
|
|
400
|
+
const nestedUrl = nestedArgs && typeof nestedArgs === 'object' && !Array.isArray(nestedArgs)
|
|
401
|
+
? nestedArgs['url']
|
|
402
|
+
: undefined;
|
|
403
|
+
const tabUrl = String(params.arguments?.['tabUrl'] ?? params.arguments?.['url'] ?? nestedUrl ?? '');
|
|
404
|
+
if (tabUrl && (0, chromex_blacklist_js_1.isBlacklisted)(tabUrl)) {
|
|
405
|
+
this.negotiateService.logAudit({
|
|
406
|
+
agentId: claims?.agentScope ?? 'unknown',
|
|
407
|
+
action: 'tool_call_blocked',
|
|
408
|
+
jti: claims?.jti,
|
|
409
|
+
tabUrl,
|
|
410
|
+
toolName: params.name,
|
|
411
|
+
result: 'blocked',
|
|
412
|
+
});
|
|
413
|
+
return { jsonrpc: '2.0', id: id ?? null, error: { code: -32001, message: 'URL is blacklisted' } };
|
|
414
|
+
}
|
|
367
415
|
response = await this.toolRouter.dispatch(sessionId, params.name, params.arguments ?? {}, id);
|
|
416
|
+
this.negotiateService.logAudit({
|
|
417
|
+
agentId: claims?.agentScope ?? 'unknown',
|
|
418
|
+
action: 'tool_call',
|
|
419
|
+
jti: claims?.jti,
|
|
420
|
+
tabUrl: tabUrl || null,
|
|
421
|
+
toolName: params.name,
|
|
422
|
+
result: 'success',
|
|
423
|
+
});
|
|
368
424
|
}
|
|
369
425
|
else {
|
|
370
426
|
// Server tool: must exist in TOOL_DEFINITIONS
|
|
@@ -671,7 +727,8 @@ exports.McpService = McpService = McpService_1 = __decorate([
|
|
|
671
727
|
project_repository_js_1.ProjectRepository,
|
|
672
728
|
browser_session_store_js_1.BrowserSessionStore,
|
|
673
729
|
tool_router_service_js_1.ToolRouterService,
|
|
674
|
-
repository_1.ToolCallRepository
|
|
730
|
+
repository_1.ToolCallRepository,
|
|
731
|
+
chromex_negotiate_service_js_1.ChromexNegotiateService])
|
|
675
732
|
], McpService);
|
|
676
733
|
function normalizeContent(result) {
|
|
677
734
|
if (Array.isArray(result.content) && result.content.length > 0) {
|
|
@@ -9,14 +9,42 @@ 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
11
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
12
|
-
exports.SendMessageDto = void 0;
|
|
12
|
+
exports.SendMessageDto = exports.ImageAttachmentDto = void 0;
|
|
13
13
|
const swagger_1 = require("@nestjs/swagger");
|
|
14
14
|
const class_validator_1 = require("class-validator");
|
|
15
|
+
const class_transformer_1 = require("class-transformer");
|
|
16
|
+
class ImageAttachmentDto {
|
|
17
|
+
data;
|
|
18
|
+
mimeType;
|
|
19
|
+
filename;
|
|
20
|
+
}
|
|
21
|
+
exports.ImageAttachmentDto = ImageAttachmentDto;
|
|
22
|
+
__decorate([
|
|
23
|
+
(0, class_validator_1.IsString)(),
|
|
24
|
+
(0, class_validator_1.IsNotEmpty)(),
|
|
25
|
+
(0, swagger_1.ApiProperty)({ description: 'Base64-encoded image data' }),
|
|
26
|
+
__metadata("design:type", String)
|
|
27
|
+
], ImageAttachmentDto.prototype, "data", void 0);
|
|
28
|
+
__decorate([
|
|
29
|
+
(0, class_validator_1.IsString)(),
|
|
30
|
+
(0, class_validator_1.IsNotEmpty)(),
|
|
31
|
+
(0, swagger_1.ApiProperty)({ description: 'MIME type (image/png, image/jpeg, image/gif, image/webp)' }),
|
|
32
|
+
__metadata("design:type", String)
|
|
33
|
+
], ImageAttachmentDto.prototype, "mimeType", void 0);
|
|
34
|
+
__decorate([
|
|
35
|
+
(0, class_validator_1.IsString)(),
|
|
36
|
+
(0, class_validator_1.IsOptional)(),
|
|
37
|
+
(0, swagger_1.ApiPropertyOptional)({ description: 'Original filename' }),
|
|
38
|
+
__metadata("design:type", String)
|
|
39
|
+
], ImageAttachmentDto.prototype, "filename", void 0);
|
|
15
40
|
class SendMessageDto {
|
|
16
41
|
message;
|
|
17
42
|
agent;
|
|
18
43
|
title;
|
|
19
44
|
mode = 'query';
|
|
45
|
+
images;
|
|
46
|
+
context;
|
|
47
|
+
browserSessionId;
|
|
20
48
|
}
|
|
21
49
|
exports.SendMessageDto = SendMessageDto;
|
|
22
50
|
__decorate([
|
|
@@ -43,3 +71,22 @@ __decorate([
|
|
|
43
71
|
(0, swagger_1.ApiPropertyOptional)({ description: 'Execution mode: query (read-only, default) or execute (full permissions)', default: 'query' }),
|
|
44
72
|
__metadata("design:type", String)
|
|
45
73
|
], SendMessageDto.prototype, "mode", void 0);
|
|
74
|
+
__decorate([
|
|
75
|
+
(0, class_validator_1.IsOptional)(),
|
|
76
|
+
(0, class_validator_1.ValidateNested)({ each: true }),
|
|
77
|
+
(0, class_transformer_1.Type)(() => ImageAttachmentDto),
|
|
78
|
+
(0, swagger_1.ApiPropertyOptional)({ description: 'Attached images (base64)', type: [ImageAttachmentDto] }),
|
|
79
|
+
__metadata("design:type", Array)
|
|
80
|
+
], SendMessageDto.prototype, "images", void 0);
|
|
81
|
+
__decorate([
|
|
82
|
+
(0, class_validator_1.IsString)(),
|
|
83
|
+
(0, class_validator_1.IsOptional)(),
|
|
84
|
+
(0, swagger_1.ApiPropertyOptional)({ description: 'Provider-agnostic context prepended to the message' }),
|
|
85
|
+
__metadata("design:type", String)
|
|
86
|
+
], SendMessageDto.prototype, "context", void 0);
|
|
87
|
+
__decorate([
|
|
88
|
+
(0, class_validator_1.IsString)(),
|
|
89
|
+
(0, class_validator_1.IsOptional)(),
|
|
90
|
+
(0, swagger_1.ApiPropertyOptional)({ description: 'Chrome X browser MCP session id for reverse browser-tool calls' }),
|
|
91
|
+
__metadata("design:type", String)
|
|
92
|
+
], SendMessageDto.prototype, "browserSessionId", void 0);
|
|
@@ -15,11 +15,21 @@ Object.defineProperty(exports, "__esModule", { value: true });
|
|
|
15
15
|
exports.MessageController = void 0;
|
|
16
16
|
const common_1 = require("@nestjs/common");
|
|
17
17
|
const swagger_1 = require("@nestjs/swagger");
|
|
18
|
+
const path_1 = require("path");
|
|
19
|
+
const fs_1 = require("fs");
|
|
18
20
|
const message_service_js_1 = require("./message.service.js");
|
|
19
21
|
const box_service_js_1 = require("../box/box.service.js");
|
|
20
22
|
const send_message_dto_js_1 = require("./dto/send-message.dto.js");
|
|
21
23
|
const list_messages_dto_js_1 = require("./dto/list-messages.dto.js");
|
|
22
24
|
const workspace_decorator_js_1 = require("../../common/decorators/workspace.decorator.js");
|
|
25
|
+
const workspace_context_store_js_1 = require("../../common/workspace-context.store.js");
|
|
26
|
+
const EXT_MIME = {
|
|
27
|
+
png: 'image/png',
|
|
28
|
+
jpg: 'image/jpeg',
|
|
29
|
+
jpeg: 'image/jpeg',
|
|
30
|
+
gif: 'image/gif',
|
|
31
|
+
webp: 'image/webp',
|
|
32
|
+
};
|
|
23
33
|
let MessageController = class MessageController {
|
|
24
34
|
messageService;
|
|
25
35
|
boxService;
|
|
@@ -39,9 +49,28 @@ let MessageController = class MessageController {
|
|
|
39
49
|
}
|
|
40
50
|
// MSG.SEND: Send message to thread (spawns crewx CLI in background)
|
|
41
51
|
async sendMessage(threadId, body, ws) {
|
|
42
|
-
const data = await this.messageService.sendMessage(threadId, body.message, body.agent, body.mode, body.title, ws.id);
|
|
52
|
+
const data = await this.messageService.sendMessage(threadId, body.message, body.agent, body.mode, body.title, ws.id, body.images, body.context, body.browserSessionId);
|
|
43
53
|
return { success: true, data };
|
|
44
54
|
}
|
|
55
|
+
serveAttachment(threadId, filename, res) {
|
|
56
|
+
if (filename.includes('/') || filename.includes('..')) {
|
|
57
|
+
throw new common_1.BadRequestException('Invalid filename');
|
|
58
|
+
}
|
|
59
|
+
const wsPath = (0, workspace_context_store_js_1.getWorkspacePath)();
|
|
60
|
+
const filePath = (0, path_1.join)(wsPath, '.crewx', 'attachments', threadId, filename);
|
|
61
|
+
if (!(0, fs_1.existsSync)(filePath)) {
|
|
62
|
+
throw new common_1.NotFoundException('Attachment not found');
|
|
63
|
+
}
|
|
64
|
+
const ext = filename.split('.').pop()?.toLowerCase() ?? '';
|
|
65
|
+
const mime = EXT_MIME[ext] ?? 'application/octet-stream';
|
|
66
|
+
const stat = (0, fs_1.statSync)(filePath);
|
|
67
|
+
res.set({
|
|
68
|
+
'Content-Type': mime,
|
|
69
|
+
'Content-Length': stat.size,
|
|
70
|
+
'Cache-Control': 'public, max-age=31536000, immutable',
|
|
71
|
+
});
|
|
72
|
+
(0, fs_1.createReadStream)(filePath).pipe(res);
|
|
73
|
+
}
|
|
45
74
|
};
|
|
46
75
|
exports.MessageController = MessageController;
|
|
47
76
|
__decorate([
|
|
@@ -65,6 +94,16 @@ __decorate([
|
|
|
65
94
|
__metadata("design:paramtypes", [String, send_message_dto_js_1.SendMessageDto, Object]),
|
|
66
95
|
__metadata("design:returntype", Promise)
|
|
67
96
|
], MessageController.prototype, "sendMessage", null);
|
|
97
|
+
__decorate([
|
|
98
|
+
(0, swagger_1.ApiOperation)({ operationId: 'MSG.ATTACHMENT', summary: 'Serve thread attachment image' }),
|
|
99
|
+
(0, common_1.Get)('attachments/:filename'),
|
|
100
|
+
__param(0, (0, common_1.Param)('threadId')),
|
|
101
|
+
__param(1, (0, common_1.Param)('filename')),
|
|
102
|
+
__param(2, (0, common_1.Res)()),
|
|
103
|
+
__metadata("design:type", Function),
|
|
104
|
+
__metadata("design:paramtypes", [String, String, Object]),
|
|
105
|
+
__metadata("design:returntype", void 0)
|
|
106
|
+
], MessageController.prototype, "serveAttachment", null);
|
|
68
107
|
exports.MessageController = MessageController = __decorate([
|
|
69
108
|
(0, swagger_1.ApiTags)('messages'),
|
|
70
109
|
(0, common_1.Controller)('ws/:slug/threads/:threadId/messages'),
|
|
@@ -12,8 +12,12 @@ var MessageService_1;
|
|
|
12
12
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
13
13
|
exports.MessageService = void 0;
|
|
14
14
|
const common_1 = require("@nestjs/common");
|
|
15
|
+
const crypto_1 = require("crypto");
|
|
16
|
+
const fs_1 = require("fs");
|
|
17
|
+
const path_1 = require("path");
|
|
15
18
|
const thread_service_js_1 = require("../thread/thread.service.js");
|
|
16
19
|
const thread_repository_js_1 = require("../../repository/thread.repository.js");
|
|
20
|
+
const sdk_1 = require("@crewx/sdk");
|
|
17
21
|
const crewx_pool_service_js_1 = require("../../modules/crewx-pool.service.js");
|
|
18
22
|
const workspace_context_store_js_1 = require("../../common/workspace-context.store.js");
|
|
19
23
|
let MessageService = MessageService_1 = class MessageService {
|
|
@@ -56,6 +60,7 @@ let MessageService = MessageService_1 = class MessageService {
|
|
|
56
60
|
prompt: t.prompt
|
|
57
61
|
|| (ctx.hot[i - 1]?.role === 'user' ? ctx.hot[i - 1].content : ''),
|
|
58
62
|
content: msg.content,
|
|
63
|
+
error: t.error ?? undefined,
|
|
59
64
|
startedAt: t.startedAt,
|
|
60
65
|
completedAt: t.completedAt ?? undefined,
|
|
61
66
|
durationMs: t.durationMs ?? undefined,
|
|
@@ -97,7 +102,7 @@ let MessageService = MessageService_1 = class MessageService {
|
|
|
97
102
|
};
|
|
98
103
|
}
|
|
99
104
|
// MSG.SEND: Send message — fire crewx SDK; ConversationPlugin persists threads row
|
|
100
|
-
async sendMessage(threadId, message, agent, mode = 'query', title, workspaceId) {
|
|
105
|
+
async sendMessage(threadId, message, agent, mode = 'query', title, workspaceId, images, context, browserSessionId) {
|
|
101
106
|
this.validateId(threadId);
|
|
102
107
|
if (!message || !message.trim()) {
|
|
103
108
|
throw new common_1.BadRequestException('message is required');
|
|
@@ -115,10 +120,28 @@ let MessageService = MessageService_1 = class MessageService {
|
|
|
115
120
|
this.logger.warn(`updateThreadTitle skipped [thread=${threadId}]: ${err.message}`);
|
|
116
121
|
}
|
|
117
122
|
}
|
|
123
|
+
const savedImages = this.saveImages(threadId, images);
|
|
124
|
+
let finalMessage = message;
|
|
125
|
+
if (savedImages.length > 0) {
|
|
126
|
+
const tags = savedImages
|
|
127
|
+
.map((img) => `<crewx_image path="${img.relativePath}" mime="${img.mimeType}" />`)
|
|
128
|
+
.join('\n');
|
|
129
|
+
finalMessage = `${message}\n${tags}`;
|
|
130
|
+
}
|
|
131
|
+
const trimmedContext = context?.trim();
|
|
132
|
+
if (trimmedContext) {
|
|
133
|
+
finalMessage = `<chrome_x_context>\n${trimmedContext}\n</chrome_x_context>\n\n${finalMessage}`;
|
|
134
|
+
}
|
|
135
|
+
const taskId = (0, sdk_1.generateId)('tsk');
|
|
136
|
+
const metadata = { thread: threadId, parentTaskId: null };
|
|
137
|
+
if (browserSessionId?.trim()) {
|
|
138
|
+
metadata.browserSessionId = browserSessionId.trim();
|
|
139
|
+
}
|
|
118
140
|
const sdkOptions = {
|
|
119
141
|
threadId,
|
|
142
|
+
taskId,
|
|
120
143
|
platform: 'cli',
|
|
121
|
-
metadata
|
|
144
|
+
metadata,
|
|
122
145
|
cwd: (0, workspace_context_store_js_1.getWorkspacePath)(),
|
|
123
146
|
};
|
|
124
147
|
const crewx = await this.crewxPool.getForCurrentRequest();
|
|
@@ -133,14 +156,36 @@ let MessageService = MessageService_1 = class MessageService {
|
|
|
133
156
|
}
|
|
134
157
|
const agentRef = `@${agent}`;
|
|
135
158
|
const sdkCall = mode === 'execute'
|
|
136
|
-
? crewx.execute(agentRef,
|
|
137
|
-
: crewx.query(agentRef,
|
|
159
|
+
? crewx.execute(agentRef, finalMessage, sdkOptions)
|
|
160
|
+
: crewx.query(agentRef, finalMessage, sdkOptions);
|
|
138
161
|
sdkCall.catch((err) => {
|
|
139
162
|
this.logger.error(`Background ${mode} failed [thread=${threadId}, agent=${agent}]: ${err.message}`, err.stack);
|
|
140
163
|
});
|
|
141
|
-
return { taskId
|
|
164
|
+
return { taskId, status: 'pending', agent };
|
|
142
165
|
}
|
|
143
166
|
// --- Private helpers ---
|
|
167
|
+
saveImages(threadId, images) {
|
|
168
|
+
if (!images?.length)
|
|
169
|
+
return [];
|
|
170
|
+
const MIME_EXT = {
|
|
171
|
+
'image/png': 'png',
|
|
172
|
+
'image/jpeg': 'jpg',
|
|
173
|
+
'image/gif': 'gif',
|
|
174
|
+
'image/webp': 'webp',
|
|
175
|
+
};
|
|
176
|
+
const wsPath = (0, workspace_context_store_js_1.getWorkspacePath)();
|
|
177
|
+
const dir = (0, path_1.join)(wsPath, '.crewx', 'attachments', threadId);
|
|
178
|
+
(0, fs_1.mkdirSync)(dir, { recursive: true });
|
|
179
|
+
return images.map((img) => {
|
|
180
|
+
const ext = MIME_EXT[img.mimeType] ?? 'bin';
|
|
181
|
+
const filename = `${(0, crypto_1.randomUUID)()}.${ext}`;
|
|
182
|
+
const filePath = (0, path_1.join)(dir, filename);
|
|
183
|
+
const buf = Buffer.from(img.data, 'base64');
|
|
184
|
+
(0, fs_1.writeFileSync)(filePath, new Uint8Array(buf.buffer, buf.byteOffset, buf.byteLength));
|
|
185
|
+
const relativePath = `.crewx/attachments/${threadId}/${filename}`;
|
|
186
|
+
return { relativePath, mimeType: img.mimeType };
|
|
187
|
+
});
|
|
188
|
+
}
|
|
144
189
|
validateId(id) {
|
|
145
190
|
if (!/^[\w\p{L}\p{N}._-]+$/u.test(id)) {
|
|
146
191
|
throw new common_1.BadRequestException('Invalid ID format');
|
|
@@ -142,18 +142,30 @@ let TaskService = TaskService_1 = class TaskService {
|
|
|
142
142
|
if (err instanceof common_1.HttpException)
|
|
143
143
|
throw err;
|
|
144
144
|
}
|
|
145
|
-
// Send SIGTERM to the process
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
const code = err.code;
|
|
151
|
-
if (code === 'EPERM') {
|
|
152
|
-
throw new common_1.HttpException({ code: 'PERMISSION_DENIED', message: '프로세스 중지 권한이 없습니다' }, 403);
|
|
145
|
+
// Send SIGTERM to the process group (negative pid kills detached ACP child processes too)
|
|
146
|
+
const pid = task.pid;
|
|
147
|
+
if (pid > 0) {
|
|
148
|
+
try {
|
|
149
|
+
process.kill(-pid, 'SIGTERM');
|
|
153
150
|
}
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
151
|
+
catch (err) {
|
|
152
|
+
const code = err.code;
|
|
153
|
+
if (code === 'EPERM') {
|
|
154
|
+
throw new common_1.HttpException({ code: 'PERMISSION_DENIED', message: '프로세스 중지 권한이 없습니다' }, 403);
|
|
155
|
+
}
|
|
156
|
+
// ESRCH = process already gone — proceed to update DB
|
|
157
|
+
if (code !== 'ESRCH')
|
|
158
|
+
throw err;
|
|
159
|
+
}
|
|
160
|
+
// SIGKILL fallback: if process group is still alive after 2s, force-kill
|
|
161
|
+
setTimeout(() => {
|
|
162
|
+
try {
|
|
163
|
+
process.kill(-pid, 'SIGKILL');
|
|
164
|
+
}
|
|
165
|
+
catch {
|
|
166
|
+
// Process already gone — ignore
|
|
167
|
+
}
|
|
168
|
+
}, 2000);
|
|
157
169
|
}
|
|
158
170
|
// Conditionally update DB (race condition safe)
|
|
159
171
|
this.taskRepo.markTaskFailed(taskId, '사용자에 의해 중지됨');
|
|
@@ -246,6 +258,7 @@ let TaskService = TaskService_1 = class TaskService {
|
|
|
246
258
|
platform: data.platform,
|
|
247
259
|
messages: messagesWithLogs,
|
|
248
260
|
metadata: data.metadata,
|
|
261
|
+
renderedPrompt: data.rendered_prompt ?? null,
|
|
249
262
|
...tokenUsage,
|
|
250
263
|
};
|
|
251
264
|
}
|
|
@@ -332,6 +345,7 @@ let TaskService = TaskService_1 = class TaskService {
|
|
|
332
345
|
durationMs: row.duration_ms ?? undefined,
|
|
333
346
|
error: row.error || undefined,
|
|
334
347
|
prompt: row.prompt || undefined,
|
|
348
|
+
renderedPrompt: row.rendered_prompt ?? null,
|
|
335
349
|
result: row.result || undefined,
|
|
336
350
|
model: row.model || undefined,
|
|
337
351
|
threadId: row.thread_id || undefined,
|
|
@@ -519,13 +533,13 @@ let TaskService = TaskService_1 = class TaskService {
|
|
|
519
533
|
}
|
|
520
534
|
return undefined;
|
|
521
535
|
}
|
|
522
|
-
/** Merge consecutive type=text entries into one
|
|
536
|
+
/** Merge consecutive type=text entries into one while preserving message boundaries. */
|
|
523
537
|
mergeConsecutiveText(entries) {
|
|
524
538
|
const merged = [];
|
|
525
539
|
for (const entry of entries) {
|
|
526
540
|
const prev = merged[merged.length - 1];
|
|
527
541
|
if (prev && prev.type === 'text' && entry.type === 'text') {
|
|
528
|
-
prev.content = (prev.content || ''
|
|
542
|
+
prev.content = this.joinTextEntries(prev.content || '', entry.content || '');
|
|
529
543
|
}
|
|
530
544
|
else {
|
|
531
545
|
merged.push({ ...entry });
|
|
@@ -533,6 +547,13 @@ let TaskService = TaskService_1 = class TaskService {
|
|
|
533
547
|
}
|
|
534
548
|
return merged;
|
|
535
549
|
}
|
|
550
|
+
joinTextEntries(previous, next) {
|
|
551
|
+
if (!previous)
|
|
552
|
+
return next;
|
|
553
|
+
if (!next)
|
|
554
|
+
return previous;
|
|
555
|
+
return previous + next;
|
|
556
|
+
}
|
|
536
557
|
/**
|
|
537
558
|
* When ACP streaming sends an initial tool_call with empty input followed by
|
|
538
559
|
* a tool_call_update with refined input, both arrive as type=tool_use with
|
|
@@ -77,6 +77,11 @@ let ThreadController = class ThreadController {
|
|
|
77
77
|
const data = this.threadService.getThreadTask(threadId, taskId, include, logLimit, ws.id);
|
|
78
78
|
return { success: true, data };
|
|
79
79
|
}
|
|
80
|
+
// TSK.RETRY: Retry a failed task — prepends dreaming context hint
|
|
81
|
+
async retryTask(threadId, taskId, ws) {
|
|
82
|
+
const data = await this.threadService.retryTask(threadId, taskId, ws.id);
|
|
83
|
+
return { success: true, data };
|
|
84
|
+
}
|
|
80
85
|
};
|
|
81
86
|
exports.ThreadController = ThreadController;
|
|
82
87
|
__decorate([
|
|
@@ -166,6 +171,17 @@ __decorate([
|
|
|
166
171
|
__metadata("design:paramtypes", [String, String, Object, String, Number]),
|
|
167
172
|
__metadata("design:returntype", void 0)
|
|
168
173
|
], ThreadController.prototype, "getThreadTask", null);
|
|
174
|
+
__decorate([
|
|
175
|
+
(0, swagger_1.ApiOperation)({ operationId: 'TSK.RETRY', summary: 'Retry a failed task with dreaming context hint' }),
|
|
176
|
+
(0, common_1.Post)(':threadId/tasks/:taskId/retry'),
|
|
177
|
+
(0, common_1.HttpCode)(202),
|
|
178
|
+
__param(0, (0, common_1.Param)('threadId')),
|
|
179
|
+
__param(1, (0, common_1.Param)('taskId')),
|
|
180
|
+
__param(2, (0, workspace_decorator_js_1.Workspace)()),
|
|
181
|
+
__metadata("design:type", Function),
|
|
182
|
+
__metadata("design:paramtypes", [String, String, Object]),
|
|
183
|
+
__metadata("design:returntype", Promise)
|
|
184
|
+
], ThreadController.prototype, "retryTask", null);
|
|
169
185
|
exports.ThreadController = ThreadController = __decorate([
|
|
170
186
|
(0, swagger_1.ApiTags)('threads'),
|
|
171
187
|
(0, common_1.Controller)('ws/:slug/threads'),
|
|
@@ -11,11 +11,13 @@ const common_1 = require("@nestjs/common");
|
|
|
11
11
|
const thread_controller_js_1 = require("./thread.controller.js");
|
|
12
12
|
const thread_service_js_1 = require("./thread.service.js");
|
|
13
13
|
const box_service_js_1 = require("../box/box.service.js");
|
|
14
|
+
const crewx_module_js_1 = require("../../modules/crewx.module.js");
|
|
14
15
|
let ThreadModule = class ThreadModule {
|
|
15
16
|
};
|
|
16
17
|
exports.ThreadModule = ThreadModule;
|
|
17
18
|
exports.ThreadModule = ThreadModule = __decorate([
|
|
18
19
|
(0, common_1.Module)({
|
|
20
|
+
imports: [crewx_module_js_1.CrewxModule],
|
|
19
21
|
controllers: [thread_controller_js_1.ThreadController],
|
|
20
22
|
providers: [thread_service_js_1.ThreadService, box_service_js_1.BoxService],
|
|
21
23
|
exports: [thread_service_js_1.ThreadService],
|
|
@@ -8,16 +8,22 @@ 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 ThreadService_1;
|
|
11
12
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
12
13
|
exports.ThreadService = void 0;
|
|
13
14
|
const common_1 = require("@nestjs/common");
|
|
14
15
|
const tokenizer_js_1 = require("../../utils/tokenizer.js");
|
|
15
16
|
const sdk_1 = require("@crewx/sdk");
|
|
16
17
|
const thread_repository_js_1 = require("../../repository/thread.repository.js");
|
|
17
|
-
|
|
18
|
+
const crewx_pool_service_js_1 = require("../../modules/crewx-pool.service.js");
|
|
19
|
+
const workspace_context_store_js_1 = require("../../common/workspace-context.store.js");
|
|
20
|
+
let ThreadService = ThreadService_1 = class ThreadService {
|
|
18
21
|
threadRepo;
|
|
19
|
-
|
|
22
|
+
crewxPool;
|
|
23
|
+
logger = new common_1.Logger(ThreadService_1.name);
|
|
24
|
+
constructor(threadRepo, crewxPool) {
|
|
20
25
|
this.threadRepo = threadRepo;
|
|
26
|
+
this.crewxPool = crewxPool;
|
|
21
27
|
}
|
|
22
28
|
// THD.LIST: List threads from threads table (with optional q search)
|
|
23
29
|
listThreads(params, workspaceId) {
|
|
@@ -189,11 +195,11 @@ let ThreadService = class ThreadService {
|
|
|
189
195
|
callerAgentId: row.caller_agent_id || undefined,
|
|
190
196
|
};
|
|
191
197
|
if (wantLogs) {
|
|
192
|
-
result.logs = this.parseLogs(row.logs, limit);
|
|
198
|
+
result.logs = this.parseLogs(row.logs, limit, this.extractAdapterKey(row));
|
|
193
199
|
}
|
|
194
200
|
if (wantChildren && children) {
|
|
195
201
|
result.childTasks = children.map((child) => {
|
|
196
|
-
const logs = this.parseLogs(child.logs, 1);
|
|
202
|
+
const logs = this.parseLogs(child.logs, 1, this.extractAdapterKey(child));
|
|
197
203
|
return {
|
|
198
204
|
taskId: child.id,
|
|
199
205
|
agentId: child.agent_id || '',
|
|
@@ -215,6 +221,42 @@ let ThreadService = class ThreadService {
|
|
|
215
221
|
return (0, sdk_1.buildContext)(threadId, this, // ThreadService satisfies ITaskReader
|
|
216
222
|
boxService, params, tiktokenTokenizer);
|
|
217
223
|
}
|
|
224
|
+
// TSK.RETRY: Retry a failed task with dreaming context hint prepended
|
|
225
|
+
async retryTask(threadId, taskId, workspaceId) {
|
|
226
|
+
this.validateId(threadId);
|
|
227
|
+
this.validateId(taskId);
|
|
228
|
+
const found = this.threadRepo.findTaskById(threadId, taskId, workspaceId);
|
|
229
|
+
if (!found) {
|
|
230
|
+
throw new common_1.NotFoundException(`Task not found: ${taskId} in thread: ${threadId}`);
|
|
231
|
+
}
|
|
232
|
+
const { task } = found;
|
|
233
|
+
if (task.status !== 'failed') {
|
|
234
|
+
throw new common_1.BadRequestException(`Task ${taskId} is not in failed state (current: ${task.status}). Only failed tasks can be retried.`);
|
|
235
|
+
}
|
|
236
|
+
const dreamingPrefix = `<crewx_system_message>\n` +
|
|
237
|
+
`Retrying a failed task.\n` +
|
|
238
|
+
`You must run \`crewx dreaming --task=${taskId}\` to review where it stopped before continuing.\n` +
|
|
239
|
+
`</crewx_system_message>\n\n`;
|
|
240
|
+
const retryPrompt = `${dreamingPrefix}${task.prompt ?? ''}`;
|
|
241
|
+
const newTaskId = (0, sdk_1.generateId)('tsk');
|
|
242
|
+
const agentRef = `@${task.agent_id}`;
|
|
243
|
+
const mode = task.mode ?? 'execute';
|
|
244
|
+
const crewx = await this.crewxPool.getForCurrentRequest();
|
|
245
|
+
const sdkOptions = {
|
|
246
|
+
threadId,
|
|
247
|
+
taskId: newTaskId,
|
|
248
|
+
platform: 'cli',
|
|
249
|
+
metadata: { thread: threadId, parentTaskId: null, retriedFromTaskId: taskId },
|
|
250
|
+
cwd: (0, workspace_context_store_js_1.getWorkspacePath)(),
|
|
251
|
+
};
|
|
252
|
+
const sdkCall = mode === 'execute'
|
|
253
|
+
? crewx.execute(agentRef, retryPrompt, sdkOptions)
|
|
254
|
+
: crewx.query(agentRef, retryPrompt, sdkOptions);
|
|
255
|
+
sdkCall.catch((err) => {
|
|
256
|
+
this.logger.error(`Retry task failed [thread=${threadId}, originalTask=${taskId}, newTask=${newTaskId}]: ${err.message}`, err.stack);
|
|
257
|
+
});
|
|
258
|
+
return { taskId: newTaskId, status: 'pending', agentId: task.agent_id };
|
|
259
|
+
}
|
|
218
260
|
// --- Private helpers ---
|
|
219
261
|
// Reconstruct messages from tasks (used by buildContext and MessageService)
|
|
220
262
|
getThreadMessages(threadId, workspaceId) {
|
|
@@ -281,7 +323,7 @@ let ThreadService = class ThreadService {
|
|
|
281
323
|
totalCostUsd,
|
|
282
324
|
sessionId,
|
|
283
325
|
error: task.error || null,
|
|
284
|
-
logs: this.parseLogs(task.logs, 50),
|
|
326
|
+
logs: this.parseLogs(task.logs, 50, this.extractAdapterKey(task)),
|
|
285
327
|
},
|
|
286
328
|
},
|
|
287
329
|
});
|
|
@@ -350,7 +392,7 @@ let ThreadService = class ThreadService {
|
|
|
350
392
|
return text.slice(0, maxLen) + '...';
|
|
351
393
|
}
|
|
352
394
|
/** @see TaskService.parseLogs — 동일 역할, ACP 파싱 로직 동기화 필요 */
|
|
353
|
-
parseLogs(logsJson, logLimit) {
|
|
395
|
+
parseLogs(logsJson, logLimit, providerId) {
|
|
354
396
|
if (!logsJson)
|
|
355
397
|
return [];
|
|
356
398
|
let rawLogs;
|
|
@@ -366,7 +408,7 @@ let ThreadService = class ThreadService {
|
|
|
366
408
|
for (const log of rawLogs) {
|
|
367
409
|
if (log.level === 'stdout') {
|
|
368
410
|
try {
|
|
369
|
-
const parsed = (0, sdk_1.parseStdoutEvent)(log.timestamp, log.message);
|
|
411
|
+
const parsed = (0, sdk_1.parseStdoutEvent)(log.timestamp, log.message, providerId);
|
|
370
412
|
if (parsed.length > 0) {
|
|
371
413
|
entries.push(...parsed);
|
|
372
414
|
continue;
|
|
@@ -387,13 +429,13 @@ let ThreadService = class ThreadService {
|
|
|
387
429
|
}
|
|
388
430
|
return deduped;
|
|
389
431
|
}
|
|
390
|
-
/** Merge consecutive type=text entries into one
|
|
432
|
+
/** Merge consecutive type=text entries into one while preserving message boundaries. */
|
|
391
433
|
mergeConsecutiveText(entries) {
|
|
392
434
|
const merged = [];
|
|
393
435
|
for (const entry of entries) {
|
|
394
436
|
const prev = merged[merged.length - 1];
|
|
395
437
|
if (prev && prev.type === 'text' && entry.type === 'text') {
|
|
396
|
-
prev.content = (prev.content || ''
|
|
438
|
+
prev.content = this.joinTextEntries(prev.content || '', entry.content || '');
|
|
397
439
|
}
|
|
398
440
|
else {
|
|
399
441
|
merged.push({ ...entry });
|
|
@@ -401,6 +443,32 @@ let ThreadService = class ThreadService {
|
|
|
401
443
|
}
|
|
402
444
|
return merged;
|
|
403
445
|
}
|
|
446
|
+
joinTextEntries(previous, next) {
|
|
447
|
+
if (!previous)
|
|
448
|
+
return next;
|
|
449
|
+
if (!next)
|
|
450
|
+
return previous;
|
|
451
|
+
return previous + next;
|
|
452
|
+
}
|
|
453
|
+
extractAdapterKey(row) {
|
|
454
|
+
if (row.metadata) {
|
|
455
|
+
try {
|
|
456
|
+
const meta = JSON.parse(row.metadata);
|
|
457
|
+
const provider = meta.provider;
|
|
458
|
+
if (provider) {
|
|
459
|
+
const slash = provider.lastIndexOf('/');
|
|
460
|
+
return slash >= 0 ? provider.slice(slash + 1) : provider;
|
|
461
|
+
}
|
|
462
|
+
}
|
|
463
|
+
catch {
|
|
464
|
+
// Ignore malformed task metadata.
|
|
465
|
+
}
|
|
466
|
+
}
|
|
467
|
+
if (row.coding_agent_command) {
|
|
468
|
+
return row.coding_agent_command.split(/\s/)[0] || undefined;
|
|
469
|
+
}
|
|
470
|
+
return undefined;
|
|
471
|
+
}
|
|
404
472
|
/**
|
|
405
473
|
* ACP providers (claude, opencode) emit the same toolUseId twice — first with
|
|
406
474
|
* a placeholder input, then with the real one. Keep only the last occurrence.
|
|
@@ -422,7 +490,8 @@ let ThreadService = class ThreadService {
|
|
|
422
490
|
}
|
|
423
491
|
};
|
|
424
492
|
exports.ThreadService = ThreadService;
|
|
425
|
-
exports.ThreadService = ThreadService = __decorate([
|
|
493
|
+
exports.ThreadService = ThreadService = ThreadService_1 = __decorate([
|
|
426
494
|
(0, common_1.Injectable)(),
|
|
427
|
-
__metadata("design:paramtypes", [thread_repository_js_1.ThreadRepository
|
|
495
|
+
__metadata("design:paramtypes", [thread_repository_js_1.ThreadRepository,
|
|
496
|
+
crewx_pool_service_js_1.CrewxPoolService])
|
|
428
497
|
], ThreadService);
|