@clinebot/core 0.0.6 → 0.0.10

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (83) hide show
  1. package/dist/agents/hooks-config-loader.d.ts +1 -0
  2. package/dist/auth/cline.d.ts +2 -0
  3. package/dist/auth/codex.d.ts +5 -1
  4. package/dist/auth/oca.d.ts +7 -1
  5. package/dist/auth/types.d.ts +2 -0
  6. package/dist/index.d.ts +3 -1
  7. package/dist/index.node.d.ts +2 -0
  8. package/dist/index.node.js +164 -162
  9. package/dist/input/mention-enricher.d.ts +1 -0
  10. package/dist/providers/local-provider-service.d.ts +1 -1
  11. package/dist/runtime/session-runtime.d.ts +1 -1
  12. package/dist/session/default-session-manager.d.ts +13 -17
  13. package/dist/session/rpc-spawn-lease.d.ts +7 -0
  14. package/dist/session/runtime-oauth-token-manager.d.ts +4 -2
  15. package/dist/session/session-agent-events.d.ts +15 -0
  16. package/dist/session/session-config-builder.d.ts +13 -0
  17. package/dist/session/session-manager.d.ts +2 -2
  18. package/dist/session/session-team-coordination.d.ts +12 -0
  19. package/dist/session/session-telemetry.d.ts +9 -0
  20. package/dist/session/unified-session-persistence-service.d.ts +12 -16
  21. package/dist/session/utils/helpers.d.ts +1 -1
  22. package/dist/session/utils/types.d.ts +1 -1
  23. package/dist/storage/provider-settings-legacy-migration.d.ts +25 -0
  24. package/dist/telemetry/core-events.d.ts +122 -0
  25. package/dist/tools/definitions.d.ts +1 -1
  26. package/dist/tools/executors/file-read.d.ts +1 -1
  27. package/dist/tools/index.d.ts +1 -1
  28. package/dist/tools/presets.d.ts +1 -1
  29. package/dist/tools/schemas.d.ts +48 -11
  30. package/dist/tools/types.d.ts +3 -3
  31. package/dist/types/config.d.ts +1 -1
  32. package/dist/types/events.d.ts +1 -1
  33. package/dist/types/provider-settings.d.ts +4 -4
  34. package/dist/types.d.ts +1 -1
  35. package/package.json +4 -3
  36. package/src/agents/hooks-config-loader.ts +2 -0
  37. package/src/auth/cline.ts +35 -1
  38. package/src/auth/codex.ts +27 -2
  39. package/src/auth/oca.ts +31 -4
  40. package/src/auth/types.ts +3 -0
  41. package/src/index.node.ts +4 -0
  42. package/src/index.ts +27 -0
  43. package/src/input/file-indexer.test.ts +40 -0
  44. package/src/input/file-indexer.ts +21 -0
  45. package/src/input/mention-enricher.test.ts +3 -0
  46. package/src/input/mention-enricher.ts +3 -0
  47. package/src/providers/local-provider-service.ts +6 -7
  48. package/src/runtime/hook-file-hooks.test.ts +51 -1
  49. package/src/runtime/hook-file-hooks.ts +91 -11
  50. package/src/runtime/session-runtime.ts +1 -1
  51. package/src/session/default-session-manager.e2e.test.ts +2 -1
  52. package/src/session/default-session-manager.ts +367 -601
  53. package/src/session/rpc-spawn-lease.test.ts +49 -0
  54. package/src/session/rpc-spawn-lease.ts +122 -0
  55. package/src/session/runtime-oauth-token-manager.ts +21 -14
  56. package/src/session/session-agent-events.ts +159 -0
  57. package/src/session/session-config-builder.ts +111 -0
  58. package/src/session/session-graph.ts +2 -0
  59. package/src/session/session-host.ts +21 -0
  60. package/src/session/session-manager.ts +2 -2
  61. package/src/session/session-team-coordination.ts +198 -0
  62. package/src/session/session-telemetry.ts +95 -0
  63. package/src/session/unified-session-persistence-service.test.ts +81 -0
  64. package/src/session/unified-session-persistence-service.ts +470 -469
  65. package/src/session/utils/helpers.ts +1 -1
  66. package/src/session/utils/types.ts +1 -1
  67. package/src/storage/provider-settings-legacy-migration.test.ts +133 -1
  68. package/src/storage/provider-settings-legacy-migration.ts +63 -11
  69. package/src/telemetry/core-events.ts +344 -0
  70. package/src/tools/definitions.test.ts +203 -36
  71. package/src/tools/definitions.ts +66 -28
  72. package/src/tools/executors/editor.test.ts +35 -0
  73. package/src/tools/executors/editor.ts +33 -46
  74. package/src/tools/executors/file-read.test.ts +29 -5
  75. package/src/tools/executors/file-read.ts +17 -6
  76. package/src/tools/index.ts +2 -0
  77. package/src/tools/presets.ts +1 -1
  78. package/src/tools/schemas.ts +88 -38
  79. package/src/tools/types.ts +7 -3
  80. package/src/types/config.ts +1 -1
  81. package/src/types/events.ts +6 -1
  82. package/src/types/provider-settings.ts +6 -6
  83. package/src/types.ts +1 -1
