Merge branch 'main' into cw/continuous-code-quality-conflict-1772832123778
# Conflicts: # apps/server/drizzle/meta/0037_snapshot.json # apps/server/drizzle/meta/_journal.json
This commit is contained in:
@@ -475,8 +475,8 @@ export function agentProcedures(publicProcedure: ProcedureBuilder) {
|
||||
const uniqueTaskIds = [...new Set(filteredAgents.map(a => a.taskId).filter(Boolean) as string[])];
|
||||
const uniqueInitiativeIds = [...new Set(filteredAgents.map(a => a.initiativeId).filter(Boolean) as string[])];
|
||||
|
||||
const [chunks, messageCounts, taskResults, initiativeResults] = await Promise.all([
|
||||
logChunkRepo.findByAgentIds(matchingIds),
|
||||
const [metrics, messageCounts, taskResults, initiativeResults] = await Promise.all([
|
||||
logChunkRepo.findMetricsByAgentIds(matchingIds),
|
||||
conversationRepo.countByFromAgentIds(matchingIds),
|
||||
Promise.all(uniqueTaskIds.map(id => taskRepo.findById(id))),
|
||||
Promise.all(uniqueInitiativeIds.map(id => initiativeRepo.findById(id))),
|
||||
@@ -486,37 +486,14 @@ export function agentProcedures(publicProcedure: ProcedureBuilder) {
|
||||
const taskMap = new Map(taskResults.filter(Boolean).map(t => [t!.id, t!.name]));
|
||||
const initiativeMap = new Map(initiativeResults.filter(Boolean).map(i => [i!.id, i!.name]));
|
||||
const messagesMap = new Map(messageCounts.map(m => [m.agentId, m.count]));
|
||||
|
||||
// Group chunks by agentId
|
||||
const chunksByAgent = new Map<string, typeof chunks>();
|
||||
for (const chunk of chunks) {
|
||||
const existing = chunksByAgent.get(chunk.agentId);
|
||||
if (existing) {
|
||||
existing.push(chunk);
|
||||
} else {
|
||||
chunksByAgent.set(chunk.agentId, [chunk]);
|
||||
}
|
||||
}
|
||||
const metricsMap = new Map(metrics.map(m => [m.agentId, m]));
|
||||
|
||||
// Build result rows
|
||||
return filteredAgents.map(agent => {
|
||||
const agentChunks = chunksByAgent.get(agent.id) ?? [];
|
||||
let questionsCount = 0;
|
||||
let subagentsCount = 0;
|
||||
let compactionsCount = 0;
|
||||
|
||||
for (const chunk of agentChunks) {
|
||||
try {
|
||||
const parsed = JSON.parse(chunk.content);
|
||||
if (parsed.type === 'tool_use' && parsed.name === 'AskUserQuestion') {
|
||||
questionsCount += parsed.input?.questions?.length ?? 0;
|
||||
} else if (parsed.type === 'tool_use' && parsed.name === 'Agent') {
|
||||
subagentsCount++;
|
||||
} else if (parsed.type === 'system' && parsed.subtype === 'init' && parsed.source === 'compact') {
|
||||
compactionsCount++;
|
||||
}
|
||||
} catch { /* skip malformed */ }
|
||||
}
|
||||
const agentMetrics = metricsMap.get(agent.id);
|
||||
const questionsCount = agentMetrics?.questionsCount ?? 0;
|
||||
const subagentsCount = agentMetrics?.subagentsCount ?? 0;
|
||||
const compactionsCount = agentMetrics?.compactionsCount ?? 0;
|
||||
|
||||
return {
|
||||
id: agent.id,
|
||||
|
||||
@@ -337,6 +337,14 @@ export function architectProcedures(publicProcedure: ProcedureBuilder) {
|
||||
});
|
||||
}
|
||||
|
||||
// Clean up orphaned pending/in_progress detail tasks from previous failed attempts
|
||||
const phaseTasks = await taskRepo.findByPhaseId(input.phaseId);
|
||||
for (const t of phaseTasks) {
|
||||
if (t.category === 'detail' && (t.status === 'pending' || t.status === 'in_progress')) {
|
||||
await taskRepo.update(t.id, { status: 'completed', summary: 'Superseded by retry' });
|
||||
}
|
||||
}
|
||||
|
||||
const detailTaskName = input.taskName ?? `Detail: ${phase.name}`;
|
||||
const task = await taskRepo.create({
|
||||
phaseId: phase.id,
|
||||
|
||||
@@ -184,11 +184,11 @@ export function errandProcedures(publicProcedure: ProcedureBuilder) {
|
||||
.input(z.object({
|
||||
projectId: z.string().optional(),
|
||||
status: z.enum(ErrandStatusValues).optional(),
|
||||
}))
|
||||
}).optional())
|
||||
.query(async ({ ctx, input }) => {
|
||||
return requireErrandRepository(ctx).findAll({
|
||||
projectId: input.projectId,
|
||||
status: input.status,
|
||||
projectId: input?.projectId,
|
||||
status: input?.status,
|
||||
});
|
||||
}),
|
||||
|
||||
|
||||
@@ -4,10 +4,35 @@
|
||||
|
||||
import { TRPCError } from '@trpc/server';
|
||||
import { z } from 'zod';
|
||||
import type { Task } from '../../db/schema.js';
|
||||
import type { Phase, Task } from '../../db/schema.js';
|
||||
import type { ProcedureBuilder } from '../trpc.js';
|
||||
import { requirePhaseDispatchManager, requirePhaseRepository, requireTaskRepository } from './_helpers.js';
|
||||
|
||||
const INTEGRATION_PHASE_NAME = 'Integration';
|
||||
|
||||
const INTEGRATION_TASK_DESCRIPTION = `Verify that all phase branches integrate correctly after merging into the initiative branch.
|
||||
|
||||
Steps:
|
||||
1. Build the project — fix any compilation errors
|
||||
2. Run the full test suite — fix any failing tests
|
||||
3. Run type checking and linting — fix any errors
|
||||
4. Review cross-phase imports and shared interfaces for compatibility
|
||||
5. Smoke test key user flows affected by the merged changes
|
||||
|
||||
Only fix integration issues (type mismatches, conflicting exports, broken tests). Do not refactor or improve existing code.`;
|
||||
|
||||
/**
|
||||
* Find phase IDs that have no dependents (no other phase depends on them).
|
||||
* These are the "end" / "leaf" phases in the dependency graph.
|
||||
*/
|
||||
function findEndPhaseIds(
|
||||
phases: Phase[],
|
||||
edges: Array<{ phaseId: string; dependsOnPhaseId: string }>,
|
||||
): string[] {
|
||||
const dependedOn = new Set(edges.map((e) => e.dependsOnPhaseId));
|
||||
return phases.filter((p) => !dependedOn.has(p.id)).map((p) => p.id);
|
||||
}
|
||||
|
||||
export function phaseDispatchProcedures(publicProcedure: ProcedureBuilder) {
|
||||
return {
|
||||
queuePhase: publicProcedure
|
||||
@@ -23,7 +48,40 @@ export function phaseDispatchProcedures(publicProcedure: ProcedureBuilder) {
|
||||
.mutation(async ({ ctx, input }) => {
|
||||
const phaseDispatchManager = requirePhaseDispatchManager(ctx);
|
||||
const phaseRepo = requirePhaseRepository(ctx);
|
||||
const phases = await phaseRepo.findByInitiativeId(input.initiativeId);
|
||||
const taskRepo = requireTaskRepository(ctx);
|
||||
|
||||
let phases = await phaseRepo.findByInitiativeId(input.initiativeId);
|
||||
const edges = await phaseRepo.findDependenciesByInitiativeId(input.initiativeId);
|
||||
|
||||
// Auto-create Integration phase if multiple end phases exist
|
||||
const existingIntegration = phases.find((p) => p.name === INTEGRATION_PHASE_NAME);
|
||||
if (!existingIntegration) {
|
||||
const endPhaseIds = findEndPhaseIds(phases, edges);
|
||||
if (endPhaseIds.length > 1) {
|
||||
const integrationPhase = await phaseRepo.create({
|
||||
initiativeId: input.initiativeId,
|
||||
name: INTEGRATION_PHASE_NAME,
|
||||
status: 'approved',
|
||||
});
|
||||
|
||||
for (const endPhaseId of endPhaseIds) {
|
||||
await phaseRepo.createDependency(integrationPhase.id, endPhaseId);
|
||||
}
|
||||
|
||||
await taskRepo.create({
|
||||
phaseId: integrationPhase.id,
|
||||
initiativeId: input.initiativeId,
|
||||
name: 'Verify integration',
|
||||
description: INTEGRATION_TASK_DESCRIPTION,
|
||||
category: 'verify',
|
||||
status: 'pending',
|
||||
});
|
||||
|
||||
// Re-fetch so the new phase gets queued in the loop below
|
||||
phases = await phaseRepo.findByInitiativeId(input.initiativeId);
|
||||
}
|
||||
}
|
||||
|
||||
let queued = 0;
|
||||
for (const phase of phases) {
|
||||
if (phase.status === 'approved') {
|
||||
|
||||
Reference in New Issue
Block a user