Resolved conflicts: - ui CompanySettingsSidebar.tsx: keep both Secrets (local) and Cloud upstream (master) nav items - ui CompanySettingsNav.tsx + test: take master's cloud-upstream/members (drops deprecated `access` tab now consolidated into `members`) - server plugin-worker-manager.ts: take master's 15min RPC timeout cap - pnpm-lock.yaml: regenerated via `pnpm install` against merged package.json files Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -0,0 +1,40 @@
|
||||
import type {
|
||||
CloudUpstreamActivationEntityType,
|
||||
CloudUpstreamConnectStartResponse,
|
||||
CloudUpstreamConnection,
|
||||
CloudUpstreamPreview,
|
||||
CloudUpstreamRun,
|
||||
CloudUpstreamsState,
|
||||
} from "@paperclipai/shared";
|
||||
import { api } from "./client";
|
||||
|
||||
export const cloudUpstreamsApi = {
|
||||
list: (companyId: string) =>
|
||||
api.get<CloudUpstreamsState>(`/cloud-upstreams?companyId=${encodeURIComponent(companyId)}`),
|
||||
startConnect: (input: { companyId: string; remoteUrl: string; redirectUri: string }) =>
|
||||
api.post<CloudUpstreamConnectStartResponse>("/cloud-upstreams/connect/start", input),
|
||||
finishConnect: (input: { pendingConnectionId: string; code: string; state: string }) =>
|
||||
api.post<CloudUpstreamConnection>("/cloud-upstreams/connect/finish", input),
|
||||
preview: (connectionId: string, input: { companyId: string }) =>
|
||||
api.post<CloudUpstreamPreview>(`/cloud-upstreams/${encodeURIComponent(connectionId)}/push-runs/preview`, input),
|
||||
createRun: (connectionId: string, input: { companyId: string; retryOfRunId?: string | null }) =>
|
||||
api.post<CloudUpstreamRun>(`/cloud-upstreams/${encodeURIComponent(connectionId)}/push-runs`, input ?? {}),
|
||||
getRun: (connectionId: string, runId: string, companyId: string) =>
|
||||
api.get<CloudUpstreamRun>(
|
||||
`/cloud-upstreams/${encodeURIComponent(connectionId)}/push-runs/${encodeURIComponent(runId)}?companyId=${encodeURIComponent(companyId)}`,
|
||||
),
|
||||
cancelRun: (connectionId: string, runId: string, input: { companyId: string }) =>
|
||||
api.post<CloudUpstreamRun>(
|
||||
`/cloud-upstreams/${encodeURIComponent(connectionId)}/push-runs/${encodeURIComponent(runId)}/cancel`,
|
||||
input,
|
||||
),
|
||||
activateEntities: (
|
||||
connectionId: string,
|
||||
runId: string,
|
||||
input: { companyId: string; entityType: CloudUpstreamActivationEntityType },
|
||||
) =>
|
||||
api.post<CloudUpstreamRun>(
|
||||
`/cloud-upstreams/${encodeURIComponent(connectionId)}/push-runs/${encodeURIComponent(runId)}/activation`,
|
||||
input,
|
||||
),
|
||||
};
|
||||
@@ -0,0 +1,25 @@
|
||||
import type { Company } from "@paperclipai/shared";
|
||||
import { companiesApi } from "./companies";
|
||||
import { ApiError } from "./client";
|
||||
import { queryKeys } from "../lib/queryKeys";
|
||||
|
||||
export type CompanyListResult = { companies: Company[]; unauthorized: boolean };
|
||||
|
||||
// Single source of truth for the `["companies"]` query. Both CompanyProvider and
|
||||
// the invite landing page read this cache entry, so they must agree on the shape —
|
||||
// returning a bare `Company[]` from one and this wrapped object from the other
|
||||
// silently corrupts the shared cache and crashes whichever reads the other's shape.
|
||||
export const companiesListQueryOptions = {
|
||||
queryKey: queryKeys.companies.all,
|
||||
queryFn: async (): Promise<CompanyListResult> => {
|
||||
try {
|
||||
return { companies: await companiesApi.list(), unauthorized: false };
|
||||
} catch (err) {
|
||||
if (err instanceof ApiError && err.status === 401) {
|
||||
return { companies: [], unauthorized: true };
|
||||
}
|
||||
throw err;
|
||||
}
|
||||
},
|
||||
retry: false,
|
||||
} as const;
|
||||
@@ -26,4 +26,5 @@ describe("executionWorkspacesApi.listSummaries", () => {
|
||||
"/companies/company-1/execution-workspaces?projectId=project-1&reuseEligible=true&summary=true",
|
||||
);
|
||||
});
|
||||
|
||||
});
|
||||
|
||||
@@ -38,4 +38,15 @@ export const healthApi = {
|
||||
}
|
||||
return res.json();
|
||||
},
|
||||
requestDevServerRestart: async (): Promise<void> => {
|
||||
const res = await fetch("/api/health/dev-server/restart", {
|
||||
method: "POST",
|
||||
credentials: "include",
|
||||
headers: { Accept: "application/json" },
|
||||
});
|
||||
if (!res.ok) {
|
||||
const payload = await res.json().catch(() => null) as { error?: string } | null;
|
||||
throw new Error(payload?.error ?? `Failed to request restart (${res.status})`);
|
||||
}
|
||||
},
|
||||
};
|
||||
|
||||
@@ -16,5 +16,6 @@ export { heartbeatsApi } from "./heartbeats";
|
||||
export { instanceSettingsApi } from "./instanceSettings";
|
||||
export { sidebarBadgesApi } from "./sidebarBadges";
|
||||
export { sidebarPreferencesApi } from "./sidebarPreferences";
|
||||
export { resourceMembershipsApi } from "./resourceMemberships";
|
||||
export { inboxDismissalsApi } from "./inboxDismissals";
|
||||
export { companySkillsApi } from "./companySkills";
|
||||
|
||||
@@ -51,6 +51,18 @@ describe("issuesApi.list", () => {
|
||||
);
|
||||
});
|
||||
|
||||
it("passes issue list sort options through to the company issues endpoint", async () => {
|
||||
await issuesApi.list("company-1", {
|
||||
limit: 500,
|
||||
sortField: "updated",
|
||||
sortDir: "desc",
|
||||
});
|
||||
|
||||
expect(mockApi.get).toHaveBeenCalledWith(
|
||||
"/companies/company-1/issues?limit=500&sortField=updated&sortDir=desc",
|
||||
);
|
||||
});
|
||||
|
||||
it("posts recovery action resolution to the source issue endpoint", async () => {
|
||||
await issuesApi.resolveRecoveryAction("issue-1", {
|
||||
actionId: "00000000-0000-0000-0000-0000000000aa",
|
||||
|
||||
@@ -60,6 +60,8 @@ export const issuesApi = {
|
||||
q?: string;
|
||||
limit?: number;
|
||||
offset?: number;
|
||||
sortField?: "updated";
|
||||
sortDir?: "asc" | "desc";
|
||||
},
|
||||
) => {
|
||||
const params = new URLSearchParams();
|
||||
@@ -86,6 +88,8 @@ export const issuesApi = {
|
||||
if (filters?.q) params.set("q", filters.q);
|
||||
if (filters?.limit) params.set("limit", String(filters.limit));
|
||||
if (filters?.offset !== undefined) params.set("offset", String(filters.offset));
|
||||
if (filters?.sortField) params.set("sortField", filters.sortField);
|
||||
if (filters?.sortDir) params.set("sortDir", filters.sortDir);
|
||||
const qs = params.toString();
|
||||
return api.get<Issue[]>(`/companies/${companyId}/issues${qs ? `?${qs}` : ""}`);
|
||||
},
|
||||
@@ -131,7 +135,7 @@ export const issuesApi = {
|
||||
data: {
|
||||
actionId?: string;
|
||||
outcome: "restored" | "false_positive" | "blocked" | "cancelled";
|
||||
sourceIssueStatus: "done" | "in_review" | "blocked";
|
||||
sourceIssueStatus: "todo" | "done" | "in_review" | "blocked";
|
||||
resolutionNote?: string | null;
|
||||
},
|
||||
) => api.post<ResolveRecoveryActionResponse>(`/issues/${id}/recovery-actions/resolve`, data),
|
||||
@@ -259,6 +263,10 @@ export const issuesApi = {
|
||||
getDocument: (id: string, key: string) => api.get<IssueDocument>(`/issues/${id}/documents/${encodeURIComponent(key)}`),
|
||||
upsertDocument: (id: string, key: string, data: UpsertIssueDocument) =>
|
||||
api.put<IssueDocument>(`/issues/${id}/documents/${encodeURIComponent(key)}`, data),
|
||||
lockDocument: (id: string, key: string) =>
|
||||
api.post<IssueDocument>(`/issues/${id}/documents/${encodeURIComponent(key)}/lock`, {}),
|
||||
unlockDocument: (id: string, key: string) =>
|
||||
api.post<IssueDocument>(`/issues/${id}/documents/${encodeURIComponent(key)}/unlock`, {}),
|
||||
listDocumentRevisions: (id: string, key: string) =>
|
||||
api.get<DocumentRevision[]>(`/issues/${id}/documents/${encodeURIComponent(key)}/revisions`),
|
||||
restoreDocumentRevision: (id: string, key: string, revisionId: string) =>
|
||||
|
||||
@@ -138,7 +138,7 @@ export interface AvailablePluginExample {
|
||||
displayName: string;
|
||||
description: string;
|
||||
localPath: string;
|
||||
tag: "example";
|
||||
tag: "example" | "first-party";
|
||||
}
|
||||
|
||||
export interface PluginLocalFolderProblem {
|
||||
|
||||
@@ -0,0 +1,21 @@
|
||||
import type {
|
||||
ResourceMemberships,
|
||||
ResourceMembershipUpdateResult,
|
||||
UpdateResourceMembership,
|
||||
} from "@paperclipai/shared";
|
||||
import { api } from "./client";
|
||||
|
||||
export const resourceMembershipsApi = {
|
||||
listMine: (companyId: string) =>
|
||||
api.get<ResourceMemberships>(`/companies/${companyId}/resource-memberships/me`),
|
||||
updateProject: (companyId: string, projectId: string, data: UpdateResourceMembership) =>
|
||||
api.put<ResourceMembershipUpdateResult>(
|
||||
`/companies/${companyId}/resource-memberships/me/projects/${projectId}`,
|
||||
data,
|
||||
),
|
||||
updateAgent: (companyId: string, agentId: string, data: UpdateResourceMembership) =>
|
||||
api.put<ResourceMembershipUpdateResult>(
|
||||
`/companies/${companyId}/resource-memberships/me/agents/${agentId}`,
|
||||
data,
|
||||
),
|
||||
};
|
||||
@@ -2,6 +2,7 @@ import type {
|
||||
CompanySecret,
|
||||
CompanySecretUsageBinding,
|
||||
CompanySecretProviderConfig,
|
||||
SecretProviderConfigDiscoveryPreviewResult,
|
||||
RemoteSecretImportPreviewResult,
|
||||
RemoteSecretImportResult,
|
||||
SecretAccessEvent,
|
||||
@@ -95,6 +96,14 @@ export interface RemoteImportInput {
|
||||
secrets: RemoteImportSelectionInput[];
|
||||
}
|
||||
|
||||
export interface SecretProviderConfigDiscoveryPreviewInput {
|
||||
provider: SecretProvider;
|
||||
config?: Record<string, unknown>;
|
||||
query?: string | null;
|
||||
nextToken?: string | null;
|
||||
pageSize?: number;
|
||||
}
|
||||
|
||||
export const secretsApi = {
|
||||
list: (companyId: string) => api.get<CompanySecret[]>(`/companies/${companyId}/secrets`),
|
||||
providers: (companyId: string) =>
|
||||
@@ -103,11 +112,21 @@ export const secretsApi = {
|
||||
api.get<SecretProviderHealthResponse>(`/companies/${companyId}/secret-providers/health`),
|
||||
providerConfigs: (companyId: string) =>
|
||||
api.get<CompanySecretProviderConfig[]>(`/companies/${companyId}/secret-provider-configs`),
|
||||
providerConfigDiscoveryPreview: (
|
||||
companyId: string,
|
||||
data: SecretProviderConfigDiscoveryPreviewInput,
|
||||
) =>
|
||||
api.post<SecretProviderConfigDiscoveryPreviewResult>(
|
||||
`/companies/${companyId}/secret-provider-configs/discovery/preview`,
|
||||
data,
|
||||
),
|
||||
createProviderConfig: (companyId: string, data: CreateSecretProviderConfigInput) =>
|
||||
api.post<CompanySecretProviderConfig>(`/companies/${companyId}/secret-provider-configs`, data),
|
||||
updateProviderConfig: (id: string, data: UpdateSecretProviderConfigInput) =>
|
||||
api.patch<CompanySecretProviderConfig>(`/secret-provider-configs/${id}`, data),
|
||||
disableProviderConfig: (id: string) =>
|
||||
api.patch<CompanySecretProviderConfig>(`/secret-provider-configs/${id}`, { status: "disabled" }),
|
||||
removeProviderConfig: (id: string) =>
|
||||
api.delete<CompanySecretProviderConfig>(`/secret-provider-configs/${id}`),
|
||||
setDefaultProviderConfig: (id: string) =>
|
||||
api.post<CompanySecretProviderConfig>(`/secret-provider-configs/${id}/default`, {}),
|
||||
|
||||
Reference in New Issue
Block a user