mirror of
https://github.com/paperclipai/paperclip.git
synced 2026-10-30 13:20:54 +01:00
## Thinking Path > - Paperclip is the open source app people use to manage AI agents for work > - Governed MCP access spans contracts, runtime enforcement, adapters, UI surfaces, and operator verification > - The parity reference PR #9534 is too large for effective automated or human review > - The feature therefore needs a linear stack whose individual diffs stay below the 100-file review limit > - This pull request is split 6/8 and focuses on UI API, shared components, and Tools/Profile surfaces > - The benefit is a standalone, testable review boundary while preserving byte-for-byte parity at the top of the stack ## Linked Issues or Issue Description - Related parity reference: #9534 - Problem: Operators need typed clients and administration surfaces that compile independently before navigation exposes them. - Proposed solution: Adds UI APIs, hooks, libraries, shared components, Tools/Profiles pages, and the plugin settings consumer required by the new company-scoped API. - Alternatives considered: keeping #9534 as one 403-file review, or rewriting the feature to manufacture seams; both were rejected in favor of path extraction plus compile-driven boundary moves. - Roadmap alignment: this advances the existing governed MCP/tool-access work already represented by #9534; it does not introduce a separate roadmap initiative. - Stack position: base branch is `pap10341-split/05-runtime-integration`. - Merge policy: merge bottom-up, in order, only after the complete eight-PR stack has been reviewed and the top-of-stack parity gate remains empty. - Requested review: UXDesigner sanity pass on Tools/Profile surfaces; Greptile on every PR. ## What Changed - Adds UI APIs, hooks, libraries, shared components, Tools/Profiles pages, and the plugin settings consumer required by the new company-scoped API. - Keeps this PR below 100 changed files and independently typecheckable. - Preserves the final tree from #9534 when combined with the other seven stack levels. ## Verification - `pnpm typecheck` - `pnpm check:token-gates` — all gates clean - Focused UI Vitest run with `NODE_ENV=test` — 28 files, 183 tests passed ## Risks - Large dead-code UI additions can drift from activation routes; PR 7 supplies the registration layer and top-level parity catches omissions. - Stack risk: merging out of order can expose incomplete layers; mitigate by following the documented bottom-up merge policy. - Parity risk: later edits to an intermediate branch can drift from #9534; mitigate by re-running the empty top-of-stack diff before merge. > For core feature work, check [`ROADMAP.md`](ROADMAP.md) first and discuss it in `#dev` before opening the PR. Feature PRs that overlap with planned core work may need to be redirected — check the roadmap first. See `CONTRIBUTING.md`. ## Model Used - OpenAI Codex, exact model ID `gpt-5.4`; runtime-managed context window; medium reasoning with repository, shell, Git, GitHub CLI, and code-execution tools enabled. ## Checklist - [x] I have included a thinking path that traces from project context to this change - [x] I have specified the model used (with version and capability details) - [x] I have checked ROADMAP.md and confirmed this PR does not duplicate planned core work - [x] I have searched GitHub for duplicate or related PRs and linked them above - [x] I have either (a) linked existing issues with `Fixes: #` / `Closes #` / `Refs #` OR (b) described the issue in-PR following the relevant issue template - [x] Internal references are omitted except the execution-plan link explicitly required for this coordinated split stack - [x] My branch name describes the change and contains no internal Paperclip ticket id or instance-derived details - [x] I have run tests locally and they pass - [x] I have added or updated tests where applicable - [x] I have updated relevant documentation to reflect my changes - [x] I have considered and documented any risks above - [ ] All Paperclip CI gates are green - [ ] Greptile is 5/5 with no open P2s, recommendations, or follow-ups - [x] I will address all Greptile and reviewer comments before requesting merge ## Stack Coordination - Internal execution plan: [PAP-13874](/PAP/issues/PAP-13874#document-plan) - Parity reference: #9534 - Stack: #9556 → #9557 → #9558 → #9559 → #9560 → #9561 → #9562 → #9563 - Merge bottom-up only after full-stack review and an empty parity diff at #9563. ## UI Evidence QA captured these from the live Garden MCP split stack at 1440px and verified clean rendering:    --------- Co-authored-by: Paperclip <noreply@paperclip.ing>
461 lines
19 KiB
TypeScript
461 lines
19 KiB
TypeScript
import type {
|
|
ToolApplication,
|
|
ToolConnection,
|
|
ToolConnectionInstall,
|
|
ToolConnectionInstallSnapshot,
|
|
ConnectToolAppResult,
|
|
FinishToolAppResult,
|
|
ToolCatalogEntry,
|
|
ToolRuntimeSlot,
|
|
ToolPolicy,
|
|
ToolConnectionHealthCheckResult,
|
|
ToolCatalogRefreshResult,
|
|
ToolAccessDecision,
|
|
ToolAccessDecisionInput,
|
|
CreateToolPolicy,
|
|
DuplicateToolPolicy,
|
|
McpJsonImportPreview,
|
|
ToolRuntimeHealthSummary,
|
|
ToolRunDecisionLookup,
|
|
ToolOAuthStartResult,
|
|
ToolStdioCommandTemplate,
|
|
ToolProfileBinding,
|
|
ToolProfileBindingTargetType,
|
|
ToolProfileDefaultAction,
|
|
ToolProfileEffectiveSummary,
|
|
ToolProfileEntry,
|
|
ToolProfileEntryEffect,
|
|
ToolProfileEntrySelectorType,
|
|
ToolProfileStatus,
|
|
ToolProfileWithDetails,
|
|
ToolProfileNewToolReviewDecision,
|
|
ToolProfileNewToolsReview,
|
|
ToolProfileNewToolsReviewResult,
|
|
ToolRiskLevel,
|
|
UpdateToolPolicy,
|
|
ReorderToolPolicies,
|
|
AppGalleryEntry,
|
|
ToolAppsAttentionResponse,
|
|
ToolConnectionActivityResponse,
|
|
ToolConnectionTestAgentsResponse,
|
|
ToolConnectionTestCallResult,
|
|
ToolConnectionTestCallStatus,
|
|
ToolActionRequest,
|
|
ToolActionRequestStatus,
|
|
ToolActionRequestsResponse,
|
|
ToolMcpGatewayWithTokens,
|
|
ToolMcpGatewayTokenCreated,
|
|
ToolMcpGatewayToken,
|
|
CreateToolMcpGateway,
|
|
CreateToolMcpGatewayToken,
|
|
UpdateToolMcpGateway,
|
|
CreateToolTrustRuleFromActionRequest,
|
|
} from "@paperclipai/shared";
|
|
import { api } from "./client";
|
|
|
|
/**
|
|
* Tools & Access API client (Phase 6, PAP-10389).
|
|
*
|
|
* Mirrors the governed MCP/tool-access contracts shipped by Phases 2-5
|
|
* (`server/src/routes/tool-access.ts` and `tool-gateway.ts`). The UI consumes
|
|
* server-side enforcement contracts directly instead of faking tool access in
|
|
* the browser.
|
|
*/
|
|
|
|
export type ToolApplicationsResponse = { applications: ToolApplication[] };
|
|
export type ToolConnectionsResponse = { connections: ToolConnection[] };
|
|
export type ToolCatalogResponse = { catalog: ToolCatalogEntry[] };
|
|
export type ToolRuntimeSlotsResponse = { runtimeSlots: ToolRuntimeSlot[] };
|
|
export type ToolRuntimeHealthResponse = ToolRuntimeHealthSummary;
|
|
export type ToolTrustRulesResponse = { trustRules: ToolPolicy[] };
|
|
export type ToolPoliciesResponse = { policies: ToolPolicy[] };
|
|
export type ToolProfilesResponse = { profiles: ToolProfileWithDetails[] };
|
|
export type ToolGalleryResponse = { apps: AppGalleryEntry[] };
|
|
export type ToolMcpGatewaysResponse = { gateways: ToolMcpGatewayWithTokens[] };
|
|
export type CreateGatewayTokenInput = Omit<CreateToolMcpGatewayToken, "expiresAt"> & {
|
|
expiresAt?: string | Date | null;
|
|
};
|
|
/** Gateway update payload — `companyId` is injected by the client method. */
|
|
export type UpdateGatewayInput = Omit<UpdateToolMcpGateway, "companyId">;
|
|
export type ReviewNewToolsInput = {
|
|
decisions: Array<{ catalogEntryId: string; decision: ToolProfileNewToolReviewDecision }>;
|
|
};
|
|
|
|
export type StdioTemplateSummary = ToolStdioCommandTemplate;
|
|
export type StdioTemplatesResponse = { templates: StdioTemplateSummary[] };
|
|
|
|
/** Admin "run your own" command-template create input (M8b, PAP-10862). */
|
|
export interface CreateStdioTemplateInput {
|
|
templateId: string;
|
|
name: string;
|
|
description?: string | null;
|
|
command: string;
|
|
args?: string[];
|
|
envKeys?: string[];
|
|
}
|
|
|
|
export interface CreateToolApplicationInput {
|
|
name: string;
|
|
description?: string | null;
|
|
type: ToolApplication["type"];
|
|
pluginId?: string | null;
|
|
metadata?: Record<string, unknown> | null;
|
|
}
|
|
|
|
export interface UpdateToolApplicationInput {
|
|
name?: string;
|
|
description?: string | null;
|
|
status?: ToolApplication["status"];
|
|
metadata?: Record<string, unknown> | null;
|
|
}
|
|
|
|
export interface CreateToolConnectionInput {
|
|
applicationId?: string;
|
|
applicationName?: string;
|
|
name: string;
|
|
transport: NonNullable<ToolConnection["transport"]>;
|
|
status?: ToolConnection["status"];
|
|
config?: Record<string, unknown>;
|
|
credentialRefs?: ToolConnection["credentialRefs"];
|
|
enabled?: boolean;
|
|
}
|
|
|
|
export interface UpdateToolConnectionInput {
|
|
name?: string;
|
|
status?: ToolConnection["status"];
|
|
config?: Record<string, unknown>;
|
|
transportConfig?: Record<string, unknown>;
|
|
credentialRefs?: ToolConnection["credentialRefs"];
|
|
enabled?: boolean;
|
|
}
|
|
|
|
export interface ToolProfileEntryInput {
|
|
selectorType: ToolProfileEntrySelectorType;
|
|
effect?: ToolProfileEntryEffect;
|
|
applicationId?: string | null;
|
|
connectionId?: string | null;
|
|
catalogEntryId?: string | null;
|
|
toolName?: string | null;
|
|
riskLevel?: ToolRiskLevel | null;
|
|
conditions?: Record<string, unknown> | null;
|
|
}
|
|
|
|
export interface CreateToolProfileInput {
|
|
profileKey: string;
|
|
name: string;
|
|
description?: string | null;
|
|
status?: ToolProfileStatus;
|
|
defaultAction?: ToolProfileDefaultAction;
|
|
metadata?: Record<string, unknown> | null;
|
|
entries?: ToolProfileEntryInput[];
|
|
}
|
|
|
|
export interface UpdateToolProfileInput {
|
|
profileKey?: string;
|
|
name?: string;
|
|
description?: string | null;
|
|
status?: ToolProfileStatus;
|
|
defaultAction?: ToolProfileDefaultAction;
|
|
metadata?: Record<string, unknown> | null;
|
|
entries?: ToolProfileEntryInput[];
|
|
}
|
|
|
|
export interface ToolProfileBindingInput {
|
|
targetType: ToolProfileBindingTargetType;
|
|
targetId: string;
|
|
priority?: number;
|
|
metadata?: Record<string, unknown> | null;
|
|
}
|
|
|
|
export type UnbindToolProfileInput = Pick<ToolProfileBindingInput, "targetType" | "targetId">;
|
|
|
|
/** Redacted tool-gateway audit row (subset of `activity_log`). */
|
|
export interface ToolGatewayAuditRow {
|
|
id: string;
|
|
companyId: string;
|
|
action: string;
|
|
actorType: string | null;
|
|
actorId: string | null;
|
|
entityType: string | null;
|
|
entityId: string | null;
|
|
details: Record<string, unknown> | null;
|
|
createdAt: string;
|
|
}
|
|
|
|
/** Normalized outcome for the humanized Activity feed. */
|
|
export type ToolAuditOutcome =
|
|
| "allowed"
|
|
| "blocked"
|
|
| "asked_first"
|
|
| "waiting"
|
|
| "failed"
|
|
| "unknown";
|
|
|
|
/**
|
|
* Audit row enriched server-side with humanized display names and a normalized
|
|
* outcome — the shape returned by `GET /tool-gateway/audit`.
|
|
*/
|
|
export interface ToolGatewayActivityEvent extends ToolGatewayAuditRow {
|
|
agentId: string | null;
|
|
runId: string | null;
|
|
applicationId: string | null;
|
|
connectionId: string | null;
|
|
agentDisplayName: string | null;
|
|
appDisplayName: string | null;
|
|
applicationDisplayName: string | null;
|
|
connectionDisplayName: string | null;
|
|
toolDisplayName: string | null;
|
|
normalizedOutcome: ToolAuditOutcome;
|
|
}
|
|
|
|
export type ToolGatewayActivityResponse = {
|
|
events: ToolGatewayActivityEvent[];
|
|
nextCursor: string | null;
|
|
};
|
|
|
|
export type ToolAuditWindow = "1h" | "24h" | "7d" | "30d";
|
|
|
|
export interface ListActivityParams {
|
|
app?: string | null;
|
|
agent?: string | null;
|
|
outcome?: string | null;
|
|
window?: ToolAuditWindow;
|
|
search?: string | null;
|
|
limit?: number;
|
|
cursor?: string | null;
|
|
}
|
|
|
|
export type ToolPolicyTestResponse = {
|
|
decision: ToolAccessDecision;
|
|
auditEvent: unknown | null;
|
|
};
|
|
|
|
export const toolsApi = {
|
|
// --- Applications ---
|
|
listGallery: (companyId: string) =>
|
|
api.get<ToolGalleryResponse>(`/companies/${companyId}/tools/gallery`),
|
|
connectApp: (companyId: string, input: {
|
|
galleryKey?: string;
|
|
link?: string;
|
|
name?: string;
|
|
credentialValues?: Record<string, string>;
|
|
configValues?: Record<string, unknown>;
|
|
applicationId?: string;
|
|
}) =>
|
|
api.post<ConnectToolAppResult>(`/companies/${companyId}/tools/apps/connect`, input),
|
|
startOAuth: (connectionId: string) =>
|
|
api.post<ToolOAuthStartResult>(`/tools/oauth/${connectionId}/start`, {}),
|
|
finishApp: (companyId: string, connectionId: string, input: {
|
|
enabledCatalogEntryIds: string[];
|
|
askFirstCatalogEntryIds: string[];
|
|
access: "all_agents" | { agentIds: string[] };
|
|
}) =>
|
|
api.post<FinishToolAppResult>(
|
|
`/companies/${companyId}/tools/apps/${connectionId}/finish`,
|
|
input,
|
|
),
|
|
listAppsAttention: (companyId: string) =>
|
|
api.get<ToolAppsAttentionResponse>(`/companies/${companyId}/tools/apps/attention`),
|
|
listApplications: (companyId: string) =>
|
|
api.get<ToolApplicationsResponse>(`/companies/${companyId}/tools/applications`),
|
|
createApplication: (companyId: string, input: CreateToolApplicationInput) =>
|
|
api.post<ToolApplication>(`/companies/${companyId}/tools/applications`, input),
|
|
updateApplication: (applicationId: string, input: UpdateToolApplicationInput) =>
|
|
api.patch<ToolApplication>(`/tool-applications/${applicationId}`, input),
|
|
deleteApplication: (applicationId: string) =>
|
|
api.delete<ToolApplication>(`/tool-applications/${applicationId}`),
|
|
|
|
// --- Connections ---
|
|
listConnections: (companyId: string) =>
|
|
api.get<ToolConnectionsResponse>(`/companies/${companyId}/tools/connections`),
|
|
getConnection: (connectionId: string) =>
|
|
api.get<ToolConnection>(`/tool-connections/${connectionId}`),
|
|
// --- Installs (Phase 3b, PAP-13618): which agents carry this connection's
|
|
// tools in their runtime context. `installed ⊆ permitted`; the server
|
|
// auto-extends access (adds profile bindings) for any newly-installed target.
|
|
getConnectionInstalls: (connectionId: string) =>
|
|
api.get<{ connectionId: string; installs: ToolConnectionInstall[] }>(
|
|
`/tool-connections/${connectionId}/installs`,
|
|
),
|
|
putConnectionInstalls: (
|
|
connectionId: string,
|
|
installs: Array<{ targetType: "company" | "agent"; targetId: string }>,
|
|
) =>
|
|
api.put<ToolConnectionInstallSnapshot>(
|
|
`/tool-connections/${connectionId}/installs`,
|
|
{ installs },
|
|
),
|
|
createConnection: (companyId: string, input: CreateToolConnectionInput) =>
|
|
api.post<ToolConnection>(`/companies/${companyId}/tools/connections`, input),
|
|
updateConnection: (connectionId: string, input: UpdateToolConnectionInput) =>
|
|
api.patch<ToolConnection>(`/tool-connections/${connectionId}`, input),
|
|
archiveConnection: (connectionId: string) =>
|
|
api.delete<ToolConnection>(`/tool-connections/${connectionId}`),
|
|
checkConnectionHealth: (connectionId: string) =>
|
|
api.post<ToolConnectionHealthCheckResult>(`/tool-connections/${connectionId}/health-check`, {}),
|
|
reconnectConnection: (connectionId: string, credentialValues: Record<string, string>) =>
|
|
api.post<ToolConnectionHealthCheckResult>(`/tool-connections/${connectionId}/reconnect`, {
|
|
credentialValues,
|
|
}),
|
|
refreshCatalog: (connectionId: string) =>
|
|
api.post<ToolCatalogRefreshResult>(`/tool-connections/${connectionId}/catalog/refresh`, {}),
|
|
listCatalog: (connectionId: string) =>
|
|
api.get<ToolCatalogResponse>(`/tool-connections/${connectionId}/catalog`),
|
|
listConnectionActivity: (connectionId: string, limit = 20) =>
|
|
api.get<ToolConnectionActivityResponse>(
|
|
`/tool-connections/${connectionId}/activity?limit=${limit}`,
|
|
),
|
|
listTestAgents: (connectionId: string) =>
|
|
api.get<ToolConnectionTestAgentsResponse>(
|
|
`/tool-connections/${connectionId}/test-agents`,
|
|
),
|
|
runTestCall: (
|
|
connectionId: string,
|
|
input: { agentId: string; toolName: string; parameters?: Record<string, unknown> },
|
|
) =>
|
|
api.post<ToolConnectionTestCallResult>(
|
|
`/tool-connections/${connectionId}/test-calls`,
|
|
input,
|
|
),
|
|
getTestCallStatus: (connectionId: string, actionRequestId: string) =>
|
|
api.get<ToolConnectionTestCallStatus>(
|
|
`/tool-connections/${connectionId}/test-calls/${actionRequestId}`,
|
|
),
|
|
importMcpJson: (companyId: string, body: { mcpJson: unknown }) =>
|
|
api.post<McpJsonImportPreview>(`/companies/${companyId}/tools/mcp/import-json`, body),
|
|
listStdioTemplates: (companyId: string) =>
|
|
api.get<StdioTemplatesResponse>(`/companies/${companyId}/tools/stdio-templates`),
|
|
createStdioTemplate: (companyId: string, input: CreateStdioTemplateInput) =>
|
|
api.post<StdioTemplateSummary>(`/companies/${companyId}/tools/stdio-templates`, input),
|
|
disableStdioTemplate: (companyId: string, templateId: string, reason?: string | null) =>
|
|
api.post<StdioTemplateSummary>(
|
|
`/companies/${companyId}/tools/stdio-templates/${encodeURIComponent(templateId)}/disable`,
|
|
{ reason: reason ?? null },
|
|
),
|
|
|
|
// --- Profiles ---
|
|
listProfiles: (companyId: string) =>
|
|
api.get<ToolProfilesResponse>(`/companies/${companyId}/tools/profiles`),
|
|
getProfileNewTools: (profileId: string) =>
|
|
api.get<ToolProfileNewToolsReview>(`/tool-profiles/${profileId}/new-tools`),
|
|
reviewProfileNewTools: (profileId: string, input: ReviewNewToolsInput) =>
|
|
api.post<ToolProfileNewToolsReviewResult>(`/tool-profiles/${profileId}/new-tools/review`, input),
|
|
createProfile: (companyId: string, input: CreateToolProfileInput) =>
|
|
api.post<ToolProfileWithDetails>(`/companies/${companyId}/tools/profiles`, input),
|
|
updateProfile: (profileId: string, input: UpdateToolProfileInput) =>
|
|
api.patch<ToolProfileWithDetails>(`/tool-profiles/${profileId}`, input),
|
|
duplicateProfile: (
|
|
profileId: string,
|
|
input: { name: string; includeAssignments?: boolean },
|
|
) => api.post<ToolProfileWithDetails>(`/tool-profiles/${profileId}/duplicate`, input),
|
|
deleteProfile: (
|
|
profileId: string,
|
|
input: { force?: boolean; reassignToProfileId?: string } = {},
|
|
) => api.delete<{ deleted: true }>(`/tool-profiles/${profileId}`, input),
|
|
addProfileEntry: (profileId: string, input: ToolProfileEntryInput) =>
|
|
api.post<ToolProfileEntry>(`/tool-profiles/${profileId}/entries`, input),
|
|
updateProfileEntry: (entryId: string, input: Partial<ToolProfileEntryInput>) =>
|
|
api.patch<ToolProfileEntry>(`/tool-profile-entries/${entryId}`, input),
|
|
deleteProfileEntry: (entryId: string) =>
|
|
api.delete<ToolProfileEntry>(`/tool-profile-entries/${entryId}`),
|
|
bindProfile: (companyId: string, profileId: string, input: ToolProfileBindingInput) =>
|
|
api.post<ToolProfileBinding>(`/companies/${companyId}/tools/profiles/${profileId}/bind`, input),
|
|
unbindProfile: (companyId: string, profileId: string, input: UnbindToolProfileInput) =>
|
|
api.post<{ unbound: number }>(`/companies/${companyId}/tools/profiles/${profileId}/unbind`, input),
|
|
getEffectiveProfilesForAgent: (companyId: string, agentId: string) =>
|
|
api.get<ToolProfileEffectiveSummary>(
|
|
`/companies/${companyId}/tools/profiles/effective/agents/${encodeURIComponent(agentId)}`,
|
|
),
|
|
|
|
// --- Runtime ---
|
|
listRuntimeSlots: (companyId: string) =>
|
|
api.get<ToolRuntimeSlotsResponse>(`/companies/${companyId}/tools/runtime-slots`),
|
|
stopRuntimeSlot: (companyId: string, slotId: string) =>
|
|
api.post<ToolRuntimeSlot>(`/companies/${companyId}/tools/runtime-slots/${slotId}/stop`, {}),
|
|
restartRuntimeSlot: (companyId: string, slotId: string) =>
|
|
api.post<ToolRuntimeSlot>(`/companies/${companyId}/tools/runtime-slots/${slotId}/restart`, {}),
|
|
getRuntimeHealth: (companyId: string) =>
|
|
api.get<ToolRuntimeHealthResponse>(`/companies/${companyId}/tools/runtime-health`),
|
|
getRunDecisionLookup: (companyId: string, runId: string) =>
|
|
api.get<ToolRunDecisionLookup>(`/companies/${companyId}/tools/runs/${runId}/decisions`),
|
|
listLiveRuntimeSlots: (companyId: string) =>
|
|
api.get<ToolRuntimeSlot[]>(`/tool-gateway/runtime-slots?companyId=${encodeURIComponent(companyId)}`),
|
|
|
|
// --- Policies (trust rules + decision simulator) ---
|
|
listPolicies: (companyId: string) =>
|
|
api.get<ToolPoliciesResponse>(`/companies/${companyId}/tools/policies`),
|
|
createPolicy: (companyId: string, input: CreateToolPolicy) =>
|
|
api.post<ToolPolicy>(`/companies/${companyId}/tools/policies`, input),
|
|
reorderPolicies: (companyId: string, input: ReorderToolPolicies) =>
|
|
api.post<ToolPoliciesResponse>(`/companies/${companyId}/tools/policies/reorder`, input),
|
|
duplicatePolicy: (companyId: string, policyId: string, input: DuplicateToolPolicy = {}) =>
|
|
api.post<ToolPolicy>(`/companies/${companyId}/tools/policies/${policyId}/duplicate`, input),
|
|
updatePolicy: (companyId: string, policyId: string, input: UpdateToolPolicy) =>
|
|
api.patch<ToolPolicy>(`/companies/${companyId}/tools/policies/${policyId}`, input),
|
|
deletePolicy: (companyId: string, policyId: string) =>
|
|
api.delete<ToolPolicy>(`/companies/${companyId}/tools/policies/${policyId}`),
|
|
listTrustRules: (companyId: string) =>
|
|
api.get<ToolTrustRulesResponse>(`/companies/${companyId}/tools/trust-rules`),
|
|
revokeTrustRule: (companyId: string, policyId: string, reason?: string | null) =>
|
|
api.post<ToolPolicy>(`/companies/${companyId}/tools/trust-rules/${policyId}/revoke`, {
|
|
reason: reason ?? null,
|
|
}),
|
|
testPolicy: (companyId: string, input: Omit<ToolAccessDecisionInput, "companyId">) =>
|
|
api.post<ToolPolicyTestResponse>(`/companies/${companyId}/tools/policy/test`, input),
|
|
|
|
// --- Review queue (Ask first) ---
|
|
listActionRequests: (companyId: string, status: ToolActionRequestStatus = "pending") =>
|
|
api.get<ToolActionRequestsResponse>(
|
|
`/companies/${companyId}/tools/action-requests?status=${encodeURIComponent(status)}`,
|
|
),
|
|
approveActionRequest: (companyId: string, actionRequestId: string) =>
|
|
api.post<ToolActionRequest>(`/tool-gateway/action-requests/${actionRequestId}/approve`, { companyId }),
|
|
declineActionRequest: (companyId: string, actionRequestId: string) =>
|
|
api.post<ToolActionRequest>(`/tool-gateway/action-requests/${actionRequestId}/decline`, { companyId }),
|
|
createTrustRuleFromActionRequest: (
|
|
companyId: string,
|
|
actionRequestId: string,
|
|
input: CreateToolTrustRuleFromActionRequest = {},
|
|
) =>
|
|
api.post<ToolPolicy>(
|
|
`/companies/${companyId}/tools/action-requests/${actionRequestId}/trust-rule`,
|
|
input,
|
|
),
|
|
|
|
// --- Named MCP gateways ---
|
|
listGateways: (companyId: string) =>
|
|
api.get<ToolMcpGatewaysResponse>(`/companies/${companyId}/tools/gateways`),
|
|
createGateway: (companyId: string, input: CreateToolMcpGateway) =>
|
|
api.post<ToolMcpGatewayWithTokens>(`/companies/${companyId}/tools/gateways`, input),
|
|
updateGateway: (companyId: string, gatewayId: string, input: UpdateGatewayInput) =>
|
|
api.patch<ToolMcpGatewayWithTokens>(`/tool-gateway/gateways/${gatewayId}`, { ...input, companyId }),
|
|
createGatewayToken: (companyId: string, gatewayId: string, input: CreateGatewayTokenInput) =>
|
|
api.post<ToolMcpGatewayTokenCreated>(`/tool-gateway/gateways/${gatewayId}/tokens`, { ...input, companyId }),
|
|
revokeGatewayToken: (companyId: string, tokenId: string) =>
|
|
api.post<ToolMcpGatewayToken>(`/tool-gateway/gateway-tokens/${tokenId}/revoke`, { companyId }),
|
|
|
|
// --- Audit / Activity ---
|
|
/**
|
|
* Humanized Activity feed with server-side filters and cursor pagination.
|
|
* Returns `{ events, nextCursor }`.
|
|
*/
|
|
listActivity: (companyId: string, params: ListActivityParams = {}) => {
|
|
const search = new URLSearchParams({ companyId });
|
|
if (params.app) search.set("app", params.app);
|
|
if (params.agent) search.set("agent", params.agent);
|
|
if (params.outcome) search.set("outcome", params.outcome);
|
|
if (params.window) search.set("window", params.window);
|
|
if (params.search) search.set("search", params.search);
|
|
if (params.cursor) search.set("cursor", params.cursor);
|
|
search.set("limit", String(params.limit ?? 50));
|
|
return api.get<ToolGatewayActivityResponse>(`/tool-gateway/audit?${search.toString()}`);
|
|
},
|
|
/** Flat audit sample (no pagination) — used by derived counters/banners. */
|
|
listAudit: (companyId: string, limit = 100) =>
|
|
api
|
|
.get<ToolGatewayActivityResponse>(
|
|
`/tool-gateway/audit?companyId=${encodeURIComponent(companyId)}&window=30d&limit=${Math.min(limit, 100)}`,
|
|
)
|
|
.then((res) => res.events),
|
|
};
|