@agirails/sdk 3.5.3 → 4.0.0-beta.0
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/cli/agirails.d.ts +4 -1
- package/dist/cli/agirails.d.ts.map +1 -1
- package/dist/cli/agirails.js +34 -2
- package/dist/cli/agirails.js.map +1 -1
- package/dist/cli/commands/agent.d.ts.map +1 -1
- package/dist/cli/commands/agent.js +62 -21
- package/dist/cli/commands/agent.js.map +1 -1
- package/dist/cli/commands/pay.d.ts +7 -0
- package/dist/cli/commands/pay.d.ts.map +1 -1
- package/dist/cli/commands/pay.js +34 -1
- package/dist/cli/commands/pay.js.map +1 -1
- package/dist/cli/commands/request.d.ts +19 -0
- package/dist/cli/commands/request.d.ts.map +1 -0
- package/dist/cli/commands/request.js +167 -0
- package/dist/cli/commands/request.js.map +1 -0
- package/dist/cli/commands/serve.d.ts +12 -7
- package/dist/cli/commands/serve.d.ts.map +1 -1
- package/dist/cli/commands/serve.js +12 -7
- package/dist/cli/commands/serve.js.map +1 -1
- package/dist/cli/commands/test.d.ts +22 -4
- package/dist/cli/commands/test.d.ts.map +1 -1
- package/dist/cli/commands/test.js +139 -292
- package/dist/cli/commands/test.js.map +1 -1
- package/dist/cli/commands/tx.js +13 -0
- package/dist/cli/commands/tx.js.map +1 -1
- package/dist/cli/index.js +7 -2
- package/dist/cli/index.js.map +1 -1
- package/dist/cli/lib/resolveAgent.d.ts +67 -0
- package/dist/cli/lib/resolveAgent.d.ts.map +1 -0
- package/dist/cli/lib/resolveAgent.js +121 -0
- package/dist/cli/lib/resolveAgent.js.map +1 -0
- package/dist/cli/lib/runRequest.d.ts +114 -0
- package/dist/cli/lib/runRequest.d.ts.map +1 -0
- package/dist/cli/lib/runRequest.js +324 -0
- package/dist/cli/lib/runRequest.js.map +1 -0
- package/dist/cli/lib/serviceNameForHash.d.ts +48 -0
- package/dist/cli/lib/serviceNameForHash.d.ts.map +1 -0
- package/dist/cli/lib/serviceNameForHash.js +62 -0
- package/dist/cli/lib/serviceNameForHash.js.map +1 -0
- package/dist/level0/request.d.ts.map +1 -1
- package/dist/level0/request.js +18 -6
- package/dist/level0/request.js.map +1 -1
- package/dist/level1/Agent.d.ts +72 -8
- package/dist/level1/Agent.d.ts.map +1 -1
- package/dist/level1/Agent.js +242 -110
- package/dist/level1/Agent.js.map +1 -1
- package/dist/negotiation/BuyerOrchestrator.d.ts +1 -1
- package/dist/negotiation/BuyerOrchestrator.d.ts.map +1 -1
- package/dist/negotiation/BuyerOrchestrator.js +10 -1
- package/dist/negotiation/BuyerOrchestrator.js.map +1 -1
- package/dist/protocol/EventMonitor.d.ts +26 -1
- package/dist/protocol/EventMonitor.d.ts.map +1 -1
- package/dist/protocol/EventMonitor.js +18 -4
- package/dist/protocol/EventMonitor.js.map +1 -1
- package/dist/runtime/BlockchainRuntime.d.ts +73 -0
- package/dist/runtime/BlockchainRuntime.d.ts.map +1 -1
- package/dist/runtime/BlockchainRuntime.js +121 -1
- package/dist/runtime/BlockchainRuntime.js.map +1 -1
- package/dist/runtime/IACTPRuntime.d.ts +29 -0
- package/dist/runtime/IACTPRuntime.d.ts.map +1 -1
- package/dist/runtime/MockRuntime.d.ts.map +1 -1
- package/dist/runtime/MockRuntime.js +21 -4
- package/dist/runtime/MockRuntime.js.map +1 -1
- package/dist/runtime/MockStateManager.d.ts.map +1 -1
- package/dist/runtime/MockStateManager.js +18 -0
- package/dist/runtime/MockStateManager.js.map +1 -1
- package/dist/runtime/types/MockState.d.ts +12 -0
- package/dist/runtime/types/MockState.d.ts.map +1 -1
- package/dist/runtime/types/MockState.js.map +1 -1
- package/dist/types/agent.d.ts +6 -1
- package/dist/types/agent.d.ts.map +1 -1
- package/package.json +2 -1
package/dist/level1/Agent.d.ts
CHANGED
|
@@ -214,6 +214,13 @@ export declare class Agent extends EventEmitter {
|
|
|
214
214
|
* Registered services
|
|
215
215
|
*/
|
|
216
216
|
private services;
|
|
217
|
+
/**
|
|
218
|
+
* Hash-keyed mirror of `services`, populated alongside it in `provide()`.
|
|
219
|
+
* Key: `keccak256(toUtf8Bytes(name)).toLowerCase()`. Lookups match
|
|
220
|
+
* on-chain `tx.serviceHash` directly so BlockchainRuntime-sourced jobs
|
|
221
|
+
* route without depending on string `serviceDescription`. PRD §5.4.
|
|
222
|
+
*/
|
|
223
|
+
private handlersByHash;
|
|
217
224
|
/**
|
|
218
225
|
* Active jobs
|
|
219
226
|
*
|
|
@@ -261,6 +268,12 @@ export declare class Agent extends EventEmitter {
|
|
|
261
268
|
* Polling interval ID (for job polling)
|
|
262
269
|
*/
|
|
263
270
|
private pollingIntervalId?;
|
|
271
|
+
/**
|
|
272
|
+
* Cleanup function returned by `BlockchainRuntime.subscribeProviderJobs`,
|
|
273
|
+
* set by `subscribeIfBlockchain()` and cleared by `unsubscribe()`. Undefined
|
|
274
|
+
* means no live subscription. PRD §5.3.
|
|
275
|
+
*/
|
|
276
|
+
private jobSubscriptionCleanup?;
|
|
264
277
|
/**
|
|
265
278
|
* Logger instance
|
|
266
279
|
*/
|
|
@@ -288,15 +301,32 @@ export declare class Agent extends EventEmitter {
|
|
|
288
301
|
/**
|
|
289
302
|
* Pause the agent
|
|
290
303
|
*
|
|
291
|
-
* Stops accepting new jobs but keeps active jobs running.
|
|
304
|
+
* Stops accepting new jobs but keeps active jobs running. PRD §5.3:
|
|
305
|
+
* pause() now tears down the on-chain subscription as well — a paused
|
|
306
|
+
* agent must not silently keep dispatching jobs via the live event path.
|
|
307
|
+
* Behavior change from 3.5.3 (was a silent bug); see CHANGELOG /
|
|
308
|
+
* MIGRATION-4.0 bullet 4 for drain-on-pause migration guidance.
|
|
292
309
|
*/
|
|
293
310
|
pause(): void;
|
|
294
311
|
/**
|
|
295
312
|
* Resume the agent
|
|
296
313
|
*
|
|
297
|
-
* Resumes accepting new jobs after being paused.
|
|
314
|
+
* Resumes accepting new jobs after being paused. PRD §5.3: re-establishes
|
|
315
|
+
* the on-chain subscription that pause() tore down.
|
|
298
316
|
*/
|
|
299
317
|
resume(): void;
|
|
318
|
+
/**
|
|
319
|
+
* Subscribe to live TransactionCreated events when the underlying runtime
|
|
320
|
+
* supports it (currently `BlockchainRuntime` only — `MockRuntime` providers
|
|
321
|
+
* receive jobs through polling). Idempotent: if a subscription is already
|
|
322
|
+
* active, this is a logged noop so a second `start()` on an already-running
|
|
323
|
+
* agent doesn't leak event listeners. PRD §5.3.
|
|
324
|
+
*/
|
|
325
|
+
private subscribeIfBlockchain;
|
|
326
|
+
/**
|
|
327
|
+
* Tear down a live subscription if one is active. Idempotent. PRD §5.3.
|
|
328
|
+
*/
|
|
329
|
+
private unsubscribe;
|
|
300
330
|
/**
|
|
301
331
|
* Restart the agent
|
|
302
332
|
*/
|
|
@@ -416,19 +446,43 @@ export declare class Agent extends EventEmitter {
|
|
|
416
446
|
* the appropriate service handler.
|
|
417
447
|
*/
|
|
418
448
|
private pollForJobs;
|
|
449
|
+
/**
|
|
450
|
+
* Shared per-transaction acceptance pipeline. Reached from two sources:
|
|
451
|
+
* - `pollForJobs` — bounded sweep over INITIATED transactions
|
|
452
|
+
* - `subscribeIfBlockchain` — live `TransactionCreated` events
|
|
453
|
+
*
|
|
454
|
+
* Atomic in single-threaded JS via `processingLocks` (Set). The lock
|
|
455
|
+
* release is in a `finally` so any error/throw — handler dispatch
|
|
456
|
+
* failure, linkEscrow revert, malformed payload — does not permanently
|
|
457
|
+
* occupy the slot. Poison TXs become re-tryable on the next sweep.
|
|
458
|
+
*
|
|
459
|
+
* Errors are emitted to consumers but never propagated; the caller loop
|
|
460
|
+
* must not be killed by a single bad TX. PRD §5.3.
|
|
461
|
+
*/
|
|
462
|
+
private handleIncomingTransaction;
|
|
419
463
|
/**
|
|
420
464
|
* Find service handler for a transaction
|
|
421
465
|
*
|
|
422
466
|
*Security: Use exact field matching instead of substring search
|
|
423
467
|
* to prevent service routing spoofing attacks.
|
|
424
468
|
*
|
|
425
|
-
*
|
|
426
|
-
*
|
|
427
|
-
*
|
|
428
|
-
*
|
|
429
|
-
*
|
|
469
|
+
* Dispatch order:
|
|
470
|
+
* PRIMARY (PRD §5.4 — on-chain Layer B):
|
|
471
|
+
* Match by `tx.serviceHash` against the `handlersByHash` map.
|
|
472
|
+
* Skips ZeroHash (Level 0 `pay` semantics — no handler routing).
|
|
473
|
+
* FALLBACK (preserves MockRuntime test fixtures + legacy clients):
|
|
474
|
+
* 5-step `serviceDescription` dispatch — JSON / legacy /
|
|
475
|
+
* hash-only / string exact match.
|
|
430
476
|
*/
|
|
431
477
|
private findServiceHandler;
|
|
478
|
+
/**
|
|
479
|
+
* Legacy string-based service dispatch — kept as a fallback for
|
|
480
|
+
* MockRuntime-style transactions where `serviceDescription` still carries
|
|
481
|
+
* the JSON / legacy / plain-name shape. PRD §5.4 routes by hash first;
|
|
482
|
+
* this method is only reached when the hash branch misses or the TX has
|
|
483
|
+
* `serviceHash === ZeroHash`.
|
|
484
|
+
*/
|
|
485
|
+
private findServiceHandlerByString;
|
|
432
486
|
/**
|
|
433
487
|
* Check if job should be auto-accepted
|
|
434
488
|
*
|
|
@@ -439,7 +493,17 @@ export declare class Agent extends EventEmitter {
|
|
|
439
493
|
*/
|
|
440
494
|
private shouldAutoAccept;
|
|
441
495
|
/**
|
|
442
|
-
* Create Job object from MockTransaction
|
|
496
|
+
* Create Job object from MockTransaction.
|
|
497
|
+
*
|
|
498
|
+
* `matched` is the handler entry returned by `findServiceHandler(tx)`.
|
|
499
|
+
* When supplied, `job.service` is taken from `matched.config.name` —
|
|
500
|
+
* this is the only correct source for hash-only TXs (BlockchainRuntime),
|
|
501
|
+
* where `serviceDescription` is empty and `extractServiceName(tx)` would
|
|
502
|
+
* return `'unknown'`. PRD §5.4.1.
|
|
503
|
+
*
|
|
504
|
+
* When `matched` is not supplied (e.g. shouldAutoAccept's autoAccept
|
|
505
|
+
* callback path before this commit, MockRuntime-only test fixtures),
|
|
506
|
+
* fall back to the legacy `extractServiceName` so behavior is unchanged.
|
|
443
507
|
*/
|
|
444
508
|
private createJobFromTransaction;
|
|
445
509
|
/**
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"Agent.d.ts","sourceRoot":"","sources":["../../src/level1/Agent.ts"],"names":[],"mappings":"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
|
|
1
|
+
{"version":3,"file":"Agent.d.ts","sourceRoot":"","sources":["../../src/level1/Agent.ts"],"names":[],"mappings":"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"}
|
package/dist/level1/Agent.js
CHANGED
|
@@ -83,6 +83,13 @@ class Agent extends events_1.EventEmitter {
|
|
|
83
83
|
* Registered services
|
|
84
84
|
*/
|
|
85
85
|
this.services = new Map();
|
|
86
|
+
/**
|
|
87
|
+
* Hash-keyed mirror of `services`, populated alongside it in `provide()`.
|
|
88
|
+
* Key: `keccak256(toUtf8Bytes(name)).toLowerCase()`. Lookups match
|
|
89
|
+
* on-chain `tx.serviceHash` directly so BlockchainRuntime-sourced jobs
|
|
90
|
+
* route without depending on string `serviceDescription`. PRD §5.4.
|
|
91
|
+
*/
|
|
92
|
+
this.handlersByHash = new Map();
|
|
86
93
|
/**
|
|
87
94
|
* Active jobs
|
|
88
95
|
*
|
|
@@ -173,6 +180,15 @@ class Agent extends events_1.EventEmitter {
|
|
|
173
180
|
* @throws {AgentLifecycleError} If agent is not in idle or stopped state
|
|
174
181
|
*/
|
|
175
182
|
async start() {
|
|
183
|
+
// PRD §5.3: idempotent start. Calling start() on a running or paused
|
|
184
|
+
// agent is a logged noop instead of a thrown AgentLifecycleError. This
|
|
185
|
+
// is a behavior change from 3.5.3 — see CHANGELOG / MIGRATION-4.0.
|
|
186
|
+
if (this._status === 'running' || this._status === 'paused') {
|
|
187
|
+
this.logger.warn('Agent.start() called on already-started agent — noop', {
|
|
188
|
+
status: this._status,
|
|
189
|
+
});
|
|
190
|
+
return;
|
|
191
|
+
}
|
|
176
192
|
if (this._status !== 'idle' && this._status !== 'stopped') {
|
|
177
193
|
throw new errors_1.AgentLifecycleError(this._status, 'start');
|
|
178
194
|
}
|
|
@@ -195,10 +211,16 @@ class Agent extends events_1.EventEmitter {
|
|
|
195
211
|
rpcUrl,
|
|
196
212
|
});
|
|
197
213
|
this.startPolling();
|
|
214
|
+
this.subscribeIfBlockchain();
|
|
198
215
|
this._status = 'running';
|
|
199
216
|
this.emit('started');
|
|
200
217
|
}
|
|
201
218
|
catch (error) {
|
|
219
|
+
// PRD §5.3: a partial start (e.g. polling started, subscription threw,
|
|
220
|
+
// or ACTPClient.create rejected) must not leak the polling timer or
|
|
221
|
+
// a live subscription. Clean both before propagating.
|
|
222
|
+
this.stopPolling();
|
|
223
|
+
this.unsubscribe();
|
|
202
224
|
this._status = 'stopped';
|
|
203
225
|
this.emit('error', error);
|
|
204
226
|
throw error;
|
|
@@ -215,8 +237,9 @@ class Agent extends events_1.EventEmitter {
|
|
|
215
237
|
}
|
|
216
238
|
this._status = 'stopping';
|
|
217
239
|
this.emit('stopping');
|
|
218
|
-
// Stop polling
|
|
240
|
+
// Stop polling + tear down subscription. PRD §5.3.
|
|
219
241
|
this.stopPolling();
|
|
242
|
+
this.unsubscribe();
|
|
220
243
|
// Wait for active jobs to complete (with timeout)
|
|
221
244
|
await this.waitForActiveJobs(30000); // 30s timeout
|
|
222
245
|
this._status = 'stopped';
|
|
@@ -226,29 +249,85 @@ class Agent extends events_1.EventEmitter {
|
|
|
226
249
|
/**
|
|
227
250
|
* Pause the agent
|
|
228
251
|
*
|
|
229
|
-
* Stops accepting new jobs but keeps active jobs running.
|
|
252
|
+
* Stops accepting new jobs but keeps active jobs running. PRD §5.3:
|
|
253
|
+
* pause() now tears down the on-chain subscription as well — a paused
|
|
254
|
+
* agent must not silently keep dispatching jobs via the live event path.
|
|
255
|
+
* Behavior change from 3.5.3 (was a silent bug); see CHANGELOG /
|
|
256
|
+
* MIGRATION-4.0 bullet 4 for drain-on-pause migration guidance.
|
|
230
257
|
*/
|
|
231
258
|
pause() {
|
|
232
259
|
if (this._status !== 'running') {
|
|
233
260
|
throw new errors_1.AgentLifecycleError(this._status, 'pause');
|
|
234
261
|
}
|
|
235
262
|
this.stopPolling();
|
|
263
|
+
this.unsubscribe();
|
|
236
264
|
this._status = 'paused';
|
|
237
265
|
this.emit('paused');
|
|
238
266
|
}
|
|
239
267
|
/**
|
|
240
268
|
* Resume the agent
|
|
241
269
|
*
|
|
242
|
-
* Resumes accepting new jobs after being paused.
|
|
270
|
+
* Resumes accepting new jobs after being paused. PRD §5.3: re-establishes
|
|
271
|
+
* the on-chain subscription that pause() tore down.
|
|
243
272
|
*/
|
|
244
273
|
resume() {
|
|
245
274
|
if (this._status !== 'paused') {
|
|
246
275
|
throw new errors_1.AgentLifecycleError(this._status, 'resume');
|
|
247
276
|
}
|
|
248
|
-
|
|
277
|
+
// PRD §5.3.1: same partial-failure shape as start() — if subscription
|
|
278
|
+
// wiring throws after the polling timer is armed, tear both down before
|
|
279
|
+
// propagating so the agent doesn't leak a live timer while still in
|
|
280
|
+
// 'paused' status. Without this, the next resume() call would short-
|
|
281
|
+
// circuit on the state check and the orphaned timer would survive.
|
|
282
|
+
try {
|
|
283
|
+
this.startPolling();
|
|
284
|
+
this.subscribeIfBlockchain();
|
|
285
|
+
}
|
|
286
|
+
catch (err) {
|
|
287
|
+
this.stopPolling();
|
|
288
|
+
this.unsubscribe();
|
|
289
|
+
throw err;
|
|
290
|
+
}
|
|
249
291
|
this._status = 'running';
|
|
250
292
|
this.emit('resumed');
|
|
251
293
|
}
|
|
294
|
+
/**
|
|
295
|
+
* Subscribe to live TransactionCreated events when the underlying runtime
|
|
296
|
+
* supports it (currently `BlockchainRuntime` only — `MockRuntime` providers
|
|
297
|
+
* receive jobs through polling). Idempotent: if a subscription is already
|
|
298
|
+
* active, this is a logged noop so a second `start()` on an already-running
|
|
299
|
+
* agent doesn't leak event listeners. PRD §5.3.
|
|
300
|
+
*/
|
|
301
|
+
subscribeIfBlockchain() {
|
|
302
|
+
if (this.jobSubscriptionCleanup) {
|
|
303
|
+
this.logger.warn('Agent: subscription already active, refusing to double-subscribe');
|
|
304
|
+
return;
|
|
305
|
+
}
|
|
306
|
+
const runtime = this._client?.runtime;
|
|
307
|
+
if (!runtime || typeof runtime.subscribeProviderJobs !== 'function') {
|
|
308
|
+
return;
|
|
309
|
+
}
|
|
310
|
+
this.jobSubscriptionCleanup = runtime.subscribeProviderJobs(this.address, (tx) => {
|
|
311
|
+
this.handleIncomingTransaction(tx).catch((err) => this.emit('error', err));
|
|
312
|
+
});
|
|
313
|
+
this.logger.info('Subscribed to on-chain TransactionCreated events', {
|
|
314
|
+
provider: this.address,
|
|
315
|
+
});
|
|
316
|
+
}
|
|
317
|
+
/**
|
|
318
|
+
* Tear down a live subscription if one is active. Idempotent. PRD §5.3.
|
|
319
|
+
*/
|
|
320
|
+
unsubscribe() {
|
|
321
|
+
if (this.jobSubscriptionCleanup) {
|
|
322
|
+
try {
|
|
323
|
+
this.jobSubscriptionCleanup();
|
|
324
|
+
}
|
|
325
|
+
catch (err) {
|
|
326
|
+
this.logger.warn('Subscription cleanup threw — continuing', { err });
|
|
327
|
+
}
|
|
328
|
+
this.jobSubscriptionCleanup = undefined;
|
|
329
|
+
}
|
|
330
|
+
}
|
|
252
331
|
/**
|
|
253
332
|
* Restart the agent
|
|
254
333
|
*/
|
|
@@ -300,7 +379,13 @@ class Agent extends events_1.EventEmitter {
|
|
|
300
379
|
if (this.services.has(config.name)) {
|
|
301
380
|
throw new errors_1.ServiceConfigError('name', `Service "${config.name}" already registered`);
|
|
302
381
|
}
|
|
382
|
+
// PRD §5.4: derive the on-chain routing key alongside the string key.
|
|
383
|
+
// Same formula used by `actp request --service <name>` (see PRD §A.1 +
|
|
384
|
+
// AgentRegistry.computeServiceTypeHash), so BlockchainRuntime jobs match
|
|
385
|
+
// the same handler that MockRuntime tests register.
|
|
386
|
+
const hashKey = ethers_1.ethers.keccak256(ethers_1.ethers.toUtf8Bytes(config.name)).toLowerCase();
|
|
303
387
|
this.services.set(config.name, { config, handler });
|
|
388
|
+
this.handlersByHash.set(hashKey, { config, handler });
|
|
304
389
|
this.emit('service:registered', config.name);
|
|
305
390
|
this.logger.info('Service registered', { service: config.name });
|
|
306
391
|
return this;
|
|
@@ -506,101 +591,18 @@ class Agent extends events_1.EventEmitter {
|
|
|
506
591
|
}
|
|
507
592
|
try {
|
|
508
593
|
// Security: Use filtered query instead of getAllTransactions
|
|
509
|
-
// This prevents DoS via memory exhaustion by only fetching relevant transactions
|
|
510
|
-
|
|
511
|
-
//
|
|
512
|
-
|
|
513
|
-
// Use optimized filtered query (max 100 jobs per poll)
|
|
514
|
-
pendingJobs = await this._client.runtime.getTransactionsByProvider(this.address, 'INITIATED', 100);
|
|
515
|
-
}
|
|
516
|
-
else {
|
|
517
|
-
// Fallback to getAllTransactions (for older runtime versions)
|
|
518
|
-
const allTransactions = await this._client.runtime.getAllTransactions();
|
|
519
|
-
pendingJobs = allTransactions.filter((tx) => tx.provider === this.address && tx.state === 'INITIATED');
|
|
520
|
-
}
|
|
594
|
+
// This prevents DoS via memory exhaustion by only fetching relevant transactions.
|
|
595
|
+
// PRD §5.1: getTransactionsByProvider is now required on IACTPRuntime —
|
|
596
|
+
// the prior duck-type fallback to getAllTransactions is gone.
|
|
597
|
+
const pendingJobs = await this._client.runtime.getTransactionsByProvider(this.address, 'INITIATED', 100);
|
|
521
598
|
this.logger.debug('Polling for jobs', {
|
|
522
599
|
pendingJobs: pendingJobs.length,
|
|
523
600
|
});
|
|
524
|
-
// Process each pending job
|
|
601
|
+
// Process each pending job through the shared acceptance pipeline so
|
|
602
|
+
// poll and subscription paths converge on identical semantics
|
|
603
|
+
// (dedup, provider check, routing, auto-accept, linkEscrow, emit).
|
|
525
604
|
for (const tx of pendingJobs) {
|
|
526
|
-
|
|
527
|
-
// Security: Check processingLocks first (atomic check)
|
|
528
|
-
// This prevents race conditions where two poll cycles both try to process
|
|
529
|
-
// the same job before either transitions the state
|
|
530
|
-
if (this.processingLocks.has(tx.id) || this.processedJobs.has(tx.id)) {
|
|
531
|
-
continue;
|
|
532
|
-
}
|
|
533
|
-
// IMMEDIATELY acquire lock (atomic in single-threaded JS)
|
|
534
|
-
this.processingLocks.add(tx.id);
|
|
535
|
-
// Security: Check if already in active jobs (LRUCache handles size limit)
|
|
536
|
-
if (this.activeJobs.has(tx.id)) {
|
|
537
|
-
this.processingLocks.delete(tx.id);
|
|
538
|
-
continue;
|
|
539
|
-
}
|
|
540
|
-
// Security: Verify this agent is authorized to accept this transaction
|
|
541
|
-
// Check that tx.provider matches our address (prevents unauthorized state transitions)
|
|
542
|
-
if (tx.provider !== this.address) {
|
|
543
|
-
this.logger.warn('Unauthorized transaction detected', {
|
|
544
|
-
txId: tx.id,
|
|
545
|
-
expectedProvider: this.address,
|
|
546
|
-
actualProvider: tx.provider,
|
|
547
|
-
});
|
|
548
|
-
this.processingLocks.delete(tx.id);
|
|
549
|
-
continue;
|
|
550
|
-
}
|
|
551
|
-
// Find matching service handler
|
|
552
|
-
const serviceHandler = this.findServiceHandler(tx);
|
|
553
|
-
if (!serviceHandler) {
|
|
554
|
-
// No handler registered for this service type
|
|
555
|
-
this.logger.debug('No handler for transaction', { txId: tx.id });
|
|
556
|
-
this.processingLocks.delete(tx.id);
|
|
557
|
-
continue;
|
|
558
|
-
}
|
|
559
|
-
// Check auto-accept behavior
|
|
560
|
-
const shouldAccept = await this.shouldAutoAccept(tx);
|
|
561
|
-
if (!shouldAccept) {
|
|
562
|
-
this.logger.debug('Auto-accept declined', { txId: tx.id });
|
|
563
|
-
this.processingLocks.delete(tx.id);
|
|
564
|
-
continue;
|
|
565
|
-
}
|
|
566
|
-
// Create Job object from transaction
|
|
567
|
-
const job = this.createJobFromTransaction(tx);
|
|
568
|
-
// Security: Add to active jobs (LRUCache prevents unbounded growth)
|
|
569
|
-
this.activeJobs.set(job.id, job);
|
|
570
|
-
// Link escrow immediately to transition out of INITIATED state
|
|
571
|
-
// This prevents polling from picking up this job again
|
|
572
|
-
try {
|
|
573
|
-
if (this._client && tx.state === 'INITIATED') {
|
|
574
|
-
await this._client.runtime.linkEscrow(tx.id, tx.amount);
|
|
575
|
-
}
|
|
576
|
-
// Successfully processed - mark as processed and release lock
|
|
577
|
-
this.processedJobs.set(job.id, true);
|
|
578
|
-
}
|
|
579
|
-
catch (escrowError) {
|
|
580
|
-
// If linking escrow fails, remove from active jobs and release lock (allow retry)
|
|
581
|
-
this.activeJobs.delete(job.id);
|
|
582
|
-
this.logger.error('Failed to link escrow', { txId: tx.id }, escrowError);
|
|
583
|
-
this.processingLocks.delete(tx.id);
|
|
584
|
-
continue;
|
|
585
|
-
}
|
|
586
|
-
finally {
|
|
587
|
-
// Always release the lock
|
|
588
|
-
this.processingLocks.delete(tx.id);
|
|
589
|
-
}
|
|
590
|
-
this._stats.jobsReceived++;
|
|
591
|
-
this.emit('job:received', job);
|
|
592
|
-
this.logger.info('Job accepted', { jobId: job.id, service: job.service });
|
|
593
|
-
// Process the job asynchronously (don't await here to continue polling)
|
|
594
|
-
this.processJob(job, serviceHandler.handler).catch((error) => {
|
|
595
|
-
this.logger.error('Job processing failed', { jobId: job.id }, error);
|
|
596
|
-
this.emit('error', error);
|
|
597
|
-
});
|
|
598
|
-
}
|
|
599
|
-
catch (error) {
|
|
600
|
-
// Log error but continue processing other jobs
|
|
601
|
-
this.logger.error('Error processing pending job', { txId: tx.id }, error);
|
|
602
|
-
this.emit('error', error);
|
|
603
|
-
}
|
|
605
|
+
await this.handleIncomingTransaction(tx);
|
|
604
606
|
}
|
|
605
607
|
// Update cached balance (non-blocking, don't await)
|
|
606
608
|
this.getBalanceAsync().catch(() => {
|
|
@@ -613,19 +615,137 @@ class Agent extends events_1.EventEmitter {
|
|
|
613
615
|
this.emit('error', error);
|
|
614
616
|
}
|
|
615
617
|
}
|
|
618
|
+
/**
|
|
619
|
+
* Shared per-transaction acceptance pipeline. Reached from two sources:
|
|
620
|
+
* - `pollForJobs` — bounded sweep over INITIATED transactions
|
|
621
|
+
* - `subscribeIfBlockchain` — live `TransactionCreated` events
|
|
622
|
+
*
|
|
623
|
+
* Atomic in single-threaded JS via `processingLocks` (Set). The lock
|
|
624
|
+
* release is in a `finally` so any error/throw — handler dispatch
|
|
625
|
+
* failure, linkEscrow revert, malformed payload — does not permanently
|
|
626
|
+
* occupy the slot. Poison TXs become re-tryable on the next sweep.
|
|
627
|
+
*
|
|
628
|
+
* Errors are emitted to consumers but never propagated; the caller loop
|
|
629
|
+
* must not be killed by a single bad TX. PRD §5.3.
|
|
630
|
+
*/
|
|
631
|
+
async handleIncomingTransaction(tx) {
|
|
632
|
+
// PRD §5.3.1: lifecycle status guard. Async polls and queued subscription
|
|
633
|
+
// callbacks can race with pause() / stop(). Drop the TX rather than
|
|
634
|
+
// accepting a new job into a paused or terminating agent.
|
|
635
|
+
//
|
|
636
|
+
// 'starting' is allowed: the subscription is wired inside start() before
|
|
637
|
+
// _status flips to 'running', and a fast on-chain event could fire in
|
|
638
|
+
// that window — dropping it would lose work.
|
|
639
|
+
if (this._status !== 'running' &&
|
|
640
|
+
this._status !== 'starting') {
|
|
641
|
+
this.logger.debug('Agent not accepting jobs, dropping incoming tx', {
|
|
642
|
+
txId: tx.id,
|
|
643
|
+
status: this._status,
|
|
644
|
+
});
|
|
645
|
+
return;
|
|
646
|
+
}
|
|
647
|
+
// Security: check dedup before acquiring the lock so a TX that finished
|
|
648
|
+
// on a prior pass returns immediately without disturbing state.
|
|
649
|
+
if (this.processingLocks.has(tx.id) || this.processedJobs.has(tx.id))
|
|
650
|
+
return;
|
|
651
|
+
if (this.activeJobs.has(tx.id))
|
|
652
|
+
return;
|
|
653
|
+
// Acquire lock (atomic in single-threaded JS). Released in finally below.
|
|
654
|
+
this.processingLocks.add(tx.id);
|
|
655
|
+
try {
|
|
656
|
+
// Authorization: TX provider must match this agent. Case-insensitive
|
|
657
|
+
// (PRD §5.3 carry-forward from §5.1 review): EventMonitor + runtime
|
|
658
|
+
// already normalize, but checksummed and lowercase forms can both
|
|
659
|
+
// reach here legitimately.
|
|
660
|
+
if (tx.provider.toLowerCase() !== this.address.toLowerCase()) {
|
|
661
|
+
this.logger.warn('Unauthorized transaction detected', {
|
|
662
|
+
txId: tx.id,
|
|
663
|
+
expectedProvider: this.address,
|
|
664
|
+
actualProvider: tx.provider,
|
|
665
|
+
});
|
|
666
|
+
return;
|
|
667
|
+
}
|
|
668
|
+
// Routing (PRD §5.4): hash-first, string fallback.
|
|
669
|
+
const serviceHandler = this.findServiceHandler(tx);
|
|
670
|
+
if (!serviceHandler) {
|
|
671
|
+
this.logger.debug('No handler for transaction', { txId: tx.id });
|
|
672
|
+
return;
|
|
673
|
+
}
|
|
674
|
+
// Auto-accept evaluation. PRD §5.4.1: thread the matched handler so
|
|
675
|
+
// every Job built inside filter/pricing/autoAccept paths carries the
|
|
676
|
+
// correct service name.
|
|
677
|
+
const shouldAccept = await this.shouldAutoAccept(tx, serviceHandler);
|
|
678
|
+
if (!shouldAccept) {
|
|
679
|
+
this.logger.debug('Auto-accept declined', { txId: tx.id });
|
|
680
|
+
return;
|
|
681
|
+
}
|
|
682
|
+
// Build Job using the matched handler so hash-only TXs carry the
|
|
683
|
+
// registered service name. PRD §5.4.1.
|
|
684
|
+
const job = this.createJobFromTransaction(tx, serviceHandler);
|
|
685
|
+
this.activeJobs.set(job.id, job);
|
|
686
|
+
// Link escrow immediately to transition out of INITIATED state.
|
|
687
|
+
// This prevents the next poll / event from picking up this job again.
|
|
688
|
+
try {
|
|
689
|
+
if (this._client && tx.state === 'INITIATED') {
|
|
690
|
+
await this._client.runtime.linkEscrow(tx.id, tx.amount);
|
|
691
|
+
}
|
|
692
|
+
this.processedJobs.set(job.id, true);
|
|
693
|
+
}
|
|
694
|
+
catch (escrowError) {
|
|
695
|
+
this.activeJobs.delete(job.id);
|
|
696
|
+
this.logger.error('Failed to link escrow', { txId: tx.id }, escrowError);
|
|
697
|
+
return;
|
|
698
|
+
}
|
|
699
|
+
this._stats.jobsReceived++;
|
|
700
|
+
this.emit('job:received', job);
|
|
701
|
+
this.logger.info('Job accepted', { jobId: job.id, service: job.service });
|
|
702
|
+
// Process the job asynchronously (don't await — handler runs out-of-band).
|
|
703
|
+
this.processJob(job, serviceHandler.handler).catch((error) => {
|
|
704
|
+
this.logger.error('Job processing failed', { jobId: job.id }, error);
|
|
705
|
+
this.emit('error', error);
|
|
706
|
+
});
|
|
707
|
+
}
|
|
708
|
+
catch (error) {
|
|
709
|
+
this.logger.error('Error processing pending job', { txId: tx.id }, error);
|
|
710
|
+
this.emit('error', error);
|
|
711
|
+
}
|
|
712
|
+
finally {
|
|
713
|
+
this.processingLocks.delete(tx.id);
|
|
714
|
+
}
|
|
715
|
+
}
|
|
616
716
|
/**
|
|
617
717
|
* Find service handler for a transaction
|
|
618
718
|
*
|
|
619
719
|
*Security: Use exact field matching instead of substring search
|
|
620
720
|
* to prevent service routing spoofing attacks.
|
|
621
721
|
*
|
|
622
|
-
*
|
|
623
|
-
*
|
|
624
|
-
*
|
|
625
|
-
*
|
|
626
|
-
*
|
|
722
|
+
* Dispatch order:
|
|
723
|
+
* PRIMARY (PRD §5.4 — on-chain Layer B):
|
|
724
|
+
* Match by `tx.serviceHash` against the `handlersByHash` map.
|
|
725
|
+
* Skips ZeroHash (Level 0 `pay` semantics — no handler routing).
|
|
726
|
+
* FALLBACK (preserves MockRuntime test fixtures + legacy clients):
|
|
727
|
+
* 5-step `serviceDescription` dispatch — JSON / legacy /
|
|
728
|
+
* hash-only / string exact match.
|
|
627
729
|
*/
|
|
628
730
|
findServiceHandler(tx) {
|
|
731
|
+
// PRIMARY: on-chain hash routing (PRD §5.4).
|
|
732
|
+
const hash = typeof tx?.serviceHash === 'string' ? tx.serviceHash.toLowerCase() : undefined;
|
|
733
|
+
if (hash && hash !== ethers_1.ethers.ZeroHash.toLowerCase()) {
|
|
734
|
+
const byHash = this.handlersByHash.get(hash);
|
|
735
|
+
if (byHash)
|
|
736
|
+
return byHash;
|
|
737
|
+
}
|
|
738
|
+
// FALLBACK: existing 5-step string dispatch.
|
|
739
|
+
return this.findServiceHandlerByString(tx);
|
|
740
|
+
}
|
|
741
|
+
/**
|
|
742
|
+
* Legacy string-based service dispatch — kept as a fallback for
|
|
743
|
+
* MockRuntime-style transactions where `serviceDescription` still carries
|
|
744
|
+
* the JSON / legacy / plain-name shape. PRD §5.4 routes by hash first;
|
|
745
|
+
* this method is only reached when the hash branch misses or the TX has
|
|
746
|
+
* `serviceHash === ZeroHash`.
|
|
747
|
+
*/
|
|
748
|
+
findServiceHandlerByString(tx) {
|
|
629
749
|
const serviceDesc = tx.serviceDescription;
|
|
630
750
|
if (!serviceDesc) {
|
|
631
751
|
return undefined;
|
|
@@ -683,9 +803,11 @@ class Agent extends events_1.EventEmitter {
|
|
|
683
803
|
* - Evaluates pricing strategy if configured
|
|
684
804
|
* - Only accepts jobs that meet pricing requirements
|
|
685
805
|
*/
|
|
686
|
-
async shouldAutoAccept(tx) {
|
|
687
|
-
//
|
|
688
|
-
|
|
806
|
+
async shouldAutoAccept(tx, matched) {
|
|
807
|
+
// PRD §5.4.1: prefer the matched handler supplied by the caller so the
|
|
808
|
+
// hash-routed `config.name` flows into every internal Job object built
|
|
809
|
+
// below. Re-derive only when caller didn't pass it.
|
|
810
|
+
const serviceHandler = matched ?? this.findServiceHandler(tx);
|
|
689
811
|
// Check service-level filters first (budget constraints)
|
|
690
812
|
if (serviceHandler?.config.filter) {
|
|
691
813
|
const filter = serviceHandler.config.filter;
|
|
@@ -712,7 +834,7 @@ class Agent extends events_1.EventEmitter {
|
|
|
712
834
|
}
|
|
713
835
|
// Check custom filter function
|
|
714
836
|
if (filter.custom && typeof filter.custom === 'function') {
|
|
715
|
-
const job = this.createJobFromTransaction(tx);
|
|
837
|
+
const job = this.createJobFromTransaction(tx, serviceHandler);
|
|
716
838
|
const customResult = await filter.custom(job);
|
|
717
839
|
if (!customResult) {
|
|
718
840
|
this.logger.debug('Job rejected: custom filter declined', { txId: tx.id });
|
|
@@ -722,7 +844,7 @@ class Agent extends events_1.EventEmitter {
|
|
|
722
844
|
}
|
|
723
845
|
// If filter is a function (legacy support)
|
|
724
846
|
else if (typeof filter === 'function') {
|
|
725
|
-
const job = this.createJobFromTransaction(tx);
|
|
847
|
+
const job = this.createJobFromTransaction(tx, serviceHandler);
|
|
726
848
|
const filterResult = filter(job);
|
|
727
849
|
if (!filterResult) {
|
|
728
850
|
this.logger.debug('Job rejected: filter function declined', { txId: tx.id });
|
|
@@ -733,7 +855,7 @@ class Agent extends events_1.EventEmitter {
|
|
|
733
855
|
// MVP: Check pricing strategy if configured
|
|
734
856
|
if (serviceHandler?.config.pricing) {
|
|
735
857
|
const { calculatePrice } = await Promise.resolve().then(() => __importStar(require('./pricing/PriceCalculator')));
|
|
736
|
-
const job = this.createJobFromTransaction(tx);
|
|
858
|
+
const job = this.createJobFromTransaction(tx, serviceHandler);
|
|
737
859
|
try {
|
|
738
860
|
const calculation = calculatePrice(serviceHandler.config.pricing, job);
|
|
739
861
|
this.logger.debug('Pricing calculation', {
|
|
@@ -838,18 +960,28 @@ class Agent extends events_1.EventEmitter {
|
|
|
838
960
|
}
|
|
839
961
|
// It's a function - evaluate it
|
|
840
962
|
if (typeof autoAccept === 'function') {
|
|
841
|
-
const job = this.createJobFromTransaction(tx);
|
|
963
|
+
const job = this.createJobFromTransaction(tx, serviceHandler);
|
|
842
964
|
return await autoAccept(job);
|
|
843
965
|
}
|
|
844
966
|
return false;
|
|
845
967
|
}
|
|
846
968
|
/**
|
|
847
|
-
* Create Job object from MockTransaction
|
|
969
|
+
* Create Job object from MockTransaction.
|
|
970
|
+
*
|
|
971
|
+
* `matched` is the handler entry returned by `findServiceHandler(tx)`.
|
|
972
|
+
* When supplied, `job.service` is taken from `matched.config.name` —
|
|
973
|
+
* this is the only correct source for hash-only TXs (BlockchainRuntime),
|
|
974
|
+
* where `serviceDescription` is empty and `extractServiceName(tx)` would
|
|
975
|
+
* return `'unknown'`. PRD §5.4.1.
|
|
976
|
+
*
|
|
977
|
+
* When `matched` is not supplied (e.g. shouldAutoAccept's autoAccept
|
|
978
|
+
* callback path before this commit, MockRuntime-only test fixtures),
|
|
979
|
+
* fall back to the legacy `extractServiceName` so behavior is unchanged.
|
|
848
980
|
*/
|
|
849
|
-
createJobFromTransaction(tx) {
|
|
981
|
+
createJobFromTransaction(tx, matched) {
|
|
850
982
|
return {
|
|
851
983
|
id: tx.id,
|
|
852
|
-
service: this.extractServiceName(tx),
|
|
984
|
+
service: matched?.config.name ?? this.extractServiceName(tx),
|
|
853
985
|
input: this.extractJobInput(tx),
|
|
854
986
|
budget: this.convertAmountToNumber(tx.amount),
|
|
855
987
|
deadline: new Date(tx.deadline * 1000), // Convert unix timestamp to Date
|