mirror of
https://github.com/paperclipai/paperclip.git
synced 2026-10-26 02:00:52 +01:00
rename proposed connector events to connection.* and normalize change_source values
User-authorized naming revision (PAP-24): connector.connection_created -> connection.created, connector.connection_updated -> connection.updated, connector.invocation_completed -> connection.invoked; change_source values update_api -> api and gallery_setup -> gallery, matching setup_flow naming. Wrapper and emitter helpers renamed to match (trackConnection*/emitConnection*). connection.invoked keeps the terminal meaning: it records completed invocation attempts and their terminal status, never invocation starts, now stated explicitly in the proposal marker and emitter doc. Triggers, properties, suppression, privacy, identity, and delivery behavior are unchanged; no dual-emit — these events are unmerged and unreleased. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
This commit is contained in:
1 parent
6ec2221659
commit
fb60a5ee53
10 files changed
+119
-117
No files matched your search
@@ -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,
|
||||
);
|
||||
}
|
||||
|
||||
@@ -18,9 +18,9 @@ export {
|
||||
trackErrorHandlerCrash,
|
||||
trackInteractionCreated,
|
||||
trackInteractionResolved,
|
||||
trackConnectorConnectionCreated,
|
||||
trackConnectorConnectionUpdated,
|
||||
trackConnectorInvocationCompleted,
|
||||
trackConnectionCreated,
|
||||
trackConnectionUpdated,
|
||||
trackConnectionInvoked,
|
||||
} from "./events.js";
|
||||
export type {
|
||||
TelemetryConfig,
|
||||
|
||||
@@ -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);
|
||||
});
|
||||
|
||||
|
||||
@@ -40,11 +40,11 @@ const describeEmbeddedPostgres = embeddedPostgresSupport.supported
|
||||
type Db = ReturnType<typeof createDb>;
|
||||
|
||||
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",
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -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);
|
||||
});
|
||||
|
||||
@@ -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<ToolConnectionRow, "connectionPurpose">)
|
||||
}
|
||||
|
||||
/**
|
||||
* 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<ToolConnectionRow, "status" | "enabled">,
|
||||
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<void> {
|
||||
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",
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -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);
|
||||
},
|
||||
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
@@ -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,
|
||||
|
||||
Reference in new issue
Block a user