@posthog/agent 2.3.5 → 2.3.11
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.
- package/dist/adapters/claude/permissions/permission-options.js +6 -1
- package/dist/adapters/claude/permissions/permission-options.js.map +1 -1
- package/dist/adapters/claude/tools.js +6 -1
- package/dist/adapters/claude/tools.js.map +1 -1
- package/dist/agent.js +9 -2
- package/dist/agent.js.map +1 -1
- package/dist/posthog-api.js +1 -1
- package/dist/posthog-api.js.map +1 -1
- package/dist/server/agent-server.d.ts +8 -0
- package/dist/server/agent-server.js +1083 -579
- package/dist/server/agent-server.js.map +1 -1
- package/dist/server/bin.cjs +1138 -634
- package/dist/server/bin.cjs.map +1 -1
- package/package.json +1 -1
- package/src/acp-extensions.ts +3 -0
- package/src/adapters/claude/tools.ts +6 -1
- package/src/resume.ts +1 -1
- package/src/sagas/resume-saga.ts +10 -2
- package/src/server/agent-server.ts +265 -30
|
@@ -513,7 +513,7 @@ var require_has_flag = __commonJS({
|
|
|
513
513
|
var require_supports_color = __commonJS({
|
|
514
514
|
"../../node_modules/supports-color/index.js"(exports, module) {
|
|
515
515
|
"use strict";
|
|
516
|
-
var
|
|
516
|
+
var os6 = __require("os");
|
|
517
517
|
var tty = __require("tty");
|
|
518
518
|
var hasFlag = require_has_flag();
|
|
519
519
|
var { env } = process;
|
|
@@ -561,7 +561,7 @@ var require_supports_color = __commonJS({
|
|
|
561
561
|
return min;
|
|
562
562
|
}
|
|
563
563
|
if (process.platform === "win32") {
|
|
564
|
-
const osRelease =
|
|
564
|
+
const osRelease = os6.release().split(".");
|
|
565
565
|
if (Number(osRelease[0]) >= 10 && Number(osRelease[2]) >= 10586) {
|
|
566
566
|
return Number(osRelease[2]) >= 14931 ? 3 : 2;
|
|
567
567
|
}
|
|
@@ -809,10 +809,10 @@ var require_src2 = __commonJS({
|
|
|
809
809
|
var fs_1 = __require("fs");
|
|
810
810
|
var debug_1 = __importDefault(require_src());
|
|
811
811
|
var log = debug_1.default("@kwsites/file-exists");
|
|
812
|
-
function check(
|
|
813
|
-
log(`checking %s`,
|
|
812
|
+
function check(path11, isFile, isDirectory) {
|
|
813
|
+
log(`checking %s`, path11);
|
|
814
814
|
try {
|
|
815
|
-
const stat = fs_1.statSync(
|
|
815
|
+
const stat = fs_1.statSync(path11);
|
|
816
816
|
if (stat.isFile() && isFile) {
|
|
817
817
|
log(`[OK] path represents a file`);
|
|
818
818
|
return true;
|
|
@@ -832,8 +832,8 @@ var require_src2 = __commonJS({
|
|
|
832
832
|
throw e;
|
|
833
833
|
}
|
|
834
834
|
}
|
|
835
|
-
function exists2(
|
|
836
|
-
return check(
|
|
835
|
+
function exists2(path11, type = exports.READABLE) {
|
|
836
|
+
return check(path11, (type & exports.FILE) > 0, (type & exports.FOLDER) > 0);
|
|
837
837
|
}
|
|
838
838
|
exports.exists = exists2;
|
|
839
839
|
exports.FILE = 1;
|
|
@@ -908,7 +908,7 @@ import { Hono } from "hono";
|
|
|
908
908
|
// package.json
|
|
909
909
|
var package_default = {
|
|
910
910
|
name: "@posthog/agent",
|
|
911
|
-
version: "2.3.
|
|
911
|
+
version: "2.3.11",
|
|
912
912
|
repository: "https://github.com/PostHog/code",
|
|
913
913
|
description: "TypeScript agent framework wrapping Claude Agent SDK with Git-based task execution for PostHog",
|
|
914
914
|
exports: {
|
|
@@ -1033,6 +1033,8 @@ var POSTHOG_NOTIFICATIONS = {
|
|
|
1033
1033
|
RUN_STARTED: "_posthog/run_started",
|
|
1034
1034
|
/** Task has completed (success or failure) */
|
|
1035
1035
|
TASK_COMPLETE: "_posthog/task_complete",
|
|
1036
|
+
/** Agent finished processing a turn (prompt returned, waiting for next input) */
|
|
1037
|
+
TURN_COMPLETE: "_posthog/turn_complete",
|
|
1036
1038
|
/** Error occurred during task execution */
|
|
1037
1039
|
ERROR: "_posthog/error",
|
|
1038
1040
|
/** Console/log output from the agent */
|
|
@@ -1580,8 +1582,8 @@ var ToolContentBuilder = class {
|
|
|
1580
1582
|
this.items.push({ type: "content", content: image(data, mimeType, uri) });
|
|
1581
1583
|
return this;
|
|
1582
1584
|
}
|
|
1583
|
-
diff(
|
|
1584
|
-
this.items.push({ type: "diff", path:
|
|
1585
|
+
diff(path11, oldText, newText) {
|
|
1586
|
+
this.items.push({ type: "diff", path: path11, oldText, newText });
|
|
1585
1587
|
return this;
|
|
1586
1588
|
}
|
|
1587
1589
|
build() {
|
|
@@ -2958,7 +2960,12 @@ var BASH_TOOLS = /* @__PURE__ */ new Set([
|
|
|
2958
2960
|
]);
|
|
2959
2961
|
var SEARCH_TOOLS = /* @__PURE__ */ new Set(["Glob", "Grep", "LS"]);
|
|
2960
2962
|
var WEB_TOOLS = /* @__PURE__ */ new Set(["WebSearch", "WebFetch"]);
|
|
2961
|
-
var AGENT_TOOLS = /* @__PURE__ */ new Set([
|
|
2963
|
+
var AGENT_TOOLS = /* @__PURE__ */ new Set([
|
|
2964
|
+
"Task",
|
|
2965
|
+
"Agent",
|
|
2966
|
+
"TodoWrite",
|
|
2967
|
+
"Skill"
|
|
2968
|
+
]);
|
|
2962
2969
|
var BASE_ALLOWED_TOOLS = [
|
|
2963
2970
|
...READ_TOOLS,
|
|
2964
2971
|
...SEARCH_TOOLS,
|
|
@@ -4993,6 +5000,45 @@ function createCodexConnection(config) {
|
|
|
4993
5000
|
};
|
|
4994
5001
|
}
|
|
4995
5002
|
|
|
5003
|
+
// src/adapters/claude/session/jsonl-hydration.ts
|
|
5004
|
+
import { randomUUID as randomUUID2 } from "crypto";
|
|
5005
|
+
import * as fs4 from "fs/promises";
|
|
5006
|
+
import * as os5 from "os";
|
|
5007
|
+
import * as path6 from "path";
|
|
5008
|
+
var CHARS_PER_TOKEN = 4;
|
|
5009
|
+
var DEFAULT_MAX_TOKENS = 15e4;
|
|
5010
|
+
function estimateTurnTokens(turn) {
|
|
5011
|
+
let chars = 0;
|
|
5012
|
+
for (const block of turn.content) {
|
|
5013
|
+
if ("text" in block && typeof block.text === "string") {
|
|
5014
|
+
chars += block.text.length;
|
|
5015
|
+
}
|
|
5016
|
+
}
|
|
5017
|
+
if (turn.toolCalls) {
|
|
5018
|
+
for (const tc of turn.toolCalls) {
|
|
5019
|
+
chars += JSON.stringify(tc.input ?? "").length;
|
|
5020
|
+
if (tc.result !== void 0) {
|
|
5021
|
+
chars += typeof tc.result === "string" ? tc.result.length : JSON.stringify(tc.result).length;
|
|
5022
|
+
}
|
|
5023
|
+
}
|
|
5024
|
+
}
|
|
5025
|
+
return Math.ceil(chars / CHARS_PER_TOKEN);
|
|
5026
|
+
}
|
|
5027
|
+
function selectRecentTurns(turns, maxTokens = DEFAULT_MAX_TOKENS) {
|
|
5028
|
+
let budget = maxTokens;
|
|
5029
|
+
let startIndex = turns.length;
|
|
5030
|
+
for (let i = turns.length - 1; i >= 0; i--) {
|
|
5031
|
+
const cost = estimateTurnTokens(turns[i]);
|
|
5032
|
+
if (cost > budget) break;
|
|
5033
|
+
budget -= cost;
|
|
5034
|
+
startIndex = i;
|
|
5035
|
+
}
|
|
5036
|
+
while (startIndex < turns.length && turns[startIndex].role !== "user") {
|
|
5037
|
+
startIndex++;
|
|
5038
|
+
}
|
|
5039
|
+
return turns.slice(startIndex);
|
|
5040
|
+
}
|
|
5041
|
+
|
|
4996
5042
|
// src/utils/gateway.ts
|
|
4997
5043
|
function getLlmGatewayUrl(posthogHost, product = "posthog_code") {
|
|
4998
5044
|
const url = new URL(posthogHost);
|
|
@@ -5176,330 +5222,165 @@ var PostHogAPIClient = class {
|
|
|
5176
5222
|
}
|
|
5177
5223
|
};
|
|
5178
5224
|
|
|
5179
|
-
//
|
|
5180
|
-
|
|
5181
|
-
|
|
5182
|
-
|
|
5183
|
-
|
|
5184
|
-
|
|
5185
|
-
|
|
5186
|
-
|
|
5187
|
-
|
|
5188
|
-
static SESSIONS_MAX_AGE_MS = 30 * 24 * 60 * 60 * 1e3;
|
|
5189
|
-
posthogAPI;
|
|
5190
|
-
pendingEntries = /* @__PURE__ */ new Map();
|
|
5191
|
-
flushTimeouts = /* @__PURE__ */ new Map();
|
|
5192
|
-
lastFlushAttemptTime = /* @__PURE__ */ new Map();
|
|
5193
|
-
retryCounts = /* @__PURE__ */ new Map();
|
|
5194
|
-
sessions = /* @__PURE__ */ new Map();
|
|
5195
|
-
logger;
|
|
5196
|
-
localCachePath;
|
|
5197
|
-
constructor(options = {}) {
|
|
5198
|
-
this.posthogAPI = options.posthogAPI;
|
|
5199
|
-
this.localCachePath = options.localCachePath;
|
|
5200
|
-
this.logger = options.logger ?? new Logger({ debug: false, prefix: "[SessionLogWriter]" });
|
|
5225
|
+
// ../shared/dist/index.js
|
|
5226
|
+
var consoleLogger = {
|
|
5227
|
+
info: (_message, _data) => {
|
|
5228
|
+
},
|
|
5229
|
+
debug: (_message, _data) => {
|
|
5230
|
+
},
|
|
5231
|
+
error: (_message, _data) => {
|
|
5232
|
+
},
|
|
5233
|
+
warn: (_message, _data) => {
|
|
5201
5234
|
}
|
|
5202
|
-
|
|
5203
|
-
|
|
5204
|
-
|
|
5205
|
-
|
|
5206
|
-
|
|
5207
|
-
|
|
5208
|
-
|
|
5235
|
+
};
|
|
5236
|
+
var Saga = class {
|
|
5237
|
+
completedSteps = [];
|
|
5238
|
+
currentStepName = "unknown";
|
|
5239
|
+
stepTimings = [];
|
|
5240
|
+
log;
|
|
5241
|
+
constructor(logger) {
|
|
5242
|
+
this.log = logger ?? consoleLogger;
|
|
5209
5243
|
}
|
|
5210
|
-
|
|
5211
|
-
|
|
5212
|
-
|
|
5244
|
+
/**
|
|
5245
|
+
* Run the saga with the given input.
|
|
5246
|
+
* Returns a discriminated union result - either success with data or failure with error details.
|
|
5247
|
+
*/
|
|
5248
|
+
async run(input) {
|
|
5249
|
+
this.completedSteps = [];
|
|
5250
|
+
this.currentStepName = "unknown";
|
|
5251
|
+
this.stepTimings = [];
|
|
5252
|
+
const sagaStart = performance.now();
|
|
5253
|
+
this.log.info("Starting saga", { sagaName: this.sagaName });
|
|
5254
|
+
try {
|
|
5255
|
+
const result = await this.execute(input);
|
|
5256
|
+
const totalDuration = performance.now() - sagaStart;
|
|
5257
|
+
this.log.debug("Saga completed successfully", {
|
|
5258
|
+
sagaName: this.sagaName,
|
|
5259
|
+
stepsCompleted: this.completedSteps.length,
|
|
5260
|
+
totalDurationMs: Math.round(totalDuration),
|
|
5261
|
+
stepTimings: this.stepTimings
|
|
5262
|
+
});
|
|
5263
|
+
return { success: true, data: result };
|
|
5264
|
+
} catch (error) {
|
|
5265
|
+
this.log.error("Saga failed, initiating rollback", {
|
|
5266
|
+
sagaName: this.sagaName,
|
|
5267
|
+
failedStep: this.currentStepName,
|
|
5268
|
+
error: error instanceof Error ? error.message : String(error)
|
|
5269
|
+
});
|
|
5270
|
+
await this.rollback();
|
|
5271
|
+
return {
|
|
5272
|
+
success: false,
|
|
5273
|
+
error: error instanceof Error ? error.message : String(error),
|
|
5274
|
+
failedStep: this.currentStepName
|
|
5275
|
+
};
|
|
5213
5276
|
}
|
|
5214
|
-
|
|
5215
|
-
|
|
5216
|
-
|
|
5277
|
+
}
|
|
5278
|
+
/**
|
|
5279
|
+
* Execute a step with its rollback action.
|
|
5280
|
+
* If the step succeeds, its rollback action is stored for potential rollback.
|
|
5281
|
+
* The step name is automatically tracked for error reporting.
|
|
5282
|
+
*
|
|
5283
|
+
* @param config - Step configuration with name, execute, and rollback functions
|
|
5284
|
+
* @returns The result of the execute function
|
|
5285
|
+
* @throws Re-throws any error from the execute function (triggers rollback)
|
|
5286
|
+
*/
|
|
5287
|
+
async step(config) {
|
|
5288
|
+
this.currentStepName = config.name;
|
|
5289
|
+
this.log.debug(`Executing step: ${config.name}`);
|
|
5290
|
+
const stepStart = performance.now();
|
|
5291
|
+
const result = await config.execute();
|
|
5292
|
+
const durationMs = Math.round(performance.now() - stepStart);
|
|
5293
|
+
this.stepTimings.push({ name: config.name, durationMs });
|
|
5294
|
+
this.log.debug(`Step completed: ${config.name}`, { durationMs });
|
|
5295
|
+
this.completedSteps.push({
|
|
5296
|
+
name: config.name,
|
|
5297
|
+
rollback: () => config.rollback(result)
|
|
5217
5298
|
});
|
|
5218
|
-
|
|
5219
|
-
|
|
5220
|
-
|
|
5221
|
-
|
|
5222
|
-
|
|
5223
|
-
|
|
5224
|
-
|
|
5225
|
-
|
|
5299
|
+
return result;
|
|
5300
|
+
}
|
|
5301
|
+
/**
|
|
5302
|
+
* Execute a step that doesn't need rollback.
|
|
5303
|
+
* Useful for read-only operations or operations that are idempotent.
|
|
5304
|
+
* The step name is automatically tracked for error reporting.
|
|
5305
|
+
*
|
|
5306
|
+
* @param name - Step name for logging and error tracking
|
|
5307
|
+
* @param execute - The action to execute
|
|
5308
|
+
* @returns The result of the execute function
|
|
5309
|
+
*/
|
|
5310
|
+
async readOnlyStep(name, execute) {
|
|
5311
|
+
this.currentStepName = name;
|
|
5312
|
+
this.log.debug(`Executing read-only step: ${name}`);
|
|
5313
|
+
const stepStart = performance.now();
|
|
5314
|
+
const result = await execute();
|
|
5315
|
+
const durationMs = Math.round(performance.now() - stepStart);
|
|
5316
|
+
this.stepTimings.push({ name, durationMs });
|
|
5317
|
+
this.log.debug(`Read-only step completed: ${name}`, { durationMs });
|
|
5318
|
+
return result;
|
|
5319
|
+
}
|
|
5320
|
+
/**
|
|
5321
|
+
* Roll back all completed steps in reverse order.
|
|
5322
|
+
* Rollback errors are logged but don't stop the rollback of other steps.
|
|
5323
|
+
*/
|
|
5324
|
+
async rollback() {
|
|
5325
|
+
this.log.info("Rolling back saga", {
|
|
5326
|
+
stepsToRollback: this.completedSteps.length
|
|
5327
|
+
});
|
|
5328
|
+
const stepsReversed = [...this.completedSteps].reverse();
|
|
5329
|
+
for (const step of stepsReversed) {
|
|
5226
5330
|
try {
|
|
5227
|
-
|
|
5331
|
+
this.log.debug(`Rolling back step: ${step.name}`);
|
|
5332
|
+
await step.rollback();
|
|
5333
|
+
this.log.debug(`Step rolled back: ${step.name}`);
|
|
5228
5334
|
} catch (error) {
|
|
5229
|
-
this.
|
|
5230
|
-
|
|
5231
|
-
error
|
|
5335
|
+
this.log.error(`Failed to rollback step: ${step.name}`, {
|
|
5336
|
+
error: error instanceof Error ? error.message : String(error)
|
|
5232
5337
|
});
|
|
5233
5338
|
}
|
|
5234
5339
|
}
|
|
5340
|
+
this.log.info("Rollback completed", {
|
|
5341
|
+
stepsAttempted: this.completedSteps.length
|
|
5342
|
+
});
|
|
5235
5343
|
}
|
|
5236
|
-
|
|
5237
|
-
|
|
5344
|
+
/**
|
|
5345
|
+
* Get the number of completed steps (useful for testing)
|
|
5346
|
+
*/
|
|
5347
|
+
getCompletedStepCount() {
|
|
5348
|
+
return this.completedSteps.length;
|
|
5238
5349
|
}
|
|
5239
|
-
|
|
5240
|
-
|
|
5241
|
-
|
|
5242
|
-
|
|
5243
|
-
|
|
5244
|
-
|
|
5245
|
-
|
|
5246
|
-
|
|
5247
|
-
|
|
5248
|
-
|
|
5249
|
-
|
|
5250
|
-
|
|
5251
|
-
|
|
5252
|
-
|
|
5253
|
-
|
|
5254
|
-
|
|
5255
|
-
|
|
5256
|
-
|
|
5257
|
-
|
|
5258
|
-
|
|
5259
|
-
|
|
5260
|
-
|
|
5261
|
-
|
|
5262
|
-
|
|
5263
|
-
|
|
5264
|
-
|
|
5265
|
-
|
|
5266
|
-
|
|
5267
|
-
|
|
5268
|
-
|
|
5269
|
-
|
|
5270
|
-
|
|
5271
|
-
|
|
5272
|
-
|
|
5273
|
-
const pending = this.pendingEntries.get(sessionId) ?? [];
|
|
5274
|
-
pending.push(entry);
|
|
5275
|
-
this.pendingEntries.set(sessionId, pending);
|
|
5276
|
-
this.scheduleFlush(sessionId);
|
|
5277
|
-
}
|
|
5278
|
-
} catch {
|
|
5279
|
-
this.logger.warn("Failed to parse raw line for persistence", {
|
|
5280
|
-
taskId: session.context.taskId,
|
|
5281
|
-
runId: session.context.runId,
|
|
5282
|
-
lineLength: line.length
|
|
5283
|
-
});
|
|
5284
|
-
}
|
|
5285
|
-
}
|
|
5286
|
-
async flush(sessionId) {
|
|
5287
|
-
const session = this.sessions.get(sessionId);
|
|
5288
|
-
if (!session) {
|
|
5289
|
-
this.logger.warn("flush: no session found", { sessionId });
|
|
5290
|
-
return;
|
|
5291
|
-
}
|
|
5292
|
-
this.emitCoalescedMessage(sessionId, session);
|
|
5293
|
-
const pending = this.pendingEntries.get(sessionId);
|
|
5294
|
-
if (!this.posthogAPI || !pending?.length) {
|
|
5295
|
-
return;
|
|
5296
|
-
}
|
|
5297
|
-
this.pendingEntries.delete(sessionId);
|
|
5298
|
-
const timeout = this.flushTimeouts.get(sessionId);
|
|
5299
|
-
if (timeout) {
|
|
5300
|
-
clearTimeout(timeout);
|
|
5301
|
-
this.flushTimeouts.delete(sessionId);
|
|
5302
|
-
}
|
|
5303
|
-
this.lastFlushAttemptTime.set(sessionId, Date.now());
|
|
5304
|
-
try {
|
|
5305
|
-
await this.posthogAPI.appendTaskRunLog(
|
|
5306
|
-
session.context.taskId,
|
|
5307
|
-
session.context.runId,
|
|
5308
|
-
pending
|
|
5309
|
-
);
|
|
5310
|
-
this.retryCounts.set(sessionId, 0);
|
|
5311
|
-
} catch (error) {
|
|
5312
|
-
const retryCount = (this.retryCounts.get(sessionId) ?? 0) + 1;
|
|
5313
|
-
this.retryCounts.set(sessionId, retryCount);
|
|
5314
|
-
if (retryCount >= _SessionLogWriter.MAX_FLUSH_RETRIES) {
|
|
5315
|
-
this.logger.error(
|
|
5316
|
-
`Dropping ${pending.length} session log entries after ${retryCount} failed flush attempts`,
|
|
5317
|
-
{
|
|
5318
|
-
taskId: session.context.taskId,
|
|
5319
|
-
runId: session.context.runId,
|
|
5320
|
-
error
|
|
5321
|
-
}
|
|
5322
|
-
);
|
|
5323
|
-
this.retryCounts.set(sessionId, 0);
|
|
5324
|
-
} else {
|
|
5325
|
-
if (retryCount === 1) {
|
|
5326
|
-
this.logger.warn(
|
|
5327
|
-
`Failed to persist session logs, will retry (up to ${_SessionLogWriter.MAX_FLUSH_RETRIES} attempts)`,
|
|
5328
|
-
{
|
|
5329
|
-
taskId: session.context.taskId,
|
|
5330
|
-
runId: session.context.runId,
|
|
5331
|
-
error: error instanceof Error ? error.message : String(error)
|
|
5332
|
-
}
|
|
5333
|
-
);
|
|
5334
|
-
}
|
|
5335
|
-
const currentPending = this.pendingEntries.get(sessionId) ?? [];
|
|
5336
|
-
this.pendingEntries.set(sessionId, [...pending, ...currentPending]);
|
|
5337
|
-
this.scheduleFlush(sessionId);
|
|
5338
|
-
}
|
|
5339
|
-
}
|
|
5340
|
-
}
|
|
5341
|
-
isAgentMessageChunk(message) {
|
|
5342
|
-
if (message.method !== "session/update") return false;
|
|
5343
|
-
const params = message.params;
|
|
5344
|
-
const update = params?.update;
|
|
5345
|
-
return update?.sessionUpdate === "agent_message_chunk";
|
|
5346
|
-
}
|
|
5347
|
-
extractChunkText(message) {
|
|
5348
|
-
const params = message.params;
|
|
5349
|
-
const update = params?.update;
|
|
5350
|
-
const content = update?.content;
|
|
5351
|
-
if (content?.type === "text" && content.text) {
|
|
5352
|
-
return content.text;
|
|
5353
|
-
}
|
|
5354
|
-
return "";
|
|
5355
|
-
}
|
|
5356
|
-
emitCoalescedMessage(sessionId, session) {
|
|
5357
|
-
if (!session.chunkBuffer) return;
|
|
5358
|
-
const { text: text2, firstTimestamp } = session.chunkBuffer;
|
|
5359
|
-
session.chunkBuffer = void 0;
|
|
5360
|
-
session.lastAgentMessage = text2;
|
|
5361
|
-
const entry = {
|
|
5362
|
-
type: "notification",
|
|
5363
|
-
timestamp: firstTimestamp,
|
|
5364
|
-
notification: {
|
|
5365
|
-
jsonrpc: "2.0",
|
|
5366
|
-
method: "session/update",
|
|
5367
|
-
params: {
|
|
5368
|
-
update: {
|
|
5369
|
-
sessionUpdate: "agent_message",
|
|
5370
|
-
content: { type: "text", text: text2 }
|
|
5371
|
-
}
|
|
5372
|
-
}
|
|
5373
|
-
}
|
|
5374
|
-
};
|
|
5375
|
-
this.writeToLocalCache(sessionId, entry);
|
|
5376
|
-
if (this.posthogAPI) {
|
|
5377
|
-
const pending = this.pendingEntries.get(sessionId) ?? [];
|
|
5378
|
-
pending.push(entry);
|
|
5379
|
-
this.pendingEntries.set(sessionId, pending);
|
|
5380
|
-
this.scheduleFlush(sessionId);
|
|
5381
|
-
}
|
|
5382
|
-
}
|
|
5383
|
-
getLastAgentMessage(sessionId) {
|
|
5384
|
-
return this.sessions.get(sessionId)?.lastAgentMessage;
|
|
5385
|
-
}
|
|
5386
|
-
extractAgentMessageText(message) {
|
|
5387
|
-
if (message.method !== "session/update") {
|
|
5388
|
-
return null;
|
|
5389
|
-
}
|
|
5390
|
-
const params = message.params;
|
|
5391
|
-
const update = params?.update;
|
|
5392
|
-
if (update?.sessionUpdate !== "agent_message") {
|
|
5393
|
-
return null;
|
|
5394
|
-
}
|
|
5395
|
-
const content = update.content;
|
|
5396
|
-
if (content?.type === "text" && typeof content.text === "string") {
|
|
5397
|
-
const trimmed2 = content.text.trim();
|
|
5398
|
-
return trimmed2.length > 0 ? trimmed2 : null;
|
|
5399
|
-
}
|
|
5400
|
-
if (typeof update.message === "string") {
|
|
5401
|
-
const trimmed2 = update.message.trim();
|
|
5402
|
-
return trimmed2.length > 0 ? trimmed2 : null;
|
|
5403
|
-
}
|
|
5404
|
-
return null;
|
|
5405
|
-
}
|
|
5406
|
-
scheduleFlush(sessionId) {
|
|
5407
|
-
const existing = this.flushTimeouts.get(sessionId);
|
|
5408
|
-
if (existing) clearTimeout(existing);
|
|
5409
|
-
const retryCount = this.retryCounts.get(sessionId) ?? 0;
|
|
5410
|
-
const lastAttempt = this.lastFlushAttemptTime.get(sessionId) ?? 0;
|
|
5411
|
-
const elapsed = Date.now() - lastAttempt;
|
|
5412
|
-
let delay3;
|
|
5413
|
-
if (retryCount > 0) {
|
|
5414
|
-
delay3 = Math.min(
|
|
5415
|
-
_SessionLogWriter.FLUSH_DEBOUNCE_MS * 2 ** retryCount,
|
|
5416
|
-
_SessionLogWriter.MAX_RETRY_DELAY_MS
|
|
5417
|
-
);
|
|
5418
|
-
} else if (elapsed >= _SessionLogWriter.FLUSH_MAX_INTERVAL_MS) {
|
|
5419
|
-
delay3 = 0;
|
|
5420
|
-
} else {
|
|
5421
|
-
delay3 = _SessionLogWriter.FLUSH_DEBOUNCE_MS;
|
|
5422
|
-
}
|
|
5423
|
-
const timeout = setTimeout(() => this.flush(sessionId), delay3);
|
|
5424
|
-
this.flushTimeouts.set(sessionId, timeout);
|
|
5425
|
-
}
|
|
5426
|
-
writeToLocalCache(sessionId, entry) {
|
|
5427
|
-
if (!this.localCachePath) return;
|
|
5428
|
-
const session = this.sessions.get(sessionId);
|
|
5429
|
-
if (!session) return;
|
|
5430
|
-
const logPath = path6.join(
|
|
5431
|
-
this.localCachePath,
|
|
5432
|
-
"sessions",
|
|
5433
|
-
session.context.runId,
|
|
5434
|
-
"logs.ndjson"
|
|
5435
|
-
);
|
|
5436
|
-
try {
|
|
5437
|
-
fs4.appendFileSync(logPath, `${JSON.stringify(entry)}
|
|
5438
|
-
`);
|
|
5439
|
-
} catch (error) {
|
|
5440
|
-
this.logger.warn("Failed to write to local cache", {
|
|
5441
|
-
taskId: session.context.taskId,
|
|
5442
|
-
runId: session.context.runId,
|
|
5443
|
-
logPath,
|
|
5444
|
-
error
|
|
5445
|
-
});
|
|
5446
|
-
}
|
|
5447
|
-
}
|
|
5448
|
-
static async cleanupOldSessions(localCachePath) {
|
|
5449
|
-
const sessionsDir = path6.join(localCachePath, "sessions");
|
|
5450
|
-
let deleted = 0;
|
|
5451
|
-
try {
|
|
5452
|
-
const entries = await fsp.readdir(sessionsDir);
|
|
5453
|
-
const now = Date.now();
|
|
5454
|
-
for (const entry of entries) {
|
|
5455
|
-
const entryPath = path6.join(sessionsDir, entry);
|
|
5456
|
-
try {
|
|
5457
|
-
const stats = await fsp.stat(entryPath);
|
|
5458
|
-
if (stats.isDirectory() && now - stats.birthtimeMs > _SessionLogWriter.SESSIONS_MAX_AGE_MS) {
|
|
5459
|
-
await fsp.rm(entryPath, { recursive: true, force: true });
|
|
5460
|
-
deleted++;
|
|
5461
|
-
}
|
|
5462
|
-
} catch {
|
|
5463
|
-
}
|
|
5464
|
-
}
|
|
5465
|
-
} catch {
|
|
5466
|
-
}
|
|
5467
|
-
return deleted;
|
|
5468
|
-
}
|
|
5469
|
-
};
|
|
5470
|
-
|
|
5471
|
-
// ../git/dist/queries.js
|
|
5472
|
-
import * as fs6 from "fs/promises";
|
|
5473
|
-
import * as path8 from "path";
|
|
5474
|
-
|
|
5475
|
-
// ../../node_modules/simple-git/dist/esm/index.js
|
|
5476
|
-
var import_file_exists = __toESM(require_dist(), 1);
|
|
5477
|
-
var import_debug = __toESM(require_src(), 1);
|
|
5478
|
-
var import_promise_deferred = __toESM(require_dist2(), 1);
|
|
5479
|
-
var import_promise_deferred2 = __toESM(require_dist2(), 1);
|
|
5480
|
-
import { Buffer as Buffer2 } from "buffer";
|
|
5481
|
-
import { spawn as spawn3 } from "child_process";
|
|
5482
|
-
import { normalize as normalize2 } from "path";
|
|
5483
|
-
import { EventEmitter } from "events";
|
|
5484
|
-
var __defProp2 = Object.defineProperty;
|
|
5485
|
-
var __getOwnPropDesc2 = Object.getOwnPropertyDescriptor;
|
|
5486
|
-
var __getOwnPropNames2 = Object.getOwnPropertyNames;
|
|
5487
|
-
var __hasOwnProp2 = Object.prototype.hasOwnProperty;
|
|
5488
|
-
var __esm = (fn, res) => function __init() {
|
|
5489
|
-
return fn && (res = (0, fn[__getOwnPropNames2(fn)[0]])(fn = 0)), res;
|
|
5490
|
-
};
|
|
5491
|
-
var __commonJS2 = (cb, mod) => function __require2() {
|
|
5492
|
-
return mod || (0, cb[__getOwnPropNames2(cb)[0]])((mod = { exports: {} }).exports, mod), mod.exports;
|
|
5493
|
-
};
|
|
5494
|
-
var __export = (target, all) => {
|
|
5495
|
-
for (var name in all)
|
|
5496
|
-
__defProp2(target, name, { get: all[name], enumerable: true });
|
|
5497
|
-
};
|
|
5498
|
-
var __copyProps2 = (to, from, except, desc) => {
|
|
5499
|
-
if (from && typeof from === "object" || typeof from === "function") {
|
|
5500
|
-
for (let key of __getOwnPropNames2(from))
|
|
5501
|
-
if (!__hasOwnProp2.call(to, key) && key !== except)
|
|
5502
|
-
__defProp2(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc2(from, key)) || desc.enumerable });
|
|
5350
|
+
};
|
|
5351
|
+
|
|
5352
|
+
// ../git/dist/queries.js
|
|
5353
|
+
import * as fs6 from "fs/promises";
|
|
5354
|
+
import * as path8 from "path";
|
|
5355
|
+
|
|
5356
|
+
// ../../node_modules/simple-git/dist/esm/index.js
|
|
5357
|
+
var import_file_exists = __toESM(require_dist(), 1);
|
|
5358
|
+
var import_debug = __toESM(require_src(), 1);
|
|
5359
|
+
var import_promise_deferred = __toESM(require_dist2(), 1);
|
|
5360
|
+
var import_promise_deferred2 = __toESM(require_dist2(), 1);
|
|
5361
|
+
import { Buffer as Buffer2 } from "buffer";
|
|
5362
|
+
import { spawn as spawn3 } from "child_process";
|
|
5363
|
+
import { normalize as normalize2 } from "path";
|
|
5364
|
+
import { EventEmitter } from "events";
|
|
5365
|
+
var __defProp2 = Object.defineProperty;
|
|
5366
|
+
var __getOwnPropDesc2 = Object.getOwnPropertyDescriptor;
|
|
5367
|
+
var __getOwnPropNames2 = Object.getOwnPropertyNames;
|
|
5368
|
+
var __hasOwnProp2 = Object.prototype.hasOwnProperty;
|
|
5369
|
+
var __esm = (fn, res) => function __init() {
|
|
5370
|
+
return fn && (res = (0, fn[__getOwnPropNames2(fn)[0]])(fn = 0)), res;
|
|
5371
|
+
};
|
|
5372
|
+
var __commonJS2 = (cb, mod) => function __require2() {
|
|
5373
|
+
return mod || (0, cb[__getOwnPropNames2(cb)[0]])((mod = { exports: {} }).exports, mod), mod.exports;
|
|
5374
|
+
};
|
|
5375
|
+
var __export = (target, all) => {
|
|
5376
|
+
for (var name in all)
|
|
5377
|
+
__defProp2(target, name, { get: all[name], enumerable: true });
|
|
5378
|
+
};
|
|
5379
|
+
var __copyProps2 = (to, from, except, desc) => {
|
|
5380
|
+
if (from && typeof from === "object" || typeof from === "function") {
|
|
5381
|
+
for (let key of __getOwnPropNames2(from))
|
|
5382
|
+
if (!__hasOwnProp2.call(to, key) && key !== except)
|
|
5383
|
+
__defProp2(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc2(from, key)) || desc.enumerable });
|
|
5503
5384
|
}
|
|
5504
5385
|
return to;
|
|
5505
5386
|
};
|
|
@@ -5509,8 +5390,8 @@ function pathspec(...paths) {
|
|
|
5509
5390
|
cache.set(key, paths);
|
|
5510
5391
|
return key;
|
|
5511
5392
|
}
|
|
5512
|
-
function isPathSpec(
|
|
5513
|
-
return
|
|
5393
|
+
function isPathSpec(path11) {
|
|
5394
|
+
return path11 instanceof String && cache.has(path11);
|
|
5514
5395
|
}
|
|
5515
5396
|
function toPaths(pathSpec) {
|
|
5516
5397
|
return cache.get(pathSpec) || [];
|
|
@@ -5599,8 +5480,8 @@ function toLinesWithContent(input = "", trimmed2 = true, separator = "\n") {
|
|
|
5599
5480
|
function forEachLineWithContent(input, callback) {
|
|
5600
5481
|
return toLinesWithContent(input, true).map((line) => callback(line));
|
|
5601
5482
|
}
|
|
5602
|
-
function folderExists(
|
|
5603
|
-
return (0, import_file_exists.exists)(
|
|
5483
|
+
function folderExists(path11) {
|
|
5484
|
+
return (0, import_file_exists.exists)(path11, import_file_exists.FOLDER);
|
|
5604
5485
|
}
|
|
5605
5486
|
function append(target, item) {
|
|
5606
5487
|
if (Array.isArray(target)) {
|
|
@@ -6004,8 +5885,8 @@ function checkIsRepoRootTask() {
|
|
|
6004
5885
|
commands,
|
|
6005
5886
|
format: "utf-8",
|
|
6006
5887
|
onError,
|
|
6007
|
-
parser(
|
|
6008
|
-
return /^\.(git)?$/.test(
|
|
5888
|
+
parser(path11) {
|
|
5889
|
+
return /^\.(git)?$/.test(path11.trim());
|
|
6009
5890
|
}
|
|
6010
5891
|
};
|
|
6011
5892
|
}
|
|
@@ -6439,11 +6320,11 @@ function parseGrep(grep) {
|
|
|
6439
6320
|
const paths = /* @__PURE__ */ new Set();
|
|
6440
6321
|
const results = {};
|
|
6441
6322
|
forEachLineWithContent(grep, (input) => {
|
|
6442
|
-
const [
|
|
6443
|
-
paths.add(
|
|
6444
|
-
(results[
|
|
6323
|
+
const [path11, line, preview] = input.split(NULL);
|
|
6324
|
+
paths.add(path11);
|
|
6325
|
+
(results[path11] = results[path11] || []).push({
|
|
6445
6326
|
line: asNumber(line),
|
|
6446
|
-
path:
|
|
6327
|
+
path: path11,
|
|
6447
6328
|
preview
|
|
6448
6329
|
});
|
|
6449
6330
|
});
|
|
@@ -7208,14 +7089,14 @@ var init_hash_object = __esm({
|
|
|
7208
7089
|
init_task();
|
|
7209
7090
|
}
|
|
7210
7091
|
});
|
|
7211
|
-
function parseInit(bare,
|
|
7092
|
+
function parseInit(bare, path11, text2) {
|
|
7212
7093
|
const response = String(text2).trim();
|
|
7213
7094
|
let result;
|
|
7214
7095
|
if (result = initResponseRegex.exec(response)) {
|
|
7215
|
-
return new InitSummary(bare,
|
|
7096
|
+
return new InitSummary(bare, path11, false, result[1]);
|
|
7216
7097
|
}
|
|
7217
7098
|
if (result = reInitResponseRegex.exec(response)) {
|
|
7218
|
-
return new InitSummary(bare,
|
|
7099
|
+
return new InitSummary(bare, path11, true, result[1]);
|
|
7219
7100
|
}
|
|
7220
7101
|
let gitDir = "";
|
|
7221
7102
|
const tokens = response.split(" ");
|
|
@@ -7226,7 +7107,7 @@ function parseInit(bare, path10, text2) {
|
|
|
7226
7107
|
break;
|
|
7227
7108
|
}
|
|
7228
7109
|
}
|
|
7229
|
-
return new InitSummary(bare,
|
|
7110
|
+
return new InitSummary(bare, path11, /^re/i.test(response), gitDir);
|
|
7230
7111
|
}
|
|
7231
7112
|
var InitSummary;
|
|
7232
7113
|
var initResponseRegex;
|
|
@@ -7235,9 +7116,9 @@ var init_InitSummary = __esm({
|
|
|
7235
7116
|
"src/lib/responses/InitSummary.ts"() {
|
|
7236
7117
|
"use strict";
|
|
7237
7118
|
InitSummary = class {
|
|
7238
|
-
constructor(bare,
|
|
7119
|
+
constructor(bare, path11, existing, gitDir) {
|
|
7239
7120
|
this.bare = bare;
|
|
7240
|
-
this.path =
|
|
7121
|
+
this.path = path11;
|
|
7241
7122
|
this.existing = existing;
|
|
7242
7123
|
this.gitDir = gitDir;
|
|
7243
7124
|
}
|
|
@@ -7249,7 +7130,7 @@ var init_InitSummary = __esm({
|
|
|
7249
7130
|
function hasBareCommand(command) {
|
|
7250
7131
|
return command.includes(bareCommand);
|
|
7251
7132
|
}
|
|
7252
|
-
function initTask(bare = false,
|
|
7133
|
+
function initTask(bare = false, path11, customArgs) {
|
|
7253
7134
|
const commands = ["init", ...customArgs];
|
|
7254
7135
|
if (bare && !hasBareCommand(commands)) {
|
|
7255
7136
|
commands.splice(1, 0, bareCommand);
|
|
@@ -7258,7 +7139,7 @@ function initTask(bare = false, path10, customArgs) {
|
|
|
7258
7139
|
commands,
|
|
7259
7140
|
format: "utf-8",
|
|
7260
7141
|
parser(text2) {
|
|
7261
|
-
return parseInit(commands.includes("--bare"),
|
|
7142
|
+
return parseInit(commands.includes("--bare"), path11, text2);
|
|
7262
7143
|
}
|
|
7263
7144
|
};
|
|
7264
7145
|
}
|
|
@@ -8074,12 +7955,12 @@ var init_FileStatusSummary = __esm({
|
|
|
8074
7955
|
"use strict";
|
|
8075
7956
|
fromPathRegex = /^(.+)\0(.+)$/;
|
|
8076
7957
|
FileStatusSummary = class {
|
|
8077
|
-
constructor(
|
|
8078
|
-
this.path =
|
|
7958
|
+
constructor(path11, index, working_dir) {
|
|
7959
|
+
this.path = path11;
|
|
8079
7960
|
this.index = index;
|
|
8080
7961
|
this.working_dir = working_dir;
|
|
8081
7962
|
if (index === "R" || working_dir === "R") {
|
|
8082
|
-
const detail = fromPathRegex.exec(
|
|
7963
|
+
const detail = fromPathRegex.exec(path11) || [null, path11, path11];
|
|
8083
7964
|
this.from = detail[2] || "";
|
|
8084
7965
|
this.path = detail[1] || "";
|
|
8085
7966
|
}
|
|
@@ -8110,14 +7991,14 @@ function splitLine(result, lineStr) {
|
|
|
8110
7991
|
default:
|
|
8111
7992
|
return;
|
|
8112
7993
|
}
|
|
8113
|
-
function data(index, workingDir,
|
|
7994
|
+
function data(index, workingDir, path11) {
|
|
8114
7995
|
const raw = `${index}${workingDir}`;
|
|
8115
7996
|
const handler = parsers6.get(raw);
|
|
8116
7997
|
if (handler) {
|
|
8117
|
-
handler(result,
|
|
7998
|
+
handler(result, path11);
|
|
8118
7999
|
}
|
|
8119
8000
|
if (raw !== "##" && raw !== "!!") {
|
|
8120
|
-
result.files.push(new FileStatusSummary(
|
|
8001
|
+
result.files.push(new FileStatusSummary(path11, index, workingDir));
|
|
8121
8002
|
}
|
|
8122
8003
|
}
|
|
8123
8004
|
}
|
|
@@ -8430,9 +8311,9 @@ var init_simple_git_api = __esm({
|
|
|
8430
8311
|
next
|
|
8431
8312
|
);
|
|
8432
8313
|
}
|
|
8433
|
-
hashObject(
|
|
8314
|
+
hashObject(path11, write) {
|
|
8434
8315
|
return this._runTask(
|
|
8435
|
-
hashObjectTask(
|
|
8316
|
+
hashObjectTask(path11, write === true),
|
|
8436
8317
|
trailingFunctionArgument(arguments)
|
|
8437
8318
|
);
|
|
8438
8319
|
}
|
|
@@ -8785,8 +8666,8 @@ var init_branch = __esm({
|
|
|
8785
8666
|
}
|
|
8786
8667
|
});
|
|
8787
8668
|
function toPath(input) {
|
|
8788
|
-
const
|
|
8789
|
-
return
|
|
8669
|
+
const path11 = input.trim().replace(/^["']|["']$/g, "");
|
|
8670
|
+
return path11 && normalize2(path11);
|
|
8790
8671
|
}
|
|
8791
8672
|
var parseCheckIgnore;
|
|
8792
8673
|
var init_CheckIgnore = __esm({
|
|
@@ -9100,8 +8981,8 @@ __export(sub_module_exports, {
|
|
|
9100
8981
|
subModuleTask: () => subModuleTask,
|
|
9101
8982
|
updateSubModuleTask: () => updateSubModuleTask
|
|
9102
8983
|
});
|
|
9103
|
-
function addSubModuleTask(repo,
|
|
9104
|
-
return subModuleTask(["add", repo,
|
|
8984
|
+
function addSubModuleTask(repo, path11) {
|
|
8985
|
+
return subModuleTask(["add", repo, path11]);
|
|
9105
8986
|
}
|
|
9106
8987
|
function initSubModuleTask(customArgs) {
|
|
9107
8988
|
return subModuleTask(["init", ...customArgs]);
|
|
@@ -9431,8 +9312,8 @@ var require_git = __commonJS2({
|
|
|
9431
9312
|
}
|
|
9432
9313
|
return this._runTask(straightThroughStringTask2(command, this._trimmed), next);
|
|
9433
9314
|
};
|
|
9434
|
-
Git2.prototype.submoduleAdd = function(repo,
|
|
9435
|
-
return this._runTask(addSubModuleTask2(repo,
|
|
9315
|
+
Git2.prototype.submoduleAdd = function(repo, path11, then) {
|
|
9316
|
+
return this._runTask(addSubModuleTask2(repo, path11), trailingFunctionArgument2(arguments));
|
|
9436
9317
|
};
|
|
9437
9318
|
Git2.prototype.submoduleUpdate = function(args, then) {
|
|
9438
9319
|
return this._runTask(
|
|
@@ -10225,8 +10106,8 @@ async function getHeadSha(baseDir, options) {
|
|
|
10225
10106
|
}
|
|
10226
10107
|
|
|
10227
10108
|
// src/sagas/apply-snapshot-saga.ts
|
|
10228
|
-
import { mkdir as
|
|
10229
|
-
import { join as
|
|
10109
|
+
import { mkdir as mkdir4, rm as rm3, writeFile as writeFile4 } from "fs/promises";
|
|
10110
|
+
import { join as join7 } from "path";
|
|
10230
10111
|
|
|
10231
10112
|
// ../git/dist/sagas/tree.js
|
|
10232
10113
|
import { existsSync as existsSync4 } from "fs";
|
|
@@ -10234,148 +10115,21 @@ import * as fs7 from "fs/promises";
|
|
|
10234
10115
|
import * as path9 from "path";
|
|
10235
10116
|
import * as tar from "tar";
|
|
10236
10117
|
|
|
10237
|
-
// ../
|
|
10238
|
-
var
|
|
10239
|
-
|
|
10240
|
-
|
|
10241
|
-
|
|
10242
|
-
|
|
10243
|
-
|
|
10244
|
-
|
|
10245
|
-
warn: (_message, _data) => {
|
|
10118
|
+
// ../git/dist/git-saga.js
|
|
10119
|
+
var GitSaga = class extends Saga {
|
|
10120
|
+
_git = null;
|
|
10121
|
+
get git() {
|
|
10122
|
+
if (!this._git) {
|
|
10123
|
+
throw new Error("git client accessed before execute() was called");
|
|
10124
|
+
}
|
|
10125
|
+
return this._git;
|
|
10246
10126
|
}
|
|
10247
|
-
|
|
10248
|
-
|
|
10249
|
-
|
|
10250
|
-
|
|
10251
|
-
|
|
10252
|
-
|
|
10253
|
-
constructor(logger) {
|
|
10254
|
-
this.log = logger ?? consoleLogger;
|
|
10255
|
-
}
|
|
10256
|
-
/**
|
|
10257
|
-
* Run the saga with the given input.
|
|
10258
|
-
* Returns a discriminated union result - either success with data or failure with error details.
|
|
10259
|
-
*/
|
|
10260
|
-
async run(input) {
|
|
10261
|
-
this.completedSteps = [];
|
|
10262
|
-
this.currentStepName = "unknown";
|
|
10263
|
-
this.stepTimings = [];
|
|
10264
|
-
const sagaStart = performance.now();
|
|
10265
|
-
this.log.info("Starting saga", { sagaName: this.sagaName });
|
|
10266
|
-
try {
|
|
10267
|
-
const result = await this.execute(input);
|
|
10268
|
-
const totalDuration = performance.now() - sagaStart;
|
|
10269
|
-
this.log.debug("Saga completed successfully", {
|
|
10270
|
-
sagaName: this.sagaName,
|
|
10271
|
-
stepsCompleted: this.completedSteps.length,
|
|
10272
|
-
totalDurationMs: Math.round(totalDuration),
|
|
10273
|
-
stepTimings: this.stepTimings
|
|
10274
|
-
});
|
|
10275
|
-
return { success: true, data: result };
|
|
10276
|
-
} catch (error) {
|
|
10277
|
-
this.log.error("Saga failed, initiating rollback", {
|
|
10278
|
-
sagaName: this.sagaName,
|
|
10279
|
-
failedStep: this.currentStepName,
|
|
10280
|
-
error: error instanceof Error ? error.message : String(error)
|
|
10281
|
-
});
|
|
10282
|
-
await this.rollback();
|
|
10283
|
-
return {
|
|
10284
|
-
success: false,
|
|
10285
|
-
error: error instanceof Error ? error.message : String(error),
|
|
10286
|
-
failedStep: this.currentStepName
|
|
10287
|
-
};
|
|
10288
|
-
}
|
|
10289
|
-
}
|
|
10290
|
-
/**
|
|
10291
|
-
* Execute a step with its rollback action.
|
|
10292
|
-
* If the step succeeds, its rollback action is stored for potential rollback.
|
|
10293
|
-
* The step name is automatically tracked for error reporting.
|
|
10294
|
-
*
|
|
10295
|
-
* @param config - Step configuration with name, execute, and rollback functions
|
|
10296
|
-
* @returns The result of the execute function
|
|
10297
|
-
* @throws Re-throws any error from the execute function (triggers rollback)
|
|
10298
|
-
*/
|
|
10299
|
-
async step(config) {
|
|
10300
|
-
this.currentStepName = config.name;
|
|
10301
|
-
this.log.debug(`Executing step: ${config.name}`);
|
|
10302
|
-
const stepStart = performance.now();
|
|
10303
|
-
const result = await config.execute();
|
|
10304
|
-
const durationMs = Math.round(performance.now() - stepStart);
|
|
10305
|
-
this.stepTimings.push({ name: config.name, durationMs });
|
|
10306
|
-
this.log.debug(`Step completed: ${config.name}`, { durationMs });
|
|
10307
|
-
this.completedSteps.push({
|
|
10308
|
-
name: config.name,
|
|
10309
|
-
rollback: () => config.rollback(result)
|
|
10310
|
-
});
|
|
10311
|
-
return result;
|
|
10312
|
-
}
|
|
10313
|
-
/**
|
|
10314
|
-
* Execute a step that doesn't need rollback.
|
|
10315
|
-
* Useful for read-only operations or operations that are idempotent.
|
|
10316
|
-
* The step name is automatically tracked for error reporting.
|
|
10317
|
-
*
|
|
10318
|
-
* @param name - Step name for logging and error tracking
|
|
10319
|
-
* @param execute - The action to execute
|
|
10320
|
-
* @returns The result of the execute function
|
|
10321
|
-
*/
|
|
10322
|
-
async readOnlyStep(name, execute) {
|
|
10323
|
-
this.currentStepName = name;
|
|
10324
|
-
this.log.debug(`Executing read-only step: ${name}`);
|
|
10325
|
-
const stepStart = performance.now();
|
|
10326
|
-
const result = await execute();
|
|
10327
|
-
const durationMs = Math.round(performance.now() - stepStart);
|
|
10328
|
-
this.stepTimings.push({ name, durationMs });
|
|
10329
|
-
this.log.debug(`Read-only step completed: ${name}`, { durationMs });
|
|
10330
|
-
return result;
|
|
10331
|
-
}
|
|
10332
|
-
/**
|
|
10333
|
-
* Roll back all completed steps in reverse order.
|
|
10334
|
-
* Rollback errors are logged but don't stop the rollback of other steps.
|
|
10335
|
-
*/
|
|
10336
|
-
async rollback() {
|
|
10337
|
-
this.log.info("Rolling back saga", {
|
|
10338
|
-
stepsToRollback: this.completedSteps.length
|
|
10339
|
-
});
|
|
10340
|
-
const stepsReversed = [...this.completedSteps].reverse();
|
|
10341
|
-
for (const step of stepsReversed) {
|
|
10342
|
-
try {
|
|
10343
|
-
this.log.debug(`Rolling back step: ${step.name}`);
|
|
10344
|
-
await step.rollback();
|
|
10345
|
-
this.log.debug(`Step rolled back: ${step.name}`);
|
|
10346
|
-
} catch (error) {
|
|
10347
|
-
this.log.error(`Failed to rollback step: ${step.name}`, {
|
|
10348
|
-
error: error instanceof Error ? error.message : String(error)
|
|
10349
|
-
});
|
|
10350
|
-
}
|
|
10351
|
-
}
|
|
10352
|
-
this.log.info("Rollback completed", {
|
|
10353
|
-
stepsAttempted: this.completedSteps.length
|
|
10354
|
-
});
|
|
10355
|
-
}
|
|
10356
|
-
/**
|
|
10357
|
-
* Get the number of completed steps (useful for testing)
|
|
10358
|
-
*/
|
|
10359
|
-
getCompletedStepCount() {
|
|
10360
|
-
return this.completedSteps.length;
|
|
10361
|
-
}
|
|
10362
|
-
};
|
|
10363
|
-
|
|
10364
|
-
// ../git/dist/git-saga.js
|
|
10365
|
-
var GitSaga = class extends Saga {
|
|
10366
|
-
_git = null;
|
|
10367
|
-
get git() {
|
|
10368
|
-
if (!this._git) {
|
|
10369
|
-
throw new Error("git client accessed before execute() was called");
|
|
10370
|
-
}
|
|
10371
|
-
return this._git;
|
|
10372
|
-
}
|
|
10373
|
-
async execute(input) {
|
|
10374
|
-
const manager = getGitOperationManager();
|
|
10375
|
-
return manager.executeWrite(input.baseDir, async (git) => {
|
|
10376
|
-
this._git = git;
|
|
10377
|
-
return this.executeGitOperations(input);
|
|
10378
|
-
}, { signal: input.signal });
|
|
10127
|
+
async execute(input) {
|
|
10128
|
+
const manager = getGitOperationManager();
|
|
10129
|
+
return manager.executeWrite(input.baseDir, async (git) => {
|
|
10130
|
+
this._git = git;
|
|
10131
|
+
return this.executeGitOperations(input);
|
|
10132
|
+
}, { signal: input.signal });
|
|
10379
10133
|
}
|
|
10380
10134
|
};
|
|
10381
10135
|
|
|
@@ -10641,18 +10395,18 @@ var ApplySnapshotSaga = class extends Saga {
|
|
|
10641
10395
|
archivePath = null;
|
|
10642
10396
|
async execute(input) {
|
|
10643
10397
|
const { snapshot, repositoryPath, apiClient, taskId, runId } = input;
|
|
10644
|
-
const tmpDir =
|
|
10398
|
+
const tmpDir = join7(repositoryPath, ".posthog", "tmp");
|
|
10645
10399
|
if (!snapshot.archiveUrl) {
|
|
10646
10400
|
throw new Error("Cannot apply snapshot: no archive URL");
|
|
10647
10401
|
}
|
|
10648
10402
|
const archiveUrl = snapshot.archiveUrl;
|
|
10649
10403
|
await this.step({
|
|
10650
10404
|
name: "create_tmp_dir",
|
|
10651
|
-
execute: () =>
|
|
10405
|
+
execute: () => mkdir4(tmpDir, { recursive: true }),
|
|
10652
10406
|
rollback: async () => {
|
|
10653
10407
|
}
|
|
10654
10408
|
});
|
|
10655
|
-
const archivePath =
|
|
10409
|
+
const archivePath = join7(tmpDir, `${snapshot.treeHash}.tar.gz`);
|
|
10656
10410
|
this.archivePath = archivePath;
|
|
10657
10411
|
await this.step({
|
|
10658
10412
|
name: "download_archive",
|
|
@@ -10667,7 +10421,7 @@ var ApplySnapshotSaga = class extends Saga {
|
|
|
10667
10421
|
}
|
|
10668
10422
|
const base64Content = Buffer.from(arrayBuffer).toString("utf-8");
|
|
10669
10423
|
const binaryContent = Buffer.from(base64Content, "base64");
|
|
10670
|
-
await
|
|
10424
|
+
await writeFile4(archivePath, binaryContent);
|
|
10671
10425
|
},
|
|
10672
10426
|
rollback: async () => {
|
|
10673
10427
|
if (this.archivePath) {
|
|
@@ -10701,7 +10455,7 @@ var ApplySnapshotSaga = class extends Saga {
|
|
|
10701
10455
|
// src/sagas/capture-tree-saga.ts
|
|
10702
10456
|
import { existsSync as existsSync5 } from "fs";
|
|
10703
10457
|
import { readFile as readFile3, rm as rm4 } from "fs/promises";
|
|
10704
|
-
import { join as
|
|
10458
|
+
import { join as join8 } from "path";
|
|
10705
10459
|
var CaptureTreeSaga2 = class extends Saga {
|
|
10706
10460
|
sagaName = "CaptureTreeSaga";
|
|
10707
10461
|
async execute(input) {
|
|
@@ -10713,14 +10467,14 @@ var CaptureTreeSaga2 = class extends Saga {
|
|
|
10713
10467
|
taskId,
|
|
10714
10468
|
runId
|
|
10715
10469
|
} = input;
|
|
10716
|
-
const tmpDir =
|
|
10717
|
-
if (existsSync5(
|
|
10470
|
+
const tmpDir = join8(repositoryPath, ".posthog", "tmp");
|
|
10471
|
+
if (existsSync5(join8(repositoryPath, ".gitmodules"))) {
|
|
10718
10472
|
this.log.warn(
|
|
10719
10473
|
"Repository has submodules - snapshot may not capture submodule state"
|
|
10720
10474
|
);
|
|
10721
10475
|
}
|
|
10722
10476
|
const shouldArchive = !!apiClient;
|
|
10723
|
-
const archivePath = shouldArchive ?
|
|
10477
|
+
const archivePath = shouldArchive ? join8(tmpDir, `tree-${Date.now()}.tar.gz`) : void 0;
|
|
10724
10478
|
const gitCaptureSaga = new CaptureTreeSaga(this.log);
|
|
10725
10479
|
const captureResult = await gitCaptureSaga.run({
|
|
10726
10480
|
baseDir: repositoryPath,
|
|
@@ -10841,57 +10595,632 @@ var TreeTracker = class {
|
|
|
10841
10595
|
`Failed to capture tree at step '${result.failedStep}': ${result.error}`
|
|
10842
10596
|
);
|
|
10843
10597
|
}
|
|
10844
|
-
if (result.data.newTreeHash !== null) {
|
|
10845
|
-
this.lastTreeHash = result.data.newTreeHash;
|
|
10598
|
+
if (result.data.newTreeHash !== null) {
|
|
10599
|
+
this.lastTreeHash = result.data.newTreeHash;
|
|
10600
|
+
}
|
|
10601
|
+
return result.data.snapshot;
|
|
10602
|
+
}
|
|
10603
|
+
/**
|
|
10604
|
+
* Download and apply a tree snapshot.
|
|
10605
|
+
* Uses Saga pattern for atomic operation with rollback on failure.
|
|
10606
|
+
*/
|
|
10607
|
+
async applyTreeSnapshot(snapshot) {
|
|
10608
|
+
if (!this.apiClient) {
|
|
10609
|
+
throw new Error("Cannot apply snapshot: API client not configured");
|
|
10610
|
+
}
|
|
10611
|
+
if (!snapshot.archiveUrl) {
|
|
10612
|
+
this.logger.warn("Cannot apply snapshot: no archive URL", {
|
|
10613
|
+
treeHash: snapshot.treeHash,
|
|
10614
|
+
changes: snapshot.changes.length
|
|
10615
|
+
});
|
|
10616
|
+
throw new Error("Cannot apply snapshot: no archive URL");
|
|
10617
|
+
}
|
|
10618
|
+
const saga = new ApplySnapshotSaga(this.logger);
|
|
10619
|
+
const result = await saga.run({
|
|
10620
|
+
snapshot,
|
|
10621
|
+
repositoryPath: this.repositoryPath,
|
|
10622
|
+
apiClient: this.apiClient,
|
|
10623
|
+
taskId: this.taskId,
|
|
10624
|
+
runId: this.runId
|
|
10625
|
+
});
|
|
10626
|
+
if (!result.success) {
|
|
10627
|
+
this.logger.error("Failed to apply tree snapshot", {
|
|
10628
|
+
error: result.error,
|
|
10629
|
+
failedStep: result.failedStep,
|
|
10630
|
+
treeHash: snapshot.treeHash
|
|
10631
|
+
});
|
|
10632
|
+
throw new Error(
|
|
10633
|
+
`Failed to apply snapshot at step '${result.failedStep}': ${result.error}`
|
|
10634
|
+
);
|
|
10635
|
+
}
|
|
10636
|
+
this.lastTreeHash = result.data.treeHash;
|
|
10637
|
+
}
|
|
10638
|
+
/**
|
|
10639
|
+
* Get the last captured tree hash.
|
|
10640
|
+
*/
|
|
10641
|
+
getLastTreeHash() {
|
|
10642
|
+
return this.lastTreeHash;
|
|
10643
|
+
}
|
|
10644
|
+
/**
|
|
10645
|
+
* Set the last tree hash (used when resuming).
|
|
10646
|
+
*/
|
|
10647
|
+
setLastTreeHash(hash) {
|
|
10648
|
+
this.lastTreeHash = hash;
|
|
10649
|
+
}
|
|
10650
|
+
};
|
|
10651
|
+
|
|
10652
|
+
// src/sagas/resume-saga.ts
|
|
10653
|
+
var ResumeSaga = class extends Saga {
|
|
10654
|
+
sagaName = "ResumeSaga";
|
|
10655
|
+
async execute(input) {
|
|
10656
|
+
const { taskId, runId, repositoryPath, apiClient } = input;
|
|
10657
|
+
const logger = input.logger || new Logger({ debug: false, prefix: "[Resume]" });
|
|
10658
|
+
const taskRun = await this.readOnlyStep(
|
|
10659
|
+
"fetch_task_run",
|
|
10660
|
+
() => apiClient.getTaskRun(taskId, runId)
|
|
10661
|
+
);
|
|
10662
|
+
if (!taskRun.log_url) {
|
|
10663
|
+
this.log.info("No log URL found, starting fresh");
|
|
10664
|
+
return this.emptyResult();
|
|
10665
|
+
}
|
|
10666
|
+
const entries = await this.readOnlyStep(
|
|
10667
|
+
"fetch_logs",
|
|
10668
|
+
() => apiClient.fetchTaskRunLogs(taskRun)
|
|
10669
|
+
);
|
|
10670
|
+
if (entries.length === 0) {
|
|
10671
|
+
this.log.info("No log entries found, starting fresh");
|
|
10672
|
+
return this.emptyResult();
|
|
10673
|
+
}
|
|
10674
|
+
this.log.info("Fetched log entries", { count: entries.length });
|
|
10675
|
+
const latestSnapshot = await this.readOnlyStep(
|
|
10676
|
+
"find_snapshot",
|
|
10677
|
+
() => Promise.resolve(this.findLatestTreeSnapshot(entries))
|
|
10678
|
+
);
|
|
10679
|
+
let snapshotApplied = false;
|
|
10680
|
+
if (latestSnapshot?.archiveUrl && repositoryPath) {
|
|
10681
|
+
this.log.info("Found tree snapshot", {
|
|
10682
|
+
treeHash: latestSnapshot.treeHash,
|
|
10683
|
+
hasArchiveUrl: true,
|
|
10684
|
+
changes: latestSnapshot.changes?.length ?? 0,
|
|
10685
|
+
interrupted: latestSnapshot.interrupted
|
|
10686
|
+
});
|
|
10687
|
+
await this.step({
|
|
10688
|
+
name: "apply_snapshot",
|
|
10689
|
+
execute: async () => {
|
|
10690
|
+
const treeTracker = new TreeTracker({
|
|
10691
|
+
repositoryPath,
|
|
10692
|
+
taskId,
|
|
10693
|
+
runId,
|
|
10694
|
+
apiClient,
|
|
10695
|
+
logger: logger.child("TreeTracker")
|
|
10696
|
+
});
|
|
10697
|
+
try {
|
|
10698
|
+
await treeTracker.applyTreeSnapshot(latestSnapshot);
|
|
10699
|
+
treeTracker.setLastTreeHash(latestSnapshot.treeHash);
|
|
10700
|
+
snapshotApplied = true;
|
|
10701
|
+
this.log.info("Tree snapshot applied successfully", {
|
|
10702
|
+
treeHash: latestSnapshot.treeHash
|
|
10703
|
+
});
|
|
10704
|
+
} catch (error) {
|
|
10705
|
+
this.log.warn(
|
|
10706
|
+
"Failed to apply tree snapshot, continuing without it",
|
|
10707
|
+
{
|
|
10708
|
+
error: error instanceof Error ? error.message : String(error),
|
|
10709
|
+
treeHash: latestSnapshot.treeHash
|
|
10710
|
+
}
|
|
10711
|
+
);
|
|
10712
|
+
}
|
|
10713
|
+
},
|
|
10714
|
+
rollback: async () => {
|
|
10715
|
+
}
|
|
10716
|
+
});
|
|
10717
|
+
} else if (latestSnapshot?.archiveUrl && !repositoryPath) {
|
|
10718
|
+
this.log.warn(
|
|
10719
|
+
"Snapshot found but no repositoryPath configured - files cannot be restored",
|
|
10720
|
+
{
|
|
10721
|
+
treeHash: latestSnapshot.treeHash,
|
|
10722
|
+
changes: latestSnapshot.changes?.length ?? 0
|
|
10723
|
+
}
|
|
10724
|
+
);
|
|
10725
|
+
} else if (latestSnapshot) {
|
|
10726
|
+
this.log.warn(
|
|
10727
|
+
"Snapshot found but has no archive URL - files cannot be restored",
|
|
10728
|
+
{
|
|
10729
|
+
treeHash: latestSnapshot.treeHash,
|
|
10730
|
+
changes: latestSnapshot.changes?.length ?? 0
|
|
10731
|
+
}
|
|
10732
|
+
);
|
|
10733
|
+
}
|
|
10734
|
+
const conversation = await this.readOnlyStep(
|
|
10735
|
+
"rebuild_conversation",
|
|
10736
|
+
() => Promise.resolve(this.rebuildConversation(entries))
|
|
10737
|
+
);
|
|
10738
|
+
const lastDevice = await this.readOnlyStep(
|
|
10739
|
+
"find_device",
|
|
10740
|
+
() => Promise.resolve(this.findLastDeviceInfo(entries))
|
|
10741
|
+
);
|
|
10742
|
+
this.log.info("Resume state rebuilt", {
|
|
10743
|
+
turns: conversation.length,
|
|
10744
|
+
hasSnapshot: !!latestSnapshot,
|
|
10745
|
+
snapshotApplied,
|
|
10746
|
+
interrupted: latestSnapshot?.interrupted ?? false
|
|
10747
|
+
});
|
|
10748
|
+
return {
|
|
10749
|
+
conversation,
|
|
10750
|
+
latestSnapshot,
|
|
10751
|
+
snapshotApplied,
|
|
10752
|
+
interrupted: latestSnapshot?.interrupted ?? false,
|
|
10753
|
+
lastDevice,
|
|
10754
|
+
logEntryCount: entries.length
|
|
10755
|
+
};
|
|
10756
|
+
}
|
|
10757
|
+
emptyResult() {
|
|
10758
|
+
return {
|
|
10759
|
+
conversation: [],
|
|
10760
|
+
latestSnapshot: null,
|
|
10761
|
+
snapshotApplied: false,
|
|
10762
|
+
interrupted: false,
|
|
10763
|
+
logEntryCount: 0
|
|
10764
|
+
};
|
|
10765
|
+
}
|
|
10766
|
+
findLatestTreeSnapshot(entries) {
|
|
10767
|
+
const sdkPrefixedMethod = `_${POSTHOG_NOTIFICATIONS.TREE_SNAPSHOT}`;
|
|
10768
|
+
for (let i = entries.length - 1; i >= 0; i--) {
|
|
10769
|
+
const entry = entries[i];
|
|
10770
|
+
const method = entry.notification?.method;
|
|
10771
|
+
if (method === sdkPrefixedMethod || method === POSTHOG_NOTIFICATIONS.TREE_SNAPSHOT) {
|
|
10772
|
+
const params = entry.notification.params;
|
|
10773
|
+
if (params?.treeHash) {
|
|
10774
|
+
return params;
|
|
10775
|
+
}
|
|
10776
|
+
}
|
|
10777
|
+
}
|
|
10778
|
+
return null;
|
|
10779
|
+
}
|
|
10780
|
+
findLastDeviceInfo(entries) {
|
|
10781
|
+
for (let i = entries.length - 1; i >= 0; i--) {
|
|
10782
|
+
const entry = entries[i];
|
|
10783
|
+
const params = entry.notification?.params;
|
|
10784
|
+
if (params?.device) {
|
|
10785
|
+
return params.device;
|
|
10786
|
+
}
|
|
10787
|
+
}
|
|
10788
|
+
return void 0;
|
|
10789
|
+
}
|
|
10790
|
+
rebuildConversation(entries) {
|
|
10791
|
+
const turns = [];
|
|
10792
|
+
let currentAssistantContent = [];
|
|
10793
|
+
let currentToolCalls = [];
|
|
10794
|
+
for (const entry of entries) {
|
|
10795
|
+
const method = entry.notification?.method;
|
|
10796
|
+
const params = entry.notification?.params;
|
|
10797
|
+
if (method === "session/update" && params?.update) {
|
|
10798
|
+
const update = params.update;
|
|
10799
|
+
const sessionUpdate = update.sessionUpdate;
|
|
10800
|
+
switch (sessionUpdate) {
|
|
10801
|
+
case "user_message":
|
|
10802
|
+
case "user_message_chunk": {
|
|
10803
|
+
if (currentAssistantContent.length > 0 || currentToolCalls.length > 0) {
|
|
10804
|
+
turns.push({
|
|
10805
|
+
role: "assistant",
|
|
10806
|
+
content: currentAssistantContent,
|
|
10807
|
+
toolCalls: currentToolCalls.length > 0 ? currentToolCalls : void 0
|
|
10808
|
+
});
|
|
10809
|
+
currentAssistantContent = [];
|
|
10810
|
+
currentToolCalls = [];
|
|
10811
|
+
}
|
|
10812
|
+
const content = update.content;
|
|
10813
|
+
const contentArray = Array.isArray(content) ? content : [content];
|
|
10814
|
+
turns.push({
|
|
10815
|
+
role: "user",
|
|
10816
|
+
content: contentArray
|
|
10817
|
+
});
|
|
10818
|
+
break;
|
|
10819
|
+
}
|
|
10820
|
+
case "agent_message": {
|
|
10821
|
+
const content = update.content;
|
|
10822
|
+
if (content) {
|
|
10823
|
+
if (content.type === "text" && currentAssistantContent.length > 0 && currentAssistantContent[currentAssistantContent.length - 1].type === "text") {
|
|
10824
|
+
const lastBlock = currentAssistantContent[currentAssistantContent.length - 1];
|
|
10825
|
+
lastBlock.text += content.text;
|
|
10826
|
+
} else {
|
|
10827
|
+
currentAssistantContent.push(content);
|
|
10828
|
+
}
|
|
10829
|
+
}
|
|
10830
|
+
break;
|
|
10831
|
+
}
|
|
10832
|
+
case "agent_message_chunk": {
|
|
10833
|
+
const content = update.content;
|
|
10834
|
+
if (content) {
|
|
10835
|
+
if (content.type === "text" && currentAssistantContent.length > 0 && currentAssistantContent[currentAssistantContent.length - 1].type === "text") {
|
|
10836
|
+
const lastBlock = currentAssistantContent[currentAssistantContent.length - 1];
|
|
10837
|
+
lastBlock.text += content.text;
|
|
10838
|
+
} else {
|
|
10839
|
+
currentAssistantContent.push(content);
|
|
10840
|
+
}
|
|
10841
|
+
}
|
|
10842
|
+
break;
|
|
10843
|
+
}
|
|
10844
|
+
case "tool_call":
|
|
10845
|
+
case "tool_call_update": {
|
|
10846
|
+
const meta = update._meta?.claudeCode;
|
|
10847
|
+
if (meta) {
|
|
10848
|
+
const toolCallId = meta.toolCallId;
|
|
10849
|
+
const toolName = meta.toolName;
|
|
10850
|
+
const toolInput = meta.toolInput;
|
|
10851
|
+
const toolResponse = meta.toolResponse;
|
|
10852
|
+
if (toolCallId && toolName) {
|
|
10853
|
+
let toolCall = currentToolCalls.find(
|
|
10854
|
+
(tc) => tc.toolCallId === toolCallId
|
|
10855
|
+
);
|
|
10856
|
+
if (!toolCall) {
|
|
10857
|
+
toolCall = {
|
|
10858
|
+
toolCallId,
|
|
10859
|
+
toolName,
|
|
10860
|
+
input: toolInput
|
|
10861
|
+
};
|
|
10862
|
+
currentToolCalls.push(toolCall);
|
|
10863
|
+
}
|
|
10864
|
+
if (toolResponse !== void 0) {
|
|
10865
|
+
toolCall.result = toolResponse;
|
|
10866
|
+
}
|
|
10867
|
+
}
|
|
10868
|
+
}
|
|
10869
|
+
break;
|
|
10870
|
+
}
|
|
10871
|
+
case "tool_result": {
|
|
10872
|
+
const meta = update._meta?.claudeCode;
|
|
10873
|
+
if (meta) {
|
|
10874
|
+
const toolCallId = meta.toolCallId;
|
|
10875
|
+
const toolResponse = meta.toolResponse;
|
|
10876
|
+
if (toolCallId) {
|
|
10877
|
+
const toolCall = currentToolCalls.find(
|
|
10878
|
+
(tc) => tc.toolCallId === toolCallId
|
|
10879
|
+
);
|
|
10880
|
+
if (toolCall && toolResponse !== void 0) {
|
|
10881
|
+
toolCall.result = toolResponse;
|
|
10882
|
+
}
|
|
10883
|
+
}
|
|
10884
|
+
}
|
|
10885
|
+
break;
|
|
10886
|
+
}
|
|
10887
|
+
}
|
|
10888
|
+
}
|
|
10889
|
+
}
|
|
10890
|
+
if (currentAssistantContent.length > 0 || currentToolCalls.length > 0) {
|
|
10891
|
+
turns.push({
|
|
10892
|
+
role: "assistant",
|
|
10893
|
+
content: currentAssistantContent,
|
|
10894
|
+
toolCalls: currentToolCalls.length > 0 ? currentToolCalls : void 0
|
|
10895
|
+
});
|
|
10896
|
+
}
|
|
10897
|
+
return turns;
|
|
10898
|
+
}
|
|
10899
|
+
};
|
|
10900
|
+
|
|
10901
|
+
// src/resume.ts
|
|
10902
|
+
async function resumeFromLog(config) {
|
|
10903
|
+
const logger = config.logger || new Logger({ debug: false, prefix: "[Resume]" });
|
|
10904
|
+
logger.info("Resuming from log", {
|
|
10905
|
+
taskId: config.taskId,
|
|
10906
|
+
runId: config.runId
|
|
10907
|
+
});
|
|
10908
|
+
const saga = new ResumeSaga(logger);
|
|
10909
|
+
const result = await saga.run({
|
|
10910
|
+
taskId: config.taskId,
|
|
10911
|
+
runId: config.runId,
|
|
10912
|
+
repositoryPath: config.repositoryPath,
|
|
10913
|
+
apiClient: config.apiClient,
|
|
10914
|
+
logger
|
|
10915
|
+
});
|
|
10916
|
+
if (!result.success) {
|
|
10917
|
+
logger.error("Failed to resume from log", {
|
|
10918
|
+
error: result.error,
|
|
10919
|
+
failedStep: result.failedStep
|
|
10920
|
+
});
|
|
10921
|
+
throw new Error(
|
|
10922
|
+
`Failed to resume at step '${result.failedStep}': ${result.error}`
|
|
10923
|
+
);
|
|
10924
|
+
}
|
|
10925
|
+
return {
|
|
10926
|
+
conversation: result.data.conversation,
|
|
10927
|
+
latestSnapshot: result.data.latestSnapshot,
|
|
10928
|
+
snapshotApplied: result.data.snapshotApplied,
|
|
10929
|
+
interrupted: result.data.interrupted,
|
|
10930
|
+
lastDevice: result.data.lastDevice,
|
|
10931
|
+
logEntryCount: result.data.logEntryCount
|
|
10932
|
+
};
|
|
10933
|
+
}
|
|
10934
|
+
|
|
10935
|
+
// src/session-log-writer.ts
|
|
10936
|
+
import fs8 from "fs";
|
|
10937
|
+
import fsp from "fs/promises";
|
|
10938
|
+
import path10 from "path";
|
|
10939
|
+
var SessionLogWriter = class _SessionLogWriter {
|
|
10940
|
+
static FLUSH_DEBOUNCE_MS = 500;
|
|
10941
|
+
static FLUSH_MAX_INTERVAL_MS = 5e3;
|
|
10942
|
+
static MAX_FLUSH_RETRIES = 10;
|
|
10943
|
+
static MAX_RETRY_DELAY_MS = 3e4;
|
|
10944
|
+
static SESSIONS_MAX_AGE_MS = 30 * 24 * 60 * 60 * 1e3;
|
|
10945
|
+
posthogAPI;
|
|
10946
|
+
pendingEntries = /* @__PURE__ */ new Map();
|
|
10947
|
+
flushTimeouts = /* @__PURE__ */ new Map();
|
|
10948
|
+
lastFlushAttemptTime = /* @__PURE__ */ new Map();
|
|
10949
|
+
retryCounts = /* @__PURE__ */ new Map();
|
|
10950
|
+
sessions = /* @__PURE__ */ new Map();
|
|
10951
|
+
logger;
|
|
10952
|
+
localCachePath;
|
|
10953
|
+
constructor(options = {}) {
|
|
10954
|
+
this.posthogAPI = options.posthogAPI;
|
|
10955
|
+
this.localCachePath = options.localCachePath;
|
|
10956
|
+
this.logger = options.logger ?? new Logger({ debug: false, prefix: "[SessionLogWriter]" });
|
|
10957
|
+
}
|
|
10958
|
+
async flushAll() {
|
|
10959
|
+
const sessionIds = [...this.sessions.keys()];
|
|
10960
|
+
const flushPromises = [];
|
|
10961
|
+
for (const sessionId of sessionIds) {
|
|
10962
|
+
flushPromises.push(this.flush(sessionId));
|
|
10963
|
+
}
|
|
10964
|
+
await Promise.all(flushPromises);
|
|
10965
|
+
}
|
|
10966
|
+
register(sessionId, context) {
|
|
10967
|
+
if (this.sessions.has(sessionId)) {
|
|
10968
|
+
return;
|
|
10969
|
+
}
|
|
10970
|
+
this.logger.info("Session registered", {
|
|
10971
|
+
taskId: context.taskId,
|
|
10972
|
+
runId: context.runId
|
|
10973
|
+
});
|
|
10974
|
+
this.sessions.set(sessionId, { context });
|
|
10975
|
+
this.lastFlushAttemptTime.set(sessionId, Date.now());
|
|
10976
|
+
if (this.localCachePath) {
|
|
10977
|
+
const sessionDir = path10.join(
|
|
10978
|
+
this.localCachePath,
|
|
10979
|
+
"sessions",
|
|
10980
|
+
context.runId
|
|
10981
|
+
);
|
|
10982
|
+
try {
|
|
10983
|
+
fs8.mkdirSync(sessionDir, { recursive: true });
|
|
10984
|
+
} catch (error) {
|
|
10985
|
+
this.logger.warn("Failed to create local cache directory", {
|
|
10986
|
+
sessionDir,
|
|
10987
|
+
error
|
|
10988
|
+
});
|
|
10989
|
+
}
|
|
10990
|
+
}
|
|
10991
|
+
}
|
|
10992
|
+
isRegistered(sessionId) {
|
|
10993
|
+
return this.sessions.has(sessionId);
|
|
10994
|
+
}
|
|
10995
|
+
appendRawLine(sessionId, line) {
|
|
10996
|
+
const session = this.sessions.get(sessionId);
|
|
10997
|
+
if (!session) {
|
|
10998
|
+
this.logger.warn("appendRawLine called for unregistered session", {
|
|
10999
|
+
sessionId
|
|
11000
|
+
});
|
|
11001
|
+
return;
|
|
11002
|
+
}
|
|
11003
|
+
try {
|
|
11004
|
+
const message = JSON.parse(line);
|
|
11005
|
+
const timestamp = (/* @__PURE__ */ new Date()).toISOString();
|
|
11006
|
+
if (this.isAgentMessageChunk(message)) {
|
|
11007
|
+
const text2 = this.extractChunkText(message);
|
|
11008
|
+
if (text2) {
|
|
11009
|
+
if (!session.chunkBuffer) {
|
|
11010
|
+
session.chunkBuffer = { text: text2, firstTimestamp: timestamp };
|
|
11011
|
+
} else {
|
|
11012
|
+
session.chunkBuffer.text += text2;
|
|
11013
|
+
}
|
|
11014
|
+
}
|
|
11015
|
+
return;
|
|
11016
|
+
}
|
|
11017
|
+
this.emitCoalescedMessage(sessionId, session);
|
|
11018
|
+
const nonChunkAgentText = this.extractAgentMessageText(message);
|
|
11019
|
+
if (nonChunkAgentText) {
|
|
11020
|
+
session.lastAgentMessage = nonChunkAgentText;
|
|
11021
|
+
}
|
|
11022
|
+
const entry = {
|
|
11023
|
+
type: "notification",
|
|
11024
|
+
timestamp,
|
|
11025
|
+
notification: message
|
|
11026
|
+
};
|
|
11027
|
+
this.writeToLocalCache(sessionId, entry);
|
|
11028
|
+
if (this.posthogAPI) {
|
|
11029
|
+
const pending = this.pendingEntries.get(sessionId) ?? [];
|
|
11030
|
+
pending.push(entry);
|
|
11031
|
+
this.pendingEntries.set(sessionId, pending);
|
|
11032
|
+
this.scheduleFlush(sessionId);
|
|
11033
|
+
}
|
|
11034
|
+
} catch {
|
|
11035
|
+
this.logger.warn("Failed to parse raw line for persistence", {
|
|
11036
|
+
taskId: session.context.taskId,
|
|
11037
|
+
runId: session.context.runId,
|
|
11038
|
+
lineLength: line.length
|
|
11039
|
+
});
|
|
11040
|
+
}
|
|
11041
|
+
}
|
|
11042
|
+
async flush(sessionId) {
|
|
11043
|
+
const session = this.sessions.get(sessionId);
|
|
11044
|
+
if (!session) {
|
|
11045
|
+
this.logger.warn("flush: no session found", { sessionId });
|
|
11046
|
+
return;
|
|
11047
|
+
}
|
|
11048
|
+
this.emitCoalescedMessage(sessionId, session);
|
|
11049
|
+
const pending = this.pendingEntries.get(sessionId);
|
|
11050
|
+
if (!this.posthogAPI || !pending?.length) {
|
|
11051
|
+
return;
|
|
11052
|
+
}
|
|
11053
|
+
this.pendingEntries.delete(sessionId);
|
|
11054
|
+
const timeout = this.flushTimeouts.get(sessionId);
|
|
11055
|
+
if (timeout) {
|
|
11056
|
+
clearTimeout(timeout);
|
|
11057
|
+
this.flushTimeouts.delete(sessionId);
|
|
11058
|
+
}
|
|
11059
|
+
this.lastFlushAttemptTime.set(sessionId, Date.now());
|
|
11060
|
+
try {
|
|
11061
|
+
await this.posthogAPI.appendTaskRunLog(
|
|
11062
|
+
session.context.taskId,
|
|
11063
|
+
session.context.runId,
|
|
11064
|
+
pending
|
|
11065
|
+
);
|
|
11066
|
+
this.retryCounts.set(sessionId, 0);
|
|
11067
|
+
} catch (error) {
|
|
11068
|
+
const retryCount = (this.retryCounts.get(sessionId) ?? 0) + 1;
|
|
11069
|
+
this.retryCounts.set(sessionId, retryCount);
|
|
11070
|
+
if (retryCount >= _SessionLogWriter.MAX_FLUSH_RETRIES) {
|
|
11071
|
+
this.logger.error(
|
|
11072
|
+
`Dropping ${pending.length} session log entries after ${retryCount} failed flush attempts`,
|
|
11073
|
+
{
|
|
11074
|
+
taskId: session.context.taskId,
|
|
11075
|
+
runId: session.context.runId,
|
|
11076
|
+
error
|
|
11077
|
+
}
|
|
11078
|
+
);
|
|
11079
|
+
this.retryCounts.set(sessionId, 0);
|
|
11080
|
+
} else {
|
|
11081
|
+
if (retryCount === 1) {
|
|
11082
|
+
this.logger.warn(
|
|
11083
|
+
`Failed to persist session logs, will retry (up to ${_SessionLogWriter.MAX_FLUSH_RETRIES} attempts)`,
|
|
11084
|
+
{
|
|
11085
|
+
taskId: session.context.taskId,
|
|
11086
|
+
runId: session.context.runId,
|
|
11087
|
+
error: error instanceof Error ? error.message : String(error)
|
|
11088
|
+
}
|
|
11089
|
+
);
|
|
11090
|
+
}
|
|
11091
|
+
const currentPending = this.pendingEntries.get(sessionId) ?? [];
|
|
11092
|
+
this.pendingEntries.set(sessionId, [...pending, ...currentPending]);
|
|
11093
|
+
this.scheduleFlush(sessionId);
|
|
11094
|
+
}
|
|
11095
|
+
}
|
|
11096
|
+
}
|
|
11097
|
+
isAgentMessageChunk(message) {
|
|
11098
|
+
if (message.method !== "session/update") return false;
|
|
11099
|
+
const params = message.params;
|
|
11100
|
+
const update = params?.update;
|
|
11101
|
+
return update?.sessionUpdate === "agent_message_chunk";
|
|
11102
|
+
}
|
|
11103
|
+
extractChunkText(message) {
|
|
11104
|
+
const params = message.params;
|
|
11105
|
+
const update = params?.update;
|
|
11106
|
+
const content = update?.content;
|
|
11107
|
+
if (content?.type === "text" && content.text) {
|
|
11108
|
+
return content.text;
|
|
11109
|
+
}
|
|
11110
|
+
return "";
|
|
11111
|
+
}
|
|
11112
|
+
emitCoalescedMessage(sessionId, session) {
|
|
11113
|
+
if (!session.chunkBuffer) return;
|
|
11114
|
+
const { text: text2, firstTimestamp } = session.chunkBuffer;
|
|
11115
|
+
session.chunkBuffer = void 0;
|
|
11116
|
+
session.lastAgentMessage = text2;
|
|
11117
|
+
const entry = {
|
|
11118
|
+
type: "notification",
|
|
11119
|
+
timestamp: firstTimestamp,
|
|
11120
|
+
notification: {
|
|
11121
|
+
jsonrpc: "2.0",
|
|
11122
|
+
method: "session/update",
|
|
11123
|
+
params: {
|
|
11124
|
+
update: {
|
|
11125
|
+
sessionUpdate: "agent_message",
|
|
11126
|
+
content: { type: "text", text: text2 }
|
|
11127
|
+
}
|
|
11128
|
+
}
|
|
11129
|
+
}
|
|
11130
|
+
};
|
|
11131
|
+
this.writeToLocalCache(sessionId, entry);
|
|
11132
|
+
if (this.posthogAPI) {
|
|
11133
|
+
const pending = this.pendingEntries.get(sessionId) ?? [];
|
|
11134
|
+
pending.push(entry);
|
|
11135
|
+
this.pendingEntries.set(sessionId, pending);
|
|
11136
|
+
this.scheduleFlush(sessionId);
|
|
10846
11137
|
}
|
|
10847
|
-
return result.data.snapshot;
|
|
10848
11138
|
}
|
|
10849
|
-
|
|
10850
|
-
|
|
10851
|
-
|
|
10852
|
-
|
|
10853
|
-
|
|
10854
|
-
|
|
10855
|
-
throw new Error("Cannot apply snapshot: API client not configured");
|
|
11139
|
+
getLastAgentMessage(sessionId) {
|
|
11140
|
+
return this.sessions.get(sessionId)?.lastAgentMessage;
|
|
11141
|
+
}
|
|
11142
|
+
extractAgentMessageText(message) {
|
|
11143
|
+
if (message.method !== "session/update") {
|
|
11144
|
+
return null;
|
|
10856
11145
|
}
|
|
10857
|
-
|
|
10858
|
-
|
|
10859
|
-
|
|
10860
|
-
|
|
10861
|
-
});
|
|
10862
|
-
throw new Error("Cannot apply snapshot: no archive URL");
|
|
11146
|
+
const params = message.params;
|
|
11147
|
+
const update = params?.update;
|
|
11148
|
+
if (update?.sessionUpdate !== "agent_message") {
|
|
11149
|
+
return null;
|
|
10863
11150
|
}
|
|
10864
|
-
const
|
|
10865
|
-
|
|
10866
|
-
|
|
10867
|
-
|
|
10868
|
-
|
|
10869
|
-
|
|
10870
|
-
|
|
10871
|
-
|
|
10872
|
-
|
|
10873
|
-
|
|
10874
|
-
|
|
10875
|
-
|
|
10876
|
-
|
|
10877
|
-
|
|
10878
|
-
|
|
10879
|
-
|
|
11151
|
+
const content = update.content;
|
|
11152
|
+
if (content?.type === "text" && typeof content.text === "string") {
|
|
11153
|
+
const trimmed2 = content.text.trim();
|
|
11154
|
+
return trimmed2.length > 0 ? trimmed2 : null;
|
|
11155
|
+
}
|
|
11156
|
+
if (typeof update.message === "string") {
|
|
11157
|
+
const trimmed2 = update.message.trim();
|
|
11158
|
+
return trimmed2.length > 0 ? trimmed2 : null;
|
|
11159
|
+
}
|
|
11160
|
+
return null;
|
|
11161
|
+
}
|
|
11162
|
+
scheduleFlush(sessionId) {
|
|
11163
|
+
const existing = this.flushTimeouts.get(sessionId);
|
|
11164
|
+
if (existing) clearTimeout(existing);
|
|
11165
|
+
const retryCount = this.retryCounts.get(sessionId) ?? 0;
|
|
11166
|
+
const lastAttempt = this.lastFlushAttemptTime.get(sessionId) ?? 0;
|
|
11167
|
+
const elapsed = Date.now() - lastAttempt;
|
|
11168
|
+
let delay3;
|
|
11169
|
+
if (retryCount > 0) {
|
|
11170
|
+
delay3 = Math.min(
|
|
11171
|
+
_SessionLogWriter.FLUSH_DEBOUNCE_MS * 2 ** retryCount,
|
|
11172
|
+
_SessionLogWriter.MAX_RETRY_DELAY_MS
|
|
10880
11173
|
);
|
|
11174
|
+
} else if (elapsed >= _SessionLogWriter.FLUSH_MAX_INTERVAL_MS) {
|
|
11175
|
+
delay3 = 0;
|
|
11176
|
+
} else {
|
|
11177
|
+
delay3 = _SessionLogWriter.FLUSH_DEBOUNCE_MS;
|
|
10881
11178
|
}
|
|
10882
|
-
|
|
11179
|
+
const timeout = setTimeout(() => this.flush(sessionId), delay3);
|
|
11180
|
+
this.flushTimeouts.set(sessionId, timeout);
|
|
10883
11181
|
}
|
|
10884
|
-
|
|
10885
|
-
|
|
10886
|
-
|
|
10887
|
-
|
|
10888
|
-
|
|
11182
|
+
writeToLocalCache(sessionId, entry) {
|
|
11183
|
+
if (!this.localCachePath) return;
|
|
11184
|
+
const session = this.sessions.get(sessionId);
|
|
11185
|
+
if (!session) return;
|
|
11186
|
+
const logPath = path10.join(
|
|
11187
|
+
this.localCachePath,
|
|
11188
|
+
"sessions",
|
|
11189
|
+
session.context.runId,
|
|
11190
|
+
"logs.ndjson"
|
|
11191
|
+
);
|
|
11192
|
+
try {
|
|
11193
|
+
fs8.appendFileSync(logPath, `${JSON.stringify(entry)}
|
|
11194
|
+
`);
|
|
11195
|
+
} catch (error) {
|
|
11196
|
+
this.logger.warn("Failed to write to local cache", {
|
|
11197
|
+
taskId: session.context.taskId,
|
|
11198
|
+
runId: session.context.runId,
|
|
11199
|
+
logPath,
|
|
11200
|
+
error
|
|
11201
|
+
});
|
|
11202
|
+
}
|
|
10889
11203
|
}
|
|
10890
|
-
|
|
10891
|
-
|
|
10892
|
-
|
|
10893
|
-
|
|
10894
|
-
|
|
11204
|
+
static async cleanupOldSessions(localCachePath) {
|
|
11205
|
+
const sessionsDir = path10.join(localCachePath, "sessions");
|
|
11206
|
+
let deleted = 0;
|
|
11207
|
+
try {
|
|
11208
|
+
const entries = await fsp.readdir(sessionsDir);
|
|
11209
|
+
const now = Date.now();
|
|
11210
|
+
for (const entry of entries) {
|
|
11211
|
+
const entryPath = path10.join(sessionsDir, entry);
|
|
11212
|
+
try {
|
|
11213
|
+
const stats = await fsp.stat(entryPath);
|
|
11214
|
+
if (stats.isDirectory() && now - stats.birthtimeMs > _SessionLogWriter.SESSIONS_MAX_AGE_MS) {
|
|
11215
|
+
await fsp.rm(entryPath, { recursive: true, force: true });
|
|
11216
|
+
deleted++;
|
|
11217
|
+
}
|
|
11218
|
+
} catch {
|
|
11219
|
+
}
|
|
11220
|
+
}
|
|
11221
|
+
} catch {
|
|
11222
|
+
}
|
|
11223
|
+
return deleted;
|
|
10895
11224
|
}
|
|
10896
11225
|
};
|
|
10897
11226
|
|
|
@@ -11104,7 +11433,7 @@ function createTappedWritableStream2(underlying, onMessage, logger) {
|
|
|
11104
11433
|
}
|
|
11105
11434
|
});
|
|
11106
11435
|
}
|
|
11107
|
-
var AgentServer = class {
|
|
11436
|
+
var AgentServer = class _AgentServer {
|
|
11108
11437
|
config;
|
|
11109
11438
|
logger;
|
|
11110
11439
|
server = null;
|
|
@@ -11113,6 +11442,7 @@ var AgentServer = class {
|
|
|
11113
11442
|
posthogAPI;
|
|
11114
11443
|
questionRelayedToSlack = false;
|
|
11115
11444
|
detectedPrUrl = null;
|
|
11445
|
+
resumeState = null;
|
|
11116
11446
|
emitConsoleLog = (level, _scope, message, data) => {
|
|
11117
11447
|
if (!this.session) return;
|
|
11118
11448
|
const formatted = data !== void 0 ? `${message} ${JSON.stringify(data)}` : message;
|
|
@@ -11295,6 +11625,32 @@ var AgentServer = class {
|
|
|
11295
11625
|
async autoInitializeSession() {
|
|
11296
11626
|
const { taskId, runId, mode, projectId } = this.config;
|
|
11297
11627
|
this.logger.info("Auto-initializing session", { taskId, runId, mode });
|
|
11628
|
+
const resumeRunId = process.env.POSTHOG_RESUME_RUN_ID;
|
|
11629
|
+
if (resumeRunId) {
|
|
11630
|
+
this.logger.info("Resuming from previous run", {
|
|
11631
|
+
resumeRunId,
|
|
11632
|
+
currentRunId: runId
|
|
11633
|
+
});
|
|
11634
|
+
try {
|
|
11635
|
+
this.resumeState = await resumeFromLog({
|
|
11636
|
+
taskId,
|
|
11637
|
+
runId: resumeRunId,
|
|
11638
|
+
repositoryPath: this.config.repositoryPath,
|
|
11639
|
+
apiClient: this.posthogAPI,
|
|
11640
|
+
logger: new Logger({ debug: true, prefix: "[Resume]" })
|
|
11641
|
+
});
|
|
11642
|
+
this.logger.info("Resume state loaded", {
|
|
11643
|
+
conversationTurns: this.resumeState.conversation.length,
|
|
11644
|
+
snapshotApplied: this.resumeState.snapshotApplied,
|
|
11645
|
+
logEntries: this.resumeState.logEntryCount
|
|
11646
|
+
});
|
|
11647
|
+
} catch (error) {
|
|
11648
|
+
this.logger.warn("Failed to load resume state, starting fresh", {
|
|
11649
|
+
error
|
|
11650
|
+
});
|
|
11651
|
+
this.resumeState = null;
|
|
11652
|
+
}
|
|
11653
|
+
}
|
|
11298
11654
|
const payload = {
|
|
11299
11655
|
task_id: taskId,
|
|
11300
11656
|
run_id: runId,
|
|
@@ -11359,6 +11715,7 @@ You MUST NOT create a new branch, close the existing PR, or create a new PR.`
|
|
|
11359
11715
|
}
|
|
11360
11716
|
}
|
|
11361
11717
|
});
|
|
11718
|
+
this.broadcastTurnComplete(result.stopReason);
|
|
11362
11719
|
return { stopReason: result.stopReason };
|
|
11363
11720
|
}
|
|
11364
11721
|
case POSTHOG_NOTIFICATIONS.CANCEL:
|
|
@@ -11394,18 +11751,19 @@ You MUST NOT create a new branch, close the existing PR, or create a new PR.`
|
|
|
11394
11751
|
name: process.env.HOSTNAME || "cloud-sandbox"
|
|
11395
11752
|
};
|
|
11396
11753
|
this.configureEnvironment();
|
|
11397
|
-
const treeTracker = this.config.repositoryPath ? new TreeTracker({
|
|
11398
|
-
repositoryPath: this.config.repositoryPath,
|
|
11399
|
-
taskId: payload.task_id,
|
|
11400
|
-
runId: payload.run_id,
|
|
11401
|
-
logger: new Logger({ debug: true, prefix: "[TreeTracker]" })
|
|
11402
|
-
}) : null;
|
|
11403
11754
|
const posthogAPI = new PostHogAPIClient({
|
|
11404
11755
|
apiUrl: this.config.apiUrl,
|
|
11405
11756
|
projectId: this.config.projectId,
|
|
11406
11757
|
getApiKey: () => this.config.apiKey,
|
|
11407
11758
|
userAgent: `posthog/cloud.hog.dev; version: ${this.config.version ?? package_default.version}`
|
|
11408
11759
|
});
|
|
11760
|
+
const treeTracker = this.config.repositoryPath ? new TreeTracker({
|
|
11761
|
+
repositoryPath: this.config.repositoryPath,
|
|
11762
|
+
taskId: payload.task_id,
|
|
11763
|
+
runId: payload.run_id,
|
|
11764
|
+
apiClient: posthogAPI,
|
|
11765
|
+
logger: new Logger({ debug: true, prefix: "[TreeTracker]" })
|
|
11766
|
+
}) : null;
|
|
11409
11767
|
const logWriter = new SessionLogWriter({
|
|
11410
11768
|
posthogAPI,
|
|
11411
11769
|
logger: new Logger({ debug: true, prefix: "[SessionLogWriter]" })
|
|
@@ -11500,26 +11858,55 @@ You MUST NOT create a new branch, close the existing PR, or create a new PR.`
|
|
|
11500
11858
|
}
|
|
11501
11859
|
async sendInitialTaskMessage(payload, prefetchedRun) {
|
|
11502
11860
|
if (!this.session) return;
|
|
11503
|
-
|
|
11504
|
-
|
|
11505
|
-
|
|
11506
|
-
|
|
11861
|
+
let taskRun = prefetchedRun ?? null;
|
|
11862
|
+
if (!taskRun) {
|
|
11863
|
+
try {
|
|
11864
|
+
taskRun = await this.posthogAPI.getTaskRun(
|
|
11865
|
+
payload.task_id,
|
|
11866
|
+
payload.run_id
|
|
11867
|
+
);
|
|
11868
|
+
} catch (error) {
|
|
11869
|
+
this.logger.warn("Failed to fetch task run", {
|
|
11870
|
+
taskId: payload.task_id,
|
|
11871
|
+
runId: payload.run_id,
|
|
11872
|
+
error
|
|
11873
|
+
});
|
|
11874
|
+
}
|
|
11875
|
+
}
|
|
11876
|
+
if (!this.resumeState) {
|
|
11877
|
+
const resumeRunId = this.getResumeRunId(taskRun);
|
|
11878
|
+
if (resumeRunId) {
|
|
11879
|
+
this.logger.info("Resuming from previous run (via TaskRun state)", {
|
|
11880
|
+
resumeRunId,
|
|
11881
|
+
currentRunId: payload.run_id
|
|
11882
|
+
});
|
|
11507
11883
|
try {
|
|
11508
|
-
|
|
11509
|
-
payload.task_id,
|
|
11510
|
-
|
|
11511
|
-
|
|
11884
|
+
this.resumeState = await resumeFromLog({
|
|
11885
|
+
taskId: payload.task_id,
|
|
11886
|
+
runId: resumeRunId,
|
|
11887
|
+
repositoryPath: this.config.repositoryPath,
|
|
11888
|
+
apiClient: this.posthogAPI,
|
|
11889
|
+
logger: new Logger({ debug: true, prefix: "[Resume]" })
|
|
11890
|
+
});
|
|
11891
|
+
this.logger.info("Resume state loaded (via TaskRun state)", {
|
|
11892
|
+
conversationTurns: this.resumeState.conversation.length,
|
|
11893
|
+
snapshotApplied: this.resumeState.snapshotApplied,
|
|
11894
|
+
logEntries: this.resumeState.logEntryCount
|
|
11895
|
+
});
|
|
11512
11896
|
} catch (error) {
|
|
11513
|
-
this.logger.warn(
|
|
11514
|
-
|
|
11515
|
-
|
|
11516
|
-
|
|
11517
|
-
runId: payload.run_id,
|
|
11518
|
-
error
|
|
11519
|
-
}
|
|
11520
|
-
);
|
|
11897
|
+
this.logger.warn("Failed to load resume state, starting fresh", {
|
|
11898
|
+
error
|
|
11899
|
+
});
|
|
11900
|
+
this.resumeState = null;
|
|
11521
11901
|
}
|
|
11522
11902
|
}
|
|
11903
|
+
}
|
|
11904
|
+
if (this.resumeState && this.resumeState.conversation.length > 0) {
|
|
11905
|
+
await this.sendResumeMessage(payload, taskRun);
|
|
11906
|
+
return;
|
|
11907
|
+
}
|
|
11908
|
+
try {
|
|
11909
|
+
const task = await this.posthogAPI.getTask(payload.task_id);
|
|
11523
11910
|
const initialPromptOverride = taskRun ? this.getInitialPromptOverride(taskRun) : null;
|
|
11524
11911
|
const initialPrompt = initialPromptOverride ?? task.description;
|
|
11525
11912
|
if (!initialPrompt) {
|
|
@@ -11538,6 +11925,7 @@ You MUST NOT create a new branch, close the existing PR, or create a new PR.`
|
|
|
11538
11925
|
this.logger.info("Initial task message completed", {
|
|
11539
11926
|
stopReason: result.stopReason
|
|
11540
11927
|
});
|
|
11928
|
+
this.broadcastTurnComplete(result.stopReason);
|
|
11541
11929
|
if (result.stopReason === "end_turn") {
|
|
11542
11930
|
await this.relayAgentResponse(payload);
|
|
11543
11931
|
}
|
|
@@ -11549,6 +11937,94 @@ You MUST NOT create a new branch, close the existing PR, or create a new PR.`
|
|
|
11549
11937
|
await this.signalTaskComplete(payload, "error");
|
|
11550
11938
|
}
|
|
11551
11939
|
}
|
|
11940
|
+
async sendResumeMessage(payload, taskRun) {
|
|
11941
|
+
if (!this.session || !this.resumeState) return;
|
|
11942
|
+
try {
|
|
11943
|
+
const conversationSummary = this.formatConversationForResume(
|
|
11944
|
+
this.resumeState.conversation
|
|
11945
|
+
);
|
|
11946
|
+
const pendingUserMessage = this.getPendingUserMessage(taskRun);
|
|
11947
|
+
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.`;
|
|
11948
|
+
let resumePrompt;
|
|
11949
|
+
if (pendingUserMessage) {
|
|
11950
|
+
resumePrompt = `You are resuming a previous conversation. ${sandboxContext}
|
|
11951
|
+
|
|
11952
|
+
Here is the conversation history from the previous session:
|
|
11953
|
+
|
|
11954
|
+
${conversationSummary}
|
|
11955
|
+
|
|
11956
|
+
The user has sent a new message:
|
|
11957
|
+
|
|
11958
|
+
${pendingUserMessage}
|
|
11959
|
+
|
|
11960
|
+
Respond to the user's new message above. You have full context from the previous session.`;
|
|
11961
|
+
} else {
|
|
11962
|
+
resumePrompt = `You are resuming a previous conversation. ${sandboxContext}
|
|
11963
|
+
|
|
11964
|
+
Here is the conversation history from the previous session:
|
|
11965
|
+
|
|
11966
|
+
${conversationSummary}
|
|
11967
|
+
|
|
11968
|
+
Continue from where you left off. The user is waiting for your response.`;
|
|
11969
|
+
}
|
|
11970
|
+
this.logger.info("Sending resume message", {
|
|
11971
|
+
taskId: payload.task_id,
|
|
11972
|
+
conversationTurns: this.resumeState.conversation.length,
|
|
11973
|
+
promptLength: resumePrompt.length,
|
|
11974
|
+
hasPendingUserMessage: !!pendingUserMessage,
|
|
11975
|
+
snapshotApplied: this.resumeState.snapshotApplied
|
|
11976
|
+
});
|
|
11977
|
+
this.resumeState = null;
|
|
11978
|
+
const result = await this.session.clientConnection.prompt({
|
|
11979
|
+
sessionId: this.session.acpSessionId,
|
|
11980
|
+
prompt: [{ type: "text", text: resumePrompt }]
|
|
11981
|
+
});
|
|
11982
|
+
this.logger.info("Resume message completed", {
|
|
11983
|
+
stopReason: result.stopReason
|
|
11984
|
+
});
|
|
11985
|
+
this.broadcastTurnComplete(result.stopReason);
|
|
11986
|
+
} catch (error) {
|
|
11987
|
+
this.logger.error("Failed to send resume message", error);
|
|
11988
|
+
if (this.session) {
|
|
11989
|
+
await this.session.logWriter.flushAll();
|
|
11990
|
+
}
|
|
11991
|
+
await this.signalTaskComplete(payload, "error");
|
|
11992
|
+
}
|
|
11993
|
+
}
|
|
11994
|
+
static RESUME_HISTORY_TOKEN_BUDGET = 5e4;
|
|
11995
|
+
static TOOL_RESULT_MAX_CHARS = 2e3;
|
|
11996
|
+
formatConversationForResume(conversation) {
|
|
11997
|
+
const selected = selectRecentTurns(
|
|
11998
|
+
conversation,
|
|
11999
|
+
_AgentServer.RESUME_HISTORY_TOKEN_BUDGET
|
|
12000
|
+
);
|
|
12001
|
+
const parts = [];
|
|
12002
|
+
if (selected.length < conversation.length) {
|
|
12003
|
+
parts.push(
|
|
12004
|
+
`*(${conversation.length - selected.length} earlier turns omitted)*`
|
|
12005
|
+
);
|
|
12006
|
+
}
|
|
12007
|
+
for (const turn of selected) {
|
|
12008
|
+
const role = turn.role === "user" ? "User" : "Assistant";
|
|
12009
|
+
const textParts = turn.content.filter((block) => block.type === "text").map((block) => block.text);
|
|
12010
|
+
if (textParts.length > 0) {
|
|
12011
|
+
parts.push(`**${role}**: ${textParts.join("\n")}`);
|
|
12012
|
+
}
|
|
12013
|
+
if (turn.toolCalls?.length) {
|
|
12014
|
+
const toolSummary = turn.toolCalls.map((tc) => {
|
|
12015
|
+
let resultStr = "";
|
|
12016
|
+
if (tc.result !== void 0) {
|
|
12017
|
+
const raw = typeof tc.result === "string" ? tc.result : JSON.stringify(tc.result);
|
|
12018
|
+
resultStr = raw.length > _AgentServer.TOOL_RESULT_MAX_CHARS ? ` \u2192 ${raw.substring(0, _AgentServer.TOOL_RESULT_MAX_CHARS)}...(truncated)` : ` \u2192 ${raw}`;
|
|
12019
|
+
}
|
|
12020
|
+
return ` - ${tc.toolName}${resultStr}`;
|
|
12021
|
+
}).join("\n");
|
|
12022
|
+
parts.push(`**${role} (tools)**:
|
|
12023
|
+
${toolSummary}`);
|
|
12024
|
+
}
|
|
12025
|
+
}
|
|
12026
|
+
return parts.join("\n\n");
|
|
12027
|
+
}
|
|
11552
12028
|
getInitialPromptOverride(taskRun) {
|
|
11553
12029
|
const state = taskRun.state;
|
|
11554
12030
|
const override = state?.initial_prompt_override;
|
|
@@ -11558,6 +12034,24 @@ You MUST NOT create a new branch, close the existing PR, or create a new PR.`
|
|
|
11558
12034
|
const trimmed2 = override.trim();
|
|
11559
12035
|
return trimmed2.length > 0 ? trimmed2 : null;
|
|
11560
12036
|
}
|
|
12037
|
+
getPendingUserMessage(taskRun) {
|
|
12038
|
+
if (!taskRun) return null;
|
|
12039
|
+
const state = taskRun.state;
|
|
12040
|
+
const message = state?.pending_user_message;
|
|
12041
|
+
if (typeof message !== "string") {
|
|
12042
|
+
return null;
|
|
12043
|
+
}
|
|
12044
|
+
const trimmed2 = message.trim();
|
|
12045
|
+
return trimmed2.length > 0 ? trimmed2 : null;
|
|
12046
|
+
}
|
|
12047
|
+
getResumeRunId(taskRun) {
|
|
12048
|
+
const envRunId = process.env.POSTHOG_RESUME_RUN_ID;
|
|
12049
|
+
if (envRunId) return envRunId;
|
|
12050
|
+
if (!taskRun) return null;
|
|
12051
|
+
const state = taskRun.state;
|
|
12052
|
+
const stateRunId = state?.resume_from_run_id;
|
|
12053
|
+
return typeof stateRunId === "string" && stateRunId.trim().length > 0 ? stateRunId.trim() : null;
|
|
12054
|
+
}
|
|
11561
12055
|
buildCloudSystemPrompt(prUrl) {
|
|
11562
12056
|
if (prUrl) {
|
|
11563
12057
|
return `
|
|
@@ -11882,20 +12376,30 @@ Important:
|
|
|
11882
12376
|
timestamp: (/* @__PURE__ */ new Date()).toISOString(),
|
|
11883
12377
|
notification
|
|
11884
12378
|
});
|
|
11885
|
-
const { archiveUrl: _, ...paramsWithoutArchive } = snapshotWithDevice;
|
|
11886
|
-
const logNotification = {
|
|
11887
|
-
...notification,
|
|
11888
|
-
params: paramsWithoutArchive
|
|
11889
|
-
};
|
|
11890
12379
|
this.session.logWriter.appendRawLine(
|
|
11891
12380
|
this.session.payload.run_id,
|
|
11892
|
-
JSON.stringify(
|
|
12381
|
+
JSON.stringify(notification)
|
|
11893
12382
|
);
|
|
11894
12383
|
}
|
|
11895
12384
|
} catch (error) {
|
|
11896
12385
|
this.logger.error("Failed to capture tree state", error);
|
|
11897
12386
|
}
|
|
11898
12387
|
}
|
|
12388
|
+
broadcastTurnComplete(stopReason) {
|
|
12389
|
+
if (!this.session) return;
|
|
12390
|
+
this.broadcastEvent({
|
|
12391
|
+
type: "notification",
|
|
12392
|
+
timestamp: (/* @__PURE__ */ new Date()).toISOString(),
|
|
12393
|
+
notification: {
|
|
12394
|
+
jsonrpc: "2.0",
|
|
12395
|
+
method: POSTHOG_NOTIFICATIONS.TURN_COMPLETE,
|
|
12396
|
+
params: {
|
|
12397
|
+
sessionId: this.session.acpSessionId,
|
|
12398
|
+
stopReason
|
|
12399
|
+
}
|
|
12400
|
+
}
|
|
12401
|
+
});
|
|
12402
|
+
}
|
|
11899
12403
|
broadcastEvent(event) {
|
|
11900
12404
|
if (this.session?.sseController) {
|
|
11901
12405
|
this.sendSseEvent(this.session.sseController, event);
|