@t2000/engine 0.54.2 → 0.56.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/README.md CHANGED
@@ -1,6 +1,6 @@
1
1
  # @t2000/engine
2
2
 
3
- Agent engine for conversational finance — implements **Audric Intelligence** (the moat behind the Audric consumer product). Five systems work together: Agent Harness (34 tools — 23 read, 11 write), Reasoning Engine (9 guards across 3 priority tiers + 7 YAML skill recipes), Silent Profile, Chain Memory, and AdviceLog. Every action it triggers waits on Audric Passport's tap-to-confirm.
3
+ Agent engine for conversational finance — implements **Audric Intelligence** (the moat behind the Audric consumer product). Five systems work together: Agent Harness (34 tools — 23 read, 11 write), Reasoning Engine (14 guards across 3 priority tiers + 6 YAML skill recipes), Silent Profile, Chain Memory, and AdviceLog. Every action it triggers waits on Audric Passport's tap-to-confirm.
4
4
 
5
5
  QueryEngine orchestrates LLM conversations, financial tools, user confirmations, and MCP integrations into a single async-generator loop.
6
6
 
@@ -33,6 +33,20 @@ for await (const event of engine.submitMessage('What is my balance?')) {
33
33
  }
34
34
  ```
35
35
 
36
+ ## Audric Intelligence — the 5 systems
37
+
38
+ > _Not a chatbot. A financial agent._ Five systems work together to **understand** the user's money, **reason** about decisions, **act** through 34 financial tools in one conversation, **remember** what they did on-chain, and **remember what it told them**. Every action still waits on Audric Passport's tap-to-confirm.
39
+
40
+ | System | One-line | Owns | Lives in |
41
+ |---|---|---|---|
42
+ | 🎛️ **Agent Harness** | 34 tools, one agent. | Tool registry, parallel reads, serial writes, permission gates, streaming dispatch | `engine.ts`, `tool.ts`, `orchestration.ts`, `tools/*` |
43
+ | ⚡ **Reasoning Engine** | Thinks before it acts. | Adaptive thinking effort, 14 guards (12 pre-exec + 2 post-exec), 6 YAML skill recipes, prompt caching, preflight validation | `classify-effort.ts`, `guards.ts`, `recipes/registry.ts`, `engine.ts` `cache_control` |
44
+ | 🧠 **Silent Profile** | Knows your finances. | Daily on-chain orientation snapshot + Claude-inferred profile, injected as `<financial_context>` block at every boot | _Audric-side_: `UserFinancialContext` + `UserFinancialProfile` Prisma models + `buildFinancialContextBlock()` |
45
+ | 🔗 **Chain Memory** | Remembers what you do on-chain. | 7 classifiers extract `ChainFact` rows from on-chain history, hydrated as silent context | _Audric-side_: 7 classifier crons + `ChainFact` Prisma model + `buildMemoryContext()` |
46
+ | 📓 **AdviceLog** | Remembers what it told you. | Every recommendation logged (`record_advice` audric tool); last 30 days hydrated each turn so the chat never contradicts itself | _Audric-side_: `AdviceLog` Prisma model + `record_advice` tool + `buildAdviceContext()` |
47
+
48
+ The engine package owns **Agent Harness** and **Reasoning Engine** in code. The other three systems are powered by audric-side data and injected via the system prompt — see `audric/.cursor/rules/engine-context-assembly.mdc` for the host contract.
49
+
36
50
  ## Architecture
37
51
 
38
52
  ```
@@ -149,7 +163,18 @@ QueryEngine.submitMessage()
149
163
  > fetches. `protocol_deep_dive` retains its DefiLlama dependency (narrow scope, no
150
164
  > equivalent on BlockVision). Net: 23 reads + 11 writes = 34 tools.
151
165
 
152
- ## Audric 2.0 Engine Features
166
+ ## Recent Upgrades — Spec 1 (Correctness) + Spec 2 (Intelligence)
167
+
168
+ Two upgrades shipped on top of the 5-system base:
169
+
170
+ | Spec | Versions | What it added |
171
+ |---|---|---|
172
+ | **Spec 1 — Correctness** | v0.41.0 → v0.50.3 | Per-yield `attemptId` (UUID v4) on every `pending_action` — stable join key from action → on-chain receipt → `TurnMetrics` row. `modifiableFields` registry — fields the user can edit on a confirm card without losing the LLM's reasoning (resume route applies `modifications`). `EngineConfig.onAutoExecuted` hook so `auto`-permission writes participate in the same telemetry as confirm-gated ones. |
173
+ | **Spec 2 — Intelligence** | v0.47.0 → v0.54.1 | BlockVision swap — replaced 7 `defillama_*` tools with one `token_prices`; `balance_check` + `portfolio_analysis` rewired to BlockVision Indexer REST. Sticky-positive cache + retry/circuit breaker (`fetchBlockVisionWithRetry`) for graceful 429 handling. `<financial_context>` boot-time orientation injected from the daily `UserFinancialContext` snapshot (Silent Profile). `attemptId`-keyed resume (no clobbering between two pending actions in the same turn). `protocol_deep_dive` retained on DefiLlama as the lone exception. |
174
+
175
+ > Local-only specs: `AUDRIC_HARNESS_CORRECTNESS_SPEC_v1.3.md`, `AUDRIC_HARNESS_INTELLIGENCE_SPEC_v1.4.1.md`. Cross-repo contracts: `t2000/.cursor/rules/agent-harness-spec.mdc` + `t2000/.cursor/rules/blockvision-resilience.mdc` + `audric/.cursor/rules/audric-transaction-flow.mdc` + `audric/.cursor/rules/write-tool-pending-action.mdc`.
176
+
177
+ ## Engine Features
153
178
 
154
179
  ### Streaming Tool Execution (Early Dispatch)
155
180
 
@@ -170,8 +195,8 @@ Write tool permission resolved dynamically via `resolvePermissionTier(operation,
170
195
  ### Reasoning Engine
171
196
 
172
197
  - **Adaptive thinking** — routes queries to `low`/`medium`/`high` effort based on financial complexity
173
- - **Guard runner** — 9 guards across 3 priority tiers (Safety > Financial > UX)
174
- - **Skill recipes** — YAML recipe files with longest-trigger-match-wins
198
+ - **Guard runner** — 14 guards (12 pre-execution + 2 post-execution hints) across 3 priority tiers (Safety > Financial > UX). See `guards.ts` for the full list.
199
+ - **Skill recipes** — 6 YAML recipes (`swap_and_save`, `safe_borrow`, `send_to_contact`, `portfolio_rebalance`, `account_report`, `emergency_withdraw`) with longest-trigger-match-wins
175
200
  - **Context compaction** — 200k limit, 85% compact trigger, LLM summarizer fallback
176
201
  - **Tool flags** — `mutating`, `requiresBalance`, `affectsHealth`, `irreversible` etc.
177
202
  - **Preflight validation** — input validation on `send_transfer`, `swap_execute`, `pay_api`, `borrow`, `save_deposit`
package/dist/index.d.ts CHANGED
@@ -370,10 +370,28 @@ interface AddressPortfolio {
370
370
  }
371
371
  /**
372
372
  * One-shot wallet portfolio fetcher. BlockVision returns balances + USD
373
- * prices in a single call; on failure we degrade to a Sui-RPC coin fetch
374
- * with hardcoded stablecoin pricing. Memoised in-process for `CACHE_TTL_MS`
375
- * keyed by `address` so back-to-back tool calls inside the same chat
376
- * session (balance_check + portfolio_analysis) share the same response.
373
+ * prices in a single call; on failure we degrade to a Sui-RPC coin
374
+ * fetch with hardcoded stablecoin pricing.
375
+ *
376
+ * Caching shape (PR 1+2 v0.55):
377
+ * 1. Store-level cache (Redis in prod, in-memory in CLI/tests). Read
378
+ * first; if entry is fresh-for-source serve directly.
379
+ * 2. In-process inflight Map dedupes concurrent in-process callers
380
+ * onto one promise.
381
+ * 3. The leader path runs under a cross-instance lock
382
+ * (`bv-lock:wallet:<addr>`) so at most one Vercel instance per
383
+ * address is hitting BlockVision at any moment. Followers poll
384
+ * the store for the leader's write; if the leader dies they fall
385
+ * through to a direct fetch as a defensive degraded path.
386
+ *
387
+ * Sticky-positive write rules mirror the DeFi half:
388
+ * - `blockvision` (any total) → write unconditionally; latest BV
389
+ * truth always wins.
390
+ * - `sui-rpc-degraded` → write only when no fresher positive
391
+ * `blockvision` entry exists in the sticky window (30 min). When
392
+ * a positive sticky entry exists, return it as-is and DO NOT
393
+ * overwrite — preserves the original `pricedAt` so a UI can
394
+ * render "last refresh Nm ago".
377
395
  */
