@posthog/agent 2.3.5 → 2.3.10

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.
@@ -509,7 +509,7 @@ var require_has_flag = __commonJS({
509
509
  var require_supports_color = __commonJS({
510
510
  "../../node_modules/supports-color/index.js"(exports2, module2) {
511
511
  "use strict";
512
- var os5 = require("os");
512
+ var os6 = require("os");
513
513
  var tty = require("tty");
514
514
  var hasFlag = require_has_flag();
515
515
  var { env } = process;
@@ -557,7 +557,7 @@ var require_supports_color = __commonJS({
557
557
  return min;
558
558
  }
559
559
  if (process.platform === "win32") {
560
- const osRelease = os5.release().split(".");
560
+ const osRelease = os6.release().split(".");
561
561
  if (Number(osRelease[0]) >= 10 && Number(osRelease[2]) >= 10586) {
562
562
  return Number(osRelease[2]) >= 14931 ? 3 : 2;
563
563
  }
@@ -805,10 +805,10 @@ var require_src2 = __commonJS({
805
805
  var fs_1 = require("fs");
806
806
  var debug_1 = __importDefault(require_src());
807
807
  var log = debug_1.default("@kwsites/file-exists");
808
- function check(path10, isFile, isDirectory) {
809
- log(`checking %s`, path10);
808
+ function check(path11, isFile, isDirectory) {
809
+ log(`checking %s`, path11);
810
810
  try {
811
- const stat = fs_1.statSync(path10);
811
+ const stat = fs_1.statSync(path11);
812
812
  if (stat.isFile() && isFile) {
813
813
  log(`[OK] path represents a file`);
814
814
  return true;
@@ -828,8 +828,8 @@ var require_src2 = __commonJS({
828
828
  throw e;
829
829
  }
830
830
  }
831
- function exists2(path10, type = exports2.READABLE) {
832
- return check(path10, (type & exports2.FILE) > 0, (type & exports2.FOLDER) > 0);
831
+ function exists2(path11, type = exports2.READABLE) {
832
+ return check(path11, (type & exports2.FILE) > 0, (type & exports2.FOLDER) > 0);
833
833
  }
834
834
  exports2.exists = exists2;
835
835
  exports2.FILE = 1;
@@ -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.3.5",
907
+ version: "2.3.10",
908
908
  repository: "https://github.com/PostHog/code",
909
909
  description: "TypeScript agent framework wrapping Claude Agent SDK with Git-based task execution for PostHog",
910
910
  exports: {
@@ -1029,6 +1029,8 @@ var POSTHOG_NOTIFICATIONS = {
1029
1029
  RUN_STARTED: "_posthog/run_started",
1030
1030
  /** Task has completed (success or failure) */
1031
1031
  TASK_COMPLETE: "_posthog/task_complete",
1032
+ /** Agent finished processing a turn (prompt returned, waiting for next input) */
1033
+ TURN_COMPLETE: "_posthog/turn_complete",
1032
1034
  /** Error occurred during task execution */
1033
1035
  ERROR: "_posthog/error",
1034
1036
  /** Console/log output from the agent */
@@ -1570,8 +1572,8 @@ var ToolContentBuilder = class {
1570
1572
  this.items.push({ type: "content", content: image(data, mimeType, uri) });
1571
1573
  return this;
1572
1574
  }
1573
- diff(path10, oldText, newText) {
1574
- this.items.push({ type: "diff", path: path10, oldText, newText });
1575
+ diff(path11, oldText, newText) {
1576
+ this.items.push({ type: "diff", path: path11, oldText, newText });
1575
1577
  return this;
1576
1578
  }
1577
1579
  build() {
@@ -4983,6 +4985,45 @@ function createCodexConnection(config) {
4983
4985
  };
4984
4986
  }
4985
4987
 
4988
+ // src/adapters/claude/session/jsonl-hydration.ts
4989
+ var import_node_crypto2 = require("crypto");
4990
+ var fs4 = __toESM(require("fs/promises"), 1);
4991
+ var os5 = __toESM(require("os"), 1);
4992
+ var path6 = __toESM(require("path"), 1);
4993
+ var CHARS_PER_TOKEN = 4;
4994
+ var DEFAULT_MAX_TOKENS = 15e4;
4995
+ function estimateTurnTokens(turn) {
4996
+ let chars = 0;
4997
+ for (const block of turn.content) {
4998
+ if ("text" in block && typeof block.text === "string") {
4999
+ chars += block.text.length;
5000
+ }
5001
+ }
5002
+ if (turn.toolCalls) {
5003
+ for (const tc of turn.toolCalls) {
5004
+ chars += JSON.stringify(tc.input ?? "").length;
5005
+ if (tc.result !== void 0) {
5006
+ chars += typeof tc.result === "string" ? tc.result.length : JSON.stringify(tc.result).length;
5007
+ }
5008
+ }
5009
+ }
5010
+ return Math.ceil(chars / CHARS_PER_TOKEN);
5011
+ }
5012
+ function selectRecentTurns(turns, maxTokens = DEFAULT_MAX_TOKENS) {
5013
+ let budget = maxTokens;
5014
+ let startIndex = turns.length;
5015
+ for (let i = turns.length - 1; i >= 0; i--) {
5016
+ const cost = estimateTurnTokens(turns[i]);
5017
+ if (cost > budget) break;
5018
+ budget -= cost;
5019
+ startIndex = i;
5020
+ }
5021
+ while (startIndex < turns.length && turns[startIndex].role !== "user") {
5022
+ startIndex++;
5023
+ }
5024
+ return turns.slice(startIndex);
5025
+ }
5026
+
4986
5027
  // src/utils/gateway.ts
4987
5028
  function getLlmGatewayUrl(posthogHost, product = "posthog_code") {
4988
5029
  const url = new URL(posthogHost);
@@ -5166,330 +5207,165 @@ var PostHogAPIClient = class {
5166
5207
  }
5167
5208
  };
5168
5209
 
5169
- // src/session-log-writer.ts
5170
- var import_node_fs2 = __toESM(require("fs"), 1);
5171
- var import_promises = __toESM(require("fs/promises"), 1);
5172
- var import_node_path3 = __toESM(require("path"), 1);
5173
- var SessionLogWriter = class _SessionLogWriter {
5174
- static FLUSH_DEBOUNCE_MS = 500;
5175
- static FLUSH_MAX_INTERVAL_MS = 5e3;
5176
- static MAX_FLUSH_RETRIES = 10;
5177
- static MAX_RETRY_DELAY_MS = 3e4;
5178
- static SESSIONS_MAX_AGE_MS = 30 * 24 * 60 * 60 * 1e3;
5179
- posthogAPI;
5180
- pendingEntries = /* @__PURE__ */ new Map();
5181
- flushTimeouts = /* @__PURE__ */ new Map();
5182
- lastFlushAttemptTime = /* @__PURE__ */ new Map();
5183
- retryCounts = /* @__PURE__ */ new Map();
5184
- sessions = /* @__PURE__ */ new Map();
5185
- logger;
5186
- localCachePath;
5187
- constructor(options = {}) {
5188
- this.posthogAPI = options.posthogAPI;
5189
- this.localCachePath = options.localCachePath;
5190
- this.logger = options.logger ?? new Logger({ debug: false, prefix: "[SessionLogWriter]" });
5210
+ // ../shared/dist/index.js
5211
+ var consoleLogger = {
5212
+ info: (_message, _data) => {
5213
+ },
5214
+ debug: (_message, _data) => {
5215
+ },
5216
+ error: (_message, _data) => {
5217
+ },
5218
+ warn: (_message, _data) => {
5191
5219
  }
5192
- async flushAll() {
5193
- const sessionIds = [...this.sessions.keys()];
5194
- const flushPromises = [];
5195
- for (const sessionId of sessionIds) {
5196
- flushPromises.push(this.flush(sessionId));
5197
- }
5198
- await Promise.all(flushPromises);
5220
+ };
5221
+ var Saga = class {
5222
+ completedSteps = [];
5223
+ currentStepName = "unknown";
5224
+ stepTimings = [];
5225
+ log;
5226
+ constructor(logger) {
5227
+ this.log = logger ?? consoleLogger;
5199
5228
  }
5200
- register(sessionId, context) {
5201
- if (this.sessions.has(sessionId)) {
5202
- return;
5229
+ /**
5230
+ * Run the saga with the given input.
5231
+ * Returns a discriminated union result - either success with data or failure with error details.
5232
+ */
5233
+ async run(input) {
5234
+ this.completedSteps = [];
5235
+ this.currentStepName = "unknown";
5236
+ this.stepTimings = [];
5237
+ const sagaStart = performance.now();
5238
+ this.log.info("Starting saga", { sagaName: this.sagaName });
5239
+ try {
5240
+ const result = await this.execute(input);
5241
+ const totalDuration = performance.now() - sagaStart;
5242
+ this.log.debug("Saga completed successfully", {
5243
+ sagaName: this.sagaName,
5244
+ stepsCompleted: this.completedSteps.length,
5245
+ totalDurationMs: Math.round(totalDuration),
5246
+ stepTimings: this.stepTimings
5247
+ });
5248
+ return { success: true, data: result };
5249
+ } catch (error) {
5250
+ this.log.error("Saga failed, initiating rollback", {
5251
+ sagaName: this.sagaName,
5252
+ failedStep: this.currentStepName,
5253
+ error: error instanceof Error ? error.message : String(error)
5254
+ });
5255
+ await this.rollback();
5256
+ return {
5257
+ success: false,
5258
+ error: error instanceof Error ? error.message : String(error),
5259
+ failedStep: this.currentStepName
5260
+ };
5203
5261
  }
5204
- this.logger.info("Session registered", {
5205
- taskId: context.taskId,
5206
- runId: context.runId
5262
+ }
5263
+ /**
5264
+ * Execute a step with its rollback action.
5265
+ * If the step succeeds, its rollback action is stored for potential rollback.
5266
+ * The step name is automatically tracked for error reporting.
5267
+ *
5268
+ * @param config - Step configuration with name, execute, and rollback functions
5269
+ * @returns The result of the execute function
5270
+ * @throws Re-throws any error from the execute function (triggers rollback)
5271
+ */
5272
+ async step(config) {
5273
+ this.currentStepName = config.name;
5274
+ this.log.debug(`Executing step: ${config.name}`);
5275
+ const stepStart = performance.now();
5276
+ const result = await config.execute();
5277
+ const durationMs = Math.round(performance.now() - stepStart);
5278
+ this.stepTimings.push({ name: config.name, durationMs });
5279
+ this.log.debug(`Step completed: ${config.name}`, { durationMs });
5280
+ this.completedSteps.push({
5281
+ name: config.name,
5282
+ rollback: () => config.rollback(result)
5207
5283
  });
5208
- this.sessions.set(sessionId, { context });
5209
- this.lastFlushAttemptTime.set(sessionId, Date.now());
5210
- if (this.localCachePath) {
5211
- const sessionDir = import_node_path3.default.join(
5212
- this.localCachePath,
5213
- "sessions",
5214
- context.runId
5215
- );
5284
+ return result;
5285
+ }
5286
+ /**
5287
+ * Execute a step that doesn't need rollback.
5288
+ * Useful for read-only operations or operations that are idempotent.
5289
+ * The step name is automatically tracked for error reporting.
5290
+ *
5291
+ * @param name - Step name for logging and error tracking
5292
+ * @param execute - The action to execute
5293
+ * @returns The result of the execute function
5294
+ */
5295
+ async readOnlyStep(name, execute) {
5296
+ this.currentStepName = name;
5297
+ this.log.debug(`Executing read-only step: ${name}`);
5298
+ const stepStart = performance.now();
5299
+ const result = await execute();
5300
+ const durationMs = Math.round(performance.now() - stepStart);
5301
+ this.stepTimings.push({ name, durationMs });
5302
+ this.log.debug(`Read-only step completed: ${name}`, { durationMs });
5303
+ return result;
5304
+ }
5305
+ /**
5306
+ * Roll back all completed steps in reverse order.
5307
+ * Rollback errors are logged but don't stop the rollback of other steps.
5308
+ */
5309
+ async rollback() {
5310
+ this.log.info("Rolling back saga", {
5311
+ stepsToRollback: this.completedSteps.length
5312
+ });
5313
+ const stepsReversed = [...this.completedSteps].reverse();
5314
+ for (const step of stepsReversed) {
5216
5315
  try {
5217
- import_node_fs2.default.mkdirSync(sessionDir, { recursive: true });
5316
+ this.log.debug(`Rolling back step: ${step.name}`);
5317
+ await step.rollback();
5318
+ this.log.debug(`Step rolled back: ${step.name}`);
5218
5319
  } catch (error) {
5219
- this.logger.warn("Failed to create local cache directory", {
5220
- sessionDir,
5221
- error
5320
+ this.log.error(`Failed to rollback step: ${step.name}`, {
5321
+ error: error instanceof Error ? error.message : String(error)
5222
5322
  });
5223
5323
  }
5224
5324
  }
5325
+ this.log.info("Rollback completed", {
5326
+ stepsAttempted: this.completedSteps.length
5327
+ });
5225
5328
  }
5226
- isRegistered(sessionId) {
5227
- return this.sessions.has(sessionId);
5329
+ /**
5330
+ * Get the number of completed steps (useful for testing)
5331
+ */
5332
+ getCompletedStepCount() {
5333
+ return this.completedSteps.length;
5228
5334
  }
5229
- appendRawLine(sessionId, line) {
5230
- const session = this.sessions.get(sessionId);
5231
- if (!session) {
5232
- this.logger.warn("appendRawLine called for unregistered session", {
5233
- sessionId
5234
- });
5235
- return;
5236
- }
5237
- try {
5238
- const message = JSON.parse(line);
5239
- const timestamp = (/* @__PURE__ */ new Date()).toISOString();
5240
- if (this.isAgentMessageChunk(message)) {
5241
- const text2 = this.extractChunkText(message);
5242
- if (text2) {
5243
- if (!session.chunkBuffer) {
5244
- session.chunkBuffer = { text: text2, firstTimestamp: timestamp };
5245
- } else {
5246
- session.chunkBuffer.text += text2;
5247
- }
5248
- }
5249
- return;
5250
- }
5251
- this.emitCoalescedMessage(sessionId, session);
5252
- const nonChunkAgentText = this.extractAgentMessageText(message);
5253
- if (nonChunkAgentText) {
5254
- session.lastAgentMessage = nonChunkAgentText;
5255
- }
5256
- const entry = {
5257
- type: "notification",
5258
- timestamp,
5259
- notification: message
5260
- };
5261
- this.writeToLocalCache(sessionId, entry);
5262
- if (this.posthogAPI) {
5263
- const pending = this.pendingEntries.get(sessionId) ?? [];
5264
- pending.push(entry);
5265
- this.pendingEntries.set(sessionId, pending);
5266
- this.scheduleFlush(sessionId);
5267
- }
5268
- } catch {
5269
- this.logger.warn("Failed to parse raw line for persistence", {
5270
- taskId: session.context.taskId,
5271
- runId: session.context.runId,
5272
- lineLength: line.length
5273
- });
5274
- }
5275
- }
5276
- async flush(sessionId) {
5277
- const session = this.sessions.get(sessionId);
5278
- if (!session) {
5279
- this.logger.warn("flush: no session found", { sessionId });
5280
- return;
5281
- }
5282
- this.emitCoalescedMessage(sessionId, session);
5283
- const pending = this.pendingEntries.get(sessionId);
5284
- if (!this.posthogAPI || !pending?.length) {
5285
- return;
5286
- }
5287
- this.pendingEntries.delete(sessionId);
5288
- const timeout = this.flushTimeouts.get(sessionId);
5289
- if (timeout) {
5290
- clearTimeout(timeout);
5291
- this.flushTimeouts.delete(sessionId);
5292
- }
5293
- this.lastFlushAttemptTime.set(sessionId, Date.now());
5294
- try {
5295
- await this.posthogAPI.appendTaskRunLog(
5296
- session.context.taskId,
5297
- session.context.runId,
5298
- pending
5299
- );
5300
- this.retryCounts.set(sessionId, 0);
5301
- } catch (error) {
5302
- const retryCount = (this.retryCounts.get(sessionId) ?? 0) + 1;
5303
- this.retryCounts.set(sessionId, retryCount);
5304
- if (retryCount >= _SessionLogWriter.MAX_FLUSH_RETRIES) {
5305
- this.logger.error(
5306
- `Dropping ${pending.length} session log entries after ${retryCount} failed flush attempts`,
5307
- {
5308
- taskId: session.context.taskId,
5309
- runId: session.context.runId,
5310
- error
5311
- }
5312
- );
5313
- this.retryCounts.set(sessionId, 0);
5314
- } else {
5315
- if (retryCount === 1) {
5316
- this.logger.warn(
5317
- `Failed to persist session logs, will retry (up to ${_SessionLogWriter.MAX_FLUSH_RETRIES} attempts)`,
5318
- {
5319
- taskId: session.context.taskId,
5320
- runId: session.context.runId,
5321
- error: error instanceof Error ? error.message : String(error)
5322
- }
5323
- );
5324
- }
5325
- const currentPending = this.pendingEntries.get(sessionId) ?? [];
5326
- this.pendingEntries.set(sessionId, [...pending, ...currentPending]);
5327
- this.scheduleFlush(sessionId);
5328
- }
5329
- }
5330
- }
5331
- isAgentMessageChunk(message) {
5332
- if (message.method !== "session/update") return false;
5333
- const params = message.params;
5334
- const update = params?.update;
5335
- return update?.sessionUpdate === "agent_message_chunk";
5336
- }
5337
- extractChunkText(message) {
5338
- const params = message.params;
5339
- const update = params?.update;
5340
- const content = update?.content;
5341
- if (content?.type === "text" && content.text) {
5342
- return content.text;
5343
- }
5344
- return "";
5345
- }
5346
- emitCoalescedMessage(sessionId, session) {
5347
- if (!session.chunkBuffer) return;
5348
- const { text: text2, firstTimestamp } = session.chunkBuffer;
5349
- session.chunkBuffer = void 0;
5350
- session.lastAgentMessage = text2;
5351
- const entry = {
5352
- type: "notification",
5353
- timestamp: firstTimestamp,
5354
- notification: {
5355
- jsonrpc: "2.0",
5356
- method: "session/update",
5357
- params: {
5358
- update: {
5359
- sessionUpdate: "agent_message",
5360
- content: { type: "text", text: text2 }
5361
- }
5362
- }
5363
- }
5364
- };
5365
- this.writeToLocalCache(sessionId, entry);
5366
- if (this.posthogAPI) {
5367
- const pending = this.pendingEntries.get(sessionId) ?? [];
5368
- pending.push(entry);
5369
- this.pendingEntries.set(sessionId, pending);
5370
- this.scheduleFlush(sessionId);
5371
- }
5372
- }
5373
- getLastAgentMessage(sessionId) {
5374
- return this.sessions.get(sessionId)?.lastAgentMessage;
5375
- }
5376
- extractAgentMessageText(message) {
5377
- if (message.method !== "session/update") {
5378
- return null;
5379
- }
5380
- const params = message.params;
5381
- const update = params?.update;
5382
- if (update?.sessionUpdate !== "agent_message") {
5383
- return null;
5384
- }
5385
- const content = update.content;
5386
- if (content?.type === "text" && typeof content.text === "string") {
5387
- const trimmed2 = content.text.trim();
5388
- return trimmed2.length > 0 ? trimmed2 : null;
5389
- }
5390
- if (typeof update.message === "string") {
5391
- const trimmed2 = update.message.trim();
5392
- return trimmed2.length > 0 ? trimmed2 : null;
5393
- }
5394
- return null;
5395
- }
5396
- scheduleFlush(sessionId) {
5397
- const existing = this.flushTimeouts.get(sessionId);
5398
- if (existing) clearTimeout(existing);
5399
- const retryCount = this.retryCounts.get(sessionId) ?? 0;
5400
- const lastAttempt = this.lastFlushAttemptTime.get(sessionId) ?? 0;
5401
- const elapsed = Date.now() - lastAttempt;
5402
- let delay3;
5403
- if (retryCount > 0) {
5404
- delay3 = Math.min(
5405
- _SessionLogWriter.FLUSH_DEBOUNCE_MS * 2 ** retryCount,
5406
- _SessionLogWriter.MAX_RETRY_DELAY_MS
5407
- );
5408
- } else if (elapsed >= _SessionLogWriter.FLUSH_MAX_INTERVAL_MS) {
5409
- delay3 = 0;
5410
- } else {
5411
- delay3 = _SessionLogWriter.FLUSH_DEBOUNCE_MS;
5412
- }
5413
- const timeout = setTimeout(() => this.flush(sessionId), delay3);
5414
- this.flushTimeouts.set(sessionId, timeout);
5415
- }
5416
- writeToLocalCache(sessionId, entry) {
5417
- if (!this.localCachePath) return;
5418
- const session = this.sessions.get(sessionId);
5419
- if (!session) return;
5420
- const logPath = import_node_path3.default.join(
5421
- this.localCachePath,
5422
- "sessions",
5423
- session.context.runId,
5424
- "logs.ndjson"
5425
- );
5426
- try {
5427
- import_node_fs2.default.appendFileSync(logPath, `${JSON.stringify(entry)}
5428
- `);
5429
- } catch (error) {
5430
- this.logger.warn("Failed to write to local cache", {
5431
- taskId: session.context.taskId,
5432
- runId: session.context.runId,
5433
- logPath,
5434
- error
5435
- });
5436
- }
5437
- }
5438
- static async cleanupOldSessions(localCachePath) {
5439
- const sessionsDir = import_node_path3.default.join(localCachePath, "sessions");
5440
- let deleted = 0;
5441
- try {
5442
- const entries = await import_promises.default.readdir(sessionsDir);
5443
- const now = Date.now();
5444
- for (const entry of entries) {
5445
- const entryPath = import_node_path3.default.join(sessionsDir, entry);
5446
- try {
5447
- const stats = await import_promises.default.stat(entryPath);
5448
- if (stats.isDirectory() && now - stats.birthtimeMs > _SessionLogWriter.SESSIONS_MAX_AGE_MS) {
5449
- await import_promises.default.rm(entryPath, { recursive: true, force: true });
5450
- deleted++;
5451
- }
5452
- } catch {
5453
- }
5454
- }
5455
- } catch {
5456
- }
5457
- return deleted;
5458
- }
5459
- };
5460
-
5461
- // ../git/dist/queries.js
5462
- var fs6 = __toESM(require("fs/promises"), 1);
5463
- var path8 = __toESM(require("path"), 1);
5464
-
5465
- // ../../node_modules/simple-git/dist/esm/index.js
5466
- var import_node_buffer = require("buffer");
5467
- var import_file_exists = __toESM(require_dist(), 1);
5468
- var import_debug = __toESM(require_src(), 1);
5469
- var import_child_process = require("child_process");
5470
- var import_promise_deferred = __toESM(require_dist2(), 1);
5471
- var import_node_path4 = require("path");
5472
- var import_promise_deferred2 = __toESM(require_dist2(), 1);
5473
- var import_node_events = require("events");
5474
- var __defProp2 = Object.defineProperty;
5475
- var __getOwnPropDesc2 = Object.getOwnPropertyDescriptor;
5476
- var __getOwnPropNames2 = Object.getOwnPropertyNames;
5477
- var __hasOwnProp2 = Object.prototype.hasOwnProperty;
5478
- var __esm = (fn, res) => function __init() {
5479
- return fn && (res = (0, fn[__getOwnPropNames2(fn)[0]])(fn = 0)), res;
5480
- };
5481
- var __commonJS2 = (cb, mod) => function __require() {
5482
- return mod || (0, cb[__getOwnPropNames2(cb)[0]])((mod = { exports: {} }).exports, mod), mod.exports;
5483
- };
5484
- var __export = (target, all) => {
5485
- for (var name in all)
5486
- __defProp2(target, name, { get: all[name], enumerable: true });
5487
- };
5488
- var __copyProps2 = (to, from, except, desc) => {
5489
- if (from && typeof from === "object" || typeof from === "function") {
5490
- for (let key of __getOwnPropNames2(from))
5491
- if (!__hasOwnProp2.call(to, key) && key !== except)
5492
- __defProp2(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc2(from, key)) || desc.enumerable });
5335
+ };
5336
+
5337
+ // ../git/dist/queries.js
5338
+ var fs6 = __toESM(require("fs/promises"), 1);
5339
+ var path8 = __toESM(require("path"), 1);
5340
+
5341
+ // ../../node_modules/simple-git/dist/esm/index.js
5342
+ var import_node_buffer = require("buffer");
5343
+ var import_file_exists = __toESM(require_dist(), 1);
5344
+ var import_debug = __toESM(require_src(), 1);
5345
+ var import_child_process = require("child_process");
5346
+ var import_promise_deferred = __toESM(require_dist2(), 1);
5347
+ var import_node_path3 = require("path");
5348
+ var import_promise_deferred2 = __toESM(require_dist2(), 1);
5349
+ var import_node_events = require("events");
5350
+ var __defProp2 = Object.defineProperty;
5351
+ var __getOwnPropDesc2 = Object.getOwnPropertyDescriptor;
5352
+ var __getOwnPropNames2 = Object.getOwnPropertyNames;
5353
+ var __hasOwnProp2 = Object.prototype.hasOwnProperty;
5354
+ var __esm = (fn, res) => function __init() {
5355
+ return fn && (res = (0, fn[__getOwnPropNames2(fn)[0]])(fn = 0)), res;
5356
+ };
5357
+ var __commonJS2 = (cb, mod) => function __require() {
5358
+ return mod || (0, cb[__getOwnPropNames2(cb)[0]])((mod = { exports: {} }).exports, mod), mod.exports;
5359
+ };
5360
+ var __export = (target, all) => {
5361
+ for (var name in all)
5362
+ __defProp2(target, name, { get: all[name], enumerable: true });
5363
+ };
5364
+ var __copyProps2 = (to, from, except, desc) => {
5365
+ if (from && typeof from === "object" || typeof from === "function") {
5366
+ for (let key of __getOwnPropNames2(from))
5367
+ if (!__hasOwnProp2.call(to, key) && key !== except)
5368
+ __defProp2(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc2(from, key)) || desc.enumerable });
5493
5369
  }
5494
5370
  return to;
5495
5371
  };
@@ -5499,8 +5375,8 @@ function pathspec(...paths) {
5499
5375
  cache.set(key, paths);
5500
5376
  return key;
5501
5377
  }
5502
- function isPathSpec(path10) {
5503
- return path10 instanceof String && cache.has(path10);
5378
+ function isPathSpec(path11) {
5379
+ return path11 instanceof String && cache.has(path11);
5504
5380
  }
5505
5381
  function toPaths(pathSpec) {
5506
5382
  return cache.get(pathSpec) || [];
@@ -5589,8 +5465,8 @@ function toLinesWithContent(input = "", trimmed2 = true, separator = "\n") {
5589
5465
  function forEachLineWithContent(input, callback) {
5590
5466
  return toLinesWithContent(input, true).map((line) => callback(line));
5591
5467
  }
5592
- function folderExists(path10) {
5593
- return (0, import_file_exists.exists)(path10, import_file_exists.FOLDER);
5468
+ function folderExists(path11) {
5469
+ return (0, import_file_exists.exists)(path11, import_file_exists.FOLDER);
5594
5470
  }
5595
5471
  function append(target, item) {
5596
5472
  if (Array.isArray(target)) {
@@ -5994,8 +5870,8 @@ function checkIsRepoRootTask() {
5994
5870
  commands,
5995
5871
  format: "utf-8",
5996
5872
  onError,
5997
- parser(path10) {
5998
- return /^\.(git)?$/.test(path10.trim());
5873
+ parser(path11) {
5874
+ return /^\.(git)?$/.test(path11.trim());
5999
5875
  }
6000
5876
  };
6001
5877
  }
@@ -6429,11 +6305,11 @@ function parseGrep(grep) {
6429
6305
  const paths = /* @__PURE__ */ new Set();
6430
6306
  const results = {};
6431
6307
  forEachLineWithContent(grep, (input) => {
6432
- const [path10, line, preview] = input.split(NULL);
6433
- paths.add(path10);
6434
- (results[path10] = results[path10] || []).push({
6308
+ const [path11, line, preview] = input.split(NULL);
6309
+ paths.add(path11);
6310
+ (results[path11] = results[path11] || []).push({
6435
6311
  line: asNumber(line),
6436
- path: path10,
6312
+ path: path11,
6437
6313
  preview
6438
6314
  });
6439
6315
  });
@@ -7198,14 +7074,14 @@ var init_hash_object = __esm({
7198
7074
  init_task();
7199
7075
  }
7200
7076
  });
7201
- function parseInit(bare, path10, text2) {
7077
+ function parseInit(bare, path11, text2) {
7202
7078
  const response = String(text2).trim();
7203
7079
  let result;
7204
7080
  if (result = initResponseRegex.exec(response)) {
7205
- return new InitSummary(bare, path10, false, result[1]);
7081
+ return new InitSummary(bare, path11, false, result[1]);
7206
7082
  }
7207
7083
  if (result = reInitResponseRegex.exec(response)) {
7208
- return new InitSummary(bare, path10, true, result[1]);
7084
+ return new InitSummary(bare, path11, true, result[1]);
7209
7085
  }
7210
7086
  let gitDir = "";
7211
7087
  const tokens = response.split(" ");
@@ -7216,7 +7092,7 @@ function parseInit(bare, path10, text2) {
7216
7092
  break;
7217
7093
  }
7218
7094
  }
7219
- return new InitSummary(bare, path10, /^re/i.test(response), gitDir);
7095
+ return new InitSummary(bare, path11, /^re/i.test(response), gitDir);
7220
7096
  }
7221
7097
  var InitSummary;
7222
7098
  var initResponseRegex;
@@ -7225,9 +7101,9 @@ var init_InitSummary = __esm({
7225
7101
  "src/lib/responses/InitSummary.ts"() {
7226
7102
  "use strict";
7227
7103
  InitSummary = class {
7228
- constructor(bare, path10, existing, gitDir) {
7104
+ constructor(bare, path11, existing, gitDir) {
7229
7105
  this.bare = bare;
7230
- this.path = path10;
7106
+ this.path = path11;
7231
7107
  this.existing = existing;
7232
7108
  this.gitDir = gitDir;
7233
7109
  }
@@ -7239,7 +7115,7 @@ var init_InitSummary = __esm({
7239
7115
  function hasBareCommand(command) {
7240
7116
  return command.includes(bareCommand);
7241
7117
  }
7242
- function initTask(bare = false, path10, customArgs) {
7118
+ function initTask(bare = false, path11, customArgs) {
7243
7119
  const commands = ["init", ...customArgs];
7244
7120
  if (bare && !hasBareCommand(commands)) {
7245
7121
  commands.splice(1, 0, bareCommand);
@@ -7248,7 +7124,7 @@ function initTask(bare = false, path10, customArgs) {
7248
7124
  commands,
7249
7125
  format: "utf-8",
7250
7126
  parser(text2) {
7251
- return parseInit(commands.includes("--bare"), path10, text2);
7127
+ return parseInit(commands.includes("--bare"), path11, text2);
7252
7128
  }
7253
7129
  };
7254
7130
  }
@@ -8064,12 +7940,12 @@ var init_FileStatusSummary = __esm({
8064
7940
  "use strict";
8065
7941
  fromPathRegex = /^(.+)\0(.+)$/;
8066
7942
  FileStatusSummary = class {
8067
- constructor(path10, index, working_dir) {
8068
- this.path = path10;
7943
+ constructor(path11, index, working_dir) {
7944
+ this.path = path11;
8069
7945
  this.index = index;
8070
7946
  this.working_dir = working_dir;
8071
7947
  if (index === "R" || working_dir === "R") {
8072
- const detail = fromPathRegex.exec(path10) || [null, path10, path10];
7948
+ const detail = fromPathRegex.exec(path11) || [null, path11, path11];
8073
7949
  this.from = detail[2] || "";
8074
7950
  this.path = detail[1] || "";
8075
7951
  }
@@ -8100,14 +7976,14 @@ function splitLine(result, lineStr) {
8100
7976
  default:
8101
7977
  return;
8102
7978
  }
8103
- function data(index, workingDir, path10) {
7979
+ function data(index, workingDir, path11) {
8104
7980
  const raw = `${index}${workingDir}`;
8105
7981
  const handler = parsers6.get(raw);
8106
7982
  if (handler) {
8107
- handler(result, path10);
7983
+ handler(result, path11);
8108
7984
  }
8109
7985
  if (raw !== "##" && raw !== "!!") {
8110
- result.files.push(new FileStatusSummary(path10, index, workingDir));
7986
+ result.files.push(new FileStatusSummary(path11, index, workingDir));
8111
7987
  }
8112
7988
  }
8113
7989
  }
@@ -8420,9 +8296,9 @@ var init_simple_git_api = __esm({
8420
8296
  next
8421
8297
  );
8422
8298
  }
8423
- hashObject(path10, write) {
8299
+ hashObject(path11, write) {
8424
8300
  return this._runTask(
8425
- hashObjectTask(path10, write === true),
8301
+ hashObjectTask(path11, write === true),
8426
8302
  trailingFunctionArgument(arguments)
8427
8303
  );
8428
8304
  }
@@ -8775,8 +8651,8 @@ var init_branch = __esm({
8775
8651
  }
8776
8652
  });
8777
8653
  function toPath(input) {
8778
- const path10 = input.trim().replace(/^["']|["']$/g, "");
8779
- return path10 && (0, import_node_path4.normalize)(path10);
8654
+ const path11 = input.trim().replace(/^["']|["']$/g, "");
8655
+ return path11 && (0, import_node_path3.normalize)(path11);
8780
8656
  }
8781
8657
  var parseCheckIgnore;
8782
8658
  var init_CheckIgnore = __esm({
@@ -9090,8 +8966,8 @@ __export(sub_module_exports, {
9090
8966
  subModuleTask: () => subModuleTask,
9091
8967
  updateSubModuleTask: () => updateSubModuleTask
9092
8968
  });
9093
- function addSubModuleTask(repo, path10) {
9094
- return subModuleTask(["add", repo, path10]);
8969
+ function addSubModuleTask(repo, path11) {
8970
+ return subModuleTask(["add", repo, path11]);
9095
8971
  }
9096
8972
  function initSubModuleTask(customArgs) {
9097
8973
  return subModuleTask(["init", ...customArgs]);
@@ -9421,8 +9297,8 @@ var require_git = __commonJS2({
9421
9297
  }
9422
9298
  return this._runTask(straightThroughStringTask2(command, this._trimmed), next);
9423
9299
  };
9424
- Git2.prototype.submoduleAdd = function(repo, path10, then) {
9425
- return this._runTask(addSubModuleTask2(repo, path10), trailingFunctionArgument2(arguments));
9300
+ Git2.prototype.submoduleAdd = function(repo, path11, then) {
9301
+ return this._runTask(addSubModuleTask2(repo, path11), trailingFunctionArgument2(arguments));
9426
9302
  };
9427
9303
  Git2.prototype.submoduleUpdate = function(args, then) {
9428
9304
  return this._runTask(
@@ -10023,22 +9899,22 @@ function createGitClient(baseDir, options) {
10023
9899
 
10024
9900
  // ../git/dist/lock-detector.js
10025
9901
  var import_node_child_process3 = require("child_process");
10026
- var import_promises2 = __toESM(require("fs/promises"), 1);
10027
- var import_node_path5 = __toESM(require("path"), 1);
9902
+ var import_promises = __toESM(require("fs/promises"), 1);
9903
+ var import_node_path4 = __toESM(require("path"), 1);
10028
9904
  var import_node_util = require("util");
10029
9905
  var execFileAsync = (0, import_node_util.promisify)(import_node_child_process3.execFile);
10030
9906
  async function getIndexLockPath(repoPath) {
10031
9907
  try {
10032
9908
  const { stdout } = await execFileAsync("git", ["rev-parse", "--git-path", "index.lock"], { cwd: repoPath });
10033
- return import_node_path5.default.resolve(repoPath, stdout.trim());
9909
+ return import_node_path4.default.resolve(repoPath, stdout.trim());
10034
9910
  } catch {
10035
- return import_node_path5.default.join(repoPath, ".git", "index.lock");
9911
+ return import_node_path4.default.join(repoPath, ".git", "index.lock");
10036
9912
  }
10037
9913
  }
10038
9914
  async function getLockInfo(repoPath) {
10039
9915
  const lockPath = await getIndexLockPath(repoPath);
10040
9916
  try {
10041
- const stat = await import_promises2.default.stat(lockPath);
9917
+ const stat = await import_promises.default.stat(lockPath);
10042
9918
  return {
10043
9919
  path: lockPath,
10044
9920
  ageMs: Date.now() - stat.mtimeMs
@@ -10049,7 +9925,7 @@ async function getLockInfo(repoPath) {
10049
9925
  }
10050
9926
  async function removeLock(repoPath) {
10051
9927
  const lockPath = await getIndexLockPath(repoPath);
10052
- await import_promises2.default.rm(lockPath, { force: true });
9928
+ await import_promises.default.rm(lockPath, { force: true });
10053
9929
  }
10054
9930
  async function isLocked(repoPath) {
10055
9931
  return await getLockInfo(repoPath) !== null;
@@ -10215,157 +10091,30 @@ async function getHeadSha(baseDir, options) {
10215
10091
  }
10216
10092
 
10217
10093
  // src/sagas/apply-snapshot-saga.ts
10218
- var import_promises3 = require("fs/promises");
10219
- var import_node_path6 = require("path");
10094
+ var import_promises2 = require("fs/promises");
10095
+ var import_node_path5 = require("path");
10220
10096
 
10221
10097
  // ../git/dist/sagas/tree.js
10222
- var import_node_fs3 = require("fs");
10098
+ var import_node_fs2 = require("fs");
10223
10099
  var fs7 = __toESM(require("fs/promises"), 1);
10224
10100
  var path9 = __toESM(require("path"), 1);
10225
10101
  var tar = __toESM(require("tar"), 1);
10226
10102
 
10227
- // ../shared/dist/index.js
10228
- var consoleLogger = {
10229
- info: (_message, _data) => {
10230
- },
10231
- debug: (_message, _data) => {
10232
- },
10233
- error: (_message, _data) => {
10234
- },
10235
- warn: (_message, _data) => {
10103
+ // ../git/dist/git-saga.js
10104
+ var GitSaga = class extends Saga {
10105
+ _git = null;
10106
+ get git() {
10107
+ if (!this._git) {
10108
+ throw new Error("git client accessed before execute() was called");
10109
+ }
10110
+ return this._git;
10236
10111
  }
10237
- };
10238
- var Saga = class {
10239
- completedSteps = [];
10240
- currentStepName = "unknown";
10241
- stepTimings = [];
10242
- log;
10243
- constructor(logger) {
10244
- this.log = logger ?? consoleLogger;
10245
- }
10246
- /**
10247
- * Run the saga with the given input.
10248
- * Returns a discriminated union result - either success with data or failure with error details.
10249
- */
10250
- async run(input) {
10251
- this.completedSteps = [];
10252
- this.currentStepName = "unknown";
10253
- this.stepTimings = [];
10254
- const sagaStart = performance.now();
10255
- this.log.info("Starting saga", { sagaName: this.sagaName });
10256
- try {
10257
- const result = await this.execute(input);
10258
- const totalDuration = performance.now() - sagaStart;
10259
- this.log.debug("Saga completed successfully", {
10260
- sagaName: this.sagaName,
10261
- stepsCompleted: this.completedSteps.length,
10262
- totalDurationMs: Math.round(totalDuration),
10263
- stepTimings: this.stepTimings
10264
- });
10265
- return { success: true, data: result };
10266
- } catch (error) {
10267
- this.log.error("Saga failed, initiating rollback", {
10268
- sagaName: this.sagaName,
10269
- failedStep: this.currentStepName,
10270
- error: error instanceof Error ? error.message : String(error)
10271
- });
10272
- await this.rollback();
10273
- return {
10274
- success: false,
10275
- error: error instanceof Error ? error.message : String(error),
10276
- failedStep: this.currentStepName
10277
- };
10278
- }
10279
- }
10280
- /**
10281
- * Execute a step with its rollback action.
10282
- * If the step succeeds, its rollback action is stored for potential rollback.
10283
- * The step name is automatically tracked for error reporting.
10284
- *
10285
- * @param config - Step configuration with name, execute, and rollback functions
10286
- * @returns The result of the execute function
10287
- * @throws Re-throws any error from the execute function (triggers rollback)
10288
- */
10289
- async step(config) {
10290
- this.currentStepName = config.name;
10291
- this.log.debug(`Executing step: ${config.name}`);
10292
- const stepStart = performance.now();
10293
- const result = await config.execute();
10294
- const durationMs = Math.round(performance.now() - stepStart);
10295
- this.stepTimings.push({ name: config.name, durationMs });
10296
- this.log.debug(`Step completed: ${config.name}`, { durationMs });
10297
- this.completedSteps.push({
10298
- name: config.name,
10299
- rollback: () => config.rollback(result)
10300
- });
10301
- return result;
10302
- }
10303
- /**
10304
- * Execute a step that doesn't need rollback.
10305
- * Useful for read-only operations or operations that are idempotent.
10306
- * The step name is automatically tracked for error reporting.
10307
- *
10308
- * @param name - Step name for logging and error tracking
10309
- * @param execute - The action to execute
10310
- * @returns The result of the execute function
10311
- */
10312
- async readOnlyStep(name, execute) {
10313
- this.currentStepName = name;
10314
- this.log.debug(`Executing read-only step: ${name}`);
10315
- const stepStart = performance.now();
10316
- const result = await execute();
10317
- const durationMs = Math.round(performance.now() - stepStart);
10318
- this.stepTimings.push({ name, durationMs });
10319
- this.log.debug(`Read-only step completed: ${name}`, { durationMs });
10320
- return result;
10321
- }
10322
- /**
10323
- * Roll back all completed steps in reverse order.
10324
- * Rollback errors are logged but don't stop the rollback of other steps.
10325
- */
10326
- async rollback() {
10327
- this.log.info("Rolling back saga", {
10328
- stepsToRollback: this.completedSteps.length
10329
- });
10330
- const stepsReversed = [...this.completedSteps].reverse();
10331
- for (const step of stepsReversed) {
10332
- try {
10333
- this.log.debug(`Rolling back step: ${step.name}`);
10334
- await step.rollback();
10335
- this.log.debug(`Step rolled back: ${step.name}`);
10336
- } catch (error) {
10337
- this.log.error(`Failed to rollback step: ${step.name}`, {
10338
- error: error instanceof Error ? error.message : String(error)
10339
- });
10340
- }
10341
- }
10342
- this.log.info("Rollback completed", {
10343
- stepsAttempted: this.completedSteps.length
10344
- });
10345
- }
10346
- /**
10347
- * Get the number of completed steps (useful for testing)
10348
- */
10349
- getCompletedStepCount() {
10350
- return this.completedSteps.length;
10351
- }
10352
- };
10353
-
10354
- // ../git/dist/git-saga.js
10355
- var GitSaga = class extends Saga {
10356
- _git = null;
10357
- get git() {
10358
- if (!this._git) {
10359
- throw new Error("git client accessed before execute() was called");
10360
- }
10361
- return this._git;
10362
- }
10363
- async execute(input) {
10364
- const manager = getGitOperationManager();
10365
- return manager.executeWrite(input.baseDir, async (git) => {
10366
- this._git = git;
10367
- return this.executeGitOperations(input);
10368
- }, { signal: input.signal });
10112
+ async execute(input) {
10113
+ const manager = getGitOperationManager();
10114
+ return manager.executeWrite(input.baseDir, async (git) => {
10115
+ this._git = git;
10116
+ return this.executeGitOperations(input);
10117
+ }, { signal: input.signal });
10369
10118
  }
10370
10119
  };
10371
10120
 
@@ -10437,7 +10186,7 @@ var CaptureTreeSaga = class extends GitSaga {
10437
10186
  if (filesToArchive.length === 0) {
10438
10187
  return void 0;
10439
10188
  }
10440
- const existingFiles = filesToArchive.filter((f) => (0, import_node_fs3.existsSync)(path9.join(baseDir, f)));
10189
+ const existingFiles = filesToArchive.filter((f) => (0, import_node_fs2.existsSync)(path9.join(baseDir, f)));
10441
10190
  if (existingFiles.length === 0) {
10442
10191
  return void 0;
10443
10192
  }
@@ -10631,18 +10380,18 @@ var ApplySnapshotSaga = class extends Saga {
10631
10380
  archivePath = null;
10632
10381
  async execute(input) {
10633
10382
  const { snapshot, repositoryPath, apiClient, taskId, runId } = input;
10634
- const tmpDir = (0, import_node_path6.join)(repositoryPath, ".posthog", "tmp");
10383
+ const tmpDir = (0, import_node_path5.join)(repositoryPath, ".posthog", "tmp");
10635
10384
  if (!snapshot.archiveUrl) {
10636
10385
  throw new Error("Cannot apply snapshot: no archive URL");
10637
10386
  }
10638
10387
  const archiveUrl = snapshot.archiveUrl;
10639
10388
  await this.step({
10640
10389
  name: "create_tmp_dir",
10641
- execute: () => (0, import_promises3.mkdir)(tmpDir, { recursive: true }),
10390
+ execute: () => (0, import_promises2.mkdir)(tmpDir, { recursive: true }),
10642
10391
  rollback: async () => {
10643
10392
  }
10644
10393
  });
10645
- const archivePath = (0, import_node_path6.join)(tmpDir, `${snapshot.treeHash}.tar.gz`);
10394
+ const archivePath = (0, import_node_path5.join)(tmpDir, `${snapshot.treeHash}.tar.gz`);
10646
10395
  this.archivePath = archivePath;
10647
10396
  await this.step({
10648
10397
  name: "download_archive",
@@ -10657,11 +10406,11 @@ var ApplySnapshotSaga = class extends Saga {
10657
10406
  }
10658
10407
  const base64Content = Buffer.from(arrayBuffer).toString("utf-8");
10659
10408
  const binaryContent = Buffer.from(base64Content, "base64");
10660
- await (0, import_promises3.writeFile)(archivePath, binaryContent);
10409
+ await (0, import_promises2.writeFile)(archivePath, binaryContent);
10661
10410
  },
10662
10411
  rollback: async () => {
10663
10412
  if (this.archivePath) {
10664
- await (0, import_promises3.rm)(this.archivePath, { force: true }).catch(() => {
10413
+ await (0, import_promises2.rm)(this.archivePath, { force: true }).catch(() => {
10665
10414
  });
10666
10415
  }
10667
10416
  }
@@ -10677,7 +10426,7 @@ var ApplySnapshotSaga = class extends Saga {
10677
10426
  if (!applyResult.success) {
10678
10427
  throw new Error(`Failed to apply tree: ${applyResult.error}`);
10679
10428
  }
10680
- await (0, import_promises3.rm)(this.archivePath, { force: true }).catch(() => {
10429
+ await (0, import_promises2.rm)(this.archivePath, { force: true }).catch(() => {
10681
10430
  });
10682
10431
  this.log.info("Tree snapshot applied", {
10683
10432
  treeHash: snapshot.treeHash,
@@ -10689,9 +10438,9 @@ var ApplySnapshotSaga = class extends Saga {
10689
10438
  };
10690
10439
 
10691
10440
  // src/sagas/capture-tree-saga.ts
10692
- var import_node_fs4 = require("fs");
10693
- var import_promises4 = require("fs/promises");
10694
- var import_node_path7 = require("path");
10441
+ var import_node_fs3 = require("fs");
10442
+ var import_promises3 = require("fs/promises");
10443
+ var import_node_path6 = require("path");
10695
10444
  var CaptureTreeSaga2 = class extends Saga {
10696
10445
  sagaName = "CaptureTreeSaga";
10697
10446
  async execute(input) {
@@ -10703,14 +10452,14 @@ var CaptureTreeSaga2 = class extends Saga {
10703
10452
  taskId,
10704
10453
  runId
10705
10454
  } = input;
10706
- const tmpDir = (0, import_node_path7.join)(repositoryPath, ".posthog", "tmp");
10707
- if ((0, import_node_fs4.existsSync)((0, import_node_path7.join)(repositoryPath, ".gitmodules"))) {
10455
+ const tmpDir = (0, import_node_path6.join)(repositoryPath, ".posthog", "tmp");
10456
+ if ((0, import_node_fs3.existsSync)((0, import_node_path6.join)(repositoryPath, ".gitmodules"))) {
10708
10457
  this.log.warn(
10709
10458
  "Repository has submodules - snapshot may not capture submodule state"
10710
10459
  );
10711
10460
  }
10712
10461
  const shouldArchive = !!apiClient;
10713
- const archivePath = shouldArchive ? (0, import_node_path7.join)(tmpDir, `tree-${Date.now()}.tar.gz`) : void 0;
10462
+ const archivePath = shouldArchive ? (0, import_node_path6.join)(tmpDir, `tree-${Date.now()}.tar.gz`) : void 0;
10714
10463
  const gitCaptureSaga = new CaptureTreeSaga(this.log);
10715
10464
  const captureResult = await gitCaptureSaga.run({
10716
10465
  baseDir: repositoryPath,
@@ -10740,7 +10489,7 @@ var CaptureTreeSaga2 = class extends Saga {
10740
10489
  runId
10741
10490
  );
10742
10491
  } finally {
10743
- await (0, import_promises4.rm)(createdArchivePath, { force: true }).catch(() => {
10492
+ await (0, import_promises3.rm)(createdArchivePath, { force: true }).catch(() => {
10744
10493
  });
10745
10494
  }
10746
10495
  }
@@ -10764,7 +10513,7 @@ var CaptureTreeSaga2 = class extends Saga {
10764
10513
  const archiveUrl = await this.step({
10765
10514
  name: "upload_archive",
10766
10515
  execute: async () => {
10767
- const archiveContent = await (0, import_promises4.readFile)(archivePath);
10516
+ const archiveContent = await (0, import_promises3.readFile)(archivePath);
10768
10517
  const base64Content = archiveContent.toString("base64");
10769
10518
  const artifacts = await apiClient.uploadTaskArtifacts(taskId, runId, [
10770
10519
  {
@@ -10784,104 +10533,679 @@ var CaptureTreeSaga2 = class extends Saga {
10784
10533
  return void 0;
10785
10534
  },
10786
10535
  rollback: async () => {
10787
- await (0, import_promises4.rm)(archivePath, { force: true }).catch(() => {
10536
+ await (0, import_promises3.rm)(archivePath, { force: true }).catch(() => {
10788
10537
  });
10789
10538
  }
10790
10539
  });
10791
10540
  return archiveUrl;
10792
10541
  }
10793
- };
10794
-
10795
- // src/tree-tracker.ts
10796
- var TreeTracker = class {
10797
- repositoryPath;
10798
- taskId;
10799
- runId;
10800
- apiClient;
10801
- logger;
10802
- lastTreeHash = null;
10803
- constructor(config) {
10804
- this.repositoryPath = config.repositoryPath;
10805
- this.taskId = config.taskId;
10806
- this.runId = config.runId;
10807
- this.apiClient = config.apiClient;
10808
- this.logger = config.logger || new Logger({ debug: false, prefix: "[TreeTracker]" });
10542
+ };
10543
+
10544
+ // src/tree-tracker.ts
10545
+ var TreeTracker = class {
10546
+ repositoryPath;
10547
+ taskId;
10548
+ runId;
10549
+ apiClient;
10550
+ logger;
10551
+ lastTreeHash = null;
10552
+ constructor(config) {
10553
+ this.repositoryPath = config.repositoryPath;
10554
+ this.taskId = config.taskId;
10555
+ this.runId = config.runId;
10556
+ this.apiClient = config.apiClient;
10557
+ this.logger = config.logger || new Logger({ debug: false, prefix: "[TreeTracker]" });
10558
+ }
10559
+ /**
10560
+ * Capture current working tree state as a snapshot.
10561
+ * Uses a temporary index to avoid modifying user's staging area.
10562
+ * Uses Saga pattern for atomic operation with automatic cleanup on failure.
10563
+ */
10564
+ async captureTree(options) {
10565
+ const saga = new CaptureTreeSaga2(this.logger);
10566
+ const result = await saga.run({
10567
+ repositoryPath: this.repositoryPath,
10568
+ taskId: this.taskId,
10569
+ runId: this.runId,
10570
+ apiClient: this.apiClient,
10571
+ lastTreeHash: this.lastTreeHash,
10572
+ interrupted: options?.interrupted
10573
+ });
10574
+ if (!result.success) {
10575
+ this.logger.error("Failed to capture tree", {
10576
+ error: result.error,
10577
+ failedStep: result.failedStep
10578
+ });
10579
+ throw new Error(
10580
+ `Failed to capture tree at step '${result.failedStep}': ${result.error}`
10581
+ );
10582
+ }
10583
+ if (result.data.newTreeHash !== null) {
10584
+ this.lastTreeHash = result.data.newTreeHash;
10585
+ }
10586
+ return result.data.snapshot;
10587
+ }
10588
+ /**
10589
+ * Download and apply a tree snapshot.
10590
+ * Uses Saga pattern for atomic operation with rollback on failure.
10591
+ */
10592
+ async applyTreeSnapshot(snapshot) {
10593
+ if (!this.apiClient) {
10594
+ throw new Error("Cannot apply snapshot: API client not configured");
10595
+ }
10596
+ if (!snapshot.archiveUrl) {
10597
+ this.logger.warn("Cannot apply snapshot: no archive URL", {
10598
+ treeHash: snapshot.treeHash,
10599
+ changes: snapshot.changes.length
10600
+ });
10601
+ throw new Error("Cannot apply snapshot: no archive URL");
10602
+ }
10603
+ const saga = new ApplySnapshotSaga(this.logger);
10604
+ const result = await saga.run({
10605
+ snapshot,
10606
+ repositoryPath: this.repositoryPath,
10607
+ apiClient: this.apiClient,
10608
+ taskId: this.taskId,
10609
+ runId: this.runId
10610
+ });
10611
+ if (!result.success) {
10612
+ this.logger.error("Failed to apply tree snapshot", {
10613
+ error: result.error,
10614
+ failedStep: result.failedStep,
10615
+ treeHash: snapshot.treeHash
10616
+ });
10617
+ throw new Error(
10618
+ `Failed to apply snapshot at step '${result.failedStep}': ${result.error}`
10619
+ );
10620
+ }
10621
+ this.lastTreeHash = result.data.treeHash;
10622
+ }
10623
+ /**
10624
+ * Get the last captured tree hash.
10625
+ */
10626
+ getLastTreeHash() {
10627
+ return this.lastTreeHash;
10628
+ }
10629
+ /**
10630
+ * Set the last tree hash (used when resuming).
10631
+ */
10632
+ setLastTreeHash(hash) {
10633
+ this.lastTreeHash = hash;
10634
+ }
10635
+ };
10636
+
10637
+ // src/sagas/resume-saga.ts
10638
+ var ResumeSaga = class extends Saga {
10639
+ sagaName = "ResumeSaga";
10640
+ async execute(input) {
10641
+ const { taskId, runId, repositoryPath, apiClient } = input;
10642
+ const logger = input.logger || new Logger({ debug: false, prefix: "[Resume]" });
10643
+ const taskRun = await this.readOnlyStep(
10644
+ "fetch_task_run",
10645
+ () => apiClient.getTaskRun(taskId, runId)
10646
+ );
10647
+ if (!taskRun.log_url) {
10648
+ this.log.info("No log URL found, starting fresh");
10649
+ return this.emptyResult();
10650
+ }
10651
+ const entries = await this.readOnlyStep(
10652
+ "fetch_logs",
10653
+ () => apiClient.fetchTaskRunLogs(taskRun)
10654
+ );
10655
+ if (entries.length === 0) {
10656
+ this.log.info("No log entries found, starting fresh");
10657
+ return this.emptyResult();
10658
+ }
10659
+ this.log.info("Fetched log entries", { count: entries.length });
10660
+ const latestSnapshot = await this.readOnlyStep(
10661
+ "find_snapshot",
10662
+ () => Promise.resolve(this.findLatestTreeSnapshot(entries))
10663
+ );
10664
+ let snapshotApplied = false;
10665
+ if (latestSnapshot?.archiveUrl && repositoryPath) {
10666
+ this.log.info("Found tree snapshot", {
10667
+ treeHash: latestSnapshot.treeHash,
10668
+ hasArchiveUrl: true,
10669
+ changes: latestSnapshot.changes?.length ?? 0,
10670
+ interrupted: latestSnapshot.interrupted
10671
+ });
10672
+ await this.step({
10673
+ name: "apply_snapshot",
10674
+ execute: async () => {
10675
+ const treeTracker = new TreeTracker({
10676
+ repositoryPath,
10677
+ taskId,
10678
+ runId,
10679
+ apiClient,
10680
+ logger: logger.child("TreeTracker")
10681
+ });
10682
+ try {
10683
+ await treeTracker.applyTreeSnapshot(latestSnapshot);
10684
+ treeTracker.setLastTreeHash(latestSnapshot.treeHash);
10685
+ snapshotApplied = true;
10686
+ this.log.info("Tree snapshot applied successfully", {
10687
+ treeHash: latestSnapshot.treeHash
10688
+ });
10689
+ } catch (error) {
10690
+ this.log.warn(
10691
+ "Failed to apply tree snapshot, continuing without it",
10692
+ {
10693
+ error: error instanceof Error ? error.message : String(error),
10694
+ treeHash: latestSnapshot.treeHash
10695
+ }
10696
+ );
10697
+ }
10698
+ },
10699
+ rollback: async () => {
10700
+ }
10701
+ });
10702
+ } else if (latestSnapshot?.archiveUrl && !repositoryPath) {
10703
+ this.log.warn(
10704
+ "Snapshot found but no repositoryPath configured - files cannot be restored",
10705
+ {
10706
+ treeHash: latestSnapshot.treeHash,
10707
+ changes: latestSnapshot.changes?.length ?? 0
10708
+ }
10709
+ );
10710
+ } else if (latestSnapshot) {
10711
+ this.log.warn(
10712
+ "Snapshot found but has no archive URL - files cannot be restored",
10713
+ {
10714
+ treeHash: latestSnapshot.treeHash,
10715
+ changes: latestSnapshot.changes?.length ?? 0
10716
+ }
10717
+ );
10718
+ }
10719
+ const conversation = await this.readOnlyStep(
10720
+ "rebuild_conversation",
10721
+ () => Promise.resolve(this.rebuildConversation(entries))
10722
+ );
10723
+ const lastDevice = await this.readOnlyStep(
10724
+ "find_device",
10725
+ () => Promise.resolve(this.findLastDeviceInfo(entries))
10726
+ );
10727
+ this.log.info("Resume state rebuilt", {
10728
+ turns: conversation.length,
10729
+ hasSnapshot: !!latestSnapshot,
10730
+ snapshotApplied,
10731
+ interrupted: latestSnapshot?.interrupted ?? false
10732
+ });
10733
+ return {
10734
+ conversation,
10735
+ latestSnapshot,
10736
+ snapshotApplied,
10737
+ interrupted: latestSnapshot?.interrupted ?? false,
10738
+ lastDevice,
10739
+ logEntryCount: entries.length
10740
+ };
10741
+ }
10742
+ emptyResult() {
10743
+ return {
10744
+ conversation: [],
10745
+ latestSnapshot: null,
10746
+ snapshotApplied: false,
10747
+ interrupted: false,
10748
+ logEntryCount: 0
10749
+ };
10750
+ }
10751
+ findLatestTreeSnapshot(entries) {
10752
+ const sdkPrefixedMethod = `_${POSTHOG_NOTIFICATIONS.TREE_SNAPSHOT}`;
10753
+ for (let i = entries.length - 1; i >= 0; i--) {
10754
+ const entry = entries[i];
10755
+ const method = entry.notification?.method;
10756
+ if (method === sdkPrefixedMethod || method === POSTHOG_NOTIFICATIONS.TREE_SNAPSHOT) {
10757
+ const params = entry.notification.params;
10758
+ if (params?.treeHash) {
10759
+ return params;
10760
+ }
10761
+ }
10762
+ }
10763
+ return null;
10764
+ }
10765
+ findLastDeviceInfo(entries) {
10766
+ for (let i = entries.length - 1; i >= 0; i--) {
10767
+ const entry = entries[i];
10768
+ const params = entry.notification?.params;
10769
+ if (params?.device) {
10770
+ return params.device;
10771
+ }
10772
+ }
10773
+ return void 0;
10774
+ }
10775
+ rebuildConversation(entries) {
10776
+ const turns = [];
10777
+ let currentAssistantContent = [];
10778
+ let currentToolCalls = [];
10779
+ for (const entry of entries) {
10780
+ const method = entry.notification?.method;
10781
+ const params = entry.notification?.params;
10782
+ if (method === "session/update" && params?.update) {
10783
+ const update = params.update;
10784
+ const sessionUpdate = update.sessionUpdate;
10785
+ switch (sessionUpdate) {
10786
+ case "user_message":
10787
+ case "user_message_chunk": {
10788
+ if (currentAssistantContent.length > 0 || currentToolCalls.length > 0) {
10789
+ turns.push({
10790
+ role: "assistant",
10791
+ content: currentAssistantContent,
10792
+ toolCalls: currentToolCalls.length > 0 ? currentToolCalls : void 0
10793
+ });
10794
+ currentAssistantContent = [];
10795
+ currentToolCalls = [];
10796
+ }
10797
+ const content = update.content;
10798
+ const contentArray = Array.isArray(content) ? content : [content];
10799
+ turns.push({
10800
+ role: "user",
10801
+ content: contentArray
10802
+ });
10803
+ break;
10804
+ }
10805
+ case "agent_message": {
10806
+ const content = update.content;
10807
+ if (content) {
10808
+ if (content.type === "text" && currentAssistantContent.length > 0 && currentAssistantContent[currentAssistantContent.length - 1].type === "text") {
10809
+ const lastBlock = currentAssistantContent[currentAssistantContent.length - 1];
10810
+ lastBlock.text += content.text;
10811
+ } else {
10812
+ currentAssistantContent.push(content);
10813
+ }
10814
+ }
10815
+ break;
10816
+ }
10817
+ case "agent_message_chunk": {
10818
+ const content = update.content;
10819
+ if (content) {
10820
+ if (content.type === "text" && currentAssistantContent.length > 0 && currentAssistantContent[currentAssistantContent.length - 1].type === "text") {
10821
+ const lastBlock = currentAssistantContent[currentAssistantContent.length - 1];
10822
+ lastBlock.text += content.text;
10823
+ } else {
10824
+ currentAssistantContent.push(content);
10825
+ }
10826
+ }
10827
+ break;
10828
+ }
10829
+ case "tool_call":
10830
+ case "tool_call_update": {
10831
+ const meta = update._meta?.claudeCode;
10832
+ if (meta) {
10833
+ const toolCallId = meta.toolCallId;
10834
+ const toolName = meta.toolName;
10835
+ const toolInput = meta.toolInput;
10836
+ const toolResponse = meta.toolResponse;
10837
+ if (toolCallId && toolName) {
10838
+ let toolCall = currentToolCalls.find(
10839
+ (tc) => tc.toolCallId === toolCallId
10840
+ );
10841
+ if (!toolCall) {
10842
+ toolCall = {
10843
+ toolCallId,
10844
+ toolName,
10845
+ input: toolInput
10846
+ };
10847
+ currentToolCalls.push(toolCall);
10848
+ }
10849
+ if (toolResponse !== void 0) {
10850
+ toolCall.result = toolResponse;
10851
+ }
10852
+ }
10853
+ }
10854
+ break;
10855
+ }
10856
+ case "tool_result": {
10857
+ const meta = update._meta?.claudeCode;
10858
+ if (meta) {
10859
+ const toolCallId = meta.toolCallId;
10860
+ const toolResponse = meta.toolResponse;
10861
+ if (toolCallId) {
10862
+ const toolCall = currentToolCalls.find(
10863
+ (tc) => tc.toolCallId === toolCallId
10864
+ );
10865
+ if (toolCall && toolResponse !== void 0) {
10866
+ toolCall.result = toolResponse;
10867
+ }
10868
+ }
10869
+ }
10870
+ break;
10871
+ }
10872
+ }
10873
+ }
10874
+ }
10875
+ if (currentAssistantContent.length > 0 || currentToolCalls.length > 0) {
10876
+ turns.push({
10877
+ role: "assistant",
10878
+ content: currentAssistantContent,
10879
+ toolCalls: currentToolCalls.length > 0 ? currentToolCalls : void 0
10880
+ });
10881
+ }
10882
+ return turns;
10883
+ }
10884
+ };
10885
+
10886
+ // src/resume.ts
10887
+ async function resumeFromLog(config) {
10888
+ const logger = config.logger || new Logger({ debug: false, prefix: "[Resume]" });
10889
+ logger.info("Resuming from log", {
10890
+ taskId: config.taskId,
10891
+ runId: config.runId
10892
+ });
10893
+ const saga = new ResumeSaga(logger);
10894
+ const result = await saga.run({
10895
+ taskId: config.taskId,
10896
+ runId: config.runId,
10897
+ repositoryPath: config.repositoryPath,
10898
+ apiClient: config.apiClient,
10899
+ logger
10900
+ });
10901
+ if (!result.success) {
10902
+ logger.error("Failed to resume from log", {
10903
+ error: result.error,
10904
+ failedStep: result.failedStep
10905
+ });
10906
+ throw new Error(
10907
+ `Failed to resume at step '${result.failedStep}': ${result.error}`
10908
+ );
10909
+ }
10910
+ return {
10911
+ conversation: result.data.conversation,
10912
+ latestSnapshot: result.data.latestSnapshot,
10913
+ snapshotApplied: result.data.snapshotApplied,
10914
+ interrupted: result.data.interrupted,
10915
+ lastDevice: result.data.lastDevice,
10916
+ logEntryCount: result.data.logEntryCount
10917
+ };
10918
+ }
10919
+
10920
+ // src/session-log-writer.ts
10921
+ var import_node_fs4 = __toESM(require("fs"), 1);
10922
+ var import_promises4 = __toESM(require("fs/promises"), 1);
10923
+ var import_node_path7 = __toESM(require("path"), 1);
10924
+ var SessionLogWriter = class _SessionLogWriter {
10925
+ static FLUSH_DEBOUNCE_MS = 500;
10926
+ static FLUSH_MAX_INTERVAL_MS = 5e3;
10927
+ static MAX_FLUSH_RETRIES = 10;
10928
+ static MAX_RETRY_DELAY_MS = 3e4;
10929
+ static SESSIONS_MAX_AGE_MS = 30 * 24 * 60 * 60 * 1e3;
10930
+ posthogAPI;
10931
+ pendingEntries = /* @__PURE__ */ new Map();
10932
+ flushTimeouts = /* @__PURE__ */ new Map();
10933
+ lastFlushAttemptTime = /* @__PURE__ */ new Map();
10934
+ retryCounts = /* @__PURE__ */ new Map();
10935
+ sessions = /* @__PURE__ */ new Map();
10936
+ logger;
10937
+ localCachePath;
10938
+ constructor(options = {}) {
10939
+ this.posthogAPI = options.posthogAPI;
10940
+ this.localCachePath = options.localCachePath;
10941
+ this.logger = options.logger ?? new Logger({ debug: false, prefix: "[SessionLogWriter]" });
10942
+ }
10943
+ async flushAll() {
10944
+ const sessionIds = [...this.sessions.keys()];
10945
+ const flushPromises = [];
10946
+ for (const sessionId of sessionIds) {
10947
+ flushPromises.push(this.flush(sessionId));
10948
+ }
10949
+ await Promise.all(flushPromises);
10950
+ }
10951
+ register(sessionId, context) {
10952
+ if (this.sessions.has(sessionId)) {
10953
+ return;
10954
+ }
10955
+ this.logger.info("Session registered", {
10956
+ taskId: context.taskId,
10957
+ runId: context.runId
10958
+ });
10959
+ this.sessions.set(sessionId, { context });
10960
+ this.lastFlushAttemptTime.set(sessionId, Date.now());
10961
+ if (this.localCachePath) {
10962
+ const sessionDir = import_node_path7.default.join(
10963
+ this.localCachePath,
10964
+ "sessions",
10965
+ context.runId
10966
+ );
10967
+ try {
10968
+ import_node_fs4.default.mkdirSync(sessionDir, { recursive: true });
10969
+ } catch (error) {
10970
+ this.logger.warn("Failed to create local cache directory", {
10971
+ sessionDir,
10972
+ error
10973
+ });
10974
+ }
10975
+ }
10976
+ }
10977
+ isRegistered(sessionId) {
10978
+ return this.sessions.has(sessionId);
10979
+ }
10980
+ appendRawLine(sessionId, line) {
10981
+ const session = this.sessions.get(sessionId);
10982
+ if (!session) {
10983
+ this.logger.warn("appendRawLine called for unregistered session", {
10984
+ sessionId
10985
+ });
10986
+ return;
10987
+ }
10988
+ try {
10989
+ const message = JSON.parse(line);
10990
+ const timestamp = (/* @__PURE__ */ new Date()).toISOString();
10991
+ if (this.isAgentMessageChunk(message)) {
10992
+ const text2 = this.extractChunkText(message);
10993
+ if (text2) {
10994
+ if (!session.chunkBuffer) {
10995
+ session.chunkBuffer = { text: text2, firstTimestamp: timestamp };
10996
+ } else {
10997
+ session.chunkBuffer.text += text2;
10998
+ }
10999
+ }
11000
+ return;
11001
+ }
11002
+ this.emitCoalescedMessage(sessionId, session);
11003
+ const nonChunkAgentText = this.extractAgentMessageText(message);
11004
+ if (nonChunkAgentText) {
11005
+ session.lastAgentMessage = nonChunkAgentText;
11006
+ }
11007
+ const entry = {
11008
+ type: "notification",
11009
+ timestamp,
11010
+ notification: message
11011
+ };
11012
+ this.writeToLocalCache(sessionId, entry);
11013
+ if (this.posthogAPI) {
11014
+ const pending = this.pendingEntries.get(sessionId) ?? [];
11015
+ pending.push(entry);
11016
+ this.pendingEntries.set(sessionId, pending);
11017
+ this.scheduleFlush(sessionId);
11018
+ }
11019
+ } catch {
11020
+ this.logger.warn("Failed to parse raw line for persistence", {
11021
+ taskId: session.context.taskId,
11022
+ runId: session.context.runId,
11023
+ lineLength: line.length
11024
+ });
11025
+ }
11026
+ }
11027
+ async flush(sessionId) {
11028
+ const session = this.sessions.get(sessionId);
11029
+ if (!session) {
11030
+ this.logger.warn("flush: no session found", { sessionId });
11031
+ return;
11032
+ }
11033
+ this.emitCoalescedMessage(sessionId, session);
11034
+ const pending = this.pendingEntries.get(sessionId);
11035
+ if (!this.posthogAPI || !pending?.length) {
11036
+ return;
11037
+ }
11038
+ this.pendingEntries.delete(sessionId);
11039
+ const timeout = this.flushTimeouts.get(sessionId);
11040
+ if (timeout) {
11041
+ clearTimeout(timeout);
11042
+ this.flushTimeouts.delete(sessionId);
11043
+ }
11044
+ this.lastFlushAttemptTime.set(sessionId, Date.now());
11045
+ try {
11046
+ await this.posthogAPI.appendTaskRunLog(
11047
+ session.context.taskId,
11048
+ session.context.runId,
11049
+ pending
11050
+ );
11051
+ this.retryCounts.set(sessionId, 0);
11052
+ } catch (error) {
11053
+ const retryCount = (this.retryCounts.get(sessionId) ?? 0) + 1;
11054
+ this.retryCounts.set(sessionId, retryCount);
11055
+ if (retryCount >= _SessionLogWriter.MAX_FLUSH_RETRIES) {
11056
+ this.logger.error(
11057
+ `Dropping ${pending.length} session log entries after ${retryCount} failed flush attempts`,
11058
+ {
11059
+ taskId: session.context.taskId,
11060
+ runId: session.context.runId,
11061
+ error
11062
+ }
11063
+ );
11064
+ this.retryCounts.set(sessionId, 0);
11065
+ } else {
11066
+ if (retryCount === 1) {
11067
+ this.logger.warn(
11068
+ `Failed to persist session logs, will retry (up to ${_SessionLogWriter.MAX_FLUSH_RETRIES} attempts)`,
11069
+ {
11070
+ taskId: session.context.taskId,
11071
+ runId: session.context.runId,
11072
+ error: error instanceof Error ? error.message : String(error)
11073
+ }
11074
+ );
11075
+ }
11076
+ const currentPending = this.pendingEntries.get(sessionId) ?? [];
11077
+ this.pendingEntries.set(sessionId, [...pending, ...currentPending]);
11078
+ this.scheduleFlush(sessionId);
11079
+ }
11080
+ }
11081
+ }
11082
+ isAgentMessageChunk(message) {
11083
+ if (message.method !== "session/update") return false;
11084
+ const params = message.params;
11085
+ const update = params?.update;
11086
+ return update?.sessionUpdate === "agent_message_chunk";
11087
+ }
11088
+ extractChunkText(message) {
11089
+ const params = message.params;
11090
+ const update = params?.update;
11091
+ const content = update?.content;
11092
+ if (content?.type === "text" && content.text) {
11093
+ return content.text;
11094
+ }
11095
+ return "";
11096
+ }
11097
+ emitCoalescedMessage(sessionId, session) {
11098
+ if (!session.chunkBuffer) return;
11099
+ const { text: text2, firstTimestamp } = session.chunkBuffer;
11100
+ session.chunkBuffer = void 0;
11101
+ session.lastAgentMessage = text2;
11102
+ const entry = {
11103
+ type: "notification",
11104
+ timestamp: firstTimestamp,
11105
+ notification: {
11106
+ jsonrpc: "2.0",
11107
+ method: "session/update",
11108
+ params: {
11109
+ update: {
11110
+ sessionUpdate: "agent_message",
11111
+ content: { type: "text", text: text2 }
11112
+ }
11113
+ }
11114
+ }
11115
+ };
11116
+ this.writeToLocalCache(sessionId, entry);
11117
+ if (this.posthogAPI) {
11118
+ const pending = this.pendingEntries.get(sessionId) ?? [];
11119
+ pending.push(entry);
11120
+ this.pendingEntries.set(sessionId, pending);
11121
+ this.scheduleFlush(sessionId);
11122
+ }
11123
+ }
11124
+ getLastAgentMessage(sessionId) {
11125
+ return this.sessions.get(sessionId)?.lastAgentMessage;
10809
11126
  }
10810
- /**
10811
- * Capture current working tree state as a snapshot.
10812
- * Uses a temporary index to avoid modifying user's staging area.
10813
- * Uses Saga pattern for atomic operation with automatic cleanup on failure.
10814
- */
10815
- async captureTree(options) {
10816
- const saga = new CaptureTreeSaga2(this.logger);
10817
- const result = await saga.run({
10818
- repositoryPath: this.repositoryPath,
10819
- taskId: this.taskId,
10820
- runId: this.runId,
10821
- apiClient: this.apiClient,
10822
- lastTreeHash: this.lastTreeHash,
10823
- interrupted: options?.interrupted
10824
- });
10825
- if (!result.success) {
10826
- this.logger.error("Failed to capture tree", {
10827
- error: result.error,
10828
- failedStep: result.failedStep
10829
- });
10830
- throw new Error(
10831
- `Failed to capture tree at step '${result.failedStep}': ${result.error}`
10832
- );
11127
+ extractAgentMessageText(message) {
11128
+ if (message.method !== "session/update") {
11129
+ return null;
10833
11130
  }
10834
- if (result.data.newTreeHash !== null) {
10835
- this.lastTreeHash = result.data.newTreeHash;
11131
+ const params = message.params;
11132
+ const update = params?.update;
11133
+ if (update?.sessionUpdate !== "agent_message") {
11134
+ return null;
10836
11135
  }
10837
- return result.data.snapshot;
10838
- }
10839
- /**
10840
- * Download and apply a tree snapshot.
10841
- * Uses Saga pattern for atomic operation with rollback on failure.
10842
- */
10843
- async applyTreeSnapshot(snapshot) {
10844
- if (!this.apiClient) {
10845
- throw new Error("Cannot apply snapshot: API client not configured");
11136
+ const content = update.content;
11137
+ if (content?.type === "text" && typeof content.text === "string") {
11138
+ const trimmed2 = content.text.trim();
11139
+ return trimmed2.length > 0 ? trimmed2 : null;
10846
11140
  }
10847
- if (!snapshot.archiveUrl) {
10848
- this.logger.warn("Cannot apply snapshot: no archive URL", {
10849
- treeHash: snapshot.treeHash,
10850
- changes: snapshot.changes.length
10851
- });
10852
- throw new Error("Cannot apply snapshot: no archive URL");
11141
+ if (typeof update.message === "string") {
11142
+ const trimmed2 = update.message.trim();
11143
+ return trimmed2.length > 0 ? trimmed2 : null;
10853
11144
  }
10854
- const saga = new ApplySnapshotSaga(this.logger);
10855
- const result = await saga.run({
10856
- snapshot,
10857
- repositoryPath: this.repositoryPath,
10858
- apiClient: this.apiClient,
10859
- taskId: this.taskId,
10860
- runId: this.runId
10861
- });
10862
- if (!result.success) {
10863
- this.logger.error("Failed to apply tree snapshot", {
10864
- error: result.error,
10865
- failedStep: result.failedStep,
10866
- treeHash: snapshot.treeHash
10867
- });
10868
- throw new Error(
10869
- `Failed to apply snapshot at step '${result.failedStep}': ${result.error}`
11145
+ return null;
11146
+ }
11147
+ scheduleFlush(sessionId) {
11148
+ const existing = this.flushTimeouts.get(sessionId);
11149
+ if (existing) clearTimeout(existing);
11150
+ const retryCount = this.retryCounts.get(sessionId) ?? 0;
11151
+ const lastAttempt = this.lastFlushAttemptTime.get(sessionId) ?? 0;
11152
+ const elapsed = Date.now() - lastAttempt;
11153
+ let delay3;
11154
+ if (retryCount > 0) {
11155
+ delay3 = Math.min(
11156
+ _SessionLogWriter.FLUSH_DEBOUNCE_MS * 2 ** retryCount,
11157
+ _SessionLogWriter.MAX_RETRY_DELAY_MS
10870
11158
  );
11159
+ } else if (elapsed >= _SessionLogWriter.FLUSH_MAX_INTERVAL_MS) {
11160
+ delay3 = 0;
11161
+ } else {
11162
+ delay3 = _SessionLogWriter.FLUSH_DEBOUNCE_MS;
10871
11163
  }
10872
- this.lastTreeHash = result.data.treeHash;
11164
+ const timeout = setTimeout(() => this.flush(sessionId), delay3);
11165
+ this.flushTimeouts.set(sessionId, timeout);
10873
11166
  }
10874
- /**
10875
- * Get the last captured tree hash.
10876
- */
10877
- getLastTreeHash() {
10878
- return this.lastTreeHash;
11167
+ writeToLocalCache(sessionId, entry) {
11168
+ if (!this.localCachePath) return;
11169
+ const session = this.sessions.get(sessionId);
11170
+ if (!session) return;
11171
+ const logPath = import_node_path7.default.join(
11172
+ this.localCachePath,
11173
+ "sessions",
11174
+ session.context.runId,
11175
+ "logs.ndjson"
11176
+ );
11177
+ try {
11178
+ import_node_fs4.default.appendFileSync(logPath, `${JSON.stringify(entry)}
11179
+ `);
11180
+ } catch (error) {
11181
+ this.logger.warn("Failed to write to local cache", {
11182
+ taskId: session.context.taskId,
11183
+ runId: session.context.runId,
11184
+ logPath,
11185
+ error
11186
+ });
11187
+ }
10879
11188
  }
10880
- /**
10881
- * Set the last tree hash (used when resuming).
10882
- */
10883
- setLastTreeHash(hash) {
10884
- this.lastTreeHash = hash;
11189
+ static async cleanupOldSessions(localCachePath) {
11190
+ const sessionsDir = import_node_path7.default.join(localCachePath, "sessions");
11191
+ let deleted = 0;
11192
+ try {
11193
+ const entries = await import_promises4.default.readdir(sessionsDir);
11194
+ const now = Date.now();
11195
+ for (const entry of entries) {
11196
+ const entryPath = import_node_path7.default.join(sessionsDir, entry);
11197
+ try {
11198
+ const stats = await import_promises4.default.stat(entryPath);
11199
+ if (stats.isDirectory() && now - stats.birthtimeMs > _SessionLogWriter.SESSIONS_MAX_AGE_MS) {
11200
+ await import_promises4.default.rm(entryPath, { recursive: true, force: true });
11201
+ deleted++;
11202
+ }
11203
+ } catch {
11204
+ }
11205
+ }
11206
+ } catch {
11207
+ }
11208
+ return deleted;
10885
11209
  }
10886
11210
  };
10887
11211
 
@@ -11094,7 +11418,7 @@ function createTappedWritableStream2(underlying, onMessage, logger) {
11094
11418
  }
11095
11419
  });
11096
11420
  }
11097
- var AgentServer = class {
11421
+ var AgentServer = class _AgentServer {
11098
11422
  config;
11099
11423
  logger;
11100
11424
  server = null;
@@ -11103,6 +11427,7 @@ var AgentServer = class {
11103
11427
  posthogAPI;
11104
11428
  questionRelayedToSlack = false;
11105
11429
  detectedPrUrl = null;
11430
+ resumeState = null;
11106
11431
  emitConsoleLog = (level, _scope, message, data) => {
11107
11432
  if (!this.session) return;
11108
11433
  const formatted = data !== void 0 ? `${message} ${JSON.stringify(data)}` : message;
@@ -11285,6 +11610,32 @@ var AgentServer = class {
11285
11610
  async autoInitializeSession() {
11286
11611
  const { taskId, runId, mode, projectId } = this.config;
11287
11612
  this.logger.info("Auto-initializing session", { taskId, runId, mode });
11613
+ const resumeRunId = process.env.POSTHOG_RESUME_RUN_ID;
11614
+ if (resumeRunId) {
11615
+ this.logger.info("Resuming from previous run", {
11616
+ resumeRunId,
11617
+ currentRunId: runId
11618
+ });
11619
+ try {
11620
+ this.resumeState = await resumeFromLog({
11621
+ taskId,
11622
+ runId: resumeRunId,
11623
+ repositoryPath: this.config.repositoryPath,
11624
+ apiClient: this.posthogAPI,
11625
+ logger: new Logger({ debug: true, prefix: "[Resume]" })
11626
+ });
11627
+ this.logger.info("Resume state loaded", {
11628
+ conversationTurns: this.resumeState.conversation.length,
11629
+ snapshotApplied: this.resumeState.snapshotApplied,
11630
+ logEntries: this.resumeState.logEntryCount
11631
+ });
11632
+ } catch (error) {
11633
+ this.logger.warn("Failed to load resume state, starting fresh", {
11634
+ error
11635
+ });
11636
+ this.resumeState = null;
11637
+ }
11638
+ }
11288
11639
  const payload = {
11289
11640
  task_id: taskId,
11290
11641
  run_id: runId,
@@ -11349,6 +11700,7 @@ You MUST NOT create a new branch, close the existing PR, or create a new PR.`
11349
11700
  }
11350
11701
  }
11351
11702
  });
11703
+ this.broadcastTurnComplete(result.stopReason);
11352
11704
  return { stopReason: result.stopReason };
11353
11705
  }
11354
11706
  case POSTHOG_NOTIFICATIONS.CANCEL:
@@ -11384,18 +11736,19 @@ You MUST NOT create a new branch, close the existing PR, or create a new PR.`
11384
11736
  name: process.env.HOSTNAME || "cloud-sandbox"
11385
11737
  };
11386
11738
  this.configureEnvironment();
11387
- const treeTracker = this.config.repositoryPath ? new TreeTracker({
11388
- repositoryPath: this.config.repositoryPath,
11389
- taskId: payload.task_id,
11390
- runId: payload.run_id,
11391
- logger: new Logger({ debug: true, prefix: "[TreeTracker]" })
11392
- }) : null;
11393
11739
  const posthogAPI = new PostHogAPIClient({
11394
11740
  apiUrl: this.config.apiUrl,
11395
11741
  projectId: this.config.projectId,
11396
11742
  getApiKey: () => this.config.apiKey,
11397
11743
  userAgent: `posthog/cloud.hog.dev; version: ${this.config.version ?? package_default.version}`
11398
11744
  });
11745
+ const treeTracker = this.config.repositoryPath ? new TreeTracker({
11746
+ repositoryPath: this.config.repositoryPath,
11747
+ taskId: payload.task_id,
11748
+ runId: payload.run_id,
11749
+ apiClient: posthogAPI,
11750
+ logger: new Logger({ debug: true, prefix: "[TreeTracker]" })
11751
+ }) : null;
11399
11752
  const logWriter = new SessionLogWriter({
11400
11753
  posthogAPI,
11401
11754
  logger: new Logger({ debug: true, prefix: "[SessionLogWriter]" })
@@ -11490,26 +11843,55 @@ You MUST NOT create a new branch, close the existing PR, or create a new PR.`
11490
11843
  }
11491
11844
  async sendInitialTaskMessage(payload, prefetchedRun) {
11492
11845
  if (!this.session) return;
11493
- try {
11494
- const task = await this.posthogAPI.getTask(payload.task_id);
11495
- let taskRun = prefetchedRun ?? null;
11496
- if (!taskRun) {
11846
+ let taskRun = prefetchedRun ?? null;
11847
+ if (!taskRun) {
11848
+ try {
11849
+ taskRun = await this.posthogAPI.getTaskRun(
11850
+ payload.task_id,
11851
+ payload.run_id
11852
+ );
11853
+ } catch (error) {
11854
+ this.logger.warn("Failed to fetch task run", {
11855
+ taskId: payload.task_id,
11856
+ runId: payload.run_id,
11857
+ error
11858
+ });
11859
+ }
11860
+ }
11861
+ if (!this.resumeState) {
11862
+ const resumeRunId = this.getResumeRunId(taskRun);
11863
+ if (resumeRunId) {
11864
+ this.logger.info("Resuming from previous run (via TaskRun state)", {
11865
+ resumeRunId,
11866
+ currentRunId: payload.run_id
11867
+ });
11497
11868
  try {
11498
- taskRun = await this.posthogAPI.getTaskRun(
11499
- payload.task_id,
11500
- payload.run_id
11501
- );
11869
+ this.resumeState = await resumeFromLog({
11870
+ taskId: payload.task_id,
11871
+ runId: resumeRunId,
11872
+ repositoryPath: this.config.repositoryPath,
11873
+ apiClient: this.posthogAPI,
11874
+ logger: new Logger({ debug: true, prefix: "[Resume]" })
11875
+ });
11876
+ this.logger.info("Resume state loaded (via TaskRun state)", {
11877
+ conversationTurns: this.resumeState.conversation.length,
11878
+ snapshotApplied: this.resumeState.snapshotApplied,
11879
+ logEntries: this.resumeState.logEntryCount
11880
+ });
11502
11881
  } catch (error) {
11503
- this.logger.warn(
11504
- "Failed to fetch task run for initial prompt override",
11505
- {
11506
- taskId: payload.task_id,
11507
- runId: payload.run_id,
11508
- error
11509
- }
11510
- );
11882
+ this.logger.warn("Failed to load resume state, starting fresh", {
11883
+ error
11884
+ });
11885
+ this.resumeState = null;
11511
11886
  }
11512
11887
  }
11888
+ }
11889
+ if (this.resumeState && this.resumeState.conversation.length > 0) {
11890
+ await this.sendResumeMessage(payload, taskRun);
11891
+ return;
11892
+ }
11893
+ try {
11894
+ const task = await this.posthogAPI.getTask(payload.task_id);
11513
11895
  const initialPromptOverride = taskRun ? this.getInitialPromptOverride(taskRun) : null;
11514
11896
  const initialPrompt = initialPromptOverride ?? task.description;
11515
11897
  if (!initialPrompt) {
@@ -11528,6 +11910,7 @@ You MUST NOT create a new branch, close the existing PR, or create a new PR.`
11528
11910
  this.logger.info("Initial task message completed", {
11529
11911
  stopReason: result.stopReason
11530
11912
  });
11913
+ this.broadcastTurnComplete(result.stopReason);
11531
11914
  if (result.stopReason === "end_turn") {
11532
11915
  await this.relayAgentResponse(payload);
11533
11916
  }
@@ -11539,6 +11922,94 @@ You MUST NOT create a new branch, close the existing PR, or create a new PR.`
11539
11922
  await this.signalTaskComplete(payload, "error");
11540
11923
  }
11541
11924
  }
11925
+ async sendResumeMessage(payload, taskRun) {
11926
+ if (!this.session || !this.resumeState) return;
11927
+ try {
11928
+ const conversationSummary = this.formatConversationForResume(
11929
+ this.resumeState.conversation
11930
+ );
11931
+ const pendingUserMessage = this.getPendingUserMessage(taskRun);
11932
+ const sandboxContext = this.resumeState.snapshotApplied ? `The sandbox environment (all files, packages, and code changes) has been fully restored from a snapshot.` : `The sandbox could not be restored from a snapshot (it may have expired). You are starting with a fresh environment but have the full conversation history below.`;
11933
+ let resumePrompt;
11934
+ if (pendingUserMessage) {
11935
+ resumePrompt = `You are resuming a previous conversation. ${sandboxContext}
11936
+
11937
+ Here is the conversation history from the previous session:
11938
+
11939
+ ${conversationSummary}
11940
+
11941
+ The user has sent a new message:
11942
+
11943
+ ${pendingUserMessage}
11944
+
11945
+ Respond to the user's new message above. You have full context from the previous session.`;
11946
+ } else {
11947
+ resumePrompt = `You are resuming a previous conversation. ${sandboxContext}
11948
+
11949
+ Here is the conversation history from the previous session:
11950
+
11951
+ ${conversationSummary}
11952
+
11953
+ Continue from where you left off. The user is waiting for your response.`;
11954
+ }
11955
+ this.logger.info("Sending resume message", {
11956
+ taskId: payload.task_id,
11957
+ conversationTurns: this.resumeState.conversation.length,
11958
+ promptLength: resumePrompt.length,
11959
+ hasPendingUserMessage: !!pendingUserMessage,
11960
+ snapshotApplied: this.resumeState.snapshotApplied
11961
+ });
11962
+ this.resumeState = null;
11963
+ const result = await this.session.clientConnection.prompt({
11964
+ sessionId: this.session.acpSessionId,
11965
+ prompt: [{ type: "text", text: resumePrompt }]
11966
+ });
11967
+ this.logger.info("Resume message completed", {
11968
+ stopReason: result.stopReason
11969
+ });
11970
+ this.broadcastTurnComplete(result.stopReason);
11971
+ } catch (error) {
11972
+ this.logger.error("Failed to send resume message", error);
11973
+ if (this.session) {
11974
+ await this.session.logWriter.flushAll();
11975
+ }
11976
+ await this.signalTaskComplete(payload, "error");
11977
+ }
11978
+ }
11979
+ static RESUME_HISTORY_TOKEN_BUDGET = 5e4;
11980
+ static TOOL_RESULT_MAX_CHARS = 2e3;
11981
+ formatConversationForResume(conversation) {
11982
+ const selected = selectRecentTurns(
11983
+ conversation,
11984
+ _AgentServer.RESUME_HISTORY_TOKEN_BUDGET
11985
+ );
11986
+ const parts = [];
11987
+ if (selected.length < conversation.length) {
11988
+ parts.push(
11989
+ `*(${conversation.length - selected.length} earlier turns omitted)*`
11990
+ );
11991
+ }
11992
+ for (const turn of selected) {
11993
+ const role = turn.role === "user" ? "User" : "Assistant";
11994
+ const textParts = turn.content.filter((block) => block.type === "text").map((block) => block.text);
11995
+ if (textParts.length > 0) {
11996
+ parts.push(`**${role}**: ${textParts.join("\n")}`);
11997
+ }
11998
+ if (turn.toolCalls?.length) {
11999
+ const toolSummary = turn.toolCalls.map((tc) => {
12000
+ let resultStr = "";
12001
+ if (tc.result !== void 0) {
12002
+ const raw = typeof tc.result === "string" ? tc.result : JSON.stringify(tc.result);
12003
+ resultStr = raw.length > _AgentServer.TOOL_RESULT_MAX_CHARS ? ` \u2192 ${raw.substring(0, _AgentServer.TOOL_RESULT_MAX_CHARS)}...(truncated)` : ` \u2192 ${raw}`;
12004
+ }
12005
+ return ` - ${tc.toolName}${resultStr}`;
12006
+ }).join("\n");
12007
+ parts.push(`**${role} (tools)**:
12008
+ ${toolSummary}`);
12009
+ }
12010
+ }
12011
+ return parts.join("\n\n");
12012
+ }
11542
12013
  getInitialPromptOverride(taskRun) {
11543
12014
  const state = taskRun.state;
11544
12015
  const override = state?.initial_prompt_override;
@@ -11548,6 +12019,24 @@ You MUST NOT create a new branch, close the existing PR, or create a new PR.`
11548
12019
  const trimmed2 = override.trim();
11549
12020
  return trimmed2.length > 0 ? trimmed2 : null;
11550
12021
  }
12022
+ getPendingUserMessage(taskRun) {
12023
+ if (!taskRun) return null;
12024
+ const state = taskRun.state;
12025
+ const message = state?.pending_user_message;
12026
+ if (typeof message !== "string") {
12027
+ return null;
12028
+ }
12029
+ const trimmed2 = message.trim();
12030
+ return trimmed2.length > 0 ? trimmed2 : null;
12031
+ }
12032
+ getResumeRunId(taskRun) {
12033
+ const envRunId = process.env.POSTHOG_RESUME_RUN_ID;
12034
+ if (envRunId) return envRunId;
12035
+ if (!taskRun) return null;
12036
+ const state = taskRun.state;
12037
+ const stateRunId = state?.resume_from_run_id;
12038
+ return typeof stateRunId === "string" && stateRunId.trim().length > 0 ? stateRunId.trim() : null;
12039
+ }
11551
12040
  buildCloudSystemPrompt(prUrl) {
11552
12041
  if (prUrl) {
11553
12042
  return `
@@ -11872,20 +12361,30 @@ Important:
11872
12361
  timestamp: (/* @__PURE__ */ new Date()).toISOString(),
11873
12362
  notification
11874
12363
  });
11875
- const { archiveUrl: _, ...paramsWithoutArchive } = snapshotWithDevice;
11876
- const logNotification = {
11877
- ...notification,
11878
- params: paramsWithoutArchive
11879
- };
11880
12364
  this.session.logWriter.appendRawLine(
11881
12365
  this.session.payload.run_id,
11882
- JSON.stringify(logNotification)
12366
+ JSON.stringify(notification)
11883
12367
  );
11884
12368
  }
11885
12369
  } catch (error) {
11886
12370
  this.logger.error("Failed to capture tree state", error);
11887
12371
  }
11888
12372
  }
12373
+ broadcastTurnComplete(stopReason) {
12374
+ if (!this.session) return;
12375
+ this.broadcastEvent({
12376
+ type: "notification",
12377
+ timestamp: (/* @__PURE__ */ new Date()).toISOString(),
12378
+ notification: {
12379
+ jsonrpc: "2.0",
12380
+ method: POSTHOG_NOTIFICATIONS.TURN_COMPLETE,
12381
+ params: {
12382
+ sessionId: this.session.acpSessionId,
12383
+ stopReason
12384
+ }
12385
+ }
12386
+ });
12387
+ }
11889
12388
  broadcastEvent(event) {
11890
12389
  if (this.session?.sseController) {
11891
12390
  this.sendSseEvent(this.session.sseController, event);