Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
8 changes: 6 additions & 2 deletions packages/control-plane/src/db/automation-store.ts
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,7 @@ export interface AutomationRow {
schedule_cron: string | null;
schedule_tz: string;
model: string;
agent: string | null;
reasoning_effort: string | null;
enabled: number; // SQLite integer boolean
next_run_at: number | null;
Expand Down Expand Up @@ -69,6 +70,7 @@ export function toAutomation(row: AutomationRow): Automation {
scheduleCron: row.schedule_cron,
scheduleTz: row.schedule_tz,
model: row.model,
agent: row.agent,
reasoningEffort: row.reasoning_effort,
enabled: row.enabled === 1,
nextRunAt: row.next_run_at,
Expand Down Expand Up @@ -113,10 +115,10 @@ export class AutomationStore {
.prepare(
`INSERT INTO automations
(id, name, repo_owner, repo_name, base_branch, repo_id, instructions,
trigger_type, schedule_cron, schedule_tz, model, reasoning_effort, enabled, next_run_at,
trigger_type, schedule_cron, schedule_tz, model, agent, reasoning_effort, enabled, next_run_at,
consecutive_failures, created_by, created_at, updated_at, deleted_at,
event_type, trigger_config, trigger_auth_data)
VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)`
VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)`
)
.bind(
row.id,
Expand All @@ -130,6 +132,7 @@ export class AutomationStore {
row.schedule_cron,
row.schedule_tz,
row.model,
row.agent,
row.reasoning_effort,
row.enabled,
row.next_run_at,
Expand Down Expand Up @@ -188,6 +191,7 @@ export class AutomationStore {
"schedule_cron",
"schedule_tz",
"model",
"agent",
"reasoning_effort",
"base_branch",
"next_run_at",
Expand Down
63 changes: 63 additions & 0 deletions packages/control-plane/src/db/repo-agent-cache.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,63 @@
import type { AvailableAgent } from "@open-inspect/shared";

interface RepoAgentCacheRow {
repo_owner: string;
repo_name: string;
branch: string;
agents_json: string;
created_at: number;
updated_at: number;
}

function normalize(value: string): string {
return value.trim().toLowerCase();
}

function parseAgents(value: string): AvailableAgent[] {
try {
const parsed = JSON.parse(value);
if (!Array.isArray(parsed)) return [];
return parsed.filter(
(agent): agent is AvailableAgent =>
typeof agent === "object" &&
agent !== null &&
typeof (agent as AvailableAgent).name === "string"
);
} catch {
return [];
}
}

export class RepoAgentCacheStore {
constructor(private readonly db: D1Database) {}

async get(owner: string, name: string, branch: string): Promise<AvailableAgent[] | null> {
const row = await this.db
.prepare(
"SELECT agents_json FROM repo_agent_cache WHERE repo_owner = ? AND repo_name = ? AND branch = ?"
)
.bind(normalize(owner), normalize(name), branch)
.first<Pick<RepoAgentCacheRow, "agents_json">>();

return row ? parseAgents(row.agents_json) : null;
}

async upsert(
owner: string,
name: string,
branch: string,
agents: AvailableAgent[]
): Promise<void> {
const now = Date.now();
await this.db
.prepare(
`INSERT INTO repo_agent_cache (repo_owner, repo_name, branch, agents_json, created_at, updated_at)
VALUES (?, ?, ?, ?, ?, ?)
ON CONFLICT(repo_owner, repo_name, branch) DO UPDATE SET
agents_json = excluded.agents_json,
updated_at = excluded.updated_at`
)
.bind(normalize(owner), normalize(name), branch, JSON.stringify(agents), now, now)
.run();
}
}
21 changes: 21 additions & 0 deletions packages/control-plane/src/router.ts
Original file line number Diff line number Diff line change
Expand Up @@ -461,6 +461,11 @@ const routes: Route[] = [
pattern: parsePattern("/sessions/:id/messages"),
handler: handleSessionMessages,
},
{
method: "GET",
pattern: parsePattern("/sessions/:id/agents"),
handler: handleSessionAgents,
},
{
method: "POST",
pattern: parsePattern("/sessions/:id/pr"),
Expand Down Expand Up @@ -932,6 +937,7 @@ async function handleSessionPrompt(
authorId?: string;
source?: string;
model?: string;
agent?: string;
reasoningEffort?: string;
attachments?: Array<{ type: string; name: string; url?: string }>;
callbackContext?: CallbackContext;
Expand All @@ -955,6 +961,7 @@ async function handleSessionPrompt(
authorId: body.authorId || "anonymous",
source: body.source || "web",
model: body.model,
agent: body.agent,
reasoningEffort: body.reasoningEffort,
attachments: body.attachments,
callbackContext: body.callbackContext,
Expand Down Expand Up @@ -1027,6 +1034,20 @@ async function handleSessionArtifacts(
);
}

async function handleSessionAgents(
_request: Request,
env: Env,
match: RegExpMatchArray,
ctx: RequestContext
): Promise<Response> {
const stub = getSessionStub(env, match);
if (!stub) return error("Session ID required");

return stub.fetch(
internalRequest(buildSessionInternalUrl(SessionInternalPaths.agents), undefined, ctx)
);
}

function getRequiredFormString(value: MultipartFieldValue | null, name: string): string | Response {
if (typeof value !== "string" || value.trim().length === 0) {
return error(`${name} is required`, 400);
Expand Down
5 changes: 5 additions & 0 deletions packages/control-plane/src/routes/automations.ts
Original file line number Diff line number Diff line change
Expand Up @@ -175,6 +175,7 @@ async function handleCreateAutomation(

// Validate model
const model = getValidModelOrDefault(body.model);
const agent = body.agent?.trim() ? body.agent.trim() : null;
const reasoningEffort = resolveReasoningEffort(model, body.reasoningEffort);
if (body.reasoningEffort !== undefined && body.reasoningEffort !== null && !reasoningEffort) {
return error("Invalid reasoning effort for selected model", 400);
Expand Down Expand Up @@ -228,6 +229,7 @@ async function handleCreateAutomation(
schedule_cron: body.scheduleCron ?? null,
schedule_tz: body.scheduleTz ?? "UTC",
model,
agent,
reasoning_effort: reasoningEffort,
enabled: 1,
next_run_at: nextRunAt,
Expand Down Expand Up @@ -347,6 +349,8 @@ async function handleUpdateAutomation(
}

const nextModel = body.model !== undefined ? getValidModelOrDefault(body.model) : existing.model;
const nextAgent =
body.agent !== undefined ? (body.agent?.trim() ? body.agent.trim() : null) : existing.agent;
const requestedReasoningEffort = body.reasoningEffort;
const resolvedReasoningEffort =
requestedReasoningEffort !== undefined
Expand All @@ -370,6 +374,7 @@ async function handleUpdateAutomation(
if (body.scheduleCron !== undefined) updateFields.schedule_cron = body.scheduleCron;
if (body.scheduleTz !== undefined) updateFields.schedule_tz = body.scheduleTz;
if (body.model !== undefined) updateFields.model = nextModel;
if (body.agent !== undefined) updateFields.agent = nextAgent;
if (body.reasoningEffort !== undefined || body.model !== undefined) {
updateFields.reasoning_effort = resolvedReasoningEffort;
}
Expand Down
25 changes: 25 additions & 0 deletions packages/control-plane/src/routes/repos.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@
*/

import { RepoMetadataStore } from "../db/repo-metadata";
import { RepoAgentCacheStore } from "../db/repo-agent-cache";
import type { Env } from "../types";
import type {
EnrichedRepository,
Expand Down Expand Up @@ -323,6 +324,25 @@ async function handleListBranches(
}
}

async function handleGetRepoAgents(
request: Request,
env: Env,
match: RegExpMatchArray,
_ctx: RequestContext
): Promise<Response> {
const params = extractRepoParams(match);
if (params instanceof Response) return params;
const { owner, name } = params;
const branch = new URL(request.url).searchParams.get("branch")?.trim();
if (!branch) {
return error("branch is required", 400);
}

const store = new RepoAgentCacheStore(env.DB);
const agents = await store.get(owner, name, branch);
return json({ agents: agents ?? [] });
}

export const reposRoutes: Route[] = [
{
method: "GET",
Expand All @@ -344,4 +364,9 @@ export const reposRoutes: Route[] = [
pattern: parsePattern("/repos/:owner/:name/branches"),
handler: handleListBranches,
},
{
method: "GET",
pattern: parsePattern("/repos/:owner/:name/agents"),
handler: handleGetRepoAgents,
},
];
1 change: 1 addition & 0 deletions packages/control-plane/src/scheduler/durable-object.ts
Original file line number Diff line number Diff line change
Expand Up @@ -612,6 +612,7 @@ export class SchedulerDO extends DurableObject<Env> {
content: instructionsOverride ?? automation.instructions,
authorId: automation.created_by,
source: "automation",
agent: automation.agent,
callbackContext,
}),
});
Expand Down
1 change: 1 addition & 0 deletions packages/control-plane/src/session/contracts.ts
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@ export const SessionInternalPaths = {
events: "/internal/events",
artifacts: "/internal/artifacts",
messages: "/internal/messages",
agents: "/internal/agents",
createPr: "/internal/create-pr",
wsToken: "/internal/ws-token",
archive: "/internal/archive",
Expand Down
Loading