@posthog/agent 2.1.45 → 2.1.47

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.
@@ -1178,12 +1178,174 @@ import {
1178
1178
  import {
1179
1179
  query
1180
1180
  } from "@anthropic-ai/claude-agent-sdk";
1181
+
1182
+ // ../shared/dist/index.js
1183
+ var consoleLogger = {
1184
+ info: (_message, _data) => {
1185
+ },
1186
+ debug: (_message, _data) => {
1187
+ },
1188
+ error: (_message, _data) => {
1189
+ },
1190
+ warn: (_message, _data) => {
1191
+ }
1192
+ };
1193
+ var Saga = class {
1194
+ completedSteps = [];
1195
+ currentStepName = "unknown";
1196
+ stepTimings = [];
1197
+ log;
1198
+ constructor(logger) {
1199
+ this.log = logger ?? consoleLogger;
1200
+ }
1201
+ /**
1202
+ * Run the saga with the given input.
1203
+ * Returns a discriminated union result - either success with data or failure with error details.
1204
+ */
1205
+ async run(input) {
1206
+ this.completedSteps = [];
1207
+ this.currentStepName = "unknown";
1208
+ this.stepTimings = [];
1209
+ const sagaStart = performance.now();
1210
+ this.log.info("Starting saga", { sagaName: this.constructor.name });
1211
+ try {
1212
+ const result = await this.execute(input);
1213
+ const totalDuration = performance.now() - sagaStart;
1214
+ this.log.debug("Saga completed successfully", {
1215
+ sagaName: this.constructor.name,
1216
+ stepsCompleted: this.completedSteps.length,
1217
+ totalDurationMs: Math.round(totalDuration),
1218
+ stepTimings: this.stepTimings
1219
+ });
1220
+ return { success: true, data: result };
1221
+ } catch (error) {
1222
+ this.log.error("Saga failed, initiating rollback", {
1223
+ sagaName: this.constructor.name,
1224
+ failedStep: this.currentStepName,
1225
+ error: error instanceof Error ? error.message : String(error)
1226
+ });
1227
+ await this.rollback();
1228
+ return {
1229
+ success: false,
1230
+ error: error instanceof Error ? error.message : String(error),
1231
+ failedStep: this.currentStepName
1232
+ };
1233
+ }
1234
+ }
1235
+ /**
1236
+ * Execute a step with its rollback action.
1237
+ * If the step succeeds, its rollback action is stored for potential rollback.
1238
+ * The step name is automatically tracked for error reporting.
1239
+ *
1240
+ * @param config - Step configuration with name, execute, and rollback functions
1241
+ * @returns The result of the execute function
1242
+ * @throws Re-throws any error from the execute function (triggers rollback)
1243
+ */
1244
+ async step(config) {
1245
+ this.currentStepName = config.name;
1246
+ this.log.debug(`Executing step: ${config.name}`);
1247
+ const stepStart = performance.now();
1248
+ const result = await config.execute();
1249
+ const durationMs = Math.round(performance.now() - stepStart);
1250
+ this.stepTimings.push({ name: config.name, durationMs });
1251
+ this.log.debug(`Step completed: ${config.name}`, { durationMs });
1252
+ this.completedSteps.push({
1253
+ name: config.name,
1254
+ rollback: () => config.rollback(result)
1255
+ });
1256
+ return result;
1257
+ }
1258
+ /**
1259
+ * Execute a step that doesn't need rollback.
1260
+ * Useful for read-only operations or operations that are idempotent.
1261
+ * The step name is automatically tracked for error reporting.
1262
+ *
1263
+ * @param name - Step name for logging and error tracking
1264
+ * @param execute - The action to execute
1265
+ * @returns The result of the execute function
1266
+ */
1267
+ async readOnlyStep(name, execute) {
1268
+ this.currentStepName = name;
1269
+ this.log.debug(`Executing read-only step: ${name}`);
1270
+ const stepStart = performance.now();
1271
+ const result = await execute();
1272
+ const durationMs = Math.round(performance.now() - stepStart);
1273
+ this.stepTimings.push({ name, durationMs });
1274
+ this.log.debug(`Read-only step completed: ${name}`, { durationMs });
1275
+ return result;
1276
+ }
1277
+ /**
1278
+ * Roll back all completed steps in reverse order.
1279
+ * Rollback errors are logged but don't stop the rollback of other steps.
1280
+ */
1281
+ async rollback() {
1282
+ this.log.info("Rolling back saga", {
1283
+ stepsToRollback: this.completedSteps.length
1284
+ });
1285
+ const stepsReversed = [...this.completedSteps].reverse();
1286
+ for (const step of stepsReversed) {
1287
+ try {
1288
+ this.log.debug(`Rolling back step: ${step.name}`);
1289
+ await step.rollback();
1290
+ this.log.debug(`Step rolled back: ${step.name}`);
1291
+ } catch (error) {
1292
+ this.log.error(`Failed to rollback step: ${step.name}`, {
1293
+ error: error instanceof Error ? error.message : String(error)
1294
+ });
1295
+ }
1296
+ }
1297
+ this.log.info("Rollback completed", {
1298
+ stepsAttempted: this.completedSteps.length
1299
+ });
1300
+ }
1301
+ /**
1302
+ * Get the number of completed steps (useful for testing)
1303
+ */
1304
+ getCompletedStepCount() {
1305
+ return this.completedSteps.length;
1306
+ }
1307
+ };
1308
+ var NOOP_COLLECTOR = {
1309
+ time: (_label, fn) => fn(),
1310
+ timeSync: (_label, fn) => fn(),
1311
+ record: () => {
1312
+ },
1313
+ summarize: () => {
1314
+ }
1315
+ };
1316
+ function createTimingCollector(enabled, log) {
1317
+ if (!enabled) return NOOP_COLLECTOR;
1318
+ const steps = {};
1319
+ return {
1320
+ async time(label, fn) {
1321
+ const start = Date.now();
1322
+ const result = await fn();
1323
+ steps[label] = Date.now() - start;
1324
+ return result;
1325
+ },
1326
+ timeSync(label, fn) {
1327
+ const start = Date.now();
1328
+ const result = fn();
1329
+ steps[label] = Date.now() - start;
1330
+ return result;
1331
+ },
1332
+ record(label, ms) {
1333
+ steps[label] = ms;
1334
+ },
1335
+ summarize(label) {
1336
+ const total = Object.values(steps).reduce((a, b) => a + b, 0);
1337
+ log(`[timing] ${label}: ${total}ms`, steps);
1338
+ }
1339
+ };
1340
+ }
1341
+
1342
+ // src/adapters/claude/claude-agent.ts
1181
1343
  import { v7 as uuidv7 } from "uuid";
1182
1344
 
1183
1345
  // package.json
1184
1346
  var package_default = {
1185
1347
  name: "@posthog/agent",
1186
- version: "2.1.45",
1348
+ version: "2.1.47",
1187
1349
  repository: "https://github.com/PostHog/twig",
1188
1350
  description: "TypeScript agent framework wrapping Claude Agent SDK with Git-based task execution for PostHog",
1189
1351
  exports: {
@@ -3256,12 +3418,14 @@ var ClaudeAcpAgent = class extends BaseAcpAgent {
3256
3418
  backgroundTerminals = {};
3257
3419
  clientCapabilities;
3258
3420
  logWriter;
3259
- processCallbacks;
3421
+ options;
3260
3422
  lastSentConfigOptions;
3261
- constructor(client, logWriter, processCallbacks) {
3423
+ debug;
3424
+ constructor(client, logWriter, options) {
3262
3425
  super(client);
3263
3426
  this.logWriter = logWriter;
3264
- this.processCallbacks = processCallbacks;
3427
+ this.options = options;
3428
+ this.debug = options?.debug ?? false;
3265
3429
  this.toolUseCache = {};
3266
3430
  this.logger = new Logger({ debug: true, prefix: "[ClaudeAcpAgent]" });
3267
3431
  }
@@ -3304,27 +3468,36 @@ var ClaudeAcpAgent = class extends BaseAcpAgent {
3304
3468
  }
3305
3469
  async newSession(params) {
3306
3470
  this.checkAuthStatus();
3471
+ const tc = createTimingCollector(
3472
+ this.debug,
3473
+ (msg, data) => this.logger.info(msg, data)
3474
+ );
3307
3475
  const meta = params._meta;
3308
3476
  const sessionId = uuidv7();
3309
3477
  const permissionMode = meta?.permissionMode && TWIG_EXECUTION_MODES.includes(meta.permissionMode) ? meta.permissionMode : "default";
3310
- const mcpServers = parseMcpServers(params);
3311
- const mcpMetadataPromise = fetchMcpToolMetadata(mcpServers, this.logger);
3312
- const options = buildSessionOptions({
3313
- cwd: params.cwd,
3314
- mcpServers,
3315
- permissionMode,
3316
- canUseTool: this.createCanUseTool(sessionId),
3317
- logger: this.logger,
3318
- systemPrompt: buildSystemPrompt(meta?.systemPrompt),
3319
- userProvidedOptions: meta?.claudeCode?.options,
3320
- sessionId,
3321
- isResume: false,
3322
- onModeChange: this.createOnModeChange(sessionId),
3323
- onProcessSpawned: this.processCallbacks?.onProcessSpawned,
3324
- onProcessExited: this.processCallbacks?.onProcessExited
3325
- });
3478
+ const mcpServers = tc.timeSync(
3479
+ "parseMcpServers",
3480
+ () => parseMcpServers(params)
3481
+ );
3482
+ const options = tc.timeSync(
3483
+ "buildSessionOptions",
3484
+ () => buildSessionOptions({
3485
+ cwd: params.cwd,
3486
+ mcpServers,
3487
+ permissionMode,
3488
+ canUseTool: this.createCanUseTool(sessionId),
3489
+ logger: this.logger,
3490
+ systemPrompt: buildSystemPrompt(meta?.systemPrompt),
3491
+ userProvidedOptions: meta?.claudeCode?.options,
3492
+ sessionId,
3493
+ isResume: false,
3494
+ onModeChange: this.createOnModeChange(sessionId),
3495
+ onProcessSpawned: this.options?.onProcessSpawned,
3496
+ onProcessExited: this.options?.onProcessExited
3497
+ })
3498
+ );
3326
3499
  const input = new Pushable();
3327
- const q = query({ prompt: input, options });
3500
+ const q = tc.timeSync("sdkQuery", () => query({ prompt: input, options }));
3328
3501
  const session = this.createSession(
3329
3502
  sessionId,
3330
3503
  q,
@@ -3336,29 +3509,40 @@ var ClaudeAcpAgent = class extends BaseAcpAgent {
3336
3509
  session.taskRunId = meta?.taskRunId;
3337
3510
  this.registerPersistence(sessionId, meta);
3338
3511
  if (meta?.taskRunId) {
3339
- await this.client.extNotification("_posthog/sdk_session", {
3340
- taskRunId: meta.taskRunId,
3341
- sessionId,
3342
- adapter: "claude"
3343
- });
3512
+ await tc.time(
3513
+ "extNotification",
3514
+ () => this.client.extNotification("_posthog/sdk_session", {
3515
+ taskRunId: meta.taskRunId,
3516
+ sessionId,
3517
+ adapter: "claude"
3518
+ })
3519
+ );
3344
3520
  }
3345
- const [modelOptions, slashCommands] = await Promise.all([
3346
- this.getModelConfigOptions(),
3347
- getAvailableSlashCommands(q),
3348
- mcpMetadataPromise
3349
- ]);
3521
+ const modelOptions = await tc.time(
3522
+ "fetchModels",
3523
+ () => this.getModelConfigOptions()
3524
+ );
3525
+ this.deferBackgroundFetches(tc, q, sessionId, mcpServers);
3350
3526
  session.modelId = modelOptions.currentModelId;
3351
3527
  await this.trySetModel(q, modelOptions.currentModelId);
3352
- this.sendAvailableCommandsUpdate(sessionId, slashCommands);
3528
+ const configOptions = await tc.time(
3529
+ "buildConfigOptions",
3530
+ () => this.buildConfigOptions(modelOptions)
3531
+ );
3532
+ tc.summarize("newSession");
3353
3533
  return {
3354
3534
  sessionId,
3355
- configOptions: await this.buildConfigOptions(modelOptions)
3535
+ configOptions
3356
3536
  };
3357
3537
  }
3358
3538
  async loadSession(params) {
3359
3539
  return this.resumeSession(params);
3360
3540
  }
3361
3541
  async resumeSession(params) {
3542
+ const tc = createTimingCollector(
3543
+ this.debug,
3544
+ (msg, data) => this.logger.info(msg, data)
3545
+ );
3362
3546
  const meta = params._meta;
3363
3547
  const sessionId = meta?.sessionId;
3364
3548
  if (!sessionId) {
@@ -3367,29 +3551,33 @@ var ClaudeAcpAgent = class extends BaseAcpAgent {
3367
3551
  if (this.sessionId === sessionId) {
3368
3552
  return {};
3369
3553
  }
3370
- const mcpServers = parseMcpServers(params);
3371
- const mcpMetadataPromise = fetchMcpToolMetadata(mcpServers, this.logger);
3554
+ const mcpServers = tc.timeSync(
3555
+ "parseMcpServers",
3556
+ () => parseMcpServers(params)
3557
+ );
3372
3558
  const permissionMode = meta?.permissionMode && TWIG_EXECUTION_MODES.includes(meta.permissionMode) ? meta.permissionMode : "default";
3373
- const { query: q, session } = await this.initializeQuery({
3374
- cwd: params.cwd,
3375
- permissionMode,
3376
- mcpServers,
3377
- systemPrompt: buildSystemPrompt(meta?.systemPrompt),
3378
- userProvidedOptions: meta?.claudeCode?.options,
3379
- sessionId,
3380
- isResume: true,
3381
- additionalDirectories: meta?.claudeCode?.options?.additionalDirectories
3382
- });
3559
+ const { query: q, session } = await tc.time(
3560
+ "initializeQuery",
3561
+ () => this.initializeQuery({
3562
+ cwd: params.cwd,
3563
+ permissionMode,
3564
+ mcpServers,
3565
+ systemPrompt: buildSystemPrompt(meta?.systemPrompt),
3566
+ userProvidedOptions: meta?.claudeCode?.options,
3567
+ sessionId,
3568
+ isResume: true,
3569
+ additionalDirectories: meta?.claudeCode?.options?.additionalDirectories
3570
+ })
3571
+ );
3383
3572
  session.taskRunId = meta?.taskRunId;
3384
3573
  this.registerPersistence(sessionId, meta);
3385
- const [slashCommands] = await Promise.all([
3386
- getAvailableSlashCommands(q),
3387
- mcpMetadataPromise
3388
- ]);
3389
- this.sendAvailableCommandsUpdate(sessionId, slashCommands);
3390
- return {
3391
- configOptions: await this.buildConfigOptions()
3392
- };
3574
+ this.deferBackgroundFetches(tc, q, sessionId, mcpServers);
3575
+ const configOptions = await tc.time(
3576
+ "buildConfigOptions",
3577
+ () => this.buildConfigOptions()
3578
+ );
3579
+ tc.summarize("resumeSession");
3580
+ return { configOptions };
3393
3581
  }
3394
3582
  async prompt(params) {
3395
3583
  this.session.cancelled = false;
@@ -3461,8 +3649,8 @@ var ClaudeAcpAgent = class extends BaseAcpAgent {
3461
3649
  isResume: config.isResume,
3462
3650
  additionalDirectories: config.additionalDirectories,
3463
3651
  onModeChange: this.createOnModeChange(config.sessionId),
3464
- onProcessSpawned: this.processCallbacks?.onProcessSpawned,
3465
- onProcessExited: this.processCallbacks?.onProcessExited
3652
+ onProcessSpawned: this.options?.onProcessSpawned,
3653
+ onProcessExited: this.options?.onProcessExited
3466
3654
  });
3467
3655
  const q = query({ prompt: input, options });
3468
3656
  const abortController = options.abortController;
@@ -3572,6 +3760,23 @@ var ClaudeAcpAgent = class extends BaseAcpAgent {
3572
3760
  await q.setModel(fallback);
3573
3761
  }
3574
3762
  }
3763
+ /**
3764
+ * Fire-and-forget: fetch slash commands and MCP tool metadata in parallel.
3765
+ * Both populate caches used later — neither is needed to return configOptions.
3766
+ */
3767
+ deferBackgroundFetches(tc, q, sessionId, mcpServers) {
3768
+ Promise.all([
3769
+ tc.time("slashCommands", () => getAvailableSlashCommands(q)),
3770
+ tc.time(
3771
+ "mcpMetadata",
3772
+ () => fetchMcpToolMetadata(mcpServers, this.logger)
3773
+ )
3774
+ ]).then(([slashCommands]) => {
3775
+ this.sendAvailableCommandsUpdate(sessionId, slashCommands);
3776
+ }).catch((err) => {
3777
+ this.logger.warn("Failed to fetch deferred session data", { err });
3778
+ });
3779
+ }
3575
3780
  registerPersistence(sessionId, meta) {
3576
3781
  const persistence = meta?.persistence;
3577
3782
  if (persistence && this.logWriter) {
@@ -3859,7 +4064,10 @@ function createClaudeConnection(config) {
3859
4064
  const agentStream = ndJsonStream(agentWritable, streams.agent.readable);
3860
4065
  let agent = null;
3861
4066
  const agentConnection = new AgentSideConnection((client) => {
3862
- agent = new ClaudeAcpAgent(client, logWriter, config.processCallbacks);
4067
+ agent = new ClaudeAcpAgent(client, logWriter, {
4068
+ ...config.processCallbacks,
4069
+ debug: config.debug
4070
+ });
3863
4071
  logger.info(`Created ${agent.adapterName} agent`);
3864
4072
  return agent;
3865
4073
  }, agentStream);
@@ -9242,133 +9450,6 @@ async function getHeadSha(baseDir, options) {
9242
9450
  import { mkdir as mkdir3, rm as rm3, writeFile as writeFile3 } from "fs/promises";
9243
9451
  import { join as join5 } from "path";
9244
9452
 
9245
- // ../shared/dist/index.js
9246
- var consoleLogger = {
9247
- info: (_message, _data) => {
9248
- },
9249
- debug: (_message, _data) => {
9250
- },
9251
- error: (_message, _data) => {
9252
- },
9253
- warn: (_message, _data) => {
9254
- }
9255
- };
9256
- var Saga = class {
9257
- completedSteps = [];
9258
- currentStepName = "unknown";
9259
- stepTimings = [];
9260
- log;
9261
- constructor(logger) {
9262
- this.log = logger ?? consoleLogger;
9263
- }
9264
- /**
9265
- * Run the saga with the given input.
9266
- * Returns a discriminated union result - either success with data or failure with error details.
9267
- */
9268
- async run(input) {
9269
- this.completedSteps = [];
9270
- this.currentStepName = "unknown";
9271
- this.stepTimings = [];
9272
- const sagaStart = performance.now();
9273
- this.log.info("Starting saga", { sagaName: this.constructor.name });
9274
- try {
9275
- const result = await this.execute(input);
9276
- const totalDuration = performance.now() - sagaStart;
9277
- this.log.debug("Saga completed successfully", {
9278
- sagaName: this.constructor.name,
9279
- stepsCompleted: this.completedSteps.length,
9280
- totalDurationMs: Math.round(totalDuration),
9281
- stepTimings: this.stepTimings
9282
- });
9283
- return { success: true, data: result };
9284
- } catch (error) {
9285
- this.log.error("Saga failed, initiating rollback", {
9286
- sagaName: this.constructor.name,
9287
- failedStep: this.currentStepName,
9288
- error: error instanceof Error ? error.message : String(error)
9289
- });
9290
- await this.rollback();
9291
- return {
9292
- success: false,
9293
- error: error instanceof Error ? error.message : String(error),
9294
- failedStep: this.currentStepName
9295
- };
9296
- }
9297
- }
9298
- /**
9299
- * Execute a step with its rollback action.
9300
- * If the step succeeds, its rollback action is stored for potential rollback.
9301
- * The step name is automatically tracked for error reporting.
9302
- *
9303
- * @param config - Step configuration with name, execute, and rollback functions
9304
- * @returns The result of the execute function
9305
- * @throws Re-throws any error from the execute function (triggers rollback)
9306
- */
9307
- async step(config) {
9308
- this.currentStepName = config.name;
9309
- this.log.debug(`Executing step: ${config.name}`);
9310
- const stepStart = performance.now();
9311
- const result = await config.execute();
9312
- const durationMs = Math.round(performance.now() - stepStart);
9313
- this.stepTimings.push({ name: config.name, durationMs });
9314
- this.log.debug(`Step completed: ${config.name}`, { durationMs });
9315
- this.completedSteps.push({
9316
- name: config.name,
9317
- rollback: () => config.rollback(result)
9318
- });
9319
- return result;
9320
- }
9321
- /**
9322
- * Execute a step that doesn't need rollback.
9323
- * Useful for read-only operations or operations that are idempotent.
9324
- * The step name is automatically tracked for error reporting.
9325
- *
9326
- * @param name - Step name for logging and error tracking
9327
- * @param execute - The action to execute
9328
- * @returns The result of the execute function
9329
- */
9330
- async readOnlyStep(name, execute) {
9331
- this.currentStepName = name;
9332
- this.log.debug(`Executing read-only step: ${name}`);
9333
- const stepStart = performance.now();
9334
- const result = await execute();
9335
- const durationMs = Math.round(performance.now() - stepStart);
9336
- this.stepTimings.push({ name, durationMs });
9337
- this.log.debug(`Read-only step completed: ${name}`, { durationMs });
9338
- return result;
9339
- }
9340
- /**
9341
- * Roll back all completed steps in reverse order.
9342
- * Rollback errors are logged but don't stop the rollback of other steps.
9343
- */
9344
- async rollback() {
9345
- this.log.info("Rolling back saga", {
9346
- stepsToRollback: this.completedSteps.length
9347
- });
9348
- const stepsReversed = [...this.completedSteps].reverse();
9349
- for (const step of stepsReversed) {
9350
- try {
9351
- this.log.debug(`Rolling back step: ${step.name}`);
9352
- await step.rollback();
9353
- this.log.debug(`Step rolled back: ${step.name}`);
9354
- } catch (error) {
9355
- this.log.error(`Failed to rollback step: ${step.name}`, {
9356
- error: error instanceof Error ? error.message : String(error)
9357
- });
9358
- }
9359
- }
9360
- this.log.info("Rollback completed", {
9361
- stepsAttempted: this.completedSteps.length
9362
- });
9363
- }
9364
- /**
9365
- * Get the number of completed steps (useful for testing)
9366
- */
9367
- getCompletedStepCount() {
9368
- return this.completedSteps.length;
9369
- }
9370
- };
9371
-
9372
9453
  // ../git/dist/sagas/tree.js
9373
9454
  import { existsSync as existsSync4 } from "fs";
9374
9455
  import * as fs5 from "fs/promises";