@@ -1,5 +1,5 @@
1
1
  import type { AgentConfig, AgentEvent, AgentResult } from "@clinebot/agents";
2
- import type { providers as LlmsProviders } from "@clinebot/llms";
2
+ import type { LlmsProviders } from "@clinebot/llms";
3
3
  import type { SessionSource } from "../../types/common";
4
4
  import type { SessionRecord } from "../../types/sessions";
5
5
  import { nowIso } from "../session-artifacts";
@@ -1,5 +1,5 @@
1
1
  import type { Agent } from "@clinebot/agents";
2
- import type { providers as LlmsProviders } from "@clinebot/llms";
2
+ import type { LlmsProviders } from "@clinebot/llms";
3
3
  import type { BuiltRuntime } from "../../runtime/session-runtime";
4
4
  import type { SessionSource } from "../../types/common";
5
5
  import type { CoreSessionConfig } from "../../types/config";
@@ -2,7 +2,11 @@ import { mkdtempSync, rmSync, writeFileSync } from "node:fs";
2
2
  import os from "node:os";
3
3
  import path from "node:path";
4
4
  import { afterEach, describe, expect, it } from "vitest";
5
- import { migrateLegacyProviderSettings } from "./provider-settings-legacy-migration";
5
+ import {
6
+ type LegacyClineUserInfo,
7
+ migrateLegacyProviderSettings,
8
+ resolveLegacyClineAuth,
9
+ } from "./provider-settings-legacy-migration";
6
10
  import { ProviderSettingsManager } from "./provider-settings-manager";
7
11
 
8
12
  describe("migrateLegacyProviderSettings", () => {
@@ -173,3 +177,131 @@ describe("migrateLegacyProviderSettings", () => {
173
177
  );
174
178
  });
175
179
  });
