@critical-path/server 0.19.0 → 0.20.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/src/router.ts CHANGED
@@ -1,4 +1,4 @@
1
- import { CriticalPathEngine, type AuthorType, type CriticalPathConfig } from '@critical-path/core';
1
+ import { CriticalPathEngine, type AuthorType, type CriticalPathConfig, type PluginRoute } from '@critical-path/core';
2
2
  import * as schemas from '@critical-path/core/schemas';
3
3
  import { buildOpenApiDocument } from './openapi.js';
4
4
 
@@ -194,511 +194,159 @@ export class CriticalPathRouter {
194
194
  );
195
195
 
196
196
  try {
197
- // OpenAPI contract (behind the same auth as every other route)
198
- if (segments[0] === 'openapi.json' && segments.length === 1 && method === 'GET') {
199
- return this.jsonResponse(buildOpenApiDocument({ serverUrl: this.openApiServerUrl(url, subpath) }));
200
- }
197
+ // Plugin init (and seeding) must finish before any request is served.
198
+ await this.engine.ready;
199
+ const pluginContext = { engine, context: context ?? undefined, url };
200
+ const handle = async (): Promise<Response> => {
201
+ const matched = this.matchPluginRoute(method, segments);
202
+ if (matched) return matched.route.handler(request, { ...pluginContext, params: matched.params });
203
+ return this.builtInRoutes(request, { url, pathname, method, subpath, segments, engine, context });
204
+ };
205
+ // Plugin middleware wraps routing, in registration order (first registered runs outermost).
206
+ const chain = this.engine.plugins
207
+ .getMiddleware()
208
+ .reduceRight<() => Promise<Response>>(
209
+ (next, middleware) => async () => middleware(request, pluginContext, next),
210
+ handle
211
+ );
212
+ return await chain();
213
+ } catch (err: unknown) {
214
+ return this.errorResponse(err, request);
215
+ }
216
+ }
201
217
 
