@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/agent/browserAgent.js +2 -2
- package/dist/agent/errors.d.ts +2 -0
- package/dist/agent/errors.js +1 -1
- package/dist/agent/index.d.ts +16 -3
- package/dist/agent/index.js +150 -79
- package/dist/ai/modelHarness.js +4 -1
- package/dist/ai/multiModelHarness.js +4 -2
- package/dist/common/events.d.ts +3 -0
- package/dist/common/operation.d.ts +53 -2
- package/dist/common/operation.js +136 -8
- package/dist/common/operation.test.js +37 -1
- package/dist/common/retry.js +2 -2
- package/dist/common/util.js +2 -2
- package/dist/connectors/browserConnector.js +2 -2
- package/dist/index.cjs +325 -125
- package/dist/index.d.cts +52 -9
- package/dist/index.d.ts +1 -1
- package/dist/index.mjs +324 -124
- package/dist/memory/image.d.ts +1 -1
- package/dist/memory/image.js +3 -2
- package/dist/memory/rendering/renderJsonParts.js +2 -2
- package/dist/memory/rendering/renderXmlParts.d.ts +1 -1
- package/dist/memory/rendering/renderXmlParts.js +3 -3
- package/dist/web/harness.js +27 -39
- package/dist/web/recovery.d.ts +2 -0
- package/dist/web/visualizer/cursor.d.ts +2 -1
- package/dist/web/visualizer/cursor.js +15 -14
- package/dist/web/visualizer/cursor.test.d.ts +1 -0
- package/dist/web/visualizer/cursor.test.js +63 -0
- package/dist/web/visualizer/index.d.ts +2 -0
- package/dist/web/visualizer/index.js +10 -6
- package/package.json +1 -1
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
|
|
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.
|
|
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
|
-
|
|
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 (
|
|
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.
|
|
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.
|
|
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
|
|
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
|
-
|
|
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
|
-
|
|
3891
|
-
|
|
3892
|
-
|
|
3893
|
-
|
|
3894
|
-
|
|
3895
|
-
|
|
3896
|
-
|
|
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
|
-
|
|
3899
|
-
|
|
3900
|
-
|
|
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.
|
|
4079
|
+
return this.pendingWork > 0;
|
|
3912
4080
|
}
|
|
3913
4081
|
whenIdle() {
|
|
3914
4082
|
return this.idle;
|
|
3915
4083
|
}
|
|
3916
|
-
|
|
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)
|
|
3920
|
-
|
|
3921
|
-
|
|
3922
|
-
|
|
3923
|
-
|
|
3924
|
-
this.
|
|
3925
|
-
|
|
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.
|
|
3936
|
-
operation.
|
|
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
|
-
|
|
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
|
|
3967
|
-
|
|
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
|
-
|
|
3992
|
-
|
|
3993
|
-
|
|
3994
|
-
|
|
3995
|
-
|
|
3996
|
-
|
|
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
|
-
|
|
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
|
-
|
|
4039
|
-
|
|
4040
|
-
|
|
4041
|
-
|
|
4042
|
-
|
|
4043
|
-
|
|
4044
|
-
|
|
4045
|
-
|
|
4046
|
-
|
|
4047
|
-
|
|
4048
|
-
|
|
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
|
-
|
|
4055
|
-
|
|
4056
|
-
|
|
4057
|
-
|
|
4058
|
-
|
|
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
|
|
4163
|
-
this.
|
|
4164
|
-
|
|
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
|
-
|
|
4188
|
-
|
|
4189
|
-
|
|
4190
|
-
|
|
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
|
-
|
|
4534
|
-
|
|
4535
|
-
|
|
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
|
-
|
|
5948
|
-
|
|
5949
|
-
|
|
5950
|
-
|
|
5951
|
-
|
|
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
|
-
|
|
5958
|
-
|
|
5959
|
-
|
|
5960
|
-
|
|
5961
|
-
|
|
5962
|
-
|
|
5963
|
-
}
|
|
5964
|
-
|
|
5965
|
-
|
|
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
|
-
|
|
5970
|
-
|
|
5971
|
-
|
|
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
|
|
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
|
|
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);
|