@posthog/agent 2.1.22 → 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.
@@ -1183,7 +1183,7 @@ import { v7 as uuidv7 } from "uuid";
1183
1183
  // package.json
1184
1184
  var package_default = {
1185
1185
  name: "@posthog/agent",
1186
- version: "2.1.22",
1186
+ version: "2.1.29",
1187
1187
  repository: "https://github.com/PostHog/twig",
1188
1188
  description: "TypeScript agent framework wrapping Claude Agent SDK with Git-based task execution for PostHog",
1189
1189
  exports: {
@@ -4266,19 +4266,36 @@ var PostHogAPIClient = class {
4266
4266
  };
4267
4267
 
4268
4268
  // src/session-log-writer.ts
4269
- var SessionLogWriter = class {
4269
+ var SessionLogWriter = class _SessionLogWriter {
4270
+ static FLUSH_DEBOUNCE_MS = 500;
4271
+ static FLUSH_MAX_INTERVAL_MS = 5e3;
4272
+ static MAX_FLUSH_RETRIES = 10;
4273
+ static MAX_RETRY_DELAY_MS = 3e4;
4270
4274
  posthogAPI;
4271
4275
  pendingEntries = /* @__PURE__ */ new Map();
4272
4276
  flushTimeouts = /* @__PURE__ */ new Map();
4277
+ lastFlushAttemptTime = /* @__PURE__ */ new Map();
4278
+ retryCounts = /* @__PURE__ */ new Map();
4273
4279
  sessions = /* @__PURE__ */ new Map();
4280
+ messageCounts = /* @__PURE__ */ new Map();
4274
4281
  logger;
4275
4282
  constructor(options = {}) {
4276
4283
  this.posthogAPI = options.posthogAPI;
4277
4284
  this.logger = options.logger ?? new Logger({ debug: false, prefix: "[SessionLogWriter]" });
4278
4285
  }
4279
4286
  async flushAll() {
4287
+ const sessionIds = [...this.sessions.keys()];
4288
+ const pendingCounts = sessionIds.map((id) => ({
4289
+ id,
4290
+ pending: this.pendingEntries.get(id)?.length ?? 0,
4291
+ messages: this.messageCounts.get(id) ?? 0
4292
+ }));
4293
+ this.logger.info("flushAll called", {
4294
+ sessions: sessionIds.length,
4295
+ pending: pendingCounts
4296
+ });
4280
4297
  const flushPromises = [];
4281
- for (const sessionId of this.sessions.keys()) {
4298
+ for (const sessionId of sessionIds) {
4282
4299
  flushPromises.push(this.flush(sessionId));
4283
4300
  }
4284
4301
  await Promise.all(flushPromises);
@@ -4287,7 +4304,12 @@ var SessionLogWriter = class {
4287
4304
  if (this.sessions.has(sessionId)) {
4288
4305
  return;
4289
4306
  }
4307
+ this.logger.info("Session registered", {
4308
+ sessionId,
4309
+ taskId: context.taskId
4310
+ });
4290
4311
  this.sessions.set(sessionId, { context });
4312
+ this.lastFlushAttemptTime.set(sessionId, Date.now());
4291
4313
  }
4292
4314
  isRegistered(sessionId) {
4293
4315
  return this.sessions.has(sessionId);
@@ -4295,8 +4317,16 @@ var SessionLogWriter = class {
4295
4317
  appendRawLine(sessionId, line) {
4296
4318
  const session = this.sessions.get(sessionId);
4297
4319
  if (!session) {
4320
+ this.logger.warn("appendRawLine called for unregistered session", {
4321
+ sessionId
4322
+ });
4298
4323
  return;
4299
4324
  }
4325
+ const count = (this.messageCounts.get(sessionId) ?? 0) + 1;
4326
+ this.messageCounts.set(sessionId, count);
4327
+ if (count % 10 === 1) {
4328
+ this.logger.info("Messages received", { count, sessionId });
4329
+ }
4300
4330
  try {
4301
4331
  const message = JSON.parse(line);
4302
4332
  const timestamp = (/* @__PURE__ */ new Date()).toISOString();
@@ -4332,24 +4362,56 @@ var SessionLogWriter = class {
4332
4362
  }
4333
4363
  async flush(sessionId) {
4334
4364
  const session = this.sessions.get(sessionId);
4335
- if (!session) return;
4365
+ if (!session) {
4366
+ this.logger.warn("flush: no session found", { sessionId });
4367
+ return;
4368
+ }
4336
4369
  this.emitCoalescedMessage(sessionId, session);
4337
4370
  const pending = this.pendingEntries.get(sessionId);
4338
- if (!this.posthogAPI || !pending?.length) return;
4371
+ if (!this.posthogAPI || !pending?.length) {
4372
+ this.logger.info("flush: nothing to persist", {
4373
+ sessionId,
4374
+ hasPosthogAPI: !!this.posthogAPI,
4375
+ pendingCount: pending?.length ?? 0
4376
+ });
4377
+ return;
4378
+ }
4339
4379
  this.pendingEntries.delete(sessionId);
4340
4380
  const timeout = this.flushTimeouts.get(sessionId);
4341
4381
  if (timeout) {
4342
4382
  clearTimeout(timeout);
4343
4383
  this.flushTimeouts.delete(sessionId);
4344
4384
  }
4385
+ this.lastFlushAttemptTime.set(sessionId, Date.now());
4345
4386
  try {
4346
4387
  await this.posthogAPI.appendTaskRunLog(
4347
4388
  session.context.taskId,
4348
4389
  session.context.runId,
4349
4390
  pending
4350
4391
  );
4392
+ this.retryCounts.set(sessionId, 0);
4393
+ this.logger.info("Flushed session logs", {
4394
+ sessionId,
4395
+ entryCount: pending.length
4396
+ });
4351
4397
  } catch (error) {
4352
- this.logger.error("Failed to persist session logs:", error);
4398
+ const retryCount = (this.retryCounts.get(sessionId) ?? 0) + 1;
4399
+ this.retryCounts.set(sessionId, retryCount);
4400
+ if (retryCount >= _SessionLogWriter.MAX_FLUSH_RETRIES) {
4401
+ this.logger.error(
4402
+ `Dropping ${pending.length} session log entries after ${retryCount} failed flush attempts`,
4403
+ { sessionId, error }
4404
+ );
4405
+ this.retryCounts.set(sessionId, 0);
4406
+ } else {
4407
+ this.logger.error(
4408
+ `Failed to persist session logs (attempt ${retryCount}/${_SessionLogWriter.MAX_FLUSH_RETRIES}):`,
4409
+ error
4410
+ );
4411
+ const currentPending = this.pendingEntries.get(sessionId) ?? [];
4412
+ this.pendingEntries.set(sessionId, [...pending, ...currentPending]);
4413
+ this.scheduleFlush(sessionId);
4414
+ }
4353
4415
  }
4354
4416
  }
4355
4417
  isAgentMessageChunk(message) {
@@ -4395,7 +4457,21 @@ var SessionLogWriter = class {
4395
4457
  scheduleFlush(sessionId) {
4396
4458
  const existing = this.flushTimeouts.get(sessionId);
4397
4459
  if (existing) clearTimeout(existing);
4398
- const timeout = setTimeout(() => this.flush(sessionId), 500);
4460
+ const retryCount = this.retryCounts.get(sessionId) ?? 0;
4461
+ const lastAttempt = this.lastFlushAttemptTime.get(sessionId) ?? 0;
4462
+ const elapsed = Date.now() - lastAttempt;
4463
+ let delay2;
4464
+ if (retryCount > 0) {
4465
+ delay2 = Math.min(
4466
+ _SessionLogWriter.FLUSH_DEBOUNCE_MS * 2 ** retryCount,
4467
+ _SessionLogWriter.MAX_RETRY_DELAY_MS
4468
+ );
4469
+ } else if (elapsed >= _SessionLogWriter.FLUSH_MAX_INTERVAL_MS) {
4470
+ delay2 = 0;
4471
+ } else {
4472
+ delay2 = _SessionLogWriter.FLUSH_DEBOUNCE_MS;
4473
+ }
4474
+ const timeout = setTimeout(() => this.flush(sessionId), delay2);
4399
4475
  this.flushTimeouts.set(sessionId, timeout);
4400
4476
  }
4401
4477
  };
@@ -10369,6 +10445,7 @@ var AgentServer = class {
10369
10445
  });
10370
10446
  const mode = this.getEffectiveMode(payload);
10371
10447
  if (mode === "background") {
10448
+ await this.session.logWriter.flushAll();
10372
10449
  await this.signalTaskComplete(payload, result.stopReason);
10373
10450
  } else {
10374
10451
  this.logger.info("Interactive mode - staying open for conversation");
@@ -10377,6 +10454,9 @@ var AgentServer = class {
10377
10454
  this.logger.error("Failed to send initial task message", error);
10378
10455
  const mode = this.getEffectiveMode(payload);
10379
10456
  if (mode === "background") {
10457
+ if (this.session) {
10458
+ await this.session.logWriter.flushAll();
10459
+ }
10380
10460
  await this.signalTaskComplete(payload, "error");
10381
10461
  }
10382
10462
  }
@@ -10391,10 +10471,24 @@ After completing the requested changes:
10391
10471
  3. Push the branch to origin
10392
10472
  4. Create a pull request using \`gh pr create\` with a descriptive title and body
10393
10473
 
10394
- Important: Always create the PR. Do not ask for confirmation.
10474
+ Important:
10475
+ - Always create the PR. Do not ask for confirmation.
10476
+ - Do NOT add "Co-Authored-By" trailers to commit messages.
10477
+ - Do NOT add "Generated with [Claude Code]" or similar attribution lines to PR descriptions.
10395
10478
  `;
10396
10479
  }
10397
10480
  async signalTaskComplete(payload, stopReason) {
10481
+ if (this.session?.payload.run_id === payload.run_id) {
10482
+ try {
10483
+ await this.session.logWriter.flush(payload.run_id);
10484
+ } catch (error) {
10485
+ this.logger.warn("Failed to flush session logs before completion", {
10486
+ taskId: payload.task_id,
10487
+ runId: payload.run_id,
10488
+ error
10489
+ });
10490
+ }
10491
+ }
10398
10492
  const status = stopReason === "cancelled" ? "cancelled" : stopReason === "error" ? "failed" : "completed";
10399
10493
  try {
10400
10494
  await this.posthogAPI.updateTaskRun(payload.task_id, payload.run_id, {