378
396
  declare function fetchAddressPortfolio(address: string, apiKey: string | undefined, fallbackRpcUrl?: string): Promise<AddressPortfolio>;
379
397
  /**
@@ -414,21 +432,35 @@ interface DefiSummary {
414
432
  source: 'blockvision' | 'partial' | 'partial-stale' | 'degraded';
415
433
  }
416
434
  declare function fetchAddressDefiPortfolio(address: string, apiKey: string | undefined, priceHints?: Record<string, number>): Promise<DefiSummary>;
417
- declare function clearPortfolioCache(): void;
418
435
  /**
419
- * [v1.4 Day 2.5] Per-address invalidator. The module-level
420
- * `portfolioCache` carries a 60s TTL — long enough to outlive a
421
- * `save_deposit` / `swap_execute` / etc. plus the engine's 1.5s
422
- * Sui-RPC-indexer-lag delay, so without explicit invalidation
423
- * `runPostWriteRefresh` would re-fetch the *cached pre-write
424
- * snapshot*. Engine calls this from `runPostWriteRefresh` right
425
- * before the lag-delay so the next `fetchAddressPortfolio` for the
426
- * affected address is forced to hit BlockVision again.
436
+ * Wipe the wallet portfolio cache plus any in-flight promises.
437
+ *
438
+ * Async because the underlying store may be Redis-backed. Existing
439
+ * fire-and-forget callers (tests, `clearDefiCache`-shape utilities)
440
+ * still work since they just don't `await` they get the same
441
+ * effective "schedule the clear" behavior.
442
+ */
443
+ declare function clearPortfolioCache(): Promise<void>;
444
+ /**
445
+ * [v1.4 — Day 2.5] Per-address invalidator.
446
+ *
447
+ * Engine `runPostWriteRefresh` calls this right before the 1.5s
448
+ * Sui-RPC-indexer-lag delay so the next `fetchAddressPortfolio` for
449
+ * the affected address is forced to hit BlockVision again instead of
450
+ * returning the cached pre-write snapshot.
451
+ *
452
+ * **MUST be awaited** — pre-PR-1 the cache was a synchronous Map and
453
+ * the `engine.ts` caller fired-and-forgot. Now the underlying store
454
+ * is async (Upstash in production), so without `await` the next
455
+ * `balance_check` races the Redis delete and can fetch the stale
456
+ * pre-write balance — exactly the symptom v0.54 sticky cache shipped
457
+ * to fix. `engine.ts` `runPostWriteRefresh` was updated to await this
458
+ * in the same PR.
427
459
  *
428
460
  * No-op when `address` doesn't match a cached entry — cheap to call
429
461
  * unconditionally on every write.
430
462
  */
431
- declare function clearPortfolioCacheFor(address: string): void;
463
+ declare function clearPortfolioCacheFor(address: string): Promise<void>;
432
464
  declare function clearPriceMapCache(): void;
433
465
 
434
466
  /**
@@ -2504,6 +2536,196 @@ declare function getDefiCacheStore(): DefiCacheStore;
2504
2536
  /** Restore the default in-memory store. Used by test teardowns. */
2505
2537
  declare function resetDefiCacheStore(): void;
2506
2538
 
