@posthog/agent 2.1.148 → 2.1.152

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.
@@ -904,7 +904,7 @@ var import_hono = require("hono");
904
904
  // package.json
905
905
  var package_default = {
906
906
  name: "@posthog/agent",
907
- version: "2.1.148",
907
+ version: "2.1.152",
908
908
  repository: "https://github.com/PostHog/twig",
909
909
  description: "TypeScript agent framework wrapping Claude Agent SDK with Git-based task execution for PostHog",
910
910
  exports: {
@@ -1810,8 +1810,14 @@ function toolInfoFromToolUse(toolUse, options) {
1810
1810
  };
1811
1811
  case "Edit": {
1812
1812
  const path9 = input?.file_path ? String(input.file_path) : void 0;
1813
- const oldText = input?.old_string ? String(input.old_string) : null;
1814
- const newText = input?.new_string ? String(input.new_string) : "";
1813
+ let oldText = input?.old_string ? String(input.old_string) : null;
1814
+ let newText = input?.new_string ? String(input.new_string) : "";
1815
+ if (path9 && options?.cachedFileContent && path9 in options.cachedFileContent) {
1816
+ const oldContent = options.cachedFileContent[path9];
1817
+ const newContent = input?.replace_all ? oldContent.replaceAll(oldText ?? "", newText) : oldContent.replace(oldText ?? "", newText);
1818
+ oldText = oldContent;
1819
+ newText = newContent;
1820
+ }
1815
1821
  return {
1816
1822
  title: path9 ? `Edit \`${path9}\`` : "Edit",
1817
1823
  kind: "edit",
@@ -1831,7 +1837,8 @@ function toolInfoFromToolUse(toolUse, options) {
1831
1837
  const filePath = input?.file_path ? String(input.file_path) : void 0;
1832
1838
  const contentStr = input?.content ? String(input.content) : void 0;
1833
1839
  if (filePath) {
1834
- contentResult = toolContent().diff(filePath, null, contentStr ?? "").build();
1840
+ const oldContent = options?.cachedFileContent && filePath in options.cachedFileContent ? options.cachedFileContent[filePath] : null;
1841
+ contentResult = toolContent().diff(filePath, oldContent, contentStr ?? "").build();
1835
1842
  } else if (contentStr) {
1836
1843
  contentResult = toolContent().text(contentStr).build();
1837
1844
  }
@@ -2325,7 +2332,8 @@ function handleToolUseChunk(chunk, ctx) {
2325
2332
  }
2326
2333
  const toolInfo = toolInfoFromToolUse(chunk, {
2327
2334
  supportsTerminalOutput: ctx.supportsTerminalOutput,
2328
- toolUseId: chunk.id
2335
+ toolUseId: chunk.id,
2336
+ cachedFileContent: ctx.fileContentCache
2329
2337
  });
2330
2338
  const meta = {
2331
2339
  ...toolMeta(chunk.name, void 0, ctx.parentToolCallId)
@@ -2351,6 +2359,47 @@ function handleToolUseChunk(chunk, ctx) {
2351
2359
  ...toolInfo
2352
2360
  };
2353
2361
  }
2362
+ function extractTextFromContent(content) {
2363
+ if (Array.isArray(content)) {
2364
+ const parts = [];
2365
+ for (const item of content) {
2366
+ if (typeof item === "object" && item !== null && "text" in item && typeof item.text === "string") {
2367
+ parts.push(item.text);
2368
+ }
2369
+ }
2370
+ return parts.length > 0 ? parts.join("") : null;
2371
+ }
2372
+ if (typeof content === "string") {
2373
+ return content;
2374
+ }
2375
+ return null;
2376
+ }
2377
+ function stripCatLineNumbers(text2) {
2378
+ return text2.replace(/^ *\d+[\t→]/gm, "");
2379
+ }
2380
+ function updateFileContentCache(toolUse, chunk, ctx) {
2381
+ const input = toolUse.input;
2382
+ const filePath = input?.file_path ? String(input.file_path) : void 0;
2383
+ if (!filePath) return;
2384
+ if (toolUse.name === "Read" && !input?.limit && !input?.offset) {
2385
+ const fileText = extractTextFromContent(chunk.content);
2386
+ if (fileText !== null) {
2387
+ ctx.fileContentCache[filePath] = stripCatLineNumbers(fileText);
2388
+ }
2389
+ } else if (toolUse.name === "Write") {
2390
+ const content = input?.content;
2391
+ if (typeof content === "string") {
2392
+ ctx.fileContentCache[filePath] = content;
2393
+ }
2394
+ } else if (toolUse.name === "Edit") {
2395
+ const oldString = input?.old_string;
2396
+ const newString = input?.new_string;
2397
+ if (typeof oldString === "string" && typeof newString === "string" && filePath in ctx.fileContentCache) {
2398
+ const current = ctx.fileContentCache[filePath];
2399
+ ctx.fileContentCache[filePath] = input?.replace_all ? current.replaceAll(oldString, newString) : current.replace(oldString, newString);
2400
+ }
2401
+ }
2402
+ }
2354
2403
  function handleToolResultChunk(chunk, ctx) {
2355
2404
  const toolUse = ctx.toolUseCache[chunk.tool_use_id];
2356
2405
  if (!toolUse) {
@@ -2362,12 +2411,16 @@ function handleToolResultChunk(chunk, ctx) {
2362
2411
  if (toolUse.name === "TodoWrite") {
2363
2412
  return [];
2364
2413
  }
2414
+ if (!chunk.is_error) {
2415
+ updateFileContentCache(toolUse, chunk, ctx);
2416
+ }
2365
2417
  const { _meta: resultMeta, ...toolUpdate } = toolUpdateFromToolResult(
2366
2418
  chunk,
2367
2419
  toolUse,
2368
2420
  {
2369
2421
  supportsTerminalOutput: ctx.supportsTerminalOutput,
2370
- toolUseId: chunk.tool_use_id
2422
+ toolUseId: chunk.tool_use_id,
2423
+ cachedFileContent: ctx.fileContentCache
2371
2424
  }
2372
2425
  );
2373
2426
  const updates = [];
@@ -3416,7 +3469,7 @@ function getAbortController(userProvidedController) {
3416
3469
  }
3417
3470
  return controller;
3418
3471
  }
3419
- function buildSpawnWrapper(sessionId, onProcessSpawned, onProcessExited) {
3472
+ function buildSpawnWrapper(sessionId, onProcessSpawned, onProcessExited, logger) {
3420
3473
  return (spawnOpts) => {
3421
3474
  const child = (0, import_node_child_process.spawn)(spawnOpts.command, spawnOpts.args, {
3422
3475
  cwd: spawnOpts.cwd,
@@ -3430,6 +3483,12 @@ function buildSpawnWrapper(sessionId, onProcessSpawned, onProcessExited) {
3430
3483
  sessionId
3431
3484
  });
3432
3485
  }
3486
+ child.stderr?.on("data", (data) => {
3487
+ const msg = data.toString().trim();
3488
+ if (msg && logger) {
3489
+ logger.debug(`[claude-code:${child.pid}] stderr: ${msg}`);
3490
+ }
3491
+ });
3433
3492
  if (onProcessExited) {
3434
3493
  child.on("exit", () => {
3435
3494
  if (child.pid) {
@@ -3521,7 +3580,8 @@ function buildSessionOptions(params) {
3521
3580
  spawnClaudeCodeProcess: buildSpawnWrapper(
3522
3581
  params.sessionId,
3523
3582
  params.onProcessSpawned,
3524
- params.onProcessExited
3583
+ params.onProcessExited,
3584
+ params.logger
3525
3585
  )
3526
3586
  }
3527
3587
  };
@@ -4524,7 +4584,7 @@ function spawnCodexProcess(options) {
4524
4584
  detached: process.platform !== "win32"
4525
4585
  });
4526
4586
  child.stderr?.on("data", (data) => {
4527
- logger.debug("codex-acp stderr:", data.toString());
4587
+ logger.warn("codex-acp stderr:", data.toString());
4528
4588
  });
4529
4589
  child.on("error", (err) => {
4530
4590
  logger.error("codex-acp process error:", err);
@@ -5087,12 +5147,14 @@ var PostHogAPIClient = class {
5087
5147
 
5088
5148
  // src/session-log-writer.ts
5089
5149
  var import_node_fs2 = __toESM(require("fs"), 1);
5150
+ var import_promises = __toESM(require("fs/promises"), 1);
5090
5151
  var import_node_path2 = __toESM(require("path"), 1);
5091
5152
  var SessionLogWriter = class _SessionLogWriter {
5092
5153
  static FLUSH_DEBOUNCE_MS = 500;
5093
5154
  static FLUSH_MAX_INTERVAL_MS = 5e3;
5094
5155
  static MAX_FLUSH_RETRIES = 10;
5095
5156
  static MAX_RETRY_DELAY_MS = 3e4;
5157
+ static SESSIONS_MAX_AGE_MS = 30 * 24 * 60 * 60 * 1e3;
5096
5158
  posthogAPI;
5097
5159
  pendingEntries = /* @__PURE__ */ new Map();
5098
5160
  flushTimeouts = /* @__PURE__ */ new Map();
@@ -5229,10 +5291,16 @@ var SessionLogWriter = class _SessionLogWriter {
5229
5291
  );
5230
5292
  this.retryCounts.set(sessionId, 0);
5231
5293
  } else {
5232
- this.logger.error(
5233
- `Failed to persist session logs (attempt ${retryCount}/${_SessionLogWriter.MAX_FLUSH_RETRIES}):`,
5234
- error
5235
- );
5294
+ if (retryCount === 1) {
5295
+ this.logger.warn(
5296
+ `Failed to persist session logs, will retry (up to ${_SessionLogWriter.MAX_FLUSH_RETRIES} attempts)`,
5297
+ {
5298
+ taskId: session.context.taskId,
5299
+ runId: session.context.runId,
5300
+ error: error instanceof Error ? error.message : String(error)
5301
+ }
5302
+ );
5303
+ }
5236
5304
  const currentPending = this.pendingEntries.get(sessionId) ?? [];
5237
5305
  this.pendingEntries.set(sessionId, [...pending, ...currentPending]);
5238
5306
  this.scheduleFlush(sessionId);
@@ -5346,6 +5414,27 @@ var SessionLogWriter = class _SessionLogWriter {
5346
5414
  });
5347
5415
  }
5348
5416
  }
5417
+ static async cleanupOldSessions(localCachePath) {
5418
+ const sessionsDir = import_node_path2.default.join(localCachePath, "sessions");
5419
+ let deleted = 0;
5420
+ try {
5421
+ const entries = await import_promises.default.readdir(sessionsDir);
5422
+ const now = Date.now();
5423
+ for (const entry of entries) {
5424
+ const entryPath = import_node_path2.default.join(sessionsDir, entry);
5425
+ try {
5426
+ const stats = await import_promises.default.stat(entryPath);
5427
+ if (stats.isDirectory() && now - stats.birthtimeMs > _SessionLogWriter.SESSIONS_MAX_AGE_MS) {
5428
+ await import_promises.default.rm(entryPath, { recursive: true, force: true });
5429
+ deleted++;
5430
+ }
5431
+ } catch {
5432
+ }
5433
+ }
5434
+ } catch {
5435
+ }
5436
+ return deleted;
5437
+ }
5349
5438
  };
5350
5439
 
5351
5440
  // ../git/dist/queries.js
@@ -9913,7 +10002,7 @@ function createGitClient(baseDir, options) {
9913
10002
 
9914
10003
  // ../git/dist/lock-detector.js
9915
10004
  var import_node_child_process3 = require("child_process");
9916
- var import_promises = __toESM(require("fs/promises"), 1);
10005
+ var import_promises2 = __toESM(require("fs/promises"), 1);
9917
10006
  var import_node_path4 = __toESM(require("path"), 1);
9918
10007
  var import_node_util = require("util");
9919
10008
  var execFileAsync = (0, import_node_util.promisify)(import_node_child_process3.execFile);
@@ -9928,7 +10017,7 @@ async function getIndexLockPath(repoPath) {
9928
10017
  async function getLockInfo(repoPath) {
9929
10018
  const lockPath = await getIndexLockPath(repoPath);
9930
10019
  try {
9931
- const stat = await import_promises.default.stat(lockPath);
10020
+ const stat = await import_promises2.default.stat(lockPath);
9932
10021
  return {
9933
10022
  path: lockPath,
9934
10023
  ageMs: Date.now() - stat.mtimeMs
@@ -9939,7 +10028,7 @@ async function getLockInfo(repoPath) {
9939
10028
  }
9940
10029
  async function removeLock(repoPath) {
9941
10030
  const lockPath = await getIndexLockPath(repoPath);
9942
- await import_promises.default.rm(lockPath, { force: true });
10031
+ await import_promises2.default.rm(lockPath, { force: true });
9943
10032
  }
9944
10033
  async function isLocked(repoPath) {
9945
10034
  return await getLockInfo(repoPath) !== null;
@@ -10105,7 +10194,7 @@ async function getHeadSha(baseDir, options) {
10105
10194
  }
10106
10195
 
10107
10196
  // src/sagas/apply-snapshot-saga.ts
10108
- var import_promises2 = require("fs/promises");
10197
+ var import_promises3 = require("fs/promises");
10109
10198
  var import_node_path5 = require("path");
10110
10199
 
10111
10200
  // ../shared/dist/index.js
@@ -10136,12 +10225,12 @@ var Saga = class {
10136
10225
  this.currentStepName = "unknown";
10137
10226
  this.stepTimings = [];
10138
10227
  const sagaStart = performance.now();
10139
- this.log.info("Starting saga", { sagaName: this.constructor.name });
10228
+ this.log.info("Starting saga", { sagaName: this.sagaName });
10140
10229
  try {
10141
10230
  const result = await this.execute(input);
10142
10231
  const totalDuration = performance.now() - sagaStart;
10143
10232
  this.log.debug("Saga completed successfully", {
10144
- sagaName: this.constructor.name,
10233
+ sagaName: this.sagaName,
10145
10234
  stepsCompleted: this.completedSteps.length,
10146
10235
  totalDurationMs: Math.round(totalDuration),
10147
10236
  stepTimings: this.stepTimings
@@ -10149,7 +10238,7 @@ var Saga = class {
10149
10238
  return { success: true, data: result };
10150
10239
  } catch (error) {
10151
10240
  this.log.error("Saga failed, initiating rollback", {
10152
- sagaName: this.constructor.name,
10241
+ sagaName: this.sagaName,
10153
10242
  failedStep: this.currentStepName,
10154
10243
  error: error instanceof Error ? error.message : String(error)
10155
10244
  });
@@ -10261,6 +10350,7 @@ var GitSaga = class extends Saga {
10261
10350
 
10262
10351
  // ../git/dist/sagas/tree.js
10263
10352
  var CaptureTreeSaga = class extends GitSaga {
10353
+ sagaName = "CaptureTreeSaga";
10264
10354
  tempIndexPath = null;
10265
10355
  async executeGitOperations(input) {
10266
10356
  const { baseDir, lastTreeHash, archivePath, signal } = input;
@@ -10381,6 +10471,7 @@ var CaptureTreeSaga = class extends GitSaga {
10381
10471
  }
10382
10472
  };
10383
10473
  var ApplyTreeSaga = class extends GitSaga {
10474
+ sagaName = "ApplyTreeSaga";
10384
10475
  originalHead = null;
10385
10476
  originalBranch = null;
10386
10477
  extractedFiles = [];
@@ -10515,6 +10606,7 @@ var ApplyTreeSaga = class extends GitSaga {
10515
10606
 
10516
10607
  // src/sagas/apply-snapshot-saga.ts
10517
10608
  var ApplySnapshotSaga = class extends Saga {
10609
+ sagaName = "ApplySnapshotSaga";
10518
10610
  archivePath = null;
10519
10611
  async execute(input) {
10520
10612
  const { snapshot, repositoryPath, apiClient, taskId, runId } = input;
@@ -10525,7 +10617,7 @@ var ApplySnapshotSaga = class extends Saga {
10525
10617
  const archiveUrl = snapshot.archiveUrl;
10526
10618
  await this.step({
10527
10619
  name: "create_tmp_dir",
10528
- execute: () => (0, import_promises2.mkdir)(tmpDir, { recursive: true }),
10620
+ execute: () => (0, import_promises3.mkdir)(tmpDir, { recursive: true }),
10529
10621
  rollback: async () => {
10530
10622
  }
10531
10623
  });
@@ -10544,11 +10636,11 @@ var ApplySnapshotSaga = class extends Saga {
10544
10636
  }
10545
10637
  const base64Content = Buffer.from(arrayBuffer).toString("utf-8");
10546
10638
  const binaryContent = Buffer.from(base64Content, "base64");
10547
- await (0, import_promises2.writeFile)(archivePath, binaryContent);
10639
+ await (0, import_promises3.writeFile)(archivePath, binaryContent);
10548
10640
  },
10549
10641
  rollback: async () => {
10550
10642
  if (this.archivePath) {
10551
- await (0, import_promises2.rm)(this.archivePath, { force: true }).catch(() => {
10643
+ await (0, import_promises3.rm)(this.archivePath, { force: true }).catch(() => {
10552
10644
  });
10553
10645
  }
10554
10646
  }
@@ -10564,7 +10656,7 @@ var ApplySnapshotSaga = class extends Saga {
10564
10656
  if (!applyResult.success) {
10565
10657
  throw new Error(`Failed to apply tree: ${applyResult.error}`);
10566
10658
  }
10567
- await (0, import_promises2.rm)(this.archivePath, { force: true }).catch(() => {
10659
+ await (0, import_promises3.rm)(this.archivePath, { force: true }).catch(() => {
10568
10660
  });
10569
10661
  this.log.info("Tree snapshot applied", {
10570
10662
  treeHash: snapshot.treeHash,
@@ -10577,9 +10669,10 @@ var ApplySnapshotSaga = class extends Saga {
10577
10669
 
10578
10670
  // src/sagas/capture-tree-saga.ts
10579
10671
  var import_node_fs4 = require("fs");
10580
- var import_promises3 = require("fs/promises");
10672
+ var import_promises4 = require("fs/promises");
10581
10673
  var import_node_path6 = require("path");
10582
10674
  var CaptureTreeSaga2 = class extends Saga {
10675
+ sagaName = "CaptureTreeSaga";
10583
10676
  async execute(input) {
10584
10677
  const {
10585
10678
  repositoryPath,
@@ -10626,7 +10719,7 @@ var CaptureTreeSaga2 = class extends Saga {
10626
10719
  runId
10627
10720
  );
10628
10721
  } finally {
10629
- await (0, import_promises3.rm)(createdArchivePath, { force: true }).catch(() => {
10722
+ await (0, import_promises4.rm)(createdArchivePath, { force: true }).catch(() => {
10630
10723
  });
10631
10724
  }
10632
10725
  }
@@ -10650,7 +10743,7 @@ var CaptureTreeSaga2 = class extends Saga {
10650
10743
  const archiveUrl = await this.step({
10651
10744
  name: "upload_archive",
10652
10745
  execute: async () => {
10653
- const archiveContent = await (0, import_promises3.readFile)(archivePath);
10746
+ const archiveContent = await (0, import_promises4.readFile)(archivePath);
10654
10747
  const base64Content = archiveContent.toString("base64");
10655
10748
  const artifacts = await apiClient.uploadTaskArtifacts(taskId, runId, [
10656
10749
  {
@@ -10670,7 +10763,7 @@ var CaptureTreeSaga2 = class extends Saga {
10670
10763
  return void 0;
10671
10764
  },
10672
10765
  rollback: async () => {
10673
- await (0, import_promises3.rm)(archivePath, { force: true }).catch(() => {
10766
+ await (0, import_promises4.rm)(archivePath, { force: true }).catch(() => {
10674
10767
  });
10675
10768
  }
10676
10769
  });
@@ -10978,6 +11071,24 @@ var AgentServer = class {
10978
11071
  posthogAPI;
10979
11072
  questionRelayedToSlack = false;
10980
11073
  detectedPrUrl = null;
11074
+ emitConsoleLog = (level, _scope, message, data) => {
11075
+ if (!this.session) return;
11076
+ const formatted = data !== void 0 ? `${message} ${JSON.stringify(data)}` : message;
11077
+ const notification = {
11078
+ jsonrpc: "2.0",
11079
+ method: POSTHOG_NOTIFICATIONS.CONSOLE,
11080
+ params: { level, message: formatted }
11081
+ };
11082
+ this.broadcastEvent({
11083
+ type: "notification",
11084
+ timestamp: (/* @__PURE__ */ new Date()).toISOString(),
11085
+ notification
11086
+ });
11087
+ this.session.logWriter.appendRawLine(
11088
+ this.session.payload.run_id,
11089
+ JSON.stringify(notification)
11090
+ );
11091
+ };
10981
11092
  constructor(config) {
10982
11093
  this.config = config;
10983
11094
  this.logger = new Logger({ debug: true, prefix: "[AgentServer]" });
@@ -11329,6 +11440,14 @@ You MUST NOT create a new branch, close the existing PR, or create a new PR.`
11329
11440
  deviceInfo,
11330
11441
  logWriter
11331
11442
  };
11443
+ this.logger = new Logger({
11444
+ debug: true,
11445
+ prefix: "[AgentServer]",
11446
+ onLog: (level, scope, message, data) => {
11447
+ const _formatted = data !== void 0 ? `${message} ${JSON.stringify(data)}` : message;
11448
+ this.emitConsoleLog(level, scope, message, data);
11449
+ }
11450
+ });
11332
11451
  this.logger.info("Session initialized successfully");
11333
11452
  this.posthogAPI.updateTaskRun(payload.task_id, payload.run_id, {
11334
11453
  status: "in_progress"
@@ -11711,15 +11830,25 @@ Important:
11711
11830
  ...snapshot,
11712
11831
  device: this.session.deviceInfo
11713
11832
  };
11833
+ const notification = {
11834
+ jsonrpc: "2.0",
11835
+ method: POSTHOG_NOTIFICATIONS.TREE_SNAPSHOT,
11836
+ params: snapshotWithDevice
11837
+ };
11714
11838
  this.broadcastEvent({
11715
11839
  type: "notification",
11716
11840
  timestamp: (/* @__PURE__ */ new Date()).toISOString(),
11717
- notification: {
11718
- jsonrpc: "2.0",
11719
- method: POSTHOG_NOTIFICATIONS.TREE_SNAPSHOT,
11720
- params: snapshotWithDevice
11721
- }
11841
+ notification
11722
11842
  });
11843
+ const { archiveUrl: _, ...paramsWithoutArchive } = snapshotWithDevice;
11844
+ const logNotification = {
11845
+ ...notification,
11846
+ params: paramsWithoutArchive
11847
+ };
11848
+ this.session.logWriter.appendRawLine(
11849
+ this.session.payload.run_id,
11850
+ JSON.stringify(logNotification)
11851
+ );
11723
11852
  }
11724
11853
  } catch (error) {
11725
11854
  this.logger.error("Failed to capture tree state", error);