180
+
181
+ // =============================================================================
182
+ // resolveLegacyClineAuth – pure in-memory tests
183
+ // =============================================================================
184
+
185
+ /** Builds a realistic LegacyClineUserInfo JSON string. */
186
+ function makeClineAccountJson(
187
+ overrides: Partial<LegacyClineUserInfo> & { userId?: string } = {},
188
+ ): string {
189
+ return JSON.stringify({
190
+ idToken: overrides.idToken ?? "id-token-abc",
191
+ expiresAt: overrides.expiresAt ?? 1750000000000,
192
+ refreshToken: overrides.refreshToken ?? "refresh-token-xyz",
193
+ userInfo: overrides.userInfo ?? {
194
+ id: overrides.userId ?? "user-42",
195
+ email: "test@example.com",
196
+ displayName: "Test User",
197
+ termsAcceptedAt: "2025-01-01T00:00:00Z",
198
+ clineBenchConsent: false,
199
+ createdAt: "2025-01-01T00:00:00Z",
200
+ updatedAt: "2025-01-01T00:00:00Z",
201
+ },
202
+ provider: overrides.provider ?? "google",
203
+ startedAt: overrides.startedAt ?? Date.now(),
204
+ } satisfies LegacyClineUserInfo);
205
+ }
206
+
207
+ describe("resolveLegacyClineAuth", () => {
208
+ it("extracts all auth fields from a complete legacy account JSON", () => {
209
+ const result = resolveLegacyClineAuth(
210
+ makeClineAccountJson({
211
+ idToken: "my-id-token",
212
+ expiresAt: 1750000000000,
213
+ refreshToken: "my-refresh",
214
+ userId: "user-123",
215
+ }),
216
+ );
217
+
218
+ expect(result).toEqual({
219
+ accessToken: "my-id-token",
220
+ refreshToken: "my-refresh",
221
+ expiresAt: 1750000000000,
222
+ accountId: "user-123",
223
+ });
224
+ });
225
+
226
+ it("maps idToken to accessToken", () => {
227
+ const result = resolveLegacyClineAuth(
228
+ makeClineAccountJson({ idToken: "tok-abc" }),
229
+ );
230
+ expect(result?.accessToken).toBe("tok-abc");
231
+ });
232
+
233
+ it("preserves expiresAt as a number", () => {
234
+ const result = resolveLegacyClineAuth(
235
+ makeClineAccountJson({ expiresAt: 9999999999999 }),
236
+ );
237
+ expect(result?.expiresAt).toBe(9999999999999);
238
+ expect(typeof result?.expiresAt).toBe("number");
239
+ });
240
+
241
+ it("maps userInfo.id to accountId", () => {
242
+ const result = resolveLegacyClineAuth(
243
+ makeClineAccountJson({ userId: "uid-xyz" }),
244
+ );
245
+ expect(result?.accountId).toBe("uid-xyz");
246
+ });
247
+
248
+ it("returns undefined accountId when userInfo is missing entirely", () => {
249
+ const raw = JSON.stringify({
250
+ idToken: "tok",
251
+ expiresAt: 1000,
252
+ refreshToken: "ref",
253
+ provider: "google",
254
+ startedAt: 1,
255
+ });
256
+
257
+ const result = resolveLegacyClineAuth(raw);
258
+ expect(result).toBeDefined();
259
+ expect(result?.accessToken).toBe("tok");
260
+ expect(result?.accountId).toBeUndefined();
261
+ });
262
+
263
+ it("returns undefined accountId when userInfo.id is missing", () => {
264
+ const raw = JSON.stringify({
265
+ idToken: "tok",
266
+ expiresAt: 1000,
267
+ refreshToken: "ref",
268
+ userInfo: {
269
+ email: "x@y.com",
270
+ displayName: "X",
271
+ termsAcceptedAt: "2025-01-01T00:00:00Z",
272
+ clineBenchConsent: false,
273
+ createdAt: "2025-01-01T00:00:00Z",
274
+ updatedAt: "2025-01-01T00:00:00Z",
275
+ },
276
+ provider: "google",
277
+ startedAt: 1,
278
+ });
279
+
280
+ const result = resolveLegacyClineAuth(raw);
281
+ expect(result).toBeDefined();
282
+ expect(result?.accountId).toBeUndefined();
283
+ });
284
+
285
+ it("returns undefined for invalid json", () => {
286
+ expect(resolveLegacyClineAuth(undefined)).toBeUndefined();
287
+ expect(resolveLegacyClineAuth("")).toBeUndefined();
288
+ expect(resolveLegacyClineAuth(" \n\t ")).toBeUndefined();
289
+ expect(resolveLegacyClineAuth("not-json{{{")).toBeUndefined();
290
+ expect(resolveLegacyClineAuth("null")).toBeUndefined();
291
+ });
292
+
293
+ it("returns undefined fields when idToken/refreshToken are missing from JSON", () => {
294
+ const raw = JSON.stringify({
295
+ userInfo: { id: "uid" },
296
+ provider: "google",
297
+ startedAt: 1,
298
+ });
299
+
300
+ const result = resolveLegacyClineAuth(raw);
301
+ expect(result).toBeDefined();
302
+ expect(result?.accessToken).toBeUndefined();
303
+ expect(result?.refreshToken).toBeUndefined();
304
+ expect(result?.expiresAt).toBeUndefined();
305
+ expect(result?.accountId).toBe("uid");
306
+ });
307
+ });
@@ -1,6 +1,6 @@
1
1
  import { existsSync, readFileSync } from "node:fs";
2
2
  import { join } from "node:path";
