mirror of
https://github.com/saltbo/agent-kanban.git
synced 2026-09-19 09:01:47 +08:00
a2fa54a729
Replace the v1 Agent/Machine runtime and CLI architecture with the v2 Resource Server, AMA-backed projections, task lifecycle, session observation, BDD specifications, and a single-package layered codebase. BREAKING CHANGE: remove the v1 CLI, internal Agent/Machine runtime entities, workspace layout, and legacy runtime coordination APIs.
93 lines
3.0 KiB
TypeScript
93 lines
3.0 KiB
TypeScript
export type TaskCancellationActor = { type: "agent" | "human" | "system"; id: string };
|
|
|
|
export interface StoredTaskCancellation {
|
|
actionId: string;
|
|
actorType: "agent" | "human" | "system";
|
|
actorId: string;
|
|
cancelledAt: string;
|
|
assigneeActorId: string | null;
|
|
}
|
|
|
|
export interface TaskCancellationTarget {
|
|
status: string;
|
|
assignedTo: string | null;
|
|
assigneeIdentityType: string | null;
|
|
cancellation: StoredTaskCancellation | null;
|
|
}
|
|
|
|
export interface TaskCancellationRepository {
|
|
findTarget(ownerId: string, taskId: string): Promise<TaskCancellationTarget | null>;
|
|
create(input: {
|
|
ownerId: string;
|
|
taskId: string;
|
|
actor: TaskCancellationActor;
|
|
assigneeActorId: string | null;
|
|
}): Promise<StoredTaskCancellation | null>;
|
|
}
|
|
|
|
export interface TaskCancellation {
|
|
id: string;
|
|
taskId: string;
|
|
cancelledByActorType: "agent" | "human" | "system";
|
|
cancelledByActorId: string;
|
|
cancelledAt: string;
|
|
}
|
|
|
|
export type TaskCancellationFailureCode = "TASK_NOT_FOUND" | "TASK_CANCELLATION_CONFLICT";
|
|
|
|
export class TaskCancellationFailure extends Error {
|
|
constructor(
|
|
readonly code: TaskCancellationFailureCode,
|
|
message: string,
|
|
) {
|
|
super(message);
|
|
this.name = "TaskCancellationFailure";
|
|
}
|
|
}
|
|
|
|
export async function replaceTaskCancellation(
|
|
repository: TaskCancellationRepository,
|
|
input: { ownerId: string; taskId: string; actor: TaskCancellationActor },
|
|
): Promise<{ cancellation: TaskCancellation; version: string; created: boolean; assigneeActorId: string | null }> {
|
|
let target = await repository.findTarget(input.ownerId, input.taskId);
|
|
if (!target) throw new TaskCancellationFailure("TASK_NOT_FOUND", "Task not found");
|
|
let assigneeActorId = target.cancellation?.assigneeActorId ?? (target.assigneeIdentityType === "realmroot_actor" ? target.assignedTo : null);
|
|
|
|
let cancellation = target.cancellation;
|
|
let created = false;
|
|
if (!cancellation) {
|
|
if (!isCancellable(target.status)) {
|
|
throw new TaskCancellationFailure("TASK_CANCELLATION_CONFLICT", `Task cannot be cancelled from ${target.status}`);
|
|
}
|
|
cancellation = await repository.create({ ...input, assigneeActorId });
|
|
if (!cancellation) {
|
|
target = await repository.findTarget(input.ownerId, input.taskId);
|
|
if (!target) throw new TaskCancellationFailure("TASK_NOT_FOUND", "Task not found");
|
|
cancellation = target.cancellation;
|
|
if (!cancellation) {
|
|
throw new TaskCancellationFailure("TASK_CANCELLATION_CONFLICT", "Task state changed before the cancellation was committed");
|
|
}
|
|
assigneeActorId = cancellation.assigneeActorId;
|
|
} else {
|
|
created = true;
|
|
}
|
|
}
|
|
|
|
return {
|
|
cancellation: {
|
|
id: input.taskId,
|
|
taskId: input.taskId,
|
|
cancelledByActorType: cancellation.actorType,
|
|
cancelledByActorId: cancellation.actorId,
|
|
cancelledAt: cancellation.cancelledAt,
|
|
},
|
|
version: cancellation.actionId,
|
|
created,
|
|
assigneeActorId,
|
|
};
|
|
}
|
|
|
|
function isCancellable(status: string): boolean {
|
|
return status === "todo" || status === "in_progress" || status === "in_review";
|
|
}
|