@thirdfy/agent-cli 0.2.35 → 0.2.37

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,23 @@ All notable changes to `@thirdfy/agent-cli` are documented here. The format is b
4
4
 
5
5
  ## [Unreleased]
6
6
 
7
+ ## [0.2.37] - 2026-07-25
8
+
9
+ ### Added
10
+
11
+ - Polymarket provider hints list `get_polymarket_user_positions` under read and order-management actions (Data API open positions; pairs with Thirdfy API action and MCP **0.0.80**).
12
+
13
+ ## [0.2.36] - 2026-07-25
14
+
15
+ ### Fixed
16
+
17
+ - `--run-mode agent_wallet` market-data reads use `/api/v1/agent/execute` again and keep the provider payload on the CLI envelope (`raw` plus coerced `data.result`). Version 0.2.34 routed every managed read through `/execute-intent` to avoid an execution-shaped reply, but solo Hermes `agent_wallet` intents come back as an empty queued envelope with no Hyperliquid universe. Live `/execute` still returns the meta payload (often as a JSON string under `data.result`); the CLI now parses that string so EarnClaw pack parsers can find `universe`. Execute-intent remains the fallback only when the execute rail rejects the action (`does not support execute rail`).
18
+ - **MCP parity:** the same rail fix belongs in `thirdfy-mcp` (`walletExecute`) as **0.0.79**. See workspace rule `cli-mcp-parity` and API docs-dev `cli-mcp-parity.md`.
19
+
20
+ ### Changed
21
+
22
+ - Reads still skip execution-wallet funding checks. Routing and identity preflight still fail closed.
23
+
7
24
  ## [0.2.35] - 2026-07-25
8
25
 
9
26
  ### Fixed
package/README.md CHANGED
@@ -40,11 +40,10 @@ Run without global install:
40
40
  npx @thirdfy/agent-cli --help
