mirror of
https://github.com/rowboatlabs/rowboat.git
synced 2026-05-31 19:15:17 +02:00
schedules for background agents
This commit is contained in:
parent
f03a00d2af
commit
858c277bd3
7 changed files with 176 additions and 0 deletions
43
apps/x/packages/core/src/agent-schedule/repo.ts
Normal file
43
apps/x/packages/core/src/agent-schedule/repo.ts
Normal file
|
|
@ -0,0 +1,43 @@
|
|||
import { WorkDir } from "../config/config.js";
|
||||
import { AgentScheduleConfig, AgentScheduleEntry } from "@x/shared/dist/agent-schedule.js";
|
||||
import fs from "fs/promises";
|
||||
import path from "path";
|
||||
import z from "zod";
|
||||
|
||||
const DEFAULT_AGENT_SCHEDULES: z.infer<typeof AgentScheduleConfig>["agents"] = {};
|
||||
|
||||
export interface IAgentScheduleRepo {
|
||||
ensureConfig(): Promise<void>;
|
||||
getConfig(): Promise<z.infer<typeof AgentScheduleConfig>>;
|
||||
upsert(agentName: string, entry: z.infer<typeof AgentScheduleEntry>): Promise<void>;
|
||||
delete(agentName: string): Promise<void>;
|
||||
}
|
||||
|
||||
export class FSAgentScheduleRepo implements IAgentScheduleRepo {
|
||||
private readonly configPath = path.join(WorkDir, "config", "agent-schedule.json");
|
||||
|
||||
async ensureConfig(): Promise<void> {
|
||||
try {
|
||||
await fs.access(this.configPath);
|
||||
} catch {
|
||||
await fs.writeFile(this.configPath, JSON.stringify({ agents: DEFAULT_AGENT_SCHEDULES }, null, 2));
|
||||
}
|
||||
}
|
||||
|
||||
async getConfig(): Promise<z.infer<typeof AgentScheduleConfig>> {
|
||||
const config = await fs.readFile(this.configPath, "utf8");
|
||||
return AgentScheduleConfig.parse(JSON.parse(config));
|
||||
}
|
||||
|
||||
async upsert(agentName: string, entry: z.infer<typeof AgentScheduleEntry>): Promise<void> {
|
||||
const conf = await this.getConfig();
|
||||
conf.agents[agentName] = entry;
|
||||
await fs.writeFile(this.configPath, JSON.stringify(conf, null, 2));
|
||||
}
|
||||
|
||||
async delete(agentName: string): Promise<void> {
|
||||
const conf = await this.getConfig();
|
||||
delete conf.agents[agentName];
|
||||
await fs.writeFile(this.configPath, JSON.stringify(conf, null, 2));
|
||||
}
|
||||
}
|
||||
63
apps/x/packages/core/src/agent-schedule/state-repo.ts
Normal file
63
apps/x/packages/core/src/agent-schedule/state-repo.ts
Normal file
|
|
@ -0,0 +1,63 @@
|
|||
import { WorkDir } from "../config/config.js";
|
||||
import { AgentScheduleState, AgentScheduleStateEntry } from "@x/shared/dist/agent-schedule-state.js";
|
||||
import fs from "fs/promises";
|
||||
import path from "path";
|
||||
import z from "zod";
|
||||
|
||||
const DEFAULT_AGENT_SCHEDULE_STATE: z.infer<typeof AgentScheduleState>["agents"] = {};
|
||||
|
||||
export interface IAgentScheduleStateRepo {
|
||||
ensureState(): Promise<void>;
|
||||
getState(): Promise<z.infer<typeof AgentScheduleState>>;
|
||||
getAgentState(agentName: string): Promise<z.infer<typeof AgentScheduleStateEntry> | null>;
|
||||
updateAgentState(agentName: string, entry: Partial<z.infer<typeof AgentScheduleStateEntry>>): Promise<void>;
|
||||
setAgentState(agentName: string, entry: z.infer<typeof AgentScheduleStateEntry>): Promise<void>;
|
||||
deleteAgentState(agentName: string): Promise<void>;
|
||||
}
|
||||
|
||||
export class FSAgentScheduleStateRepo implements IAgentScheduleStateRepo {
|
||||
private readonly statePath = path.join(WorkDir, "config", "agent-schedule-state.json");
|
||||
|
||||
async ensureState(): Promise<void> {
|
||||
try {
|
||||
await fs.access(this.statePath);
|
||||
} catch {
|
||||
await fs.writeFile(this.statePath, JSON.stringify({ agents: DEFAULT_AGENT_SCHEDULE_STATE }, null, 2));
|
||||
}
|
||||
}
|
||||
|
||||
async getState(): Promise<z.infer<typeof AgentScheduleState>> {
|
||||
const state = await fs.readFile(this.statePath, "utf8");
|
||||
return AgentScheduleState.parse(JSON.parse(state));
|
||||
}
|
||||
|
||||
async getAgentState(agentName: string): Promise<z.infer<typeof AgentScheduleStateEntry> | null> {
|
||||
const state = await this.getState();
|
||||
return state.agents[agentName] ?? null;
|
||||
}
|
||||
|
||||
async updateAgentState(agentName: string, entry: Partial<z.infer<typeof AgentScheduleStateEntry>>): Promise<void> {
|
||||
const state = await this.getState();
|
||||
const existing = state.agents[agentName] ?? {
|
||||
status: "scheduled" as const,
|
||||
lastRunAt: null,
|
||||
nextRunAt: null,
|
||||
lastError: null,
|
||||
runCount: 0,
|
||||
};
|
||||
state.agents[agentName] = { ...existing, ...entry };
|
||||
await fs.writeFile(this.statePath, JSON.stringify(state, null, 2));
|
||||
}
|
||||
|
||||
async setAgentState(agentName: string, entry: z.infer<typeof AgentScheduleStateEntry>): Promise<void> {
|
||||
const state = await this.getState();
|
||||
state.agents[agentName] = entry;
|
||||
await fs.writeFile(this.statePath, JSON.stringify(state, null, 2));
|
||||
}
|
||||
|
||||
async deleteAgentState(agentName: string): Promise<void> {
|
||||
const state = await this.getState();
|
||||
delete state.agents[agentName];
|
||||
await fs.writeFile(this.statePath, JSON.stringify(state, null, 2));
|
||||
}
|
||||
}
|
||||
|
|
@ -1,6 +1,8 @@
|
|||
import container from "../di/container.js";
|
||||
import type { IModelConfigRepo } from "../models/repo.js";
|
||||
import type { IMcpConfigRepo } from "../mcp/repo.js";
|
||||
import type { IAgentScheduleRepo } from "../agent-schedule/repo.js";
|
||||
import type { IAgentScheduleStateRepo } from "../agent-schedule/state-repo.js";
|
||||
import { ensureSecurityConfig } from "./security.js";
|
||||
|
||||
/**
|
||||
|
|
@ -11,10 +13,14 @@ export async function initConfigs(): Promise<void> {
|
|||
// Resolve repos and explicitly call their ensureConfig methods
|
||||
const modelConfigRepo = container.resolve<IModelConfigRepo>("modelConfigRepo");
|
||||
const mcpConfigRepo = container.resolve<IMcpConfigRepo>("mcpConfigRepo");
|
||||
const agentScheduleRepo = container.resolve<IAgentScheduleRepo>("agentScheduleRepo");
|
||||
const agentScheduleStateRepo = container.resolve<IAgentScheduleStateRepo>("agentScheduleStateRepo");
|
||||
|
||||
await Promise.all([
|
||||
modelConfigRepo.ensureConfig(),
|
||||
mcpConfigRepo.ensureConfig(),
|
||||
agentScheduleRepo.ensureConfig(),
|
||||
agentScheduleStateRepo.ensureState(),
|
||||
ensureSecurityConfig(),
|
||||
]);
|
||||
}
|
||||
|
|
|
|||
|
|
@ -12,6 +12,8 @@ import { FSOAuthRepo, IOAuthRepo } from "../auth/repo.js";
|
|||
import { FSClientRegistrationRepo, IClientRegistrationRepo } from "../auth/client-repo.js";
|
||||
import { FSGranolaConfigRepo, IGranolaConfigRepo } from "../knowledge/granola/repo.js";
|
||||
import { IAbortRegistry, InMemoryAbortRegistry } from "../runs/abort-registry.js";
|
||||
import { FSAgentScheduleRepo, IAgentScheduleRepo } from "../agent-schedule/repo.js";
|
||||
import { FSAgentScheduleStateRepo, IAgentScheduleStateRepo } from "../agent-schedule/state-repo.js";
|
||||
|
||||
const container = createContainer({
|
||||
injectionMode: InjectionMode.PROXY,
|
||||
|
|
@ -33,6 +35,8 @@ container.register({
|
|||
oauthRepo: asClass<IOAuthRepo>(FSOAuthRepo).singleton(),
|
||||
clientRegistrationRepo: asClass<IClientRegistrationRepo>(FSClientRegistrationRepo).singleton(),
|
||||
granolaConfigRepo: asClass<IGranolaConfigRepo>(FSGranolaConfigRepo).singleton(),
|
||||
agentScheduleRepo: asClass<IAgentScheduleRepo>(FSAgentScheduleRepo).singleton(),
|
||||
agentScheduleStateRepo: asClass<IAgentScheduleStateRepo>(FSAgentScheduleStateRepo).singleton(),
|
||||
});
|
||||
|
||||
export default container;
|
||||
Loading…
Add table
Add a link
Reference in a new issue