feat: enrich listWaitingAgents with task/phase/initiative context via DB joins
Replaces the in-memory filter (agentManager.list() + filter) with a direct repository query that LEFT JOINs tasks, phases, and initiatives to return taskName, phaseName, initiativeName, and taskDescription alongside agent fields. - Adds AgentWithContext interface and findWaitingWithContext() to AgentRepository port - Implements findWaitingWithContext() in DrizzleAgentRepository using getTableColumns - Wires agentRepository into TRPCContext, CreateContextOptions, and TrpcAdapterOptions - Adds requireAgentRepository() helper following existing pattern - Updates listWaitingAgents to use repository query instead of agentManager - Adds 5 unit tests for findWaitingWithContext() covering all FK join edge cases - Updates existing AgentRepository mocks to satisfy updated interface Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
@@ -139,6 +139,7 @@ describe('MultiProviderAgentManager', () => {
|
|||||||
findByStatus: vi.fn().mockResolvedValue([mockAgent]),
|
findByStatus: vi.fn().mockResolvedValue([mockAgent]),
|
||||||
update: vi.fn().mockResolvedValue(mockAgent),
|
update: vi.fn().mockResolvedValue(mockAgent),
|
||||||
delete: vi.fn().mockResolvedValue(undefined),
|
delete: vi.fn().mockResolvedValue(undefined),
|
||||||
|
findWaitingWithContext: vi.fn().mockResolvedValue([]),
|
||||||
};
|
};
|
||||||
|
|
||||||
mockProjectRepository = {
|
mockProjectRepository = {
|
||||||
|
|||||||
@@ -46,7 +46,8 @@ describe('OutputHandler completion mutex', () => {
|
|||||||
async findByTaskId() { throw new Error('Not implemented'); },
|
async findByTaskId() { throw new Error('Not implemented'); },
|
||||||
async findByName() { throw new Error('Not implemented'); },
|
async findByName() { throw new Error('Not implemented'); },
|
||||||
async findBySessionId() { throw new Error('Not implemented'); },
|
async findBySessionId() { throw new Error('Not implemented'); },
|
||||||
async delete() { throw new Error('Not implemented'); }
|
async delete() { throw new Error('Not implemented'); },
|
||||||
|
async findWaitingWithContext() { throw new Error('Not implemented'); }
|
||||||
};
|
};
|
||||||
|
|
||||||
beforeEach(() => {
|
beforeEach(() => {
|
||||||
|
|||||||
@@ -8,6 +8,14 @@
|
|||||||
import type { Agent } from '../schema.js';
|
import type { Agent } from '../schema.js';
|
||||||
import type { AgentMode } from '../../agent/types.js';
|
import type { AgentMode } from '../../agent/types.js';
|
||||||
|
|
||||||
|
/** Agent row enriched with joined task/phase/initiative context fields. */
|
||||||
|
export interface AgentWithContext extends Agent {
|
||||||
|
taskName: string | null;
|
||||||
|
phaseName: string | null;
|
||||||
|
initiativeName: string | null;
|
||||||
|
taskDescription: string | null;
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Agent status values.
|
* Agent status values.
|
||||||
*/
|
*/
|
||||||
@@ -117,4 +125,10 @@ export interface AgentRepository {
|
|||||||
* Throws if agent not found.
|
* Throws if agent not found.
|
||||||
*/
|
*/
|
||||||
delete(id: string): Promise<void>;
|
delete(id: string): Promise<void>;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Find all agents with status 'waiting_for_input', enriched with
|
||||||
|
* task, phase, and initiative names via LEFT JOINs.
|
||||||
|
*/
|
||||||
|
findWaitingWithContext(): Promise<AgentWithContext[]>;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -277,3 +277,91 @@ describe('DrizzleAgentRepository', () => {
|
|||||||
});
|
});
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
|
describe('DrizzleAgentRepository.findWaitingWithContext()', () => {
|
||||||
|
let agentRepo: DrizzleAgentRepository;
|
||||||
|
let taskRepo: DrizzleTaskRepository;
|
||||||
|
let phaseRepo: DrizzlePhaseRepository;
|
||||||
|
let initiativeRepo: DrizzleInitiativeRepository;
|
||||||
|
|
||||||
|
beforeEach(() => {
|
||||||
|
const db = createTestDatabase();
|
||||||
|
agentRepo = new DrizzleAgentRepository(db);
|
||||||
|
taskRepo = new DrizzleTaskRepository(db);
|
||||||
|
phaseRepo = new DrizzlePhaseRepository(db);
|
||||||
|
initiativeRepo = new DrizzleInitiativeRepository(db);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('returns empty array when no waiting agents exist', async () => {
|
||||||
|
const result = await agentRepo.findWaitingWithContext();
|
||||||
|
expect(result).toEqual([]);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('only returns agents with status waiting_for_input', async () => {
|
||||||
|
await agentRepo.create({ name: 'running-agent', worktreeId: 'wt1', status: 'running' });
|
||||||
|
await agentRepo.create({ name: 'waiting-agent', worktreeId: 'wt2', status: 'waiting_for_input' });
|
||||||
|
|
||||||
|
const result = await agentRepo.findWaitingWithContext();
|
||||||
|
expect(result).toHaveLength(1);
|
||||||
|
expect(result[0].name).toBe('waiting-agent');
|
||||||
|
});
|
||||||
|
|
||||||
|
it('populates taskName, phaseName, initiativeName, taskDescription when FK associations exist', async () => {
|
||||||
|
const initiative = await initiativeRepo.create({ name: 'My Initiative' });
|
||||||
|
const phase = await phaseRepo.create({ initiativeId: initiative.id, name: 'Phase 1' });
|
||||||
|
const task = await taskRepo.create({
|
||||||
|
phaseId: phase.id,
|
||||||
|
name: 'Implement feature',
|
||||||
|
description: 'Write the feature code',
|
||||||
|
});
|
||||||
|
|
||||||
|
await agentRepo.create({
|
||||||
|
name: 'ctx-agent',
|
||||||
|
worktreeId: 'wt3',
|
||||||
|
status: 'waiting_for_input',
|
||||||
|
taskId: task.id,
|
||||||
|
initiativeId: initiative.id,
|
||||||
|
});
|
||||||
|
|
||||||
|
const result = await agentRepo.findWaitingWithContext();
|
||||||
|
expect(result).toHaveLength(1);
|
||||||
|
expect(result[0].taskName).toBe('Implement feature');
|
||||||
|
expect(result[0].phaseName).toBe('Phase 1');
|
||||||
|
expect(result[0].initiativeName).toBe('My Initiative');
|
||||||
|
expect(result[0].taskDescription).toBe('Write the feature code');
|
||||||
|
});
|
||||||
|
|
||||||
|
it('returns null for context fields when agent has no taskId or initiativeId', async () => {
|
||||||
|
await agentRepo.create({ name: 'bare-agent', worktreeId: 'wt4', status: 'waiting_for_input' });
|
||||||
|
|
||||||
|
const result = await agentRepo.findWaitingWithContext();
|
||||||
|
expect(result).toHaveLength(1);
|
||||||
|
expect(result[0].taskName).toBeNull();
|
||||||
|
expect(result[0].phaseName).toBeNull();
|
||||||
|
expect(result[0].initiativeName).toBeNull();
|
||||||
|
expect(result[0].taskDescription).toBeNull();
|
||||||
|
});
|
||||||
|
|
||||||
|
it('returns null phaseName when task has no phaseId', async () => {
|
||||||
|
const initiative = await initiativeRepo.create({ name: 'Orphan Init' });
|
||||||
|
const task = await taskRepo.create({
|
||||||
|
phaseId: null,
|
||||||
|
name: 'Orphan Task',
|
||||||
|
description: null,
|
||||||
|
});
|
||||||
|
|
||||||
|
await agentRepo.create({
|
||||||
|
name: 'orphan-agent',
|
||||||
|
worktreeId: 'wt5',
|
||||||
|
status: 'waiting_for_input',
|
||||||
|
taskId: task.id,
|
||||||
|
initiativeId: initiative.id,
|
||||||
|
});
|
||||||
|
|
||||||
|
const result = await agentRepo.findWaitingWithContext();
|
||||||
|
expect(result).toHaveLength(1);
|
||||||
|
expect(result[0].phaseName).toBeNull();
|
||||||
|
expect(result[0].taskName).toBe('Orphan Task');
|
||||||
|
expect(result[0].initiativeName).toBe('Orphan Init');
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|||||||
@@ -4,13 +4,14 @@
|
|||||||
* Implements AgentRepository interface using Drizzle ORM.
|
* Implements AgentRepository interface using Drizzle ORM.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
import { eq } from 'drizzle-orm';
|
import { eq, getTableColumns } from 'drizzle-orm';
|
||||||
import { nanoid } from 'nanoid';
|
import { nanoid } from 'nanoid';
|
||||||
import type { DrizzleDatabase } from '../../index.js';
|
import type { DrizzleDatabase } from '../../index.js';
|
||||||
import { agents, type Agent } from '../../schema.js';
|
import { agents, tasks, phases, initiatives, type Agent } from '../../schema.js';
|
||||||
import type {
|
import type {
|
||||||
AgentRepository,
|
AgentRepository,
|
||||||
AgentStatus,
|
AgentStatus,
|
||||||
|
AgentWithContext,
|
||||||
CreateAgentData,
|
CreateAgentData,
|
||||||
UpdateAgentData,
|
UpdateAgentData,
|
||||||
} from '../agent-repository.js';
|
} from '../agent-repository.js';
|
||||||
@@ -116,4 +117,20 @@ export class DrizzleAgentRepository implements AgentRepository {
|
|||||||
throw new Error(`Agent not found: ${id}`);
|
throw new Error(`Agent not found: ${id}`);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
async findWaitingWithContext(): Promise<AgentWithContext[]> {
|
||||||
|
return this.db
|
||||||
|
.select({
|
||||||
|
...getTableColumns(agents),
|
||||||
|
taskName: tasks.name,
|
||||||
|
phaseName: phases.name,
|
||||||
|
initiativeName: initiatives.name,
|
||||||
|
taskDescription: tasks.description,
|
||||||
|
})
|
||||||
|
.from(agents)
|
||||||
|
.where(eq(agents.status, 'waiting_for_input'))
|
||||||
|
.leftJoin(tasks, eq(agents.taskId, tasks.id))
|
||||||
|
.leftJoin(phases, eq(tasks.phaseId, phases.id))
|
||||||
|
.leftJoin(initiatives, eq(agents.initiativeId, initiatives.id));
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -23,6 +23,7 @@ import type { ConversationRepository } from '../db/repositories/conversation-rep
|
|||||||
import type { ChatSessionRepository } from '../db/repositories/chat-session-repository.js';
|
import type { ChatSessionRepository } from '../db/repositories/chat-session-repository.js';
|
||||||
import type { ReviewCommentRepository } from '../db/repositories/review-comment-repository.js';
|
import type { ReviewCommentRepository } from '../db/repositories/review-comment-repository.js';
|
||||||
import type { ErrandRepository } from '../db/repositories/errand-repository.js';
|
import type { ErrandRepository } from '../db/repositories/errand-repository.js';
|
||||||
|
import type { AgentRepository } from '../db/repositories/agent-repository.js';
|
||||||
import type { AccountCredentialManager } from '../agent/credentials/types.js';
|
import type { AccountCredentialManager } from '../agent/credentials/types.js';
|
||||||
import type { DispatchManager, PhaseDispatchManager } from '../dispatch/types.js';
|
import type { DispatchManager, PhaseDispatchManager } from '../dispatch/types.js';
|
||||||
import type { CoordinationManager } from '../coordination/types.js';
|
import type { CoordinationManager } from '../coordination/types.js';
|
||||||
@@ -85,6 +86,8 @@ export interface TrpcAdapterOptions {
|
|||||||
projectSyncManager?: ProjectSyncManager;
|
projectSyncManager?: ProjectSyncManager;
|
||||||
/** Errand repository for errand CRUD operations */
|
/** Errand repository for errand CRUD operations */
|
||||||
errandRepository?: ErrandRepository;
|
errandRepository?: ErrandRepository;
|
||||||
|
/** Agent repository for enriched agent queries */
|
||||||
|
agentRepository?: AgentRepository;
|
||||||
/** Absolute path to the workspace root (.cwrc directory) */
|
/** Absolute path to the workspace root (.cwrc directory) */
|
||||||
workspaceRoot?: string;
|
workspaceRoot?: string;
|
||||||
}
|
}
|
||||||
@@ -170,6 +173,7 @@ export function createTrpcHandler(options: TrpcAdapterOptions) {
|
|||||||
reviewCommentRepository: options.reviewCommentRepository,
|
reviewCommentRepository: options.reviewCommentRepository,
|
||||||
projectSyncManager: options.projectSyncManager,
|
projectSyncManager: options.projectSyncManager,
|
||||||
errandRepository: options.errandRepository,
|
errandRepository: options.errandRepository,
|
||||||
|
agentRepository: options.agentRepository,
|
||||||
workspaceRoot: options.workspaceRoot,
|
workspaceRoot: options.workspaceRoot,
|
||||||
}),
|
}),
|
||||||
});
|
});
|
||||||
|
|||||||
@@ -96,7 +96,8 @@ describe('Crash marking race condition', () => {
|
|||||||
async findByTaskId() { throw new Error('Not implemented'); },
|
async findByTaskId() { throw new Error('Not implemented'); },
|
||||||
async findByName() { throw new Error('Not implemented'); },
|
async findByName() { throw new Error('Not implemented'); },
|
||||||
async findBySessionId() { throw new Error('Not implemented'); },
|
async findBySessionId() { throw new Error('Not implemented'); },
|
||||||
async delete() { throw new Error('Not implemented'); }
|
async delete() { throw new Error('Not implemented'); },
|
||||||
|
async findWaitingWithContext() { throw new Error('Not implemented'); }
|
||||||
};
|
};
|
||||||
|
|
||||||
outputHandler = new OutputHandler(mockRepo);
|
outputHandler = new OutputHandler(mockRepo);
|
||||||
|
|||||||
@@ -20,6 +20,7 @@ import type { ConversationRepository } from '../db/repositories/conversation-rep
|
|||||||
import type { ChatSessionRepository } from '../db/repositories/chat-session-repository.js';
|
import type { ChatSessionRepository } from '../db/repositories/chat-session-repository.js';
|
||||||
import type { ReviewCommentRepository } from '../db/repositories/review-comment-repository.js';
|
import type { ReviewCommentRepository } from '../db/repositories/review-comment-repository.js';
|
||||||
import type { ErrandRepository } from '../db/repositories/errand-repository.js';
|
import type { ErrandRepository } from '../db/repositories/errand-repository.js';
|
||||||
|
import type { AgentRepository } from '../db/repositories/agent-repository.js';
|
||||||
import type { AccountCredentialManager } from '../agent/credentials/types.js';
|
import type { AccountCredentialManager } from '../agent/credentials/types.js';
|
||||||
import type { DispatchManager, PhaseDispatchManager } from '../dispatch/types.js';
|
import type { DispatchManager, PhaseDispatchManager } from '../dispatch/types.js';
|
||||||
import type { CoordinationManager } from '../coordination/types.js';
|
import type { CoordinationManager } from '../coordination/types.js';
|
||||||
@@ -87,6 +88,8 @@ export interface TRPCContext {
|
|||||||
projectSyncManager?: ProjectSyncManager;
|
projectSyncManager?: ProjectSyncManager;
|
||||||
/** Absolute path to the workspace root (.cwrc directory) */
|
/** Absolute path to the workspace root (.cwrc directory) */
|
||||||
workspaceRoot?: string;
|
workspaceRoot?: string;
|
||||||
|
/** Agent repository for enriched queries (e.g., findWaitingWithContext) */
|
||||||
|
agentRepository?: AgentRepository;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -119,6 +122,7 @@ export interface CreateContextOptions {
|
|||||||
errandRepository?: ErrandRepository;
|
errandRepository?: ErrandRepository;
|
||||||
projectSyncManager?: ProjectSyncManager;
|
projectSyncManager?: ProjectSyncManager;
|
||||||
workspaceRoot?: string;
|
workspaceRoot?: string;
|
||||||
|
agentRepository?: AgentRepository;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -155,5 +159,6 @@ export function createContext(options: CreateContextOptions): TRPCContext {
|
|||||||
errandRepository: options.errandRepository,
|
errandRepository: options.errandRepository,
|
||||||
projectSyncManager: options.projectSyncManager,
|
projectSyncManager: options.projectSyncManager,
|
||||||
workspaceRoot: options.workspaceRoot,
|
workspaceRoot: options.workspaceRoot,
|
||||||
|
agentRepository: options.agentRepository,
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -20,6 +20,7 @@ import type { ConversationRepository } from '../../db/repositories/conversation-
|
|||||||
import type { ChatSessionRepository } from '../../db/repositories/chat-session-repository.js';
|
import type { ChatSessionRepository } from '../../db/repositories/chat-session-repository.js';
|
||||||
import type { ReviewCommentRepository } from '../../db/repositories/review-comment-repository.js';
|
import type { ReviewCommentRepository } from '../../db/repositories/review-comment-repository.js';
|
||||||
import type { ErrandRepository } from '../../db/repositories/errand-repository.js';
|
import type { ErrandRepository } from '../../db/repositories/errand-repository.js';
|
||||||
|
import type { AgentRepository } from '../../db/repositories/agent-repository.js';
|
||||||
import type { DispatchManager, PhaseDispatchManager } from '../../dispatch/types.js';
|
import type { DispatchManager, PhaseDispatchManager } from '../../dispatch/types.js';
|
||||||
import type { CoordinationManager } from '../../coordination/types.js';
|
import type { CoordinationManager } from '../../coordination/types.js';
|
||||||
import type { BranchManager } from '../../git/branch-manager.js';
|
import type { BranchManager } from '../../git/branch-manager.js';
|
||||||
@@ -236,3 +237,13 @@ export function requireErrandRepository(ctx: TRPCContext): ErrandRepository {
|
|||||||
}
|
}
|
||||||
return ctx.errandRepository;
|
return ctx.errandRepository;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export function requireAgentRepository(ctx: TRPCContext): AgentRepository {
|
||||||
|
if (!ctx.agentRepository) {
|
||||||
|
throw new TRPCError({
|
||||||
|
code: 'INTERNAL_SERVER_ERROR',
|
||||||
|
message: 'Agent repository not available',
|
||||||
|
});
|
||||||
|
}
|
||||||
|
return ctx.agentRepository;
|
||||||
|
}
|
||||||
|
|||||||
@@ -11,7 +11,7 @@ import type { ProcedureBuilder } from '../trpc.js';
|
|||||||
import type { TRPCContext } from '../context.js';
|
import type { TRPCContext } from '../context.js';
|
||||||
import type { AgentInfo, AgentResult, PendingQuestions } from '../../agent/types.js';
|
import type { AgentInfo, AgentResult, PendingQuestions } from '../../agent/types.js';
|
||||||
import type { AgentOutputEvent } from '../../events/types.js';
|
import type { AgentOutputEvent } from '../../events/types.js';
|
||||||
import { requireAgentManager, requireLogChunkRepository, requireTaskRepository, requireInitiativeRepository, requireConversationRepository } from './_helpers.js';
|
import { requireAgentManager, requireAgentRepository, requireLogChunkRepository, requireTaskRepository, requireInitiativeRepository, requireConversationRepository } from './_helpers.js';
|
||||||
|
|
||||||
export type AgentRadarRow = {
|
export type AgentRadarRow = {
|
||||||
id: string;
|
id: string;
|
||||||
@@ -191,9 +191,8 @@ export function agentProcedures(publicProcedure: ProcedureBuilder) {
|
|||||||
|
|
||||||
listWaitingAgents: publicProcedure
|
listWaitingAgents: publicProcedure
|
||||||
.query(async ({ ctx }) => {
|
.query(async ({ ctx }) => {
|
||||||
const agentManager = requireAgentManager(ctx);
|
const agentRepo = requireAgentRepository(ctx);
|
||||||
const allAgents = await agentManager.list();
|
return agentRepo.findWaitingWithContext();
|
||||||
return allAgents.filter(agent => agent.status === 'waiting_for_input');
|
|
||||||
}),
|
}),
|
||||||
|
|
||||||
getActiveRefineAgent: publicProcedure
|
getActiveRefineAgent: publicProcedure
|
||||||
|
|||||||
@@ -241,7 +241,7 @@ Index: `(phaseId)`.
|
|||||||
| InitiativeRepository | create, findById, findAll, findByStatus, update, delete |
|
| InitiativeRepository | create, findById, findAll, findByStatus, update, delete |
|
||||||
| PhaseRepository | + createDependency, getDependencies, getDependents, findByInitiativeId |
|
| PhaseRepository | + createDependency, getDependencies, getDependents, findByInitiativeId |
|
||||||
| TaskRepository | + findByParentTaskId, findByPhaseId, createDependency |
|
| TaskRepository | + findByParentTaskId, findByPhaseId, createDependency |
|
||||||
| AgentRepository | + findByName, findByTaskId, findBySessionId, findByStatus |
|
| AgentRepository | + findByName, findByTaskId, findBySessionId, findByStatus, findWaitingWithContext (LEFT JOIN enriched) |
|
||||||
| MessageRepository | + findPendingForUser, findRequiringResponse, findReplies |
|
| MessageRepository | + findPendingForUser, findRequiringResponse, findReplies |
|
||||||
| PageRepository | + findRootPage, getOrCreateRootPage, findByParentPageId |
|
| PageRepository | + findRootPage, getOrCreateRootPage, findByParentPageId |
|
||||||
| ProjectRepository | + junction ops: setInitiativeProjects (diff-based), findProjectsByInitiativeId |
|
| ProjectRepository | + junction ops: setInitiativeProjects (diff-based), findProjectsByInitiativeId |
|
||||||
|
|||||||
@@ -68,7 +68,7 @@ Each procedure uses `require*Repository(ctx)` helpers that throw `TRPCError(INTE
|
|||||||
| getAgentPrompt | query | Assembled prompt — reads from DB (`agents.prompt`) first; falls back to `.cw/agent-logs/<name>/PROMPT.md` for pre-persistence agents (1 MB cap) |
|
| getAgentPrompt | query | Assembled prompt — reads from DB (`agents.prompt`) first; falls back to `.cw/agent-logs/<name>/PROMPT.md` for pre-persistence agents (1 MB cap) |
|
||||||
| getActiveRefineAgent | query | Active refine agent for initiative |
|
| getActiveRefineAgent | query | Active refine agent for initiative |
|
||||||
| getActiveConflictAgent | query | Active conflict resolution agent for initiative (name starts with `conflict-`) |
|
| getActiveConflictAgent | query | Active conflict resolution agent for initiative (name starts with `conflict-`) |
|
||||||
| listWaitingAgents | query | Agents waiting for input |
|
| listWaitingAgents | query | Agents waiting for input — returns `AgentWithContext[]` enriched with `taskName`, `phaseName`, `initiativeName`, `taskDescription` via SQL LEFT JOINs |
|
||||||
| listForRadar | query | Radar page: per-agent metrics (questionsCount, messagesCount, subagentsCount, compactionsCount) with time/status/mode/initiative filters |
|
| listForRadar | query | Radar page: per-agent metrics (questionsCount, messagesCount, subagentsCount, compactionsCount) with time/status/mode/initiative filters |
|
||||||
| getCompactionEvents | query | Compaction events for one agent: `{agentId}` → `{timestamp, sessionNumber}[]` (cap 200) |
|
| getCompactionEvents | query | Compaction events for one agent: `{agentId}` → `{timestamp, sessionNumber}[]` (cap 200) |
|
||||||
| getSubagentSpawns | query | Subagent spawn events for one agent: `{agentId}` → `{timestamp, description, promptPreview, fullPrompt}[]` (cap 200) |
|
| getSubagentSpawns | query | Subagent spawn events for one agent: `{agentId}` → `{timestamp, description, promptPreview, fullPrompt}[]` (cap 200) |
|
||||||
|
|||||||
Reference in New Issue
Block a user