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