@ddwang/magnitude-core 0.3.1-ddwang.2 → 0.3.1-ddwang.3

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/index.cjs CHANGED
@@ -7,6 +7,8 @@ var EventEmitter = require('eventemitter3');
7
7
  var z = require('zod');
8
8
  var sharp = require('sharp');
9
9
  var node_async_hooks = require('node:async_hooks');
10
+ var crypto = require('node:crypto');
11
+ var node_perf_hooks = require('node:perf_hooks');
10
12
  var fs = require('node:fs');
11
13
  var path = require('node:path');
12
14
  var os = require('node:os');
@@ -14,7 +16,7 @@ var node_child_process = require('node:child_process');
14
16
  var cuid2 = require('@paralleldrive/cuid2');
15
17
  var posthogNode = require('posthog-node');
16
18
  var node_module = require('node:module');
17
- var crypto = require('crypto');
19
+ var crypto$1 = require('crypto');
18
20
  var fs$1 = require('fs');
19
21
  var os$1 = require('os');
20
22
  var path$1 = require('path');
@@ -24,7 +26,6 @@ var type_builder = require('@boundaryml/baml/type_builder');
24
26
  var zodToJsonSchema = require('zod-to-json-schema');
25
27
  var playwright = require('playwright');
26
28
  var objectHash = require('object-hash');
27
- var crypto$1 = require('node:crypto');
28
29
  var magnitudeExtract = require('magnitude-extract');
29
30
 
30
31
  var _documentCurrentScript = typeof document !== 'undefined' ? document.currentScript : null;
@@ -1145,20 +1146,35 @@ class OperationDeadlineError extends AgentError {
1145
1146
  }
1146
1147
  class AgentBusyError extends AgentError {
1147
1148
  constructor() {
1148
- super("Agent has an active operation; await whenIdle() before reusing it", { variant: "busy" });
1149
+ super("Agent has pending work; await whenIdle() before reusing it", { variant: "busy" });
1149
1150
  this.name = "AgentBusyError";
1150
1151
  }
1151
1152
  }
1152
1153
 
1154
+ function attachOperationDiagnostics(error, diagnostics) {
1155
+ if (!(error instanceof Error)) return;
1156
+ try {
1157
+ if (!("operation" in error)) Object.defineProperty(error, "operation", { value: diagnostics, enumerable: true });
1158
+ } catch {
1159
+ }
1160
+ }
1153
1161
  const operations = new node_async_hooks.AsyncLocalStorage();
