mirror of
https://github.com/alkimake/paperclip.git
synced 2026-06-19 12:10:37 +09:00
Merge remote-tracking branch 'public-gh/master' into paperclip-routines
* public-gh/master: (46 commits) chore(lockfile): refresh pnpm-lock.yaml (#1377) fix: manage codex home per company by default Ensure agent home directories exist before use Handle directory entries in imported zip archives Fix portability import and org chart test blockers Fix PR verify failures after merge fix: address greptile follow-up feedback Address remaining Greptile portability feedback docs: clarify quickstart npx usage Add guarded dev restart handling Fix PAP-576 settings toggles and transcript default Add username log censor setting fix: use standard toggle component for permission controls fix: add missing setPrincipalPermission mock in portability tests fix: use fixed 1280x640 dimensions for org chart export image Adjust default CEO onboarding task copy fix: link Agent Company to agentcompanies.io in export README fix: strip agents and projects sections from COMPANY.md export body fix: default company export page to README.md instead of first file Add default agent instructions bundle ... # Conflicts: # packages/adapters/pi-local/src/server/execute.ts # packages/db/src/migrations/meta/0039_snapshot.json # packages/db/src/migrations/meta/_journal.json # server/src/__tests__/agent-permissions-routes.test.ts # server/src/__tests__/agent-skills-routes.test.ts # server/src/services/company-portability.ts # skills/paperclip/references/company-skills.md # ui/src/api/agents.ts
This commit is contained in:
commit
e3c92a20f1
96 changed files with 15366 additions and 1684 deletions
|
|
@ -20,7 +20,9 @@ const mockAccessService = vi.hoisted(() => ({
|
|||
setPrincipalPermission: vi.fn(),
|
||||
}));
|
||||
|
||||
const mockApprovalService = vi.hoisted(() => ({}));
|
||||
const mockApprovalService = vi.hoisted(() => ({
|
||||
create: vi.fn(),
|
||||
}));
|
||||
const mockBudgetService = vi.hoisted(() => ({}));
|
||||
const mockHeartbeatService = vi.hoisted(() => ({}));
|
||||
const mockIssueApprovalService = vi.hoisted(() => ({
|
||||
|
|
@ -180,13 +182,26 @@ describe("agent skill routes", () => {
|
|||
budgetMonthlyCents: Number(input.budgetMonthlyCents ?? 0),
|
||||
permissions: null,
|
||||
}));
|
||||
mockApprovalService.create = vi.fn(async (_companyId: string, input: Record<string, unknown>) => ({
|
||||
mockApprovalService.create.mockImplementation(async (_companyId: string, input: Record<string, unknown>) => ({
|
||||
id: "approval-1",
|
||||
companyId: "company-1",
|
||||
type: "hire_agent",
|
||||
status: "pending",
|
||||
payload: input.payload ?? {},
|
||||
}));
|
||||
mockAgentInstructionsService.materializeManagedBundle.mockImplementation(
|
||||
async (agent: Record<string, unknown>, files: Record<string, string>) => ({
|
||||
bundle: null,
|
||||
adapterConfig: {
|
||||
...((agent.adapterConfig as Record<string, unknown> | undefined) ?? {}),
|
||||
instructionsBundleMode: "managed",
|
||||
instructionsRootPath: `/tmp/${String(agent.id)}/instructions`,
|
||||
instructionsEntryFile: "AGENTS.md",
|
||||
instructionsFilePath: `/tmp/${String(agent.id)}/instructions/AGENTS.md`,
|
||||
promptTemplate: files["AGENTS.md"] ?? "",
|
||||
},
|
||||
}),
|
||||
);
|
||||
mockLogActivity.mockResolvedValue(undefined);
|
||||
mockAccessService.canUser.mockResolvedValue(true);
|
||||
mockAccessService.hasPermission.mockResolvedValue(true);
|
||||
|
|
@ -297,6 +312,95 @@ describe("agent skill routes", () => {
|
|||
);
|
||||
});
|
||||
|
||||
it("materializes a managed AGENTS.md for directly created local agents", async () => {
|
||||
const res = await request(createApp())
|
||||
.post("/api/companies/company-1/agents")
|
||||
.send({
|
||||
name: "QA Agent",
|
||||
role: "engineer",
|
||||
adapterType: "claude_local",
|
||||
adapterConfig: {
|
||||
promptTemplate: "You are QA.",
|
||||
},
|
||||
});
|
||||
|
||||
expect(res.status, JSON.stringify(res.body)).toBe(201);
|
||||
expect(mockAgentInstructionsService.materializeManagedBundle).toHaveBeenCalledWith(
|
||||
expect.objectContaining({
|
||||
id: "11111111-1111-4111-8111-111111111111",
|
||||
adapterType: "claude_local",
|
||||
}),
|
||||
{ "AGENTS.md": "You are QA." },
|
||||
{ entryFile: "AGENTS.md", replaceExisting: false },
|
||||
);
|
||||
expect(mockAgentService.update).toHaveBeenCalledWith(
|
||||
"11111111-1111-4111-8111-111111111111",
|
||||
expect.objectContaining({
|
||||
adapterConfig: expect.objectContaining({
|
||||
instructionsBundleMode: "managed",
|
||||
instructionsEntryFile: "AGENTS.md",
|
||||
instructionsFilePath: "/tmp/11111111-1111-4111-8111-111111111111/instructions/AGENTS.md",
|
||||
}),
|
||||
}),
|
||||
);
|
||||
expect(mockAgentService.update.mock.calls.at(-1)?.[1]).not.toMatchObject({
|
||||
adapterConfig: expect.objectContaining({
|
||||
promptTemplate: expect.anything(),
|
||||
}),
|
||||
});
|
||||
});
|
||||
|
||||
it("materializes the bundled CEO instruction set for default CEO agents", async () => {
|
||||
const res = await request(createApp())
|
||||
.post("/api/companies/company-1/agents")
|
||||
.send({
|
||||
name: "CEO",
|
||||
role: "ceo",
|
||||
adapterType: "claude_local",
|
||||
adapterConfig: {},
|
||||
});
|
||||
|
||||
expect(res.status, JSON.stringify(res.body)).toBe(201);
|
||||
expect(mockAgentInstructionsService.materializeManagedBundle).toHaveBeenCalledWith(
|
||||
expect.objectContaining({
|
||||
id: "11111111-1111-4111-8111-111111111111",
|
||||
role: "ceo",
|
||||
adapterType: "claude_local",
|
||||
}),
|
||||
expect.objectContaining({
|
||||
"AGENTS.md": expect.stringContaining("You are the CEO."),
|
||||
"HEARTBEAT.md": expect.stringContaining("CEO Heartbeat Checklist"),
|
||||
"SOUL.md": expect.stringContaining("CEO Persona"),
|
||||
"TOOLS.md": expect.stringContaining("# Tools"),
|
||||
}),
|
||||
{ entryFile: "AGENTS.md", replaceExisting: false },
|
||||
);
|
||||
});
|
||||
|
||||
it("materializes the bundled default instruction set for non-CEO agents with no prompt template", async () => {
|
||||
const res = await request(createApp())
|
||||
.post("/api/companies/company-1/agents")
|
||||
.send({
|
||||
name: "Engineer",
|
||||
role: "engineer",
|
||||
adapterType: "claude_local",
|
||||
adapterConfig: {},
|
||||
});
|
||||
|
||||
expect(res.status, JSON.stringify(res.body)).toBe(201);
|
||||
expect(mockAgentInstructionsService.materializeManagedBundle).toHaveBeenCalledWith(
|
||||
expect.objectContaining({
|
||||
id: "11111111-1111-4111-8111-111111111111",
|
||||
role: "engineer",
|
||||
adapterType: "claude_local",
|
||||
}),
|
||||
expect.objectContaining({
|
||||
"AGENTS.md": expect.stringContaining("Keep the work moving until it's done."),
|
||||
}),
|
||||
{ entryFile: "AGENTS.md", replaceExisting: false },
|
||||
);
|
||||
});
|
||||
|
||||
it("includes canonical desired skills in hire approvals", async () => {
|
||||
const db = createDb(true);
|
||||
|
||||
|
|
@ -324,4 +428,35 @@ describe("agent skill routes", () => {
|
|||
}),
|
||||
);
|
||||
});
|
||||
|
||||
it("uses managed AGENTS config in hire approval payloads", async () => {
|
||||
const res = await request(createApp(createDb(true)))
|
||||
.post("/api/companies/company-1/agent-hires")
|
||||
.send({
|
||||
name: "QA Agent",
|
||||
role: "engineer",
|
||||
adapterType: "claude_local",
|
||||
adapterConfig: {
|
||||
promptTemplate: "You are QA.",
|
||||
},
|
||||
});
|
||||
|
||||
expect(res.status, JSON.stringify(res.body)).toBe(201);
|
||||
expect(mockApprovalService.create).toHaveBeenCalledWith(
|
||||
"company-1",
|
||||
expect.objectContaining({
|
||||
payload: expect.objectContaining({
|
||||
adapterConfig: expect.objectContaining({
|
||||
instructionsBundleMode: "managed",
|
||||
instructionsEntryFile: "AGENTS.md",
|
||||
instructionsFilePath: "/tmp/11111111-1111-4111-8111-111111111111/instructions/AGENTS.md",
|
||||
}),
|
||||
}),
|
||||
}),
|
||||
);
|
||||
const approvalInput = mockApprovalService.create.mock.calls.at(-1)?.[1] as
|
||||
| { payload?: { adapterConfig?: Record<string, unknown> } }
|
||||
| undefined;
|
||||
expect(approvalInput?.payload?.adapterConfig?.promptTemplate).toBeUndefined();
|
||||
});
|
||||
});
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue