feat: add temporal run engine integration
This commit is contained in:
@@ -11,6 +11,7 @@ export interface SessionAdapter {
|
||||
}
|
||||
|
||||
export interface StartInput {
|
||||
sessionId?: string;
|
||||
runId: string;
|
||||
roleId: string;
|
||||
backend: Backend;
|
||||
|
||||
@@ -54,6 +54,10 @@ export class FakeSessionAdapter implements SessionAdapter {
|
||||
this.now = options.now ?? (() => new Date());
|
||||
}
|
||||
|
||||
reserveSessionId(): string {
|
||||
return this.sessionIdFactory();
|
||||
}
|
||||
|
||||
async start(input: StartInput): Promise<SessionHandle> {
|
||||
if (input.backend !== "fake") {
|
||||
throw new DevflowError("FakeSessionAdapter only supports the fake backend", {
|
||||
@@ -63,7 +67,7 @@ export class FakeSessionAdapter implements SessionAdapter {
|
||||
});
|
||||
}
|
||||
|
||||
const handle: SessionHandle = { sessionId: this.sessionIdFactory() };
|
||||
const handle: SessionHandle = { sessionId: input.sessionId ?? this.sessionIdFactory() };
|
||||
const record: FakeSessionRecord = {
|
||||
handle,
|
||||
runId: input.runId,
|
||||
|
||||
@@ -6,7 +6,7 @@ import {
|
||||
runs,
|
||||
tuiSessions,
|
||||
} from "@devflow/db";
|
||||
import { and, eq, inArray, ne, notInArray, sql } from "drizzle-orm";
|
||||
import { and, eq, inArray, notInArray, sql } from "drizzle-orm";
|
||||
|
||||
import type {
|
||||
ProbeResult,
|
||||
@@ -196,11 +196,11 @@ export class SessionManager implements SessionRuntime {
|
||||
.where(
|
||||
this.recoveryRunIds === undefined
|
||||
? and(
|
||||
ne(tuiSessions.state, "FAILED_NEEDS_HUMAN"),
|
||||
notInArray(tuiSessions.state, [...nonRecoverableSessionStates]),
|
||||
notInArray(runs.state, [...terminalRunStates]),
|
||||
)
|
||||
: and(
|
||||
ne(tuiSessions.state, "FAILED_NEEDS_HUMAN"),
|
||||
notInArray(tuiSessions.state, [...nonRecoverableSessionStates]),
|
||||
notInArray(runs.state, [...terminalRunStates]),
|
||||
inArray(tuiSessions.runId, [...this.recoveryRunIds]),
|
||||
),
|
||||
@@ -218,6 +218,7 @@ export class SessionManager implements SessionRuntime {
|
||||
try {
|
||||
const resumed = await this.resumeWithRetry(handle);
|
||||
this.handles.set(resumed.sessionId, resumed);
|
||||
await this.markStartupRecoverySucceeded(session, resumed);
|
||||
recoveredSessionIds.push(resumed.sessionId);
|
||||
} catch (error) {
|
||||
await this.markRecoveryFailed(session, error);
|
||||
@@ -228,6 +229,59 @@ export class SessionManager implements SessionRuntime {
|
||||
return { recoveredSessionIds, failedSessionIds };
|
||||
}
|
||||
|
||||
private async markStartupRecoverySucceeded(
|
||||
session: {
|
||||
id: string;
|
||||
runId: string;
|
||||
roleId: string;
|
||||
backend: string;
|
||||
recoveryAttempts: number;
|
||||
state: string;
|
||||
},
|
||||
handle: SessionHandle,
|
||||
): Promise<void> {
|
||||
if (this.db === undefined || !["CREATED", "BOOTSTRAPPING"].includes(session.state)) {
|
||||
return;
|
||||
}
|
||||
|
||||
const eventRepository = new RunEventRepository(this.db);
|
||||
const sessionUpdate: {
|
||||
state: "READY";
|
||||
lastKnownPanePid?: number;
|
||||
tmuxSession?: string;
|
||||
tmuxWindow?: string;
|
||||
} = { state: "READY" };
|
||||
if (handle.pid !== undefined) {
|
||||
sessionUpdate.lastKnownPanePid = handle.pid;
|
||||
}
|
||||
if (handle.tmuxSession !== undefined) {
|
||||
sessionUpdate.tmuxSession = handle.tmuxSession;
|
||||
}
|
||||
if (handle.tmuxWindow !== undefined) {
|
||||
sessionUpdate.tmuxWindow = handle.tmuxWindow;
|
||||
}
|
||||
|
||||
await this.db.transaction(async (tx) => {
|
||||
await tx.update(tuiSessions).set(sessionUpdate).where(eq(tuiSessions.id, session.id));
|
||||
await eventRepository.appendInTransaction(tx, {
|
||||
runId: session.runId,
|
||||
type: "session.created",
|
||||
payload: { sessionId: session.id, roleId: session.roleId, backend: session.backend },
|
||||
idempotencyKey: `session.created:${session.id}`,
|
||||
});
|
||||
await eventRepository.appendInTransaction(tx, {
|
||||
runId: session.runId,
|
||||
type: "session.ready",
|
||||
payload: {
|
||||
sessionId: session.id,
|
||||
roleId: session.roleId,
|
||||
recoveryAttempts: session.recoveryAttempts,
|
||||
},
|
||||
idempotencyKey: `session.ready:${session.id}:${session.recoveryAttempts}`,
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
private async markRecoveryFailed(
|
||||
session: {
|
||||
id: string;
|
||||
@@ -380,6 +434,7 @@ export class SessionManager implements SessionRuntime {
|
||||
}
|
||||
|
||||
const terminalRunStates = ["completed", "failed", "aborted"] as const;
|
||||
const nonRecoverableSessionStates = ["FAILED_NEEDS_HUMAN"] as const;
|
||||
|
||||
function isTerminalRunState(state: string): state is (typeof terminalRunStates)[number] {
|
||||
return terminalRunStates.includes(state as (typeof terminalRunStates)[number]);
|
||||
|
||||
Reference in New Issue
Block a user