@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
package/dist/index.d.ts
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
export { A as AcpConnection, a as AcpConnectionConfig, b as Agent, c as AgentAdapter, C as CodexProcessOptions, I as InProcessAcpConnection, O as OtelLogConfig, d as OtelLogWriter, S as SessionContext, e as SessionLogWriter, f as SessionLogWriterOptions, g as createAcpConnection } from './agent-
|
|
1
|
+
export { A as AcpConnection, a as AcpConnectionConfig, b as Agent, c as AgentAdapter, C as CodexProcessOptions, I as InProcessAcpConnection, O as OtelLogConfig, d as OtelLogWriter, S as SessionContext, e as SessionLogWriter, f as SessionLogWriterOptions, g as createAcpConnection } from './agent-BJ7Uacyp.js';
|
|
2
2
|
import { McpServerConfig } from '@anthropic-ai/claude-agent-sdk';
|
|
3
3
|
import { L as Logger } from './logger-DDBiMOOD.js';
|
|
4
4
|
export { a as LoggerConfig } from './logger-DDBiMOOD.js';
|
package/dist/index.js
CHANGED
|
@@ -1169,12 +1169,174 @@ import {
|
|
|
1169
1169
|
import {
|
|
1170
1170
|
query
|
|
1171
1171
|
} from "@anthropic-ai/claude-agent-sdk";
|
|
1172
|
+
|
|
1173
|
+
// ../shared/dist/index.js
|
|
1174
|
+
var consoleLogger = {
|
|
1175
|
+
info: (_message, _data) => {
|
|
1176
|
+
},
|
|
1177
|
+
debug: (_message, _data) => {
|
|
1178
|
+
},
|
|
1179
|
+
error: (_message, _data) => {
|
|
1180
|
+
},
|
|
1181
|
+
warn: (_message, _data) => {
|
|
1182
|
+
}
|
|
1183
|
+
};
|
|
1184
|
+
var Saga = class {
|
|
1185
|
+
completedSteps = [];
|
|
1186
|
+
currentStepName = "unknown";
|
|
1187
|
+
stepTimings = [];
|
|
1188
|
+
log;
|
|
1189
|
+
constructor(logger) {
|
|
1190
|
+
this.log = logger ?? consoleLogger;
|
|
1191
|
+
}
|
|
1192
|
+
/**
|
|
1193
|
+
* Run the saga with the given input.
|
|
1194
|
+
* Returns a discriminated union result - either success with data or failure with error details.
|
|
1195
|
+
*/
|
|
1196
|
+
async run(input) {
|
|
1197
|
+
this.completedSteps = [];
|
|
1198
|
+
this.currentStepName = "unknown";
|
|
1199
|
+
this.stepTimings = [];
|
|
1200
|
+
const sagaStart = performance.now();
|
|
1201
|
+
this.log.info("Starting saga", { sagaName: this.constructor.name });
|
|
1202
|
+
try {
|
|
1203
|
+
const result = await this.execute(input);
|
|
1204
|
+
const totalDuration = performance.now() - sagaStart;
|
|
1205
|
+
this.log.debug("Saga completed successfully", {
|
|
1206
|
+
sagaName: this.constructor.name,
|
|
1207
|
+
stepsCompleted: this.completedSteps.length,
|
|
1208
|
+
totalDurationMs: Math.round(totalDuration),
|
|
1209
|
+
stepTimings: this.stepTimings
|
|
1210
|
+
});
|
|
1211
|
+
return { success: true, data: result };
|
|
1212
|
+
} catch (error) {
|
|
1213
|
+
this.log.error("Saga failed, initiating rollback", {
|
|
1214
|
+
sagaName: this.constructor.name,
|
|
1215
|
+
failedStep: this.currentStepName,
|
|
1216
|
+
error: error instanceof Error ? error.message : String(error)
|
|
1217
|
+
});
|
|
1218
|
+
await this.rollback();
|
|
1219
|
+
return {
|
|
1220
|
+
success: false,
|
|
1221
|
+
error: error instanceof Error ? error.message : String(error),
|
|
1222
|
+
failedStep: this.currentStepName
|
|
1223
|
+
};
|
|
1224
|
+
}
|
|
1225
|
+
}
|
|
1226
|
+
/**
|
|
1227
|
+
* Execute a step with its rollback action.
|
|
1228
|
+
* If the step succeeds, its rollback action is stored for potential rollback.
|
|
1229
|
+
* The step name is automatically tracked for error reporting.
|
|
1230
|
+
*
|
|
1231
|
+
* @param config - Step configuration with name, execute, and rollback functions
|
|
1232
|
+
* @returns The result of the execute function
|
|
1233
|
+
* @throws Re-throws any error from the execute function (triggers rollback)
|
|
1234
|
+
*/
|
|
1235
|
+
async step(config) {
|
|
1236
|
+
this.currentStepName = config.name;
|
|
1237
|
+
this.log.debug(`Executing step: ${config.name}`);
|
|
1238
|
+
const stepStart = performance.now();
|
|
1239
|
+
const result = await config.execute();
|
|
1240
|
+
const durationMs = Math.round(performance.now() - stepStart);
|
|
1241
|
+
this.stepTimings.push({ name: config.name, durationMs });
|
|
1242
|
+
this.log.debug(`Step completed: ${config.name}`, { durationMs });
|
|
1243
|
+
this.completedSteps.push({
|
|
1244
|
+
name: config.name,
|
|
1245
|
+
rollback: () => config.rollback(result)
|
|
1246
|
+
});
|
|
1247
|
+
return result;
|
|
1248
|
+
}
|
|
1249
|
+
/**
|
|
1250
|
+
* Execute a step that doesn't need rollback.
|
|
1251
|
+
* Useful for read-only operations or operations that are idempotent.
|
|
1252
|
+
* The step name is automatically tracked for error reporting.
|
|
1253
|
+
*
|
|
1254
|
+
* @param name - Step name for logging and error tracking
|
|
1255
|
+
* @param execute - The action to execute
|
|
1256
|
+
* @returns The result of the execute function
|
|
1257
|
+
*/
|
|
1258
|
+
async readOnlyStep(name, execute) {
|
|
1259
|
+
this.currentStepName = name;
|
|
1260
|
+
this.log.debug(`Executing read-only step: ${name}`);
|
|
1261
|
+
const stepStart = performance.now();
|
|
1262
|
+
const result = await execute();
|
|
1263
|
+
const durationMs = Math.round(performance.now() - stepStart);
|
|
1264
|
+
this.stepTimings.push({ name, durationMs });
|
|
1265
|
+
this.log.debug(`Read-only step completed: ${name}`, { durationMs });
|
|
1266
|
+
return result;
|
|
1267
|
+
}
|
|
1268
|
+
/**
|
|
1269
|
+
* Roll back all completed steps in reverse order.
|
|
1270
|
+
* Rollback errors are logged but don't stop the rollback of other steps.
|
|
1271
|
+
*/
|
|
1272
|
+
async rollback() {
|
|
1273
|
+
this.log.info("Rolling back saga", {
|
|
1274
|
+
stepsToRollback: this.completedSteps.length
|
|
1275
|
+
});
|
|
1276
|
+
const stepsReversed = [...this.completedSteps].reverse();
|
|
1277
|
+
for (const step of stepsReversed) {
|
|
1278
|
+
try {
|
|
1279
|
+
this.log.debug(`Rolling back step: ${step.name}`);
|
|
1280
|
+
await step.rollback();
|
|
1281
|
+
this.log.debug(`Step rolled back: ${step.name}`);
|
|
1282
|
+
} catch (error) {
|
|
1283
|
+
this.log.error(`Failed to rollback step: ${step.name}`, {
|
|
1284
|
+
error: error instanceof Error ? error.message : String(error)
|
|
1285
|
+
});
|
|
1286
|
+
}
|
|
1287
|
+
}
|
|
1288
|
+
this.log.info("Rollback completed", {
|
|
1289
|
+
stepsAttempted: this.completedSteps.length
|
|
1290
|
+
});
|
|
1291
|
+
}
|
|
1292
|
+
/**
|
|
1293
|
+
* Get the number of completed steps (useful for testing)
|
|
1294
|
+
*/
|
|
1295
|
+
getCompletedStepCount() {
|
|
1296
|
+
return this.completedSteps.length;
|
|
1297
|
+
}
|
|
1298
|
+
};
|
|
1299
|
+
var NOOP_COLLECTOR = {
|
|
1300
|
+
time: (_label, fn) => fn(),
|
|
1301
|
+
timeSync: (_label, fn) => fn(),
|
|
1302
|
+
record: () => {
|
|
1303
|
+
},
|
|
1304
|
+
summarize: () => {
|
|
1305
|
+
}
|
|
1306
|
+
};
|
|
1307
|
+
function createTimingCollector(enabled, log) {
|
|
1308
|
+
if (!enabled) return NOOP_COLLECTOR;
|
|
1309
|
+
const steps = {};
|
|
1310
|
+
return {
|
|
1311
|
+
async time(label, fn) {
|
|
1312
|
+
const start = Date.now();
|
|
1313
|
+
const result = await fn();
|
|
1314
|
+
steps[label] = Date.now() - start;
|
|
1315
|
+
return result;
|
|
1316
|
+
},
|
|
1317
|
+
timeSync(label, fn) {
|
|
1318
|
+
const start = Date.now();
|
|
1319
|
+
const result = fn();
|
|
1320
|
+
steps[label] = Date.now() - start;
|
|
1321
|
+
return result;
|
|
1322
|
+
},
|
|
1323
|
+
record(label, ms) {
|
|
1324
|
+
steps[label] = ms;
|
|
1325
|
+
},
|
|
1326
|
+
summarize(label) {
|
|
1327
|
+
const total = Object.values(steps).reduce((a, b) => a + b, 0);
|
|
1328
|
+
log(`[timing] ${label}: ${total}ms`, steps);
|
|
1329
|
+
}
|
|
1330
|
+
};
|
|
1331
|
+
}
|
|
1332
|
+
|
|
1333
|
+
// src/adapters/claude/claude-agent.ts
|
|
1172
1334
|
import { v7 as uuidv7 } from "uuid";
|
|
1173
1335
|
|
|
1174
1336
|
// package.json
|
|
1175
1337
|
var package_default = {
|
|
1176
1338
|
name: "@posthog/agent",
|
|
1177
|
-
version: "2.1.
|
|
1339
|
+
version: "2.1.47",
|
|
1178
1340
|
repository: "https://github.com/PostHog/twig",
|
|
1179
1341
|
description: "TypeScript agent framework wrapping Claude Agent SDK with Git-based task execution for PostHog",
|
|
1180
1342
|
exports: {
|
|
@@ -3291,12 +3453,14 @@ var ClaudeAcpAgent = class extends BaseAcpAgent {
|
|
|
3291
3453
|
backgroundTerminals = {};
|
|
3292
3454
|
clientCapabilities;
|
|
3293
3455
|
logWriter;
|
|
3294
|
-
|
|
3456
|
+
options;
|
|
3295
3457
|
lastSentConfigOptions;
|
|
3296
|
-
|
|
3458
|
+
debug;
|
|
3459
|
+
constructor(client, logWriter, options) {
|
|
3297
3460
|
super(client);
|
|
3298
3461
|
this.logWriter = logWriter;
|
|
3299
|
-
this.
|
|
3462
|
+
this.options = options;
|
|
3463
|
+
this.debug = options?.debug ?? false;
|
|
3300
3464
|
this.toolUseCache = {};
|
|
3301
3465
|
this.logger = new Logger({ debug: true, prefix: "[ClaudeAcpAgent]" });
|
|
3302
3466
|
}
|
|
@@ -3339,27 +3503,36 @@ var ClaudeAcpAgent = class extends BaseAcpAgent {
|
|
|
3339
3503
|
}
|
|
3340
3504
|
async newSession(params) {
|
|
3341
3505
|
this.checkAuthStatus();
|
|
3506
|
+
const tc = createTimingCollector(
|
|
3507
|
+
this.debug,
|
|
3508
|
+
(msg, data) => this.logger.info(msg, data)
|
|
3509
|
+
);
|
|
3342
3510
|
const meta = params._meta;
|
|
3343
3511
|
const sessionId = uuidv7();
|
|
3344
3512
|
const permissionMode = meta?.permissionMode && TWIG_EXECUTION_MODES.includes(meta.permissionMode) ? meta.permissionMode : "default";
|
|
3345
|
-
const mcpServers =
|
|
3346
|
-
|
|
3347
|
-
|
|
3348
|
-
|
|
3349
|
-
|
|
3350
|
-
|
|
3351
|
-
|
|
3352
|
-
|
|
3353
|
-
|
|
3354
|
-
|
|
3355
|
-
|
|
3356
|
-
|
|
3357
|
-
|
|
3358
|
-
|
|
3359
|
-
|
|
3360
|
-
|
|
3513
|
+
const mcpServers = tc.timeSync(
|
|
3514
|
+
"parseMcpServers",
|
|
3515
|
+
() => parseMcpServers(params)
|
|
3516
|
+
);
|
|
3517
|
+
const options = tc.timeSync(
|
|
3518
|
+
"buildSessionOptions",
|
|
3519
|
+
() => buildSessionOptions({
|
|
3520
|
+
cwd: params.cwd,
|
|
3521
|
+
mcpServers,
|
|
3522
|
+
permissionMode,
|
|
3523
|
+
canUseTool: this.createCanUseTool(sessionId),
|
|
3524
|
+
logger: this.logger,
|
|
3525
|
+
systemPrompt: buildSystemPrompt(meta?.systemPrompt),
|
|
3526
|
+
userProvidedOptions: meta?.claudeCode?.options,
|
|
3527
|
+
sessionId,
|
|
3528
|
+
isResume: false,
|
|
3529
|
+
onModeChange: this.createOnModeChange(sessionId),
|
|
3530
|
+
onProcessSpawned: this.options?.onProcessSpawned,
|
|
3531
|
+
onProcessExited: this.options?.onProcessExited
|
|
3532
|
+
})
|
|
3533
|
+
);
|
|
3361
3534
|
const input = new Pushable();
|
|
3362
|
-
const q = query({ prompt: input, options });
|
|
3535
|
+
const q = tc.timeSync("sdkQuery", () => query({ prompt: input, options }));
|
|
3363
3536
|
const session = this.createSession(
|
|
3364
3537
|
sessionId,
|
|
3365
3538
|
q,
|
|
@@ -3371,29 +3544,40 @@ var ClaudeAcpAgent = class extends BaseAcpAgent {
|
|
|
3371
3544
|
session.taskRunId = meta?.taskRunId;
|
|
3372
3545
|
this.registerPersistence(sessionId, meta);
|
|
3373
3546
|
if (meta?.taskRunId) {
|
|
3374
|
-
await
|
|
3375
|
-
|
|
3376
|
-
|
|
3377
|
-
|
|
3378
|
-
|
|
3547
|
+
await tc.time(
|
|
3548
|
+
"extNotification",
|
|
3549
|
+
() => this.client.extNotification("_posthog/sdk_session", {
|
|
3550
|
+
taskRunId: meta.taskRunId,
|
|
3551
|
+
sessionId,
|
|
3552
|
+
adapter: "claude"
|
|
3553
|
+
})
|
|
3554
|
+
);
|
|
3379
3555
|
}
|
|
3380
|
-
const
|
|
3381
|
-
|
|
3382
|
-
|
|
3383
|
-
|
|
3384
|
-
|
|
3556
|
+
const modelOptions = await tc.time(
|
|
3557
|
+
"fetchModels",
|
|
3558
|
+
() => this.getModelConfigOptions()
|
|
3559
|
+
);
|
|
3560
|
+
this.deferBackgroundFetches(tc, q, sessionId, mcpServers);
|
|
3385
3561
|
session.modelId = modelOptions.currentModelId;
|
|
3386
3562
|
await this.trySetModel(q, modelOptions.currentModelId);
|
|
3387
|
-
|
|
3563
|
+
const configOptions = await tc.time(
|
|
3564
|
+
"buildConfigOptions",
|
|
3565
|
+
() => this.buildConfigOptions(modelOptions)
|
|
3566
|
+
);
|
|
3567
|
+
tc.summarize("newSession");
|
|
3388
3568
|
return {
|
|
3389
3569
|
sessionId,
|
|
3390
|
-
configOptions
|
|
3570
|
+
configOptions
|
|
3391
3571
|
};
|
|
3392
3572
|
}
|
|
3393
3573
|
async loadSession(params) {
|
|
3394
3574
|
return this.resumeSession(params);
|
|
3395
3575
|
}
|
|
3396
3576
|
async resumeSession(params) {
|
|
3577
|
+
const tc = createTimingCollector(
|
|
3578
|
+
this.debug,
|
|
3579
|
+
(msg, data) => this.logger.info(msg, data)
|
|
3580
|
+
);
|
|
3397
3581
|
const meta = params._meta;
|
|
3398
3582
|
const sessionId = meta?.sessionId;
|
|
3399
3583
|
if (!sessionId) {
|
|
@@ -3402,29 +3586,33 @@ var ClaudeAcpAgent = class extends BaseAcpAgent {
|
|
|
3402
3586
|
if (this.sessionId === sessionId) {
|
|
3403
3587
|
return {};
|
|
3404
3588
|
}
|
|
3405
|
-
const mcpServers =
|
|
3406
|
-
|
|
3589
|
+
const mcpServers = tc.timeSync(
|
|
3590
|
+
"parseMcpServers",
|
|
3591
|
+
() => parseMcpServers(params)
|
|
3592
|
+
);
|
|
3407
3593
|
const permissionMode = meta?.permissionMode && TWIG_EXECUTION_MODES.includes(meta.permissionMode) ? meta.permissionMode : "default";
|
|
3408
|
-
const { query: q, session } = await
|
|
3409
|
-
|
|
3410
|
-
|
|
3411
|
-
|
|
3412
|
-
|
|
3413
|
-
|
|
3414
|
-
|
|
3415
|
-
|
|
3416
|
-
|
|
3417
|
-
|
|
3594
|
+
const { query: q, session } = await tc.time(
|
|
3595
|
+
"initializeQuery",
|
|
3596
|
+
() => this.initializeQuery({
|
|
3597
|
+
cwd: params.cwd,
|
|
3598
|
+
permissionMode,
|
|
3599
|
+
mcpServers,
|
|
3600
|
+
systemPrompt: buildSystemPrompt(meta?.systemPrompt),
|
|
3601
|
+
userProvidedOptions: meta?.claudeCode?.options,
|
|
3602
|
+
sessionId,
|
|
3603
|
+
isResume: true,
|
|
3604
|
+
additionalDirectories: meta?.claudeCode?.options?.additionalDirectories
|
|
3605
|
+
})
|
|
3606
|
+
);
|
|
3418
3607
|
session.taskRunId = meta?.taskRunId;
|
|
3419
3608
|
this.registerPersistence(sessionId, meta);
|
|
3420
|
-
|
|
3421
|
-
|
|
3422
|
-
|
|
3423
|
-
|
|
3424
|
-
|
|
3425
|
-
|
|
3426
|
-
|
|
3427
|
-
};
|
|
3609
|
+
this.deferBackgroundFetches(tc, q, sessionId, mcpServers);
|
|
3610
|
+
const configOptions = await tc.time(
|
|
3611
|
+
"buildConfigOptions",
|
|
3612
|
+
() => this.buildConfigOptions()
|
|
3613
|
+
);
|
|
3614
|
+
tc.summarize("resumeSession");
|
|
3615
|
+
return { configOptions };
|
|
3428
3616
|
}
|
|
3429
3617
|
async prompt(params) {
|
|
3430
3618
|
this.session.cancelled = false;
|
|
@@ -3496,8 +3684,8 @@ var ClaudeAcpAgent = class extends BaseAcpAgent {
|
|
|
3496
3684
|
isResume: config.isResume,
|
|
3497
3685
|
additionalDirectories: config.additionalDirectories,
|
|
3498
3686
|
onModeChange: this.createOnModeChange(config.sessionId),
|
|
3499
|
-
onProcessSpawned: this.
|
|
3500
|
-
onProcessExited: this.
|
|
3687
|
+
onProcessSpawned: this.options?.onProcessSpawned,
|
|
3688
|
+
onProcessExited: this.options?.onProcessExited
|
|
3501
3689
|
});
|
|
3502
3690
|
const q = query({ prompt: input, options });
|
|
3503
3691
|
const abortController = options.abortController;
|
|
@@ -3607,6 +3795,23 @@ var ClaudeAcpAgent = class extends BaseAcpAgent {
|
|
|
3607
3795
|
await q.setModel(fallback);
|
|
3608
3796
|
}
|
|
3609
3797
|
}
|
|
3798
|
+
/**
|
|
3799
|
+
* Fire-and-forget: fetch slash commands and MCP tool metadata in parallel.
|
|
3800
|
+
* Both populate caches used later — neither is needed to return configOptions.
|
|
3801
|
+
*/
|
|
3802
|
+
deferBackgroundFetches(tc, q, sessionId, mcpServers) {
|
|
3803
|
+
Promise.all([
|
|
3804
|
+
tc.time("slashCommands", () => getAvailableSlashCommands(q)),
|
|
3805
|
+
tc.time(
|
|
3806
|
+
"mcpMetadata",
|
|
3807
|
+
() => fetchMcpToolMetadata(mcpServers, this.logger)
|
|
3808
|
+
)
|
|
3809
|
+
]).then(([slashCommands]) => {
|
|
3810
|
+
this.sendAvailableCommandsUpdate(sessionId, slashCommands);
|
|
3811
|
+
}).catch((err) => {
|
|
3812
|
+
this.logger.warn("Failed to fetch deferred session data", { err });
|
|
3813
|
+
});
|
|
3814
|
+
}
|
|
3610
3815
|
registerPersistence(sessionId, meta) {
|
|
3611
3816
|
const persistence = meta?.persistence;
|
|
3612
3817
|
if (persistence && this.logWriter) {
|
|
@@ -3894,7 +4099,10 @@ function createClaudeConnection(config) {
|
|
|
3894
4099
|
const agentStream = ndJsonStream(agentWritable, streams.agent.readable);
|
|
3895
4100
|
let agent = null;
|
|
3896
4101
|
const agentConnection = new AgentSideConnection((client) => {
|
|
3897
|
-
agent = new ClaudeAcpAgent(client, logWriter,
|
|
4102
|
+
agent = new ClaudeAcpAgent(client, logWriter, {
|
|
4103
|
+
...config.processCallbacks,
|
|
4104
|
+
debug: config.debug
|
|
4105
|
+
});
|
|
3898
4106
|
logger.info(`Created ${agent.adapterName} agent`);
|
|
3899
4107
|
return agent;
|
|
3900
4108
|
}, agentStream);
|
|
@@ -4594,6 +4802,7 @@ var Agent = class {
|
|
|
4594
4802
|
taskId,
|
|
4595
4803
|
deviceType: "local",
|
|
4596
4804
|
logger: this.logger,
|
|
4805
|
+
debug: this.debug,
|
|
4597
4806
|
processCallbacks: options.processCallbacks,
|
|
4598
4807
|
allowedModelIds,
|
|
4599
4808
|
codexOptions: options.adapter === "codex" && gatewayConfig ? {
|
|
@@ -4694,133 +4903,6 @@ var OtelLogWriter = class {
|
|
|
4694
4903
|
}
|
|
4695
4904
|
};
|
|
4696
4905
|
|
|
4697
|
-
// ../shared/dist/index.js
|
|
4698
|
-
var consoleLogger = {
|
|
4699
|
-
info: (_message, _data) => {
|
|
4700
|
-
},
|
|
4701
|
-
debug: (_message, _data) => {
|
|
4702
|
-
},
|
|
4703
|
-
error: (_message, _data) => {
|
|
4704
|
-
},
|
|
4705
|
-
warn: (_message, _data) => {
|
|
4706
|
-
}
|
|
4707
|
-
};
|
|
4708
|
-
var Saga = class {
|
|
4709
|
-
completedSteps = [];
|
|
4710
|
-
currentStepName = "unknown";
|
|
4711
|
-
stepTimings = [];
|
|
4712
|
-
log;
|
|
4713
|
-
constructor(logger) {
|
|
4714
|
-
this.log = logger ?? consoleLogger;
|
|
4715
|
-
}
|
|
4716
|
-
/**
|
|
4717
|
-
* Run the saga with the given input.
|
|
4718
|
-
* Returns a discriminated union result - either success with data or failure with error details.
|
|
4719
|
-
*/
|
|
4720
|
-
async run(input) {
|
|
4721
|
-
this.completedSteps = [];
|
|
4722
|
-
this.currentStepName = "unknown";
|
|
4723
|
-
this.stepTimings = [];
|
|
4724
|
-
const sagaStart = performance.now();
|
|
4725
|
-
this.log.info("Starting saga", { sagaName: this.constructor.name });
|
|
4726
|
-
try {
|
|
4727
|
-
const result = await this.execute(input);
|
|
4728
|
-
const totalDuration = performance.now() - sagaStart;
|
|
4729
|
-
this.log.debug("Saga completed successfully", {
|
|
4730
|
-
sagaName: this.constructor.name,
|
|
4731
|
-
stepsCompleted: this.completedSteps.length,
|
|
4732
|
-
totalDurationMs: Math.round(totalDuration),
|
|
4733
|
-
stepTimings: this.stepTimings
|
|
4734
|
-
});
|
|
4735
|
-
return { success: true, data: result };
|
|
4736
|
-
} catch (error) {
|
|
4737
|
-
this.log.error("Saga failed, initiating rollback", {
|
|
4738
|
-
sagaName: this.constructor.name,
|
|
4739
|
-
failedStep: this.currentStepName,
|
|
4740
|
-
error: error instanceof Error ? error.message : String(error)
|
|
4741
|
-
});
|
|
4742
|
-
await this.rollback();
|
|
4743
|
-
return {
|
|
4744
|
-
success: false,
|
|
4745
|
-
error: error instanceof Error ? error.message : String(error),
|
|
4746
|
-
failedStep: this.currentStepName
|
|
4747
|
-
};
|
|
4748
|
-
}
|
|
4749
|
-
}
|
|
4750
|
-
/**
|
|
4751
|
-
* Execute a step with its rollback action.
|
|
4752
|
-
* If the step succeeds, its rollback action is stored for potential rollback.
|
|
4753
|
-
* The step name is automatically tracked for error reporting.
|
|
4754
|
-
*
|
|
4755
|
-
* @param config - Step configuration with name, execute, and rollback functions
|
|
4756
|
-
* @returns The result of the execute function
|
|
4757
|
-
* @throws Re-throws any error from the execute function (triggers rollback)
|
|
4758
|
-
*/
|
|
4759
|
-
async step(config) {
|
|
4760
|
-
this.currentStepName = config.name;
|
|
4761
|
-
this.log.debug(`Executing step: ${config.name}`);
|
|
4762
|
-
const stepStart = performance.now();
|
|
4763
|
-
const result = await config.execute();
|
|
4764
|
-
const durationMs = Math.round(performance.now() - stepStart);
|
|
4765
|
-
this.stepTimings.push({ name: config.name, durationMs });
|
|
4766
|
-
this.log.debug(`Step completed: ${config.name}`, { durationMs });
|
|
4767
|
-
this.completedSteps.push({
|
|
4768
|
-
name: config.name,
|
|
4769
|
-
rollback: () => config.rollback(result)
|
|
4770
|
-
});
|
|
4771
|
-
return result;
|
|
4772
|
-
}
|
|
4773
|
-
/**
|
|
4774
|
-
* Execute a step that doesn't need rollback.
|
|
4775
|
-
* Useful for read-only operations or operations that are idempotent.
|
|
4776
|
-
* The step name is automatically tracked for error reporting.
|
|
4777
|
-
*
|
|
4778
|
-
* @param name - Step name for logging and error tracking
|
|
4779
|
-
* @param execute - The action to execute
|
|
4780
|
-
* @returns The result of the execute function
|
|
4781
|
-
*/
|
|
4782
|
-
async readOnlyStep(name, execute) {
|
|
4783
|
-
this.currentStepName = name;
|
|
4784
|
-
this.log.debug(`Executing read-only step: ${name}`);
|
|
4785
|
-
const stepStart = performance.now();
|
|
4786
|
-
const result = await execute();
|
|
4787
|
-
const durationMs = Math.round(performance.now() - stepStart);
|
|
4788
|
-
this.stepTimings.push({ name, durationMs });
|
|
4789
|
-
this.log.debug(`Read-only step completed: ${name}`, { durationMs });
|
|
4790
|
-
return result;
|
|
4791
|
-
}
|
|
4792
|
-
/**
|
|
4793
|
-
* Roll back all completed steps in reverse order.
|
|
4794
|
-
* Rollback errors are logged but don't stop the rollback of other steps.
|
|
4795
|
-
*/
|
|
4796
|
-
async rollback() {
|
|
4797
|
-
this.log.info("Rolling back saga", {
|
|
4798
|
-
stepsToRollback: this.completedSteps.length
|
|
4799
|
-
});
|
|
4800
|
-
const stepsReversed = [...this.completedSteps].reverse();
|
|
4801
|
-
for (const step of stepsReversed) {
|
|
4802
|
-
try {
|
|
4803
|
-
this.log.debug(`Rolling back step: ${step.name}`);
|
|
4804
|
-
await step.rollback();
|
|
4805
|
-
this.log.debug(`Step rolled back: ${step.name}`);
|
|
4806
|
-
} catch (error) {
|
|
4807
|
-
this.log.error(`Failed to rollback step: ${step.name}`, {
|
|
4808
|
-
error: error instanceof Error ? error.message : String(error)
|
|
4809
|
-
});
|
|
4810
|
-
}
|
|
4811
|
-
}
|
|
4812
|
-
this.log.info("Rollback completed", {
|
|
4813
|
-
stepsAttempted: this.completedSteps.length
|
|
4814
|
-
});
|
|
4815
|
-
}
|
|
4816
|
-
/**
|
|
4817
|
-
* Get the number of completed steps (useful for testing)
|
|
4818
|
-
*/
|
|
4819
|
-
getCompletedStepCount() {
|
|
4820
|
-
return this.completedSteps.length;
|
|
4821
|
-
}
|
|
4822
|
-
};
|
|
4823
|
-
|
|
4824
4906
|
// ../git/dist/queries.js
|
|
4825
4907
|
import * as fs4 from "fs/promises";
|
|
4826
4908
|
import * as path5 from "path";
|