@posthog/agent 2.1.17 → 2.1.29

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.
@@ -46,10 +46,17 @@ interface SessionLogWriterOptions {
46
46
  logger?: Logger;
47
47
  }
48
48
  declare class SessionLogWriter {
49
+ private static readonly FLUSH_DEBOUNCE_MS;
50
+ private static readonly FLUSH_MAX_INTERVAL_MS;
51
+ private static readonly MAX_FLUSH_RETRIES;
52
+ private static readonly MAX_RETRY_DELAY_MS;
49
53
  private posthogAPI?;
50
54
  private pendingEntries;
51
55
  private flushTimeouts;
56
+ private lastFlushAttemptTime;
57
+ private retryCounts;
52
58
  private sessions;
59
+ private messageCounts;
53
60
  private logger;
54
61
  constructor(options?: SessionLogWriterOptions);
55
62
  flushAll(): Promise<void>;
package/dist/agent.d.ts CHANGED
@@ -1,4 +1,4 @@
1
- export { b as Agent } from './agent-LrKyX9KN.js';
1
+ export { b as Agent } from './agent-DcBmoTR4.js';
2
2
  import './types.js';
3
3
  import '@agentclientprotocol/sdk';
4
4
  import './logger-DDBiMOOD.js';
package/dist/agent.js CHANGED
@@ -276,7 +276,7 @@ import { v7 as uuidv7 } from "uuid";
276
276
  // package.json
277
277
  var package_default = {
278
278
  name: "@posthog/agent",
279
- version: "2.1.17",
279
+ version: "2.1.29",
280
280
  repository: "https://github.com/PostHog/twig",
281
281
  description: "TypeScript agent framework wrapping Claude Agent SDK with Git-based task execution for PostHog",
282
282
  exports: {
@@ -348,11 +348,11 @@ var package_default = {
348
348
  },
349
349
  devDependencies: {
350
350
  "@changesets/cli": "^2.27.8",
351
+ "@posthog/shared": "workspace:*",
352
+ "@twig/git": "workspace:*",
351
353
  "@types/bun": "latest",
352
354
  "@types/tar": "^6.1.13",
353
355
  minimatch: "^10.0.3",
354
- "@posthog/shared": "workspace:*",
355
- "@twig/git": "workspace:*",
356
356
  msw: "^2.12.7",
357
357
  tsup: "^8.5.1",
358
358
  tsx: "^4.20.6",
@@ -360,16 +360,16 @@ var package_default = {
360
360
  vitest: "^2.1.8"
361
361
  },
362
362
  dependencies: {
363
+ "@agentclientprotocol/sdk": "^0.14.0",
364
+ "@anthropic-ai/claude-agent-sdk": "0.2.42",
365
+ "@anthropic-ai/sdk": "^0.71.0",
366
+ "@hono/node-server": "^1.19.9",
367
+ "@modelcontextprotocol/sdk": "^1.25.3",
363
368
  "@opentelemetry/api-logs": "^0.208.0",
364
369
  "@opentelemetry/exporter-logs-otlp-http": "^0.208.0",
365
370
  "@opentelemetry/resources": "^2.0.0",
366
371
  "@opentelemetry/sdk-logs": "^0.208.0",
367
372
  "@opentelemetry/semantic-conventions": "^1.28.0",
368
- "@agentclientprotocol/sdk": "^0.14.0",
369
- "@anthropic-ai/claude-agent-sdk": "0.2.12",
370
- "@anthropic-ai/sdk": "^0.71.0",
371
- "@hono/node-server": "^1.19.9",
372
- "@modelcontextprotocol/sdk": "^1.25.3",
373
373
  "@types/jsonwebtoken": "^9.0.10",
374
374
  commander: "^14.0.2",
375
375
  diff: "^8.0.2",
@@ -1362,19 +1362,9 @@ function streamEventToAcpNotifications(message, sessionId, toolUseCache, fileCon
1362
1362
  }
1363
1363
  }
1364
1364
  async function handleSystemMessage(message, context) {
1365
- const { session, sessionId, client, logger } = context;
1365
+ const { sessionId, client, logger } = context;
1366
1366
  switch (message.subtype) {
1367
1367
  case "init":
1368
- if (message.session_id && session && !session.sessionId) {
1369
- session.sessionId = message.session_id;
1370
- if (session.taskRunId) {
1371
- await client.extNotification("_posthog/sdk_session", {
1372
- taskRunId: session.taskRunId,
1373
- sessionId: message.session_id,
1374
- adapter: "claude"
1375
- });
1376
- }
1377
- }
1378
1368
  break;
1379
1369
  case "compact_boundary":
1380
1370
  await client.extNotification("_posthog/compact_boundary", {
@@ -2339,7 +2329,7 @@ function buildSessionOptions(params) {
2339
2329
  ),
2340
2330
  ...params.onProcessSpawned && {
2341
2331
  spawnClaudeCodeProcess: buildSpawnWrapper(
2342
- params.sessionId ?? "unknown",
2332
+ params.sessionId,
2343
2333
  params.onProcessSpawned,
2344
2334
  params.onProcessExited
2345
2335
  )
@@ -2348,8 +2338,11 @@ function buildSessionOptions(params) {
2348
2338
  if (process.env.CLAUDE_CODE_EXECUTABLE) {
2349
2339
  options.pathToClaudeCodeExecutable = process.env.CLAUDE_CODE_EXECUTABLE;
2350
2340
  }
2351
- if (params.sessionId) {
2341
+ if (params.isResume) {
2352
2342
  options.resume = params.sessionId;
2343
+ options.forkSession = false;
2344
+ } else {
2345
+ options.sessionId = params.sessionId;
2353
2346
  }
2354
2347
  if (params.additionalDirectories) {
2355
2348
  options.additionalDirectories = params.additionalDirectories;
@@ -2426,7 +2419,7 @@ var ClaudeAcpAgent = class extends BaseAcpAgent {
2426
2419
  async newSession(params) {
2427
2420
  this.checkAuthStatus();
2428
2421
  const meta = params._meta;
2429
- const internalSessionId = uuidv7();
2422
+ const sessionId = uuidv7();
2430
2423
  const permissionMode = meta?.permissionMode && TWIG_EXECUTION_MODES.includes(meta.permissionMode) ? meta.permissionMode : "default";
2431
2424
  const mcpServers = parseMcpServers(params);
2432
2425
  await fetchMcpToolMetadata(mcpServers, this.logger);
@@ -2434,18 +2427,20 @@ var ClaudeAcpAgent = class extends BaseAcpAgent {
2434
2427
  cwd: params.cwd,
2435
2428
  mcpServers,
2436
2429
  permissionMode,
2437
- canUseTool: this.createCanUseTool(internalSessionId),
2430
+ canUseTool: this.createCanUseTool(sessionId),
2438
2431
  logger: this.logger,
2439
2432
  systemPrompt: buildSystemPrompt(meta?.systemPrompt),
2440
2433
  userProvidedOptions: meta?.claudeCode?.options,
2441
- onModeChange: this.createOnModeChange(internalSessionId),
2434
+ sessionId,
2435
+ isResume: false,
2436
+ onModeChange: this.createOnModeChange(sessionId),
2442
2437
  onProcessSpawned: this.processCallbacks?.onProcessSpawned,
2443
2438
  onProcessExited: this.processCallbacks?.onProcessExited
2444
2439
  });
2445
2440
  const input = new Pushable();
2446
2441
  const q = query({ prompt: input, options });
2447
2442
  const session = this.createSession(
2448
- internalSessionId,
2443
+ sessionId,
2449
2444
  q,
2450
2445
  input,
2451
2446
  permissionMode,
@@ -2453,19 +2448,23 @@ var ClaudeAcpAgent = class extends BaseAcpAgent {
2453
2448
  options.abortController
2454
2449
  );
2455
2450
  session.taskRunId = meta?.taskRunId;
2456
- this.registerPersistence(
2457
- internalSessionId,
2458
- meta
2459
- );
2451
+ this.registerPersistence(sessionId, meta);
2452
+ if (meta?.taskRunId) {
2453
+ await this.client.extNotification("_posthog/sdk_session", {
2454
+ taskRunId: meta.taskRunId,
2455
+ sessionId,
2456
+ adapter: "claude"
2457
+ });
2458
+ }
2460
2459
  const modelOptions = await this.getModelConfigOptions();
2461
2460
  session.modelId = modelOptions.currentModelId;
2462
2461
  await this.trySetModel(q, modelOptions.currentModelId);
2463
2462
  this.sendAvailableCommandsUpdate(
2464
- internalSessionId,
2463
+ sessionId,
2465
2464
  await getAvailableSlashCommands(q)
2466
2465
  );
2467
2466
  return {
2468
- sessionId: internalSessionId,
2467
+ sessionId,
2469
2468
  configOptions: await this.buildConfigOptions(modelOptions)
2470
2469
  };
2471
2470
  }
@@ -2473,34 +2472,31 @@ var ClaudeAcpAgent = class extends BaseAcpAgent {
2473
2472
  return this.resumeSession(params);
2474
2473
  }
2475
2474
  async resumeSession(params) {
2476
- const { sessionId: internalSessionId } = params;
2477
- if (this.sessionId === internalSessionId) {
2475
+ const meta = params._meta;
2476
+ const sessionId = meta?.sessionId;
2477
+ if (!sessionId) {
2478
+ throw new Error("Cannot resume session without sessionId");
2479
+ }
2480
+ if (this.sessionId === sessionId) {
2478
2481
  return {};
2479
2482
  }
2480
- const meta = params._meta;
2481
2483
  const mcpServers = parseMcpServers(params);
2482
2484
  await fetchMcpToolMetadata(mcpServers, this.logger);
2483
2485
  const permissionMode = meta?.permissionMode && TWIG_EXECUTION_MODES.includes(meta.permissionMode) ? meta.permissionMode : "default";
2484
2486
  const { query: q, session } = await this.initializeQuery({
2485
- internalSessionId,
2486
2487
  cwd: params.cwd,
2487
2488
  permissionMode,
2488
2489
  mcpServers,
2489
2490
  systemPrompt: buildSystemPrompt(meta?.systemPrompt),
2490
2491
  userProvidedOptions: meta?.claudeCode?.options,
2491
- sessionId: meta?.sessionId,
2492
+ sessionId,
2493
+ isResume: true,
2492
2494
  additionalDirectories: meta?.claudeCode?.options?.additionalDirectories
2493
2495
  });
2494
2496
  session.taskRunId = meta?.taskRunId;
2495
- if (meta?.sessionId) {
2496
- session.sessionId = meta.sessionId;
2497
- }
2498
- this.registerPersistence(
2499
- internalSessionId,
2500
- meta
2501
- );
2497
+ this.registerPersistence(sessionId, meta);
2502
2498
  this.sendAvailableCommandsUpdate(
2503
- internalSessionId,
2499
+ sessionId,
2504
2500
  await getAvailableSlashCommands(q)
2505
2501
  );
2506
2502
  return {
@@ -2569,20 +2565,21 @@ var ClaudeAcpAgent = class extends BaseAcpAgent {
2569
2565
  cwd: config.cwd,
2570
2566
  mcpServers: config.mcpServers,
2571
2567
  permissionMode: config.permissionMode,
2572
- canUseTool: this.createCanUseTool(config.internalSessionId),
2568
+ canUseTool: this.createCanUseTool(config.sessionId),
2573
2569
  logger: this.logger,
2574
2570
  systemPrompt: config.systemPrompt,
2575
2571
  userProvidedOptions: config.userProvidedOptions,
2576
2572
  sessionId: config.sessionId,
2573
+ isResume: config.isResume,
2577
2574
  additionalDirectories: config.additionalDirectories,
2578
- onModeChange: this.createOnModeChange(config.internalSessionId),
2575
+ onModeChange: this.createOnModeChange(config.sessionId),
2579
2576
  onProcessSpawned: this.processCallbacks?.onProcessSpawned,
2580
2577
  onProcessExited: this.processCallbacks?.onProcessExited
2581
2578
  });
2582
2579
  const q = query({ prompt: input, options });
2583
2580
  const abortController = options.abortController;
2584
2581
  const session = this.createSession(
2585
- config.internalSessionId,
2582
+ config.sessionId,
2586
2583
  q,
2587
2584
  input,
2588
2585
  config.permissionMode,
@@ -2775,6 +2772,7 @@ var ClaudeAcpAgent = class extends BaseAcpAgent {
2775
2772
  }
2776
2773
  case "tool_progress":
2777
2774
  case "auth_status":
2775
+ case "tool_use_summary":
2778
2776
  return null;
2779
2777
  default:
2780
2778
  unreachable(message, this.logger);
@@ -3388,19 +3386,36 @@ var PostHogAPIClient = class {
3388
3386
  };
3389
3387
 
3390
3388
  // src/session-log-writer.ts
3391
- var SessionLogWriter = class {
3389
+ var SessionLogWriter = class _SessionLogWriter {
3390
+ static FLUSH_DEBOUNCE_MS = 500;
3391
+ static FLUSH_MAX_INTERVAL_MS = 5e3;
3392
+ static MAX_FLUSH_RETRIES = 10;
3393
+ static MAX_RETRY_DELAY_MS = 3e4;
3392
3394
  posthogAPI;
3393
3395
  pendingEntries = /* @__PURE__ */ new Map();
3394
3396
  flushTimeouts = /* @__PURE__ */ new Map();
3397
+ lastFlushAttemptTime = /* @__PURE__ */ new Map();
3398
+ retryCounts = /* @__PURE__ */ new Map();
3395
3399
  sessions = /* @__PURE__ */ new Map();
3400
+ messageCounts = /* @__PURE__ */ new Map();
3396
3401
  logger;
3397
3402
  constructor(options = {}) {
3398
3403
  this.posthogAPI = options.posthogAPI;
3399
3404
  this.logger = options.logger ?? new Logger({ debug: false, prefix: "[SessionLogWriter]" });
3400
3405
  }
3401
3406
  async flushAll() {
3407
+ const sessionIds = [...this.sessions.keys()];
3408
+ const pendingCounts = sessionIds.map((id) => ({
3409
+ id,
3410
+ pending: this.pendingEntries.get(id)?.length ?? 0,
3411
+ messages: this.messageCounts.get(id) ?? 0
3412
+ }));
3413
+ this.logger.info("flushAll called", {
3414
+ sessions: sessionIds.length,
3415
+ pending: pendingCounts
3416
+ });
3402
3417
  const flushPromises = [];
3403
- for (const sessionId of this.sessions.keys()) {
3418
+ for (const sessionId of sessionIds) {
3404
3419
  flushPromises.push(this.flush(sessionId));
3405
3420
  }
3406
3421
  await Promise.all(flushPromises);
@@ -3409,7 +3424,12 @@ var SessionLogWriter = class {
3409
3424
  if (this.sessions.has(sessionId)) {
3410
3425
  return;
3411
3426
  }
3427
+ this.logger.info("Session registered", {
3428
+ sessionId,
3429
+ taskId: context.taskId
3430
+ });
3412
3431
  this.sessions.set(sessionId, { context });
3432
+ this.lastFlushAttemptTime.set(sessionId, Date.now());
3413
3433
  }
3414
3434
  isRegistered(sessionId) {
3415
3435
  return this.sessions.has(sessionId);
@@ -3417,8 +3437,16 @@ var SessionLogWriter = class {
3417
3437
  appendRawLine(sessionId, line) {
3418
3438
  const session = this.sessions.get(sessionId);
3419
3439
  if (!session) {
3440
+ this.logger.warn("appendRawLine called for unregistered session", {
3441
+ sessionId
3442
+ });
3420
3443
  return;
3421
3444
  }
3445
+ const count = (this.messageCounts.get(sessionId) ?? 0) + 1;
3446
+ this.messageCounts.set(sessionId, count);
3447
+ if (count % 10 === 1) {
3448
+ this.logger.info("Messages received", { count, sessionId });
3449
+ }
3422
3450
  try {
3423
3451
  const message = JSON.parse(line);
3424
3452
  const timestamp = (/* @__PURE__ */ new Date()).toISOString();
@@ -3454,24 +3482,56 @@ var SessionLogWriter = class {
3454
3482
  }
3455
3483
  async flush(sessionId) {
3456
3484
  const session = this.sessions.get(sessionId);
3457
- if (!session) return;
3485
+ if (!session) {
3486
+ this.logger.warn("flush: no session found", { sessionId });
3487
+ return;
3488
+ }
3458
3489
  this.emitCoalescedMessage(sessionId, session);
3459
3490
  const pending = this.pendingEntries.get(sessionId);
3460
- if (!this.posthogAPI || !pending?.length) return;
3491
+ if (!this.posthogAPI || !pending?.length) {
3492
+ this.logger.info("flush: nothing to persist", {
3493
+ sessionId,
3494
+ hasPosthogAPI: !!this.posthogAPI,
3495
+ pendingCount: pending?.length ?? 0
3496
+ });
3497
+ return;
3498
+ }
3461
3499
  this.pendingEntries.delete(sessionId);
3462
3500
  const timeout = this.flushTimeouts.get(sessionId);
3463
3501
  if (timeout) {
3464
3502
  clearTimeout(timeout);
3465
3503
  this.flushTimeouts.delete(sessionId);
3466
3504
  }
3505
+ this.lastFlushAttemptTime.set(sessionId, Date.now());
3467
3506
  try {
3468
3507
  await this.posthogAPI.appendTaskRunLog(
3469
3508
  session.context.taskId,
3470
3509
  session.context.runId,
3471
3510
  pending
3472
3511
  );
3512
+ this.retryCounts.set(sessionId, 0);
3513
+ this.logger.info("Flushed session logs", {
3514
+ sessionId,
3515
+ entryCount: pending.length
3516
+ });
3473
3517
  } catch (error) {
3474
- this.logger.error("Failed to persist session logs:", error);
3518
+ const retryCount = (this.retryCounts.get(sessionId) ?? 0) + 1;
3519
+ this.retryCounts.set(sessionId, retryCount);
3520
+ if (retryCount >= _SessionLogWriter.MAX_FLUSH_RETRIES) {
3521
+ this.logger.error(
3522
+ `Dropping ${pending.length} session log entries after ${retryCount} failed flush attempts`,
3523
+ { sessionId, error }
3524
+ );
3525
+ this.retryCounts.set(sessionId, 0);
3526
+ } else {
3527
+ this.logger.error(
3528
+ `Failed to persist session logs (attempt ${retryCount}/${_SessionLogWriter.MAX_FLUSH_RETRIES}):`,
3529
+ error
3530
+ );
3531
+ const currentPending = this.pendingEntries.get(sessionId) ?? [];
3532
+ this.pendingEntries.set(sessionId, [...pending, ...currentPending]);
3533
+ this.scheduleFlush(sessionId);
3534
+ }
3475
3535
  }
3476
3536
  }
3477
3537
  isAgentMessageChunk(message) {
@@ -3517,7 +3577,21 @@ var SessionLogWriter = class {
3517
3577
  scheduleFlush(sessionId) {
3518
3578
  const existing = this.flushTimeouts.get(sessionId);
3519
3579
  if (existing) clearTimeout(existing);
3520
- const timeout = setTimeout(() => this.flush(sessionId), 500);
3580
+ const retryCount = this.retryCounts.get(sessionId) ?? 0;
3581
+ const lastAttempt = this.lastFlushAttemptTime.get(sessionId) ?? 0;
3582
+ const elapsed = Date.now() - lastAttempt;
3583
+ let delay;
3584
+ if (retryCount > 0) {
3585
+ delay = Math.min(
3586
+ _SessionLogWriter.FLUSH_DEBOUNCE_MS * 2 ** retryCount,
3587
+ _SessionLogWriter.MAX_RETRY_DELAY_MS
3588
+ );
3589
+ } else if (elapsed >= _SessionLogWriter.FLUSH_MAX_INTERVAL_MS) {
3590
+ delay = 0;
3591
+ } else {
3592
+ delay = _SessionLogWriter.FLUSH_DEBOUNCE_MS;
3593
+ }
3594
+ const timeout = setTimeout(() => this.flush(sessionId), delay);
3521
3595
  this.flushTimeouts.set(sessionId, timeout);
3522
3596
  }
3523
3597
  };