@posthog/agent 2.1.45 → 2.1.47
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/{agent-DcBmoTR4.d.ts → agent-BJ7Uacyp.d.ts} +2 -0
- package/dist/agent.d.ts +1 -1
- package/dist/agent.js +140 -56
- package/dist/agent.js.map +1 -1
- package/dist/index.d.ts +1 -1
- package/dist/index.js +265 -183
- package/dist/index.js.map +1 -1
- package/dist/server/agent-server.js +264 -183
- package/dist/server/agent-server.js.map +1 -1
- package/dist/server/bin.cjs +264 -183
- package/dist/server/bin.cjs.map +1 -1
- package/package.json +3 -3
- package/src/adapters/acp-connection.ts +6 -1
- package/src/adapters/claude/claude-agent.ts +105 -60
- package/src/agent.ts +1 -0
|
@@ -1178,12 +1178,174 @@ import {
|
|
|
1178
1178
|
import {
|
|
1179
1179
|
query
|
|
1180
1180
|
} from "@anthropic-ai/claude-agent-sdk";
|
|
1181
|
+
|
|
1182
|
+
// ../shared/dist/index.js
|
|
1183
|
+
var consoleLogger = {
|
|
1184
|
+
info: (_message, _data) => {
|
|
1185
|
+
},
|
|
1186
|
+
debug: (_message, _data) => {
|
|
1187
|
+
},
|
|
1188
|
+
error: (_message, _data) => {
|
|
1189
|
+
},
|
|
1190
|
+
warn: (_message, _data) => {
|
|
1191
|
+
}
|
|
1192
|
+
};
|
|
1193
|
+
var Saga = class {
|
|
1194
|
+
completedSteps = [];
|
|
1195
|
+
currentStepName = "unknown";
|
|
1196
|
+
stepTimings = [];
|
|
1197
|
+
log;
|
|
1198
|
+
constructor(logger) {
|
|
1199
|
+
this.log = logger ?? consoleLogger;
|
|
1200
|
+
}
|
|
1201
|
+
/**
|
|
1202
|
+
* Run the saga with the given input.
|
|
1203
|
+
* Returns a discriminated union result - either success with data or failure with error details.
|
|
1204
|
+
*/
|
|
1205
|
+
async run(input) {
|
|
1206
|
+
this.completedSteps = [];
|
|
1207
|
+
this.currentStepName = "unknown";
|
|
1208
|
+
this.stepTimings = [];
|
|
1209
|
+
const sagaStart = performance.now();
|
|
1210
|
+
this.log.info("Starting saga", { sagaName: this.constructor.name });
|
|
1211
|
+
try {
|
|
1212
|
+
const result = await this.execute(input);
|
|
1213
|
+
const totalDuration = performance.now() - sagaStart;
|
|
1214
|
+
this.log.debug("Saga completed successfully", {
|
|
1215
|
+
sagaName: this.constructor.name,
|
|
1216
|
+
stepsCompleted: this.completedSteps.length,
|
|
1217
|
+
totalDurationMs: Math.round(totalDuration),
|
|
1218
|
+
stepTimings: this.stepTimings
|
|
1219
|
+
});
|
|
1220
|
+
return { success: true, data: result };
|
|
1221
|
+
} catch (error) {
|
|
1222
|
+
this.log.error("Saga failed, initiating rollback", {
|
|
1223
|
+
sagaName: this.constructor.name,
|
|
1224
|
+
failedStep: this.currentStepName,
|
|
1225
|
+
error: error instanceof Error ? error.message : String(error)
|
|
1226
|
+
});
|
|
1227
|
+
await this.rollback();
|
|
1228
|
+
return {
|
|
1229
|
+
success: false,
|
|
1230
|
+
error: error instanceof Error ? error.message : String(error),
|
|
1231
|
+
failedStep: this.currentStepName
|
|
1232
|
+
};
|
|
1233
|
+
}
|
|
1234
|
+
}
|
|
1235
|
+
/**
|
|
1236
|
+
* Execute a step with its rollback action.
|
|
1237
|
+
* If the step succeeds, its rollback action is stored for potential rollback.
|
|
1238
|
+
* The step name is automatically tracked for error reporting.
|
|
1239
|
+
*
|
|
1240
|
+
* @param config - Step configuration with name, execute, and rollback functions
|
|
1241
|
+
* @returns The result of the execute function
|
|
1242
|
+
* @throws Re-throws any error from the execute function (triggers rollback)
|
|
1243
|
+
*/
|
|
1244
|
+
async step(config) {
|
|
1245
|
+
this.currentStepName = config.name;
|
|
1246
|
+
this.log.debug(`Executing step: ${config.name}`);
|
|
1247
|
+
const stepStart = performance.now();
|
|
1248
|
+
const result = await config.execute();
|
|
1249
|
+
const durationMs = Math.round(performance.now() - stepStart);
|
|
1250
|
+
this.stepTimings.push({ name: config.name, durationMs });
|
|
1251
|
+
this.log.debug(`Step completed: ${config.name}`, { durationMs });
|
|
1252
|
+
this.completedSteps.push({
|
|
1253
|
+
name: config.name,
|
|
1254
|
+
rollback: () => config.rollback(result)
|
|
1255
|
+
});
|
|
1256
|
+
return result;
|
|
1257
|
+
}
|
|
1258
|
+
/**
|
|
1259
|
+
* Execute a step that doesn't need rollback.
|
|
1260
|
+
* Useful for read-only operations or operations that are idempotent.
|
|
1261
|
+
* The step name is automatically tracked for error reporting.
|
|
1262
|
+
*
|
|
1263
|
+
* @param name - Step name for logging and error tracking
|
|
1264
|
+
* @param execute - The action to execute
|
|
1265
|
+
* @returns The result of the execute function
|
|
1266
|
+
*/
|
|
1267
|
+
async readOnlyStep(name, execute) {
|
|
1268
|
+
this.currentStepName = name;
|
|
1269
|
+
this.log.debug(`Executing read-only step: ${name}`);
|
|
1270
|
+
const stepStart = performance.now();
|
|
1271
|
+
const result = await execute();
|
|
1272
|
+
const durationMs = Math.round(performance.now() - stepStart);
|
|
1273
|
+
this.stepTimings.push({ name, durationMs });
|
|
1274
|
+
this.log.debug(`Read-only step completed: ${name}`, { durationMs });
|
|
1275
|
+
return result;
|
|
1276
|
+
}
|
|
1277
|
+
/**
|
|
1278
|
+
* Roll back all completed steps in reverse order.
|
|
1279
|
+
* Rollback errors are logged but don't stop the rollback of other steps.
|
|
1280
|
+
*/
|
|
1281
|
+
async rollback() {
|
|
1282
|
+
this.log.info("Rolling back saga", {
|
|
1283
|
+
stepsToRollback: this.completedSteps.length
|
|
1284
|
+
});
|
|
1285
|
+
const stepsReversed = [...this.completedSteps].reverse();
|
|
1286
|
+
for (const step of stepsReversed) {
|
|
1287
|
+
try {
|
|
1288
|
+
this.log.debug(`Rolling back step: ${step.name}`);
|
|
1289
|
+
await step.rollback();
|
|
1290
|
+
this.log.debug(`Step rolled back: ${step.name}`);
|
|
1291
|
+
} catch (error) {
|
|
1292
|
+
this.log.error(`Failed to rollback step: ${step.name}`, {
|
|
1293
|
+
error: error instanceof Error ? error.message : String(error)
|
|
1294
|
+
});
|
|
1295
|
+
}
|
|
1296
|
+
}
|
|
1297
|
+
this.log.info("Rollback completed", {
|
|
1298
|
+
stepsAttempted: this.completedSteps.length
|
|
1299
|
+
});
|
|
1300
|
+
}
|
|
1301
|
+
/**
|
|
1302
|
+
* Get the number of completed steps (useful for testing)
|
|
1303
|
+
*/
|
|
1304
|
+
getCompletedStepCount() {
|
|
1305
|
+
return this.completedSteps.length;
|
|
1306
|
+
}
|
|
1307
|
+
};
|
|
1308
|
+
var NOOP_COLLECTOR = {
|
|
1309
|
+
time: (_label, fn) => fn(),
|
|
1310
|
+
timeSync: (_label, fn) => fn(),
|
|
1311
|
+
record: () => {
|
|
1312
|
+
},
|
|
1313
|
+
summarize: () => {
|
|
1314
|
+
}
|
|
1315
|
+
};
|
|
1316
|
+
function createTimingCollector(enabled, log) {
|
|
1317
|
+
if (!enabled) return NOOP_COLLECTOR;
|
|
1318
|
+
const steps = {};
|
|
1319
|
+
return {
|
|
1320
|
+
async time(label, fn) {
|
|
1321
|
+
const start = Date.now();
|
|
1322
|
+
const result = await fn();
|
|
1323
|
+
steps[label] = Date.now() - start;
|
|
1324
|
+
return result;
|
|
1325
|
+
},
|
|
1326
|
+
timeSync(label, fn) {
|
|
1327
|
+
const start = Date.now();
|
|
1328
|
+
const result = fn();
|
|
1329
|
+
steps[label] = Date.now() - start;
|
|
1330
|
+
return result;
|
|
1331
|
+
},
|
|
1332
|
+
record(label, ms) {
|
|
1333
|
+
steps[label] = ms;
|
|
1334
|
+
},
|
|
1335
|
+
summarize(label) {
|
|
1336
|
+
const total = Object.values(steps).reduce((a, b) => a + b, 0);
|
|
1337
|
+
log(`[timing] ${label}: ${total}ms`, steps);
|
|
1338
|
+
}
|
|
1339
|
+
};
|
|
1340
|
+
}
|
|
1341
|
+
|
|
1342
|
+
// src/adapters/claude/claude-agent.ts
|
|
1181
1343
|
import { v7 as uuidv7 } from "uuid";
|
|
1182
1344
|
|
|
1183
1345
|
// package.json
|
|
1184
1346
|
var package_default = {
|
|
1185
1347
|
name: "@posthog/agent",
|
|
1186
|
-
version: "2.1.
|
|
1348
|
+
version: "2.1.47",
|
|
1187
1349
|
repository: "https://github.com/PostHog/twig",
|
|
1188
1350
|
description: "TypeScript agent framework wrapping Claude Agent SDK with Git-based task execution for PostHog",
|
|
1189
1351
|
exports: {
|
|
@@ -3256,12 +3418,14 @@ var ClaudeAcpAgent = class extends BaseAcpAgent {
|
|
|
3256
3418
|
backgroundTerminals = {};
|
|
3257
3419
|
clientCapabilities;
|
|
3258
3420
|
logWriter;
|
|
3259
|
-
|
|
3421
|
+
options;
|
|
3260
3422
|
lastSentConfigOptions;
|
|
3261
|
-
|
|
3423
|
+
debug;
|
|
3424
|
+
constructor(client, logWriter, options) {
|
|
3262
3425
|
super(client);
|
|
3263
3426
|
this.logWriter = logWriter;
|
|
3264
|
-
this.
|
|
3427
|
+
this.options = options;
|
|
3428
|
+
this.debug = options?.debug ?? false;
|
|
3265
3429
|
this.toolUseCache = {};
|
|
3266
3430
|
this.logger = new Logger({ debug: true, prefix: "[ClaudeAcpAgent]" });
|
|
3267
3431
|
}
|
|
@@ -3304,27 +3468,36 @@ var ClaudeAcpAgent = class extends BaseAcpAgent {
|
|
|
3304
3468
|
}
|
|
3305
3469
|
async newSession(params) {
|
|
3306
3470
|
this.checkAuthStatus();
|
|
3471
|
+
const tc = createTimingCollector(
|
|
3472
|
+
this.debug,
|
|
3473
|
+
(msg, data) => this.logger.info(msg, data)
|
|
3474
|
+
);
|
|
3307
3475
|
const meta = params._meta;
|
|
3308
3476
|
const sessionId = uuidv7();
|
|
3309
3477
|
const permissionMode = meta?.permissionMode && TWIG_EXECUTION_MODES.includes(meta.permissionMode) ? meta.permissionMode : "default";
|
|
3310
|
-
const mcpServers =
|
|
3311
|
-
|
|
3312
|
-
|
|
3313
|
-
|
|
3314
|
-
|
|
3315
|
-
|
|
3316
|
-
|
|
3317
|
-
|
|
3318
|
-
|
|
3319
|
-
|
|
3320
|
-
|
|
3321
|
-
|
|
3322
|
-
|
|
3323
|
-
|
|
3324
|
-
|
|
3325
|
-
|
|
3478
|
+
const mcpServers = tc.timeSync(
|
|
3479
|
+
"parseMcpServers",
|
|
3480
|
+
() => parseMcpServers(params)
|
|
3481
|
+
);
|
|
3482
|
+
const options = tc.timeSync(
|
|
3483
|
+
"buildSessionOptions",
|
|
3484
|
+
() => buildSessionOptions({
|
|
3485
|
+
cwd: params.cwd,
|
|
3486
|
+
mcpServers,
|
|
3487
|
+
permissionMode,
|
|
3488
|
+
canUseTool: this.createCanUseTool(sessionId),
|
|
3489
|
+
logger: this.logger,
|
|
3490
|
+
systemPrompt: buildSystemPrompt(meta?.systemPrompt),
|
|
3491
|
+
userProvidedOptions: meta?.claudeCode?.options,
|
|
3492
|
+
sessionId,
|
|
3493
|
+
isResume: false,
|
|
3494
|
+
onModeChange: this.createOnModeChange(sessionId),
|
|
3495
|
+
onProcessSpawned: this.options?.onProcessSpawned,
|
|
3496
|
+
onProcessExited: this.options?.onProcessExited
|
|
3497
|
+
})
|
|
3498
|
+
);
|
|
3326
3499
|
const input = new Pushable();
|
|
3327
|
-
const q = query({ prompt: input, options });
|
|
3500
|
+
const q = tc.timeSync("sdkQuery", () => query({ prompt: input, options }));
|
|
3328
3501
|
const session = this.createSession(
|
|
3329
3502
|
sessionId,
|
|
3330
3503
|
q,
|
|
@@ -3336,29 +3509,40 @@ var ClaudeAcpAgent = class extends BaseAcpAgent {
|
|
|
3336
3509
|
session.taskRunId = meta?.taskRunId;
|
|
3337
3510
|
this.registerPersistence(sessionId, meta);
|
|
3338
3511
|
if (meta?.taskRunId) {
|
|
3339
|
-
await
|
|
3340
|
-
|
|
3341
|
-
|
|
3342
|
-
|
|
3343
|
-
|
|
3512
|
+
await tc.time(
|
|
3513
|
+
"extNotification",
|
|
3514
|
+
() => this.client.extNotification("_posthog/sdk_session", {
|
|
3515
|
+
taskRunId: meta.taskRunId,
|
|
3516
|
+
sessionId,
|
|
3517
|
+
adapter: "claude"
|
|
3518
|
+
})
|
|
3519
|
+
);
|
|
3344
3520
|
}
|
|
3345
|
-
const
|
|
3346
|
-
|
|
3347
|
-
|
|
3348
|
-
|
|
3349
|
-
|
|
3521
|
+
const modelOptions = await tc.time(
|
|
3522
|
+
"fetchModels",
|
|
3523
|
+
() => this.getModelConfigOptions()
|
|
3524
|
+
);
|
|
3525
|
+
this.deferBackgroundFetches(tc, q, sessionId, mcpServers);
|
|
3350
3526
|
session.modelId = modelOptions.currentModelId;
|
|
3351
3527
|
await this.trySetModel(q, modelOptions.currentModelId);
|
|
3352
|
-
|
|
3528
|
+
const configOptions = await tc.time(
|
|
3529
|
+
"buildConfigOptions",
|
|
3530
|
+
() => this.buildConfigOptions(modelOptions)
|
|
3531
|
+
);
|
|
3532
|
+
tc.summarize("newSession");
|
|
3353
3533
|
return {
|
|
3354
3534
|
sessionId,
|
|
3355
|
-
configOptions
|
|
3535
|
+
configOptions
|
|
3356
3536
|
};
|
|
3357
3537
|
}
|
|
3358
3538
|
async loadSession(params) {
|
|
3359
3539
|
return this.resumeSession(params);
|
|
3360
3540
|
}
|
|
3361
3541
|
async resumeSession(params) {
|
|
3542
|
+
const tc = createTimingCollector(
|
|
3543
|
+
this.debug,
|
|
3544
|
+
(msg, data) => this.logger.info(msg, data)
|
|
3545
|
+
);
|
|
3362
3546
|
const meta = params._meta;
|
|
3363
3547
|
const sessionId = meta?.sessionId;
|
|
3364
3548
|
if (!sessionId) {
|
|
@@ -3367,29 +3551,33 @@ var ClaudeAcpAgent = class extends BaseAcpAgent {
|
|
|
3367
3551
|
if (this.sessionId === sessionId) {
|
|
3368
3552
|
return {};
|
|
3369
3553
|
}
|
|
3370
|
-
const mcpServers =
|
|
3371
|
-
|
|
3554
|
+
const mcpServers = tc.timeSync(
|
|
3555
|
+
"parseMcpServers",
|
|
3556
|
+
() => parseMcpServers(params)
|
|
3557
|
+
);
|
|
3372
3558
|
const permissionMode = meta?.permissionMode && TWIG_EXECUTION_MODES.includes(meta.permissionMode) ? meta.permissionMode : "default";
|
|
3373
|
-
const { query: q, session } = await
|
|
3374
|
-
|
|
3375
|
-
|
|
3376
|
-
|
|
3377
|
-
|
|
3378
|
-
|
|
3379
|
-
|
|
3380
|
-
|
|
3381
|
-
|
|
3382
|
-
|
|
3559
|
+
const { query: q, session } = await tc.time(
|
|
3560
|
+
"initializeQuery",
|
|
3561
|
+
() => this.initializeQuery({
|
|
3562
|
+
cwd: params.cwd,
|
|
3563
|
+
permissionMode,
|
|
3564
|
+
mcpServers,
|
|
3565
|
+
systemPrompt: buildSystemPrompt(meta?.systemPrompt),
|
|
3566
|
+
userProvidedOptions: meta?.claudeCode?.options,
|
|
3567
|
+
sessionId,
|
|
3568
|
+
isResume: true,
|
|
3569
|
+
additionalDirectories: meta?.claudeCode?.options?.additionalDirectories
|
|
3570
|
+
})
|
|
3571
|
+
);
|
|
3383
3572
|
session.taskRunId = meta?.taskRunId;
|
|
3384
3573
|
this.registerPersistence(sessionId, meta);
|
|
3385
|
-
|
|
3386
|
-
|
|
3387
|
-
|
|
3388
|
-
|
|
3389
|
-
|
|
3390
|
-
|
|
3391
|
-
|
|
3392
|
-
};
|
|
3574
|
+
this.deferBackgroundFetches(tc, q, sessionId, mcpServers);
|
|
3575
|
+
const configOptions = await tc.time(
|
|
3576
|
+
"buildConfigOptions",
|
|
3577
|
+
() => this.buildConfigOptions()
|
|
3578
|
+
);
|
|
3579
|
+
tc.summarize("resumeSession");
|
|
3580
|
+
return { configOptions };
|
|
3393
3581
|
}
|
|
3394
3582
|
async prompt(params) {
|
|
3395
3583
|
this.session.cancelled = false;
|
|
@@ -3461,8 +3649,8 @@ var ClaudeAcpAgent = class extends BaseAcpAgent {
|
|
|
3461
3649
|
isResume: config.isResume,
|
|
3462
3650
|
additionalDirectories: config.additionalDirectories,
|
|
3463
3651
|
onModeChange: this.createOnModeChange(config.sessionId),
|
|
3464
|
-
onProcessSpawned: this.
|
|
3465
|
-
onProcessExited: this.
|
|
3652
|
+
onProcessSpawned: this.options?.onProcessSpawned,
|
|
3653
|
+
onProcessExited: this.options?.onProcessExited
|
|
3466
3654
|
});
|
|
3467
3655
|
const q = query({ prompt: input, options });
|
|
3468
3656
|
const abortController = options.abortController;
|
|
@@ -3572,6 +3760,23 @@ var ClaudeAcpAgent = class extends BaseAcpAgent {
|
|
|
3572
3760
|
await q.setModel(fallback);
|
|
3573
3761
|
}
|
|
3574
3762
|
}
|
|
3763
|
+
/**
|
|
3764
|
+
* Fire-and-forget: fetch slash commands and MCP tool metadata in parallel.
|
|
3765
|
+
* Both populate caches used later — neither is needed to return configOptions.
|
|
3766
|
+
*/
|
|
3767
|
+
deferBackgroundFetches(tc, q, sessionId, mcpServers) {
|
|
3768
|
+
Promise.all([
|
|
3769
|
+
tc.time("slashCommands", () => getAvailableSlashCommands(q)),
|
|
3770
|
+
tc.time(
|
|
3771
|
+
"mcpMetadata",
|
|
3772
|
+
() => fetchMcpToolMetadata(mcpServers, this.logger)
|
|
3773
|
+
)
|
|
3774
|
+
]).then(([slashCommands]) => {
|
|
3775
|
+
this.sendAvailableCommandsUpdate(sessionId, slashCommands);
|
|
3776
|
+
}).catch((err) => {
|
|
3777
|
+
this.logger.warn("Failed to fetch deferred session data", { err });
|
|
3778
|
+
});
|
|
3779
|
+
}
|
|
3575
3780
|
registerPersistence(sessionId, meta) {
|
|
3576
3781
|
const persistence = meta?.persistence;
|
|
3577
3782
|
if (persistence && this.logWriter) {
|
|
@@ -3859,7 +4064,10 @@ function createClaudeConnection(config) {
|
|
|
3859
4064
|
const agentStream = ndJsonStream(agentWritable, streams.agent.readable);
|
|
3860
4065
|
let agent = null;
|
|
3861
4066
|
const agentConnection = new AgentSideConnection((client) => {
|
|
3862
|
-
agent = new ClaudeAcpAgent(client, logWriter,
|
|
4067
|
+
agent = new ClaudeAcpAgent(client, logWriter, {
|
|
4068
|
+
...config.processCallbacks,
|
|
4069
|
+
debug: config.debug
|
|
4070
|
+
});
|
|
3863
4071
|
logger.info(`Created ${agent.adapterName} agent`);
|
|
3864
4072
|
return agent;
|
|
3865
4073
|
}, agentStream);
|
|
@@ -9242,133 +9450,6 @@ async function getHeadSha(baseDir, options) {
|
|
|
9242
9450
|
import { mkdir as mkdir3, rm as rm3, writeFile as writeFile3 } from "fs/promises";
|
|
9243
9451
|
import { join as join5 } from "path";
|
|
9244
9452
|
|
|
9245
|
-
// ../shared/dist/index.js
|
|
9246
|
-
var consoleLogger = {
|
|
9247
|
-
info: (_message, _data) => {
|
|
9248
|
-
},
|
|
9249
|
-
debug: (_message, _data) => {
|
|
9250
|
-
},
|
|
9251
|
-
error: (_message, _data) => {
|
|
9252
|
-
},
|
|
9253
|
-
warn: (_message, _data) => {
|
|
9254
|
-
}
|
|
9255
|
-
};
|
|
9256
|
-
var Saga = class {
|
|
9257
|
-
completedSteps = [];
|
|
9258
|
-
currentStepName = "unknown";
|
|
9259
|
-
stepTimings = [];
|
|
9260
|
-
log;
|
|
9261
|
-
constructor(logger) {
|
|
9262
|
-
this.log = logger ?? consoleLogger;
|
|
9263
|
-
}
|
|
9264
|
-
/**
|
|
9265
|
-
* Run the saga with the given input.
|
|
9266
|
-
* Returns a discriminated union result - either success with data or failure with error details.
|
|
9267
|
-
*/
|
|
9268
|
-
async run(input) {
|
|
9269
|
-
this.completedSteps = [];
|
|
9270
|
-
this.currentStepName = "unknown";
|
|
9271
|
-
this.stepTimings = [];
|
|
9272
|
-
const sagaStart = performance.now();
|
|
9273
|
-
this.log.info("Starting saga", { sagaName: this.constructor.name });
|
|
9274
|
-
try {
|
|
9275
|
-
const result = await this.execute(input);
|
|
9276
|
-
const totalDuration = performance.now() - sagaStart;
|
|
9277
|
-
this.log.debug("Saga completed successfully", {
|
|
9278
|
-
sagaName: this.constructor.name,
|
|
9279
|
-
stepsCompleted: this.completedSteps.length,
|
|
9280
|
-
totalDurationMs: Math.round(totalDuration),
|
|
9281
|
-
stepTimings: this.stepTimings
|
|
9282
|
-
});
|
|
9283
|
-
return { success: true, data: result };
|
|
9284
|
-
} catch (error) {
|
|
9285
|
-
this.log.error("Saga failed, initiating rollback", {
|
|
9286
|
-
sagaName: this.constructor.name,
|
|
9287
|
-
failedStep: this.currentStepName,
|
|
9288
|
-
error: error instanceof Error ? error.message : String(error)
|
|
9289
|
-
});
|
|
9290
|
-
await this.rollback();
|
|
9291
|
-
return {
|
|
9292
|
-
success: false,
|
|
9293
|
-
error: error instanceof Error ? error.message : String(error),
|
|
9294
|
-
failedStep: this.currentStepName
|
|
9295
|
-
};
|
|
9296
|
-
}
|
|
9297
|
-
}
|
|
9298
|
-
/**
|
|
9299
|
-
* Execute a step with its rollback action.
|
|
9300
|
-
* If the step succeeds, its rollback action is stored for potential rollback.
|
|
9301
|
-
* The step name is automatically tracked for error reporting.
|
|
9302
|
-
*
|
|
9303
|
-
* @param config - Step configuration with name, execute, and rollback functions
|
|
9304
|
-
* @returns The result of the execute function
|
|
9305
|
-
* @throws Re-throws any error from the execute function (triggers rollback)
|
|
9306
|
-
*/
|
|
9307
|
-
async step(config) {
|
|
9308
|
-
this.currentStepName = config.name;
|
|
9309
|
-
this.log.debug(`Executing step: ${config.name}`);
|
|
9310
|
-
const stepStart = performance.now();
|
|
9311
|
-
const result = await config.execute();
|
|
9312
|
-
const durationMs = Math.round(performance.now() - stepStart);
|
|
9313
|
-
this.stepTimings.push({ name: config.name, durationMs });
|
|
9314
|
-
this.log.debug(`Step completed: ${config.name}`, { durationMs });
|
|
9315
|
-
this.completedSteps.push({
|
|
9316
|
-
name: config.name,
|
|
9317
|
-
rollback: () => config.rollback(result)
|
|
9318
|
-
});
|
|
9319
|
-
return result;
|
|
9320
|
-
}
|
|
9321
|
-
/**
|
|
9322
|
-
* Execute a step that doesn't need rollback.
|
|
9323
|
-
* Useful for read-only operations or operations that are idempotent.
|
|
9324
|
-
* The step name is automatically tracked for error reporting.
|
|
9325
|
-
*
|
|
9326
|
-
* @param name - Step name for logging and error tracking
|
|
9327
|
-
* @param execute - The action to execute
|
|
9328
|
-
* @returns The result of the execute function
|
|
9329
|
-
*/
|
|
9330
|
-
async readOnlyStep(name, execute) {
|
|
9331
|
-
this.currentStepName = name;
|
|
9332
|
-
this.log.debug(`Executing read-only step: ${name}`);
|
|
9333
|
-
const stepStart = performance.now();
|
|
9334
|
-
const result = await execute();
|
|
9335
|
-
const durationMs = Math.round(performance.now() - stepStart);
|
|
9336
|
-
this.stepTimings.push({ name, durationMs });
|
|
9337
|
-
this.log.debug(`Read-only step completed: ${name}`, { durationMs });
|
|
9338
|
-
return result;
|
|
9339
|
-
}
|
|
9340
|
-
/**
|
|
9341
|
-
* Roll back all completed steps in reverse order.
|
|
9342
|
-
* Rollback errors are logged but don't stop the rollback of other steps.
|
|
9343
|
-
*/
|
|
9344
|
-
async rollback() {
|
|
9345
|
-
this.log.info("Rolling back saga", {
|
|
9346
|
-
stepsToRollback: this.completedSteps.length
|
|
9347
|
-
});
|
|
9348
|
-
const stepsReversed = [...this.completedSteps].reverse();
|
|
9349
|
-
for (const step of stepsReversed) {
|
|
9350
|
-
try {
|
|
9351
|
-
this.log.debug(`Rolling back step: ${step.name}`);
|
|
9352
|
-
await step.rollback();
|
|
9353
|
-
this.log.debug(`Step rolled back: ${step.name}`);
|
|
9354
|
-
} catch (error) {
|
|
9355
|
-
this.log.error(`Failed to rollback step: ${step.name}`, {
|
|
9356
|
-
error: error instanceof Error ? error.message : String(error)
|
|
9357
|
-
});
|
|
9358
|
-
}
|
|
9359
|
-
}
|
|
9360
|
-
this.log.info("Rollback completed", {
|
|
9361
|
-
stepsAttempted: this.completedSteps.length
|
|
9362
|
-
});
|
|
9363
|
-
}
|
|
9364
|
-
/**
|
|
9365
|
-
* Get the number of completed steps (useful for testing)
|
|
9366
|
-
*/
|
|
9367
|
-
getCompletedStepCount() {
|
|
9368
|
-
return this.completedSteps.length;
|
|
9369
|
-
}
|
|
9370
|
-
};
|
|
9371
|
-
|
|
9372
9453
|
// ../git/dist/sagas/tree.js
|
|
9373
9454
|
import { existsSync as existsSync4 } from "fs";
|
|
9374
9455
|
import * as fs5 from "fs/promises";
|