fix(hub): beantwortete Asks über den Check-in-Kanal nachliefern
Realer Fall heute: kimi stellte ASK-0004 um 16:43 und blockierte in `agenthub_ask(wait:true)`. Der Wait läuft nach 300 s aus. Der Architekt brauchte für die Architekturentscheidung 18 Minuten — danach war kimi dormant, das korrekt emittierte `ask`-Event lief ins Leere, und ein Mensch musste den Agenten wecken. Dieselbe Taubheit wie in TSK-0274, nur an einer anderen Stelle: dormant = unerreichbar. Deshalb wandert die Antwort jetzt in den pending-Block — `resolvePending` liefert beantwortete eigene Fragen mit, sodass sie beim nächsten Hub-Kontakt (task_log, checkin, start) ankommt, egal wie lange der Architekt gebraucht hat. Tests: 290 → 292. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
This commit is contained in:
parent
e2bbae2044
commit
a6f640242a
@ -1,5 +1,6 @@
|
||||
import { listTasks } from './taskService.js';
|
||||
import { listMessages } from './messageService.js';
|
||||
import { listAsks } from './askService.js';
|
||||
import { agentIdentity } from './identityService.js';
|
||||
|
||||
/**
|
||||
@ -49,6 +50,16 @@ export interface PendingForAgent {
|
||||
waitingTasks: string[];
|
||||
/** Vom Agenten eingereichte Tasks, die auf das Architekten-Review warten. */
|
||||
awaitingReview: string[];
|
||||
/**
|
||||
* Vom Agenten gestellte Fragen, die inzwischen beantwortet sind.
|
||||
*
|
||||
* Warum das hierher gehört: `agenthub_ask(wait:true)` wartet nur 300 s. Der
|
||||
* Architekt braucht für eine Architekturentscheidung regelmäßig länger (real:
|
||||
* 18 min bei ASK-0004). Danach ist der Agent dormant und die Antwort erreicht
|
||||
* ihn nie — ein Mensch muss ihn wecken. Über den Check-in kommt sie beim
|
||||
* nächsten Hub-Kontakt an.
|
||||
*/
|
||||
answeredAsks: Array<{ id: string; answer: string }>;
|
||||
unreadCount: number;
|
||||
messages: PendingMessagePreview[];
|
||||
/** Klartext für das Modell — nur gesetzt, wenn wirklich etwas anliegt. */
|
||||
@ -87,10 +98,16 @@ export function resolvePending(
|
||||
const pending: PendingForAgent = {
|
||||
waitingTasks: [],
|
||||
awaitingReview: [],
|
||||
answeredAsks: [],
|
||||
unreadCount: 0,
|
||||
messages: [],
|
||||
};
|
||||
|
||||
for (const ask of listAsks(cwd, { status: 'answered' })) {
|
||||
if (!isMine(ask.from)) continue;
|
||||
pending.answeredAsks.push({ id: ask.id, answer: previewOf(ask.answer) });
|
||||
}
|
||||
|
||||
for (const task of tasks) {
|
||||
if (task.status === 'open' && isMine(task.assignedTo)) pending.waitingTasks.push(task.id);
|
||||
else if (task.status === 'review' && isMine(task.claimedBy ?? task.assignedTo)) pending.awaitingReview.push(task.id);
|
||||
@ -129,6 +146,7 @@ export function resolvePending(
|
||||
const parts: string[] = [];
|
||||
if (pending.interrupted) parts.push(pending.interrupted.action);
|
||||
if (pending.unreadCount > 0) parts.push(`${pending.unreadCount} ungelesene Nachricht(en) — agenthub_inbox lesen und antworten.`);
|
||||
if (pending.answeredAsks.length > 0) parts.push(`Deine Frage(n) ${pending.answeredAsks.map((a) => a.id).join(', ')} sind BEANTWORTET — lies die Antwort, bevor du weiterbaust.`);
|
||||
if (pending.waitingTasks.length > 0) parts.push(`Offen für dich: ${pending.waitingTasks.join(', ')}.`);
|
||||
if (parts.length > 0) pending.note = parts.join(' ');
|
||||
|
||||
@ -140,6 +158,7 @@ export function hasPending(pending: PendingForAgent): boolean {
|
||||
return (
|
||||
pending.interrupted !== undefined ||
|
||||
pending.unreadCount > 0 ||
|
||||
pending.answeredAsks.length > 0 ||
|
||||
pending.waitingTasks.length > 0
|
||||
);
|
||||
}
|
||||
|
||||
@ -5,6 +5,7 @@ import { join } from 'node:path';
|
||||
import { createTask, claimTask, reviewTask, reopenTask, cancelTask, assignTask } from '../src/core/services/taskService.js';
|
||||
import { createMessage } from '../src/core/services/messageService.js';
|
||||
import { resolvePending, hasPending } from '../src/core/services/checkinService.js';
|
||||
import { createAsk, answerAsk } from '../src/core/services/askService.js';
|
||||
|
||||
/**
|
||||
* TSK-0274 — der Check-in-Kanal.
|
||||
@ -133,3 +134,25 @@ describe('resolvePending — Nachrichten und Zuweisungen', () => {
|
||||
expect(JSON.stringify(pending).length).toBeLessThan(1500);
|
||||
});
|
||||
});
|
||||
|
||||
describe('resolvePending — beantwortete Fragen (Ask-Timeout-Lücke)', () => {
|
||||
it('liefert die Antwort auf eine eigene Frage nach — auch lange nach dem 300s-Wait', () => {
|
||||
const ask = createAsk(cwd, { from: 'kimi', to: 'claude', question: 'Welcher Weg?' });
|
||||
answerAsk(cwd, ask.id, 'Nimm Variante B.', 'claude');
|
||||
|
||||
const pending = resolvePending(cwd, 'kimi');
|
||||
|
||||
// agenthub_ask(wait) gibt nach 300s auf; braucht der Architekt laenger,
|
||||
// erreicht die Antwort den dormanten Agenten sonst NIE.
|
||||
expect(pending.answeredAsks.map((a) => a.id)).toContain(ask.id);
|
||||
expect(pending.answeredAsks[0]?.answer).toContain('Variante B');
|
||||
expect(pending.note).toMatch(/BEANTWORTET/);
|
||||
expect(hasPending(pending)).toBe(true);
|
||||
});
|
||||
|
||||
it('zeigt fremde Antworten nicht an', () => {
|
||||
const ask = createAsk(cwd, { from: 'codex', to: 'claude', question: 'X?' });
|
||||
answerAsk(cwd, ask.id, 'Y', 'claude');
|
||||
expect(resolvePending(cwd, 'kimi').answeredAsks).toHaveLength(0);
|
||||
});
|
||||
});
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user