@senpi/runtime 3.0.81 → 3.0.82
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/actions/decision-engine.d.ts +16 -0
- package/dist/actions/decision-engine.d.ts.map +1 -1
- package/dist/actions/decision-engine.js +137 -1
- package/dist/actions/decision-engine.js.map +1 -1
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +1 -5
- package/dist/index.js.map +1 -1
- package/dist/skills-manager/manifest.d.ts.map +1 -1
- package/dist/skills-manager/manifest.js +1 -0
- package/dist/skills-manager/manifest.js.map +1 -1
- package/dist/skills-manager/skills-manifest.json +1 -0
- package/dist/widgets/errors.d.ts +3 -15
- package/dist/widgets/errors.d.ts.map +1 -1
- package/dist/widgets/errors.js +3 -20
- package/dist/widgets/errors.js.map +1 -1
- package/dist/widgets/index.d.ts +3 -10
- package/dist/widgets/index.d.ts.map +1 -1
- package/dist/widgets/index.js +3 -6
- package/dist/widgets/index.js.map +1 -1
- package/dist/widgets/registry.d.ts.map +1 -1
- package/dist/widgets/registry.js +10 -3
- package/dist/widgets/registry.js.map +1 -1
- package/dist/widgets/tool.d.ts +6 -9
- package/dist/widgets/tool.d.ts.map +1 -1
- package/dist/widgets/tool.js +37 -147
- package/dist/widgets/tool.js.map +1 -1
- package/dist/widgets/types.d.ts +20 -58
- package/dist/widgets/types.d.ts.map +1 -1
- package/dist/widgets/types.js +4 -4
- package/dist/widgets/widgets/fund-user-wallet.d.ts +4 -5
- package/dist/widgets/widgets/fund-user-wallet.d.ts.map +1 -1
- package/dist/widgets/widgets/fund-user-wallet.js +27 -67
- package/dist/widgets/widgets/fund-user-wallet.js.map +1 -1
- package/openclaw.plugin.json +1 -4
- package/package.json +2 -3
- package/dist/widgets/strategy-lookup.d.ts +0 -39
- package/dist/widgets/strategy-lookup.d.ts.map +0 -1
- package/dist/widgets/strategy-lookup.js +0 -70
- package/dist/widgets/strategy-lookup.js.map +0 -1
- package/dist/widgets/widgets/fund-strategy-wallet.d.ts +0 -13
- package/dist/widgets/widgets/fund-strategy-wallet.d.ts.map +0 -1
- package/dist/widgets/widgets/fund-strategy-wallet.js +0 -139
- package/dist/widgets/widgets/fund-strategy-wallet.js.map +0 -1
- package/skills/senpi-widgets/SKILL.md +0 -167
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"fund-user-wallet.js","sourceRoot":"","sources":["../../../src/widgets/widgets/fund-user-wallet.ts"],"names":[],"mappings":"
|
|
1
|
+
{"version":3,"file":"fund-user-wallet.js","sourceRoot":"","sources":["../../../src/widgets/widgets/fund-user-wallet.ts"],"names":[],"mappings":"AAEA,MAAM,WAAW,GAAG,kBAAkB,CAAC;AACvC,MAAM,cAAc,GAAG,CAAC,CAAC;AAEzB;;;;;;;;GAQG;AACH,MAAM,CAAC,MAAM,kBAAkB,GAAe;IAC5C,UAAU,EAAE,WAAW;IACvB,aAAa,EAAE,cAAc;IAC7B,WAAW,EACT,sGAAsG;QACtG,0GAA0G;QAC1G,4BAA4B;IAC9B,QAAQ,EACN,oGAAoG;QACpG,+BAA+B;IACjC,MAAM,EACJ,uGAAuG;QACvG,iEAAiE;IACnE,QAAQ,EAAE;QACR,kFAAkF;QAClF,sCAAsC;QACtC,2FAA2F;QAC3F,4FAA4F;KAC7F;IACD,OAAO,EAAE,GAAG,EAAE,CAAC,CAAC;QACd,OAAO,EAAE;YACP,WAAW,EAAE,WAAW;YACxB,cAAc,EAAE,cAAc;YAC9B,MAAM,EAAE,OAAO;SAChB;QACD,IAAI,EACF,0DAA0D;YAC1D,wEAAwE;KAC3E,CAAC;CACH,CAAC"}
|
package/openclaw.plugin.json
CHANGED
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
"id": "runtime",
|
|
3
3
|
"name": "Senpi Runtime",
|
|
4
4
|
"description": "Senpi runtime plugin for OpenClaw. Orchestrates scanners, LLM-based trade decisions, and a DSL trailing stop-loss exit engine.",
|
|
5
|
-
"version": "3.0.
|
|
5
|
+
"version": "3.0.82",
|
|
6
6
|
"commandAliases": [
|
|
7
7
|
{
|
|
8
8
|
"name": "senpi"
|
|
@@ -19,9 +19,6 @@
|
|
|
19
19
|
"show_widget"
|
|
20
20
|
]
|
|
21
21
|
},
|
|
22
|
-
"skills": [
|
|
23
|
-
"skills/senpi-widgets"
|
|
24
|
-
],
|
|
25
22
|
"configSchema": {
|
|
26
23
|
"type": "object",
|
|
27
24
|
"additionalProperties": false,
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@senpi/runtime",
|
|
3
|
-
"version": "3.0.
|
|
3
|
+
"version": "3.0.82",
|
|
4
4
|
"description": "Senpi runtime plugin for OpenClaw",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"main": "dist/index.js",
|
|
@@ -8,8 +8,7 @@
|
|
|
8
8
|
"files": [
|
|
9
9
|
"dist/",
|
|
10
10
|
"openclaw.plugin.json",
|
|
11
|
-
"examples/"
|
|
12
|
-
"skills/senpi-widgets/"
|
|
11
|
+
"examples/"
|
|
13
12
|
],
|
|
14
13
|
"scripts": {
|
|
15
14
|
"setup:hooks": "sh -c 'git rev-parse --git-dir >/dev/null 2>&1 && git config core.hooksPath .githooks || true'",
|
|
@@ -1,39 +0,0 @@
|
|
|
1
|
-
import type { StrategyLookup } from "./types.js";
|
|
2
|
-
/**
|
|
3
|
-
* Bounds one validation lookup. The user is waiting on a card, and the tool
|
|
4
|
-
* result is what unblocks the turn, so a backend that never answers has to
|
|
5
|
-
* become an error the model can retry rather than a hung turn.
|
|
6
|
-
*/
|
|
7
|
-
export declare const STRATEGY_LOOKUP_TIMEOUT_MS = 10000;
|
|
8
|
-
export declare function isWalletAddress(value: string): boolean;
|
|
9
|
-
/** The `strategy_list` row fields this lookup reads. */
|
|
10
|
-
interface StrategyRow {
|
|
11
|
-
id: string;
|
|
12
|
-
strategyWalletAddress: string;
|
|
13
|
-
status: string;
|
|
14
|
-
strategyName: string;
|
|
15
|
-
}
|
|
16
|
-
/** The slice of `SenpiClient` this lookup needs. */
|
|
17
|
-
export interface StrategyLister {
|
|
18
|
-
listStrategies(filters?: {
|
|
19
|
-
strategyAddresses?: string[];
|
|
20
|
-
}, opts?: {
|
|
21
|
-
signal?: AbortSignal;
|
|
22
|
-
}): Promise<StrategyRow[]>;
|
|
23
|
-
}
|
|
24
|
-
/**
|
|
25
|
-
* Address → strategy, over `strategy_list` with an address filter.
|
|
26
|
-
*
|
|
27
|
-
* Deliberately not `SenpiClient.getStrategy`: that one falls back to the first
|
|
28
|
-
* row when no row matches the requested address, and drops the address from its
|
|
29
|
-
* result, so a backend that ignored the filter would resolve any address to
|
|
30
|
-
* some strategy. Both are fine for a details read and fatal for a gate, so this
|
|
31
|
-
* matches the address itself and never falls back.
|
|
32
|
-
*
|
|
33
|
-
* `listStrategies` already fails closed — an unrecognizable response throws
|
|
34
|
-
* instead of reading as an empty list — so an empty result here is a real
|
|
35
|
-
* "no strategy of yours holds that address" and is returned as `null`.
|
|
36
|
-
*/
|
|
37
|
-
export declare function createSenpiStrategyLookup(withClient: <T>(fn: (client: StrategyLister) => Promise<T>) => Promise<T>): StrategyLookup;
|
|
38
|
-
export {};
|
|
39
|
-
//# sourceMappingURL=strategy-lookup.d.ts.map
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"file":"strategy-lookup.d.ts","sourceRoot":"","sources":["../../src/widgets/strategy-lookup.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,cAAc,EAAiB,MAAM,YAAY,CAAC;AAEhE;;;;GAIG;AACH,eAAO,MAAM,0BAA0B,QAAS,CAAC;AAyBjD,wBAAgB,eAAe,CAAC,KAAK,EAAE,MAAM,GAAG,OAAO,CAEtD;AAED,wDAAwD;AACxD,UAAU,WAAW;IACnB,EAAE,EAAE,MAAM,CAAC;IACX,qBAAqB,EAAE,MAAM,CAAC;IAC9B,MAAM,EAAE,MAAM,CAAC;IACf,YAAY,EAAE,MAAM,CAAC;CACtB;AAED,oDAAoD;AACpD,MAAM,WAAW,cAAc;IAC7B,cAAc,CACZ,OAAO,CAAC,EAAE;QAAE,iBAAiB,CAAC,EAAE,MAAM,EAAE,CAAA;KAAE,EAC1C,IAAI,CAAC,EAAE;QAAE,MAAM,CAAC,EAAE,WAAW,CAAA;KAAE,GAC9B,OAAO,CAAC,WAAW,EAAE,CAAC,CAAC;CAC3B;AAED;;;;;;;;;;;;GAYG;AACH,wBAAgB,yBAAyB,CACvC,UAAU,EAAE,CAAC,CAAC,EAAE,EAAE,EAAE,CAAC,MAAM,EAAE,cAAc,KAAK,OAAO,CAAC,CAAC,CAAC,KAAK,OAAO,CAAC,CAAC,CAAC,GACxE,cAAc,CA+BhB"}
|
|
@@ -1,70 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* Bounds one validation lookup. The user is waiting on a card, and the tool
|
|
3
|
-
* result is what unblocks the turn, so a backend that never answers has to
|
|
4
|
-
* become an error the model can retry rather than a hung turn.
|
|
5
|
-
*/
|
|
6
|
-
export const STRATEGY_LOOKUP_TIMEOUT_MS = 10_000;
|
|
7
|
-
/**
|
|
8
|
-
* Bounds the whole lookup, not just the MCP request.
|
|
9
|
-
*
|
|
10
|
-
* The signal passed to `listStrategies` covers the request only. Reaching it
|
|
11
|
-
* means first connecting a client, and the MCP SDK gives `initialize` its own
|
|
12
|
-
* 60s default — so an unreachable backend held the agent turn for over a minute
|
|
13
|
-
* while this module advertised a ten-second bound. The race is what makes the
|
|
14
|
-
* bound true; the signal stays so the in-flight request is really cancelled.
|
|
15
|
-
*/
|
|
16
|
-
async function withDeadline(work, ms) {
|
|
17
|
-
let timer;
|
|
18
|
-
const deadline = new Promise((_resolve, reject) => {
|
|
19
|
-
timer = setTimeout(() => reject(new Error(`strategy lookup timed out after ${ms}ms`)), ms);
|
|
20
|
-
});
|
|
21
|
-
try {
|
|
22
|
-
return await Promise.race([work, deadline]);
|
|
23
|
-
}
|
|
24
|
-
finally {
|
|
25
|
-
clearTimeout(timer);
|
|
26
|
-
}
|
|
27
|
-
}
|
|
28
|
-
const WALLET_ADDRESS_RE = /^0x[0-9a-fA-F]{40}$/;
|
|
29
|
-
export function isWalletAddress(value) {
|
|
30
|
-
return WALLET_ADDRESS_RE.test(value);
|
|
31
|
-
}
|
|
32
|
-
/**
|
|
33
|
-
* Address → strategy, over `strategy_list` with an address filter.
|
|
34
|
-
*
|
|
35
|
-
* Deliberately not `SenpiClient.getStrategy`: that one falls back to the first
|
|
36
|
-
* row when no row matches the requested address, and drops the address from its
|
|
37
|
-
* result, so a backend that ignored the filter would resolve any address to
|
|
38
|
-
* some strategy. Both are fine for a details read and fatal for a gate, so this
|
|
39
|
-
* matches the address itself and never falls back.
|
|
40
|
-
*
|
|
41
|
-
* `listStrategies` already fails closed — an unrecognizable response throws
|
|
42
|
-
* instead of reading as an empty list — so an empty result here is a real
|
|
43
|
-
* "no strategy of yours holds that address" and is returned as `null`.
|
|
44
|
-
*/
|
|
45
|
-
export function createSenpiStrategyLookup(withClient) {
|
|
46
|
-
return {
|
|
47
|
-
async findByWallet(wallet) {
|
|
48
|
-
// Lowercased for the backend, not just for the comparison below. The
|
|
49
|
-
// `strategy_addresses` filter is an exact SQL `in` against a column
|
|
50
|
-
// written lowercase, so a checksummed address matches nothing and comes
|
|
51
|
-
// back as an ordinary empty list — a correct address reported as "not
|
|
52
|
-
// yours", which is the one answer this gate must never invent.
|
|
53
|
-
const wanted = wallet.toLowerCase();
|
|
54
|
-
const rows = await withDeadline(withClient((client) => client.listStrategies({ strategyAddresses: [wanted] }, { signal: AbortSignal.timeout(STRATEGY_LOOKUP_TIMEOUT_MS) })), STRATEGY_LOOKUP_TIMEOUT_MS);
|
|
55
|
-
const matches = rows.filter((row) => row.strategyWalletAddress.toLowerCase() === wanted);
|
|
56
|
-
// An address may carry more than one row over its lifetime; a live
|
|
57
|
-
// strategy on it is the answer, and its status is what the gate judges.
|
|
58
|
-
const match = matches.find((row) => row.status.toUpperCase() === "ACTIVE") ?? matches[0];
|
|
59
|
-
if (!match)
|
|
60
|
-
return null;
|
|
61
|
-
return {
|
|
62
|
-
id: match.id,
|
|
63
|
-
walletAddress: match.strategyWalletAddress,
|
|
64
|
-
status: match.status,
|
|
65
|
-
name: match.strategyName,
|
|
66
|
-
};
|
|
67
|
-
},
|
|
68
|
-
};
|
|
69
|
-
}
|
|
70
|
-
//# sourceMappingURL=strategy-lookup.js.map
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"file":"strategy-lookup.js","sourceRoot":"","sources":["../../src/widgets/strategy-lookup.ts"],"names":[],"mappings":"AAEA;;;;GAIG;AACH,MAAM,CAAC,MAAM,0BAA0B,GAAG,MAAM,CAAC;AAEjD;;;;;;;;GAQG;AACH,KAAK,UAAU,YAAY,CAAI,IAAgB,EAAE,EAAU;IACzD,IAAI,KAAgD,CAAC;IACrD,MAAM,QAAQ,GAAG,IAAI,OAAO,CAAQ,CAAC,QAAQ,EAAE,MAAM,EAAE,EAAE;QACvD,KAAK,GAAG,UAAU,CAAC,GAAG,EAAE,CAAC,MAAM,CAAC,IAAI,KAAK,CAAC,mCAAmC,EAAE,IAAI,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC;IAC7F,CAAC,CAAC,CAAC;IACH,IAAI,CAAC;QACH,OAAO,MAAM,OAAO,CAAC,IAAI,CAAC,CAAC,IAAI,EAAE,QAAQ,CAAC,CAAC,CAAC;IAC9C,CAAC;YAAS,CAAC;QACT,YAAY,CAAC,KAAK,CAAC,CAAC;IACtB,CAAC;AACH,CAAC;AAED,MAAM,iBAAiB,GAAG,qBAAqB,CAAC;AAEhD,MAAM,UAAU,eAAe,CAAC,KAAa;IAC3C,OAAO,iBAAiB,CAAC,IAAI,CAAC,KAAK,CAAC,CAAC;AACvC,CAAC;AAkBD;;;;;;;;;;;;GAYG;AACH,MAAM,UAAU,yBAAyB,CACvC,UAAyE;IAEzE,OAAO;QACL,KAAK,CAAC,YAAY,CAAC,MAAc;YAC/B,qEAAqE;YACrE,oEAAoE;YACpE,wEAAwE;YACxE,sEAAsE;YACtE,+DAA+D;YAC/D,MAAM,MAAM,GAAG,MAAM,CAAC,WAAW,EAAE,CAAC;YACpC,MAAM,IAAI,GAAG,MAAM,YAAY,CAC7B,UAAU,CAAC,CAAC,MAAM,EAAE,EAAE,CACpB,MAAM,CAAC,cAAc,CACnB,EAAE,iBAAiB,EAAE,CAAC,MAAM,CAAC,EAAE,EAC/B,EAAE,MAAM,EAAE,WAAW,CAAC,OAAO,CAAC,0BAA0B,CAAC,EAAE,CAC5D,CACF,EACD,0BAA0B,CAC3B,CAAC;YACF,MAAM,OAAO,GAAG,IAAI,CAAC,MAAM,CAAC,CAAC,GAAG,EAAE,EAAE,CAAC,GAAG,CAAC,qBAAqB,CAAC,WAAW,EAAE,KAAK,MAAM,CAAC,CAAC;YACzF,mEAAmE;YACnE,wEAAwE;YACxE,MAAM,KAAK,GAAG,OAAO,CAAC,IAAI,CAAC,CAAC,GAAG,EAAE,EAAE,CAAC,GAAG,CAAC,MAAM,CAAC,WAAW,EAAE,KAAK,QAAQ,CAAC,IAAI,OAAO,CAAC,CAAC,CAAC,CAAC;YACzF,IAAI,CAAC,KAAK;gBAAE,OAAO,IAAI,CAAC;YACxB,OAAO;gBACL,EAAE,EAAE,KAAK,CAAC,EAAE;gBACZ,aAAa,EAAE,KAAK,CAAC,qBAAqB;gBAC1C,MAAM,EAAE,KAAK,CAAC,MAAM;gBACpB,IAAI,EAAE,KAAK,CAAC,YAAY;aACzB,CAAC;QACJ,CAAC;KACF,CAAC;AACJ,CAAC"}
|
|
@@ -1,13 +0,0 @@
|
|
|
1
|
-
import type { WidgetSpec } from "../types.js";
|
|
2
|
-
/**
|
|
3
|
-
* `fund_strategy_wallet` — a card whose button tops the strategy up from the
|
|
4
|
-
* user's own Senpi wallet. This is an internal transfer, not a deposit: a
|
|
5
|
-
* strategy wallet has no deposit address anyone may be shown, because USDC sent
|
|
6
|
-
* to it directly bypasses accounting and may be unrecoverable.
|
|
7
|
-
*
|
|
8
|
-
* The address is validated here rather than at the schema, because "is one of
|
|
9
|
-
* this user's active strategies" is not something a JSON schema can state. The
|
|
10
|
-
* payload carries the resolved strategy id: the top-up API keys on the id.
|
|
11
|
-
*/
|
|
12
|
-
export declare const fundStrategyWalletSpec: WidgetSpec;
|
|
13
|
-
//# sourceMappingURL=fund-strategy-wallet.d.ts.map
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"file":"fund-strategy-wallet.d.ts","sourceRoot":"","sources":["../../../src/widgets/widgets/fund-strategy-wallet.ts"],"names":[],"mappings":"AAEA,OAAO,KAAK,EAAgC,UAAU,EAAE,MAAM,aAAa,CAAC;AA4G5E;;;;;;;;;GASG;AACH,eAAO,MAAM,sBAAsB,EAAE,UA0DpC,CAAC"}
|
|
@@ -1,139 +0,0 @@
|
|
|
1
|
-
import { WidgetLookupError, WidgetValidationError } from "../errors.js";
|
|
2
|
-
import { isWalletAddress } from "../strategy-lookup.js";
|
|
3
|
-
const WIDGET_TYPE = "fund_strategy_wallet";
|
|
4
|
-
const WIDGET_VERSION = 1;
|
|
5
|
-
/**
|
|
6
|
-
* The tracking id becomes the top-up's `idempotencyKey`, so it has to be unique
|
|
7
|
-
* per intent and safe to carry as a key. The charset excludes `:`, which would
|
|
8
|
-
* otherwise let a digits-then-text id trip OpenClaw's value redaction and reach
|
|
9
|
-
* the client masked; the length floor keeps a model from minting "1".
|
|
10
|
-
*/
|
|
11
|
-
const TRACKING_ID_RE = /^[A-Za-z0-9._-]{8,128}$/;
|
|
12
|
-
const TRACKING_ID_MAX = 128;
|
|
13
|
-
/** Only a live strategy can take a top-up. */
|
|
14
|
-
const ACTIVE = "ACTIVE";
|
|
15
|
-
/** `strategyName` is nullable on the backend; the id is what always identifies a row. */
|
|
16
|
-
function strategyLabel(match) {
|
|
17
|
-
return match.name.trim() || match.id;
|
|
18
|
-
}
|
|
19
|
-
function requireFormat(args) {
|
|
20
|
-
const wallet = String(args.strategy_wallet).trim();
|
|
21
|
-
const trackingId = String(args.tracking_id).trim();
|
|
22
|
-
const problems = [];
|
|
23
|
-
const fields = [];
|
|
24
|
-
if (!isWalletAddress(wallet)) {
|
|
25
|
-
fields.push("strategy_wallet");
|
|
26
|
-
problems.push(`strategy_wallet "${wallet}" is not a wallet address (expected 0x followed by 40 hex characters). ` +
|
|
27
|
-
"Call strategy_list and pass the strategyWalletAddress of the strategy you mean");
|
|
28
|
-
}
|
|
29
|
-
if (!TRACKING_ID_RE.test(trackingId)) {
|
|
30
|
-
fields.push("tracking_id");
|
|
31
|
-
problems.push("tracking_id must be 8-128 characters of letters, digits, dot, underscore or hyphen — " +
|
|
32
|
-
"generate a fresh UUID for this funding request");
|
|
33
|
-
}
|
|
34
|
-
if (problems.length > 0) {
|
|
35
|
-
throw new WidgetValidationError(`show_widget failed for widget_type "${WIDGET_TYPE}": ${problems.join("; ")}.`, WIDGET_TYPE, fields);
|
|
36
|
-
}
|
|
37
|
-
return { wallet, trackingId };
|
|
38
|
-
}
|
|
39
|
-
async function resolveStrategy(ctx, wallet) {
|
|
40
|
-
if (!ctx.strategies) {
|
|
41
|
-
throw new WidgetLookupError(`show_widget failed for widget_type "${WIDGET_TYPE}": the strategy wallet could not be verified ` +
|
|
42
|
-
"because this deployment has no Senpi backend configured. Do not retry; handle the request in text.", WIDGET_TYPE);
|
|
43
|
-
}
|
|
44
|
-
let match;
|
|
45
|
-
try {
|
|
46
|
-
match = await ctx.strategies.findByWallet(wallet);
|
|
47
|
-
}
|
|
48
|
-
catch (err) {
|
|
49
|
-
// The address was never judged. Saying "not yours" here would make the model
|
|
50
|
-
// swap a correct address for a wrong one.
|
|
51
|
-
throw new WidgetLookupError(`show_widget failed for widget_type "${WIDGET_TYPE}": strategy wallet ${wallet} could not be verified — ` +
|
|
52
|
-
"the Senpi backend did not answer. The address may well be correct. Try the same call again.", WIDGET_TYPE, err);
|
|
53
|
-
}
|
|
54
|
-
if (!match) {
|
|
55
|
-
throw new WidgetValidationError(`show_widget failed for widget_type "${WIDGET_TYPE}": ${wallet} is not one of your strategy wallets. ` +
|
|
56
|
-
"Call strategy_list and pass the strategyWalletAddress of the strategy you mean.", WIDGET_TYPE, ["strategy_wallet"]);
|
|
57
|
-
}
|
|
58
|
-
if (match.status.toUpperCase() !== ACTIVE) {
|
|
59
|
-
// `strategyName` is nullable and the status covers the whole onboarding
|
|
60
|
-
// lifecycle, not just PAUSED/CLOSED — so neither is quoted raw.
|
|
61
|
-
throw new WidgetValidationError(`show_widget failed for widget_type "${WIDGET_TYPE}": that wallet belongs to strategy ` +
|
|
62
|
-
`"${strategyLabel(match)}", whose status is ${match.status.trim() || "unknown"}, not ${ACTIVE}. ` +
|
|
63
|
-
`Only an ${ACTIVE} strategy can be funded — tell the user to resume or finish setting it up, ` +
|
|
64
|
-
"or pass a different strategy's wallet.", WIDGET_TYPE, ["strategy_wallet"]);
|
|
65
|
-
}
|
|
66
|
-
if (match.id.trim().length === 0) {
|
|
67
|
-
// The funding API takes a strategy id, never the address, so an id-less row
|
|
68
|
-
// is unusable even though the address checked out.
|
|
69
|
-
throw new WidgetLookupError(`show_widget failed for widget_type "${WIDGET_TYPE}": the backend matched the wallet but returned no ` +
|
|
70
|
-
"strategy id, so the card cannot be funded. Retrying is unlikely to help — tell the user this " +
|
|
71
|
-
"strategy cannot be funded right now.", WIDGET_TYPE);
|
|
72
|
-
}
|
|
73
|
-
return match;
|
|
74
|
-
}
|
|
75
|
-
/**
|
|
76
|
-
* `fund_strategy_wallet` — a card whose button tops the strategy up from the
|
|
77
|
-
* user's own Senpi wallet. This is an internal transfer, not a deposit: a
|
|
78
|
-
* strategy wallet has no deposit address anyone may be shown, because USDC sent
|
|
79
|
-
* to it directly bypasses accounting and may be unrecoverable.
|
|
80
|
-
*
|
|
81
|
-
* The address is validated here rather than at the schema, because "is one of
|
|
82
|
-
* this user's active strategies" is not something a JSON schema can state. The
|
|
83
|
-
* payload carries the resolved strategy id: the top-up API keys on the id.
|
|
84
|
-
*/
|
|
85
|
-
export const fundStrategyWalletSpec = {
|
|
86
|
-
widgetType: WIDGET_TYPE,
|
|
87
|
-
widgetVersion: WIDGET_VERSION,
|
|
88
|
-
description: "Show the user a card that funds one of their own strategies from their Senpi wallet. " +
|
|
89
|
-
"Use it when a strategy has too little balance to trade, or the user asks to fund, top up or add to a strategy. " +
|
|
90
|
-
"Never state a deposit address for a strategy wallet — funding a strategy is an internal transfer and the card owns it.",
|
|
91
|
-
fields: [
|
|
92
|
-
{
|
|
93
|
-
name: "strategy_wallet",
|
|
94
|
-
type: "string",
|
|
95
|
-
required: true,
|
|
96
|
-
description: "The strategyWalletAddress of one of the user's ACTIVE strategies, from strategy_list. " +
|
|
97
|
-
"Rejected if it is not one of theirs.",
|
|
98
|
-
maxLength: 64,
|
|
99
|
-
},
|
|
100
|
-
{
|
|
101
|
-
name: "tracking_id",
|
|
102
|
-
type: "string",
|
|
103
|
-
required: true,
|
|
104
|
-
description: "A fresh UUID you generate for this funding request, 8-128 characters of letters, digits, dot, " +
|
|
105
|
-
"underscore or hyphen. Never reuse one: it is the idempotency key, and a repeat replays the first top-up.",
|
|
106
|
-
maxLength: TRACKING_ID_MAX,
|
|
107
|
-
},
|
|
108
|
-
{
|
|
109
|
-
name: "reason",
|
|
110
|
-
type: "string",
|
|
111
|
-
required: false,
|
|
112
|
-
description: 'One plain sentence on why you are showing the card, e.g. "This strategy has no margin left." ' +
|
|
113
|
-
"Recorded with the call; the card renders its own copy, so this is not displayed to the user.",
|
|
114
|
-
maxLength: 280,
|
|
115
|
-
},
|
|
116
|
-
],
|
|
117
|
-
handler: async (args, ctx) => {
|
|
118
|
-
const { wallet, trackingId } = requireFormat(args);
|
|
119
|
-
const match = await resolveStrategy(ctx, wallet);
|
|
120
|
-
return {
|
|
121
|
-
payload: {
|
|
122
|
-
widget_type: WIDGET_TYPE,
|
|
123
|
-
widget_version: WIDGET_VERSION,
|
|
124
|
-
status: "shown",
|
|
125
|
-
strategy_id: match.id,
|
|
126
|
-
// The backend's spelling, not the model's. The web client re-resolves
|
|
127
|
-
// the strategy from the args (the payload does not survive a reload),
|
|
128
|
-
// so this is carried for clients that render from the payload.
|
|
129
|
-
strategy_wallet: match.walletAddress,
|
|
130
|
-
tracking_id: trackingId,
|
|
131
|
-
},
|
|
132
|
-
// No address in here: this text is the agent's to repeat, and a strategy
|
|
133
|
-
// wallet address repeated in chat reads as a deposit address.
|
|
134
|
-
text: `Funding card shown for strategy "${strategyLabel(match)}". The user funds it from the card. ` +
|
|
135
|
-
"Do not show it again in this turn, and never give an address for funding a strategy.",
|
|
136
|
-
};
|
|
137
|
-
},
|
|
138
|
-
};
|
|
139
|
-
//# sourceMappingURL=fund-strategy-wallet.js.map
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"file":"fund-strategy-wallet.js","sourceRoot":"","sources":["../../../src/widgets/widgets/fund-strategy-wallet.ts"],"names":[],"mappings":"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"}
|
|
@@ -1,167 +0,0 @@
|
|
|
1
|
-
---
|
|
2
|
-
name: senpi-widgets
|
|
3
|
-
description: "Use the show_widget tool to render a native Senpi card in the chat instead of describing a flow in text. Use when the user has no funds, cannot open a trade for lack of balance, asks how to deposit, add money, fund their wallet, fund or top up a strategy — and whenever you are about to explain a Senpi flow that the app already has a screen for. Triggers on: fund wallet, add funds, deposit, no balance, insufficient balance, how do I put money in, top up, fund my strategy, strategy has no margin, show widget, show card, funding card."
|
|
4
|
-
---
|
|
5
|
-
|
|
6
|
-
# Senpi widgets
|
|
7
|
-
|
|
8
|
-
The Senpi web and mobile clients render cards from the `show_widget` tool call.
|
|
9
|
-
Calling the tool puts a real, working card in the conversation. It is not a
|
|
10
|
-
picture and not a description — the card's own button drives the product flow.
|
|
11
|
-
|
|
12
|
-
## The one tool
|
|
13
|
-
|
|
14
|
-
```
|
|
15
|
-
show_widget(widget_type, <fields for that type>)
|
|
16
|
-
```
|
|
17
|
-
|
|
18
|
-
`widget_type` is an enum. Pass only the fields the tool description lists for
|
|
19
|
-
that type; anything else is rejected.
|
|
20
|
-
|
|
21
|
-
## Two wallets, two widgets
|
|
22
|
-
|
|
23
|
-
There are two different wallets, and they are funded in two different ways.
|
|
24
|
-
|
|
25
|
-
| | User wallet | Strategy wallet |
|
|
26
|
-
|---|---|---|
|
|
27
|
-
| Whose | The user's own Senpi (self-custody) wallet | One wallet per strategy |
|
|
28
|
-
| How it is funded | External deposit — USDC sent to an address | Internal transfer from the user's own wallet |
|
|
29
|
-
| Widget | `fund_user_wallet` | `fund_strategy_wallet` |
|
|
30
|
-
|
|
31
|
-
Pick by the wallet the money is going *into*. If the user has no money in Senpi
|
|
32
|
-
at all, that is `fund_user_wallet` — a strategy cannot be funded from an empty
|
|
33
|
-
wallet.
|
|
34
|
-
|
|
35
|
-
## Widget types
|
|
36
|
-
|
|
37
|
-
### `fund_user_wallet`
|
|
38
|
-
|
|
39
|
-
The card explains that Senpi wallets are self-custody and its button opens the
|
|
40
|
-
deposit screen for the user's own wallet.
|
|
41
|
-
|
|
42
|
-
Use it when:
|
|
43
|
-
|
|
44
|
-
- the user has no balance, or too little balance to open the trade they asked for
|
|
45
|
-
- the user asks how to deposit, add funds, or top up
|
|
46
|
-
- you are about to explain the deposit flow in prose
|
|
47
|
-
|
|
48
|
-
Fields:
|
|
49
|
-
|
|
50
|
-
| Field | Required | Notes |
|
|
51
|
-
|---|---|---|
|
|
52
|
-
| `widget_type` | yes | `"fund_user_wallet"` |
|
|
53
|
-
| `reason` | no | One plain sentence on why you are showing the card. Not displayed — the card owns its copy. Omit it rather than passing an empty string. |
|
|
54
|
-
|
|
55
|
-
Worked example — the user says *"open a 5x long on HYPE"* and the account has
|
|
56
|
-
no balance:
|
|
57
|
-
|
|
58
|
-
```
|
|
59
|
-
show_widget({
|
|
60
|
-
widget_type: "fund_user_wallet",
|
|
61
|
-
reason: "Your wallet needs a balance before this trade can open."
|
|
62
|
-
})
|
|
63
|
-
```
|
|
64
|
-
|
|
65
|
-
Then answer in prose, for example: "Add funds from the card above and I'll open
|
|
66
|
-
the 5x HYPE long straight after."
|
|
67
|
-
|
|
68
|
-
Do not pass an address to this widget. The card resolves the user's own wallet
|
|
69
|
-
from their session, and an address is rejected.
|
|
70
|
-
|
|
71
|
-
### `fund_strategy_wallet`
|
|
72
|
-
|
|
73
|
-
The card's button moves money from the user's own Senpi wallet into one of their
|
|
74
|
-
strategies. It is an internal transfer, so no address is ever shown or needed by
|
|
75
|
-
the user.
|
|
76
|
-
|
|
77
|
-
Use it when:
|
|
78
|
-
|
|
79
|
-
- a strategy is out of margin, or too small to take the trade the user wants
|
|
80
|
-
- the user asks to fund, top up or add to a named strategy
|
|
81
|
-
- you are about to explain how to move money into a strategy in prose
|
|
82
|
-
|
|
83
|
-
Fields:
|
|
84
|
-
|
|
85
|
-
| Field | Required | Notes |
|
|
86
|
-
|---|---|---|
|
|
87
|
-
| `widget_type` | yes | `"fund_strategy_wallet"` |
|
|
88
|
-
| `strategy_wallet` | yes | The `strategyWalletAddress` of one of the user's **ACTIVE** strategies. Get it from `strategy_list`; never from memory or from the user's typing. |
|
|
89
|
-
| `tracking_id` | yes | A fresh UUID **you** generate for this one funding request. 8-128 characters, letters/digits/`.`/`_`/`-` only. |
|
|
90
|
-
| `reason` | no | One plain sentence on why you are showing the card. Not displayed. |
|
|
91
|
-
|
|
92
|
-
Worked example — the user says *"put another $200 into my HYPE momentum
|
|
93
|
-
strategy"*. First call `strategy_list` to get that strategy's
|
|
94
|
-
`strategyWalletAddress`, then:
|
|
95
|
-
|
|
96
|
-
```
|
|
97
|
-
show_widget({
|
|
98
|
-
widget_type: "fund_strategy_wallet",
|
|
99
|
-
strategy_wallet: "0x1111111111111111111111111111111111111111",
|
|
100
|
-
tracking_id: "0d2f4a1c-3b7e-4f0a-9c11-6ad2e5b8c701",
|
|
101
|
-
reason: "The user wants to add $200 to this strategy."
|
|
102
|
-
})
|
|
103
|
-
```
|
|
104
|
-
|
|
105
|
-
The user chooses the amount on the card. Do not put an amount in the call.
|
|
106
|
-
|
|
107
|
-
## House rules
|
|
108
|
-
|
|
109
|
-
**Show a card once per turn.** After the tool returns, the card is on screen.
|
|
110
|
-
Answer every follow-up in text. Calling `show_widget` again in the same turn
|
|
111
|
-
stacks duplicate cards in the transcript.
|
|
112
|
-
|
|
113
|
-
**Never write a deposit address, QR code or network list in text.** The card and
|
|
114
|
-
the deposit screen own those. A pasted address in chat is a phishing surface and
|
|
115
|
-
can go stale.
|
|
116
|
-
|
|
117
|
-
**Never give a deposit address for a strategy wallet — there is no such thing.**
|
|
118
|
-
USDC sent straight to a strategy wallet skips Senpi's accounting, corrupts the
|
|
119
|
-
strategy's PnL and may be unrecoverable. Funding a strategy is only ever the
|
|
120
|
-
`fund_strategy_wallet` card. If a user asks where to send funds for a strategy,
|
|
121
|
-
tell them it is an internal transfer and show the card.
|
|
122
|
-
|
|
123
|
-
**Generate a new `tracking_id` for every `fund_strategy_wallet` call.** It is the
|
|
124
|
-
idempotency key for the top-up. Reusing one from an earlier card replays that
|
|
125
|
-
first top-up: the amount is whatever the user typed the first time, and nothing
|
|
126
|
-
new is transferred. Never copy a tracking id out of the conversation.
|
|
127
|
-
|
|
128
|
-
**Correct numbers when you talk about deposits in prose:** for a deposit into the
|
|
129
|
-
user's own wallet the minimum is $10, and most traders start with $100-250. Do
|
|
130
|
-
not invent other figures — the deposit card's own copy uses these. These are
|
|
131
|
-
deposit figures; do not quote them for a strategy top-up.
|
|
132
|
-
|
|
133
|
-
**Do not claim the deposit landed.** The tool result only says the card was
|
|
134
|
-
shown. Wait for the user to say so, or for the client's own "I've funded my
|
|
135
|
-
wallet" message, before acting as though there is a balance.
|
|
136
|
-
|
|
137
|
-
**Do not ask the user to confirm before showing a card.** Showing it costs
|
|
138
|
-
nothing and authorizes nothing.
|
|
139
|
-
|
|
140
|
-
## When the tool returns an error
|
|
141
|
-
|
|
142
|
-
The error names the exact fields that were wrong: missing, invalid, or
|
|
143
|
-
unexpected. Fix those fields and call `show_widget` again. Keep the same
|
|
144
|
-
`widget_type` unless the error itself names a different one. Dropping the call
|
|
145
|
-
and describing the flow in prose instead is the wrong recovery.
|
|
146
|
-
|
|
147
|
-
- `unexpected fields: amount` — that widget takes no amount. Remove the field.
|
|
148
|
-
- `invalid fields: reason (must be a non-empty string; omit it instead)` — drop
|
|
149
|
-
`reason` entirely, or pass a real sentence.
|
|
150
|
-
- `unknown widget_type "..."` — the message lists the available types. Pick one
|
|
151
|
-
of those; do not invent a name.
|
|
152
|
-
- `... is not one of your strategy wallets` — call `strategy_list` and use a
|
|
153
|
-
`strategyWalletAddress` from it. Do not retry the same address.
|
|
154
|
-
- `whose status is PAUSED, not ACTIVE` — only a live strategy can be funded. Tell
|
|
155
|
-
the user, or pass a different strategy's wallet.
|
|
156
|
-
- `it takes no address` on `fund_user_wallet` — drop `strategy_wallet`, or switch
|
|
157
|
-
to `fund_strategy_wallet` if the money is meant for a strategy.
|
|
158
|
-
- `tracking_id must be 8-128 characters ...` — generate a fresh UUID and call
|
|
159
|
-
again with everything else unchanged.
|
|
160
|
-
|
|
161
|
-
**"could not be verified — the Senpi backend did not answer"** is different from
|
|
162
|
-
every error above: nothing was found wrong with your call. Retry the *same* call,
|
|
163
|
-
tracking id included — no card rendered, so that id is still unused. Do not swap
|
|
164
|
-
the address, and do not tell the user their strategy does not exist.
|
|
165
|
-
|
|
166
|
-
If a widget type is reported as not enabled on this deployment, do not retry it.
|
|
167
|
-
Handle the request in prose instead.
|