41
41
  ```
42
42
 
43
- ## What's new in v0.2.35
43
+ ## What's new in v0.2.37
44
44
 
45
- - Read-only actions return the provider payload directly in every run mode. Market data reads such as `get_hyperliquid_perps_meta` no longer come back wrapped as an execution intent, and `--run-mode self` no longer refuses them.
46
- - Read detection now matches the Thirdfy action catalog, so `fetch_*`, `show_*`, `dogeos_get_*`, and `*_info` actions are recognized as reads.
47
- - Reads are not 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.
45
+ - Polymarket provider hints include `get_polymarket_user_positions` next to open-order reads, so `actions` / provider discovery surfaces Data API position inventory.
46
+ - Pairs with MCP **0.0.80**. Requires the matching Thirdfy API action on the host you call.
48
47
 
49
48
  Older versions: see [CHANGELOG.md](./CHANGELOG.md) and [GitHub Releases](https://github.com/thirdfy/agent-cli/releases).
50
49
 
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@thirdfy/agent-cli",
3
- "version": "0.2.35",
3
+ "version": "0.2.37",
4
4
  "description": "Thirdfy Agent CLI for onboarding, governance preflight, execute-intent, and status polling.",
5
5
  "type": "module",
6
6
  "bin": {
@@ -84,21 +84,68 @@ function isReadOnlyAction(resolved) {
84
84
  return isReadOnlyResolvedAction(resolved);
85
85
  }
86
86
 
87
- // Reads carry no transaction, so wallet balance never blocks them. Every other preflight failure
88
- // (identity, routing, wallet mismatch) still fails closed, same as a write.
89
- const FUNDING_ONLY_PREFLIGHT_BLOCKS = new Set(['INSUFFICIENT_FUNDS', 'INVALID_FUNDING_BALANCE']);
90
-
91
- function isFundingOnlyPreflightBlock(preflight) {
92
- const reason = String(preflight?.blockedReason || '').trim().toUpperCase();
93
- return FUNDING_ONLY_PREFLIGHT_BLOCKS.has(reason);
94
- }
95
-
96
87
  function shouldFallbackAgentWalletToExecuteIntent(resolved, response) {
97
88
  if (!isReadOnlyAction(resolved)) return false;
98
89
  const err = String(response?.error || response?.message || '').toLowerCase();
99
90
  return err.includes('does not support execute rail');
100
91
  }
101
92
 
93
+ // Managed /execute often returns provider payloads as JSON strings under data.result. Pack parsers
94
+ // (and find_value_by_key) need a real object tree, so coerce strings that look like JSON.
95
+ function coerceJsonTreeValue(value) {
96
+ if (typeof value !== 'string') return value;
97
+ const trimmed = value.trim();
98
+ if (!trimmed || (trimmed[0] !== '{' && trimmed[0] !== '[')) return value;
99
+ try {
100
+ return JSON.parse(trimmed);
101
+ } catch {
102
+ return value;
103
+ }
104
+ }
105
+
106
+ function shapeManagedExecuteReadResponse(response) {
107
+ if (!response || typeof response !== 'object') return response;
108
+ const data =
109
+ response.data && typeof response.data === 'object' ? { ...response.data } : response.data;
110
+ if (data && typeof data === 'object' && Object.prototype.hasOwnProperty.call(data, 'result')) {
111
+ data.result = coerceJsonTreeValue(data.result);
112
+ }
113
+ return { ...response, data };
114
+ }
115
+
116
+ function normalizeManagedExecuteResponse(response, { preflight, payload, isRead }) {
117
+ const shaped = isRead ? shapeManagedExecuteReadResponse(response) : response;
118
+ const normalized = normalizeIntentResponse({
119
+ success: Boolean(shaped?.success),
120
+ status: shaped?.success ? 'completed' : 'failed',
121
+ mode: 'agent_wallet',
122
+ txHash: shaped?.txHash || null,
123
+ blockedReason: shaped?.blockedReason || shaped?.data?.blockedReason || null,
124
+ blockedStage: shaped?.blockedStage || shaped?.data?.blockedStage || null,
125
+ error: shaped?.error || null,
126
+ executionWalletAddress:
127
+ shaped?.executionWalletAddress || shaped?.data?.executionWalletAddress || null,
128
+ signerMethod: 'managed_wallet_server',
129
+ idempotencyKey: payload.executionIdempotencyKey || null,
130
+ executionWalletPreflight: preflight,
131
+ amountNormalization:
132
+ shaped?.amountNormalization || shaped?.data?.amountNormalization || null,
133
+ // Reads must keep provider payload on the envelope. EarnClaw packs parse
134
+ // data.raw.data.result (see parse_thirdfy_action_result).
135
+ ...(isRead && shaped?.data !== undefined ? { data: shaped.data } : {}),
136
+ raw: shaped,
137
+ });
138
+ normalized.executionWalletAddress =
139
+ shaped?.executionWalletAddress || shaped?.data?.executionWalletAddress || null;
140
+ normalized.signerMethod = 'managed_wallet_server';
141
+ normalized.executionWalletPreflight = preflight;
142
+ normalized.amountNormalization =
143
+ shaped?.amountNormalization || shaped?.data?.amountNormalization || null;
144
+ normalized.raw = shaped;
145
+ normalized.idempotencyKey = payload.executionIdempotencyKey || null;
146
+ return normalized;
147
+ }
148
+
102
149
  async function runPreflightByMode(runMode, ctx, flags, resolved, options = {}) {
103
150
  const resolvedAction = resolved.resolvedAction;
104
151
  if (runMode === 'agent_wallet') {
@@ -417,44 +464,18 @@ async function runReadOnlyActionViaIntentRail(ctx, flags, resolved, options, pre
417
464
  }
418
465
 
419
466
  async function executeManagedWalletRun(ctx, flags, resolved, options) {
420
- // Read-only actions carry no transaction. Routing them through the managed-wallet execute rail
421
- // returns an execution envelope instead of the queried data, which silently starves callers that
422
- // asked for market state (see earnclaw-api HL universe probe regression, 2026-07-25).
423
- // The wallet address is still resolved for response parity, but a read is never gated on funding.
424
- if (isReadOnlyAction(resolved)) {
425
- // Do not swallow lookup/routing errors: writes let them propagate, and reads must fail closed
426
- // the same way (wallet mismatch, missing identity, failed /execution-wallet).
427
- const readPreflight = await resolveManagedExecutionPreflight(ctx, flags, resolved, {
428
- runMode: 'agent_wallet',
429
- requireFundingCheck: false,
430
- preflightSeed: options?.managedPreflight,
431
- });
432
- // Funding is intentionally skipped for reads (`requireFundingCheck: false`), but routing
433
- // failures (wallet mismatch, missing identity, failed address seed) must still fail closed.
434
- if (readPreflight && readPreflight.success === false && !isFundingOnlyPreflightBlock(readPreflight)) {
435
- const normalized = normalizeIntentResponse({
436
- success: false,
437
- status: 'failed',
438
- mode: 'agent_wallet',
439
- blockedReason: readPreflight.blockedReason || 'PRECHECK_FAILED',
440
- blockedStage: readPreflight.blockedStage || 'routing',
441
- error: readPreflight.error || 'Managed wallet preflight failed',
442
- preflightBlocked: true,
443
- executionWalletAddress: readPreflight.executionWalletAddress || null,
444
- executionWalletPreflight: readPreflight,
445
- });
446
- normalized.executionWalletAddress = readPreflight.executionWalletAddress || null;
447
- normalized.executionWalletPreflight = readPreflight;
448
- return normalized;
449
- }
450
- return runReadOnlyActionViaIntentRail(ctx, flags, resolved, options, readPreflight);
451
- }
467
+ // Market-data reads under agent_wallet must use /execute and keep the provider payload on the
468
+ // envelope (raw + parsed data.result). Routing them only through /execute-intent was the 0.2.34
469
+ // attempt to avoid a burial bug, but solo Hermes agent_wallet intents come back as an empty
470
+ // queued envelope with no universe. Live /execute still returns the Hyperliquid meta payload
471
+ // (often as a JSON string under data.result). Reads skip funding gates; routing still fails closed.
472
+ const isRead = isReadOnlyAction(resolved);
452
473
  const skipPreflight = Boolean(options?.skipPreflight);
453
474
  let preflight = null;
454
475
  if (!skipPreflight) {
455
476
  preflight = await resolveManagedExecutionPreflight(ctx, flags, resolved, {
456
477
  runMode: 'agent_wallet',
457
- requireFundingCheck: true,
478
+ requireFundingCheck: !isRead,
458
479
  preflightSeed: options?.managedPreflight,
459
480
  });
460
481
  if (!preflight.success) {
@@ -490,28 +511,7 @@ async function executeManagedWalletRun(ctx, flags, resolved, options) {
490
511
  null;
491
512
  return intentResult;
492
513
  }
493
- const normalized = normalizeIntentResponse({
494
- success: Boolean(response?.success),
495
- status: response?.success ? 'completed' : 'failed',
496
- mode: 'agent_wallet',
497
- txHash: response?.txHash || null,
498
- blockedReason: response?.blockedReason || response?.data?.blockedReason || null,
499
- blockedStage: response?.blockedStage || response?.data?.blockedStage || null,
500
- error: response?.error || null,
501
- executionWalletAddress: response?.executionWalletAddress || response?.data?.executionWalletAddress || null,
502
- signerMethod: 'managed_wallet_server',
503
- idempotencyKey: payload.executionIdempotencyKey || null,
504
- executionWalletPreflight: preflight,
505
- amountNormalization: response?.amountNormalization || response?.data?.amountNormalization || null,
506
- raw: response,
507
- });
508
- normalized.executionWalletAddress = response?.executionWalletAddress || response?.data?.executionWalletAddress || null;
509
- normalized.signerMethod = 'managed_wallet_server';
510
- normalized.executionWalletPreflight = preflight;
511
- normalized.amountNormalization = response?.amountNormalization || response?.data?.amountNormalization || null;
512
- normalized.raw = response;
513
- normalized.idempotencyKey = payload.executionIdempotencyKey || null;
514
- return normalized;
514
+ return normalizeManagedExecuteResponse(response, { preflight, payload, isRead });
515
515
  }
516
516
 
517
517
  async function executeSelfRun(ctx, flags, resolved, options) {
@@ -680,15 +680,19 @@ async function resolveManagedExecutionPreflight(ctx, flags, resolved, options =
680
680
  try {
681
681
  parsedRawBalance = BigInt(rawBalance);
682
682
  } catch {
683
- return {
684
- success: false,
685
- blockedReason: 'INVALID_FUNDING_BALANCE',
686
- blockedStage: 'sizing',
687
- error: `Execution wallet ${executionWalletAddress || 'unknown'} returned invalid funding token balance.`,
688
- executionWalletAddress,
689
- signerMethod,
690
- fundingTokenBalance: response?.fundingTokenBalance || null,
691
- };
683
+ // Reads pass requireFundingCheck: false; a malformed balance must not block them.
684
+ // Writes still fail closed on unparseable fundingTokenBalance.raw.
685
+ if (options.requireFundingCheck) {
686
+ return {
687
+ success: false,
688
+ blockedReason: 'INVALID_FUNDING_BALANCE',
689
+ blockedStage: 'sizing',
690
+ error: `Execution wallet ${executionWalletAddress || 'unknown'} returned invalid funding token balance.`,
691
+ executionWalletAddress,
692
+ signerMethod,
693
+ fundingTokenBalance: response?.fundingTokenBalance || null,
694
+ };
695
+ }
692
696
  }
693
697
  }
694
698
  const expectedWallet = String(flags.walletAddress || flags.executionWalletAddress || '').trim().toLowerCase();
@@ -247,6 +247,7 @@ export function createProviderHints({ getNegotiatedCapabilitiesCache }) {
247
247
  'get_polymarket_onboarding_plan',
248
248
  'get_polymarket_order',
249
249
  'get_polymarket_user_orders',
250
+ 'get_polymarket_user_positions',
250
251
  ],
251
252
  discoveryFlow: [
252
253
  'get_polymarket_tags or search_polymarket',
@@ -260,6 +261,7 @@ export function createProviderHints({ getNegotiatedCapabilitiesCache }) {
260
261
  'place_polymarket_order',
261
262
  'get_polymarket_order',
262
263
  'get_polymarket_user_orders',
264
+ 'get_polymarket_user_positions',
263
265
  'cancel_polymarket_order',
264
266
  ],
265
267
  setupActions: [