fix(work): bereits gehaltene Task wird genannt statt verschwiegen
Vom CEO gefunden: TSK-0293 stand auf in_progress bei codex — und codex tat nichts. Ursache: `findAddressedOpenTask` sucht ausschliesslich Tasks mit Status `open`. Haelt ein Agent bereits eine in_progress-Task (typisch nach einem Session-Neustart), greift der Busy-Guard und der Loop liefert NICHTS zurueck. Der Agent wartet dann auf neue Arbeit, die nie kommt, waehrend seine eigene Task unbearbeitet liegt — und von aussen sieht es aus, als sei er tot. `resolvePending` liefert jetzt `heldTask`, und alle drei Wege sagen es dem Agenten deutlich: - `agenthub start <agent>`: "DU HAELTST BEREITS <id>" ganz oben - `agenthub work` (CLI): nennt die Task und beendet sich, statt zu warten - `agenthub_work` (MCP): gibt Task samt Body zurueck statt zu blockieren Tests: 295 → 297. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
This commit is contained in:
parent
8a1728cbce
commit
908a043ac5
@ -201,6 +201,11 @@ export async function startAgentSession(options: StartAgentOptions): Promise<str
|
||||
? ((await remoteClient.getPending(serverUrl, agent)) as ReturnType<typeof resolvePending>)
|
||||
: resolvePending(projectCwd, agent);
|
||||
const lines: string[] = [];
|
||||
// Zuerst und am deutlichsten: eine bereits gehaltene Task. Sonst wartet der
|
||||
// Agent nach einem Neustart auf neue Arbeit, waehrend seine eigene liegt.
|
||||
if (pending.heldTask) {
|
||||
lines.push(`⚠️ DU HAELTST BEREITS ${pending.heldTask} — arbeite DORT weiter und reiche sie ein, bevor du Neues anfaengst.`);
|
||||
}
|
||||
if (pending.waitingTasks.length) lines.push(`Offen für dich: ${pending.waitingTasks.join(', ')}`);
|
||||
if (pending.awaitingReview.length) lines.push(`Wartet auf Architekten-Review: ${pending.awaitingReview.join(', ')} — du bist so lange gebunden.`);
|
||||
if (pending.unreadCount) lines.push(`Ungelesene Nachrichten: ${pending.unreadCount}`);
|
||||
|
||||
@ -1,5 +1,6 @@
|
||||
import { parseSSEBuffer } from './watch.js';
|
||||
import { announceAgent, findAddressedOpenTask, claimAndPrintTask, type AgentContext } from './start.js';
|
||||
import { resolvePending } from '../../core/services/checkinService.js';
|
||||
import { discoverServer as discoverHubServer } from '../../discovery.js';
|
||||
import { remoteClient } from '../remoteClient.js';
|
||||
|
||||
@ -61,6 +62,21 @@ async function drainAgentMessages(ctx: WorkAgentContext): Promise<number> {
|
||||
export async function workAgent(ctx: WorkAgentContext): Promise<void> {
|
||||
await announceAgent(ctx.serverUrl, ctx.agent, ctx.role);
|
||||
|
||||
// Haelt der Agent schon eine Task? Der Finder unten sucht nur `open` — nach
|
||||
// einem Session-Neustart wartet der Worker deshalb auf neue Arbeit, waehrend
|
||||
// die eigene, bereits geclaimte Task unbearbeitet liegt. Das war ein echter
|
||||
// Stillstand: Task auf in_progress, Agent tut nichts, niemand sieht warum.
|
||||
try {
|
||||
const held = resolvePending(ctx.projectCwd, ctx.agent).heldTask;
|
||||
if (held) {
|
||||
console.log(
|
||||
`AgentHub: du haeltst bereits ${held}. Arbeite dort weiter und reiche sie mit `
|
||||
+ `\`agenthub task review ${held}\` ein — erst danach kommt Neues.`,
|
||||
);
|
||||
return;
|
||||
}
|
||||
} catch { /* ohne Projekt-Kontext weiter wie bisher */ }
|
||||
|
||||
// Already-waiting task?
|
||||
const found = await findAddressedOpenTask(ctx);
|
||||
if (found) {
|
||||
|
||||
@ -49,6 +49,18 @@ export interface PendingForAgent {
|
||||
};
|
||||
/** Offene Tasks, die an den Agenten adressiert sind (inkl. Reopens). */
|
||||
waitingTasks: string[];
|
||||
/**
|
||||
* Die Task, die der Agent GERADE HAELT (in_progress, von ihm geclaimt).
|
||||
*
|
||||
* Klingt trivial, war aber eine echte Sackgasse: der Work-Loop sucht nur
|
||||
* Tasks mit Status `open`. Haelt ein Agent bereits eine in_progress-Task —
|
||||
* etwa nach einem Session-Neustart —, greift der Busy-Guard und der Loop
|
||||
* liefert NICHTS zurueck. Der Agent wartet dann auf neue Arbeit, die nie
|
||||
* kommt, waehrend seine eigene Task unbearbeitet liegt. Deshalb muss der
|
||||
* gehaltene Task ueberall auftauchen, wo ein Agent nach "was ist zu tun"
|
||||
* fragt.
|
||||
*/
|
||||
heldTask?: string;
|
||||
/** Vom Agenten eingereichte Tasks, die auf das Architekten-Review warten. */
|
||||
awaitingReview: string[];
|
||||
/**
|
||||
@ -130,7 +142,8 @@ export function resolvePending(
|
||||
isMine(task.assignedTo);
|
||||
|
||||
for (const task of tasks) {
|
||||
if (task.status === 'open' && addressedToMe(task)) pending.waitingTasks.push(task.id);
|
||||
if (task.status === 'in_progress' && isMine(task.claimedBy ?? task.assignedTo)) pending.heldTask = task.id;
|
||||
else if (task.status === 'open' && addressedToMe(task)) pending.waitingTasks.push(task.id);
|
||||
else if (task.status === 'review' && isMine(task.claimedBy ?? task.assignedTo)) pending.awaitingReview.push(task.id);
|
||||
}
|
||||
|
||||
@ -174,6 +187,9 @@ export function resolvePending(
|
||||
|
||||
const parts: string[] = [];
|
||||
if (pending.interrupted) parts.push(pending.interrupted.action);
|
||||
if (pending.heldTask && !pending.interrupted) {
|
||||
parts.push(`Du haeltst bereits ${pending.heldTask} — arbeite dort weiter und reiche sie ein, bevor du Neues anfaengst.`);
|
||||
}
|
||||
if (pending.unreadCount > 0) parts.push(`${pending.unreadCount} unbeantwortete Nachricht(en) — agenthub_inbox lesen, beantworten und mit agenthub_message quittieren.`);
|
||||
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(', ')}.`);
|
||||
@ -186,6 +202,7 @@ export function resolvePending(
|
||||
export function hasPending(pending: PendingForAgent): boolean {
|
||||
return (
|
||||
pending.interrupted !== undefined ||
|
||||
pending.heldTask !== undefined ||
|
||||
pending.unreadCount > 0 ||
|
||||
pending.answeredAsks.length > 0 ||
|
||||
pending.waitingTasks.length > 0
|
||||
|
||||
@ -311,6 +311,21 @@ export async function startMcpServer(cwd: string, options: { agent?: string } =
|
||||
LOOP += ' UNATTENDED MODE: never pause for human input — when you need a decision, ' +
|
||||
'call agenthub_ask (it routes to the architect) and await the answer instead of stalling.';
|
||||
}
|
||||
// Haelt der Agent bereits eine Task, wartet der Finder ins Leere (er sucht
|
||||
// nur `open`). Das nennen wir jetzt beim Namen, statt ihn blockieren zu
|
||||
// lassen — sonst wartet er auf Arbeit, die er laengst hat.
|
||||
if (!reviewer) {
|
||||
const held = resolvePending(root, agent).heldTask;
|
||||
if (held) {
|
||||
const detail = remote ? await remoteClient.getTask(serverUrl!, held) : getTask(root, held);
|
||||
return asText({
|
||||
held,
|
||||
body: (detail as { body?: string }).body,
|
||||
note: `Du haeltst bereits ${held}. Arbeite dort weiter und reiche sie mit agenthub_task_review ein — erst danach bekommst du Neues.`,
|
||||
loop: LOOP,
|
||||
});
|
||||
}
|
||||
}
|
||||
const immediate = await finder();
|
||||
if (immediate) {
|
||||
await leave('work delivered');
|
||||
|
||||
@ -188,3 +188,24 @@ describe('resolvePending — Adressierung wie im Work-Loop', () => {
|
||||
expect(resolvePending(cwd, 'codex').waitingTasks).toHaveLength(0);
|
||||
});
|
||||
});
|
||||
|
||||
describe('resolvePending — bereits gehaltene Task', () => {
|
||||
it('nennt die in_progress-Task, die der Agent haelt', () => {
|
||||
const id = openTaskFor('codex');
|
||||
claimTask(cwd, id, 'codex');
|
||||
|
||||
const pending = resolvePending(cwd, 'codex');
|
||||
|
||||
// Der Work-Loop sucht nur `open` — ohne diesen Hinweis wartet ein Agent
|
||||
// nach dem Neustart auf neue Arbeit, waehrend seine eigene liegt.
|
||||
expect(pending.heldTask).toBe(id);
|
||||
expect(pending.note).toMatch(/haeltst bereits/i);
|
||||
expect(hasPending(pending)).toBe(true);
|
||||
});
|
||||
|
||||
it('meldet keine fremde in_progress-Task als gehalten', () => {
|
||||
const id = openTaskFor('kimi');
|
||||
claimTask(cwd, id, 'kimi');
|
||||
expect(resolvePending(cwd, 'codex').heldTask).toBeUndefined();
|
||||
});
|
||||
});
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user