mirror of
https://github.com/paperclipai/paperclip.git
synced 2026-11-06 23:20:53 +01:00
Merge master b189f586d into Codex runner default candidate
Preserve master transport, lifecycle, and release verification controls alongside macOS qualification. Co-Authored-By: Paperclip <noreply@paperclip.ing>
This commit is contained in:
commit
f4abdc2cfc
749 files changed
+124181
-68418
No files matched your search
@@ -16,7 +16,7 @@ export interface RunForIssue {
|
||||
invocationSource: string;
|
||||
responsibleUserId?: string | null;
|
||||
errorCode?: string | null;
|
||||
/** Bounded provider message for a failed model rejection. */
|
||||
/** Bounded model rejection or the fixed credential-sharing denial message. */
|
||||
error?: string | null;
|
||||
usageJson: Record<string, unknown> | null;
|
||||
resultJson: Record<string, unknown> | null;
|
||||
|
||||
@@ -34,3 +34,10 @@ describe("agentsApi.retryFailedRun", () => {
|
||||
.rejects.toThrow("Task execution is paused.");
|
||||
});
|
||||
});
|
||||
|
||||
it("retries lifecycle work through the company-scoped agent route", async () => {
|
||||
const result = { id: "agent-1", lifecycleState: "preparing" };
|
||||
const post = vi.spyOn(api, "post").mockResolvedValue(result);
|
||||
await expect(agentsApi.retryLifecycle("agent-1", "company-1")).resolves.toEqual(result);
|
||||
expect(post).toHaveBeenCalledWith("/agents/agent-1/lifecycle/retry?companyId=company-1", {});
|
||||
});
|
||||
@@ -202,6 +202,7 @@ export const agentsApi = {
|
||||
api.delete<AgentInstructionsBundle>(
|
||||
agentPath(id, companyId, `/instructions-bundle/file?path=${encodeURIComponent(relativePath)}${baseHash ? `&baseHash=${baseHash}` : ""}`),
|
||||
),
|
||||
retryLifecycle: (id: string, companyId?: string) => api.post<Agent>(agentPath(id, companyId, "/lifecycle/retry"), {}),
|
||||
pause: (id: string, companyId?: string) => api.post<Agent>(agentPath(id, companyId, "/pause"), {}),
|
||||
resume: (id: string, companyId?: string) => api.post<Agent>(agentPath(id, companyId, "/resume"), {}),
|
||||
clearError: (id: string, companyId?: string) =>
|
||||
|
||||
@@ -21,7 +21,7 @@ export type {
|
||||
} from "@paperclipai/shared";
|
||||
|
||||
export type ChatProvider =
|
||||
"slack" | "github" | "discord" | "microsoft-teams" | "telegram" | "agentmail" | "imessage-photon";
|
||||
"slack" | "github" | "discord" | "microsoft-teams" | "telegram" | "agentmail" | "imessage-photon" | "speko";
|
||||
export type ChatEndpointStatus =
|
||||
| "draft"
|
||||
| "verifying"
|
||||
@@ -118,6 +118,7 @@ export interface ChatEndpoint {
|
||||
botExternalId?: string | null;
|
||||
photonAllocation?: "dedicated" | "shared";
|
||||
allowDirectMessages?: boolean;
|
||||
requireAtMention?: boolean;
|
||||
allowGroupChats?: boolean;
|
||||
allowUnlinkedPeople: boolean;
|
||||
replyMode?: "subscribed" | "mention_each_reply" | null;
|
||||
|
||||
@@ -43,6 +43,24 @@ afterEach(() => {
|
||||
});
|
||||
|
||||
describe("tenant-session recovery", () => {
|
||||
it("loads anonymous Dot consent despite concurrent protected background probes", async () => {
|
||||
const reload = vi.fn();
|
||||
const requestId = `pcmcp_request_${"a".repeat(43)}`;
|
||||
const recovery = createTenantSessionRecoveryCoordinator(reload, () => `/dot-connect/${requestId}`);
|
||||
vi.spyOn(tenantSessionRecovery, "recoverIfNeeded").mockImplementation(recovery.recoverIfNeeded);
|
||||
fetchMock.mockImplementation((url: string) => Promise.resolve(
|
||||
url.includes("/dot-mcp/requests/")
|
||||
? jsonResponse({ id: requestId, agentConnection: true })
|
||||
: errorResponse({ error: "tenant_session_required" }),
|
||||
));
|
||||
|
||||
const background = api.get("/health");
|
||||
const consent = api.get(`/dot-mcp/requests/${requestId}`);
|
||||
await expect(background).rejects.toMatchObject({ status: 401 });
|
||||
await expect(consent).resolves.toEqual({ id: requestId, agentConnection: true });
|
||||
expect(reload).not.toHaveBeenCalled();
|
||||
});
|
||||
|
||||
it("keeps concurrent failures pending and schedules one top-level reload", async () => {
|
||||
const reload = vi.fn();
|
||||
const recovery = createTenantSessionRecoveryCoordinator(reload);
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
import type {
|
||||
CostSummary,
|
||||
CostDataQualityReport,
|
||||
CostByAgent,
|
||||
CostByUserReport,
|
||||
CostByProviderModel,
|
||||
@@ -25,6 +26,10 @@ function dateParams(from?: string, to?: string): string {
|
||||
}
|
||||
|
||||
export const costsApi = {
|
||||
dataQuality: (companyId: string, from?: string, to?: string) =>
|
||||
api.get<CostDataQualityReport>(`/companies/${companyId}/costs/data-quality${dateParams(from, to)}`),
|
||||
retryAccounting: (companyId: string, runId: string) =>
|
||||
api.post<{ accounted: boolean }>(`/companies/${companyId}/costs/retry-accounting`, { runId }),
|
||||
summary: (companyId: string, from?: string, to?: string) =>
|
||||
api.get<CostSummary>(`/companies/${companyId}/costs/summary${dateParams(from, to)}`),
|
||||
byUser: (companyId: string, from?: string, to?: string) =>
|
||||
|
||||
@@ -0,0 +1,36 @@
|
||||
import type {
|
||||
FastResponseSettingsResponse,
|
||||
FastResponseSettings,
|
||||
FastResponseTestResult,
|
||||
FastResponseHistoryEntry,
|
||||
UpdateFastResponse,
|
||||
} from "@paperclipai/shared";
|
||||
import { api } from "./client";
|
||||
export const fastResponsesApi = {
|
||||
settings: (companyId: string) =>
|
||||
api.get<FastResponseSettingsResponse>(
|
||||
`/companies/${companyId}/fast-response`,
|
||||
),
|
||||
update: (companyId: string, settings: UpdateFastResponse) =>
|
||||
api.put<FastResponseSettings>(
|
||||
`/companies/${companyId}/fast-response`,
|
||||
settings,
|
||||
),
|
||||
models: (companyId: string, connectionId: string) =>
|
||||
api.get<Array<{ id: string; label?: string }>>(
|
||||
`/companies/${companyId}/fast-response/models?connectionId=${encodeURIComponent(connectionId)}`,
|
||||
),
|
||||
test: (companyId: string) =>
|
||||
api.post<FastResponseTestResult>(
|
||||
`/companies/${companyId}/fast-response/test`,
|
||||
{},
|
||||
),
|
||||
history: (companyId: string, from?: string, to?: string) => {
|
||||
const params = new URLSearchParams();
|
||||
if (from) params.set("from", from);
|
||||
if (to) params.set("to", to);
|
||||
return api.get<FastResponseHistoryEntry[]>(
|
||||
`/companies/${companyId}/fast-response/history?${params}`,
|
||||
);
|
||||
},
|
||||
};
|
||||
@@ -2,6 +2,8 @@ import type {
|
||||
ChatEndpointSetupState,
|
||||
GitHubChatConfiguration,
|
||||
GitHubTaskReview,
|
||||
GitHubAppRegistrationInput, GitHubAppWizardState,
|
||||
GitHubRepositoryPage,
|
||||
} from "@paperclipai/shared";
|
||||
import { api } from "./client";
|
||||
import type { ChatEndpoint, ChatEndpointResource } from "./chatEndpoints";
|
||||
@@ -21,6 +23,12 @@ export type GitHubVerification = {
|
||||
};
|
||||
const path = (endpointId: string) => `/chat-endpoints/${endpointId}/github`;
|
||||
export const githubChatApi = {
|
||||
repositories: (id: string, options: { offset?: number; search?: string; limit?: number } = {}) => {
|
||||
const query = new URLSearchParams({ limit: String(options.limit ?? 20), offset: String(options.offset ?? 0), search: options.search ?? "" });
|
||||
return api.get<GitHubRepositoryPage>(`${path(id)}/repositories?${query}`);
|
||||
},
|
||||
toggleAllRepositories: (id: string, enabled: boolean) =>
|
||||
api.put<{ success: true }>(`${path(id)}/repositories/access`, { enabled }),
|
||||
configuration: (id: string) =>
|
||||
api.get<GitHubConfigurationRecord>(`${path(id)}/configuration`),
|
||||
save: (
|
||||
@@ -32,15 +40,16 @@ export const githubChatApi = {
|
||||
expectedRevision,
|
||||
configuration,
|
||||
}),
|
||||
registration: (id: string, name: string) =>
|
||||
api.post<{
|
||||
registrationUrl: string;
|
||||
manifest: Record<string, unknown>;
|
||||
expiresAt: string;
|
||||
}>(`${path(id)}/registration`, { name }),
|
||||
registration: (id: string, input: GitHubAppRegistrationInput) =>
|
||||
api.post<GitHubAppWizardState>(`${path(id)}/registration`, input),
|
||||
restartRegistration: (id: string, registrationId: string) => api.post<GitHubAppWizardState>(`${path(id)}/registration/restart`, { registrationId, appNotCreated: true }),
|
||||
saveDraft: (id: string, input: GitHubAppRegistrationInput) => api.put<{ saved: true }>(`${path(id)}/draft`, input),
|
||||
advance: (id: string) => api.post<GitHubAppWizardState>(`${path(id)}/setup`, {}),
|
||||
startIdentity: (id: string) => api.post<{ authorizationUrl: string }>(`${path(id)}/identity/start`, {}),
|
||||
confirmIdentity: (id: string, githubUserId: string) => api.post<GitHubAppWizardState>(`${path(id)}/identity/confirm`, { githubUserId }),
|
||||
connectApp: (
|
||||
id: string,
|
||||
credentials: { appId: string; privateKey: string; webhookSecret: string },
|
||||
credentials: { appId: string; privateKey: string; webhookSecret: string; clientId?: string; clientSecret?: string },
|
||||
) => api.post<ChatEndpoint>(`${path(id)}/app`, credentials),
|
||||
refreshRepositories: (id: string) =>
|
||||
api.post<ChatEndpointResource[]>(`${path(id)}/repositories/refresh`, {}),
|
||||
@@ -74,5 +83,6 @@ export const githubChatApi = {
|
||||
`${path(id)}/people/lookup`,
|
||||
{ login },
|
||||
),
|
||||
review: (id: string, reviewId: string) => api.get<GitHubTaskReview>(`${path(id)}/reviews/${reviewId}`),
|
||||
reviews: (id: string) => api.get<GitHubTaskReview[]>(`${path(id)}/reviews`),
|
||||
};
|
||||
@@ -284,6 +284,7 @@ export const issuesApi = {
|
||||
id: string,
|
||||
data: {
|
||||
executionReconciliation?: ExecutionReconciliation;
|
||||
workspaceBaseRef?: { requestedRef: string; branch: string };
|
||||
actionId?: string;
|
||||
outcome: "restored" | "false_positive" | "blocked" | "cancelled";
|
||||
sourceIssueStatus: "todo" | "done" | "in_review" | "blocked";
|
||||
|
||||
@@ -0,0 +1,17 @@
|
||||
import type { SubscriptionCostReport, UpdateSubscriptionPrice } from "@paperclipai/shared";
|
||||
import { api } from "./client";
|
||||
|
||||
export const subscriptionsApi = {
|
||||
report(companyId: string, from?: string, to?: string) {
|
||||
const query = new URLSearchParams();
|
||||
if (from) query.set("from", from);
|
||||
if (to) query.set("to", to);
|
||||
if (!from && !to) query.set("period", "all");
|
||||
return api.get<SubscriptionCostReport>(`/companies/${companyId}/costs/subscriptions?${query}`);
|
||||
},
|
||||
refresh: (companyId: string) => api.post<{ status: string }>(`/companies/${companyId}/costs/subscriptions/refresh`, {}),
|
||||
update: (companyId: string, id: string, input: UpdateSubscriptionPrice) =>
|
||||
api.patch(`/companies/${companyId}/costs/subscriptions/${id}`, input),
|
||||
link: (companyId: string, id: string, targetId: string, expectedRevision: number, targetRevision: number) =>
|
||||
api.post(`/companies/${companyId}/costs/subscriptions/${id}/link`, { targetId, expectedRevision, targetRevision }),
|
||||
};
|
||||
Reference in new issue
Block a user