202
- // Webhooks API
203
- if (segments[0] === 'webhooks') {
204
- const webhookId = segments[1];
205
- if (!webhookId) {
206
- if (method === 'GET') {
207
- return this.jsonResponse({ webhooks: await engine.getWebhooks() });
208
- }
209
- if (method === 'POST') {
210
- const body = await this.readBody(request, schemas.CreateWebhookSchema);
211
- const { webhook, secret } = await engine.createWebhook(body);
212
- return this.jsonResponse({ webhook, secret }, 201);
213
- }
214
- } else {
215
- if (method === 'GET') {
216
- const webhook = await engine.getWebhook(webhookId);
217
- if (!webhook) return this.jsonResponse({ error: 'Webhook not found' }, 404);
218
- return this.jsonResponse({ webhook });
219
- }
220
- if (method === 'PATCH' || method === 'PUT') {
221
- const body = await this.readBody(request, schemas.UpdateWebhookSchema);
222
- const webhook = await engine.updateWebhook(webhookId, body);
223
- if (!webhook) return this.jsonResponse({ error: 'Webhook not found' }, 404);
224
- return this.jsonResponse({ webhook });
225
- }
226
- if (method === 'DELETE') {
227
- const deleted = await engine.deleteWebhook(webhookId);
228
- if (!deleted) return this.jsonResponse({ error: 'Not found' }, 404);
229
- return this.jsonResponse({ success: true });
230
- }
218
+ private matchPluginRoute(
219
+ method: string,
220
+ segments: string[]
221
+ ): { route: PluginRoute; params: Record<string, string> } | null {
222
+ for (const route of this.engine.plugins.getRoutes()) {
223
+ if (route.method !== method && !(route.method === 'PATCH' && method === 'PUT')) continue;
224
+ const pattern = route.path.split('/').filter(Boolean);
225
+ if (pattern.length !== segments.length) continue;
226
+ const params: Record<string, string> = {};
227
+ const matches = pattern.every((part, i) => {
228
+ if (part.startsWith(':')) {
229
+ params[part.slice(1)] = decodeURIComponent(segments[i]);
230
+ return true;
231
231
  }
232
- }
232
+ return part === segments[i];
233
+ });
234
+ if (matches) return { route, params };
235
+ }
236
+ return null;
237
+ }
233
238
 
234
- // Workflows API
235
- if (segments[0] === 'workflows') {
236
- const workflowId = segments[1];
237
- if (!workflowId) {
238
- if (method === 'GET') {
239
- const workflows = await engine.getWorkflows();
240
- return this.jsonResponse({ workflows });
241
- }
242
- if (method === 'POST') {
243
- const body = await this.readBody(request, schemas.CreateWorkflowSchema);
244
- const workflow = await engine.createWorkflow(body);
245
- return this.jsonResponse({ workflow }, 201);
246
- }
247
- } else {
248
- if (method === 'GET') {
249
- const workflow = await engine.getWorkflow(workflowId);
250
- if (!workflow) return this.jsonResponse({ error: 'Workflow not found' }, 404);
251
- return this.jsonResponse({ workflow });
252
- }
253
- if (method === 'PATCH' || method === 'PUT') {
254
- const body = await this.readBody(request, schemas.UpdateWorkflowSchema);
255
- const updated = await engine.updateWorkflow(workflowId, body);
256
- if (!updated) return this.jsonResponse({ error: 'Workflow not found' }, 404);
257
- return this.jsonResponse({ workflow: updated });
258
- }
259
- if (method === 'DELETE') {
260
- const deleted = await engine.deleteWorkflow(workflowId);
261
- if (!deleted) return this.jsonResponse({ error: 'Not found' }, 404);
262
- return this.jsonResponse({ success: true });
263
- }
264
- }
265
- }
239
+ private async builtInRoutes(
240
+ request: Request,
241
+ { url, pathname, method, subpath, segments, engine, context }: {
242
+ url: URL;
243
+ pathname: string;
244
+ method: string;
245
+ subpath: string;
246
+ segments: string[];
247
+ engine: CriticalPathEngine;
248
+ context: RequestContext | null | undefined;
249
+ }
250
+ ): Promise<Response> {
251
+ // OpenAPI contract (behind the same auth as every other route)
252
+ if (segments[0] === 'openapi.json' && segments.length === 1 && method === 'GET') {
253
+ return this.jsonResponse(buildOpenApiDocument({ serverUrl: this.openApiServerUrl(url, subpath) }));
254
+ }
266
255
 
267
- // Projects API
268
- if (segments[0] === 'projects') {
269
- const projectId = segments[1];
270
- const subResource = segments[2];
271
- if (!projectId) {
272
- if (method === 'GET') {
273
- const projects = await engine.getProjects();
274
- return this.jsonResponse({ projects });
275
- }
276
- if (method === 'POST') {
277
- const body = await this.readBody(request, schemas.CreateProjectSchema);
278
- const project = await engine.createProject(body);
279
- return this.jsonResponse({ project }, 201);
280
- }
281
- } else if (subResource === 'critical-path') {
282
- if (method === 'GET') {
283
- const analysis = await engine.calculateCriticalPath(projectId);
284
- return this.jsonResponse({ analysis });
285
- }
286
- } else if (subResource === 'ladder' || subResource === 'timeline-ladder') {
287
- if (method === 'GET') {
288
- const level = (url.searchParams.get('level') || 'all') as any;
289
- const containerId = url.searchParams.get('containerId') || undefined;
290
- const iterationId = url.searchParams.get('iterationId') || undefined;
291
- const ladder = await engine.getTimelineLadder(projectId, { level, containerId, iterationId });
292
- return this.jsonResponse({ ladder });
293
- }
294
- } else if (subResource === 'workload' || subResource === 'workload-distribution') {
295
- if (method === 'GET') {
296
- const startDate = url.searchParams.get('startDate') || undefined;
297
- const endDate = url.searchParams.get('endDate') || undefined;
298
- const interval = (url.searchParams.get('interval') || undefined) as any;
299
- const groupBy = (url.searchParams.get('groupBy') || undefined) as any;
300
- const metric = (url.searchParams.get('metric') || undefined) as any;
301
- const defaultWeeklyCapacityHours = url.searchParams.get('defaultWeeklyCapacityHours')
302
- ? parseFloat(url.searchParams.get('defaultWeeklyCapacityHours')!)
303
- : undefined;
304
-
305
- const workload = await engine.getWorkloadDistribution(projectId, {
306
- startDate,
307
- endDate,
308
- interval,
309
- groupBy,
310
- metric,
311
- defaultWeeklyCapacityHours
312
- });
313
- return this.jsonResponse({ workload });
314
- }
315
- } else {
316
- if (method === 'GET') {
317
- const project = await engine.getProject(projectId);
318
- if (!project) return this.jsonResponse({ error: 'Project not found' }, 404);
319
- return this.jsonResponse({ project });
320
- }
321
- if (method === 'PATCH' || method === 'PUT') {
322
- const updates = await this.readBody(request, schemas.UpdateProjectSchema);
323
- const project = await engine.updateProject(projectId, updates);
324
- if (!project) return this.jsonResponse({ error: 'Project not found' }, 404);
325
- return this.jsonResponse({ project });
326
- }
327
- if (method === 'DELETE') {
328
- const deleted = await engine.deleteProject(projectId);
329
- if (!deleted) return this.jsonResponse({ error: 'Not found' }, 404);
330
- return this.jsonResponse({ success: true });
331
- }
256
+ // Webhooks API
257
+ if (segments[0] === 'webhooks') {
258
+ const webhookId = segments[1];
259
+ if (!webhookId) {
260
+ if (method === 'GET') {
261
+ return this.jsonResponse({ webhooks: await engine.getWebhooks() });
332
262
  }
333
- }
334
-
335
- // Tasks API & Task Sub-resources (Dependencies, Lifecycle State, Transitions)
336
- if (segments[0] === 'tasks') {
337
- const taskId = segments[1];
338
- const subResource = segments[2];
339
-
340
- if (!taskId) {
341
- if (method === 'GET') {
342
- const projectId = url.searchParams.get('projectId') || undefined;
343
- const tasks = await engine.getTasks(projectId);
344
- return this.jsonResponse({ tasks });
345
- }
346
- if (method === 'POST') {
347
- const body = await this.readBody(request, schemas.CreateTaskSchema);
348
- const task = await engine.createTask(body);
349
- return this.jsonResponse({ task }, 201);
350
- }
351
- } else if (subResource === 'comments') {
352
- if (method === 'GET') {
353
- const comments = await engine.getComments(taskId);
354
- return this.jsonResponse({ comments });
355
- }
356
- if (method === 'POST') {
357
- const body = await this.readBody(request, schemas.CreateCommentSchema, { taskId });
358
- const comment = await engine.addComment(body);
359
- return this.jsonResponse({ comment }, 201);
360
- }
361
- } else if (subResource === 'attachments') {
362
- if (method === 'GET') {
363
- const attachments = await engine.getAttachments({ taskId });
364
- return this.jsonResponse({ attachments });
365
- }
366
- if (method === 'POST') {
367
- const body = await this.readBody(request, schemas.CreateAttachmentSchema, { taskId });
368
- const attachment = await engine.createAttachment(body);
369
- return this.jsonResponse({ attachment }, 201);
370
- }
371
- } else if (subResource === 'dependencies') {
372
- if (method === 'GET') {
373
- const graph = await engine.getTaskDependencyGraph(taskId);
374
- return this.jsonResponse({ graph });
375
- }
376
- if (method === 'POST') {
377
- const body = await this.readBody(request, schemas.CreateDependencySchema);
378
- const dep = await engine.addDependency({
379
- taskId,
380
- dependsOnTaskId: body.dependsOnTaskId,
381
- type: body.type || 'blocking'
382
- });
383
- return this.jsonResponse({ dependency: dep }, 201);
384
- }
385
- } else if (subResource === 'state' || subResource === 'lifecycle') {
386
- if (method === 'GET') {
387
- const state = await engine.getTaskLifecycleState(taskId);
388
- if (!state) return this.jsonResponse({ error: 'Task not found' }, 404);
389
- return this.jsonResponse({ state });
390
- }
391
- } else if (subResource === 'transitions' || subResource === 'allowed-transitions') {
392
- if (method === 'GET') {
393
- const allowedNextStatuses = await engine.getAllowedTaskTransitions(taskId);
394
- const allowedPreviousStatuses = await engine.getAllowedPreviousTaskTransitions(taskId);
395
- return this.jsonResponse({ allowedNextStatuses, allowedPreviousStatuses });
396
- }
397
- } else if (subResource === 'ladder') {
398
- if (method === 'GET') {
399
- const taskLadder = await engine.getTaskLadder(taskId);
400
- if (!taskLadder) return this.jsonResponse({ error: 'Task not found' }, 404);
401
- return this.jsonResponse({ taskLadder });
402
- }
403
- } else if (subResource === 'metrics') {
404
- if (method === 'GET') {
405
- const metrics = await engine.getTaskMetrics(taskId);
406
- if (!metrics) return this.jsonResponse({ error: 'Task not found' }, 404);
407
- return this.jsonResponse({ metrics });
408
- }
409
- } else if (subResource === 'progress-history') {
410
- if (method === 'GET') {
411
- const progressHistory = await engine.getTaskProgressHistory(taskId);
412
- if (!progressHistory) return this.jsonResponse({ error: 'Task not found' }, 404);
413
- return this.jsonResponse({ progressHistory });
414
- }
415
- } else {
416
- if (method === 'GET') {
417
- const task = await engine.getTask(taskId);
418
- if (!task) return this.jsonResponse({ error: 'Task not found' }, 404);
419
- return this.jsonResponse({ task });
420
- }
421
- if (method === 'PATCH' || method === 'PUT') {
422
- const body = await this.readBody(request, schemas.UpdateTaskSchema);
423
- const updated = await engine.updateTask(taskId, body);
424
- if (!updated) return this.jsonResponse({ error: 'Task not found' }, 404);
425
- return this.jsonResponse({ task: updated });
426
- }
427
- if (method === 'DELETE') {
428
- const deleted = await engine.deleteTask(taskId);
429
- if (!deleted) return this.jsonResponse({ error: 'Not found' }, 404);
430
- return this.jsonResponse({ success: true });
431
- }
263
+ if (method === 'POST') {
264
+ const body = await this.readBody(request, schemas.CreateWebhookSchema);
265
+ const { webhook, secret } = await engine.createWebhook(body);
266
+ return this.jsonResponse({ webhook, secret }, 201);
432
267
  }
433
- }
434
-
435
- // Teams API
436
- if (segments[0] === 'teams') {
437
- const teamId = segments[1];
438
- if (!teamId) {
439
- if (method === 'GET') {
440
- const teams = await engine.getTeams();
441
- return this.jsonResponse({ teams });
442
- }
443
- if (method === 'POST') {
444
- const body = await this.readBody(request, schemas.CreateTeamSchema);
445
- const team = await engine.createTeam(body);
446
- return this.jsonResponse({ team }, 201);
447
- }
448
- } else {
449
- if (method === 'GET') {
450
- const team = await engine.getTeam(teamId);
451
- if (!team) return this.jsonResponse({ error: 'Team not found' }, 404);
452
- return this.jsonResponse({ team });
453
- }
454
- if (method === 'PATCH' || method === 'PUT') {
455
- const body = await this.readBody(request, schemas.UpdateTeamSchema);
456
- const updated = await engine.updateTeam(teamId, body);
457
- if (!updated) return this.jsonResponse({ error: 'Team not found' }, 404);
458
- return this.jsonResponse({ team: updated });
459
- }
460
- if (method === 'DELETE') {
461
- const deleted = await engine.deleteTeam(teamId);
462
- if (!deleted) return this.jsonResponse({ error: 'Not found' }, 404);
463
- return this.jsonResponse({ success: true });
464
- }
268
+ } else {
269
+ if (method === 'GET') {
270
+ const webhook = await engine.getWebhook(webhookId);
271
+ if (!webhook) return this.jsonResponse({ error: 'Webhook not found' }, 404);
272
+ return this.jsonResponse({ webhook });
465
273
  }
466
- }
467
-
468
- // Task Containers API
469
- if (segments[0] === 'containers') {
470
- const containerId = segments[1];
471
- if (!containerId) {
472
- if (method === 'GET') {
473
- const projectId = url.searchParams.get('projectId');
474
- if (!projectId) return this.jsonResponse({ error: 'projectId parameter required' }, 400);
475
- const containers = await engine.getContainers(projectId);
476
- return this.jsonResponse({ containers });
477
- }
478
- if (method === 'POST') {
479
- const body = await this.readBody(request, schemas.CreateContainerSchema);
480
- const container = await engine.createContainer(body);
481
- return this.jsonResponse({ container }, 201);
482
- }
483
- } else {
484
- if (method === 'GET') {
485
- const container = await engine.getContainer(containerId);
486
- if (!container) return this.jsonResponse({ error: 'Container not found' }, 404);
487
- return this.jsonResponse({ container });
488
- }
489
- if (method === 'PATCH' || method === 'PUT') {
490
- const body = await this.readBody(request, schemas.UpdateContainerSchema);
491
- const updated = await engine.updateContainer(containerId, body);
492
- if (!updated) return this.jsonResponse({ error: 'Container not found' }, 404);
493
- return this.jsonResponse({ container: updated });
494
- }
495
- if (method === 'DELETE') {
496
- const deleted = await engine.deleteContainer(containerId);
497
- if (!deleted) return this.jsonResponse({ error: 'Not found' }, 404);
498
- return this.jsonResponse({ success: true });
499
- }
274
+ if (method === 'PATCH' || method === 'PUT') {
275
+ const body = await this.readBody(request, schemas.UpdateWebhookSchema);
276
+ const webhook = await engine.updateWebhook(webhookId, body);
277
+ if (!webhook) return this.jsonResponse({ error: 'Webhook not found' }, 404);
278
+ return this.jsonResponse({ webhook });
500
279
  }
501
- }
502
-
503
- // Deliverables API
504
- if (segments[0] === 'deliverables') {
505
- const deliverableId = segments[1];
506
- if (!deliverableId) {
507
- if (method === 'GET') {
508
- const projectId = url.searchParams.get('projectId');
509
- if (!projectId) return this.jsonResponse({ error: 'projectId parameter required' }, 400);
510
- const deliverables = await engine.getDeliverables(projectId);
511
- return this.jsonResponse({ deliverables });
512
- }
513
- if (method === 'POST') {
514
- const body = await this.readBody(request, schemas.CreateDeliverableSchema);
515
- const deliverable = await engine.createDeliverable(body);
516
- return this.jsonResponse({ deliverable }, 201);
517
- }
518
- } else if (segments[2] === 'summary') {
519
- if (method === 'GET') {
520
- const summary = await engine.getDeliverableSummary(deliverableId);
521
- if (!summary) return this.jsonResponse({ error: 'Deliverable not found' }, 404);
522
- return this.jsonResponse({ summary });
523
- }
524
- } else {
525
- if (method === 'GET') {
526
- const deliverable = await engine.getDeliverable(deliverableId);
527
- if (!deliverable) return this.jsonResponse({ error: 'Deliverable not found' }, 404);
528
- return this.jsonResponse({ deliverable });
529
- }
530
- if (method === 'PATCH' || method === 'PUT') {
531
- const body = await this.readBody(request, schemas.UpdateDeliverableSchema);
532
- const updated = await engine.updateDeliverable(deliverableId, body);
533
- if (!updated) return this.jsonResponse({ error: 'Deliverable not found' }, 404);
534
- return this.jsonResponse({ deliverable: updated });
535
- }
536
- if (method === 'DELETE') {
537
- const deleted = await engine.deleteDeliverable(deliverableId);
538
- if (!deleted) return this.jsonResponse({ error: 'Not found' }, 404);
539
- return this.jsonResponse({ success: true });
540
- }
280
+ if (method === 'DELETE') {
281
+ const deleted = await engine.deleteWebhook(webhookId);
282
+ if (!deleted) return this.jsonResponse({ error: 'Not found' }, 404);
283
+ return this.jsonResponse({ success: true });
541
284
  }
542
285
  }
286
+ }
543
287
 
544
- // Iterations API
545
- if (segments[0] === 'iterations') {
546
- const iterationId = segments[1];
547
- if (!iterationId) {
548
- if (method === 'GET') {
549
- const projectId = url.searchParams.get('projectId');
550
- if (!projectId) return this.jsonResponse({ error: 'projectId parameter required' }, 400);
551
- const iterations = await engine.getIterations(projectId);
552
- return this.jsonResponse({ iterations });
553
- }
554
- if (method === 'POST') {
555
- const body = await this.readBody(request, schemas.CreateIterationSchema);
556
- const iteration = await engine.createIteration(body);
557
- return this.jsonResponse({ iteration }, 201);
558
- }
559
- } else {
560
- if (method === 'GET') {
561
- const iteration = await engine.getIteration(iterationId);
562
- if (!iteration) return this.jsonResponse({ error: 'Iteration not found' }, 404);
563
- return this.jsonResponse({ iteration });
564
- }
565
- if (method === 'PATCH' || method === 'PUT') {
566
- const body = await this.readBody(request, schemas.UpdateIterationSchema);
567
- const updated = await engine.updateIteration(iterationId, body);
568
- if (!updated) return this.jsonResponse({ error: 'Iteration not found' }, 404);
569
- return this.jsonResponse({ iteration: updated });
570
- }
571
- if (method === 'DELETE') {
572
- const deleted = await engine.deleteIteration(iterationId);
573
- if (!deleted) return this.jsonResponse({ error: 'Not found' }, 404);
574
- return this.jsonResponse({ success: true });
575
- }
288
+ // Workflows API
289
+ if (segments[0] === 'workflows') {
290
+ const workflowId = segments[1];
291
+ if (!workflowId) {
292
+ if (method === 'GET') {
293
+ const workflows = await engine.getWorkflows();
294
+ return this.jsonResponse({ workflows });
576
295
  }
577
- }
578
-
579
- // Activities API
580
- if (segments[0] === 'activities') {
296
+ if (method === 'POST') {
297
+ const body = await this.readBody(request, schemas.CreateWorkflowSchema);
298
+ const workflow = await engine.createWorkflow(body);
299
+ return this.jsonResponse({ workflow }, 201);
300
+ }
301
+ } else {
581
302
  if (method === 'GET') {
582
- const projectId = url.searchParams.get('projectId') || undefined;
583
- const taskId = url.searchParams.get('taskId') || undefined;
584
- const activities = await engine.getActivities({ projectId, taskId });
585
- return this.jsonResponse({ activities });
303
+ const workflow = await engine.getWorkflow(workflowId);
304
+ if (!workflow) return this.jsonResponse({ error: 'Workflow not found' }, 404);
305
+ return this.jsonResponse({ workflow });
586
306
  }
587
- }
588
-
589
- // Comments API
590
- if (segments[0] === 'comments') {
591
- const commentId = segments[1];
592
- const subResource = segments[2];
593
- if (commentId && subResource === 'reactions') {
594
- if (method === 'POST') {
595
- const body = await this.readBody(request, schemas.CommentReactionSchema);
596
- const comment = await engine.addCommentReaction(commentId, body);
597
- if (!comment) return this.jsonResponse({ error: 'Comment not found' }, 404);
598
- return this.jsonResponse({ comment }, 200);
599
- }
600
- if (method === 'DELETE') {
601
- const emoji = url.searchParams.get('emoji');
602
- if (!emoji) {
603
- return this.jsonResponse({ error: 'emoji query parameter is required' }, 400);
604
- }
605
- const comment = await engine.removeCommentReaction(commentId, { emoji });
606
- if (!comment) return this.jsonResponse({ error: 'Comment not found' }, 404);
607
- return this.jsonResponse({ comment }, 200);
608
- }
609
- } else if (!commentId) {
610
- if (method === 'GET') {
611
- const taskId = url.searchParams.get('taskId');
612
- if (!taskId) return this.jsonResponse({ error: 'taskId parameter required' }, 400);
613
- const comments = await engine.getComments(taskId);
614
- return this.jsonResponse({ comments });
615
- }
616
- if (method === 'POST') {
617
- const body = await this.readBody(request, schemas.CreateCommentSchema);
618
- const comment = await engine.addComment(body);
619
- return this.jsonResponse({ comment }, 201);
620
- }
621
- } else {
622
- if (method === 'GET') {
623
- const comment = await engine.getComment(commentId);
624
- if (!comment) return this.jsonResponse({ error: 'Comment not found' }, 404);
625
- return this.jsonResponse({ comment });
626
- }
627
- if (method === 'PATCH' || method === 'PUT') {
628
- const body = await this.readBody(request, schemas.UpdateCommentSchema);
629
- const updated = await engine.updateComment(commentId, body);
630
- if (!updated) return this.jsonResponse({ error: 'Comment not found' }, 404);
631
- return this.jsonResponse({ comment: updated });
632
- }
633
- if (method === 'DELETE') {
634
- const deleted = await engine.deleteComment(commentId);
635
- if (!deleted) return this.jsonResponse({ error: 'Not found' }, 404);
636
- return this.jsonResponse({ success: true });
637
- }
307
+ if (method === 'PATCH' || method === 'PUT') {
308
+ const body = await this.readBody(request, schemas.UpdateWorkflowSchema);
309
+ const updated = await engine.updateWorkflow(workflowId, body);
310
+ if (!updated) return this.jsonResponse({ error: 'Workflow not found' }, 404);
311
+ return this.jsonResponse({ workflow: updated });
638
312
  }
639
- }
640
-
641
- // Attachments API
642
- if (segments[0] === 'attachments') {
643
- const attachmentId = segments[1];
644
- if (attachmentId === 'presign') {
645
- if (method === 'POST') {
646
- const body = await this.readBody(request, schemas.PresignAttachmentSchema);
647
- const presigned = await engine.getPresignedAttachmentUploadUrl(body);
648
- return this.jsonResponse({ presigned });
649
- }
650
- } else if (attachmentId === 'upload') {
651
- if (method === 'POST') {
652
- const body = await this.readBody(request, schemas.UploadAttachmentSchema);
653
- const attachment = await engine.uploadAttachmentFile(body);
654
- return this.jsonResponse({ attachment }, 201);
655
- }
656
- } else if (!attachmentId) {
657
- if (method === 'GET') {
658
- const taskId = url.searchParams.get('taskId') || undefined;
659
- const projectId = url.searchParams.get('projectId') || undefined;
660
- const commentId = url.searchParams.get('commentId') || undefined;
661
- const attachments = await engine.getAttachments({ taskId, projectId, commentId });
662
- return this.jsonResponse({ attachments });
663
- }
664
- if (method === 'POST') {
665
- const body = await this.readBody(request, schemas.CreateAttachmentSchema);
666
- const attachment = await engine.createAttachment(body);
667
- return this.jsonResponse({ attachment }, 201);
668
- }
669
- } else {
670
- if (method === 'GET') {
671
- const attachment = await engine.getAttachment(attachmentId);
672
- if (!attachment) return this.jsonResponse({ error: 'Attachment not found' }, 404);
673
- return this.jsonResponse({ attachment });
674
- }
675
- if (method === 'DELETE') {
676
- const deleted = await engine.deleteAttachment(attachmentId);
677
- if (!deleted) return this.jsonResponse({ error: 'Not found' }, 404);
678
- return this.jsonResponse({ success: true });
679
- }
313
+ if (method === 'DELETE') {
314
+ const deleted = await engine.deleteWorkflow(workflowId);
315
+ if (!deleted) return this.jsonResponse({ error: 'Not found' }, 404);
316
+ return this.jsonResponse({ success: true });
680
317
  }
681
318
  }
319
+ }
682
320
 
683
- // Time Tracking API
684
- if (segments[0] === 'time-entries') {
321
+ // Projects API
322
+ if (segments[0] === 'projects') {
323
+ const projectId = segments[1];
324
+ const subResource = segments[2];
325
+ if (!projectId) {
685
326
  if (method === 'GET') {
686
- const taskId = url.searchParams.get('taskId');
687
- if (!taskId) return this.jsonResponse({ error: 'taskId parameter required' }, 400);
688
- const entries = await engine.getTimeEntries(taskId);
689
- return this.jsonResponse({ timeEntries: entries });
327
+ const projects = await engine.getProjects();
328
+ return this.jsonResponse({ projects });
690
329
  }
691
330
  if (method === 'POST') {
692
- const body = await this.readBody(request, schemas.LogTimeSchema);
693
- const entry = await engine.logTime(body);
694
- return this.jsonResponse({ timeEntry: entry }, 201);
331
+ const body = await this.readBody(request, schemas.CreateProjectSchema);
332
+ const project = await engine.createProject(body);
333
+ return this.jsonResponse({ project }, 201);
695
334
  }
696
- }
697
-
698
- // Workload & Capacity API
699
- if (segments[0] === 'workload') {
335
+ } else if (subResource === 'critical-path') {
336
+ if (method === 'GET') {
337
+ const analysis = await engine.calculateCriticalPath(projectId);
338
+ return this.jsonResponse({ analysis });
339
+ }
340
+ } else if (subResource === 'ladder' || subResource === 'timeline-ladder') {
341
+ if (method === 'GET') {
342
+ const level = (url.searchParams.get('level') || 'all') as any;
343
+ const containerId = url.searchParams.get('containerId') || undefined;
344
+ const iterationId = url.searchParams.get('iterationId') || undefined;
345
+ const ladder = await engine.getTimelineLadder(projectId, { level, containerId, iterationId });
346
+ return this.jsonResponse({ ladder });
347
+ }
348
+ } else if (subResource === 'workload' || subResource === 'workload-distribution') {
700
349
  if (method === 'GET') {
701
- const projectId = url.searchParams.get('projectId') || undefined;
702
350
  const startDate = url.searchParams.get('startDate') || undefined;
703
351
  const endDate = url.searchParams.get('endDate') || undefined;
704
352
  const interval = (url.searchParams.get('interval') || undefined) as any;
@@ -718,39 +366,442 @@ export class CriticalPathRouter {
718
366
  });
719
367
  return this.jsonResponse({ workload });
720
368
  }
369
+ } else {
370
+ if (method === 'GET') {
371
+ const project = await engine.getProject(projectId);
372
+ if (!project) return this.jsonResponse({ error: 'Project not found' }, 404);
373
+ return this.jsonResponse({ project });
374
+ }
375
+ if (method === 'PATCH' || method === 'PUT') {
376
+ const updates = await this.readBody(request, schemas.UpdateProjectSchema);
377
+ const project = await engine.updateProject(projectId, updates);
378
+ if (!project) return this.jsonResponse({ error: 'Project not found' }, 404);
379
+ return this.jsonResponse({ project });
380
+ }
381
+ if (method === 'DELETE') {
382
+ const deleted = await engine.deleteProject(projectId);
383
+ if (!deleted) return this.jsonResponse({ error: 'Not found' }, 404);
384
+ return this.jsonResponse({ success: true });
385
+ }
721
386
  }
387
+ }
722
388
 
723
- // Agent Status / Telemetry API
724
- if (segments[0] === 'status') {
389
+ // Tasks API & Task Sub-resources (Dependencies, Lifecycle State, Transitions)
390
+ if (segments[0] === 'tasks') {
391
+ const taskId = segments[1];
392
+ const subResource = segments[2];
393
+
394
+ if (!taskId) {
395
+ if (method === 'GET') {
396
+ const projectId = url.searchParams.get('projectId') || undefined;
397
+ const tasks = await engine.getTasks(projectId);
398
+ return this.jsonResponse({ tasks });
399
+ }
725
400
  if (method === 'POST') {
726
- const body = await this.readJson(request);
727
- const status = typeof body.status === 'string' ? body.status : 'active';
728
- if (engine.events) {
729
- engine.events.publish({
730
- type: 'agent.status_updated' as any,
731
- aggregateId: body.taskId || body.projectId || 'system',
732
- payload: {
733
- status,
734
- taskId: body.taskId,
735
- projectId: body.projectId,
736
- details: body.details,
737
- isEngaged: body.isEngaged ?? true,
738
- timestamp: Date.now()
739
- }
740
- } as any);
741
- }
742
- return this.jsonResponse({
743
- success: true,
744
- status,
745
- timestamp: Date.now()
401
+ const body = await this.readBody(request, schemas.CreateTaskSchema);
402
+ const task = await engine.createTask(body);
403
+ return this.jsonResponse({ task }, 201);
404
+ }
405
+ } else if (subResource === 'comments') {
406
+ if (method === 'GET') {
407
+ const comments = await engine.getComments(taskId);
408
+ return this.jsonResponse({ comments });
409
+ }
410
+ if (method === 'POST') {
411
+ const body = await this.readBody(request, schemas.CreateCommentSchema, { taskId });
412
+ const comment = await engine.addComment(body);
413
+ return this.jsonResponse({ comment }, 201);
414
+ }
415
+ } else if (subResource === 'attachments') {
416
+ if (method === 'GET') {
417
+ const attachments = await engine.getAttachments({ taskId });
418
+ return this.jsonResponse({ attachments });
419
+ }
420
+ if (method === 'POST') {
421
+ const body = await this.readBody(request, schemas.CreateAttachmentSchema, { taskId });
422
+ const attachment = await engine.createAttachment(body);
423
+ return this.jsonResponse({ attachment }, 201);
424
+ }
425
+ } else if (subResource === 'dependencies') {
426
+ if (method === 'GET') {
427
+ const graph = await engine.getTaskDependencyGraph(taskId);
428
+ return this.jsonResponse({ graph });
429
+ }
430
+ if (method === 'POST') {
431
+ const body = await this.readBody(request, schemas.CreateDependencySchema);
432
+ const dep = await engine.addDependency({
433
+ taskId,
434
+ dependsOnTaskId: body.dependsOnTaskId,
435
+ type: body.type || 'blocking'
746
436
  });
437
+ return this.jsonResponse({ dependency: dep }, 201);
438
+ }
439
+ } else if (subResource === 'state' || subResource === 'lifecycle') {
440
+ if (method === 'GET') {
441
+ const state = await engine.getTaskLifecycleState(taskId);
442
+ if (!state) return this.jsonResponse({ error: 'Task not found' }, 404);
443
+ return this.jsonResponse({ state });
444
+ }
445
+ } else if (subResource === 'transitions' || subResource === 'allowed-transitions') {
446
+ if (method === 'GET') {
447
+ const allowedNextStatuses = await engine.getAllowedTaskTransitions(taskId);
448
+ const allowedPreviousStatuses = await engine.getAllowedPreviousTaskTransitions(taskId);
449
+ return this.jsonResponse({ allowedNextStatuses, allowedPreviousStatuses });
450
+ }
451
+ } else if (subResource === 'ladder') {
452
+ if (method === 'GET') {
453
+ const taskLadder = await engine.getTaskLadder(taskId);
454
+ if (!taskLadder) return this.jsonResponse({ error: 'Task not found' }, 404);
455
+ return this.jsonResponse({ taskLadder });
456
+ }
457
+ } else if (subResource === 'metrics') {
458
+ if (method === 'GET') {
459
+ const metrics = await engine.getTaskMetrics(taskId);
460
+ if (!metrics) return this.jsonResponse({ error: 'Task not found' }, 404);
461
+ return this.jsonResponse({ metrics });
462
+ }
463
+ } else if (subResource === 'progress-history') {
464
+ if (method === 'GET') {
465
+ const progressHistory = await engine.getTaskProgressHistory(taskId);
466
+ if (!progressHistory) return this.jsonResponse({ error: 'Task not found' }, 404);
467
+ return this.jsonResponse({ progressHistory });
468
+ }
469
+ } else {
470
+ if (method === 'GET') {
471
+ const task = await engine.getTask(taskId);
472
+ if (!task) return this.jsonResponse({ error: 'Task not found' }, 404);
473
+ return this.jsonResponse({ task });
474
+ }
475
+ if (method === 'PATCH' || method === 'PUT') {
476
+ const body = await this.readBody(request, schemas.UpdateTaskSchema);
477
+ const updated = await engine.updateTask(taskId, body);
478
+ if (!updated) return this.jsonResponse({ error: 'Task not found' }, 404);
479
+ return this.jsonResponse({ task: updated });
480
+ }
481
+ if (method === 'DELETE') {
482
+ const deleted = await engine.deleteTask(taskId);
483
+ if (!deleted) return this.jsonResponse({ error: 'Not found' }, 404);
484
+ return this.jsonResponse({ success: true });
747
485
  }
748
486
  }
487
+ }
749
488
 
750
- return this.jsonResponse({ error: `Route not found: ${method} ${pathname}` }, 404);
751
- } catch (err: unknown) {
752
- return this.errorResponse(err, request);
489
+ // Teams API
490
+ if (segments[0] === 'teams') {
491
+ const teamId = segments[1];
492
+ if (!teamId) {
493
+ if (method === 'GET') {
494
+ const teams = await engine.getTeams();
495
+ return this.jsonResponse({ teams });
496
+ }
497
+ if (method === 'POST') {
498
+ const body = await this.readBody(request, schemas.CreateTeamSchema);
499
+ const team = await engine.createTeam(body);
500
+ return this.jsonResponse({ team }, 201);
501
+ }
502
+ } else {
503
+ if (method === 'GET') {
504
+ const team = await engine.getTeam(teamId);
505
+ if (!team) return this.jsonResponse({ error: 'Team not found' }, 404);
506
+ return this.jsonResponse({ team });
507
+ }
508
+ if (method === 'PATCH' || method === 'PUT') {
509
+ const body = await this.readBody(request, schemas.UpdateTeamSchema);
510
+ const updated = await engine.updateTeam(teamId, body);
511
+ if (!updated) return this.jsonResponse({ error: 'Team not found' }, 404);
512
+ return this.jsonResponse({ team: updated });
513
+ }
514
+ if (method === 'DELETE') {
515
+ const deleted = await engine.deleteTeam(teamId);
516
+ if (!deleted) return this.jsonResponse({ error: 'Not found' }, 404);
517
+ return this.jsonResponse({ success: true });
518
+ }
519
+ }
520
+ }
521
+
522
+ // Task Containers API
523
+ if (segments[0] === 'containers') {
524
+ const containerId = segments[1];
525
+ if (!containerId) {
526
+ if (method === 'GET') {
527
+ const projectId = url.searchParams.get('projectId');
528
+ if (!projectId) return this.jsonResponse({ error: 'projectId parameter required' }, 400);
529
+ const containers = await engine.getContainers(projectId);
530
+ return this.jsonResponse({ containers });
531
+ }
532
+ if (method === 'POST') {
533
+ const body = await this.readBody(request, schemas.CreateContainerSchema);
534
+ const container = await engine.createContainer(body);
535
+ return this.jsonResponse({ container }, 201);
536
+ }
537
+ } else {
538
+ if (method === 'GET') {
539
+ const container = await engine.getContainer(containerId);
540
+ if (!container) return this.jsonResponse({ error: 'Container not found' }, 404);
541
+ return this.jsonResponse({ container });
542
+ }
543
+ if (method === 'PATCH' || method === 'PUT') {
544
+ const body = await this.readBody(request, schemas.UpdateContainerSchema);
545
+ const updated = await engine.updateContainer(containerId, body);
546
+ if (!updated) return this.jsonResponse({ error: 'Container not found' }, 404);
547
+ return this.jsonResponse({ container: updated });
548
+ }
549
+ if (method === 'DELETE') {
550
+ const deleted = await engine.deleteContainer(containerId);
551
+ if (!deleted) return this.jsonResponse({ error: 'Not found' }, 404);
552
+ return this.jsonResponse({ success: true });
553
+ }
554
+ }
555
+ }
556
+
557
+ // Deliverables API
558
+ if (segments[0] === 'deliverables') {
559
+ const deliverableId = segments[1];
560
+ if (!deliverableId) {
561
+ if (method === 'GET') {
562
+ const projectId = url.searchParams.get('projectId');
563
+ if (!projectId) return this.jsonResponse({ error: 'projectId parameter required' }, 400);
564
+ const deliverables = await engine.getDeliverables(projectId);
565
+ return this.jsonResponse({ deliverables });
566
+ }
567
+ if (method === 'POST') {
568
+ const body = await this.readBody(request, schemas.CreateDeliverableSchema);
569
+ const deliverable = await engine.createDeliverable(body);
570
+ return this.jsonResponse({ deliverable }, 201);
571
+ }
572
+ } else if (segments[2] === 'summary') {
573
+ if (method === 'GET') {
574
+ const summary = await engine.getDeliverableSummary(deliverableId);
575
+ if (!summary) return this.jsonResponse({ error: 'Deliverable not found' }, 404);
576
+ return this.jsonResponse({ summary });
577
+ }
578
+ } else {
579
+ if (method === 'GET') {
580
+ const deliverable = await engine.getDeliverable(deliverableId);
581
+ if (!deliverable) return this.jsonResponse({ error: 'Deliverable not found' }, 404);
582
+ return this.jsonResponse({ deliverable });
583
+ }
584
+ if (method === 'PATCH' || method === 'PUT') {
585
+ const body = await this.readBody(request, schemas.UpdateDeliverableSchema);
586
+ const updated = await engine.updateDeliverable(deliverableId, body);
587
+ if (!updated) return this.jsonResponse({ error: 'Deliverable not found' }, 404);
588
+ return this.jsonResponse({ deliverable: updated });
589
+ }
590
+ if (method === 'DELETE') {
591
+ const deleted = await engine.deleteDeliverable(deliverableId);
592
+ if (!deleted) return this.jsonResponse({ error: 'Not found' }, 404);
593
+ return this.jsonResponse({ success: true });
594
+ }
595
+ }
596
+ }
597
+
598
+ // Iterations API
599
+ if (segments[0] === 'iterations') {
600
+ const iterationId = segments[1];
601
+ if (!iterationId) {
602
+ if (method === 'GET') {
603
+ const projectId = url.searchParams.get('projectId');
604
+ if (!projectId) return this.jsonResponse({ error: 'projectId parameter required' }, 400);
605
+ const iterations = await engine.getIterations(projectId);
606
+ return this.jsonResponse({ iterations });
607
+ }
608
+ if (method === 'POST') {
609
+ const body = await this.readBody(request, schemas.CreateIterationSchema);
610
+ const iteration = await engine.createIteration(body);
611
+ return this.jsonResponse({ iteration }, 201);
612
+ }
613
+ } else {
614
+ if (method === 'GET') {
615
+ const iteration = await engine.getIteration(iterationId);
616
+ if (!iteration) return this.jsonResponse({ error: 'Iteration not found' }, 404);
617
+ return this.jsonResponse({ iteration });
618
+ }
619
+ if (method === 'PATCH' || method === 'PUT') {
620
+ const body = await this.readBody(request, schemas.UpdateIterationSchema);
621
+ const updated = await engine.updateIteration(iterationId, body);
622
+ if (!updated) return this.jsonResponse({ error: 'Iteration not found' }, 404);
623
+ return this.jsonResponse({ iteration: updated });
624
+ }
625
+ if (method === 'DELETE') {
626
+ const deleted = await engine.deleteIteration(iterationId);
627
+ if (!deleted) return this.jsonResponse({ error: 'Not found' }, 404);
628
+ return this.jsonResponse({ success: true });
629
+ }
630
+ }
631
+ }
632
+
633
+ // Activities API
634
+ if (segments[0] === 'activities') {
635
+ if (method === 'GET') {
636
+ const projectId = url.searchParams.get('projectId') || undefined;
637
+ const taskId = url.searchParams.get('taskId') || undefined;
638
+ const activities = await engine.getActivities({ projectId, taskId });
639
+ return this.jsonResponse({ activities });
640
+ }
641
+ }
642
+
643
+ // Comments API
644
+ if (segments[0] === 'comments') {
645
+ const commentId = segments[1];
646
+ const subResource = segments[2];
647
+ if (commentId && subResource === 'reactions') {
648
+ if (method === 'POST') {
649
+ const body = await this.readBody(request, schemas.CommentReactionSchema);
650
+ const comment = await engine.addCommentReaction(commentId, body);
651
+ if (!comment) return this.jsonResponse({ error: 'Comment not found' }, 404);
652
+ return this.jsonResponse({ comment }, 200);
653
+ }
654
+ if (method === 'DELETE') {
655
+ const emoji = url.searchParams.get('emoji');
656
+ if (!emoji) {
657
+ return this.jsonResponse({ error: 'emoji query parameter is required' }, 400);
658
+ }
659
+ const comment = await engine.removeCommentReaction(commentId, { emoji });
660
+ if (!comment) return this.jsonResponse({ error: 'Comment not found' }, 404);
661
+ return this.jsonResponse({ comment }, 200);
662
+ }
663
+ } else if (!commentId) {
664
+ if (method === 'GET') {
665
+ const taskId = url.searchParams.get('taskId');
666
+ if (!taskId) return this.jsonResponse({ error: 'taskId parameter required' }, 400);
667
+ const comments = await engine.getComments(taskId);
668
+ return this.jsonResponse({ comments });
669
+ }
670
+ if (method === 'POST') {
671
+ const body = await this.readBody(request, schemas.CreateCommentSchema);
672
+ const comment = await engine.addComment(body);
673
+ return this.jsonResponse({ comment }, 201);
674
+ }
675
+ } else {
676
+ if (method === 'GET') {
677
+ const comment = await engine.getComment(commentId);
678
+ if (!comment) return this.jsonResponse({ error: 'Comment not found' }, 404);
679
+ return this.jsonResponse({ comment });
680
+ }
681
+ if (method === 'PATCH' || method === 'PUT') {
682
+ const body = await this.readBody(request, schemas.UpdateCommentSchema);
683
+ const updated = await engine.updateComment(commentId, body);
684
+ if (!updated) return this.jsonResponse({ error: 'Comment not found' }, 404);
685
+ return this.jsonResponse({ comment: updated });
686
+ }
687
+ if (method === 'DELETE') {
688
+ const deleted = await engine.deleteComment(commentId);
689
+ if (!deleted) return this.jsonResponse({ error: 'Not found' }, 404);
690
+ return this.jsonResponse({ success: true });
691
+ }
692
+ }
753
693
  }
694
+
695
+ // Attachments API
696
+ if (segments[0] === 'attachments') {
697
+ const attachmentId = segments[1];
698
+ if (attachmentId === 'presign') {
699
+ if (method === 'POST') {
700
+ const body = await this.readBody(request, schemas.PresignAttachmentSchema);
701
+ const presigned = await engine.getPresignedAttachmentUploadUrl(body);
702
+ return this.jsonResponse({ presigned });
703
+ }
704
+ } else if (attachmentId === 'upload') {
705
+ if (method === 'POST') {
706
+ const body = await this.readBody(request, schemas.UploadAttachmentSchema);
707
+ const attachment = await engine.uploadAttachmentFile(body);
708
+ return this.jsonResponse({ attachment }, 201);
709
+ }
710
+ } else if (!attachmentId) {
711
+ if (method === 'GET') {
712
+ const taskId = url.searchParams.get('taskId') || undefined;
713
+ const projectId = url.searchParams.get('projectId') || undefined;
714
+ const commentId = url.searchParams.get('commentId') || undefined;
715
+ const attachments = await engine.getAttachments({ taskId, projectId, commentId });
716
+ return this.jsonResponse({ attachments });
717
+ }
718
+ if (method === 'POST') {
719
+ const body = await this.readBody(request, schemas.CreateAttachmentSchema);
720
+ const attachment = await engine.createAttachment(body);
721
+ return this.jsonResponse({ attachment }, 201);
722
+ }
723
+ } else {
724
+ if (method === 'GET') {
725
+ const attachment = await engine.getAttachment(attachmentId);
726
+ if (!attachment) return this.jsonResponse({ error: 'Attachment not found' }, 404);
727
+ return this.jsonResponse({ attachment });
728
+ }
729
+ if (method === 'DELETE') {
730
+ const deleted = await engine.deleteAttachment(attachmentId);
731
+ if (!deleted) return this.jsonResponse({ error: 'Not found' }, 404);
732
+ return this.jsonResponse({ success: true });
733
+ }
734
+ }
735
+ }
736
+
737
+ // Time Tracking API
738
+ if (segments[0] === 'time-entries') {
739
+ if (method === 'GET') {
740
+ const taskId = url.searchParams.get('taskId');
741
+ if (!taskId) return this.jsonResponse({ error: 'taskId parameter required' }, 400);
742
+ const entries = await engine.getTimeEntries(taskId);
743
+ return this.jsonResponse({ timeEntries: entries });
744
+ }
745
+ if (method === 'POST') {
746
+ const body = await this.readBody(request, schemas.LogTimeSchema);
747
+ const entry = await engine.logTime(body);
748
+ return this.jsonResponse({ timeEntry: entry }, 201);
749
+ }
750
+ }
751
+
752
+ // Workload & Capacity API
753
+ if (segments[0] === 'workload') {
754
+ if (method === 'GET') {
755
+ const projectId = url.searchParams.get('projectId') || undefined;
756
+ const startDate = url.searchParams.get('startDate') || undefined;
757
+ const endDate = url.searchParams.get('endDate') || undefined;
758
+ const interval = (url.searchParams.get('interval') || undefined) as any;
759
+ const groupBy = (url.searchParams.get('groupBy') || undefined) as any;
760
+ const metric = (url.searchParams.get('metric') || undefined) as any;
761
+ const defaultWeeklyCapacityHours = url.searchParams.get('defaultWeeklyCapacityHours')
762
+ ? parseFloat(url.searchParams.get('defaultWeeklyCapacityHours')!)
763
+ : undefined;
764
+
765
+ const workload = await engine.getWorkloadDistribution(projectId, {
766
+ startDate,
767
+ endDate,
768
+ interval,
769
+ groupBy,
770
+ metric,
771
+ defaultWeeklyCapacityHours
772
+ });
773
+ return this.jsonResponse({ workload });
774
+ }
775
+ }
776
+
777
+ // Agent Status / Telemetry API
778
+ if (segments[0] === 'status') {
779
+ if (method === 'POST') {
780
+ const body = await this.readJson(request);
781
+ const status = typeof body.status === 'string' ? body.status : 'active';
782
+ if (engine.events) {
783
+ engine.events.publish({
784
+ type: 'agent.status_updated' as any,
785
+ aggregateId: body.taskId || body.projectId || 'system',
786
+ payload: {
787
+ status,
788
+ taskId: body.taskId,
789
+ projectId: body.projectId,
790
+ details: body.details,
791
+ isEngaged: body.isEngaged ?? true,
792
+ timestamp: Date.now()
793
+ }
794
+ } as any);
795
+ }
796
+ return this.jsonResponse({
797
+ success: true,
798
+ status,
799
+ timestamp: Date.now()
800
+ });
801
+ }
802
+ }
803
+
804
+ return this.jsonResponse({ error: `Route not found: ${method} ${pathname}` }, 404);
754
805
  }
755
806
 
756
807
  /**