feat(FN-1257): add runContext audit trail for task mutations
- Add RunMutationContext type to track which agent run caused a mutation - Thread runContext through TaskStore.logEntry, addComment, addSteeringComment, and pauseTask - Propagate runContext from HeartbeatMonitor.executeHeartbeat to task store operations - Propagate runContext from TaskExecutor.execute to task store operations - Add GET /api/agents/:id/runs/:runId/mutations endpoint to query mutations by runId - Add createTaskLogToolWithContext for heartbeat tools with run context support - Add comprehensive tests for RunMutationContext across store and heartbeat modules - Update memory.md with RunMutationContext usage convention
This commit is contained in:
@@ -44,6 +44,7 @@ import type {
|
||||
Task,
|
||||
} from "./types.js";
|
||||
import { AGENT_VALID_TRANSITIONS, agentToConfigSnapshot, diffConfigSnapshots, CheckoutConflictError } from "./types.js";
|
||||
import type { RunMutationContext } from "./types.js";
|
||||
import type { TaskStore } from "./store.js";
|
||||
import { computeAccessState } from "./agent-permissions.js";
|
||||
import { Database } from "./db.js";
|
||||
@@ -795,7 +796,7 @@ export class AgentStore extends EventEmitter {
|
||||
* @param taskId - The task ID to assign, or undefined to unassign
|
||||
* @returns The updated agent
|
||||
*/
|
||||
async assignTask(agentId: string, taskId: string | undefined): Promise<Agent> {
|
||||
async assignTask(agentId: string, taskId: string | undefined, runContext?: RunMutationContext): Promise<Agent> {
|
||||
return this.withLock(agentId, async () => {
|
||||
const agent = await this.getAgent(agentId);
|
||||
if (!agent) {
|
||||
@@ -816,6 +817,11 @@ export class AgentStore extends EventEmitter {
|
||||
this.emit("agent:assigned", updated, taskId);
|
||||
}
|
||||
|
||||
// Log the assignment to the task when a non-empty taskId is provided
|
||||
if (taskId && this.taskStore) {
|
||||
await this.taskStore.logEntry(taskId, `Task assigned to agent ${agentId}`, undefined, runContext);
|
||||
}
|
||||
|
||||
return updated;
|
||||
});
|
||||
}
|
||||
@@ -824,7 +830,7 @@ export class AgentStore extends EventEmitter {
|
||||
* Acquire a checkout lease for a task.
|
||||
* Throws CheckoutConflictError when another agent already holds the lease.
|
||||
*/
|
||||
async checkoutTask(agentId: string, taskId: string): Promise<Task> {
|
||||
async checkoutTask(agentId: string, taskId: string, runContext?: RunMutationContext): Promise<Task> {
|
||||
if (!this.taskStore) {
|
||||
throw new Error("TaskStore not configured for checkout operations");
|
||||
}
|
||||
@@ -848,14 +854,14 @@ export class AgentStore extends EventEmitter {
|
||||
}
|
||||
|
||||
const updated = await this.taskStore.updateTask(taskId, { checkedOutBy: agentId });
|
||||
await this.taskStore.logEntry(taskId, `Checked out by agent ${agentId}`);
|
||||
await this.taskStore.logEntry(taskId, `Checked out by agent ${agentId}`, undefined, runContext);
|
||||
return updated;
|
||||
}
|
||||
|
||||
/**
|
||||
* Release a checkout lease for a task.
|
||||
*/
|
||||
async releaseTask(agentId: string, taskId: string): Promise<Task> {
|
||||
async releaseTask(agentId: string, taskId: string, runContext?: RunMutationContext): Promise<Task> {
|
||||
if (!this.taskStore) {
|
||||
throw new Error("TaskStore not configured for checkout operations");
|
||||
}
|
||||
@@ -874,20 +880,20 @@ export class AgentStore extends EventEmitter {
|
||||
}
|
||||
|
||||
const updated = await this.taskStore.updateTask(taskId, { checkedOutBy: null });
|
||||
await this.taskStore.logEntry(taskId, `Released by agent ${agentId}`);
|
||||
await this.taskStore.logEntry(taskId, `Released by agent ${agentId}`, undefined, runContext);
|
||||
return updated;
|
||||
}
|
||||
|
||||
/**
|
||||
* Force release a task checkout lease regardless of holder.
|
||||
*/
|
||||
async forceReleaseTask(taskId: string): Promise<Task> {
|
||||
async forceReleaseTask(taskId: string, runContext?: RunMutationContext): Promise<Task> {
|
||||
if (!this.taskStore) {
|
||||
throw new Error("TaskStore not configured for checkout operations");
|
||||
}
|
||||
|
||||
const updated = await this.taskStore.updateTask(taskId, { checkedOutBy: null });
|
||||
await this.taskStore.logEntry(taskId, "Checkout force-released");
|
||||
await this.taskStore.logEntry(taskId, "Checkout force-released", undefined, runContext);
|
||||
return updated;
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user