mirror of
https://github.com/paperclipai/paperclip.git
synced 2026-11-11 21:59:38 +01:00
Implement Reflection Coach built-in bundle server support
Co-Authored-By: Paperclip <noreply@paperclip.ing>
This commit is contained in:
1 parent
a56d05ea41
commit
752b4a32a5
11 files changed
+1253
-37
No files matched your search
@@ -0,0 +1,26 @@
|
||||
CREATE TABLE "built_in_managed_resources" (
|
||||
"id" uuid PRIMARY KEY DEFAULT gen_random_uuid() NOT NULL,
|
||||
"company_id" uuid NOT NULL,
|
||||
"bundle_key" text NOT NULL,
|
||||
"resource_kind" text NOT NULL,
|
||||
"resource_key" text NOT NULL,
|
||||
"resource_id" uuid NOT NULL,
|
||||
"stock_version" text NOT NULL,
|
||||
"stock_hash" text NOT NULL,
|
||||
"defaults_json" jsonb DEFAULT '{}'::jsonb NOT NULL,
|
||||
"created_at" timestamp with time zone DEFAULT now() NOT NULL,
|
||||
"updated_at" timestamp with time zone DEFAULT now() NOT NULL
|
||||
);
|
||||
|
||||
ALTER TABLE "built_in_managed_resources"
|
||||
ADD CONSTRAINT "built_in_managed_resources_company_id_companies_id_fk"
|
||||
FOREIGN KEY ("company_id") REFERENCES "companies"("id") ON DELETE cascade ON UPDATE no action;
|
||||
|
||||
CREATE INDEX "built_in_managed_resources_company_idx"
|
||||
ON "built_in_managed_resources" ("company_id");
|
||||
|
||||
CREATE INDEX "built_in_managed_resources_resource_idx"
|
||||
ON "built_in_managed_resources" ("resource_kind", "resource_id");
|
||||
|
||||
CREATE UNIQUE INDEX "built_in_managed_resources_company_bundle_resource_uq"
|
||||
ON "built_in_managed_resources" ("company_id", "bundle_key", "resource_kind", "resource_key");
|
||||
@@ -0,0 +1,31 @@
|
||||
import { index, jsonb, pgTable, text, timestamp, uniqueIndex, uuid } from "drizzle-orm/pg-core";
|
||||
import { companies } from "./companies.js";
|
||||
|
||||
export const builtInManagedResources = pgTable(
|
||||
"built_in_managed_resources",
|
||||
{
|
||||
id: uuid("id").primaryKey().defaultRandom(),
|
||||
companyId: uuid("company_id")
|
||||
.notNull()
|
||||
.references(() => companies.id, { onDelete: "cascade" }),
|
||||
bundleKey: text("bundle_key").notNull(),
|
||||
resourceKind: text("resource_kind").notNull(),
|
||||
resourceKey: text("resource_key").notNull(),
|
||||
resourceId: uuid("resource_id").notNull(),
|
||||
stockVersion: text("stock_version").notNull(),
|
||||
stockHash: text("stock_hash").notNull(),
|
||||
defaultsJson: jsonb("defaults_json").$type<Record<string, unknown>>().notNull().default({}),
|
||||
createdAt: timestamp("created_at", { withTimezone: true }).notNull().defaultNow(),
|
||||
updatedAt: timestamp("updated_at", { withTimezone: true }).notNull().defaultNow(),
|
||||
},
|
||||
(table) => ({
|
||||
companyIdx: index("built_in_managed_resources_company_idx").on(table.companyId),
|
||||
resourceIdx: index("built_in_managed_resources_resource_idx").on(table.resourceKind, table.resourceId),
|
||||
companyBundleResourceUq: uniqueIndex("built_in_managed_resources_company_bundle_resource_uq").on(
|
||||
table.companyId,
|
||||
table.bundleKey,
|
||||
table.resourceKind,
|
||||
table.resourceKey,
|
||||
),
|
||||
}),
|
||||
);
|
||||
@@ -6,6 +6,7 @@ export { cloudUpstreamConnections, cloudUpstreamRuns } from "./cloud_upstreams.j
|
||||
export { instanceUserRoles } from "./instance_user_roles.js";
|
||||
export { userSidebarPreferences } from "./user_sidebar_preferences.js";
|
||||
export { agents } from "./agents.js";
|
||||
export { builtInManagedResources } from "./built_in_managed_resources.js";
|
||||
export { agentMemberships } from "./agent_memberships.js";
|
||||
export { boardApiKeys } from "./board_api_keys.js";
|
||||
export { cliAuthChallenges } from "./cli_auth_challenges.js";
|
||||
|
||||
@@ -1163,7 +1163,9 @@ export {
|
||||
agentSkillSyncSchema,
|
||||
type AgentSkillSync,
|
||||
createAgentSchema,
|
||||
builtInAgentEmptyMutationSchema,
|
||||
builtInAgentProvisionSchema,
|
||||
builtInAgentResetSchema,
|
||||
createAgentHireSchema,
|
||||
updateAgentSchema,
|
||||
agentInstructionsBundleModeSchema,
|
||||
@@ -1184,6 +1186,7 @@ export {
|
||||
updateAgentPermissionsSchema,
|
||||
type CreateAgent,
|
||||
type BuiltInAgentProvision,
|
||||
type BuiltInAgentReset,
|
||||
type CreateAgentHire,
|
||||
type UpdateAgent,
|
||||
type UpdateAgentInstructionsBundle,
|
||||
|
||||
@@ -96,6 +96,16 @@ export const builtInAgentProvisionSchema = z.object({
|
||||
|
||||
export type BuiltInAgentProvision = z.infer<typeof builtInAgentProvisionSchema>;
|
||||
|
||||
export const builtInAgentEmptyMutationSchema = z.object({}).strict().default({});
|
||||
|
||||
export type BuiltInAgentEmptyMutation = z.infer<typeof builtInAgentEmptyMutationSchema>;
|
||||
|
||||
export const builtInAgentResetSchema = z.object({
|
||||
resources: z.array(z.enum(["agent", "instructions", "skill", "routine"])).optional(),
|
||||
}).strict().default({});
|
||||
|
||||
export type BuiltInAgentReset = z.infer<typeof builtInAgentResetSchema>;
|
||||
|
||||
export const createAgentHireSchema = createAgentSchema.extend({
|
||||
sourceIssueId: z.string().uuid().optional().nullable(),
|
||||
sourceIssueIds: z.array(z.string().uuid()).optional(),
|
||||
|
||||
@@ -247,7 +247,9 @@ export {
|
||||
|
||||
export {
|
||||
createAgentSchema,
|
||||
builtInAgentEmptyMutationSchema,
|
||||
builtInAgentProvisionSchema,
|
||||
builtInAgentResetSchema,
|
||||
createAgentHireSchema,
|
||||
updateAgentSchema,
|
||||
agentRuntimeConfigSchema,
|
||||
@@ -269,6 +271,7 @@ export {
|
||||
updateAgentPermissionsSchema,
|
||||
type CreateAgent,
|
||||
type BuiltInAgentProvision,
|
||||
type BuiltInAgentReset,
|
||||
type CreateAgentHire,
|
||||
type UpdateAgent,
|
||||
type UpdateAgentInstructionsBundle,
|
||||
|
||||
@@ -7,14 +7,22 @@ import {
|
||||
agents,
|
||||
approvals,
|
||||
budgetPolicies,
|
||||
builtInManagedResources,
|
||||
companies,
|
||||
companySkillVersions,
|
||||
companySkills,
|
||||
createDb,
|
||||
principalPermissionGrants,
|
||||
routines,
|
||||
routineTriggers,
|
||||
} from "@paperclipai/db";
|
||||
import { readPaperclipSkillSyncPreference } from "@paperclipai/adapter-utils/server-utils";
|
||||
import {
|
||||
getEmbeddedPostgresTestSupport,
|
||||
startEmbeddedPostgresTestDatabase,
|
||||
} from "./helpers/embedded-postgres.js";
|
||||
import { HttpError } from "../errors.ts";
|
||||
import { agentInstructionsService } from "../services/agent-instructions.ts";
|
||||
import { agentService } from "../services/agents.ts";
|
||||
import { approvalService } from "../services/approvals.ts";
|
||||
import {
|
||||
@@ -49,6 +57,12 @@ describeEmbeddedPostgres("built-in agents", () => {
|
||||
}, 20_000);
|
||||
|
||||
afterEach(async () => {
|
||||
await db.delete(routineTriggers);
|
||||
await db.delete(routines);
|
||||
await db.delete(builtInManagedResources);
|
||||
await db.delete(companySkillVersions);
|
||||
await db.delete(companySkills);
|
||||
await db.delete(principalPermissionGrants);
|
||||
await db.delete(agentConfigRevisions);
|
||||
await db.delete(activityLog);
|
||||
await db.delete(approvals);
|
||||
@@ -67,13 +81,14 @@ describeEmbeddedPostgres("built-in agents", () => {
|
||||
id: companyId,
|
||||
name: "Paperclip",
|
||||
issuePrefix: issuePrefix(companyId),
|
||||
defaultResponsibleUserId: "responsible-user",
|
||||
requireBoardApprovalForNewAgents: true,
|
||||
});
|
||||
return companyId;
|
||||
}
|
||||
|
||||
it("validates the static registry and rejects invalid definitions", () => {
|
||||
expect(listBuiltInAgentDefinitions().map((definition) => definition.key).sort()).toEqual(["briefs", "learning"]);
|
||||
expect(listBuiltInAgentDefinitions().map((definition) => definition.key).sort()).toEqual(["briefs", "learning", "reflection-coach"]);
|
||||
expect(() => validateBuiltInAgentDefinitions([
|
||||
{
|
||||
key: "briefs",
|
||||
@@ -393,6 +408,108 @@ describeEmbeddedPostgres("built-in agents", () => {
|
||||
});
|
||||
});
|
||||
|
||||
it("auto-provisions a paused Reflection Coach bundle with skill sync and a disabled routine", async () => {
|
||||
const companyId = await seedCompany();
|
||||
await agentService(db).create(companyId, {
|
||||
name: "CEO",
|
||||
role: "ceo",
|
||||
status: "idle",
|
||||
adapterType: "codex_local",
|
||||
adapterConfig: { model: "gpt-5.4", apiKey: "do-not-copy" },
|
||||
runtimeConfig: {},
|
||||
permissions: {},
|
||||
});
|
||||
|
||||
const result = await reconcileBuiltInAgentsOnStartup(db);
|
||||
expect(result.autoEnsured).toBeGreaterThanOrEqual(1);
|
||||
|
||||
const state = await builtInAgentService(db).get(companyId, "reflection-coach");
|
||||
expect(state).toMatchObject({
|
||||
status: "paused",
|
||||
agent: {
|
||||
companyId,
|
||||
name: "Reflection Coach",
|
||||
role: "general",
|
||||
title: "Reflection Coach",
|
||||
icon: "eye",
|
||||
adapterType: "codex_local",
|
||||
permissions: {
|
||||
canCreateAgents: false,
|
||||
canCreateSkills: false,
|
||||
},
|
||||
},
|
||||
});
|
||||
expect(state.agent?.adapterConfig).toMatchObject({
|
||||
instructionsBundleMode: "managed",
|
||||
instructionsEntryFile: "AGENTS.md",
|
||||
});
|
||||
expect(state.agent?.adapterConfig).not.toMatchObject({ model: "gpt-5.4", apiKey: "do-not-copy" });
|
||||
expect(state.resources.map((resource) => [resource.resourceKind, resource.stockStatus])).toEqual([
|
||||
["instructions", "stock_current"],
|
||||
["skill", "stock_current"],
|
||||
["routine", "stock_current"],
|
||||
]);
|
||||
|
||||
const agentRows = await db.select().from(agents).where(eq(agents.companyId, companyId));
|
||||
expect(agentRows.filter((row) => readBuiltInAgentMarker(row.metadata)?.key === "reflection-coach")).toHaveLength(1);
|
||||
|
||||
const [skill] = await db
|
||||
.select()
|
||||
.from(companySkills)
|
||||
.where(eq(companySkills.key, "paperclipai/bundled/paperclip-operations/reflection-coach"));
|
||||
expect(skill).toMatchObject({
|
||||
key: "paperclipai/bundled/paperclip-operations/reflection-coach",
|
||||
slug: "reflection-coach",
|
||||
});
|
||||
expect(readPaperclipSkillSyncPreference(state.agent!.adapterConfig as Record<string, unknown>).desiredSkills).toContain(
|
||||
"paperclipai/bundled/paperclip-operations/reflection-coach",
|
||||
);
|
||||
|
||||
const [routine] = await db.select().from(routines).where(eq(routines.companyId, companyId));
|
||||
expect(routine).toMatchObject({
|
||||
title: "Review recent agent trajectories for coaching proposals",
|
||||
status: "paused",
|
||||
assigneeAgentId: state.agentId,
|
||||
});
|
||||
const [trigger] = await db.select().from(routineTriggers).where(eq(routineTriggers.routineId, routine!.id));
|
||||
expect(trigger).toMatchObject({
|
||||
kind: "schedule",
|
||||
enabled: false,
|
||||
cronExpression: "0 9 * * 1",
|
||||
timezone: "UTC",
|
||||
});
|
||||
});
|
||||
|
||||
it("preserves Reflection Coach instruction drift on reconcile and restores it on reset", async () => {
|
||||
const companyId = await seedCompany();
|
||||
const builtIns = builtInAgentService(db);
|
||||
const created = await builtIns.ensure(companyId, "reflection-coach");
|
||||
const instructions = agentInstructionsService();
|
||||
|
||||
await instructions.writeFile(created.agent!, "AGENTS.md", "# Custom Reflection Coach\n\nOperator edit.\n");
|
||||
|
||||
const reconciled = await builtIns.ensure(companyId, "reflection-coach");
|
||||
const drift = reconciled.resources.find((resource) => resource.resourceKind === "instructions");
|
||||
expect(drift).toMatchObject({
|
||||
stockStatus: "operator_modified",
|
||||
updateAvailable: true,
|
||||
resetAvailable: true,
|
||||
changedFiles: ["AGENTS.md"],
|
||||
});
|
||||
await expect(instructions.readFile(reconciled.agent!, "AGENTS.md")).resolves.toMatchObject({
|
||||
content: "# Custom Reflection Coach\n\nOperator edit.\n",
|
||||
});
|
||||
|
||||
const reset = await builtIns.reset(companyId, "reflection-coach");
|
||||
expect(reset.resources.find((resource) => resource.resourceKind === "instructions")).toMatchObject({
|
||||
stockStatus: "stock_current",
|
||||
resetAvailable: false,
|
||||
});
|
||||
const resetFile = await instructions.readFile(reset.agent!, "AGENTS.md");
|
||||
expect(resetFile.content).toContain("Reflection Coach");
|
||||
expect(resetFile.content).not.toContain("Operator edit.");
|
||||
});
|
||||
|
||||
it("blocks deleting a built-in agent", async () => {
|
||||
const companyId = await seedCompany();
|
||||
const state = await builtInAgentService(db).ensure(companyId, "briefs");
|
||||
@@ -472,7 +589,9 @@ describeEmbeddedPostgres("built-in agents", () => {
|
||||
});
|
||||
|
||||
const result = await reconcileBuiltInAgentsOnStartup(db);
|
||||
expect(result).toMatchObject({ scanned: 1, reconciled: 1, unknown: 0, duplicates: 0 });
|
||||
expect(result).toMatchObject({ unknown: 0, duplicates: 0 });
|
||||
expect(result.scanned).toBeGreaterThanOrEqual(1);
|
||||
expect(result.reconciled).toBeGreaterThanOrEqual(1);
|
||||
|
||||
const [row] = await db.select().from(agents).where(eq(agents.id, agentId));
|
||||
expect(row).toMatchObject({
|
||||
@@ -521,4 +640,108 @@ describeEmbeddedPostgres("built-in agents", () => {
|
||||
},
|
||||
} satisfies Partial<HttpError>);
|
||||
});
|
||||
|
||||
it("automatically materializes the Reflection Coach bundle without enabling background work", async () => {
|
||||
const companyId = await seedCompany();
|
||||
const root = await agentService(db).create(companyId, {
|
||||
name: "CEO",
|
||||
role: "ceo",
|
||||
status: "idle",
|
||||
adapterType: "codex_local",
|
||||
adapterConfig: { model: "gpt-5.4" },
|
||||
runtimeConfig: {},
|
||||
permissions: {},
|
||||
});
|
||||
|
||||
const state = await builtInAgentService(db).ensure(companyId, "reflection-coach");
|
||||
|
||||
expect(state.agent).toMatchObject({
|
||||
companyId,
|
||||
name: "Reflection Coach",
|
||||
title: "Reflection Coach",
|
||||
icon: "eye",
|
||||
reportsTo: root.id,
|
||||
adapterType: "codex_local",
|
||||
budgetMonthlyCents: 0,
|
||||
});
|
||||
expect(state.status).toBe("paused");
|
||||
expect(readBuiltInAgentMarker(state.agent?.metadata)).toEqual({
|
||||
key: "reflection-coach",
|
||||
featureKeys: ["reflection-coach"],
|
||||
});
|
||||
expect(state.resources.map((resource) => [resource.resourceKind, resource.stockStatus])).toEqual([
|
||||
["instructions", "stock_current"],
|
||||
["skill", "stock_current"],
|
||||
["routine", "stock_current"],
|
||||
]);
|
||||
|
||||
const [skill] = await db
|
||||
.select()
|
||||
.from(companySkills)
|
||||
.where(eq(companySkills.key, "paperclipai/bundled/paperclip-operations/reflection-coach"));
|
||||
expect(skill).toMatchObject({
|
||||
key: "paperclipai/bundled/paperclip-operations/reflection-coach",
|
||||
slug: "reflection-coach",
|
||||
});
|
||||
expect(readPaperclipSkillSyncPreference(state.agent!.adapterConfig).desiredSkills).toContain(skill!.key);
|
||||
|
||||
const [routine] = await db.select().from(routines).where(eq(routines.companyId, companyId));
|
||||
expect(routine).toMatchObject({
|
||||
title: "Review recent agent trajectories for coaching proposals",
|
||||
status: "paused",
|
||||
assigneeAgentId: state.agentId,
|
||||
originKind: "built_in_agent_bundle",
|
||||
originId: "reflection-coach:recent-agent-reflection",
|
||||
});
|
||||
const [trigger] = await db.select().from(routineTriggers).where(eq(routineTriggers.routineId, routine!.id));
|
||||
expect(trigger).toMatchObject({
|
||||
kind: "schedule",
|
||||
enabled: false,
|
||||
cronExpression: "0 9 * * 1",
|
||||
timezone: "UTC",
|
||||
});
|
||||
|
||||
const grants = await db
|
||||
.select()
|
||||
.from(principalPermissionGrants)
|
||||
.where(eq(principalPermissionGrants.principalId, state.agentId!));
|
||||
expect(grants.map((grant) => grant.permissionKey)).not.toContain("tasks:assign");
|
||||
});
|
||||
|
||||
it("preserves Reflection Coach stock drift until explicit reset", async () => {
|
||||
const companyId = await seedCompany();
|
||||
const created = await builtInAgentService(db).ensure(companyId, "reflection-coach");
|
||||
const agent = created.agent!;
|
||||
|
||||
const instructionsSvc = agentInstructionsService();
|
||||
await instructionsSvc.writeFile(agent, "AGENTS.md", "# Custom Reflection Coach\n\nDo not overwrite me.\n");
|
||||
await db
|
||||
.update(companySkills)
|
||||
.set({ markdown: "---\nname: reflection-coach\n---\n\n# Custom skill\n" })
|
||||
.where(eq(companySkills.companyId, companyId));
|
||||
|
||||
const drifted = await builtInAgentService(db).ensure(companyId, "reflection-coach");
|
||||
expect(drifted.resources.find((resource) => resource.resourceKind === "instructions")).toMatchObject({
|
||||
stockStatus: "operator_modified",
|
||||
resetAvailable: true,
|
||||
});
|
||||
expect(drifted.resources.find((resource) => resource.resourceKind === "skill")).toMatchObject({
|
||||
stockStatus: "operator_modified",
|
||||
resetAvailable: true,
|
||||
});
|
||||
expect((await instructionsSvc.readFile(drifted.agent!, "AGENTS.md")).content).toContain("Do not overwrite me.");
|
||||
|
||||
const reset = await builtInAgentService(db).reset(companyId, "reflection-coach", {
|
||||
resources: ["instructions"],
|
||||
});
|
||||
expect(reset.resources.find((resource) => resource.resourceKind === "instructions")).toMatchObject({
|
||||
stockStatus: "stock_current",
|
||||
resetAvailable: false,
|
||||
});
|
||||
expect(reset.resources.find((resource) => resource.resourceKind === "skill")).toMatchObject({
|
||||
stockStatus: "operator_modified",
|
||||
resetAvailable: true,
|
||||
});
|
||||
expect((await instructionsSvc.readFile(reset.agent!, "AGENTS.md")).content).toContain("built-in Reflection Coach");
|
||||
});
|
||||
});
|
||||
+2
-7
@@ -780,14 +780,9 @@ export async function startServer(): Promise<StartedServer> {
|
||||
logger.error({ err }, "startup reconciliation of codex_local managed homes failed");
|
||||
});
|
||||
|
||||
void instanceSettingsService(db as any).getExperimental()
|
||||
.then((experimental) => {
|
||||
if (experimental.enableBuiltInAgents !== true) return null;
|
||||
return reconcileBuiltInAgentsOnStartup(db as any);
|
||||
})
|
||||
void reconcileBuiltInAgentsOnStartup(db as any)
|
||||
.then((result) => {
|
||||
if (!result) return;
|
||||
if (result.reconciled > 0 || result.unknown > 0 || result.duplicates > 0) {
|
||||
if (result.reconciled > 0 || result.unknown > 0 || result.duplicates > 0 || result.autoEnsured > 0) {
|
||||
logger.warn(
|
||||
result,
|
||||
"startup reconciliation of built-in agents complete",
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
import { Router, type Request } from "express";
|
||||
import type { Db } from "@paperclipai/db";
|
||||
import { builtInAgentProvisionSchema } from "@paperclipai/shared";
|
||||
import { builtInAgentEmptyMutationSchema, builtInAgentProvisionSchema, builtInAgentResetSchema } from "@paperclipai/shared";
|
||||
import { validate } from "../middleware/validate.js";
|
||||
import { forbidden, notFound } from "../errors.js";
|
||||
import { accessService, instanceSettingsService, logActivity } from "../services/index.js";
|
||||
@@ -10,9 +10,36 @@ import { assertCompanyAccess, getActorInfo } from "./authz.js";
|
||||
import type { BuiltInAgentState } from "../services/built-in-agents.js";
|
||||
|
||||
function redactBuiltInAgentListState(state: BuiltInAgentState): BuiltInAgentState {
|
||||
if (!state.agent) return state;
|
||||
const definition = {
|
||||
...state.definition,
|
||||
defaultInstructions: state.definition.defaultInstructions ? "[file-backed]" : "",
|
||||
bundle: state.definition.bundle
|
||||
? {
|
||||
stockVersion: state.definition.bundle.stockVersion,
|
||||
instructions: {
|
||||
entryFile: state.definition.bundle.instructions.entryFile,
|
||||
files: Object.keys(state.definition.bundle.instructions.files),
|
||||
},
|
||||
skill: {
|
||||
skillKey: state.definition.bundle.skill.skillKey,
|
||||
displayName: state.definition.bundle.skill.displayName,
|
||||
slug: state.definition.bundle.skill.slug,
|
||||
canonicalKey: state.definition.bundle.skill.canonicalKey,
|
||||
files: Object.keys(state.definition.bundle.skill.files),
|
||||
},
|
||||
routine: {
|
||||
routineKey: state.definition.bundle.routine.routineKey,
|
||||
title: state.definition.bundle.routine.title,
|
||||
status: state.definition.bundle.routine.status,
|
||||
triggerCount: state.definition.bundle.routine.triggers.length,
|
||||
},
|
||||
}
|
||||
: undefined,
|
||||
} as BuiltInAgentState["definition"];
|
||||
if (!state.agent) return { ...state, definition };
|
||||
return {
|
||||
...state,
|
||||
definition,
|
||||
agent: {
|
||||
...state.agent,
|
||||
adapterConfig: {},
|
||||
@@ -49,7 +76,7 @@ export function builtInAgentRoutes(db: Db) {
|
||||
req: Request,
|
||||
input: {
|
||||
companyId: string;
|
||||
action: "built_in_agent.provision_requested" | "built_in_agent.reset" | "approval.created";
|
||||
action: "built_in_agent.provision_requested" | "built_in_agent.reconcile" | "built_in_agent.reset" | "approval.created";
|
||||
key: string;
|
||||
agentId: string | null;
|
||||
status: string;
|
||||
@@ -82,6 +109,30 @@ export function builtInAgentRoutes(db: Db) {
|
||||
res.json(states.map(redactBuiltInAgentListState));
|
||||
});
|
||||
|
||||
router.get("/companies/:companyId/built-in-agents/:key/status", async (req, res) => {
|
||||
const companyId = req.params.companyId as string;
|
||||
const key = req.params.key as string;
|
||||
assertCompanyAccess(req, companyId);
|
||||
await assertBuiltInAgentsEnabled();
|
||||
res.json(redactBuiltInAgentListState(await svc.get(companyId, key)));
|
||||
});
|
||||
|
||||
router.post("/companies/:companyId/built-in-agents/:key/reconcile", validate(builtInAgentEmptyMutationSchema), async (req, res) => {
|
||||
const companyId = req.params.companyId as string;
|
||||
const key = req.params.key as string;
|
||||
await assertBuiltInAgentsEnabled();
|
||||
await assertCanProvisionBuiltInAgents(req, companyId);
|
||||
const state = await svc.ensure(companyId, key);
|
||||
await logBuiltInAgentMutation(req, {
|
||||
companyId,
|
||||
action: "built_in_agent.reconcile",
|
||||
key,
|
||||
agentId: state.agentId,
|
||||
status: state.status,
|
||||
});
|
||||
res.json(redactBuiltInAgentListState(state));
|
||||
});
|
||||
|
||||
router.post(
|
||||
"/companies/:companyId/built-in-agents/:key/provision",
|
||||
validate(builtInAgentProvisionSchema),
|
||||
@@ -113,16 +164,16 @@ export function builtInAgentRoutes(db: Db) {
|
||||
approvalId: approval.id,
|
||||
});
|
||||
}
|
||||
res.status(approval ? 202 : 200).json({ ...state, approval });
|
||||
res.status(approval ? 202 : 200).json(redactBuiltInAgentListState({ ...state, approval }));
|
||||
},
|
||||
);
|
||||
|
||||
router.post("/companies/:companyId/built-in-agents/:key/reset", async (req, res) => {
|
||||
router.post("/companies/:companyId/built-in-agents/:key/reset", validate(builtInAgentResetSchema), async (req, res) => {
|
||||
const companyId = req.params.companyId as string;
|
||||
const key = req.params.key as string;
|
||||
await assertBuiltInAgentsEnabled();
|
||||
await assertCanProvisionBuiltInAgents(req, companyId);
|
||||
const state = await svc.reset(companyId, key);
|
||||
const state = await svc.reset(companyId, key, req.body);
|
||||
await logBuiltInAgentMutation(req, {
|
||||
companyId,
|
||||
action: "built_in_agent.reset",
|
||||
@@ -130,7 +181,7 @@ export function builtInAgentRoutes(db: Db) {
|
||||
agentId: state.agentId,
|
||||
status: state.status,
|
||||
});
|
||||
res.json(state);
|
||||
res.json(redactBuiltInAgentListState(state));
|
||||
});
|
||||
|
||||
return router;
|
||||
|
||||
File diff suppressed because it is too large.
Load diff
@@ -12,6 +12,8 @@ export {
|
||||
reconcileBuiltInAgentsOnStartup,
|
||||
validateBuiltInAgentDefinitions,
|
||||
type BuiltInAgentDefinition,
|
||||
type BuiltInManagedResourceState,
|
||||
type BuiltInManagedResourceStockStatus,
|
||||
type BuiltInAgentState,
|
||||
type BuiltInAgentStatus,
|
||||
} from "./built-in-agents.js";
|
||||
|
||||
Reference in new issue
Block a user