@thirdfy/agent-cli 0.2.33 → 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 CHANGED
@@ -4,6 +4,15 @@ 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
+
7
16
  ## [0.2.33] - 2026-07-24
8
17
 
9
18
  ### 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.33
43
+ ## What's new in v0.2.34
44
44
 
45
- - `login email` always surfaces `executionWallets` (and `executionWalletsError` when present) and fails closed for `agent_wallet` when execution wallets are missing. Fund the printed execution address, not the owner login wallet.
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
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@thirdfy/agent-cli",
3
- "version": "0.2.33",
3
+ "version": "0.2.34",
4
4
  "description": "Thirdfy Agent CLI for onboarding, governance preflight, execute-intent, and status polling.",
5
5
  "type": "module",
6
6
  "bin": {
@@ -79,15 +79,29 @@ function applyExecutionFallbackHints(normalized, { flags, runMode, resolvedActio
79
79
  return next;
80
80
  }
81
81
 
82
- function shouldFallbackAgentWalletToExecuteIntent(resolved, response) {
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 = action.startsWith('get_') || action.startsWith('search_');
88
+ const readOnlyByName =
89
+ action.startsWith('get_') || action.startsWith('search_') || action.startsWith('list_');
89
90
  const readOnlyByMeta = meta.supportsExecute === false && meta.supportsExecuteIntent !== false;
90
- if (!readOnlyByName && !readOnlyByMeta) return false;
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
- requireFundingCheck: true,
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 userDid = resolveEffectiveUserDid(flags, {
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({