crewx 0.9.0-rc.2 → 0.9.0-rc.21
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/assets/MarketPage-DDPL2d7x.js +36 -0
- package/dist/assets/{PromptTab-hdXDbbT6.js → PromptTab-UC3K4qx2.js} +3 -3
- package/dist/assets/{arc--CBVVcyH.js → arc-DdP3-fUR.js} +1 -1
- package/dist/assets/{architectureDiagram-UYN6MBPD-DUiYwqJn.js → architectureDiagram-UYN6MBPD-D16VON1i.js} +1 -1
- package/dist/assets/{blockDiagram-ZHA2E4KO-DV86vAgG.js → blockDiagram-ZHA2E4KO-qs1dIRkl.js} +4 -4
- package/dist/assets/{c4Diagram-6F5ED5ID-CvWE7Cx_.js → c4Diagram-6F5ED5ID-BeDy8akQ.js} +1 -1
- package/dist/assets/channel-DtrxBSkc.js +1 -0
- package/dist/assets/{chunk-5HRBRIJM-DAxM5Ul_.js → chunk-5HRBRIJM-BC1zIb11.js} +1 -1
- package/dist/assets/{chunk-7U56Z5CX-BuYaEj8L.js → chunk-7U56Z5CX-BuGil3QD.js} +1 -1
- package/dist/assets/{chunk-ASOPGD6M-2Kx0e45C.js → chunk-ASOPGD6M-DmKdDtBQ.js} +1 -1
- package/dist/assets/{chunk-KFBOBJHC-CR4QS6Rt.js → chunk-KFBOBJHC-D3BvHLPa.js} +1 -1
- package/dist/assets/{chunk-T2TOU4HS-CffcwNHx.js → chunk-T2TOU4HS-B5MoUp-N.js} +1 -1
- package/dist/assets/{chunk-TMUBEWPD-BGpYtekN.js → chunk-TMUBEWPD-AvWe_lsV.js} +1 -1
- package/dist/assets/classDiagram-LNE6IOMH-BMT5B_JB.js +1 -0
- package/dist/assets/classDiagram-v2-MQ7JQ4JX-BMT5B_JB.js +1 -0
- package/dist/assets/{dagre-4EVJKHTY-yBWukkrE.js → dagre-4EVJKHTY-B6DewFyM.js} +1 -1
- package/dist/assets/{diagram-QW4FP2JN-CIr1PLq3.js → diagram-QW4FP2JN-BTsQ8JSd.js} +1 -1
- package/dist/assets/{erDiagram-6RL3IURR-xG7SygyU.js → erDiagram-6RL3IURR-BM5nvgVR.js} +1 -1
- package/dist/assets/{flowDiagram-7ASYPVHJ-CKalfOqp.js → flowDiagram-7ASYPVHJ-D1UCCuOG.js} +1 -1
- package/dist/assets/{ganttDiagram-NTVNEXSI-Bnx7zrX4.js → ganttDiagram-NTVNEXSI-CNIGs7Cn.js} +1 -1
- package/dist/assets/{gitGraph-YCYPL57B-TQlF0fiA.js → gitGraph-YCYPL57B-DSLjx1yQ.js} +1 -1
- package/dist/assets/{gitGraphDiagram-NRZ2UAAF-DxlaFkwO.js → gitGraphDiagram-NRZ2UAAF-B11zX1Fa.js} +1 -1
- package/dist/assets/{infoDiagram-A4XQUW5V-B1-arkqa.js → infoDiagram-A4XQUW5V-DkjvODYl.js} +1 -1
- package/dist/assets/{journeyDiagram-G5WM74LC-Cj439GUX.js → journeyDiagram-G5WM74LC-BkCQPMn6.js} +1 -1
- package/dist/assets/{kanban-definition-QRCXZQQD-BvrIwEIc.js → kanban-definition-QRCXZQQD-_OX7pNfJ.js} +1 -1
- package/dist/assets/{linear-BKYNK0ri.js → linear-BYjwVAR6.js} +1 -1
- package/dist/assets/main-CZdKLdUl.css +10 -0
- package/dist/assets/main-CkkZNCy-.js +1561 -0
- package/dist/assets/{mindmap-definition-GWI6TPTV-BExfJY0G.js → mindmap-definition-GWI6TPTV-BzYpSnv8.js} +1 -1
- package/dist/assets/ort-wasm-simd-threaded.asyncify-DMmc6YqF.wasm +0 -0
- package/dist/assets/{pieDiagram-YF2LJOPJ-RPWpvrC8.js → pieDiagram-YF2LJOPJ-CEFg6ao0.js} +1 -1
- package/dist/assets/{quadrantDiagram-OS5C2QUG-DmbBzk5Q.js → quadrantDiagram-OS5C2QUG-_5w2x3VV.js} +1 -1
- package/dist/assets/{requirementDiagram-MIRIMTAZ-DFQuFXq3.js → requirementDiagram-MIRIMTAZ-B_VQYReZ.js} +1 -1
- package/dist/assets/{sankeyDiagram-Y46BX6SQ-p_bw56lJ.js → sankeyDiagram-Y46BX6SQ-CgmOlLhh.js} +1 -1
- package/dist/assets/{sequenceDiagram-G6AWOVSC-DTbBkLeD.js → sequenceDiagram-G6AWOVSC-MGVt0Ai2.js} +1 -1
- package/dist/assets/{stateDiagram-MAYHULR4-DBvC6UcF.js → stateDiagram-MAYHULR4-DaZjgTu-.js} +1 -1
- package/dist/assets/stateDiagram-v2-4JROLMXI-DN1wGYkV.js +1 -0
- package/dist/assets/{timeline-definition-U7ZMHBDA-B6utvIg1.js → timeline-definition-U7ZMHBDA-CusetmK6.js} +1 -1
- package/dist/assets/transformers.web-BN8Ug43_.js +45 -0
- package/dist/assets/{xychartDiagram-6QU3TZC5-BHG0s1Ye.js → xychartDiagram-6QU3TZC5-CTndf-if.js} +1 -1
- package/dist/index.html +2 -2
- package/dist-server/app.module.js +4 -0
- package/dist-server/bootstrap/codex-writable-roots.js +88 -0
- package/dist-server/bootstrap/crewx-server.js +10 -2
- package/dist-server/common/decorator/skip-request-log.decorator.js +8 -0
- package/dist-server/common/interceptor/http-logging.interceptor.js +17 -4
- package/dist-server/common/task-log/stdout-json-fallback.js +200 -0
- package/dist-server/domain/agent/agent.controller.js +14 -0
- package/dist-server/domain/agent/agent.service.js +246 -18
- package/dist-server/domain/agent/dto/create-agent.dto.js +12 -0
- package/dist-server/domain/agent/dto/remote-provider.dto.js +82 -0
- package/dist-server/domain/agent/dto/reorder-agents.dto.js +51 -0
- package/dist-server/domain/agent/dto/update-agent.dto.js +3 -1
- package/dist-server/domain/agent/provider.controller.js +12 -1
- package/dist-server/domain/doc/doc.controller.js +43 -9
- package/dist-server/domain/doc/doc.service.js +80 -6
- package/dist-server/domain/doc/image-constants.js +11 -1
- package/dist-server/domain/events/workspace-events.controller.js +2 -0
- package/dist-server/domain/message/dto/send-message.dto.js +49 -1
- package/dist-server/domain/message/message.controller.js +1 -1
- package/dist-server/domain/message/message.module.js +2 -1
- package/dist-server/domain/message/message.service.js +83 -13
- package/dist-server/domain/settings/settings.service.js +188 -1
- package/dist-server/domain/system/system.controller.js +41 -0
- package/dist-server/domain/system/system.module.js +19 -0
- package/dist-server/domain/task/task.constants.js +8 -1
- package/dist-server/domain/task/task.service.js +59 -67
- package/dist-server/domain/thread/dto/list-threads.dto.js +18 -0
- package/dist-server/domain/thread/overdrive.types.js +38 -0
- package/dist-server/domain/thread/thread.service.js +83 -70
- package/dist-server/domain/usage/usage.controller.js +18 -0
- package/dist-server/domain/usage/usage.module.js +1 -0
- package/dist-server/domain/usage/usage.service.js +246 -4
- package/dist-server/domain/usage-report/dto/usage-report.dto.js +2 -0
- package/dist-server/domain/usage-report/usage-report.controller.js +72 -0
- package/dist-server/domain/usage-report/usage-report.module.js +23 -0
- package/dist-server/domain/usage-report/usage-report.service.js +135 -0
- package/dist-server/domain/wi/wi.service.js +45 -3
- package/dist-server/main.js +1 -0
- package/dist-server/repository/repository.module.js +2 -0
- package/dist-server/repository/task.repository.js +56 -0
- package/dist-server/repository/thread.repository.js +13 -0
- package/dist-server/repository/usage-report.repository.js +27 -0
- package/package.json +9 -9
- package/packages/cli/dist/bootstrap/codex-writable-roots.d.ts +45 -0
- package/packages/cli/dist/bootstrap/codex-writable-roots.js +88 -0
- package/packages/cli/dist/bootstrap/crewx-cli.js +2 -0
- package/packages/cli/dist/commands/execute.js +10 -2
- package/packages/cli/dist/commands/parse-common-flags.d.ts +5 -1
- package/packages/cli/dist/commands/parse-common-flags.js +9 -2
- package/packages/cli/dist/commands/ps.js +51 -2
- package/packages/cli/dist/commands/query.js +10 -2
- package/packages/cli/dist/commands/restart.js +20 -6
- package/packages/cli/dist/commands/write-output.d.ts +3 -0
- package/packages/cli/dist/commands/write-output.js +24 -0
- package/packages/cli/dist/main.js +7 -0
- package/packages/cli/package.json +1 -1
- package/dist/assets/MarketPage-T1o_oIrw.js +0 -41
- package/dist/assets/channel-7q16D7P9.js +0 -1
- package/dist/assets/classDiagram-LNE6IOMH-C6qjk4uc.js +0 -1
- package/dist/assets/classDiagram-v2-MQ7JQ4JX-C6qjk4uc.js +0 -1
- package/dist/assets/main-C5mwv79r.css +0 -10
- package/dist/assets/main-DmvFaz-d.js +0 -1486
- package/dist/assets/stateDiagram-v2-4JROLMXI-CCMApx_v.js +0 -1
|
@@ -12,13 +12,25 @@ var __param = (this && this.__param) || function (paramIndex, decorator) {
|
|
|
12
12
|
return function (target, key) { decorator(target, key, paramIndex); }
|
|
13
13
|
};
|
|
14
14
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
15
|
-
exports.DocController = void 0;
|
|
15
|
+
exports.DocController = exports.DOC_HTML_CSP = void 0;
|
|
16
16
|
const common_1 = require("@nestjs/common");
|
|
17
17
|
const swagger_1 = require("@nestjs/swagger");
|
|
18
18
|
const fs_1 = require("fs");
|
|
19
19
|
const path_1 = require("path");
|
|
20
20
|
const doc_service_js_1 = require("./doc.service.js");
|
|
21
21
|
const doc_dto_js_1 = require("./dto/doc.dto.js");
|
|
22
|
+
exports.DOC_HTML_CSP = "sandbox allow-scripts; " +
|
|
23
|
+
"default-src 'none'; " +
|
|
24
|
+
"style-src 'self' 'unsafe-inline'; " +
|
|
25
|
+
"script-src 'self' 'unsafe-inline'; " +
|
|
26
|
+
"img-src 'self' data:; " +
|
|
27
|
+
"font-src 'self' data:; " +
|
|
28
|
+
"connect-src 'none'; " +
|
|
29
|
+
"form-action 'none'; " +
|
|
30
|
+
"base-uri 'none'; " +
|
|
31
|
+
"object-src 'none'; " +
|
|
32
|
+
"worker-src 'none'; " +
|
|
33
|
+
"frame-ancestors 'self'";
|
|
22
34
|
let DocController = class DocController {
|
|
23
35
|
docService;
|
|
24
36
|
constructor(docService) {
|
|
@@ -55,16 +67,38 @@ let DocController = class DocController {
|
|
|
55
67
|
});
|
|
56
68
|
(0, fs_1.createReadStream)(absPath).pipe(res);
|
|
57
69
|
}
|
|
58
|
-
// DOC.FILE: Serve PDF file inline
|
|
70
|
+
// DOC.FILE: Serve PDF or HTML file inline
|
|
59
71
|
serveFile(filePath, res) {
|
|
60
72
|
const { absPath, mime } = this.docService.getDocFilePath(filePath);
|
|
61
73
|
const stat = (0, fs_1.statSync)(absPath);
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
74
|
+
const safeFilename = (0, doc_service_js_1.sanitizeFilename)((0, path_1.basename)(absPath));
|
|
75
|
+
const isHtml = mime.startsWith('text/html');
|
|
76
|
+
if (isHtml) {
|
|
77
|
+
// C-3: Security headers hardcoded — not configurable via env/config
|
|
78
|
+
// C-4: Cache-Control for HTML: private, no-store
|
|
79
|
+
// C-5: Isolation header set. sandbox allow-scripts permits the mockup's own
|
|
80
|
+
// inline/local CSS+JS to render, but WITHOUT allow-same-origin — so the framed
|
|
81
|
+
// document cannot read cookies/session or call same-origin APIs. Resource loads
|
|
82
|
+
// are scoped to same-origin wireframe assets only.
|
|
83
|
+
res.set({
|
|
84
|
+
'Content-Type': 'text/html; charset=utf-8',
|
|
85
|
+
'Content-Length': stat.size,
|
|
86
|
+
'Content-Disposition': `inline; filename="${safeFilename}"`,
|
|
87
|
+
'Cache-Control': 'private, no-store',
|
|
88
|
+
'Content-Security-Policy': exports.DOC_HTML_CSP,
|
|
89
|
+
'X-Content-Type-Options': 'nosniff',
|
|
90
|
+
'X-Frame-Options': 'SAMEORIGIN',
|
|
91
|
+
});
|
|
92
|
+
}
|
|
93
|
+
else {
|
|
94
|
+
// PDF: preserve existing behavior
|
|
95
|
+
res.set({
|
|
96
|
+
'Content-Type': mime,
|
|
97
|
+
'Content-Length': stat.size,
|
|
98
|
+
'Content-Disposition': `inline; filename="${safeFilename}"`,
|
|
99
|
+
'Cache-Control': 'public, max-age=3600',
|
|
100
|
+
});
|
|
101
|
+
}
|
|
68
102
|
(0, fs_1.createReadStream)(absPath).pipe(res);
|
|
69
103
|
}
|
|
70
104
|
// DOC.SAVE: Save document content
|
|
@@ -115,7 +149,7 @@ __decorate([
|
|
|
115
149
|
__metadata("design:returntype", void 0)
|
|
116
150
|
], DocController.prototype, "serveImage", null);
|
|
117
151
|
__decorate([
|
|
118
|
-
(0, swagger_1.ApiOperation)({ operationId: 'DOC.FILE', summary: 'Stream a PDF document file inline' }),
|
|
152
|
+
(0, swagger_1.ApiOperation)({ operationId: 'DOC.FILE', summary: 'Stream a PDF or HTML document file inline' }),
|
|
119
153
|
(0, common_1.Get)('file'),
|
|
120
154
|
__param(0, (0, common_1.Query)('path')),
|
|
121
155
|
__param(1, (0, common_1.Res)()),
|
|
@@ -7,14 +7,32 @@ var __decorate = (this && this.__decorate) || function (decorators, target, key,
|
|
|
7
7
|
};
|
|
8
8
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
9
9
|
exports.DocService = void 0;
|
|
10
|
+
exports.sanitizeFilename = sanitizeFilename;
|
|
10
11
|
const common_1 = require("@nestjs/common");
|
|
11
12
|
const fs_1 = require("fs");
|
|
13
|
+
const node_fs_1 = require("node:fs");
|
|
12
14
|
const path_1 = require("path");
|
|
13
15
|
const node_crypto_1 = require("node:crypto");
|
|
14
16
|
const doc_1 = require("@crewx/doc");
|
|
15
17
|
const knowledge_core_1 = require("@crewx/knowledge-core");
|
|
16
18
|
const workspace_context_store_js_1 = require("../../common/workspace-context.store.js");
|
|
17
19
|
const image_constants_js_1 = require("./image-constants.js");
|
|
20
|
+
/** Allowed directory prefix for HTML/HTM files and companion assets (C-2) */
|
|
21
|
+
const HTML_ALLOWED_DIR = 'docs/wireframe';
|
|
22
|
+
/** Max file size for HTML files: 5MB (C-size limit) */
|
|
23
|
+
const HTML_MAX_BYTES = 5 * 1024 * 1024;
|
|
24
|
+
/**
|
|
25
|
+
* Sanitize a filename for use in Content-Disposition header (C-7).
|
|
26
|
+
* Strips CR, LF, double-quote, and other control characters.
|
|
27
|
+
* Allows alphanumeric, dot, hyphen, underscore, parentheses, and space.
|
|
28
|
+
*/
|
|
29
|
+
function sanitizeFilename(name) {
|
|
30
|
+
return name.replace(/[^\w.\-() ]/g, '_').replace(/\s+/g, ' ').trim();
|
|
31
|
+
}
|
|
32
|
+
function isDocsWireframePath(filePath) {
|
|
33
|
+
const normalized = filePath.replace(/\\/g, '/').toLowerCase();
|
|
34
|
+
return normalized === HTML_ALLOWED_DIR || normalized.startsWith(HTML_ALLOWED_DIR + '/');
|
|
35
|
+
}
|
|
18
36
|
// Screen ID → page directory mapping for code existence check
|
|
19
37
|
const SCREEN_CODE_PATHS = {
|
|
20
38
|
NAVUI: 'src/ui/components/layout',
|
|
@@ -107,7 +125,9 @@ let DocService = class DocService {
|
|
|
107
125
|
}
|
|
108
126
|
else if (entry.isFile()) {
|
|
109
127
|
const ext = entry.name.split('.').pop()?.toLowerCase() ?? '';
|
|
110
|
-
|
|
128
|
+
const entryRel = rel ? `${rel}/${entry.name}` : entry.name;
|
|
129
|
+
const isWireframeHtml = (ext === 'html' || ext === 'htm') && isDocsWireframePath(entryRel);
|
|
130
|
+
if (entry.name.endsWith('.md') || image_constants_js_1.IMAGE_EXTENSIONS.has(ext) || ext === 'pdf' || isWireframeHtml) {
|
|
111
131
|
children.push(this.fileNode(entryAbs));
|
|
112
132
|
}
|
|
113
133
|
}
|
|
@@ -368,20 +388,74 @@ let DocService = class DocService {
|
|
|
368
388
|
return { absPath: abs, mime: image_constants_js_1.EXT_MIME[ext] };
|
|
369
389
|
}
|
|
370
390
|
/**
|
|
371
|
-
* DOC.FILE: Validate a PDF path and return its absolute filesystem path.
|
|
372
|
-
*
|
|
391
|
+
* DOC.FILE: Validate a PDF/HTML path and return its absolute filesystem path.
|
|
392
|
+
* Allowed extensions: pdf, html, htm.
|
|
393
|
+
* Security conditions C-1~C-6 enforced here.
|
|
373
394
|
*/
|
|
374
395
|
getDocFilePath(filePath) {
|
|
375
|
-
|
|
376
|
-
if (
|
|
396
|
+
// C-6: Null byte / control character rejection
|
|
397
|
+
if (filePath.includes('\0') || /[\x01-\x1f\x7f]/.test(filePath)) {
|
|
377
398
|
throw new common_1.NotFoundException('Not found');
|
|
378
399
|
}
|
|
400
|
+
// C-6: Lowercase-normalize extension before whitelist check
|
|
401
|
+
const normalizedPath = filePath.toLowerCase();
|
|
402
|
+
// Extract final extension only (guards against double-extension like .html.pdf)
|
|
403
|
+
const ext = normalizedPath.split('.').pop() ?? '';
|
|
404
|
+
const mime = image_constants_js_1.FILE_MIME[ext];
|
|
405
|
+
if (!mime) {
|
|
406
|
+
// C-6: svg/xhtml and anything outside whitelist → 404 (no info leak)
|
|
407
|
+
throw new common_1.NotFoundException('Not found');
|
|
408
|
+
}
|
|
409
|
+
// C-2: HTML/HTM and wireframe assets (css/js) must be under docs/wireframe/ only
|
|
410
|
+
const isHtml = ext === 'html' || ext === 'htm';
|
|
411
|
+
const isWireframeAsset = ext === 'css' || ext === 'js';
|
|
412
|
+
if (isHtml || isWireframeAsset) {
|
|
413
|
+
const normalizedRelative = filePath.replace(/\\/g, '/');
|
|
414
|
+
if (!isDocsWireframePath(normalizedRelative)) {
|
|
415
|
+
throw new common_1.NotFoundException('Not found');
|
|
416
|
+
}
|
|
417
|
+
}
|
|
418
|
+
// Path traversal boundary check (rejects '..' and paths outside projectRoot)
|
|
379
419
|
this.validatePathBoundary(filePath);
|
|
380
420
|
const abs = (0, path_1.resolve)(this.projectRoot, filePath);
|
|
421
|
+
// C-1: Symlink defense — reject symlinks outright (lstat check)
|
|
422
|
+
let lstatResult;
|
|
423
|
+
try {
|
|
424
|
+
lstatResult = (0, fs_1.lstatSync)(abs);
|
|
425
|
+
}
|
|
426
|
+
catch {
|
|
427
|
+
throw new common_1.NotFoundException('Not found');
|
|
428
|
+
}
|
|
429
|
+
if (lstatResult.isSymbolicLink()) {
|
|
430
|
+
throw new common_1.NotFoundException('Not found');
|
|
431
|
+
}
|
|
432
|
+
// C-1: Realpath re-validation — resolve any remaining symlinks in parent dirs
|
|
433
|
+
// projectRoot must also be realpath'd to handle /tmp → /private/tmp on macOS
|
|
434
|
+
let realAbs;
|
|
435
|
+
let realRoot;
|
|
436
|
+
try {
|
|
437
|
+
realAbs = (0, node_fs_1.realpathSync)(abs);
|
|
438
|
+
realRoot = (0, node_fs_1.realpathSync)(this.projectRoot);
|
|
439
|
+
}
|
|
440
|
+
catch {
|
|
441
|
+
throw new common_1.NotFoundException('Not found');
|
|
442
|
+
}
|
|
443
|
+
const realCmp = realAbs.replace(/\\/g, '/').toLowerCase();
|
|
444
|
+
const rootCmp = realRoot.replace(/\\/g, '/').toLowerCase();
|
|
445
|
+
if (realCmp !== rootCmp && !realCmp.startsWith(rootCmp + '/')) {
|
|
446
|
+
throw new common_1.NotFoundException('Not found');
|
|
447
|
+
}
|
|
381
448
|
if (!(0, fs_1.existsSync)(abs)) {
|
|
382
449
|
throw new common_1.NotFoundException('Not found');
|
|
383
450
|
}
|
|
384
|
-
|
|
451
|
+
// File size limit for HTML and wireframe assets (C-size limit: 5MB)
|
|
452
|
+
if (isHtml || isWireframeAsset) {
|
|
453
|
+
const stat = (0, fs_1.statSync)(abs);
|
|
454
|
+
if (stat.size > HTML_MAX_BYTES) {
|
|
455
|
+
throw new common_1.NotFoundException('Not found');
|
|
456
|
+
}
|
|
457
|
+
}
|
|
458
|
+
return { absPath: abs, mime };
|
|
385
459
|
}
|
|
386
460
|
validateEditable(filePath) {
|
|
387
461
|
const lower = filePath.toLowerCase();
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
"use strict";
|
|
2
2
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
-
exports.EXT_MIME = exports.IMAGE_EXTENSIONS = void 0;
|
|
3
|
+
exports.FILE_EXTENSIONS = exports.FILE_MIME = exports.EXT_MIME = exports.IMAGE_EXTENSIONS = void 0;
|
|
4
4
|
exports.IMAGE_EXTENSIONS = new Set(['png', 'jpg', 'jpeg', 'gif', 'webp']);
|
|
5
5
|
exports.EXT_MIME = {
|
|
6
6
|
png: 'image/png',
|
|
@@ -9,3 +9,13 @@ exports.EXT_MIME = {
|
|
|
9
9
|
gif: 'image/gif',
|
|
10
10
|
webp: 'image/webp',
|
|
11
11
|
};
|
|
12
|
+
/** MIME map for DOC.FILE — pdf/html/htm plus wireframe assets (css/js). svg/xhtml excluded. */
|
|
13
|
+
exports.FILE_MIME = {
|
|
14
|
+
pdf: 'application/pdf',
|
|
15
|
+
html: 'text/html; charset=utf-8',
|
|
16
|
+
htm: 'text/html; charset=utf-8',
|
|
17
|
+
css: 'text/css; charset=utf-8',
|
|
18
|
+
js: 'text/javascript; charset=utf-8',
|
|
19
|
+
};
|
|
20
|
+
/** Allowed file extensions for DOC.FILE endpoint */
|
|
21
|
+
exports.FILE_EXTENSIONS = new Set(Object.keys(exports.FILE_MIME));
|
|
@@ -19,6 +19,7 @@ const rxjs_1 = require("rxjs");
|
|
|
19
19
|
const operators_1 = require("rxjs/operators");
|
|
20
20
|
const workspace_event_bus_js_1 = require("./workspace-event.bus.js");
|
|
21
21
|
const workspace_decorator_js_1 = require("../../common/decorators/workspace.decorator.js");
|
|
22
|
+
const skip_request_log_decorator_js_1 = require("../../common/decorator/skip-request-log.decorator.js");
|
|
22
23
|
const KEEPALIVE_INTERVAL_MS = 15_000;
|
|
23
24
|
let WorkspaceEventsController = class WorkspaceEventsController {
|
|
24
25
|
bus;
|
|
@@ -42,6 +43,7 @@ __decorate([
|
|
|
42
43
|
__metadata("design:returntype", rxjs_1.Observable)
|
|
43
44
|
], WorkspaceEventsController.prototype, "stream", null);
|
|
44
45
|
exports.WorkspaceEventsController = WorkspaceEventsController = __decorate([
|
|
46
|
+
(0, skip_request_log_decorator_js_1.SkipRequestLog)(),
|
|
45
47
|
(0, common_1.Controller)('ws/:slug/events'),
|
|
46
48
|
__metadata("design:paramtypes", [workspace_event_bus_js_1.WorkspaceEventBus])
|
|
47
49
|
], WorkspaceEventsController);
|
|
@@ -9,7 +9,7 @@ var __metadata = (this && this.__metadata) || function (k, v) {
|
|
|
9
9
|
if (typeof Reflect === "object" && typeof Reflect.metadata === "function") return Reflect.metadata(k, v);
|
|
10
10
|
};
|
|
11
11
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
12
|
-
exports.SendMessageDto = exports.ImageAttachmentDto = void 0;
|
|
12
|
+
exports.SendMessageDto = exports.OverdriveDto = exports.ImageAttachmentDto = void 0;
|
|
13
13
|
const swagger_1 = require("@nestjs/swagger");
|
|
14
14
|
const class_validator_1 = require("class-validator");
|
|
15
15
|
const class_transformer_1 = require("class-transformer");
|
|
@@ -37,23 +37,55 @@ __decorate([
|
|
|
37
37
|
(0, swagger_1.ApiPropertyOptional)({ description: 'Original filename' }),
|
|
38
38
|
__metadata("design:type", String)
|
|
39
39
|
], ImageAttachmentDto.prototype, "filename", void 0);
|
|
40
|
+
class OverdriveDto {
|
|
41
|
+
action;
|
|
42
|
+
turns;
|
|
43
|
+
}
|
|
44
|
+
exports.OverdriveDto = OverdriveDto;
|
|
45
|
+
__decorate([
|
|
46
|
+
(0, class_validator_1.IsIn)(['enable-count', 'enable-latch', 'disable', 'use-current']),
|
|
47
|
+
(0, swagger_1.ApiProperty)({
|
|
48
|
+
description: 'Overdrive (boost) action to apply for this request',
|
|
49
|
+
enum: ['enable-count', 'enable-latch', 'disable', 'use-current'],
|
|
50
|
+
}),
|
|
51
|
+
__metadata("design:type", String)
|
|
52
|
+
], OverdriveDto.prototype, "action", void 0);
|
|
53
|
+
__decorate([
|
|
54
|
+
(0, class_validator_1.IsInt)(),
|
|
55
|
+
(0, class_validator_1.Min)(1),
|
|
56
|
+
(0, class_validator_1.IsOptional)(),
|
|
57
|
+
(0, swagger_1.ApiPropertyOptional)({ description: 'Turn count for enable-count (default 3)', minimum: 1 }),
|
|
58
|
+
__metadata("design:type", Number)
|
|
59
|
+
], OverdriveDto.prototype, "turns", void 0);
|
|
60
|
+
/** True when the request only toggles boost state and carries no message. */
|
|
61
|
+
function isStateOnlyOverdrive(o) {
|
|
62
|
+
return ((!o.message || !o.message.trim()) &&
|
|
63
|
+
!!o.overdrive &&
|
|
64
|
+
o.overdrive.action !== 'use-current');
|
|
65
|
+
}
|
|
40
66
|
class SendMessageDto {
|
|
67
|
+
// message/agent are required for a normal send, but NOT for a state-only boost
|
|
68
|
+
// toggle (enable-count / enable-latch / disable), which carries `overdrive` alone.
|
|
41
69
|
message;
|
|
42
70
|
agent;
|
|
43
71
|
title;
|
|
44
72
|
mode = 'query';
|
|
73
|
+
model;
|
|
45
74
|
images;
|
|
46
75
|
context;
|
|
47
76
|
browserSessionId;
|
|
77
|
+
overdrive;
|
|
48
78
|
}
|
|
49
79
|
exports.SendMessageDto = SendMessageDto;
|
|
50
80
|
__decorate([
|
|
81
|
+
(0, class_validator_1.ValidateIf)((o) => !isStateOnlyOverdrive(o)),
|
|
51
82
|
(0, class_validator_1.IsString)(),
|
|
52
83
|
(0, class_validator_1.IsNotEmpty)(),
|
|
53
84
|
(0, swagger_1.ApiProperty)({ description: 'Message content to send' }),
|
|
54
85
|
__metadata("design:type", String)
|
|
55
86
|
], SendMessageDto.prototype, "message", void 0);
|
|
56
87
|
__decorate([
|
|
88
|
+
(0, class_validator_1.ValidateIf)((o) => !isStateOnlyOverdrive(o)),
|
|
57
89
|
(0, class_validator_1.IsString)(),
|
|
58
90
|
(0, class_validator_1.IsNotEmpty)(),
|
|
59
91
|
(0, swagger_1.ApiProperty)({ description: 'Target agent ID' }),
|
|
@@ -71,6 +103,12 @@ __decorate([
|
|
|
71
103
|
(0, swagger_1.ApiPropertyOptional)({ description: 'Execution mode: query (read-only, default) or execute (full permissions)', default: 'query' }),
|
|
72
104
|
__metadata("design:type", String)
|
|
73
105
|
], SendMessageDto.prototype, "mode", void 0);
|
|
106
|
+
__decorate([
|
|
107
|
+
(0, class_validator_1.IsString)(),
|
|
108
|
+
(0, class_validator_1.IsOptional)(),
|
|
109
|
+
(0, swagger_1.ApiPropertyOptional)({ description: 'Model override for this request (falls back to agent default)' }),
|
|
110
|
+
__metadata("design:type", String)
|
|
111
|
+
], SendMessageDto.prototype, "model", void 0);
|
|
74
112
|
__decorate([
|
|
75
113
|
(0, class_validator_1.IsOptional)(),
|
|
76
114
|
(0, class_validator_1.ValidateNested)({ each: true }),
|
|
@@ -90,3 +128,13 @@ __decorate([
|
|
|
90
128
|
(0, swagger_1.ApiPropertyOptional)({ description: 'Chrome X browser MCP session id for reverse browser-tool calls' }),
|
|
91
129
|
__metadata("design:type", String)
|
|
92
130
|
], SendMessageDto.prototype, "browserSessionId", void 0);
|
|
131
|
+
__decorate([
|
|
132
|
+
(0, class_validator_1.IsOptional)(),
|
|
133
|
+
(0, class_validator_1.ValidateNested)(),
|
|
134
|
+
(0, class_transformer_1.Type)(() => OverdriveDto),
|
|
135
|
+
(0, swagger_1.ApiPropertyOptional)({
|
|
136
|
+
description: 'Overdrive (boost) action. enable-count: N-turn countdown; enable-latch: until disabled; disable: off; use-current: read thread state.',
|
|
137
|
+
type: OverdriveDto,
|
|
138
|
+
}),
|
|
139
|
+
__metadata("design:type", OverdriveDto)
|
|
140
|
+
], SendMessageDto.prototype, "overdrive", void 0);
|
|
@@ -50,7 +50,7 @@ let MessageController = class MessageController {
|
|
|
50
50
|
}
|
|
51
51
|
// MSG.SEND: Send message to thread (spawns crewx CLI in background)
|
|
52
52
|
async sendMessage(threadId, body, ws) {
|
|
53
|
-
const data = await this.messageService.sendMessage(threadId, body.message, body.agent, body.mode, body.title, ws.id, body.images, body.context, body.browserSessionId);
|
|
53
|
+
const data = await this.messageService.sendMessage(threadId, body.message, body.agent, body.mode, body.title, ws.id, body.images, body.context, body.browserSessionId, body.overdrive, body.model);
|
|
54
54
|
return { success: true, data };
|
|
55
55
|
}
|
|
56
56
|
serveAttachment(threadId, filename, res) {
|
|
@@ -13,12 +13,13 @@ const message_service_js_1 = require("./message.service.js");
|
|
|
13
13
|
const thread_service_js_1 = require("../thread/thread.service.js");
|
|
14
14
|
const box_service_js_1 = require("../box/box.service.js");
|
|
15
15
|
const crewx_module_js_1 = require("../../modules/crewx.module.js");
|
|
16
|
+
const settings_module_js_1 = require("../settings/settings.module.js");
|
|
16
17
|
let MessageModule = class MessageModule {
|
|
17
18
|
};
|
|
18
19
|
exports.MessageModule = MessageModule;
|
|
19
20
|
exports.MessageModule = MessageModule = __decorate([
|
|
20
21
|
(0, common_1.Module)({
|
|
21
|
-
imports: [crewx_module_js_1.CrewxModule],
|
|
22
|
+
imports: [crewx_module_js_1.CrewxModule, settings_module_js_1.SettingsModule],
|
|
22
23
|
controllers: [message_controller_js_1.MessageController],
|
|
23
24
|
providers: [message_service_js_1.MessageService, thread_service_js_1.ThreadService, box_service_js_1.BoxService],
|
|
24
25
|
})
|
|
@@ -8,6 +8,9 @@ var __decorate = (this && this.__decorate) || function (decorators, target, key,
|
|
|
8
8
|
var __metadata = (this && this.__metadata) || function (k, v) {
|
|
9
9
|
if (typeof Reflect === "object" && typeof Reflect.metadata === "function") return Reflect.metadata(k, v);
|
|
10
10
|
};
|
|
11
|
+
var __param = (this && this.__param) || function (paramIndex, decorator) {
|
|
12
|
+
return function (target, key) { decorator(target, key, paramIndex); }
|
|
13
|
+
};
|
|
11
14
|
var MessageService_1;
|
|
12
15
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
13
16
|
exports.MessageService = void 0;
|
|
@@ -20,15 +23,18 @@ const thread_repository_js_1 = require("../../repository/thread.repository.js");
|
|
|
20
23
|
const sdk_1 = require("@crewx/sdk");
|
|
21
24
|
const crewx_pool_service_js_1 = require("../../modules/crewx-pool.service.js");
|
|
22
25
|
const workspace_context_store_js_1 = require("../../common/workspace-context.store.js");
|
|
26
|
+
const settings_service_js_1 = require("../settings/settings.service.js");
|
|
23
27
|
let MessageService = MessageService_1 = class MessageService {
|
|
24
28
|
crewxPool;
|
|
25
29
|
threadService;
|
|
26
30
|
threadRepo;
|
|
31
|
+
settingsService;
|
|
27
32
|
logger = new common_1.Logger(MessageService_1.name);
|
|
28
|
-
constructor(crewxPool, threadService, threadRepo) {
|
|
33
|
+
constructor(crewxPool, threadService, threadRepo, settingsService) {
|
|
29
34
|
this.crewxPool = crewxPool;
|
|
30
35
|
this.threadService = threadService;
|
|
31
36
|
this.threadRepo = threadRepo;
|
|
37
|
+
this.settingsService = settingsService;
|
|
32
38
|
}
|
|
33
39
|
// MSG.LIST: List messages with Hot/Warm zone integration
|
|
34
40
|
listMessages(threadId, boxService, params, workspaceId) {
|
|
@@ -83,6 +89,9 @@ let MessageService = MessageService_1 = class MessageService {
|
|
|
83
89
|
if (meta?.logs) {
|
|
84
90
|
item.logs = this.filterLogs(meta.logs);
|
|
85
91
|
}
|
|
92
|
+
if (meta?.progressSteps) {
|
|
93
|
+
item.progressSteps = meta.progressSteps;
|
|
94
|
+
}
|
|
86
95
|
}
|
|
87
96
|
}
|
|
88
97
|
// 2) Include logs for latest logsTail completed tasks
|
|
@@ -99,6 +108,9 @@ let MessageService = MessageService_1 = class MessageService {
|
|
|
99
108
|
if (meta?.logs) {
|
|
100
109
|
completedItems[i].logs = this.filterLogs(meta.logs);
|
|
101
110
|
}
|
|
111
|
+
if (meta?.progressSteps) {
|
|
112
|
+
completedItems[i].progressSteps = meta.progressSteps;
|
|
113
|
+
}
|
|
102
114
|
}
|
|
103
115
|
}
|
|
104
116
|
// Merge warm boxes + hot tasks in chronological order
|
|
@@ -113,8 +125,23 @@ let MessageService = MessageService_1 = class MessageService {
|
|
|
113
125
|
};
|
|
114
126
|
}
|
|
115
127
|
// MSG.SEND: Send message — fire crewx SDK; ConversationPlugin persists threads row
|
|
116
|
-
async sendMessage(threadId, message, agent, mode = 'query', title, workspaceId, images, context, browserSessionId) {
|
|
128
|
+
async sendMessage(threadId, message, agent, mode = 'query', title, workspaceId, images, context, browserSessionId, overdrive, model) {
|
|
117
129
|
this.validateId(threadId);
|
|
130
|
+
// A boost toggle (enable-count / enable-latch / disable) carries no message.
|
|
131
|
+
// It only mutates thread overdrive state and must NOT dispatch an agent.
|
|
132
|
+
// Detecting this lets the same endpoint serve "send message" and "toggle boost"
|
|
133
|
+
// without spawning a phantom empty task.
|
|
134
|
+
const isStateOnlyOverdrive = (!message || !message.trim()) &&
|
|
135
|
+
!!overdrive &&
|
|
136
|
+
overdrive.action !== 'use-current';
|
|
137
|
+
if (isStateOnlyOverdrive) {
|
|
138
|
+
this.threadRepo.resolveOverdriveForRequest(threadId, workspaceId, overdrive.action, {
|
|
139
|
+
turns: overdrive.turns,
|
|
140
|
+
defaultTurns: overdrive.turns ?? this.getOverdriveDefaultTurns(),
|
|
141
|
+
updatedBy: 'ui',
|
|
142
|
+
});
|
|
143
|
+
return { taskId: null, status: 'ok', agent: null };
|
|
144
|
+
}
|
|
118
145
|
if (!message || !message.trim()) {
|
|
119
146
|
throw new common_1.BadRequestException('message is required');
|
|
120
147
|
}
|
|
@@ -131,6 +158,31 @@ let MessageService = MessageService_1 = class MessageService {
|
|
|
131
158
|
this.logger.warn(`updateThreadTitle skipped [thread=${threadId}]: ${err.message}`);
|
|
132
159
|
}
|
|
133
160
|
}
|
|
161
|
+
// ensureThread MUST run before resolveOverdriveForRequest so that the
|
|
162
|
+
// thread row exists when overdrive state is read/written. Without this
|
|
163
|
+
// ordering, the very first message on a new thread (ThreadNewPage) would
|
|
164
|
+
// find no thread row and overdrive would be silently ignored.
|
|
165
|
+
const crewx = await this.crewxPool.getForCurrentRequest();
|
|
166
|
+
const ensureWorkspaceId = workspaceId ?? crewx.workspaceId;
|
|
167
|
+
// Pre-call ensureThread so REST response is safe to follow with GET /threads/:id
|
|
168
|
+
// (frontend ThreadNewPage navigates to /threads/:id right after POST /messages).
|
|
169
|
+
const conv = crewx.plugins.find((p) => p.name === 'conversation')?.conversationProvider;
|
|
170
|
+
try {
|
|
171
|
+
await conv?.ensureThread(threadId, 'cli', ensureWorkspaceId);
|
|
172
|
+
}
|
|
173
|
+
catch (err) {
|
|
174
|
+
this.logger.warn(`ensureThread skipped [thread=${threadId}]: ${err.message}`);
|
|
175
|
+
}
|
|
176
|
+
// Resolve overdrive state atomically before dispatching the agent call.
|
|
177
|
+
// This runs inside a single SQLite transaction so concurrent requests
|
|
178
|
+
// cannot double-decrement the remaining count.
|
|
179
|
+
const overdriveAction = overdrive?.action ?? 'use-current';
|
|
180
|
+
const odResult = this.threadRepo.resolveOverdriveForRequest(threadId, workspaceId, overdriveAction, {
|
|
181
|
+
turns: overdrive?.turns,
|
|
182
|
+
defaultTurns: overdrive?.turns ?? this.getOverdriveDefaultTurns(),
|
|
183
|
+
consumeCurrent: overdriveAction === 'enable-count',
|
|
184
|
+
updatedBy: 'ui',
|
|
185
|
+
});
|
|
134
186
|
const savedImages = this.saveImages(threadId, images);
|
|
135
187
|
let finalMessage = message;
|
|
136
188
|
if (savedImages.length > 0) {
|
|
@@ -154,23 +206,31 @@ let MessageService = MessageService_1 = class MessageService {
|
|
|
154
206
|
if (browserSessionId?.trim()) {
|
|
155
207
|
metadata.browserSessionId = browserSessionId.trim();
|
|
156
208
|
}
|
|
209
|
+
// Propagate the overdrive signal + resulting state to the SDK.
|
|
210
|
+
// The SDK (WI-20260702-OD-CORE) reads metadata.overdrive to apply the
|
|
211
|
+
// configured provider/model override profile.
|
|
212
|
+
// The metadata is also forwarded to task:start → persisted in task.metadata,
|
|
213
|
+
// satisfying the "task records boost execution" requirement.
|
|
214
|
+
if (odResult?.applied) {
|
|
215
|
+
const overdriveMode = odResult.appliedMode ?? (odResult.state === 'latch' ? 'latch' : 'count');
|
|
216
|
+
metadata.overdrive = true;
|
|
217
|
+
metadata.overdriveState = overdriveMode;
|
|
218
|
+
metadata.overdriveMode = mode;
|
|
219
|
+
}
|
|
157
220
|
const sdkOptions = {
|
|
158
221
|
threadId,
|
|
159
222
|
taskId,
|
|
160
223
|
platform: 'cli',
|
|
161
224
|
metadata,
|
|
162
225
|
cwd: (0, workspace_context_store_js_1.getWorkspacePath)(),
|
|
226
|
+
...(model ? { model } : {}),
|
|
227
|
+
// Forward the resolved overdrive signal to the SDK so it applies the
|
|
228
|
+
// configured provider/model override profile for this request. Without
|
|
229
|
+
// this, the SDK never sees the boost signal and runs the base model.
|
|
230
|
+
...(odResult?.applied
|
|
231
|
+
? { overdrive: { mode: odResult.appliedMode === 'latch' ? 'latch' : 'count' } }
|
|
232
|
+
: {}),
|
|
163
233
|
};
|
|
164
|
-
const crewx = await this.crewxPool.getForCurrentRequest();
|
|
165
|
-
// Pre-call ensureThread so REST response is safe to follow with GET /threads/:id
|
|
166
|
-
// (frontend ThreadNewPage navigates to /threads/:id right after POST /messages).
|
|
167
|
-
const conv = crewx.plugins.find((p) => p.name === 'conversation')?.conversationProvider;
|
|
168
|
-
try {
|
|
169
|
-
await conv?.ensureThread(threadId, sdkOptions.platform, crewx.workspaceId);
|
|
170
|
-
}
|
|
171
|
-
catch (err) {
|
|
172
|
-
this.logger.warn(`ensureThread skipped [thread=${threadId}]: ${err.message}`);
|
|
173
|
-
}
|
|
174
234
|
const agentRef = `@${agent}`;
|
|
175
235
|
const sdkCall = mode === 'execute'
|
|
176
236
|
? crewx.execute(agentRef, finalMessage, sdkOptions)
|
|
@@ -204,6 +264,14 @@ let MessageService = MessageService_1 = class MessageService {
|
|
|
204
264
|
return { relativePath, mimeType: img.mimeType, filename: img.filename };
|
|
205
265
|
});
|
|
206
266
|
}
|
|
267
|
+
getOverdriveDefaultTurns() {
|
|
268
|
+
try {
|
|
269
|
+
return this.settingsService?.getSettings().overdrive.defaultTurns;
|
|
270
|
+
}
|
|
271
|
+
catch {
|
|
272
|
+
return undefined;
|
|
273
|
+
}
|
|
274
|
+
}
|
|
207
275
|
validateId(id) {
|
|
208
276
|
if (!/^[\w\p{L}\p{N}._-]+$/u.test(id)) {
|
|
209
277
|
throw new common_1.BadRequestException('Invalid ID format');
|
|
@@ -217,7 +285,9 @@ let MessageService = MessageService_1 = class MessageService {
|
|
|
217
285
|
exports.MessageService = MessageService;
|
|
218
286
|
exports.MessageService = MessageService = MessageService_1 = __decorate([
|
|
219
287
|
(0, common_1.Injectable)(),
|
|
288
|
+
__param(3, (0, common_1.Optional)()),
|
|
220
289
|
__metadata("design:paramtypes", [crewx_pool_service_js_1.CrewxPoolService,
|
|
221
290
|
thread_service_js_1.ThreadService,
|
|
222
|
-
thread_repository_js_1.ThreadRepository
|
|
291
|
+
thread_repository_js_1.ThreadRepository,
|
|
292
|
+
settings_service_js_1.SettingsService])
|
|
223
293
|
], MessageService);
|