mirror of
https://github.com/harivansh-afk/sandbox-agent.git
synced 2026-04-21 04:00:14 +00:00
feat(foundry): revert actions to queue/workflow pattern with direct sends
Revert actor communication from direct action calls to queue/workflow-based patterns for better observability (workflow history in RivetKit inspector), replay/recovery semantics, and idiomatic RivetKit usage. - Add queue/workflow infrastructure to all actors: organization, task, user, github-data, sandbox, and audit-log - Mutations route through named queues processed by workflow command loops with ctx.step() wrapping for c.state/c.db access and observability - Remove command action wrappers (~460 lines) — callers use .send() directly to queue names with expectQueueResponse() for wait:true results - Keep sendPrompt and runProcess as direct sandbox actions (long-running / large responses that would block the workflow loop or exceed 128KB limit) - Fix workspace fire-and-forget calls (enqueueWorkspaceEnsureSession, enqueueWorkspaceRefresh) to self-send to task queue instead of calling directly outside workflow step context Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
This commit is contained in:
parent
4111aebfce
commit
a171956298
21 changed files with 1443 additions and 688 deletions
|
|
@ -1,7 +1,10 @@
|
|||
// @ts-nocheck
|
||||
import { and, desc, eq } from "drizzle-orm";
|
||||
import { actor } from "rivetkit";
|
||||
import { actor, queue } from "rivetkit";
|
||||
import { workflow, Loop } from "rivetkit/workflow";
|
||||
import type { AuditLogEvent } from "@sandbox-agent/foundry-shared";
|
||||
import { selfAuditLog } from "../handles.js";
|
||||
import { logActorWarning, resolveErrorMessage } from "../logging.js";
|
||||
import { auditLogDb } from "./db/db.js";
|
||||
import { events } from "./db/schema.js";
|
||||
|
||||
|
|
@ -24,6 +27,91 @@ export interface ListAuditLogParams {
|
|||
limit?: number;
|
||||
}
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
// Queue names
|
||||
// ---------------------------------------------------------------------------
|
||||
|
||||
const AUDIT_LOG_QUEUE_NAMES = ["auditLog.command.append"] as const;
|
||||
|
||||
type AuditLogQueueName = (typeof AUDIT_LOG_QUEUE_NAMES)[number];
|
||||
|
||||
function auditLogWorkflowQueueName(name: AuditLogQueueName): AuditLogQueueName {
|
||||
return name;
|
||||
}
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
// Mutation functions
|
||||
// ---------------------------------------------------------------------------
|
||||
|
||||
async function appendMutation(c: any, body: AppendAuditLogCommand): Promise<{ ok: true }> {
|
||||
const now = Date.now();
|
||||
await c.db
|
||||
.insert(events)
|
||||
.values({
|
||||
repoId: body.repoId ?? null,
|
||||
taskId: body.taskId ?? null,
|
||||
branchName: body.branchName ?? null,
|
||||
kind: body.kind,
|
||||
payloadJson: JSON.stringify(body.payload),
|
||||
createdAt: now,
|
||||
})
|
||||
.run();
|
||||
return { ok: true };
|
||||
}
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
// Workflow command loop
|
||||
// ---------------------------------------------------------------------------
|
||||
|
||||
type AuditLogWorkflowHandler = (loopCtx: any, body: any) => Promise<any>;
|
||||
|
||||
const AUDIT_LOG_COMMAND_HANDLERS: Record<AuditLogQueueName, AuditLogWorkflowHandler> = {
|
||||
"auditLog.command.append": async (c, body) => appendMutation(c, body),
|
||||
};
|
||||
|
||||
async function runAuditLogWorkflow(ctx: any): Promise<void> {
|
||||
await ctx.loop("audit-log-command-loop", async (loopCtx: any) => {
|
||||
const msg = await loopCtx.queue.next("next-audit-log-command", {
|
||||
names: [...AUDIT_LOG_QUEUE_NAMES],
|
||||
completable: true,
|
||||
});
|
||||
|
||||
if (!msg) {
|
||||
return Loop.continue(undefined);
|
||||
}
|
||||
|
||||
const handler = AUDIT_LOG_COMMAND_HANDLERS[msg.name as AuditLogQueueName];
|
||||
if (!handler) {
|
||||
logActorWarning("auditLog", "unknown audit-log command", { command: msg.name });
|
||||
await msg.complete({ error: `Unknown command: ${msg.name}` }).catch(() => {});
|
||||
return Loop.continue(undefined);
|
||||
}
|
||||
|
||||
try {
|
||||
// Wrap in a step so c.state and c.db are accessible inside mutation functions.
|
||||
const result = await loopCtx.step({
|
||||
name: msg.name,
|
||||
timeout: 60_000,
|
||||
run: async () => handler(loopCtx, msg.body),
|
||||
});
|
||||
await msg.complete(result);
|
||||
} catch (error) {
|
||||
const message = resolveErrorMessage(error);
|
||||
logActorWarning("auditLog", "audit-log workflow command failed", {
|
||||
command: msg.name,
|
||||
error: message,
|
||||
});
|
||||
await msg.complete({ error: message }).catch(() => {});
|
||||
}
|
||||
|
||||
return Loop.continue(undefined);
|
||||
});
|
||||
}
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
// Actor definition
|
||||
// ---------------------------------------------------------------------------
|
||||
|
||||
/**
|
||||
* Organization-scoped audit log. One per org, not one per repo.
|
||||
*
|
||||
|
|
@ -35,6 +123,7 @@ export interface ListAuditLogParams {
|
|||
*/
|
||||
export const auditLog = actor({
|
||||
db: auditLogDb,
|
||||
queues: Object.fromEntries(AUDIT_LOG_QUEUE_NAMES.map((name) => [name, queue()])),
|
||||
options: {
|
||||
name: "Audit Log",
|
||||
icon: "database",
|
||||
|
|
@ -43,22 +132,14 @@ export const auditLog = actor({
|
|||
organizationId: input.organizationId,
|
||||
}),
|
||||
actions: {
|
||||
async append(c, body: AppendAuditLogCommand): Promise<{ ok: true }> {
|
||||
const now = Date.now();
|
||||
await c.db
|
||||
.insert(events)
|
||||
.values({
|
||||
repoId: body.repoId ?? null,
|
||||
taskId: body.taskId ?? null,
|
||||
branchName: body.branchName ?? null,
|
||||
kind: body.kind,
|
||||
payloadJson: JSON.stringify(body.payload),
|
||||
createdAt: now,
|
||||
})
|
||||
.run();
|
||||
// Mutation — self-send to queue for workflow history
|
||||
async append(c: any, body: AppendAuditLogCommand): Promise<{ ok: true }> {
|
||||
const self = selfAuditLog(c);
|
||||
await self.send(auditLogWorkflowQueueName("auditLog.command.append"), body, { wait: false });
|
||||
return { ok: true };
|
||||
},
|
||||
|
||||
// Read — direct action (no queue)
|
||||
async list(c, params?: ListAuditLogParams): Promise<AuditLogEvent[]> {
|
||||
const whereParts = [];
|
||||
if (params?.repoId) {
|
||||
|
|
@ -95,4 +176,5 @@ export const auditLog = actor({
|
|||
}));
|
||||
},
|
||||
},
|
||||
run: workflow(runAuditLogWorkflow),
|
||||
});
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue