crewx 0.8.7-rc.21 → 0.8.7-rc.22
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/assets/{MarketPage-aY16UW1_.js → MarketPage-lLBtAENs.js} +10 -15
- package/dist/assets/{PromptTab-C3aTJS6u.js → PromptTab-Daq54dMi.js} +1 -1
- package/dist/assets/_baseUniq-BJDqmZBO.js +1 -0
- package/dist/assets/{arc-BCQAokB1.js → arc-ByjRIa5p.js} +1 -1
- package/dist/assets/{architectureDiagram-UYN6MBPD-Bc87ujv2.js → architectureDiagram-UYN6MBPD-DcKpqSFb.js} +1 -1
- package/dist/assets/{blockDiagram-ZHA2E4KO-wC9uH8Lb.js → blockDiagram-ZHA2E4KO-YvUeULT5.js} +1 -1
- package/dist/assets/{c4Diagram-6F5ED5ID-CqB7lqni.js → c4Diagram-6F5ED5ID-vggzH2N5.js} +1 -1
- package/dist/assets/channel-1Y6YooBb.js +1 -0
- package/dist/assets/{chunk-5HRBRIJM-Cc1L17Eh.js → chunk-5HRBRIJM-BbaQwJkn.js} +1 -1
- package/dist/assets/{chunk-7U56Z5CX-Df3nu_7G.js → chunk-7U56Z5CX-DVYVpFaa.js} +1 -1
- package/dist/assets/{chunk-ASOPGD6M-DIXslLG-.js → chunk-ASOPGD6M-CXEvWb-8.js} +1 -1
- package/dist/assets/{chunk-KFBOBJHC-FytVljyP.js → chunk-KFBOBJHC-CadVXFV3.js} +1 -1
- package/dist/assets/{chunk-T2TOU4HS-CU5z-cXq.js → chunk-T2TOU4HS-lDZr1sVF.js} +1 -1
- package/dist/assets/{chunk-TMUBEWPD-CWo1AFyn.js → chunk-TMUBEWPD-D5A0MrFD.js} +1 -1
- package/dist/assets/classDiagram-LNE6IOMH-CDxkV2JV.js +1 -0
- package/dist/assets/classDiagram-v2-MQ7JQ4JX-CDxkV2JV.js +1 -0
- package/dist/assets/clone-6xznz_9G.js +1 -0
- package/dist/assets/{dagre-4EVJKHTY-CgZW5tg4.js → dagre-4EVJKHTY-Bncx7Ijq.js} +1 -1
- package/dist/assets/{diagram-QW4FP2JN-CJAng8RC.js → diagram-QW4FP2JN-RkxVNs99.js} +1 -1
- package/dist/assets/{erDiagram-6RL3IURR-C1uHztHX.js → erDiagram-6RL3IURR-DIiKqQdx.js} +1 -1
- package/dist/assets/{flowDiagram-7ASYPVHJ-Yiu2odO-.js → flowDiagram-7ASYPVHJ-DQ5PN7v6.js} +1 -1
- package/dist/assets/{ganttDiagram-NTVNEXSI-tutc0dJP.js → ganttDiagram-NTVNEXSI-BeNWGpB6.js} +1 -1
- package/dist/assets/{gitGraph-YCYPL57B-C3jM6T4G.js → gitGraph-YCYPL57B-CdMe059q.js} +1 -1
- package/dist/assets/{gitGraphDiagram-NRZ2UAAF-0aqHEKzz.js → gitGraphDiagram-NRZ2UAAF-BEke5TyK.js} +1 -1
- package/dist/assets/{graph-DC224ILV.js → graph-DyQIwWJd.js} +1 -1
- package/dist/assets/{infoDiagram-A4XQUW5V-Do7Mpzkx.js → infoDiagram-A4XQUW5V-CUcC8I_5.js} +1 -1
- package/dist/assets/{journeyDiagram-G5WM74LC-DAruEJc7.js → journeyDiagram-G5WM74LC-CQ5DqAH5.js} +1 -1
- package/dist/assets/{kanban-definition-QRCXZQQD-DB63vLR0.js → kanban-definition-QRCXZQQD-BBsgOii7.js} +1 -1
- package/dist/assets/{layout-BiYH5x6Q.js → layout-D7vBs_Wy.js} +1 -1
- package/dist/assets/{linear-CLyGW2s0.js → linear-CEOF9oJz.js} +1 -1
- package/dist/assets/{main-CEKkUqZb.js → main-Bx1nZ7_p.js} +278 -273
- package/dist/assets/main-C4ebvOIJ.css +10 -0
- package/dist/assets/{min-ChJMFU2N.js → min-RK9J5Utn.js} +1 -1
- package/dist/assets/{mindmap-definition-GWI6TPTV-CtADqQgd.js → mindmap-definition-GWI6TPTV-BXvEU7qC.js} +1 -1
- package/dist/assets/{pieDiagram-YF2LJOPJ-BSOfzo3O.js → pieDiagram-YF2LJOPJ-TrXqcpni.js} +1 -1
- package/dist/assets/{quadrantDiagram-OS5C2QUG-BLBQNFVb.js → quadrantDiagram-OS5C2QUG-B-chTEU4.js} +1 -1
- package/dist/assets/{requirementDiagram-MIRIMTAZ-DqorvuS6.js → requirementDiagram-MIRIMTAZ-BgJhNLp_.js} +1 -1
- package/dist/assets/{sankeyDiagram-Y46BX6SQ-4NDcrLkB.js → sankeyDiagram-Y46BX6SQ-BVsgYKK7.js} +1 -1
- package/dist/assets/{sequenceDiagram-G6AWOVSC-yha6BxK2.js → sequenceDiagram-G6AWOVSC-ByJOpkq3.js} +1 -1
- package/dist/assets/{stateDiagram-MAYHULR4-XJ_I41ez.js → stateDiagram-MAYHULR4-Dw7lUL1B.js} +1 -1
- package/dist/assets/stateDiagram-v2-4JROLMXI-DYJiM3bF.js +1 -0
- package/dist/assets/{timeline-definition-U7ZMHBDA-DCNrmvE7.js → timeline-definition-U7ZMHBDA-CSoEYs60.js} +1 -1
- package/dist/assets/{xychartDiagram-6QU3TZC5-9N-Ohhdp.js → xychartDiagram-6QU3TZC5-0_rG7p3L.js} +1 -1
- package/dist/index.html +2 -2
- package/package.json +8 -8
- package/packages/cli/package.json +1 -1
- package/dist/assets/_baseUniq-DxEFlwNt.js +0 -1
- package/dist/assets/channel-DNLHkiNp.js +0 -1
- package/dist/assets/classDiagram-LNE6IOMH-B430Onym.js +0 -1
- package/dist/assets/classDiagram-v2-MQ7JQ4JX-B430Onym.js +0 -1
- package/dist/assets/clone-B9O3E3P1.js +0 -1
- package/dist/assets/main-D3YeRndF.css +0 -10
- package/dist/assets/stateDiagram-v2-4JROLMXI-CxcyPJO2.js +0 -1
- package/dist-server/adapters/adapter.module.js +0 -35
- package/dist-server/adapters/adapter.service.js +0 -79
- package/dist-server/adapters/http-router.service.js +0 -91
- package/dist-server/app.module.js +0 -111
- package/dist-server/bootstrap/crewx-server.js +0 -80
- package/dist-server/bootstrap/tls.js +0 -138
- package/dist-server/common/analytics.client.js +0 -180
- package/dist-server/common/analytics.module.js +0 -21
- package/dist-server/common/cors.js +0 -15
- package/dist-server/common/decorators/project.decorator.js +0 -21
- package/dist-server/common/decorators/workspace.decorator.js +0 -24
- package/dist-server/common/device-id.js +0 -33
- package/dist-server/common/interceptor/api-id-header.interceptor.js +0 -53
- package/dist-server/common/interceptor/http-logging.interceptor.js +0 -124
- package/dist-server/common/limits/defaults.js +0 -13
- package/dist-server/common/limits/limits-provider.js +0 -6
- package/dist-server/common/limits/limits.controller.js +0 -38
- package/dist-server/common/limits/limits.module.js +0 -24
- package/dist-server/common/limits/local-limits.provider.js +0 -27
- package/dist-server/common/middleware/workspace.middleware.js +0 -210
- package/dist-server/common/repository.exception-filter.js +0 -48
- package/dist-server/common/workspace-context.store.js +0 -25
- package/dist-server/domain/agent/agent.controller.js +0 -96
- package/dist-server/domain/agent/agent.module.js +0 -27
- package/dist-server/domain/agent/agent.service.js +0 -335
- package/dist-server/domain/agent/agent.types.js +0 -3
- package/dist-server/domain/agent/dto/create-agent.dto.js +0 -80
- package/dist-server/domain/agent/dto/preview-prompt.dto.js +0 -77
- package/dist-server/domain/agent/dto/provider-meta.dto.js +0 -2
- package/dist-server/domain/agent/dto/update-agent.dto.js +0 -92
- package/dist-server/domain/agent/provider.controller.js +0 -38
- package/dist-server/domain/agent/template-processor.service.js +0 -71
- package/dist-server/domain/auth/auth.constants.js +0 -4
- package/dist-server/domain/auth/auth.controller.js +0 -123
- package/dist-server/domain/auth/auth.module.js +0 -47
- package/dist-server/domain/auth/decorators/public.decorator.js +0 -7
- package/dist-server/domain/auth/guards/base-auth.guard.js +0 -66
- package/dist-server/domain/auth/session-store.js +0 -26
- package/dist-server/domain/auth/utils/ip.utils.js +0 -31
- package/dist-server/domain/box/box.controller.js +0 -77
- package/dist-server/domain/box/box.module.js +0 -21
- package/dist-server/domain/box/box.service.js +0 -97
- package/dist-server/domain/box/dto/create-box.dto.js +0 -74
- package/dist-server/domain/cli/cli.controller.js +0 -136
- package/dist-server/domain/cli/cli.module.js +0 -21
- package/dist-server/domain/cli/cli.service.js +0 -598
- package/dist-server/domain/cli/dto/login-cli.dto.js +0 -25
- package/dist-server/domain/cli/dto/update-cli.dto.js +0 -31
- package/dist-server/domain/doc/doc.controller.js +0 -95
- package/dist-server/domain/doc/doc.module.js +0 -21
- package/dist-server/domain/doc/doc.service.js +0 -368
- package/dist-server/domain/doc/dto/doc.dto.js +0 -64
- package/dist-server/domain/document/document.controller.js +0 -57
- package/dist-server/domain/document/document.module.js +0 -22
- package/dist-server/domain/document/document.service.js +0 -274
- package/dist-server/domain/document/dto/register-document.dto.js +0 -24
- package/dist-server/domain/fs/dto/mkdir-request.dto.js +0 -47
- package/dist-server/domain/fs/dto/mkdir-response.dto.js +0 -2
- package/dist-server/domain/fs/dto/quick-access.dto.js +0 -2
- package/dist-server/domain/fs/dto/tree-node.dto.js +0 -2
- package/dist-server/domain/fs/dto/tree-query.dto.js +0 -35
- package/dist-server/domain/fs/fs.controller.js +0 -63
- package/dist-server/domain/fs/fs.module.js +0 -22
- package/dist-server/domain/fs/fs.service.js +0 -303
- package/dist-server/domain/git/git.module.js +0 -20
- package/dist-server/domain/git/git.service.js +0 -222
- package/dist-server/domain/goal/dto/goal.dto.js +0 -49
- package/dist-server/domain/goal/goal.controller.js +0 -86
- package/dist-server/domain/goal/goal.module.js +0 -22
- package/dist-server/domain/goal/goal.service.js +0 -197
- package/dist-server/domain/goal/parser/goal-md-parser.js +0 -166
- package/dist-server/domain/goal/parser/goal-md-serializer.js +0 -110
- package/dist-server/domain/health/health.controller.js +0 -57
- package/dist-server/domain/health/health.module.js +0 -19
- package/dist-server/domain/knowledge/dto/graph-knowledge.dto.js +0 -27
- package/dist-server/domain/knowledge/dto/knowledge.dto.js +0 -71
- package/dist-server/domain/knowledge/knowledge.controller.js +0 -122
- package/dist-server/domain/knowledge/knowledge.module.js +0 -21
- package/dist-server/domain/knowledge/knowledge.service.js +0 -538
- package/dist-server/domain/market/dto/add-registry.dto.js +0 -32
- package/dist-server/domain/market/dto/install-market.dto.js +0 -30
- package/dist-server/domain/market/market.controller.js +0 -163
- package/dist-server/domain/market/market.module.js +0 -22
- package/dist-server/domain/market/market.service.js +0 -1447
- package/dist-server/domain/mcp/browser-session.store.js +0 -247
- package/dist-server/domain/mcp/crewx-tool.factory.js +0 -119
- package/dist-server/domain/mcp/mcp-auth.guard.js +0 -61
- package/dist-server/domain/mcp/mcp.constants.js +0 -4
- package/dist-server/domain/mcp/mcp.controller.js +0 -125
- package/dist-server/domain/mcp/mcp.dto.js +0 -12
- package/dist-server/domain/mcp/mcp.module.js +0 -49
- package/dist-server/domain/mcp/mcp.service.js +0 -684
- package/dist-server/domain/mcp/tool-router.service.js +0 -120
- package/dist-server/domain/message/dto/list-messages.dto.js +0 -57
- package/dist-server/domain/message/dto/send-message.dto.js +0 -45
- package/dist-server/domain/message/message.controller.js +0 -73
- package/dist-server/domain/message/message.module.js +0 -25
- package/dist-server/domain/message/message.service.js +0 -160
- package/dist-server/domain/onboarding/onboarding.controller.js +0 -97
- package/dist-server/domain/onboarding/onboarding.module.js +0 -23
- package/dist-server/domain/onboarding/onboarding.service.js +0 -106
- package/dist-server/domain/onboarding/onboarding.types.js +0 -2
- package/dist-server/domain/onboarding/presets/custom.js +0 -10
- package/dist-server/domain/onboarding/presets/dev-team.js +0 -41
- package/dist-server/domain/onboarding/presets/index.js +0 -15
- package/dist-server/domain/onboarding/presets/marketing-team.js +0 -29
- package/dist-server/domain/onboarding/presets/planning-team.js +0 -29
- package/dist-server/domain/onboarding/presets/solo.js +0 -17
- package/dist-server/domain/planner/dto/planner.dto.js +0 -58
- package/dist-server/domain/planner/planner.controller.js +0 -74
- package/dist-server/domain/planner/planner.module.js +0 -22
- package/dist-server/domain/planner/planner.service.js +0 -269
- package/dist-server/domain/project/dto/create-project.dto.js +0 -46
- package/dist-server/domain/project/dto/update-project.dto.js +0 -60
- package/dist-server/domain/project/project.controller.js +0 -129
- package/dist-server/domain/project/project.module.js +0 -22
- package/dist-server/domain/project/project.service.js +0 -173
- package/dist-server/domain/settings/settings.controller.js +0 -52
- package/dist-server/domain/settings/settings.module.js +0 -22
- package/dist-server/domain/settings/settings.service.js +0 -105
- package/dist-server/domain/task/dto/all-tasks.dto.js +0 -55
- package/dist-server/domain/task/dto/list-tasks.dto.js +0 -69
- package/dist-server/domain/task/dto/search-tasks.dto.js +0 -66
- package/dist-server/domain/task/dto/send-message.dto.js +0 -31
- package/dist-server/domain/task/dto/task-status.dto.js +0 -34
- package/dist-server/domain/task/dto/workspace-usage.dto.js +0 -38
- package/dist-server/domain/task/task.constants.js +0 -5
- package/dist-server/domain/task/task.controller.js +0 -169
- package/dist-server/domain/task/task.module.js +0 -23
- package/dist-server/domain/task/task.service.js +0 -775
- package/dist-server/domain/thread/dto/build-context.dto.js +0 -42
- package/dist-server/domain/thread/dto/list-messages.dto.js +0 -49
- package/dist-server/domain/thread/dto/list-threads.dto.js +0 -70
- package/dist-server/domain/thread/dto/search-threads.dto.js +0 -13
- package/dist-server/domain/thread/dto/update-thread.dto.js +0 -23
- package/dist-server/domain/thread/thread.controller.js +0 -160
- package/dist-server/domain/thread/thread.module.js +0 -23
- package/dist-server/domain/thread/thread.service.js +0 -419
- package/dist-server/domain/usage/usage.controller.js +0 -118
- package/dist-server/domain/usage/usage.module.js +0 -21
- package/dist-server/domain/usage/usage.service.js +0 -358
- package/dist-server/domain/usage/usage.types.js +0 -4
- package/dist-server/domain/wbs/wbs.controller.js +0 -51
- package/dist-server/domain/wbs/wbs.module.js +0 -21
- package/dist-server/domain/wbs/wbs.service.js +0 -94
- package/dist-server/domain/workflow/workflow.controller.js +0 -53
- package/dist-server/domain/workflow/workflow.module.js +0 -21
- package/dist-server/domain/workflow/workflow.service.js +0 -209
- package/dist-server/domain/workspace/dto/create-workspace.dto.js +0 -45
- package/dist-server/domain/workspace/dto/switch-workspace.dto.js +0 -32
- package/dist-server/domain/workspace/dto/workspace-info.dto.js +0 -2
- package/dist-server/domain/workspace/workspace.controller.js +0 -98
- package/dist-server/domain/workspace/workspace.module.js +0 -22
- package/dist-server/domain/workspace/workspace.service.js +0 -198
- package/dist-server/main.js +0 -204
- package/dist-server/modules/crewx-pool.service.js +0 -75
- package/dist-server/modules/crewx.module.js +0 -47
- package/dist-server/repository/base-sqlite.repository.js +0 -18
- package/dist-server/repository/box.repository.js +0 -99
- package/dist-server/repository/message.repository.js +0 -22
- package/dist-server/repository/project.repository.js +0 -51
- package/dist-server/repository/repository.module.js +0 -34
- package/dist-server/repository/request-log.repository.js +0 -96
- package/dist-server/repository/task.repository.js +0 -105
- package/dist-server/repository/thread.repository.js +0 -165
- package/dist-server/shared/crewx-home.js +0 -58
- package/dist-server/shared/crewx-state.js +0 -66
- package/dist-server/test-utils/compat-database.js +0 -30
- package/dist-server/utils/tokenizer.js +0 -8
|
@@ -1,775 +0,0 @@
|
|
|
1
|
-
"use strict";
|
|
2
|
-
var __decorate = (this && this.__decorate) || function (decorators, target, key, desc) {
|
|
3
|
-
var c = arguments.length, r = c < 3 ? target : desc === null ? desc = Object.getOwnPropertyDescriptor(target, key) : desc, d;
|
|
4
|
-
if (typeof Reflect === "object" && typeof Reflect.decorate === "function") r = Reflect.decorate(decorators, target, key, desc);
|
|
5
|
-
else for (var i = decorators.length - 1; i >= 0; i--) if (d = decorators[i]) r = (c < 3 ? d(r) : c > 3 ? d(target, key, r) : d(target, key)) || r;
|
|
6
|
-
return c > 3 && r && Object.defineProperty(target, key, r), r;
|
|
7
|
-
};
|
|
8
|
-
var __metadata = (this && this.__metadata) || function (k, v) {
|
|
9
|
-
if (typeof Reflect === "object" && typeof Reflect.metadata === "function") return Reflect.metadata(k, v);
|
|
10
|
-
};
|
|
11
|
-
var TaskService_1;
|
|
12
|
-
Object.defineProperty(exports, "__esModule", { value: true });
|
|
13
|
-
exports.TaskService = void 0;
|
|
14
|
-
const common_1 = require("@nestjs/common");
|
|
15
|
-
const promises_1 = require("fs/promises");
|
|
16
|
-
const path_1 = require("path");
|
|
17
|
-
const fs_1 = require("fs");
|
|
18
|
-
const child_process_1 = require("child_process");
|
|
19
|
-
const js_yaml_1 = require("js-yaml");
|
|
20
|
-
const task_constants_js_1 = require("./task.constants.js");
|
|
21
|
-
const sdk_1 = require("@crewx/sdk");
|
|
22
|
-
const task_repository_js_1 = require("../../repository/task.repository.js");
|
|
23
|
-
const project_repository_js_1 = require("../../repository/project.repository.js");
|
|
24
|
-
const workspace_context_store_js_1 = require("../../common/workspace-context.store.js");
|
|
25
|
-
const crewx_pool_service_js_1 = require("../../modules/crewx-pool.service.js");
|
|
26
|
-
const MAX_LOG_ENTRY_FIELD_LEN = 128 * 1024; // 128KB worth of UTF-16 code units
|
|
27
|
-
// Data source: .crewx/conversations/ JSON files (per design doc "태스크 데이터 소스").
|
|
28
|
-
// crewx.db is NOT used for task data — conversations JSON is the canonical source.
|
|
29
|
-
// TSK005 uses crewx.db for execution status and logs.
|
|
30
|
-
let TaskService = TaskService_1 = class TaskService {
|
|
31
|
-
taskRepo;
|
|
32
|
-
crewxPool;
|
|
33
|
-
projectRepo;
|
|
34
|
-
logger = new common_1.Logger(TaskService_1.name);
|
|
35
|
-
reaperInterval;
|
|
36
|
-
constructor(taskRepo, crewxPool, projectRepo) {
|
|
37
|
-
this.taskRepo = taskRepo;
|
|
38
|
-
this.crewxPool = crewxPool;
|
|
39
|
-
this.projectRepo = projectRepo;
|
|
40
|
-
}
|
|
41
|
-
onModuleInit() {
|
|
42
|
-
const reaped = this.taskRepo.reapOrphanedTasks();
|
|
43
|
-
if (reaped > 0) {
|
|
44
|
-
this.logger.warn(`Reaped ${reaped} orphaned task(s) on startup`);
|
|
45
|
-
}
|
|
46
|
-
this.reaperInterval = setInterval(() => {
|
|
47
|
-
try {
|
|
48
|
-
const n = this.taskRepo.reapOrphanedTasks();
|
|
49
|
-
if (n > 0)
|
|
50
|
-
this.logger.warn(`Reaped ${n} orphaned task(s)`);
|
|
51
|
-
}
|
|
52
|
-
catch { /* non-fatal */ }
|
|
53
|
-
}, 5 * 60 * 1000);
|
|
54
|
-
}
|
|
55
|
-
onModuleDestroy() {
|
|
56
|
-
if (this.reaperInterval !== undefined) {
|
|
57
|
-
clearInterval(this.reaperInterval);
|
|
58
|
-
}
|
|
59
|
-
}
|
|
60
|
-
// TSK.ALL: All tasks from crewx.db with filtering, search, and pagination
|
|
61
|
-
getAllTasks(params, workspaceId) {
|
|
62
|
-
const agents = params.agent?.split(',').map(s => s.trim()).filter(Boolean);
|
|
63
|
-
const statuses = params.status?.split(',').map(s => s.trim()).filter(Boolean);
|
|
64
|
-
const page = Math.max(1, params.page || 1);
|
|
65
|
-
const limit = Math.min(100, Math.max(1, params.limit || 20));
|
|
66
|
-
const offset = (page - 1) * limit;
|
|
67
|
-
const sortDir = params.sort === 'started_at:asc' ? 'ASC' : 'DESC';
|
|
68
|
-
const { tasks, total } = this.taskRepo.findAllTasks({
|
|
69
|
-
workspaceId, agents, statuses, q: params.q, limit, offset, sortDir,
|
|
70
|
-
});
|
|
71
|
-
return {
|
|
72
|
-
tasks: tasks.map(t => ({
|
|
73
|
-
taskId: t.id,
|
|
74
|
-
agentId: t.agent_id,
|
|
75
|
-
status: t.status,
|
|
76
|
-
prompt: this.truncate(t.prompt || '', 100),
|
|
77
|
-
model: t.model || null,
|
|
78
|
-
startedAt: t.started_at,
|
|
79
|
-
completedAt: t.completed_at || null,
|
|
80
|
-
durationMs: t.duration_ms ?? null,
|
|
81
|
-
inputTokens: t.input_tokens ?? 0,
|
|
82
|
-
outputTokens: t.output_tokens ?? 0,
|
|
83
|
-
costUsd: t.cost_usd ?? 0,
|
|
84
|
-
threadId: t.thread_id ?? null,
|
|
85
|
-
provider: this.extractProviderFromMeta(t.metadata) ?? null,
|
|
86
|
-
})),
|
|
87
|
-
total,
|
|
88
|
-
page,
|
|
89
|
-
limit,
|
|
90
|
-
};
|
|
91
|
-
}
|
|
92
|
-
// TSK004: Send message — fires crewx executeAgent in background and returns immediately
|
|
93
|
-
async sendMessage(threadId, message, agent) {
|
|
94
|
-
this.validateTaskId(threadId);
|
|
95
|
-
if (!message || typeof message !== 'string' || !message.trim()) {
|
|
96
|
-
throw new common_1.BadRequestException('message is required');
|
|
97
|
-
}
|
|
98
|
-
if (!agent || typeof agent !== 'string' || !agent.trim()) {
|
|
99
|
-
throw new common_1.BadRequestException('agent is required');
|
|
100
|
-
}
|
|
101
|
-
// Fire-and-forget: get the current workspace's Crewx instance from the pool
|
|
102
|
-
// (ConversationPlugin handles threads row upsert). Never close — pool owns lifecycle.
|
|
103
|
-
this.crewxPool.getForCurrentRequest().then((crewx) => {
|
|
104
|
-
crewx
|
|
105
|
-
.execute(`@${agent}`, message, {
|
|
106
|
-
threadId,
|
|
107
|
-
platform: 'cli',
|
|
108
|
-
metadata: { thread: threadId },
|
|
109
|
-
})
|
|
110
|
-
.catch((err) => {
|
|
111
|
-
this.logger.error(`Background executeAgent failed [thread=${threadId}]: ${err.message}`, err.stack);
|
|
112
|
-
});
|
|
113
|
-
}).catch((err) => {
|
|
114
|
-
this.logger.error(`CrewxPool.getForCurrentRequest failed [thread=${threadId}]: ${err.message}`, err.stack);
|
|
115
|
-
});
|
|
116
|
-
// Return the threadId as taskId — frontend polls getTaskStatus(threadId)
|
|
117
|
-
// which searches crewx.db for tasks WHERE command LIKE '%--thread={threadId}%'
|
|
118
|
-
return { taskId: threadId, status: 'pending', agent };
|
|
119
|
-
}
|
|
120
|
-
// TSK.STOP: Stop a running task by killing its process
|
|
121
|
-
stopTask(taskId, workspaceId) {
|
|
122
|
-
this.validateTaskId(taskId);
|
|
123
|
-
const task = this.taskRepo.findTaskForStop(taskId, workspaceId);
|
|
124
|
-
if (!task) {
|
|
125
|
-
throw new common_1.HttpException({ code: 'TASK_NOT_FOUND', message: '태스크를 찾을 수 없습니다' }, 404);
|
|
126
|
-
}
|
|
127
|
-
if (task.status !== 'running') {
|
|
128
|
-
throw new common_1.HttpException({ code: 'TASK_NOT_RUNNING', message: '실행 중인 태스크만 중지할 수 있습니다' }, 400);
|
|
129
|
-
}
|
|
130
|
-
if (task.pid == null) {
|
|
131
|
-
throw new common_1.HttpException({ code: 'PID_NOT_FOUND', message: '프로세스 ID를 찾을 수 없습니다' }, 422);
|
|
132
|
-
}
|
|
133
|
-
// PID ownership verification: ensure PID belongs to a crewx-related process
|
|
134
|
-
try {
|
|
135
|
-
const cmdOutput = (0, child_process_1.execSync)(`ps -p ${task.pid} -o command=`, { encoding: 'utf-8', timeout: 3000 }).trim();
|
|
136
|
-
if (!cmdOutput.includes('crewx') && !cmdOutput.includes('claude') && !cmdOutput.includes('node')) {
|
|
137
|
-
throw new common_1.HttpException({ code: 'PID_NOT_FOUND', message: '프로세스 ID를 찾을 수 없습니다' }, 422);
|
|
138
|
-
}
|
|
139
|
-
}
|
|
140
|
-
catch (err) {
|
|
141
|
-
// Process not found (already exited) — proceed to update DB status
|
|
142
|
-
if (err instanceof common_1.HttpException)
|
|
143
|
-
throw err;
|
|
144
|
-
}
|
|
145
|
-
// Send SIGTERM to the process
|
|
146
|
-
try {
|
|
147
|
-
process.kill(task.pid, 'SIGTERM');
|
|
148
|
-
}
|
|
149
|
-
catch (err) {
|
|
150
|
-
const code = err.code;
|
|
151
|
-
if (code === 'EPERM') {
|
|
152
|
-
throw new common_1.HttpException({ code: 'PERMISSION_DENIED', message: '프로세스 중지 권한이 없습니다' }, 403);
|
|
153
|
-
}
|
|
154
|
-
// ESRCH = process already gone — proceed to update DB
|
|
155
|
-
if (code !== 'ESRCH')
|
|
156
|
-
throw err;
|
|
157
|
-
}
|
|
158
|
-
// Conditionally update DB (race condition safe)
|
|
159
|
-
this.taskRepo.markTaskFailed(taskId, '사용자에 의해 중지됨');
|
|
160
|
-
return {
|
|
161
|
-
taskId,
|
|
162
|
-
status: 'failed',
|
|
163
|
-
error: '사용자에 의해 중지됨',
|
|
164
|
-
stoppedAt: new Date().toISOString(),
|
|
165
|
-
};
|
|
166
|
-
}
|
|
167
|
-
// TSK.LIST: List tasks
|
|
168
|
-
async getTasks(params, _workspaceId) {
|
|
169
|
-
const tasksDir = this.resolveTasksPath(params.project);
|
|
170
|
-
const files = await this.listJsonFiles(tasksDir);
|
|
171
|
-
const allSummaries = [];
|
|
172
|
-
const allAgents = new Set();
|
|
173
|
-
const allPlatforms = new Set();
|
|
174
|
-
for (const file of files) {
|
|
175
|
-
try {
|
|
176
|
-
const data = await this.readTaskFile((0, path_1.join)(tasksDir, file));
|
|
177
|
-
const summary = this.toSummary(data);
|
|
178
|
-
allPlatforms.add(summary.platform);
|
|
179
|
-
summary.agents.forEach((a) => allAgents.add(a));
|
|
180
|
-
allSummaries.push(summary);
|
|
181
|
-
}
|
|
182
|
-
catch {
|
|
183
|
-
// Skip malformed files
|
|
184
|
-
}
|
|
185
|
-
}
|
|
186
|
-
// Filter
|
|
187
|
-
let filtered = allSummaries;
|
|
188
|
-
const platform = params.platform || 'all';
|
|
189
|
-
if (platform !== 'all') {
|
|
190
|
-
filtered = filtered.filter((t) => t.platform === platform);
|
|
191
|
-
}
|
|
192
|
-
if (params.agent) {
|
|
193
|
-
filtered = filtered.filter((t) => t.agents.includes(params.agent));
|
|
194
|
-
}
|
|
195
|
-
// Sort
|
|
196
|
-
const sort = params.sort || 'updatedAt';
|
|
197
|
-
const order = params.order || 'desc';
|
|
198
|
-
filtered.sort((a, b) => {
|
|
199
|
-
let cmp = 0;
|
|
200
|
-
if (sort === 'messageCount') {
|
|
201
|
-
cmp = a.messageCount - b.messageCount;
|
|
202
|
-
}
|
|
203
|
-
else if (sort === 'createdAt') {
|
|
204
|
-
cmp = new Date(a.createdAt).getTime() - new Date(b.createdAt).getTime();
|
|
205
|
-
}
|
|
206
|
-
else {
|
|
207
|
-
cmp = new Date(a.updatedAt).getTime() - new Date(b.updatedAt).getTime();
|
|
208
|
-
}
|
|
209
|
-
return order === 'asc' ? cmp : -cmp;
|
|
210
|
-
});
|
|
211
|
-
// Pagination
|
|
212
|
-
const total = filtered.length;
|
|
213
|
-
const limit = this.normalizeLimit(params.limit, 50, 200);
|
|
214
|
-
const offset = this.normalizeOffset(params.offset);
|
|
215
|
-
const tasks = filtered.slice(offset, offset + limit);
|
|
216
|
-
return {
|
|
217
|
-
tasks,
|
|
218
|
-
total,
|
|
219
|
-
agents: Array.from(allAgents).sort(),
|
|
220
|
-
platforms: Array.from(allPlatforms).sort(),
|
|
221
|
-
};
|
|
222
|
-
}
|
|
223
|
-
// TSK.GET: Get task detail
|
|
224
|
-
async getTask(taskId, workspaceId) {
|
|
225
|
-
this.validateTaskId(taskId);
|
|
226
|
-
const tasksDir = this.resolveTasksPath(workspaceId);
|
|
227
|
-
const filePath = (0, path_1.join)(tasksDir, `${taskId}.json`);
|
|
228
|
-
// Ensure resolved path stays within tasksDir
|
|
229
|
-
const resolved = (0, path_1.resolve)(filePath);
|
|
230
|
-
if (!resolved.startsWith((0, path_1.resolve)(tasksDir))) {
|
|
231
|
-
throw new common_1.BadRequestException('Invalid taskId');
|
|
232
|
-
}
|
|
233
|
-
let data;
|
|
234
|
-
try {
|
|
235
|
-
data = await this.readTaskFile(filePath);
|
|
236
|
-
}
|
|
237
|
-
catch {
|
|
238
|
-
throw new common_1.NotFoundException(`Task not found: ${taskId}`);
|
|
239
|
-
}
|
|
240
|
-
// Enrich assistant messages with logs from crewx.db
|
|
241
|
-
const messagesWithLogs = this.enrichMessagesWithLogs(data, workspaceId);
|
|
242
|
-
// Aggregate token/cost data from crewx.db for this thread
|
|
243
|
-
const tokenUsage = this.taskRepo.getThreadTokenUsage(data.threadId, workspaceId);
|
|
244
|
-
return {
|
|
245
|
-
taskId: data.threadId,
|
|
246
|
-
platform: data.platform,
|
|
247
|
-
messages: messagesWithLogs,
|
|
248
|
-
metadata: data.metadata,
|
|
249
|
-
...tokenUsage,
|
|
250
|
-
};
|
|
251
|
-
}
|
|
252
|
-
// TSK003: Search tasks
|
|
253
|
-
async searchTasks(params, _workspaceId) {
|
|
254
|
-
const query = params.q;
|
|
255
|
-
if (!query || query.trim().length === 0) {
|
|
256
|
-
throw new common_1.BadRequestException('Search query is required');
|
|
257
|
-
}
|
|
258
|
-
const tasksDir = this.resolveTasksPath(params.project);
|
|
259
|
-
const files = await this.listJsonFiles(tasksDir);
|
|
260
|
-
const limit = this.normalizeLimit(params.limit, 20, 100);
|
|
261
|
-
const offset = this.normalizeOffset(params.offset);
|
|
262
|
-
const queryLower = query.toLowerCase();
|
|
263
|
-
const results = [];
|
|
264
|
-
for (const file of files) {
|
|
265
|
-
try {
|
|
266
|
-
const data = await this.readTaskFile((0, path_1.join)(tasksDir, file));
|
|
267
|
-
// Apply filters
|
|
268
|
-
const platform = params.platform || 'all';
|
|
269
|
-
if (platform !== 'all' && data.platform !== platform)
|
|
270
|
-
continue;
|
|
271
|
-
if (params.agent) {
|
|
272
|
-
const hasAgent = data.messages.some((m) => m.metadata?.agent_id === params.agent);
|
|
273
|
-
if (!hasAgent)
|
|
274
|
-
continue;
|
|
275
|
-
}
|
|
276
|
-
// Search messages
|
|
277
|
-
const matchedMessages = [];
|
|
278
|
-
for (const msg of data.messages) {
|
|
279
|
-
if (msg.text.toLowerCase().includes(queryLower)) {
|
|
280
|
-
matchedMessages.push({
|
|
281
|
-
id: msg.id,
|
|
282
|
-
text: this.highlightMatch(msg.text, query),
|
|
283
|
-
userId: msg.userId,
|
|
284
|
-
isAssistant: msg.isAssistant,
|
|
285
|
-
agentId: msg.metadata?.agent_id,
|
|
286
|
-
timestamp: msg.timestamp,
|
|
287
|
-
});
|
|
288
|
-
}
|
|
289
|
-
}
|
|
290
|
-
if (matchedMessages.length > 0) {
|
|
291
|
-
results.push({
|
|
292
|
-
taskId: data.threadId,
|
|
293
|
-
platform: data.platform,
|
|
294
|
-
matchedMessages,
|
|
295
|
-
score: matchedMessages.length,
|
|
296
|
-
});
|
|
297
|
-
}
|
|
298
|
-
}
|
|
299
|
-
catch {
|
|
300
|
-
// Skip malformed files
|
|
301
|
-
}
|
|
302
|
-
}
|
|
303
|
-
// Sort by score (most matches first)
|
|
304
|
-
results.sort((a, b) => b.score - a.score);
|
|
305
|
-
const paginated = results.slice(offset, offset + limit);
|
|
306
|
-
return {
|
|
307
|
-
results: paginated,
|
|
308
|
-
total: results.length,
|
|
309
|
-
query,
|
|
310
|
-
};
|
|
311
|
-
}
|
|
312
|
-
// TSK005: Get task execution status from crewx.db
|
|
313
|
-
getTaskStatus(taskId, params, workspaceId) {
|
|
314
|
-
this.validateTaskId(taskId);
|
|
315
|
-
const includes = (params.include || '').split(',').map((s) => s.trim());
|
|
316
|
-
const wantLogs = includes.includes('logs');
|
|
317
|
-
const wantChildren = includes.includes('children');
|
|
318
|
-
const logLimit = this.normalizeLimit(params.logLimit, task_constants_js_1.DEFAULT_TASK_LOG_LIMIT, task_constants_js_1.MAX_TASK_LOG_LIMIT);
|
|
319
|
-
const row = this.taskRepo.findTaskStatus(taskId, workspaceId);
|
|
320
|
-
if (!row) {
|
|
321
|
-
throw new common_1.NotFoundException(`Task not found in crewx.db: ${taskId}`);
|
|
322
|
-
}
|
|
323
|
-
const parsedMeta = this.tryParseJson(row.metadata);
|
|
324
|
-
const result = {
|
|
325
|
-
taskId: row.id,
|
|
326
|
-
status: row.status,
|
|
327
|
-
agent: row.agent_id,
|
|
328
|
-
callerAgentId: row.caller_agent_id || undefined,
|
|
329
|
-
parentTaskId: row.parent_task_id || undefined,
|
|
330
|
-
startedAt: row.started_at,
|
|
331
|
-
completedAt: row.completed_at || undefined,
|
|
332
|
-
durationMs: row.duration_ms ?? undefined,
|
|
333
|
-
error: row.error || undefined,
|
|
334
|
-
prompt: row.prompt || undefined,
|
|
335
|
-
result: row.result || undefined,
|
|
336
|
-
model: row.model || undefined,
|
|
337
|
-
threadId: row.thread_id || undefined,
|
|
338
|
-
inputTokens: row.input_tokens ?? undefined,
|
|
339
|
-
outputTokens: row.output_tokens ?? undefined,
|
|
340
|
-
costUsd: row.cost_usd ?? undefined,
|
|
341
|
-
mode: row.mode || undefined,
|
|
342
|
-
crewxVersion: row.crewx_version || undefined,
|
|
343
|
-
pid: row.status === 'running' ? (row.pid ?? undefined) : undefined,
|
|
344
|
-
};
|
|
345
|
-
if (parsedMeta) {
|
|
346
|
-
result.metadata = parsedMeta;
|
|
347
|
-
}
|
|
348
|
-
const adapterKey = this.extractAdapterKey(row);
|
|
349
|
-
if (wantLogs) {
|
|
350
|
-
result.logs = this.parseLogs(row.logs, logLimit, adapterKey);
|
|
351
|
-
}
|
|
352
|
-
result.toolUsage = this.aggregateToolUsage(row.logs, adapterKey);
|
|
353
|
-
if (wantChildren) {
|
|
354
|
-
// Need to open DB to get children — use withDb
|
|
355
|
-
const dbPaths = this.taskRepo.resolveDbPaths();
|
|
356
|
-
for (const dbPath of dbPaths) {
|
|
357
|
-
if (!(0, fs_1.existsSync)(dbPath))
|
|
358
|
-
continue;
|
|
359
|
-
const children = this.taskRepo.withDb((db) => this.taskRepo.findChildTasks(db, row.id), { dbPath, readOnly: true });
|
|
360
|
-
if (children.length > 0) {
|
|
361
|
-
result.childTasks = children.map((child) => this.toChildTaskStatus(child));
|
|
362
|
-
break;
|
|
363
|
-
}
|
|
364
|
-
}
|
|
365
|
-
if (!result.childTasks) {
|
|
366
|
-
result.childTasks = [];
|
|
367
|
-
}
|
|
368
|
-
}
|
|
369
|
-
return result;
|
|
370
|
-
}
|
|
371
|
-
// USG.MONTHLY: Workspace usage summary from crewx.db
|
|
372
|
-
getWorkspaceUsageSummary(workspaceId) {
|
|
373
|
-
const rows = this.taskRepo.getWorkspaceUsageSummary(workspaceId);
|
|
374
|
-
const wsIds = rows.map((r) => r.workspace_id).filter((id) => id != null && id !== 'unknown');
|
|
375
|
-
const wsNameMap = new Map();
|
|
376
|
-
for (const id of new Set(wsIds)) {
|
|
377
|
-
const project = this.projectRepo.findById(id);
|
|
378
|
-
wsNameMap.set(id, project?.name ?? id);
|
|
379
|
-
}
|
|
380
|
-
const workspaces = rows.map((row) => {
|
|
381
|
-
const inputTokens = row.input_tokens ?? 0;
|
|
382
|
-
const outputTokens = row.output_tokens ?? 0;
|
|
383
|
-
const wsId = row.workspace_id ?? 'unknown';
|
|
384
|
-
return {
|
|
385
|
-
workspaceId: wsId,
|
|
386
|
-
workspaceName: wsNameMap.get(wsId) ?? 'Unknown Workspace',
|
|
387
|
-
inputTokens,
|
|
388
|
-
outputTokens,
|
|
389
|
-
totalTokens: inputTokens + outputTokens,
|
|
390
|
-
totalCost: row.cost_usd ?? 0,
|
|
391
|
-
taskCount: row.task_count ?? 0,
|
|
392
|
-
};
|
|
393
|
-
});
|
|
394
|
-
return { workspaces };
|
|
395
|
-
}
|
|
396
|
-
// --- TSK005 private helpers ---
|
|
397
|
-
toChildTaskStatus(row) {
|
|
398
|
-
const logs = this.parseLogs(row.logs, 1, this.extractAdapterKey(row));
|
|
399
|
-
return {
|
|
400
|
-
taskId: row.id,
|
|
401
|
-
agentId: row.agent_id,
|
|
402
|
-
status: row.status,
|
|
403
|
-
startedAt: row.started_at,
|
|
404
|
-
completedAt: row.completed_at || undefined,
|
|
405
|
-
durationMs: row.duration_ms ?? undefined,
|
|
406
|
-
error: row.error || undefined,
|
|
407
|
-
latestLog: logs.length > 0 ? logs[logs.length - 1] : undefined,
|
|
408
|
-
};
|
|
409
|
-
}
|
|
410
|
-
tryParseJson(value) {
|
|
411
|
-
if (!value)
|
|
412
|
-
return undefined;
|
|
413
|
-
try {
|
|
414
|
-
return JSON.parse(value);
|
|
415
|
-
}
|
|
416
|
-
catch {
|
|
417
|
-
return undefined;
|
|
418
|
-
}
|
|
419
|
-
}
|
|
420
|
-
/** @see ThreadService.parseLogs — 동일 역할, ACP 파싱 로직 동기화 필요 */
|
|
421
|
-
parseLogs(logsJson, logLimit, providerId) {
|
|
422
|
-
if (!logsJson)
|
|
423
|
-
return [];
|
|
424
|
-
let rawLogs;
|
|
425
|
-
try {
|
|
426
|
-
rawLogs = JSON.parse(logsJson);
|
|
427
|
-
}
|
|
428
|
-
catch {
|
|
429
|
-
return [];
|
|
430
|
-
}
|
|
431
|
-
if (!Array.isArray(rawLogs))
|
|
432
|
-
return [];
|
|
433
|
-
const entries = [];
|
|
434
|
-
for (const log of rawLogs) {
|
|
435
|
-
if (log.level === 'stdout') {
|
|
436
|
-
try {
|
|
437
|
-
const parsed = (0, sdk_1.parseStdoutEvent)(log.timestamp, log.message, providerId);
|
|
438
|
-
if (parsed.length > 0) {
|
|
439
|
-
entries.push(...parsed);
|
|
440
|
-
continue;
|
|
441
|
-
}
|
|
442
|
-
// Parsed successfully but no displayable entries (e.g. thinking-only assistant message).
|
|
443
|
-
// Skip fallback if the raw message is a known Claude stream-json event type.
|
|
444
|
-
const trimmed = log.message.trimStart();
|
|
445
|
-
if (trimmed.startsWith('{')) {
|
|
446
|
-
try {
|
|
447
|
-
const obj = JSON.parse(trimmed);
|
|
448
|
-
if (obj.type === 'assistant' ||
|
|
449
|
-
obj.type === 'user' ||
|
|
450
|
-
obj.type === 'system' ||
|
|
451
|
-
obj.type === 'result' ||
|
|
452
|
-
obj.type === 'rate_limit_event') {
|
|
453
|
-
continue;
|
|
454
|
-
}
|
|
455
|
-
}
|
|
456
|
-
catch { /* not valid JSON, fall through to text fallback */ }
|
|
457
|
-
}
|
|
458
|
-
}
|
|
459
|
-
catch { /* adapter parse failure — fallback to text */ }
|
|
460
|
-
entries.push({ timestamp: log.timestamp, type: 'text', content: log.message });
|
|
461
|
-
}
|
|
462
|
-
else if (log.level === 'info') {
|
|
463
|
-
const infoEntries = (0, sdk_1.defaultParseEvent)(log.message, log.timestamp);
|
|
464
|
-
if (infoEntries.length > 0) {
|
|
465
|
-
entries.push(...infoEntries);
|
|
466
|
-
}
|
|
467
|
-
else if (log.message) {
|
|
468
|
-
entries.push({ timestamp: log.timestamp, type: 'info', content: log.message });
|
|
469
|
-
}
|
|
470
|
-
}
|
|
471
|
-
}
|
|
472
|
-
// Pipeline: merge consecutive text → deduplicate tool_use → truncate → logLimit slice
|
|
473
|
-
const merged = this.mergeConsecutiveText(entries);
|
|
474
|
-
const deduped = this.deduplicateToolUse(merged);
|
|
475
|
-
const truncated = deduped.map((e) => this.truncateEntry(e));
|
|
476
|
-
// Return the latest N entries (trim from the beginning)
|
|
477
|
-
if (truncated.length > logLimit) {
|
|
478
|
-
return truncated.slice(truncated.length - logLimit);
|
|
479
|
-
}
|
|
480
|
-
return truncated;
|
|
481
|
-
}
|
|
482
|
-
aggregateToolUsage(logsJson, providerId) {
|
|
483
|
-
const entries = this.parseLogs(logsJson, task_constants_js_1.MAX_TASK_LOG_LIMIT, providerId);
|
|
484
|
-
const counts = new Map();
|
|
485
|
-
for (const entry of entries) {
|
|
486
|
-
if (entry.type === 'tool_use' && entry.toolName) {
|
|
487
|
-
counts.set(entry.toolName, (counts.get(entry.toolName) || 0) + 1);
|
|
488
|
-
}
|
|
489
|
-
}
|
|
490
|
-
return Object.fromEntries(counts);
|
|
491
|
-
}
|
|
492
|
-
/** Extract full provider string from metadata JSON (e.g. "cli/claude"). */
|
|
493
|
-
extractProviderFromMeta(metadata) {
|
|
494
|
-
if (!metadata)
|
|
495
|
-
return undefined;
|
|
496
|
-
try {
|
|
497
|
-
const meta = JSON.parse(metadata);
|
|
498
|
-
return typeof meta.provider === 'string' ? meta.provider : undefined;
|
|
499
|
-
}
|
|
500
|
-
catch {
|
|
501
|
-
return undefined;
|
|
502
|
-
}
|
|
503
|
-
}
|
|
504
|
-
/** Extract adapter key (e.g. "opencode") from "cli/opencode" style provider string. */
|
|
505
|
-
extractAdapterKey(row) {
|
|
506
|
-
if (row.metadata) {
|
|
507
|
-
try {
|
|
508
|
-
const meta = JSON.parse(row.metadata);
|
|
509
|
-
const provider = meta.provider;
|
|
510
|
-
if (provider) {
|
|
511
|
-
const slash = provider.lastIndexOf('/');
|
|
512
|
-
return slash >= 0 ? provider.slice(slash + 1) : provider;
|
|
513
|
-
}
|
|
514
|
-
}
|
|
515
|
-
catch { /* ignore */ }
|
|
516
|
-
}
|
|
517
|
-
if (row.coding_agent_command) {
|
|
518
|
-
return row.coding_agent_command.split(/\s/)[0] || undefined;
|
|
519
|
-
}
|
|
520
|
-
return undefined;
|
|
521
|
-
}
|
|
522
|
-
/** Merge consecutive type=text entries into one (fixes Gemini delta streaming fragmentation) */
|
|
523
|
-
mergeConsecutiveText(entries) {
|
|
524
|
-
const merged = [];
|
|
525
|
-
for (const entry of entries) {
|
|
526
|
-
const prev = merged[merged.length - 1];
|
|
527
|
-
if (prev && prev.type === 'text' && entry.type === 'text') {
|
|
528
|
-
prev.content = (prev.content || '') + (entry.content || '');
|
|
529
|
-
}
|
|
530
|
-
else {
|
|
531
|
-
merged.push({ ...entry });
|
|
532
|
-
}
|
|
533
|
-
}
|
|
534
|
-
return merged;
|
|
535
|
-
}
|
|
536
|
-
/**
|
|
537
|
-
* When ACP streaming sends an initial tool_call with empty input followed by
|
|
538
|
-
* a tool_call_update with refined input, both arrive as type=tool_use with
|
|
539
|
-
* the same toolUseId. Keep only the latest (most complete) one per toolUseId.
|
|
540
|
-
*/
|
|
541
|
-
deduplicateToolUse(entries) {
|
|
542
|
-
const latestIdx = new Map();
|
|
543
|
-
for (let i = 0; i < entries.length; i++) {
|
|
544
|
-
const e = entries[i];
|
|
545
|
-
if (e.type === 'tool_use' && e.toolUseId) {
|
|
546
|
-
latestIdx.set(e.toolUseId, i);
|
|
547
|
-
}
|
|
548
|
-
}
|
|
549
|
-
return entries.filter((e, i) => {
|
|
550
|
-
if (e.type === 'tool_use' && e.toolUseId) {
|
|
551
|
-
return latestIdx.get(e.toolUseId) === i;
|
|
552
|
-
}
|
|
553
|
-
return true;
|
|
554
|
-
});
|
|
555
|
-
}
|
|
556
|
-
/** Apply truncation to a TaskLogEntry (task.service only) */
|
|
557
|
-
truncateEntry(entry) {
|
|
558
|
-
const result = { ...entry };
|
|
559
|
-
if (result.content)
|
|
560
|
-
result.content = this.truncate(result.content, MAX_LOG_ENTRY_FIELD_LEN);
|
|
561
|
-
if (result.toolInput)
|
|
562
|
-
result.toolInput = this.truncate(result.toolInput, MAX_LOG_ENTRY_FIELD_LEN);
|
|
563
|
-
if (result.resultPreview)
|
|
564
|
-
result.resultPreview = this.truncate(result.resultPreview, MAX_LOG_ENTRY_FIELD_LEN);
|
|
565
|
-
if (result.errorMessage)
|
|
566
|
-
result.errorMessage = this.truncate(result.errorMessage, MAX_LOG_ENTRY_FIELD_LEN);
|
|
567
|
-
return result;
|
|
568
|
-
}
|
|
569
|
-
// --- TSK.GET logs enrichment ---
|
|
570
|
-
enrichMessagesWithLogs(data, workspaceId) {
|
|
571
|
-
const threadId = data.threadId;
|
|
572
|
-
// Collect matching task rows from ALL available dbs
|
|
573
|
-
let taskRows = this.taskRepo.findTasksByThread(threadId, workspaceId);
|
|
574
|
-
// Fallback: if no matches by thread name, try matching by prompt text overlap
|
|
575
|
-
if (taskRows.length === 0 && data.messages.length > 0) {
|
|
576
|
-
const firstUserMsg = data.messages.find((m) => !m.isAssistant);
|
|
577
|
-
if (firstUserMsg) {
|
|
578
|
-
const hint = firstUserMsg.text.slice(0, 60).replace(/[%_]/g, '');
|
|
579
|
-
if (hint.length >= 10) {
|
|
580
|
-
taskRows = this.taskRepo.findTasksByPromptHint(hint, workspaceId);
|
|
581
|
-
}
|
|
582
|
-
}
|
|
583
|
-
}
|
|
584
|
-
// Sort combined rows by started_at ascending
|
|
585
|
-
taskRows.sort((a, b) => new Date(a.started_at).getTime() - new Date(b.started_at).getTime());
|
|
586
|
-
if (taskRows.length === 0) {
|
|
587
|
-
return data.messages;
|
|
588
|
-
}
|
|
589
|
-
// Build time windows: each user message starts a "turn", the next user message ends it
|
|
590
|
-
const turns = this.buildTurns(data.messages);
|
|
591
|
-
const enriched = [...data.messages];
|
|
592
|
-
for (let i = 0; i < turns.length; i++) {
|
|
593
|
-
const turn = turns[i];
|
|
594
|
-
if (turn.assistantIdx === -1)
|
|
595
|
-
continue;
|
|
596
|
-
// Find tasks whose started_at is within [turn.start, turn.end)
|
|
597
|
-
const matchingTasks = taskRows.filter((t) => {
|
|
598
|
-
const startMs = new Date(t.started_at).getTime();
|
|
599
|
-
return startMs >= turn.startMs && startMs < turn.endMs;
|
|
600
|
-
});
|
|
601
|
-
if (matchingTasks.length === 0)
|
|
602
|
-
continue;
|
|
603
|
-
// Merge logs from all matching tasks
|
|
604
|
-
const allLogs = [];
|
|
605
|
-
for (const task of matchingTasks) {
|
|
606
|
-
const parsed = this.parseLogs(task.logs, 100, this.extractAdapterKey(task));
|
|
607
|
-
allLogs.push(...parsed);
|
|
608
|
-
}
|
|
609
|
-
// Sort by timestamp
|
|
610
|
-
allLogs.sort((a, b) => new Date(a.timestamp).getTime() - new Date(b.timestamp).getTime());
|
|
611
|
-
enriched[turn.assistantIdx] = {
|
|
612
|
-
...enriched[turn.assistantIdx],
|
|
613
|
-
logs: allLogs,
|
|
614
|
-
};
|
|
615
|
-
}
|
|
616
|
-
return enriched;
|
|
617
|
-
}
|
|
618
|
-
buildTurns(messages) {
|
|
619
|
-
const turns = [];
|
|
620
|
-
for (let i = 0; i < messages.length; i++) {
|
|
621
|
-
const msg = messages[i];
|
|
622
|
-
if (msg.isAssistant)
|
|
623
|
-
continue;
|
|
624
|
-
// This is a user message — find the assistant message that follows
|
|
625
|
-
let assistantIdx = -1;
|
|
626
|
-
for (let j = i + 1; j < messages.length; j++) {
|
|
627
|
-
if (messages[j].isAssistant) {
|
|
628
|
-
assistantIdx = j;
|
|
629
|
-
break;
|
|
630
|
-
}
|
|
631
|
-
}
|
|
632
|
-
const startMs = new Date(msg.timestamp).getTime();
|
|
633
|
-
// End at the next user message, or far future
|
|
634
|
-
let endMs = Number.MAX_SAFE_INTEGER;
|
|
635
|
-
for (let j = i + 1; j < messages.length; j++) {
|
|
636
|
-
if (!messages[j].isAssistant) {
|
|
637
|
-
endMs = new Date(messages[j].timestamp).getTime();
|
|
638
|
-
break;
|
|
639
|
-
}
|
|
640
|
-
}
|
|
641
|
-
turns.push({ startMs, endMs, assistantIdx });
|
|
642
|
-
}
|
|
643
|
-
return turns;
|
|
644
|
-
}
|
|
645
|
-
// --- Private helpers ---
|
|
646
|
-
resolveTasksPath(project) {
|
|
647
|
-
const base = project || (0, workspace_context_store_js_1.getWorkspacePath)();
|
|
648
|
-
const resolved = (0, path_1.resolve)(base);
|
|
649
|
-
// Prevent path traversal
|
|
650
|
-
if (project && project.includes('..')) {
|
|
651
|
-
throw new common_1.BadRequestException('Invalid project path');
|
|
652
|
-
}
|
|
653
|
-
return (0, path_1.join)(resolved, '.crewx', 'conversations');
|
|
654
|
-
}
|
|
655
|
-
validateTaskId(taskId) {
|
|
656
|
-
// Only allow safe filename characters
|
|
657
|
-
if (!/^[a-zA-Z0-9._-]+$/.test(taskId)) {
|
|
658
|
-
throw new common_1.BadRequestException('Invalid taskId format');
|
|
659
|
-
}
|
|
660
|
-
}
|
|
661
|
-
async listJsonFiles(dir) {
|
|
662
|
-
try {
|
|
663
|
-
const entries = await (0, promises_1.readdir)(dir);
|
|
664
|
-
return entries.filter((f) => f.endsWith('.json'));
|
|
665
|
-
}
|
|
666
|
-
catch {
|
|
667
|
-
return [];
|
|
668
|
-
}
|
|
669
|
-
}
|
|
670
|
-
async readTaskFile(filePath) {
|
|
671
|
-
const content = await (0, promises_1.readFile)(filePath, 'utf-8');
|
|
672
|
-
const data = JSON.parse(content);
|
|
673
|
-
// Fallback: derive threadId from filename if not present
|
|
674
|
-
if (!data.threadId) {
|
|
675
|
-
data.threadId = (0, path_1.basename)(filePath, '.json');
|
|
676
|
-
}
|
|
677
|
-
return data;
|
|
678
|
-
}
|
|
679
|
-
toSummary(data) {
|
|
680
|
-
const agents = new Set();
|
|
681
|
-
const users = new Set();
|
|
682
|
-
for (const msg of data.messages) {
|
|
683
|
-
if (msg.isAssistant && msg.metadata?.agent_id) {
|
|
684
|
-
agents.add(msg.metadata.agent_id);
|
|
685
|
-
}
|
|
686
|
-
if (!msg.isAssistant) {
|
|
687
|
-
users.add(msg.userId);
|
|
688
|
-
}
|
|
689
|
-
}
|
|
690
|
-
const firstMsg = data.messages[0]?.text || '';
|
|
691
|
-
const lastMsg = data.messages[data.messages.length - 1]?.text || '';
|
|
692
|
-
return {
|
|
693
|
-
taskId: data.threadId,
|
|
694
|
-
platform: data.platform,
|
|
695
|
-
messageCount: data.metadata?.messageCount ?? data.messages.length,
|
|
696
|
-
agents: Array.from(agents),
|
|
697
|
-
users: Array.from(users),
|
|
698
|
-
firstMessage: this.truncate(firstMsg, 100),
|
|
699
|
-
lastMessage: this.truncate(lastMsg, 100),
|
|
700
|
-
createdAt: data.metadata?.createdAt || data.messages[0]?.timestamp || '',
|
|
701
|
-
updatedAt: data.metadata?.updatedAt || data.messages[data.messages.length - 1]?.timestamp || '',
|
|
702
|
-
};
|
|
703
|
-
}
|
|
704
|
-
truncate(text, maxLen) {
|
|
705
|
-
if (text.length <= maxLen)
|
|
706
|
-
return text;
|
|
707
|
-
return text.slice(0, maxLen) + '...';
|
|
708
|
-
}
|
|
709
|
-
normalizeLimit(limit, fallback, max) {
|
|
710
|
-
const num = Number(limit);
|
|
711
|
-
if (!Number.isFinite(num) || num <= 0)
|
|
712
|
-
return fallback;
|
|
713
|
-
return Math.min(Math.floor(num), max);
|
|
714
|
-
}
|
|
715
|
-
normalizeOffset(offset) {
|
|
716
|
-
const num = Number(offset);
|
|
717
|
-
if (!Number.isFinite(num) || num < 0)
|
|
718
|
-
return 0;
|
|
719
|
-
return Math.floor(num);
|
|
720
|
-
}
|
|
721
|
-
highlightMatch(text, query) {
|
|
722
|
-
const escaped = query.replace(/[.*+?^${}()|[\]\\]/g, '\\$&');
|
|
723
|
-
const regex = new RegExp(`(${escaped})`, 'gi');
|
|
724
|
-
return text.replace(regex, '**$1**');
|
|
725
|
-
}
|
|
726
|
-
// TSK006: Get agent list from crewx.yaml
|
|
727
|
-
getAgents() {
|
|
728
|
-
const configPath = this.resolveCrewxConfigPath();
|
|
729
|
-
if (!configPath || !(0, fs_1.existsSync)(configPath)) {
|
|
730
|
-
return { agents: [] };
|
|
731
|
-
}
|
|
732
|
-
try {
|
|
733
|
-
const content = (0, fs_1.readFileSync)(configPath, 'utf-8');
|
|
734
|
-
const config = (0, js_yaml_1.load)(content);
|
|
735
|
-
const rawAgents = config?.agents;
|
|
736
|
-
if (!Array.isArray(rawAgents))
|
|
737
|
-
return { agents: [] };
|
|
738
|
-
const agents = rawAgents.map((a) => ({
|
|
739
|
-
id: String(a.id || ''),
|
|
740
|
-
name: String(a.name || a.id || ''),
|
|
741
|
-
role: String(a.role || ''),
|
|
742
|
-
team: String(a.team || ''),
|
|
743
|
-
description: String(a.description || ''),
|
|
744
|
-
}));
|
|
745
|
-
return { agents };
|
|
746
|
-
}
|
|
747
|
-
catch {
|
|
748
|
-
return { agents: [] };
|
|
749
|
-
}
|
|
750
|
-
}
|
|
751
|
-
resolveCrewxConfigPath() {
|
|
752
|
-
// Check CREWX_CONFIG env var first
|
|
753
|
-
const envPath = process.env.CREWX_CONFIG;
|
|
754
|
-
if (envPath && (0, fs_1.existsSync)(envPath))
|
|
755
|
-
return envPath;
|
|
756
|
-
// Fall back to project root crewx.yaml
|
|
757
|
-
const base = (0, workspace_context_store_js_1.getWorkspacePath)();
|
|
758
|
-
const candidates = [
|
|
759
|
-
(0, path_1.join)(base, 'crewx.yaml'),
|
|
760
|
-
(0, path_1.join)(base, 'crewx.yml'),
|
|
761
|
-
];
|
|
762
|
-
for (const p of candidates) {
|
|
763
|
-
if ((0, fs_1.existsSync)(p))
|
|
764
|
-
return p;
|
|
765
|
-
}
|
|
766
|
-
return null;
|
|
767
|
-
}
|
|
768
|
-
};
|
|
769
|
-
exports.TaskService = TaskService;
|
|
770
|
-
exports.TaskService = TaskService = TaskService_1 = __decorate([
|
|
771
|
-
(0, common_1.Injectable)(),
|
|
772
|
-
__metadata("design:paramtypes", [task_repository_js_1.TaskRepository,
|
|
773
|
-
crewx_pool_service_js_1.CrewxPoolService,
|
|
774
|
-
project_repository_js_1.ProjectRepository])
|
|
775
|
-
], TaskService);
|