3
- import { models, providers } from "@clinebot/llms";
3
+ import { LlmsModels, LlmsProviders } from "@clinebot/llms";
4
4
  import { resolveClineDataDir } from "@clinebot/shared/storage";
5
5
  import type { ProviderSettings } from "../types/provider-settings";
6
6
  import { emptyStoredProviderSettings } from "../types/provider-settings";
@@ -162,6 +162,53 @@ export interface MigrateLegacyProviderSettingsResult {
162
162
  lastUsedProvider?: string;
163
163
  }
164
164
 
165
+ export type LegacyClineUserInfo = {
166
+ idToken: string;
167
+ expiresAt: number;
168
+ refreshToken: string;
169
+ userInfo: {
170
+ id: string;
171
+ email: string;
172
+ displayName: string;
173
+ termsAcceptedAt: string;
174
+ clineBenchConsent: boolean;
175
+ createdAt: string;
176
+ updatedAt: string;
177
+ };
178
+ provider: string;
179
+ startedAt: number;
180
+ };
181
+
182
+ /**
183
+ * Resolves legacy Cline account auth data from the raw `cline:clineAccountId`
184
+ * secret string into the auth fields used by `ProviderSettings`.
185
+ *
186
+ * Returns `undefined` when the input is missing, empty, whitespace-only, or
187
+ * unparseable JSON.
188
+ */
189
+ export function resolveLegacyClineAuth(
190
+ rawAccountData: string | undefined,
191
+ ): ProviderSettings["auth"] | undefined {
192
+ const trimmed = rawAccountData?.trim();
193
+ if (!trimmed) {
194
+ return undefined;
195
+ }
196
+ try {
197
+ const data = JSON.parse(trimmed) as LegacyClineUserInfo;
198
+ if (!data) {
199
+ return undefined;
200
+ }
201
+ return {
202
+ accessToken: data.idToken,
203
+ refreshToken: data.refreshToken,
204
+ expiresAt: data.expiresAt,
205
+ accountId: data.userInfo?.id,
206
+ };
207
+ } catch {
208
+ return undefined;
209
+ }
210
+ }
211
+
165
212
  function trimNonEmpty(value: string | undefined): string | undefined {
166
213
  const trimmed = value?.trim();
167
214
  return trimmed ? trimmed : undefined;
@@ -326,7 +373,7 @@ function resolveLegacyCodexAuth(
326
373
  }
327
374
 
328
375
  function getDefaultModelForProvider(providerId: string): string | undefined {
329
- const builtInModels = models.getGeneratedModelsForProvider(providerId);
376
+ const builtInModels = LlmsModels.getGeneratedModelsForProvider(providerId);
330
377
  const firstModelId = Object.keys(builtInModels)[0];
331
378
  return firstModelId ?? undefined;
332
379
  }
@@ -400,14 +447,19 @@ function buildLegacyProviderSettings(
400
447
  Object.assign(providerSpecific, resolveLegacyCodexAuth(legacySecrets));
401
448
  }
402
449
  if (providerId === "cline") {
403
- const accountId = trimNonEmpty(
404
- legacySecrets["cline:clineAccountId"] ?? legacySecrets.clineAccountId,
405
- );
406
- if (accountId) {
407
- providerSpecific.auth = {
408
- ...(providerSpecific.auth ?? {}),
409
- accountId,
410
- };
450
+ try {
451
+ const legacyAuthString = trimNonEmpty(
452
+ legacySecrets["cline:clineAccountId"],
453
+ );
454
+
455
+ if (legacyAuthString) {
456
+ providerSpecific.auth = {
457
+ ...(providerSpecific.auth ?? {}),
458
+ ...resolveLegacyClineAuth(legacyAuthString),
459
+ };
460
+ }
461
+ } catch {
462
+ // Failed to parse stored cline auth data
411
463
  }
412
464
  }
413
465
  if (providerId === "openai" && legacyGlobalState.openAiHeaders) {
@@ -516,7 +568,7 @@ function buildLegacyProviderSettings(
516
568
  ...(timeout ? { timeout } : {}),
517
569
  ...providerSpecific,
518
570
  };
519
- const parsed = providers.ProviderSettingsSchema.safeParse(settings);
571
+ const parsed = LlmsProviders.ProviderSettingsSchema.safeParse(settings);
520
572
  if (!parsed.success) {
521
573
  return undefined;
522
574
  }
@@ -0,0 +1,344 @@
1
+ import type { ITelemetryService, TelemetryProperties } from "@clinebot/shared";
2
+
3
+ const MAX_ERROR_MESSAGE_LENGTH = 500;
4
+
5
+ export const LegacyTelemetryEvents = {
6
+ USER: {
7
+ AUTH_STARTED: "user.auth_started",
8
+ AUTH_SUCCEEDED: "user.auth_succeeded",
9
+ AUTH_FAILED: "user.auth_failed",
10
+ AUTH_LOGGED_OUT: "user.auth_logged_out",
11
+ },
12
+ TASK: {
13
+ CREATED: "task.created",
14
+ RESTARTED: "task.restarted",
15
+ COMPLETED: "task.completed",
16
+ CONVERSATION_TURN: "task.conversation_turn",
17
+ TOKEN_USAGE: "task.tokens",
18
+ MODE_SWITCH: "task.mode",
19
+ TOOL_USED: "task.tool_used",
20
+ SKILL_USED: "task.skill_used",
21
+ DIFF_EDIT_FAILED: "task.diff_edit_failed",
22
+ PROVIDER_API_ERROR: "task.provider_api_error",
23
+ MENTION_USED: "task.mention_used",
24
+ MENTION_FAILED: "task.mention_failed",
25
+ MENTION_SEARCH_RESULTS: "task.mention_search_results",
26
+ SUBAGENT_STARTED: "task.subagent_started",
27
+ SUBAGENT_COMPLETED: "task.subagent_completed",
28
+ },
29
+ HOOKS: {
30
+ DISCOVERY_COMPLETED: "hooks.discovery_completed",
31
+ },
32
+ } as const;
33
+
34
+ function emit(
35
+ telemetry: ITelemetryService | undefined,
36
+ event: string,
37
+ properties?: TelemetryProperties,
38
+ ): void {
39
+ telemetry?.capture({ event, properties });
40
+ }
41
+
42
+ function truncateErrorMessage(errorMessage?: string): string | undefined {
43
+ if (!errorMessage) {
44
+ return undefined;
45
+ }
46
+ return errorMessage.substring(0, MAX_ERROR_MESSAGE_LENGTH);
47
+ }
48
+
49
+ export function captureAuthStarted(
50
+ telemetry: ITelemetryService | undefined,
51
+ provider?: string,
52
+ ): void {
53
+ emit(telemetry, LegacyTelemetryEvents.USER.AUTH_STARTED, { provider });
54
+ }
55
+
56
+ export function captureAuthSucceeded(
57
+ telemetry: ITelemetryService | undefined,
58
+ provider?: string,
59
+ ): void {
60
+ emit(telemetry, LegacyTelemetryEvents.USER.AUTH_SUCCEEDED, { provider });
61
+ }
62
+
63
+ export function captureAuthFailed(
64
+ telemetry: ITelemetryService | undefined,
65
+ provider?: string,
66
+ errorMessage?: string,
67
+ ): void {
68
+ emit(telemetry, LegacyTelemetryEvents.USER.AUTH_FAILED, {
69
+ provider,
70
+ errorMessage: truncateErrorMessage(errorMessage),
71
+ });
72
+ }
73
+
74
+ export function captureAuthLoggedOut(
75
+ telemetry: ITelemetryService | undefined,
76
+ provider?: string,
77
+ reason?: string,
78
+ ): void {
79
+ emit(telemetry, LegacyTelemetryEvents.USER.AUTH_LOGGED_OUT, {
80
+ provider,
81
+ reason,
82
+ });
83
+ }
84
+
85
+ export function identifyAccount(
86
+ telemetry: ITelemetryService | undefined,
87
+ account: {
88
+ id?: string;
89
+ email?: string;
90
+ provider?: string;
91
+ organizationId?: string;
92
+ organizationName?: string;
93
+ memberId?: string;
94
+ },
95
+ ): void {
96
+ const distinctId = account.id?.trim();
97
+ if (distinctId) {
98
+ telemetry?.setDistinctId(distinctId);
99
+ }
100
+ telemetry?.updateCommonProperties({
101
+ account_id: account.id,
102
+ account_email: account.email,
103
+ provider: account.provider,
104
+ organization_id: account.organizationId,
105
+ organization_name: account.organizationName,
106
+ member_id: account.memberId,
107
+ });
108
+ }
109
+
110
+ export function captureTaskCreated(
111
+ telemetry: ITelemetryService | undefined,
112
+ properties: {
113
+ ulid: string;
114
+ apiProvider?: string;
115
+ openAiCompatibleDomain?: string;
116
+ },
117
+ ): void {
118
+ emit(telemetry, LegacyTelemetryEvents.TASK.CREATED, properties);
119
+ }
120
+
121
+ export function captureTaskRestarted(
122
+ telemetry: ITelemetryService | undefined,
123
+ properties: {
124
+ ulid: string;
125
+ apiProvider?: string;
126
+ openAiCompatibleDomain?: string;
127
+ },
128
+ ): void {
129
+ emit(telemetry, LegacyTelemetryEvents.TASK.RESTARTED, properties);
130
+ }
131
+
132
+ export function captureTaskCompleted(
133
+ telemetry: ITelemetryService | undefined,
134
+ properties: {
135
+ ulid: string;
136
+ provider?: string;
137
+ modelId?: string;
138
+ mode?: string;
139
+ durationMs?: number;
140
+ },
141
+ ): void {
142
+ emit(telemetry, LegacyTelemetryEvents.TASK.COMPLETED, properties);
143
+ }
144
+
145
+ export function captureConversationTurnEvent(
146
+ telemetry: ITelemetryService | undefined,
147
+ properties: {
148
+ ulid: string;
149
+ provider?: string;
150
+ model?: string;
151
+ source: "user" | "assistant";
152
+ mode?: string;
153
+ tokensIn?: number;
154
+ tokensOut?: number;
155
+ cacheWriteTokens?: number;
156
+ cacheReadTokens?: number;
157
+ totalCost?: number;
158
+ isNativeToolCall?: boolean;
159
+ },
160
+ ): void {
161
+ emit(telemetry, LegacyTelemetryEvents.TASK.CONVERSATION_TURN, {
162
+ ...properties,
163
+ timestamp: new Date().toISOString(),
164
+ });
165
+ }
166
+
167
+ export function captureTokenUsage(
168
+ telemetry: ITelemetryService | undefined,
169
+ properties: {
170
+ ulid: string;
171
+ tokensIn: number;
172
+ tokensOut: number;
173
+ model: string;
174
+ },
175
+ ): void {
176
+ emit(telemetry, LegacyTelemetryEvents.TASK.TOKEN_USAGE, properties);
177
+ }
178
+
179
+ export function captureModeSwitch(
180
+ telemetry: ITelemetryService | undefined,
181
+ ulid: string,
182
+ mode?: string,
183
+ ): void {
184
+ emit(telemetry, LegacyTelemetryEvents.TASK.MODE_SWITCH, { ulid, mode });
185
+ }
186
+
187
+ export function captureToolUsage(
188
+ telemetry: ITelemetryService | undefined,
189
+ properties: {
190
+ ulid: string;
191
+ tool: string;
192
+ modelId?: string;
193
+ provider?: string;
194
+ autoApproved?: boolean;
195
+ success: boolean;
196
+ isNativeToolCall?: boolean;
197
+ },
198
+ ): void {
199
+ emit(telemetry, LegacyTelemetryEvents.TASK.TOOL_USED, properties);
200
+ }
201
+
202
+ export function captureSkillUsed(
203
+ telemetry: ITelemetryService | undefined,
204
+ properties: {
205
+ ulid: string;
206
+ skillName: string;
207
+ skillSource: "global" | "project";
208
+ skillsAvailableGlobal: number;
209
+ skillsAvailableProject: number;
210
+ provider?: string;
211
+ modelId?: string;
212
+ },
213
+ ): void {
214
+ emit(telemetry, LegacyTelemetryEvents.TASK.SKILL_USED, properties);
215
+ }
216
+
217
+ export function captureDiffEditFailure(
218
+ telemetry: ITelemetryService | undefined,
219
+ properties: {
220
+ ulid: string;
221
+ modelId?: string;
222
+ provider?: string;
223
+ errorType?: string;
224
+ isNativeToolCall?: boolean;
225
+ },
226
+ ): void {
227
+ emit(telemetry, LegacyTelemetryEvents.TASK.DIFF_EDIT_FAILED, properties);
228
+ }
229
+
230
+ export function captureProviderApiError(
231
+ telemetry: ITelemetryService | undefined,
232
+ properties: {
233
+ ulid: string;
234
+ model: string;
235
+ errorMessage: string;
236
+ provider?: string;
237
+ errorStatus?: number;
238
+ requestId?: string;
239
+ isNativeToolCall?: boolean;
240
+ },
241
+ ): void {
242
+ emit(telemetry, LegacyTelemetryEvents.TASK.PROVIDER_API_ERROR, {
243
+ ...properties,
244
+ errorMessage: truncateErrorMessage(properties.errorMessage) ?? "unknown",
245
+ timestamp: new Date().toISOString(),
246
+ });
247
+ }
248
+
249
+ export function captureMentionUsed(
250
+ telemetry: ITelemetryService | undefined,
251
+ mentionType:
252
+ | "file"
253
+ | "folder"
254
+ | "url"
255
+ | "problems"
256
+ | "terminal"
257
+ | "git-changes"
258
+ | "commit",
259
+ contentLength?: number,
260
+ ): void {
261
+ emit(telemetry, LegacyTelemetryEvents.TASK.MENTION_USED, {
262
+ mentionType,
263
+ contentLength,
264
+ timestamp: new Date().toISOString(),
265
+ });
266
+ }
267
+
268
+ export function captureMentionFailed(
269
+ telemetry: ITelemetryService | undefined,
270
+ mentionType:
271
+ | "file"
272
+ | "folder"
273
+ | "url"
274
+ | "problems"
275
+ | "terminal"
276
+ | "git-changes"
277
+ | "commit",
278
+ errorType:
279
+ | "not_found"
280
+ | "permission_denied"
281
+ | "network_error"
282
+ | "parse_error"
283
+ | "unknown",
284
+ errorMessage?: string,
285
+ ): void {
286
+ emit(telemetry, LegacyTelemetryEvents.TASK.MENTION_FAILED, {
287
+ mentionType,
288
+ errorType,
289
+ errorMessage: truncateErrorMessage(errorMessage),
290
+ timestamp: new Date().toISOString(),
291
+ });
292
+ }
293
+
294
+ export function captureMentionSearchResults(
295
+ telemetry: ITelemetryService | undefined,
296
+ query: string,
297
+ resultCount: number,
298
+ searchType: "file" | "folder" | "all",
299
+ isEmpty: boolean,
300
+ ): void {
301
+ emit(telemetry, LegacyTelemetryEvents.TASK.MENTION_SEARCH_RESULTS, {
302
+ queryLength: query.length,
303
+ resultCount,
304
+ searchType,
305
+ isEmpty,
306
+ timestamp: new Date().toISOString(),
307
+ });
308
+ }
309
+
310
+ export function captureSubagentExecution(
311
+ telemetry: ITelemetryService | undefined,
312
+ properties: {
313
+ ulid: string;
314
+ durationMs: number;
315
+ outputLines: number;
316
+ success: boolean;
317
+ },
318
+ ): void {
319
+ emit(
320
+ telemetry,
321
+ properties.success
322
+ ? LegacyTelemetryEvents.TASK.SUBAGENT_COMPLETED
323
+ : LegacyTelemetryEvents.TASK.SUBAGENT_STARTED,
324
+ {
325
+ ...properties,
326
+ timestamp: new Date().toISOString(),
327
+ },
328
+ );
329
+ }
330
+
331
+ export function captureHookDiscovery(
332
+ telemetry: ITelemetryService | undefined,
333
+ hookName: string,
334
+ globalCount: number,
335
+ workspaceCount: number,
336
+ ): void {
337
+ emit(telemetry, LegacyTelemetryEvents.HOOKS.DISCOVERY_COMPLETED, {
338
+ hookName,
339
+ globalCount,
340
+ workspaceCount,
341
+ totalCount: globalCount + workspaceCount,
342
+ timestamp: new Date().toISOString(),
343
+ });
344
+ }