@auto-engineer/pipeline 1.97.2 → 1.99.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/package.json CHANGED
@@ -14,8 +14,8 @@
14
14
  "get-port": "^7.1.0",
15
15
  "jose": "^5.9.6",
16
16
  "nanoid": "^5.0.0",
17
- "@auto-engineer/message-bus": "1.97.2",
18
- "@auto-engineer/file-store": "1.97.2"
17
+ "@auto-engineer/file-store": "1.99.0",
18
+ "@auto-engineer/message-bus": "1.99.0"
19
19
  },
20
20
  "devDependencies": {
21
21
  "@types/cors": "^2.8.17",
@@ -24,7 +24,7 @@
24
24
  "publishConfig": {
25
25
  "access": "public"
26
26
  },
27
- "version": "1.97.2",
27
+ "version": "1.99.0",
28
28
  "scripts": {
29
29
  "build": "tsc && tsx ../../scripts/fix-esm-imports.ts",
30
30
  "test": "vitest run --reporter=dot",
@@ -10,6 +10,7 @@ export interface PipelineContext {
10
10
  startPhased?: (handler: ForEachPhasedDescriptor, event: Event) => Promise<void>;
11
11
  eventStore?: InMemoryEventStore;
12
12
  messageBus?: MessageBus;
13
+ clearMessages?: () => Promise<void>;
13
14
  }
14
15
 
15
16
  export interface RuntimeConfig {
@@ -1,3 +1,4 @@
1
+ import http from 'node:http';
1
2
  import { createPipelineServerV2 } from './pipeline-server-v2.js';
2
3
 
3
4
  describe('PipelineServerV2', () => {
@@ -46,16 +47,25 @@ describe('PipelineServerV2', () => {
46
47
 
47
48
  const port = await server.start();
48
49
 
49
- const controller = new AbortController();
50
- const response = await fetch(`http://localhost:${port}/events`, {
51
- signal: controller.signal,
50
+ const { response, dataPromise, destroy } = await new Promise<{
51
+ response: http.IncomingMessage;
52
+ dataPromise: Promise<string>;
53
+ destroy: () => void;
54
+ }>((resolve) => {
55
+ const req = http.get(`http://localhost:${port}/events`, (res) => {
56
+ const chunks: string[] = [];
57
+ const dataPromise = new Promise<string>((resolveData) => {
58
+ res.on('data', (chunk: Buffer) => {
59
+ chunks.push(chunk.toString());
60
+ resolveData(chunks.join(''));
61
+ });
62
+ });
63
+ resolve({ response: res, dataPromise, destroy: () => req.destroy() });
64
+ });
52
65
  });
53
66
 
54
- expect(response.status).toBe(200);
55
- expect(response.headers.get('content-type')).toContain('text/event-stream');
56
-
57
- const reader = response.body!.getReader();
58
- const decoder = new TextDecoder();
67
+ expect(response.statusCode).toBe(200);
68
+ expect(response.headers['content-type']).toContain('text/event-stream');
59
69
 
60
70
  await fetch(`http://localhost:${port}/command`, {
61
71
  method: 'POST',
@@ -63,14 +73,12 @@ describe('PipelineServerV2', () => {
63
73
  body: JSON.stringify({ type: 'Ping', data: {} }),
64
74
  });
65
75
 
66
- const { value } = await reader.read();
67
- const text = decoder.decode(value);
76
+ const text = await dataPromise;
68
77
  const parsed = JSON.parse(text.replace('data: ', '').trim());
69
78
 
70
79
  expect(parsed).toEqual({ type: 'Pong', data: { value: 42 } });
71
80
 
72
- await reader.cancel().catch(() => {});
73
- controller.abort();
81
+ destroy();
74
82
  await server.stop();
75
83
  });
76
84
 
@@ -42,7 +42,7 @@ export async function createPipelineServerV2(config?: { port?: number }) {
42
42
 
43
43
  engine.onEvent(listener);
44
44
 
45
- req.on('close', () => {
45
+ res.on('close', () => {
46
46
  connected = false;
47
47
  });
48
48
  });
@@ -67,7 +67,7 @@ export class PipelineServer {
67
67
  private readonly eventCommandMapper: EventCommandMapper;
68
68
  private readonly phasedBridge: ReturnType<typeof createPhasedBridge>;
69
69
  private readonly sseManager: SSEManager;
70
- private readonly eventStoreContext: PipelineEventStoreContext;
70
+ private eventStoreContext: PipelineEventStoreContext;
71
71
  private readonly itemKeyExtractors = new Map<string, (data: unknown) => string | undefined>();
72
72
  private readonly commandGate: ReturnType<typeof createCommandGate>;
73
73
  private readonly middleware: express.RequestHandler[] = [];
@@ -344,6 +344,48 @@ export class PipelineServer {
344
344
  return;
345
345
  }
346
346
 
347
+ if (command.type === 'Reset') {
348
+ // 1. Clear event store (in-memory)
349
+ this.clearEventStore();
350
+
351
+ // Re-apply SQLite sync wrapper on the fresh event store
352
+ if (this.sqliteEventStore) {
353
+ const originalAppend = this.eventStoreContext.eventStore.appendToStream.bind(
354
+ this.eventStoreContext.eventStore,
355
+ );
356
+ this.eventStoreContext.eventStore.appendToStream = async (streamName, events, options) => {
357
+ const result = await originalAppend(streamName, events, options);
358
+ await this.sqliteEventStore!.appendToStream(streamName, events);
359
+ return result;
360
+ };
361
+ }
362
+
363
+ // 2. New session
364
+ this.currentSessionId = `session-${nanoid()}`;
365
+
366
+ // 3. Broadcast PipelineRunStarted (persists event + SSE to UI)
367
+ await this.broadcastPipelineRunStarted(this.currentSessionId, 'Reset');
368
+
369
+ // 4. Run Clean command synchronously to remove generated files before restarting
370
+ const cleanRequestId = `req-${nanoid()}`;
371
+ const cleanCommand: Command & { correlationId: string; requestId: string } = {
372
+ type: 'Clean',
373
+ data: {},
374
+ correlationId: this.currentSessionId,
375
+ requestId: cleanRequestId,
376
+ };
377
+ if (this.commandHandlers.has('Clean')) {
378
+ await this.emitCommandDispatched(this.currentSessionId, cleanRequestId, 'Clean', {});
379
+ await this.processCommand(cleanCommand);
380
+ }
381
+
382
+ // 5. Emit PipelineStarted to trigger pipeline handlers (kicks off fresh run)
383
+ await this.emitPipelineStartedEvent();
384
+
385
+ res.json({ status: 'ack', sessionId: this.currentSessionId });
386
+ return;
387
+ }
388
+
347
389
  if (!this.commandHandlers.has(command.type)) {
348
390
  res.status(404).json({
349
391
  status: 'nack',
@@ -1232,9 +1274,16 @@ export class PipelineServer {
1232
1274
  },
1233
1275
  eventStore: this.eventStoreContext.eventStore,
1234
1276
  messageBus: this.messageBus,
1277
+ clearMessages: async () => {
1278
+ this.clearEventStore();
1279
+ },
1235
1280
  };
1236
1281
  }
1237
1282
 
1283
+ private clearEventStore(): void {
1284
+ this.eventStoreContext = createPipelineEventStore();
1285
+ }
1286
+
1238
1287
  private routeEventToPhasedExecutor(event: EventWithCorrelation): void {
1239
1288
  for (const pipeline of this.pipelines.values()) {
1240
1289
  for (const handler of pipeline.descriptor.handlers) {
@@ -7,9 +7,9 @@ export default pipelineConfig({
7
7
  '@auto-engineer/server-generator-apollo-emmett',
8
8
  '@auto-engineer/narrative',
9
9
  '@auto-engineer/generate-react-client',
10
- '@auto-engineer/react-component-implementer',
11
10
  '@auto-engineer/server-implementer',
12
11
  '@auto-engineer/app-implementer',
12
+ '@auto-engineer/component-implementor-react',
13
13
  '@auto-engineer/dev-server',
14
14
  ],
15
15
  pipeline: createKanbanFullPipeline(),