diff --git a/packages/shared/src/telemetry/events.ts b/packages/shared/src/telemetry/events.ts index b652e7cb8f8..74e6288c379 100644 --- a/packages/shared/src/telemetry/events.ts +++ b/packages/shared/src/telemetry/events.ts @@ -153,7 +153,7 @@ export function trackAgentTaskRun( }); } -export function trackConnectorConnectionCreated( +export function trackConnectionCreated( client: TelemetryClient, dims: { connector_key: RawDimension<"custom">; @@ -166,20 +166,20 @@ export function trackConnectorConnectionCreated( ): void { client.track( // @ts-expect-error -- proposed-telemetry(https://github.com/paperclipai/paperclip/issues/13578): measure which catalog connectors installations create connections for - "connector.connection_created", + "connection.created", dims, ); } -export function trackConnectorConnectionUpdated( +export function trackConnectionUpdated( client: TelemetryClient, dims: { connector_key: RawDimension<"custom">; transport: RawDimension<"mcp_remote" | "rest_api" | "local_stdio">; auth_kind: RawDimension<"oauth" | "api_key" | "none">; change_source: RawDimension< - | "update_api" - | "gallery_setup" + | "api" + | "gallery" | "oauth_callback" | "credential_refresh" | "archive" @@ -194,12 +194,12 @@ export function trackConnectorConnectionUpdated( ): void { client.track( // @ts-expect-error -- proposed-telemetry(https://github.com/paperclipai/paperclip/issues/13578): measure connector lifecycle transitions (configured, paused, archived) after creation - "connector.connection_updated", + "connection.updated", dims, ); } -export function trackConnectorInvocationCompleted( +export function trackConnectionInvoked( client: TelemetryClient, dims: { connector_key: RawDimension<"custom">; @@ -217,8 +217,8 @@ export function trackConnectorInvocationCompleted( }, ): void { client.track( - // @ts-expect-error -- proposed-telemetry(https://github.com/paperclipai/paperclip/issues/13578): measure whether connected connectors are successfully used and where invocations fail - "connector.invocation_completed", + // @ts-expect-error -- proposed-telemetry(https://github.com/paperclipai/paperclip/issues/13578): measure whether connected connectors are successfully used and where invocations fail; records completed invocation attempts and their terminal status, never invocation starts + "connection.invoked", dims, ); } diff --git a/packages/shared/src/telemetry/index.ts b/packages/shared/src/telemetry/index.ts index c09c72ab81b..61f945ad834 100644 --- a/packages/shared/src/telemetry/index.ts +++ b/packages/shared/src/telemetry/index.ts @@ -18,9 +18,9 @@ export { trackErrorHandlerCrash, trackInteractionCreated, trackInteractionResolved, - trackConnectorConnectionCreated, - trackConnectorConnectionUpdated, - trackConnectorInvocationCompleted, + trackConnectionCreated, + trackConnectionUpdated, + trackConnectionInvoked, } from "./events.js"; export type { TelemetryConfig, diff --git a/packages/shared/src/telemetry/proposed-connector-events.test.ts b/packages/shared/src/telemetry/proposed-connector-events.test.ts index 38d1e84ed67..a8a6826e9de 100644 --- a/packages/shared/src/telemetry/proposed-connector-events.test.ts +++ b/packages/shared/src/telemetry/proposed-connector-events.test.ts @@ -2,9 +2,9 @@ import { afterEach, describe, expect, it, vi } from "vitest"; import { TelemetryClient } from "./client.js"; import { resolveTelemetryConfig } from "./config.js"; import { - trackConnectorConnectionCreated, - trackConnectorConnectionUpdated, - trackConnectorInvocationCompleted, + trackConnectionCreated, + trackConnectionUpdated, + trackConnectionInvoked, } from "./events.js"; import type { TelemetryState } from "./types.js"; @@ -29,7 +29,7 @@ function makeClient(config?: { enabled?: boolean }) { } function trackAllProposedConnectorEvents(client: TelemetryClient) { - trackConnectorConnectionCreated(client, { + trackConnectionCreated(client, { connector_key: "github", transport: "mcp_remote", auth_kind: "oauth", @@ -37,17 +37,17 @@ function trackAllProposedConnectorEvents(client: TelemetryClient) { status: "active", enabled: true, }); - trackConnectorConnectionUpdated(client, { + trackConnectionUpdated(client, { connector_key: "github", transport: "mcp_remote", auth_kind: "oauth", - change_source: "update_api", + change_source: "api", previous_status: "draft", status: "active", previous_enabled: false, enabled: true, }); - trackConnectorInvocationCompleted(client, { + trackConnectionInvoked(client, { connector_key: "github", transport: "mcp_remote", status: "succeeded", @@ -92,9 +92,9 @@ describe("proposed connector events against the real TelemetryClient", () => { it("reports the three connector event names as unregistered", () => { const { client } = makeClient(); - expect(client.isRegisteredEventName("connector.connection_created")).toBe(false); - expect(client.isRegisteredEventName("connector.connection_updated")).toBe(false); - expect(client.isRegisteredEventName("connector.invocation_completed")).toBe(false); + expect(client.isRegisteredEventName("connection.created")).toBe(false); + expect(client.isRegisteredEventName("connection.updated")).toBe(false); + expect(client.isRegisteredEventName("connection.invoked")).toBe(false); expect(client.isRegisteredEventName("project.created")).toBe(true); }); diff --git a/server/src/__tests__/tool-access-connector-telemetry.test.ts b/server/src/__tests__/tool-access-connector-telemetry.test.ts index 45fffaa2a96..b80c750fd3f 100644 --- a/server/src/__tests__/tool-access-connector-telemetry.test.ts +++ b/server/src/__tests__/tool-access-connector-telemetry.test.ts @@ -40,11 +40,11 @@ const describeEmbeddedPostgres = embeddedPostgresSupport.supported type Db = ReturnType; function createdEvents() { - return track.mock.calls.filter(([name]) => name === "connector.connection_created"); + return track.mock.calls.filter(([name]) => name === "connection.created"); } function updatedEvents() { - return track.mock.calls.filter(([name]) => name === "connector.connection_updated"); + return track.mock.calls.filter(([name]) => name === "connection.updated"); } async function createCompany(db: Db) { @@ -251,7 +251,7 @@ describeEmbeddedPostgres("connector lifecycle telemetry (tool-access)", () => { expect(events).toHaveLength(1); expect(events[0]?.[1]).toMatchObject({ connector_key: "custom", - change_source: "update_api", + change_source: "api", previous_enabled: true, enabled: false, previous_status: "active", @@ -272,7 +272,7 @@ describeEmbeddedPostgres("connector lifecycle telemetry (tool-access)", () => { await svc.updateConnection(parent.id, { enabled: false }); let events = updatedEvents(); - // Parent transition (update_api) + child cascade (composio_sync). + // Parent transition (api) + child cascade (composio_sync). expect(events).toHaveLength(2); const childEvent = events.find( ([, dims]) => (dims as { change_source: string }).change_source === "composio_sync", diff --git a/server/src/__tests__/tool-gateway-connector-telemetry.test.ts b/server/src/__tests__/tool-gateway-connector-telemetry.test.ts index c8afd9624a0..1ead5d320ef 100644 --- a/server/src/__tests__/tool-gateway-connector-telemetry.test.ts +++ b/server/src/__tests__/tool-gateway-connector-telemetry.test.ts @@ -240,7 +240,7 @@ async function settledEvents(expectedCount: number) { await vi.waitFor(() => { expect( track.mock.calls.filter( - ([name]) => name === "connector.invocation_completed", + ([name]) => name === "connection.invoked", ).length, ).toBeGreaterThanOrEqual(expectedCount); }); @@ -248,7 +248,7 @@ async function settledEvents(expectedCount: number) { // chance to land before asserting the exact count. await new Promise((resolve) => setTimeout(resolve, 150)); return track.mock.calls.filter( - ([name]) => name === "connector.invocation_completed", + ([name]) => name === "connection.invoked", ); } @@ -458,7 +458,7 @@ describeEmbeddedPostgres("gateway connector invocation telemetry", () => { // filter to the proposed connector event. await new Promise((resolve) => setTimeout(resolve, 150)); expect( - track.mock.calls.filter(([name]) => name === "connector.invocation_completed"), + track.mock.calls.filter(([name]) => name === "connection.invoked"), ).toHaveLength(0); const [pendingRequest] = await db.select().from(toolActionRequests); diff --git a/server/src/services/connector-telemetry.test.ts b/server/src/services/connector-telemetry.test.ts index 42bc937056f..2fef1f55cce 100644 --- a/server/src/services/connector-telemetry.test.ts +++ b/server/src/services/connector-telemetry.test.ts @@ -14,9 +14,9 @@ vi.mock("../telemetry.js", () => ({ const { connectorKeyForConnection, - emitConnectorConnectionCreated, - emitConnectorConnectionUpdated, - emitConnectorInvocationCompleted, + emitConnectionCreated, + emitConnectionUpdated, + emitConnectionInvoked, invocationOrigin, } = await import("./connector-telemetry.js"); @@ -119,10 +119,10 @@ describe("invocationOrigin", () => { }); }); -describe("emitConnectorConnectionCreated", () => { +describe("emitConnectionCreated", () => { it("emits catalog identity and lifecycle state for tool connections", () => { - emitConnectorConnectionCreated(connectionRow(), "gallery"); - expect(track).toHaveBeenCalledWith("connector.connection_created", { + emitConnectionCreated(connectionRow(), "gallery"); + expect(track).toHaveBeenCalledWith("connection.created", { connector_key: "github", transport: "mcp_remote", auth_kind: "oauth", @@ -133,40 +133,40 @@ describe("emitConnectorConnectionCreated", () => { }); it("never emits for channel or AI connections", () => { - emitConnectorConnectionCreated(connectionRow({ connectionPurpose: "channel" }), "api"); + emitConnectionCreated(connectionRow({ connectionPurpose: "channel" }), "api"); expect(track).not.toHaveBeenCalled(); }); it("is a no-op without an initialized telemetry client", () => { telemetryClient = null; - expect(() => emitConnectorConnectionCreated(connectionRow(), "gallery")).not.toThrow(); + expect(() => emitConnectionCreated(connectionRow(), "gallery")).not.toThrow(); }); it("swallows client failures instead of failing the setup path", () => { track.mockImplementation(() => { throw new Error("sink offline"); }); - expect(() => emitConnectorConnectionCreated(connectionRow(), "gallery")).not.toThrow(); + expect(() => emitConnectionCreated(connectionRow(), "gallery")).not.toThrow(); }); }); -describe("emitConnectorConnectionUpdated", () => { +describe("emitConnectionUpdated", () => { it("stays silent when the committed write left status and enabled unchanged", () => { - emitConnectorConnectionUpdated( + emitConnectionUpdated( connectionRow(), { status: "active", enabled: true }, - "update_api", + "api", ); expect(track).not.toHaveBeenCalled(); }); it("emits the transition when lifecycle status changes", () => { - emitConnectorConnectionUpdated( + emitConnectionUpdated( connectionRow(), { status: "draft", enabled: true }, "oauth_callback", ); - expect(track).toHaveBeenCalledWith("connector.connection_updated", { + expect(track).toHaveBeenCalledWith("connection.updated", { connector_key: "github", transport: "mcp_remote", auth_kind: "oauth", @@ -179,10 +179,10 @@ describe("emitConnectorConnectionUpdated", () => { }); it("emits when only the enabled flag flips", () => { - emitConnectorConnectionUpdated( + emitConnectionUpdated( connectionRow({ enabled: false }), { status: "active", enabled: true }, - "update_api", + "api", ); expect(track).toHaveBeenCalledTimes(1); expect(track.mock.calls[0]?.[1]).toMatchObject({ @@ -192,19 +192,19 @@ describe("emitConnectorConnectionUpdated", () => { }); it("never emits for non-tool connections even on lifecycle changes", () => { - emitConnectorConnectionUpdated( + emitConnectionUpdated( connectionRow({ connectionPurpose: "channel" }), { status: "draft", enabled: true }, - "update_api", + "api", ); expect(track).not.toHaveBeenCalled(); }); }); -describe("emitConnectorInvocationCompleted", () => { +describe("emitConnectionInvoked", () => { it("emits terminal outcome, origin, and duration from the committed row", async () => { - await emitConnectorInvocationCompleted(fakeDb(invocationRow(), connectionRow()), "inv-1"); - expect(track).toHaveBeenCalledWith("connector.invocation_completed", { + await emitConnectionInvoked(fakeDb(invocationRow(), connectionRow()), "inv-1"); + expect(track).toHaveBeenCalledWith("connection.invoked", { connector_key: "github", transport: "mcp_remote", status: "succeeded", @@ -214,7 +214,7 @@ describe("emitConnectorInvocationCompleted", () => { }); it("omits duration when the invocation never recorded a start time", async () => { - await emitConnectorInvocationCompleted( + await emitConnectionInvoked( fakeDb(invocationRow({ startedAt: null, status: "denied" }), connectionRow()), "inv-1", ); @@ -223,7 +223,7 @@ describe("emitConnectorInvocationCompleted", () => { }); it("ignores in-flight statuses so approval waits never count as failures", async () => { - await emitConnectorInvocationCompleted( + await emitConnectionInvoked( fakeDb(invocationRow({ status: "awaiting_approval" }), connectionRow()), "inv-1", ); @@ -231,11 +231,11 @@ describe("emitConnectorInvocationCompleted", () => { }); it("ignores invocations without a connection or with a non-tool connection", async () => { - await emitConnectorInvocationCompleted( + await emitConnectionInvoked( fakeDb(invocationRow({ connectionId: null }), connectionRow()), "inv-1", ); - await emitConnectorInvocationCompleted( + await emitConnectionInvoked( fakeDb(invocationRow(), connectionRow({ connectionPurpose: "channel" })), "inv-1", ); @@ -244,7 +244,7 @@ describe("emitConnectorInvocationCompleted", () => { it("resolves without emitting when the invocation row is missing", async () => { await expect( - emitConnectorInvocationCompleted(fakeDb(null, null), "inv-missing"), + emitConnectionInvoked(fakeDb(null, null), "inv-missing"), ).resolves.toBeUndefined(); expect(track).not.toHaveBeenCalled(); }); @@ -252,17 +252,17 @@ describe("emitConnectorInvocationCompleted", () => { it("reads nothing from the database while the event name is unregistered", async () => { isRegisteredEventName.mockReturnValue(false); const db = fakeDb(invocationRow(), connectionRow()); - await emitConnectorInvocationCompleted(db, "inv-1"); + await emitConnectionInvoked(db, "inv-1"); expect(db.selectCalls()).toBe(0); expect(track).not.toHaveBeenCalled(); expect(isRegisteredEventName).toHaveBeenCalledWith( - "connector.invocation_completed", + "connection.invoked", ); }); it("loads the invocation and connection with a single read once registered", async () => { const db = fakeDb(invocationRow(), connectionRow()); - await emitConnectorInvocationCompleted(db, "inv-1"); + await emitConnectionInvoked(db, "inv-1"); expect(db.selectCalls()).toBe(1); expect(track).toHaveBeenCalledTimes(1); }); diff --git a/server/src/services/connector-telemetry.ts b/server/src/services/connector-telemetry.ts index 495263f42b6..dd071f6aaef 100644 --- a/server/src/services/connector-telemetry.ts +++ b/server/src/services/connector-telemetry.ts @@ -2,9 +2,9 @@ import { eq } from "drizzle-orm"; import { toolConnections, toolInvocations, type Db } from "@paperclipai/db"; import { getConnectableAppDefinition } from "@paperclipai/shared"; import { - trackConnectorConnectionCreated, - trackConnectorConnectionUpdated, - trackConnectorInvocationCompleted, + trackConnectionCreated, + trackConnectionUpdated, + trackConnectionInvoked, } from "@paperclipai/shared/telemetry"; import { logger } from "../middleware/logger.js"; import { getTelemetryClient } from "../telemetry.js"; @@ -14,8 +14,8 @@ type ToolInvocationRow = typeof toolInvocations.$inferSelect; export type ConnectorSetupFlow = "gallery" | "api" | "example" | "composio_sync"; export type ConnectorChangeSource = - | "update_api" - | "gallery_setup" + | "api" + | "gallery" | "oauth_callback" | "credential_refresh" | "archive" @@ -79,12 +79,12 @@ function isToolPurpose(connection: Pick) } /** - * Emits one proposed `connector.connection_created` event for a tool-purpose + * Emits one proposed `connection.created` event for a tool-purpose * connection row that a caller already committed. Channel and AI connections * never emit. This function never throws; a telemetry failure must never fail * a connection setup path. */ -export function emitConnectorConnectionCreated( +export function emitConnectionCreated( connection: ToolConnectionRow, setupFlow: ConnectorSetupFlow, ): void { @@ -92,7 +92,7 @@ export function emitConnectorConnectionCreated( const client = getTelemetryClient(); if (!client) return; if (!isToolPurpose(connection)) return; - trackConnectorConnectionCreated(client, { + trackConnectionCreated(client, { connector_key: connectorKeyForConnection(connection), transport: connection.transport, auth_kind: connection.authKind, @@ -103,19 +103,19 @@ export function emitConnectorConnectionCreated( } catch (err) { logger.warn( { err, connectionId: connection.id }, - "failed to emit connector.connection_created telemetry", + "failed to emit connection.created telemetry", ); } } /** - * Emits one proposed `connector.connection_updated` event when a committed + * Emits one proposed `connection.updated` event when a committed * write changed a tool-purpose connection's persisted lifecycle state * (`status` or `enabled`). Metadata-only saves, health polls, credential * rotation, and catalog refreshes do not change either field and therefore * never emit. This function never throws. */ -export function emitConnectorConnectionUpdated( +export function emitConnectionUpdated( connection: ToolConnectionRow, previous: Pick, changeSource: ConnectorChangeSource, @@ -130,7 +130,7 @@ export function emitConnectorConnectionUpdated( ) { return; } - trackConnectorConnectionUpdated(client, { + trackConnectionUpdated(client, { connector_key: connectorKeyForConnection(connection), transport: connection.transport, auth_kind: connection.authKind, @@ -143,14 +143,16 @@ export function emitConnectorConnectionUpdated( } catch (err) { logger.warn( { err, connectionId: connection.id }, - "failed to emit connector.connection_updated telemetry", + "failed to emit connection.updated telemetry", ); } } /** - * Emits one proposed `connector.invocation_completed` event for an invocation - * a caller already wrote to a terminal status. Never await it: like + * Emits one proposed `connection.invoked` event for an invocation + * a caller already wrote to a terminal status. Despite the name, this is a + * completion event: it records finished invocation attempts and their + * terminal status, never invocation starts. Never await it: like * `agent.task_run`, this is best-effort background work and must not delay * the caller's own response or lifecycle writes. * @@ -175,14 +177,14 @@ export function emitConnectorConnectionUpdated( * replay that re-writes a terminal status emits again. Deduplication state * is out of scope for this proposal. */ -export async function emitConnectorInvocationCompleted( +export async function emitConnectionInvoked( db: Db, invocationId: string, ): Promise { try { const client = getTelemetryClient(); if (!client) return; - if (!client.isRegisteredEventName("connector.invocation_completed")) return; + if (!client.isRegisteredEventName("connection.invoked")) return; const joined = await db .select({ invocation: toolInvocations, connection: toolConnections }) @@ -209,7 +211,7 @@ export async function emitConnectorInvocationCompleted( ? Math.max(0, Math.round((completedAtMs - startedAtMs) / 1000)) : undefined; - trackConnectorInvocationCompleted(client, { + trackConnectionInvoked(client, { connector_key: connectorKeyForConnection(connection), transport: connection.transport, status: invocation.status, @@ -219,7 +221,7 @@ export async function emitConnectorInvocationCompleted( } catch (err) { logger.warn( { err, invocationId }, - "failed to emit connector.invocation_completed telemetry", + "failed to emit connection.invoked telemetry", ); } } diff --git a/server/src/services/tool-access.ts b/server/src/services/tool-access.ts index f8a00882a00..a5f126bddba 100644 --- a/server/src/services/tool-access.ts +++ b/server/src/services/tool-access.ts @@ -3,8 +3,8 @@ import { instanceSettingsService } from "./instance-settings.js"; import { githubBotRequest } from "./chat-github-client.js"; import { syncConnectionCredentialBindings } from "./connection-credential-bindings.js"; import { - emitConnectorConnectionCreated, - emitConnectorConnectionUpdated, + emitConnectionCreated, + emitConnectionUpdated, } from "./connector-telemetry.js"; import { canBrowseProjectRepositoryGrant, mergeProjectRepository } from "./project-repositories.js"; import { captureRunIdentity } from "./run-identity.js"; @@ -6414,7 +6414,7 @@ export function toolAccessService( return { connection: updatedConnection, applicationArchived }; }); - emitConnectorConnectionUpdated(archived.connection, connection, "archive"); + emitConnectionUpdated(archived.connection, connection, "archive"); // Only now, with every access path closed, revoke the credentials. Each // `secrets.remove` marks the row deleted before it calls the provider, so a @@ -8017,7 +8017,7 @@ export function toolAccessService( await ensureDefaultOrganizationGrant(updated); await syncCredentialBindings(updated); await ensureRuntimeSlot(updated); - emitConnectorConnectionUpdated(updated, existing, "example"); + emitConnectionUpdated(updated, existing, "example"); return { row: updated, created: false }; } const connectionId = randomUUID(); @@ -8046,7 +8046,7 @@ export function toolAccessService( await ensureDefaultOrganizationGrant(created); await syncCredentialBindings(created); await ensureRuntimeSlot(created); - emitConnectorConnectionCreated(created, "example"); + emitConnectionCreated(created, "example"); return { row: created, created: true }; } @@ -10391,7 +10391,7 @@ export function toolAccessService( .returning(); if (updated) { await syncCredentialBindings(updated); - emitConnectorConnectionUpdated(updated, connection, "credential_refresh"); + emitConnectionUpdated(updated, connection, "credential_refresh"); } return updated ?? null; } @@ -11280,7 +11280,7 @@ export function toolAccessService( ) .returning(); await syncCredentialBindings(reauthorizationRequired); - emitConnectorConnectionUpdated( + emitConnectionUpdated( reauthorizationRequired, latestConnection, "credential_refresh", @@ -12735,13 +12735,13 @@ export function toolAccessService( .returning(); } if (revivedConnectionPrevious) { - emitConnectorConnectionUpdated( + emitConnectionUpdated( connectionRow, revivedConnectionPrevious, - "gallery_setup", + "gallery", ); } else { - emitConnectorConnectionCreated(connectionRow, "gallery"); + emitConnectionCreated(connectionRow, "gallery"); } if (personalIdentityUserId) { // "Just me" (PAP-17835 seam #4). The credential is committed straight to @@ -13768,10 +13768,10 @@ export function toolAccessService( return { profileId, profileBindings, policies, updatedConnection }; }); - emitConnectorConnectionUpdated( + emitConnectionUpdated( transactionResult.updatedConnection, connection, - "gallery_setup", + "gallery", ); const details = await profileDetails( @@ -15496,7 +15496,7 @@ export function toolAccessService( ), ) .returning(); - emitConnectorConnectionUpdated( + emitConnectionUpdated( connection, preActivationLifecycle, "oauth_callback", @@ -15875,7 +15875,7 @@ export function toolAccessService( tx, ); }); - emitConnectorConnectionUpdated( + emitConnectionUpdated( connection, preCallbackLifecycle, "oauth_callback", @@ -16096,7 +16096,7 @@ export function toolAccessService( await ensureDefaultOrganizationGrant(connection, tx); await syncCredentialBindings(connection, [], tx); }); - emitConnectorConnectionUpdated( + emitConnectionUpdated( connection, preCallbackLifecycle, "oauth_callback", @@ -17389,7 +17389,7 @@ export function toolAccessService( await ensureDefaultOrganizationGrant(row); await syncCredentialBindings(row); await ensureRuntimeSlot(row); - emitConnectorConnectionCreated(row, "api"); + emitConnectionCreated(row, "api"); return toConnection(row); }, @@ -18280,7 +18280,7 @@ export function toolAccessService( .returning(); await syncCredentialBindings(row); await ensureRuntimeSlot(row); - emitConnectorConnectionUpdated(row, existing, "update_api"); + emitConnectionUpdated(row, existing, "api"); return toConnection(row); }, diff --git a/server/src/services/tool-action-review.ts b/server/src/services/tool-action-review.ts index bafa0485055..b429b35ce69 100644 --- a/server/src/services/tool-action-review.ts +++ b/server/src/services/tool-action-review.ts @@ -9,7 +9,7 @@ import { } from "@paperclipai/db"; import { conflict, forbidden, notFound } from "../errors.js"; import { assertIssueThreadInteractionResolverAudience } from "./issue-thread-interaction-resolution.js"; -import { emitConnectorInvocationCompleted } from "./connector-telemetry.js"; +import { emitConnectionInvoked } from "./connector-telemetry.js"; import { toolAccessPolicyService } from "./tool-access-policy.js"; import { logActivity, @@ -242,7 +242,7 @@ export async function commitToolActionReview( // transaction above; emit only after that commit. Approvals stay in-flight // and reach their terminal status in the gateway execution paths. if (input.decision === "rejected") - void emitConnectorInvocationCompleted(db, source.invocationId); + void emitConnectionInvoked(db, source.invocationId); for (const publication of publications) publishActivity(publication); return result; } diff --git a/server/src/services/tool-gateway.ts b/server/src/services/tool-gateway.ts index 1f097386e36..c815717f19b 100644 --- a/server/src/services/tool-gateway.ts +++ b/server/src/services/tool-gateway.ts @@ -8,7 +8,7 @@ import { githubGuestBotConnectionForSession, githubBotToolsForSession } from "./ import { githubChatReviewService } from "./chat-github-reviews.js"; import { runIdentityContexts } from "@paperclipai/db"; import { captureRunIdentity } from "./run-identity.js"; -import { emitConnectorInvocationCompleted } from "./connector-telemetry.js"; +import { emitConnectionInvoked } from "./connector-telemetry.js"; import { resolveManagedGitHubIdentitySelection } from "./git-credentials.js"; import { extractRemoteMcpPending } from "./remote-mcp-pending.js"; import { logger } from "../middleware/logger.js"; @@ -2224,7 +2224,7 @@ export function createToolGatewayService( updatedAt: new Date(), }) .where(eq(toolInvocations.id, input.invocation.id)); - void emitConnectorInvocationCompleted(db, input.invocation.id); + void emitConnectionInvoked(db, input.invocation.id); await writeToolCallEvent({ invocationId: input.invocation.id, actionRequestId: input.actionRequest?.id ?? null, @@ -2262,7 +2262,7 @@ export function createToolGatewayService( updatedAt: new Date(), }) .where(eq(toolInvocations.id, input.invocation.id)); - void emitConnectorInvocationCompleted(db, input.invocation.id); + void emitConnectionInvoked(db, input.invocation.id); throw new ToolGatewayHttpError( 500, "Approval request was not created", @@ -2312,7 +2312,7 @@ export function createToolGatewayService( updatedAt: new Date(), }) .where(eq(toolInvocations.id, input.invocation.id)); - void emitConnectorInvocationCompleted(db, input.invocation.id); + void emitConnectionInvoked(db, input.invocation.id); throw new ToolGatewayHttpError( 500, error.message, @@ -2479,7 +2479,7 @@ export function createToolGatewayService( updatedAt: new Date(), }) .where(eq(toolInvocations.id, input.invocation.id)); - void emitConnectorInvocationCompleted(db, input.invocation.id); + void emitConnectionInvoked(db, input.invocation.id); throw new ToolGatewayHttpError( 409, "The approval request was resolved before it could be signed", @@ -7185,7 +7185,7 @@ export function createToolGatewayService( // After the bookkeeping above: a throw there is caught below, overwrites // the row to failed, and emits — an earlier success emit would make one // execution report both outcomes. - void emitConnectorInvocationCompleted(db, args.invocationId); + void emitConnectionInvoked(db, args.invocationId); return { decision: "allowed" as const, invocationId: args.invocationId, @@ -7215,7 +7215,7 @@ export function createToolGatewayService( updatedAt: new Date(), }) .where(eq(toolInvocations.id, args.invocationId)); - void emitConnectorInvocationCompleted(db, args.invocationId); + void emitConnectionInvoked(db, args.invocationId); await writeToolCallEvent({ invocationId: args.invocationId, session: args.session, @@ -7330,7 +7330,7 @@ export function createToolGatewayService( updatedAt: new Date(), }) .where(eq(toolInvocations.id, invocation.id)); - void emitConnectorInvocationCompleted(db, invocation.id); + void emitConnectionInvoked(db, invocation.id); await reflectToolActionInteractionLifecycle({ actionRequestId, status: "failed", @@ -7374,7 +7374,7 @@ export function createToolGatewayService( updatedAt: new Date(), }) .where(eq(toolInvocations.id, invocation.id)); - void emitConnectorInvocationCompleted(db, invocation.id); + void emitConnectionInvoked(db, invocation.id); await reflectToolActionInteractionLifecycle({ actionRequestId, status: "failed", @@ -7540,7 +7540,7 @@ export function createToolGatewayService( return true; }); if (!settled) return { reasonCode, message, settled: false }; - void emitConnectorInvocationCompleted(db, input.invocationId); + void emitConnectionInvoked(db, input.invocationId); await reflectToolActionInteractionLifecycle({ actionRequestId: input.actionRequestId, status: "failed", @@ -7593,7 +7593,7 @@ export function createToolGatewayService( updatedAt: now, }) .where(eq(toolInvocations.id, input.invocationId)); - void emitConnectorInvocationCompleted(db, input.invocationId); + void emitConnectionInvoked(db, input.invocationId); await reflectToolActionInteractionLifecycle({ actionRequestId: expired.id, status: "expired", @@ -7990,7 +7990,7 @@ export function createToolGatewayService( updatedAt: now, }) .where(eq(toolInvocations.id, invocation.id)); - void emitConnectorInvocationCompleted(db, invocation.id); + void emitConnectionInvoked(db, invocation.id); await db .update(toolActionRequests) .set({ status: "executed", resolvedAt: now, updatedAt: now }) @@ -9060,7 +9060,7 @@ export function createToolGatewayService( updatedAt: new Date(), }) .where(eq(toolInvocations.id, invocationId)); - void emitConnectorInvocationCompleted(db, invocationId); + void emitConnectionInvoked(db, invocationId); throw new ToolGatewayHttpError( 500, error.message, @@ -9135,7 +9135,7 @@ export function createToolGatewayService( if (!accessDecision.allowed) { // recordInvocation inserted this row already terminal (denied or // rate_limited), so this is its only completion boundary. - void emitConnectorInvocationCompleted(db, invocationId); + void emitConnectionInvoked(db, invocationId); await writeAudit({ session, companyId: input.companyId, @@ -9335,7 +9335,7 @@ export function createToolGatewayService( updatedAt: now, }) .where(eq(toolInvocations.id, row.invocationId)); - void emitConnectorInvocationCompleted(db, row.invocationId); + void emitConnectionInvoked(db, row.invocationId); await reflectToolActionInteractionLifecycle({ actionRequestId: row.id, status, @@ -10303,7 +10303,7 @@ export function createToolGatewayService( if (!accessDecision.allowed) { // recordInvocation inserted this row already terminal (denied or // rate_limited), so this is its only completion boundary. - void emitConnectorInvocationCompleted(db, invocationId); + void emitConnectionInvoked(db, invocationId); await writeAudit({ session, companyId: session.companyId, @@ -10511,7 +10511,7 @@ export function createToolGatewayService( // After the bookkeeping above: a throw there is caught below, overwrites // the row to failed, and emits — an earlier success emit would make one // execution report both outcomes. - void emitConnectorInvocationCompleted(db, invocationId); + void emitConnectionInvoked(db, invocationId); return { invocationId, status: "completed" as const, @@ -10568,7 +10568,7 @@ export function createToolGatewayService( updatedAt: completedAt, }) .where(eq(toolInvocations.id, invocationId)); - void emitConnectorInvocationCompleted(db, invocationId); + void emitConnectionInvoked(db, invocationId); if (input.approvedActionRequestId) { const [failedRequest] = await db .update(toolActionRequests) @@ -10752,7 +10752,7 @@ export function createToolGatewayService( if (!accessDecision.allowed) { // recordInvocation inserted this row already terminal (denied or // rate_limited), so this is its only completion boundary. - void emitConnectorInvocationCompleted(db, invocationId); + void emitConnectionInvoked(db, invocationId); await writeAudit({ session: sessionLike, companyId: input.runContext.companyId, @@ -10870,7 +10870,7 @@ export function createToolGatewayService( // After the bookkeeping above: a throw there is caught below, overwrites // the row to failed, and emits — an earlier success emit would make one // execution report both outcomes. - void emitConnectorInvocationCompleted(db, invocationId); + void emitConnectionInvoked(db, invocationId); return resultValidation.value as typeof result; } catch (err) { const status = err instanceof ToolGatewayHttpError ? err.status : 502; @@ -10891,7 +10891,7 @@ export function createToolGatewayService( updatedAt: new Date(), }) .where(eq(toolInvocations.id, invocationId)); - void emitConnectorInvocationCompleted(db, invocationId); + void emitConnectionInvoked(db, invocationId); await writeToolCallEvent({ invocationId, session: sessionLike,