server for rowboatx

This commit is contained in:
Ramnique Singh 2025-12-02 13:24:58 +05:30
parent ae877e70ae
commit 9ad6331fbc
38 changed files with 2223 additions and 1088 deletions

20
apps/cli/src/runs/lock.ts Normal file
View file

@ -0,0 +1,20 @@
export interface IRunsLock {
lock(runId: string): Promise<boolean>;
release(runId: string): Promise<void>;
}
export class InMemoryRunsLock implements IRunsLock {
private locks: Record<string, boolean> = {};
async lock(runId: string): Promise<boolean> {
if (this.locks[runId]) {
return false;
}
this.locks[runId] = true;
return true;
}
async release(runId: string): Promise<void> {
delete this.locks[runId];
}
}

79
apps/cli/src/runs/repo.ts Normal file
View file

@ -0,0 +1,79 @@
import { Run } from "./runs.js";
import z from "zod";
import { IMonotonicallyIncreasingIdGenerator } from "../application/lib/id-gen.js";
import { WorkDir } from "../config/config.js";
import path from "path";
import fsp from "fs/promises";
import { RunEvent, StartEvent } from "../entities/run-events.js";
export const ListRunsResponse = z.object({
runs: z.array(Run.pick({
id: true,
createdAt: true,
agentId: true,
})),
nextCursor: z.string().optional(),
});
export const CreateRunOptions = Run.pick({
agentId: true,
});
export interface IRunsRepo {
create(options: z.infer<typeof CreateRunOptions>): Promise<z.infer<typeof Run>>;
fetch(id: string): Promise<z.infer<typeof Run>>;
appendEvents(runId: string, events: z.infer<typeof RunEvent>[]): Promise<void>;
}
export class FSRunsRepo implements IRunsRepo {
private idGenerator: IMonotonicallyIncreasingIdGenerator;
constructor({
idGenerator,
}: {
idGenerator: IMonotonicallyIncreasingIdGenerator;
}) {
this.idGenerator = idGenerator;
}
async appendEvents(runId: string, events: z.infer<typeof RunEvent>[]): Promise<void> {
await fsp.appendFile(
path.join(WorkDir, 'runs', `${runId}.jsonl`),
events.map(event => JSON.stringify(event)).join("\n") + "\n"
);
}
async create(options: z.infer<typeof CreateRunOptions>): Promise<z.infer<typeof Run>> {
const runId = await this.idGenerator.next();
const ts = new Date().toISOString();
const start: z.infer<typeof StartEvent> = {
type: "start",
runId,
agentName: options.agentId,
subflow: [],
ts,
};
await this.appendEvents(runId, [start]);
return {
id: runId,
createdAt: ts,
agentId: options.agentId,
log: [start],
};
}
async fetch(id: string): Promise<z.infer<typeof Run>> {
const contents = await fsp.readFile(path.join(WorkDir, 'runs', `${id}.jsonl`), 'utf8');
const events = contents.split('\n')
.filter(line => line.trim() !== '')
.map(line => RunEvent.parse(JSON.parse(line)));
if (events.length === 0 || events[0].type !== 'start') {
throw new Error('Corrupt run data');
}
return {
id,
createdAt: events[0].ts!,
agentId: events[0].agentName,
log: events,
};
}
}

70
apps/cli/src/runs/runs.ts Normal file
View file

@ -0,0 +1,70 @@
import z from "zod";
import container from "../di/container.js";
import { IMessageQueue } from "../application/lib/message-queue.js";
import { AskHumanResponseEvent, RunEvent, ToolPermissionResponseEvent } from "../entities/run-events.js";
import { CreateRunOptions, IRunsRepo } from "./repo.js";
import { IAgentRuntime } from "../agents/runtime.js";
import { IBus } from "../application/lib/bus.js";
export const ToolPermissionAuthorizePayload = ToolPermissionResponseEvent.pick({
subflow: true,
toolCallId: true,
response: true,
});
export const AskHumanResponsePayload = AskHumanResponseEvent.pick({
subflow: true,
toolCallId: true,
response: true,
});
export const Run = z.object({
id: z.string(),
createdAt: z.iso.datetime(),
agentId: z.string(),
log: z.array(RunEvent),
});
export async function createRun(opts: z.infer<typeof CreateRunOptions>): Promise<z.infer<typeof Run>> {
const repo = container.resolve<IRunsRepo>('runsRepo');
const bus = container.resolve<IBus>('bus');
const run = await repo.create(opts);
await bus.publish(run.log[0]);
return run;
}
export async function createMessage(runId: string, message: string): Promise<string> {
const queue = container.resolve<IMessageQueue>('messageQueue');
const id = await queue.enqueue(runId, message);
const runtime = container.resolve<IAgentRuntime>('agentRuntime');
runtime.trigger(runId);
return id;
}
export async function authorizePermission(runId: string, ev: z.infer<typeof ToolPermissionAuthorizePayload>): Promise<void> {
const repo = container.resolve<IRunsRepo>('runsRepo');
const event: z.infer<typeof ToolPermissionResponseEvent> = {
...ev,
runId,
type: "tool-permission-response",
};
await repo.appendEvents(runId, [event]);
const runtime = container.resolve<IAgentRuntime>('agentRuntime');
runtime.trigger(runId);
}
export async function replyToHumanInputRequest(runId: string, ev: z.infer<typeof AskHumanResponsePayload>): Promise<void> {
const repo = container.resolve<IRunsRepo>('runsRepo');
const event: z.infer<typeof AskHumanResponseEvent> = {
...ev,
runId,
type: "ask-human-response",
};
await repo.appendEvents(runId, [event]);
const runtime = container.resolve<IAgentRuntime>('agentRuntime');
runtime.trigger(runId);
}
export async function stop(runId: string): Promise<void> {
throw new Error('Not implemented');
}