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:
DottaandPaperclip committed 2026-10-12 07:55:09 -05:00
commit f4abdc2cfc
749 files changed
+124181 -68418

No files matched your search

+1 -1
View File
@@ -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;
+7
View File
@@ -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", {});
});
+1
View File
@@ -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) =>
+2 -1
View File
@@ -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;
+18
View File
@@ -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);
+5
View File
@@ -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) =>
+36
View File
@@ -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}`,
);
},
};
+17 -7
View File
@@ -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`),
};
+1
View File
@@ -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";
+17
View File
@@ -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 }),
};