@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.
@@ -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
- //const htmlContent = await this.page.content();
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 = {
@@ -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
  }
@@ -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 an active operation; await whenIdle() before reusing it', { variant: 'busy' });
54
+ super('Agent has pending work; await whenIdle() before reusing it', { variant: 'busy' });
55
55
  this.name = 'AgentBusyError';
56
56
  }
57
57
  }
@@ -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 stopped;
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
- protected runOperation<T>(options: OperationOptions, fn: () => Promise<T>): Promise<T>;
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
  }
@@ -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
- stopped = false;
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.busy)
162
+ if (kind === 'start' && this.activeOperation)
119
163
  throw new AgentBusyError();
120
- // Register telemetry if enabled - do on start instead of cons to prevent weird subclass event issues
121
- if (this.options.telemetry)
122
- telemetrifyAgent(this);
123
- //console.log('setting up model')
124
- await this.models.setup();
125
- //console.log('done setting up model')
126
- logger.info("Agent: Starting connectors...");
127
- for (const connector of this.connectors) {
128
- if (connector.onStart)
129
- await connector.onStart();
130
- }
131
- this.events.emit('start');
132
- this.stopped = false;
133
- logger.info("Agent: All connectors started.");
134
- // logger.info("Making initial observations...");
135
- // await this._recordConnectorObservations();
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.activeOperation !== undefined;
193
+ return this.pendingWork > 0;
152
194
  }
153
195
  whenIdle() {
154
196
  return this.idle;
155
197
  }
156
- async runOperation(options, fn) {
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
- throw new AgentBusyError();
162
- if (this.stopped)
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
- let markIdle;
167
- this.idle = new Promise(resolve => { markIdle = resolve; });
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.finally(() => {
178
- operation.dispose();
230
+ const settled = worker.catch(error => {
231
+ operation.fail(error);
232
+ throw error;
233
+ }).finally(() => {
234
+ operation.finish();
179
235
  this.activeOperation = undefined;
180
- markIdle();
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 actionDefinition.resolver({ input: parsed.data, agent: this, memory, ...operationOptions() });
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
- checkOperation();
235
- for (const connector of this.connectors) {
236
- // could do Promise.all if matters
237
- const connObservations = connector.collectObservations ? await connector.collectObservations(operationOptions()) : [];
238
- checkOperation();
239
- //observations.push(...connObservations);
240
- for (const obs of connObservations) {
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
- this.events.emit('observationsRecorded');
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
- checkOperation();
290
- const messages = await memory.render(options);
291
- checkOperation();
292
- const connectorInstructions = [];
293
- for (const connector of this.connectors) {
294
- if (connector.getInstructions) {
295
- const instructions = await connector.getInstructions(operationOptions());
296
- checkOperation();
297
- if (instructions) {
298
- connectorInstructions.push({
299
- connectorId: connector.id,
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
- return {
306
- instructions: memory.instructions,
307
- observationContent: messages,
308
- //observationContent: content,
309
- connectorInstructions: connectorInstructions
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 untilAborted(new Promise((resolve) => {
453
- this._pauseResolve = resolve;
454
- }), currentOperation()?.signal);
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
- checkOperation();
479
- /**
480
- * Stop the agent and close the browser context.
481
- * May be called asynchronously and interrupt an agent in the middle of a action sequence.
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
  }
@@ -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.toSorted((a, b) => (b.roles ? b.roles.length : 9999) - (a.roles ? a.roles.length : 9999));
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
- await Promise.all(this.uniqueModels.map(model => model.setup()));
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
@@ -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 options;
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
- constructor(owner: object, options: OperationOptions);
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]>;