@clinebot/core 0.0.29 → 0.0.30

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.
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "@clinebot/core",
3
3
  "description": "Cline Core SDK for Node Runtime",
4
- "version": "0.0.29",
4
+ "version": "0.0.30",
5
5
  "type": "module",
6
6
  "types": "./dist/index.d.ts",
7
7
  "main": "./dist/index.js",
@@ -36,8 +36,8 @@
36
36
  "test:watch": "vitest --config vitest.config.ts"
37
37
  },
38
38
  "dependencies": {
39
- "@clinebot/agents": "0.0.29",
40
- "@clinebot/llms": "0.0.29",
39
+ "@clinebot/agents": "0.0.30",
40
+ "@clinebot/llms": "0.0.30",
41
41
  "@opentelemetry/api": "^1.9.0",
42
42
  "@opentelemetry/api-logs": "^0.214.0",
43
43
  "@opentelemetry/exporter-logs-otlp-http": "^0.214.0",
@@ -58,7 +58,7 @@
58
58
  },
59
59
  "devDependencies": {
60
60
  "@clinebot/rpc": "0.0.28",
61
- "@clinebot/shared": "0.0.29"
61
+ "@clinebot/shared": "0.0.30"
62
62
  },
63
63
  "engines": {
64
64
  "node": ">=22"
@@ -30,7 +30,8 @@ async function createGitRepo(): Promise<string> {
30
30
  return cwd;
31
31
  }
32
32
 
33
- describe("createCheckpointHooks", () => {
33
+ // Currently disabled for process?.env?.CLINE_CHECKPOINT
34
+ describe.skip("createCheckpointHooks", () => {
34
35
  it("creates one checkpoint at the start of each root run and appends metadata", async () => {
35
36
  const cwd = await createGitRepo();
36
37
  let metadata: Record<string, unknown> | undefined;
@@ -344,6 +344,7 @@ export class DefaultSessionManager implements SessionManager {
344
344
  onConsecutiveMistakeLimitReached:
345
345
  configWithProvider.onConsecutiveMistakeLimitReached,
346
346
  completionGuard: runtime.completionGuard,
347
+ consumePendingUserMessage: () => this.consumeSteerMessage(sessionId),
347
348
  logger: runtime.logger ?? configWithProvider.logger,
348
349
  extensionContext: configWithProvider.extensionContext,
349
350
  onEvent: (event: AgentEvent) =>
@@ -964,6 +965,27 @@ export class DefaultSessionManager implements SessionManager {
964
965
  });
965
966
  }
966
967
 
968
+ /**
969
+ * Consume the first steer-delivery pending prompt for injection into the
970
+ * running agent loop. Called synchronously by the agent between iterations.
971
+ */
972
+ private consumeSteerMessage(sessionId: string): string | undefined {
973
+ const session = this.sessions.get(sessionId);
974
+ if (!session) {
975
+ return undefined;
976
+ }
977
+ const steerIndex = session.pendingPrompts.findIndex(
978
+ (entry) => entry.delivery === "steer",
979
+ );
980
+ if (steerIndex < 0) {
981
+ return undefined;
982
+ }
983
+ const [steer] = session.pendingPrompts.splice(steerIndex, 1);
984
+ this.emitPendingPrompts(session);
985
+ this.emitPendingPromptSubmitted(session, steer);
986
+ return steer.prompt;
987
+ }
988
+
967
989
  private enqueuePendingPrompt(
968
990
  sessionId: string,
969
991
  entry: {
@@ -229,8 +229,8 @@ export class OpenTelemetryAdapter implements ITelemetryAdapter {
229
229
  ): TelemetryProperties {
230
230
  return {
231
231
  ...this.commonProperties,
232
- ...properties,
233
232
  ...this.metadata,
233
+ ...properties,
234
234
  ...(this.distinctId ? { distinct_id: this.distinctId } : {}),
235
235
  ...(required ? { _required: true } : {}),
236
236
  };
@@ -98,6 +98,197 @@ describe("createOpenTelemetryTelemetryService", () => {
98
98
  expect(telemetry).toBeInstanceOf(TelemetryService);
99
99
  });
100
100
 
101
+ it("preserves metadata when disabled", () => {
102
+ const metadata = {
103
+ extension_version: "1.0.0",
104
+ cline_type: "kanban",
105
+ platform: "kanban",
106
+ platform_version: "v22.0.0",
107
+ os_type: "darwin",
108
+ os_version: "15.0",
109
+ };
110
+ const { telemetry } = createConfiguredTelemetryService({
111
+ metadata,
112
+ enabled: false,
113
+ });
114
+ const spy = vi.fn();
115
+ (telemetry as any).adapters.push({
116
+ name: "test",
117
+ emit: spy,
118
+ emitRequired: spy,
119
+ isEnabled: () => true,
120
+ recordCounter: vi.fn(),
121
+ recordHistogram: vi.fn(),
122
+ recordGauge: vi.fn(),
123
+ flush: async () => {},
124
+ dispose: async () => {},
125
+ });
126
+ telemetry.captureRequired("test.event", {});
127
+ expect(spy).toHaveBeenCalledWith(
128
+ "test.event",
129
+ expect.objectContaining({
130
+ cline_type: "kanban",
131
+ platform: "kanban",
132
+ }),
133
+ );
134
+ });
135
+
136
+ it("preserves metadata in the enabled (OTEL) path", async () => {
137
+ const metadata = {
138
+ extension_version: "1.0.0",
139
+ cline_type: "kanban",
140
+ platform: "kanban",
141
+ platform_version: "v22.0.0",
142
+ os_type: "darwin",
143
+ os_version: "15.0",
144
+ };
145
+ const { telemetry, provider } = createOpenTelemetryTelemetryService({
146
+ metadata,
147
+ enabled: true,
148
+ logsExporter: "console",
149
+ });
150
+ const spy = vi.fn();
151
+ (telemetry as any).adapters.push({
152
+ name: "test",
153
+ emit: spy,
154
+ emitRequired: spy,
155
+ isEnabled: () => true,
156
+ recordCounter: vi.fn(),
157
+ recordHistogram: vi.fn(),
158
+ recordGauge: vi.fn(),
159
+ flush: async () => {},
160
+ dispose: async () => {},
161
+ });
162
+ telemetry.captureRequired("test.event", {});
163
+ expect(spy).toHaveBeenCalledWith(
164
+ "test.event",
165
+ expect.objectContaining({
166
+ cline_type: "kanban",
167
+ platform: "kanban",
168
+ }),
169
+ );
170
+ await provider.dispose();
171
+ });
172
+
173
+ it("delivers metadata to the OTEL logger without duplication", async () => {
174
+ const otelEmit = vi.fn();
175
+ const provider = new OpenTelemetryProvider({
176
+ enabled: true,
177
+ });
178
+ // Replace the loggerProvider with a mock so we can inspect emit calls
179
+ (provider as any).loggerProvider = {
180
+ getLogger: () => ({ emit: otelEmit }),
181
+ forceFlush: async () => {},
182
+ shutdown: async () => {},
183
+ };
184
+
185
+ const metadata = {
186
+ extension_version: "1.0.0",
187
+ cline_type: "kanban",
188
+ platform: "kanban",
189
+ platform_version: "v22.0.0",
190
+ os_type: "darwin",
191
+ os_version: "15.0",
192
+ };
193
+
194
+ const telemetry = provider.createTelemetryService({ metadata });
195
+
196
+ telemetry.captureRequired("test.otel_event", { custom_prop: "value" });
197
+
198
+ expect(otelEmit).toHaveBeenCalledTimes(1);
199
+ const emittedAttributes = otelEmit.mock.calls[0][0].attributes;
200
+
201
+ // Metadata fields must be present
202
+ expect(emittedAttributes).toMatchObject({
203
+ cline_type: "kanban",
204
+ platform: "kanban",
205
+ extension_version: "1.0.0",
206
+ custom_prop: "value",
207
+ });
208
+
209
+ // Verify no key appears more than once (flattened object can't have
210
+ // duplicate keys, but this guards against nested duplication patterns
211
+ // like metadata appearing under a sub-prefix)
212
+ const keys = Object.keys(emittedAttributes);
213
+ const metadataKeys = Object.keys(metadata);
214
+ for (const mk of metadataKeys) {
215
+ const occurrences = keys.filter((k) => k === mk || k.endsWith(`.${mk}`));
216
+ expect(
217
+ occurrences,
218
+ `metadata key "${mk}" should appear exactly once, found: ${occurrences.join(", ")}`,
219
+ ).toHaveLength(1);
220
+ }
221
+
222
+ await provider.dispose();
223
+ });
224
+
225
+ it("propagates updateMetadata to OTEL logger output", async () => {
226
+ const otelEmit = vi.fn();
227
+ const provider = new OpenTelemetryProvider({
228
+ enabled: true,
229
+ });
230
+ (provider as any).loggerProvider = {
231
+ getLogger: () => ({ emit: otelEmit }),
232
+ forceFlush: async () => {},
233
+ shutdown: async () => {},
234
+ };
235
+
236
+ const metadata = {
237
+ extension_version: "1.0.0",
238
+ cline_type: "kanban",
239
+ platform: "kanban",
240
+ platform_version: "v22.0.0",
241
+ os_type: "darwin",
242
+ os_version: "15.0",
243
+ };
244
+
245
+ const telemetry = provider.createTelemetryService({ metadata });
246
+
247
+ // Update metadata after construction
248
+ telemetry.updateMetadata({ cline_type: "kanban-updated" });
249
+
250
+ telemetry.captureRequired("test.updated_event", {});
251
+
252
+ // The OTEL logger should see the updated value
253
+ const emittedAttributes =
254
+ otelEmit.mock.calls[otelEmit.mock.calls.length - 1][0].attributes;
255
+ expect(emittedAttributes.cline_type).toBe("kanban-updated");
256
+
257
+ await provider.dispose();
258
+ });
259
+
260
+ it("preserves logger when disabled", () => {
261
+ const logger: BasicLogger = {
262
+ debug: vi.fn(),
263
+ log: vi.fn(),
264
+ error: vi.fn(),
265
+ };
266
+ const { telemetry } = createConfiguredTelemetryService({
267
+ metadata: {
268
+ extension_version: "1.0.0",
269
+ cline_type: "kanban",
270
+ platform: "kanban",
271
+ platform_version: "v22.0.0",
272
+ os_type: "darwin",
273
+ os_version: "15.0",
274
+ },
275
+ enabled: false,
276
+ logger,
277
+ });
278
+
279
+ telemetry.capture({
280
+ event: "session.started",
281
+ properties: { sessionId: "session-1" },
282
+ });
283
+
284
+ expect(logger.log).toHaveBeenCalledWith(
285
+ "telemetry.event",
286
+ expect.objectContaining({
287
+ event: "session.started",
288
+ }),
289
+ );
290
+ });
291
+
101
292
  it("attaches the logger adapter when creating configured telemetry", () => {
102
293
  const logger: BasicLogger = {
103
294
  debug: vi.fn(),
@@ -133,10 +133,9 @@ export class OpenTelemetryProvider {
133
133
  metadata: options.metadata,
134
134
  });
135
135
  return new TelemetryService({
136
+ ...options,
136
137
  adapters: [adapter],
137
138
  distinctId: resolveCoreDistinctId(options.distinctId),
138
- commonProperties: options.commonProperties,
139
- logger: options.logger,
140
139
  });
141
140
  }
142
141
 
@@ -299,6 +298,7 @@ export function createConfiguredTelemetryService(
299
298
  if (options.enabled !== true) {
300
299
  return {
301
300
  telemetry: new TelemetryService({
301
+ ...options,
302
302
  distinctId: resolveCoreDistinctId(options.distinctId),
303
303
  }),
304
304
  };