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