prova-agent-kit 0.1.1 → 0.1.2

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/README.md CHANGED
@@ -50,17 +50,19 @@ const agent = new SolanaAgentKit(new ProvaWallet(realWallet, { attester }), RPC_
50
50
  ```
51
51
 
52
52
  ### No bloquea al agente
53
- Las atestaciones son **fire-and-forget con batching**: un fallo de Prova nunca rompe ni frena una acción del agente. Los items se acumulan y se envían con `batchAttest` (hasta 100/tx) cada `maxSize` items o `flushIntervalMs` ms.
53
+ Las atestaciones son **fire-and-forget con batching por debounce**: un fallo de Prova nunca rompe ni frena una acción del agente. Una ráfaga de tool calls (multi-tool-calling) se agrupa y se envía en **UNA sola tx** (`batchAttest`, hasta 100) apenas la ráfaga se detiene (`flushDelayMs`), o al llegar a `maxSize`.
54
54
 
55
55
  ```typescript
56
56
  attachProva(agent, {
57
57
  attester,
58
58
  rules: (name) => name !== 'fetchPrice', // qué acciones atestar (default: todas)
59
- batch: { maxSize: 25, flushIntervalMs: 10_000 },
59
+ batch: { maxSize: 25, flushDelayMs: 1000 }, // agrupa la ráfaga y envía ~1s tras la última acción
60
60
  onError: (err, ctx) => logger.warn(ctx.action, err),
61
61
  });
62
62
  ```
63
63
 
64
+ > **Importante:** llama a `handle.stop()` cuando el agente termine su turno para garantizar el flush final de lo pendiente.
65
+
64
66
  ## Modelo de claves
65
67
  Prova atesta con **su propio par operador/agente de Devnet**, independiente del wallet de trading del agente. Ventajas: no necesita la private key de trading, funciona con wallets Privy/Turnkey, y el operador Prova es la identidad que avala al agente. Registra el agente una vez con `prova.registerAgent({ operatorKeypair })`.
66
68
 
package/dist/batcher.d.ts CHANGED
@@ -1,10 +1,10 @@
1
1
  import type { AttestationItem, BatchOptions, ProvaAttester } from './types';
2
2
  export interface Batcher {
3
- /** Encola una atestación; dispara flush si se alcanza maxSize. */
3
+ /** Encola una atestación; programa el flush (o lo dispara si se alcanza maxSize). */
4
4
  add(item: AttestationItem): void;
5
5
  /** Envía inmediatamente lo pendiente. */
6
6
  flush(): Promise<void>;
7
- /** Detiene el timer y hace un último flush. */
7
+ /** Cancela el timer y hace un último flush (llamar al cerrar el agente). */
8
8
  stop(): Promise<void>;
9
9
  }
10
10
  export declare function createBatcher(attester: ProvaAttester, options?: BatchOptions, onError?: (error: unknown) => void): Batcher;
@@ -1 +1 @@
1
- {"version":3,"file":"batcher.d.ts","sourceRoot":"","sources":["../src/batcher.ts"],"names":[],"mappings":"AAIA,OAAO,KAAK,EAAE,eAAe,EAAE,YAAY,EAAE,aAAa,EAAE,MAAM,SAAS,CAAC;AAG5E,MAAM,WAAW,OAAO;IACtB,kEAAkE;IAClE,GAAG,CAAC,IAAI,EAAE,eAAe,GAAG,IAAI,CAAC;IACjC,yCAAyC;IACzC,KAAK,IAAI,OAAO,CAAC,IAAI,CAAC,CAAC;IACvB,+CAA+C;IAC/C,IAAI,IAAI,OAAO,CAAC,IAAI,CAAC,CAAC;CACvB;AAMD,wBAAgB,aAAa,CAC3B,QAAQ,EAAE,aAAa,EACvB,OAAO,GAAE,YAAiB,EAC1B,OAAO,GAAE,CAAC,KAAK,EAAE,OAAO,KAAK,IAAqD,GACjF,OAAO,CA2CT"}
1
+ {"version":3,"file":"batcher.d.ts","sourceRoot":"","sources":["../src/batcher.ts"],"names":[],"mappings":"AAMA,OAAO,KAAK,EAAE,eAAe,EAAE,YAAY,EAAE,aAAa,EAAE,MAAM,SAAS,CAAC;AAG5E,MAAM,WAAW,OAAO;IACtB,qFAAqF;IACrF,GAAG,CAAC,IAAI,EAAE,eAAe,GAAG,IAAI,CAAC;IACjC,yCAAyC;IACzC,KAAK,IAAI,OAAO,CAAC,IAAI,CAAC,CAAC;IACvB,4EAA4E;IAC5E,IAAI,IAAI,OAAO,CAAC,IAAI,CAAC,CAAC;CACvB;AAMD,wBAAgB,aAAa,CAC3B,QAAQ,EAAE,aAAa,EACvB,OAAO,GAAE,YAAiB,EAC1B,OAAO,GAAE,CAAC,KAAK,EAAE,OAAO,KAAK,IAAqD,GACjF,OAAO,CAwDT"}
package/dist/batcher.js CHANGED
@@ -1,19 +1,28 @@
1
1
  "use strict";
2
- // Buffer de atestaciones: acumula items y los envía en lote (batchAttest) cada
3
- // N items o cada T ms, lo que ocurra primero. Aísla la latencia/coste de Solana
4
- // del hot-path del agente.
2
+ // Buffer de atestaciones con flush por DEBOUNCE: agrupa una ráfaga de acciones
3
+ // (multi-tool-calling) y las envía en UNA sola tx (batchAttest, hasta 100) apenas
4
+ // la ráfaga se detiene. Esto arregla el bug de "N tool calls seguidos → 0 guardadas":
5
+ // antes el flush solo ocurría a los 25 items o cada 10s, así que una ráfaga corta
6
+ // que terminaba antes quedaba sin enviar.
5
7
  Object.defineProperty(exports, "__esModule", { value: true });
6
8
  exports.createBatcher = createBatcher;
7
9
  const runtime_1 = require("./runtime");
8
10
  const DEFAULT_MAX_SIZE = 25;
9
- const DEFAULT_INTERVAL_MS = 10_000;
11
+ const DEFAULT_FLUSH_DELAY_MS = 1000;
10
12
  const HARD_BATCH_LIMIT = 100;
11
13
  function createBatcher(attester, options = {}, onError = (error) => (0, runtime_1.warn)('[Prova] batch error:', error)) {
12
14
  const maxSize = Math.min(Math.max(options.maxSize ?? DEFAULT_MAX_SIZE, 1), HARD_BATCH_LIMIT);
13
- const intervalMs = options.flushIntervalMs ?? DEFAULT_INTERVAL_MS;
15
+ const flushDelayMs = options.flushDelayMs ?? DEFAULT_FLUSH_DELAY_MS;
14
16
  let buffer = [];
15
17
  let timer;
18
+ function clearTimer() {
19
+ if (timer !== undefined) {
20
+ (0, runtime_1.clearTimeoutSafe)(timer);
21
+ timer = undefined;
22
+ }
23
+ }
16
24
  async function flush() {
25
+ clearTimer();
17
26
  if (buffer.length === 0)
18
27
  return;
19
28
  const batch = buffer;
@@ -30,24 +39,29 @@ function createBatcher(attester, options = {}, onError = (error) => (0, runtime_
30
39
  onError(error);
31
40
  }
32
41
  }
33
- function ensureTimer() {
34
- if (intervalMs > 0 && timer === undefined) {
35
- timer = (0, runtime_1.setIntervalSafe)(() => void flush(), intervalMs);
42
+ // Debounce: cada acción reinicia el timer, de modo que el flush ocurre
43
+ // `flushDelayMs` ms DESPUÉS de la última acción de la ráfaga → todo en 1 tx.
44
+ function scheduleFlush() {
45
+ clearTimer();
46
+ if (flushDelayMs <= 0) {
47
+ void flush();
48
+ return;
36
49
  }
50
+ timer = (0, runtime_1.setTimeoutSafe)(() => void flush(), flushDelayMs);
37
51
  }
38
52
  return {
39
53
  add(item) {
40
54
  buffer.push(item);
41
- ensureTimer();
42
- if (buffer.length >= maxSize)
43
- void flush();
55
+ if (buffer.length >= maxSize) {
56
+ void flush(); // llegó al tope → envía ya
57
+ }
58
+ else {
59
+ scheduleFlush(); // si no, agrupa la ráfaga y envía al detenerse
60
+ }
44
61
  },
45
62
  flush,
46
63
  async stop() {
47
- if (timer !== undefined) {
48
- (0, runtime_1.clearIntervalSafe)(timer);
49
- timer = undefined;
50
- }
64
+ clearTimer();
51
65
  await flush();
52
66
  },
53
67
  };
@@ -1 +1 @@
1
- {"version":3,"file":"batcher.js","sourceRoot":"","sources":["../src/batcher.ts"],"names":[],"mappings":";AAAA,+EAA+E;AAC/E,gFAAgF;AAChF,2BAA2B;;AAkB3B,sCA+CC;AA9DD,uCAAmF;AAWnF,MAAM,gBAAgB,GAAG,EAAE,CAAC;AAC5B,MAAM,mBAAmB,GAAG,MAAM,CAAC;AACnC,MAAM,gBAAgB,GAAG,GAAG,CAAC;AAE7B,SAAgB,aAAa,CAC3B,QAAuB,EACvB,UAAwB,EAAE,EAC1B,UAAoC,CAAC,KAAK,EAAE,EAAE,CAAC,IAAA,cAAI,EAAC,sBAAsB,EAAE,KAAK,CAAC;IAElF,MAAM,OAAO,GAAG,IAAI,CAAC,GAAG,CAAC,IAAI,CAAC,GAAG,CAAC,OAAO,CAAC,OAAO,IAAI,gBAAgB,EAAE,CAAC,CAAC,EAAE,gBAAgB,CAAC,CAAC;IAC7F,MAAM,UAAU,GAAG,OAAO,CAAC,eAAe,IAAI,mBAAmB,CAAC;IAElE,IAAI,MAAM,GAAsB,EAAE,CAAC;IACnC,IAAI,KAA0B,CAAC;IAE/B,KAAK,UAAU,KAAK;QAClB,IAAI,MAAM,CAAC,MAAM,KAAK,CAAC;YAAE,OAAO;QAChC,MAAM,KAAK,GAAG,MAAM,CAAC;QACrB,MAAM,GAAG,EAAE,CAAC;QACZ,IAAI,CAAC;YACH,IAAI,KAAK,CAAC,MAAM,KAAK,CAAC,EAAE,CAAC;gBACvB,MAAM,QAAQ,CAAC,MAAM,CAAC,KAAK,CAAC,CAAC,CAAE,CAAC,CAAC;YACnC,CAAC;iBAAM,CAAC;gBACN,MAAM,QAAQ,CAAC,WAAW,CAAC,KAAK,CAAC,CAAC;YACpC,CAAC;QACH,CAAC;QAAC,OAAO,KAAK,EAAE,CAAC;YACf,OAAO,CAAC,KAAK,CAAC,CAAC;QACjB,CAAC;IACH,CAAC;IAED,SAAS,WAAW;QAClB,IAAI,UAAU,GAAG,CAAC,IAAI,KAAK,KAAK,SAAS,EAAE,CAAC;YAC1C,KAAK,GAAG,IAAA,yBAAe,EAAC,GAAG,EAAE,CAAC,KAAK,KAAK,EAAE,EAAE,UAAU,CAAC,CAAC;QAC1D,CAAC;IACH,CAAC;IAED,OAAO;QACL,GAAG,CAAC,IAAqB;YACvB,MAAM,CAAC,IAAI,CAAC,IAAI,CAAC,CAAC;YAClB,WAAW,EAAE,CAAC;YACd,IAAI,MAAM,CAAC,MAAM,IAAI,OAAO;gBAAE,KAAK,KAAK,EAAE,CAAC;QAC7C,CAAC;QACD,KAAK;QACL,KAAK,CAAC,IAAI;YACR,IAAI,KAAK,KAAK,SAAS,EAAE,CAAC;gBACxB,IAAA,2BAAiB,EAAC,KAAK,CAAC,CAAC;gBACzB,KAAK,GAAG,SAAS,CAAC;YACpB,CAAC;YACD,MAAM,KAAK,EAAE,CAAC;QAChB,CAAC;KACF,CAAC;AACJ,CAAC"}
1
+ {"version":3,"file":"batcher.js","sourceRoot":"","sources":["../src/batcher.ts"],"names":[],"mappings":";AAAA,+EAA+E;AAC/E,kFAAkF;AAClF,sFAAsF;AACtF,kFAAkF;AAClF,0CAA0C;;AAkB1C,sCA4DC;AA3ED,uCAAiF;AAWjF,MAAM,gBAAgB,GAAG,EAAE,CAAC;AAC5B,MAAM,sBAAsB,GAAG,IAAI,CAAC;AACpC,MAAM,gBAAgB,GAAG,GAAG,CAAC;AAE7B,SAAgB,aAAa,CAC3B,QAAuB,EACvB,UAAwB,EAAE,EAC1B,UAAoC,CAAC,KAAK,EAAE,EAAE,CAAC,IAAA,cAAI,EAAC,sBAAsB,EAAE,KAAK,CAAC;IAElF,MAAM,OAAO,GAAG,IAAI,CAAC,GAAG,CAAC,IAAI,CAAC,GAAG,CAAC,OAAO,CAAC,OAAO,IAAI,gBAAgB,EAAE,CAAC,CAAC,EAAE,gBAAgB,CAAC,CAAC;IAC7F,MAAM,YAAY,GAAG,OAAO,CAAC,YAAY,IAAI,sBAAsB,CAAC;IAEpE,IAAI,MAAM,GAAsB,EAAE,CAAC;IACnC,IAAI,KAA0B,CAAC;IAE/B,SAAS,UAAU;QACjB,IAAI,KAAK,KAAK,SAAS,EAAE,CAAC;YACxB,IAAA,0BAAgB,EAAC,KAAK,CAAC,CAAC;YACxB,KAAK,GAAG,SAAS,CAAC;QACpB,CAAC;IACH,CAAC;IAED,KAAK,UAAU,KAAK;QAClB,UAAU,EAAE,CAAC;QACb,IAAI,MAAM,CAAC,MAAM,KAAK,CAAC;YAAE,OAAO;QAChC,MAAM,KAAK,GAAG,MAAM,CAAC;QACrB,MAAM,GAAG,EAAE,CAAC;QACZ,IAAI,CAAC;YACH,IAAI,KAAK,CAAC,MAAM,KAAK,CAAC,EAAE,CAAC;gBACvB,MAAM,QAAQ,CAAC,MAAM,CAAC,KAAK,CAAC,CAAC,CAAE,CAAC,CAAC;YACnC,CAAC;iBAAM,CAAC;gBACN,MAAM,QAAQ,CAAC,WAAW,CAAC,KAAK,CAAC,CAAC;YACpC,CAAC;QACH,CAAC;QAAC,OAAO,KAAK,EAAE,CAAC;YACf,OAAO,CAAC,KAAK,CAAC,CAAC;QACjB,CAAC;IACH,CAAC;IAED,uEAAuE;IACvE,6EAA6E;IAC7E,SAAS,aAAa;QACpB,UAAU,EAAE,CAAC;QACb,IAAI,YAAY,IAAI,CAAC,EAAE,CAAC;YACtB,KAAK,KAAK,EAAE,CAAC;YACb,OAAO;QACT,CAAC;QACD,KAAK,GAAG,IAAA,wBAAc,EAAC,GAAG,EAAE,CAAC,KAAK,KAAK,EAAE,EAAE,YAAY,CAAC,CAAC;IAC3D,CAAC;IAED,OAAO;QACL,GAAG,CAAC,IAAqB;YACvB,MAAM,CAAC,IAAI,CAAC,IAAI,CAAC,CAAC;YAClB,IAAI,MAAM,CAAC,MAAM,IAAI,OAAO,EAAE,CAAC;gBAC7B,KAAK,KAAK,EAAE,CAAC,CAAC,2BAA2B;YAC3C,CAAC;iBAAM,CAAC;gBACN,aAAa,EAAE,CAAC,CAAC,+CAA+C;YAClE,CAAC;QACH,CAAC;QACD,KAAK;QACL,KAAK,CAAC,IAAI;YACR,UAAU,EAAE,CAAC;YACb,MAAM,KAAK,EAAE,CAAC;QAChB,CAAC;KACF,CAAC;AACJ,CAAC"}
package/dist/runtime.d.ts CHANGED
@@ -2,8 +2,8 @@
2
2
  export type TimerId = number | {
3
3
  readonly __timer?: unique symbol;
4
4
  };
5
- /** setInterval que no bloquea la salida del proceso (unref en Node si existe). */
6
- export declare function setIntervalSafe(handler: () => void, ms: number): TimerId;
7
- export declare function clearIntervalSafe(id: TimerId): void;
5
+ /** setTimeout que no bloquea la salida del proceso (unref en Node si existe). */
6
+ export declare function setTimeoutSafe(handler: () => void, ms: number): TimerId;
7
+ export declare function clearTimeoutSafe(id: TimerId): void;
8
8
  export declare function warn(...args: unknown[]): void;
9
9
  //# sourceMappingURL=runtime.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"runtime.d.ts","sourceRoot":"","sources":["../src/runtime.ts"],"names":[],"mappings":"AAIA,6EAA6E;AAC7E,MAAM,MAAM,OAAO,GAAG,MAAM,GAAG;IAAE,QAAQ,CAAC,OAAO,CAAC,EAAE,OAAO,MAAM,CAAA;CAAE,CAAC;AAUpE,kFAAkF;AAClF,wBAAgB,eAAe,CAAC,OAAO,EAAE,MAAM,IAAI,EAAE,EAAE,EAAE,MAAM,GAAG,OAAO,CAKxE;AAED,wBAAgB,iBAAiB,CAAC,EAAE,EAAE,OAAO,GAAG,IAAI,CAEnD;AAED,wBAAgB,IAAI,CAAC,GAAG,IAAI,EAAE,OAAO,EAAE,GAAG,IAAI,CAE7C"}
1
+ {"version":3,"file":"runtime.d.ts","sourceRoot":"","sources":["../src/runtime.ts"],"names":[],"mappings":"AAIA,6EAA6E;AAC7E,MAAM,MAAM,OAAO,GAAG,MAAM,GAAG;IAAE,QAAQ,CAAC,OAAO,CAAC,EAAE,OAAO,MAAM,CAAA;CAAE,CAAC;AAUpE,iFAAiF;AACjF,wBAAgB,cAAc,CAAC,OAAO,EAAE,MAAM,IAAI,EAAE,EAAE,EAAE,MAAM,GAAG,OAAO,CAKvE;AAED,wBAAgB,gBAAgB,CAAC,EAAE,EAAE,OAAO,GAAG,IAAI,CAElD;AAED,wBAAgB,IAAI,CAAC,GAAG,IAAI,EAAE,OAAO,EAAE,GAAG,IAAI,CAE7C"}
package/dist/runtime.js CHANGED
@@ -3,20 +3,20 @@
3
3
  // @types/node ni de los tipos del DOM. En ejecución real los provee Node o el
4
4
  // navegador; aquí los declaramos mínimamente para mantener el adapter ligero.
5
5
  Object.defineProperty(exports, "__esModule", { value: true });
6
- exports.setIntervalSafe = setIntervalSafe;
7
- exports.clearIntervalSafe = clearIntervalSafe;
6
+ exports.setTimeoutSafe = setTimeoutSafe;
7
+ exports.clearTimeoutSafe = clearTimeoutSafe;
8
8
  exports.warn = warn;
9
9
  const runtime = globalThis;
10
- /** setInterval que no bloquea la salida del proceso (unref en Node si existe). */
11
- function setIntervalSafe(handler, ms) {
12
- const id = runtime.setInterval(handler, ms);
10
+ /** setTimeout que no bloquea la salida del proceso (unref en Node si existe). */
11
+ function setTimeoutSafe(handler, ms) {
12
+ const id = runtime.setTimeout(handler, ms);
13
13
  const maybe = id;
14
14
  if (typeof maybe.unref === 'function')
15
15
  maybe.unref();
16
16
  return id;
17
17
  }
18
- function clearIntervalSafe(id) {
19
- runtime.clearInterval(id);
18
+ function clearTimeoutSafe(id) {
19
+ runtime.clearTimeout(id);
20
20
  }
21
21
  function warn(...args) {
22
22
  runtime.console.warn(...args);
@@ -1 +1 @@
1
- {"version":3,"file":"runtime.js","sourceRoot":"","sources":["../src/runtime.ts"],"names":[],"mappings":";AAAA,wEAAwE;AACxE,8EAA8E;AAC9E,8EAA8E;;AAc9E,0CAKC;AAED,8CAEC;AAED,oBAEC;AAhBD,MAAM,OAAO,GAAG,UAAuC,CAAC;AAExD,kFAAkF;AAClF,SAAgB,eAAe,CAAC,OAAmB,EAAE,EAAU;IAC7D,MAAM,EAAE,GAAG,OAAO,CAAC,WAAW,CAAC,OAAO,EAAE,EAAE,CAAC,CAAC;IAC5C,MAAM,KAAK,GAAG,EAAuC,CAAC;IACtD,IAAI,OAAO,KAAK,CAAC,KAAK,KAAK,UAAU;QAAE,KAAK,CAAC,KAAK,EAAE,CAAC;IACrD,OAAO,EAAE,CAAC;AACZ,CAAC;AAED,SAAgB,iBAAiB,CAAC,EAAW;IAC3C,OAAO,CAAC,aAAa,CAAC,EAAE,CAAC,CAAC;AAC5B,CAAC;AAED,SAAgB,IAAI,CAAC,GAAG,IAAe;IACrC,OAAO,CAAC,OAAO,CAAC,IAAI,CAAC,GAAG,IAAI,CAAC,CAAC;AAChC,CAAC"}
1
+ {"version":3,"file":"runtime.js","sourceRoot":"","sources":["../src/runtime.ts"],"names":[],"mappings":";AAAA,wEAAwE;AACxE,8EAA8E;AAC9E,8EAA8E;;AAc9E,wCAKC;AAED,4CAEC;AAED,oBAEC;AAhBD,MAAM,OAAO,GAAG,UAAuC,CAAC;AAExD,iFAAiF;AACjF,SAAgB,cAAc,CAAC,OAAmB,EAAE,EAAU;IAC5D,MAAM,EAAE,GAAG,OAAO,CAAC,UAAU,CAAC,OAAO,EAAE,EAAE,CAAC,CAAC;IAC3C,MAAM,KAAK,GAAG,EAAuC,CAAC;IACtD,IAAI,OAAO,KAAK,CAAC,KAAK,KAAK,UAAU;QAAE,KAAK,CAAC,KAAK,EAAE,CAAC;IACrD,OAAO,EAAE,CAAC;AACZ,CAAC;AAED,SAAgB,gBAAgB,CAAC,EAAW;IAC1C,OAAO,CAAC,YAAY,CAAC,EAAE,CAAC,CAAC;AAC3B,CAAC;AAED,SAAgB,IAAI,CAAC,GAAG,IAAe;IACrC,OAAO,CAAC,OAAO,CAAC,IAAI,CAAC,GAAG,IAAI,CAAC,CAAC;AAChC,CAAC"}
package/dist/types.d.ts CHANGED
@@ -45,10 +45,14 @@ export interface ProvaAttester {
45
45
  }
46
46
  /** Configuración del batching de atestaciones. */
47
47
  export interface BatchOptions {
48
- /** Nº máximo de atestaciones por transacción (1–100). Default 25. */
48
+ /** Nº máximo de atestaciones por transacción (1–100). Al alcanzarlo, flush inmediato. Default 25. */
49
49
  maxSize?: number;
50
- /** Flush automático cada N ms. Default 10000. 0 = sin flush por tiempo. */
51
- flushIntervalMs?: number;
50
+ /**
51
+ * Debounce: hace flush este nº de ms tras la ÚLTIMA acción. Así una ráfaga de
52
+ * tool calls (multi-tool-calling) se agrupa en UNA sola tx on-chain apenas
53
+ * termina la ráfaga. Default 1000. 0 = flush inmediato en cada acción (sin batch).
54
+ */
55
+ flushDelayMs?: number;
52
56
  }
53
57
  /** Opciones de `attachProva`. */
54
58
  export interface AttachProvaOptions {
@@ -1 +1 @@
1
- {"version":3,"file":"types.d.ts","sourceRoot":"","sources":["../src/types.ts"],"names":[],"mappings":"AAKA,yDAAyD;AACzD,MAAM,MAAM,YAAY,GAAG,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC;AAEnD,wEAAwE;AACxE,MAAM,WAAW,UAAU;IACzB,IAAI,EAAE,MAAM,CAAC;IACb,OAAO,EAAE,CAAC,KAAK,EAAE,OAAO,EAAE,KAAK,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,KAAK,OAAO,CAAC,YAAY,CAAC,CAAC;IACnF,CAAC,GAAG,EAAE,MAAM,GAAG,OAAO,CAAC;CACxB;AAED,wEAAwE;AACxE,MAAM,WAAW,SAAS;IACxB,OAAO,EAAE,UAAU,EAAE,CAAC;IACtB,CAAC,GAAG,EAAE,MAAM,GAAG,OAAO,CAAC;CACxB;AAED,+DAA+D;AAC/D,MAAM,WAAW,UAAU;IACzB,QAAQ,CAAC,SAAS,EAAE;QAAE,QAAQ,IAAI,MAAM,CAAA;KAAE,CAAC;IAC3C,eAAe,CAAC,CAAC,EAAE,WAAW,EAAE,CAAC,GAAG,OAAO,CAAC,CAAC,CAAC,CAAC;IAC/C,mBAAmB,CAAC,CAAC,EAAE,YAAY,EAAE,CAAC,EAAE,GAAG,OAAO,CAAC,CAAC,EAAE,CAAC,CAAC;IACxD,sBAAsB,CAAC,CAAC,EAAE,WAAW,EAAE,CAAC,EAAE,OAAO,CAAC,EAAE,OAAO,GAAG,OAAO,CAAC;QAAE,SAAS,EAAE,MAAM,CAAA;KAAE,CAAC,CAAC;IAC7F,WAAW,CAAC,OAAO,EAAE,UAAU,GAAG,OAAO,CAAC,UAAU,CAAC,CAAC;CACvD;AAED,6EAA6E;AAC7E,MAAM,MAAM,eAAe,GACvB,aAAa,GACb,UAAU,GACV,iBAAiB,GACjB,UAAU,GACV,gBAAgB,GAChB,aAAa,GACb,QAAQ,CAAC;AAEb,yEAAyE;AACzE,MAAM,WAAW,eAAe;IAC9B,UAAU,EAAE,UAAU,CAAC;IACvB,UAAU,EAAE,eAAe,CAAC;CAC7B;AAED;;;GAGG;AACH,MAAM,WAAW,aAAa;IAC5B,UAAU,CAAC,OAAO,EAAE,MAAM,GAAG,OAAO,CAAC,UAAU,CAAC,CAAC;IACjD,MAAM,CAAC,IAAI,EAAE,eAAe,GAAG,OAAO,CAAC;QAAE,WAAW,EAAE,MAAM,CAAA;KAAE,CAAC,CAAC;IAChE,WAAW,CAAC,KAAK,EAAE,eAAe,EAAE,GAAG,OAAO,CAAC;QAAE,WAAW,EAAE,MAAM,CAAA;KAAE,CAAC,CAAC;CACzE;AAED,kDAAkD;AAClD,MAAM,WAAW,YAAY;IAC3B,qEAAqE;IACrE,OAAO,CAAC,EAAE,MAAM,CAAC;IACjB,2EAA2E;IAC3E,eAAe,CAAC,EAAE,MAAM,CAAC;CAC1B;AAED,iCAAiC;AACjC,MAAM,WAAW,kBAAkB;IACjC,0DAA0D;IAC1D,QAAQ,EAAE,aAAa,CAAC;IACxB,+DAA+D;IAC/D,KAAK,CAAC,EAAE,CAAC,UAAU,EAAE,MAAM,KAAK,OAAO,CAAC;IACxC,iCAAiC;IACjC,KAAK,CAAC,EAAE,YAAY,CAAC;IACrB,4EAA4E;IAC5E,OAAO,CAAC,EAAE,CAAC,KAAK,EAAE,OAAO,EAAE,OAAO,EAAE;QAAE,MAAM,EAAE,MAAM,CAAA;KAAE,KAAK,IAAI,CAAC;CACjE"}
1
+ {"version":3,"file":"types.d.ts","sourceRoot":"","sources":["../src/types.ts"],"names":[],"mappings":"AAKA,yDAAyD;AACzD,MAAM,MAAM,YAAY,GAAG,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC;AAEnD,wEAAwE;AACxE,MAAM,WAAW,UAAU;IACzB,IAAI,EAAE,MAAM,CAAC;IACb,OAAO,EAAE,CAAC,KAAK,EAAE,OAAO,EAAE,KAAK,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,KAAK,OAAO,CAAC,YAAY,CAAC,CAAC;IACnF,CAAC,GAAG,EAAE,MAAM,GAAG,OAAO,CAAC;CACxB;AAED,wEAAwE;AACxE,MAAM,WAAW,SAAS;IACxB,OAAO,EAAE,UAAU,EAAE,CAAC;IACtB,CAAC,GAAG,EAAE,MAAM,GAAG,OAAO,CAAC;CACxB;AAED,+DAA+D;AAC/D,MAAM,WAAW,UAAU;IACzB,QAAQ,CAAC,SAAS,EAAE;QAAE,QAAQ,IAAI,MAAM,CAAA;KAAE,CAAC;IAC3C,eAAe,CAAC,CAAC,EAAE,WAAW,EAAE,CAAC,GAAG,OAAO,CAAC,CAAC,CAAC,CAAC;IAC/C,mBAAmB,CAAC,CAAC,EAAE,YAAY,EAAE,CAAC,EAAE,GAAG,OAAO,CAAC,CAAC,EAAE,CAAC,CAAC;IACxD,sBAAsB,CAAC,CAAC,EAAE,WAAW,EAAE,CAAC,EAAE,OAAO,CAAC,EAAE,OAAO,GAAG,OAAO,CAAC;QAAE,SAAS,EAAE,MAAM,CAAA;KAAE,CAAC,CAAC;IAC7F,WAAW,CAAC,OAAO,EAAE,UAAU,GAAG,OAAO,CAAC,UAAU,CAAC,CAAC;CACvD;AAED,6EAA6E;AAC7E,MAAM,MAAM,eAAe,GACvB,aAAa,GACb,UAAU,GACV,iBAAiB,GACjB,UAAU,GACV,gBAAgB,GAChB,aAAa,GACb,QAAQ,CAAC;AAEb,yEAAyE;AACzE,MAAM,WAAW,eAAe;IAC9B,UAAU,EAAE,UAAU,CAAC;IACvB,UAAU,EAAE,eAAe,CAAC;CAC7B;AAED;;;GAGG;AACH,MAAM,WAAW,aAAa;IAC5B,UAAU,CAAC,OAAO,EAAE,MAAM,GAAG,OAAO,CAAC,UAAU,CAAC,CAAC;IACjD,MAAM,CAAC,IAAI,EAAE,eAAe,GAAG,OAAO,CAAC;QAAE,WAAW,EAAE,MAAM,CAAA;KAAE,CAAC,CAAC;IAChE,WAAW,CAAC,KAAK,EAAE,eAAe,EAAE,GAAG,OAAO,CAAC;QAAE,WAAW,EAAE,MAAM,CAAA;KAAE,CAAC,CAAC;CACzE;AAED,kDAAkD;AAClD,MAAM,WAAW,YAAY;IAC3B,qGAAqG;IACrG,OAAO,CAAC,EAAE,MAAM,CAAC;IACjB;;;;OAIG;IACH,YAAY,CAAC,EAAE,MAAM,CAAC;CACvB;AAED,iCAAiC;AACjC,MAAM,WAAW,kBAAkB;IACjC,0DAA0D;IAC1D,QAAQ,EAAE,aAAa,CAAC;IACxB,+DAA+D;IAC/D,KAAK,CAAC,EAAE,CAAC,UAAU,EAAE,MAAM,KAAK,OAAO,CAAC;IACxC,iCAAiC;IACjC,KAAK,CAAC,EAAE,YAAY,CAAC;IACrB,4EAA4E;IAC5E,OAAO,CAAC,EAAE,CAAC,KAAK,EAAE,OAAO,EAAE,OAAO,EAAE;QAAE,MAAM,EAAE,MAAM,CAAA;KAAE,KAAK,IAAI,CAAC;CACjE"}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "prova-agent-kit",
3
- "version": "0.1.1",
3
+ "version": "0.1.2",
4
4
  "license": "Apache-2.0",
5
5
  "author": "Prova Labs",
6
6
  "description": "Prova adapter for Solana Agent Kit — emit verifiable on-chain receipts for every action a SAK agent executes",
@@ -29,7 +29,7 @@ describe('attachProva', () => {
29
29
 
30
30
  const handle = attachProva(agent, {
31
31
  attester,
32
- batch: { maxSize: 1, flushIntervalMs: 0 },
32
+ batch: { maxSize: 1, flushDelayMs: 0 },
33
33
  });
34
34
 
35
35
  const result = await agent.actions[0]!.handler(agent, { amount: 10 });
@@ -72,7 +72,7 @@ describe('attachProva', () => {
72
72
  const handle = attachProva(agent, {
73
73
  attester,
74
74
  rules: (name) => name === 'trade',
75
- batch: { maxSize: 1, flushIntervalMs: 0 },
75
+ batch: { maxSize: 1, flushDelayMs: 0 },
76
76
  });
77
77
 
78
78
  await agent.actions[0]!.handler(agent, {});
@@ -0,0 +1,70 @@
1
+ import { describe, expect, it } from 'vitest';
2
+ import { createBatcher } from './batcher';
3
+ import type { AttestationItem, ProvaAttester } from './types';
4
+
5
+ function recordingAttester() {
6
+ const attests: AttestationItem[] = [];
7
+ const batches: AttestationItem[][] = [];
8
+ const attester: ProvaAttester = {
9
+ hashAction: async () => new Uint8Array(32),
10
+ attest: async (item) => {
11
+ attests.push(item);
12
+ return { txSignature: 'tx' };
13
+ },
14
+ batchAttest: async (items) => {
15
+ batches.push(items);
16
+ return { txSignature: 'tx' };
17
+ },
18
+ };
19
+ return { attester, attests, batches };
20
+ }
21
+
22
+ const item = (): AttestationItem => ({ actionHash: new Uint8Array(32), actionType: 'ToolCall' });
23
+ const wait = (ms: number) => new Promise((r) => setTimeout(r, ms));
24
+
25
+ describe('createBatcher — multi-tool-calling (bug del mentor)', () => {
26
+ it('agrupa una RÁFAGA de 11 acciones en UNA sola batchAttest', async () => {
27
+ const { attester, attests, batches } = recordingAttester();
28
+ const batcher = createBatcher(attester, { maxSize: 25, flushDelayMs: 20 });
29
+
30
+ for (let i = 0; i < 11; i++) batcher.add(item());
31
+
32
+ await wait(50); // pasa el debounce
33
+ expect(batches).toHaveLength(1); // UNA sola tx
34
+ expect(batches[0]).toHaveLength(11); // con las 11 dentro
35
+ expect(attests).toHaveLength(0); // ninguna suelta
36
+ });
37
+
38
+ it('no pierde nada si el agente cierra antes del debounce (stop hace flush)', async () => {
39
+ const { attester, batches } = recordingAttester();
40
+ const batcher = createBatcher(attester, { maxSize: 25, flushDelayMs: 5000 });
41
+
42
+ for (let i = 0; i < 11; i++) batcher.add(item());
43
+ await batcher.stop(); // cierre inmediato
44
+
45
+ expect(batches).toHaveLength(1);
46
+ expect(batches[0]).toHaveLength(11);
47
+ });
48
+
49
+ it('hace flush inmediato al alcanzar maxSize', async () => {
50
+ const { attester, batches } = recordingAttester();
51
+ const batcher = createBatcher(attester, { maxSize: 5, flushDelayMs: 5000 });
52
+
53
+ for (let i = 0; i < 5; i++) batcher.add(item());
54
+ await wait(0);
55
+
56
+ expect(batches).toHaveLength(1);
57
+ expect(batches[0]).toHaveLength(5);
58
+ });
59
+
60
+ it('una sola acción usa attest (no batchAttest)', async () => {
61
+ const { attester, attests, batches } = recordingAttester();
62
+ const batcher = createBatcher(attester, { flushDelayMs: 10 });
63
+
64
+ batcher.add(item());
65
+ await wait(30);
66
+
67
+ expect(attests).toHaveLength(1);
68
+ expect(batches).toHaveLength(0);
69
+ });
70
+ });
package/src/batcher.ts CHANGED
@@ -1,21 +1,23 @@
1
- // Buffer de atestaciones: acumula items y los envía en lote (batchAttest) cada
2
- // N items o cada T ms, lo que ocurra primero. Aísla la latencia/coste de Solana
3
- // del hot-path del agente.
1
+ // Buffer de atestaciones con flush por DEBOUNCE: agrupa una ráfaga de acciones
2
+ // (multi-tool-calling) y las envía en UNA sola tx (batchAttest, hasta 100) apenas
3
+ // la ráfaga se detiene. Esto arregla el bug de "N tool calls seguidos → 0 guardadas":
4
+ // antes el flush solo ocurría a los 25 items o cada 10s, así que una ráfaga corta
5
+ // que terminaba antes quedaba sin enviar.
4
6
 
5
7
  import type { AttestationItem, BatchOptions, ProvaAttester } from './types';
6
- import { clearIntervalSafe, setIntervalSafe, warn, type TimerId } from './runtime';
8
+ import { clearTimeoutSafe, setTimeoutSafe, warn, type TimerId } from './runtime';
7
9
 
8
10
  export interface Batcher {
9
- /** Encola una atestación; dispara flush si se alcanza maxSize. */
11
+ /** Encola una atestación; programa el flush (o lo dispara si se alcanza maxSize). */
10
12
  add(item: AttestationItem): void;
11
13
  /** Envía inmediatamente lo pendiente. */
12
14
  flush(): Promise<void>;
13
- /** Detiene el timer y hace un último flush. */
15
+ /** Cancela el timer y hace un último flush (llamar al cerrar el agente). */
14
16
  stop(): Promise<void>;
15
17
  }
16
18
 
17
19
  const DEFAULT_MAX_SIZE = 25;
18
- const DEFAULT_INTERVAL_MS = 10_000;
20
+ const DEFAULT_FLUSH_DELAY_MS = 1000;
19
21
  const HARD_BATCH_LIMIT = 100;
20
22
 
21
23
  export function createBatcher(
@@ -24,12 +26,20 @@ export function createBatcher(
24
26
  onError: (error: unknown) => void = (error) => warn('[Prova] batch error:', error),
25
27
  ): Batcher {
26
28
  const maxSize = Math.min(Math.max(options.maxSize ?? DEFAULT_MAX_SIZE, 1), HARD_BATCH_LIMIT);
27
- const intervalMs = options.flushIntervalMs ?? DEFAULT_INTERVAL_MS;
29
+ const flushDelayMs = options.flushDelayMs ?? DEFAULT_FLUSH_DELAY_MS;
28
30
 
29
31
  let buffer: AttestationItem[] = [];
30
32
  let timer: TimerId | undefined;
31
33
 
34
+ function clearTimer(): void {
35
+ if (timer !== undefined) {
36
+ clearTimeoutSafe(timer);
37
+ timer = undefined;
38
+ }
39
+ }
40
+
32
41
  async function flush(): Promise<void> {
42
+ clearTimer();
33
43
  if (buffer.length === 0) return;
34
44
  const batch = buffer;
35
45
  buffer = [];
@@ -44,24 +54,29 @@ export function createBatcher(
44
54
  }
45
55
  }
46
56
 
47
- function ensureTimer(): void {
48
- if (intervalMs > 0 && timer === undefined) {
49
- timer = setIntervalSafe(() => void flush(), intervalMs);
57
+ // Debounce: cada acción reinicia el timer, de modo que el flush ocurre
58
+ // `flushDelayMs` ms DESPUÉS de la última acción de la ráfaga → todo en 1 tx.
59
+ function scheduleFlush(): void {
60
+ clearTimer();
61
+ if (flushDelayMs <= 0) {
62
+ void flush();
63
+ return;
50
64
  }
65
+ timer = setTimeoutSafe(() => void flush(), flushDelayMs);
51
66
  }
52
67
 
53
68
  return {
54
69
  add(item: AttestationItem): void {
55
70
  buffer.push(item);
56
- ensureTimer();
57
- if (buffer.length >= maxSize) void flush();
71
+ if (buffer.length >= maxSize) {
72
+ void flush(); // llegó al tope → envía ya
73
+ } else {
74
+ scheduleFlush(); // si no, agrupa la ráfaga y envía al detenerse
75
+ }
58
76
  },
59
77
  flush,
60
78
  async stop(): Promise<void> {
61
- if (timer !== undefined) {
62
- clearIntervalSafe(timer);
63
- timer = undefined;
64
- }
79
+ clearTimer();
65
80
  await flush();
66
81
  },
67
82
  };
package/src/runtime.ts CHANGED
@@ -6,23 +6,23 @@
6
6
  export type TimerId = number | { readonly __timer?: unique symbol };
7
7
 
8
8
  interface RuntimeGlobals {
9
- setInterval(handler: () => void, ms: number): TimerId;
10
- clearInterval(id: TimerId): void;
9
+ setTimeout(handler: () => void, ms: number): TimerId;
10
+ clearTimeout(id: TimerId): void;
11
11
  console: { warn(...args: unknown[]): void };
12
12
  }
13
13
 
14
14
  const runtime = globalThis as unknown as RuntimeGlobals;
15
15
 
16
- /** setInterval que no bloquea la salida del proceso (unref en Node si existe). */
17
- export function setIntervalSafe(handler: () => void, ms: number): TimerId {
18
- const id = runtime.setInterval(handler, ms);
16
+ /** setTimeout que no bloquea la salida del proceso (unref en Node si existe). */
17
+ export function setTimeoutSafe(handler: () => void, ms: number): TimerId {
18
+ const id = runtime.setTimeout(handler, ms);
19
19
  const maybe = id as unknown as { unref?: () => void };
20
20
  if (typeof maybe.unref === 'function') maybe.unref();
21
21
  return id;
22
22
  }
23
23
 
24
- export function clearIntervalSafe(id: TimerId): void {
25
- runtime.clearInterval(id);
24
+ export function clearTimeoutSafe(id: TimerId): void {
25
+ runtime.clearTimeout(id);
26
26
  }
27
27
 
28
28
  export function warn(...args: unknown[]): void {
package/src/types.ts CHANGED
@@ -56,10 +56,14 @@ export interface ProvaAttester {
56
56
 
57
57
  /** Configuración del batching de atestaciones. */
58
58
  export interface BatchOptions {
59
- /** Nº máximo de atestaciones por transacción (1–100). Default 25. */
59
+ /** Nº máximo de atestaciones por transacción (1–100). Al alcanzarlo, flush inmediato. Default 25. */
60
60
  maxSize?: number;
61
- /** Flush automático cada N ms. Default 10000. 0 = sin flush por tiempo. */
62
- flushIntervalMs?: number;
61
+ /**
62
+ * Debounce: hace flush este nº de ms tras la ÚLTIMA acción. Así una ráfaga de
63
+ * tool calls (multi-tool-calling) se agrupa en UNA sola tx on-chain apenas
64
+ * termina la ráfaga. Default 1000. 0 = flush inmediato en cada acción (sin batch).
65
+ */
66
+ flushDelayMs?: number;
63
67
  }
64
68
 
65
69
  /** Opciones de `attachProva`. */