@ixo/oracle-runtime-workers 0.31.0 → 0.33.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 -17
- package/src/hub-actions/chain-tx-refusals.ts +307 -0
- package/src/hub-actions/client.ts +128 -30
- package/src/hub-actions/request.ts +32 -25
- package/src/hub-actions/transfer.ts +187 -33
- package/src/plugin-api/types.ts +103 -25
- package/src/plugins/hub-actions/chain-tx-catalog.ts +1451 -0
- package/src/plugins/hub-actions/hub-actions.plugin.ts +558 -222
- 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/ixo-chain/kinds/indexed/dao-proposals.ts +37 -2
- package/src/plugins/ixo-chain/kinds/indexed/daos.ts +145 -9
- 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.33.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.9.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/common": "1.7.1",
|
|
70
71
|
"@ixo/ixo-transaction": "0.2.0",
|
|
71
|
-
"@ixo/ucan": "2.3.0"
|
|
72
|
-
"@ixo/common": "1.7.1"
|
|
72
|
+
"@ixo/ucan": "2.3.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,8 @@ import {
|
|
|
54
54
|
import { chainTxOperation, prepareHubAction } from '../hub-actions/request';
|
|
55
55
|
import {
|
|
56
56
|
bankReceiptSummary,
|
|
57
|
-
|
|
57
|
+
chainTxAmountsInBaseUnits,
|
|
58
|
+
chainTxNeedsTokenList,
|
|
58
59
|
} from '../hub-actions/transfer';
|
|
59
60
|
import {
|
|
60
61
|
HUB_CHECK_AFTER_EXPIRY_MS,
|
|
@@ -638,6 +639,14 @@ const ACTION_TICK_BUDGET_MS = 15_000;
|
|
|
638
639
|
const ACTION_PRUNE_INTERVAL_MS = 60 * 60_000;
|
|
639
640
|
/** How long the Hub's capabilities answer is reused per object. */
|
|
640
641
|
const HUB_CAPABILITIES_CACHE_MS = 60_000;
|
|
642
|
+
/**
|
|
643
|
+
* A capabilities read on a turn's critical path (the chain transaction
|
|
644
|
+
* tool's types): a short timeout, and after a failed read none for this
|
|
645
|
+
* long, so a slow or failing Hub costs a turn at most this timeout once per
|
|
646
|
+
* window instead of the full Hub timeout on every turn.
|
|
647
|
+
*/
|
|
648
|
+
const HUB_TURN_CAPABILITIES_TIMEOUT_MS = 3_000;
|
|
649
|
+
const HUB_CAPABILITIES_FAILURE_CACHE_MS = 30_000;
|
|
641
650
|
/** A Hub network name as the chain-id config key it is configured under. */
|
|
642
651
|
/**
|
|
643
652
|
* Whether two Hub outcomes are the same event. Receipts are compared in the
|
|
@@ -1054,11 +1063,17 @@ export function createUserOracleDO(opts: UserOracleDOOptions) {
|
|
|
1054
1063
|
private actionProposals: ActionProposalStore | null = null;
|
|
1055
1064
|
private hubActionStore: HubActionStore | null = null;
|
|
1056
1065
|
private hubActionClient: HubActionClient | null = null;
|
|
1057
|
-
/** The Hub's capabilities for this oracle account, reused for `HUB_CAPABILITIES_CACHE_MS
|
|
1066
|
+
/** The Hub's capabilities for this oracle account, reused for `HUB_CAPABILITIES_CACHE_MS` (and on a turn, while a read fails). */
|
|
1058
1067
|
private hubCapabilities: {
|
|
1059
1068
|
value: ActionCapabilitiesV1;
|
|
1060
1069
|
at: number;
|
|
1061
1070
|
} | null = null;
|
|
1071
|
+
/** The last failed capabilities read: a turn does not read again within `HUB_CAPABILITIES_FAILURE_CACHE_MS`. */
|
|
1072
|
+
private hubCapabilitiesFailure: { error: unknown; at: number } | null =
|
|
1073
|
+
null;
|
|
1074
|
+
/** A turn's capabilities read in flight, shared by concurrent turns. */
|
|
1075
|
+
private hubTurnCapabilitiesRead: Promise<ActionCapabilitiesV1> | null =
|
|
1076
|
+
null;
|
|
1062
1077
|
/** The last `hub_action_options` answer for this owner (Gmail connection checks). */
|
|
1063
1078
|
private lastHubOptions: {
|
|
1064
1079
|
value: HubActionOptionsV2 | HubActionOptionsV3;
|
|
@@ -2277,7 +2292,10 @@ export function createUserOracleDO(opts: UserOracleDOOptions) {
|
|
|
2277
2292
|
...(this.hubActionStore && this.hubActionClient
|
|
2278
2293
|
? {
|
|
2279
2294
|
hubActions: {
|
|
2280
|
-
capabilities: () =>
|
|
2295
|
+
capabilities: (opts) =>
|
|
2296
|
+
opts?.turn
|
|
2297
|
+
? this.turnHubCapabilities()
|
|
2298
|
+
: this.cachedHubCapabilities(),
|
|
2281
2299
|
options: (input) => this.hubActionOptions(input),
|
|
2282
2300
|
request: (input: {
|
|
2283
2301
|
ownerDid: string;
|
|
@@ -2385,6 +2403,8 @@ export function createUserOracleDO(opts: UserOracleDOOptions) {
|
|
|
2385
2403
|
this.hubActionStore = null;
|
|
2386
2404
|
this.hubActionClient = null;
|
|
2387
2405
|
this.hubCapabilities = null;
|
|
2406
|
+
this.hubCapabilitiesFailure = null;
|
|
2407
|
+
this.hubTurnCapabilitiesRead = null;
|
|
2388
2408
|
this.lastHubOptions = null;
|
|
2389
2409
|
this.topicProposals = null;
|
|
2390
2410
|
this.attachmentTextCache = null;
|
|
@@ -3524,16 +3544,57 @@ export function createUserOracleDO(opts: UserOracleDOOptions) {
|
|
|
3524
3544
|
}
|
|
3525
3545
|
|
|
3526
3546
|
/** The Hub's capabilities, cached per object for `HUB_CAPABILITIES_CACHE_MS`. */
|
|
3527
|
-
private async cachedHubCapabilities(
|
|
3547
|
+
private async cachedHubCapabilities(
|
|
3548
|
+
timeoutMs?: number,
|
|
3549
|
+
): Promise<ActionCapabilitiesV1> {
|
|
3528
3550
|
const client = this.hubActionClient;
|
|
3529
3551
|
if (!client) throw new Error('Hub actions are unavailable');
|
|
3530
3552
|
const now = Date.now();
|
|
3531
3553
|
const cached = this.hubCapabilities ?? null;
|
|
3532
3554
|
if (cached && now - cached.at < HUB_CAPABILITIES_CACHE_MS)
|
|
3533
3555
|
return cached.value;
|
|
3534
|
-
|
|
3535
|
-
|
|
3536
|
-
|
|
3556
|
+
try {
|
|
3557
|
+
const value = await client.capabilities(timeoutMs);
|
|
3558
|
+
this.hubCapabilities = { value, at: Date.now() };
|
|
3559
|
+
this.hubCapabilitiesFailure = null;
|
|
3560
|
+
return value;
|
|
3561
|
+
} catch (error) {
|
|
3562
|
+
this.hubCapabilitiesFailure = { error, at: Date.now() };
|
|
3563
|
+
throw error;
|
|
3564
|
+
}
|
|
3565
|
+
}
|
|
3566
|
+
|
|
3567
|
+
/**
|
|
3568
|
+
* The capabilities for a turn's tool list: the cached ones while fresh;
|
|
3569
|
+
* else one read with `HUB_TURN_CAPABILITIES_TIMEOUT_MS`, shared by
|
|
3570
|
+
* concurrent turns and not repeated within
|
|
3571
|
+
* `HUB_CAPABILITIES_FAILURE_CACHE_MS` of a failed read. While reads
|
|
3572
|
+
* fail, the last capabilities read are answered (the Hub enforces its
|
|
3573
|
+
* own on every prepare); with none, the failure.
|
|
3574
|
+
*/
|
|
3575
|
+
private async turnHubCapabilities(): Promise<ActionCapabilitiesV1> {
|
|
3576
|
+
const cached = this.hubCapabilities;
|
|
3577
|
+
if (cached && Date.now() - cached.at < HUB_CAPABILITIES_CACHE_MS)
|
|
3578
|
+
return cached.value;
|
|
3579
|
+
const failure = this.hubCapabilitiesFailure;
|
|
3580
|
+
if (
|
|
3581
|
+
failure &&
|
|
3582
|
+
Date.now() - failure.at < HUB_CAPABILITIES_FAILURE_CACHE_MS
|
|
3583
|
+
) {
|
|
3584
|
+
if (cached) return cached.value;
|
|
3585
|
+
throw failure.error;
|
|
3586
|
+
}
|
|
3587
|
+
this.hubTurnCapabilitiesRead ??= this.cachedHubCapabilities(
|
|
3588
|
+
HUB_TURN_CAPABILITIES_TIMEOUT_MS,
|
|
3589
|
+
).finally(() => {
|
|
3590
|
+
this.hubTurnCapabilitiesRead = null;
|
|
3591
|
+
});
|
|
3592
|
+
try {
|
|
3593
|
+
return await this.hubTurnCapabilitiesRead;
|
|
3594
|
+
} catch (error) {
|
|
3595
|
+
if (cached) return cached.value;
|
|
3596
|
+
throw error;
|
|
3597
|
+
}
|
|
3537
3598
|
}
|
|
3538
3599
|
|
|
3539
3600
|
/**
|
|
@@ -3586,13 +3647,15 @@ export function createUserOracleDO(opts: UserOracleDOOptions) {
|
|
|
3586
3647
|
/**
|
|
3587
3648
|
* One chain transaction of the model's messages. The network is the
|
|
3588
3649
|
* account's capability, the chain id this Worker's own for that
|
|
3589
|
-
* network; the signer and the owner's identity in
|
|
3650
|
+
* network; the signer and the owner's identity in the messages come
|
|
3590
3651
|
* 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
|
-
*
|
|
3652
|
+
* them. Bank steps name a listed token and a display amount, stake
|
|
3653
|
+
* steps an amount of IXO: with at least one of either, the owner's
|
|
3654
|
+
* options are read once, fresh (the balances must be current), and each
|
|
3655
|
+
* amount is converted to base units and checked against the listed
|
|
3656
|
+
* balance (`chainTxAmountsInBaseUnits`). A new group proposal's coin
|
|
3657
|
+
* send of another token than IXO reads them too, for the token's
|
|
3658
|
+
* decimals only. No fee is sent: the Hub simulates gas and sets it.
|
|
3596
3659
|
*/
|
|
3597
3660
|
private async chainTransactionHubAction(input: {
|
|
3598
3661
|
ownerDid: string;
|
|
@@ -3623,9 +3686,7 @@ export function createUserOracleDO(opts: UserOracleDOOptions) {
|
|
|
3623
3686
|
const chainId = chainIdsFromConfig(chainIdConfigSchema.parse(this.env))[
|
|
3624
3687
|
HUB_NETWORK_CHAIN[capabilities.network]
|
|
3625
3688
|
];
|
|
3626
|
-
const tokens = input.messages
|
|
3627
|
-
(message) => message.type === 'cosmos.bank.send',
|
|
3628
|
-
)
|
|
3689
|
+
const tokens = chainTxNeedsTokenList(input.messages)
|
|
3629
3690
|
? ((await this.hubActionOptions(input, true)).transfer?.tokens ?? null)
|
|
3630
3691
|
: null;
|
|
3631
3692
|
const operation = chainTxOperation({
|
|
@@ -3633,7 +3694,7 @@ export function createUserOracleDO(opts: UserOracleDOOptions) {
|
|
|
3633
3694
|
chainId,
|
|
3634
3695
|
network: capabilities.network,
|
|
3635
3696
|
memo: input.memo,
|
|
3636
|
-
messages:
|
|
3697
|
+
messages: chainTxAmountsInBaseUnits(input.messages, tokens),
|
|
3637
3698
|
});
|
|
3638
3699
|
return this.requestHubAction({ ...input, operation }, true);
|
|
3639
3700
|
}
|
|
@@ -0,0 +1,307 @@
|
|
|
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 proposal again: chain_lookup gov_proposals for a network proposal, dao_proposals for a group proposal (copy its `id`, `proposalModule` and `dao`).',
|
|
45
|
+
},
|
|
46
|
+
proposal_not_open: {
|
|
47
|
+
means:
|
|
48
|
+
'voting on this proposal is not open (it has closed, or a network proposal has 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
|
+
group_not_supported: {
|
|
172
|
+
means:
|
|
173
|
+
'this group runs a version these cards don’t support yet, or the proposal ends at a block number rather than a time',
|
|
174
|
+
next: 'Do not retry; tell them to use the group’s own app for this.',
|
|
175
|
+
},
|
|
176
|
+
not_in_group: {
|
|
177
|
+
means: 'that proposal list is not one of this group’s',
|
|
178
|
+
next: 'Copy `dao` and `proposalModule` again together from chain_lookup dao_proposals or daos.',
|
|
179
|
+
},
|
|
180
|
+
group_paused: {
|
|
181
|
+
means:
|
|
182
|
+
'the group is paused, so a proposal with actions can’t be carried out right now',
|
|
183
|
+
next: 'Do not retry now; tell them it can be carried out once the group is no longer paused.',
|
|
184
|
+
},
|
|
185
|
+
no_voting_power: {
|
|
186
|
+
means: 'they had no vote in this group when the proposal started',
|
|
187
|
+
next: 'Do not retry; tell them only members with a vote when it started can vote on it.',
|
|
188
|
+
},
|
|
189
|
+
already_voted: {
|
|
190
|
+
means:
|
|
191
|
+
'they have already voted on this proposal, and this group doesn’t allow changing a vote',
|
|
192
|
+
next: 'Do not retry; tell them their earlier vote stands.',
|
|
193
|
+
},
|
|
194
|
+
same_vote: {
|
|
195
|
+
means: 'they have already voted this way on this proposal',
|
|
196
|
+
next: 'Nothing needs to change; tell them.',
|
|
197
|
+
},
|
|
198
|
+
not_a_member: {
|
|
199
|
+
means: 'this group lets only its members do this',
|
|
200
|
+
next: 'Do not retry; tell them a member of the group has to do it.',
|
|
201
|
+
},
|
|
202
|
+
proposal_not_passed: {
|
|
203
|
+
means:
|
|
204
|
+
'this proposal can’t be carried out: it hasn’t passed (yet, or at all), or it was already carried out',
|
|
205
|
+
next: 'Check its status with chain_lookup dao_proposals and tell them.',
|
|
206
|
+
},
|
|
207
|
+
proposal_not_rejected: {
|
|
208
|
+
means: 'only a proposal that did not pass can be closed',
|
|
209
|
+
next: 'Check its status with chain_lookup dao_proposals and tell them.',
|
|
210
|
+
},
|
|
211
|
+
group_actions_would_fail: {
|
|
212
|
+
means:
|
|
213
|
+
'if carried out now, what the proposal makes the group do would fail (in most groups that would mark the proposal failed for good)',
|
|
214
|
+
next: 'Do not retry now; tell them it can be carried out once that is fixed (for example once the group holds enough).',
|
|
215
|
+
},
|
|
216
|
+
deposit_not_supported: {
|
|
217
|
+
means:
|
|
218
|
+
'this group takes its proposal deposit in a token these cards can’t pay',
|
|
219
|
+
next: 'Do not retry; tell them to make the proposal in the group’s own app.',
|
|
220
|
+
},
|
|
221
|
+
proposal_too_large: {
|
|
222
|
+
means: 'the proposal is larger than this group allows',
|
|
223
|
+
next: 'Ask for a shorter description or fewer actions.',
|
|
224
|
+
},
|
|
225
|
+
not_for_groups: {
|
|
226
|
+
means: 'a group can’t do this kind of action through a card',
|
|
227
|
+
next: 'Leave that action out of the proposal and tell them.',
|
|
228
|
+
},
|
|
229
|
+
not_group_owned: {
|
|
230
|
+
means: 'the group does not own that entity',
|
|
231
|
+
next: 'Check the entity’s owner with chain_lookup entity; only an entity the group owns can be used.',
|
|
232
|
+
},
|
|
233
|
+
group_not_found: {
|
|
234
|
+
means:
|
|
235
|
+
'that address is not the group or its proposal list (the two may be swapped, or it is not a group contract at all)',
|
|
236
|
+
next: 'Re-check `dao` (the group) and `proposalModule` (its proposal list) with chain_lookup dao_proposals, copy them again and try again.',
|
|
237
|
+
},
|
|
238
|
+
list_disabled: {
|
|
239
|
+
means:
|
|
240
|
+
'the group has switched that proposal list off: no new proposals can be made through it, and its proposals with actions can’t be carried out (votes and closes still work)',
|
|
241
|
+
next: 'Do not retry; tell them the group would have to switch it back on, or use another of its proposal lists for a new proposal (chain_lookup daos).',
|
|
242
|
+
},
|
|
243
|
+
closes_too_soon: {
|
|
244
|
+
means: 'voting on this proposal closes before the card could be signed',
|
|
245
|
+
next: 'Do not retry; tell them voting is about to close.',
|
|
246
|
+
},
|
|
247
|
+
group_inactive: {
|
|
248
|
+
means: 'the group is not taking new proposals right now',
|
|
249
|
+
next: 'Do not retry now; tell them it can be proposed once the group takes proposals again.',
|
|
250
|
+
},
|
|
251
|
+
deposit_changed: {
|
|
252
|
+
means: 'the group’s proposal deposit changed while the card was being made',
|
|
253
|
+
next: 'Check the deposit again with chain_lookup daos, tell them the new amount and ask again.',
|
|
254
|
+
},
|
|
255
|
+
} as const satisfies Record<
|
|
256
|
+
ChainTxRefusalCode,
|
|
257
|
+
{ means: string; next: string }
|
|
258
|
+
>;
|
|
259
|
+
|
|
260
|
+
/**
|
|
261
|
+
* A message field path as the protocol names one (`validatorAddress`,
|
|
262
|
+
* `payments.approval.amount[1].denom`): checked here as well, because the
|
|
263
|
+
* path is repeated in text the model reads.
|
|
264
|
+
*/
|
|
265
|
+
const REFUSAL_FIELD =
|
|
266
|
+
/^[A-Za-z][A-Za-z0-9_]*(?:\[\d{1,3}\])?(?:\.[A-Za-z][A-Za-z0-9_]*(?:\[\d{1,3}\])?){0,5}$/;
|
|
267
|
+
|
|
268
|
+
/**
|
|
269
|
+
* A parsed refusal the runtime puts into words: a message index 0..7, a
|
|
270
|
+
* plain field path of at most 100 characters and a code of the protocol's
|
|
271
|
+
* list. Anything else is passed on as the Hub's quoted reason instead.
|
|
272
|
+
*/
|
|
273
|
+
export function wordableRefusal(
|
|
274
|
+
refusal: ChainTxRefusal | null,
|
|
275
|
+
): (ChainTxRefusal & { code: ChainTxRefusalCode }) | null {
|
|
276
|
+
if (!refusal) return null;
|
|
277
|
+
const { index, field, code } = refusal;
|
|
278
|
+
if (
|
|
279
|
+
!Number.isInteger(index) ||
|
|
280
|
+
index < 0 ||
|
|
281
|
+
index >= CHAIN_TX_MAX_MESSAGES ||
|
|
282
|
+
field.length > 100 ||
|
|
283
|
+
!REFUSAL_FIELD.test(field) ||
|
|
284
|
+
code === null ||
|
|
285
|
+
!CHAIN_TX_REFUSAL_CODES.includes(code)
|
|
286
|
+
)
|
|
287
|
+
return null;
|
|
288
|
+
return { index, field, code };
|
|
289
|
+
}
|
|
290
|
+
|
|
291
|
+
/**
|
|
292
|
+
* A coded chain transaction refusal in the model's words: which step, what
|
|
293
|
+
* it means for the person and the next step. The field and code are for
|
|
294
|
+
* the model only; the Hub's reason text is not repeated.
|
|
295
|
+
*/
|
|
296
|
+
export function chainTxRefusalWords(
|
|
297
|
+
refusal: ChainTxRefusal & { code: ChainTxRefusalCode },
|
|
298
|
+
): string {
|
|
299
|
+
const words = CHAIN_TX_REFUSAL_WORDS[refusal.code];
|
|
300
|
+
// A new group proposal's action keeps its own codes under its path.
|
|
301
|
+
const action = /^actions\[(\d{1,3})\]\./.exec(refusal.field)?.[1];
|
|
302
|
+
const where =
|
|
303
|
+
action === undefined
|
|
304
|
+
? `step ${refusal.index + 1}`
|
|
305
|
+
: `the group’s action ${Number(action) + 1} of the proposal in step ${refusal.index + 1}`;
|
|
306
|
+
return `Auth Hub could not prepare this transaction: in ${where}, ${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}).`;
|
|
307
|
+
}
|