mirror of
https://github.com/paperclipai/paperclip.git
synced 2026-11-06 23:20:53 +01:00
merge: reconcile published native prerequisite and current master
Preserve canonical startup ownership, scoped evidence, current Storybook IDs, and the Codex form controls while carrying Claude effort and immutable source bindings. Co-Authored-By: Paperclip <noreply@paperclip.ing>
This commit is contained in:
commit
48ffca9438
415 files changed
+22501
-15340
No files matched your search
@@ -1,3 +1,4 @@
|
||||
import type { SlackSetupOptions, SlackManagedProvisionInput, SlackManagedResult } from "@paperclipai/shared";
|
||||
import { api } from "./client";
|
||||
import type {
|
||||
SlackAppConfiguration,
|
||||
@@ -140,7 +141,8 @@ export interface ChatEndpoint {
|
||||
messagingEndpoint?: string | null;
|
||||
command?: string | null;
|
||||
slackApp?: SlackAppConfiguration;
|
||||
slackSetupMethod?: "automatic" | "manual" | "existing";
|
||||
slackSetupMethod?: "managed" | "automatic" | "manual" | "existing";
|
||||
slackManagerError?: string;
|
||||
slackRegistration?: SlackRegistrationState;
|
||||
slackAvatar?: SlackAvatarState;
|
||||
slackAccount?: SlackAccountState;
|
||||
@@ -202,7 +204,7 @@ export const chatEndpointsApi = {
|
||||
api.get<ChatEndpoint>(`/chat-endpoints/${endpointId}`),
|
||||
create: (
|
||||
companyId: string,
|
||||
input: { provider: ChatProvider; assignedAgentId: string; slackApp?: SlackAppConfiguration },
|
||||
input: { provider: ChatProvider; assignedAgentId: string; slackApp?: SlackAppConfiguration; slackSetupMethod?: "managed" | "automatic" | "manual" | "existing" },
|
||||
) => api.post<ChatEndpoint>(`/companies/${companyId}/chat-endpoints`, input),
|
||||
update: (
|
||||
endpointId: string,
|
||||
@@ -216,6 +218,11 @@ export const chatEndpointsApi = {
|
||||
photon?: PhotonChannelConfiguration;
|
||||
},
|
||||
) => api.post<ChatEndpoint>(`/chat-endpoints/${endpointId}/setup`, input),
|
||||
sendSlackTestMessage: (endpointId: string, input: import("@paperclipai/shared").SlackTestMessageInput) =>
|
||||
api.post<import("@paperclipai/shared").SlackTestMessageResult>(`/chat-endpoints/${endpointId}/slack/test-message`, input),
|
||||
slackSetupOptions: (companyId: string) => api.get<SlackSetupOptions>(`/companies/${companyId}/chat-slack/setup-options`),
|
||||
authorizeSlackManager: (endpointId: string) => api.post<import("@paperclipai/shared").SlackManagerAuthorization>(`/chat-endpoints/${endpointId}/slack/managed/authorize`, {}),
|
||||
provisionManagedSlack: (endpointId: string, input: SlackManagedProvisionInput) => api.post<SlackManagedResult>(`/chat-endpoints/${endpointId}/slack/managed/provision`, input),
|
||||
createSlackApp: (endpointId: string, input: SlackRegistrationInput) =>
|
||||
api.post<ChatEndpoint>(`/chat-endpoints/${endpointId}/slack/registration`, input),
|
||||
installSlackApp: (endpointId: string) =>
|
||||
|
||||
@@ -3,7 +3,7 @@ import {
|
||||
createTenantSessionRecoveryCoordinator,
|
||||
tenantSessionRecovery,
|
||||
} from "@/lib/tenant-session-recovery";
|
||||
import { __inflightGetCount, api, detachInflightGet } from "./client";
|
||||
import { api, detachInflightGet } from "./client";
|
||||
import { toolsApi } from "./tools";
|
||||
|
||||
interface Deferred<T> {
|
||||
@@ -93,6 +93,10 @@ describe("tenant-session recovery", () => {
|
||||
status: 401,
|
||||
message: "unauthorized",
|
||||
});
|
||||
|
||||
fetchMock.mockResolvedValue(jsonResponse({ recovered: true }));
|
||||
await expect(api.get("/ordinary-401")).resolves.toEqual({ recovered: true });
|
||||
expect(fetchMock).toHaveBeenCalledTimes(2);
|
||||
});
|
||||
});
|
||||
|
||||
@@ -104,13 +108,10 @@ describe("in-tab GET coalescing", () => {
|
||||
const p1 = api.get("/coalesce-a");
|
||||
const p2 = api.get("/coalesce-a");
|
||||
expect(fetchMock).toHaveBeenCalledTimes(1);
|
||||
expect(__inflightGetCount()).toBe(1);
|
||||
|
||||
d.resolve(jsonResponse({ value: 1 }));
|
||||
expect(await p1).toEqual({ value: 1 });
|
||||
expect(await p2).toEqual({ value: 1 });
|
||||
// Entry cleared once settled.
|
||||
expect(__inflightGetCount()).toBe(0);
|
||||
});
|
||||
|
||||
it("issues a fresh fetch after the previous one settles", async () => {
|
||||
@@ -140,10 +141,8 @@ describe("in-tab GET coalescing", () => {
|
||||
const first = deferred<Response>();
|
||||
fetchMock.mockReturnValueOnce(first.promise);
|
||||
const previousAccount = api.get("/detach-me");
|
||||
expect(__inflightGetCount()).toBe(1);
|
||||
|
||||
detachInflightGet("/detach-me");
|
||||
expect(__inflightGetCount()).toBe(0);
|
||||
|
||||
const second = deferred<Response>();
|
||||
fetchMock.mockReturnValueOnce(second.promise);
|
||||
@@ -155,7 +154,6 @@ describe("in-tab GET coalescing", () => {
|
||||
second.resolve(jsonResponse({ companies: ["current"] }));
|
||||
expect(await previousAccount).toEqual({ companies: ["previous"] });
|
||||
expect(await currentAccount).toEqual({ companies: ["current"] });
|
||||
expect(__inflightGetCount()).toBe(0);
|
||||
});
|
||||
|
||||
it("never coalesces mutations", async () => {
|
||||
@@ -188,7 +186,7 @@ describe("per-caller abort semantics", () => {
|
||||
expect(await p2).toEqual({ value: 2 });
|
||||
});
|
||||
|
||||
it("aborts the shared fetch once every caller has aborted", async () => {
|
||||
it("aborts the shared fetch after all callers cancel and permits a fresh request", async () => {
|
||||
const d = deferred<Response>();
|
||||
let sharedSignal: AbortSignal | undefined;
|
||||
fetchMock.mockImplementation((_url: string, init: RequestInit) => {
|
||||
@@ -206,7 +204,11 @@ describe("per-caller abort semantics", () => {
|
||||
await expect(p1).rejects.toMatchObject({ name: "AbortError" });
|
||||
await expect(p2).rejects.toMatchObject({ name: "AbortError" });
|
||||
expect(sharedSignal?.aborted).toBe(true);
|
||||
expect(__inflightGetCount()).toBe(0);
|
||||
|
||||
fetchMock.mockResolvedValueOnce(jsonResponse({ value: "fresh" }));
|
||||
const fresh = api.get("/abort-b");
|
||||
expect(fetchMock).toHaveBeenCalledTimes(2);
|
||||
expect(await fresh).toEqual({ value: "fresh" });
|
||||
});
|
||||
|
||||
it("rejects immediately if the caller signal is already aborted", async () => {
|
||||
|
||||
@@ -165,11 +165,6 @@ export function detachInflightGet(path: string): void {
|
||||
inflightGets.delete(path);
|
||||
}
|
||||
|
||||
/** Test-only: number of in-flight coalesced GET keys. */
|
||||
export function __inflightGetCount(): number {
|
||||
return inflightGets.size;
|
||||
}
|
||||
|
||||
function isRequestOptions(value: unknown): value is RequestOptions {
|
||||
return typeof value === "object" && value !== null && "signal" in value;
|
||||
}
|
||||
|
||||
Reference in new issue
Block a user