@t2000/engine 1.24.11 → 1.24.12
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/index.d.ts +19 -55
- package/dist/index.js +158 -160
- package/dist/index.js.map +1 -1
- package/package.json +2 -2
package/dist/index.d.ts
CHANGED
|
@@ -53,25 +53,6 @@ declare function extractAllProactiveMarkers(text: string): Array<{
|
|
|
53
53
|
subjectKey: string;
|
|
54
54
|
}>;
|
|
55
55
|
|
|
56
|
-
type PostWritePollOutcome = 'detected_change' | 'ceiling' | 'aborted' | 'fallback_no_baseline' | 'fallback_no_address' | 'fallback_no_rpc';
|
|
57
|
-
interface PostWritePollResult {
|
|
58
|
-
outcome: PostWritePollOutcome;
|
|
59
|
-
/** Number of poll attempts made (0 if fallback fired before polling). */
|
|
60
|
-
attempts: number;
|
|
61
|
-
/** Wall-clock ms from `pollForIndexerCatchup` entry to return. */
|
|
62
|
-
resolvedAtMs: number;
|
|
63
|
-
}
|
|
64
|
-
interface PostWritePollOptions {
|
|
65
|
-
suiRpcUrl: string | undefined;
|
|
66
|
-
address: string | undefined;
|
|
67
|
-
/** Hard ceiling on total wait time. Default 1500ms (matches old fixed sleep). */
|
|
68
|
-
ceilingMs: number;
|
|
69
|
-
/** Wait between poll attempts. Default 250ms. */
|
|
70
|
-
pollIntervalMs: number;
|
|
71
|
-
signal: AbortSignal;
|
|
72
|
-
}
|
|
73
|
-
declare function pollForIndexerCatchup(options: PostWritePollOptions): Promise<PostWritePollResult>;
|
|
74
|
-
|
|
75
56
|
/** Rough token count for a message array. */
|
|
76
57
|
declare function estimateTokens(messages: Message[]): number;
|
|
77
58
|
interface CompactOptions {
|
|
@@ -1736,41 +1717,6 @@ interface EngineConfig {
|
|
|
1736
1717
|
* Omit (undefined / empty map) to disable post-write refresh entirely.
|
|
1737
1718
|
*/
|
|
1738
1719
|
postWriteRefresh?: Record<string, string[]>;
|
|
1739
|
-
/**
|
|
1740
|
-
* [SPEC 19 Phase B / 2026-05-09] Pre-warmed indexer-catchup poll Promise.
|
|
1741
|
-
*
|
|
1742
|
-
* Hosts that know the wallet address + Sui RPC URL the moment a resume
|
|
1743
|
-
* request lands (e.g. `app/api/engine/resume/route.ts`, where both are
|
|
1744
|
-
* available immediately after JWT validation) can fire
|
|
1745
|
-
* `pollForIndexerCatchup` in parallel with `createEngine` and pass the
|
|
1746
|
-
* returned Promise here. The engine awaits this Promise inside
|
|
1747
|
-
* `runPostWriteRefresh` instead of starting a fresh poll, eliminating
|
|
1748
|
-
* up to ~1.5s of serial wait when engine boot wall-clock ≥ poll
|
|
1749
|
-
* wall-clock.
|
|
1750
|
-
*
|
|
1751
|
-
* Behavior:
|
|
1752
|
-
* - Promise resolves before refresh starts → instant pass-through
|
|
1753
|
-
* (poll already done during boot). This is the typical case.
|
|
1754
|
-
* - Promise still pending when refresh starts → engine awaits it.
|
|
1755
|
-
* Net effect: same as pre-Phase-B fresh-poll behavior.
|
|
1756
|
-
* - Promise undefined → engine starts a fresh poll (pre-Phase-B path).
|
|
1757
|
-
* - Promise rejects → engine logs warn + falls back to fresh poll
|
|
1758
|
-
* (correctness preserved; same as `outcome: 'fallback_*'`).
|
|
1759
|
-
*
|
|
1760
|
-
* Telemetry: `engine.pwr.sleep_ms` carries `source` tag = `'pre-warmed' | 'engine'`
|
|
1761
|
-
* so the host can verify the parallelization is shaving wall-clock.
|
|
1762
|
-
* On the pre-warmed path, the `outcome` + `attempts` tags are inherited
|
|
1763
|
-
* from the host's poll result — same shape, just resolved earlier.
|
|
1764
|
-
*
|
|
1765
|
-
* Safe because:
|
|
1766
|
-
* - Poll baseline is captured via Sui RPC `getAllBalances` and is
|
|
1767
|
-
* independent of any BlockVision cache state. Pre-firing the poll
|
|
1768
|
-
* before engine boot does NOT race the engine's cache invalidation.
|
|
1769
|
-
* - The poll's correctness contract (never proceed until indexer
|
|
1770
|
-
* reflects the write) is preserved — it just runs on a different
|
|
1771
|
-
* timeline.
|
|
1772
|
-
*/
|
|
1773
|
-
indexerCatchupPromise?: Promise<PostWritePollResult>;
|
|
1774
1720
|
}
|
|
1775
1721
|
interface LLMProvider {
|
|
1776
1722
|
chat(params: ChatParams): AsyncGenerator<ProviderEvent>;
|
|
@@ -1921,7 +1867,6 @@ declare class QueryEngine {
|
|
|
1921
1867
|
private readonly blockvisionApiKey;
|
|
1922
1868
|
private readonly portfolioCache;
|
|
1923
1869
|
private readonly postWriteRefresh;
|
|
1924
|
-
private readonly indexerCatchupPromise;
|
|
1925
1870
|
private matchedRecipe;
|
|
1926
1871
|
private messages;
|
|
1927
1872
|
private abortController;
|
|
@@ -2071,6 +2016,25 @@ declare class QueryEngine {
|
|
|
2071
2016
|
*/
|
|
2072
2017
|
declare function validateHistory(messages: Message[]): Message[];
|
|
2073
2018
|
|
|
2019
|
+
type PostWritePollOutcome = 'detected_change' | 'ceiling' | 'aborted' | 'fallback_no_baseline' | 'fallback_no_address' | 'fallback_no_rpc';
|
|
2020
|
+
interface PostWritePollResult {
|
|
2021
|
+
outcome: PostWritePollOutcome;
|
|
2022
|
+
/** Number of poll attempts made (0 if fallback fired before polling). */
|
|
2023
|
+
attempts: number;
|
|
2024
|
+
/** Wall-clock ms from `pollForIndexerCatchup` entry to return. */
|
|
2025
|
+
resolvedAtMs: number;
|
|
2026
|
+
}
|
|
2027
|
+
interface PostWritePollOptions {
|
|
2028
|
+
suiRpcUrl: string | undefined;
|
|
2029
|
+
address: string | undefined;
|
|
2030
|
+
/** Hard ceiling on total wait time. Default 1500ms (matches old fixed sleep). */
|
|
2031
|
+
ceilingMs: number;
|
|
2032
|
+
/** Wait between poll attempts. Default 250ms. */
|
|
2033
|
+
pollIntervalMs: number;
|
|
2034
|
+
signal: AbortSignal;
|
|
2035
|
+
}
|
|
2036
|
+
declare function pollForIndexerCatchup(options: PostWritePollOptions): Promise<PostWritePollResult>;
|
|
2037
|
+
|
|
2074
2038
|
/**
|
|
2075
2039
|
* SPEC 7 v0.3 Quote-Refresh ReviewCard — per-tool result freshness budgets.
|
|
2076
2040
|
*
|
package/dist/index.js
CHANGED
|
@@ -5786,120 +5786,6 @@ Only offer to execute actions you have tools for. If you retrieved a quote, data
|
|
|
5786
5786
|
- Cap: at most ONE proactive block per turn.
|
|
5787
5787
|
- Skip proactive blocks when nothing notable changed since the last turn, when the user is mid-flow on something else, or when you'd just be restating the financial-context block. Quality over quantity \u2014 a block ignored is worse than no block.`;
|
|
5788
5788
|
|
|
5789
|
-
// src/post-write-poll.ts
|
|
5790
|
-
async function pollForIndexerCatchup(options) {
|
|
5791
|
-
const { suiRpcUrl, address, ceilingMs, pollIntervalMs, signal } = options;
|
|
5792
|
-
const start = Date.now();
|
|
5793
|
-
if (!suiRpcUrl) {
|
|
5794
|
-
await sleepWithFallback(ceilingMs, signal);
|
|
5795
|
-
return {
|
|
5796
|
-
outcome: "fallback_no_rpc",
|
|
5797
|
-
attempts: 0,
|
|
5798
|
-
resolvedAtMs: Date.now() - start
|
|
5799
|
-
};
|
|
5800
|
-
}
|
|
5801
|
-
if (!address) {
|
|
5802
|
-
await sleepWithFallback(ceilingMs, signal);
|
|
5803
|
-
return {
|
|
5804
|
-
outcome: "fallback_no_address",
|
|
5805
|
-
attempts: 0,
|
|
5806
|
-
resolvedAtMs: Date.now() - start
|
|
5807
|
-
};
|
|
5808
|
-
}
|
|
5809
|
-
let baseline;
|
|
5810
|
-
try {
|
|
5811
|
-
const coins = await fetchWalletCoins(address, suiRpcUrl);
|
|
5812
|
-
baseline = new Map(
|
|
5813
|
-
coins.map((c) => [c.coinType, BigInt(c.totalBalance)])
|
|
5814
|
-
);
|
|
5815
|
-
} catch (err) {
|
|
5816
|
-
console.warn(
|
|
5817
|
-
"[post-write-poll] baseline fetch failed; falling back to fixed sleep:",
|
|
5818
|
-
err
|
|
5819
|
-
);
|
|
5820
|
-
await sleepWithFallback(ceilingMs, signal);
|
|
5821
|
-
return {
|
|
5822
|
-
outcome: "fallback_no_baseline",
|
|
5823
|
-
attempts: 0,
|
|
5824
|
-
resolvedAtMs: Date.now() - start
|
|
5825
|
-
};
|
|
5826
|
-
}
|
|
5827
|
-
const maxAttempts = Math.max(1, Math.floor(ceilingMs / pollIntervalMs));
|
|
5828
|
-
for (let attempt = 1; attempt <= maxAttempts; attempt++) {
|
|
5829
|
-
if (signal.aborted) {
|
|
5830
|
-
return {
|
|
5831
|
-
outcome: "aborted",
|
|
5832
|
-
attempts: attempt - 1,
|
|
5833
|
-
resolvedAtMs: Date.now() - start
|
|
5834
|
-
};
|
|
5835
|
-
}
|
|
5836
|
-
await new Promise((resolve) => {
|
|
5837
|
-
const t = setTimeout(resolve, pollIntervalMs);
|
|
5838
|
-
signal.addEventListener(
|
|
5839
|
-
"abort",
|
|
5840
|
-
() => {
|
|
5841
|
-
clearTimeout(t);
|
|
5842
|
-
resolve();
|
|
5843
|
-
},
|
|
5844
|
-
{ once: true }
|
|
5845
|
-
);
|
|
5846
|
-
});
|
|
5847
|
-
if (signal.aborted) {
|
|
5848
|
-
return {
|
|
5849
|
-
outcome: "aborted",
|
|
5850
|
-
attempts: attempt,
|
|
5851
|
-
resolvedAtMs: Date.now() - start
|
|
5852
|
-
};
|
|
5853
|
-
}
|
|
5854
|
-
let current;
|
|
5855
|
-
try {
|
|
5856
|
-
const coins = await fetchWalletCoins(address, suiRpcUrl);
|
|
5857
|
-
current = new Map(
|
|
5858
|
-
coins.map((c) => [c.coinType, BigInt(c.totalBalance)])
|
|
5859
|
-
);
|
|
5860
|
-
} catch (err) {
|
|
5861
|
-
console.warn(
|
|
5862
|
-
`[post-write-poll] poll attempt ${attempt} failed; continuing:`,
|
|
5863
|
-
err
|
|
5864
|
-
);
|
|
5865
|
-
continue;
|
|
5866
|
-
}
|
|
5867
|
-
if (balancesDiffer(baseline, current)) {
|
|
5868
|
-
return {
|
|
5869
|
-
outcome: "detected_change",
|
|
5870
|
-
attempts: attempt,
|
|
5871
|
-
resolvedAtMs: Date.now() - start
|
|
5872
|
-
};
|
|
5873
|
-
}
|
|
5874
|
-
}
|
|
5875
|
-
return {
|
|
5876
|
-
outcome: "ceiling",
|
|
5877
|
-
attempts: maxAttempts,
|
|
5878
|
-
resolvedAtMs: Date.now() - start
|
|
5879
|
-
};
|
|
5880
|
-
}
|
|
5881
|
-
function balancesDiffer(a, b) {
|
|
5882
|
-
if (a.size !== b.size) return true;
|
|
5883
|
-
for (const [coinType, balance] of a) {
|
|
5884
|
-
if (b.get(coinType) !== balance) return true;
|
|
5885
|
-
}
|
|
5886
|
-
return false;
|
|
5887
|
-
}
|
|
5888
|
-
async function sleepWithFallback(ms, signal) {
|
|
5889
|
-
if (signal.aborted) return;
|
|
5890
|
-
await new Promise((resolve) => {
|
|
5891
|
-
const t = setTimeout(resolve, ms);
|
|
5892
|
-
signal.addEventListener(
|
|
5893
|
-
"abort",
|
|
5894
|
-
() => {
|
|
5895
|
-
clearTimeout(t);
|
|
5896
|
-
resolve();
|
|
5897
|
-
},
|
|
5898
|
-
{ once: true }
|
|
5899
|
-
);
|
|
5900
|
-
});
|
|
5901
|
-
}
|
|
5902
|
-
|
|
5903
5789
|
// src/proactive-marker.ts
|
|
5904
5790
|
var VALID_TYPES = /* @__PURE__ */ new Set([
|
|
5905
5791
|
"idle_balance",
|
|
@@ -7506,12 +7392,6 @@ var QueryEngine = class {
|
|
|
7506
7392
|
// [v1.5] See `EngineConfig.postWriteRefresh` — drives the post-write
|
|
7507
7393
|
// synthetic read injection in `resumeWithToolResult`.
|
|
7508
7394
|
postWriteRefresh;
|
|
7509
|
-
// [SPEC 19 Phase B / 2026-05-09] Pre-warmed indexer-catchup poll Promise.
|
|
7510
|
-
// When provided by the host (resume route fires `pollForIndexerCatchup`
|
|
7511
|
-
// in parallel with `createEngine`), `runPostWriteRefresh` awaits this
|
|
7512
|
-
// instead of starting a fresh poll. See `EngineConfig.indexerCatchupPromise`
|
|
7513
|
-
// for the full design rationale.
|
|
7514
|
-
indexerCatchupPromise;
|
|
7515
7395
|
matchedRecipe = null;
|
|
7516
7396
|
messages = [];
|
|
7517
7397
|
abortController = null;
|
|
@@ -7587,7 +7467,6 @@ var QueryEngine = class {
|
|
|
7587
7467
|
this.onAutoExecuted = config.onAutoExecuted;
|
|
7588
7468
|
this.onGuardFired = config.onGuardFired;
|
|
7589
7469
|
this.postWriteRefresh = config.postWriteRefresh;
|
|
7590
|
-
this.indexerCatchupPromise = config.indexerCatchupPromise;
|
|
7591
7470
|
this.blockvisionApiKey = config.blockvisionApiKey;
|
|
7592
7471
|
this.portfolioCache = config.portfolioCache;
|
|
7593
7472
|
this.tools = config.tools ?? (config.agent ? getDefaultTools() : []);
|
|
@@ -7977,6 +7856,10 @@ var QueryEngine = class {
|
|
|
7977
7856
|
blockvisionApiKey: this.blockvisionApiKey,
|
|
7978
7857
|
portfolioCache: this.portfolioCache
|
|
7979
7858
|
};
|
|
7859
|
+
const canSafetyNet = !!(this.walletAddress && this.suiRpcUrl);
|
|
7860
|
+
const safetyNetBaseline = canSafetyNet ? fetchWalletCoins(this.walletAddress, this.suiRpcUrl).then(
|
|
7861
|
+
(coins) => new Map(coins.map((c) => [c.coinType, BigInt(c.totalBalance)]))
|
|
7862
|
+
).catch(() => null) : null;
|
|
7980
7863
|
const cacheInvalidationStart = Date.now();
|
|
7981
7864
|
if (this.walletAddress) {
|
|
7982
7865
|
this.portfolioCache?.delete(this.walletAddress);
|
|
@@ -7987,45 +7870,10 @@ var QueryEngine = class {
|
|
|
7987
7870
|
Date.now() - cacheInvalidationStart,
|
|
7988
7871
|
{ has_wallet: this.walletAddress ? "1" : "0" }
|
|
7989
7872
|
);
|
|
7990
|
-
|
|
7991
|
-
|
|
7992
|
-
|
|
7993
|
-
|
|
7994
|
-
try {
|
|
7995
|
-
pollResult = await this.indexerCatchupPromise;
|
|
7996
|
-
sleepSource = "pre-warmed";
|
|
7997
|
-
} catch (err) {
|
|
7998
|
-
console.warn(
|
|
7999
|
-
"[engine.pwr] pre-warmed indexer catchup promise rejected; falling back to fresh poll:",
|
|
8000
|
-
err
|
|
8001
|
-
);
|
|
8002
|
-
pollResult = await pollForIndexerCatchup({
|
|
8003
|
-
suiRpcUrl: this.suiRpcUrl,
|
|
8004
|
-
address: this.walletAddress,
|
|
8005
|
-
ceilingMs: 1500,
|
|
8006
|
-
pollIntervalMs: 250,
|
|
8007
|
-
signal
|
|
8008
|
-
});
|
|
8009
|
-
}
|
|
8010
|
-
} else {
|
|
8011
|
-
pollResult = await pollForIndexerCatchup({
|
|
8012
|
-
suiRpcUrl: this.suiRpcUrl,
|
|
8013
|
-
address: this.walletAddress,
|
|
8014
|
-
ceilingMs: 1500,
|
|
8015
|
-
pollIntervalMs: 250,
|
|
8016
|
-
signal
|
|
8017
|
-
});
|
|
8018
|
-
}
|
|
8019
|
-
getTelemetrySink().histogram(
|
|
8020
|
-
"engine.pwr.sleep_ms",
|
|
8021
|
-
Date.now() - sleepStart,
|
|
8022
|
-
{
|
|
8023
|
-
aborted: signal.aborted ? "1" : "0",
|
|
8024
|
-
outcome: pollResult.outcome,
|
|
8025
|
-
attempts: String(pollResult.attempts),
|
|
8026
|
-
source: sleepSource
|
|
8027
|
-
}
|
|
8028
|
-
);
|
|
7873
|
+
getTelemetrySink().counter("engine.pwr.skipped_sleep_count", {
|
|
7874
|
+
has_wallet: this.walletAddress ? "1" : "0",
|
|
7875
|
+
can_safety_net: canSafetyNet ? "1" : "0"
|
|
7876
|
+
});
|
|
8029
7877
|
if (signal.aborted) return;
|
|
8030
7878
|
const refreshStart = Date.now();
|
|
8031
7879
|
const idStem = `pwr_${action.toolUseId.slice(-6)}`;
|
|
@@ -8130,6 +7978,35 @@ var QueryEngine = class {
|
|
|
8130
7978
|
is_bundle: isBundle ? "1" : "0"
|
|
8131
7979
|
}
|
|
8132
7980
|
);
|
|
7981
|
+
if (safetyNetBaseline && canSafetyNet) {
|
|
7982
|
+
const wallet = this.walletAddress;
|
|
7983
|
+
const rpc = this.suiRpcUrl;
|
|
7984
|
+
void (async () => {
|
|
7985
|
+
try {
|
|
7986
|
+
const [baseline, current] = await Promise.all([
|
|
7987
|
+
safetyNetBaseline,
|
|
7988
|
+
fetchWalletCoins(wallet, rpc).then(
|
|
7989
|
+
(coins) => new Map(
|
|
7990
|
+
coins.map((c) => [c.coinType, BigInt(c.totalBalance)])
|
|
7991
|
+
)
|
|
7992
|
+
).catch(() => null)
|
|
7993
|
+
]);
|
|
7994
|
+
if (!baseline || !current) return;
|
|
7995
|
+
const stale = !walletStateChanged(baseline, current);
|
|
7996
|
+
if (stale) {
|
|
7997
|
+
getTelemetrySink().counter(
|
|
7998
|
+
"engine.pwr.observed_stale_balance_check",
|
|
7999
|
+
{
|
|
8000
|
+
stale: "1",
|
|
8001
|
+
is_bundle: isBundle ? "1" : "0",
|
|
8002
|
+
tool_count: String(refreshTools.length)
|
|
8003
|
+
}
|
|
8004
|
+
);
|
|
8005
|
+
}
|
|
8006
|
+
} catch {
|
|
8007
|
+
}
|
|
8008
|
+
})();
|
|
8009
|
+
}
|
|
8133
8010
|
}
|
|
8134
8011
|
interrupt() {
|
|
8135
8012
|
this.abortController?.abort();
|
|
@@ -9060,6 +8937,13 @@ ${recipeCtx}`;
|
|
|
9060
8937
|
}
|
|
9061
8938
|
}
|
|
9062
8939
|
};
|
|
8940
|
+
function walletStateChanged(before, after) {
|
|
8941
|
+
if (before.size !== after.size) return true;
|
|
8942
|
+
for (const [coinType, balance] of before) {
|
|
8943
|
+
if (after.get(coinType) !== balance) return true;
|
|
8944
|
+
}
|
|
8945
|
+
return false;
|
|
8946
|
+
}
|
|
9063
8947
|
function isCorruptHistoryError(err) {
|
|
9064
8948
|
const msg = err instanceof Error ? err.message : String(err);
|
|
9065
8949
|
return msg.includes("tool_use") && msg.includes("tool_result") || msg.includes("roles must alternate") || msg.includes("400") && msg.includes("invalid_request_error");
|
|
@@ -9169,6 +9053,120 @@ function harnessShapeForEffort(effort) {
|
|
|
9169
9053
|
return "max";
|
|
9170
9054
|
}
|
|
9171
9055
|
}
|
|
9056
|
+
|
|
9057
|
+
// src/post-write-poll.ts
|
|
9058
|
+
async function pollForIndexerCatchup(options) {
|
|
9059
|
+
const { suiRpcUrl, address, ceilingMs, pollIntervalMs, signal } = options;
|
|
9060
|
+
const start = Date.now();
|
|
9061
|
+
if (!suiRpcUrl) {
|
|
9062
|
+
await sleepWithFallback(ceilingMs, signal);
|
|
9063
|
+
return {
|
|
9064
|
+
outcome: "fallback_no_rpc",
|
|
9065
|
+
attempts: 0,
|
|
9066
|
+
resolvedAtMs: Date.now() - start
|
|
9067
|
+
};
|
|
9068
|
+
}
|
|
9069
|
+
if (!address) {
|
|
9070
|
+
await sleepWithFallback(ceilingMs, signal);
|
|
9071
|
+
return {
|
|
9072
|
+
outcome: "fallback_no_address",
|
|
9073
|
+
attempts: 0,
|
|
9074
|
+
resolvedAtMs: Date.now() - start
|
|
9075
|
+
};
|
|
9076
|
+
}
|
|
9077
|
+
let baseline;
|
|
9078
|
+
try {
|
|
9079
|
+
const coins = await fetchWalletCoins(address, suiRpcUrl);
|
|
9080
|
+
baseline = new Map(
|
|
9081
|
+
coins.map((c) => [c.coinType, BigInt(c.totalBalance)])
|
|
9082
|
+
);
|
|
9083
|
+
} catch (err) {
|
|
9084
|
+
console.warn(
|
|
9085
|
+
"[post-write-poll] baseline fetch failed; falling back to fixed sleep:",
|
|
9086
|
+
err
|
|
9087
|
+
);
|
|
9088
|
+
await sleepWithFallback(ceilingMs, signal);
|
|
9089
|
+
return {
|
|
9090
|
+
outcome: "fallback_no_baseline",
|
|
9091
|
+
attempts: 0,
|
|
9092
|
+
resolvedAtMs: Date.now() - start
|
|
9093
|
+
};
|
|
9094
|
+
}
|
|
9095
|
+
const maxAttempts = Math.max(1, Math.floor(ceilingMs / pollIntervalMs));
|
|
9096
|
+
for (let attempt = 1; attempt <= maxAttempts; attempt++) {
|
|
9097
|
+
if (signal.aborted) {
|
|
9098
|
+
return {
|
|
9099
|
+
outcome: "aborted",
|
|
9100
|
+
attempts: attempt - 1,
|
|
9101
|
+
resolvedAtMs: Date.now() - start
|
|
9102
|
+
};
|
|
9103
|
+
}
|
|
9104
|
+
await new Promise((resolve) => {
|
|
9105
|
+
const t = setTimeout(resolve, pollIntervalMs);
|
|
9106
|
+
signal.addEventListener(
|
|
9107
|
+
"abort",
|
|
9108
|
+
() => {
|
|
9109
|
+
clearTimeout(t);
|
|
9110
|
+
resolve();
|
|
9111
|
+
},
|
|
9112
|
+
{ once: true }
|
|
9113
|
+
);
|
|
9114
|
+
});
|
|
9115
|
+
if (signal.aborted) {
|
|
9116
|
+
return {
|
|
9117
|
+
outcome: "aborted",
|
|
9118
|
+
attempts: attempt,
|
|
9119
|
+
resolvedAtMs: Date.now() - start
|
|
9120
|
+
};
|
|
9121
|
+
}
|
|
9122
|
+
let current;
|
|
9123
|
+
try {
|
|
9124
|
+
const coins = await fetchWalletCoins(address, suiRpcUrl);
|
|
9125
|
+
current = new Map(
|
|
9126
|
+
coins.map((c) => [c.coinType, BigInt(c.totalBalance)])
|
|
9127
|
+
);
|
|
9128
|
+
} catch (err) {
|
|
9129
|
+
console.warn(
|
|
9130
|
+
`[post-write-poll] poll attempt ${attempt} failed; continuing:`,
|
|
9131
|
+
err
|
|
9132
|
+
);
|
|
9133
|
+
continue;
|
|
9134
|
+
}
|
|
9135
|
+
if (balancesDiffer(baseline, current)) {
|
|
9136
|
+
return {
|
|
9137
|
+
outcome: "detected_change",
|
|
9138
|
+
attempts: attempt,
|
|
9139
|
+
resolvedAtMs: Date.now() - start
|
|
9140
|
+
};
|
|
9141
|
+
}
|
|
9142
|
+
}
|
|
9143
|
+
return {
|
|
9144
|
+
outcome: "ceiling",
|
|
9145
|
+
attempts: maxAttempts,
|
|
9146
|
+
resolvedAtMs: Date.now() - start
|
|
9147
|
+
};
|
|
9148
|
+
}
|
|
9149
|
+
function balancesDiffer(a, b) {
|
|
9150
|
+
if (a.size !== b.size) return true;
|
|
9151
|
+
for (const [coinType, balance] of a) {
|
|
9152
|
+
if (b.get(coinType) !== balance) return true;
|
|
9153
|
+
}
|
|
9154
|
+
return false;
|
|
9155
|
+
}
|
|
9156
|
+
async function sleepWithFallback(ms, signal) {
|
|
9157
|
+
if (signal.aborted) return;
|
|
9158
|
+
await new Promise((resolve) => {
|
|
9159
|
+
const t = setTimeout(resolve, ms);
|
|
9160
|
+
signal.addEventListener(
|
|
9161
|
+
"abort",
|
|
9162
|
+
() => {
|
|
9163
|
+
clearTimeout(t);
|
|
9164
|
+
resolve();
|
|
9165
|
+
},
|
|
9166
|
+
{ once: true }
|
|
9167
|
+
);
|
|
9168
|
+
});
|
|
9169
|
+
}
|
|
9172
9170
|
async function regenerateBundle(engine, action) {
|
|
9173
9171
|
if (action.canRegenerate !== true) {
|
|
9174
9172
|
return {
|