@senpi/runtime 3.0.80 → 3.0.81

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.
Files changed (39) hide show
  1. package/dist/index.d.ts +13 -0
  2. package/dist/index.d.ts.map +1 -1
  3. package/dist/index.js +21 -0
  4. package/dist/index.js.map +1 -1
  5. package/dist/widgets/errors.d.ts +23 -0
  6. package/dist/widgets/errors.d.ts.map +1 -0
  7. package/dist/widgets/errors.js +33 -0
  8. package/dist/widgets/errors.js.map +1 -0
  9. package/dist/widgets/index.d.ts +39 -0
  10. package/dist/widgets/index.d.ts.map +1 -0
  11. package/dist/widgets/index.js +36 -0
  12. package/dist/widgets/index.js.map +1 -0
  13. package/dist/widgets/registry.d.ts +24 -0
  14. package/dist/widgets/registry.d.ts.map +1 -0
  15. package/dist/widgets/registry.js +26 -0
  16. package/dist/widgets/registry.js.map +1 -0
  17. package/dist/widgets/strategy-lookup.d.ts +39 -0
  18. package/dist/widgets/strategy-lookup.d.ts.map +1 -0
  19. package/dist/widgets/strategy-lookup.js +70 -0
  20. package/dist/widgets/strategy-lookup.js.map +1 -0
  21. package/dist/widgets/tool.d.ts +51 -0
  22. package/dist/widgets/tool.d.ts.map +1 -0
  23. package/dist/widgets/tool.js +250 -0
  24. package/dist/widgets/tool.js.map +1 -0
  25. package/dist/widgets/types.d.ts +91 -0
  26. package/dist/widgets/types.d.ts.map +1 -0
  27. package/dist/widgets/types.js +10 -0
  28. package/dist/widgets/types.js.map +1 -0
  29. package/dist/widgets/widgets/fund-strategy-wallet.d.ts +13 -0
  30. package/dist/widgets/widgets/fund-strategy-wallet.d.ts.map +1 -0
  31. package/dist/widgets/widgets/fund-strategy-wallet.js +139 -0
  32. package/dist/widgets/widgets/fund-strategy-wallet.js.map +1 -0
  33. package/dist/widgets/widgets/fund-user-wallet.d.ts +13 -0
  34. package/dist/widgets/widgets/fund-user-wallet.d.ts.map +1 -0
  35. package/dist/widgets/widgets/fund-user-wallet.js +78 -0
  36. package/dist/widgets/widgets/fund-user-wallet.js.map +1 -0
  37. package/openclaw.plugin.json +16 -1
  38. package/package.json +3 -2
  39. package/skills/senpi-widgets/SKILL.md +167 -0
