@ddwang/magnitude-core 0.3.1-ddwang.2 → 0.3.1-ddwang.4
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 +4 -45
- 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 +392 -154
- package/dist/index.d.cts +52 -9
- package/dist/index.d.ts +1 -1
- package/dist/index.mjs +391 -153
- 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/pageContent.d.ts +2 -0
- package/dist/web/pageContent.js +78 -0
- 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
|
@@ -6,6 +6,7 @@ import { partitionHtml, serializeToMarkdown } from 'magnitude-extract';
|
|
|
6
6
|
import EventEmitter from "eventemitter3";
|
|
7
7
|
import { retry } from "@/common/retry";
|
|
8
8
|
import { checkOperation } from '@/common/operation';
|
|
9
|
+
import { getVisiblePageContent } from '@/web/pageContent';
|
|
9
10
|
// export interface StartAgentWithWebOptions {
|
|
10
11
|
// agentBaseOptions?: Partial<AgentOptions>;
|
|
11
12
|
// webConnectorOptions?: BrowserConnectorOptions;
|
|
@@ -29,47 +30,6 @@ export async function startBrowserAgent(options //StartAgentWithWebOptions = {}
|
|
|
29
30
|
//console.log('agent started');
|
|
30
31
|
return agent;
|
|
31
32
|
}
|
|
32
|
-
async function getFullPageContent(page) {
|
|
33
|
-
checkOperation();
|
|
34
|
-
// 1. Get all iframe element handles
|
|
35
|
-
const iframeHandles = await page.locator('iframe').elementHandles();
|
|
36
|
-
// 2. Iterate through each iframe handle
|
|
37
|
-
for (const iframeHandle of iframeHandles) {
|
|
38
|
-
checkOperation();
|
|
39
|
-
// 3. Get the Frame object for the iframe
|
|
40
|
-
const frame = await iframeHandle.contentFrame();
|
|
41
|
-
checkOperation();
|
|
42
|
-
if (frame) {
|
|
43
|
-
// 4. Get the HTML content of the iframe
|
|
44
|
-
const iframeContent = await frame.content();
|
|
45
|
-
checkOperation();
|
|
46
|
-
// 5. Use evaluate to replace the iframe element with its content.
|
|
47
|
-
// We pass the content as an argument to avoid issues with string escaping.
|
|
48
|
-
await iframeHandle.evaluate((iframeNode, { content }) => {
|
|
49
|
-
// Create a new div element to hold the iframe's content
|
|
50
|
-
const div = document.createElement('div');
|
|
51
|
-
// Use DOMParser to handle Trusted Types restrictions
|
|
52
|
-
const parser = new DOMParser();
|
|
53
|
-
const doc = parser.parseFromString(content, 'text/html');
|
|
54
|
-
// Move all body children to the div
|
|
55
|
-
while (doc.body.firstChild) {
|
|
56
|
-
div.appendChild(doc.body.firstChild);
|
|
57
|
-
}
|
|
58
|
-
// Also preserve any head elements that might be important (styles, etc)
|
|
59
|
-
const headElements = doc.head.querySelectorAll('style, link[rel="stylesheet"]');
|
|
60
|
-
headElements.forEach(el => div.appendChild(el.cloneNode(true)));
|
|
61
|
-
// Add a data-attribute to mark that this was an expanded iframe
|
|
62
|
-
div.dataset.expandedFromIframe = 'true';
|
|
63
|
-
div.dataset.iframeSrc = iframeNode.getAttribute('src') || '';
|
|
64
|
-
// Replace the iframeNode with the new div
|
|
65
|
-
iframeNode.parentNode?.replaceChild(div, iframeNode);
|
|
66
|
-
}, { content: iframeContent });
|
|
67
|
-
}
|
|
68
|
-
}
|
|
69
|
-
// 6. Return the final, modified page content
|
|
70
|
-
checkOperation();
|
|
71
|
-
return page.content();
|
|
72
|
-
}
|
|
73
33
|
export class BrowserAgent extends Agent {
|
|
74
34
|
browserAgentEvents = new EventEmitter();
|
|
75
35
|
constructor({ agentOptions, browserOptions }) {
|
|
@@ -91,15 +51,14 @@ export class BrowserAgent extends Agent {
|
|
|
91
51
|
this.browserAgentEvents.emit('nav', url);
|
|
92
52
|
checkOperation();
|
|
93
53
|
await this.require(BrowserConnector).getHarness().navigate(url);
|
|
94
|
-
});
|
|
54
|
+
}, 'nav');
|
|
95
55
|
}
|
|
96
56
|
async extract(instructions, schema, options = {}) {
|
|
97
|
-
return this.runOperation(options, () => this._extract(instructions, schema));
|
|
57
|
+
return this.runOperation(options, () => this._extract(instructions, schema), 'extract');
|
|
98
58
|
}
|
|
99
59
|
async _extract(instructions, schema) {
|
|
100
60
|
this.browserAgentEvents.emit('extractStarted', instructions, schema);
|
|
101
|
-
|
|
102
|
-
const htmlContent = await retry(async () => await getFullPageContent(this.page), { retries: 5, delay: 200, exponential: true });
|
|
61
|
+
const htmlContent = await retry(() => getVisiblePageContent(this.page), { retries: 5, delay: 200, exponential: true });
|
|
103
62
|
// const accessibilityTree = await this.page.accessibility.snapshot({ interestingOnly: true });
|
|
104
63
|
// const pageRepr = renderMinimalAccessibilityTree(accessibilityTree);
|
|
105
64
|
const partitionOptions = {
|
package/dist/agent/errors.d.ts
CHANGED
|
@@ -1,8 +1,10 @@
|
|
|
1
|
+
import type { OperationDiagnostics } from '@/common/operation';
|
|
1
2
|
export interface AgentErrorOptions {
|
|
2
3
|
variant?: string;
|
|
3
4
|
adaptable?: boolean;
|
|
4
5
|
}
|
|
5
6
|
export declare class AgentError extends Error {
|
|
7
|
+
readonly operation?: OperationDiagnostics;
|
|
6
8
|
readonly options: Required<AgentErrorOptions>;
|
|
7
9
|
constructor(message: string, options?: AgentErrorOptions);
|
|
8
10
|
}
|
package/dist/agent/errors.js
CHANGED
|
@@ -51,7 +51,7 @@ export class OperationDeadlineError extends AgentError {
|
|
|
51
51
|
}
|
|
52
52
|
export class AgentBusyError extends AgentError {
|
|
53
53
|
constructor() {
|
|
54
|
-
super('Agent has
|
|
54
|
+
super('Agent has pending work; await whenIdle() before reusing it', { variant: 'busy' });
|
|
55
55
|
this.name = 'AgentBusyError';
|
|
56
56
|
}
|
|
57
57
|
}
|
package/dist/agent/index.d.ts
CHANGED
|
@@ -5,7 +5,7 @@ import { AgentEvents } from "@/common/events";
|
|
|
5
5
|
import { AgentConnector } from '@/connectors';
|
|
6
6
|
import { RenderableContent } from '@/memory/observation';
|
|
7
7
|
import { LLMClient } from "@/ai/types";
|
|
8
|
-
import { type OperationOptions } from '@/common/operation';
|
|
8
|
+
import { type OperationDiagnostics, type OperationKind, type OperationOptions } from '@/common/operation';
|
|
9
9
|
import { AgentMemory, MemoryRenderOptions } from "@/memory";
|
|
10
10
|
import { ActionDefinition } from "@/actions";
|
|
11
11
|
import { MultiModelHarness } from '@/ai/multiModelHarness';
|
|
@@ -33,18 +33,30 @@ export declare class Agent {
|
|
|
33
33
|
private _paused;
|
|
34
34
|
private _pauseResolve;
|
|
35
35
|
private activeOperation?;
|
|
36
|
+
private latestOperation?;
|
|
36
37
|
private idle;
|
|
37
|
-
private
|
|
38
|
+
private resolveIdle?;
|
|
39
|
+
private pendingWork;
|
|
40
|
+
private lifecycleTail;
|
|
41
|
+
private lifecycleRequest?;
|
|
42
|
+
private lifecycleState;
|
|
43
|
+
private telemetryStarted;
|
|
38
44
|
protected latestTaskMemory: AgentMemory;
|
|
39
45
|
constructor(baseConfig?: Partial<AgentOptions>);
|
|
40
46
|
getConnector<C extends AgentConnector>(connectorClass: new (...args: any[]) => C): C | undefined;
|
|
41
47
|
require<C extends AgentConnector>(connectorClass: new (...args: any[]) => C): C;
|
|
42
48
|
start(): Promise<void>;
|
|
49
|
+
get lifecycle(): 'new' | 'starting' | 'ready' | 'stopping' | 'stopped';
|
|
50
|
+
private beginWork;
|
|
51
|
+
private endWork;
|
|
52
|
+
private scheduleLifecycle;
|
|
43
53
|
identifyAction(action: Action): ActionDefinition<any>;
|
|
44
54
|
/** True until underlying work settles, including after a cancelled caller returns. */
|
|
45
55
|
get busy(): boolean;
|
|
46
56
|
whenIdle(): Promise<void>;
|
|
47
|
-
|
|
57
|
+
/** A payload-free snapshot of the active or most recent operation. */
|
|
58
|
+
get operation(): OperationDiagnostics | undefined;
|
|
59
|
+
protected runOperation<T>(options: OperationOptions, fn: () => Promise<T>, kind?: OperationKind): Promise<T>;
|
|
48
60
|
exec(action: Action, memory?: AgentMemory, options?: OperationOptions): Promise<unknown>;
|
|
49
61
|
private _exec;
|
|
50
62
|
protected _recordConnectorObservations(memory: AgentMemory): Promise<void>;
|
|
@@ -61,4 +73,5 @@ export declare class Agent {
|
|
|
61
73
|
resume(): void;
|
|
62
74
|
get paused(): boolean;
|
|
63
75
|
stop(): Promise<void>;
|
|
76
|
+
private stopConnectors;
|
|
64
77
|
}
|
package/dist/agent/index.js
CHANGED
|
@@ -3,7 +3,7 @@ import EventEmitter from "eventemitter3";
|
|
|
3
3
|
import z from "zod";
|
|
4
4
|
import { Observation } from '@/memory/observation';
|
|
5
5
|
import { ActionLimitError, AgentBusyError, AgentError } from "@/agent/errors";
|
|
6
|
-
import { Operation, checkOperation, currentOperation, operationOptions, untilAborted } from '@/common/operation';
|
|
6
|
+
import { Operation, attachOperationDiagnostics, checkOperation, currentOperation, measureOperation, operationOptions, untilAborted, withoutOperation, } from '@/common/operation';
|
|
7
7
|
import { AgentMemory } from "@/memory";
|
|
8
8
|
import { taskActions } from "@/actions/taskActions";
|
|
9
9
|
import { memoryActions } from '@/actions/memoryActions';
|
|
@@ -46,8 +46,14 @@ export class Agent {
|
|
|
46
46
|
_paused = false;
|
|
47
47
|
_pauseResolve = null;
|
|
48
48
|
activeOperation;
|
|
49
|
+
latestOperation;
|
|
49
50
|
idle = Promise.resolve();
|
|
50
|
-
|
|
51
|
+
resolveIdle;
|
|
52
|
+
pendingWork = 0;
|
|
53
|
+
lifecycleTail = Promise.resolve();
|
|
54
|
+
lifecycleRequest;
|
|
55
|
+
lifecycleState = 'new';
|
|
56
|
+
telemetryStarted = false;
|
|
51
57
|
latestTaskMemory; // | null = null;
|
|
52
58
|
constructor(baseConfig = {}) {
|
|
53
59
|
this.options = {
|
|
@@ -114,27 +120,63 @@ export class Agent {
|
|
|
114
120
|
return connector;
|
|
115
121
|
}
|
|
116
122
|
async start() {
|
|
123
|
+
return this.scheduleLifecycle('start', async () => {
|
|
124
|
+
if (this.lifecycleState === 'ready')
|
|
125
|
+
return;
|
|
126
|
+
this.lifecycleState = 'starting';
|
|
127
|
+
if (this.options.telemetry && !this.telemetryStarted) {
|
|
128
|
+
telemetrifyAgent(this);
|
|
129
|
+
this.telemetryStarted = true;
|
|
130
|
+
}
|
|
131
|
+
try {
|
|
132
|
+
await this.models.setup();
|
|
133
|
+
for (const connector of this.connectors)
|
|
134
|
+
await connector.onStart?.();
|
|
135
|
+
}
|
|
136
|
+
catch (error) {
|
|
137
|
+
await this.stopConnectors();
|
|
138
|
+
this.lifecycleState = 'stopped';
|
|
139
|
+
throw error;
|
|
140
|
+
}
|
|
141
|
+
this.lifecycleState = 'ready';
|
|
142
|
+
this.events.emit('start');
|
|
143
|
+
});
|
|
144
|
+
}
|
|
145
|
+
get lifecycle() {
|
|
146
|
+
return this.lifecycleState;
|
|
147
|
+
}
|
|
148
|
+
beginWork() {
|
|
149
|
+
if (this.pendingWork++ === 0)
|
|
150
|
+
this.idle = new Promise(resolve => { this.resolveIdle = resolve; });
|
|
151
|
+
}
|
|
152
|
+
endWork() {
|
|
153
|
+
if (--this.pendingWork === 0) {
|
|
154
|
+
const resolve = this.resolveIdle;
|
|
155
|
+
this.resolveIdle = undefined;
|
|
156
|
+
withoutOperation(() => this.latestOperation?.markIdle());
|
|
157
|
+
resolve?.();
|
|
158
|
+
}
|
|
159
|
+
}
|
|
160
|
+
scheduleLifecycle(kind, fn) {
|
|
117
161
|
checkOperation();
|
|
118
|
-
if (this.
|
|
162
|
+
if (kind === 'start' && this.activeOperation)
|
|
119
163
|
throw new AgentBusyError();
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
//
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
// logger.info("Initial observations recorded");
|
|
137
|
-
// Initial observations are handled by the first getObservations call in exec
|
|
164
|
+
if (this.lifecycleRequest?.kind === kind)
|
|
165
|
+
return this.lifecycleRequest.promise;
|
|
166
|
+
this.beginWork();
|
|
167
|
+
// Cleanup belongs to the agent, not the operation that requested stop().
|
|
168
|
+
return withoutOperation(() => {
|
|
169
|
+
const promise = this.lifecycleTail.then(fn).finally(() => {
|
|
170
|
+
if (this.lifecycleRequest?.promise === promise)
|
|
171
|
+
this.lifecycleRequest = undefined;
|
|
172
|
+
this.endWork();
|
|
173
|
+
});
|
|
174
|
+
this.lifecycleRequest = { kind, promise };
|
|
175
|
+
this.lifecycleTail = promise.catch(() => { });
|
|
176
|
+
if (kind === 'stop')
|
|
177
|
+
this.activeOperation?.cancel('Agent stopped');
|
|
178
|
+
return promise;
|
|
179
|
+
});
|
|
138
180
|
}
|
|
139
181
|
identifyAction(action) {
|
|
140
182
|
// Get definition corresponding to an action
|
|
@@ -148,23 +190,34 @@ export class Agent {
|
|
|
148
190
|
}
|
|
149
191
|
/** True until underlying work settles, including after a cancelled caller returns. */
|
|
150
192
|
get busy() {
|
|
151
|
-
return this.
|
|
193
|
+
return this.pendingWork > 0;
|
|
152
194
|
}
|
|
153
195
|
whenIdle() {
|
|
154
196
|
return this.idle;
|
|
155
197
|
}
|
|
156
|
-
|
|
198
|
+
/** A payload-free snapshot of the active or most recent operation. */
|
|
199
|
+
get operation() {
|
|
200
|
+
return this.latestOperation?.snapshot();
|
|
201
|
+
}
|
|
202
|
+
async runOperation(options, fn, kind = 'exec') {
|
|
157
203
|
const inherited = currentOperation();
|
|
158
204
|
if (inherited?.owner === this)
|
|
159
205
|
inherited.check();
|
|
160
|
-
if (this.busy)
|
|
161
|
-
|
|
162
|
-
|
|
206
|
+
if (this.busy) {
|
|
207
|
+
const error = new AgentBusyError();
|
|
208
|
+
if (this.activeOperation)
|
|
209
|
+
attachOperationDiagnostics(error, this.activeOperation.snapshot());
|
|
210
|
+
throw error;
|
|
211
|
+
}
|
|
212
|
+
if (this.lifecycleState === 'stopped')
|
|
163
213
|
throw new AgentError('Agent is stopped; call start() before using it');
|
|
164
|
-
const operation = new Operation(this, options
|
|
214
|
+
const operation = new Operation(this, options, kind, snapshot => {
|
|
215
|
+
this.events.emit('operation', snapshot);
|
|
216
|
+
});
|
|
165
217
|
this.activeOperation = operation;
|
|
166
|
-
|
|
167
|
-
this.
|
|
218
|
+
this.latestOperation = operation;
|
|
219
|
+
this.beginWork();
|
|
220
|
+
operation.announce();
|
|
168
221
|
const worker = operation.run(async () => {
|
|
169
222
|
operation.check();
|
|
170
223
|
try {
|
|
@@ -174,10 +227,13 @@ export class Agent {
|
|
|
174
227
|
operation.check(); // Preserve the cancellation/deadline cause through downstream errors.
|
|
175
228
|
}
|
|
176
229
|
});
|
|
177
|
-
const settled = worker.
|
|
178
|
-
operation.
|
|
230
|
+
const settled = worker.catch(error => {
|
|
231
|
+
operation.fail(error);
|
|
232
|
+
throw error;
|
|
233
|
+
}).finally(() => {
|
|
234
|
+
operation.finish();
|
|
179
235
|
this.activeOperation = undefined;
|
|
180
|
-
|
|
236
|
+
this.endWork();
|
|
181
237
|
});
|
|
182
238
|
return untilAborted(settled, operation.signal);
|
|
183
239
|
}
|
|
@@ -203,6 +259,9 @@ export class Agent {
|
|
|
203
259
|
if (!parsed.success) {
|
|
204
260
|
throw new AgentError(`Generated action '${action.variant}' violates input schema: ${parsed.error.message}`, { adaptable: true });
|
|
205
261
|
}
|
|
262
|
+
const operation = currentOperation();
|
|
263
|
+
operation?.prepareAction(actionDefinition.name);
|
|
264
|
+
checkOperation();
|
|
206
265
|
const memoryOnly = memoryActions.includes(actionDefinition);
|
|
207
266
|
if (!memoryOnly)
|
|
208
267
|
for (const connector of this.connectors) {
|
|
@@ -211,7 +270,19 @@ export class Agent {
|
|
|
211
270
|
}
|
|
212
271
|
this.events.emit('actionStarted', action);
|
|
213
272
|
checkOperation();
|
|
214
|
-
const data = await
|
|
273
|
+
const data = await measureOperation('action', async () => {
|
|
274
|
+
const options = operationOptions();
|
|
275
|
+
operation?.actionState('started');
|
|
276
|
+
try {
|
|
277
|
+
const result = await actionDefinition.resolver({ input: parsed.data, agent: this, memory, ...options });
|
|
278
|
+
operation?.actionState('completed');
|
|
279
|
+
return result;
|
|
280
|
+
}
|
|
281
|
+
catch (error) {
|
|
282
|
+
operation?.actionState('failed');
|
|
283
|
+
throw error;
|
|
284
|
+
}
|
|
285
|
+
});
|
|
215
286
|
checkOperation();
|
|
216
287
|
this.events.emit('actionDone', action);
|
|
217
288
|
checkOperation();
|
|
@@ -231,24 +302,23 @@ export class Agent {
|
|
|
231
302
|
return data;
|
|
232
303
|
}
|
|
233
304
|
async _recordConnectorObservations(memory) {
|
|
234
|
-
|
|
235
|
-
|
|
236
|
-
|
|
237
|
-
|
|
238
|
-
|
|
239
|
-
|
|
240
|
-
|
|
241
|
-
memory.recordObservation(obs);
|
|
305
|
+
return measureOperation('observations', async () => {
|
|
306
|
+
for (const connector of this.connectors) {
|
|
307
|
+
const connObservations = connector.collectObservations ? await connector.collectObservations(operationOptions()) : [];
|
|
308
|
+
checkOperation();
|
|
309
|
+
for (const obs of connObservations) {
|
|
310
|
+
memory.recordObservation(obs);
|
|
311
|
+
}
|
|
242
312
|
}
|
|
243
|
-
|
|
244
|
-
|
|
313
|
+
this.events.emit('observationsRecorded');
|
|
314
|
+
});
|
|
245
315
|
}
|
|
246
316
|
get memory() {
|
|
247
317
|
//if (!this.latestTaskMemory) throw new Error("No memory available");
|
|
248
318
|
return this.latestTaskMemory;
|
|
249
319
|
}
|
|
250
320
|
async act(taskOrSteps, options = {}) {
|
|
251
|
-
return this.runOperation(options, () => this._runAct(taskOrSteps, options));
|
|
321
|
+
return this.runOperation(options, () => this._runAct(taskOrSteps, options), 'act');
|
|
252
322
|
}
|
|
253
323
|
async _runAct(taskOrSteps, options) {
|
|
254
324
|
const instructions = [
|
|
@@ -286,28 +356,25 @@ export class Agent {
|
|
|
286
356
|
})(task));
|
|
287
357
|
}
|
|
288
358
|
async _buildContext(memory, options) {
|
|
289
|
-
|
|
290
|
-
|
|
291
|
-
|
|
292
|
-
|
|
293
|
-
|
|
294
|
-
|
|
295
|
-
|
|
296
|
-
|
|
297
|
-
|
|
298
|
-
|
|
299
|
-
|
|
300
|
-
instructions: instructions
|
|
301
|
-
});
|
|
359
|
+
return measureOperation('context', async () => {
|
|
360
|
+
const messages = await memory.render(options);
|
|
361
|
+
checkOperation();
|
|
362
|
+
const connectorInstructions = [];
|
|
363
|
+
for (const connector of this.connectors) {
|
|
364
|
+
if (connector.getInstructions) {
|
|
365
|
+
const instructions = await connector.getInstructions(operationOptions());
|
|
366
|
+
checkOperation();
|
|
367
|
+
if (instructions) {
|
|
368
|
+
connectorInstructions.push({ connectorId: connector.id, instructions });
|
|
369
|
+
}
|
|
302
370
|
}
|
|
303
371
|
}
|
|
304
|
-
|
|
305
|
-
|
|
306
|
-
|
|
307
|
-
|
|
308
|
-
|
|
309
|
-
|
|
310
|
-
};
|
|
372
|
+
return {
|
|
373
|
+
instructions: memory.instructions,
|
|
374
|
+
observationContent: messages,
|
|
375
|
+
connectorInstructions,
|
|
376
|
+
};
|
|
377
|
+
});
|
|
311
378
|
}
|
|
312
379
|
async _act(description, memory, options = {}) {
|
|
313
380
|
checkOperation();
|
|
@@ -433,7 +500,7 @@ export class Agent {
|
|
|
433
500
|
await this._recordConnectorObservations(this.latestTaskMemory);
|
|
434
501
|
const memoryContext = await this._buildContext(this.memory, options);
|
|
435
502
|
return await this.models.query(memoryContext, query, schema);
|
|
436
|
-
});
|
|
503
|
+
}, 'query');
|
|
437
504
|
}
|
|
438
505
|
async queueDone() {
|
|
439
506
|
checkOperation();
|
|
@@ -449,9 +516,13 @@ export class Agent {
|
|
|
449
516
|
return; // A pause listener may have resumed synchronously.
|
|
450
517
|
logger.info("Agent: Paused");
|
|
451
518
|
try {
|
|
452
|
-
await
|
|
453
|
-
this.
|
|
454
|
-
|
|
519
|
+
await measureOperation('paused', async () => {
|
|
520
|
+
if (!this._paused)
|
|
521
|
+
return; // A diagnostic listener may have resumed.
|
|
522
|
+
await untilAborted(new Promise((resolve) => {
|
|
523
|
+
this._pauseResolve = resolve;
|
|
524
|
+
}), currentOperation()?.signal);
|
|
525
|
+
});
|
|
455
526
|
}
|
|
456
527
|
finally {
|
|
457
528
|
this._pauseResolve = null;
|
|
@@ -475,19 +546,20 @@ export class Agent {
|
|
|
475
546
|
return this._paused;
|
|
476
547
|
}
|
|
477
548
|
async stop() {
|
|
478
|
-
|
|
479
|
-
|
|
480
|
-
|
|
481
|
-
|
|
482
|
-
|
|
483
|
-
this.stopped = true;
|
|
484
|
-
this.activeOperation?.cancel('Agent stopped');
|
|
485
|
-
this.doneActing = true;
|
|
486
|
-
if (this._paused) {
|
|
549
|
+
return this.scheduleLifecycle('stop', async () => {
|
|
550
|
+
if (this.lifecycleState === 'stopped')
|
|
551
|
+
return;
|
|
552
|
+
this.lifecycleState = 'stopping';
|
|
553
|
+
this.doneActing = true;
|
|
487
554
|
this._paused = false;
|
|
488
555
|
this._pauseResolve?.();
|
|
489
556
|
this._pauseResolve = null;
|
|
490
|
-
|
|
557
|
+
await this.stopConnectors();
|
|
558
|
+
this.lifecycleState = 'stopped';
|
|
559
|
+
this.events.emit('stop');
|
|
560
|
+
});
|
|
561
|
+
}
|
|
562
|
+
async stopConnectors() {
|
|
491
563
|
logger.info("Agent: Stopping connectors...");
|
|
492
564
|
for (const connector of this.connectors) {
|
|
493
565
|
try {
|
|
@@ -498,7 +570,6 @@ export class Agent {
|
|
|
498
570
|
logger.warn(`Agent: Error stopping connector ${connector.id}: ${error instanceof Error ? error.message : String(error)}`);
|
|
499
571
|
}
|
|
500
572
|
}
|
|
501
|
-
this.events.emit('stop');
|
|
502
573
|
logger.info("Agent: All connectors stopped.");
|
|
503
574
|
logger.info("Agent: Stopped successfully.");
|
|
504
575
|
}
|
package/dist/ai/modelHarness.js
CHANGED
|
@@ -11,7 +11,7 @@ import { parsePlannerResponse, PlannerResponseError, memoryUpdatesSchema } from
|
|
|
11
11
|
import { anthropicOutputFormat, plannerSchema, usesStructuredOutput } from './structuredOutput';
|
|
12
12
|
import { ModelResponseError } from './modelResponseError';
|
|
13
13
|
import { DEFAULT_BASETEN_MODEL } from './baseten';
|
|
14
|
-
import { checkOperation, operationOptions } from '@/common/operation';
|
|
14
|
+
import { beginOperationPhase, checkOperation, operationOptions } from '@/common/operation';
|
|
15
15
|
export class ModelHarness {
|
|
16
16
|
/**
|
|
17
17
|
* Strong reasoning agent for high level strategy and planning.
|
|
@@ -67,7 +67,9 @@ export class ModelHarness {
|
|
|
67
67
|
// Scope usage to this invocation, including failed parses and provider
|
|
68
68
|
// retries. A shared cumulative collector can double-count concurrent calls.
|
|
69
69
|
const collector = new Collector('model-call');
|
|
70
|
+
const finish = beginOperationPhase('model');
|
|
70
71
|
try {
|
|
72
|
+
checkOperation();
|
|
71
73
|
try {
|
|
72
74
|
return await invoke(collector);
|
|
73
75
|
}
|
|
@@ -98,6 +100,7 @@ export class ModelHarness {
|
|
|
98
100
|
}
|
|
99
101
|
}
|
|
100
102
|
finally {
|
|
103
|
+
finish();
|
|
101
104
|
for (const log of collector.logs) {
|
|
102
105
|
for (const call of log.calls) {
|
|
103
106
|
try {
|
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import { ModelHarness } from "./modelHarness";
|
|
2
2
|
import { allBrowserAgentRoles } from "./types";
|
|
3
3
|
import EventEmitter from "eventemitter3";
|
|
4
|
+
import { drainAll } from '@/common/operation';
|
|
4
5
|
export class MultiModelHarness {
|
|
5
6
|
/**
|
|
6
7
|
* Delegates model responsibilites to different LLMs and consolidates their usage
|
|
@@ -11,7 +12,7 @@ export class MultiModelHarness {
|
|
|
11
12
|
events = new EventEmitter();
|
|
12
13
|
constructor(clients) {
|
|
13
14
|
// Sort by specificity (from least specific to most specific)
|
|
14
|
-
const sortedClients = clients.
|
|
15
|
+
const sortedClients = [...clients].sort((a, b) => (b.roles ? b.roles.length : 9999) - (a.roles ? a.roles.length : 9999));
|
|
15
16
|
for (const client of sortedClients) {
|
|
16
17
|
const harness = new ModelHarness({ llm: client });
|
|
17
18
|
this.uniqueModels.push(harness);
|
|
@@ -30,7 +31,8 @@ export class MultiModelHarness {
|
|
|
30
31
|
}
|
|
31
32
|
}
|
|
32
33
|
async setup() {
|
|
33
|
-
|
|
34
|
+
// A failed model must not release startup while another is still initializing.
|
|
35
|
+
await drainAll(this.uniqueModels.map(model => model.setup()));
|
|
34
36
|
}
|
|
35
37
|
describe() {
|
|
36
38
|
// for now - describe least specific model
|
package/dist/common/events.d.ts
CHANGED
|
@@ -1,7 +1,10 @@
|
|
|
1
1
|
import { Action } from "@/actions/types";
|
|
2
2
|
import { ActOptions } from "@/agent";
|
|
3
3
|
import { ModelUsage } from "@/ai/types";
|
|
4
|
+
import type { OperationDiagnostics } from './operation';
|
|
4
5
|
export interface AgentEvents {
|
|
6
|
+
/** Payload-free snapshots. Existing action/thought events retain their original payloads. */
|
|
7
|
+
'operation': (diagnostics: OperationDiagnostics) => void;
|
|
5
8
|
'start': () => void;
|
|
6
9
|
'stop': () => void;
|
|
7
10
|
'thought': (thought: string) => void;
|
|
@@ -3,25 +3,76 @@ export interface OperationOptions {
|
|
|
3
3
|
/** Absolute Unix time in milliseconds, shared by every step, retry, and wait. */
|
|
4
4
|
deadline?: number;
|
|
5
5
|
}
|
|
6
|
+
export type OperationKind = 'act' | 'query' | 'extract' | 'nav' | 'exec';
|
|
7
|
+
export type OperationPhase = 'preparing' | 'context' | 'observations' | 'model' | 'action' | 'screenshot' | 'stability' | 'cooldown' | 'retry' | 'paused' | 'cursor';
|
|
8
|
+
export interface OperationTiming {
|
|
9
|
+
count: number;
|
|
10
|
+
totalMs: number;
|
|
11
|
+
}
|
|
12
|
+
export interface OperationDiagnostics {
|
|
13
|
+
id: string;
|
|
14
|
+
kind: OperationKind;
|
|
15
|
+
status: 'running' | 'draining' | 'finished';
|
|
16
|
+
outcome?: 'succeeded' | 'failed' | 'cancelled' | 'deadline';
|
|
17
|
+
phase: OperationPhase;
|
|
18
|
+
startedAt: number;
|
|
19
|
+
elapsedMs: number;
|
|
20
|
+
cancellationToDrainMs?: number;
|
|
21
|
+
cancellationToIdleMs?: number;
|
|
22
|
+
lastAction?: {
|
|
23
|
+
index: number;
|
|
24
|
+
name: string;
|
|
25
|
+
state: 'pending' | 'started' | 'completed' | 'failed';
|
|
26
|
+
};
|
|
27
|
+
/** Inclusive totals: nested and concurrent phases overlap and must not be summed. */
|
|
28
|
+
timings: Partial<Record<OperationPhase, OperationTiming>>;
|
|
29
|
+
}
|
|
30
|
+
/** Preserve the original error and add only a payload-free snapshot, when extensible. */
|
|
31
|
+
export declare function attachOperationDiagnostics(error: unknown, diagnostics: OperationDiagnostics): void;
|
|
6
32
|
export declare class Operation {
|
|
7
33
|
readonly owner: object;
|
|
8
|
-
private
|
|
34
|
+
private kind;
|
|
35
|
+
private onUpdate?;
|
|
9
36
|
private controller;
|
|
10
37
|
private timer?;
|
|
11
38
|
private closed;
|
|
12
39
|
readonly signal: AbortSignal;
|
|
13
40
|
readonly deadline?: number;
|
|
41
|
+
private externalSignal?;
|
|
14
42
|
private externalAbort;
|
|
15
|
-
|
|
43
|
+
private readonly id;
|
|
44
|
+
private readonly startedAt;
|
|
45
|
+
private readonly started;
|
|
46
|
+
private finished?;
|
|
47
|
+
private cancelled?;
|
|
48
|
+
private idle?;
|
|
49
|
+
private announced;
|
|
50
|
+
private outcome?;
|
|
51
|
+
private lastAction?;
|
|
52
|
+
private timings;
|
|
53
|
+
private spans;
|
|
54
|
+
constructor(owner: object, options: OperationOptions, kind?: OperationKind, onUpdate?: ((diagnostics: OperationDiagnostics) => void) | undefined);
|
|
16
55
|
private scheduleDeadline;
|
|
17
56
|
cancel(reason?: unknown): void;
|
|
18
57
|
check(): void;
|
|
19
58
|
run<T>(fn: () => Promise<T>): Promise<T>;
|
|
59
|
+
announce(): void;
|
|
60
|
+
private publish;
|
|
61
|
+
snapshot(): OperationDiagnostics;
|
|
62
|
+
beginPhase(phase: OperationPhase): () => void;
|
|
63
|
+
prepareAction(name: string): void;
|
|
64
|
+
actionState(state: 'started' | 'completed' | 'failed'): void;
|
|
65
|
+
fail(error: unknown): void;
|
|
66
|
+
finish(): void;
|
|
67
|
+
markIdle(): void;
|
|
20
68
|
dispose(): void;
|
|
21
69
|
}
|
|
22
70
|
export declare function currentOperation(): Operation | undefined;
|
|
71
|
+
export declare function withoutOperation<T>(fn: () => T): T;
|
|
23
72
|
export declare function checkOperation(): void;
|
|
24
73
|
export declare function operationOptions(): OperationOptions;
|
|
74
|
+
export declare function beginOperationPhase(phase: OperationPhase): () => void;
|
|
75
|
+
export declare function measureOperation<T>(phase: OperationPhase, fn: () => Promise<T>): Promise<T>;
|
|
25
76
|
/** Wait for every branch, even when one fails, before releasing the operation. */
|
|
26
77
|
export declare function drainAll<T extends readonly unknown[]>(promises: {
|
|
27
78
|
[K in keyof T]: Promise<T[K]>;
|