@t2000/engine 0.46.10 → 0.46.11
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 +55 -5
- package/dist/index.js +81 -11
- package/dist/index.js.map +1 -1
- package/package.json +2 -2
package/dist/index.d.ts
CHANGED
|
@@ -219,6 +219,15 @@ interface GuardConfig {
|
|
|
219
219
|
costWarning?: boolean;
|
|
220
220
|
retryProtection?: boolean;
|
|
221
221
|
inputValidation?: boolean;
|
|
222
|
+
/**
|
|
223
|
+
* Root-cause guard for "LLM types a recipient address from memory and
|
|
224
|
+
* loses funds to a wrong-but-valid address". When enabled (default),
|
|
225
|
+
* `send_transfer.to` is rejected unless the address can be sourced
|
|
226
|
+
* from a saved contact, the user's own wallet, or the user's recent
|
|
227
|
+
* messages. Set to `false` only if the host has its own equivalent
|
|
228
|
+
* upstream guard (e.g. an off-process verifier).
|
|
229
|
+
*/
|
|
230
|
+
addressSource?: boolean;
|
|
222
231
|
}
|
|
223
232
|
declare const DEFAULT_GUARD_CONFIG: GuardConfig;
|
|
224
233
|
declare class BalanceTracker {
|
|
@@ -249,6 +258,7 @@ declare function createGuardRunnerState(): GuardRunnerState;
|
|
|
249
258
|
declare function runGuards(tool: Tool, call: PendingToolCall, state: GuardRunnerState, config: GuardConfig, conversationContext: {
|
|
250
259
|
fullText: string;
|
|
251
260
|
lastAssistantText: string;
|
|
261
|
+
recentUserText: string;
|
|
252
262
|
},
|
|
253
263
|
/**
|
|
254
264
|
* [v1.4 Item 4] Optional per-guard observation hook. Fired exactly
|
|
@@ -256,7 +266,21 @@ declare function runGuards(tool: Tool, call: PendingToolCall, state: GuardRunner
|
|
|
256
266
|
* up in `events`/`injections`/`block`). Errors thrown by the host
|
|
257
267
|
* are caught so a misbehaving collector can't break tool execution.
|
|
258
268
|
*/
|
|
259
|
-
onGuardFired?: (guard: GuardMetric) => void
|
|
269
|
+
onGuardFired?: (guard: GuardMetric) => void,
|
|
270
|
+
/**
|
|
271
|
+
* Identity context for the address-source safety guard. The guard
|
|
272
|
+
* accepts `send_transfer.to` only when sourced from a saved contact,
|
|
273
|
+
* the user's own wallet, or the user's recent messages — preventing
|
|
274
|
+
* the LLM from typing addresses from memory and shipping funds to a
|
|
275
|
+
* wrong-but-syntactically-valid recipient.
|
|
276
|
+
*/
|
|
277
|
+
identity?: {
|
|
278
|
+
contacts?: ReadonlyArray<{
|
|
279
|
+
name: string;
|
|
280
|
+
address: string;
|
|
281
|
+
}>;
|
|
282
|
+
walletAddress?: string;
|
|
283
|
+
}): GuardCheckResult;
|
|
260
284
|
declare function updateGuardStateAfterToolResult(toolName: string, tool: Tool | undefined, input: unknown, result: unknown, isError: boolean, state: GuardRunnerState): void;
|
|
261
285
|
declare function extractConversationText(messages: Array<{
|
|
262
286
|
role: string;
|
|
@@ -264,6 +288,7 @@ declare function extractConversationText(messages: Array<{
|
|
|
264
288
|
}>): {
|
|
265
289
|
fullText: string;
|
|
266
290
|
lastAssistantText: string;
|
|
291
|
+
recentUserText: string;
|
|
267
292
|
};
|
|
268
293
|
|
|
269
294
|
/**
|
|
@@ -366,8 +391,20 @@ declare const PERMISSION_PRESETS: {
|
|
|
366
391
|
* `config.autonomousDailyLimit`, an otherwise-`auto` tier is downgraded to
|
|
367
392
|
* `confirm`. This is the runtime guard for the daily autonomous spend cap.
|
|
368
393
|
* Tiers above `auto` are returned unchanged.
|
|
394
|
+
*
|
|
395
|
+
* Send-safety rule: when `operation === 'send'` and the destination
|
|
396
|
+
* address is a raw `0x...` (i.e. NOT one of the user's saved contacts),
|
|
397
|
+
* an otherwise-`auto` tier is downgraded to `confirm` regardless of
|
|
398
|
+
* amount. This bounds the "LLM/user typo silently ships funds" failure
|
|
399
|
+
* mode to a single confirmation per recipient — once saved as a contact,
|
|
400
|
+
* subsequent sends to the same address auto-approve under tier as normal.
|
|
369
401
|
*/
|
|
370
|
-
declare function resolvePermissionTier(operation: string, amountUsd: number, config: UserPermissionConfig, sessionSpendUsd?: number
|
|
402
|
+
declare function resolvePermissionTier(operation: string, amountUsd: number, config: UserPermissionConfig, sessionSpendUsd?: number, sendContext?: {
|
|
403
|
+
to?: string;
|
|
404
|
+
contacts?: ReadonlyArray<{
|
|
405
|
+
address: string;
|
|
406
|
+
}>;
|
|
407
|
+
}): 'auto' | 'confirm' | 'explicit';
|
|
371
408
|
declare function toolNameToOperation(toolName: string): PermissionOperation | undefined;
|
|
372
409
|
/**
|
|
373
410
|
* Resolve the USD value of a tool call from its inputs.
|
|
@@ -698,6 +735,18 @@ interface EngineConfig {
|
|
|
698
735
|
priceCache?: Map<string, number>;
|
|
699
736
|
/** Per-user permission config for USD-threshold write tool gating (B.4). */
|
|
700
737
|
permissionConfig?: UserPermissionConfig;
|
|
738
|
+
/**
|
|
739
|
+
* Saved contacts for the current user. Used by `guardAddressSource`
|
|
740
|
+
* (a saved contact's address is considered a trusted source for
|
|
741
|
+
* `send_transfer.to`) and by `permission-rules.resolvePermissionTier`
|
|
742
|
+
* (sends to non-contact addresses always require confirmation,
|
|
743
|
+
* regardless of amount). Hosts SHOULD also surface these in the
|
|
744
|
+
* dynamic system prompt block so the LLM can resolve "send to <name>".
|
|
745
|
+
*/
|
|
746
|
+
contacts?: ReadonlyArray<{
|
|
747
|
+
name: string;
|
|
748
|
+
address: string;
|
|
749
|
+
}>;
|
|
701
750
|
/**
|
|
702
751
|
* [v1.4] Cumulative USD already auto-executed in the current session.
|
|
703
752
|
* Forwarded to `ToolContext` and consulted by `resolvePermissionTier` to
|
|
@@ -880,6 +929,7 @@ declare class QueryEngine {
|
|
|
880
929
|
private readonly contextSummarizer;
|
|
881
930
|
private readonly priceCache;
|
|
882
931
|
private readonly permissionConfig;
|
|
932
|
+
private readonly contacts;
|
|
883
933
|
private readonly sessionSpendUsd;
|
|
884
934
|
private readonly onAutoExecuted;
|
|
885
935
|
private readonly onGuardFired;
|
|
@@ -1810,8 +1860,8 @@ declare const withdrawTool: Tool<{
|
|
|
1810
1860
|
}>;
|
|
1811
1861
|
|
|
1812
1862
|
declare const sendTransferTool: Tool<{
|
|
1813
|
-
amount: number;
|
|
1814
1863
|
to: string;
|
|
1864
|
+
amount: number;
|
|
1815
1865
|
memo?: string | undefined;
|
|
1816
1866
|
}, {
|
|
1817
1867
|
success: boolean;
|
|
@@ -1879,8 +1929,8 @@ declare const mppServicesTool: Tool<{
|
|
|
1879
1929
|
}, Record<string, unknown>>;
|
|
1880
1930
|
|
|
1881
1931
|
declare const swapExecuteTool: Tool<{
|
|
1882
|
-
amount: number;
|
|
1883
1932
|
to: string;
|
|
1933
|
+
amount: number;
|
|
1884
1934
|
from: string;
|
|
1885
1935
|
byAmountIn?: boolean | undefined;
|
|
1886
1936
|
slippage?: number | undefined;
|
|
@@ -1896,8 +1946,8 @@ declare const swapExecuteTool: Tool<{
|
|
|
1896
1946
|
}>;
|
|
1897
1947
|
|
|
1898
1948
|
declare const swapQuoteTool: Tool<{
|
|
1899
|
-
amount: number;
|
|
1900
1949
|
to: string;
|
|
1950
|
+
amount: number;
|
|
1901
1951
|
from: string;
|
|
1902
1952
|
byAmountIn?: boolean | undefined;
|
|
1903
1953
|
}, _t2000_sdk.SwapQuoteResult>;
|
package/dist/index.js
CHANGED
|
@@ -3609,7 +3609,8 @@ var DEFAULT_GUARD_CONFIG = {
|
|
|
3609
3609
|
artifactPreview: true,
|
|
3610
3610
|
costWarning: true,
|
|
3611
3611
|
retryProtection: true,
|
|
3612
|
-
inputValidation: true
|
|
3612
|
+
inputValidation: true,
|
|
3613
|
+
addressSource: true
|
|
3613
3614
|
};
|
|
3614
3615
|
var BalanceTracker = class {
|
|
3615
3616
|
lastBalanceAt = 0;
|
|
@@ -3666,7 +3667,7 @@ function guardIrreversibility(tool, _call, conversationText) {
|
|
|
3666
3667
|
if (!tool.flags.irreversible) {
|
|
3667
3668
|
return { verdict: "pass", gate: "irreversibility", tier: "safety" };
|
|
3668
3669
|
}
|
|
3669
|
-
const hasPreview = /preview|here.s what|confirm
|
|
3670
|
+
const hasPreview = /preview|here.{0,2}s what|confirm.{0,200}send|looks? good/i.test(conversationText);
|
|
3670
3671
|
if (hasPreview) {
|
|
3671
3672
|
return { verdict: "pass", gate: "irreversibility", tier: "safety" };
|
|
3672
3673
|
}
|
|
@@ -3762,7 +3763,7 @@ function guardSlippage(tool, _call, lastAssistantText) {
|
|
|
3762
3763
|
if (tool.name !== "swap_execute") {
|
|
3763
3764
|
return { verdict: "pass", gate: "slippage_warning", tier: "financial" };
|
|
3764
3765
|
}
|
|
3765
|
-
const hasEstimate =
|
|
3766
|
+
const hasEstimate = /~?\$?\d[\d,]{0,30}(?:\.\d{1,10})?\s*(SUI|USDC|USDT|WETH)/i.test(lastAssistantText) || /approximately|≈|about|expect|receive/i.test(lastAssistantText);
|
|
3766
3767
|
if (hasEstimate) {
|
|
3767
3768
|
return { verdict: "pass", gate: "slippage_warning", tier: "financial" };
|
|
3768
3769
|
}
|
|
@@ -3788,6 +3789,41 @@ function guardCostWarning(tool, _call, conversationText) {
|
|
|
3788
3789
|
message: "This action has a monetary cost. Confirm the user is aware before proceeding."
|
|
3789
3790
|
};
|
|
3790
3791
|
}
|
|
3792
|
+
var SUI_ADDRESS_REGEX = /^0x[a-fA-F0-9]{64}$/;
|
|
3793
|
+
function normalizeAddress(addr) {
|
|
3794
|
+
return addr.trim().toLowerCase();
|
|
3795
|
+
}
|
|
3796
|
+
function guardAddressSource(tool, call, userText, contacts, walletAddress) {
|
|
3797
|
+
if (tool.name !== "send_transfer") {
|
|
3798
|
+
return { verdict: "pass", gate: "address_source", tier: "safety" };
|
|
3799
|
+
}
|
|
3800
|
+
const input = call.input;
|
|
3801
|
+
const rawTo = String(input.to ?? "");
|
|
3802
|
+
if (!rawTo) {
|
|
3803
|
+
return { verdict: "pass", gate: "address_source", tier: "safety" };
|
|
3804
|
+
}
|
|
3805
|
+
if (!SUI_ADDRESS_REGEX.test(rawTo)) {
|
|
3806
|
+
return { verdict: "pass", gate: "address_source", tier: "safety" };
|
|
3807
|
+
}
|
|
3808
|
+
const normalizedTo = normalizeAddress(rawTo);
|
|
3809
|
+
if (walletAddress && normalizeAddress(walletAddress) === normalizedTo) {
|
|
3810
|
+
return { verdict: "pass", gate: "address_source", tier: "safety" };
|
|
3811
|
+
}
|
|
3812
|
+
for (const c of contacts) {
|
|
3813
|
+
if (normalizeAddress(c.address) === normalizedTo) {
|
|
3814
|
+
return { verdict: "pass", gate: "address_source", tier: "safety" };
|
|
3815
|
+
}
|
|
3816
|
+
}
|
|
3817
|
+
if (userText.toLowerCase().includes(normalizedTo)) {
|
|
3818
|
+
return { verdict: "pass", gate: "address_source", tier: "safety" };
|
|
3819
|
+
}
|
|
3820
|
+
return {
|
|
3821
|
+
verdict: "block",
|
|
3822
|
+
gate: "address_source",
|
|
3823
|
+
tier: "safety",
|
|
3824
|
+
message: `Safety check failed: the recipient address "${rawTo}" was not provided by the user (no saved contact matches, address is not the user's own wallet, and it does not appear verbatim in the user's recent messages). For safety, addresses must be supplied directly by the user \u2014 never reconstructed from memory or partial recall. Ask the user to paste the destination address again exactly.`
|
|
3825
|
+
};
|
|
3826
|
+
}
|
|
3791
3827
|
function guardArtifactPreview(result) {
|
|
3792
3828
|
if (!result || typeof result !== "object") return null;
|
|
3793
3829
|
const r = result;
|
|
@@ -3815,7 +3851,7 @@ function createGuardRunnerState() {
|
|
|
3815
3851
|
lastHealthFactor: null
|
|
3816
3852
|
};
|
|
3817
3853
|
}
|
|
3818
|
-
function runGuards(tool, call, state, config, conversationContext, onGuardFired) {
|
|
3854
|
+
function runGuards(tool, call, state, config, conversationContext, onGuardFired, identity) {
|
|
3819
3855
|
const results = [];
|
|
3820
3856
|
const now = Date.now();
|
|
3821
3857
|
const fire = (verdict, tier, gate, hadInjection) => {
|
|
@@ -3856,6 +3892,17 @@ function runGuards(tool, call, state, config, conversationContext, onGuardFired)
|
|
|
3856
3892
|
if (config.retryProtection !== false) {
|
|
3857
3893
|
results.push(guardRetryProtection(tool, call, state.retryTracker));
|
|
3858
3894
|
}
|
|
3895
|
+
if (config.addressSource !== false) {
|
|
3896
|
+
results.push(
|
|
3897
|
+
guardAddressSource(
|
|
3898
|
+
tool,
|
|
3899
|
+
call,
|
|
3900
|
+
conversationContext.recentUserText,
|
|
3901
|
+
identity?.contacts ?? [],
|
|
3902
|
+
identity?.walletAddress
|
|
3903
|
+
)
|
|
3904
|
+
);
|
|
3905
|
+
}
|
|
3859
3906
|
if (config.irreversibility !== false) {
|
|
3860
3907
|
results.push(guardIrreversibility(tool, call, conversationContext.fullText));
|
|
3861
3908
|
}
|
|
@@ -3926,6 +3973,7 @@ function updateGuardStateAfterToolResult(toolName, tool, input, result, isError,
|
|
|
3926
3973
|
}
|
|
3927
3974
|
function extractConversationText(messages) {
|
|
3928
3975
|
const textParts = [];
|
|
3976
|
+
const userParts = [];
|
|
3929
3977
|
let lastAssistantText = "";
|
|
3930
3978
|
for (const msg of messages) {
|
|
3931
3979
|
if (!Array.isArray(msg.content)) continue;
|
|
@@ -3934,13 +3982,19 @@ function extractConversationText(messages) {
|
|
|
3934
3982
|
textParts.push(block.text);
|
|
3935
3983
|
if (msg.role === "assistant") {
|
|
3936
3984
|
lastAssistantText = block.text;
|
|
3985
|
+
} else if (msg.role === "user") {
|
|
3986
|
+
userParts.push(block.text);
|
|
3937
3987
|
}
|
|
3938
3988
|
}
|
|
3939
3989
|
}
|
|
3940
3990
|
}
|
|
3991
|
+
const recentUserParts = userParts.slice(-10);
|
|
3992
|
+
const MAX_REGEX_INPUT = 16 * 1024;
|
|
3993
|
+
const cap = (s) => s.length <= MAX_REGEX_INPUT ? s : s.slice(-MAX_REGEX_INPUT);
|
|
3941
3994
|
return {
|
|
3942
|
-
fullText: textParts.join("\n"),
|
|
3943
|
-
lastAssistantText
|
|
3995
|
+
fullText: cap(textParts.join("\n")),
|
|
3996
|
+
lastAssistantText: cap(lastAssistantText),
|
|
3997
|
+
recentUserText: cap(recentUserParts.join("\n"))
|
|
3944
3998
|
};
|
|
3945
3999
|
}
|
|
3946
4000
|
|
|
@@ -4240,7 +4294,12 @@ var PERMISSION_PRESETS = {
|
|
|
4240
4294
|
]
|
|
4241
4295
|
}
|
|
4242
4296
|
};
|
|
4243
|
-
function
|
|
4297
|
+
function isKnownContactAddress(to, contacts) {
|
|
4298
|
+
if (!to) return false;
|
|
4299
|
+
const normalized = to.trim().toLowerCase();
|
|
4300
|
+
return contacts.some((c) => c.address.trim().toLowerCase() === normalized);
|
|
4301
|
+
}
|
|
4302
|
+
function resolvePermissionTier(operation, amountUsd, config, sessionSpendUsd, sendContext) {
|
|
4244
4303
|
const rule = config.rules.find((r) => r.operation === operation);
|
|
4245
4304
|
const autoBelow = rule?.autoBelow ?? config.globalAutoBelow;
|
|
4246
4305
|
const confirmBetween = rule?.confirmBetween ?? 1e3;
|
|
@@ -4249,7 +4308,10 @@ function resolvePermissionTier(operation, amountUsd, config, sessionSpendUsd) {
|
|
|
4249
4308
|
else if (amountUsd < confirmBetween) tier = "confirm";
|
|
4250
4309
|
else tier = "explicit";
|
|
4251
4310
|
if (tier === "auto" && typeof sessionSpendUsd === "number" && sessionSpendUsd + amountUsd > config.autonomousDailyLimit) {
|
|
4252
|
-
|
|
4311
|
+
tier = "confirm";
|
|
4312
|
+
}
|
|
4313
|
+
if (tier === "auto" && operation === "send" && sendContext?.to && !isKnownContactAddress(sendContext.to, sendContext.contacts ?? [])) {
|
|
4314
|
+
tier = "confirm";
|
|
4253
4315
|
}
|
|
4254
4316
|
return tier;
|
|
4255
4317
|
}
|
|
@@ -4497,6 +4559,9 @@ var QueryEngine = class {
|
|
|
4497
4559
|
contextSummarizer;
|
|
4498
4560
|
priceCache;
|
|
4499
4561
|
permissionConfig;
|
|
4562
|
+
// Saved contacts — consulted by `guardAddressSource` and the permission
|
|
4563
|
+
// tier resolver (sends to non-contact addresses always require confirm).
|
|
4564
|
+
contacts;
|
|
4500
4565
|
// [v1.4] Session-scoped autonomous spend tracking.
|
|
4501
4566
|
sessionSpendUsd;
|
|
4502
4567
|
onAutoExecuted;
|
|
@@ -4545,6 +4610,7 @@ var QueryEngine = class {
|
|
|
4545
4610
|
this.contextSummarizer = config.contextSummarizer;
|
|
4546
4611
|
this.priceCache = config.priceCache;
|
|
4547
4612
|
this.permissionConfig = config.permissionConfig;
|
|
4613
|
+
this.contacts = config.contacts ?? [];
|
|
4548
4614
|
this.sessionSpendUsd = config.sessionSpendUsd;
|
|
4549
4615
|
this.onAutoExecuted = config.onAutoExecuted;
|
|
4550
4616
|
this.onGuardFired = config.onGuardFired;
|
|
@@ -5064,11 +5130,13 @@ ${recipeCtx}`;
|
|
|
5064
5130
|
const operation = toolNameToOperation(call.name);
|
|
5065
5131
|
if (operation) {
|
|
5066
5132
|
const usdValue = resolveUsdValue(call.name, call.input, context.priceCache);
|
|
5133
|
+
const callInput = call.input;
|
|
5067
5134
|
const tier = resolvePermissionTier(
|
|
5068
5135
|
operation,
|
|
5069
5136
|
usdValue,
|
|
5070
5137
|
context.permissionConfig,
|
|
5071
|
-
context.sessionSpendUsd
|
|
5138
|
+
context.sessionSpendUsd,
|
|
5139
|
+
operation === "send" ? { to: typeof callInput.to === "string" ? callInput.to : void 0, contacts: this.contacts } : void 0
|
|
5072
5140
|
);
|
|
5073
5141
|
return tier !== "auto";
|
|
5074
5142
|
}
|
|
@@ -5098,7 +5166,8 @@ ${recipeCtx}`;
|
|
|
5098
5166
|
this.guardState,
|
|
5099
5167
|
this.guardConfig,
|
|
5100
5168
|
convCtx,
|
|
5101
|
-
this.onGuardFired
|
|
5169
|
+
this.onGuardFired,
|
|
5170
|
+
{ contacts: this.contacts, walletAddress: this.walletAddress }
|
|
5102
5171
|
);
|
|
5103
5172
|
this.guardEvents.push(...check.events);
|
|
5104
5173
|
if (check.blocked) {
|
|
@@ -5228,7 +5297,8 @@ ${recipeCtx}`;
|
|
|
5228
5297
|
this.guardState,
|
|
5229
5298
|
this.guardConfig,
|
|
5230
5299
|
convCtx,
|
|
5231
|
-
this.onGuardFired
|
|
5300
|
+
this.onGuardFired,
|
|
5301
|
+
{ contacts: this.contacts, walletAddress: this.walletAddress }
|
|
5232
5302
|
);
|
|
5233
5303
|
this.guardEvents.push(...check.events);
|
|
5234
5304
|
if (check.blocked) {
|