@kapeta/local-cluster-service 0.52.2 → 0.53.0

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/CHANGELOG.md CHANGED
@@ -1,3 +1,17 @@
1
+ # [0.53.0](https://github.com/kapetacom/local-cluster-service/compare/v0.52.3...v0.53.0) (2024-06-12)
2
+
3
+
4
+ ### Features
5
+
6
+ * Emit code fixing events to UI ([#172](https://github.com/kapetacom/local-cluster-service/issues/172)) ([c10f5d0](https://github.com/kapetacom/local-cluster-service/commit/c10f5d03fdcf696085b2114aae45510a46ee6ec3))
7
+
8
+ ## [0.52.3](https://github.com/kapetacom/local-cluster-service/compare/v0.52.2...v0.52.3) (2024-06-12)
9
+
10
+
11
+ ### Bug Fixes
12
+
13
+ * reject promise on ERROR_INTERNAL ([d796a58](https://github.com/kapetacom/local-cluster-service/commit/d796a58f2a810f5dc14aebac2bd4941badc46b5c))
14
+
1
15
  ## [0.52.2](https://github.com/kapetacom/local-cluster-service/compare/v0.52.1...v0.52.2) (2024-06-12)
2
16
 
3
17
 
@@ -25,6 +25,7 @@ export declare class StormCodegen {
25
25
  * Generates the code for a block and sends it to the AI
26
26
  */
27
27
  private processBlockCode;
28
+ private emitBlockStatus;
28
29
  private verifyAndFixCode;
29
30
  private tryToFixFile;
30
31
  private classifyErrors;
@@ -34,6 +34,7 @@ exports.StormCodegen = void 0;
34
34
  const codegen_1 = require("@kapeta/codegen");
35
35
  const codeGeneratorManager_1 = require("../codeGeneratorManager");
36
36
  const stormClient_1 = require("./stormClient");
37
+ const events_1 = require("./events");
37
38
  const event_parser_1 = require("./event-parser");
38
39
  const stream_1 = require("./stream");
39
40
  const nodejs_utils_1 = require("@kapeta/nodejs-utils");
@@ -44,8 +45,8 @@ const node_os_1 = __importDefault(require("node:os"));
44
45
  const fs_1 = require("fs");
45
46
  const yaml_1 = __importDefault(require("yaml"));
46
47
  const fs = __importStar(require("node:fs"));
47
- const uuid_1 = require("uuid");
48
48
  const SIMULATED_DELAY = 1000;
49
+ const ENABLE_SIMULATED_DELAY = false;
49
50
  class SimulatedFileDelay {
50
51
  file;
51
52
  stream;
@@ -149,43 +150,8 @@ class StormCodegen {
149
150
  },
150
151
  });
151
152
  break;
152
- case 'FILE_START':
153
- case 'FILE_CHUNK_RESET':
154
- this.out.emit('data', {
155
- ...data,
156
- payload: {
157
- ...data.payload,
158
- blockName,
159
- blockRef,
160
- instanceId,
161
- },
162
- });
163
- break;
164
- case 'FILE_CHUNK':
165
- this.out.emit('data', {
166
- ...data,
167
- payload: {
168
- ...data.payload,
169
- blockName,
170
- blockRef,
171
- instanceId,
172
- },
173
- });
174
- break;
175
- case 'FILE_STATE':
176
- this.out.emit('data', {
177
- ...data,
178
- payload: {
179
- ...data.payload,
180
- blockName,
181
- blockRef,
182
- instanceId,
183
- },
184
- });
185
- break;
186
153
  case 'FILE_DONE':
187
154
  return this.handleFileDoneOutput(blockUri, blockName, data);
188
- break;
189
155
  }
190
156
  }
191
157
  handleFileEvents(blockUri, blockName, data) {
@@ -299,10 +265,11 @@ class StormCodegen {
299
265
  }
300
266
  // Gather the context files for implementation. These will be all be passed to the AI
301
267
  const contextFiles = relevantFiles.filter((file) => ![codegen_1.AIFileTypes.SERVICE, codegen_1.AIFileTypes.WEB_SCREEN].includes(file.type));
268
+ const blockUri = (0, nodejs_utils_1.parseKapetaUri)(block.uri);
302
269
  // Send the service and UI templates to the AI. These will be sent one-by-one in addition to the context files
303
270
  const serviceFiles = allFiles.filter((file) => file.type === codegen_1.AIFileTypes.SERVICE);
304
271
  if (serviceFiles.length > 0) {
305
- await this.processTemplates((0, nodejs_utils_1.parseKapetaUri)(block.uri), block.aiName, stormClient_1.stormClient.createServiceImplementation.bind(stormClient_1.stormClient), serviceFiles, contextFiles);
272
+ await this.processTemplates(blockUri, block.aiName, stormClient_1.stormClient.createServiceImplementation.bind(stormClient_1.stormClient), serviceFiles, contextFiles);
306
273
  }
307
274
  const basePath = this.getBasePath(block.content.metadata.name);
308
275
  if (this.isAborted()) {
@@ -322,7 +289,7 @@ class StormCodegen {
322
289
  const filePath = (0, path_2.join)(basePath, screenFile.payload.filename);
323
290
  await (0, promises_1.writeFile)(filePath, screenFile.payload.content);
324
291
  }
325
- const screenFilesConverted = screenFiles.map(screenFile => {
292
+ const screenFilesConverted = screenFiles.map((screenFile) => {
326
293
  return {
327
294
  filename: screenFile.payload.filename,
328
295
  content: screenFile.payload.content,
@@ -332,10 +299,12 @@ class StormCodegen {
332
299
  };
333
300
  });
334
301
  allFiles.push(...screenFilesConverted);
302
+ const blockRef = block.uri;
303
+ this.emitBlockStatus(blockUri, block.aiName, events_1.StormEventBlockStatusType.QA);
335
304
  const filesToBeFixed = serviceFiles.concat(contextFiles).concat(screenFilesConverted);
336
305
  const codeGenerator = new codegen_1.BlockCodeGenerator(block.content);
337
- await this.verifyAndFixCode(codeGenerator, basePath, filesToBeFixed, allFiles);
338
- const blockRef = block.uri;
306
+ this.emitBlockStatus(blockUri, block.aiName, events_1.StormEventBlockStatusType.BUILDING);
307
+ await this.verifyAndFixCode(blockUri, block.aiName, codeGenerator, basePath, filesToBeFixed, allFiles);
339
308
  this.out.emit('data', {
340
309
  type: 'BLOCK_READY',
341
310
  reason: 'Block ready',
@@ -348,7 +317,20 @@ class StormCodegen {
348
317
  },
349
318
  });
350
319
  }
351
- async verifyAndFixCode(codeGenerator, basePath, filesToBeFixed, allFiles) {
320
+ emitBlockStatus(blockUri, blockName, status) {
321
+ this.out.emit('data', {
322
+ type: 'BLOCK_STATUS',
323
+ reason: status,
324
+ created: Date.now(),
325
+ payload: {
326
+ status,
327
+ blockName,
328
+ blockRef: blockUri.toNormalizedString(),
329
+ instanceId: event_parser_1.StormEventParser.toInstanceIdFromRef(blockUri.toNormalizedString()),
330
+ },
331
+ });
332
+ }
333
+ async verifyAndFixCode(blockUri, blockName, codeGenerator, basePath, filesToBeFixed, allFiles) {
352
334
  let attempts = 0;
353
335
  let validCode = false;
354
336
  for (let i = 0; i <= 3; i++) {
@@ -362,11 +344,17 @@ class StormCodegen {
362
344
  }
363
345
  if (result && !result.valid) {
364
346
  console.debug('Validation error:', result);
347
+ this.emitBlockStatus(blockUri, blockName, events_1.StormEventBlockStatusType.PLANNING_FIX);
365
348
  const errors = await this.classifyErrors(result.error, basePath);
366
- for (const [filename, fileErrors] of errors.entries()) {
367
- // todo: only try to fix file if it is part of filesToBeFixed
368
- await this.tryToFixFile(basePath, filename, fileErrors, allFiles, codeGenerator);
349
+ if (errors.size > 0) {
350
+ this.emitBlockStatus(blockUri, blockName, events_1.StormEventBlockStatusType.FIXING);
351
+ const promises = Array.from(errors.entries()).map(([filename, fileErrors]) => {
352
+ // todo: only try to fix file if it is part of filesToBeFixed
353
+ return this.tryToFixFile(blockUri, blockName, basePath, filename, fileErrors, allFiles, codeGenerator);
354
+ });
355
+ await Promise.all(promises);
369
356
  }
357
+ this.emitBlockStatus(blockUri, blockName, events_1.StormEventBlockStatusType.FIX_DONE);
370
358
  }
371
359
  }
372
360
  catch (e) {
@@ -380,10 +368,10 @@ class StormCodegen {
380
368
  console.error(`Validation failed for ${basePath} after ${attempts} attempts`);
381
369
  }
382
370
  }
383
- async tryToFixFile(basePath, filename, fileErrors, allFiles, codeGenerator) {
371
+ async tryToFixFile(blockUri, blockName, basePath, filename, fileErrors, allFiles, codeGenerator) {
384
372
  console.log(`Processing ${filename}`);
385
373
  const language = await codeGenerator.language();
386
- const relevantFiles = allFiles.filter(file => file.type != codegen_1.AIFileTypes.IGNORE);
374
+ const relevantFiles = allFiles.filter((file) => file.type != codegen_1.AIFileTypes.IGNORE);
387
375
  for (let attempts = 1; attempts <= 5; attempts++) {
388
376
  if (fileErrors.length == 0) {
389
377
  console.log(`No more errors for ${filename}`);
@@ -393,17 +381,17 @@ class StormCodegen {
393
381
  const filesForContext = await this.getErrorDetailsForFile(basePath, filename, fileErrors[0], relevantFiles, language);
394
382
  console.log(`Get error details for ${filename} requesting code fixes`);
395
383
  const fix = this.createFixRequestForFile(basePath, filename, fileErrors[0], filesForContext, relevantFiles, language);
396
- const codeFixFiles = await this.codeFix(fix);
384
+ const codeFixFile = await this.codeFix(blockUri, blockName, fix);
397
385
  console.log(`Got fixed code for ${filename}`);
398
- for (const codeFixFile of codeFixFiles) {
399
- const filePath = codeFixFile.filename.indexOf(basePath) > -1 ? codeFixFile.filename : (0, path_2.join)(basePath, codeFixFile.filename);
400
- const existing = (0, fs_1.readFileSync)(filePath);
401
- if (existing.toString().replace(/(\r\n|\r|\n)+$/, '') == codeFixFile.content.replace(/(\r\n|\r|\n)+$/, '')) {
402
- console.log(`${filename} not changed by gemini`);
403
- continue;
404
- }
405
- (0, fs_1.writeFileSync)(filePath, codeFixFile.content);
386
+ const filePath = codeFixFile.filename.indexOf(basePath) > -1
387
+ ? codeFixFile.filename
388
+ : (0, path_2.join)(basePath, codeFixFile.filename);
389
+ const existing = (0, fs_1.readFileSync)(filePath);
390
+ if (existing.toString().replace(/(\r\n|\r|\n)+$/, '') == codeFixFile.content.replace(/(\r\n|\r|\n)+$/, '')) {
391
+ console.log(`${filename} not changed by gemini`);
392
+ continue;
406
393
  }
394
+ (0, fs_1.writeFileSync)(filePath, codeFixFile.content);
407
395
  const result = await codeGenerator.validateForTarget(basePath);
408
396
  if (result && result.valid) {
409
397
  return;
@@ -421,7 +409,11 @@ class StormCodegen {
421
409
  errorStream.on('data', (evt) => {
422
410
  if (evt.type === 'ERROR_CLASSIFIER') {
423
411
  const eventFileName = this.removePrefix(basePath + '/', evt.payload.filename);
424
- const fix = { error: evt.payload.error, lineNumber: evt.payload.lineNumber, column: evt.payload.column };
412
+ const fix = {
413
+ error: evt.payload.error,
414
+ lineNumber: evt.payload.lineNumber,
415
+ column: evt.payload.column,
416
+ };
425
417
  let existingFixes = fixes.get(eventFileName);
426
418
  if (existingFixes) {
427
419
  existingFixes.push(fix);
@@ -438,25 +430,26 @@ class StormCodegen {
438
430
  const filePath = filename.indexOf(basePath) > -1 ? filename : (0, path_2.join)(basePath, filename); // to compensate when compiler returns absolute path
439
431
  return new Promise(async (resolve, reject) => {
440
432
  const request = {
441
- "language": language,
442
- "sourceFile": {
443
- "filename": filename,
444
- "content": (0, fs_1.readFileSync)(filePath, 'utf8')
433
+ language: language,
434
+ sourceFile: {
435
+ filename: filename,
436
+ content: (0, fs_1.readFileSync)(filePath, 'utf8'),
445
437
  },
446
- "error": error,
447
- "projectFiles": allFiles.map(f => f.filename)
438
+ error: error,
439
+ projectFiles: allFiles.map((f) => f.filename),
448
440
  };
449
441
  const detailsStream = await stormClient_1.stormClient.createErrorDetails(JSON.stringify(request), []);
450
442
  detailsStream.on('data', (evt) => {
451
443
  if (evt.type === 'ERROR_DETAILS') {
452
444
  resolve(evt.payload.files);
453
445
  }
446
+ reject(new Error('Error details: Unexpected event [' + evt.type + ']'));
454
447
  });
455
448
  this.out.on('aborted', () => {
456
449
  detailsStream.abort();
450
+ reject(new Error('aborted'));
457
451
  });
458
452
  detailsStream.on('error', (err) => {
459
- console.log("error", err);
460
453
  reject(err);
461
454
  });
462
455
  await detailsStream.waitForDone();
@@ -465,31 +458,27 @@ class StormCodegen {
465
458
  createFixRequestForFile(basePath, filename, error, filesForContext, allFiles, language) {
466
459
  const files = new Set(filesForContext);
467
460
  files.add(filename);
468
- const requestedFiles = Array.from(files).flatMap(file => {
461
+ const requestedFiles = Array.from(files).flatMap((file) => {
469
462
  if (fs.existsSync(file)) {
470
463
  return file;
471
464
  }
472
465
  // file does not exist - look for similar
473
466
  const candidateName = file.split('/').pop();
474
- return allFiles
475
- .filter(file => file.filename.split('/').pop() === candidateName)
476
- .map(f => f.filename);
467
+ return allFiles.filter((file) => file.filename.split('/').pop() === candidateName).map((f) => f.filename);
477
468
  });
478
469
  const filePath = filename.indexOf(basePath) > -1 ? filename : (0, path_2.join)(basePath, filename);
479
470
  const content = (0, fs_1.readFileSync)(filePath, 'utf8');
480
471
  const affectedLine = this.getErrorLine(error, content);
481
472
  const fixRequest = {
482
- "language": language,
483
- "filename": filename,
484
- "errors": error.error,
485
- "affectedLine": affectedLine,
486
- "projectFiles": requestedFiles.map(filename => {
473
+ language: language,
474
+ filename: filename,
475
+ error: error.error,
476
+ affectedLine: affectedLine,
477
+ projectFiles: requestedFiles.map((filename) => {
487
478
  const filePath = filename.indexOf(basePath) > -1 ? filename : (0, path_2.join)(basePath, filename);
488
479
  const content = (0, fs_1.readFileSync)(filePath, 'utf8');
489
480
  return { filename: filename, content: content };
490
481
  }),
491
- "conversationId": this.conversationId,
492
- "sessionId": (0, uuid_1.v4)()
493
482
  };
494
483
  return JSON.stringify(fixRequest);
495
484
  }
@@ -497,7 +486,7 @@ class StormCodegen {
497
486
  const lines = sourceCode.split('\n');
498
487
  const errorLine = lines[errorDetails.lineNumber - 1];
499
488
  if (!errorLine) {
500
- return "Error: Line number out of range.";
489
+ return 'Error: Line number out of range.';
501
490
  }
502
491
  return errorLine;
503
492
  }
@@ -510,22 +499,36 @@ class StormCodegen {
510
499
  /**
511
500
  * Sends the code to the AI for a fix
512
501
  */
513
- async codeFix(fix, history) {
502
+ async codeFix(blockUri, blockName, fix, history) {
514
503
  return new Promise(async (resolve, reject) => {
515
- const fixStream = await stormClient_1.stormClient.createCodeFix(fix, history);
516
- fixStream.on('data', (evt) => {
517
- if (evt.type === 'CODE_FIX') {
518
- resolve(evt.payload.content.files);
519
- }
520
- });
521
- this.out.on('aborted', () => {
522
- fixStream.abort();
523
- });
524
- fixStream.on('error', (err) => {
525
- console.log("error", err);
526
- reject(err);
527
- });
528
- await fixStream.waitForDone();
504
+ try {
505
+ const fixStream = await stormClient_1.stormClient.createCodeFix(fix, history, this.conversationId);
506
+ let resolved = false;
507
+ fixStream.on('data', (evt) => {
508
+ if (this.handleFileEvents(blockUri, blockName, evt)) {
509
+ return;
510
+ }
511
+ if (evt.type === 'CODE_FIX') {
512
+ resolved = true;
513
+ resolve(evt.payload);
514
+ }
515
+ });
516
+ this.out.on('aborted', () => {
517
+ fixStream.abort();
518
+ reject(new Error('aborted'));
519
+ });
520
+ fixStream.on('error', (err) => {
521
+ reject(err);
522
+ });
523
+ fixStream.on('end', () => {
524
+ if (!resolved) {
525
+ reject(new Error('Code fix never returned a valid event'));
526
+ }
527
+ });
528
+ }
529
+ catch (e) {
530
+ reject(e);
531
+ }
529
532
  });
530
533
  }
531
534
  /**
@@ -561,7 +564,13 @@ class StormCodegen {
561
564
  instanceId: event_parser_1.StormEventParser.toInstanceIdFromRef(ref),
562
565
  },
563
566
  };
564
- return new SimulatedFileDelay(fileEvent, this.out).start();
567
+ if (ENABLE_SIMULATED_DELAY) {
568
+ // Simulate a delay when sending the file
569
+ return new SimulatedFileDelay(fileEvent, this.out).start();
570
+ }
571
+ else {
572
+ this.out.emit('data', fileEvent);
573
+ }
565
574
  });
566
575
  return Promise.all(promises);
567
576
  }
@@ -292,7 +292,9 @@ class StormEventParser {
292
292
  };
293
293
  }
294
294
  const clientTypes = kaplang_core_1.DSLDataTypeParser.parse(clientConsumerBlock.content.spec.entities.source.value, { ignoreSemantics: true });
295
- const apiTypes = kaplang_core_1.DSLDataTypeParser.parse(apiProviderBlock.content.spec.entities?.source?.value, { ignoreSemantics: true });
295
+ const apiTypes = kaplang_core_1.DSLDataTypeParser.parse(apiProviderBlock.content.spec.entities?.source?.value, {
296
+ ignoreSemantics: true,
297
+ });
296
298
  apiTypes.forEach((apiType) => {
297
299
  if (clientTypes.some((clientType) => clientType.name === apiType.name)) {
298
300
  // Already exists
@@ -111,9 +111,8 @@ export interface StormEventCodeFix {
111
111
  reason: string;
112
112
  created: number;
113
113
  payload: {
114
- content: {
115
- files: StormEventErrorDetailsFile[];
116
- };
114
+ filename: string;
115
+ content: string;
117
116
  };
118
117
  }
119
118
  export interface StormEventErrorClassifierInfo {
@@ -210,6 +209,24 @@ export interface StormEventBlockReady {
210
209
  instanceId: string;
211
210
  };
212
211
  }
212
+ export declare enum StormEventBlockStatusType {
213
+ QA = "QA",
214
+ FIXING = "FIXING",
215
+ PLANNING_FIX = "PLANNING_FIX",
216
+ FIX_DONE = "FIX_DONE",
217
+ BUILDING = "BUILDING"
218
+ }
219
+ export interface StormEventBlockStatus {
220
+ type: 'BLOCK_STATUS';
221
+ reason: string;
222
+ created: number;
223
+ payload: {
224
+ status: StormEventBlockStatusType;
225
+ blockName: string;
226
+ blockRef: string;
227
+ instanceId: string;
228
+ };
229
+ }
213
230
  export interface StormEventDone {
214
231
  type: 'DONE';
215
232
  created: number;
@@ -223,7 +240,8 @@ export interface StormEventDefinitionChange {
223
240
  export declare enum StormEventPhaseType {
224
241
  META = "META",
225
242
  DEFINITIONS = "DEFINITIONS",
226
- IMPLEMENTATION = "IMPLEMENTATION"
243
+ IMPLEMENTATION = "IMPLEMENTATION",
244
+ QA = "QA"
227
245
  }
228
246
  export interface StormEventPhases {
229
247
  type: 'PHASE_START' | 'PHASE_END';
@@ -232,4 +250,4 @@ export interface StormEventPhases {
232
250
  phaseType: StormEventPhaseType;
233
251
  };
234
252
  }
235
- export type StormEvent = StormEventCreateBlock | StormEventCreateConnection | StormEventCreatePlanProperties | StormEventInvalidResponse | StormEventPlanRetry | StormEventCreateDSL | StormEventCreateDSLResource | StormEventError | StormEventScreen | StormEventScreenCandidate | StormEventFileLogical | StormEventFileState | StormEventFileDone | StormEventFileChunk | StormEventDone | StormEventDefinitionChange | StormEventErrorClassifier | StormEventCodeFix | StormEventErrorDetails | StormEventBlockReady | StormEventPhases;
253
+ export type StormEvent = StormEventCreateBlock | StormEventCreateConnection | StormEventCreatePlanProperties | StormEventInvalidResponse | StormEventPlanRetry | StormEventCreateDSL | StormEventCreateDSLResource | StormEventError | StormEventScreen | StormEventScreenCandidate | StormEventFileLogical | StormEventFileState | StormEventFileDone | StormEventFileChunk | StormEventDone | StormEventDefinitionChange | StormEventErrorClassifier | StormEventCodeFix | StormEventErrorDetails | StormEventBlockReady | StormEventPhases | StormEventBlockStatus;
@@ -1,9 +1,18 @@
1
1
  "use strict";
2
2
  Object.defineProperty(exports, "__esModule", { value: true });
3
- exports.StormEventPhaseType = void 0;
3
+ exports.StormEventPhaseType = exports.StormEventBlockStatusType = void 0;
4
+ var StormEventBlockStatusType;
5
+ (function (StormEventBlockStatusType) {
6
+ StormEventBlockStatusType["QA"] = "QA";
7
+ StormEventBlockStatusType["FIXING"] = "FIXING";
8
+ StormEventBlockStatusType["PLANNING_FIX"] = "PLANNING_FIX";
9
+ StormEventBlockStatusType["FIX_DONE"] = "FIX_DONE";
10
+ StormEventBlockStatusType["BUILDING"] = "BUILDING";
11
+ })(StormEventBlockStatusType || (exports.StormEventBlockStatusType = StormEventBlockStatusType = {}));
4
12
  var StormEventPhaseType;
5
13
  (function (StormEventPhaseType) {
6
14
  StormEventPhaseType["META"] = "META";
7
15
  StormEventPhaseType["DEFINITIONS"] = "DEFINITIONS";
8
16
  StormEventPhaseType["IMPLEMENTATION"] = "IMPLEMENTATION";
17
+ StormEventPhaseType["QA"] = "QA";
9
18
  })(StormEventPhaseType || (exports.StormEventPhaseType = StormEventPhaseType = {}));
@@ -174,6 +174,7 @@ function sendEvent(res, evt) {
174
174
  if (res.closed) {
175
175
  return;
176
176
  }
177
+ console.log('Sending event', evt.type);
177
178
  res.write(JSON.stringify(evt) + '\n');
178
179
  }
179
180
  function onRequestAborted(req, res, onAborted) {
@@ -66,7 +66,12 @@ class StormClient {
66
66
  out.end();
67
67
  });
68
68
  out.on('aborted', () => {
69
- abort.abort();
69
+ try {
70
+ abort.abort();
71
+ }
72
+ catch (e) {
73
+ console.warn('Error aborting stream', e);
74
+ }
70
75
  });
71
76
  return out;
72
77
  }
@@ -11,6 +11,7 @@ export declare class StormStream extends EventEmitter {
11
11
  private conversationId;
12
12
  private lines;
13
13
  private aborted;
14
+ private done;
14
15
  constructor(prompt?: string, conversationId?: string | null);
15
16
  getConversationId(): string;
16
17
  isAborted(): boolean;
@@ -10,6 +10,7 @@ class StormStream extends node_events_1.EventEmitter {
10
10
  conversationId = '';
11
11
  lines = [];
12
12
  aborted = false;
13
+ done = false;
13
14
  constructor(prompt = '', conversationId) {
14
15
  super();
15
16
  this.conversationId = conversationId || '';
@@ -34,6 +35,7 @@ class StormStream extends node_events_1.EventEmitter {
34
35
  }
35
36
  }
36
37
  end() {
38
+ this.done = true;
37
39
  this.emit('end');
38
40
  }
39
41
  on(event, listener) {
@@ -43,6 +45,9 @@ class StormStream extends node_events_1.EventEmitter {
43
45
  return super.emit(eventName, ...args);
44
46
  }
45
47
  waitForDone() {
48
+ if (this.done) {
49
+ return Promise.resolve();
50
+ }
46
51
  return new Promise((resolve, reject) => {
47
52
  const errorHandler = (err) => {
48
53
  this.removeListener('error', errorHandler);
@@ -54,8 +59,8 @@ class StormStream extends node_events_1.EventEmitter {
54
59
  this.removeListener('end', endHandler);
55
60
  resolve();
56
61
  };
57
- this.once('error', errorHandler);
58
- this.once('end', endHandler);
62
+ this.on('error', errorHandler);
63
+ this.on('end', endHandler);
59
64
  });
60
65
  }
61
66
  abort() {
@@ -63,6 +68,7 @@ class StormStream extends node_events_1.EventEmitter {
63
68
  return;
64
69
  }
65
70
  this.aborted = true;
71
+ this.done = true;
66
72
  this.emit('aborted');
67
73
  }
68
74
  }
@@ -25,6 +25,7 @@ export declare class StormCodegen {
25
25
  * Generates the code for a block and sends it to the AI
26
26
  */
27
27
  private processBlockCode;
28
+ private emitBlockStatus;
28
29
  private verifyAndFixCode;
29
30
  private tryToFixFile;
30
31
  private classifyErrors;