@thirdfy/agent-cli 0.4.1 → 0.4.3
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/CHANGELOG.md +39 -0
- package/README.md +4 -7
- package/package.json +5 -3
- package/src/cli/help.mjs +1 -1
- package/src/cli/options/index.mjs +11 -11
- package/src/cli/options/onboarding/credentials.mjs +17 -0
- package/src/cli/program.mjs +1 -1
- package/src/commands/{agent.mjs → account/agent.mjs} +5 -5
- package/src/commands/{credits.mjs → account/credits.mjs} +13 -15
- package/src/commands/{wallet.mjs → account/wallet.mjs} +29 -33
- package/src/commands/{walletControl.mjs → account/walletControl.mjs} +3 -3
- package/src/commands/discovery.mjs +1 -1
- package/src/commands/doctor.mjs +5 -5
- package/src/commands/execute.mjs +4 -13
- package/src/commands/index.mjs +9 -9
- package/src/commands/{bootstrap.mjs → onboarding/bootstrap.mjs} +8 -8
- package/src/commands/{consoleMandate.mjs → onboarding/consoleMandate.mjs} +2 -2
- package/src/commands/{credentials.mjs → onboarding/credentials.mjs} +16 -29
- package/src/commands/{delegation.mjs → onboarding/delegation.mjs} +5 -5
- package/src/commands/{avantis.mjs → venues/avantis.mjs} +1 -1
- package/src/commands/{condor.mjs → venues/condor.mjs} +4 -4
- package/src/commands/{hummingbot.mjs → venues/hummingbot.mjs} +4 -4
- package/src/commands/{hyperliquid.mjs → venues/hyperliquid.mjs} +1 -1
- package/src/commands/{lighter.mjs → venues/lighter.mjs} +1 -1
- package/src/commands/{polymarket.mjs → venues/polymarket.mjs} +4 -4
- package/src/commands/{venue.mjs → venues/venue.mjs} +3 -3
- package/src/core/chainRegistry.mjs +11 -0
- package/src/core/constants.mjs +0 -19
- package/src/core/earnclawAttribution.mjs +1 -1
- package/src/core/{actionTimeouts.mjs → execution/actionTimeouts.mjs} +2 -2
- package/src/core/{walletExecuteChainId.mjs → execution/walletExecuteChainId.mjs} +1 -1
- package/src/core/http.mjs +1 -1
- package/src/runtime/actions/selection.mjs +0 -1
- package/src/runtime/execution/hostedRuntimeWriteGate.mjs +1 -1
- package/src/runtime/execution/payloads.mjs +4 -4
- package/src/runtime/execution/runners/agentWalletLane.mjs +109 -0
- package/src/runtime/execution/runners/createExecutionRunners.mjs +312 -0
- package/src/runtime/execution/runners/hybridLane.mjs +88 -0
- package/src/runtime/execution/runners/managedPreflight.mjs +253 -0
- package/src/runtime/execution/runners/managedResponse.mjs +119 -0
- package/src/runtime/execution/runners/selfLane.mjs +233 -0
- package/src/runtime/execution/runners/thirdfyLane.mjs +97 -0
- package/src/runtime/execution/svmLane.mjs +1 -1
- package/src/runtime/handlers.mjs +15 -20
- package/src/runtime/main.mjs +1 -11
- package/src/runtime/owsSigning.mjs +17 -16
- package/src/runtime/providerHints/cex.mjs +1 -52
- package/src/runtime/risk/notional.mjs +1 -1
- package/src/runtime/validation/actionParamsGate.mjs +1 -1
- package/src/cli/options/credentials.mjs +0 -15
- package/src/runtime/bitfinexProbe.mjs +0 -121
- package/src/runtime/execution/runners.mjs +0 -1134
- package/src/runtime/onboardingHints.mjs +0 -54
- /package/src/cli/options/{agent.mjs → account/agent.mjs} +0 -0
- /package/src/cli/options/{credits.mjs → account/credits.mjs} +0 -0
- /package/src/cli/options/{wallet.mjs → account/wallet.mjs} +0 -0
- /package/src/cli/options/{walletControl.mjs → account/walletControl.mjs} +0 -0
- /package/src/cli/options/{bootstrap.mjs → onboarding/bootstrap.mjs} +0 -0
- /package/src/cli/options/{delegation.mjs → onboarding/delegation.mjs} +0 -0
- /package/src/cli/options/{condor.mjs → venues/condor.mjs} +0 -0
- /package/src/cli/options/{hummingbot.mjs → venues/hummingbot.mjs} +0 -0
- /package/src/cli/options/{polymarket.mjs → venues/polymarket.mjs} +0 -0
- /package/src/cli/options/{venue.mjs → venues/venue.mjs} +0 -0
- /package/src/core/{executionContext.mjs → execution/executionContext.mjs} +0 -0
- /package/src/core/{fundingTokenIn.mjs → execution/fundingTokenIn.mjs} +0 -0
- /package/src/core/{hostedRuntimeIdempotency.mjs → execution/hostedRuntimeIdempotency.mjs} +0 -0
- /package/src/core/{readOnlyActions.mjs → execution/readOnlyActions.mjs} +0 -0
- /package/src/core/{signedTx.mjs → execution/signedTx.mjs} +0 -0
|
@@ -1,11 +1,11 @@
|
|
|
1
|
-
import { requireFlag, parseJsonFlag } from '
|
|
2
|
-
import { createCliError } from '
|
|
3
|
-
import { getEffectiveRunMode } from '
|
|
4
|
-
import { apiGet, apiPost, requestWithRouteFallback } from '
|
|
1
|
+
import { requireFlag, parseJsonFlag } from '../../core/args.mjs';
|
|
2
|
+
import { createCliError } from '../../core/envelope.mjs';
|
|
3
|
+
import { getEffectiveRunMode } from '../../core/runMode.mjs';
|
|
4
|
+
import { apiGet, apiPost, requestWithRouteFallback } from '../../core/http.mjs';
|
|
5
5
|
import { createRequire } from 'module';
|
|
6
6
|
|
|
7
7
|
const require = createRequire(import.meta.url);
|
|
8
|
-
const { normalizeDelegationStatus } = require('
|
|
8
|
+
const { normalizeDelegationStatus } = require('../../utils/cli/delegationNormalization.cjs');
|
|
9
9
|
|
|
10
10
|
export function createDelegationCommands(deps) {
|
|
11
11
|
const {
|
|
@@ -1,7 +1,7 @@
|
|
|
1
|
-
import { apiPost } from '
|
|
2
|
-
import { exitAfterOutput } from '
|
|
3
|
-
import { parseJsonFlag } from '
|
|
4
|
-
import { negotiateCapabilities } from '
|
|
1
|
+
import { apiPost } from '../../core/http.mjs';
|
|
2
|
+
import { exitAfterOutput } from '../../core/envelope.mjs';
|
|
3
|
+
import { parseJsonFlag } from '../../core/args.mjs';
|
|
4
|
+
import { negotiateCapabilities } from '../../core/http.mjs';
|
|
5
5
|
import { resolveConfigAgentKey } from './polymarket.mjs';
|
|
6
6
|
|
|
7
7
|
function agentApiKeyFromFlags(flags, ctx) {
|
|
@@ -1,7 +1,7 @@
|
|
|
1
|
-
import { apiGet, apiPost } from '
|
|
2
|
-
import { exitAfterOutput } from '
|
|
3
|
-
import { parseJsonFlag } from '
|
|
4
|
-
import { negotiateCapabilities } from '
|
|
1
|
+
import { apiGet, apiPost } from '../../core/http.mjs';
|
|
2
|
+
import { exitAfterOutput } from '../../core/envelope.mjs';
|
|
3
|
+
import { parseJsonFlag } from '../../core/args.mjs';
|
|
4
|
+
import { negotiateCapabilities } from '../../core/http.mjs';
|
|
5
5
|
import { resolveConfigAgentKey } from './polymarket.mjs';
|
|
6
6
|
|
|
7
7
|
function orgIdFromFlags(flags) {
|
|
@@ -1,7 +1,7 @@
|
|
|
1
|
-
import { loadProfileConfig } from '
|
|
2
|
-
import { negotiateCapabilities } from '
|
|
3
|
-
import { parseBooleanFlag } from '
|
|
4
|
-
import { exitAfterOutput } from '
|
|
1
|
+
import { loadProfileConfig } from '../../core/context.mjs';
|
|
2
|
+
import { negotiateCapabilities } from '../../core/http.mjs';
|
|
3
|
+
import { parseBooleanFlag } from '../../core/runMode.mjs';
|
|
4
|
+
import { exitAfterOutput } from '../../core/envelope.mjs';
|
|
5
5
|
|
|
6
6
|
function extractAgentKey(value) {
|
|
7
7
|
if (!value || typeof value !== 'object') return '';
|
|
@@ -1,6 +1,6 @@
|
|
|
1
|
-
import { apiGet, apiPost } from '
|
|
2
|
-
import { loadProfileConfig } from '
|
|
3
|
-
import { normalizeVenueProviderId } from '
|
|
1
|
+
import { apiGet, apiPost } from '../../core/http.mjs';
|
|
2
|
+
import { loadProfileConfig } from '../../core/context.mjs';
|
|
3
|
+
import { normalizeVenueProviderId } from '../../core/venueBrand.mjs';
|
|
4
4
|
|
|
5
5
|
// `avantis` is the internal venue id the API expects; `veranta` (the venue's new name) maps onto it.
|
|
6
6
|
const VENUES = new Set(['polymarket', 'hyperliquid', 'lighter', 'avantis']);
|
|
@@ -293,6 +293,17 @@ export function resolveChainRpcUrl({ chainId, rpcUrl, env = process.env } = {})
|
|
|
293
293
|
return getChainInfo(id)?.rpcUrl || '';
|
|
294
294
|
}
|
|
295
295
|
|
|
296
|
+
/**
|
|
297
|
+
* An ethers JsonRpcProvider pinned to `chainId` (`staticNetwork`). Without a pinned network, ethers
|
|
298
|
+
* probes the RPC for its chain and, when the RPC is down, prints a retry line to stdout every second
|
|
299
|
+
* and keeps the process alive, which breaks `--json` output. Pinned, a dead RPC is one rejected
|
|
300
|
+
* call. `getNetwork()` then echoes the pin; read the RPC's real chain with `send('eth_chainId', [])`.
|
|
301
|
+
*/
|
|
302
|
+
export function pinnedRpcProvider(ethers, { rpcUrl, chainId }) {
|
|
303
|
+
const id = positiveChainId(chainId) || DEFAULT_CHAIN_ID;
|
|
304
|
+
return new ethers.JsonRpcProvider(rpcUrl, id, { staticNetwork: true });
|
|
305
|
+
}
|
|
306
|
+
|
|
296
307
|
/**
|
|
297
308
|
* Parse a --chain-id value. Returns null when absent, a positive integer when valid,
|
|
298
309
|
* and NaN when present but not a positive integer (the caller rejects it).
|
package/src/core/constants.mjs
CHANGED
|
@@ -1,25 +1,6 @@
|
|
|
1
1
|
export const DEFAULT_API_BASE = 'https://api.thirdfy.com';
|
|
2
2
|
export const DEFAULT_TIMEOUT_MS = 30000;
|
|
3
3
|
export const DEFAULT_CATALOG_CACHE_TTL_MS = 60_000;
|
|
4
|
-
export const DEFAULT_BITFINEX_BASE_URL = 'https://api.bitfinex.com';
|
|
5
|
-
export const BITFINEX_PERMISSION_PROFILES = {
|
|
6
|
-
funding: {
|
|
7
|
-
id: 'funding',
|
|
8
|
-
credentialRef: 'bitfinex:funding-primary',
|
|
9
|
-
required: {
|
|
10
|
-
funding: { read: 1, write: 1 },
|
|
11
|
-
wallets: { read: 1, write: 0 },
|
|
12
|
-
},
|
|
13
|
-
},
|
|
14
|
-
spot: {
|
|
15
|
-
id: 'spot',
|
|
16
|
-
credentialRef: 'bitfinex:spot-primary',
|
|
17
|
-
required: {
|
|
18
|
-
orders: { read: 1, write: 1 },
|
|
19
|
-
wallets: { read: 1, write: 0 },
|
|
20
|
-
},
|
|
21
|
-
},
|
|
22
|
-
};
|
|
23
4
|
export const RUN_MODES = ['thirdfy', 'self', 'hybrid', 'agent_wallet'];
|
|
24
5
|
export const HYBRID_WALLET_MODES = ['self', 'agent_wallet'];
|
|
25
6
|
export const CUSTODY_MODES = ['managed', 'external'];
|
|
@@ -4,4 +4,4 @@ export {
|
|
|
4
4
|
resolveExecutionContextFromInput as resolveEarnClawAttributionFromInput,
|
|
5
5
|
buildExecutionContextHeaders as buildEarnClawAttributionHeaders,
|
|
6
6
|
mergeExecutionContext as mergeEarnClawContext,
|
|
7
|
-
} from './executionContext.mjs';
|
|
7
|
+
} from './execution/executionContext.mjs';
|
|
@@ -1,6 +1,6 @@
|
|
|
1
|
-
import { DEFAULT_TIMEOUT_MS } from '
|
|
1
|
+
import { DEFAULT_TIMEOUT_MS } from '../constants.mjs';
|
|
2
2
|
import { isReadOnlyActionName, isReadOnlyResolvedAction } from './readOnlyActions.mjs';
|
|
3
|
-
import { toLegacyVenueActionKey } from '
|
|
3
|
+
import { toLegacyVenueActionKey } from '../venueBrand.mjs';
|
|
4
4
|
|
|
5
5
|
/**
|
|
6
6
|
* Default HTTP timeout for managed writes. A perps order or TP/SL update signs, broadcasts, and waits
|
package/src/core/http.mjs
CHANGED
|
@@ -222,7 +222,6 @@ function inferActionProvider(action) {
|
|
|
222
222
|
if (key.includes('lighter')) return 'lighter';
|
|
223
223
|
if (key.includes('avantis') && key.includes('vault')) return 'avantis-vault';
|
|
224
224
|
if (key.includes('avantis')) return 'avantis';
|
|
225
|
-
if (key.includes('bitfinex')) return 'bitfinex';
|
|
226
225
|
if (key.includes('polymarket') || key.includes('prediction')) return 'polymarket';
|
|
227
226
|
if (key.includes('vaults-fyi')) return 'vaults-fyi';
|
|
228
227
|
if (key.includes('yield-xyz')) return 'yield-xyz';
|
|
@@ -2,7 +2,7 @@ import { createCliError } from '../../core/envelope.mjs';
|
|
|
2
2
|
import {
|
|
3
3
|
HOSTED_RUNTIME_IDEMPOTENCY_NAMESPACE,
|
|
4
4
|
readHostedRuntimeUuid,
|
|
5
|
-
} from '../../core/hostedRuntimeIdempotency.mjs';
|
|
5
|
+
} from '../../core/execution/hostedRuntimeIdempotency.mjs';
|
|
6
6
|
|
|
7
7
|
const POLYMARKET_PLACE_ACTIONS = new Set([
|
|
8
8
|
'place_polymarket_order',
|
|
@@ -1,14 +1,14 @@
|
|
|
1
1
|
import { randomUUID } from 'crypto';
|
|
2
|
-
import { wrapWriteIdempotencyKeyForHostedRuntime } from '../../core/hostedRuntimeIdempotency.mjs';
|
|
3
|
-
import { resolveExecutionContextFromEnv } from '../../core/executionContext.mjs';
|
|
2
|
+
import { wrapWriteIdempotencyKeyForHostedRuntime } from '../../core/execution/hostedRuntimeIdempotency.mjs';
|
|
3
|
+
import { resolveExecutionContextFromEnv } from '../../core/execution/executionContext.mjs';
|
|
4
4
|
import { requireFlag, parseJsonFlag } from '../../core/args.mjs';
|
|
5
5
|
import { findAgentApiKey, loadProfileConfig } from '../../core/context.mjs';
|
|
6
6
|
import { createCliError } from '../../core/envelope.mjs';
|
|
7
7
|
import { toLegacyVenueActionKey } from '../../core/venueBrand.mjs';
|
|
8
8
|
import { normalizeRunMode, normalizeManagedRunMode, normalizeHybridWalletMode } from '../../core/runMode.mjs';
|
|
9
9
|
import { assertHostedRuntimePolymarketWriteIdempotency } from './hostedRuntimeWriteGate.mjs';
|
|
10
|
-
import { resolveTokenInForFunding } from '../../core/fundingTokenIn.mjs';
|
|
11
|
-
import { resolveWalletExecuteChainId } from '../../core/walletExecuteChainId.mjs';
|
|
10
|
+
import { resolveTokenInForFunding } from '../../core/execution/fundingTokenIn.mjs';
|
|
11
|
+
import { resolveWalletExecuteChainId } from '../../core/execution/walletExecuteChainId.mjs';
|
|
12
12
|
|
|
13
13
|
function isTruthyFlag(value) {
|
|
14
14
|
return value === true || String(value || '').trim().toLowerCase() === 'true';
|
|
@@ -0,0 +1,109 @@
|
|
|
1
|
+
import { apiPost } from '../../../core/http.mjs';
|
|
2
|
+
import { withActionTimeout } from '../../../core/execution/actionTimeouts.mjs';
|
|
3
|
+
import { actionNameForApi } from '../../actions/selection.mjs';
|
|
4
|
+
import { isEmptyQueuedExecuteIntent } from '../managedReadExecuteRail.mjs';
|
|
5
|
+
import {
|
|
6
|
+
isReadOnlyAction,
|
|
7
|
+
shouldFallbackAgentWalletToExecuteIntent,
|
|
8
|
+
normalizeManagedExecuteResponse,
|
|
9
|
+
READ_FAST_PATH_PREFLIGHT,
|
|
10
|
+
fastPathWalletMismatch,
|
|
11
|
+
} from './managedResponse.mjs';
|
|
12
|
+
import { createRequire } from 'module';
|
|
13
|
+
|
|
14
|
+
const require = createRequire(import.meta.url);
|
|
15
|
+
const { normalizeIntentResponse } = require('../../../utils/cli/intentNormalization.cjs');
|
|
16
|
+
|
|
17
|
+
/** The agent_wallet lane: the API signs with the agent's execution wallet. */
|
|
18
|
+
export function createAgentWalletLane(deps, lanes) {
|
|
19
|
+
const {
|
|
20
|
+
buildManagedExecutePayload,
|
|
21
|
+
} = deps;
|
|
22
|
+
const { shouldUseManagedReadFastPath, runReadOnlyActionViaIntentRail, resolveManagedExecutionPreflight } = lanes;
|
|
23
|
+
|
|
24
|
+
async function executeManagedWalletRun(ctx, flags, resolved, options) {
|
|
25
|
+
// Market-data reads under agent_wallet must use /execute and keep the provider payload on the
|
|
26
|
+
// envelope (raw + parsed data.result). Routing them only through /execute-intent was the 0.2.34
|
|
27
|
+
// attempt to avoid a burial bug, but solo Hermes agent_wallet intents come back as an empty
|
|
28
|
+
// queued envelope with no universe. Live /execute still returns the Hyperliquid meta payload
|
|
29
|
+
// (often as a JSON string under data.result). Reads skip funding gates; routing still fails closed.
|
|
30
|
+
const isRead = isReadOnlyAction(resolved);
|
|
31
|
+
const skipPreflight = Boolean(options?.skipPreflight);
|
|
32
|
+
// A seeded preflight (Hyperliquid/Lighter/Veranta address defaults) already ran; reuse it.
|
|
33
|
+
const readFastPath =
|
|
34
|
+
!skipPreflight && !options?.managedPreflight && shouldUseManagedReadFastPath(flags, resolved, options);
|
|
35
|
+
let preflight = readFastPath ? READ_FAST_PATH_PREFLIGHT : null;
|
|
36
|
+
if (!skipPreflight && !readFastPath) {
|
|
37
|
+
preflight = await resolveManagedExecutionPreflight(ctx, flags, resolved, {
|
|
38
|
+
runMode: 'agent_wallet',
|
|
39
|
+
requireFundingCheck: !isRead,
|
|
40
|
+
preflightSeed: options?.managedPreflight,
|
|
41
|
+
});
|
|
42
|
+
if (!preflight.success) {
|
|
43
|
+
const normalized = normalizeIntentResponse({
|
|
44
|
+
success: false,
|
|
45
|
+
status: 'failed',
|
|
46
|
+
mode: 'agent_wallet',
|
|
47
|
+
blockedReason: preflight.blockedReason || 'PRECHECK_FAILED',
|
|
48
|
+
blockedStage: preflight.blockedStage || 'routing',
|
|
49
|
+
error: preflight.error || 'Managed wallet preflight failed',
|
|
50
|
+
preflightBlocked: true,
|
|
51
|
+
executionWalletAddress: preflight.executionWalletAddress || null,
|
|
52
|
+
executionWalletPreflight: preflight,
|
|
53
|
+
});
|
|
54
|
+
normalized.executionWalletAddress = preflight.executionWalletAddress || null;
|
|
55
|
+
normalized.executionWalletPreflight = preflight;
|
|
56
|
+
return normalized;
|
|
57
|
+
}
|
|
58
|
+
}
|
|
59
|
+
const payload = buildManagedExecutePayload(flags, {
|
|
60
|
+
resolvedAction: resolved.resolvedAction,
|
|
61
|
+
apiAction: actionNameForApi(resolved),
|
|
62
|
+
runMode: 'agent_wallet',
|
|
63
|
+
forceIdempotency: true,
|
|
64
|
+
});
|
|
65
|
+
const actionCtx = withActionTimeout(ctx, resolved);
|
|
66
|
+
const response = await apiPost(actionCtx, '/api/v1/agent/execute', payload);
|
|
67
|
+
if (readFastPath) {
|
|
68
|
+
const mismatch = fastPathWalletMismatch(flags, response);
|
|
69
|
+
if (mismatch) return mismatch;
|
|
70
|
+
}
|
|
71
|
+
if (shouldFallbackAgentWalletToExecuteIntent(resolved, response)) {
|
|
72
|
+
// On the fast path the wallet comes from the /execute answer, not a preflight.
|
|
73
|
+
const fallbackPreflight = readFastPath
|
|
74
|
+
? {
|
|
75
|
+
...READ_FAST_PATH_PREFLIGHT,
|
|
76
|
+
executionWalletAddress: response?.executionWalletAddress || response?.data?.executionWalletAddress || null,
|
|
77
|
+
}
|
|
78
|
+
: preflight;
|
|
79
|
+
const intentResult = await runReadOnlyActionViaIntentRail(ctx, flags, resolved, options, fallbackPreflight);
|
|
80
|
+
if (isEmptyQueuedExecuteIntent(intentResult.raw || intentResult)) {
|
|
81
|
+
// A read that did not answer in time (often a cold provider cache): nothing changed, and the
|
|
82
|
+
// same read usually answers on the next try. Same fields as thirdfy-mcp walletExecute.
|
|
83
|
+
intentResult.success = false;
|
|
84
|
+
intentResult.status = 'failed';
|
|
85
|
+
intentResult.error = intentResult.error || 'MANAGED_READ_EMPTY_EXECUTE_INTENT';
|
|
86
|
+
Object.assign(intentResult, {
|
|
87
|
+
errorCode: 'MANAGED_READ_EMPTY_EXECUTE_INTENT',
|
|
88
|
+
retryable: true,
|
|
89
|
+
retryAfterSeconds: 3,
|
|
90
|
+
nextAction: 'retry_later',
|
|
91
|
+
hint:
|
|
92
|
+
intentResult.hint ||
|
|
93
|
+
'The read did not answer in time; nothing was changed. Repeat the same read in a few seconds. Do not treat the empty queued result as data.',
|
|
94
|
+
});
|
|
95
|
+
}
|
|
96
|
+
intentResult.executionWalletAddress =
|
|
97
|
+
preflight?.executionWalletAddress ||
|
|
98
|
+
response?.executionWalletAddress ||
|
|
99
|
+
response?.data?.executionWalletAddress ||
|
|
100
|
+
null;
|
|
101
|
+
return intentResult;
|
|
102
|
+
}
|
|
103
|
+
return normalizeManagedExecuteResponse(response, { preflight, payload, isRead });
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
return {
|
|
107
|
+
executeManagedWalletRun,
|
|
108
|
+
};
|
|
109
|
+
}
|
|
@@ -0,0 +1,312 @@
|
|
|
1
|
+
import { ACTION_PREFLIGHT_VENUE_FIELDS, isApiActionBlock } from '../actionPreflight.mjs';
|
|
2
|
+
import { normalizeHybridWalletMode } from '../../../core/runMode.mjs';
|
|
3
|
+
import { apiPost } from '../../../core/http.mjs';
|
|
4
|
+
import { actionNameForApi } from '../../actions/selection.mjs';
|
|
5
|
+
import { isReadOnlyAction } from './managedResponse.mjs';
|
|
6
|
+
import { createManagedPreflight } from './managedPreflight.mjs';
|
|
7
|
+
import { createThirdfyLane } from './thirdfyLane.mjs';
|
|
8
|
+
import { createSelfLane } from './selfLane.mjs';
|
|
9
|
+
import { createAgentWalletLane } from './agentWalletLane.mjs';
|
|
10
|
+
import { createHybridLane } from './hybridLane.mjs';
|
|
11
|
+
import { createRequire } from 'module';
|
|
12
|
+
|
|
13
|
+
const require = createRequire(import.meta.url);
|
|
14
|
+
const { normalizeIntentResponse } = require('../../../utils/cli/intentNormalization.cjs');
|
|
15
|
+
|
|
16
|
+
/** Execution runners: the preflight and the run for each run mode, wired from the lane files. */
|
|
17
|
+
export function createExecutionRunners(deps) {
|
|
18
|
+
const {
|
|
19
|
+
buildIntentPayload,
|
|
20
|
+
shouldUseBuildTxPreflight,
|
|
21
|
+
} = deps;
|
|
22
|
+
const { shouldUseManagedReadFastPath, resolveManagedExecutionPreflight } = createManagedPreflight(deps);
|
|
23
|
+
const { executeThirdfyRun, runReadOnlyActionViaIntentRail } = createThirdfyLane(deps);
|
|
24
|
+
const { executeSelfRun, performSelfBroadcast, resolveEffectCheckResult, submitSignedTx } = createSelfLane(deps, { runReadOnlyActionViaIntentRail });
|
|
25
|
+
const { executeManagedWalletRun } = createAgentWalletLane(deps, { shouldUseManagedReadFastPath, resolveManagedExecutionPreflight, runReadOnlyActionViaIntentRail });
|
|
26
|
+
const { executeHybridRun } = createHybridLane(deps, { runReadOnlyActionViaIntentRail, executeManagedWalletRun, executeSelfRun });
|
|
27
|
+
|
|
28
|
+
async function runPreflightByMode(runMode, ctx, flags, resolved, options = {}) {
|
|
29
|
+
const resolvedAction = resolved.resolvedAction;
|
|
30
|
+
if (runMode === 'agent_wallet') {
|
|
31
|
+
const preflight = await resolveManagedExecutionPreflight(ctx, flags, resolved, {
|
|
32
|
+
runMode,
|
|
33
|
+
// Reads carry no transaction; keep funding optional so preflight matches run.
|
|
34
|
+
requireFundingCheck: !isReadOnlyAction(resolved),
|
|
35
|
+
preflightSeed: options.managedPreflight,
|
|
36
|
+
// Ask the API to check policy and venue readiness for this action, not just the wallet.
|
|
37
|
+
evaluateAction: true,
|
|
38
|
+
});
|
|
39
|
+
const normalized = normalizeIntentResponse({
|
|
40
|
+
success: preflight.success,
|
|
41
|
+
status: preflight.success ? 'ready' : 'failed',
|
|
42
|
+
mode: 'agent_wallet',
|
|
43
|
+
executionWalletAddress: preflight.executionWalletAddress || null,
|
|
44
|
+
signerMethod: preflight.signerMethod || 'managed_wallet_server',
|
|
45
|
+
blockedReason: preflight.blockedReason || null,
|
|
46
|
+
blockedStage: preflight.blockedStage || null,
|
|
47
|
+
error: preflight.success ? null : preflight.error || 'Managed wallet preflight failed',
|
|
48
|
+
preflightBlocked: !preflight.success,
|
|
49
|
+
fundingTokenBalance: preflight.fundingTokenBalance || null,
|
|
50
|
+
executionWalletPreflight: preflight,
|
|
51
|
+
});
|
|
52
|
+
normalized.executionWalletAddress = preflight.executionWalletAddress || null;
|
|
53
|
+
normalized.signerMethod = preflight.signerMethod || 'managed_wallet_server';
|
|
54
|
+
normalized.executionWalletPreflight = preflight;
|
|
55
|
+
normalized.fundingTokenBalance = preflight.fundingTokenBalance || null;
|
|
56
|
+
normalized.policyEvaluated = preflight.policyEvaluated === true;
|
|
57
|
+
for (const key of [...ACTION_PREFLIGHT_VENUE_FIELDS, 'executionVenue']) {
|
|
58
|
+
if (preflight[key] != null) normalized[key] = preflight[key];
|
|
59
|
+
}
|
|
60
|
+
// Only an API refusal of the action is `blocked`; CLI-local checks keep `failed`.
|
|
61
|
+
const apiBlocked = isApiActionBlock(preflight);
|
|
62
|
+
if (apiBlocked) normalized.status = 'blocked';
|
|
63
|
+
return {
|
|
64
|
+
message: preflight.success
|
|
65
|
+
? 'Managed wallet preflight completed'
|
|
66
|
+
: preflight.blockedStage === 'venue_readiness'
|
|
67
|
+
? 'Managed wallet preflight blocked: venue not ready'
|
|
68
|
+
: apiBlocked
|
|
69
|
+
? 'Managed wallet preflight blocked by policy'
|
|
70
|
+
: 'Managed wallet preflight failed',
|
|
71
|
+
route: 'execution_wallet_preflight',
|
|
72
|
+
normalized,
|
|
73
|
+
};
|
|
74
|
+
}
|
|
75
|
+
if (runMode === 'self') {
|
|
76
|
+
// Reads have no unsigned tx to prepare. Keep preflight on validation-only execute-intent so
|
|
77
|
+
// operators dry-running self mode do not trigger a real catalog read (run still uses the read rail).
|
|
78
|
+
if (isReadOnlyAction(resolved)) {
|
|
79
|
+
const payload = buildIntentPayload(flags, {
|
|
80
|
+
validationOnly: true,
|
|
81
|
+
forceIdempotency: false,
|
|
82
|
+
resolvedAction,
|
|
83
|
+
apiAction: actionNameForApi(resolved),
|
|
84
|
+
runMode: 'self',
|
|
85
|
+
mirrorOnly: false,
|
|
86
|
+
});
|
|
87
|
+
const response = await apiPost({ ...ctx, writeAction: false }, '/api/v1/agent/execute-intent', payload);
|
|
88
|
+
const normalized = normalizeIntentResponse(response);
|
|
89
|
+
const blockedCount = normalized.blocked || 0;
|
|
90
|
+
if (!normalized.success || blockedCount > 0) {
|
|
91
|
+
normalized.success = false;
|
|
92
|
+
normalized.error = normalized.error || 'Preflight blocked self execution';
|
|
93
|
+
normalized.preflightBlocked = true;
|
|
94
|
+
}
|
|
95
|
+
return {
|
|
96
|
+
message: normalized.success
|
|
97
|
+
? 'Self preflight completed'
|
|
98
|
+
: normalized.preflightBlocked
|
|
99
|
+
? 'Self preflight blocked by governance'
|
|
100
|
+
: 'Self preflight failed',
|
|
101
|
+
route: 'execute_intent_validation',
|
|
102
|
+
normalized,
|
|
103
|
+
};
|
|
104
|
+
}
|
|
105
|
+
const route = shouldUseBuildTxPreflight(runMode, resolved) ? 'build_tx' : 'execute_intent_validation';
|
|
106
|
+
const normalized = await executeSelfRun(
|
|
107
|
+
ctx,
|
|
108
|
+
flags,
|
|
109
|
+
resolved,
|
|
110
|
+
{ skipPreflight: route === 'build_tx' }
|
|
111
|
+
);
|
|
112
|
+
const blockedByGovernance = !normalized.success && Boolean(normalized.preflightBlocked);
|
|
113
|
+
return {
|
|
114
|
+
message: blockedByGovernance
|
|
115
|
+
? 'Self preflight blocked by governance'
|
|
116
|
+
: normalized.success
|
|
117
|
+
? 'Self preflight completed (unsigned transaction ready)'
|
|
118
|
+
: 'Self preflight failed',
|
|
119
|
+
route,
|
|
120
|
+
normalized,
|
|
121
|
+
};
|
|
122
|
+
}
|
|
123
|
+
if (runMode === 'hybrid') {
|
|
124
|
+
// Reads have nothing to mirror and need no wallet. Keep preflight on validation-only
|
|
125
|
+
// execute-intent so dry-runs do not trigger a real catalog read (run still uses the read rail).
|
|
126
|
+
if (isReadOnlyAction(resolved)) {
|
|
127
|
+
const payload = buildIntentPayload(flags, {
|
|
128
|
+
validationOnly: true,
|
|
129
|
+
forceIdempotency: false,
|
|
130
|
+
resolvedAction,
|
|
131
|
+
apiAction: actionNameForApi(resolved),
|
|
132
|
+
runMode: 'hybrid',
|
|
133
|
+
mirrorOnly: false,
|
|
134
|
+
hybridWalletMode: options.hybridWalletMode,
|
|
135
|
+
});
|
|
136
|
+
const response = await apiPost({ ...ctx, writeAction: false }, '/api/v1/agent/execute-intent', payload);
|
|
137
|
+
const normalized = normalizeIntentResponse(response);
|
|
138
|
+
const blockedCount = normalized.blocked || 0;
|
|
139
|
+
if (!normalized.success || blockedCount > 0) {
|
|
140
|
+
normalized.success = false;
|
|
141
|
+
normalized.error = normalized.error || 'Preflight blocked hybrid execution';
|
|
142
|
+
normalized.preflightBlocked = true;
|
|
143
|
+
}
|
|
144
|
+
return {
|
|
145
|
+
message: normalized.success
|
|
146
|
+
? 'Hybrid preflight completed'
|
|
147
|
+
: normalized.preflightBlocked
|
|
148
|
+
? 'Hybrid preflight blocked by governance'
|
|
149
|
+
: 'Hybrid preflight failed',
|
|
150
|
+
route: 'execute_intent_validation',
|
|
151
|
+
normalized,
|
|
152
|
+
};
|
|
153
|
+
}
|
|
154
|
+
if (options.hybridWalletMode === 'agent_wallet') {
|
|
155
|
+
const managedPayload = buildIntentPayload(flags, {
|
|
156
|
+
validationOnly: true,
|
|
157
|
+
forceIdempotency: false,
|
|
158
|
+
resolvedAction,
|
|
159
|
+
apiAction: actionNameForApi(resolved),
|
|
160
|
+
runMode: 'agent_wallet',
|
|
161
|
+
mirrorOnly: false,
|
|
162
|
+
hybridWalletMode: 'agent_wallet',
|
|
163
|
+
});
|
|
164
|
+
const managedResponse = await apiPost({ ...ctx, writeAction: false }, '/api/v1/agent/execute-intent', managedPayload);
|
|
165
|
+
const managedNormalized = normalizeIntentResponse(managedResponse);
|
|
166
|
+
const mirrorPayload = buildIntentPayload(flags, {
|
|
167
|
+
validationOnly: true,
|
|
168
|
+
forceIdempotency: false,
|
|
169
|
+
resolvedAction,
|
|
170
|
+
apiAction: actionNameForApi(resolved),
|
|
171
|
+
runMode: 'hybrid',
|
|
172
|
+
mirrorOnly: true,
|
|
173
|
+
hybridWalletMode: 'agent_wallet',
|
|
174
|
+
});
|
|
175
|
+
const mirrorResponse = await apiPost({ ...ctx, writeAction: false }, '/api/v1/agent/execute-intent', mirrorPayload);
|
|
176
|
+
const mirrorNormalized = normalizeIntentResponse(mirrorResponse);
|
|
177
|
+
return {
|
|
178
|
+
message:
|
|
179
|
+
managedNormalized.success && mirrorNormalized.success
|
|
180
|
+
? 'Hybrid preflight completed (agent_wallet + thirdfy mirror)'
|
|
181
|
+
: 'Hybrid preflight failed (agent_wallet + thirdfy mirror)',
|
|
182
|
+
route: 'execute_intent_validation_dual',
|
|
183
|
+
normalized: normalizeIntentResponse({
|
|
184
|
+
success: Boolean(managedNormalized.success && mirrorNormalized.success),
|
|
185
|
+
status: mirrorNormalized.status || managedNormalized.status || 'failed',
|
|
186
|
+
mode: 'hybrid',
|
|
187
|
+
hybridWalletMode: 'agent_wallet',
|
|
188
|
+
managed: managedNormalized,
|
|
189
|
+
mirror: mirrorNormalized,
|
|
190
|
+
error: managedNormalized.error || mirrorNormalized.error || null,
|
|
191
|
+
}),
|
|
192
|
+
};
|
|
193
|
+
}
|
|
194
|
+
}
|
|
195
|
+
const payload = buildIntentPayload(flags, {
|
|
196
|
+
validationOnly: true,
|
|
197
|
+
forceIdempotency: false,
|
|
198
|
+
resolvedAction,
|
|
199
|
+
apiAction: actionNameForApi(resolved),
|
|
200
|
+
runMode,
|
|
201
|
+
mirrorOnly: runMode === 'hybrid',
|
|
202
|
+
});
|
|
203
|
+
const response = await apiPost({ ...ctx, writeAction: false }, '/api/v1/agent/execute-intent', payload);
|
|
204
|
+
const normalized = normalizeIntentResponse(response);
|
|
205
|
+
return {
|
|
206
|
+
message: normalized.success
|
|
207
|
+
? runMode === 'hybrid'
|
|
208
|
+
? 'Hybrid mirror preflight completed'
|
|
209
|
+
: 'Preflight completed'
|
|
210
|
+
: runMode === 'hybrid'
|
|
211
|
+
? 'Hybrid mirror preflight failed'
|
|
212
|
+
: 'Preflight failed',
|
|
213
|
+
route: 'execute_intent_validation',
|
|
214
|
+
normalized,
|
|
215
|
+
};
|
|
216
|
+
}
|
|
217
|
+
|
|
218
|
+
async function runExecutionByMode(runMode, ctx, flags, resolved, options) {
|
|
219
|
+
if (runMode === 'thirdfy') {
|
|
220
|
+
const normalized = await executeThirdfyRun(ctx, flags, resolved, options, runMode);
|
|
221
|
+
const preflightBlocked = !normalized.success && Boolean(normalized.preflightBlocked);
|
|
222
|
+
return {
|
|
223
|
+
code: normalized.success ? 'RUN_QUEUED' : preflightBlocked ? 'PREFLIGHT_BLOCKED' : 'RUN_FAILED',
|
|
224
|
+
message: normalized.success
|
|
225
|
+
? 'Execution intent queued'
|
|
226
|
+
: preflightBlocked
|
|
227
|
+
? 'Preflight blocked execution'
|
|
228
|
+
: 'Execution failed',
|
|
229
|
+
idempotencyKey: normalized.idempotencyKey || null,
|
|
230
|
+
normalized,
|
|
231
|
+
};
|
|
232
|
+
}
|
|
233
|
+
if (runMode === 'agent_wallet') {
|
|
234
|
+
const normalized = await executeManagedWalletRun(ctx, flags, resolved, options);
|
|
235
|
+
const preflightBlocked = !normalized.success && Boolean(normalized.preflightBlocked);
|
|
236
|
+
return {
|
|
237
|
+
code: normalized.success ? 'WALLET_EXECUTED' : preflightBlocked ? 'PREFLIGHT_BLOCKED' : 'RUN_FAILED',
|
|
238
|
+
message: normalized.success
|
|
239
|
+
? 'Managed wallet execution completed'
|
|
240
|
+
: preflightBlocked
|
|
241
|
+
? 'Managed wallet preflight blocked execution'
|
|
242
|
+
: 'Managed wallet execution failed',
|
|
243
|
+
idempotencyKey: normalized.idempotencyKey || null,
|
|
244
|
+
normalized,
|
|
245
|
+
};
|
|
246
|
+
}
|
|
247
|
+
if (runMode === 'self') {
|
|
248
|
+
const normalized = await executeSelfRun(ctx, flags, resolved, options);
|
|
249
|
+
const preflightBlocked = !normalized.success && Boolean(normalized.preflightBlocked);
|
|
250
|
+
const readOnlySuccess =
|
|
251
|
+
normalized.success && normalized.routeFallback === 'execute_intent_read_only';
|
|
252
|
+
return {
|
|
253
|
+
code: normalized.success
|
|
254
|
+
? readOnlySuccess
|
|
255
|
+
? 'SELF_READ_COMPLETED'
|
|
256
|
+
: 'SELF_UNSIGNED_TX_READY'
|
|
257
|
+
: preflightBlocked
|
|
258
|
+
? 'PREFLIGHT_BLOCKED'
|
|
259
|
+
: 'SELF_EXECUTION_FAILED',
|
|
260
|
+
message: normalized.success
|
|
261
|
+
? readOnlySuccess
|
|
262
|
+
? 'Read-only action completed via delegated execute-intent'
|
|
263
|
+
: 'Unsigned transaction prepared for self-custody execution'
|
|
264
|
+
: preflightBlocked
|
|
265
|
+
? 'Preflight blocked self execution'
|
|
266
|
+
: 'Self-custody execution failed',
|
|
267
|
+
idempotencyKey: null,
|
|
268
|
+
normalized,
|
|
269
|
+
};
|
|
270
|
+
}
|
|
271
|
+
const normalized = await executeHybridRun(ctx, flags, resolved, options);
|
|
272
|
+
const preflightBlocked = !normalized.success && Boolean(normalized.preflightBlocked);
|
|
273
|
+
const hybridWalletMode = normalizeHybridWalletMode(
|
|
274
|
+
normalized.hybridWalletMode || options?.hybridWalletMode || flags.hybridWalletMode || 'self',
|
|
275
|
+
);
|
|
276
|
+
const readOnlySuccess =
|
|
277
|
+
normalized.success && normalized.routeFallback === 'execute_intent_read_only';
|
|
278
|
+
return {
|
|
279
|
+
code: normalized.success
|
|
280
|
+
? readOnlySuccess
|
|
281
|
+
? 'HYBRID_READ_COMPLETED'
|
|
282
|
+
: 'HYBRID_READY'
|
|
283
|
+
: preflightBlocked
|
|
284
|
+
? 'PREFLIGHT_BLOCKED'
|
|
285
|
+
: 'HYBRID_FAILED',
|
|
286
|
+
message: normalized.success
|
|
287
|
+
? readOnlySuccess
|
|
288
|
+
? 'Read-only action completed via delegated execute-intent'
|
|
289
|
+
: hybridWalletMode === 'agent_wallet'
|
|
290
|
+
? 'Hybrid execution prepared (agent wallet + mirror preflight)'
|
|
291
|
+
: 'Hybrid execution prepared (self tx + mirror preflight)'
|
|
292
|
+
: preflightBlocked
|
|
293
|
+
? 'Preflight blocked hybrid execution'
|
|
294
|
+
: 'Hybrid execution failed',
|
|
295
|
+
idempotencyKey: normalized.idempotencyKey || null,
|
|
296
|
+
normalized,
|
|
297
|
+
};
|
|
298
|
+
}
|
|
299
|
+
|
|
300
|
+
return {
|
|
301
|
+
runPreflightByMode,
|
|
302
|
+
runExecutionByMode,
|
|
303
|
+
executeThirdfyRun,
|
|
304
|
+
executeManagedWalletRun,
|
|
305
|
+
executeSelfRun,
|
|
306
|
+
executeHybridRun,
|
|
307
|
+
resolveManagedExecutionPreflight,
|
|
308
|
+
performSelfBroadcast,
|
|
309
|
+
resolveEffectCheckResult,
|
|
310
|
+
submitSignedTx,
|
|
311
|
+
};
|
|
312
|
+
}
|