1154
1162
  class Operation {
1155
- constructor(owner, options) {
1163
+ constructor(owner, options, kind = "exec", onUpdate) {
1156
1164
  this.owner = owner;
1157
- this.options = options;
1165
+ this.kind = kind;
1166
+ this.onUpdate = onUpdate;
1158
1167
  if (options.deadline !== void 0 && !Number.isFinite(options.deadline)) {
1159
1168
  throw new TypeError("deadline must be a finite Unix timestamp in milliseconds");
1160
1169
  }
1161
1170
  this.deadline = options.deadline;
1171
+ this.externalSignal = options.signal;
1172
+ this.signal.addEventListener("abort", () => {
1173
+ this.cancelled = node_perf_hooks.performance.now();
1174
+ this.outcome = this.signal.reason instanceof OperationDeadlineError ? "deadline" : "cancelled";
1175
+ attachOperationDiagnostics(this.signal.reason, this.snapshot());
1176
+ this.publish();
1177
+ }, { once: true });
1162
1178
  options.signal?.addEventListener("abort", this.externalAbort, { once: true });
1163
1179
  if (options.signal?.aborted) this.externalAbort();
1164
1180
  this.scheduleDeadline();
@@ -1168,7 +1184,19 @@ class Operation {
1168
1184
  closed = false;
1169
1185
  signal = this.controller.signal;
1170
1186
  deadline;
1171
- externalAbort = () => this.cancel(this.options.signal?.reason);
1187
+ externalSignal;
1188
+ externalAbort = () => this.cancel(this.externalSignal?.reason);
1189
+ id = crypto.randomUUID();
1190
+ startedAt = Date.now();
1191
+ started = node_perf_hooks.performance.now();
1192
+ finished;
1193
+ cancelled;
1194
+ idle;
1195
+ announced = false;
1196
+ outcome;
1197
+ lastAction;
1198
+ timings = {};
1199
+ spans = /* @__PURE__ */ new Set();
1172
1200
  scheduleDeadline() {
1173
1201
  if (this.deadline === void 0 || this.signal.aborted) return;
1174
1202
  const remaining = this.deadline - Date.now();
@@ -1176,28 +1204,106 @@ class Operation {
1176
1204
  else this.timer = setTimeout(() => this.scheduleDeadline(), Math.min(remaining, 2147483647));
1177
1205
  }
1178
1206
  cancel(reason) {
1207
+ if (this.closed) return;
1179
1208
  clearTimeout(this.timer);
1180
1209
  if (!this.signal.aborted) this.controller.abort(new OperationCancelledError(reason));
1181
1210
  }
1182
1211
  check() {
1183
- if (!this.signal.aborted && this.deadline !== void 0 && Date.now() >= this.deadline) {
1212
+ if (this.signal.aborted) throw this.signal.reason;
1213
+ if (this.closed) throw new OperationCancelledError("Operation already finished");
1214
+ if (this.deadline !== void 0 && Date.now() >= this.deadline) {
1184
1215
  this.controller.abort(new OperationDeadlineError(this.deadline));
1185
1216
  }
1186
1217
  if (this.signal.aborted) throw this.signal.reason;
1187
- if (this.closed) throw new OperationCancelledError("Operation already finished");
1188
1218
  }
1189
1219
  run(fn) {
1190
1220
  return operations.run(this, fn);
1191
1221
  }
1222
+ announce() {
1223
+ this.announced = true;
1224
+ this.publish();
1225
+ }
1226
+ publish() {
1227
+ if (!this.announced) return;
1228
+ try {
1229
+ this.onUpdate?.(this.snapshot());
1230
+ } catch {
1231
+ logger.warn("Operation diagnostic listener failed");
1232
+ }
1233
+ }
1234
+ snapshot() {
1235
+ const now = this.finished ?? node_perf_hooks.performance.now();
1236
+ const timings = {};
1237
+ for (const [phase2, timing] of Object.entries(this.timings)) timings[phase2] = { ...timing };
1238
+ let phase = "preparing";
1239
+ for (const span of this.spans) {
1240
+ phase = span.phase;
1241
+ const timing = timings[phase] ??= { count: 0, totalMs: 0 };
1242
+ timing.count++;
1243
+ timing.totalMs += now - span.started;
1244
+ }
1245
+ return {
1246
+ id: this.id,
1247
+ kind: this.kind,
1248
+ status: this.finished !== void 0 ? "finished" : this.cancelled !== void 0 ? "draining" : "running",
1249
+ outcome: this.outcome,
1250
+ phase,
1251
+ startedAt: this.startedAt,
1252
+ elapsedMs: now - this.started,
1253
+ ...this.cancelled !== void 0 && this.finished !== void 0 ? { cancellationToDrainMs: this.finished - this.cancelled } : {},
1254
+ ...this.cancelled !== void 0 && this.idle !== void 0 ? { cancellationToIdleMs: this.idle - this.cancelled } : {},
1255
+ ...this.lastAction ? { lastAction: { ...this.lastAction } } : {},
1256
+ timings
1257
+ };
1258
+ }
1259
+ beginPhase(phase) {
1260
+ if (this.closed) return () => {
1261
+ };
1262
+ const span = { phase, started: node_perf_hooks.performance.now() };
1263
+ this.spans.add(span);
1264
+ this.publish();
1265
+ return () => {
1266
+ if (!this.spans.delete(span)) return;
1267
+ const timing = this.timings[phase] ??= { count: 0, totalMs: 0 };
1268
+ timing.count++;
1269
+ timing.totalMs += node_perf_hooks.performance.now() - span.started;
1270
+ this.publish();
1271
+ };
1272
+ }
1273
+ prepareAction(name) {
1274
+ this.lastAction = { index: (this.lastAction?.index ?? 0) + 1, name, state: "pending" };
1275
+ this.publish();
1276
+ }
1277
+ actionState(state) {
1278
+ if (this.lastAction) this.lastAction.state = state;
1279
+ }
1280
+ fail(error) {
1281
+ this.outcome ??= "failed";
1282
+ attachOperationDiagnostics(error, this.snapshot());
1283
+ }
1284
+ finish() {
1285
+ this.finished = node_perf_hooks.performance.now();
1286
+ this.outcome ??= "succeeded";
1287
+ this.dispose();
1288
+ this.publish();
1289
+ }
1290
+ markIdle() {
1291
+ if (this.idle !== void 0) return;
1292
+ this.idle = node_perf_hooks.performance.now();
1293
+ this.publish();
1294
+ }
1192
1295
  dispose() {
1193
1296
  this.closed = true;
1194
1297
  clearTimeout(this.timer);
1195
- this.options.signal?.removeEventListener("abort", this.externalAbort);
1298
+ this.externalSignal?.removeEventListener("abort", this.externalAbort);
1196
1299
  }
1197
1300
  }
1198
1301
  function currentOperation() {
1199
1302
  return operations.getStore();
1200
1303
  }
1304
+ function withoutOperation(fn) {
1305
+ return operations.exit(fn);
1306
+ }
1201
1307
  function checkOperation() {
1202
1308
  currentOperation()?.check();
1203
1309
  }
@@ -1206,6 +1312,19 @@ function operationOptions() {
1206
1312
  const operation = currentOperation();
1207
1313
  return { signal: operation?.signal, deadline: operation?.deadline };
1208
1314
  }
1315
+ function beginOperationPhase(phase) {
1316
+ return currentOperation()?.beginPhase(phase) ?? (() => {
1317
+ });
1318
+ }
1319
+ async function measureOperation(phase, fn) {
1320
+ const finish = beginOperationPhase(phase);
1321
+ try {
1322
+ checkOperation();
1323
+ return await fn();
1324
+ } finally {
1325
+ finish();
1326
+ }
1327
+ }
1209
1328
  async function drainAll(promises) {
1210
1329
  const results = await Promise.allSettled(promises);
1211
1330
  const failure = results.find((result) => result.status === "rejected");
@@ -2347,7 +2466,7 @@ function getCodebaseId() {
2347
2466
  encoding: "utf8",
2348
2467
  stdio: "pipe"
2349
2468
  }).trim();
2350
- return crypto.createHash("sha256").update(firstCommitHash).digest("hex").substring(0, 12);
2469
+ return crypto$1.createHash("sha256").update(firstCommitHash).digest("hex").substring(0, 12);
2351
2470
  } catch (error) {
2352
2471
  return void 0;
2353
2472
  }
@@ -2487,8 +2606,8 @@ function openUrl(url) {
2487
2606
  const CLIENT_ID = "9d1c250a-e61b-44d9-88ed-5944d1962f5e";
2488
2607
  const CREDS_PATH = path$1.join(os$1.homedir(), ".magnitude", "credentials", "claudeCode.json");
2489
2608
  function generatePKCE() {
2490
- const verifier = crypto.randomBytes(32).toString("base64url");
2491
- const challenge = crypto.createHash("sha256").update(verifier).digest("base64url");
2609
+ const verifier = crypto$1.randomBytes(32).toString("base64url");
2610
+ const challenge = crypto$1.createHash("sha256").update(verifier).digest("base64url");
2492
2611
  return { verifier, challenge };
2493
2612
  }
2494
2613
  function getAuthorizationURL(pkce) {
@@ -2824,7 +2943,7 @@ async function retryOnError(fnToRetry, retryOptions) {
2824
2943
  }
2825
2944
  }
2826
2945
  }
2827
- if (attempt < options.retryLimit) await operationSleep(options.delayMs);
2946
+ if (attempt < options.retryLimit) await measureOperation("retry", () => operationSleep(options.delayMs));
2828
2947
  }
2829
2948
  throw lastError;
2830
2949
  }
@@ -3455,7 +3574,9 @@ class ModelHarness {
3455
3574
  async _withUsage(invoke) {
3456
3575
  checkOperation();
3457
3576
  const collector = new baml.Collector("model-call");
3577
+ const finish = beginOperationPhase("model");
3458
3578
  try {
3579
+ checkOperation();
3459
3580
  try {
3460
3581
  return await invoke(collector);
3461
3582
  } finally {
@@ -3478,6 +3599,7 @@ class ModelHarness {
3478
3599
  }
3479
3600
  }
3480
3601
  } finally {
3602
+ finish();
3481
3603
  for (const log of collector.logs) {
3482
3604
  for (const call of log.calls) {
3483
3605
  try {
@@ -3758,7 +3880,7 @@ class MultiModelHarness {
3758
3880
  uniqueModels = [];
3759
3881
  events = new EventEmitter();
3760
3882
  constructor(clients) {
3761
- const sortedClients = clients.toSorted((a, b) => (b.roles ? b.roles.length : 9999) - (a.roles ? a.roles.length : 9999));
3883
+ const sortedClients = [...clients].sort((a, b) => (b.roles ? b.roles.length : 9999) - (a.roles ? a.roles.length : 9999));
3762
3884
  for (const client of sortedClients) {
3763
3885
  const harness = new ModelHarness({ llm: client });
3764
3886
  this.uniqueModels.push(harness);
@@ -3777,7 +3899,7 @@ class MultiModelHarness {
3777
3899
  }
3778
3900
  }
3779
3901
  async setup() {
3780
- await Promise.all(this.uniqueModels.map((model) => model.setup()));
3902
+ await drainAll(this.uniqueModels.map((model) => model.setup()));
3781
3903
  }
3782
3904
  describe() {
3783
3905
  return this.uniqueModels[0].describeModel();
@@ -3831,8 +3953,14 @@ class Agent {
3831
3953
  _paused = false;
3832
3954
  _pauseResolve = null;
3833
3955
  activeOperation;
3956
+ latestOperation;
3834
3957
  idle = Promise.resolve();
3835
- stopped = false;
3958
+ resolveIdle;
3959
+ pendingWork = 0;
3960
+ lifecycleTail = Promise.resolve();
3961
+ lifecycleRequest;
3962
+ lifecycleState = "new";
3963
+ telemetryStarted = false;
3836
3964
  latestTaskMemory;
3837
3965
  // | null = null;
3838
3966
  constructor(baseConfig = {}) {
@@ -3887,17 +4015,57 @@ class Agent {
3887
4015
  return connector;
3888
4016
  }
3889
4017
  async start() {
3890
- checkOperation();
3891
- if (this.busy) throw new AgentBusyError();
3892
- if (this.options.telemetry) telemetrifyAgent(this);
3893
- await this.models.setup();
3894
- logger.info("Agent: Starting connectors...");
3895
- for (const connector of this.connectors) {
3896
- if (connector.onStart) await connector.onStart();
4018
+ return this.scheduleLifecycle("start", async () => {
4019
+ if (this.lifecycleState === "ready") return;
4020
+ this.lifecycleState = "starting";
4021
+ if (this.options.telemetry && !this.telemetryStarted) {
4022
+ telemetrifyAgent(this);
4023
+ this.telemetryStarted = true;
4024
+ }
4025
+ try {
4026
+ await this.models.setup();
4027
+ for (const connector of this.connectors) await connector.onStart?.();
4028
+ } catch (error) {
4029
+ await this.stopConnectors();
4030
+ this.lifecycleState = "stopped";
4031
+ throw error;
4032
+ }
4033
+ this.lifecycleState = "ready";
4034
+ this.events.emit("start");
4035
+ });
4036
+ }
4037
+ get lifecycle() {
4038
+ return this.lifecycleState;
4039
+ }
4040
+ beginWork() {
4041
+ if (this.pendingWork++ === 0) this.idle = new Promise((resolve) => {
4042
+ this.resolveIdle = resolve;
4043
+ });
4044
+ }
4045
+ endWork() {
4046
+ if (--this.pendingWork === 0) {
4047
+ const resolve = this.resolveIdle;
4048
+ this.resolveIdle = void 0;
4049
+ withoutOperation(() => this.latestOperation?.markIdle());
4050
+ resolve?.();
3897
4051
  }
3898
- this.events.emit("start");
3899
- this.stopped = false;
3900
- logger.info("Agent: All connectors started.");
4052
+ }
4053
+ scheduleLifecycle(kind, fn) {
4054
+ checkOperation();
4055
+ if (kind === "start" && this.activeOperation) throw new AgentBusyError();
4056
+ if (this.lifecycleRequest?.kind === kind) return this.lifecycleRequest.promise;
4057
+ this.beginWork();
4058
+ return withoutOperation(() => {
4059
+ const promise = this.lifecycleTail.then(fn).finally(() => {
4060
+ if (this.lifecycleRequest?.promise === promise) this.lifecycleRequest = void 0;
4061
+ this.endWork();
4062
+ });
4063
+ this.lifecycleRequest = { kind, promise };
4064
+ this.lifecycleTail = promise.catch(() => {
4065
+ });
4066
+ if (kind === "stop") this.activeOperation?.cancel("Agent stopped");
4067
+ return promise;
4068
+ });
3901
4069
  }
3902
4070
  identifyAction(action) {
3903
4071
  const actionDefinition = this.actions.find((def) => def.name === action.variant);
@@ -3908,22 +4076,31 @@ class Agent {
3908
4076
  }
3909
4077
  /** True until underlying work settles, including after a cancelled caller returns. */
3910
4078
  get busy() {
3911
- return this.activeOperation !== void 0;
4079
+ return this.pendingWork > 0;
3912
4080
  }
3913
4081
  whenIdle() {
3914
4082
  return this.idle;
3915
4083
  }
3916
- async runOperation(options, fn) {
4084
+ /** A payload-free snapshot of the active or most recent operation. */
4085
+ get operation() {
4086
+ return this.latestOperation?.snapshot();
4087
+ }
4088
+ async runOperation(options, fn, kind = "exec") {
3917
4089
  const inherited = currentOperation();
3918
4090
  if (inherited?.owner === this) inherited.check();
3919
- if (this.busy) throw new AgentBusyError();
3920
- if (this.stopped) throw new AgentError("Agent is stopped; call start() before using it");
3921
- const operation = new Operation(this, options);
3922
- this.activeOperation = operation;
3923
- let markIdle;
3924
- this.idle = new Promise((resolve) => {
3925
- markIdle = resolve;
4091
+ if (this.busy) {
4092
+ const error = new AgentBusyError();
4093
+ if (this.activeOperation) attachOperationDiagnostics(error, this.activeOperation.snapshot());
4094
+ throw error;
4095
+ }
4096
+ if (this.lifecycleState === "stopped") throw new AgentError("Agent is stopped; call start() before using it");
4097
+ const operation = new Operation(this, options, kind, (snapshot) => {
4098
+ this.events.emit("operation", snapshot);
3926
4099
  });
4100
+ this.activeOperation = operation;
4101
+ this.latestOperation = operation;
4102
+ this.beginWork();
4103
+ operation.announce();
3927
4104
  const worker = operation.run(async () => {
3928
4105
  operation.check();
3929
4106
  try {
@@ -3932,10 +4109,13 @@ class Agent {
3932
4109
  operation.check();
3933
4110
  }
3934
4111
  });
3935
- const settled = worker.finally(() => {
3936
- operation.dispose();
4112
+ const settled = worker.catch((error) => {
4113
+ operation.fail(error);
4114
+ throw error;
4115
+ }).finally(() => {
4116
+ operation.finish();
3937
4117
  this.activeOperation = void 0;
3938
- markIdle();
4118
+ this.endWork();
3939
4119
  });
3940
4120
  return untilAborted(settled, operation.signal);
3941
4121
  }
@@ -3956,6 +4136,9 @@ class Agent {
3956
4136
  if (!parsed.success) {
3957
4137
  throw new AgentError(`Generated action '${action.variant}' violates input schema: ${parsed.error.message}`, { adaptable: true });
3958
4138
  }
4139
+ const operation = currentOperation();
4140
+ operation?.prepareAction(actionDefinition.name);
4141
+ checkOperation();
3959
4142
  const memoryOnly = memoryActions.includes(actionDefinition);
3960
4143
  if (!memoryOnly) for (const connector of this.connectors) {
3961
4144
  await connector.beforeAction?.(action, operationOptions());
@@ -3963,9 +4146,18 @@ class Agent {
3963
4146
  }
3964
4147
  this.events.emit("actionStarted", action);
3965
4148
  checkOperation();
3966
- const data = await actionDefinition.resolver(
3967
- { input: parsed.data, agent: this, memory, ...operationOptions() }
3968
- );
4149
+ const data = await measureOperation("action", async () => {
4150
+ const options = operationOptions();
4151
+ operation?.actionState("started");
4152
+ try {
4153
+ const result = await actionDefinition.resolver({ input: parsed.data, agent: this, memory, ...options });
4154
+ operation?.actionState("completed");
4155
+ return result;
4156
+ } catch (error) {
4157
+ operation?.actionState("failed");
4158
+ throw error;
4159
+ }
4160
+ });
3969
4161
  checkOperation();
3970
4162
  this.events.emit("actionDone", action);
3971
4163
  checkOperation();
@@ -3988,21 +4180,22 @@ class Agent {
3988
4180
  return data;
3989
4181
  }
3990
4182
  async _recordConnectorObservations(memory) {
3991
- checkOperation();
3992
- for (const connector of this.connectors) {
3993
- const connObservations = connector.collectObservations ? await connector.collectObservations(operationOptions()) : [];
3994
- checkOperation();
3995
- for (const obs of connObservations) {
3996
- memory.recordObservation(obs);
4183
+ return measureOperation("observations", async () => {
4184
+ for (const connector of this.connectors) {
4185
+ const connObservations = connector.collectObservations ? await connector.collectObservations(operationOptions()) : [];
4186
+ checkOperation();
4187
+ for (const obs of connObservations) {
4188
+ memory.recordObservation(obs);
4189
+ }
3997
4190
  }
3998
- }
3999
- this.events.emit("observationsRecorded");
4191
+ this.events.emit("observationsRecorded");
4192
+ });
4000
4193
  }
4001
4194
  get memory() {
4002
4195
  return this.latestTaskMemory;
4003
4196
  }
4004
4197
  async act(taskOrSteps, options = {}) {
4005
- return this.runOperation(options, () => this._runAct(taskOrSteps, options));
4198
+ return this.runOperation(options, () => this._runAct(taskOrSteps, options), "act");
4006
4199
  }
4007
4200
  async _runAct(taskOrSteps, options) {
4008
4201
  const instructions = [
@@ -4035,28 +4228,25 @@ class Agent {
4035
4228
  })(task);
4036
4229
  }
4037
4230
  async _buildContext(memory, options) {
4038
- checkOperation();
4039
- const messages = await memory.render(options);
4040
- checkOperation();
4041
- const connectorInstructions = [];
4042
- for (const connector of this.connectors) {
4043
- if (connector.getInstructions) {
4044
- const instructions = await connector.getInstructions(operationOptions());
4045
- checkOperation();
4046
- if (instructions) {
4047
- connectorInstructions.push({
4048
- connectorId: connector.id,
4049
- instructions
4050
- });
4231
+ return measureOperation("context", async () => {
4232
+ const messages = await memory.render(options);
4233
+ checkOperation();
4234
+ const connectorInstructions = [];
4235
+ for (const connector of this.connectors) {
4236
+ if (connector.getInstructions) {
4237
+ const instructions = await connector.getInstructions(operationOptions());
4238
+ checkOperation();
4239
+ if (instructions) {
4240
+ connectorInstructions.push({ connectorId: connector.id, instructions });
4241
+ }
4051
4242
  }
4052
4243
  }
4053
- }
4054
- return {
4055
- instructions: memory.instructions,
4056
- observationContent: messages,
4057
- //observationContent: content,
4058
- connectorInstructions
4059
- };
4244
+ return {
4245
+ instructions: memory.instructions,
4246
+ observationContent: messages,
4247
+ connectorInstructions
4248
+ };
4249
+ });
4060
4250
  }
4061
4251
  async _act(description, memory, options = {}) {
4062
4252
  checkOperation();
@@ -4145,7 +4335,7 @@ class Agent {
4145
4335
  await this._recordConnectorObservations(this.latestTaskMemory);
4146
4336
  const memoryContext = await this._buildContext(this.memory, options);
4147
4337
  return await this.models.query(memoryContext, query, schema);
4148
- });
4338
+ }, "query");
4149
4339
  }
4150
4340
  async queueDone() {
4151
4341
  checkOperation();
@@ -4159,9 +4349,12 @@ class Agent {
4159
4349
  if (!this._paused) return;
4160
4350
  logger.info("Agent: Paused");
4161
4351
  try {
4162
- await untilAborted(new Promise((resolve) => {
4163
- this._pauseResolve = resolve;
4164
- }), currentOperation()?.signal);
4352
+ await measureOperation("paused", async () => {
4353
+ if (!this._paused) return;
4354
+ await untilAborted(new Promise((resolve) => {
4355
+ this._pauseResolve = resolve;
4356
+ }), currentOperation()?.signal);
4357
+ });
4165
4358
  } finally {
4166
4359
  this._pauseResolve = null;
4167
4360
  }
@@ -4184,15 +4377,19 @@ class Agent {
4184
4377
  return this._paused;
4185
4378
  }
4186
4379
  async stop() {
4187
- checkOperation();
4188
- this.stopped = true;
4189
- this.activeOperation?.cancel("Agent stopped");
4190
- this.doneActing = true;
4191
- if (this._paused) {
4380
+ return this.scheduleLifecycle("stop", async () => {
4381
+ if (this.lifecycleState === "stopped") return;
4382
+ this.lifecycleState = "stopping";
4383
+ this.doneActing = true;
4192
4384
  this._paused = false;
4193
4385
  this._pauseResolve?.();
4194
4386
  this._pauseResolve = null;
4195
- }
4387
+ await this.stopConnectors();
4388
+ this.lifecycleState = "stopped";
4389
+ this.events.emit("stop");
4390
+ });
4391
+ }
4392
+ async stopConnectors() {
4196
4393
  logger.info("Agent: Stopping connectors...");
4197
4394
  for (const connector of this.connectors) {
4198
4395
  try {
@@ -4201,7 +4398,6 @@ class Agent {
4201
4398
  logger.warn(`Agent: Error stopping connector ${connector.id}: ${error instanceof Error ? error.message : String(error)}`);
4202
4399
  }
4203
4400
  }
4204
- this.events.emit("stop");
4205
4401
  logger.info("Agent: All connectors stopped.");
4206
4402
  logger.info("Agent: Stopped successfully.");
4207
4403
  }
@@ -4508,14 +4704,15 @@ function parseTypeContent(content) {
4508
4704
  }
4509
4705
 
4510
4706
  class CursorVisual {
4707
+ constructor(animate = true) {
4708
+ this.animate = animate;
4709
+ }
4511
4710
  /**
4512
4711
  * Manages the visual indicator for actions on a page
4513
4712
  */
4514
4713
  page;
4515
4714
  visualElementId = "action-visual-indicator";
4516
4715
  lastPosition = null;
4517
- constructor() {
4518
- }
4519
4716
  async setActivePage(page) {
4520
4717
  this.page = page;
4521
4718
  page.on("load", async () => {
@@ -4530,9 +4727,11 @@ class CursorVisual {
4530
4727
  );
4531
4728
  }
4532
4729
  async move(x, y) {
4533
- this.lastPosition = { x, y };
4534
- await this._drawVisual(x, y, false);
4535
- await operationSleep(300);
4730
+ await measureOperation("cursor", async () => {
4731
+ this.lastPosition = { x, y };
4732
+ await this._drawVisual(x, y, false);
4733
+ if (this.animate) await operationSleep(300);
4734
+ });
4536
4735
  }
4537
4736
  async setupOnPage() {
4538
4737
  if (this.lastPosition) {
@@ -4543,7 +4742,7 @@ class CursorVisual {
4543
4742
  async _drawVisual(x, y, showClickEffect) {
4544
4743
  try {
4545
4744
  await this.page.evaluate(
4546
- ({ x: x2, y: y2, id, showClickEffect: showClickEffect2 }) => {
4745
+ ({ x: x2, y: y2, id, showClickEffect: showClickEffect2, animate }) => {
4547
4746
  const viewportX = x2;
4548
4747
  const viewportY = y2;
4549
4748
  const docX = x2 + window.scrollX;
@@ -4585,7 +4784,6 @@ class CursorVisual {
4585
4784
  pointerElement.style.height = "32px";
4586
4785
  pointerElement.style.zIndex = "2147483647";
4587
4786
  pointerElement.style.pointerEvents = "none";
4588
- pointerElement.style.transition = "left 0.3s cubic-bezier(0.25, 0.1, 0.25, 1), top 0.3s cubic-bezier(0.25, 0.1, 0.25, 1)";
4589
4787
  const svg = document.createElementNS("http://www.w3.org/2000/svg", "svg");
4590
4788
  svg.setAttribute("width", "32");
4591
4789
  svg.setAttribute("height", "32");
@@ -4608,11 +4806,12 @@ class CursorVisual {
4608
4806
  pointerElement.appendChild(svg);
4609
4807
  document.body.appendChild(pointerElement);
4610
4808
  }
4809
+ pointerElement.style.transition = animate ? "left 0.3s cubic-bezier(0.25, 0.1, 0.25, 1), top 0.3s cubic-bezier(0.25, 0.1, 0.25, 1)" : "none";
4611
4810
  pointerElement.style.left = `${viewportX}px`;
4612
4811
  pointerElement.style.top = `${viewportY}px`;
4613
4812
  pointerElement.style.transform = "translate(-1px, -3px)";
4614
4813
  },
4615
- { x, y, id: this.visualElementId, showClickEffect }
4814
+ { x, y, id: this.visualElementId, showClickEffect, animate: this.animate }
4616
4815
  );
4617
4816
  } catch (error) {
4618
4817
  logger.trace(`Failed to draw visual: ${error.message}`);
@@ -5090,12 +5289,13 @@ class ActionVisualizer {
5090
5289
  this.context = context;
5091
5290
  this.options = {
5092
5291
  showCursor: options.showCursor ?? true,
5292
+ animateCursor: options.animateCursor ?? true,
5093
5293
  showHoverCircle: options.showHoverCircle ?? false,
5094
5294
  showClickRipple: options.showClickRipple ?? true,
5095
5295
  showDragLine: options.showDragLine ?? false,
5096
5296
  showTypeEffects: options.showTypeEffects ?? true
5097
5297
  };
5098
- this.cursor = new CursorVisual();
5298
+ this.cursor = new CursorVisual(this.options.animateCursor);
5099
5299
  this.mouseEffects = new MouseEffectVisual(this.options);
5100
5300
  this.typeEffects = new TypeEffectVisual();
5101
5301
  }
@@ -5103,18 +5303,18 @@ class ActionVisualizer {
5103
5303
  }
5104
5304
  async setActivePage(page) {
5105
5305
  this.page = page;
5106
- await this.cursor.setActivePage(page);
5306
+ if (this.options.showCursor) await this.cursor.setActivePage(page);
5107
5307
  await this.mouseEffects.setActivePage(page);
5108
5308
  await this.typeEffects.setActivePage(page);
5109
5309
  }
5110
5310
  async moveVirtualCursor(x, y) {
5111
- await this.cursor.move(x, y);
5311
+ if (this.options.showCursor) await this.cursor.move(x, y);
5112
5312
  }
5113
5313
  async hideAll() {
5114
- await this.cursor.hide();
5314
+ if (this.options.showCursor) await this.cursor.hide();
5115
5315
  }
5116
5316
  async showAll() {
5117
- await this.cursor.show();
5317
+ if (this.options.showCursor) await this.cursor.show();
5118
5318
  }
5119
5319
  }
5120
5320
 
@@ -5944,33 +6144,33 @@ class WebHarness {
5944
6144
  return this.tabs.getActivePage();
5945
6145
  }
5946
6146
  async screenshot(options = {}) {
5947
- let dpr;
5948
- let buffer;
5949
- const retries = 3;
5950
- for (let attempt = 0; attempt <= retries; attempt++) {
5951
- checkOperation();
5952
- try {
5953
- dpr = await this.page.evaluate(() => window.devicePixelRatio);
5954
- checkOperation();
5955
- buffer = await this.page.screenshot({ type: "png", ...options });
6147
+ return measureOperation("screenshot", async () => {
6148
+ let dpr;
6149
+ let buffer;
6150
+ const retries = 3;
6151
+ for (let attempt = 0; attempt <= retries; attempt++) {
5956
6152
  checkOperation();
5957
- break;
5958
- } catch (err) {
5959
- checkOperation();
5960
- const error = err;
5961
- if (error.message.includes("Target page, context or browser has been closed")) {
5962
- throw new Error("Attempted to take screenshot but page, context or browser is closed");
5963
- }
5964
- if (attempt >= retries) {
5965
- throw new Error(`Unable to capture screenshot after retries, error: ${error.message}`);
6153
+ try {
6154
+ dpr = await this.page.evaluate(() => window.devicePixelRatio);
6155
+ checkOperation();
6156
+ buffer = await this.page.screenshot({ type: "png", ...options });
6157
+ checkOperation();
6158
+ break;
6159
+ } catch (err) {
6160
+ checkOperation();
6161
+ const error = err;
6162
+ if (error.message.includes("Target page, context or browser has been closed")) {
6163
+ throw new Error("Attempted to take screenshot but page, context or browser is closed");
6164
+ }
6165
+ if (attempt >= retries) {
6166
+ throw new Error(`Unable to capture screenshot after retries, error: ${error.message}`);
6167
+ }
5966
6168
  }
5967
6169
  }
5968
- }
5969
- const base64data = buffer.toString("base64");
5970
- const image = Image.fromBase64(base64data);
5971
- const { width, height } = await image.getDimensions();
5972
- const rescaledImage = await image.resize(width / dpr, height / dpr);
5973
- return rescaledImage;
6170
+ const image = Image.fromBase64(buffer.toString("base64"));
6171
+ const { width, height } = await image.getDimensions();
6172
+ return await image.resize(width / dpr, height / dpr);
6173
+ });
5974
6174
  }
5975
6175
  // async goto(url: string) {
5976
6176
  // // No need to redraw here anymore, the 'load' event listener handles it
@@ -6169,7 +6369,7 @@ class WebHarness {
6169
6369
  }
6170
6370
  async waitForStability(timeout) {
6171
6371
  checkOperation();
6172
- await this.stability.waitForStability(timeout);
6372
+ await measureOperation("stability", () => this.stability.waitForStability(timeout));
6173
6373
  checkOperation();
6174
6374
  }
6175
6375
  // async applyTransformations() {
@@ -6430,7 +6630,7 @@ class BrowserProvider {
6430
6630
  async _launchOrReuseBrowser(options) {
6431
6631
  const hash = objectHash({
6432
6632
  ...options,
6433
- logger: options.logger ? crypto$1.randomUUID() : ""
6633
+ logger: options.logger ? crypto.randomUUID() : ""
6434
6634
  // replace unserializable logger - use UUID to force re-instance in case different loggers provided
6435
6635
  });
6436
6636
  let activeBrowser;
@@ -6688,7 +6888,7 @@ async function retry(fn, options = {}) {
6688
6888
  delay * Math.pow(multiplier, attempt),
6689
6889
  maxDelay
6690
6890
  );
6691
- await operationSleep(currentDelay);
6891
+ await measureOperation("retry", () => operationSleep(currentDelay));
6692
6892
  }
6693
6893
  }
6694
6894
  }
@@ -6822,7 +7022,7 @@ class BrowserConnector {
6822
7022
  this.cancelWait = () => controller.abort(new OperationCancelledError("Browser stopped"));
6823
7023
  this.recovery.waitUntil = Date.now() + duration;
6824
7024
  try {
6825
- await operationSleep(duration, controller.signal);
7025
+ await measureOperation("cooldown", () => operationSleep(duration, controller.signal));
6826
7026
  } finally {
6827
7027
  operation?.signal.removeEventListener("abort", abort);
6828
7028
  this.cancelWait = void 0;
@@ -6927,7 +7127,7 @@ class BrowserConnector {
6927
7127
  }
6928
7128
  url.hash = "";
6929
7129
  url.searchParams.sort();
6930
- const fingerprint = crypto$1.createHash("sha256").update(JSON.stringify([url.href, state.text, state.scroll, state.scrollers, state.input])).digest("hex");
7130
+ const fingerprint = crypto.createHash("sha256").update(JSON.stringify([url.href, state.text, state.scroll, state.scrollers, state.input])).digest("hex");
6931
7131
  const responses = [...this.responses.get(page)?.values() ?? []];
6932
7132
  const response = responses.find((record) => record.status === 429 && record.navigation) ?? responses.find((record) => record.status === 429) ?? responses.at(-1);
6933
7133
  this.recovery.observe(fingerprint, this.pendingAction, detectBlock(state.headings, response));
@@ -7059,10 +7259,10 @@ class BrowserAgent extends Agent {
7059
7259
  this.browserAgentEvents.emit("nav", url);
7060
7260
  checkOperation();
7061
7261
  await this.require(BrowserConnector).getHarness().navigate(url);
7062
- });
7262
+ }, "nav");
7063
7263
  }
7064
7264
  async extract(instructions, schema, options = {}) {
7065
- return this.runOperation(options, () => this._extract(instructions, schema));
7265
+ return this.runOperation(options, () => this._extract(instructions, schema), "extract");
7066
7266
  }
7067
7267
  async _extract(instructions, schema) {
7068
7268
  this.browserAgentEvents.emit("extractStarted", instructions, schema);