crewx 0.9.0-rc.73 → 0.9.0-rc.74
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-server/common/validation.pipe.js +51 -0
- package/dist-server/domain/thread/dto/thread-label.dto.js +93 -0
- package/dist-server/domain/thread/dto/update-thread.dto.js +4 -4
- package/dist-server/domain/thread/thread-label.controller.js +90 -0
- package/dist-server/domain/thread/thread-label.service.js +238 -0
- package/dist-server/domain/thread/thread-title-common-code.backfill.js +50 -0
- package/dist-server/domain/thread/thread-title.backfill.js +33 -0
- package/dist-server/domain/thread/thread-title.types.js +22 -4
- package/dist-server/domain/thread/thread.module.js +4 -2
- package/dist-server/domain/thread/thread.service.js +64 -24
- package/dist-server/main.js +2 -1
- package/dist-server/repository/repository.module.js +2 -0
- package/dist-server/repository/thread-label.repository.js +79 -0
- package/dist-server/repository/thread.repository.js +104 -0
- package/dist-server/shared/thread-label.js +8 -0
- package/package.json +13 -13
- package/packages/cli/package.json +1 -1
|
@@ -0,0 +1,51 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.mapThreadLabelValidationError = mapThreadLabelValidationError;
|
|
4
|
+
exports.createServerValidationPipe = createServerValidationPipe;
|
|
5
|
+
const common_1 = require("@nestjs/common");
|
|
6
|
+
const THREAD_LABEL_DTO_NAMES = new Set([
|
|
7
|
+
'ThreadLabelListQueryDto',
|
|
8
|
+
'CreateThreadLabelDto',
|
|
9
|
+
'UpdateThreadLabelDto',
|
|
10
|
+
'ArchiveThreadLabelDto',
|
|
11
|
+
]);
|
|
12
|
+
const SERVER_VALIDATION_PIPE_OPTIONS = {
|
|
13
|
+
whitelist: true,
|
|
14
|
+
transform: true,
|
|
15
|
+
};
|
|
16
|
+
// Delegate non-thread-label errors to Nest's own factory so nested paths and envelopes stay unchanged.
|
|
17
|
+
const defaultExceptionFactory = new common_1.ValidationPipe(SERVER_VALIDATION_PIPE_OPTIONS).createExceptionFactory();
|
|
18
|
+
function isThreadLabelValidationError(error) {
|
|
19
|
+
const target = error.target;
|
|
20
|
+
if (!target || typeof target !== 'object')
|
|
21
|
+
return false;
|
|
22
|
+
const constructorName = target.constructor?.name;
|
|
23
|
+
return typeof constructorName === 'string' && THREAD_LABEL_DTO_NAMES.has(constructorName);
|
|
24
|
+
}
|
|
25
|
+
function hasConstraint(errors, properties, constraint) {
|
|
26
|
+
return errors.some((error) => isThreadLabelValidationError(error)
|
|
27
|
+
&& properties.includes(error.property)
|
|
28
|
+
&& Boolean(error.constraints?.[constraint]));
|
|
29
|
+
}
|
|
30
|
+
/** Translate only thread-label DTO type errors; other DTOs keep Nest's normal messages. */
|
|
31
|
+
function mapThreadLabelValidationError(errors) {
|
|
32
|
+
if (hasConstraint(errors, ['kind'], 'isString'))
|
|
33
|
+
return 'THREAD_LABEL_KIND_INVALID';
|
|
34
|
+
if (hasConstraint(errors, ['name', 'nameOverride'], 'isString'))
|
|
35
|
+
return 'THREAD_LABEL_INVALID_NAME';
|
|
36
|
+
if (hasConstraint(errors, ['color'], 'isString'))
|
|
37
|
+
return 'THREAD_LABEL_INVALID_COLOR';
|
|
38
|
+
return undefined;
|
|
39
|
+
}
|
|
40
|
+
/** The application-wide pipe, including stable thread-label validation codes. */
|
|
41
|
+
function createServerValidationPipe() {
|
|
42
|
+
return new common_1.ValidationPipe({
|
|
43
|
+
...SERVER_VALIDATION_PIPE_OPTIONS,
|
|
44
|
+
exceptionFactory: (errors) => {
|
|
45
|
+
const machineCode = mapThreadLabelValidationError(errors);
|
|
46
|
+
return machineCode
|
|
47
|
+
? new common_1.BadRequestException(machineCode)
|
|
48
|
+
: defaultExceptionFactory(errors);
|
|
49
|
+
},
|
|
50
|
+
});
|
|
51
|
+
}
|
|
@@ -0,0 +1,93 @@
|
|
|
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
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
12
|
+
exports.ArchiveThreadLabelDto = exports.UpdateThreadLabelDto = exports.CreateThreadLabelDto = exports.ThreadLabelListQueryDto = void 0;
|
|
13
|
+
const swagger_1 = require("@nestjs/swagger");
|
|
14
|
+
const class_transformer_1 = require("class-transformer");
|
|
15
|
+
const class_validator_1 = require("class-validator");
|
|
16
|
+
const repository_1 = require("@crewx/sdk/repository");
|
|
17
|
+
class ThreadLabelListQueryDto {
|
|
18
|
+
kind;
|
|
19
|
+
includeArchived;
|
|
20
|
+
}
|
|
21
|
+
exports.ThreadLabelListQueryDto = ThreadLabelListQueryDto;
|
|
22
|
+
__decorate([
|
|
23
|
+
(0, swagger_1.ApiProperty)({ enum: ['state', 'priority'] }),
|
|
24
|
+
(0, class_validator_1.IsString)(),
|
|
25
|
+
__metadata("design:type", String)
|
|
26
|
+
], ThreadLabelListQueryDto.prototype, "kind", void 0);
|
|
27
|
+
__decorate([
|
|
28
|
+
(0, swagger_1.ApiPropertyOptional)({ default: false }),
|
|
29
|
+
(0, class_validator_1.IsOptional)(),
|
|
30
|
+
(0, class_transformer_1.Transform)(({ value }) => {
|
|
31
|
+
if (typeof value === 'boolean')
|
|
32
|
+
return value;
|
|
33
|
+
if (value === 'true')
|
|
34
|
+
return true;
|
|
35
|
+
if (value === 'false')
|
|
36
|
+
return false;
|
|
37
|
+
return value;
|
|
38
|
+
}),
|
|
39
|
+
(0, class_validator_1.IsBoolean)(),
|
|
40
|
+
__metadata("design:type", Boolean)
|
|
41
|
+
], ThreadLabelListQueryDto.prototype, "includeArchived", void 0);
|
|
42
|
+
class CreateThreadLabelDto {
|
|
43
|
+
name;
|
|
44
|
+
color;
|
|
45
|
+
}
|
|
46
|
+
exports.CreateThreadLabelDto = CreateThreadLabelDto;
|
|
47
|
+
__decorate([
|
|
48
|
+
(0, swagger_1.ApiProperty)(),
|
|
49
|
+
(0, class_validator_1.IsOptional)(),
|
|
50
|
+
(0, class_validator_1.IsString)(),
|
|
51
|
+
__metadata("design:type", String)
|
|
52
|
+
], CreateThreadLabelDto.prototype, "name", void 0);
|
|
53
|
+
__decorate([
|
|
54
|
+
(0, swagger_1.ApiProperty)({ enum: repository_1.COMMON_CODE_COLORS }),
|
|
55
|
+
(0, class_validator_1.IsOptional)(),
|
|
56
|
+
(0, class_validator_1.IsString)(),
|
|
57
|
+
__metadata("design:type", String)
|
|
58
|
+
], CreateThreadLabelDto.prototype, "color", void 0);
|
|
59
|
+
class UpdateThreadLabelDto {
|
|
60
|
+
nameOverride;
|
|
61
|
+
color;
|
|
62
|
+
}
|
|
63
|
+
exports.UpdateThreadLabelDto = UpdateThreadLabelDto;
|
|
64
|
+
__decorate([
|
|
65
|
+
(0, swagger_1.ApiPropertyOptional)({ nullable: true }),
|
|
66
|
+
(0, class_validator_1.IsOptional)(),
|
|
67
|
+
(0, class_validator_1.IsString)(),
|
|
68
|
+
__metadata("design:type", Object)
|
|
69
|
+
], UpdateThreadLabelDto.prototype, "nameOverride", void 0);
|
|
70
|
+
__decorate([
|
|
71
|
+
(0, swagger_1.ApiPropertyOptional)({ enum: repository_1.COMMON_CODE_COLORS }),
|
|
72
|
+
(0, class_validator_1.IsOptional)(),
|
|
73
|
+
(0, class_validator_1.IsString)(),
|
|
74
|
+
__metadata("design:type", String)
|
|
75
|
+
], UpdateThreadLabelDto.prototype, "color", void 0);
|
|
76
|
+
class ArchiveThreadLabelDto {
|
|
77
|
+
archived;
|
|
78
|
+
}
|
|
79
|
+
exports.ArchiveThreadLabelDto = ArchiveThreadLabelDto;
|
|
80
|
+
__decorate([
|
|
81
|
+
(0, swagger_1.ApiProperty)(),
|
|
82
|
+
(0, class_transformer_1.Transform)(({ value }) => {
|
|
83
|
+
if (typeof value === 'boolean')
|
|
84
|
+
return value;
|
|
85
|
+
if (value === 'true')
|
|
86
|
+
return true;
|
|
87
|
+
if (value === 'false')
|
|
88
|
+
return false;
|
|
89
|
+
return value;
|
|
90
|
+
}),
|
|
91
|
+
(0, class_validator_1.IsBoolean)(),
|
|
92
|
+
__metadata("design:type", Boolean)
|
|
93
|
+
], ArchiveThreadLabelDto.prototype, "archived", void 0);
|
|
@@ -21,10 +21,10 @@ function IsThreadStateValue() {
|
|
|
21
21
|
propertyName,
|
|
22
22
|
validator: {
|
|
23
23
|
validate(value) {
|
|
24
|
-
return (0, thread_title_types_js_1.isThreadState)(value);
|
|
24
|
+
return (0, thread_title_types_js_1.isThreadState)(value) && !(typeof value === 'string' && value.startsWith('custom:'));
|
|
25
25
|
},
|
|
26
26
|
defaultMessage() {
|
|
27
|
-
return 'state must be a
|
|
27
|
+
return 'state must be a stable thread-state code';
|
|
28
28
|
},
|
|
29
29
|
},
|
|
30
30
|
});
|
|
@@ -37,7 +37,7 @@ class UpdateThreadDto {
|
|
|
37
37
|
}
|
|
38
38
|
exports.UpdateThreadDto = UpdateThreadDto;
|
|
39
39
|
__decorate([
|
|
40
|
-
(0, swagger_1.ApiPropertyOptional)({ description: 'Thread name
|
|
40
|
+
(0, swagger_1.ApiPropertyOptional)({ description: 'Thread name (1–120 chars). Legacy composite titles are parsed only when exact aliases match.' }),
|
|
41
41
|
(0, class_validator_1.ValidateIf)((_obj, value) => value !== undefined),
|
|
42
42
|
(0, class_validator_1.IsString)(),
|
|
43
43
|
(0, class_validator_1.MinLength)(1),
|
|
@@ -57,7 +57,7 @@ __decorate([
|
|
|
57
57
|
], UpdateThreadDto.prototype, "priority", void 0);
|
|
58
58
|
__decorate([
|
|
59
59
|
(0, swagger_1.ApiPropertyOptional)({
|
|
60
|
-
description: 'Thread progress
|
|
60
|
+
description: 'Thread progress code. null clears the stored state.',
|
|
61
61
|
nullable: true,
|
|
62
62
|
}),
|
|
63
63
|
(0, class_validator_1.ValidateIf)((_obj, value) => value !== undefined && value !== null),
|
|
@@ -0,0 +1,90 @@
|
|
|
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 __param = (this && this.__param) || function (paramIndex, decorator) {
|
|
12
|
+
return function (target, key) { decorator(target, key, paramIndex); }
|
|
13
|
+
};
|
|
14
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
15
|
+
exports.ThreadLabelController = void 0;
|
|
16
|
+
const common_1 = require("@nestjs/common");
|
|
17
|
+
const swagger_1 = require("@nestjs/swagger");
|
|
18
|
+
const workspace_decorator_js_1 = require("../../common/decorators/workspace.decorator.js");
|
|
19
|
+
const thread_label_dto_js_1 = require("./dto/thread-label.dto.js");
|
|
20
|
+
const thread_label_service_js_1 = require("./thread-label.service.js");
|
|
21
|
+
let ThreadLabelController = class ThreadLabelController {
|
|
22
|
+
service;
|
|
23
|
+
constructor(service) {
|
|
24
|
+
this.service = service;
|
|
25
|
+
}
|
|
26
|
+
list(query, ws) {
|
|
27
|
+
return this.service.list(query.kind, ws.id, query.includeArchived ?? false);
|
|
28
|
+
}
|
|
29
|
+
create(kind, dto, ws) {
|
|
30
|
+
return { item: this.service.create(kind, ws.id, dto) };
|
|
31
|
+
}
|
|
32
|
+
update(kind, code, dto, ws) {
|
|
33
|
+
return { item: this.service.update(kind, code, ws.id, dto) };
|
|
34
|
+
}
|
|
35
|
+
archive(kind, code, dto, ws) {
|
|
36
|
+
return { item: this.service.archive(kind, code, ws.id, dto.archived) };
|
|
37
|
+
}
|
|
38
|
+
};
|
|
39
|
+
exports.ThreadLabelController = ThreadLabelController;
|
|
40
|
+
__decorate([
|
|
41
|
+
(0, swagger_1.ApiOperation)({ operationId: 'THD.LABEL.LIST', summary: 'List conversation state or priority labels' }),
|
|
42
|
+
(0, swagger_1.ApiQuery)({ name: 'kind', required: true, enum: ['state', 'priority'] }),
|
|
43
|
+
(0, swagger_1.ApiQuery)({ name: 'includeArchived', required: false, type: Boolean }),
|
|
44
|
+
(0, common_1.Get)(),
|
|
45
|
+
__param(0, (0, common_1.Query)()),
|
|
46
|
+
__param(1, (0, workspace_decorator_js_1.Workspace)()),
|
|
47
|
+
__metadata("design:type", Function),
|
|
48
|
+
__metadata("design:paramtypes", [thread_label_dto_js_1.ThreadLabelListQueryDto, Object]),
|
|
49
|
+
__metadata("design:returntype", void 0)
|
|
50
|
+
], ThreadLabelController.prototype, "list", null);
|
|
51
|
+
__decorate([
|
|
52
|
+
(0, swagger_1.ApiOperation)({ operationId: 'THD.LABEL.CREATE', summary: 'Create a conversation state label' }),
|
|
53
|
+
(0, common_1.Post)(':kind'),
|
|
54
|
+
(0, common_1.HttpCode)(common_1.HttpStatus.CREATED),
|
|
55
|
+
__param(0, (0, common_1.Param)('kind')),
|
|
56
|
+
__param(1, (0, common_1.Body)()),
|
|
57
|
+
__param(2, (0, workspace_decorator_js_1.Workspace)()),
|
|
58
|
+
__metadata("design:type", Function),
|
|
59
|
+
__metadata("design:paramtypes", [String, thread_label_dto_js_1.CreateThreadLabelDto, Object]),
|
|
60
|
+
__metadata("design:returntype", void 0)
|
|
61
|
+
], ThreadLabelController.prototype, "create", null);
|
|
62
|
+
__decorate([
|
|
63
|
+
(0, swagger_1.ApiOperation)({ operationId: 'THD.LABEL.UPDATE', summary: 'Update a conversation label display' }),
|
|
64
|
+
(0, common_1.Patch)(':kind/:code'),
|
|
65
|
+
(0, common_1.HttpCode)(common_1.HttpStatus.OK),
|
|
66
|
+
__param(0, (0, common_1.Param)('kind')),
|
|
67
|
+
__param(1, (0, common_1.Param)('code')),
|
|
68
|
+
__param(2, (0, common_1.Body)()),
|
|
69
|
+
__param(3, (0, workspace_decorator_js_1.Workspace)()),
|
|
70
|
+
__metadata("design:type", Function),
|
|
71
|
+
__metadata("design:paramtypes", [String, String, thread_label_dto_js_1.UpdateThreadLabelDto, Object]),
|
|
72
|
+
__metadata("design:returntype", void 0)
|
|
73
|
+
], ThreadLabelController.prototype, "update", null);
|
|
74
|
+
__decorate([
|
|
75
|
+
(0, swagger_1.ApiOperation)({ operationId: 'THD.LABEL.ARCHIVE', summary: 'Archive or restore a conversation label' }),
|
|
76
|
+
(0, common_1.Post)(':kind/:code/archive'),
|
|
77
|
+
(0, common_1.HttpCode)(common_1.HttpStatus.OK),
|
|
78
|
+
__param(0, (0, common_1.Param)('kind')),
|
|
79
|
+
__param(1, (0, common_1.Param)('code')),
|
|
80
|
+
__param(2, (0, common_1.Body)()),
|
|
81
|
+
__param(3, (0, workspace_decorator_js_1.Workspace)()),
|
|
82
|
+
__metadata("design:type", Function),
|
|
83
|
+
__metadata("design:paramtypes", [String, String, thread_label_dto_js_1.ArchiveThreadLabelDto, Object]),
|
|
84
|
+
__metadata("design:returntype", void 0)
|
|
85
|
+
], ThreadLabelController.prototype, "archive", null);
|
|
86
|
+
exports.ThreadLabelController = ThreadLabelController = __decorate([
|
|
87
|
+
(0, swagger_1.ApiTags)('thread-labels'),
|
|
88
|
+
(0, common_1.Controller)('ws/:slug/thread-labels'),
|
|
89
|
+
__metadata("design:paramtypes", [thread_label_service_js_1.ThreadLabelService])
|
|
90
|
+
], ThreadLabelController);
|
|
@@ -0,0 +1,238 @@
|
|
|
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
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
12
|
+
exports.ThreadLabelService = void 0;
|
|
13
|
+
exports.parseThreadLabelKind = parseThreadLabelKind;
|
|
14
|
+
const common_1 = require("@nestjs/common");
|
|
15
|
+
const repository_1 = require("@crewx/sdk/repository");
|
|
16
|
+
const thread_label_repository_js_1 = require("../../repository/thread-label.repository.js");
|
|
17
|
+
const PRIORITY_DEFAULT_ALIASES = {
|
|
18
|
+
urgent: 'U 긴급 Urgent',
|
|
19
|
+
high: 'P0 급함 High',
|
|
20
|
+
medium: 'P1 중요 Medium',
|
|
21
|
+
low: 'P2 보통 Low',
|
|
22
|
+
};
|
|
23
|
+
const STATE_DEFAULT_ALIASES = {
|
|
24
|
+
investigating: '조사중 Looking into',
|
|
25
|
+
'in-progress': '진행중 In progress',
|
|
26
|
+
'in-review': '검토중 In review',
|
|
27
|
+
'on-hold': '보류 On hold',
|
|
28
|
+
done: '완료 Done',
|
|
29
|
+
};
|
|
30
|
+
const DEFAULT_NAME_KEYS = {
|
|
31
|
+
urgent: 'threadTitle.priority.urgent',
|
|
32
|
+
high: 'threadTitle.priority.high',
|
|
33
|
+
medium: 'threadTitle.priority.medium',
|
|
34
|
+
low: 'threadTitle.priority.low',
|
|
35
|
+
investigating: 'threadTitle.state.investigating',
|
|
36
|
+
'in-progress': 'threadTitle.state.in-progress',
|
|
37
|
+
'in-review': 'threadTitle.state.in-review',
|
|
38
|
+
'on-hold': 'threadTitle.state.on-hold',
|
|
39
|
+
done: 'threadTitle.state.done',
|
|
40
|
+
};
|
|
41
|
+
function appendSearchAlias(existing, name) {
|
|
42
|
+
const normalized = name.trim();
|
|
43
|
+
if (!existing)
|
|
44
|
+
return normalized;
|
|
45
|
+
if (!normalized || existing.includes(normalized))
|
|
46
|
+
return existing;
|
|
47
|
+
return `${existing} ${normalized}`;
|
|
48
|
+
}
|
|
49
|
+
function parseThreadLabelKind(value) {
|
|
50
|
+
if (value === 'state' || value === 'priority')
|
|
51
|
+
return value;
|
|
52
|
+
throw new common_1.BadRequestException('THREAD_LABEL_KIND_INVALID');
|
|
53
|
+
}
|
|
54
|
+
function isThreadLabelColor(value) {
|
|
55
|
+
return typeof value === 'string' && repository_1.COMMON_CODE_COLORS.includes(value);
|
|
56
|
+
}
|
|
57
|
+
function normalizeName(value) {
|
|
58
|
+
const name = typeof value === 'string' ? value.trim() : '';
|
|
59
|
+
const length = [...name].length;
|
|
60
|
+
if (length < 1 || length > 20) {
|
|
61
|
+
throw new common_1.BadRequestException('THREAD_LABEL_INVALID_NAME');
|
|
62
|
+
}
|
|
63
|
+
return name;
|
|
64
|
+
}
|
|
65
|
+
function normalizeColor(value) {
|
|
66
|
+
if (!isThreadLabelColor(value)) {
|
|
67
|
+
throw new common_1.BadRequestException('THREAD_LABEL_INVALID_COLOR');
|
|
68
|
+
}
|
|
69
|
+
return value;
|
|
70
|
+
}
|
|
71
|
+
function mapDefaultNameKey(row) {
|
|
72
|
+
return row.is_builtin === 1 ? DEFAULT_NAME_KEYS[row.code] ?? null : null;
|
|
73
|
+
}
|
|
74
|
+
function mapItem(kind, row) {
|
|
75
|
+
const color = isThreadLabelColor(row.color) ? row.color : 'gray';
|
|
76
|
+
return {
|
|
77
|
+
group: kind,
|
|
78
|
+
code: row.code,
|
|
79
|
+
nameOverride: row.code_name,
|
|
80
|
+
nameSource: row.code_name === null ? 'default' : 'user',
|
|
81
|
+
defaultNameKey: mapDefaultNameKey(row),
|
|
82
|
+
color,
|
|
83
|
+
sortOrder: row.sort_order,
|
|
84
|
+
scope: row.workspace_id === null ? 'global' : 'workspace',
|
|
85
|
+
isBuiltin: row.is_builtin === 1,
|
|
86
|
+
isArchived: row.is_archived === 1,
|
|
87
|
+
};
|
|
88
|
+
}
|
|
89
|
+
function mapRepositoryError(error) {
|
|
90
|
+
if (!(error instanceof repository_1.CommonCodeRepositoryError))
|
|
91
|
+
throw error;
|
|
92
|
+
switch (error.reason) {
|
|
93
|
+
case 'COMMON_CODE_NAME_INVALID':
|
|
94
|
+
case 'COMMON_CODE_UPDATE_EMPTY':
|
|
95
|
+
throw new common_1.BadRequestException('THREAD_LABEL_INVALID_NAME');
|
|
96
|
+
case 'COMMON_CODE_COLOR_INVALID':
|
|
97
|
+
throw new common_1.BadRequestException('THREAD_LABEL_INVALID_COLOR');
|
|
98
|
+
case 'COMMON_CODE_BUILTIN_ARCHIVE_FORBIDDEN':
|
|
99
|
+
throw new common_1.BadRequestException('THREAD_LABEL_BUILTIN_ARCHIVE_FORBIDDEN');
|
|
100
|
+
case 'COMMON_CODE_NAME_CONFLICT':
|
|
101
|
+
throw new common_1.ConflictException('THREAD_LABEL_NAME_CONFLICT');
|
|
102
|
+
case 'COMMON_CODE_NOT_FOUND':
|
|
103
|
+
case 'COMMON_CODE_SCOPE_FORBIDDEN':
|
|
104
|
+
throw new common_1.NotFoundException('THREAD_LABEL_NOT_FOUND');
|
|
105
|
+
default:
|
|
106
|
+
throw error;
|
|
107
|
+
}
|
|
108
|
+
}
|
|
109
|
+
/** Feature-level service that translates SDK ledger rows to the thread-label wire contract. */
|
|
110
|
+
let ThreadLabelService = class ThreadLabelService {
|
|
111
|
+
labels;
|
|
112
|
+
constructor(labels = new thread_label_repository_js_1.ThreadLabelRepository()) {
|
|
113
|
+
this.labels = labels;
|
|
114
|
+
}
|
|
115
|
+
list(kindValue, workspaceId, includeArchived = false) {
|
|
116
|
+
const kind = parseThreadLabelKind(kindValue);
|
|
117
|
+
try {
|
|
118
|
+
return { items: this.labels.list(kind, workspaceId, includeArchived).map((row) => mapItem(kind, row)) };
|
|
119
|
+
}
|
|
120
|
+
catch (error) {
|
|
121
|
+
return mapRepositoryError(error);
|
|
122
|
+
}
|
|
123
|
+
}
|
|
124
|
+
create(kindValue, workspaceId, input) {
|
|
125
|
+
const kind = parseThreadLabelKind(kindValue);
|
|
126
|
+
if (kind === 'priority')
|
|
127
|
+
throw new common_1.BadRequestException('THREAD_LABEL_KIND_READ_ONLY');
|
|
128
|
+
const name = normalizeName(input.name);
|
|
129
|
+
const color = normalizeColor(input.color);
|
|
130
|
+
try {
|
|
131
|
+
return mapItem(kind, this.labels.createState(workspaceId, name, color));
|
|
132
|
+
}
|
|
133
|
+
catch (error) {
|
|
134
|
+
return mapRepositoryError(error);
|
|
135
|
+
}
|
|
136
|
+
}
|
|
137
|
+
update(kindValue, code, workspaceId, input) {
|
|
138
|
+
const kind = parseThreadLabelKind(kindValue);
|
|
139
|
+
const update = {};
|
|
140
|
+
if (input.nameOverride !== undefined) {
|
|
141
|
+
if (input.nameOverride === null)
|
|
142
|
+
update.name = null;
|
|
143
|
+
else
|
|
144
|
+
update.name = normalizeName(input.nameOverride);
|
|
145
|
+
}
|
|
146
|
+
if (input.color !== undefined)
|
|
147
|
+
update.color = normalizeColor(input.color);
|
|
148
|
+
try {
|
|
149
|
+
return mapItem(kind, this.labels.update(kind, code, workspaceId, update));
|
|
150
|
+
}
|
|
151
|
+
catch (error) {
|
|
152
|
+
return mapRepositoryError(error);
|
|
153
|
+
}
|
|
154
|
+
}
|
|
155
|
+
archive(kindValue, code, workspaceId, archivedValue) {
|
|
156
|
+
const kind = parseThreadLabelKind(kindValue);
|
|
157
|
+
if (typeof archivedValue !== 'boolean')
|
|
158
|
+
throw new common_1.BadRequestException('THREAD_LABEL_INVALID_NAME');
|
|
159
|
+
try {
|
|
160
|
+
return mapItem(kind, this.labels.archive(kind, code, workspaceId, archivedValue));
|
|
161
|
+
}
|
|
162
|
+
catch (error) {
|
|
163
|
+
return mapRepositoryError(error);
|
|
164
|
+
}
|
|
165
|
+
}
|
|
166
|
+
/** Validate a new metadata reference; archived codes are read-only history. */
|
|
167
|
+
assertSelectable(kindValue, code, workspaceId) {
|
|
168
|
+
const kind = parseThreadLabelKind(kindValue);
|
|
169
|
+
const row = this.labels.resolve(kind, code, workspaceId);
|
|
170
|
+
if (!row)
|
|
171
|
+
throw new common_1.NotFoundException('THREAD_LABEL_NOT_FOUND');
|
|
172
|
+
if (row.is_archived === 1)
|
|
173
|
+
throw new common_1.ConflictException('THREAD_LABEL_ARCHIVED');
|
|
174
|
+
return row;
|
|
175
|
+
}
|
|
176
|
+
resolve(kindValue, code, workspaceId) {
|
|
177
|
+
const kind = parseThreadLabelKind(kindValue);
|
|
178
|
+
return this.labels.resolve(kind, code, workspaceId);
|
|
179
|
+
}
|
|
180
|
+
/** Map exact legacy state suffix names to stable codes for compatibility parsing. */
|
|
181
|
+
stateAliases(workspaceId) {
|
|
182
|
+
const aliases = new Map();
|
|
183
|
+
for (const [code, names] of Object.entries(STATE_DEFAULT_ALIASES)) {
|
|
184
|
+
aliases.set(names.split(' ')[0], code);
|
|
185
|
+
aliases.set(names.split(' ').slice(1).join(' '), code);
|
|
186
|
+
}
|
|
187
|
+
try {
|
|
188
|
+
for (const row of this.labels.list('state', workspaceId, true)) {
|
|
189
|
+
if (row.code_name !== null)
|
|
190
|
+
aliases.set(row.code_name, row.code);
|
|
191
|
+
}
|
|
192
|
+
}
|
|
193
|
+
catch {
|
|
194
|
+
// Search/backfill remains available for builtin aliases if the ledger is unavailable.
|
|
195
|
+
}
|
|
196
|
+
return aliases;
|
|
197
|
+
}
|
|
198
|
+
/** Include stable codes, built-in KO/EN aliases, and current display names in q search. */
|
|
199
|
+
searchAliases(workspaceId) {
|
|
200
|
+
const aliases = new Map();
|
|
201
|
+
for (const [code, names] of Object.entries(PRIORITY_DEFAULT_ALIASES)) {
|
|
202
|
+
aliases.set(code, names);
|
|
203
|
+
}
|
|
204
|
+
for (const [code, names] of Object.entries(STATE_DEFAULT_ALIASES)) {
|
|
205
|
+
aliases.set(code, names);
|
|
206
|
+
}
|
|
207
|
+
try {
|
|
208
|
+
for (const kind of ['state', 'priority']) {
|
|
209
|
+
for (const row of this.labels.list(kind, workspaceId, true)) {
|
|
210
|
+
if (row.code_name !== null) {
|
|
211
|
+
aliases.set(row.code, appendSearchAlias(aliases.get(row.code), row.code_name));
|
|
212
|
+
}
|
|
213
|
+
}
|
|
214
|
+
}
|
|
215
|
+
}
|
|
216
|
+
catch {
|
|
217
|
+
// A missing optional ledger must not hide the raw thread code from reads.
|
|
218
|
+
}
|
|
219
|
+
return aliases;
|
|
220
|
+
}
|
|
221
|
+
/** Return a code for a strict legacy state name, including archived history. */
|
|
222
|
+
findStateCodeByName(name, workspaceId) {
|
|
223
|
+
const builtin = this.stateAliases(workspaceId).get(name);
|
|
224
|
+
if (builtin)
|
|
225
|
+
return builtin;
|
|
226
|
+
try {
|
|
227
|
+
return this.labels.findByName('state', name, workspaceId)?.code ?? null;
|
|
228
|
+
}
|
|
229
|
+
catch {
|
|
230
|
+
return null;
|
|
231
|
+
}
|
|
232
|
+
}
|
|
233
|
+
};
|
|
234
|
+
exports.ThreadLabelService = ThreadLabelService;
|
|
235
|
+
exports.ThreadLabelService = ThreadLabelService = __decorate([
|
|
236
|
+
(0, common_1.Injectable)(),
|
|
237
|
+
__metadata("design:paramtypes", [thread_label_repository_js_1.ThreadLabelRepository])
|
|
238
|
+
], ThreadLabelService);
|
|
@@ -0,0 +1,50 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.runThreadTitleCommonCodeBackfill = runThreadTitleCommonCodeBackfill;
|
|
4
|
+
const sdk_1 = require("@crewx/sdk");
|
|
5
|
+
const thread_title_types_js_1 = require("./thread-title.types.js");
|
|
6
|
+
const thread_title_backfill_js_1 = require("./thread-title.backfill.js");
|
|
7
|
+
/**
|
|
8
|
+
* Run the SDK data migration and then clean only strictly recognized composite
|
|
9
|
+
* titles. The settings value is read as migration input and never updated.
|
|
10
|
+
*/
|
|
11
|
+
function runThreadTitleCommonCodeBackfill(deps) {
|
|
12
|
+
const configuredLabels = (0, thread_title_backfill_js_1.readProgressExtras)(deps.globalSettingsStore);
|
|
13
|
+
const migration = (0, sdk_1.migrateLegacyThreadTitleCommonCodes)({ configuredLabels });
|
|
14
|
+
let compositeTitlesCleaned = 0;
|
|
15
|
+
const aliasesByWorkspace = new Map();
|
|
16
|
+
for (const row of deps.threadRepo.findAllThreads()) {
|
|
17
|
+
const workspaceKey = row.workspace_id ?? '';
|
|
18
|
+
let aliases = aliasesByWorkspace.get(workspaceKey);
|
|
19
|
+
if (!aliases) {
|
|
20
|
+
aliases = deps.threadLabelService.stateAliases(row.workspace_id ?? undefined);
|
|
21
|
+
aliasesByWorkspace.set(workspaceKey, aliases);
|
|
22
|
+
}
|
|
23
|
+
const parsed = (0, thread_title_backfill_js_1.parseCompositeThreadTitle)(row.title ?? '', aliases);
|
|
24
|
+
if (!parsed)
|
|
25
|
+
continue;
|
|
26
|
+
// Existing threadTitle metadata is authoritative. Only remove the stale
|
|
27
|
+
// string decoration from the title; do not infer or overwrite metadata.
|
|
28
|
+
if ((0, thread_title_types_js_1.hasThreadTitleKey)(row.metadata)) {
|
|
29
|
+
if (parsed.name !== (row.title ?? '')) {
|
|
30
|
+
deps.threadRepo.updateThreadTitle(row.id, parsed.name, row.workspace_id ?? undefined);
|
|
31
|
+
compositeTitlesCleaned += 1;
|
|
32
|
+
}
|
|
33
|
+
continue;
|
|
34
|
+
}
|
|
35
|
+
const written = deps.threadRepo.updateThreadTitleMeta(row.id, {
|
|
36
|
+
...(parsed.priority !== undefined ? { priority: parsed.priority } : {}),
|
|
37
|
+
...(parsed.state !== undefined ? { state: parsed.state } : {}),
|
|
38
|
+
}, row.workspace_id ?? undefined, { title: parsed.name });
|
|
39
|
+
if (written)
|
|
40
|
+
compositeTitlesCleaned += 1;
|
|
41
|
+
}
|
|
42
|
+
deps.logger?.log(`[thread-title] common-code migration: migratedRows=${migration.migratedRows}, ` +
|
|
43
|
+
`createdCodes=${migration.createdCodes}, remainingCustomReferences=${migration.remainingCustomReferences}, ` +
|
|
44
|
+
`orphanReferences=${migration.orphanReferences}, compositeTitlesCleaned=${compositeTitlesCleaned}`);
|
|
45
|
+
if (migration.remainingCustomReferences > 0 || migration.orphanReferences > 0) {
|
|
46
|
+
deps.logger?.warn(`[thread-title] common-code migration diagnostics: remaining custom/orphan references require attention ` +
|
|
47
|
+
`(custom=${migration.remainingCustomReferences}, orphan=${migration.orphanReferences})`);
|
|
48
|
+
}
|
|
49
|
+
return { ...migration, compositeTitlesCleaned };
|
|
50
|
+
}
|
|
@@ -3,6 +3,7 @@ Object.defineProperty(exports, "__esModule", { value: true });
|
|
|
3
3
|
exports.LEGACY_PROGRESS_LABELS = void 0;
|
|
4
4
|
exports.readProgressExtras = readProgressExtras;
|
|
5
5
|
exports.parseLegacyThreadTitle = parseLegacyThreadTitle;
|
|
6
|
+
exports.parseCompositeThreadTitle = parseCompositeThreadTitle;
|
|
6
7
|
exports.runThreadTitleBackfill = runThreadTitleBackfill;
|
|
7
8
|
const sdk_1 = require("@crewx/sdk");
|
|
8
9
|
const thread_title_types_js_1 = require("./thread-title.types.js");
|
|
@@ -20,6 +21,7 @@ const PREFIX_MAP = {
|
|
|
20
21
|
P2: 'low',
|
|
21
22
|
};
|
|
22
23
|
const PREFIX_RE = /^(U|P0|P1|P2) /;
|
|
24
|
+
const COMPOSITE_PREFIX_RE = /^(U|P0|P1|P2)(?= |$)/;
|
|
23
25
|
const TITLE_SEP = ' - ';
|
|
24
26
|
const BUILTIN_STATE_LABELS = {
|
|
25
27
|
조사중: 'investigating',
|
|
@@ -110,6 +112,37 @@ function parseLegacyThreadTitle(title, extraLabels) {
|
|
|
110
112
|
return null;
|
|
111
113
|
return { name, priority, state, matchedLegacyLabel };
|
|
112
114
|
}
|
|
115
|
+
/**
|
|
116
|
+
* Parse a legacy composite title against the already migrated state ledger.
|
|
117
|
+
* Unknown suffixes and empty post-split names return null so callers preserve
|
|
118
|
+
* the exact original title. This parser is compatibility-only; new requests
|
|
119
|
+
* send title, priority, and state separately.
|
|
120
|
+
*/
|
|
121
|
+
function parseCompositeThreadTitle(title, stateAliases) {
|
|
122
|
+
let rest = title;
|
|
123
|
+
let priority;
|
|
124
|
+
const prefix = rest.match(COMPOSITE_PREFIX_RE);
|
|
125
|
+
if (prefix) {
|
|
126
|
+
priority = PREFIX_MAP[prefix[1]];
|
|
127
|
+
rest = rest.slice(prefix[0].length);
|
|
128
|
+
}
|
|
129
|
+
let state;
|
|
130
|
+
const sepIdx = rest.lastIndexOf(TITLE_SEP);
|
|
131
|
+
if (sepIdx >= 0) {
|
|
132
|
+
const suffix = rest.slice(sepIdx + TITLE_SEP.length);
|
|
133
|
+
const parsedState = stateAliases.get(suffix);
|
|
134
|
+
if (!parsedState)
|
|
135
|
+
return null;
|
|
136
|
+
state = parsedState;
|
|
137
|
+
rest = rest.slice(0, sepIdx);
|
|
138
|
+
}
|
|
139
|
+
if (!priority && !state)
|
|
140
|
+
return null;
|
|
141
|
+
const name = rest.trim();
|
|
142
|
+
if (name.length === 0)
|
|
143
|
+
return null;
|
|
144
|
+
return { name, priority, state };
|
|
145
|
+
}
|
|
113
146
|
function appendProgressExtras(store, labels) {
|
|
114
147
|
if (labels.length === 0)
|
|
115
148
|
return [];
|
|
@@ -37,6 +37,11 @@ const STATE_SEARCH_TEXT = {
|
|
|
37
37
|
'on-hold': '보류 On hold',
|
|
38
38
|
done: '완료 Done',
|
|
39
39
|
};
|
|
40
|
+
function searchTextWithDefault(defaultText, currentText) {
|
|
41
|
+
if (!currentText || currentText.includes(defaultText))
|
|
42
|
+
return currentText ?? defaultText;
|
|
43
|
+
return `${defaultText} ${currentText}`;
|
|
44
|
+
}
|
|
40
45
|
function isThreadPriority(value) {
|
|
41
46
|
return typeof value === 'string' && PRIORITY_SET.has(value);
|
|
42
47
|
}
|
|
@@ -45,6 +50,8 @@ function isThreadState(value) {
|
|
|
45
50
|
return false;
|
|
46
51
|
if (BUILTIN_STATE_SET.has(value))
|
|
47
52
|
return true;
|
|
53
|
+
if (value.startsWith('c_'))
|
|
54
|
+
return value.length > 2;
|
|
48
55
|
if (!value.startsWith(CUSTOM_STATE_PREFIX))
|
|
49
56
|
return false;
|
|
50
57
|
const label = value.slice(CUSTOM_STATE_PREFIX.length);
|
|
@@ -119,16 +126,27 @@ function mergeThreadTitleMeta(metadataJson, patch) {
|
|
|
119
126
|
return Object.keys(root).length === 0 ? null : JSON.stringify(root);
|
|
120
127
|
}
|
|
121
128
|
/** Search haystack so `q=P1` / `q=진행중` still match after title is name-only. */
|
|
122
|
-
function threadTitleSearchHaystack(meta) {
|
|
129
|
+
function threadTitleSearchHaystack(meta, labelNames = new Map()) {
|
|
123
130
|
const parts = [];
|
|
124
|
-
if (meta.priority)
|
|
125
|
-
parts.push(PRIORITY_SEARCH_TEXT[meta.priority]);
|
|
131
|
+
if (meta.priority) {
|
|
132
|
+
parts.push(searchTextWithDefault(PRIORITY_SEARCH_TEXT[meta.priority], labelNames.get(meta.priority)));
|
|
133
|
+
}
|
|
126
134
|
if (meta.state) {
|
|
127
135
|
if (meta.state.startsWith(CUSTOM_STATE_PREFIX)) {
|
|
128
136
|
parts.push(customStateLabel(meta.state) ?? '');
|
|
129
137
|
}
|
|
138
|
+
else if (STATE_SEARCH_TEXT[meta.state]) {
|
|
139
|
+
parts.push(searchTextWithDefault(STATE_SEARCH_TEXT[meta.state], labelNames.get(meta.state)));
|
|
140
|
+
}
|
|
141
|
+
else if (labelNames.has(meta.state)) {
|
|
142
|
+
parts.push(meta.state, labelNames.get(meta.state) ?? '');
|
|
143
|
+
}
|
|
144
|
+
else if (meta.state.startsWith('c_')) {
|
|
145
|
+
// Keep an orphan stable code visible for fail-soft diagnostics.
|
|
146
|
+
parts.push(meta.state);
|
|
147
|
+
}
|
|
130
148
|
else {
|
|
131
|
-
parts.push(STATE_SEARCH_TEXT[meta.state]);
|
|
149
|
+
parts.push(STATE_SEARCH_TEXT[meta.state] ?? '');
|
|
132
150
|
}
|
|
133
151
|
}
|
|
134
152
|
return parts.filter(Boolean).join(' ');
|
|
@@ -9,7 +9,9 @@ Object.defineProperty(exports, "__esModule", { value: true });
|
|
|
9
9
|
exports.ThreadModule = void 0;
|
|
10
10
|
const common_1 = require("@nestjs/common");
|
|
11
11
|
const thread_controller_js_1 = require("./thread.controller.js");
|
|
12
|
+
const thread_label_controller_js_1 = require("./thread-label.controller.js");
|
|
12
13
|
const thread_service_js_1 = require("./thread.service.js");
|
|
14
|
+
const thread_label_service_js_1 = require("./thread-label.service.js");
|
|
13
15
|
const box_service_js_1 = require("../box/box.service.js");
|
|
14
16
|
const crewx_module_js_1 = require("../../modules/crewx.module.js");
|
|
15
17
|
let ThreadModule = class ThreadModule {
|
|
@@ -18,8 +20,8 @@ exports.ThreadModule = ThreadModule;
|
|
|
18
20
|
exports.ThreadModule = ThreadModule = __decorate([
|
|
19
21
|
(0, common_1.Module)({
|
|
20
22
|
imports: [crewx_module_js_1.CrewxModule],
|
|
21
|
-
controllers: [thread_controller_js_1.ThreadController],
|
|
22
|
-
providers: [thread_service_js_1.ThreadService, box_service_js_1.BoxService],
|
|
23
|
+
controllers: [thread_controller_js_1.ThreadController, thread_label_controller_js_1.ThreadLabelController],
|
|
24
|
+
providers: [thread_service_js_1.ThreadService, thread_label_service_js_1.ThreadLabelService, box_service_js_1.BoxService],
|
|
23
25
|
exports: [thread_service_js_1.ThreadService],
|
|
24
26
|
})
|
|
25
27
|
], ThreadModule);
|
|
@@ -27,40 +27,63 @@ const provider_resume_js_1 = require("../../common/task-log/provider-resume.js")
|
|
|
27
27
|
const pending_instruction_js_1 = require("../../common/pending-instruction.js");
|
|
28
28
|
const task_error_projection_js_1 = require("../../common/task-error-projection.js");
|
|
29
29
|
const overdrive_types_js_1 = require("./overdrive.types.js");
|
|
30
|
+
const thread_title_common_code_backfill_js_1 = require("./thread-title-common-code.backfill.js");
|
|
31
|
+
const thread_label_service_js_1 = require("./thread-label.service.js");
|
|
30
32
|
const thread_title_backfill_js_1 = require("./thread-title.backfill.js");
|
|
31
33
|
const thread_title_types_js_1 = require("./thread-title.types.js");
|
|
32
34
|
// Legacy running-instruction text entries were persisted with a localized display
|
|
33
35
|
// prefix before the structured `running_instruction` type existed. Match every known
|
|
34
36
|
// locale prefix so classification stays language-neutral for a global product.
|
|
35
37
|
const RUNNING_INSTRUCTION_LEGACY_PREFIXES = ['Running instruction:', '추가 지시:'];
|
|
36
|
-
let ThreadService =
|
|
38
|
+
let ThreadService = class ThreadService {
|
|
39
|
+
static { ThreadService_1 = this; }
|
|
37
40
|
threadRepo;
|
|
38
41
|
crewxPool;
|
|
39
42
|
taskRepo;
|
|
40
43
|
globalSettingsStore;
|
|
44
|
+
threadLabelService;
|
|
41
45
|
logger = new common_1.Logger(ThreadService_1.name);
|
|
42
|
-
|
|
46
|
+
static commonCodeBackfillPaths = new Set();
|
|
47
|
+
constructor(threadRepo, crewxPool, taskRepo = new task_repository_js_1.TaskRepository(), globalSettingsStore = (0, sdk_1.createGlobalSettingsStore)(), threadLabelService = new thread_label_service_js_1.ThreadLabelService()) {
|
|
43
48
|
this.threadRepo = threadRepo;
|
|
44
49
|
this.crewxPool = crewxPool;
|
|
45
50
|
this.taskRepo = taskRepo;
|
|
46
51
|
this.globalSettingsStore = globalSettingsStore;
|
|
52
|
+
this.threadLabelService = threadLabelService;
|
|
47
53
|
}
|
|
48
54
|
onModuleInit() {
|
|
55
|
+
const dbPath = process.env.CREWX_DB || process.env.CREWX_TRACES_DB || 'default-crewx-db';
|
|
56
|
+
if (ThreadService_1.commonCodeBackfillPaths.has(dbPath))
|
|
57
|
+
return;
|
|
49
58
|
try {
|
|
50
|
-
(0,
|
|
59
|
+
(0, thread_title_common_code_backfill_js_1.runThreadTitleCommonCodeBackfill)({
|
|
51
60
|
threadRepo: this.threadRepo,
|
|
61
|
+
threadLabelService: this.threadLabelService,
|
|
52
62
|
globalSettingsStore: this.globalSettingsStore,
|
|
53
63
|
logger: this.logger,
|
|
54
64
|
});
|
|
65
|
+
ThreadService_1.commonCodeBackfillPaths.add(dbPath);
|
|
55
66
|
}
|
|
56
67
|
catch (e) {
|
|
57
68
|
const err = e;
|
|
58
|
-
this.logger.error(`[thread-title]
|
|
69
|
+
this.logger.error(`[thread-title] common-code migration failed: ${err.message}`, err.stack);
|
|
59
70
|
}
|
|
60
71
|
}
|
|
61
72
|
// THD.LIST: List threads from threads table (with optional q search)
|
|
62
73
|
listThreads(params, workspaceId) {
|
|
63
|
-
const
|
|
74
|
+
const priorityFilter = (0, thread_title_types_js_1.parseThreadPriorityFilter)(params.priority);
|
|
75
|
+
const stateFilter = (0, thread_title_types_js_1.parseThreadStateFilter)(params.state);
|
|
76
|
+
const titleFilters = {
|
|
77
|
+
...(priorityFilter ? {
|
|
78
|
+
priority: { values: [...priorityFilter.values], includeNone: priorityFilter.includeNone },
|
|
79
|
+
} : {}),
|
|
80
|
+
...(stateFilter ? {
|
|
81
|
+
state: { values: [...stateFilter.values], includeNone: stateFilter.includeNone },
|
|
82
|
+
} : {}),
|
|
83
|
+
};
|
|
84
|
+
const rows = priorityFilter || stateFilter
|
|
85
|
+
? this.threadRepo.findAllThreadsByTitleFilters(workspaceId, titleFilters)
|
|
86
|
+
: this.threadRepo.findAllThreads(workspaceId);
|
|
64
87
|
// Filter by platform
|
|
65
88
|
let filtered = rows;
|
|
66
89
|
const platform = params.platform || 'all';
|
|
@@ -68,22 +91,14 @@ let ThreadService = ThreadService_1 = class ThreadService {
|
|
|
68
91
|
filtered = filtered.filter((r) => r.platform === platform);
|
|
69
92
|
}
|
|
70
93
|
// Filter by stored priority/state values (between platform and q)
|
|
71
|
-
const
|
|
72
|
-
const stateFilter = (0, thread_title_types_js_1.parseThreadStateFilter)(params.state);
|
|
73
|
-
if (priorityFilter || stateFilter) {
|
|
74
|
-
filtered = filtered.filter((r) => {
|
|
75
|
-
const meta = (0, thread_title_types_js_1.readThreadTitleMeta)(r.metadata);
|
|
76
|
-
return ((0, thread_title_types_js_1.matchesThreadTitleFilter)(meta.priority, priorityFilter) &&
|
|
77
|
-
(0, thread_title_types_js_1.matchesThreadTitleFilter)(meta.state, stateFilter));
|
|
78
|
-
});
|
|
79
|
-
}
|
|
94
|
+
const labelNames = this.threadLabelService.searchAliases(workspaceId);
|
|
80
95
|
// Filter by search keyword (q param). Haystack includes tab codes + KO/EN labels
|
|
81
96
|
// so habits like q=P1 / q=진행중 still match after title is name-only.
|
|
82
97
|
if (params.q && params.q.trim().length > 0) {
|
|
83
98
|
const queryLower = params.q.toLowerCase();
|
|
84
99
|
filtered = filtered.filter((r) => {
|
|
85
100
|
const meta = (0, thread_title_types_js_1.readThreadTitleMeta)(r.metadata);
|
|
86
|
-
const text = [r.title, r.first_message, r.last_message, (0, thread_title_types_js_1.threadTitleSearchHaystack)(meta)]
|
|
101
|
+
const text = [r.title, r.first_message, r.last_message, (0, thread_title_types_js_1.threadTitleSearchHaystack)(meta, labelNames)]
|
|
87
102
|
.filter(Boolean)
|
|
88
103
|
.join(' ')
|
|
89
104
|
.toLowerCase();
|
|
@@ -216,19 +231,43 @@ let ThreadService = ThreadService_1 = class ThreadService {
|
|
|
216
231
|
}
|
|
217
232
|
title = trimmed;
|
|
218
233
|
}
|
|
219
|
-
|
|
220
|
-
|
|
221
|
-
|
|
222
|
-
|
|
223
|
-
|
|
234
|
+
const parsedTitle = hasTitle
|
|
235
|
+
? (0, thread_title_backfill_js_1.parseCompositeThreadTitle)(title, this.threadLabelService.stateAliases(workspaceId))
|
|
236
|
+
: null;
|
|
237
|
+
if (parsedTitle)
|
|
238
|
+
title = parsedTitle.name;
|
|
239
|
+
const effectivePriority = hasPriority
|
|
240
|
+
? patch.priority
|
|
241
|
+
: parsedTitle?.priority;
|
|
242
|
+
const effectiveState = hasState
|
|
243
|
+
? patch.state
|
|
244
|
+
: parsedTitle?.state;
|
|
245
|
+
const hasEffectivePriority = hasPriority || parsedTitle?.priority !== undefined;
|
|
246
|
+
const hasEffectiveState = hasState || parsedTitle?.state !== undefined;
|
|
247
|
+
// Validate the thread before touching the ledger so cross-workspace thread
|
|
248
|
+
// updates never reveal or mutate another workspace's label set.
|
|
249
|
+
const existing = this.threadRepo.findThreadById(threadId, workspaceId);
|
|
250
|
+
if (!existing)
|
|
251
|
+
throw new common_1.NotFoundException('Thread not found');
|
|
252
|
+
if (hasEffectivePriority && effectivePriority !== null && effectivePriority !== undefined) {
|
|
253
|
+
this.threadLabelService.assertSelectable('priority', effectivePriority, workspaceId);
|
|
254
|
+
}
|
|
255
|
+
if (hasEffectiveState && effectiveState !== null && effectiveState !== undefined) {
|
|
256
|
+
this.threadLabelService.assertSelectable('state', effectiveState, workspaceId);
|
|
257
|
+
}
|
|
258
|
+
if (hasEffectivePriority || hasEffectiveState) {
|
|
259
|
+
const written = this.threadRepo.patchThreadTitleMetadata(threadId, {
|
|
260
|
+
...(hasEffectivePriority ? { priority: effectivePriority } : {}),
|
|
261
|
+
...(hasEffectiveState ? { state: effectiveState } : {}),
|
|
262
|
+
}, workspaceId);
|
|
224
263
|
if (!written) {
|
|
225
|
-
throw new common_1.NotFoundException(
|
|
264
|
+
throw new common_1.NotFoundException('Thread not found');
|
|
226
265
|
}
|
|
227
266
|
}
|
|
228
|
-
|
|
267
|
+
if (hasTitle) {
|
|
229
268
|
const updated = this.threadRepo.updateThreadTitle(threadId, title, workspaceId);
|
|
230
269
|
if (!updated) {
|
|
231
|
-
throw new common_1.NotFoundException(
|
|
270
|
+
throw new common_1.NotFoundException('Thread not found');
|
|
232
271
|
}
|
|
233
272
|
}
|
|
234
273
|
const row = this.threadRepo.findThreadById(threadId, workspaceId);
|
|
@@ -741,7 +780,8 @@ exports.ThreadService = ThreadService;
|
|
|
741
780
|
exports.ThreadService = ThreadService = ThreadService_1 = __decorate([
|
|
742
781
|
(0, common_1.Injectable)(),
|
|
743
782
|
__param(3, (0, common_1.Optional)()),
|
|
783
|
+
__param(4, (0, common_1.Optional)()),
|
|
744
784
|
__metadata("design:paramtypes", [thread_repository_js_1.ThreadRepository,
|
|
745
785
|
crewx_pool_service_js_1.CrewxPoolService,
|
|
746
|
-
task_repository_js_1.TaskRepository, Object])
|
|
786
|
+
task_repository_js_1.TaskRepository, Object, thread_label_service_js_1.ThreadLabelService])
|
|
747
787
|
], ThreadService);
|
package/dist-server/main.js
CHANGED
|
@@ -53,6 +53,7 @@ const auth_constants_js_1 = require("./domain/auth/auth.constants.js");
|
|
|
53
53
|
const ip_utils_js_1 = require("./domain/auth/utils/ip.utils.js");
|
|
54
54
|
const project_repository_js_1 = require("./repository/project.repository.js");
|
|
55
55
|
const browser_open_js_1 = require("./bootstrap/browser-open.js");
|
|
56
|
+
const validation_pipe_js_1 = require("./common/validation.pipe.js");
|
|
56
57
|
async function bootstrap() {
|
|
57
58
|
const httpsOptions = await (0, tls_js_1.resolveHttpsOptions)();
|
|
58
59
|
// RT-6: bodyParser disabled to preserve raw body for adapter webhook signature verification.
|
|
@@ -110,7 +111,7 @@ async function bootstrap() {
|
|
|
110
111
|
exposedHeaders: ['Mcp-Session-Id'],
|
|
111
112
|
});
|
|
112
113
|
// Enable class-validator DTO validation
|
|
113
|
-
app.useGlobalPipes(
|
|
114
|
+
app.useGlobalPipes((0, validation_pipe_js_1.createServerValidationPipe)());
|
|
114
115
|
// Global prefix for API routes (exclude MCP and auth login/logout — they live at /mcp, /auth/*)
|
|
115
116
|
app.setGlobalPrefix('api/v1', {
|
|
116
117
|
exclude: [
|
|
@@ -18,6 +18,7 @@ const usage_limit_snapshot_repository_js_1 = require("./usage-limit-snapshot.rep
|
|
|
18
18
|
const usage_report_repository_js_1 = require("./usage-report.repository.js");
|
|
19
19
|
const notification_repository_js_1 = require("./notification.repository.js");
|
|
20
20
|
const agent_suggestion_repository_js_1 = require("./agent-suggestion.repository.js");
|
|
21
|
+
const thread_label_repository_js_1 = require("./thread-label.repository.js");
|
|
21
22
|
const repositories = [
|
|
22
23
|
thread_repository_js_1.ThreadRepository,
|
|
23
24
|
task_repository_js_1.TaskRepository,
|
|
@@ -29,6 +30,7 @@ const repositories = [
|
|
|
29
30
|
usage_report_repository_js_1.UsageReportRepository,
|
|
30
31
|
notification_repository_js_1.NotificationRepository,
|
|
31
32
|
agent_suggestion_repository_js_1.AgentSuggestionRepository,
|
|
33
|
+
thread_label_repository_js_1.ThreadLabelRepository,
|
|
32
34
|
];
|
|
33
35
|
let RepositoryModule = class RepositoryModule {
|
|
34
36
|
};
|
|
@@ -0,0 +1,79 @@
|
|
|
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 __param = (this && this.__param) || function (paramIndex, decorator) {
|
|
12
|
+
return function (target, key) { decorator(target, key, paramIndex); }
|
|
13
|
+
};
|
|
14
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
15
|
+
exports.ThreadLabelRepository = void 0;
|
|
16
|
+
exports.threadLabelGroup = threadLabelGroup;
|
|
17
|
+
const common_1 = require("@nestjs/common");
|
|
18
|
+
const repository_1 = require("@crewx/sdk/repository");
|
|
19
|
+
const GLOBAL_LOOKUP_WORKSPACE = '__crewx_global_lookup__';
|
|
20
|
+
function threadLabelGroup(kind) {
|
|
21
|
+
return kind === 'state' ? 'THREAD_STATE' : 'THREAD_PRIORITY';
|
|
22
|
+
}
|
|
23
|
+
/**
|
|
24
|
+
* Server adapter around the SDK common-code repository.
|
|
25
|
+
*
|
|
26
|
+
* The adapter translates feature terminology (`state`/`priority`) to SDK
|
|
27
|
+
* groups. It deliberately does not own a second group list, palette, or SQL
|
|
28
|
+
* implementation of common-code rules.
|
|
29
|
+
*/
|
|
30
|
+
let ThreadLabelRepository = class ThreadLabelRepository {
|
|
31
|
+
sdkRepository;
|
|
32
|
+
constructor(sdkRepository) {
|
|
33
|
+
this.sdkRepository = sdkRepository ?? new repository_1.CommonCodeRepository();
|
|
34
|
+
}
|
|
35
|
+
list(kind, workspaceId, includeArchived) {
|
|
36
|
+
const group = threadLabelGroup(kind);
|
|
37
|
+
const lookupWorkspace = workspaceId || GLOBAL_LOOKUP_WORKSPACE;
|
|
38
|
+
return includeArchived
|
|
39
|
+
? this.sdkRepository.listForHistory(group, lookupWorkspace)
|
|
40
|
+
: this.sdkRepository.listSelectable(group, lookupWorkspace);
|
|
41
|
+
}
|
|
42
|
+
resolve(kind, code, workspaceId) {
|
|
43
|
+
const row = this.sdkRepository.resolve(threadLabelGroup(kind), code);
|
|
44
|
+
if (!row)
|
|
45
|
+
return null;
|
|
46
|
+
if (row.workspace_id !== null && row.workspace_id !== (workspaceId ?? ''))
|
|
47
|
+
return null;
|
|
48
|
+
return row;
|
|
49
|
+
}
|
|
50
|
+
findByName(kind, name, workspaceId) {
|
|
51
|
+
const rows = this.sdkRepository.listForHistory(threadLabelGroup(kind), workspaceId || GLOBAL_LOOKUP_WORKSPACE);
|
|
52
|
+
const workspaceMatch = workspaceId
|
|
53
|
+
? rows.find((row) => row.workspace_id === workspaceId && row.code_name === name)
|
|
54
|
+
: undefined;
|
|
55
|
+
return workspaceMatch
|
|
56
|
+
?? rows.find((row) => row.workspace_id === null && row.code_name === name)
|
|
57
|
+
?? null;
|
|
58
|
+
}
|
|
59
|
+
createState(workspaceId, name, color) {
|
|
60
|
+
return this.sdkRepository.createThreadState({ workspaceId, name, color });
|
|
61
|
+
}
|
|
62
|
+
update(kind, code, workspaceId, input) {
|
|
63
|
+
return this.sdkRepository.updateDisplay({
|
|
64
|
+
group: threadLabelGroup(kind),
|
|
65
|
+
code,
|
|
66
|
+
workspaceId,
|
|
67
|
+
...input,
|
|
68
|
+
});
|
|
69
|
+
}
|
|
70
|
+
archive(kind, code, workspaceId, archived) {
|
|
71
|
+
return this.sdkRepository.archive(threadLabelGroup(kind), code, workspaceId, archived);
|
|
72
|
+
}
|
|
73
|
+
};
|
|
74
|
+
exports.ThreadLabelRepository = ThreadLabelRepository;
|
|
75
|
+
exports.ThreadLabelRepository = ThreadLabelRepository = __decorate([
|
|
76
|
+
(0, common_1.Injectable)(),
|
|
77
|
+
__param(0, (0, common_1.Optional)()),
|
|
78
|
+
__metadata("design:paramtypes", [repository_1.CommonCodeRepository])
|
|
79
|
+
], ThreadLabelRepository);
|
|
@@ -13,6 +13,10 @@ const drizzle_orm_1 = require("drizzle-orm");
|
|
|
13
13
|
const repository_1 = require("@crewx/sdk/repository");
|
|
14
14
|
const base_sqlite_repository_js_1 = require("./base-sqlite.repository.js");
|
|
15
15
|
const thread_title_types_js_1 = require("../domain/thread/thread-title.types.js");
|
|
16
|
+
const THREAD_STATE_INDEX_EXPR = "json_extract(metadata, '$.threadTitle.state')";
|
|
17
|
+
const THREAD_PRIORITY_INDEX_EXPR = "json_extract(metadata, '$.threadTitle.priority')";
|
|
18
|
+
const THREAD_STATE_INDEX = 'idx_threads_workspace_thread_state';
|
|
19
|
+
const THREAD_PRIORITY_INDEX = 'idx_threads_workspace_thread_priority';
|
|
16
20
|
let ThreadRepository = class ThreadRepository extends base_sqlite_repository_js_1.BaseSqliteRepository {
|
|
17
21
|
sdkThread = new repository_1.ThreadRepository();
|
|
18
22
|
/** Fetch all threads from all DB paths, deduplicated by id.
|
|
@@ -28,6 +32,99 @@ let ThreadRepository = class ThreadRepository extends base_sqlite_repository_js_
|
|
|
28
32
|
throw e;
|
|
29
33
|
}
|
|
30
34
|
}
|
|
35
|
+
/**
|
|
36
|
+
* Query exact metadata values through the same JSON expressions used by the
|
|
37
|
+
* SDK-owned expression indexes. The in-memory list path remains the fallback
|
|
38
|
+
* for unscoped legacy callers; HTTP workspace requests always provide an id.
|
|
39
|
+
*/
|
|
40
|
+
findAllThreadsByTitleFilters(workspaceId, filters) {
|
|
41
|
+
if (!workspaceId) {
|
|
42
|
+
const rows = this.findAllThreads(workspaceId);
|
|
43
|
+
return rows.filter((row) => {
|
|
44
|
+
const state = this.readRawThreadTitleValue(row.metadata, 'state');
|
|
45
|
+
const priority = this.readRawThreadTitleValue(row.metadata, 'priority');
|
|
46
|
+
return this.matchesRawFilter(state, filters.state) && this.matchesRawFilter(priority, filters.priority);
|
|
47
|
+
});
|
|
48
|
+
}
|
|
49
|
+
const query = this.buildThreadTitleFilterQuery(workspaceId, filters);
|
|
50
|
+
const seenIds = new Set();
|
|
51
|
+
const result = [];
|
|
52
|
+
for (const dbPath of this.resolveDbPaths()) {
|
|
53
|
+
if (!(0, fs_1.existsSync)(dbPath))
|
|
54
|
+
continue;
|
|
55
|
+
const handle = (0, repository_1.openDrizzleDb)(dbPath);
|
|
56
|
+
try {
|
|
57
|
+
for (const row of handle.db.all(query)) {
|
|
58
|
+
if (seenIds.has(row.id))
|
|
59
|
+
continue;
|
|
60
|
+
seenIds.add(row.id);
|
|
61
|
+
result.push(row);
|
|
62
|
+
}
|
|
63
|
+
}
|
|
64
|
+
finally {
|
|
65
|
+
handle.close();
|
|
66
|
+
}
|
|
67
|
+
}
|
|
68
|
+
return result;
|
|
69
|
+
}
|
|
70
|
+
/** Test/diagnostic plan for the exact THD.LIST title-filter SQL. */
|
|
71
|
+
explainThreadTitleFilterPlan(workspaceId, filters) {
|
|
72
|
+
const query = this.buildThreadTitleFilterQuery(workspaceId, filters);
|
|
73
|
+
for (const dbPath of this.resolveDbPaths()) {
|
|
74
|
+
if (!(0, fs_1.existsSync)(dbPath))
|
|
75
|
+
continue;
|
|
76
|
+
const handle = (0, repository_1.openDrizzleDb)(dbPath);
|
|
77
|
+
try {
|
|
78
|
+
return handle.db.all((0, drizzle_orm_1.sql) `EXPLAIN QUERY PLAN ${query}`).map((row) => row.detail);
|
|
79
|
+
}
|
|
80
|
+
finally {
|
|
81
|
+
handle.close();
|
|
82
|
+
}
|
|
83
|
+
}
|
|
84
|
+
return [];
|
|
85
|
+
}
|
|
86
|
+
buildThreadTitleFilterQuery(workspaceId, filters) {
|
|
87
|
+
const clauses = [(0, drizzle_orm_1.sql) `workspace_id = ${workspaceId}`];
|
|
88
|
+
if (filters.state)
|
|
89
|
+
clauses.push(this.buildRawFilterSql(THREAD_STATE_INDEX_EXPR, filters.state));
|
|
90
|
+
if (filters.priority)
|
|
91
|
+
clauses.push(this.buildRawFilterSql(THREAD_PRIORITY_INDEX_EXPR, filters.priority));
|
|
92
|
+
const index = filters.state ? THREAD_STATE_INDEX : filters.priority ? THREAD_PRIORITY_INDEX : undefined;
|
|
93
|
+
const indexedTable = index
|
|
94
|
+
? (0, drizzle_orm_1.sql) `threads INDEXED BY ${drizzle_orm_1.sql.raw(index)}`
|
|
95
|
+
: (0, drizzle_orm_1.sql) `threads`;
|
|
96
|
+
return (0, drizzle_orm_1.sql) `SELECT * FROM ${indexedTable} WHERE ${drizzle_orm_1.sql.join(clauses, (0, drizzle_orm_1.sql) ` AND `)} ORDER BY updated_at DESC`;
|
|
97
|
+
}
|
|
98
|
+
buildRawFilterSql(expression, filter) {
|
|
99
|
+
const expressionSql = drizzle_orm_1.sql.raw(expression);
|
|
100
|
+
const valuesSql = filter.values.length > 0
|
|
101
|
+
? (0, drizzle_orm_1.sql) `${expressionSql} IN (${drizzle_orm_1.sql.join(filter.values.map((value) => (0, drizzle_orm_1.sql) `${value}`), (0, drizzle_orm_1.sql) `, `)})`
|
|
102
|
+
: undefined;
|
|
103
|
+
if (filter.includeNone && valuesSql)
|
|
104
|
+
return (0, drizzle_orm_1.sql) `(${valuesSql} OR ${expressionSql} IS NULL)`;
|
|
105
|
+
if (filter.includeNone)
|
|
106
|
+
return (0, drizzle_orm_1.sql) `${expressionSql} IS NULL`;
|
|
107
|
+
return valuesSql ?? (0, drizzle_orm_1.sql) `1 = 1`;
|
|
108
|
+
}
|
|
109
|
+
readRawThreadTitleValue(metadata, key) {
|
|
110
|
+
if (!metadata)
|
|
111
|
+
return null;
|
|
112
|
+
try {
|
|
113
|
+
const root = JSON.parse(metadata);
|
|
114
|
+
const value = root.threadTitle?.[key];
|
|
115
|
+
return typeof value === 'string' ? value : null;
|
|
116
|
+
}
|
|
117
|
+
catch {
|
|
118
|
+
return null;
|
|
119
|
+
}
|
|
120
|
+
}
|
|
121
|
+
matchesRawFilter(value, filter) {
|
|
122
|
+
if (!filter)
|
|
123
|
+
return true;
|
|
124
|
+
if (value === null)
|
|
125
|
+
return filter.includeNone;
|
|
126
|
+
return filter.values.includes(value);
|
|
127
|
+
}
|
|
31
128
|
/** Find only the threads whose id or title exactly matches one of `ids`.
|
|
32
129
|
* Avoids loading the entire threads table for scoped lookups (WI-20260718-001). */
|
|
33
130
|
findThreadsByIdsOrTitles(ids, workspaceId) {
|
|
@@ -189,6 +286,13 @@ let ThreadRepository = class ThreadRepository extends base_sqlite_repository_js_
|
|
|
189
286
|
handle.close();
|
|
190
287
|
}
|
|
191
288
|
}
|
|
289
|
+
/** Delegate metadata writes to the SDK's atomic read/merge/write patch. */
|
|
290
|
+
patchThreadTitleMetadata(threadId, patch, workspaceId) {
|
|
291
|
+
const result = this.sdkThread.patchThreadTitleMetadata(threadId, patch, workspaceId);
|
|
292
|
+
if (result instanceof repository_1.ThreadTitleMetadataPatchError)
|
|
293
|
+
throw result;
|
|
294
|
+
return result;
|
|
295
|
+
}
|
|
192
296
|
/** Toggle pinned 0↔1 for a thread. Returns new state or null if not found. */
|
|
193
297
|
togglePin(threadId, workspaceId) {
|
|
194
298
|
try {
|
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
/**
|
|
3
|
+
* Transport-only contract for conversation state and priority labels.
|
|
4
|
+
*
|
|
5
|
+
* This file intentionally has no Nest, Node, database, or SDK dependency so
|
|
6
|
+
* browser clients can reuse the wire shape without pulling server code.
|
|
7
|
+
*/
|
|
8
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "crewx",
|
|
3
|
-
"version": "0.9.0-rc.
|
|
3
|
+
"version": "0.9.0-rc.74",
|
|
4
4
|
"description": "CrewX — AI agent team dashboard with Electron UI and CLI (Web + Electron + Global CLI)",
|
|
5
5
|
"main": "server.js",
|
|
6
6
|
"bin": {
|
|
@@ -82,20 +82,20 @@
|
|
|
82
82
|
"wink-nlp-utils": "2.1.0",
|
|
83
83
|
"yargs": "17.7.0",
|
|
84
84
|
"zod": "3.25.76",
|
|
85
|
-
"@crewx/chromex": "0.1.0-rc.
|
|
86
|
-
"@crewx/
|
|
87
|
-
"@crewx/
|
|
88
|
-
"@crewx/dreaming": "0.1.0-rc.
|
|
89
|
-
"@crewx/doc": "0.1.9-rc.
|
|
90
|
-
"@crewx/
|
|
91
|
-
"@crewx/
|
|
85
|
+
"@crewx/chromex": "0.1.0-rc.106",
|
|
86
|
+
"@crewx/cron": "0.1.10-rc.108",
|
|
87
|
+
"@crewx/cli": "0.9.0-rc.74",
|
|
88
|
+
"@crewx/dreaming": "0.1.0-rc.99",
|
|
89
|
+
"@crewx/doc": "0.1.9-rc.66",
|
|
90
|
+
"@crewx/memory": "0.1.23-rc.90",
|
|
91
|
+
"@crewx/sdk": "0.9.0-rc.74",
|
|
92
92
|
"@crewx/shared": "0.0.6",
|
|
93
|
-
"@crewx/sdk": "0.9.0-rc.73",
|
|
94
|
-
"@crewx/search": "0.1.10-rc.68",
|
|
95
93
|
"@crewx/skill": "0.1.20",
|
|
96
|
-
"@crewx/
|
|
97
|
-
"@crewx/
|
|
98
|
-
"@crewx/workflow": "0.3.22-rc.
|
|
94
|
+
"@crewx/knowledge-core": "0.1.17-rc.83",
|
|
95
|
+
"@crewx/search": "0.1.10-rc.69",
|
|
96
|
+
"@crewx/workflow": "0.3.22-rc.120",
|
|
97
|
+
"@crewx/wi": "0.1.10-rc.94",
|
|
98
|
+
"@crewx/wbs": "0.1.10-rc.99"
|
|
99
99
|
},
|
|
100
100
|
"devDependencies": {
|
|
101
101
|
"@anthropic-ai/claude-agent-sdk": "0.2.132",
|