@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.
@@ -1175,7 +1175,7 @@ var import_uuid = require("uuid");
1175
1175
  // package.json
1176
1176
  var package_default = {
1177
1177
  name: "@posthog/agent",
1178
- version: "2.1.22",
1178
+ version: "2.1.29",
1179
1179
  repository: "https://github.com/PostHog/twig",
1180
1180
  description: "TypeScript agent framework wrapping Claude Agent SDK with Git-based task execution for PostHog",
1181
1181
  exports: {
@@ -4258,19 +4258,36 @@ var PostHogAPIClient = class {
4258
4258
  };
4259
4259
 
4260
4260
  // src/session-log-writer.ts
4261
- var SessionLogWriter = class {
4261
+ var SessionLogWriter = class _SessionLogWriter {
4262
+ static FLUSH_DEBOUNCE_MS = 500;
4263
+ static FLUSH_MAX_INTERVAL_MS = 5e3;
4264
+ static MAX_FLUSH_RETRIES = 10;
4265
+ static MAX_RETRY_DELAY_MS = 3e4;
4262
4266
  posthogAPI;
4263
4267
  pendingEntries = /* @__PURE__ */ new Map();
4264
4268
  flushTimeouts = /* @__PURE__ */ new Map();
4269
+ lastFlushAttemptTime = /* @__PURE__ */ new Map();
4270
+ retryCounts = /* @__PURE__ */ new Map();
4265
4271
  sessions = /* @__PURE__ */ new Map();
4272
+ messageCounts = /* @__PURE__ */ new Map();
4266
4273
  logger;
4267
4274
  constructor(options = {}) {
4268
4275
  this.posthogAPI = options.posthogAPI;
4269
4276
  this.logger = options.logger ?? new Logger({ debug: false, prefix: "[SessionLogWriter]" });
4270
4277
  }
4271
4278
  async flushAll() {
4279
+ const sessionIds = [...this.sessions.keys()];
4280
+ const pendingCounts = sessionIds.map((id) => ({
4281
+ id,
4282
+ pending: this.pendingEntries.get(id)?.length ?? 0,
4283
+ messages: this.messageCounts.get(id) ?? 0
4284
+ }));
4285
+ this.logger.info("flushAll called", {
4286
+ sessions: sessionIds.length,
4287
+ pending: pendingCounts
4288
+ });
4272
4289
  const flushPromises = [];
4273
- for (const sessionId of this.sessions.keys()) {
4290
+ for (const sessionId of sessionIds) {
4274
4291
  flushPromises.push(this.flush(sessionId));
4275
4292
  }
4276
4293
  await Promise.all(flushPromises);
@@ -4279,7 +4296,12 @@ var SessionLogWriter = class {
4279
4296
  if (this.sessions.has(sessionId)) {
4280
4297
  return;
4281
4298
  }
4299
+ this.logger.info("Session registered", {
4300
+ sessionId,
4301
+ taskId: context.taskId
4302
+ });
4282
4303
  this.sessions.set(sessionId, { context });
4304
+ this.lastFlushAttemptTime.set(sessionId, Date.now());
4283
4305
  }
4284
4306
  isRegistered(sessionId) {
4285
4307
  return this.sessions.has(sessionId);
@@ -4287,8 +4309,16 @@ var SessionLogWriter = class {
4287
4309
  appendRawLine(sessionId, line) {
4288
4310
  const session = this.sessions.get(sessionId);
4289
4311
  if (!session) {
4312
+ this.logger.warn("appendRawLine called for unregistered session", {
4313
+ sessionId
4314
+ });
4290
4315
  return;
4291
4316
  }
4317
+ const count = (this.messageCounts.get(sessionId) ?? 0) + 1;
4318
+ this.messageCounts.set(sessionId, count);
4319
+ if (count % 10 === 1) {
4320
+ this.logger.info("Messages received", { count, sessionId });
4321
+ }
4292
4322
  try {
4293
4323
  const message = JSON.parse(line);
4294
4324
  const timestamp = (/* @__PURE__ */ new Date()).toISOString();
@@ -4324,24 +4354,56 @@ var SessionLogWriter = class {
4324
4354
  }
4325
4355
  async flush(sessionId) {
4326
4356
  const session = this.sessions.get(sessionId);
4327
- if (!session) return;
4357
+ if (!session) {
4358
+ this.logger.warn("flush: no session found", { sessionId });
4359
+ return;
4360
+ }
4328
4361
  this.emitCoalescedMessage(sessionId, session);
4329
4362
  const pending = this.pendingEntries.get(sessionId);
4330
- if (!this.posthogAPI || !pending?.length) return;
4363
+ if (!this.posthogAPI || !pending?.length) {
4364
+ this.logger.info("flush: nothing to persist", {
4365
+ sessionId,
4366
+ hasPosthogAPI: !!this.posthogAPI,
4367
+ pendingCount: pending?.length ?? 0
4368
+ });
4369
+ return;
4370
+ }
4331
4371
  this.pendingEntries.delete(sessionId);
4332
4372
  const timeout = this.flushTimeouts.get(sessionId);
4333
4373
  if (timeout) {
4334
4374
  clearTimeout(timeout);
4335
4375
  this.flushTimeouts.delete(sessionId);
4336
4376
  }
4377
+ this.lastFlushAttemptTime.set(sessionId, Date.now());
4337
4378
  try {
4338
4379
  await this.posthogAPI.appendTaskRunLog(
4339
4380
  session.context.taskId,
4340
4381
  session.context.runId,
4341
4382
  pending
4342
4383
  );
4384
+ this.retryCounts.set(sessionId, 0);
4385
+ this.logger.info("Flushed session logs", {
4386
+ sessionId,
4387
+ entryCount: pending.length
4388
+ });
4343
4389
  } catch (error) {
4344
- this.logger.error("Failed to persist session logs:", error);
4390
+ const retryCount = (this.retryCounts.get(sessionId) ?? 0) + 1;
4391
+ this.retryCounts.set(sessionId, retryCount);
4392
+ if (retryCount >= _SessionLogWriter.MAX_FLUSH_RETRIES) {
4393
+ this.logger.error(
4394
+ `Dropping ${pending.length} session log entries after ${retryCount} failed flush attempts`,
4395
+ { sessionId, error }
4396
+ );
4397
+ this.retryCounts.set(sessionId, 0);
4398
+ } else {
4399
+ this.logger.error(
4400
+ `Failed to persist session logs (attempt ${retryCount}/${_SessionLogWriter.MAX_FLUSH_RETRIES}):`,
4401
+ error
4402
+ );
4403
+ const currentPending = this.pendingEntries.get(sessionId) ?? [];
4404
+ this.pendingEntries.set(sessionId, [...pending, ...currentPending]);
4405
+ this.scheduleFlush(sessionId);
4406
+ }
4345
4407
  }
4346
4408
  }
4347
4409
  isAgentMessageChunk(message) {
@@ -4387,7 +4449,21 @@ var SessionLogWriter = class {
4387
4449
  scheduleFlush(sessionId) {
4388
4450
  const existing = this.flushTimeouts.get(sessionId);
4389
4451
  if (existing) clearTimeout(existing);
4390
- const timeout = setTimeout(() => this.flush(sessionId), 500);
4452
+ const retryCount = this.retryCounts.get(sessionId) ?? 0;
4453
+ const lastAttempt = this.lastFlushAttemptTime.get(sessionId) ?? 0;
4454
+ const elapsed = Date.now() - lastAttempt;
4455
+ let delay2;
4456
+ if (retryCount > 0) {
4457
+ delay2 = Math.min(
4458
+ _SessionLogWriter.FLUSH_DEBOUNCE_MS * 2 ** retryCount,
4459
+ _SessionLogWriter.MAX_RETRY_DELAY_MS
4460
+ );
4461
+ } else if (elapsed >= _SessionLogWriter.FLUSH_MAX_INTERVAL_MS) {
4462
+ delay2 = 0;
4463
+ } else {
4464
+ delay2 = _SessionLogWriter.FLUSH_DEBOUNCE_MS;
4465
+ }
4466
+ const timeout = setTimeout(() => this.flush(sessionId), delay2);
4391
4467
  this.flushTimeouts.set(sessionId, timeout);
4392
4468
  }
4393
4469
  };
@@ -10361,6 +10437,7 @@ var AgentServer = class {
10361
10437
  });
10362
10438
  const mode = this.getEffectiveMode(payload);
10363
10439
  if (mode === "background") {
10440
+ await this.session.logWriter.flushAll();
10364
10441
  await this.signalTaskComplete(payload, result.stopReason);
10365
10442
  } else {
10366
10443
  this.logger.info("Interactive mode - staying open for conversation");
@@ -10369,6 +10446,9 @@ var AgentServer = class {
10369
10446
  this.logger.error("Failed to send initial task message", error);
10370
10447
  const mode = this.getEffectiveMode(payload);
10371
10448
  if (mode === "background") {
10449
+ if (this.session) {
10450
+ await this.session.logWriter.flushAll();
10451
+ }
10372
10452
  await this.signalTaskComplete(payload, "error");
10373
10453
  }
10374
10454
  }
@@ -10383,10 +10463,24 @@ After completing the requested changes:
10383
10463
  3. Push the branch to origin
10384
10464
  4. Create a pull request using \`gh pr create\` with a descriptive title and body
10385
10465
 
10386
- Important: Always create the PR. Do not ask for confirmation.
10466
+ Important:
10467
+ - Always create the PR. Do not ask for confirmation.
10468
+ - Do NOT add "Co-Authored-By" trailers to commit messages.
10469
+ - Do NOT add "Generated with [Claude Code]" or similar attribution lines to PR descriptions.
10387
10470
  `;
10388
10471
  }
10389
10472
  async signalTaskComplete(payload, stopReason) {
10473
+ if (this.session?.payload.run_id === payload.run_id) {
10474
+ try {
10475
+ await this.session.logWriter.flush(payload.run_id);
10476
+ } catch (error) {
10477
+ this.logger.warn("Failed to flush session logs before completion", {
10478
+ taskId: payload.task_id,
10479
+ runId: payload.run_id,
10480
+ error
10481
+ });
10482
+ }
10483
+ }
10390
10484
  const status = stopReason === "cancelled" ? "cancelled" : stopReason === "error" ? "failed" : "completed";
10391
10485
  try {
10392
10486
  await this.posthogAPI.updateTaskRun(payload.task_id, payload.run_id, {