Author normal Slack setup with private human slash command and real wizard activation

This commit is contained in:
Dotta committed 2026-10-27 02:39:51 -05:00
1 parent eeccfd9bde
commit b21eeaa239
17 files changed
+2289 -87

No files matched your search

@@ -214,3 +214,25 @@ persisted human comments comprise the two original-query turns and the product's
two human-attributed proposal-resolution notices. Approval proves the exact
saved reference and consumer, with no Morgan task run or task-delivery event;
it never proves that a later task already consumed or validated the value.
The `connect-slack` and `channel-prerequisites` journeys additionally select
`slackBrowserSessionEnv`, a private reference containing Playwright storage-state
JSON for the designated disposable **human** Slack account. It is separate from
the sender API token. Cookies/storage may belong only to official HTTPS Slack
hosts. The finite isolated browser verifies the selected workspace/channel URL
and copies the current person's actual member ID from their own profile before
submitting exactly the saved app's slash command with `connect`. Missing or
changed controls, a different member, an existing draft, expired sign-in or an
unavailable designated session fail that prerequisite; no API-created slash event
substitutes for it. The provider-signed callback and current processed
`lastConnectAt` are independently required before the real ownership button,
Continue to message test and Done controls. The final case also requires a new
actual two-way native conversation; credential saving or endpoint activation
alone does not pass. These callbacks are authored and calibrated offline, not
live-qualified Slack/browser evidence.
The private session JSON, each cookie/storage value, bot token and signing secret
remain in the selected private envelope, redactor, process memory and private
0600 child handoff. They never enter model input, public case state, argv,
subprocess environment, trace, video, screenshots or closed receipts. The child
handoff is removed unconditionally. Supply no unrelated browser sessions.
@@ -26,8 +26,8 @@ export const externalRequirements: Record<string, { provider: ExternalProvider;
"connection-setup.notion-expired": { provider: "notion", fields: ["workspaceId", "recentPageId", "expiredAuthorizationEnv", "oauthBrowserSession"] },
"connection-setup.return-from-login": { provider: "notion", fields: ["workspaceId", "recentPageId", "oauthBrowserSession", "registeredCallbackOrigin"] },
"connection-setup.save-and-resume": { provider: "notion", fields: ["workspaceId", "recentPageId", "oauthBrowserSession"] },
"connection-setup.channel-prerequisites": { provider: "slack", fields: ["workspaceId", "channelId", "senderUserId", "registeredWebhookOrigin"] },
"chat-channels.connect-slack": { provider: "slack", fields: ["workspaceId", "channelId", "senderUserId", "registeredWebhookOrigin"] },
"connection-setup.channel-prerequisites": { provider: "slack", fields: ["workspaceId", "channelId", "senderUserId", "botUserId", "appId", "registeredWebhookOrigin", "webhookRelayPort", "botTokenEnv", "senderTokenEnv", "signingSecretEnv", "configurationTokenEnv", "slackBrowserSessionEnv"] },
"chat-channels.connect-slack": { provider: "slack", fields: ["workspaceId", "channelId", "senderUserId", "botUserId", "appId", "registeredWebhookOrigin", "webhookRelayPort", "botTokenEnv", "senderTokenEnv", "signingSecretEnv", "configurationTokenEnv", "slackBrowserSessionEnv"] },
"chat-channels.link-sender": { provider: "slack", fields: ["workspaceId", "channelId", "senderUserId", "botUserId", "appId", "registeredWebhookOrigin", "webhookRelayPort", "botTokenEnv", "senderTokenEnv", "signingSecretEnv", "configurationTokenEnv"] },
"chat-channels.reply-in-thread": { provider: "slack", fields: ["workspaceId", "channelId", "senderUserId", "botUserId", "appId", "registeredWebhookOrigin", "webhookRelayPort", "botTokenEnv", "senderTokenEnv", "signingSecretEnv", "configurationTokenEnv"] },
"chat-channels.channel-file": { provider: "slack", fields: ["workspaceId", "channelId", "senderUserId", "botUserId", "appId", "registeredWebhookOrigin", "webhookRelayPort", "botTokenEnv", "senderTokenEnv", "signingSecretEnv", "configurationTokenEnv"] },
@@ -2,15 +2,16 @@ import { readFile, writeFile } from "node:fs/promises";
import path from "node:path";
import { chromium, test, type Cookie } from "@playwright/test";
import { validGrokRoutinePrivateInput, matchesConfiguredGrokRoutine, type GrokRoutinePrivateInput } from "./external-grok-private-contract.js";
import { validSlackIdentityInput, matchingSlackIdentityPreview, type SlackIdentityPrivateInput } from "./external-slack-identity-contract.js";
interface Request { mode: "secure-key" | "canary" | "gateway-token-rotation" | "company-secret-create" | "personal-secret-create" | "grok-routine-configure" | "slack-identity-confirm"; slackIdentity?:SlackIdentityPrivateInput; grok?:GrokRoutinePrivateInput; companyId?:string; personal?:{definitionId:string;userId:string}; gateway?: { id: string; oldTokenId: string; otherToken: string; endpointPath: string; readName: string }; baseURL: string; setupURL: string; providerURL: string; credential: string; cookies: Cookie[]; executablePath?: string; channel?: string }
import { validSlackIdentityInput, validSlackWizardIdentityInput, matchingSlackIdentityPreview, type SlackIdentityPrivateInput } from "./external-slack-identity-contract.js";
import { validSlackSetupPrivate, type SlackSetupPrivateInput } from "./external-slack-setup-private-contract.js";
interface Request { mode: "secure-key" | "canary" | "gateway-token-rotation" | "company-secret-create" | "personal-secret-create" | "grok-routine-configure" | "slack-identity-confirm" | "slack-endpoint-configure" | "slack-wizard-confirm"; slackSetup?:SlackSetupPrivateInput; slackIdentity?:SlackIdentityPrivateInput; grok?:GrokRoutinePrivateInput; companyId?:string; personal?:{definitionId:string;userId:string}; gateway?: { id: string; oldTokenId: string; otherToken: string; endpointPath: string; readName: string }; baseURL: string; setupURL: string; providerURL: string; credential: string; cookies: Cookie[]; executablePath?: string; channel?: string }
/** Separate Node process: no Playwright Test worker, API-step listener or artifact
* recorder exists. This module never enables tracing, video or screenshots. */
async function main() {
let attached = false; try { test.info(); attached = true; } catch { /* expected outside Test */ }
if (attached) throw new Error("Private browser worker cannot run inside Playwright Test");
const input = JSON.parse(await readFile(process.argv[2]!, "utf8")) as Request;
if (!input.credential || input.credential.length > (input.mode === "grok-routine-configure" ? 16384 : 8192) || !["secure-key", "canary", "gateway-token-rotation", "company-secret-create", "personal-secret-create", "grok-routine-configure", "slack-identity-confirm"].includes(input.mode)) throw new Error("Invalid private browser request");
if (!input.credential || input.credential.length > (input.mode === "grok-routine-configure" ? 16384 : 8192) || !["secure-key", "canary", "gateway-token-rotation", "company-secret-create", "personal-secret-create", "grok-routine-configure", "slack-identity-confirm", "slack-endpoint-configure", "slack-wizard-confirm"].includes(input.mode)) throw new Error("Invalid private browser request");
const base = new URL(input.baseURL);
if (base.hostname !== "127.0.0.1" || base.protocol !== "http:" || new URL(input.setupURL).origin !== base.origin) throw new Error("Private browser target is outside the disposable loopback instance");
const browser = await chromium.launch({ headless: true, ...(input.executablePath ? { executablePath: input.executablePath } : {}), ...(input.channel ? { channel: input.channel } : {}) });
@@ -23,6 +24,48 @@ async function main() {
if (await field.inputValue() !== input.credential) throw new Error("Canary entry failed");
return { processIsolation: true, testRunnerAttached: false, credentialEntered: true, tracingEnabled: false, videoEnabled: false, apiStepRecorder: false };
}
if(input.mode === "slack-wizard-confirm"){
const identity=input.slackIdentity;
if(!validSlackWizardIdentityInput(input.companyId,identity,input.setupURL)||!identity)throw new Error("Invalid finite normal Slack wizard identity operation");
const session=await context.request.get("/api/auth/get-session");if(!session.ok()||(await session.json()).user?.id!==identity.userId)throw new Error("Slack wizard requires the exact same signed-in person");
const endpointPath=`/api/chat-endpoints/${identity.endpointId}`;
const before=await context.request.get(endpointPath);if(!before.ok())throw new Error("Slack wizard endpoint missing");const endpoint=await before.json();if(endpoint.companyId!==input.companyId||endpoint.provider!=="slack"||endpoint.status!=="verifying"||endpoint.setup?.step!=="test")throw new Error("Normal Slack wizard is not waiting at actual verified identity/test boundary");
const principals=await context.request.get(`${endpointPath}/principals`);if(!principals.ok())throw new Error("Slack command discovery unavailable");
const rows=await principals.json() as Array<{principalId:string;externalDetail:string;lastConnectAt?:string;status:string;paperclipUserId?:string}>;const candidate=rows.filter(row=>row.principalId===identity.principalId&&row.externalDetail===identity.externalDetail&&row.lastConnectAt&&Date.parse(row.lastConnectAt)>=Date.parse(endpoint.setup.testStartedAt));if(candidate.length!==1||candidate[0]!.status==="linked")throw new Error("Genuine current Slack connect receipt is required before wizard ownership confirmation");
await page.goto(input.setupURL,{waitUntil:"domcontentloaded"});
const intentResponse=page.waitForResponse(response=>new URL(response.url()).pathname===`${endpointPath}/principals/${identity.principalId}/link-intent`&&response.request().method()==="POST"&&response.status()===201);
const confirmed=page.waitForResponse(response=>new URL(response.url()).pathname==="/api/chat-identity-links/confirm"&&response.request().method()==="POST"&&response.ok());
await page.getByRole("button",{name:`Link ${identity.externalLabel} to my Paperclip account`,exact:true}).click();
const intent=await(await intentResponse).json(),token=new URL(intent.confirmationUrl,base.origin).searchParams.get("token"),confirmation=await confirmed;
if(!token||confirmation.request().postDataJSON()?.token!==token||(await confirmation.json()).endpointId!==identity.endpointId)throw new Error("Actual Slack ownership button did not confirm its own generated scoped link");
await page.getByText("Linked to you",{exact:true}).waitFor();
const linkedResponse=await context.request.get(`${endpointPath}/principals`);if(!linkedResponse.ok()||!(await linkedResponse.json()).some((row:{principalId:string;status:string;paperclipUserId?:string})=>row.principalId===identity.principalId&&row.status==="linked"&&row.paperclipUserId===identity.userId))throw new Error("Wizard ownership did not persist as the exact same person");
await page.getByRole("button",{name:"Continue to message test",exact:true}).click();
const activation=page.waitForResponse(response=>new URL(response.url()).pathname===`${endpointPath}/finish`&&response.request().method()==="POST"&&response.ok());
await page.getByRole("button",{name:"Done",exact:true}).click();const activated=await(await activation).json();
const afterResponse=await context.request.get(endpointPath);if(!afterResponse.ok())throw new Error("Activated Slack endpoint unavailable");const after=await afterResponse.json();
if(activated.id!==identity.endpointId||activated.status!=="active"||after.status!=="active"||after.companyId!==input.companyId||after.setup?.step!=="complete")throw new Error("Actual wizard Done did not independently save activation");
return {processIsolation:true,testRunnerAttached:false,tracingEnabled:false,videoEnabled:false,apiStepRecorder:false,endpointId:identity.endpointId,principalId:identity.principalId,paperclipUserId:identity.userId,identityConfirmed:true,identityUiVisible:true,humanSubmittedSecurely:true,activationUiVisible:true,endpointActive:true};
}
if(input.mode === "slack-endpoint-configure"){
const slack=input.slackSetup;
if(!validSlackSetupPrivate(input.companyId,slack,input.setupURL)||!slack||!input.companyId||!input.credential.startsWith("xoxb-"))throw new Error("Invalid finite existing Slack credential form");
const endpointPath=`/api/chat-endpoints/${slack.endpointId}`,beforeResponse=await context.request.get(endpointPath);
if(!beforeResponse.ok())throw new Error("Owned Slack draft unavailable");
const before=await beforeResponse.json();
if(before.companyId!==input.companyId||before.provider!=="slack"||before.assignedAgentId!==slack.agentId||before.setup?.slackSetupMethod!=="existing"||before.providerAccountId)throw new Error("Slack credential form must target the exact fresh existing-app draft");
await page.goto(input.setupURL,{waitUntil:"domcontentloaded"});
const bot=page.getByLabel("Bot User OAuth Token",{exact:true}),secret=page.getByLabel("Signing Secret",{exact:true});
await bot.waitFor();if(await bot.getAttribute("type")!=="password"||await secret.getAttribute("type")!=="password")throw new Error("Slack credential fields are not protected");
await bot.fill(input.credential);await secret.fill(slack.signingSecret);
const saved=page.waitForResponse(response=>new URL(response.url()).pathname===`${endpointPath}/setup`&&response.request().method()==="POST"&&response.ok());
await page.getByRole("button",{name:"Connect Slack app",exact:true}).click();const response=await saved,submitted=response.request().postDataJSON();
if(submitted.action!=="configure"||submitted.credentials?.botToken!==input.credential||submitted.credentials?.signingSecret!==slack.signingSecret)throw new Error("Slack UI submitted another private setup operation");
const result=await response.json(),afterResponse=await context.request.get(endpointPath);if(!afterResponse.ok())throw new Error("Saved Slack endpoint unavailable");const after=await afterResponse.json();
if(result.id!==slack.endpointId||after.id!==slack.endpointId||after.companyId!==input.companyId||after.assignedAgentId!==slack.agentId||after.providerAccountId!==slack.workspaceId||!after.setup?.webhookSecretConfigured)throw new Error("Slack UI credential save did not persist the intended real workspace/agent");
await bot.waitFor({state:"hidden"});const storageClean=await page.evaluate(values=>!values.some(value=>JSON.stringify([Object.entries(localStorage),Object.entries(sessionStorage),document.body.innerText,location.href]).includes(value)),[input.credential,slack.signingSecret]);
return {processIsolation:true,testRunnerAttached:false,tracingEnabled:false,videoEnabled:false,apiStepRecorder:false,endpointId:slack.endpointId,slackCredentialUiVisible:true,humanSubmittedSecurely:true,storageClean};
}
if (input.mode === "slack-identity-confirm") {
const identity = input.slackIdentity;
if (!validSlackIdentityInput(input.companyId, identity, input.setupURL) || !identity || !input.companyId) throw new Error("Invalid finite Slack confirmation operation");
@@ -7,8 +7,9 @@ import { promisify } from "node:util";
import { chromium, type Cookie } from "@playwright/test";
import type { GrokRoutinePrivateInput } from "./external-grok-private-contract.js";
import type { SlackIdentityPrivateInput } from "./external-slack-identity-contract.js";
export interface PrivateBrowserReceipt { processIsolation: true; testRunnerAttached: false; tracingEnabled: false; videoEnabled: false; apiStepRecorder: false; credentialEntered?: boolean; connectionId?: string; challengeVisible?: boolean; passwordInput?: boolean; autocompleteOff?: boolean; humanSubmittedSecurely?: boolean; visibleReady?: boolean; storageClean?: boolean; newTokenId?: string; tokenUiVisible?: boolean; newWorkedBeforeRevocation?: boolean; oldWorkedUntilRevocation?: boolean; oldRevoked?: boolean; newWorksAfter?: boolean; oldStatusAfter?: number; otherWorksAfter?: boolean; secretId?:string; secretUiVisible?:boolean; bindingId?:string; routineConfigured?:boolean; routineUiVisible?:boolean; identityConfirmed?:boolean; identityUiVisible?:boolean; principalId?:string; paperclipUserId?:string }
export async function privateBrowserEntry(input: { mode: "secure-key" | "canary" | "gateway-token-rotation" | "company-secret-create" | "personal-secret-create" | "grok-routine-configure" | "slack-identity-confirm"; slackIdentity?:SlackIdentityPrivateInput; grok?:GrokRoutinePrivateInput; companyId?:string; personal?:{definitionId:string;userId:string}; gateway?: { id: string; oldTokenId: string; otherToken: string; endpointPath: string; readName: string }; baseURL: string; setupURL: string; providerURL: string; credential: string; cookies: Cookie[] }, onToken?: (token: { id: string; token: string }) => void) {
import type { SlackSetupPrivateInput } from "./external-slack-setup-private-contract.js";
export interface PrivateBrowserReceipt { processIsolation: true; testRunnerAttached: false; tracingEnabled: false; videoEnabled: false; apiStepRecorder: false; credentialEntered?: boolean; connectionId?: string; challengeVisible?: boolean; passwordInput?: boolean; autocompleteOff?: boolean; humanSubmittedSecurely?: boolean; visibleReady?: boolean; storageClean?: boolean; newTokenId?: string; tokenUiVisible?: boolean; newWorkedBeforeRevocation?: boolean; oldWorkedUntilRevocation?: boolean; oldRevoked?: boolean; newWorksAfter?: boolean; oldStatusAfter?: number; otherWorksAfter?: boolean; secretId?:string; secretUiVisible?:boolean; bindingId?:string; routineConfigured?:boolean; routineUiVisible?:boolean; identityConfirmed?:boolean; identityUiVisible?:boolean; principalId?:string; paperclipUserId?:string; endpointId?:string; slackCredentialUiVisible?:boolean; activationUiVisible?:boolean; endpointActive?:boolean }
export async function privateBrowserEntry(input: { mode: "secure-key" | "canary" | "gateway-token-rotation" | "company-secret-create" | "personal-secret-create" | "grok-routine-configure" | "slack-identity-confirm" | "slack-endpoint-configure" | "slack-wizard-confirm"; slackSetup?:SlackSetupPrivateInput; slackIdentity?:SlackIdentityPrivateInput; grok?:GrokRoutinePrivateInput; companyId?:string; personal?:{definitionId:string;userId:string}; gateway?: { id: string; oldTokenId: string; otherToken: string; endpointPath: string; readName: string }; baseURL: string; setupURL: string; providerURL: string; credential: string; cookies: Cookie[] }, onToken?: (token: { id: string; token: string }) => void) {
const directory = await mkdtemp(path.join(tmpdir(), "paperclip-private-browser-"));
try {
const request = path.join(directory, "request.json");
@@ -23,7 +24,9 @@ export async function privateBrowserEntry(input: { mode: "secure-key" | "canary"
} catch { throw new Error("Private browser entry failed; child output and credential-bearing request withheld"); }
let receipt: PrivateBrowserReceipt; try { receipt = JSON.parse(output) as PrivateBrowserReceipt; } catch { throw new Error("Private browser returned invalid closed receipt"); }
if (receipt.processIsolation !== true || receipt.testRunnerAttached !== false || receipt.tracingEnabled !== false || receipt.videoEnabled !== false || receipt.apiStepRecorder !== false
|| Object.keys(receipt).some(key => !["processIsolation", "testRunnerAttached", "tracingEnabled", "videoEnabled", "apiStepRecorder", "credentialEntered", "connectionId", "challengeVisible", "passwordInput", "autocompleteOff", "humanSubmittedSecurely", "visibleReady", "storageClean", "newTokenId", "tokenUiVisible", "newWorkedBeforeRevocation", "oldWorkedUntilRevocation", "oldRevoked", "newWorksAfter", "oldStatusAfter", "otherWorksAfter", "secretId", "secretUiVisible", "bindingId", "routineConfigured", "routineUiVisible", "identityConfirmed", "identityUiVisible", "principalId", "paperclipUserId"].includes(key))) throw new Error("Private browser receipt exceeds its closed contract");
|| Object.keys(receipt).some(key => !["processIsolation", "testRunnerAttached", "tracingEnabled", "videoEnabled", "apiStepRecorder", "credentialEntered", "connectionId", "challengeVisible", "passwordInput", "autocompleteOff", "humanSubmittedSecurely", "visibleReady", "storageClean", "newTokenId", "tokenUiVisible", "newWorkedBeforeRevocation", "oldWorkedUntilRevocation", "oldRevoked", "newWorksAfter", "oldStatusAfter", "otherWorksAfter", "secretId", "secretUiVisible", "bindingId", "routineConfigured", "routineUiVisible", "identityConfirmed", "identityUiVisible", "principalId", "paperclipUserId", "endpointId", "slackCredentialUiVisible", "activationUiVisible", "endpointActive"].includes(key))) throw new Error("Private browser receipt exceeds its closed contract");
if (input.mode === "slack-wizard-confirm" && (receipt.endpointId !== input.slackIdentity?.endpointId || receipt.principalId !== input.slackIdentity?.principalId || receipt.paperclipUserId !== input.slackIdentity?.userId || receipt.identityConfirmed !== true || receipt.activationUiVisible !== true || receipt.endpointActive !== true)) throw new Error("Invalid closed Slack wizard completion receipt");
if (input.mode === "slack-endpoint-configure" && (receipt.endpointId !== input.slackSetup?.endpointId || receipt.slackCredentialUiVisible !== true || receipt.humanSubmittedSecurely !== true || receipt.storageClean !== true)) throw new Error("Invalid closed Slack credential form receipt");
if (input.mode === "slack-identity-confirm" && (receipt.identityConfirmed !== true || receipt.identityUiVisible !== true || receipt.principalId !== input.slackIdentity?.principalId || receipt.paperclipUserId !== input.slackIdentity?.userId)) throw new Error("Invalid closed Slack identity confirmation receipt");
if (input.mode === "grok-routine-configure" && (receipt.bindingId !== input.grok?.bindingId || receipt.routineConfigured !== true || receipt.routineUiVisible !== true || receipt.visibleReady !== undefined)) throw new Error("Invalid closed Grok routine configuration receipt");
if (input.mode === "gateway-token-rotation") {
@@ -1,85 +1,697 @@
import type { ChatCatalogContext,ChatSlackIngressReceipt } from "../chat-catalog-contract.js";
import type { ChatConversation,ChatEndpoint,ChatEndpointResource } from "../../../packages/shared/src/types/chat-channels.js";
import { selectedExternalBinding,selectedExternalCredential } from "./external-fixtures.js";
import { slackProvider,assertSlackFixture,slackCompanionFields } from "./external-slack-provider.js";
import type {
ChatCatalogContext,
ChatSlackIngressReceipt,
} from "../chat-catalog-contract.js";
import type {
ChatConversation,
ChatEndpoint,
ChatEndpointResource,
} from "../../../packages/shared/src/types/chat-channels.js";
import {
selectedExternalBinding,
selectedExternalCredential,
} from "./external-fixtures.js";
import {
slackProvider,
assertSlackFixture,
slackCompanionFields,
} from "./external-slack-provider.js";
import { startSlackRelay } from "./external-slack-relay.js";
import { pollUntil } from "../api.js";
import {
privateBrowserEntry,
type PrivateBrowserReceipt,
} from "./external-private-browser.js";
import { privateSlackHumanConnect } from "./external-slack-human.js";
import type { SlackHumanConnectReceipt } from "./external-slack-human-contract.js";
import { root } from "./external-support.js";
export { slackCompanionFields };
interface Principal {principalId:string;externalLabel:string;externalDetail:string;paperclipUserId:string|null;status:string}
export interface SlackMessage {ts:string;thread_ts?:string;user?:string;bot_id?:string;text?:string;files?:Array<{id:string;permalink?:string;url_private_download?:string;size?:number}>}
interface Principal {
principalId: string;
externalLabel: string;
externalDetail: string;
paperclipUserId: string | null;
status: string;
lastConnectAt?: string | null;
}
export interface SlackMessage {
ts: string;
thread_ts?: string;
user?: string;
bot_id?: string;
text?: string;
files?: Array<{
id: string;
permalink?: string;
url_private_download?: string;
size?: number;
}>;
}
/** Preserve the selected app's actual scopes/events; only point its existing
* callback surfaces at this attempt's signed product webhook. */
export function slackCallbackManifest(original:Record<string,unknown>,webhook:string){
const url=new URL(webhook);if(url.protocol!=="https:"||!/^\/api\/chat-webhooks\/[a-zA-Z0-9_-]+\/slack$/.test(url.pathname)||url.search||url.hash||url.username||url.password)throw new Error("Disposable Slack callback is not the actual supported HTTPS product route");
const manifest=structuredClone(original) as Record<string,any>;manifest.settings??={};manifest.settings.event_subscriptions??={};
if(!Array.isArray(manifest.settings.event_subscriptions.bot_events)||!manifest.settings.event_subscriptions.bot_events.some((event:string)=>["message.channels","message.groups"].includes(event)))throw new Error("Setup blocker: allowlisted Slack app must already have authorized message event subscriptions; fixture will not silently widen scopes");
manifest.settings.event_subscriptions.request_url=webhook;
manifest.settings.interactivity={...manifest.settings.interactivity,is_enabled:true,request_url:webhook};
for(const command of manifest.features?.slash_commands??[])command.url=webhook;
return manifest;
export function slackCallbackManifest(
original: Record<string, unknown>,
webhook: string,
) {
const url = new URL(webhook);
if (
url.protocol !== "https:" ||
!/^\/api\/chat-webhooks\/[a-zA-Z0-9_-]+\/slack$/.test(url.pathname) ||
url.search ||
url.hash ||
url.username ||
url.password
)
throw new Error(
"Disposable Slack callback is not the actual supported HTTPS product route",
);
const manifest = structuredClone(original) as Record<string, any>;
manifest.settings ??= {};
manifest.settings.event_subscriptions ??= {};
if (
!Array.isArray(manifest.settings.event_subscriptions.bot_events) ||
!manifest.settings.event_subscriptions.bot_events.some((event: string) =>
["message.channels", "message.groups"].includes(event),
)
)
throw new Error(
"Setup blocker: allowlisted Slack app must already have authorized message event subscriptions; fixture will not silently widen scopes",
);
manifest.settings.event_subscriptions.request_url = webhook;
manifest.settings.interactivity = {
...manifest.settings.interactivity,
is_enabled: true,
request_url: webhook,
};
for (const command of manifest.features?.slash_commands ?? [])
command.url = webhook;
return manifest;
}
export async function prepareSlackFixture(ctx:ChatCatalogContext, options:{unlinkBeforeCanonical?:boolean}={}){
const binding=selectedExternalBinding(ctx);assertSlackFixture(binding);const p=binding.prerequisites;
if(!ctx.input.privateSecrets)throw new Error("Slack fixture requires private credential redaction");
const port=Number(p.webhookRelayPort);if(!Number.isInteger(port)||port<1024||port>65535)throw new Error("Setup blocker: dedicated webhookRelayPort for the isolated HTTPS tunnel is required");
const priorExperiments=await ctx.input.api.get<{enableChatConnectors?:boolean}>("/api/instance/settings/experimental");
const provider=slackProvider(ctx,binding);let endpoint:ChatEndpoint|undefined,relay:Awaited<ReturnType<typeof startSlackRelay>>|undefined,originalManifest:Record<string,unknown>|undefined,manifestChanged=false;
const createdThreads:string[]=[],createdFiles:string[]=[];
async function cleanup(){
try{if(endpoint)await ctx.input.api.post(`/api/chat-endpoints/${endpoint.id}/setup`,{action:"remove"});}
finally{try{if(manifestChanged&&originalManifest)await provider.call("configuration","apps.manifest.update",{app_id:p.appId,manifest:originalManifest});}finally{try{await provider.cleanupOwned();}finally{try{await relay?.close();}finally{await ctx.input.api.patch("/api/instance/settings/experimental",{enableChatConnectors:priorExperiments.enableChatConnectors===true});}}}}
}
try{
await ctx.input.api.patch("/api/instance/settings/experimental",{enableChatConnectors:true});
endpoint=await ctx.input.api.post<ChatEndpoint>(`${root(ctx)}/chat-endpoints`,{provider:"slack",assignedAgentId:ctx.input.fixtures.agent.id,name:"Disposable catalog Slack",slackSetupMethod:"manual"});
const webhook=endpoint.setup.webhookUrl;if(!webhook||new URL(webhook).origin!==new URL(p.registeredWebhookOrigin!).origin)throw new Error("Setup blocker: actual server PAPERCLIP_CHAT_WEBHOOK_PUBLIC_URL must match designated registeredWebhookOrigin before any Slack operation");
relay=await startSlackRelay({port,endpointPublicId:endpoint.publicId,baseURL:ctx.input.api.baseURL,signingSecret:selectedExternalCredential(ctx,binding,"signingSecretEnv"),workspaceId:p.workspaceId!,senderUserId:p.senderUserId!,channelId:p.channelId!});
const sender=await provider.call<{ok:true;team_id:string;user_id:string;bot_id?:string}>("sender","auth.test",{}),bot=await provider.call<{ok:true;team_id:string;user_id:string;bot_id?:string}>("bot","auth.test",{});
if(sender.team_id!==p.workspaceId||sender.user_id!==p.senderUserId||sender.bot_id||bot.team_id!==p.workspaceId||bot.user_id!==p.botUserId||!bot.bot_id)throw new Error("Setup blocker: selected Slack credentials do not identify the designated disposable workspace/human/bot");
const app=await provider.call<{ok:true;bot:{app_id:string}}>("bot","bots.info",{bot:bot.bot_id});if(app.bot?.app_id!==p.appId)throw new Error("Setup blocker: actual Slack bot token belongs to a different app than the allowlisted disposable app");
const human=await provider.call<{ok:true;user:{id:string;team_id:string;name:string;is_bot?:boolean;deleted?:boolean}}>("bot","users.info",{user:p.senderUserId});if(human.user.id!==p.senderUserId||human.user.team_id!==p.workspaceId||human.user.is_bot||human.user.deleted||!human.user.name)throw new Error("Setup blocker: designated Slack sender is not the selected active human");
endpoint=await ctx.input.api.postSensitive<ChatEndpoint>(`/api/chat-endpoints/${endpoint.id}/setup`,{action:"configure",credentials:{botToken:selectedExternalCredential(ctx,binding,"botTokenEnv"),signingSecret:selectedExternalCredential(ctx,binding,"signingSecretEnv")}});
const exported=await provider.call<{ok:true;manifest:Record<string,unknown>}>("configuration","apps.manifest.export",{app_id:p.appId});originalManifest=exported.manifest;
if(!originalManifest||typeof originalManifest!=="object")throw new Error("Setup blocker: disposable Slack app manifest could not be independently read");
const manifest=slackCallbackManifest(originalManifest,webhook);
manifestChanged=true;await provider.call("configuration","apps.manifest.update",{app_id:p.appId,manifest});
endpoint=await pollUntil({label:"actual Slack signed callback verification",deadlineAt:Date.now()+60000,load:()=>ctx.input.api.get<ChatEndpoint>(`/api/chat-endpoints/${endpoint!.id}`),accept:row=>!!row.setup.webhookVerifiedAt});
endpoint=await ctx.input.api.post<ChatEndpoint>(`/api/chat-endpoints/${endpoint.id}/setup`,{action:"verify"});
await ctx.input.api.patch(`/api/chat-endpoints/${endpoint.id}`,{requireAtMention:false,allowGroupChats:true,allowUnlinkedPeople:false});
const resources=await ctx.input.api.get<ChatEndpointResource[]>(`/api/chat-endpoints/${endpoint.id}/resources`);if(!resources.some(row=>row.providerResourceId===p.channelId&&row.availability==="available"))throw new Error("Setup blocker: app bot is not an actual member of the allowlisted disposable Slack channel");
await ctx.input.api.putSensitive(`/api/chat-endpoints/${endpoint.id}/resources`,{resources:resources.map(row=>({id:row.id,enabled:row.providerResourceId===p.channelId}))});
async function postRoot(text:string){const result=await provider.call<{ok:true;channel:string;ts:string;message:{user:string;text:string}}>("sender","chat.postMessage",{channel:p.channelId,text,unfurl_links:false,unfurl_media:false});if(result.channel!==p.channelId||result.message?.user!==p.senderUserId||!/^\d+\.\d{6}$/.test(result.ts))throw new Error("Actual Slack root did not bind the designated human/channel");createdThreads.push(result.ts);return result.ts;}
const threadId=await postRoot(`Disposable launch discussion ${ctx.input.nonce}`);
const principals=await pollUntil({label:"actual unlinked Slack sender discovery",deadlineAt:Date.now()+60000,load:()=>ctx.input.api.get<Principal[]>(`/api/chat-endpoints/${endpoint!.id}/principals`),accept:rows=>rows.some(row=>row.externalDetail===`@${human.user.name}`)});
const principal=principals.filter(row=>row.externalDetail===`@${human.user.name}`);if(principal.length!==1||principal[0]!.status==="linked")throw new Error("Setup blocker: the genuine Slack human must have one unlinked discovered identity in the new endpoint");
const intent=await ctx.input.api.postSensitive<{confirmationUrl:string}>(`/api/chat-endpoints/${endpoint.id}/principals/${principal[0]!.principalId}/link-intent`,{expiresInSeconds:600}),url=new URL(intent.confirmationUrl,ctx.input.api.baseURL),token=url.searchParams.get("token");if(url.pathname!=="/chat-identity/confirm"||!token)throw new Error("Actual Slack identity link returned an unsupported confirmation surface");ctx.input.privateSecrets.push(token);
// Fixture setup uses the same supported signed-in confirmation route as the
// human UI, through the noninstrumented sensitive transport. The bearer link
// never enters a Test browser URL/trace; this is labelled operator API setup.
await ctx.input.api.postSensitive("/api/chat-identity-links/confirm",{token});
const linked=(await ctx.input.api.get<Principal[]>(`/api/chat-endpoints/${endpoint.id}/principals`)).filter(row=>row.principalId===principal[0]!.principalId&&row.status==="linked");if(linked.length!==1||!linked[0]!.paperclipUserId)throw new Error("Actual signed-in person did not complete the selected Slack identity link");
endpoint=await ctx.input.api.post<ChatEndpoint>(`/api/chat-endpoints/${endpoint.id}/finish`,{});if(endpoint.status!=="active")throw new Error("Setup blocker: supported Slack finish did not activate the actual endpoint");
let linkedPaperclipUserId: string|null=linked[0]!.paperclipUserId;
const principalId=principal[0]!.principalId;
if(options.unlinkBeforeCanonical){
await ctx.input.api.delete(`/api/chat-endpoints/${endpoint.id}/principals/${principalId}/link`);
const current=(await ctx.input.api.get<Principal[]>(`/api/chat-endpoints/${endpoint.id}/principals`)).filter(row=>row.principalId===principalId);
if(current.length!==1||current[0]!.status==="linked")throw new Error("Public unlink did not revoke the exact qualified sender");
linkedPaperclipUserId=null;
export async function prepareSlackFixture(
ctx: ChatCatalogContext,
options: {
unlinkBeforeCanonical?: boolean;
normalWizard?: { endpointId: string; agentId: string };
} = {},
) {
const binding = selectedExternalBinding(ctx);
assertSlackFixture(binding);
const p = binding.prerequisites;
if (!ctx.input.privateSecrets)
throw new Error("Slack fixture requires private credential redaction");
const port = Number(p.webhookRelayPort);
if (!Number.isInteger(port) || port < 1024 || port > 65535)
throw new Error(
"Setup blocker: dedicated webhookRelayPort for the isolated HTTPS tunnel is required",
);
const priorExperiments = await ctx.input.api.get<{
enableChatConnectors?: boolean;
}>("/api/instance/settings/experimental");
const provider = slackProvider(ctx, binding);
let endpoint: ChatEndpoint | undefined,
relay: Awaited<ReturnType<typeof startSlackRelay>> | undefined,
originalManifest: Record<string, unknown> | undefined,
manifestChanged = false;
const createdThreads: string[] = [],
createdFiles: string[] = [];
let credentialUiReceipt: PrivateBrowserReceipt | undefined,
identityUiReceipt: PrivateBrowserReceipt | undefined,
humanCommandReceipt: SlackHumanConnectReceipt | undefined;
async function cleanup() {
try {
if (endpoint)
await ctx.input.api.post(`/api/chat-endpoints/${endpoint.id}/setup`, {
action: "remove",
});
} finally {
try {
if (manifestChanged && originalManifest)
await provider.call("configuration", "apps.manifest.update", {
app_id: p.appId,
manifest: originalManifest,
});
} finally {
try {
await provider.cleanupOwned();
} finally {
try {
await relay?.close();
} finally {
await ctx.input.api.patch("/api/instance/settings/experimental", {
enableChatConnectors:
priorExperiments.enableChatConnectors === true,
});
}
}
}
}
}
try {
await ctx.input.api.patch("/api/instance/settings/experimental", {
enableChatConnectors: true,
});
if (options.normalWizard) {
endpoint = await ctx.input.api.get<ChatEndpoint>(
`/api/chat-endpoints/${options.normalWizard.endpointId}`,
);
if (
endpoint.companyId !== ctx.input.fixtures.company.id ||
endpoint.provider !== "slack" ||
endpoint.assignedAgentId !== options.normalWizard.agentId ||
endpoint.setup.slackSetupMethod !== "existing" ||
endpoint.providerAccountId
)
throw new Error(
"Normal Slack setup must use the exact human-created fresh existing-app draft",
);
} else
endpoint = await ctx.input.api.post<ChatEndpoint>(
`${root(ctx)}/chat-endpoints`,
{
provider: "slack",
assignedAgentId: ctx.input.fixtures.agent.id,
name: "Disposable catalog Slack",
slackSetupMethod: "manual",
},
);
const webhook = endpoint.setup.webhookUrl;
if (
!webhook ||
new URL(webhook).origin !== new URL(p.registeredWebhookOrigin!).origin
)
throw new Error(
"Setup blocker: actual server PAPERCLIP_CHAT_WEBHOOK_PUBLIC_URL must match designated registeredWebhookOrigin before any Slack operation",
);
relay = await startSlackRelay({
port,
endpointPublicId: endpoint.publicId,
baseURL: ctx.input.api.baseURL,
signingSecret: selectedExternalCredential(
ctx,
binding,
"signingSecretEnv",
),
workspaceId: p.workspaceId!,
senderUserId: p.senderUserId!,
channelId: p.channelId!,
});
const sender = await provider.call<{
ok: true;
team_id: string;
user_id: string;
bot_id?: string;
}>("sender", "auth.test", {}),
bot = await provider.call<{
ok: true;
team_id: string;
user_id: string;
bot_id?: string;
}>("bot", "auth.test", {});
if (
sender.team_id !== p.workspaceId ||
sender.user_id !== p.senderUserId ||
sender.bot_id ||
bot.team_id !== p.workspaceId ||
bot.user_id !== p.botUserId ||
!bot.bot_id
)
throw new Error(
"Setup blocker: selected Slack credentials do not identify the designated disposable workspace/human/bot",
);
const app = await provider.call<{ ok: true; bot: { app_id: string } }>(
"bot",
"bots.info",
{ bot: bot.bot_id },
);
if (app.bot?.app_id !== p.appId)
throw new Error(
"Setup blocker: actual Slack bot token belongs to a different app than the allowlisted disposable app",
);
const human = await provider.call<{
ok: true;
user: {
id: string;
team_id: string;
name: string;
is_bot?: boolean;
deleted?: boolean;
};
}>("bot", "users.info", { user: p.senderUserId });
if (
human.user.id !== p.senderUserId ||
human.user.team_id !== p.workspaceId ||
human.user.is_bot ||
human.user.deleted ||
!human.user.name
)
throw new Error(
"Setup blocker: designated Slack sender is not the selected active human",
);
if (options.normalWizard) {
credentialUiReceipt = await privateBrowserEntry({
mode: "slack-endpoint-configure",
companyId: ctx.input.fixtures.company.id,
slackSetup: {
endpointId: endpoint.id,
agentId: options.normalWizard.agentId,
workspaceId: p.workspaceId!,
signingSecret: selectedExternalCredential(
ctx,
binding,
"signingSecretEnv",
),
},
baseURL: ctx.input.api.baseURL,
providerURL: ctx.input.api.baseURL,
setupURL: `${ctx.input.api.baseURL}/${ctx.input.fixtures.company.issuePrefix}/apps/chat/connect?provider=slack&purpose=chat&resume=${endpoint.id}&stage=credentials`,
credential: selectedExternalCredential(ctx, binding, "botTokenEnv"),
cookies: await ctx.input.page.context().cookies(ctx.input.api.baseURL),
});
endpoint = await ctx.input.api.get<ChatEndpoint>(
`/api/chat-endpoints/${endpoint.id}`,
);
} else
endpoint = await ctx.input.api.postSensitive<ChatEndpoint>(
`/api/chat-endpoints/${endpoint.id}/setup`,
{
action: "configure",
credentials: {
botToken: selectedExternalCredential(ctx, binding, "botTokenEnv"),
signingSecret: selectedExternalCredential(
ctx,
binding,
"signingSecretEnv",
),
},
},
);
const exported = await provider.call<{
ok: true;
manifest: Record<string, unknown>;
}>("configuration", "apps.manifest.export", { app_id: p.appId });
originalManifest = exported.manifest;
if (!originalManifest || typeof originalManifest !== "object")
throw new Error(
"Setup blocker: disposable Slack app manifest could not be independently read",
);
const manifest = slackCallbackManifest(originalManifest, webhook);
manifestChanged = true;
await provider.call("configuration", "apps.manifest.update", {
app_id: p.appId,
manifest,
});
endpoint = await pollUntil({
label: "actual Slack signed callback verification",
deadlineAt: Date.now() + 60000,
load: () =>
ctx.input.api.get<ChatEndpoint>(`/api/chat-endpoints/${endpoint!.id}`),
accept: (row) => !!row.setup.webhookVerifiedAt,
});
if (options.normalWizard) {
const page = await ctx.input.page.context().newPage();
try {
await page.goto(
`${ctx.input.api.baseURL}/${ctx.input.fixtures.company.issuePrefix}/apps/chat/connect?provider=slack&purpose=chat&resume=${endpoint.id}`,
{ waitUntil: "domcontentloaded" },
);
await page
.getByRole("heading", {
name: "Verify Slack connection",
exact: true,
})
.waitFor();
const verification = page.waitForResponse(
(response) =>
new URL(response.url()).pathname ===
`/api/chat-endpoints/${endpoint!.id}/setup` &&
response.request().method() === "POST" &&
response.ok(),
);
await page
.getByRole("button", { name: "Continue", exact: true })
.click();
const response = await verification;
if (response.request().postDataJSON()?.action !== "verify")
throw new Error("Slack verification UI submitted another action");
endpoint = (await response.json()) as ChatEndpoint;
} finally {
await page.close();
}
} else
endpoint = await ctx.input.api.post<ChatEndpoint>(
`/api/chat-endpoints/${endpoint.id}/setup`,
{ action: "verify" },
);
await ctx.input.api.patch(`/api/chat-endpoints/${endpoint.id}`, {
requireAtMention: false,
allowGroupChats: true,
allowUnlinkedPeople: false,
});
const resources = await ctx.input.api.get<ChatEndpointResource[]>(
`/api/chat-endpoints/${endpoint.id}/resources`,
);
if (
!resources.some(
(row) =>
row.providerResourceId === p.channelId &&
row.availability === "available",
)
)
throw new Error(
"Setup blocker: app bot is not an actual member of the allowlisted disposable Slack channel",
);
await ctx.input.api.putSensitive(
`/api/chat-endpoints/${endpoint.id}/resources`,
{
resources: resources.map((row) => ({
id: row.id,
enabled: row.providerResourceId === p.channelId,
})),
},
);
async function postRoot(text: string) {
const result = await provider.call<{
ok: true;
channel: string;
ts: string;
message: { user: string; text: string };
}>("sender", "chat.postMessage", {
channel: p.channelId,
text,
unfurl_links: false,
unfurl_media: false,
});
if (
result.channel !== p.channelId ||
result.message?.user !== p.senderUserId ||
!/^\d+\.\d{6}$/.test(result.ts)
)
throw new Error(
"Actual Slack root did not bind the designated human/channel",
);
createdThreads.push(result.ts);
return result.ts;
}
const threadId = await postRoot(
`Disposable launch discussion ${ctx.input.nonce}`,
);
const principals = await pollUntil({
label: "actual unlinked Slack sender discovery",
deadlineAt: Date.now() + 60000,
load: () =>
ctx.input.api.get<Principal[]>(
`/api/chat-endpoints/${endpoint!.id}/principals`,
),
accept: (rows) =>
rows.some((row) => row.externalDetail === `@${human.user.name}`),
});
const principal = principals.filter(
(row) => row.externalDetail === `@${human.user.name}`,
);
if (principal.length !== 1 || principal[0]!.status === "linked")
throw new Error(
"Setup blocker: the genuine Slack human must have one unlinked discovered identity in the new endpoint",
);
if (options.normalWizard) {
const command = endpoint.setup.command;
if (
typeof command !== "string" ||
!/^\/[a-zA-Z0-9_-]{1,100}$/.test(command) ||
!(
originalManifest.features as
{ slash_commands?: Array<{ command: string }> } | undefined
)?.slash_commands?.some((row) => row.command === command)
)
throw new Error(
"Setup blocker: actual saved app command does not match the selected installed Slack app",
);
const selectedChannel = await provider.call<{
ok: true;
channel: { id: string; name: string };
}>("bot", "conversations.info", { channel: p.channelId });
if (
selectedChannel.channel.id !== p.channelId ||
!/^[a-z0-9_-]{1,80}$/.test(selectedChannel.channel.name)
)
throw new Error(
"Setup blocker: selected Slack channel identity is unavailable for independent human UI verification",
);
const startedAt = Date.now();
relay.expectHumanConnect(command);
humanCommandReceipt = await privateSlackHumanConnect(
{
workspaceId: p.workspaceId!,
channelId: p.channelId!,
channelName: selectedChannel.channel.name,
senderUserId: p.senderUserId!,
command,
sessionJson: selectedExternalCredential(
ctx,
binding,
"slackBrowserSessionEnv",
),
},
ctx.input.privateSecrets,
);
await pollUntil({
label: "genuine Slack-signed human connect command callback",
deadlineAt: Date.now() + 60000,
load: async () => relay!.receipt(),
accept: (row) => row.humanConnectCaptured,
});
await pollUntil({
label: "actual processed current Slack connect discovery",
deadlineAt: Date.now() + 60000,
load: () =>
ctx.input.api.get<Principal[]>(
`/api/chat-endpoints/${endpoint!.id}/principals`,
),
accept: (rows) =>
rows.some(
(row) =>
row.principalId === principal[0]!.principalId &&
!!row.lastConnectAt &&
Date.parse(row.lastConnectAt) >= startedAt - 1000,
),
});
const session = await ctx.input.api.get<{ user: { id: string } }>(
"/api/auth/get-session",
);
identityUiReceipt = await privateBrowserEntry({
mode: "slack-wizard-confirm",
companyId: ctx.input.fixtures.company.id,
slackIdentity: {
endpointId: endpoint.id,
principalId: principal[0]!.principalId,
userId: session.user.id,
externalDetail: principal[0]!.externalDetail,
externalLabel: principal[0]!.externalLabel,
},
baseURL: ctx.input.api.baseURL,
providerURL: ctx.input.api.baseURL,
setupURL: `${ctx.input.api.baseURL}/${ctx.input.fixtures.company.issuePrefix}/apps/chat/connect?provider=slack&purpose=chat&resume=${endpoint.id}`,
credential: "finite-slack-normal-wizard-confirmation",
cookies: await ctx.input.page.context().cookies(ctx.input.api.baseURL),
});
} else {
const intent = await ctx.input.api.postSensitive<{
confirmationUrl: string;
}>(
`/api/chat-endpoints/${endpoint.id}/principals/${principal[0]!.principalId}/link-intent`,
{ expiresInSeconds: 600 },
),
url = new URL(intent.confirmationUrl, ctx.input.api.baseURL),
token = url.searchParams.get("token");
if (url.pathname !== "/chat-identity/confirm" || !token)
throw new Error(
"Actual Slack identity link returned an unsupported confirmation surface",
);
ctx.input.privateSecrets.push(token);
// Fixture setup uses the same supported signed-in confirmation route as the
// human UI, through the noninstrumented sensitive transport. The bearer link
// never enters a Test browser URL/trace; this is labelled operator API setup.
await ctx.input.api.postSensitive("/api/chat-identity-links/confirm", {
token,
});
}
const linked = (
await ctx.input.api.get<Principal[]>(
`/api/chat-endpoints/${endpoint.id}/principals`,
)
).filter(
(row) =>
row.principalId === principal[0]!.principalId &&
row.status === "linked",
);
if (linked.length !== 1 || !linked[0]!.paperclipUserId)
throw new Error(
"Actual signed-in person did not complete the selected Slack identity link",
);
endpoint = options.normalWizard
? await ctx.input.api.get<ChatEndpoint>(
`/api/chat-endpoints/${endpoint.id}`,
)
: await ctx.input.api.post<ChatEndpoint>(
`/api/chat-endpoints/${endpoint.id}/finish`,
{},
);
if (endpoint.status !== "active")
throw new Error(
"Setup blocker: supported Slack finish did not activate the actual endpoint",
);
let linkedPaperclipUserId: string | null = linked[0]!.paperclipUserId;
const principalId = principal[0]!.principalId;
if (options.unlinkBeforeCanonical) {
await ctx.input.api.delete(
`/api/chat-endpoints/${endpoint.id}/principals/${principalId}/link`,
);
const current = (
await ctx.input.api.get<Principal[]>(
`/api/chat-endpoints/${endpoint.id}/principals`,
)
).filter((row) => row.principalId === principalId);
if (current.length !== 1 || current[0]!.status === "linked")
throw new Error(
"Public unlink did not revoke the exact qualified sender",
);
linkedPaperclipUserId = null;
}
return {
binding,
provider,
relay,
endpoint,
threadId,
createdThreads,
createdFiles,
principalId,
credentialUiReceipt,
identityUiReceipt,
humanCommandReceipt,
senderIdentity: {
userId: human.user.id,
workspaceId: human.user.team_id,
handle: human.user.name,
},
get linkedPaperclipUserId() {
return linkedPaperclipUserId;
},
async readPrincipal() {
const rows = await ctx.input.api.get<Principal[]>(
`/api/chat-endpoints/${endpoint!.id}/principals`,
);
const matches = rows.filter((row) => row.principalId === principalId);
if (matches.length !== 1)
throw new Error(
"Exact Slack principal is no longer uniquely inspectable",
);
return matches[0]!;
},
async refreshLinkedIdentity() {
const rows = await ctx.input.api.get<Principal[]>(
`/api/chat-endpoints/${endpoint!.id}/principals`,
),
current = rows.filter(
(row) =>
row.principalId === principalId &&
row.status === "linked" &&
!!row.paperclipUserId,
);
if (current.length !== 1)
throw new Error(
"Actual Slack principal has no current confirmed identity",
);
linkedPaperclipUserId = current[0]!.paperclipUserId;
return linkedPaperclipUserId!;
},
async createDistractor(text: string) {
return postRoot(text);
},
async readThread(rootTs: string) {
if (!createdThreads.includes(rootTs))
throw new Error("Slack egress read escaped an owned thread");
const result = await provider.call<{
ok: true;
messages: SlackMessage[];
has_more?: boolean;
}>("bot", "conversations.replies", {
channel: p.channelId,
ts: rootTs,
limit: 100,
});
if (result.has_more || !Array.isArray(result.messages))
throw new Error("Slack thread exceeds bounded fixture observation");
return result.messages;
},
async resolveIngress(
query: string,
messageId: string,
providerCalls: number,
rootTs = threadId,
): Promise<ChatSlackIngressReceipt> {
if (!createdThreads.includes(rootTs))
throw new Error(
"Slack ingress resolution escaped an owned actual root",
);
if (!linkedPaperclipUserId)
throw new Error(
"Slack source cannot resolve before actual identity confirmation",
);
const conversations = await pollUntil({
label: "actual Slack canonical source persistence",
deadlineAt: Date.now() + 60000,
load: () =>
ctx.input.api.get<ChatConversation[]>(
`/api/chat-endpoints/${endpoint!.id}/conversations`,
),
accept: (rows) =>
rows.some(
(row) =>
row.externalThreadId === `slack:${p.channelId}:${rootTs}`,
),
}),
conversation = conversations.find(
(row) => row.externalThreadId === `slack:${p.channelId}:${rootTs}`,
)!;
const comments = await ctx.input.api.get<
Array<{
id: string;
body: string;
authorUserId: string;
metadata?: { sourceChannel?: string };
}>
>(`/api/issues/${conversation.issueId}/comments?order=asc`),
humanComments = comments.filter(
(row) =>
row.body === query &&
row.authorUserId === linkedPaperclipUserId &&
row.metadata?.sourceChannel === "slack",
);
if (!humanComments.length)
throw new Error(
"Canonical Slack input did not persist under the linked human identity",
);
return {
query,
endpointId: endpoint!.id,
conversationId: conversation.id,
issueId: conversation.issueId,
humanCommentId: humanComments.at(-1)!.id,
linkedPaperclipUserId: linkedPaperclipUserId!,
providerCalls,
provider: {
workspaceId: p.workspaceId!,
channelId: p.channelId!,
senderUserId: p.senderUserId!,
threadId: rootTs,
canonicalMessageId: messageId,
},
};
},
cleanup,
};
} catch (error) {
await cleanup();
throw error;
}
return {binding,provider,relay,endpoint,threadId,createdThreads,createdFiles,principalId,senderIdentity:{userId:human.user.id,workspaceId:human.user.team_id,handle:human.user.name},
get linkedPaperclipUserId(){return linkedPaperclipUserId;},
async readPrincipal(){const rows=await ctx.input.api.get<Principal[]>(`/api/chat-endpoints/${endpoint!.id}/principals`);const matches=rows.filter(row=>row.principalId===principalId);if(matches.length!==1)throw new Error("Exact Slack principal is no longer uniquely inspectable");return matches[0]!;},
async refreshLinkedIdentity(){const rows=await ctx.input.api.get<Principal[]>(`/api/chat-endpoints/${endpoint!.id}/principals`),current=rows.filter(row=>row.principalId===principalId&&row.status==="linked"&&!!row.paperclipUserId);if(current.length!==1)throw new Error("Actual Slack principal has no current confirmed identity");linkedPaperclipUserId=current[0]!.paperclipUserId;return linkedPaperclipUserId!;},
async createDistractor(text:string){return postRoot(text);},
async readThread(rootTs:string){if(!createdThreads.includes(rootTs))throw new Error("Slack egress read escaped an owned thread");const result=await provider.call<{ok:true;messages:SlackMessage[];has_more?:boolean}>("bot","conversations.replies",{channel:p.channelId,ts:rootTs,limit:100});if(result.has_more||!Array.isArray(result.messages))throw new Error("Slack thread exceeds bounded fixture observation");return result.messages;},
async resolveIngress(query:string,messageId:string,providerCalls:number,rootTs=threadId):Promise<ChatSlackIngressReceipt>{
if(!createdThreads.includes(rootTs))throw new Error("Slack ingress resolution escaped an owned actual root");
if(!linkedPaperclipUserId)throw new Error("Slack source cannot resolve before actual identity confirmation");
const conversations=await pollUntil({label:"actual Slack canonical source persistence",deadlineAt:Date.now()+60000,load:()=>ctx.input.api.get<ChatConversation[]>(`/api/chat-endpoints/${endpoint!.id}/conversations`),accept:rows=>rows.some(row=>row.externalThreadId===`slack:${p.channelId}:${rootTs}`)}),conversation=conversations.find(row=>row.externalThreadId===`slack:${p.channelId}:${rootTs}`)!;
const comments=await ctx.input.api.get<Array<{id:string;body:string;authorUserId:string;metadata?:{sourceChannel?:string}}>>(`/api/issues/${conversation.issueId}/comments?order=asc`),humanComments=comments.filter(row=>row.body===query&&row.authorUserId===linkedPaperclipUserId&&row.metadata?.sourceChannel==="slack");
if(!humanComments.length)throw new Error("Canonical Slack input did not persist under the linked human identity");return {query,endpointId:endpoint!.id,conversationId:conversation.id,issueId:conversation.issueId,humanCommentId:humanComments.at(-1)!.id,linkedPaperclipUserId:linkedPaperclipUserId!,providerCalls,provider:{workspaceId:p.workspaceId!,channelId:p.channelId!,senderUserId:p.senderUserId!,threadId:rootTs,canonicalMessageId:messageId}};
},cleanup};
}catch(error){await cleanup();throw error;}
}
@@ -0,0 +1,80 @@
import { expect, it } from "vitest";
import {
validSlackHumanConnect,
validSlackHumanReceipt,
} from "./external-slack-human-contract.js";
const input = {
workspaceId: "T123",
channelId: "C123",
channelName: "owned-test-channel",
senderUserId: "U123",
command: "/owned-agent",
session: {
cookies: [
{
name: "d",
value: "synthetic-private-session",
domain: ".slack.com",
path: "/",
secure: true,
},
],
origins: [
{
origin: "https://app.slack.com",
localStorage: [{ name: "session", value: "synthetic-private-storage" }],
},
],
},
};
it("admits only a bounded official Slack session and exact named workspace/person connect command", () => {
expect(validSlackHumanConnect(input)).toBe(true);
for (const mutate of [
(x: typeof input) => {
x.workspaceId = "other";
},
(x: typeof input) => {
x.senderUserId = "UBOT\nother";
},
(x: typeof input) => {
x.command = "/owned-agent delete";
},
(x: typeof input) => {
x.session.cookies[0]!.domain = "slack.com.attacker.invalid";
},
(x: typeof input) => {
x.session.cookies[0]!.secure = false;
},
(x: typeof input) => {
x.session.origins[0]!.origin = "http://app.slack.com";
},
(x: typeof input) => {
x.session.cookies = [];
},
]) {
const bad = structuredClone(input);
mutate(bad);
expect(validSlackHumanConnect(bad)).toBe(false);
}
});
it("requires independently copied exact human ID and closed isolated browser receipt", () => {
const r = {
processIsolation: true,
testRunnerAttached: false,
tracingEnabled: false,
videoEnabled: false,
verifiedWorkspaceId: input.workspaceId,
verifiedHumanId: input.senderUserId,
commandSubmitted: true,
};
expect(validSlackHumanReceipt(r, input)).toBe(true);
for (const wrong of [
{ ...r, verifiedHumanId: "another" },
{ ...r, verifiedWorkspaceId: "TOTHER" },
{ ...r, commandSubmitted: false },
{ ...r, testRunnerAttached: true },
{ ...r, tracingEnabled: true },
{ ...r, session: "unexpected" },
])
expect(validSlackHumanReceipt(wrong, input)).toBe(false);
});
@@ -0,0 +1,101 @@
import type { PublicSignInStorage as StorageState } from "./external-public-signin-contract.js";
export interface SlackHumanConnectInput {
workspaceId: string;
channelId: string;
channelName: string;
senderUserId: string;
command: string;
session: StorageState;
}
export function validSlackHumanConnect(input: SlackHumanConnectInput) {
if (
!/^T[A-Z0-9]+$/.test(input.workspaceId) ||
!/^C[A-Z0-9]+$|^G[A-Z0-9]+$/.test(input.channelId) ||
!/^[a-z0-9_-]{1,80}$/.test(input.channelName) ||
!/^[UW][A-Z0-9]+$/.test(input.senderUserId) ||
!/^\/[a-zA-Z0-9_-]{1,100}$/.test(input.command) ||
!input.session ||
!Array.isArray(input.session.cookies) ||
!input.session.cookies.length ||
input.session.cookies.length > 100 ||
!Array.isArray(input.session.origins) ||
input.session.origins.length > 10
)
return false;
return (
input.session.cookies.every(
(cookie) =>
!!cookie &&
typeof cookie.name === "string" &&
cookie.name.length > 0 &&
cookie.name.length <= 200 &&
typeof cookie.value === "string" &&
cookie.value.length > 0 &&
cookie.value.length <= 65536 &&
cookie.secure === true &&
/^\.?([a-z0-9-]+\.)*slack\.com$/i.test(cookie.domain) &&
typeof cookie.path === "string" &&
cookie.path.startsWith("/"),
) &&
input.session.origins.every((origin) => {
try {
const u = new URL(origin.origin);
return (
u.protocol === "https:" &&
/^([a-z0-9-]+\.)*slack\.com$/i.test(u.hostname) &&
u.origin === origin.origin &&
!u.username &&
!u.password &&
!u.port &&
Array.isArray(origin.localStorage) &&
origin.localStorage.length <= 200 &&
origin.localStorage.every(
(item) =>
typeof item.name === "string" &&
item.name.length <= 1000 &&
typeof item.value === "string" &&
item.value.length <= 262144,
)
);
} catch {
return false;
}
})
);
}
export interface SlackHumanConnectReceipt {
processIsolation: true;
testRunnerAttached: false;
tracingEnabled: false;
videoEnabled: false;
verifiedWorkspaceId: string;
verifiedHumanId: string;
commandSubmitted: true;
}
export function validSlackHumanReceipt(
value: unknown,
input: Pick<SlackHumanConnectInput, "workspaceId" | "senderUserId">,
) {
const r = value as Partial<SlackHumanConnectReceipt> | undefined;
return (
!!r &&
Object.keys(r).every((key) =>
[
"processIsolation",
"testRunnerAttached",
"tracingEnabled",
"videoEnabled",
"verifiedWorkspaceId",
"verifiedHumanId",
"commandSubmitted",
].includes(key),
) &&
r.processIsolation === true &&
r.testRunnerAttached === false &&
r.tracingEnabled === false &&
r.videoEnabled === false &&
r.verifiedWorkspaceId === input.workspaceId &&
r.verifiedHumanId === input.senderUserId &&
r.commandSubmitted === true
);
}
@@ -0,0 +1,123 @@
import { readFile, writeFile } from "node:fs/promises";
import path from "node:path";
import { chromium, test } from "@playwright/test";
import {
validSlackHumanConnect,
type SlackHumanConnectInput,
} from "./external-slack-human-contract.js";
/** No Playwright Test worker/report/artifact listener is attached. Never emits
* screenshots, trace, cookies, browser state, profile or provider response data. */
async function main() {
let attached = false;
try {
test.info();
attached = true;
} catch {
/* standalone */
}
if (attached)
throw new Error("Private Slack operation cannot run within Test");
const input = JSON.parse(
await readFile(process.argv[2]!, "utf8"),
) as SlackHumanConnectInput & { executablePath?: string; channel?: string };
if (!validSlackHumanConnect(input))
throw new Error("Invalid finite Slack human operation");
const browser = await chromium.launch({
headless: true,
...(input.executablePath ? { executablePath: input.executablePath } : {}),
...(input.channel ? { channel: input.channel } : {}),
});
try {
const storage = path.join(path.dirname(process.argv[2]!), "storage.json");
await writeFile(storage, JSON.stringify(input.session), { mode: 0o600 });
const context = await browser.newContext({ storageState: storage });
await context.grantPermissions(["clipboard-read", "clipboard-write"], {
origin: "https://app.slack.com",
});
const page = await context.newPage();
page.setDefaultTimeout(20000);
await page.goto(
`https://app.slack.com/client/${input.workspaceId}/${input.channelId}`,
{ waitUntil: "domcontentloaded" },
);
const target = () => {
const url = new URL(page.url());
return (
url.origin === "https://app.slack.com" &&
url.pathname === `/client/${input.workspaceId}/${input.channelId}`
);
};
if (!target())
throw new Error(
"Slack browser did not retain the exact selected workspace/channel",
);
// These are actual user controls, not a display-name identity assertion. The
// copied member ID must equal independent official auth.test/users.info IDs.
await page
.getByRole("button", {
name: /^(Your profile|Your profile and preferences|Profile and preferences)$/i,
})
.click();
await page
.getByRole("menuitem", { name: /^(Profile|View profile)$/i })
.click();
const panel = page
.locator('[data-qa="flexpane"], [role="complementary"], [role="dialog"]')
.filter({
has: page.getByRole("button", { name: /^More(?: actions)?$/i }),
});
if ((await panel.count()) !== 1)
throw new Error(
"Slack own-profile controls are not uniquely inspectable",
);
await panel.getByRole("button", { name: /^More(?: actions)?$/i }).click();
await page.evaluate(() => navigator.clipboard.writeText(""));
await page.getByRole("menuitem", { name: /^Copy member ID$/i }).click();
const actualUserId = await page.evaluate(() =>
navigator.clipboard.readText(),
);
if (actualUserId !== input.senderUserId || !target())
throw new Error(
"Actual signed-in Slack human does not match selected provider identity",
);
await page.keyboard.press("Escape");
const composer = page.getByRole("textbox", {
name: new RegExp(`^Message #?${input.channelName}$`, "i"),
});
if ((await composer.count()) !== 1)
throw new Error(
"Slack named channel composer is unavailable or ambiguous",
);
if ((await composer.innerText()).trim())
throw new Error(
"Slack composer has an existing draft; finite fixture will not overwrite it",
);
const command = `${input.command} connect`;
await composer.fill(command);
if ((await composer.innerText()).trim() !== command || !target())
throw new Error(
"Slack composer differs from the exact saved app connect command",
);
await composer.press("Enter");
await composer.filter({ hasText: command }).waitFor({ state: "hidden" });
return {
processIsolation: true,
testRunnerAttached: false,
tracingEnabled: false,
videoEnabled: false,
verifiedWorkspaceId: input.workspaceId,
verifiedHumanId: actualUserId,
commandSubmitted: true,
};
} finally {
await browser.close();
}
}
main()
.then((receipt) => process.stdout.write(JSON.stringify(receipt)))
.catch(() => {
process.stderr.write(
"Private Slack human operation failed; details withheld\n",
);
process.exitCode = 1;
});
@@ -0,0 +1,122 @@
import { mkdtemp, writeFile, rm } from "node:fs/promises";
import { tmpdir } from "node:os";
import path from "node:path";
import { fileURLToPath } from "node:url";
import { execFile } from "node:child_process";
import { promisify } from "node:util";
import { chromium } from "@playwright/test";
import {
validSlackHumanConnect,
validSlackHumanReceipt,
type SlackHumanConnectInput,
type SlackHumanConnectReceipt,
} from "./external-slack-human-contract.js";
/** This finite child performs one genuine named Slack human command. It neither
* synthesizes a Slack event nor qualifies Paperclip's resulting connection. */
export async function privateSlackHumanConnect(
input: Omit<SlackHumanConnectInput, "session"> & { sessionJson: string },
privateSecrets: string[],
) {
if (
!input.sessionJson ||
Buffer.byteLength(input.sessionJson) > 2 * 1024 * 1024
)
throw new Error(
"Setup blocker: bounded private selected Slack human browser session is required",
);
let session: SlackHumanConnectInput["session"];
try {
session = JSON.parse(input.sessionJson);
} catch {
throw new Error(
"Setup blocker: selected Slack browser state is not valid private JSON",
);
}
privateSecrets.push(input.sessionJson);
const request = {
workspaceId: input.workspaceId,
channelId: input.channelId,
channelName: input.channelName,
senderUserId: input.senderUserId,
command: input.command,
session,
};
if (!validSlackHumanConnect(request))
throw new Error(
"Setup blocker: selected private Slack session/command is outside the designated official human workspace contract",
);
for (const cookie of session?.cookies ?? [])
if (typeof cookie.value === "string" && cookie.value)
privateSecrets.push(cookie.value);
for (const origin of session?.origins ?? [])
for (const item of origin.localStorage ?? [])
if (typeof item.value === "string" && item.value)
privateSecrets.push(item.value);
const directory = await mkdtemp(
path.join(tmpdir(), "paperclip-slack-human-"),
);
try {
const channel = process.env.PAPERCLIP_PLAYWRIGHT_CHANNEL,
requestFile = path.join(directory, "request.json");
await writeFile(
requestFile,
JSON.stringify({
...request,
executablePath:
process.env.PAPERCLIP_RUNNER_E2E_CHROMIUM_EXECUTABLE ??
(channel ? undefined : chromium.executablePath()),
channel,
}),
{ mode: 0o600 },
);
let output: string;
try {
output = (
await promisify(execFile)(
process.execPath,
[
"--import",
fileURLToPath(
new URL(
"../../../server/node_modules/tsx/dist/loader.mjs",
import.meta.url,
),
),
fileURLToPath(
new URL("./external-slack-human-worker.ts", import.meta.url),
),
requestFile,
],
{
timeout: 120000,
maxBuffer: 8192,
env: {
PATH: process.env.PATH ?? "",
HOME: directory,
TMPDIR: directory,
NODE_ENV: "test",
DO_NOT_TRACK: "1",
},
},
)
).stdout;
} catch {
throw new Error(
"Setup blocker: genuine Slack human browser session/control/member identity did not complete the finite connect command; private child diagnostics withheld",
);
}
let receipt: unknown;
try {
receipt = JSON.parse(output);
} catch {
throw new Error("Slack human command returned an invalid closed receipt");
}
if (!validSlackHumanReceipt(receipt, request))
throw new Error(
"Slack human command did not prove the exact actual workspace/person",
);
return receipt as SlackHumanConnectReceipt;
} finally {
await rm(directory, { recursive: true, force: true });
}
}
@@ -50,3 +50,16 @@ it("requires actual preview company, endpoint, Slack provider and independently
])
expect(matchingSlackIdentityPreview(wrong, companyId, input)).toBe(false);
});
import { validSlackWizardIdentityInput } from "./external-slack-identity-contract.js";
it("restricts normal wizard continuation to the same real identity and exact saved Slack setup", () => {
const setup = `http://127.0.0.1:3100/ACORN/apps/chat/connect?provider=slack&purpose=chat&resume=${input.endpointId}`;
expect(validSlackWizardIdentityInput(companyId, input, setup)).toBe(true);
for (const wrong of [
setup + "&stage=test",
setup.replace(input.endpointId, companyId),
setup.replace("slack", "discord"),
setup + "#test",
])
expect(validSlackWizardIdentityInput(companyId, input, wrong)).toBe(false);
});
@@ -55,3 +55,29 @@ export function matchingSlackIdentityPreview(
row.canConfirm !== false
);
}
export function validSlackWizardIdentityInput(
companyId: unknown,
input: SlackIdentityPrivateInput | undefined,
setupURL: string,
) {
try {
const url = new URL(setupURL);
return (
!!input &&
validSlackIdentityInput(
companyId,
input,
`${url.origin}/apps/chat/${input.endpointId}/access`,
) &&
url.pathname.endsWith("/apps/chat/connect") &&
url.searchParams.size === 3 &&
url.searchParams.get("provider") === "slack" &&
url.searchParams.get("purpose") === "chat" &&
url.searchParams.get("resume") === input.endpointId &&
!url.hash
);
} catch {
return false;
}
}
@@ -13,3 +13,13 @@ describe("genuine Slack-signature preserving disposable relay",()=>{
]){const i=signed();mutate(i);expect(verifiedSlackRelayRequest(i)).toBe(false);}
});
});
import {slackHumanConnectPayload} from "./external-slack-relay.js";
it("binds a genuine signed form's connect meaning to the exact selected human/workspace/channel/command",()=>{
const expected={workspaceId:"T123",senderUserId:"U123",channelId:"C123",command:"/owned-agent"};
const payload={team_id:"T123",user_id:"U123",channel_id:"C123",command:"/owned-agent",text:"connect"};
const body=(value:typeof payload)=>Buffer.from(new URLSearchParams(value).toString());
expect(slackHumanConnectPayload(body(payload),expected)).toBe(true);
for(const wrong of [{...payload,user_id:"UOTHER"},{...payload,team_id:"TOTHER"},{...payload,channel_id:"COTHER"},{...payload,command:"/unrelated"},{...payload,text:"delete"}])expect(slackHumanConnectPayload(body(wrong),expected)).toBe(false);
expect(slackHumanConnectPayload(Buffer.from(new URLSearchParams(payload).toString()+"&user_id=UOTHER"),expected)).toBe(false);
});
@@ -14,10 +14,14 @@ export function verifiedSlackRelayRequest(input:{pathname:string;expectedPath:st
* emulator. Only genuine Slack-signed bytes for one actual endpoint are
* forwarded. The one captured canonical delivery stays private in this closure
* and may be replayed once to prove product idempotency. */
export function slackHumanConnectPayload(body:Buffer,expected:{workspaceId:string;senderUserId:string;channelId:string;command:string}){
const form=new URLSearchParams(body.toString("utf8"));
return ["team_id","user_id","channel_id","command","text"].every(key=>form.getAll(key).length===1)&&form.get("team_id")===expected.workspaceId&&form.get("user_id")===expected.senderUserId&&form.get("channel_id")===expected.channelId&&form.get("command")===expected.command&&form.get("text")==="connect";
}
export async function startSlackRelay(input:{port:number;endpointPublicId:string;baseURL:string;signingSecret:string;workspaceId:string;senderUserId:string;channelId:string}){
if(!Number.isInteger(input.port)||input.port<1024||input.port>65535||!/^[a-zA-Z0-9_-]+$/.test(input.endpointPublicId)||!input.signingSecret)throw new Error("Setup blocker: a designated disposable Slack webhook relay port and signing reference are required");
const target=new URL(input.baseURL);if(target.origin!==input.baseURL||target.hostname!=="127.0.0.1"||target.protocol!=="http:")throw new Error("Slack relay target must be the isolated loopback application");
const expectedPath=`/api/chat-webhooks/${input.endpointPublicId}/slack`;let capture:Captured|undefined,expectedQuery:string|undefined,expectedThread:string|undefined,replayed=false;let forwarded=0,attempts=0;
const expectedPath=`/api/chat-webhooks/${input.endpointPublicId}/slack`;let capture:Captured|undefined,expectedQuery:string|undefined,expectedThread:string|undefined,replayed=false;let forwarded=0,attempts=0;let humanCommand:string|undefined,humanConnectCaptured=false;
async function forward(body:Buffer,headers:Record<string,string>,retry=false){
if(++attempts>80)throw new Error("Slack relay exceeded its bounded callback count");
const response=await fetch(new URL(expectedPath,target),{method:"POST",redirect:"error",signal:AbortSignal.timeout(20000),headers:{...headers,...retry?{"x-slack-retry-num":"1","x-slack-retry-reason":"http_timeout"}:{}},body:new Uint8Array(body)});
@@ -29,6 +33,10 @@ export async function startSlackRelay(input:{port:number;endpointPublicId:string
const chunks:Buffer[]=[];let size=0;for await(const chunk of request){size+=chunk.length;if(size>2*1024*1024)throw new Error("Webhook exceeds bounds");chunks.push(Buffer.from(chunk));}const body=Buffer.concat(chunks);
if(!verifiedSlackRelayRequest({pathname:request.url,expectedPath,body,headers:request.headers,signingSecret:input.signingSecret})){response.writeHead(403);response.end();return;}
const headers={"content-type":request.headers["content-type"]??"application/json","x-slack-signature":String(request.headers["x-slack-signature"]),"x-slack-request-timestamp":String(request.headers["x-slack-request-timestamp"])};
if(headers["content-type"].split(";")[0]?.trim()==="application/x-www-form-urlencoded"){
if(humanCommand&&slackHumanConnectPayload(body,{workspaceId:input.workspaceId,senderUserId:input.senderUserId,channelId:input.channelId,command:humanCommand}))humanConnectCaptured=true;
const result=await forward(body,headers);forwarded++;response.writeHead(result.status,{"content-type":result.contentType});response.end(result.bytes);return;
}
const payload=JSON.parse(body.toString("utf8")) as {type?:string;team_id?:string;event_id?:string;event?:{type?:string;user?:string;channel?:string;thread_ts?:string;ts?:string;text?:string}};
if(payload.type==="event_callback"&&payload.team_id!==input.workspaceId){response.writeHead(403);response.end();return;}
const event=payload.event;if(expectedQuery&&payload.type==="event_callback"&&typeof payload.event_id==="string"&&event?.user===input.senderUserId&&event.channel===input.channelId&&event.text===expectedQuery&&(event.thread_ts??event.ts)===expectedThread){
@@ -38,8 +46,10 @@ export async function startSlackRelay(input:{port:number;endpointPublicId:string
}catch{response.writeHead(502);response.end("Disposable Slack relay failed; private details withheld");}
});
server.listen(input.port,"127.0.0.1");await once(server,"listening");
return {expectedPath,expectCanonical(query:string,threadId:string){if(!query||capture||!/^\d+\.\d{6}$/.test(threadId))throw new Error("Slack relay capture has an invalid finite canonical phase");expectedQuery=query;expectedThread=threadId;},
receipt(){return {forwarded,canonicalDeliveryCaptured:!!capture,duplicateReplayed:replayed};},
return {expectedPath,
expectHumanConnect(command:string){if(humanCommand||!/^\/[a-zA-Z0-9_-]{1,100}$/.test(command))throw new Error("Exactly one finite saved Slack human connect command is allowed");humanCommand=command;},
expectCanonical(query:string,threadId:string){if(!query||capture||!/^\d+\.\d{6}$/.test(threadId))throw new Error("Slack relay capture has an invalid finite canonical phase");expectedQuery=query;expectedThread=threadId;},
receipt(){return {forwarded,canonicalDeliveryCaptured:!!capture,duplicateReplayed:replayed,humanConnectCaptured};},
async replayCanonicalOnce(){if(!capture||replayed)throw new Error("Exactly one genuine captured canonical Slack delivery may be replayed");replayed=true;const result=await forward(capture.body,capture.headers,true);return {status:result.status,providerEventCaptured:true};},
nextFilePhase(){if(!capture)throw new Error("Cannot advance before a genuine canonical Slack event");capture=undefined;expectedQuery=undefined;expectedThread=undefined;replayed=false;},
async close(){capture=undefined;expectedQuery=undefined;server.closeAllConnections();await new Promise<void>((resolve,reject)=>server.close(error=>error?reject(new Error("Slack relay cleanup failed")):resolve()));},
@@ -0,0 +1,28 @@
import { expect, it } from "vitest";
import { validSlackSetupPrivate } from "./external-slack-setup-private-contract.js";
it("restricts private Slack credential entry to the exact owned existing-app draft", () => {
const company = "11111111-1111-4111-8111-111111111111",
input = {
endpointId: "22222222-2222-4222-8222-222222222222",
agentId: "33333333-3333-4333-8333-333333333333",
workspaceId: "T123",
signingSecret: "synthetic-private-signing",
},
url = `http://127.0.0.1:3100/ACORN/apps/chat/connect?provider=slack&purpose=chat&resume=${input.endpointId}&stage=credentials`;
expect(validSlackSetupPrivate(company, input, url)).toBe(true);
for (const wrong of [
url.replace("slack", "discord"),
url.replace(input.endpointId, company),
url + "&reconnect=1",
url.replace("credentials", "test"),
url + "#other",
])
expect(validSlackSetupPrivate(company, input, wrong)).toBe(false);
expect(
validSlackSetupPrivate(
company,
{ ...input, signingSecret: "bad\nsecret" },
url,
),
).toBe(false);
});
@@ -0,0 +1,36 @@
export interface SlackSetupPrivateInput {
endpointId: string;
agentId: string;
workspaceId: string;
signingSecret: string;
}
export function validSlackSetupPrivate(
companyId: unknown,
input: SlackSetupPrivateInput | undefined,
setupURL: string,
) {
try {
const url = new URL(setupURL);
return (
typeof companyId === "string" &&
/^[a-f0-9-]{36}$/i.test(companyId) &&
!!input &&
[input.endpointId, input.agentId].every((id) =>
/^[a-f0-9-]{36}$/i.test(id),
) &&
/^T[A-Z0-9]+$/.test(input.workspaceId) &&
!!input.signingSecret &&
input.signingSecret.length <= 8192 &&
!/[\r\n]/.test(input.signingSecret) &&
url.pathname.endsWith("/apps/chat/connect") &&
url.searchParams.get("provider") === "slack" &&
url.searchParams.get("purpose") === "chat" &&
url.searchParams.get("resume") === input.endpointId &&
url.searchParams.get("stage") === "credentials" &&
url.searchParams.size === 4 &&
!url.hash
);
} catch {
return false;
}
}
@@ -0,0 +1,262 @@
import { expect, it } from "vitest";
import { gradeSlackSetup, slackSetupGuidance } from "./external-slack-setup.js";
function valid() {
const run = (id: string, agentId: string, issueId: string) => ({
id,
agentId,
companyId: "company",
runtimeMode: "native",
status: "succeeded",
contextSnapshot: { issueId },
}),
query =
"Please say your exact Paperclip agent name and confirm you can reply in this same Slack conversation. Do not start a separate task.";
const source = {
query,
endpointId: "endpoint",
conversationId: "conversation",
issueId: "slack-source",
humanCommentId: "human",
linkedPaperclipUserId: "person",
providerCalls: 1,
provider: {
workspaceId: "T123",
channelId: "C123",
senderUserId: "U123",
threadId: "1000000000.000001",
canonicalMessageId: "1000000000.000003",
},
};
return {
scenarioId: "connection-setup.channel-prerequisites",
companyId: "company",
leadId: "ceo",
origin: "http://127.0.0.1:3100",
reply: "Slack is ready; you can talk to Morgan there.",
state: {
represented: { id: "morgan", name: "Morgan" },
noEndpointsBefore: true,
providerEnabledBefore: true,
initialRunId: "initial",
canonicalIssueId: "canonical",
agentEndpointCreateStatus: 403,
createdByHumanUi: true,
workspaceId: "T123",
senderUserId: "U123",
botUserId: "UBOT",
currentHumanId: "person",
},
phaseReplies: [
{
runId: "initial",
body: "You must connect Slack through supported setup and select Morgan. Confirm your account before chatting.",
},
{ runId: "final", body: "Slack is ready; you can talk to Morgan there." },
],
runs: [
run("initial", "ceo", "canonical"),
run("probe", "morgan", "slack-source"),
run("final", "ceo", "canonical"),
],
humanQueries: [
"Let me talk to Morgan from Slack.",
"Let me talk to Morgan from Slack.",
],
tasks: [],
projects: [],
goals: [],
documents: [],
revisions: [],
comments: [],
activity: [],
links: [],
connections: [],
profiles: [],
secrets: [],
endpoints: [{ id: "endpoint" }],
interactions: [],
slackSetup: {
endpoint: {
id: "endpoint",
companyId: "company",
provider: "slack",
assignedAgentId: "morgan",
providerAccountId: "T123",
status: "active",
setup: {
step: "complete",
webhookVerifiedAt: "2031-02-14T10:00:00Z",
testStartedAt: null,
},
activatedAt: "2031-02-14T10:02:00Z",
allowUnlinkedPeople: false,
},
credentialUi: {
endpointId: "endpoint",
slackCredentialUiVisible: true,
humanSubmittedSecurely: true,
storageClean: true,
},
identityUi: {
endpointId: "endpoint",
identityConfirmed: true,
identityUiVisible: true,
activationUiVisible: true,
endpointActive: true,
paperclipUserId: "person",
processIsolation: true,
testRunnerAttached: false,
},
humanCommand: {
commandSubmitted: true,
verifiedWorkspaceId: "T123",
verifiedHumanId: "U123",
},
signedConnect: true,
lastConnectAt: "2031-02-14T10:01:00Z",
principalStatus: "linked",
principalUserId: "person",
source,
sourceBound: true,
sourceRunId: "probe",
sourceReply: {
id: "reply",
createdByRunId: "probe",
authorAgentId: "morgan",
body: "I am Morgan, replying in this same Slack conversation.",
},
messages: [
{ ts: "1000000000.000003", user: "U123", text: query },
{
ts: "1000000000.000004",
user: "UBOT",
text: "I am Morgan, replying in this same Slack conversation.",
},
],
},
};
}
it("requires complete normal existing-app human setup plus actual post-activation two-way native delivery", () =>
expect(gradeSlackSetup(valid()).passed).toBe(true));
it("rejects plausible credential-only, wrong-human/agent, missing callback, activation and routing successes", () => {
for (const mutate of [
(o: ReturnType<typeof valid>) => {
o.state.noEndpointsBefore = false;
},
(o: ReturnType<typeof valid>) => {
o.phaseReplies[0]!.body = "Morgan's Slack is ready; setup is complete.";
},
(o: ReturnType<typeof valid>) => {
o.phaseReplies[0]!.body =
"Connect Slack and select the CEO instead of Morgan.";
},
(o: ReturnType<typeof valid>) => {
o.state.agentEndpointCreateStatus = 201;
},
(o: ReturnType<typeof valid>) => {
o.state.createdByHumanUi = false;
},
(o: ReturnType<typeof valid>) => {
o.slackSetup.endpoint.companyId = "other";
},
(o: ReturnType<typeof valid>) => {
o.slackSetup.endpoint.assignedAgentId = "ceo";
},
(o: ReturnType<typeof valid>) => {
o.slackSetup.endpoint.status = "verifying";
},
(o: ReturnType<typeof valid>) => {
o.slackSetup.endpoint.setup.webhookVerifiedAt = "";
},
(o: ReturnType<typeof valid>) => {
o.slackSetup.credentialUi.storageClean = false;
},
(o: ReturnType<typeof valid>) => {
o.slackSetup.identityUi.activationUiVisible = false;
},
(o: ReturnType<typeof valid>) => {
o.slackSetup.identityUi.endpointActive = false;
},
(o: ReturnType<typeof valid>) => {
o.slackSetup.signedConnect = false;
},
(o: ReturnType<typeof valid>) => {
o.slackSetup.lastConnectAt = "2031-02-14T10:03:00Z";
},
(o: ReturnType<typeof valid>) => {
o.slackSetup.humanCommand.verifiedHumanId = "other";
},
(o: ReturnType<typeof valid>) => {
o.slackSetup.principalUserId = "other";
},
(o: ReturnType<typeof valid>) => {
o.slackSetup.sourceBound = false;
},
(o: ReturnType<typeof valid>) => {
o.slackSetup.sourceReply.createdByRunId = "previous";
},
(o: ReturnType<typeof valid>) => {
o.slackSetup.messages = o.slackSetup.messages.slice(0, 1);
},
(o: ReturnType<typeof valid>) => {
o.runs[1]!.agentId = "ceo";
},
(o: ReturnType<typeof valid>) => {
o.runs[2]!.contextSnapshot.issueId = "unrelated-final";
},
(o: ReturnType<typeof valid>) => {
o.endpoints.push({ id: "duplicate" });
},
(o: ReturnType<typeof valid>) => {
o.phaseReplies[1]!.body =
"Slack is not connected yet, and Morgan cannot reply.";
},
]) {
const o = valid();
mutate(o);
expect(gradeSlackSetup(o).passed).toBe(false);
}
expect(gradeSlackSetup(undefined).passed).toBe(false);
for (const key of [
"endpoint",
"credentialUi",
"identityUi",
"humanCommand",
"sourceReply",
"messages",
]) {
const o = valid();
delete (o.slackSetup as Record<string, unknown>)[key];
expect(gradeSlackSetup(o).passed).toBe(false);
}
});
it("accepts truthful natural prerequisites and rejects premature or wrong represented-agent guidance", () => {
expect(
slackSetupGuidance(
"Slack isn't connected yet. You must use supported setup to connect Morgan and confirm your human identity.",
"Morgan",
true,
),
).toBe(true);
expect(
slackSetupGuidance(
"You can set up Slack for Morgan through the supported human authorization page.",
"Morgan",
true,
),
).toBe(true);
expect(
slackSetupGuidance(
"Slack is ready. You can configure Morgan later.",
"Morgan",
true,
),
).toBe(false);
expect(
slackSetupGuidance(
"Select the CEO to connect Slack; Morgan will use it later.",
"Morgan",
true,
),
).toBe(false);
});
@@ -0,0 +1,711 @@
import { expect } from "@playwright/test";
import type {
ChatCatalogContext,
ChatSlackIngressReceipt,
} from "../chat-catalog-contract.js";
import type {
ChatEndpoint,
ChatConversation,
ExternalChannelBindingSummary,
} from "../../../packages/shared/src/types/chat-channels.js";
import type { IssueComment } from "../../../packages/shared/src/types/issue.js";
import { pollUntil } from "../api.js";
import { sendChatMessage } from "../chat-flow.js";
import { slackIngressReceiptMatches } from "../chat-slack-ingress.js";
import {
definition,
checks,
externalObservation,
observeExternal,
root,
verifyRenderedReply,
type ExternalObservation,
} from "./external-support.js";
import {
openStandingChat,
identityWorker,
waitForReply,
probeAgentPermission,
} from "./identity-support.js";
import { selectedExternalBinding } from "./external-fixtures.js";
import {
slackProvider,
assertSlackFixture,
slackCompanionFields,
} from "./external-slack-provider.js";
import {
prepareSlackFixture,
type SlackMessage,
} from "./external-slack-fixture.js";
import type { Entity } from "./internal-support.js";
export const slackSetupFields = [
...slackCompanionFields,
"slackBrowserSessionEnv",
] as const;
type Fixture = Awaited<ReturnType<typeof prepareSlackFixture>>;
interface Local {
fixture?: Fixture;
createdEndpointId?: string;
represented: Entity;
source?: ChatSlackIngressReceipt;
sourceRunId?: string;
sourceReply?: IssueComment;
messages?: SlackMessage[];
}
const fixtures = new WeakMap<ChatCatalogContext, Local>();
function owned(ctx: ChatCatalogContext) {
const local = fixtures.get(ctx);
if (!local) throw new Error("Slack setup context missing");
return local;
}
interface Observation extends ExternalObservation {
slackSetup: {
endpoint?: ChatEndpoint;
source?: ChatSlackIngressReceipt;
sourceBound: boolean;
sourceRunId?: string;
sourceReply?: IssueComment;
messages: SlackMessage[];
credentialUi?: Fixture["credentialUiReceipt"];
identityUi?: Fixture["identityUiReceipt"];
humanCommand?: Fixture["humanCommandReceipt"];
signedConnect: boolean;
lastConnectAt?: string | null;
principalUserId?: string | null;
principalStatus?: string;
};
}
function affirmativeReady(reply: string) {
return reply
.split(/[.!?;\n]+|\bbut\b/i)
.some(
(clause) =>
/ready|working|connected|configured|(?:is|are|now|already|has been|have been)\s+set up|(?:setup|connection)\s+(?:is\s+)?complete|can (?:now )?(?:message|talk|chat)/i.test(
clause,
) &&
!/^\s*(?:please\s+)?(?:connect|select|click|continue|set up|get)\b/i.test(
clause,
) &&
!/\b(?:not|isn['’]t|cannot|can't|never|before|once|after|until|pending|will|would|need|requires?|must)\b/i.test(
clause,
),
);
}
export function slackSetupGuidance(
reply: string,
represented: string,
forMorgan: boolean,
) {
return (
/slack/i.test(reply) &&
/connect|setup|set up|authorize|authorization|human|sign.in/i.test(reply) &&
!affirmativeReady(reply) &&
(!forMorgan || /Morgan/i.test(reply)) &&
!(
forMorgan && /connect (?:your|the) CEO|select (?:the )?CEO/i.test(reply)
) &&
(!/select|represent/i.test(reply) ||
new RegExp(represented.replace(/[.*+?^${}()|[\]\\]/g, "\\$&"), "i").test(
reply,
) ||
!forMorgan)
);
}
export function gradeSlackSetup(value: unknown) {
const c = checks(),
o = externalObservation(value) as Observation | undefined,
t = o?.slackSetup;
c.add(
"actual-human-Slack-setup-evidence",
!!o && !!t && Array.isArray(t.messages),
"Actual saved endpoint, private credential/identity/wizard receipts, genuine signed connect and two-way native source are required.",
);
if (!o || !t || !Array.isArray(t.messages)) return c.result();
const first = o.phaseReplies.find(
(row) => row.runId === o.state.initialRunId,
),
last = o.phaseReplies
.filter((row) => row.runId !== o.state.initialRunId)
.at(-1),
e = t.endpoint,
s = t.source,
represented = o.state.represented as Entity | undefined,
run = o.runs.find((row) => row.id === t.sourceRunId);
c.add(
"unconfigured-context-and-correct-human-handoff",
o.state.noEndpointsBefore === true &&
o.state.providerEnabledBefore === true &&
!!represented &&
!!first &&
slackSetupGuidance(
first.body,
String(represented.name),
o.scenarioId === "connection-setup.channel-prerequisites",
) &&
o.state.agentEndpointCreateStatus === 403 &&
o.state.createdByHumanUi === true,
"Slack starts available without an endpoint; chat directs the correct represented agent and actual human-created existing-app setup instead of claiming premature readiness.",
);
c.add(
"real-private-credential-form-and-normal-wizard-activation",
!!e &&
e.companyId === o.companyId &&
e.provider === "slack" &&
e.assignedAgentId === represented?.id &&
e.providerAccountId === o.state.workspaceId &&
e.status === "active" &&
e.setup.step === "complete" &&
!!e.setup.webhookVerifiedAt &&
e.allowUnlinkedPeople === false &&
t.credentialUi?.endpointId === e.id &&
t.credentialUi.slackCredentialUiVisible === true &&
t.credentialUi.humanSubmittedSecurely === true &&
t.credentialUi.storageClean === true &&
t.identityUi?.endpointId === e.id &&
t.identityUi.identityConfirmed === true &&
t.identityUi.identityUiVisible === true &&
t.identityUi.activationUiVisible === true &&
t.identityUi.endpointActive === true,
"The actual protected existing-app form saves credentials, real callback verification completes and the human uses normal identity/Continue/Done controls; activation is independently saved rather than inferred from credential storage.",
);
c.add(
"genuine-Slack-human-command-and-same-person-link",
t.signedConnect === true &&
t.humanCommand?.commandSubmitted === true &&
t.humanCommand.verifiedWorkspaceId === o.state.workspaceId &&
t.humanCommand.verifiedHumanId === o.state.senderUserId &&
!!t.lastConnectAt &&
!!e?.activatedAt &&
Date.parse(t.lastConnectAt!) <= Date.parse(e.activatedAt!) &&
t.principalStatus === "linked" &&
t.principalUserId === o.state.currentHumanId &&
t.identityUi?.paperclipUserId === o.state.currentHumanId &&
t.identityUi?.processIsolation === true &&
t.identityUi?.testRunnerAttached === false,
"Only a real named Slack browser human, independently copied member ID, genuinely provider-signed connect command and persisted processed principal receipt authorize the same actual signed-in Paperclip person.",
);
c.add(
"two-way-message-after-actual-activation",
!!s &&
t.sourceBound &&
!!run &&
run.companyId === o.companyId &&
run.agentId === represented?.id &&
run.contextSnapshot?.issueId === s.issueId &&
run.runtimeMode === "native" &&
run.status === "succeeded" &&
t.sourceReply?.createdByRunId === run.id &&
t.sourceReply.authorAgentId === represented?.id &&
!!represented?.name &&
t.sourceReply.body.includes(represented.name) &&
t.messages.some(
(message) =>
message.ts === s.provider.canonicalMessageId &&
message.user === o.state.senderUserId &&
message.text === s.query,
) &&
t.messages.some(
(message) =>
message.user === o.state.botUserId &&
message.ts > s.provider.canonicalMessageId &&
(message.text ?? "").includes(represented.name!),
),
"An actual subsequent synthetic human message persists under the right company/sender/thread; the genuine represented-agent native reply is independently read back from that same provider thread.",
);
c.add(
"separate-truthful-ready-confirmation-and-no-extra-work",
!!last &&
affirmativeReady(last.body) &&
/slack/i.test(last.body) &&
(o.scenarioId !== "connection-setup.channel-prerequisites" ||
/Morgan/i.test(last.body)) &&
o.runs.length === 3 &&
o.runs.every(
(row) =>
row.companyId === o.companyId &&
row.runtimeMode === "native" &&
row.status === "succeeded" &&
(row.id === t.sourceRunId ||
(row.agentId === o.leadId &&
row.contextSnapshot?.issueId === o.state.canonicalIssueId &&
[o.state.initialRunId, last.runId].includes(row.id))),
) &&
o.humanQueries.length === 2 &&
o.endpoints.length === 1,
"Only the two canonical Agent Chat phases and separately attributed two-way Slack probe run exist; the final ready claim follows actual delivery and does not manufacture an additional endpoint.",
);
return c.result();
}
function setupCase(
id: "chat-channels.connect-slack" | "connection-setup.channel-prerequisites",
) {
return definition(
id,
async (ctx) => {
const prior = await ctx.input.api.get<{ enableChatConnectors?: boolean }>(
"/api/instance/settings/experimental",
);
await ctx.input.api.patch("/api/instance/settings/experimental", {
enableChatConnectors: true,
});
const represented =
id === "connection-setup.channel-prerequisites"
? await identityWorker(ctx, "Morgan")
: await ctx.input.api.get<Entity>(
`/api/agents/${ctx.input.fixtures.agent.id}`,
);
const endpoints = await ctx.input.api.get<ChatEndpoint[]>(
`${root(ctx)}/chat-endpoints`,
);
if (endpoints.length)
throw new Error(
"Slack setup requires an initially unconfigured disposable company",
);
fixtures.set(ctx, { represented });
const chat = await openStandingChat(ctx);
await ctx.input.api.patch(`/api/issues/${chat.id}`, {
description: `Slack chat is enabled, but this company has no chat endpoint or linked sender. The requested represented agent is ${represented.name} (${represented.id}); that agent already has an eligible runtime. The signed-in human must connect their authorized disposable existing Slack app through supported chat setup.`,
});
return {
represented,
noEndpointsBefore: true,
providerEnabledBefore: true,
canonicalIssueId: chat.id,
priorChatConnectors: prior.enableChatConnectors === true,
};
},
gradeSlackSetup,
{
authentication: "human-session",
expectedRunCount: 3,
minimumRunCount: 3,
expectedHumanMessageCount: 2,
turnTimeoutMs: 120000,
attemptTimeoutMs: 900000,
externalInput: { provider: "slack", fields: slackSetupFields },
sourceFiles: [
"chat-catalog-domains/external-slack-setup.ts",
"chat-catalog-domains/external-slack-fixture.ts",
"chat-catalog-domains/external-slack-provider.ts",
"chat-catalog-domains/external-slack-relay.ts",
"chat-catalog-domains/external-slack-human.ts",
"chat-catalog-domains/external-slack-human-worker.ts",
"chat-catalog-domains/external-slack-human-contract.ts",
"chat-catalog-domains/external-private-browser.ts",
"chat-catalog-domains/external-private-browser-worker.ts",
"chat-catalog-domains/external-slack-setup-private-contract.ts",
"chat-catalog-domains/external-slack-identity-contract.ts",
"chat-catalog-domains/external-fixtures.ts",
"chat-catalog-domains/external-support.ts",
"chat-catalog-domains/internal-support.ts",
"chat-catalog-domains/identity-support.ts",
"chat-slack-ingress.ts",
"../../ui/src/pages/apps/chat/ChatEndpointSetup.tsx",
"../../ui/src/pages/apps/chat/SlackIdentityStep.tsx",
"../../ui/src/pages/apps/chat/slack/OwnSlackAppSetup.tsx",
"../../server/src/services/chat-channels/setup-probes.ts",
"../../server/src/services/chat-channels/slack/commands.ts",
"../../server/src/services/chat-channels/slack/identity-links.ts",
"../../server/src/services/chat-channels/identity-links.ts",
"../../server/src/services/chat-channels.ts",
`chat-scenarios/${id.split(".")[0]}.yaml`,
],
async verifySetup(ctx, state) {
const endpoints = await ctx.input.api.get<ChatEndpoint[]>(
`${root(ctx)}/chat-endpoints`,
),
enabled = await ctx.input.api.get<{ enableChatConnectors?: boolean }>(
"/api/instance/settings/experimental",
);
const ready =
endpoints.length === 0 &&
enabled.enableChatConnectors === true &&
(await ctx.allRuns()).length === 0;
return {
passed: ready,
checks: [
{
id: "real-unconfigured-Slack-context",
passed: ready,
detail:
"Actual available provider and company endpoint inventory have no configured route or native work.",
},
],
};
},
async act(ctx, state) {
await waitForReply(ctx);
const local = owned(ctx),
initial = await observeExternal(ctx, state),
first = initial.phaseReplies.at(-1);
if (
!first ||
!slackSetupGuidance(
first.body,
String(local.represented.name),
id === "connection-setup.channel-prerequisites",
)
)
throw new Error(
"Wrong represented-agent guidance or premature ready claim cannot be rescued by correct human setup",
);
state.initialRunId = first.runId;
if (
(
await ctx.input.api.get<ChatEndpoint[]>(
`${root(ctx)}/chat-endpoints`,
)
).length
)
throw new Error(
"An endpoint appeared before the required human creation",
);
const href = initial.links.find((link) => {
try {
const u = new URL(link, initial.origin);
return (
u.origin === initial.origin &&
u.pathname ===
`/${ctx.input.fixtures.company.issuePrefix}/apps/chat/connect` &&
u.searchParams.get("provider") === "slack" &&
(!u.searchParams.has("agentId") ||
u.searchParams.get("agentId") === local.represented.id) &&
!u.searchParams.has("resume")
);
} catch {
return false;
}
});
if (!href)
throw new Error(
"Slack human setup must be directed to its actual supported chat setup and requested agent",
);
state.agentEndpointCreateStatus = await probeAgentPermission(
ctx,
`${root(ctx)}/chat-endpoints`,
"POST",
{ provider: "slack", assignedAgentId: local.represented.id },
);
const binding = selectedExternalBinding(ctx);
assertSlackFixture(binding);
const provider = slackProvider(ctx, binding),
manifest = (
await provider.call<{
ok: true;
manifest: {
features?: { slash_commands?: Array<{ command: string }> };
};
}>("configuration", "apps.manifest.export", {
app_id: binding.prerequisites.appId,
})
).manifest;
const commands =
manifest.features?.slash_commands?.map((row) => row.command) ?? [];
if (commands.length !== 1 || !/^\/[a-z0-9_-]{1,31}$/.test(commands[0]!))
throw new Error(
"Setup blocker: the designated existing app must expose one supported saved slash command",
);
const page = await ctx.input.page.context().newPage();
try {
await page.goto(href, { waitUntil: "domcontentloaded" });
const purpose = page.getByRole("button", {
name: /^Chat with an agent/,
});
if (await purpose.isVisible()) await purpose.click();
await page
.getByRole("heading", {
name: "Which agent do you want to chat with?",
exact: true,
})
.waitFor();
const agentButton = page.getByRole("button", {
name: String(local.represented.name),
exact: true,
});
if (!(await agentButton.isVisible())) {
await page
.getByRole("button", {
name: "Choose an active agent",
exact: true,
})
.click();
await page
.getByRole("button", {
name: `Select ${local.represented.name}`,
exact: true,
})
.click();
}
await page
.locator("summary")
.filter({ hasText: /^Advanced$/ })
.click();
await page
.getByLabel("Slash command", { exact: true })
.fill(commands[0]!);
const created = page.waitForResponse(
(response) =>
new URL(response.url()).pathname ===
`${root(ctx)}/chat-endpoints` &&
response.request().method() === "POST" &&
response.status() === 201,
);
const own = page.getByRole("button", {
name: "Use your own app",
exact: true,
});
if (await own.isVisible()) await own.click();
else
await page
.getByRole("button", { name: "Continue", exact: true })
.click();
const response = await created,
endpoint = (await response.json()) as ChatEndpoint;
local.createdEndpointId = endpoint.id;
if (
endpoint.companyId !== ctx.input.fixtures.company.id ||
endpoint.provider !== "slack" ||
endpoint.assignedAgentId !== local.represented.id
)
throw new Error(
"Actual UI created another represented-agent/company endpoint",
);
const existing = page.getByRole("button", {
name: "Use an existing app",
exact: true,
});
await page
.locator("details")
.filter({ has: existing })
.locator("summary")
.click();
const saved = page.waitForResponse(
(response) =>
new URL(response.url()).pathname ===
`/api/chat-endpoints/${endpoint.id}` &&
response.request().method() === "PATCH" &&
response.ok(),
);
await existing.click();
if (
(await (await saved).json()).setup?.slackSetupMethod !== "existing"
)
throw new Error("Actual existing-app choice did not persist");
state.createdByHumanUi = true;
} finally {
await page.close();
}
local.fixture = await prepareSlackFixture(ctx, {
normalWizard: {
endpointId: local.createdEndpointId!,
agentId: local.represented.id,
},
});
const f = local.fixture;
state.workspaceId = f.binding.prerequisites.workspaceId;
state.senderUserId = f.binding.prerequisites.senderUserId;
state.botUserId = f.binding.prerequisites.botUserId;
state.currentHumanId = (
await ctx.input.api.get<{ user: { id: string } }>(
"/api/auth/get-session",
)
).user.id;
const query =
"Please say your exact Paperclip agent name and confirm you can reply in this same Slack conversation. Do not start a separate task.";
f.relay.expectCanonical(query, f.threadId);
const start = f.provider.receipts.length;
const sent = await f.provider.call<{
ok: true;
channel: string;
ts: string;
message: { text: string; user: string; thread_ts: string };
}>("sender", "chat.postMessage", {
channel: f.binding.prerequisites.channelId,
thread_ts: f.threadId,
text: query,
unfurl_links: false,
unfurl_media: false,
});
if (
sent.channel !== f.binding.prerequisites.channelId ||
sent.message.user !== f.binding.prerequisites.senderUserId ||
sent.message.text !== query ||
sent.message.thread_ts !== f.threadId
)
throw new Error(
"Actual two-way Slack probe changed sender/thread/text",
);
await pollUntil({
label: "actual signed Slack post-activation message",
deadlineAt: Date.now() + 30000,
load: async () => f.relay.receipt(),
accept: (row) => row.canonicalDeliveryCaptured,
});
local.source = await f.resolveIngress(
query,
sent.ts,
f.provider.receipts.length - start,
);
const runs = await pollUntil({
label: "genuine represented-agent Slack two-way native",
deadlineAt: Date.now() + 150000,
intervalMs: 1000,
load: () => ctx.allRuns(),
accept: (rows) =>
rows.length === 2 &&
rows.some(
(row) =>
row.agentId === local.represented.id &&
row.contextSnapshot?.issueId === local.source!.issueId &&
row.status === "succeeded" &&
row.runtimeMode === "native",
),
reject: (rows) =>
rows.length > 2 ||
rows.some((row) =>
["failed", "cancelled", "timed_out"].includes(row.status),
)
? "Unexpected or failed real Slack setup probe native"
: undefined,
});
local.sourceRunId = runs.find(
(row) => row.contextSnapshot?.issueId === local.source!.issueId,
)!.id;
const comments = await ctx.input.api.get<IssueComment[]>(
`/api/issues/${local.source.issueId}/comments?order=asc`,
);
local.sourceReply = comments
.filter(
(row) =>
row.createdByRunId === local.sourceRunId &&
row.authorAgentId === local.represented.id,
)
.at(-1);
local.messages = await pollUntil({
label: "actual represented-agent reply back to original Slack thread",
deadlineAt: Date.now() + 60000,
intervalMs: 2000,
load: () => f.readThread(f.threadId),
accept: (rows) =>
rows.some(
(row) =>
row.user === f.binding.prerequisites.botUserId &&
row.ts > sent.ts &&
(row.text ?? "").includes(String(local.represented.name)),
),
});
await ctx.input.evidence("chat-catalog-slack-actual-human-setup.json", {
credentialUi: f.credentialUiReceipt,
identityUi: f.identityUiReceipt,
humanCommand: f.humanCommandReceipt,
signedConnect: f.relay.receipt().humanConnectCaptured,
source: local.source,
sourceRunId: local.sourceRunId,
sourceReply: local.sourceReply,
messages: local.messages,
});
await sendChatMessage(ctx.input.page, ctx.scenario.query);
},
async observe(ctx, state) {
const o = await observeExternal(ctx, state),
local = owned(ctx),
f = local.fixture;
let sourceBound = false;
let endpoint: ChatEndpoint | undefined,
principal: Awaited<ReturnType<Fixture["readPrincipal"]>> | undefined;
if (f) {
endpoint = await ctx.input.api.get<ChatEndpoint>(
`/api/chat-endpoints/${f.endpoint.id}`,
);
principal = await f.readPrincipal();
if (local.source) {
const [conversations, binding, comments] = await Promise.all([
ctx.input.api.get<ChatConversation[]>(
`/api/chat-endpoints/${f.endpoint.id}/conversations`,
),
ctx.input.api.get<ExternalChannelBindingSummary>(
`/api/issues/${local.source.issueId}/chat-binding`,
),
ctx.input.api.get<IssueComment[]>(
`/api/issues/${local.source.issueId}/comments?order=asc`,
),
]);
sourceBound = slackIngressReceiptMatches({
receipt: local.source,
query: local.source.query,
companyId: ctx.input.fixtures.company.id,
agentId: local.represented.id,
endpoint,
conversation: conversations.find(
(row) => row.id === local.source!.conversationId,
),
binding,
comment: comments.find(
(row) => row.id === local.source!.humanCommentId,
),
links: [principal],
linksEndpointId: endpoint.id,
senderIdentity: f.senderIdentity,
});
}
}
return {
...o,
slackSetup: {
endpoint,
source: local.source,
sourceBound,
sourceRunId: local.sourceRunId,
sourceReply: local.sourceReply,
messages: local.messages ?? [],
credentialUi: f?.credentialUiReceipt,
identityUi: f?.identityUiReceipt,
humanCommand: f?.humanCommandReceipt,
signedConnect: f?.relay.receipt().humanConnectCaptured === true,
lastConnectAt: principal?.lastConnectAt,
principalUserId: principal?.paperclipUserId,
principalStatus: principal?.status,
},
} satisfies Observation;
},
async verifyResult(ctx, _state, value) {
const o = value as Observation;
await verifyRenderedReply(ctx, value);
if (!o.slackSetup.endpoint)
throw new Error("Actual activated Slack endpoint missing");
const page = await ctx.input.page.context().newPage();
try {
await page.goto(
`${o.origin}/${ctx.input.fixtures.company.issuePrefix}/apps/chat/${o.slackSetup.endpoint.id}/settings`,
{ waitUntil: "domcontentloaded" },
);
await expect(page.getByRole("main")).toContainText(
String((o.state.represented as Entity).name),
);
} finally {
await page.close();
}
},
async cleanup(ctx, state) {
const local = fixtures.get(ctx);
try {
if (local?.fixture) await local.fixture.cleanup();
else if (local?.createdEndpointId)
await ctx.input.api.post(
`/api/chat-endpoints/${local.createdEndpointId}/setup`,
{ action: "remove" },
);
} finally {
fixtures.delete(ctx);
await ctx.input.api.patch("/api/instance/settings/experimental", {
enableChatConnectors: state.priorChatConnectors === true,
});
}
},
},
);
}
export const slackSetupExternalCases = [
setupCase("chat-channels.connect-slack"),
setupCase("connection-setup.channel-prerequisites"),
];