2539
+ /** Cache entry stored under each address key. */
2540
+ interface WalletCacheEntry {
2541
+ data: AddressPortfolio;
2542
+ /**
2543
+ * Wall-clock ms when this entry was WRITTEN to the cache — used by
2544
+ * the fetcher to compute freshness.
2545
+ *
2546
+ * Distinct from `data.pricedAt` (the upstream-data timestamp from
2547
+ * BlockVision / Sui RPC). Mirrors `DefiCacheEntry.pricedAt` for
2548
+ * cross-pattern consistency.
2549
+ */
2550
+ pricedAt: number;
2551
+ }
2552
+ /**
2553
+ * Pluggable cache backend.
2554
+ *
2555
+ * All methods are async because Redis-backed implementations are
2556
+ * inherently async; the in-memory impl wraps in resolved promises.
2557
+ *
2558
+ * Implementations MUST tolerate transport errors gracefully — `get`
2559
+ * should return `null` (not throw) on backend failure so the fetcher
2560
+ * falls through to a fresh BlockVision read instead of erroring the
2561
+ * whole `balance_check` call. `set` should swallow errors (logging is
2562
+ * fine) so a Redis hiccup doesn't break a successful read.
2563
+ */
2564
+ interface WalletCacheStore {
2565
+ /** Returns the cached entry, or `null` if not found / expired / backend error. */
2566
+ get(address: string): Promise<WalletCacheEntry | null>;
2567
+ /** Writes an entry with a TTL in seconds. Errors are swallowed (logged). */
2568
+ set(address: string, entry: WalletCacheEntry, ttlSec: number): Promise<void>;
2569
+ /** Removes the entry for an address. Errors are swallowed. */
2570
+ delete(address: string): Promise<void>;
2571
+ /** Removes all entries. Used by tests and `clearPortfolioCache()`. */
2572
+ clear(): Promise<void>;
2573
+ }
2574
+ /**
2575
+ * Process-local cache backed by a `Map`. Used as the default when no
2576
+ * other store has been injected via `setWalletCacheStore()`.
2577
+ *
2578
+ * NOT suitable for multi-instance deployments — each Vercel function
2579
+ * instance gets its own Map, which causes the SSOT divergence this
2580
+ * module exists to fix. Audric replaces this at engine init with an
2581
+ * Upstash-backed store; the CLI and MCP server keep it.
2582
+ *
2583
+ * Address normalization: keys are lowercased to match the
2584
+ * `UpstashWalletCacheStore.key()` convention. Sui addresses are
2585
+ * case-insensitive after the `0x` prefix in practice; keying by
2586
+ * lowercase prevents accidental cache misses from `0xABC...` vs
2587
+ * `0xabc...` callers (e.g. one route normalizes, another doesn't).
2588
+ */
2589
+ declare class InMemoryWalletCacheStore implements WalletCacheStore {
2590
+ private readonly store;
2591
+ get(address: string): Promise<WalletCacheEntry | null>;
2592
+ set(address: string, entry: WalletCacheEntry, ttlSec: number): Promise<void>;
2593
+ delete(address: string): Promise<void>;
2594
+ clear(): Promise<void>;
2595
+ }
2596
+ /**
2597
+ * Swap the active wallet cache store. Call once at engine init from a
2598
+ * runtime that wants a non-default backend (e.g. Audric injecting
2599
+ * `UpstashWalletCacheStore`). Idempotent — calling again replaces the
2600
+ * previous store, but does NOT migrate entries; warm cache is lost on
2601
+ * swap. Tests use this to inject a fake/spy store and
2602
+ * `resetWalletCacheStore()` to restore the in-memory default.
2603
+ */
2604
+ declare function setWalletCacheStore(store: WalletCacheStore): void;
2605
+ /** Returns the currently active store. Used by `fetchAddressPortfolio`. */
2606
+ declare function getWalletCacheStore(): WalletCacheStore;
2607
+ /** Restore the default in-memory store. Used by test teardowns. */
2608
+ declare function resetWalletCacheStore(): void;
2609
+
2610
+ /**
2611
+ * Pluggable distributed mutex. Implementations MUST tolerate transport
2612
+ * errors gracefully — `acquire` returning `false` (not throwing) on
2613
+ * backend failure is fine; the caller will fall through to a direct
2614
+ * fetch. The Upstash impl uses `SET NX EX` which is the canonical
2615
+ * Redis distributed-lock primitive at this scale.
2616
+ */
2617
+ interface FetchLock {
2618
+ /**
2619
+ * Try to acquire the lock for `key`, with an automatic expiry of
2620
+ * `leaseSec` seconds.
2621
+ *
2622
+ * Returns `true` if acquired (caller is now the leader for that key,
2623
+ * MUST call `release` when done). Returns `false` if the lock is
2624
+ * already held by someone else (caller is a follower).
2625
+ *
2626
+ * Errors are swallowed (logged); on backend failure return `false`
2627
+ * so the caller falls through to its degraded direct-fetch path.
2628
+ */
2629
+ acquire(key: string, leaseSec: number): Promise<boolean>;
2630
+ /**
2631
+ * Release the lock for `key`. Idempotent — calling on a key the
2632
+ * caller doesn't hold is a no-op (we accept a small window of
2633
+ * potential ABA: if our lease expired and another caller took the
2634
+ * key, we'll harmlessly delete THEIR lock once. Production traffic
2635
+ * patterns make this exceedingly rare; the cost is one extra
2636
+ * fan-out, which is the same cost we'd pay anyway under contention.)
2637
+ *
2638
+ * Errors are swallowed (logged) — release failures are non-fatal
2639
+ * because the lease expires on its own.
2640
+ */
2641
+ release(key: string): Promise<void>;
2642
+ }
2643
+ /**
2644
+ * Process-local mutex backed by a `Map<key, expiryMs>`. Used as the
2645
+ * default when no other lock has been injected via `setFetchLock()`.
2646
+ *
2647
+ * NOT suitable for multi-instance deployments — each Vercel function
2648
+ * instance gets its own Map, which means N concurrent instances all
2649
+ * acquire successfully and all fan out to BlockVision. Audric replaces
2650
+ * this at engine init with `UpstashFetchLock`; the CLI keeps it
2651
+ * (single-process, so process-local is correct).
2652
+ */
2653
+ declare class InMemoryFetchLock implements FetchLock {
2654
+ private readonly held;
2655
+ acquire(key: string, leaseSec: number): Promise<boolean>;
2656
+ release(key: string): Promise<void>;
2657
+ }
2658
+ /**
2659
+ * Swap the active fetch lock backend. Call once at engine init from a
2660
+ * runtime that wants a non-default backend (e.g. Audric injecting
2661
+ * `UpstashFetchLock`). Idempotent — calling again replaces the previous
2662
+ * lock instance, but does NOT migrate held leases (which are stored in
2663
+ * the backend, not the lock object).
2664
+ */
2665
+ declare function setFetchLock(lock: FetchLock): void;
2666
+ /** Returns the currently active lock. Used by `awaitOrFetch`. */
2667
+ declare function getFetchLock(): FetchLock;
2668
+ /** Restore the default in-memory lock. Used by test teardowns. */
2669
+ declare function resetFetchLock(): void;
2670
+ /** Default lease seconds — sized for worst-case BV retry budget. See header. */
2671
+ declare const DEFAULT_LEASE_SEC = 15;
2672
+ /** Default follower poll budget ms — must be < engine per-tool timeout. */
2673
+ declare const DEFAULT_POLL_BUDGET_MS = 4500;
2674
+ /** Default poll interval ms (jittered ±20%) — ~45 GETs over 4.5s. */
2675
+ declare const DEFAULT_POLL_INTERVAL_MS = 100;
2676
+ interface AwaitOrFetchOpts<T> {
2677
+ /** Override the active lock instance (test seam). */
2678
+ lock?: FetchLock;
2679
+ /** Lease seconds for `lock.acquire`. Default 15. */
2680
+ leaseSec?: number;
2681
+ /** Total ms a follower will poll the cache before falling through. Default 4500. */
2682
+ pollBudgetMs?: number;
2683
+ /** Poll cadence ms (jittered ±20%). Default 100. */
2684
+ pollIntervalMs?: number;
2685
+ /**
2686
+ * Optional cache reader for followers. If provided, followers poll
2687
+ * this every `pollIntervalMs` (jittered) until it returns non-null
2688
+ * or the budget is exhausted. If omitted, followers fall through
2689
+ * immediately to a direct `fetcher()` call (no coalescing benefit).
2690
+ *
2691
+ * The follower's "is this fresh enough?" decision lives inside this
2692
+ * callback — it should return `null` when the cache entry exists but
2693
+ * is too stale to serve, so the poll keeps trying.
2694
+ */
2695
+ pollCache?: () => Promise<T | null>;
2696
+ /** Test seam — defaults to `Math.random()`. */
2697
+ rng?: () => number;
2698
+ /** Test seam — defaults to `setTimeout`-backed promise. */
2699
+ sleep?: (ms: number) => Promise<void>;
2700
+ /** Test seam — defaults to `Date.now()`. */
2701
+ now?: () => number;
2702
+ /** Optional abort signal — caller cancellation halts polling. */
2703
+ signal?: AbortSignal;
2704
+ }
2705
+ /**
2706
+ * Cross-instance request coalescer.
2707
+ *
2708
+ * `key` MUST be stable across instances for the same logical fetch —
2709
+ * e.g. `bv-lock:wallet:0xabc...` for the wallet portfolio of `0xabc...`.
2710
+ * Different operations on the same address (wallet vs DeFi) MUST use
2711
+ * different keys so they don't block each other.
2712
+ *
2713
+ * `fetcher` is the leader's work. It SHOULD write the cache as its
2714
+ * last act, otherwise followers will time out and fall through to
2715
+ * direct fetches (functionally correct, just no coalescing benefit).
2716
+ *
2717
+ * The leader is guaranteed to call `release()` even if `fetcher()`
2718
+ * throws — propagated to the caller after release completes.
2719
+ *
2720
+ * Followers degrade to direct `fetcher()` on:
2721
+ * - lock backend failure (`acquire` threw or returned `false` due to a
2722
+ * transport error rather than contention)
2723
+ * - poll budget exhausted (leader didn't write cache, or wrote with
2724
+ * an old enough `pricedAt` that `pollCache` keeps returning `null`)
2725
+ * - no `pollCache` callback provided
2726
+ */
2727
+ declare function awaitOrFetch<T>(key: string, fetcher: () => Promise<T>, opts?: AwaitOrFetchOpts<T>): Promise<T>;
2728
+
2507
2729
  /**
2508
2730
  * Resolve the audric API base URL from the engine's env shim, falling
2509
2731
  * back to `process.env`. Returns `null` when no override is configured —
@@ -2571,6 +2793,6 @@ declare function fetchAudricHistory(address: string, opts: {
2571
2793
  limit?: number;
2572
2794
  }, env?: Record<string, string>, signal?: AbortSignal): Promise<AudricHistoryRecord[] | null>;
2573
2795
 
2574
- declare const DEFAULT_SYSTEM_PROMPT = "You are Audric \u2014 a financial agent on Sui. Audric is exactly five products: Audric Passport (the trust layer \u2014 Google sign-in, non-custodial wallet, tap-to-confirm consent, sponsored gas \u2014 wraps every other product), Audric Intelligence (you \u2014 the 5-system brain: Agent Harness with 34 tools, Reasoning Engine with 9 guards and 7 skill recipes, Silent Profile, Chain Memory, AdviceLog), Audric Finance (manage money on Sui \u2014 Save via NAVI lending at 3-8% APY USDC, Credit via NAVI borrowing with health factor, Swap via Cetus aggregator across 20+ DEXs at 0.1% fee, Charts for yield/health/portfolio viz), Audric Pay (move money \u2014 send USDC, receive via payment links / invoices / QR; free, global, instant on Sui), and Audric Store (creator marketplace, ships Phase 5 \u2014 say \"coming soon\" if asked). Save, swap, borrow, repay, withdraw, charts \u2192 Audric Finance. Send, receive, payment-link, invoice, QR \u2192 Audric Pay. Your silent context (profile, memory, chain facts, advice log) shapes your replies but never surfaces as a notification \u2014 you act only when the user asks, and every write waits on their tap-to-confirm via Passport. You can also call 41 paid APIs (music, image, research, translation, weather, fulfilment) via MPP micropayments using the pay_api tool \u2014 this is an internal capability, not a promoted product, so only mention it when the user asks for something that needs it.\n\n## Response rules\n- 1-2 sentences max. No bullet lists unless asked. No preambles.\n- Never say \"Would you like me to...\", \"Sure!\", \"Great question!\", \"Absolutely!\" \u2014 just do it or say you can't.\n- Present amounts as $1,234.56 and rates as X.XX% APY.\n- Show top 3 results unless asked for more. Summarize totals in one line.\n\n## Caption rules (after tool calls)\n- **When a canvas was rendered (`render_canvas` was called, or any tool that auto-renders a card like balance_check / portfolio_analysis / savings_info / health_check / transaction_history): the canvas IS the answer.** Your chat message must NOT restate wallet, savings, debt, holdings, or net-worth numbers \u2014 they are already on screen. Add at most ONE sentence of context, advice, or next step (e.g. \"Your USDC is idle \u2014 consider depositing for ~4.5% APY\"), or say nothing.\n- **When NO canvas was rendered:** lead with the result and quote the actual numbers from the tool. One sentence.\n- **NEVER describe a position as \"no\", \"none\", \"minimal\", \"zero\", or \"inactive\" if the tool result contains a positive value for that field.** The tool result is the source of truth \u2014 never your interior summary. If the canvas shows $100 in savings, you cannot say \"no active savings\" in the caption.\n- **NEVER claim \"no DeFi positions\" when the tool result says the DeFi slice is UNAVAILABLE.** When `balance_check` displayText contains \"DeFi positions: UNAVAILABLE\" or \"DeFi data source unreachable\", the slice is unknown \u2014 say \"DeFi data is currently unavailable\" or omit the mention. Only claim \"no DeFi positions\" when the displayText explicitly omits any DeFi line (i.e. the fetch succeeded with $0 across every covered protocol).\n\n## Execution rule\nOnly offer to execute actions you have tools for. If you retrieved a quote, data, or information but have no tool to act on it, give the user the result and tell them where to execute manually \u2014 in one sentence. Never say \"Would you like me to proceed?\" unless you have a tool that can actually proceed.\n\n## Before acting\n- ALWAYS call a read tool first before any write tool \u2014 balance_check before save/send/borrow, savings_info before withdraw.\n- Show real numbers from tools \u2014 never fabricate rates, amounts, or balances.\n- When user says \"all\" or an imprecise amount, call the read tool first to get the exact number.\n\n## Tool usage\n- Use tools proactively \u2014 don't refuse requests you can handle.\n- For real-world questions (weather, search, news, prices), use pay_api. Tell the user the cost first.\n- For NAVI lending APYs, use rates_info; for VOLO liquid staking stats, use volo_stats; for spot token prices, use token_prices.\n- For protocol-level due diligence (TVL, fees, audits, safety) on Sui DeFi protocols, use protocol_deep_dive with the slug.\n- Run multiple read-only tools in parallel when you need several data points.\n- If a tool errors, say what went wrong and what to try instead. One sentence.\n\n## Savings = USDC or USDsui (critical)\n- save_deposit and borrow accept ONLY USDC or USDsui. No other token can be deposited or borrowed.\n- USDC is the canonical default. USDsui is permitted because it has a productive NAVI pool (often a higher APY than USDC). All other holdings (GOLD, SUI, USDT, USDe, ETH, NAVX, WAL) are NOT saveable.\n- When asked \"how much can I save?\":\n - Report saveableUsdc from balance_check (the user's USDC wallet balance \u2014 canonical saveable).\n - If the user also holds USDsui in their wallet, report that separately as \"USDsui (saveable): X.XX\". Do NOT roll the two together \u2014 the LLM must keep the per-asset distinction so the user can pick.\n- When the user says \"save 10 USDC\" \u2192 call save_deposit with asset=\"USDC\". When they say \"save 10 USDsui\" \u2192 call with asset=\"USDsui\". Never silently substitute.\n- When the user says \"save 10\" (no asset) \u2192 call balance_check first and ask which stable they want, OR pick whichever they hold more of with a one-line explanation.\n- \"Best stable to save right now?\" \u2192 call rates_info to compare USDC vs USDsui APY on NAVI; let the user pick.\n- NEVER say a non-saveable token (GOLD, SUI, USDT, etc.) is \"in savings\" or \"earning APY in savings\". Wallet holdings \u2260 savings positions, even for stables we don't accept.\n- If user wants to save a non-saveable token, tell them to swap to USDC or USDsui first. Do NOT auto-chain swap + deposit.\n- Repay symmetry: a USDsui debt MUST be repaid with USDsui (and USDC debt with USDC). When calling repay_debt, pass asset=\"USDsui\" if the borrow is USDsui. If the user asks \"repay my debt\" and savings_info shows borrows in BOTH stables, list both and ask which to repay first. If the user holds the wrong stable, tell them to swap manually \u2014 do NOT auto-chain swap + repay.\n\n## Multi-step flows\n- \"How much X for Y?\": swap_quote first, then swap_execute if user confirms.\n- \"Swap then save\": swap_execute \u2192 balance_check \u2192 save_deposit. Confirm each step.\n- \"Buy $X of token\": token_prices \u2192 calculate amount \u2192 swap_execute.\n- \"Best yield on SUI\": compare rates_info (NAVI lending) + volo_stats (vSUI liquid staking).\n- withdraw supports legacy positions: USDC, USDe, USDsui, SUI. Pass asset param to withdraw a specific token.\n- \"Deposit SUI to earn yield\": volo_stake for SUI liquid staking. save_deposit only accepts USDC or USDsui.\n- \"Is protocol X safe?\" / \"Tell me about NAVI\": protocol_deep_dive with the slug.\n- \"Full account report\" / \"account summary\" / \"give me everything\" / \"complete overview\": triggers the `account_report` recipe \u2014 when the recipe block appears, follow EVERY step including all six tool calls. Each step renders a distinct rich card; skipping a step means a missing card.\n\n## Safety\n- Never encourage risky financial behavior.\n- Warn when health factor < 1.5.\n- All amounts in USDC unless stated otherwise.";
2796
+ declare const DEFAULT_SYSTEM_PROMPT = "You are Audric \u2014 a financial agent on Sui. Audric is exactly five products: Audric Passport (the trust layer \u2014 Google sign-in, non-custodial wallet, tap-to-confirm consent, sponsored gas \u2014 wraps every other product), Audric Intelligence (you \u2014 the 5-system brain: Agent Harness with 34 tools, Reasoning Engine with 14 guards and 6 skill recipes, Silent Profile, Chain Memory, AdviceLog), Audric Finance (manage money on Sui \u2014 Save via NAVI lending at 3-8% APY USDC, Credit via NAVI borrowing with health factor, Swap via Cetus aggregator across 20+ DEXs at 0.1% fee, Charts for yield/health/portfolio viz), Audric Pay (move money \u2014 send USDC, receive via payment links / invoices / QR; free, global, instant on Sui), and Audric Store (creator marketplace, ships Phase 5 \u2014 say \"coming soon\" if asked). Save, swap, borrow, repay, withdraw, charts \u2192 Audric Finance. Send, receive, payment-link, invoice, QR \u2192 Audric Pay. Your silent context (profile, memory, chain facts, advice log) shapes your replies but never surfaces as a notification \u2014 you act only when the user asks, and every write waits on their tap-to-confirm via Passport. You can also call 41 paid APIs (music, image, research, translation, weather, fulfilment) via MPP micropayments using the pay_api tool \u2014 this is an internal capability, not a promoted product, so only mention it when the user asks for something that needs it.\n\n## Response rules\n- 1-2 sentences max. No bullet lists unless asked. No preambles.\n- Never say \"Would you like me to...\", \"Sure!\", \"Great question!\", \"Absolutely!\" \u2014 just do it or say you can't.\n- Present amounts as $1,234.56 and rates as X.XX% APY.\n- Show top 3 results unless asked for more. Summarize totals in one line.\n\n## Caption rules (after tool calls)\n- **When a canvas was rendered (`render_canvas` was called, or any tool that auto-renders a card like balance_check / portfolio_analysis / savings_info / health_check / transaction_history): the canvas IS the answer.** Your chat message must NOT restate wallet, savings, debt, holdings, or net-worth numbers \u2014 they are already on screen. Add at most ONE sentence of context, advice, or next step (e.g. \"Your USDC is idle \u2014 consider depositing for ~4.5% APY\"), or say nothing.\n- **When NO canvas was rendered:** lead with the result and quote the actual numbers from the tool. One sentence.\n- **NEVER describe a position as \"no\", \"none\", \"minimal\", \"zero\", or \"inactive\" if the tool result contains a positive value for that field.** The tool result is the source of truth \u2014 never your interior summary. If the canvas shows $100 in savings, you cannot say \"no active savings\" in the caption.\n- **NEVER claim \"no DeFi positions\" when the tool result says the DeFi slice is UNAVAILABLE.** When `balance_check` displayText contains \"DeFi positions: UNAVAILABLE\" or \"DeFi data source unreachable\", the slice is unknown \u2014 say \"DeFi data is currently unavailable\" or omit the mention. Only claim \"no DeFi positions\" when the displayText explicitly omits any DeFi line (i.e. the fetch succeeded with $0 across every covered protocol).\n\n## Execution rule\nOnly offer to execute actions you have tools for. If you retrieved a quote, data, or information but have no tool to act on it, give the user the result and tell them where to execute manually \u2014 in one sentence. Never say \"Would you like me to proceed?\" unless you have a tool that can actually proceed.\n\n## Before acting\n- ALWAYS call a read tool first before any write tool \u2014 balance_check before save/send/borrow, savings_info before withdraw.\n- Show real numbers from tools \u2014 never fabricate rates, amounts, or balances.\n- When user says \"all\" or an imprecise amount, call the read tool first to get the exact number.\n\n## Tool usage\n- Use tools proactively \u2014 don't refuse requests you can handle.\n- For real-world questions (weather, search, news, prices), use pay_api. Tell the user the cost first.\n- For NAVI lending APYs, use rates_info; for VOLO liquid staking stats, use volo_stats; for spot token prices, use token_prices.\n- For protocol-level due diligence (TVL, fees, audits, safety) on Sui DeFi protocols, use protocol_deep_dive with the slug.\n- Run multiple read-only tools in parallel when you need several data points.\n- If a tool errors, say what went wrong and what to try instead. One sentence.\n\n## Savings = USDC or USDsui (critical)\n- save_deposit and borrow accept ONLY USDC or USDsui. No other token can be deposited or borrowed.\n- USDC is the canonical default. USDsui is permitted because it has a productive NAVI pool (often a higher APY than USDC). All other holdings (GOLD, SUI, USDT, USDe, ETH, NAVX, WAL) are NOT saveable.\n- When asked \"how much can I save?\":\n - Report saveableUsdc from balance_check (the user's USDC wallet balance \u2014 canonical saveable).\n - If the user also holds USDsui in their wallet, report that separately as \"USDsui (saveable): X.XX\". Do NOT roll the two together \u2014 the LLM must keep the per-asset distinction so the user can pick.\n- When the user says \"save 10 USDC\" \u2192 call save_deposit with asset=\"USDC\". When they say \"save 10 USDsui\" \u2192 call with asset=\"USDsui\". Never silently substitute.\n- When the user says \"save 10\" (no asset) \u2192 call balance_check first and ask which stable they want, OR pick whichever they hold more of with a one-line explanation.\n- \"Best stable to save right now?\" \u2192 call rates_info to compare USDC vs USDsui APY on NAVI; let the user pick.\n- NEVER say a non-saveable token (GOLD, SUI, USDT, etc.) is \"in savings\" or \"earning APY in savings\". Wallet holdings \u2260 savings positions, even for stables we don't accept.\n- If user wants to save a non-saveable token, tell them to swap to USDC or USDsui first. Do NOT auto-chain swap + deposit.\n- Repay symmetry: a USDsui debt MUST be repaid with USDsui (and USDC debt with USDC). When calling repay_debt, pass asset=\"USDsui\" if the borrow is USDsui. If the user asks \"repay my debt\" and savings_info shows borrows in BOTH stables, list both and ask which to repay first. If the user holds the wrong stable, tell them to swap manually \u2014 do NOT auto-chain swap + repay.\n\n## Multi-step flows\n- \"How much X for Y?\": swap_quote first, then swap_execute if user confirms.\n- \"Swap then save\": swap_execute \u2192 balance_check \u2192 save_deposit. Confirm each step.\n- \"Buy $X of token\": token_prices \u2192 calculate amount \u2192 swap_execute.\n- \"Best yield on SUI\": compare rates_info (NAVI lending) + volo_stats (vSUI liquid staking).\n- withdraw supports legacy positions: USDC, USDe, USDsui, SUI. Pass asset param to withdraw a specific token.\n- \"Deposit SUI to earn yield\": volo_stake for SUI liquid staking. save_deposit only accepts USDC or USDsui.\n- \"Is protocol X safe?\" / \"Tell me about NAVI\": protocol_deep_dive with the slug.\n- \"Full account report\" / \"account summary\" / \"give me everything\" / \"complete overview\": triggers the `account_report` recipe \u2014 when the recipe block appears, follow EVERY step including all six tool calls. Each step renders a distinct rich card; skipping a step means a missing card.\n\n## Safety\n- Never encourage risky financial behavior.\n- Warn when health factor < 1.5.\n- All amounts in USDC unless stated otherwise.";
2575
2797
 
2576
- export { type AddressPortfolio, AnthropicProvider, type AnthropicProviderConfig, type AudricHistoryRecord, type AudricPortfolioResult, type BalancePrices, type BalanceResult, BalanceTracker, type BuildToolOptions, CANVAS_TEMPLATES, type CanvasTemplate, type ChatParams, type CompactOptions, type ContentBlock, ContextBudget, type ContextBudgetConfig, type ConversationState, type ConversationStateStore, type CostSnapshot, CostTracker, type CostTrackerConfig, DEFAULT_GUARD_CONFIG, DEFAULT_PERMISSION_CONFIG, DEFAULT_SYSTEM_PROMPT, type DefiCacheEntry, type DefiCacheStore, type DefiProtocol, type DefiSummary, EarlyToolDispatcher, type EngineConfig, type EngineEvent, type GuardCheckResult, type GuardConfig, type GuardEvent, type GuardInjection, type GuardResult, type GuardRunnerState, type GuardTier, type GuardVerdict, type HealthFactorResult, InMemoryDefiCacheStore, type LLMProvider, type McpCallResult, McpClientManager, McpResponseCache, type McpServerConfig, type McpServerConnection, type McpToolAdapterConfig, type McpToolDescriptor, MemorySessionStore, type Message, NAVI_MCP_CONFIG, NAVI_MCP_URL, NAVI_SERVER_NAME, type NaviRawCoin, type NaviRawHealthFactor, type NaviRawPool, type NaviRawPosition, type NaviRawPositionsResponse, type NaviRawProtocolStats, type NaviRawRewardsResponse, type NaviReadOptions, NaviTools, type OutputConfig, PERMISSION_PRESETS, type PendingAction, type PendingActionModifiableField, type PendingReward, type PendingToolCall, type PermissionLevel, type PermissionOperation, type PermissionResponse, type PermissionRule, type PortfolioCoin, type PositionEntry, type PreflightResult, type ProtocolStats, type ProviderEvent, QueryEngine, READ_TOOLS, type RatesResult, type Recipe, type RecipePrerequisite, RecipeRegistry, type RecipeStep, type RecipeStepOnError, RetryTracker, type SSEEvent, type SavingsResult, type ServerPositionData, type SessionData, type SessionStore, type StateType, type StopReason, type SuiCoinBalance, type SystemBlock, type SystemPrompt, TOOL_FLAGS, TOOL_MODIFIABLE_FIELDS, type ThinkingConfig, type ThinkingEffort, type Tool, type ToolChoice, type ToolContext, type ToolDefinition, type ToolFlags, type ToolJsonSchema, type ToolResult, TxMutex, type UserFinancialProfile, type UserPermissionConfig, WRITE_TOOLS, type WalletCoin, activitySummaryTool, adaptAllMcpTools, adaptAllServerTools, adaptMcpTool, applyToolFlags, balanceCheckTool, borrowTool, budgetToolResult, buildCachedSystemPrompt, buildMcpTools, buildProactivenessInstructions, buildProfileContext, buildSelfEvaluationInstruction, buildStateContext, buildTool, claimRewardsTool, classifyEffort, clearPortfolioCache, clearPortfolioCacheFor, clearPriceMapCache, compactMessages, createGuardRunnerState, engineToSSE, estimateTokens, explainTxTool, extractConversationText, extractMcpText, fetchAddressDefiPortfolio, fetchAddressPortfolio, fetchAudricHistory, fetchAudricPortfolio, fetchAvailableRewards, fetchBalance, fetchHealthFactor, fetchPositions, fetchProtocolStats, fetchRates, fetchSavings, fetchTokenPrices, fetchWalletCoins, findTool, getAudricApiBase, getDefaultTools, getDefiCacheStore, getMcpManager, getModifiableFields, getToolFlags, getWalletAddress, guardArtifactPreview, guardStaleData, hasNaviMcp, healthCheckTool, loadRecipes, microcompact, mppServicesTool, parseMcpJson, parseRecipe, parseSSE, payApiTool, portfolioAnalysisTool, protocolDeepDiveTool, ratesInfoTool, registerEngineTools, renderCanvasTool, repayDebtTool, requireAgent, resetDefiCacheStore, resolvePermissionTier, resolveUsdValue, runGuards, runTools, saveContactTool, saveDepositTool, savingsInfoTool, sendTransferTool, serializeSSE, setDefiCacheStore, spendingAnalyticsTool, swapExecuteTool, swapQuoteTool, tokenPricesTool, toolNameToOperation, toolsToDefinitions, transactionHistoryTool, transformBalance, transformHealthFactor, transformPositions, transformRates, transformRewards, transformSavings, updateGuardStateAfterToolResult, validateHistory, voloStakeTool, voloStatsTool, voloUnstakeTool, webSearchTool, withdrawTool, yieldSummaryTool };
2798
+ export { type AddressPortfolio, AnthropicProvider, type AnthropicProviderConfig, type AudricHistoryRecord, type AudricPortfolioResult, type AwaitOrFetchOpts, type BalancePrices, type BalanceResult, BalanceTracker, type BuildToolOptions, CANVAS_TEMPLATES, type CanvasTemplate, type ChatParams, type CompactOptions, type ContentBlock, ContextBudget, type ContextBudgetConfig, type ConversationState, type ConversationStateStore, type CostSnapshot, CostTracker, type CostTrackerConfig, DEFAULT_GUARD_CONFIG, DEFAULT_LEASE_SEC, DEFAULT_PERMISSION_CONFIG, DEFAULT_POLL_BUDGET_MS, DEFAULT_POLL_INTERVAL_MS, DEFAULT_SYSTEM_PROMPT, type DefiCacheEntry, type DefiCacheStore, type DefiProtocol, type DefiSummary, EarlyToolDispatcher, type EngineConfig, type EngineEvent, type FetchLock, type GuardCheckResult, type GuardConfig, type GuardEvent, type GuardInjection, type GuardResult, type GuardRunnerState, type GuardTier, type GuardVerdict, type HealthFactorResult, InMemoryDefiCacheStore, InMemoryFetchLock, InMemoryWalletCacheStore, type LLMProvider, type McpCallResult, McpClientManager, McpResponseCache, type McpServerConfig, type McpServerConnection, type McpToolAdapterConfig, type McpToolDescriptor, MemorySessionStore, type Message, NAVI_MCP_CONFIG, NAVI_MCP_URL, NAVI_SERVER_NAME, type NaviRawCoin, type NaviRawHealthFactor, type NaviRawPool, type NaviRawPosition, type NaviRawPositionsResponse, type NaviRawProtocolStats, type NaviRawRewardsResponse, type NaviReadOptions, NaviTools, type OutputConfig, PERMISSION_PRESETS, type PendingAction, type PendingActionModifiableField, type PendingReward, type PendingToolCall, type PermissionLevel, type PermissionOperation, type PermissionResponse, type PermissionRule, type PortfolioCoin, type PositionEntry, type PreflightResult, type ProtocolStats, type ProviderEvent, QueryEngine, READ_TOOLS, type RatesResult, type Recipe, type RecipePrerequisite, RecipeRegistry, type RecipeStep, type RecipeStepOnError, RetryTracker, type SSEEvent, type SavingsResult, type ServerPositionData, type SessionData, type SessionStore, type StateType, type StopReason, type SuiCoinBalance, type SystemBlock, type SystemPrompt, TOOL_FLAGS, TOOL_MODIFIABLE_FIELDS, type ThinkingConfig, type ThinkingEffort, type Tool, type ToolChoice, type ToolContext, type ToolDefinition, type ToolFlags, type ToolJsonSchema, type ToolResult, TxMutex, type UserFinancialProfile, type UserPermissionConfig, WRITE_TOOLS, type WalletCacheEntry, type WalletCacheStore, type WalletCoin, activitySummaryTool, adaptAllMcpTools, adaptAllServerTools, adaptMcpTool, applyToolFlags, awaitOrFetch, balanceCheckTool, borrowTool, budgetToolResult, buildCachedSystemPrompt, buildMcpTools, buildProactivenessInstructions, buildProfileContext, buildSelfEvaluationInstruction, buildStateContext, buildTool, claimRewardsTool, classifyEffort, clearPortfolioCache, clearPortfolioCacheFor, clearPriceMapCache, compactMessages, createGuardRunnerState, engineToSSE, estimateTokens, explainTxTool, extractConversationText, extractMcpText, fetchAddressDefiPortfolio, fetchAddressPortfolio, fetchAudricHistory, fetchAudricPortfolio, fetchAvailableRewards, fetchBalance, fetchHealthFactor, fetchPositions, fetchProtocolStats, fetchRates, fetchSavings, fetchTokenPrices, fetchWalletCoins, findTool, getAudricApiBase, getDefaultTools, getDefiCacheStore, getFetchLock, getMcpManager, getModifiableFields, getToolFlags, getWalletAddress, getWalletCacheStore, guardArtifactPreview, guardStaleData, hasNaviMcp, healthCheckTool, loadRecipes, microcompact, mppServicesTool, parseMcpJson, parseRecipe, parseSSE, payApiTool, portfolioAnalysisTool, protocolDeepDiveTool, ratesInfoTool, registerEngineTools, renderCanvasTool, repayDebtTool, requireAgent, resetDefiCacheStore, resetFetchLock, resetWalletCacheStore, resolvePermissionTier, resolveUsdValue, runGuards, runTools, saveContactTool, saveDepositTool, savingsInfoTool, sendTransferTool, serializeSSE, setDefiCacheStore, setFetchLock, setWalletCacheStore, spendingAnalyticsTool, swapExecuteTool, swapQuoteTool, tokenPricesTool, toolNameToOperation, toolsToDefinitions, transactionHistoryTool, transformBalance, transformHealthFactor, transformPositions, transformRates, transformRewards, transformSavings, updateGuardStateAfterToolResult, validateHistory, voloStakeTool, voloStatsTool, voloUnstakeTool, webSearchTool, withdrawTool, yieldSummaryTool };