mirror of
https://github.com/paperclipai/paperclip.git
synced 2026-10-11 14:10:50 +02:00
fix(dot): honor standalone rollout in imports and dev startup
Co-Authored-By: Paperclip <noreply@paperclip.ing>
This commit is contained in:
1 parent
c3a53dfca0
commit
5a1bdea4ca
5 files changed
+58
-12
No files matched your search
@@ -65,6 +65,11 @@ Database migration `0312_broken_sentinels.sql` adds only Dot tables and extensio
|
||||
after the merged gateway migrations. It is safe to reapply. The earlier
|
||||
prototype migration number is retired; published master migrations are intact.
|
||||
|
||||
Company package imports use the same independent Dot option. An unpaired imported
|
||||
Dot can be saved after the external billing acknowledgement; pair it before
|
||||
assigning work. The dev launcher also prepares the shared Runner binary when
|
||||
Dot is enabled, even when the general Runner option is off.
|
||||
|
||||
## Troubleshoot plugin OAuth discovery
|
||||
|
||||
Use the exact server URL `<origin>/mcp/runner` with OAuth authentication. The
|
||||
|
||||
@@ -103,7 +103,7 @@ const agentInstructionsSvc = {
|
||||
};
|
||||
|
||||
const instanceSettingsSvc = {
|
||||
getExperimental: vi.fn(async () => ({ enableNativeRunner: false })),
|
||||
getExperimental: vi.fn(async (): Promise<{ enableNativeRunner: boolean; enableOpenAiDot?: boolean }> => ({ enableNativeRunner: false })),
|
||||
};
|
||||
|
||||
const managedAgentProfileSvc = {
|
||||
@@ -6030,6 +6030,33 @@ describe("company portability", () => {
|
||||
expect(preview.plan.issuePlans).toHaveLength(0);
|
||||
});
|
||||
|
||||
it("imports an unpaired Dot using its own option without enabling other Runner providers", async () => {
|
||||
const portability = companyPortabilityService({} as any);
|
||||
const exported = await portability.exportBundle("company-1", { include: { company: false, agents: true, projects: false, issues: false } });
|
||||
agentSvc.list.mockResolvedValue([]);
|
||||
agentSvc.create.mockImplementation(async (_companyId: string, input: Record<string, unknown>) => ({ id: "agent-created", ...input }));
|
||||
const input = {
|
||||
source: { type: "inline" as const, rootPath: exported.rootPath, files: exported.files },
|
||||
include: { company: false, agents: true, projects: false, issues: false },
|
||||
target: { mode: "existing_company" as const, companyId: "company-1" },
|
||||
agents: "all" as const, collisionStrategy: "rename" as const,
|
||||
adapterOverrides: { claudecoder: { adapterType: "paperclip_runner", adapterConfig: { provider: "openai_dot", allowUnmeteredProvider: true, lifecycleMode: "per_turn" } } },
|
||||
};
|
||||
instanceSettingsSvc.getExperimental.mockResolvedValue({ enableNativeRunner: true, enableOpenAiDot: false });
|
||||
await expect(portability.importBundle(input, "user-1")).rejects.toMatchObject({ status: 422, details: { code: "paperclip_runner_dot_disabled" } });
|
||||
expect(agentSvc.create).not.toHaveBeenCalled();
|
||||
instanceSettingsSvc.getExperimental.mockResolvedValue({ enableNativeRunner: false, enableOpenAiDot: true });
|
||||
await expect(portability.importBundle({ ...input, adapterOverrides: { claudecoder: { adapterType: "paperclip_runner", adapterConfig: { provider: "codex" } } } }, "user-1"))
|
||||
.rejects.toMatchObject({ status: 422, details: { code: "paperclip_runner_rollout_disabled" } });
|
||||
expect(agentSvc.create).not.toHaveBeenCalled();
|
||||
await portability.importBundle(input, "user-1");
|
||||
expect(agentSvc.create).toHaveBeenCalledWith("company-1", expect.objectContaining({ adapterType: "paperclip_runner", adapterConfig: expect.objectContaining({ provider: "openai_dot", allowUnmeteredProvider: true }) }));
|
||||
const createdConfig = agentSvc.create.mock.calls[0]![1].adapterConfig;
|
||||
expect(createdConfig.dotBindingId).toBeUndefined();
|
||||
const { resolvePaperclipRunnerProviderProfile } = await import("../services/native-runtime/provider-profile.js");
|
||||
expect(() => resolvePaperclipRunnerProviderProfile(createdConfig)).toThrow(expect.objectContaining({ code: "paperclip_runner_dot_config_invalid" }));
|
||||
});
|
||||
|
||||
it("rejects runner imports while disabled and accepts the same selection when enabled", async () => {
|
||||
const portability = companyPortabilityService({} as any);
|
||||
const exported = await portability.exportBundle("company-1", {
|
||||
|
||||
@@ -49,8 +49,9 @@ async function main(): Promise<void> {
|
||||
console.log(
|
||||
JSON.stringify({
|
||||
nativeRunnerRequired:
|
||||
experimental.enableNativeRunner === true || persistedNativeRun,
|
||||
experimental.enableNativeRunner === true || experimental.enableOpenAiDot === true || persistedNativeRun,
|
||||
rolloutEnabled: experimental.enableNativeRunner === true,
|
||||
dotRolloutEnabled: experimental.enableOpenAiDot === true,
|
||||
persistedNativeRun,
|
||||
persistedActiveNativeRun,
|
||||
persistedRetryableFailedNativeRun,
|
||||
|
||||
@@ -109,6 +109,7 @@ import type {
|
||||
import {
|
||||
PaperclipRunnerProviderProfileError,
|
||||
resolvePaperclipRunnerProviderProfile,
|
||||
validatePaperclipRunnerDotConfig,
|
||||
} from "./native-runtime/provider-profile.js";
|
||||
import { managedAgentProfileService } from "./managed-agent-profiles.js";
|
||||
import { remoteAgentProfileService } from "./remote-agent-profiles.js";
|
||||
@@ -3600,6 +3601,10 @@ export function companyPortabilityService(db: Db, storage?: StorageService) {
|
||||
if (adapterType === "paperclip_runner") {
|
||||
let profile;
|
||||
try {
|
||||
if (adapterConfig.provider === "openai_dot") {
|
||||
validatePaperclipRunnerDotConfig(adapterConfig, false);
|
||||
return;
|
||||
}
|
||||
profile = resolvePaperclipRunnerProviderProfile(adapterConfig);
|
||||
} catch (error) {
|
||||
if (error instanceof PaperclipRunnerProviderProfileError) {
|
||||
@@ -5248,19 +5253,27 @@ export function companyPortabilityService(db: Db, storage?: StorageService) {
|
||||
.filter((entry) => entry.action !== "skip")
|
||||
.map((entry) => entry.slug),
|
||||
);
|
||||
const selectsNativeRunner = sourceManifest.agents.some((agent) =>
|
||||
const runnerSelections = sourceManifest.agents.filter((agent) =>
|
||||
importedAgentSlugs.has(agent.slug)
|
||||
&& (input.adapterOverrides?.[agent.slug]?.adapterType ?? agent.adapterType)
|
||||
=== "paperclip_runner",
|
||||
);
|
||||
if (
|
||||
selectsNativeRunner
|
||||
&& (await instanceSettingsService(db).getExperimental()).enableNativeRunner !== true
|
||||
) {
|
||||
throw unprocessable(
|
||||
"Paperclip Runner is experimental and disabled on this instance.",
|
||||
{ code: "paperclip_runner_rollout_disabled" },
|
||||
);
|
||||
if (runnerSelections.length > 0) {
|
||||
const experimental = await instanceSettingsService(db).getExperimental();
|
||||
for (const agent of runnerSelections) {
|
||||
const config = input.adapterOverrides?.[agent.slug]?.adapterConfig ?? agent.adapterConfig;
|
||||
if (config.provider === "openai_dot") {
|
||||
if (experimental.enableOpenAiDot !== true) throw unprocessable(
|
||||
"OpenAI Dot is experimental and disabled on this instance.",
|
||||
{ code: "paperclip_runner_dot_disabled" },
|
||||
);
|
||||
} else if (experimental.enableNativeRunner !== true) {
|
||||
throw unprocessable(
|
||||
"Paperclip Runner is experimental and disabled on this instance.",
|
||||
{ code: "paperclip_runner_rollout_disabled" },
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -95,7 +95,7 @@ function createBroker(db: Db) {
|
||||
return {
|
||||
enabled,
|
||||
async createPairing(input: { companyId: string; agentId: string; operatorId: string; dotUrl?: string }) {
|
||||
if (!await enabled()) throw fail("Enable OpenAI Dot, Paperclip Runner, and Assistant connections (MCP) in experimental settings.");
|
||||
if (!await enabled()) throw fail("Enable OpenAI Dot and Assistant connections (MCP) in experimental settings.");
|
||||
if (input.dotUrl && !/^https:\/\/chatgpt\.com\/dots\/[A-Za-z0-9-]+$/.test(input.dotUrl)) throw fail("Use the Dot's ChatGPT URL.");
|
||||
const code = randomBytes(24).toString("base64url");
|
||||
const binding = await db.transaction(async tx => {
|
||||
|
||||
Reference in new issue
Block a user