Enforce AgentStack status jumps (#81)

This commit is contained in:
phucnguyen1707 2026-06-16 17:18:54 +07:00 committed by GitHub
parent 82e1389b8d
commit e38688f16e
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
2 changed files with 35 additions and 5 deletions

View file

@ -40,7 +40,13 @@ export function isDidTask(value: unknown): value is DidTask {
);
}
const TERMINAL: ReadonlySet<TaskStatus> = new Set(["complete", "failed", "cancelled"]);
const TERMINAL: ReadonlySet<TaskStatus> = new Set(["complete", "failed", "cancelled"]);
const ALLOWED_TRANSITIONS: Partial<Readonly<Record<TaskStatus, readonly TaskStatus[]>>> = {
pending: ["pending", "queued", "cancelled"],
queued: ["queued", "running", "cancelled"],
running: ["running", "blocked", "complete", "failed", "cancelled"]
};
// Blocked re-entry is still a PRD open question, so keep its current permissive behavior for now.
/**
* In-memory AgentStack coordinator: registers agents, tracks portable tasks through their
@ -136,10 +142,14 @@ export class AgentStack {
patch: Partial<Pick<DidTask, "reputationEventId" | "paymentIntentId" | "escrowId" | "metadata">> = {}
): DidTask {
const task = this.requireTask(taskId);
if (TERMINAL.has(task.status)) {
throw new Error(`Task ${taskId} is already ${task.status} and cannot transition to ${status}`);
}
const updated: DidTask = { ...task, ...patch, status, updatedAt: this.now() };
if (TERMINAL.has(task.status)) {
throw new Error(`Task ${taskId} is already ${task.status} and cannot transition to ${status}`);
}
const allowedStatuses = ALLOWED_TRANSITIONS[task.status];
if (allowedStatuses && !allowedStatuses.includes(status)) {
throw new Error(`Invalid task status transition: ${task.status} -> ${status}`);
}
const updated: DidTask = { ...task, ...patch, status, updatedAt: this.now() };
this.tasks.set(taskId, updated);
this.emit({ type: "task.updated", task: updated });
return updated;