Merge origin/master into provider routing connections

Co-Authored-By: Paperclip <noreply@paperclip.ing>

* origin/master:
  fix(ui): let a selected saved subscription be used without a tile click (#14996)
  fix: report pending execution phase at Stop timeout (#14990)
  fix: retain resolved ACP execution timeout metadata (#14986)
  fix(agents): reduce hiring templates to role descriptions (#14985)
This commit is contained in:
DottaandPaperclip committed 2026-10-02 21:56:32 -05:00
commit cba9868517
60 files changed
+2587 -1065

No files matched your search

@@ -1,4 +1,5 @@
import express from "express";
import { readFile } from "node:fs/promises";
import request from "supertest";
import { beforeEach, describe, expect, it, vi } from "vitest";
@@ -113,7 +114,9 @@ vi.mock("../services/instance-settings.js", async (importOriginal) => ({
vi.mock("../adapters/index.js", () => ({
findServerAdapter: vi.fn(() => mockAdapter),
findActiveServerAdapter: vi.fn(() => mockAdapter),
findActiveServerAdapter: vi.fn((adapterType: string) => adapterType === "paperclip_runner"
? { ...mockAdapter, supportsInstructionsBundle: true }
: mockAdapter),
listAdapterModels: vi.fn(),
detectAdapterModel: vi.fn(),
}));
@@ -156,7 +159,9 @@ function registerModuleMocks() {
vi.doMock("../adapters/index.js", () => ({
findServerAdapter: vi.fn(() => mockAdapter),
findActiveServerAdapter: vi.fn(() => mockAdapter),
findActiveServerAdapter: vi.fn((adapterType: string) => adapterType === "paperclip_runner"
? { ...mockAdapter, supportsInstructionsBundle: true }
: mockAdapter),
listAdapterModels: vi.fn(),
detectAdapterModel: vi.fn(),
}));
@@ -1117,34 +1122,68 @@ describe.sequential("agent skill routes", () => {
expect(mockAgentInstructionsService.materializeManagedBundle).not.toHaveBeenCalled();
});
it("materializes the bundled CEO instruction set for default CEO agents", async () => {
it.each(["claude_local", "paperclip_runner"].flatMap((adapterType) =>
["agents", "agent-hires"].map((route) => ({ adapterType, route })),
))("materializes only the CEO entry file for $adapterType via $route", async ({ adapterType, route }) => {
mockInstanceSettingsService.getExperimental.mockResolvedValue({
enableBetaSkills: false,
enableNativeRunner: true,
});
const res = await requestApp(await createApp(), (baseUrl) => request(baseUrl)
.post("/api/companies/company-1/agents")
.post(`/api/companies/company-1/${route}`)
.send({
name: "CEO",
role: "ceo",
adapterType: "claude_local",
adapterConfig: {},
adapterType,
adapterConfig: adapterType === "paperclip_runner" ? { provider: "codex" } : {},
}));
expect([200, 201], JSON.stringify(res.body)).toContain(res.status);
const createdAgentId = expectResponseId(res.body.id);
expect(mockAgentInstructionsService.materializeManagedBundle).toHaveBeenCalledWith(
expect.objectContaining({
id: createdAgentId,
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"),
}),
expect(res.status, JSON.stringify(res.body)).toBe(201);
const createdAgentId = expectResponseId(route === "agents" ? res.body.id : res.body.agent.id);
const entry = await readFile(new URL("../onboarding-assets/ceo/AGENTS.md", import.meta.url), "utf8");
expect(mockAgentInstructionsService.materializeManagedBundle).toHaveBeenCalledExactlyOnceWith(
expect.objectContaining({ id: createdAgentId, role: "ceo", adapterType }),
{ "AGENTS.md": entry },
{ entryFile: "AGENTS.md", replaceExisting: false },
);
});
it.each(["claude_local", "paperclip_runner"].flatMap((adapterType) =>
["agents", "agent-hires"].map((route) => ({ adapterType, route })),
))("preserves an explicit CEO instruction bundle for $adapterType via $route", async ({ adapterType, route }) => {
mockInstanceSettingsService.getExperimental.mockResolvedValue({
enableBetaSkills: false,
enableNativeRunner: true,
});
const customFiles = {
"AGENTS.md": "You lead the bespoke research company. Read NOTES.md for its current focus.",
"NOTES.md": "Focus on the board's research questions.",
};
const res = await requestApp(await createApp(), (baseUrl) => request(baseUrl)
.post(`/api/companies/company-1/${route}`)
.send({
name: "Research Lead",
role: "ceo",
adapterType,
adapterConfig: adapterType === "paperclip_runner" ? { provider: "codex" } : {},
instructionsBundle: { entryFile: "AGENTS.md", files: customFiles },
}));
expect(res.status, JSON.stringify(res.body)).toBe(201);
const createdAgentId = expectResponseId(route === "agents" ? res.body.id : res.body.agent.id);
expect(mockAgentInstructionsService.materializeManagedBundle).toHaveBeenCalledExactlyOnceWith(
expect.objectContaining({ id: createdAgentId, role: "ceo", adapterType }),
customFiles,
{ entryFile: "AGENTS.md", replaceExisting: false },
);
const returnedAgent = route === "agents" ? res.body : res.body.agent;
expect(returnedAgent.adapterConfig).toMatchObject({
instructionsEntryFile: "AGENTS.md",
instructionsFilePath: `/tmp/${createdAgentId}/instructions/AGENTS.md`,
});
expect(returnedAgent.adapterConfig).not.toHaveProperty("promptTemplate");
});
it("materializes the bundled default instruction set for non-CEO agents with no prompt template", async () => {
const res = await requestApp(await createApp(), (baseUrl) => request(baseUrl)
.post("/api/companies/company-1/agents")
@@ -35,7 +35,11 @@ afterEach(async () => {
});
describe.skipIf(!sentryPackage)("run failure context with the real Sentry SDK", () => {
it("keeps unconfirmed Stop context off unrelated events and drops arbitrary error fields", async () => {
it.each([
{ phase: "workspace_restore", phaseElapsedMs: 60_123, expectedPhase: "workspace_restore", expectedElapsedMs: 60_123 },
{ phase: "private-provider-phase", phaseElapsedMs: 100, expectedPhase: "unknown", expectedElapsedMs: null },
{ phase: "workspace_restore", phaseElapsedMs: Infinity, expectedPhase: "workspace_restore", expectedElapsedMs: null },
])("keeps bounded unconfirmed Stop context off unrelated events ($expectedPhase)", async ({ phase, phaseElapsedMs, expectedPhase, expectedElapsedMs }) => {
const Sentry = sentryPackage!;
const events: Array<Record<string, unknown>> = [];
vi.stubEnv("SENTRY_DSN_BACKEND", "https://public@example.invalid/1");
@@ -55,6 +59,7 @@ describe.skipIf(!sentryPackage)("run failure context with the real Sentry SDK",
const timeout = Object.assign(new AdapterStopTimeoutError(60_000, {
runId: "11111111-1111-4111-8111-111111111111", adapterType: "cursor",
runtimeMode: "legacy", abortRequested: true,
phase, phaseElapsedMs,
}), {
cause: new Error("private-provider-cause"),
providerResponse: { headers: "private-provider-headers", body: "private-provider-body" },
@@ -72,6 +77,7 @@ describe.skipIf(!sentryPackage)("run failure context with the real Sentry SDK",
contexts: { adapter_stop: {
runId: "11111111-1111-4111-8111-111111111111", adapterType: "cursor",
runtimeMode: "legacy", abortRequested: true, timeoutMs: 60_000,
phase: expectedPhase, phaseElapsedMs: expectedElapsedMs,
} },
});
expect(JSON.stringify(events)).not.toContain("private-provider-");
+6 -2
View File
@@ -136,6 +136,7 @@ describe("captureException", () => {
const error = new AdapterStopTimeoutError(60_000, {
runId: "11111111-1111-4111-8111-111111111111",
adapterType: "cursor", runtimeMode: "legacy", abortRequested: true,
phase: "instruction_collection", phaseElapsedMs: 60_321,
});
Object.assign(error, { providerResponse: "private fixture payload" });
captureException(error);
@@ -144,7 +145,8 @@ describe("captureException", () => {
expect(sdk.captureException.mock.calls[0]).toEqual([
expect.objectContaining({ message: error.message, stack: error.stack }),
{ tags: { error_code: "adapter_stop_unconfirmed" }, fingerprint: ["{{ default }}"], contexts: {
adapter_stop: { runId: "11111111-1111-4111-8111-111111111111", adapterType: "cursor", runtimeMode: "legacy", abortRequested: true, timeoutMs: 60_000 },
adapter_stop: { runId: "11111111-1111-4111-8111-111111111111", adapterType: "cursor", runtimeMode: "legacy", abortRequested: true, timeoutMs: 60_000,
phase: "instruction_collection", phaseElapsedMs: 60_321 },
} },
]);
expect(JSON.stringify(sdk.captureException.mock.calls[0])).not.toContain("private fixture payload");
@@ -159,10 +161,12 @@ describe("captureException", () => {
await sentryReady;
captureException(new AdapterStopTimeoutError(NaN, {
runId: "private fixture payload", adapterType: "private fixture payload", runtimeMode: "private fixture payload",
phase: "private fixture payload", phaseElapsedMs: Infinity,
}));
expect(JSON.stringify(sdk.captureException.mock.calls)).not.toContain("private fixture payload");
expect(sdk.captureException.mock.calls[0]).toEqual([expect.any(Error), expect.objectContaining({ contexts: {
adapter_stop: { runId: null, adapterType: "unknown", runtimeMode: "unknown", abortRequested: null, timeoutMs: null },
adapter_stop: { runId: null, adapterType: "unknown", runtimeMode: "unknown", abortRequested: null, timeoutMs: null,
phase: "unknown", phaseElapsedMs: null },
} })]);
});
@@ -1,4 +1,6 @@
import { beforeEach, describe, expect, it, vi } from "vitest";
import { readFile } from "node:fs/promises";
import { parseFrontmatterMarkdown } from "@paperclipai/shared/frontmatter";
import type { CatalogTeam } from "@paperclipai/shared";
const mockAgentService = vi.hoisted(() => ({
@@ -34,12 +36,13 @@ vi.mock("../services/activity-log.js", () => ({
const {
collectCatalogTeamSkillPreparations,
listCatalogTeams,
readCatalogTeamProvenance,
teamsCatalogService,
} = await import("../services/teams-catalog.js");
const CORE_EXEC_TEAM_ID = "paperclipai:bundled:company-defaults:core-exec-team";
const CORE_EXEC_TEAM_HASH = "sha256:0f20e9d56124c1dc90a1e4b128fabd863538bcc935117220f719d9620f7c89f1";
const CORE_EXEC_TEAM_HASH = (await listCatalogTeams()).find((team) => team.id === CORE_EXEC_TEAM_ID)!.contentHash;
function agentWithCatalogTeam(originHash: string | null, extra: Record<string, unknown> = {}) {
return {
@@ -113,6 +116,39 @@ describe("teamsCatalogService", () => {
expect(prepared.source.files[".paperclip.yaml"]).toEqual(expect.stringContaining("reportsToExistingAgentSlug: \"engineering-manager\""));
});
it.each(["core-exec-team", "product-engineering", "product-design", "content-machine"])(
"preserves %s role files and canonical skill references through catalog import",
async (teamRef) => {
const svc = teamsCatalogService({} as any);
const prepared = await svc.prepareCatalogTeamSource("company-1", teamRef);
expect(prepared.errors).toEqual([]);
for (const slug of prepared.team.agentSlugs) {
const filePath = `agents/${slug}/AGENTS.md`;
const original = parseFrontmatterMarkdown(await readFile(
new URL(`../../../packages/teams-catalog/${prepared.team.path}/${filePath}`, import.meta.url),
"utf8",
));
const imported = parseFrontmatterMarkdown(prepared.source.files[filePath] as string);
const requiredSkills = (original.frontmatter.skills as string[]).map((ref) => {
const requirement = prepared.team.requiredSkills.find((skill) => skill.ref === ref && skill.agentSlugs.includes(slug));
expect(requirement, `${filePath}: ${ref}`).toMatchObject({ resolved: true });
return requirement?.type === "catalog" ? requirement.catalogSkillKey : ref;
});
expect(imported.frontmatter, filePath).toEqual({ ...original.frontmatter, skills: requiredSkills });
expect(imported.body, filePath).toBe(original.body);
}
await svc.installCatalogTeam("company-1", teamRef);
const [importInput] = mockCompanyPortabilityService.importBundle.mock.calls.at(-1)!;
expect(importInput.source.files).toEqual(prepared.source.files);
const requiredCatalogIds = prepared.skillPreparations
.filter((skill) => skill.action === "catalog_install_required")
.map((skill) => skill.catalogSkillId).sort();
expect(mockCompanySkillService.installFromCatalog.mock.calls.map((call) => call[1].catalogSkillId).sort())
.toEqual(requiredCatalogIds);
},
);
it("resolves target-manager slug against same-company agents before rendering reparent metadata", async () => {
mockAgentService.list.mockResolvedValue([
{ id: "manager-1", companyId: "company-1", name: "CEO" },