export type BossWorkActorKind = "user" | "agent" | "device" | "thread"; export type BossWorkClaimStatus = "active" | "stealable" | "released"; export type BossWorkClaim = { claimId: string; resourceId: string; actorId: string; actorKind: BossWorkActorKind; acquiredAt: string; expiresAt: string; status: BossWorkClaimStatus; stealableAfter?: string; handoffFromActorId?: string; reason?: string; }; export type BossWorkClaimEventType = | "claim_acquired" | "claim_conflict" | "claim_released" | "claim_handoff_released" | "claim_handoff_acquired" | "claim_marked_stealable" | "claim_stale_detected"; export type BossWorkClaimEvent = { type: BossWorkClaimEventType; resourceId: string; claimId: string; actorId: string; at: string; nextActorId?: string; reason?: string; }; export type BossWorkClaimResult = { ok: boolean; reason?: "claim_conflict" | "actor_mismatch"; claim?: BossWorkClaim; conflictingClaim?: BossWorkClaim; events: BossWorkClaimEvent[]; }; export function claimWork(input: { claims: BossWorkClaim[]; resourceId: string; actorId: string; actorKind: BossWorkActorKind; now: string; ttlMs: number; claimId?: string; }): BossWorkClaimResult { const conflictingClaim = input.claims.find( (claim) => claim.resourceId === input.resourceId && claim.status === "active" && new Date(claim.expiresAt).getTime() > new Date(input.now).getTime() && claim.actorId !== input.actorId, ); if (conflictingClaim) { return { ok: false, reason: "claim_conflict", conflictingClaim, events: [ { type: "claim_conflict", resourceId: input.resourceId, claimId: conflictingClaim.claimId, actorId: input.actorId, at: input.now, reason: "claim_conflict", }, ], }; } const claim: BossWorkClaim = { claimId: input.claimId ?? makeClaimId(input.resourceId, input.actorId, input.now), resourceId: input.resourceId, actorId: input.actorId, actorKind: input.actorKind, acquiredAt: input.now, expiresAt: new Date(new Date(input.now).getTime() + input.ttlMs).toISOString(), status: "active", }; return { ok: true, claim, events: [ { type: "claim_acquired", resourceId: claim.resourceId, claimId: claim.claimId, actorId: claim.actorId, at: input.now, }, ], }; } export function releaseClaim(input: { claim: BossWorkClaim; actorId: string; now: string; reason?: string; }): BossWorkClaimResult { if (input.claim.actorId !== input.actorId) { return { ok: false, reason: "actor_mismatch", claim: input.claim, events: [], }; } const claim = { ...input.claim, status: "released" as const, reason: input.reason }; return { ok: true, claim, events: [ { type: "claim_released", resourceId: claim.resourceId, claimId: claim.claimId, actorId: input.actorId, at: input.now, reason: input.reason, }, ], }; } export function handoffWork(input: { claim: BossWorkClaim; fromActorId: string; toActorId: string; toActorKind: BossWorkActorKind; now: string; ttlMs: number; reason?: string; }): BossWorkClaimResult { if (input.claim.actorId !== input.fromActorId) { return { ok: false, reason: "actor_mismatch", claim: input.claim, events: [], }; } const claim: BossWorkClaim = { ...input.claim, claimId: makeClaimId(input.claim.resourceId, input.toActorId, input.now), actorId: input.toActorId, actorKind: input.toActorKind, acquiredAt: input.now, expiresAt: new Date(new Date(input.now).getTime() + input.ttlMs).toISOString(), status: "active", handoffFromActorId: input.fromActorId, reason: input.reason, }; return { ok: true, claim, events: [ { type: "claim_handoff_released", resourceId: input.claim.resourceId, claimId: input.claim.claimId, actorId: input.fromActorId, at: input.now, nextActorId: input.toActorId, reason: input.reason, }, { type: "claim_handoff_acquired", resourceId: claim.resourceId, claimId: claim.claimId, actorId: input.toActorId, at: input.now, reason: input.reason, }, ], }; } export function markClaimStealable(input: { claim: BossWorkClaim; actorId: string; now: string; reason?: string; }): BossWorkClaimResult { if (input.claim.actorId !== input.actorId) { return { ok: false, reason: "actor_mismatch", claim: input.claim, events: [], }; } const claim = { ...input.claim, status: "stealable" as const, stealableAfter: input.now, reason: input.reason, }; return { ok: true, claim, events: [ { type: "claim_marked_stealable", resourceId: claim.resourceId, claimId: claim.claimId, actorId: input.actorId, at: input.now, reason: input.reason, }, ], }; } export function detectStaleClaims(input: { claims: BossWorkClaim[]; now: string }): { staleClaims: BossWorkClaim[]; events: BossWorkClaimEvent[]; } { const nowMs = new Date(input.now).getTime(); const staleClaims = input.claims.filter( (claim) => claim.status !== "released" && new Date(claim.expiresAt).getTime() <= nowMs, ); return { staleClaims, events: staleClaims.map((claim) => ({ type: "claim_stale_detected", resourceId: claim.resourceId, claimId: claim.claimId, actorId: claim.actorId, at: input.now, reason: "claim_expired", })), }; } function makeClaimId(resourceId: string, actorId: string, now: string): string { return `claim_${slug(resourceId)}_${slug(actorId)}_${new Date(now).getTime()}`; } function slug(value: string): string { return value.replace(/[^a-zA-Z0-9]+/g, "-").replace(/^-|-$/g, "").toLowerCase(); }