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.
Files changed (104) hide show
  1. package/dist/assets/MarketPage-DDPL2d7x.js +36 -0
  2. package/dist/assets/{PromptTab-hdXDbbT6.js → PromptTab-UC3K4qx2.js} +3 -3
  3. package/dist/assets/{arc--CBVVcyH.js → arc-DdP3-fUR.js} +1 -1
  4. package/dist/assets/{architectureDiagram-UYN6MBPD-DUiYwqJn.js → architectureDiagram-UYN6MBPD-D16VON1i.js} +1 -1
  5. package/dist/assets/{blockDiagram-ZHA2E4KO-DV86vAgG.js → blockDiagram-ZHA2E4KO-qs1dIRkl.js} +4 -4
  6. package/dist/assets/{c4Diagram-6F5ED5ID-CvWE7Cx_.js → c4Diagram-6F5ED5ID-BeDy8akQ.js} +1 -1
  7. package/dist/assets/channel-DtrxBSkc.js +1 -0
  8. package/dist/assets/{chunk-5HRBRIJM-DAxM5Ul_.js → chunk-5HRBRIJM-BC1zIb11.js} +1 -1
  9. package/dist/assets/{chunk-7U56Z5CX-BuYaEj8L.js → chunk-7U56Z5CX-BuGil3QD.js} +1 -1
  10. package/dist/assets/{chunk-ASOPGD6M-2Kx0e45C.js → chunk-ASOPGD6M-DmKdDtBQ.js} +1 -1
  11. package/dist/assets/{chunk-KFBOBJHC-CR4QS6Rt.js → chunk-KFBOBJHC-D3BvHLPa.js} +1 -1
  12. package/dist/assets/{chunk-T2TOU4HS-CffcwNHx.js → chunk-T2TOU4HS-B5MoUp-N.js} +1 -1
  13. package/dist/assets/{chunk-TMUBEWPD-BGpYtekN.js → chunk-TMUBEWPD-AvWe_lsV.js} +1 -1
  14. package/dist/assets/classDiagram-LNE6IOMH-BMT5B_JB.js +1 -0
  15. package/dist/assets/classDiagram-v2-MQ7JQ4JX-BMT5B_JB.js +1 -0
  16. package/dist/assets/{dagre-4EVJKHTY-yBWukkrE.js → dagre-4EVJKHTY-B6DewFyM.js} +1 -1
  17. package/dist/assets/{diagram-QW4FP2JN-CIr1PLq3.js → diagram-QW4FP2JN-BTsQ8JSd.js} +1 -1
  18. package/dist/assets/{erDiagram-6RL3IURR-xG7SygyU.js → erDiagram-6RL3IURR-BM5nvgVR.js} +1 -1
  19. package/dist/assets/{flowDiagram-7ASYPVHJ-CKalfOqp.js → flowDiagram-7ASYPVHJ-D1UCCuOG.js} +1 -1
  20. package/dist/assets/{ganttDiagram-NTVNEXSI-Bnx7zrX4.js → ganttDiagram-NTVNEXSI-CNIGs7Cn.js} +1 -1
  21. package/dist/assets/{gitGraph-YCYPL57B-TQlF0fiA.js → gitGraph-YCYPL57B-DSLjx1yQ.js} +1 -1
  22. package/dist/assets/{gitGraphDiagram-NRZ2UAAF-DxlaFkwO.js → gitGraphDiagram-NRZ2UAAF-B11zX1Fa.js} +1 -1
  23. package/dist/assets/{infoDiagram-A4XQUW5V-B1-arkqa.js → infoDiagram-A4XQUW5V-DkjvODYl.js} +1 -1
  24. package/dist/assets/{journeyDiagram-G5WM74LC-Cj439GUX.js → journeyDiagram-G5WM74LC-BkCQPMn6.js} +1 -1
  25. package/dist/assets/{kanban-definition-QRCXZQQD-BvrIwEIc.js → kanban-definition-QRCXZQQD-_OX7pNfJ.js} +1 -1
  26. package/dist/assets/{linear-BKYNK0ri.js → linear-BYjwVAR6.js} +1 -1
  27. package/dist/assets/main-CZdKLdUl.css +10 -0
  28. package/dist/assets/main-CkkZNCy-.js +1561 -0
  29. package/dist/assets/{mindmap-definition-GWI6TPTV-BExfJY0G.js → mindmap-definition-GWI6TPTV-BzYpSnv8.js} +1 -1
  30. package/dist/assets/ort-wasm-simd-threaded.asyncify-DMmc6YqF.wasm +0 -0
  31. package/dist/assets/{pieDiagram-YF2LJOPJ-RPWpvrC8.js → pieDiagram-YF2LJOPJ-CEFg6ao0.js} +1 -1
  32. package/dist/assets/{quadrantDiagram-OS5C2QUG-DmbBzk5Q.js → quadrantDiagram-OS5C2QUG-_5w2x3VV.js} +1 -1
  33. package/dist/assets/{requirementDiagram-MIRIMTAZ-DFQuFXq3.js → requirementDiagram-MIRIMTAZ-B_VQYReZ.js} +1 -1
  34. package/dist/assets/{sankeyDiagram-Y46BX6SQ-p_bw56lJ.js → sankeyDiagram-Y46BX6SQ-CgmOlLhh.js} +1 -1
  35. package/dist/assets/{sequenceDiagram-G6AWOVSC-DTbBkLeD.js → sequenceDiagram-G6AWOVSC-MGVt0Ai2.js} +1 -1
  36. package/dist/assets/{stateDiagram-MAYHULR4-DBvC6UcF.js → stateDiagram-MAYHULR4-DaZjgTu-.js} +1 -1
  37. package/dist/assets/stateDiagram-v2-4JROLMXI-DN1wGYkV.js +1 -0
  38. package/dist/assets/{timeline-definition-U7ZMHBDA-B6utvIg1.js → timeline-definition-U7ZMHBDA-CusetmK6.js} +1 -1
  39. package/dist/assets/transformers.web-BN8Ug43_.js +45 -0
  40. package/dist/assets/{xychartDiagram-6QU3TZC5-BHG0s1Ye.js → xychartDiagram-6QU3TZC5-CTndf-if.js} +1 -1
  41. package/dist/index.html +2 -2
  42. package/dist-server/app.module.js +4 -0
  43. package/dist-server/bootstrap/codex-writable-roots.js +88 -0
  44. package/dist-server/bootstrap/crewx-server.js +10 -2
  45. package/dist-server/common/decorator/skip-request-log.decorator.js +8 -0
  46. package/dist-server/common/interceptor/http-logging.interceptor.js +17 -4
  47. package/dist-server/common/task-log/stdout-json-fallback.js +200 -0
  48. package/dist-server/domain/agent/agent.controller.js +14 -0
  49. package/dist-server/domain/agent/agent.service.js +246 -18
  50. package/dist-server/domain/agent/dto/create-agent.dto.js +12 -0
  51. package/dist-server/domain/agent/dto/remote-provider.dto.js +82 -0
  52. package/dist-server/domain/agent/dto/reorder-agents.dto.js +51 -0
  53. package/dist-server/domain/agent/dto/update-agent.dto.js +3 -1
  54. package/dist-server/domain/agent/provider.controller.js +12 -1
  55. package/dist-server/domain/doc/doc.controller.js +43 -9
  56. package/dist-server/domain/doc/doc.service.js +80 -6
  57. package/dist-server/domain/doc/image-constants.js +11 -1
  58. package/dist-server/domain/events/workspace-events.controller.js +2 -0
  59. package/dist-server/domain/message/dto/send-message.dto.js +49 -1
  60. package/dist-server/domain/message/message.controller.js +1 -1
  61. package/dist-server/domain/message/message.module.js +2 -1
  62. package/dist-server/domain/message/message.service.js +83 -13
  63. package/dist-server/domain/settings/settings.service.js +188 -1
  64. package/dist-server/domain/system/system.controller.js +41 -0
  65. package/dist-server/domain/system/system.module.js +19 -0
  66. package/dist-server/domain/task/task.constants.js +8 -1
  67. package/dist-server/domain/task/task.service.js +59 -67
  68. package/dist-server/domain/thread/dto/list-threads.dto.js +18 -0
  69. package/dist-server/domain/thread/overdrive.types.js +38 -0
  70. package/dist-server/domain/thread/thread.service.js +83 -70
  71. package/dist-server/domain/usage/usage.controller.js +18 -0
  72. package/dist-server/domain/usage/usage.module.js +1 -0
  73. package/dist-server/domain/usage/usage.service.js +246 -4
  74. package/dist-server/domain/usage-report/dto/usage-report.dto.js +2 -0
  75. package/dist-server/domain/usage-report/usage-report.controller.js +72 -0
  76. package/dist-server/domain/usage-report/usage-report.module.js +23 -0
  77. package/dist-server/domain/usage-report/usage-report.service.js +135 -0
  78. package/dist-server/domain/wi/wi.service.js +45 -3
  79. package/dist-server/main.js +1 -0
  80. package/dist-server/repository/repository.module.js +2 -0
  81. package/dist-server/repository/task.repository.js +56 -0
  82. package/dist-server/repository/thread.repository.js +13 -0
  83. package/dist-server/repository/usage-report.repository.js +27 -0
  84. package/package.json +9 -9
  85. package/packages/cli/dist/bootstrap/codex-writable-roots.d.ts +45 -0
  86. package/packages/cli/dist/bootstrap/codex-writable-roots.js +88 -0
  87. package/packages/cli/dist/bootstrap/crewx-cli.js +2 -0
  88. package/packages/cli/dist/commands/execute.js +10 -2
  89. package/packages/cli/dist/commands/parse-common-flags.d.ts +5 -1
  90. package/packages/cli/dist/commands/parse-common-flags.js +9 -2
  91. package/packages/cli/dist/commands/ps.js +51 -2
  92. package/packages/cli/dist/commands/query.js +10 -2
  93. package/packages/cli/dist/commands/restart.js +20 -6
  94. package/packages/cli/dist/commands/write-output.d.ts +3 -0
  95. package/packages/cli/dist/commands/write-output.js +24 -0
  96. package/packages/cli/dist/main.js +7 -0
  97. package/packages/cli/package.json +1 -1
  98. package/dist/assets/MarketPage-T1o_oIrw.js +0 -41
  99. package/dist/assets/channel-7q16D7P9.js +0 -1
  100. package/dist/assets/classDiagram-LNE6IOMH-C6qjk4uc.js +0 -1
  101. package/dist/assets/classDiagram-v2-MQ7JQ4JX-C6qjk4uc.js +0 -1
  102. package/dist/assets/main-C5mwv79r.css +0 -10
  103. package/dist/assets/main-DmvFaz-d.js +0 -1486
  104. 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
- res.set({
63
- 'Content-Type': mime,
64
- 'Content-Length': stat.size,
65
- 'Content-Disposition': `inline; filename="${(0, path_1.basename)(absPath)}"`,
66
- 'Cache-Control': 'public, max-age=3600',
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
- if (entry.name.endsWith('.md') || image_constants_js_1.IMAGE_EXTENSIONS.has(ext) || ext === 'pdf') {
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
- * Only PDF is allowed in v1. Non-PDF and missing files return 404.
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
- const ext = filePath.split('.').pop()?.toLowerCase() ?? '';
376
- if (ext !== 'pdf') {
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
- return { absPath: abs, mime: 'application/pdf' };
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);