@ixo/oracle-runtime-workers 0.31.0 → 0.32.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/package.json +4 -4
- package/src/core/middlewares/tool-repetition-guard.ts +6 -3
- package/src/core/middlewares/turn-boundary.ts +6 -1
- package/src/core/tool-not-run.ts +30 -0
- package/src/core/wrap-plugin-tool.ts +9 -1
- package/src/do/user-oracle-do.ts +78 -15
- package/src/hub-actions/chain-tx-refusals.ts +217 -0
- package/src/hub-actions/client.ts +91 -27
- package/src/hub-actions/request.ts +32 -25
- package/src/hub-actions/transfer.ts +76 -26
- package/src/plugin-api/types.ts +59 -18
- package/src/plugins/hub-actions/chain-tx-catalog.ts +1239 -0
- package/src/plugins/hub-actions/hub-actions.plugin.ts +394 -215
- package/src/plugins/ixo-chain/ixo-chain.plugin.ts +1 -1
- package/src/plugins/ixo-chain/kinds/chain/index.ts +2 -0
- package/src/plugins/ixo-chain/kinds/chain/validators.ts +151 -0
- package/src/plugins/yoma/engine.ts +107 -55
- package/src/plugins/yoma/submission.ts +3 -6
- package/src/plugins/yoma/yoma.plugin.ts +12 -3
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@ixo/oracle-runtime-workers",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.32.0",
|
|
4
4
|
"description": "QiForge oracle runtime for Cloudflare Workers — Durable Object per user (SQLite in WASM over DO storage), Matrix E2EE gateway Durable Object, Hono HTTP/SSE shell.",
|
|
5
5
|
"private": false,
|
|
6
6
|
"publishConfig": {
|
|
@@ -34,7 +34,7 @@
|
|
|
34
34
|
"!src/plugins/pod-creator/test-fixtures.ts"
|
|
35
35
|
],
|
|
36
36
|
"dependencies": {
|
|
37
|
-
"@ixo/action-protocol": "0.
|
|
37
|
+
"@ixo/action-protocol": "0.8.0",
|
|
38
38
|
"@blocknote/server-util": "0.29.1",
|
|
39
39
|
"@composio/core": "0.10.0",
|
|
40
40
|
"@composio/langchain": "0.9.2",
|
|
@@ -67,9 +67,9 @@
|
|
|
67
67
|
"protobufjs": "^7.6.5",
|
|
68
68
|
"@ixo/topic-protocol": "1.0.0-rc.8",
|
|
69
69
|
"survey-core": "2.3.11",
|
|
70
|
-
"@ixo/
|
|
70
|
+
"@ixo/common": "1.7.1",
|
|
71
71
|
"@ixo/ucan": "2.3.0",
|
|
72
|
-
"@ixo/
|
|
72
|
+
"@ixo/ixo-transaction": "0.2.0"
|
|
73
73
|
},
|
|
74
74
|
"devDependencies": {
|
|
75
75
|
"@cloudflare/vitest-pool-workers": "^0.22.0",
|
|
@@ -7,6 +7,7 @@ import { type AgentMiddleware, createMiddleware } from 'langchain';
|
|
|
7
7
|
import type { Logger } from '../../plugin-api/types';
|
|
8
8
|
import { NOOP_LOGGER } from '../utils';
|
|
9
9
|
import { isCapabilityGateRefusal } from './capability-gate';
|
|
10
|
+
import { isToolNotRun } from '../tool-not-run';
|
|
10
11
|
import { turnCarryOf, turnStart } from './turn-boundary';
|
|
11
12
|
|
|
12
13
|
export { turnStart };
|
|
@@ -145,9 +146,11 @@ export const createToolRepetitionGuardMiddleware = (
|
|
|
145
146
|
const msg = messages[i];
|
|
146
147
|
if (!(msg instanceof ToolMessage)) continue;
|
|
147
148
|
if (msg.status !== 'error') continue;
|
|
148
|
-
// The capability gate refused it without running it
|
|
149
|
-
//
|
|
150
|
-
|
|
149
|
+
// The capability gate refused it without running it, or the tool
|
|
150
|
+
// said it did not run until a step comes first: after that step
|
|
151
|
+
// (`load_capability`, reading the rules) the same call is the
|
|
152
|
+
// expected next one.
|
|
153
|
+
if (isCapabilityGateRefusal(msg) || isToolNotRun(msg)) continue;
|
|
151
154
|
if (msg.name !== toolName) continue;
|
|
152
155
|
|
|
153
156
|
const priorArgs = argsById.get(msg.tool_call_id);
|
|
@@ -22,6 +22,7 @@ import {
|
|
|
22
22
|
} from '@langchain/core/messages';
|
|
23
23
|
import { isAttachmentViewMessage } from '../../attachments/retention';
|
|
24
24
|
import { isCapabilityGateRefusal } from './capability-gate';
|
|
25
|
+
import { isToolNotRun } from '../tool-not-run';
|
|
25
26
|
|
|
26
27
|
export const SUMMARY_PREFIX = 'Here is a summary of the conversation to date:';
|
|
27
28
|
|
|
@@ -140,7 +141,11 @@ export function carriedCallsOfTurn(
|
|
|
140
141
|
if (call.id) calls.set(call.id, { name: call.name, args: call.args });
|
|
141
142
|
continue;
|
|
142
143
|
}
|
|
143
|
-
if (
|
|
144
|
+
if (
|
|
145
|
+
!ToolMessage.isInstance(message) ||
|
|
146
|
+
isCapabilityGateRefusal(message) ||
|
|
147
|
+
isToolNotRun(message)
|
|
148
|
+
)
|
|
144
149
|
continue;
|
|
145
150
|
const call = calls.get(message.tool_call_id);
|
|
146
151
|
if (!call) continue;
|
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
import { ToolMessage } from '@langchain/core/messages';
|
|
2
|
+
|
|
3
|
+
/** `response_metadata` key of a result whose tool did not run (never sent to a provider). */
|
|
4
|
+
const NOT_RUN = 'toolNotRun';
|
|
5
|
+
|
|
6
|
+
/**
|
|
7
|
+
* A tool result saying the tool did not run because another step must come
|
|
8
|
+
* first (for example reading a message type's rules before preparing it).
|
|
9
|
+
* It is an error result, so it never counts as a write that happened, and
|
|
10
|
+
* like a capability-gate refusal the repetition guard lets the identical
|
|
11
|
+
* call through once the step is done (`isToolNotRun`).
|
|
12
|
+
*/
|
|
13
|
+
export function toolNotRunMessage(input: {
|
|
14
|
+
toolCallId: string;
|
|
15
|
+
name: string;
|
|
16
|
+
content: string;
|
|
17
|
+
}): ToolMessage {
|
|
18
|
+
return new ToolMessage({
|
|
19
|
+
tool_call_id: input.toolCallId,
|
|
20
|
+
name: input.name,
|
|
21
|
+
content: input.content,
|
|
22
|
+
status: 'error',
|
|
23
|
+
response_metadata: { [NOT_RUN]: true },
|
|
24
|
+
});
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
/** A result `toolNotRunMessage` made: the tool did not run. */
|
|
28
|
+
export function isToolNotRun(message: ToolMessage): boolean {
|
|
29
|
+
return message.response_metadata?.[NOT_RUN] === true;
|
|
30
|
+
}
|
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import { ToolMessage } from '@langchain/core/messages';
|
|
1
2
|
import { Command } from '@langchain/langgraph';
|
|
2
3
|
import { capToolResult, type ResultCapConfig } from './middlewares/result-cap';
|
|
3
4
|
import { tool } from '@langchain/core/tools';
|
|
@@ -125,7 +126,14 @@ export function wrapPluginTool(
|
|
|
125
126
|
if (pluginName !== undefined)
|
|
126
127
|
requireToolPlane(ctx, pluginName, pluginTool);
|
|
127
128
|
const output = await pluginTool.handler(args, ctx);
|
|
128
|
-
|
|
129
|
+
// A tool's own message (a result that says the tool did not run) is
|
|
130
|
+
// returned whole, never capped into text.
|
|
131
|
+
if (
|
|
132
|
+
!resultCap ||
|
|
133
|
+
output instanceof Command ||
|
|
134
|
+
ToolMessage.isInstance(output) ||
|
|
135
|
+
output == null
|
|
136
|
+
)
|
|
129
137
|
return output;
|
|
130
138
|
const text = typeof output === 'string' ? output : JSON.stringify(output);
|
|
131
139
|
if (typeof text !== 'string' || text.length <= resultCap.capChars)
|
package/src/do/user-oracle-do.ts
CHANGED
|
@@ -54,7 +54,7 @@ import {
|
|
|
54
54
|
import { chainTxOperation, prepareHubAction } from '../hub-actions/request';
|
|
55
55
|
import {
|
|
56
56
|
bankReceiptSummary,
|
|
57
|
-
|
|
57
|
+
chainTxAmountsInBaseUnits,
|
|
58
58
|
} from '../hub-actions/transfer';
|
|
59
59
|
import {
|
|
60
60
|
HUB_CHECK_AFTER_EXPIRY_MS,
|
|
@@ -638,6 +638,14 @@ const ACTION_TICK_BUDGET_MS = 15_000;
|
|
|
638
638
|
const ACTION_PRUNE_INTERVAL_MS = 60 * 60_000;
|
|
639
639
|
/** How long the Hub's capabilities answer is reused per object. */
|
|
640
640
|
const HUB_CAPABILITIES_CACHE_MS = 60_000;
|
|
641
|
+
/**
|
|
642
|
+
* A capabilities read on a turn's critical path (the chain transaction
|
|
643
|
+
* tool's types): a short timeout, and after a failed read none for this
|
|
644
|
+
* long, so a slow or failing Hub costs a turn at most this timeout once per
|
|
645
|
+
* window instead of the full Hub timeout on every turn.
|
|
646
|
+
*/
|
|
647
|
+
const HUB_TURN_CAPABILITIES_TIMEOUT_MS = 3_000;
|
|
648
|
+
const HUB_CAPABILITIES_FAILURE_CACHE_MS = 30_000;
|
|
641
649
|
/** A Hub network name as the chain-id config key it is configured under. */
|
|
642
650
|
/**
|
|
643
651
|
* Whether two Hub outcomes are the same event. Receipts are compared in the
|
|
@@ -1054,11 +1062,17 @@ export function createUserOracleDO(opts: UserOracleDOOptions) {
|
|
|
1054
1062
|
private actionProposals: ActionProposalStore | null = null;
|
|
1055
1063
|
private hubActionStore: HubActionStore | null = null;
|
|
1056
1064
|
private hubActionClient: HubActionClient | null = null;
|
|
1057
|
-
/** The Hub's capabilities for this oracle account, reused for `HUB_CAPABILITIES_CACHE_MS
|
|
1065
|
+
/** The Hub's capabilities for this oracle account, reused for `HUB_CAPABILITIES_CACHE_MS` (and on a turn, while a read fails). */
|
|
1058
1066
|
private hubCapabilities: {
|
|
1059
1067
|
value: ActionCapabilitiesV1;
|
|
1060
1068
|
at: number;
|
|
1061
1069
|
} | null = null;
|
|
1070
|
+
/** The last failed capabilities read: a turn does not read again within `HUB_CAPABILITIES_FAILURE_CACHE_MS`. */
|
|
1071
|
+
private hubCapabilitiesFailure: { error: unknown; at: number } | null =
|
|
1072
|
+
null;
|
|
1073
|
+
/** A turn's capabilities read in flight, shared by concurrent turns. */
|
|
1074
|
+
private hubTurnCapabilitiesRead: Promise<ActionCapabilitiesV1> | null =
|
|
1075
|
+
null;
|
|
1062
1076
|
/** The last `hub_action_options` answer for this owner (Gmail connection checks). */
|
|
1063
1077
|
private lastHubOptions: {
|
|
1064
1078
|
value: HubActionOptionsV2 | HubActionOptionsV3;
|
|
@@ -2277,7 +2291,10 @@ export function createUserOracleDO(opts: UserOracleDOOptions) {
|
|
|
2277
2291
|
...(this.hubActionStore && this.hubActionClient
|
|
2278
2292
|
? {
|
|
2279
2293
|
hubActions: {
|
|
2280
|
-
capabilities: () =>
|
|
2294
|
+
capabilities: (opts) =>
|
|
2295
|
+
opts?.turn
|
|
2296
|
+
? this.turnHubCapabilities()
|
|
2297
|
+
: this.cachedHubCapabilities(),
|
|
2281
2298
|
options: (input) => this.hubActionOptions(input),
|
|
2282
2299
|
request: (input: {
|
|
2283
2300
|
ownerDid: string;
|
|
@@ -2385,6 +2402,8 @@ export function createUserOracleDO(opts: UserOracleDOOptions) {
|
|
|
2385
2402
|
this.hubActionStore = null;
|
|
2386
2403
|
this.hubActionClient = null;
|
|
2387
2404
|
this.hubCapabilities = null;
|
|
2405
|
+
this.hubCapabilitiesFailure = null;
|
|
2406
|
+
this.hubTurnCapabilitiesRead = null;
|
|
2388
2407
|
this.lastHubOptions = null;
|
|
2389
2408
|
this.topicProposals = null;
|
|
2390
2409
|
this.attachmentTextCache = null;
|
|
@@ -3524,16 +3543,57 @@ export function createUserOracleDO(opts: UserOracleDOOptions) {
|
|
|
3524
3543
|
}
|
|
3525
3544
|
|
|
3526
3545
|
/** The Hub's capabilities, cached per object for `HUB_CAPABILITIES_CACHE_MS`. */
|
|
3527
|
-
private async cachedHubCapabilities(
|
|
3546
|
+
private async cachedHubCapabilities(
|
|
3547
|
+
timeoutMs?: number,
|
|
3548
|
+
): Promise<ActionCapabilitiesV1> {
|
|
3528
3549
|
const client = this.hubActionClient;
|
|
3529
3550
|
if (!client) throw new Error('Hub actions are unavailable');
|
|
3530
3551
|
const now = Date.now();
|
|
3531
3552
|
const cached = this.hubCapabilities ?? null;
|
|
3532
3553
|
if (cached && now - cached.at < HUB_CAPABILITIES_CACHE_MS)
|
|
3533
3554
|
return cached.value;
|
|
3534
|
-
|
|
3535
|
-
|
|
3536
|
-
|
|
3555
|
+
try {
|
|
3556
|
+
const value = await client.capabilities(timeoutMs);
|
|
3557
|
+
this.hubCapabilities = { value, at: Date.now() };
|
|
3558
|
+
this.hubCapabilitiesFailure = null;
|
|
3559
|
+
return value;
|
|
3560
|
+
} catch (error) {
|
|
3561
|
+
this.hubCapabilitiesFailure = { error, at: Date.now() };
|
|
3562
|
+
throw error;
|
|
3563
|
+
}
|
|
3564
|
+
}
|
|
3565
|
+
|
|
3566
|
+
/**
|
|
3567
|
+
* The capabilities for a turn's tool list: the cached ones while fresh;
|
|
3568
|
+
* else one read with `HUB_TURN_CAPABILITIES_TIMEOUT_MS`, shared by
|
|
3569
|
+
* concurrent turns and not repeated within
|
|
3570
|
+
* `HUB_CAPABILITIES_FAILURE_CACHE_MS` of a failed read. While reads
|
|
3571
|
+
* fail, the last capabilities read are answered (the Hub enforces its
|
|
3572
|
+
* own on every prepare); with none, the failure.
|
|
3573
|
+
*/
|
|
3574
|
+
private async turnHubCapabilities(): Promise<ActionCapabilitiesV1> {
|
|
3575
|
+
const cached = this.hubCapabilities;
|
|
3576
|
+
if (cached && Date.now() - cached.at < HUB_CAPABILITIES_CACHE_MS)
|
|
3577
|
+
return cached.value;
|
|
3578
|
+
const failure = this.hubCapabilitiesFailure;
|
|
3579
|
+
if (
|
|
3580
|
+
failure &&
|
|
3581
|
+
Date.now() - failure.at < HUB_CAPABILITIES_FAILURE_CACHE_MS
|
|
3582
|
+
) {
|
|
3583
|
+
if (cached) return cached.value;
|
|
3584
|
+
throw failure.error;
|
|
3585
|
+
}
|
|
3586
|
+
this.hubTurnCapabilitiesRead ??= this.cachedHubCapabilities(
|
|
3587
|
+
HUB_TURN_CAPABILITIES_TIMEOUT_MS,
|
|
3588
|
+
).finally(() => {
|
|
3589
|
+
this.hubTurnCapabilitiesRead = null;
|
|
3590
|
+
});
|
|
3591
|
+
try {
|
|
3592
|
+
return await this.hubTurnCapabilitiesRead;
|
|
3593
|
+
} catch (error) {
|
|
3594
|
+
if (cached) return cached.value;
|
|
3595
|
+
throw error;
|
|
3596
|
+
}
|
|
3537
3597
|
}
|
|
3538
3598
|
|
|
3539
3599
|
/**
|
|
@@ -3586,13 +3646,14 @@ export function createUserOracleDO(opts: UserOracleDOOptions) {
|
|
|
3586
3646
|
/**
|
|
3587
3647
|
* One chain transaction of the model's messages. The network is the
|
|
3588
3648
|
* account's capability, the chain id this Worker's own for that
|
|
3589
|
-
* network; the signer and the owner's identity in
|
|
3649
|
+
* network; the signer and the owner's identity in the messages come
|
|
3590
3650
|
* from the owner's DID (`chainTxOperation`). The model names none of
|
|
3591
|
-
* them. Bank steps name a listed token and a display amount
|
|
3592
|
-
*
|
|
3593
|
-
* must be current), and each
|
|
3594
|
-
* checked against the listed
|
|
3595
|
-
* is sent: the Hub
|
|
3651
|
+
* them. Bank steps name a listed token and a display amount, stake
|
|
3652
|
+
* steps an amount of IXO: with at least one of either, the owner's
|
|
3653
|
+
* options are read once, fresh (the balances must be current), and each
|
|
3654
|
+
* amount is converted to base units and checked against the listed
|
|
3655
|
+
* balance (`chainTxAmountsInBaseUnits`). No fee is sent: the Hub
|
|
3656
|
+
* simulates gas and sets it.
|
|
3596
3657
|
*/
|
|
3597
3658
|
private async chainTransactionHubAction(input: {
|
|
3598
3659
|
ownerDid: string;
|
|
@@ -3624,7 +3685,9 @@ export function createUserOracleDO(opts: UserOracleDOOptions) {
|
|
|
3624
3685
|
HUB_NETWORK_CHAIN[capabilities.network]
|
|
3625
3686
|
];
|
|
3626
3687
|
const tokens = input.messages.some(
|
|
3627
|
-
(message) =>
|
|
3688
|
+
(message) =>
|
|
3689
|
+
message.type === 'cosmos.bank.send' ||
|
|
3690
|
+
message.type === 'cosmos.staking.delegate',
|
|
3628
3691
|
)
|
|
3629
3692
|
? ((await this.hubActionOptions(input, true)).transfer?.tokens ?? null)
|
|
3630
3693
|
: null;
|
|
@@ -3633,7 +3696,7 @@ export function createUserOracleDO(opts: UserOracleDOOptions) {
|
|
|
3633
3696
|
chainId,
|
|
3634
3697
|
network: capabilities.network,
|
|
3635
3698
|
memo: input.memo,
|
|
3636
|
-
messages:
|
|
3699
|
+
messages: chainTxAmountsInBaseUnits(input.messages, tokens),
|
|
3637
3700
|
});
|
|
3638
3701
|
return this.requestHubAction({ ...input, operation }, true);
|
|
3639
3702
|
}
|
|
@@ -0,0 +1,217 @@
|
|
|
1
|
+
import {
|
|
2
|
+
CHAIN_TX_MAX_MESSAGES,
|
|
3
|
+
CHAIN_TX_REFUSAL_CODES,
|
|
4
|
+
type ChainTxRefusal,
|
|
5
|
+
type ChainTxRefusalCode,
|
|
6
|
+
} from '@ixo/action-protocol';
|
|
7
|
+
|
|
8
|
+
/**
|
|
9
|
+
* What each chain transaction refusal code means, for the person (`means`,
|
|
10
|
+
* plain words the model may say) and what the model does next (`next`).
|
|
11
|
+
* Auth Hub sends the code when it will not make a card from what it read on
|
|
12
|
+
* chain, or when the node's simulation names one message
|
|
13
|
+
* (`invalid_message: messages[<i>].<field>: <code>`); the reason text itself
|
|
14
|
+
* is never given to the person. `satisfies` makes every code of the
|
|
15
|
+
* protocol need an entry.
|
|
16
|
+
*/
|
|
17
|
+
export const CHAIN_TX_REFUSAL_WORDS = {
|
|
18
|
+
validator_not_found: {
|
|
19
|
+
means: 'there is no validator with that address on the network',
|
|
20
|
+
next: 'Find the validator again with chain_lookup validators and copy its address exactly.',
|
|
21
|
+
},
|
|
22
|
+
no_stake: {
|
|
23
|
+
means: 'they have nothing staked with this validator',
|
|
24
|
+
next: 'Check where their stake is with chain_lookup staking.',
|
|
25
|
+
},
|
|
26
|
+
stake_too_small: {
|
|
27
|
+
means: 'that is more than they have staked with this validator',
|
|
28
|
+
next: 'Check how much is staked there with chain_lookup staking and ask for a smaller amount.',
|
|
29
|
+
},
|
|
30
|
+
same_validator: {
|
|
31
|
+
means: 'the stake is already with that validator',
|
|
32
|
+
next: 'Ask which other validator it should move to.',
|
|
33
|
+
},
|
|
34
|
+
invalid_amount: {
|
|
35
|
+
means: 'the network does not accept that amount',
|
|
36
|
+
next: 'Ask for the amount again.',
|
|
37
|
+
},
|
|
38
|
+
nothing_to_collect: {
|
|
39
|
+
means: 'there are no rewards to collect from this validator yet',
|
|
40
|
+
next: 'Leave this validator out of the card.',
|
|
41
|
+
},
|
|
42
|
+
proposal_not_found: {
|
|
43
|
+
means: 'there is no proposal with that number',
|
|
44
|
+
next: 'Find the open proposals with chain_lookup gov_proposals.',
|
|
45
|
+
},
|
|
46
|
+
proposal_not_open: {
|
|
47
|
+
means:
|
|
48
|
+
'voting on this proposal is not open (it has closed, or not started yet)',
|
|
49
|
+
next: 'Tell them; a vote cannot be recorded now.',
|
|
50
|
+
},
|
|
51
|
+
invalid_vote_option: {
|
|
52
|
+
means: 'that is not a vote the network accepts',
|
|
53
|
+
next: 'Ask whether they vote yes, no, abstain or no with veto.',
|
|
54
|
+
},
|
|
55
|
+
token_not_found: {
|
|
56
|
+
means: 'one of these impact tokens could not be found',
|
|
57
|
+
next: 'Check the tokens they hold with chain_lookup token_balances.',
|
|
58
|
+
},
|
|
59
|
+
collection_not_found: {
|
|
60
|
+
means: 'there is no claim collection with that number',
|
|
61
|
+
next: 'Find the collection with chain_lookup claim_collections.',
|
|
62
|
+
},
|
|
63
|
+
reservations_off: {
|
|
64
|
+
means: 'this collection does not take reservations',
|
|
65
|
+
next: 'File the claim without a reservation.',
|
|
66
|
+
},
|
|
67
|
+
reservation_exists: {
|
|
68
|
+
means: 'they already have a reservation in this collection',
|
|
69
|
+
next: 'File the claim with it, or wait for it to run out.',
|
|
70
|
+
},
|
|
71
|
+
claim_not_found: {
|
|
72
|
+
means: 'there is no claim with that id',
|
|
73
|
+
next: 'Find the claim with chain_lookup claims.',
|
|
74
|
+
},
|
|
75
|
+
claim_in_other_collection: {
|
|
76
|
+
means: 'that claim belongs to a different collection',
|
|
77
|
+
next: 'Check the claim’s collection with chain_lookup claim.',
|
|
78
|
+
},
|
|
79
|
+
wrong_admin: {
|
|
80
|
+
means: 'that is not the collection’s admin account',
|
|
81
|
+
next: 'Copy the admin account from chain_lookup claim_collection.',
|
|
82
|
+
},
|
|
83
|
+
already_in_state: {
|
|
84
|
+
means: 'the collection is already in that state',
|
|
85
|
+
next: 'Nothing needs to change; tell them.',
|
|
86
|
+
},
|
|
87
|
+
start_date_missing: {
|
|
88
|
+
means: 'the collection has no start date yet',
|
|
89
|
+
next: 'Ask when it should open and include that start date.',
|
|
90
|
+
},
|
|
91
|
+
end_before_start: {
|
|
92
|
+
means: 'the end date would come before the start date',
|
|
93
|
+
next: 'Ask for dates where the end comes after the start.',
|
|
94
|
+
},
|
|
95
|
+
quota_below_count: {
|
|
96
|
+
means: 'the new limit is below the number of claims already filed',
|
|
97
|
+
next: 'Check the count with chain_lookup claim_collection and ask for a higher limit.',
|
|
98
|
+
},
|
|
99
|
+
token_payments_present: {
|
|
100
|
+
means:
|
|
101
|
+
'that payment includes contract tokens, which cannot be changed from here',
|
|
102
|
+
next: 'Leave that payment out of the card so it stays as it is.',
|
|
103
|
+
},
|
|
104
|
+
ends_too_soon: {
|
|
105
|
+
means:
|
|
106
|
+
'the end date is too soon: it has to be after this review link expires',
|
|
107
|
+
next: 'Ask for a later end date.',
|
|
108
|
+
},
|
|
109
|
+
deadline_too_soon: {
|
|
110
|
+
means:
|
|
111
|
+
'the date to decide by is too soon: it has to be after this review link expires',
|
|
112
|
+
next: 'Ask for a later date.',
|
|
113
|
+
},
|
|
114
|
+
current_permission_ends_too_soon: {
|
|
115
|
+
means: 'their current permission ends too soon to keep its end date',
|
|
116
|
+
next: 'Ask whether to set a new end date.',
|
|
117
|
+
},
|
|
118
|
+
unlimited_permission_exists: {
|
|
119
|
+
means: 'they already have an unlimited permission of this kind',
|
|
120
|
+
next: 'It has to be removed first; ask whether to remove it in the same card.',
|
|
121
|
+
},
|
|
122
|
+
other_permission_exists: {
|
|
123
|
+
means: 'a different kind of permission is already set up for them here',
|
|
124
|
+
next: 'It has to be removed first; ask whether to remove it in the same card.',
|
|
125
|
+
},
|
|
126
|
+
too_many_constraints: {
|
|
127
|
+
means:
|
|
128
|
+
'this permission already covers too many collections to change it from a card',
|
|
129
|
+
next: 'Some collections have to be removed from it first; tell them.',
|
|
130
|
+
},
|
|
131
|
+
no_such_permission: {
|
|
132
|
+
means: 'they do not have this permission',
|
|
133
|
+
next: 'Check what they have with chain_lookup authz_grants.',
|
|
134
|
+
},
|
|
135
|
+
collection_of_other_entity: {
|
|
136
|
+
means: 'that collection belongs to a different entity',
|
|
137
|
+
next: 'Check the collection with chain_lookup claim_collection.',
|
|
138
|
+
},
|
|
139
|
+
entity_not_found: {
|
|
140
|
+
means: 'there is no entity with that id',
|
|
141
|
+
next: 'Find the entity with chain_lookup entities.',
|
|
142
|
+
},
|
|
143
|
+
account_name_taken: {
|
|
144
|
+
means: 'this entity already has an account with that name',
|
|
145
|
+
next: 'Ask for another name.',
|
|
146
|
+
},
|
|
147
|
+
document_not_found: {
|
|
148
|
+
means: 'there is no identity record with that id',
|
|
149
|
+
next: 'Check the id with chain_lookup did_document.',
|
|
150
|
+
},
|
|
151
|
+
nothing_to_remove: {
|
|
152
|
+
means: 'that item is not in the identity record',
|
|
153
|
+
next: 'Read the record with chain_lookup did_document and copy the id exactly.',
|
|
154
|
+
},
|
|
155
|
+
already_exists: {
|
|
156
|
+
means: 'an item with that id is already there',
|
|
157
|
+
next: 'Choose another id, or remove the old item first.',
|
|
158
|
+
},
|
|
159
|
+
not_available: {
|
|
160
|
+
means: 'that isn’t available on this service yet',
|
|
161
|
+
next: 'Do not retry; tell the person it cannot be done here for now.',
|
|
162
|
+
},
|
|
163
|
+
chain_data_too_large: {
|
|
164
|
+
means: 'this is too large to change in one go here',
|
|
165
|
+
next: 'Do not retry; tell the person it cannot be done through chat.',
|
|
166
|
+
},
|
|
167
|
+
invalid_did: {
|
|
168
|
+
means: 'one of the identifiers is not a valid ID on the network',
|
|
169
|
+
next: 'Copy it again exactly from a lookup.',
|
|
170
|
+
},
|
|
171
|
+
} as const satisfies Record<
|
|
172
|
+
ChainTxRefusalCode,
|
|
173
|
+
{ means: string; next: string }
|
|
174
|
+
>;
|
|
175
|
+
|
|
176
|
+
/**
|
|
177
|
+
* A message field path as the protocol names one (`validatorAddress`,
|
|
178
|
+
* `payments.approval.amount[1].denom`): checked here as well, because the
|
|
179
|
+
* path is repeated in text the model reads.
|
|
180
|
+
*/
|
|
181
|
+
const REFUSAL_FIELD =
|
|
182
|
+
/^[A-Za-z][A-Za-z0-9_]*(?:\[\d{1,3}\])?(?:\.[A-Za-z][A-Za-z0-9_]*(?:\[\d{1,3}\])?){0,5}$/;
|
|
183
|
+
|
|
184
|
+
/**
|
|
185
|
+
* A parsed refusal the runtime puts into words: a message index 0..7, a
|
|
186
|
+
* plain field path of at most 100 characters and a code of the protocol's
|
|
187
|
+
* list. Anything else is passed on as the Hub's quoted reason instead.
|
|
188
|
+
*/
|
|
189
|
+
export function wordableRefusal(
|
|
190
|
+
refusal: ChainTxRefusal | null,
|
|
191
|
+
): (ChainTxRefusal & { code: ChainTxRefusalCode }) | null {
|
|
192
|
+
if (!refusal) return null;
|
|
193
|
+
const { index, field, code } = refusal;
|
|
194
|
+
if (
|
|
195
|
+
!Number.isInteger(index) ||
|
|
196
|
+
index < 0 ||
|
|
197
|
+
index >= CHAIN_TX_MAX_MESSAGES ||
|
|
198
|
+
field.length > 100 ||
|
|
199
|
+
!REFUSAL_FIELD.test(field) ||
|
|
200
|
+
code === null ||
|
|
201
|
+
!CHAIN_TX_REFUSAL_CODES.includes(code)
|
|
202
|
+
)
|
|
203
|
+
return null;
|
|
204
|
+
return { index, field, code };
|
|
205
|
+
}
|
|
206
|
+
|
|
207
|
+
/**
|
|
208
|
+
* A coded chain transaction refusal in the model's words: which step, what
|
|
209
|
+
* it means for the person and the next step. The field and code are for
|
|
210
|
+
* the model only; the Hub's reason text is not repeated.
|
|
211
|
+
*/
|
|
212
|
+
export function chainTxRefusalWords(
|
|
213
|
+
refusal: ChainTxRefusal & { code: ChainTxRefusalCode },
|
|
214
|
+
): string {
|
|
215
|
+
const words = CHAIN_TX_REFUSAL_WORDS[refusal.code];
|
|
216
|
+
return `Auth Hub could not prepare this transaction: in step ${refusal.index + 1}, ${words.means}. ${words.next} Tell the person in your own plain words; this is not text to show them (for you: messages[${refusal.index}].${refusal.field}, ${refusal.code}).`;
|
|
217
|
+
}
|