@@ -0,0 +1,24 @@
1
+ import type { RegisteredWidget, WidgetSpec } from "./types.js";
2
+ /**
3
+ * Every widget type this plugin can render. Adding one here adds it to the
4
+ * `show_widget` enum, to the generated tool description, and to the config
5
+ * gate — the tool name and the manifest are untouched.
6
+ */
7
+ export declare const WIDGET_SPECS: readonly WidgetSpec[];
8
+ /**
9
+ * Plugin config gate, keyed by `widget_type`. Absent or `true` means enabled;
10
+ * only an explicit `false` turns a widget off. Config keys the registry does
11
+ * not know are ignored, and so is a config value that is not an object.
12
+ */
13
+ export type WidgetsConfig = Record<string, boolean>;
14
+ export interface WidgetRegistry {
15
+ /** Every spec, including disabled ones. */
16
+ all(): readonly RegisteredWidget[];
17
+ /** Specs the model may call. Source for the enum and the description. */
18
+ enabled(): readonly RegisteredWidget[];
19
+ /** Lookup by `widget_type`, enabled or not. */
20
+ get(widgetType: string): RegisteredWidget | undefined;
21
+ enabledTypes(): string[];
22
+ }
23
+ export declare function buildWidgetRegistry(config?: unknown, specs?: readonly WidgetSpec[]): WidgetRegistry;
24
+ //# sourceMappingURL=registry.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"registry.d.ts","sourceRoot":"","sources":["../../src/widgets/registry.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,gBAAgB,EAAE,UAAU,EAAE,MAAM,YAAY,CAAC;AAI/D;;;;GAIG;AACH,eAAO,MAAM,YAAY,EAAE,SAAS,UAAU,EAAiD,CAAC;AAEhG;;;;GAIG;AACH,MAAM,MAAM,aAAa,GAAG,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC;AAEpD,MAAM,WAAW,cAAc;IAC7B,2CAA2C;IAC3C,GAAG,IAAI,SAAS,gBAAgB,EAAE,CAAC;IACnC,yEAAyE;IACzE,OAAO,IAAI,SAAS,gBAAgB,EAAE,CAAC;IACvC,+CAA+C;IAC/C,GAAG,CAAC,UAAU,EAAE,MAAM,GAAG,gBAAgB,GAAG,SAAS,CAAC;IACtD,YAAY,IAAI,MAAM,EAAE,CAAC;CAC1B;AAED,wBAAgB,mBAAmB,CACjC,MAAM,CAAC,EAAE,OAAO,EAChB,KAAK,GAAE,SAAS,UAAU,EAAiB,GAC1C,cAAc,CAiBhB"}
@@ -0,0 +1,26 @@
1
+ import { fundStrategyWalletSpec } from "./widgets/fund-strategy-wallet.js";
2
+ import { fundUserWalletSpec } from "./widgets/fund-user-wallet.js";
3
+ /**
4
+ * Every widget type this plugin can render. Adding one here adds it to the
5
+ * `show_widget` enum, to the generated tool description, and to the config
6
+ * gate — the tool name and the manifest are untouched.
7
+ */
8
+ export const WIDGET_SPECS = [fundUserWalletSpec, fundStrategyWalletSpec];
9
+ export function buildWidgetRegistry(config, specs = WIDGET_SPECS) {
10
+ const gate = config && typeof config === "object" && !Array.isArray(config)
11
+ ? config
12
+ : undefined;
13
+ const widgets = specs.map((spec) => ({
14
+ ...spec,
15
+ enabled: gate?.[spec.widgetType] !== false,
16
+ }));
17
+ const byType = new Map(widgets.map((w) => [w.widgetType, w]));
18
+ const enabled = widgets.filter((w) => w.enabled);
19
+ return {
20
+ all: () => widgets,
21
+ enabled: () => enabled,
22
+ get: (widgetType) => byType.get(widgetType),
23
+ enabledTypes: () => enabled.map((w) => w.widgetType),
24
+ };
25
+ }
26
+ //# sourceMappingURL=registry.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"registry.js","sourceRoot":"","sources":["../../src/widgets/registry.ts"],"names":[],"mappings":"AACA,OAAO,EAAE,sBAAsB,EAAE,MAAM,mCAAmC,CAAC;AAC3E,OAAO,EAAE,kBAAkB,EAAE,MAAM,+BAA+B,CAAC;AAEnE;;;;GAIG;AACH,MAAM,CAAC,MAAM,YAAY,GAA0B,CAAC,kBAAkB,EAAE,sBAAsB,CAAC,CAAC;AAmBhG,MAAM,UAAU,mBAAmB,CACjC,MAAgB,EAChB,QAA+B,YAAY;IAE3C,MAAM,IAAI,GACR,MAAM,IAAI,OAAO,MAAM,KAAK,QAAQ,IAAI,CAAC,KAAK,CAAC,OAAO,CAAC,MAAM,CAAC;QAC5D,CAAC,CAAE,MAAkC;QACrC,CAAC,CAAC,SAAS,CAAC;IAChB,MAAM,OAAO,GAAuB,KAAK,CAAC,GAAG,CAAC,CAAC,IAAI,EAAE,EAAE,CAAC,CAAC;QACvD,GAAG,IAAI;QACP,OAAO,EAAE,IAAI,EAAE,CAAC,IAAI,CAAC,UAAU,CAAC,KAAK,KAAK;KAC3C,CAAC,CAAC,CAAC;IACJ,MAAM,MAAM,GAAG,IAAI,GAAG,CAAC,OAAO,CAAC,GAAG,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,CAAC,UAAU,EAAE,CAAC,CAAC,CAAC,CAAC,CAAC;IAC9D,MAAM,OAAO,GAAG,OAAO,CAAC,MAAM,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,OAAO,CAAC,CAAC;IACjD,OAAO;QACL,GAAG,EAAE,GAAG,EAAE,CAAC,OAAO;QAClB,OAAO,EAAE,GAAG,EAAE,CAAC,OAAO;QACtB,GAAG,EAAE,CAAC,UAAkB,EAAE,EAAE,CAAC,MAAM,CAAC,GAAG,CAAC,UAAU,CAAC;QACnD,YAAY,EAAE,GAAG,EAAE,CAAC,OAAO,CAAC,GAAG,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,UAAU,CAAC;KACrD,CAAC;AACJ,CAAC"}
@@ -0,0 +1,39 @@
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
@@ -0,0 +1 @@
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"}
@@ -0,0 +1,70 @@
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
@@ -0,0 +1 @@
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"}
@@ -0,0 +1,51 @@
1
+ import type { WidgetRegistry } from "./registry.js";
2
+ import type { StrategyLookup, WidgetLogger, WidgetPayload } from "./types.js";
3
+ /**
4
+ * The plugin's single agent tool. The name is static — new widget types are
5
+ * new `widget_type` enum values, never new tools — so it is declared once in
6
+ * `openclaw.plugin.json` under `contracts.tools`.
7
+ */
8
+ export declare const SHOW_WIDGET_TOOL_NAME = "show_widget";
9
+ export interface ShowWidgetToolResult {
10
+ content: Array<{
11
+ type: "text";
12
+ text: string;
13
+ }>;
14
+ details: WidgetPayload;
15
+ }
16
+ export interface ShowWidgetTool {
17
+ name: string;
18
+ description: string;
19
+ parameters: Record<string, unknown>;
20
+ execute(toolCallId: string, params: unknown): Promise<ShowWidgetToolResult>;
21
+ }
22
+ export interface CreateShowWidgetToolOptions {
23
+ registry: WidgetRegistry;
24
+ logger?: WidgetLogger;
25
+ /** Ephemeral session id from the plugin tool factory context; logged only. */
26
+ sessionId?: string;
27
+ /** Backend a widget validates addresses against. Absent = those widgets fail closed. */
28
+ strategies?: StrategyLookup;
29
+ }
30
+ /**
31
+ * Tool description, generated from the enabled specs. This is the text the
32
+ * model selects a `widget_type` on, so it carries the per-type field list that
33
+ * the flat JSON schema cannot express.
34
+ */
35
+ export declare function buildShowWidgetDescription(registry: WidgetRegistry): string;
36
+ /**
37
+ * Declared JSON schema: one flat object, no unions at any depth.
38
+ *
39
+ * OpenClaw flattens top-level unions, Gemini flattens nested ones, and OpenAI
40
+ * strict mode rejects a schema containing one anywhere — so a discriminated
41
+ * union on `widget_type` would buy nothing. Only `widget_type` is required;
42
+ * per-widget requiredness is enforced in `execute()`.
43
+ */
44
+ export declare function buildShowWidgetParameters(registry: WidgetRegistry): Record<string, unknown>;
45
+ /**
46
+ * Build the `show_widget` tool for one agent turn. `sessionId` comes from the
47
+ * plugin tool factory context and is used for logging only — nothing about the
48
+ * result depends on it.
49
+ */
50
+ export declare function createShowWidgetTool(options: CreateShowWidgetToolOptions): ShowWidgetTool;
51
+ //# sourceMappingURL=tool.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"tool.d.ts","sourceRoot":"","sources":["../../src/widgets/tool.ts"],"names":[],"mappings":"AACA,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,eAAe,CAAC;AACpD,OAAO,KAAK,EAEV,cAAc,EAEd,YAAY,EACZ,aAAa,EACd,MAAM,YAAY,CAAC;AAEpB;;;;GAIG;AACH,eAAO,MAAM,qBAAqB,gBAAgB,CAAC;AAInD,MAAM,WAAW,oBAAoB;IACnC,OAAO,EAAE,KAAK,CAAC;QAAE,IAAI,EAAE,MAAM,CAAC;QAAC,IAAI,EAAE,MAAM,CAAA;KAAE,CAAC,CAAC;IAC/C,OAAO,EAAE,aAAa,CAAC;CACxB;AAED,MAAM,WAAW,cAAc;IAC7B,IAAI,EAAE,MAAM,CAAC;IACb,WAAW,EAAE,MAAM,CAAC;IACpB,UAAU,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC;IACpC,OAAO,CAAC,UAAU,EAAE,MAAM,EAAE,MAAM,EAAE,OAAO,GAAG,OAAO,CAAC,oBAAoB,CAAC,CAAC;CAC7E;AAED,MAAM,WAAW,2BAA2B;IAC1C,QAAQ,EAAE,cAAc,CAAC;IACzB,MAAM,CAAC,EAAE,YAAY,CAAC;IACtB,8EAA8E;IAC9E,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB,wFAAwF;IACxF,UAAU,CAAC,EAAE,cAAc,CAAC;CAC7B;AAQD;;;;GAIG;AACH,wBAAgB,0BAA0B,CAAC,QAAQ,EAAE,cAAc,GAAG,MAAM,CAuB3E;AAED;;;;;;;GAOG;AACH,wBAAgB,yBAAyB,CAAC,QAAQ,EAAE,cAAc,GAAG,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAyD3F;AAyID;;;;GAIG;AACH,wBAAgB,oBAAoB,CAAC,OAAO,EAAE,2BAA2B,GAAG,cAAc,CA2CzF"}
@@ -0,0 +1,250 @@
1
+ import { WidgetLookupError, WidgetValidationError } from "./errors.js";
2
+ /**
3
+ * The plugin's single agent tool. The name is static — new widget types are
4
+ * new `widget_type` enum values, never new tools — so it is declared once in
5
+ * `openclaw.plugin.json` under `contracts.tools`.
6
+ */
7
+ export const SHOW_WIDGET_TOOL_NAME = "show_widget";
8
+ const WIDGET_TYPE_PARAM = "widget_type";
9
+ function fieldLine(field) {
10
+ const parts = [`${field.required ? "required" : "optional"} ${field.type}`];
11
+ if (field.maxLength !== undefined)
12
+ parts.push(`max ${field.maxLength} chars`);
13
+ return ` ${field.name} (${parts.join(", ")}): ${field.description}`;
14
+ }
15
+ /**
16
+ * Tool description, generated from the enabled specs. This is the text the
17
+ * model selects a `widget_type` on, so it carries the per-type field list that
18
+ * the flat JSON schema cannot express.
19
+ */
20
+ export function buildShowWidgetDescription(registry) {
21
+ const widgets = registry.enabled();
22
+ const lines = [
23
+ "Render a Senpi UI card in the user's chat, for cases where a card communicates better than text.",
24
+ "",
25
+ `Pass ${WIDGET_TYPE_PARAM} plus only the fields listed for that type. Any other field is rejected.`,
26
+ "",
27
+ ];
28
+ if (widgets.length === 0) {
29
+ lines.push("No widget types are enabled on this deployment.");
30
+ return lines.join("\n");
31
+ }
32
+ lines.push("Widget types:");
33
+ for (const widget of widgets) {
34
+ lines.push(`- ${widget.widgetType}: ${widget.description}`);
35
+ lines.push(" fields:");
36
+ if (widget.fields.length === 0) {
37
+ lines.push(" none — pass widget_type only");
38
+ continue;
39
+ }
40
+ lines.push(...widget.fields.map(fieldLine));
41
+ }
42
+ return lines.join("\n");
43
+ }
44
+ /**
45
+ * Declared JSON schema: one flat object, no unions at any depth.
46
+ *
47
+ * OpenClaw flattens top-level unions, Gemini flattens nested ones, and OpenAI
48
+ * strict mode rejects a schema containing one anywhere — so a discriminated
49
+ * union on `widget_type` would buy nothing. Only `widget_type` is required;
50
+ * per-widget requiredness is enforced in `execute()`.
51
+ */
52
+ export function buildShowWidgetParameters(registry) {
53
+ const widgets = registry.enabled();
54
+ if (widgets.length === 0) {
55
+ throw new Error("show_widget has no enabled widget types; do not register the tool");
56
+ }
57
+ const properties = {
58
+ [WIDGET_TYPE_PARAM]: {
59
+ type: "string",
60
+ enum: widgets.map((w) => w.widgetType),
61
+ description: "Which widget to render.",
62
+ },
63
+ };
64
+ // Fields shared by several widget types collapse into one property, so the
65
+ // shared property has to admit every owner's input: per-widget limits are
66
+ // enforced in execute(), and a declared bound stricter than one owner's would
67
+ // make the provider reject a call that execute() would have accepted.
68
+ const owners = new Map();
69
+ for (const widget of widgets) {
70
+ for (const field of widget.fields) {
71
+ if (field.name === WIDGET_TYPE_PARAM) {
72
+ throw new Error(`show_widget: ${widget.widgetType} declares a field named "${WIDGET_TYPE_PARAM}", which is the discriminator`);
73
+ }
74
+ const entry = owners.get(field.name);
75
+ if (entry) {
76
+ entry.types.push(widget.widgetType);
77
+ entry.fields.push(field);
78
+ continue;
79
+ }
80
+ owners.set(field.name, { types: [widget.widgetType], fields: [field] });
81
+ }
82
+ }
83
+ for (const [name, { types, fields }] of owners) {
84
+ const valueTypes = [...new Set(fields.map((f) => f.type))];
85
+ if (valueTypes.length > 1) {
86
+ throw new Error(`show_widget: field "${name}" is declared as ${valueTypes.join(" and ")} by ${types.join(", ")}; one schema property cannot be both`);
87
+ }
88
+ const property = { type: valueTypes[0] };
89
+ const bounds = fields.map((f) => f.maxLength);
90
+ if (bounds.every((b) => b !== undefined)) {
91
+ property.maxLength = Math.max(...bounds);
92
+ }
93
+ property.description =
94
+ types.length === 1
95
+ ? `${fields[0].description} Accepted by: ${types[0]}.`
96
+ : fields.map((f, i) => `${types[i]}: ${f.description}`).join(" ");
97
+ properties[name] = property;
98
+ }
99
+ return {
100
+ type: "object",
101
+ properties,
102
+ required: [WIDGET_TYPE_PARAM],
103
+ additionalProperties: false,
104
+ };
105
+ }
106
+ function availableTypesSentence(registry) {
107
+ const types = registry.enabledTypes();
108
+ return types.length === 0
109
+ ? "No widget types are available."
110
+ : `Available widget types: ${types.join(", ")}.`;
111
+ }
112
+ /** Anything that is not a plain object carries no fields — treat it as empty. */
113
+ function toArgs(params) {
114
+ return params && typeof params === "object" && !Array.isArray(params)
115
+ ? params
116
+ : {};
117
+ }
118
+ function resolveWidget(registry, args) {
119
+ const raw = args[WIDGET_TYPE_PARAM];
120
+ const available = availableTypesSentence(registry);
121
+ if (raw === undefined || raw === null) {
122
+ throw new WidgetValidationError(`show_widget failed: "${WIDGET_TYPE_PARAM}" is required. ${available}`, "(none)", [WIDGET_TYPE_PARAM]);
123
+ }
124
+ if (typeof raw !== "string") {
125
+ throw new WidgetValidationError(`show_widget failed: "${WIDGET_TYPE_PARAM}" must be a string. ${available}`, "(none)", [WIDGET_TYPE_PARAM]);
126
+ }
127
+ const widget = registry.get(raw);
128
+ if (!widget) {
129
+ throw new WidgetValidationError(`show_widget failed: unknown ${WIDGET_TYPE_PARAM} "${raw}". ${available}`, raw, [WIDGET_TYPE_PARAM]);
130
+ }
131
+ if (!widget.enabled) {
132
+ throw new WidgetValidationError(`show_widget failed: ${WIDGET_TYPE_PARAM} "${raw}" is not enabled on this deployment. ${available}`, raw, [WIDGET_TYPE_PARAM]);
133
+ }
134
+ return widget;
135
+ }
136
+ /**
137
+ * Validate the remaining args against the widget's own field list. Every
138
+ * problem is reported in one message so the model can fix them all in a single
139
+ * retry instead of discovering them one call at a time.
140
+ */
141
+ function validateFields(widget, args) {
142
+ // Guarded fields are accepted as keys so the widget can answer them itself;
143
+ // they are absent from the description and the declared schema.
144
+ const guarded = widget.guardedFields ?? [];
145
+ const allowed = new Set([
146
+ WIDGET_TYPE_PARAM,
147
+ ...widget.fields.map((f) => f.name),
148
+ ...guarded.map((f) => f.name),
149
+ ]);
150
+ const problems = [];
151
+ const offending = [];
152
+ const unexpected = Object.keys(args).filter((key) => !allowed.has(key));
153
+ if (unexpected.length > 0) {
154
+ offending.push(...unexpected);
155
+ // Guarded names are parseable but not on offer, so they stay out of this list.
156
+ const offered = [WIDGET_TYPE_PARAM, ...widget.fields.map((f) => f.name)];
157
+ problems.push(`unexpected fields: ${unexpected.join(", ")} (allowed: ${offered.join(", ")})`);
158
+ }
159
+ const missing = [];
160
+ const invalid = [];
161
+ const values = {};
162
+ // A guarded field goes to the handler exactly as it arrived. Its answer is
163
+ // "drop this field", which is the same for every value, so complaining about
164
+ // the type or the length first would only buy a retry that corrects a field
165
+ // the model then has to remove anyway.
166
+ for (const field of guarded) {
167
+ const value = Object.hasOwn(args, field.name) ? args[field.name] : undefined;
168
+ if (value !== undefined && value !== null)
169
+ values[field.name] = value;
170
+ }
171
+ for (const field of widget.fields) {
172
+ // Own keys only: an inherited `reason`/`toString` must not read as a value
173
+ // the caller passed, since the unexpected-key check above only sees own keys.
174
+ const value = Object.hasOwn(args, field.name) ? args[field.name] : undefined;
175
+ if (value === undefined || value === null) {
176
+ if (field.required)
177
+ missing.push(field.name);
178
+ continue;
179
+ }
180
+ if (typeof value !== "string") {
181
+ invalid.push(`${field.name} (must be a ${field.type})`);
182
+ continue;
183
+ }
184
+ if (value.trim().length === 0) {
185
+ invalid.push(`${field.name} (must be a non-empty ${field.type}; omit it instead)`);
186
+ continue;
187
+ }
188
+ if (field.maxLength !== undefined && value.length > field.maxLength) {
189
+ invalid.push(`${field.name} (${value.length} chars, limit is ${field.maxLength})`);
190
+ continue;
191
+ }
192
+ values[field.name] = value;
193
+ }
194
+ if (missing.length > 0) {
195
+ offending.push(...missing);
196
+ problems.push(`missing required fields: ${missing.join(", ")}`);
197
+ }
198
+ if (invalid.length > 0) {
199
+ offending.push(...invalid.map((entry) => entry.split(" ")[0]));
200
+ problems.push(`invalid fields: ${invalid.join(", ")}`);
201
+ }
202
+ if (problems.length > 0) {
203
+ throw new WidgetValidationError(`show_widget failed for ${WIDGET_TYPE_PARAM} "${widget.widgetType}": ${problems.join("; ")}. ` +
204
+ "Fix the named fields and call show_widget again with the same widget_type.", widget.widgetType, offending);
205
+ }
206
+ return values;
207
+ }
208
+ /**
209
+ * Build the `show_widget` tool for one agent turn. `sessionId` comes from the
210
+ * plugin tool factory context and is used for logging only — nothing about the
211
+ * result depends on it.
212
+ */
213
+ export function createShowWidgetTool(options) {
214
+ const { registry, logger, sessionId, strategies } = options;
215
+ const session = sessionId ?? "(unknown)";
216
+ const ctx = { strategies };
217
+ return {
218
+ name: SHOW_WIDGET_TOOL_NAME,
219
+ description: buildShowWidgetDescription(registry),
220
+ parameters: buildShowWidgetParameters(registry),
221
+ async execute(_toolCallId, params) {
222
+ try {
223
+ const args = toArgs(params);
224
+ const widget = resolveWidget(registry, args);
225
+ const values = validateFields(widget, args);
226
+ const result = await widget.handler(values, ctx);
227
+ logger?.info(`[senpi-widgets] widget shown | widget_type=${widget.widgetType} widget_version=${widget.widgetVersion} session=${session}`);
228
+ return {
229
+ content: [{ type: "text", text: result.text }],
230
+ details: result.payload,
231
+ };
232
+ }
233
+ catch (err) {
234
+ if (err instanceof WidgetValidationError) {
235
+ logger?.warn(`[senpi-widgets] validation failed | widget_type=${err.widgetType} fields=${err.fields.join(",")} session=${session} | ${err.message}`);
236
+ }
237
+ else if (err instanceof WidgetLookupError) {
238
+ // The args were never judged — this is a backend problem, and it is the
239
+ // only signal that a card the user asked for could not be built.
240
+ logger?.warn(`[senpi-widgets] lookup failed | widget_type=${err.widgetType} session=${session} | ${err.message} | cause=${err.cause instanceof Error ? err.cause.message : String(err.cause ?? "(none)")}`);
241
+ }
242
+ else {
243
+ logger?.error?.(`[senpi-widgets] unhandled failure | session=${session} | ${err instanceof Error ? err.message : String(err)}`);
244
+ }
245
+ throw err;
246
+ }
247
+ },
248
+ };
249
+ }
250
+ //# sourceMappingURL=tool.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"tool.js","sourceRoot":"","sources":["../../src/widgets/tool.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,91 @@
1
+ /**
2
+ * Widget spec types.
3
+ *
4
+ * A widget spec is the single source for one `widget_type`: the fields it
5
+ * accepts, the text that teaches the model about it, and the handler that
6
+ * builds the result. The `show_widget` tool descriptor (enum + description) is
7
+ * generated from the spec list, so the two cannot drift.
8
+ */
9
+ /** Field value kinds a widget may declare. Extend as new widgets need them. */
10
+ export type WidgetFieldType = "string";
11
+ export interface WidgetFieldSpec {
12
+ readonly name: string;
13
+ readonly type: WidgetFieldType;
14
+ /** Enforced in `execute()`; the declared JSON schema keeps every field optional. */
15
+ readonly required: boolean;
16
+ /** One line, shown to the model in the generated tool description. */
17
+ readonly description: string;
18
+ /** Upper bound for string fields. */
19
+ readonly maxLength?: number;
20
+ }
21
+ /** Rendered by the client. Always carries the three shared keys. */
22
+ export interface WidgetPayload {
23
+ readonly widget_type: string;
24
+ readonly widget_version: number;
25
+ readonly status: string;
26
+ readonly [key: string]: unknown;
27
+ }
28
+ export interface WidgetResult {
29
+ readonly payload: WidgetPayload;
30
+ /** Short line for the agent — what happened and what not to do next. */
31
+ readonly text: string;
32
+ }
33
+ /** One of the caller's strategies, as the backend reported it. */
34
+ export interface StrategyMatch {
35
+ readonly id: string;
36
+ /** The address exactly as the backend spells it, not as the model typed it. */
37
+ readonly walletAddress: string;
38
+ /** Lifecycle status, e.g. "ACTIVE", "PAUSED", "CLOSED". */
39
+ readonly status: string;
40
+ readonly name: string;
41
+ }
42
+ export interface StrategyLookup {
43
+ /**
44
+ * `null` means the backend answered and no strategy of the caller's holds that
45
+ * address. A backend that did not answer usably **throws** — the two must never
46
+ * collapse, or one timeout turns a good address into "not yours".
47
+ */
48
+ findByWallet(wallet: string): Promise<StrategyMatch | null>;
49
+ }
50
+ /** Everything a handler may reach for beyond its own args. */
51
+ export interface WidgetContext {
52
+ /** Absent when the plugin registered the tool without a backend. */
53
+ readonly strategies?: StrategyLookup;
54
+ }
55
+ /**
56
+ * A field a widget always refuses but wants to diagnose itself, because another
57
+ * widget type declares it and the model can misroute it here.
58
+ *
59
+ * Matched by name only. No type or length check is applied, and the value
60
+ * reaches the handler exactly as it arrived: the widget's answer is "drop this
61
+ * field", which does not change with the value, and a type complaint would send
62
+ * the model back to correct a field it should be removing.
63
+ */
64
+ export interface GuardedFieldSpec {
65
+ readonly name: string;
66
+ /** Why it is refused. For readers of the spec; the model sees the widget's own error. */
67
+ readonly description: string;
68
+ }
69
+ export interface WidgetSpec {
70
+ readonly widgetType: string;
71
+ /** Bumped when the payload shape changes; the client falls back on unknown versions. */
72
+ readonly widgetVersion: number;
73
+ /** When to use this widget. Reproduced verbatim in the tool description. */
74
+ readonly description: string;
75
+ readonly fields: readonly WidgetFieldSpec[];
76
+ /** Never advertised in the description or the declared schema. */
77
+ readonly guardedFields?: readonly GuardedFieldSpec[];
78
+ /** Receives args validated against `fields`, plus any guarded field verbatim. */
79
+ readonly handler: (args: Readonly<Record<string, unknown>>, ctx: WidgetContext) => WidgetResult | Promise<WidgetResult>;
80
+ }
81
+ /** A spec plus its resolved on/off state from plugin config. */
82
+ export interface RegisteredWidget extends WidgetSpec {
83
+ readonly enabled: boolean;
84
+ }
85
+ export interface WidgetLogger {
86
+ info(...args: unknown[]): void;
87
+ warn(...args: unknown[]): void;
88
+ /** Unhandled failures only; every expected rejection is a WARN. */
89
+ error?(...args: unknown[]): void;
90
+ }
91
+ //# sourceMappingURL=types.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"types.d.ts","sourceRoot":"","sources":["../../src/widgets/types.ts"],"names":[],"mappings":"AAAA;;;;;;;GAOG;AAEH,+EAA+E;AAC/E,MAAM,MAAM,eAAe,GAAG,QAAQ,CAAC;AAEvC,MAAM,WAAW,eAAe;IAC9B,QAAQ,CAAC,IAAI,EAAE,MAAM,CAAC;IACtB,QAAQ,CAAC,IAAI,EAAE,eAAe,CAAC;IAC/B,oFAAoF;IACpF,QAAQ,CAAC,QAAQ,EAAE,OAAO,CAAC;IAC3B,sEAAsE;IACtE,QAAQ,CAAC,WAAW,EAAE,MAAM,CAAC;IAC7B,qCAAqC;IACrC,QAAQ,CAAC,SAAS,CAAC,EAAE,MAAM,CAAC;CAC7B;AAED,oEAAoE;AACpE,MAAM,WAAW,aAAa;IAC5B,QAAQ,CAAC,WAAW,EAAE,MAAM,CAAC;IAC7B,QAAQ,CAAC,cAAc,EAAE,MAAM,CAAC;IAChC,QAAQ,CAAC,MAAM,EAAE,MAAM,CAAC;IACxB,QAAQ,EAAE,GAAG,EAAE,MAAM,GAAG,OAAO,CAAC;CACjC;AAED,MAAM,WAAW,YAAY;IAC3B,QAAQ,CAAC,OAAO,EAAE,aAAa,CAAC;IAChC,wEAAwE;IACxE,QAAQ,CAAC,IAAI,EAAE,MAAM,CAAC;CACvB;AAED,kEAAkE;AAClE,MAAM,WAAW,aAAa;IAC5B,QAAQ,CAAC,EAAE,EAAE,MAAM,CAAC;IACpB,+EAA+E;IAC/E,QAAQ,CAAC,aAAa,EAAE,MAAM,CAAC;IAC/B,2DAA2D;IAC3D,QAAQ,CAAC,MAAM,EAAE,MAAM,CAAC;IACxB,QAAQ,CAAC,IAAI,EAAE,MAAM,CAAC;CACvB;AAED,MAAM,WAAW,cAAc;IAC7B;;;;OAIG;IACH,YAAY,CAAC,MAAM,EAAE,MAAM,GAAG,OAAO,CAAC,aAAa,GAAG,IAAI,CAAC,CAAC;CAC7D;AAED,8DAA8D;AAC9D,MAAM,WAAW,aAAa;IAC5B,oEAAoE;IACpE,QAAQ,CAAC,UAAU,CAAC,EAAE,cAAc,CAAC;CACtC;AAED;;;;;;;;GAQG;AACH,MAAM,WAAW,gBAAgB;IAC/B,QAAQ,CAAC,IAAI,EAAE,MAAM,CAAC;IACtB,yFAAyF;IACzF,QAAQ,CAAC,WAAW,EAAE,MAAM,CAAC;CAC9B;AAED,MAAM,WAAW,UAAU;IACzB,QAAQ,CAAC,UAAU,EAAE,MAAM,CAAC;IAC5B,wFAAwF;IACxF,QAAQ,CAAC,aAAa,EAAE,MAAM,CAAC;IAC/B,4EAA4E;IAC5E,QAAQ,CAAC,WAAW,EAAE,MAAM,CAAC;IAC7B,QAAQ,CAAC,MAAM,EAAE,SAAS,eAAe,EAAE,CAAC;IAC5C,kEAAkE;IAClE,QAAQ,CAAC,aAAa,CAAC,EAAE,SAAS,gBAAgB,EAAE,CAAC;IACrD,iFAAiF;IACjF,QAAQ,CAAC,OAAO,EAAE,CAChB,IAAI,EAAE,QAAQ,CAAC,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC,EACvC,GAAG,EAAE,aAAa,KACf,YAAY,GAAG,OAAO,CAAC,YAAY,CAAC,CAAC;CAC3C;AAED,gEAAgE;AAChE,MAAM,WAAW,gBAAiB,SAAQ,UAAU;IAClD,QAAQ,CAAC,OAAO,EAAE,OAAO,CAAC;CAC3B;AAED,MAAM,WAAW,YAAY;IAC3B,IAAI,CAAC,GAAG,IAAI,EAAE,OAAO,EAAE,GAAG,IAAI,CAAC;IAC/B,IAAI,CAAC,GAAG,IAAI,EAAE,OAAO,EAAE,GAAG,IAAI,CAAC;IAC/B,mEAAmE;IACnE,KAAK,CAAC,CAAC,GAAG,IAAI,EAAE,OAAO,EAAE,GAAG,IAAI,CAAC;CAClC"}
@@ -0,0 +1,10 @@
1
+ /**
2
+ * Widget spec types.
3
+ *
4
+ * A widget spec is the single source for one `widget_type`: the fields it
5
+ * accepts, the text that teaches the model about it, and the handler that
6
+ * builds the result. The `show_widget` tool descriptor (enum + description) is
7
+ * generated from the spec list, so the two cannot drift.
8
+ */
9
+ export {};
10
+ //# sourceMappingURL=types.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"types.js","sourceRoot":"","sources":["../../src/widgets/types.ts"],"names":[],"mappings":"AAAA;;;;;;;GAOG"}
@@ -0,0 +1,13 @@
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
@@ -0,0 +1 @@
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"}