@posthog/agent 2.3.1 → 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.
- package/dist/adapters/claude/conversion/tool-use-to-acp.js +7 -12
- package/dist/adapters/claude/conversion/tool-use-to-acp.js.map +1 -1
- package/dist/agent.js +10 -13
- 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 +1084 -590
- package/dist/server/agent-server.js.map +1 -1
- package/dist/server/bin.cjs +1139 -645
- package/dist/server/bin.cjs.map +1 -1
- package/package.json +1 -1
- package/src/acp-extensions.ts +3 -0
- package/src/adapters/claude/conversion/tool-use-to-acp.ts +8 -11
- 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.10",
|
|
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() {
|
|
@@ -1738,11 +1740,10 @@ function isMcpToolReadOnly(toolName) {
|
|
|
1738
1740
|
}
|
|
1739
1741
|
|
|
1740
1742
|
// src/adapters/claude/conversion/tool-use-to-acp.ts
|
|
1741
|
-
var
|
|
1742
|
-
|
|
1743
|
-
|
|
1744
|
-
|
|
1745
|
-
</system-reminder>`;
|
|
1743
|
+
var SYSTEM_REMINDER_REGEX = /\s*<system-reminder>[\s\S]*?<\/system-reminder>/g;
|
|
1744
|
+
function stripSystemReminders(value) {
|
|
1745
|
+
return value.replace(SYSTEM_REMINDER_REGEX, "");
|
|
1746
|
+
}
|
|
1746
1747
|
function toDisplayPath(filePath, cwd) {
|
|
1747
1748
|
if (!cwd) return filePath;
|
|
1748
1749
|
const resolvedCwd = path.resolve(cwd);
|
|
@@ -2080,9 +2081,7 @@ function toolUpdateFromToolResult(toolResult, toolUse, options) {
|
|
|
2080
2081
|
return {
|
|
2081
2082
|
type: "content",
|
|
2082
2083
|
content: text(
|
|
2083
|
-
markdownEscape(
|
|
2084
|
-
(itemObj.text ?? "").replace(SYSTEM_REMINDER, "")
|
|
2085
|
-
)
|
|
2084
|
+
markdownEscape(stripSystemReminders(itemObj.text ?? ""))
|
|
2086
2085
|
)
|
|
2087
2086
|
};
|
|
2088
2087
|
}
|
|
@@ -2104,9 +2103,7 @@ function toolUpdateFromToolResult(toolResult, toolUse, options) {
|
|
|
2104
2103
|
};
|
|
2105
2104
|
} else if (typeof toolResult.content === "string" && toolResult.content.length > 0) {
|
|
2106
2105
|
return {
|
|
2107
|
-
content: toolContent().text(
|
|
2108
|
-
markdownEscape(toolResult.content.replace(SYSTEM_REMINDER, ""))
|
|
2109
|
-
).build()
|
|
2106
|
+
content: toolContent().text(markdownEscape(stripSystemReminders(toolResult.content))).build()
|
|
2110
2107
|
};
|
|
2111
2108
|
}
|
|
2112
2109
|
return {};
|
|
@@ -2206,7 +2203,7 @@ function itemToText(item) {
|
|
|
2206
2203
|
if (!item || typeof item !== "object") return null;
|
|
2207
2204
|
const obj = item;
|
|
2208
2205
|
if (obj.type === "text" && typeof obj.text === "string") {
|
|
2209
|
-
return obj.text;
|
|
2206
|
+
return stripSystemReminders(obj.text);
|
|
2210
2207
|
}
|
|
2211
2208
|
try {
|
|
2212
2209
|
return JSON.stringify(obj, null, 2);
|
|
@@ -4998,6 +4995,45 @@ function createCodexConnection(config) {
|
|
|
4998
4995
|
};
|
|
4999
4996
|
}
|
|
5000
4997
|
|
|
4998
|
+
// src/adapters/claude/session/jsonl-hydration.ts
|
|
4999
|
+
import { randomUUID as randomUUID2 } from "crypto";
|
|
5000
|
+
import * as fs4 from "fs/promises";
|
|
5001
|
+
import * as os5 from "os";
|
|
5002
|
+
import * as path6 from "path";
|
|
5003
|
+
var CHARS_PER_TOKEN = 4;
|
|
5004
|
+
var DEFAULT_MAX_TOKENS = 15e4;
|
|
5005
|
+
function estimateTurnTokens(turn) {
|
|
5006
|
+
let chars = 0;
|
|
5007
|
+
for (const block of turn.content) {
|
|
5008
|
+
if ("text" in block && typeof block.text === "string") {
|
|
5009
|
+
chars += block.text.length;
|
|
5010
|
+
}
|
|
5011
|
+
}
|
|
5012
|
+
if (turn.toolCalls) {
|
|
5013
|
+
for (const tc of turn.toolCalls) {
|
|
5014
|
+
chars += JSON.stringify(tc.input ?? "").length;
|
|
5015
|
+
if (tc.result !== void 0) {
|
|
5016
|
+
chars += typeof tc.result === "string" ? tc.result.length : JSON.stringify(tc.result).length;
|
|
5017
|
+
}
|
|
5018
|
+
}
|
|
5019
|
+
}
|
|
5020
|
+
return Math.ceil(chars / CHARS_PER_TOKEN);
|
|
5021
|
+
}
|
|
5022
|
+
function selectRecentTurns(turns, maxTokens = DEFAULT_MAX_TOKENS) {
|
|
5023
|
+
let budget = maxTokens;
|
|
5024
|
+
let startIndex = turns.length;
|
|
5025
|
+
for (let i = turns.length - 1; i >= 0; i--) {
|
|
5026
|
+
const cost = estimateTurnTokens(turns[i]);
|
|
5027
|
+
if (cost > budget) break;
|
|
5028
|
+
budget -= cost;
|
|
5029
|
+
startIndex = i;
|
|
5030
|
+
}
|
|
5031
|
+
while (startIndex < turns.length && turns[startIndex].role !== "user") {
|
|
5032
|
+
startIndex++;
|
|
5033
|
+
}
|
|
5034
|
+
return turns.slice(startIndex);
|
|
5035
|
+
}
|
|
5036
|
+
|
|
5001
5037
|
// src/utils/gateway.ts
|
|
5002
5038
|
function getLlmGatewayUrl(posthogHost, product = "posthog_code") {
|
|
5003
5039
|
const url = new URL(posthogHost);
|
|
@@ -5181,330 +5217,165 @@ var PostHogAPIClient = class {
|
|
|
5181
5217
|
}
|
|
5182
5218
|
};
|
|
5183
5219
|
|
|
5184
|
-
//
|
|
5185
|
-
|
|
5186
|
-
|
|
5187
|
-
|
|
5188
|
-
|
|
5189
|
-
|
|
5190
|
-
|
|
5191
|
-
|
|
5192
|
-
|
|
5193
|
-
static SESSIONS_MAX_AGE_MS = 30 * 24 * 60 * 60 * 1e3;
|
|
5194
|
-
posthogAPI;
|
|
5195
|
-
pendingEntries = /* @__PURE__ */ new Map();
|
|
5196
|
-
flushTimeouts = /* @__PURE__ */ new Map();
|
|
5197
|
-
lastFlushAttemptTime = /* @__PURE__ */ new Map();
|
|
5198
|
-
retryCounts = /* @__PURE__ */ new Map();
|
|
5199
|
-
sessions = /* @__PURE__ */ new Map();
|
|
5200
|
-
logger;
|
|
5201
|
-
localCachePath;
|
|
5202
|
-
constructor(options = {}) {
|
|
5203
|
-
this.posthogAPI = options.posthogAPI;
|
|
5204
|
-
this.localCachePath = options.localCachePath;
|
|
5205
|
-
this.logger = options.logger ?? new Logger({ debug: false, prefix: "[SessionLogWriter]" });
|
|
5220
|
+
// ../shared/dist/index.js
|
|
5221
|
+
var consoleLogger = {
|
|
5222
|
+
info: (_message, _data) => {
|
|
5223
|
+
},
|
|
5224
|
+
debug: (_message, _data) => {
|
|
5225
|
+
},
|
|
5226
|
+
error: (_message, _data) => {
|
|
5227
|
+
},
|
|
5228
|
+
warn: (_message, _data) => {
|
|
5206
5229
|
}
|
|
5207
|
-
|
|
5208
|
-
|
|
5209
|
-
|
|
5210
|
-
|
|
5211
|
-
|
|
5212
|
-
|
|
5213
|
-
|
|
5230
|
+
};
|
|
5231
|
+
var Saga = class {
|
|
5232
|
+
completedSteps = [];
|
|
5233
|
+
currentStepName = "unknown";
|
|
5234
|
+
stepTimings = [];
|
|
5235
|
+
log;
|
|
5236
|
+
constructor(logger) {
|
|
5237
|
+
this.log = logger ?? consoleLogger;
|
|
5214
5238
|
}
|
|
5215
|
-
|
|
5216
|
-
|
|
5217
|
-
|
|
5239
|
+
/**
|
|
5240
|
+
* Run the saga with the given input.
|
|
5241
|
+
* Returns a discriminated union result - either success with data or failure with error details.
|
|
5242
|
+
*/
|
|
5243
|
+
async run(input) {
|
|
5244
|
+
this.completedSteps = [];
|
|
5245
|
+
this.currentStepName = "unknown";
|
|
5246
|
+
this.stepTimings = [];
|
|
5247
|
+
const sagaStart = performance.now();
|
|
5248
|
+
this.log.info("Starting saga", { sagaName: this.sagaName });
|
|
5249
|
+
try {
|
|
5250
|
+
const result = await this.execute(input);
|
|
5251
|
+
const totalDuration = performance.now() - sagaStart;
|
|
5252
|
+
this.log.debug("Saga completed successfully", {
|
|
5253
|
+
sagaName: this.sagaName,
|
|
5254
|
+
stepsCompleted: this.completedSteps.length,
|
|
5255
|
+
totalDurationMs: Math.round(totalDuration),
|
|
5256
|
+
stepTimings: this.stepTimings
|
|
5257
|
+
});
|
|
5258
|
+
return { success: true, data: result };
|
|
5259
|
+
} catch (error) {
|
|
5260
|
+
this.log.error("Saga failed, initiating rollback", {
|
|
5261
|
+
sagaName: this.sagaName,
|
|
5262
|
+
failedStep: this.currentStepName,
|
|
5263
|
+
error: error instanceof Error ? error.message : String(error)
|
|
5264
|
+
});
|
|
5265
|
+
await this.rollback();
|
|
5266
|
+
return {
|
|
5267
|
+
success: false,
|
|
5268
|
+
error: error instanceof Error ? error.message : String(error),
|
|
5269
|
+
failedStep: this.currentStepName
|
|
5270
|
+
};
|
|
5218
5271
|
}
|
|
5219
|
-
|
|
5220
|
-
|
|
5221
|
-
|
|
5272
|
+
}
|
|
5273
|
+
/**
|
|
5274
|
+
* Execute a step with its rollback action.
|
|
5275
|
+
* If the step succeeds, its rollback action is stored for potential rollback.
|
|
5276
|
+
* The step name is automatically tracked for error reporting.
|
|
5277
|
+
*
|
|
5278
|
+
* @param config - Step configuration with name, execute, and rollback functions
|
|
5279
|
+
* @returns The result of the execute function
|
|
5280
|
+
* @throws Re-throws any error from the execute function (triggers rollback)
|
|
5281
|
+
*/
|
|
5282
|
+
async step(config) {
|
|
5283
|
+
this.currentStepName = config.name;
|
|
5284
|
+
this.log.debug(`Executing step: ${config.name}`);
|
|
5285
|
+
const stepStart = performance.now();
|
|
5286
|
+
const result = await config.execute();
|
|
5287
|
+
const durationMs = Math.round(performance.now() - stepStart);
|
|
5288
|
+
this.stepTimings.push({ name: config.name, durationMs });
|
|
5289
|
+
this.log.debug(`Step completed: ${config.name}`, { durationMs });
|
|
5290
|
+
this.completedSteps.push({
|
|
5291
|
+
name: config.name,
|
|
5292
|
+
rollback: () => config.rollback(result)
|
|
5222
5293
|
});
|
|
5223
|
-
|
|
5224
|
-
|
|
5225
|
-
|
|
5226
|
-
|
|
5227
|
-
|
|
5228
|
-
|
|
5229
|
-
|
|
5230
|
-
|
|
5294
|
+
return result;
|
|
5295
|
+
}
|
|
5296
|
+
/**
|
|
5297
|
+
* Execute a step that doesn't need rollback.
|
|
5298
|
+
* Useful for read-only operations or operations that are idempotent.
|
|
5299
|
+
* The step name is automatically tracked for error reporting.
|
|
5300
|
+
*
|
|
5301
|
+
* @param name - Step name for logging and error tracking
|
|
5302
|
+
* @param execute - The action to execute
|
|
5303
|
+
* @returns The result of the execute function
|
|
5304
|
+
*/
|
|
5305
|
+
async readOnlyStep(name, execute) {
|
|
5306
|
+
this.currentStepName = name;
|
|
5307
|
+
this.log.debug(`Executing read-only step: ${name}`);
|
|
5308
|
+
const stepStart = performance.now();
|
|
5309
|
+
const result = await execute();
|
|
5310
|
+
const durationMs = Math.round(performance.now() - stepStart);
|
|
5311
|
+
this.stepTimings.push({ name, durationMs });
|
|
5312
|
+
this.log.debug(`Read-only step completed: ${name}`, { durationMs });
|
|
5313
|
+
return result;
|
|
5314
|
+
}
|
|
5315
|
+
/**
|
|
5316
|
+
* Roll back all completed steps in reverse order.
|
|
5317
|
+
* Rollback errors are logged but don't stop the rollback of other steps.
|
|
5318
|
+
*/
|
|
5319
|
+
async rollback() {
|
|
5320
|
+
this.log.info("Rolling back saga", {
|
|
5321
|
+
stepsToRollback: this.completedSteps.length
|
|
5322
|
+
});
|
|
5323
|
+
const stepsReversed = [...this.completedSteps].reverse();
|
|
5324
|
+
for (const step of stepsReversed) {
|
|
5231
5325
|
try {
|
|
5232
|
-
|
|
5326
|
+
this.log.debug(`Rolling back step: ${step.name}`);
|
|
5327
|
+
await step.rollback();
|
|
5328
|
+
this.log.debug(`Step rolled back: ${step.name}`);
|
|
5233
5329
|
} catch (error) {
|
|
5234
|
-
this.
|
|
5235
|
-
|
|
5236
|
-
error
|
|
5330
|
+
this.log.error(`Failed to rollback step: ${step.name}`, {
|
|
5331
|
+
error: error instanceof Error ? error.message : String(error)
|
|
5237
5332
|
});
|
|
5238
5333
|
}
|
|
5239
5334
|
}
|
|
5335
|
+
this.log.info("Rollback completed", {
|
|
5336
|
+
stepsAttempted: this.completedSteps.length
|
|
5337
|
+
});
|
|
5240
5338
|
}
|
|
5241
|
-
|
|
5242
|
-
|
|
5339
|
+
/**
|
|
5340
|
+
* Get the number of completed steps (useful for testing)
|
|
5341
|
+
*/
|
|
5342
|
+
getCompletedStepCount() {
|
|
5343
|
+
return this.completedSteps.length;
|
|
5243
5344
|
}
|
|
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
|
-
|
|
5274
|
-
|
|
5275
|
-
|
|
5276
|
-
|
|
5277
|
-
|
|
5278
|
-
const pending = this.pendingEntries.get(sessionId) ?? [];
|
|
5279
|
-
pending.push(entry);
|
|
5280
|
-
this.pendingEntries.set(sessionId, pending);
|
|
5281
|
-
this.scheduleFlush(sessionId);
|
|
5282
|
-
}
|
|
5283
|
-
} catch {
|
|
5284
|
-
this.logger.warn("Failed to parse raw line for persistence", {
|
|
5285
|
-
taskId: session.context.taskId,
|
|
5286
|
-
runId: session.context.runId,
|
|
5287
|
-
lineLength: line.length
|
|
5288
|
-
});
|
|
5289
|
-
}
|
|
5290
|
-
}
|
|
5291
|
-
async flush(sessionId) {
|
|
5292
|
-
const session = this.sessions.get(sessionId);
|
|
5293
|
-
if (!session) {
|
|
5294
|
-
this.logger.warn("flush: no session found", { sessionId });
|
|
5295
|
-
return;
|
|
5296
|
-
}
|
|
5297
|
-
this.emitCoalescedMessage(sessionId, session);
|
|
5298
|
-
const pending = this.pendingEntries.get(sessionId);
|
|
5299
|
-
if (!this.posthogAPI || !pending?.length) {
|
|
5300
|
-
return;
|
|
5301
|
-
}
|
|
5302
|
-
this.pendingEntries.delete(sessionId);
|
|
5303
|
-
const timeout = this.flushTimeouts.get(sessionId);
|
|
5304
|
-
if (timeout) {
|
|
5305
|
-
clearTimeout(timeout);
|
|
5306
|
-
this.flushTimeouts.delete(sessionId);
|
|
5307
|
-
}
|
|
5308
|
-
this.lastFlushAttemptTime.set(sessionId, Date.now());
|
|
5309
|
-
try {
|
|
5310
|
-
await this.posthogAPI.appendTaskRunLog(
|
|
5311
|
-
session.context.taskId,
|
|
5312
|
-
session.context.runId,
|
|
5313
|
-
pending
|
|
5314
|
-
);
|
|
5315
|
-
this.retryCounts.set(sessionId, 0);
|
|
5316
|
-
} catch (error) {
|
|
5317
|
-
const retryCount = (this.retryCounts.get(sessionId) ?? 0) + 1;
|
|
5318
|
-
this.retryCounts.set(sessionId, retryCount);
|
|
5319
|
-
if (retryCount >= _SessionLogWriter.MAX_FLUSH_RETRIES) {
|
|
5320
|
-
this.logger.error(
|
|
5321
|
-
`Dropping ${pending.length} session log entries after ${retryCount} failed flush attempts`,
|
|
5322
|
-
{
|
|
5323
|
-
taskId: session.context.taskId,
|
|
5324
|
-
runId: session.context.runId,
|
|
5325
|
-
error
|
|
5326
|
-
}
|
|
5327
|
-
);
|
|
5328
|
-
this.retryCounts.set(sessionId, 0);
|
|
5329
|
-
} else {
|
|
5330
|
-
if (retryCount === 1) {
|
|
5331
|
-
this.logger.warn(
|
|
5332
|
-
`Failed to persist session logs, will retry (up to ${_SessionLogWriter.MAX_FLUSH_RETRIES} attempts)`,
|
|
5333
|
-
{
|
|
5334
|
-
taskId: session.context.taskId,
|
|
5335
|
-
runId: session.context.runId,
|
|
5336
|
-
error: error instanceof Error ? error.message : String(error)
|
|
5337
|
-
}
|
|
5338
|
-
);
|
|
5339
|
-
}
|
|
5340
|
-
const currentPending = this.pendingEntries.get(sessionId) ?? [];
|
|
5341
|
-
this.pendingEntries.set(sessionId, [...pending, ...currentPending]);
|
|
5342
|
-
this.scheduleFlush(sessionId);
|
|
5343
|
-
}
|
|
5344
|
-
}
|
|
5345
|
-
}
|
|
5346
|
-
isAgentMessageChunk(message) {
|
|
5347
|
-
if (message.method !== "session/update") return false;
|
|
5348
|
-
const params = message.params;
|
|
5349
|
-
const update = params?.update;
|
|
5350
|
-
return update?.sessionUpdate === "agent_message_chunk";
|
|
5351
|
-
}
|
|
5352
|
-
extractChunkText(message) {
|
|
5353
|
-
const params = message.params;
|
|
5354
|
-
const update = params?.update;
|
|
5355
|
-
const content = update?.content;
|
|
5356
|
-
if (content?.type === "text" && content.text) {
|
|
5357
|
-
return content.text;
|
|
5358
|
-
}
|
|
5359
|
-
return "";
|
|
5360
|
-
}
|
|
5361
|
-
emitCoalescedMessage(sessionId, session) {
|
|
5362
|
-
if (!session.chunkBuffer) return;
|
|
5363
|
-
const { text: text2, firstTimestamp } = session.chunkBuffer;
|
|
5364
|
-
session.chunkBuffer = void 0;
|
|
5365
|
-
session.lastAgentMessage = text2;
|
|
5366
|
-
const entry = {
|
|
5367
|
-
type: "notification",
|
|
5368
|
-
timestamp: firstTimestamp,
|
|
5369
|
-
notification: {
|
|
5370
|
-
jsonrpc: "2.0",
|
|
5371
|
-
method: "session/update",
|
|
5372
|
-
params: {
|
|
5373
|
-
update: {
|
|
5374
|
-
sessionUpdate: "agent_message",
|
|
5375
|
-
content: { type: "text", text: text2 }
|
|
5376
|
-
}
|
|
5377
|
-
}
|
|
5378
|
-
}
|
|
5379
|
-
};
|
|
5380
|
-
this.writeToLocalCache(sessionId, entry);
|
|
5381
|
-
if (this.posthogAPI) {
|
|
5382
|
-
const pending = this.pendingEntries.get(sessionId) ?? [];
|
|
5383
|
-
pending.push(entry);
|
|
5384
|
-
this.pendingEntries.set(sessionId, pending);
|
|
5385
|
-
this.scheduleFlush(sessionId);
|
|
5386
|
-
}
|
|
5387
|
-
}
|
|
5388
|
-
getLastAgentMessage(sessionId) {
|
|
5389
|
-
return this.sessions.get(sessionId)?.lastAgentMessage;
|
|
5390
|
-
}
|
|
5391
|
-
extractAgentMessageText(message) {
|
|
5392
|
-
if (message.method !== "session/update") {
|
|
5393
|
-
return null;
|
|
5394
|
-
}
|
|
5395
|
-
const params = message.params;
|
|
5396
|
-
const update = params?.update;
|
|
5397
|
-
if (update?.sessionUpdate !== "agent_message") {
|
|
5398
|
-
return null;
|
|
5399
|
-
}
|
|
5400
|
-
const content = update.content;
|
|
5401
|
-
if (content?.type === "text" && typeof content.text === "string") {
|
|
5402
|
-
const trimmed2 = content.text.trim();
|
|
5403
|
-
return trimmed2.length > 0 ? trimmed2 : null;
|
|
5404
|
-
}
|
|
5405
|
-
if (typeof update.message === "string") {
|
|
5406
|
-
const trimmed2 = update.message.trim();
|
|
5407
|
-
return trimmed2.length > 0 ? trimmed2 : null;
|
|
5408
|
-
}
|
|
5409
|
-
return null;
|
|
5410
|
-
}
|
|
5411
|
-
scheduleFlush(sessionId) {
|
|
5412
|
-
const existing = this.flushTimeouts.get(sessionId);
|
|
5413
|
-
if (existing) clearTimeout(existing);
|
|
5414
|
-
const retryCount = this.retryCounts.get(sessionId) ?? 0;
|
|
5415
|
-
const lastAttempt = this.lastFlushAttemptTime.get(sessionId) ?? 0;
|
|
5416
|
-
const elapsed = Date.now() - lastAttempt;
|
|
5417
|
-
let delay3;
|
|
5418
|
-
if (retryCount > 0) {
|
|
5419
|
-
delay3 = Math.min(
|
|
5420
|
-
_SessionLogWriter.FLUSH_DEBOUNCE_MS * 2 ** retryCount,
|
|
5421
|
-
_SessionLogWriter.MAX_RETRY_DELAY_MS
|
|
5422
|
-
);
|
|
5423
|
-
} else if (elapsed >= _SessionLogWriter.FLUSH_MAX_INTERVAL_MS) {
|
|
5424
|
-
delay3 = 0;
|
|
5425
|
-
} else {
|
|
5426
|
-
delay3 = _SessionLogWriter.FLUSH_DEBOUNCE_MS;
|
|
5427
|
-
}
|
|
5428
|
-
const timeout = setTimeout(() => this.flush(sessionId), delay3);
|
|
5429
|
-
this.flushTimeouts.set(sessionId, timeout);
|
|
5430
|
-
}
|
|
5431
|
-
writeToLocalCache(sessionId, entry) {
|
|
5432
|
-
if (!this.localCachePath) return;
|
|
5433
|
-
const session = this.sessions.get(sessionId);
|
|
5434
|
-
if (!session) return;
|
|
5435
|
-
const logPath = path6.join(
|
|
5436
|
-
this.localCachePath,
|
|
5437
|
-
"sessions",
|
|
5438
|
-
session.context.runId,
|
|
5439
|
-
"logs.ndjson"
|
|
5440
|
-
);
|
|
5441
|
-
try {
|
|
5442
|
-
fs4.appendFileSync(logPath, `${JSON.stringify(entry)}
|
|
5443
|
-
`);
|
|
5444
|
-
} catch (error) {
|
|
5445
|
-
this.logger.warn("Failed to write to local cache", {
|
|
5446
|
-
taskId: session.context.taskId,
|
|
5447
|
-
runId: session.context.runId,
|
|
5448
|
-
logPath,
|
|
5449
|
-
error
|
|
5450
|
-
});
|
|
5451
|
-
}
|
|
5452
|
-
}
|
|
5453
|
-
static async cleanupOldSessions(localCachePath) {
|
|
5454
|
-
const sessionsDir = path6.join(localCachePath, "sessions");
|
|
5455
|
-
let deleted = 0;
|
|
5456
|
-
try {
|
|
5457
|
-
const entries = await fsp.readdir(sessionsDir);
|
|
5458
|
-
const now = Date.now();
|
|
5459
|
-
for (const entry of entries) {
|
|
5460
|
-
const entryPath = path6.join(sessionsDir, entry);
|
|
5461
|
-
try {
|
|
5462
|
-
const stats = await fsp.stat(entryPath);
|
|
5463
|
-
if (stats.isDirectory() && now - stats.birthtimeMs > _SessionLogWriter.SESSIONS_MAX_AGE_MS) {
|
|
5464
|
-
await fsp.rm(entryPath, { recursive: true, force: true });
|
|
5465
|
-
deleted++;
|
|
5466
|
-
}
|
|
5467
|
-
} catch {
|
|
5468
|
-
}
|
|
5469
|
-
}
|
|
5470
|
-
} catch {
|
|
5471
|
-
}
|
|
5472
|
-
return deleted;
|
|
5473
|
-
}
|
|
5474
|
-
};
|
|
5475
|
-
|
|
5476
|
-
// ../git/dist/queries.js
|
|
5477
|
-
import * as fs6 from "fs/promises";
|
|
5478
|
-
import * as path8 from "path";
|
|
5479
|
-
|
|
5480
|
-
// ../../node_modules/simple-git/dist/esm/index.js
|
|
5481
|
-
var import_file_exists = __toESM(require_dist(), 1);
|
|
5482
|
-
var import_debug = __toESM(require_src(), 1);
|
|
5483
|
-
var import_promise_deferred = __toESM(require_dist2(), 1);
|
|
5484
|
-
var import_promise_deferred2 = __toESM(require_dist2(), 1);
|
|
5485
|
-
import { Buffer as Buffer2 } from "buffer";
|
|
5486
|
-
import { spawn as spawn3 } from "child_process";
|
|
5487
|
-
import { normalize as normalize2 } from "path";
|
|
5488
|
-
import { EventEmitter } from "events";
|
|
5489
|
-
var __defProp2 = Object.defineProperty;
|
|
5490
|
-
var __getOwnPropDesc2 = Object.getOwnPropertyDescriptor;
|
|
5491
|
-
var __getOwnPropNames2 = Object.getOwnPropertyNames;
|
|
5492
|
-
var __hasOwnProp2 = Object.prototype.hasOwnProperty;
|
|
5493
|
-
var __esm = (fn, res) => function __init() {
|
|
5494
|
-
return fn && (res = (0, fn[__getOwnPropNames2(fn)[0]])(fn = 0)), res;
|
|
5495
|
-
};
|
|
5496
|
-
var __commonJS2 = (cb, mod) => function __require2() {
|
|
5497
|
-
return mod || (0, cb[__getOwnPropNames2(cb)[0]])((mod = { exports: {} }).exports, mod), mod.exports;
|
|
5498
|
-
};
|
|
5499
|
-
var __export = (target, all) => {
|
|
5500
|
-
for (var name in all)
|
|
5501
|
-
__defProp2(target, name, { get: all[name], enumerable: true });
|
|
5502
|
-
};
|
|
5503
|
-
var __copyProps2 = (to, from, except, desc) => {
|
|
5504
|
-
if (from && typeof from === "object" || typeof from === "function") {
|
|
5505
|
-
for (let key of __getOwnPropNames2(from))
|
|
5506
|
-
if (!__hasOwnProp2.call(to, key) && key !== except)
|
|
5507
|
-
__defProp2(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc2(from, key)) || desc.enumerable });
|
|
5345
|
+
};
|
|
5346
|
+
|
|
5347
|
+
// ../git/dist/queries.js
|
|
5348
|
+
import * as fs6 from "fs/promises";
|
|
5349
|
+
import * as path8 from "path";
|
|
5350
|
+
|
|
5351
|
+
// ../../node_modules/simple-git/dist/esm/index.js
|
|
5352
|
+
var import_file_exists = __toESM(require_dist(), 1);
|
|
5353
|
+
var import_debug = __toESM(require_src(), 1);
|
|
5354
|
+
var import_promise_deferred = __toESM(require_dist2(), 1);
|
|
5355
|
+
var import_promise_deferred2 = __toESM(require_dist2(), 1);
|
|
5356
|
+
import { Buffer as Buffer2 } from "buffer";
|
|
5357
|
+
import { spawn as spawn3 } from "child_process";
|
|
5358
|
+
import { normalize as normalize2 } from "path";
|
|
5359
|
+
import { EventEmitter } from "events";
|
|
5360
|
+
var __defProp2 = Object.defineProperty;
|
|
5361
|
+
var __getOwnPropDesc2 = Object.getOwnPropertyDescriptor;
|
|
5362
|
+
var __getOwnPropNames2 = Object.getOwnPropertyNames;
|
|
5363
|
+
var __hasOwnProp2 = Object.prototype.hasOwnProperty;
|
|
5364
|
+
var __esm = (fn, res) => function __init() {
|
|
5365
|
+
return fn && (res = (0, fn[__getOwnPropNames2(fn)[0]])(fn = 0)), res;
|
|
5366
|
+
};
|
|
5367
|
+
var __commonJS2 = (cb, mod) => function __require2() {
|
|
5368
|
+
return mod || (0, cb[__getOwnPropNames2(cb)[0]])((mod = { exports: {} }).exports, mod), mod.exports;
|
|
5369
|
+
};
|
|
5370
|
+
var __export = (target, all) => {
|
|
5371
|
+
for (var name in all)
|
|
5372
|
+
__defProp2(target, name, { get: all[name], enumerable: true });
|
|
5373
|
+
};
|
|
5374
|
+
var __copyProps2 = (to, from, except, desc) => {
|
|
5375
|
+
if (from && typeof from === "object" || typeof from === "function") {
|
|
5376
|
+
for (let key of __getOwnPropNames2(from))
|
|
5377
|
+
if (!__hasOwnProp2.call(to, key) && key !== except)
|
|
5378
|
+
__defProp2(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc2(from, key)) || desc.enumerable });
|
|
5508
5379
|
}
|
|
5509
5380
|
return to;
|
|
5510
5381
|
};
|
|
@@ -5514,8 +5385,8 @@ function pathspec(...paths) {
|
|
|
5514
5385
|
cache.set(key, paths);
|
|
5515
5386
|
return key;
|
|
5516
5387
|
}
|
|
5517
|
-
function isPathSpec(
|
|
5518
|
-
return
|
|
5388
|
+
function isPathSpec(path11) {
|
|
5389
|
+
return path11 instanceof String && cache.has(path11);
|
|
5519
5390
|
}
|
|
5520
5391
|
function toPaths(pathSpec) {
|
|
5521
5392
|
return cache.get(pathSpec) || [];
|
|
@@ -5604,8 +5475,8 @@ function toLinesWithContent(input = "", trimmed2 = true, separator = "\n") {
|
|
|
5604
5475
|
function forEachLineWithContent(input, callback) {
|
|
5605
5476
|
return toLinesWithContent(input, true).map((line) => callback(line));
|
|
5606
5477
|
}
|
|
5607
|
-
function folderExists(
|
|
5608
|
-
return (0, import_file_exists.exists)(
|
|
5478
|
+
function folderExists(path11) {
|
|
5479
|
+
return (0, import_file_exists.exists)(path11, import_file_exists.FOLDER);
|
|
5609
5480
|
}
|
|
5610
5481
|
function append(target, item) {
|
|
5611
5482
|
if (Array.isArray(target)) {
|
|
@@ -6009,8 +5880,8 @@ function checkIsRepoRootTask() {
|
|
|
6009
5880
|
commands,
|
|
6010
5881
|
format: "utf-8",
|
|
6011
5882
|
onError,
|
|
6012
|
-
parser(
|
|
6013
|
-
return /^\.(git)?$/.test(
|
|
5883
|
+
parser(path11) {
|
|
5884
|
+
return /^\.(git)?$/.test(path11.trim());
|
|
6014
5885
|
}
|
|
6015
5886
|
};
|
|
6016
5887
|
}
|
|
@@ -6444,11 +6315,11 @@ function parseGrep(grep) {
|
|
|
6444
6315
|
const paths = /* @__PURE__ */ new Set();
|
|
6445
6316
|
const results = {};
|
|
6446
6317
|
forEachLineWithContent(grep, (input) => {
|
|
6447
|
-
const [
|
|
6448
|
-
paths.add(
|
|
6449
|
-
(results[
|
|
6318
|
+
const [path11, line, preview] = input.split(NULL);
|
|
6319
|
+
paths.add(path11);
|
|
6320
|
+
(results[path11] = results[path11] || []).push({
|
|
6450
6321
|
line: asNumber(line),
|
|
6451
|
-
path:
|
|
6322
|
+
path: path11,
|
|
6452
6323
|
preview
|
|
6453
6324
|
});
|
|
6454
6325
|
});
|
|
@@ -7213,14 +7084,14 @@ var init_hash_object = __esm({
|
|
|
7213
7084
|
init_task();
|
|
7214
7085
|
}
|
|
7215
7086
|
});
|
|
7216
|
-
function parseInit(bare,
|
|
7087
|
+
function parseInit(bare, path11, text2) {
|
|
7217
7088
|
const response = String(text2).trim();
|
|
7218
7089
|
let result;
|
|
7219
7090
|
if (result = initResponseRegex.exec(response)) {
|
|
7220
|
-
return new InitSummary(bare,
|
|
7091
|
+
return new InitSummary(bare, path11, false, result[1]);
|
|
7221
7092
|
}
|
|
7222
7093
|
if (result = reInitResponseRegex.exec(response)) {
|
|
7223
|
-
return new InitSummary(bare,
|
|
7094
|
+
return new InitSummary(bare, path11, true, result[1]);
|
|
7224
7095
|
}
|
|
7225
7096
|
let gitDir = "";
|
|
7226
7097
|
const tokens = response.split(" ");
|
|
@@ -7231,7 +7102,7 @@ function parseInit(bare, path10, text2) {
|
|
|
7231
7102
|
break;
|
|
7232
7103
|
}
|
|
7233
7104
|
}
|
|
7234
|
-
return new InitSummary(bare,
|
|
7105
|
+
return new InitSummary(bare, path11, /^re/i.test(response), gitDir);
|
|
7235
7106
|
}
|
|
7236
7107
|
var InitSummary;
|
|
7237
7108
|
var initResponseRegex;
|
|
@@ -7240,9 +7111,9 @@ var init_InitSummary = __esm({
|
|
|
7240
7111
|
"src/lib/responses/InitSummary.ts"() {
|
|
7241
7112
|
"use strict";
|
|
7242
7113
|
InitSummary = class {
|
|
7243
|
-
constructor(bare,
|
|
7114
|
+
constructor(bare, path11, existing, gitDir) {
|
|
7244
7115
|
this.bare = bare;
|
|
7245
|
-
this.path =
|
|
7116
|
+
this.path = path11;
|
|
7246
7117
|
this.existing = existing;
|
|
7247
7118
|
this.gitDir = gitDir;
|
|
7248
7119
|
}
|
|
@@ -7254,7 +7125,7 @@ var init_InitSummary = __esm({
|
|
|
7254
7125
|
function hasBareCommand(command) {
|
|
7255
7126
|
return command.includes(bareCommand);
|
|
7256
7127
|
}
|
|
7257
|
-
function initTask(bare = false,
|
|
7128
|
+
function initTask(bare = false, path11, customArgs) {
|
|
7258
7129
|
const commands = ["init", ...customArgs];
|
|
7259
7130
|
if (bare && !hasBareCommand(commands)) {
|
|
7260
7131
|
commands.splice(1, 0, bareCommand);
|
|
@@ -7263,7 +7134,7 @@ function initTask(bare = false, path10, customArgs) {
|
|
|
7263
7134
|
commands,
|
|
7264
7135
|
format: "utf-8",
|
|
7265
7136
|
parser(text2) {
|
|
7266
|
-
return parseInit(commands.includes("--bare"),
|
|
7137
|
+
return parseInit(commands.includes("--bare"), path11, text2);
|
|
7267
7138
|
}
|
|
7268
7139
|
};
|
|
7269
7140
|
}
|
|
@@ -8079,12 +7950,12 @@ var init_FileStatusSummary = __esm({
|
|
|
8079
7950
|
"use strict";
|
|
8080
7951
|
fromPathRegex = /^(.+)\0(.+)$/;
|
|
8081
7952
|
FileStatusSummary = class {
|
|
8082
|
-
constructor(
|
|
8083
|
-
this.path =
|
|
7953
|
+
constructor(path11, index, working_dir) {
|
|
7954
|
+
this.path = path11;
|
|
8084
7955
|
this.index = index;
|
|
8085
7956
|
this.working_dir = working_dir;
|
|
8086
7957
|
if (index === "R" || working_dir === "R") {
|
|
8087
|
-
const detail = fromPathRegex.exec(
|
|
7958
|
+
const detail = fromPathRegex.exec(path11) || [null, path11, path11];
|
|
8088
7959
|
this.from = detail[2] || "";
|
|
8089
7960
|
this.path = detail[1] || "";
|
|
8090
7961
|
}
|
|
@@ -8115,14 +7986,14 @@ function splitLine(result, lineStr) {
|
|
|
8115
7986
|
default:
|
|
8116
7987
|
return;
|
|
8117
7988
|
}
|
|
8118
|
-
function data(index, workingDir,
|
|
7989
|
+
function data(index, workingDir, path11) {
|
|
8119
7990
|
const raw = `${index}${workingDir}`;
|
|
8120
7991
|
const handler = parsers6.get(raw);
|
|
8121
7992
|
if (handler) {
|
|
8122
|
-
handler(result,
|
|
7993
|
+
handler(result, path11);
|
|
8123
7994
|
}
|
|
8124
7995
|
if (raw !== "##" && raw !== "!!") {
|
|
8125
|
-
result.files.push(new FileStatusSummary(
|
|
7996
|
+
result.files.push(new FileStatusSummary(path11, index, workingDir));
|
|
8126
7997
|
}
|
|
8127
7998
|
}
|
|
8128
7999
|
}
|
|
@@ -8435,9 +8306,9 @@ var init_simple_git_api = __esm({
|
|
|
8435
8306
|
next
|
|
8436
8307
|
);
|
|
8437
8308
|
}
|
|
8438
|
-
hashObject(
|
|
8309
|
+
hashObject(path11, write) {
|
|
8439
8310
|
return this._runTask(
|
|
8440
|
-
hashObjectTask(
|
|
8311
|
+
hashObjectTask(path11, write === true),
|
|
8441
8312
|
trailingFunctionArgument(arguments)
|
|
8442
8313
|
);
|
|
8443
8314
|
}
|
|
@@ -8790,8 +8661,8 @@ var init_branch = __esm({
|
|
|
8790
8661
|
}
|
|
8791
8662
|
});
|
|
8792
8663
|
function toPath(input) {
|
|
8793
|
-
const
|
|
8794
|
-
return
|
|
8664
|
+
const path11 = input.trim().replace(/^["']|["']$/g, "");
|
|
8665
|
+
return path11 && normalize2(path11);
|
|
8795
8666
|
}
|
|
8796
8667
|
var parseCheckIgnore;
|
|
8797
8668
|
var init_CheckIgnore = __esm({
|
|
@@ -9105,8 +8976,8 @@ __export(sub_module_exports, {
|
|
|
9105
8976
|
subModuleTask: () => subModuleTask,
|
|
9106
8977
|
updateSubModuleTask: () => updateSubModuleTask
|
|
9107
8978
|
});
|
|
9108
|
-
function addSubModuleTask(repo,
|
|
9109
|
-
return subModuleTask(["add", repo,
|
|
8979
|
+
function addSubModuleTask(repo, path11) {
|
|
8980
|
+
return subModuleTask(["add", repo, path11]);
|
|
9110
8981
|
}
|
|
9111
8982
|
function initSubModuleTask(customArgs) {
|
|
9112
8983
|
return subModuleTask(["init", ...customArgs]);
|
|
@@ -9436,8 +9307,8 @@ var require_git = __commonJS2({
|
|
|
9436
9307
|
}
|
|
9437
9308
|
return this._runTask(straightThroughStringTask2(command, this._trimmed), next);
|
|
9438
9309
|
};
|
|
9439
|
-
Git2.prototype.submoduleAdd = function(repo,
|
|
9440
|
-
return this._runTask(addSubModuleTask2(repo,
|
|
9310
|
+
Git2.prototype.submoduleAdd = function(repo, path11, then) {
|
|
9311
|
+
return this._runTask(addSubModuleTask2(repo, path11), trailingFunctionArgument2(arguments));
|
|
9441
9312
|
};
|
|
9442
9313
|
Git2.prototype.submoduleUpdate = function(args, then) {
|
|
9443
9314
|
return this._runTask(
|
|
@@ -10230,8 +10101,8 @@ async function getHeadSha(baseDir, options) {
|
|
|
10230
10101
|
}
|
|
10231
10102
|
|
|
10232
10103
|
// src/sagas/apply-snapshot-saga.ts
|
|
10233
|
-
import { mkdir as
|
|
10234
|
-
import { join as
|
|
10104
|
+
import { mkdir as mkdir4, rm as rm3, writeFile as writeFile4 } from "fs/promises";
|
|
10105
|
+
import { join as join7 } from "path";
|
|
10235
10106
|
|
|
10236
10107
|
// ../git/dist/sagas/tree.js
|
|
10237
10108
|
import { existsSync as existsSync4 } from "fs";
|
|
@@ -10239,148 +10110,21 @@ import * as fs7 from "fs/promises";
|
|
|
10239
10110
|
import * as path9 from "path";
|
|
10240
10111
|
import * as tar from "tar";
|
|
10241
10112
|
|
|
10242
|
-
// ../
|
|
10243
|
-
var
|
|
10244
|
-
|
|
10245
|
-
|
|
10246
|
-
|
|
10247
|
-
|
|
10248
|
-
|
|
10249
|
-
|
|
10250
|
-
warn: (_message, _data) => {
|
|
10113
|
+
// ../git/dist/git-saga.js
|
|
10114
|
+
var GitSaga = class extends Saga {
|
|
10115
|
+
_git = null;
|
|
10116
|
+
get git() {
|
|
10117
|
+
if (!this._git) {
|
|
10118
|
+
throw new Error("git client accessed before execute() was called");
|
|
10119
|
+
}
|
|
10120
|
+
return this._git;
|
|
10251
10121
|
}
|
|
10252
|
-
|
|
10253
|
-
|
|
10254
|
-
|
|
10255
|
-
|
|
10256
|
-
|
|
10257
|
-
|
|
10258
|
-
constructor(logger) {
|
|
10259
|
-
this.log = logger ?? consoleLogger;
|
|
10260
|
-
}
|
|
10261
|
-
/**
|
|
10262
|
-
* Run the saga with the given input.
|
|
10263
|
-
* Returns a discriminated union result - either success with data or failure with error details.
|
|
10264
|
-
*/
|
|
10265
|
-
async run(input) {
|
|
10266
|
-
this.completedSteps = [];
|
|
10267
|
-
this.currentStepName = "unknown";
|
|
10268
|
-
this.stepTimings = [];
|
|
10269
|
-
const sagaStart = performance.now();
|
|
10270
|
-
this.log.info("Starting saga", { sagaName: this.sagaName });
|
|
10271
|
-
try {
|
|
10272
|
-
const result = await this.execute(input);
|
|
10273
|
-
const totalDuration = performance.now() - sagaStart;
|
|
10274
|
-
this.log.debug("Saga completed successfully", {
|
|
10275
|
-
sagaName: this.sagaName,
|
|
10276
|
-
stepsCompleted: this.completedSteps.length,
|
|
10277
|
-
totalDurationMs: Math.round(totalDuration),
|
|
10278
|
-
stepTimings: this.stepTimings
|
|
10279
|
-
});
|
|
10280
|
-
return { success: true, data: result };
|
|
10281
|
-
} catch (error) {
|
|
10282
|
-
this.log.error("Saga failed, initiating rollback", {
|
|
10283
|
-
sagaName: this.sagaName,
|
|
10284
|
-
failedStep: this.currentStepName,
|
|
10285
|
-
error: error instanceof Error ? error.message : String(error)
|
|
10286
|
-
});
|
|
10287
|
-
await this.rollback();
|
|
10288
|
-
return {
|
|
10289
|
-
success: false,
|
|
10290
|
-
error: error instanceof Error ? error.message : String(error),
|
|
10291
|
-
failedStep: this.currentStepName
|
|
10292
|
-
};
|
|
10293
|
-
}
|
|
10294
|
-
}
|
|
10295
|
-
/**
|
|
10296
|
-
* Execute a step with its rollback action.
|
|
10297
|
-
* If the step succeeds, its rollback action is stored for potential rollback.
|
|
10298
|
-
* The step name is automatically tracked for error reporting.
|
|
10299
|
-
*
|
|
10300
|
-
* @param config - Step configuration with name, execute, and rollback functions
|
|
10301
|
-
* @returns The result of the execute function
|
|
10302
|
-
* @throws Re-throws any error from the execute function (triggers rollback)
|
|
10303
|
-
*/
|
|
10304
|
-
async step(config) {
|
|
10305
|
-
this.currentStepName = config.name;
|
|
10306
|
-
this.log.debug(`Executing step: ${config.name}`);
|
|
10307
|
-
const stepStart = performance.now();
|
|
10308
|
-
const result = await config.execute();
|
|
10309
|
-
const durationMs = Math.round(performance.now() - stepStart);
|
|
10310
|
-
this.stepTimings.push({ name: config.name, durationMs });
|
|
10311
|
-
this.log.debug(`Step completed: ${config.name}`, { durationMs });
|
|
10312
|
-
this.completedSteps.push({
|
|
10313
|
-
name: config.name,
|
|
10314
|
-
rollback: () => config.rollback(result)
|
|
10315
|
-
});
|
|
10316
|
-
return result;
|
|
10317
|
-
}
|
|
10318
|
-
/**
|
|
10319
|
-
* Execute a step that doesn't need rollback.
|
|
10320
|
-
* Useful for read-only operations or operations that are idempotent.
|
|
10321
|
-
* The step name is automatically tracked for error reporting.
|
|
10322
|
-
*
|
|
10323
|
-
* @param name - Step name for logging and error tracking
|
|
10324
|
-
* @param execute - The action to execute
|
|
10325
|
-
* @returns The result of the execute function
|
|
10326
|
-
*/
|
|
10327
|
-
async readOnlyStep(name, execute) {
|
|
10328
|
-
this.currentStepName = name;
|
|
10329
|
-
this.log.debug(`Executing read-only step: ${name}`);
|
|
10330
|
-
const stepStart = performance.now();
|
|
10331
|
-
const result = await execute();
|
|
10332
|
-
const durationMs = Math.round(performance.now() - stepStart);
|
|
10333
|
-
this.stepTimings.push({ name, durationMs });
|
|
10334
|
-
this.log.debug(`Read-only step completed: ${name}`, { durationMs });
|
|
10335
|
-
return result;
|
|
10336
|
-
}
|
|
10337
|
-
/**
|
|
10338
|
-
* Roll back all completed steps in reverse order.
|
|
10339
|
-
* Rollback errors are logged but don't stop the rollback of other steps.
|
|
10340
|
-
*/
|
|
10341
|
-
async rollback() {
|
|
10342
|
-
this.log.info("Rolling back saga", {
|
|
10343
|
-
stepsToRollback: this.completedSteps.length
|
|
10344
|
-
});
|
|
10345
|
-
const stepsReversed = [...this.completedSteps].reverse();
|
|
10346
|
-
for (const step of stepsReversed) {
|
|
10347
|
-
try {
|
|
10348
|
-
this.log.debug(`Rolling back step: ${step.name}`);
|
|
10349
|
-
await step.rollback();
|
|
10350
|
-
this.log.debug(`Step rolled back: ${step.name}`);
|
|
10351
|
-
} catch (error) {
|
|
10352
|
-
this.log.error(`Failed to rollback step: ${step.name}`, {
|
|
10353
|
-
error: error instanceof Error ? error.message : String(error)
|
|
10354
|
-
});
|
|
10355
|
-
}
|
|
10356
|
-
}
|
|
10357
|
-
this.log.info("Rollback completed", {
|
|
10358
|
-
stepsAttempted: this.completedSteps.length
|
|
10359
|
-
});
|
|
10360
|
-
}
|
|
10361
|
-
/**
|
|
10362
|
-
* Get the number of completed steps (useful for testing)
|
|
10363
|
-
*/
|
|
10364
|
-
getCompletedStepCount() {
|
|
10365
|
-
return this.completedSteps.length;
|
|
10366
|
-
}
|
|
10367
|
-
};
|
|
10368
|
-
|
|
10369
|
-
// ../git/dist/git-saga.js
|
|
10370
|
-
var GitSaga = class extends Saga {
|
|
10371
|
-
_git = null;
|
|
10372
|
-
get git() {
|
|
10373
|
-
if (!this._git) {
|
|
10374
|
-
throw new Error("git client accessed before execute() was called");
|
|
10375
|
-
}
|
|
10376
|
-
return this._git;
|
|
10377
|
-
}
|
|
10378
|
-
async execute(input) {
|
|
10379
|
-
const manager = getGitOperationManager();
|
|
10380
|
-
return manager.executeWrite(input.baseDir, async (git) => {
|
|
10381
|
-
this._git = git;
|
|
10382
|
-
return this.executeGitOperations(input);
|
|
10383
|
-
}, { signal: input.signal });
|
|
10122
|
+
async execute(input) {
|
|
10123
|
+
const manager = getGitOperationManager();
|
|
10124
|
+
return manager.executeWrite(input.baseDir, async (git) => {
|
|
10125
|
+
this._git = git;
|
|
10126
|
+
return this.executeGitOperations(input);
|
|
10127
|
+
}, { signal: input.signal });
|
|
10384
10128
|
}
|
|
10385
10129
|
};
|
|
10386
10130
|
|
|
@@ -10646,18 +10390,18 @@ var ApplySnapshotSaga = class extends Saga {
|
|
|
10646
10390
|
archivePath = null;
|
|
10647
10391
|
async execute(input) {
|
|
10648
10392
|
const { snapshot, repositoryPath, apiClient, taskId, runId } = input;
|
|
10649
|
-
const tmpDir =
|
|
10393
|
+
const tmpDir = join7(repositoryPath, ".posthog", "tmp");
|
|
10650
10394
|
if (!snapshot.archiveUrl) {
|
|
10651
10395
|
throw new Error("Cannot apply snapshot: no archive URL");
|
|
10652
10396
|
}
|
|
10653
10397
|
const archiveUrl = snapshot.archiveUrl;
|
|
10654
10398
|
await this.step({
|
|
10655
10399
|
name: "create_tmp_dir",
|
|
10656
|
-
execute: () =>
|
|
10400
|
+
execute: () => mkdir4(tmpDir, { recursive: true }),
|
|
10657
10401
|
rollback: async () => {
|
|
10658
10402
|
}
|
|
10659
10403
|
});
|
|
10660
|
-
const archivePath =
|
|
10404
|
+
const archivePath = join7(tmpDir, `${snapshot.treeHash}.tar.gz`);
|
|
10661
10405
|
this.archivePath = archivePath;
|
|
10662
10406
|
await this.step({
|
|
10663
10407
|
name: "download_archive",
|
|
@@ -10672,7 +10416,7 @@ var ApplySnapshotSaga = class extends Saga {
|
|
|
10672
10416
|
}
|
|
10673
10417
|
const base64Content = Buffer.from(arrayBuffer).toString("utf-8");
|
|
10674
10418
|
const binaryContent = Buffer.from(base64Content, "base64");
|
|
10675
|
-
await
|
|
10419
|
+
await writeFile4(archivePath, binaryContent);
|
|
10676
10420
|
},
|
|
10677
10421
|
rollback: async () => {
|
|
10678
10422
|
if (this.archivePath) {
|
|
@@ -10706,7 +10450,7 @@ var ApplySnapshotSaga = class extends Saga {
|
|
|
10706
10450
|
// src/sagas/capture-tree-saga.ts
|
|
10707
10451
|
import { existsSync as existsSync5 } from "fs";
|
|
10708
10452
|
import { readFile as readFile3, rm as rm4 } from "fs/promises";
|
|
10709
|
-
import { join as
|
|
10453
|
+
import { join as join8 } from "path";
|
|
10710
10454
|
var CaptureTreeSaga2 = class extends Saga {
|
|
10711
10455
|
sagaName = "CaptureTreeSaga";
|
|
10712
10456
|
async execute(input) {
|
|
@@ -10718,14 +10462,14 @@ var CaptureTreeSaga2 = class extends Saga {
|
|
|
10718
10462
|
taskId,
|
|
10719
10463
|
runId
|
|
10720
10464
|
} = input;
|
|
10721
|
-
const tmpDir =
|
|
10722
|
-
if (existsSync5(
|
|
10465
|
+
const tmpDir = join8(repositoryPath, ".posthog", "tmp");
|
|
10466
|
+
if (existsSync5(join8(repositoryPath, ".gitmodules"))) {
|
|
10723
10467
|
this.log.warn(
|
|
10724
10468
|
"Repository has submodules - snapshot may not capture submodule state"
|
|
10725
10469
|
);
|
|
10726
10470
|
}
|
|
10727
10471
|
const shouldArchive = !!apiClient;
|
|
10728
|
-
const archivePath = shouldArchive ?
|
|
10472
|
+
const archivePath = shouldArchive ? join8(tmpDir, `tree-${Date.now()}.tar.gz`) : void 0;
|
|
10729
10473
|
const gitCaptureSaga = new CaptureTreeSaga(this.log);
|
|
10730
10474
|
const captureResult = await gitCaptureSaga.run({
|
|
10731
10475
|
baseDir: repositoryPath,
|
|
@@ -10846,57 +10590,632 @@ var TreeTracker = class {
|
|
|
10846
10590
|
`Failed to capture tree at step '${result.failedStep}': ${result.error}`
|
|
10847
10591
|
);
|
|
10848
10592
|
}
|
|
10849
|
-
if (result.data.newTreeHash !== null) {
|
|
10850
|
-
this.lastTreeHash = result.data.newTreeHash;
|
|
10593
|
+
if (result.data.newTreeHash !== null) {
|
|
10594
|
+
this.lastTreeHash = result.data.newTreeHash;
|
|
10595
|
+
}
|
|
10596
|
+
return result.data.snapshot;
|
|
10597
|
+
}
|
|
10598
|
+
/**
|
|
10599
|
+
* Download and apply a tree snapshot.
|
|
10600
|
+
* Uses Saga pattern for atomic operation with rollback on failure.
|
|
10601
|
+
*/
|
|
10602
|
+
async applyTreeSnapshot(snapshot) {
|
|
10603
|
+
if (!this.apiClient) {
|
|
10604
|
+
throw new Error("Cannot apply snapshot: API client not configured");
|
|
10605
|
+
}
|
|
10606
|
+
if (!snapshot.archiveUrl) {
|
|
10607
|
+
this.logger.warn("Cannot apply snapshot: no archive URL", {
|
|
10608
|
+
treeHash: snapshot.treeHash,
|
|
10609
|
+
changes: snapshot.changes.length
|
|
10610
|
+
});
|
|
10611
|
+
throw new Error("Cannot apply snapshot: no archive URL");
|
|
10612
|
+
}
|
|
10613
|
+
const saga = new ApplySnapshotSaga(this.logger);
|
|
10614
|
+
const result = await saga.run({
|
|
10615
|
+
snapshot,
|
|
10616
|
+
repositoryPath: this.repositoryPath,
|
|
10617
|
+
apiClient: this.apiClient,
|
|
10618
|
+
taskId: this.taskId,
|
|
10619
|
+
runId: this.runId
|
|
10620
|
+
});
|
|
10621
|
+
if (!result.success) {
|
|
10622
|
+
this.logger.error("Failed to apply tree snapshot", {
|
|
10623
|
+
error: result.error,
|
|
10624
|
+
failedStep: result.failedStep,
|
|
10625
|
+
treeHash: snapshot.treeHash
|
|
10626
|
+
});
|
|
10627
|
+
throw new Error(
|
|
10628
|
+
`Failed to apply snapshot at step '${result.failedStep}': ${result.error}`
|
|
10629
|
+
);
|
|
10630
|
+
}
|
|
10631
|
+
this.lastTreeHash = result.data.treeHash;
|
|
10632
|
+
}
|
|
10633
|
+
/**
|
|
10634
|
+
* Get the last captured tree hash.
|
|
10635
|
+
*/
|
|
10636
|
+
getLastTreeHash() {
|
|
10637
|
+
return this.lastTreeHash;
|
|
10638
|
+
}
|
|
10639
|
+
/**
|
|
10640
|
+
* Set the last tree hash (used when resuming).
|
|
10641
|
+
*/
|
|
10642
|
+
setLastTreeHash(hash) {
|
|
10643
|
+
this.lastTreeHash = hash;
|
|
10644
|
+
}
|
|
10645
|
+
};
|
|
10646
|
+
|
|
10647
|
+
// src/sagas/resume-saga.ts
|
|
10648
|
+
var ResumeSaga = class extends Saga {
|
|
10649
|
+
sagaName = "ResumeSaga";
|
|
10650
|
+
async execute(input) {
|
|
10651
|
+
const { taskId, runId, repositoryPath, apiClient } = input;
|
|
10652
|
+
const logger = input.logger || new Logger({ debug: false, prefix: "[Resume]" });
|
|
10653
|
+
const taskRun = await this.readOnlyStep(
|
|
10654
|
+
"fetch_task_run",
|
|
10655
|
+
() => apiClient.getTaskRun(taskId, runId)
|
|
10656
|
+
);
|
|
10657
|
+
if (!taskRun.log_url) {
|
|
10658
|
+
this.log.info("No log URL found, starting fresh");
|
|
10659
|
+
return this.emptyResult();
|
|
10660
|
+
}
|
|
10661
|
+
const entries = await this.readOnlyStep(
|
|
10662
|
+
"fetch_logs",
|
|
10663
|
+
() => apiClient.fetchTaskRunLogs(taskRun)
|
|
10664
|
+
);
|
|
10665
|
+
if (entries.length === 0) {
|
|
10666
|
+
this.log.info("No log entries found, starting fresh");
|
|
10667
|
+
return this.emptyResult();
|
|
10668
|
+
}
|
|
10669
|
+
this.log.info("Fetched log entries", { count: entries.length });
|
|
10670
|
+
const latestSnapshot = await this.readOnlyStep(
|
|
10671
|
+
"find_snapshot",
|
|
10672
|
+
() => Promise.resolve(this.findLatestTreeSnapshot(entries))
|
|
10673
|
+
);
|
|
10674
|
+
let snapshotApplied = false;
|
|
10675
|
+
if (latestSnapshot?.archiveUrl && repositoryPath) {
|
|
10676
|
+
this.log.info("Found tree snapshot", {
|
|
10677
|
+
treeHash: latestSnapshot.treeHash,
|
|
10678
|
+
hasArchiveUrl: true,
|
|
10679
|
+
changes: latestSnapshot.changes?.length ?? 0,
|
|
10680
|
+
interrupted: latestSnapshot.interrupted
|
|
10681
|
+
});
|
|
10682
|
+
await this.step({
|
|
10683
|
+
name: "apply_snapshot",
|
|
10684
|
+
execute: async () => {
|
|
10685
|
+
const treeTracker = new TreeTracker({
|
|
10686
|
+
repositoryPath,
|
|
10687
|
+
taskId,
|
|
10688
|
+
runId,
|
|
10689
|
+
apiClient,
|
|
10690
|
+
logger: logger.child("TreeTracker")
|
|
10691
|
+
});
|
|
10692
|
+
try {
|
|
10693
|
+
await treeTracker.applyTreeSnapshot(latestSnapshot);
|
|
10694
|
+
treeTracker.setLastTreeHash(latestSnapshot.treeHash);
|
|
10695
|
+
snapshotApplied = true;
|
|
10696
|
+
this.log.info("Tree snapshot applied successfully", {
|
|
10697
|
+
treeHash: latestSnapshot.treeHash
|
|
10698
|
+
});
|
|
10699
|
+
} catch (error) {
|
|
10700
|
+
this.log.warn(
|
|
10701
|
+
"Failed to apply tree snapshot, continuing without it",
|
|
10702
|
+
{
|
|
10703
|
+
error: error instanceof Error ? error.message : String(error),
|
|
10704
|
+
treeHash: latestSnapshot.treeHash
|
|
10705
|
+
}
|
|
10706
|
+
);
|
|
10707
|
+
}
|
|
10708
|
+
},
|
|
10709
|
+
rollback: async () => {
|
|
10710
|
+
}
|
|
10711
|
+
});
|
|
10712
|
+
} else if (latestSnapshot?.archiveUrl && !repositoryPath) {
|
|
10713
|
+
this.log.warn(
|
|
10714
|
+
"Snapshot found but no repositoryPath configured - files cannot be restored",
|
|
10715
|
+
{
|
|
10716
|
+
treeHash: latestSnapshot.treeHash,
|
|
10717
|
+
changes: latestSnapshot.changes?.length ?? 0
|
|
10718
|
+
}
|
|
10719
|
+
);
|
|
10720
|
+
} else if (latestSnapshot) {
|
|
10721
|
+
this.log.warn(
|
|
10722
|
+
"Snapshot found but has no archive URL - files cannot be restored",
|
|
10723
|
+
{
|
|
10724
|
+
treeHash: latestSnapshot.treeHash,
|
|
10725
|
+
changes: latestSnapshot.changes?.length ?? 0
|
|
10726
|
+
}
|
|
10727
|
+
);
|
|
10728
|
+
}
|
|
10729
|
+
const conversation = await this.readOnlyStep(
|
|
10730
|
+
"rebuild_conversation",
|
|
10731
|
+
() => Promise.resolve(this.rebuildConversation(entries))
|
|
10732
|
+
);
|
|
10733
|
+
const lastDevice = await this.readOnlyStep(
|
|
10734
|
+
"find_device",
|
|
10735
|
+
() => Promise.resolve(this.findLastDeviceInfo(entries))
|
|
10736
|
+
);
|
|
10737
|
+
this.log.info("Resume state rebuilt", {
|
|
10738
|
+
turns: conversation.length,
|
|
10739
|
+
hasSnapshot: !!latestSnapshot,
|
|
10740
|
+
snapshotApplied,
|
|
10741
|
+
interrupted: latestSnapshot?.interrupted ?? false
|
|
10742
|
+
});
|
|
10743
|
+
return {
|
|
10744
|
+
conversation,
|
|
10745
|
+
latestSnapshot,
|
|
10746
|
+
snapshotApplied,
|
|
10747
|
+
interrupted: latestSnapshot?.interrupted ?? false,
|
|
10748
|
+
lastDevice,
|
|
10749
|
+
logEntryCount: entries.length
|
|
10750
|
+
};
|
|
10751
|
+
}
|
|
10752
|
+
emptyResult() {
|
|
10753
|
+
return {
|
|
10754
|
+
conversation: [],
|
|
10755
|
+
latestSnapshot: null,
|
|
10756
|
+
snapshotApplied: false,
|
|
10757
|
+
interrupted: false,
|
|
10758
|
+
logEntryCount: 0
|
|
10759
|
+
};
|
|
10760
|
+
}
|
|
10761
|
+
findLatestTreeSnapshot(entries) {
|
|
10762
|
+
const sdkPrefixedMethod = `_${POSTHOG_NOTIFICATIONS.TREE_SNAPSHOT}`;
|
|
10763
|
+
for (let i = entries.length - 1; i >= 0; i--) {
|
|
10764
|
+
const entry = entries[i];
|
|
10765
|
+
const method = entry.notification?.method;
|
|
10766
|
+
if (method === sdkPrefixedMethod || method === POSTHOG_NOTIFICATIONS.TREE_SNAPSHOT) {
|
|
10767
|
+
const params = entry.notification.params;
|
|
10768
|
+
if (params?.treeHash) {
|
|
10769
|
+
return params;
|
|
10770
|
+
}
|
|
10771
|
+
}
|
|
10772
|
+
}
|
|
10773
|
+
return null;
|
|
10774
|
+
}
|
|
10775
|
+
findLastDeviceInfo(entries) {
|
|
10776
|
+
for (let i = entries.length - 1; i >= 0; i--) {
|
|
10777
|
+
const entry = entries[i];
|
|
10778
|
+
const params = entry.notification?.params;
|
|
10779
|
+
if (params?.device) {
|
|
10780
|
+
return params.device;
|
|
10781
|
+
}
|
|
10782
|
+
}
|
|
10783
|
+
return void 0;
|
|
10784
|
+
}
|
|
10785
|
+
rebuildConversation(entries) {
|
|
10786
|
+
const turns = [];
|
|
10787
|
+
let currentAssistantContent = [];
|
|
10788
|
+
let currentToolCalls = [];
|
|
10789
|
+
for (const entry of entries) {
|
|
10790
|
+
const method = entry.notification?.method;
|
|
10791
|
+
const params = entry.notification?.params;
|
|
10792
|
+
if (method === "session/update" && params?.update) {
|
|
10793
|
+
const update = params.update;
|
|
10794
|
+
const sessionUpdate = update.sessionUpdate;
|
|
10795
|
+
switch (sessionUpdate) {
|
|
10796
|
+
case "user_message":
|
|
10797
|
+
case "user_message_chunk": {
|
|
10798
|
+
if (currentAssistantContent.length > 0 || currentToolCalls.length > 0) {
|
|
10799
|
+
turns.push({
|
|
10800
|
+
role: "assistant",
|
|
10801
|
+
content: currentAssistantContent,
|
|
10802
|
+
toolCalls: currentToolCalls.length > 0 ? currentToolCalls : void 0
|
|
10803
|
+
});
|
|
10804
|
+
currentAssistantContent = [];
|
|
10805
|
+
currentToolCalls = [];
|
|
10806
|
+
}
|
|
10807
|
+
const content = update.content;
|
|
10808
|
+
const contentArray = Array.isArray(content) ? content : [content];
|
|
10809
|
+
turns.push({
|
|
10810
|
+
role: "user",
|
|
10811
|
+
content: contentArray
|
|
10812
|
+
});
|
|
10813
|
+
break;
|
|
10814
|
+
}
|
|
10815
|
+
case "agent_message": {
|
|
10816
|
+
const content = update.content;
|
|
10817
|
+
if (content) {
|
|
10818
|
+
if (content.type === "text" && currentAssistantContent.length > 0 && currentAssistantContent[currentAssistantContent.length - 1].type === "text") {
|
|
10819
|
+
const lastBlock = currentAssistantContent[currentAssistantContent.length - 1];
|
|
10820
|
+
lastBlock.text += content.text;
|
|
10821
|
+
} else {
|
|
10822
|
+
currentAssistantContent.push(content);
|
|
10823
|
+
}
|
|
10824
|
+
}
|
|
10825
|
+
break;
|
|
10826
|
+
}
|
|
10827
|
+
case "agent_message_chunk": {
|
|
10828
|
+
const content = update.content;
|
|
10829
|
+
if (content) {
|
|
10830
|
+
if (content.type === "text" && currentAssistantContent.length > 0 && currentAssistantContent[currentAssistantContent.length - 1].type === "text") {
|
|
10831
|
+
const lastBlock = currentAssistantContent[currentAssistantContent.length - 1];
|
|
10832
|
+
lastBlock.text += content.text;
|
|
10833
|
+
} else {
|
|
10834
|
+
currentAssistantContent.push(content);
|
|
10835
|
+
}
|
|
10836
|
+
}
|
|
10837
|
+
break;
|
|
10838
|
+
}
|
|
10839
|
+
case "tool_call":
|
|
10840
|
+
case "tool_call_update": {
|
|
10841
|
+
const meta = update._meta?.claudeCode;
|
|
10842
|
+
if (meta) {
|
|
10843
|
+
const toolCallId = meta.toolCallId;
|
|
10844
|
+
const toolName = meta.toolName;
|
|
10845
|
+
const toolInput = meta.toolInput;
|
|
10846
|
+
const toolResponse = meta.toolResponse;
|
|
10847
|
+
if (toolCallId && toolName) {
|
|
10848
|
+
let toolCall = currentToolCalls.find(
|
|
10849
|
+
(tc) => tc.toolCallId === toolCallId
|
|
10850
|
+
);
|
|
10851
|
+
if (!toolCall) {
|
|
10852
|
+
toolCall = {
|
|
10853
|
+
toolCallId,
|
|
10854
|
+
toolName,
|
|
10855
|
+
input: toolInput
|
|
10856
|
+
};
|
|
10857
|
+
currentToolCalls.push(toolCall);
|
|
10858
|
+
}
|
|
10859
|
+
if (toolResponse !== void 0) {
|
|
10860
|
+
toolCall.result = toolResponse;
|
|
10861
|
+
}
|
|
10862
|
+
}
|
|
10863
|
+
}
|
|
10864
|
+
break;
|
|
10865
|
+
}
|
|
10866
|
+
case "tool_result": {
|
|
10867
|
+
const meta = update._meta?.claudeCode;
|
|
10868
|
+
if (meta) {
|
|
10869
|
+
const toolCallId = meta.toolCallId;
|
|
10870
|
+
const toolResponse = meta.toolResponse;
|
|
10871
|
+
if (toolCallId) {
|
|
10872
|
+
const toolCall = currentToolCalls.find(
|
|
10873
|
+
(tc) => tc.toolCallId === toolCallId
|
|
10874
|
+
);
|
|
10875
|
+
if (toolCall && toolResponse !== void 0) {
|
|
10876
|
+
toolCall.result = toolResponse;
|
|
10877
|
+
}
|
|
10878
|
+
}
|
|
10879
|
+
}
|
|
10880
|
+
break;
|
|
10881
|
+
}
|
|
10882
|
+
}
|
|
10883
|
+
}
|
|
10884
|
+
}
|
|
10885
|
+
if (currentAssistantContent.length > 0 || currentToolCalls.length > 0) {
|
|
10886
|
+
turns.push({
|
|
10887
|
+
role: "assistant",
|
|
10888
|
+
content: currentAssistantContent,
|
|
10889
|
+
toolCalls: currentToolCalls.length > 0 ? currentToolCalls : void 0
|
|
10890
|
+
});
|
|
10891
|
+
}
|
|
10892
|
+
return turns;
|
|
10893
|
+
}
|
|
10894
|
+
};
|
|
10895
|
+
|
|
10896
|
+
// src/resume.ts
|
|
10897
|
+
async function resumeFromLog(config) {
|
|
10898
|
+
const logger = config.logger || new Logger({ debug: false, prefix: "[Resume]" });
|
|
10899
|
+
logger.info("Resuming from log", {
|
|
10900
|
+
taskId: config.taskId,
|
|
10901
|
+
runId: config.runId
|
|
10902
|
+
});
|
|
10903
|
+
const saga = new ResumeSaga(logger);
|
|
10904
|
+
const result = await saga.run({
|
|
10905
|
+
taskId: config.taskId,
|
|
10906
|
+
runId: config.runId,
|
|
10907
|
+
repositoryPath: config.repositoryPath,
|
|
10908
|
+
apiClient: config.apiClient,
|
|
10909
|
+
logger
|
|
10910
|
+
});
|
|
10911
|
+
if (!result.success) {
|
|
10912
|
+
logger.error("Failed to resume from log", {
|
|
10913
|
+
error: result.error,
|
|
10914
|
+
failedStep: result.failedStep
|
|
10915
|
+
});
|
|
10916
|
+
throw new Error(
|
|
10917
|
+
`Failed to resume at step '${result.failedStep}': ${result.error}`
|
|
10918
|
+
);
|
|
10919
|
+
}
|
|
10920
|
+
return {
|
|
10921
|
+
conversation: result.data.conversation,
|
|
10922
|
+
latestSnapshot: result.data.latestSnapshot,
|
|
10923
|
+
snapshotApplied: result.data.snapshotApplied,
|
|
10924
|
+
interrupted: result.data.interrupted,
|
|
10925
|
+
lastDevice: result.data.lastDevice,
|
|
10926
|
+
logEntryCount: result.data.logEntryCount
|
|
10927
|
+
};
|
|
10928
|
+
}
|
|
10929
|
+
|
|
10930
|
+
// src/session-log-writer.ts
|
|
10931
|
+
import fs8 from "fs";
|
|
10932
|
+
import fsp from "fs/promises";
|
|
10933
|
+
import path10 from "path";
|
|
10934
|
+
var SessionLogWriter = class _SessionLogWriter {
|
|
10935
|
+
static FLUSH_DEBOUNCE_MS = 500;
|
|
10936
|
+
static FLUSH_MAX_INTERVAL_MS = 5e3;
|
|
10937
|
+
static MAX_FLUSH_RETRIES = 10;
|
|
10938
|
+
static MAX_RETRY_DELAY_MS = 3e4;
|
|
10939
|
+
static SESSIONS_MAX_AGE_MS = 30 * 24 * 60 * 60 * 1e3;
|
|
10940
|
+
posthogAPI;
|
|
10941
|
+
pendingEntries = /* @__PURE__ */ new Map();
|
|
10942
|
+
flushTimeouts = /* @__PURE__ */ new Map();
|
|
10943
|
+
lastFlushAttemptTime = /* @__PURE__ */ new Map();
|
|
10944
|
+
retryCounts = /* @__PURE__ */ new Map();
|
|
10945
|
+
sessions = /* @__PURE__ */ new Map();
|
|
10946
|
+
logger;
|
|
10947
|
+
localCachePath;
|
|
10948
|
+
constructor(options = {}) {
|
|
10949
|
+
this.posthogAPI = options.posthogAPI;
|
|
10950
|
+
this.localCachePath = options.localCachePath;
|
|
10951
|
+
this.logger = options.logger ?? new Logger({ debug: false, prefix: "[SessionLogWriter]" });
|
|
10952
|
+
}
|
|
10953
|
+
async flushAll() {
|
|
10954
|
+
const sessionIds = [...this.sessions.keys()];
|
|
10955
|
+
const flushPromises = [];
|
|
10956
|
+
for (const sessionId of sessionIds) {
|
|
10957
|
+
flushPromises.push(this.flush(sessionId));
|
|
10958
|
+
}
|
|
10959
|
+
await Promise.all(flushPromises);
|
|
10960
|
+
}
|
|
10961
|
+
register(sessionId, context) {
|
|
10962
|
+
if (this.sessions.has(sessionId)) {
|
|
10963
|
+
return;
|
|
10964
|
+
}
|
|
10965
|
+
this.logger.info("Session registered", {
|
|
10966
|
+
taskId: context.taskId,
|
|
10967
|
+
runId: context.runId
|
|
10968
|
+
});
|
|
10969
|
+
this.sessions.set(sessionId, { context });
|
|
10970
|
+
this.lastFlushAttemptTime.set(sessionId, Date.now());
|
|
10971
|
+
if (this.localCachePath) {
|
|
10972
|
+
const sessionDir = path10.join(
|
|
10973
|
+
this.localCachePath,
|
|
10974
|
+
"sessions",
|
|
10975
|
+
context.runId
|
|
10976
|
+
);
|
|
10977
|
+
try {
|
|
10978
|
+
fs8.mkdirSync(sessionDir, { recursive: true });
|
|
10979
|
+
} catch (error) {
|
|
10980
|
+
this.logger.warn("Failed to create local cache directory", {
|
|
10981
|
+
sessionDir,
|
|
10982
|
+
error
|
|
10983
|
+
});
|
|
10984
|
+
}
|
|
10985
|
+
}
|
|
10986
|
+
}
|
|
10987
|
+
isRegistered(sessionId) {
|
|
10988
|
+
return this.sessions.has(sessionId);
|
|
10989
|
+
}
|
|
10990
|
+
appendRawLine(sessionId, line) {
|
|
10991
|
+
const session = this.sessions.get(sessionId);
|
|
10992
|
+
if (!session) {
|
|
10993
|
+
this.logger.warn("appendRawLine called for unregistered session", {
|
|
10994
|
+
sessionId
|
|
10995
|
+
});
|
|
10996
|
+
return;
|
|
10997
|
+
}
|
|
10998
|
+
try {
|
|
10999
|
+
const message = JSON.parse(line);
|
|
11000
|
+
const timestamp = (/* @__PURE__ */ new Date()).toISOString();
|
|
11001
|
+
if (this.isAgentMessageChunk(message)) {
|
|
11002
|
+
const text2 = this.extractChunkText(message);
|
|
11003
|
+
if (text2) {
|
|
11004
|
+
if (!session.chunkBuffer) {
|
|
11005
|
+
session.chunkBuffer = { text: text2, firstTimestamp: timestamp };
|
|
11006
|
+
} else {
|
|
11007
|
+
session.chunkBuffer.text += text2;
|
|
11008
|
+
}
|
|
11009
|
+
}
|
|
11010
|
+
return;
|
|
11011
|
+
}
|
|
11012
|
+
this.emitCoalescedMessage(sessionId, session);
|
|
11013
|
+
const nonChunkAgentText = this.extractAgentMessageText(message);
|
|
11014
|
+
if (nonChunkAgentText) {
|
|
11015
|
+
session.lastAgentMessage = nonChunkAgentText;
|
|
11016
|
+
}
|
|
11017
|
+
const entry = {
|
|
11018
|
+
type: "notification",
|
|
11019
|
+
timestamp,
|
|
11020
|
+
notification: message
|
|
11021
|
+
};
|
|
11022
|
+
this.writeToLocalCache(sessionId, entry);
|
|
11023
|
+
if (this.posthogAPI) {
|
|
11024
|
+
const pending = this.pendingEntries.get(sessionId) ?? [];
|
|
11025
|
+
pending.push(entry);
|
|
11026
|
+
this.pendingEntries.set(sessionId, pending);
|
|
11027
|
+
this.scheduleFlush(sessionId);
|
|
11028
|
+
}
|
|
11029
|
+
} catch {
|
|
11030
|
+
this.logger.warn("Failed to parse raw line for persistence", {
|
|
11031
|
+
taskId: session.context.taskId,
|
|
11032
|
+
runId: session.context.runId,
|
|
11033
|
+
lineLength: line.length
|
|
11034
|
+
});
|
|
11035
|
+
}
|
|
11036
|
+
}
|
|
11037
|
+
async flush(sessionId) {
|
|
11038
|
+
const session = this.sessions.get(sessionId);
|
|
11039
|
+
if (!session) {
|
|
11040
|
+
this.logger.warn("flush: no session found", { sessionId });
|
|
11041
|
+
return;
|
|
11042
|
+
}
|
|
11043
|
+
this.emitCoalescedMessage(sessionId, session);
|
|
11044
|
+
const pending = this.pendingEntries.get(sessionId);
|
|
11045
|
+
if (!this.posthogAPI || !pending?.length) {
|
|
11046
|
+
return;
|
|
11047
|
+
}
|
|
11048
|
+
this.pendingEntries.delete(sessionId);
|
|
11049
|
+
const timeout = this.flushTimeouts.get(sessionId);
|
|
11050
|
+
if (timeout) {
|
|
11051
|
+
clearTimeout(timeout);
|
|
11052
|
+
this.flushTimeouts.delete(sessionId);
|
|
11053
|
+
}
|
|
11054
|
+
this.lastFlushAttemptTime.set(sessionId, Date.now());
|
|
11055
|
+
try {
|
|
11056
|
+
await this.posthogAPI.appendTaskRunLog(
|
|
11057
|
+
session.context.taskId,
|
|
11058
|
+
session.context.runId,
|
|
11059
|
+
pending
|
|
11060
|
+
);
|
|
11061
|
+
this.retryCounts.set(sessionId, 0);
|
|
11062
|
+
} catch (error) {
|
|
11063
|
+
const retryCount = (this.retryCounts.get(sessionId) ?? 0) + 1;
|
|
11064
|
+
this.retryCounts.set(sessionId, retryCount);
|
|
11065
|
+
if (retryCount >= _SessionLogWriter.MAX_FLUSH_RETRIES) {
|
|
11066
|
+
this.logger.error(
|
|
11067
|
+
`Dropping ${pending.length} session log entries after ${retryCount} failed flush attempts`,
|
|
11068
|
+
{
|
|
11069
|
+
taskId: session.context.taskId,
|
|
11070
|
+
runId: session.context.runId,
|
|
11071
|
+
error
|
|
11072
|
+
}
|
|
11073
|
+
);
|
|
11074
|
+
this.retryCounts.set(sessionId, 0);
|
|
11075
|
+
} else {
|
|
11076
|
+
if (retryCount === 1) {
|
|
11077
|
+
this.logger.warn(
|
|
11078
|
+
`Failed to persist session logs, will retry (up to ${_SessionLogWriter.MAX_FLUSH_RETRIES} attempts)`,
|
|
11079
|
+
{
|
|
11080
|
+
taskId: session.context.taskId,
|
|
11081
|
+
runId: session.context.runId,
|
|
11082
|
+
error: error instanceof Error ? error.message : String(error)
|
|
11083
|
+
}
|
|
11084
|
+
);
|
|
11085
|
+
}
|
|
11086
|
+
const currentPending = this.pendingEntries.get(sessionId) ?? [];
|
|
11087
|
+
this.pendingEntries.set(sessionId, [...pending, ...currentPending]);
|
|
11088
|
+
this.scheduleFlush(sessionId);
|
|
11089
|
+
}
|
|
11090
|
+
}
|
|
11091
|
+
}
|
|
11092
|
+
isAgentMessageChunk(message) {
|
|
11093
|
+
if (message.method !== "session/update") return false;
|
|
11094
|
+
const params = message.params;
|
|
11095
|
+
const update = params?.update;
|
|
11096
|
+
return update?.sessionUpdate === "agent_message_chunk";
|
|
11097
|
+
}
|
|
11098
|
+
extractChunkText(message) {
|
|
11099
|
+
const params = message.params;
|
|
11100
|
+
const update = params?.update;
|
|
11101
|
+
const content = update?.content;
|
|
11102
|
+
if (content?.type === "text" && content.text) {
|
|
11103
|
+
return content.text;
|
|
11104
|
+
}
|
|
11105
|
+
return "";
|
|
11106
|
+
}
|
|
11107
|
+
emitCoalescedMessage(sessionId, session) {
|
|
11108
|
+
if (!session.chunkBuffer) return;
|
|
11109
|
+
const { text: text2, firstTimestamp } = session.chunkBuffer;
|
|
11110
|
+
session.chunkBuffer = void 0;
|
|
11111
|
+
session.lastAgentMessage = text2;
|
|
11112
|
+
const entry = {
|
|
11113
|
+
type: "notification",
|
|
11114
|
+
timestamp: firstTimestamp,
|
|
11115
|
+
notification: {
|
|
11116
|
+
jsonrpc: "2.0",
|
|
11117
|
+
method: "session/update",
|
|
11118
|
+
params: {
|
|
11119
|
+
update: {
|
|
11120
|
+
sessionUpdate: "agent_message",
|
|
11121
|
+
content: { type: "text", text: text2 }
|
|
11122
|
+
}
|
|
11123
|
+
}
|
|
11124
|
+
}
|
|
11125
|
+
};
|
|
11126
|
+
this.writeToLocalCache(sessionId, entry);
|
|
11127
|
+
if (this.posthogAPI) {
|
|
11128
|
+
const pending = this.pendingEntries.get(sessionId) ?? [];
|
|
11129
|
+
pending.push(entry);
|
|
11130
|
+
this.pendingEntries.set(sessionId, pending);
|
|
11131
|
+
this.scheduleFlush(sessionId);
|
|
10851
11132
|
}
|
|
10852
|
-
return result.data.snapshot;
|
|
10853
11133
|
}
|
|
10854
|
-
|
|
10855
|
-
|
|
10856
|
-
|
|
10857
|
-
|
|
10858
|
-
|
|
10859
|
-
|
|
10860
|
-
throw new Error("Cannot apply snapshot: API client not configured");
|
|
11134
|
+
getLastAgentMessage(sessionId) {
|
|
11135
|
+
return this.sessions.get(sessionId)?.lastAgentMessage;
|
|
11136
|
+
}
|
|
11137
|
+
extractAgentMessageText(message) {
|
|
11138
|
+
if (message.method !== "session/update") {
|
|
11139
|
+
return null;
|
|
10861
11140
|
}
|
|
10862
|
-
|
|
10863
|
-
|
|
10864
|
-
|
|
10865
|
-
|
|
10866
|
-
});
|
|
10867
|
-
throw new Error("Cannot apply snapshot: no archive URL");
|
|
11141
|
+
const params = message.params;
|
|
11142
|
+
const update = params?.update;
|
|
11143
|
+
if (update?.sessionUpdate !== "agent_message") {
|
|
11144
|
+
return null;
|
|
10868
11145
|
}
|
|
10869
|
-
const
|
|
10870
|
-
|
|
10871
|
-
|
|
10872
|
-
|
|
10873
|
-
|
|
10874
|
-
|
|
10875
|
-
|
|
10876
|
-
|
|
10877
|
-
|
|
10878
|
-
|
|
10879
|
-
|
|
10880
|
-
|
|
10881
|
-
|
|
10882
|
-
|
|
10883
|
-
|
|
10884
|
-
|
|
11146
|
+
const content = update.content;
|
|
11147
|
+
if (content?.type === "text" && typeof content.text === "string") {
|
|
11148
|
+
const trimmed2 = content.text.trim();
|
|
11149
|
+
return trimmed2.length > 0 ? trimmed2 : null;
|
|
11150
|
+
}
|
|
11151
|
+
if (typeof update.message === "string") {
|
|
11152
|
+
const trimmed2 = update.message.trim();
|
|
11153
|
+
return trimmed2.length > 0 ? trimmed2 : null;
|
|
11154
|
+
}
|
|
11155
|
+
return null;
|
|
11156
|
+
}
|
|
11157
|
+
scheduleFlush(sessionId) {
|
|
11158
|
+
const existing = this.flushTimeouts.get(sessionId);
|
|
11159
|
+
if (existing) clearTimeout(existing);
|
|
11160
|
+
const retryCount = this.retryCounts.get(sessionId) ?? 0;
|
|
11161
|
+
const lastAttempt = this.lastFlushAttemptTime.get(sessionId) ?? 0;
|
|
11162
|
+
const elapsed = Date.now() - lastAttempt;
|
|
11163
|
+
let delay3;
|
|
11164
|
+
if (retryCount > 0) {
|
|
11165
|
+
delay3 = Math.min(
|
|
11166
|
+
_SessionLogWriter.FLUSH_DEBOUNCE_MS * 2 ** retryCount,
|
|
11167
|
+
_SessionLogWriter.MAX_RETRY_DELAY_MS
|
|
10885
11168
|
);
|
|
11169
|
+
} else if (elapsed >= _SessionLogWriter.FLUSH_MAX_INTERVAL_MS) {
|
|
11170
|
+
delay3 = 0;
|
|
11171
|
+
} else {
|
|
11172
|
+
delay3 = _SessionLogWriter.FLUSH_DEBOUNCE_MS;
|
|
10886
11173
|
}
|
|
10887
|
-
|
|
11174
|
+
const timeout = setTimeout(() => this.flush(sessionId), delay3);
|
|
11175
|
+
this.flushTimeouts.set(sessionId, timeout);
|
|
10888
11176
|
}
|
|
10889
|
-
|
|
10890
|
-
|
|
10891
|
-
|
|
10892
|
-
|
|
10893
|
-
|
|
11177
|
+
writeToLocalCache(sessionId, entry) {
|
|
11178
|
+
if (!this.localCachePath) return;
|
|
11179
|
+
const session = this.sessions.get(sessionId);
|
|
11180
|
+
if (!session) return;
|
|
11181
|
+
const logPath = path10.join(
|
|
11182
|
+
this.localCachePath,
|
|
11183
|
+
"sessions",
|
|
11184
|
+
session.context.runId,
|
|
11185
|
+
"logs.ndjson"
|
|
11186
|
+
);
|
|
11187
|
+
try {
|
|
11188
|
+
fs8.appendFileSync(logPath, `${JSON.stringify(entry)}
|
|
11189
|
+
`);
|
|
11190
|
+
} catch (error) {
|
|
11191
|
+
this.logger.warn("Failed to write to local cache", {
|
|
11192
|
+
taskId: session.context.taskId,
|
|
11193
|
+
runId: session.context.runId,
|
|
11194
|
+
logPath,
|
|
11195
|
+
error
|
|
11196
|
+
});
|
|
11197
|
+
}
|
|
10894
11198
|
}
|
|
10895
|
-
|
|
10896
|
-
|
|
10897
|
-
|
|
10898
|
-
|
|
10899
|
-
|
|
11199
|
+
static async cleanupOldSessions(localCachePath) {
|
|
11200
|
+
const sessionsDir = path10.join(localCachePath, "sessions");
|
|
11201
|
+
let deleted = 0;
|
|
11202
|
+
try {
|
|
11203
|
+
const entries = await fsp.readdir(sessionsDir);
|
|
11204
|
+
const now = Date.now();
|
|
11205
|
+
for (const entry of entries) {
|
|
11206
|
+
const entryPath = path10.join(sessionsDir, entry);
|
|
11207
|
+
try {
|
|
11208
|
+
const stats = await fsp.stat(entryPath);
|
|
11209
|
+
if (stats.isDirectory() && now - stats.birthtimeMs > _SessionLogWriter.SESSIONS_MAX_AGE_MS) {
|
|
11210
|
+
await fsp.rm(entryPath, { recursive: true, force: true });
|
|
11211
|
+
deleted++;
|
|
11212
|
+
}
|
|
11213
|
+
} catch {
|
|
11214
|
+
}
|
|
11215
|
+
}
|
|
11216
|
+
} catch {
|
|
11217
|
+
}
|
|
11218
|
+
return deleted;
|
|
10900
11219
|
}
|
|
10901
11220
|
};
|
|
10902
11221
|
|
|
@@ -11109,7 +11428,7 @@ function createTappedWritableStream2(underlying, onMessage, logger) {
|
|
|
11109
11428
|
}
|
|
11110
11429
|
});
|
|
11111
11430
|
}
|
|
11112
|
-
var AgentServer = class {
|
|
11431
|
+
var AgentServer = class _AgentServer {
|
|
11113
11432
|
config;
|
|
11114
11433
|
logger;
|
|
11115
11434
|
server = null;
|
|
@@ -11118,6 +11437,7 @@ var AgentServer = class {
|
|
|
11118
11437
|
posthogAPI;
|
|
11119
11438
|
questionRelayedToSlack = false;
|
|
11120
11439
|
detectedPrUrl = null;
|
|
11440
|
+
resumeState = null;
|
|
11121
11441
|
emitConsoleLog = (level, _scope, message, data) => {
|
|
11122
11442
|
if (!this.session) return;
|
|
11123
11443
|
const formatted = data !== void 0 ? `${message} ${JSON.stringify(data)}` : message;
|
|
@@ -11300,6 +11620,32 @@ var AgentServer = class {
|
|
|
11300
11620
|
async autoInitializeSession() {
|
|
11301
11621
|
const { taskId, runId, mode, projectId } = this.config;
|
|
11302
11622
|
this.logger.info("Auto-initializing session", { taskId, runId, mode });
|
|
11623
|
+
const resumeRunId = process.env.POSTHOG_RESUME_RUN_ID;
|
|
11624
|
+
if (resumeRunId) {
|
|
11625
|
+
this.logger.info("Resuming from previous run", {
|
|
11626
|
+
resumeRunId,
|
|
11627
|
+
currentRunId: runId
|
|
11628
|
+
});
|
|
11629
|
+
try {
|
|
11630
|
+
this.resumeState = await resumeFromLog({
|
|
11631
|
+
taskId,
|
|
11632
|
+
runId: resumeRunId,
|
|
11633
|
+
repositoryPath: this.config.repositoryPath,
|
|
11634
|
+
apiClient: this.posthogAPI,
|
|
11635
|
+
logger: new Logger({ debug: true, prefix: "[Resume]" })
|
|
11636
|
+
});
|
|
11637
|
+
this.logger.info("Resume state loaded", {
|
|
11638
|
+
conversationTurns: this.resumeState.conversation.length,
|
|
11639
|
+
snapshotApplied: this.resumeState.snapshotApplied,
|
|
11640
|
+
logEntries: this.resumeState.logEntryCount
|
|
11641
|
+
});
|
|
11642
|
+
} catch (error) {
|
|
11643
|
+
this.logger.warn("Failed to load resume state, starting fresh", {
|
|
11644
|
+
error
|
|
11645
|
+
});
|
|
11646
|
+
this.resumeState = null;
|
|
11647
|
+
}
|
|
11648
|
+
}
|
|
11303
11649
|
const payload = {
|
|
11304
11650
|
task_id: taskId,
|
|
11305
11651
|
run_id: runId,
|
|
@@ -11364,6 +11710,7 @@ You MUST NOT create a new branch, close the existing PR, or create a new PR.`
|
|
|
11364
11710
|
}
|
|
11365
11711
|
}
|
|
11366
11712
|
});
|
|
11713
|
+
this.broadcastTurnComplete(result.stopReason);
|
|
11367
11714
|
return { stopReason: result.stopReason };
|
|
11368
11715
|
}
|
|
11369
11716
|
case POSTHOG_NOTIFICATIONS.CANCEL:
|
|
@@ -11399,18 +11746,19 @@ You MUST NOT create a new branch, close the existing PR, or create a new PR.`
|
|
|
11399
11746
|
name: process.env.HOSTNAME || "cloud-sandbox"
|
|
11400
11747
|
};
|
|
11401
11748
|
this.configureEnvironment();
|
|
11402
|
-
const treeTracker = this.config.repositoryPath ? new TreeTracker({
|
|
11403
|
-
repositoryPath: this.config.repositoryPath,
|
|
11404
|
-
taskId: payload.task_id,
|
|
11405
|
-
runId: payload.run_id,
|
|
11406
|
-
logger: new Logger({ debug: true, prefix: "[TreeTracker]" })
|
|
11407
|
-
}) : null;
|
|
11408
11749
|
const posthogAPI = new PostHogAPIClient({
|
|
11409
11750
|
apiUrl: this.config.apiUrl,
|
|
11410
11751
|
projectId: this.config.projectId,
|
|
11411
11752
|
getApiKey: () => this.config.apiKey,
|
|
11412
11753
|
userAgent: `posthog/cloud.hog.dev; version: ${this.config.version ?? package_default.version}`
|
|
11413
11754
|
});
|
|
11755
|
+
const treeTracker = this.config.repositoryPath ? new TreeTracker({
|
|
11756
|
+
repositoryPath: this.config.repositoryPath,
|
|
11757
|
+
taskId: payload.task_id,
|
|
11758
|
+
runId: payload.run_id,
|
|
11759
|
+
apiClient: posthogAPI,
|
|
11760
|
+
logger: new Logger({ debug: true, prefix: "[TreeTracker]" })
|
|
11761
|
+
}) : null;
|
|
11414
11762
|
const logWriter = new SessionLogWriter({
|
|
11415
11763
|
posthogAPI,
|
|
11416
11764
|
logger: new Logger({ debug: true, prefix: "[SessionLogWriter]" })
|
|
@@ -11505,26 +11853,55 @@ You MUST NOT create a new branch, close the existing PR, or create a new PR.`
|
|
|
11505
11853
|
}
|
|
11506
11854
|
async sendInitialTaskMessage(payload, prefetchedRun) {
|
|
11507
11855
|
if (!this.session) return;
|
|
11508
|
-
|
|
11509
|
-
|
|
11510
|
-
|
|
11511
|
-
|
|
11856
|
+
let taskRun = prefetchedRun ?? null;
|
|
11857
|
+
if (!taskRun) {
|
|
11858
|
+
try {
|
|
11859
|
+
taskRun = await this.posthogAPI.getTaskRun(
|
|
11860
|
+
payload.task_id,
|
|
11861
|
+
payload.run_id
|
|
11862
|
+
);
|
|
11863
|
+
} catch (error) {
|
|
11864
|
+
this.logger.warn("Failed to fetch task run", {
|
|
11865
|
+
taskId: payload.task_id,
|
|
11866
|
+
runId: payload.run_id,
|
|
11867
|
+
error
|
|
11868
|
+
});
|
|
11869
|
+
}
|
|
11870
|
+
}
|
|
11871
|
+
if (!this.resumeState) {
|
|
11872
|
+
const resumeRunId = this.getResumeRunId(taskRun);
|
|
11873
|
+
if (resumeRunId) {
|
|
11874
|
+
this.logger.info("Resuming from previous run (via TaskRun state)", {
|
|
11875
|
+
resumeRunId,
|
|
11876
|
+
currentRunId: payload.run_id
|
|
11877
|
+
});
|
|
11512
11878
|
try {
|
|
11513
|
-
|
|
11514
|
-
payload.task_id,
|
|
11515
|
-
|
|
11516
|
-
|
|
11879
|
+
this.resumeState = await resumeFromLog({
|
|
11880
|
+
taskId: payload.task_id,
|
|
11881
|
+
runId: resumeRunId,
|
|
11882
|
+
repositoryPath: this.config.repositoryPath,
|
|
11883
|
+
apiClient: this.posthogAPI,
|
|
11884
|
+
logger: new Logger({ debug: true, prefix: "[Resume]" })
|
|
11885
|
+
});
|
|
11886
|
+
this.logger.info("Resume state loaded (via TaskRun state)", {
|
|
11887
|
+
conversationTurns: this.resumeState.conversation.length,
|
|
11888
|
+
snapshotApplied: this.resumeState.snapshotApplied,
|
|
11889
|
+
logEntries: this.resumeState.logEntryCount
|
|
11890
|
+
});
|
|
11517
11891
|
} catch (error) {
|
|
11518
|
-
this.logger.warn(
|
|
11519
|
-
|
|
11520
|
-
|
|
11521
|
-
|
|
11522
|
-
runId: payload.run_id,
|
|
11523
|
-
error
|
|
11524
|
-
}
|
|
11525
|
-
);
|
|
11892
|
+
this.logger.warn("Failed to load resume state, starting fresh", {
|
|
11893
|
+
error
|
|
11894
|
+
});
|
|
11895
|
+
this.resumeState = null;
|
|
11526
11896
|
}
|
|
11527
11897
|
}
|
|
11898
|
+
}
|
|
11899
|
+
if (this.resumeState && this.resumeState.conversation.length > 0) {
|
|
11900
|
+
await this.sendResumeMessage(payload, taskRun);
|
|
11901
|
+
return;
|
|
11902
|
+
}
|
|
11903
|
+
try {
|
|
11904
|
+
const task = await this.posthogAPI.getTask(payload.task_id);
|
|
11528
11905
|
const initialPromptOverride = taskRun ? this.getInitialPromptOverride(taskRun) : null;
|
|
11529
11906
|
const initialPrompt = initialPromptOverride ?? task.description;
|
|
11530
11907
|
if (!initialPrompt) {
|
|
@@ -11543,6 +11920,7 @@ You MUST NOT create a new branch, close the existing PR, or create a new PR.`
|
|
|
11543
11920
|
this.logger.info("Initial task message completed", {
|
|
11544
11921
|
stopReason: result.stopReason
|
|
11545
11922
|
});
|
|
11923
|
+
this.broadcastTurnComplete(result.stopReason);
|
|
11546
11924
|
if (result.stopReason === "end_turn") {
|
|
11547
11925
|
await this.relayAgentResponse(payload);
|
|
11548
11926
|
}
|
|
@@ -11554,6 +11932,94 @@ You MUST NOT create a new branch, close the existing PR, or create a new PR.`
|
|
|
11554
11932
|
await this.signalTaskComplete(payload, "error");
|
|
11555
11933
|
}
|
|
11556
11934
|
}
|
|
11935
|
+
async sendResumeMessage(payload, taskRun) {
|
|
11936
|
+
if (!this.session || !this.resumeState) return;
|
|
11937
|
+
try {
|
|
11938
|
+
const conversationSummary = this.formatConversationForResume(
|
|
11939
|
+
this.resumeState.conversation
|
|
11940
|
+
);
|
|
11941
|
+
const pendingUserMessage = this.getPendingUserMessage(taskRun);
|
|
11942
|
+
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.`;
|
|
11943
|
+
let resumePrompt;
|
|
11944
|
+
if (pendingUserMessage) {
|
|
11945
|
+
resumePrompt = `You are resuming a previous conversation. ${sandboxContext}
|
|
11946
|
+
|
|
11947
|
+
Here is the conversation history from the previous session:
|
|
11948
|
+
|
|
11949
|
+
${conversationSummary}
|
|
11950
|
+
|
|
11951
|
+
The user has sent a new message:
|
|
11952
|
+
|
|
11953
|
+
${pendingUserMessage}
|
|
11954
|
+
|
|
11955
|
+
Respond to the user's new message above. You have full context from the previous session.`;
|
|
11956
|
+
} else {
|
|
11957
|
+
resumePrompt = `You are resuming a previous conversation. ${sandboxContext}
|
|
11958
|
+
|
|
11959
|
+
Here is the conversation history from the previous session:
|
|
11960
|
+
|
|
11961
|
+
${conversationSummary}
|
|
11962
|
+
|
|
11963
|
+
Continue from where you left off. The user is waiting for your response.`;
|
|
11964
|
+
}
|
|
11965
|
+
this.logger.info("Sending resume message", {
|
|
11966
|
+
taskId: payload.task_id,
|
|
11967
|
+
conversationTurns: this.resumeState.conversation.length,
|
|
11968
|
+
promptLength: resumePrompt.length,
|
|
11969
|
+
hasPendingUserMessage: !!pendingUserMessage,
|
|
11970
|
+
snapshotApplied: this.resumeState.snapshotApplied
|
|
11971
|
+
});
|
|
11972
|
+
this.resumeState = null;
|
|
11973
|
+
const result = await this.session.clientConnection.prompt({
|
|
11974
|
+
sessionId: this.session.acpSessionId,
|
|
11975
|
+
prompt: [{ type: "text", text: resumePrompt }]
|
|
11976
|
+
});
|
|
11977
|
+
this.logger.info("Resume message completed", {
|
|
11978
|
+
stopReason: result.stopReason
|
|
11979
|
+
});
|
|
11980
|
+
this.broadcastTurnComplete(result.stopReason);
|
|
11981
|
+
} catch (error) {
|
|
11982
|
+
this.logger.error("Failed to send resume message", error);
|
|
11983
|
+
if (this.session) {
|
|
11984
|
+
await this.session.logWriter.flushAll();
|
|
11985
|
+
}
|
|
11986
|
+
await this.signalTaskComplete(payload, "error");
|
|
11987
|
+
}
|
|
11988
|
+
}
|
|
11989
|
+
static RESUME_HISTORY_TOKEN_BUDGET = 5e4;
|
|
11990
|
+
static TOOL_RESULT_MAX_CHARS = 2e3;
|
|
11991
|
+
formatConversationForResume(conversation) {
|
|
11992
|
+
const selected = selectRecentTurns(
|
|
11993
|
+
conversation,
|
|
11994
|
+
_AgentServer.RESUME_HISTORY_TOKEN_BUDGET
|
|
11995
|
+
);
|
|
11996
|
+
const parts = [];
|
|
11997
|
+
if (selected.length < conversation.length) {
|
|
11998
|
+
parts.push(
|
|
11999
|
+
`*(${conversation.length - selected.length} earlier turns omitted)*`
|
|
12000
|
+
);
|
|
12001
|
+
}
|
|
12002
|
+
for (const turn of selected) {
|
|
12003
|
+
const role = turn.role === "user" ? "User" : "Assistant";
|
|
12004
|
+
const textParts = turn.content.filter((block) => block.type === "text").map((block) => block.text);
|
|
12005
|
+
if (textParts.length > 0) {
|
|
12006
|
+
parts.push(`**${role}**: ${textParts.join("\n")}`);
|
|
12007
|
+
}
|
|
12008
|
+
if (turn.toolCalls?.length) {
|
|
12009
|
+
const toolSummary = turn.toolCalls.map((tc) => {
|
|
12010
|
+
let resultStr = "";
|
|
12011
|
+
if (tc.result !== void 0) {
|
|
12012
|
+
const raw = typeof tc.result === "string" ? tc.result : JSON.stringify(tc.result);
|
|
12013
|
+
resultStr = raw.length > _AgentServer.TOOL_RESULT_MAX_CHARS ? ` \u2192 ${raw.substring(0, _AgentServer.TOOL_RESULT_MAX_CHARS)}...(truncated)` : ` \u2192 ${raw}`;
|
|
12014
|
+
}
|
|
12015
|
+
return ` - ${tc.toolName}${resultStr}`;
|
|
12016
|
+
}).join("\n");
|
|
12017
|
+
parts.push(`**${role} (tools)**:
|
|
12018
|
+
${toolSummary}`);
|
|
12019
|
+
}
|
|
12020
|
+
}
|
|
12021
|
+
return parts.join("\n\n");
|
|
12022
|
+
}
|
|
11557
12023
|
getInitialPromptOverride(taskRun) {
|
|
11558
12024
|
const state = taskRun.state;
|
|
11559
12025
|
const override = state?.initial_prompt_override;
|
|
@@ -11563,6 +12029,24 @@ You MUST NOT create a new branch, close the existing PR, or create a new PR.`
|
|
|
11563
12029
|
const trimmed2 = override.trim();
|
|
11564
12030
|
return trimmed2.length > 0 ? trimmed2 : null;
|
|
11565
12031
|
}
|
|
12032
|
+
getPendingUserMessage(taskRun) {
|
|
12033
|
+
if (!taskRun) return null;
|
|
12034
|
+
const state = taskRun.state;
|
|
12035
|
+
const message = state?.pending_user_message;
|
|
12036
|
+
if (typeof message !== "string") {
|
|
12037
|
+
return null;
|
|
12038
|
+
}
|
|
12039
|
+
const trimmed2 = message.trim();
|
|
12040
|
+
return trimmed2.length > 0 ? trimmed2 : null;
|
|
12041
|
+
}
|
|
12042
|
+
getResumeRunId(taskRun) {
|
|
12043
|
+
const envRunId = process.env.POSTHOG_RESUME_RUN_ID;
|
|
12044
|
+
if (envRunId) return envRunId;
|
|
12045
|
+
if (!taskRun) return null;
|
|
12046
|
+
const state = taskRun.state;
|
|
12047
|
+
const stateRunId = state?.resume_from_run_id;
|
|
12048
|
+
return typeof stateRunId === "string" && stateRunId.trim().length > 0 ? stateRunId.trim() : null;
|
|
12049
|
+
}
|
|
11566
12050
|
buildCloudSystemPrompt(prUrl) {
|
|
11567
12051
|
if (prUrl) {
|
|
11568
12052
|
return `
|
|
@@ -11887,20 +12371,30 @@ Important:
|
|
|
11887
12371
|
timestamp: (/* @__PURE__ */ new Date()).toISOString(),
|
|
11888
12372
|
notification
|
|
11889
12373
|
});
|
|
11890
|
-
const { archiveUrl: _, ...paramsWithoutArchive } = snapshotWithDevice;
|
|
11891
|
-
const logNotification = {
|
|
11892
|
-
...notification,
|
|
11893
|
-
params: paramsWithoutArchive
|
|
11894
|
-
};
|
|
11895
12374
|
this.session.logWriter.appendRawLine(
|
|
11896
12375
|
this.session.payload.run_id,
|
|
11897
|
-
JSON.stringify(
|
|
12376
|
+
JSON.stringify(notification)
|
|
11898
12377
|
);
|
|
11899
12378
|
}
|
|
11900
12379
|
} catch (error) {
|
|
11901
12380
|
this.logger.error("Failed to capture tree state", error);
|
|
11902
12381
|
}
|
|
11903
12382
|
}
|
|
12383
|
+
broadcastTurnComplete(stopReason) {
|
|
12384
|
+
if (!this.session) return;
|
|
12385
|
+
this.broadcastEvent({
|
|
12386
|
+
type: "notification",
|
|
12387
|
+
timestamp: (/* @__PURE__ */ new Date()).toISOString(),
|
|
12388
|
+
notification: {
|
|
12389
|
+
jsonrpc: "2.0",
|
|
12390
|
+
method: POSTHOG_NOTIFICATIONS.TURN_COMPLETE,
|
|
12391
|
+
params: {
|
|
12392
|
+
sessionId: this.session.acpSessionId,
|
|
12393
|
+
stopReason
|
|
12394
|
+
}
|
|
12395
|
+
}
|
|
12396
|
+
});
|
|
12397
|
+
}
|
|
11904
12398
|
broadcastEvent(event) {
|
|
11905
12399
|
if (this.session?.sseController) {
|
|
11906
12400
|
this.sendSseEvent(this.session.sseController, event);
|