@thirdfy/agent-cli 0.2.35 → 0.2.36
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 +11 -0
- package/README.md +4 -4
- package/package.json +1 -1
- package/src/runtime/execution/runners.mjs +77 -73
package/CHANGELOG.md
CHANGED
|
@@ -4,6 +4,17 @@ All notable changes to `@thirdfy/agent-cli` are documented here. The format is b
|
|
|
4
4
|
|
|
5
5
|
## [Unreleased]
|
|
6
6
|
|
|
7
|
+
## [0.2.36] - 2026-07-25
|
|
8
|
+
|
|
9
|
+
### Fixed
|
|
10
|
+
|
|
11
|
+
- `--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`).
|
|
12
|
+
- **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`.
|
|
13
|
+
|
|
14
|
+
### Changed
|
|
15
|
+
|
|
16
|
+
- Reads still skip execution-wallet funding checks. Routing and identity preflight still fail closed.
|
|
17
|
+
|
|
7
18
|
## [0.2.35] - 2026-07-25
|
|
8
19
|
|
|
9
20
|
### Fixed
|
package/README.md
CHANGED
|
@@ -40,11 +40,11 @@ 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.36
|
|
44
44
|
|
|
45
|
-
-
|
|
46
|
-
-
|
|
47
|
-
- Reads
|
|
45
|
+
- `--run-mode agent_wallet` market-data reads (`get_hyperliquid_perps_meta` and siblings) keep the live provider payload from `/api/v1/agent/execute`, including stringified `data.result` coercion, so pack callers can parse the Hyperliquid universe again.
|
|
46
|
+
- Execute-intent is only used for managed reads when the execute rail rejects the action. Solo agent_wallet intents no longer replace a successful market-data reply with an empty queue.
|
|
47
|
+
- Reads still skip funding checks; routing and identity preflight still fail closed.
|
|
48
48
|
|
|
49
49
|
Older versions: see [CHANGELOG.md](./CHANGELOG.md) and [GitHub Releases](https://github.com/thirdfy/agent-cli/releases).
|
|
50
50
|
|
package/package.json
CHANGED
|
@@ -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
|
-
//
|
|
421
|
-
//
|
|
422
|
-
//
|
|
423
|
-
//
|
|
424
|
-
|
|
425
|
-
|
|
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:
|
|
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
|
-
|
|
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
|
-
|
|
684
|
-
|
|
685
|
-
|
|
686
|
-
|
|
687
|
-
|
|
688
|
-
|
|
689
|
-
|
|
690
|
-
|
|
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();
|