@thirdfy/agent-cli 0.2.32 → 0.2.34
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 +15 -0
- package/README.md +3 -2
- package/package.json +1 -1
- package/src/commands/bootstrap.mjs +34 -5
- package/src/runtime/execution/runners.mjs +81 -27
package/CHANGELOG.md
CHANGED
|
@@ -4,6 +4,21 @@ All notable changes to `@thirdfy/agent-cli` are documented here. The format is b
|
|
|
4
4
|
|
|
5
5
|
## [Unreleased]
|
|
6
6
|
|
|
7
|
+
## [0.2.34] - 2026-07-25
|
|
8
|
+
|
|
9
|
+
### Fixed
|
|
10
|
+
|
|
11
|
+
- Read-only actions run under `--run-mode agent_wallet` no longer go through the managed-wallet execute rail. `run`, `preflight`, and pack callers that request `get_*`, `list_*`, or `search_*` actions (or any catalog action with `supportsExecute: false`) are routed to the read rail up front, so the provider payload is returned as-is instead of being reshaped into an execution intent envelope (`intentId`, `blocked`, `executed`).
|
|
12
|
+
- Reads on `agent_wallet` are no longer gated on execution-wallet funding. The execution wallet address is still resolved and reported for response parity, but an unfunded wallet does not block a read. Every other preflight failure (`MISSING_USER_DID`, `EXECUTION_WALLET_MISMATCH`, and other routing checks) still fails closed with `PREFLIGHT_BLOCKED`, exactly as it does for a write.
|
|
13
|
+
|
|
14
|
+
Operator impact: `get_hyperliquid_perps_meta` and `get_hyperliquid_all_mids` previously returned an envelope with the market universe buried under `data.raw`, which consumers could not parse. Hyperliquid agents fell back to a stale cached universe and reported no trading edge. Upgrade to 0.2.34 on every runtime that executes with `--run-mode agent_wallet`.
|
|
15
|
+
|
|
16
|
+
## [0.2.33] - 2026-07-24
|
|
17
|
+
|
|
18
|
+
### Fixed
|
|
19
|
+
|
|
20
|
+
- `login email` always prints `executionWallets` / `executionWalletsError` and fails closed with `LOGIN_EMAIL_INCOMPLETE` when `runMode=agent_wallet` and execution wallets are missing or errored. Fund the managed execution wallet, not the owner embed.
|
|
21
|
+
|
|
7
22
|
## [0.2.32] - 2026-07-23
|
|
8
23
|
|
|
9
24
|
### Fixed
|
package/README.md
CHANGED
|
@@ -40,9 +40,10 @@ Run without global install:
|
|
|
40
40
|
npx @thirdfy/agent-cli --help
|
|
41
41
|
```
|
|
42
42
|
|
|
43
|
-
## What's new in v0.2.
|
|
43
|
+
## What's new in v0.2.34
|
|
44
44
|
|
|
45
|
-
-
|
|
45
|
+
- Read-only actions (`get_*`, `list_*`, `search_*`) requested with `--run-mode agent_wallet` now return the provider payload directly instead of an execution envelope. Market data reads such as `get_hyperliquid_perps_meta` no longer come back wrapped as an intent.
|
|
46
|
+
- Reads are no longer gated on execution-wallet funding. The execution address is still reported, but an unfunded wallet does not block a read. Routing and identity checks still apply.
|
|
46
47
|
|
|
47
48
|
Older versions: see [CHANGELOG.md](./CHANGELOG.md) and [GitHub Releases](https://github.com/thirdfy/agent-cli/releases).
|
|
48
49
|
|
package/package.json
CHANGED
|
@@ -450,13 +450,18 @@ async function commandLoginEmail(ctx, flags) {
|
|
|
450
450
|
const agentKey = extractAgentKey(data);
|
|
451
451
|
const wallets = data.wallets && typeof data.wallets === 'object' ? data.wallets : {};
|
|
452
452
|
const executionWallets = data.executionWallets && typeof data.executionWallets === 'object' ? data.executionWallets : {};
|
|
453
|
+
const executionWalletsError = data.executionWalletsError
|
|
454
|
+
? String(data.executionWalletsError)
|
|
455
|
+
: data.execution_wallets_error
|
|
456
|
+
? String(data.execution_wallets_error)
|
|
457
|
+
: null;
|
|
453
458
|
const userDid = String(data.owner?.creatorDid || wallets.userDid || '').trim();
|
|
454
459
|
const primaryEvmWallet = String(wallets.primaryEvmWallet || data.owner?.creatorWallet || '').trim();
|
|
455
460
|
const persistedWallets = {
|
|
456
461
|
...wallets,
|
|
457
462
|
...(userDid ? { userDid } : {}),
|
|
458
463
|
...(primaryEvmWallet ? { primaryEvmWallet } : {}),
|
|
459
|
-
|
|
464
|
+
executionWallets,
|
|
460
465
|
};
|
|
461
466
|
const runMode = normalizeRunMode(flags.runMode || current.runMode || process.env.THIRDFY_RUN_MODE || 'agent_wallet');
|
|
462
467
|
const custodyMode = normalizeCustodyMode(flags.custodyMode || current.custodyMode || process.env.THIRDFY_CUSTODY_MODE, runMode);
|
|
@@ -491,7 +496,27 @@ async function commandLoginEmail(ctx, flags) {
|
|
|
491
496
|
...(agentKey ? { agentKey } : {}),
|
|
492
497
|
};
|
|
493
498
|
}
|
|
499
|
+
// Persist first so operators keep session tokens even when wallet provisioning failed.
|
|
494
500
|
persistProfileConfig(next);
|
|
501
|
+
|
|
502
|
+
const executionWalletAddresses = Object.values(executionWallets)
|
|
503
|
+
.map((row) => (row && typeof row === 'object' ? String(row.walletAddress || '').trim() : ''))
|
|
504
|
+
.filter((addr) => /^0x[a-fA-F0-9]{40}$/.test(addr));
|
|
505
|
+
const hasExecutionWallet = executionWalletAddresses.length > 0;
|
|
506
|
+
if (runMode === 'agent_wallet' && (executionWalletsError || !hasExecutionWallet)) {
|
|
507
|
+
const fundHint = executionWalletAddresses[0] || 'the managed execution wallet address for each chain';
|
|
508
|
+
throw createCliError(
|
|
509
|
+
'LOGIN_EMAIL_INCOMPLETE',
|
|
510
|
+
executionWalletsError
|
|
511
|
+
? `Email login completed but execution wallets failed: ${executionWalletsError}. Fund the managed execution wallet (${fundHint}), not the owner embedded login wallet.`
|
|
512
|
+
: `Email login completed without execution wallets. Fund the managed execution wallet for agent_wallet trading (${fundHint}), not the owner embedded login wallet. Re-run login email or call /api/v1/agent/onboarding/cli/email/complete and confirm executionWallets is returned.`,
|
|
513
|
+
{
|
|
514
|
+
executionWallets,
|
|
515
|
+
executionWalletsError: executionWalletsError || null,
|
|
516
|
+
}
|
|
517
|
+
);
|
|
518
|
+
}
|
|
519
|
+
|
|
495
520
|
printEnvelope({
|
|
496
521
|
success: true,
|
|
497
522
|
code: 'LOGIN_EMAIL_OK',
|
|
@@ -504,13 +529,17 @@ async function commandLoginEmail(ctx, flags) {
|
|
|
504
529
|
evmWallets: Array.isArray(persistedWallets.evm) ? persistedWallets.evm : [],
|
|
505
530
|
solanaWallets: Array.isArray(persistedWallets.solana) ? persistedWallets.solana : [],
|
|
506
531
|
primaryEvmWallet: persistedWallets.primaryEvmWallet || null,
|
|
507
|
-
executionWallets
|
|
508
|
-
|
|
509
|
-
? persistedWallets.executionWallets
|
|
510
|
-
: {},
|
|
532
|
+
executionWallets,
|
|
533
|
+
executionWalletsError: executionWalletsError || null,
|
|
511
534
|
ownerLinkedWallet: data.ownerLinkedWallet || null,
|
|
512
535
|
configPath: getProfileConfigPath(),
|
|
513
536
|
nextSteps: response?.nextSteps || [],
|
|
537
|
+
...(runMode === 'agent_wallet'
|
|
538
|
+
? {
|
|
539
|
+
fundingReminder:
|
|
540
|
+
'Fund executionWallets addresses for agent_wallet. Do not fund the owner embedded wallet for Path A trading.',
|
|
541
|
+
}
|
|
542
|
+
: {}),
|
|
514
543
|
},
|
|
515
544
|
meta: { apiBase: ctx.apiBase },
|
|
516
545
|
});
|
|
@@ -79,15 +79,29 @@ function applyExecutionFallbackHints(normalized, { flags, runMode, resolvedActio
|
|
|
79
79
|
return next;
|
|
80
80
|
}
|
|
81
81
|
|
|
82
|
-
function
|
|
82
|
+
function isReadOnlyAction(resolved) {
|
|
83
83
|
const meta = resolved?.resolvedActionMeta || {};
|
|
84
84
|
const action = String(resolved?.resolvedAction || '')
|
|
85
85
|
.trim()
|
|
86
86
|
.toLowerCase()
|
|
87
87
|
.replace(/-/g, '_');
|
|
88
|
-
const readOnlyByName =
|
|
88
|
+
const readOnlyByName =
|
|
89
|
+
action.startsWith('get_') || action.startsWith('search_') || action.startsWith('list_');
|
|
89
90
|
const readOnlyByMeta = meta.supportsExecute === false && meta.supportsExecuteIntent !== false;
|
|
90
|
-
|
|
91
|
+
return readOnlyByName || readOnlyByMeta;
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
// Reads carry no transaction, so wallet balance never blocks them. Every other preflight failure
|
|
95
|
+
// (identity, routing, wallet mismatch) still fails closed, same as a write.
|
|
96
|
+
const FUNDING_ONLY_PREFLIGHT_BLOCKS = new Set(['INSUFFICIENT_FUNDS', 'INVALID_FUNDING_BALANCE']);
|
|
97
|
+
|
|
98
|
+
function isFundingOnlyPreflightBlock(preflight) {
|
|
99
|
+
const reason = String(preflight?.blockedReason || '').trim().toUpperCase();
|
|
100
|
+
return FUNDING_ONLY_PREFLIGHT_BLOCKS.has(reason);
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
function shouldFallbackAgentWalletToExecuteIntent(resolved, response) {
|
|
104
|
+
if (!isReadOnlyAction(resolved)) return false;
|
|
91
105
|
const err = String(response?.error || response?.message || '').toLowerCase();
|
|
92
106
|
return err.includes('does not support execute rail');
|
|
93
107
|
}
|
|
@@ -97,7 +111,8 @@ async function runPreflightByMode(runMode, ctx, flags, resolved, options = {}) {
|
|
|
97
111
|
if (runMode === 'agent_wallet') {
|
|
98
112
|
const preflight = await resolveManagedExecutionPreflight(ctx, flags, resolved, {
|
|
99
113
|
runMode,
|
|
100
|
-
|
|
114
|
+
// Reads carry no transaction; keep funding optional so preflight matches run.
|
|
115
|
+
requireFundingCheck: !isReadOnlyAction(resolved),
|
|
101
116
|
preflightSeed: options.managedPreflight,
|
|
102
117
|
});
|
|
103
118
|
const normalized = normalizeIntentResponse({
|
|
@@ -304,7 +319,68 @@ async function executeThirdfyRun(ctx, flags, resolved, options, runMode = 'third
|
|
|
304
319
|
return normalized;
|
|
305
320
|
}
|
|
306
321
|
|
|
322
|
+
async function runReadOnlyActionViaIntentRail(ctx, flags, resolved, options, preflight) {
|
|
323
|
+
const userDid = resolveEffectiveUserDid(flags, {
|
|
324
|
+
runMode: 'agent_wallet',
|
|
325
|
+
hybridWalletMode: options?.hybridWalletMode,
|
|
326
|
+
preferDelegationIdentity: options?.preferDelegationIdentity,
|
|
327
|
+
});
|
|
328
|
+
const intentResult = await executeThirdfyRun(
|
|
329
|
+
ctx,
|
|
330
|
+
{
|
|
331
|
+
...flags,
|
|
332
|
+
chainId: Number(flags.chainId || 8453),
|
|
333
|
+
runMode: 'thirdfy',
|
|
334
|
+
userDid: userDid || flags.userDid,
|
|
335
|
+
executionScope: flags.executionScope || 'solo_owner_mirror',
|
|
336
|
+
},
|
|
337
|
+
resolved,
|
|
338
|
+
{ ...options, skipPreflight: true },
|
|
339
|
+
'thirdfy'
|
|
340
|
+
);
|
|
341
|
+
intentResult.mode = 'agent_wallet';
|
|
342
|
+
intentResult.routeFallback = 'execute_intent_read_only';
|
|
343
|
+
if (preflight) {
|
|
344
|
+
intentResult.executionWalletPreflight = preflight;
|
|
345
|
+
intentResult.executionWalletAddress = preflight.executionWalletAddress || null;
|
|
346
|
+
}
|
|
347
|
+
intentResult.signerMethod = 'managed_wallet_server';
|
|
348
|
+
return intentResult;
|
|
349
|
+
}
|
|
350
|
+
|
|
307
351
|
async function executeManagedWalletRun(ctx, flags, resolved, options) {
|
|
352
|
+
// Read-only actions carry no transaction. Routing them through the managed-wallet execute rail
|
|
353
|
+
// returns an execution envelope instead of the queried data, which silently starves callers that
|
|
354
|
+
// asked for market state (see earnclaw-api HL universe probe regression, 2026-07-25).
|
|
355
|
+
// The wallet address is still resolved for response parity, but a read is never gated on funding.
|
|
356
|
+
if (isReadOnlyAction(resolved)) {
|
|
357
|
+
// Do not swallow lookup/routing errors: writes let them propagate, and reads must fail closed
|
|
358
|
+
// the same way (wallet mismatch, missing identity, failed /execution-wallet).
|
|
359
|
+
const readPreflight = await resolveManagedExecutionPreflight(ctx, flags, resolved, {
|
|
360
|
+
runMode: 'agent_wallet',
|
|
361
|
+
requireFundingCheck: false,
|
|
362
|
+
preflightSeed: options?.managedPreflight,
|
|
363
|
+
});
|
|
364
|
+
// Funding is intentionally skipped for reads (`requireFundingCheck: false`), but routing
|
|
365
|
+
// failures (wallet mismatch, missing identity, failed address seed) must still fail closed.
|
|
366
|
+
if (readPreflight && readPreflight.success === false && !isFundingOnlyPreflightBlock(readPreflight)) {
|
|
367
|
+
const normalized = normalizeIntentResponse({
|
|
368
|
+
success: false,
|
|
369
|
+
status: 'failed',
|
|
370
|
+
mode: 'agent_wallet',
|
|
371
|
+
blockedReason: readPreflight.blockedReason || 'PRECHECK_FAILED',
|
|
372
|
+
blockedStage: readPreflight.blockedStage || 'routing',
|
|
373
|
+
error: readPreflight.error || 'Managed wallet preflight failed',
|
|
374
|
+
preflightBlocked: true,
|
|
375
|
+
executionWalletAddress: readPreflight.executionWalletAddress || null,
|
|
376
|
+
executionWalletPreflight: readPreflight,
|
|
377
|
+
});
|
|
378
|
+
normalized.executionWalletAddress = readPreflight.executionWalletAddress || null;
|
|
379
|
+
normalized.executionWalletPreflight = readPreflight;
|
|
380
|
+
return normalized;
|
|
381
|
+
}
|
|
382
|
+
return runReadOnlyActionViaIntentRail(ctx, flags, resolved, options, readPreflight);
|
|
383
|
+
}
|
|
308
384
|
const skipPreflight = Boolean(options?.skipPreflight);
|
|
309
385
|
let preflight = null;
|
|
310
386
|
if (!skipPreflight) {
|
|
@@ -338,34 +414,12 @@ async function executeManagedWalletRun(ctx, flags, resolved, options) {
|
|
|
338
414
|
const actionCtx = withActionTimeout(ctx, resolved.resolvedAction);
|
|
339
415
|
const response = await apiPost(actionCtx, '/api/v1/agent/execute', payload);
|
|
340
416
|
if (shouldFallbackAgentWalletToExecuteIntent(resolved, response)) {
|
|
341
|
-
const
|
|
342
|
-
runMode: 'agent_wallet',
|
|
343
|
-
hybridWalletMode: options.hybridWalletMode,
|
|
344
|
-
preferDelegationIdentity: options.preferDelegationIdentity,
|
|
345
|
-
});
|
|
346
|
-
const chainId = Number(flags.chainId || 8453);
|
|
347
|
-
const intentResult = await executeThirdfyRun(
|
|
348
|
-
ctx,
|
|
349
|
-
{
|
|
350
|
-
...flags,
|
|
351
|
-
chainId,
|
|
352
|
-
runMode: 'thirdfy',
|
|
353
|
-
userDid: userDid || flags.userDid,
|
|
354
|
-
executionScope: flags.executionScope || 'solo_owner_mirror',
|
|
355
|
-
},
|
|
356
|
-
resolved,
|
|
357
|
-
{ ...options, skipPreflight: true },
|
|
358
|
-
'thirdfy'
|
|
359
|
-
);
|
|
360
|
-
intentResult.mode = 'agent_wallet';
|
|
361
|
-
intentResult.routeFallback = 'execute_intent_read_only';
|
|
362
|
-
intentResult.executionWalletPreflight = preflight;
|
|
417
|
+
const intentResult = await runReadOnlyActionViaIntentRail(ctx, flags, resolved, options, preflight);
|
|
363
418
|
intentResult.executionWalletAddress =
|
|
364
419
|
preflight?.executionWalletAddress ||
|
|
365
420
|
response?.executionWalletAddress ||
|
|
366
421
|
response?.data?.executionWalletAddress ||
|
|
367
422
|
null;
|
|
368
|
-
intentResult.signerMethod = 'managed_wallet_server';
|
|
369
423
|
return intentResult;
|
|
370
424
|
}
|
|
371
425
|
const normalized = normalizeIntentResponse({
|