@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.
@@ -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.17",
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: {
@@ -1255,11 +1255,11 @@ var package_default = {
1255
1255
  },
1256
1256
  devDependencies: {
1257
1257
  "@changesets/cli": "^2.27.8",
1258
+ "@posthog/shared": "workspace:*",
1259
+ "@twig/git": "workspace:*",
1258
1260
  "@types/bun": "latest",
1259
1261
  "@types/tar": "^6.1.13",
1260
1262
  minimatch: "^10.0.3",
1261
- "@posthog/shared": "workspace:*",
1262
- "@twig/git": "workspace:*",
1263
1263
  msw: "^2.12.7",
1264
1264
  tsup: "^8.5.1",
1265
1265
  tsx: "^4.20.6",
@@ -1267,16 +1267,16 @@ var package_default = {
1267
1267
  vitest: "^2.1.8"
1268
1268
  },
1269
1269
  dependencies: {
1270
+ "@agentclientprotocol/sdk": "^0.14.0",
1271
+ "@anthropic-ai/claude-agent-sdk": "0.2.42",
1272
+ "@anthropic-ai/sdk": "^0.71.0",
1273
+ "@hono/node-server": "^1.19.9",
1274
+ "@modelcontextprotocol/sdk": "^1.25.3",
1270
1275
  "@opentelemetry/api-logs": "^0.208.0",
1271
1276
  "@opentelemetry/exporter-logs-otlp-http": "^0.208.0",
1272
1277
  "@opentelemetry/resources": "^2.0.0",
1273
1278
  "@opentelemetry/sdk-logs": "^0.208.0",
1274
1279
  "@opentelemetry/semantic-conventions": "^1.28.0",
1275
- "@agentclientprotocol/sdk": "^0.14.0",
1276
- "@anthropic-ai/claude-agent-sdk": "0.2.12",
1277
- "@anthropic-ai/sdk": "^0.71.0",
1278
- "@hono/node-server": "^1.19.9",
1279
- "@modelcontextprotocol/sdk": "^1.25.3",
1280
1280
  "@types/jsonwebtoken": "^9.0.10",
1281
1281
  commander: "^14.0.2",
1282
1282
  diff: "^8.0.2",
@@ -2242,19 +2242,9 @@ function streamEventToAcpNotifications(message, sessionId, toolUseCache, fileCon
2242
2242
  }
2243
2243
  }
2244
2244
  async function handleSystemMessage(message, context) {
2245
- const { session, sessionId, client, logger } = context;
2245
+ const { sessionId, client, logger } = context;
2246
2246
  switch (message.subtype) {
2247
2247
  case "init":
2248
- if (message.session_id && session && !session.sessionId) {
2249
- session.sessionId = message.session_id;
2250
- if (session.taskRunId) {
2251
- await client.extNotification("_posthog/sdk_session", {
2252
- taskRunId: session.taskRunId,
2253
- sessionId: message.session_id,
2254
- adapter: "claude"
2255
- });
2256
- }
2257
- }
2258
2248
  break;
2259
2249
  case "compact_boundary":
2260
2250
  await client.extNotification("_posthog/compact_boundary", {
@@ -3219,7 +3209,7 @@ function buildSessionOptions(params) {
3219
3209
  ),
3220
3210
  ...params.onProcessSpawned && {
3221
3211
  spawnClaudeCodeProcess: buildSpawnWrapper(
3222
- params.sessionId ?? "unknown",
3212
+ params.sessionId,
3223
3213
  params.onProcessSpawned,
3224
3214
  params.onProcessExited
3225
3215
  )
@@ -3228,8 +3218,11 @@ function buildSessionOptions(params) {
3228
3218
  if (process.env.CLAUDE_CODE_EXECUTABLE) {
3229
3219
  options.pathToClaudeCodeExecutable = process.env.CLAUDE_CODE_EXECUTABLE;
3230
3220
  }
3231
- if (params.sessionId) {
3221
+ if (params.isResume) {
3232
3222
  options.resume = params.sessionId;
3223
+ options.forkSession = false;
3224
+ } else {
3225
+ options.sessionId = params.sessionId;
3233
3226
  }
3234
3227
  if (params.additionalDirectories) {
3235
3228
  options.additionalDirectories = params.additionalDirectories;
@@ -3306,7 +3299,7 @@ var ClaudeAcpAgent = class extends BaseAcpAgent {
3306
3299
  async newSession(params) {
3307
3300
  this.checkAuthStatus();
3308
3301
  const meta = params._meta;
3309
- const internalSessionId = uuidv7();
3302
+ const sessionId = uuidv7();
3310
3303
  const permissionMode = meta?.permissionMode && TWIG_EXECUTION_MODES.includes(meta.permissionMode) ? meta.permissionMode : "default";
3311
3304
  const mcpServers = parseMcpServers(params);
3312
3305
  await fetchMcpToolMetadata(mcpServers, this.logger);
@@ -3314,18 +3307,20 @@ var ClaudeAcpAgent = class extends BaseAcpAgent {
3314
3307
  cwd: params.cwd,
3315
3308
  mcpServers,
3316
3309
  permissionMode,
3317
- canUseTool: this.createCanUseTool(internalSessionId),
3310
+ canUseTool: this.createCanUseTool(sessionId),
3318
3311
  logger: this.logger,
3319
3312
  systemPrompt: buildSystemPrompt(meta?.systemPrompt),
3320
3313
  userProvidedOptions: meta?.claudeCode?.options,
3321
- onModeChange: this.createOnModeChange(internalSessionId),
3314
+ sessionId,
3315
+ isResume: false,
3316
+ onModeChange: this.createOnModeChange(sessionId),
3322
3317
  onProcessSpawned: this.processCallbacks?.onProcessSpawned,
3323
3318
  onProcessExited: this.processCallbacks?.onProcessExited
3324
3319
  });
3325
3320
  const input = new Pushable();
3326
3321
  const q = query({ prompt: input, options });
3327
3322
  const session = this.createSession(
3328
- internalSessionId,
3323
+ sessionId,
3329
3324
  q,
3330
3325
  input,
3331
3326
  permissionMode,
@@ -3333,19 +3328,23 @@ var ClaudeAcpAgent = class extends BaseAcpAgent {
3333
3328
  options.abortController
3334
3329
  );
3335
3330
  session.taskRunId = meta?.taskRunId;
3336
- this.registerPersistence(
3337
- internalSessionId,
3338
- meta
3339
- );
3331
+ this.registerPersistence(sessionId, meta);
3332
+ if (meta?.taskRunId) {
3333
+ await this.client.extNotification("_posthog/sdk_session", {
3334
+ taskRunId: meta.taskRunId,
3335
+ sessionId,
3336
+ adapter: "claude"
3337
+ });
3338
+ }
3340
3339
  const modelOptions = await this.getModelConfigOptions();
3341
3340
  session.modelId = modelOptions.currentModelId;
3342
3341
  await this.trySetModel(q, modelOptions.currentModelId);
3343
3342
  this.sendAvailableCommandsUpdate(
3344
- internalSessionId,
3343
+ sessionId,
3345
3344
  await getAvailableSlashCommands(q)
3346
3345
  );
3347
3346
  return {
3348
- sessionId: internalSessionId,
3347
+ sessionId,
3349
3348
  configOptions: await this.buildConfigOptions(modelOptions)
3350
3349
  };
3351
3350
  }
@@ -3353,34 +3352,31 @@ var ClaudeAcpAgent = class extends BaseAcpAgent {
3353
3352
  return this.resumeSession(params);
3354
3353
  }
3355
3354
  async resumeSession(params) {
3356
- const { sessionId: internalSessionId } = params;
3357
- if (this.sessionId === internalSessionId) {
3355
+ const meta = params._meta;
3356
+ const sessionId = meta?.sessionId;
3357
+ if (!sessionId) {
3358
+ throw new Error("Cannot resume session without sessionId");
3359
+ }
3360
+ if (this.sessionId === sessionId) {
3358
3361
  return {};
3359
3362
  }
3360
- const meta = params._meta;
3361
3363
  const mcpServers = parseMcpServers(params);
3362
3364
  await fetchMcpToolMetadata(mcpServers, this.logger);
3363
3365
  const permissionMode = meta?.permissionMode && TWIG_EXECUTION_MODES.includes(meta.permissionMode) ? meta.permissionMode : "default";
3364
3366
  const { query: q, session } = await this.initializeQuery({
3365
- internalSessionId,
3366
3367
  cwd: params.cwd,
3367
3368
  permissionMode,
3368
3369
  mcpServers,
3369
3370
  systemPrompt: buildSystemPrompt(meta?.systemPrompt),
3370
3371
  userProvidedOptions: meta?.claudeCode?.options,
3371
- sessionId: meta?.sessionId,
3372
+ sessionId,
3373
+ isResume: true,
3372
3374
  additionalDirectories: meta?.claudeCode?.options?.additionalDirectories
3373
3375
  });
3374
3376
  session.taskRunId = meta?.taskRunId;
3375
- if (meta?.sessionId) {
3376
- session.sessionId = meta.sessionId;
3377
- }
3378
- this.registerPersistence(
3379
- internalSessionId,
3380
- meta
3381
- );
3377
+ this.registerPersistence(sessionId, meta);
3382
3378
  this.sendAvailableCommandsUpdate(
3383
- internalSessionId,
3379
+ sessionId,
3384
3380
  await getAvailableSlashCommands(q)
3385
3381
  );
3386
3382
  return {
@@ -3449,20 +3445,21 @@ var ClaudeAcpAgent = class extends BaseAcpAgent {
3449
3445
  cwd: config.cwd,
3450
3446
  mcpServers: config.mcpServers,
3451
3447
  permissionMode: config.permissionMode,
3452
- canUseTool: this.createCanUseTool(config.internalSessionId),
3448
+ canUseTool: this.createCanUseTool(config.sessionId),
3453
3449
  logger: this.logger,
3454
3450
  systemPrompt: config.systemPrompt,
3455
3451
  userProvidedOptions: config.userProvidedOptions,
3456
3452
  sessionId: config.sessionId,
3453
+ isResume: config.isResume,
3457
3454
  additionalDirectories: config.additionalDirectories,
3458
- onModeChange: this.createOnModeChange(config.internalSessionId),
3455
+ onModeChange: this.createOnModeChange(config.sessionId),
3459
3456
  onProcessSpawned: this.processCallbacks?.onProcessSpawned,
3460
3457
  onProcessExited: this.processCallbacks?.onProcessExited
3461
3458
  });
3462
3459
  const q = query({ prompt: input, options });
3463
3460
  const abortController = options.abortController;
3464
3461
  const session = this.createSession(
3465
- config.internalSessionId,
3462
+ config.sessionId,
3466
3463
  q,
3467
3464
  input,
3468
3465
  config.permissionMode,
@@ -3655,6 +3652,7 @@ var ClaudeAcpAgent = class extends BaseAcpAgent {
3655
3652
  }
3656
3653
  case "tool_progress":
3657
3654
  case "auth_status":
3655
+ case "tool_use_summary":
3658
3656
  return null;
3659
3657
  default:
3660
3658
  unreachable(message, this.logger);
@@ -4268,19 +4266,36 @@ var PostHogAPIClient = class {
4268
4266
  };
4269
4267
 
4270
4268
  // src/session-log-writer.ts
4271
- 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;
4272
4274
  posthogAPI;
4273
4275
  pendingEntries = /* @__PURE__ */ new Map();
4274
4276
  flushTimeouts = /* @__PURE__ */ new Map();
4277
+ lastFlushAttemptTime = /* @__PURE__ */ new Map();
4278
+ retryCounts = /* @__PURE__ */ new Map();
4275
4279
  sessions = /* @__PURE__ */ new Map();
4280
+ messageCounts = /* @__PURE__ */ new Map();
4276
4281
  logger;
4277
4282
  constructor(options = {}) {
4278
4283
  this.posthogAPI = options.posthogAPI;
4279
4284
  this.logger = options.logger ?? new Logger({ debug: false, prefix: "[SessionLogWriter]" });
4280
4285
  }
4281
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
+ });
4282
4297
  const flushPromises = [];
4283
- for (const sessionId of this.sessions.keys()) {
4298
+ for (const sessionId of sessionIds) {
4284
4299
  flushPromises.push(this.flush(sessionId));
4285
4300
  }
4286
4301
  await Promise.all(flushPromises);
@@ -4289,7 +4304,12 @@ var SessionLogWriter = class {
4289
4304
  if (this.sessions.has(sessionId)) {
4290
4305
  return;
4291
4306
  }
4307
+ this.logger.info("Session registered", {
4308
+ sessionId,
4309
+ taskId: context.taskId
4310
+ });
4292
4311
  this.sessions.set(sessionId, { context });
4312
+ this.lastFlushAttemptTime.set(sessionId, Date.now());
4293
4313
  }
4294
4314
  isRegistered(sessionId) {
4295
4315
  return this.sessions.has(sessionId);
@@ -4297,8 +4317,16 @@ var SessionLogWriter = class {
4297
4317
  appendRawLine(sessionId, line) {
4298
4318
  const session = this.sessions.get(sessionId);
4299
4319
  if (!session) {
4320
+ this.logger.warn("appendRawLine called for unregistered session", {
4321
+ sessionId
4322
+ });
4300
4323
  return;
4301
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
+ }
4302
4330
  try {
4303
4331
  const message = JSON.parse(line);
4304
4332
  const timestamp = (/* @__PURE__ */ new Date()).toISOString();
@@ -4334,24 +4362,56 @@ var SessionLogWriter = class {
4334
4362
  }
4335
4363
  async flush(sessionId) {
4336
4364
  const session = this.sessions.get(sessionId);
4337
- if (!session) return;
4365
+ if (!session) {
4366
+ this.logger.warn("flush: no session found", { sessionId });
4367
+ return;
4368
+ }
4338
4369
  this.emitCoalescedMessage(sessionId, session);
4339
4370
  const pending = this.pendingEntries.get(sessionId);
4340
- 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
+ }
4341
4379
  this.pendingEntries.delete(sessionId);
4342
4380
  const timeout = this.flushTimeouts.get(sessionId);
4343
4381
  if (timeout) {
4344
4382
  clearTimeout(timeout);
4345
4383
  this.flushTimeouts.delete(sessionId);
4346
4384
  }
4385
+ this.lastFlushAttemptTime.set(sessionId, Date.now());
4347
4386
  try {
4348
4387
  await this.posthogAPI.appendTaskRunLog(
4349
4388
  session.context.taskId,
4350
4389
  session.context.runId,
4351
4390
  pending
4352
4391
  );
4392
+ this.retryCounts.set(sessionId, 0);
4393
+ this.logger.info("Flushed session logs", {
4394
+ sessionId,
4395
+ entryCount: pending.length
4396
+ });
4353
4397
  } catch (error) {
4354
- 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
+ }
4355
4415
  }
4356
4416
  }
4357
4417
  isAgentMessageChunk(message) {
@@ -4397,7 +4457,21 @@ var SessionLogWriter = class {
4397
4457
  scheduleFlush(sessionId) {
4398
4458
  const existing = this.flushTimeouts.get(sessionId);
4399
4459
  if (existing) clearTimeout(existing);
4400
- 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);
4401
4475
  this.flushTimeouts.set(sessionId, timeout);
4402
4476
  }
4403
4477
  };
@@ -10371,6 +10445,7 @@ var AgentServer = class {
10371
10445
  });
10372
10446
  const mode = this.getEffectiveMode(payload);
10373
10447
  if (mode === "background") {
10448
+ await this.session.logWriter.flushAll();
10374
10449
  await this.signalTaskComplete(payload, result.stopReason);
10375
10450
  } else {
10376
10451
  this.logger.info("Interactive mode - staying open for conversation");
@@ -10379,6 +10454,9 @@ var AgentServer = class {
10379
10454
  this.logger.error("Failed to send initial task message", error);
10380
10455
  const mode = this.getEffectiveMode(payload);
10381
10456
  if (mode === "background") {
10457
+ if (this.session) {
10458
+ await this.session.logWriter.flushAll();
10459
+ }
10382
10460
  await this.signalTaskComplete(payload, "error");
10383
10461
  }
10384
10462
  }
@@ -10393,10 +10471,24 @@ After completing the requested changes:
10393
10471
  3. Push the branch to origin
10394
10472
  4. Create a pull request using \`gh pr create\` with a descriptive title and body
10395
10473
 
10396
- 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.
10397
10478
  `;
10398
10479
  }
10399
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
+ }
10400
10492
  const status = stopReason === "cancelled" ? "cancelled" : stopReason === "error" ? "failed" : "completed";
10401
10493
  try {
10402
10494
  await this.posthogAPI.updateTaskRun(payload.task_id, payload.run_id, {