feat(FN-978): add diagnostic logging, semaphore resilience, and executor tests

- Add structured diagnostic logging to executor, stuck-task-detector, and pi.ts with subsystem prefixes
- Add defensive guards to AgentSemaphore (limit minimum 1, invalid limit handling)
- Add comprehensive integration tests for agent execution flow (executor.test.ts)
- Add unit tests for semaphore resilience (concurrency.test.ts) and stuck-task-detector (stuck-task-detector.test.ts)
- Fix TypeScript errors in test task objects and duplicate execution test
- Document engine diagnostic logging points in AGENTS.md
This commit is contained in:
gsxdsm
2026-04-05 13:37:21 -07:00
parent 64dbbdd5ba
commit 856ceebe88
9 changed files with 586 additions and 7 deletions

View File

@@ -268,8 +268,12 @@ export class TaskExecutor {
private rootDir: string,
private options: TaskExecutorOptions = {},
) {
executorLog.log(`TaskExecutor constructed (rootDir=${rootDir}, hasSemaphore=${!!options.semaphore}, hasStuckDetector=${!!options.stuckTaskDetector})`);
store.on("task:moved", ({ task, to }) => {
executorLog.log(`[event:task:moved] ${task.id}${to}`);
if (to === "in-progress") {
executorLog.log(`[event:task:moved] Initiating execute() for ${task.id}`);
this.execute(task).catch((err) =>
executorLog.error(`Failed to start ${task.id}:`, err),
);
@@ -449,6 +453,7 @@ export class TaskExecutor {
* as-is. Branches remain task-scoped (`kb/{task-id}`).
*/
async execute(task: Task): Promise<void> {
executorLog.log(`execute() called for ${task.id} (already executing=${this.executing.has(task.id)})`);
if (this.executing.has(task.id)) return;
this.executing.add(task.id);
@@ -618,10 +623,12 @@ export class TaskExecutor {
}
this.activeWorktrees.set(task.id, worktreePath);
executorLog.log(`${task.id}: worktree ready at ${worktreePath}`);
this.options.onStart?.(task, worktreePath);
const detail = await this.store.getTask(task.id);
executorLog.log(`${task.id}: fetched task detail (${detail.steps.length} steps, prompt length=${detail.prompt?.length ?? 0})`);
// Initialize steps from PROMPT.md if empty
if (detail.steps.length === 0) {
@@ -688,6 +695,8 @@ export class TaskExecutor {
? SessionManager.open(task.sessionFile!)
: SessionManager.create(worktreePath);
executorLog.log(`${task.id}: creating agent session (provider=${executorProvider ?? "default"}, model=${executorModelId ?? "default"}, resuming=${isResuming})`);
let { session, sessionFile } = await createKbAgent({
cwd: worktreePath,
systemPrompt: EXECUTOR_SYSTEM_PROMPT,
@@ -732,11 +741,13 @@ export class TaskExecutor {
// Register with stuck task detector for heartbeat monitoring
stuckDetector?.trackTask(task.id, session);
executorLog.log(`${task.id}: session registered (model=${describeModel(session)}, stuckDetector=${!!stuckDetector})`);
try {
// Record activity on prompt start (heartbeat for stuck detection)
stuckDetector?.recordActivity(task.id);
executorLog.log(`${task.id}: calling promptWithFallback()...`);
if (isResuming) {
// Session already has full conversation history — just tell the
// agent it was paused and should pick up where it left off.