diff --git a/backend/src/lib/copilot-transform.ts b/backend/src/lib/copilot-transform.ts index e00015d..b1010ad 100644 --- a/backend/src/lib/copilot-transform.ts +++ b/backend/src/lib/copilot-transform.ts @@ -5,6 +5,7 @@ */ import { llm } from './llm.js'; +import { trackEvent } from './telemetry.js'; function withTimeout(promise: Promise, ms: number): Promise { return Promise.race([ @@ -72,7 +73,10 @@ export async function runCopilotTransform(action: CopilotAction, text: string): maxTokens: 4096, }), 60_000); const out = result.content.trim(); - if (out.length > 0) return out; + if (out.length > 0) { + trackEvent('copilot_transform', 'system', { action, durationMs: String(Date.now()) }); + return out; + } break; // empty response — fall through to heuristic } catch (err: unknown) { const isRateLimit = err instanceof Error && (err.message.includes('429') || err.message.includes('rate')); diff --git a/backend/src/modules/note-prompts/routes.ts b/backend/src/modules/note-prompts/routes.ts index 1d04b38..54a697e 100644 --- a/backend/src/modules/note-prompts/routes.ts +++ b/backend/src/modules/note-prompts/routes.ts @@ -7,6 +7,7 @@ import { z } from 'zod'; import { getUserId, getRequestProductId } from '../../lib/request-context.js'; import { BadRequestError, NotFoundError } from '@bytelyst/errors'; import { isFeatureEnabled } from '../../lib/feature-flags.js'; +import { trackEvent } from '../../lib/telemetry.js'; import { embedText, cosineSimilarity, stripHtmlForEmbedding } from '../../lib/embeddings.js'; import { llm } from '../../lib/llm.js'; import { @@ -46,6 +47,7 @@ export async function notePromptRoutes(app: FastifyInstance): Promise { const userId = getUserId(req); const input = CreatePromptTemplateSchema.parse(req.body); const created = await repo.createPromptTemplate(userId, input); + trackEvent('smart_action_template_created', userId, { category: input.category ?? 'transform', inputType: input.inputType ?? 'text' }); reply.code(201); return created; }); @@ -381,6 +383,7 @@ Return ONLY valid JSON, no other text.`, content = lines.slice(1).join('\n').trim(); } + trackEvent('url_extract_completed', userId, { domain: new URL(input.url).hostname, wordCount: String(rawText.split(/\s+/).length) }); return { title, content, url: input.url, summarized: true, model: result.model, usage: result.usage }; }); diff --git a/backend/src/modules/note-prompts/runner.ts b/backend/src/modules/note-prompts/runner.ts index 23d6e37..c2ed6ab 100644 --- a/backend/src/modules/note-prompts/runner.ts +++ b/backend/src/modules/note-prompts/runner.ts @@ -4,6 +4,7 @@ import { llm } from '../../lib/llm.js'; import { config } from '../../lib/config.js'; +import { trackEvent } from '../../lib/telemetry.js'; import { buildVisionMessage, hasVisionContent, @@ -101,10 +102,23 @@ export async function executePrompt( output.approvalState = 'applied'; } + trackEvent('smart_action_run', 'system', { + templateSlug: template.slug, + inputType: template.inputType, + model: result.model, + totalTokens: String(result.usage.totalTokens), + }); + trackEvent('smart_action_result_saved', 'system', { + outputAction: template.outputType, + resultType: output.approvalState ?? 'applied', + }); + return output; } catch (err: unknown) { lastError = err; - const isRateLimit = err instanceof Error && (err.message.includes('429') || err.message.includes('rate')); + const errorType = err instanceof Error ? (err.message.includes('429') ? 'rate_limit' : err.message.includes('timed out') ? 'timeout' : 'llm_error') : 'unknown'; + trackEvent('smart_action_error', 'system', { errorType, templateSlug: template.slug }); + const isRateLimit = errorType === 'rate_limit'; if (isRateLimit && attempt < maxRetries - 1) { await new Promise((r) => setTimeout(r, baseDelayMs * Math.pow(2, attempt))); continue; diff --git a/backend/src/modules/note-prompts/scheduler.ts b/backend/src/modules/note-prompts/scheduler.ts index 6203331..098220c 100644 --- a/backend/src/modules/note-prompts/scheduler.ts +++ b/backend/src/modules/note-prompts/scheduler.ts @@ -13,6 +13,7 @@ import { BadRequestError, NotFoundError } from '@bytelyst/errors'; import { getCollection } from '../../lib/datastore.js'; import { PRODUCT_ID } from '../../lib/product-config.js'; import { isFeatureEnabled } from '../../lib/feature-flags.js'; +import { trackEvent } from '../../lib/telemetry.js'; import { llm } from '../../lib/llm.js'; import * as noteRepo from '../notes/repository.js'; import * as promptRepo from './repository.js'; @@ -212,6 +213,7 @@ export async function runSchedulerTick(): Promise { updatedAt: new Date().toISOString(), }); + trackEvent('scheduled_action_fired', schedule.userId, { scheduleId: schedule.id, templateSlug: template?.slug ?? schedule.templateId }); ran++; } catch (err: unknown) { const msg = err instanceof Error ? err.message : 'Unknown scheduler error'; @@ -413,6 +415,7 @@ export async function promptSchedulerRoutes(app: FastifyInstance): Promise updatedAt: new Date().toISOString(), }); + trackEvent('webhook_triggered', userId, { webhookId: id, triggerEvent: webhook.triggerEvent }); return { triggered: true, webhookId: id, result }; });